pax_global_header00006660000000000000000000000064145607570600014524gustar00rootroot0000000000000052 comment=df34aef9e6759eb959e78a181a1f107424a9cecb fheroes2-1.0.12+dfsg/000077500000000000000000000000001456075706000142415ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/.clang-format000066400000000000000000000044161456075706000166210ustar00rootroot00000000000000--- AccessModifierOffset: -4 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlinesLeft: Right AlignOperands: true AlignTrailingComments: false AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: true BinPackArguments: true BinPackParameters: true BreakAfterJavaFieldAnnotations: true BreakBeforeBraces: Custom BraceWrapping: AfterClass: true AfterControlStatement: false AfterEnum: true AfterFunction: true AfterNamespace: true AfterStruct: true AfterUnion: true BeforeCatch: true BeforeElse: true SplitEmptyFunction: false SplitEmptyRecord: false SplitEmptyNamespace: false BreakBeforeBinaryOperators: All BreakBeforeTernaryOperators: true BreakConstructorInitializers: BeforeComma BreakStringLiterals: false ColumnLimit: 170 ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: false FixNamespaceComments: false IncludeBlocks: Regroup IncludeCategories: - Regex: '^$' Priority: 1 - Regex: '^<' Priority: 2 - Regex: '^"' Priority: 4 IndentCaseLabels: false IndentWidth: 4 IndentWrappedFunctionNames: true JavaImportGroups: - 'java' - 'android' - 'androidx' - 'org.apache' - 'org.libsdl' - 'com.github.stephenc' KeepEmptyLinesAtTheStartOfBlocks: false MaxEmptyLinesToKeep: 1 NamespaceIndentation: All PenaltyBreakBeforeFirstCallParameter: 19937 PenaltyReturnTypeOnItsOwnLine: 19937 PointerAlignment: Middle ReflowComments: true SortIncludes: true SortUsingDeclarations: true SpaceAfterCStyleCast: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInCStyleCastParentheses: false SpacesInParentheses: true SpacesInSquareBrackets: false Standard: c++17 TabWidth: 4 UseTab: Never --- Language: Cpp --- Language: Java fheroes2-1.0.12+dfsg/.clang-tidy000066400000000000000000000024311456075706000162750ustar00rootroot00000000000000Checks: >- -*, bugprone-*, -bugprone-easily-swappable-parameters, cppcoreguidelines-*, -cppcoreguidelines-avoid-magic-numbers, -cppcoreguidelines-avoid-non-const-global-variables, -cppcoreguidelines-init-variables, -cppcoreguidelines-non-private-member-variables-in-classes, -cppcoreguidelines-owning-memory, -cppcoreguidelines-pro-bounds-array-to-pointer-decay, -cppcoreguidelines-pro-bounds-constant-array-index, -cppcoreguidelines-pro-bounds-pointer-arithmetic, -cppcoreguidelines-pro-type-reinterpret-cast, -cppcoreguidelines-pro-type-union-access, misc-*, -misc-non-private-member-variables-in-classes, modernize-*, -modernize-use-auto, -modernize-use-nodiscard, -modernize-use-trailing-return-type, performance-*, readability-*, -readability-avoid-const-params-in-decls, -readability-braces-around-statements, -readability-function-cognitive-complexity, -readability-identifier-length, -readability-implicit-bool-conversion, -readability-magic-numbers CheckOptions: - key: cppcoreguidelines-special-member-functions.AllowMissingMoveFunctionsWhenCopyIsDeleted value: true - key: cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor value: true - key: readability-redundant-member-init.IgnoreBaseInCopyConstructors value: true fheroes2-1.0.12+dfsg/.gitignore000066400000000000000000000023271456075706000162350ustar00rootroot00000000000000# Prerequisites *.d # Compiled Object files *.slo *.lo *.o *.obj # Precompiled Headers *.gch *.pch # Compiled Dynamic libraries *.so *.dylib *.dll # Compiled Static libraries *.lai *.la *.a *.lib # Executables *.exe *.out *.app # GNU gettext *.pot *.pot~ *.po~ *.po.tmp *.po.tmp~ *.mo ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. *.suo *.sln *.vcxproj.filters *.user *.log *.ilk *.pdb *.tlog *.idb *.iobj *.ipdb *.obj *.pch *.tmp # visual studio directory .vs .vscode # Ignore Heroes of Might & Magic II game files *.agg *.mp2 *.mx2 *.h2c *.hxc *.hs *.smk *.ogg DATA/NETLODR.DAT # build directory build # fheroes2 related files files/save # Never put 'fheroes2' in this list. It breaks SonarQube analysis fheroes2.cfg # demo directory script/demo/demo /demo # Installed packages for Visual Studio VisualStudio/packages # Third-party packages for Android Studio android/app/jni/SDL2/ android/app/jni/SDL2_mixer/ android/sdl2/ # Android Studio files specific to the developer's local configuration android/local.properties android/.idea android/.gradle # Automatically created Android assets android/app/src/main/assets/files/ # Mac magic folders .DS_Store fheroes2-1.0.12+dfsg/CMakeLists.txt000066400000000000000000000167711456075706000170150ustar00rootroot00000000000000########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2022 - 2023 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### cmake_minimum_required(VERSION 3.20) cmake_policy(SET CMP0074 NEW) file(STRINGS ${CMAKE_SOURCE_DIR}/version.txt FHEROES2_VERSION LIMIT_COUNT 1 REGEX "^[0-9]+\.[0-9]+\.[0-9]+$") project(fheroes2 VERSION ${FHEROES2_VERSION} LANGUAGES C CXX) set(CMAKE_CXX_STANDARD 17) include(CMakeDependentOption) include(GNUInstallDirs) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) # # Compile-time options # option(ENABLE_STRICT_COMPILATION "Enable strict compilation mode (turns warnings into errors)" OFF) option(ENABLE_IMAGE "Enable the use of SDL_image (requires libpng)" OFF) option(ENABLE_TOOLS "Enable the build of additional tools" OFF) # Available only on macOS cmake_dependent_option(MACOS_APP_BUNDLE "Create a Mac app bundle" OFF "APPLE" OFF) # Should not be available if we are creating a Mac app bundle cmake_dependent_option(GET_HOMM2_DEMO "Fetch and install the HoMM II demo data" OFF "NOT MACOS_APP_BUNDLE" OFF) set(USE_SDL_VERSION SDL2 CACHE STRING "Version of the SDL library used") set(FHEROES2_DATA ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME} CACHE STRING "Path to the fheroes2 data directory (relative to CMAKE_INSTALL_PREFIX)") # For ComboBox in CMake GUI set_property(CACHE USE_SDL_VERSION PROPERTY STRINGS SDL2) # # Library & feature detection # find_package(${USE_SDL_VERSION} REQUIRED) find_package(${USE_SDL_VERSION}_mixer REQUIRED) find_package(ZLIB REQUIRED) find_package(Threads) # TODO: Modern versions of SDL2* packages provide the CMake config files that define the SDL2::SDL2, SDL2_mixer::SDL2_mixer and SDL2_image::SDL2_image # TODO: imported targets, but older versions do not. To solve this problem, we have to use FindSDL2*.cmake modules from the cmake/ subdirectory and # TODO: define aliases for imported targets provided by these modules. # TODO: remove this alias and module from the cmake/ subdirectory as soon as the distributions start shipping a newer version of SDL_mixer add_library(${USE_SDL_VERSION}_mixer::${USE_SDL_VERSION}_mixer ALIAS ${USE_SDL_VERSION}::Mixer) if(ENABLE_IMAGE) find_package(${USE_SDL_VERSION}_image REQUIRED) find_package(PNG REQUIRED) # TODO: remove this alias and module from the cmake/ subdirectory as soon as the distributions start shipping a newer version of SDL_image add_library(${USE_SDL_VERSION}_image::${USE_SDL_VERSION}_image ALIAS ${USE_SDL_VERSION}::Image) endif(ENABLE_IMAGE) # # Source files # set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) add_subdirectory(src) # # Translation files # if(NOT WIN32) find_program(MAKE_EXECUTABLE make) add_custom_target( translations ALL COMMAND ${MAKE_EXECUTABLE} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/files/lang ) endif(NOT WIN32) # # macOS app bundle # if(MACOS_APP_BUNDLE) add_custom_target( app_bundle_h2d_files ALL COMMAND mkdir -p ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${PROJECT_NAME}.app/Contents/Resources/h2d COMMAND cp *.h2d ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${PROJECT_NAME}.app/Contents/Resources/h2d DEPENDS fheroes2 WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/files/data ) add_custom_target( app_bundle_translations ALL COMMAND mkdir -p ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${PROJECT_NAME}.app/Contents/Resources/translations COMMAND cp *.mo ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${PROJECT_NAME}.app/Contents/Resources/translations DEPENDS translations WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/files/lang ) add_custom_target( run_dylibbundler ALL COMMAND dylibbundler -od -b -x ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${PROJECT_NAME}.app/Contents/MacOS/${PROJECT_NAME} -d ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${PROJECT_NAME}.app/Contents/libs DEPENDS fheroes2 app_bundle_h2d_files app_bundle_translations WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) endif(MACOS_APP_BUNDLE) # # Installation of auxiliary files # if(NOT MACOS_APP_BUNDLE) install( DIRECTORY files/data/ DESTINATION ${FHEROES2_DATA}/files/data FILES_MATCHING PATTERN *.h2d ) install( FILES docs/README.txt LICENSE changelog.txt DESTINATION ${CMAKE_INSTALL_DOCDIR} ) if(GET_HOMM2_DEMO) include(FetchContent) set(FETCHCONTENT_QUIET OFF) FetchContent_Declare( demodata URL https://archive.org/download/HeroesofMightandMagicIITheSuccessionWars_1020/h2demo.zip URL_HASH SHA256=12048c8b03875c81e69534a3813aaf6340975e77b762dc1b79a4ff5514240e3c ) FetchContent_MakeAvailable(demodata) file(COPY ${demodata_SOURCE_DIR}/DATA/ DESTINATION data PATTERN *.*) file(COPY ${demodata_SOURCE_DIR}/MAPS/ DESTINATION maps PATTERN *.*) install( DIRECTORY ${demodata_SOURCE_DIR}/DATA/ DESTINATION ${FHEROES2_DATA}/data FILES_MATCHING PATTERN *.* ) install( DIRECTORY ${demodata_SOURCE_DIR}/MAPS/ DESTINATION ${FHEROES2_DATA}/maps FILES_MATCHING PATTERN *.* ) endif(GET_HOMM2_DEMO) if(WIN32) install( FILES script/demo/download_demo_version.bat script/demo/download_demo_version.ps1 DESTINATION ${CMAKE_INSTALL_DOCDIR}/demo ) install( FILES script/homm2/extract_homm2_resources.bat script/homm2/extract_homm2_resources.ps1 DESTINATION ${CMAKE_INSTALL_DOCDIR}/homm2 ) install( FILES script/homm2/resource_extraction_toolset.bat script/homm2/resource_extraction_toolset.ps1 DESTINATION ${CMAKE_INSTALL_DOCDIR}/homm2 ) else(WIN32) install( DIRECTORY files/lang/ DESTINATION ${FHEROES2_DATA}/files/lang FILES_MATCHING PATTERN *.mo ) install( FILES script/demo/download_demo_version.sh PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ WORLD_EXECUTE WORLD_READ DESTINATION ${CMAKE_INSTALL_DOCDIR}/demo ) install( FILES script/homm2/extract_homm2_resources.sh PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ WORLD_EXECUTE WORLD_READ DESTINATION ${CMAKE_INSTALL_DOCDIR}/homm2 ) install( FILES script/packaging/common/fheroes2.metainfo.xml DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/metainfo ) install( FILES src/resources/fheroes2.png DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps ) install( FILES script/packaging/common/fheroes2.desktop DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications ) endif(WIN32) endif(NOT MACOS_APP_BUNDLE) fheroes2-1.0.12+dfsg/CONTRIBUTING.md000066400000000000000000000022261456075706000164740ustar00rootroot00000000000000## Contribution and Development This repository is a place for everyone. If you want to contribute, please read more [**here**](https://github.com/ihhub/fheroes2/wiki/F.A.Q.#q-how-can-i-contribute-to-the-project). To build the project from source, please follow [**this guide**](docs/DEVELOPMENT.md). [![Build Status](https://github.com/ihhub/fheroes2/actions/workflows/push.yml/badge.svg)](https://github.com/ihhub/fheroes2/actions) [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=ihhub_fheroes2&metric=bugs)](https://sonarcloud.io/dashboard?id=ihhub_fheroes2) [![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=ihhub_fheroes2&metric=code_smells)](https://sonarcloud.io/dashboard?id=ihhub_fheroes2) [![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=ihhub_fheroes2&metric=duplicated_lines_density)](https://sonarcloud.io/dashboard?id=ihhub_fheroes2) To assist with the graphical asset efforts of the project, please look at our [**graphical artist guide**](docs/GRAPHICAL_ASSETS.md). If you would like to help translating the project, please read the [**translation guide**](docs/TRANSLATION.md). fheroes2-1.0.12+dfsg/LICENSE000066400000000000000000000432541456075706000152560ustar00rootroot00000000000000 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. fheroes2-1.0.12+dfsg/Makefile000066400000000000000000000067411456075706000157110ustar00rootroot00000000000000########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2021 - 2023 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### # Options: # # FHEROES2_STRICT_COMPILATION: build in strict compilation mode (turns warnings into errors) # FHEROES2_WITH_DEBUG: build in debug mode # FHEROES2_WITH_ASAN: build with UB Sanitizer and Address Sanitizer (small runtime overhead, incompatible with FHEROES2_WITH_TSAN) # FHEROES2_WITH_TSAN: build with UB Sanitizer and Thread Sanitizer (large runtime overhead, incompatible with FHEROES2_WITH_ASAN) # FHEROES2_WITH_IMAGE: build with SDL_image (requires libpng) # FHEROES2_WITH_TOOLS: build additional tools # FHEROES2_MACOS_APP_BUNDLE: create a Mac app bundle (only valid when building on macOS) # FHEROES2_DATA: set the built-in path to the fheroes2 data directory (e.g. /usr/share/fheroes2) TARGET := fheroes2 PROJECT_VERSION := $(file < version.txt) .PHONY: all bundle clean all: $(MAKE) -C src $(MAKE) -C files/lang ifndef FHEROES2_MACOS_APP_BUNDLE @cp src/dist/$(TARGET) . endif bundle: ifdef FHEROES2_MACOS_APP_BUNDLE @mkdir -p "src/dist/$(TARGET).app/Contents/Resources/translations" @mkdir -p "src/dist/$(TARGET).app/Contents/Resources/h2d" @mkdir -p "src/dist/$(TARGET).app/Contents/MacOS" @cp ./src/resources/fheroes2.icns "src/dist/$(TARGET).app/Contents/Resources" @cp ./files/lang/*.mo "src/dist/$(TARGET).app/Contents/Resources/translations" @cp ./files/data/*.h2d "src/dist/$(TARGET).app/Contents/Resources/h2d" @sed -e "s/\$${MACOSX_BUNDLE_EXECUTABLE_NAME}/$(TARGET)/" -e "s/\$${MACOSX_BUNDLE_ICON_FILE}/fheroes2.icns/" -e "s/\$${MACOSX_BUNDLE_GUI_IDENTIFIER}/com.fheroes2.$(TARGET)/" -e "s/\$${MACOSX_BUNDLE_BUNDLE_NAME}/$(TARGET)/" -e "s/\$${MACOSX_BUNDLE_BUNDLE_VERSION}/$(PROJECT_VERSION)/" -e "s/\$${MACOSX_BUNDLE_SHORT_VERSION_STRING}/$(PROJECT_VERSION)/" ./src/resources/Info.plist.in > "src/dist/$(TARGET).app/Contents/Info.plist" @mv "src/dist/$(TARGET)" "src/dist/$(TARGET).app/Contents/MacOS" @dylibbundler -od -b -x "src/dist/$(TARGET).app/Contents/MacOS/$(TARGET)" -d "src/dist/$(TARGET).app/Contents/libs" @cp -R "src/dist/$(TARGET).app" . endif clean: $(MAKE) -C src clean $(MAKE) -C files/lang clean @rm -f ./$(TARGET) @rm -rf ./$(TARGET).app fheroes2-1.0.12+dfsg/Makefile.switch000066400000000000000000000044131456075706000172030ustar00rootroot00000000000000########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2021 - 2023 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### # Options: # # FHEROES2_STRICT_COMPILATION: build in strict compilation mode (turns warnings into errors) # FHEROES2_WITH_DEBUG: build in debug mode # FHEROES2_DATA: set the built-in path to the fheroes2 data directory (e.g. /usr/share/fheroes2) TARGET := fheroes2 MAKE := make PROJECT_TITLE := fheroes2 PROJECT_AUTHOR := fheroes2 resurrection team PROJECT_VERSION := $(file < version.txt) PATH := $(PATH):/opt/devkitpro/portlibs/switch/bin/:/opt/devkitpro/tools/bin/ .PHONY: all clean all: $(MAKE) -f Makefile -C src PLATFORM=switch @cp src/dist/$(TARGET) $(TARGET).elf nacptool --create "$(PROJECT_TITLE)" "$(PROJECT_AUTHOR)" "$(PROJECT_VERSION)" $(TARGET).nacp elf2nro $(TARGET).elf $(TARGET).nro --icon=files/images/platform/switch/icon.jpg --nacp=$(TARGET).nacp clean: $(MAKE) -f Makefile -C src clean @rm -rf $(TARGET).elf $(TARGET).nacp $(TARGET).nro fheroes2-1.0.12+dfsg/Makefile.vita000066400000000000000000000060331456075706000166450ustar00rootroot00000000000000########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2021 - 2023 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### # Options: # # FHEROES2_STRICT_COMPILATION: build in strict compilation mode (turns warnings into errors) # FHEROES2_WITH_DEBUG: build in debug mode # FHEROES2_DATA: set the built-in path to the fheroes2 data directory (e.g. /usr/share/fheroes2) TARGET := fheroes2 MAKE := make PROJECT_TITLE := fheroes2 PROJECT_TITLEID := FHOMM0002 .PHONY: all clean $(TARGET).vpk param.sfo $(TARGET).elf translations all: package package: $(TARGET).vpk $(TARGET).vpk: eboot.bin param.sfo translations vita-pack-vpk -s param.sfo -b eboot.bin \ --add files/images/platform/psv/sce_sys/icon0.png=sce_sys/icon0.png \ --add files/images/platform/psv/sce_sys/livearea/contents/bg.png=sce_sys/livearea/contents/bg.png \ --add files/images/platform/psv/sce_sys/livearea/contents/startup.png=sce_sys/livearea/contents/startup.png \ --add files/images/platform/psv/sce_sys/livearea/contents/template.xml=sce_sys/livearea/contents/template.xml \ --add files/data=files/data \ --add files/lang/vita_temp=files/lang \ $(TARGET).vpk @rm -r files/lang/vita_temp translations: $(TARGET).elf $(MAKE) -C files/lang mkdir -p files/lang/vita_temp cp files/lang/*.mo files/lang/vita_temp eboot.bin: $(TARGET).velf vita-make-fself $(TARGET).velf eboot.bin param.sfo: vita-mksfoex -s TITLE_ID="$(PROJECT_TITLEID)" "$(PROJECT_TITLE)" param.sfo $(TARGET).velf: $(TARGET).elf arm-vita-eabi-strip -g $< vita-elf-create $< $@ $(TARGET).elf: $(MAKE) -f Makefile -C src PLATFORM=vita @cp src/dist/$(TARGET) $(TARGET).elf clean: $(MAKE) -f Makefile -C src clean $(MAKE) -C files/lang clean @rm -rf $(TARGET).velf $(TARGET).elf $(TARGET).vpk eboot.bin param.sfo fheroes2-1.0.12+dfsg/README.md000066400000000000000000000105711456075706000155240ustar00rootroot00000000000000# fheroes2 **fheroes2** is a recreation of the Heroes of Might and Magic II game engine. This open source multiplatform project, written from scratch, is designed to reproduce the original game with significant improvements in gameplay, graphics and logic (including support for high-resolution graphics, improved AI, numerous fixes and user interface improvements), breathing new life into one of the most addictive turn-based strategy games. You can find a complete list of all of our changes and enhancements in [**its own wiki page**](https://github.com/ihhub/fheroes2/wiki/Features-and-enhancements-of-the-project).

## Download and install Please follow the [**installation guide**](docs/INSTALL.md) to download and install fheroes2. [![Github Downloads](https://img.shields.io/github/downloads/ihhub/fheroes2/total.svg)](https://github.com/ihhub/fheroes2/releases) ## Copyright All rights for the original game and its resources belong to former The 3DO Company. These rights were transferred to Ubisoft. We do not encourage and do not support any form of illegal usage of the original game. We strongly advise to purchase the original game on [**GOG**](https://www.gog.com) or [**Ubisoft Store**](https://store.ubi.com) platforms. Alternatively, you can download a free demo version of the game. Please refer to the [**installation guide**](docs/INSTALL.md) for more information. ## License This project is licensed under the [**GNU General Public License v2.0**](https://github.com/ihhub/fheroes2/blob/master/LICENSE). Initially, the project was developed on [**sourceforge**](https://sourceforge.net/projects/fheroes2/). ## Contribution and Development This repository is a place for everyone. If you want to contribute, please read more [**here**](https://github.com/ihhub/fheroes2/wiki/F.A.Q.#q-how-can-i-contribute-to-the-project). To build the project from source, please follow [**this guide**](docs/DEVELOPMENT.md). [![Build Status](https://github.com/ihhub/fheroes2/actions/workflows/push.yml/badge.svg)](https://github.com/ihhub/fheroes2/actions) [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=ihhub_fheroes2&metric=bugs)](https://sonarcloud.io/dashboard?id=ihhub_fheroes2) [![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=ihhub_fheroes2&metric=code_smells)](https://sonarcloud.io/dashboard?id=ihhub_fheroes2) [![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=ihhub_fheroes2&metric=duplicated_lines_density)](https://sonarcloud.io/dashboard?id=ihhub_fheroes2) To assist with the graphical asset efforts of the project, please look at our [**graphical artist guide**](docs/GRAPHICAL_ASSETS.md). If you would like to help translating the project, please read the [**translation guide**](docs/TRANSLATION.md). ## Donation We accept donations via [**Patreon**](https://www.patreon.com/fheroes2), [**PayPal**](https://www.paypal.com/paypalme/fheroes2) or [**Boosty**](https://boosty.to/fheroes2). All donations will be used only for the future project development as we do not consider this project as a source of income by any means. [![Donate](https://img.shields.io/badge/Donate-Patreon-green.svg)](https://www.patreon.com/fheroes2) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/paypalme/fheroes2) [![Donate](https://img.shields.io/badge/Donate-Boosty-green.svg)](https://boosty.to/fheroes2) ## Contacts Follow us on social networks: [**Facebook**](https://www.facebook.com/groups/fheroes2) or [**VK**](https://vk.com/fheroes2). We also have a [**Discord**](https://discord.gg/xF85vbZ) server to discuss the development of the project. [![Facebook](https://img.shields.io/badge/Facebook-blue.svg)](https://www.facebook.com/groups/fheroes2) [![VK](https://img.shields.io/badge/VK-blue.svg)](https://vk.com/fheroes2) [![Discord](https://img.shields.io/discord/733093692860137523.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/xF85vbZ) ## FAQ You can find answers to the most commonly asked questions on our [**F.A.Q. page**](https://github.com/ihhub/fheroes2/wiki/F.A.Q.). fheroes2-1.0.12+dfsg/changelog.txt000066400000000000000000004077431456075706000167500ustar00rootroot00000000000000version 1.0.12 (07 February 2024) - update the Russian translation - update the Spanish translation - correct icons alignment on the hero meeting screen - update the Polish translation - fix the retaliation of archers attacked by an ally under the influence of Berserker or Hypnotize spells, improve the AI usage of Hypnotize spell in general - fix incorrect Artesian Spring evaluation by the AI - make rules for AI surrender and retreat more unified - fix Oasis object passability - always offer one native hero at the beginning of a new week - puzzle and View World dialogs rendering improvements - implement Landscape Miscellaneous (non-action) objects placing in Editor - fix OKAY button translation for Interface Settings dialog after any setting change - make the AI not to know what objects contain - fix issue when Blind or Paralyze has been applied to a hypnotized unit during its turn - implement Adventure (action) Miscellaneous objects placing in Editor - tune the AI performance at different difficulty levels - add surrender cost to battle result dialog - improve overall image processing performance - update spell points, formations and artifacts descriptions - add infos on the cost of surrender - allow AI units covering an archer with the AREA_SHOT capability to attack neighboring units on their own - use proper name for PoL campaign in High Scores - use CP1257 code page for Lithaunian - offer the Wisdom skill to the heroes of "magic" classes on a mandatory basis at least once every three level-ups - implement Mountains, Rocks and Trees placing in the Editor - make the AI attack human heroes that have a greater chance to win than the AI heroes - implement Power-ups object placement in the Editor - allow AI units that ignore retaliatory strikes to attack neighboring enemy units while covering archers - avoid visiting Oasis and Watering Hole objects by AI heroes if they bring no benefit - make AI hero behavior depend on the time spent on the map - improve fog discovery by the AI heroes - adjust text in the skill popup dialog - implement Mines placing in the Editor - improve AI hero interaction with Witch's Hut object - allow the AI-controlled hero to surrender if his kingdom has sufficient gold reserves - add text width-adapted CANCEL button, change to StandardWindow for Resolution, Language, Adventure, File and Scenario Info dialogs - update Hungarian translation - add dwellings into the Editor - fix rare fog of war rendering issues - allow AI heroes to surrender if the conditions of retreat have been met, but it is impossible to retreat - implement the AI logic to transfer the slowest troops to the garrison at the end of the turn to try to get a movement bonus on the next turn - mark fheroes2 as DPI-aware on Windows - speed up image loading - speed up file loading and reduce memory usage on the Adventure Map - improve the AI logic for pay-to-hire dwellings on the Adventure Map - improve the AI heuristics of covering shooters when using defensive tactics - update buttons and add popup windows for them in Battle Only mode - use the "cautious offensive" tactics for AI only against melee-only armies without the distant attack potential (including damage spells) - update the Swedish translation - fix heroes with no names - add base code to load new map format files - use singular Mine form for the corresponding object - fix inaccurate Jail object removal - add missing rules for the plural in Hungarian language - restrict secondary skill name and level to the UI area - fix too wide text in quick hero info dialog - hide swap army/artifact buttons in the Battle Only mode - update several text descriptions - add Campfire object in the Editor - take into account SHIFT and CAPS LOCK being used at the same time - make radar rendering optimization - fix the AI spell damage estimation logic for units immune to the given spell - rework editor instrument groups: add new buttons, rework instrument panel, add evil interface - fix multiple spellbook-related issues in the Battle Only mode - update French translation - show an error when it is not possible to save a game on a hard disk version 1.0.11 (23 December 2023) - improve the logic of AI defensive tactics - add Tree of Knowledge claimed and cost info to quick info dialog - add a different object popup window logic for the Editor - implement town and castle placing in the Editor - update the Spanish translation - update the French translation and add missing offset for an A with accent - update Bulgarian translation - increase width and height of the Campaign Difficulty selection dialog - make speed of fade-in & fade-out animations for AI-controlled heroes dependent on the AI speed setting - speed up battle pathfinder logic - update Spanish translation - add Spanish and Portuguese button font letters - fix invalid focus update after AI hero battle - update Danish translation - speed up the original map format reading - fix a crash with castle overlapping inside a map - add basic logic to load and save maps through Editor UI - add text rendering within a given ROI - do not show quick info for hidden buttons inside castle building info dialog - add missing AI team alliance to Evil campaign's 6th scenario - update Swedish translation - update Hungarian translation - update Vietnamese Translation - fix update of the morale/luck indicator when changing the hero in the Battle Only mode - add base map file format for the Editor - add Hotkeys categories for the future UI improvements - fix scroll location on file deletion - rework Battle Only mode - add cell selection in Details mode for the Editor - speed up map loading for New Game - allow application closure by close button if no resources available - fix multi-font text offset calculation - properly evaluate potential attack positions for wide units - correctly manage touch device events - add ability to place Ocean Objects within the Editor version 1.0.10 (18 November 2023) - update Russian translation - rework Town Portal dialog - add artifact placement in the Editor - make Oracle dialog use selected interface mode (Good/Evil) - add treasure placement in the Editor - update hero status after digging on the Adventure Map - fix player names overlapping in Oracle and Thieves' Guild dialogs - fix dialog frames not being consistent for the same size windows - speed up monochrome cursors loading - fix rendering issue in Kingdom Overview dialog while dismissing a hero - fix castle and hero lists' updates after dismissing a hero in Kingdom Overview dialog - fix invalid attack positions from moat during battle - add support for right SHIFT, CTRL and ALT hotkeys - AI takes into account the spell immunity when calculating the effectiveness of mass damage spells - update Hungarian translation - fix incorrect AI estimation for moves during battle when a battlefield contains objects - update Vietnamese translation - fix several issues with loading ICN resources - add support for the future new heroes - fix the logic of purchasing a boat in a shipyard - increase the priority of those enemy units that have not yet got their turn, for proper AI decision making - add hero action button image - fix spelling in campaign text - display 0 shots during battle for archers with no shots left - fix random generator in normal and auto-combat modes - fix castle dialog rendering issues - adjust welcome window text at the first game launch - teach AI to use the marketplace to accelerate the development of their castles - update German translation - implement erase tool in the Editor - fix recruit dialog's MIN/MAX buttons when switching between creature upgrades - update Swedish translation - show proper Quick Info window for a hero during battle - fix troops' sprites overlapping in quick info - prioritize hiring those heroes whose portraits are not used by scenario specific heroes version 1.0.9 (11 October 2023) - add "tent visited" info to barrier quick info dialog - update Vietnamese translation - rework Hot-Keys dialog - fix combat victory window creature position - Town Gate and Town Portal spells: use depersonalized wording - add an ability to place heroes in the Editor - add an ability to place Random Monsters in the Editor - fix rendering of very long scenario names in the campaign briefing dialog - disable the not implemented Random Map option in the Editor - update German translation - make a long press on the "continue movement" button to reset a hero's path - use the "continue movement" button to interact with an object a hero is standing on - make Grass the default terrain for new maps in the Editor - restrict heroes' meetings and army merging on Easy difficulty - AI should avoid splitting troops on Easy difficulty - add functionality to place monsters in the Editor - add a mirror image check during spell evaluation - teach battle AI to use shield spells - implement streams' placing in the Editor - adjust AI combat spell heuristics - fix rendering of long text in the Hero screen status bar - update Ukrainian translation - implement roads' placing in the Editor - fix the AI first strike logic - update Hungarian translation - fix battle AI protecting paralyzed archers - properly display player's name in the New Game window - fix memorizing the position in the map list when changing the map size filter - adjust the position of luck and morale icons in the hero info window - update the followers window to support text mode - fix the logic of ALWAYS_RETALIATE regarding paralyzed units - add historical changes support for the Editor - add the ability to confirm actions in battle for touch devices - update Russian translation - fix Editor Main Menu hotkeys - update Swedish translation - speed up overall rendering for cycling animation - do not render text beyond the info and status bars during battles - rework game settings on the Adventure Map - remove double spaces in translatable texts - update Bulgarian translation - update Slovak translation - update Czech translations - update Danish translation - use Home and End hotkeys to navigate among items within a list - always use the nearest cell to attack the nearby unit if a monster is under the Berserker spell - fix an assertion when a hero starts moving and a player immediately mouse left clicks on the Adventure Map - improve path calculations by AIWorldPathfinder in planning mode - Wand of Negation should grant immunity against the Mass Dispel spell version 1.0.8 (11 September 2023) - fix number placeholders not being replaced in campaign spell bonus texts - fix spell rendering issues when words are divided in the middle - fix ? symbols appearing in single-line texts - generate vertical high scores buttons - display missing resource dialog for at least 5 seconds - show only skills that the hero does not already have in battle only skill selection list - show highscores after multiplayer game - improve the logic of kiting enemies with archers - update Vietnamese translation - update Hungarian Translation - adjust the description of the Eagle Eye skill - fix an empty gap in between lines in buttons font - update Russian translation - translate disabled buttons and reset them when changing languages - make vertical buttons translatable - implement terrain transitions in Editor - rework the Save/Load game dialog to allow variable dialog size and support Evil interface - change Army Order to Turn Order - Add Czech to original resource exception - do not close File options menu on cancelling load game dialog - fix foreign pixels on snow battlefield objects - do not try to scout the area if the hero is in jail and gets an artifact assigned using the map editor - update Slovak translation - cancel Hut of Magi reveal area animation when a player clicks any button - show the "You have been eliminated from the game" message in the multiplayer game - add small skill icons for Xanadu visit dialog - fix invalid logic for some objects on water - update Polish translation - speed up map, translation and save files loading - fix missing hero and town name in dialogs for non-English languages - fix Load/Save file title position and color - change Shipyard construction message - expand clickable area for settings in the Main Menu - shift text in Surrender window - show ... in Load/Save File dialog for long names - fix monster name position in monster info dialog - speed up rendering for specific buttons - fix multiple rendering issues in the Kingdom Overview dialog - adjust text for Quick Info dialogs - improve fading animation using Redmean color distance calculation - limit Dimension Door spell usage for AI heroes on lower difficulties - center text in hero battle info window - add A and O with diaereses to CP1252 button font - fix size of the button letter Æ - improve Mage guild status message for learnt spells - add "New Month!" title for new month - add "Victory!" title to victory dialog - update Danish translation - update Ukrainian translation - allow early exit from puzzle map during reveal animation - remember map filter selection until you quit the game - improve the distance estimation when an AI-controlled hero passes through objects on the map - update Swedish translation - AI heroes should decline battles if passing by - implement basic terrain draw in Map Editor version 1.0.7 (15 August 2023) - properly update uncovered fog with all hero actions - fix an issue when AI hero "jumps back" during the fog discovery - AI heroes should not know what exactly a Treasure or Sea Chest contains - do not play simultaneous walk/fly sounds for battle speed 9 and 10 - do not handle an event on the map twice - fix AI estimation of the castle garrison strength for priority tasks - rework "select from list" dialogs and also make all elements within dialogs right clickable - battle AI should switch to offense mode instead of passing turns - update Russian translation - add the ability to export and import save files in Android Toolset application - make many in-game buttons translatable - fix Army info button in level up dialog - update Ukrainian translation - update German translation - add Summon Boat spell support for AI - rephrase various game dialogs - add a dialog to confirm auto combat - add "Quit" title to quit dialog - fix Zounds grammar - update Norwegian Translation - the Shield spell should not affect the damage of the castle towers - apply AI growth bonus only on first AI player turn - show "cast down" animation for heroes on the battlefield - apply Cure & Mass Cure spells only if the target unit is either damaged or has negative magical effects - fix hero last frame delay after the spell casting animation - speed up several spell animations during battle - fix missing first animation frame of heroes on the battlefield - change heroes animation speed on the battlefield - fix puzzle screen exit button theme - reduce CPU usage while rendering castle's Well dialog - fix campaign buttons' rendering issues - make campaign buttons translatable - update Slovak translation - update Danish translation - update Swedish translation - disable Campaign button if no campaign resources are present - improve AI pathfinding logic regarding protected and guarded tiles - avoid engine execution with corrupted video files - add ability to select instrument brush and brush size in Editor UI - make thieves guild adapt background to chosen interface - fix a possible crash in Android toolset application version 1.0.6 (15 July 2023) - allow to lower the campaign difficulty during playthrough - update Russian translation - reduce CPU usage while rendering High Score dialog - add some missing horizontal buttons to translations - fix missing redraw in Battle Only dialog after cancelling hero selection - fix last hotkey not showing up in the hotkey dialog - add Dismiss hotkey to hero screen - create basic Map Editor GUI - update Polish translation - add checks for object reachability when calculating their value for AI heroes - fix AI logic for visiting Shrines and Arena - correct spell value estimation for AI heroes - fix a case when a dead AI hero was trying to reinforce army in a castle - update priorities for AI heroes based on battle outcome and fog uncover events - rework the spell generation in castles' Mage Guilds - update Spanish translation - speed up Armageddon spell rendering - fix invalid pixels in multiple images from the original resources - speed up resource loading - update Slovak translation - do Main Menu fade-in before showing first game run - fix the fade effect on the "View World" screen - properly restore environment sounds and terrain music after opening a castle dialog from the Kingdom Overview screen - fix cases when false symbols appear after the blinking cursor in text input dialog - rework game credits to keep aspect ratio - add extra windows and greying out conditions for Summon Boat spell - fix several button text placements and button press rendering version 1.0.5 (14 June 2023) - improve AI castle defensive strategy on Adventure Map - fix rating in campaign congratulations text in win video - update Danish translation - adjust battlefield Idle and Flag animation speed and avoid setting 0 animation delay for battlefield actions - show the currently selected bonus in the campaign scenario UI if the campaign scenario is already in progress - update Polish translation - add shadows for the Virtual Keyboard buttons - fix okay button not being redrawn in battle options dialog - improve army distribution in castles for AI players - make the text input cursor blink and add the ability to set the cursor position anywhere in the edited string - move jobless AI heroes into castles on a last day of a week - fix monster upgrade logic when AI was wasting resources for nothing - do not check whether a hero can cast a spell if spellcasting is prohibited in principle - fix AI heroes behavior ignoring empty castles with commanders - speed up AI decision making on Adventure Map - fix Magellan's Maps visit by AI heroes when discovered objects were not being added for exploration - fix missing logic for AI heroes to meet each other in the middle of a turn - reduce unit growth bonus for AI - do not play the same kill/wince sounds simultaneously - allow AI heroes to retreat when the kingdom has full house of heroes and when there are enough resources on the next turn - fix a crash when AI heroes capture a dwelling protected by monsters - add Editor Main Menu (only for development builds) - fix the Skeleton passability - fix the logic for defeated heroes in campaign scenarios - fix the logic of recognizing objects guarded by neighboring monsters - update hero's path after inspecting a castle - add Evil interface version of Monster recruitment dialog - update Bulgarian translation - add screen fade effect for transiting between game sections - add a check to avoid a crash for corrupted game resources - fix the rendering of the number of units during the castle siege - update Russian translation - speed up battlefield rendering - fix incorrect spell efficiency display for resurrection spells - set music type to MIDI if no MUSIC directory exist - fix cases with dropping audio volume when MUSIC directory is not present - fix crashes for Android devices due to using a buggy third-party package - fix cases when AI ignores some objects during path evaluation - use 22050 sound sample rate for PS Vita to fix high CPU load - add diacritic signs to CP-1250 button font - update Slovak translation - update Ukrainian translation - fix Android version for Android 7 or lower devices - do not reset castle or heroes scrollbars when hero moves version 1.0.4 (13 May 2023) - add Polish and Russian languages support for Virtual Keyboard - fix absence of delay after animating creature wince and before making next move - make haunt, set elemental guardian, identify hero and visions spells gray out when cannot be used - do not stop hero's movement after the first Dimension Door jump - do not show that enemy town or castle has no garrison if kingdom has no Thieves' Guilds - fix idle animation lag while casting any spell - fix creature idle animation freeze during Blind / Paralyze spell casting - fix an issue that caused an AI-controlled hero standing next to Stone Liths to block the path of another AI-controlled hero standing on these Stone Liths - add checks to detect corrupted data resources - improve the resource value estimation algorithm for AI - fix logic and reward of Daemon Cave object for AI - add an ability to cancel waiting for the end of the pre-battle sound - update Polish translation - create small and large obstacles on the battlefield at the same time - do not make an empty text line at the end of phrase and fix text rendering in a wider area than given maximum width - fix Battlefield animations freeze while waiting for Good Luck sound to end - add popup windows for monster dialog buttons - fill main menu with background when we do not stretch the main menu main image - update Russian translation - fix not returning to load menu while switching between campaign scenarios - fix Shipwreck Survivor bonuses - fix AI pathfinding when Spell Scroll was considered as an artifact which requires to defeat an army - rework logic for all Adventure Map objects' metadata - fix adventure map fog reveal on campaign scenario restart and when loading a Hot Seat save made in the first game day - adjust spell info message when hero has zero (0) mana - fix calculation of castle towers strength for AI - improve dialog titles related to losing the game - update Bulgarian translation - strategic AI economy update - AI should check for castles and funds before retreat - fix software cursor freezes during certain fading animations - rework the search algorithm for available scaled resolutions - show map ratings in victory video, add subtitles and color fading animation - add extra checks to prevent loading renamed MX2 maps on demo version - add "End Turn" title to dialog when heroes can move and you are ending your turn - catapult and castle towers should still act, even if there are no troops in the respective armies capable of taking any action - update Vietnamese translation - fix the behavior of Berserk and Hypnotize spells - add language support for highscores - expand wide screen support - properly handle absolute and relative paths in the CDDrive registry key for Windows scripts - update Danish translation - update Swedish translation - reset battlefield grid cursor position after using the spell book - fix a crash on deleting last save file - update Czech translation - update Slovak translation - update Ukrainian translation - fix recurring global events - redraw the status window after the amount of kingdom resources has changed at the beginning of the day - do not render hero/boat over the bottom map border - add "New week!" title to dialog showing start of a new week version 1.0.3 (11 April 2023) - update German translation - update Norwegian translation - preparations for Google Play Market release - add missing frame for hero information dialog when it is opened from hero meeting dialog - improve the destination indication for Teleport spell - translate more UI buttons within the game - fix endless jumping AI hero behavior while using Dimension Door spell - add Virtual Keyboard open button to Save Game and Player Name dialogs - add values to Morale and Luck stats within artifact description - show approximate information about the mine guardians to a non-owner player - adjust number of resources subtracted in event messages - fix projectile render position when hitting the target creature - reduce AI obsession with Wells - optimize object priority logic for AI heroes to prioritize more important locations - remove path icon after a hero/boat makes a turn, not before - show mirrored monster image in army order - make hotkey names translatable - improve AI fog discovery logic - fix a case when a hero has the same secondary skill but different levels of it set by an author of a map - add gold missing in Statue and Dungeon building description - add Vietnamese translation - fix the number of regenerated spell points with the Mysticism skill - focus on the AI hero before he disappears due to boarding a boat or using a Dimension Door or Town Portal spell - add legacy game detection on Windows x64 - fix clicks in Android MIDI music - speed up Adventure Map rendering - fix AI hero animation being interrupted while moving inside a fog for the current player - fix Alchemist Lab flag and Ghosts from Abandoned Mine rendering while rest of the object is not visible on the screen - fix boat's shadow being rendered over a boat while sailing diagonally - adjust recruitment window position on castle well and kingdom overview - apply the commanding hero/captain bonuses to the castle towers - fix the number of raised skeletons by Necromancy after battle - update Polish translation - fix incorrect number of Ghosts present in Abandoned and Haunted Mines - add Slovak translation - properly handle the situation when wandering monsters and an AI hero eliminate each other - update battle log with special abilities of Vampire Lords and Ghosts - update Belarusian translation - add numeric speed of monsters in information window - fix quick info for non-action objects when their action tile is under fog - block Chain Lightning if first target unit resists the spell - fix cases when Road sprite is drawn over a hero - fix Hero shadow rendering on Adventure Map - update Russian translation - show Spell Points for spells in Magic Book popup dialogs - fix necromancy and calculation of casualties with mirrors - properly track the Mirror Image spell duration - update documentation about Android - update Czech translation - update Swedish translation version 1.0.2 (12 March 2023) - fix display of Haunted Mines on mini-map and in View World - update the Norwegian translation - allow resurrected units to act on the current turn if they have not yet had their turn - add missing part of Captain's Quarter in Sorceress Town - allow to resurrect Elementals - fix the battle casualty report when non-true Resurrect spell was used during battle - redraw hero icons to display movement bar change before action dialog box is shown - fight the guardians on dwellings on Adventure Map only once per game - change the logic of reinforcement of the guest hero's army during the castle defence - do not show Dragon City on mini-map - summon boat spell will only summon friendly boats - allow the player to refuse to level up in the Tree of Knowledge even if this level up is for free - add virtual keyboard support for touch devices - grey out Summon Boat, Town Gate and Town Portal spells if they cannot be used - clean up AI priority tasks if attacker was killed - update sounds used while visiting dwellings on Adventure Map - add logic of AI heroes using town portal - add missing symbols to button font - update Polish translation - fix high CPU usage during battle while using highest animation speeds - fix battle AI tactics estimation - fix pathfinding not being updated after revealing the fog on visiting Magellan's Map - animate lighthouse in lighthouse dialog - fix the experience calculation for AI-controlled heroes while visiting Tree of Knowledge - add a frame for a hero portrait on the quick info dialog - add missing experience being displayed in the Tree of Knowledge window - fix music effects while visiting an empty object on Adventure Map - display the world date for save files - rework AI hero task priority logic - fix invalid popup window being displayed while clicking on an empty hero list area within Adventure Map - add Graphics option icon - fix corner cases of fog rendering - fix invalid calculation of AI path leading to a crash - show sleeping hero icon for hero's portrait within castle dialog - speed up rendering on Adventure Map - properly restore the screen after closing the hero dialog during combat - render background between HEROES and TOWNS/CASTLES buttons in Kingdom Overview window - fix possible random crashes during the gameplay - fix Iron and Steel Golems animation issues - show kingdom income when gold per day income is clicked within Kingdom Overview window - fix a crash while visiting a castle after an ally captured it - speed up file searching on Windows - fix the spell id calculation for a spell scroll while loading a map - fix puzzle map window position - fix View World default adventure map position - memorize zoom level of the View World screen - fix inability to move monsters after Good Morale event - fix unintended ability to upgrade troops during battle - fix huge CPU usage in hero window during battle - redevelop standard window generation logic - update Bulgarian translation - fix volcano sounds - update Russian translation (main menu and dialog buttons) - fix issue with the puzzle rendering - fix musical effects for various objects - fix stereo audio playing in SMK files - add letter "Ў" and "’" sign to Cyrillic (cp1251) button font - add lighthouse information popup in kingdom overview - fix an invalid option for scenario 1 of Roland Campaign - optimize mini-map rendering - update Czech translation - add proper hotkeys option icon - fix title in German translation - optimize View World rendering - add tooltip damage info for single target attack spells during battle version 1.0.1 (08 February 2023) - add the ambient sound for Abandoned Mine - add support for scaled resolutions - instantly update status bar in battle for hero's dialog options - fix invalid Kingdom income calculation - fix incorrect status bar update call - update Czech translation - add German and Russian translations of Android Toolset - Android Save File Manager: implement filtering of save files by type - fix scroll bar bottom position when dragging and fix high FPS rate when dragging scroll bar by mouse - make a narrower button font for cp-1251 (Cyrillic) characters - fix Druid projectile start position for shooting up - add PS Vita fixes and gamepad mapping improvements - add Danish translation - fix shooters projectile position during battle - fix high CPU load on Adventure map when Hero speed is set to Jump - Android toolset: add the ability to browse & delete save files - fix loss of the first frame in the after attack unit animation - update spell points for campaign heroes at the start of a scenario - uncover the fog during the hero's instantaneous movement only if the hero is actually moving - do not allow to dig for Ultimate Artifact second time - show spell effect animation so as not to cover the front troops - fix screen restoration after closing monster info dialog - fix the AI resurrect spell logic - fix the Dimension Door shadow on resolutions 640+x480 & 640x480+, temporarily hide auxiliary panels in "no interface" mode - rework pathfinding algorithm during battle to fix non-optimal paths for monsters - add Mossy Rock object which appears only on Swamp terrain while in the original game this object has no name - optimize Adventure Map rendering - do not do redundant redraws when dragging Adventure Map with the mouse - fix walking animations of certain monsters - fix the bridge operation when a wide friendly unit steps on it from the moat - fix creature wince animation for low morale and Lich cloud cases during battle - optimize Holy Shout spell effect rendering during battle - allow wide units to take a step back during battle - fix certain Peasant Huts being displayed as Thatched Huts with no ambient sounds on Adventure Map - update Polish translation - avoid excessing CPU usage for multiple windows in the game - set audio frequency to 44100 on Windows to avoid cracking sounds during playback - add fh2 toolset application on Android to help with setting up the main application - update French translation - reduce CPU usage in Select Language dialog - fix a case when external music was not being played on Ubuntu - fix incorrect logic for monster placement on Adventure Map - update German translation - do not lowercase creature name (or other string) when it is put first in phrase - fix forced Idle animation after switching to Static animation - fix screen flickering in SDL2 - optimize Death Wave spell effect rendering during battle - automatically assemble artifacts sets if their parts were captured in the battle - fix controller support for PS Vita - add a full stop, A and E characters with accents for CP1252 button font version 1.0.0 (21 December 2022) - add Ubuntu ARM support - update Polish translation - update the French translation - fix incorrect resource placements on certain maps - adjust "Battlefield Casualties" vertical position - fix bridge logic after morale event - enable the instant battle by default - optimize rendering in multiple places - update the Norwegian translation - add global hotkey to toggle the text support mode - make the regular small buttons translatable - make Trade button in Marketplace window disabled by default - add popup windows for monster recruit dialog - fix animation of slowed flying creatures - fix multiple hero related awards for campaigns - fix Death Wave, Holy Shout and Holy Word spell animations - do not mirror corpses of defeated troops during battle - fix rendering an extra empty line for specific texts - fix monster idling animation during battle - use logarithmic scale of sound volume - enable touch support for all devices - make separate hotkeys to cast battle & adventure spells - add the "Quit game" hotkey - handle hotkeys in the Adventure Map's File dialog - optimize defensive strategy for AI castles - fix black area not being rendering during video playback - reduce priority for bottles in sea for AI - make File Dialog and Adventure Dialog buttons translatable - do not stop object animation during AI turn - update Russian translation - fix text rendering for game option's names of certain translations - fix incorrect battlefield image restoration in the place of the previous damage popup - adjust several button font characters - rework the hotkeys concerning the hero movement - make the Main Menu's dialog buttons translatable for specific languages - display the amount of gold in the diplomacy dialog - show object animation during Hut of Magi visit - fix Dwarves death animation - fix very slow hero movement animation when moving a cursor with software cursor rendering - fix translated Cancel button in monster recruit dialog being switched to English - fix animation of bridge destruction during battle - fix the smoke animation when demolishing a castle building during battle - improve Adventure Map rendering performance - fix multiline word splitting for short phrases with a long first word - fix position of left barbarian's captain quarters part - reset the cursor position at the start of 'human turn' - add an option to toggle screen scaling type - recreate the original rainbow animation during battle - fix Scenario Info window elements - improve hexagon shading during battle - add Danish language option and font - improve rendering speed with software cursor rendering - fix French and Italian characters not being shown with their assets - set the minimum supported version to Android 5.1 - introduce AI Scout role - fix fullscreen mode with DPI higher than 100% on Windows - fix multi-monitor fullscreen support on Windows - update Bulgarian Translation - add Android installation and usage documentation - fix the scenario info dialog position - generate resizable buttons for both interfaces - fix inability to toggle V-Sync option dynamically - do not show resolution dialog on handheld devices on the first startup - add mouse right click simulation on touchscreen devices - fix cursor being too small for certain resolutions - disable Adventure Map scrolling for handheld devices by default - change battlefield hexagon generation to match the original - make fullscreen mode as a default state for Android devices - implement all troops transfer between armies within castle dialog - scroll Adventure Map with mouse left button pressed - use parabolic catapult projectile flight trajectory - update credits page - always show the Ultimate Artifact behind one of the last four central pieces of the puzzle - update captain's mana on map load and when building the Captain's Quarters - remove and integrate the rest of experimental options - always center text when a maximum width is provided - fix UI elements for the unhired hero popup window - update Ballista and Turrets damage information when they are destroyed - correctly reflect the change of fullscreen mode in the graphics settings dialog when it was toggled using a hotkey version 0.9.21 (12 November 2022) - fix audio playback issues on Windows OSes - do not show any hero status for AI heroes - add hotkeys for moving armies between heroes - update the Norwegian translation - fix monster upgrade cost formula - reset AI hero meeting flag every time if hero's strength has changed - add missing alliance conditions for the Succession Wars campaign last scenarios - add small optimizations for the pathfinding algorithm - fix incorrect evaluation of army strength where hero's abilities should not be taken into account - fix handicap income calculation - add right click quick info in more dialogs for hero and castle captain - fix possible assertion hit for campaign scenario info dialog - Luck / Morale related objects are now reset after meeting / battle for AI heroes - add a separate graphics options window - make more contrast for player objects on the minimap - do not override hero or castle selection focus on a game load - change language selection window to support many languages - improve the combat log's use of plural forms - draw the correct arrows for the paths on the adventure map - rework the troops arrangement mechanics for the castle defense - fix catapult's boulder animation - update position for text in difficulty selection dialog - add CP1252 French quotation marks - clear the kingdom's visited objects according to their lifetime, even if this kingdom has already been vanquished - update AI logic for monster distribution and recruitment - do not increase the number of upgraded creatures on Week Of/Month Of basic creatures - generate new MAX/MIN buttons for other languages - add Czech language option in game and add fonts - fix the troop splitting logic - include project's resource files into Android packages - translate language names in native language - add proper CP1250 font - update French translation - do not waste time to get useless monsters for an AI army - add audio support for Android devices - take Magic Book into account while visiting a castle by an AI hero - avoid using armies with 1 monster for AI heroes - lock the screen orientation to landscape for Android devices - fix campaign score sorting issue - fix an issue when kingdom overview scrollbar does not not remember last position - fix terrain colors on the minimap - memorize selected Scenario in Scenario Selection dialog - do not overwrite default platform's soundfonts if none is included into the package - update Hungarian translation - add Hungarian language option and font replacements - fix the shade used for mountains and trees - fix UI elements position in Select Scenario dialog - fix hero's fight cursor offset - fix heroes position on battle screen - add Android compilation - update Russian translation - redraw the castle's resource panel when a hero purchases the spellbook - add Dutch font and language option - update language dependent resources while changing language - update Bulgarian Translation - fix the Week Of / Month Of logic - update Belarusian translation - fix button font generation for original buttons version 0.9.20 (10 October 2022) - add Russian and French button translations - update the Norwegian translation - recalculate the hero's path when the hero was selected with the mouse - fix stopped music in events of quickly closing dialogs with internal music - add proper logic for artifact exchange between AI heroes - add Dutch translation - do not let the archers perform a double attack if they are out of ammo - add Hotkey support for restarting Campaign's Scenario - AI heroes now use spells from scrolls - fix neutral town unit growth - display detailed information about the composition of alliances in the scenario info window - add date to logging for the future bug fixing - add turkish translation - make sure that there is a place for the Ultimate Artifact in the artifact bag before trying to dig it out - do not ask player's name if the score is too low for High score board - add Romanian translation - hide console window for Windows OSes and add log file for this platform - add Romanian font and language option - update Polish translation - add Turkish language option and CP1254 font - add proper icons for Petrification spell - speed up Adventure Map rendering - take into consideration a case with full artifact bag while checking AI hero's path - generate button fonts for different code pages - apply usual rules for unit growth in neutral towns/castles with custom troops - decrease monster growth bonus for AI - fix incorrect usage of Dimension Door by AI heroes - make logging for monster movement in battle more user-friendly - add UI improvements of the Settings dialog - fix logic of processing cells containing units available for possible AI unit attack - fix incorrect object evaluation for AI heroes on boats - add a feature to give human player control to AI (debug build only) - make creature names lowercased for certain dialogs - generate new Gift button - generate full button English alphabet and new Battle Only button - fix switching of terrain music in case of hero loss - update Bulgarian translation - add Handicap feature for single and multiplayer games - fix a case when a hidden scrollbar during battle is visible for one frame - fix sound volume for objects while using external music type - include rating calculation for Campaign's high score - fix incorrectly rendered monochrome cursor for campaign selection window - update French translation version 0.9.19 (11 September 2022) - add popup dialogs for Well's buttons - generate MIN button based on newly added font - fix constantly moving AI heroes when they have nothing to do - update Polish translation - implement some improvements when exchanging an army between heroes - show intro videos like in the original game - fix messages displayed for captains during battle - update Bulgarian translation - correct start resources for AI players - add a popup dialog for Days spent info within campaign dialog - make Artifact evaluation for AI more granular - add Difficulty selection for Campaigns - add popup dialogs for mage guild and scenario info windows - fix rendering of Mine Guardians - do not show monsters and campfires on Puzzle screen - fix moving AI hero rendering - implement instant finishing of battles - update Norwegian translation - fix incorrect rendering of some icons in The Price of Loyalty campaign windows - make "Show terrain penalty" option the default - fix double income from captured objects - fix the distribution of Mutant Zombies in the Graveyard - fix the battle order after bad morale events - always use the current color of a unit when toggling auto battle (take the Hypnotize spell into account) - speed up file search on case-insensitive file systems - optimize Mini-map rendering - do not show boats on puzzle image - fix a possible crash in UI window - fix castle rendering on Mini-map - fix Ultimate Artifact placement conditions - fix rendering of towns for View Towns spell - fix tall object rendering version 0.9.18 (10 August 2022) - speed up rendering of Mini Map area - fix missing shadows from path arrows when they are on the edge of screen - render correct digging holes for terrains - add monster fading animation when both wandering monsters and hero are dying after a battle - fix incorrect boat fading animation - do not show digging holes on Puzzle Map - fix out of view rendering of boats and heroes - fix Ultimate Artifact digging conditions - fix the logic of joining monsters for free and for money - do not draw objects over the map edge if they are in fog - fix cases of boats being shifted when a hero boards it - fix wrong rendering priority of Hero and Jail - do not render shadow of a tall monster over another monster nearby - properly render trees and Sawmill - fix Fountain being displayed over a moving hero, not under - fix certain shadows of objects being too dark - fix cases when hero's flag was not being displayed properly when a hero stands near a tall object - fix cases when some parts of heroes being displayed over other objects - fix monsters being displayed over mountains - draw a flag over Magic Garden object - draw Adventure Map decors in the same way as in the original game - fix flag rendering priority from Windmill - fix incorrect moving boat rendering in relation to other boats - fix boat being cut near Magellan Maps object - fix incorrect boat rendering priority - fix wasteland terrain object displayed over hero's sprite - fix Mine's flag being displayed over a hero - fix cases when a moving hero being drawn over other heroes - fix cases when some parts of a ship can be seen through terrain objects - fix cases when hero overlaps ship while passing nearby - hero does not step on route arrows anymore - fix the pre-battle arrangement of neutral troops - add separate sound font for SDL 2 builds - fix paused music being played with old music volume while restarting it - fix hero starting experience values - fix amount of experience from Tree of Knowledge - fix missing retaliation delay after last animation frame - fix music being too quiet while fast switching between music types (Windows OS) - fix crashes on Windows 8 using MIDI music - fix external audio not always returning to on after turning volume to zero (Windows OS) - fix horse's steps being faded while crossing terrain border using MIDI sound (Windows OS) - fix sound volume being lower during combat (Windows OS) - fix no sounds situation when music is off even when sounds are not (Windows OS) - show correct hero's level and experience on level up - fix incorrect castle's area leading to wrong castle's info - add missing window frame for hero's dialog during level up - fix number of ghosts and splitting for Shipwreck, Haunt and Abandoned Mine cases - update Bulgarian Translation - update treasure chest AI decision logic - fix a case when a hero got 2 Magic Books - update Russian and Hungarian translations - add support for loading of MIDI sound fonts - show upgraded monster images in Adventure Map objects upon a successful upgrade - fix redrawing of the status window in cases when the hero's army or the castle garrison change without changing focus - make best AI fighter as a champion - update Polish translation version 0.9.17 (11 July 2022) - fix possible crashes during sound playback - always ask about rewriting an existing save file - allow to remap Hotkeys within the game - fix distance evaluation during Scouting - fix invalid logic when neutral monsters whose number was set in the Editor did not grow at all - add The Price of Loyalty team to credits - show a window stating about missing video files for campaigns - fix the class of a hero named Joseph - fix extra shadow being drawn for Hero Screen dialog on high resolutions - align File Options dialog position - fix Campaign Window button flickering - fix missing column of pixels in Map Scenario Info window - update French translation - allow different translations of "Red Tower" - implement AI courier hero role for better logistics - fix situation when MIDI music still continues to play during fast switching between music types - fix case when Adventure Map sounds are still being played while opening Main Menu - AI should keep some creatures in reserve for castle defence - update Ukrainian translation - update Norwegian translation - increase Genie special ability to 20% like it is done in the original game and correct log messages during battle - remember the last open page in Spell Book during battle - fix possible synchronization issues for audio playback - enhance AI logic towards defending its castles - improve usage of spell on Adventure Map by AI - fix displaying of negative resources in dialog - fix situations when AI was hiring too many heroes - respect the Ultimate Artifact's radius specified by the map creator - update Polish translation - fix the castle's well build logic for AI - align hero's button in level-up dialog - fix scenarios when ambient sounds from Adventure Map objects are still played while opening a castle's window - fix cases when Random Monsters appear from a different level due to invalid format of an object on the map - update Mine's sprite properly after defeating Ghosts in Abandoned Mine - fix multiple issues related to hero's info popup window and updating his position on a mini-map - update sounds and music before a hero making an action - add an environmental sound for Freeman's Foundry - add initial implementation of true "Evil" interface in the game - add missing Ultimate Artifact successful digging sound - do not play pre-battle sound if sounds are off - fix potential thread-access issues for Nintendo Switch - fix possible inter-thread race for Adventure Map Status window - fix a delay in game while crossing a terrain and playing MIDI music version 0.9.16 (12 June 2022) - add Flatpak support - add Belarusian translation - swap spell icons for Death Wave and Death Ripple - make the "no interface" mode more usable - add music resume ability for MP3, OGG and FLAC formats - update translation guide - replace "C with cedilla" with "latin C" in the French translation - do not auto-save at the end of a turn if the player has been vanquished on that turn - fix Puzzle window exit button rendering - add sound for rocks in water with seagulls - fix Sorceress captain purchase building rendering - fix issues with Romanian translation - properly deal with human-only colors in single player mode - fix the invalid behavior of "no interface" mode - introduce 3D audio option - change adventure map environment sounds for every step hero moves - fix boats being considered as free tiles - if attacker is able to attack multiple targets at once and has a built-in spell, then this spell should be applied to any (but only one) of the targets - fix some typos in translation texts - take into account AI hero's role during meeting - fix false highscore entry while cancelling to continue a campaign - highlight latest highscore after completion scenario/campaign - show evil resolution icon for Evil interface - add truncated shadow to Scenario Information window - generate missing parts of Red Tower sprite - fix incorrect tiles being marked as road - fix multiple cases while using Mirror Image and Summon Elemental spells - fix the victory/defeat verification logic - update error message if hero tries to open a spell book and does not have one - improve status text if only one troop is selected - fix inability to add new entries with similar high scores - add Campaign high scores - fix dwelling popup window on adventure map - make AI to be aware about map conditions - fix incorrect hero icon in adventure interface - do not show languages with empty translations - add Swedish language option and font support - update Ukrainian translation - well MAX buy: add condition for not enough room, make recruitment happen in guest hero's army too, more text flexibility - add Portuguese language option and initial font generation - speed up AI turn - impose a limit of turns for an attacking AI-controlled hero - cache AI hero's and neutral monsters' strength during AI turn - add initial Spanish language support - make castle's flags for Knight and Wizard factions appear in a correct color - add window header for campaign awards and bonuses - update German translation - fix plural forms translation for some languages - fix character filtering when generating the save file name - update Polish translation - fix Red Tower rendering with Rainbow - expand Hotkey usage on adventure map - fix German font support version 0.9.15 (07 May 2022) - add HotKey window and expand the list of system options - do not allow higher-level Mage Guilds to be built if the previous-level Mage Guild has not been built yet - Battle Only mode: always assign a random spell to the Spell Scroll - add initial support for Romanian language - add more conditions to define a blocking AI hero - fix incorrect video playback on Descendants campaign, scenario 5 - change some descriptions of campaign awards and bonuses - do not auto save after the completion of the last scenario in a campaign - add images and full description of bonuses and awards by right mouse click - fix some incorrect campaign bonuses - display None if no awards available for the current scenario - add hot keys to select bonuses - fix rendering of campaign UI window as it was very slow on response on changing bonuses - fix missing kingdom income value for scenarios with additional bonus resources - game type interface for campaign window must be unique and should not depend on overall game interface - allow already selected scenario to appear in ALL scenarios list - improved Week of the creature window - changed town portal's text and condition to be similar to town gate - update Russian translation - properly extract animations from the GOG distribution of HoMM2 for Windows - update French translation - improve search for video files - add text truncation method to be used in campaign's choices and awards - encourage AI to explore more - redesign Hot Key event file handling - add double-lined headers to adventure map options - add Belarusian, Bulgarian and Ukrainian languages support - populate resolution list based till the lowest resolution - fix 1000 and 100000 short values display - update hero quick info popup dialog - disable Town Portal dialog OK button by default - changed AI's turn to be repeated multiple times for heroes - generate French 'MIN' button - generate French 'GIFT' button - add Translation Context for Secondary Skill Campaign Bonuses - add Ogre Alliance and Dwarfbane full strings for translation - fix the positioning of the damage info popup - update Norwegian translation - fix calculation of morale & luck modifiers for in-castle army without a commander - properly calculate the number of travel days to the destination tile for a hero - AI should replenish lost spell points more frequently - do not allow a hero to have multiple spell books in the "Battle Only" mode - fix rendering of the Arm of the Martyr's shadow - AI uses View All spell to scout fog - hero should have at least 69MP to cast Dimension Door, Town Gate and Town Portal spells - fix spacing of player squares in Select Recipients window and properly center resource icons for "Your Funds" and "Planned Gift" horizontally - fix French small font Cedilla c rendering - update Polish translation - reserve half of AI spell points for Dimension Door spell - hero should be able to cast spells that consume movement points if he is able to move from his current tile - fix MP3 support in Windows SDL1 packages - reset the mixer volume before playing video soundtracks - expand Test Support mode to cover all campaign related actions - add Black-White cursor support - add music control options to the configuration dialog - fix failing music file search - properly work with guardians on the objects that can be captured - fix call to get secondary skill name leading to a crash - fix multiple issues related to the interface settings - allow to change the auto battle settings if player's army doesn't have a commander - fix a crash when a hero tries to get a second magic book - always grab the mouse in fullscreen mode to properly support devices with notch version 0.9.14 (09 April 2022) - generate Italian "Battle Only" button - fix line breaking in welcome message for various languages - update and improve Italian translation - generate Polish "Battle Only" button - fix switching fullscreen mode in Mac OS for certain resolutions - generate French "Battle Only" button - fix a crash due to an assertion failure when the shooter hits back at a unit under Berserk spell - fix scrolling in full-screen mode in case of an aspect ratio mismatch - fix rare cases for incorrect letters being generated while switching languages - add Norwegian language option and font generation - update French translation - add initial implementation of Dimension Door spell usage for AI - fix Gold Watch artifact invalid bonus - rework artifact logic with proper separation and calculations of artifact's bonuses and curses - play only one sound while getting ar artifact within an event - speed up castle and hero icons on adventure map - add "Battle Only" button generation for German version of the game - fix Mage Guild bottom status bar rendering - fix incorrect artifact discovery description - add in-game Italian language option and diacritics - fix army order option related crash - fix High Score Music after winning scenario - adjust AI spell estimates - fix rendering of bottom bar in Well dialog - update Polish translation - remove unnecessary call to play VICTORY music during Campaign intro - fix the calculation of the cost of surrender - fix Town Gate spell logic - prioritize AI castles further from enemy territory - take the weekly growth into account when calculating monthly population growth bonuses and fines - update minimap colors - optimize AI fighter hero priorities - add native macOS application generation - build player influence map to inform AI kingdom development decisions - speed up data reading from AGG files - update Norwegian translation - add a popup window for battle log - add a mandatory requirement of h2d file being present - fix incorrect logic for palette application function - fix position of armies for quick info window for both castle and enemy hero - set the direction for attacking and defending units in battle in a more OG-like way - fix incorrect popup monster window in Well - greatly speedup heavy video loading - do not render extra frame while playing an audio only video file - properly center the first sentence in the battle surrender dialog - dynamically update Game Language dialog - fix translation and UI in Tavern dialog - add MP3 file support - increase the number of sound channels to 32 - allow a hero to surrender to the captain during the siege - fix rare case of Shipwreck having no battle - display experience amount for Tree of Knowledge - fix Witch's Hut message body - for Anduran assembly window show artifact name as a title - for hero level up window with one secondary skill make the skill right clickable - for hero level up window with no secondary skill display the primary skill icon - add search for FLAC files if OGG files aren't found - add popup dialogs for Arena's skill choices version 0.9.13 (07 March 2022) - show captain quick info dialog in Kingdom Overview - add popup windows for campaign awards and bonuses - remove Map loading screen - add popup windows for campaign scenario dialog buttons - remember map size selection in New Game dialog - allow selecting options while restarting a campaign scenario - display the correct size of player-owned objects on mini-map - add base support for text mode - fix Sirens logic, AI interaction and UI - show missing last frame of wince animation - add dynamic generation of scrollbar slider - fix Sirens action dialogs - add missing artifact discovery event description - fix some French translations - fix directory and file detection on Windows - Kingdom Income window was changed to have Kingdom Income title and Kingdom Income per day to be more obvious for a player - events on land and sea now show artifact and resources in one window - most of objects related dialogs contain right mouse clickable icons - Pyramid visit outcome shows the correct horizontal position of icons and all icons are right mouse clickable - fix Artifact frame drawings - fix text if translation for a specific language was not found - update AI hero recruitment logic - allow a short name for all campaign bonus artifacts and spells - AI should save current unit if retreating - add Czech language support - fix monster strength heuristic regression - make AI more aggressive in losing situation - fix the logic of protecting tiles by neutral monsters - change music and surrounding sounds while jumping through Stone Liths - fix possible AI pathfinding issues while loading another scenario - fix battle grid and ballista info area - add popup dialogs for right mouse click in resolution dialog - fix hero name not being updated while switching languages - fix possible errors for translation related code - force to use only the original palette from the game - fix lowercase conversion of translations - AI archers should not ignore blinded units during battle - add missing take-off and landing sounds for Vampire and Vampire Lord - reset environment sounds and music theme at the beginning and at the end of the human turn only - fix player's allies configuration for 'Defeat the other side' victory condition - fix sounds of Lean-To and Skeleton objects - rename "Lean To" -> "Lean-To" - allow AI fog discovery only for teleport endpoints with reachability information - fix missing sounds for some objects on map - do not play castle's music if a terrain does not have its own music - fix missing sound of player's turn in Hot Seat mode - adjust monthly monster spawn - fix neutral monster growth formula - fix necromancer build order in castles for AI - allow a boat to move near the coast above the center of a town version 0.9.12 (07 February 2022) - add a confirmation window for restarting a campaign scenario - fix wrong scenario order in "Voyage Home" campaign - add Betrayal missing for Roland and Archibald campaigns - fix logic for switching on and off Auto battle option - fix normal and retaliation attack of monsters with multi-cell attack abilities - fix missing logging during certain spell casts in battle - implement a heuristic for hiring heroes that does not allow to offer the same heroes for hire in several kingdoms at the same time, if there are enough free heroes - use 'r' key instead of Escape to Retreat during battle - fix Hydra behavior under Berserk or Hypnotize spell effect - add highlighting of cells attacked by a unit capable of attacking all neighboring cells - fix a penetrating attack made by a wide unit from the tail cell - fix attack cursor display during battle - fix cases when no monsters were spawned at the beginning of a month - display ? symbol instead of empty space for unrecognized symbols - fix crashes while rendering non-English text - fix rare cases of a screen being black on a startup of the application - make language area and campaign choice selection to cover text too - fix campaign text translation - modify army order setting - add German font generation - fix multiple issues related the hero recruitment process - update Russian translation - show monster info on right click in castle's well - fix cases when the last AI hero was blocking an entrance to Stone Liths - fix small font positioning for Polish GoG version - fix most of existing cases with passabilities - fix the display of properties for maps from the list in scenario selection dialog - update German translation - fix translation of saved default player names - improve the AI fog discovery logic - AI properly uses Whirlpools for path calculation - fix cases when a hero was able to create a path over another hero - display dead monster information during battle - fix Mass Dispel usage during battle - update Polish translation - fix Russian small k per in both fonts - AI heroes properly calculate paths through water - add translation context for color and syntax flexibility to Traveller's Tent and Barrier - fix Knowledge spite rendering for Arena - fix sounds after battle cases - fix missing Russian letters on Switch - changed captain's name spelling - adds missing "pourcent" in description of Golden Bow - fix cases when a hero was able to walk on water - update French translation - show in-game only players for Gift marketplace option - fix glowing red pixel in Warlock's Red Tower - improve AI behavior during castle defence - fix CPU player icon's extra color column - show proper language name for mismatched by language save files - add a spell title when no enough SP window appears - fix a possible crash in French letters generation - fix usage of plural/singular translations for map's objects - update the logic of re-calculating the paths of heroes on the adventure map - add support of Buka version of Anim2 directory for video playback - always enable autosave - generate proper Russian E with 2 dots on top - allow translation of "Cost per troop:" text - update UI elements in marketplace window - fix a crash during AI turn when AI army has an invalid monster slot version 0.9.11 (23 December 2021) - add popup dialog for map information - fix hero's path not being updated properly after new monster month event - show available game languages in vertical order rather than horizontal - fix handling of national symbols in the paths to music files on Windows - improve AI behavior during castle defence - fix pressed buttons' background - fix incorrect output on console window for Windows users with non-English system language - add support of Russian language by default - update Polish translation - generate correct letters for French language - update Settings description to cover all actions - fix heroes meeting swap arrows - update PS Vita controls - fix crash for some systems after changing game resolution - add hotkey for settings in Main Menu - do not reset the hero's path in Sleeper mode, reset the Sleeper mode only with the beginning of the hero's movement - fix River being considered as a Road - allow to select player class using mouse wheel - add additional spell info for spells owned by a hero - fix button rendering artifacts after changing resolution - fix crash on Windows for some Archibald scenarios - always mark the entrance to the castle/town as a road, do not automatically mark the tile south of the entrance as a road version 0.9.10 (05 December 2021) - update background music on the fly while changing it within settings - fix horizontal position of town name and town icon in town construction dialog - improve marketplace gift button placement - fix vertical text position in skill icon - fix Summon Boat logic - add video playback in the Price of Loyalty campaign selection window - play sound for visiting Observation Tower - fix vertical position of building construction name - add missing button shadows in dialogs - add popup window to OK button in game settings - play campaign music in campaign scenario selection screen - fix Enter button pressed state being passed from gift dialog - show credits at the end of campaign - fix passability for Dimension Door spell - add V-Sync option for SDL2 - update troops split logic change - properly draw battle grid - use the OK button instead of YES button for the Tavern window - add Champion image for Stables object - play sounds from closest surrounding objects first - make consistent campaign info dialog - add default castle's name if none is specified by editor - show heroes on minimap - update Norwegian translation - show available creature count for the Price of Loyalty monster objects - fix Witch's Hut name - add Italian translation - properly handle the day-week-month routine - add titles for most of dialogs from map's objects - always mark Artesian Spring as visited, even if the hero was not able to drink from it - fix movement penalty calculation algorithm - implement last move logic for hero movement on the map - update hero movement points UI upon visiting multiple objects on the map - fix rendering for difficulty text in scenario info dialog - fix AI obsession with whirlpools and being always in sea - load palette from AGG file - fix monster recruit window position for multiple places - fix logic for Slow and Haste spell AI usage during battle - add random sign messages - fix maximum music and sound volume - use correct message in marketplace for first exchange when not enough resources - speed up for multiple image readings and for unit rendering during battle - allow to change Battle Speed using mouse wheel - update UI elements in Well's dialog - if a hero on the map has the custom empty army (OG editor bug/feature), give him a minimum army (one unit of level 1) - add translations for Battle Only mode texts - set correct starting army in Archibald 7 scenario - fix cases when AI creature doesn't move during battle - fix sound effects when receiving artifacts from world map - add hotkey to Okay button in Campaign select window - fix results on instant battle when Resurrect spell was being used - fix cases when opposing heroes still exist on the map after defeat - improve AI usage of Disrupting Ray spell during battle - add runtime language generation - fix terrain music being played during battle - fix water object passabilities - allow to use mouse wheel to change system options - set correct building requirements for Upgraded Jousting Arena and Upgraded Cathedral - update messages during battle - shorten artifacts names to be fit for campaign UI - always highlight the cell under the cursor in combat if the Shadow Cursor is enabled, even if this cell is not reachable by the current unit version 0.9.9 (05 November 2021) - do not leak the hotkey state from the experimental settings dialog to the scenario info dialog - fix Stonehenge gold cost - fix Stonehenge building requirements - add buttons in castle construction window - fix rendering of Captain's Quarters in Warlock castle - fix Marketplace messaging - fix MIDI Expansion music option being always reset upon restarting the game - fix incorrect sounds while playing MIDI music in certain platforms - add credits to the original HoMM2 team - correct original Waterfall images to remove Cave - use original alignment for Rating, Map Size in gameinfo window - fix interaction passability with water objects - change Shipwreck, Graveyard and Derelict Ship messages - add the Resources Settings page to the Windows installer - fix passability for Reefs - fix invalid Stonelithts after Barrier removal - fix passabilities for diagonal moves - clear fog for allies upon visiting Magellan's maps - fix Archibald scenario 7 description - fix bad pixels on multiple images - verify Victory/Loss conditions after a static hero action - modify Magellan's maps logic and UI - fix monster count in Dragon City for Archibald scenario 7 - update Victory message for Archibald scenario 7 - fix possible crash for Russian version of the game - fix incorrect map description length - fix spell points in Battle Only mode - apply Bane and Alliance condition only for Human heroes - add extra empty line in New Month window - disable bonus choice for campaign scenario info window during scenario play - add Norwegian Translation - fix the position of campaign days spent text - close certain dialogs containing only one Exit button with Enter key - add Barbarian Captain Quarter's missing part - add hotkeys for selection of SW/PoL campaign - generate the default High Score values - fix "show damage" feature for blessed/cursed troops - fix high scores word - fix AI lust for Magellan Maps object - hide the OKAY button in campaign info window - handle default exit hotkey in Campaign scenario select / info - add Sphinx handling by AI - remove incorrect status message for hero portrait in hero screen - fix map size button states on hotkey press - restore environment music & ambient sounds after exiting from the campaign info screen to the adventure map - do not allow to use lower than original resolutions - freed from Jail AI hero starts movement at the same turn - add AI interaction with Alchemist Tower - fix incorrect visiting status of Magic Well, Stables, Artesian Spring and Bad Luck objects for AI - fix invalid assessment of Monster upgrade objects for AI - fix cases when AI hero ignores some objects while defeating an army just on the way to them - fix losing hero artifacts upon flee or surrender - enable a pressed state for View Intro button post-launch in Campaign screen - fix bonus artifact names in campaign screen - return to the Main Menu after cancelling campaign scenario - fix AI heroes being stuck on teleports - fix button rendering in Restart battle dialog - update Portuguese translation - fix inability to dig on a tile where monster was located - fix inability to apply Hypnotize spell on Dwarves - fix shooting penalty detection - add extra popup windows for UI elements in castle dialog - fix passability with shadow sprites - AI to use Hydra ability in battle - reinforce a AI hero standing in a castle at the start of turn - update status bar in castle screen dialog even during construction animation - fix incorrect active area of certain buildings in castle screen dialog due to invalid Z levels - hide cursor while opening a dwelling info in castle screen dialog by right mouse click - add Italian language support - update Polish translation - properly show the attack cursor when the attack square is located at the top of the castle sprite - fix infinite loop for AI trying to use Stoneliths version 0.9.8 (05 October 2021) - fix a case when a hero loses his artifacts after retreat in multiplayer mode - fix monster position on World Map - correct resource position in castle window - new week status is not changed while reloading the same save file - add headers for all Daemon Cave dialogs - fix rewards in the Daemon Cave - fix Waterwheel incorrect visited status - fix spell points reduction while visiting a well with more than maximum spell points - add confirmation message in Alchemist Tower - add cell highlighting for monsters with double cell attack ability - add correct evaluation of an AI hero recruitment and meeting - fix artifact selection area - add a title to all artifact related windows - grey out experimental disabled option - show map type icons in New Map lists - do not place wandering troops on coast tile if there are no other coast tiles nearby - develop a castle during AI hero purchase - fix Altars' passabilities - fix AI hero being stuck on Stoneliths - fix passabilities of action objects - add Windows scripts to automatically copy & extract all necessary assets from original HoMM2 distributions - fix unresponsive application during AI turn while attacking an object with guards - fix active building area in castle construction window - AI can now visit Pyramids - fix scrollbar rendering in select lists - replace scrollbar in Experimental Settings dialog - unselect initial town in the Town Portal spell list - fix troop position in the Pyramid - fix bridge animation during battle - fix deterministic battle outcome - fix an empty list of heroes in the Kingdom Overview after the dismissal or loss of a hero - fix passability for tall objects - fix Roland campaign awards after ending scenarios 7 and 8 - set 15 character limit when inputting player name in High Scores - correct battle speed change for monsters - fix incorrect level of Lightning Rod artifact - correct castle building' active area - fix passabilities related to cracks - align UI elements on artifact combat outcome dialog - add Spell description window in monster info dialog - fix scrollbar position after dismissing a hero - add logic for ENTER and SPACE keys for a hero on World Map - add hotkey for View World dialog - fix some cases with Tree passabilities - update Polish translation - fix post battle crash - fix incorrect rendering for game settings window with evil interface - fix appearance of sounds of the sea when a hero stands close to map's edge - display a message about Ultimate Artifact after the battle - fix adventure border rendering on higher resolutions - fix incrementing days spent value after reloading a campaign save - lock mouse within the window in multi-monitor setup - fix passability of mines - fix an ability to attack a monster through a barrier - allow hero to traverse Stone Liths and Whirlpools even if he has no movement points left - fix black edges in View World - fix passability for tiles with shadows only version 0.9.7 (05 September 2021) - add popup dialogs for buttons in High Scores and save files manipulation windows - fix Faerie Ring passability - boat boarding animation speed doesn't depend on hero speed - add base support of German translation - fix medium size hero portraits - fix incorrect object info when a hero stands on it - remove flags from most of the captured objects for a lost player - fix alignment of scenario info window - fix boat froth drawing conditions - fix Battle Garb of Anduran missing assembly for The Price of Loyalty campaign - add monsters into High Score dialog - add short save file description window by right clicking - add base roles for AI heroes - restart of the battle should lead to the same results - fix logic in moral indicator - AI can capture Haunted mines - fix scrollbar position in Kingdom Overview dialog - allow guardian heroes upgrade their troops - add object count for weekly object income message - open game settings window while clicking on the main menu door - fix object passability nearby Hut of Magi - modify Necromancer Captain Quarters look - fix Radar appearance while right clicking on hero/castle - do not show file options window upon file saving - save Kingdom Overview UI selection in save files - add missing hotkey for Kingdom Overview - fix missing mine being drawn in View Mines - fix incorrect fading of some objects on World Map - fix AI behaviour during siege - fix cases of incorrect monster stack splitting logic - handle Escape/Enter keys in various settings dialogs - use distinct image for the "Swap heroes" button - properly restore captain's spell points after quick battle - remember UI position in Kingdom Overview dialog - add strict ordering of save files ignoring capitalization of letters - add popup dialogs in Kingdom Overview UI dialog - fix missing game resolutions on some machines - fix incorrect rendering on specific fog tile - display same name maps in the list of available maps - improve pathfinding algorithm for AI - place Ultimate artifact not always in the center of puzzle image - fix max ballista shots during battle - update French translation - add missing Farm sprite in Knight castle - update every scrollbar logic in the game - fix a crash while shooting from castle's tower during castle siege - fix Holy Shout being applied on Dwarves - properly handle cross-moat attacks during castle siege - do not let the player get a random artifact that allows him to win the game - allow player to immediately edit save file name without clicking on the input field - add few performance optimization in music and audio - update Polish translation - not visited obelisk remains not visited after the Ultimate Artifact has been found - fix polish translation encoding - game settings dialog: reset current language to English if game resources do not support other languages version 0.9.6 (06 August 2021) - update status window if troops have fallen overboard - add direct preview of changes in system options dialog - update Polish translation - display the position of a hero or a castle on radar while right clicking on them - add titles to some dialogs - add translation support for missing places - fix monster joining logic with and without diplomacy - add Nintendo Switch build - make captured object event messages to contain yellow titles - fix a crash while AI uses Summon Elemental spell during battle - fix incorrect Alliance conditions for certain maps - fix a case when a hero in castle's tavern always changes after reloading a save file - fix incorrect icons for scenario information dialog - update skill order for new heroes - add normal Game settings dialog - fix inability to attack during battle - do not play ambient sounds during AI turn - add monster names, abilities and hero skills to translations - fix AI archer avoiding hand battle logic - fix UI defects in Kingdom castle view dialog - properly update resource and artifact sprites on World map - fix infinite loop after chain lightning spell - do not draw flags for puzzle image - highlight cells from where a monster will attack - restore background music after playing short-term music effects - add language selection window - fix Town's fog area unveiling - implement regular win/loss conditions for multiplayer mode - update View World while resizing the application - fix English phrasing and punctuation - fix simultaneous activation of buttons in hero meeting screen - add Town Screen and Well hotkeys - fix incorrect logic nearby castles when an attacking hero was actually defending the castle - improve win/loss checks of scenarios - make AI to use Lich's ability properly - update Russian and French translations - make fog discovery by AI more efficient - fix missing monster spell casting during battles - center fast separation buttons in Monster separation dialog - remove required spell points from scrolls in Mage Guilds - fix Next Hero selection logic - fix Wizards campaign scenario 3 with custom Ultimate Artifact - add 500 experience for conquering a town regardless of defending hero existence - change battlefield pathfinding algorithm to more stable version avoiding AI skipping some moves - align text evenly inside secondary skill window - display translated text in campaign dialog - fix a crash during Summon Boat spell usage - if the hero's army does not have any of the original troops left alive at the end of the battle, then the hero should lose - fix typo in Fizbin Medal - fix small font for Russian localization - fix town/castle passability for some cases - fix incorrect mouse cursor appearance during AI turn after a battle with Necromancy outcome - fix attacking monster sprite on World map - AI should check if an object is guarded while unveiling fog - add Alchemist Tower to the list of objects to be avoided by AI - fix a case when AI heroes did not ever capture empty castles version 0.9.5 (04 July 2021) - do not hire AI heroes if no tasks exist for them - make AI be more aggressive if no tasks exist for them - all scrollbars support continuous scrolling - fix placement of creatures during Month of Monster event - add an option to restart Campaign scenario - do not show x marker for fully built castle icon - fix boat summoning logic in relation to some objects on a world map - do not show other types of save files on while saving or loading a save file on Windows - improve Sorceress castle building logic for AI - make AI to visit Lighthouse - fix displayed object info in popup dialogs - fix places where a hero could teleport for Dimension Door spell - fix passabilities of objects on World Map - fix multiple issues with castle music - fix disembarkation on coast during diagonal move - indicate the number of Ghost guarding Haunted and Abandoned Mines - add extra conditions for object visiting by AI - make AI to use Hut of Magi and Jail objects - AI heroes do not block way for other heroes anymore - use spacebar only for activating object - fix missing UI update in hero meeting dialog while moving artifacts within internal hero's dialog - speed up campaign game menu opening time - fix incorrect object rendering order on World Map - after loading a map and starting a new game the same map will be selected in the list of maps - improve AI logic to capture defendless castles - fix black screen for loading a campaign map without files - allow AI hero to revisit a castle more than once - fix a case of splitting monster stack for 2 monsters in stack - add monster descriptions - allow to visit Artesian Spring once a week - allow right click on captured artifact after battle - fix summoning boat logic in relation to AI - improve fog revealing algorithm for AI - fix Stables movement bonus - fix AI heroes interaction between each other (reset meeting flag if a hero updates an army) - AI heroes do not visit signs anymore - AI do not visit shrines for useless spells - fix AI spellcasting and army estimates - do not remove Sphere of Negation artifact while visiting Alchemist's Tower - fix the status of visited objects for a play with ally - make proper rendering of some of disabled buttons - fix AI behavior to visit Magellan Maps only once - center quickinfo dialog under mouse cursor - add more gamepad key mapping for PlayStation Vita - fix the displayed text in the army bar while splitting monster stack - do not let nearby monsters automatically attack hero if he moved to this location using Stone Lith, Whirlpool or Dimension Door - fix incorrect text position insertion while creating a save file - fix title background for recruit dialog - fix hero's mobility indicator for small number of move points - in scenario with random players random opponents are generated so that all races are present and different when possible - add new UI text rendering classes - fix issue when abandoned mine becomes OBJ_ZERO when captured by AI-controlled hero - fix missing road identification on broken by editor maps - make per-user config file as default option - allow attacking another hero located on a Temple - fix attacking monster sprite behavior - fix number of choices when splitting a troop into multiple troops - fix incorrect trading rate during battle - fix missing shadow removal of objects under a boat - show Dwarf's resistance message in the log only after spell completion - correct campaign bonuses' names - fix case when boat could be positioned over another boat over whirlpool - fix case of triggering Genie's special ability for a stack of 1 monster - fix the Price of Loyalty campaign missing rendering version 0.9.4 (04 June 2021) - add Windows executable installers - use user profile directories to store files to allow multi-user access to the game - fix incorrect text splitting in some rare cases - fix crash in Roland campaign, scenario 10 - add missing popup dialogs for system options and buttons - fix inaccurate object sorting by distance - fix multiple issues with AI and spell logic using distance evaluation - read campaign files in non-case sensitive manner - make fog uncovering task for AI more efficient - fix AI obsession over Observation Towers - fix missing path drawing while a hero moving through Whirlpool - mark resource generator objects as visited for allies - fix incorrect game ending during time loss condition - show extended shadow for 2-hex monsters in battle - do not show gray flag over Haunted mine - add configuration option for first game run - support PoL heroes and artifacts in "battle only" mode - fix inaccessible beach tiles - add hiding and showing cursor logic for SDL2 - fix shadows for random resources and artifacts - draw correct castle icon in popup window - fix incorrect hero position facing left - remove unneeded AI hero animation under fog - do not clear morale modifiers too early for Tavern case - highlight the door in Main Menu for mouse over event - speed up and fix many places with cursor rendering - recruit an AI hero if none exists - fix cheating AI behavior for dwellings with defenders - add an option to replay Intro video for campaign - force display rendering on app activation - fix Stables, Alchemist's Tower and Water Wheel passabilities - fix puzzle drawings - avoid using useless spell during battle for AI - return to load screen after cancelling loaded campaign scenario - play hero's vanishing sound after AI vs human battle - make Ultimate Crown as artifact for campaign scenarios - fix incorrect hero receiving a bonus spell for campaign scenario - fix monster recruitment logic when a hero present in a castle - fix well's max button hiring logic - add an option to open Hero Screen within battle - fix move points and spell points replenishment logic for heroes from hero pool - set proper difficulty level for campaign scenarios - directly show next campaign scenario for end of scenario save - modernize and secure demo version installation scripts - add video playback for campaign scenarios - do not reset hero mana points to maximum in the beginning of a new week if they are above maximum - fix Roland, chapter 9 map conditions - fix Tower shooting logic during castle's siege - build Mage Guild before Special building for AI - do not count monsters under fog for Visions spell - mute sound by setting music and effects volume to zero instead of pausing them - speed up AI hero movement - add an option to hide AI movements - do not draw objects under the fog if they are far from a revealed area - add base code for the Price of Loyalty campaign support - add dismiss and upgrade hotkeys in unit dialog info - add shadow for Spell Book - unveil the fog at the start of hero's movement - fix extra place with hero's double shadow at adventure world - add extended cursor icons from 4+ to 7+ - fix UI defect of selected creature appearing after closing meeting dialog - fix incorrect rendering of View World image - add Lose Sorceress Village Condition for The Succession Wars campaign - fix flags in Oracle/Thieves' Guild dialog - fix sound mute in background on MacOS with SDL1 - add Home and End button support for save naming - fix elements layout for Oracle/Thieves dialogs - generate obstacles on battlefield based on the battle tile index - use non-plural name for 1 creature while viewing with Crystal Ball - add correct support of The Price of Loyalty add-on maps - fix double clicking between dialog in castle's window - speed up MIDI loading - fix bug with mouse cursor not visible after left-clicking on hero's secondary skill in Kingdom Overview - fix the "world: use unique artifacts for resource affecting" option - reduce CPU usage for video playback and puzzle revealing - fix Mix-up in Roland's Campaign Scenarios - fix issue when troop info window was not showing after moving a single unit between army bars using drag & drop method - simulate level-ups for campaign-specific heroes that start at a higher level - reset current music from the previous turn in the beginning of a new human turn - upgrade Rendering engine - fix broken behavior of scrollbars - do not offer to exchange artifact with spell book - disable controllers on non-console platforms version 0.9.3 (04 May 2021) - add additional info to oracle dialog - check that unit is (im)movable after each unit action - save completed Campaign scenario to file - drop support of saves older than 0.9 version - remove incorrect battle background for Graveyard - fix catapult miss animation: show this animation near the actual target object - fix magic book behavior in inventory - fix spell indicator for hero's icon - add special Victory conditions for campaign scenarios - add initial support for Campaign Awards - allow switching heroes in hero dialog if it was opened from meeting dialog - fix issues with battles logs and sounds while Dwarves resist spell - fix inability to Teleport monster in battle for certain cases - add missing "Not enough gold (xxxx)" text in joining window with Diplomacy - do not generate Puzzle image on map loading - do not take Mage Guild level into account when calculating count of archers in castle towers - fix shadows for all monster mini sprites - allow the hero to retreat if, while in the castle, he attacks the hero outside the castle - show Mage Guild building always in center - add titles to Arena, Alchemist Tower and Stables dialogs - add World animation during Dimension door spell - show correct message for hero's option dialog buttons - fix bridge operation for flying units in instant battle - add letters for World View and View spells - properly handle filenames with dots - fix Main Menu UI redraw - do not run animation of lowering the bridge on bridge destruction if bridge is already lowered - fix the display of the army in the castle quick info window - display correct message when capturing abandoned mine - align columns and flags at oracle dialog - fix cursor during video playback - do not allow the player to scroll world map while hero is moving - add initial support of other languages: German, Polish, French and Russian - fix information displayed in castle quick info window - fix sphinx riddle dialogs - add swap buttons in hero meeting dialog - correct spells order in Magic Book - enable triggering of positive morale event after soft wait - fix saving scroll speed in the game config file - fix Artesian Springs logic - fix inability to use Stone Liths - fix incorrect heroes for surrender conditions - fix gold and experience dialog for the demon cave - remove exclamation mark in Hero info window for "Blood" morale - fix double shadow from heroes - preserve the order of catapult shots - highlight Lich's shot area - implement correct post-battle necromancy window - rename Coast to Beach - fix large object coverage in battles - add message when visiting Oracle - update hero's path while selecting next hero by an option button - fix Dragon City guardians - load correct video files to be played - implement the "faster monster of opposite army goes first" logic in the battle mode - fix excessive FPS issue in dialog of joining the army - fix game score calculation - reset the spell on tile when AI-controlled hero has successfully captured an object - immediately hide hero's path before executing the animation of the Dimension Door spell - fix Necromancy incorrect percentage output - fix ultimate artifact digging logic - speed up images loading from resource files - fix fading animation synchronization - fix a case of inability to confirm end turn while cursor is over options buttons version 0.9.2 (04 April 2021) - fix full artifact bag message - add missing dot to sentences in few windows - display Lighthouse count in Kingdom Overview - add proper large obstacle generation for battlefields - fast AI units should try to get the first strike - fix Sphinx missing rewards - reset paralyze state when receiving spell damage - randomize attack position of AI monsters - fix missing hero paths after loading a save - fix incorrect window resolution in fullscreen mode for SDL 2 - fix Choose Your Lord video sound - improve castle garrison estimation for AI - remove the mobility index sprite info from the hero Quick Info dialog - prohibit the guardian hero from casting adventure spells - fix merging of the castle guardian army - fix AI defensive unit behavior - add AI estimation of hero spell strength - force units into empty slots first during merge - do not always offer recruitment of heroes from the player's initial class, make recruitment more random - allow player to control the hero during the turn of a hypnotized enemy unit - archers should not lose shots when attacking in melee - check that the hero is able to move before focusing him and starting his movement - respect initial order of units (from top to bottom) when determining the order of unit moves in battle - add PlayStation Vita support - fix creature recruit window UI - fix rendering of Arm of the Martyr - fix blind spell logic - do not give additional 500 experience to the hero who won the battle if the opposing hero surrenders or flees - fix hypnotize spell behavior and the spell description - update positions of monster sprites and numbers on army status panel - fix possibly incorrect arrow direction from shooters - fix scrollbar position in multiple places - standartize icon position in town's dialog - fix Max/Min button position in monster recruit dialog - show the surrender dialog even if there is not enough gold to surrender - do not apply morale penalty to a hero if he wins a battle in a Graveyard, Shipwreck or Derelict Ship - add cross army drag split - reset player focus if focused hero or castle was removed - allow to switch players for multiplayer map selection - allow to use mouse wheel anywhere for split troop dialog - fix logic of placing monsters for a New Month - fix Kingdom Overview selection drawings - fix boat drawing issues - do not hide mouse cursor if the Skill Info dialog called from the Level Up dialog contains OK button - fix incorrect drawings for battle OKAY button - fix rendering of the Castle Captain's spell points bar - fix missing sounds within System dialog - fix interaction with the castle bridge for flying units - use rule of 9 for Ultimate Artifact placement - fix last focus option - fix incorrect cursor icon over ally's castle - add initial implementation of View spells - show normal monster names in Visions spell - show full 1-pixel black border for Show Interface option - show more detailed morale and luck description - AI predicts double cell attacks in combat - reset blind spell upon taking any damage - allow to place units in moat in front of the bridge - align spells in the magic book - add manual replay of battle if instant battle outcome is negative - sounds off when minimizing game window - fix playing background music during video playback on SDL1 - change damage description log message for some mass spells - add executable file description and icon on Windows - fix pathfinding backwards move and penalty in battle - fix City of the dead incorrect monster info - update no data error message - do not allow to manage monsters in unhired hero dialog - AI supports additional harmful combat spells - do not open the hero screen in readonly mode if it was opened from the meeting screen, just disable the DISMISS button instead - do not show external music option with an absent folder - wait for user input at the end of winning video - fix double exit dialog appearance - fix missing cursor in Army Bar for SDL 1 - ranged AI troops should move when blocked by a strong unit - if the unit attacks twice, apply the second attack to the same cell as the first attack - fix double hex attack target for AI troops - show the build window with the OKAY button disabled if there is not enough resources to build this building - AI uses mass damage spells in combat - improvements for AI wide unit pathfinding - fix the pathfinding near monsters: do not allow the hero to pass through the monster - fix MIN and MAX button drawings - do not give an extra day of life to a player without castles - preserve unit direction in battle if it moves strictly vertically - do not allow the visiting hero to learn Library spells if the Library hasn't been built yet - fix inability to reorganize troops in Kingdom View dialog version 0.9.1 (04 March 2021) - add "Artifacts" category in Oracle Screen - add barrier fading animation - improve AI retreat condition - fix Split-related issues and implement some new techniques - disable retreat for auto battle mode - fix object's visited info and grammar - fix issues with path finding during combat to avoid being stopped by moat - fix incorrect names of Ballista and Turret during battle - add missing shadow for arrow cursor on SDL2 - fix ACCEPT button font for Good Interface - fix AI wide units pathfinding and moat logic - allow to modify a hero during level up - fix summon boat logic - fix monster movement nearby moat - AI does not chase faster units during battle - fix hero shadow drawing on world map - fix missing animation frame for wince - fix object fading animation on world map - fix missing last lost hero condition reset - fix simultaneous animation of boat while during another building construction - change logic in monster hiring window - fix save loading crash for broken saves - add middle resolution status support - do not show waiting cursor for an exhausted hero - fix post Daemon Cave missing music - fix well's max button drawing - fix incorrect castle focus while visiting by a hero - fix fog drawings - reduce income window area in towns - fix autosave option logic - fix overlapped battleground objects - remember scenario difficulty while restarting or choosing a new scenario - fix multiple game freezes with MIDI music playback - add resizable window support on SDL2 - make deterministic bonus for hero level-up - fix wrong controller pointer speed option name and touch coordinates translation - add auto battle resolve mode to the game - add logic to support video frame rescaling - optimize screen resolution logic - fix garrison strength estimation by AI - fix magic gardens priority if object capture is enabled - optimize AI's troop placement before the battle - use same colour for all battlegrounds - change text in map size hint, to be displayed in more pleasant manner - load game button from UI should lead to common load game logic - initial implementation of "View World" - fix application crash for drawings - change building status message for Dwellings - fix crash when entering ally castle - add Campaign continuation initial support - speed up rendering of World Map - fix spell book status on hovering - save fullscreen mode in configuration file while switching between modes - AI should avoid spellcasting if has advantage in battle - fix rendering with mouse emulation - fix extensive memory usage by dialog windows version 0.9.0 (04 February 2021) - make AI to buy magic books in castles - fix infinite AI turns - optimize AI hero movements - do not visit useless objects by AI heroes - fix AI castle defence mechanics - restore cursor theme after HeroDialog or CastleDialog - do not show marketplace icon for joining creature dialog is no markets exist - allow to retreat for a hero with no castles - add more logic for AI jumping into whirlpools - do not block shooters with overpowered AI army - fix overlapped flag in Knight's castle - disable actions while buying a hero or during construction - change the text for the number of secondary skills in level up window - play music after showing New Week/Month dialog - reset hero direction when recruiting - align dates by left in the load file dialog - cap Necromancy bonus to 100% instead of 90% - fix multiple bugs in Kingdom View - fix Transcribe Spell Description Text - cancel spell selection with ESC button in combat - add a separate option for displaying terrain penalty - fix missing music for Load Game option - fix Scouting skill bugs and make crystal ball work - do not play dwarves' resist sound during mass spells - fix Berserk spell logic - fix Chain lightning spell logic - fix castle flag drawing for nearby hero - add empty line for visited info - allow Anti-magic spell on mirror-master creature - do not draw wall for destroyed bridge - fix pathfinding issues and missing path display - fix Kingdom overview crash - do no freeze building animation during construction or hero's hiring - show Spade of Necromancy artifact bonus in Necromancy Skill window - display troop counter only for static animations - show pointer cursor most of the time during combat - fix castle's object positions - display correct text when right-clicking on map tile - fix path selection for AI hero in patrol - fix town portal buttons in Good interface - add new standard window with shadow - fix events when AI was choosing to disembark on empty tiles - change highlighted cursor area for spells during battle - implement Alt hotkey to unite stacks of same type - implement CTRL split even with selected troop - add touchpad support - fix joining creature logic with the lack of gold - quick Info dialogs have fixed position when clicked from right bar - add a check for special objects that you can't move through - after AI loses to either castle or other player, center the view on them - fix animation order of creatures during combat - do not close File Save window while cancelling file overriding - fix marketplace button drawings in Surrender window - fix castle's battlefield drawings - hide Marketplace button for Surrender window - always draw troop counter at the top of monster sprites - fix a log message when a tower kills one creature - display battle grid for whole battlefield - fix object drawings on battlefield - Bone Dragon morale malus shows in battle and in dialog window - add PNG image saving support for SDL2 - add palette support for SDL2 - fix morale and luck sprites positioning in meeting screen - fix mouse cursor switching while navigating on hero or castle panel - always display the correct monster combat speed - fix Player Rankings message bar - add game controller support - add Gold version support for video playback - fix Earthquake spell animation - fix enemy castle capture action - speed up interface drawings for World map - fix Hydra's movement during battle - set correct size for spell point and experience point zone in hero window - fix income info clickable area at castle options window - add spells into Hero's book at the start of a map - add initial Nintendo Switch support - fix Battle Only mode UI - fix logic in mouse button press - make interface itemsbar adjustment while fixing some split logic - add FPS display support - fix clickable area for Heroes and Castles in their lists - differentiate save files based on game type - fix a crash while trying to get settings path on SDL 2 - fix path calculation for 2-hex creatures in battle - add software emulation support for cursor version 0.8.4 (23 December 2020) - speed up sprites allocation and deallocation (noticeable for low-profile devices) - add extra info in first game message about F4 key - fix moral and luck indicator messages - fix elements' layout at monster info dialog - disable monster attack using Dimension Door - remove click area around "exit" button in castle dialog - make elements gray for inactive game settings - fix cursor disappearance during resource collection on SDL 1 - fix Summon Boat animation - fix Summon boat logic - fix popup message for Abandoned Mine - fix Select button movement in Scenario window - fix position of monsters in battle - modify monster upgrade window for 3 monsters - make proper calculation of movement points - fix Hide interface reset issue - show correct artifact transfer dialog after battle - fix impassable barrier - fix meeting hero update after exiting hero's dialog - adjust troop separation dialog position - fix incorrect hero values for Kingdom Overview screen - fix a crash in multiplayer game selection - fix Transcribing spell scrolls logic and UI - change info bar text at bottom of screen when mouse cursor positioned under spell book - fix starting unit values - implement smooth slider - add an option to reveal secondary skill info by right click - implement Stack split hotkeys (Ctrl and Shift) - fix Resurrect logic - ignore fog for dimension door - disable gift button for a single player - fix empty castle capture behavior - fix interaction with skeleton on map - persist random race for new games - fix road penalty text - fix incorrect town frames for evil town portal UI - speed up generation of few images - fix UI defect after closing hero meeting dialog - fix UI defect after using town portal spell - modify a message of joining 1 creature due to diplomacy - do not show income popup window for exit button in castle screen - change Tavern dialog caption color to yellow - fix rendering and mouse behavior for SDL2 - fix elements layout for surrendering dialog - fix shadow under battle summary dialog - fix surrendering cost calculation - fix Wagon sounds - fix castle and hero interaction with mouse in Kingdom Overview - play sound only after a battle with a monster to grab an artifact - change window title font color to yellow - fix Pyramid bad luck position - correct few sprites of mini-monsters - add a popup message for View Hero button - remove spaces for Level messages - clear message log for retreat case - add missing Info word for artifact status bar - add popup messages for next/previous pages in spell book - use keypad Enter as a normal one - add magic book image to popup window for no gold case - add evil UI elements for "Town portal" spell window - show proper information about enemy based on Thieves Guild count - fix layout for campaign selection window - fix wrong title and wrong text for Freeman's Foundry window - fix battle log text layout - use Delete key to erase a part of save filename - fix post game conditions - do not reset army while moving a hero to castle - start battle instantly if sounds are off - add captain portraits in Kingdom Overview - correctly reduce hero army after whirlpool - fix Mine info dialog near hero - reset Adventure Options buttons when cursor move out - draw shadow for empty boat - do not show army formation with no captain - fix stack overflow when using spells - fix disappearing hero while attacking monsters standing on a barrier - select a hero with move points when the focus is on a castle - increase Meteor Shower spell animation - fix spell order in magic book - change Artesian spring logic - fix Alchemist Tower artifact manipulation logic - assemble Battle Garb of Anduran artifact for hero meeting - place a hero into castle at the start of game - change map size popup window text - fix primary skill frame color in hero meeting dialog - add a title for joining army dialog - fix battle false hex shadow - fix drawings for meeting hero dialog - fix map loading message typo - update hero availability pool after hero purchase - show hero's name in magic book related windows - fix scenario selection stuck condition - fix portraits/hero information display in battle only mode - fix normal moral icon image - fix enemy army threat check for AI - fix changing boat direction by AI at boarding - fix Sorceress castle background image - fix missing spell point digits version 0.8.3 (04 November 2020) - protect higher value (based on buildings built) AI castles first - recruit AI heroes in castles other than the first one in the list - improve castle build order for AI (priority on gold income buildings) - improve castle monster recruitment - fix slowed flying creature movement - remember boat direction when a hero boarding it - update Town Portal dialog - fix "jump" flying animation when unit has to turn for attack - make one rumour per week in Taverns - fix a case of second attack on blinded creature - fix Monster Info dialog in evil interface - fix few castle icon sprites - speed up video loading - play sound on shipwreck pickup - fix Freeman Foundry UI - fix War Troll's stone sprite - fix Cavalry sprite - fix mage guild dialog message - change troop logic exchange during hero meeting - fix boat summoning animation - AI heroes are able to exchange armies and artifacts - add road connection drawings for Barbarian castle - fix castle defense trigger condition - do not show teleport animation in some AI cases - center Good and Evil interface windows - teach AI units to block enemy archers - show boat froth sprite when stationary - fix mine guardians sprites - fix battle only mode - improve army merge logic - fix lighthouse missing movement bonus - fix boat and hero positions - speed up drawings for World Map - fix pass image position on the world map - give at least 1 skeleton via necromancy after victory - fix Hero screen status bar message position - fix Hero meeting screen title position - change Pyramid Bad Luck message - fix Hero's portrait position in hiring window - fix town population status text position - fix boat animation and castle building rendering - fix troop counter window position - fix adjacent monsters when removing one - add after play animation - fix redraw order for unit count - use hero spell power when applying Earthquake - fix Lighthouse fog clearance - change Hut of the Magi behavior - add game resolution popup window - fix spelling in castle dialog - always show building requirements even if the building is already built - fix Knowledge message in Arena - five every magic guild a guaranteed damage and non-damage spell - fix OK button in joining army window - adjust few elements in resolution window - fix battle option text overlapping - adjust post battle shadowing - change main menu popup window descriptions - add a support of hero AI patrol mode set in scenario - make autosave with capital letters - disable spell animation after mine was cleared - save boat direction when disembarking - set parts of wide objects as visited - allow AI player to start it's move with action - use original difficulty bonuses for the game - check armies for validity before starting the battle - remove mud pool from cover object list - fix non-necro undead units decreasing morale in an all-undead army - remember Ghost number after the battle - AI is able to collect/capture multiple objects on the same move - adjust heuristics to make sure AI doesn't miss important objects - fix a bug where AI was able to buy boats in a castle without shipyard - fix the artifact spell damage modifiers - display a proper message for Sea Bottle - make AI more aggressive when dealing with archers during battle - replace "Ship Wreck" word with "Shipwreck" - set random starting experience for starting heroes - play flotsam disappearing sound after closing the UI window - fix income window drawings - when retreating, give the hero its default army instead of 1 T1 unit - stop battle after AI kills the last creature - use word 'one' instead of '1' in battle log - fix Thieves Guild background - fix visibility of AI hero moving close to fog - limit Player name text within given input box - show skip turn message during AI turns - allow to cast a spell with no move points - fix incorrect resurrected troops after battle - honor player count for Hot Seat mode - fix system options UI dialog - remove scrollbars from interface in battle only mode - add fading animation for defeated AI hero - fix time loss conditions in Info window - fix displaying of remaining move points - fix multiple text issues - modify messages for splitting army window - fix the clipped boat sprite version 0.8.2 (04 October 2020) - make correct spawn of neutral monsters and their growth - display correct window while right clicking on a monster in Kingdom Overview - add a transition while boarding and off-boarding boats - make hero animation movement smooth - fix price of loyalty artifact selection - fix tents/barriers from Price of Loyalty - fix monster placement at the beginning of months - fix missing dialog box in some rare cases - do not show journal scrollbar when it's not open - fix second attack for blinded creatures - fix typo in description of "Knowledge" skill at hero screen - replace the original project AI with an enhanced version of AI (making it not that stupid) - save neutral army monsters after battle - always reduce moral for visited Graveyard and Ship Wreck - allow heroes to move last step with remaining move points - update village's shadow after a castle was built - fix drawing castle's shadows under roads - fix missing objects on puzzle map - fix empty puzzle map - fix Ultimate image on puzzle map - fix puzzle map color scheme - fix treasure location map doesn't update after visiting obelisk - fix amount of creatures position in monster information dialog - fix object and shadow drawings on the World Map - fix few hero's names - draw all dead troops on the battlefield - allow to open hero's dialog during heroes meeting - fix dialog frame generation - fix bookmark positions in Spell Book - fix text position in creature recruit window - add Information window by right click on buttons in File dialog - add the word "Skill" for "Attack Skill" and "Defense Skill" in monster window - fix font generation and its position - fix popup windows for Spell Book - fix cursor for Adandoned mine protected by monsters - fix scrollbar in Settings window - extend campaign support - add proper AI base code for battle - change hero's description in tavern - use single OKAY button for Config window - don't shift High Score background for high resolutions - shift second hero's portrait in town - change unit movement speed in battle under spells - add dynamic color for item selection - add a popup window for clicking on Income in castle construction screen - remember move points of surrendered/retreated hero - fix bottom-to-top scrolling issue by keyboard key - reset dismissed hero's army - focus on a new hired hero in a castle - check magic resistance against Chain Lighting spell - fix multiple UI issues in Hero's Screen - add proper credits - center Hero's Options dialog - display spell points in magic book from bottom to top - display Liches for the City of the Dead - fix hero button position for joining window - fix a crash for Mirror image creature - reset music after exiting a castle - fix Daemon Cave incorrect window - fix Blind spell name - fix victory message in battle - fix File Options window position - fix text color for joining window - fix monster info shadow position - remove extra wince animation for cold ray spell - fix some icon positions in hero's dialog - center spell point text in hero's dialog - add word 'player' for defeated player window - fix creature shadow contour issue - add button choice support for campaign - fix cycling colors for mirrored Genie - add external music support by default - fix "battle: show damage info" option - fix Bloodlust animation - speed up calculations for path finding algorithm - fix caption issue with map's objects - fix missing resolutions and black screen for SDL 1 - add a dot at the end of multiple messages - fix few Phoenix frames - make scrollbar more user friendly in battle log - fix game info buttons - disable Next Hero button if none of heroes can move - fix multiplayer player count window position - fix graphical issues in big text font - fix creature animation going out of window - fix few scenario information window UI issues - optimize video playback - fix map size button state selection - fix New Game window UI elements - fix a crash while downgrading resolution in 8-bit mode - remove "castle: allow recruits special/expansion heroes" option - fix sky view status - fix generated window position - make Exit button disabled for popup windows in castle - fix Resurrection animation frame and moving dead units version 0.8.1 (04 September 2020) - play video intro at the opening - set focus on a hero while opening spell book - set minimum Spell Power to be always 1 - fix castle mini status icon - fix drawings of Mage Guilds in castles - remove multiple redundant / useless configuration settings - fix message dialog shadow - add proper support of configuration file - fix shift after opening and closing system options dialog - fix Gift button for evil interface - fix drawings of letters - fix Resurrect spell animation - add a support of disabled buttons - fix Air elemental drawings - remove "Manage creatures" button's shadow - add missing glowing effects of units in UI - fix screen dimming effects - change missing file message - fix Armageddon animation - update interface after exiting hero dialog - fix incorrect map extension crash - show diplomacy window always and fix marketplace button - add dot at the end of player's turn text - always center scenario window position - fix map difficulty text and its position - use proper config background window image - reset interface while changing its type - add game scenario dialog shadow - do not refocus after exiting castle's window - make Disabled Campaign button in High Score board - disable Campaign button if no files present - fix abandoned mine cursor - fix Sphinx message - modify Graveyard robber message - polish "battle only" button - show upgrade button if there's not enough money - fix lighthouse spelling - fix Dismiss button position in Hero's dialog - add missing title for graveyard message - fix graveyard message - fix marketplace text - show a dialog if a hero has no space for Spell Book - don't display artifact dialog for unknown artifact - fix an ability to skip button state by using a keyboard - apply Chain Lightning damage on allies as well - skip turn of resurrected troop - fix EXIT button position in creature window - fix AI turn crash - remove black background over army counter - don't show start and end position for monster log movement - add logic to handle inability to buy monsters in well - don't draw tent images in the castle window avoiding weird animation during castle construction - remove move penalty for visiting whirlpool/teleport - fix descriptions for Thief's Guild and for Pathfinding skill - fix logic of Town Gate spell - fix wrong OKAY button in System Options for Evil interface - skip heroes who do not have move points for move or sleep version 0.8 (26 July 2020) - add in-game option to change resolution - add fade-in / fade-out animation for AI heroes - fix logic when all stone liths were connected to each other even with different types - fix logic for stone liths in regard to allies - fix flickering of status bar in castle - fix attack cursor while pointing a monster - fix wolf's low attack animation - fix monster attack and fade animation on the World Map - add boat building animation - adjust hero battle dialog to align text - display boat froth sprite only if away from the coast - use correct sprite for castle's exit button - do not play sounds in castle's window when restoring the application - fix cursor for empty enemy castle - fix incorrect popping window in tavern - fix delayed mouse click on a cell during battle - fix hero's level up window with single secondary skill - fix system menu OKAY button - add SMK video file support - reset sound only if battle is shown - add support for ogg music from GOG version - fix castle drawings on the World Map - add Holy Shout spell blur effect - fix pathfinding calculations - increase the amount of sound channels to 16 - add death wave/ripple spell effect animation - fix Hypnotize and Berserk spell behaviors - correct battle hero animation - update elemental storm spell - "Summon boat" spell should not be used on a board - do not focus screen on a hero after adjusting scroll speed - fix Shipwreck empty battle - use only screen supported resolutions for the game - update focus logic for AI moving out of the fog - change mass spell icons - add frame border to highscore screen - reuse SDL2 texture for drawing speedup - adjust arena object dialog - set monster to static state after receiving a hit - fix mage guild window background - fix hero's status bar item locations - fix going out the battle window animation - shift battle log window by 1 pixel up - add a partial drawing support for disabled buttons - add Chain Lightning spell animation - add color cycling for active monster aura - fix incorrect hero's interaction with objects during movement - adjust flags animation with battle speed - make hero's movement smoother - show a proper status window message depending on resolution - fix last unit being hit 1 frame missing animation - remove Skill word from level up window text - add full animation in castles - update cursor while pointing over empty interface area - make "Next hero" button disable when no heroes exist - fix extra shifting upon landing for flying monsters - adjust music fade in effect and better sound selection - add monster animation on the World Map - fix fullscreen behavior in SDL 2 - fix World Map drawings and screen jumping during hero movement - fix display drawing during mouse movement in SDL 2 - fix ship shifting upon boarding it - add keyboard support for navigation in spell book - display different message in Mage Guild if no hero is in castle - fix text positioning in status bar - add lightning spell animation - do not run idle monster animation at every human's turn - fix marketplace window UI issues - fix creature contour drawing - add keyboard support for navigating between castles and heroes - add hero's flag animation in a world map - draw post attack animation in parallel so that battle animation does not look slow - add a flag to castle's captain - fix cursor offsets so that the cursor does not jump while changing its type - fix incorrect drawings for many interactive dialogs - make yellow frame for not available building info - fix hero hiring animation position - fix incorrect mouse cursor type during hero's movement by keyboard - add magic resist sound - add missing Skill word for Attack and Defense - fix fog drawings - multiple tweaks for AI - fix Spell Power naming in its icon and description - add animation of many objects on World Map - fix Mirror Image clone placement - add smooth screen scrolling during hero's movement - fix screen shifting when a hero initiates movement without movement points - complete hero's movement cell if an user clicks by mouse during hero's movement - add Stunning animation - fix Resurrect spell animation and logic - fix Armageddon and Earthquake animations - show gray text for spells which need more than current hero spell amount - set correct yellow and white font - fix BloodLust spell animation - fix incorrect stunning state rendering - add mirror image effect - fix puzzle view on high resolutions - remove Visual Studio 2015 Redistributable package dependency for 64-bit application on Windows - add F4 fullscreen hotkey support - set correct animation for Holy Word and Holy Shout spells - fix join/flee condition - fix castle siege logic - add an improved battle obstacle selection - fix multiple places with shadows while using alpha blending in SDL 1 - hide World Map for next turn in hot seat mode - add an ability to use scroll by mouse in SDL 2 - fix transparency on World Map and during Battle - fix message content and scrollbar for Town Portal spell - fix a crash during hero's dismiss - fix retaliation condition logic - fix a crash while checking hero with focused castle - change monster value heuristics for battle and strategic decisions - fix Mutant Zombie HP value - add color cycling for glowing effects on monsters during battle - show adventure map quick info by right click for dimension door spell - make hero's path visible after using a portal - add a log message for flying monsters - fix teleport and luck animation - fix ripple effect for disrupting ray and cold ray - update focus after hero's dismiss - add a condition to check down bridge for unit defense calculation - set World Map border size depending on resolution - add flag animation of neutral captain in a castle/town - do not move to current hero after closing hero's stats window - make interface background more uniform on high resolutions - add exit button during Dimension Door spell - update sounds and music upon teleport spell usage - add bad luck animation - fix blurred buttons in Adventure Panel - fix teleport delay in animation - sort spell icons by their duration in monster statistics window - fix defense formula for monster during battle - fix application exiting behavior (add support for Alt+F4 in Windows for SDL 2) - ignore bad spell status for Archmage attack - set correct post battle video sequence - fix hero fading animation - add min and max buttons for troop separation dialog - speed up rendering - fix hero's flag position in battle - fix inability to cast another spell upon cancellation teleport spell - play resource collection and sound after reading a dialog - fix monster frame jumps from multiple spells - fix medusa's stunning effect icon - fix Lich animation - fix monster count window position during battle - fix Archmage dispel post attack effect - fix Tower and Magic Arrow animation - fix the logic for Summon Boat spell - fix Identify Hero spell behavior - fix 0 monster count for Visions spell - add correct battle speed adjustment formula - show disable Dismiss button for a hero in a castle - fix idling condition check - add correct attacks and timing for monster animation during battle - give a control to an opponent over hypnotized monster - do not shift application window while loading a map for SDL 2 - fix Haunt spell incorrect behavior - add shadows to most of dialogs - play animation of treasure chest after the collection - fix incorrect position of a stunned monster - remove red cross over town's icon - fix new dwelling building animation - fix an ability to continue movement after Town portal spell - fix quick info window position at world map - show reflected monster animation in a dialog for right-side monsters - do not auto disable dimension door spell nearby world map edges - remove tiny ship icon from hero's status - do not move a hero over a portal above another hero - add hero movement interruption by left click - use shadowing for end movement cell during battle - fix inability to move artifacts - show a message if not enough points for digging - fix monster attack at the start of a new game - add fading animation after battle - limit Dimension Door spell range - fix invisible hero's path after Dimension Door spell - fix landing from a ship event to attack a monster after landing - fix incorrectly mixed spells for hero's book - fix Mass Cure spell icon - fix spell icon size and position for monster's info - fix cursor behavior beyond world edge - set correct position of monster in dwelling info - fix minimap colors - add an option to switch music type in settings - fix incorrect map object naming - fix hero, castle and status army info - add support for swapping palettes - fix hero movement at low speeds - fix display info of resources with scouting - fix spell sorting crash and mage guild positioning - add an optional aspect ratio correction in full-screen mode - fix a crash for unhired hero - fix alignment of text in monster info dialog - fix shifted monster position in recruit window - fix negative resources in kingdom possession - rework of midi music and sounds - fix current unit animation freezing state during battle - draw main menu as a background for High Score window - fix a case when not destroyed castle gate remains opened after a troop from a castle crossed the bridge during battle - fix Phoenixes naming - fix EXIT button position in castle options - set a correct position for buttons in Configuration dialog - fix Genie special ability - fix surrender action during battle - fix UI value input selectors - fix hero animation in battle - fix hero position in battle - add monster animation in army's info dialog - add monster movement animation in Well - use BIN file information for correct monster animation - fix unit damage formula - fix morale and luck events - add *nix case insensitive file access - make Army Order window during battle optional - fix flying monster animation offset - fix a crash for hot seat game start - add quit dialog popup window and change File Options dialog behavior - fix incorrect position of recruit Creature Info window in higher than default resolutions - add scalable Sprites support - do not show text for hidden Dismiss button - fix multiple memory leaks - add double quotes to surrender dialog version 0.7 (18 April 2020) - add an edge of the World Map - complete hero movement cell if a player stops it by any keyboard button - add hotkey 'N' to start a new game within the World Map like in the original game - fix crashes in multiple maps during map loading or during AI turns - fix game freezes during animation of resources collection - make human turn always first - do not show file save loading dialog if no saves exist - fix 'Adventure Options' button is being blurred after pressing it once on MacOS - cancel a dialog by pressing close window button - do not allow to move artifacts for not hired heroes in a castle - allow file selection by double-clicking on a list entry - fix MAX button state for monster change in recruitment window - update a cursor while stopping hero's movement - add monster animation in castle's well - set correct animation speed - fix scrolling behavior and add diagonal scrolling - display recruit monster window even for 0 creatures within castle's well - fix castle well UI elements - show hero movement arrow icons with proper colors - fix resizing of mini-map in fixed interface mode - do not show Necromancy window for 0 obtained skeletons - show an empty area instead of tavern building in Necromancer castle - fix a state of no information displayed in Signs - fix a crash after loading a save and starting any new game after - fix a crash during loading empty or newly saved configuration file - add initial campaign support (very early beta) - fix crystal resource position - fix hero path cursor display - add missing option's titles in Battle Option dialog - fix a popup messages for different types of resources at the World Map - fix application title disappearing after loading any map on SDL 2 - fix disabled black surrender button in hero's options in SDL 2 mode - move creature window info to the top of the castle screen - make "Buy from well" as a default option - remove speed 0 for battle and the World Map - fix spell book is being displayed in semi transparent way - hero path is recalculated each time when an user chooses the hero - show creature information in recruitment building - fix incorrectly displayed shadows on the World Map for SDL 2 - change AI behaviour for castle building strategy - fix positions of labels in in-game settings - remove 'Dismiss' button for a hero who is not hired yet in a castle - fix incorrectly shown message of found resources in the World Map bottom-right window - fix battle grid - add missing text in battle options dialog - fix World Map UI is being drawn inside castle options - fix shadow display in a battle - disable Spell button for castles - change font color for a creature's name - fix observation tower coverage area - hide dismiss button for monsters if a hero has only one stack of them - fix incorrect message for Shipyard by right click - fix missing battle hexagonal net was not appearing if enabled until next battle - display a warning message about missing files from the original game - disable Continue Movement button for a hero which does not have chosen path - required building names are displayed on a separate line each - fix no building image for already built building for right mouse click - fix incorrect image for Captains Quarters - renamed 'Free Heroes II' to 'fheroes2' - show disabled "Continue Movement" button for castle - fix number of monsters being displayed in a black box - add a script to download a demo version of the original game - set default display size to 640x480 pixels - add Windows build 30th August 2019 - move the repository to https://github.com/ihhub/fheroes2 fheroes2-1.0.12+dfsg/cmake/000077500000000000000000000000001456075706000153215ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/cmake/FindSDL2_image.cmake000066400000000000000000000103421456075706000207720ustar00rootroot00000000000000# Licensed under the ISC license. Please see the LICENSE.md file for details. # Copyright (c) 2019 Sandro Stikić #[[============================================================================ FindSDL2 --------- Try to find SDL2_image. This module defines the following IMPORTED targets: - SDL2::Image — Link against this. This module defines the following variables: - SDL2_IMAGE_FOUND - SDL2_IMAGE_VERSION_STRING - SDL2_IMAGE_LIBRARIES (deprecated) - SDL2_IMAGE_INCLUDE_DIRS (deprecated) #============================================================================]] # Ensure SDL2 is installed. find_package(SDL2 REQUIRED QUIET) if(NOT SDL2_FOUND) message(FATAL_ERROR "SDL2 not found") endif() # Look for SDL2_image. find_path(SDL2_IMAGE_INCLUDE_DIR SDL_image.h PATH_SUFFIXES SDL2 include/SDL2 include PATHS ${SDL2_IMAGE_PATH} ) if (NOT SDL2_IMAGE_LIBRARIES) # Determine architecture. if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(_SDL2_IMAGE_PATH_SUFFIX lib/x64) else() set(_SDL2_IMAGE_PATH_SUFFIX lib/x86) endif() # Look for the release version of SDL2. find_library(SDL2_IMAGE_LIBRARY_RELEASE NAMES SDL2_image PATH_SUFFIXES lib ${_SDL2_IMAGE_PATH_SUFFIX} PATHS ${SDL2_IMAGE_PATH} ) # Look for the debug version of SDL2. find_library(SDL2_IMAGE_LIBRARY_DEBUG NAMES SDL2_imaged PATH_SUFFIXES lib ${_SDL2_IMAGE_PATH_SUFFIX} PATHS ${SDL2_IMAGE_PATH} ) include(SelectLibraryConfigurations) select_library_configurations(SDL2_IMAGE) endif() # Find the SDL2_image version. if(SDL2_IMAGE_INCLUDE_DIR AND EXISTS "${SDL2_IMAGE_INCLUDE_DIR}/SDL_image.h") file(STRINGS "${SDL2_IMAGE_INCLUDE_DIR}/SDL_image.h" SDL2_IMAGE_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL_IMAGE_MAJOR_VERSION[ \t]+[0-9]+$") file(STRINGS "${SDL2_IMAGE_INCLUDE_DIR}/SDL_image.h" SDL2_IMAGE_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL_IMAGE_MINOR_VERSION[ \t]+[0-9]+$") file(STRINGS "${SDL2_IMAGE_INCLUDE_DIR}/SDL_image.h" SDL2_IMAGE_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL_IMAGE_PATCHLEVEL[ \t]+[0-9]+$") string(REGEX REPLACE "^#define[ \t]+SDL_IMAGE_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_IMAGE_VERSION_MAJOR "${SDL2_IMAGE_VERSION_MAJOR_LINE}") string(REGEX REPLACE "^#define[ \t]+SDL_IMAGE_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_IMAGE_VERSION_MINOR "${SDL2_IMAGE_VERSION_MINOR_LINE}") string(REGEX REPLACE "^#define[ \t]+SDL_IMAGE_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL2_IMAGE_VERSION_PATCH "${SDL2_IMAGE_VERSION_PATCH_LINE}") set(SDL2_IMAGE_VERSION_STRING ${SDL2_IMAGE_VERSION_MAJOR}.${SDL2_IMAGE_VERSION_MINOR}.${SDL2_IMAGE_VERSION_PATCH}) unset(SDL2_IMAGE_VERSION_MAJOR_LINE) unset(SDL2_IMAGE_VERSION_MINOR_LINE) unset(SDL2_IMAGE_VERSION_PATCH_LINE) unset(SDL2_IMAGE_VERSION_MAJOR) unset(SDL2_IMAGE_VERSION_MINOR) unset(SDL2_IMAGE_VERSION_PATCH) endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(SDL2_image REQUIRED_VARS SDL2_IMAGE_LIBRARIES SDL2_IMAGE_INCLUDE_DIR VERSION_VAR SDL2_IMAGE_VERSION_STRING) if(SDL2_IMAGE_FOUND) set(SDL2_IMAGE_LIBRARIES ${SDL2_IMAGE_LIBRARY}) set(SDL2_IMAGE_INCLUDE_DIR ${SDL2_IMAGE_INCLUDE_DIR}) # Define the SDL2_image target. if(NOT TARGET SDL2::Image) add_library(SDL2::Image UNKNOWN IMPORTED) set_target_properties(SDL2::Image PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${SDL2_IMAGE_INCLUDE_DIR}") if(SDL2_IMAGE_LIBRARY_RELEASE) set_property(TARGET SDL2::Image APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) set_target_properties(SDL2::Image PROPERTIES IMPORTED_LOCATION_RELEASE "${SDL2_IMAGE_LIBRARY_RELEASE}") endif() if(SDL2_IMAGE_LIBRARY_DEBUG) set_property(TARGET SDL2::Image APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG) set_target_properties(SDL2::Image PROPERTIES IMPORTED_LOCATION_DEBUG "${SDL2_IMAGE_LIBRARY_DEBUG}") endif() if(NOT SDL2_IMAGE_LIBRARY_RELEASE AND NOT SDL2_IMAGE_LIBRARY_DEBUG) set_property(TARGET SDL2::Image APPEND PROPERTY IMPORTED_LOCATION "${SDL2_IMAGE_LIBRARY}") endif() endif() endif() fheroes2-1.0.12+dfsg/cmake/FindSDL2_mixer.cmake000066400000000000000000000103321456075706000210330ustar00rootroot00000000000000# Licensed under the ISC license. Please see the LICENSE.md file for details. # Copyright (c) 2019 Sandro Stikić #[[============================================================================ FindSDL2 --------- Try to find SDL2_mixer. This module defines the following IMPORTED targets: - SDL2::Mixer — Link against this. This module defines the following variables: - SDL2_MIXER_FOUND - SDL2_MIXER_VERSION_STRING - SDL2_MIXER_LIBRARIES (deprecated) - SDL2_MIXER_INCLUDE_DIRS (deprecated) #============================================================================]] # Ensure SDL2 is installed. find_package(SDL2 REQUIRED QUIET) if(NOT SDL2_FOUND) message(FATAL_ERROR "SDL2 not found") endif() # Look for SDL2_mixer. find_path(SDL2_MIXER_INCLUDE_DIR SDL_mixer.h PATH_SUFFIXES SDL2 include/SDL2 include PATHS ${SDL2_MIXER_PATH} ) if (NOT SDL2_MIXER_LIBRARIES) # Determine architecture. if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(_SDL2_MIXER_PATH_SUFFIX lib/x64) else() set(_SDL2_MIXER_PATH_SUFFIX lib/x86) endif() # Look for the release version of SDL2. find_library(SDL2_MIXER_LIBRARY_RELEASE NAMES SDL2_mixer PATH_SUFFIXES lib ${_SDL2_MIXER_PATH_SUFFIX} PATHS ${SDL2_MIXER_PATH} ) # Look for the debug version of SDL2. find_library(SDL2_MIXER_LIBRARY_DEBUG NAMES SDL2_mixerd PATH_SUFFIXES lib ${_SDL2_MIXER_PATH_SUFFIX} PATHS ${SDL2_MIXER_PATH} ) include(SelectLibraryConfigurations) select_library_configurations(SDL2_MIXER) endif() # Find the SDL2_mixer version. if(SDL2_MIXER_INCLUDE_DIR AND EXISTS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h") file(STRINGS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h" SDL2_MIXER_VERSION_MAJOR_LINE REGEX "^#define[ \t]+SDL_MIXER_MAJOR_VERSION[ \t]+[0-9]+$") file(STRINGS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h" SDL2_MIXER_VERSION_MINOR_LINE REGEX "^#define[ \t]+SDL_MIXER_MINOR_VERSION[ \t]+[0-9]+$") file(STRINGS "${SDL2_MIXER_INCLUDE_DIR}/SDL_mixer.h" SDL2_MIXER_VERSION_PATCH_LINE REGEX "^#define[ \t]+SDL_MIXER_PATCHLEVEL[ \t]+[0-9]+$") string(REGEX REPLACE "^#define[ \t]+SDL_MIXER_MAJOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_MIXER_VERSION_MAJOR "${SDL2_MIXER_VERSION_MAJOR_LINE}") string(REGEX REPLACE "^#define[ \t]+SDL_MIXER_MINOR_VERSION[ \t]+([0-9]+)$" "\\1" SDL2_MIXER_VERSION_MINOR "${SDL2_MIXER_VERSION_MINOR_LINE}") string(REGEX REPLACE "^#define[ \t]+SDL_MIXER_PATCHLEVEL[ \t]+([0-9]+)$" "\\1" SDL2_MIXER_VERSION_PATCH "${SDL2_MIXER_VERSION_PATCH_LINE}") set(SDL2_MIXER_VERSION_STRING ${SDL2_MIXER_VERSION_MAJOR}.${SDL2_MIXER_VERSION_MINOR}.${SDL2_MIXER_VERSION_PATCH}) unset(SDL2_MIXER_VERSION_MAJOR_LINE) unset(SDL2_MIXER_VERSION_MINOR_LINE) unset(SDL2_MIXER_VERSION_PATCH_LINE) unset(SDL2_MIXER_VERSION_MAJOR) unset(SDL2_MIXER_VERSION_MINOR) unset(SDL2_MIXER_VERSION_PATCH) endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(SDL2_mixer REQUIRED_VARS SDL2_MIXER_LIBRARIES SDL2_MIXER_INCLUDE_DIR VERSION_VAR SDL2_MIXER_VERSION_STRING) if(SDL2_MIXER_FOUND) set(SDL2_MIXER_LIBRARIES ${SDL2_MIXER_LIBRARY}) set(SDL2_MIXER_INCLUDE_DIR ${SDL2_MIXER_INCLUDE_DIR}) # Define the SDL2_mixer target. if(NOT TARGET SDL2::Mixer) add_library(SDL2::Mixer UNKNOWN IMPORTED) set_target_properties(SDL2::Mixer PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${SDL2_MIXER_INCLUDE_DIR}) if(SDL2_MIXER_LIBRARY_RELEASE) set_property(TARGET SDL2::Mixer APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) set_target_properties(SDL2::Mixer PROPERTIES IMPORTED_LOCATION_RELEASE ${SDL2_MIXER_LIBRARY_RELEASE}) endif() if(SDL2_MIXER_LIBRARY_DEBUG) set_property(TARGET SDL2::Mixer APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG) set_target_properties(SDL2::Mixer PROPERTIES IMPORTED_LOCATION_DEBUG ${SDL2_MIXER_LIBRARY_DEBUG}) endif() if(NOT SDL2_MIXER_LIBRARY_RELEASE AND NOT SDL2_MIXER_LIBRARY_DEBUG) set_property(TARGET SDL2::Mixer APPEND PROPERTY IMPORTED_LOCATION ${SDL2_MIXER_LIBRARY}) endif() endif() endif() fheroes2-1.0.12+dfsg/cmake/LICENSE.md000066400000000000000000000013721456075706000167300ustar00rootroot00000000000000Copyright © 2019 [Sandro Stikić](mailto:sandro@stikic.me) Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. **The software is provided "as is" and the author disclaims all warranties with regard to this software including all implied warranties of merchantability and fitness. In no event shall the author be liable for any special, direct, indirect, or consequential damages or any damages whatsoever resulting from loss of use, data or profits, whether in an action of contract, negligence or other tortious action, arising out of or in connection with the use or performance of this software.** fheroes2-1.0.12+dfsg/cmake/README.md000066400000000000000000000002531456075706000166000ustar00rootroot00000000000000# CMake modules to find SDL libraries These files are taken from [cmake-modern-findsdl2](https://github.com/opeik/cmake-modern-findsdl2/) and licensed under ISC License. fheroes2-1.0.12+dfsg/cmake/README_findsdl2.md000066400000000000000000000025611456075706000203710ustar00rootroot00000000000000


sdl2-logo
CMake Modern FindSDL2

CMake modules to find SDL2 and its extensions.

Usage • License • Acknowledgments

## Usage Add this repository as a submodule to your project. ```cmake git submodule add https://github.com/opeik/cmake-modern-findsdl2 cmake/sdl2 ``` Include the new modules. ```cmake list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/sdl2) ``` Find the package you want to use. ```cmake find_package(SDL2 REQUIRED) find_package(SDL2_ttf REQUIRED) find_package(SDL2_gfx REQUIRED) find_package(SDL2_image REQUIRED) find_package(SDL2_mixer REQUIRED) find_package(SDL2_net REQUIRED) ``` Link against it. ```cmake target_link_libraries(${PROJECT_NAME} PUBLIC SDL2::SDL2 SDL2::TTF SDL2::Image SDL2::Mixer SDL2::Net SDL2::Gfx) ``` If you're writing an application, link against `SDL2::SDL2`. If you're writing a library, link against `SDL2::Core` instead. ## License This project is licensed under the ISC license. Please see the [`LICENSE.md`](LICENSE.md) file for details. ## Acknowledgments * Thanks to [aminosbh](https://github.com/aminosbh) for creating similar cmake scripts which I referenced fheroes2-1.0.12+dfsg/docs/000077500000000000000000000000001456075706000151715ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/docs/DEVELOPMENT.md000066400000000000000000000063501456075706000173010ustar00rootroot00000000000000# [**fheroes2**](README.md) build and contribution guide ## Build Please follow the instructions below to be able to build the project from source: ### Windows * Go to the directory `script/windows` and run the file `install_packages.bat`. This script will install all the required packages to build the project using Microsoft Visual Studio. * If you have the original game, then copy the subdirectories `ANIM`, `DATA`, `MAPS` and `MUSIC` (some of them may be missing depending on the version of the original game) to the project root directory. * Alternatively, go to the directory `script/demo` and run the file `download_demo_version.bat`. This script will download a demo version of the original game, which is the minimum required for development. * Open the file `fheroes2-vs2019.vcxproj` (targeted for Visual Studio 2019) and build the project. * Visual Studio will automatically copy game files in the root directory to the build directory. ### macOS and Linux * Depending on your OS, run the following scripts to install the dependencies required for the build: * macOS: go to the directory `script/macos` and run the file `install_sdl2.sh`. * Linux: go to the directory `script/linux` and run the file `install_sdl2_dev.sh`. * If you have the original game, then copy the subdirectories `ANIM`, `DATA`, `MAPS` and `MUSIC` (some of them may be missing depending on the version of the original game) to the project root directory. * Alternatively, go to the directory `script/demo` and run the file `download_demo_version.sh`. This script will download a demo version of the original game, which is the minimum required for development. * Run the `make` command in the root directory of the project to build it. ### Android * Go to the directory `script/android` and run the file `install_packages.bat` if your development platform is Windows or `install_packages.sh` if your development platform is macOS or Linux. This script will install all the required packages to build the project using Android Studio. * Launch Android Studio, open the project in the `android` directory and run the build. ### PlayStation Vita If you would like to build and run this project on PlayStation Vita please follow the instructions on [**this page**](README_PSV.md). ### Nintendo Switch If you would like to build and run this project on Nintendo Switch please follow the instructions on [**this page**](README_switch.md). ### Build with CMake If you would like to build the project using CMake please follow the instructions on [**this page**](README_cmake.md). ## Contribution We welcome and appreciate any help, even if it is a tiny text or code change. Please read our [**contribution guide**](https://github.com/ihhub/fheroes2/blob/master/CONTRIBUTING.md) before starting work on a pull request. Not sure what to start with? Feel free to refer to [**good first issue**](https://github.com/ihhub/fheroes2/issues?q=is%3Aissue%20is%3Aopen%20label%3A%22good%20first%20issue%22) or [**help wanted**](https://github.com/ihhub/fheroes2/issues?q=is%3Aissue%20is%3Aopen%20label%3A%22help%20wanted%22) tags. fheroes2-1.0.12+dfsg/docs/GRAPHICAL_ASSETS.md000066400000000000000000000102321456075706000200650ustar00rootroot00000000000000# [**fheroes2**](README.md) graphical artist guide The original game has its graphical assets stored in the `HEROES2.agg` file and potentially the `HEROES2X.agg` file if the expansion pack The Price of Loyalty is present. These two AGG files are located in the `DATA` folder of the original Heroes 2 installation. For convenience's sake the fheroes2 project provides a set of tools and a script that artists can use to easily extract and convert the original graphical assets into viewable PNG files. New assets that the fheroes2 project adds to the game are included in the `files/data/resurrection.h2d` data file. This guide is designed for Linux, MacOS and Windows users. The source files to these tools are available in the project's repository. ## Where to get the tools You can download the precompiled tools for your operating system (OS) of choice here:
[Windows 32-bit](https://github.com/ihhub/fheroes2/releases/download/fheroes2-windows-x86-SDL2/fheroes2_tools_windows_x86_SDL2.zip)
[Windows 64-bit](https://github.com/ihhub/fheroes2/releases/download/fheroes2-windows-x64-SDL2/fheroes2_tools_windows_x64_SDL2.zip) [MacOS](https://github.com/ihhub/fheroes2/releases/download/fheroes2-osx-sdl2_dev/fheroes2_tools_macos_x86-64_SDL2.zip) [Linux Ubuntu 64-bit](https://github.com/ihhub/fheroes2/releases/download/fheroes2-linux-sdl2_dev/fheroes2_tools_ubuntu_x86-64_SDL2.zip)
[Linux Ubuntu ARM 64-bit](https://github.com/ihhub/fheroes2/releases/download/fheroes2-linux-arm-sdl2_dev/fheroes2_tools_ubuntu_arm64_SDL2.zip) Here is a list and short description of the various graphical artist tools: - extractor This tool extracts all the contents of the AGG files mentioned above. Among the extracted files are the ICNs which hold the graphical assets. - icn2img This tool converts the various graphical assets contained within the ICNs into PNG or BMP files. - pal2img This tool generates a PNG or BMP file from the palette file `KB.PAL`. It displays it as 16x16 tiles of 16 pixels each. Some colors in the game are used for the cycling animation. They are marked in the generated image by a black area in the top-left corner on a tile. Use these colors only if the animation is a requirement: - 214, 215, 216, 217 - 218, 219, 220, 221 - 231, 232, 233, 234, 235 - 238, 239, 240, 241 ## How to use the tools to extract the images Place the `HEROES2.agg` and optionally the `HEROES2X.agg` files in the same folder as you extracted the tools to. Then run the script called `extract_agg`. This will create two folders: `agg` and `icn`. Both of them have a subdirectory named `HEROES2`, and potentially a `HEROES2X` one, reflecting what AGG file the contents come from. In addition running the script will generate a PNG file of the palette of the game with the name `palette.png` in the same directory as the folders `agg` and `icn`. The folder named `agg` contains the untouched contents of the AGG files. Meanwhile, the folder named `icn` contains the PNG-converted graphical assets. These assets will be stored within folders named according to what ICN they were stored in, e.g. the folder `apanel` came from the ICN `apanel.icn`. The `icn` folder is in other words the most relevant one for graphical artists. ## Where is help needed We have labelled and tracked some issues that need help from graphical artists. These can be found [here](https://github.com/ihhub/fheroes2/issues?q=is%3Aopen+is%3Aissue+label%3Aassets). NOTE! Before starting to work on an issue make sure to check if someone isn't already assigned to it by looking at the relevant place within the issue. Also notify the fheroes2 team that you wish to work on the issue. This is to avoid duplicate work. ## The work process 1. Choose the item you are going to work on and notify the fheroes2 team to avoid duplicate work. 2. Present a prototype / complete enough work in your opinion of your work. 3. 1-2 members of the team will give their feedback. 4. Address the given feedback if any. 5. Repeat steps 3-4 until approval. 6. Expand the review team to 3-4 members and steps 3-4 are repeated. 7. Once everyone approves we will add this file into the `resurrection.h2d` data file and change the code in the fheroes2 source code to use it. fheroes2-1.0.12+dfsg/docs/INSTALL.md000066400000000000000000000401071456075706000166230ustar00rootroot00000000000000# [**fheroes2**](README.md) installation guide ## Requirements You will need to have a demo version or the full version of **Heroes of Might and Magic II** game to be able to play. We strongly advise to purchase the original game on [**GOG**](https://www.gog.com) or [**Ubisoft Store**](https://store.ubi.com) platforms. Alternatively, you can **download a free demo version of the game (in English only) using the bundled script**. See detailed instructions below. ## Installation Precompiled binaries of the release version are currently available for the following platforms and operating systems: * [**Windows**](#windows) * [**Windows installer**](#windows-installer) * [**Windows ZIP archive**](#windows-zip-archive) * [**macOS**](#macos) * [**Homebrew**](#homebrew-mac) * [**MacPorts**](#macports) * [**macOS ZIP archive**](#macos-zip-archive) * [**macOS native app**](#macos-native-app) * [**Linux**](#linux) * [**Flatpak**](#flatpak-linux) * [**Gentoo**](#gentoo-package) * [**Homebrew**](#homebrew-linux) * [**AUR package**](#aur-package) * [**Linux ZIP archive**](#linux-zip-archive) * [**Android**](#android) * [**PlayStation Vita**](#playstation-vita) * [**Nintendo Switch**](#nintendo-switch) Alternatively, you can download the precompiled binaries of the latest commit (snapshot) [**here**](#snapshots-latest-builds). ## Windows ### Windows installer * Download one of the following Windows installer packages:
[**Windows x64 (64-bit)**](https://github.com/ihhub/fheroes2/releases/latest/download/fheroes2_windows_x64_SDL2_installer.exe) or
[**Windows x86 (32-bit)**](https://github.com/ihhub/fheroes2/releases/latest/download/fheroes2_windows_x86_SDL2_installer.exe) * After downloading the installer, launch it and follow the instructions. * During the installation process, you will be prompted to extract game resources from the original game (select this option if you already have a legally purchased copy of the original game installed), or download the demo version of the original game and extract game resources from it (you can also do this later by clicking the appropriate shortcut in the program's group in the Windows Start menu). * If you purchased a copy of the original game only after installing fheroes2, you can run the `Extract game resources from the original distribution of HoMM2` shortcut in the program's group in the Windows Start menu. This script will try to perform an automatic search for an existing installation of the original game and extract all the necessary resource files. If it can't find an existing installation, you will be prompted to enter the location of the original game manually. * As an alternative to the previous step, you can manually copy the subdirectories `ANIM`, `DATA`, `MAPS` and `MUSIC` (some of them may be missing depending on the version of the original game) from the original game directory to the fheroes2 installation directory. ### Windows ZIP archive * Download one of the following Windows ZIP archives:
[**Windows x64 (64-bit)**](https://github.com/ihhub/fheroes2/releases/latest/download/fheroes2_windows_x64_SDL2.zip) or
[**Windows x86 (32-bit)**](https://github.com/ihhub/fheroes2/releases/latest/download/fheroes2_windows_x86_SDL2.zip) * After downloading the ZIP archive, extract it to a suitable directory of your choice. * If you have a legally purchased copy of the original game, run the `extract_homm2_resources.bat` script supplied in the ZIP archive. This script will try to perform an automatic search for an existing installation of the original game and extract all the necessary resource files. If it can't find an existing installation, you will be prompted to enter the location of the original game manually. * As an alternative to the previous step, you can manually copy the subdirectories `ANIM`, `DATA`, `MAPS` and `MUSIC` (some of them may be missing depending on the version of the original game) from the original game directory to the fheroes2 installation directory. * If you don't have a legally purchased copy of the original game, you can download and install the demo version of the original game by running the `download_demo_version.bat` script supplied in the ZIP archive. If you see complaints about missing DLLs when running fheroes2, then you may need to install the Microsoft Visual C++ redistributable package. You can download it using the following URLs: [**https://aka.ms/vs/17/release/vc_redist.x64.exe**](https://aka.ms/vs/17/release/vc_redist.x64.exe) - for 64-bit x64 fheroes2 builds;
[**https://aka.ms/vs/17/release/vc_redist.x86.exe**](https://aka.ms/vs/17/release/vc_redist.x86.exe) - for 32-bit x86 fheroes2 builds. ## macOS ### Homebrew If you are using [**Homebrew**](https://brew.sh/), you can install the game by running the following command: ```sh brew install fheroes2 ``` Follow the [**instructions below**](#macos-resources) to gather resources necessary for fheroes2 to function as expected. ### MacPorts If you are using [**MacPorts**](https://www.macports.org/), you can install the game by running the following command: ```sh port install fheroes2 ``` Then follow the instructions on the screen. Follow the [**instructions below**](#macos-resources) to gather resources necessary for fheroes2 to function as expected. ### macOS ZIP archive * Download the [**macOS ZIP archive**](https://github.com/ihhub/fheroes2/releases/latest/download/fheroes2_macos_x86-64_SDL2.zip). Currently only x86-64 binaries are provided. If you use a machine with an Apple Silicon chip, you should choose another installation method (using [**MacPorts**](#macports) or [**Homebrew**](#homebrew-mac)), or [**build the project from source**](DEVELOPMENT.md#macos-and-linux). * After downloading the ZIP archive, extract it to a suitable directory of your choice and then run the script `install_sdl2.sh` from the `script/macos` subdirectory. This will install the SDL libraries required to run the game. Follow the [**instructions below**](#macos-resources) to gather resources necessary for fheroes2 to function as expected. ### macOS native app * Download the source and compile with the `-DMACOS_APP_BUNDLE=ON` option (if using CMake) or using the following commands (with make): ```sh make FHEROES2_MACOS_APP_BUNDLE=ON make FHEROES2_MACOS_APP_BUNDLE=ON bundle ``` Follow the [**instructions below**](#macos-resources) to gather resources necessary for fheroes2 to function as expected. ### Gathering game resources Once you obtain the fheroes2 executable using any of the options above, you should follow these steps to load in the correct resources: * If you have a legally purchased copy of the original game in a self-extracting Windows executable (such as from GOG), you can utilize [`innoextract`](https://constexpr.org/innoextract/#use) to extract files out of the exe without having to use Wine/Windows emulation software on your *UNIX-based machine. * If you have a legally purchased copy of the original game, run the extract resources script which will prompt you to enter the location of the original game, and will extract all the necessary resource files. The script can be run from the following paths depending on how you installed fheroes2: * `fheroes2-extract-resources` if you used a package manager (MacPorts or Homebrew); * `script/homm2/extract_homm2_resources_for_app_bundle.sh` if you built from source using the [**macOS native app**](#macos-native-app) method; * `script/homm2/extract_homm2_resources.sh` for all other cases. * As an alternative to the previous step, you can manually copy the subdirectories `ANIM`, `DATA`, `MAPS` and `MUSIC` (some of them may be missing depending on the version of the original game) from the original game directory to * `~/Library/Application Support/fheroes2` if you built from source using the [**macOS native app**](#macos-native-app) method; * `~/.fheroes2` for all other cases. * If you don't have a legally purchased copy of the original game, you can download and install the demo version of the original game by running the download demo script. The script can be run from the following paths depending on how you installed fheroes2: * `fheroes2-install-demo` if you used a package manager (MacPorts or Homebrew); * `script/homm2/download_demo_version_for_app_bundles.sh` if you built from source using the [**macOS native app**](#macos-native-app) method; * `script/homm2/download_demo_version.sh` for all other cases. ## Linux ### Flatpak If you are using [**Flatpak**](https://flatpak.org) and [**Flathub**](https://flathub.org), you can install the game by running the following command: ```sh flatpak install flathub io.github.ihhub.Fheroes2 ``` And launch from the start menu or the console: ```sh flatpak run io.github.ihhub.Fheroes2 ``` Alternatively, you can use an application manager like Discover, which is also available on Steam Deck. After the first start you will be asked for the original files. There are three possibilities: 1. Install GOG version (recommended) 2. Manual install 3. Install demo The recommended option requires the Heroes of Might and Magic II installer file (*.exe) from GOG. This will extract the appropriate resources by itself. For the manual installation you have to copy the subdirectories `ANIM`, `DATA`, `MAPS` and `MUSIC` from the original game or demo directory to the `~/.var/app/io.github.ihhub.Fheroes2/data/fheroes2` directory. The destination folder will be opened when this option is selected. ### Gentoo package If you are using [**Gentoo**](https://gentoo.org), you can install the game using package manager. There are several options: #### GOG version If you purchased the game from GOG, add `games-strategy/homm2-gold-gog GOG-EULA` line to your `/etc/portage/package.license`, then use the following command: ```sh sudo emerge -av games-strategy/homm2-gold-gog ``` It will ask you to put the .exe installer of the game and optionally the zip file with FLAC music to your distfiles directory, and if it's there, it will unpack everything to the correct places and pull `games-engines/fheroes2` as a dependency. USE-flag `flac` determines whether the game will use FLAC music or OGG music. #### Demo version You can install the demo version for free by adding `games-strategy/homm2-demo HoMM2-Demo` line to your `/etc/portage/package.license`, then using the following command: ```sh sudo emerge -av games-strategy/homm2-demo ``` #### Another legal installation If you installed the game from original CD, use the following commands: ```sh sudo emerge -av games-engines/fheroes2 /usr/share/fheroes2/extract_homm2_resources.sh ``` The second command (Note: run it without root) will ask you where your data files are. ### Homebrew If you are using [**Homebrew**](https://brew.sh/), you can install the game by running the following command: ```sh brew install fheroes2 ``` If you have a legally purchased copy of the original game, copy the subdirectories `ANIM`, `DATA`, `MAPS` and `MUSIC` (some of them may be missing depending on the version of the original game) from the original game directory to the `$XDG_DATA_HOME/fheroes2` (usually `~/.local/share/fheroes2`) directory. Otherwise, you can download and install the demo version of the original game by running the `/usr/share/fheroes2/download_demo_version.sh` script. ### AUR package If you are using Arch Linux or compatible distribution, you can install [**fheroes2 package**](https://aur.archlinux.org/packages/fheroes2) from AUR (Arch User Repository). #### Install using AUR helper If you use one of AUR helpers, e.g. `yay`, you can install the game by running the following command: ```sh yay -S aur/fheroes2 ``` #### Install using the official guide Follow the [**official guide**](https://wiki.archlinux.org/title/Arch_User_Repository#Installing_and_upgrading_packages). One of possible command sets: ```sh git clone https://aur.archlinux.org/fheroes2.git cd fheroes2 makepkg -si ``` ### Linux ZIP archive * Download one of the following Linux ZIP archives:
[**Linux x86-64**](https://github.com/ihhub/fheroes2/releases/latest/download/fheroes2_ubuntu_x86-64_SDL2.zip) or
[**Linux ARM64**](https://github.com/ihhub/fheroes2/releases/latest/download/fheroes2_ubuntu_arm64_SDL2.zip) * After downloading the ZIP archive, extract it to a suitable directory of your choice. Then you will need to install the SDL libraries required to run the game. The installation procedure depends on the Linux distribution you are using: * **Debian-based**: run the script `install_sdl2.sh` from the `script/linux` subdirectory. * **Pacman-based (e.g. Arch Linux)**: run the following command: `sudo pacman -S sdl2 sdl2_mixer`. * **RedHat-based**: for RPM-based distributions (such as Fedora or RedHat) use the commands `sudo yum install SDL*` or `sudo dnf install SDL*`. * **openSUSE**: openSUSE supports the One-Click-Install using the `SDL_mixer.ymp` file from the `script/linux` subdirectory. * **Gentoo**: run the following command: `emerge --ask media-libs/sdl2-mixer`. * After all dependencies are installed, run the `extract_homm2_resources.sh` script supplied in the ZIP archive if you have a legally purchased copy of the original game. You will be prompted to enter the location of the original game, and the script will extract all the necessary resource files. * As an alternative to the previous step, you can manually copy the subdirectories `ANIM`, `DATA`, `MAPS` and `MUSIC` (some of them may be missing depending on the version of the original game) from the original game directory to the fheroes2 installation directory. * If you don't have a legally purchased copy of the original game, you can download and install the demo version of the original game by running the `download_demo_version.sh` script supplied in the ZIP archive. ## Android * Install the fheroes2 app from [**Google Play**](https://play.google.com/store/apps/details?id=org.fheroes2) or download the [**Android ZIP archive**](https://github.com/ihhub/fheroes2/releases/latest/download/fheroes2_android.zip). * Follow the [**instructions**](README_android.md). ## PlayStation Vita **Please note**: you need to be running custom firmware for it to work. * Download the [**PlayStation Vita ZIP archive**](https://github.com/ihhub/fheroes2/releases/latest/download/fheroes2_psv.zip). * Follow the [**instructions**](README_PSV.md). ## Nintendo Switch **Please note**: you need to be running custom firmware for it to work. * Download the [**Nintendo Switch ZIP archive**](https://github.com/ihhub/fheroes2/releases/latest/download/fheroes2_switch.zip). * Follow the [**instructions**](README_switch.md). ## Snapshots (latest builds) You can download the precompiled binaries of the latest commit (snapshot) for **Windows** ( [**x64 SDL2**](https://github.com/ihhub/fheroes2/releases/tag/fheroes2-windows-x64-SDL2) and [**x86 SDL2**](https://github.com/ihhub/fheroes2/releases/tag/fheroes2-windows-x86-SDL2) ), **macOS x86-64** ( [**SDL2**](https://github.com/ihhub/fheroes2/releases/tag/fheroes2-osx-sdl2_dev) ), **Ubuntu** ( [**x86-64 SDL2**](https://github.com/ihhub/fheroes2/releases/tag/fheroes2-linux-sdl2_dev) and [**ARM64 SDL2**](https://github.com/ihhub/fheroes2/releases/tag/fheroes2-linux-arm-sdl2_dev) ), [**Android**](https://github.com/ihhub/fheroes2/releases/tag/fheroes2-android), [**PlayStation Vita**](https://github.com/ihhub/fheroes2/releases/tag/fheroes2-psv-sdl2_dev) and [**Nintendo Switch**](https://github.com/ihhub/fheroes2/releases/tag/fheroes2-switch-sdl2_dev). **These binaries incorporate all the latest changes, but also all the latest bugs, and are mainly intended for developers.** **DON'T EXPECT THEM TO WORK PROPERLY.** fheroes2-1.0.12+dfsg/docs/Makefile000066400000000000000000000040221456075706000166270ustar00rootroot00000000000000########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2022 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### LIN_DATA := $$HOME/.local/share/fheroes2 LIN_CONF := $$HOME/.config/fheroes2 UNIX_DATA := $$HOME/.fheroes2 SEDEX := /^\#_SG/,/^\#_EG/ d ifneq ($(FHEROES2_DATA),) SEDEX := /^\#_SG/,/^\#_EG/ { /^\#_[SE]G/d; s:@FHEROES2_DATA@:$(FHEROES2_DATA):} endif ifeq ($(shell uname), Linux) SEDEX := $(SEDEX); s:@data@:$(LIN_DATA):; s:@config@:$(LIN_CONF): else SEDEX := $(SEDEX); /@data@/,/^This path/ {s:@data@:$(UNIX_DATA):; /^This path/d} SEDEX := $(SEDEX); /@config@/,/^This path/ {s:@config@:$(UNIX_DATA):; /^This path/d} endif .PHONY: all clean all: @sed -e '$(SEDEX)' fheroes2.6.in > fheroes2.6 clean: @rm -f fheroes2.6 fheroes2-1.0.12+dfsg/docs/PRIVACY_POLICY.md000066400000000000000000000017261456075706000177150ustar00rootroot00000000000000# [**fheroes2**](README.md) privacy policy **fheroes2** is provided at no cost and is intended for use as is. This document is used to inform regarding our policies with the collection, use, and disclosure of Personal Information if anyone decided to use fheroes2. If you choose to use fheroes2, then you agree to this policy. ## General **fheroes2** is an open source project and it does not collect any personal information in any of its official GitHub and Google Play Store releases. The project does not contain any library or programming module from third-party suppliers which collects sensitive personal information from its users. ## Contact information If you have any questions or concerns about any of the matters in relation to this Privacy Policy then there are several ways of contacting us: - via email to fhomm2@gmail.com - via Discord at https://discord.gg/xF85vbZ by contacting any of the collaborators This Privacy Policy was last updated 04th April 2023. fheroes2-1.0.12+dfsg/docs/README.md000066400000000000000000000105211456075706000164470ustar00rootroot00000000000000# fheroes2 **fheroes2** is a recreation of the Heroes of Might and Magic II game engine. This open source multiplatform project, written from scratch, is designed to reproduce the original game with significant improvements in gameplay, graphics and logic (including support for high-resolution graphics, improved AI, numerous fixes and user interface improvements), breathing new life into one of the most addictive turn-based strategy games. You can find a complete list of all of our changes and enhancements in [**its own wiki page**](https://github.com/ihhub/fheroes2/wiki/Features-and-enhancements-of-the-project).

## Download and install Please follow the [**installation guide**](INSTALL.md) to download and install fheroes2. [![Github Downloads](https://img.shields.io/github/downloads/ihhub/fheroes2/total.svg)](https://github.com/ihhub/fheroes2/releases) ## Copyright All rights for the original game and its resources belong to former The 3DO Company. These rights were transferred to Ubisoft. We do not encourage and do not support any form of illegal usage of the original game. We strongly advise to purchase the original game on [**GOG**](https://www.gog.com) or [**Ubisoft Store**](https://store.ubi.com) platforms. Alternatively, you can download a free demo version of the game. Please refer to the [**installation guide**](INSTALL.md) for more information. ## License This project is licensed under the [**GNU General Public License v2.0**](https://github.com/ihhub/fheroes2/blob/master/LICENSE). Initially, the project was developed on [**sourceforge**](https://sourceforge.net/projects/fheroes2/). ## Contribution and Development This repository is a place for everyone. If you want to contribute, please read more [**here**](https://github.com/ihhub/fheroes2/wiki/F.A.Q.#q-how-can-i-contribute-to-the-project). To build the project from source, please follow [**this guide**](DEVELOPMENT.md). [![Build Status](https://github.com/ihhub/fheroes2/actions/workflows/push.yml/badge.svg)](https://github.com/ihhub/fheroes2/actions) [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=ihhub_fheroes2&metric=bugs)](https://sonarcloud.io/dashboard?id=ihhub_fheroes2) [![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=ihhub_fheroes2&metric=code_smells)](https://sonarcloud.io/dashboard?id=ihhub_fheroes2) [![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=ihhub_fheroes2&metric=duplicated_lines_density)](https://sonarcloud.io/dashboard?id=ihhub_fheroes2) To assist with the graphical asset efforts of the project, please look at our [**graphical artist guide**](GRAPHICAL_ASSETS.md). If you would like to help translating the project, please read the [**translation guide**](TRANSLATION.md). ## Donation We accept donations via [**Patreon**](https://www.patreon.com/fheroes2), [**PayPal**](https://www.paypal.com/paypalme/fheroes2) or [**Boosty**](https://boosty.to/fheroes2). All donations will be used only for the future project development as we do not consider this project as a source of income by any means. [![Donate](https://img.shields.io/badge/Donate-Patreon-green.svg)](https://www.patreon.com/fheroes2) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/paypalme/fheroes2) [![Donate](https://img.shields.io/badge/Donate-Boosty-green.svg)](https://boosty.to/fheroes2) ## Contacts Follow us on social networks: [**Facebook**](https://www.facebook.com/groups/fheroes2) or [**VK**](https://vk.com/fheroes2). We also have a [**Discord**](https://discord.gg/xF85vbZ) server to discuss the development of the project. [![Facebook](https://img.shields.io/badge/Facebook-blue.svg)](https://www.facebook.com/groups/fheroes2) [![VK](https://img.shields.io/badge/VK-blue.svg)](https://vk.com/fheroes2) [![Discord](https://img.shields.io/discord/733093692860137523.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/xF85vbZ) ## FAQ You can find answers to the most commonly asked questions on our [**F.A.Q. page**](https://github.com/ihhub/fheroes2/wiki/F.A.Q.). fheroes2-1.0.12+dfsg/docs/README.txt000066400000000000000000000044151456075706000166730ustar00rootroot00000000000000--- Copyright --- All rights for the original game and its resources belong to former The 3DO Company. These rights were transferred to Ubisoft. We do not encourage and do not support any form of illegal usage of the original game. We strongly advise to purchase the original game on GOG or Ubisoft Store platforms. Alternatively, you can download a free demo version of the game (in English only) using one of the bundled scripts. --- Installation and Requirements --- You will need to have a demo version or the full version of Heroes of Might and Magic II game to be able to play. If you do not have the original Heroes of Might and Magic II game, run the 'download_demo_version.bat' file for Windows or 'download_demo_version.sh' for Linux OS and macOS. This script will download and install all the necessary files from the demo version of the original Heroes of Might and Magic II game. If you have a legally purchased copy of the original game, run the 'extract_homm2_resources.bat' file for Windows or 'extract_homm2_resources.sh' for Linux OS and macOS. This script will extract all the necessary resource files. As an alternative to the previous step, you can manually copy the subdirectories 'ANIM', 'DATA', 'MAPS' and 'MUSIC' (some of them may be missing depending on the version of the original game) from the original game directory to the fheroes2 installation directory. If you are running fheroes2 on Windows and see complaints about missing DLLs, then you may need to install the Microsoft Visual C++ redistributable package. You can download it using the following URLs: https://aka.ms/vs/17/release/vc_redist.x86.exe - for 32-bit x86 fheroes2 builds; https://aka.ms/vs/17/release/vc_redist.x64.exe - for 64-bit x64 fheroes2 builds. Or you can install the game using the installer, in this case, this package will be installed automatically. --- License --- This project is under GNU General Public License v2.0. Refer to file LICENSE for more details. --- Donation --- Currently we accept donations via Patreon at https://www.patreon.com/fheroes2, via PayPal at https://www.paypal.com/paypalme/fheroes2 and via Boosty at https://boosty.to/fheroes2. All donations will be used only for the future project development as we do not consider this project as a source of income by any means. fheroes2-1.0.12+dfsg/docs/README_PSV.md000066400000000000000000000037431456075706000172070ustar00rootroot00000000000000# PlayStation Vita port of [**fheroes2**](README.md) project ## Install Install fheroes2.vpk to your Vita. fheroes2 requires data files from the original Heroes of Might and Magic II. Copy HEROES2.AGG and HEROES2X.AGG (if you own Price of Loyalty expansion) from the original games "DATA" folder into "ux0:data/fheroes2/data" and everything from "MAPS" folder into "ux0:data/fheroes2/maps". Music files in OGG format (from GoG release of the game) should be placed into the "ux0:data/fheroes2/music/" folder. fheroes2 supports ingame cinematics. To play into and/or other videos, make sure that all of "*.SMK" files are placed inside "ux0:data/fheroes2/heroes2/anim/" folder. [rePatch reDux0](https://github.com/dots-tb/rePatch-reDux0) OR [FdFix](https://github.com/TheOfficialFloW/FdFix) plugin is required for proper suspend/resume support (only use one at a time). ## Building ### Prerequisites - VitaSDK - libSDL2 - libSDL2-mixer To build the game just run ``` make -f Makefile.vita ``` ## Controls - Left analog stick - Pointer movement - Right analog stick - Map scrolling - × - Left mouse button - ○ - Right mouse button - □ - End turn - △ - Open spellbook - D-Pad left - Next hero - D-Pad right - Next castle - D-Pad down - Re-visit the object that hero stands on - R1 - Cursor acceleration - SELECT - System menu - START - Enter Text input is done with D-Pad. - Left - Remove character - Right - Add new character - Down - Next character (alphabetically) - Up - Previous character - R1, L1 - Switch current character between uppercase/lowercase ## Vita specific options Pointer movement speed can be changed with 'gamepad_pointer_speed' parameter in "ux0:data/fheroes2/fheroes2.cfg". Use "fullscreen = on" option to scale game area to native Vita resolution or "fullscreen = off" to keep the game area at the center of the screen. Native resolution is supported (set "videomode = 960x544" option in config file or just click on the left door in main menu and select resolution manually). fheroes2-1.0.12+dfsg/docs/README_android.md000066400000000000000000000052571456075706000201610ustar00rootroot00000000000000# Android port of [**fheroes2**](README.md) project ## Installation Install the fheroes2.apk to your device running Android. You need to have set the necessary permissions on your device to do this. fheroes2 requires the data files from the original Heroes of Might and Magic II. If you do not have the original game you can download a demo version of the game from https://archive.org/download/HeroesofMightandMagicIITheSuccessionWars_1020/h2demo.zip If you already have a legally purchased copy of the original game, then put the directories "ANIM", "DATA", "MAPS" and "MUSIC" from your original Heroes of Might and Magic II installation into a ZIP archive and copy this archive to your Android device. Run the "fh2 Toolset" app, press the "Extract HoMM2 assets" button and select either the ZIP archive of the demo version or the ZIP archive created by you in the previous step to extract the assets of the original game to the proper place. Depending on your version of Heroes 2, the music files that need to be placed in the "MUSIC" directory can either be on your original CD or in the installation folder (GOG and Ubisoft). Ingame cinematics are supported by fheroes2 and for these to be supported it is necessary to have them in the "ANIM" folder. Depending on your original version of Heroes 2 they can either be found on your CD, disc image or in a folder named "anim" in your Heroes 2 installation directory. ## Controls To simulate a right-click to get info on various items, you need to first touch and keep touching on the item of interest and then touch anywhere else on the screen. You can then remove your first finger from the screen and keep viewing the info on the item. By default normal adventure map scrolling on the borders of the screen is disabled. To pane the viewing area around you need to press anywhere on the adventure map and slide around to change where you are viewing. During combat, actions (such as moving, attacking, or casting a spell) performed using the touchpad/touchscreen must be confirmed. The first touch event just places the cursor at the touch point, displaying information about the action being performed (the cell/cells to be moved to, the angle of attack and the cells subject to attack, the spell and the cells affected by this spell, potential damage, and so on). The second touch event is confirmatory, and performs the corresponding action if and only if this action exactly corresponds to the action that the first touch demonstrates. ## Android specific notes Fullscreen is enabled by default. To have the info bar with the clock visible you can disable fullscreen mode within the main menu settings under "Graphics". The minimum supported Android version is 5.1. fheroes2-1.0.12+dfsg/docs/README_cmake.md000066400000000000000000000041621456075706000176130ustar00rootroot00000000000000Building with CMake -------------------------------- ### Linux and macOS [**fheroes2**](README.md) can be built with CMake buildsystem. First, you need to install dependencies. For Linux and macOS follow to instructions as described above. Next, you can configure the project with following commands: ```shell cmake -B build ``` After configuration let's build the project: ```shell cmake --build build ``` After building, executable can be found in `build` directory. ### Windows / Visual Studio For Windows you'll need Visual Studio 2019 with C++ support and [vcpkg package manager](https://vcpkg.readthedocs.io/en/latest/) for dependency management. Here quick and short instruction for deployment: ```shell # Clone vcpkg repository. For convenient, let's place it in C:\vcpkg directory, but it can be anywhere. git clone https://github.com/microsoft/vcpkg.git # Now initialize manager .\vcpkg\bootstrap-vcpkg.bat ``` Your vcpkg is ready to install external libraries. Assuming that you use x64 system, let's install all needed dependencies: ```shell .\vcpkg\vcpkg --triplet x64-windows install sdl2 sdl2-image sdl2-mixer zlib ``` If you planning to develop fheroes2 with Visual Studio, you may want to integrate vcpkg with it (requires elevated admin privileges). After following command Visual Studio automagically will find all required dependencies: ```shell .\vcpkg\vcpkg integrate install ``` Now you are ready to configure the project. cd to fheroes2 directory and run `cmake` command (note for `-DCMAKE_TOOLCHAIN_FILE` and `-DVCPKG_TARGET_TRIPLET` options): ```shell cmake -B build -DCMAKE_TOOLCHAIN_FILE="C:\vcpkg\scripts\buildsystems\vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=x64-windows ``` After configuration let's build the project: ```shell cmake --build build --config Release ``` After building, executable can be found in `build\Release` directory. ### Using Demo data CMake project allows to download and install original HoMM II Demo files which used by fheroes2 project. To do this please add `-DGET_HOMM2_DEMO=ON` to configuration options. For example: ```shell cmake -B build -DGET_HOMM2_DEMO=ON ``` fheroes2-1.0.12+dfsg/docs/README_switch.md000066400000000000000000000037751456075706000200450ustar00rootroot00000000000000# Nintendo Switch port of [**fheroes2**](README.md) project It's a homebrew app, so you need to be running custom firmware for it to work. ## BUILDING You will need to install the [devkitPro](https://devkitpro.org/) toolchain. Make sure to include the following package: ``` libnx switch-sdl2_mixer ``` After installation run `make -f Makefile.switch -j 2` command to build the package. ## SETUP You will need a copy of the official game to run this port. fheroes2 root directory is hardcoded as `/switch/fheroes2`. Put the game files there (specifically `ANIM`, `DATA`, `MAPS` and `MUSIC` folders), then copy over the `files` directory, as well as `fheroes2.nro`. If you have a Russian version from Buka Enternainment, you'll likely have `Anim2` folder instead. Rename it to `ANIM` if you wish use the Buka game data with this port. At the end you should have the following directory tree on your SD card: switch | +-- ... +-- fheroes2 <--- this is the game directory | +--- anim <--- HoMM2 game data +--- data <--- HoMM2 game data +--- files <--- Part of fheroes2 release +--- maps <--- HoMM2 game data +--- music <--- HoMM2 game data +--- fheroes2.nro <--- Part of fheroes2 release Generally, you will need game resources from the localized version of HoMM2 in order to use translations in fheroes2. During the first run, the game should auto-detect the game data you have and offer to choose a language you'd like to use. English is always available. ## RUNNING This build of fheroes2 was tested on 12.0.3|AMS M.19.4|S (FAT32). exFAT is not recommended. USB mice and keyboards connected via an OTG adapter are supported. Working controls are: - Touchscreen - emulates mouse, including dragging - L-stick - move mouse cursor - R-stick/D-pad - scroll - A - left mouse click - B - Right mouse click - X - Escape - Y - Enter - (+) - Cast spell - (-) - End turn - R - Cycle through towns - L - Cycle through heroes fheroes2-1.0.12+dfsg/docs/TRANSLATION.md000066400000000000000000000224541456075706000173200ustar00rootroot00000000000000# [**fheroes2**](README.md) translation guide This project uses portable object (PO) files to handle localization in various languages. The PO files are located in the `files/lang` subdirectory of the project source tree. The current instruction is designed for Linux, MacOS and Windows users. ## Current status ![Belarusian](https://img.shields.io/endpoint?url=https://ihhub.github.io/fheroes2/json/lang_be.json) ![Bulgarian](https://img.shields.io/endpoint?url=https://ihhub.github.io/fheroes2/json/lang_bg.json) ![Czech](https://img.shields.io/endpoint?url=https://ihhub.github.io/fheroes2/json/lang_cs.json) ![German](https://img.shields.io/endpoint?url=https://ihhub.github.io/fheroes2/json/lang_de.json) ![Danish](https://img.shields.io/endpoint?url=https://ihhub.github.io/fheroes2/json/lang_dk.json) ![Spanish](https://img.shields.io/endpoint?url=https://ihhub.github.io/fheroes2/json/lang_es.json) ![French](https://img.shields.io/endpoint?url=https://ihhub.github.io/fheroes2/json/lang_fr.json) ![Hungarian](https://img.shields.io/endpoint?url=https://ihhub.github.io/fheroes2/json/lang_hu.json) ![Italian](https://img.shields.io/endpoint?url=https://ihhub.github.io/fheroes2/json/lang_it.json) ![Lithuanian](https://img.shields.io/endpoint?url=https://ihhub.github.io/fheroes2/json/lang_lt.json) ![Norwegian](https://img.shields.io/endpoint?url=https://ihhub.github.io/fheroes2/json/lang_nb.json) ![Dutch](https://img.shields.io/endpoint?url=https://ihhub.github.io/fheroes2/json/lang_nl.json) ![Polish](https://img.shields.io/endpoint?url=https://ihhub.github.io/fheroes2/json/lang_pl.json) ![Brazilian Portuguese](https://img.shields.io/endpoint?url=https://ihhub.github.io/fheroes2/json/lang_pt.json) ![Romanian](https://img.shields.io/endpoint?url=https://ihhub.github.io/fheroes2/json/lang_ro.json) ![Russian](https://img.shields.io/endpoint?url=https://ihhub.github.io/fheroes2/json/lang_ru.json) ![Slovak](https://img.shields.io/endpoint?url=https://ihhub.github.io/fheroes2/json/lang_sk.json) ![Swedish](https://img.shields.io/endpoint?url=https://ihhub.github.io/fheroes2/json/lang_sv.json) ![Turkish](https://img.shields.io/endpoint?url=https://ihhub.github.io/fheroes2/json/lang_tr.json) ![Ukrainian](https://img.shields.io/endpoint?url=https://ihhub.github.io/fheroes2/json/lang_uk.json) ![Vietnamese](https://img.shields.io/endpoint?url=https://ihhub.github.io/fheroes2/json/lang_vi.json) ## Adding new translations/localizations If you want to add a new localization, you will first need to add it to the `SupportedLanguage` list/enumeration in the source code. Afterwards, a new PO file for it will need to be added. It will have to be named according to the ISO standard's two-character language abbreviations. Then to have font support, you will have to specify what font encoding/charset is compatible by adding the language to the font generation code found in `src/fheroes2/gui/ui_font.cpp`. If a compatible font encoding has not currently been implemented, then code for that will need to be written. ## Editing translations - Before you start Before starting, you will have to set up the necessary environment: fork the fheroes2 repository and install an application used for translating. When you have your own fork of fheroes2 you should create a branch. At this point you can start translating. After you finish your work, and you are ready to share it, you will have to prepare a pull request. ### Forking the fheroes2 repository A fork is your copy of the fheroes2 repository. It gives you a safe environment to prepare and test your changes. You can read here how to [**create a fork**](https://docs.github.com/en/get-started/quickstart/fork-a-repo). ### Translation editing software We encourage you to use [**poedit**](https://poedit.net/) or [**gtranslator**](https://wiki.gnome.org/Apps/Gtranslator) to edit translations. Currently all implemented languages adhere to a standardized font encoding/charset. ## Editing translations - For the first time or again ### Syncing your fork Before you start working on the first or next translation, make sure that your fork has all the recent changes from the fheroes2 repo. To do this, [**sync your fork**](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork). ### Creating a branch Next you will set up an environment for working on your translation, in addition to testing and sharing your changes. Read more [**about branches**](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-branches). To create a branch, please follow GitHub's instructions on [**creating a branch within your repo**](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-and-deleting-branches-within-your-repository). ### Submitting translations Finally you will use your translation software of choice to make changes to the translation. All original translatable strings are located in the source code of the project. If you need to clarify where this string is being used you can search for it. The string below, for example, can be translated in PO files: ```cpp _( "Are you sure you want to quit?" ) ``` ### Testing your changes Once the translation files have been modified, for Linux/MacOS run the `make` command below in the `files/lang` subdirectory to create machine object (MO) binary files which can be used by the fheroes2 engine. For example, for the German PO file, `de.po`, the following would be the command: ```bash make de.mo ``` To make the engine use this MO, the file should be placed in the `files/lang` folder used by the fheroes2 executable. The exact location of this folder depends on the operating system. On Windows, it is usually located in the app installation directory. On Linux, it is usually located in the `/usr/share/fheroes2` or `/usr/local/share/fheroes2`. Currently for MacOS users this location is dependent on what third-party package manager is used to install fheroes2. The Flatpak version of the fheroes2 installation from Flathub is located in the `usr/.var/app/io.github.ihhub.Fheroes2` directory. For Windows users who use POEdit or a similar application, it is possible to compile the MO file using said program. However, note that the program will need to be set to compile the MO file in the font encoding/charset that the language that you are translating to has been set to. For example, for German you will have to set font encoding to CP1252, while for Russian this would be CP1251. Later when submitting a pull request with your changes, you will have to save the PO file in UTF-8 encoding because this is what Github supports. ### Sharing your translation work When you are satisfied with your work, you can proceed with sharing it. The first step is to commit your work into the branch you made of your own fheroes2 fork. Then, create a pull request that proposes to introduce your changes into the fheroes2 repository. How to [**commit**](https://github.com/git-guides/git-commit). How to [**create a pull request from your fork**](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork). The pull request title has to be something human-understandable. This allows the team to quickly identify the purpose of your work, I.E. "Update the Slovak translation".
Pull request restrictions - Please read The fheroes2 team has set a maximum of 400 total modified lines for any pull request for translations. For contributors wanting to add translated lines to a new language this has a maximum of 30 total modified lines for that first pull request. These limitations have been set because every pull request needs to be reviewed by our team, and so changing too many lines at once will only slow this process down. In addition, GitHub becomes increasingly difficult to navigate once too many changes, comments and so on are present within the same pull request page, further slowing down the process of reviewing it. Furthermore, we have decided on a minimum amount of 15 changed strings for a translation pull request. For languages that have translations that are more or less complete, less than this amount can be accepted. Preferably a pull request should contain a small amount of changes, about 100 lines, all focused on translating a specific part of the game - for example creature names or castle buildings.
## Updating PO templates and translatable strings in PO files Currently all PO files are automatically updated with new strings after each commit that brings changes to the ingame text. If for whatever reason you still need to update strings locally, this can be achieved by running the command below in `src/dist` to generate a new portable object template (POT) file. Windows users will need to set up an environment that lets them run `make`, like Windows Subsystem for Linux (WSL) or [**Cygwin**](https://www.cygwin.com/)/[**MSYS2**](https://www.msys2.org/). ```bash make pot ``` Once the POT file has been created, go to the `files/lang` folder and run the command below to update translatable strings in the PO files. ```bash make merge ``` If you are using programs mentioned above like POEdit, then they have options to merge new strings from a POT file.fheroes2-1.0.12+dfsg/docs/_config.yml000066400000000000000000000000331456075706000173140ustar00rootroot00000000000000theme: jekyll-theme-merlot fheroes2-1.0.12+dfsg/docs/fheroes2.6.in000066400000000000000000000042431456075706000174050ustar00rootroot00000000000000.\" Hey, EMACS: -*- nroff -*- .\" (C) Copyright 2022 fheroes2 team .\" .TH fheroes2 6 "May 2022" .\" Please adjust this date whenever revising the manpage. .SH NAME fheroes2 \- free remake of Heroes of Might and Magic II game engine .SH SYNOPSIS .B fheroes2 .SH DESCRIPTION \fBfheroes2\fR is a free implementation of Heroes of Might and Magic II game engine, a classic turn-based strategy, with significant improvements in the gameplay, graphics and logic (including support for high-resolution graphics, improved AI, numerous fixes and UI improvements). .PP To play the game, original resources from a demo or full version are needed. .SH GAME DATA PATHS The following directories are searched as data root: #_SG .TP .B @FHEROES2_DATA@ The system-wide location that can be accessed by all users. #_EG .TP .B FHEROES2_DATA This environment variable can contain a path to a directory with game files. .TP .B Current working directory .TP .B @data@ Per-user data directory. This path can be overridden by XDG_DATA_HOME environment variable. .PP The game resources which searched in these directories: .TP .B data/*.agg Original assets (required). .TP .B files/data/*.h2d Additional non-original assets from fheroes2 project. .TP .B files/lang/*.mo Translations from fheroes2 project. .TP .B files/soundfonts/* Additional SoundFont files for MIDI synthesizer. sf2 and sf3 formats are supported. .TP .B maps/* .br Single scenarios and campaigns (h2c, hxc, mp2, mx2 files). .TP .B music/* CD audio music tracks which are ripped in mp3, ogg or flac formats. The tracks should be named as "homm2_XX.ext" or "TrackXX.ext" where XX is a track number and ext is an extension. Numeration starts from "01" for the "homm2_" prefix and from "02" for the "Track" prefix. .TP \fBanim/*.smk\fP or \fBanim2/*.smk\fP or \fBheroes2/anim/*.smk\fP Video files. .SH ENVIRONMENT .TP .B FHEROES2_DATA See the section \fBGAME DATA PATHS\fR above. .SH FILES .TP .B @config@ This directory contains per-user configuration files. \fBfheroes2.cfg\fR is for game options and \fBfheroes2.key\fR is for hotkeys. This path can be overridden by the XDG_CONFIG_HOME environment variable. fheroes2-1.0.12+dfsg/docs/images/000077500000000000000000000000001456075706000164365ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/docs/images/screenshots/000077500000000000000000000000001456075706000207765ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/docs/images/screenshots/screenshot_battle.png000066400000000000000000004362611456075706000252300ustar00rootroot00000000000000‰PNG  IHDR€à,ÖPLTEüüüðððèèèÜÜÜÔÔÔÈÈÈÀÀÀ´´´¬¬¬¤¤¤˜˜˜„„„|||ppphhh\\\TTTHHH@@@888,,,$$$üìÜðÜÈèиàĨظ˜Ð¨ˆÈ xÀ”l¸ˆ`¬|T¤pHœh<”`4ŒT,„L |Dt<h4`0 X(P$H<4,$ ÀÌü¬¸ð˜¨èˆ˜àxˆØhxÐXhÄLX¼@L´4@¬(4¤ 0˜($ˆ € xpd\T L D¬àœœÔŒÌ€€Àpt¸dh°\\¤PPœDH<<ˆ04|(,t$$l d\T L@80( üôÐøè¬ôàŒðÜlìÔPèÌ4äÈàÄØ¼ аÈ ¼˜´Œ¨|œp”d ˆX |LtDh<`0T(L äÌøØ¼ìȨäÀœØ´ŒÐ¨|Äœp¼d´„X¨xL pD”d8Œ\0€P(xH l@d8\,P D8,´ôü¨èð àè”ØàˆÌÔ€ÄÌx¸Äp°¼d¨°\œ¨X” PŒ”H€Œ@t€8ht0`l(T` LX@L8@,8 $,$üääôÐÐð¼¼è¬¬äœœÜŒŒØ||ÐllÌ\\ÄPPÀDD¼88¸,,´$$¨ œ„ xl`THüäœøØŒôÐ|ðÄpì¸`è¬Pä Dà8à„,Ìt ¸d¤X”H€<l0\(üX Ü4À¤üüüÌÀŒŒH¤Ð”¼„¬xœhŒ\xLh@X4H(xÐhÄH´8¬ T¼8¬@´„àü˜üP䤌Œ¼ppœTT|<<\Vî¬ IDATxœÌ½kw\×u-ˆÏ ’ßxÇ”l‘¢^¤DÉfn“"(&ã‘û-£ò‰$€:g?Î9UÙâ0Ý£ƒoq;±õ¶q“ØîtÇù?½æœk ‡'¹éÜ"Q Uç±÷Úë1×\kÏ=ûù±0û§Ç©û§Ç³gó§NÍŸ*ûßðÌþΟNéw>ósögÇ?-ð§EûþlñÙ⩯>ðyžG:…ÿúEÕküÓ³y>Ÿ¸ÌòÀÕÛéX|9Æ©S~ ?Ö¿÷Pþ>ï«øúcîÿ÷3þ‹‹˜ÅEŒ K–Më©" ±\ü²xzq~aiáäpSÐødX<ñŽAÁk‹‹K‹‹öI;ÕâbùœpaqÁEOÒ3;ì)®^ü‚ެÏâϾú(×üeQþw{ü~Bwü8õìß!|õñ¯ÀùÅùg‹_yéÄó_}ÿo{œš_üÊqæO~xiiiñÙüÂÂ<SŠgiÏÌ/êyÞesÞe+ÿÙ‹ó/\=5ÿ-{y 6ÐnÉŽÌ÷Ù˜ô½ðOuÕÞsõ[\9ÏÜ%<–$ønÏ0‘ §!‹²„ö³œ¸¡I) JÖÖÌ2~–:åÓ)j,JòøŒ‘°lT‹)Æàe¾ÙæÄ„f ÏöÍG@ùê·N]µ Á¼Ìcý\ýÖUü¿Z®B>¦É/%Ý$O¶\JÝÖÂÂ)»$êââŠâÿ‚ìÆ‚Ô/bæòU.*ºª‰yLç·l*ìR_\|ñ*¦‘êðôÒ—FçäEa9È­ÀtŸZ\Âûñ8m7ºP4™= B¾e󺀧°À^|áªMî‹W¯ÎQȯڗvñª~ÿÚÏþûUüNéûåp‘êêÂì=óåʪŽ]þTއ÷,Q¿=ƒ|ÙçqQ¸¤\²çç_àÃNwõ[úA½ª)ûÊ‘\ÀÄ™óÆù;mÍð¬ù×Ûô‹ÿû¢›ßÏEW(<‹îÐAùQ0íì&ú¶¤Ÿ™-Á™¼ºÈ×¹ÖñΫ˜x».üïZ:-5`S÷‚  Íº? €›£y”<ªy»§ø ÏŠC¼øŒ®¦nÉ$q .ÆÚ^³WNË<ƒ€š,-.”pSs%è8×ñO˜b;ó‹X¸,ÜP¹c”‹š?uÚËÂ3n»;Ž>ÎuzîjÑS¼,ÑÙmÚT_Õ^Ã-á³>vKz.¯ÿõKãAxmz,,ê—ï=y”¯}þ™=½`ú•FåÛ–4ô¸¶¥ÓöX*Ç+üN¹Ã²’¡*¶!·ÉµŸ©Kÿ-Ò§áE>—ìÊ0Wtaüvb$¯Úy!²(§¿4nþêÒ½ã™ÞwZ—ZFàÔo<´ ¯âw=^´yYZ´‹^x‘ª73ÿUOïÄ}m&ÏöÄ…/Ö•sä}ôÿ ø/|}64§¿á¢ žN}ëø±0›¹ÅÙ;–ôîô«>àé3g¾clêæàÉÓË4þð8cÿü·µÝM¬ëúéèéãÇÿÑõïö°ûÖ¡i‚=l ùîÿÌôeýs¸tæÌsׯçqMÚèÉþþ7}æ;óRJéz¶·6ý?øÆwý>0ð/ŒšÃzT×a?4£—›ÑÁ“ýÇOþÇÿƒ3gNÿ·ýQ=jF8ÇáȆ«±oûÿ]®ÿ_ôÀuÔ‡&t6k!Rþð”ìL¿üû—Îþ!…‡ v&;vQþ² à’ÉÞËmnÛ>Im ÕööÑèéÁ—¡½1]K9ç®k³iÊÐÄ1¶üþ´‹)åïüÝßý-ê`j¬Æ9mÄ¿ñ­&xg_úû”rÙ{G±9¨í;d/„ÃÈŸsÓ<Ýò¯¸Ø?8ó°oZ¦ ÈZ#ZÛÜףѨކýßn9t}Á$õñÓÅ2†¿ì6Ò„ÕEÙÃ8%Jcch³ó/ÿþÚ‰)ÿ·>0!šhÿÞÕ\vi!æ4îǹ5õuÍ®iîÙ™³&{/ç¾›ô&S6˜6[8Aƒ ×ýŒF~ ÓÖ„´5éëަ÷6Œ:üý̶)Û³×ìó˜¾ß1æ‹|û;/]¿ž2w„348Sѵ9x/¦öº­ ÓËœ–ç‚ÑàÚöG‡0ć¦íãÁ¾?yüø÷SÝðvìŒcÖ¸S ‘-ºšÆÎÖž R 8 ’‚æpô BxæìÙkmצ„‹Ù7EüäñþÓ§Oÿb8ìI÷MjJ¾EžTs†ùlfÛT I`l»Üå|ý¿ƒb9×0ñ1æ¶¿|ퟗB[³5?´rC6êÇ“®5)œëM»¾ïúÜÆŒ».Q?ánx¶Ô÷öß>h‚ÚwÐ~;8‹‘ ²DùÀD|³vÒÃÔÙÙÿüÒµ,ûíúàËùKˆèŸ}çÚK×ÿ>F»È„1NÍlI=DpTÿÙ™?»vÝDÏÑ$•«ÊÉ-RƒUT›š²¿Àµ5™¬9W!ŽŸþn!¤ì]³+èL®)É”~|~dñš0º œ‰o€=¶ó !VÇË=îÙ.èËjø© êáÓ§¿k žxPþö°¤B˜ÍfäÉ5A§¿BþøÆ„Ui& Bhºðì¿Ö$<=Õ<žŒg6Ahûë¿Kârµ,"´ žðá!ƒ}—çLŸÙ£k»˜8€(—B»½Èõ“ÉžÉm×™´ŽÛ.5a&y!ÊëõObTl¿>¯ˆo®Á&ØùlPBó•G}àfñôŸ}ç¥kX¸7;i¢˜#ŽH6¦œìú#o̸®oí}f`)©)Ö¸êÊÆ¬#ôø¡Ù¾¦Þ‡ÜÔºx|ÜLæ×'ë„‚—`Ï£‰vJ>$TõÔü#-ÏQ=Ø`™Â!ŒõAãjªÉîÂÔMŽ]+̉ͣ½ÍôØþþáÓƒýšßþ9ÓñtØpæäâ%O/anc’Ëg"QÇè² ; ÷_Qç·aÊf½úö—ÿRU8:„Ÿ+ÙÃ?×Rv;™rñòµo>àHþMElŠR£‹`ók28nçÌîBŒmŒMa7æF7gB(Kl÷`šÒ © DY‡üòà«<é-fíóoö»ïÆãúä·= R™k‚¦£ÃÌ% ÛˆŠÏ¤°†à’à Œû6Û¨Ú dºfÌQ-!¨)…v#ÞÓè ™è&ýÑVã!¬˜ÍÚß}ÇÕ‚i¼³ùÚuJœÍZjõ-w‰×äÊŒZ•KºÑ›p‡C®•†4ä%COH–íÐõ>äϾš§fžÌ­<<4q„Nü¦Y¤Å­Mþj©þ ÅçIìŠ)É 9ü!Qäm¶1ÅQ»!ûÍ÷µßSÚeb½»‚)ZÇÕMÄ”fê‰lÝ—¡-vͺV:¬FŒbEÍf+Û®h®ƒC×ÒÉ MŽ~*†EºkÁ ¶ÉEÝQZ¢ÜÓóö†©` “/T?]¦’#nÿ[„'i9å¾ha‚uœÄ…móÌÔ&Ži‹7b˜ñ';?.–Á`i!¦bÖf”#K-ñ¡þÆsMi¦²ÂÕórl©ý¤ºì"QϦ€¾2â(Â@Ó. ‹OmÜ@Œ°fì(&Ä´âøgF˜ÚÒ>v`ѳ=L©ËH l,“=ÜèHÑÆÆ2®³Ÿ9U ]+P@kÍœ=s5á¶ t²´–ìµïœ=}úw=M –›¹" v¨ Ì6ŽaÒ YZ™3f3"g%ú„Ä„ÐÊê@&„íœÙa 9ƒYa.©Æ?åwBW ”Á×PbOÃŒ9F›sPÖ ÌQj²Ùh¸Âúºæ“NcpÆ¢1 †¬_˜¹ð°¥K!€ÎA†pò›x˜ÜÕpÕË3ÕQC»uâ¼C!Ö^ÛÂå4‰Ã*ÀmÀ2¤–ÂCã`DŒ³ÁðˆÎ «ǰ¥¦`˜˜ë¢Çè ,›}bt8Q0›/ *í@¦·œ†»CÒÀxAõÉ- Yb‘ä ¹xÔî›§F¢jOÐÃV‹žÍØË×ùø&“ìž}M3îAD®¸\nðìÈ}+cÑ!B„ÈÅŠÿuôÀÈ]Ç‹™›šC—ÆIÑ,¢å¦èã\ü‰p¬Üy~Ýo ©ƒ¨9,Léå)BeÎŒô^Ã0Hg…©ÂàqlŽ=Ñ9„rÄò45™:³¾4Àí徊E=CTÛAôEbr6âÛáê22ªý÷ÈÈ)ø#dÊëÑÞ%a÷›IY„¯Mh—|55WI1Ô ¬MSKá»ôéö•Ó“Ž CžŸ\zÔuE·0àÇôqd ùmì Z鉋E2U¯II<6¤_áXM4Å+j%ÏŒ<3œ›L<Ãæ­ÓÏv¦cI,I 9äZá;?†ÛVZµïÔ$ûà–söÌ«‰å‚ü¸&€T#\³Ñ»Ô_ ƒi`Ãl¸UnäÑÄ|áÆ&¦®,à‡þê⺾€jx=EBÈ1jT˜´¸Ðˆ­Ì2üR‹ºés°çpìn&½‰ãfu"ê2‚\ÓAJºOZ⚎üÙ¾Zº&ÓË@OÕ°à°}^RN½"2=äÙøÇè¶&º#¬Öe°djŸ4&0‰žS¢­²×R1lÁAF×Ίp¸»7p=`YCÛ'ØôædÀëfQ²è땳–fC¦“€>v\P¸Eº¾–§ƒ;•[”©Ï|9ClŽ_8¶Ç¯!¬(ŽKÉàúÀž0áEàŠRžy–ÔÙs“1g¼MþNEw楥ÔÀ5dl}cª‰š q<~€¢ë‰%ŽÇcÁqã¶whËèÒ‘tšé%èžœ¤1RTdÐS ¯K üh~…š¦F2~Ç ‹­ç.ô”PÈÇ©=¼•VZz‹a3q$L_bêqk¹¨•âèS§Á®ÝÂÈçMðÊèìŠá\vÃÞ°sïû÷Þ½?<Øyמ)º#‡Ìðgà/ ?PWðÞÜŒGz…2AqGP0¬‹if…Œq²150ÓZã^K Š0çßGèf¢G¯H­;ÂZqe"ÜÈ]/w˜/èØÿC07žšþ²[êÓ ;/mI#G¡›@¤¶©²‘NiÒ¦± Bn\ÁZ„ gI°âø´]ceÇÇrMÐ>éR¼1¦î±ÀaxúŽ@ûo¦±UdŠ I¯¢= ÐNé$>ô%é£|Q²‚Ǹ›AÆ,ѺœpHË“Ä>]° ä+!(³M®Z)}3útÌ]è%Á,q†4öô%èŽYP:F†˜Ø0^‡«)k²‚äg¡qFpR’Ce‰0)‰€,¯-‡Io’n~Œ,¾ìʺñ$3ØhKô» 4!VO‚d%Ï0\n N 0dÛÐ31„ù¢:„¢&G…a ÷ð’†1Œ —‚•Œž¼;k'üôbƒ Ô%W°IöÕÃú;Ãýáûß4 ?|pß~üÓû÷†âAâÃOîÿk)ûŒc[:p™^S«è¨ßJ\|Üãhm.¾§›ó•aÒÆ'¤¬  ‘Þ“ùökR8ÓR¢ÿH :²Dá•C)%þõñ™]Pº–ÜÍ0A8tò¶ho”Š…ˆ_l! ø¥'„ 3˸£C#Ag™"í‘è.é °G9»«ÅpAj‡¨´¢›LO‹hcF„Àï÷R£“êI?…×Y'DǦ©i3:B"€ÂháF£$ˆM”‡Ž˜btË3Í­¹IiÒèÈIãàŒtbÞ§&2B6 ý|òð `-_;Î`f¦=”+O&kP!&ôáRV”àŽ€Iݽw‡Ýa¸sïî ³†ø%‰ „ ™$8C½‹eÑzTþÇpDHÂL‰.‹™¸cÆÒ¦ é¬u­;Ù Š*ðº§B 43d’ä‰,Ððšj»­üZf#ó˜È–ÒC:ñ˜!F©ÍÊÅÓ€ïÃÎAŒÚq¯P§4_ À "Ø3l's†šÑ(ÍA‹øc‚ÄD> ¢c˜9Š• Ø”7–å"˜ ØŸFrΛ$¬Ú$Fevs Lܦô,£`Rz+f“ÇY¸uÒ|§YPty“¨ æ¿„À80ªgGgljˆ—Åcô—f,5¢4ˆE$ÔjDD¥†d ê)OþÏ3»EÙÓOɃäi=E‹Åƒ$¶Ù ÷˜ú»¿7¬¯¼»9üpçÝ¡L±ùw2È¿õuJÍä (äœÞ€sIÜ -ÌõyBWÁíìz{ Âȶ‹@dò¸x Y$€µÒM`2x–­e¦kÜÍ|>Ïûå—Ä‹±ø:‰­AÔ“E*z5– ” qDÁðíÇFâ¤jŽÛŸ!^bÍ8DGù#1pLùƒŠ4ž0)o§oA\Ìì%¢dswWEW£ûb¯+yéôðw&<.5F c"c¶ËS€1„ºÍ¨›Ì!C›ÖøÈ„ü#0L·K¤‹£l—üµ&ÎÁت¹“‡‰$BùMã£^+JᢗRf:é ,‹%¡ >dVxJÏÎdy÷Þ°cðÊå•••Mûa戧ᑩCGÉÓÜÔd¤Ú¤ ܉(˜€ÌÌ”Ÿ#Òš uxó°ä)Ë(¸yA–ô"Š,ZG¾A;™Ðµ“ÁÉ4ˆ ¹#VqÌ…׈ôTö!òdŸ­i榓©É׸w¢‹L/}»¾£“ €”ù Úß\þ®´¶ à´›BïMÆï“FÐ3pš…÷Û¡EZÃ#ÀäuÁ»`Ð`¥Öa³ò8&·xÄžù>1a{&.iŠÄƒkqá•Jê"öÍ ‰_‘HÇÓÌ{ 'ÀC¸‹Ô©[ÊF<òZÜ6]«tBR‚Â%p&‚Q"¨¿Ž\fB„Š“QúÃÁâ•KÏŸYÙ8W•¤½}nv”aI×`98ïJ°£2‚ðˆœ(”}Õ±­ G'ÑaŠg§lpA`„òÀKú–²@,±€‡Ì^¡ókÇGƒ”P®™AvÕ™ É®ûx“tÝé–Å  òÇÈ0¥â½»d:o–/9{aëÂüfÁsRvw°$êz @íêÏÂ$_r\DɹN™¶”ë¼d›g¶$:»ï”a´Ê¡gì*MQÇkdÌ‚¹îªZì‰CÐÉÊR˜M™:¨¸_°å˜t±cڙĹ„J„PPè t¶i®Ÿ‚-ûÐ3ÜÈBö:G‡MîZB•¸târ“gûfWèo‡ÑÆg!,È¥‡ÅÁù!_’4Ë¢4¦.ƒ{-‰Î/S¾XÔ¼xÓ¸ŠDòA€Ãð)©‰À®‚øÂ[y炨çœÆÑILÐ3ÅyåÅ0y `py4cžf"Oƒ4/-ü‘bm.¦&ÜkÕ &K¸ yjÀ3‰' ç* ç­V/]º¸½üܹ3›R_9 ¸3(¯3üð‡Ïi:qù9æ¿Ecó8N|+÷AetÊ9\3S· kS8&k”d«#J&è`3wœ=ê8Ñu¬ähéÒí+!UT`Koƒis~:épž Ó¢“å€~ F´ h¸œxx´»cx«€#”RzzÝ$î2|Ç<ÿŠ©4ˆÇ¤›Lq4`xÔ¨À«THÔri€U ©IëR„Þ’ƒ-ò`6©Ý²chc’‡èbÀsKÄÐ$i1]¸ên¡Ê öRR.‘NÕ b}Ê”Ô$Ù›o0"•_¾aãäA²_)˜3T¼A±J’&楨… ë”X Ÿ„çfšý¶½µ²Zm\ÞØÜª¶8íø $Úoï ìy¥©¨^xvËBê5áê`·ÆZÔÁ0h‰0 ÌA)øtîF’Ÿ„h‘íT¬c«D¬çâq.¾Æ¨?{jÄ^ƒšCšB ẽÄR.Ä\G³K<Ïl+ÔÙö¶§6·„`rꮞRYò*LNAâÆÓV‘ÉXAó÷„Ž4…!9¥š¸\qA­U:£4g6‡=)>ˆÂ;‘m$ÈÁ§ëfAÀõiIÏyyAqÜ„í63LPæv4Ia¦M •УՅ^®#»H$Fgd ©©P£+¨D˜»V agvµQq­[tOµ¾|¡ú?«êÆRÄ_ðÇaog÷á°sÿÞá)æ15%,ËSóΠŒ% ’¢•!쨪•è—‹«…–BI"ŸD¢Aþ"l(æ%l÷8í‰~ÐL’€, |(4÷t‚8Òe…à4_]ò–¸Œ9×ʲ1™A"€I‰ÑxbòÖ„k'x¥G@@d*/+#ÀÆ>61iœ !¿Käò‡Ì~Õî|»ÂI¬WâÒшKxh ûWil#U?pð©$׫Ѕ€'|1™GØûâù¤8c5†¢!ªG IDATùf4)G/faÀ¨¸ Ã&=ÚëüjL‡î±8/ƒHŸäÕ"eì©Pz¨ʆ u©°•8T]á°ÕÚ¥óçªKϯonloÃ)îݤ »ïÞ°ó`¸÷ÝA%æZ v^O¥(×Eš—1–?˜Ži}¤>ð2baÕ6*ZâáfRW” qÎ&K(| „ié;™K¦¯0Óô—r3 ¿IG޾¸«¾îR#è+‹Ÿ6‚ f^$xÍÈ}týûÐ|@A ÿrÔ1A;–®#+›½N¤R´лé4èÎ cXD\ M 9ùY¡wNç ¡q¡av€QUX‚{Š„9 ³§fáIÞ"E>1nä›…¦LÏQý9Ô¢ÀdñxÌê^NÊ1ÈAŒ¢¡QþLΛQœ©™x8ŠHqÛ²ŽæFAv¿iÊ:³)3÷Ë’É^çtÑ3.¡ª¶Ö®œß\¹|fiáùMØÝ‡JÈ þøûwïÝ·hdçÞm•’'IR 76QËe:1.Î0`a–ôT˜ÑüJLFÕäÅ$~i4Þ Û`²;€Ê∗±(áÂn]ë”q¦¦¥¤[ú×ÁF "C쇵÷àÖqNÁ-@=“·‰sÆ&‚æÚ?SŒˆ¿'À›;R¡:id–¶dR»{oOH›Ÿ"]xY–xzý¶TCÃÁâ(t é:yø«¢L`þd8 †Pàìlùä °)À&» ·˜ˆ’»Å¹äe}®K®•¿(~ `d®^*áRz–T}N´»ñ\9¢)†OÑ 5y…( ÑyÎT‰\Ç"}Yôþö¤‰óØg»ZÛÜÚZ[©–/]X¯Ì'v÷† †áî÷¿¿óhçþΆ;·íTß nÚJ1‹(=\}†T ÂH ):"gÄž?‹'`?™%exƒR7È 0¥F«=#Þ óTJÌŽUFTïÒ¤ $aãREštŽjë™dÚìâæHó£ÂbÊ œ¾Éx2!óÙ¾Ûÿ) =.a=¨0TΔ(eñÓw®P™µQ¼“ăËf‚‚{o!>Y©b.‚!YÊjˆÄk±û¶€ód¨²u˜Dš`4NBÄLê€Xa““›Ï¢!K森= „Ió˜Ý¹|Máë™Ì4,^[»=ÃvšŒ½ªÄÞ}HBužp9ò¦Z§¼1äpgØ}ÿj{ãJµzy­Úؾp¶2wÓäÏTØjÑpçáðh÷½;w^7¼sÛÝûÆ9·` ¥  %³ƒÊ1ÃaŸ´¤¥´¬/`v C0âMÒõ*'¸J 6Å-_H91àè$4q#ÆŠ(# PTÆ$»-ˆš\¦îÐ<‘‰0®‰sy¥Ìh˜M[ý’„AŠmbï$O!cöK+sǸjÜ“ -Õ‘¿×«Ð“x± ÆHºŠ Œ€͹‰™K¸”‰j™I¯& ¦´1Ö5$^–™ƒw™ƒØ0Áƒkž(¨¡‡ÈDO«€•؈â¿;­H£ïa3’o*ëß?dÑ_3seâ&þ”³Ãud/•õb¤”mïU%.æZÕÖêvµzqóù‹&…ÛÛ[M†»v S ª wn¾zíö;7oÎ\wºÐÌcÖn˜C(P?‰–1ÂW3ÅBˆ<{â[ot?¤‘3¨FÏM¢¥m¤Ì{õ]ÉÓûNci=9+*^U¶Í RíPwàÌ4¥|Ñm@šï³e\Á\ËR›Ëè÷}XÕÉîÝøý)X*=ë6zé˾WI\žmÏXdŒãA`Zá‹\Kò~蔤‰F­(1˜0ˆ2Î!¤·'yÜ. ©d(xáЭo³×'¦¢èBPtS"ž¦tõˆÞÀÇ¡À0ƒÐ-RQ«íJ]²*QÔ •¥qè€j Ö)•åH¦ž™·(Þ^"›<½.Þ]™îjkkkyµº|aãìókÕÊÊfefwgoàUØ Ãæ¥‡î¼ùío¿9¼ùÆõÛ Ð¬ÔÉJÑÔ(·)HÉþØsR;¡ª•Æ0«¬"6æHŒÀQG•uð,òv †ÝcøÑ÷¦Çœr¤H·¬êè Ò+ÄÍ6©0¶q5…Èΰº4̉Í2-‚6F¶m'GS9Óˆ|Rç‘j gŽ(˜‚DŒAxÚóºÆøÀXy³¾“è…ZÐ[-¤l`L6 6±<áìÆŠ‹¼*Ì 3ÍT4²@cfÀÙƒá%6bJpäjOYax>ˆ@^ˆ©°µä·9”ÇE×Ħä1R¡R ƒåXR¿ôis,J“}Bd÷é¼…¾Ñ -z±ïY0K^‚êèS4ó»\]>¿ýüÙíµÕj»Ú¢ü%…å&~Ãæ¹½»w^yáµ7‡[¯ßæ56MÉ`¦cŠ…Ü×&K6Ya`!ËÞΞ‚xî𚡈Qx_˜y³jsÚ¥,.åN bRóÏúLH®]¸ŽYfÐrª`*Üt`©qœCQ3#aÞ’BsÚÏ<…)ÍÈÁޏ˜©àDd* •8*LÅ!ìX 2fqPGâ4hg²Híz¦“±»ãæFŒäo0Oj¢“)h±q50Pðœ£r­t,.i®ý”ü|xâœ1ç“‘¢€/K',gH/a 䳼Qî]œAµ>çdDáIJ¢C¤¬¬l¼—ŽË<½øÓd¤(!›Á4#ÀÅÚc‚ÌÞI¹-], +¦Z­ÖÏŸ¯.œÙº²µ]mn®^¡üщiØ^©†á¡EĆG{1ð7ÕÝÇÝ> Rv•˜ÄópÄ (­ªÅH’ ìˆk ;J¨|¼ f¯e*sUgQÏŒfN &Dˆ!,’¤ï)&« @ýÌ–GJB…ˆ ! ÔŘ0ÎɆ2¤¡K …"!bâvÔá°¤L¡Ï 6÷`æ¶“óoø¨Ôe+>-e¦ÏBìÎ˺pÓðäE#x9¹mòä`I¢<1KåDðR1#Œ¹µ³(.®ÌD.D¦F¸Ä‚xhòwXϸ1b§8Ë—ÄO ”rÈšäõò©TÎ5$¥3S24Ï4A-èwÒØžŠéì™6UWÎm¬ÉÄ|]‹<ÂaK­¢+­®I;Ž’ïÌàÌæV±hðòAyQ<Ð|‚˜"£ìi§ ('nF‚¡P&¥P<5z‚ŸGêD”D;a 9«\°&lÓ¨qelJ¯MLZZ´Åò[ÕÆÙ¡ªí+‹Û«ÏŸÝ|®Z>ÿÜåò÷(0> C5lo_>;ôª™îÞŽQõP K×E™2ªu»Þ/Ó¸Qµ­žJÔEׯBÝ)ÐÐløðSë5O­ØÜ=£Mx{Ó‰ì^ßyž.w¢ìA‘Ž©vQòKõÌÍ-ž@khárǬ׸?ê@ý#’œAFQ Ó’M5¡zÒ$‹ .»8“~a0¬ÙDp„2M‚ˆ%^g95ø ñk²S;˜G(ÒHVÁ§"(å‚U´˜##™ž ñ'RŽÉüÆMÔÝ( Q¨ŠDºR9ˆŽœC1-MA«™RFøXÈ8³IÈ¥Ü{¹ÌßyÚ*Jƒe—Aú©‹¼#ŒçIceTšœå¦¬©¨nqk½ªNŸÝ¼xesíòóçMÆìš’¿+ðr~xûÑw_½ùæ/ƒ4íü;©ÞYdŸÃŒ|P²+¥‰ ®:gy2D€Ø1a–j+¢w2 gõ,Ùù¬;gŒÖij;‡Á²Ãí'DƒêÜ~¬7ÀXWO›J@q>¾m€PÊ–”ô»9ÄÝ9$(òËÈgiÉÓþG}7‰ÉH=š’í×µS¢àä§¶pM-µ~wXf´Îä L[EL*ø„6kÂ!eMͯðjBÔ‚ÅÜmÛdzu‚sæÐ…¢Ñ“‰Ê¨Ðç0h¶ŽZ•σžP €ã;Ñ£R‰£›—‚>+ÿäi¹Â@¥=”[5ÍŠ›F,V5¨}oiçKw…èÑ5š±!ì"%…¡‹¨ ]—½% õ»=WkÏ]®6Ÿ[[ßX_¡üíí €Ñ0 ÕúÅ3g†¥¡Ù†W^©Ö¡Uø<£5y$z;è¨ÐFBÕGñ¾†¾Dh€Ith"ɳqŸ…ϳ UfV‡0ñ@= ìÄÏ‚Ì*‘ (á$Ñ:µB 4_èm¢H  ÑSê@¾¢v16OSsö³ùyÓ)mè˜áÄO;1©C|"Uen DmÒGÔ'A`ÈìÅ©Ÿî °éN¥X•ˆ#M®“¡E¯ ÑíQf˜HU2!²’ß+™‰lÅCk˜…4¥?ARÍ'Í'ò¤k©±$õ%´%­€„âz6êDš…ÒMà¢,*[°yyH B‰ ©’gEgM©¤³È…r -«I[m\XxþÊfu¥ÚX[uý·¹I¢^5TkçÏž]n£-ÑðjµuãáÃ!È}õÐ3­åaƒR31j8‹¥ D#Ë*: p+7¼ªZ:–4‡S‹PêÙûR`…«9Ÿ?Ñ>Ǥ†Â#$HãÒa÷Xí?­ŠÎgãŽ6ôÊ‹9£ŠTwÍY<‰!o€®0n²15×NŽÞÏ´ÈcÖgå›/T(¤Fsæ¼½ WȘ2ë}Aµz§E/1I¢î½q@±P¡;{ô¨%î3‹î0™¢õ‘ÝÚ”¬RM2ªBöˆv¨n$eXø™X5R ^È„ó©9öäYÆtܦÈ5Œ KŠ_j„®Ðh=Üè &ÕYT{sQ·”{v¯ªqú6˜J½ð¿ªÖ.=wnãÒjµ±¹9Ü3ùÛªK+{ó°½:l^:wîÜðÎÓ;ë×o î>¬UeSlm)‡t ¯èÛ+´B­®¸Zd ZdõAʱ$¥ æìaIR âÄ$F‰):•6û´0Û0¦àŽcÈÉÖõ(]SõIËÍ›0[#Ï"'¶éš£‚-©:a4wcDˆGÞ7…Èh~2VöY™uVð–ê¥Vˆ &Œ6bWòè°ŸdÃï»S¿)§¶dF•²óÈT§N7ì§¼yæ¢ÅÁ)©µd¤eÔHí³È”?X]M¯µ&µ–…‰jÂÈå ÇmŠØÅÒI "ïÀ=ñð–ü„„ÞÃì@™Jz®‹s<Ïž BÀ]›ÔÞf#©0磺T­^Þ\¾Xmn@ÿí™þ[­ªÍ*š¸ué‚y€+ÃÛ·¿÷ú0ì<Þúö ³lŽ˜¼†Vd‹¥YM_H³‹(Æ»¿v\èÛz¹X£|ú—h7\ì䓸ƒ1.+ÕHÅ::"=f,²0Y)ÌTö[…‡±9«j擳NÁX³#É"‚……Luˆå Ègò~?9*3ÉS&Ûw,kÉ*ð#'?«pJuÓâr!@g§ºæ`4ŠSöCEòA«¯•h³W`ŽHD—"‰s&[6ÁÕ ÙQyYôŒ­¢·-^ÉLÜ™ë=R’……$ž¬WòìH.¥+eb«çNÁ8zð=I­R)U­Áoê²WZ¡ÃÍH^åÿ oÕ±Ž*RÝh@c¹¥±@ûØ=‘LÕx¦‡ó‚’!ñUëò·ÈL”Öhè{fn–{ú*If!‡ìÆ:ó„7bar–XଠlôJ‡bËÊ8?T[ëçÏœ½€ìÇÂþšü]±¨¸º`òXU+—Ÿ_¾ûÝ×nUwÞ~ûÊ–Lî ¥ádò4œ’"Y¹Õà|,u#Pâ`›-6'wÞë¸qUÊO©pØbñ™‘ˆ&¶I̾W3&¾€KCCŒ)‘ ÷S0ñûð“O?û›/>«lBí¾Ø cÆÕR³Í`Œ%<³š ‡Ðá.j´ð9@'ÉSǵbÓfÒgG0c[m\Ú¸bø“ÿtoÇ ðòêFuþùåKƒ… ›Ë熷߹uãµïAo†»ö¦Á;&ÄRFî'JavÜØÀÁÕ˜Êã› `¶WA|¥Õ…XZ¾vÏØëŒÓ, «²Ru㬔´`U5»S¨©©Wð’Nª¾úÙGŸ˜~ò‘Ißçó¹‰`—Z‘>ée²‰Zk>`+z»T°"ù¬Xê8ô¤KÌkŒ[f&L”ÇS¶ÉISaßBYe%ÙZˆÌ}¡æê£ËÎ&±¥ßŒ‡KÐoazê@&òÅq§zd¸ˆyµìŠ¢&å`+4¢®"w[60H-™,ðÅ^Ô-Ñ‘ƒÂu'j…áþ’BSÊfÉx-¥$õA]|/Pa†yf¶~û=‹Tcp2_S[è{qµ:·x¥ª†Ÿüá»&€ë«›Õóðÿô“ª{¤h …˜è’Ï$Vd¹û·´ÞÉOÌDø9=#òžá ™ë{öi¢Ä‚Wæ¼™,a'!µ¶"ŒÃ–N„måÛ¥RfŠâ“že& j½$t&©„•¢pMý±¥:#EuwD†‡: gP''òÃÜ®ñÎy€yP':Q¯[QÇ…|5Ή¿¤Ð*Ò…­®=Â뉢ˆ†ëÙÕ‰tqãmôKJÎÉ¢up|MÜjÅQµ±Q]|nþìòpwø£?¼;lml-“ÃÖÄlÓŒððÎí×^~õöwßþži›>Ø^y©Šî^¤`üfB,µ"ÒmĨXÒ,jz*Óç¶±(I_,µ*gí7ôD/#+Ñ”µŽ9"Æ2¹+SFÅô '-Ûû˜v}õÁ'&xŸüôƒ?úôóŸb ±ªä«›¿‡8[èéº`DK4Ι(—YÎã鄉˜1:¡YÖX>õ‘R1Ï“§lÙ[²gÊ2S:°§=v ²RÙ(bLþ©ô¾šçÈ” ²,¹WC¸ìûœ ¸éF`1üåÚhc¥ÖW81BßAÑ2dªøœD–h|I!iqªDMðšÌFÍŠÚäx0üÑO†íå‹›C?ŠãakusnßzíÆkï¼õÎ;UC÷xxôù_½T5ÊQ(ìwœRWä{†D5Cò¦ÌpYUÞRGG‘ɺkÕ’ÃÑY<7ÇÄÙYO¥¤ÖCnLXh<¡ÿ×÷GGIˤhç-3Á}ôÉçŸþÉÏ~úiÀÊ~­h™$ô¹¡ö2Üx±n[4:ý€Æ %M)í¡0,VŠyÒù®ŒH¾DÊg"Ü1Qâ¤TLJ¹@|¹SÕ êÀò3èF¶$鳺öŠÀ…x ÿhä´$ĺ°Šo‰ò+<÷ÁOtÚÆ$•áã­©®8þ’BÓx¼cSJ=¼y1Üÿ(ïΔ;–TÖ‡¥~"Õ`¬6V.Ÿ[[>¿ŽdïúêÖöåå :0 Ë—7‡Û7oÜxãö[·Ë ©ÞùðÃooê¶¼v`–suKK*J8‰‹$ë±ÆaóÖ©÷E§ ŸEZóxL1s„_cÖÒã)›ÒxƒÖHp}Ü1ï$P) 3Æ!üiU}öé~üÙç•9½Uòì*ûÍ`;§Œjb•^tÖ6\žÞ *ŸJ¬©gF©G«È~È3’DÅ6¯€n˜{™Fò x bUD¨= ™=mô˜†n¨¢ÞÖßÄV¾½R~ìÄE’?ÛA0n¶Ð"ü¨öä’çE[¸>dbiaá†P|˜Y¡rðŒ‚¦ðH ܼD0™W™„YVÅH£‘of#úW*&ñDÂ3Åp\µUU[ÊX„O-Þ\¹pöÔÆ`Áðúóg«êò•aèÒ°vn¸ýúë¯Ü|ãí·ª0oï¼õÚK/VŽßª«Òìb\V)¥™À·IJ7OgYš'z7ùøeh4z¢&ÒÚú7 %~¥Ý<ŽYÚŽªžTPœ ©¬>û裫jTäе`‚¤C.8:ɧюQ]¯=O]©*"\ÇÔC¶g¶ËiPÑrovÏ@ÐLÊê””1T,K¶#°ÙDËžŽÌ¶ƒÇ¯|‰hæ`BÎ:»Løo[L]OhÁ¬“ËßQSœeiÊ…-¡ ÛiÛ# KrM♓à`ø*I!0Ø%áæYå¦$MUQRGÅ"9—£Ì4°Oå¬ŒÈædkk«Ò‰~œÆUµvùâé3w¶··¶·ÖWÎ]Ú^våîÐTâí7߀ު†§iÕí[×7•¥Nj]"7Ÿ3zj«R—®8Xíÿ³j‰º¶T¹åèId¹æÙ¹Þžˆ‹…4"Œ-I%ˆ%Ò:é;r»4O‹@£ÈX™dÌhÓ 9ÔbÀZ§k×ws6%ß5# wL‰É¡*GÖýÙ{º>´}ì[ïÔ e!~¬agíß@Yéò‘÷RL.½Þ⦀3=f|'–©ôy–žONÅ÷ö3:. ¤¶Šå*d¦“rÓ+Ä Ï†µ©¸a°¯[Gdrö*¥9«p2Š÷]G…¯’œZåä詃P{OéJ|‘|Ùô2‰ @´œá‹Ó ›°µµ ¬ Ÿ?ž¾ßÕÆåç/ÎßÙÞ´xóüêæ² àÝGí~7 ß{ëÍ7nÝzó­¡}:$¼ò-Ôü8ÈŽ ylÇ–”=$G1”~Œ¬_eq•R¨š»gá\ø8˜­C‹MQª…ÔØ©£¥¢ÃNÜ-ú<Þ¯ªŸ¡&€1WbK58Q˜}Y”ª #Tlý2þÉxó`­k˜e¹M)vãÄf½LI° ¦°K¦6°ÆÐçŒ&ãV^k«¶I]É$£‹ŒjŽEîæ$/ª´g¨ÚöBr¸ĘLŸÖ[Ä{W¬Ž%›*ÎaêÕÑ=u&ÔbVö‹?3ÇRTUöhŠ%1@·íI ¶:ù¢-X^éŽe³Î½Ø½'‰$#™ëãöÄD =Èð{¿ý±i»jýÒóËw¶6ÖÖÖ6ª•‹kðûîÞ^Ód¯HO:oI‰Å¢jÃÌ‘PÚ/«qVQ\øŸ×!±¡WƒZ9Uó؇EQíyi]fSYjP9‡dY^ÀD¢¹±edg³Å­ÀrißaâR¥™ã;³E¬“Ö”dC}ÍÅY ªÁG\Õ˜’Z&Û2(N¨\µ[BvÙ½Z¤5ƽ“½™šºá¨É¼Z90LB2a?a¶®Dådμªüz•a¢†‡1ÉÓ)z÷²3eC)íð©Å•wƒëe˜^`šÇ3Hô©4 œ)©ÂtüFRPr­añ‰º¢;™]TA 7µ×‘Q;hO&<§x€&€››”¿µõµ¡òp÷Á£¡:¿z§2¼¸|i­Ú&ï?ÚÙ»»»[§'ü÷8ìƒÃÚŠxrjKDáÝ €¡…O&ñ QM¾Ø¨6Ž‘“ÉG/­Õ/*BT—òJiïFÊ¡àΙ÷­¶±*~YÁ#ƒÜi­{j›¦Òð º $VbÕ7·Mâ¶’mœ#Ù8UžöFÝ¿R|Ók‰ÞâîwØ2%Ù_½ÍÈwIsdY¹¥jF§Fä-tã)ÀK@ÊLðux#B¬F¤l[OØü#uÉ· Ó¬ ·ãØ™wÈŽE™më³øµvqKÞõJpà1N«ÈË™°Ù[îxQ Y@hpÇnFRð^©Žé¦ k¯}`tIW …çSN<+]w¶>¢ÞM à°vùÒ•asîîí “;wîlolo¯_¾°xæâðãµf}M÷¦5ÿN†·*åÇŠ©Ïº Æ‹La°/A­ºß™OìÚ e"¼ÕyW´Bk[ï’”ÊtdJtBB©˜TöëvËð¶WŸžN~!‚b¦ÙaJBæa Lp©O±ÉC i…2¬™sÚgǹÚO²=Ì¢²‚øP‰¦\Ó,Qf“¶ìiAòFÓ¼N‘Ÿn-Ïð±¡af>ÃÑrê¿> ‚Fßwúb†,Š IDATd1»=äBù‹H®2Ûc¦ºÕË…:I{f4ÞA¢d1KÁ ÆDfwÏ=‚ÀZ¸†}ŽÚxLRˆ!|‰¤Ðh{Uvˆ©·7…+"|# [L `ã‘d‰©£7LÛÞ^[ÛÞ¶V.œáêîÎ0L~êðÅŒuG}D^Íd<å¶”9ttC¸™¶qd4Ã2Á`‚u’Ö^éaYuÜ}J-èÜq)°f~E1ÈA¡Hzïz')x…QNn¿ IA|A!ÚT½ÂäŽÜËnT›R¯NñÎPj”m/lo®®n¦ìž_6Lþv‡ýØ$p°×/¯n¯^¼xe«¦‡Ãêú½½»î=|ëÖÍ›@I®fM-ü­]Å:¶èwÀTÃ6X³”gw^«a<°¶¤ Ä‹ÉIïSj.:ìétÁ >;×DþzD±à›•ŠJt‰ß­KËm±U„ô ?3ׄPÀ#R¤oªÍ>3—b¡Æ>9 t(L)‚W‚‹u‹Bî„ÈT G  ‰[Âd¢³3Ð ä8Yª»!Èæ Ñaa:>Bç¨ó„õ*I›]S¤3Ú1ôV§ô¥a&ÃŽÈ­Ñ“¨AIÝÚFz15ÙrÂIÍÍhK±¥èÂÅŒa£“¯‘f°‰lšºürçM&I(M)䙿É- «ŸÃÇ0´RXaÖ¨4¡ ÂÖúú– à¥Óf€ÿè?½;tÓ£lC6 ›«—.UÛÕöÙ¥¡?4 xeØ8 Ü»ûèÛo!"oè›c˜³‚¸è‰8U{kº$ñì^Ë¡n‡ÝhY¸CÎ@¯m5H1ŠÔ5I ÁU\Ðj'Ú'RÖiLú}×z”LíŒo¸Àç£Ñ‡Ÿ}8†‚ž'O£‚Kç­+QG:W9pø´óß/ Q 99\ô¹É v“…‰p÷J»±ŠáÐEÈ™N<˜BûÙs“©I ©äž;yt:õIÝçÈ“ÅG³³Ó éÊ‚æWàÔŽ¨…©·´³YBÀY[ÖÆóiäF8¢_H ±ðŒó1IAN–"úLë·3’TÏpMr£$VSâ:ý§ŽŽ‚llQþΜ6öîþd˜NÛéQô>´àöò¥KçÏžšzØÞÖÏÏÏÏ£n÷î£[oWT¨A᪟'ÄÉZ{2„|4­¡Æ[FÏôD±¹½CZºGÀröƺ”f­ç>ý¢[.>1qJ`ß²g¤ÀBNMë;ÓVèr8Œ>5-8lVÚUÐ,¶ú»šS?qO“*š7Ñ©E[?-8v¤o=+§µ­ƒ#;ÿ(3Pnª³c‡^=¥»#íº0îzÔ›€^ÓRÇe_r:QÚèÙDZj”žRçCáJ1µËà üÂ+¥ˆ©àÁ>iÍh$l=8?ß"(›…í¨·H I·æ®½v³Co„=7âzÊàœ  úÈæDŒX~Û™x†¾cóîxI¦CO5Èì5Ó›¾Ó¢™0O8!g>ö¢I“%vÙT+y.´)JйWzÔ(Í£rÖDæù¨Âér_2[o¥V0sg1x"CØ**I´y–'I Ñéƒr¥jÄREmf ÓÊ/ÞÕ½Sƒ"|UŠ«?~«L®Ãá;ÿݾաêŽòûÛ§Ó!Y]»ÝØ ÛÃêÅç €ëÏ?2؇ƒöVä½äÖ±d%s*»’§‘Cáážkú¡™¿C¦Òg‡=ùƒm‰òËþ üÍ3Xj~6ÉL LÛYÎc¬ zvÁŽ¥c âÁÎ+7‡×_~cãÝ?݆ª`Õf"žlTMù\v«ýæÑïV™(µÖ²à—;»yQªÈŽ,Äì2(îÙ7kÂ}jÆêŽÊV@]:-Ó`Ùë”L §ýûP“Y5 tܱؔûåÖ¡¢=pN7cn 9nK²Réð–qHîÁœ"Œ’”‡mT³:úÞ.ôm¨B EêH'I _E¢qC«è´úÚ}AUÛk’ý´tu(#ψѭªóª-(Àamù¼… [ë€üd¼ßæv ?uíÂÅ¡ì]kÏ/>=¬˜!~¸ÛŽÂà¶°-Ôœbya¤¨-J½h¸´pSD$ì8:˜´êƒKÊRKb³@µ¶à:ÅmŽžœçŽÎÌôN™¿+ &ÓXjí°(PUÌë;¯¿õÒëo½ùêÆÎv,»<ŠIÐ:[˜ÍaJ/À­¯YðL=o[ÐdRŒ[ö; dÀ‡¶îoÄx³uôµê½Xúh0Cw÷hÂî[¦ÔLÒM} í­ônë[©‰Þ"º>´^û> ÓSö㘎Uš™àï9#â´RrÐ d§Sè’{µÑ·$jJG04Fl $SšÌÌH Ò$±%Yk¨þ–ÝÎk’)Ù˜Zû`¨¸Á+?šÐ8Q‘`wµôöúÖöú°uaX6¯œ3ó ƒÛÕ“œ&GJãL‹HªµaX¾xΜ§‹ç–LÝûC’/Fm[{Äc%QD³|>a¢£à?y³t­ Gáýµ¹ìФ÷J"wHâìúi‚CRË+&ùŒ?³aä¤{ÓÜzÀìHŒÖ±:qÌm]¿ÿêµoo½üjµ³¥Ž j’¶ÌV\çÄ: òIoC•KùöP£Ò/3«-ÝøÌ¯ŠÁŽ.‘å*ÊCI9éVæúM³lOÙAh†ô"`´^lÀýSZߊ•³¸ø(:s‹‹¶D}©VÇêv‡¾Twç‘§X‚¤õ°‘?Ð@©ü€Í~[±à¾FRà:U*ŽØ|E…›è4x‹Ù9šP`h5Jð@Oz{cÓ´Þ¥ÅSÃÆÅçÏœÖ/Ÿ¿x‘ɶ6N†­µ•õÍ‹•yìk•… ÕamíÌp?\† èbË©›Að(Þ«]…Í4êóBfs©AbÛÆÃÔЈµ¢vjm«nR­¶b)¦)Y2‰ešÈ5М‚ˆÚMÞdYˆzx·Ø¶”=ºh#ª×îûÆ·^»výÕj}¯B#›& ¦Å.Ü>À.~ŽÕû¡¥W’ ™$$ ‰™úøô2;œ²¹9\ ‹€R7IÔêŠÎÕ¼Z¬Tð$°\°±iHÑwÀV¨¤°B*GRÑyÉVä(Ž¶ÖŽÆìut4öÚþr‘ YÕEF­?PŽ­aÛ<Þ®ƒ÷Bs÷œR•cð4›È‘Gø*IAÝG½4®VFW læågzkQ Ôˆ8+g¯4)(x®¶×!€ç—†•+gOŸ–/œ½páû>»öIS)ÌÆ•mÔÚÛí-oC€º*ßß®ª×^¿ùÊë·ï¯WR‘·À£&m°Ñ©c.9òE)”ýPÍq “öj5Ø"ˆ¦˜êÚ0íA‰U–èû'o1ŽÜÙûêZƒU5ÕŽÄ܇â%Y{Ó~Ýl ¨Ñ¡ê4æ¦a ÔðÃÛJ°Õ:ûK;K¢ø(IƒœR¢¿»e%ÖÌ—¢aKÐÄWY± Yê—9lýÓyoGwgnDr-¡Ûbþ‚g}µ$JëHøÎ]gÓ³m¸»»³k_{»»vîn˜J´ÇPmë+—.þ¼Ç÷lollnn[Û»lfvÐeÏzdE–¢;Ýbàîdg0ÛZ kÒ‰˜Š©Ô{MoÏLǸóíÈÙëOõÜR lI-€&ßøÆîzýü¥íímÀK¶aæ{Àªú__¹þò7ªû?\¯"VÆì½v à®R íÙrã©(Þ•+nG Ö´‚Îâ[mÅ`Þ¡[!·ß‘„8vÜ®€0¶0„(޹qUªDMÝÈ;y^ˆFoÜPíC{äPèB²#:n}Ðàq{ÒVDÜ–!DñkcØ4f_”TŠpœ¢õä”tkJ.U½Qƒoî!È4’U.ªp˜?kÔ¢C 6Æ¡u·,²vâdyô_â~Hë[C³û`_{{»öÃÎîÞCŠ[µ±mŠîpØÝÛk™€Ö»»wMòìOí½ƒbŠC×}AA˜:ì©ap[j`\CêUœNT& ! 5÷ñµjJÛ8€Ôol9ëc2‚Ñ9¶F'Š‚i³ Ѳ66hã"p{“ßmú«d ë¾öí—®¿ñà‡Xc4_¯‰"ÖÃÝ6EFˆÎPP`)r5ÀÈF}@¡ÖÚêÒéåÊÁÿãÍàü ývÝß%>Ö2`k² t@û3x²+{Ûä…§Ð…ý+7¸ÓKî_cs†Ì½‘i¡Lk‹.r©Ð¡ ®¦ådnmœ‰›Û˜´AÜtîõæ­bÂŒ%âL<޽´ä +H’ñÖ­"¬¦èe&ÇÁK± ä ÛWžì‰Ýâ{‡€¨m­¯'öL š$B$«juˆOüÕä²yˆ/üR’ØÇy_ ŸwÜks*E¨±t‰‹3ªg!›3óÀ ŽàEqÑiÁƒ¦OÞ[§R{ߊÃÛMuÔ“páM€ „™*¤ruÅÔßövµù_í‡íÆ&€Ãçþâ‹×þ|§Ú«†>DÚò†¾ ž$4yéæX¶½q¡Ü«Ò¸0ËF1¡_U—+òÒÀfDè›™–ÄÔÚ4­c{3fó2IíR-ŵgo\7Å S‹’&ã·_y• ÊÙ3Ë6âàÔÁ¶ñörÜ+ÆYnF;{©÷2"“M6qœ(²Åf*…O]V )7®&•òVh®Q0 Á¡"ÆSôL1T~íIc. 0FoÉÊ‹œLäh"ÜÂw¦ÃwÍ#®Úݓ¸] ÃŽFC×ívqÿ0L%£ÄÃý'ƒPEÇ„¼aËÒ,BVP•Ëúø©¸‹Á˜·‰ ¦ð|·t)±ñ 3¦¾;%2J/…Üw¥ýÁ˜ÐiÍÔ ˜¤vÄjyÓÑÆö_­° pøâ‹¿øóÿÉB‘_ ÿõÇ‘®5K›Z%J¡¿çØ]¥rJŽ‚'œÒ=ئêyûGÛÚã=uÚÛ¥QðÁhGµ²ÍR(Yú&³F°‘WŸ¹k31 W€­öOýäÖ+¯`L•Ù‹ìr¤m13ãÃÌu-ÚŸõÉcH` »œÅH!©²-6_æœæþ¸‚zOæ–‘tiµ¨QFrGŸ{$Ë™!#Ûæž›Ãi%ˇiÕMY‰Ù©õjè›`ÜÚìNÚpØ´»ýè-ÉÛæ( “î°ÛmSóôiØíL­<~šŽž>È—›¹Š»£ç‡³Ÿ0r9‘˜œüÈ>èºáÕIQ ;8™•bÄ­—s§&PÞ²§÷¸¸-ÛŽ%•Ü!‹w®ö0÷À‰ñÙg¿øÅ¯¹òÓÍÍÊtàv†O?ý_þì/¾Ø1ùk>ø«>¼¯‡÷GìUkAHë4n5CWZ^¼èÆyƪ:]U5÷b À©IPÍÚºi ™¾_#ôÚM…j5Ë>=F烬mFMÙñúWn˜›qëú«I­Ûˆˆªñ/·¥@?V–æ:hë ¥@“Ü2/5Õ–0Ñ·ÓÌm1Yìpo*Pº>ÌðÚ¶€kµk–¤%û4ë¾¼C²ÈEù¿Ñõ’ĉ:2W§Êj*lö·£aÙzâÓƒn¯ëŸ> »}Þb‚Öx<ب<ëéê¾Ö3Ä ýw¥Ã—Àdø€­PÁ:ù¦YJ¶Q Ñ\0ªžuÇ`.i[ʾ÷]g€r×/’Œ»V&KZ=±à4ÇêãÇuül}ýW·¹½eò·]ÅáƒO~ñŸþüóÏ¿>¨ú³Ÿ šQï&0¢Eoñcs+¡Vr¿a÷G‘„‰µ®UÌxRsæ£à¸‚d ¸užÆ)_¬±§-£õ,õÕȯÊR[ý¯¾òê+/_{ùÕW_½ö*7c%7Jë„%Á¸x¬[\•îT kÜ”6â¡Bç¤Â–¼2n‡JÛ¶¢fø^û[¼¹ÒQ£¤ñµïeôðO5Š4ïÒ@IVŒEð,Õ¶B*mYêV½Us…&šÆ!>9lŽóáãƒñž àãý¼×Ö&€?ÕÓýau­ ÓÝqk‚×<œô&ˆOº!Õ°V¢j΋øÓSp&hûA.nçE¨ƒ\‹ÎŸB±R¾³&åÙB®¯}þùÉO5ò* ’š²oãÂDžzÍ *¶[®®üb?übõòå+W~¹Fù«†O~úñÏþó?ýì‹áÓ¿Š?K¥.yc*O¸Xܪ¢®8Ì8’ŸÕ)‰-ꘪN,¬LB‘5l%Ĥº¨a+ÒNÌ Ö e–—ÎbÙÉ5›EL^íÕ×nÝ~ûW®_{éåWYeróäë¾E¢ªa?zꓘ›šŠú¢ à¨BˆK X‡¹ÌŽ©BÞ‘º€•èÄ1jÆ—ÚÀX<ÚU¦ gÁJ,Ü}§mÒ‚4âÌÈñ.ÎÒXm£}hž¶{]~úøpòp’Ÿ>Ùoõã8ü(Ž'q¸øÜåjº;íÃþ“øÈ^=yr‡6>bÉdË÷ó°Qî_©caLèȬnœú˜»óAQQsà&óâÎOÛÕ*Öe+îC‰°w‚F>l?pbkÉŠæfVýT=ÿÙ/.Ÿ]:weeõ×+› oh óé‡?ûøç_ Ÿ|ü×ùƒ!³_.ñ[ÁŒ,/ž£gú©QãÞ¬ºmUåÕÕ~ðÞƒÂ)몸#^íºžôíȮȉŽdOZØ2ˆè|~1ï4õ©Ñö ÞA=÷ã›oÞº9¿ùæ­×_¦ nMÉØ·Ñ@Æ§ãÆ£r¸ëR¤ 0‘ä„ìÝ!qlV7±Ò =æ’díã›ëY}Tœ½®M§ÕØ'ù>î6±®„0;œh·I/…¥þ¸nÖÞxW8ü9ž„A$°¶’í4CÜã½.í?÷ÆÝÓýÚ rÜOÝ0ùñÁ°yéôùjow’ö§Ž£¹€£ü¨OûƒËI%[tv5bPb#ßyZ’ï[È>a2H¢GG–܊Я•a. ö‘öMv¢|/¢²1–æ8]«Ã’[O>rí#)›·ÏÿæW+ÏW›++¿\ÙØ23<|öÑ'Ÿ|ôчŸ|úÅ0úøg?ýdÕÎ[Ó·ïºF4OÙƒpx*¸_JÕîŸü`÷ÁÎÝhð‘U]È-›Ñ†•n#Ó ÀA…δÂÚáÿÕ¬^B¨›ÒwŽ ¯á‹ù>3H>tÜOÉB°7ß|ãú˯ÞÈ­}»¦†\`a° U?»aºP(’«¶¨´˜Äå±dµ´ä43–Í HÙ³–ý3´¹ˆ+3Õ½É}j‚F‚“Qq[‚§”«…Úc©…OPË>ÇhÕ„ÊÄIš8v“âž4»»}{pÐܽ;n÷G{»»ùÀ†hòPmØÞ½{¯}z0½ûpooüøé“<ÜŽžµ¥eFðþ*rqix íy/ ú à.ùâÚ˱–+^+ͦÝÓFžDÁŽ=Íaí|êÕV¥ ÑÍÒKV+ÇD#·¸ù›_¯]XÙª.\úÇ_­¯¯oWÖñùÏ?E‹¶_˜ø³-+éݙ鲷îÜ,4!:äÒ×VÕ{?xÏdðÞŽ àν?®À±ZÝve_Ø\€r`2=‘Î €–g%TË$3 bêm‘õ¸Á1—Ãrs6n¾a:ð•öæÍ7®ÛǸ(ºÄôrõ¸ñQ"„ƈB »î¨–ºRg¶~mµ/“(uB'ˆ¹רÎTIÀ!¼%VI#Ÿg˜ $VTô0F&#¸×϶T^³OòÍN\ĦFOLðÞ}7$ûv7T¬Õ}Ó‡èØ<©˜Ú:•Þ„Š”¦æØxá5ÿ¨9H¾5 âF´ÃÀÔ¸]Èjð% 5R“J)`›å³•$“÷õ›_/_Ù¸rñâòÿó«Íµ$å†á‹/>C›è_ ~?l ÃȽ/{`~ÙAÆ)Ä¿ªjçÁ{²wÿþý{÷߃V9Tâ@‚WNxœh—ˆm#ÕW+h?:L&D1¡êÕ De´£vTÑôwÌÚÊ úéÍ7^yõzºyó:¨öìÑ©Q° ¤\ß6éT z&H¦‘ôÓ‡žÍŠZî¾ÎÙ¢Bd&Ú=*Ï%QOz:})hÐTaëaˆºb`ÛµºDoÄ"2¶z4RŒâ_©ÖÝc[5Óù»[v¢>Ìw!iû)Ú·>­½áÞ½ww'? wßå+c;V§¬®¸W^Ь…¥&ãìM}x½#e;óv’÷ël<†òZ¾Úýº`ÅŒ—MÅ1–Ez¼ý³ØHÞepë}eåòÅ —–¹±¹²Œ&8ÕGçóÙ'?ÿøÓüÑ 6ŒÊÙ½ý@›¹OH_šèbKñ»÷Ç;{ï=¸w÷û´c‚¸ó}ó_*må'jcrëÕñQ^öè´Aëú<«•Ï'Ý€;dR vt3¸)WOÔõò n½ùúë7^¹ñòË7®µìyi÷DÏÔžæR‚If•ñ% Êĉ–t_hˆŽÒC²7ʓŊƒ¦fÁ÷sŒ IDAT1Aw ïHHíq ì¡&6ûC1`»u}×®¿ûðìðÏjÏM¾'»%ßFé7û«åÕÕååÕÿûW[kW–7`ƒ?>ÿüo¾øÂð§?ýéÏ>TEî‰fÖ@œ“£ŽŒeO\Ý»WU¦¿ÿG¼óÞ{ïíØ¯©âr»¢‚zlÖ½ðg…ïA[âÚìT-9DË’fw«¤ÉDa.·ëÆ·Þ|ãW_}ùåk×^zéz.4°Nœh® ’àÍe±pémDº7kúÔ‹ÎJ]«íj‚82¢ 2å(½ æt¾†êZóÍî8©óÄ6/ΘVçDꢹKµÓµkîõP)P·ÕÛo›Ü‚ªðÓíj<ÆÖàÓÉäh2}r4æ ‡¾zû;wО¼b%!y½^øN?©‹Šf™ôqx)8ØÇ›¾(õí(tãÖ™¡qŒ¥€ÝAxµ¡eU¹bZyæÇO`“š3+¢ÁÂÿͯÿÛßþúâúúÚ¯ÿ~e{ýÊ¥åõí­íês3Â_Øúàƒþê§ ÿ$;æÅPú÷sH0=hª¡˜ ˜sõÞîý»ß°[Uîß· „X‡X%t YBÿ^[¦jrÌÁ£¯Š*¹‹bïƒXОUHê°$¥ ~.þ£Î²Ÿ¾yû¶Åů¾öÊõ—L¯u$ìB˜ä}$×UŒ3º8(S—•N¤çá=§ê¿¤R‰P0'­ú¬]` ³à-(u#­Þ:ëØƒDâ<•Ž ÞÓ(kžd®ÒÿAâƒé½ÛwÞ¾Sq5Nªï~ïíÛ &“)v¨Ÿ²Ö,`SÚ± àÛß­ÞyçÍŠ$d6Õ™°%±b¬Æ;I£5ÇD™FíËstd&©.•}‰„7Ȉµ¤2‘– ‰õ|'ˆ¡uPñ—¢°áZ¯‡–9ªüÍ?üïÿöV¶ÿáWkÕÆÚ•‹WVö›;w†Ñ_ÿõÿõ_?úÙ ÿôOÞlÕ»_°4¼Ÿóª Z†qYckz°ºïÞnÕnuk’ƒ÷BnÅ çÕ#ìãØHbG’äЮ {ž–*Î%ß–2OáTì1¶øö[ï¼úÚë&‚/·ÎH¡ŸÓÂcæ$ºK®lê+ëk½ƒ?KÉ’'ûL€šÒ-¨)}aQxê[¬‘1㬭8žàt\šåQrt´dGTXŸÝCÇm×ÑãTj~æ5ío}Rçê-VÄdR½uëíÛo›v›Âôšòc½(@&sÕíwÞ~{ë»7ߨȌ4-8Åw5X!žÅ®¤Äl%-Nžªíò]å×®çl,Fùt¡Jê·BÛ[k?¯úd¥™Pz^É;Ï•tWÖ8Wž»ðÿþíÇá[[«ëk—.^^Eò›_½òíï õüì¯>þdø‡4 l…Cs·í£<Ç¢;¶lã®Ô¹Õ¶8;mrdc§-˜IåLŽFEM*A3EZ«ûãD jXôCî)Û³&l~Þù&7¾sˆçáÔÉ||ëæÍ7o½öòµï\»þ25 ZAÔ”n:3H¹ ¤ÂçÚX€<%®=‚rh¢ méé¬x1ÍDHû@hÄ›²GºÞ*)@Q¿*i<ùî Ó™´D&“(2^½õöÍ·ªÈ–'ãêö-ûªï舎 × [(Vï¼~ûí­·^y¥š4ï§Ò©ïÑ©¬ öÿ%!Þÿ[oÖ%Çy] â«mk 1ªrølY³d‰ˆé¥ïr/-«%™Nw[º" ° o TF|CDdfÏ·}oË—+dIE€ÄŒz³HSÄPj-KW¤(»õ ÓßÞû| ­¾]$A …¬¬ˆç;gŸ½÷AK8žFáµ> òV®W4Rœš 9— ‚)Æ(rC³‚9>“n“W»?bÓ é*~Tœ«`3eÊ¡µp©ô— (ã«ØL$FÉt؃—˜HàÂxºápظÛÛæÛôå/|áKŸçûA‹³‰%'e‘U"1|{œzÞØ-6lûA´<“!ßc«â K±oY¤1'ja•RæõX™¬Ç/ÙX²ìåÀYݹÝX”R•¾¼d%rQˆPƒçüJNjàˆÏçîô3Ïü•CòcÚ›Qdúwì«u|ô̾˜?¹Õè%jôfEЗ)µ²"=Í9à›ˆ€a§ƒ9[k–ìžÕSRö—0[üꉹ] þ$ú¦èõž"ë8þô§žÚwøÈƒ°ûï+û¬¬î?xhuüá¯~÷Ù/}5`ýþ/ï}ÐÿÇ¿ÿö£ßÿþæmçÆ‰ReéÑîaag´FèÀ|–TUb^N78ljZßPšuÂjaʤág- ­¬±ä‹óîáÌj„GÒùeŽ\§óa7:!sQå?{ö™g¿òÅ/&[Å…b&hàd*ú—Ž‘Ñ¶¸¿±ò匈¼º\·ÀºÜlÎõ~Ôyl§Q>ß)îç´²dÒÍžƒ!Ö¨1’eMõÛfEÉB*«“½Q-™A`pÿŠë$íž¹ã_ÿšëu§¤Æƒ‚Bõª{ö+_;6:ú¥Ï#Z»ZYG\a5þè7¿ýío?üõ¯>üñÇ»øxüèñîƒû>È}ðÿþæ[w†#3¡Â)¼•¬(úQl&ØÈ5RO:HW:‚ƒöŽÁ&FŽÝÞ‹&´‡î‘!uÔuËCÁ!ØŽ8jx+TDzóFFÎÖFzTÆäÀêV­#@;}±Šâ^F«:ku@ué(¶úIfL<½áýiùö½0=Ñ»*ã*™Y˜èŒ&`³½`ÕL B¤igÆ1*à’ÄÚ\¾›qwè,à_:%¼Üm¬æ‹áf¤Úƒ‘ÇÄX«_øì3£¿úÂgþ|u6ïD†°Ü»·üPn®›µµÀ„ìIkêÖR £P³g^ßU£jk‡óS<­©<µç ¡Væó:Óe’ÈÆ¥å¦؃µßýþnè>þøênNÇñ1ðèûv€o\½~Û Çh6Á Ä(.M­*c‹(WIòXtºð®Ôè‚ åÑ €»ú¢öSÄR¢ ö3 àˆÎÌ_dP›õ‹F(Ì­¢nW¶$ñ²œº‹K†¢'kQô+Z]W´VÐj5k¾ˆOmLc ˆò>¡š)HÕÜœnG;ô[Ò/úe“äY+XßGUüd:éŒV’¬ºÌï8Ÿ¶øëóÅlÞ¬~õséh-@¿³Õ¯þùŸÿÅh˜ûÛ|BçÿæôÍÉ¡:_ýrÎ~ŸÿÊgÿò3£álîé ÷_ð±÷ÿþ/{õCþ`e“ddAÈ©ÅRëDÅé¾.d§ÂäƒÍ3˜B¶UU­‚¤¶^Xî¤U0J­—Ð7l.<¬=ðÉxðÇõÕÃGެ ×r$ºýñõ] Ú‡÷óüÁ£ÇûoÿÍOߺzãÎx<¶©H‹Y0O¡d4|­ÀåÊ,¢œƒchN¨/‰VâÓÓaf=.‰ñ”ÎEBƒ*4¢ÐãTø‚Ô¢÷Ô†ÈÞ™–_Iä$a Iã^в£y`mªPc¶‹†°[“S˜H«ÁøÜÈäå4µ4µþYágá7-ØJ4^•UNŒ«`-Q0âÇ$Ö/âŒââ^Æøâù˜jóåèf«_ýË?ÿÌj§”—ÏÞÕ¯ŽGûö;…-ËDxç¹ú¥Ïýùg\N—«ùg9˜6fÀæ÷þp/þ 85J#'Q<°x(¤Z‚‚æ¬Éõ}À®3´"¤§’·Þ Z½£y VØÑóh¨Xž3o϶!!ÊÁ®€+‡VVW×ðÏðãnßx¾ß¹ÿ O÷<|üàþÃþ;ßüæÏ¯ß¸=¹¤E‚ÍlOŠÒ ‘'¨utH;uòduúÂéW¿˜k¸Æ·gSwlj\ cáH†`ktõžER¥›«ãË]÷±6J¡¹ÛñÑÕñV‰·êiýŽ{ìT<]Ìc)ÚD%"žj_ B{Kv¶âU`ôÁ¶‡è1]ï”ÖŸ/£ ,gg…gâmª"û¦$ò‘ji'—ŸÚ]©Ù¨I¦JH²S„èf]ç([ýs^M dîv18øä~ÌÜFC¸Â9¿®O<Ì?ΕVãëü¯ÿøÿø_÷îýÇÿ¾wï^þÜpdÔûD•S’aÕSëékKÞÛè@ŽÚQg\¶êÄ;Ex@Û#&óM{Ášb) +ùÎX19 ×8#puueåã_tëÆßõ|p¿OéÞ#ðQÿÍoþìÊÕ[ãÑ(%sÙ£Á­¬ä“UBãžk»0m§þ4Ÿ2ÇsŽƒFŠ:‚£ -+U>ÊéïñýGý·¾õÍËWoÞÌz!B–éeS*:yƒ݉gžsîôsÇœ#Híb,V*Ál`+Õ ,!0Ï'vˆ’9¦ŠüAãpQ> '-Z X˱ŸÙ|)?ãX=Nmššü”ª1ÂÉIàÙ:zá©§ûîuNÄ›i«ÓXÍȆ_•|äÔs‡åi’67 R9U˜é툥Šp‚ñ]™ŠKn”2ë“Q*Í8§aê˜FmsñÝbQr^þ 8…á™Ý°°Û,lØš•OÄë ü!>öþ°ßÛãü¢ù†Bðt{âC>•QÃk Zÿ#bU“L¬¦"Ñ"ws ߆¥zßW¾hxÙ*“jE’0ÿáÊÚÊÁ§ö`þú7]»óöÕþÞ/Þ»×ûêa>…‚šðwß|ãêÍÛ£¡í lZ­kõ¥´ÔþéœîÔÑS,GÇ»=Çœ‘jùw("4¢®˜õAäJÖg(-óNé@KT0kK‘\¦¼ŽOqp½MÂeg@0Äk¸Ëó™¶§ìíª¤}Çêp’Æø²>É —Ê%>©×¦[Uh°én†Rò‰S¥ˆ7@å§fñÕýfñ>˜òù·p<©©®šNikgŒFN­çŽe%ö-jMKÀ~Ëy2c;h6Æ%Ù‹b”Y7·OœÏçI)èŸ~øO?ì¸wï?õÿ´wïñ«š'‚oxòèÖy:?¦Í©æÜxÄ7Ë*^:a×ALA®+2…µµ¶e{§Sõ›^½¨ÊpœÀS|ÉÄü(NÃï~}hõPÀœü·ÿñÛ[o_»õóþþ{¿øà^?Þ¿ŸàãÝþ[ßyãÊÛnd⸶Û ‹7BÎ 0s'O=ãÜÿpî×î¸C:%hÔgá¢f höðv ûIr+óiV­2F ÆvÄoLÙDÖ¯ð›)€q”ÆÈî]m4½HŒþ̯auEÅQ”5-ø8îK:ÌdDÇC].9¿ U)c(¶‰Å Ú ~¢¦¹†§Ô±2Áœ”"bƒ$qWUgr8¦3XO°M‡tÒÁC‘bEî…—{ì¼ä<ÓmÔµYÍšžòNþ¯|¢6pALôßñÑ÷å‡üa|¬(—=y˜1›‰`ÁCÇ”T&FŸŒQG'›Y£gd< )ä¥Ô=TíÁëi4^ TVS£Èãåïüî7ÿ–CoßþCk++¿úð£ß^¿üóÛ—¿Ý¿÷ÞûÀ‡ÜÛÉøï¼y9w!ã @¢.8Òñ‡ÅP"y=ÇéãÏŠ3:†$9{r¨lžÊý[ƒÚdUåÄ5‰sHŸX‚µjê1 Tðë†%ÅÜëjWžÃ…,–ÚºšZL÷´¯ª^&y¸ÔFxMžÝuË”¦d Jžì ¬(2‹F’ãr«Í2n,îÄø˜EƒÎi_f‘#j=^¡Z–¤x@¾㰭àµh)t~ÓÐBdOOøfk1›i< výúð¢3}öYäÒ k ÿà,“âç8‡½eµ»h:®x%oÍĵ·‰'QY‘¦ªƒ­'N¶ÍæÎö‡ÿ6\]Ù—Ûà_ýê·|ëÎOÞ¼ñæ·ûÞ{?§ÀÍúÑÎ΃ǻM?™üäÆÍ;á0pÉǦJ–h•-@ŒwêÔ×á§ãFî˜Ãp¤‚ÖK& ¾ø&kU•ö“ÞƒñÄT-Ö Áº /Ž£@Nʵµ-DyV$ÐÈ(—ÛÉkÚœ±„ÁÉSžt :k2–Ò´†F±>XHJ˜ZÒ¿Å+-´R£M?+!`êyp#ÝJj­ Ú³bÂÌŠìÚB WÇÔêÆ¹“ÇŸB3–Á±õ,Ýä®`a”ûxÎk>¥Ò¨ÖelfQ]_ÔR´yçfñRE•4,†ôtĶ!ݤl÷U“hkö|,iЄ/ ÛÆÊ›¨[ ›ž! =ÃÍÛ}ô«_=x°û›_ÿÛ‡o_{ûæõŸÞøÙwúûï¾ûË€Õîî£Ý0ËøÆµ|Möˆ‡1C¥ËÄå/Á:öL®A¯ Ü3.ríŒýH¹¯–ÉŠ7 x’ 7òQÞ”¹{°U$í«L[Õp â‚6ÁÊðÚÏls¨~2º‰Ç~Ö&šƒ0‘>|R9U’éˆÖ!P ‹¾-³šò9>¹ÇQâ!,ª}3üÌz3zV9ô46”P¥PTä4,I†7s#‰EÎTKsäDKb·x;íÕ®hä#K`Sséž"—lh5¨7öèÒ3«…e›•/sêì8)(SÍ¥egH Ah°äEAH†¬ÂC,‰C[¼1¯Ygù£*E£—‰¤7sþf÷ü«~÷£?¼½ýæ›oÝüÉöÏ·û_¼ûÞ¿îô›~w÷ñÅÐÿÝäïþùgWÞÁÌ^ƒû‚ƒÍÁ4 ’çža|<çn±SæxäÂp4æ‹LDâ&ˆ›Q‰D£ŠU~¢ *©ŒÅN´%Qº¤ý‰".[v¬¬^ã3K+Ü.mô¦° iEûÒ…§ÌSUM¢ÃîE©1Ú1mB2žE´Õ­Œ ¯•Aì L6[i/€Ñæ…è¤q’&#qÈŸ@>¹•V\JÊ!¨q×6€BòA¤bf~é×íoBˆX„^œD½óº1ä+X%£R{LkæÐ •¨$fBéøø*RÍ Tc,ú§töá9΂&ÜÈSÛš9~G›2±õ”™H”4žÕÍ·oÝúèîùóñÁG^»yûŸ]¹ùÓío»ÿ_Þ}÷n¿rVMÿwÏ¿ñÓ›··GN˜hP)üŒÃR#¸æÁ>uôä1wduøì)‡/èòõ÷£Á`4\ŽCÕG5¢oNñ“¦‚ X¸p † 3ŸGUšL=hSâµÑ¼h¸7Y¶ù6½uf¦(Êë8šg™`·©7+ƒÚ¦¤Ð˜"œÞ‚éF$gE· ;ø¢‡ÔÉkŸ‚mR^ÇÛt±6SX@ˆÜ¦niÛ@b!g¿Qyåu҆؞¬~· ½p*p½ø3¾Ìùñ¼È’&å&عӧOsÃã'ÑÇäÚZOîÖãºïAH࢞/òì­è™¬ÝK’ ”P½^×¶Y#7…’|3‰Ð‰Sy¤2v• ·z B2¯G&˜ˆ9¢  Bk&Cùµ÷µe»‹¿dx©À?u \øŠüœ:.<6ÐTúQ†ù8Meân X‹-mwí‡äßá\@8,@/•7íOc‹Kw%ŒÃ—²„/­6u‚2ÐÔîЮÃ\Œ³å¡¨ŠÐ,yfcÊí¿D±y6Ç¢œ/òo5šä‘«Š>ªû½ÕˆÚj^Oƒ¿j‚ U0‘abæÌ…ì•·o¾óæÍõîÍ;×|ùú]¹óæ7ú|÷½wßÿåý>»ï··o߸qõíkã‘Êçä÷°ÜÁ`OY-¯;qêô)çŽ:–ÛàÓÇsÝh0÷k}ÀœóO„F&Å„# BA<‘ +Í„õL8ɉ´Ya5aÔ"t©D´^wêôÉã9ž ‰ÑÚÈõ¬¦Ñø¢HÚÕT„QyÝœgÕT²cßH·†›r X=RZ[•¹¦»QM¢KGÛ3–Œ¬AFàV 4ù(­‚­ñe .—3&,ìdlEl÷êCÔâD®½ûÑŒ-ŽÔqÇQˆ 8ª1ظ#ꥰj[ÒVð2%‰Ö¶ZmV*n5Ä’tÄ)Z¡´×)6™–£¤~cTʆ7ÈП(B:KÇ+$”X›k6KÑJ¬F6¡À¤Åº|¥Þ3*•ÜUmç>Á*mm-vF”H³a’ìUì[ÛWtùÖ?zëÚíŸüøg×ï¼ukûÛ·®Ý¼“‹À_|p·}»ïïܺqýÚµwrŽá7]«ª‘D±ßø¹<<Ã“Õ ^¢nϧÎËp8_&ѲM«­ÿ'¼hÇºÜ 4 ,ÚȨɷ×ô"*& ” VRHuß5(ŒØP§êÁu%“Ÿ<üºa/™¯*Âø¢&ÜÁhS§`9Š5‘]EƒO‚Ø–oݾþæ;·ú£·nܺüƵ›Û—/ߺùÎÛW¯ß¼ ‘ùVÀÛ7sxõ+WÞ¹6O†ÀžM¯j»ÌfîäÑc9æ<ùìqŒä޹ñxèúñxwǹñ`4¬ö6B°A•);4ªÐkoiÐ ùãfÔÀÝnµÖ^ Gk QKWñ8UsW ÕhÀ_Àª ì¸#’ƆW‹â9ŽðBŽ–9Ô|y†žî¤6ÿpów ¤Iû°ìhÛÄpµ´‰wÂ:<'ûúT6f$Ü´Ö°/âçŠiUüÔEXî2P_ÊS…Í™­„àR*(Qé$­£Ù‹ÞPU6‘ˆ`l …´kÎèžj,Wq &¾ŽÑoL=+<©xi"Ï OÔ‘y‰¦×FÒLÊòW­ ¬éÒŽàšú ØP±âõŸÝ¸þέŸ¾yùV>hoýä§·o_¿våʵ›·n¿þú,¥Ùk¯ç¼öε܅\¿u{„m",ªögrqñrý[wâøsÇ¿~—ðÙ£Gs<íŽ.ŸÀîñØ­­ŽÜÚç†ý&Vr¨£õÆ.æÙÅ©l-þ»£D„zJÔ×|Ï‘¦MŒ FöpJ²N “?ù#ð¾—Ò©Ž}Ê«Fj>6Øl2RÇVSNìOÄÎŒ=)[ª\Ìn+ÞiÉа±MŠbé•Û$†F0Ù= îø!`“lFÃoI1Àűì_Y–)zL•죦iZé$Zn‘MÙÆé 3%pš4­ÉFÿ‹*AÕö¥3±„궃a…üFÞWþ¦ߤŒ[’üY7È“T"¯Héum˜¾q`¨1Ýsƒ—! Po>†Ë7®¾õÖí·r¼|e;‡ÚÕw®_½vóõyN í¥×¯½~'‡æö·Þzëmxg±ãÁ¦$3×a1gôl4¾Ï?‘[÷ìÉ羞ðD>sü ws3"ŽÌxÐ'—}š»Rh¿&*€©x4þ œ‘*˜¿ð6h}ƒ AÙë7æQ‰gB†Q+ËX>¢êBqytØhKˆOÑ ˆ:Qe‚- HœqðY]²vóD·½-&- egé ‡O<ÿc+ïnØ7  /¢€˜Pn xBPÒ@›f‰á¨Å›E“"ZÑ“_etŠªœ5š «R%(%Kù栆 Ô&!¡˜¾È¶câ ÇvÓ‚áŠäìÒ5•÷r“ƒ`)¤9¿džx•´Ž€E!‡¯?ýàG?úÙ?ùñ•í;·ooß¼yûÎí×gùmÍ_{ýõ×sômßxû­ášŽÜÈ‘-›_qÆ_VNè€w'Ÿ;qô¹“ˆCwì¨;yʇ\k6Úþá—ƒQŽ¿ÁÃÜ`T’ЧBÖãqŽK /¢àV~‰ ³GÃå¢oÕ¨òR© ¦Ææ‚iMY9‘?/ì{Ùv(Iò Of;̧02—¶Z¸©•(KKØh}±Bk€A4 KZƆ¦]žÜU@…ã<*Ë9÷-”^­éD¥Â e·Ukëu(#aíŸjjJSÎÕ|™µMM ܺ­,”v3íY­>ƒ–ÛÑÈßlVŒJ]‰™^Ú-í‘YµÎêI‚ ÏÙiA½§Œ%BÓ+?$÷ºdË””—ñ¶T‘ø—z7\Åz»ÜຟÿøŸÿô­w®å˜{8xo¾óóÑh”ÿ|-Ç2µ]Ô€è6*wòøÉ¯çÞ÷ôÉc¹É 1¦È€#÷p4úÃv‡c7øC­¢/’ÄŒ%zí«€8cƒÖƒzRƒž:Lš2œµ>uR‰‡›_‹µl.2(®Õ¥ªû0é|ä²3Ú÷ÇŠûklèìËU  ö|Ñ–ü;³V;›i›(‹$ž¿/Jyˆì¡†.r,Ñ›hŽšJ~IO!ÙÙVy PQÌb3ׯ* ÈÚ¸²œ¹ÚQdyKÕ¦*Söi¨ÌgÏk}ޤT¡3²®=NƦÝå(åwDÝ"¿|ƒZ1P€K”§âªHvÆØÑB NËCÌo_n~R¯)máFÍ:7:²ººšÓÛÌ ƒÕáptûÊÏzåò•«oß¼qíÖÕÑïµµ–ŠŸ— Î=¤P”ôàçŽ=ûì±Iþÿ‰ãÇŽ»Ñˆëær˜Ïàá`gm”Odª“@Õ£·°èø•/Êlîƒ+ l€]“Ìxºƒât«Öl÷9*4€1d(@D^ØøZß‘J…—ì÷6!ã_ ×|/}רÚ­(G¥¶´Âá?Š­@ÉRscÛ›WÑùd{æ)ž±1ƒ¦Êì[ZP¥8ã3Þa¤ÜŠúQ E´ d.pÓ3ó†Œy¡Ÿè#î¡:Lкˆ† ð¡:L~:K"Y4òB0†GB$å¦27‚ ÁH­Ÿ+¢R5P9¼„_‰`Ì–¨Rq¾Ø@F¦Ùf¬æ)«¦ ¾w3wðÐþ#7›»ÁÊ¡+ù ìfó–Ö^ãÁh8ç$;¬AÒIix¸ ªI£0œŸ{ö¹ÉdurâÄñÓ'Žç²oŒdÔ݃Ã!p³¢*©É>H_, QÔ5n(~EmN^Ή´`0FsòSVd]h!€ý«ÓÒ?ya㫾àbK <Åéü ’FÍbÑÔ1áq)” > çà(ÃÐ[ÎèèÌ•*s‹x¤; &¬¯‘ÐTC4Á\tŠÒ²e¥`vò¢nsŽP3uä ÜÚJ ‚8¨ìà`Ϙ ¬×xØÑÛÒŠÉTæ^m\dȪ’À+EPPÙ3’NÊ{ã óZêhÔ{N…AÅOËig$Ã*ŠÎ¡âÏË¡ÓÛ¨-ÖÑÎñ<£F©Æ*f—Ò-æ;´ß¡knpxÿÓ‡s· ;n»wùpAÕáÝ‘•ñÐÁߊdî\ªÓ7³'2Ü©S'NLVVVV'ùãäh8®±ìG£Ç¹19š®j 8¦Žþ"O91ø¦µŒ¢ØXܱCvº:aå“h=«ÖØaÀJ¯TONCªìÌŽ:*’]‰Ô(ì,FŒ76XÁ”MªüÔ­Ú¨%ñï¥U®y„ É0—A’“Opz—hjƒ|µ±€ÄFñhà5sÐNIrd`™X¼Fi¦ä"ÇG2è©ÃCRÛ)lzÈ21Õ‚º¹|Ìl¾.Ÿ?i¼ªßTk È Z¼nÒ4’è–=…x¼œi(üìš²·cŸnR˜q>„ZªZZÔñlÁaÅ3‡‹™kitdze—®ç| 8kÜá}O^ÓdÑu‹Ö­®å„q·vhÍM¹kƒ¦~{´èÔpÄ= ybÚ2©J»‚ùQ…•h›ªŸãs¶‚h/SËM|½ºTɺ*ÎUŽÍæk³¢÷¢™iJ ~Ãa—g¶ F¹òœÉÆPUÁ$Jr°qmÔZ7­Zm„h¾¾ÚDðĬz¹šÙ1Á&M£kr+Ì ø²ü„QB3vS/¦#arÁO2&ÅGª†áëîH»5×µ3·²ÿéŠó.åóKˆÝáÃkC‡ÕÏîÈÁÃ/ÈA`0ÒU­6º"~¹íÜéÉÚ‘•ƒûLx¼³Ã­ÞL|}?¾w×í>¸ûË{ª²é•Xâ$•&Ø,‰Q¶¡öQGt€ÙÀš¦¦!¹g(PœÌV"ÔŒ>l%T`˜fˆÕ6\vƒÑìh:‘U¹*»Â£6˜ñ…U ÷ê“Ì7¢f G>:9[6èq(HŠŽÁ8ŒRƒ¸Â{i[a&±L\“š}®ÔBGå¿­LIhKÅíb’ÜÜG¿4Îe„vG¸Ž5QÚrB©›åX&œ€ðW3é¢â’?ˆÂÏ#¾JE(`>7AüYþö®nmÿS‡(üÜê§óÏd°žïÄÜå’Ðá„™»ÃåHô”‘×XתMFµÊÍû~{×v“|ê®^:tdg÷ƒp8@òÃ<îáx8\[{ÈN¦ (©©  #!ðʼnãTSÚüt´©ê„àa=ƒLŸ{µ¢¦N rŒ#°eûÑêTbrYš]qaôf~ɺ­µ…L̰I¸ŠdШóP._‹øpìj .—s/"ß:ršÑ«Ón Qƒ #–Uái©3*2ŠämÅþ±¶§¯0[TÈÙaëÍiØbi¡­´y„ÝmÙ“°"n¯ÿ1×éëpÉ‘iå5"ÏŸ1¥ª“r"ɪ…ÉjžfwœF$B š_ OCk>q)=i|MË#èœ>î Â.W}ntpßá!~FÏäjþeÓ…|Nï;èòèÀóÍÁ³‡%§HN™n´½ûÈÎ0·ÔkÃQn¨'»÷ò„îÇ óŒ^{ÕE¨¢,%%ŸÌà¦J"¹¾Íµ„Ac€4á†&{¸Íœ,â-ü·ÆF]ךh±bÕk+ìØ›Ëo¬ ÐãìåD-oÐÆMöùîb³Ò·¹l•{ñZÁFÓà²ø׆q-.áJ.´&%†Ç0›œhãUC-¼<õ­R73h‘Ahµ\¬ÄŒÆ‰ KÀÃO>´c<€Y”µ²Ò•‹¹ÚðhãPZfEZ+0­" ,Q(ÆCFd]/U”øR ˆnÅ̯$ŠŠêËûRRÖšï®sžÎ]0Þèé§¹ ?×ÍÌ8¼êòuÎ-IÒý°}m¦ÃìÑ(™ßëhœão´½óØ>qz¢\þMN?züKžÀ˜€` ØhÀ6„½·W‹ú6Zg7ÕIÐï³ë€÷HùHóؾ!¦áÉ…ÓlªvµÅZº(jR$±ÈCá|fÖCZ«¸&8jÿ0—?´`.‘yJœB‡è_Y6É%ÊM×*W¼ª£y¨·¤‹k#ÍRˆx‡0™Œ„* ß- ´hKÁhUr•áHÜÀoU“ŒP›œU¡èuÌzÓºÛx—ÀÆÕz[‚xØýŠ®@-i²ž›Î ‚‡ÝÊëÿùœ~BÍ>;üJúFãñöî.ø€§N>}ò4â6«÷îçÎÃðîãñðÝÎ( J•ÈÝ’ îÉÛSs‹«ZµÆ 5Ùµ ¢ Ó¶Aíj×ñ˜¸´];!8ǪRdÀV"ÏNYËc hEm¢eÓÌÜðbm&#Îb¯‹Æ§ÕXÀ ¾Öð_L•Å,&y¶er”Ùoh,é,–ã1z~Aq¸H¼6“ÚH2¨ä¦S™˜Ûöh5:g ÄùO©˜ê¤¸‘•ê5êåw*õQäØlÚÜ&k }™âxœ6ä„Ô›S уâ!„¯É6Xrã°t€1c”«“úR")¼º‡Æ4xuûŸ>2„Áz3ÏUöxÿ¾œáBœ ÅCOd2l[{Ä Å†\ä1?"süÔ1hBN»Snwûáã÷ÇÜC<þ×û÷ï»Ññôl<‰*EcsYŸhFå[ ÖñÝq]º?°¥Íw[EaÒ‹ŸPF:̽ ÖûœÁ!Cµ8SØÙ*íqpfà1O¥Ô²ÆÄ½ét>6Ñn<^©i[Q¡5äHìH ÃT}$Çj4ùç8ë¯&xs˜¬£šøŠ KQ„ ’L_WÜb³ ë'´€£6kšX0fI lâÛ{ÒáKżoAI=iJÒ‚x±ŸiSž¼¡£Osn‹$Ãl^Ìàt3J®R·1*r¿ñ¼ÐjŸÖ8SitßòW3Ó…éçCm¼°Úçæw°‚s7_Fwhÿšƒëf3Gß±ÿ€“ùpƒ xØq#±€Ü„ÈÃƒçŒøsÛ3O=zìYwòÄ çn»ÇþòÞ{wß»{÷i—O`´sVwDV«buŒB—¼QüŽ=OO:¸ à`%B‹ùhîÒ!š†#bUwËÙ®Š8‚ý8Ù:R0!ÜÔ3à›ˆê«“¼Ôãrlžž¨C:£Ü·jHØ}t:›ÛŽp å–ÌŸ"(qlË‚=þ<5¾dpÅ!¨iI•·¹®„IS£Qy-`VŽ“¢c™G´ ,ÔKb¾(óµÑ ‹ôœNé l±Z22|±ÀävPAÝæ‚kQ‘¶@¹>‡ØÇyG#¢52zEÞÞÑf¥îÜÚßPx"R+³œæ¼eí¶ºb!ïÁ9¬eÖÕnõà^ØÜqâDìÐ49sæ“Ùº=”„ÚXæÈÚ~üKÊN|öÄ3îäñ£GO¸ÇÛnwÔ>~ôëÇŽå#xw)PtÔ€"IIãFøÙtSÄCùìÒ³¾=ŒÃ¤ ©1ƒIEs¬‘¸Ø}¢TGÈa!&Aâ&•ªB‰RXÅ'LJ£¿`Ó°7þ±ÅFlSâ/~0ºsRœµVø‰² &¡–Ú¹üMØq¬Ó=æ‡C‘®:ß0øÿÞ†l¬Ç ðâí´ f†E¹:íSE9µÅ–¶(FsƒzU ‚®¼Ël¢gdiµ »hYØ5ó\á8ÚÌ-‹WÎ7ÓÓ`tu¹kyÁ(T`;Y•]]’n2!&úíFªRÀú%#¡ªÝÁv‹™G(¢ï…—{Š ™Né¹»\6sHÈÄêØ#Ä8f ‚¼ÿøîî£îÔɧN{æ÷ðáöãH<ˆA›§_¬§µ±‡¬š‰šeþÑà:÷"­gBï™drJë< :B/ŒS–Š¡“¤- lU8Pƒ‹¶µâFyVnFQ`— Ñ'뢠B·¨ rç#AM0q@¼*+È…LÉ¡&O5í¸özý†¶u¢>“¹¯I¬ÚV‹‰š6?°©cÉäã“f#˜»Y­½’j,Ö´ÍZŒþešQK³´’Ù¼‡d³žßÄîîÄ_Ìñ·[Û–¼š¥š!Kü‡z¢é&ì¤!¹žN«D# ~íÊ–Lªþ¬ ˆCB’æüV%‘Qg £®£ªŠ ©‰Ûÿ4Â.ÿÂ|únµãôÂî*wàHþµ¿ X¼æ,˜žœÜJÞ}üxçþî®;q T˜cÏÏ%àýÝ_>ãáÚ£>ÞNû²$a¹`„Jš®$ §óC£MY úYÔ6i๵áh†ciƒ¢Z¡yìKIÜC\,U@ªŒêì±ä9*<†G>)¹r=-{i+EÆfŠ…Rçp×qs…Äꦦ‘R öƺžTüfÔ‡0+XmÄ‹:ɲ…¹Ñ™ìÈŒ"mÕI¢µ@ÞàÍI0†Òް•© ®ƒ˜ÉVìN¦¯nMªÝÝ@2šž”¾Ù±_!„«h^'µ)X¦¶üJîÌ6#ˆÀ陋 FÿÊ7IQ)~t-]mêÖíjÕ-À¾Î?[q ªWê{Ñ}úÀ‘‘ ň+¿½=âhŒaü‚î>Ú¹·ûø‘;9®®qò`ûÞx÷îƒ$?ä@ Ñ“‘¹AwlÔQ8¥®Z.½áˆÝ,ò®y°"eÕ­äfAKš‡LÑÎ;ü´¡j(÷ï1–š.ÙbɇL\²¢ÙpK²©V¥mEö"Í%Ò4ç}Ä#!v$Ôª3k2^哊ϑÕ_ILD¿ð»Žòðñ¬0£RëŠlÞ$~ôäÁ$"nAz8æZô•âðŠòI–r(]Ú`ÑÆí È¥Þî¤y¥m滿˃X|=|݆ 'A®¯t~«*À¶4mž˜J&Çl$RPBP›¡<Š3X3ßå£*‹õFSyr¶èžÞ¿ê€‰ ¸zùF™-bØzÓÎ÷ü÷Èß!ŸÀ# ÀуG»vÜw“Á`0§‹•¶å×ßÃïsÞ’Gîîcì˜{4Y GGšlß½›óÞî½ûwYºÜïyÒL§ZH·È º—Yó‚1l¤®$ý«ŠeˆJ?R„³éƒÆ¸jM¾FÄŽ´ ,¢±d»aÃH:4SÆ IDAT£sÀì#g¹_!ÓfVÓÎ:é°¯(u*y+>ƒz U@Vï·šv ¡^®Û%`¹ª=¦…ù#hT+÷8äPßYÌj =k-e‰ û*«`ÛG*^N£ Y#ÅÝïM~à_ˆ¿zqi"‹[ ±ŒA%ö"h:ýOí é:ÄB²P³ñ„gȃ‚½é&Y¿¯év°]áIØ›ü“ÜÓ‡öIc"§0¦HÂF±šfز•Ô‰¿‡Âxø¤Ì!vïÁÎ΃Hêk+‡W'Û÷@?ÞyÿѽñÈÝÛù×G}¥ž”M-)ĉ&–ÆDÞ9¦aúÀÄ“{ %Glˆ / %I¬$¹OÓ”ècÃÀˆ¨ß¸*œFñx…¢TÌü·é™! ¾·î_gŽ©fÇ^w&²Y¤ÁU%z~¿刖Yù'­ÁÝ– I¬h•¬-†ÌE$bJ²M=÷ýšƒ¶Hö^†<Ö£ˆ]µÔÁ°ey÷óIb,«¡~÷?Oú¾ÙDümͺ­Kù ]…ƒKæ_S ¦0á ´â#zj#é ú…Ä. T)™o-o3‚*{ì Óøp““ÅÍ*˜¢sÃù&ÝË\–s‘Þv´¼È6KÖêFIÜC< Ëp€#{ã þËC#=xðËÝïÝ8ìiÛE·3­6YQO}yLHˆ,,䃛äÇÚݳüFnTQN ‚¡労ˆ×p‚ÑY&l£È£zBµä“q•´ðSI Ø£ ¬ë, qù K• €º¨Ó F7¢J&’lœß*í4bà‹vÐI›·„fùÆk1Õe ˜Dq×õ€X¥§±²ªB=Ugu‘ÄnQ9#’²vN xk숎ùì¾Ô¯Oˆ?ܦ .L'²qМ@!D‘QÐÊ&ìù‹›ê†*«ì&E›£F:¢ͻӥÚ %¸ƒ„¨ÍÌò<2îÀá5,5'Þí;GÒ¢K-Md%Úåü‹W؈FA>’¦Wë˜×8kyîR-NÎIÑò…Púe–¢aF£ÙŒkž¦“ÐìØkýÌ“c©ñZ{ú¿?3ì_8{®?óÒn.\˜Lf3 ûq™éx“ÉÈ+_|ØHÈk÷¦Êº4¶0q"­íšdà…\ͤ„ŽPBo\}AB1_ì­ÁÅ[ÏÉ0÷«•ùÕÔt›¦‰za"c/Ã$štÉv“RéÖ<¿¢Í¹“²Ð\½+dr,y×e¡0àÇŽç@>QŸ‚ÉœnhbÍæƒ yX³/Ñ@iÿÆh¼öÿ À¨B˜#éñAþaˆ È‹žh 6Œáâƒ¡Ò QÖû¢ÍJ&èåFÅaD2‡½Fèäž ùý‰üSG9 ÷uôC#e¾_ ?¥3´ËìÎ’6BÏ,%4¥ïбéÕJ.P#D;òòeÊ/ÆÂ Iˆ§î^B §»«ð_øÕóM½ uÉÁb*1ä÷¥™m][âÝ—{kZ¤ü)Ï.É‚&¼öFöæsÒ÷ƒ³/ã¶ìîvçϧnk’+þW7ó}žá¬n´‹î‹h=Èôzʱ Wÿð`7[|ƒäS Á(rü6»·$ÉiþUY(\ºÌJ†ØÃB~üpŒÄ/§ÔŽj ¹‡O”[:òñ@dÀáÚêƒm±`l<æxœ@U­G‡Œ3•8ž ŃÂZg.\Ò\ y 5ׄŽÔé`úͰJÐ Žª[(4b-§â Ñ“hÀ¤Œ6áÒR˜|¨Qc£–„œ0F©C ‚1hrƒ5tÀË(‡kƒ]0±¸1$“îç_ãÖÞI¸@lÔÈŽ@OÕTæ±Fp°U)êhø-È!RN2˜ð$Ÿ™ñVÓXœL.l]Û ݉jÄQœ‰žù€¹½]ÊâY%S¢9“ §‘‚]Õ—È{lu4_e‡~@š{ò&=ƒmC'C‚y·h0$$B`Ö[2­Ü¢µ›ÊY …BgÅOlÁcN›¸kùaçÍL»FúïïžwÝ`ä^Ãßig“±ËµÄâµÉk 30IꊂH.ÑNÃʘÙùºÎó®œÑ‰ÆÚ @3ôò& V¹š·¥w»ñ"RÉJ8ØiZpó©Aݘ=³%VE+“Aü¸'Îx àh\°Äß`´òÁ½m´Èä* ÉÝ—¤J4ÌJüÜx¯®iÒgS2Í&B£5õmšå ðA2øh"‚fuXCPkä.ì-Úub¶E2ª ©UðÓá|¾•EcSFkLRÄùÈ!LVÍ53õß-ÑfU €¥TbÔªBÈÌÒ;ÛÏ ä)Lí! ÉÎIg¯ÝywQa’舣ãY\U{µù0QT¤!kTO­¹A#ÊtîÁgüè:ûDK¦þÅá”ëÃ{åçÏçO›LF¯¼zñb5™oÍ’(ýu×¢³ÓJ¬ünºü.XSm6&éÏ4z UZ™¿;ž£d•&“¶üÙ­Ûà¨QÏOe Z.•¾µHŽ. ÏÄ#8…áp4BgËs6YOÚßd8ÚÝy÷ƒÝ푱sŽF÷û^ÈÈÜžÀ…KD=µ,Ϋ»ZåkË«yÓ@šÍÆ8Ãø?_R˜­j áF{¹<ç@!×µ±X™i‘f:}-CùÆ|¡5KÅí<×,ÐÒZ·5”!åw¢²±‘I4b•Ç^]Ü_¹KÉ@˜{Æyˆ~•® 9„¸äüˆ›$g›Ù\`¶œE´lSV¤ÔÐïß(ÿ,:›ãóºÜZÌò¿óüÃ\‡cþvúþ¥s£s룗7Æù\k^ùÁ…‹'2àl¶˜õßß¾ðâËg{HV5C²C {T~G‹Ï;¬ËÎiPˆ•ð¯ ô†úYâùp?'Âd”Ect-» ÐlTÞ6ÙWSºKA^&>qYOä#ØDñ'ÍdôøáÝÁp0™üq>)½NÔé!霊6Vª£ˆ’ÑŒ?8ëÐÈ”¨|”zò,Àxö∲üeµ+p¤6Ÿ¤@/œÊY+"S âƒõVЪ%Lâ(à.-«;´dÉy®“äÐÒF¡õFztÙü 0j€°¦"дÎTž ¦°Ã #•˜³¥œ1ÜùóΛw…¬ÎU§‡2d‹e/—÷¶3ÞËꛇ¯ÒB”{ÎÉx¹TFì1;t\¡O¡ÿîË£³Ã³£õsc7=16‹­ÉdâÆÍ|>YÌçý gGß?óý3gòmòµ‘c7†/Ž {Ϊó^x±X̽€h§’CàYÆššäB¸Rå‹>*ÔS9IÛæ]XThêCcHîaâZ’*pQ“î2dÆB‹Þü[Œ;Æß’£r“Ç;;Ã#‡BêÜè]²†ýr»$zÔÂËåœHÇõÆYOwR2îò¿s¿]2"²èU®@"o H“a4kšCø…qÌ6Ev‚AÖ1õO n²#¥Ã|OKÕ/¬ÍsüÈJÑHùÑ2Ž *S˜My¥ö¥1*Ôs SrÜD´ŸæïŸh3[¶”67i âMI¤q²¥H(¬=í¾rtK†º¨sBdÛ‡Þ÷/®»³g†ëƒñhØÖáütvi2{y+ŒÉ|Ö¿<~á?¯Ÿ¼°ñ2‘‹l4wËa ¥P˪­vŽÓ}žÃ0Ú¨@ö5ðƒ7Ï>^G¶ ¢5ª‘yYØ¢ñnéþÒÄÒ,”ÂÒ3_owÞŸw·џÛ8›c°yµ\š\˜œ&´m.çýwÝᯥ ÃÁzŽ@”´/¯s? ¾Aôu‹.€³y»ÈgqŽõ…úlAÁ)ñ3‘OÕÀ²´àòGNØF(Ô·H9Ùê»Ó|³¦É:9¼ûN#üœrLfýkh†XBdöÝö6J„Æ‹¤+„pèžp9×%Sei‘Žà`HòØuç9ü句” ó …«£¨=¨âzø&çf¼´¤ê\9Qx´‚ìÙS-÷‚Žõëh{µÓÒ$µ^>Œni«¸ÛM®S6Žc£FĦ ´ ³áÂ@jã ¸ó³‹]´ÂI±·ü‰èוi‡ šql¤Æ%h;œ¦0ØhxPæàX0sÌäúngšK¿ÿ£_Õ“üÅ'?h&ó×&›“áx88ëb;ŸäÀêÝwûñ‹ùˆ¬r¢+ö›ßê´\ÂÌg[9nm-h\rHÎEI®¦­µÚ–=;»±G*Õ&OòZôdV¥Ý‚£iY‘‚ÅOãÄŒ†~]-«\í=Z–ˆ9ˆRð˜=ó~þžc_¤H Â3€i²H~j¡œN!¹Ú4Æy-ÂzÍÖ#ŠþÉs–R_Èo[ù<‘ì¹ðCÒ–ÙfAggž”„]ä†JjLìZã÷I££³ÑÖ÷¨ÙXÕ°¥ *þ ­Xÿ4aEÀ=†œÙ$CX’J"Ëý(0[Å:ª?­Æáâ^™O]!æ'rÌÖƒEŠ7ïi±Û“$˜T.Œ.}3Nã†4•bæð[ä_¢a˜måì¶îŸôgÏ RŽÀÙ<ÇÞb¶1™l —½rñât‚¾¢Ÿ÷gÖÈã³½ô9š9Óœ#þp¼/n±V~Íÿ×[éÏóù˜šû4—óMty¦ ½}He©z2ž…‘_¦¬Ô33ï嘩ÔB/Q×JFÎEd:ŸæBCÈ÷•D©5sÞ›!Çù –æªY8–™Š"OäÖô®ãÄ¿‘ý ½øZ),mE%‚|’vÁ„¿´*Ò§"M@J”x€¼‰Ó]¸Üwrµl‘ÆSjÊ‚®–Ä]ö Ržóakã„ú¢ ¸ÐhˆNÀ’H‹AAg#¼H´ZÞI$»ÊW¸ôžã2wáâ<9 ☠V)•¨æ³˜)#j¿%JþÍðÈw3œ¿ˆ‘gfþXÌæXìwΜëÏl¼2kÐðÎúÉk‹Ùp8À"çšnk²Åšq6Î"¢«âËM&Ȩø9J‘ópÄÏ`¯›¿N“ï÷|Œ˜ mà"M3!¥ñf'óI,_Õ›Äd•É3I~¡É/ïJöþ¢»’íe1/XsD® ÎÝ=i~ÄDÍ‘ßjǼ‡ÃyÎÐÌ‘˜¿GÞj“‡7 úïz)E@Ë#¯YšÐk±ï³Áª¢Ç`þyjl`—–„'ŸÊƆûy…dnpàŽ¶3+vm{5uÓÈˬ4*Ál “¥´Ö2VЩC"¾–+™m?;Q=<ÔQ‘ÈD¬’…§éîÕI§8náÁ¯…2Äsäˆsf˜¤¥÷KXƦÇ/ʾ6‹ùrè‚éTÎú^î×Çc³üYýVjîÜKë¹z?3F³AÍQšÃrýÌ G`5í˜öÌrEߢίŒðe^]4 …;óaaMQ‹<˜kr`¯…Í]^vxÂ;®4\`õ·$¿Ðb_ÔnÏôGl££õ#+²:£*.ÈQ³Ç>ÖÜýÏ€°vÓ‡å½üþð' ÀÄÍ ¤¸0Ó°Ò25¾—Ñ!©t¸™b7¶&Ї©Z£ÉËmé7TS%¢ŒD6ÉØZPW‹§æ*¦›b )Ú”no”õðÆ1k+©ß#wÍè„eТ®Q½*.N5¦pªS*^¾³Q»_7¿O«ùÌQ"Åà<ø¥/ÊöIO§7€–€`Ð.ÍÚü^Ûuäg¥ÜGcoD‰n"ˆn¡ëûŤ©&“ íy®­Óìå3ÃÁöõù‹žPtn0ì l ÎöøÖfp+Ä š^œ¸9›Î™Qd¶Ì…íbÑiß;ؤÍÅ>–³Ž<1=}Xð"- J¯B~‘HWåqmVÄšÆKŸÁ1 xȰè "nåÒ'pî” çðÙbÉʰ˘«[üÉ0Á3!÷ɦÞúäÝÃQÏÔ¤0®×ˆË†¬ÄEhŒ KG㬣ƒ8²Y§–#Ú'i2I,*,¹‰®WljfdR‰L”/ Á³12€®^ŒNó¾²'NÁ%•2eו‡”­¥Ó2¿‹3cî$÷×ßú›oþoßú¶cm¿+:l8WÏ£ÔÌÔvà _.d@*§Å1@ YQ,Y¥OËùëmÙ‡Gœ¡øÆ3º…VmÆŠ ‡*Šr´…9wM¦9’.Lò‘”Û‰Åk£|Ôn`ŒÐζ&޽í<ê™õ³ÃœùYŸà¿iþ1¿`n@fz9uÙ‹® Þfkß5k2,äûGéMV¡ôÄÙ·™/`ecfH­~ 6“¯§7Ù"’ÐîA?WyP)0êíYr =“®…`~³ØÌ\Ì´Àë´$•ÛÊÌE)ýNkÆ/¡Èr)þÁ×îÈCՊ膨ÕE¦!W¥¹ÚlN‹SEØ5’(ÇÊ„òèÒ¬µÔZ+¶¼Yw›ž&4±À„h¼ºÆ†u‰ºÑD £’‘2“(ú…7“¿ ûÆ7Ÿþùoý¯ßrÜKQ Ì ð¼B ¬ùAíûüaæÇ:XG)æ¤õ•ôF¡ÖÁÌÆ!g@ö½Èƒ¹]`Úë áDdãu1ÉÁÔ׋Í0_ñ¥\Ž^tm3Ÿ8$JÔý 92ë9}¾\('ó]Ί¿9K¹ÎãüË­ÂÔ&ÄÚ(:A^Z•í×"›^§3(QîÃæ*íGW0ÛJü†5¦Hl­+å÷÷žñž€ÀžZ€}èBZ¤ç¶Iaˆ_/€Bz ,‹µF ¨$3òt†‰ ¯Ð¥^f"£ê7ÍnCl "5F!P D¥²D•î¹\œÀ¡rk˦™Y)Ìé,ÉoNx±5o¨©Ž“™ÚStìµL6蔪¥ÂÊ+˜´Ú8ò/ºo|ûùç'Šý½tetÜqççµÛ÷‰½ŸØwwlõ‹öÙÃe9µ¼¢Ž™ó“.j«—êgžÄ‘Ý/ŽÜ™Æ@ Áè¬$"½@Ÿ›ìÉdÖÇܸ¶çùÆ:¡zóbS j»þÌÙÁúh ˜= “ÙäÂb‚“Gü%XndÁ”¨5YY@åÅœ—VD. 1ã ®¸ƒj9h ÌxI~¡ïÚä(š…­£ÏóœSÇF39¬ià›z4(vŒE6Á0ÙGÞ[ËGp{ ãºQ?ãê„Î6:²¨ÍE– ÔÆA&SªhòpâÉÂN0MkMÑ&¶J+¯\Ú&C‚c1 Ñ苊F)3‰VKJÞiT†J hg£\ÉfˆxBT!¨@6–¿ÿ¹6‰½«ºÞ&Yú,Åd²² ˆûÖ7rü=ïòåÚrsp²;>ŸÀO?ý'òÔ•ÁpøWOî +äù…¢Í*Û‡¯Å¶&¸´’k8òȱ`µ ÔR¸„êT "YmÍgù³óQ‹ÃhkqæÅ3tPns2pÐÍÖ<ŸÁg×s<ØmœËwoâ›ÉäÕù4‡Ë-ÚÌ•}ð‚éP¨eÁWNT.œ±ïš·Z[IMGoûº0k¯%‹PÅG=Z¾LXÑmçfÁÈôM®Q€9àf­¥Àsô¾#$ÀüÆ{Ö‰3ΩdÒâéÊ󗽪® aƒMœ‰œp±y’Ë$à$á1*ñP)â°.5É»…Õb°ÅlÁº\R÷ÜüÛ8©À¤#%Ù•FE¡…Q/v³Ô±i§A[¢ã²È{ÚÖÞH²Ç¿Rc<¿÷ç¿ý¿u ?­º¹óê‚a õJŽ¿§>ù©O=uàÈê³ÇV?ù…/¬%m4þgå±.37!4¬™*_õ¸U(ȘòÀ—šÍŽâò¡4붘·æì¶æs|n×§4 Íèܹ¨gǹÍއɬë‡9sôË)p¸¾Ñ·“nºõZ΀€Ñ[#Þð“n®T3ç¿ÞLÈeÝ&èÙp¢ÍeBP\È/Ìë¤*R¹†¬’@dŠØé°‘W6̹'çC”‹’Uè8fsÞ²$į0‹D\0váÉ2•à2š““|x…GÖâ>Eˆ8/!¸4d×·T©‘W"v àöNu™¬€’¬:ð(2²ßœ®ÚŠŸExNç-ÓVSÌĉ·ÑZÔé*Ó(›ñFC £L]Qn6vd( J©¤ŽÄ¹¿þæßüÍߺK]õÊ…ª[8ó¸ðJ€G>ñ¿üéÓ‡W8ôÅ/\#FQû>™ë”Ìî}1v¯É›¬keJÒQæ„Crçè¯5(š?ððñî ÊËc±è/ÌòµØ8»¾¾1†p6“\-¹ü·ûsƒsH£Ü ¯Üz?Ÿl]X¼öj‡ ¨ÊokÆ)CºáÑž£"MŽEÉkÃW”3¹Œ´í%©Á8Ûµ¬/Ñ`HuYúò ÝaIŠÔã| ±˜ÚÐ&פsL‘è€þ± FA8Zë ÞçŒ)°ÏŠ@ †YMðöZÚF.2A7ø<­j.,qƒš£% JÃáÂà–ƒÜ±hR‘ã©3ÖNâD.©O%Y50}c«Qi:’q°’<ÆòJЦÊ<ƒãµÀ’ÑPgžÞFÁ†‡z Œ­Àþ÷ßøÖ·ò¼è¼uÚ9ïj\¬ºó[É­Œ}â“O:|è+¾t`Å'¥;…7§&ÒXJè;5?IOfÖá0õΙ’B_:Î-:æ'&*BÈsžÈ9#Íú”‹÷­KƒÑ9Èf‡Ãq›&À)Fn«?·1ç6d0­Ÿ=3^ÎøêìµW[ÐQò°^ ÿà Ï(G‡“Ô-ä2%‹«l¼OYk¥PÌjÄ~1 ÆË|¸ùEúÉìi J!i_f`iÚÔQ!`0dOä¹ã÷ŠÌ•€"`ÛÊ2¯ð¨„g‘›YkFusŠTæd§šQ¡¸šÿÐŽ’€oת. uë5)L:$qFªÛhaòê¹¥T.ª"9Óê IDATrù´ü´ð\Yz“†§¢‹¼I¦VrŸeÎ¥ˆùR42Í”"~&‹.ˆéòéûí¿þëoOžÏ HL+9nÁàsð§{ÿìàê‘ûÇ#O÷'-àæV8zµ)ÒŠ½2f»).%$- ç€ù‘9Ž ŽBØüÎÔÏÐ=´É÷ýV®†f[ç66@F83†Õc¿±1È¿÷Âúúú¹an„‡£~6ùAóÚÅnÒNŒeMFB~eÀ2ȉèçÕÀâÌO‰ñ²È–¡A1 š±™7j(li.€"žAas3L%%ŽÉxÀÌåd™öÐ[ÜÛEnv×úÀïB?rk}æêIÀÒBÝÀdù×ÆÆ úm%’”©^Üã&)%ëåáÛåg²c|vl‹Õ w!΄úÍÔ.KÝA&0$œ‚6è×Rk¿–v ñµAš— Åí4†S(ÛÕ‹¢´³ä¢…º ÆµµíðŒm'¶>ó+»< 9ží39Ôð‹ü³ sÚÚÓ96îéÑh´ïÀÚ!øß±Î¤B¬9ÞD–‹&PWÌÕҨײpÒÈ$‘ý'p¬åÙÌf Þn©<œõ}ß-êØ÷±Ù -¸÷ç¸B2wˆm;Aø½|f#Ÿ¼ÃQÿÂú ÷ÁÃ3à%ô“WÒ¥Ì.M&À1Ÿò\Ï·ÀMGµ9óbÂÑ[%ƒÄ¥âE*@Åky>®I§ÊVi&Â4iÓ¼eª%²±nò•·Þ0q³ ³8ï)· ,çU?—öØ)Â#dŠ)YA¢Ä£œ m¾•gCe•ŒâYääɮ̋ ƒ_‡æA8`RYÎ~'pâfÔÊ*Qç©Ê¡ý_`MESN“çœKkJ'9käbi£? 7' Ò’Bµ\F:Äd¯¬ÿ6²¿1!bâ$æt^­ó(Ýa÷ê+9~âÈÚÚêèÈš±â¡2‰Ôz ¼Ü,LÛ£u ÄbUmµèl‘êæBŸÛ\ åÇuÁ³·ÛZ°bËÅÒwûK±©¯ÒVßξ<<çÀ%‘äÅ3ùÌ=7^ºþÌúYGÖ‡ÃáF?©º×~оVM:õäúë‹å¶H`.¾ eGÆl6Ɉæ P[ï«ipcr÷x‘Ù<>a"“cr,§ŸY¯•-€ %8— 7 ÷°l±ó­ö ÷Íä_\탃 ˜ d @{ ±•p€UE†# –‡q%™™<Ï#ú6mP‡Û"Ût ùÄç»í ø´, ™1ëîs”ÔÙj"¯À¯š_,÷"Íñ½Õt=b“`ŽFЬbCe5.gIvÞPØÑŸW«ålË+àr‹¹»ðÊÅÎ]tÓúÈê>w!W€º÷éý+k+++ƒžuÖ#QŽAýG0^‰,‘q§^ì•Y‹nÍE`¶êÉ™=óV°Yÿý~û³}lûÑYwî…áÙs.7Â/¯o¼|æEœºëëƒ lµ:svðòp}p‰°Ÿ]zµ¹Tù %Īç3q±:‚8ûD¬ËÃ…Ê`t)cU­è1 g¢ø<ªÿˆ‰`æÃA>"(ˆZÞCxK¥Rðƒê¹‡§àó>W¯½"'òx48Ôçc¾`Oœ‹Þ® áèÍŒ˜’…)q?jcþ«W14AïÛŠI7½£ð¼“‰$b¨n휖۽XòhåÉWæã$¯«VãâŽ/ù[¬›Š"=.e¥!–°§q2.<½h §,-@­¶8XøtwéÒÂM/L»­© ƒOïuί5îð#+«Gr¸2æƒdM8'¥ÒnJíWkl€/Wiͼ¹“ŠóÆOó®Y /1ŠÅG _Ž¿Áf‘à¢ë×Ç/¾8<ûâ†sãsç^~ñÅ3/¾¼1Œ^X?ƒº9_ȿܜäðì—^m_›¢ ÑŽð ŸÖȯù__.¹ 1QU+¢Í4 —biŽ)0ëÍZ2ª'L„E›ôA Aw[ ±Ná“mPK\Tƒj¥K¹ñ^í Ty¬Íä6t%=÷Ò„€švqèJúS­ŸÆ\Æ`úësÉס̋V‚Ùê6«H « þk¹>ÎFdQxu I•–Û°ÅÀK‡¥öˆ}uÇ9ª?¦jä+dûýL8¢%LÒG%ÌóÙ “&ÎÉæËÂè¤AwîÒbËUS?[$78üÔ~‡ø;xèàSŸÞ¿²ïS+ã6ÈÙ©,7Ĩ<©ÿˆ¥Ï–ÐË'ÚcØ&"B޽¹zŽœ—üý &lýÙú—úпøâÆð%0aF9ÿ 6ΞyéìÆèÜàÌË/œ}9—PÀœûò¼þBî“×ûW·.U‹Ùdƶ—P7Zœ…°žY“¿Zª%{GKµñ`P{ª]²„•ÐRqJð²dö¶Lö !Vl°$tÑ6+jÂ8Üoâ l˜YN„{é›Ôüôc7<ÔGñ2jNóaˆJtÎ\Ȥ¸i5IÒaú¤r@Û”J{ ̱!H×jÊZ"ö&2}‰´)ý>òiiÎD­1bá†zónâùÍ6µ3±ñiúaµ¦—£µ¢F0éHêLæô©M DŠ¥·û7yÃû}îoÝV.ÉÀÓÜ jŸ»X¯ÍÜ'ÿìO?yxÿÓŸŽ[íšEI–êhqˆ937Ó ­C2;· "[DÄ-Œ ÈÍB~ F.·Èg¿‹è{i£OýÆ?|o}ýÅÁ¹ñp0çxñwöÜ‹g_ ÓÜ.^XÏ'ð`ýÌú87º­Í­É€z·?_€¦•‹Ì$Xy»’בײÉ‹9rðEÝ®îi3&ÿÇ6²~“[‚pYÚ;¢o`[Ù&óþD@ìÑeà`H9OP@©©*-óûb;B¯ª ˜¶d†$ëXÔl$7ÐS%»´µ¡ e}‡¬NÉË¢u YPˆDö'QŽS®,à:hYdmÆT‘Ëo*½ [šÎ¦ÃM4Z™Û3–è3môiŒ×ˆ“ZoJÊKæ“Êÿ#a—oLþö¯ÝVw±›¹îÛÿ)ßc¿–ܧöï]9¼/ÿ¢öXWʱɲ`JF¶QrÔQS5/MòAÅ{4N I¹ ž7j…93¿ë_z©?»>ìÛþ»¹Ð{ñ…së5·±±ñòFn6^Ê!¸A3 ààÜFÁ³¹9×_ºÐ.&ì©y/ý5$DÔ¬&P­ÝÔÞ4J¢Øê9®ÉxÆýAÆ7U¤oHuÂíž‘Œ@}öhОMg>¦KņfãÂñ‡U¨v|L{ å[àhjØlIcCh’jOÍk%*o–òÖ~§ "‡–»ìP½ÍÙU¡Tȸ…©HZ•é{e"nzãFFkÌÛH°Im±’JÖrPäQTƒ†›7ÂMªÄY P^Õbæ¯îrÔÑý§ &qõE$ð[ßþ›ÎM§kû“}ƒ}îsƒœ¸93h!|]SX·7b¶K§V]kZ€™÷Þ(€LÈR³V!ƒßFŠX)§ÀqÿZ?ø‡ï½°±‘k½s熣Aޏs/mä¦c°®µ¦#ì꿇x0xq#gÈþµ‹óKq gœªÕfA¨™ÈÂøÂJ[¦@óòêÔŠÝaàjÃ|³7Ÿã–L„P,p-[Žós1åQP‰z·Ü…¶‡­«VC¨t‘[«Ô–’¦„~DKJävÊÑ`hCŠ••¬eqÐÕºw5Ùø†Õ%É×:ó"` ØÌÌ–^1–Ñ…D$màE›¤[æÀ *ÍŒM`;õ{àe‰™ÕvJy,üZ~剒խÎßV–,üŽëÄöLš^éä¨iFáÓ^г þÿeëݞ㺷ó@>krâ$Ή`hûaæe*UG¢DÉF\5S>¥‡²çèBݽw_Ð$¢{ÿ.ûÖ~vìñqR{昷ys%“ÄINŽ$ÄñýØN@ài2v¢¿cÖú¾µ¡£ÄD„K÷êuý.Ƀ'ÿ‹¦À“¥4‚?L>nöR€á­··þ‡½¾|úkû m’]'¹ÇNÜ/½_ma`É’ÌjØ`Ýk­ •Æ™˜4^Ú ÒA«ïK~6Hb¥JæIGÃ=)¿ƒt´—ªŒ€LʼnÖåö ™A$’EÒöl½:\ SÙ"ÎAï_š¨/h "Gj½Zi³sÄp7´7÷sĸï"–T^+ôA¬é7eL H¦Hò¶ŒQZ¡}f …ÞÅ´bDÞ `_J2L$‹îÂ8gÒ5˜B`ÑPíu;Œ; 8Ù"(é±ìTü¨6 ³kErøø4q@>«JVfùÑiöå·SøÙiÊ¥õ9‡%œ© Іz mQVÍÈÅ×±­³=— “‡øñƒ¤®–¯ëõ:ùûIòZpëú{ hl¨ „ðÒf-tŒåŽM†lÂßxñ],\GÊ$A K)¿í¨MÚ¢M¿øì‹/Ž$ü2=ïKý•hìÓÒ¯¿O„¾tÚÊ ¼—HÜbéõÎ4- -®îÁánlã¡-¥è\qËÒùŒ?tÈU}¡H©f*éVªq=Í=^ž‹öoÌS%,A§Â—ã°a—mÀ ¯Òs:ߊ m|»ìi:è=ï×:¦B%-§øÏÍ\Œ¯‰ nŸù|çyžüý_IäÇÔãl’äKI€M¹ä·Ô§…5Ðr×´I¼¡r(m“ãfK£ Ë—#â6»+¬ŠµMYa2`¹nÛ] < Á~Û>ýüéHÇÝÝtIÐõ5çíJî&{’¥g*¼¬Pªôè961‡¤™TE ®ªÆV=MY—¹êUu,FãN9H¼Ò›&zB®HoÆAñŒªoü°ÜÄÚ¸“9­8ô¹«¼‘3ÊP#,uî¿…£8 &mA£(¸‹ä<Ì´ "ŽÊ1²¬z*üpvEK¯uÜ Š$ÑæX›„Pt£pDÖ-aKSM( =/IŒa]¡X>‚®¬1‹è£ÙQàp8.¹”©¡¶¡¼Ð:./ÒÅ« %Î!Tòh˜ií×L„×q‚– øøÑc½¡žŸ+áôô¤X,\ä;«Da±Þœò²‚&7f¹M» Ǧ¬‡åK—BüÒp2ðõÅ*Ù¯&,„¦ŒCéòvÛþó¶í÷³Ïžf»™J6î·»{’SÉ}}M}i¶›Ê’1wû»ÃAw_’'þo°ˆÑ$ËaGBpUv-A®ŒRÚJHàQlÄ-h¸àBÏN §N1$;»ïˆµ9^Øá­¡É:Q`èúÀuô-£áäHÁÛ±Ò± 6+"p«V÷tu •f#i/AÎoe6kBzÆ‘ã]x“–Ô§½4€>Vu )EÇßáúL²(ÆóŒ¶gz¢iHa˜e[ÈÜ{Éwâ)„ß°……á #ZV%CÁ G,Š‹ †ì Iƒ†S,àãŸ<9lÖͪv§''‹JP2áÎZÒ Yn¦CèϹ³]šJ ‰³œ‰#^ïÝèH,1'8Kme¼t5Ù- šÅÃmû)@·}öT!Ï»ÒÚIX¥™Ê‹JîííK\JêÛígû£ªgG€º£Iöõ‚j :ЭviÖ\«'‘PX$-!2â)zÍŸ€‡Kmõ{=ß[΀DÈ5ƒ{*ž/¿+Ö–›0…ö4GVw©€}ªôq‹ü »ßÞ’màéæa»ŠàŠ„f†ÙGéŸx Ö«ß2(ôqó³-dáy’S XI³ŸH¤V`TÒ‘ ÇʪáÖA1¾IS[œ·,MxÈdfÏ€Ÿ“+A\Xª(\ÓOàðk…U²?Ãp•É£!glHæDbè›äñÇ>yx˜4MÔøsk Š$îè,ì Ìâ ’ŽÀ[¢%_býg Áêq„³nGK°ÚIþ ±54´9*HÄ”ThÃXµ-h•m¿ý€Yzô¼/so"µUÁýTâLº>@)ÓY¶/©)P~#å÷™äIÍ€]6+Œ8= çh]gkâÀâ]²Û3ªwwî¸íÒˆq RõnŸubmØIS‹\¡L¨DZV*²wxÿÒÇù·d¼5á–æèCgìg)F—vœÃJõ¤æú§¢°} ¸¡Ý|;¾²ŠhÄ@Í®ãzºTç_}”üðÑãd--ZòR²`8üŸÿ¾4¹òﺿeÇ ´<@Ábθ‡?¦= ûp-n€',ÊÆ„Êê/’š1øU‚0@ moøìh”êze7Ñ‚+sÈÞ@B.ë÷ww³þ¾Þ^÷8 ÏçòKÖ6¼,c­ÃõN æÆâŽyn& 8.eO2¦¾°²-Ÿ÷z·{;½*Ÿf~:uSßY9ÙNúça Z]‰aá84v+n±`q¥“S¡ `*J('ZŸl,r6rݪÙS/åkÉuàX’¶‰"Õ- Y IQh ¥¨0“‘ÚNqzãõž‰Óx‡ƒÐÔ u¡@a”ß®!Lµ8ó|0„jǃg)ØE‡äš/L ¿’ÃDÆß•„ÛùùºZ%ù&Iâáÿú«‡Ò5›dÂ~œÿó…f€p¦þÚ¬¬5GEÒ @#DÓ±Ã× 2sïäèþV<Ýj^+'³®×!(*«}ž¦»IoÔÒf²¿·;PýèVÞÙSªH?ÝÏöÛ‘^‚÷†*Ÿ ‡>cWŠiʲ¡V &Ü%‡"Ä¢/r×}ƒžD8%^míôvvvü4›N*ŸËÔ[óØýÝ Âœ&(j§¾6¿ÇàÑ[Ý¥”$Ð@o »¬ùoçalyÈ…wvš¼Ð¼Xy®9Rœ«õŽÀLĦ޺3½ÌŠUc7„LˆlDÅ« LÂ&z5HÚ8Á©5¶¶?´¢~œ|¡bE&JDq׋¸ã×£¤é›ž®#˜‚ÀkqÙé¢x @i½BžlÖ’(’:Y¯ÕÊQÞk†˜ß¾_DØbz`†¼Äwšâ<–è?MÄÇÚ ½ˆUdƒÔFÊÔ0¡>Öz­—³º‘V[±ùé³çƒLÚ»öœ‹@‰ ö%÷û»{’÷²T~mv5öÒ¡æÁ–ÐSÒë  Ha Ø’·4RÐ1{î]|çþäÜ´·µu§ÚÙÉFà4MÇ“tD`£û¯TËQP/u7up.c@Б¡ø?oÇ[Õ6‹]ãX‚IõM…#wÌ6ºáGE ýÎd:ì:ªçÁ£¤ žY9O€Å/+KSš"«ì,wU6:hÓ¤6\×”d¥S¡­0\—ÖÔ@>‚ÖfÓu!d¹°u~ªÄ8:MQÔöZõ al¢PI¬S«º_s*ÐL¥À‚ð²ÜtȈЯ]žHæHdRL$ –'q•'pGÂ3£«³×:æÜÉ,]'ÞT‚#ËuZЄâs:Át\„I ºÁ†Xä‘ D²Öš„‘•‘ø|ût¦i?ÙOû€=C¤£¯`|i%¢'íÞs™UöFÑŠüT¶é¦$Gi¨ÀZ Iº‰Ì­—‡v³~%’€Îò|ê§!{kçí»’³Ì>Êò£{i–eºRß„¹ˆÑDú‘„×ý'€‚&Åî–¿‰ÀÈS+}Û} ®€ä¨¬¡“c 2Æ3æ‰ê§”º»œ6@w OŽF¦n¹M gâËÃGbà«T°Z儦°‹hÕC3NkTÒ è¢0hƒ@…Ò8æM ‰;õ`(wêãkÇÄ rŸ3|MA,Â-ÁûuR,¹b‰¿—/]R½~­h–Í(Á P]±€y!'‡O ViðíàÌæhðÀã•+q‡éee‹BU´5[ñb¦>"Žä[“1äù³Áp$)ï°jÎW)Àûý~ãݶML?ËdIÚ#iwQƒû­ÎÑšfñ7yqkI°Úµ{ ­ôÁ¡‡¶È›\Ëm˜NS©¼;Û¾× “4OCö‘ŸÈ·0U'¤å⻃pg§M¾§%dv¨IÒä-ný Kfð€îše¬Iõé]¶Êy² $‹§¹´Ü¤Ã&H ù\ Cyâs¢{¬r2F†Îö€TÃ0©TŒ° ST+#B"lÏuƒ#!DLy$ãR#˜ÜhÅÑ [¦’D: .lµG;êÂDz}Å ~.™›.ئ½p=*’“óýuR/N_¾vµª‰¿zñ:H å g7p Kª¶• ¾5ö˜|9Ú1Ęrw¢Óåæ$S %mSsf]᫼ÚÕ<{ö,Žöö“Ýåáú¼nÎ÷v÷åo5|ÙMöÚL‡TÚÄ~–È’*ë(ÙK¤\kä”®­å ‘Ž‹ v›e —¶‡F_.²¬(³tÚŒ';îí;áöV:”ˆžN?Œ“÷?L'YîÌb­gå.3–û< Wpœ×¶zº8%•ßnoÈÉÝ~L¢°*ô|—ÔëÂFƒË[}‚óh—&ZTê÷Ïý‹Frá8 –v#@ûhlƱO¶†.PDK^MÍ£0tsÙÓ{KŸÚ"§KRAØ ê,@lt6É"á´B}äý fT¤ä @sM9\æRƒíœ–àýóUO_¾P>œDÅþ‰K öÑŠ ò4 A„-¬§DH°è€œM€Ñçõ-paåRÝŠ¢´5.¨µ1|¨¢šâž Þ¥ƒáîð¹t|»ÒØžqµ‘ADêq›””´2„¤í¡k%Cê"%9㼂ìÞÊ4pu ÁÉ_zhUÙ(N;±%m %³2N'ÏGÅx–?¸'%˜ùw?ªîÝÏ| g_¸‚e¹ÏnXr UÐä‘úÛ•Õª[¶‹³œb$Ögd†@œ5d"¶~ælvñÏ1³Ú IDATD¡:”z‰èñ@µ…–\¦±*°”¿VÂlœ nÊ Î¢k¸e2²ÝcpÇe,):¶èÜ-8ÇŒM¿-–|]±YÕ©À Åttñ”U‹Jâïñ“'ûçIqzúâå"ÙÔU<Ù—^°^';INª‡ï¤‡LyœB…ß>¶ŽL'Œ…d®aЕý *ùDqá¢D5UŒÚ=ìí°?ØUj{xªfÕfÕ¤Zm¥-¤ŠÑoe6Yº¼M÷ÚöùHšC VÓ¶4šçÊä•îá©@iLÌÇóx¾„â˜:þH ÌÒz<žL«òÝ>Ì3/åßý½ìÃìÝûwîŽKøåÒM¥XçÙ´cÉq;‹¦M‡Ic¹ JÊÎ,ó¡öáxr¢-ÍŒtÖgxÄdØö6áÐQÒßC£IŠÚ’ÆmޱÎÕ®ð°ÁFµá¶$Þ¤kÓ°M)CM±,‰9óCI¿â𨠸‘Ü^G¹ËˆÑ% |‰Í9hÈ\1ÊRySkl¸Ú X,—Ô¤ŒŽÂDÞ,(x7‘Ï¡øøðq’ÔªúU$›¦zý"&Éj½IvT\4vÚ.Ì ‡D[èËÁ‘£É¿n8ÐÓ@‚LIçZK¬‹`Uܵ(êà WôÆ=oÙh0’Pk%ÈëÍz#ÿ㹄ãîîó£T*­”f½ß—‡‡mÚ†öéQo” ööû­«ˆƒA%®õ5±Ô€,¨‘ÄB0(7Š—ü”ãI9§1›úòCÉ€Ùh<šfùî½ûòx8 X\ɆR}'S]·•‰P§ò ]hq/½,oq>EpÈ×™Ÿ`y=ÏôQ:›/$!ʳµXŒ¸§Ô‡pÔ‰à¨EÚYF*Ãõ°ñTgÆ.“@M¦‡#Ý¡ùˆv!á%OõY¼£–†‘2yŽÑ‘ò×äü¡W‰7REîKZz0‹)H]­vb&Ë›¡™¦ù‚~ÄA+ðÈRžÃ´L¯É_¼pºšVO½ÄÛÂø>GÖ¯txÓŽ$£R1N¿@nÜLýZÕŠÇŽîì[k·Aå#\æT¼q—nŸ?ýbÐ>¶GÙ^zÔú¶,šóMB½‘I¸e–ÊSW çú\õCZ©Ž­´2ë9¸•¬Û” (ð+t¨Æ ×¾šJmMû Z@j‰H@†ÙÑ8Í¢ä¶÷î»<ËÞ_æ’ïÝ»_.ÿZ¸Q:OG™|Ü´cÉy;{ê¿+SÝ2·lIWð6ÏãÙñ±÷ÓT‡á\>.0g3¬kÇî-c©Yg³ã N¨kO¡Ý#oPpÛgÆ’Xž€ŽÜ…=¨G]Ÿ®§aéIu¯’ Æcã±®$·49à¤+µ 0˜Ã‰Ä$¤aê½Ã`ƒÄœàeÍâGª Ä RÕƒ­v°JÉ bbòäÉc™ƒ×ëÕù¹<ÙÉyø«êóDÿ\TX:w(üHäÒ…‰h–œƒûJ’¹XøV˜qA¿„T84É´ bŠZ•ðr»™cÚO³£´}:h5ùí}ÚºeR­‹rSDÕ3Ú´ý£Q_©‰Œ#‰ ‡Ä&µÏw³Ýd4LUY@C —Ëo™_H?n(Ò»K©|xíGŸ¼è¦’eÜ•˜+ßÿྯ²ðÁ‡…ûàƒû÷õ™.b¦Ùdt4M?x_²¤±äx€¤R)„p— ê•·L¡]Ê|þ:•_&òôOFÓ“àf“É„ÂÔ‰ú‚äa<žN}B)G:ÍQàK¹´¦çY‰º˜éh´­Û(ÊffÂ÷‚1ØM R“HÉf6À ÄÎŒÁñLí‘Èsc[Bû§Ä© ~ä® ”ÿ³ª¼m{‰z)ب@áZŽèí^X!ðѓǺŠnÜò\Þ’×§Aê± ’‰”愇ŒÛÌ¯áø‹òÈÔi"Ô‹ö„¡á.XUê+Ñ«‚g2Ón fÅùϳImýô¨}~Ô†¼måÛn%¢b}¾ÒÁmÓ>}úôh Z]š®å'¨‚íÓLb/MÊ/ƒޝ±X¨M­|E”`­W*À¬Lç6V¦eöƒîϦ2uèX첃"Üûð>ˆàzÓщßé|rï¨Ì>I’4–œ™ûa!]A~á”%§Ð!ÀEM²XŒÇÅét:P@Q:›ù±ä;¯‘¨Ül>—`œÄU§þt ÃhÂèùˆ¬‚=oÓV$“×X}®hWr*Å€NyTJ§Vfh¸*Võž‘QdÑÖé%ÆyÜäÌhO-P«Xãè?Z«»7š‘Ö¢2& «1XpS”{ËQP»AW'øñ#ÉtµtLçÕê|k˜dýúõZS`‚3=çô%SsiR`P:ô˜®€ôawPVUzÅv¬lЦ“„Ñ긂•7ÐÊù0æ­÷çiëóVd Ȳì\K_È÷6:Òt?ŽÇc l‹D©¼’ýñ,]H;êÒ´ë°õ e”“ÒìW‹PC‡£¢&B a77ŸvDÄik@iˆ‚œ7›Š¦fr€à†dàTs¯) .U8¼ë(žó«ƒ'2²7U.eF•7R­vŸ¹H òS$‘ˆ³5Ø’šAÉ“‡?J44Ö«Õ²9|ð+‡ñµKN_äçÉ#‰L=­Ñc9g.o»Mèã ±qË"ßÐ1@p’W0g­©Ò a|€l§÷Š&/ƒ¥ŒÊY[úv/%ûª"µY—E›>{ªüÝ]L!ÉéYîuEÔ>IÌúùXg$½Î¦r õÚœ\¡KjÀDÝÊo:{^Ifï¾÷a|ÿ¾ïõzÎmßîmÝÙáåרúnÚd÷ïùÞõ™àfãÏ((C·9·d"ñ·f¨–›éZin«b3›Ç2•KÈÈ6p£€HÔÞݽRðÃ( “QæVãÈ~M«+¯ {b†©êY^ÌG*|Á>:¨ËÙg]\¾äˆŠô•£ÐRuamÈ\'3ÜÁk H‘w"÷ôA¬,— @Õ¨¨0õxUc•2lüH Pñæxç jdÉ3@òødÀäü¼©NOÜêüðð°^¾Ì%"×uòðÑ“Dï…Þ¡róìä °à i:o&5¨‹vÕiThµ’^_fà­ÑI nl ZÙ5Ô 'IpÐòZ/©0ëïí&íùFÅíÓg}uGÚÝWºp+­Âù!&E׎T7+Ù$­ã‰Z\&C3:­ý«õ€ABÈÈ«Æ9ö1ò5Ë>üèÞ‡ÀÜmݹ}w«îåËÞwYr‹"Ü7û0û{1 ól)íÏ÷úž‘8F,j)_·¦ò"Ü4“9a*a&CÎ86ãqŠŒ/=_(kyJ&¾NH6a2ž…ÉÌ4i)Iþ9¢‹G ^0ÌKsá ›¿AÐ ×#Ä£Òμ↊úTU ƒè_Ab)WPÅ0д‚³ÞfÙ>ÝÝí'é kÉæ’_:Š:̃@¢Í8DšSú†èv¢*x B°öïÞÿƒz[þ»·ïÜÙVPL^]Æ‚²î¿?pÙt$É*æçvÏíÜéNn%71’n5ëºÜ,'ǶŠÙHü É€Cmå¤>7 ‘‰e9“Gr"Uq: iŠÐ¾ s¸LÛ9-ð–WN7$_oÊ I@¥ñÛhÍÐñÓ 2: «ÀÏ„jÑY_—íFŽàlïL°¬>ÞÐæhòàqzQo„þ¿’[‘Bï(v’3Œ5›E’³’G?~òà‰LËÓ“\uíNN¶€O=|œ°¤qckB!SènCBr„÷!Ȩf˜Ä½È/D¢®`¤¥‘;Amí š›øîb ˆéÕP2^ª¬_Å¿4›fÕ†ý=xƒÈ¢ðôu¾ÅéwPUÊdb.\¾{ª¿&)ðÝ{¿bÏ[½JaÑÒÞñ2"KÈ©âþ½{fÓéûïË ,õ‹Ñ€nlH4Uݪ›Ùé\m^šxýìõì´i²4Óæ\zÍ:V…&BDÇtü¢˜O¥ŽM§2.k™Æm-VQ3ˆ°äðk§   HH–~\4|øæc# Ò%T·¬ÉÁ嬴ª^`µÃîªD½$[¡Œ7xÔH¦=V`nL`57œÂV:¥7r'4ˆÁûÄçÉÿÅ£zòè‡Ož×EpAI& ^€ËÓ(àRÑ7FÞ‹| ÇàiaÓ.ÕøóÁ`8Žv%çí·›bÝ}z”î õ·ý >§UuHÆ“ŽÁý~Òµh!·§_ÍÊp5Äe'ϺjÖ:SΓ¢¾÷˺TÛ¹}ûÎííí^okkkg;þ¼}œ~þû÷?øÀû% éóÀ~1Ú>‚æó’+n•›c‰¿ã¶¨ŸÃ㘦/õE[û€"ëC=¯u‘U-§™<^ǯói%a[ä3ýw/<ƒÈ£^›ÿ ý?˜ ¸‘ <š•MÁ*pÔ•§­Êàêß+°Þ!Ã…>“®Õ CX’™É¢/"?W¿Ü«àó2€™žðšKŠ Æ°´hð’‚ÀÉáÃ>~ü€spé^/ ]I'›êô¥“|ðàQbàR´ÕÀÕ"¼K²@‰ÈÅ‘ëÕÚcÁ´hhLͲ0åg5SÌÆØ»JÉ;G*Úxc`ë…«Íž®Îè*-¿îk –`”øSÓ†¬ÝÉ\z(%µƒ#•ÐJGmN—y‚M…uõª¡? °¸Å•fJ¢¿ø0œêœ~ðKQÑÐùÎNo§¿³³uûnèe äâ.Hîxo2—éÖ? _ =)ÝT>À6Wzí[ÅTâo>›[o/Útëñ$näC6~Üaì*ç¥òúq*Ñ5—‡s2©ÆÇ•&çrÉûËhÑA˜È*)ÈÓ¤D%瑞ÆpôQ/‹oé¼úÔœäÐQS([ Ô#@\c´Ž+wÑ4¸ÃÜO1hìþƒ­±¯ÐÔ±¥Bðc H“2,ñ ¼Þ¹©–<ùä“Ç$ JVáôäåYpíNå½G|¢Œ«7 ®‚àŸhãá…PF)\ª&xSS¿ì +C§T AàÌQ¢0TúÃ.Uè-o?}ÖÏTZW.¤lŸö4MöÕ»U©rR™ON‡$Ú¶ÏúûRœÓ–—1]…4fVÓ’¹ʽ¦!Í´ØôCqïÂýäÿÞ ~GKð;[wwvv·3ð[ô ¾Êè”ßÞ¿?õÙ=‘{[±ë%vㆅ[›àü?Ï»›Ay<ž5“™´}“qqœž¦àôòËt0YÒÒ}2Î4.)‰;ÿ_%hÒ&3IÌ\=Æ º E·£ Õž†›ET¯\á$h¢ŒÕj# ìôP¨/HК”"…äè *¹Ž°®œxß¡‘[vÚúXMFÓïg seÐ%Láƒôè㇇Ÿ¨IœÌ½ùÉ‹—¯“'Éùº^œžÊ{øÉƒ‡ÉkàÀóÜîÙàii)óEŽ«¤B À÷7v} ‡ *—"¤_"Kê„Uæ$3=#-«m¬ôYšB PXŠ>H£lÔ%û}AvÕ?îüâsA7éîÞ°¥T{¤Gå ì;½€N­`ÓK¤Í ë•V| 0 3ÈåÕ¹ì…w¶Ý»½íÝ|{61W„ܘÖbÉ Ùt<˜L§y/w[=ßõ‹ØŠÌ§ÏÖ­Qª p2ŸW)h›Í´HÓ¢9ž³É<•"[IŸ:7›æ~r<Ìã A#ŸÞ;o áÂw‚¬‘ ±sY°™„×9V“çå'q+ÅC| äTõ|EQpˆÀiÃà%ôù ¸0E-È¥¹rXÅ;™#›)R¬ŸÈá[GåT£9›©‰TàÃøøÑ¡´}Ë/$ìž$›u¡SÈ“‡|ü(1–'·.-^+Kð˜{±Bå§å±<{:`®€B(Ì¢q§Q¸B>ÒVPŸþ˜ÿ5oN£·õý§ƒÁ Ý5!¬¤­êö³Ïž¥’èúIÖîõw‡‰óõ!áñí ¿;R™J“þ³]$¿îŽq .Ã1eDÿ´¢H•YŽªV½ü­wîö\o»Óë5VXèæaš¾Ÿ 'ÞËòÝ~‘mŒý­Íðx¾B’ÎëyÇÙæäxþ_F›M|’ôo8•þ/“œ½ŸAh(OÇøÜÞÇ·äþ¶BoÏ ‹ŠÃ-˜ÒÝ<Û¼’¼‚jØ–¥ç²Åu¡¢¨¯M`¥é 2feeÒ šÉR_pêgÑŸ­v,ÎÁ˜•|Mï™ %°Í)Лؒq´¨£$/³eÔ|ò«?|ôèp¿ ‹S̾ªÀ—Ô1høñÃÄ›u6 Ëe¨Ácåϧƒ½E‚w$,Ðös/\ =JãÕÒ¨!ëÕÊçÿè¿yËq/nO%â†{g»L€*¡:H³þH‰è€ªÌñJj0AÚ£lئØ ¤¯è”„ÛóŠ÷n7-#øx )“Îä^A°÷*OäÞýè£ÌËœáIðË#!˹˜¹Ñx˜ŽÎÂbg§ü¶_Œ½L‚øš½5ÓðûfžÁÌÐûm6­ç“êxþÍàól2™ž$Iïnoúád’I’ðlV)Áq4ÈUD©)×S‘DØ0ƒ?‚ïYx°ÖP£7m„À•3¶üOè×yèE0Kc¢»¾Ju¼*DE<†Û@-.®W 3 2$ o}i\ÔP/dÙlõW€®„å¸-¿Ñ±!R—7‹m‚ ©!”“èkQµù•HBùUóê —?NÁeEºº‚¦×dWFw$3ÉA”K°{÷É…îvÐŒ`¦„õIz8>’vÇÅŸï{E´n)†[:Ï¿8šÏá+W’&&ãñFfãoþê³l“)¢×»=›8ù„1ŽGÙËéTOj>}žN)¾æ$IéºÒDƒ¬0Ü¡šVÛ¡jT­·†ÈŠž¥…@íLí!p|ÌËt—†ðföÐ%xAb¤@$6l:hÆÅñ^èñfé ¥_@q€{Ð>/’ïI4#¬+éï‰û“Î þª྄]"ExYK ¸/øø«ep‡UY¯Ï#ç.Ìï¥5z•QèÈ&X?)z¿Á ÉÃ/†ßˆøWEOK…hùü×ñö[oýÖoýº½){Cзg2€@õWmÕÒ]UˆQy¶v7é÷‡Iˆõ¡4d‡¯[É­úÛ€jª„ Š¡#ÐéQÉ‘ƒ n˜*ÒäÉL§pd2(V•÷îÇRaµ Ï* \š®DSi³i6 Ù`·^õÎöýbÜÖ"Lt;nÁ’GƒlαD1/Š(9ü'ÃçIòö÷ÿîßùÛãñ$L&>§q&ÿVîÉ…k”¢ša lî"!ѤÞ®âÆ—W@Ë`š@²ÂF§ S2âBH÷>P@{ f¦'Òë\ô”€×BÌ;å‚ W4a2š¶–é$Ç[M,È$‰äA0½¬ ÄAŸÈœ+Cðà~â^Ÿ.*©À›ýu-Áš¡cUT Þ‰ç5p¡ qpކƒ[A6ºn‡µdÖûñ· ð©ráÚªm³=…à“ä–ª­†ÍQú\}ºÖêó¼·ìÜáúU«(%˜B…2*T¨¹MGÀC3(I¸´s᩺2ê½·n–Œayß»OŠh5}{ mÏÔ&Q è0%(‹ªW¢_ ½íÈ×{M'"çéÓã9%'‘³éêx2˜‡Óñ(ùÅ·¿ß»{{:ݞͦ>Œ³E9›•㱋³ ¨Ñª®•5×0p#ˆu±¦Ê’n3¾¢QŽ-É kÀEBõŠ0Oçè× =ÉÐu È0‘-ãSA›ºk­g­ŠÔ·þ‰€¬Â$ uQ¨ƒäñ­. âãv³v[†àG?ùá'2„HØI<]6ÉFÁÑÒŸIz|ôñƒÇ Ív»CpqcT N+“E`ªˆfå 8vÔhúh[®“@ICÕcÀ2ÿßüßøßüíßþíß|ë7ø¾rÀ¤ë¨%%ᥩ”àõ!Xíß+öÔ>3I[•)×Ut¹>ô‡¯›M;8Ò ˜Y°Y·òä¡/Þ¦aêY½ªÖ®R=²³—  ï݇\O/)¿;^B¬·ìõòñp4O³ét¨‹£ž$Ê·ïÞíUu/àID{áoaË$¢—_>jyoŽÇÓùàxÓñIoçû·“ÉäÃE–J4dŒK'Ùk}®²1í8©5£,ˆ½‘×–"ÒHD2Ut<ާŽM8kt’¨%PY(ÝÑä+•­­–™ðƒÂn­Á]WÑ(ŸÆ¤ÑQ5{BbMª˜orGØÐQ‚Ch!¨8V¢Äq}Q @®¼.—“Ÿ *!Q^ˆô€›s‰Fž'’Û···wn¿“¦µOeðåN‘1ò9稴ª³aaR˜ð)×^ÄcBŸk1­ŒKvW—5rå™vÅ·˜ãóeŠYHƒuÉeJÀS\v‹”@4+ ,F}'êŽðØïv›>B )ÿ¢-`/j .Š¥O?üøñ£O>ù¡àºÉs ÁÕâµ È>y¨›h½Øº‹î»&@×?‹n=·óSä’<¦xyú“ *®zÅ)>uUóJ"_òÇ|{ëÇí[ön^”ô“¡ü¤ »µFàªÅ.°¯\ÌÃýdOaY±99<Û4{í® æT˜lW1™€µé„öQu³¤±+Ù !ézVR‚ ò`$Ç)B+ô¶¶nû»;Ûq>É>J‹_z¯Ì2è”Ú/}ˆ<Î Ú*Ýâä¯$þÂÙL÷k2€Gé|ú—óãÙf3×ÖîÝ­·oçÊqš*ß P˜Ž%åVp!¦”½,=ŸuÛkƒ÷M'2àØ83}ãT2RÆYG ä!zéã‡D¦_j£„äGáó.`©€Iuk’˽­¡Ž‘Âø•ríRñEÓ©÷¢¯R–®q<ÆDom<üøÁ©¹åéË—2–È{ÕâåKÅÂ<øD’`B=QÏ`a:Àƒ±`,G]ç%GžÿËocO>Sí‚›µÂ`a$!˜ç¿ýcý«•&ïýöó•ôjm[û£/¤÷÷ü)|X[-ËRhww“|qÈ\¼Z/6¯ÖÉ E†ÎÇ7d"¯1ø`**×J}Q G-µ¤.ð5¥—êLP²íÛ½ÅÖÎÝí­Åf’¹ì£÷>x¯Ê ÜnŠR‚ÕUžW8T‚¨Gã[ØÎe‘§f:•Gk¬Ëèù||ôÍlRÅ4Súþí·“ñ8ËŽÁJL&0Ê0˜Ïçê“ü§+tì» £ûpcB± È<¦O¶j h[á€Œá˜Ø-ìÂmžT5C¨;Ô(«öågu” IDATiúŒæÈ?t4ô^Ðøœ]Ëȉ| "(H"0ª:áTK~Æ¿¹´€<–ü6ëDO"áôåirøøáƒµC'GZ×WZeë”õ’ÆŽº|‰v×Ô‘¸6jª¯¡æ*3S ñJû }Á5Ëäù?æ[Ûþãî-‡¦æJmS…¤Ê¬{Øð€'½Þ§Ãýô¤>ÜK÷“ç{ɪ^VKù˜½#‰ÏN©K¡æÖí!ë÷c=he¸ÄuµÆ4Ò@¢ÜpdÁY.„ž“jqL½$¦ÞÎRþéÝ–þo¹ÕóÓã<ûè£wk<õ¹*èÊÀRvêò3®YÁ!$eò+‰±˜ű„d:OÓôeQwl—¥]ï ¤Œ €4Oj‚ )áì0y”žQ ¸—Ó|ôè“Ç À*"ïI6áåi.ï=|(ãÉ£„@_Ðáš(ŒNí¡ð}‚Ï<¿)-ôÐ|Æ ’¥ LÒ >ÚJq ñª*wj@D´' Îæò4Ûx;ˆ¿éd\KFD†‡N&ûcw|,__˰’«ýœTÉÀ53mhb·ƒ1~ˆ­| r8·|‡b0°¦QceCX‹L÷úçÜwf74BR/‡PÙj:Òm¡ i/±ÝMÄøÎ„°kd‚B:çÔE…ìB,ÌÖÛtqt_’<~üèp-…÷Ÿ®‹ ï=zøÉ¯>z”€mþ×¼Êã5Pqµ„³¥ÏKŸÙVª[]š\iÙCˆ2ãkß)ÃåÛüΫö(û¬ÝµY[šže%%8ö÷öN7Õz·Ÿ%Ï÷Õ;óyÚßka@RÒS"xÂÿk»¼@†f$Ä+ϯÍà;„ν#˜ÐÉ·`A–aY?ÍDµgòäôQ¯ËJSe° Їˆõ4ã¢ñÆË­‘8XÂÁ¡Õûòf‚.œmÐôªHçÏbF´bQ^ !`D…«R‡Ž޾ˆz&ÒÐA!†:覺ê4„3ú³ŸP¯ Jœ…ª-ÂO45ï¾Ì€|;”ØK™VþkN¶X8v¸?˜šŽš•éŸVØ=7&(´µkH“&Ôu²+$>-åV©J×>o?;ÊÚÝ/Z³˜C øô׎Òaªz1q³éïî'ûÏ÷Ÿ+Dº]Ñ€„Ÿªpˆ9ÌÀ ~¬$ ¯ ‚n7ê#Õ0qEߨq—wB}ú9À2oŒä]Ê»oïl½z½Í_fÒ¢Íï¿§{ç'*¦S.¤Õ›…p2M0„|3ŸÎç€zN¤ÈO7ŠÄŸOfÏ'µBž‹r1‚ZFœLÆ<œ Ï3mhB‰ cu¬±€.ctaI„hP^æù¿¢¥$¹ Hp Íî-P<¥04 µ®"ÓÕÅñ¯Ò¥k.¬¨èo°ˆ\À”l¿X5È)AJç:c,¡/hKiCHbªv¥õ’.‰´€‡>~ˆ¼'‰,yô%ZK(ȯº0¼9ÙêÕÖó?»‚[£Ât+Ës\_ @¯P€ërát³ÕhÑ"–ÃrÚS þ˜B–Z5<emû£–†[š·Víógé Ÿ©2LµiÎw“}éU.a*QUùžqì³»[ƒËÊHJµáajZŠ8ˆY•WXG£ºá*=pü(óé2¸^¯Ü–VÐM§§Çéxºr“©Ë±* NãNþ¿ñ8ÌféBžÞ|”y­U^#+¦Ñ0™(äYC`œ¡âž9úß/.JÞ/Hd$ÁW?Öö€6?à¶uq}}¡ÏÎDͼ±ìÈž ‰´ÞÀP&¿À5`iJøúÉ  @Ô«ã´lŒR\ÐúpwÉCéõ>¾äß óWM’à9r X±<Ó¾õÖ¯¿õ[¿þO¶|mRv™LU~HãnÈ¿@²‘¹yUµþ³ÖÜF9WíÄʸüÔ”„íàïÛ#©½ígíçí³4ݕѶms]hÛ£OŸ·£4“hk¥iØ$û»i’ªyWÛDÐ=èØÊ/°ƒhàÒÞQ@¬3Àߪ ‡›`MYCÏ=z¼‘v•?û.8ݺî¼]îlÏÆ³É`¼r³L¢Î)××â.ÆWìõäá~: ³[ó™ŒÀ2•ǰñµÌÅÙ7ÉŒ ÇxÚ&D©¸³iŠm|¸|®‰ ¹¼xê°òG!HüqI’¡ñgy”æ‹ Z8I”Òh”غ(Ç]ÝŠ˜Ä`¼à‰º$”õâzs-RÈÿRԬƒXI…`*@<µÙª£½"°áÊÖ¾*¾#ç}Hb?§Z°S §µÄYòàcEä7È{U²Ù(“L¾ùZBPJ&™ì¼Øþø­¿õ~̘sâB©(èõÄ‘G¾z£;_ ·†CpmÞ4Ê$lͪ¢âQjŠŠsƒÚöù…"û>m?´¿öôiö4ÍÔy¡‘¿W.hNŽÚçi;WJy›Çudi¶Ÿî©KR CøÅ*Á‘,0jâolŽé62 üCT••|BYyÇ£oBê²û÷âGïà«Rd8š¤éø(Ê`{œÇùÜ‹;y̤°ÇŠf:…pk>M%«X*S…;óq6;Ç“qF$FÝ£JÌ)vˆˆ|‰“™ñ htYQ•Â,S B Œ~TÎaASÑ…Dú½òâúüúúëX~u½¹(¨m õ1÷¼|Ä ¶ñ”Í•h•Ô°Ô_+pBÊ‚zÔ(Ã2º”Ñô£Y3‘—à€wáõÿ•[£”œX ÷P@ÃX‘äãÇ?—÷ôWÅÅë×Ë !(ò’lßúíß|ë­·ìdÙsÆh¢%] €cÕJEo*4|òW³* Ÿ–zeÊXÐþ ¹99ëP™KÉý4k”¥ýìh0jåƒ[TÛçÃöó´¦ísͺF”2\œïíîígý}ÀeW9s=ȽÕ^fÝeó¼·.Áh;Ä¥§:#ÕÌ £$¸NGÃ2+?üàÝJú¸ñêi6š8 º©Þm%õTˆ;-XE5ÔE<‘pŒNp¨5UmžG©|ÖIœ§éñàLê,›ÖÇÇ:’DÊ`ùôhv †|Ȧéx4È^EúyâêÆyä9¨M`™R¦ºB,°±ðC)ýú+Í’Óί¯.âÅ•„VpÝÚX5<ób ¬¾äª¯®¯Ï5™•Ô-}'s¬Õã$Ì%äMbpж!NÛ¨¡€ò•ŽtXDsÈ-&‘!ºýù¼'= Ú¢/—Ëp.Ip©¢l7÷ÚòOìh››ÄÂ2 öO¥Ó¾®>DJ’nCt—€txÅU&¦OœFãÒµßy£ÁpígPÍúlÔ†°­·mËõF…{[Åþ—´–ô O.uön,ÖAƒZS¤reÆqMçÖáuO§Ý96@ì÷ß«>(ŒœeÖ•ŒÆ“l¦#ƒ$1Tí`Âc¹ö„Úh¥ãq¼5?Ïçõ¬’WÅh!i³™ÏfÙ?ÈÆµ¤Î&žÊטó–KšT¿ôQãgªZ„ÁÛåÒî \+ã9cG:Ɔ›Ç××›õõõWòGŠ€W?‘¼’Ø*¾&’ÅRp±¾¾ˆ¶Nùêújƒ,X‰Õ|—¬cB°M^ í“ÁtaÀDØOµ‹dI9œdäP_xÝ%Žrô_å=ÚªjNIšv}<Ó¶o}{´E v`?ä”6†‡àc«Š‹èæœÜH¥¿Â<ºþìÆ2½¢—+¬¯,u…Öµ^ÿ6V=Õ”‚ÔäöÓQû£´ý4m±öê9æz 0z GzrBÝwò¢ê[ÚÞÆy8ß4KœÃ—(K)‹‚Á (%v1 ½45+6†NuàaîÐïßó@yJ•ìò?ðY¨îð~™H½}&Xä;»î­»;T—„6¾âÖÔI žÿÕ|s6Sˆ 6=óÙ|6gòŽŽÒi6Å…Â)FŸ8P ǃ‰™ºš›—)\!ÇÆÂFÔÀñ•·[ðŽÛ1yûúzµÙ¬¯¾üZ"îzsõæÍï¾Yo®®¿þúê\Šnaæ 4ËÅ×WWoV«õ•Ôîë«sÌ"A—…©_„o³w0¡ƒØD‹v«¦kÍ…dIÄ^Žu¤y“†¢1žö<ÊÓ$%Xrà·y¯2UW…³“mûsG[ QµHN¢SEcQ™"%NÁuÁ36|…¼S„­™kG¿%tmgðaRÛhjÒÃ@G ù§už‰±5·bJÛ˜ª3ÇX‡o¢ÿ­Á-J xÃF#Jøž¸e » Ïd¡ì;¿òþ‡÷J§`ée&™ÎîÏç¿6Ïݼ/ÁÑÛùžG›>§¯i1Qι¼ÿ2MoÉÄ;—¢=•Iy¦g}½üš4‹a$¡FŸ§µ@>fҕߥ³‘­F±`ñBî‰<”Ñr•ïï·ðDRæýõõúüüÍ›7_þä¼÷æÍ¿””Løµ¶‚PåÔ`¸øéõù•®\—~oÖÒ§\]w•˜Ûå Ëjl³y†“G‰§aÄ%Qy›‹åÂE»ÐDZ0¢fËS…[]”øóo5-f|N%7îGþÛ“-HÆ8ÄÁøó݃KB†‹`б"K¨·5µEš§Ë FÏ%í)o¹0§›"Ÿ2OB±ýs{ÓÀ<ÀÈ÷Ø÷Æ>-ï|”Ù"uÃ}ªÙDX‹[J.•ÀloïߟÆì2õý¼;-ùxüÅD‘z»>4wîÊ3¾m§)“qÜÈ·¸ñéÓáü8@R§àçé7Ÿ*ù/n¢b¯ »­â†~<£±"?#ÏÃ} Ïs0?^ÒΩJ[PuÜ€(¨Ü%tä¡QQJÕÝ›7?yó¦^K$¾YI@^}%Áuý•…ò_/¤ê^}- ìßJø©€èêÍÕµä¿ó ·1šÁt{\™Ân`øà)ÆM„ÞÖ&ÆjG$#`)Áƒ&‚žî`žê—9}að° ç- Lài‹æü͵*šà¬d&¡…ä q ’.唕VSM*‡b$®­ D¼4È_l;»%Rñ Ñi8k Ö¦¨dv1ÄËwpfý =µ†ªƒ0` ˜>VeŠXç@PWÆ_ÑoªÎ ¢“A V»ÉÞÎÝw¶wv$ß¾ó¾ÏéÝLfÔÇïm¿sG>xGZG ñ$=ãɬ–¸‹·¦Gó¹ÂŠñXo»’¾ËC9›Í³Ík,0ªh{˜£éEz—ñƾ!ǘûå?ØØüƒ€I]¢H^ Ũ„€ºêÔГÖG~-Vë7g[˜|åSJüU뫯¾üê+ùsUÙmÞ\ÉÛJr§~HTi¯¾èpîKð÷1MXdQž£dd•®Š9L¬]ïFU¹ìׇÀ,5í0Miý‰—ŽØ$ü{;OÛ ´åÎж´»¢ép¡© W º<ìž¼‡¨s›NŨ7Z $?zâ0l¹,ÚPÒ9¬ïn 9©Ö÷-–yïÌ]\\,|qЮ9ÐTâi¡Ž‰Nˆ ¢OÕŠÖ« ’X¥†bä8nöFŸH­Ù;w¶z··ÕƵç»ãw§™ætLJÛýíXìì„b“D+K:ŸdãÙq¨¥ëÅÎ`…âKçß Çç‡ÏžeÇ‘¤ÖOMóœªò.—ü 7¢œöVĤDSl)Î$˜e—áQÀ!ÓmøúêNZzòÓʯ¡ÒD¸ÒB¬›ùTWò€hÈIŒªüAÍøkÖš©á±,Uüó”²³G_РUÀ‰n=%Ïê”J°.·¤hN.B ®“ò4. ƒ•¿Ô©ì™B4׉\JÁSÛ®à&¬‹”xmöë• ]®›5ÌÖëó0ï²@Å[TBzÚâ³®êê@—C™Aa†ØÝa6ÅÇ6œʼ3ãî¿ÉÝÁi88ÛÄ‹ÿûÿ¼@”&E“`…„®£ÆŠ&꼉˜cD…|Ûp­v6ÁÑà4Á$CK|/Êß[woßÙ7³wï£ìƒ|œæ€öjî„¿¹Óûžà‡·ûIâcº‰£ÿ2?>ÙdcLÁTBU‹‰ÆÑ(,6“Ù ûô‹¡;&ͨ¿3@ÉfÝ•[³Á'_—(žÞQŒ—ÆÜ Ý•†Š WM*H÷æ/4ëµ”Ù R.ü…¤½B“â›Ë¸<9-"Ò¥<[26±ÏÂWƒÒLrˆÔ‡’¹OáØ e ³Ë½! ³©ƒnAîu<}™ÜÀ=v‡hº;¿-Ê›ÁŒO-Ä›#“Y?¥\O:ž†´Zþ— _¯*SàÕ~1¢â¤õªS¨ZS¢TÝU‡œ’àT·T?vȹ9öa6;o°sÅ9ðoÆŸü_§•ÄÝÁÅÿñ]œgM}PÍAU£­j¬æ¢ÔÕŒ^C°µh@\⋤ò6,%j0T÷zåÎÎö;·ï¾ã¶z¡Üzÿ~6™ #oÞœy­ÄE%àÕÅõêúâ«7ò\JøÉkþdéb%ï+›vu~õo¿ñ\¢LïEc*˜žn Ž5° BN°C0M rF¾RH4ÕªT2¤Ë0Õ1uäÊGó&I›³ä;rƒÎ ŽUz»É«¹šTСª¸Ù9—÷5VeŒZ’¹‚q¹¤,Ý´±ÐC<¢ÄmŽ)dÙ (¬æ1F}s®MJBH¡bÀaôÁùOVåAõå¿üòK—Wh›ƒUäÒ‹ø°”9ŸÏÿêSê—¬T<@‡z6ÓæCFlèàêž»pcùèAsÔ­tUÂþ7òBRAuÍ9éèàFKÆe,ëË7õ›…¼Ž%J!¾’I£9ÿêÍ¥+ß\^^úåâ…<{Õ¥<¼ªn»ºúÉO®/xРœŒJ•¬˜'„waQaN ½ôïÊFd -Áš’ý±ÔG¤0A…: Ä&è°~÷Ü€Dƒ"¯“€€.-G/è°ÏÄòTråZŸÈµ´uCõ§!¸ŠuC_$GàžSÁ«µ*±#,![T7º…ÁÖiÛåóÒö¬¥Uå(×ÀðÓgïà­ƒÂK,|,WÕä?MMQ¡³0¯:%&)PDVÈZQLá ,3°Âs¢ÛÞÙRêööí;·oïäÅ­_Ö–{8Á„·½½îܽSlo7ÛwÇûïüÝï¿$χ@q…î%|$ÎñMúk£ÁÀ)Ñc>ýâ?MÙ¢ëúvãéw«Yn:ÅÀáÐ*E8L†™ùµrH´¬$Ï<šìÖZ’¾¦d‰Ÿ¾y£g©ËKýq/—ҘȨQœ¿‘ŒÕ———‹E!ñ·\¼|!Oååe¡><¥ "ÿú'×ç8 #üjRñ؆¦_R˜W@$D†™M„Ï%Å>h¦\`º"zË˜Íøn2ž”;Œ„èZuɉ*FSR‘é:yõ'aá5uO+Ѥõk6*¾r.½•v1š¸tô8/%Ç$B0Ö•½©ØéWòÀI¨ b³8_0út~)¡£n:ÓÍ yسhë¿ÞâIZ¿Õ_UÁ×z‚‘ÿAG[O‘Ý=jzÅZ6êÔ2T’"ÒL1ð„^/¿{{kg§¿ÓƒLõ½-°˜²Ypã±Ìñ{½»¯;ÓÛw{ßû“Ñx(ðx"õáÖü?Í%èfz>st4Ùl‚‡fÛ`4‰¶<žrÎBîÕ vœ £›òÅ,ä”ZàÀŒ 1ùêÜÏÄ›·Â_(=Q0œ.Cqyy¹”±ûM™»æòÍUYKü½>ñ—úë‹^ïȗg'Aªôï^]ã ÒIDÓ˜F&#®y `4Û·`±GÇD³@âCX-ÕLÝ„>îE©Ô` üŽII ­Vé¹££ð ÞÈ J#äO_ýº4ÁNCk³Öü§Žç}çTæ“߮Υ¸­Ô7×Sl,Kÿ†J¢X Vºœ.¡_©§9x²YvvDz¥”Ÿ'¬µ90;Žg’¡CT¸õê ä¥dÁþpM ÿÐãÐŽYÊ«;p„vªœ 7ÝH-t=åBo{wgçîÖ;Z‚wŠz§¸Û òm¾Î&©"`îÞÝ’äïô¾'ÿš,n§w2NC ?L§€TÅ"`Å"ÈøyÂ(u Iýf2›o4›ãÉnʰ×MP LW°üL¾ó©YúÁ¨Sy׆~çóȶsñå—!h¾~™—>?)$%ÎâI.­^yqqyùêԇ˓—/_¼r——ò´Ÿ¼|±(u\–A˜˜„‚ò[;’(¢¶ Žà&JA×áÆ.b¹ª¬Ö’j»Sæ\Ò(2‹wCxÅ•F;Ô¹æ*F«fÎÔiêè¸Ô[~¥,–Ž@ñi ”àÒçvU(ßqmh»ºÜ”š6ëU‘©„jJ''SÒÚ —Ⲥ…©n£©Œ¯èëQ7*¿3{ôû]a5ˆ¾ÿçwÿíÁJþ çõAÙèg$Y_,Rlâ4µÙ W¼FRÇÓzY#¤o»ÝíÞÝ;aûØŒv¶²*öâÇ㻷˰ó·îÞ¹S­îÜyçöÎööv’ %8㙑 Ä.Å2Öβqy±(?ýÏs?Gìÿr¼¡Dpt2‚ãÒLúYÌ'”z`p´«§ŠÍh¸Œ.èTØ\­ÓñÅõõ—_J^ºg!^.^ºË³WNBñ•äÂ7_^ËÛ«üúú嫯__^æ‹…äÁWK_kø]­7ç××ç« 7ŒåŒcÎÆM[”Hbþ™¾Á”(Ù°0㘯¡êŠî¢d1‰sæBë+s³|¡c<F‰tjc#²ÚÇKþãg-)dã׆…”$ù ;VŒT-Uÿ¤ÍHr†WÌm%Ï 9ñ{”w¡½…¦ —Œ2úzwuçRüÓöÏþ…Ä_Üœ8é+™C}Ý—­»I•Ž qÒÝ'yêpQZ1Ñ” [ðížëÝ}g뺧lª¸£ø©0ž¢ÿzçL"Ê—Ûþ›·ßÞº«LùÁfìU†mêÜ­ÙtŠú;ŸÎf“tJ­ããÙlúüÿ›£*ðè‹éä$Ö(îl¤®hß§µX~7›-l¯lQ]à˜ L¶ KÂ’¬ÝÏꘫû½Ëøâd/ó¹ÔÚ¥¼sâÊ˯®®ÿü/®O¯ÿâúÅ‹i_ŸJø½\ìl®äyY_saóÇueð‡Œ(޲z—ëdúq³=yi/‡]õ¤ÑÓDÈ›ñû’! 0C„°„-ÁG“©¹×ô©W¶/ Øè l­ì¶ )>(’ƒÂ«›.×*„†÷%íˆ IDAT›3)˜f1wVÔpVà hÃz)®p!6%Օ嬊/ÌÄò«¦>‰Ôæ@~JAk|q ŠxÖÒˆ ÊD«sye©wã`¬ÚP ÜΔóˆz§qd#úÌ{åK8fNC › ‰f²ýŽÙ‰<þÇÙ_êJb ’ø'\ÅﯲÁhœN3®)|ƒÀI±äž®2ˆWªbHj9½ ¯^žäÁË2sœIüòêúOö××ö×/O._¼~õR²ß"V•®±¸R04¶ §]:ÔI1xR9K¾.ÝyLëŠÿ•]Aä ýB¦=ÀÁ\…)¢è­U˜òM´^—WˆºÅnýD´ ìÈð:H.À Aˆ#«èQas^ë=Xcæ‹ÞŠõs2Ã%# Í&SÓ©êÅHÞÈd|ü4{6ØlRo ÁŒ]ɾƒÔúql J$0(`Ÿk¸3åmÞƒF_’—¶àÙÂûëëÅ©àÙå믯¯¿þòâò÷þÝHþ»ßÿ£Ÿ]ý‡ë¯^¼:UÙŒK¼é"p=àÿÏÖ›>·uŸk‚üÌX"ÅMÜ7œšê¾Iœh±lËÎéºsû&í¾K’›Ø±-‰$€³Q\€³/NO»Þ`)øºº¦j¦&¶6îKbI”HI~êîXÒß1ïò©;°,R\@ðœ÷÷®Ïû<Ló 9ÅåsæŸä•õŸ à ´vØâxU‰˜­ÉoòR“ϲ'¼€ˆB$êà±£X$ D"º9Ø‹!6'S/.|lp{†}BE»~•lIô`°µ\«ž„^ÂÃCŒziÉ÷¹{L=hj¢‚y•æ襂„ßAýJšS}îïþÀY¹±¼t= ×Å¥iõ5IúúÞÆînybêÀ;2Ä\pd’èÇd䙡—6ãá·†×ÃØ|ÚÔä­Í$í°èS'L° ö°ì’î!GðºwáøÍd*x+L5-6ͯHÎÌÌŒ¢ª+‘˜A±òƒ¾×õ«`SW¿³_ä ÅÖho aèš¹¢a]5?XÐÕÁ qºPæTP×óƒ1<¦j h,ÃD<"M'ƒí æõcE¯N S\°Ýl.—Ýãg`‚è÷ÛǻۻûhgÿøàáÁ“öÑ·Ç‹7Ê%¿yb}ÈáXÅͤà,Ë{È DL k 1[ÃM¹o×Ñp`ÑŒ:o_DZA€’ÒND(N¨}ƒ\^ mˆwd¥ ”d¦V¼I`§ÆfrNÄ4W’‹Ÿ£×«²UÓ*#øANòº¨r"€¹W@.ÌK=Ö’KSª‚ ý—`ôE*§òÒß|懸.íoi…€§Òðð̘~,<¢r@u"×°HÁ•º×ðëSfÀÔ0ôÊhM“vF˜1ÒÐF.¾Sv‘O÷þŸzç/¾óføæÔ›a§9ù‰¸–¹ `Vìú1˜ ]áº]†nA h¿,š/ì ¬/C0Œ‰}A PQÔ«š¯**V:~‰d‰©øvŠŠp¤Þ÷.• í8¦­^!2=„Í¢%xòw®.¹ØqnßXÝk··6·v!ÿñO´Zà+hy¥²+ö À%ìÒRo¼Ìø–} Äy'Ó´@Pæv脜÷ÖÁ:MÈ ª]CÎ&»!öY¢öGM—ÁýûF¡íˆ6fÛš>ŸWlã•]PM†îÂ7CbfH¼_ŸR«&µ½3t-©DÌY¡ÑC5NäMÅ ‹©'-äȵ…üÇÿ§†ò­ú6WãEœjXæH4<˜©ˆ ×$ÀSQØà p?üIÕ9LJ‡{­æÎÖÚööú&áßüéÑQ»}„ã`4¾%¸ÈñVµŽw|ÁY)VžbÊ]b;÷Är<#„££µufØEpyŽêãjiD®”Ž"®‰Y¸„—:\ŽÔ¨cAìÕ<¨hh×>Ì+¨Šqõ£¢‘eÜ%µGÜ]¢$D·R©Òà{‚Œ äÖ4IV<ÜÔMV':i9SýÁ”NgšÀ~À.0ý»ýäÀ…äïÑââ–k‹˜ÞX\„»È|–VrÃö]d—Æ#EeyÅ…ú$‘äCâ> \<îy"“j7ÿÐSV¿5¬hnø¼óìà­7ýóৃ SxÉô¼Fu.ˆHˆÙ5mÕwÞôEfÃ,@Éai¶¥ºXP÷ZªmÌ릻úKõJÑÌtÓÅ„<±”%ÌÃiCÏ]&f”’Ç\E”#]yÛ©ÐÂŽUUD;_H*ìûì¸]¯?>Ükï¬­í®¯oîƒ üæá“VëÙÁc—P‰6 ;{Hm$zÆÄ’ÏhDB&Ltî±IG”‡Wó‘%šx˜›2çá_ˆå™ÉÒC1ç DMì ]v|S&Vž7SR@ <ß\Òuþãk³Sccð%oð$iµÆÜ8Þ­VÙ誂Šk¤‚Á!(7!²ròË·ÅB®!¥u]4ˆha «ßÛ‡ÎêÒJùуìÀÀY½Ž"È%—›V¤Þ‘™éd€I‚ã·œÈqêb¸1‹Æç­Fi~Mñʱ‡ÖHi`\áÖ$Ekt͸¢‚=$ù~çÒS¾Éù×/ž §&°˜¨9Ì;FËkH§ælhÌÂG|#oÎCÙ[ÄTÐ.İlÏ[`wzAÑUÕS(pÍ0\±»b 'r´çCOC5ˆ ŠáE}÷I¡XíÅp•ô…£™­ AÝû¬^oמìo®­¯­mî·w |Ðl=xøä°} — ä½Z„d0V‡_ˆÌHU‰lEäâžbxö"ÁSÀ…Q(¨8a4?6nbn4Í9‘YÔX„kZÀ/WË|¥ÎÍ£æßÿÐ$m´ ²ÌWs‰­«j-­U*bËB´WЉ¨©¡ã87〒2ªª¢L›D03 fÊ;‡åGKå•ÕòÁêÒâÒâ%¨ƒÁ.>BÄi1˜üfðó¤É!¨C*¡Gã?Nå eCdüI”ç!ÓýHÚ‹K+QÌu7bSÁäŵPÊ=¤ið±Þ„|+zóé»IrŸK§ÎUb7M‹%D ø–óM ¡–\¯¬±†:—i]vÑ´t»0?I4[›W RnÈcYw5_ÌkЧ©Ú’oê`½ îzbÖI<.¨]nla޶/Yñü§¾˜ÛBX!³ A ®{íúÑãÇ»ÇíÍûë;›ëPˆ4›þøàÉ“vûùsxeeeµT*¯”J¥¯)­eFÜÛ!´ÿ t¹ÕÁ¸#Dtä$î ¶'ɬ8<ãæax Ÿ÷™…åyø÷üê‰Ô&#Œ2ÊËÐÕ>V骩WÞ×Èš³ ð»÷ Îé:N'ZbØÊEf…³}«ŽK­^°E„@žˆ‚$&Cµ .¶¯JÛ÷ï”]ï‰ûÈ-“ \‚Ö"‹¤U!ºã„%I¥žay¹æÈU”2ˆ¼5Õ¡.’¹ñ—ÖÅŒ2™LÃ<…\c»†Æ‚¼=3/2Â2ïøéÅ$ºxîâÏ&§ÎOÇSA¬jQìzÌ3f“ò®Y„‚tÕ,€§WÐ/¹˜Zf¡·òùb±(›š­Fºm@ 2§ØúÕ•+iFÙÖ¡ÔÓ]¤é½|9¦2šŠ )a9y.=àý *{BÆïó^dB³|ˆåaá ´mïÕën»…\RGûíÖú½{›÷6vÚà>xÒl>Å$PAì>Šv/wPÀý­¸+`›-HÛ»­æúú½ÍÍíÝÍ­ãæ£Gž`ºwãFieµyR‚P ¸Ïh˜ë•`ñÙò®rGo3ä™ çX¯„Œ¦: èñ"Ƽº)H¶õ"úF5p‰ç+´u{!oø~fëJQ/À9um[Éê5Ýrf ÊEG0׺dq•(3ašŽÐƒèé™@~DM@„«$Ï )Aa{,Ïõ¯´³yÿèÐ=¼{çþýÇeçÁ£GK=€ÇÒ7Kdð|N*{àß"Œ±3}R&Ÿ’¤;[»I&GÒØ¤äÉU9¬Õd=T ¬ÜWæþ7žðz©Á4²ÈE¸øéáíwËØ‹ ¼wÀ/\8?ynúÜ´(gð{®I+B6ÑHê:š…V4=|:ƒ]Šš/^Í_ù¸øÒšƒªÃ4Ñ$W¶¢Ì›®‘E*ÔÆ–)elj«"’þæRBŒ™¬ÇæyÃ?ú‰GøtÚ:Æ­`jc õ5†‰@# yF@+õ•¥òš`ˇÜz…ðÆúÆÆúÚÆV{{1­#ð€ÇX€Ðž´wŠ`Þ-XÀˆ;oØ—Ií1ŠÊÈ;|Ñ^È€=Ið)Lªî»Ü 9O`¦„à{4Mx‚æ7Ë \˜s:+[[PíbÁB<‚¦XJÁ Ü…•/˜ :ÁT©îE£Íï*n'áz#`ªÌI_QE‚ˆ…Ô9 £Œ!`$XœÑj8þÝÞ8 î>½swíîcgùჇårùà€üßAÙ»+­ÊU7€ý%UY†ðÌ ÎG Ik²42<&I!„ã X# Tª‘VX¸†Šv.~Òt"£µi$$A‡_‚ÉgÞyóõsçí‹çÞ¹xqb .?r܇¾¹ê±"8â³L›7«‰g(¥.KS¡™¿ú¡6§š^X˜ÏmÍ,^ËÛV¾h¬êºçDZä-kùàòåÃÃÇ¿ù\ äàÒ¹÷¿Mã‰ð™CÄ›úáS€1×·/¨ñcâ¤\¨ƒÎxÂïÒ* È¶ÁŸïc% -¨ƒ›ü㟞<=^Z†Ìº´ºtcµ„M*¬€;bq ¾÷˜ )²˜HNYlޤ4]#uk&êè„î€M3$qFüñ°¸ƒÒ¢r‰2‘¿boÙ»Ë#·l`[¦fúwf‚³ûꕆÀÈÌÿÅTt\dð€@éý]›I†›ðûô¨zˆd¥hd*ÇÖ¯TbaI‡»jUÚݸxoíþÆÆýÇ¥7€'òAA¼ô¨\~|Ü[•‘…œ`EÁÛ%™4<=-eUiR^áùˆ,@%C™àª¼ñ‘VÐ 'ÇúÞ”5ø0Ô»q¤ëd®6Ï{ë-ó'—Þ:nzúÜTÄëÙ.R¬y'(ÜV"¿ øe\¯Ô¥þE›»vuanaA3Lµ¸ðѼfʺ¡\{e›3ÀÐtÃ̃ýÁãÉ“Gß^†|NzûÂO/N õv÷"VgòÇ=ÿ~Œ‡ø¤‹Á¬"­`¦†d¿Bõ@ÔWîܽçîHñÐ>i>kï·Ûë›[Û;{íýæƒOŽŽ¯/./Q+zñúbÙK²,#rT±‘ÄxæÛ Ä(iä Œx$À1¬¢E—%¤& Fc‚²„ÑöHÆšåô|F± #dÞ_d‰´n‰çËÖ h0@Ò–y,Ó4Ã2”W*=ö‚ƒò\°ƒƒòQ°ÀÐ8i“k¸})õõôK‰Ô70(˹(«f¹é©ñ±œÈI…´"¿qJ¬ù,OôýÆŠE\tUÈ8ù'ä`.”Áî…·Ìsß¾ôú/þí~ù¤ âúb¥’6€1*Cfb2¤ K+äÕB~~¾¨êªiä¯}TÐ4S1Tå YkºêU¼©RµÂåËß~ûíÁ£Ç=¿ ?5–Þ¹4vvpðÔ©áñé™™óƒó£q_ `ž€ðnšáwÀ›¼'8!þþ?ÿWãæÍ$yË¥R»Þ||Ø~º •ðÆýûë`ƒûÍ„^Ã'×J¯J IDAT&I3ªd‚¼MØžPóx9ŽA7­äѹd_(R¸°ãã"fK ÄL8Ú^á ì^ˆÏ²ø"Qxøbµ ×ÖÑÇG€A؆™Ÿ_0ýÑÖ+ÓÔ‘ÏÎR¯©yGµZ­ŠÿU…³Cž• |¬ã ÉqkÓ+CÉÄ!” "m*>÷'6ÕbšÿîmÜ[[ßÚÛZ geõàÉá8uáîÑ4(BÄŠ*<&£±H&é-é¹Á.Dúå JqAÕPU©ð~þjÞzÿ@ ¯À15ŒÔjÅqMå2Zàóg‡‡ß^Æp,½538p¦¯o`xbòÒ[“g~ô£© OS¡ûåÙƒ€5‰¹—Å\1×W¾þ¿%ÉÝl~Ýl]G”_ýñQë"kk÷îoìîc+ð!vaN€0ˆäÌÚmœ‚p€AxÏ2räcJ ;Ëñ^(è«<†8Ÿt£Ù> “åq×:¢´­,+b³àQE‡–6È";o+„Ú(¯šYdÁ¿®wMÏÂÌ0ÍXP3b†©VjÈŠP¡†4ü_cpj„Ìi1Y[—]äêàK6HoÒ„™RžÄÆÂ· ønmoÝ;ò–VÀüž>½<~üØyÉÈÑ&ø?9ܼ³¹Yja39ˤ‰é\n¬wljb" _Ìr½CÓ’Ÿ† QNÅ€&@azÚIO‘¦F0U&„©ÔÄ—É$é_±Aõ¾óo€NÍLOÑ96À¸¼"’”D»Î1¤…>*⺠ÝеâTº ®þnájþÅ‚‘‡RØ6³²(W,Ýs=ÿWˆˆÌÉщþÁ‰Þ×NŽþøõ‘©C|±„ýZr…a‡œï¬K¡—›%h‚õ?ÜiHÒò×kÍ{[mˆ­uÄ¡µö›Ç[pi÷÷!ÿéOЗVWËŽC`|ðˆ{ûb°Šñš˜RD^G˜†¡Ž%)àä û‘ž°:^agü3uo<Ñ(§‡B6„þŒÂ'v3ß6M„ÝÓ÷Û¶^àªó&b‹úµ"Dø™ˆ±Tt0aN…Í®VKÁ  ÿ"†4ÆÎÌ<á°:‚­¼öFH©ˆÅ; ëÒ˜ßîÞöZ¸Ðþî^ù‰÷äÎáÑÝÍi5ج1 Q"l€F(958*õœ „´Ï¯$ruvbªø¢ZU&ZrÌà ŠtÒÑêâí¼„Î\)RP"+“‰¨³„Sï”Ló¢¦ÿéìôù)aAH¶áD£Í:ƒAŒÊ Åyøù.MÑ-CËk†¢*Åù+¼u¾h«syÓ^ÒñGº«êú y>¼ü lðùÑÑåËN49>|vèã¹ÑÞ3£c£?~}x<"5>UCáSZp«À¼3Îðe‡†‚¡^nlß½óÕõÖífk©YßÚÙm=m=ƒ:dkksggw¯ùð1¢Ÿ`tc‚@vÁ~Ä1±Ã¿€=´ë8"ŒÆU7`âçŽV' jJZ+Ñ÷mËq‘vÖ £N¿HÌGàûlS5¬r@Ïì2Õ0 µ`BD6ŠP¶ézõâÕ<ª=’.š\ ã0úÁj™Î1)$¢,$‹áñ0DeÏwRFÓNd%ažè˜Xu`›€iC‚±½~ÿöSïðéÚöÖú³£§‡GîѳµµM\ÊDF„S’N2­É5ibJʤ±IéÌ™3g'$(8äJE™†²X†¼,©ªÕ¹ ©>ï{P%LE¯X‘q5!^k¢Œ…p\“K(óÃÿlê'SÑ*›™×*'ë¦ãØØzF©8ï-ñµ¢¯k×í.L¥™ÉÙÙœ©\ÍÏxåꇅ}÷ rý‚DÖázjp)MÃU\tàK cxÂå[ѧÅÒX?øïîÞ‰©©ÉY8a³~**¥m"»$µ_‘M'Ó¿ƒ…n,‚6z³õuêàÖ T![„[Û¸ö¶…€+…´Ú‹\Ʊ(ѰÏ*Ü(v™\Hä\yRFÆH  âF3·"yÜaÆH3=²9Þä<H>›ŠöKÝ6æ 7ãφˆ?(ÌAh lÝ0ͦif¡¯és ¨öÈê܈¦BÌ µF°ýR¥˜Œ6X©ñÒR%õ¼€o2ÖŸ)«#”™0HÖ-Fþ*TÁ[÷owî­omo£ý=»("t˜/¢ú!£¡ÑNrc3“ÒÀ™\Ï©ž¡ÉI©X›ž™•¼Z&Wˆ.烕 ΂ ûÏõ†(:DúÉiÚVÂW$Èú=¦¤‚+®åõs?œ<n¦›ºV !ÏS‹(‡]a0p"óùØ#¡8ðŠf—ˆÔÜì ÔCf±¨À‡,M)¯l°58¾ŽbK )Nˆ¢rož˜z­»{x\àÙñ©ñññééÂG±DJ¨(ÃD‹ÞÍa;y¢¢XBïgc¬ß…R¤y·ùõý½•¥ãúÖú~ÂðæÚýµíý6º>(‹¯/"Ì÷ÆrÉ…“É 6h;&˜Ì!+J¾™á‹¶éuè@hwøDÌP(ó0™FtB™]ÐnptfWKôpÍü•,#q9ßC35E³|(HL’þ6#ÏX¸VÔ pÌÑ–á `…KÛ“`iÕ¬óÀÎ ”ǵ,©ÕÈ«HŠ{¬RÇ…–/0ð…q¥J3 ¶¿Ý­õ{wî¯oïnoßž=]__ßÞÞÂÏ ¢€tGRÄXÑÊ\*Icm!j ŽMn˜¾Ñœ$ã¾.)ÑP†¶ES8f¼èÞáˆã½©Î †§1x@„¢ :abWÌ¢|þÇo¼1•ø%ˆ^€T¦à°< 0Ÿ(¦*^'ÂF— ùŸmÎN¿-a–,[Ö‹êeÕŒdÇQÓÔtz:aPHeº¿{`xtxdrlxb|rbìÌÌx.kE¶#ìALJ¦Ñ[G«K¸ ±‚Ÿh4Öo·Z_·îo´·›­úúnsÿ¸¹µ½¶µ¹µEð£Ã§ÇàQ‹ TRÚ¡ê†YBÏiÁC°`bl§ r£ú‚ÇOE´GTÙˆ"^B~—¦¸À‰Ø†C¦¸â-bø DY[™#>wÄ¢ÚÖœmæ?6LœÈY`€† &m\›ÓòïSF}P7!Yå‚ÕÞTÉ!ŠŠ¤LpBbÑ‘_KHÐÏ[Ò¸€Îö·¹vgmgko{ûé³µ­ÁÈØæ Sж!°9i°¯/×7˜›œ‘ËN,§`ãÓ3±—ÕhI MjE"*ÛwSÞ½LD÷ "F‡EbSžbŸ$D\Þ r £*ß~ó'ÓÓ3I¨—´Õâ "ú×5#F]S$¹Gœ¥¥ª¢ÆË抉ˆh¨gÞ—s‡Qä»BáÚµ…b±7ÎÐQPÑò\£©h~ñôØÀÙÑÙÙ©±‘Ñ‘þ³ãC}ã9¢Ö åu˜|;¯èkxoóL$lн3êh7ÿÐ>¾³¹³Ò>®ïî‚绿µ·³½·×<88zÖþ«°-@Êx™© dHÕ½¾  r#1ˆ—äÈò: “¿ðø˜§½³†B \0dÉ/‹|p°¦$ÛZ0uṎŒeÍ E»8ocFÅzÄôÂ,Ó *ª=ÒØ‡¥(­'*T¢Á‘)f´«”‘eVS–’q™”# £Hpc8QôB©§d÷înnïînon¬­oîlo£õ­²™vÀ{‚F ®ñìp_ÏäÈéžÑIY./ûrzUÈ +¸%g²`A¥E ÏK¾ç¦‰÷¹ø*Ž½Ô™DF"‡‹`VQ“­†33ïü gä42f¥.cÕˆ#Æ+ãåÕ!ñ‚qQñ­‚ߥØv!_Ƚ{q\Æ8lZHT4ÿÑ•ßÍtµ®S’uÂ@…§±¬xf´oh2—“&†Ïôœ>3vv°w&Ón¬É·Ù ÄØÊ£ÞÑ=fa‰|ÞË }Átá׿j·nßi¶6ÚKÍúÎ^«½~ Û€Çhv8‡[.Q0­´Ú{¾É"33mãS@ Åu$TŒ¸Â•xbê¢M)Ñ„Z£ÜÑ¡Ïü€ÄúBëìøQÖáŠ%¤çù%BÐì–bX”º‚$UK×,L5Â&²¡ Õ½ŒøÃCV2ÂÆ½Ô ¬‘Í¡;+¬a$&ÒÖ ^"Œi뉈‘©H~…AìoÍRÀ]r~¢©ý9Á¸@ü ±S.KãcÒø DßUùº'{~ A¨¸„ÔD¼+@ˆÜaDê/(W¢˜×1©ôa ,«ãÈ$!Å#ȸÊÏ6qå:e\Âé4ö’ Ÿ·4]gļ¢Ny3öcÍÏü8CvHëº2ˆ*37=~AÂ8lY×òE%_˜»zíjÑ(Ì•m?ÂrÛ‹Q¾Õw—øؙ¿}g»Çdž$)ÇXˆ—PÆÆaì¦ËÔho‘ù¢ú$"ªŽ¾e ¤]|·~ü‡æñW÷Û÷¶–J­ú~³µµµ±¾½C«™‡èÿ–WVWJ+^ˆS}H7‚2©bÈÆ<IP¢Æ$º„‡íˆsñ~¼ tfšÝ_¾Ú²P¦š¥3ÜãÈ5wÔ}û•¦.¨Ôÿq°ˆö;3 lE·_(†™y%£h¢Ú£XÕ"¸5¶G©CÕ:@&¥D7HÖHwÜ—£ÅÓ„‘¨Dvñø ¢†µK—¢0ä}»L†µJÈiÚk#Ì^Ä€ú¤†³Zi°çôô™QY.:ö4è–ÊYÊÄ1•½UÊT‹C …¼ÂûšxD&­ƒ/¨Ðhû‘„{ü²_&Õ8ⓞ×uUGÒd¯¬ç?06˜‡ìeI±\Íô=S75ËíZˆÀÍ契ñã0¤ÕêUuA™›»ò;#ïš(ÒâI.;$ßê…9xÈÒèèÈ©¡é!éo¦)¯ÄÓ·iâ숽FT éLù‚’>mc£‰%¾·þÕíVû«»Çw¶ZNi︹vogok"ðŸ¾98:þã"­‡ˆ©!/4c+ˆÞ Czä3 !CcЖPÍ«VóI°#üû²W,I>ê$±^,zÚ.5‰,]·ÍÜdˆaØÖ Š:_(.Á 4¾Ð aïª*4$öì“FF{¦g%„ýS=é2-‚÷WøB깈¦ƒø‚+ÂLÍÁ˼!éÞ$vSo¶o·ï¬µÚ÷×7Úííõ À¯6›K‹K«+`b¨ž´/tùD|sÆx¡é0ƒ<…Q/äYICtiBVŠ7Ó;+ê>ÓŒó¢Ú,÷JëTi 6²G¼dª)ÛVÈäÄ>Cþò‘ _ˆ¥Z/LßP´¹WØhÅÁSFJ¢¼õY.—‰/:é ßžU#ßZËjiƸhø+«¢&’{pÙ¢8ˆšš8àÿ JÛ,”`~iEÁ¿3JF,SbÎècv†v.õ÷÷LÉËYB脸"»H‰†òõ"1`Œ, |«Üñ«RA’àÖmàQ šÿk›•”4g)%fš0— [ÞFçøB1V5”y‚+‹jQóuÔOGþqðiouÑ:e—–ÄHoç¦b ö Õ¯¢ sʼ’À ÍU‡s #)ǹéÉÉ1(á%éßK±¦pŠ%†A‡­…Ù_pŸ,`2íér‹ J¯Lµ2!·×!ò¯·×Ö[­{w· °kïƒ<|v¼´ &H_¥µã]¢÷:GâaQ›ÁOpº‡Íg¼¾P¶¥uš¢ |4wY¥'d¼*¯¦0ÕÑó -_4^¸CD:øÚÑüt+̈]ÆFô•2o¢-B2h)yT_AµGÏ7æ¯\)Ò š~q´ö¸tªHÀ|bۯѠ˜D”jIC¨Æo@{)Sùýíìí²í­¦U&ô#X¼"L™e2Fí—¤ÒH_Ït¯mV=9–Èÿ<$©¤T…P‘Øg²¤¸–².q WôÁ|êL“ øÀ-ËsHú6`ýceCßUŠ(ùXÀûàé¦P×}pç ß!ÐðºÔB R!ä& ÇDû7W(ªóÏå‹FƒåRkÂsÅXq½xvvfv6 #7§ Ða‰x’Í,æj섲<ÒmE›syu=`†Œ˜µ]ÚèCñ¯úñWÍãÛ­ûwšÍæöÝû›û»8yøBnsiÉqÁ!=‚»BJꬾ!dQé"x„ñJ9LF\ý CýÇCÀ¸ ËXÛzTº ‚?xO“æo¨R0¡‰éy¹¬UïÓ¥2 X서_°Õ<ä5Ó\Ðôbþ­k×òïT ‚ }S$^ š F[’¬ÁšÇƒÒªÂpÁšèH£ &DÔr14IâÀÅùz?Lú˜+ˆ©È‘Ï/°CäAØ+¤GŸKÁ«LŒM½6t²ŒT•+9ªÊ!„`L©3Ìôk´ƒN-éŠà'Jȱ0@>Ý14êrqÑN’Ãâx‚£Š„\KELÆTdDðMÇšùñ$í)ù2£zX%êʧ åÒ¶4õfnR•tS/óç>†lYF¤á,™>–åÇŽÏš ØvÑ<Ó& >Ÿ¿bR‰pQ?@äˆS2Êç©~Ä×YtªžS¿Ûúú^ûx}½Ùjîo­C ððñãÃöõåæòr ¾Ŭ‰’Rˆ‡SN@jbý.âÒ”°©êð™b=á2 ¥,¸O•Ï:„ï Dß\@ÓAºD^I½[µµkEƒËÀ`!Ù\\ÃP抿+êd‚êÕâû¤öˆ˜V•¼Há’f#´2P¥ ]MÀò«ûc¬*ÑNÑtî*ØŸHûh·=aÊ–„×4(3#ŸÆD†Qáý¥8†<=>1 p}3ENÔ BÁ#lð{¬ó€ ã(õ«0H‘¢ŽV’€gædwlŠÄÇB /¿£×îrväêõÔ§màïL¤6ðÒD‚‚Îâ?ÔèÊÃBû{!MAd]•TM)ççòª!‘ˆWböCËs™ ~ž¥ÙºEÀçõ«P´’s.¦¬Ç $6 äeÕ¹ûðµ;´ÐŒ/Úqê÷Ú·[_ßon´ÖÚÍí­­Ý]äExö¬½tj`2·¸ÕÞÝó¹_ÌJ‘.ËA¸¼… IŸ±|^giŽˆºÂKòxø¢Tâð\™Š#ŸI4CšËgÑ­P,³CÔ͛ꋗ&} ñ„ˆÆÝ8 › Å9ýwWTøÙ®YÌkß½¯²Ž7òèd¢?-ãt„,±B=t‡ 5ªùX ÔXL“+/Æûº*Ò¾Hpò’ì x$¬?G¿i£ š§tºg¦w`|á1·oßƒÒ ÑYÊ3ÈD¥— ¡tÚ„Ãâ»Zv˜0!ÄJ vTi,. ½ÔË%yË[$’pØà4º„y¸Y3tÚSòƒ<‹°2‘q—fVtÛ~eÛŠ4‘›ˆeÃ2%E]Ð óŠ!…‚‰×aN,Õ ’¨Œ%^LÝÖÝUêw`ž2‡ÅˆQx„ebÂAÑ‘abi'oVo}Ý>¾»ÑjßC@Vs§Ýl<|Bxµì4[´Í}¼/v¹Öí<ß§t›áP¿ Y45[F¼Â]çÛñ„mf ]OÚ8çü”šE¨Vø”Ó¯ÁºïAf-H‘q×>гËV`p5”^-|PD>ËÀT͹÷ÿ§A­‚ÍfLÈ…µ\Àœ>ö‘(ÿÃpIƒaÅÐÖ&Ñy€7ô¸¼Œ#ê·;¤øËÜõLãÛáfýB¡É£ ibzº¿÷Tÿø´$}UvCY–†FÁc9ªÉdªvލrKǬ¨“ JÄ1©)S´ÃC‹é&Ï^DÒ,<#[Zþçªb¬ÖóðEª¥ª¨n„W_U1˜ÇpÖe€w¤¬J3…c²ÁÙYœφ‰WF&^̲< Á¶Çªõ¨hê;ºfš•ݺG‚~ëµz¼zæU•0›©`Kb:Ÿ{I&ÍÖÆÆqsc jÝæ.í¤?{z|c¹Y†?lÑÀ "0)¿œøA_@ŸÙ9Áä6#ks‘~mpð à„i }1'"‚43èÀ±¸cãSvMPFjŸ“اeÚ†fb4Â6Œi¾Òþ§EÝó•RP_ÍwÔu½xeN Xé&° –›#À,ÖŠ6^*YÆÁ‰ñ’ía+´¼ª´±T©%Uøâ˜àña…{vbK¿†é‡XÓí…VÛyjKŠ"‰Ôß75ÑúÌÀ¸ì 8kL˜ž–’DNeÙñYâ‹Ö X(‚}©ØHN;¤\q,x§ƒ…‹ÐYqEч7‚ê`Ç/Ùà£lØ{‹ \X]/hª$œÂbÀs)|Æ.„¥ÛKu p pöÝéIif2bÛ|^L4]×FXµ¡¢s-xþª‰ðše>¬jJ8ÎH,”yLFEô¨ltúÑ„ËLgÄÖƒ.ðÞfëv»uç˜ß½íæqóàÑA»ýüøú1ä‡Q«…»˜Î‰Û ˜ž~*!‰•:þûÊËêÈ5Iؽ„ôªÀ™óXšS•ëk0ißâÙaþ)ÒÊ ™âDØžKÃ/ˆEÆ"›n²ªAõf™–ª(Øø… ©Ó>ŽGRƒàm›Zªº:+M¼ý¦œ{{zfV2p¢‚?S×ó¤6ŽÞ×FéG¢Ipò Eö IDAT`æ*ŽF8%.†PÒEMgC?ƒ_ô>f€ õ°Õ€Á½¾Þ¼ûõqûöÆÖññænk—Cžc^¼^šàÿ*íí½}„í=qÀ‘#¬Õ~ûxÔDf‰ÔŽøsñ'Å)6P7ÐÜ”…²]±(0L²^\^i®–Ê!TÀÆ_Ä]ˆŽèú}¿öÛßþÓ?þÃ{¿úMìŸÀ:„&ˆ‰w+b®”£=–öyÈFämèšy•9 w c¢1O !0Õ ‘A™/f™¨‚5]+êVfë$Ϩöȵ¥C!÷ÁÿRªeHíñ{È5AÒ<Äà B±ô_Fù_BtúÕ„—8ÓFã“Ï>û¼þûïÄ‘µ$¬bÃkjB˜ËpÃX:“4l’8wzlxêÌé¡ñÉžþñ³R?®§CX­Ê©ë„™B¥Í˜‡‰ª™•.ø[WßÛ Â_°Ðb‰2ŸUõ¤×%m‚ Ì¾k–]Û„ï5tMױХ`Iãù¡.¸r1u±LMQ§ßyç]y|ü­™˜ÙW1ä¹ð-˜µA.æAå¡|Eaˆ‡ªzäȈ¼2f¡Rã1Š‹€eO,‚0Ô“™ë;›ò¾ËÄnž__¿}¼Öº»y¼v¯µ±†˜ˆ¹‹«NL6!.ü‘"dÖC?¨ýöW¿þÕ¿üú—ÿøÞojµ°“¨Ì~Œ-#¢ƒá1Y‘4ËçÎt™J¯x”XCNDëˆd†EÌ+(ht¯ŒÂK­1 lËБñ8°UH‡‘UÛÅ«ù–®Î+sBцÕYšD ¼°ýÌ‘—± 5¯Á·¼4ÌjˆÁh~Ÿ~qóÖï VñJh-i}‘pK‘* ÄÄèë` BÆHZÃq®gôtÿÄÐø@ßD_ÿ¸4Ù3<<)A)’Ê/: ëv´å˜+Š%)9I ) '‘±Ððê@&7ˆ?®Ïîl‚ĨM1Út]î•mÃUS_b@ŒèÌQ¨ÓëvÊCu]QgåKÒ…ñwsÆJÌd V5÷"vGÉ»4ž·ê*ÙÞÏ„Õ5^§ù1CG(˜Ø"…<¸•î $º³ú=ðàùö÷Ž›[;Íý6d‚ÍÅe¿…SðöÎþîÎþ>Îí*AðïÃï¯ÞûÅÏÿ3uXq¦Ol1Kwáicäž;HAxŽ s¹ÂHøEu #ÁT3†Eg}þ*(}_aÈ«-ˆ†Y¾mâZ—¦ÛYÖgB\T?RÞ/FØ£ÕY˲M4|$mÃô† à Ù r×쑘³ÒƧŸ}ñÙh~`€ óUHÑ#ej5=á°Bè…,¬D”FGÇOœžêy­oüì”ÔÓ3=1*yr¥&Ë8…©ÒxCì3-!Ô#R÷¨$˜Ø «”-9DŠ‹ÊäTî2Àˆˆg‰â3³`lŽ™žnšT9x™b¬Z6†n—܇'ìr ;e T_(/¥·.Cüµ¬ˆÕz‰øÿ:é§)®ãŒXÑ´•€ÜI$h È<u!µq7¢!ñÿˆâ„Háp_›94X$>­úÚVû>Øà6$~P ·¶ö[Çû-”Poa–tŒ“¹Ö~çAáâïoõŸßûÅ?Hg'ú'Á}6ô ­e"!bBô¢«BYoÐùŒPõÂlŒ/XXL‚±65r™ëKz\‹2ª7L‹¦:tpQTŶà jÃÓÚcQͨ¼¯àfºP{Ä‚¤ša±€5nÊó72Fš3ú§ÄUi|öEýóÏ¿¼u«ÑÀµ8ôlHZ*°Q$¡” )/ ¥7a"æ/Hr=#€‡ÆÎô æ&Çû{†pkàÕ~ãŽ|kÊóL`’X:ë•x˜…“˜ôà¨âP„â¬Çtà¾I½´8-FÎ 5B ä/­eÁ²‰&Ë D§]E˔뒦Þ}WN -ñPöÕFgJ:€û`¨¦€)›že†ÒHþx"¦í1Úã˜~þD@ 5^R8!éMÍÁqë›Ç÷¶vŽ×¶öZÛ{Ç[[ûíýv+tcŒ¿.¯$1Pã‹ø/ÿðóŸ¿7=:490Õ76HÁ¼V<ìºJàp;’j`Ì™.•Ã%8ÜÁÍv?‰y2Gž‹i—ŃÎ&¶…²fx¢0u±4Eà ÄB^EDõGãÚ;Hàz¤ö%äg#–]e@4f~è1¤\a kœÊVŸÕoÞ¼ùùÍú—7ßÀ=%Š ED¬ntvÖ0XÂfkdÿlÏ™3“ƒS=?8390söÔp²O0,T%Z8« u6¥åÁá(’ºsA°É }’uÐcPºD+Häè|´p÷WhÕ:¸†S–M úÕÕæ0RâÀ ð…!Ï´…ª’(&Í.O˜ß Sy©so¿}ùÝ·c5 âNa&ˆÔT4˜1䓊g@vWMÇÒÊÈ¿âøü[Б'ÖÖ(èÐ^ ) T2Â/9ø ñ9'IëàÿŽ››Èˆ°ßlíîol¡ p”´wÁâ–o\¿ðÛ‡Ùñ>¼ð€¿þû¿ÿŬ4455>6:}VªÑÌÎBP“²Þ ­Fú….›@$ê[ÞRæY0s = páÅŠ…ÊïS¡Ï(ʰF<à+ÃÄí3Äá»iæ‹æïŠ -Ãõ¹€Õ p+~– ¸“D-?¬DPÌS¿´šÔ’25>©Ÿêî»õ%!·¤§J¨äˆc*5®~©Žeù°ŽÆœÖÍ÷ O}­ûµÉ©Áž¾!É‘“HÎh ,#"5M "ñ$Ñ’¹zA¥á©q‰‚oñÓóôÈõxâÆMXÌM׿â寒w òØJˆµÁ.ò¦C•¦£ªÚ+ÛÓ žð‹¾àzBw׺ʶà,îÕü‚¦ô™ñÚ»—gL; „C… NÙ"Z½c"IsÁŒd;÷Tœ'¹ìïçÒ2®Ç0L¡“ Ÿãûõo~óIrþ̓LÓúöNóvk¿½u|ÜÚ[lïì€%‚½ïì4]·¼|}iÙ‰Ò.ÆAŽøÞßÿÝÏgǦ'†úûÏNRˆÈa)‹^EÈŒ1‚þŽŠŸ„€zãx:Ë ,(F0ëy´\!ïÉ8ÇM‡¢fãfÿ ©9Ðê6Fâïp84LsA1¯¯!¾‰Ôyý“‘á]\š{$Ôx® ´†NO`¥qxœ&œÊ²[?èî«Ø`X¶cÖ2$Å¥˜ä¿y ýå'O÷OLôžêéŸêžc”ä2¹=dé…7•ˆø;|ø#/9è¹,–¥>)êÐ" <6Møh “LÐxhSõæ¼Ô¹b˜E?Ïà¶ ìåI›¢%ÅÏt žPý^‹À-š]pˆܯ~ùaÑÐUMN€#©õʈÙ¥ oÕ„ŸL#`TDв=ijûbÁƒ+ሠKps$Qˆ£éŸÿéW¿~ï¿øOÿ(IŸüxD˜ó3©ï·îmµ×wÀ nÂûÇ»Ô>Þ«¯\_n6‘×%j "¦>ií_þá½ÿøsi|hrh ·§§çì¬Tc,½‡DHi‘(˜3w[áÈŽÝð [9ï(±}R yÈËGaä4jÄn1üï¶>¯Zd”˜¹è†nG|eŒ+¨öFj5ãš27o€gÍ|R{dí:¸<u="m«®" Äà)Ä2˜?ÑèýýŸÿÜhüùÖ­×n‚ý5„f ÁC+Œ%ü`Íùäß<tf3tÇú†&ú'»_{mjPŠÁÊ"Y®aÌ•«‘3ƒL¸Œ@A™ÒiXBsç!Bp†ö\H¨ˆàÄ`E¿8õFq>ïæ‹Ø ñy _Y4ŠÆ+ÓΔØŒî zjzÈ#Ô)à †a}N±4UÍÍŒ¿]dd)fÖïï476ÁÛ{uwyùFIìÆµÛû$º›Ö~ûË_þó?Iã`}p={ÁRˆ÷á\iÄ£€ÊĸCf„äkLØÁÜÌ‚ ¤*ï h!~‰Ã‹T‚­ˆž iˆæ3Cè,[Å¡ì÷æxI’áxÝ:Ÿ¥™»YúÅßýIõèþâ_»ÿN»BIpRÀ]p{`v‘[íÍÝæî~s¿î¬..–Y°kaêRÕ ¦ÿžjx°wL¾tñ¼Ü+Õ¦ $²I²œ ÚOPLè>ò}¢0çÞ3WJhs®Àr;@0~˜Ð62œ¯ˆ–á^-3•}q­ ý}g½¸¢(6%–¾¡ªnŽÕœ†Ú8$áêxÕŠfˆ~–T2’(¯">Ûè¾ÕxöüÏ~^<‹.0¦]%VN'–sÞgƒ®ä¿vwÑýÅü¾ƒ¶š›í=ýZÏàØdïTÏÀU¤„Nd'F}$·CB¬IRÚ ¹ ÆÌOz ]`Ú!DH÷#ÿ‡FÈݘ|NOÉ_½¦î\Ñ›ÿØô ' ?½€½Ð€Ê-ÚšjÃ×b¿ÙDðÊ£ÃÑõ.×¶(,jÈäU†à}0g§* ¤ƒsÕC~|Óô41v½—É ;š“œðÑ®dijj Méÿ`)—Î_¸ôÖ;o½qá’Tÿä“ú÷G•Ûª××·ÁävÀ6·¶š­cÓóV¯¯À >IJc`I:ÛæÝ S=¹×§ÀY3ty¹œàÈè ctˆôLÆH,ªóƨ üåQ0ÿâS(°ßƒR>éÀVÁŒ  y …0‚-»ðá|®š7_^S_ØÜ'jD›ª¦¸YMj¬kC>7 îZ/­Æ8ôàÝÎûª´xE”mðn£»qðäÑ“£o³î[_~ùÅ— .JyHÆ’™ ëx°¬×»ëÝ'³ÅÙþñ±Þž×ú†§ú{§FFr¾\…Ò7”Ë¡¼êcü_˜e¸#BN(»NX‘©‘-I“R…9ªQ“íI`ð¥â¦Lû<äôŠóW}Í3á÷- Ï)-’6•‡P>(|Áa¾„ëc{sy›‰ËTÈúPìÈ4»"×H±PÄÒî¨< ?o®¬\µ¨(ÞBA!zòËZQ½A#`xâ|ž jD`»gÂk’!Ä-Bët2¬A<éâ…ïýß§ŸugŸ~öé§Ÿ~æp?«CH—¦õúþÞ>Äõö½íÖÚvûxkŒr@”ãóQ)©Ý¦u,ü¼št¶wæârþÂë?¤Kh>ÛƒÏ!”,ŠYã#uL©ñ™Ëˆ 8 CsÍ0djClf–,BXŽ€$åp:’%¶õÒÄkéãý£ùù¼e½TŒWªzÕÂ/Œ_šzѸ6_°Ú#:fZ  YÆ Ìõ æÈ°á‡5p%JªLVC\ntKo~sp”ý ÷Ó/¾ø¼Ñˆ"¯ÐîdËÌrøR~ÙýåÍ/?íæËŠ3–™Sƒã“ý}gG¦ûN÷NN¢N°d7–—!Üz1y½L–KÄXI‰!Y 43’“ÄmÁÛ0?a‰|Ðýh®˜ŸÿØ*ä•‚©+EäfË+:U·1·[ÑÜ\¨„3Õ.Bzü±Y@Y)ÍÔ}±¿dÛ]¸‚h•~À©ÍÊ—.˳±aТ¨¦™iìéÉÁÁýá P×iw†VtO(hùWˆ‹Gâ~âg$0¿ØÿóӣǾÕýù­[Y÷çøpH=œEéY­R¯×¡ÞÙolía[z³ ¸‚9 ·¼²¼¸Sk»Ø‹Ü;{é‡?|ýüŒU0íq`̹'ѳ¢÷ HQ˜*\χçP¤%:_jÂt< `u YÝ }ÂNý%3€ De~¶b]YÈÏ›vÁœ7ô…¼I?Ä2t3ÿþ•p|¬ö°üÏ4¬ˆat‰x¼F~Œ˜ap¦Á’êXWài Jo¿}|ô¼v«÷“ÏoÞlÄa ×j’1{š CWòó[Ý¿à²Æ`€Rïhÿàôtnvr°·g¨°\"çõR H½U–ª«²—É8yQ8’º¥!)!C"éÅqƒÛ‘+wy"Gé?:=° Óv‹ª6©˜˜›gó  ñ§H˜»ê¹ša[ZÁš·pbd"A× èùf…©ö«ïìïtý*CtÝt‰sÀG%æ"ó“#;¹š/ªàxR%yGÌÇLlŠ4Á§ÕÙˆQ'!–!±ï¥O¿yùðrbE= Q©î ÛRÚá¾á}ˆÆ|bgo¿qŒCá]ŒÁ¥•Õår³ /zÕõB’êÊ û“FÁÏ¿þú^Ç>`À˜Ø°V#¶J P»롘¼à nƒg½Q(d<ƒoR ÃhÊc#!rH@/´X»h›Ê)‹…«yeÞ°óŠa!êB’hê¼qíý<áPíÑÏÙ¥ÏS˜0ÚŒ0Ë¡ŸÖhHeLîg¤ÆãGGGϳ[½ŸÕ%dsªÐ,˜Xh‡—ºÐ‡®d.+üÅ—>'õŽ÷öôžž“ΜŸER˜þÔäT¡æ­ &&•«’ ¥HEÊȦèÁü¦$!“ˆ…7ÉÿñF"­­’,´ë¡)(ªñWWuKÓ,ø¬Y„4hIVr>$œLËq Ëp³åwE‰™q+ú%³}Ò0$·X'™`¤hšŠä¼ˆ|UÃñJà”ŠAáïÙYòy,`5UžÒÅ4_x7,¥Ù³Ãî~üd7õîHJ¿¬Yw‚Š`]ÂZ„5ëÛÍ6]ÒFk—Kâfy圤f$QÕ¸@È'¥id¤oöÇç.Í "A üôÚ/Y É)z?ÖsE°&uòÄPP†âÃt‚Ä.q<_ÀÊèŠÒwS6gë†áXÛ öµDYÛ´æ¯Ù8é`î†Awტš¬öÈ€.’c¨œÛ* ÔU õžk©ØÔL €MÁÆ`ãOGÏÐÝû¯ŸK_~ Øá«â¶]90/ä—uº¬Ýü>õ¥éñþÓ½gFƇûá ÎHlQDM\ É•€Td)‘KQB\ q*õÍNH„hÈ5v”â&‹²yb9êŠÉIp|¦k*ã3Óhlžá;&éà—’”R6g5Ó°SK#³¢WÁ@Þ…†‚îÏ~ññ¸G†!Ä37ý%M3òÄOþü²“û4ß§©eÏs6xŠz^Ä‘Ï§Š“àŠQ¸½ì_ŽÊµGµ£'Ÿ<ú„“†äd7ááøAÂK T ©Ic«½¿s Vç†íæÎ~k¿¾Ül:‹‹¥€{1”ƒû›•FáÒöõÎüô‡?:G˜Hj¿ìþeñx…%‚/Øå—FÝ=¿#a‡Ý·ÏRž4,ƂڄÑÜ]Ào %%-†nÏ|¡œÈ.X–Á†©CRôá¼ê±ÚcçEà4’­òApµJ"–0IBC/OâR¢PˆRã‹là_?¯ß¬ßºu“*a®@ª c÷ÐBr“ ¼¬üîM¡6ÒðÄtïÀÈøÔpoOßÈ´ä—©íÉI‚ã·j a©`•xBð)˜d w•¶$±J;õ=áûØj\žá;¾‡5ˆfšh¹qiÆUá7GÖÀ‚|LKi#ßò•¨``¦aaž¥äønöómḎ2¶^è& C´­@n˜âwÚHýüè2ŠÃé–…dD®Ž³\ì yv&‘¥ˆÄM$ZæðÞWûûQ9Èž<{zxðP¦V½:5|ËÔK,—AVp#ÏÚ[ÍF³ìgí7VK«X…,^‡çDYXœ–FsoIÒ»/œ»€9 ìÉþüˆ!ã¡ 9aé‡_+NFj 뚂𜈷€C!è D!ñÚ&3D¿°LC)¾ 8‘5×׆¬Å(ªú•÷¯êBí1` ¯¥´Õ~«U^¿EÚÞ*¯Â¥´œŠ•¤´Ñ'eRß` ¿¿õû[7ÿ{2@B2èŠe<¢ÿ¿Ia\¥qäÁ—¦ûû!”±I"' P€8P†@–S©šfR þ&h‚àSgå`6—¦¬a ? /èmÖãE (|¨˜ ®˜Ÿšš™ÊOKÓnÙ7J&¸=º\‘I.° !XÆÄ¤¢BÈË@ˆ?òÜ.7Ž ÁßÙ¸éo˜Ô‰YÈà r‘ 7¶tȹM&‡Æ±¯gi ª®Ã@E{Ìy;(…+50¸Í‹ay}çöÚ;ð’*ÕçGOn¯ïЧ¤ÿê’j.‘#2ªƒŽ?üÕ«{¸µÝ¯WnxyMgµGr?¬/X…Á‡T/3G[q Þ¬Š‘;!©6n ŽJ½ŸßºyëÖï7?ÿü‹›ã ÊR°9ÇËØDL}…ðÇr0†Tpù pb §·oh*7Ð?4˜“|¹ zQ…L3Bæs‘ùQHKJÌØMAhxµõÕþæ9…ò³öôÛ£ÇvpÄ"ý•’[»×Ð'vâ*ŒÀ' ù={Ühì·Z>:Àǰx+؈ƶ2¤sà4¤™Ñi¼¿·wPê—ÞxãwÞ…ƒ3MÀ@²??f®TrƒÝzꀛ.?½?A6HvòÄ‚Œ8ýCˆ'Ö!³‡Å&8÷1†¸HózÑÔæt]/èÊ•ç5V{ŒBJäkƒïÑ6Aƺ d~¬(ŒI`ÂÅDçFýËÑîOnÞ¬×o6nÕ?ýì‹ÏG&ÌXA(BL‚ )îi# / â屩Àè>izx¨¯o` 9¼!KÈŒàË«åçq)Ù’¤B(‘¤Š/Ç¥•¨Ђž°0"Bw•öØx¡ŒÒØð½hü‹ÓSÒ d†Z6CÃïiöwW Ë–|I²Ž—vç˜ZÜh—k7U‰Žÿ…Š fŽ;1Jìï>æ…:õ\ÓØ®ã‚9ûvņ8¯û<„åÕ/‚A ¾Û0ܺ½ŸePŠÜHß|ûmÅ{Òñ¼RFˆ€Šï'Uf &ú³Gh~i²¡¶ñàêJÐ ½åë%0ÁˆöÇ ¸ŒJ}g¤Ñ¾Sg†¤^éÍ·ß•FgG'Õ‡ Ù¤û±Ç+Ê\Q‘áaF6G¹Xž´^˜™rúÖÍ:¤€·êŸ|òÙg½Ýc„‰HÚ æí]²ŽÐPŸÉŸ‡Ó#Ãýý£ÓSgF$I—0C{Ñ¥”= $C·âÁ`c’ <`%&‰cL‘Àë`·`xÁc©LMŒ¾1:;;;íyª®éˆµÀ¬#W$Â׿íÿŽ<7;;9#!óR™IdÀqu¡²ú>[7^Í+74H*s܉Á™6·¬%šf®–œÃ¶‰ݶ=¤òL—’IH†ð W3 †oGÙÏœ «9«ËÏÞ‰$òåÒ ürÅ $Ö|üxöðñ³Fƒ¤÷Ú葳ä!CÛÊõë^@Eø?i¸Oè•&Gúz΂JRÿìèðLãaÈñ—7ÒÙ²¢Î0½œ˜çt\Ã[㉠IDATSc˜ (Îýð¤4Æ_ 0ìì—R–K$\[Èc›ýê¦bE¨wu8¯†®hº~íý+ªš/„ ¡Œ(²™·É¥F4ŽBÈvÈŠ*UNq?¸Qÿì‹î¾zýóÏë_ÞºùÙç½§ºG  !ÎÈXlðÆÌaJ™!r‡Ãm¥*9H™Q k_°¯é‘¾³P²a'zÕ‡B[e°Ç*Ú,d1ãR5‘ÃUTP÷1 c†Ý!ˆƒ·,Èë YäVÁä¿\&r,Ï‹’ÉÉ‘ñÜÌÓSP•馃=°Rd†šŽU+ŽààÛʳïNÏHÄyT&BvÍiVÁ†­Íït×д9:1Zì£ÒC€,[¸í…HJA9pÕ]ˆÍ.Näh±Í£Ì(ä8L¥zØî"Œ²•pÉ•«a){|Ùý/Oú¯`ÉK)3¦@¥ñÍã§Ÿ>zJ懧»RÙm7>k´Z·Šåo®.®R R“ÆF¤¾Ó³½ãÓ33³Rÿèif|¢á»hZÈùvEsO€žÁì\Ž®’™–ƒ|cØYá :2ÖÌ߯ŕÀ¤RuÂÃN^‹3ló/Ú_>Ôç S…[ÇyT{E#ÿá+MEµÇH´fS Y%î*Wˆ¦Vekˆ©¦qH¥ ðgŸœþäË/ÀoÝéëí¹ybp× M!#bUKŠ1’y84õ!Qx“åÆFGÎŽNMBÄÉå2t‚q$û5š‰È9l%`3KªeQ†ÒJr&×20[R&~4l>ëP‡]b±Y¾)Ìå†Ç&.MÍäfJšÆë˜XÿÞð«øR·ƒ ¬Dšž™Í¹ qÖ™ëtåá6 ZÖ l˜f˜7©“Ú\^7”Õ'V#¦B†Ž&ï÷ø‚>”;j‚„Ñ0œoÁ­KËY–\Ë™ÿô0 -ù`HsLùS°¿Æãçž?FóÃ:„Ý!8@ÔˆóVø¾Ð¹¾˜°öõKgzFÆáÂMMNN`î7;Û¸ì€ý ý¶Q‹¾€Y±ÿéì,ãxŽ£©(QxÛƒP08dÔëz‚ÙRØ%¨âÛ~a›WLõªrÍ´4œêR,Å90@i‰Á3q LňW©1ò§žcLñ”æ•`€Ýýàë_|Ñ&&G¾ø´þåàX#í̃+„àH‘¿QäÑ5¦‰¶~I0¤éÑ¡áÑØEn`pð,ÖÙZ&Çú¾¤*ÏÌNÏJ9úqS– #Ä™œ4>+±40Ë4Ðþ,íP0ä0 × ttNÍH³ïŒ¿ OgL!xÚ hŠp"_Zª›ùoOÏBüõÅ~üqJ]y=B4–e+àü ß+éy-¤N Ä Å,–£DU<ãòB2¼,?P‡2Q¢Ž`h‚7…ŠtĹv($®é7¿úU–,–ß[yúŸÜ’|¨AÂÿ—­7mŽã:ÓDñ™²%.â¨1wl‘ÄB$Hˆ 6¤¸€Ý·m‹PKV•-.¨ÌsNž¬*€Ñ1ã17Tý Ûr»Ým[ôuÇØŸnLH­_0?àž÷yÞ„<†MŠ$€BUå›ïy—g±f»g E½2‰¶‚ò{_ß§Ã?ÿÏP v½Áô%’&4Áá|>|ptèСQùÔÑ#ÇNŽ”GNös‰¿{çGw¹õBx{5 V6¯Ãe`Bk^ @ò ]NuðÐt ´Ð$å«FH“^iVÄÀ+]^,'ðƒŸ|û]ÂpÉO1K-°Ï²‚ë’ 6æ®°1Cüýú×ÿú2ê¿zõá±~è?^¿‰Âo¯^¾Ü;r`¨ULã îZ~ìÒ‰„ƒ–Â^X–Ò G¥ry¬42 ‰IÍácƒ¥1)Ö ³<:Z Ý+Èy2Qxt8~£(|ý3è½I»ÃÐ*Àp#Œ®8AtêõÿææKQIzo7 #æµ-¬R†æFE8A$D¤]é­ðÍRJo×¾%‹°HBÃXª@R··ÂÝUó§²U›#á(¨±!÷ƒ 4ZŸØÜê. HÀõÌ1mtçæµ;=×¾ýèzûfdÓè ›Ì4­ë÷BäõåBý¦ÿ›ß3þ±ÿ—¿|Õýç?ÿÏðfüÕN'ÈH¸ÏF 5Né衃‡>>128Ô¿(ñwûþÄ|H™ZqqóE°½ð£þîwý¯Â¿ýóŸÿ,Fé_}õÿü9ü©³#„‘£Ñè±Ñè©£²ï<>4Xêß>p"Äß¿=ùS·HiDFú‚ŒëI#…œ<ÛŽR =–ˆˆ …s;Neu›eu³#f7,X ±WÛ ¿ªºàæfmó'?ݬJí-*ä®°øL]Úí)ÖÕ*Æ!ºD;è×ÿòâ™à^ôõï¿þ×åÈ©ãG~÷®¯¸h¦öz"ö¾#GbºˆûåÔûcd—†GdLWŽ†ŽŸv|pìäÉÁñSÃ¥ñá~Q¡ …øñØÇ£ƒRÊ?tø@r ËµN„Žé±Ã <5&S,ÝC¯‘‹V\E9”™mQèCëY«òS45T·<}±NIâ«`…kØlå /2ª€¥Êà+Ô~YÞlÊ›(?8±„"Š´¤‚ú‰½!hè8ÁrªSÉÜN€+KW×ÖC¼ñxu-òÿ(éµçÐ ArÜôq ,¸Ûß‘t(o¡œÈ2°ýç¿üewç/_uz_ý³ì@vCcwdäØH8+ʃG>úð£C'FJ㌿ӿy2Ñß•93Ú˜{kÜ÷Êä$+ÈYtŽñ%io M:IØ”‘ª2'›^'‡N´¡ëÍïêq­ºYm†ß…›¾õí?Ä[¶Ötâ¨~µx”\ýÂã ÿ¼«Jl„ t!RÈèÝWý×€¿þÍoC\~;ôjooïíëw{¯ú…ŒÊgt‰#ø¼Ó!f“»´ÃÈÊÃåñryp°¯<>tR Ö$úöÃ_¨ükW bhƒ;.m ø‚¨Fõ) hJ4Å’´Yµ, Áñ±òH©j©`™Fó‘]ȇ†#0"-w¸i#°¢%“v'Ôá·zîÇCu*üÅF£Õ5µ¦°*ð]ÔGG‡8™ÄrÅI^í!! @ÝúðÉ(š›{´¼|óÖíõl¯­.ßòqxVtChf:FIZÙ˜Ã`1\Ÿü«¿€nǯÂw£òááƒÃ'JãC£CÇ<$¥Mˆ¿C·ïß·¿}bå¦þdÑš[nÑÈXVÓ:Xçx˜¼ºb£hª©#Ã=H¦ÈŠB1Ú‘N‚}H³ÙŒëq¨ÿÖ«Ææ?l~ûwp{´4ŠÀœ#¡œ»d!Õö@)“¦™²ÿ aVÞëEïwú¯_|µ½ÿão×?qðåÇÏfõâÅË·R$wB'Üëe )å*ÓÚè+Î7g&@ÊQT‡îx©<Æ@£8>àY²û}4ªB ÌGzÐ&",Z/¤lãýpc¢þ",ÁzšâÁËeˆº·Zã‘k-å§F"ø ÜþÔ®ÆÉ@£%KÒ\Е¹OL(¬b&’‡«#ñ›W ± 1tP¡XôQ ÐÑ)Õ©Ó Å9††B|¹…¯?÷èÖÝ[7VîºöÚÒõH8’b¹†&I´›q!±ÛÉ\SÙNÈÏúX;u•’îÃ[òÏ_íF'†ž>:¢PâïhÈ÷×nß~ìû»jzr¦MÅcÊü"Ù‰$…c¹JßGï.P` ô¨!9=Û÷ääPµ"«çÚÖÖ·•oãFMÀÁ›µÆæƒï*u8óp®z38¼a£Úôè™÷„™Ô!Äy‡8äþÛ7‡‡Þî½ÿ/|´w Ä_ˆ> @„ }Ûà¤%¦ê.¶«9Ü$èzª‘…™³jØmR]Z.({Ûsnåa’­êЙ¯‡Ç…”­ÌœQ'¦7q ¼€Û6i6=TØš2\ €ÓÆF;õjºàòÒ¸œ¢0¯C×Ý@ *÷^:bÕlƒ'¦`7ľVnyöBöwÛÊÌŠÑFŠïk£·$„;S,|ýïÿòï_¯¯ß¾±¼|½Ó¾u/ Ÿ G”•®:!ß%Î$Ç2ÐZÅ•ÒÐv–­AH”»ÑÉS‡O+ŸÅOsJñJ^JŠ%È@^¥gÏ’“ÜŽ†ZèÆ=€aär@Š’¥æ©ì2’4% móÓ¶ðµµñhl´úž¸õìÙè¨íV·ò|x¼‘u:ÒLSæ£nêõt )´ÎJ‰gZ{›Šû˜‚AÊ4ÕÆç/‰wKö!bÆ1€»œu7FÖEöO¿ÿí¿†ø»Îàµ;ݺ U¯ûÌÂ[‚*7ðøÒÌ`0½#±Ýz„# ’¿äkC:’!ýáôØRBÔ¢Ï×¾y$WÌ$ öÂëî¡—ÔÏÂŒl'„¢ïªù S¤ƒàú·3 „ÄJÃX™ŸVK¤ÂB”T„ 0þ¶5:::îó­…“³¥(ó[^•mê×hÄf€ç¥ÿD¤Å˜ý¢o †ár@Q¨-«”v‚&Ä’$™~g>v›òMß™y9ÿí÷ÿïú½;·ïܹsïY;úC÷e/åz¤(#S—*ê‘áGt5PΪAŒC–¡ß-õ_? xçþý벋 ^£d7IiзñhÙ•²™jªúôéþ÷0€¦”“+TŒŠœ ls«µùàï7ãŸV«ßÄßVBܨþäguÑî(6(FQ6¸2‘Í6 VÃH‚¨pM0íW¢þW_•Ë})|£á“åcGCF앺+²¥Ôÿ£¢©Œâ\wSNƒuÌ})“CQÕwˆáï!ŽÐ3{¹„ñfDš‚x.¦Aà½z]ò€­-¬DuöŸ$ê:&réb‰†ÔkÕPÛôikK,?¢ÑùºlY2[}º0{N0«nJ¹^¯··«•¥q’凜bzPàâ4…Mˆ³‰š‹h‚'‰"´õ|Ô áKEB£XtùÈ.¼ÿ79€ïܽ{ûÖÍ»ëÝ?¼EwYÕ‚ÃÚ35 MRd)¨çʘÄR¹œÍ92ààHôÃÇNF å1ö¿#÷ûÿuíþïß¿-nÚ^J󎢌—×}þ#çVžé‚Lõ,=mEÙsx£`tÒV.‰£w&É#njß„wTH!Õõ­‡[âa©[Æ +§Š@ÌŒŽ¦©¹l IPFËê0ßH44XzwdøË/¿ 1øô`ýGdM©mBV+õy¥ q—(ÙŠÐé­‡øÁhEª=ÀiÂ'`'rÅBÅ|æ$@¡=t¸qÌ™bfO‘@i?Úi}£&‘P‰ŸÖjiÜˆÇÆK~'mw‰ŠËÂÌùᨓÕj‚¹Â¥®TÂ×Wø¡Hì!¦$%I–Á•É‚ w›šà´"Iiõ&_ e)´Ÿ1ä"FÔr‘kB’‰Ú ø÷ßþþ?B ¼¹zãî½èOVׯ¯¯GÐçì]®bÎTò<¸<¤¾%Û ŒïÆîX9:|´42]\X@ü]œ»ØÿbíþŸòß~vë>Ð#൑½b I©ÊçЈé¼ètý戡7…Ž¡°WZ|Ð5t, Es­úM5>V½VÝü¼ñ`3tă)<Ì©Š²RœÕ) ã4Žÿv¹›“7×uûç>øøÃƒå¨<Ö?5‰À/ßõCõVHÛK ,A[PÒÏÒªÆóxFÕÖaðjGÊY™uu3ˆ¿Ûd™òa”ȹ"ì Áv4HŽYPžÑ$B¹N.sRK¤ýpf«ŠVõ`½"§S¯[FÇÆ"“å çK£Qèzª›(î° ©ÇÕ8p<«¾,L³0¤Ã|˜ö™Ú¥ $á–Mx44Iø§Äøä"g\ˆß…÷øÝ¹Ç+ ³ÓÓ3Òvþ'³¼¾¾¼²N….I§0ކࡠGŒâR ¯-Œí(­BGac9qìT©ë: !þÎwÃûõúûù/’»!¡s`Д†QÖ•õ)Nª¬b[+³Ö4û«,Ç=\(t9ÌÄXÚc„—œˆÛcMp0ÍfܨnnƵz³°”E#Î^s©Wi(஋Kj‹§,\ÙÐÃî|õÕÈÈ©¯¾ú²Eýò;'ôûÄ<ÅD2†ÝûzΛÌiÞÚ¶½¡zt.F3â—ºÕ-¨`]Êžwé:jJÝ-†û¬‰u±¸bêä¢ý…ÑöÃmmÓl< .ÔƒõjC‡=å1AB-\˜™/7§®Z©êó:+‘æ€Æ›jɸMÔ‚TÀÙGÌá°‡I(Ò ñ²Ý–ŠÔ*¶F.¬ÚÀ&|&J: ÿñþÜÓkË+w&Ïüxûbå+ë+¡]P_«¯¶cÞXô1ÜO çxË ÆQúøÐ‡Ï.tž.Ü^8ßížïÿ—õpÎ'O~ž÷wi#êß"[`«Cq[4 ­Ê±ÒVÛtÜö™®ÐôÀç.Ï!j©K†‰ŽM áä¬)ÔÕXZÀF£ò°Q}תãϱ(%Žl¢Ñ×µÿoäZ4ÁH8]´ý×CÇ÷¡ûöÝ—}ÜËJ-H .(¶õ¼úvqî‚<ˆþé,&y,õóÑ÷:PØ—ï$ê­KVèðâH¶—G­Þþ*‘"©IÚ•D÷*<›‡)§6ÂŽÞ©Ý®¬“x|¬|ana¬NÜ_XL(œVk›õú`¬ž­tý ø4ŽEê­ÉÓô aˆ3 ‹¦iS>0„‘‡‘BfX^,Ã=ÙŽ_¸ÞX^º²²|ga.ÜÝrk¬¬®DNÍžÐÉí …·æ­FÌxJÜ;ªî³ _³;uäàGƒ#ƒ/ü‘›(r!»g ÙÚÚÕë×?]Z]¸{o=2&Z_‹ ‹WÎ\ò {—Zó—ýB› ŒðÝîXtâã“#ÇY蘅ÒÈH¹ÿ›[÷o^¿wÿÎ=ÁÂ8çô °Ô"ó±ÔðŠLj5¸îÂôÓÙbàXàŠ=ÉΔJø•¤0\51qm6[õï*ßUëqs£¹!6·Ž×'M)EíÚÏôiAŠ·9 ë´9jÉ–¼ÿúøG_ï½xõúÕ›wBÉ„¹¯Œœ;€ˆ™Z‚ Ò1‰,åu%X{]f „Ÿi ’ç…á\\wéQr O¢e2Å(ÓK¬ÂòDâ:‹K—‘öÚpJdÕj5áEWêikxñâ…óç£f³ÖÈêN*7³ò(¤Ãj3mÎyu.sÂ{Ñ&N*õüLË@'óbN`ä;%ê±.€ˆ Ótб¶±’n8§Ebmùêõ+k ëwîEé¶sQšò^"愎–¨ï`÷ûÚ}YW$û,1ÌP|ÚFXé#Ç•xêã÷dáÔàHÿ7×ï_Ÿ\¿÷Ù]A¤fÂ×K¹æP“,f>d’^X BŸ*ëúÏÎî³6¡ è–Miô„û'5XxÊü]£Ui„Sø»j „7EýÐ)™ÊçÈ„Euehåˆq3]•½LäË£?z¹÷âåÞ˽7”É/$#åø5b©ª«Ûfp©’Ù0¡WÈlÂȧu¯«û9‰9Š‚aËêœöHЫ¥g7Š×B÷ÙÒNý¹dÿÃ0ü"€jNrSB¼ßªTÝèh©„*p|üÂÅÝ¡Ññ­­8¯TBÞ ‘ú¤U‘ Nêx×Håx qú\„–à>CKØF'’ä@jµH˜÷ÄÒOHEØFÓÎÚÎD>D›Ý]X¿»°v}ieáÞí·Cûëv%ÑHí ½ÑíÖ÷¸x:+©Š!§Â£¢²‚$ _"ÄÌÇžü`ha§½0xl¤ÿxíþéûëwÖd-³7‘'f7Á×ø}æSÀ•ŒÎ$¯†Þİn•OíN ½,î‰ífkË·ŠM4î oÕCäIŒ7ÍgÚv`¨ªs,µfÂcMÉ€kàÎÃÐô¸“Pè¿8tøÃ{/ž¿}ùš)°S¸)„Ñ"Gá‹Ã«ãׇï̸ÎÐ]® £ïPPßÑeëK=t©;Øøö ¸ f †ÅNMT›Ðš:ÿ˜e&<¥¿ÛÚæ.W^]£ië_m5FËCƒ§†ËáëøóCуôAÅÖªµG©P}ãŠ8¦‰ÅÅ€œ&WmqY⊆ŒüpC1/M9øI $Lo¤W‘”ˆãšT;1̰TЦ²NûIhT××ïÞ¹´ºòß­ËQxyÏÐB& A…œl opÈÏT±LJA’ñqÿ’Hv£áòBtdøÐÉS;Î,?>„\¿½&È‘2ÕÎ*ðû̇#g¬ïdâð%N¡±1€G¦W5¡Ì)£)NKK³4î"¥ )ð»füp³Vû®^k6Âÿ²Jä9Á! …NŽ ‰Y‘M²]„„é¿8x䃯B¾xùæK`§§>„âv¢û58 vDƒÎêÂ餅ƒÊXаFšwŠ 3Å…¸>ùu^í¾ä,{¢”O©ÑÑ0þ–`›Y‡eh?DW.bÝ´¬‰›êõ(ÄßÉ•¡©±ht¾Ó;`}»ûVøŠ–°;êõøQ¸œÕZu€ LÅŽ\òÁ…i‹A•ËYW£ÒÚb#à Õ)L!å8Ʊf5ÔoÅå‰:é“¶õ wîܺ³~éÒíµÛ·B÷ÑNzx¡¢‰Þ®ºÒ<ÓE|&Ë5šÓ:`)iWÄÉK.tMtääèÂN²0|ôxÈ€S÷¾ø‘¿þÙz—¤ßTVÙ…c˜Ž¥J‡XêgurD”¨ÙS¸\Ï–¢EãPˆ|I:õV³„¡…2`³¶Y©ÕïPÆ ‡ã{äÉkÏtÉrÒ*+™u©‡DWx81!ÔYrÀ£¼xýòù»"sµ¨ñ¥NW\=jž,9;;;¹à ÃûŒŠ?ø IDATÍF8‡A6ïŠØ9ÆÖÝ.:‘\qÝžzmñ Z:Ò8úSëìWfgPÙLi9T¯‚¥Òàái5¢áA·ô¿‡q]>?2T%íz-ôf©©¥ãVïàÓ¸2ày÷Þ™F2^ ©1i5vͦÛõ- tTH©–pâ ¿ê&AÖ(r¬¶Qó¤ð,H,iupmtÁ`o‚iŽlóŒ*ÉÉsN\Ô~ü‹í¬»Û¹sçæúÝÛ·×VWoÞ\ ÿH7Å\KÓ,®du_áT$ bƒ–JÍdz Ò#«5*/‹ŽŸ:>6ÞO³[韸^×+¢Þ²Éœ@1ËB 3=¶Ú$Ò5x+¸"«µ˜¡ßXA"6¦œ„»C j«YÙ y°Ò(6Óm¾î„I%1°Èv…†«øœ9q˜’¡CNU™¦¼{þÑóÿþË7¯^ì½~ñúËwoû}“yGº¢Ê¡~ÕœªxÙîb·,é³áWÏâžWt?¸îÈw$ýe=–€TK²ìrËí»£ˆ Ð3ÎÏM—Ax Ù+³z%Ç[¹ffF†g‡GÐ×76ª§õz'ÉkYþ¸¶^}§R{ämnBÊ9"•UÈ}(Ã+3å§ð9BÁ—5¶€Ì´éù›*ž4¥ù/ÆHá5´E±?²‘yÜé=[ð!tïÞ¹³¶vÓ<~´ð c740 'Sæüûëâ9Ëh¼JæšGb s`¾•G95Z>6t</gÿþ™}ò›'îâ.ÏRìà A¦>St¬³EbD*—©%—·F RjŽǵ((žBìTü,Þ†$$1n ío³ÙˆkÕz½enêTDE‰î™¶õÜQ+>•-}yF2 tµz÷«Ÿÿò¿¿}õ<à«wïÞ„ ØíS˜.ö¼^VôF²æF (!P=`]‚€(ÇIz¤´C>Ä!‰—ð§–†ÅQù %HD§8{ ×dèOCý—ìg ($!Œ•o>°¦QϦ¦¦g"Ÿ JºWí$õ8îfµ´76œ ŲËåB†&$eõ–nŸ3IIâIŽ'(ùâì%¥Ø²&M1-–g+Ôe‘\HÚ!®à¿+ 0(zºûl×-\ºtÉ›/=N/]Zð»`F9¾Ê¤üê4¾ vO_\ÃÙ`‘Œpw£ñÁò¡ò¡ÑáèøØh48:ûxýO~cs4Áâåˆ Ó1Ùq¸Gâ©™Òø¨€‡§‡ºçV˜0yÑ9/'·Å*ãáÉIÏé¤Ûï¬n‡2Yua'5âpØØÐÛt²NŠ£Ë{©J/+Sé‹l!©óxÑ\ÔÂÃòݯ>ÿåÛ{ýüÕÞËWoßî½ëw‹ñ^H`árÞ·ƒ¶¤›+š/+<.w€îïBßÅ Fm‡@¡Úõl|¡Èvš3XnÜ1¤¦ A8zyŽÅ ·²€ƒÉT¾þ^Ù°i£VNèÖšÍGYÖ u_5T]æa%vµM ê ,XjµÚ@8tŸJÈ·qµÄ‘U1/â”)m0"‹PȪbÄÒ…ß¶%v‹,Q%@)†oÄá ÈlHÛþYfÖ×ï]ÂÇúúB0ôÏä–b[`ö—.%ïBè*l%Á—ÔKGˇFŽ|ÿàÕó_þpïÕ˽7r¿ ]ˆ B† Þéž= 2YÖë#ܺ¼Gp`x ñg焦'š¾™×£Ó˜^ünχÏuØç‚,qæSE¤"aS ¡ØGR3ËU6Â1\ ïS%Þoà̼µ•éÆC™\T6ærgª!âXšßG¨´mÖh‡V·-ÆÑÛ²R¦)¬±rÚJ¬)LD3œ“m¯>X)Ï^[ SʃЈ­ÄEÝ…»ílýæÊ¥{·n/HŒs\Ír£ÇÆÒ xÌ}MS¯1(¾´Íò8Šmˆ¿áã'KÃGC÷1rrøÄÇ'K»ý,ëC*_®¶7‰îZ1Üñ8F “w^ù&еòpÆz˜M Y)@bA^žËDº4›/<`Oä:2g X{X?ÿæAí»¸AxWÎI #{;„râWÇò„B;å=ŽÞ½{uà¸]Òè¨×}÷òƒ·Ïuàù«—¯ßî‰uú»þ3;‡ÓÖ$™–o¹ºíõ ­JöJƒ,[eZ…·Û3IR×)0@l‡q~KËH¨cèfû6³: sÀÅ1¦»a™A©ÌQ z†ÚVC€©á¢Æ›vÖNOÍÛxºöó¬ÊyÛ }škµb]ó.; Õ<‘B3r±ËñrÂVÂ*`"­ùo`¢Kl!¿À:±"9DZ,Šåðì,æ¶ÑÂÓ^¿ycáÞí[ ,%ÈP^)t«YAB¿=ãÔ múEç@4Œ“³s7佡S§Ž>t¬4>Z:þñ±Òîn_ €@/c‰aTj{ ¹=@Y^†½§‡ñÌOÀ"ª§+›’x3U{“!†háÖ÷â/.v8ŒÃü ÞŒ?ÿ»õJ,ë¹ÀžGWº5?0YRM™ËQ´—FGbrÔïFG!$Þ½ øâÀá½W¡¿Bì‹bW᪫|¤$@ß# •¹ÌdD½ç{‚=w9«Á}*ÐX]c¨ŽÊÙ»¤ îRѼm[ §¥ n×9©á`¥F\5µ ;—ª™ÏMÍךÓÓUð_«sÞvê¡ä­ &‡«üûPíT8Yµ(íÓÐú¹xÜÒ@>1ÙyCè;N]«TaíhŠÓ9DåÂ]o×oÜZ¸}[Ñà'†+YCv£”Ù‚“¥k®òÎìsÃÔ.^h‚O>rüÈáLJƆ‡N=íú]%{²ÂÀã²xT}6(¶!ÅXT²gTjV¸y—hj(Bf9iËчc »Ôγ¸Ñz؈kÿ¿jý›—_š¢CÌæÝL ˆì°jnæm¨ŒŽÔä( ŒŽBXô÷~øîW/~ùƒ=IP*—µwÔ€iª„ó\™çäZR¿¼§¾ hHz¡Á¤É¡ÇŠ˜€J%`é'ߘËÍâ• qäx‰;O‡´#ì²w XE þ¡ii\‰çG’’ª3sµê̼©Ÿ;åÜÅ·ÇiÚŽ`5ô@D9;À¤p¯LÛBüÝFNLÈI’F‹Ÿ§á{Úr<`ˆÒ4£T[ <=¸§N±%ö~'Ö×]¾¾¶¾°¾~6PfU†TCø(²‘\1¢5s,Ðx\¢D”ͧ‡”úˆÝhxhðTùä‘ÃÇKQT¤]«<ãNoø8ÉÚÂÙƒãã ;B¶Æ— ]ÕöoUÚÔO€U(iR‹Ê‰ S7ïÂ8®=ø»¸þÓÆ3ºùöÈ=ÂæLKI•ÉċӢ@”=ésÔW·#…€é¿ùèÝóp‹LùÛ7¯C#Ì5«®PˆrŠ¿Ð¾UÚ\pås*óëAf“ ‰½ö(ø2/ ß"“:E¥¡Såñ±²¨EÓi#<Ž;[.)vJ^ç}‹ÔA7VožiÓ±|ôÅAéØdªòÆá±³ä=Ò©Hö]¾p{üü›zü Ñå,”SNªüNAÅ£6 ‚beM:òßÜ„tž=ë÷?zûòWÏCk"éoïµ ¿×NCT6fG™nÄ›bÀ‡v¸Ú5\h@/ ÈHœà˜{Z ¢Ì÷íhÀ‡4Nûy,„k–Yέ·ž8“˜EŠP³Öª7eiÌÌôt}cêÌü/æfM[Êí…9BH³,©Õ¬{ÜQ@ä€M0ûp+Çýs‚>5!Š)qÙ~ aèÌè8¡gI~<±ŒN;‘‚èFó²†;w:š9;uÚ¨´bdáÙÛ‘OH4vFOIÕ÷ÆÌNxÃÀÓ4ߨÐH$J8#ÇOD»ôŸ”{{ŸFäÔÞ(Sž NWÖšžh›vaR‡€ç,²Ø¨èµåfNNcÑ%I)\>j´5ÿóáO*µzµA\‰PÏĘ)5J;ÖþÀšBv$tÁÔ«Æš;¼k…:pÿà›W¿zþË$CÊ*„¸ÎáP!ú´¹;PñjHÿ.˜¿ÐîÝÉ!ÓAˆ ZŠ<[ž…3Wà8” á êúJ*þHJ ]’D[^ RdøL56¶Ùã°&ûÛz«ž>•¥„9wz²²1}f¾636íÒ’IçgçQMùΖøMgJy5m.Ð8~Vò/Ö½r÷4Å-Š,¨¿-Zým¸ÎqŽÍ3· ™íIñ—„÷4š>7’ßd47É}.mþ·î|O],àÑ;8@¨¶–sšy—ßÙN<>8Œ9Ñ`CüsÿÖ;5ŽƒupN‹à)_-– &zXm‡€ê¸Š;Ípæ!«¸}ß5¾ý澩ÕkqC®»¤ Ñ)–Ôà ©…Oy=i+Œh铿ߗfþOî½|þËoß½y-^5o÷Ï`Ñ,…Äiqá[2ªªe6aSŒ`8³•ƒvhF„“Ù½‡8%Éé ÷}@tž)0ÐXÊQÔDòzÎGqUúÝz«•™XÇ2y½ÖùNà¶3§'ê[S¿›¯ÏÎλ¼TJç熴üí<L‚Uáöm ±Z••‘ |mŒeèí :_‘ßÈ EÉ¡·4.¥!!ánI¨ L! rв0(<¢‰³§ÏNNFó¦"-=:kJñ–L˰{ý3HeìËŒø 1>1 ?"p§·++ëÙÓ"‘£á —q@0ùeTb1tãÖ ˜äA°)5`¸ÐÏ$ã?ûÙF¼ñmÜèqµÅõ•tøŠIÜGd4/T¤£ËY`áµ‘Žƒ±“÷püíËÏ·.a¨¿{×ça›’ßU`)'â].…1îˆî4f.2þ²“d·œHÁô)Û‘" {X+&«g <ª5š°¹¼HÀ=ªÕ–šÍ8~l+µð†Ôcá÷wÌÓF­fÜcoҹɳg•©Éùüâì¬éÌŽ™€iŽ{:¯Vå¾!`¥:Fœ®nƆ@¨Ê¦mi˜e°H~@›¨æ„ $'Ð|:á-!œlÔ!á}‰æçÎ…ìwiú“(ï¤NŒ}]8Àß$Npsí‘yJla–Q½O(‘ ÆÐ»"•?xdäãC'Jc¥¡cdž#1Ü$¸ÙžÿÀN—¾$[f:ôl®‰F$_-…íŽÛ—LUãå{¹Õs!¶\ΞèË6ª[߈Gæ7q3@à'ÁøK=×.ÓV˜U ú)ç8ûÙé $Ÿ:}ØyÿÃ#o_¿|þÑGoà÷îKÑjËTžkc–3—’ÂÏZ/L™"† Ùõr"spÐ?{¢Ú]¶ÀXç‰FaÞ˜¥à¥ 3$…Ä$‡¡Gšn‡vôÁVÚ¨oÔŒmTëVŒ¥mZ•ÓU.ûüÄÙ³ÊäÄôüÜìHêf'Kçg‡žZ Acâ8íBj¾›s¹*{U<:ØÒ…)* €JÕÕÑ+ßÍ®ïûc«álŸÜ¤òm¨IlGè¨ã¸YûÙOªõ†äò†ÉþeiK a¦5à¾{õ^çN¨ŠT;É©rßÞË—/?8òöÝÛ½7¡On01§Ýpôäó¬lB”«.™ygGÒaOÀ«) ƒºxëôÂ%ˆ¢Õ•ÕåÅk¡¯\qiÚ‘M2-hIcŒÁ9ì:ØW9*£b¼‡ÿ>¬šmÓhÊDþ¹NW)OæÞO„žx"t!ó336-MŽŸf­g+•ø ¼ä@‚Hä¥ô½B_RݾÈ:c°‘ÆÇéÜ6’ É—'\(Àfé>+éSÞ*pPtqæô™™èÜÔé(êSVì@<ßÓ<%'%Ž(C×Î¥½ ½Ö ç ÝÍ…&äÔÈà©áhüÔØÈø©┹Ò â›d¯º·/a¡Çýƒ(RB‚pß»H!®ð³ÉT> Œx§2pV1Aä ”R» ¦8M‰6ÇÿÚ¨Õ$zÐô â´EwE`-öêôšF (3ψ1 lCXõ÷Þ~Rß«WÇ>8’Ÿ€äƒ~Á=ѹê{Â.~G'ë)‰ EÙMDŠÚœæÒöuº‘´!7–—¯|z9êqQœYƬræòÁC¥ªæ©}TÛª[ ¿U·j±xrÉGµŠéÍܹÉ)Ó¨O„3xîâÌŒ1ÓcS¥g›Mâz!$>ÎT+æÆÜºÉaœaâ-gí¶Ãäå©”—ÀÄ;Ë;y*s˜ŒbÃ-;hPxíÚŒËîhanâ“ó¡™üäLÉÏOuúæÀ‹·oÞ¼8€¨îk3Ø­s\ç÷XÜ™¥è¹0ÓG‡G†J²;q<:v|1ØÃ¿9øå‹b¹ŠÔ–±ÖGæ¾™ÑFþ?ÊNlëxî[Pã­v,Ì$!`&~.à¨õÚw?ÙܪT `€IR–”œépä¨# 9ï6jšM ¸ãý¿zóÑB¾~sð‡Ç  (#!e¶"•½.¥°((ÓËK=ªt;*¸‘© _*Ö®!ëEkË‹W¯..^½rùrµT4#~‡I™Û°­é¨JËþÍzÍšªX-|×Dë%H\ÅÁ07*¿8žœœ˜º4;;“šÙ³S3%»M€¸F¸¡àÝ€SDz „†p&‘$ذYdhRÚà¶•µ'woÒ&b‘G,±|}ø4·àÌ%áE_šžþd2ŠæfC3|&B‡ƒè«\…rê÷[`ÿWX:L¥=ªŠP7bÙ:\ŠŽ}tx<І£ÁOF§NF»|`Ý«òÑ_%ޤrn† °ÚgÀ gIq¯ähpœ’'g÷]¾¼'p–ºþTˆ‘ÐÍZÍV³QÙú&tÁqë–ŠJR_}8å¢óÁ2¯|ÞbðO6"Ìñƒ½W¯ßþ ¾oß9pœÞy'KCÇÃC¦ o ö¿Ø÷ˆ6!Jv¾¾C§Ub]Â塸=§…y´E7—¯… gðÕ+Ъ]*UË^Á¦ŠÇ‚*•d’%ôh…4$« ªã IDAT fc£c³Z½ÓËn…è릶;7n§¹éó3&­™™ofjÚ¤³“Ãå­ª¡¨Z~…H‡Ùõ 0]v80mG Ažfd IzØ–Ðl:˜bÇ Ûa>*†éýC?úòÔ±992täðP_l¬\¯£Âjª=¬ £±×ð€XÛ²Fdãþ-Z¹v%üºâ0ZZºv9êêX3KÜ3yq¢ŒvÀ‹Sºº¼þj¥ޏê“^šnÄup¶9ÿ~nÞ™ù÷fC|œž˜˜š?7^ÙÐÙR™š&Æ4›-‡ÓA&OÄ1sýTé­Èõ]§t@ÈDʶC}ޑݦŠ í6ç„(‘2)q ‡â03ÑüŹ©³“§'αû8l=ßôÃY~é”JJ„&e·°[Eù/ê(Ö·81xêèÉèØÁcѱCG´­Û M 8V¬ß?°¢5ËIªüõÇ•sD:¦W{£;KY–à/$Ãà‰3 —†.¸ÇÕø'Õf½Ùê·–ª~˜ÕóœÛ½ì{NR:Ö”¹×LŠèäN·ÿÑÑSG§áF¤‚â ¤ (k Õ„¢M”³0Ô€ÕF­ñ³føC'A½b ü’ŸhÃùˆø"ÔL¤Ar=ìCcÑ ÕZ <„ÙÁŽK¸=x|pðБeÄÞ³]¨;‡7UY˜ÅÅö ¡|Õ@þÃ…á*Q„÷g‘YYŒ®Êé>>”ØÉ­5DͰOUí L;Hб*8‡Ý¿«…NhcËøíJpùÇêü×ó3!×\œ ïLÀé³3ÓS3֖ΖGÿ‚–À¥„[t3>pÔˆ¾!#7.e¶A”K¶·Û€l±ˆ—\ö áú$ÍÊñ(:ªypNH^‹ÌxLúW&=XX„ü…EÑ€fE¹¦FDOyQsK%þ»=…{þÍåjBñl:2dÀIÐg}ŸRîm1{¡gˆÁ.PÆ%`Ä”³R«Je¶U·ÇðKŒý(Ø)Å x¡´)‰Õ¡7‚ßê¡,¨@Ïe¦±rîÛ"ñ&AÓ/>èQ(m¯öÂBÊ UhÂc‚ÏêR}Ã+¹³Â.×Ñ#{seqéÚµkKQt-¢˜‡T“4P±d+B[’Ú²ï™zÂw`$hµºñµÐúÞ“fk~föýÜŹÁËLOžžœžŸ˜qiéôðXEfxÛ ­ ‡rÕßmÝÆ¡¼¸Á6¶¸2ósO“투?@‘¾yÄ¥L•:ŒL¦å}ƨ&¥¦ƒã˜dr( @bIF†8Ç ‡´†-8tÕÓ'¢]N²x¦; ÙÃý€á†ßý?> ¶­ÊgHµ!OPÀ³YjUÓ`ÇþCPù€äQÔôú .5\;Ca¦°~‡3Äeº¡"´8lƒCøå®°Œ(‘ *„<•N‚ž[øÍðU‘^.M·-k|h ©c‚¶·JFR“Bps¹*­#{ÈzXàaˆ}ˆà:Öª„Wjs°úd½ûh7ÄÞê•ËW®.^»,)ÍÎÜow)(ý©Š<`IÅ[×qÜÕ¥>Tq‚Ovr ÌgZ-“ÏÏ{aa6¼øxjêÌééKŸÌ¤iéÌ䨶òôå ‚Ì½«:?@X2ÉH¡¦n6`ÛA:å-¿ ¯. ÛNCúº ¡ÃóþG‚Iaè˜È„D^4|…Z¨1Ú£h(›ï)…Ëq¢²\‹ŠÃJêM½|ùÚâ5){]CQÀ]ˆ }(–œÇ‰f$•¸zT«Õl‡·$n™Ÿ×âðÕ6o…˜Ÿ}rफT&'Îþîtyb:Ác5[bä’çëiÕUÈoc~¥¦Å²6•¯ˆ]M¨ý‡q’ bzˆ›á˘Il˜múšŠªEÆÂ™luÈ1érøv£Àã ÷UÁZCÏ[,+T»QžˆÇ\ ïÚ:ÄR½Í@`Ò@~€tOÜRF·ûŒ\]‡Q3+Öœûà†÷û.4ÔT€ë‰ü$(‡B¡ Šeäíj¸z+HÝxã,¸öè_xl{5‰€&¯†¾Ix>(ö :6!üy_‘£““vUS*Ð6íoUaÑ* U,Yñ©‚ ¢îö¢+ËKË«+K!ÞŒ"¾Ê4 e1š¸ŸÈr íÊ­ß|T„±Z6nµòV3¸ƒÐkC©wz¹áDeWŒEZ?ÜÆ¢p$…ËÀ¹GµÀ^®Ê¨8ÚáÍý€ñðÞI-ØóÑõÕ••¥Åkׯ^:é«6ƒ8·bÙ-wª†{aöý|…Y³–Öë‚D O¥"»¸:uíÜû÷Ó³ÖÆ¡ 9;}îÒäHÀÉÐ —JÚmTÓj5üÚÚÚP@«Ñ»ØQTL^ßv’ŠÎKÊI^ÌvšµC©‚m £7(Fª†E<`Ö „]¬\Dˆ–˜mR®Ô©Pz"Áæ Ázlò½‚7q®A&º´à®;=¬àµî(…dÕ3ƒå§òŠèׄ“YVÚä‚8»¿Ø8¯ §{)0SF'g4ÔR°à2gÚE3z.ðH‰^¢B‰ÉÛTïxŸíï‘¥¢ Ctls<µ¼U„VN §\Í‹r"²)KÈóŽ ¦‡!JW‹9á K:Ïr*y_…úËÈ;ŠBò[¾±²xåêu‰z×.ôùFYV,Ÿ&TζA¹”»|V~@UTï%$ÛõJø®Øãc禿~?q&±SÓg'/ž=S2C2‰.•äk«©aŽúf5Ý£QÙÐÓôÈpY bùòƒB0Oä%·›¤p:LÆ3gsKIQÓq*ðñ^È ‰S‚iq§„‡ý•lsô„ð¢­@MÊ ÙV¦›#Y\D𠺇DF ±9¿ŸõXí)…pë†oñ¨r$(Á©pqjúäU0ÕR©-AA¥2&EýœÓR…”’f9s,î¯VÙB×"ö†Ÿ–5C§åÔÇÏwf¾¯5zTåÈê ›•®·¢âØ-X røª¹%ßì” èƒëKWW¯-ݺ¾xåÚÕO£ˆWBë¡Ì°G%@ðEô’Š…­-ú“¦«*iÓÍfÝ9‘æŒÃß/̼Ÿ:}Öš‰É‰ééÓÓ“Ó¥ÉGÇ&KòùP7Vã4©ÆÕͪHôZ8q´Õ­”zUµHÀ´.a°}Cç'ýKhh¶Ù¼r2ž£lçžò;r£§P”Ë)Uì™ÙBD?#4WÕ+0#™ïYÆD`š4¬ËÅS¡²‡ºïþ¢³9'<[ 4)FW^Ž;Lš-djÿH ‡!ÈÀ¦Pbí©2€B ,áªÚÞ[‚$˦ÍQ»†‹N _0ù˜J¡ÁèTAÚ2.y^5SŠZ¢cnÌR¤¦†+c 'íÕ@Ïv`-Óœ—yL ìø<Æ…Ø»¾º¼|íÚÊêµ+W×nÝX -È2ÈÅŠA@•eIñS«ÀõèÀ¾šÚZ%IU c36I\ Y)ÏëÂÂg'Ïœnœžž9=3?=61Q°j¬xt…þ9ü¾57Y÷Hë\£còÉ!™ú 0¾¾‰ò¥8ô±·R]¡Ë‘ûÄ*ÒC20os¢Ø³FÀ–•é©Y†Ë.jX”ª‡¨)WWÔËÈT¿E•õØêÔ‚!ëá{X¦palP¾zªÊ¸T4‘ƒ…8?žåžº˜”>‚%‡2&ÓæB¢âghbGq­6ÉX©å¦(VÕú'ÚÆ³&ÄÚ¦…\8ä,@ù¦Q~BK¤;¬¨-4›qÿªJ‹UǾ2˜° ¾‡²¡» l9pÆ¢W‰>èÒâÕ+Káô½ríÊâòg’———£Èj1Ã=°Hˆ…ß"Ù€Ä"©^ߨØbŒ§‚Lµ6ü“‹ÃO›Ÿ½059qƵ&&§Ï àädi«RèL»8å"[ÈRJ7b >@HÀO·%‹B XBmü¶âþR*r…v¯gïB<‰l™rNK}жÓ=ãEïÍaÆbžÕJ-t¥ Á”Õ„QãV8Ÿ»l*å­r„8‘à]Â&0>ÓãÉ‘³sšÏxsÕ¡ðÀLõÕ&ùÃé ÓíÏž­~­–vªŸO]ÊQ]–ŠúQ½ y6‘±<ÖR˜rMÂTèÞÈÞ,”Ô± ðô‰ÞׂO‚.)Ë Áôs✠˜±à)mCã±´´týæÊòêÚÒÒÕˆ‹+Ÿ-‰k•`®E”´XãL'¬*<4ÉÀ0êÃ|TÞñ¸ò b˜ò!6¬®×z#¼óg¿ž9sڹɩ©©³“å‘òD©<4Q.Uë[ãÊÜlhABøÍÇñ…G=PI°èÄœÀÛI¢è|a¸Iã¡ L‚”Cm¶®ŸBö† <Y‡ÈnR׺Z[£ ª†2QD;¡çÈÀä¾€ž:½Í¬ÒÓ­j+©âJ1œ²¼ì$qdÅîŽî4ŽK_Øþ`=¦Óň6êU£Ó?X¡dT~pF‹UΪ_ÈX¡¢z?Õ­’¥Ò?g@)¯ù•À|‘)òü&>× ÁbCCIe!Û¸”´IÂ4©XºïYŶ<-¬L9@`:­,]»zåÚòêg7¯ßº±z}õÆâÕÏB+,5áòâÕËW£‚ÁJï ãX_§*c`“d› m‹Û¬:Y!VL½nê5ñvóó3_Ÿ:cÃ<÷»É‘K¥)$Àjµ2óÐVç·B`~:ž€!ZXIØr:¤ÐB‘ä>öSîNRŠÃ¥h õÉå™ [:(^d®¥ûÏI6+ÊD$B'lNAÜ~dL:6%$ åoxng•äˆØª#ê A÷JÉ–p¬=¸ƒSa4äf/zޏï3$³2–>…Œ–Ó°'2ëlQtЉ£ÛÏ‘#]6!|u>Ó?y<Ñ|û)æ'ŽT)ôû9nªDWÕòäsKL„áÇ©N$ûÎTëA•­/¶*4bƒÝªåÄÔ9A |úi8v×®¯† ðÓËWVשּׁ®Ý¸¾¶¶¼¼©.U“¥Ëj0‚[zÇkçŽÜÿžˆ˜ÆF8ZåÍÏÏ~}úÌ„™œ™>=1;?==6=9\ ]Ke«žÌWM%©Ômµj/HÝ•rŒœ::mC˜ˆ#@eUz¿m8‚vØ*ËF˜Å47j„&Ï2gŒDV슧BM;a°û"¬tq\ZŒý¸.eíäPˆQ_^m’,)¤¤\RXU&;¤Íɧ𻅻qÕÌBìж8èîzå»"êˆÈ†JÆyNò«“§7áÌÅ îTD£?÷WJÔ:ÉVRpf¸idšm§„:rÄ3£#SL!fãh…×F3oG¹úTÑ*,­(8XMu‘k þ‡T;´¾Àk+·îF+ׯ…peåÆòµÅ¥•p8‡Sx)ÂSòi+?ˆ-‰ææî3LÌBrr@l=©Ö+,’Cò«›Ju[þ»N‹ù¹Ð„œ¶“3s“¿›˜.MO—'&K¡l¬\˜­§âx³RݬÌ͸ó²Å$ƒ ¤§ƒìѱp϶)bN´jÈ~ÛT,*v &¥ p"ÏÍͽŸ<}æìÌÜÜä鉑òTyrb¨d+¾z~&œ¼®Š •àÌ€'z2£œQ½AuK ‘–>Ab·OÛPŒNUˆÁƒ• 1bCÒ™œ{”W”ÝbŽsx[lQ1^/v j ’Q¤DLkµIÕj C:§ÌÐòܽY-%aHýj¶È¬ªT“ÒÒPgÁŦؠõ,@6ûaX¸xð€o8€‡†’¶µÌÑ^ä”TixÜr¬R ÿ5-#Z=@8YÖ¥Ž0½=µèÃy.oÊ|MªS7—ש˜¥ã1Å|X?5º1~Oe½Ñq!C«!(èÕèê„W¯-ß^ ÅßÊZ´“'OwŸ` Þ‘23³Ý~ú„y×Í{²R-&2Ë„{'Æ\¥žH5æf&çÞÿáôĹ™ó—&NOL—'K¥ð~–²óÓÕ Õ³®²µé«Ù…¹é¬&Û„ÿµe×Ëý´9¨â Ä'A?—>XÝ‚±àVù!y{Ð ð4ò† £Ü$€z R„ÚpÌëˆ L^8б¶p5·û¸y¯« „r&¸ˆu’¿SžZ„"ÕØ¢Æ·šŒ”éÐé&˜ýw! ¬ cÇ'Ž“ßPŒC)2ÁûÖøvšÂQŒÐè'&yÖ§Höz爼´§ÏP&ÊÜ™ÙBÕ¦*Ž0ˆ)!DÐÑÓÙ(–³?–€ìÎäï!é-  ©nñJ´tõʧW–>[¼rùêâÐñó/’ûŒsÊ3§TO­P(REW¯-®ÞX‹\§ã«'dŒ˜ôTk5Œ)ò©¹¹é¯ÏLœ›>?;söô¥sÓÓ“S%kK¥$;ÎÌ™j¼UÛ»à  ¼Ï ¼j” u]{» }|8)ȧq¡ÞÆ TÏdl‹åmÅ;gµžC©Å ÇXb¸ï—ÃÞª÷Õ­“-’š+Öl²@BÍ”Ž\3dØŽ¸èT~­ð›õ‘Wìx%‘÷«5ÕÝ$Ï)ÌJ½»ô/¡uZ ž)Ñî`R•׳b™:–$S)õŒÅ,wìl ¢{¦[DÝ) c£ûb5-HOx±\‚£0Oî0Oˆ:Ä Û¼juj‰Aâ¼U ð¸£++ËËëëvÛø(Z^ ‘tùÓÅë”áP¾{ëÆõ[Ù}:ïèœê•fåžîŽ7ÑÕ«Ë+²¾» ÆV½ ÷l®Ôm½¾Y«z@g.^˜~?11u~æâÔé3—¦g¦¦‡K®ÚüüÌÌ…jÏÎV«>?nSu\—$Ývm ëÏB|«Å~Az!`îEN_” åCP#9Îk@¥ 3 9FœSó,X>°Î ·T×…`w§µ˜Î-:l‘µÇÄa³ e\?Î2¤„µ w¡¼JC‹Sž æÑü#s~°˜ÏaJÎIµ/¦v–Ɇ«Ø êÉÎAŸF‹ßÇê0\ªã¿âiëçL¨ C“Ì><ÖªLú…:ÜÔz–h ¬@@± éA@¹¥T°¥”·Úlq…Up¢@ ÿݾ±º¼nÿüñ“è²À°$Ãá‹ÕÜhmyiq-â&Çjn UÁSr›á£¥Ø_ˆÂµkÉÅt¹lÅ‘&|$®“†rlú⥹©ÉÉ©‰Ó>ùd¢<3snº””†GKÞtΟŸ›3 s®šççç8„H½%3ÉH‡³òÃ!°ÖQ"-°Ü<¤†R[še Ëf„ |ps©p4‰¼Ušël.•Ê¿íÈ2 ‘ç²ÚòÊ;rFËŠá4‡2ìÁØ¿ê™çIFÓ¨xÃø…*p¥Z²è•¯Ë‹å-îø»³äëä\’en?Ú9oáð8Ãp +)ªb¹ãÑ3Óë<ÝyPÉgÖݪUËÅñŸ8ó1ÄeÐ…B%3™8¹ÈãpËG"wèƒr÷™8²È½ƒÔBàÓ)…ͱU³d ÀÏn­g_üâ‹»ÑåËr®¬®ÈúêµO/‡|&£ÀÅÚŽ¸èFA ,)}³ù“ŸnV*QhW"«b‡-IJVÿYˆP߬V*x¥çfçg¾þzbjjrêôé‰ùÙÉ©¹±ÒððpÉ„ú÷ü…›-T“jhHÏÈY ( RŽ¥ØNÓíí”C˜ð‰mÑ ÝDpoµ) (/—‚Û_P„@ÊS1^lÄPäîY@‹Æ<Çpµp¤ÿÕÕÐß7¿øÅýhuíÖÚ­ÕÈ~/øŒœ âPÔªmÖ¶*›õ­Ï·Z8E©›Ö°Y½Ñ°¡ôÓÉ+ñS¢¶ó|~zjnêýܹ©¹yG=Û IDATO~|v¢4YG°¬B1c]µ³UÛ¬lá>ðÔÑ ¹M¸¾`]äK$B/Rr9Ès)|©o'D‰ˆq°0\¤‡(Ó¡—–kŽ8÷LÈ@A”Â’P‚A¶6G¡%“ç<+Ò‡ót×Už@103ê³ãŠËœé‘›Á憙3 ì‘9œ!ÍDÄø7ƒIuNÞ‰Ë ÉB‹kø,Sr(qº©q ŽÈ”àÑ¢}=iÏ^$K­j‹°áθÚΊÝ|yNR½ìo«-£‡E«zW)ž7eN…ÜWm ˜ŽN±j°#W˜oݹ¾rõòåËW—Bù'ƒékW®-Ý·éúÅÅ¥¥å¥Õ(-ôQ-œb·•]E­Ö³¸¶ÕRù,PÕj®ÑØf½’ãVˆFºƒgnöýìì×fÏÍ…&øô¹òØXyâLIÞÚjUŒã*ç7kÓ­ªqV§åÔÛu)U01peTq IïžÊ͵Í!¥3í„Ø–¶s ­K!ÄR ÅRÑ_#u^‘*áyK›I ›la2ŒR§[7¶œ,Ù40DÙ:¦œX×ÄÕI,¦ZÄÓ=ls5aàÁYÄNÆ•-¾Փΰ-¹zª˜PŒy¼.„yÌÃèAp¸^Œ~E­:£öÜg{•…óB­5ìÐ !vrá¼Hœ¢N® AH[+«8]‰p*Ñ-á¬ämñÅ.Ó݉>»¾xåÓu+ÑjÈ…WB,.®®¯ßZxêòÒrTiöA]á[#÷¬~•#ÿ,j¦éþôá‹j¨ +q•îqÍÆÃJ±1wn~fnîýù¹s“§tæ“éKåÒÄ™¡’x{ûJ( íª[!š]ö+ŒJ#%جò6ÙñE꩜ ÒÌ05„CkJ¥ÜÉØ§òî”oAx2N ¦€t5ŠŽÓÆ‚ ¥ÉÙn莌 œÖÁ.ƪf½nuÖAÞ*!]Ÿ£ãh~3 *Î? “_ŸïÏðTŸÙèèš*xR;HÊ4©)J9î^SÌš½r) ôáYRð4·4ÊѤ"«§8%‡£ô¥úˆ×®[Åž)h…§a)îwÀÄZ×H_Uî-,Q¹øO†Ö×éPZû£h-ºµríÊU!¤¯.…øûôò•Å•èÆêÒâÕð§¥¥ˆ"»Ôè*$¤…g߉0 yà¸ELHŸ×dÐ ëºüaÅOÚuæ/ÎüáýÜìÙO~ôã3çÆf&NO ‡3°ÞØÚÚªVëõÍÊæfž›à·a°`ºG~}Šþ— ‡LðÎŽà­#(R•§4õBnÐÙ¦ QR¾$OœÌ¹âÄÌ|J‘§O8ÒŠŠÕ*=Òû\'"¶qû €Dš×¢!E3šŒ8d^÷ÁX:;ô:åàÁù¦Îôh& “<©9àcæË3¢Þ±ÅxHæØQœódºÕæs«d£ŒŠ—5e*Qí‹–›¯Û£ðwˆpBRq€û…da Í--Ù„è¤Regrn‘P´¼¨$cKg8B\›‚ ¼ºx}%Ĝ̣—£×€—Ã1i:–ûZ‡úvÁítLu¶%×åc¿úÙhÈ&8¼æž1U+·¥4¼Á~áÜÌ.ÌLœýñÿu¶<âo²·jÕ×6·â¸WcAåÛpÃ~ͨHð"ê S†pöâhvÿ{oÚÇye âsr1H‘¢¨ÒBRI$"K"I€ žjo’Êši>>Œ«l-–(J†Üj13â]"2Aësµ»Fn?üý3êé×x\Õ®¿ÁåÌ{Ï9odr³EÊS6§$€\"#"#nÜõÜsË¡çL`A¬–c[…¦G›îIß#j:¹5‹#œJäD¥o«ðÍ`ÌPHµÒQ]¶jUV´9LKƒ:N çeÈ+«\—J~HŽ?|ÐØ÷œS…/a áÂñ·ˆ—–Ïl.1{{ 8%u¡R *ÊË»œ¼0¶uMCNzgè"¡Ïl…Ë> Ëð‚ ^¥7Ç´Ã5lg|ZkH¾Å·^ì³vh;¤à Q¦ÓöÚWÿæÍö›ßûÎw¾ýïó×ßNñï[?x“ šI/ÊþùÝ]§|¦w;6u¹·÷ÇíWÚ;o^¼]©5Þ]ÿ#Ô¿Ó¾ÆÎÕ“4F’îïœ?¿uùâÚêòÊæ¥Í^ŠDN×ãxíúÞÇÉ‘||ñ¼û¸û8ÿ}h6ÕˆLÐ %kS‚KÓ…Öh57c‡F$f%¾z)Ö€'pœ Ãæ+ùøLÕÁ‘®™ ÆÚÞÉîØu­HFY3‹k×ÑŒ=áT¤hµrMœºæ¬ ЋFÆI€{J ¼êÆ14‹S›aƒV¨Í¡Mœ˜¶¬™E€ŽXç`½è•³Öd¢H£ŠÛ¨B.‡ß±J‰j4|N¢£ÍQx$Ô,Êw@ê'êFƒ+B½DœŠ÷ §(°UŽW§À;ÕïYkg¦Ô~ Ôþ›äç½ñýï}7EßIàø¾•áþ··R8œž»Íº]R>»¿‡&%äp닪šüâ‹ú‹9¯^½ýÙÛoÎÌŸ|ˆÛÃØj>*(ì•Mݬª­K;—×WÏ­¼2ØX_ét’ÊëŸ\ï]¿véCwy`áp:°9fL-÷3Â`V#ò¤ßPeH2Yc:a/ ÀòlL–5t£¤$ÂT8Ž]–5ë¨^Þ7úœ1]@W¶©Åqa®ŸÅ½“ÈIÏ>3+¤„ô3è6¤A˜´ Ü/r&’˜ÿ½8×hÇÐÜ®Ò /¡®ª]E•ï•È´tQ°œ¢d4Ú&"[•Èuy›`4"Î@zeÔ²+á#ü”¥\ 왹£Ž°˜Êþ0É™ÑõL˜EÉ!ý=¯þ~ï› %ò‘ÆÑ¤û ¢„Û×oÿÍëÉæ¾ùºcRÜñíï¶ß|ó͘|ãûß5H´gßåîïwÙþ‰½»­Éõ/&íSwÆoV½ÍtOº>ÂRfòÉÏü$í¿¶A“æâì¤e}u¥køÒ«§­e4}èúµúÚ¥Á–  ™Û¹`?ÇŒ³ ê”A„^‚ c>fÉAíì™7‚+ÄÌf €¤²l ¸’زÀN/dš£±q•`!OÑJV¼t«ðÄÛôÔ¨YJŸ!µÏ¾HŠˆ—ÚåEÖ‡yèªâ4ó (FÓ,&èx2v¨;ÃyãÇŒUiWŽ[Ìþ¨Åù(c!½TèãÄ­º6¦«M ËŠÄ"^.í{P…E(ôÕ¡ˆÁG%)á’U¶xê£.g ‰E¥›(Ä”ãˆ4T.œ¼m$H¶„ætM†‚öIý}û{íXå÷ÛÆP™ž~÷·®Š³Q‰vÿ›]N D`ú…ûõÍêòË/¾²Ó»üöÛ,µ˜A¹ö‘´L²Æ“=Þ–ÖgéÀ”´sigc½»>8?ØxõôI__'¼´™Ö¼f~®b®ohÒ4b⥔WXÚ×¢`×aˆÑ™s  G0¯ÝN~ök€KÁçÞ\Á€‚µÙZ£I0ü‚/j8=e–ÅÈØBð;fUÈ&i%EëR1NXÁ¼B> •zqâ•Â&/.B“*&#›B8·uf4l.3ä6·¢R^~]…5`•Ë%šÃ $EÀíâ(wðÓÐÓ´ï¨@<ý³y“]QÚvì‰#0÷ÃæN7'@41òpá”ûð5Üt›Mj¬Ó øÆýºu¨ÿ‡ï&èÔñAJQï(€Èñ$)yuç‹/¾¸PM=÷W;;Ïï HÂûúu‹R ª÷öxÕŒ¼‹sÇ;ÛƒÞö`û•WO%Œ¼ÐÐÉãñöyõô¹¹²ˆ»{펊#Ç0ÊZQÐWŽÒâÈÌ…C$ 3[cF`°Ý€ÐŠW®ÄD_–…]Åš·ùL]æ4¥)pÖXŽ˜§ µ}Âr B+Z)Áá õ±™6„`'ANè.Cri"I•ÆQ«ˆó•”ôé¡9~Œ¶£5/¨NXLjdOô:`Ôb.~ÆËQMí”kÞGH QéÇ”~#ë'ìº ¢=/™$q Ž Ú 86ш'„Š-ÖÞ«wÄâËöøÆëo˜˜¼Á¼‘’ï'/ÐD˜ k´% I•ÿ>=ìäIzîÅÕ‹íöÅ«/~ï“O<ËBéûìAï¦ÐÍH˜Â¹³}auõô©S§Îœ&ÏÊ\eÒìò›kð&jÖ=´‹Õ…G¬xÈ;!(¥7#È¡mÄôˆEÊãªÈUuƒ$¡gæåÆ&o¤BZÛé$E¨0cddpÓ#õ!Q†[Î *KÑ ‘#…HÜ3séЫ">žBdù˜úp!cª8‹Xgö'£úB]ˆpƒÕqØ“‘øYQ£ Jl ¾léOÄ%, cŠYlϵÔo¥sqðÇSmUjíRIÄÕ¹säSp<³#u€4M°wâ¦pBÊàmøÑ(¿Á/a]…ò{¯]ûþ0I`Z^ûßÿæûßÿþo¼Õfß ûALwÿ×Uÿ›]2ù•§žûÖ §nºxøÈɤÿÚíOöÄßkûÜÛÛ –>²­ïÝ µÂMsG×Îöéó§·O1°Wt¿Mgn”LĸV‹2€«µF7Tä̳3ó WЉsdsØ¢žv3bŸ±æyš ôª§"2“4Ús7,ðÆ %¬øWÓå+ØTMÓ 9á›Ù¹Ô€KÙ”YUô#5o“)Au`ò^ö š˜¸ox µ’¢çHÒ¬ä¹méÒ1}E›1RPO9»“îÝ»{÷N»=1â«Ï>Õ2òí;÷n—N¬¸¶™Ýß]M7tRiÓí—ÛÇŸ»49tê•Å$ïýäoßùðgNÍU\» ¶û¢B§ r*7œ>•ñEh¸FSƒ»œC&½Æ›·e…7ëL¶ù ¤aŸ©e-èµY†`9'`‘/<^†HøìݤÓH7}sN´y(“Xr0ÍqEO›9øl 3 ùzB)Cź>Âß°Ù’FF4ºõÊ„3‰‹£Ç8šΠT 4eÖDr#‘ZÐØÔi®–‘aKå4*ê{%š¤Q6äkṴ™EãîŠå$İ·Z?Ô$-Gð›Ãw¦½ºt¼lœ:‡Ð*©]ˆˆÛ60€VkÒAZßäïî$‚–Þøôç?¿1ríöí»·ï܆‚‘¹û_Mú»&'íö‹íÃ[›G^ÚYmŸj¿ów?úñ;;•£pÂæ˜l´Š,žj—KzBhƒÍå[fŽE°šf¶0wäG»þ,¡>D´/”ˆ/3%û¢ýˆÓ¨j'^¢°òm˜™JR ™S“oø«Z@BפËbž´( @½â# èŠÐäD& ìvÏ<Qjì~ ÷eÄT ¸<Æfp)µQÅnn5a|£RWº³ ;FSI‹™ÁDâe¸«±¼R1Lÿ!a^¢5+dä–±„¡ˆçéâk9!D Jœ÷ƒI¿wæy‰XÙn¨B^šUhb¡kâö´Ü¾=;] }Ô^º-*yÛèî?™úÿa^`û•ö«)`i/î\<ÕnÿøG?úñ?¢+ÚôáТ>Dtà}̰%¹E–|“„ÎaÈ Mo¸©…¤Ê÷Âߣû<]s{ Xh-xÌA¸ŒôZN¿¤ÐÐN|ÍÚRÅ‚‚aVHUÀþZ»¸µ†UÕ‚)ÕQ¤É¨‘˜÷‚äƒIô0˜¦Á*(q³¾'+Kú f#b›ÔäOÿ1`„´ö¹a’àdÄk2VúºˆmÍ‘¡^¡=;å]Û©FxÙH{¯òv ߤWJ׊ý,Ï2¢ŽºoËÐ8éû”€³%ŽM¹bCå}؛ĎM˜2\ßôñ±Äps'Ä|FŸä/éÀ»÷nkxß¹mÿ˜Ôf™ R ìü^»}²}h|hg5Éß©ößýíßþäÇ×c!Ïaêv‚#$D^Äû‚á¡—ëg> áÆ MA— nc.ð²az¬€cg¡SÕD´: ¯ÞªH96æáÞb U!ãØ+éèûwѤµ‰v´•ÀñU&s³dó—®„¸É< „¯:FŒ%c½†|¿$ «”¨‚0P¸¨ljVaWE2Y°{&sHÇØ£©ü™H B ìî?_2úîú½$/Z¼ôbûùdÿöݽ_~²çÙ:™[ ÕÅ)‡·¡ æ>Ÿ[Ö¤¼59byÀxcyÀ4™iõQ°ŠØŸe^‡øW0-ÌjºV1ÁÌ›‘ GéœÚø —`jÇ…±ÏÔ¢¶ò6>¹ò,ÀÕ1S«PØZ|Ò™²´‰­ Ñ8DÁq¦¡gŸcWD5zÁø”n‰®PÑPÙDk›o³Õ0¬ƒÔcžóa裦h#(A3=XP ìUÅ&ÁÜ@%/ä qð'gмQÞΪÒm•Ìè}Ö›Õ‘6Ìô5üÓ€Á—Œ¾¡‚lm>Cf¸´ë oŸ-Å{ô!É\’:0 •^F9IὂEæ ¾ÞýýUÙÿcwgût’À•“íc¯%Mø£†í]ÚQ&:“!#¢ºt˜­ BxØw+äÆfE¦aØùd´˜dá-­ô# ¿‚)"΢± ž#Ç‘!0=nDvfÝJp9S¡”N¬Ÿ6dd”î{ ¹íòŽYip„’˜7Â93ødOª…¨ž‹Ü<…•*M•!“R’‰FX¿ ü2(¦(Ä™§šnjÇpb(¤d쀉NÌ•ûÏÛ±R%’Í…Ã. ¿®+z$N2"Ô ´«Jå%\LQˆ0+Å1y#ç)M£‚·”klÈN‘†Esjp™ÆE±S tq@ýØp„uÂÛÐzIç݆¼šR½w/i@NßÖ°„ÝMh¾Øÿµ»=N6Ø* /œ1 ø|ðÉÞÖdÛKZ™‰Å`UßCvZdZ ’Ø`XF’99 Á@gµóqD5 ·|„V^h¤ÒY0qCg môÈ1q)6„™3b‡–üµ³!íø$NªºÉEõÑB¥::ŽÀ`ÓR!2¬Õm‹ jCŒÃ?CÒ§¬ª€2\¡Ì²É²b3úyzhó „yV †«y¶aà - 4Vñ¸ªmG0)PZ+qüa{ÌMÇÌsd¯ð6ˆAiO&#[[Ì;±N~Ç;EÑp1L Nã4ψez²cq€J‘#<+Üæû%çïî=e®aͤI”|' üý¿^EL°º;>ݶ ð íg^¶:ÈV:²ñŽ¢o%)]ƒÿmšûÑwc‹‰wæË¤¿sA3ùFhü°²ˆ±XRNˆ[1²"Lº-ÏÍãÊy•èôÒ)öÐ’ÚÇ®P‘ÏÌu¨G5§B­ÀòOQ±Ðni— g}äÈÑFˆ!‹±„,¡ëØŠÅ YYÅåÉQ)È ®-À–…ŒÆ´R¥ìºÊ¤“J«:TþX;AÞ?qÒ0Mg444I2M¶¡]âå Ba8\ò¿‚xÁi /A9éºÉb2àsã»$·†™á¡%«ã«‘Ñ#Ü0±4ÓB {œâƒ8°¦dÅQÌ)ã Ì˽"Z ö¥ð2‹4Áò;Î×4~ä [_˦‰;g;é§Ê‹  ¾TŒˆÃ™ñÁôyæ`¢27Ð= "©ƒíÚj×dÆÖežrÏdjW<,VÌ=&sw^ '[¤ „d¦µ˜wä˜@å[ Þ3ÄÁx6Ê•’˜’rhâ‘¥äP÷€‰`õ¶­Ç¾#Û³1M>Òž,Z¹ »z’TàÅ+·Ž¼uåÊ™ö/ì»N.í¸&ÄûmyL6'¢  .Nîc%ýˆh Õâ‡*’ÝçVýE}ø†SjÀIÎΔä0Å–b¶Ç‘]Âëì74œ*ªsI¦ÍágâVƒÐâ»Ê0]¡¬MýUl[Šðf":ˆT7@Û0ܱd÷I`Ý#tå1=.gš¼Ë1‰9ºŠ‘0çÇ9IY ®Êâ*`yTÄ!àì@ffU/0¥BJ‚‹y®ËˆWO_cÌCñRƒBšzfYs·»K&Ñçô¡PQ½•.f¯©Tä$Æ|Ê]† ¨LR ‰¤shúðn“ÙѰ|)óç²ÀÝ»w;4õf~Q{/ `ṳ̂ÞÞØÝª>¯’üç×ÎÌŸhëÉ/vv<+Ç>÷Qæø÷ oÉÀî(ÊdŽÁ°EòhÝj8—1>ø†‡ÉZÜ•ô›eXÈF˜¾‹t1z—¦w%ÃT8‹tUcÎ#YŠç_PGú–•[ò{—¼ØÊÉ0h…vTv ,¿žðÏ{•YªAÎ×2^#Ð0 À-%©‘(„€XŒho}Æ?‡Æ T)ޤ©ù}ÇÆ W×LB>žÊ@ß+Éü"ÒW2íè´ê™Ãg^8=™lì¶ÝŸ_¸õÛ3ß:sìäKí_\Ü!ÆÉРZU`"sÙñþØÑ8•E®+Q" ƒ‹1Ö¥« _ª¾m$E£‘MS‡Ýð˜+=G†˜Û™b*ŠŽ šu!Wè^°ÍU ‘`0ÂIÄžéÒšµ’Òs8Þ·‡ã)œV¼fΡWJ–®õT´0-è8ÔžŠ ]£fr IDAT8½€Q§›bbùW‘0)]xgãÀ¦^ȸÉsEóÖuÎr¶š1ƒ]eËŸDfÄȾF5¹çPEZÓŠä ˜ Ëæ8£4Ý^0F•â³ u„Í⃎­$%<3¶L·eø€+å‘B«DðLSÃø1Ç-–Œ@èÚxÁªË!¥ãhŸ8ùÊéלñ’§ø÷Tûè‹Ç^l·Ïœ’óB%í³8ˆ„ß•™óH—Aõh’nÌ9ÚóÑ\A@¹[ Šo‹+HÍ¡{͘sÔ3ÍnL;Ö]›Ã¡±M 1ÝVÞ!¬s¨ 1Í|‚jR¢.áÄ÷bؓڰ«5¥d’FîSzed5ÀloÅ$q$~Ï«¥Hâ*+Ë[‘XјóiùŠáuPSmy?ßPde«N^CF¹@"šô'µ’Bó½ªò.CmÏIN] ¬­±>DÝHÌ‹T˜s‡­ïZ¢òBs9¡ýJ¢3g²0,e}$Uʬ$sIJ ²&‹[÷6½>Ññݽw[½vt, ª'âkN¿ÂNÍ$g^Nxúô«¿¿uªM-|N« žd²l`±p}ãÇD û ,Ù^äË9ÜÛ¬t€‹¦L/:``iÕ \’ˆxÂbÈF1k”±ØehÓ ‡¬ëF•Èèv°ä¹ZEY«qútR˜¦b‚2¶^&««ÐÃ>s4‚W)Ï ásÑ•]Nl¿ˆ| IL-Ù+‰¬1bŽø¿ ˜'Ž‹;t)ãW `E‡MkÞßmà|Pk²—Ä(D‚¤´aÉœb£ºæ­Â¸·P/BßGÖÙí*WÁ+övˆŠ‘ód6UÜ ú!)·³ P`§{·á:KÝš6¼ÂáÀ УTiL·ÿõªí¡ýJûä©$·~÷»¹Õ>•ù±pXûQÆ(Gar²ÉÑÄicÕ€ÇçÀ7åý¹‚Ú=X¦ÕÌ/jÔ]B=b›¦ãXß3fŽ ’ )-h°ñLҤÑ9"#Ã;,Âc>=zߨw %ÌMëRGX™‹ÂA5.¨ެF^YÞ æ¬³Ð øË£ù Mà/Z8 `ðÒ|9C*MbKÅBU¶Ý°8„k Šê$å'½c©ÏSßÎa;–W)„a·w¤ô`3 u¡ª“˜p‡¨¤5U"³+B¥ÈH5%P¼^mÏÂâ{ ݆ì¢GŒ°ÈìÿãÐÓ‘“7Ç ˆo—R'ž}O4ØØÉî¿^M/·O'ù{øO¿M*ð–÷™6„ÉPßT_|NI骘w¸”ÁJ¾}¬ïÕZŽ µcs /nú/9Ãaî=1'ã†S1ÑÐhòop¤k‰>vs‡žv7,9X‰!`Ø9t:2HãDè¶X#¬g%Ë (vn+Á¢qsUéüKÂ]J¤ÿ|¾¿Uê2ǤLW%o H ç Û2/¢§à•0Mz MÉ´éPú]ìN2G xi¯Äî– >Q%ÁŒê‰å *~ ׄ,f‘dœƒÝ$|D„½ŸšSÏ6Lzˆ¹ø “ì³Ü©‚!¸4Æxyžhº"XÔÐXN1ˆb`XS)½ì'ªýÙýýÿºêÍ·ŸOøR’Àßü6Ù`DðYƒˆž7ßÁ(¢†-f.ÃÐlÌN™b*á6Ð(L´[Jsht–©DÙd:Ú®?xdpªàì ¯ §&ˆ‹¬¦LSmje4ÆØ ô¼Š(E‘„ùTRi;õSÐŒ’'C>üp–+©'2Ædš5ÉyŽ(8²ß^Uꜜ¶“ÈVšNf|Xü R¤17š"TƒÕ5ÈT"'‚½ˆ|MS„î©ÙB-r½E¨ÿ˜ibEæœÉš»(ÊÈÈËP¨R¦ÚVP¸ÈêGV˜)8S£ârÝD*Ë´žB‚ä Þ£¨Mà ¸“%yÒGv÷ÿ\õ)yþÔ‹§þ øO&€Ê`;'”M-31A®Iù”e§£Â%”§˜ã}eÌ~ÄB J£0nK¥Ã¾R·3ø5`Aòý,d…Ü27Œ9#š›¢Ï¨® ;óuîsA“ÚlÆk­^o/K„­Ú0`ª£žnÇRYI,TüÙb&”æl"dp‹’ %ªàA_Ï{–z¥ç7†.¥7ZåT%­±c¯(Fв‰Cô°{‚þbñy:þ¸ÓšŠqàþM<£¸}‰*ƒ^À99cI·OÓ ê²7ÉÂ@ „ê{ KX%ït‰–ü0suˆææïÞ»ƒÍsò.1ë¶$BJ¯íþvãjûµ“§N¤/zqïå[·vÚ™"”žž|½è²3ˆ})3Ïñ²èê Ê4ѲÎyÎá2q,<@p¨ö|tÃVq£å\l'±Ò%fÊ¥¨ÃØ|ù¹E+UœaÉók¬iÖW9u{$aJR—´¡M6Í8µEÉa6ÚáÕf&½4~—Ü㎙5ÎŽÉëSìÚ¤¿Våè„Æ:2ìõ ” ˆìG¤ Ù¢€Ýi0é+O õ04#:£¨"b¦£úYLó²ðHu©[³"i§\ÌŠËŸR}šBx:Ö Xì`žŒƒ%9›V’ÄÛ—y–Úå¨v"ctñ嘹ÇJˆŸLÒÇî“;Aä%Ù ¨åÂîÖ ýRõÒ‰—O¼4zÉ¿\}òWí ô¿²[øR%‡…œõqr"¼’óV¯ün%ê¬%73^–ªâ¬Šx‰ˆBFHñ!h.ˆz* cQõñ1;#Tæ,N¥oTc%Jù†9I9‚3£ »2,²©\,:V&U8³dÏr‰¤&eUQƒŒˆö¤ÀfMb Q²°›†=pà‘² mXb_‚s¥…ÏXæúD…ie*©‹\ȤG„-â=Àaš…[4fP*<•{¡õK&×ä`%ÀA*K¶—Ì‚³)-8´ÅíOî˜Þ™Œëý$€‚¯€œôÁÅò€ï}øÑÞõŸ¾óî{~òI;ßì<#)—CYå™GH 8ôu¹KO³ ’-æ"Ü¢d]ß®aaE_ )©=%Ë>?B¢ô{ÆsÃD!€=1 ‚Z¥/å=ç6JâZ¤ü¼seÓg¢¦® P£˜wd™Rˆ[~Út¸¦ÐÕæˆEa„ByD€=n¤x3ÏD­ŽT Lˆñn°î«æ>Ê‹7Å`ruEñ%D¹vœ‚ü"·æ8üÐ"C¹TÇ[5u·•DŒyͦn—óã¨ÈÑ êRæ™xuf}Õõæ„qÊf°ž‰»á µJ–¤î Áê ~\ß¾s×8ÔL3Ⱥø/-¦Û|ðÑÞÇï½ûλïô³vNF²ÁŒx%Kàå!ÁdXIÇ׌O^*Ï ) ØpK­ môÇ#ƒðBD„ÐxüJH0»‚# ðð$¶Qº!|aôòì‚í,Ô™^ ó§­UI^EN¨g{Zlø1È€ÈT²JŽnÖòƒÌ4ñ>ìÕ àÀìß œÊTóoh“Rš¨‚oXúìB€!ÅÈa3’®äF¡4VxæuôóKBxÔßé™¶¯^Ùë…=ôd!¸!ä!1hÞ¡™ˆ\ìàäx3LN½F¥S%üf3°EA :hõ‚ðTMä’§Š0deéq2žX%äÞ~’ºýqŠEŒBm<Þ¯Ó3®–°Õ²_À´©½Ÿ¾wíã½÷ßyç÷?Ükåk‚@VR~Np]!`£A#ˆ¤`Ù@H/h¥Åb”L°gîÙèFrpq€ÚruLŒq%[$“/B%qAA$›è@¬l ïì€áq2‰äž’ÿÆÁ‘@¶—b—¤!Bc8Ñr—^Mdö´D“Xî § ›åðÜü£V—^MIž=œl¦£ Å¨áÓËWrÚíc¦žÙO’‡Ù p6#a5G$¯±ua¤µ=72D¢°Q¢0÷Þg˜=½ü)ÿ5©Åà¦9¯uaVŒt±¢ª(ìïqsÿÞÝÛV‰[’4á~ò'û&~û“$•ÅßciýŸßúûV w|ïÃ÷Þû¸ýÓŸ¼ó“÷>¸Þ&tNÙ¯  ó 0—=A…¬‘4'àÎ¥ÏÊ.Ó04@*’–Ûóê»I†p÷ÂÖ:BJèÜqVÊ(cœöq·E€·Ì)¼áˆê v¹¦(U13…™YPã 5-’cJ Wù‡1ŒCÈöžA& ¥ÜXâðMâš0ݤz ˆÒò™Eú\{Ùõîs7SÅ9ƒJMúœ˜ö¬£X’÷ôöÙ‘œab–WAo³°D:òÆe@P>²BÆÍyŒ6d´_Ö4žiTPþYC†J›¡ÊaL’Õýýý›“{wîìï«—B`09sÙO&Ô€_¦ÿ­/¿”LÇ·wý½÷®¥ ä'ï¾ûÁGí´!Õ;ÈÙIÈ^>›TŽŒòq1å×sŽXÐM5GD«e½‘‹V ²8×úEFnxò0Y8—Åhh¡É bÔl<²õvYº%ˆÍpÄQÈv˜½gN˜…µcÌ[E¹Š¦+çhÉAëÉóåè÷³9¬¿8V÷q  Xåz«Æs¦ Å2ÉÐÙaŸ«Êçj/?Bs]eÓ͵¹ Ž¢Èâ‡ÉPj(ÏœÜ S'²€Ç!(o_ˆ¬8º›”fdLuèÃÖ±q½Üõ˜›ž½¦b暯 ·ÓVJ€[J ¥‚iBf¼œFŸ,ðÍÉÛ·“Ö»mÝÁß4&礹¿ü¯¿üeúiµþë/ÕÂãä:MÚÿôýk{דþôÃ$€ L;g¹ÅpBì ÔÀ6•@djŒª–áòÏ7 SÒCëŠþu@³o& ·F˜Êݯ2çÇûùY»VgµXÖÜ’ž«Âòj‚PI‚)¤¿ÃB²ˆü_.Û¤(P`èŸ$²ÙÔœAŸƒ#¶BR¤/*§2fÞ_DEÎp„žI&‡ð ‘·q!ïãª6ŸD=³LÐôc7f:U=¼„Tugg*ˆ‘1ü;{£ã%ì¬å‰£V3pvBÒZº‰Å4öŒìÇ»yše¬íÄšëU›h¨þ褑²Šx¢Xs®U¹@/²~tö°Ð§‚ð9%XÌ<hc.Ç´Ü|,IáIlÛht•ü£-ÖÞè–rrŽkÊng½è™L—åîɾ<@ƒì§àÈîe(~õeúwëW­VúݲÇ_‹lï}tízûúOßÿà£öÚ“ AJ’8u Kï†R=Up!jÉ|¢`7ø‚1#Œl W P:Žs¢~(¨½«$rvŸ}Q ŒÙTŒ‘¼68—¨e%ag£ÙmºËAy?ê1fäcÕr¦`Ûði‡ñ¹è wYô‚ú¾G`·€bÈ2±UŒL €_V}Eî‘Ëä±Á!•ü*FBÌ8Qª`!ÄW ¡«‘ôÀ¦!²[y¥W$ V'J‡Ñi´’R†îÏÀ_ zÜE‘¦Ñ«ÀOÏxF¹qHŸºN°AÁL‹óì;BPåa[j‹<êýÉÝ{û7SH²_Ãöî›(Úv‹ÿ†åÖ-ûÅÇEò÷Û{Ünï}ðþ‡Àýýiö…)ÜReT4L¯ž:2°LD·‰)Øp¨v’Ä”GJN ‡Ã€KžRŒ8›Ý rùê›ÆzÃŒêm ôEŒÖÀ‚i¥º,'EèF¡æY€‚7`tä…Ï¡¼ú=õcNÐ|1V”ôû€láHê Ûʱ­1‡äb-h/î5"ªÌ«ÒS:Ÿ+¢±”¨ @jùin'¾q˜ZÃS“d:ãȹñVª³¢¤+ ³X|Á2GKK ̆vÔôIè=B+ÅŒïsüQ’K5âFU•U¬9…³*QçœÅ·]HAHò¡þö-c® máQ•´V¦RÝ»víg×?i' d Å•árît,¼›¾BŽ“¦Ði¡&wscˆ¯éÔ<†d%R$¨aÉ“ç«$YÄq³ñvŒD ß5dÙ Ö”)û;V%IAðdbÕÃf]Ÿ<Ûh{»½³=¸` 1;—M /›(±Ê…ØœÍ °­„âà»8 X%7”­izoQ/½6ODZ"£ «OÇ\ÜAV§(%cô µ:ª¡³T&ù©õ3zW½Ã`\¤¥IÀákÓåàŽ5–Ë1¿ n’`R€4lR´ÙHéª!f!÷›™f°˜A$1»:²9ÞPÌ«E­¹-8ƒCI•å2JžŸ ü[O²‡^8+Á‚Óƒß?Jó¹›û“0¶BI{{µ¿Õ¾ra}£¿y~°±¹ÓÞŸP›D4Ñ (òãûˆu¤L7 ”ãB…‘ís‘e¶h ÍðŠ,¥DæOí•@ÞÖž®0e¬@AК¤–±²1S„–j1·äôµq>•ù¤téÆLÁP´‡ò÷`–2æ€n™ò‚LdÅ+ë|Î47´T%Ù-\ÎI•Mb&q5ýB¯aè’M&7¢ohJ™®Q3yÕ”T¶£3ôªWË1óèâç¡«`s (YïÌà% Ùd¯0ÍÙ´3 Ë8¿€l%éó|®«þIG+HVÉ\ÀyâJÀ^'á»=߻ǢJ²ÀI’Lí<ª|sŸIo&x'Iᥠ뫫ÛWÚíИߠ~VÉ"-A¹AÏ\[I$ÈÆ±ŠÉtô’¨„oTÞ(‰¨' ' @EÙ(uÊø)L(Q …YµPÁ³ûn94„‰«8fA£áâqÖóâæ¡ÈYæà9Ì%²ÑǰzD…ÔRt,3ÑΜ®'lTÕ$gJOÄsFK”ʼnäĘ8—óÐLz½)Ú¢¤;¨¥UíɶÀìD Ÿe—{å‚z4›É8§ˆÊanÑ{±` Ìò<âÀú™ˆ2Á=ï™øŒŒŽ5—†å]ÖèÊÎÑÝåI¥‚IMk²)¤@ÄÒ/uú]OögA­V#€&6É6{û½ßÞjï¼Ön_¼r´ú¡?yt%|Æ0S«³oZ(to‚e«1p±¶L3´Y;›\Œ‹ˆ% ‚ý*LÓ¨¹3¼F¤ò°U¬=$¢7®ðC#ŒÍÇÐîüÊØ°LÖ`@ lÔb“èNy„œMˆ°Ï>Z—}pcG=á”>©èßÖÔòJò¬ä÷•­ÔÄÉ!S2‚ŒãGб ¹Å/Òò2€.¥¢jε¶ÏHÓjtç*àa•2>Ž~­åãÉÆˆ‰Õ Pë54µ¦)=QN==)0t„Ì,à›ø¶ô>笌\–ñlÕ‘ß¶ç·÷ÇÖ–iª/}Îô›ŒƒjÀ,s)ØiåoÞ¼¹ŸðÒ—¶/··¶·/_iOÅ.Ÿ•Z³×òÍšÅT^¬—wš1ùð½ç€×ÖÀ²O– ´NKëŽK‡6Ä…dlA_4$}¼ "q„™®”CkÙA^­Ö€y¶A =À…dGÉŒ¦Íą̃X€;‰òf#¹tkÎÝ(‘Nö¹-?Ë o–Ð* ¼ˆh2:` ~e ÎtØ`'œtP6šI—ƒë|^ž7ºÈpivˆRö¼„¤Ë»¤œB“!#¨»4UÚÔ,eÖ…ú0 xm˜s¹¬t “HÙ¤«Þoæ&åü›mûömCN˜ŒÜß¿)µ* øeë¿|ÙjL0½5×n—“ööÖÅ×lžpÿ|Ü–°¹“äMJÿ‰ï¨[…Ó­ þ"*!®ÖdæU«@ÏFxà ²Víp¥“ »ió¬‡íp#¨X;s#xŒ3 žAfvb£‘HÍ–AÒdÀÈøÛ@«\‹ø*Ÿ…Œ*y‘a¬DSúPQåò.#V¹X £©’8bSªŸIgÎ)$ Ò@9ŸÂIJĈI,ê+±Ä‘øj„þƒõ¦–ùíŠ U™y²Å†(øº5íƒ)`²¯b%|\# /`!¢vç4B׿Žx}$r·9¦F0–‘3!dlVð·ïÞÛ¿sÛhlݤ{ƤZXþÕ/[­Ö/ù¸PRÛíl¶w¶.´··¯¼va;Œâù+í8@|Ö}J ª¹iFI{¶`*wâ)¡D[¦õGo°2ƒÔÃç `Ä*™+I_„¸Æ\Á‚ô±„—¨nëÆ m`QÜ"تĪüÖ㚆ש²P’þ»äœH3a5¡î´Z`)7øŒÍ¢rcÇ8Â~gA_=»…J+9Æ¨ÒÆ„hÈ͈ …S`€ýp( IDATàü%>›¢ÜɆñ9WsãXØAÏ»‚n«òAxèá˜ÓÃQãaÕšâ˜SßHzt‚T—e¢&g*]¹a™[䘔ø!ð( ¥sÒDôË’ï—|À»–w³b†˜Tþ‡_ÿZ`«Ó‚‡6ß^»½Ù[ï_¸p%¹^Ÿ¤f Ã4|”ƒJ˜*áÙ²¼(â«t„Ïéç1\­ qÜeÖIüO™ †¬')ú¥œpé(Tþ²©[ XàïìïV{}°±¶9ØÞN¶øB›91 °ñG Öè ›#f%”ôá¼Ï|Œàç,j(‡èIG¹Ð\Øý Ê~3›ìbaZm4Dá=vHá÷–Fl„—ÒnFŒ£=c¨ÁÜçbY%G.Ô ~ë*s2–l"Öu±A¿fm(¥­Ã%ž?JŠ Ã—Ã&Âk±8-&[Ûå°†ys j#¦N&«Ã•GUY¡óêQš¡p{µç0U ©i ©Pj¬,27LUQ)ì®J'v;(G'AÉÐgô©e÷“¨»2›L¤¶IGÉžˆÀ‚ˆS9U)¦eèŠC‚Ýøîíz¿–§¢1½fÖ€¿¼õe«ÅG¿‚bÙŸ s~°Ö[퟿x¡mq€ŠÑ¹ <ú\ñ"qaæO¾yZ5ðaø­éF#0þG«ø®G,d”œ›T†¡Ùb°]‰¾¨@õÎaN A0Ái¦º5-n Ñ4g_Óh ì{É$Ø¥èÉcæ «̲ 6ŠEŒ™¨O·ã’¨†µŠeÕPÅLxåØBxšŽÌ2Ò&óNÊ–Ì%AÌ„‹£úRå`"Ä Hˆ¸ÅÜ ªö@Àgšk®’e¦Tg&Üjû`´Hô´«4¨N(ñ \nDT©¨C {Ž‹hºhàX ¦¡WœÛ§ª"‘(AZ2f-gkrË’hÿm¦¬0ÏtƒážnèAms(Ül‘':“¹à¶)dèY”ªæÁèæH> 挖jõ`C[=Hý jD¿üCNÓ,F#Œ·Æ°uEs’«àÇÕÙM'«žX=\M˜ÖuGèƒ@ù´€xÀ¨–˜Ìà2Ö8K Ú¥.‘èܬ7>æb«æš>|R[P©"”u• pˆ(È•Fˆ ÅN FöPU1ÇUh´©ôíèD¡*t´–Sªrïy˜¹ú¹eœ™$§¹1ÒZô¯¼üªÝñò6b3È+[æ"Šõ6úìwy…¿"èË.Ô´²” ÇäKÎXÎT'©0. 9¦6Õ+ªÑ@?“ª_ªTa?è^$ hY— X¥¶o‡ö…¶‚¸{«‹ €AÑÃ‘Ç º™Aœi£b}ÖOpG€ó¬lBƒiòß’‰TÅ!¹†k¬ŸÃM·Uˆaèñ5€´MÈo¡m9Êrdyïé85ˆ§2Ô¼­s—$­_¦07 `¨Qe’kú™™k¼24BŠª,«˜q5Hs›÷i¾zDKV#/Ì!µù¶¼nI4)qœÕD¡>fÈĬ¾3(!]0*4Z4çÅ—Ïø… ,š¼ Öv\ü¨žBË|C®r'LŽZal8_Üԣ̨ÙT_&ð!³8(†b’ŸÏN!?K"Ïæ‚לâÑ Ídfâ4™4nTðR+šÃ'5i7a!èS|ãð¹ÁÒzCåélç“äPXúwb=Dœ%NNF•+Y¡®øUL ‘sX+ôsò>$ìÆ6 mŪH‹LzeÀ˜Dc‡j,Q›¯”â 4'(ÁS8[SfÍìv¥ð£¶ÑÙt¹ÂF™¬˜p.€:«2Ø‹íBÈà“Á”0ÚP;ñ>8…Ã!³\•tó(1ltsðµ¤`˜ƒqä{Ašdß ëZMV9§u‚ ¼ËˇÂU¨rœì”PVåG®›>Žœ†„7÷+g#_ß.¨skº}#ÛD¤ˆqþ¼(ýý\2ê½¥¯º,§p|øÍòÍòØå‰Å) àÚÒ—³gí¿ýmMÿž]à“–=m-õÚÃΧß,ß,\:Ã,NIt <# ÔYI•=Kÿ; &Nsí͵¯,±Kg—函µõõô}­×ív{«kxüÍÏ¿‡Ÿ‡^Z[…ä·{Ã$NçÓrÖþ=,@‹|Ooš8Ím7xv‰Ê³×ëéw Ÿõ²®&™KKwy±³°°¸Ü]ûfùw»$!XXèd!X]&q:—¥•D¥õ-Ö²ÿ->ºO[[ý•ôgkkK¿·¸ôðlk£ÇO$\][][YêœÅÒ9·¶ú'úwâèÁ´<í§[­ãoÑ6xðÄÚ*MÓ.f×HúÚÇ}òpZœÔÞŽ>í7š9~6ŽGý'û×jÍ?ý§~é\–¥¬°µÔy¬í,5¢y¿lmöLÖZ­ü»ù¿´ÕJÞŸI }b´jËÊ"MzgáìÂÊj/½ò5Ž·l™?~|¾Õzä:{}ú3ßzè5Û`Úb/mÝþίÚNî[g¾õµ];YmvÂ×ïûF_e;ÓãOŸšoi£ó­¯w|÷·ã8GOðóÀ +éâwèÛ-®@,FÀ¯( àÖ¹­µ•NÇ$¿[[ü½´ÕëtÒ§¶zÙ§/w,`Y^>—\ÎÅdŸW¿îÎùüæ`s3£G­s|~þnƒ|àµVÚž½>ßJé÷ =›YÇ^ûºÇ¾©mc'3Û×7ÚüŠû˜=þ–Žu³÷Àñ>ÅÏìy{Ô9úê?½ÌïÒ¹åe“„Î2Tðñò±Ëˬv6{gÏ™ìABôô›/à™à¶§.öš$oµ»xv!KwÅ–ô·÷”?-;9››ýtÁùþã^ŸþàäÞÿ·—¶ ì§ßýôpvôÚÓ³öÛ·¿ÚÉÌö[öØößßüjÛiŽóx‹ÇºÙ{ðxŸøç¾ó¶ùˆsôÕò\j{¼pv±»jrhº¨kzé†|À?°,Ì>œÀ$_½Þ}¨ß|È?¦o˜²]Y²vûýþÚòÙN „V;IMvµôžøoçÜž÷ûöüø!³_Ç»égúɚͼþü<ÿæÏÏ·ÇçáuÐë›ø Ù°-ÛoüÒ~ù¡n÷ùCÒò|z=}Ÿ×Fò½î‰y˜ÓVZ;B>þ>÷ßåñ7Ûç7 ýôœ_äÐÑéΦßmöøñžm&™à¼½£øFóûýâOôºœ¸CGyÞ´ºÝoæü¡ëñÇ®U·› dgq%=èœ]^K"Ð5YX‚Xüq\¼O§ØYCÀ‘b‹?&€Ÿ/¯I{ýn5½Ø]Yfº¢øT?ýîJ:9ÍóãxœÎ½Åãû5}=ÿÕúóvºí5øNóve´-œø ûìŒò=}¨Û=x¤Ë­äÝiŸöÆ >;oÎ\«•£;s¬Ú¿Ž¿ÙþÆ}û²íÃ&ÏlsºŸéñçÏö!1ÍöðÝÓ>÷ýmëøÚØÞÈûÃñæcîçWŸî:u)vðþ—WºéÒ¯&è-J×–?ÿãð>c<À$^çÎÁÏkY´‘Ämc*€Æ ˆì^2,\Yí.,®®¬,]XK¢¸°dú™æø ÿn¬$)±8ñG“Ðl¤³µ‘^<Þ·w6ðv~=]ùôzþüsö²]œûH_[ÂváÊóûÜ dKó¡þ†-ö:vÇýÚó ½±2»]MßäOÇ?ÝþFwæmlmÍo¤Þ°íà ÛÏqîwzüù¸ÓËým/½×š_Iÿ÷ým+&a¶BÏšýÙfmuÛ^þ~ðz<ö=ûÕKW~iu-ýZYY]\è®®HÍ/ýç'ÀÙ4L²¾½í°Å»=“?`¯ƒ˜¯˜~Öé!9›4_À¥åÞ¹•Šôú¦ ;Ý•sOû/üM§gý("ÈÖÑ•õt.×íõÙ×7^<~ ÓüÙùÖºÖjé>³Ïž[Áïé{Ø2¶ KØjñ𚶦7ô)¾–aæÈÓU]Ï[žÙ÷ì7:·q¤µž¶0³Íéw[yð¨·ð^³ {Ï„üqßzÜÍÑ5ûÃ7äêÓõžê áWR€Iíõ{IVÎõ–—’&MxAH¯óÙÓ à²üiÙ³ Øe{Úá xå,ÐTàJÇ2€éHÒC»/Ï®ö×’>±æ›þ•º;zàù¤±ì>rp¥»qäßn^7Ítü°éû|zÿùôRZ‹šiª[TaÒ€ÓuñzË^·Ýͬ§7Vf¶ËC0ÝÒ¿i•Ö€ù86îß/4m Çc;K0sü:-l¬›5à Ôã|÷qß_û6Ím+t¡ùšï ó¡Õ±¾vôT0]ø¥µþj2ödÕ$À²s—¦h1K¯ùË'½™?x45ÁvÓ,éOg‰Oø|i‰/ðÓ€ÿ©Óéš LŽÀRrå¬%ot}1ÇSú€é§"¹Ñ'Ì™înœ˜?x¢ß?z°ÛÿÖáä=wè8®Rózÿà‘ ¼ÏöIŸ<œ>ÞO·MœÐ6Å7°é£¦uíCð³¦:r Ÿ~t¤k3¿ÂUÒ¯™7úô-Ô°CØHﮤµxÜG[/¼ðÂs‡úÓè;Í|£nÿða¾ÖlsúݦǯÏ`ÀÅõ{®5_2…gÏšìv:ÿI¸¥ÒþJ½mÍüá»9y‚bðR¯ýóÎÂbw-…!лý&:¬¥h¨³ò#¨?ø·ßGIáðÉÃÝÍþÉùôhþd÷ðáv!Ÿ;ܵw§¯Ÿ„!{žïož“Þ8Ö;zô­ÒO¿§o@f<Ÿ´U¿9„´û9îDÇlú½ú›Í7êÎëy¼>Ýæô»M_ŸãÇðEú/:ÔíÙ¶íû<úûwÝïM.ïëiõN‚ýSEÁIºkƒæÅ>½±•ÕµîâBççÀ­J¦¹,ª@@ÑÊ%Ž–ÞÀ'XÚíO;g;‹çÖ»ç’빘$°¿ñÙgŸ~º>€U^Zý:ù´ÍœÜÜÌù3äÐ67‘Úy}ó¾üš>ןnã¾<àôõ¼­™÷°™õz3ûÜ|pI›u›üÚƒÛ¹/÷Ð~7ñݦû`Íæs}ƒG~äg_ß¼ï{Þwôý¾bnòá<àêìí`ýÓO?û,iáÞ²ÉÁ¹îú¹Å$Ÿ²¢ÒENm.qtôîTŸdiCÏ„`š7EßýWߺzõ³u“¿ÅÞzïé3íVµ¤“ˆ …=¶¿=fðWù8¿ÞËs†ŸUƒfÓJÇ&>;]?­wß{öïñ‡ëæ}òxì9,b2‰ÚצީÙ>sÿ¾{ͺ›y›ùµz³ûÙ|`¼­ÙczÜ÷O&ûàÌë÷íozÞšc¾o_OX Y·˜·³²þÙÕtÙoô‘Ká¨ÎJ‘7¡—‡0v‹ —™|ž-[9ø$‹õ¤üœy N'ýï,¯¬ßxó{W¯¶®~š$0Iã×­«þÅþ£Iü³Ç#’•=ùo±û•d.Éß§é’_ýÞ›7ÖW–;æNl|À$mÂ±Ì  ¼A àAX“  Ø,ËIþ¾ûׯ'xõ³ä–öÿt¸¿¬ƒóü÷ç>ŽÇÝ¿Á~ô§ŸBΤÿ®¾õú_7IàòŒ8H"j…—Çb.%pF*¨• -ôj«Å¢†Wg¶Õp¬B¤;¨È¬ßüÎ_ç{oÝ€üÿsö±œÇòç>ŠÇ-ÿ¦G xã­ï¥Ëþæp5ØÎ}09zÙËëAKY;¥©> =MÕÜ¿´†¡¸Ò% 'éÀ×_‡ü-,,õþü¨Ý§ý9qŒ¼G½—Ìéó)XïqÛJkäõ Î÷ˆulƒßùiÑ)vX€¾þºY`Àòº+ò•†¡˜bŽ8L¦ß|¬—¦¸=+€9U˜s5JàY àÂâ¹~AÇro0Xo|nQp:²åõtŒÏÞÏ vïà#Þ·j^ó| ÖÛ˜oµµ½Ö|k^ÓZÜŸ½üð½Ÿè‡–¬î;XÿüÆp¸> î¬tûç’(J³¨¤_g©ÉNÓ@º‡=é–7 àç¤öúƒî"ò€ƒ¼ ,´8ó³´´æ·’ÐØ·GøåþW[ö'8 æü#6¶ÕÚ:ÒzÔ'³Ágiô- Ÿõ—ò_üžyœ©ÀV+ƒ ·¶–·@à“ïT lôño€JHЕüʳõ¯µµ„jÛþ=êý–åqõxëxKën®µà6ÏÒöŽ´Èß¶×™n ¶Ó¶ÿìßúIÿÙ¯ÁêâÐ0g×Òe74Ì*pq%©ÄåÅâO'€[ÀsýÁºAò“´ h®hÇð€ skƒ'ª¬ü¥,7/µÇXðÔ`t/nμÉ5’<^Äšéõ‹¶æñôhë¸>xñH~È5þØŸe\¼Ãv,ü4<`’Ã.¯Rò'À¹ÛÚº?cŸ(—Öƒ5ƒ@›’ÝMû^³˜xaq9½²ùlþ»„. <b/="ŒnsäeºæñVZ÷â%[¯_:Òº8HÏ/e,^ZßïâGL¯sƒ³{{†þñ×ê²e^:Iã­/Ÿ5Y0\XX6©X*ÿ$PÑ0"âûÓ1¦ËsIãm ))Ý8ŠôÂÚÊòòÒ¹îà™]’H·¿I`ÒÿÁ¥–ýJ‹ÁèìÍéš­ãÍS<ÈÏ.=ð¼}ÆiI¿Ÿ;À7ÿÈŸ±¥{niy9Éß`º'™?4%%opýÜ×ÀKÇ­-´·o{ 쮟O1÷ÂÙ¤×ÖÖ’Sšlðù$‚)0Ê«gðßeËJxùÈKéçÀù—¹tùÒуçÓωÁA®yéàÑ´–I2wùðamaþà‰K—í½K)êHkzŸLk]N?.vøØyÁèøÒI­xòð}ÏÏëù¼bñΟ?|øØ‰Ã‡æ¥_8tèlðÙ[x©ÈIŸ[^5B/½Òïº|LspçqéKJ¶–¶:úGs¼4õ MýÚæc FEnÁêÈIþ.<óÿ®\Ö¿‡¿€¿ZóþÇøäý[Èëè“Óg—/4kèÝgë߃/ Ö%I{kI6×üTC 3#  Ô`¯‘Àž´â¬†õÍ . V €ˆôúà—-ý\x&ÿ^Á²s£®l¥{´cÒƒ+ÍúxGŸKës…-}ëá/V»°ÅÍ`½+Ø××vÿ¾÷Ã÷ÞC+ Ö%‹çVM6×C#€‘¿û8‹®Sï³ÀÓ†,½vÜl]ØÚô»K……N²¿[öü›Ÿ?¾¬°ÉÀR·!lFØŸûêKï) ù1íl;í?ù¢½•nòFQÁÚbë¾Çßü}6ÿ>î½G¬„ »Ò3!°çƒ~À'’§öS@ò“  ü‰Õß,ÿ¿[f…Àð‰ä©Ý~2Hþ–Aòm®&Ë9 ˜#!½{äk-ìýo–¿è¥,óé|³,w|õ$Òqú§^]¬Ó4òîIEND®B`‚fheroes2-1.0.12+dfsg/docs/images/screenshots/screenshot_castle.png000066400000000000000000004745441456075706000252360ustar00rootroot00000000000000‰PNG  IHDR€à,ÖPLTEüüüðððèèèÜÜÜÔÔÔÈÈÈÀÀÀ´´´¬¬¬¤¤¤˜˜˜„„„|||ppphhh\\\TTTHHH@@@888,,,$$$üìÜðÜÈèиàĨظ˜Ð¨ˆÈ xÀ”l¸ˆ`¬|T¤pHœh<”`4ŒT,„L |Dt<h4`0 X(P$H<4,$ ÀÌü¬¸ð˜¨èˆ˜àxˆØhxÐXhÄLX¼@L´4@¬(4¤ 0˜($ˆ € xpd\T L D¬àœœÔŒÌ€€Àpt¸dh°\\¤PPœDH<<ˆ04|(,t$$l d\T L@80( üôÐøè¬ôàŒðÜlìÔPèÌ4äÈàÄØ¼ аÈ ¼˜´Œ¨|œp”d ˆX |LtDh<`0T(L äÌøØ¼ìȨäÀœØ´ŒÐ¨|Äœp¼d´„X¨xL pD”d8Œ\0€P(xH l@d8\,P D8,´ôü¨èð àè”ØàˆÌÔ€ÄÌx¸Äp°¼d¨°\œ¨X” PŒ”H€Œ@t€8ht0`l(T` LX@L8@,8 $,$üääôÐÐð¼¼è¬¬äœœÜŒŒØ||ÐllÌ\\ÄPPÀDD¼88¸,,´$$¨ œ„ xl`THüäœøØŒôÐ|ðÄpì¸`è¬Pä Dà8à„,Ìt ¸d¤X”H€<l0\(üX Ü4À¤üüüÌÀŒŒH¤Ð”¼„¬xœhŒ\xLh@X4H(xÐhÄH´8¬ T¼8¬@´„àü˜üP䤌Œ¼ppœTT|<<\Vî¬ IDATxœÄ½ksÛVº.èÏ’x¿âÊ’H eÙŽ;q‹R$[‘-ßâtœN§{ÏLÕÔ©I©¤vúÔTÍ|Ù}lõœª¶#ëâJ"J¾H&öÙ»{ÿYR.î/çœ:ýHàû<ïZ E_ú²{_¤H€Hxð¼Ïó®w-iM´6ñ—˜l½2%&´ >ŸˆMD+QLãÑHt<Ò;Ñ+¼Å•è»mÄ*¸Ñ}‚nñ |Nï§·ÿìíON‡ _»R»³àuKi#ôkbØ üâp8Ô3 ÷üý™i¸sg>!ì?ÉårMü­+ª$JŠªª²Š—9UT0©² Öh=š4ý‰¦±p(§ÝG·È8íZºßŒDi_÷›Âãaº‡{8^Œw>$z“Úñ•lŽ} >íf$øØ¿æü Èxx¼ûݯ ‡^™^óV°½Øâ×Mýýì7̲éÈáQÚüµð‡ÇMš‰ød|r‚CiãÖß4¶…•H°™‘J3ÑhØê–6°û§jb‚Á8øà-öÈ¿Ž½à[­)þ#¦[÷ƒ·»+àá>ŸjO=hcÍûô„zÿþT‹Íò‰^ÑÞ˜bÿ†oKÐ6°“ˆC`Œ¼x8_?…{¦»‡;oF[m¥çÖ]ÅqÐ[w$öž&K2aRÌÆè E¢­ÝÕ¦¦®©šœÓ¦cN‡¾Âï•›ô +´kÇîÆ%U<Ð=³GZgœvÛºñ!;,ãQ†Ýp%2N3t˜*QöÇïÑàVWÂá >*À:Ç NŠñq6?ΦpÏ-Äïãô;°Æ_·^à€ëc·à©¯¯ïHçà¶&­ÖOF¾nµâ-`¦5‘ˆ'ª1vì*‹Å¢·bÑÃ_O¨»µÁX/¿…í¾X€RƇK´¤:ÛXF—bKUþº:¯N,±gš›ˆ3Ðo‚o«{Z 7E¿”à÷`ŠXަÿ_šg¯9܈%·°áïÿ=I›Ã¾x"ÛÀ‹°ã£ Hd£‰C¡{vE7‚ã…©JÁ‘‹9Zc=§­ë MUyLñÝœæ®çšëÍõ5IRW%q4›z’ª%jÄ”ºÚ\Wñš"ÊR:†ƒz³÷;6*õÑ7*Ä?²Ã;à|ˆðIk†9Ê7|#Lx ¸ €"DèªD#nŒDnvÃaËÃúø§u˜9Òyb3aöwËÀ8Ή_ýÐÇ8ØÇ°ÇǦ~ ;>Õ8À6Só±ÝÉa?O±j”Â(?z± 𢣥x®Ä¢116"Çè\ÂÙÄ£26 w:ž´a7é]:À¡à@ÓaDøß¾ðÍ0ö¾/>±Ùåäû-°SmBì±[­žu1U_}› ã*N~ÖÜŒ¼8ìñ›7Ø v-Ûvj±3 ·¢´­´¥ØTöH§U¥âQ ½ÙÌ5Ý5­±®+cYI^ÏÝÓñfNsî)š Pà¸àä4Ÿ`NÑ›òºÞ¼§ ¢$eS±è %·(~D :1Ø™< Ä^ô«‚pH`d|ÇC/Q‡g@” [?ÿøgl/r¸6êþUø—ÐfE¾½É09ΰNXïìƒÿLãÃt’v˜OúÆ™´øSÁ¶ÃxÄ}ý]Èõ@¿ÇC0—hñÁÅgfa$ÎdY4z«ÂDXöÖ…m.ËbVjdh_V˜¾aSœ=L2×å³´±„ïä@ŽìnÔEÜwsœä_ŒðZùø—Ÿüü㣈Þd’–9Ep ±»…oyÌb“EáÚ‘  8ð8˜ÌƒÐ{oAxe!¶WÙJ¼?E‡ÐÌ”áÒ7U–.QÿÏâ¢i, ì >˜¢¨7õB€#½ÈÓÌMòß-.š y+Oq®Âη0tø¤°zggÓè†:3Ý &²@x“„(ÇŸüQ îO³hÜ~8Õáý©HÎû°ÑS\[í­6_…£o‹³ÍÜî†t¶}S/näËxMÑw'&ã­9ˆÕz*ä×hhÀß:PVäøÛõ'ÀYs­±ÖPê²âkk9_‘$SD|dM]Óõ†­8R£¡Ü²ž ¬+Ù©¼Áûµ ü‰à˜äy¨p–c`¦iœbàzBË@ Èðvø“OÀ_~ÈS)‰­ÂöYú|ía6q³^@û±/᩽ÕÉöœSèm!l­)zEe©î'ÝïÌó/ÜD\uÖ×uM#ú[oÜkÞÝiŽ¢È€$Œ†¾ž[suWqT_’}ÅW}_ÎŽJX,"ö: x\)«u)£dÕY–8ZhrˆÔPÂg"ÎH¬ ƒMÜB¯þ,E3þø‹_~ü´ûÃçסÀ¹Ž‡C|5[þë>èׯ~8Çyä(¾ëfTúú^ÁŽë}=ò:¬vr,Ú i<³¬RÙÜÿ`²ohcÒ)ĉM–‚•Yä€.ß`8g÷ò»…|a¡P½™ÈÒXy“ %,Ñq¿.óÉr“ü"yæ©Hæ ]ßt¶à+ð\e0Δ3¯R6ßE[вŸ@·!;ÉôEY±ÅÞKÜʶt²5Ù³´õâ<)ØUuLúAÓÝ£ûºëâ-*¯¡Í&$ßš£4Ô5¼¡)`@¹áø yÝUï©«´Ô^“JŽ ’*§I›Ò®ž’åtÇþb¶ƒ6;Ü`áq¾`ÁYgfà—¿$ üÅ'vâ#‹ÁÂ?9õ÷xV™¾Nžî/QÛK¦¢›çëà?÷µ,dà+rîÅ]kÑ²ÊæÞ³âÑ“oäc¤RÈ~‘$¯ ¿BùJgÄØ'!zWY:­Š‡ æÒHœD78;jGo²¡,Wþ6ÈpÅÞ±«L½ó$Oé½lº ×<åÂülÄ·l½ Egé°+Æú{ÝÁËa±G±ý+&N]Ô˜þ³Øëê‘t¸À!PrÂ3s·hVÉØ7¬hn–%½…CÂò`<;Fi ¼÷X:,‹)zÙ,ÂÂa‘ªAª b—™°›ì`Zzœ>6J;<µcBŸ2Õd…Èj³üeÀ6XŽu<Ê“‹ ,€Ý· §L°U7Ip¼§ÁÁz:<‡óJs€Ž= tZ^PQœq9è£á[Œh‰Ñï1o«Q\cUÊú }]×›ZAØWeY»Þl®ºª"4wSÊÏÀÛµ[[—Q’3¾”î°.)ÝÉÖæDÐîÉÒäüŒì¹ýêx ; qþ~ñË_üÝGý/pÓ¿dõª/0Ý@ç¬øó°{Mt?"ŒŽ¦Sɘ–Ó’IË€—Øûh(ÜÁæi(â1ÖÌüÅú*Á{Äoš–.î»Ec߲̅b$h³ ò1Lüñ\e3nr%Ì>\H(œ`~„èf Qˆio|0Bh…²Ù<5L²Š1²…•È·aîÁIG³ÕqÚ/Z„:yXîaH²à=ð 7ú4Þ° .M$–xQr:·®Rì¥Ðë®­çî=iäô{ZsUa=|¸\k45">1Ö_Õr‹Š¼â¬ª+ÍwU…En6V+êúЬú¢(gÄc^‹D8©ëÄdŒK¦o°›‚¦‡Næ7`¿ `> @xÈcý}Ÿ°éŸüüg¾Xÿ&l×»ìFÛþ?GlÁ¯zMõ!›Í”s¹ÜQ]{C•Þ䱓Šüɉ“J§ý“R–t@ª±[Ê=ƒ¿ª¬]•NÔÊYü§tü¸$”þ·“þØ)Q,ŒG·ÙˆSmŒ³„.…bZ²Á3TDQ8´o9n€&ÞyçT''ã0ÀtÕÉMæ ™Æ§º3±LœÏ¬jh< Á> uéaîg€¿¤T`‡Ì‰êE輎>;åþ×}lú TÂkbo¯v=2¦°I:.YIËJY ËLXÉïS懿OYûI#i&¬dÚ,}˜†CI•Ò©ƒ”i&Ìý„iE£˜H˜EÌ&ÌÃ0cûñسoTݳœ\0 ߌE7˜‘ :¿ÞdÇ9Âv„Iž›<ófà«°ÉEYb#QOR}A‚ÖaVa'¸âdÎ •<ÕÅt7*Ý|åkjü7þÜØñ2͆¢5H?]kæAàz.çærOš¹&ìªã: §á‚]x·ÑôUj§»×DÄFÔ^qV9U]Ót}ÍVtխƈ¢(ùb&ÛBX@4Ài·g'6ÉÞæ< š)˜#ÒOëòa!yÊñI@?ÿÅG=h:LÐñüÝ¿–ÿJŒþuFæˆ ÓÐà™îïèúÛ§Ÿ`ætã-];ý–«ÎáÖ8sºyútãèz[?­ë¹·u¼‰¹·ô·›šþ¶vlpæ'g4Þ¤ Ó%þáI•›QVrD`ÄC…%™ô£†gß&ki‰OlDYê;Î+ºZTPŸè8Õ­Ö V5¼M)¥Í .XYÙNåæCøaCao}Ñ—ŒL<¾‚Pº…Ÿ~©Aï­R0ÍÜZS¢¯»Orz.\£ttcm}–Xk:Šþ P×5WWõ†*–5µϲ&75ÀqeEVêŽìû‚%¡,ÐÞ= ñ^Qफ9«îûkë@\rsM¦4McýIóž¶JÍÀõœã6µ÷×›Úºßð›ÊzS‚‘}Ñ—EÐ àfPƒÙ…à$8¿ú›j+q+ÆöEæj'ûUá)*ÝãE Ð>…ä¨KÄÂ#ž•)&ÌX_¢//„ú»Æ #ٺ陾®3鶬ušÔº«¼ØŒû¯F^ç#±ÝD %ªg?M[EË2£Â±±±ì±L9[†IΞÌO³Â˜ åÕ±QQüäSâñ“¸gÞ8¥œRߢ)ñXl!’§ôEt#”ÔÑq¼ÅŠŽ˜c˜:Tv[Š1¥W¥,ð$kƒâ‘èÕâlY»ÛFÁá6E oT󇹩fœŒS[ ¯ÚÃàÑ6ïýDuÂN=«KUjynMV[Õ Vª8ÿÍdr’5Ž{÷P[ž£AU€9‡æ°\®©ªÚU„Ú¦®ë×ÁsW5ÕiB+6¢ñZNkh×õëŽÞ\]×W¯õ†¼âÊÌ¢èK¢ÜI9Bf°°K¥q¬yªŠ=DÅLT*·‚mºÉKMI²D@h<^0 £xÒ°~W,†Ž% óFÚ,Œ$Béž0˜ƒž¬s{8„ÈðtDÌd øÎà™L&•JDM³ß•~ZJÅ-Ã,hX%ëXù؇e3aÄÍÁAéC kX–ÌrÉ´þg¦tüããdzƒgåÂX*a&7''á&É,À%йÇ.ŽSžu‰•ë“‘f¢fƒùè[ŒóhŸOÀö’ÃÝľ‡,„ýÝ|ŠS÷ÚÚ‡mo÷©-ŽÑ`›ód2dˆ¢#C}„;änYN˜²Ûß2¨Æ+A• Ë—Ó¡OS8ýØÖõFC¿¦¯!¨æš{:äž ð¹zÎEð]S4fpµµ¦ÛÐycp㺶ö¾s½¡ª+È'àÂu‡¤âuw}]º+”eßWýLVÚK”R`•Ød²XAóè+DàûŠŠyñU8ôM§6/ÄmI*‰CS Ë ©I«” ùÄÓH&Ús¬™ Ügµë?,UîZã¯Ðû§(ñw(7$•¬b±„ù`ïÙ³biÑøìÙb|ѰvMÓü`aqwa~waT_x¶k,,š»Æbqw¡°·[€ûýÈz{ðTZ°’bì°lmý=Š­õJ©M˜˜`Lø›MÞw£²DÉø¤„“›“] ¾[/wÙ î‡ßSåMq“ìV¥ú+רª‘§=&x‰7¯‰g®à·IÉÄ£uçýÆZ³ùaîc­Ñt1‹çÆÕ5USס¯kZ³±æ8 MC(Öu˜bhA}]?¦j™5]Ó¶¯iººÖÔÙñÉ…Œ ~6%‰­*ï²àü?‘ Âá•Ov±r–NW‘J§TЍ}džËËf5< YqL=)fg…rFìë$†_<Þ„­Þ´\O«ÄŸÏÑü{P’†t΀ˆ®érÑLŽF c·¸ž+šFiqwÏ´>Àcqaq´Îî..<3öJ{¥Ýg Ï Åââþî®aâ´OK‰t¼OMú÷y§ V¡Ò©¥b½©S÷YI);~ã;žÊõ@}ñD§/ SÝ·x2ˆh¥Za)ÛÖË`»@só…'¦£âÐQñ‰M×âKUª}ŒS£ÿ–*á{3^MÐ×aÁæd%&Y{3³à“­µÆ5ðƒnj ÷ºûþ“5ÀLwž4MÕ\õý{šÖS8WwׯçGwÖ4—\‰ººÖ¼î6êPsV|¦ùú²ò¾"ˆþj=ëeDAHS¤_¯Üú?°^¬ì­Ò©”"΃…ªŒóZ¿q^?˪VŠ…b*ŸÌD†F Å|~!?-&FúóI!!$;¬v˜9>L÷÷÷d¶_,•ÿšŽ—€=07¤½3;;–.'÷Íâî"µ°`™¦a}hîŒg ùPàéùüÞîî¾i–Ì=co×(.îæA’†ö©ž.ÆÅFºDÅûMmñRê.4µÕæ˜éV;áXÇ6+d¨¾«r‹7×i–˜!Ýä}2b¬:‹Ò}¬Tp+ 8f|É.NÄ+8jTŽÈêÎ)Æ\Šò~ølâÔJ<€7ï°GÈœ\b%Pý‰Íåç$Ax¢µí®pkˆ«kÎ=ÊêÔ—s@ejîÞ]wÕµ{¹Õ'O`z¯»:@zmµîÒm¬+×®­çΊ‚ˆÜTÖ$ 8ê;~ÙñEèìŒ ´[KÔðë½ÅãLËŽwËóùí0CÇèŸ,#lŒ ˆTÒY627ŒBq ?œ¢CÜu ÜlôzÐþþÿ´½Ù{nl‚ì„`}HO[åäh–Œý…}óàƒÝÅݬû–ù ”ˆ ecoo7¿g–,kß4>Û]Y,|`<“ϼ“Ç>.Æ^­#i½Õº†58¼›Ü+ÜŒU‡)àp§ÃÆø 2Ô0|3(Wª6`—U‘,UƒÞ+x¨l²ÜYå®%uÑnçÑ Ã(z[¼ÏÞ$ÿÉ›/HÉÉÍV}ÕÖ×®ëîbé:Eãµ5ǧ´³®ÓìjãÞšûá>ÑêpÀÞu(Âë÷+êêº Ãr½Žì¨J3çJÊš+¯ú’—ñ}qØË ˜•Å,‘rµÕ†ZÀ †?vZ0>Œ±¦¹p¤“z zcS:e&’fÒ°¬ØpæwÅxÌc)#܆~úÿƒ±xeêïîžÆCî| ¼!bÀA=WΔ‡ 1a±¸X4?[ØÛÛßßñl,îî« N2 µhîöñIXølwW9«AA€µ¸úºOa¶5}˜£ë­RÞlU'6©˜ÈŽíÝ ¤tÜ?ÝyLaäã•[ö-kžàmÀã¼^0ʳ8d©áAu1Ç™âÞdéÆNµ;¸´ºT]Ú¨Tx+ ÙêK:™˜ ª²H–Qa^¢}ݹ§;Ík°õ{®K×ÌiîÕ\C]´Ö5ÿZã‰ë^_m4®¯][«»z½~Ouš0.Oœk ŠÚ×Võµºü¾ÚTW\ßWšu_<ÑK ¾Ô𠥬)g¶ÚmÏö½VŒZzX'üÂjl’1at¼ÂöEè›NQLƒ¿KZF 1(nX…7E³ð»,‰‘, ÷‡}óÃ]ˆð§õú«×<"JžýT3…Œ1Z6Ͱ¹{ÀÖî æáLLb»Ý¢´ ~4ͅϬŸí¦±»øÌ/..Ì¿­é©df4VØê–Æs¯ð §òýþ þ£×ân&XFU VƒNPhÔÈFU[¼ï6F•T7y¶äÆ1u«²±Ä+ämw„Ç ²8™›ôyñN“2•>$˜ÝälJ„®õÕ[Ô>K¹¶ UC8]×€½•k°ÍúŠÛtXõk~C«¯6TlYW_Õ®­º«¬I®÷ñRpÕlªÚ“õ†¢^óݺïÔ|yT=Áо°êûeAv¯^óë«;ìt ªñ¸—5òÉl»Sðºþؘ ”³ÇôrVR´ÁœXÎjg4¥œ9–N‹‰D ¯ï%×1ÐS(ø‚.ìï1$ÿëÀ@Jd•rì’Ÿúo¯3~€’È$ e£õ7S¢5:VÂe˜Ÿí›{–Y×}d˜ˆÂ–¬}pøÌø Ü·,ô¾Ü3ɘì.ŒÌÏŸTO%­rÁŠN½âr; ¼­©€·z|êf§»z+9ÑbÞ€µˆBëQw›(¯)¼ÅÛæ!û­)ãªé0ËooÜŠm,m,U'».|«Ó~äòX€Mv ×?ËöuÖ·sR€GñSnUÉ{ _sï¹õëà·Æ—ÚÝ.7®6Vïkuþ„ Ÿ€÷ZN_¿æ6VÕëMç @{ÏÕœuõ:¼ñšö¾ó [2 IDAT$GýçVVäë¶ï×}¿¶LF؆ÄšdûY`¬_«×²ÉNú‰Gï×k™t*›êvÁ ºÖ °Íg¥hä …ßL …üBhx$´ÐŸþÇÈB!?œìKË–”:¬x€û;ÝÙþ Ì PÆH$DÆå•Ø §|ª¸ÐdYÆi%D£I)•L¦c|q$g…Œ‹½ÀQ¡Ã€¹ÜI3meF¢ý°ÞÞbñƒÏxö÷J%D`ó`oÁÚ'äý—äw–™þÒ2–àŠó‹‹Ÿêï¤âÙh¹ÈóÄAìÝ⩘© [åýÞΓ¼Ñ,å‡wq²æá ó;]Q_U*åŠ|$ÛÑ(w:òR ëC²9±‘Øìa×*“qÌkƒñ‚ÚÃà©J]¨¹ÊìÿXåx‹X4±I5ñíkõëÍUuõºÖ¨7ê¾ë®ªW\§±vÝ­¯¹M AYSךõ'T ÓPzëõúZƒÚáœkTŒPoø× ëš~ïzC—jñ©1˜pç{²'”/+ƒ ˜Bf´4y´Õúг ŽlÇXOIÞi2 Ãq†Ãx¬XHFò§ +SNP&Ð(Œ d Äàø@_r8OÉèCÜ™x•çÛßz ¼§þP,íÕl_%O2Y!ý™…Si6îƒJ±$9»˜N¤RIIeU†Ôm·©ñ[Ê$2Édèé@˜u<"Œ ˜ã-!øå µo>[ŒÀûZ?5÷M«|äH¼¿w|~ÑÚ3J)kÏ*ŸYû‹ÏvA{{{Ú©hš…|’ ŸÑâhë¶Pô yœGh}o˜ûÇç-Ùúh4Ë¢±±h€,‹¦öΩ¸YÀx öR€~«Œ÷»Î ¸S8 €GSÍóq4¡íí¤bŒÐ¢L Ropj þ$Ud†xìÅi‹+½ ãFœF°¡ÿf-ö|`ìÁ¨çwàmÀØÐ7Yk51-Ukwâ}_¦·ûZojˆíªëÞm®]]V/»õåë&(lXº{ x媿ÒpWõÕ¦v˜Ã kîû`Ë{¶¦«ðÈ ¹éø^Íψžì_ömÛKA âpÖ¼Œ-f¼Ú]Ú}’ 4ºöe¿nׯ^Å;@j;ÙJD_U€FÑ0cÅSq#ccr" V,D²F̰ò}‰ˆ˜%þ‚%x%ö¾”7éë{šôlÍWEß–\;•D!Áh2Òò ú¨·Œ¢jŽíÜõ%©”Nzªç¬EQ¥îYª¯H5_Ê&“Iø|’”UŽˆTŽ5Ä}0µ„ŠÅHÉ‚ü>¾a)Ó²g}fYf ˆûÞøÄ*í/0Æf©„wŠû‹Åg ÍÒ)]³äb,Qf»ËK§R@˜¨z”J FdxI©mL<¬7pn/Û5:5D¡C¡\C‚zèˆ7*‚‡Ã%ðtI§*¡Ú¢Ag6bAðÍ(v‡WººÕ8A%%|3€Hê?lxúrïÁ¨}uµùGc\±/Ãû"×ÝeǹÒp|sáUW[S­Ñ¼®]uVüËT¸î6W\m½Þ\Ã2÷jÝU¯4.MYÑeÛ“lÁöë—ëõ+¾Ie2äEê6í P G/ëË5o™øï®]¿b{‚-¦ÅLÖ‹¿ ÀB±ðO‰¢Œ¨/㎀'ÃHä5ËŠѾÄS53/ ¼vý4ª^¸ƒTF T tš‚©¾þX[ñU{Ù¡4“ˆ­ª:VÃ*Y!Kò:ØSµ•Ñÿ îD ÊÙÎB_bÅàÑÁAÞRÈ[ÅLißZ\x† k,>4 ÷‰5Dàg{¤Í¥Eò0o•ÊV©¤¿£¥2Ð Cþe;Qô286L%\ÑX5Î;=$6³Ùòêˉø €@‚žÿØóÁì5¯3%#Á¸O\óuFk¢¾Â¬÷&e'‚!·X¶˜çÿ*Ô­›{´Ÿ˜¬²òjÆç}T}äËýµx}_W‰ª¾ˆw¥®×/¯¸~£q¥~×m\…ÌsVUíʪ¯5´•ÕÆ²¿ê»«.ø¯Ùl¼¿Q×µ†ãÀ~\ñ¯6Vz†Ä^öÜæ²ÖX•ë¶RÏf½¬ïþl NÈiqûJ„á¬]·3BínÝGìõñ¶»\Çáô²™´°l{íÐ ¤¥ãYõÌQí'‚x,­å>Äl9«ªgÕcb:$å¨"‹Ö¿~à£Ë |Š_SaŸ²8@3Ùl6•¶±@S +º**$1!.›>Ÿ/ÉŠ²,+’/‹¸yét*ݶÕ#’t”Sà k )ŽÅÊùÒ¹X˜_Øÿ©ñ™±8¿hüt1ö{Ëú²8´oì픂¶¾/R‚3¶Ì½Ÿš¥ƒ’þé©h6 é;k¯Ì®,ûAdͦÓà¾/ѪN0‡ù»µN 9âÀ‚žCëã¯ÆÞ|ìµÄÁ™U^XåcTQÃG˜èŽ_euø¬±žeúx7*–F#T.U6»u1•`´©Né«ãêôw ÉùŽ‚¤V.×]çrC«/7œË®s±qÕ^uGv¯úuG]ó]ÿª«º—¨§9D敚áVì†æ^nèþJ½~·uˆò&<âÕe‰‰úòåúÝ+öln&ƒWxwEûò²ëÛ—ë—íå:1!Í1‚±s„d´‡ÐR%±XŒ‹‰‘‘‘á°68˜¶ŒbÁLo$’¦12Ü|gx>ú·S`8ýQS†s’d@êÀ“—%ø¥t?ˆdÜ]½.…kD CØMVk©¬Êt´Â/œ‰®¯`Á*ÉÂL625èEÿˆ(qd-!Åø‚ýlÑ´Š‹# Ôø;¿»°g|·g~W*ý_C°ÇÖÇ¥©ƒË¥¥ƒ}s¿”)•²Š•Œâöà%’Ë ´{³ø½8ÕSñh"Ùö—ësõegÿcv$H~SðÁê6c2Ͳ&[ÝDN0Và»ÔnQÅ0¬R3~œJX+7©ŒÛä W Ôÿ4ÀÖËp㯿é É¯è« à3çúÎÕúåÆ\}µ â_u ô®Ösî üÈ]˜’ëõëNcµáÂÇ^Qº+ê 5ÿ^wœÆU¢£6ÝúŠöÄÉ]µå:¶0ãyîeçªë,;¶#é±ÝFg$bólýòc{Ù­ÏÖX ¾Œóú"4Û£íXß@<ö´¯/d¤2Y3“)¥Ì”UûCC’i˜FQ)YHX…áùùÿô$ü·œ‡þ"{¡h题õ¥Ç’äùí lD†È@Wìt]_ƒÔ)V{þ²Ûá²zI_ÓWq÷¨@pg:›öd[–H"KÃP†ZBŠÉ¢5¡úwçwŸ-î’Å0‹–õÝ>dß¾_ú ÷¾·ŒréKÃ,}Ïbsé#‹•c¥‹¥¼íÏÝÅ©ìÛ 9ŸAD í6Ú.˜ô²{‰6:tÍÚ³Ø_——¿Yœˆu Ë6"r}–¤ŠM»tg6íK¯JÆŠùßgM«ôQɺñû%£_P‡†T+U-§“ÉRÂJñaa°0|#ôôoß@‹"(œ+¯¬ùăn}OòႲ³„@ð‹²ªæ‚átD‡° kå‹Úcÿ1ÕªQˆ^U›@¢ëŠÔ™Íb£dŸBðBðÑ¡!j IÅÑrÊúõÙBÑXÜ]Ã}°k<³Jæa½1o•¬`„à= Öä`aßXX|V2M*ÇÊ–RbayÖž»(m¸Òi–ïÔ$}Í ÅÇš«8ÀhÝ›•6a"z+¶Qác…Üä9Ö_4 »ÜX®w¼}g@á_°‰màÀX©»õKŠ|Éuæü9Õoø¾s͹àRôUÝYßt]Ÿ/Iu ¼ZWÁ ÇVî\C¿ª:6‰ Ïwç,=âTáBhy <ÓLýŽ X»¼<ë³l~…]ämc—Ö¼6ó‘…´Y0GY+™N†#’>4¤eâ¥Lê†a¦2V!Ö? õ Ïÿ ø‹z„7Ù× PW€?àÊ‘ù›~M¼ƒ°FèÕ€;Ÿ©=MÃRWWa²‚,I@žFѧ4ç+U_Å;"Ð*§ÛYA“å@ ´„ä `mÑ„ß0ö̽ݽÝ}Ãü~´ôѾõF ÞAx±ˆ¼Øí—F@“æÞ+ÇJ¥źsáòl}Æ›­mcoÞñ.,¸3[³Ë«í‹t\¿š³ç.9—æjØ£Û€VþÃß~~{›Ó ƒ¡à%)8 ÷  Æé‰„£Á`b‘p…Hˆ éŽlÌFW¤ÎÈa6â¬cƒº WâK›¬ßÃ^—‰kŽŒ‡[€Ó¬ý¨Lv.ú¶ÿÕe{NÅüEß_ñí 8+¢6ÖÔ9˜„k¢lÇ->®ò~mÖ¿zA"Ò¼¬5TçªêªÒ¬—Îìxö,ЯE ¯“Þ£¤ vƒ]¿ƒS˜Ø”X#ÆiËÎÉÇuZÐuj~=]ΔҥH!iF"õŽ£h&4àgš–‘îËGÏÆòý}ÛlwŠyÌ;h„?Êïu’Š-ÄEÕ_¶%x©;¤èÚˆ¯€™îûú¬¢ök GæÙ‡7qŸM‡N´5Wƒ³ HàЯ춄„ûäm÷ÿx àÁ=ëû¢1j.¾7ÌÏŠZx´Š¦Y>#ïïš…½EËì”cågmûB}öâ…YÕW³~í+ßkog3©Œ7c³ æ\¼s¹~i{øR}¦ÎÅ•ç ›~êøyp{ØÚ Á_–&ºíÔ`ÌjZ6‚a¬²Ñ¾‘¹ Ö'³Â2z²p%Àl€×ÚPv0š¹2Q¥ü6KMP“r…ç±Ç· ë e†Ã ~½N°€²¸@¯}Äû‚ZWVœ•+`Á¯TöyÎ&T9̽?‡7±Ó⪰z¨»)KÕöÜ9ŠÁŽs‡»±¶‡/ƒ6Ä×Ú°«lhèìY¾?ê³¾‡ØñÈhwf=[ÌŽùÙÓGsŠr,“S’i^Y˪öý´šM%4îRΈ‰)þ”´“eM•= éäÃä* Œ˜õ€OQD\DÈsTuEWTjßö(ÿ쉂î8Çš®ù4T¾¦S&gE±OÎfå,Q*c@^øNN52¥r<-™ûÖ‡PyϯÅg ‹»Åkï ô!Ã[yߨ·öá +÷¦™Ü-ŒZåÒ~PŽe&=ÿâÅYoöòÜì{ ÏßÙi·…ÛÞyÏ›µ/ÎÔç.Ù3¶ƒâo‡>Úyþ×þóï9d"1b›íóm¯§8ûMè›0o =ýõ@(tØE^c ;dJÐÈÞm+èZ‹§‡æ‚ z‡Íӽ〲B°Èf»€dÎù³~„ Ê2 0{ñŽ Æ¨­þ• úÒ%œâ`@šÖǼoÏ}…hs|”N·½mìUûÒì%û¢}áÂÜ*B‘°¹}û?~÷«/øòùÞ6ÏÉÎ1*$¨WÎ\Ý×fU00Z›þ„jõò%÷H̹¤Ø¾sÏ.Ñ K|I¥MÏØ—á/º8ÍX ØnoŸÃÍ÷Îys…iήπoã”E„!zØ‚v;iY©ðÏ«Šg5 cXhVÒLèÃB_²opð? }OSÿ² œªùš„àË<œ•¢jø#9èÛ€¢¢9we…óEŽ„Ôᆗk*kÞöj€˜èI8÷`S€=hC„ã­?-‰Y/#K1 uI‡t].Ç,œ•¹· å~üÕó?”óÆ¢¹w`}W2;x3~ºWÜ[0„\¹<4ô¶–/Ü0ÿ>(ÇJçüúÅÇ„;ïÍ.{Zv¦·AÞÃ4àlìÑK—.xq²Ûç½ó€gºýü»ƒ¾/¾øáàùù6x+€;ÓXÖn…^ª£}i” ¤ÞÞ }½§¼PçÖ;¬Do¶ÚÀÀÄ6DÂì ~#ÂNÈýdü·ü 2xÙçû–RèsîWõK¶ºâ;øg†PCø»ˆ|Éqf=…+AÈ?f® ÕeHE"x0š÷عOV·±Kl¢@ =³S÷ê3µ™íŠ$IÁoîöÀG'öŽ7ó(mÄ­R:r*Y4ãá|XÑŽÕð)iÓ4FQ4SÅÂ@2t,7x"6ŸüòŸ²"+à9€H¡]w|E'Jé!+T_ R,¶%E¢Ú„,¸){Ë`J_&÷!ƒ0—ë4F„ÒÔ›9Ue *$(imY¦F=¦yKÜЙ³eÓ,IÆP¡xPþà¹ð¼üåwÏÿxŒ‰ƒ¢y uñ¶¸·¸°`XòÑâ;ø?ml×úÎàåX Á÷j³µ _Ùx:_;ïù3ì¼nogzÓm€ ;ðÑLíÈ¢ÆÀÓÎ9ðAûyû‡ƒÝ¾¾/¾88øÕóG·w­q\v¦³möO­È¿aoÿ@ìá¹oPàeÏxÞo?'Ù6öú{ÞœïC´—.P½À¢«s`m%Û;@ CîÆáªsÅs Àä „bÝg‚¨U½Yö©ÞLðêÎÞn{ôaçHŒ<ò.Øì'#\kwæj61à¬}»iÓ?Û¶X„݈˜²™(ü¾ðßci-7˜;Š%½B²`©ã¦™4C}ÉäÑ£ƒÅh6ëK¥R²]¸?œJÄ¢?e-µN®‘5Gòÿ="–Œ´Q(…ò…þ¼”4,_°\²0(¡ùD¡7-ý4AÔ§Ú‚„mßì©T^@ÙøV…€H^DUtG£ö…FÞ”0©aƒ4¢à‹jɤÁ†b¦!q—Îf}8•†„u´Uj‰Ó”2&ª$ÙË’îÊtáž Èð¬ W.>„ ùñ­/¿~üâGýya¿TÊ”zñV.}´ŸÒ†R7ŠÙVŽe%³åt)J?ªÁ¿wñÒW;ömoúÑÃiêŒ8üèá¹íéTû>íˆÁ;`À÷vfk ºö?'~¾ëýjß;7ݾ`ŸØ~8óà!þéÜÃÖƒv{êÑtõß±Y"Y€Ä³w(ëâ± Çõ—GøªyêŒ^^]¾è!ÏÁ,pÆ”8ªM™›ÇØM¦Ë#@"þvÒ(³„MÆ„¾”j_°ç·íÝñèŸy‹Ð¬ÇB-84…‚$œ¥Úá†bá‚Q,œ,ĬB&>1ÄD܈¥à6Ù¡°2†Q¬,2¨$9•6ËE€.ÔI ÉFb8ß_èædúC8w(–" zfž$z¬zŽ×†ÙU Á²£;ª®*¬ÜYå-!°ÂÀ#\\®‡øÛ–Dp t (7C@ö*ì ¢/ º‚0ì!òÊË®4j'TQÃ7°b„¡@ž€sOâÄÊÀGýˆ€¸ûI²¾(•JÿÅìÅÛÿ4= ò­ÎoY+Dz£CÐX?~tçýEûÂöWÞô¹6]ZaúÁÃsçB ¶L¥$[ÛJúüÎù¥vîáù?üâþåÌ_òë/ý?â¡Ôp<ÜÉ8EÛ,£,*¾è+ör͵¨)„/ Fñ5M¦RSÊùBÊ´øš£Ù„K™À¨P$•2¾çeR©tF ¤ A1“N#–SùI@LB¹+*IÀ‡JŠ:FªRæmÁ¼uHò #R,™?ýüÇý>⣾/žüjÑ*Ö‹7á³½}ëÍÁd¶”.ZQ#6oZ¬+SMŽ £Ð£é’Ù>»ûÞìÌöù°"ÐsïAjO?x0ÝúoˆÈì҈Љ¹‘¯· ‡á~ñůžŸûüG¬”ËÇv§§ïŸ»n. Øš~8=“M¶Zñè­p'­ê 3D76l^Ðo'¸’åŸÒºAéUÌn´ IDATèi*¤}¿C‘@4C9¶ú €92„1†?éT<–l·3Ì!x„:‘v=‘R½}žTzñ¨M85Â#Er&.•P^Åó²—EaØ©Í9LnÎ06}Dµ³Tˆ±ÃPITúˆðG¤ŽeÓæ±Œa¨à%‘.èF"]JŽ„5õm†À\šäOY3e § ùcÉ´iEò ÛHòèñB²8Ÿ õ±‹…´©µ¡aQ¢Œ Ñ e˜UB†¦J4ø5eT”UVÊj«¨Ã$@3 ¿tBFÊÒh¯Œú8S™TV°5j¦‚,—ú*ÑàMQÔ¥“Ù1Pê„`b@]ûHЉTú`ÏzóÇd‰²ƒb_|a•JŸq¼åÞF‹9£¤ Rûžš£ ËzG׊f*5” ‚Å–•jŸŸ½ðxæü¹sÀݦIN·¶§Cº4âvŽÏÍœoÿáù?>Ÿ¾¿µõck²5¨ÐUë6Ój±å[ÓYoû\í½‡Ó©½‡ÆÏ݈ޜؘhUo}{“ ­[aåö„½Ê8oŒûf<üÍxä[Å,ô5!ðëÐø@xü›_‡&¼9²êL\i@mVF,4ÇB$7  ,P@Lq~{  o³ô€<ÍͲä$%>°>à ݆(Ÿ…ò Ô{|‡šÈé[¨}A`ƒÜ#‡Kf»}[ ÆèvègÁø´ÛñXûA ;ò‘yã÷*d ™L• Ë‘#ý1`@%™LZIK6Íÿl (Ÿ- ¿/'æó©ÈÈ ÿQ)"¥åÈxè¾Ç ­|†<™Fé‚<£ø«Ùäw_*-ÐtVb@FÖYa55f8tPÅv èèB}BŠÅTªï1#œÎzšÃ>Rm¬j+ëzN'yâ„$ª§yT8.vðÓÁ!ákíŒXΘCê×éô¾õ÷JîGÀo÷óa‡´¾3M%GÍÉ#_ÞÊx¶ÊoÖß|ãä©“ÇËÇÊŸ=;««fwîÁô¹ééíV²õpkú!]í]QçH¤¡V²ŠPd¼£C§³ìªu[­-¾¼ýð|tj:5ý ME‚““Àá+œÞlmNl°Ay©U˜.Õô-n•ð·ìo•H­x’Àþ¾êÎÜc{–jœÈ‘z)J±lß!å?C¢ß#èÍòt°Çà“Á:ém ¶wê²èc]»[¼#xAï‘„©!ÊÍŠ|ŽÝ!Ñiƒ£Àÿ…ÝEö/¹“¥¢µí ¥·³má‘ÐN&R;·ëLR¥˜•µRf¢l’ÖGù'I#YJD´·U@â¯1äÂï’©´a$ ÓLž6“ÉŸí&É“’rô ‚ß|r a>ð(ÍL™<_–æ5U¬Í ÞC¢˜ Ÿ R™Fõöê +Ì׉dAȃ==ê±¹´±ìP+XMf­Æê\WωQUTO‡c’v²ÿ{„ÄÏÿP–\ n å“À­V%FKÆ Ã2 ê£T²Ì½³geÁ42Ѹ0J„e,hZ–Ù~?<ý …蛜º?ÝÞš‚ä—F|\ ‘Qp¼©(©Üƒ«ÖMË)rCNÓE¦ w[ü²†T«ºÁêª( ¿¦¶ôënÛ0Eß°æqY¶Cî K´ \¿>5àÖ©ík¶î׉è{‚V º?fbŒ SXÕ,EL"Ýiް$ÒÁJ䨃'diÚ,O” |žŽ5ÐC†=R7Ë'ZÆÊêHXÞ©yeQÔ´œö¦(hâqI¥ãª Ž Ù¢–ËqÔŽáWŒ‰Ù±¬X©NLŽA£¥¤äh*6ú4–ïHôÇ"ÌHøòcæ?èÊÔcReõ̪ƒ° Z¤v[þˆWt¯“D5ð*©@U£ð-┈AE¢“,ËŠž©þ$#RÄF<}V•uý„vBVO¨~})}R.ËŠ¤atÆ3¹”8dÐ !pŸÅDso¡ôãüÂÁ÷{e³”c«dùs¹ülß2wËÔQs17¨ž0RceÖ›MQ83ÊÊ…L³”zx~f{zº•š¾?ÕzØJÝŸžf—F|»{ÄÓ²&+&÷bÅâ‚úD®Z§^@ø»?èNMo½»Es¬£û& u¾T­DÃãt%YvÙçoh‹o+áqªü&4>\z”ª¬B_·f«kC÷QkÃÛLàµÉ‘ 8k,}¡Þ%7 ¶òÊè:4çÑø4ÒÆ²ÈŽñƒ!—Cœé åe;"kCð‚Ù`1¢gO¶šÇ‘ëy3ŸÏ"Å‘‘âcÉB~Ž8_ FúÚÂñ hJ–q#þà#‘¬eÆ-È dºTÊ,„ò™áPìžB‘áh‚‘ß㯈ú(úú¬àŠLµ`ÈT£Ê*KŸø>•P<Ñxq»9D–A…òc‘’Ð>u¸ôd9»LélXál*Eç–£á)¤|öIE, å¬x¢”=%©'ÄQ64G®s¡Vs¨Tf3ª"ˆ àŸ•„Tâ®$¼´=¢âe¨Ç0öUV¤>#Š:˜Ôt1)cê)`xê„òGEyû¸pRâ Øá™!±Tâ„3Lgr¹Ónî´¦•K¥\°Z®KKÏ>Ø]ü°H}GÏžÕSFI-Åig3)@Z`K¥$Xoì´dr«}?Å.¨÷\ q¨[Q¨ ªÁUëÎô,GìÆÿMÓU<·îO?¸¸‹€ :ÜŒÓøZ7éêßáptó݉ê- áoœ0ô5+7% 8&ùÆò}uÈ8÷³Ïm模×Ç¡ñeÆ` óñÍòø#º"—ðé‘bC åÑrƒ‹q¤(vßí¬áupG¨»ÝƒÁÎ{™>³ÙB!bFcq螘V° Ö솑ú(mŒ S%<‹À8Û“f:ce fª”WnX©ÒGéìhé£b~>=í:Ÿ€OÒœýt™µ¨ù’Ïz]Úd~eàQ!:º ëàK©êû¦64™y_êøF•öô!2ën.`—Ñ.ÉÒ¨×˴ᾜDðUs9I=…P=Fn9{L=®Ž)êÙêq²/Ò عPëÐP©C8ÿ ;U>!—¢±b¾‹FÌïºÊ^×µ°·¸»¸»ûlqa!÷éñt±”MA€IÁr–ìˆe%M£D±9“NNƒÇØ¥õ.ˆ_¥BåÀ~Ânã´s/,§NîïBýmÞŸš~К·Þ-¶ZÕ‰ØøÍ–NRÝýVkjs"ürw7¦ïŸcƒåðf(—Ìš>y;œí°L¦>ÿÌFp†#ãÔǬ…Èz‘s$r4zŒäªˆ÷²Œî‚˜J„hŒƒƒ.ð#gLÎ}‚x›Ÿ² ìà.pd±9-Ä ¥ôh:—S¦`¥­b+å¡‚$ÌÐN’˺‘)eÒ7J‘Ÿ$­Œ!Ü€NÅçûý¡£:5'%ûúÃTéâãGÈ”ÈSUJû©äS%Ù¥–8V¾LÁ3§;ô¬Ù¼NfÙ@ÞÕñÈžN6ÑWÛæ¶¨U6á ŸDD?)ŽŽ•JceëqbP;£iÇô#¾rt(à™¡wJ‡„£kgôwr'R&Õ¸˜©tÜÊ«åzh©`íÅö‹»óïèRI(ˆE#Q“‚â(©Á2ÎLÓJýÞ*e³xœ¢KñÜéIé“Ò''e]{[—Õ?ÊÒ1AQ“NHØ4öTŠ(LS&-µª±Éjts¢5ñ.¢ðºÀà$øú-±a‰&z±ÇK«¬elƒ5µÍ1™wçq‰:j—‰CâV¢÷I6Æ`ÙZ¢@ÏËÜŠÜfPL§Ëé4 ØA³Ž;tlX,æu#·9~ƒ‰¨¯ØowY`—ãa+’0Sf:Y`ìÒFÚˆ— †YˆCo«, Ó“XQÇ 4ùwVéF𻑱Êó}…ùt¤0<¨–C}}j"•Lô¨ÚTb‰ªv¡| kè ÂŸZÐV@}ú*ÑXƒÁIѨ „9ꢂó$…¶Ybä‡]Ÿ,K"V$ŒQÞE<®IÚ˜t2cÏŒâ€×rЖ9NX<X‘ý¡!Î3ƒƒšÙC8gÕSzNK–Ëb𣨡£iÉÒ1…Îû…Q²^8cAÀËŽfYi“4Ydcj‰†9J¿P8&XÖË×hzí˜ØPo$«K4N%‰ÞªT[1G÷>qfj‹º£“; }Ý5Á­v+Ùò<r!ïfüÙǾW§1jÒ2Eœv"' ª'‰IYÙCX»—(ì6 ¸ lžà@©*“ò>å2ÝG3¢ñ4½H$ Ö8ßµ¸Œ9ÒçÑgwM -bÊ;Fì F,BC’¥3©7VºXLÒ+Aög5õmž…1 BÆL[Xè'o¤J7þ9˜Ê Ô„$=;¨$²}}‰¾(Õ$K~`‡É¬²$ ÀHdmêÊAö#çª@"M­û¬=Žš€  *âñ죌Ä2 ÇØ™Ôý†ÁlL{Sʪ’4 úË £07JqûTœxüÄ)Äz„`u°;:–~Z1ž~³ñŽ>ø©~vP?K\øo:úOÔ·ÿÓÚ›êOrjnðMU}K=­©?ùß¹h ŒìvÇØgI ÂápŒcä ¹Óü}I`»Ÿ6Qç£Â½îM¯¹Óýéíé-¦´w)ýîæ»“Õx$nnÆnU[ïnRßÍÍÖÖdu‰Æz_¾ ËLM¸µ yäcHÔÉ•®ÓAƒ´Pn{N¤¬˜•@\À „ã/¤Aâ„_ä ½-Þ–XºAïˆË¤—!ä‰ xËiAaë.VÂ[´ñ™ 7Ø@Úo¥³¿ÅfñN†ÜgÌèvL8­ál-gŲp\>‘ÞҤ옚QÎæxI§&މ´D†õœ*…²(?–F(öÆ3ù>j N„°µ2»¬6ˆR–™dÞ]Ä L ç@û9‚dDïÉ ¡‹z"PÓÓÁ’Ï‚õ¡IÖ©c0þCÃ.•5ùÔ˜˜Qމ£ÇàªæjeJYéÄñ?jê HG‡û䟺ZÜŸ½2Ü{ » …|gŒý‚0F{•I@‘Ë"6KÌR2ÉR™PIC"år âQ!s¬<:*W”»×hz͘Ê÷©õdkkÏS[°ÁˆÃ›­Ö»·bt)õwA{ˆ4®¿ŽÈÃöyïΣ‡ÓÞ,ÂíŽÇûÉËä$öçIË`?ò‚ꪺFo$Å ììX™w„X%Z¢qÄGY€ 莅hQZØ™ÏÚïÕU–– ƒ ËDoª¿B@ôŽa‡`dþ9‰—G¤\Y‡ñ'þV輛홄l©1>ŠŠyÇ(˜…ðB>ç †RŒ/ óøxÞr¬˜òÇŠÃ0òlä›P¤næCü|½[sdWz%VÏ äõÜ÷¹ìsN&2ÏeŸkf%P2=ÝVYÅnQRŒÆ1RõâíEÈ1v„Ý-²21êVOKj6€º±ŠÅÖüÅš²Âò³c"LüùîõíšMr]‰%Š#EÅY7Ž¢XïÂO'­µÛ.‘hª£a£ñ( ]>jPqo´Nù™Íµ©°þѲ©·ã¾Ëeú—liõ÷ðHO)øc—ðW”1h`Ç.ô\ì'× )]&µGPóû¢•²Šhßdö»U2Ç­cx°e×ܹåxý°¿íóM‡û_>-î¿z2ÉÙÙþhÿfÆ~B,½’¤2¿Ê7†c (ÀÒÍ~-nƒùÀ›ºþÄ™:ØU°E?ÜY²)®üdéùÓMŽ;|‡û×C&ø‹ïÊ=:^†Î—“gawºpÄ?ÿÎWÖû’Š;¿ü»UtåWÖ•ŒxAÈ´€Å‘ÈŠt¯ß_ºöÊÚµÅËy°äy å0–÷hš)>Ê=¶¼º1?¨89¢è,]FîÞ_ed°LÒ*˜³a.-SÏÙså“\3¿4Ì•&;ü ƒ÷½1@ …u­wV@ym€IÜÓbKdQš*8e’´½LÑ™H›ÝéM1LÒëv¥«ÃAEj"ÔD1’áІˆ•µQ²ÑÞÛS»}Þi¶×UzaÔÙ±ÊpÈ»”æ_óc*5ù[â`”˽ZZŽ,e¡oÀJm »ÐÌ!ïJ2Ò½~ esíUðØ9èA™>›0Î'¾“;lì8Þö7v.ÙçÓÀÛñœ[örPKç+$’æÁ,âÃîÚ¡O¾ Lží%ûû73ö)A¬ÿ+¿º&ƒtË3ÊŒ<Ë 0ÿžË?…Ûâc“$/ ¡ã3»°ˆ²:îøÊ½mWqV¤óÅw»ÿâ»ßéþ‹ÞwÈ÷J¥üîwþýw¿ø÷ð½ßívÿ×Þ_üÝ¿üò{¿ü15´AÙ~ôž´4xçjùXŸó«%…Háy®n^Ÿ½¤ é!YÉ„QVI§U¥Y‹ïsê»ñÁ¿ayRd7ïOø°>¶2ê†V•i|0Í)šŠGdq m1£(põ‹Ÿýœ²?ûâ;óÝοSÈû~è0Fßû[ˆã¿“9³Õ¸Ç¥¤ÃàiIe€í}D݃d܆ŒÄIRGûœÆ›z‡ÔbMA Ë”Œ†q޼ïÐu$,V¡–+&9ä’6™wŒ”çeÒÅ"Ø£Séìøn*£;•¢¼+›LL#>YÀ‰PvWÀâjr#?– T;™ªâ^:Šò=ÜÖ4m ¬4Ô†²fE¸²ÜÞ»OÄNš CuË>‘Â@Ž"cÐKîœìõWC ©_¯Êg¼c|ÆH[T1H‡º˜5šcJà¸&|fªÓ³¯,†òtËCŽ¿ÐŒ§K:ÈáT–‹(R¦P á‚dzÔ7VÁè…eh@ˆå:,º–,á:Ù|£Þ~|-DVb P$]!|;þÁ¿*÷5™'z¦‚Ϭ3ÎePßIcñ´«ÜóUt 3Õ„®Ç*ãt´K´¦>ÚSl-ÿ×aßµ¥á4ë_’öW#Õ°ï{²Ç¨v*™‰ÁµBÖb)bM F¤rrÀ\. #‡ ŒIúþð­­ü’^Û³Mã*acÇ­axSâMYáÁ:ܳÆXØk $ãss]`‰Š)Ì{ÌÈ@cSM`í2Æid´N31;3Ö“ó(IûÉbt3Y&–fè²ÁJ ák´¾â‚y¦ ÝøŽ¿Ë<5ªŒföy½©–DJš)I[‡ÞÁc±mÈÓTþÅï¬ûå—”xd^æg+[¿§÷è}EBÕ’Ù"¬éÈ“9‚ð0)dpŸß?=åd&§”´ÙËZSÊëëJHÆtHF7•ÂŒU‹R§2ä çÎé{àÇ )}¾<:­â:yt¹w"åà}Ií±ëò,·jC+*óìí¹nP|‘Vê··=jº£6d\\2©•Ê2° ­òmnW…'§¢¾m³Ú4øœ¯ª—D.KÓ ¶€1åUéÛµ‘”óp‡¢ây76ZñÜÍÆÍ+ÚÐHCHúGE÷”¦ÈŒ³„=R ¡Iàô–òXÁàüÚ±ÿë è;Æ[„É@/P4H ·®/í’u>.-'„ÕáVƒWÖÀÀ1³K³ÖÍÜ4©åV–$ô |:V-Yæiž‰˜‹5Ë,ª¯×2C,«rS­¹TNÚ­ZgçožÅ‹ô`t3Yft vågVI„ÕæÆ*’¾öÎÆÀ•m.ܯõ$wD‘3]L`§q¬$ØÛ‰0 CÀ;{n‘|mÆþ—_þ¶G@ÄØ{ÖòÌ^Œ$;ƒ ËЖçݧú^B'"–"®÷eM% ÊÞ݇aþµMÒ@ŠN°ähd³`ˆ²–?qDâøˆFÛùž ƒy®’åÁÿ¼gSꊒô@8<Á1=…/ÙÞ!@P2ø#ß;<†¼‘èæ” }•ihŠfipÁƳ\®Â)CmY¿±`w´F5­Í§TP^¯ÀPrÁQ·ÅCºE‘GYv4ІO¢ÑhØîAǬ\° Œ[Ãáúz³ÝZ}ÙÞˆÚ£a{ØlnÈ$ðúúŸÎþxÔXk´ñu›á |D¥.DódÓc£õ€Ê¤íz”)ö–ÇÄÏ<Çþ’—â î ?u×§¢lpüe—öøí|b¼=fÞŽ;'¥0q&våB{p^¸™¾Ã&ÆÄñjŸ—süv!ƒ•»ìøÛ^iÆÂܲ03/@ëŒÊÈôTÏpƒð~¶0@åÌý.8Þß¿™,“`Ѫ߆lÉôªk1"ž„„à‚Ì\oêÏv¸+2žˆ‰gfj7•TËôD«;ªú…öÅú—÷V<&cŸ–=¨SA«® IDAT>K^$òw-iؾ-{X%aÆß÷m9 Í¡]ýàSfýHŽÌ¡™Æ'K*zƒ ©4&È~cÙ.µ/œ.Ö„g³Òʵ\¥ßéHô;9òù ˆßOž!ÇOI|Ht—÷ñµ¬rr©D“Š•Àb`W•™c”ä) >Ó­OžCqVn‘þÍŠ³0ÉÁx¨–-n>·Úi«=jµÕ4ÖE-aeo=K£^¢ŠQ1ñËëZ„4Iå¡Rkpډ͘*†…ª\Pñ %n c0ÈR#×áƒE‚e‚cLsa±2¸EP¸Ì^zþFüâ`q/vçN&'ˬ¢€…"5•ô»«à “üPšç æP.Ü3s›Áܲ:…Á„š)U+ V;F,ÙÞ U¢QÉö `—ìÃwòw‹‹ä"’ç“a“Ø8ñNù!lƒ2¬BâÔF‘EIá kÜü•` šçaíkP*‹˜ò²,ù‡4P‘{GžË´Ÿ8ðKÓs]1áZÉØÈx)3ìK;¦1Œà©eÙ Euè&+Z ( ¤3òOò¬Ea}ª¢c™²Âü$=æé²™‹Vhe}×|C4#: ©ÓyšF†–‰gz¢v±@÷ Gkn¯ì %=E¤¢ÑÉ…ë£e‚kÖ½[U_$¢Ñ6;ÃÆh=˜þëV„^»’µ¢Ëå±Ü'rÑý‡XƒÃ+׸ÂBa§ºt¾­&ñÛå Uð;tÍq„ž†¡?…ˆð~8ϧÖÜŸóIàOØNÀwÐÀ->¶k™oÇ"»³;¼vËKà8 ÅˆòÙ;»îÀÅZ ®¨eÑ@OSlX½Òդǚ;„U¥¨¢^9…z–½H³ƒälq X-Ñ {$ƒ/¦,Ë¿F@2?sU!XÜÔiY×*˜OÍÔš‚½Z…£jÂs½º1µ)c1Á¬2*ÅqÞ»®eÒ§1YÄ‚g_‚üe™-Α#) ,âPÚÖ 1@@^ÇľÎvÞ—umRwPÇô}™·<:œõÞs]³†,uœ#Ymé­I‰ÇpL/š¨èéy¡J/O y" ™ªF`‡$fðd÷)ˆv_6Î’ï•b„‚½¼—«9-Â'Ï¡@L³ð¿y^†N—š,RY†ˆ8·»­O"5Õè*=ñ€pZ¥Æ0¶$ÖÞ2Ú{Æu1ÖÀS„PÒ¼-4r¤&©:‰DŒžEtœkÔhÆ{F»±¶ru¯±×°)/AeWË›†s_ÆÒIRø«US0A×ñ>r>¢jèãc÷×Äÿ>ÂÊÐüû€ŽêüñÁ$˜lQ Õ€óyLÜwudvcoì~îLªYmL.§s×ÎsÛÏ-ÛñÃ?àt\+8à<¤¥Þb–0A‹ `fæ¼p/ö²4Š8ã,c¶–R¤4Ëj[¦é™±x#=¿ž,£嘄ÀULN›sS€dÉ5LðZÓä»ÊY¦:”¶5±3X›p|xµf¡ØW+û#ü“V(ðŠ-íÁ¼ü,¿¸XÀm8`îÁ1žùÒ½.Áý@"e…LN%TÑÇë”Ò‘O$Ž…â”µµW³Ü=|xÕý‚£Á£Z…–3-×AU+(éCY>‚?G@½CÙ8FÎKFv(ôrè¾G³+€ÆOé5ü´1 ÝÐs°èŠÚóÌzžþUf¡}’=ùiê(,é ¢‚,P‹z*Œ‡:Î)Íñûa\½^¬ÞKT#mEúM"Î-TE¤A¥æ=ý(V¢4é¥ ]»¨ý¨¹·¡Cüªa?t¨¡½5¥õ÷Òú¸¬³¢zâ„`€ÁÉS(vlûü>‹‚/´3±±8pì#Æmÿõ2ZòUñË.,2˜ÀÙµ3vA°¦Î,w×…àŸ_Ú˘”‚XžQÃíÌa¬sâb¶W4f ÈLl0±8ÑÀkE®A dX¼Ü†ë %iºX¤wÓó M–É*5«x–LÎïÆñð¯bÖ¦CëPIXº>a™9¶á™(è•…é䔌ƒ,–·Ü`UNy0òH?âý„ÉDÈOm{i-¼¸øs‘Åçñé_ØœÊØÊÒ‘©ýŒb1ïÓy‡X£…‡Ò‹Ê¦™\"?$_©ëRbÈ"¥9`M‡à•Tžm×LKXR™–ÐÓ^Á¨ƒ–¬—2qx24)Náh96^^Ð%­7Õ0Xfi:fñü¹QšEñ‰YÂêŒâxd=§¼HN{77¥ú°ª› :’„†+ЩšEÌ3î=ƒÖ‹a–Bª?渖ìHÊ"5‹Sf0Åp'±i'Áu‹%ëA€¨®ªè§y»!µ)-ÅwWMçKþk²¤ûôq{,)²ß±”žY’'Á²~´üHÖíSp 0àc7„ÞR5_8åá;»l‹$pÈÝ Ÿpw|ÉAo¼jÇ)\§rKÓ5ÇoQ„g‡\0¤þ—ív¡ëNÑ;(‚ï‘b„2@k æX7Ye³­Ó;,£5xõc³zÛf]hõ'²Ð%ŽSZ4¾b°e/ïOðÎ’Q0P(öF¯Ñ\ë(56Ì8 Ò‘³‰ ƒ©ÁÍ¥°³Oš‚zÙN° ïƒàʤ•X—¹'ž,¡9^ÊùDW”üõ ˜Á’CIX +C>öé%þS(hruSîÖÄûlÙXZ…;aS¤<-8{‡ï†Ü™»Ë 8Zžåè:´‡j€“éºkµ „Ì«šrÅ"ÍEònrv ƒ±‘VfKÔ[åAd(plS ü< Å=]{Lö8ØñˆwÂó۠€5U>Y¼²ì©7qmRÚ¹ë㧪ÂLŒÿ»:âp„K’P[éŸ@â—//–2üÆå*ñc¶9f¿)ÆDd„Næ5IÝå†A¯²™ä~8ŠEÛœ†—Pïÿ1%BO}`¤wx•3S‡OL3(¤ÊJ4°àÜbðõd¤NÀO¸{zr´ràKrÐK™q¨$ŸÎÏ"öaj$0žË‚b·x‹EA5Ôª®î4­O-£0+ì7÷}oÔ…¨xÎ2'míSÜoÔ¢!㎧ÄQk4êí,ÛºL$hPêhm}3z¦B«Q7n[£æÈ~ šïÜPuFÑz´ño|½±fЈuÃàrÚ%‰ha?4,ù)%½mïÔ9¦Sâîß ¢½*`LàÇœù4Ø%‡L½±]ß§,+–{̧Œûe@‚©]ê—>žÔZQŽséN‚[–9k ˆ~e‚1*Í ‘9™—$*xŸ'´šAgà"´—ÆܸͭUîÐãÍñÓûxß²øÀãrÖÕ”…çÝfîÅ܉láYSйxþÀïÆ—Öà77ýÃ6œ)(+]B@ÌÆ)(ìO(;9® ËeàÆ™«ó,ѩݘ Ê’%Šå”eÁJ³;IZÄwÅ"¡³ƒ±n!è¼ ê ¨Ø5¡¢Ô5Láƒë &_±]Üï Öñºü]Š5®ìd O Ó®lð§*2Ó™L+‡ÊÙ“ìÆçâšÛÒú¨¬vöužeÉg‹Åÿ“Ç9ÅMÈ¥‘«€ý.ee°¿Êkª}[R÷ÿ¡Kc™@âËX<±¤Ø?¶ÉUºn@¦ËåPwj¶ŽVVf@¿\ÊT*¾Ϋ§1<•šäÈ?‘™;oi[×9J2ƒR.Ìç‚W¶lt³Lée Kš[&‘ÌŽL•~ÔóŽ(”ÙîˆHôÊt V÷–P™ôŠ^¯×™Ð’H;=~=—ãa é ©¨Žl]ÄGFW)«rbƘóÒ&\#nÎJÇs+R(¹iV•k¤å(,Г’#)uÙàѦSëil¼ BBÄ=à°¤"­*¥fÕª åº8æç­ÊJnê4É9Œžx'w ­øºµ)/ezåÏAù؈„ e“Ó•…Èd¶ì úaU¥âàû¯ÀVÍüìÔ[ù`È6?õ]Ã,þ WÛ–Aq²H9â‰S„ÄŸdi]Éʱ£ò“Žhãlç4†‡( , ¨-P«‚ZÀ1¤:‚Eæ”–e ¤²O¨t`râIJé.© Úv° ƒjÓxnªV©“ ,mìWujd!«¤œ\:~MÎÅðÙ°“@Û*n¢'Š!Ò(U2Mô’NúGBWE¦í7tH9Ù’äws‰ˆu}ÓHÕæw£^ çÛmO(½žŒ­Æ£ýõFÓ†»ÙZS¥p´¢š«J,àÛz²¬”~Òö˜¬cËÊq)Þ£>ÖGÑjîIJ*‚Á1sÇöçMa‚ó0˜;»€5ÆÇÎ̸cžAï8[—NiS“æ˜9“[[5$Χ”áÀ…H3£²+ì|'WA~€x¶Ë*ÊÄ ¡³øhŒ§`‰ÉPžÚu‘ Ä;¡G¶jä¬?.Mš ¿¹Ñ¾^UÚïœs3Ë¿nm L*ÓYñnÉ\¼Ö®õ›¯„‡½úòc_Í‚­,óÙb”Do€ö¯¯éÆ Ãø$Q fÈT#•í1•9ó¹—ÄUn—ÔÿAHÔžÀ¥»x1J…̃Þ_äL¼Êç_ÊejnÙ… í@Ýø&Üû)Ów(zNèØÜ÷\j)±=[ö®S«i¿'ù)%‹ò²t~LjB†¤Ès™)—Í=ž¬Áĵö[Ý(‰•^úQ*D”i–ž¾•é=Ñ{fi÷@ "Xr@žÄË‹^®Š#…bÑÓn*´èWjŠÿÕF{£95i{ÁÃжUÅöà‡×-P;XØ~âs*Á ‡rײ"LÖ$Ò¤“¹O½“0 Oi,ZÈkçÄž€0U¦G3‡lˆaÇsxìº}?;; WºvýræË-0VUcàÔîŸÁÛùX;ÍA.˜‚Â7·2Q¸E–9l ’©i5%ûK”BÍ *  lŠR§ûgÉE’ 2±Ì(WE¤rå¼"~G×vÇïèÈÉsT“§åb0ßX{a@ñ–"³¿²6 ]EÊ|^,Ò¤¬ ׊W¥„«Zx*z!ÄW>ô{¡.jw³RXQëï_\Øú\'uImvزdÅ„iÜÈSÏ”! „x@®™Y°4IöQqHŠGB£ì &Šö©UéÐÄB¡ ᦕv]=9“ráó|*à’EÅô¤ä*`[MµÖ ‘¸¶ÎÍÕ¨fÖdrä ÉeçÀþ*IL’¸§fÊ„2ºÚ½L蚀*% ª§ð·×pà‰ª&𽉛Áø¢nB:qÒ†º± 0b£ÑÞ‹¢ÖÞžrC4ZJ£±ÑPýå) Ú“ÈIçs²?ÝÀ’=ô)fJoëØ;:¥Ô‡{âzϳrÊe¼¢òì×ë‡=HÜŠùóËz¶Å2ˆÝj\1‘åã]7/·ŒÒ(ÇnõöÔÛsWÈ ðܳJ¡¹$WÇ'nf—” ¥89åT0¡Ë&qVP0!OÄÁ»ûç™m¤ÐÍ]X†S.kÒ»H <2eSb.ÝLiš”Pfu1ÑÝቀEQÕ_Y[½ŠÕ°ÿ/ Ê÷3^¸U«Â¥§PE½š<°‡aÛ< ñ$6ßš–F~·ÐGÑuuÝ Æ€±LÏeFöJ¬Ô¥RÏ{o°›ï/#Í1NT)D‚ÁeFôç$1zý„Ñf’SeR¯ Þ‡ÎÀQ4•5y‘vê´Ìs¯å‘w|"û˨ªóÚ}€ƒò•Rþ‘¬œÈ(}iI‘æÐ ÃìÚ7±{[¦ª? pH»Õi«Q*–@=¾W‰2F§ÃÏõb¾NŠ`+{T£ ¤î3Ë8ë)ÀB¡Re´P磸«‰(z¤(Î_2[iFó@k…=ׂ9ö¿  ké2ÙHg`¬ŽÒØ39têñ§lÕ ˆŸªÿw‰´6ŵ]ï>±vìÕŠåž–ÉóCRKÏÿÀ¦’ÔÒâµíSfÝf[Vî’ÞñxùÇWo×W¬úûꩬdFÐè^€¶rS6×X,p7-<ÜP „7ìäo8ç…aZkkÍõ+*‡~à/]*Bਗ਼–B3·ÿXfE©4ㄇ ä¼òhs²1‚«ÍT·j;³¿ßgãiÙ–•í¿3yÇwÌ1hÏÝqq9¿]צ€î/³Œƒ•Üâr:Û*(¼‰­Ÿ«Áå…ÉYÙ?€®ŒÁ­Û”›Ì$U %W³Ç††ÑùžŠæ 8Ü·ÈâJÀ¶å¨ü©&ÀpH£ÄŠÃ^Xå~Õ^‚îúïxáP‡¿kmŒB7c'ØÞY¤ƒ-/-ŒÚLhÄHàO½m¯¹çÛ:6¸àWU¥™ ë•Õue]ÿÆ Ë)‚§[쪺"K“s%*V…7ÒØüRr†úÈp ÿd9¤q79ˆÙOmƒh!ø„AJ–¬Aûñ[9m¦U ].Û‹¬UÚO˜d–²]Û•›”ô0ÙšÄÀ%ÇJRÌ‚B£ð)– Qƒˆò‚€<9ow åÀ݆x”8JÜîÄû-¸×(nG£a3…ùM%ªY/u†Ãæ“v<u¢Q{hGI/zµ6Fí7šÍæ¨Ù\­mh{ë{kjÏ{­t]o¬í5:KYPÿ€{ÁÄ«¨`c)aÐv*¶xèË-Ѓ0|ˆ— ‰ HòXžïXÉ*aÜò¦;á¬ïóK¦¸l§UÎÆA¶;›Ø¥kÛ¦×VéXlBŽxË™® p&ƒÂþm,eý- ´ùh_X–[ýz!‹ü`YÉþ‹ƒ‹ó4þ ¤RB‰™1äÚÉ«¼, ²u6Vð;|àÏ©» B¸ ê²ãÓY6úukãlʦ®5 ?_Tƒp§87 3™¸S?1uÝTvÏ55øÁí ‹;Ò5Åp”‘lïµæ¿ cªö©Ié‡çÅ3lIèf?–hÄçÙªÀrß'[Á‡þ¸€–L“v¶H °òü×U#ÓÐáDr]§Rz:lŠ1ŠÏ S72ö©lD¨ì›Û5³”¡ û}È|‘Ë/¨X6¤2N S¹”}¥0€øó‚cÙ$ÞnEè^øRø^¥¹pKIQ¡Fjuö#éF(Ÿƒ Bk¾7lª¨5j»OT'*X PcÑÚH{MHµ½ÖÚzàFÿ¯C¯™ø…î*C;‚èúrEâ]¶<þðAè?8 ‚£ûGÂþޏãÖp‰…×ziæ–enÝËíþ.Ы§†ÈÒh4zyÁœšÿ‰­BêŽgÞ¥O€9™šÐì_MÈçƒ@…ç<Ëóo.è`FÙs©Lk) ì\œö_¼8OöfÒŽ½ÛÚt7Ý`óvŠOU”®&“£ä…w`ˆÁî߾ߧ ìÊþºµ!sëâdP\”ƒÿ<1ìHd,N¡5¦þJíŽL(¸qz'¬ VÆTË•Z¦>”á·ðk0FqS§ôƒõ4±`I ÏÁmó@tÆa|)»å²;Î>·Ó,‹ãƒ3a_ä†C£9LBA½!z°C]¥†gSÖW4€z™%+ÎDRº’™2BÁ‰º÷eGòȶ\÷¾çüVíx«’O‚@5 RRŸ¬É(”zAÐi·Fi¢èILÁ¼h8¼-T °€]ÝK4EPJ¤©Qà ð £€„?…¹Æq”&iÔ¢Ì0~8…aRÌ&ít½5e¯µßØè‡v+Š{ê^SQ©`W¢ôªpˆ©L ‘:"^â?`T"}„׿Mü‰]ƒö™žaçeVŽK~ CÛzèÏa¼®ôI ÍÇéA<Ü…}OÝ©Ï(¬Pyþ¦lš>ýÜó[œÙ« ð¶ Q| èÜÜÀý&‘”Tø¸ÞR,ØPË…~p~ppv°¿¿HýÝw²ØHªœ»®? Ã>¥7ß›:ÓT™íTUEõ^N3¬A8ƒšs«ª¿nm.]B£ æúbs0Ø1ϵñ0Q  xö¼P55M^$j;Qš|—ê£ÕPuÃHÒ8§aa߀±ì™xI2ÍÔh:ƒ»¤¢"mØšÁžé«ÊÛ9Þ n]™Ù^¼¿o–U©Ñ ;ÐdšæXüG{•^›µ¬\]£1©”÷Æ: +Që#~W[+'¼šÕTH-Ý*‚UÒþîv•vûÑSU‰»ñÇzb€~2yàiàåeœÊAî}žkÆs÷}š€b[v€ý$äUGô(B[˜¥³FsQÆøcUƒÒRbm?ó§õM|%ÉÇvsM‘¡yWž=îýŽýÑ¥‘ŸÁV‹f‡6Š®‹ä‰ÍÙˆŠEª #‹r=r¾!”ôžè LµäñºA©³Õ!º¤Øl IDAT™Zþ–ª ìT8‹$Ú˜Æ Qq õzI¯M¹=umoo­ÝïsGmh­ÆFCÁË“…ö…¬8·(/µª~sìÛ®,êu|Zü I6M&ö¸¦H¬(ÃÂÎ*wÌ·ÂÛ úóê:õÆ Dû§DëÜŸº»|§Õâô(QÇWpöKü€ÎC;$XÂo¿“f ÞìÄ(ĪŸ ¸¿»C\›Ò ¹E…[iªc‘`Ë:%‰æìp‡ï†}ê÷ýÍ‘A*Á±úëÖd¢ðip’¸0ë$¦>‘8IÈ$5S¨LŒ¼ŠP'Dp®š»µHY”YýíoÀÕ¨'jÜ«1pn ‘ŸÇíÇ­õö£fÔzE ­°8ZFUQY–ÞsaÒ:å­a;–Ó¤Yó~¦ºéPzÍX·å0Jê °ÒÑp´ŸD&y`«ú”zœT_T¤<†kð‰w/Ò4¹xó"Y$éÂiq \èK?|¾·¦Âæôg~ùÇIÔé$4l¦ ˆÔˆû™Hl;šy¬k÷r히"¸˜U&κÃÓ‰â×ÁO§”˜³£g©Š÷.”Hž7„ýµcXUuûÔ45šC’`.»Íj#ÛÊIˆæ ±1\8oU•Kç±÷a¡Ì,Pc¥ãï5Š O!6ÙçÞ˜z3CªMýmê-˼TS³Â¬=/¼¤xO=¥´IiÃdøÖ-oRPØÙšfpÁߺÊu¦\vJÔÌX.êÿùA™VÖ4ÒIà¦zaPÙjF½äfž9 j—Ñ¡Š”¬ñ·œÍÓ0Ø&³ÛÝ}è?œ‡ý]JäL±ýuóáÜÊí¯[¬ZÊ‹Š*żBTð­ØHªÈ)CHÇQ ´ÔrW“¹™)”,w˜¦Žz±–} ÆšQ,tKãxD¢?ñz›·ép>ÁRÕ!.w;è´F½èã8ù8yòTמ=0¾€Û–AqÉu*Õ9˜”²¡cGCɸ “« öò"£FÔ[«: ümSŒ‚ Àõ V¼¶/¼ W¢–gí¿Y¤ï¦‹E²¸Hñ!}‘¼»xÑ1t" Ú3í™HZÝQ»ÓŠ; "3“‘á«ØÕtH"àI°ºÞb§¬æ“›ú½LoQ÷!DqôÑ{Ñíe𾔉†¢èk46̬jÔh´€‰ëÚªúû}×6ñc‰•Ë&²äy^ËJ^*O i|å[`€ã±W‚ã¦àT¬L™b^îÔ°¿Ë]<,Ø|•zSJHJ²‚áåVaàEþŒ•¯&—µçWeP8ÀeÙ·€NžÚcÇ ¦á"Ý„Zç«v‹¬.c$%d÷§Ž'«± ‚m¾}¼ÍwüÙñ,¤!3½®;QRyÓ"qm‡•g_·6›ez1ñç?¸x›îš²3àa-‹œ“Q»ƒßÞð;‚A¿6áƒÁH‡¬ci¶ÿ ‹ÏQŸŒâ¨Ù¶¢«Â™(™äy–ùñ:îj5ׇ£f³x@³E|ò2r4²ÚšÑëuÛmÞíO4¿Øc1t‹7ô›Yâ!Ät툸1Š,ÊD»Ò½®µßzÖæEb8Þ®·³Mìä5ödJG½P~¢zêäõze寋`i6If•WpUpçG} ï}^1ëí’Q¨¦z€_Ëh¶aɵ¨©ßÐ&dUöÛ•`lzý©·m”T kÒ°nsª?R÷öšk­¨íó™ºG“*Ú¶´?IôeP˜‡®ý·€.>g€Ð*¦|±*ÕjiY,·ÎŒFQƒ~úXðïP.˜ÒÄÁmv@»ºžÁê„ÐhÌ‹ùŒ/è3ÝÌC¿*²¯[Ûö¦»~߸ ŽÑÁ?·ÌÑWVçnS¡îæÉv¸í¸Ì„×4EO³2£c+ã1›¶J‘ÒâB¿í© ~-X˜¦š¨ÇžsºQ÷ÉhmÙ;š¢ÿºþ{ÓáˆuM£“G *ReùÐÏàÅ”"nüXÓ:OZ£V\Ç1Ó™AwÐŒ°¤p’Õ~^ÇûZ)TŸð°&úZ‹¸Þ:û|„©¬Ëéââ?f¯.^ŠìåË|L%÷zM/È_½¬A…Eƒ}µQÒ½ñqËšÚxÕñˆ¨k7Úâ΋XÛ£nuG¦fA„ ï%âYòxãÖh}ÚÓÅúˆöÏãQ»=jFøì_)Øií=ec¯í­ mþÖÞkkk$Éé0£û4ã…œU@ntÞô³žQE*)dŸO¼ w­ÊqôÊ)KW8¥ñ9«°æõÔ,=÷6'+Ç[ã-&¶V©·¢šz;n56³¬]905†Lœ­wjøŽ[!à*(ìoÎúÐY;/vîÕ?™×ä*I­†=ÎÎåD8jŒç3¶û`ðçÜU¸) »ÔqµªÒrðFÈDt» Øf¦B—rtãÖ–›ØuÛ?Ì6ݵ9IKW›ñ™¬8ÞÜ*î¥é"îú}–M;ì-é)ÝåÐtxrÃr°¬…Öˆ^Èq¥ÉÇ@-Q-:UÜ ’á„×[Í®nÿÊü;“˜%Ãì¾CI%Rª5°Ú†FóCø[Fh`ú”Ž3-k™+¸u:³W]LÒÛ¡ÍæÁ¤J’8gB¶é·ã šã‹Hàf0Ÿ÷ïÜùÑæ§ùÅâÕÎõ-{ùü³pɼVkZæ~"C8òÓŒD[Õ(!ê‰{ZÞjE2û›Þ‹LÕ€ NßêIJæf2V¢F"RÓvTP&@¿—R˜ZIÔæhx˜D‰+ë{j£9Zßh4‹µùð)æF›^¯<Ëc5ƒ*O—A gÊr:Š(+ K6ÁùþÔg¹[UŽ[ÛcU-Ó’Ù©kv ebUc?=è‡Ü“©·-™zƒbÞésý Ë’—6ìûþÌ« gK†aüA ƒÂa@U,ß:©……áFùàZ¬B­\€ekÉn0¸Ñßñ˜®ê4Ø  ”° ¢~Ešnª0(U1LMF •—AYE®™šñi ~ðuk+„ Å3O’]{Óa·¡ê³ìÝn5;ÛÛßß"Y­Úƒ  %{n:ÛËWC¦ìʤz$í_õâõ¨%Þ2 }­´ºÃîÃ;†ïoy9â£õ'qäoA Gw#=Sͤ3ŒcÎ{½x? Dõ:xõúåË—¯éãKQýSðòõËTÑRÕóæý­Às¨WFp=Õ¨ÒÓøÅùËŒ¿ÚëÏû›9ùËW¯òžêå‹lº úÛÜæš^üf:IdD‡ÎøÑFâªDFnW°6è^MtÕ$ôQ Ë4“Ñ6 ?=…ö`{ pbHU3xUÐiQ¬Š§õ…(k·Õ¤«Šø­µVc¾  õqÂý{æ^Ôóû±%b‡ö?„ù‘RL:Í ë.„Ȩa““#ä¸B LßÉsÊMš–«×Ÿ{S›m—ä_ú°À€;%ßrÇ[—SˆßÚŸMiÈã„ê ¨)ksÉa€»Þå-Û̈A”ûwd/ï7nbOÛsañí@h»øŸ‹ƒ±í°¡£+й"‹©8£(á[5dþ~¦SdJTÆ6¶+Ê”™Ft¤pðÄ€›™WfìÖfÄß™ò®-o?­Eùgi¾8XŠ ‘DgggY¥‹*µ¾ËŒ,îÊŽxƒíé4† å†ÇL?6‡­ápcCw°¹kÃJFÍQs¨Ò4Ï;zÖÓº­!8à£æãGÝÇÃ'£Çþ>i==i?qŸŒà¨Û^ß¼ážö“öÓ^sÂÕšSµ(O{öª¯'~ó™ø,½¸ ¿éÅf‹+J™.kMš¦ç¿3ï4½ª¼=š¤ÄàdéþHíuÒ®ˆ”òiOèI L£?EhQäEª–õÒLmF6žhW.œPR¡%qÚZ/"êN4ȉ¤§¨‰×~™IŒç­µ”~¶×úžC·eT(ðf|Þÿ‹-2AÊTQY>ë¬0á4c˜¾gËWÎÆ†“œ9nA plb×þ¹MGôû™+žÚ“­-ŸS°Àèâé}îæI„%Èø<˜]úsgæÝ*ð|ƒ¾ ‡^Y8Õ·€Ž¦‡ÁmÃó¯£%ƒù8K·2ö5Y`ÓÉJZjT°+j )¬eJšv3Y†F¼¼pYžƒ8x¶A‘ïšÑùS¨ÇoX›QX:ã± a<ø_¶ÞÈ­¬&ìKߟe"] ÏâJ¼Q¼9ìÔtZ÷E’1‹,•P¥Ža)¬¼àÁÖ)Íä`ÿøpÛ0ò!9Ö‡ sA¹÷¨–ô1ܪJv±ëè €rl$‘;Ó ÌÕxxÚYZ–xdףᆢi½l–‚H³N¯5zÜ)¦•(S)U‘j¦.g_¯?ËJÁêlœç ãÿgUÿO¯€Ÿ/?{™Š—éKåËnŸýY–{Ñè>£ª_(}Koucx6R<‘Qe_ÚëDê=?{ÜšQ)Ú[Í®9½‹`ë÷ªJÒR‚8ÖÒ(Ž„Þ{Q*ºJꈲ9ÑÛ¤{;ëÖ7´µÀßìšàLdy»þl۹̷îÜÞÜ À@™ûå²  Jizs²*  ŒÙÔ¯Y•̪˚Ž>²wêËàaH£ç‰eÔÞñwùpÏO‚`—6öò û(Ù}/ØygÎë[Ôü}æ $Õ·€Lѳå„ùu´d0óÇû%ì.¶kF§8h‰åËJs]#£+X!Rj£S¡i¡Ò‹ÉÛVõûý¯¤ÄàN0삹+²òÖÈŠ ¿õ‡Ò+÷ûžW$ùân²ÀVÌ’w_¤‹û£3–œ`àUM+Õ ØcÝa eD>140¿Ôt¬0˜Ž:Ñ~sƒõC[oµ××7º”nOº€5jðª}Ïwmkiû›r㪉ÅÛ\’Z3åñÙÅ)]“~F¥Þ,~’h³‹È¢áÇFlQüˆt>Ž£N¢$±‰m³}µp&î~ãÖñ¥,§æœªÈóR†ü«·ª*þ±Ú‚ȰR`KE}é-¥CÑ{l!ê,7°¼iÜ@¡XTªMïÑMM[¼´UWÁª…ˆ4¨ÛëÆâiÆ"™©Ñ”zU¸tšO´6jÄë¦>ðwå¼ÛÐsÆ›Soîo÷ççÛÿÄfÿ|~TªÊÀŸ@28'á JWUŸ^gõÄrªH²íËÉì ßŸ;V<ö¿ºÊ<ô'ÜÉ V*§æe–vN)Ä(ƒx˜ómß»•¬Ádç}pÒqûÐÕ¶;¦®-üÌ*Z2è{ãý¬*óÌ·²ÊŠŽ`9%; ™RÓvˆAÿß®½ß1ºªr'Þï_ò óR{¿6˜û¹(¿am&5óyIåþTq}žZ/3±ÈDþŸÁG’ÅÁþÙþ™u¶?ªîÖƒ`•d4@®r˜º_èÏôàYfh†17ô­(ry'âµëMcéMzÑú£®¢åCdEK;£ž Ëò¢ngÔîÅݸÛîDqÀ 5[í'Í ÿ?)ɳQŽÃ6ùö©cøÿ©Õv«äž:ÿ‹4-ÏžÅÑp'Æ3Ò¿YØggñ½ ¿/Ò{ù¸—²)õuMI¾n;|ó=^½ŠY“—c>tŠf·•ª=ñqo¢é >‰ ”þ¡:T°½{ÊFç"WØ€ñÝKÁ }׈õ"Qï}EøÉÔT„§VWÄÕ«RÜEí…ÛüÎd2±o;ÿl»|Öзëͪ&‹Ú þ¯Û›Ð"¤Ù¤?– Š“ÐóMzùý¬¨¡÷¦ž7½ô\{: ¾yÑRy| • á`àãË¢v…ýVQŠx!Xñvf·t¯Êh€ Û÷!«o»C@<õ&~x-¡pÉþ‚ÙM:±‡Ñá 'V^Ò9y¥3ùAùGúøw¬Î‚¾œ.A:«ež¼•.Îã$;ÈÌúvÿóoX[N SzßÛA8 NÌ¢—e/4±RäìÅùÙ ±wv¦EËxJ¦&µ-•.‹`yÁðÿ Ñ*Ÿ(–øvì‡Kü»±×üÍ+u£õIJl×·]g­E”¯gt@óð Í”sXF™j¨à ÿu¢Ž¼'°ÕN µ­ûŽÿZ³´n×q6O–^’e4LŒ,}¦=!5¢fÊS«XÔqôF"î¾{‘¾™‰·òÜ}ýò• ÜKSq³„q&b{ò:Žöiœ¼EIæNï U@ÒÝ˹ª¦4žŽ ¬1Ùô™©‰{™¢öìÂ\®rž‚ê žÊE/«TÕˆ{I¤F‰ sr(pM% =kïºÍ½íÉ`PƒÐÍÉô¶ÿ©²ÿ©ªg·w–[Ð#ÿw¸3Ýô?V Q~â‡TšZ}>©¶Æ3¸ ¯²9äò-ïÒí‚,Mcx¾LÒ¯"_L $ÜÛ °Öçùè,νþîŸRc:4®56âDIç.5]~èj6¥±¬Ð@Á<[}c°9ÎïUl”8®.@ÿg3{»ºv°2*0!1q¶œÚ.Õ¤goˆEÇo^œŸ§ÚâàîÙ pB[› ,›sÐQ¾kO‹Dµ8HRP=q 0@˜¡8ÿ·ƒm›¦™ÑÔ3Ó6˜š›ò0c ÐŽE(·¨ýÍëÉ»s‡í¦ÊýY·ÉÝpb;ÑšæÎi5=wÓµ±å¼rŒt£i•¯7-;än/z<ŠZkk¹Ã_çL^1R¯¦Ž“Q§î°eêÝ( žÒ¬œÏÞ>}–¦/Ÿ]¼Â[å¯_¾~ýÒ>/îeo¦/Þ¸›&o¤ù²WÑþë}ï3YùjØÕ•]¾}•›¶—Ë~*Ó¡ÚRp/6eT)ûÌñ†Ý³¼ëžLÿú¬ ÉgûpÆ06X_¦Ckr¤¯2WŒyÎ7Çõmwº=ÉœÒéwüÊ®q‰ùóvæƒp°iK¼е­p¶Lg¸Ö> ùÜ)ƒÚ¯`1¢¥^|^QUBµ;Þu©¤ ‡dÎ)r“ùƒŽü:IF¿Â­>+!j`¤Q\ø>îÞyçƒL“jeq·(Ø·€Wd×…–výþM¸n°ÅŹÅ~ðäwìÛõÄ.'4]þËË{o¤ož/Ò»Qš¼X$½·.βóø Pô—g&d ÷$üšµQg&M¬ 0Vbï³Â€†<;xÈÈÅArð†üÖYr°Á6M02+³zŽ¢ë«ÃŒ5VlÐ’‡lÓ;«»ŸZž¥jAßg½QܪP`¾ï€ŽZí^{}¨?Ž=~Ô„.Þe‘1|Ô®+ê£a÷Ùå+Û ‚<ð]ߟ[Íîå+îM„EocÄpmR|<6›x[*Dü ò5~µ`‹‹ êÛJ^¼±Ÿ¾í½z½;Ø­_Ùiúêâ³ÅËôÍÏ.ùÎâõðõ`ÏjèŠÕîÚ£vÚi:tðÂh4ê¾q,b%¢ã?:TŠÿGo@Ìÿ¶%.`5õ””Ƙ’Ç-›ÎȘX¾í£›4&]ÎÙî,øçºög$q·‰ìÕãzŽ]7qjæýÈsÀÞú؃UÙ®Ypg+ø~¤ßvt'„Ô¥iè\ý‡ «ž÷9x­Á© Žê2ú6ò —\Ž ¹Ô#77‚RîÕn™§‹Í?§¤ÞÊ´¼Ð-‘WþDžü  óÆ4aüx;¼ × 6qQËzÀ°§'Û'ÎØyÛúôì JÎÓÈõâ@ÄâͶOܽ8Hßã?–G4¹ÖµAœR’Jƒ fÙ:"!c¥–¿8_Ä/.uáŸS6?]$ggåàO(˜é«¬•~|s˜1“ѱ€ÿ«ö·›»›MPÓÐS“vŒÍëÌ\oØ\µ’¸ÛYo5µv³IùƒfsÝØhn4×›=>nú¯GVOµÕ\ƒWr\u迆«õCWÓ`# ¶ÈGxxdF1MdIž&í狉/_ÆÙK`]v·ð.ƒ ðÞþ‹4»HgÃ7'‹WûƒÁþUª[ª¢ÅØ:z¬«¦!î1ý“,eQ©eùQpžœfµEªÀ‡–qy-‚=A±uFMˆ®ö°M‡) ×Ìá d·Ù|5¬Õõ`{k^T*ŸNh2ÐÜ»=æ6·æúØ’S§™^뜅»|>ÆÖ/ùKj5à¦kobíšÂË;Õ<ó¯bhÉ33‰zÙ@îÌ߸þ©«VàÂ÷wœ[`UÔ —š•éÀ¿³É·€Î¢„{ 1‡y®xÓï׊m•;±]øÀ®‡ÍÅâà®È~ð2IÎΣ»éE~Ü]Äïî'go¾X€öÄÉÅ‹$~ó<Ë-¸àþ,Ãþsì^Q&0k‚íKÕv„‘—®MÝd²Ô±8??Ûÿ,ù³ä M0ô…€ EªçzA9K¾:Ì8SŸ>5mð ~þÕݹ=°ía¼·6¬ò¿´á¶†–××syŠ¥eé¡Q ¿¥ôb£Ïr:6Ö2¬\WgOâ¤5ZoGÆ–?«Ø,sõ^ÞpL\ùؘ;½Nc†7ÛV“nR|fÞ‹)n=ð_Ä?°_Öt4X`š%éÝ—Y¼ˆî ž½ úûöKCUT½Õ튄F22=Ë¢g÷4:w|ut\¦Šïgúï&— ÁK"XZ §j)‚ÓLèl5](¿˜³pwezòšõñ‡Üáîl^×5hÙtk"çË£ª‚ЙL(Hi:»xäv16u½;õÎÆÙø—Te9Ë? Šmæý[gÌœÒö.¹»ãm¶N™¦9Ì©ÐÅ,ø ó#Éä²±"Ô8ÚßÏ2Óݽ¥¦£†Ÿ´Ê,NÜû6ÐQ½®ãÕÎ~®lyû)+àS«»Æ÷¡@Š?OÞÌÀÉè u7ÏÓý³L|ÿüÅ›/Îî&oœ'p˜é›ÉÈÁ9äý?ô yç¡#Ô"£ù¼–ëQA÷ÉäŒ.Àr*(BVÛzGã;8O¨o]€b,$!ÄŽ~f&4Ä>©À]”¯3þ"ÖÛO,oNío¿sÆñÞÀqMÝàVÈ¿¬ci]æÈ>¾µšGämÒ™,ó¦ŒF;`ÍÝÙE@•÷¹®t[ðÈŒÿåìb´Þˆ?¬í3>í _öt[ ’ð"Ê}Üv„#&ÌBem‹} ‰~@ ¸Ÿ÷MT[î‹ B!óùž÷y¾ï]Lfä§®^dºÆÕCÛðÁp؇À›g<:¦ªz"°Ý¢,\ïÀqŸƒ­ÙEWo¿QØŽíB©ƒþcBþËÜÀûsøÔ!*p]p¼Ö/" nB0¨K!§Z!ÌÄ쌿 :ÃNº“¶íøé¹cj´ûç§i[8Ív^ 9—æèêè‹ŒÎÆ›Óñ,Þ9¿Ódt~nJQð5v£Íèüx<†•ûâ<ŒÇ› '=ܰ\3àzŠ:#ÙÒD&Ôò ÐHe žÑL"«™.¦M ¼iÑVL¦s6gä«xƦyà %*P`þÙü÷ÃŒAP)^]þög3ŽMicùÔ¢bšÍ_žÙh*ºn>¬Ì&¸ØÅpYYh-/ÄËgßo(à—ÃÖ™_„­(–Ö­5©H£›ƒ¹& àIöP5ô3§#Õm[ñig-ìÐÇdµ)íV„žÂùlÞÚ¬¢èÅwÅnv Zâü´Švv!p¬Þk –á78-6eþ"—”ziX—×Ô4®(²¦‹ :оè #kíé¬jÒhR¼GÈh#ôýgÒë¸oQ€=(Ü^àw빨Gn~?÷§$º°u­½kƒ¾]CÎu»Xs°­iF†µ–kk‰axžÀZÌ›oæúA‘ZjПƒØNy„gmØ6§Ui_ÇŸeEèMPñà ¢Ö&²â–]¦+O¿«Wÿ’èÚÉrЀöê³ýâà]¶.x,W›F¬mÒDO¸@á|=Œh³hcMéæø\E/°óÓâ&žŸññ|M㤙Ž|Ç¢ v[À‘Nš®kâ¨TÕœ¨¢”€u,]!I,Øæ´šW`±’j,ø¬³É(•mj½-µLf$Šþl˜±ü±daùÛ×f‡Ëgõì;rk é•h/ü/gÏ.Ÿ^0%pŽÑÂR3¶Y3mž—–AR½ :»­haAq@¶ê`·u>Ó1ôÓË¡äØª£.Ÿ‰Á¤ØáÏOµN3¹n) ÉNîìD{ÕÎÞtó\ĀъWVuãez.ª¦óhºW‹Û•h­Ÿ]_á:ÑO7"'`™ Êqš'ö'øQì×b­’LÆ ®<›ÒÕ¤˜N—pžµã>Ÿ½¢çŒ®?°ÛféÚf×óŽo,~0`žΰ4‰•kYC‡­AÔtº2h¿”gÞÐ>¡ž–Æ$Àaí,qm×(,"9¨LÞ IDAT/„ª.Y{%o[¥Ý‡ŸS’”Õ;ëuç&â~MûYG,"Ph¦@€bžxœGÞZ RMü%Ñixr¢›‰{óÙ~1Ï5L´%¦3¹ˆ„Où9 ¸ˆŸß­ø¶[À~ã8W@<Óñùi|­ìy€Áx½ÃŠB‚ï”8XË p¿Xå¤t>â8nX€ÞáZfbɽIhsŠŸs>ç#Î&Õ ¸^l  —”¤«XûgÃŒ™{XþöµÇ.ùŠ£(§Î,Z¾a:d‘]>)9 ¦µùGª*5e¥©ù²¤’{gRű“Sa ¢#BP#6_:c'–ý‘ÞZ?6,Ý–‰†åÕ#„ 8ÅR®çŸ÷ÈÉõÙ|ÞÚmMç±x7I¾ÃãjÎ^<=ÛœïíÌ÷¢j§S±VªÌÐåP2Y¹í2•-ÍTt") 2£nÇŸH2ü÷•™¤hŠªL!pñì5!6¥Ê1DžÀýÌ+ô>€ê0C+-í¢´³~÷Z¼Á÷ëÛØw:̇k¥§cc2bà ðšg¤é1Íx§üéáVÇÍ,Oµ@;®Ùmk:eÍzb÷m«ÀlœHº ø&Ž?ð$Ào‰ð4™â`…MIÙ Ë*µ5p+îÐwLŽ}Å¿NtfÛ° ÚN½_<¯7ŒyÄÙt¦êÓâì<œZxl ü7ÞØÜœ2Êêa›q<³i4;W¡{é¤Z'ïÞh4ú"½˜ÀÃ':Dz¾ÈBOð¼^Šcª,•ȸ¹1³¹Ì¯‚Çš ù.Ãj°×, |p0$Må{Q“¨°V:ÆŠùýÎ÷r§—²=Vf¹¸(è«É^iÿ»YÇ­ÅÖö)¬h±uòLl{õ¶¡\ªfš†ŸjF¹…ý¼Lܪ¿«,œY~¡Å#Ä0ð»ÇíðLØTáâþ¼Õ2mÏ»ÓeªÅ¿?^Šw«|=>ÞîkÅë;1߉áÂU³8^ §ÑnȯÎÖãø`/‹Ö7[Æ.ÑÏ4Bªcû8»&âŒM—TÈMíܤ‘Á4JŽs_%Űk“ëÙü§ð|S,#^ o3ÉïwúÈWµ0ú#Çl›Zá†cfN@홿–ƒ{­6$…^h²Ý‡ ©·GO!ãuÓ§ìqLØò\ÏΣ0GDwÐþØé¥m‚êt =MÀM…"¨ö5üy¶SZuNA nLQÅäDšrÍ(Œž(žJäÚ_%#ÃÎ|¥]ï;¸a ËA•ÁV†/ d à›D³ bÊ#\5 §àÎïp:=7ÝO7#0 c6Ÿ£{| לÊÙ{‚áåá7G‰Â“Kß< ؘM ëä¹Lo+‚a—¥Eœ…‘¢¨ˆx¸·f‰g7²!RSzAöÝÒí»þ Ã(¿ì•¤li†W–¶caÊÞ° ¡öX'˜bð%$ËÒ ñSÝ Màš£qc˜[¦‹?Ê6SÛÄ^”mwè¸kv¶f»¥UZý´m&\$œâÐÎ9ù:zÄrÖÌDDþ€¢ —'h¼ËÓüÕg§ä/‰N'"ÉÑé]Ót{Xê‘“$#¨(âõÝé B,??¡ã,êθZŸVà:!úFÓ„çéÎz<›3¶»> Á8ë_ÀÏ F£×ŽeâñãÇ^gÀß`;X¸J™ŽØÌéò8† ô9Œ±„_Ý©âx³Úà‚3<¡„MÎ%]]"ÅJ©·õÂðÜÁ­>x9‡»¼m› 6±\¸CÁN®&-,þ|ñT£u^–±E$©8»(q‡›8 r‰ÛVË<ÔÞjJ¦­ªlš¶¯Ÿ eSµ—~ÞzaY¥¶ÜÚüER=ޙŸÝwrùÜñÎsÕªè¹]:Û­?¤ˆÌnµ»ÙÚX×¹ÒjÛFݘ·®Ë%šb˜X$k`Õ•ÛªLd³Ùh4p–IZË-ѽåmß´ôWȇDÓÍ'fgK7‚a9\-WJ·ìŽJg`Ø¥†=r Ãê8À|îg˜ÿ‡yœÇ[Íõöqƒ…`à=}')î?¶í~?ÇrË¡I¦ :ä pϱõ:çèÍJd©á+W$:ŽÓ? çÝ>D–TAôJü1ëÜO–¿b<;•Às±¯6’q;Ø.°Ãîà‰EGWé¿PQ]¨vÆÓqWlÀO!ª:‰à'l®O^œ¬Wët4šN«6™¾1Τ»\؟೯=|ýáÁ+û/=9.•(X*k¬©Q‰i¨ ˆÐ ÛˆÙ[œÆp#ÙÄ_<™‹Ç›¼‘ÅI’Yd™¶½g~0\Å1>hÐpBØ=7÷°¥Ì§°_ŽÃ°«I[úEܲŒ-Ï“–Àþ.~üË_Ö ˜²ñ‹(¬OàïÆÏ»‰‘z–}ª%30Ц:Ø… ë¸¶ž\V3_=s&ù üÄÓ'_8£*‹`Â’ìil·ûf'Ï3 ÁðÌ`¥VÕò¹g.Ÿ[ýô“h³µÙƤOLFÀ¢ ò‰¡]& vDR–âÛÍF`ø‹[º¾5c<µÖ3Vi×Êç§Ê_h{ª’0kŸ$æñù#"‰h2' ïÆp^Ì9e|‚ÄÇ(\føä¾xqt~6ªj¼^żz‹Q%y±°öÃÑþþ£‡F—®^zeÿ`ÿu™¿ª“0V⥧•9š,r=ñFâi^íÑÉ|ŒâÕÉ Ñøn &„%ðäÈ"Uo¸ßÅôF¿žÇ^w›µW€Üá·éeYæÚƒ[¬Þ·5íä)Í0åÅÖ/MÈK¿\jµ–‹á™Ó`{Ï,,Ÿ]>µ à‹ZðF­ÁîÙ%EnÍ3±Äå¶av uy%5œ¸õ‚ÄûÍh91R#n-Å ¡1 ‚|i‡Å»Ñæüîù§;Ï;t7šO)«?Œç;ç™]½Ó:3Þ€€ Ô§)x¦“º©¦&ãlÓO4Òh45bÖåë%¤EYa`Åq|s›w ]}Y¼BÀÊ_ªÝDà}æbLó°ôµ_ô;?Xívº««8½£.#vË¡Z¤Ðmè ‚üß0…–gÖ¨ˆÝú€ß³ÿˆ91¾çš–JÛs¬a€gÐŽÝ(¶ì†‘çfþ‘<¾19Ë´À#]M76feº}¢¢83+€;ƒ§@$ð€x2ûœè„!L£žä¦X¶ÀY7F.e@8ˆ„ 4z¡qü7;ƒ?Åý˜ ›þ“€×FçF÷G/½tñÒÃѨΩâóÙŒyßèöûáþãÑ¿hÿµ?yõÒ+½ÆMø7Y'9 ˜­Ù `;8ŸØgÕœÏ<ÐÕ0ʪ–aK%¾¤¤KHÒÞî›–6ø=×U9. #ÑSí¡^¨8±Ç¹Fí©Ó§N5É™S/¨¶ÔˆÏàaë  ;ƒè[@6„¼8 á™ÅErË3—~½‡œ|@å…% ü»†é¬¹&KGvª UÆvó††) ¿ªÝ™VÑn÷Õ;Ïál ˆx¬?Qr÷ü{óOÃÍõV±@jÒCTuC$¿ÂQ'2‚m38]± f/ª¸Q#½1r»È} ’‹‚x~õè¢î?ôî¯úu…ª,+p8x«ÂÕÕÀÒ+-I™ñz>¦HcÆqJvné‚WàtSPa TÌ›ÿ]»>•à]}2kZψí¥. %³­›%<#1À¶²¤I;§±ù"Ý«@¨APá»çÙ‹B¥ }.=z Ãìão}3ÝߟóÑ(½ ¾C_åBX½íQð^öî£KhGFFr&ñE&+àXʺ™q!aWžóì*,>žƒÉš°9 |6Ý|ñ0°‰š¤Dµ(¡D½5Ðë{CÜõïÓeÚbF…1¥3vüOa®*//,,œ‰Îœ²–~¹xæ4üx)ïBô±ÒhÈÊq$®ùïr0ÿÅ‚4VÃðãE¢æJ¾wT÷5:ª›biê!n™Y&VìÁ²ÝÓéú<Ú¬·ûpç¹5CÜÕÛÏøá<Ä÷z¿º·¶Ö‹»2îGÖÓµÍ#øV–®q¢+”/ëƒB¹ù%öp<Ò¨žŠÄE%mû=S×5ùCà@ø˜üTSƒþgÎg¼‚² b¤è÷˜&:´Ør0t»»Úë­A¨p0Tx+™mÿt7GfÞªó´ÛÒ‡oËüšqr°šµ½aŠ)÷xÐëÔ#j\LþA‚ÎÔKu!yjck€e€ÝÍp—ÇÅv–<žÑ*¢s‘­0ˆw5 `× *c^™"2rìG+JÂIjàT‰XÏž˜Ø°C*SÁl`?¶9Û1ãUÅÄÎúœSž§IþòËV_­ýŕѣo_z´å×Þóñø‚=²ón_©£ïÛÄv/’ÑÀypåʃ—1ËGÑ •ÂS°42U%\¼À€cÙÛ3ÆÂñÞÎÎUQµ±¼CÕ`µn^¯ *ZÚòÖÞóíÕËö`¼ÕÕì4æNiÂÿißÇ´Õ9uæ{a1^Pe,;–Ö¹nŸ¥hØMM,³L+uvWqŒÕ± »x[¦³‹Õ°è_pÈëÑq,U7q0§3: ¹·G6ç{U½Ý‡;ÏU Åc<>ÿÐùôWã0»»õd ÎS÷Ñ\’šäKÙ”5IÂè+É™ a¬·ÍsžÚjRäß´ló⫦Q7i¸HT_ç»…k¦Žù' ¬CË+ è+§v·O·–?ƒØ ñy­ ¤è½õ30šŠÛw‚Ñ vœ_³cfvF„ ÈáZ:r‡ea¢¶ÀJNæ1Ãe-@½Ó62ÓéVJÓxXßäÕÇ€pÍ.Ö6]xà„bÆÈœ™iÅßâ¹,žJ`ý•À–ƒ¥î*`XBãÀ€à¯õÔ7Á–ñ8NŽ æ–]Vs:ÅŠ¡:ŸñÀ0¾¹ÿÚÅoí¿tñÉ«ß|mÿ¥·=ܧc®¥ ÿl×í‡û›é«ûîýáãGßxtåÊç/C S¸Æ$•å&+æ¸Ãsàp€ßæñ"ñΰ5N4"¬YE«ž„Γb@@ˆ”8B/Xµo(ª¼‘‘©µ§;F1e*‚•[Zrú `¢Ôí§µ£DýDŽá6*B…{‰xqd´¬Ë¦|—¬fٖݶs[Q ×5á3G¤ö«ò‘rˆx3ððð¸o’¢¦ïVdížÕ±®}¾·3«¡X¿Á‘ùƒ»I¥ Ë2ŸÍ¹µ`!à C¸×ð| ºƒ†°GKVG'1ä.Á-»äfÿÙæK{XdVjƒ2å £:0 9@f[ß#LaÖ¨nk…6Æ;>{ W§°¬<3(7é¯nS…YØnòÑ ,w ®aš€+A$>2O%RNU®€ž©QD³E•°¿Ü%ªÊ\90 IIÂd`Ç–4¦|ΫÉ„ßxŽ:U7=güÖ•ý+W®<„×} ´¶GlOÇ»†eÝŽFþã'û¿‘“'æÃ‡ÞÁk¯îï_¹_, ¡b‰œ›2w« ¦.3â ӄU›ÓqVêìæÅÀmËœê ¦4PP°Eû‰Ó-Ÿ`ºOÏîB0o¥AÄ÷Ìé,S°Î%¡z[–Z>F¸Õ „ŸÆ>‘UYU—¤ÛØj÷ëû–ÐþصàÙ¤¤È%ÛLd£þ(E=”5§×AVt³î¤…Ð%ú‰~·".3piçbÜyŽj(Öo¾BfÉE+¹[Ô¬›Té8K7ÕTIV{JÂÅ‘a˱ài[/ËN[·4ò*Hr¬3à"i‚éŒ0–[¾S¬^6Ê-ˆéû7ï™à'Až™ù ö¿éf— îœãÝhèö³gxtº$/³Rq@•‰ìl¬yqä ñ‹mÛ UÖ†¶àgðï08ÎFv×àÉd¹m 4Ÿ)Ç&Rò]´!úŒ!ªQgÍ6x’%¹åžÀ÷$; °žÓ%RB¢e–U&¬®BJ ÛVJ  gàh)i5+ð½ç#º>Ý2çvi•8¯õ]ëà[O®¸û9àÉ~øðÊ¥W¼4J/ŽF/] ‰sÅyõ xôÍ¿z4º$±FG¯¯`Ý:!mZn4Ij2g;¾¹ïÌf`Afáx<ðtcº©`!ê£À[Ô4‹'kv»}`w hÀ/Íéâ ñ‡Ï¦ü€`ÖR„ÅLÚ' °hDÃ~j,7µN‹þ·nˆƒŠ Bl¦¨zޓ圪Ä×Ö,«ƒÃåãé9@A‡8øG`Tÿ;GÄ Å ˜þy|U– PœC±~ó2ËÝO3º‚Uq u¾[·ê!g¨YTU™Fºa*Td·¬‚‹äy˜’Gp—cÅq¢Š³Æ ÇxÿÚQ0ê+]O±MÔ€^ï ×[uJƒü©ó½¿ÅÂ]·p2#K5y0ÌoxXBÙ ÖÖ2%[ŠÒa0È,†K<Ì<ï2P DaÏë¬"5FÞq§Ñ!ÄNÓÂC%Xþ™©†'usȺE½ƒáû¿xu?rø°Û¶ê63ôDÏ)Ë;A]®n¤2•D#ã\)na½\3 ®âfa¸Ïc­ä ¶2ð›ˆxgÎ*ZíLÇq…HVw'â"Hì}÷G¯"ÿÙé+Ï=z÷‹Ñh $ì7<íãõÑ£QºÿêË/™`†S^¸¢aó' 0BZš¡%’8_ WŽ}âªOãñx /jÐ#ØÂ0¶æ ƒíbdkHŽá¥!QÒž*G ‹ìø°Îö2Y¨Ç)ÚmôXuÈ'j­•ã)Ÿä¶¤ÔÆÀˆj,5%®ÊFÈ çªawpʺ¬šú!О~ŒÔãæ¼ÿ4 ÕÙ2+ùÿU–Y¶ùܪ¸º½0vù5GÄ(ºÔT@ê¬)¼Ñ“·-ë#MUj`@°_ c7qÜêû[×ÞÿòÚïûnÙw ¸Q£¬®•õË{`ƒ×z«èÑ:dz@Œ6vBì—Z}«ï`67¥îЃ”Š Èî–ë½1üIôÄJe°Á1Äà4I›\¢²š›:•’¶››²!4‘),šd Ff4®Æ³›ï1ž©v%]®YÃÑ›ß~„Ñ÷ÊþŸþùE~å‘wðÆèüts,;];õ'W̃×^û|4ÚOG£wJà£+ûçνÄ1æ%" 8¼‹d22ëü`Ò·(澊ª#?‹ÒÀÆ;![ÁÆ¥bÉ*pY»mØmw€' 8Ù¤;èõíÎüø°Îî‚ÞÀ*eœ¾ à( L8ÖœASäŒz.bÝ_’( ÎrSt­ÙEÒÌ· ½ÝptÛVQâ‹Y#Û9bŽžFçŒ[Ë2 ãu¢¯mx×ßËÿ£²LÑ­«âôº²y<`”²áFb•c†aç{¶ãvöìÙ0b¶ü!ö¿qýý摺e~ðÓ÷¿üòK±ƒ,$i ;õžè0X½_ðsuˆÅ’V» fÇÅÍ‚áp˜•Fé´5;è«)8ot‚5ç<F’ [ÜZ5[ÇiVàg}ת4Q€l¨Sfg•ã(HœˆíŽ`)çGD/ŽÌUÓ6Ž¿Õ³rk`ØE¶qbþÝäf7èãép˜ ™kÜ,J¸ð<Šb-¡Š@ÊBÖ€s•0=•c1eïÆàtÆjrÏßv¡¥kðüÞ°¿õðÕWö|±?zp08=à#:Û$mb;£ÁèµñÁ{´ÿÒhôØþÂÜß_qƒë\Õ• &¤8ÕØ.goÓ¨ÚÝ´º€i1|<‰ÎÑJ zÀZí# D‚^ÌôÂ|RêVþ¤ l;[nž_·ÎÖµÕ©%(¥ÂÉ ù¥„õoê!ðž«ѸݔÉmù6@SnS£"¥EŸ‘HÝο–]Ó:îíÜÔ4œmí&m³n¹ìÝŲLz7ÂÖwY"cÉ©ž¿ª=Ñ‹­òu£¸–š+¯YkŸ>­Š3‰jÔJ‰Q÷LÕU¹IrMË;–-8Ž Cóm…-ÖˆÎ"ݼqóèúáû‡7ÍÃßÿ§æO?P wÕj{¤ÄÔŽï ¶17Áë%Px¥Õ1í¬£ûõ9Eá ne  £˜ß †m° ^X”o{µí}*öà;,M;Ô$ ¬}‹÷a•‚j®'1Â3‡ÿÂáÈ|‰k€BXÓ&ÜP(¸õ½b ”wÛ8Á„¼›n8ºN„<A •Ï’D•c‰ ­ ŽV`n–PÀ è [ž«Ñd†u³¥(ïR€yÙɲέƒÑ4!OFß=úüµÑ7÷÷÷GÀum3·£ÑÈ=x5ˆ»Ðû£‡{#wôÅ•Ç#P€ŸŸÿxIÈ7 ­•x€Æ8¹]Ûã¢Øìy š bpDϧgb³Ä$(êi‹úRíƒÒ¶õîÐw-G9MÏ$_·Îk2Ù¨‘ºF=Ô(¶}I»ŒãØÃUp'¤¤\”ѱ9NÀk‘P6tj¤Í<ïü+NuÖQ,Žê³qxõ gËúŸX–3:=¹³Ãñ>f³»8WàźRø¸,óàYUÜñÌ0÷kÑmº¢[à*;¸ùa³( —Ïže¶U¸–g߸°ûððƒÞ?üðèÃßÿèƒ÷¿|ÿƒÀ[écwO€ º'Ï?Ë;«««€Àº³[²aYع…)À€nfI ë>pUÌê‹"ì]Bá†ÁÐÉÁI¬ÝúÆó?ì¬ ~M;üòKl ô1¦´2ÃN놚`œÔD®[²ã¬7ÝàˆHSáL‚ãQ#¦q‚sQ‰ìÁ½ÆM.@ìH"±àZ 4 ¢&qSŠ¥DÏ¥€ÿáСŠÔ„žŽwé,žUl³1{kžgl·|r`%¸­ü­o=úÆÃ/ÆþêáÃÏ?ãò£½À 3Cë?0²Ñ¨¬8J•Ñèá•G°GWÀæÉ\ÊŽ )E¥3%1‹è^?z>\Ï.›¼Òs<Žg;$°IBÚ–9 b -„p€)z¯w° –Îî4¥…Xýºuv²áMS.¡Ì»¹ê\ºdÆç Æ¿gÉ;ÿù?ý¯ÿéåKuŸdUWÑV`¬zê‚ÜIøÊŠY4YãÌz¤:nü¾ìG5Z†EpÿÐIªWf;ë{œ~<Õºóé'Œ³½=Ü«z^–—Ϫâ,„2Ðî½èjÛÀÈsÓ*ÄJÇÀ–* ÏBðñÌ÷чG[ÖÖ‡€À÷ßÿð££?¨ÿª‡þ`Åê–« ‚±ª3öX”4ü ø¬Ó±ßóûŸy풀à _}R¤|)MË` ÷BhB¨MSÁÕöZ0쥺~É6V·q[/T͹ IÅðÑlBÀÀ®' È,Àæ@I­žáUVêFw1–n[‰¥X9:7‚MèdU’€ó B°7t}'`¶$œŽ¤c· [“i3i‚Å&’p î–Š˜' Ž¢îl'Ž';3’|Î㬠B¥Ø—ñú«_ì<ø@5]þüñçï\¾|ùÊÄL’µQ™¦kÃ#ýÁÈ|P Vef8Þ| =Pö[¹Eo£¥ZÂYXÅo3vA¼K笮Ng/îV@,!`©ÝÜ2Òl†áÄ~ zÐä]7pýžs+‰èÆ»_·Î®Lšêm¿” w<:õÈ„V¸=1Bï^ýOæï~ÍÄVo6ã.‹‘cŸ@¢4 ¬Þy¶˜& …™š‚“>ÑüÂU¶žöµÅ9d{¤Âv6à:è”Æw9±fq ´÷¬,3œÒçUqȳ>Í ElÍœZyÖÉpÈ_=ËÒ¯ Èì-çæQqóã®}ô!°ßÑáµCÀàGÆGº”}»ÃnÇ*¿¿:Ç»ºÚí®®Þï® ‡5„ÀËí\Ç!ƒ:Xgèd¹Â#&›Ó H±•øù IDAT%ÁÐd˜šéXü£x²{~>«ªYµ³>×§Q´îNªhIC"|:â·PÄh@Û¸…*Ã~ ð«‡@OhÎtí­Û¶ÏÏ`lL¹±à.,u ô‡m AúsEæ".x"µMp÷OµamÀãX–€/ÏÆ;t+áV£Ì's“ž‡Kå»#1j_yppåÁãÇÞ忾üèÑåËÕþ_pàÄî@}rxŤTŸ33¹òæ÷ÞüÎú@æDE'1Süàçrð a5è1_e{xÔ‚}m)??ßL‡Ç& @¤-eÚiãÑ¥t³zq;«Íl¨_³ÎžAª®aÿò<Ïdî{¾Mé&óQ®Úùíßýö×—Ž[í’§cKqÊ0,ÍTQÔ‚%ÀÔY[:‰UXD_ˆš€OÐA–~<§±°Ü»X–¹w0²LÝÖSéæÓl˜“ËçžWÅAô…{ø#øy?ªq]vÁvè¶~¨9u‡‹ ðcG7›×¯]¿yýȾ´íkGG¶sí¸+ÿ–i@ˆö†ýú,ôž;Ä >wè®®vV»?è‚^±š]ÚæQÕûÉ%GD9n?s‘°Äê£Ó7ÿo+¬Ïw€gÆãu„`¸3Ãù㨚Wëq®Ûõ<2ÌdÇI(™±›$¦ü‰Z È Ôb=.{ºkF]?+êoäó½´(L¯±’<—š ®g8y)¡q$K8D}iYoMr—2#ÕK;Ó)‚j XLª KM5¥év¯Äƒ+o}óÍ'FÃËŸ?úüóËUEGútÂæÝ¶k ûe ýVFVÏßÕ©¼y%±®à 9€Ÿ©sŽóqz¶Íñü—½Å÷fó9«ønÄg˜[oª‚3ì ˜€Žl²‘»Á03ÐyÒÇA$†ºH¢ä­4}j«Ú:{¤¹‰G ØJW±û3gØ®uéxÓ$ÔKœ+¿ûõíßÝAâÃ" c_úC«îc©Ã¯®™Š&ñU9)2pꎥܖ1[ E¥idzim÷W–ežÜ¥+Åݽ{I’ชôÓöÓ²LÌÓz^—½Lx=/»mÊEá¡êí†mþx—éĹ~ãšsýÇ×®ßÿ±u˾yëæÑ-×UMÝ´ 4•[NпowVÀ4ôÀr ‹ÁWY)C`¿!¬·•M0Á±êIYÞ0O!Ðdqx¥p¬vŽåºÞ…½ XP:ߦÓp®Ão† ¤áßú½1»h…;ëQ;Á³(å~ÜU•k|« ÙnL³ª›s¹nj¬ÕM} 9ÄRiG«Xµ>4eMÀõTT¡åØd3Ã~ïr’41bƒ'ÁÅœ,Ù@‚•pãø-6ÆŽ3|2ž+¢=¯JÏŸÉ·F —ßüÛQòp0p_¹ü.D`ˆœói5Fßõæ`Á¡ëÈŸs=ÁCä n{ƒ¡×ÁdšŒó Á†¨\™X”DWœ^€·ñls"î&žœª¥2}äõ61[(ÞÓ*‘Ó„V§Ö¹Bëì™$Ä©ÐÀ~8ûö׿ö†<ðÝ51 Ukà Ç^¡W³¿ øCÜŒùoÇ{Îõ˜b#?”8ï$ºI©´SšH𩆇Êà°C*nÄ&uYæ,–ufüíNÖ])ÊvÞÎsñ›ã²Ì:OëyUnåà a RKÏ û5üZ­³gOE! ™imÝ<ºñ?®_»xóG·nlÝò\W„Ù­o`»Z÷uÛyÝwM·£·WÀk`‹)ß¿S³õ¾Âa¿ãÞ/ÿx/s°C.8=Íb•[Ûà_Øq:Ö{Nàlôˆÿ!ð«t¬ü8Ë+í€,~ˆnz•– ¯÷ö®Raã;ÕœéYýNH«~_dÈ\ØU5‰*5iCÂa;ª ÐLš5•J-ÄüÄ|§š«y'ðû®·Íˆ‘^L5×êÚ LŽWÔÜT1hÌTÓàŠNrú VÌ$šÀW³‰à»ã0G±xKí?ñl‹¸¾}ÓZµ¯¤“oÍéåêmÜ>¹¦öJÖé¹þ °±±”Úþ®ÐRÁ$œê¥ÊT! ¾TBYˆånDWð<–¾ Šê­Í)ŸŸíTízì‚Q(¸3"Ò¦ô¢²ÎzÛ A]c/3éLzl«çÖy¼ŠRX%.öDvÿêéÒ¥ÿò]Ã~ù;9\¶žã9L{ç·÷îý3•õ# ·f£> ©7ŒÁ–Ȭ‹ƒ4ÂÓªåfR³))Š"Åð¸X¬èºú?ïåë ”ø”Ó½G>‚ÃÌ£_Âi4™Lêl˜çUqGúÿ°­#×  mÀ¨>,IAü="‚Ô¶þþðïo±p¹0®ý„Eÿ2]מtÇZj8ŽíùoØ]à­®•šÕñ‡fßÿýÚ ´€ÿï–çà³áäavê|¸ ÿA:@ºŽÎDw”Saà·íÁ¿u>ÍgEí¦÷ØxÝñŒâ¨žÂ 0\B7¹,IŽÐÊõÌh“"ÍM[U8öú O6ß½tÜÑ2m Py ×M‰¡8*¾IeÕi‹‰ÈLÀ­ÈbØÂEs¬ñØ›§à0ù˜ÀT3Í ÈôÄÙêŪý=`@›Ò˘?8Ÿqžv:æ¼pÝ[ã;öP¨¹¿+'8CȰP$ž$1“à]<]h饞,ˆ«8T¨ââ­9£ó®:‰܈NjÂ;“\7£¹oÁH·à®Ðã0=¶ÎYõÔ:{zÖÀá¿R XÓß#>àï¿Xž7t{ ¼ÝKÎ%çžà'l¸Ã‰Õ˜¾@~(íH×$ 7`Õ˜õ´Üƒ˜k€Tp41¶xØÂ™j“=,Ë„¸¿÷ΞîìÌvûοø¿ÿ—àn¶3áߟLªsñóª8ì l…n—k»W^¤>Ó1cm92·~¼õÓ­æ¶ Ÿu¶þm‹°Öòr¤É¹ÁÂñ©V+Jƒd€Ù®(Ãæµ^àÿ©ïcèíÝ »¿ ýÀíkK³Œ:aâñŽ…cNAn×ËÝg@ÜöDuͶû:ÜòóúзïÝã÷þÐù¾¥iuÛd…¨Š’ÐÛ PRDXŠEѶ-·Ë@¼²1Hžë’wÜ0N(óÙ¤ÙîC× l¦±XÎÐQ 0&`§—'&vƒ1 "lY fjx¶J0C>©›ÑjΫ=+ Àao*À-šãtÚ+í=߯š4ˆƒñ_Kóì?ïyÃ.ÎöFiRú÷)H¾$Ó¸J2‰«:Ó&ÃÏÇãx9×y3Ÿýeõ64ÂÑq¥í€½g5™ð.à”Áñ3Pq 󆱾æ»o8K„6²Ú:ƒÒÊõ¼BëL±W,'‰C©°øâVß¿d®¸Û¥‹U‹~qïw¿»WÜ+ŠâžfÛõaîCáN£ ãé°Š#°E¾$-DйJb§¶â>î1ÀÓ–eNÃè.r¶í£ÃÃ#7jEÿðOÿÄÚíHgaëÔ&Dh=×݄خk­–^Ýk$¸W›ÞïAÂç>óVW»kk†1p}×_síÿ  g.Ô9ÏÞ6æ>#’Þ.×s“‰¹+¾_–ŸÖT}O{Ú6™r ä!´FCÐèÌ—ú ÓÅJNifš¥"Õ?’/û£Yæ'h5«ÞÕjtê©D€ÛÕ8>MŽÁëÅ2n0f˜0—ð¦¡QÄ!ªbQÅßÙÙ¥@€»ñ|¾Ís#°ôMÕ žØŽœiß³ÚBø&&>é îô‹ß9«î­$éû÷%tÁd á‰­5£($Ë ³ð*°{ÅÞá{œ¿=Ñ9‰ÉN¹Ðj w]oEïz¨«;@þäê²Ct-l݇JÙû×µU4t¾JDz³X0¡zöPpÝímDà6üÁøŒ ø¬]2ÄÒáÝíãä„?|Õ6y îsÌÎ,„§OµbÉÄÙµŠ«d¸ÀR[ ~§6ÂÚ6NÌ';•–÷‚Ápä$A"%™šé,(mðå2²‘À"T™œá1q®2%ÑòBfL™aÚ½9ElPúÖNl”A®76eX5 ße[+³ëïJì*fB'PwäÙÁšø÷U-W9¬ã¹.âàÞ🕃ˆÕs=‡Ug˜ìoÚºïûÕ4Ž.}lh D°$ ‘çÐsæ6ÎÅa$^÷ÄÉVʪ§ÖÖ_µaø®îê!'\¹ÓÛüE†ShRbpÓ —oã?u² &‰ãb àA‚‡*ëÏGbªÊ¢Äàó61ƒÊr¬`k,ŸhF{„îнhÆÙîc}:¹ó;¿Þ‰ >ý—éIïMüÆÉ“Kþ¤®ŠS-C?ô×ú¦;èw»@?¾—'l9·3ð¼Ä8Úº¶åüôH:ºµ}“ýôÇÒR~´ÑúYdýS›±¶ž$ájÃXHðâ !wƒÌ*VAßÝ÷< ¾ ¸/ ݮ癥W ®úÞðy:–]¦œ¦‰X5ÜJ뽞ÝéÔ¡ ;¼àY»d:ÏÀ½=೶ɋӧééÓ§£¸ÕˆK\…KU˜„Ŷ²ŠÓÀëDåªÐN nûNqŒ@~Û-ÖÌ4ˆyBK¨À31•ƒp‚É€ðŠÇ+L1@¬ ¬˜c @,u5:ø‹«” 3Ð͆¢×½ í&ån—ùö}Æ?b8pNˆUÏëX ååÂõï+wYÁY¸à|¥zÖ GŠ6ŽðDxLÙžxK168S‘ˆ¤“¹vnMsLÉWpˆ4¤bÁñyÔqá²úZèù›nÞhÄZgÀ¢QÅéáÌËÖÒâéV¤Ý†5ðâ›0Ú©ZÑÇrFæœ5p9$¸5$0±MS I«7` ÈÂgtLõÐâE¹ÑŒõv¯×ullÕúK Bã0Ñ)Ø\z.Wú·§“§»0¿ ÒöúÉ“í|âË‹§€5—K?’éH¥û˜3市eº›V膽µm€Ý-ölËÑùÍq¢$ÿͨgv[ÇYñ™iä¶c¸CÛÁö$V´y–ïÙwîxå  @/ Øq†¶]ä¶åÖ.8pR%SÌ`´V4HnÐÆæζ[ç³v{v×ô¼mp#ÏÚ%‹ÕýÃÓô¬{ÏÛ&?kƒ;0©š¨$o²æm†û£¹h˜&“ %ÅQË'ø<sÜ„»â•¹"Àò%Z¦ Xw7p¿§•£@ËHÊÁÝ`{*ÀbGêdïm:Àm Ü9/ß ˆmn]¦€-lŠnê»÷w÷({§Úâ­Jðw¸1ò­À±mpÊYϧLË„Ê8,à[ ­Éœ2ÛN•/UÜ4üb2´ÏôçbÐ1¦‡A·_¢&—Z±©‘忉i–¶ï'Šë—7†‡”DßÑ™&G8¿#ÒÍ6Yl6—–áÃŨÑÀ(™,+œ®Wë`Ö£…FØ a´Jf©‘HXÃU¡‰-R ^?XSFâ‹Ôm4M#Ј;êu\œ X?³°ŠãjªÛÓ§Û0¿Z‰ÏLö^žø’_ú“$Œd3 ?ÊË2˳Ìu¾}ä™mæ`"¨ ªðùq“EË'€'££\£Í PûM˱Ûİá&bB@²í:I§À¤+6‚®ï‚ý}ox7¢½Ô€CkèÕ}@J·n~0ìƒL''Ü`ÍJ¥˜Å)„`ö¶Sç úh€ë¸<¤u»d^eî½§äÏÛ&0çpè¹}¯o;vÞVêzfªâ.‚Ä5\ºCT†G›B:QMªyVZA¿N•ÖÕ@f8„=1…j&!Øb-‘—À•R×@n9ðIÅ"vɘŒÁ€<ÇçÕw¬ÀL>_üÝÝN&íP=÷]“îî1ãó„rí]ºNgYàÁ#*`—#U€ÆmãLÁa×rü£Ž©5Ä™Žð;½‰Ód–6i5Â3K‹­0¤ÿFÕÛ¸¶L¡w‘N z†­Ex¦$ÑXR±EMVŸ¦ã+.æ†rœÈ ËXnJ “Eѱ·UKR–¤†ÂêqÈ1gÑr%¾Àfqø¸Síþþ×Ã;¿¢loF³8Kp'Æïø“–¢˜¦ic:V¡w ×#`~Aèn[¶f™IN1Ún5Y 𠬣âÙ©å“'[cþŠý8T‹˜F“y<Ïâ˜e™¬:Ž3´Ì£¯:EöîØ½5LMv½5'ÏuR:uA·ŸŠfÛÜhLÈ¢†3Ž„œSÞ:`}hA/ —š›ö¦‰ËŸðÏÛ&›£k9kkN§Ì2LÝ8Tp`ƒI’‚§­Øƒ.¸¢°|¢ªØ)Às<×JdÊAª ON£Ð……‰°[ŸsxLs€(#³Ô2x4ŸÏÆ&)ï^}Ïæ`eˆë¥n\ן  ²`æo ¾—¼Ã0±àû^¯‹ã#ìa’¢ ‘™n Â%À9Ð-h~]§K[=œ±Ó`…'1»òÝÎÆuCÍÎO5lÑKáùá6L$“¥–Tg8˼ф_¼i; ŒbЯ‹Q¸¼ÙŠÂcÿ+g-l‚&V”.z%Š?PíüF„£'¶µÂE7²ãL,¬^OÌ›1¬ãÛM sÒq648ä8jªª>B$k`S4¦*|ZĦàúÕÕÝgìï¦ÕfÑbe2éM›’b[Ž¡[÷ß[µqZ¯ïçNÙÄ`<Å~>-öãæÑu÷Ì™¨/"ؾ ü†Ýnp@?‘-ϲ]o{õ)×pë4“—lÎÖ±]2¾ü<çyÛdaY\ÀJ(YžÃ…‡åybŽÉ‘@ªr¢¸<ÒáåÄñZy|*¡‚JxÂAྡQr,∠€°Œ]Š©’áž öŒ&¬Åâ èµI":¡Ü&¡ã=MxÎðtéҦƷýÝ,yK°=Êߦ ȹwÝ`õ‰mšŠÉ Ï›áÉá\oÎÀó$rQ…ÆþýɈ>@:PèUDûSxÀËWÓ -Ø®“ì˜ou"Kuœ…«Ùø¸+Š—ÂE‘ä8J,ŠÃÖéåF‡!Î+V–Â0ZoË·¹D1ç¶ÁÂ(fêÍ×¶Žþþ&–eþ¤ábEqSÒ’º1ßð1„¦Üü8j*ä!vŒÔ@DJQ‘õÀø[vûb*f¢Æ°H"§"- l§PU ú‡p¡‡þÝN<{‡'½À⫨XØÆê[®Ý»Å¶GúÀS¡U‰`›mÚÜþñÖ™£íŸlå:Í©@ì¸nŽYŽÓ“QÛìù‘ÉVæ>Võ–^Kô`„ÜuKÔt®k{v~duoÖô8tÖr5åZfÙ¾µ°Æ$ÉŸ{ltý^½! ¨E2C,Ûœ¦îwžðÓçm“)M¬ájAàj2V޼ׄ¶´³ ·"¨‚\x‚Rú¶ØzíøTB3ˆ$z*§ ]TFÀ7À|¨nP=w•õLÏ´ÜÑi XOðxH›üïæ0HøÂñsòk'à[ÒX] nWÓ=ö6ãs~Q® ëÓnû÷ *œ„¤*nbÔ£·Ày¦ øXQ-:¡{1$ì>Ä®ùù6Æ\ `"aÊv¢†L7ÂÖ¼4"…7£(V[¢ `0.ÕuT‰Ãÿ°Q{,׳pK:Žá¿áRÅ­…FLS²uóÿ¼v sž®ýo1<‹) &2ÈU‰ñ8“ÍÞJ]‚‡a'aŠe¬à07 ~PÊð“m6—¤4qbÞÑ­UËéÙîÝÎWËq±~D¯£°n8æÚÚÀvúpÓUÃ÷n©7 v*t†…‘‚¸šù­­Ÿ2¶%ÝòÜí6"Pƒ×rÿA$Ói´y!ÊM$Û‚¸eùY&S+G IDAT#\‰KØR0Þv=»ßµÝ6 p °Ù‘¦V]>t“454„¥!q™©*•TtÁ`} ç)ûxn†-@ðOǪûé3ò¼mrÒ4 ʉcª$ [:@¸d€›H`Jsê!4ª10üÄîœRQ®â©„?*ò”7.5Ü$Ú68z®¡¶ä𯄂.0‡»¯›¥Îg;³Íu@!ØàIt.Å~äà8÷u~RÜh`3«3›Ç¸¹±6cº7^ÈàÍ•šKx®(ã zŒÿ[ïþÝÈu öïr7  Þ§ªNUªN½P…ƒ ‰•¬5V·{®s—b÷“ê–®?ä&ØŠFŠdÙrÿ6k&¹Î8Ž×º™DV7ðmu“ºãLâñZ^³@$Ev«ý«nlYýˆÀï³w‘­ÈÊ ›ï€ÚçÛßwÎÞßNoŒsÜ“ˆäl¤ñ9ÛáwþDßè­þxÒýÉúúúO¾üã1ŽªqÞã1ˆªd6[ô»ß©Ô’êw¿+ùbDd1k7Lj Jë:F–Š×ÜÅ'ŽÙÓ³›ŠõYsn¢©úý‹Ðë ‚`ňJ€÷WkÀÿff+žp-N’DAö–!ÛÆ&ü—¼Ér/ûŽá/4´;]£B" ƒý>„h†Ñ¼¥ÑÆzbšÚ\ "ñ‡›–—žRØÕ´ªž(ëÔ0TYp•Jøà¡¡ º ]ÒŠ€6­8g ÛOM÷›éi$eoà³ö«ðÒ'fðD„|j›Ì¼8£XméŠûç„!=àïÆÍ¬´{3}Áë‡Vôgþbä¢5܆iAúWËnPÐ0À"äÚ P#$qìXñx.ã! PhF°Z² {^eƒ%vÕuO©»íÊtSïM­Ðw]š×Aƒxîÿá¾q`Þjè…<‡Ç –úÃ Í ûsrYôä]‰M/õww!f†öK‰@Q…Ðåw0z ¦lvÉîÛ»6$#»Ô¶sÞ´ φ›Ž@ç} {÷–ÃEå:® Áiã`Q€|í¸™È”viÚóÆâ×à6Y™¬¬vWÇÏNb`XÊïˆ;@ZÍ$iÕ¿ã¿õúwyž bK;ÐÞ­F ´Úº¦á?|æ»lŸ9YÇ-N1ô²8S#ê+‘ЉK²¡‘Û·AjIDb%Jê T’7×5Ïm2QIŽ&†Å+íÒS‹üÚÏT=.ƒ"fŠiǼ8£¡¸i¨Q$ã0‰„D!ÎÝ™X ¦®Ôl¦‡¢¡«jýHcMÆ™’, ¤«kÀ'®bo9V_ÀLÁ[49EÀæV¦i÷/íÛh—ÌbóÈ¢IÍ?<:j=±MÎq^6`™Ê¼Q<¢ÃxWNc0mŸÖê#‰‘å8 àmO©é¸mbôHq@%= …Іk'ºœëséÔsàÔœ Y«è×ܴ!%¹ë…5£ ØÜ§ñ—:ÒQõã¶#ùGÞËþÞžëî±Ð^2TØ2#ij{pWt\ˆÅþç LÑhE“*(ƃ)1@•¢&ŠEå N1'æù~&‚Þp§Xvâ ½u‹pAF}8]Dw¸ó>'À"ôrˆy!—˜ím–õAÎ2LØ¥éé"\X_íM~„½¶“ÎùÞ…µžtÒ¾)^¨XµÚÜïµ£pß{=y­rôÆÑÚ*DðTNQCßFg#s ;¡ .»tÖn6•‰Y·šŠX–úypY ä‘–:¨ªe uJG½E½t­žCŸV{“êžÝ.Ù>,qû ýÅŠõ6 × ˆœPá›jE%$ªêõj,GžDê*¤-m~‹*¾g‹¦Ù¶iÕÊZ91¶š†Ëg<EâOÆÎ‰¤­§@Kõ@„Œº£~Š€3[iiLÁè÷Ý>:º(fpž#˜ÉÛd/ÇIü2`°Èžä30Óp˜6‰ÀŸˆ$XyaÚŒ 8rý¨–¶eyÂ\ 5„ é–y °$(ŽP•à"IåÛÓEOS¿ÔÏ}ÿ¶û:Hã‚©¸Ÿ @ §ã˜TðJJþÑÚ“Cü¹¯Ž†î^°iÍœ7ZX:ûÈ壀”eJ̺ۃ·ºiJÊ5Á†€ás¸¥¹ƒ-ò8²ž]æ!ÑÛ%-;•%Ûi;¥bÛigЊ€ÇyjB €FÁ¢Fž¹ÛN ˆ¡KÊqvú%×Á”[\„KËPLÕs¦œRƶ3>Ñë[[k“µN÷ü…îd2§!CøìXRò•¹ú›Ãï…£äåaùêG#,žÅ 6`†@ µ £H™7L1ÙÔ%ÂM¹›šö2LÌäìö”$žˆBŽ68±€1ÎNH=!U­Ð˜WUÇwj <ËL}¢…¶gçVî.+^±øTbèO=õ÷3'°º–é—Ÿzjäè¨ .¡EBQUƒD¢î— âDµZ×^`1g3®®IR‹¶ ‘R…}ÆûxÏ+¨¾PÒU¬yÑÄT°–ÿ4·RÉ¥¾»;æ_ÆÀÛ2­ö‚’(Ol“eÐ :êBuk¦šD°Îƒ8Ñ È²ƒU8‡ÀOàÓB${Bü7A=@¬ß¢*AW(TÅ× "ÕÎy‚ Š4âCUÃÑHÀ3‘/`¿‰&¸%ܼ JEQlÞ9|á³7JŒ–:t‚üwAZ¼6dû¯cÿ« ýf -ó–EC/o>âU&!„[Ƚf¢ ’h¤ÐHÕ*òH´;zǹ8¶ؼߟj/ÙKöG¿d»¼ƒi´T´Kð£‘Ë{œä!X5$?›a¥ÅLVÀz!{×ÙuÚ¬ â¢ãO`å{L”œeA%@—¾¶¶2é,ßív{cìjXS‰ YK¥³V’¯ÇGÉþðÏݽ½×†GGöÊI«°ï¿öúÑQtºK­Ô…6Z†e§§…¦ÔlhssIêi$bé….‰’AŒ<\kE5jFKk6Í9ê³›gÏ::n™y€¿Èwë.v{ÉàHK¢ßþSÖ›žŠ”dX´5-Ð|Eƒô,ᣠÆ N (°¼8™Öär]"h ­H:[à(2ïgÜœªëB …M °ŒóÓ ñFM:(]êûþ€i¤†v±Ô“>k›,â™-¥eZŸ3æšu¯ 2$ÔT XO‰UODÁCvIáÑM匷ïÝ–'*(R<ïѽÐ/`!|yXö#×ÍznZ¨(ò@Àá'qpñUx ‡G»ÞkžìùU³ ûíÏÌ'A_XJ©Üvø|üHð€m¦mÃÁУ,Ì0ó-*yX}±&ª—å .Ç4†ö“Õˆ:Ï¡ºXœÞ†µŠ¤‡ÝK7pΕJÈžÚTSMhè`_5ÜÏãæÚ˜s™+ÿ‚t±ZÙV”Ljv†>ÐÓmtâ…ïÑóÀ{|¸„º®O6ÆNço–»ÎJO›|™ÃÉ5ÕIU!N±5Gç#åÁpÿµ~¯üòÁþhxðâ?7§Í„äÑBêë Äq¦¥2ðçì4ÀŒí#˜VR UEVBÐvân¦Ù4æ¶N'7®;D IÍ2)H×>묭ç×=gÕZÖ§¦R¤ŸI Nº‡0@y¬kDÇîÜâ¿¢+¶x«ˆaCIà~Õ%j­{¢‘760Éšq(–ËÀí‰T!ðË:6…rž¸Y°ê3Z͘¥æ Vš›[†×ï;h—œÚ&Ãuò>k› YÊu¹D¯4fæMZǽd,ÜKá0Ñöûd²©()êo´ïç… E[QxQÐI¤„š…‚€{/Rɱ«ØÔHR‚Hšêgû Ç"áÛ«û Ñÿ üé­[€¸Î¥¼±çaYýÐÛaIð8ßx1­Ân…B~îJ%­B€@RVå2üMàHO4%—›ÁZœFe‘cçNmãfó"p!·_,Á›]Ô±(fº|èâH¤”ž$Y®ŠÖJ–æ4–¸s¯XЄè]Q92d:ˆÇ3#Ü©¯áì^ªAèy~hk{€5–Ð'Á•rúÈ@§ ˜ÍÞ#/ÖÇhº=JK•iÒÊg3°&^ÔHÐØýÑžïí9ìª÷ G<ÒŒ¨‘Ÿ•”JEÓIÅTCÀ½gÒëý…ŸÁ٠ȼK÷KØæXÕ.âA<–Š»ðO>Ú8H!ÝU†×øƒOA„ÙS¥RDõõ ºIgCß0¶,4³‡ ˆ„§$¼ )z~ÊêzgµÓýß»Ë4ü= UêÃÃ)^|‰Lµ^‹çƒ7¢²çE/^f}¯xp#ðzçÂu¬Ð|!®™µÖ|ÔtG{ß àRîùK‡‡?l=®Wò~0 ‡Ãò%rYÙÃR™L.‰tMIÛG,ñRW}‹ðC™Ò¨ZkÎÒºY—åœ"ÚW^Tå@Áj•ü¼AÓ/ÆòòfnêÜ'Öʤ–·ÏXM‡2¨Ö1ÖÂÄèPœl†‘¤ÉP)ª?†õèq©o2zZ†çŒëý„i7øUHjèÓZm–Î$åXÃr9FßE%-éÀF2‘ÏŒ‚‘PP¤…<ʼnéó b S=D ŠÍ‘¦žJØEâ~]¤ør™Ä\é’7øC6¼:òÔoh…û,š³H_u5²Ý|þ‘p¹»´tÙ¿ÐZL sÅMÖA„äós{> ct( €œ`߇´šåíLÖÎ9‚Ä–÷@QðŽÍã•ìÅv±½¸¹8e~¶Ý‡H„‡ ŒošeD•®ÁªÞÊ[?5úÕµ ¥½åãeeÒYèÝe}emPC‡«ßÊoýtmm}}mµÛÕ» :–•nœ6¨<®Äxœ—(©ª³õ$þ¨ROÑÑ^|p©ÿ½~ß}½ì׿ƒï—£xðÆí¿ð†£@ºAbßÇCêÆ= )ƒ7Q›É "VüOW]¥@‰Ä²X£ùÀßà|qAÀ†FMEgfËYQ–&»ñOÿ4¯l=5YÕò—ê=U´~Ÿ+ßT*$\Ð'@_ªXIN#Ê ˆc \éô :TŒ%=ÊD¤™¸âD’CœŠ©å¹Ú F]#‚ §Ž2 = þ°Ú@lZ¶3pÐ.ùÄ69ùœm²ç#"yôh"Aú7!Êu¹¬„e™ ?…Dü3A gñTBÓ£€¹ÀGÂHÄR~‚Sºð@NÇÑ+A¨x°¼"/š08ô=4M°aT/èZ›/ ÛÒé6LZ¦%mÀV®R{•[ÑÈ[µ°ÚÈMt3 ù„Ò‰ˆ¢?uoºôsx°+ëäPK䘈Eµ‚€Ç» tqó¸”ÞÎbæÅw‘⺳϶m[Â7ž]_ßüñúÆ:5××AÐj“Õµî^ÕWV7Æ]Òëvî÷: 4ÆËø¿·º²²Ò½+-ß¹»<^Y‰xæä š} g;¼Ï$ ÍÙ´D›#•Y3©¹æÌaP èðµAù( úÁáÐÛÛwYÒ¨ÇÉþ³·ä ØÀ-ã‘Ï ðHr'Ák®fü•V 5ï§í…©VÄ«†—£z ëX›åu¬ep˜Â{þ4 T¦‚#^œ·ÿ¶óÔ?uŸzª7ÅQ4N=,'ž 4CÑ”ç'á¼B_•H»¨4-VÕ8ÙŽÔXOwiàÊR´¸Ñ$i<Ñ´±< ü}Iâ„@P”Ä÷ÉÇžQÔ¶N¦0¤•ùX–ˆ´íbcNÍEÛäøs¶ÉR[¤§Èe—’Zèùå"°iTä8ÖRYзX‡]8gö¼kûѺžfúùÙšŒÝv”pB]+ãöè´2¬&tªBf‰›®‘(k_aƒîÐî»U+hb;‡} Ö“‘Éx§ý9 î±Ë¾˜É‚À».vÖÂ:‡¸›Ëk „÷…Ÿ>[Ê•ŠÓ ¦¶·ùû\ ¢po‡ßÆ“lžMcqJ¿tu/0»E"üýá'J‹íŧG56Wz+«äo:wem®M:ڸ׽ÛS—×V{“4Ôºk“qçBÞ%ò_#Ý“Že±Ù·KH·ý¯%¸¥wá—3Hq÷=IÖ* *%VUMB?y3Økü~˜øk^íJ¿;VGrßœùþL@^v›Aììzû./f³ÇX²Ê ^Z*ƒñ!eD¸ qZÄÍUž‰•8®Íb ]„«+( ˆ$výÏlõ…EìkRwõ2|¶ÃŸšâ„©º¡â(Y=òB7«Pï k)Ö®éúº¿¨¼øXUb5–q{˜du‚—µ¾¤O°‹^֥㱨b©æd¢¥Þ†úŒ·ºãÚ,ô‹Akgl ÔT=e …r% iY(£t ½œBgIpÛÜÞeΨ´{BÍÈ £–#àL§yØ•ª†y³ñ(5ht]@"Q”=ߊwæ§Þù'þxŠÿäîr+ V7tÏÊr¼úpj*ŽÛÐõÉ‹Z¤>NÀSûD›(d©¦‹ûØDýòpÄœ>„Ì’Ãή=ÂÍSœ¡ ⺟á|ɇ¼K›5ç1 #Z[Zûª3t}ˆ·Ô6YþŒm2šºKL„ —¨qȤP BUkT«ÕZ-iÔXE33uZ×÷Ö(~¤¯ã¦p‹d?P}U¹«Ç$V51Tqf+ê#¬”Å” J&ðUB‚sýÛ% ž—ݾ=¬$êHÓq%Š@ÌE墯 ó @Iç-ºÙw¤´Yñ=î¾eļ,šy£É/ù¾“å„û yvïúÜ+aÑ Ð=ÀÀ ˆw‰ýûͯn~©·±e®÷~blüÝúFïbï'_]ùòxLºº»½ñ…î¤Óíïuþ¦Óí^„°ÃxÄâ‚^çBw|¾»|÷Â…•Îz½‹ÝÕ Äßx­Ûé£-£KP¿ÓŠƒÙ`ß{ýpïè裘+oUˆÐ×÷éÚhïÕ²Ûß»íô—ühèGV~¦DÍ%×»m¸auÔÔ´(uéF Bê=Yx’:·éjc¶FkZ"yÓ9U'u5Œ´ZÌ`ÙqÂs"çqÁ»¾ ,x²-ÃC÷ì)ÛvéöŽ—ÿaü?þÇ»ÿ8Îöþøø“•ñ ÎXåx#Ô nm☈Ǒ+H¶ªŠ¼þ 2$Ñ¨æº ¿ÝÌÀ—г¡ß¤>(ïnC8.e‡·´ 5^yÏu¯z,à•xÜœmBŠ4 ÝRcÎqÑ.yÈ^˜¼á`}Ú¿¦M·î½K»ö®ƒ¹â]­Õð>H’ø$Qëm=Ô1 ¤àaðªÔ¬ŸœJ õZˆ¡S¿P IDATWÅ–YôFÓiŒx «€j ­ðø31ö ÓšPrvÛ—wÛKKíþ@^-h‚d‚삜,ª±ñ’iÂç ú£G¸ÏW—c!€‡­ðKC€•GŠs‰$ÈÄ.žçÞ³{ÀÁ·½½¿+a¶Uhåà\–WØç¶$¹w~ܹ1s~¥³BÆhWð%¬Î—.¢’èö:ÏtàŽØ-ƒÚ½ûÎÿv x·s÷î;Âå»wA„œïÃ'îßíõ”Þ2¸ƒ¢Ìs’Œåx80Ñt_¶ò{4Ç1aGrAŸpgÜ-.:™mÇÙæîgîïi\T’i•šæ–¡LÖV&+J¯Ó¹8¹¸ú£ Ò=?~¦£½Ž$Ž­u%ˆ8ˆ®åÎñ;ï\8¾süfÛÎùãgîÞ9ÿÅÎùÎy@À/B*þb7ÞÛá‡Lðêz-”üm†½’’lTÓŠƒï6÷îžT삆–3•¿h!„ÚkkïIˆæq… —5ó5Óú«ä1ÝÐÆ­#~©Hˆ€"GóWÑÂ,/Aø…‰ МµZL0!õÄu¹ïEÑ "0f{¾¸ÀDçyåªx­$%`76ÓýíÿöÉñÛŸ,ÿlúm c~<ßÝ06 Y§fºÚ#jô±¹ á§âØ JBO—h´‹6èõÒÙ{ÃÁmy—J—qú&À–;ŠÚ¶Í]*áåígúýKŸYÓK»¹ôS¼3À ‚»öÒ»Šõ½ÌÁúòËÛðÆ8ƒK¶= špi°ä祻hM¥a£Ü44f$/ô•3ØXî7µ‚…>Ô« ‰@CYÑâ RÛ(Šä bx$‰úþF z7Ã×#–é/‘p’Ú™JX}À·—5½ªÖ)‰%ž¨I+?Ç›ùæ£=FÀ1ïdyˆ8G« oæEiçr dï»E§„/x¼‘¹Gi«u2Tï-µ¹¹µiRбkßU;x;s V/|éBç—@P@Âg€Þ½{ŒÀqø3Àå À »Ë“nš”; @;wÂÐs‘“n&2o >WŽ% -õ™øIÅÁÁpïµÙÃÓŠƒ¼:_€5ùkÝ¢[zè9{\~8 “ƒº¼ƒ)(Ûrc»Yv  pÄúýsýöT¿_šê·Ûg—†¦·mg0Ävtø1ôp…¸ƒWî\ÁKàÄ8î¸Ó¥ÛKödŸõÝ\ÎcnhaDI£©†ÏŸqÜK#©^/˜j¶´Ô”-”<ÙKB%" -Ò” äqxGB¿ŽD É:0Í}°‘`y<ïg9-1ÈûZÇHjóÛycO¹ õ‚fÀñRIšV5ê4ÿè@(åÇc8Ò¾W\|·øîÓ‹pç‹ï*Fº±ÝjµÀèƒdCé’HZøaåbïé`^¸yHÞ]Œ@8ÌÆç±”T$àå¿Y¾srí;wß9þü;ã»AfX'ê¤uѼS\A"¢¤&ÔÐ?­8^Œ¼Ê¡ŸV´Ôùjád[B·ò›Íz¯U›Û ?¬G—<Ï èüƒf³y¿.( ã:Ί°`åPxO¶€5l^ œhèu\o&èlÃß EvóÆàÚuÛɲÏ] ”›Ä%<êde½¾iI[éüã1Ÿ[îÞÿìxÌǽÎJg²…§khë¯ÝļI­]¬—ÑÇšŽ#d:'c¢žšüYè˜`š".¦®;=í8SOÏ)ž[:Û/µáÝsçìïŸ,CšWþNøòÑsoFQå£çÂðZôÜAà ä.ÝvöàÒ.°È24ðoúѾdï— qÜÖˆ(AXÑfŒf³ªü™¥ÑȾ–ÌМ^*ƒðOUŠp“SS\7¥k pؤ~Eæ=Y‹Aªð®*«œ¼‡ÛèaÓ_quŲŽ;Ku]“c–¥–·¼GФÀ“ \•yÌá=´.Á/ˆá®Sâsð>„6Ìøa±ˆeyéAT¡Ú²’,ñÜ¡QmJv©œ?8æ`½†X‚Ž ²°³Š9Ÿ÷eËe}âÆšW#]u9Zì/,ÈlA*‹‘¼ ”Jria¡„oì’6Ú˜il®¯‘^G[9¾{§{÷“n°À|<îë”<§(/è=‚Ҹ鷋Ó"àÊMJ`P?5ù;Bú•Â¥"<þ60µÏ¹ì #*î¾ß>ç?Hw!\OnðÖ˜›ŸŸ¿5?W£?¤ l}ñýÁÒðÏåÀ·d/A :·xÃ8ìB(:—¥¥K»h Ê8¦—uÜs¬ýÌÒïJ™þebƒ†ž„B¤iå=B…ÒD ø«´XM=H”²*xH¬±SŒ“!¥x\7»Åd_†LZ•<®×RP¤8!óyXjyëÑ­ª$Á±^ Û8LD³iX¡?8·¸¸Èqâýþ½¬Møw äfËÄÔ;W/´êV+±€•µ*Qg¥»"Cþ]Y]×W»x¬Ñí-w/,/÷.¨?ZU;«Iôß1 •È•n÷Kˈ0öº? õu)ë ±Þ´SÊ@ü ZP›˜yõIÅÁaxø—Û®{8z9­8˜7 Ö&ð)X÷\…Öa-©È<û¬oÖ LŸ;ü੺S¥ ud#Óq›¢€gz.Ïû"ÚìH‚Ÿcs•Z%‰µ˜SH‹¥ß-À톿pc!¾É®.ð%Æþ ™æs0/áÙ<(À¾Ûø¥%FÌ4Kð@(*7› †ëõ3, $uR˜Ë[y:k(¡¢IU%‘ ’GÕq@ä ¢«O£b@E­`u²6|ú,fQ},:DUrYp§íl&ËãD9UÉV5Ÿßó”$x©bò`OKL•ÖÜTî\»¸ÈæZË:9~ÿîA!o´*­¹¸ÐªnE&dá‚õ‘dùâÅîsñ¸7yfù<\ŠåÞÝÉòä/'ËÞdm­«#ö:½qO °lb./»ÿÔÂV€ œ.¦Õ¨™R&Sâ}ɯâxN}vî3ûÆûF4Œ¿ÃØ+€Â7xã­Â¡ <(Þ—[|XUYÊ¡ÆC…ÌUÿõ(›ã"óHÞã…U®ò;2ö֡ј¤ÈãsèÒMHƒD’R¯24v\#tSy^(ÙׯÁµvù ÊÕ{l85Æd‚ú}ùîÒ9¶‹;»8Ÿkƒ=‚­v šLƒäÕ¿µë¥gz:ºNUþ‰B€ZæZVœá×]ʼWÕÊLËÈ÷n²Y©ónÛ%JúNRàÁéXÕ*V¥aÔt¸P§¶Ñù–arY*---í‚^@0:)úõ‡˜ŒÀ¥)à’öÔ.føËÎ%ÈÜ»ªªñ=wh5`€sf â+ #ò‚À߃´«ÄºªR˜¢Õ´DCÇ\)RÃFu@=^­.“@†„ À&x¾—“E>=íå%Ì Ž(¢¿C¾=ŠàWÆŠšÔ-©Š>Câ2¹ídÏ•îÒmÒúo¬b° •@àÃBá¡i=,´´ÌÈxO.h0@6 z+|;X¾@¸Ò»<¨àX°8unÇqEßá},²ü2  ‚0p±¤Ô/-fÜŒ#°¬“ s¾^ÕÔIü¹Šƒ½ƒ—ËGÍ ~Rq 3äÚ#Á‡Ãsaœ¡â\Ä ~7E_ŒE¼TŽ…fhÞ|ôˆ€¼QÕxB”Xæ%?â<)è/þ¼¸x¶„8ú=_?¡|y«u  ?„´[hÕ,+.˜ƒzÁ*‡ ‚{½Iouµs|Tí…q÷üøí Ëãåó½cØx”ã²¶“Ë0ÞÞæË xæØ÷V´:hVÀÚ‹Å{XUxÚâ»TKH¬®â@;_>ˆkWÂáIÅBë"äÚLý Pxå47+à‹ Ý«(ÍÈ(GnY­ RÆ6Âm3n•›'x.àmÞÇf;t뤜TÚòHÃ.’ :%ò¢knÉ ñêMIJŠ*é*Ä‹¡©«k'Á¶<ñØ/ð‰ñÐatM—è'4˜‚5s )œ Ô”‰2FcëIê9iš…­­´\Ó²°ÆÅp=OkXZý¤É íØÔ—Jß@¬¸B&úÇÆ·¨! ‹jc•ߎº†¶ò§©Ü0hM) NiÚ¹ô$ä@>ÿû 7oúÀ)0ÃûA¬7ý3ب­aIþ-Ó2k$Cèqà‡¡a§*Hab¤³Úe²UB•Ütn›Ÿ¾ç¹%û—Ë x6ã¤yàDYÊäXÍ¡*‰‡Go”æiMP¡,EÓÇ!?Nª•PšÚu¬4ø`Iþ;åƒâ*º¼Õ ÖƒV¡b ¿‡‡ñšf¬ª+'«kk{¡‡§¹çAóÞyæø¯%ÑdÙ¥q²…)Ö.ÓÚåöeâû!Äc2°Í³œgÓ\ ‹Nÿ âÀ’•Ûlÿ ¬8¤Šñž‡‘"¼xxt½uèeÝŠà€ –›ZN¢ï¿xt$©uÑ>wvjŠ9.ó}[ AöbÅ,Ç|œr#8B6IÔ$¡Ò zÉrîŠ/^•‹Wäk WYIŽé†v}m² q‡‘—¢^:’ñqGÓu8]S1OAúÕÓ™±ß¶á#}’J`@?¬‚šÀ{t5ŒÀ´¼ ¶ZL×»¢6S7ËP[Õ>¶w´½þîÒÀõüJ­×N œnát—B¡öxsÖ˜Û,À¯0@¿ž¤ò|kv¶`ÜJäsŽör¯8WÎ¥ÆE©šéÇ)§ôkY TªäÌÀöù¸a SÕ¼eÐ2\ƒˆ”kÑoÔZµA*„÷(àd €î!²:ͦíé)ŽçÏ9Ù¬ É‚—œ[b ø¡8À ­ÌUãJûxw<¢å›š¯”Ü{@ý™PFf–qƒñùƒTˆ­Ö‰Ú°*-ãSÊW€¼{0—ÏÃJ«[$<ø¾·0q–Ï£ Yž\ìþDïñùîžyûÎÝwîÈ ƒáìdЂ¨XÄŠ܊ʾ’á˜3s@édJY,œvœElã´Ó-ÀœË¶!2B½‰Fû\ÅÁȱxü{ÅY§xrËãëzœä€Ìz ªà*Q) Vj¡dÏ9Ó%û+:èxâtÌ<ˆt¾äºî#µZ\¥bÄlÑ_0*®|“»f§©¼¹ehD’ÔNzÀ ¯zcÜVß]¢!Cv;òÿž¦âA›Šx2©XÿÖ ÍÄX*#OTe<&„DC4-4Mjnm¡ß𑾯öŽÔq—¦…WæáÒÉhïþÒ.ú`‘ïǵÊ´;ÆÚ¼ÑÜœ‡þwJ™§OÔµ©W÷rßóýp¸‘÷þûf#O"iš¿*0HØØW>#R Å›8Â.ßÒÕR²65%Èq9QPHh`ð?f@úƒ¨ÅÌ:\N¸îÝû|ÎósçÈ2Ï󘥕ë͈¬$j„ È—¶]Á•¼®¡ÃB­[ ç2Jå°,ËJõÑ#]OÁoÃì¡e|Jù°H±Y ÈÍ‚` ZÑ𣕂üíaÕDoÉ>—õs;׮ݸ‘=¬!ÔDô׸¦œN¡ÆÔÛ[éNV&ˆ„Ç€rà@Ä­rS}^Soè†~rÓˆ®›9_¨éèkIc²Š'%8 .e/f£Æ÷ÎÁötÜ…4ÐëýA:Ú»a„…~€ZGkÕ4£çMÈÁÈ· kë4££D4SUS°ŒQ¤bä½_;™i£‚ra·£(*§3ëP±²W ¥Ú™¯Üºúo×$tÃI`/I|Öã9– rŽwPG;9#険¹‚|š&Aq>+äüt(6ú’Ê>Ïqhà#‚щ^…GÙ>¯és6ÚVÁÿ§&œv[&]‡•ãjõû¸xjµï# ÖÙO)¦Û–UŒFÚGϤe}áÇ®éÝ ãå/þ]1¬!ËJ¥ûÓÄ`гØon·Ï¶~ý«ôöËáÞáá+{{«=î¹²´Îá@™‚wygaVµäsžV,D–‰ÑÈ0ó†**RönßöZG)T ´Ó™òdTù /z|Ñ̈·}XhðT ËDŸ—-‹ EuÑ×Åc¨Ï¹í+9þÜÂõìÿyÝ)g¯l~€JÓô@á¯.§€|+ŒGXqQ×d¬BEw7H­$‹89‰As7•¾  å1|ƒª¯SýÒ‹Âeœ=í5o¤GÖ¶wø^d¾_5š33³¦ÇÒÑÞ‹£%o$ø/ ´ÄîlÝ´j'³_¿¯fââ4£§ƒ¶ ›ð0±ŸT0ò’µD»vxµ8òo³Ý’ÝNgÖk˜6T¿g>üý­Ë·nÝú-xœfP Ù,ïH—q4Ú@þeQšúlè8.Çš¦Š%– )¢tŠCƒís¾Ïá¨òàñ0>æBÒ±b ã¯ýóö"*§´ØF:@XŽ4àè¥U¯ µrJùNÓm¡vd˜F½ !µ–µâP“$9ètåÎr÷üùåÎ… r÷‚‹§Ø·øï·â¯ñ/¿úÕ¿¼÷‹áÞƒZÃá+ÛüYPïÞ+Û9¾$:>Ïp,0$›ÿŸŠíº·e`}ˆËÈ ðºõÒ¿U^øÚ‡~¸°ãÄmÚ0t„ÕŒ´TÍ?!˜'}Ft·/ µÎx5 )/’ÿ‰÷p+Ãñ¢KÀƼ´[ Ï:0ÓÝÛqv]§ß‡$ùnûiì`ºøôÓOS4H"ÕȧÇnV«^Ã-¡Ï¦ÛðÀIJzëd«¦6kÁ‡ù£ƒòÉ…ã;äŸEŠâARŬۆWç°V¿],Maç\˱­Œµs¿tïþýÝÖî/i·€\ß³ï÷ýËáþю˳@ô™øJÎ c´b ÿ¡â Ü3u5†…ÁZG¶½ $ Dá’ñ!sã1=÷ô4Ä–“=æ§žzÊNMµÑ[]‡E¸¢ï©üˆÝfC®ë Ä0cüy×EáìëŒËè’¬ÿëBžÞ·:¯.¯tƽñxy²Ú½Û™t$žŒ(„Á®ºÀ "!žxÀ>” Uk::œäÇ IDAT‚ýœjWßÀÞJOÞÔ”öaÑ•ªŒ,:P FÆ ÿ""ŸA×uã$S09EÀ¦‰Ce SÇöw$™Iã€ïØÞÑrFµ¨V‹o³¹'¨Æº9Œr«°•?ýu'x < X2öÜú¾?½þÀ;í99Á4°‡!0"dÕμôÒK¿ƒ—_ú: –ýl%…%®*8-Ýq`an©èb™î˜€Ñ é–9êâ“–)‘¼îà~¯ë”pdXé^ñç?/¾ûóRÑ)– â” ™Ñ)¦2"R’í…r\3±"¿^Èß² ߯ÕZ8¿ç$ݬ˜´®·Œ–Y˜mÐÚ[õ¨üZ+šWîòb,è.ÎÒ],a_&–Oï‘[lû¢(·8fI–Ãmoo·Üþ×ÓU¸»;½qô¾ÏÆA :|B¢‰ ø*†’¸Ñb…ƒAIÂzðŠ& Ñc…¹ÓÎJ¹0àY.nÔ!¯z6ÄYé©âÙâ9 !<Ÿ %+çGY—“ÂwÅq"2–ñ>y?¼.)ªm_~I©²>Y»“ª•Õ1Æ]Š€½qWíê›* [ºÑX&]¢“çq¼fÚ›™Þ°AÓ°J• Çx {I×tãEúØÂ7ÝH·íNТ-kÐj‹b{Û Í÷“™z½Ñ4}!íT¾u0+ä­2£Ym~þtªk¥Q™Ã郧€zЧxÆ—7) A@/°{=À ”Â`ØW°FÈwáÙ›9óÒï~G!ãÍ—>þ:’Y¼›è¼m˜rW7ó wq^ŽÕ…¼›7Í4W¦„éŠð“-BQ)žÊ£¼(þnñ]H{Š Uî¥m¶XPtýÔÒ*½Á܃ämÍåáÉXëæOŽDj-Hsiºd4’ nkfÙ:lUŽºùòáæAU©x¾zv‹«Š_mfç¦Û¼¯T2¡Ü’K´Jm¨­ø¿ÿßÿò/¿üg ¾_ÿúÿe-öG»£`ïÁÑû£‘wtµ–JÿCÅ{ßÔrò`­¼SN°á0 ã°\.ƒè-»„aB\!RLpœÝÒY§ÈÇÅÙ’ïç“Ú&Q©IµHÒÎe—¹+ÞUþºt­Œ[«k›+“µ †ÛdeoB‘¯Óëâ'‰Æâ Ä6º‘4:ÁA*žƒLô “ :O£ ùÖ.GRXÄÓáõÇ÷M­ÖÌÇVš3gNU0D#* :d¯yΘ~u¦^Móð`oè¹}×»bšG#?©Õ¢ ê,,ÍúIö¶ê*öùâ@ÃOðO±Î´nCêxyÀó°ƒØsÚ¥]dž|2³>ßqÝœ§Ÿ¹Q¦ˆÏô-óCú¸¬›ÀNœXÑ5"¢yÝÀ6g Œ(O @=1RMÒBkßò†ûÔð&…ä94*&}75>sú̵ï¹Xýé§gð¥°.s±“öòÚ,,'p¿7¼öì³ëk½àl Ù îY=l¡ò°ªæ!W6fgLýpfŽT÷“Ãü¢5t¯â:,“I7þ‹È Ê›„Iœ*ÆÂ=ÿöºÓrZ|®uߺ׶½]H­û\k¸;ö_Bt¹ÓýA+Bæú\ÅqØüa#ïµJxЇþ !©Ö€ÕIRÜ@$§Øf¨}y>ÀÚv×Å~æ¬Í84GÆ-;ç‹õp¦V&bÕCûr–¹ 2wE¸.IW¯3Í…µµÉÆ€àoþÿ×ÈÆ+XÛU(:¾"KÏ£ò}þk:dVPQUôœVOÒ°®«ubÚ#ê߯‹hȵt´·w›A¼8Fë,Ÿ>š›ÕSlPaÂz¨'xjádˤ©qÑkî`i´wû¶<Ç F”nßtPYϱøÌÿøÿöáK˜‡?úÆ·oµôÙVJÿp7i²-,ðJŸÚOe"°¡‡%ƒÛÑNC‚w€"-€8Å©žš²yõiP{ß-½[*wïOã¹*T«¨8 -âHÀÜ)-¿…`mkþ§k:]›Ð?ýÊúÊêÚŸ¬ ó+ ÄŒñðV T 妆VÎGñœÿVrt°÷ nQóP'¾ïdeÛÀÛ€¼}ŸWMEI¬JkáÅŒoüöÃ[¥c­Òcælßßfl§å±–CwáÖ:-X­’›¨Šn!Í¢•?¨80ÂJÜ(ù£Ý 8û4zœF~†yYV.+‰S\| Øn–q8VÀƒ0Ín3¬9ÀŒÀ<ÇKšú1‰³Ï…2àâϽfso_wùçTºE)ÂÜÊd%E¾•UAˆ>ø 76Gq·7_z loÂ{é† =ɾ(=(Åc8bñVndîj*¡Ôð¯úx¼f|ã'Yø~¹Â“m˜×]¸žíýD38òß?8uÜÝÅÑÞì0„uÔµßW«-mN¥5 @ˆÊ$`c–?ÔO·Pñ@HOzNØÀ ¼Ý2ŒãÞvtf}ˆÝú.Ÿãjg~{ëÃûí‡7žÿÝ×é·Œç?ƇA-|LS–áqc­§›tb š7·p#æ_«…þ…´pw•òªÌçᙦ!0õ»g UÍç£ O‘ª¬ O[}¦©74µºfl<Ͳ±ñ§­¯|õO¿ú•g¿ò§«2éफþ—µ”j¶@åýÍB¡,7f’z¥öªµ_)öÄ–y8£ãlÙ(pp/pçÃw:òßzñ.~ãþ^ÿmëiU¯‘ãÐÁ-[ßrE‡Ñ!&Ž€pY²¢Y&`‰ñð*€%3+Dý¾Ns‹OŸ-…Ó! 5žXI)“ê"üå/œcH6Ùå·ya{ûùúäð¡Ül&•ø÷•œç.¤öR×Îµí²«~Û‚êaIÙ8M½½UÄÁUFÀ½ÞÅ¢z,øC’@$Ÿ,㸫'Ê Úm?囿wÃèèËñÃï†o–1[uk®‰ÑüPSî Ëðq¯›ºÙ/c!j¨ \ðG-T=—SG­µÔ~b2­ê©=¤¤FYX«D¨àQEþAõd¯ò©5¨ËѬµ'ÿ)ãàáÌÖÛ0{Z¹çóâX)Bh¼/œ1x¾§Õ‡ëÍOø¿ãû”(ÿ [¡G~”u‹,MZDq"]6d% ƒ4ê—°½}çÎÉ{§ì÷ ø RðEøÅ#ª«Ë" WUõŠNVWÕæ´H¦XSçµÛ«c4Ö×.ŒëЏæÅëo]°(I¸¿]Ϩ߭Ghú`®]<ÃÖ4àí›!ÐQöNeüÃ+ºG4.Jµß=ÛªÊj<`Uû”ÁLøH)ËæÓ{ Ñ{–­Õ‹¨˜Ð‹¿GM<¢º‰»MÓEžRíÌÚÚkׯÿžªæÕo¬Õ³{ðj8ˆ©xØ¬×Ø.¾WOht3VŒtB;GÖ³.l–©µM:]¤QK´IMéaÚ³zÖ]•šÄí¼øõ'Ø|qs~{ãä/Ý>¹õåÛ·oþ½ÇŸ¼÷Þ{'·nïlîìl½|g«_`=\>Nákïû¾gfUÚ·™ÛM-»ø‰ý¿”±O6µ~ÂôôñOXžæš1Z®“ïhˆ€ÈÍå†ZeT]%—{ÖJQõºêO+oÝãkõK_º\eBõà—¯ÿÛ¯«`‰y¸ļo_ìý§Œƒ8éËí°=S•ý/|!Lò0wCÏt–†vŸÍ•áJâ×y0H­ÑTö!Dµ9K¤¸Šá´ŒA³@ý\¸¤ªz¤+âkШ¬¸ƒà»EñÂo¾IR:Ìç›'[šYÛ\ƪªœPµ6hRΟ:¯‹ŽðÍÜP¨î ¨jæ®YMË0õK7nÄf¹S·=e¦¥S:´}šZ2 NgP[Z'7=hàÚ«7…—”›7_íª¢ ‡mvGeŽ÷ñx¥_YF½"è1kå^ÛÜ¥\=ÜÓ÷HpÆo±!ÕÔè”óǺ7 £Gqì—† Øm^£ Ÿ–g>~cíÒ§]zå§Ÿ`É0ÊeTº¶TWˆ%Y9=^Ù%à_s㮤SOWpb̓8Ã!Yˆu¿Ör|Q;¶Ø²Šùßo|eccþÂæ­Ó®-øûù{€àÆÆ­“÷ðÍ{Ô¦jç½Ï·ïe¯—„~ga½~ŽNÓ:“IÇ[É%£ÝÅ«6cÕ«™™ÅÚÊhÚO¿WuGzö8wÊ0RÕŸYfBmTƒD ¹ Ñq®[©¶®&z¡8Z­ï)Â뫯ÿõå/UŠ÷ÿJÒ¿ÿößTü ‚šD”—§Ü«ìï9Î8È¡—M«âR÷œç'\šÇa Ÿ¸ ®ÃΆRÇþaˆ—•sé äâ„–'Q¬PUî•Å$ŒBIQAËcàUѲw“90ßQc¸1߯û\Ói›¥Nª—IÿiŠrEA<¡Ýh¢>ŠYsÚ†ÑNTj÷W—2ª ŽOÏŸ_}íif]gÆU-5Ìy…÷N;]Ѽӳ j÷¢Õ í»xß™ì7/?ÜÔÝ#„à٣ü›—ÚÈ\ÑÆ£²> aï°r³ÑÞ=ÍŸ¶ÿ ç:ÛÆÖã?rÞáÁå<ö¹á+ûßÂØëÔ¸Z ý$éŸùÆÇkŸ|òÙ§Ÿ~úÉÓo¬­)ð¸¶J ó©Í zšF> l8ÿ\ì]h;I4fÈ¢.kõNRE§‡Ä•eW¢Øàí¯rœa‹’hv_T6nÝÙÜ “ô¯¨_Ù¼}[<¹õ¥[·¿|û+·o‚_záKŸ,åA/ŽéÄ”M5¡m=Ja°VåÙØ²M#uFÝQ6ëîZ£CÃ…Æñа§½j:(#JÈ(Là|\s¾ñ~¬F+÷F#kE¯ºzæ¢_ò¥ÿI©V«Z:øÛê·¿ýUùmÁ¿ÿ /zðí(*T«2†Ý?g³²Ô‹HÐ:ÈB0ô8¤ʼnïŸuG#}0”†7\ß‹"߇q#jKÑ:ˆ“Æ’i&yyìØ¨'^Õg«æj«6›ßÙÙ æ#Ñ7ú`; 776!bÞÀ x©I¢°¾ÙU ¨m(`E•r~uS¯µ‘ pH¬p¢)æ ±ôrpþüÕ·Þ0/ºzI¸d@›±² tìZõGÚ@‚ðY¿ÅŽ<ª¦ß9«éþ¾Dªâ'PmG‡e™¿¯Ffß\9[ÃŽÌÒ²ÆtLø#$8×FÀ†«dFݸèàà[Q¸÷Åɤ èAŒ|+|\{¢ÉŠ.'~˜YàÓË^ühmíkíMÍ!¤¼ìº‘ˆ¨Ú–)ÛªÈTüϲ,ˆ¦Âá DZ â4”œ¤È´QÂ*ò$ŒRÂÚ&+Å,`Z«¥Ê-·¦hë›/ª/¾8ßxqcã+ ?xûüäóÏo‰·n¿wëöÆç·ß”^ñ«Ø ý¥%Ìx½…%ŸÖm;'…ù#+ƒêt~lÿ81íØ™>nì)Kì£\g³ “K anG[ä­ ¡+r6„.IpãŸ(Éj­ªŒ³P”®ÚÓi­Pþ¹]Uû{ëqû‚ð@+ê<¢T^Š"zª¨épˆj¡&yžÃwä]pÑs”lAÇÝ­/¸F# ¹ŽÎ‡‹¼KÝÁî#¬.z-ŸÆy¥Š\B®i`È…Ft1ÉéµìµWx´öímÝùÉÓwøŽM¨ÁÍ ukCe¦|IKV/%ªü/¬RQÑØU tìKÒO7”š+ñز¿ˆ4Ô§fÿíágWWùüÅK;u³¼ÓFT B T‹Á0º±ã Ø1V  ¬>’÷ጃÇmòn™kkæH›z-÷ØÌЭ‘iŒ ~LØ¥Î)V5µ¯øß à qxóQ$?Œü½›¡ÿèñÃØûr"Æ‚Çljy棵+ߺrùÃ?|úá7Þ¼r\'ýÇhX1P‡k§ª NQåEÛi ª rŽ!×ÓJÛÔšA+”j9hÓøU³²i§\×Áy¥ž{ÈÐÃÒnŸl|åÖüö&TàÉ%÷¾xû…ÛÔ¦å¯67¿òÂæ–œwSч²_‡ &Ðõ$:³ÛS³–•í™Ýe3{4s²C«l·#¦™#6Ë­èeš”Šª©Ð÷aä%T¹LÝù cnäи•5v ×{zº\éø¨–}»úöþº¨Tûf¼ìßþíƒç>øà—0JB"rÎû>a£uCJü 4åç~±¿WÏ…Pžl»PŠøÝ2 ‹Kˆ¯ð M—SÄ É ˆS³Û-ä®YMèÓeˆÆ¢( ¯]|•fîì€îhÛeë ‚xWéW¹¬È«ò³&[W u uC5ë\?ÄG5—u>eŠŠ\;É‹V[ïúÓ§—’4¹xñ¼G Y¥Zèãºé3‘×é@¤þ­)›YS€Mm¯…«¹¯ìûá#ÿ»`ÀøûpyÙßG¬Y8`üô)îÖéX€W8§pfôìØFPÆy0¡ëÅõˆ¼t웵yR¢˜g`‚×Ö>üðë}üégW¯\ìL]ïQŠæXšöã$W•ÈÑhh8Ú 7^ð"[³H®Óñs½%HM¡MÃîçL6ØÅ]y±mË ¿Ô y«PoS Û›2ÕvÝ‚þòí/ßú\Hñž"æä;¥NÈ|ÈþF<,£»–š Ëþ‚“¶Ív:ÖÎã¼LÙ¡Ùflšçq…ÑKe&+T‚žòI2 I y–5ÔT$½+¥j¦Vª\J=iY—*/ƒó‡³ãÇÙ¿}ðöÜ¿ýë{îY¡Ó9¾ß™¼ïù­ Ïò\¦> ñ9w²»ÝäÝ!ßtƒ–2XŽK gyŸfà,R‡AŸrÒ¢ -{EWU-‹—Ã`$ÈW’ðµ‹ÂÞ^÷’éX;w¶á4 „à;sÚ|Þ¤6Âwî̱ð/+U ËÔ™PQ.ŸnAS ÆQVÕÚœÖëó`hB˜‰2Ÿ v5­â[¶õôÓìâê…‹G“K;ýºf¸¸«Wu§ƒ+³î~ªTìøØÆ‡ÙÍÄf3­šÂ½M÷üGA›¥_þ°«è#sÜû§<îCóYiáŽH†¶A~V”ø›“à¼oß £C/<ønP{ŸÂ‹9öÁ€kì“+kôöé'kkoSÏM*ÁwÈÎ8˜¡ˆ-žSŒ²éh’H}îÚÀü†eÐ6l¼¸†Þ¦4¶]­Ñ¼¡µa1Ïy \ÔP*=püp¹!ƒú6nÝþ’F ‚n¿·A©¾·Õ¢HZ/,¸ÉúÒº/…™’*BäÓPË÷ív5c¬ÊÙ÷+–Wí©=3ØK'yœ›‡¬´«ƒ!‹ÜNÜ‚°øC™ÆÂÎÐÂ:"^NTC xIW[üg*PK¶cªr¡¤]»L{Y/„û•%y7f³Ùå)hÂÙ~ ÇÑð& ^ÃÛ£Ò&:Íp)ç–R;i ÏûGišv.‡Ë‰{¶ãò\,ùÍ`Ћc/ãþÑÈïã¸6/&2Í1n½òÚ£½H×MšFgllÛÀÞÿAÑ—è&xóή)kÊÅK`>Y¢¸zR É¿úìMÕæôLæ4ʇ²°t#½ÁÏ µ†B\]Þ1«ëoÒôò¯¨¯®žçÓb^—‰ ®1‡ÎàvOË2•ƒ€±è¸M2)lꬳã=š„h=¦t¬ÒViÒL=C 8ìÞ³1°FÉPu*a±ãp ŒâSÎó‚}ßu~Ó?ðh°­}ó•=ÿ¦Ð_ñŠžvæC ¸—”µ±õÑõë­^1l›tÚŽ¡| M¶dS£‰ê ;ËÉÖåŒlC‘tSmE°~AùTO?t„– ‚ÔòÆ:X Æ™J i:5ƒú_ ¢VðÖæíaŒoÁç4Ì.¼‰·´à®/¹¢Š:¡ÊQqn»r¦uNÓ²cfϬĜRÝ**û€•æ¿F¿c³Îû/Ã&qÞ72‰Faj¦¨ýÜ0þ~È….ÅT OÃ(¥"é)¶F ¨è¡WíÏŽ¾ýKJ”ù7õy¿9©<*X]/×½Ž˜RrŸ×¤l3ßÕ¸çD~£µ¬‹Ïó¦&üK 6)Ψõ§„9ñqoXôjYð‹Â1ý"î’SõeK¿¢ëÿÈ4Z¬ó¼çº“†¿:¦ ‚DI‘n+Ôí¾®·„äSôz’˜Vc 8$ìÑ }B•ðÃu'£çåëo_ûìrv¥ÿwû¯^ˆBøF­ØV˹föåmµrê${·­!{‡rKŒÍn2"Ã㯚Æm†'ÔÕ¯ýGe=„YzÒîe×à»PÒÓ1ócª§;›øÇ5‰Úç=ü¦ç>:¨7Ûë“ý=ßS5Yö3.×ô3oHW?dŸ©úÉëŸ\¿þßWUæÉyŽñ¬§5‘®»C ‘8°M]’d­å3+Ve1¨iË2¥ ²1qqÝÍêÅV©K´f:¦¢4cAh5Ø*ˆSðÒnRS|8ÁûWŠ,N“0|à­Có­w|Ñ ~…8*e©ªàs&ÿÒiWÔƒÚïÙ?`íöSM‡cèŽQuÈÌ8²r{Öpë^æa^„A¯,3y õT97˜U~*ù¾Ê-6Ü_Ü…:× ÊÔªÈôTO~îU Z¿Âûþ£@Ԅ燊pTMÐÙk,¹~(PšãõY¼œöÜÙªþÍêþ:Åê*bVÆq’H^d©Ì ±f-›ºf¦EºøjÓ½f\jcZL¢Î4îâdýì’æs‹tN¼°è-.Q#>áîæÿ‰—^EÚ,C —u½%%Ê_*ÐB ÍERÌ9U»©  Iýtep—ªÜFÔ¾ ríÚÇiöÙgG{ûß;ÿŠIJõüNÝ+Æ¡—èÄn«¦ì¨=­Ø;‘FçlÇñ‘!Äø/, ó/NâJ(ï¼È-b?vL4u<²ÌÁôøx¿s4‹q«KÚÙü½SõWoÊ[¯<ôöĘç ‘ŸQ´Ïô?¶Ÿ~ýê‡^¿týúuCv4q¤§×LhH g1•F‹MÁ1DE€¶ ¶©ŠœÇIøO³6ã°?:¤­Õ– …‹9Oµ%¬ØV Ô§H²/‡ŠbY>µTC•(ßw‚qý´OYÂЃC,Emr _p’/¥wÁëXeŽ­+eiôŸ†iEY:VT Þ/|M<[AôV”ÉíïM&=÷~““…„÷Ϲ‹ï 4×ÕúÂs‚Ë å•Bíjæ²nYj,ca§™˜ç"lvišý *ÒÔ ^ Œ§dqg·aT–ÏÙ|Ä4Ë=ø¥s낯÷—8„ߎC%Í'«›z]k¬Ô…•ô%(Qãñ:±—²³1ªY7µ·(![7¬?¢V•{{«—.;–¡õ,­K2Q/hÓ†ÒKÀcµlŸjÀ€"‹ÔKtØq4›>CrN IDATM[ù‹“8j¡2í«Àƒ=™ì¸Ä“«þâGÚÉŸv¶c7Ú¯Õß«]þæzpó‘x{)õ(Í—{J~fí Hb®}¼öeÅ\ºëkÑÈ0:ü5r´‚¨6S ã5%€&E¥H<@©¯Ðº¢Š'îP΀Ѧí K£qSjË®‹ÁŽ­sˆh ®ÙÐ%›,ï2wëا áV]š¸Kþ"d Hô@VµL¡‰bŠRÌSUl;öl…9eé8x­–›öÚ”W<>tfì¨v..dšèt»i¤JiAÓèu=¢F¡­@¢S±žEWÚ$ÑD‹¦²«QÿAEͪìVõ~Øhhg¿PI_Á§òþ¢K#H<ȺT‰<ÿùEÿ,/GÔ‚û¹³‘ªºÒÍE¡Þ`ëë¿ËJ8¡˜Ò~Ü—“¢(𝵼:-¾-vX ¶x¿åµ¢8ÃôLRÅXJ" $[ ñ'¼>ߢR&šºEúŽÌo=]<®V³ žY½&”¥H”‰)•Êul¯e˜×n¼ýñjöÉ[`À4¸Du¦ÞSäb§¤¹;s<~M›ç:ÍÈ"ÆûŒ>èlf²šÛîD†Çq"ÉuØ7û»¥>ê· ŠÕ4¹ð×á/yÆ"›„Çmž¦þqg{5¸Iãdº7ƒ‰ç烇´tž–¢Þ:³–~xuõ¢3üÑõ?¼pýŠâ0ò!€ JŒƒ×¡¦Å˜¢#r‹-ÿc8ø ¦™ ÇC"J´COMêt,6f¦Õ£p––Ìs†Ç©ŽVΙNsä Õsc7ÞÌ€;Q’BžJµoº¾@# ]À(”TR•X‰Å WÀèe`¬#ƒ•ǃŸ)å=dÓÒ)È)£’ý‹'u£Ój*d¢,M ͤ¤žV¥ò"M Ë^h6\ŽSd‘ì‰ÃÇ¢$ ÷;ÍJB°­¼ªÉy¾ÖøBÅCÆBç6¿p[nGR»Š¸çÝ0d]o=×0uc®äm…Ëz71JA‡ÆÀtù¨Àsý}7Yäi QqáÒ¢×±8Po ¡V—¯An[*ìäéY»ÁÌÝrÄ´ Ñ Ü€Æ yusFÊy“ÿ§NÓ4‰ð«YŠCdI d9üJn6ræá³·ßþìÒ¥Œ==:zåÒ%<ÊâÒðq(i¨u‡a”"ÄUÇ1¢=³Ö€ï€ý¢P1LkÆe½‚YCp`›¦þ’÷æ {gWèö…o=¢lþ(þM²ÜßÿæéÎöÍ}÷‘÷ðÕ½–4z±îŸYû0][ûžIüäú‡¯¼r±ÞŒ¦]$Õpê,‘ÇÓóÿ¨w\dS@,!ákH@ . "·Ù2¨Ç*EbÓâd¯. Jo-°Æ/¹\«c±\Å"Zb+ ¼€rÖ—Á+7üHN•º¥h¾¡Èj?SsxükM‚6w¶®ÃèÔsshö¨‡|i,iaš$)í‰äyÒ Y)óÎúý•Â/-ç>¯AcŸ,oªX(tüA%‚ªäLÏLÉCĘœ¥]P(;·3i¸“¿W•ÿùšreëD"ù§n~ÎâáVþóUðߵ¢¶]Yfÿ^¹ÍØÚ7>º|9·ž^ âþŽYÒ¶u¡JB»Oݤ҈F%ÛV¯Ò©vî¸NjëD†ˆ­`•'ԅÂ5ªárÁìS:ÂôϼÉy<‰ïÐ9ó±é‘ Õ÷ ¢…Á#D^ùæ~t°&÷hoÝó!’JEðâÚÚ§øï“« Àó«V}biàÃt>KŠ,˺dÈ-G3$M”|^ $ÃäAxŠ(ØmkÍ¢F2ö q±c +ÑB§ÃÌ,jáz6;Ë1eÑãEQ”<>$®M»ò;ÊÚïx]¡Y pZ®ðu‰™ÙíÑñéB3±f½ÝªÿcCY&lêùœWlr{!èG`W=‘¨ç8õÖ´Ôün&ªð€W’÷›¼&ÑèxIP‹ža:åŽýúƒêŠn¾nŠ Ï‡^£êLŽrê}·és¼ ¸výhйðÜ‚` úˆŽi¢æŠÉ€¼ºS¤y¡†b7S,ê·#t€=Þç|™^CU ‚/&ñ¡ -¾¥HU ¥åM:êÈ£–l×½¹Ôéèbxîü’»Ô™PÀ=¹\R„ËZÝóTùœ_ÊøËŸ¿·9WaûA”ÕèÛµYß C}ÆÞ^»öé;Z–Å£‹”¼Š5ßÇ]I±*BD²óQ¤%I(°Ž¢±Æf*"1‘!m¬¨FQܽ냚Cc¤·ÿ’øl‚ž9º÷—Áx÷Âãðæ£ð{Ñã(úîà zôMÚÙ~èï)áÍDJãHK‹¢×K"àÚÇרÚÿ¸þéà•Ò šôžŽB´×ôl³E} u¹Ñ¢0ˉ‚Á·BGãy>BûEü…±jË·˜Ÿ²Âûþ‚ç@"x‹ç-:º·È5 ª~¤IR>.¢dá=ÉߣZ¶à$¨dEÎe!Í~M̃%‡P ãÑ®Énù è³Y™]fV3vXÿcâ‹?I ¾/d¸8BÄE‚ i} F›ëF¡ kŸH¢Òðyn.{n¢G¹šoÂÏYÿ÷¯ÿÛªèŠrå ž{ο¶»4ñ(Ÿ¯‘|}BhÛ” â?9|¢iE®Èéͽ’“ |-I‰1(ô,ïå¼[ŒL[c,á:ó¡ î²’Ž¯ 5©«9#øšVÎM&Kžß Z“Ý ‰ù\˜poÑ,tŠÉÙu¯jŽÆšœŸÈ'Eaª-danA@Ú«:ŸY:¥\ûä£ËJòZdêåŽ ´¤$ˆU“§ab‰i $}Ä£'æðž%±XÑYú§0†vÎM•¬nÖ£SŠ‚B0án D@fûh¸óŸƒ1ðQÂùÒXøÞ<Õ¾Yl•½^©i+•òâ?Ô Ìï×RgmíãëŸÀƒ¬]§¤Y•VLÕ©Ûû› ÃPsŽ¥ršNcŽšŽ!x>ÄL°k’Žt$ݦ¡ë¥rqºÈÛ’ØŠB À_pm«bkÑ[l¸TáT*£{Ëf@eÙQÐpñz»Üƒt5MãX@ðLõ’x‹¸Æ¢gëÍVŒ‡;ÔØ€YN_«œnDÇV~ÈO‚E¾žÙ÷À'ÎIa °>C•EÖÏi3dG+޶k¿a˜=ùÇ10©yñ°šÚš±Îf>ÈÏB$ ”›-¦‰hôi€²F°Ñ:Ђ Ç˱žïàªY\OýI¥â“>~2÷öËÎ’öó!3@ð‚,AŠP2ø¼ã1›ååQ+Ìêý¢H•âå'¼X&ø=“Þ¸¿´0Yç!zëLá¶Ý†dŒx>ÃÓbø¦ò¥Ñ¡‘´|Ìiv 3)ì‘Áêm‡·¤‹ssÊ9ÕÒ¨]ʲ¹EÍ×…ÏAW/ÍcÙd#§¢´8*©¡2këÚÕ·®ßxã³Ï>3´RÃ#Ö±º#Q“ ‚±Pibv¦©|Ìg©,@³^Ytü j"´c d—hl;žq™€×aŸ¥¿Ð€É{ËÛ&‹+ âc9|GìÑVT^ôT8‡$„K!•‘§¢Ì2‘Ã08£õÖœ¾úúß°¯›‘†ÔÈFÙ&Ñ9Moí!f¹-±îï@[Îäý[MÎ…n•™jØòE^Ò«64 mºÒâF;/M?¾Ç ÛP–xͨÓgi±iÀ†åL "ŽúÓO&ΤãO¼z>*ô Õr£È™¦)¹^QÊÙÔyÆ€Ç Ì‹ƒÐ©XÔ>ªÊ¶†tTÑa‹•Aý Õ”‡rk œëóXjž)u¦¿"H…ªH®˜("Ç©80u«*µ Wé]µT‰ß-»->\ôðbK#'$„Í>õÒÓ1ø7L©Ëq–¨YšÊi$åeÿ¸«%†´×¤Û¯4&(”‹…Q˜Á•G²Œ»Þ3¦Š¦qw°KÕƒþ’%XNh“t¨ßmï¶)k… Õ¶ )•8Ú$tø®ÙluÖùp¢qIÝ=ú÷ìÑrAu`÷1µLíáD¬Šf¯±·¯¾yá©þÖÛOßÖL‰®E”¥­j³å˾GªLí½KxÞ4•XœwpL^.´Ú„däX°¢½1¨ü' (€0) q[‘¡•²Ñ4–K5סŸ²4‹b>Œø$+ñ7‡ñ¾$zž&Û:©m‰_·ëìíÓàK§9”…¯49ÿ.\ð2žs¢“üâ÷[¼Ké¹Úâ[ 0—ë1úýTHdÊ&(Õ{½0õgv_ÉbQb!¡o7³7í¸ þÞþDÁÛs×]/Œ!Ne1U”®ÒUYWû/kv›J f½jxªÁa‘²Y·*ÇUØž²^ví685ë",cñŒ™è³ÙûB$Ç@ˆ´«(ÊuŸxÀ¬/抡6|AéßeÕO¤Ÿ(Ù,Ôu»à·lÙ]v˜åËn¼ìvéS·û£(þ~ýy>fÑøGOâ^FH¡q`¶1UL"5úƒUÃÃó –Ô 1Y»LÏ¥Ù3ÊvÝ„”™Xq (ÃŽØPQÿ;ÕôÎ?ÕíOÚígõ tLÙ69ž—š ~±ãq–lx^¨Sœ¨Fñ¦Ö‡oÖ=ȪPéÖÛÑÅošOÏ_½põé×/Ó¡¿HuºÎ?âIæYÀK«!ÍÛ c‰bE”À`bM"´ "L …à˜*šó]PmAÔ‚ãDYaÇÀÚ ¢¶Å0ð¥f@9fË´g¤)=ª!ÒVM•/*IÒ ’º^À‡bú~¯xFÒúN„_Sh–Õ¾ø&%ÿ™u·%Ú‡¦-¥¡@ ÿ'ºï°Q𤋲å-øk…XÜ”K) •:¢$¬¢rb…kÜòd6 _gGÇG³ÙŒ™‚cj@}{Pn{ïƒB!ã$NumðÈÛõ:%#,¯X§ è˜õÓ'åÀa Lˆv8í§»NŽè£•ùò.•g±Ù©K EæcÚËçuW‹–R² ¾LxWàåÞvÕ•JÉ><³òì!›NgåÙòìÁá>”gñÝRtˆï‚ú'~ðä ;œÍ{õ ÄX†P”¥P1Ô Œ.5á_r›¼ÜÎ’$µ)2X÷X&È™ Ùx øþÒ:ïò0'xɵùmƒäfbR~'5Ú#ÖÄ©{­³+'M¿åž-¸ÎúÂÒ‚X–1ìýÙ/irµ›ƒ Ôß«…1¦rEóéÕ×/\½Z\|ó²aY©"…P=jœÃ+‘ÃÅ'*Ÿ„<—fé_Qz­R6.êªÝJQÛvß缟¸€DWL.ø¢‘ۧH€åCÀPSØ1pN[“UÄd™š§U‡Ç^ýVw†|Uò€=h*Û?#éÌ9´Û I¡öqwð›š]ÔèÓ ´a×¢ô£–e(*ÇáÕÀËÁ;õP¨÷ÿð„û¶®^V,\†YÈ ¡´ØñÙSg³'xŸ=cíX³ª'Ó .£³}J^Ú£~ ‘˜Ô½ÙT-È ÁR;CúÌ p›Lvø®÷ÉØ3e.xÚO‹¬²GFFýÝ ÃÌKˆ§–¤Q$SÇüH’8ÏO$‰¨j\ÏÌ:oú,·V¬®ü;ýöÈvFk±ˆL ïõ'ÖrœÀ©ßêŸúÙ}GcMyQ6DU¡öXì±aveK\t[³a¨W9 Gú¾Ô7N;;QùÖ²qO™xnó\H}@™‘¨óÍíí9mG›Šˆ @Ïâ R+«{øÒ”°†ä¦;aÎYwi})hz¿7Ë¡¡ÀŒwsUä¹Aó_!_ò9P?0ß<ÿúùׯ¾~éÍóoj–”Yqä]2µVøÇí¡ÇQÈÓÞ†ÅrBGO¼$À`%±$A2™IÛéSºY®R4Ô‹:£’PíΙç½K5”šN“Í¥V(  } ©%ÕÀJà¾Sø¹D|‚ïCZøœ9é1Gƒ|¢éù\ Öë¯!š¦×'q =∆/È2×´ÔÖ"Ö¢_Q¨³lîVcÇ铃‚ga%Ψù›M=>ä÷fÇéìOÈã^âÚ ÕÔ˜cÌ|_HIëBÄyþ$òšB¢Ö†Ù6‹ ´«ÑéY™ãŒÇvÏrŒw©(z8¬º%;ÒÆ¥Ér¸`±˜m6îf?ì®XYžáFFn±HRÀ€>/D-ž[ˆa<ÏE/ètze$R®ÜÕ2˜¹´NñŰî3uÌ“ƒ/~¡ƒW®ñ| H™¬1OÒºJJQÜZ`—Ã2 ã4®iFNTš‚¼ÓqFÎ"o)༛\àåG,ïVûü/᩽;æüN‡ãƒ¸Û…vg~‡zêný#UÂÊí÷Sçy*‹n;u¥-UßꦙæqÆÂ„ ˆ]Ϲ®…ŸÇ± ÉÔ¾@Wd1¦7\p¨“­]>MÏ¿yÞŒã'mêç=å’˜™¡$4÷O)r=NÖåd(E²’18c1VŒM. sÛl;I²2êëx€f½ ±X5õþÜ€õ¥Ö˜‘"B„qÔ€A]àxn±sÚ­®N¥¶°õ|,ŽdˆAÀ…A ðg4ÍqR‹B°ªw@¯u36Øbø\ÕA\ÐA{¦ÊÏù¸ž^ñhZŽ S80´ê(‡ÉíØ&3ïÚ¸ýx… F¨°éìI<;~=À¡š¶^zé¥F›ÍÜ 8ó(ÓÓ¯Ë&"êÚ‰åjú‹Ë)!£ûýŸÚÕp@à™•9ìßýÝ!Ø©ªi¿x\±ÌŽ˜;®ò¡]y^:c«›¥ãL‰ BA½_Ÿû/òîÄŸðÁ8a9Ò ¿,V8!æEË «ª³å¬°ƒäùç;ûy^ÀzV5Æ–”<…ˆ+ŒŒvÝÆì —¥tþ·„XÄUoù-E‚€§SX£Ù¹¹è·âP5Sž“KªØ‚1ãÿ¿HP¬qiK¿6'¦!Þ¹³óó­¶·ïܳL)Q/$£Ÿ¡¯N*5¶IIStÄÒpµ=®n¹¾”´&­Wµ4cœxZ!ßU@:|Ýt0É£dmmíâ…Ï>» ¼höeÊàú$ Þäcí‚.[}]¢#'ɇ-Ö%ñœ`¬,9iH4áŽe¿ÑÊ5 /é•Q’‡õ êÆù e¥*bA¥(:I‘‚ŒR%¯+ü¾KUª4! äÀy|Ëþ×0ãz`eBëƒÒ¥äŒª›Î!¨ËjFI?=|Mm±¨ñ†ês£¦=¦,Ô_ühi‚µì®¦I·:îåÅ“‘STúîPë)Fqר`0ž@(Õo¿áÚ\5… ¡~³ðRã7(N&¤Sçµó?ò'/Vr 73Ì2³«<ýIWn™†ˆábT¬GT2V² {Êfi ÞN¡óï'•åŒs6ÒWÒL#ƒ–šv5Ed DY¹Hlµ:Í÷ñbp¥×u›µ+ :&ÎuËðy×?×r›“}‹“‰Ÿ?ñ¡Oc=jH2ve ÎÕ, c >fA‰ÀRÑ%B•*ã×PÁLÂñQ[ 8jˆ/u¶ÝðÀGxàÔi,“‡æÂÃ%µ‰µ‡ÃÝJžw4cN9¾ÿ k§uÖDS–Sý+ ’ÚÒI—Ûñ;ç.u¤ˆó3?LŠB×õ?WÈGxh¢˜Ä0kk×ÁËŸ]9þ‚L‰ÃŒi}dÅ–yÑ´BšeEÖsOW .”D^JM#.LQ1°¢Ì€ó9з|/R”Þ ÔÒ]«œkÌê\w·)oOïÒü—ŠO… ¼¼.y±pâ$£XÈÓ̲\ ùH¦|F.¬TMÎèfî.#Ë©¦BÀšàÏvT«n‰ê@èJ¦5(ÕÊr¬¡©«¼¦ª©fÓ'ÓiAÍ¥œ˜á¦=Íîä&íñQaQÞo~q^(±}ƒ¿lº›ŽqÜYŸ¼ÿ>a‘r=©Æ|ÓW¥ÔZÑsCú;Ȧ¢[öiÓ»È{èáá“›-W]‡ÅìÐÉÇÔ©³²†ÙÈÒ»Y^ÀÃQžÍ® `íd…Ü%Ö(ŠžGꬱè7DU˜Íü,Æ÷âFÇÛ¿ñÀ_.uÀoñ¯QtæO‹²°–2-©%šÑËãÔd‡}ð©˜à9Q!-%‚á爡±×14ª=׳žD¹–‰’*‰À#2P–öd ×twÇ’õ2ˆïç[›ó;›Ú6¥ü½n\« è˜Q2ÚúJ›¼Ñ6»JÐZd”"xQçÆ-!lÈ‘òç y_H"%×Áûk—Öž½]ÊK–PC°´`L¼v§+žþ§0d-Òx¿ßÕÕº£/³F†Ê,™kañYŒ‡É 1ŒóßÃ9äº*h¹,v‹n¿«öb¨íz+*¦‘顸…—t™çñ$å˜W`ï•8±ž §–·!4…²3º^:¸Ûf妖Išn©à²œëõu§ÊóSÚÌ+2²u§õÉ4ƒ£ Ž«†GñÌ;³ÜT3TMÎ #‘•”÷÷›ÆK ¼=7HÙ”p y‚‰Lå‹Þ„§úí„Æû¸QË ¼èæÏó"³ÇTðÞ=ÖÁ£ñr:‹Yêè‡vɦ¦ùýŒ¥öc8v>²Û¥ÁÒͰ©•íHC P j‘™ìnàw|1ô½}ËÖ(AÍ´sß¿ÿ ·ãqso½bÁ÷:ž\‚ê(…·,OsÃÌ ½›à -Á1‚çú1íC«ZžÊ6¥Ä+Ôj¢U…žÁ ¦aYÿ'³]—ø;CaýW½&(Eì8†mlÿÍöß¼¼µõµù\Ÿoß«{Öœøq—ºi‘Ðk<h•™+FÒâ|X;¡Ã·ä" ]¼‹¥¬˜2ïkòŸ*ä½Ðã¹€Ex×ÖdàïÕ×Ò$Å¥îªpVÝ)¡¥I™jFÀ™À@ärNû=%S»ÀàôÝ àhˆa7Ìâ8’„£Øì‘‘#æË´\v²L‰9„ê„÷G”»/Á‰¢lC“ª©jÊœ{MZÝÑë^êt„d¦s¥xFQGÎháOëá·À†°#f·î Hó;JÝí}óŸ€ò^YõÍöý´XÃw»–íÒyÔ69™‚ Fè&:›Aò-â/›z7c‘Eí¾Y9…O†ítP ´çCÚ„¾ '¢¬RÇKݶó. Îd?Ò¬qÆ*›Óg;hW,ÊgCÌsÜ.sÛ6zW²Â®ŽÌÜÔ 6mø¸SHl9‚¹–ixX$Hߢ¾<°ïS{rUÏ íô°Ò;Þºû¾±ôÏ !ø¬Û’aU2Ù¶{y’°%IÒã\ëá"x^°Â’"áñ"Ã4ŸÉavl÷̤1Y ;î9êÁÚ9LJ"O›ÙÊÐP½º#âR ¨ÎÝ“í;³5ÿêÖ?|í¯^þ‡ùÎÏO+Æ©»n>kÖ ¹q‹Ð”8Ìv;Ï|‘&ó¼ù”:®hÌŠô$Ö;ÿìÆŸ*ä]ÑóeE”õ5!X!ü½Båú‚¤šç ”,+1?¡FQ½m­§9kÙ¶ÑÓ²-Os•ªN4E‹CA“¤µ¬é) OüF5:;mú—BÀ– *Ö´Ü €ætó¯DE“µU?#Ýà«æ†ìÛ›ª®˜ÿC©œÃA›õhƒq»¯kCÚ’)hÀqß ÎGv9S¤gÈ;þãî÷Ðþ= ÀñV«ãn7ïŽÛÇ6nJ Ip2#°ÛCWÔ@y/éòÂð¡Bl“ !>afe|Q·3ñýÔ& ¥ J¤¾’Ô㤊v5­¨çQž#+³ª¬ÞÜ/Û6Mª( ¹¿b;C62-§ÌÛøXbSgG†©45áµx>R¤¼ÈžÈs|=¦ÎüVãÜ9×øBߦ  gT‘·´¤yë‚;¾ÇgÛ*––‡IàF¬Z’¤±ŸH;ê' ½Ì)öŠ 6VLØËh}ÒiL¸¾IÑ£î œ‰vqoe™Þ¾·@ ®Å„m½üòÎöÉxƒ½ IDATük_}ys{kûv4é.£Ó~ÿÞiè9?¦•úR<6hŒ–ÉTf OÆ•ÇÊCÚo.´(ºI‰Û¡õ§ y.öÅ\å•ëWÁ€×µµèU ‘T$*꥞G ´pØ!BPRk>Ÿ SÏ fJÔT3£Ù14„-“$Çq%~]êìy0И‹n§Ò Mºü×Ä‹v¬†Áe¨kU/*z=«X…àƒWÉðG)ðšm'ƒƒÏèå¸fÀ\Ò „`UG.3š©Ó—©™ðÉqö xE½,–YæÐ\ð£ o¿ÓÅ›åÄnZªµ“©`0ü½¸Ê# ¸Ê öýˆÑ^àñìˆ|1|÷ ¼ óáŸútøUŸ1Qc­oQk@ÚJ¶Gíbj;~`;‘5 ²t¥]ÂVUÛÑí”Á çæ  ªA—â÷舽Çî’€» w+‰!´‡ð€÷©“ß n>Õ¹dÓ Ö•ë‘V1wÿ~c]w1®¦éu¢qOÐVqm»:5…Îc °¥Q5-¡14 ,£eª¦€!h§¨ÒBÍÂá´yŽa0}œÉƒP ­ 804¶·N¶Í¯Í¾óòÖ|þU€ð«Ýa9¼kiXP¶ó¬¸µVƒ¶Sµ±¸2‘{)ÃÙtJR–ÂdOÉEªWýŽÔÔØÚ•§×"øõ¢ŽS Z‘ZŠõÜ&1b. SJèGìI9êv%KÈZÚ ä#’1T ÈáÂS æwÜјš1EQ’Ê <ÍåcE¹JõÊb‹x„w ¸úzxg 5R™Sm•¬o‚WMÚg&õrÒ©àI7r}H{‚ê\ï«ð´z @*î.S6X_ÜX)YD£ÛÚGäjÖë¿ÙG 7-ÔÚÉ ?ÜÖ{i”÷äO¶¸v'©U•ÇÆ»æ0Y¢L¬ @XV?€}H»ˆ²³6ËØ ZÐfÎ÷Ye±q>£-—éØž™Æ «Vf¥å¬P¬§ÌÅC‹ÍÆtÐ 7Øì~K ‰°¢ËóœßºÏ{­VÝ Øw@A¿ÉR¿¿e¨¸ ¹4½Ž>ñÆl?¿ô÷ýXTÔü—áœNº™!çqy^3-gö{‰“%<_êNÀêlfq‘÷ÏqžÇ“÷È[@}ßìÑ”ª—…ŠÖ›Cxðϵú;;Û;wï˜/ÿ|þó¿žom¿ðÕ¿ùÚËðW+%)•ŠWíÓfô°_ñXe~“&Ì@øl,và¾õ"K²Ëº³lz R-üÞÁÍHúc…ü>Ȧ”+]g¯_|ûmÁ\[Ë/$¯EtöQ—M¥KJMBéœ6Ëhœr¡6è÷C´„)[¨*ÆÇ† t1%ÎrTēĔLGyÖ ^œø‚ò÷°ðÎQ(¾&›w±\Eݰ4|ªCg'ø'ë%µ²NÀ|À†N°‚K-!ò÷Aï ñÍ©§Ý5 €]º3• RɧîÒ‰ XüžNż=-fÓi4În:Ïj'S!‡^tª¹—^"#:²_Íc¹˜™±Ù6ל¡Å– #¸é ÔF=ôÕ(5̽¢*c‡2®ˆ!ÿìˆÒuín oAÿd%#x¢ÜÌz…>¶€R`–$lšY‹`§l*E$Y@ìõD•ó"Þ%lÐØŸz÷wh–òbg!óC§¥¿\\Ò\ÈO¨“¿GÓe¢(™¹ªIÛÍOÄ£Nd:3¬±•SÒÄÊÈÜ55Ûôi‘AG-¡«,Ó dU*u‡ 8~i©Ãű¸»}²õ;;æÝ¿ÞÞÚÚ¾3iþÕ;¿Ÿëõ+Щk{ŽºÐQÌxÎGÌÒ«™Å”H ]w««fº1è{qÃÿc…¼§Q£¡™_õÚ ýé…µK¯¨Á®¦!–Æ} ‰ñÔ æ!MS˜³˜æ ‘ߢÊM’„ÁþÓ[ž§RšfqHƒ_¢ëØí¸ uûå–ï…g¤`}ÒLiU6u1§Ry²)1 ”Ò¤&¯ŸWt*š9n1\tƒ­àÒ·ïÙ‚È«FB†Ê4s#[šð4e›E‚Ë™w·w¶)öžÐÐYá‹'·N¶w© ¥3;ÖØ¤c ÑÖºRbä0ŒÆðŠ÷¼Ç­{±•+•žÆágÄ‚Ä?³ÀÌꆅ6ßµ¶Û—͵ëko}7-kíòåöšÕî¿Ý i8Ób«ì‡‚d¡äѤ2¯˜!ˆQâÆ>¢³ëæìº4QSCm%†D'C!¯¦´‰Cc¡M“Êcq•„p'“…¨c?D€‚WPŽDÂD €UMáz Z­ã¯ièÚ™ÑLç” £çÔg.§-«väÔÓîµ”}Ðë³Ø»"ú{BɦkG°³ÇþÅjZt‹¢ªr7UõÚÉT0bYM‰ï\—ÄÆC…E@žÖP†„ FNo¶²¢—}:vg½'ì,¢‹Q@ª¶±i9è²Y^Œòbh9Ã2aÀë¡Î»++ÐzAÜý¯ÉãH-vpœB¹õØTëþ?Œ ÕßC`F‰¹PŒ"?¾OôGƒ²< QÀ³x¿óÅuö¦g-ºgÁ„€Q®1O³ƒ4¤Fmº~^ˆE#Õ ÖÌ´à—~Ì5©¹PŠeñkhRjˆe‘¬±#j ˆ0?B•âÒ¢±zBÌgòûþùP\w×ýu8 ô©ýä6øz’ol¾t²qgks¾½utžcÑÌæ8É`4R(ðb@[a/\šìß|ýÆ÷n®ŠW:÷.¬ V!T$uÑ3žY`sY͵~MÈVÍìÆõ«òKk+¯Z7˜ÈôWyI„Ùj’¹ðšàè¥ÙñòØÐ­%R“)35s1ö)=G*Œ H÷e¡]ÀEÓ6ä«¥‰ÇKù»” Z bŒ›tíð0ˆÒþ:­0ƒ+_–:PîbÇαÊÜV·¶æ··o€wîl~ysãääέ»Ô¼t—®D,„IJ ‰fVVvË;¿.§ª_¹òíhæ*5å’u8o4šÚ3ò+][¾'ðj¡Ço\6Ãñ×®ÙVª¿°k®yíÚ+¯—¯È¢è'šªÂŠê, õCLTQ¤b¢(ËŠìãk™ê°¨ƒ&«u¢V"Á‹q²¡ `@íMSÁÉÔ2ÄTÌH¥ºOa¢]“{e*bFè•ò¹Z·o¥ÖJôÜ"j¯ ¾BvœxŒ°»ÕrõR_† ¡ñÚµ§5¶ÓŸÂëHÐfS%l÷º9(,ß‘ŠìÀÛ¬¾ß5htaa[¥GFN†Á` bO nÂØu‹ú4Ωßh<€¿ÅRô «ªl*Sùa­w’Ù¼u{kkãÅùí­¯ ïìÖª¦æI%ûëè<ۄ䜚 Y¸ü}Û°i[á·¹èÉbøŒü@jÌ«ÚUêÙm^5/²k«æÚ¥·®Ý;oZçW­k7ù·^©ñ%Jð‘’©ì×µí5ô:ê Dç—’¬H±LÃOÁWo&Ó nFóQ…ŽÔæwÔ4^Íd\sCºpEï¸vò­P.¹«×®4!ÔešrÏÒË»ø7Wš4ôI–´3ß‚GÀL?WêC Á=ØnmBäǼŠ4È’pßsÃ}W•úPØ/ÚíV Çœ)+¶k'ãÀ`‹n§œGL§q–¶ž­ñÓÿû °gÕ“7G¼Ö§S»= Þª÷M» »f/$ƒìL,£LÙ4ïæÊPO@z4¡9VØáAúä´J.¯¦øíˆÁÊŽø@[_ÿ"‘â0,0µŸ^ÿç%Pá’m >서b-é#¤`·¡Õ‡ÿ¤Ýã,8î¬,Y‡Ax‘—Í>Dçqøó<÷á ˆh nŠ"™®w ó]«]êÎpÇxÔšÐÞKºK KQ"²“»ÚÆæ¿ÚÜÙØØ¢‘„[[''·6¾jìž°ÿŸ¼wínë¼ÎEõY7^°î÷ÀÖkH€³?œVC¶ä¸§¶d“’Ó4MÜ„L•Œ8±ùÙíhbŸÄÝí{œÑ¦/H)µ¥´ûìv'»M$Y¤y“”ïçŒîú˜À÷3Ÿw”d;µ¥ZM½D‚¸é]ÀZÏšóyæ;ß9/bÎPœ…"ñ[.'‡(I¨$pž¤»éŽ.³âQI6@¨ ƒŒQEЉˆ¡v¿ÒG±ôžbY#é‚"jZQð²Æ¶}¤Òô5Ëžj¶HÍ,ˆb'[º™jZk.X³²ÑógΔ\2ל«¡JÖí &ÕÖ–U·Ç[YkªŽ$B¦dZD±¸™ —Imž§Úr“èµr­F=[«e[ßNâ2‹¶6D¢>UW éTה؜Zi¢TŽFDpÓXßÜ\_][]]óÖV#Û›´WƒÊÖÖ6Ê£f®VÖì5s‚ðZ²W×c;]¨^µ79býÚ]:ž¸—°ð M\ …—ÍGÌþ ´ÇŠc³EŽÏ?ḆS†AÅÂu¼‹†qH!Õ“=dD ¢È²‰4¬HÑ_u®9oƒÏ†sdÈuŽ+Ï7e²ê¦y«¦ŸßÊNYu½úl³ÁªU›X ý‹ñÍ·{ÓåT9Àz0©[ʸžm™7jIBrc;NÊi”A.̵^* 6²¡YXR«£÷WWQæÎ…¶½V#|l•ÙF3‰­ºE2yõ ÖɉG7GÁ"y.¸`,š=xà!LÏ"$82<4\àxÃQ›vT*Ū&"Ù= C+#Žâb¤’¨êxRB:–ë‹W‹_%(;rBT ÃäV"—éàWƱæÜª-›Å"?ꆢ $ºÜ…€3»½öÂboi }àç{GHŒ,Ì÷ú½Î… ‹HÆBe²ÃscCE:g•\6­¢Æ08Qqž†Ü]ÞÇ‹º’šöBßã\27ºaÙ_É})wú´ý´¨TO™3¡y"81}ræ ã$Ñ?ÌH¨ª”°rƒd•H‚!è_¤ cHryÖèZ…éb% Y‹:¤®Š>Ó:ƒä‰Ê2úT.–½‹lf¬¤e VÈPȪãÄõÙ¡¨kb (m즠(zêûòzN °8GœŒÌI3K:Ì0•TÓN®äóÕV6[+ù±æºÂþõ¢HÖíZ‘ùV-/™“µÚʼn)K'Gü¢Œ•,¹à"9]OÍ6¬Éqznb*)7³­sq9IçØI}0cake] ò“çnà GRÒMú–aæ¬Áx” IÞW·vñ—'º‘­˜Û¥Ø^Ï¡öM.×"îTI„”[ä‚ëéBuÓæÞ$cç:×™ä(¢i1‰‡s É(’sGÉÓ—y“Þrx¨VÔöF)ì× ü šU2£#‡s(©—hÁDâ7m¯ö6"WÑ™»xÈ®è• °ì_†÷&Ÿ£jIž$Ȥ-Šd„ µD:[bá#{žØëöÎÃÏ¿þú<š4̹xqeÅÖAâlžOŒÚDâÍ Y§Q9$1OÕu W‰ûÀúÁö‘AÚxX°ãÉ>±4*ËæþÐÎM?£¼‹OûwÂ9ñäõޤ?qšÙ:bCtîéz"â.a9Ý® õ° ÔGŸÜi(£ä*kO§ˆäP•X¦/Æþ£FŠ'€[$-Ô ’Ͳ¨DL9£cÎJ«“ V•¼’<¸În‡äƒ#Ž“¥žªì#_׸eh-2€5RÄ ݘH5-I’VµžÍgÏ~ëÌsŠ;ê’e(\ä@b”oZSXIˆ´¨* PÅ䂱J„E¾7Ÿ ºFæææ®½å^»ŒÉ´/.p¨ßFÜÁ#ù4âòôm ¿¡b¦èÞA¬ ):ݹñFk½„K>уD«$ÛY…:4,“SÐzÑV`bÖ*¬  8wðÐ~Y ]·;QbÓü˜cG ]FYóºç¯êŠUŠ"?Ý %ca©×&‡Û[\@gÂó °~¤‰æçû‹=ueyù²íYÛ 3=Tôe_M+‡ü¡•¹V(ŒFë'$º.JN‘óFå¢# ž+Ñ •ªa%›}öó_Ì}ñ÷>¯<ýôé X{ÂSŸ)þÞ’èË()·ƒÀ7hë)¢'cqDìPAatUØ2Ö B&“)áºBr•ŒýбÉPbQBôÞaÈDøFaQ AÐ@Q@T°2Yï_x2².yD{û\“¦tkÜnN5nÕZX†ÖÒk]ï¤i/й7 +I¸Â(7rÍÍZ>òþ6*OXM«6•m5­ÆäøÖÄÅ:« ÑÐoMLÕ­W³Úä³­º6^Ë‘ÓM.¤†Ï²'¡8ô´¿YéV±¤>7µ]rG/a•eË®¡ ^+·Y)OÙkIsËÞsÁÙÖºÙ"¨Ó*!z ñY•äÔ4¡Pf!BO+”åËš"‰ 9sÑ!ou²´Ø~…›252Vý• "h͉ñVˬéJ¶®¶ê €ç¾µöµR3ôÉ·YSu©]­^€Ž¨×'ÞE&r½­w땊U›œªWª¹¤¼™Êlƒz`“$«]±u(­:N wô-gÔÍ(Y;.ò—ËÅQ±ŠV2ÄÂIýVâWm/YßÜ\õÖÖVWW7×£Ü:æÚ°P“p·ÉVäµVm/6¯ÄûŒ¨n¯Fº_Ï…¨úA§Ì1$×"Bd/¢U"§8„ W‡ä€2~TQK5k,¶sA\ŽÈ¸JȽ$±ˆÎZIU[-y.É/³µn%±Í‘ ñÂÀ—$#)MšeÕÐZ_6­ØT;—óaDtò°€$ Ÿ©´µ¢#+ä:óCKà~; ýþÂÂÂù…vûü{Àùæn·CžXÿ‹/YfÝ 'pd°EŸ÷2‘ zŠxí )âOiqÀû¢ïÅb‘d3™BÑÙ¬ýnõ´ñ”]×I("Q‚ˆa1ò9ÇW¼„È‘®k"]ok%q\G…R¡‹FAÑPMˇuhæ6˲8ÄqCC.?â¼\Æ´’$:)CLrb­³œ")ak–9­ç¬/]ÓÐOõgfÚmofqQYZ_"'|þ|ï‡ K¢¸°ðÞaïGèa†ßÞ|§¿¼B¾ø¢Mt(ŽKXY›„!’'ɃEò©…ÓúçDDêèìCð¡DäT5„‘éÍ䈿¤(§I=åœtNœ øf|^ Q£Zä2™¼(]–Ò «…N˜€Dc°¤DèÐvʈ•S êoJÓ²ø?QpTXëv€Áâ,º/j! _‹˜¸û‰eCÖ–W80lj—‰:¢O7™®„\°•SBp4bÏÚõ¦U­Ú­Ic¢¦çè*óÕ¿’¯ÆþõØNbŸ¤™ÊI»nä&²Äñj•–U¯$µf>·29ž%µ±Éš³l6k;ŸolMk“жFµ‹® j‹—‘E¬Òå±LÆç\1\ôfã „‘QúM;ß´×ms“ߦ´Ù°¬òNl%·V'¸monš¶×ÜÄŠ¼‰œk¯…„Ë5çF´e¯6ƒÕz3òžWÌNÆ-f8—ñ½¢r^&ñœÐ¯”J^è¸aTŠy]0c$W¬1¥•[uAV µ"ŽŠ¤ðb%QwE[ +E$S*â GHÕ\|ú7ì‘Ò ºÚ´ÒEñd é†Mþ “’xº$žžxòév[•åiQúÑâÎÂúQŸLßù~·ßÛi“#^Üé} 9Šd mãBײèØY–“)4Œ8V%½Ý“O)Ú3*¨fpe,R† ÐCÇå|Äôä”:ísþ4 ¶ÝOðFI1+ —Éd)=Y@Ñé m ÀcÄ:A¨„3]Jé´‹òÿW‚ƒÖà¶#Âì Ê-âa8Rá±U¥Ã «,MFUbSÌòpÕ0#XiLS.)Z(ÊûZVnML»mê·¦²Y–ú lÞ¬µ¾ò*²çrÕ2©èÖ ÏyΪÖÏ’©³­êÖÅz3)רe['£¤^7IWtØSfe+ßÜ¢ë·OøC’Á¸¡NªYR/(1yCÏ'òË–¤2Q³KΛd‘YRQƒÒ,¦¬æúfiss=Ü´+¥JnU'aœ­n%ä‚=¬È› ¸Z ³öªeolmMØ}‡l-WBvváM§ècg¬DkžØFéYÑÝL¦”Е[†®–5­n;aƒ, ©é*š¹ ¨•$ªjÐWnLVôªNò‚Xb¥˜qÿ¦àpîedX(òîÒPd5q oíÍur?âI¾$ïüåÓ'Å'OœX}RôÜÕ§¦Ÿ–§{ $€ww~øWKó;ˆÌ, s7ž…ù#]²ˆö¶½N×ì.Û,ÃÐPP5Ñ?Oz.iFVfˆ¤J2¬ÿSP(X@µ?> fÕЬ€¼sàó¾3BF9£ùNFæèefëàoELõ"%aT2ti/N]´1ù¶$ Ó§˜½ 6ÈùB Eéû*O+‚‡µ,(%˜C¬$" GP)òe‰„%ÙèËôVÍÄ 4YFñ2?O‚f­5k®RÖÈõÖÉN´êüq.g·ò­×Ï—8fºd ù˜ë«¡„NòÑzI'ç*¡†‹¡aÙpYe]M_™¦ýÍ x¨ ¯ øŸP(äYò*ÈÊ(¾4Y?F4Ù‘¹€<]HXd/p!˜ÍÝRr¹FÕ˜¸À^Ž"„Sš¡’•7 kN‡´A›¥&EÃ3‹z±je.«5 ÉjCI .“Ý‘c1Õù¾SX(ƒÌᩘ®v:•²' DÊ"ñoe´hZ->It%n+ƒÂßÒ2;HˆyD¬¯,’Y\’æ;z·k’3Î^´ëŸ¯Æ±ŒõäVàQ¸‘Ñ!‘1ò¤ì=)¸/T8'#†™)”RxTŒñXy)„ e0«JaÈD©ŽH‰QÄ $ö¤“þˆ…ç•gŒ¾ZÖQ°ÈD HVOF ÅPAŠx¢©–©c:xZ@À BÉãIô©DvÙ«d=%TDEjZ­¿,£¬ì³s9QÊf­Šª'~•9âf­Ö =2e¿$ÒWi¢e[7«“­lí\œmmžÛ*×Ó©µºefµ•ÊržMo '¶59•­¯4X÷G(ÜDõ²P$à‰Q ú.]‡ô9™œ.1@rÀÈÕw‘2:ç¼yõê5¶|­Ι;ì*¦e5·‰ÔŒIc«± ²‡¦±ö–µ†y¹Ä.ÅäØ}ÓÞ¬–c;NêV”)d8i$š£‹03º£NèúDª]"4‚PJå¦>9Q5–±”†µ?UT¢ÜšUäa«±{ôVô¬©[‰.Z93)jfõEOäÉ–Eºš‚K.uR“¨§†¼H'Î0mÜ‹KŽ×ò]Aòˆ‡:ô¼ï?Y$-. .ñδDè‚$Â×ýCPz¡¼°Ðf|pA"KH6p;ÛË]"aÝåîÌÒ!O@'%‡Jê3J(óOœwRçèsdÜ(òG3%?C–9"ŽŠ:ªV I0 ¼ÑÂfŠàñºzHoI:ña™¬Ÿ|JO©Ñ)Ãèë¦ýýéSÿ¬”N‰O?cª^0 9cXÊçôÏêç éi³š²Ä#$ÒÁÐ@Qf] d˜E÷1::¬Ý6zÐ8 ™»µnçê-5‘[ä‰/Úf+Çr”iDtÇfrŽdéÛ¹Öfªu- äz%±j*›ÞÐ b#AU£¡sùõ$AÜfÓ´ÎêˆvU’Psï2iÅÀ§èüù>/’Æ nñê›×9ÂâÕk®{Í˸îœûæX±0ìÉ›µõ8N^ˆ##ë«¶_5㩬¶Ö°"o2_¶7õ‰zìÅêß&öÙÀÂR£ ðGæF<2¦Þ0_Ð"N©N㺎° Š”¦¹IU/'¥„ÈD²MϘÀÆjI­kªÞ2”¢Q·³“däÙÁ(š‰ÍœW¥ïñ訃k\W"‚w8ê)hÑ¢p $¨<ÊK®ó #þ žè¬z×GÏû#Ç};“%¸à¥#dðq,I°—òÀ%èá6g‚Vß ‹ r¹¬N·c£ iKWˆ\„‘›aëÈ逆‘$j¼94:Âsk.§é‘Ç=-I1}SÏ‹¢˜`Jt‘£SÁ®>Á™@"hQ‰týAO«òçúÊ)º0§§ÿß§­$H=ÑŸùç¸'ð¡Ò)UžögNË%A u¨gM<Ué•$•d×b®™GÜOŒBIÄãJXð$ï3šyº˜[¶Anœybµ%›ËUkÈñ³šïVßµ'§*‰Ýj•·*ðºHXh˜]㉢DWjšÆ¦7bÉŸ+H¼TéZF6¿¦ðè k‹NÃåH@jÙ"O}Þ >ã|Ï÷X¾«”ÊÞ‚Ç››Cî”S˜öì’Y56¾½¹N*xùÏÿ2Ef/»‰yS­ÄŽc{\µÏV%ÉÞVd’0^fxlŒØP!ðuÔðµ‹ëš¨uI!Y„ôDU¹D÷VE ¬ˆ¯„VÖ/YÍl.KE3Û¾Ðëˆ7ßëþÅ…l>‹|LÕÊ®Uç °Gœ¥ /²t)¢å<=2”„>gnì«sXFY˜›-J=ËjÔÍ­°7íµ‰l«Dþ¶aEžÕŠì³‰‘³ÏÆö6ñOIÓ\–Çæ†~¨P " … #ÔXÉÄ0DBn¹¬s"[å*¡¬£ÑFÆåø•±¨•[;‹*vFÖ­ÎëKÞQì‰Ý¥îÿi勈S4«:ëðˆ:ƒW‡8“!Ò¡ˆ„“ÔPSP Ð“eO¢'H¥zj#Ÿd…ÐÉÏ]s3™wè4ôƒn³ŸE,’ƒ)$‹Çb‚KˆG³”…O†ls\\ì._¼`¯,£e®mXÝEÂ$²Ð³&¦‹+ò2(€JXh¿îˆë`=8&#‰zg0J[:‡õL iêÿÒ si‘õȦCæEq¨ZX˜´Œ‰ÀXóÉÁxB0ÄQG 3ãd§%•%ù+?¼HÜçÊsÎèììá™(9kïë¶ 9ÒDÀ¥e¾Ê¦Öì‹ÆÅpŽŠZ1Àø–»fm9ˆ÷-aÈÈbzcÎAF7+iŒkV~›D«v[il¹àDAÀæ#CA )ä‹@!Ï AÆw‹ž¯¨m Q¹.RUŠs³sWçÞœ›=0[ð…—ŒÉ8ÚªjÛ†½]5ªFlŸm™(Ë"(š…õ‚Z …>Ñp×½Z8t–…ÂìÐ2Q¹À2l’e&EÓLLD6Õˆb›*©TÓ¤Ïèq~Ä^èœA# ’« ‚i1â&𦠧\>4B¬XCO0dSÉÚ}eFñy‰›Éx‚?­,Ò€$Œè¤Îæ€ gd`µ ¯©lɱH¬n9!;EÎNöÍ]ýñØì¡±}×DÈNò’|$Gª+VÈD,ÆG÷éLWuµªV50>I1y¡`l$ÌHã6ª\"w’àÁ!µX,&o·L£šl¯“y'­òðBè†$ƒÑã…4H! Þ.’,ö ô[DžÀ\qvöêì\aöÀV(’IªUJö¶cG‚Š¢h/KQQ7•ñ>¹ %D}-)S|kh®8”uq$“ñ 9ª±Ø_eœ£¢–ÝDÏçjOF…û²€x¨'g.¹ž `!˜ÒÒnZËV>‹BÉJQ-£“nz ³ž4q%ð1qŠÐ¥¬.q¢µ¸úñôeW1q…•1ã åIL¥äÑa΄˜•ÃlPÈK#[7#Ç$/ATú˜ìb „žKúàƒÂ~§ßmwH/wíôÔ µ S6@cÙ šÈZ¬º¼eM–~ß@r2’I‰p éa[&yâ@ Už#2’šFf!qÚ9œf‰ÍÊ»èÐÎlà ï„%U’# Í`M»¯ýõ<Žç¢’HßìÔ ™å¥²$/!¸D¾âz²Ô‡Ä L—š@‡"Fh”Å}WÝ‘¡kÎU§xµèÐ3äKÈ'“¨ÁÇÚSLÄøjz½«•¾VîkF‚„YóçMwxì->ˆx UXpà ¤ô¼°¢6ò1i›íd›|§Ñ,Ù#æÔ“…!—Œ~¯>I. \tšôÐE ‹,† ÎXqÎ+Ì:ss3÷ã‡ÆW Ã¥Õ’ÐÉ àŠ‰¯ø¢%…dÓ4Û®Ô*j D…á¢Ì=4†’<æÊäX+ƒúUËJ‰è·bh9bl‚Q’HÅŠ—‰‚† q\œ8àfË~©nX´–¦Ø¢RÍ¢gñ#¶o†<"FÓ>«’6ˆÉ†´E\Q S?Šá|ȘA;)FÅÌ çÏ<åÊLxêäO|G8%KO-‚ø¥™Ò ÈŒ^H©ßRš´Jw–˜TA+@¹”šDL¢‹‹ËÝþëÂÒ}Pa5—»ˆx˜RÒ+::P[,èaÕ![°°œâ*Š U7ŠèøºNŒÁGH‘~=:8Š0Ã(UIÊÈ Á3y‡HþI"|¥ ,^¤‘Uå‡#fEîœãÞâ¢M>Mª$DÛzs_4 ªF$i Ë0_©öMTö9îÈ(‹¿…¡&£¢®ÑÒ—Q j€ñé5Dm¥S¦ Æ'£ÓHP,Œ€ŒczÃ#ìŸ÷@ïúJ+¿ÇYƒõ²[^ HÝõ„Ë܈3ìÕ¥ôÁ õ¼O ÅõXª2ý^%YìÌŠ×Þœ¥[70&kFÀÓ+^ѹÌûü›ž£˜† ÀK˜fÙè©Âˆ02â ͆ݡaN3Ê: cÜ·ãD35u¼Ï ÙŽ}? Eúê¼"¹JS°E؉ŒzûSU£n±.1Ã6´âh‹/Ú£vQ°ÑÐ/Ÿom+éºEòZ1}ÿ7㘒òÈ.ŠÌÁוPž ~ZTd£IvN‹ôž“¾xªOOøî)y†Þ‰”htÀ«Ç‡e#í˜Gæ‚çÚKôø <4ñÀ¦_úÝåþ…Út½Oï2×VÈ ê9Ž,ºŽÛUC³¨¾³¬߃ IDAT—#:¦Ý c"¾¬‘©iïŠ+q¬pnH—ѰCÇäDé x¡/ º+›f_B?\ƪF„§Ñ§$!V˜ðþˆ'J¤pcÄ¢#Y$‹*IéLbáÊß’>ã þ,6ÈËž£+Ñ>‡uyŸEí‘ÓÉ‚GäY2]­h50¾þ¸¡U•*6¯¢¥ -‚è’;LÊJ@ß”c:VÜ”Þiz~ý+i¹c3ûbåÕ Ú˜!:Á¨WôŠn,çó„.ÞÏ1`ºu‰‚^-È3 Xw‹Î,(r€2WÆ ¢ü(9ßÔ‚d…|ˤ)“šâ‹££²6Cæ GxEIÄDѬPªè]Tªp1Røß é˜H,y!ä¼õªe²~hñigôŒ­¸’Á‘m³EÕT„L>»McÀ»†©A¢t­ E—¡ Ú‹.p=D †YGù•(£¯Ò»‘c=,EâÉpmô­ù”ã^o0ñÿÊäï3pà$EæÛ óý6C" å<½ ³ÇmÎ íååþ ±Á•SÏ";‹Ä…‹ô01Àª^±PYM©!™M¤ÏfÑ•—x¡‰õ¢`bYŸ¢ú~Ñá=…¥•’žÌdÈ1À¢“Ò7B WRé=z5D$ÅS‘+ããf‘+q*àˆ„@úæ$©etbR#ÒÖ½HãÂz…IæHж/‘z¬;-ù‹£„ËLŽE¤ŽÆW]P/DF“]ãCItÁœ7—x>"¿ ‚ˆæyÄ÷$Ô­ä¶[DE^Þ®Nfm¢îz\â‡TF±Á*!‹P@Ãaƒ  @&pç]uÙÚ]7˜+8W ¤„ –á H¡)úÎe²ssc#‘¡‘&[¢Ê2†¤I&*èb¡L£ó—¥¤BÄO2ºä…cC'ñ¢kòpÆ͈‘" ¨Ÿµ¯ä*%Íç«V§Jœ-LͰÅ*9ùQÎÆ•Ý2d‘+’·#I!`…7éѽäËtuеMj³ 8Ät0N$²4avYbuf扢ŸTÂçÄ OyZ:­3>ˆbª~!€Y šHaïu@æžÙ,1ûYJㄘ°;B¾x0Øë,'ìtûý®¾Ü5º*È@˜5.š†‰ެK'\²¡áîE²ñ Ó°`Cæ îI{£.%4–û5Ïý8ð%I %Ž­X30‰›ð"àYV€¸B×uÅ6ÅCÃÒ(’ kôe3™cÁÒâšäšÑJ“(zs öy<›DÅÁˆ…ðô¯TºrŒÚáž/[`|:yKÁ³u0¾A<'²æ¢’ƒ h›ä79`ùUÄ ´üz3‰I:kϦ¥(f†ÃcEp=Ž­å/’Ëæ1,°ôƒ”¶ œÇ÷­L"h¢9(:£‡‰ŠcWº¨ª¦vyÔH$ôú¤+²yQA¦©’½1+t!ÞÔT" z¢}þ[_ÿtmßb¾µ;@þ–ß[\ì§M #nÓëÀowØ”–‘(?¸¼ŒÛ~·ËÊ]„)´³Qüˆh ÖnÈ‹‘BgiVrìà£Id–I#‡bÇ+b˜$<êêM&ì¥e%a,­AKßR©RA3è$ ¥8!R‰ EøU¶ ‰ÜÌ?\’y‰0GŠAjW ïs=Bêu„!õ›D^F¢ZÜ%:ý.ù ‹&‰¾y¨ðåFü«Piôç»_%‹å»é…±Y”ž*¢÷–çÒ|-¿e5M¤·vÝàm;‰\nd¸H>·HŠÁ 3.’å u„¼k˜›CoYz䂘#uæ\"‚Nq„‹œP w•i·;‹m§†»fáê…Î2‰ö~¿Ó[¶b++Ä»†Ñ]F/}…8"ÑC£«kúEXÅ+@Ò…f³>Ø6z²ëfl‰jâûô¬UEâV-!7]"§!Hòž²5µ‘0+%9 #ÁG„q8W95P´HÕ.´ôðßÀôx¬\bóUQÝG‚ë `å0’Ȳ./ðHä¼ä^r±hÒgëk= }eÁ!oÓT„ÇM+ï‰äBßœ{èÇsco^-ºîu+%ìh[V0Zm¼Ú"÷­š9K7]h„@6ì wºä2kX,~õ²²ˆ ®¢z®ç­#7}©XÚ£Œƒ)’áâÐ0k"U±Ækß'qÆf‰x\•¥6W*Uâ<ãtm]å‰|%é9õïsÛí£ßt÷ýü¿rK?,ÊW²©¹%VĈhÈöµ¡;ˆm”ø%Üë,n³Õ ¤Rw:Ë]‚`·ÛAå-dq-//-¡-ç…ìËèöº´++Dõ¥å•,³“ö2¹fb)–‘ºj,\4â•ГA19OáÑ7Xë@Ó¨œ™Ð ¹Iã†Yâ1»àz‡ã9d‹›ò‘ìÏ–Ÿ@Ï (Šú€Š®ï Xü@MS­{²tY¸DÌàÄ;oŽA 0Æ' ºâz\@ O!`;üÐØadxòäŠÓõß…ÂÜÙ9Võñ*ÈCÝn×%å,†7ªe¢Ùj’Ã,IÁðÛ„$ :Að¸žçÆ Iy¼é">%4ECï5W(ò˜Ü’23†èãÓ¥©TT^Öá²F‡Á Q+Ð%D2;ÔÐH‹44}G’¹Â¿ €û“ýIòQ€J’ÛoØOf#ù7~••P`28]ĉXu;]EÌŠjµñýENÿ<‚‡ËKË Š,¿Ûéh{D {h Ûîh ‹0§mT(쮬XË+&4ÛY\ÖsÙ‹¹Üál¤Ø5,“¨Óre=hMÓl 9>k˜˜Ò%Sȉe*vM#¿Œ¥R×,C”P#-êp¤îü ÇQ6t ó’Zš…(D=õ² fˆEtÑ>²2hIÅ(E¬ÅZ1燂R8ŒPBÄZ|.Y?ÍA”ý,ŽÍ‘p˜#dþææŠî¥âCcoÎͱÑËW眵à¬_f­– ¶¬:lR8™ë’ï³R’“ñc@ÍPŠs®ïòBs˜2á™P‰D$[pœÎeÈ„Gåä[òH2¤¯©FôV É†¡K#ÁPÃDbù“㜘áwÏéýyÄrPް߀ !»ܰ‡ûÏÞ~Ã훋 –å%e‰ ‡‹lJŽ„C_ Âö }¡×ä2ÌC?//¶—É.Ã+³8!™N²…‹lIŸÜs¯³€ÒèH¨éw;]}¡¿h.-,þvoa¾ØÎÖá– ]¨hŠÆ…º™…§¶sdôÌ$â„’Aê¡D~™©hÒ«݈òÚ2åDâ9'©)‘ìç"¡Ä ga¥“¢$=MÔô¥D‹6yŸG²pÄì IÒ%AùÌ[d”.¡€TáÍMŠÎPÁ%¼A4°m…CW c$%Ðñ€ÙXqs)”ŒCÕ³¹1Ð* ÉtÎSÍ_JÉùúTªœ,û‚QSY-=-è¾vÒ—ôXR>ä}Îs×á¥\W¨QH2%”´·¤XH0ÑMRík“fU¸òO YI2Rˆ'ãXQôŠY[&jº¼Ä_öÁ9 ïs«Àão¥î§Ûýx* ÷ãa%ýsç3ìuÜÜç–~Xé)aFIgç™0T„£EH!ÂùþyBfé[H·p5‰Ö ì?t‘Ð…°ÍyæÞa-=žïö—–ûç{Ý¥ßn÷{‹P)ZŠû[Ú¸Ž°ê"€“Å„k¡4$ŒãRb™*:,fíÚrUŽÑRÍ´Í +Tr?äp]Ž÷0Œ¥1˜Ió©jäŽ/ ’ÂKûÐØž´±Ê*Â!kC ㇩ÌKWQ¬§0 sˆbf„²‡Æ†nÆÓŇ’Ž/|Ì›ñ$| h?ôÐìHqιJ¾û¡9˜ÇkäB ôŒ,â«‚˜”É÷¿JŸrJ7'Í sjÒ4êzU­$šŠ6`$¿Æ2_O¹‘Y7jUã™Kzž.žˆŒQ`!®”b¬§ˆH1!j ]ºüÕÐâXNX1mÖ–ãèÓ×IÏitŸÛøþññh?Ýé!ÝÇF éO´û{ÇŸôæþ¶ôà k¾8­HK=yqAø`¨¥öbžPxþË¢ÔÅÊê»±°ô‚ÕmPD–Ý€ŸùÛî¶o¡‡iä¥~¯}¾·Hv°ýºÑÇtÊEFs63¦‘Î+³ šlä}Y²TÒX:ò¤^]½Ò%™¬Ë‰jaa~¢H‘DTUQR¶®š¥¥"aÙX>B®t–Ä˲·O}„ô|¶jËeÈøt9Ö\Õ ÎuÑàáâð¹ËŒ0BŒ.»d& ‡‹èõëÁ„/) Q]eP\äóñP³äÀÙC²mΈÇÓ;bU¯èµq“ –,%;G·Û%U¯êZ mzT3)©ô­:®é7 U¥×Õ8‰Yº¤élWR+&JHP蕾*l#’"F¸Æ¡˜l&C?8§ñ}nµýéïþZ è~ú =¼ý"ýÞñ7ûïswé‡}Jú½ü7–ÞÑ“$xãÅ=›¶*ä%˜£[ZZJ§Pèïü`¥³´°ÔYb K×y.,²Ù=QLh›Õç‚m_\^AqÅ4W¬ æDð¸¡"—¾zÓvF½‰l¬Ö5-KE2žÙOlÄtHœô+3xÒ ‹­‚óRYð;-­¡€?ÄÉ J9iaß¹¾ù¯ýæoÏ¿ü Óäþ¶ý¤A’‰d?ýìg‚d"<ƒWð;A7ô»ûL’ì¾ç¾6|Øoó×}Èôöõ¯ã›/Lí;ûÒ‹ß~á7;ÇX¾¯mr2ýƒ{“åÉÁýrú0}u÷NúL¹|û=÷³€ÿ[œ•ÎNí{áÅ—_~ñ7{ù•W^>ûõʧc{ž>ìK/ýºÙ¿Çömð;ÿ»lg¿>þéØžÿu©¯í倯¼üï³½ò€öôqÇ=ûõê§c{þÁ‡ÿ8ãøòÙçÿ=¶³/çås`WÜoÞÛ¸/‚ˆÝ×GúõŒûk?¾÷<îK~íðÇú¯þÌÃÃßÁÏg†zø0ý;tèÐÁ‡:x×v`ÿ¿¶Ñ«n¿õàÞýç_þ΋/|í“ßÁ§sÜñrÇ$8HôÆôÚ£R)#s— ŽËìv°á^úþ$ÁÿÜMâR\°ãîûÄ7Œ{v€‡þë!àÇ q°‡éçá!vÈo;ÜÀñÜà}‡˜ýÛÝî¢îÀ<~‘O~ŸÎqËq¢_/ú—§ÊK¥x÷Ñí-DW_4˜Æ è&îþ<‰ c »fùÁŽû`øÂ€|¸Â‡vïàß!ºÞ;;Ì8À»9Ÿ?˜ivµß>ìåÝû鹿¤oøð|:ÇM"¬Áú`t-÷ÓI` ~ú$ýñq‡=b™é­7xËXÙ#ö¿ü(y°ã>X¦Ç—ÜðÃ{wàj>dÛ=¬éa<ÈŸÝÿu>wm鹯üŽGlNÓ)¦uÇÓn:xÈjžJÛkÓ÷ `וÊÝ4‰-m8[ðÂøÁŽû`È ¹áƒ‡öîÐÑ>ô~ªÃ*»ŠÙż·å_~áx;ø 7)>ª3¥æ†Ùf§XRö—&Ö“ÔM_%‰53ž;ø?¬Š‹w9?Šì¸€·/î½Ê?¸ýø®sp\ðv©Qjöî§_äþvp÷£»wðé7)…¾»ë±2`kr½=Èà©€yL¼+K¤(*IÈæÀ?¦4@9±;ø`Æ}°$'Cç0ó4ƒ;¤õá1ÝÓ‡˜:ô!6€àw0ÿüÁ|z°|ðåÝm`QîcwžÍîàÓ9î@Á pxƒÓ`  "R§e’®!3c y»H¡ÿê;Îmø€Æ½ e³ÙàAvdÙaݽÃïÃì°?|0=Ô³sÞ?x'%ÊŸ}îÕ=Í·ÇÃÜÞÞaïgïàYØÁ§sÜ2ÅañÙM†~ ›z¼Ûzp'‘_Œâ˜Á& ©THRÜʃ÷þñ÷Òêö‡"ðNîÚŸr:ŒÿxÏ?|×MæÁwøVÀèHàmŠó>*tûáà„ÞÓ>ŒÓ¿ÿá§sÜ$=w ~ªV÷¼d d+ó¤p“%¿XtÃ8L_ßcv^в;€ò Æ½oüÝ\Ÿ]> w°sôûôf†?à2î]»{Ç}p,óþço¼þ:@xöÃØöû¶ô‹ÜÓ>Ööé@qn»ÃP0X¬ôé0*¨|×é/CÉ®©ÚEŒK@)?Øq÷õáÛ¯ÆßöÆÚììõyÃlwŽ?Úëí÷¤Þè‡ðƒÌ‡\àÌîÈÿÏþüû¯¿ö§Áo|ìÿñvpGLí7tÜ$ RK5p–àj)`nûJ†Ú"ß%OêQi/hG/î…ë<²T^˜<Øqw5>^NÒP6ÞˆUB^Dµ_ÀltsuãÌÆìÕw>ø†;x´÷èñãG;í£ßo÷ÈïÒ;¶;£.ù7þü Âàw_#¾öÝ?Îï¿~œÜÛöé·Èïà‘åÇ:ýãÇÛG ·Ì |ÿQ;°w“Ï¿öúöƒï}ï{ßýÞ`žž;ó±üGïàþNè§mÜ$‰\çNKÅB&»@x($I9Š˜#Ç%¥`0e±k¯vr§|0ãð75UC¾N³Í°¢lRÏ+•?Ôþnl¬!¼zýú™÷¿ãNvÿÝ?ëë?¶Ôï÷z<"ïŒììôF~<8ØCïqÿh'3Dðûî¿öƒ7^ÿîŸþÉ~€øÚŸÜ?j÷~N?ã& Úyú,R’JÎAؾr`¥H Å"èZ¸Åó`/‚rP¼»ø`ÆM5eÛSl«ONLÔjôS©ÔƃêxõC˜mnn=·ýÕ­õÙÙ3ëW7Þoïà£íGþËòQrÂÇÚ>Ö“å^?­ÒÄþøÐÐpïh§ÓÛé}¿óXo‡Ë÷»ùtèžXý§s\ÅÙŠ'PüTž²)µ¢ãz)NB¯Ç‹ÃÁ4ïîöa|㦪Õë ¿ZÈ ,áx¥:ÕjÀìöµòììÛ³oŸyçÌìì;Ûwk•;øøcïœ?ÞýNï ³ƒr7¬à£K¾·#öv:uÚÇpóX»C7ùüŸãûßûÓ?yýûùüëßûÞ=‰±:·üá{=¡Ÿ¶q (µ T›ƒW$˜„äþw×v±—‹NÒU Ò4‚_ À2îž´íf³‘AlSæT‰¬á˜½uksó:yßÕ·¯Ÿ9ó<ðÌÛwÁ»TðÑÞyº¢¿·Óéí\í½Çzä~–Út¹ý~§ß9ºÔ>Ú~ýØÒë ½Þ{ùü÷¾ûÝïç™ ¾§0ÌÇÝÁ½žÐOÛ¸{@`$ÍYÁƒ(&‰ÒLé¼dl{F†Ë ‘¶ÁÝ@¹[?˜qSPM4›(JÙÄ–šÂjµ6Q®MÖÞÉf·om’ó]?³~æêFHVð:Á«w"ðNžß9ÿÚÎŽ0¿³Ð9zÌ<Ö^~ô±Ç?~ìñGz‹ÿW›Žòn”†îöÙÊRáCÞî1½ øñwp¯@ùt»0 eO¬µÜ;lX˜Öma» ¿»3¶©¥º€hÜlø †=²€t¯YKª“eÔØß³ƒÙæ­[Û7‰’~þí«³gÞž¥ÛÙëoÏf?€¯íÈ ó;äa޼¾xôñö½îcź{ù³íþÑöí(ÍÑcGX-’Ñ»Žé=ðãï ÿÑCÞ׸÷”5nŒ 0Îón¤dp‘:uažR#µfÈ~yP±IénWI@Ù±xP々…çVZ+!µƒÌNL4Ç«“A4¾!ÿûέw66ž› 7ÎÌnœyûúÕÙU⃫k·mà].˜õÊKëÌÓUÝ;ü‘Ç?Û9ÊÿVïxïèÒí(ÍñGVé_¸BÛϯìß?t(eß÷à‚?Þ~«½“?”¿½ƒOp\PùõKöÈwwg,n%ŠK,:7ð’wXª MjqŠn˜”Ká rœJW§èïZª5nŠ+ûîù´h‡mÙM2„ãQ’ L á‚òKot?v;Jó8ý½rågÿãÊÿüÙOÿéJqaòü·>ò°ïø³ƒÞ#½àÁ÷vwð Ž»ðÞÿã&¥ðöŒÅžN ‚Û@ dF,A PJq¸;qá¿ß? q¯›˜œ¨V*e´· }äï‡ìýõf³Z«G¥jy"›À[7n^¿±¹¹±ýÎì,xàUÂáZx†,á™=-|W2Âù‘ãǺ½%ãñök;"ú'ï<Þý­ÞÑ…ß>ßû½(Íñn÷H›à÷W~öW~þÓŸvv^wv¸Ñü7?ƒIø±ƒ^ÿHo'ŸÉÎÝÝÁ¿&2ïmÜÞÿ±°3ÿ`\Ý]ж0 ãRw%EËîûÍs n)‰îJ_PÊvÜ4 mNÕ§&Ó`·,"•¦lsr¯LVËYÆoÅõ››[åÙ­çÞÙX=óΙ-ò¿g¼¾7)r'n„ßÙéÿà5ž$9ÞùG{KG{;D°wŽ÷ìñ#ËËÝßùì£Çû;;WÌü•ÿùÿü,g™¹+ä}zâ{;ùç>€?Üui¹ƒGzÇûGvò#ùãPçl;ÃuB?ö¸Dtÿ#ŒË,Õ®T¸Ë8ñÁæƒÔx…颎] ¤òÜbL0 nªXÀ0.)à:ëÇÄâÐÐ! &F&Êa9ª6'+ã~¹VK_®×ËÛÛñúÖ LÏ®‘éC4f•ðGùí°—åwz;ó;ç{A$üh‡àHoá(×6ýþh‡ºdë=rå~ñWþñ¿]¹yãgÿx¥:wvøü ™Õïz¯§§_ä£wp^8Ò~$Ÿ·d+ŸovÀïüê|üq!Üw=vüÈ';.}Þ¥{—‰…ÛŽ’Ž2Àƒ=³”==иEX´ÀßË`¹ÓR=¨q÷eËSS“““ÌöUÇÇaúÊ¥8 ݨV)ÛSS>¹a0ûʹï¼|c{{ûíÏýé¿gά<.H–ðÌZö°ÇqÒÎyyç5þ|ùã,œ?”; ØŽ´k_ùû¿ÿû_4~¶þÎZ«õGç¿GïÞÉ“ç†öø|ÀîÌ–~‘ÞÁ£½ö‘vþ°uÐÚŸov@úWîàÆÍëdV;GzŸð¸GÚõïqÜ4i``§R´„ jþmv¶g©ö\g ßqR@¡ÇÙS«jÜ}Ùqs0 GÛ$‚0“˜«%•‰Záé°69•º`H­ë·6¢Í­çn]ÝZß„a\Û5æ„ïàgÿËQ.#ˆý^oäùÛþxgžΣ=:æ(;wt©×9Ún_ùÅ?üô畉Æz=ùÇŸÃj.ôzùç¹!Ãøà¤ýÞÌAúE>zí£ýÇò¢eò¦>’ì@~ïWïàžÆ%¢¨è‘OxÜÏ>þÙ£÷6.Ê kt?ZB.µO~0¨wíÜ ïw¼ÔV¥@ î ?¨q1 H?5ÌÃU FÐÖ«5êãÁIe²> ø›7nݼ±qcëí­¯•7¾onΞøfgW¯¾³¶qæÚ ÆÜ5×>ú»ý£s‰Bï¶?–éÚ?¿Ó¤ßù&½×ééÿùOÿéæVþÖf5ÿ³+‡G€ßê‘oÚøQwmÃ;KDÒ-Û4Ìô‹|Œ{ÿÚh†ØÎã=6]<Ïÿâï~ó—ïlÿ²µõ‹6&y¤—ÿæÎÎñ%:5óǽwèáC‡Ù:žöÔ¾n™ô1­Ý©­Úÿ^>sãz~dD¸á<Û¹¶_¹ƒ™, IDAT=nþ…ç¾ñ­oçÏæÿàôt6ûÉû^þÈg÷ÑÇáp´?æ¸årÄÔêÞ*µˆ Uäû{Yð™áž’M1ÞŽÚR\NçÎîpÁhÜ}Y2wé$ÝNÁ“'®TÆ+ QÁ4G¡Ù„I‡¼òÊKÍs·Î½üâ‹ç¶¶^|q<Šn$2ˆ/m½t#û> (èßäAžÎmÜ;Þé<Ò'§óZƒ²éâÞÎ/þÇ•í­›ï¼³¹uë;<ß#<$=JoÛ¸†¯yø¯{z_gS7h Ú7wõ.ckóZšæg;è<ú«wðqÇe£½0{îÊÙüç¾øõ3_ù¤Æ=ß;ÖnÿÙ£>þÙþ£»ä£Ç-'ÑWƒ­ñKe¤¹û·MTì…ê^3÷L¢vaBHajõKõ ÆÝ—mÖ§vsëdúÒ0L³Ñ¨Õ&›w- ÉÖ õW^ÉÞzéfýÖKßyéÖw^Ú®¿øâ+Ùòvý¥_|i;[Ñ_^Rxù5©}lé¶?&æ}äÑ•~¯?¿#÷{lºxáHûÊ­ÖöÌÖ_´>²@Œ§¡—ÉðÜ{œÀ“@ä?óðáÿ[6úG+  o˜†18¡½ƒ•ü/·ìíµ[ÛÛ[ë×7óGÈ ÿÿì}i{Ç•®?Ã7,½¯([I¸J Ú™És‡²$Ë’;÷E” '²ck·l+ÏÌLÆrl-í$vfl-@r¼FûF8¶(îâ˜dÆ?À¾ßsª»Á$“ˆsÇÏ“" ‚ øV³ûí÷,uªjC,wËÇGÝ,Eº_:Lt<ð‡†›Tú¶nÙœpNÈl\²®M'Zß«ª¢¼¼ÌÊ”Û)’ò²é\qù,/Èñ8D¨A«–KçC+‹‹Ú LouxN-–N9x ¹é¯Ã“WtýüY×QþæFÁ\šá#Í[2èØcéw!-Ó{ÿµ)@ºñ͘1>j€ywØPM3¾¡qæ§È~¸:¿ôx}x>r»ÝžodÑÚ)Yîû›/Ðö©–î€L«£ÚÈøÈØý â¿G%OM5æî`Ù¸é~ÔÀÔåO½-ŠÞûppûŸêݘ’‚»hY¸~ÑMæá‚a,£E¡ôÊ—–U@øZnœÍJ—gS%Ù˜Á2˜ÀÇZΈVW ÷‘œâœ§ÓO¤"ýxD×g¼6(ަV„»Zdù4–[mÓÛç7ebç<(tûp¸xCÌ0&FîM÷Gï²¢˜éSÏl‰“ýgp߸ÝÒÌ ¯ÇǦe1ƒ+úÃéæUº‡ ä!‹wàß@â¸ÃðøWú$8šä"xƒþGýœÊÝÁrpáÀãÈ{¼š"ä±h4 ÞØû0pA#!Ø—NöamÖ©9¸~ÁGÈ<\Z6e“ÂϪ¬N9™9š=)wäiš4eŽ¿†U|ÖˆEpeqçsìom³|@ÊlÎcÖË?`Y†î¹Ì²±·ðÜŸãÀü|óà„aLŽ ß½o@ÓL]ÑÌm›·4‚Ú_2°ÎJ¾à>cÈAÛ÷Y´òü±—ȹódjJ×Çþ82zo½A à0ÉÝÁ2pœ8—~pŽÄ–Ù£¹‘Ö®) ‚Ï7+½B¸óåšñð7°È[èu{¼²(Š ƒ¬×ãsîñâƒÇÎ9|y£ÆäèÈÄ—#ã÷‡G'MÓË ’¾rÐLCüLrË¦ØÆMiQÆ>Õlò¥ÏÚ;GqÒ‹tàý;,ǧ¦&§ÆGî~92<:®=Z Låî`I\Ë‚vqä|!Ý»wïjíÜÙñ#ŒB6¦ÿvÜ4Åe6¤3½çíDt—øåÃÇ$⟇‹Ó'K²Fϱ”VXPn›Åi¢袥ÀLþà“â5•XDZ‚Å|+‹»0§¦§ßRôy8µnOš—‡Ë,ëeáÆçå½`á–Çkà5Fï ŒOÜ7}È@åÕƒæ¹ôF²?þäS7oèí£{‹ÑªY;ù"Ë’¬fÚYŸ*gäùã¡ò,ð$DÀ_|9>qwìþ›©ë×c¹;X× úîK³VÛÓÓ±«cç.p4Ïaþä¡á2éXú©'¼¦q‰F4æØq…ÌÇÅ„±,ÊŠ¡P¹3G#K;WG9RÄŠ–òižXáDIq©5ÑhÚ\!ܹœÊRo&xi2ha|pŽ9VWöÙ24pqâµnpÌǰiQÇ'›œÆ-Õá;ç¹74 &<³ “)`|«Í1Î;C²¾´µU"îî©Jj6ù‚×VÖTA¶}ÀÅ: * ¯]G¤ããcÃ÷F «‰)/,ƒ¥qO°^`ËøŠFïijêÒu=J".:Ñ …àzœãõë’Jüš_•p X2‰Rꥤ¢&äDT©lÏ̶Šå%kÀ¬–«€+U¡`…Á_JJ+€(3”j¥p牚#l% Æ3jÝàÚcÁó/Òä4miž°‹#Sc#_ N˜«L“3 ¿—yÃKösè`‹²¢dź†vòEÎÀ/ÉN5Ìb¨Dñ‡«ë‰r||tèHàÄø”a` —»ƒ%q9È/z˜hwt?ð¹.Ýíq?D\̱»°”0‹ëǹ‰ß/+ЃN„B/™…‹“tKíµ3Ö–VÔ§‰‚ÕóðPæŒg””WàŒÇ×–‚Áȵ™6‹*pN ®,î2Xö¶Ù‰h¸¹­Ü‹Y·—žb¸÷á”ã(fb‰(81i€Þ™4sµ9eø’æ0)ˆ ?¼Œ`qeмlòãjxIr|ÀÅ: pÁÜáµuÄw|£Éñ‰ã¥t€ÁiΖÄåÓàÄñð,ÚÞÖ}ùx÷ÎŽ(ñþ’yh¸øú Ë)d,\¿ß¯éª*Èóà« ^ÝG, Ìâ†pq4{ŽxqY°ºÂ©×³‰b _PžW€;VZ‚»†¯-Áê*º´•µ·G%ÖZÍ^#zepW–€ôŒÒÜ˹áÆû½l"½ß3¼h¤G€'GîÝ5eS4ÇÇý<1TãKãî!ƒ»'¢O0Ý`'_A–…ŒœUÀÜâ“ U¿¾¾ºÎÏ?>Šü»<]Ä 'åê`)\˜Ÿ<¯w´ìljÛóRwKÛ¡ÎÁCÂ¥¢ …H™{ ˆ¸³=¸~À@I÷K>Vðq¹ŠÈL\dZëÒX¡"àŒFd‰°K¤Š!R¥z8ƒüñb0šAºý‘µ’hö¼àÁ]YÚFndÖëvc¦ l &§ÑÕfÁÂmLŒ ‚Æäè½áqSP̱Ñ1Ã5ø2F ÐÆhqÁÐ8Éd`Mœ3’³¢c¼‘§{×?Vç÷ÊÇOLN?þxÄKúúdA”ru°.rÍ¥ÞÝÖ´ý‡mÑŸï8þòîΨª<\¸w‰,÷<¦ýðOøu7¸'À7¿Ä <çõ5ÖD27h)U-û,¢”—Í2•ÓDY»&€ãkÖ‚UT¯Ö””UBüZSSEË—ËJ‹g˜àÂ]YÊ>oö§Žµ—uÛÉiLÍ ;ù´1~otÜ0¦&ÇGÆL7§˜ÃÃãÆÈ„aŒ#Cw ](%cY\ œ|‘3‚$Šj6úËÝ™š×e¬¯©'n½ð8´pƒ_$J,.ȹ:X 7-¥3`(Óú®öæç~ØÚ ž‡P‡Lã-¢XÎÍ–ØE{V ê P”S¢Õ)3Ù-Y»æqhkÖÓÅ^æ!+»,N-²Xôâ<%*§%†º N8Íey­ä4žsð€¼Ö˜½™ºo2²y|ê>²qdhü>H Ž,ÊÔâbJMQhò^”éò ¶ Æd‘] ‚u_Þ!ºO ôÉ…¤v½_ôIÕ´þ\,…Ë ]Ò‚ÞÕ²£¥kWS3¹ñÙ Òµ#ªªÂ_‹k± ȇÙE–$Ìá !ЍK*ľ*§sºÂ1D“}EŒHˆ.È2k)­;F¢ËðÀ)º í¾­]SŒzåŽR•Z\YKUÊš½6W>î²è÷ÙÅ¿’€ò)pÀ¥ŒÀ»¸ÇYšê§ WŸyY‡a‰‘Ñq àäˆéõæä$º€S#÷†Ç&DÁŠ–P‹+ Ï§R‹&czM= ?Yÿ\@Q´G¯fw@ëîWÃ#“„0h`CCCícáBÙ?nh*Õäè`)\&c8½³yûö¶]Ý-M䯇7ÈîQÌ;ÿ5¸À=ZŠI¥R’DMÔ4ÐM`"8z*!^^Ujª$ˆ¬NT…“erëö*OÈÌj‹(x™±f¥Ü*Ê›Îו;Y9xÃÚÊÎì°gŒÛ‹·”:[ÊÌ,›Z)Ü¥Ù§_¾xñ³ùËæ¬œI@xJ汋O[’O*öI.zñ0>cbl|L°irD1'GǾû Žà}ƒìW2Ö`g -nBƺ&&_UÂ`¡ãS hù耰ñÃ#c„pÄ«¬o”7„ Î?…‡¹;X ×òÛôΦg[:vwõ´u‘×ÈžnBTõ¯Á-ÃÉRŠÀJÀFEÖd– A$¢_è<0Qõq‘­ªä'î+7\Dî:¸4]bÍÿF¢);œ:=‡(»¬Š®š ì•ijë§Ù›.̪\!Ü¥è÷áeàŸÿÊ\–é’ij>—«ÀîÈ"<†Rë%¦}Š"¢w„UUBŽ>p vC÷†FÆ –”“u£÷ÆïAž„°á¾¡’—%tÞ镤Õ¦3hòÅ*D nÖ?¢˜¹: ¼åHÁ%–ü>Òð¸hð)…*( æçrv°.D¡,§ïjknéÚ½«­½{·¾PS2Ê_K€\ªw”"0Œ,‚»Ç‹"Çò¼ÄÉ~à„!§ù ' ÁîýºL\Wo¬Ò8  ê˜J›(h ÁTVXÂd–•;rUf×U#S*ÎL#$еˆ}eæHÈ á.ĺôûðòµËýó¨_ðÓæÁ‡ ³9“€ùLæ £¦9ÓapúEp‹RÐ[Qñ)ÇgÞÊdDÇdžF' #Ÿ¼ Ô»?99úDhH4žD†ÖIµŠQU¥ßÊ­9 ¨åì¨'òy<X®†àã`ƒ!xôCèÞUΖÂѽÐ;ÛÛº£=?njn'/‘žBÇåLî_ó{xW±‚&»ep%…žÇO ø)0¼¦ë!°.Ê:¢¢>èò¥Ê4QÖ¢ë:D UØž™$LkU­“/.©°–ï£Úå,#i¯ã2«"zep™gHõÅ/_üð²ÿÚeÿLàÉç½.ŒÁ"<­*¢@+¢ánÅhÄŒÊ3à]Ë>–÷ÊE®#0p|txljôž‘ ûp .Ù/¡ëN¸'ãe¤ÉêS)øŠ}Asv0Óx¬6ÐðbÃc ‚è›0cïa’«ƒ¥pE1#’®ööîèžžöÖçvvì"ÑÝmdÁÄXî_W~i2æ”…èŠ(º_UE|@NôªV\"3nx™ˆ<ÍFË"h"!=u«À?K‹¬1 0•Õ¸D³Õè ¢i¶8³‹Ö—„°Ò%»íViöaî¼àÁ}D¿ø‡?üáã‹ëÑ?vÙ!‡~ù2’ï|Í B, ”|ðXÄËêiIfÙøj&œF8㺠0L©$y ½Ÿ*ÊÁÄ‚111>9ö•1f¬¯Z\WUOÔ$\+U 5«ø¯¥fÉ º}tj9Ž­d'ãä‡îO5ÁŠÒÐcµ/5¼þúëDñOM4÷’‡IŽ–Â…H[{s{twOW{[G!Q`b\Vrø¸ºHtUW%Þ!𪠂VDye^y/ñ+*a™óx‘©¢ ‹’(¡ì):#êd®µ”nm%*UU0[£bÑe6Q,2TU©L9Û¹-¬€+ƒûˆþÉejM?µY÷±ßõ)åòïC‹€—çÇ çñod’‹ð4¸â²ÈÊIE¯Kd„¢"¶ü?0O>œ)ÖGðKã®aèvþKÁÔ~• ±®½x+HÚ,ëiÂ[ísò€¹; ãã£RO‘ŠÀÚµåµëÖýìÅ_oØ»×PŸ& w°.8éêlnŠƒ~(}dw{Ó¢j?Í£ä:ðù¸º"ü­,iX8ÃièóV‰£™¿®°Œ[Õˆ_ÖOápp‰> · 7ë(µCR õÔÎúUY[‰­x-Ë€ã£ef +… üp@@Ê@ à‡^¤ \€€gñOÏ ˆ“‘Xî}\ƒUÑUZŒæ“eÊEYàÊ> ò••Ö$ưñ|W£©pg›Xg'÷ fùPðÕ™5›|Q猭æîplt|bŠàÝYRR(¯8ü"4²7hhªñÊ‹dá–ÂŶïlÙsìСý‡©Ï°»ýÇÏEÉÎí$÷ÏÇÕU‡HË ™ç½XÃFVK+jš(ª0W=>‰^Õ‘Ï# â†BåÎRº¥kÊ‚U6Q²•UªRµ6TûZNï§e+ÕœmV 8Í@ýâMpøn!©ZœÏ¿Gô§&ؽ‹…'Ì3 pN/ܪgÐ)œ HL¹”.óøJ«ÜB‰©ëÄ7& 2.žÉóœÉ~9CÍ—Lc^t¥œä‹:gl5wë ÇÆÇ§PZ«è®å¡êÚš½5?{ ¼ÂW^2î`)\8ðr–tw5ÿ¨cÙG^ †ö~wËÎç¶7õ´Fsø<\¿¬Z@ðëT^àD¿âÇe¸o°Éº¦¿Ê뚟ç}^øYÀ+( ⧇ÌÊ ¢ÌX3-;^1sÆ.è‘/”9K8gs'óò€÷‘iº>Õõ®Üô_ñßúôÒÇ–~t ~ñÉüÿBȇ‡ó‹P”48Û:Í஀ÑÏ(˜ $ÀS¿@ð ÔU0í¢#EM‚TНè/y!öì]]DÄ aÁ±u­Áã°§ì ãÓ3…œé»–I]“:½ˆ}©³±Ì¬‘•ÁE^¦VøòïÿéàGW®ý·.ýÞåG"ÿ>_h‚:õ%4Ág! }€QÕÑ÷«p^‘p®Õ'KB_ááBpRM¸×Iyy=Üùàö€o#š ËòœHˆZ³g’,ˆ"ºõV]TÍ[ÍÝ™‚(däÞ½¯î(ʽÑ0!Á½u5õ5µu¡àë^>²pKá"ŠŒã¤­i{÷ž£ÆQ】æÓ !m­=Ï5Gsø\\M~„ç*æ¢5™HxÇúUQó‹~¿ˆ{~QTeå0Âà+æå‘…qƒ¡DÁAÛ@yÖ[³²uÎJQJˊה£TMÛÊ,QÖ–:u{+…ûeàåË—€À@›€þ[¿ÿÜÅÀß/DÀsÙ<à?.B@.Õ(ùì“ l<-ö)ò)EE^`=8Sƒ@n}I•½!Id‰Â™,ÇŠdP¬z:9‰ƒX²ŠS*M¾Ì[]¤°Ácc£ÃC÷FFAņÃáðÞ½dtlâ8¦fýäY¸ƒ%qEIòÇ Ò±sÇž#@¿ÃOÓÔ¤§©-šûÀçâ‹ Ž…[P×áÿÇ<Œ%` -J'”Qy]‘9Æç…[]PDƵænØDYÀ|‰U,o×ÍOÓëÚ5è­#Yšý^2½ÂJáÚD6} ø1ýác$à¥\(Å(1 sVÌä¹Ê <¥x·£‰ÑÐ[Q¸ü `2Ø2*’JªÊ×ù%p·ÉK$Þô2<Ë+@@̽á}Žßdjldy€®g8{lu‘üëïON€›˜TW©£áÐÞ &u“:8òÓ#$GKâŠ>Ÿ×xÕ0HûsÍ ‡Œý†ï8‰FåÜ>W•8E°4OƒW“ÁÏ$¢D8|¤ ͳ¢Â]«Ï0ˆy®<5®,ؾÚãe!tÖœ¥ ʳqâ;IW ÞZ°Ò"Šc,K)Qæ!÷‘,/bóg O?üðF.^P(Y}ñ‘Ÿ×K÷ ŸL D"H]4eÉ…óçN’M¼½òZb*‚_$Žˆ¦Qîó1^†P>‰±†Àñ^AÂ)ýê™>8ësÆVéÀhh˜ššRàkJÕ‰ð~{90ð_ÚGH®–Ä••"0¼G4´þ°}šàƒc¸5Ķ,\ä„¶@3v!øåÁ“$Þ"·Ç]$a ƒb9‡’ ä%á®^EráR¥*±•ê±’ÊšPvï¢lÍh6ugÛJ$DÈÞÍÈ!Š,ÌVÀÀAÀ?þäF½Y^üøæR0È?œœ·,«ÌgàL¾ñÙûðê`ãYDê4ïÅpªzš/÷I¡¤š˜Ñ­!÷§L¸¹YÙKdÓðñ¼ “§0ÙwJ> v/ò”|Ÿ¤CýsÇVsw`T””5´ªNðsúñãÇ÷ïßèÈÑC/¾´ïÉÝÁR¸à|ùÐ ù¤óGÛ÷¼j#ì³üŒº\\¢ð bH@øôñ a½î¢Âü¼·$p#Ȩ•"¼²À3‚NV¯ö’\¸UAJÊŒ’5Å•5UÎ.ŽPe©âÌBcY´ÆÍJ³E+´ì`ƼàÂ}ÄaàAîn|rq?¼vóÒ@ý‚DõOS}çÙqȬ(ØfKÆ3ðÙ.,ƒµQDÖ-±²¦™ #˜djrrÊ$`XbNA 9¨àhžžÔ)¬®.*ô‚/<8wl5WF°¤¢rAhjWòË~V<>]ê©äî` \¬F”€„ówþèÙ=„EZÞ·l\‰FX ‡ˆø&è,ã-**,,ôà"ªþR€ÛçÜ*a Ü$'®U4€a'HNñ㕵UÖì@Y¹“®ËfOìÚLZEÌÎf2¥¥NÝ^Ù̱à•ÀM@ÿ'?ñ|Ù&àg×>û|Aþ©V)‚.y½J ÿƒ»eú|t°]óã©F[¬R9T5do5S:Nj¦ès3æý±‘1` 7ï×Êiå” „»û Dt0­®<7˃ÐÌ[ÍÕ©)©¬FþS”"íBÅ-áPtpRÎÝÁâ¸xàŠl‡ÁU±~¼‡-$‡ŽöŸ$ËÅXÞ¡ÁëºÈûX™(Ëં…ù… „>Ö ëXŒ '•ÄiµÏ´««Áíƒ[¾¬ÌÇ1Ã0>g t±ЂbÀ¢û‹ãÍS Q¡P*3wKãêªaD÷ìî†Öݳ‡(\Ë2ö< ]ä“%qÕ"·Ï#øuY¦qÞ|Ô?ÜË„…X„aYa:Z’ ë"¢f²èñN/"DµÇÊ‚ H¸*®T¿öñÇéô4kŠšõ Þ f²¢"TcW™Î JÉ\>|\‡X_¦ü»üáÇ1%xéÒüBTEâÀ9¡ôË[mеšj ÃÀ9Cqt_iц³±(`…0޾S0‹\pxx„P1‡G TœƒIÍ£œIªgDñ¤ì ”ò Ëø|^Ÿ³(Ïb böŽ–sæå7T´Ú€&çî`i\]0˜èîîÖ–ÖŽn âž(eW¡D€}ùÄ„oKájEî¼|A€»Ad/“ŸïÃ(Ç/ø(ÿàKèÎÔˆ&)_ñƒ¸èñZc¶N@ZZ\Z‰ƒ%k×àœHh%k‹‹áIñÚx¶¶ØæÍš`˜Ó`–)eÖYÙì±à‡ëðïc«"æC$àE‹€7 Í& Þ§Jà[ú—— ¤68¯ °U<7o³B0“Ö ¾†• *‰Žåx M¼¬ 2S05qÀ/Ç& S%æÄÐ]c@#&è©ùN«§ûOãr+X¿””O1Ë*àCc»ºd‘èQVE\Õ+¾4Y‚knâ{rw°4® ˆoG{ºv>û\S'±«{!QJE°ø·$®ê)ZíáˆHxQd|î<7BÄ•¸ ®,˜ eZ`ýAö¹AdÉâÇ¢u{QÀîU–—¯][êŒÏìyBg’P 2f r% Ú›•Úmf"z…pþ]ºhñïH@ÿ ”ûä“‹—f2PiŠÈù¨ûG hkàêBT@ËŸ»MÏUÒA|]ï×2ŠN½kp¾i‘:V#©~â^Ñd€w¿™2 bà‘{V+!M<,`ÊiVWŸ:s Y9)Ë&ÃTÊí¬—qòj¹;ê8»‚—pÀgcà {@0‘³ƒ¥qE9#‹Ñî¦ç~ôã¶hwWGk[g‘ÙUù2+H¤ðQ²$®êÎsåãb<˳^MÕÏ W€G•N}ÖNÁ=¤é2 ¨¸—8^¬\¶‰BgíV”—”e3Å»ŽÙfuÍÊqýðb K©5™rz."ZY\J¬‹À?¬¼èðó>ºä¿tñ“O®\™ž-¢ÇtMX›€`ƒ¡]«Vÿ@Š8×…¹;%y½8ˆ¢£ŸÝ臋çN@à08ߊ|Èd“cƒG?ubtlÌ@—ß%õžÝôŸÑÍ3X]Ý/g”¤Hl²Bõï—ÏÚw¾ºH@@…cyŽ“œü¨B /Q-Œú£¹;XCQ1º«udzϵD­MÍ-{èZH«<Š˜O–ÆÕ¼y^ 7_ÈháJ’O`­2j(!„(ùD)Ô?ÞJ…£d•å RÙLñ ÂdsÅ΋¥%kÖ”T¢V9U{ÔTVÌWÀ‡Š‹Äº|ý¦E@à%àç]ºtÉ ¼tåÊ¥l*ZïóË’àDÀ@Al‰ÐW8æŠæðÏ:ˆOjú°Ü . é×­qL«ežEãþøèØè¸¡÷'GîŽj·²«ƒV¸šìöÈý§ÑìœÑÌ~E€}>¤ý9»ºd‘€u2Çs,#ëøTäx0ÁЪîRsw°4.8ËóÑŽ–X—ínoÝÑ´GÙEtÎ'¯ÊƒxI\uµÇCd\hÈWT§I:¸¼$ó¢Ô­íÒÔ]=*ÞCÞ|Âù?^‹(¥ViTE¨ÒÚæ¼<»©ÑŒoY¢ØU¥k×”V‚¿fG¬% ðáãZ¼~óæåoÚÄ¢J@d ˜a‡zBOŠÇw)í`‘›òçæ) J8ˆ¯Ól.5d˜Ò€KH4¿¢œ7³ø €#8;]3&Çï}5jð\U#e>ƒ“À…ž=˜Š3Á0! `˜&¡¥ ôÏÙ%r±u¼Ìó¬¨ZbÈXßηJ’œ»ƒ¥qáÀÛ$?ogÓ³QÝmoÞù\T\¢ó®P&K⪯›p¼ °^Nñc©qp¥NQìê’è:`p6œlá<~z¼Z4в8ƒ…µ%Öš¸åÁàôêeÓJeÒi¥²Wú+-^SªÆ}ìœ %¸²¸6H@:A³Ñ)ñû'Ùzh=áW ‡ÇÑ {¬i°Ïcm-À Rl½X¨´ŠXy‰U¾ôBš>Yî%±™÷[R‰Ñ\¡HŽà„]U à$1ç±® ¸’šiª2ëñy<ð¢n“ úEÉÑÏ- ¼wœÂbÈ¢ ˆCkº˜»ƒ%q½<ÏpÀ†hg˳?Œêj´£©¥3ªaÁ hM…_We€€'°“Ñüá4®—á%JÀ?Ô5bY湸QÐV–‚ÊTV¦ëUʜͳ¦•Êj{p"Q ªª2›5Æh5¸²¸”Y7oÞDN™g}›1¬÷Jè‡Ë’$â8ˆµ¦Ž.¿ÆÁM N‹t~,d¼,\4û ÒG¢ R7ܵ>¯(àòŠr8 . „¤´ µ,+qñ(/ÒÌS ŸOðøXLò„8w~®py‘øñ 1 !8àÀ³Q%T Λ»ƒ¥q}¾*AT `KTÓ¢]ÍϵFyº¨ý²qUŸB€Î……ÄXá}$‹Ë3¼Èƒ8ŠH@qEå)ELÍà#Ê"˳q«(¥ÖvZ•¡ŠéÍ:,q²—Ê:k³” ËœÁ[‹(3gÅ­ ®†Ö/_½råŸ_½zU¿zé³Ï/_Óõk×à¥+32ºÚ-™L&ÎÿãçzÉþ¹MÓç&¢á"|Àzy:ýó¦² Šš sÇbk;/†éäE\„+³<ìª<³[U¬YqYšÙbÚ  Ox\ÐÇUfMð‚°l=@&˜ÇEÁáç¢êõ¼Çç æî` Üëùɲmß¹£-ªvD;ül´KC®f°­…„pÇ€œ›¨tq_YëVõ­Cí¨Áz—Š‘²—p^¢Ö’P¬†”I÷\/znU°¼d-Î /--ÒT”;²ä„YRL{kÎn—e8J[RI§“ÓlJIq¶t~¥p³ìršßþ柿ôPî] g.T4«­nùãfy)ÍóJç.ðùÛä.ðÿ9.XQ‰æW–ò™œè-dŠ<.³çÚô€Z¹>Ô;KLËYV`ÙgYŽia|r«Ðæ¬8š£.åz0Á’J ® 88„¤ZSÕ–ÿ¬&S´jt° Zã-ZEt°.Gµ¬ÔjÑŽÏîØ#[Ü•ËAg).…e,X8pë ».0‹)¤•aDØB?/₃Ðѧ§£CéTe¹š"ëó n"àzE Å[Ås|ýš+)«¨ª,wVаè`Å YV8Be¹pt©!œÍ‹kºXîÚÌý‚W÷‘‡Þæ¤aÀ Á'ÇùošsÔö}œåÇ‚`žD,,2áz1žS4Mðpµ ¾¡ÊuЉu}>[L} ÷€a½?b+…MMÒ¯³SR®dUÎ Üxܵ(ƒ æYÒ©¨m8S‚sé ëcÛ$—RvÛCP9;èQÛ5¢ƒ×*ð`²ü`‰2•R›¤tvvh½ŽÒÛWz \™W“Ü,cKà¶ó;…&Y†Èã‡ÍQpÈ¢=]»ö4ãnss‡ÖUàz¶ÀÜWà:¸ðÊ<\µ|>7 ©—ÅË3…z»Ö!Ë‚eŒ?àð»::;Âù}ùÄ»ŠtuüªYÖr¯£T¥àöŠèº´–´¬”bzÕ4{õ ²rœ9i­éRA·T-µ–šöWwab-‹9v4œÛæ“cxŸ·¼‰ùT*ƒ>‰K³;šÄLs«ØÚ¢Ö)ªé1MÜ)Ù'+&ß,´´6+`‚›i¬{îÁ7ÜŸØ* ÌYNâ9PÏ+JMšÌz‹­$wZµˆóÞ"'¾Ÿ€éhhÝÍîJvtêšZàJ¸”6Q/pið‚}ì`)\¡Üâ½§kÇÎ(j}õðÏì‰FáÀÿÜ’lêJ(p¥·ÀÈqÀ-pÍÅõkµu·³këÐÛ$Âó>wA£!†Ö‰¸RŸ,v¨a‰7Ÿ¸Wˆ…D&ãxCè«•Z+U£¯ö¶2šå+›W··R¸ 2ky̱£áLž³Ì'|rpjy¶‰å$)”À™a<<lø0Ü㚊ʧû‰iêªÀ &³#nâ•]ä ï¬ï?|ÍbLàD©¹é›"//*ܹäqÆÍs ¼v;ý£M­g9Ð;]’9†±0g¦CÕëàûŽr:¸@ tR)¤¶:XWàCEnÖÏE÷t¶áÌ®ÎhWgwg{ëÎh4*´UËâYÀ@\èhÔ¥À5·CƽÇíÆ²+U©ƒ(·(_ƒˆ(×…ëÏuu¶ezH¯Ô–ɱh5ñ¸üäœzB8^ÜTp-æz×TÖÔÚÕ¡Ùjѹ?`í”]Eêü2„5~Î\Þ™\!ÜE¸µ.e‚}`>á“ò\ˆoi[eD”×$ð ÛÒ"ÔJn¥aê8ÏÍì0uEÄð¾½Ó0Ì¿?h[ÄÖ˜–m;wìTµòÒ5¯ñ»ŸlL‹Ù¥isv íðŸ½P¥¯¯®®«®©­'ç?€ÎSB`4wQ‡ªÀ,‰ëÃaÃöŒF÷ìQ¢»vuµïüq4ÚñCÒÕw`ÜáÀ¥×î8™‹ÛµKï&"ñå‘ðóä®.Âzò\.PB®½c'ßÒÄqL+×”ìêHø½îG Sð¨‹X'„oÞñf‰R\Y-µÊEk­†Oœ—²E¤uµÎ H–r»rj!>dÜ¿€KmÕÕÞ­vÝÙÙ¤ü‰ñµÛ™V±¥­‰û省]nUñë?|Ý)ðíš©ÉÕ" ù‘õáúª`u°¶¾–‘ȺõëÖ­'â *ø„àQõâ‚ .è`i\þOÁhWWw[ëÎçvïißNTö·´â‚Ç×Sà‚ßUàD,̘‡»›4Õ­]wGTðNywqù¿¹¯¹à®ÿf=-ÊN¾‰iîÙE>ÞFà·xBºµ] /ÈLi1^cÐÄââÇ‹_ƒíqë?œok¦±fM±ý…Õ¤kÖ–fçoÌ$àÊà.Ȭåú€ ÆË³(Ëb¯˜T”ÿâ¤Î&¢N¹£³¥[iêDqlZt¥ƒ§&瘫¦Iè=š"ú3¬’ƒ;ƒÿÙ+ïL[ )TQöX§)ßýÆ}ž}ÿÜ“`ƒ°ÉúGë€Fx]ýºuµ`|CåUÙ2caçYè Ÿ* t†¸Â…¸ ƒ¥qÁ‹ Ì®Îö¦í{éè ¤¨ñu÷¿ \í£J; ©/@\² în±…¨kêî.­Ë>²º˜$77iÚ×r“Ì75©j{Bä||48!.›°ÐñÒ`…¦¤kC±f¯VŠ–`Ýhi }†k­JÒëkK­bR{-|ê«•”Í BVwYTû‹ÚL¶J-Á–Ö?ÿç†þKTXè­íìѺ”¯÷ 8²Jè'¦ÛTÓ¯é<ÃÈš9Ý$UÆ5¢U™îÇVÂ…S‚Áò~%ó‹³.løÞ†Æï7Æ7þª×¾ ¹;ÐAñÂa#b4DÖÕ×W­#»wwvìªó+¾f: E«ZÌ­*ª[UÄàè`I\1ýcÎê1¾6²‡ÍMmA‰£¸ÛâÀçɸ`»yªâ»U¢gZ9éEêâ%õßÒ)RÜ]à0Pq‘„bç¼ã UJéÜlB.».d™•&É.‘aeQ¦_.sŠ–Iäøz–€+„»²IúsU{›ÔÞùµ¬É%vÈ­íZ§$ïBqÐ;T„JI3ýD5ÜSÒš9±©Š¯ád©¯Ijma| ûg+áâN€Xþª7Ý›ÙOÿ 7½)óô¦„/Ú™Ýæt zò¾îà°ÀÖ“ ^üžÇm8¿y¸’XÁW4ùÅZ"‘N­s6®äÉ“¿¶`ÝÄz’Y×:¬¹¸’¯Rf¸æéb½žëxó¸yǬ°v4 Xƒ^‡N^ÎáN™ói5g¡@Y–SÖÏÙõWwe ˆþR«ðŸú“Ü)IZÏ×2ËJJ·ÚÞŽâ( wئk}Ý&Ñu‘+*de³U¥Ý)5‹`”YQ…çvì´.š;ùMºÈ÷=vãæ ú}Ó…L£}A—è@hS”¦¦LO[™ÛÖ²cgÜŠHÜé +nøoÅÕµçv´[¸ÚÃÄ¥'$çñQhÉ5ù¿•ØÍ©‡·_²~˜þ%|•Xæ±Ôþ¡7.¯\YÜ•% úKnîìlÑE¹]UxE ¶(Õ-ê®m—$Éuà¶7w´Ý&Cü…LŠbâ6-b“*üY•›Î§wøZ…â5î5¢"{ÒçÎ}Scã÷¸tï&ø¾¿Û\²ƒÚ`¥ªrJBšÛØ pè ñÔÖÍ›i+n›@\ù¡âZ'$×ñqÀ5Ë”¤D)±9TbQÈf ýÑ"%J‰Å1ºQyv}ÀÂ]Y¢¿$wtuF:$Ž—ºõîjõkák^íiWš»[:[«šÁ¦~¬T:UI¶]?…åÎ*M!My®ŠžãdFÑ*ž5˜p9÷ä6^ G‚eíïqëYF’P-A8¾Ü¾¸¡ 3HAÇa+hᔳ‚óŒ-Êé²åÓkÜgߘñçÀ“ª•ÅýëH¶Ð] P–ծί[šAäB-r›(s²öµª±pzIòR•¬ÏkZ‡Â‡x>³SÐXŽù“byyÇŽg½|ÙÚÇŠeMõ–cÂ¥qCúûxlØ”Èl¶¾Ã£õ<ü¾¸ÕUAÌÓÑ.]@ŸZé8{îx¥õÛ`°²"h¥éœßTTƒ•Ùwч`UÍÊâþuüëénêîÎÁÀÙQpWwçµ·H|«Ôư^¦ª EâN¶…QyYäÄ åM¼(î¬8‹%vªäAâ¤Ï>Ç](**òxUM}‡³ýT|ã¦Íxdâ½›ñ{ãÆ OÛ¦ç!wPÞ»ùÛ‰K‡ÃìѰP•½f } ÏíOl8FʾÏú-¾Šï ¯X¿Vw1‰Ë%rzÏöΞží= ÿz&ÿGÚÁW_{åðKÇýöáÎ%`–rº~s¹^# SLßÕÓÞÙÓÕ¡OÿÙ ²NðØ¡ÿ‘vNÐÑÃÇýöáÎ! ~öw.I¿páâ .— pï.È@½µgG§«§ÅfëÖ­6lÝúÌüʼn^{夽úÚk¯þ÷Ûˆ;›€ú…/ι\¿K€üýº&´N$áˆkî6HÖ;Ûº]íÝ;t‹ßÛúO|ë÷žž·8ÑßÛßÛâm&õ >– ‡¿;«WÖÅÞKW…÷úŽ]€Ûw=»³½§•îíµµñ{›·>¹uÓÿyb˼ʼnþ‡Úk¯¿öwÜo%îÌIIÖÕÇû±ªŸFªzE>Ü{îh8\Û/ÍYžˆNXjîélîìéDk½õ‰ï>¹eËÖ­›þñÉïm»8Ñ¡ƒÿí𫯿zôðßq¿…¸ÓÔ/„×¹>êMÔ®ëðï R8Ñ'#ýê,ê'±é==4 ÖõgŸhlܸ\À'7|÷[çÌŠ{ù6´úÁð`=îŠP_ŸáT*ƒ·Ã©Ûƒƒ7oÂÇ­[ð6x¶H¤^‹è+°Âõuõ¸oDÓ‘H_‰ÀwÀßL€Ä¿ôV&óVìí·ã™xâÔégN=sêTâíÌ-¶„ÓN3Öã6a— €5ŒH}ª¶Ô% ·/ïO&ÉäÖx:> 'ûúÎ2'â‰Þxï÷ãñ¾x2‘ìK&úžI<Çh{2–¼LÆ{ÿ=//ÿ<ºŒSÞÞ¼üü½ø¼ÀÂ-(ø~~'ÿ‰ü'òàcõêÕ«žXµzÕ¬ö¨k±¿}tú­«V÷Ð +…ë0+ŽÕÕññD¸!¡GD1\‹”K¢Íš—yê—ÿäú矿éïÙÙÖÝãחׇ·l݈)¹gžzzóÖ¹|ø7H?€v‘zÊŸT8‚зëoãç­[·nÞD"“€juáˆ^¦âTH¿T$B—0À'ù™€r9t´xè…t:“ÎdboÇOn9G¦õ éð1Ýð9!û_„ýû ÁwýêW¿2j‘x5À½š°1XÛZ§G/ [¸±x<™LHç’Éd_R2ÆÏù ã‰DZo< ¿íˆÅ™X`ÈÌ©¾YýûÅ›§OŸzû_ß +„÷Õ…ÃûÂ[Ÿ~ê7oz4p)û}À–0eSxZ ‘·PÿÂÀÍAx¼x(+á€o¨—ðNE-ÔµHŠþ±½Í èüÑí°M@KßÞŠ ÂÇ[1»LæBº7«}ofp÷ôCÇŽì?ºÿØáC@Aú²i@Î…„#²®:]}ùrÕ… 4ÛÒß?|ë.ê‰mÉo¼ï§ûà…g}}‰X<HcñÞ^xw*ÛöÂþm©T<ó ]Dìçß)%|gûêüw€‰g-\Б¼¼'ìÕ (¶šÐë8ï :WÑz¾¯öôåÆ_;q§»d.¼äâûu|Æs¿s¹Þ}wÝôÒo¿yò_OœÒ]§þ‰20®® ×U?½õ»ßÙàÚôô?Pú#Ä IDAT<ñÔ|’eYƒˆDL…•·ïÜAü(dª¯¡Æ ÊÀàâ›ð«H„ãH½mÈÑþ¢U§ä¼í(à›iàÝ[ñ“±¤ßÊ<ȼÁ¼)„NÄgØ_²øÇºY7ïf‚ä-l¤´Þ¨G;¼>R{%^?X7|çúÝ,“ýIé™^0¿æÀ€_÷÷'½ž ‰D_2Ÿñä–X¬o` OÄcÔ¾”+uìð‘çoKOÿGÁ;Ûó~³½ ÿíÛóß¡KÚý&fáZvŒ^Ê9vl•saéu³®cö".Ò¾¸Nha]åºwÎÄ|Bƒa÷»‹_¸\‡Ëj þö¿~ã_ßÔõÓ¿8¬ ï«u…«~ðôO|óæ-ßý‡' p èA»‹úWo©X‘º}ýòïVàz*|ª¯A10‰4øƒz ` Þ_DW”zÿl/ %21•²¸mý#½oÅßÞrŠç?ûdôÎí«#C_^»:2vw¸$Ýû&Úãô¯2i²ÿð«¬$ó¸:ÎPÅÌdªÂµë «EB‘ÚšTjêØÕ©‘çmbÇ }ýg½qE5û} ý™Ä¹léKôk1%ž)Hè[©ÒÛ^ÚŸb\©£‡^~~›;Õûl>áíïÀׯá+oû;Ûß)ìu.h^Þê߀ŽÀå\=×—Be©U‹åµo'®¼ÿ“÷ߟÖxΞ‹q „¾ÿõÁÐ!LºÂú4þËyK×]o"«ÃûªêêÊŸÞôÄwžÜ¸å™Í6Í#`-êÔ`˜º~Ô¡KE(Ã5ð*%àud` ¸ ¢V5P5øQ¤:@E³>¨†_išŠÔ”¼„¼¤F5pÐ&JæŒ2üéðõTÍ•Û÷ï_ÿjïä¥ûW.}5ôûBîŸÑÿÃèôWT«ø“`<‚/ß Õ[4jRµáÔº;—†ï|úUVúû3q·;Ùw,ñ@âTâ›ßyAýÉþgú“ý‡øQ㽉Ø6ˆ¼ºÿ'Û R½¿}÷Ý÷~SøÞö_çoßþί½ý×çm.(#óÛÅYyö%Ïú^–ô|;q)ÿ¼^ï»?q¹Þçx½¦tHï=ûNåÁƒï‡%Ax¯ï…,ÿåíŸÿÛ/Þ|[ÿ§“HÀ}á}ÁêpÕ–Í?ø.¸Oÿà™g BRÔýCTêQ둃h›ÁþR¼]]]Pw±20x-  6VVVW‡i¢ …!ìD¼M­q=bd h„õW†‡k†Foß¿ucxïÄØ­ñÑáO¿,)£ˆÀ‘ý¯â~•ãDŒ‚i$œ©½R[1hÅ>p}MjpjlôöØ›€‰~ƒàÆX¿_7“Û’&„#ž"7„ÅÚ@"‘HžéíÐúc§Û¶½þÚÑW^C¾xØ4·¥ ~»ýÝývûo SðÛÂí…ùÛsÁž»ñÄjp¥ð‚ÉÕhÚp½ã'С‘[½€È̾šÎ{íÛ‰‹üûw:òár ®³q°Å ½êƒ  ì…ëj‡èoÿó[?ÿ·Ÿ¿ño§þï)$`e¸6X®Ý´éé']›¶<ýƒ'çå_ÀìJ8åÄ hXŠúƒ·DÔ¼EC`ZÈ€pÛ×nª++£ú:Ð ^¨îÙ‰š·A_Ò6Áµ wÖß½4y}y=6roøæí+cÃûî]ýò/t;âé ©²UÜ®E”kSÕ©TM­A#@¥ÖÑÝvhê t‰d¯o@ï‡ ,“Ž%àɤÙ?Я <£ôÅ·ýlß¾}Ô 2 L&UðCó= ÞAøÞ{ÛßûÝô: ˆ×ð ëÒ‹×õ‰UÖµ|‚^dëùªY>× ?kÚÑ·þvâÿÞ±F>Pì\guéÝÞ„¾®·~F20KÀ7Nº\ÿtÂ¥[ W‡1 n|êéMÏ<ó·Îߦ¡ž«ÔG,ay4†«ŒáïÍ[·-‚øá“êJA à`Øâ_eXÓh0JV„2#a…ŠØ ÊƸ7o: ¬ÆWøPmë_e%„Ã4¶S/NÆÊ#blý#õÕëkëÇ/A ô¹u{è±?Þž¼R6üÅów‡JQƒdÿVĵa9|è0à‘£$’:64<45 ­Ã4Œ• ßš¸>25xËÂUûÍX²ïÔ… ™a¼œ¾LÌ{Žñ~HöçvèÉd\¾òʶmûö8’bXSá<¦ÆºL ÞöÂß¾[ŸÿáNÛ&Ø]° ¡²£HçÉ2ýùNýÿ \j_ -÷}–ØÅU]¹âÞ«xÉõýªjÅè'ùæ[¿zûä'ï`¤iB}ã÷¶lÞ¼iëB[u…Œ&¸®.¬)a BÓ9xë6Ƹׯg ʯTªáåj‡• xV0 na} æ«ëÑþ†ëi–(iý#•ëjƒ©Ë!èïÖàñu“Ã##£×&†‡Ÿß›`z:(á–5D}iÿa\NR$\žx Ù/õõ ½,'püÓ± 8RÜ×ßÏö¢>|hÛ^;rôè½/jdðÖ‹)™+<× ò‹¥qøÎIœ÷¦9OÁª¹—åÑ¿š,ÿKpý,ãs OŒ -ƒ€ÚL±Óxî=Ï{±$EªO¢Ã¯kjCu6MÛà` (àm‡—96¬Æ¤1Ra¬0ÀÅ2Ë·1è½²èÈßàG•åeÕÕÕ2xêð65ƈÀèõl1P¯¾]@'©®¡¶¶öúzˆtîܼ:6zeìÒ•+žçkÉQ0À~âWÂ_Q'zý‘ÔÈ¡¡ÁuWïƒÿ—4R ·ÂƒucáÁq·c нDœ½†½Éd?—æ3Þt‚acX~qJÿÙmÇ_=zäño{åÈÁƒ'ºJ®Ü›r›…+NÀ[/$/8 Ø‹yÈ ¸…¤ /ÚW3ÿϪU’‹€Ö0\PËŽÓ$^hÍOqÜ=ðýÊË!d”/èÈ¢mˆS`8¬XœÂèv=umÖ‘£z°Çtשׂ¿{oäÊÄèíëCc·ï¦Y.HŽA xéGD¯—áÅ|õÔÈÈÔØp-¡Õ†kk‡Rc×jIà^2!$ôÓVYL"‘ñÅ{ù^>mÏ$Å×¶½ô ?}^ßü;°wïOž'ª&êäÆ[šÏÖe°x!Ó h0öÔfð$Ó«L»fÕÅÕùéÆx<ý ýV|sú§`õê¿(Zøß« ºªJÞÑ‘¯¦¦¦t×—$àM‰;b§« ᳉ðº°Ö§£`uvMð²ëÒõ›woÏ~} ÄNœPò)èÒñàÁÁúÁªª€CÀuþÊÊ@üÊ€S©O"„”¡%žá ‚@@4嘄w ¢‰zhGÁ`=‰1Ø@S)µuµÏ ^¹:2ð³—ý² Ë4êVÓDÓÛ›†H:ž¶ç1ŸTàÚKÇÁ²¥ASÒ˜ôƒøæxl#>lŽÅá®mžkÛò‰²<\oÞ_JÀeâÿ°I^ïðØÔÞž®Ü8M@Eíï•QìÖiaWñƒ ¾šš$‘~óv ¾uù³K×oßYpÆÈlÖ;iÀ”µá©F F‚†² ¦"‘²@pðÚM <£?Ï ð/H0ê@Òáä0±þ‘uõŠfRR‘Úk£_ì½;94|wäùêš½GW±ì?Lt/) ‚Ä{%]ÑÂW?® Vcl0Ö¥Â57ê§n4X¸hr!òÍ€'Nõ>ƒÖô샳À¦ÊbaƒÎyÌø…÷·;vØO¶mÛöÚ«‡¾°÷gįˆ:§ñ’.ä›é˜™‰Ç’ñdÜ1ÁñÆøF[NÒ›Ó`ßzc 'oÅ3ñÆÞXcìͽoFÒiˆ°Ïöõ_ ö/(ËÁ=ŸNó—p™¸º.øýc_irltjoGtiÏœÑ4­v<œÑû# ÔðÌ=‚¡IX]pr¦>H³0 ñoqàתê£ìÓU¥ºUzg~h(R*¼™ú( „n‡nÞFêÙ?W–¬h$ˆ-\¨ ÛÕ5ƒÔƒeŽXÿHCC}¤a=–öR“×F†zuôÊÈØÈõ±/>_¤a¨‹‡‰_&^ŽpL‘G5"©ñ+#£×n…oTÓ2@£!e †ªG¯×WÛİ|Àþ„|,-º€ýÉ>^û“¦ yUE,òúóLLï‘#nŠ´íõW~¶÷ÀËG4ݯŠËKDô™qüöÅc½ÎݨlŒõ=µyË–M·lH_øU .c¼qSc:Þ˜§™X$˜8—ì=›ŒŸX6—ÄM§ÏS5ø ¸Ü´¬èý»w‰41< Ü» ÎÚþã‘yÏ$ÙbK#ÌRÀH=ÖUÕ×GTÍ*2UiIØßì å2 \YÙÍ[ƒX ¼vÄÏþ¹Œþ2d0” „0þEULÝ®EŸ0b%µÞ ©2bÙ†ë£ÃûÆnB 2rcì§e>ÂÍÄ&Âù ®Úvmbäêõ‰š¬†nÀåÛˆwªjêïØÐûM „¥$ÄÁ~=ÙoʪœLj¦ ûTIÔU™¸Ìþm/½ôâ>ܘXñëÛ^Ü»í•C^E\nDá¿ì1ÓLƒ~Æ!à›·ÄN¦›77ÆAT¸§c™ÆXcú©M›âOÅcŸLUëH2q~ v¾ÿý~xXÂ/Fä<>V^— ˜~7ùÔÔØ8ûã—ãSS’²4r›E@Ü_¨Gp›S¬3­èÀžÚj{^}¨:ûº5x âŒÁе›Ü:?[¿t0 aú«fn;•©ÛW;Nc©õë±¢9ÒPõ‹/þx{tìÊäЉç>m•9|ø0‘…ð2.DêO­kH]¹6z'<ˆ‹‚~¦ˆ XéuÝÂÕ¨ìõÅ2AôjcÐ?YT%Í«É7½ÛŽ9¢ê7<ß¶oÛëÛŽüô0°—SR¿ã̳1¤`ÚvêÓç@6Òg7mØòt¼Ñû½ô¦tcï“}›ã™M›bpY7$7u•2p>å4×â \..9þúë¯A æu³’¾z¹‰èeoú¬&N›œ¹wwD`t‘Dà‚¤+žº\ËÙ,$×»f™à¦± ‹VÙãÑXÔ‡ ´WSÿ¼½Ûwçu/ègXqéKݯŸÍP$úZU]]ÕUÉÓ/ÙVŽœ^+9Ë‘,ß’¬1-Þ €¸ÄÓ¬•­™˜Te’œØ ¢Ñè®Y'3ŠMQ¢D -‹݈Io3kþ¡û}öþª$% ÈxŠ$КÕ]¿úíýÛßÞû3Æß/xèrß-¼{{u|ܸs h ¾Ž~˜X(˜-šzÁD4Æ—À¦ª«`_]¨¨Å¢ã;eß3Ìö‡Þ¹ÑýhíîêæñoE] 8Ÿˆ¬ ‰~VÇÜb±{óýJ<®¶*t)Žø¶UqwâðrEp£!­¬4úýzØ[ésý€×\lJ‘àf³:7=IXEÕÕêéêÂ\õÂkÕ«»‹Õsš|‚Á3·ƒ^¯^»Þ[‰c@¼˜»W‘3zõïÖzµÚ¯–_ú~ý;/V/׿_'ÿü²ŸÏ‘ÖìÔk§NN¶~ÿï­¯”í’™‰Óç¦/qisüOŽä+#Ãað+ÙEÇ!*;[Ý­ÍîVg»ÓÙèlï|´uïïÀ¡xòó—lòø§î@ K舆ІˆÚÁ2͈€ïV½ñc«·÷ŒÌ8­â‡Ø~ýð!Z4ý|‡ÖaÙn«åÊv @ÀjyV0Tñ[ö*èùnÆÝÁ,ঠÜmÕ8žKK„®…ø­Še¬}Ôj— ,†±sÏEîlÏŠ‡ç4µ N»?vÇzËËÍZ½¾Ü¿¾p:òŸ"0A#XiÔëÕ3§Ný<0QÅjUš½(¼–¾V=7¥Èl °ïM¯Ö—k‹ƒjé¥Úë»éÝE”/Õÿ§Þ ‹ÿåjïß«}÷ÊÊ àÔ^¬×¿SkXãN¡5}!â¿[oµ£û‰×¯f—Ì»—.ÍMUüEÉu_Oïî>½]_Ó}οßÀos³½¾¹ÝÙØé¶ÉN÷ë2àC¨:ˆ¿ìY2 0Ò Q A °eéÀ@à»ï¿ pp€Ü0èø.ð¹¸Ñ“ýÐÈGøbú¯åb« °©ªE/Õ±œ«ÁàÀ<)ÿËžõhÆ£+À^Üf©DÍ Cp=š ŒÂÃ_äŽ7 {4é0MBÓ6:¼p À² À[µnúžÓ*û-£uo­{«óAéK&YIˆBÆ8ŽI¥Y"0@†TZïÜìtnÝÇì·Ù2oµZ>-âbë]dw¸¡i4ƒjµúYâj-1¶Š„×ÜãNl5BÐ("uFUu!-“‹¦æƒ¥ÄÕ] u]ød°Ø«!•îŽEvÿËbúÅ•Þ÷–wwû "_ØÝíÕàß›»ÉÝÝ%åûuÌþ•¬Bk¢U"GljY8Jvw“Éݱ/u˜_É.`y~â"a,?!;—9|Ôyj»>ÑDž—ý„¿½¹¶¾¾¹öQWÚܤ ŸþAv‡¿¯´S×>ësiüëÙè‡5¬kôÉ2HºyIL‚tZî©S0>w ù‡þŠöjL.û»¸´§ÅiÐŽë–oƒai•{k£ÝÞh·ps8!1<’@P²Ìñ¸-¥ˆuYGJ÷:€Ô–á9•ŽSÀ{£âùšY.ÄÍ@adHu²ZOÕ>KŒ$€SD%öÀ¸KmXmTÏŸ:~÷#Éð³S禃Úb²:}²Z={¡ÚlŸ«7êË‹¿Žì&¯b>w±÷‹W¯¾ #,Š<¾Sû~8®4ŽÛ­ ­Âø÷ZÁöw#ÉÄ¡¡Ç/d|%»dúÜéÙy"ñÅ챂}ìHÎrÔ§¶ ”X†‘üçüN»½¹±¹¾¶)íɇ;p9ö·ûEüýgÐëTדU-êòÅ¥€¤#«T²q9ÐqAÉ®°”L•é>; é8ºBÑ\m­FAàj¼ÄŒ’&!àÛ}ÍÎ/T|£@ KwìèÀuÓ$>92’`” p¼ a£¬ÊŒ& ™P1'¸vï­µvÑñL÷†{Ã+àJñ]˯ÄLbÑ} îLµ¡4ª|blð¨r`P[^i¬Ôë¤:?;MtM—8IÑ.ÎÎMýk ‡kð—kKµ•Ë˵å^½^‹·Ôù_’)¸ì@(ÿøzr÷x´ïõà¢ö®õj/,õ–_'ÃÆÙ‰FëlË0?h½ýÂÑF¯Ç~–ØM%F‡¿ùŪ€¯d—\r/ÍTˆ/rŽiŒ7²Ïgœ§µ  Ê%“’?nxc½+)¼÷Î_Òý}í~UCCCÿ™.x¯€”6YRª-Ú bGû7Æè¤'~è*îR Ÿ”,ðAÆÄ "n ef+Z^½ƒÆž«º-`ÖxÉÌs4Çö~o•5gHЭ´Û÷N`]¤Ä%’‰«ðàƒ`QHbMèY!ïw7nC4ÙÂF<>EU@~ÅŠìŽîÒ’¿fõTu¥^­²‰±ë@AÔÄdШÒ=¼€s¦IJƒß—N s§/ž {µ~¯ºÔ_î/]_Z^®¯4»õÈn:Õë&w{»×v¯²@.Wwûßí÷1ºQ¹†¤F#\ilå2ã¹VÉ´òG¼Þ ½8¿ÝTr4ñÙ£¥yû_É.¹pú‚KæÎ-ˆ¬o3ìÊøó™§µ«ú¾ªðc#¼â'ýîÆF{»»¥*ÒÖÆ=âƒÔÛ×î({‚ã®Æô%¬‹ŽZ3mÿzeÀT7­ÇC¸©ºBÃD x7YTAV™¦=Y´,ËF5¸ŠlP¿¶ª½X[cõmL˜˜¨¬•à‚‰6!…gÒi^XTñ^DÃÊ|mB$ B6­ÕrnŒ[pZ.&cZŽïÅ®ýÛ U­Y½xüìDU€hŽET4Aá lH€Î©ž;uúÐ-Ø…sŸž<7;ìV«õZ­¶Ø{sq¹v½Qo4VàˆØ[\~±—H0x5_ïí&¿ÓßÝÛýþ"`áÚnØXY§/Õê@Ä'ZÙñçsV…äŸwjßýîò‹ßéƒØM^ÛºÄ鬌?Ûø¯`÷ê‹À€åÙŠ?sΗy3["•¡JÅJ»êýí-ßù+ø£þööÖfw{k‡(çøŠ¢ùûÚýã0êÜAì¨5.B¤ÁY|Pi"ã5b=M•Eø,("‡{Úkȃš.‹¢Ô‚ëÛr±¸*š«î©à–çä‹–k:æª1fË}ï½»w)¬’¦¤9ÜÝ^àyAÀÀPà‹C66:·Þë®âúwÙ¹…¸¯Re B’©¦Öœ;Q­^8_e8;`?Ö€¿ _«¯2 ˆV5‘I1qœTpmqw¹‡+ÀË=PÑK¸ šƒ¥­þë•c©t—°¿óÒµ×GÇ L{)ì…ËK×®7Âðz%U«ËäKEÒû\Ê—àÚ_K$Ÿ=û§Ï>G…~ÓãûâAvÁcövÉÌéIwf~~ÊWe¡X1ýQ+[ðžÎ®ºÕét7}…K :üÍõõµÎÖýmxëuˆ}m_»\ºQWœ7´µ¼¨=¤Õª ò"üè^p§ÈÀ|¸Z¬D”™”\PÁÇ-­¥Ñ®81ã©”ä‹ï–?(Z7 ¢AÛnu7Û7is„Äãf­X ͧS©4øc’)žÖÇ´·ÖµožgZ¶m8.È`Û‚h4î5ùL ëWæÏÌT“$¨@€@ÐP êõ¦¦Ì?ÏNë,jpÊ&f§¦‚«cµ•åÞÕkK‹×—pÆ ü¹ÞlÒË/Õ^¨;@Ö{qyù»}ðj¯ƒVî5—p½TpÃv*» Å*ùüQkéjoù{c‡R/¼OÛM%vß|ÙŸþ¶'aT£” IDATöEY9ÐîÕ×Á’9wú\åÜœOTYâó–•)(OgW½ß]_[ow}ŸçÿY{ý£»kû[[à}ßßÖöµûÇ Õ 8 ËŽÚ“Z­hõÌŰc>©ˆ! =®LÃ*€š*Â’P"}²”·“†#hÄæ©êÃôý¢mY7]óqàµ6n¶×n‚ð%’̤S܇A65<šLK<›e$–íwß³-¿eV+F¡Ñü\΋pWjÖAçΜX¸xr¾Ú ’ DÐáƒFM'ŠªWÏŸ9±p¬ÀIÓÎÏž VÒËH}ýÆõËõ:pá2è0ÞT°öý¥jõ?¯ÿùK=’ýï|¯Ñïõ¯í²ý.__üù Ðj~©˜ óFþí÷[½^í»µÞØÈX2ñY"•šü³?}î_Y©/€—¶»xmó s“£hz‚x:»êvgýîÖÖ6¶ÁƒÞî¬}øQç~w£»|¸ÓQ÷µûÇ`‹NÑÀ”ÝÕ¨8 À'¨() !ø£øÓDŒìQ’ß.ädš¾A Ol´0G×¢5†€Ì–¦ R­[¦m­"­ìsÍÕµ€ +¡? >ˆ¤YK'ǬDDrcýfÛY5?0WßµZ·à®ÆaeV\’ÿ§ÿ”C½Q>±0{¢ºðp: '`Õe"ÐUIBp¼½êÔ4faTO\š¿879ì.Õzµ«K >—+8¹ð7ˆ£¹ õ^íûË=q—½Þ“(¹ì^ý®7þfq^WÑt­BÖ*~ðÞÍÛß}ñ{˜ÞýûD2µ»›ø÷±±±Äg»}/'¼Ìí.÷»ô°‹ùB ( ™<•]ˆ»í»Þ½·¾¹µåáØYS÷µ{ žö«‰9€Q=û‡%8Xˆ6–ûx•ÔTôŠXEBsk€FÊxbăg†X“d¨"ч·^š2Èâ^ÉîÛVÞ¥“®Êî{kw‰,‰|(IÆ‘:ÂŽÀgpÁaÈv{Ã{ãfÅ0§h:z8(!¾ó™+rôÇÌ©… ¯UwQSAC% 8#E’‚ˆœ¦atÁb\çg§&fçƒàZm©×»Ö»~õÚU€ârtð•È.+0\kŒ£ãÅ\|±_[ÜÝ ± P×à ³¾mæóFSVù£Gœ¼´üÂÒÕ]ö³±±Ý^úõd"Åô$¡¯¨ÔCpÙMõHsyéúõÚÊJÓ·ŠùñÄ+ rù©ìª›n·½vïÞz»Ý>ã'ÆFaÒå™C#þß¾§îk÷@üMOœ?qnâk ðsŒ ÙXÁ¼ŠE&«îНþ4BÓ.*… ¢ËšN ââ, @p»’¢‹à¯}`HôÅ€@O‹èáê2¨ç=\ö[®U.­®/í¼³AdЇ† ÜvÈ€à‚“àQ³Œ¤4é¶o¶o¾Óš{¯âÜÞ2+«´ °RÑü8 ÃJZC©V/;viê—Õ+89M’Ü·2þ„$IáÆÆÚæ¦aY«ðvÙ±°?Ø×°=)²ûY³^«g¦_[˜9[ FÅ{CÐ8 ˆ*À£0L*ê ú#DVy↠˜Ÿ:¤ƒëµàPP»ÚM$/×êf¼© ŸèÃUdRpôàzŽ%ÇÒÀÊI¦×ƒÀ8ÔÃ… €@ð•ù-‘ý^|©ÿú µïaȶػÞGe†yPlBã@»¤¾xíê›×®.Ö®/Õýb®`úÍ%OnWí¬}t·ÝítÖï}t¯«núI_ÕA‰.²œÈßÒö·{ÝÁwÎ>941©}éSö  Ö¨5îŽ2¸ÎSFÑK°FP‡ˆ_S DDŠ€FQWY§ò$2r"‚ŸA˺Z.1Gõ ƒ4LÎ_-š9/ï”Ë.ºà"ÄOkDy&1öÜ¡$/“ ä@$7~ÓÿòcpÁïnßl¿k¼{»¸Úš3[tX%¡-êN\ Ó ¥zþì¥ '.ÍH}Œ"¨<æ1«£HÕBŠáE±ïIæT1)V ¦'°Œo:GwWêA]ìl.ôQ‰'pQIüH@”ÿ’)ϱí²ëéáܤ_6 ¹LÑ› ¥°Rò{ßïý ñÒ‹µï¿øBOToáz.Ó0WY9È.©/]_|óÚââÒ"~ð¢ß¨ûÇá8é?©]$Àµv§ÓÞØh¯oi›Þ>‰“@˜‰)Éÿ¦_Ûßî#øûÝ}C›ÇùÔg&fbÞqébSp!X@¸CtFHÂý#ò) n€·˜âÔTJ‚™‹@ NˆÑž pÕ8 ›ƒ4 ÄTlŸg˜$“·–JrÉ<ªJú\·]pÂÓs ‚3™Be!”ä°ä¿V[þÎ÷¾ÿçß]ZAaF×,1ž’•þÀ¥}¹]²|ýê"×W¯-Öá¥?wæµãþ“ÚU·666:µ?Üív¶Ô­µuÿY⎅+•òµì~Ž¥;øÖ¬ç]œ™˜ðÞà)S_ €Ñ<"ÌÀ@ -äÞY­ ÿiTÜ¢ñ+Ž£ L)YJɲ «"YDŽ*Ù ‰!ÐG?Ê„.Ó)¢Ñ'%ª±VåX A jW eÅÑ@FWl‰OÑòA,ð¼¤!µ~|úcHĽõÎíw:ï™&¶¤›T€oˆV$îZµ:{ñì¥ùœ›À £ßEª:Ï&xn‰K3„¯Y]H§U1• &‚´$Ù@J»AØTšƒ˜êuAè¥RàÕº \ß~}»˜ËæŠN˜ .^òíBÖp§C™ "f¢k/Ô^ÀÊcˆ"ðC.§ü+>ÂvIX[¹¾¼‚\ZF¬…õ¥%ÿµÉã§OfWÅUMÁXßRÕ­îÆ= f ámPxÁÎW°û)Ý›˜y˜½Ù‰s{Ô8xÊìWd@-n^½³êÆË¸w<Š?P½ PtÊ>8 Ó Å G+2DeF©TÑERk¨c1u lµJ-èÙ€cøÏ6-0µo´H`–ã9A¢ъ㦠PüøìÇ ER,¬uï¶ß.Ú–¿¶ØÂà‘P§a2ÇöÃêüéù™“ÜÖaFJ˜bÄb‡$—’9D ü¯J‚O ÂÈsR0HiÄà!.\¯†MuÀ€+ ‚øzêïÓ)ñRü.ºµdòu†ñ+¹l6ïè¦.LžrKV©r:T B˜õAôzJ]Æ-„xG šo‚?«CT%`—„ ¿¹ìãxÿzm xðÚ`Ñ?>uüøù_úOfWÝÜÚ÷ÛéntÕ­Íû[Ý6D}æà-®P?Àî#¤twn6rÁÑõ…oÍÍÌíQãà)_ÝGø[¥Eô8MȺ£à‚›¬Ò+ˆò] @,1bUh‘Œey%h¨FëŽ.ˆ=ô‘ŒQlY˜ïßµ Û®T€wÞéÞ&$:—rè/%a2†(Ÿù˜“ÀW’¢ù»µ»Å·!Ø·¼‚õ6­‡ö ȤA5ÌRï¹Ï†Õ‹Ó'/˜OdE€Ÿ(+€:¢Y’Y9 âDä%<°$9™‚ððP0¨¤A‹,7>ö´Ç*˜í±=¦á: T%—d$ /“€ÇÆsv(¦åpöµI·ÕZ˜ eE ©¬')t½š…õ¸p.`—€à¨û¶¿¼ÔÀ!5‹ôXòOœ>þËóÇý'²«võÖ×Ö:ªz¸½©ç‡áí>B€ÞÌĹ¡‰h{¤XmÌ{s““3{Ô8xÎìC=ÐÓaøp–Bc7LSì¹sÛV)B_\¢UbfϲqÙŽ€Æ¨ÆÔ¨×‰h¯byfiÕXµ?ð=PÂÎ[·ZDàQŽÃ>ßX,I¦ôÏþ˜'R:lÿîí¼Õ2,׿t€:­2^ŠK/Ž&v¯V/ÍZ$ˆGSȪ0‚øÃTÒ  ð pAhk*UO¢”yN =-i5r³¡as B˜t2ÍBX…ð!°=Ž¥‡_)³ã™œí…¨áÌvƒÌ…øÊ½0ͲÁÂå¾,GS©´”°Ûóí¬·æ7Wõeˆáo=ôOž>~áô¤.‰_ß.¸ànwýÞÝ»mug{Ô nøÙg†Gž=ôÌs‰4ªàƒÎ÷s87}6"À=µA9lá9—ð9aTûrJj W‚[«ÔÓ©ø«tM£9™®û >ìRÉzˆý n0[Èçóà‡­RÙ×€àG5[à„Þ÷! èÝÂÑ0ÅüŠõß²?ètx B8}Bf) Ç~|êô';€G¸™‰õ® îFÉÇñ¿ ÁGHbÌ}–ü·?c«çOÜ,H]nä±½-úu|’&ˆ,®Ùð‚ˆ§(F9NHÖÅ€“Väž ÞXÆö¦˜±“ILcŒ%)6€\àšâ"k/œó3ŸËm?TyQCì U"Ëð¬O$þí7dQRpÁˆ^U¥/ìomæ3G³e ÁFs1VˆÕ›¡ÿÚ…Sǧô¡gŸÄ®¾ÝiwÛkÞíh[ÛÝÍÏñ|yàù>€ß» 0R€É9ïÁO¢' Ô¾D^·â¹øw"ÚH€˜×¢@ ˆˆ´Ì½rè­’Îç –hAâí2D‹«g*À€´Ùþ§‹ðžå²d*«–ê+4h[o!ää Aç¨pñZÜØÇ§_ûäcÀ2£û{#oÞ+šV‚FÍÇ  ï«àI9VÁA¦£oV/ÿks B"УºšÀÄìbN•hi¼,•ƒ #B pHf½Ù ñ “dÇÆ’é±ÄX2…rU85çWÌbÑ,{¡È©*OëÇDp}<«:¾ÒÁÑ7 èáæ3\8ê‹üþvûœ_2rÇžÏäŠ. ›õ%L.×WšþÄ™ÉSúØðy»êæÆZ·ÓnkÃêfÇOøx÷¥†UàF÷“¾~àùîá/b³¹é8ÜS”çÏàOb ŒžýXEüÈJH‹néA‹è݈@Õ! Œ$ˆSF+SÄFa+gåàIÐŒ(P‰êgbÒéåƒzÀlÅ+ £,š4í—ÍVqí.@® D¸¤”JFãñ?þäÜé¿üô Ä4±Þ*¾ÿ·s¶ZQŒø¶%®ÔA1Bzä_Ÿù3®ºˆ*øockAN˜×V‰* ¿…s’Ð/À&r˜T¥`651¥@k"õ<ˆ]Zrl Si‰±ˆJ¸¤¸ºÐzáüä”ï—02àòT»*p€?VôL_Hê@*}øÃ'ƒ/û q»\Ï7­|&[,˜e`m)ªž›ÔǾÉéþÙÅL`w‹z.I6ý´¥¸tMRtà¿-x;8ßþ"6;51u*fÀÚðæfç‚2à€û§ˆ`‰N2E Ü¢A  ‘=€‰*” @l Š&·TÈå …bÎÈ ˆøDY,i¶Q Ä—¥JàèpíX£A ¢J®Šø‹ø;Ë)šFÖ´ ZMàWþ£Øº»öìÆ$+ñ˜¸ƒÁO>ž<õ1|WáBòorkz§@'‚ ãï%¢{‹)–ƒ¨Js0iBj`¤?¢BE#pN²Š|ˆ u™“TÇBr4ŠÀBÀåF3ÔÂp@‘,æçÆF“#ɱD’Á e8?}fŠÞ ¨ü»Ã&fC™a¤Ðôy éáO_èKàÔð)r]¸,ï-™}‰]–óÁ¯;:^4Ý0lÔ±Íg4þÀC. »êöæIˆ%!&j!ê†7иµ½©t¾ÙìÜÔÌä€cWKðâ^tˆ ¸§Š§§ˆ ®cƾmË/C@¼x2:4¸Wœ¨Z‡‹WùØËsVU%ÆŠðj ¥ Î@qéu0ƪ Êj0!õ†Uô-ëf¹e8”+›·ÝwÚ7Þ¥ÒJj$-D¹ÀO&¦Î~¢b²@"©1’kÿGþ÷74Ÿ¶ï]­‚A ?p•©„>¿$ ÂO–‡_‰<8(IÅI¿*=¥Kàï55ÐUˆ eUNŒ€³ ‚´À ø´4«õfó+—ã ÊFéa´|Ä2š`˜´ï<æ&Ï#•Œ,á²ð›ÿýßs­ªJXòûRS-òM¾/ñ² c`ô²¯]ÀŠŸÏ=|4kù@€Mp¿ :`Ýsݱo²Ê™ó“Çý'²«îõPB"X’ð¡úp‰F8ß={Ü g0`äu5:Ø”¢1fÀ ³ƒg?¤ˆ¿Ì¯F›‚öàÃ-Óƒ»`èÂðuѸ§™À†•Ïg,Mah¾ZFýK(KÈí@¥¥ht;9o0žíV¥‚¥6Äv€¿\‚…õ¾×ÚºíщR2Í3Dàd.õÉùɳŸpª"ܸÅ[Ek×Þ\/rÁ¾Š‹q1‚ìKŽýë¡çp*DäEÈ atI€Û @œ*ž¦B`iVe™ H¤U¡ëć‚z3Ð}P} «!Å%®¯b\•€ËwJ…"Ȫ¦!†¾myá¿üæÿç]¾îû!„M ¡$A‚+Ú0·ï¥ÂƒgÛ×n2Áø…LvüÈѬë7‘ë•z£é{™ÃÇ\–Ìœ?~òüqß»i¸…´×7”íuuìÐÐÐsτԙ£Çr>€»‰àâì±#YW=7sæø‰sÇOû_ß.ÃàÛÙ]ûè£u¸ˆ3 W ÐfûA^×0.ÎÌ>кô‰{ªx6Ä_@a@‘íKxÇÃÌ\8Ì«(qȧ MêYyK6¢"h§lÛ%Ó°1Û‡â¸OÁÎ% TQpèjÔcð¶±ê¬:-ß·Ê8jÈzoì´ÜŠûÎ-”(UeæÐð°(±q((†S¤ô(1nM«`LjK!ÄÑõ¥åZoéjCåxB Tuh 1T^ãq08A Jð9Œ @Ü',Ó¡F!âOÓ÷Ö‚†Ç˜èWr|U!²OÂör™ñ¼mW@‚„¡oCÞ»võ×ÿò¦Ú+3ÁG«©µ>°H]¢…ã¢w¬¼¯]&òí|À]tBŒ*±ä5ú;jãÇ\íÂÙ gŽŸ:qÜÿúv Ùê®ßûpm Bbô *Ä€÷IGWºú¾çû>È>2¯mï!£'Ò¼ å¾6þRFUXѦ˜x`µ;v‡# 1»‡øÃ• ”­eÇÇö8”ÇŠL—GŒ‚aÚ¶eG¤KÙHšDQZ6]b¡ÃcöB¥âø¤l›¶ãWŒöíÊÂj¥RìR´8ø‘CcÉal0©(VˆÎˆm:«XCÿ/zàùñR.^Ôû F’ zÅv`ó;©Š.º¤6q‘™è¸^‡ ÕÉ1 àî^ûé¿@ÓƒŠh9eáW”K’("Ÿ`” É—Œl6Ÿ7ÃÉi?W€€-¬_¿~íÚ¯¯5j¡³|Œâé%•PPª˜×èc‚CÙÏ..†øVýÍr¤/耠W?2î3e2{îÌÄ©ãgŽû_ß.Ç&}²³µ¾¾Ö݃@EVxUSý”¯muÚwÕ}Ï÷¤QÞçòˈµ8‰¸oà¥^y¤0*?¸=À ê ˆOÆYf@Ø×®”Ë–¼å` Š•Ï²ìVÊ¥2.  ‚q „Vb‰‘×â®_Q¥M¼bÑ.-_uÀ,Çuˆ»¹ åÕ²9X  j±9‰0™Í¾Jµl” Y\¹8q1,n©XtÃZýúoÿéŸCß-•| ó4‡…6®bv ¾±Ÿ]ÜÔ72ù\¡X(Cˆü·²²zf‚BÓõg/œ<=qjøuí ’ä‹8‡¨C[t°æRe$‹iSí¾çûFQÞ#EYÚ4UÁ3sq‰Ìç½ôÔ£ë!Æ€Qò˜öÂ!Ñt¿® ¬å;>BÁJ)›-€I£ ß,»nÉBFÓt‰„·­ Ýv0àúïMp¹eÀOÙ´‹øfÛsMóÝâ-º9*™gÇžEÌK[Ó¿x!• Ø„ 7å?ÂÇX„àÆÀ0 ¦‹,gÁ .ëRTÑ×á(s*5jð›¢KUÓÖa5ª­Â Pi…Z=Ôôø‚"§©ô.-7¤.1žñË–™ËŽgÇK¯)¾S.dž/` X¿þOÿô/áéÓÅ,2 Õ4Ñ^A)j_À6€ýì <¢Ë£ãDz‚ÿ5šÀ÷FÞÀ©ÌþüÄkç&(~]»‚Ð÷%$;u«}oK%2ϧ¥ç$t7ºëê¾çûNÆQÞÃEYÚÄì£%22 ö`=dÏ? ÀUkõÎíhSLÜ•ð6bNÇüÐ>öÝ2xK_IÝi‘ÿ‘‶ 1а}ñNKûœl8éh¸ (“¨‘S©à6ì´Ù.žŒÐîܰ°£Íó, ü÷ªo¬µZ·7\»@+±Aà’šÆ²è˜áÍbF‰…mLžOÉÇ‚húÈ0`Ž‚Á;GRXDœ„ !œ(câE–°² `§.̢ ¢JKA¸¼\o¢çýÁ P'Р®5êñrU °Ô/§ÓGE11’|kÜwl ÇáäÝ¡[ÊgŠpcW-^ÿ͵ë!ÈÛ§üAA¨F¼BiXÄT·›ÍpD”ãÇž~<áe³ŽS½ *Ñi±„´þô©3çN÷¿¾]‘ç}œpÔ£ˆe#‰‘ÑC#Šâ3>Á’÷}ÏwÀ[ͱçs§+r³{?Ù{¢÷€/¿XDø)Rü>·©~@\¢óEñi½+K®ïÚ ã;\ê–ÖºžÌ”º@7€:ĉ¢F <:$µ‹…Îx»í;])Ø@|~k£E[ˆßʦAí‹ø‹ R ¹Ñ¦Ì›o[¸A ©ÄÔç‘h=dPÝh4¯@Ø5ÙJZ e>Ú¯¬`]´Bç!à:ÜP¡åÀ’Ä&pß—ëšÖlÔþÀk Ri1\ˆ†" i~t”ÉüÃïÓãxGš¸ mVrP‹¬,×êµë×kK!ˆ|?"Úͪ€?”äøa}?»o§Ò,l@à±q @PÁZj,.Ò…³÷§Ïœ8{ü¸ÿv!r’­ô5bZdÙÄ(«É~Òßê¶õ}Ïw/t{PÙ¢y—æg¦æµG#ÀGžø€/Åë8`?Dßû·=Ê€*²9]Tó+t&iÅQ] XÈ6Arãã9ÃÆ‘1®cŽ,–J–m¹ïï¯~¥¢µâ~“è…l¼Ûi9”¼Z˜X¸áQû½Í›&F“QÉ'ΞŒG÷ QxÒþçÄݲ7oÑ>¦–W©Ä{…T°­)!FµYC‚ y§a)>ÞÊ •À˜Ñk0!F]¸xin¦ òXàÙT#ÀÆúËõåè!háåzõAL…í›´J#{&$žE‚ì±;Üä– 9Ûm¬Ô–—WêË‹8°ÍW£` þ5e ÿ7^ÌXÙn& IDAT_ayÉ~vý_y.ç—ŠÙ °àQ ¢‰jš@| ÃÍ‚Š]q8?5<µ*¸ÐMWéFàŠÒƪéa"A÷ÉÔÄÉO¢w K4ž'¿UÆ‚ ÚI\ñüH,A<âí<0¶°3"ˆ!"@"+bˆxEä@#ð]À$Ö,j—æf/ÍWÏbϳÀË©T*êa€[} BL؈{!ä(®}]’Ìò¼˜Îˆ½c ãÊ•͘§O‡áâo¯__^ñOûyß—úÔI´¤ c*Ë<ñªîk7u´8qq±ÚļddT! œ¼.Ö¹ïOŸ ðIìòÏû 4 ºW׉ §Å¤À*o÷:ßÇðKÑø%ýr<àû4¬hØŽN;1¤@Áߘf<"РÑ™ñ£Ù‚Y†XÃ(Åb`#"5U¶šà¸èè…87j'"¾«Òµ4·bâ¤B·§þdzêôé_~ú)xaB¸”B¤t3΄ØvÿcE‹»‘¨Á‡‘Ý*p%ˆÄRçÁ:0\ W $. Ø „¸P¿´pqêBõÄôù)laNó*ÏqQ¿\’PÓBuåò £j´nPÖÞÀþ+žO0Y‚Ôñ·â$n8{ 3uð¨qý·oþ°áœÎû2Ç *rªI^Žòº4¤’°Ë¦Hè[fdoÑ̯ˆ)‚`¨ƒ_eÿ8à³þ“ØMŽ"UQ„OØ•ª <­¿×ïüÚÇ#Œƒ¿[è[œ#O+ àÕxŽMË‹¦Y0J&â/ÌÀ;T2b'£Òá•f î;¬GPuµ§®H•H·Z± .¾;õûx·#pÙÖcݼٺUjÑšòɧS“g>%DJ(Da8¢°Iòîª_Y-Zf §JWœ æ!¤aùÀcÑH 鎀ĭ&u¡:j#@BŸ#Iè—Ź‹ 3¸cúÉj€ÏWY…X^åÒÏ´’, ’:¾ pѲ •‡‹‘VR’ȹl‚-„ñ1¯„ºÞ ëK×®/]_\®#ñr60&J¼  ‰ ìp$×AvEì’eXE;?þ'ãÒl¬¬„Mc('Od—æ£\.ÑýéØžß8è|÷ÓƒIø{ãó¿qÐôüÏ0€vbÒÒR_ß eѨJ¬ˆ¶œB>sôèÑlÞ0±C$ÚËËS-3@@¾¯ÑIn@Q¸Ç€¹ãFÁNsÓ1\Ý+´.„uƒ˜©sŸbG0+`œ‚ßù¨St=+Ÿâ»®âªt0H•pìÚÕ+Íe€' |&T«Ó@lX“¯‘IƒFÔ´K³Ó³ ó ³Ug r8™Zfƒ¸i^r³Þö*¢ñ@MIÔ+Š,]†¸Qàx&‘à¸d ›Ó1uCEÈÒrJ!ÏsLÑ„\W³$¸/1ÇFÓÙ%”Q=ˆ¯3‡#s$A´¨+°ëŸBü=‰]N€pP•ˆÓm!áæ<è|÷ÇߣCó¿Úøü/2 ]°§¨PÔ€% ³Vâ ÇÈå3ÇŽËfrVñA‡ÂW S€:AðF@(ŽÄý WÕ0…Lûm[wÁ´Ì·½Šïµ k·Š/zKȧ“Ÿ>Ü—©I ¹Qñ*ÖM£ˆ|çV\{°‚}é~œÞaq›AÌÂp˜ 'Lb¯õ~½zi^E5ƒÀ’ (à…ÙÉɹ…Ks¦ƒFÀHšÌ|*`Ç’iPÀ: Bˆ¹i˜¨C[UÀe]€àš¦9,‚*­nÕt\90Ê\ÈÉ+´=¯¥ˆŸ$êÍ$)Tö\Ú—Û%ÔP|)wôˆSÌX °F[gÀ®ÿKŠ¿'± ôýû;T‹`pOÈ`9øÀó=€{8|€_#ü½ÿþª¯«>] öUÚ[¤éžS¶­¨ÿƒnXèXFn˜5ä¾"EV¥" P͆­$à‚[tϹÁ^qFgÃ4¢Ù~«®Ñ*úž{³íY«¢Däü§uÃ#–º•[¥›-Ïs<7«¦™˜('¢—Ri@“FÀ…x÷ÓÁîÕ¹¾\½87ƒ5bXÕ…k,J°03sq~êÂÂô…ùºåH‰ ¸d 1ò!D -ì«’8­Å2ÆB²&@< Á9\ ”fCÙÁa`X·ѲD«4à©‚&ÓÑRد° “g¹$/b¯ESycE,î¹]‚¿ý¸‡t‹þJc…Ö¤êɈÁžÐ®(É£ÚÎýû÷·ï?¨C@þ£K"(IP"|’A¬xo[źc»¤Bö8hLße’ת±Dg+'‚«Iv±Z9þ<¡)@€2“H&ƒ…¹ “Óø_ÎOAí/ê¨x&0I,G d! PÄZ¸—Wä1>Æp©w€Ý_’•2ŒÞô=øóHݳ ¹¬ÈOcW d ¸ ܸ÷ÑÝ{›ê}ø²³¾~w½£dw_,}ãQüõUð =™®‚DÄÆt8ü  õ³F ‹¤qÔ}Tƒj5f‘óEKÈ)‚ ®W§/¾1 ÓÂC¢â¹»…ÙäÕ   1vç€ÊæÈ§ç?Å9˜’ÄJ4 Ä .‘ÜúÀ'Ë)ã( TZãíUäv“‰Ý@ç°•YArwl·ZÅåGº¢¢]E’))¸0 xj"Љ iA7vm±Ò5*'´=¬ª´.š'ÄËœˆ1^¨ª AI åˆ)RE"²Å á´/ÃåÂ+W—ùŠ˜f“éä‘ñq.:À®úÁÙ”#I™4ð I|‹¸ãk)žÊ®,v77»m`{SÜí¬¯Ý½×Q²»?˜žäøßÑè‚€H|8MâöÄaÕnBcÈXãdG7T¼´†Ê$‹¸„œ'þ|]dÚûŒÆ¯h¹ grÐ< Æ~ ‡M³å·¬OÏ}Š¿WàÒ#ŒŒo!¥aI3)®k¼kàŠ ë»®C\Z‹å`Mê ½›Hüz, ¬ ¸!üvllì7i`@B9š¯‰œœJsó³.¸¬Ì¦‚ÆòJ] öY$ÂFSÁ™ÏZcq°.ðÑ)%:V‡ ^Î TuÅÐ- ”5/H%½¨ë‚hÁé›~"„ˉպ2Íl\ÆüZŸçÆÆž>ÃqØÕd|eœL[³l u~!ôØÑ¸dŠ91¬„CçÂXF,.ÓÑÍ%5Ô–*•À¹‚å` —vtçÿ€UéÄ{¯ËZ-ÛŽç¶|»â®š¶¡\ávgÕ·n|ú)Î$E–Is¨@džÇF9ðÄ[ f¡lµœ–£y„¨¢}Z˜0N”ºz¹Ȭç£&öúõ•z­:?3K´@B¦†»‰K$Ä`áÒ…©|m ðR —?­†Ë õàÚçóÖûq°©ÒRª«0ÁYbxø ÂG˲”çÀ$`@W—©¤*㇘¾ßÐäË}ù \ÔËð‡“„þèØÑçî»A=OHbD"nq˜Šµß7¦ØPE)*YR1ç'É[NZÖ‹FX‘™àŠŠ:¾,ƒÒrýв(o ampʱc©슜 r—!®~Ü$ža›V1—1 :ö§² ±1øàN»³…£R;ÛÀöz§V~¤d—b&óüÒ‘y<Ò–Ûpæ ˆ+ö¨B»ìc,ž€¨"àOÔ°v›&ÐÙÆé0‰ä*•Ê`x€«Á8­|ZÄò4ßA좃ùWù¶ayP’ !‹ÅÐO$EP'€œkÛ¦‰Ïv²³äc•pœ¬õkK À½ƒ"XÂÕ´zufÖéòšÈb‚ù -…r<Ë!­»Ó‚úlÁ… °†Ú_ Ñ*^ÑxÆ8[™V,Á+ ÑY‹²î(‚¤›9—óõ´¡h—üP•jðF Io\ úÒò å Aø•”?Ê`Î4…pö8^\Kî?ˆÕ§³«h|*Ó‰6:¨ƒw¶6ÖÛîÚ‡÷Ôƒì"þ²Gþä?ëÈ< À÷)önG=I¸­‚NU06„`í¶FGAà…-J´QM¤ŠmØK¥’ëSüá݆ÿSQ¢8=8¨†süx%õÀÞò*[,î€~‹BHŠ•¤jV"a(ÀI)–¼k[«¦`´°ÿÿHŠÄüõkÈ Q6[‘êÕYÐÀ‚Ž $pWA<® ¸`Üu§ôâFÖ£iœoµènöŠSXtà@@d\]‚Ë¡´Ý2¨J#,,ô‡‡¾êš  ÓsE#êb‚/Xl9°LÕÃ+ʕ朻‚R éršg˜ƒìj°~9_v­üxÖ"N!gYåb B õ©ìª*Ï ÿ C¯»þ‡!ܹTؽ·¶¦dð—?òmz|ë[Cߢÿð!~¤ßüöÞCúÝøøÜ×Cøwè[ß~®Æ4ïD.øÎ*/ëtUMƪÀH†THT§ 1YŒD©‰(–qP´m!uJ2®Åá QSq_CZÐ+´‹‚ïZtv§›7¶oWö"„å9`=–ÖcMPo,IÄ4V Æ-Ú¼ªFkÀQS~Œ¸Ô¯Ëà‚q~C*¨õ—àOõÌÉèk!Ä 2u"&‡ÓA”æÁIR²’:`ïEƒ¼ñˆþà§Ø‡Që/ïåû²±P£x°Ü [œdÑwJ’ż”Ã&a»a„…l.[ðUúiUƒª^ÛóŠ(]~ã ˆ°$ùWƒy{_j—$G€ÿ ™2øàlÉ!°Ÿa,‚òâ)ìÂù RŸ¨; |·6Ö>úèÞ}°¸»±Ñ9Ðî7¾aGüõ­øß×>¾…á÷ÿäùÏçiEôJ>ÖAkt˜"Âî!>‹6µVVQŒ`MˆFÉñ0á†}¨Cp17J Äí%ùÄÍÙ–ïÅ›,Tl7~o=·iZ&QÂàú7‡e0$K&'Èí¢a[yäLÅBÒ%BøîÖꯩf—à«Z 4ð4­¨B VVÂÚZœ…‹Ï’4üÜìèßà½AÐLªËÕ þÜa%B¯*!w _š$KW|×e‡.å‹¥°l˜à63™¼ÿ ýÚßýèohÁWä¾Ò”úrC ™6é»p—ˆÄÌåJåR¡èx¾[2 ¦Q°qÌÜÓØ…ó¥í tXê&ŽŒE¼ø£)é__ìo÷e À#x Áç¡#Op á_øðF%Ñ­;t Z4– s}XË >¾YAÌáýe¿kÀ“³Ñ'ã5¦¤S_5ZÅJ'*¤à=Näµ¶¦rì Q+øTRvWõÝÛ˜"ÖÓ)JH£¯ÔR(ßµQú‚VT„ž‡#R+{y@DˆY@ˆrýr]Z©ÎNœ'¼ªhŒ'¦‹©áဠËE‹ 0gÙ„‚õ2aPG"l4,< a?ÎJñ¾Ã8 .-4`~­Ï+2Ä¡iæó¹Ìx¶,Ålf¼à+«¼*IR?Úå,”šWЯ½¡MYâ° Þ…”Í|Ñt ÃÆþCÛ2JðùÛ¿QžÆ.ž/|k£½ Bx³ÓQ·;í{èÁÿ‚ü !ûÚ}€{ø;:8âÇG|~øÛ{O:ãïàçó_Â;ú•P*ZÊ Þ Dü)¸¬ª·îwl#gÜÑÁ+(gÐÈ.UϘßP貨‚ó14ÜX{0 Ò•‹;®‹F>;~ìØ±Bè”LÌK¾(J×ÇnpþïþgÌÉá‡aò2>Ø.¸Ý\±dòV WË% 9Ž{õ)쪸o·\îf÷¾ºÝ¾×ÁÁ©@,?ßßîðw438ŽFßüèá§à“öð÷xÆ]™æÈ ãX@u€@¬d¯8åR @hê:Ýxuµ%ƒ ¶AècOœ§(t¯sœo/ ÷õŠf£R‡°b—:¶­ø ªe§LþòoÛ†yßCÒбQ1—Àº¶vÊnåÿ1JeÏst¨Ï +´­˜Nx‹ìæ9.Ïå°Ã’—RA•çe™z`¬k1j:5œBÍK,ùV9Yåi’òÁ‘ø3š¬/_´#Âý4Ø®þ¡”¤ŸJ¢ðJŠåøýeÛ(dÆ=?n„^©˜Ï¥’¯Á¥žO%S/éÀ=Œ¸˜D*‘H`W`yR)¬‚°ŒS¶éÐ1 'fpOjW×~v1 ؾ×í¶××ÖÖqX~§Û½wo½ƒw¢v€Ý=¦à°GæÈaüt4þÞÑèKûpü9~ÒQŠÝoy,Wð‹‡sÜYõPÌcÆ%*ÏDaQ M´Š¦¹&f¼Šë9^I± K–e 5¡7¡}$¨@Ы{3RcšÛåÉ\Á÷M—dÛñp‡«ÍîsÆ<õ½ Ç1ÉhB¹.qÜØ#McÎlçKePÓ¾W†_ì Öi>º‹Ž)üu.pAÀqBpªú*÷juvj’ð˜2Tðl8”C•p®}ä@:ÈÂúÓ ðƒ?îb]‰xp‘¹Bìz¤û…bf—ÿh!•Äqz Bp0,ðS6“ÍùÀ€ùl¶XÄ–t™N§J&y!E‡ß‹<ÛC$RLšÝ×®†v‰W6ÁnÞ(ÓÖæ$\ {"»Oí êN@×Y¿·¾Ö‰¨Tp§«Š2Ý ÅG¶ ÁÇè1"ìðPü ¾D¾³éÏ3ôë‡ð÷˜põ΃#â¶U•î$² )VÊŠ[v5Ù-;¸:g˜8¯’n–N%¶Vað‡«y¨H1Eݰ6hn1‹“œm«Ž#ïÈ;¦)Y„NÅM‡7Qò*ËŒ%¸h-X☑‘4o,©”ŠÝiK5œr¹R7¬û IuãDôËÿçèócÇ96xm¾šÏQŒY!MŸÇÍ’Ò"3ÇÏEIü±®W áY¸(šˆg¸ÿYprÀ¢ùB.7Ëç_Î\š†¥r²59Hì\]xõ'’d+²o’‹áù’âf5Ù ›J'{ìØ¿ÿäF ´¹…ÓÕsS¸t‘lµJÀ%)œˆWÜÛ?*Tg_Ã$º*%󧥟*–ôÓb~ÿ0°hR:™ßø´À+yM1,—MýŽeqo¥ÞbyŽÿ‰òß.A´–ÏäJvoD„MàØù•W^æôòßÿ×ðn±½á¡—¥W†‡þ*uÝ¿Ã* V”5†¨S4/Ò"•þašýÙÏ~ªþü§ÿë+h÷o. õ‡^ÿŠvÂý5¯ítµÍ{ªÚÑÀ¸±®m¶·åŸý,>_~H|¼Ý‡ó€{Ð: `ü= ƒGQ÷…‡ûîOø9"qÆ„F¼(Šz¥RÁ’ܪˈŠós„;ßó4¸åX†eº¿‰¢ÒÝ—h‹¾F]p,B²íL'ÛÝTgXÇê[ÛS…ÜLN3çÌ\щ¬'‘K@ŽK&YR|ggÛ°ÌŠkt²ãìäŽÒî´;ÃCžx¨\Š·kÏf2ãL°dþµ…3ó¯]ˆwêäÙ‰¨O Δc%¼òÿ¨ñÓŸ *ÚíýXІ‡Tñ*+r_Áî«?â3)`= ½{÷64‚ÊíÎÖ}²½ÿò»;IDATMÆÈÏ/¿o¿'?Þîcx$¢Ä£4$´‡Ž> C*:>÷ð!~ÞÓÑøÖÑß¡øK1‚g„€.E `3þù¨Bâ ðÏtœC'aاÉ?ÍS£MÔé¦K{1 eÍ•ÊÍœ\ì·1~,ÛeÃp ª;€õR*ºWÑà\§Ø·í÷¦³3›ÍŒo¶§»y¸žVÉÙ=–ΦRãGƒRÀh:ÎΜ½H´4Hi­:{BÒùC p9Ž)ŠÀˆ³SðQbY! 3›+ÿV$ˆG2“ÌÄ.ãRó2Š¥ÐO¥Òét‚có¯ðœÍ°¾W*–KÅbÑðü0ôÁ?r9¿0é§~“z,²‚><ô¸`Af‡‡žÿvz»ŠÍyâô91Êæ\[±Ž,Åéê3Lê*µ‚=ŠIrÜ4Òv¹Âèƒn·ÛÖ4`½Ÿí•›3éQüÛåÙÇÛ},ÒD3¾ ‰=ˆ£´ËWàj´9ƒI“0ðȦbäN*‘`Ôø4)]Ï 0G¹çó˜6nè** tuU<`G7àDúC׌«â1ÜfÁv-,´ åüxÞÜ2JÖ øòû4îc8^!v CqÇB¿p3ßÇ`q3sl3ŸÍæM –ñî¤5<4»Ç€ãl&••KJ>PH’LÍ,œ¼€—(Õ 'Ï몜ddÈY‘/æ¹Bõ4–} c££_(”ŒÌó#Ïÿå±ÂðP‰F€ÃCÿmü¯ùE¶ÀýèÇ«R ¤%˾ÌsiæG?â-Qò]?—-i[´Urp¤. ±”ŸôáoádøëÝ—µ¡øøÛdìÃß>|]‚«ApÂàOp;U@ Ké‹o&Si°+ü<¶÷ã¿þá_Ûqô+Ø•R@m»ÛÒ“ÇÙÙÙ!8GþŽ‘{BíþHxõñv @ša¦ËPÄ!3™Á<ë+p5žÐ»Jó0ò!“ýÿÚ»²æ6®+­g"AÞ7¬ÄÒû €d¥jªÆ‘e[ªUq'CÙ'²=v$R¤¶ÇyJõó”c¹¬$|ÊS]vMc M/?`"›? ‘ÞçœÛ  )KS¡ÈÍOT÷íÛ}Oƒg¹}ï¹Ù\ÜWìaÆù=:„y¸d„VÐQX ~t[€`|q‹ãàÉR#9yìJ¾áú+šÛ™´íU}Å4þÛpì-{-øvœ ƒ”¾ ÇK×d¥È®}{ L›Áœnë#0äðÐo%¡­ËŒd{¶I’b•Vo^%é' ¥‹×V•ÎG¶Wæ-ûâE_½Ë1[JHj†aÐh¨ÐÜyôÖ?'Ý0t~ÎZ ç„ÙùiöûYy~AšSdaûáÆöÒdkÒ1ZðÅX–Nè¶s}ÇÄ{{è7¹ÁÞã'|¦f“ ÁÈ1?(w{cû%Ì(Á±t.›93‚ŠN°54”¥Š—Ù ä>’y—8J¿i>M.~^žë*=9Ñz*Nþeyºôåæ~^B¹Ì»¿Ü˜€;ìëëe®ÅåZ­Û!]MªkIÕ^ ¸'ÁÔÝ8×jø´ƒZLp.‹Åuwˆ:WˆÃø"G2')&qY\¬z‚¸8M ånb˜¦«oº–¬áLwom ]IG¿åö´Þxék|LÄÿ.ùöƒ/´Ï_w¾Ô7õææfÍ7ææçŸƒ¸ÒÕ€…Á_ÂÌ'QA‘h\ž°H—dE½½v÷âU\”Tb£°àÿ\‹¥à"É‘ F™‰þ乊ÿ'ÇšóÁþ²b »@p³™ŒƒãØi{_”¦-•ùÞZœe´€KFza€€À1u°á]µGRxnÀÏÔ†h.—KËê0eàþñ<Èï½â{²\0²Ä1<›Ï Ÿ9£p`³Ìaºq H2Å\qÜ´+`Û¹ax¥ßüá䪕Xë‰bOëa:AäKß~î Ê“–÷—;HÀ2ª¶$ÂwÝ}5©NŠ{ø·ÃΧÝÞè@\µðµ 1Ù|ž°p4KQ@Áv{’¦/.çA—صçe à_G?üa¹¥Ò›$‡ÏR9jtdxxxˆJ'‚o;õÁ”…r-BÂC°`ÀÃùž*>/Ç–¾Þ$ËÔìh½~ ÜéTq¹ÄåjŒ˜fƒû^}­6ÐqódN„aa?b}?ˆ,² 1Ù\ÌBŠvHL«›®ñmYýÊwů<û6ÐxË Ú’ï{`ÐÛD뉱…°³ëñòž VÚ~åˆíl êFφ›Mommu_ÅY̼³­ø@@œÚT)–Ô;7ï®áhYˆèa íB+,”Úã…hkó‘%#žMûé,øíÙ4÷0‰þ¾à{¤Åñ—ÞP.oÿByb‘S&'Zá8x˜àÖQ{on,^ DV˜áŠ˜ÑK?ƒ(’c³i9Ȧ‰Ü¿Ý;÷¹¸:ž$€šbixæÃYø³/¾ùæÈ-ÊùNŒå*‰¼ yO—Ë)£ҷ_ã\oeGëÅr…%LKA>oòûï‘ÛOÀý8õÅ ÃðÓNˆ=ÐèâJ²r!.dÛT’/<›£ÀO´;„1'Ðü‚×À°ñ„^†Ì„)qJÆñPœ””ø€2è0? Ü•Mó»å¦ë’orà ÁŒG ò8¦y°yj”¡É 1LLcšúf8ŠôCìÍÌ 5´ÕäUœž×]Ž1$O "Ó$•Døs¸{}ívTü6’¹H]Ç#Ó *¾ Æ’*‰ù(e@FðË -Q±ð¼žDÁKÛ’*-/-,Js ê’(]QáÛå—¦å¿È|œ$“ä žœœÙ˜a7¦Îc%ñwÓâÜ,—™‡ÓªÄð¹¡û%6GäžûðüÓå&a™ß‹cZXaÜÅD^)yÏ$×,íÁ^¹CûË%ÔžN¬g…¾Ëgú$&àgˆ°D¦€€G³0_(Iyb…‘‚ 9°¿òN%]8ô¸(˜˜…,Uˆ=Ø@À®h®‰¾kÒüÍuઽ…݈@É×­`piN hû¡A‘y6GçóÙ1WŠSZhþïxÞ¦f|ëüWCO4 O:,¶]ž‰p¥¦¢,É¥õÛ+ï㊅H¦#‰Dˆz‡~áqߎÈY¢ä•ޏL4y£ˆ4m;?•Ÿ|¡¢¨¾±½¸$1Œ(/+ sâ¬Åo/pü|A]úÝ2?/ËÓÓߘ·è9UÿmJœDYfpÙÌK—§4QædAÝxô˜ÎI¯¾vïñÈup´‘P:<¹1»–iÀuÒCóH’AVÊgjœ¹«DRú—Hw^©$æXˆ$Àð#Ç€(ãÔ×äzd¡É¹¦k 2øÏNAL¾ÛhYÍ®uc ×ö½ ðÇ;ãíñV ”ÉåiI`i‘§„<éƒ-à RP•½ú×75Íð ò ïz–»ùu§ýgÓMÈ}o—dÓ“å`!**2æG¹³ æ·0þ8p$™ç"a$âr‘˜‰B§0&¥H`Ì :ÉÃ`’9ŽÖ,ÞżưIÿ— -(À!Ùš•ÝÙEåʬzE¥à÷²¼0³¸°=/ù–©(¬!ßq½ûYaZá*Òô‡.ççø±êhœØÜ£ßÞåŸÎŸÿGöÑG¯Œ\V+ ÜûDî¹ç/ x€Ø5+|Àð“0 šÌ„—à*IES¨8x8Öƒ…Q]‚(q9ÐVGÆàcˆsÎÀ´ºlp0khrf7Í—üà¾zCûâ:s­a~iš¶\·ýoœ@)á~V(0´”—r|ÜWÐÅD.ÖªöÀ² ËÞÔ­ˆ‰ßL4 - œ`ZzÞ Y˜P½{ûìÇÉçV#q8’†"a8Ê¥#ÍfpÕ†° €1 F×ó¹: ð±\‘åÄ岫lóÛœòÆ‚<³<»4çÌ€±m™ò’"J"ïŠ<ÇÊ û÷襗^þøÕs¯Ý|þÅ”{ªu€èŸ˜~ƒh@pÒ7@˜Դ­!ãì‚BLˆÂ0²9Yhýõœ–Óò£Ílß(åòTs¬NÕëùº¦ÓÝaHsâ<ÍP´ã`ƈËEößçm^Pþ\„{ú4'SƸ&¯"XÍ‹¬8õú%ö^6›ÍQCüícø:_ùøÕ×Î_€¯Sÿèü‹)÷¹eFX¾÷Îaàú­Ûë«ÏáV'r_`¹Së@ÀÕ•ÃÀ ¸áÚs¸Õ‰ÜTîúíõS7oÞ¹½~8¸uû։ܹýr×:§nݺs‚nMžº}çnû'8Ü›Ú]±ú®±S{ïðBC¯ÔÌr(äŒ!ßî°Zí2°€È¿„€è^­óª†c4›cˆ96°©|ɰ>3‘ñjñ¢ð°ŸAòâ=õãô5'ÂAucÍ&¨ <¬]í#`—;> á_YÑ@Ö©§•›G­Ì üdRŽC¥(‡˜»ü™Ó€<”è 1´Ä®ž!³³“&T*¾Ts µ–MÓñÆ!–<9•I¥ͧÓ;¥íÈ#mµLz§fç'óÜŸÀ¡¡YêhšîÔAj•_ö0a`€1ÿúøV¹2¦Aôâ\¸pµY³ŽÚ¡=8¸º›I¹® ÿ3)8NÀ†”2è„+tJŽ»-t·[Öw.N.s“C´Ò©=7Š‹;òH3LüÑ­ŒbžÓ¯}°ÆÊÍ«.8µrY«”ßЀ1ûØúõù€oÅ«:XgòÊ…«šƒË´x™”áyÙcÁóHÉȤºÅlŠêV’FRF¥åÅÍ»›øÔ(6 z únDЕפ2üìÜÁëoü{þ¿þ¡Át´«^ùÉY+æRËDÛ™ý> 0°Ü¯°YÕwö¥w›•ÚQwf ¨TIYV?dOÅÙˆº‡¸ ²ñœÿl¯9Èô®ÃËz%¨Ç´uÉEƒ7ÂÖ=yx °¬¿¦¯œ ãj•æ»/EXn‚üÕ€ &üëïŒu`€ïTšÞX¹î¡ãíiÝ>6HÙaQÄ{Ü„©T÷·aötJáN£äÙí³ G AÛ'ܨ'/ ãxº¯¦Þày((–—Ô`ó¤A’‹àÈóƒY­™UϪÄ–6ÞïxfþЫ8$à¯- `Åóê5Ϭ×4ïØ€Lgî“cR—ìGN÷êvµJªúKɦïlnˆÝÕ¤3 o@F'‘ÑßàPžÂá@«ÕM¯V÷¼ Ðúu—€@3sßwÁ]Œ¯9>+µF¥îùÎ<6ÇU–úŠ8*2.ƒ ,®Û¹,¹”\×-ûäto3Þ«ÞÓ'¯·ëÖôŒï½ë‹`ŽW¯4jåªî;×öŒ0p7€€+nà5ªårÕƒè. Hvüc\lÿ˜+ì1bÚxÈ¢†¸+}©Ü­TZõZ¡g6MÓ7ÿ8Т{ÔŸãXÈCh¶¼ÕξT;µ7cº±òþ 7ŽúSS¬Ü°:û2í—ž€Ä­‹IEND®B`‚fheroes2-1.0.12+dfsg/docs/images/screenshots/screenshot_world_map.png000066400000000000000000007476241456075706000257510ustar00rootroot00000000000000‰PNG  IHDR@$KäçPLTEüüüðððèèèÜÜÜÔÔÔÈÈÈÀÀÀ´´´¬¬¬¤¤¤˜˜˜„„„|||ppphhh\\\TTTHHH@@@888,,,$$$üìÜðÜÈèиàĨظ˜Ð¨ˆÈ xÀ”l¸ˆ`¬|T¤pHœh<”`4ŒT,„L |Dt<h4`0 X(P$H<4,$ ÀÌü¬¸ð˜¨èˆ˜àxˆØhxÐXhÄLX¼@L´4@¬(4¤ 0˜($ˆ € xpd\T L D¬àœœÔŒÌ€€Àpt¸dh°\\¤PPœDH<<ˆ04|(,t$$l d\T L@80( üôÐøè¬ôàŒðÜlìÔPèÌ4äÈàÄØ¼ аÈ ¼˜´Œ¨|œp”d ˆX |LtDh<`0T(L äÌøØ¼ìȨäÀœØ´ŒÐ¨|Äœp¼d´„X¨xL pD”d8Œ\0€P(xH l@d8\,P D8,´ôü¨èð àè”ØàˆÌÔ€ÄÌx¸Äp°¼d¨°\œ¨X” PŒ”H€Œ@t€8ht0`l(T` LX@L8@,8 $,$üääôÐÐð¼¼è¬¬äœœÜŒŒØ||ÐllÌ\\ÄPPÀDD¼88¸,,´$$¨ œ„ xl`THüäœøØŒôÐ|ðÄpì¸`è¬Pä Dà8à„,Ìt ¸d¤X”H€<l0\(üX Ü4À¤üüüÌÀŒŒH¤Ð”¼„¬xœhŒ\xLh@X4H(xÐhÄH´8¬ T¼8¬@´„àü˜üP䤌Œ¼ppœTT|<<\Vî¬ IDATxœì½Yp$×y&Šg°74PkîKU•YUY È*4ü2á ‡¨Å1áq¸ T垀r·zCwk™o„¯ck4–ǯu=¢,Ň¹/º³\["ÙäÏØ”äK-Ï3 Ûã}¿ßÿgVo”ÌÔ“M P'÷<ß÷ÿrNÎX–eO|²,§Ý°Ö‡•J•¿·j•Êâ⥊ÕlÕiÍzÓiÚøͶUo`±­ê%,«Õn5êuú®Õé¶©¡V­PK¥Z³ÍfkÒßÇK¥j㈵*•K‹‹Uì²Ñlw»í¶Õ®U«õ¦e·š—~áe±j;|VõF{eçú ,W¯¾ôÙ;&¤Ùì~ÜþѶ·ëõZeñ}¾õ¶Uk4ªè­f£þaô¼zÝqÐéÑÅíF»Õ¤Î]k>´8MîõÇ ¶u§QÇÞj5ô[§na§º¨jµÑjZµÅKÕ ƒ;A›-Ò‚3ÆšS»Åíàƒ5Úífͪ7›õÖ³pî_³ÕnÖñw…Zp­ ºKtp,u‹®N¹é8u€ØòÖ›À%­_c{ët¥ÕzÛa?@¼5cÛM¬ `;ñz}Ø>n\½¯°àîÕÒKÏWG­&…³¨f'AôP›²ÝKDËÁ‰¶[õº]~O=Zc‘þªÒM§ûF7·7/ÛšàŠ•ñE§ƒkÅ©‚hjŽS³Z8iðÐϳðM¦ã/Öìw¶NoçúõíéòÙ«èè”è·´íÜ1§lýs-5 XÅÿèåJçCéy ôm§T©ÕÑk‹5«ºh5Èúñˆ5ëv†âãóF'kPÀ €v 2xÖêNlp©Æ!Ѝo0íšM@ûxÇ ‹Vmâ2¬ ·ƒåsøpn=±Iö$øAðÎ`ô;¸äN˪,Z8¤Ýé¶èÓ i0ßÔŸº4tW*ÓÛ Êlwœv‡ú_ó2Þ¾z=°µÔq·´íÖ1þ3«È ò-™,{Š˜”ƇÐó0 2¾Xó–Í(mU›Ç8%ÃǾ:ý"c˜âFSÁfý ãxX+´ÚŠÅЊ5(Ö§Î ·Ô;¤µE —b±~ ëjWéÑ"ÁœOjâDê.Na –À ·ã±PÓq­ÚÆÈjòß8’Cà¨j¥ÑبnøA}¦ÑiZ[› ˆJ¥Ùi6{a°¥8ð8Ÿ¯.Æñóv}k+Ù]ºd[ÀݺÜ.¢’:D@…ùˆpW'z©UŸ'+&©E:ëV ·ÿs;íë³íY|h9|‹L¾ü¬èaÑåUéæ·»N½R±F j vZÂ=}Y´›3δӾ»s=ö†áx<>ó[è×wîvÉe±>nÿhÛI¹N­"úý¬ ¯´ÖpÐmÐA OÑ?„žG}ö±Áж-(•Â,6l’ÒÖÔ²:dÂkÉÀxZw0‰r4pA®Ã€®@âÇ8¼-]:ÎÕvNT¾†c/’×dñAUƒ j°!Á›Çò›hkz´¹Í(¬X,Òúâ8{¾Þ¸”?ÿ|ö7Î*É8¸‚ƒ }œÖg–ZÕÆV²åœÅJg)í%[É8IiĤ â¸V£æ+¸JÀߨ¤iˆ^ã—Iw¼vìke^NƒŸ?`<“/½„ç°Ó½þ™Ï¼ÔÆgö¥•ì2Ù ã‘ñ$ë7<ÂZ¥ZoÖÉuiÐþ ¶¯6[™¶;Öhtt@oãÊFdz¿ovç.žì »~ÜþQµŸÈhôe(^î)@þ£Û&ØØE ñ´ëüc{ir›  kbÀß!dÃn·)6‰5¦Dï~tY§^å¸YÝÊðO;ï»l±n“‚ÉÜj<£üª·¬)T¹ûr°mkÃUPMt•Û>ArÃqKNS >Äîå¶ým%qÔ´ÛN5ˆc§é{ã(mÏô¬öx àð¤1ÖÛÚ;$î„›ã*+N;[[¾GX´Ø=ºtɲÛ_£ÅÉÎÌW=ÅÜ|r) ަ°®B–à!´v:×?ñ™ÙÏ\oï4AÄXœêÔÐSˈmÊ!tǪDïpôÈ=#¿ÅÉ¢%O\ê :¿j¾`½½ã¥ÆðŸoÔÏpl°øü¸ý£m?é25@qò,/€ÂpX:À¿ÝÒ2·~ñÃíyUÒ#D„欗ã¨v§Õ„·ü>-ðÜ›pÐë63C< \l£Á@N9Ë€ ÑTC»ÓuNmØl9¤èÑÃ5X­FP·¦WÅ2€ì!Eá>kú5f«1ÝŠáØ¶-ô}¦°Jš¦>¶`”ã|[ø;H"€Ø óÛq܈À i2Óv\BXiõi4Nâ$»Ýd+ÞLg% ‚È[\ÈT†ërÚ_ýƒ¯~õk³_sX¹ßrj¡Ó¥`EýD¶ã)ܸû¶?÷k¿ö‰Ï|æ37&âë7nTªÇAà̳Óqvíxv•nÞñyÊ$âÇ-…CBžÖqTð†·ñ›W6<ê1IP~‹·´íÓ.Seü“n®×«Õ§?dù:(òÕ+Ç¡µÊ‡Ñó2p±"AŸ%Uá¹ü!/Iì¿ÿÃ)!éÉ!r¨Õ¤jª™ºÑ!6]–ÙÐn4ÚÍútC›Ài3J`Ãæ4«´“LΞ\Å$áÔkÓsæ»VÃî øU@2K@€†ÀP¸u?n8ÊN·Ó°ƒ¨Y«¤cÒ5'N‚4M¬úL«Õ¯÷:¶ÆÑR³ív“$u——Â`+Nž·ZÞ 1 ‡pCé´¾ò»¿ÿ•¯~ MS[¼tü éƒT?•w©Æ4ŒçðkÿäW~•žÄõ‡‰ØiM u¶j•n¡uŠ ÊàìÙPms~ç)Ë"k$H%¦é&SÇ[ ¹þÖolpÌ: 5|ÜþÑ·×8—Ô¬g~3DzŸæPF­EÑCØæÚ Q©~8=¯2 J\¢ Ž@¯AÞJ‡£TiÞ÷~ðý¿û»÷gß¿´4¥3ïM’ê˜âßâ,`£Ý†ÂbO6´hÎ4R¾ŽãøðXF´êÇÝœBL¤j¦©Ò¡F) "€¯}õ+¿ÿ»_0)$²h9ín××Jb›P^¨è.·’¨[ms4 Ùµ¿k-[ÖLci©µ‚“ˆ»5§W_n×ÛAl9Ë+QŽ“Š]ÇAMìé׈-«õ•ßùÝpÐñ´xéüt!´&ßÒê4’A, vè9ÜÀsø'¿úkx ø÷7LŸC¥šEp)Ñbïé³i5)“BnÀ±ð˜à«¤¸(cbqöƒÓ•DÔ½õßø+ÃáÐË: U³¦±¢Û?²öcaOÙò ÿ”ý®6Ÿé¡$ÓjÃÔÕ`(ÉòdàÏ÷£ç‘‰™ràÞf7>óc(i€±èüõßc ´[LŒxÊ”aeRý°Ñœ€ïÌð·˜N6¤;ƒഘȰ r–|¬.ž˜4º´F–dË®¶:Z­–á±à«_ùW¿û;_iñ@DÕf»Ûét’ N|Üæ$ŽüïÛN§ñ¼ÀÛh7«qÜaTg¬v³µÜhÆQ·Yï­z—œÿe·½á€­kõiô£Y¯P ³Í^ÀïüîïÿÁ× Ä¸hjÿkåè©,f T÷ß… Hÿ“۹Π?…i„SÍtC¶mM*ªNI B›@§ÛÅ3¨§aO;%¨4šYîçD*á9Tí»;·n¡þæoþó+ëaŒ¸ÛdëôqûGÛ>•ºSÝnP·nö£˜ v8ÙOp€ÌÊ“ÁÏ‘¼DÏ#jyÐóê\uBy8à–bï$QÈ=o$Aÿå¿g@΀اC‚ ­é¬.UØ‹à4)ÿ,8Ùp^¬CÛ‚è ”÷£J"ò"˜œTY2ƒÿ1Î(øØr*\ Ôlµ¾ö¿@Ð!pwž¯9mrõëYÌß¶¯Ûàè×+vè½ØXïØÕf¼Ô´•™jÓîtšÍ¸:lÉi´3ç¿ÕîÖí‡Eã̹°³r@Êd|ËW›MVF—² ¥4j¬èÔø«,B:u ¬ÅxÁSÀ<ñÐé©‘ßSk43J¹Tã ®¯ÂN}»N±ßf‹8€K±¦Ú¨©I`МbÉzF&qFNkgç¡uÏ»q'Ð_Ô²U>nÿèÚxðè.Ìálÿ«WÕ,Æ5Ðß%Ó€ýµÚÇÝ'«‚Éz÷£_¬çÕ‹ZÉÞ6¬LXÄ7Ó…ÃþÓx àÿþë ÈS ¸~› Jã"(ë‡5+ŒÿãLÀɆMçøkÀ~ºà[Rcü“¬©sš’íYFu…A[-"<ÈMÍæW N‹ÏrÙ>võ* ©ÔZ] Œ“Æ‹N»Ö±+ÕF·«veôL:>©pÐR«Ñaç©Õi;T2Hµ‘|IôÙuâíЉ‘»½˜= *Oädg'OÎæÆôn×) ƒˆ)uQ\ý‰sÏ)Qãúˆ:I²ÚK>}°T›e@%»Vô¦ ùdŽ[ƒù¶J5”Ä]Õêñ^X„Q¾”HŒÕâàH/·Ýé--õÐ%ºã´ ÍÕÖœý¥:ë¬Ü‚o}£±´ÔiP¤îç€BÚ­å%Ç™>–Û?²vÊèg‚|êóÁ vì“Ð@³… ß…Ñ£R?îüœÉV ¿7­=±l?èyõgö¼L-PÏc›U«7ô¼&t*ç%˜{=aèªB¦Èÿå¯ÿæûÖq.Ž•q§aQÚ K€u2K­S徦ްÞdÅß:Nnf2ƒ p¡Ùø©ç?¥Z§Ù ú Ü3Cå8dÉåö ãŒàè ïŒ+Šs¶hÌU7ÖîX«CFþ¸ò§EÏú÷A(%\t n®¾³Ž‹é.1à×[ë^€ãvkÏÛT”¶@ÎÌò(ÜŠÆN:Çpüö’{yÅq^.÷œì±Øä&áû%*7:…Sþ¨š³Áz¤ÅЉ9Ý¥åe¶Í¼ôV–!×±E‹ïà=R,YcÕï,-¯œÚ`yy©‹Ý8T Ìxæ¥Õ¦ÍVzÇ›f TÀƒeyyÙuÝ•åwùÁ7½e·O¿iYqÑÒ]é¯,/ƒþÇím;¾]Yž>,ú´âö]4œz¬<ñ•"†n‹ýÛ¬çñXºæ£=¯ýôž×î>Òó²þ=íyutòV«›š¾F"ÑØW¯Û&¶—:'˜ä?{õÅjcš‚{qÉé=´B§»Òc$w:t.dØNˆ‰v϶­¥¥öñFò»t°Ù©¥»¼ÌC—Ni+l­x/.wÉ–b ‚ö:.­Ûqë#»Ö¶+• ŸêƒâŠÝžY·’4i;©_µÛ–c~¼|¼|¼ü#k±ÎÑhò*ÕúlLÓ{ñá¿kVÛzø'õØj5gh€FžŒ£F»^m˜áȽhcc#Š’ˆ?NÂ(ŒFqDÔ&ãqÅã8ËH‚Qx¥²j¨Š(˲ %¡X* ùÒBÑËñ.ä$2°DŠquv¶k ù‚W” ‘"›º–ÏGŠjš²ªê²¡éz¹TÊçóó‚ä¥rY”E’Dya¾(ˆš©éš\$¡ìåEϋʑS ‚p%>Ÿ_€zž…qâB\„ïG>}…ëÁ÷‰âSD¡—„qø¬í£ ð’(Ž<¬èû±¢‰~øÞÈó±ùã··ýÈÂö6™eS¶¶=Ù;::ÚÛ;<<<8:Ú=œî`9<Ü=ÂW‡G´Q~ì¢ G“g·îbÏ“É.VÄ:{¼úÁþÞ­ÜŽÃbkjÙÛߥ­iû»ØlŸÛ'Gø½µöé@8'ì«ÒyâCì ‡Ýß= µ'G‡»‡Ø×þþ·<éøÇ¡Ë'ñ=]уþ÷ýaˆ¢ «š¢ây†,öUWø©ÒWJŠT’U]u±®¦ ’¤÷û²Tê=£}uµ$Éý¾.I‚ªiŠîb¹$aõ¾òSÁUûèB’,k’(ˆºÛÇ2Ђ Èšùþ{æà­û}½<¸¼ö‡ôÇè ºñôåYí´˜õE‹ˆKµÆ‹£Še†a‚åŸþÓ$¹€%|h¹pá“Yè¾d‹ªÎˆâ0ƾÿ¤í«‘åWl ÿlÊÕv&ÂîÞ怠ƒß€!ps”µ0¤&ûG{G€twwr>«P½°·K@Î÷™$vñ=ÖØ£­v± ìá`ìNˆ†v'€1>ïN±åähÐ=:àµ÷w‰+p¤]:Àdog{p°žÐ^'¼F>ŽJõ¤ãw(Mþ£äô–ö½·¾÷࿳cô5HD71îÜ6€Ñï*}ÑÕôr¾¤ë® »ýU¹Tê+ú@—ú½ÞÓÛÝÁÀíKø¨ôK%yµïÊ‚«ë¥|Y×\±¯|WuA²«Ê®è*]ºPT6…ß7AJÒª.¿yLÆb6+U8óë‹/Ž‚ZÅùå#€ZµæŒš¶=N¢V§Þžq»NÏiÕjHßxÑŒÃd}{8CÏ‹?ØŒƒ4N£$&.ÆéæV²µ½¹l'ñö8 Âp³¬˜ XYVÔBIÊB>W,æòÅ|±T K%öß™í4V£×°šM#¿‹$QWLMÖu KQ¤)†f¨š,Je¥PÈEzäºfèªTÊÍÏ‹žÖÑ´R^ÕtA–"?W(ø©? a”#ús“¹NÃØ‹½ìöë^‡@2¤žÈØyñ0ðjòž±ýh”q$VÁ$ѷƒÁÆOÜÞòª^½bSm[½ÑêÂ&ÈìîD>ð|p°78ÀºHØ&8‚`eÀ#úïéí@òö2µôû„cÀÛìoìÓÖ¼‡Ý}j'KN:X'´1±Lh¯l×'àÆ1vrˆÛ´(¨ƒ½=° (¤4!‚sµ<ñø4Ø¿J#Äçm«fìp¾qûgàrÛ•—TÝuu¡”jdâ!òäï«€ÿ½ÞÓÚu½/Šôsu0ø.̼Nô"ÚB-‘|”!ð¥X’%IÉL)æÎçÌ3æß¡/‰ƒ¾n˜è$̶½Xw¬ XÏ´büÓéò0ü‰žEO¦€ Tªíåv³–ÄÍ¥F½5³Ü±:KŽU©iÐ_ÍŒÑ#0@4ŠÖG$-Œ~’¤[i8Ž·Æq¼Éf²¹µ·77Ót+o•EU)²X‡zŠeˆøræ»P*å0VovÖ¶[ÐÆl¯×3ró¹²°¼²Bán¯¿" …’Ufšª&Á•|oaA‰ü¼DªÏ0UpÉÅBYÔ5Í0A ªXòM74%,/\,°™E>É} 1Ó˜²›!Œ|€km®ÖÃ4 Ǿôýä6†¡âðÁö¦ù³Û`ä㔹|-RâŽÀƒª€¥‡Åhûéñ­ÐŠGµØ®eZRdY8ͽ]ò`T*2åГ ¨à ÎÆþˆLû~†ögµÃT“o0uè¤ü ¡dšÉ¬ävÚ¤è'ÐXuŸý#F8TÂî>q P¿L“ȘÀAØ%" õ°w˜ñÏœ6ÀÐ^&{ ³'¿ÓjXÙ|Tƒšq °¼|ƒ‚óE׬iQÿÕÛðTUªemURаðPøR_…jïݽ÷ùþSÚ]–`WÒdˆ}øPEEZ%»âŠª*íº"»®b¸h‚+ Ì«wî™sfÑ”e «²¶Æða8 §Y±Zuo”†iÕùå#o½Rk¯,5ýpÝ™Yw,»×±«Îx+Ùô_4GÃ0 ^ßwcÄ‚7úc2õñæöx3Ù¦Ââ¿·šÝ)ÝoG%ÙÔ‚PVM)*”KEn@)W„qΗ½¢_’ àêÕ«ÆU³×k €¨ö—inØF§ÛYHÐ ‚¬Ê d>ü³¨<,ä45,€x@¾¨ˆE¸¢Ðë M“bü6tÙ y8çT9ÔˆFðàñ;òâ$M}Rícà= /ÞKoEãÀ#äÒ_)P»kžmoÎ7fû`¦'܇? )žà' È%ŠÚþäøm_KR8ÁœÁmuØŽ’ƒ½;!ðî‘ó¼{ÈN4à sNtCÐTä"À=«A í°òä‚“>8<Ê{Dæ™ÿ¢õÐLà>Èw'¼Â5O&Ä)´7hý ¯´K® {)»|Ú&88$g‚ÈŒ™…YèIǧñ1–ý*qÀ<àÖÍÏ]»LË€5ø·Ü[î»WoC¹úxY $A ü/¹2ºÉÝ/Ü@O·“r?nÀg„çTãÃÀ]hPÁÀÿÒ€Ö…Nìà+¸0xBJ’Õéªæ/) Å‹ÿì¬)¾ƒ´Ëkæ>]hûShÕª'þÓıN¹Iòþ³³a8;û8>‹~qøC„þúÒÆJ#nŽiPPk&ò7ìõ^Ã{æÆÆ}ÚKÒ$ 7àßFŒÒd Hèc s¼ ¹H“íÍ­ñVà+†„©nx……B±(ˆ‚XKåb©T*x¥¢‡+!׿eÝ5[êggÛ†$›m§Ýê,õV–ûS•Ê‚( Š¢šŠ¢Ëª  ň¢4¢óe©,—Ë"´œiþuzöX²T@¡P„—¯N=Î;H8„¦dÌážû››q²¾ß=Æñ(ÜŒ|¬¤t})možWM31ÍÓÛãapGEÄSO†ñéí§Ç¯կעºgY5›²¨6¥IwÉŽ“ïL"û¹GV˜‚g°¾dä¡°:‚Õ>XÂ>y Ïh?â(á„ö9!ÉŸ ˆðÌÂïN Ø÷å û @2~âdÖúØ|Ÿv°KÄ¡`ã4aÝ™'&p H*°å‡a"nÙ§0Àß¡âÙF­êÖ,W2®f:XŸ5Œa|áÞòeû¯Þ¾ û®ºÚw¥UÐVŒR¾ õþwwzûévSS4“Ú c I°,´aH…|‰< A^•¾«ÁpádŠîÀPV)ø'9pÐWÌ;·¿$¹øÜ™‹³æ@Äþdš§@ÿgO‡øÓÛÍf«^í´ü4M«MÇþå#ž¬ÕDc½9ÃÓ¹Nc-YÚ§Á‹Ã‡è÷ñfmŒôíÐO£ñ&|þx3I¶¢íds{+ÝÚÞ䩃6ÇQZ L]6a– å0_,KrJ¬¸/–ÂRYˆ㋎atïxþWëMpª¹²2PIЛêÚš¦Ê%Q‘TrðŸë^!R¿84À%byATÀ÷p*<@€ÞÀjðéæret]V¢°+Ž`š£aì…1À Ú‚CN¦Ær'„’£q2^AÏØoBÛÏhš£öÝ<›×£¡ùÒ-óÔö#\L†l­“…'gŠŸ€ÈóOm?=~ÝŽ«VÄS¦ÍݲiæÀa‰¬0°L`"ØØÂ+<³p?V"³:a€ýgµÐÔÀ<ñ¤Ò9ÈHºœjÜt÷ IDATŒ6¤?ÀLŽ;ØØ<¤ø?ÛøýLž ©pDq‚ƒ=þrŸ÷±O6z‚Ä] à¹ ì,7ó¤ãwhš®yK¶jŠVt[×¢q4ŒÆÆN«kìì¬^¾ì^…4D ÏÖ RêÊÀ•eBÿÝÏß½}ÕtË¥Óí Ó2eA–ú?ôaÕ%gÓ]„þàG} ²;P¨}¬9©‰’ኂ$V¿û¶!¹}Š˜·îÞPæžSæÎÎ>gdI¹|Y1Q?+ø•Ÿ¥'@½Ýª¶Û>d_­ýâè ˜å…(€H€–Ÿ…é±ðÿPa`#‰lš$È«Ïðt`ýv;Ž{Im±1ÜðGà qƒá0 À9åý‚mx›ÛÑv ëÛClm& ¨Ù “4V5¢¶î¼ ß ºX$ŒVÓø"œá&¥®Ö{¼H!wž—ËîXPD€[•%S—E¯4—¶]„ rß³€ÿÁr«7þñxŽáÅ níܾ|ùêmcÇAàYú²(«®Hè¿{÷öÍk‚鲨ÐŽÇÞ0ms +÷ßReq@íhq¢¬¾uæŸÜUC¡Y×EãÍûè^«ï¾ÙÇÊ¢$Â[½{ënéÌü‚ajê¬éªŠ$‘kñh"P ÈŸÕ9FÒŽm§ùâ(xÔø¨ ›¬Ûvœ¦Áú O6è¤ÑòR»ú|ÝôFëÃ(EiìmÀƒ†ÆÝŠâ­`3‰S8ý[ÛÛIno¦ À?oaâýDˆƒ W,xó’ –KåB©øÆ=<3óîíÙ«W=cvÖtŒYÇ(êÚê #€Áò² \«’ª²fâƒ,ŽDpI Éa~TÊ犰¬Qî8¡u·^Ó@na>ï•ÑfÈŠä‹”«‹½u?!ÙqÒŸrúžsï…# „|ã0 7VŠßã ßÈ7ÎÎ E8/I¥œižl`'œâ;.Âuð# Á?Ùý‚Ä«¦WìªU‹,«Q­ÇÈOàSS\žt5ÇÌ)¶Fp„('–zobjY¸0 <³}2!o`Ÿ2ñÙwü3#%õˆwȆïM2ÁH,@"‚£ûœ•È’{Yêÿh©½ Ó "GìGÙß§=ð¯'?«m­Ø®k¹ÆíÕ èwvüÿúù×$»~~Á—ÐUþÓ \” ÑuÃu{wÿ_ùÎßýð¥ûs&ÌüæévÓ\²l ½Ý¿¯¯rû›²¬âãýþÛš>€êw]Cp]ÑŠåÁ"ã/ $]Õ%M„o »šy÷Ú-åâìܼ隷L*0Ë«ŠžÀ[5ïþz|ûÀΗ¤8Z§<QÀ½{Çðÿ/³ü3û#䟟|ä§Ê?g²ŸO%€Ÿ‡ÌMæÄV½Ÿ´:Ñ Ov9ýn¯µx©aŽ^]ñ_EXz&ñ >ÜýÍ­íhk+ÞÞ b Ž·¶`÷“T]Å1ì° ÿ?_X(På…(J%à±k÷ŒÎg;À>¼€ëõÎ5lªf¯£e°Ô×L¨/K¢—Ò>ŠùX5ƒÂF1‚'¡Å¢BšNUUD`j¦f¨Jy~¾£tc)â–¨ËSênÿ{i@ }”ÂÆ<}ÂO)K‚  ç7£M4¤ÕBÐEâ›çÄbÙ\LI¼hÌãí#bÃhûûáz˜Œ=?]Âàƒ'Ž·§Áx¸¢ZjÕ¬W©€ôëTD“>”:0ØÀ;Ìê>‡õÙdR.nŸRp»„=Bæe)8'ÈÕü@¨S¦>Øä_x…$UÑSð|ÏOŒ³ rA‘Ð å‹.šÇÛƒ*¸Ѓã–vzCê‚¢ãíýq ¾ð‚ñÈN*µ$¬…aíUÛš·-+×huŽ8LÎp"áÎÐ;ÚËJ9pÈѺ ççÉ߇•Ý=˜ÆØŸÒN@1yõ´.@™?vå ¬@ü¥é(DGü³ÇÕ@G,ä‰%Hæ`tbõì6'‡ž ~D¡ÃCÊü±§˜ÕQ)ã“ßæé³Üê{Ư~R¿¦ëúÿ|ç­ÿxöþ;_ùƒ¯ýá5ý“¿jÜ6Œ’©. ¢ª(’¨½/Þ¹öÙ«eøaÌ%E8Ýnþð¿ÖÛ]- ÓvA‘`ð-¡¤âëh(‚¡PBZ¥ºAŠ%ô ¬¤ݼv[)Î=wnCÀepaytUû™(@ùñàñ퀳N Ј³<€y ñ{÷¨ˆú||d ¹Lßù`ðÿ@°Ô¨5“¨WµÝîÆhqqÆ©6–“Ъó4 qËŒà÷É4ÆAœP%\ÆÁ(M`ñ“Í­t;ÚŽÈí­Í8Ä^Y1 ²ÿF¡XÈG~Á/ ÐÝJ,S½,Üy8wN¯§( õÚ5º¦oûõ×%Ç0Íž90zpÄrYV¨˜S¨R¡pqa9*‚_d ý+T¤ÒZ:˜7G ¢$J……|±XŠ¢ªFJ { N}4À‚Cø‡_LHA½Ñ(òÇëTào0è`C#srAT­(çKp6Í+Óí±/oZ\yñ:¹㈨ÐQp`ºýèJp `<ò-ûëõjhWk¶U[´r4 UÎìQDêw8ý½›‰.¿%€É±uN²A}fû>§é()O1úÃìoÅáý â#Xp üe„Up@%=TÈvŸ$Å>kvÊ rÔq]}*7`Ö .ÙÖI=ðwt ŸÝý'¿M/u±€ºÛWõåž3Öß¹ÿŸ¯ýçûoëïÜüƒÿÙ»­_½}ÛhpîEAZôû”àïÝ»yõs‚¢Ï™’.öÅSíæÞÿÁýoï›hWJÇíøWU4C*áKAëê"•ôûƒU‰7¨ôž´èf« Û€ò¶õ|ç#'€fÕŽ{í kÃÕêŒSit#·^çi@¬Ž9½l GëÔ›·aðü3oCéF”ü ¶7·ÇÜŽ’­íØ—àÀÃ:ëÅ G‹_ŒDQQbÉ+‡bˆ›Å߃úÿÜçz=]ï½ñí×¾õÍ?õusÉYa0»=]K‚"cO¦j(ºPÎçs¹a¾ ©>LzЦÀÑ×u8tº¡¦&ËðI(—Êùb9_‹B¨(Q”ó}€§;„y¸ŸJ€(²7Š@@öx3 º>?ˆŒÜ¼RKZi~a¾dzA]ɶ§`*‡ ?‚;àÿ €è7DÓí±è’À[÷C“Ù¤~k4IM‘b€Gd­)î6ÙÏ¢ñ ëAÄS= À4¡ô Dåp?Ùy®~zû4O$³‹õB4•ûífeD<€€Ngp@Ù}Jé±. ìr0UdeFTÔG8¦ÌàÞ„4È4s‰/9IÊF°OQÌÇÿŸøÄÞômÆík­¿ÿâ¿ù½Ñìý¿šý¿¾÷÷ÿþ»÷gG¿÷oÚõæÍ›ܺ~¹, ´rIRܾn\»áêHšyß_=Õnþí~ð7ßÿÁû¯¸êà¤}µï.–ìyI¨Ø°OõÁuE*•µT.÷5ôI1åü… çÎ_\˜»p1WÖ´Ây0ÀE³T„`è«2×Ö̧0ÀAþ”&ÌÖúÕ‹ÄþÍÐcÎN‚}Fü3È(àñAÙrš×þó†Í^£V‰—;ü€ÚóÕ™¦]¯GËN“§±–Lª˜õ7à ÍXn3qZšlQÎ/à¸ÿ8ÚÚüA©§êJdÊÅBÍOE .ä¸$…^ÙÌ7^ý7Þà»óÆ·_í/^ýú7^Ãf·¹Ì!³·bÈ2l¹¢Â»×)%XÌÍç@² G\C Ç€ÑÇOUþA7&„žX†(-(åb¡P.J¢0EQ±D9º”D;x4‚¯ú°ÛA8òÂq˜ØÇ·›Ñ(àë]1Îç$¡(k Ï 9Û'Óíaý)ó—ëãá0“ÇOÅ‘Cb…!JeÛCZDÉ:'Ò‘mÃj:¬ˆrål„ÌÁÞWÓQrŸòT5wDyu¨IV©K±{àqEɸÃg¶Ov³¯Ù°™ç’Ýý¬2cüäÏsAIBÿ¹ä€ì;¿Ïi¾½Œ*¨(˜K–©ü¡AD…‚{T~DG™ì‹&=þ'^ ¥Ûvêv͸yû6ÍÃqûvö;ût{çêí׿`H àWWß”ú¥R©¯™%Åuµ9S×À8îžj7ÿæ½÷ÿæ½ïÿ?¯@ì¿‚…Û]·T’\{€-t NT³¬R£Ô—Þ\ÕËΩb*…¹óÏ=w~þü™3Ï»0?öìì…çΙT(¾-©n_ƒê4?œAfËiw–h Înã—“`•âºÃó=¿Xi7­Åh¥íð vÏäÑ/ðFØ2èTêšIšÀnQ`›RÄ[T¥i°)ÁsJeˆÿ\Þ/ò_uC‘e?,G©©o¼ñúk¯ í[ßxõ[¯k&¾ ·¹¬2,-“{¯ˆ€?œÙG9¸õ…\.¤º/{R9I ªþÑÔŒN˜ßJ%NÓÈ—S‹¹R±,…AÁ×T±P$oŸ²~á? ×CÖìPpç7†°Ô›ààe×çË%¹ )¹³òüœ Ï'n?‚µO¨d ‡øÓÇôuâ­cKÇòö>çhðÈ2±£ÐмÆ×ùËV»³·Ëázª©Û¥*œ,8—%¦E½»\ë7!dÁÀsA=‹óg´“'¿Gy¬Aãùˆ¨lârTh|ÀbãËùÉ¿?+PR‘ªíY‘ñÞ$°ÄuýûGYí/Ï„ë™HИ?x/ûT üèñ_8^–:Ýf£nÜ0nïܺyãÆëËÿ§{•&åºILp³uýú]CWú}Q]UÜ¿ÒE¡§š‚ ’ÿ¯‚çцG\:ÕnþÍ{ÿûïAèê+çÎÌ^¡vü“lØL ;!Š?š¯J¶­ÿ•«¬šúZ ¦2?ü_<†¡-0{þÌùçLQ•%x*¬†¾÷4'àƒ/f³á´—ã¶pÛöiȤÿwî=\LÁÁ'¹'Ëw>ôŸæ$< €ºUãé ªöÌ(Hßm7—‹Ö‹Ãž s£UÁP œ &zÿˆÆÑ(€t{sk‚`›j¶Ò­t-̇…b½˜/ T/«GªËakj´ýñ§ñú†öÆk¯}ó¯Á­ovqëMÅ\^Vår  ÂÕ‡´Ïç 9/Ìç 9E"9T…ƒ¦”Põ¯†• *9¤” DŒ!Ea1T?O#þb 6¬ëš\î‰Ó8þÏÀ‹ñ Ž¯Ï¼ •´¢¡äæÕyʳp…¶çêÚ_â“dH®HÅã÷éÁö žŒIúÖ#«fÍ_±jU»ÊU`5‹Ìd•ó»\©»ËU´\EÏ5ê¤Æ÷9Îw°ÿÌö̵ç\ÍïOKó(áGE:æ> $ @ÿðx½ êC3e²í¹Ð2Œý+±—G`ùcöFiL˜~Ó¸·Ë :þ Ÿþ£ÿŸ|á…^¯ãØÆMãÎå×½±²|ë?ÞÄo2Úƒ»Ëíæõë· QzWï¿%Puzï ŸUeà_.–Õ•©ðAûªùÞÿÛ÷HïÕxå\á•Wž;÷ pE]v±]c l>è««.­oHï®™ï* %w~öÜܸgÏœ9wþÂÜsPçM¨JÕ•tMRå·±wÓ”>0õ& E1À$ùà±Ë/JŽmUâ¿0Àª6f`ØÇô¦°ezùF»kzÐÎAäo )ZF#br¢ˆß1<ãxs{+N¶h8`Ê`±×Ôr 9“ƒ gî'ü ¤èßþ3Û‰v)nOŸ¨t‡•?¹ä¡€yÜÿ>ÖáÑü$Aȹ'ÈîòPp \/D ù… ñW*rÊðp?‹dûßåòB¾ ƒŸ=þ /|êSŸzá×_øÔ'>½²²Ô}½i7Wé­xÎÒMùšã´›v³îÞØK·›/"ƒ‰‚Ôwû½ÏùúmU-Ì™}UÌ—$Mw]cÐG¿ÉÚUCüàý÷•Š}ñ镹WÐÞ® —l[íKÅ‚ªÚèjß]3à(š¦!ÂÆ.’Ý/äæ.ýçÏœ}nþÜEâ‚ÆJð$ÝÕûæÚš¢ýü ÀÁ©2AÓ¶êí^Üâ, õ ˜-‚J„žõGÀ“Á ö'‘‚™M˜øÎrþ©3nÅc'ÍÆ×;-“ŠH+ÇþÐ $9»aŒcôü„^ï ï3Ý¢ÀÖöx;öCÊÌ“ù‡¹-擼W,…ó ¹é1̵F^$@füÿìOþíŸüÉ7aû‘Zð“H•J&CÜ êÍ/äòCI䆹‚$‘2QuZ Ï”É]—Š0’¤˜¸å\"–‘¢¨¤(C_ ©c²Ë`-Ï G úpÃ@¯óÄ £hDÕü<€®/IÌ‹JÁûÌÍC,.˜<ÊÛót1I˜¬S}0´ÓÐO£áC$Óíñ­·SL!ˆÏò#›â€v­ÎÓ6w8GU¼°›4â|BBšây'ìrº.Sù°­”ž;à2ܧ·Ó»œêçÁ}™í§ }6f~þó)ï¸7¡ÑˆܧísÚž2ž¤zŸ=¢¤]”Ͳ·—M$R„B{œd \&MðÈñA¿þéOêSî MîÖ2n\^ì´ £sK¾ÑÁ¯N}qùÆÀ^^j~ÖÐ×LsMSßuû+_¼ûÛ×îPüÊ÷EiU}__¢vW—sð^³zñŒ½h¿‚öWH(Š+Z A\µmöŒà꒨ȺÛ_{×4Ö8î_0 gΞ=g ù‹syáâ¹3ùœÙs ¹’êºeIyëíû—/Ã!ÕÿÑ`·–üî†Gu@Àw>jhÓt€8n¨7š3[Iš6“”Æ×@°î’ÔÇéü!GÌñßp”PÜ;H¶ D ˆÇ[²ûž\Šäç¹f.ðͤ‰XbIÎàÛ¯¿ö*à/¾ùïþìOÿìÏ¿ñ®Ðàú’—Fð)R²ÿ#?WÌc$Õs¹UiªnÐT ªÿ¦P…bNÐX^C€É/ʪæý‘LÉš·#£”†öAøAš•'8û(¹2Ú¤)‚²ë‹Ìy…F’¨Åóóª¤7O¶§u„ÒA4¯CKwA£F4jxº=žŒý^ÂzD/2©Evm±Æï”€$,u(y6áÉ3&<&pŸèïòúi@Žês÷¸d‡-êÅïhÌð3ÚaÜiú®ÝÉÒ†l½³äÿáQV÷··»Ç+q5/WóqÜ.nt”ÍæAaÊ#F6Ï2@íûTL‰„ý¬ˆÆúïïò€B R ñqÇÿÔ¯Áøä§VV–»íÖšñÙË—ºÆõ—ŒîíŽqëša´/-_ë[ËË[r0¼k†áŠ+Ÿ¿uõ–èÎ1ƒhˆ(BÀøS»¨_éŸ9ƒ›jšuSHRéj'ï«J¶¦UEUÕ®¨EM]“v `ÎÊüùsgÊà‚ÜŹù sîØþí¾‚½  ÑxÂÁýûï˜ïšOIèlP Y¯7ÚÝ8Úh@36æéAÀdýÉ8¦€'@¶U’|xpL&OX_ŠK4y{fEø÷¸|šJ˜°CÀó“2Øg…pÈãÈ1Ø?È&Ùã•q”•dn™%{ü>ñë/|ú…Oÿ‚³Ö,vn}ùË;Ý®±óå/ß2:‹ËWW@Í;ä€Á%Y\ù’{ã–jÌ™¨;ÎÊiŠ!”}|hWÕ’yæÌE³nÿ°öÁ@£á½%r K‚6x¯jIº1E²¥Š*ÊÓ±&óðì.ž-˜‚©LçÅ;Öœ7”3€Á¾Ä·o¾õÖÛ«ïš þ2&ÍØÛíõº½¥öúÈùå#ž°¹ÙZÝé.Ïô—ãV£?à±ÀU«eBùSÚlˆ¤ù°€šQ•ÂS¢Û‹ÒM»5Ná:HUh}?Ÿ/E4@^ "ÑKaß_ŽI‚ÞÀÿ®½ñí×¾ùê_¼Ï€×.–²ÁõEX˜ŽeU•áø/@ú º&GÑ0÷~$ ò¥Bv'èQ(Eô šH¡A¦..,äò‹ž¢(~^ð}ÈñšzŸëùëp*6CøìWhžÏ o‡ÙõùfŽf#)Í/¨Å ³M–0ÝžF­ûá8¦i!ÒhH4@|Bã$FÓí×§÷û«‡µf5²ì†ýuŠò‹_:‡\ wÀú\nÈ?hþ„§øÛç)xhtÍÞAâz’ xF;ç×”ä›dFŽ÷SÝ1Á’M9=šC0›ìýÒúìðïMg Oƒb‹dï³p%—ïqª‘ãTP´Ÿ SaŦà æø/|>À'_XY:-§e\½\ëÿ~ɬ,ÝÿÃøò’µòÛýæJ}pRNv Y|þæŽ,̙岈¿ìwáA„»²«”ѼS9Ï÷¿ÿòË&·¿-•JÆ*EWRIz»@6Är™&”Е&þË&ùg˦ráÜà_Ót0Ѝ™çgÍ &•Ii$Ñ›oþä§?úñÛoC¼ûÐÐàgPÀ£í&Ï#Ü[êõèÕ>™ Á=óþ¿3ýMßýì0¡Óƒ…‰cffvvöÀ³×ùä'y:@« ýÕ©Úè˜3àªNÿÞ—^æ±À6€7¢×7’yó“1Mž•›Œ¢0q†cXE4„›4"`{œnG‘G¥º=òd-ŠÅ’'xEšèSSÄx¤¡{o¼ñío½ª|ëu¥4W¤êÚ‹JÙàúrÞ‡²ØŽdM óÃa!Ÿ/z†ª$^>§Dò¦Iå¦Â@C‡¦Ó•²@»$0ƒA3Š™šZÌ-ä/.Ìsª¦„_X_’T"ž§‹æêQotž¿§ã„F3Ð,pep}fÞS.^\ÐÎÉfB fÛß”àóh&Ñ-¨: iÍ IDAT*öÂÔ °ÎÈÜý Æë¼=½ž€gÄ¡w›wxŠžlˆKÐ×÷í³M%´íeù·}ÎN&ÙT`{Ïj']Î!›Þ+å;µó»<‹±ÁþÁ„E•S ÏJà ù?â Ã(°OÅ4Ì7#€]r3xžlÄÚ刳\!ð¸ã¿@©€þÊR·Óh¯·.×—Œÿ°üååßÄÿÿÁ€é_¾é:ÍÁêMC*º?½|Y¬|éÎmQœ3a“UC‹š;¬±{çþÔueÀv ˆ¢ùò—^Þ¹³6m‡±×eÀ¸óVÍC•$—¼D þýtͼsÃ4óçs¦pÑìëØ …yaÖœ5‹2’TAßüñO~ò“ƒÞa¯áÓƒ\¿0âéwdEŒr ~Xð|ÓP °óš*'r„¡Lé?…þ‹pÿeAL±?*&ÐÊ ùü\¡°PœóÈ‚ùa”ÍþáÓLÆ!Õê}žxs¼Yƒë™‚íÍ¡YÐp}`€¹y­tÞð˜j…°}„SŒGÕúmxãQì‡<$ ûÆÙýž¾?~by¡íÕìùWíy+g×›%ªf+JIwšŽ‹,3ÕÞò]®8¢qB‡°#Y>ÙÀ3Ú÷(ÚÇÁ9OÚI_R™ÀdÂÕ?47(U°ãNv "+ä;¤t"«š Çà!Y1ÀËž-è0+ó£ðLÀTN¼— züñ§Ðm· ãÖjcyù_î|Ù}©÷å¹¼¼\_¹=è VoݼiˆƒUSﯮ|áÆÎ`Î\Õxü«²QTEÕ¥~£ û«ýÁ÷äJSy|þöÚq»¢ÐÔSè U€¿êÚòªÑwá&êk†¾¶ÆP&üƒÎÍ›Ðü4|¤$J®nΟ3Ï™}Ií÷uMÔ¿'ö¡Þ|“â—ÍË øÃ?ú£?~,»°NC}|ªë3øcžØ[Ò~±Ðî?îþ„8Üúô…oôÞiw¨à6‹Îrfž°Âi5²Î‡¬Áy°l¨/9úÙ\`Ïj§¹;yïCªœdøÉá´Â€‹ûÈÂïñ¤T8DËy,R63è!MÄ1>šà“‚…<Øþ!ÏüËüµÇs‡R= Åy ‘Ã'¿×[ê¶»mãÎA³ÑYúr§5ül6[—ï¬ÿ÷v s õÍË¢ºrï×ÿ—Š €¿t‹j¹()Ðx¢B¶\gÇ˽›_¸wÇ|Ð^Ì—JùJ½ªX®kÝz¥ïuIÕD(Fü ü0÷ òÞ”¾'»jÁ$Ó4XÕKâ /•i±¢$Ý¿ÿÝa®½C"€9àñ,ð˜‘@Çœ g@€ê<–¾ó‘@«aW»_ºwûÚ”ZMÇ} '¼´˜ÀV“J߯Cü„‡;Œ²b (‹{‡# ÊK¡ÿí-hpP)„a!ˆT®4ls9‚9L7UY‰’íØ4ÈR¿aJ°ý#ÓÌÍŠÓÁõ æ ø‚o¤r1¿PÈGÅ|'`BÊÇÞP6Æ0ñ”¤:»¥‚À.®+…b!w1náâ‚W\ ž¢úeU‰ £„¢÷\ž.ói€_Hs÷ø›#/Þo{ÛìlA˜%×gJ¥¼RÎñ:U÷%¶ѬHœ-wø[4h²¦†>ÍþðýY§±Ã#ÞÞJ<èR~MÙñ”`YíÝ!Çy~ýƒ O­“¥×ɰfSÿSùÿWð>«}7+ÚÏ¢z»Ù |”#žÊ—Ic?3þsÎ, ¶kõòÓþ[ýê{ê`~Â^±¥5Ói¬Ý_3x:ÀɇÚiÚeú\¸ýjÿ§?qEøùr_’%lÄ¡]ÚÝ>l¾fþhͤ©Ãñù¬)k4ˆBKô& ÛÉJõþýû« MMGs<ìO&Ô±ø%#€vݪV:Àÿ¨7èÍh‹™ ›Gq>˜6*kã×i„L>§½ž57ÑíSJ…ɲ¢¨ø«!dO5¼X 2¯ÙO6)š3ú£Ô”aûÅ|òçæÎäxp}JNE”@7,ÄpÿK~¾šdô£" &çMQXèºÊ©ÆbØ0”KÂÅ<<šBÄ_òëC¯ìkª §¡üa|%€wÐŒ>ÍËföŒ6 »›[c(¯Í Å)m¥f¾”3ÊEÝc?Þ âÛûÃÍôC£}ãñØó+“F©ŸCïϺO¬Bï¡9ÇFÙöó•ZÔªÕzFûoã©úŽ(¿FÓhQ©þϸ½¿Ëí8”ÆÅ9{»Ó‚ÝgµSàŽß5Bƒv2ã{ñÇèhz_˜tžK<›Ó÷à SïÙ„Â4#¥ü'4÷8 > ±„ÌM{<6xj÷²7 ‘O2ɼÝ=.IxÂñéÍuvË1vîèYîíÜâw쨒٠`ª„¼d÷i‰T·k*€m¨¶(©ôz^Èwz+„hH%ó¡v»f¬A£f×-ëU]ìÓ$¢ °ÓINÑÀ3f_ä)þ‡ï@ÏAH®Ky_WïÓ,S®(ºŠ» RXíLøÚe #¼ó‚€ÕÁ²x¢î”ÿ@&þé÷£°Í–‡Â‚ß™ºO]>ü‰lzy1 Å°Wm™gkw:ÝNÛL!ïƒÄg霽cŒ(Þ ¼‡÷¦Éð`cš7H¶i¬>MÅAó4&†áÑv„‡4HÁ¡LÖP‡í7͸`ž1ÎçJÓÁõPÑf¼•hŠP„é/*‚•ó0îp%²NÄOADU£ÙÂ4S ˆ9☠ÖsùrÀÏ•Š …bX,ޤB(k£uUU …ð=šŸWðÙ§ù=Â8 ßWxVÿt ×'e+1ËÁôM±hTù|Åã ¼‘ñhèùë Œ»?öSß‹ÆC+½>ŒÒ(>uüñÆM@fÛ׿múÇo¡w:TŸ7áÑýϸŸÙišå‹fã¦~<6‡³kìLd/zz;í’«ñ'<ã' RÙ— Ò<¿”j`ŸRT€P:=>×îñ;€È\è?­ëcfÈà>ÉÞLvp02HôÃ~ÀŽßí4põö¡bÕv ;k×\“W¾p«'+Æ@µ­º;ÐDÑW™§ÉĪö{–[t§C’ÞJJñ¢džn·«ØQÍ®À)¨Š/«2WŽ0d™ÊÐV  п ·ßÕ×tM[ó¦´ªI+¨¢1è+ØZ¥ ’ŽCi¦Ë¦ñÓµw³A‚›ÿãq‰Å0 PÓW‡P„&à¬øE¦]ÊU.ÍÐk†ù½ë4wÓ2·x²Ì (! {DáÐÝÉ'Nùµ7Ùª’£Y2cŸ&óWa+ŽxÆÍŒƒHˆáÑËðâ8M6=?o^ âóæÉàú{Ž7cÍTò¢Ÿ—CE Â=yš”æmÃ0Œ0–q4ï¨Jt®ˆB)Ÿ`ý…|¾@Nˆ¿¡”ж··¨`¼½-˜^hŠ×L"‡,ÎÇSþé5a±ã“-RCJ’O¿áÔýI(]˜ÄÕ(ªU‚a µR«Óò¨:žN8<È”ý8üâ¾iÀþAV†C$ÁžÕž½ú‡öR.0¦ Gy‚^~‰Ø¿%䀧ØŸP4Ÿg$<âYüi‚r*å£ J83AÀyÀQ6˜pB'¼Ï%Æ4‘¦ßçqG<Áàßm7jôf†±S3Œk–uûæµ[Ö Ë5vv®Ã°çwŒêÛ¦Ü-õhðUÛµ­÷àÜ‹µ²¨Ð{®+¶åÚv±1/]{­½Ö6‹³0_ÐOµk¶-Òô°ºd‰ešøo \ 6JyqP¤©¦}*xù¦Ù§×‡0¼|ÝT†Â/ £ô ¬¸E6ú.ÍGQ¢× ŒµUsu üÌ?>h> ¨i)ð“äý“àO뜢€ï|ø€ÐrÓ³ã7Ï,ñùV+—*5‹€^£1bˆ“çIõ“ ëß¡—ò6©qH‘µ„G¿ð¸+úÉtöLÒÛÅù¹ù\>_âT™Ä;yó½… ›ú¥H #¬¤‡CÁ©ˆ1¦Àßè —6ÒYƒw®dïò#‘‚ßÛc?1ip£Y¼fÒÛLº¾Qv}ܡшfÿ¤ñ¿#* ‡qJ„’¦ôï ÷§^‹<{ž À¦‚‹ÝL±Ó8}Ì®÷>Åè¯IVbCѽ çüyÆÿƒ ƒñ©íG<éG‹ËИ…û(êwÀ/âê¼É—éîÒkþ§/¥p?æÍÆ1æ§c 'ê?ÊFp Ÿ|4T€Ów²?ó¤ãÔjóRú7oܺuÍØ¹f\»íZ6„¿{›ôEwv»÷…;wzrÕµæõ ÕN(pR竞L‹K UÝVÉ,å/æaÁµ‡Úvå=«:?éõ´Áþ{Æ_IRyU©„˜Æí?^ãqGÀº–}AS„ ô>Í 6 „ƒäê’`¸·¯éê@2e•Æø¬­Ñ„¡Lþó«ÁÔ#ð_°j8»*ÀÒCPÀ¶Åñ8ÊÒY\è\?Ù@/ßH¼4 úz›ÆÑíƒðÁ¸›é¸™ðâÅù¹ ù"PÄ ƒ°477waÞ˜{î9ý‚¡Ksgó³÷ÏâHØv0J|š<\¥´n™ßÒ H¶Ótks›f Öþ?æÞ¬9®óLÔ5Å=‘™gßDî¶ÌÙU]…@²Ay2ó,™H¸JjQ$eGÏt;æÂÁy:ºzæÖQ5îZ¦º+z¦Ú¶lY«%^˼ÑR]}û˜÷y¿ pABvuŒRâ˳€8ïó½ëó˜ø­‘ƒ… 4:–ité—MΛ¦hdÈdEU“Ð m»›ö5‰ßè¸tÿOKÿzÌÙÐýúA„ÿcÎ቟ï`P 8@²¼ÇÑ‘Ÿ0ÐQªd僔£$=&zá¿O6“à ]–UVåâAXgÏ™koÆ’¼' ¾6[tæó<Þhé:ÂûE>q¾PæQøDÈüB É4ìÌÙ ^‘)ëû°Œyèo(ª”cDLÏ»:ûÌEöb,¦—€¢‹qÈ ®_«–`¥dÿwî~æŸ)¸9²}²ÛUȂ޿ŸÈÙ£´ñƽïoäµ:›3þÁhkʘÙÃ&ª\>ñyžüZ/óä·]*Wެó§z¾n˜M[25 fO~¼IO†c9ïo«¶‰ncƒÿÞuM6eµùÙoèŸÙªj6N½ÞpìÍÈHKõí&èéÝfÓЬm0‘9P®þ³gËßyxÇ Ê{Ú,Ÿ;Ú" <€çÀS?Ùü¿GPxr‹û­ToáPd}(8?Qÿ\¤ìÑÔƒ¨}´ˆ¸Êc`ÂŒ fÂÿ!8õ7ÌìäÏÄí¼èúµj![Èå?ÍÒŽÿyü¹ìçùDFvs \—‚lN–õÒÆÆFn…§¦ÄžžIä?_­ç³ù\&‹ôlfb½T*Ë…ãëÍl¼kÙüCK‡¨®›¦EѼ¦‘Í7šèäFRÛrTòìf£ùQƒ{Ó¡@ÞBÛÈÇÎÇïä6LUoh–­ºm×ÁäZt¯Ÿ'ð—wÜ ¾…PY@NÀÚ!`Ñ¿b¹H!@ÜA×kÄ·qaÐ¥îuB£ ¹“îtëõõJ©Ìòdjžô²ÛéP8)íyÖ¥ ^»qÅLß À8©Øÿ`°£S'÷iÃw¸“Àõœö~&AT`†P†“, Ó†®í´¤vxYñå´Nû;¼~ÆJh±|€48®ßn‘Oàƒ÷=ýqÀ2äËÇA´Þ‹Ÿ>ÞuŸ¬ãÏ‚<_ªV9)Ç$À¼¹Ž0å7ÕtåB\“wÝ)7ÿÓÂ|´|gzÇ¢ô6ƒLÛ…o3€?Á?NÇsÁN††B!,À){ŽèqôgçŠÿyV>e–)âæ@Œ&ê>ÿø*‚ÍB6Ï1|&¿š ƒ@N$W¼ L„i82À&–;…¿BØñõ£Øº½n§Ûë@ë(A1tÉÑgưӯ“‡{ß}¼GWx±HŽDJ_ÔÌß\LçˆÅMRwØÅ'Óeëð#°óYàD´ìž{Ì"7°tÏPtCû‡»÷Ç7ý3c8KÌY0MȬ@,nD¤ù™†LWü¢ã ùÏ¡Œ'_ yü(þ6ŸO&ó¨.]ÿüsòÈÖùM™,Ç9‡ÊUE¶ôŒaB!P#»·eû#Umª²d4êjÝúÐlhuÛQR²ãÔU£ÞØ6d– tôF½ qÍtêúWÛfCÕTE7 ™‚ N©YM]Ñõo@æ7¨þ)·% TY/½T,¯Rg®^¥@µC#P÷;>ú] uÒãLvÁ‹ËÙ§\ßÚ*oT*k•*Zé:-ïÒËÿ)é…èð¿ä…žçÊj]ü…‹"‚8¾ƒn`Ú`)òß?8èEñAØïA‹c¸  #]lj0Ìฎ"°LV“Õlk’”î¸fÞõ%uq|}øã1S °ÙŠ)ˆaûaÔmùdÀaÏï+˜›B!ˆÖj¡lðÌzô¼u®i»î{O=„äpñ8 TõÙ&+ñ ›͹ÂH¹9h2Šß¬¯;Åìšx—¬ÏäŸ –ß©0Z±)‹iÝ«ƒ‹Z¤Çœ¶2žbÜ—åDD1„Ÿ"º„™YÀEÂbÊŽ 7°oåŽGYâh™löól¶ˆzh±˜§?¹Z¤‹…Bò·K×óyÚJ‘MÁ›lóùûEú˜Ë/ØÀ@HL~¼j7h‹¯#øq=…°À¢@ßQÓøÈpÈ9ü°ÙÜn8NCƒ,4C_~õõWº©Y¦"[Û¶mXªÑtM]7Üãc“  è1ê(óE<Â@ %<„õý¶(rŸ_ûø:zŸ°¹·n[wÑÕ²¹ygm;*f¸Ðòú=pØ#BÅ}¸àìá™}æÛ½æTPsb³g³T óeë ÷âzž‚!¦ûÄ›ÆBœI¿Cnüóí|Â*ßCF¤8S9eL™uaq+Ðî>e·1B¼ðxx€E2UòìWsyÚÞ‹Ålœvöt“‹K×é¯äý³»ÆjÖZKä3„èÌål`  ·´O>Õ5)MÏ€ù!²ŽÃº?¸ {[7%ò(Ðä(èu[«Ó–o`B°á40wH¤û¡i×ú6fÓ U¶1ŸHöZmï¸Aý~oÈJùÕ«™Bi­R(¼T ¨³W³^„PÐd9,ÖÀîÁk†”Þcìç­C„÷èz]Ú\/ÕÊ•õʆ$†wÞ9kü Šì—앳n» nÏóºL¥Ïǃi3€Â6t‡ú çy]d øäZŽíÙf °Œb<Ø´M(?V;º–H`>DW–n†ªb¾?¾~ï16ÑA'¦(LݸΠ6 y~LŽ|Øo³ð'*üQôd}w7&o‚„ž^'¸ûÖ¿|ã^ÍúÖæýÙÜØlÔ Ú€B4ÇsX˜ uÌÞ!fz˜^cÊ]Âs°‡ D -v¾l]Ì ywž ª³Çt,ÔÐè‹1Ý!sw˜Æï#nëÇ€òTàÿ±épv¨' Zx:ºˆ8Æ|~A>üÂãkˆJùß’f¸ªÂ¯"’y°kzöJËÖùEÁçü&¤ýÈ5ø|‘ 'ÖZ°A :…ú¦n(¡}¿.ËF³aÛ.Åòͦ®ªšKÿëuÃU)tÐé’Jß·í£5r">6µOêÎï×m÷cd 4ž:;ê<-ü 3¨ßð<€eæÿìùädáþ ÞH@ûÓZ‰þ¼"à ÚȘ/ÏïöÚPÕE©/ ÉÚþázpd¬j?НoÕkëkôÐWJÕMPê‡Þ9óœñ·„ž#'ϸ!a³ÃŽžßÛÛëÁm÷{"ô ?@À„¢`xt­žv³afhZŽiòìí˜r*‘6%‚„=E&àî¶[­6ß_M¢ ¸·À.q}´,>D]Ñ$ýÀp!üq×Àñu€ƒ£Þ±u÷uñëïîžqÝíþ«l® l½~ïM”©M¸ÇŽ[f`,“ Ö"˜æbßC7!¿Áî>K.ݟ¹ÿ&ꟈÆ—¡õ$Á¸&³ Çœâ¼ÃYqlºŸˆ+ û‰ñ@Pyy(‰x$XTAFˆff';ñøA€ ‰@øä¢µºxÁÎËåÒ²uzŒ”K…ü€<'U3ìd), ðl`díVÝL7>²W)noÖaÛªõíèZƒëäøÛÒ¦õ¯`º IDAT WN¥uÔAžmaIöT(O×?lÖ›–Ùl:Î'NãcËúŠÿ¯Y§Mü ô7¨o% €û[+Q¬Å°~ú»>ùË´Yîƒ ?î2“6¸>úÝÝ ìÇýp±Yï€s§ñ:¿UÛ(U‹µruck ¢ã·.þ•ö·çÙð´ÔÚüÙÍöÚcÇGûýA+ Ó¢È…À~?´°ß[6Åà¶Å£žY`Ž·T$)>PViŠÞLÛ2Zm­cRJâûÃõ£Å{éçÚO°«ÅãÁhìíáïPùcÕ?(â—U¯·Ñ Ü¢uðŠ¶ÈŸ8ºN°Vy#쇭°ïÞ¯¬¹÷ïo_»Vݽ›¿J›&eƒÞˆ›jÉb¦¼cΘ°K¤ûéM`1˜õ]¶.2‰3qòébÃgÑ?žDï ±R(j õFRpM™àw,d†"ŸÈ~…`ö˜ÍÅ#ß4\ô@ÒŒ)D™'äÄãk5 +ˆëéSmíØKØù²u|!ÓŸ4™<VÙ"²ÿyÎ p—EN°ÙNÝJÉôtçîjN£™TL§N~½Ë"úžkÔë¦Ù¬Óö`è/0¼­¨"˜ !¬ªŠU¯Œ¸7ˆ M½áÔmòNAü8BðŽÔ)`ƒ^K@à8„€“Í ¬?€£@Œ1] ƒw¹%˜vO_¤½Éòá0÷Ÿ»Çž‚XߌÖÖJá¥!Aè·ÛÞ9ûìß©³r­€fBN[é„Gn´wÆ í÷‚˜¶ÿ8ê0F2‹y\Šý{܈j 4E1,M¶cÚeÅ6ä4g œN^L‰ûãgíBAA o$É÷‰º€9> ôúaŒ±h^ßmûý.’~qй P‚'ëdÿÍÍÊFÿê÷ûAŸ°ªy÷þ½k×^¿çÞGR02ààÔüS·Ìõ³Ë ©Ž÷ä1;+dD—®³TLjOÉ “„Lfb:—á`:á¢* B2œf<Ò7™q.wuÁü=eAráöƒ£1'HÙ¡%@´r²Ù¢zÀƒ†ˆëEW0ÏôNY‘ UÄwúq1dõ>~ÀdùñÓYÀzšã&¸4ÉRIKÖg<Ô(óìüŸSLõçôùÏ\¬×ó¹z<Á‚%§sM;m¨úJ üq Èš¡«¿j*’N¶_W]rêNSÕÔ† XÏ#ÿ_µå¤ÉU0Ëj–ª[®­j®c«Í¦Ñ¤ Á;štžúü´à7¨#°±x=kþOKƒý1½žFßž&œ(ðý­U €b­VÌ^½ruµD€@@Û=yÊ~w?܇Hp¯Ð&Úß%“éû ¿[¬‡»Í9\ïìöô’]E[›ôÀÓ[[µjfÞE7>ÿw+ÒßýMòÜù¿´¼‹/ŸI$¼ŽwäŒÏOƆŒùÿ­ì˜€mûÀ nöääå”m@žï´tÓJ¥ FáÓ„ÙáJ*±âóõÁѶ[  ÀT¡Wÿ±›ßiƒ%Àoc(àPK$æ›@Ñ€"d9Ñ<ˆÁ€‚ ¡OQPÄ}²}$iÝýgìü·æZùÌgÿí³Ï>û?:óãîÿ3÷ þ5@¡X–k´aƒ§sÌZ¬À#¤Q @Q4YGOdÓ@©Ž‹ö<ÀÙ´×ßY«º‡B~'¬KDFoFð‹÷\áT+ï°-NyN•QD2=ñPÌïÍx¶GH°øyÊÑB€„S #n]â1D”Y:T¤†¬²ìxž bö1ä%¹™lhLu8z‚KÖ‡¢ iT«ÕÊe²)r ‰l†ÙÖr(ä²9K]•‹MÛ–$[–d•ÐÀ`YÛÔÉž U³LS×LzT Í4LU“¼´¥j²©¥eORwX·€IÀû2»ô‚!\¤‡B d‚ÜÏ/;ž%NGÜ €æ!þI„b13œ.Y .ep£S€Þ¢b±eËÏ'ŠÜ! žF7¹‚dé–'£z¹ØÉÖMMÅTO£NŠîªí*ª'™Žî)dù ˜0dËÓËH{š«Éõ¦glëŠ^¯kÈ3î7h3¨gàç?Çÿ?ÿù þ¸q䣸N|äãÃÿ”@w•½zu•ì¿@‘úKkëHµrýµ¶^«y¼ÛÑ~è“Áïà }G:EÆ>vk0âŠu´ü®wÉ+è ˜Þª_ߪ€n ºµÎLJ€¿¶/»)ý?iÿùò_Ylçlò¢·ÂÃÁíð©ãÅnÛéõ;û˜ ìó<Ð$<„ møI)%¥R©¤”F#Æ„ d@ÞýÖ §“覾>ûAàB6KÐ XÇ'•²öÿ豈ßG@EXûßô[ñ ÓŠú*€´Xwï½Qù‡ÿåß?ðÏ|úÁ™_ôÿåÃOÏøþ}õ^ë­·\]%äçPÜ㑾‰È–Ö#'ý€Cl¥#¡É1f·ž½á w Ÿ¼gBð…‘¿ñþ»ÿï»î½Gªõ?üç·Ö×kå[ãñ¢.À´`ÜÓ3Bq€ÁLß<'°¨÷/êü#V…3V³$èHˆƒ>¢ÉhéñSÑ <"ƒ ñaàЄÙ¦ËÖÇsq~¦3œÔ„P,|žEKpí«èòQõ4º ê¶Fºi©ô¹nÚ EÑ›¶"ëf]×=•,Ú@û¸¡[²îéºÝÝ `Á30[꩚V7€Y'Áp­oÒŒY€cõ <ïuhœqüdøùæ{~G¨Ñ]ñݡ̲¾ö{_y/B7?3úíý¥3ß}2°’_º-ÖãÎãõ èô ‚æõfc³¾µQXßÚâãi÷õÎþ]pÙ]ùë–ñ×ÉËž|ö\ò¼“‚-·ÐHsìx¿M!:ìœ ˆwDý^ÁþºàpL9X‘Òɤ”9¸ÅB!.$ƒU‚„´¬»žnXŠj[A:PøúÈêÅݰ߂µ÷aëa$t|0°K?­vaê~4¢Èîw0æÀ7À:(EBT@‘= P"&HŠz(´Ü·„î=‹Þß?üêÞý×ïÝyó_¹yQPÄÎtŒò=Fu†L¦?dŠgËFóCÑžÉX´ô£c}KÖiŸŸÍvvnÝ"øàÝ¿ÿ9¹Ÿ=úƒ?¸µAö_½5› I"1O8{øˆÇ ‘\´OÆb·pË €{t!G†Þ¡‘(Ÿòl1ÇlðpÙ§ã¥ÇO1’4áñ£ñhA&4-Ñc¤-[ÜÃcn¤ÆyÀCnØgçøÄõÙ­­Íõ[·vnÕ|ô‹¿ÿ¯¿&øìÑ­õZµR«ÝbG}<!a7œ?|ôȶBäG‚ògÎM}¬'„ܽH; è(8¾pYÁ'ÊMC&,О=Zz<³†róx$¢„¾É\Ì6L—­óõ1h0c.4úI¸YCíùB¦Ï$rvN14²Ø”ªSì'§¶›²KÑ€QoXdó ÍÚ6ë@cØ ýހïymä&h.¾KÆÌÓ6$XP ÐaŽÀi^ûfÀqƒZ$O‡ó~1…€“àù//õè f_*} ™LkχgÞŽ8×Ǭ·0äbÊmZû¢§f±Þíto®ío¯)µÍõúÖõk´Mîïp$¼‹iõâŸIï‚d]N¿|æ‚“<ÿrâŒçuŠ® Î?¶)Øï¡Ð;ˆ{ƒ“‚ÒVOP¯¥¤ ºš–W´´F›=æ„ dHK²¬X p”4Y{:ÅŽ °ë(Oúƒ®/´<âäü¸Ïì?Q_(¡À’¢q€„ÀnÔëµú­A´@ÎK/î÷Ý·Ü·¯mÕëw¶6ïþò-ú µªæ÷7«¥7ß¼ç®f2Ü0b%Üù,µÁô\ïæ–ý!2zb]cN©?o}ggë;;SúŠŽ{·}öÙ=÷ãŸÿýß¿ïÞýŒ ¾¹Y[[ÿÎ|¶`ìƒ)>|ôðÑdш‚š„˜ú—wWÑ0åyÁûÌü5!Ì‚@˜% Ÿœ›¦(EpÞnÉñ`™Šë£¡qÆÄS¨ ¥”eë\SœŠBAô•9€ð/9›¦ÀßÎ9vVO‘áëJê×_)¨fêN}»ñÐi|¢ƒ<ÞQ Ž  ´kŽœ9‘´ã[¦mk†çÉ t]–„œM]µ®_;Jp$h>õ÷7¨o Çîð¥µÚÚÚãÌÚš‡H¹Ój!ºï™˜c!Ø/„ÿX¯÷wÃö~óúö†rA©ln5···®Çýôòűwñÿ6ÎÙöŠ÷òŠ¬Ê †ðsé3ç<¶­ÇLJá~û6ý}ºY*ÂÁÁÀîBÜã®?[UV’!@:¥` 2ð®c™*F‚Ȉٞ£'ät;-¥‘ßç.€]ð”a€°«-°‹u|E¿ÏÅ ϭþ`Bî‚ߟ@¢³Ûþ?ˆÃÁ€À‰Â‚¥€5Å¢ƒˆÀ}k;›+VÊëoo”ËÕR¾T¨¿Õ̯ß+}Ï¥í Ç“Èÿaôf":ç'öB>‘ͬZv6oçìzÞ2%Š µôé”&§Ri§AXL íý ÿ•¢|‡üèÍë¶b[®ƒ\€çX† _ŸÖµIo/€Š™!óúcà4AÞqƒú†ð$Åw¾ïèwN k‡·Vî½Tί¡jW(”Jå\¾âµü½VË÷÷À Åód >ÚæcÖÈîuû¡¿ˆ ëdG­­èZÓ3_¾¤kõë[ñA¿½?èA:Èû‹®qÞµWìËg’–#§\ð„A­::OŽ÷i× w™X„ÎßéÔ£˜Bú=€˜¶z—œC¿$º"+T#y2Èq-rÝ4”|è] •Ô¤ÜpÁSìŠw¡vÈS¯ƒ>"ë­“>ÄÃÐv"á¿Ç|ˆ‡ë , ft¢ÄQ0ˆ sï\»Z«ºní®q§FŸj…«›wšùÍõÒŸ¸ù Æ®K5.–¡‹—Âf £vÊô™³ñT`ÂËiÂ˳ˆ ð}áúÎ+¯Ý|…"ÿûî=üGàüúg°`ÂG¯lVÿà'L À*#²ÿÏ(xôHÌ"2þ0é0'ãQ—üÀÈP¢pÎ\#(É MQ‘†^ú”á,=~ÈsÑ< È‚µ>A?~ò:“"ÂYšìÓr3¾õõj1ŸOäȰrõ¬]ÏåW³H [f2•6-ìörZ•1è§[ë Ï0LO×=Í‘àåÃÂ]Ý´]~€Tz„ -&ô'm«h Æ y×ío7¨oTò¹2Æ«J•Ju-_TJù ª‚RU¼½ùÿ-¿½×ñ÷c“1 p4½õ"DÆ=ìš18À¯w·Ö·eÏJŸ9ŸP®_ßÚÑOô…¡wYs^vC÷¢wéÂùä%Åcþ@£(¦‹ã;ýN·ÇÎv§Íç¿í“•A¾“ÎB¿(Ç1=Ûpy&Д“²©JŠfºƒ,8°[³“{ƒ\£›–”Pkó=2v=á)ü…±Ð9ßÐ:Az“'Ú é3*ƒq¿EëYÁ]Ÿ¼p• Ш´÷O®]Ysßüž»v¯æÞ}Ãu«W6ßhäÈ¿‹$`®PªÊ5–Û`rŽÏN8®òˆþ"a6äí–'l`vœ_ÃÌð‹ÖwnݸÁ©¿…pïžýÞÏ>à/ þÑþáw¾Ãõtìõˆÿ~öÙC¼=⹞ž‹¾= Mi(ß9oÈÚ`äÙƒí "áÊ¢¤—Ï #¢xÊŠDEçË‚§ý£. c±àÅz"2î„­ ž¬ï[[µõ’g)é3/_XQ® B´òôúýï²ëžû›{.áxÉs¦+y=F€Xl§||zÛ¨×íõ£Ç秃{ÜdAªÅ±º ÆZš&'UC= …vú[ôûÕtÛt=òíb8f B£´ýGŒ] Ù v¹ÚßãëÇ|}f?†–tPÛ -¤7Áý$?}4ü è!­‡dúû½A¯ïï3\­Ýýáﯭ¹÷øÃ»níêæë[¥·AYÎërm(’ñ¢N>e1ž1?êÜ¥Ã}³B²‡‡üAÅ=áò÷Ië;;·^½yßýÍÇ|ð‘pQß·]v>qïÜš’uƒl4fû/@s̹5 <ÈÀG ¥"Ø®PúZ(„jþ9El´è \z<3Mü…cQËd…補b¸Â©LBï §H€ÌwÈÈd³ÅRî¡eq#@•ò+F|úCkÕrÈ6±7è†fY²d{óMÕÓ¡:ꑟ/ÔCD7¹ØP]Óp%C‘MY“)ɺã=ëëþ?xÇ êÛ««Eµ|®òÒZ%KÑ@ <,«Å"<€Ëáá3èpÉÉ";ÐÈ‘1krmp±¾¾¹Q©)„RÎ^H$•”ð⃸€>^ï®xn¾òΛ^êe·{§ý5òÅñAgw f? @¹ ¢p>'ì÷¢že êÜ `ƒ €pÚÖ4îúuèWª¨¶aºmaPÈÒÁíÚZ:i¤tîïëߨզí<ê=¹~ÇEXcÖ:arÓ¸ÃD(Òz—ÿ1 ¡qúÇ¡½Ÿ<põèþÜׯekîY¯7·6ݵÿÇýáznëO¥­BóûnIAAÄ x‚ÛŸÛݹMìþ îÇ-¬™þ Ø5 2ƒ'­ïìÜxÛýô£_`øï¿Ýßýä7î[k\¶JˆqÄþ /D°0ÔÜÀ1(Êf¿Ã-f€ÄDüѤ+ËÅ{O:S!’AnÌè"ÊÝEËÖû —Çø'“W_»qóÕÚF¹P(T*ɼž…ÿï® 7T–tŠùÓzžŒßÊ«²S7ê¨ók6EtpÊZ˜µ5Ýu¬…ì( Aõ<°ŠÊjZS%Z×5EJÛîsgÿý©oxÇ êØ0ÐQƒžù€øØkyhpZ(Wqs%ºËleí¥R5“­³W3ÅLÅó[Á®ßm·Èh¡5?X>£è¿Ö2Šv(²¼^ßäæòÑõˉK …,,&ðbˆ“ pqà^ò<ÕK\ Ûl{É—í6¦oY<ˆÏ±zi:ÿv{?¼½8Ðíz³b©.0,Ü *áwÉÿâž)]Nkº…Ù¿rp9ë©•¶&wün_`?¢>&‚™ˆhq} Á„S'fÖ_!ן¹øŸáÈ: Ì]õÜ»× ëî»›?ÜüŸéÿw]Úú7ߪ—KÍí·Ü SW—ªµÚ¢g‡äæ‚—ûû0Ô+yÇ¢þ5áê x!~âúÎïÓvï¾÷ÁŸÀ>úèã߸w*ävÜš‰¶=øÇೇ¸dÌŠ pòá” ½x¶ÐœŽËÈò$3F1Äy@Dés<ùx¦™3a²ý“93"ólò\(¼á"H•¡¿`ÆÕ‡Wfõ­õ &„ò?Ѳ¹fùŸ¤¬éÆJÂsÒ鲞¯[ùY1x~ŒfÃiššæ™šIriA‰ß4è‘`c±UÃÕ,™öÿ¦±ýЦgLÕÒôÖSµ¾Á;nPßB »ºJJ¶XÍfª%€r¦†û-\-æÐÛíV«Õm…PÀ@ŽŒl”¶K–Ú¦í´ÓYtÍb}}}£R{ƒçÚÙËçátÉ@¼Ë^8@Å p^¹|ÞÓÎx~·Ûö/;̯·"q~dçÈä:Ì*¼OçßÛpÒ='Hu»&Êý.ìß6-…^ üõt9‘ZIBô›SHÚ'H£ÌçÓí ¥B]]–è^ŸB~.iôÑÔ¢ *æà~ÈŒ!L)êw;ýÇëñ.ÖÙÃá¥ëÞÝ.nnþ›û?¬oã‡÷ÿÍææfaë^³ÖܾˀÉ|¡$£˜«Z(zƒ. ;ïˆ 6¹õ•©7POã„Sú pòúÎ÷ÝßÞ{ïÝwßý廿üÕ{ïýúÃ>£€^˜Dþì<Î"ÀhÄÝ;àEw³ ™å[L €9T´"MÄ.fÓiÚª¡ M“TÅ0‘8<(;Þqƒòž6Ñ'ÜüŸ”柧IžrÅ«@-Sä«YôU”òW‹€NÈíøLÖ½ÛÂ@,Œ¾Û§È¸O– ì>úg˜Y3ƺTß*W*kŵ"ÅßÊùs—’ mƒðÀKxûý8:ˆ½Ënä]^!¿üŒ‡­ÔK^¼ä¶0‚ƒŽ;•° ="Öà¾ÝG!0lõâ¨ß# r 5lAèQè¸Ù–‘Rt]ÇîïZž¢­¤WÒ)ÉÔ9Gè8&ÅsÉ˱””Þ=ªTw8Ÿ æ?æÁõCh|÷!ìv@ú¹¨Ò1~Lð×}²/Ö㸿0¸o¿Ý,kë?¬UÂb™>–J•koo“ýÿà¾Ë¥êBª\­¡áu$jð\9GoÊÃ}´{ÎØfÂ01ê;dñ^æ[²þ6€Ìþ=÷Ý_º¿ú•ûÞ{ïøÑÇî[<Ì+d}Fã›;7«>âP„szØÇLQ:Avqˆ$¿P#sñm*(I± ¤LPþçûòä'ì•<ÜûhÈLC.oŒ8Ǹl}²P@pQÐñÎN­’,åJµra5_6t)Û& W µ~ÉuVä\&GÞ¿S¯Û «®SÆ1mU‘UÝ¡àÐ!ŸŒ?­Ò¾o¨¶å™žjjt¸FÛŠnyM þ¿¢ÉÚ5f'ã~e (Â8fPßFȯæK…r•î3O­fW«ùUrX Wàøüï2[¶OQ@ÈdàÑ@‘.dÖ_úÂoGQ„^aXÛ*×ÐX\,•(:—.œ¿˜RöýöÁ²Þ Šú=ï¢w^:ï]ºtÆ »žw!å¤Û[œŸl )7tÑù÷‘€ ú»û!zÌ®jaîbàHä:–)¥uè†Ñ†oéòJòr*)I)¹Ìº¶zy%mØ&º{ý³ ìZ°÷àsˆë÷˜Û³ƒ†!$$;Hô1øÓoE,þ.Áç­ÈÏqßÚl^Ûn^»Óܾ\£Ï×®]Û¾¶ýöÝ·ñ8@üë J0Ñ'¹g8]"ð4@@p¥@1Àj¾ºJwJ@~Ýï•üj1w%[ñàßvº­ îñô =ô€2cÁ¬ÉSõqi_ºõR¥X­TªòÙó­´Ýö£ƒw)åÅ`õ!P.{gÕ´ã ¼‹Ócks-Îtqþððü·¸×aШZZ&jû.k…ÃèÕtR7ñÛšªHRâr*%'¤†:È p=M‘‰t`1 HÄmþ»b]½#×[mˆý ëøˆkx¸ -ÿÄõîAä¾þ½?¹ÿî[ÿòõ?ùÞ÷¾÷æ;øÁò¦ëfî¿qŸÙîÐ „p2Ü‚- vµÐÓbáÞ©˜ça–Mf ç–{ÖÞ[¶þ¶ûɇ¾÷Þ/ß}÷?ûùÏ~ö‹_þêƒxg¢—<æW_yíÖMtΧòè=—Ïî^¸¬Ðþ¸u÷bÊ ÷÷<ålÂó  §íygRcG¯…6 èv7@íïøùQÜ‹{ ´:߃äñ N¡€!Kò|èPR—+ E–å”$C‚~S¶–J®¬´$Ë1i÷'—‚Ë}ЙÃ.\¿…q?1íÔêû‹¸¾AŸ ¼ÕY²Åîý·ç¼~pÿ.kÜÏL8ÆT|sÔ·àÙ¢4X¬ü9æQxÁ®'ÑP»lðÉïÿš ð.ÙÿGlÿwèåb‡žLnùß¼ù ¢€G·nÎfp¸¹Ž«öcí‘mÇæ]xÄ8 ~A_`ÆQÎ ³7ºfœÆ\v$ŠºH#(1Às…lxÔíRÍhkck}­:çÒæº‡yʳßm­Èj‡âêÛ´î^8›M_LzhýaÆïÈ;—'x+ìqV ‚}tþÛÇÏ|Ðî$¬;ˆz[Ð8¶¾’–4™¼}Ý¥_´”X!¥ôJJ•5´tض.¥Ò©KR×¶[æ ûÀÿ v|X\?¢Àƒ¾ò‘ù É[ £…o‚A {t½ÿìzxðÂG! îçïƒÅ„×5.¡ót?=ì\X›¥>°|Mc¦ßçýû«HÍ—®SðÙÇ|@AÀ/ßýå/ß{ï=€»ºw„@—ܹysgçÖWo¸yóælôðá\°v ?cV4ØÎÇ¢ùuG§ %A8ás¡P9‘ÇÅ{1à<[züXô¢‘ iC !17òaÅpÙ:÷-hO1zÈY úN­œO`ТôÓl²’(êÊŠa’­)JÙÕeÔ‚µt*õ“Ïsu©®f ºé6­|®PWÈúmIZUuEQ3)Û•\YwVå&ÂuͶxÈsœ:…úöW_~qÝý³ÿãÿ<1 x”ð¸A}û ’¿zµHwÈ ™J€µZ¼²ZD+0Ùœ¶ZA«Í£ïqðHÖÇmAMî3ýÙ ¶6¤­ÚFq­X«•6Ö6Û´#©ÛwÓ¾Jë·÷ûaÏwW.œ»@î¸KÆìI—È€{àZ9—‹B€´ÄCA…ué´fY 8ÉnGNK‹î"²lÌ ÂO§äÏa'³]Ÿûˆüùv@ÔÍwðˆÈUh/Öû}t õŸ·îÞï»îýö½·Þ¸Þ »îýûoRÐíÞws,޶˳ð2ÅbgáÙ‹1îž›/z&Sу¸Àóeëó1À'}ô €Ì=÷=œÀp²=B›¯Ý|åáΛ¯ 5À}{ÜdÀÍ÷³…B(z˜ûsÎÃxLF̼Â|»‚"|,ò~ ä6ÅG§?îC„C®|°ä9k‘ó0â²upŒYŸåÓ˜p‘q­œËÐcQ.•®©2¸üùe£ˆ.`ÝJÉËJóË ÖóÍ|•u´÷QŒ¨hj^±$Y—G'sÛÐdê{–Ñ4TË«7]³nš–õõµ¯ –¸Çà˜A`ÖG€C3‡ûÿ!àw7îX½B@@¶’)”^ª•2ÅVþJ¦Ì@a?BK.¨˜öH$æ|f×â|:ãèÓææzµVZm{¥´^«mÅ¥¤‹çÎû²Š·!õ>8ð<ó?ag?öÎ]>'óâ^xÀd ½ý9<ÿí£ç¿}x<(Ç8;}d]×TäÅo±AõÙÐ4ÉÐÒ*&™°; GŠe)TÒéNwîm E'“Ϥ­%~qý'çGˆÞ >êîDÊïñzŸžY‡§ÿÖ»À~Ã}ã­“ãÞƒÿOO€<ÕJm(®‚Ž9HNJÇK× ò&̧Êr>Ƙù„C€,˜Á~B€?UI*2Åî*Òø’îY˜H|Nµ:ÔDó,,œ/dÌl.ïéj^wó´…x²”Rm»®Õ¿vßh|ñõ'Öv“¾¶ MN3ðÓ¯ï<€ãõ ày¿£!ÀB*ü÷€Rö š ²è,Öò±d¯dÈa)gÑD†ß¢ç>[æd@‹A{„„ëA´܇û‡’ÓúÆFÖ=Ã2S.ÞöSZàGH¸…èç눠Õb‹ARïéÑæß㟹A'z|þÇÇ“w€FAM'§Ÿ¥¿R)MÑmKwl×6Œÿº©IYÕu” É•£ÐL¥°†Àh[‡×G'_üÔõçG9÷‰YÇh/ ýˆ‹¢Ó¬S@ÿbÝåu×¥?…aäì*Õ*…¸ãg¾D ½t¢b>ÉC´ÂÀEléúìµ›·nÜ|mçµ›¯Þ„¯ÿê+7‘¹H°'^á;·nŽÆ·^£X൛èf3å1ß9Œb¶µÏ9ë¹qÌæÏØ£ç2³͘ˆt¡-6\~üd´ˆ¸ÝµtbFP6Yº>¬£6\ȉj„0 ”eÅ ŠN.@æ§ÙŠi®6›Ù²¢gW3¹f.—YÍ&XM|ü¡ùL"ÿùj=¯åsUeJ¡,ÅúªîZuUÖëªi{À€ú‡š¶mš_לBä17¨o!dËd®dòµâKùõR®F÷»Z\Í¢x¯ìµ:ao_ð}Q‹ödßïF]tÂÑJ^y»ÝkÇu2ûJ©¼†( VYß i«'çëì¹ï^’5AÁߦÀá…Ç#->ÌèhX ’C ÿ^ ;æôÕ4úŠ¢*¦,«äâ¡Ë³t åhÃG±v%!©š‘Nò h3h§4²Ûe!ÀÝ}NÔÁVŽGRû¤ûC%ð÷Xçý g©Z夓óæ™Ý©ô¦ÂBE¬F ÍPž\ aøœûæ¢ï|Šõ]ºƒù…ˆñ3ùÕÄ*™8·x˜ ºàÂêBC(Ãòâ?Eɱ¢#BËj>û™ÝÖäº&7,Ó%ûÿ´NÏ”IÀo4£©¥YÇ êTp¼é稑 øÁü¾ @™b–î –+­ç_*jùZ>·š)¯æ¯®–=¿Ý"ç¯bxÎmtE( v{d>m¶"òÑÃui½ õ×D«ÕÍ6tÿ,M>{þ»¾¤¡Ã‡ŒcêçÏJ]=ã#ú…æq¿#ïß'§;jÇQDÑw RNÚ€M˔ԕ$Eÿ†f*iŠ­¶çšé4¹ý–iØ­&M[H›iÅÔµ´dtÒ¡Ôåf^®éó|o‰.æ€î¢…±ß%÷÷»¯gÂB./þCP Ñ1É6Ks™ñ“Çܘ-T0±þ|êá˜Ç&ËÖÉ%~…"û×^Íw^}õyù"^†í w*ôºè]0ÇÉ›¸(·$WŒ›œ»CgÐt!òÉ]xÜ+ˆÜFF#Aß9;ÿTŒ+N(b2³/wñòdH52¿Šž#ô/h…¹Ëo:æ9ß“×_x}F€Bþó¤W²B^0?€ÝßæóÉdXÔ Vr+¹ ¡ý!dX…ž}Èæ~š+Z†U7ÏR¬m]‘M¬~»¾í˜ÖCò>ÑæõoÀà7¨o”W¯æWË™ÕÝe­Px©”¥ÏÕ|¦\^-_Í–½]òÿƒQ÷v[àé{0cü·T9ùºáVy£RY«Pü_«®onViëó,W—Îî^ò}ƒÞÏüñããý§ŽG¶iÿnDQÔ û½âÛ}NÌõ!Ò ú1È‚¡ªkÉ•´qkšFVob P‘Uô¹Ž¥ËÉDB³Mì´m9E‡¨†v’é´Ï€ø';cŠ4bØ>9îOß_ç©ûû×s™Âj·òT1È´–Lv9b… ôÒ¢yÄø˜³T'{ÐàÃcž|2«eëHô^AÊŒü[¯ÀÏ]¸¢Ú8[ЉÞàV"-ûã™è$B$ÛLôADçò™š“ÕDyìh(м±=?&X}}ŠoøŒ%K¸ÇØ Saü™ÉHy¶cŠáx(†‹OZ?áúà/äsÔù?Ïf‹Yæ¸Â¤[®Ñ€b¡üí ?…|ÁÍ&óðŠ„Ù¼¥ÊÅA6éÙª&S`ëÛõí1ä¦!+vó9€'pÜ ~oÀë?ÀG¿Ÿù²WéÎÊ™|•î€9Üo)_¡ï®æÊ(;AgÏoµ¡óGÛ\¯^¿ß1˜òÄlkk²ŽÕJ¥¶±¾±µlv|òtM9{~ÏO,‚öÎáñ؃ƒ'ÇßfŸ¸ñ>ÆþÉú½ƒA¯wuãíý=挘—ÂÀívz%”àü‘‘àZJ7\Ï’RÒå´¢96ÏÛä‰t "b›²r9`¢½©ú‰zí¨…pØ„ þØý!-Ð=‹ûû×Éë ³Ø› \`²™GuÙmžÉÔ™l—CNp¢…wP"+vò:Xtæ7^{e报ó§÷……²s—ZX8¦ïØá„wöÎL?¬î7ôƒ(8ùVq~øô8?g2ŸÈ@lÎ Óˆi±÷cЉ•Ì€ÜÍÄ!Á¤Á,oL?ý˜½ÿ×Oº>w¾µ’4¸s–¬¸×9<¾ÝÝÝ1Q̤{æ &DÁ€bC´¿DØ—{á~?Ü?88.ôȯwpE’‚”¤u\‹~Yôeð!ìH¦&)RZ³ÉèÌxÛz§Ó’ ctÂtªsØÅŒ„Cr„q'¦(r§ïãÂ1ØŽúGïïw\ÏÄlœ 3ÙCm@!ÊÇã7œDçBvËñD0vÎÁÕË!Ü2@"Ä.[ ú›¯×ýæMÏbÈvÁ2cAžìezÄÄp¯yÃæ_Ž„ZñéøçœŒ#Ù"4˜?}þáì0­¹=È0}L˜ôD¬ÃiAƒèœp}€)Ò–®Ÿtý¢€Rþ·dÇn·b¾{芳|AC±ÄóØyp`Ò?¥Ï\CÌå,m;Ÿo˜ªéZ¦¬6¿øÚÓÔ†®×µ/¾üʰT#-©ªçþÙ©]ï¸A}c8Þð{øõøõO(P”ÊWr X+ò•|þJ¦¸šÉ— ^·Ó! E¡ì^‹¶²|€^' §}ú 좮¿U¯­#ô¯”ª›äG CÏöM¾}®uyE58èî´{Á“ã¹É§ËÆ0qˆuàM ÿ&à úƒ8Š›Ífÿ ?›XÄÂûàývÌ õ]9•–X˜v­£*È4¥”¢+†¢@ùœ°Zº®Jé´a;ðÒi 좰‡,#ÿî':v?~‡'~¡|OÝÿé×ó!Ldò‡XW‹[hÄv8™'ôýîrÓ È4ÉÄ1:|ò:+†M`ÏÈaISŒ×BxÌNøxÎêz1̹7fÍo¨°‹ „C]?s¡70õ&ü\rþyúxJbX¹»á„y™±GÁ;;3ûΙPŒýÇËÖO¼>dEåJQ$È!­=fã¬Vår‰_Å «‰À3 P(¦‹èÐú‹\–´Œ®ç?ª×UÁÚW_š¦.S˜ioùÅ×¶iëó®rþʼnpÜ ¾}P(å3«ÅÌ•<Ýe¡´öRu£¼ž¯d®ä®s•BΣÖoù{ì…`}ï½=rÛÉY?„ƒ}Ú6k¥j±V®nl­uáШc›ºr~oÏ¿$àÛiwÛàèñý˜ö <B£“UiÃôÁ®C.@ìyÍb¹Ö„Ðþ€ÞßÛöáí;–á·÷$=Ý&›†ü‚ëZ~ºíÐþ¯Éä¹­h¦A¿?PÁQ€ ¡eº~)©h¦ãfPøÐ wÁŒ!`¸ña»óøþºqüàGÀûw(Ê÷wt=YÀ)׳íf>•6'ðòð|.syP =Õô›ÏL(àð,8÷G‚‰k6^ºŽ¼Âh,d¸†"ß7‹‰£‰h@¢p&.>=4÷©ø0áÙCPeÁÉ‚ëgÈ£z“e燿À³»sæ/ Q@¡î1a³¡  oC&dÄLÓáDDC'¯Ÿ|ýZ ÊâˆûéSmíØ«úøU«­AK¤„p ˜B Ê£~˜Í©vÓÝÖd ‚‚Î×_Ô5l“ºg|ùÕ×f\Óöž“|Q@à7¨SÀq°CiàØK€Àï ¹B%W¤»ËTòëå*ÀFi=[-^)fÊ2æ°µ‹9à¸×ò;]Q@‹wÛ~¿K8æõk×·¶J±¥ÚVm#`•½önäÓf¬Ò…ï^ºìK†¨%·ãÇ·¡þq;Dn!Øõy²6îÒýƒ}¯¶Q«zÌ(ºOîÁ   M¡KO)í®Òmæýð;¦áš†ÒI¥Û¦ mS£è߆‡€\ eëF:•HÓ/бÌP »©tsÍíN¿n¼xÌ÷»ïâÖ]nÒ甽H°æ÷Ò11øB‹›óCÑUÀù6úŠ!üåçgu² ;#P eypŽİÆ|$÷4#ÉÁépÁ,vòúÉ×' Š=¾RY«Õ08ÅÒ"XOa6g,Xß\«U*ä”K‚®,-¨&r͆iB/\U›Žýå—Û†"iªL[I£Ùp¾ø²iè×ôíë‡.€s žô5¨o%”3twÕìzic£öÒÚ&8L¡g´Êl²ìù- ›£6!4´wa¦´k!íͽííõõÍêF±´µ.!G’ýƒæÃEàÂwÏ]^‘ ôÚ0ÓÇSÇ£¿¯»ë÷q;èF ô^7êw÷ãÈ#ó'(”jÍýù½xõû´T¿%[„-a¼¿mÝèÐ~ßõåööŽaÛ¦eÄô>Ë1A à˜É•Dª­ S`˜zG’Út1"¿…/îÚ_N?zçGïøï°ÇðRèþŽ­“×Oqªuzº ^ý8€ lÄVÅr@œŸð°¦k˜ø—)÷àæ M>î‹/[gz_{–誉$þ¢>?Aºp&ÚöX؇«íÓCÆá¥Ɖ[xÌ÷qòùgr¼?«0“ .]_v}fUÎÊT . ²ŠÅsOŸ_@@ªüÓ<úsÛ¹ŽuÓP4ï«/_¾>ݼ\´‡6/zŒQR¤|¤è\ZƒpÀ§½³CsÛïí{V@UÒ¯¥5¯yƒ{?êY`¢ÝÞ4UKÑAãPH—P!Tƒ@·4þÑGÁð\“•BQ4)V$]S[:9¡$ÇQ M|-LàþâîÞy'ìç'\F ¨à $€ã£çž¿¦"PNä14´šÍÑ“«ëŠ¢ÔUYQÕ¦ ';¿ê}ýÅ—_i†ãÒãö¢‰ “<€o!ž€Â3!ä0ü=?Ø "²<àq]´ý8Z_—ÐhQ!`sc?ÞïC¸ïà ?èwºÏê²töÒ…VÂ71:@›<Ù÷Ñã[rŸãÛhDÇŒ4Žân¯×ë{ ²ô Úü7èsÕk‚w?ìE1j~é¡Á{=ù¡i€º¡çtŒ¶®Ä=¦g[=ÓäFrtYSMúÚs,Ë’’²¤èA7ݱ---µbˆ†a¯/î¯ûàAø£wÂKÆON@L_Òqb]týÒýGh ú< |tÝ3AK‹Á$³‰šœ”½l.“¿J@¥Zã†7¹Ä(ÐLÈ„ñl0{ùÓ¹h€ŸÎýL˜‡ÓöKÖ§ìL̸~ÎsCîsv›ˆçs¡'0æÿñh!í3fÙNŒ#£±n|8xÈZ_ü¶©h^v~–ç˜AÌð±hFžÐS8™ó¸8>çÂ9óÀí?„FèúY¶¾ìúÌzŠ4‹ºÌ·†$Ëôùç__CÚ™DÎÎÕ=ÚåmÛ û'‡ß4¶ë†ªË²"7„Múíê§axN? pºTßñw•6xd—@Ôîúíönî…HäuXÃz8ÝÍpcm­P)—jÕ­MPêăA¼š¿“uY]Mò/íîù>jÞÐwäø0Üít:·):è}¡Ô…zŒ:Þ&‚yèô¢};´·›šÕ…þ‡E¬æjZjhflÈè1ºm C¶*Iª†’ ËZAZ:ÔÃ@ ,³“‚ÐAÀ¤^}äó(&!Ó÷}ñéSðàÁáý±8HØA é=?î •Ä§×=KWÓÉKçÎ_J$S©$Ù?´+sW3ÜÐ…à»íã…ã=rÃÅ =là“AûÉëLûÿQ÷fÍm¥Ùµ`=ÛåªJ‘¦ƒP‹Ä{rKU¨‡½¢a²¢‰IåÀ˜‡%KüƒöOŽxj ܽ¾éõ&Xüâè®ptŠTbyÿõPkjT“?­§«©rM“, _.è!@¡”;y\”ˆ1›ÿûpˆ;È ›<@ù]pÿqàÿ8°wÝÔo84è-Ó°‡CÛZÐÐHV™ãÀ;8>lw»½N§{pxx| ¥-7€ô‡?®ð”íLæ£á`k+³½­åTOìÊþñØ“\€DZCpý@eÐÁ)ýXE•?fÐ> #ÄÑ$(Sï* ¶›“,˜’¯#Í—¨?pszâ ¥B)” ¼"’˜!Ÿ×²:ô¨&šÏ•Jºæ8ºž± nVc¯Þ·ÇØKÇãHÎý¿ø+ó/þú‘Tÿ±þâ¯âûsXû“àœ%žËQˆ,é7Ö+¾Ö³År!Ÿ«¨)ô½'Pÿ[àC·õÈ º  Wà‚•Ú9K…°Ì…06¬S1˜´1Q׊s8ÔçCjŽ8pê‚pI*²X®/Rkf!SЇÅ__P²—ßËM×ç ]È?²§€Šãl­ü [vÈå“Ñ‚HaÀûø@6®oz}>y  *—óóÃØF¾$E$ò¾ëv:­ÿœ çîÞzµý’^ÌÁþ%’+ÊùUÐüÿðñóB?ŸÏ¦%´+Ý'ø·sïs×Íÿªaßæ.¾éê@0òm €â`§¦åF¾çBJûàè ×lµ:Í^ï¸k™CÄÁbÖ’¬ËËÀ»àØß>0L-õàA*ƒžXte?å8A2@>ÞÉMÏu.jâ øvØC `<‰  .@:hþ ¾Sôõb…`Ÿ<¤KEÉö+âJTB i *@LŒAÉ$(I †ÏYì@:þ8»V8™˜)fïYõx¦xùßßÅýGà,ˆ*‰hÿ][¯Po2—Ë¢¨¥’‰TºRƒ€õ^½ÑjõØÌÂp‹œk¿Õr®rdÐàΕ\L%þ/­7®ÏAÉC-mö 1ãƒøwAQŽÐ0Ëg—~¦ C ‚ýˆÄ"À….Õß|Aº€õ’£}¯?%i!)@8Aô>Á,u,Õ ¿ð±mTGÝpãúæ×G\¤t19¨$“èÎç·\¿»ßn©!‚Z¿Ö¯nõku9÷Óé\&óó_=îƒìãõÇrþW>†øl²‚÷)þþ9€Þ`⸠4\H€bnGRxÃ=rŽ·;bü݃Öññ!A,îƒq '‹_•|N û#ã£ôö¾ÿýD.PR»Ø™]TÍPH¼ !Ë3ÌÀ¯<>>>:øOu{íär ¢ñÄ G-fýצäþ`û‡pqÔ0—û÷$hp-y(–ep} IGc ÜJAWÖŸÜÞú_Û© uÁªµF«›îͨ†< TÄcÞ°zœ£r`rð‰]ƒZtI/D+EÄuáÞ|1?¿¢/YÍ6^w^Õ2žê[)Ž?Šbª„…ø,QZœ®ÔP".2ݼ¾ùý­§1K"y (ºÎÇ­¢é-×ïí·Xø‰œþ` ­ÖjôàéÜ/ñËþÏu³‚þ<ÿq¾•ïçóÅr6·ód“¸æÚß V®8€öõ@ëf BTî˜~è›'èlÓôŽ{­ýV·uÐ<8èÙ® îË '£ý9†1 Km‰óØÖ¶¾ÿGÆw¿Ÿc—Ÿû-ÛËoÈ Û.÷»   üå_MïâþèL [>øDyÿbú£p†Áh4–/×…+¢HQb넯ï”ÈTìº+@<@»Gî*îc/WjTvÆAÅЫ`ý/Øõµ6­K€»F.RÍXiˆ¥üå*®âø#¶oNÔ.g%¾;e|±doýVAüÍdC„{\o‚B¡Ì(ª¦Øjdã%2‘3górª^Ãúæ×Ÿ’,\±…0BPÄéË…’0¸åúûf X ÚÉÓúýú–|†J ›ýÕ/û¿8ÉõåCõ¸_ʲýÇùþã¿?)V 'âž<¹:ü.°|ÕÔÛ¾} õ¨]¿†¨w®9€i¸äLväÄ6]ËF—ÿààH’¦æ~w¿òxTÚ '¡_oVbòR>§=0·’æƒï}×øîG‰<䄱?€±!Øw µËëq¿ü—žWé}ü¸tt€ÀÑÑ~ ô'“I(æÅ" @Á‘£\×Ëèñ»ªÒWô~$çüNi@€dð/Œ1—+Á„®üln{;‘| Yâ3ìdÖö™ø¦íúA41¡<0ÆÉÆboL‚pÞÂ98$®Ì'³¸CáâhŒ Fñ~!d:¨“|ô½ïþÁüÁ~o+‘®4Ù  "!BÕ±~O¸:ñ7+û˜çHø;kà4OÖ ×›ÖñÉGÏ üåâBMƒ¥®lš#HН[Åê1Ñÿ<…†õ¥€8tCgä/šª‰»®?WÊÁ³™KÁ<Ÿqt‘’†+òü¬‰z$”y¡@ºæL—÷YßôúH¤¦ÔN]*Aœ÷Ó‹h¾ÿúû½V£ñ[õ|UâÿÝj_4^é— é\?û¸Òœ+öÙ,_¥Üãñ9ºž¼E_(‡^uú}µïëÞþôMs÷ëý¯TiKRzÕôjë5€‰åÛÔÁ 't „-îKö3-ÄqÄ)˜ˆŸþ\NKhpnæ÷Íï ¾k<0&ÐÿØæ; +¯…~ÀÛý&:VPétZo7ÉæÔ8999‚eE!¦QÈ/=Wáý0wìÊÿ­ Ú³ž>ªH4P„KHˆKÌŸÉæsi]¢¶B±Pª”ŠZb;ù`{KKe³c§ƒ €î8 ü±9мI`z&FFÞ™‰º? •@$éòþ=uÿÑ$eQ8! xá¾ÿGbÿôƒíTP òQ´º=@fnÍ1žµÖùýP훑õfNŽÁUs *^$ÛÄgsk.§1pn5UòëÈŒಠÀ–;†7\¤=òBžg,ˆK›È”ÚãŠóW¡ŠP¼cì³fWdJi°»×7½>©AÖ$ C7`Á¼e®BÔn»>Tœ[51ƒŸäª{ðµz?W9ùøä'z.ÝϦ÷?>ÉëE½|rd‰žÕõñI¬”*wу\qÕÆîï†üwu-¹«êpØÞ»æ&¡|î]chæÐŽlè‚âiµÛ½ö~K;p6òȨት@ ›É%¶’ÛÉÄ÷¿7ü¡ýz°mšD¤ä4;Š<œôP»¶Ÿ[99.Ÿ©_Ožúý\<ö#?ûî…6`Ƀ‰S Xt) n`=;ÐNj¡• TDK9MÒ]Ïr¨ý¡vSÊl'“¥R’œx…¢ž-zNÒ°ƒÀ²lÛ¨ýzfè™ÁXbsߟZ¬Ëû§ïßCÀ4ia$·RÈç2â¶$øÃï~+¡e+ªõŸÖ0yš˜§dôØÉ‡€ 5·9ѯÀ»ƒ‹Åj|Î7¬ÃÆÖ@0qf…ï‚JxÁqc´ÃâÓy®F‘£Ã.æÌÐål}ñòüüB<8öª_‰ºÛÆëÏw/TÃY?Óoܘ*&ÄlÞ+jŠƒ…ˆ£ºsúŒx2éŽõ¯AdFU˜o˜CBmNtÑÅ´ãm×ïµ›ÍÚîîã¦VoVk¬êýÊÉ/~Y΃ X+<Þ©ŸäÊbÿ;â²’áî`²<·ƒ àoÿ·÷'W@ýQ훃ÜßÜæîóJ•F»ö¨~Åt[ª¼u¡í:¡X¥)ÀÐÁ̼Ö?nw:ûÍýfûx¿ß? X™–ã’Ð’@z.“ÎØ¶üà{øúõ씕"#Ö ä\à‰?±lN\ÝoNß/ëOŽòOŽ OŽºf³™ÌGcÉz ˜ôñ ˜öÙÁi²ž|» ñ@5?èJhVÊ(æ¤óyð`–+™T*ùQ"™HØ[nÂ.–lW\ˆfŠy#G üHÍ÷M@F%A@|ãû»X{ÿÞåýœYö'ˆâ ¹ýѾ÷Gß8Wi Xo$ÛÝ«3EŸÃÎ6Zç*qEmžbÁà [ì6#\‡ÝlÓ:'ÄTæŠbø¸)¸<”šÏø)¤È`ûªý9…ŠE{ ßzþâåÙ‹çоRˆ ‡¯˜_!r¦l[.ÑšÇTQ¨+ÙoR†€ b¿Ó8i¸Çúæ÷·Œ©ˆu§à’‚F¸ þÖëw›ÍˆÁ÷våÿP­ß¯=.íþ«_þªT)f²élAüA¹ß/ö‹åÇùtöñã>æLÑt’^éoþ†>@}•Øyºéª÷ÚßBPk·÷äÞ®8€C ®¤àðð,Ã0}pyaOkKø¿ß•ø¿Û=¶»*0ØA(Ó‘Ãp p.ck~ð]ó{?0Ó–2+ΟŸøL—lY¶ûî~Ïð‚ ø; ™œ.¿žà+•ÉMp–Ø€°Hò‡.a\üø`}'*©†`±ÐÒ‘t*+)€8¦år!¥¥’ßÛ~°miÛnÊ*íL±à¥L±mŠƒ`üH¿@ÝŸí¿½¿Ëõ÷Þÿ(šÀKMBÔ#$€ØÞÞóÏä+õO–zã’La×” 6ùï—¤ßãºÝ u¾±Ô…Ó™yþ†õ¥ªòp\0ŸŸqjVAågl=(î¨ï±KO$Âr¥è†<õêùù+j_¶ñ1»éúA̧K…IZ31§ÒÇJI ('µßJ‘‘CégA¢¿)'w7­o|}j.I’ˆ,=NL\!sX²yËõ{Ín£S¯ÿd«V¯iýZµÖ/f~ù‹_þêç¿(åsÐîôO>ΕQ ,–õ”öñÇ¥|ÿ±þCèPO0À¯ÿ5ö×ÀÞÃÝnû¾)À]®à}?óM‚þ› Ñîî>Ü»âöjj—ÀIW€i`ôÔí¶:Ín§#®âÓp‚ŵ/nB‚äq0F`kšù‡?0SòÏÿaÚ†ïZgúaAŒÔ9®yËþÀª /ŽVˈéäAöØÔ%ý"€5ÀØ—˜ä—¡Û¼SÔ¡ß.‡}¡X’,-À3ÁtÝrš•P” wvr¹„–|ÜN¥rÉD*“2]W/ØV‘ áÞõûóß½¿M÷?–h4AåQy-•JiâÆ*•š,`MɃ#2† ¶b³RG:Ly q0õÕp°Jo©0OÞ¦u ‰fœÂC„Á*ß”†5ãŸVq¥ž¤€Ä,I«~Jq 3~v&À™Øþz¥<€ü¶d¡`¾ÚtýÔ8<ßk3Whbài˜ê-°²¦!#¸_"Ëß´¾éõçÊbÌr¿ÀiÂwDxëõYû®Öêÿ…âBµúÇÅœ–ùù/~ù ù·ßïç×w˹½l?­ï€2ðñãt²ÿ8/?]+·ùô=©üMüE/Gößú–:€<À[ÐíTU/°¹c±È¡^oË>>:èµÚÝN÷ø°o‚Q? ^€™¾ålK‚áJÅÎiæ÷¶Åü+¦ïþ)X÷ð/ä¿ÈškD·ì |1‹X=©et[Í ŠÊJ6(óï`ÎûbY¡üËq; À \.b@7¥ùž¬ak1“Î> Xø¶–´·³Éael„/)¶½éþ6­;Qԣ܄žS_yyéC*Sîí5”X°^5W\^Pû^P™w‰Ó -&æ¬V£þ…ì`«eݽØ=H¸±¬8u—€¾*Àz™’ $ ý;th±¯IǃY™g§gÏ_¾ysŽ¿«R€¢'ráî룭ŽR$d:Ê9çõQÄl.—æJ0YÌŒ> ›×7½þœDÔ \ÍÈ{1uŽAQ­àÖëwA VßêK Ю®g3Ú¯$8ùøq.Ÿÿû“ê^5·W¯çrzÿDÏ=–ïãþÿõ_ú¹V¡ÑÈ7Ø~RêIˆ/‡Lû­øÛ¿ì¿Yû%›À7+û½ëjMx€·@ÔêÃꥈ‚P>æÆÀ0ϱŽö[ [n8Ç–ÁÛ©Ûé¹Jf Cdû“Юä3bþfÊQiJ6ý§Väø–ëÂØ’RG·ï~G¯ “žÜ–c:‹zÀ“'fïÄ0 q^9Ž`î¬ö¡ÃW`ßOÉÝÄ $pr)ÝcMPŠ\"™Iˆá'ÒÚvJs%Hɧ\½dĤ0Êûöþ¼÷Üßæõp$ÿÓO®²ìí÷z͈PTIXÝ#IøqhS±Cü¡&Þ IDATUš‹Ù–cŽæ ±]^ðnß¹NY-EÍ'!Ât©Øƒ¦œâõÙž_ªé_UÃáOSâä¾X$ÏÏÎÎÎ_ óF*@ÖᥪPl¸><À,¦Åè«j8PM1,”9Ïâ×_Æ#{±‡Ø´¾ñõç ˆÁæ%1Ò$˜­.:†·]¿ ’à½Z}ï§T ÛêçÓÉLþÿù¿Kº~Ò^­×úÕÜO'ó彯eIô¸^ÛKÕÐø‡få?7Úÿ­õCýI©ÝÛ‰£Ø<Üë6ö¾µ`¯ÑÝ{xÅ´Úûâ”pßBÉ‚ Ãt'~ƒhH8M@\‚®[½2O·‹Ý)ät¹f.“ÓÓ™TCž +­Y^©ä¤Ìœ¦ïMÝÞq½M÷w¯õÐóœ¶z¦?Á"/¬ù{rpÔª»µ½f;KU+Êzá“˸>æªã¼>éC«ñõA¡nÃ: 2颷VÆŒŒçUŠ‚ŠˆœP töç>òöi¤½í ¢3>°Ä\ÿ΃>®Äú˜¸Åļ f1Ï»kÿÁÁ!à…‚žÉkrü? !yû ª#®!û,€óG“ø» 8ÃCqhÁ’!1È?ºüÀ€ä…B:«—2…\:Mæ²Z&•–ì!¥iCS+”rùR¾¨oº¿û¯c¼ëøfÝóÔ¯¯ÕõN·;'Ÿ9ÊR+öÊÆ.ª,w3¦´*óg½ŽT–w¯$ŒXœ˜qVsþô¢4…pc"`­ŽJdf±D¬üô¹ÜÓéÙ‹³ç¯^S÷ˆ©Ã6_¡J‹‹qCY?ªy«Éãx¥7®¦£(R6¯o|}v׳¸bʂǂÕÒ)µ n½~¯ÑjüïUP6U÷ШíJ^¹÷StnwON$9ØBYìDâ€Ýê#ª‰ïBYü§{ê]n€î[ëÛT€$ì³rÐÛû/'à·Ä\Lv÷öÚoÀC‰É £/ïYÇÇàýjw¡þ»ßjõ:ÇpK+„îלûDljåÛ8*ñ›dÉ–í“Øö†6øüè1cgƒRSLE’æ O€Þ…ØD")i@2ù ‘̘.ƒm[òmà Œ©†9¡ ´„  ~“AÀyrö—ø%ÑAÁƒ¸VÌë©”žÏ¹VJËé%ïAÆL§òâ!¬«×—ˆ~00-õþÁý‡– X¹vTðpý÷ÿvýçS•ÄÁªõV·ËœÖËà ¼Þ„¯«n;,zJsRõ<Îr‘ŸâRèßU ßvÁùw×:'`– % ²Œq1°]+¢@q!/Q_ŸKðüù+ž˜˜ž+¾²Mû?xZäKJ˜òIÀ^9°0%}ÚJÍõrr]K ŽÊ¶ãò>ûzNžÓ)£&DPh5×ÕzsKrú½úîÖ®˜8L¸ù>¦Ý@úVWë‰Än½ö/HÿY3PEn÷Îzå°Ví¾—ô~e»(ºÝü;€f·Z»âvk½G{ _Œ££.¤›-ÐóµÄtl'ó¶hå.È‚MˆâáÄC+=qÎÏú¾Mî\±mßÀد˜8Âe¥Ñ :^[¾ ý¡€NÂq½âááa¡°SÑ‹§R8ýQ Håd³ a” A- ¦ðÉ(òÆš„#ùF4 ‚Ócq0Ì'»EL)êùB±Ör9«€¾Ÿ^Jj™A:£0ík †&–@Ly8|> Xô‡ðçåºï„Îåú÷RT¬¿ïùPŠA&Š€35`ËþZ1Ø1Ÿ./x¾Pðš1MXÿ¿ pFLÚAÌ|üÏJcŒ¸—›Ö9ËxFÒ‹¹Ê˜gŠŸlyЂ…=~zŠF:ÅËç /·Ú¸ÿ×9 ¤ªS2ûªR='¦¡4WŠå8Ø)T¢”`VDs¾iÿjª8ÆI @Fñ©ªWÐ4êÿR ªäëø§ý¯u±è::¸ÿV¬ïí"œ“ÿ¥ P½ÖváµúÍõJ·z +ð·Ï´»Õö°×i<êT¬À÷µh-ÉǶ-ößëˆC0CܸX°Xr`ú’ãOÙ¶KBq6öÐ-·ˆä+ò¡Å7€”&5º=* ›Ð膴†l±Ë€Ojì¹LR²ÿ[I Zà&. ©=I%œ ôGA¤Jâ&D’ádýï”Ë>FƒéÊ;eÝC:~ :ïÙ‚•Ó´”½SpSSËZ¾ü#§ú 壑¸šákóõÀfŠS°n[¿þþÃà…WŸ>¶dŒMkU޲3F²Jšä®nºw9SÙ:e­xâ-Ès/keàÁ¶aPïD;‹jÁ›Öø'ЂLdd\q¼¬¢Èà1[KvÐéé©TJn7îÿÐuPö.Ù±½Ç|vêUìx'Döã(ܱ?碧œj+ÒÝûÕ8°¢:gÀAâr¢ˆåtPkT÷ªÕ©V›UʆI&/߃ïn6‰U O²GÍZsºÃP}k$€ô¸¾^©7¾¸Û\uoöæÎ÷]ëöW¸æÚõúUÐlï6*bmýƒ£N¯#1@³}pÜ;<8<> Ž<Ò÷¡bYÖ´ˆ‘µLÛvü¶ æOÛ aÛb“qäDÑÈr ›7ÀÖ/4ºåË•´> H¬ïï”öë-æ2™,€¶hºü(D$‡»G¾? ½hH(àG“Ñ(W§(8ñYÎ`‡´ÀyÓÂ:&•0,Y@¡èf4ù†c[¹tÆ–¬Þ1‡ƒ×˜ Š€í5‡¯å”7œ¡i ïÖõkï߃Ƹ %ÄËçƒó¡îYµŸî1¨1X¨ª:Ö$õ‡U,€qSÔHñ§,°PŽžé‰-ù,@¹TÔ—jvwNålŽÖA¬ _ÃE5x# j÷¬UÓl1SMò)¥Èô¿¾þÜ´ÿC×§*˜_¡sÐ3e‹qv#ÊÇ$|ÐP ¶ð! ‰pè"-Öàîý` Z#'€ë`%% 5ÝÐc»•¬^Žh‡¢9ÐhBK¼Ö×]C Y£‡Õ#Lh4àzBͯÚjÝ\¯4Ú¿? Û¾æê=q¾iZ‡ô·šÝÖA¯p¨… çf ®YÖDTrwá¥mK¬.?mÙ(Ò…´ƒ5g4a¹\,ªÜ¶ ¨Òè%a¨ È—ËyÌïåÒéD"¹JlmoåÃЖõ¡$Ôˆ|ðò|?ÂQ€N€|…âh eR‚—0% € âÿJÓ@…\^"‹\¡TÔ’)-_Ègu#_ð²9ËN”+6 CìÝEÔD3ÌáPŒ04HÎ|Ûú•÷/‘‰ñÎó‘D`C~¯~¡ çãìPÁ8 og5j±Pþ žØ,R‘Ä{®X;H‡½$¯íL‰Pß)æn7­Ó逭úé,æ!P8Zœ÷³˜-€3Ê—J‡Œ\Zäú¹{ÿ‡®“'%Üçâ˜#Ž{îÎÙ à71õÏâÑÔp&twïçXÞœ*¬9¸àñª‡, UÿW±Û=µ:üÂÔ;Ü€Xt³%ÿ¯u8„zK"ƒV«Ýê€KL‚º&~îõJ£û^ð~óë.Š®;ÛöÞ~­û:€Ãk ÖÛmJ àt[ûûíÎÁqòK±R9¡¡­Ÿ* ÖÄÑŒÐÿÒ¶*c…@öŒ!&N¼‰ÖœÖ^ä{ƒ8ÂLq±F·ü-”+‹û½}Éàó:'jÅR·lkv"‘·äõ}ñ0Ž+¯îŽB‰<ÉúAÇ5F+0˜H(P„‚‡ÅBå@‚…ŠåB!i¼^ÒÓ©í”&.a˜IëzÖ± ÊÆp) 뇨0k9C±wCÞ‰oˆùß¹¿ÿwŸOMžOÕ¯R¼Š@ýrˆÄ|/ç?5ÛÉ-ñËÛsC3Q2É+ôBÌ ‹é‹Mâòª11,9x C‚ õ%(Šý—òùBIÏ& M<@Q7¬œÕµLr4‘‡kº†cLƒè4L× ä´7ŒÉ†uõþßÿ|pú»ªªüHqÀݱhM9 5¢áX–—ê6$ÑYqŽ•²ßhÕÆN„ñXîŸÃœ–€°\¨qœ;×™̈£YÏÎù…ñ¹ù*Žˆ×¸ƒZ!0|ªèð45– 0üv?aLËëû?|}ÆÙ]$p}+õ|áa²XµHû¹T]PP|+dô¦ý$ž+1*2AfLÅr/½”Å‘÷Ëzû×¾”hvZÝnš£°`Ä&o=´±:ǹ¹^iÝß¼Ïø¯º€ûþ8€æ ЪØÁñ1˜ÿ;­ƒC´ôTƒƒ‘ƒz¼kø´¥âÒ{áøC)ãýmÛ Æ¡ S’Œ Bƒ y¿ë‹1TEþRE"IÐ1PŸI#H>°·­D"gQGµ¾B$n%’Wa<¡8.’`Grÿ€@%'¬vXW”oé~¡ä»ÉíÄÖÐa@/x–­YÚ8  Šm8RþЗ· #–pß|nŒ6¬óý¿ïùÔ½ª-ÿÀú(*Ã(@Ž ·9D‹©UZPC .rcÚU\ÎO6ë{€Ë¢âÍÑá•âùç4ŸÒõº{V2'QïìüãÇåã{~®h4gjXAØÈù›…:¯áŸ@Gˆ åíþõE¶peÿ‡®SYDÑzP€T"*¢WÃJˆ8JL3’ï(¦R]}Ã~ÖB˜›TÌà (WŠ‚‹4ÃÉw:û½ÞõëSyt-f-ÆÞ»ø‚cž/zo¿n®ÿ^;€ýz«âhíƒn»}Ôí£iï¸dþò"Û–(ÞwMg|؆$ö¾ëc„–€€¸y¡¹®À7–l]¬4‘=æT صüÈõÜk™¨ï»å jÅ’$J ¨ÙÎvBGcTâreü¸ÜAà‡cøÄá¢áh8`x@JsaF¨²#6_A+Ð ¥RàŠsq0f¨™NÊ-–,+gåRöªT‹ŒáÐ0-?]ߎ³rÂ>6¬ãý¿ïùÔ÷jµÀFU¨qYPdÅŸƒêþjQ2Ã2<3ŠÐ<ëŽ.Ò9ý£ÑØ&ŽÒÄg ßõ1gàAÀ4¬P2€Š\ù²dìi’ê‹àlÛYyýPÂ9†KÂq/\'ô w,W‹aøòŸÑd&àRŽxŒ BEÝÅ7è‘S(eí”LZ©@³½’izº•ÕìɉdY¶38r ;bàÁH¾|Û2µiÅ¿÷=ŸšÿÓº]× yÁ›ítoI¼,ô­Y¶_›÷ÁÕ„I+§ê;£ àŒ bh$ð%ΖÂ;K6»6­“CùòùÇýÃÃýîñ"ØÃs˜ ‘€l<Η %›Á{ÕŒPÅåþÙŒ°;”ݧŠ^—û?p¼Ý¤'@„Oéþ\.ùFæîA Ì"Å¢^KŸ— A¢£ Ù–a$Ý‚¯»š§õB&en»£ ¾¼9ãMÛ4 CÞG0†Bê`øÚœlXçû¿ñ|ê5¯¾'ϧVˆ©ÕÍNo½dΉ<9?z1ÅÇÍ+ºƒ%xëXx'bx1S5n5<‹ès•«Q½‹4þöuÔÁñÑŸ‹ýöºÇûHbÏã1 ‚R†$<2‘ýÏùq^’‡@ígž `SÌ&ëõ¯#³G&¿älÔ\£ëþħÈáE4øˆ&ŽyÁÔ€tiwï_($ðL)…Ïgì°9Sb¨®?#t ¨kbY\ :øNWä*½¶þoá>ÜüïíÆ×@·Û¨ôŽZn³spløL{@¶~£j}F$¯ƒÀÜU6{‡UÁÈ‘ð>òØ&eîØŒ_“0ùbÊHlÛuÊÅb¾×ÝG±.“• ál'SövBsÒyÏ„²€=ô'ˆá7¾i6üŠï‰_ñIÊåNF~QKÙC þ`]w‹ ps…ÒÈKYVÖ* ž*ró’ QI¼âr K”o›ƒåú›°ùMë7¼ñ|ª5õ|(6Qô¨–À40eý¦Šßcª,|ªô>13'Ó?ôƒÉ¢Cú &Ì4h•,c æñ#¶Ž§:ao[?½ø’:ÿáÇ'zÇ`nœ«Þü…²Ž‚É@S+f"Q'ëåÂÍL9r‹vÜZqñS¸ìƒÖgŠ•l¶¤`òj;Ìd\.P´‹iŒQ»g‚â¨"E`±aÿ”´b‹é\¡®©Ë@’°øÜßt}¦ Ša|½ˆqF ¶¨©ðκ„пOààšètk•£^û¨³ß9<F{‘æ;f„"g"ÙGḴ]ÓÛ Q±#¶y`ϲL7ˆÿ%\Ç,ʃ/À¶éE@ç…@¾²9piNÂÖ–„™@l. ²@ ?’“Wq\¼c/O$/Ÿø”T*@d …|h©è;iðJ€‘÷ôR¨Û Ëû]ïÙb!›/H'â;"DôÃÁÐCwÐ h…¸ö``nZ§†ÂûžOÍV“%õ¦† çp¬Îª=ã€>dpË•bÑ_«Vþx>Ês"TÆ©´3Oªö¨nׂhâÛ×O_ï˜|3äôGUP~ º-2Fƒ|µ@Ë”ã~ý‰œÕH G€ØDbÄ`²m9³u J¡ò—Í¥3@‘ •ÌP ä´õ}ŸS öÀ å’®Œ< øa(×õ-ê‚3Ì¿’@7ø`´8DÅGº3´@%än'¬rÁ4ô‚“¢}C Åõ Ó ¨2»—¬ä膹iÝxßó©60£°~ÕÈ­"n¨ã(85oƒ ú…âÐ@æHlX¨”W5¹Õ(ç`ÅÞ9ßGï;×Åð1ãwöêììü±$òÕ=>èýyÿøãsÆ›SãwÎàšC€¤,Uš{sôߦ$ ‹§ € &ç'Ë”½ºþæ‹o¸®¸çŠBh1‡$Ál®”ϧ Ö?YñL ZBɬmÚÏŠó x_o¾ õ¯Yuqئësv’ž- œÆ{ÇpÀÒ7×ïŸ|+@ç;û7@¿·ßjî·úŽgEžkØø€ŽB½úZþNàF…u\É0? íç·h[‘EI˃€&}ÉÚ k ñP¬5M&Þx4CSØÅ…”Êe½ @£¼ZœšŽ•L$mWˤítÐ:`_!²B;$Y©øŸÉxä¡À Qq; —Ø_/ïhÙ¾ã•J;¥bÁ*‹AÁLçÐô¶“ ½\t̲§a®`4±>s\דÃõÆáðõ¸dzÅ`s·¬û·<ŸšW¯ïîî1èt{j¸•,9©NUqJ‘U’ f¡ò‹¹ ¹mȨ3A½J*kÆf$°-¯w¯Ÿž¾á—DOþ烓?Ðï÷ÿ§^çð|¡jëkbÿVj¾˜ƒ:›]’s`¦Jêüº*¾R ãqÁM­Ã¼ÁÒ\ 'Üo}AÂâè~1§d!ÊSÅ6ÎBèbMBYW¼fhñqîiÓþ%ƒ­õÁùúqÙå»{_?æ#å:ɕѢ…°ñlusý÷ÎônFíÖþ~«wpä{‡e!ŽáÙQà²ü'Y±…q_IämLûƒ.°‰9ª‚膇h×»$ÍBnl9ƒJ ½GíIà#+Ó¼n¥^§Ãù}]K§ðËu“ަ¥ó/ReGó¢Ð`hÛNM$ôÅ>á\H ZghÁ…x çš©Œ_ ¸SÐ]/é¡54u‰,-¹-‰‡îz•”Ç©bã³PÒ€áÉØôsCH~Z6×%߸mÝ»åùHðÿh¯º·»·GÀŒJ7Ĭ±'Æaõ•¢·PÌy1òžÕ·Uɪ¤•}‚5å,f d­Rá÷bšð®õÕéé+±ÿçâOþÓŸ‹Øß?ús8€åE+Ž¢Z$÷\°XÆâˆ8Wj`˜…ùùð7ˆXtTo×gTû·¼¸ÿ:{rKU¨_b}Jå¶1X2YQñ‡IÜ?çèÄœ-KFñ ¨Œj IDAT›ö°dz€õǘÌ#öÙt}e .e ë_,)‚¶àE®­ÿÞ9€ƒàˆ ÀÎáEOËamK‚[6û$¿†L¾’—›¾‰ž Qd±É‚A:`|Ú†$ÅŽ ,”xÇ£pXPf$ØÑ¨\.R @'“G&—N¥“ž¦YZBK%²v$‡€ú "c­ÝòF`ãÉE#Ä%Âàâ Å£A]^5,ê‰ÿËò=?,<†ÅÐó4Cs%ÐrÅÔH‚‘ÈøL² €þ‡¯‘ËË;ÅÁ€Ê€ëãÑ­ëï> Ë­Õ½j]\À^“¨}»ˆÛ~ä%+ïœ\˜Sýn– Y’§Ð9½ÐD_Î1¡ô—¬þ-V›ÖW &âúÇ''ÊÁÀÔ%h´Z=6£€Òb“,߲͡9Ø¡¼[C2}ñb~„õ ^õ}ëv,szËó! ¨Zk´ºéå*8ú§âj"–!,ŠG(M“ý—…7~ Çvf¬¦¤S :‹ƒh .6­¯ Žùäôôñ±®?=Ê?ÕûÊ|úÉ~´z¶X+Źâ,TšÛ¬QTˆsû U«·À¨g°FöÎú›Ë4ážëL½¡#xs‘ õV 8|s6ÍÄeÃ~æóé|ñžýjœêîë#?ciöæú’Ú£7×ßußÌpÿÃ@ëÐÚou÷kª§Eö[„^–Äö~ ǽm¡ îpæK¸b‰žïÜ»áaIpœ—hŒ‰3@fŒà?ìHï9'rrôL§ÙÉ1¿ŸÍ¤³^Jsm'me3:ÄI$˜ð¡Çƒ¾B„ž›ì¹9d£` b©\t<‡®ÙÓ]ÝôB cX³°Ž;ÛY½ṪºårÉb¾RH'1Rì ÏÐÇô†ãóáЃæ¯e %t‰FÚ?îà}ë‹·<Ÿºý¼Q5ñíùíWqz¹R£ª3Ìñ­”îUÌs¡€Qd™]ÄÉr )IùcZ‡´Íë ’gÏ>ùñ>ùÑÃè?yú£§¹OsŸ>}öÉÓÕ³5Ø‚Ð@ø:ºD¸ÂZ’‘BñÅî·òá<‰¼²~¹|!.xÏux;Ôn®#rPb$_ß\_"p_,7í_€ãRï¬Ó×-ïq}ŒmÌÞYç4ÃÅÔÖÛõë ö-tµ»€Ömí·;ÇûhÑP6Añ%1¸ƒ~À±7%‡bŒƒ•ÔÜ·TUЊ ×# ];tÍ$lG½\¬?Å>Z÷ªw7Є¨V¡XÌHPÈæÒZ:g™š“µµl®qQG¨¾‚äÁAr¡ä“sÅrÉwôbEò}ݳ ^ÑöQQà·¼„¦IfP*ÚZºPÖËž^H¥²-«—õd^2ˆA[ûÌÅàŸ1v÷%࿺:/·}˺íÞõ|ö„@S a³¾Ïª˜š‹ð=e€¬-a0D±¤-„€œ%Îh̽®Ö ç:߸¾x6[‹µ?{úãÿøÿøGŸ|úcú‚?]<{ú銜šà[Ÿ Ý=uü³$ÉI¥‹›}+Îsð êÏ.Ö¯ÛÆï·®ØK¦³wÖ—¤&ÇÓ À;ë1úfã~”H–Óù»ûX`Óõc¬äú½ëìb^_¿îÚ•oj¸÷u7ö®ý×@ûºhÞp½H™ìiE¨¯|´ál°üÛŽEr|¾äö1Å>#Ú6ìÀô#àãÑÃt¬iMìZ`Aå@ܾ%D"wV¢rYrv½Ójæ¡ù‘Ëå2nÖJn;š•µrÇÁ•@<ÂÓO%X(áð59@9  XöTá.–"\>?;;»"N ˜‘G­OoZˆ,ßo}®ÄOçïž°ðŽx8Œ†ï=ƒoØ¿"Çébùn°TÌã®O¸Q7×§”&šß\ÿ½w-É:Ǫ§å˜ ü´%ÆIìÃø8h£ß/§¿ ZŒ@‚bG~ŠTÿŠŒú FfYÑprv¤ˆ|äh¼ñ¤üuy'ŸP‡•êÙtÚͧ´“#+AGR +B_!R}ÛŠØsCuUÀ¨ì&§°³S‘¢ ¸å zÃÅòŽ^Hk6Å­m«P,É7Š-‘Ndór“ ø*m¤‡Ÿ$0-£m¯ÏMOrË2LGÖ=' $ϘÀãy`¹º~ëó©Ö­ú#@õF+ ,ðBeÛÜáÜ:ý¼A!nYm Ù„²ªëQ¿[©ìP"Hñé"®žÆ¾aƒóÐ(/ðl>—ß>}Š¿®ž1J'e'ù?èIÖ·/c,±>{õêU,~|:»âõ"½ÿ«9òú~ë°Ã9Ó›9< $)sv–7×Õ ½Ú´/Š"äË›û§ 5Ž}÷õµâ„ôÍûG¤âõØÔ^›ªð»˜ÿû]ÀÕ•÷ÿäm{¯9€NcC2°#=-êߨ¦ªØrˆG¶E†_ï‚ÜÐ/€‡}`³Þf<âvÅZmŽœÛFrùÖ$‡áò>“Q4)~]þ*_@~dÒ™Œ“ÔRYÄÄ*"ô"öä…#Ó÷H9 ç•!Ü[¬ìì”Åî]# ²®Ø¾8 å ¤Rèúåõ´ãI/”ÜìVFÓ2lCzÖ óYû³È¦­¼õµ•¶¾‡r¨»#°EŽ“Ì@RL\®ßú|ê •­“4Ùîö8ékØ™&3ÿZ©ï­•X0¸Â(ùƒjU.ñ£8½V€Ï/•T6ËêœV\`ÖW«g²ø >àÓO>ùD~ÿôé¿úéŸÂ¬Áœ‡™ÂÙLMM)G@ý9ÝÉr=–šüíÍÅNÖ!®¯–§§çתäoV÷\'Áb}zúêú:õÓ˜Žê÷ôôôÆ~´ãØæ¼s?'+ä„y~c}Á  ×_²9›¿¸¹Îç¾zg]@oÐzmº4¨½o¡Ø»î½ë Ýì¶÷ÙÓB±ÍÉ}iðƒ‚İà‚#3Šä¬5ͬÀʶcVàлÐÐõIžŒóydŽl9Áßóäø—¿®|U,¢Ëæõœžs³;einÚÊÊ¥AjÄ3ˆû ä‡<¹±ÈA1*œlB€å œŒç¤| ùK‚t˜«¥@\ƒïiɬiá’î¥ÒÙT*+'»ë¼nkŸ[ÆgVÎÊ;ò¯•5 {øÚѺ§IÊMHD 0kh Í+ë·=ŸZ­EU4.)Áö,Ûr®Œ#·€ß [Í耔9kÖ¿‘RÆG!ç©YÇcn(ká¶Ú´>[=cgïÙ³é qÀ ü§ÿ§OÁº­Rðd)á’)Õu“ Å]R æqŽôT"]º–ñi`j}%ë/NU’ ¾”ñæuy±ùt9\ñùÕu5¬˜Tåü>{qúòåÕuR˜fdÃ~:ÅÅúù‹—ô@o× "šm¼>E’Öë³óç§gׯ¯èo®W:GûJ†Ñ-à>Æ›aov¿ƒ¨·¯;€ýN³£zf(i®œë¦–$·8Ú!Ž'É7‡€£@i˜–å¯lHxË÷C‰ý-Aø`¶Š.˜CÁBã#ðÅ~ûõo¾Úù !@:FñzÖÔ2ÝɆ¿–£HBÐoþ+ÿ¨ž[4ò1OàO²9Ï£Vp¹TrŠ–ã†v&+6^ÜÙX(šš ²@ÏKmÙŽ¤ NˆHk“H‚w+5Ô^> ¬¬õuîuªÑLC|? ýø€U¿ü¦`6 \×Äëâô‚[ŸOj5²Í ´$¬VMÁÎêÈgÑg/u»Õp°Jš‰0—?´ÂX€Ã¦±˜N͸Gã†ï\Æ:Øú™¤ OñõìÙ'€:ÀÔ;°òèÈ­)†a­‚â>Œsç¯$xóF>ãgë¥j|ñ „é•õó—°À/^½x±Rh†{­£‹9›Ÿ¿8;Å2ÃP'£4茓‘sºËӳ篞¿€!ŽDâ¢)»w³ÅÆýhAΖ§ÏÏ^¾xù‚¯ÿü9QŠ—tãõá–ëÓ³W积ÔõŸ¿˜±;ºÁÕ;ë•nÿpµÇk¿• {=ht®§F¯/¦zÑÓ20î¦XoÈã|¶Gâ 9õ 9ÒÉømšèØÍeµÏ À€|ßÁä/)ó/fE‹}]8€¯¿þ-(²Y1ÜrÁÕܬ“µÒ ¿ü5®@°c…ªåŸcc’X¼ ŠE[‡²h©Èö_>—°=+¦Ðü*2)Í %ùw3ÉdÎÚˆxIŽ-“M¤ý`±umP|–oç, r¯³’¿˜¶áƒyD"ý±j"Û7q±>'yûó€D}o¯¡À‚õ¦¹âúZ ²Gùu|Ö8w¿¦¢ü¤ÐTS¹ ÏhÖqT]±Û È­›Ö1¡öŒxx4ù•ˆæb¨¡3}ÆÞ¤* `.Fu+ XX"þŸƒrc)v~þâüå™|‰=çÈ¡ôØeýåźøYŸ‘pë~ë„ Ÿ¾zñòL¬PÖÅ”˜1›Ab5{ñêì\ŽØ/±þüŠ~„J¬H©t÷~Lù<áÓ—g/¸þj¦š±PÑÝ×_c|ãÕùÙóó3FA²ÿTåVªŒ{s½Ò;éû‘?»{õö£Êí¦øo÷uÓàßýºêµë×Ú€µÖ[>¤­îÁñ‘㨞XýÉä%öììx†ïÅÕ—¿mƒè¶m^Ø6ê.ÎìU±À‚læ.dµ}Ç&­Àx2ö¢¯¾þí×_ù5B9õKår)çæÜðËÂ×_޾üu„vŸ‰~‚DÛ.û ì¹9i‡ø+é„;ö!X*ê9 ø½¼îê%–K;åRÁÒs^:ï i^šz¢Åœ–LÅáøQãgõŸ5ÿÜøÙÏ$ûŠH¥S­´ä3Æk‰|X*@~9x‡¦­Öƒ‘wûó©zn½ )N÷ÈóÃé}…]¯ebó ¡Ÿb¹œ.”t/…ì‡ÏAl9CE~ÉÙ•%¡:Š@dó:’„§šÆ§;ûSE÷¹z¦° ª3Á¾–âЪ€ä=$ X/`úòuvúó…k:ÞÖÏÎ/×ß|!ëëo° ‡®XŸX,èÅóS–2ÉùÏùþ™œÞ/^¾|.f,~öò•"K„…¹p÷~x´ósIaΞ¿01lÇ)Á±•£k Vhù´2l9ÿ!f}Ù/—Á~ǵM×Ç¿óÇ5óeéKrè鬮ç _~9ùrò¾>@v0”?+t€/‚šÐåýݵºÈN&‘?¶`$…ÆÊûcHLÔW:ÓÿÇêw›•É RVÊÈüI;m›ÃëÛÛø)°ŽM HàŠrþc]¢ŸapÇóìt­VÝ­í5ÛY…µŸ!Û¦öµªo¯)aCÈ-Õ2€kQsÁ 5 ·1Ÿ/=Ïl¥ô:Uûój³MëL¶ xöìé³g3  qðìéÓ§SER´R CEF€nf}X®À˜8œ>—Ïû)ÎÿòuJÍí5§Õúêù©œ~ñ:»c÷]‡:¡ÜŽÄ¯ž¿8{y~úüÕ2MDãcJ(ˆ¾µÉéËWgSj‚)%äõ¦ýÔ˜Î$D%Gô«ç‹0ÏÁ.¦c®OòÐõü•쉓^I;­§±¸¾^éõÅ|êÕ½Aíµé^ýû˜ÿfpõgÅ´wV¯8€ƒ9¤®´Û£#Ôì úl¯¤¸dþ—Hßr¡ìùCßC'¤ h8 ùÒ 1J±·2ІPÝOŒÕ´ßèäÅš’L¢¯&Ñ—ù|^KM0ùMþËøõ±bhÞܨ"b áòþî¿.q}èÁKÁ˜#wøLÿÿýÇ~&åXíœ™Í ^§_·-ù†¾–2 x@‘\c4’œÇ°±>@úûó©5<¥ بwºÝùTµÙÀöÉ!3Ö‘@¼©P¿`©›1%Ui¸âçÀTïŒk5+|I²RþÇÜÝë VG}pÆ&Àì隽ň©>ã,†ÓqP—”µ±µ ö¿Zy5¡ØE^¢¶¶V5‡øú39yON|ÿuúZàùK¦Ö´J¥æ­ò<©õ ìf ?Ἣ¥Ë_lÚOu@y/ÏÏžÇYÿš”xô˜ýßx}©¼’$†½›f1w˜âK¸¶^éïw½z­Ê^`ã[èíΣ‡{oÀao¿öèŠhõŽŽ@ù)åÀ%Œ¿®áÁ°€Ë{+ÌÆu%@tœmû6ŽJüip „|CbÓt|‡“‡.e@$–PV ƒû­»ö“#±’uË à‰#ySãÈ4 ǸrñL Ó Ò™×)£­5É×é–eHFŸ$?·0½8CÌö'˜b mÏ0dÝCЦßòþëþ^U‰ƒW[­voFÚŒØ)²›~KeĘŸÁ(-ˆ"¸[+: Eô5›_ôèpTo¥ô6ì_²¹8S‚¼ØHl,$öb®~k5{[W\*ú½¹.ÖÿòìÚ:û¨Ä‰p”ðý<Û``Ë[ ,ævÖ½˜ûFÍ—[,T3TxÈq±¢ Þ4œÌ]Å{ŠëÉ7Q>Á åÅó•#üÎç+!Âÿâý•ž8ßm4Ù ü·vï¿Ú7u-x€+ ×«>ª¾MŽ÷$ ö@ü'Fé¶ü=Bz3Ÿ„£|ÃÙ¾{ÝXVa8?ìû6çŒ]ÐìalÖLß Û Å¼-Z¹ä•ÆBëƒ÷£"Oô -?ÍY~±ÖÈȲFÆ(š7“éËW&áXÉÏZéLæOÒF²a ,ÃOž´P§ ýâ3ö'“Ç‘uÇ4lÃ@âýï´Àpò¤ÅP {¾Žu¸È¾à00Ä)ØŠ?Y½8˜ÿ¥LÈœõÙ’'çì‚¡o±Ü´ÅF´€â»$?ž¸ ¹Ê‡0ÁÀäÀVÚ32¬æ×ùž¨–qvÆ%ÔÞ á%«~ù‚R'$)x'Ä–Çrv¾à»\_±WŠÜO|ÏK¥ð¹Ro–‚(K~ƒVºôØ %È,Zª!X¨ $(êÕ$ ,™(ä©TF õ<´X+Åoœj°¡šð¦ýœ2hu½œÆS> U¥%a°tO`*#;áJ… b´HÑï^gÅóÈkåædºQÄCYmqÆãR‰÷Ü(²AÈPÖ—Šàj‘@j‚._bôq:§ì7<̘Ÿ¦9{AÝ¥u,®†’ ‰˜”j‡\ðži©8 Ð'Dïêó=ßð|ŸoøÿÃýŒº‡ò1FÏà¾_ׯ@°×èì>ì\2µš­ÎÞÃêEpÿض%s¶,@ß1àâò5ò·ßJhI´÷íìƒ)®?H$3o×%*w9.h;~HÛ ˆ á[ÔÑÑfgá bzœõ•ÿr¢Öøœ1¿'>äíGÙ!ºh`r? ž9À ÄHDÇuÍüàk³`eŒC7›Íör¹ýB` þä³ÚŸÔÉ?IÕ>7‡¾¤Ÿ†¯¡>4‚f—Å„®5Hl4´shƒ'ëñó¹Xwk¾I@µ.¿7ZÀØÏL)b+åêwŠš•ù•3£’„)õy§$Öž-È®TWNMÀ.âOë†ýèÈ ­ã—]q°Z)FN˜çŒ@"¥@Ñ †Þ Eé»Ü¸ÎRBÜÔ\(5`²œ²7yÑf[,iÄÄðŽn´ÙâМ,Ás4.Ûlü!%Ú1ƒy ±uÕ)üx;pÂÏçŠß’гA"0†d³_¢nI¡-Ÿì®¥%¶“ù0Ì$’ÉÖ󹤖H¦séí×Å2ý m bá…í!a±ìÀ– ¶ë9I7˜&©žë˜„þƽ÷c9DÀ‹®îGÖÙ¨ÖÅæ‘ç¼aú0£[YO7u«äùêú IDATD@6 8Pãg ?ûç’†áÏ)dнGè®1à8 xÂAf\®›î•u˯K°[¯Ö9 Ðéô`$ '—ñ9¬‡øŸ…"XðÄáéG]¾9u§+5óCÎ_%@¼bXù@®7í§-¬ãdN )È/UmÈ¥†[Œ*Rt;æ)P8Ùµ¢Û¸sŒC841s· [æ4Nàgñ`r¸:0 ãt“½´!Iß‚w=Y8Þm€LÀq?å‹ÏÔ{!ñZÍ>+Ä´e±½Tó} þÇÄÃ륒!_Àé@p˜Ê?jûŸïÍýæë£ýý½Fߪïüïí*Žùº6Ø}Xïî>”H¥ûƒV«^Ý«V[»›Õ^»Òèõ]0ý¨ñí„g»(¯c‡³¤âèùi[©T¶\É%´D"—wrÙTRKjùb:ñýÄ6º‡0cÛ ]Jͧ‚aì[êüC$öí«ùaƒ\Û¨ú…CëîçD…Iãý»öhäâa,L8»N˜{êfÞ.dKºª¼âãbñqáÉ“4ò µןێ·…‘ Ñ8’€C‚þáºåP`„ò¾u¯Zƒ2p­^k‚¤»¯ø5È»Ã8:þŒ.H-ÍÈ6ød¡º²J²àÍ'š}©ç<îÖK5I·šnÚ?›©Wâp ¾ æ<¿xbs𕯬¤°Tø¬æø6®¯Ù/DEG"Êz&T ƒ°!Bmgl·­8Ý‹[¹ µù6 òKâ Ô ;Æó9ÇfñŒd€ê©û^MÅ7RÀÛ µäµâUV´¡ªéHÎDñð(i|è󽹿⚯›ƒýv=xøp·ú.Àw…r\=|¸Wo‹h~çàBè@«>|Ô>hWö{Ú!pmîˆߎãBÄ3|À ßrýÔV*™Ê+;ÉăœVÈëÙB:Ê狹L*!q&€Åð L´ÏHž-§ûÀU|Á R:?6Zjáž%ˆCÜË@x_¤÷߆ñÝ aRµß…z-o@2 ׋‚h`Bð;›Ö|î0kL½u iÍL'Ói´ò¹n£ÕÍu3ýzýgÿ•a î +Fãñx$nÃ×±ðÀ}kªð÷¯» KÌ«¾µÅrË>Î8½1 7+rjh_ÑQ †0¿dšb-„VªÜ<‡9.z` ÏMûY?Cô;_Rûƒ"3rBRxÅpW>¾*+™©aDdÚ ÕØ´¾b`#a¬‚ÝF+1í8ñÛÀš§xDé¾;l³"šêLgW‡mV¼cžÝ±ô/f©BTÎcîa F/¨‘¸˜Å!Ûˆ<ÈýÃ?±bÏDcñÁÏ÷æ~e`ûí@ëN÷=ãÀÿ±àæ*#P싽÷º‡{ƒï´[’ìíîî5[ëÕN¥»xà‹±TM4Ö>Fð!Ñ- éD*‘Éé•LRÓ²…ÊN:—Ëd åR1ÊjéÒ‰üC Ø` #=ŽgÓ–ÙЦ›qƒ/X&'óäÛöçjîÀädíýöK¢EËs º!¶£ŒÀÒ …[®` A9ÈdÙlþuÎ*º¹ƒ´DAÍüýç“”vñ[+é1DXþw6ÆA_dùûðµ1°ì‘k@5üÖu0Vw40ŒƒGº`+2q£çEÖ-~ü}~\Âbœ;S ÞòÚÙ:Vë]‘ôª))ê7ì­Gæ‰<€™À¤‘ÍƒŽŒ"|—±¾­Š°M&%7­Ã>§t(«µº 0‘Ω¢Å©;5n˘™À¿µDóËéôƸ-‰L­Ñ#Uu5n‹8›´ _PÊ€Í1Âá<#îg6jsu-<]@,gDлÌ(ÈŒÄAe5*¡ù°ç{s? ̵ëµGHð­sáRwro{µVíµÙüÎAG€nk»{½JóH“¤ÙõÜ‘X;êû`þµ<‚w–Ø6¶Œ&H—+Ù¤D¥J¥Oe²Ùlr+=†)Ù„ÏÉáKp@dºÌ©0ݦÖÇ >1ƒí@“/HÝ—Cû^ûm Û‘äü®¸ÄA€(uHl³l9°Õ‚ÊÀpM òC=Sêf)À¾˜}£uñ›ÖŠLÿ_úÝšDùò DfãÑ$äüÆp€Ìß ?Ðíë{¨²ÐÍ.8¡º^¬ßvÀЧ`ž"ö]’ô²±Ä¦Ù\µ»f*aGKÊš2sU~Þ´zùbkŠ]R€ó¶ƒà9Kd”ÀíMgJ•l­J›Ö%ƒŸRVT˜óåœÂì¡ Ø·@ €|æ×7f`Ú) OnP‰[½º'ÄÞN9®8W˜jU= LúZÑšá(FÅ0òÄKd Ð]G±Püúé?ßûƒ ì ç"?ü΄ ÿ^ r(ŸSO"€ÁþkÓÙÛÿN«ÙhµjÕ½‡»õÎÃz£ÒkYbûÎÈÙòÑêa¨Á—RÆÀ˦3Z6Ÿ­FzKÓ z¥œIi…\¹’ϦÓÙtN×’[YÅðLE9º ÏŒ¢È·ÝD¦¦K¸Îù€`€á<3òÌ€‰ß!Mr÷~+æ%gp¡eXH^pò?”Æ´ìA䒱Ò×J§‡‡¹Aζ ;§7µÆA@#uñ[«1)µÒýì§™¡i ¡ê"PèjÁâ-ùƒ=ÓÞ]@ê`dÿ"5?gºŠ…BXJ_’‘“sƒT œ‘Al…Bób jTÓќڼŸé/„r)6¢P*”V'Yíæ$%jR—d]’d`ãºri ›Ó çÔ(RJ£,ÁÅ”[tDÚN®ý¸]…r ™Åœø€Ù*ž[DóOi¦²VÈ+“»€æ6'Ä`Nm5ŒMÌYOáûGiŽ?še5wȹF5¯EY Ù?ßû+Ç£ÑÈ­¹ÞnºÖî·ÏtÚÕ®ç=üˆTÜmˆh?j´«»»öº{»íJçàèÀq?Gàà™µª{äïh -‘Öòz._Ç^.¥¥4½P,$“élAÏþæÞí9ŽóÊä³"¶£-u¯¬K& TUÞ3« $öe#Úv÷Lļئ€ªÊû¥ªÚ–šæU$%qöa£z'fçag:Úê±@Aq&z§=”%Z¶ÕaiÉ/==–¦m«nÀÿ0眯’-¨ {vÄ…õÕ—UH也óû¥g²©WÌ¥ææ1:ŽÅ¡ã€T£Øƒ€µ¤H½c¦0þþgùþƒëïÆVqˆˆj‚‡ï÷q ôÀ;(‰½˜†uptÇö°Rïv)3mçÚÙy+keýFZM×En¹´¬¤–—Ù—¤Ò÷méÿ[Ò”bÚζ?ngZ·ºq çë@Ôa¾Ûi·%ÂÑéo9Ñ'××DQ\“ªš1&C† >f5©ÉpÝXìËcã»æ7œV Gc’“½‰áá´Ïº~©½9lÛ¿@J­kãi«¹ÒT߯VY2ŽKN3VÖÙŒÀîî¡ë¬+nÄâÄ Às*ŸS`ts—Õìw© z0Ú9ºIï݇2˜‚nŽ õp2aM€S$~ÌE ™ðÒÖ Ðx‰'ÿðY#*³Púޱ”¡gO0€Ã!ëVšùúÜ/˜¢R‡{_jÄ`‘~¹ðë+£ ÿT%¨Ö0´Ýyõ˜¬WìX¯Vdõ¸((‹f݉[X5wº.\ôÍÛàZ¯¥Tr.‘J¦³E$Ý.•‘Â#_* Yx([.³‰l:ËÊt V<«ƒ2‰0þÓæ0Ï- /5E5$ÙÅÎ]ßG˜1,õcó!ı,Ááû;›d\&;™‘yÛ\ G]P ž‡IIëƒ hÐÎäZù†“§@Ýú‘8÷#Yº?·5÷£­­õËilÌ(ED'¶rìJ[»|Ëju,‚x) K ,«mw,ï—¬‹3bUJª :Àºõp2­¿3:tuGt“>&†° ÅnÄê„tC²`{Ì0´ gwÀ¦{4Ûû©û{Ì7¬9¯ÒëÀPgü`Ú'0 Z= îV>Iö!ëbæÄ&jVP"SŽ”qÛ‡õfM{c‚&™²$î> »MJ‘¼ nîLa· CR0•Àbr6EŒˆ?¬—i—`¿Ðã`|_TkÅ& )=¦–ƈ)It6ØÀåÑ®ïÁý‚6o˜¡/«:Ä]ç=Ðî€L;¾‚нH<¦V+ªÆóxM0•)ì©É»$E¡åPeMÈ'ÓiPsIPn̼—ù’ùä3¥b*™M摎«€Ý+X“Å`Kr®ãŽ606ë!¥0B}á¼ÄÏ–Õ‹;aÏé1æø=7F îaWááû´ÿq… —‰cpºßFjAxÌ…hÁsÿƒ²™ÕT#s*·šÉYð/ÏiõlJ2 MËjfΤ¶ \¿g*¬E0ö?»’V“‰¶ôcDþ^í€i[= àJY-ÛnAðËÖkµY ®¯‚8S vÿŒÙ¤¥’ð›0 EÕ)L»ai}¸Ë¢j}Án”Š™³ùÉcòf?}?9øj#æ•XŽœÐºÐP’©u— bº}ÂP‡‡¯S~}L‰rL”H9Y^ÆV¼»O¼Áš}Fì— záÓÄ»Ó160L‰7(…‡A8Ê$KZîÒ4½;ÚŠ5RgЀõMX[!‘w“êEÚ¤&ÄrÔë{p¿ Í/5ÃÀ0ë ˆüÕ߬ø§¨‚_¾O0/îFAÝïöz…*Q¯Ñ!Ç«‚ܨ×i¼Íòì®o¹äX+Ž'@ÌŸÌfSÉD*ÕŠål&™¿Pʤ3ùl+éb¾,…2—6‘È Ó"©/fåB·wˆ;Yv ƒRÝÁFÑ‹Ä0@ìïCtXc=¿ç¡Ÿ¶ý³ìG¸!&´°gŸ²ÿN0Ý1‚…ñžÖõ¬8$!o¥[).ÝXÉ&:\ª©ÕU]ŲÐêõ"Ö!ø|ÞV-+Ûɾi?‚K®Î}¡ˆùÄ‹`¼w,ì þ%눀 MÞÚÔ-·ž2ÞÏãýæÙ1ð`$Sh2Y¦(¾&Tbg&#fŒµ±\Ns.‡ïǶ<†Å¶Ý)$)ãÄÃü9ÃÚë~ÇÞº©gÌšýYgFD5!v‚VíGÔá@¸cOP{íDÿñ!Q¼|€zk‡• &h¸G½EÕ–¯ÇØ`B ÔȨÓ(µ:ío@ŒÅ9"x=îÒÃ5Ѱ5=›F/x]Ù»8âõ}z¿ .4)”u©» êöÿr  ’ÐÕÀÙ6TÅ\¨S+Çk²Æ€:_4c©¸‹þ+:͈‡Q€[Îs‰L®˜žM§çòÅr9Mr\©œÏ&sã‘IgS9p ‰‹K}n.iƒ‡pqý±¥a:Žøpˆ$‚>¶ì£‡Hø_‚Žî{¸‚Í€˜±ÃAâϴߥ®_Ÿ¸LF ¦QeÚŸV§åQÌà9€³’Þ’¥=îúÛf@ÜþQ€Ï•^¯W¢*`ñã´ÝÊZé¹TRÉ>z„Ä6Øw; Áõ÷P‘í°+°½j}b½63§ 9 "õ“q3Ï–Êç„O³ãÉ`”—5Ö'4 ³;˜6¥2‹:¤d4já æÙOM±ÔýOÙ1ä!ú@*Ú YiŒÔôfG¬¹u ¶Î²k#69<¡;µñ»ÁÎSä›cÖÓƒAÕæ’o’ ±A@¬NÉ7©mhÀ†we!ú/”ÀJ‘ØòG½{˜@©¥4Åp F=P#}Ð Þ)eGyƒ£^߃û±ªËRèÉ Cž¯i¿2øÍ¶s D&݆èš,À£¨JjqÕš— XÒ½. …P~6ØcZ-ä ®›²½²çd}'Ÿ+‚6È–3ùÏ¥ó…B¡œMÍå°X.€üƒ—07ç¯à°0uœb‘eåÀ1“{*†Ùzê@AßoÁWüð bÆ4æ¶ßg\!˜B .“0&t¢`o¿O¨Â¾ë¸+¾Cö½¡_@W‘ÁofÕd]âó:|6¸\3Çy­n·Oz¡^(¼U[iÉJ¶Ò+ÉÜ£Õ 8ý8aƒƒ£H˜*Á²_{µm\¯ÌHªÊ ñ¡AX¬¾5JƈÁ=x‹½’Ýëc#vf4'‹bwæ©VãýS¾¢Ñ9Ë b 'ð™,Çó|)•Ë!6w‰ãË¥ÔÌìL*‘Þ«ÉQ¿e#n Š2rlQ­žx~\§…”V/„wæ{þA±‚B+°>Ã~ ¶b*z1`ðt?²»NÜò! AûŸÉ~PÔ S×8S×óðYಀžÑ9=«4µŒÆymDZ Ùz&½Uè‚~胔,WÞσ‹¶®í’gaFÇê´»Õqž^¯Uj4 ¼KœS—ȪÈÅz3U¯Ç¬^7õÍ)›Å(»©Z¸‹EøÉpÌÆê’eŸðýôýôT¨'óOé< Å)&߀2ôØfCúb<-ep~¬ÇîS×±ÚGï klhp@íþÌÊìƒ“Ô ýàˆfkFÔ“€ G €b‰á„ºoÐÒŽQ ©p‡(•èךÐÀv€]ÆžHßà·¢ªµâp!ž{Ä`Ï©J©`ÂwH¨eð±#^߃û©¢ª²xRua¾¢ kõà#ÿ³EMî‰zI¢$ËóÊ1µ:¼RÓ©*(.h‚¡/êE[68̽l[¹A$𥒗Lp_Ìùy!WÌ&æ’™\>Ë ™|™/—¹\®¤¿TÊ%ÉÄì\"ë3žpL$¶ö…-ìïyTá#žËöbäú@¼¾Ñc³!X϶o³ÿ~¦ý1q™ %1²byíGXq+=ѱ~‚ˆƒÅ÷‹EåäÉ4~Êð™;ùdÐăã¬ÔªÕá²uE—Ÿÿ8“n%ì$Ò­ÎÌÌ¥·œÀÁÿ°¬8D~P/Â7‡•€N§Q€ûÄzU !‚SÍŒ¦U œrhè÷‚¸Œ]vÓ3rÊ^³›ÂJѾa^êˆ:çѽ¶BÁìfP¹š2á;ÄM4f7ôÛñq  þ p0½^Æ0:l}wB£¹”kÄ|•ËF$ƒXmDQ³9¹ÑpècBÈ4z?ž:FÔ£‹ë háx8M1±úˆj ˜:’–aÀþØâ°C¶{‡á.Ð6Ó$3›%Àç¨ð0e=žàÙQMLŽz}îôVËBâøjåø|U•Ø¡iôMV˜>*É«–ôÛ9ð¼á“à;À’~Eµ…šÖÅšdÔŽ#,x%<^ÓXU°fªÜЛhUWí>øÜ.Êph—‹g§>WäŠ~ãTfÎM&,ù —çÐÿ/–žO$ggçrss ›‚!Šh‡Zb?Ž[`‹‘t]wâù ‘z çé 'vêC0ï ù ÕùÛÏȈ+À‘b¿çGÔNäĤÉTîýb¡˜¹ÿC-óïi™¯ýPËþËÕ5½†ö_Ï45%ÃÕ=kEÀCYÍY™Ôªšêj¯f³E~ B/ üNÂ×r°Öà!c˜‹)6–÷×E±*ŠŠŒÃÀÔ‡w(Ý«ˆdÇŠlÔËT»CG4ÚOóðCì½amh#‡£~Lm£ûŠªñaûw#áˆaN!6Ó`šjøêÃ¥„<©ÞŽUöáppøú€ºG ‡›QžïÚ’M¨«™ ˆt8$P¢`8fÙ9B0Ýe8&Êhþ±ÊÇÀP°?g¿ƒbO´w.ð€ŒXß/gŠðÒŒ‰-ÛèÍMT¤ƒ°G°ÈÔë{p¿P—m/ŠŒÀ;¾‚6­¯}ýëðE7 äQ|Hoé«–§ýv)m"ü)„O‹Þ<àEn³…Q»ú±Q±fÃ'VæU†_¢ê‚i*ÍÀñ;>x°½<ùQ¶Ò³~Š ¶¹b¹àBๅb9åË<ï—¸rIà ¾,dgç’Ég™¹NÊñÐz#f(Ò-ø“L¢g"Ï™xˆ£mÇëÁTpØ;!"ùùÅ{‡í'tbœPFTnÆeòÔþÀ¥ö×ûŸç¸÷!z)Ê÷ïgäû?TôÞÏj÷ Z=Ÿsi šfZG ½Ò¶Š¹dª.+TMØ­vïT+533“È<Àì~x~ÆTÜñÀê#ÛèƒN«óÄ:(PôªüXd$ˆ…ý¢¥Ьـ6vhFe…ªÎSnægO°Û}@ãóx·á4uÙQCÿ§î'L|1šCÄjà˜ ™%Ä'0tÆÅ„æìYÂïÉ!>l}4ÍÛ“.3eƒG,ÁañlÂ2ohæÙ´=ƒ%¢¡JH»Œ(p „Þ.Íg&P°Q«òpi‡æª‡lÊ!B\S´ V瘢£Ò(Iÿ_oB£A¿c»òQ¯ïÁýBÓì±!ù U™•×´—^úú×­¸•Fˆ0ŽœîÚoå€W²#/á§( V äÿ¥—"GQu1òÉ FEU?FO\‰Ë@4uAVÌæ Ú]x¯ýn? â^¹\*x)?ø¶ œ“òRY®P²ð)€TAP„¯¡”žMv’³­œ7;—ÂËn9à¦Û,+ùc; ‡?°ì2Q˜m"§aJ¯GLcÞgØÒ`2.t:žØ|%8 øA2‘HdÞ/æsÅbæäI ?!О XàYm%õ‚]Ô$¥¾U@¬P+›9¥öN©­N.›åüí8rÜ$ÞvÜ ¶Þj»v 2B tìÇ번¹X3;ãà¤.ÜY?ô“'4(ƒÖˆ¹ÑlüœAõ"v AÔaú[)½Eœ»Ôé~Ø~ŒÏ¿jÿ…ÇÌü“ý¤I’ú2  —$x0m’¶>d¤çø&©­€¦ýPúÔä?&ü>B×£Ù<¶$LX»+í ÷z‚ÆÓŠúXwCÏ|nF£<Á¡D†F@@ÔÙ„ó¸,DÇ×e0+TqÑàî+ÿÓÑT±²Ö-q*éˆ×÷à~a‰ÒìFÕe&¶V“´3ç¿÷ÞÙ?u­©ÅF*ÕªÈ@+¿£ ‚ßvâ ªT!ìªö§gßûÞù¸ëº¶ a­¢„U“Mý=Qö½†\©éŠ*èš²¸Š9´Ðï! 6"âñàp~ÒI¹Iø±,p¥,ÒÃ_( …¢‡Üœ%>Ç—Êe.Ñ™›u@Ü2éD2ë: Ä±íÆ–o §w‘çÇ ;Á ;!¡ ÛX 0OÄÿ :ûaû;ˆÖûx¿ÿÔ~ŸpÌlÿ}pò…÷AÊ‹¢²¥`PÝR·”GfÎÌ(u‘S0Ðr\s”vkeõ…)Yb@!§PÈ£9ðÒÛA».òxAßwÛíò—by‚<ÇÞ_E1À.£íaˆvØ›ñ/c÷&|9BL(ø€jmÔZCmq,xfxÚÃ!ƒ¢ºŸã#šØgݶ“)2 Ù”ŠðqCÞ˜ÎI)uL]ß%(rJ³£¤S/M ïФ/Ž;È‹g¨>»Ô^O§¥¡‚$6rêê!ô¿ ÃB­5a´;8­e2ßbH)8:Ò4!j**àõ¥ùf ۇ̿'·Q.,ɘ Èvˆf2Ž|}ìV}°¥’ ±6˜Ø=ùÿÞ{ïÀUlË Ž , JÔ”@ì·pT«5Iq^\©ùvÚ(ÿï}ï{giŽÔî`™l¡AŠŠzLsnl†² ªJM4­Qo†^¡óƒØïõÞP,•¸‚²Ý”ŸÊB‘s¹¢—w¢B©V_(f ÿ#ë¦À— ¹ÌL29ëÎ¥Rˆm Í a;¶[X0ÃI~×ö1L鲃ŒÇ±{dkÑ€oìâÇgÙï ùþ~LÊ?±J|/ü @€B¡Xh6 'š'šÍ¥Ò ¡Àåôt]â²z–Ëià ØVªµ¢¤VsuI•Ì\ÎÀÏS (´¬ö¨>@é…ì„aìQØïÂ$¼w±DFçL×ÄZmMT$•:§wÔ¼K·=»Å†¤0ýM¸3TAŸLóJD€ÍXnˆBˆáñ¢_¾·ûýäÅYäA2Bè(qcòÌÉÖQmL‰9Z“!¦Æà°õsÝÈ„A…Ò( Uç˜ލT8dc>8#¼Ë†ÇÔU€}uÓaãñËÒ 6Ø ©ÛÎø1N¬¦‰®=ƒûÙÝ¥f@6LŒBIÁ)"UP ‡„ý7bmÏ4(4¦©`têlØïÈ×÷à~ÁÖ £[óL¬Zùºë IDAT„òÿÝ·¾ûÞ3ïÅÈ“U­UæÝîÏ?þù>­ã¿Éc~¾Rµˆ±¶÷WÿúYxùïÚÖù3¦*)ŽßcÑT{½^°ªë vŸ.ˆ²6¯ˆä4Ížë…vÜã~ƒÌsœŸHÙœëdypøýdÊ-:A)* ˜(–ò þ%ð„b1Ïås`ÿ½Ôl’K¦ÛŠq>ßrbBõµ[±¹Ä¯‰)z,ï[¡v''ÆÙ¾%ìËõÿ°ý>rÇvøx¿ÿÔ~6r¸÷‹…¸EŸÅ@[²xkK‚Ï­z:wÿäICNj½Ö©¼ƒð(133›yÐîxnäy8H„MŽï$èƒÛb!–…ëU‘2(–ùbÜ›Ìßa€2TwbdÁˆ†K”@˜M¢h—²nèÇR3=£º¦´ÇpvlÒS²ˆaJÛýì8d\ÖQ÷ˆ 켟Cîñ÷Áàç ã ëgdòð½ÐØÔê]Ì  ê\õÿÑ¡)Ùº|ÿ~^»ÿC@V»_ò’333épbP}ìBpB©¸çûÝ^ Z©ëUUÀ”œ¡Ï°FÒµ¯bjDyAâí¦ÎÔiPNÅ1†v¹—ùƘ”bqÌ{§9hUÿÔýˆ—C3ô¬gnš…§Ê=ÇÐÐ=öâ!î&eɼkM ¶Ž%û1‰Éx‡•i|å\se_ñh†ùÔ¡Ó!kŠ®§AºÉËgš@×HÀá&ÖêzRB/j‹)pTU%)Ú…|:J:H<Öð@¸á&x²X÷­<½u¼Öl¨iJÂÆ¸=v}î´ê¼&)QPUºŠ$Ù6„ïü§¿ùÛ·Þ;ÿ’)Ç–ØŠ ±uÅR(ÊŠNQºø›=PþÕ8îzaÉ’öÒù÷ÞúÛ¿ùOïœ?£««¸þµX«›Š¤}¾ªSU½Df,ETº\_LA„݃`-\Â] ò~&™±AÒóÅ"¶ú¦æ2>W*b W,ðB™/òHÅ]FO;™JÏeR|Ù››×¾‘Â|†ö~øêè¡[‘¹ »¢#TßNl!âÿÔ¶ãLP¬þ÷Û^°âzïÏÔ¦ À#ÿdÐ(L=£¥õ´–~ ¥Ôú+ ÀÞ¡¬PÈ=eR|ß ºÈìb£¨'ˆ>úÄ àצuPð¼qÁt±ò9yà’)¢Ö€àuišu—ú„§Ó¯»,hß%÷§l)@Žë„œÔ]Æ6zØ~J™S€Š þ,!@ +†í–¿R+.ú÷8³0fõzjæÿÔõÝi‡ Á„â/5"XÝ¢<ñÓÒš,a. ÄW'éžštU‘“²’BTXY×HúE””¤")ÊC9-ŠÒZ­V™ï$ñ ,ƒðK P)ìy x˃†÷C.bÝãÓÔ˜¨gðÂHÝú+CJ8 z}îÔê¼dÊj`J f"zuxþoû{çÏÀ{Ô@¦\Ýt}ЊšQ»+ÿ†¸°QûÝÐ4!À0 ùíoƒüÃ5¶ëõ¥^5öÁð›ü1E¯øÚ,[®¯€` »Ù½UP±ñ 8;9—θ)‚|L¤ì¤ïå@î‹\,ø%Ð…"/9Gír’lgù‚7㹎ãÄh[8òQzzÜAˆ¿VÇGî`¬õw Ƈ=¶Á‰ß·í¾G4 ÿ„ýþ/Û´ÝÞÑ(þ¸È!nI±XHsMç2z†+fç \j¦I|.“›ûBiæƒèåw5YI1¤p?Ó½;­S)%© B ³©~Çw‘\4ò 8ÑîÀûêÁõŠã(¿éÑ]ˆðà@ÉêÃi÷,µµ²Œú€Y`ŽöÂæ0š Â×F»E=­ØÃ ³g°”¬®þ©ûYØN¯6a-5ôF¸—ÍÉ¡_ËŠÔÙË`¯Qööêú`Zo§ÝË· Ó{4ÖMÕñ!øA•*âβ.”cáš4ÃCm û¥ÄŠ7jlYxxÒ6í¯ÊUÚ%I¸,‰UD\ŸÙÆs©Š*ã9(D0t•ž&âkêÁ" /™Ò±ïÒTñQ¯ïÁý‚†€R¯‡A¨ª^„éà{÷Ο B4¨^Ô›–ëBê.,/p­Žú›=Àõ1#?v<¼ QAà;Ð$\¢FMÔ•@uµv¼e3D5а=ÁiUQh†R_ãe¯ §7ĵ`Ù `ÝRé”ãfza·úÀwÒiŸ_ êÈ@„+ðò¥l>;—JÙ³žóR9¤ˆ‘&,é„°¹‡¼ƒc{à©{šÏüu°ì ¨8Å!?Ø}›F}ÛþŒûíÝ>¸ßÂd†çÇÈx„9€x.‹gduNÏ(Üb6Q($g 'ÏæŸ›û݉™ß)œ˜ý…u#;M ö¬t딸õ£Ú}{˜Ïb! ô[*øc–åYA?Œ!Ýku\\¯VÄ…*?6Ê2N+HT#q¡äñ„!Î\7%ÏsƒªMØAGó ¹í˜¾bP”¢îÞ=l?‘‘`ˆJîÀxB„¼$ÎXec½9,Ù6AJŽ!k}ße ¬áÿS×±‹‡¡m œÀ8IÉÑÌ+2Hð‚(.ÔЂƒLË$ÄhÀÅš —‡%ª²¸^‚„“„Ò Ž“ˆÒ[P©Ui yÖ@ðk•ŠXÙ®Vgf°Ü"Žâ®ÊSq‡(ANH2{‰Å Ø»ƒ¿<«íOè*¢fû¨×÷à~A±P–?”¥†Æ>:‡Qo4–â Üæ’µq,x*æâb³nî{E¿±Ã¨7©êåS<»†ï@“Dp¹r 4lè×ZòªåjÇ´-”UÊXÎãð‚&‹ÍÆuÇêìàÒúéTrÎñ3]?}Pä DÈr¥Ð+Å®6•À(rٟϦfV29׿y¤á±]Ë¢ =lÐGЯ1‡ãØQÿB7À†½Ðåˆ ýHãa¼OcAŸ}?¦‚Þ mþQ‘ŽÂSU€l‚–Ù|9ùl¶ø\¾<û;ùòܳJF¹/×3Š˜IgÒz¶—Oõ”žÕ2¡¼u€ÔvX CjWÄXþîhÿ!ˆá\÷y½*">ô`̸oЮñe3¥CLÎ ¨á•Œç„¦î)ž¦N›!+„Öf§¨mf„),ôÍG‡ïŸŽ°`ì„édób§¦›|šßcš‹'žó=넽¸8=;¼­ÑX\Z\4çe.O`P'¢z̨(‘¢jØž°:_U“†ŽIH~4ˆzîvüÜ6ÛÃ2_¶9ÅB1ì…°¦E¾\¦Ï  ‡-³3i7cÏ$“åûl—:õp°y»„Èåá€ýÆW𬰃à½àÃÛ!ŽüA€ý¾ˆê‡ýÿGÚÎú1÷p–\ˆPŠh°´‘Æ`ŸÓ2 Mm+dæ©ô÷#ü¨ÝWÒZZâÚDRà\φ °XhËå LrŇNäZ«^·Ãr Î8ˆº>²ÆHjìuaÝòAt$±³V†¦Íˆ:S©%—Ø6ë>š0øžáãódðäF ÛOY-ÖÎGÃì˜ÍÆd<µÂàšÆ€Zx03ÁÊ…ÖC`BŸºÎÚŠ@£Œ©$8måCmƒÁ¼,m×µÊÒ"þCßý¡(ÎÍ¡tÊÛµ*[¯, Å Á5«Õ@Ôæjâ'×k Øê¶*¢¶VIøkHÄJB?µûør"ÌãÌ\}z·lŽñ P1`À°EŽt}îš”f¯«žãÛí–i£±oèzÓ0ëÍ¥Àí4¬z½a²¬¥ñdRä7é˜MÛ:Ѭ×÷OßÐ4xø¢æq '“B:ÕcfM‰|Uïjµy{¡¦ ŠZ_4CÏÂùÂÜ÷<' ÛpK·Ñš‡Høò7zØí†Ý8*ÊðŽç MÃùd"Ñáòíg]—ǽŽ3ýHÉbË‚ ƒèF‘ÏØ#"?@Â?‚î ÚoÄèmØ‚ï^Êa]€BáǰùXT4N§2`±TnMðÀ`éd±Œ!@¦ñ*¹ ½B>ý¹S§à'+?À@òÑ:„t«Äy ëðªp}ðHzˆ]‚ÀD[”TE×G¬ë„¦Úw¦-fÓÙê*° 6cÕ$hÜC #Øk†T9maŸ°”9è‡ígimê×ÝaÌ Ô„³<Œ-‹ar ÙCÿÀÎzÕû ë4ùƒ^?!æ`}@ ³;†An¹(ÏT „¦ÿyf»áë¶J°õ¹¹±¶]AÁ…/(ýUSÚßÇ'×ET 5ÊÔ¤'ÿÇ  *‘þ¥0…Ò c¢ ¤±aúSñúÜÏ:k¦Œñ¾Ý¤›ªcMï.«ó?qî÷WÖªdÂ¥Št xÌ«f૦8_Y¨Êº€¥—¦g58HºtÛr;íÀ#ä¾ÓõûQØïõû`ðz…_5 €o-ða±ÅV€l®Ð™ë8Âô J.öì8ëšjç‰óûlº÷'ø§¯;40ìuú×AjáÇv¼ÒAôÀ¶”@lŸI>*r˜x2Èy+ì`bÑÁôQp”-XÈJà$(àp/åTS å2ê’÷hÞ×JÛ·ãvçàõ±íyÑB´u¢H6Ȥqxj†ÇÑZV…'h­=~{ºsßþèëè”OÂzÊ3È/Ý©=ŸÜÚßÞØîÁCöOhv‡ªÆ@ ‚¡kb¾*V1ÌÃý:Ⱥ…Ñà$¤I…ûn†À8Ÿç+3âöm¨ð< ñSëUñ‰õ*ŽZX÷7gÄX¦ë”>Á#$âHÚEH Ïzý Œ’ ˆ\&|!CvY:åB0ýI@Û”kÝùº1Tà¿ü5ßš0h Y‰5Tc¦RA˜•σ:V5AVU³ÚxAÇj¬¥G®ãF ~6I!FÜa÷Â~ˆ„x…2O]€`þÁ(æË…¹¹Äé{Ü™ÓÉ„[ä;„fg“ ¢× $/¤ROœdÙ¶YÍžæwƒÀ!lßϺNxŸå,|©(€ôNè­´@VÛ-=à²ä¹ƒŒ‹›uý``1ïƒÈs¹œžÕzZI‰[õ¬–Ö”°P¯spz§2_Nžjêàå?¸®Ó!PÑÂÂÕüϼN áØ!ä{4÷"’°aÕ tWW;{Õ–U*mÏY ¸ÛÅ„¬…¯éWÝ•–UàŒ\FËdëùaJ¬ë¸¸ ± Áu¾úÕSÍ&*Ô$܉â­k^ë…¯®„~kÕzòú d1¼ÍZEZÀ;œØÍ$2¸,E>l#}‚ß~øËøíºNÙ{º!Fnš Ú¡!œ+î ЉðƄ˽C¸½è¬ HI¶ L—‘÷<°I|xŽ¡€ÿîÿëµÊ …ð•×ÑO¯ÊäÚ딯Ã2žÇãçø§ÐÓ2`E¬ÍÈÒBe[´k.«•jg×ð@þ×+­×j×jè4”à¦ÅuT{(ƈP˜0U9`ùš!<üúïN†¤#P‡Fˆé·¬ @` èŠ/Ú0"•:ÿÒ~y>PjõóÜ_ë<Â'ΣÓ@2x'ªªh²zL3l+îjAUjp' º)/v0œ}l·aBT-dúCx@øŽ·žÝéì÷pT¨‡vŸ/Ü:Ö ùÜÜöÝï^x÷Ìé É'ð ÿ p ÜDé›_´R³ÏìºAV¾T> Õöú}öu"Á¿å# ß”B€è?âÀn­¶\âý-•JW1 gV÷B{eÅ*LÖˆ!€¦,Hè‰^ñãtëÔ—[§t®´€«B±|s#ßà qà¶V;îÞõÁ´DŒÔµJÍÇF@4J5R, È8µGÄPÅKá·?ê: écÙkH˜þ8¦ƒ©k’æ ÀÓÌþ«QPoD 0œ³G ãCöïd1æ¢oLóˆÿc(È‹H!~õõJcv¸"ç ¢2 Hš¡_ªðgA4›ß0릮jª ~=µŠÒ‹çh½Æ¼þóê+ i÷EÑ–évíúõê-Ñ©AL±ùêlí³‚ˆôŒŠ±C!:#dЃ#‚(8ìúaØÏxKÐWÂî!¬«Ž €Q íÆÙ˜°QF;‚VStEiú‘Øm¨Šý—†òùòA €L7¼}øJ à×:ð‰ó0rÀ`ÉíÆ±RS)<ÖêTTE¯/"„ú(û¾v"ê´kµB°ÆsÛQö#ÌôûQ¿\æÊ ö`èýkœ€“Ãù ïÜ}÷ôöéçÎ$²©\¹”OÙ³‰V"]º¶ À›M&÷Ïo!ƒWäx‘õq&Àw"*©r }Ÿ\©oS³¨¥ÐEö`D¡Î`PU­p„\£J´ja%`gàg.ù¢o­¼ÐæL8¬ó¥êdEÿÀômX¨§uNé5¬ü©Ä /<*f³°[xsãúZ‰@ç´0ÎÀND?´BüؽJèTÚw1Š„”Qb`»{Ø—Tž>„ßþÈë˜íÃAXt9Ⱥ`ßå¸hF!nÃNµ0°þi*Ì ¶Ÿ*ŠØ^Kø¸#‚;ÙÕu……{þMã_sªrƒ×Áû—¤jíõmLàƒ8{æÏ^zå¥E¬Ž/6ÍK/ž£u¡ÕÈëßì) €¨µ@Dð­Ûkë7o°4@imÍ®Tí’׊ª5·2 à¦&ÿ†„iX™X€‡_¦©~ºCÅHBD ¡cå&¬2rŒÆHF¶k­Â¯j6Ì@6 ƒP•j˜þXˆê¼¸¯~óŸ8n"9`hà D¡zL§ª…ù LÐ4ÉXáqiš¶ƒxwE{BîÂÿAt,' »`ûûÝø}ðÿÁ÷/èo¾QÊ|>ïÝwßÙ~ëô™wÏœI$ç²^:cÛ³IðÃ×7Êà+8ÉDfïüù>|Å2kvñŸxý'×qNðà:ÄHïÎCb÷±fˆ#*€ZNá$ÇWÚ˜(<?sÙŸ¹ýl6 :+§@˜`ê395§ÖÅûJ®Ùlî-`ÀÙÙvâTëT³Ž“¸ûã[k·ÖŠåék´½ëCID\*ú5 “€ Õg«šfRêoLÓeØXŠŽ7„ÆoÔõñâœ;gSã:5À’F!{0ðÑD Ž ìRŸ£ÏÁ$ù!ûÚ0#Þ Ú¥[×äiP­JÕ™ É0\™dyaÆ«Ø÷SAGÚî¿È\~·õ Ï¿Ê_~¹Y-°øâ¹Ól}M¬Îø•Ê6yõ†ÿQM¬xx5›ß¸¶~“Ë_­Ê¥õkr-(ÅÕéõ©P€!€¬Õà!„b÷©¶Á¡×¯Ï‘=©ã #b!ÊÂtÚad  sl:H˜ZQU‰)€ö'òÇ_úÒùÚ×.^¼ølæ•W_»º|5³xéòåW_}ùÊ•Ë éþàÿwþ‹_úÒÿÉŸü³Ç @T«ÇçÁvï)€+Ÿ8V>ùÐþñXj“d…qH~˜™‹» þý îÆ% !W,–ËÅÒë˜b+ݽwîî;§ïÞÝ>½}19“Ì”8/™I´S¥o­9¥22%÷Îoy‚ù#ù0ª¿‹x¿Îã×zÝcxÀ˜cÛ{’˜ûø„ Þ l9Bi̳@8_èP‘¯D)»tbf.ó¨H?sýû¿à@¡­œZi³ 8âò¦.pZ&Ûã¸N§uj¥¥<Âí¾½y}ç÷^)r‘X 1 «¶G+ j`€Ì1áèï¬ÖˆÊÊ$œÃÃùí¶>bYXó"&!ö W›Ý©w«v„uE ºhùKg{²ö§í0"‡œPHî¶¡JÓr¿"U0?WCeP‘°¯¶ µÁƒ¯¸µNÍŠª Lí%?ö;~ÌŸ×Lþüùçž~éEþ,[÷kVµÆÕN^½-V;pV óf@ù»oò77øëµêF­Z*mÜ|]ŒãRÉm;}*b ©Ù0¢]DlŠü©×—8 …l@³?TÀ_Ëþ»¬·¨ŠpÈhW [%0dEÅéZR ÿ¤.\¼té¢1÷{¿—(•~/ݸüò¥W®¾vùµË—Íßÿé °¯@næAr÷Àå+>ÀýüÄcûû à©óÔ5³ÅuêP`«5£«¨F¨¡&Єº¬Ô͆C0[X³\ز­Ø  ÜsV±ÐëºA¯®@©\ˆ+¬\(]»á@ùÝííw·ßyëÌ™gîžN%fÓ°îzÉ¹ÒæõÒ›Ù²P(rnvz~ߢ$>6÷w( ±—/Üý§×}Û†ð?F¢é:•ûC»…MÕ^ÜAÊß+Œ`µµòÕ/eÅ*noBJÙu¶·²Û['~ö‹ç…•ÎsÂ/~êÿ\€ÇaÁHooÁ¿Ìö–²uߨÞ%¶·zÎΧ;§¾¼rjKØÞbgº}ãÖÆµý×Xµld-‚·êuÄ/¨ºNŪ±áÌš(k&ÎŽQÖ™aévHÕÎaüöG]§ò#ÝÂ#jBÂê¶ñMˆs{´3€a{v© ‡s”ó±q¥OÝ!òì2S«Y¢T3¢D™.£&¨ù’(ÕÉöEÿå¦Öˆ¿ŒM”1_./Ÿ=îùç_<ÇŸ§u¡²`Õàþ«ùóg"‰jÍkUý*f\;w6oݼv£²¾!Š¥[kë’$–¶ëõkö è |’bŒ €m0}›4 1"ÐÑC®#'#M®Á IDATíHm¤ÈG”02Œ'pý‰º»ʱɡ,K4]ËÉÿù‹W^~¥™N§3Ù\Ž+<÷\ó勯\}õòÕË—ÁøCþ‹Lì)€©Ü‚äî+€ƒ‡ÕYñ¬vÛ¾|yåk—÷ÀÓç1助ë±ok 1’zÌ”«R]’ ˆ±qlM Un¦†+‹æ`FÍiùÄdQÛãu¼~¿vã~Üïv1X,•‹ ÿ¥ëë|©Äî½³}úî…ÜiøúàÌ\ò\y«\v¹Ô­õÒúúÆzAàKoεèü}ó÷‹÷àVtíØ |,0úà”xݧÖÃØÚo©O‰€Aü–g[²‡Ýr@MqMúÀL0+ ” ,4à'¡–íþÏú áýçú±Ð³ñ9ô¤'þ=_*| ñBéùY¹W°¸|'=ûÕS-ZÄ3ݹçè4”?c^ÝqhMÑ AÉÀ]BXô#­3Úù üöG\ǹVÔR1oŒ!ü.Mð7e³†ÔàF)/ºÏÚwÌPñÙOIt¢ ú¾1"çììÖuE¢„_E¬Ê°AØ à¹5[Ü®,H²S“Dɪ互-9ü¹çþayy™Cç™øþÕÿñ¯øež/?qÁ+´.ûa-ÙýWsÜ–÷ªç.èüÖmþÎæµüµ[ÁFis£tmS,EmœÃnDHþu¬k0$&‚"ï²ÿÐëK?ðâ/8& A?PV`@„‰ ˜¸M蟯¬‹Ÿ_µ{_üâøË—^I'ÿmv®ø^9_Ú(ŠÍ‹—_{´Â•+ ø/|ñ‹OzLnçÉv3péòMS}ÇiwÈ8¸º§ž>..huÍ¢¦Zµlõ˜!Ù-Õð‚®”l,‚Ûê„(ûqpûa/ŒÀÆa†Ëî†^¿Ûïw«ß/ð üðYâJ·¾u£”„{÷Þ½ðîö…·.œ~ðàì…·ß>w®\âP?”677 Ó?1ׯóc³1RzØßý8‘ çö£¾ë÷»a7êÅaÜ®ÃããÅ2¢›Mï <ˆb»¸ƒYC¿ea³ð§'•ÙÙû4%9«6rjÖbáݾ_"áç¥?ú~ÞúðøMNj x¢œ”ʬ’lfõn–µö©öÇÅt.ÝéØ_Á§äŒ”<{ûÎæÚÚífNKˉٙûÓ4FUÆ…¿Þý˜Àª!*¨¦›˜3¦ z‡¡‚PG ÚÙCøí¸¾ÃRZX­Fþ!Õ†»Ór7ÎùïâXjlDõýá»— Û¿CÕ á ä‘Rq»†.ÏI2>x?•¬õ-t FgŠb)m˯yæ—|þ÷þEù¿.›ê3?ø¯?øÁþÍÿùÌ_ü9ÿ/~ÿP¯îÝ•mºÿjéžUóBœÿøzîÆ:1ÀúÚææõõÍÍÒÆú­AÉÍvBxQ–ædÝ@D f¦1@XÅ”ó8äú" 0›û£¹H2þ°aÝpH€M ²^LMìªW#±¥¬ZÞàò¥fî›ßL%‹Ø5Oðt/½òÚÕW^¾üêïÿÁ'<€Kûã½û àà¡=@à@˜ÞYùÄâžxú<Öê‚¢6Œ0ÌjU /¸öªÒÒõHYHÑ©°c&¯mÑ`­åSøü]§ïcù¯ßíuû½~ |$(2í[*òo½óîÝ»omŸÙ¾púîwÞÞÎ>xpú\¡Àå‹¥›·©TJŠ.™dçwÉœ#á–' „ã~¯ ¦¼a–'ìÁ`ë]? 7Dù6Æþ¿cCL;8ûë‚ L1Û[ÊéI1!n%³ 9•hdÔ$þ,ZWx(?5äýTèýc¼ò áÃíÙdVžUDx¢ *¾Ï6ÌöÁ(îÙªô¹ÏUì¯àS FB¾³qçÆMð;óêœVf+=>¢›Ax"àõ.Ôæ×DÊÈ*Ü®U;dð;8çxî$èÃP÷ßÿ¾§ž>îY«JÛÐâÐD?¦KUJ[ÈZ$WdÄ0ueÑÄ[9$QÃÎøC!ù\î®ya‡!+Æ=Š<_,9о××oÝ,•/Ü}ëÝwî¾saûîéõ³o?¸wúáiøó–|ø«\[[OØn4x)<`‡ö´xæ³Bލß*˜>‚—ôÃ~áÐ}„Åý8ìö#LQäO÷Æ&íÀuqLê–Mtá^3¦_ù‡ÿPæfù‡ø³ð‹ÿhyYÞ¡ßý9÷qô‘ ¬$M”“I%)ƒÊ€] ™Ëi)YÜJ(m³pªMU»­žÒ4’âÍÆíõ[o¼Z"™g¶f}êI¦E·‹µ ?¦½±¥V•Å0©Í¡¨°ë11ut(¿ýQ×qêu@uÄHÀ™ã)ˆÇîÎc¸_KQ Áu2øP¤êúôý#æ= §èû»4;h ˜áןá¯U úG±°5íÆ3ÏüYŽª5vkÙü¹Ë͆—ðý»/Ýýþ÷Ê?8óÿ­q®üâ¹s¼{ðþ‹±›?C¿Ñ¡x.hþæòµ››×Ök·Jë›^Å%gÕuû5xyø À[‘„Ê¢ œMöÐoøé×oÊAHíÒÌ5bt–h •øˆ˜@sNv|î&«Ð¬Íc€ÀåWOœ<¹„ÎúâÒÉ“‹Í¥ÅK(®W®¾ü@ à àq?Ï^àå‹/³¿CÀ5}À=X~ÀÔ½Ç÷¿?Q|â<:‰{CSBSF Iµ®[(€*zªb*ºY?‘BÖNŒ¾±á.îYV/î„=Ç Ñ&G]4Ò} @:ƒ_*s Þ·®½YŠo½õÎ…»ïn¿òúÚ»ï\rÒûãOÌñ^] yÈÎç{Zqñä‰'ø‡Eþa‰¾Â?ApÝGýF£,?ßßûi¹ûQÿgÿý£â"¢ ávI¸M¨€è´Eül›Â©v¡DÚ `…ƒå;·oÞÚ€_†f‡JÅÛE$QÄQe¸;]K °ï\Ó$ã+¯Ô Õë´[O®ÿ `P–\´È¨’ °´…ìGó¢  !ËJ½ÑA©ì`ç}‹è9 ð!öB”0û½>BƒŸÞó£(,ùRUÀë7(üÝw¶ïÝ»pwûÁéÍ7î¾{áî™ÓwÏžãKÅÍ[¥ko\ÛxÃ/úår!•°èü‘ƒY³ÐmÅ„q„Ù¿®‹Rï“—¦Û…í#/€\ý¸ëbî€ö;q[ˆh Àiù«ÊFñ¨Û“BC¥Ù” ©.™² <ê÷·ô†#ü\à… ôü‡ó]áád©PÈ•´7 ,}X,éô– ò/èœ@׿RÌs‹M]ºóæíµµë·¦OÒîû.Þª¸FðÕ¯9lz•ìFÌ;ËÎ#ÊßìpÈZÎ?ßþÈëT¯cò;¡X—´ê4(@o…Xï‘{Ê^ n<º¾ø~ÝO#ñ˜ ›ö¦ÕL]WeEN`½¾:£ûÏßH¥_¿Æ›ëßʽý&D¨‘y-‡?sîÜy8ÎcßÙOçοxî§/ó~çñýu}+ˆm§ jn {¾cwóö;o®olnòëüú†½y{óÚÚZ)°3ˆ«8Žo"oFÕu“&0jÇŠÞÎt”ÿë7Þa¸IŒÁŒžM£ƒÃ&½Éž'2pÅÁbˆE5ñ©ÀøÅ ¯6ÿúÎ_/7äú î¯àP—^¹ú/¯¾öêå—³ ž®ìÕ=ðøx¿p_»ß`æò¦Ó^0žížxÊ~à©ó°N@Í€(qÕ E´…Tæeð ÕЭ¾ÔBã >5þ»pã0è8z;»¢vq°¤À¾€U€¿Ž˜¸w!ww»´víîwOoÝ=ý³çÌ®­Ýô»Œ`BžÛÆóCÜì†XÖƒ¿±×qlR, \8îE  3€¯o3òûðDxÝ#{ Š,<>Õb;r¢t—'%s ýˆ¸ÐàÙ‡Éð©ó?' 5„ÞŠûó|®ÿ¡ |´/›¹F¶^Ò¥‡\îv³Ð,”YM““m³Œ ཥ8‚þl 2Þܼƒçæ:Çž”1@Eðëx1fÀᾯˆó¢\›yAõ´dìŸÂÓÒXú¡üöG\§²÷„Åë»S²à]ŠðQX)o)æfÐÙBöC¬‚ñ¡û Þt‡Èv±Žo‡Üä_&õ‡™þFãæ­ÅEíëËë¹ÌúµKMc3/ÄqÐñøoðçΟ=óo|ãtóÛK/ž†ã j‚3ÚéÓy?Ø¿ÿ0lÄ« lmê¸òÁåïÜâïln¬_[¿y+€0týÚ͵8òLÛî1 Â:YUð]9:ázh˜0R¹¾»ÄUÊ¢Êb×Ãý¦˜7˜Vtö†'B¸ÜÅȃ[@Ö¦À4pîò 3ÑÕæ"ÝšæÒå—¯^yõÕËW¯¼<ÍìyO"S.îß×4@>kæœÖª©zŽÓi§<~ÎT8Ï"uÊ~h"s‰v¬ijмPE§‚Ð[©;j!$²r#_\ðÞöì~¡xa,3"¿\(­ßºuíµ÷Ö6h€{oƒå/ݸöî»ÛgÎl]8½%”À¸¶~ÝOû‚ —Jâù©{3™¡æÁþ1äÇcƒ¤÷‚nˆ >Ø' Ç~?€è ‹k6àSÚÍÚ›p"$ÿQRÅ+|’?)0@­+:¸¦hHá#]`4œ²¿]« Ï Â ?sû‚Ñ03ojR^Ö õ"x%ÎȈbÛìƒP\mÐ8 .@Vkdoß¹µ¾ycsóÆ5zRÿ§…Ü}ö>,ˆEp\ûÖñ柯B:‹¨`Š¡™ >šBN¢›&‰Oå·?ò:ùŽ©¹g2RGË.k‰Çɸ]VîŸÐ–ýefDù íCö³!®ÒãDä•5917'ÖØ@tµ&,^|æÿ]ßh¬/3›ÛXo..o…Àï1†¿ðüâÒÒ7›gÿËø¾ÇòŦ®œ>}Ž÷÷ï¿(€Û#D6æVØŠ]pE  ¼QþÎ~¤ž¿~ÿõµ¨T …ŽÝö»U‚™›KÀ‚ ÀÀ^=â&"^tø9äú )@â£jþgôªˆF6Hµj f³‘aqY‹zªñtàåWuM7²¾Ø04U¯7š'N\ºòÚå×^½rõÊÓ9€OŒñ¢Ø?Þ¿¨i&yÙœY7´ºJè"s ö¦K S©ÙlH4¯|¬¾hªr­µ8²lU¨7ÓTŒzÅ®½ë´âä«ú½ÖéC/êö<ÄFâ0ͱ ðÄÌ·6˜ö-ñïÞ»·½ýööV©tó&D¿{÷ìŸ}çŒPÚ¸O¸¾–ò…’Í•ÖA„ÈéÉX€} ò×Sç/L?ÞOæóSÀ™ù¸¦ºü>Î<Ø{ÎT¦˜‹uU–£šÊ – S‘Me §íßCnD¾G*n‰°cˆÒ]øËÓ~"- PÆdÄú\~º·ý–pï^¾saûî™ ç¶/lamæÆÚ·6Ö|ðÜâ57‘ à.„±¾¶BÊ=ð÷ ˆé+Þ$àéÅð³Ûïâ"²Á­Ó:8Ôž’PÝ"´$¦Op²ÌïåÔºlʦ¨I†,|„%€·«åF£ÿ9áCì äšÓAþ3B>>@±XÈ¥Ó‰D²(pÙ¹gçææžMÂÉÏ>ËqÙÒÆæ;××ÖOD°!þúŠ—½oÃÛCìm‚·#ïØ[©‰‰!Ö€ ÒucB¼7È+Eàš‡óÛqÛ]‰ˆ‚õ=zÞ]¢ùÂŽö1•$GDsKë h!ùÁŸaÿ`Ì @& ¡1–° ’òOlÐä~ùô3~‘_Τ_æ¯þŠ  <ä¿ñü¼¡ó¼q–û†ß i¾ùe±YW¾Æûï?¸´½.Ö!*\¸Žºp;¾Éß¾uûÖúòƵ[üZióVéæõ5ˆ­RÐò¬a°SRàMéðæÈàO¦ÐÅh¹½¾à¨0>¦¬Ê¼:¨&&DDˆt˜ÞL†‚Õ‘µº†u`½®I:_YEÖ¿aœ0á ™Í¥åfãåW^»ôÚ˯]yº@®<1Ö·§èX9ï>œ}TÔ PÉæÊÏ–æ m…Q*€8ïž_¥ç1pð<Þ›Z*’Þ»êp÷J¤Ô€A}QÑtP—K^{¹‡ çíá‚ûžÝÆN{°½ÈÒŸ üB™í{ãÆúõ'ðÅíw/<¸w¾µùw/l_¸{z;[J›7oܸqs­X.Ú›o$“):?Âꇈí~]ÜŠ[ˆþrßÂ.a…]¯¶¿ :SAØÇõ j~áÃ@CÄ}ØÚ~'üQ]¬×S'rösÌ Ðá×ÿ­Ð4î÷û÷þ§¡ð‹Þs}ôÿPàè…z¶Ô0!¼À(-–hœ q²,,62 ô·îÜÚ|óúk7¦'aÿ£Ž§fï{ží[ˆ_8U?fyæTkó­C“h$ˆ¾óç‡ðÛu}‚)‡1ÕöG,ÁÇXÊ&˜ñXKüxD˜4.3˜Šš"ßüôýÀA°ÍΨ­Œüòº,á<”¢¿ Ÿæ¿ýÜzäÿõ×ÿãß=sáBŽïaf§Çíùã&úOyóœÁŸ}‰çõãÍ—NÔšMã,O•ÕéýgCH2.„~pÿÅ=¤Øóî€r¹É_¿±~ãÆ-´1ë×Ö7Öâ˜Ëë9½J¥ºVE-ÄÔoÝ‘õN޽þ4ëD¿1ŒïN ;G )©@ =X01ßSBC7A î÷üñ¿À•EÃ\j6Æâ¢i6–N<ר¿|€×.ò_øzS =1ü·§0;zÎi¯vÞ8û>h³¦æ´^0tMl:­r«@Ø-ø<ïSϪ°¹¸ZÃìA\t ÉšðG«7 ´iÀMjjðÁ˜1š>DzÛñzð% Ü]·u»”­'ùÇ^@q‡‹¿Æ—ŠßílŸ~pWGu|ýÖÜÖöé­»Û[YŒ66 nªT*æKk7½TˆM½b~‚ë€Ê3š & GrcÓ!ê·ëAŸ­÷ÐóG`BX úDfì÷»]û~¦^Wêõ“ìTüÕ‹ð~–áÇ‚!ö?üè¡P×ï ÂýøCïß—>JÉþãG^ÖJ\‰ËfD©ÜÐàêñGÏ-ªñÏþñ£mk·ïܾyûæÆÍk7¯ùJàQ>ÊÕ  8­Ðñ¼š´íˆžX™¡RTµZ™f¤¼žM¬/Oç·?âú„e®4ü¾gÀ±ávùb&L%¿ k”Ç[›0s0vØþÑ´n@û†‰(Ôº©I Ühˆì/‹Â¹Ä_ýÝ3ßJgÀº›üúšrá·rÙ—neùÞTÌg¯^=ošüù«WÏòÆ|óÅEPÊÞóž¸ÿ@úÁˆÑ º ÏÅÒü-Öo¬¯onð›¥;pâÏk¡´Ûà©v+à£Vå‹.¼AJ|ÐðFñ‡]ßi‡Hÿ¯×„FƒØðq 3OŠ‚ Âk4ê‚q>휻r¥i6ɬÓàãÄsÏåW¯\½úê¥KO{ ¸2ËÜÕ+€³ ¬ÎêIîýöìC£  ½’ÑU±iuP´AþÛBÑé¬ÀóÎî+€§Ïƒøäº‰ùÇź¡34YŒTÅ®(¶^ÓÁ‹YDšîÀr¨ÊŽÊÀöÛŶŒto».ÖQôhÜû|Þíë¼ó¶s¦µú.6lnÞ…àÂwÎ^<÷Ð,Ý8}ïÆ™ÓœoË^éúM7‘ hÚ§¡{€0àD~ÇÃÊŸ¦±^+¢†ƒ(ì" ™/Jëq`0S’\t@9³vsyy‘×ÞШ`,® QEÿâóUƒÿõ¥åÅ&oþGþj½¶øõÊ¢´|‘GÇóñýGSÞÚœÅØ Iù;ü&ØÍõë›ÌÆ”âR(x–µc†&FùXdÙÃ7iË8efþôëÇhÄ“QŸ°¦`¤£­C‚@Á6"™†¦*5„ß=~¼¾§§/^¹òš©¿93›H|ÿ½F£¾¸|âòE°þW_¹ôÊkOO‚à6Y EÞWgñhn=PfÕ-MQ·T þ“L‚¢Å|0µ•LÀð‰û à©óh5±¶0JjQVÍ H…üËg‘©A ½ BðTëK!ãèXÏ‹TÛ¡ãÚˆbTÞ»„á‚ ”Øycmcýî;·/8§W·ÒõÍ l€àí·½ÒNX6éK¥~j6: Ùžb?¶ÝØŠ±ÌR`¢ýzpô»ÙyxQÌ î­ƒB€;‡û° –±cà‡õ„ZÓSà'ggs û?Ù¯ÍÔ~òÞðG½ç„¸\*—‘ѰÿóN¯+{ö?SÎ ûÇb3£§¥b½(”ãê,ýìgK –>ú™¦Ü¿xßЕ‹†ö㓊Ї~¾úÑÉâ¼>VÕ«ýÖÞõ¹m+ËÍçœIb[$ è±Ä'€/ÑvÕ=ñ¤3}úsfb‘x¿HvWç¸ÛqœÈΟ0u«ëÞª™éزõb’gnù¤»“¹uÏLÇ–¨‡õõŽ_q’NúÓ­óý®µAJ¤mŧ; Y´Ä @›Ø{ýÖo­½öZÃ"V³ÀXÀÙ’Ž¶pn˜(þ+”J`¯iQÍ™ožWßþ‡¶cÆn’“xôF>p²\O‚ú¿"³¸·ðʯÁÿ–¬TàŠ8ï½þ$•¶¢¤ {e…2ñ—ËÚJMýÓú‹Kì?®ˆµ•d´0hI.Ø’}ñí“Ūø/õ«õ¿ƒïAÕÏ5åòïL] IDATrëÄ9Ärþ¶k;¨0ç.¹ˆë7o V—Ö®ËÀ«¡ï Ž`eÄçO˜æ!€U•¯¾%Œžóü o“´( ©ÐˆÓ„d%!Ã_‘BÏßL¢%JÅø§é|ç/_~¿®Vjÿ÷o@üe0Z­wÁøÿêåK,^õù:·ðä'à|Õ‹¥:_/Ž¿K§ØJ¦ÿ§ëoâ™0yŒ Èi/~ðþ¥jy Õó—>ø ŽY[dIÕ € UÚò}ÝC“d4 ]Ìz ¿ú̶‰Æw¿‡>zIð»]ÂV—0‹?û­î»u®}îÖEŒ \ˆÝ:7ü'ìç Kض°~íF×v)Ï5°€¦ñ;fhx–cØ@Ý€ÎÛA¿ïö{X€ÏCçO„{^M}lï‘ö>Väü>‰"ë½Û‰R•IF&ÀÚõÕ›8}÷tĆ¡üÀ[ÎH]éï»"üx¯/=”¨ý¯ÚV:“f“€ÒÉj¿ùàÞÃ{?„—{¥í;›ïœÛ¬m¿»Ýê~ øç-tïI¼{ÝÛø|À.É»ÿž+Ï–ò$–йR.7{÷añ6U#ÛMŸWßþ‡¶“ o¤&ï×Ñ6\²'»W0à÷Û¨¢G”öŽD½e@\óÃúÝXïç9×#Š¡8¡dÉX·O.Êeµ ´³LJ‰¨ñë+7ê õZ©µ$‹ Wê~MZ'ŠoŸ(ÕëW.\mþ¬võÂh)6ηÔÖ‰·ÏÍ'æüd†n]PÑüë…øè&ü,¯ˆº JHÁ¥@²"ñš+FE³¡‹2âBvþyϤÿþ*Z²%Üèh }-$|7f˜WÓ$‹P.ª(Pêñ}@BßâÃóï^Y¬¯ƒöWÐh]ºtäÿÝÅ«`èÑ1¹\n°¾Ï9¾XV“|qü]f¹$W…ÿ+*_—ñÄ ˜¸Œ[4Ô>¸t^+@{ëü¥÷«”©¦^è®+MßÄåyÃ7°ÚeèÚ=Û²0"Û ÀÖ·æröìï„sgAÞµekõ3“½xkxváÜÚÊG7®/}~·!ä K%Ðap}-p\Ë4BÜ?oX!Éúk¶CW@­£žÚïaêÑžG¼ŽA—Œ:ü¸ß\€È?®ô£uƒ;Å*%WSÖ—¯}¸²tmko{{^†ÃáÞ0@Ž‘–¤ð?ü{V6¶¥Þ—"–7ß?¸-®^×*’å¤ðþÝð~³y¯ùåãû@Þ ïÝ©lT§¶Nª VpÏ‘D£Ÿ¼÷F°°a© FkÎÏͩ͢/f`þÍ‘Ú9Ü0W!¿K²,9&¾¿¾ýmÿ+t"-'¡¯ßÈ¡ï°Ôõ·ß¹U§ˆÂ€¾ýê«ñŽ „R û9×÷­L”}T” Ëñä!%¼†QDx0,üz–e™_ƒF½¼>à³Ù¤Øë‹/¶Ê%µzU­¸%^A/¼xäÿÒlüƒùg…ø|q#J/D³/šÝž'ŠƒõµÕåµPËÂêÍ5! $Mú`ÝÎÌhR§9—/‘òd¤ò0úåoŸûüÈsÀå€oGARßà¢QRmákRí nƒÙ”ÿ Ô8¢úèwZª`„·æ›ï\Z¼zõ]@€÷€¿ž€_ÂWܨ¨\qü½Á%“ÉôèM%9zÏœ€ñ}*èRß¿tþ-µ8õgÐa ó·T$¥Z)kõ²Z©TkVÝ÷°øîÚ5Óï’ú7¾ezîÒ% ºÇó™ßf"n¿ðôù-Ý à\ç,ùM 87Üøïš6•'l0VWÖn€o¶ƒ[>pÓ‡eõ,à^”p(tàôˆã ~7 ^\tq#¨¸† “áY7èÝ©VO5€ˆ×€²×o¬®,-¡òß1ÑËžHs=NÖææ¤Ùô¤·j|šIý§¤æÃ4ÿ£æ£ûï=ì÷ PÎíjÚ¥»ïx'=·ûF¿Ÿ–ÞNz›¶kaèÙX$i±gHÙš\¼0Êf‰ ƒù| bWp‡Ê÷×·ÿ¡í ãeÌÿ oà~DÇŽ?àòÝQ *•ý25dKÐÔƒãù×ã²_y¼á çâsejMÁºÀðÅÖÊz½þÞZçÙd’~1Öj5Z-1Ûº!öÅsõÖÉ­“¿l8霄ÿOž¨ Kºþ¼ç§’¼ ö¿újÀI¶}EÂ'¿!ùp7$FâVÂ@š`Γ/qûñãw./¾ß %ä¾^k.¾séêÕÅK‹ï\¾"êùš€ã€ç}®XP®8þ.à¼MÞd’£7ÚÓpü掫 ÿ?›€_B‡°ò•ZE-© ýJ•jÚê&ã1Á C›m†çÙÝÀö£˜<1›Ùçöëƒåõ[–?t.šÏ.àºÿõµÏ7>U>=¾6™' ·m®l‘£¦ ò挭[¶ƒ‰½=ÝÆ:]XsŒ.øcºýW1$°ïGíÐæ“úÀP!ôúýÍ2€µ•›7>\ùd0Ü`oÞÜŽÅã{Û‰xâ®XÄbòTï1ñHÃ\«uG<)mMÇse…9>x~ù¸ùèѽæãÇïõ>‚¯fëÎŽº··×[â¯ß–îI]va¡`àn@ ġ.h9ŒÀ€Â&e‹ãç‘`ÜÊóêÛÿÐv”]¬ÇWÆÈ©#ªUª1¸WWÉ*#¡' ËÌVŸw=ü R +zÂå@š+æËp…¬! ñkåu1+&æŸþie ¶8­Ñà€_üìç.¼}î¿þâç?ûÙÏÎþò—¸‚}é­ŸŸE÷Â[Œ‰ù‡Å£çëYÝÌ¿ž‡¾˜­dØw„?A¿óêÒÍVWª„VݰHú¡ÑD}+Žº ]îóˆÆœ'Ä‚›§1òw£"aôI ‚†‰ þFš¨}!€XÓ¶/]~ÿòûuôÁÍ7ëÕjöãS û—Þ#ù77pc”Ÿ.êétqü]hà|½©$GoŽ™ÄÓ&À¬ òÿËÈã§?Ž€U‘d­&Ãs©—@1T¦©ãv,’Ø×»–Ýï!…‰’èÛð-‰Ü~ ­ <ï–û™µyë,dnmÍúû7;Ÿž÷”©VÿÅâè×ø`Ä—zž‡ùAp²[1+1º‡}Ü1àwCâ$›ˆ7i´ÿWW—?°+™_¯Ó,wmmY,¦ÅBZÌï‰Å“bQ´©þ=á?¤þ(Èö·¸)hðµW9YÿàËæã‡_>l>î>xüeóÁÃ{_P»ÌÞžüð¾nÞ{tJZ¤{÷ø{V,ð;—@ï?|ÏIb ÂL”Æçp{,0 üœúö?´õpT˜¯\Q&KÑa•n°ÓKT©Lƒ“ªÜHIPžË…b™*IÙ*=÷zòwàŒãÑ:'œY²²X)`?Ä_eV¹A£ÑùÆkµXJûx)tÅ ³Ï8.]x›Ô ¸09ÿÂñóÅ<• ŽÑiݾß-ªç»‚;À|ÞúÒòɰ’i5B Ç`4˜`ŽÉÏau²¨çÏ{~ˆß|~oI¥Ä¯1q(®|M&˜,„CEF à@ F :ÄRùKW._¾tùÄÇøjU®~zðÑÇS»üþ{ï_è8œ€¸Ø^}cÃ4ëûßðbŽþâÍïaDZƒyÕZ­ZÓ”èó«Š„k”X \U›ÕšY¯Û ,lÛ¡àR;°ðÐÇï¢üÒ$·×ëŸbÞ:§› ä}¨ˆ» yš§Lå ‹¶m¿ó,Ë‚q5­6nù+/À€° ×¶‰Ù[z¤™‡?`/ÓðÛ±®øšL`ávµˆ@ƒå¥ûÑêõ\ÿðÃëkb1#T±@v*µ pŠÿWêÁç%Ñ@V¸U›€ršO+©âÉþƒæ—îƒòï?~Ðï?ì67Á¸wï­…þ])ÿî¾)I½î[èy·M ©ºf-Å*uQiâþÇ­Á9ŒK!ª³yd×ß_ßþ‡¶—Ê…-ò×KQñl8JQ{‘Ô,AØÂk” yô‘!€‚¬{—Ÿw=¡Ó³ä R‰ ‰Š÷Á'§ »²ø+~°’nµj#¨§Dâ@ù=Ò;\ç‚sÁö°ø²>šãçëDÕŸ±z¥c§Üàd‹ ÍÐñÁhÎàãùyÉh5:­|n8;s0³dHPF)=ÿùMPIZDÌ‚@‚¾• ÿ#Ɇ¥]Âu’à[iZ þü´àÏ€?ý>O.‚`ç­†]}~+}„¹+åŠTÆ"¢ ý´äjC…ÓuJË7±¸9áðØ-TÓ J’ÛŽÿ®µÉšæÙ[‚t<¿cygÿ«çiÓyÂÌõÁú¥% ¾<]Ûö{Àz¶my®Ý >î C\|ì"ÿ£'pþÄ öp÷P£à·îÕ2ºÿ>f×—Óƒµ«ƒõåk¿^Ãh`­T-‹U²)€ï÷?îwv”(HJŸ™d ÿÍ«'j/›÷6õ•~SæÂ/ûûý/ï=ìŸ} õßð\úhõúµ_/­_ßÞ–vvno·6Õ/†·Õ·J…œ ò-ooV¨´TÏÔk<§quñÿ©‰ÿ:/þk†Tú}{©ßLŠÍæ©þ©æ£û»÷u})Î<‚ ’úðîmŸézÏ5 ®[$B*(?²A£¨Tô‚ø>ðÜúö?´½PšÁÕÇÂìHÊ>NT4ŠÁ¨x¶Çã`­ çHÜLTÐ+ÕÖ{Îõ3QsäÓxÆå ¹Î;E‹ïYh외û“¬ÿ`NwÇøÀh~‰äù‰HH ´Û¤2ĨýôOOÃ?×ðp㨋‰Ø$4ÕqÚ®fÖ;~VªÔ]ƒö<~º"éÄôÌ’}ÊdQvfæyÏHA²í3þ” $Û†IÀ$¾÷IDâ¤iúó˃?)¸îñTF@Ø‚r©"ÕšZcìþV«Š©4x¯¢)MÛ³1 ­6?°-Ü•ƒÞ@0ÏŸàöƒÕ¥e‡}çÓ÷j½®çëëm¸ê܃Ãó¿ÛÀùxe}}y°¼Lݬ-­ü:óá:·»C˜¿ü…6¼óÅV‰Tþ0fƒJ•Ò&ÒØÏ ùE@yM"'4ïÉHþï6²Íz6ö¨ù°ùðÞ}éÍ{ýü½Ôí{»'âñ¸ÀßNö~ÿ-3?gž_ßþ‡µó—Þ!̵?~“ =ª\Ø(Wlƒe˜xœ©r§êÜ©ßÖ¤­J9áaó¾¬ý_ÍjMÒª ìa@p¿/Ý—¤îÃ/»aï±Ûªg~£LuàáŒûš³Ò?·¾}ì9í“×gèD|¢çùäL"Nc;{,•wPÏí·“”Ä…ãÅñõÏnÿþþ¡íÁh¹çévòŸ_Ècòf2ÄÈ‘t‹(Ìï»?þ&‰O÷° ÝT;ʾ/‡µ»O?ÿç|þ¢;KömÑ ˆXU¤‘xpÑ]3SÀÂbQmpL‚e±ªã·¤ÒÓwQM€ï°$" „4%P@ÁË…?çxZpÿR÷© œGP®TË/”dPò…¹ãÇçJ`TÀÈ•«uIªµùª¢¨”1—V­¡i˜lÛÄ퀘€Ûé™fÏõ{“9€‘Ûkê` 6$ªáaöž ‰±ðö§¿þë`ž°[YWX¿ysyó 6uw¼QÈÁ4$¸Úkú@qý,jŒ$ù÷#Ñá•'~¢xÏ«ìm$é ËÑkËË×>úXÀêµ~2ÉI[ÈüoßÞ*ÕÿÔ Û°t–eâLŠi¨"ühqâ‰Ñ ÷öOÉÍfMªI§ÍfÿÞÃ{ÒÃG}éMQ’NٞΠ±Xì¶LW;L¡õ??;[(º…çÕ·ÏĨQ{*vôí“×óll&Nc{TÉgèD쪚NÌÄ@±ðZÄHã9,Š[D¹îñ9tâ¡êË?Ýþ½ýû_j;îh¹çév, 2Ì{¦1b,ÞŒ5UAüàhüÑχô`züAµOµ#û‡vx}vûäõÏÿû??ÂA`5(SJ¥€ë  ¹x¾àJ¤$x¨¬~ƒ¹OpûYöGÙCøR+¤þökR.Lš(ÌåWü“½÷sÏrÞý¥îSÉåv¯@zúB±XÑÊŹ㳅2>©Ú,kõ êùê|£Vm¨¸  6€Æ4UÇÇÈÌ(·g»6©Ùs¸}JÒ*[Ï“ŽÖ„æoÀ¬Ž?@êüf¸ñá7Ž)`ê¦A”7À²½ÀÚ&tŒˆr¨»aß­ïûNÐ'‘€}¿ï†X€ÿ=ÑŽéb=•¯rÃS`Œ|òÑêµ÷>Z]º&Iƒë`ªó¼4¼³]ÊÏýþ6sgkcT´ ‚gˆøÇOüN:õ¹Ô¬Š§ªâgõÑ ›Í{bYªk Ijö?h>쇤GBð†¤÷ÿþÑC+ ÷¥è*õ¦Ù±Æý?°¡Ÿ]ßžNúö©cÇ(ÒÎÇÄɃv´¤ÝÂäõOÌPñÄ\žH"–JP1:ÉfÒs<ÇÇ&r|Šz…ÉŒp¢ürKOÑþƒöCû÷ôß?¼ 1–{žjýæ@>†5¿ÓùÜ,2þ&<9Ì¥”;ž#ÎЊ¤6Ë*@­ Î›¾ÝÍÞ”r}¾^oV0ÜÐ$^{×Â,îýÀÈíQ§‡ýPÊvܶ gh¡áªµq}€ßm¼}T€°¼¾6ÎpÑq‚ƒ`B× 8Á·|°í°ça2`LBÚëâ≊ò†‚Ô‡½éöÀëú]GVù {ç“•ëë­,S{ p-³´.%“„ܹ½yixç‹í_ýŒê2qýT<βº–ÿüçˆóÍýîãú_YR§pàK°¤ ÷mÉ–ï/œ œ“BìUªs¡ÝõŸ¸Áí¹Âaõíéx,Áå½d<¬Ú96AÇ ËÄâ¤}ÖBÖêM\ŸÍ¦i*‘8–`"ùgbT >F03l†¥ffRBjf†Žãîù:ºm\ÎǪ<9gD{s&.LN´Ö¿§ÿþáí¡ !Öþôê÷?ݯ×:6Ù¶¹Ÿ7Àv'ƒ ÍÀCƒ];–o“ þèáóûQ°oè“,aä· ïNµû¡‡Eø Ïküæ'ìÊÖÇkİrY €çv6nßÞ¹ý{ú­ýÒž#öÏÚfûT–DÍ+R£&íŸì Q­Õ›Rÿqóñƒþ©·Ðï½qïaGºÿò]i³úߪUªªz}$nÑ·Ky·˜Ó ‡Ô·§g(*•Nîpy6E%èÍñLü•x íæ _˜s@Þö¯—$‘£€1ŠòÏ$âq:Á²ð¶Ã¤Ób†ŠÍKÒKP nFvA»åìB®8kâú\˜sÛs~¡H6ÆÚ圭LõOž-(QÿàïËsŽ2<¬ÿSý{b¹gª½`f>_êNqÛ÷º]\„Å’èÁ‹F+«MŽ?.ŽÛAìzí¾ŸÑ>yýÁóÿ¾Ïϧàì¼+WJ ùÿ{tÀOªRR0ˆ*76¢Ä_ß‚ò{x…TFÀŠ¡¸=à«?DE…¤IÀ8À«¯ž®1³¶&dW³@8·úáõ+KKKÿøë›$!Èù/?=Þ{ê8óô[ûÇ¡û§•ṨNÀÅf©ZRÊ•¼:%.íZC©–UY®V[æÉf+ôl'´IaNÌ(0Î Ü~¬Ó]é¥n¥‡‰ûB?0ü`øù~}Ïtÿïûüø|Çs nOCéÿUt Èå¡0ZA,©˜þwý%]}ogã÷ÊÕ(™"É|¥Õj]Áº‚˜Dš¨1Œ3½z:µžÉ ëËëÙÁ ËÆ^:’\ÿÇܼ¹róÆÒdF gÀå÷žøÂ¢ÀO¾·ÿõ<¨îÀ4(+µ¦¦ÚžÛ¨ªJ­RSkµVóÔ|ÓîvmßC_—ë9žåE9€·/îëtObMõˆfËY©Ï‡Ÿï×À€Xëy¦‚ mÓ bcê7J?`Q€îè’B!ŒaãöÀé†N8jŠgYžÓn²µvÀµµ•°€ÕIì‹R¿ÿEÿ§4þ 1­qrœTöìƒBå8’‘hwäÿÄ<§’´ü,¶,©Öì¼Þyýõÿ ܱÞtLÉyô¸ïtû·)ªZ¢«‰’nvÎtLÝžƒ¹•ËÙù¼á>«¾=§âI6-‰ÉM§2’Ȱl2 3ƒg¨Íe’0íœvÎÍÁó®ç²bV :P‰8EÓTŠâ@ýK'aÛÐYŒ8Á±P×Ê·gÝ9t¯kX@Äó³~ë:äs³Ks/4ó2´›rÞôäQÿdèŸ.»æœ|&oÉð÷MÙÕ-yºÿ²{\± }²}'èÛÖÁrZíÙüüáÉ!v-ËA@ ©ê’è~_Çš®&˜ï¡EÐ<ÿq;®ÿ¡þ·‘ àÏO´\_'žÿaŸÿù…ÐåÊ? ø6!{%¹hÅfs¹Â 2’õ_ƒüoí¨ÚpxåÊ·_}óíb …çÎðû1Pð[iZ Fð“('à«ÿ7²ë«ëBv-›aéXŒæÖ>ü?—Wo¬¯,¯Læ<ž< ýŒct:æåËgžj»ü¿ÊÐi4§8öT @±¬(–n7h§ &@¥¦¨õzëdë 8æävl ð½Þï¸ßÝÒä®·¯Ó¥—ƒëÈc7DݧŸ¶_À÷La&ÀAÞ€ÎæT0¡E`­‘™a Qÿ¨í} þÃÔ?ãÅÜJÒ„‘v,©„]¾¢ò*˳ümá“å›7>¾±Ç Ó}òFìH}Pï'¤‡ÃaK{beψ— ÃÝ=féT6{jÿ„Ì).³kIZƒíô:\§gf;ô¦¡?âi·«€˜T•*Uí¶n¶3†S˜3ýÜöÿõíAþS`»óY`ì©$“•R `3G%S©Tb†²“ƒ§ Ï7ºžDI@ñ€:Æ0´ ]æ3™ “°X“ËJè fñX à!‹ÇІùÀÒÑ víR7'9Ý•²á”¶#›ŠeÊŠ)“þ)KÖqÅ– ¥ç«²âø é¿õ_‘Že·Éþa7/÷<ˆ<»ûí…Âàšéäƒ!²€‰¦,äíÀ fR… +z~x2ÿ¨Õ>¾„Æiô<Õ>q=æa?ÿƒÏÁ‡¶‡ˆGaü|ÂŽvä?ˆÿþ¡Ô-–»E²s`¦¬b ï¾ûÿêÖÖFEÛø÷!‘ýí;Ã÷·¶¶”í+_c61iZ Ö8ý?~mÏf×y!ñ©d’åV}-³²Ê¯¬’ºßËÞ½üÄW¥‚åÁ;:¡O¶î(’þi•"@IUK¹ã?:>[@ >µ¨”Ô¢Šæ ®Ù€í[ÑTY­VëÕÖ|7`wÞxš^€A—®tŒqÿ@§K/µ„ëË5 ÒCOût¸qPÀwc òœ»5LØvüд‚6130³b],ûuûÝ^T0¸ßE¯ ÙÔ«z>F“Nñ•âÆðöÆíÍÛ…ÒðÎfñvq£°y;gþ}Äc"Sþ£0ð‘¿·»7¿+¶Z»ÍìŠû'àm¿ÿ]¥˜jÁtŒvgÁÐÑŽ9J…ÉúöeåÓ¤SËg„43CÓ™´”MRt†ÍJ\ŠAODšN̤¿Ótt=È¿(Ј7±d’N¦ÑŸA14\’C@Œë46ŨD*ŽÞ¸ƒétõ|TXl»8W ¾Hkàþm}S7•Ž£@ÿä93ÐÝÌ›Š*†éÈU¹b ÜHv•¹¡ìv];†ìù©þõ\“#Ë=ûžÝq{A–VÝü~¼(BÏÀÌw–iàV>2¾¨ºu×`ÇGH FŒ?¶£ì#€ä"LÍÑõ8× ?éÁó~Ü{xc‚ëÙ@jÚÑó±@…yOÈ? ZÅrÁÀ]’ÔÒÕÅ«Ww¶Tms(«…+Wî ···”÷ï.*[›‹ÊÌ¢,M TašœþŸ™Ì˜^f=K½He³i.y$Á._¤o 2«Ïgï>yT6,>É™§€B¹< ýËaG‹/”€äfÇl¤¦RÅÌÃ$`’‹jÐÍrIÑ”ZC­ž8áG‰¹Ã ôºØ@Ë¥…Iní½ u?lhZxkøÙ¸>€êû.Ú~Þ€á­Í©`B¼©ëÀÜôÉÜ7lTÑÞ_Üþã{DÝ÷]ã~Ð@èæñœnxH—¦òl¯K#32¼2•T‰“Sr^ÞÒÜÂÒž¥ q   @ïžj6wOÍHÑ E¦$K܇URj‰ƒûÀà>2¹]¬¾$U¥ ¦¡ëºÑ^°œ¦™üìd}{(:ð\š’2,ESt:Ãg &•Isf&E³iž¥ãqPj8sýèú pü,O§X^X`ûL”bA|½©TܤæLó­¹¾a÷y×mÏò³Ž{\¶Y55¬lÉ×’ €=Pµ-Þ0 ËT@õ›2þúÄß4‘xðâé²Ò“CìŸ<êÙ 1ZîÙ·Çý¸¿±ÞÝß &a€‰íÏt,RÆ/` ŸçØ‘#GgâËa§Éñ'€ö¿Ä<Ù(<Ús`|ï?ÿýÏ{Ïk5ßôÓŠªÉ‘çZ¬<Y‚ý …•]$i{¸½sewwG’‹;[;W®,..jÚð‹/¶®(»Ú»;›;ïoÝþbkû΀iJ ö}QmÀ̸o63ô8°áhr°‡Aú¯__Œj~\ºté]ò…ÿ[¶ãȤO¥3U•Çïïÿ” Q÷r`«”^(©@Ur³³ÇÃï…ŠD6|‘M«eYÖ4 Su)¦¦6*˜TµZ=b 0¦ârQ÷»¶áê®”YœÔé[+ÂòÊÊåP¢á†ŸÞºõÙÙq}0AÓöó ÏmNµˆF}œûžéu`¤L)H†6©?0;¬Ñ^èâÞ €]^ãq! ¥Á~ö!]âZ,íY¢’& ª—óúžÝ œÒ´ÝS ½›•¢d–œþÌû`Ðdµ €©‚! [ † ¦ÁB{úŸ3@ÑÔ·—@…Ç4Ť€XdáãÏ ¼”‚·RYèp"Ťҙl©ïæC+´ÉõôKð‰8-dP¼“@úÙ””¢cJ¦³’ÈÅ@x‚MÑÌ Q¸Bn‘ñq½í™aÞŸÍφ¢äMvL èŠ«è@øeà ¨Éž©‚°Ë†¬)fP ú‚ h`öT ž¡˜‚/ç•Qÿz®Û €Š{^}³;ê¿%7?r¬q¼¨w ìGÓiw€è˜çs|Ùı#/ý/½r4Fqæã.!!OÏÇtM¯ @æÏÁó~× l«9ßÀß°¤4ú!àùèö¦ ™8€\Fí¿½§îìì¼âõjëÊ{;;‹;Ã;›ïÌ· ¶ïܾ·µ5Tvv¶”+”¦ªxÀøÁ`-+ ²|VC4E½˜JK¾$­®fÖnÜà'kï\Šüß²ÜW¹™Þlm¦ÕMyüþþÿ‡%€:~|ä¿XVK/hUEQ4’èA­ªªT$$±P’eU­Õ0€l;“nÔ°l¥V×ZÐýIÀúÖÄÒ|ŽtÔÅ2-:} ÓþjšB+t>ÿ|c¿>1…š»Ÿ7àì¦1Lè¬`°!– 6ѺCcXžã]ÏC÷®úcF8LIìG!À>F"?@à§qg¹Z[Iâ+]Â×R¤Çiñp>i¥ÃÆ™çõl°2ЀڿaŒÈ Å$‡§—8¸(~¸èý *GØÕ";p馠Ýð±~¤ üŸãÓ>U’“„¤3J‰«Ð*ƒ¯¥$pz®Å7RÅ•tY0¤ã¬özaµÚî)š~ 3œN£!¡À}*ðJ )'•Rù™©«cþ½Ùns¿ÿãúö€& ðsšJ ï㳩$ÅßÏI&É¥8\`x¸~LÃc ÈfaÔáz\ìËfpu/Áe$o°(®¦~Ä?ÃeE)m¤bð;EQ‰$ÏÆIf s\Z3ŒÀô\²Ù:Ì;Sîh Zù¦‹µ2`Òz ó®© ( ä Ô)˜fUnÍ&Là ¦¬ oEÛwa‡Ý—{&¬À€Dýcþçcˆ‘+`æ»ö"æˆÆåÿ•c3G_y9B€†›~d6‰‚ŸŸšíP×ÝKLº“ó'šŸ˜QÌÄ좆3ßpÄBŒfs ø„nø” ²Pýïªww÷Þ“¤V£qegçÊÕ««ä¸‚ÇÎðöí­áÕE}´~¿¹³Y–¦jÒ°pÍñÙl&“¡^¤ÙŒ' âÊ f®xŽàƒc7s§r‡Tf9Í꜑$¿ÎææÄ)ϾOaª³/ä&C‡\jFlj¸2I}vÐu­ÙÀ‚GåªÒlô|0ü»~€µ0mFSzfü¤N¬7–×—A͸’fkŸßúl\ 2[¾´Ÿ7 ¦M®;ºo¶ÜÁŒ…9‚`ï¹óÓ ˆÒ'YI ^€ÑF¸GÐ |§×·} <«ñ2í0€,ô-r™t†e6ÊJšÅM@¬ÙõàV‚1€#:ÁKñšCLŠ”"J”«S×3à€eú¿_ß>Ë¥`®31†‰ã`RL†M Y°Y¬¸$“¢Y 0ÒôÑ8…{Ü<ßî¤qE2‰3IଘåÅ4Øû±xX?Ͱ åŽ)àÿ þh:À62~Ž×z¾’°Y¨<ˆxl{àñUÅ·U¹¦èÀ´PŒ²]3]YöXË ?*p¤¶ƒœÁÖ€¨è'@\hw`*„n¯ë„¡?i.fx¬ KË7×ÌÑã²0H…U§AaÛÎh|Ù#/KT€tϽò ÀË@¦ÇÊ~8 xz~„˜ŒÁ ½Ðœ?ÎO“ý˜\ÐÑ݆k8¸·ØEwn!rûÏfRãîÞŽr¹ºw¥Šà*¢ÀÎÖâ{Û›››C€Í (ûÄ PiZ F `䀀½–f¸$ͦYŽž .ÅŠ@€ÌÚÈð= àâ;û_ü.hi.¥±V{(eéfóàœCàøds/”&°y/IÒ]<ö¢˜ —Ôg·` kõj½Z‘Õrµ^³üOypÉNzÙ²ÏLètÔhbkI³¼Ï‡Ÿ GõF&â–¤óhÓÁ„ÂÒ*PŽÐ!IŸ04Žea6 À!˜ Y'˜"^7Ú ˆÿEyŠZ Ès)u4QÿÄ X©¤ˆì¿öêk¯I—*¬ÿY*0i›e0Øpüþ=áÄHþ=øìvljN¾P‘.½W‘«+È#”$tEöŸ@@Â@÷Ììö½mGý×·3¶M›NÖ±R®Å±< A*›äœ   ²):ÎâR`6ò,!G9`‡Á>™4zÁVÀØŸTÒa(,wDQt’C¡D'b‰P*aSV†£á¥cp†ì¸ ªB?Dé =9vôè+3q ©€»ïíln]Ù»{·zõª”åWùÆ•E@€«›Ã­÷w¶[­ÅÍÅ­í!±€lIÓ5í2ë×YŽ>B±|ÀüÅD2ö Êk7ÖøÁó}÷Ý]~T\K±úªL2€ÄÆÇ³ï3J{0>^ÐT’9Êôª‚Pìî‚üû@øR#³ô @´†R®¨•²\mXX&¼|dÀ×–8×ìº:Ýs…›7ׯ [Ö‘TËOÝúü–„õöMD§ÕòIÞNs'ƒ -KXù8´ÑÐ#«¼mÓ}Ü ¦¿Å’:@X7 SuÃ Ê à»i- äY9 ¢_Bæ¶:JïüÚ«ÿÛ«¯ýX’.2¬NŠ…JÇk€P&NöWîÞÛzøo¢%•”.J£+Œ·«°…˜†AñgªÕ"ܶm,à:€ēȾmwºJB6>%f`²‚À§i:‘Îp,Øö©4Ì7fÙ ¨r!-fz&6C'˜žÓö€|E“ üÒ¬”N1 û; ¤Ù$‹!€1 t*n2´ÉóŽéI:ÐàÀ·ÑwVz,3sôè‘#/‰Ñ„Êˈ6jr½b)ªîÕˆ×O‘kž%[Ь›5=助+0"²§xþˆ€U$¸pØëÖý^cÚ³ ó£ "&ÜX_  1îæ Ý@ÝN»­ÛXržŒ/{ôh¼|çß¿ø·Ÿ¿øâ_#xe&òŒ?ȼiá­<5?P¢q·zèN̬:m[¾ƒ©Å,Ýqµª_w£ýƒèjF—>›ìîílîn½¿½swïÊÕ€Íßjó_¼Šë‹‹Û¸¸½±yˆzmJ ö‰x´¤ Ëq¼À \†N$3I6# n,2$à{À…‹£¯]ŠãFÖ8l4MnmÂ×ÖÌæøœC@wd@Õ^ d·O §a]d;»»{cÖ4®Ï:®Y¯×”²Z’ËõyÀ§çbš.× œPzÉ6 Oët/ÂÍ¥›= °ž¤†öéÆçŸjŸž?¿o"ÖÂnóh #vLØ~û¢k ÂòG0zÁRÖÆ 2hÜ™˜†¨‡Y°P/ª¿{=Ç P\ §~ÏâY•Ïh|•ã§iÿë Âóã×ñEâ¤s\+VŠwÀ·u÷ðƒìƒ1Çý›†nN(âåç8éÇóêk¯ãË_ß7" TMq-äþŒ~ÆÖuK×;¸Àì¦åõ|OÁ¡é´È³.'}ú„7%JI òC‡Ö Ñ¥Ã&Ë“Há‚kZÄ>Ű ýd’46åùg’IÎÆëÆR‰¤‰ÏºtŒ¢’.ó™rà‘JZ +~ìÈ+p#T>òö¡Ñ ¾2CÞ´äª 6€¢èèöó@í˦Nñ~À ýc¼PEnض_s*¦w ¹6VÙü}™9š8þ@<™ž¤‘`”è©ùÑö²Ø›˜?¸øoà¶²wX×ÔVËDŸ!î¬a$‹e“>€ì|Ðж«»‹Õ½½»ì]‘² îÙ¬Ð:8êõF ð„­€È¦ê‰8€u~ÍËã…4Œ8£&RùÆêàùqQvñ3ì­Ø6_Q¨7ãÙcB\­œ‰J:\°/,ó[ÈcùƨFmAF¨” s¸*Xœ+ØÝÙà6Ò$¢ÿáy!Ùrê +H”µê|+Ê¿%‚ƒ )e‚šíZ¤Óæ{ƒåõ›×—`Ø’Úöðù0ûôü„‰ØÊ¶ÂšDáßÀàQ &@i‡ÂÚ‡ËX$Üò Ün€íNÇ'HŽû‹1Ú‡TÇ ¿.É lcÅ`ÇÁÌn×<–ǨzX€LãÂ?MÜö•$ªî×òÓ×_{í'RR:Ëeõ$s'žÒ]'É@R`—ƒø[09 ãÑ£ä GKgié'¯½öúO‚Â/¡+@NV’¥j±JSUܼ z§ófÇ2 óŒa{~€%‹zf–O§M:á¦ù4ïfÒ¢E'ã6ÃûnÒ«Þc¹4òÜ{$&€ßÇY0çMx¾¨1$1“Js+€ø'AÿσÆx‚‚Ÿ96)Àœ50©wLŠ#p3nÜF¿˜ëÝÄ • û$p( àÚÛ::ø: ÿʃ´[h¸ø¦ì(ȪíÈí޹‚‚~@¹b>”5*@³6BÍзp¹g Íw°¶¼´´„‚ C ÚÙÇÌΡƒ¾xÜÂGÆ—PL%^ŠÏýègý zãtzzüѶq À#0ðôü ó§íZÞÄüq@?aQL-FLµÞ4ëXaÄr´Z=4ÁéhˆO3€½ÝËïon«—k{W÷ö4›F½V‹j.©XÝs ,€Û_,‚ðƒðž²%M Ô~ÀO^ÿ[ ¸žEÐ$—bãìË©FqHâê¯o®ˆ?þ[dßçÏŸ¿pÃ(v·b»ª¦Ö+VûMU©êVû é6€Ù†_à¼ó‡ô æ å @%_P£þ–fg.!ÜÝÓ±‹ï²¯h4™žIW« fP.—ëZ+ôLÃÖNž %êäü|Íñ» ÓÑÎ V„•ååÁRº?/©>œ«~.Ä|ÂDô[†S“ŽzGÁ£ÜE¯í„®0X>\·C4æ{ ×j ÅóÚMŠõ å€h=ÐÃ( `^ &`y¾ÊóZšç‹2Íà*Z¸(X"þ¿¿}ýÇ?‘¤·ÀôN³•B³v"+Ô?èoÓUé®s&:ÁU€·¤Ÿüøõ¿ÅËá>©Ç$‘ÿË4<Ž*–"¬À 0; Æ‚i˜ z§Ý±l¬a6’#Hï01—æùd36˜Žá³´Ãƒ»B:+À´ÈˆY)]~,‘Œë´…EQp<Ÿå` qšbÀ:ˆÍP¨âÓ†Ã"+ˆv›„QÐÕ˜Â[ÎQ“Á8N]1ÓÉ™¥ BÉî|åØÑc”îËØ) ñ¶Ž Ùe\Ý7€ß+žå(>Qòèö󸂦ØJÅT” RÑl°•ÐN©¨RK•÷­Àn­‡•ܺƒpãC˜:¢üEb§…5;\³ zÇ—7J,•Läöóßyéå#OŒ?"€;¢è xz~À+æ uƒƒùã5°  Ô˜Þ\«°ÊÛvöj®«/„Vwoâør±£ñòfXWúýG_~ù(€Ñø·£ñv €ÉùÑÞŸ¶¸:ÂÀþüñ]@5²ýÈ×= FhÍ7kžZÕ}€?Ãô=«öžD€,ÈÿÉù»wwÞßÚÙYÜÞÛû€0€ºF2fŽOÊ.©hl£ðo½·5Ü" M ÔônÀÿüª ðˆ¬”¢_L¾H¿˜™‘° ×ÖV®Ý|þnÀ·ñ¨ol–còІ\_( z‚o–ñMzƒJÀ/xâ!P*Íbç°¦T®¢½PVærJ)€p|®47òììâ*IäÀ­ ºI}vßj4h0*•JUOžç„Z=¨Õ3'ƒVK«…¶%퇺' „åk+7ë­Ì¼´Œz«Îâ Øž³o"öÝ J/YÆ8x4‹»ñ`¾¬­|â€=`„ ›Ö°CŒôÄ„’¾IAHZ,MJê¸"8`YöºÀB:Ås ûU^c1æ§„«Œ"ã:¾œ$¯H ¤_0>Í åd*2ýþ£À2l™·­ÛFtXÚ²ô‹ HÂ}ù—ˆú'·f¨d•".#uËÀ<Ç¡½ ££ÒõD i—²h›‚³RZH¥a¶z¼/ˆ^ÊðÀ@ ˆ¬(d³é„Y`ãÁ§Rq¦ Ž îÃÐ?bÊ¡ ÄøÀ!€:àdʤ:EP+{ ¡›ðGR4ëƒéÉs)šŠaRÆ$}®+ë&´n®<4 ˆhºý@v€FÞ!‹ÀÙt;¦¬¢¡6滞We+Õ<#+Ðí¹}¿Ô`"¬,¯ÝÜß"Ðë£#§†„¸¯3èî»\üHLÞ©¤YùÎÍÊÌ˯›‰3éÑø[ãñ'Ú„óc¿ËU€Ñb¸ó·Ù éÁ.0a:ºkšn­Z3u[·áŸñ øœX«Í»›ïj{‹Ä €P­iu04µ‚e”°d@k Èÿâ»ð‚‚‹;uiZ žÌ€aX`I6É%“\–ΊƒÁ`íÆòóó¼}¾êÅR¯ÇߥSl%S‡ÿÓõ‰7ñÌCzuJ®¤äæ”2€<§b‹ÇKù9b8ÒÖÎ.ɬˆ¬ ‹l\F&õÙ-ÅmheÌVUš-'´1´V›ï‚ø«ýÀõ¤,€|×CàæòR ¥F«¾¨ùž¡{0"cmcÏ—^š혡áYÂÍõ‹[<Ú– í€î†‹YH:¦é÷@Æq÷^ò°ž‡›Ý> €Î¡—™lÔxe•®Ð ˆëvL…¼Ê%xG:ËJ”™|AI<މÁÖ±;.+; :Þ”¥³t9?܇&«Šð ¶PƒKU䟪o gÚ`O8 BíŽ$ÀÃde/éŒI›6írRVâÓvšw8+È€>ȧ2`ÿK¼ŠÙ ›œ¡¨˜ ª>i$i € “â$.;F%b“L¥˜¤ãZ):5Cè1^RL ”Ŧ–c(šƒ“E ®£e{¿_€/Ì)O€ÁZÉÄ1ËBǾ){ (:r<¯šßÑ1©Ð¾€««0vÇë˜JÝFذeE³Iè00·kÏ÷¼F£ïh5[³Ð³ØŠ­õACX]†+û[| ›ÙuÏþÎ8wÖ\Çí÷¹Ä±cÔöðÎææa¥r ,€X"™¿3’ ȉ`b~ì·“ùãLûóÃ~ч=Ú;lBy Ôš û¼öú–O.,ÞÝØ~ogkçrmïƒuµÚÐ0ŽN)ImG¹µ8ÜÚÞÁè€ÅVëD«¥JÓ5b?åxõu\–ccì‹É¿JÑ1Z¬¬¬,¯¬E à§ßÃÎáÁËj’/Ž¿Ë,—m‡uþøâø»Œ'ùÒñ"€:'#”œ¢àΧ㥹ˆ„Òöö®;ªÃèZ†¸êï×g¯7T·(c ÁjÓ©¹¾ç(–~`‘ÒKm˜ËkK¡WkzZ̶¾`Þ^´ÁDÜ!îö‰Õ‚G1««Ù„›.»d}’:@W1s ©#UÁ@ìIB@Ìîû]˜»˜$À”@~lB› À÷íषÁ¨¥*w ÉÑ4f„¬né¦îÛ†Ó‰N£ÓŸ}à*€LU‹TµÓiÿÑÑõˆ‰ À€^&Jé´› Í´m¥Ðt)Úæ-O ½¼V‰ `ÀÏsi7ô&:F¥)ÚBÀ2hPñ\žŽ3®Ë`AQ`𺀮K³Ž™¤xd:YÑàRflj–í]µßð€LÒ4§dR3ÇŽÉJHú€3[NÏë;>2Žg÷MàžÝì‡ø# 7²¯êt‹ñƒ•›k™Ì` êÑñ=ðKøâŠ•+Ž¿7€F$Ó£7•äèM<ó0˜+/”A„¡Ÿ€œ’›U ³@XŠ?€%mwAŽÀh"õÙAñOÔgWju¬ R¥«UÜëæ•…ÕZ»Ý^s}ÁVÖK+ƒ¥Ðõ\Z\cA_0­‘‰Xsû0ø}饩-„VÏr«Ë^[Õ€Þ㉫¶®c+,H¬OÚE—?ÆÿãþôT7ª ‚)Ëà‡Ò€“{W·wwöö®ÜÝÛÛE¨VçµfŠ)ŸËJ­‘ìƒ4”±,RAš¨Ñò]T\<}úo^dÙ©b€æ±4¿vL1ªþ=€Ÿ}®XP®8þ.à,JÞd’£7Ú‡ÀŠ`Ì”Yè)ðèï ³¥ürÄ]€]x¦îdy²>{C¯7µ,WŠ€‡Æ(ÇÀ-)ÑÛ•Ø.ÌÿUI œ5PÖ6Fk„¾ét °‰‰ˆ•…Qèq7=qÞ3ÑŽ`YºÄ=Îf’ÌFœS¦tÇ$€,Àç6<ÓhG'È —”.íï*’‰‰vË€*d#Ðà|wÚíÃpC˜•pÛ0û}\Ýó“ C§<^pDd>†A`€Ê|EWƒÓ|Pô1& ˜#Ð$pY8ƒ‹ÍÄcpƒFX|Ú¡‹¢L@=ꌕp)Ib:IQ4Ÿµ26§¨7‹‹5Âjó5€:ÀÜ_c½g8¡¦Êr©Œ ˜ ¹bYš¨‘÷>BñoNŸþ[€”IÆŽ¼tô壬4XN°¶:Ȉ‘ü‡Š£Ÿ.êétqü]Àš–éÑ›JrôæáL¢’ûQ¾4 ýƒ^ÊseN)‹eîGy€+mm!lÃ#´ŸUŸ]©ÕªJ¥¢ÊJ½f:Þ’ct§¥—pHÝÁÀ•4wàbÁ_LÔÕµjpâ…6šˆpM·ë“]”^š Ñ 4Ö\,#`Kw°>œ#RZÊñ°f,î <ÝoùÝ t1rÁï’¡·¾;0'½Ëqé*[,*zÇ¡µ\‹e"/Àzó':~¨ $@³Â Ð9ct: 60±LH¸‹*ÜãÜ$•4AÒq=OjOºiÇøÀ4ÆdE& Éâ-)š‰'i1ëÄgLP!‹ëéEÅc *ÁÄgl€€4—59*i%7 Ó ×ÊÍH'Ié +Ë™4 üž˜‰±šó¶îŒ={È Ü&ƒÌÀ•õƒñíáʼörÐ2@üaü»ÈÓL\!„=Ü,¤æÿgîݚ丮sA?Ÿ3”Iö¥î·î»ë~ɬ¬¬ÎDÉG–1 ¨Êû­ª"f, ƒ p:ÝèFŠÐq8æÉ"xdGÌ£h[ NÌãˆísžGøó};«»«€¾:´ @£»wUfVå^ßþÖÚk} +hÇku?«Gº&éjO×{`/² ÃZЇ¥Òeå^”÷e¥Ùäò«/¿úÊ+¯üþ7¾ñò«Ë,˜òü÷_’"xQƒxýûÒ“Ã'ÒIt íÙüC4™Òw„ùÁWenàѸŶ®m¦g¨žÜQ·\×À…Ã'ð9¿-æOc]µÛU¸Ù¯¨DÁf W@£?ÐØdŸÑJ­*ƒ,Ô|ÏŸ¯ûÛ¯Ã(­?\_—JXŒ(åPø~Yijo×ñ{¿þµeõŽÿá‹5ûÿ¹_žÑ¼z >@}C\¥€*¿Û¬o666âL@î<ýÓçàÿüË?óýÙ½£þìM–íf³!wU«×ó5j,\v]½×—^Òåž;º¤ô5ß§4‘k›æèÊ£-%°@¸A˜¹ú+å«óÉ£•”>|ÿƒøÇG6Ûˆ`Ù6î(\TæsǪ¯žÐ€t£X#$ E«RÖ©þŒÀwÍ®€´=#¨z+ *PŸË ’~Üέç•T½™½2²°Tƒ»çŒlúò¶?¡Ñ®'¥gN2r8NFxi%<7 »/æ#É+ÁÆa²R1¿^`…B£ÌzÁ]r£X¡t}-ŸJ¼š0)û™L-;`…ü~' Ÿ¤‘O:@‹|ÃK‰|i=oåíÄ+i'ô¶rò}iÇ3Ç>8f–Gf0»¿Ùï¯è¾ëS÷Õ÷ߤíG ÕÂßY€À˜‚¢Z/Ðo›9]-ê“ `Âtmßûð#!þöðá<ÊÛÖ‚>¨ïóþMÃ\rué•—_áciöŸãÚàò“'xÿO$ IOŸâÇÇ냡çü0 ˲äÊŽìp”œ[ÔqEí°ÖS´í­“Úhx¶:ãS€Õ¯m6Å;Šª®Í\€È6u]SÚm°~e“`uµ,-Ô¿CYðÚÆFWG¨êåv…€µÙ¸´Ù.€7•}þ/ÿôèóþÍ?ÿˬ?»ñƒ´Žú³«–Öì6[2h€ÚUµžÐXP]ßèYäiKRtMÔ€ N4L¶p²-Y–^asñhšO](=ø¤ôÑûÅmD»·}îÕx¢%|àrÓ‡)Š˜Ô% ˜Äl›Ð°Ìdâ:cω›É Å ¬´ÑHÆŠ@)a·Q$ÝLõóÄòòÿûyÛYW¬Ñˆ ÁÌNem«£å?!Ù’n6’±"PJ¸þÜHÀqxÐT­;rF@€†9šþ"ÚõÆÁ+{דL%m' _¶<ðìTÊËøÃ±H¥f÷7Î…ß|rÿãñ@T\O0ë ìË}‡ ¹4w¬ã9½8ÒK2l¥7™`Ð+•2?_(x±Ÿ¼¸¿0QÆ\6-*œÒ¹ï/>*ˆ©Ö`g‹;„@7k"J½±³ÀÖÖb=Êú4;5Y’º-­'õ{ª èÅЬ•Ë¯½öZEîµ{5@¥]¯6z5iÑ ¨Àó;$ YÌP(¬Á €¿¾^ð×™4œÃBoäs£WgÝ›Œ‹EI*dR þ« õ ,­¬¸îÊʪCLض?™‚ÅÃß±Â\üù»ñçqIÚÀ™—ò ¿”-HFÀuѦ¡œz}¼¿> ënyaÜç¸ÅqªñŠ@Úñ¸ ¸ÖÓÃà7º9,Û*¾ÔË£¼_zïþ‚ìÂýÅzØg÷דŸJO¾€µ“÷?…öÿø±àR¾çÌ1Náz¾ ßfË@Üó`{ÇR ‡J6 ‰„òç]€5@»ÛiƒÞøõf·§IÛšªÌra:åÍ×6˶Ðmk5bB]Ö·T­--ÔWuÛOÿjíÁ+-øåK@€rMnü^­Û¬Ê¸ÞÍÆf…©À³6î…yaOX€¥#Û›ÓPà¸ÖmË2­¿Õ ºX_ŒcF‘)2ÈtüzÚ€=uà†¾o\óQ.ëÙÎ| (Ne{i]η‰_ï0…3€#É Õ±ü$ðaE¤ Xî¬  · 3˜vo_ÀΡiº?ÙôÒGÃ+øñŠ;4/ÿkµ1âö½åçRiXþR:½¼’N%2Ü=ç^ xÄÈ_ÀÿCþŸâ°Þül€)³É䀫€òë%0Ì3ƒa´ØÅ<¡q> Ö/˜ŒÇ…Òz 0  p‚R© eS«¥5ÉKg<‰b@ëk™ ®)±´Ìd:µ¼¼ìÚÉÕÕ/aÀâü’4øRŽç—²¥¼´/\ßèw§Ë3u™›í»QÀ™„¾¤ØÇâ/^éÃ÷$`qa©4{–ûQÀÚ}Ñ$Èr±þƒ`¹üøð·ýÇÏž>í?áOøù ð€sççâüu,ø˜LΆ·3ìxL)eÕê,åæ÷×ÿê§š¬Èj]€¢öE& ªÈ{‡RV£ÙVt]U:Zc;—zõ?¾¼üòv_– êß!”\@®6»µß«×åš\«n²/ÜÆfK²¨®O,‰iV·ÔHç4Äx¯—”U @§-+­n_íÚ†ï„ÖpWèLƯw\&_kUÐÖ€I/ b®úLµ—fªô|‘ÙëE]2WÑ1¾C‹0ß¾;™‚ÿÓ ÀD±þÛy#ñ ßðMc»š#Ç©Ã]§c`ƒ áŒF#øÒÌdÿ§ðq •ñ¬Ë,3?o8BèÓºŒ™æ áè ‡6®Ë5†w’ ŒÛ¶éÂêíòæwK%sèe ²fn3 z"CgSʦ¸)˜h2ö kë" Ë?ˆ@©$­¥——˜”Ïe#¶ Y+drYfý$â‘Ì®¼:òrÉUŠná¢QøÙù 9ŒÃþ× ^C lƒã{/\ßâø‹×ö8½ f?²[à¾ãé‘Ô ÔY¡¸[*}rA¹æþÚ¶C NÅä}LÑ"w\éðÙÓC0,ÿ_`ÕÿâñáSüüXú-ã`çÏÏ“ùçD¢ŽÅ‡_Øv©.=à¬*Œ)ZðbŸöz]Y©Ã†ô–Òë‹r` Àk‚åVSÖ5@{G˜Z)ñŠÔ“ J’šµßåñ¢á~]ÇimnÔ6[åÍ*®R®×¯YÁÿZ¹ÕÚlmTZ³m"Ûd>û-cÑå—ᑆ‚}<ާЂ›Ü©Ëª¦t©æO(Õÿ!$äK)ÚŒÇpÐUçŠoK/©º²ê¸'í¥K>\h#"^Ï…¹€¬ëþÔÔH IDAT(ôšè[〵ež?aVA8Ê`Uˆ k›™_£ÓÓ±ZÎp䙯,:.–ö˘2x_æÓ†K0ÌæÒé•wàWVRêÆ{…SÅtŒáÈfb`IJ Ó‚˜æÈ¡èíÐЖ¸Ð‘µ¹¸làið8\‘¿¡ØÈAT‹:v `1Ó4š¤œ€SÑóùuæüdV“¯¤’Ò$[k=—˯™ýO%“+ÉT*a'Œ\Ê¡îÿbîù Þñù¥A–òÍYi}=gÆ€_ß»'×w7¾¾…ñS®ÿœqz.õ'ä‚°®¢&uôbD”Çõ•Ü^Ö ™ñaáv…¾á>|Ž¢óÃm<“HûŸÀø?î?~rxøL‚ßJÒEós6„¡Â÷¤8ŠýeÂh0Œ½é”ËE%§/f Ðí©=Àv[ @×tÊP  ÝnÕ«µzGïÉÝ­ÎÑj›ú uÁ à»×¿rô¾|jðk:N«²+k•k\'3«¼Þf­ßnV[R$ Ù('iEWfRECo÷`æxϲ4µ«É¬ ‚ $þ˜],Égr®3™˜)4Æj4‰T@³¥e¤—ôÞb (°çÛK—̵QÄë óܱºÇð¢ñ$EBÃI±ù€I™B†Eæ ð¶ë˜W†{X´™ùEÁ.ÎÖú\6†îè²3´Møñ9áûgð5‰u6¹’!ctÌQ‹gÉ™ Úá B€@0©:‡µ‡{”€¸ Áh¸ùÀÙ,:»bÙâ¢ãm(JÑ[GÕ•¶ÅÌeæ2ÃR¸î¯Kkæs190E5±º”J±À:!Ÿ]+’9Ðî‚¡€Œå\!“òN¶éN߯;91kYÛ JP#+Εõì½Ý½“ë»_ßüøi×î¸i=g*ÞÜ€žqáT#»”ð£Òß>(ýìÃ9 ØøÇG.3>|8MWºÿq@}‹”ëý³C>?~Jpø”þã";à¼ùy4¹-d,`0™.Ä¢Ñ |€®cD]{|]€µç@¤lQHn§ÝëmIë´~”Ü¿%ú~ÕêíA¿ÛÝ‚—0èo©Ý>?ÜEƒbôþµ¯ü8}àë9N«ŠëjµkM@õª—ZMFá0í&ζá~‰èù‰pð¬󸆕ŸÅÑFWcç°ÐóâÞ“q{9˜—ÞFc+t+>».½h¿z®½ôB‘Ùë}ð7f§Z¾P'™ÉÔ'¥ɘ*´Ó×MFæ°J9ÖpÜoS…+á˜Ü`¬Þ­Ñ¬Ñ#ç²1¢“ïæÒ©L^‹mVت'âN¢˜Tö³<“ž6–¹¡ÃõÏ2 dÁ¬@Æ¥Q,°­×66¿û‡%Д!õ„.³'Åq"Šã&­¸š‰E³kkììAmO/Ÿ-%Ùd*Hd³ì ±VÌ®­Ò™\>Ì$W3©4./•Ë®¬sùÜ|¢Vt”¨%Ø9{o‹&‹³óŠv ¥”H¦0<‘s°wà»{³ëÛ¯o~ü´ë?œù7°Dñþœ1‹ÁM×'½Y÷ ôɼ?'g|°,ψÙ6 ,ÿÒÓgÏž‰°ÿá!〇ýÃçÏðí“ç3/àÂùÉ„¸(ðâ|€?CFð½¦S/$ûUa59f~zô4VßÓzš&­o‹ñJ§‹E¯Q¯²¸3ììl÷•vû§³‡´hPÿþZÜ h¶.Ug ËÚµÚ¥2Ó›u‰ÁT*­ãÍ`òža:Á»‘Þ.Æg=œCMæeKnueM údcLúÌÜ0 & a QèŲªr}.*·òOÚKÏ·a^ÏM^'b"Pœ{>Œ ÀË„#ML6¸üJX×qF¼J ltd‚Œ°Ç“A;0Ðt–'^à ³Ù4wé$;ëeÒ€ƒÈ‚LŒ?4 ö@5á/ÀÒ-æ0YØf%"Þ“g1{á|{ ›åï— ç )çcdÒGŽSQÖ…ëBDÑ’FXŒpÍàÿàþ¬ÕK¬fã;W,沉e¶ýÉf1–…{ÏÂÚ“‰´ «Ë6«íìé©Ú¢ †@óÄùw¶õ­þV±X”,IôJ Ù›¥Â츳ëÛß·NÒ-1¾wêõŸŒŸþþ¸+Lïω||F¼>iyÞþ|^ö(ãÃü5^V*½ÿóȧ’Ÿ„À³§OEÿD:ä·€‰_>–¤/1? Úü À”öAL¶¶So2få ç‘§ Ûkÿ«ø¯$¼€Ÿ*Ý®¬ªš>Øêã;Y“Ô† ¨4A}ƒí®0ü¿š=¤Eƒz^à›ð­ïܾõÖµk×oÞxóú?|ã-YnµÞúÑŸýïßyýb=UaèAÆrG„"þ?ÓWÔøWü ʪÀÍZy³Q¾TÃUÖ›ÊïuÔV·Ö._ª^jTÛõª ™²"5=šÙÂ÷Ÿ²3×cU_<îÅãªÖé6åfƒ ºýnoÌÌS0@öëž²V‡¬Ä<N|çHâ¤QÌÕ«oŒé´Yð™gí¥ý‡smD:8=—uÜN(@37™ÅAÜŽ‚2€ŸCß m*[0{ÍYpÒC“å‹àIQ8ø#{H0dê\nÝѰF~.'6±Äf2éDj)‘É0¼nð\ì‡a8ΰиÿ:ŸüÀOÆ`†…gšÎÐmc£\Æ$·ññtgŠQ\VÜÆu L ™0P‚/å‹EDŽ  (q㯾ŸL§ÒLÄ{¥³ùB†Z¡+‰T2±ìær©¤}Z±µ±AEäï3W²-|Õ·ui(&³,¨€ëoÝÅë÷ÞÝóööÄõý$¼Ë닎 .ðœS®ÿd\Óœ:îùc/~S\åÀ|é%\_éÃè±>¨÷£ëÖIƇ:~éÁÏ>t٬˖…ýŸ=}ÂØßò~9ßÐx"]8?A0L|&¡áLµ‰¶=ðñÇ›u¸e·©±h9}HÿµhúÓŸöµ^_Í߆°ÕHƒho³Öœ-úÂðÿzö jQè[ßúƒo¾~óÖÍ·nÜxëæ·®ýðê ªwnüèÏ~ø‹TõÈìE|@à!~àʪ§§ZoW¸ºr»Öm©€ÚìV:Kr‹@2æÃ؆qfçXC!pÑ‹ãZWî0'°E t¬ù*xЦœ-¦Ä )äq, áÏ÷ŠÆQèRÀ9n/øÒ\»Yðã‡ux”%ß±ž`…ÃÒ¢SÐÔÃCÙ{¡Ã ~ÎÑÅîï˜óÛÃÍõ¶éq·À‚çhŽè°Ûv.)¢ÿ™mᤒfãÍLàûûXᇖÁäοm2·m:ñ|<ÞÈ÷‡ÖP€r9[¢BµkZ ï¨Õñ££’F žë1úç±§YimMŠ{…%2B@*ÒJ‹éĪðõ L*åó™|1›³–WWœEÁl7Ÿv¼îΗk,þ[ò"|\Þ–Äót‡ÚB;¥‚´GÂ3-¸þ± ÿppÀëÜåõE,¹Äº0zäÏ$`…ŒÑDèo'àg6i?!àiÿÙ,ð˜uOgÀ¨à“/žHçÏO6wpèÿÁ[›z0 KÆì, fs;êÌû!Å¥NÛ\}Âñ€‘ÜGÙÙÚÚîÚjgnÕ†/Ò‡ð”ƒzNð›ßüÖwnݺ‰Çí›7o\»þ–ÜnTáyß¼ñÆ¿}±& ÃiAd9CÁÑ×£0ÿ<@U´3 UÆÕu*ݦªvÔ0e?£MÁXÄ!œª±k™ÇýåÉ3=Sh(gãZÒî6Ù7°Ñl·»½nã~5°L¹Å €_8tƒ9Yù¤Pè«Qàc©í¥qc'mD WhÇÛOgG>àĵ66ÀŸLÃhN£ÐÙ Äw ‘ êmØ;7¦FX #7¾XíÜbÃ8ú†ÆðʈÍÀ àJ2™ZN$2¡cFîxìŠl9'Ö €8˜ìì1ï²-êƒÅ_0‡-ƒ(ëûXÿ×¼T6W¤¡-WVLxË =N†q30#Ë9l !äì ]æàüƒ8¿PÚÙÑ(Ï&\ÿý}úÿ»ÁÞ³GsßÝß·í=Sz{ç8þÞ ×‰ñ™·ñâûs§>…dÌ( )áú3Þ'eq}|ò3jK×y}ó^ððaé“?úØl +?xÿ!ì ¿Ÿ9€p€gÏÃxrøø±tîüd¶ŸßgO¿×Gî÷äL Q³‰ŠrS_tœà³ãD 9 üן>ÿø¯'«þjHJoÞ Nû|ó›@€Ç­Û¤7;­zµR®4Ú7¯¿q]ô8—(Gœ/þâ‡#ˆ@^í͘ÀÙ @\eUº=Ui1?|€aö޽1Ãã!8®iEÙ†‚u4>ë¯Îq­«¶•Nƒ›]EŒÛv0 Ä. ³€áš‡˜6Ò‰,Ä\/ )¤1 ”ÐteÕÄíWŽÚˆdÚ8>Űಠ#ŠjÞÐd²§ã1ŒN©‘ˆ½·°L¸+PÈÎ7 ˜>0©iÎüõþ>ë|Mx„öe€„ëŒLàÏcÓ-ARî¦^M¬d"ù\{ÌœyËë9Þ°Çmº”ì’Œ;åýùÌÊƱù& û˜÷½¤ë°ñßÿÁ}[ZâõÍg|x"@ðÐÇ¡¤gœþ'Oígàñ!<ÿ©ÔúäðÉÜ øB:w~r)°µ^Wînm[;î6.w\Åg{)ÊŒ„˜Ì˜N¥'€½6gÕ¥˜<÷ é¿à5 j‘¾Mð쿼ɒ‚róæõk×/fj~fð2é^޽~«êø3còŒœ "¥ ÕRp¥t:­uÂ7ËÕ6.Xl°¹—INúËcEÆ A<Îu9:×’ýf£#·˜¢37O0t^GáØž—…8 EÑØ£`7½/¾Àâ6"™¸v Çÿ·™e†éÐ7ã%ŽK°(rÀ‘xXÁLV©X³@å]†_ Ý¡‹€an L°mš½á^±±lrÙ„HÿÁúŸYá~@2™É¸Œ¤ùX\)-?ï0¹Ã4²X=C–£ãµsëz+¼t8>@Fô­gäH”Ò:GLSqÙ. ˜ aÃRi­°^Ìä Â Ö V6SÌL ¹BÏ(øp ÖÖ¼Ä(™Z5Vùbf^ŠŸµ†ØšSˆ¶2À@(>!±!f.ž¿X¸s'ýîÁΈ?(þ;ðÿßÅÚ°·gîíÞÝ¿3"0¹9hqxà݃¹ëß‘™{Ç¢±/¼?‘ ãçDÀíÀŸã}:5_?úÙ'xEC,w!ãÃõJ~ð Œd_b”ïé“> ÿáSI‚ñ>‹ÃRè??øâ é¼ùé9†ïô·ú]E÷ÌÐÅ]aÞ4uý å«D€™ (-FÒs߯ÅDàùÇsCüýšÔîÎÔI_€ïÆ à›ß¼ û‡  j *µ¦¬tÞ¼V›õ¼ 0[ö³JÜž þ…¦.<0œÃ@P6YÊú¿õ¶dš˜3Ñ»ªöø¹þòtx]1nŠñ£þó­¯È•’áhÇ㎌Ã13ö]ðóÈ—…Pâ@PçG×3cp ±sd^1a¼n7*n#¢P£žµ¿#Ÿ¹a"°cÀŠ}sº¦D‘ËÅì OÅpß ýiÈ0¨ºáÀT¡-rM1Gàì±$Ùox †F¦ˆÍ+^–.È?i@* (Hм¼„q¼0pLv ¡`0—„Lƒ£3µ}V•Á%°/  R¡ °Ö^䥺DzԱt-ÅxB| ¨m€÷Rr@é\¾0 äKkëpRÜ„GkRa=ïçÙ3Ð+æWŒT(Lå³ 7–mg:PàM<‚Sga! t|þ=qþA1÷.Œ»¸×÷`÷@üÛ;Øw÷ÞÝ¿·kî“ØËá<{vý{¸~œËôÍwŽeãŸ{bèKQ˜1û­»ö ïÓKKïðá}Ì#éq}s@ÓRégïäÀ€ðÛLJàýg} `Q ¾î`ýI\€gý§_<–NŸ–à^l“5 ìpèb…ÁOWØóÑq&,À}€-e¤ÁiqÀçéµçâÏ Ö¥–z üQÜÀnÜ#½¥)Mô[MYí4;7®½~qo@Úvž /hCDN?¶eíÈþå/­E¨¶%ô †Ûˆåí´þòBãö\ÿù¤Öë0ÐVº­žÃJ + ˜p0ñH’#¦ôÄ<ùWÒ¯>U˜+LÇ¢tÍ×¾PÌ‘¦ëêgÒr/>¿ˆ)Å;Ïž5 E±&:èøÄ¥²‹yl ðŠD…› Ku|oÄ»n˜lá1U>eÉÎñ[êZ— ^€åäEp&ɵe‰É@K‰ÌØ6Vk\mXflqÿÐ5F<ào‹êÀÉ©\z Rc+zF €YÔ-8jLÁ¥Ò™æDáÄck³iì~FE F^ôòá”2+«éåÕ4–x·à‰>"ëpBØQj­˜\M++†½â¤ò9Ÿ-wLp_Ÿ>ÜqðyL¸3ïQBÑÿÝqþA1wpö^ZƒË í¾cš{xÀèñooßÜÝ5­ÝÝF,ÿÁOð-ÈiÝ¥Õ‡B|˜pÜ8æ¹÷‹ó­ÃéØÇv£yÞWúù'¥?|ÿ=À˼¾…ŒÐ{ðð£÷ß»ïr½úÅSnø÷ŸÒúÀ¯ïðëÓgq~PŸ)BR<ÿØàd~ ¨æ ±>ÃÞÚö¹U0ùc`H{\0u4`_ v˜Á§#ÀÚ ?–Î;b€ àõ›·nݼõöÐuok=•Rúí–Ú{ëoÇ àÎé sû¼zÄýÕ8ô¯Ä[€ºLÛ禥ZàèïÀªp°–ŠêӵƳþòs .V<~Ü^ŒkTPaË€®&L߯làv€v꫽ÂVo^ÂôBKºŽ/A.#@Äúpô…âû€@—Šñù™ŒCEq?®°‡XÏ„ìÇúÿpâãvF/ö|ì`h#¸'&cUà©¡3 Ù98ˆFÔü7F°¦ô‘“r™UÁ—2̳€ýÆÀš@ôöÃEÀ•`u³`mÇò ƒ:'SÒy¢„í•/‘0aŸWÜZí¸5óè-PN«¿ÈF &3Xš?%‡öGz5Å>ßË«);‘NŽ}üzÍËãËZ±PXuV— +³ì&3ŸZXS¥m~4xÏì–À·k‹JžVìáüûÅ»wþ’ö®üdö~÷ÀÜ}—NpàîÞý»ï˜»ïÜ øÃÀ€Ý¼ÞÁ!ܽpŸ¶„o½`ïô÷ÇñÐÆ§ VÆDíÉ<ï£ÚÃïðñ{¶ôû¼¾ùŒH{ÀÁÇãÄæü’ôŸPäÿÀ¶él‘¼Û;ÂmœËøpÝ8@àÆÂ?Oû3˜ü3I²ùŲ¶©ödGÐÆ%Î?ª7ºÊéû‡q!vgG¡<°ômþ!hìsó'¤b˜X$ ÿeœ€óŒÿLø£ãÀwnݾuÝz››õYin4›íFõæÕïÌbç15Çh¿,Øÿу€ÂĤ «°Ê*F<åË@sÊu‰ÉôîØãöÝÅþêL±²2íÊö9n.Œ+½n¯ÛnÉÍn¿ï™:0íEêm]7ƒYÖŒì ÆbÍp8léÃe+“™_z)>¿å KŠÌ£*¸ÐB Î÷Xúb{"ÞŸN‡EpÛ]VÅF˜!|sÃñFÃSì0) ¶û_Ê 8ïÁÖŽÞy1€?Œ poj2«ŠÛ ̱¶ùZù.fJN¦J¡" @€Sáh" ˆƒ¿ Ô$ÜP,hL£µœñ‹˜KúY\Ñ:ͶÜn¶Ô-Xþ4-¼¹U”ÕõH?–…P¼hêÒ+¡/¢VjÄdÈW0%T¸ëˆß?k¼‚É<ï+= FðûïÌ´@¾v.ã#(=|PúøÃ~ÆMÀ§Òá“§ ýméÛ[J§' =k, ¶úœŸô úŒÐ`Ö¡ R‡Ë ï°ê‡%†oY¡ªx½À E  Ñéó'ð&|L¥ííÞa€õ—@ƒ£©q:ˆó¾%à­ÛQð úZµ%·ë›õÊ¥òÍ ø£s€û‰9Y=f „' Çù´/@+˜ž °ã3)žw*ÅíõÔNSn´š½-8¢b“‹;eô) „Où¨-{üzÒËp™E…“^iбÜg+0SúÆìüž l ÝÄ<#­¥•G$¸NtD#VŸÐ釂ډì¸ÐrI•EaºËØJ~ÚÜTp¯x#x…î0—jÀÂôWàd2ÜXaÀ<& XiÌ”ÓkKŽ!ThÜé„{oì:¹±S€òé.RìBqzÂLô ÕŒc `½HéOf'VÖøå•ÕTÒ4K¹L. ŠÅ’h"’J®.­¬$–V¹˜ X¸|êá²6ðáœÀ'¹,äò½1óñ¡°qK˜øÔßg÷ÎÚû]Øö]ó=üsO¸÷îÜÛçžéí¾câ退Â;ûw÷ùì½»÷îâà`Ï œXLZx$ÌX ß?:¸Ö ÿíÇ¿÷A_ʉÔè(ãC턱ä'ŸÜgÌïé³Ç‡Ožm±æOÒûížd¦?Зžk IDATUÌÏøÕóá€Ó³9‰gŒBºö戅[¼K üÝžjõÓ€‚Óç3ÕÙhRÚÞê휅gšÿÚó±.ÕÏ`"àõ[1ü‰{CWÚ@­ñZ£¾Y!¸8fß™AÿÙú, H§/@ d¿œ P_€—)®ª—ÍñÙAŒŸäêÖšNSË˜ŠŽ.ìÕŠµ@4†e!Vñ~ŸõãX«ÇLç‰4/ò#A0Å"'Qœ_ü11÷™âNA«@¬ûLEë¿Ï\îø€ÖÁÏókLÂb8ðD îûÆ;ã!·õÙ½Ó10¶0²Í8Î(Ï2@ ‚ж"0•^ ˆLPý1ÉfE¢c‹*PΑe»glF6õgíò†°ÿÊAR¤ªrS‘ED“i0=€5:ÿÅ|qÝ _]]^½’L®ÂΓ‰¬•6¥B±P\óJ¬f"ˆÁÊê(•LW I‡ z‘Î2 ž5W}€Lmâ…ü?ð"š8–ýÉî¿<`àž¹{÷VxTߤ±€gá˾·ëíïò¹»ðôïÞýËP€{&<†»& ã`÷Î^œ;„ÿ‚ÏÜ;yv4‰š¢DÛ£#À¬¸ ŽqÌû¦¥ìÏ©œ~pÿCéeýV {‘ñáÿõkV(÷ïÿÝÇïIÒÓ>—xnJ^(I½ŽdcÉh æç`ÐÛÚÒÅv $±ß¯MwÞBMë)@Ï`dkAWíyÛ£-óè¶gÌ1‡íi(é[]Ài°vŠñvòuq@ªÉ§æüÑú.À·oÞ¼uûæuâB-ºó­Jm³ù]€ï’œ9áÝw”<“ã„€£”@þ eM“s‚ ´å/”kóPkm4¥0bF¹wþ6ÏYãÉn·Óh5ÍfOîû$W!œäñ$Ûm8ÈRügB/ú¾ß˜„TfñîÑ•t6 ¢B –”œ‹LÀ6Ï>0H‹¦ä yà™i‹ aª–Ãô™€g€|²JÆŒp³ÁT0´Ùÿ%â{3`ü˜ì#òg±¯gŽrYÑ(÷̈a++édÆŸÂþdl>MºíQ>0àŽ,Ç2¼‘}²M:€Í ¬|3(/n“žl£šAЌӦŸe|ì¬3ý¯T($„…gØd%•Hæ3£D"_\+}V ¦A’+«i/¹j¬¬Œ,f3 6ˆ&¦9Á›€ùFŒ‹¸N½˜I0æ6Þ!`oïó/ÿÒÃâ÷àÞ½ƒàîž9£ú€€ŸÜx&À®)ž~ïÞÞ†§àÞ]¼ìÀ|¿­ÎÞ¨":y0CQäÜøÔÝÙ‘Œ LŽxŸ5–JáÞÿðáŽôŠ®_ÓÇÓð³Gk©¸ÖÄ ïàƒÒÇ÷?É¥á0$œ_Ö$ßÛôeÌÏ®Nµ@Iâü x`“nWëmÛý­¨×Oóü®ÜoQ3˜µ’N0ôΘ?7‰]*›oÓþÅùÊàçWüÒÜ×38TožÍX xûÖ­ë7£à ²ãÔÕ«—7*å[7Þø `–ù«Êq2Ð,'H€YR œ¡EF¾4ë/€ï\ö¹íu^¢ëiO—á…4ÛøÓý @„#Y"VùŽ_JP0ž%"¾”#ˆ˜[„Ã’¤‹U<éÒ7²ºÕcš½)ŒÜ£ãÁ`DÉ Î{ wd“W^¬*‹åÃ$ú'‰0”2˜6‰„a"ÂaLSó‚¡mddN¤\ß Çp¨Ý%³Œš› ‰P"…°±±¹Y1«vå”qߊªÁfµbÙu¿Q£XtÖ¤¢êÏ넵ì&Vé$mö+óòùBa½”ñ_YYÍfáоšLZÙd’}Wø^M+ D©ŠGµÉ¤ BåxÓ€©>»ïÒïßxw±lß»7¾ ëßÛTߣü`qspT Ëþ;wûû »UÁv÷íÝwáý…•fÝõÛa«1ŽXδ;&„ Þׯ½.M}©hõ(ÿ‹0_däç3i¹ªØÿ/•*~ð±å<9|ÆÀÿ3‰óKê)ÒÈ ´®äú-v xz(‰ù)FÔë†;ÆŽ5ÚqwFz?BKUÝÁ*†ªb·ÿÔùÃÏnh»pHp-„]G€µ…åÿKÄ¥V­} ˆ«_ÿÃÛ·Þ¾uûÆÍ?qw¹Ýµÿç³üÇ_–«·®ÿèKTþEŽ€šSfYJ.ø‘Èpòj‡±ŽŽpN*ð1´k Û€õZ¹%]†ç}™A®³S=YuÖ¸¦%›Lhw;ý¤ËTç¦L¸ðC3 <éV NÞoÀú$Hˆ½L¶Ÿ,sIÛ Jŵ¼ë'VÓéDÑK’©¥ä²Î¦¨ÃEé³lT=wˆË‰Ì©7µ¢I;Œ*ðnÍ}s·w°‹åÚ<Øßß…ì¾K>p÷à'æ>©¾'F½ïoGÝ¡æ ý¯3ïí’3Ž–ÿ½½Å÷W­øQµåea°¶Â1÷=¼Ï©¾U I±¦êµÐÔE-¿!Û——<€ ð·¿÷>n«' 󧀄ù%iŠÇГ¥Ë;½$ ˜ë’LcÇ@I½ÑÎpg´¥ivk$¼4Ç»hþеm2žz YÀ‹ò g€\­¿Gzß¾uëöÛ̆ ð–*·›õ³_ü_¿¨mvnþ À‰?ç0€¿Ð4e–„%?;s¸úç9'w:,Œóƒó_‚Ô«òTê›õ&UÞöðìbó‹A:SjµZÝŽÖguÐ6 B´ÁæÞŸmOüI¿ß—r9<âÒìT')¬fÃñ™ ¤ëª®å®qU»dÈ™¯Ç3l+Š÷©þ`[–Ø’8.†aÀ\1Ìâ8Ö૸í®#ÐÁc1I¯Ûê¶ÝfKT5*ü®K'J3ZXu½Jµ2¬—ëQÍ÷زVq­Ú¬ªz<Î ÀYãǯ¯×«¾ÓŠfGÝ9H¯,¯¤’XôWÅN¥Åb6Ö ¥dT̬:ËVºTJ»ÙäJÂÆsª”Äa|´ü(ät®F–yn;œvÆÑ¸‰Ï½Å¼··Ý›îSÌp9Ç"~oN=ؼÇ·Zî>Ã~ïxïzø.þ»ÿåÞÞ½Ý}ÂbàcÀÆîóï¯^‡z‰3™øðD"q}R¢òO‹·ô-©óãÀ¬ºf= Âz0í(Ò7š­v»Yš>,=|øså“û€SˆÕÿé3jp~I˜Õ=¸þ]}ÐW¶#_âü”âùF?²6gú¦o…Z_ßÙr»G0ÇsçCGc—ì0ídRð¸¤b.óSûÌ[ÿ©Õ@¥Ó@m•_dßÀºM¸yëÍÛÑ®ë*<¬ß­ÚfåÖÍ7®~çâöàjá™ô3Píâ{e¦ ‚ü3J f*È_ å–:µj«.Yöåˆg—{^PÚÓd·ÞêÈm¥ÛT‡rÌœ4xÑ«…ô÷ÂUU/lI/÷û½^_tt“•Oôx¾iZx­gê>Þ“x½k‘Ì2B` µ[‡u_ÁLøŠ= #+8)‡õÊa/å¤X`Àc¥Õnˆ†gr»Õ’»=3¬ÕârÓF¥ZõjfÅðj•ŠW©¸5–CWË¥1@ÆpúøÜë«U¿ZaОFÑÌø‹˜v+ÉÕD ó=i¥V“Kø~yu@à|& f3%©ä¬$“é+SÉájŠÞMÝªÛ /¨5›Êœn•Tˆ+ë˜ .ãq`~aÍŒ• H„-,äÞ=.ú÷Ì}P}ð°§xû¼ã½ã1€HáýÄü Áäf!~³g̓ëq«à`÷Å÷‡+©ù­øýùŽ×àõI¯TC\_¤G}©£ëZ4 *>÷)ÆáDÊ5[Ó ÞI•JþÃÞøç(é"=•<:~<¿@´¾ÖSØŸ[“ž›Ÿ®IY2›¦á<«?Úñxx ]4€—tÃé$ÚÞRÛÝÞ`§¯öEµÿÑb^=ð‹ @m‚K/Àµø±®Þ¾yûí[7o¿uëOÞ¼vSÇ"£´kx¢róæõ7¾ Èj¡À|Ø{޹€¹Ù€üãÑé´;9ü¨pÔËÍùjÀÊ¥r«U•„àÃ8|¸|ªàÃE‚µ×¤w:-9Ùírwš<ÀlÛ¸¦¯^ÓwÆÒËÑxœ·{clQR˜Ä'ޝ*zŽ˜åñg¯g2ÓÞ(D%DïáþY£}Üø?[ã¢q!(áh]Q³ÀgÈl,8AÐ\z„ñª_«n^Iµ²y¿\ T‡›^Ù±0íÇ/Íà¬ñ¹×íºQ‰êQБ§3û—ÒÉôj2Hyy7åÙ¯fât <¼dNZ_+À ¸`«ÅR±à¦ÓIsäJê–Wó6Ë›f<§êm”íËM»Qi†NcÒ]»žfJfÉ”"oÀ÷íš ÿÝÔs€wS­TªîHAy饠Ոê“f©HøÐÿÖ7›å‚ æÑü’4U}j_Q·N›Ÿ®E4ª5ô¶†¾m°ƒ„}ñüãJ?Ábdj»ßÅ}è}bçDñƒö¾MA6˜ö 3>Ž@GUk›ÕEˆ»ƒÿùúÛ·o¿ý¶¨¼ýè†&‹,ä[€k€¸?øyåÀ0ý\ÕxÛ/•™ ”ÇÒŸgÓ…£f(òPݬ©sP¾´Iu)–tºìŽÅ^î™’OçHBiQOëÈ uu¥§z¾H±X™Œ¹âe¹g§¨·õO}]¤ë¦ïLBêVH2S×Åñ͹ó¿^hÝSDìù…á`@å­ %±.’R¶£Éðû;f³ÙnšŠª-Uš  §ÆE¹ šëV«ø¤ÌšeÕ«•Êf€E¯V9ŸÕÔΟ}ƒÍä'ÍHVº³µ–IbÝÏ$¬dÖuSùeÛJ³ Åá ŠkT_%“)@¶è¥FÉTªV«U3©0+pø+FPkÔ,œ´Ü¬†ÞØt2­©ÚÁzê Ö-0 +¬÷àÁ´[žIˆé·L¯íu,LÁxü; ` ôÚxfhîïâckÅŒa“ÖïþþøþË`ø­NÓªáú¤—âë Ìš¨x>+s<-ФoÔlìí—ZŒŽašXÒ±Pp–™³ùÅöFZwKîvûÒióÓ —ó‡`xv·çǰö¹óG†0oöÙà&ñv:±²š)ÁKå…JÐì¡ï°ë­4ØtwŽ@m I0s¾@éV7ª‹ F€õ·ßfÉÊÛ 7ÿä:(€F›íÈL xóúõõØþÏi®æàãçX”—…öGG˜g& È  Â8`Žq ×@=Ú“p$ú8>OôñQHT—[­NOe¢o ãýþDz WLø(ŠL]U׿ìøÓiHòüñÏòúxË?ªÀ¡?Øj çBQÌ‹E3™2oµn ÝiaùoÂê6ªU`~Y³ªpÜ£Š7,µ:þÔ0½k¸óf¥¾9ß»€ VgŒ/¾^@Ãi„Ê,°&ÁüS)½äƒ íÂøÍä2À-æ“É„'”ƒƒUcÙ4éÒ:ÖÇ´±œvj^¹bÕª÷k•%¯\~´ÉÛŒSL«­rX{¤4¢Ž$åaöYO¸Ï+±`Ö˃÷ï{EX3>PyjN¸½kv`€ƒVŒ ^‡ÿ{;ò hÝÖWþ†‡…½bžùþâ÷ï6&͆S¯I/=}aµVvkŒõHßС¹Q3*ŒÇR' &Sæ›I/ çˆ.;ðÞfêȹӗN›Ÿ,‹¶˜Œb)=‘…$t Ï›?Ç{Æ¡ÃF Ô €ç7Øê±×å”-"GH$kL¦ÒÎÎŽ¼}"Z´õÏë ¹[Ù¨,0€ø±þ¶¨yñßxûÍkoõØeï‰â 7®_]7ãÇ9™€]•)Ь½-´A´œ2ãþ Š`%/íž ¸Â®Ü9Ú£J8‘}¾ü»ÊB»~G“åF»ÙlàŠ=¶f²F)ýA„OÕÅážá7~=&B$Éš¦úóçg¨&¯f³ ˜¡ÚÁV—_çe±½Sd±/²ÒXj@Ç:Xý¹Є'ØõkžUuê˜q“F£YqëåjÖVƒ‹W+Wê0b§Œ —¿ÊñÍYÀYãÏ¿¾Z¯{µÎ?f©ådÆJ¬&óż×7á¤Ìåäª^/äI» ­OWS«)3•ÉäŠy×¶ÄñUyÜZ5>|íQÍ…AnÚÍ*„Z]‰šfmy%/·mïàm§ °ð~fÖóš^Pi´±ÞWj¶ëµ‚N‡³jÃsuú €€FÓódÍ@ð¼:þ¯6ÌjË;÷ýÅïÿQ½ ÇUÊÊêÂõ•7m\^UÓ¯Ö²¡®+0ÆœH8ëÎtö¥,·©àÀùaÙ@Wº[Ý-éôù ¢çÎíúÌC6Lêž9ØÍAì3³Ëƺ`Ùà ˜N·ñN+vÃv"æjxaÈŽÈád‚Aekv»vZÝ^OcI‡PIÏ3ö¯\ªÌÀW|ü«Ê‚Ç€ë“Û­¸TîàŽ?\þ]Cp\õµ¦§áîwU­g‹Í0€¹víÇêlÁ‹W„a´Ãþ§½°`ª‹ç=ÅëƒP´r½A:±÷’_ZÄ1¬ˆÁ:³¿ý™ã¢±„9ºZ_¨Ã €/ ÊXz*QP«µÚívë“&>L[LiPüŠh‰IϵßW+÷Ë5Œ‹rÀJ¥~Öø ¯¯7Ú-E›€¤ç%œB±˜É•\`'DSðl)祿B±”s s”J§K%+•Î;âøµÆî\í_z;Ì“Àiô¾fòiWØyÃHyÞ`7n"©b¹¾$ÂõÕ« üí´•ªG±J,㵦÷QÝiµ-·Õn6<£ÞôLx: @Ó<ïý‰ëS]]Ù‘^RsšvÊõù¡é·?W,'w[×¥ŽÝVzúŽ"½¤è=±Q3›_Ò`k«×ÛêJgÌOør,TÁMf–å°ôóŒùCø?šl9!|ZöNwœ`bcÝ7(&oLiØ~äŒÇNèOomx»¶éÝRÞÁ'¬ÿõ"4[Êæ¥ò1|ÕÇYðµG|ø¹'@ûߌ@´~ºü»·†šƒEuÔ VšlÞs4/PÔG?Ö? t¬ó?6\ÑØÙ_éöÖº,Éú-SWÏ/2¶ãûçZ¡è HÂü_~ùÕW^]·Æ¢‹^ûÓÇOZK©F @VM˯Æ|µ%“Ôë• ÖHÌò* ¼RÞ„¡W0¾AÊ‹ M€‹¸»rÖø‹¯oTZ-Y$`*–°|)_Ì%M|—´ ÉÕB1›LgÒy?™Jg¬\ºDÀ[9:þ#Z~¹V]jà&>‚ÇâX›05ƒ×ÿ¨–N§³vr5•Éîlû-S€äJ:m¦úÑõm_ ¾ƒŒëÛÄxÝ«6½ª]mbêxfÝk€_Äãuw£qîûãõiº¯3«C¿æG§\ßýžrMQÓ=]7Ù|ËÔUM'dè×òtâãùu.øVOëv¥3ç';»Wœ!k¶(uæüÁÍ?žŽkQ$žé,Ü`‚ kø!+Ü¢YCÎjƒ¸õÚà»qõÆ€óÇÇÃb{æñtüÓ¿Z_€F­!—/ÀWœ_Ïq\*Ë Q €ö_‹]–ªô?Øòx<ôší8­¦MœQЉôÞ-ÜfEÆZ¼eâõŠªöM75=2åÎsÇg?y|½Re'étbyÖÿ*ÀÍ1/jžyÒ\²ÏÌ î¶ÔnàTkXÿÁY«­*S6áÏpjcÕbG¨rSðÙºxƆXæñØ|J°µfmF‡ŸŸ{ýÑx½^k$ÿí_²“i}¿`±òwÍr¬´WZ/¤¬‚•X_/Á†ÉÈik5aeç¿ôèèøø©Rž;¾¸~ž?“®VÓéL"•JWzšmSɬ[è§]_Ý­ó×ߨ5éæc¼߀¿¯xñxý¢÷‡ñžvÀaá)ׇ¿þ1î× Ìž®˜½ Ð>×{ª®åËç—p»·ôî–ܑΞq•Æ5«XÂÏž?öÜü# 8±WÀ5(1aÛt*šºq{÷¹ù3ø³ëWÿîG7sóÇ Ý±÷Ó“íÀTj@€J‹ð»y=€eûÌ–]|åþö‹ãsí¥û¬_i¶ú}–-ÓÙr­Qo·Z­¦ óÕ˜î6Oè.èì†à¼›¯½öˆtó9:|2>÷úãñZ T9Ñ^_+f<'é5;í\0Yà o¥2æäóżUȺÉDÒKæNy½8þÒÉñk奨xáü™l:…×7jéMÀAºfz} mñõ›V<^s½øüµ/ýþªUØþ ½ò¹œv}øk—«kL˜¢Ã`þÿ¢ç¯Eê/Î/¿¾µ% v¶T`´tÁüsLÜNûÜù3?ÿ°(PÞÅbã8f’’PsÞ¦t¸€“ù3¸öæô÷ÿ÷ææOè‡Á\oѵ#جɕKúWuÛOÿZíÁ«õüÿÚæt[eØÿѶ%€éÛ–h­ zì}¹þëŽkxü`™·Çí5Kë²ÎÖßj´ÛJ§ï03ƒM£D›‹’ÖQ:¥~¼«¿²ß¸jûv|~¶’‰Ïêñ˜…Î8?ÅÑê‚<7AzÝf«ÕiɽŽ]­x^u3vb1µ 檵ÀÊõÆRõ~­ÚÔªe@C>8}ZÑ‹yó5|ˆõfùÄ^¯>*ÓþmÇkñøÊÊæ)ã‹ÇgÇß\;Ç8Ic$A˜vŸÐEã5¸e2ƒÚ}:)ÏŸÿ*–r…‹¿,ÁÆ(ãÈ@>n]àIý-}«§;ý0ZznþÍæ³?íãùs2/š>Û=˜æéó“âÆsópãDîê iþüøÖÜ5±c¸°Ñþ÷õöÖÿKÇ V+Æø~œåKY-Ã'º¨ÿº'r–’+÷yfÐë%e• ömYiuûj—»˜ê7raшWd*Ûÿòѯ®_ýüú8ÀG8ÜJFœÿÔã–_}å•¥å¥ÁYç÷ØñbOï<Š7G»`ÿN«§uE¬`žÖ«÷¹£¿Ñlˆä¦ ]üj££ÕêUšÏ#1·+u,„˜ð›ÕJ£©–Áƒ7 ÷Ë ãŒÎר:þêx–‡qZàJõKŒ×âq6ìñ/L¶ú›.@£qÑ8! G]®&›Xñ; ‘Ð h4›|&°'$8{bZ`š„Trw|ÞEØ¿ßÓëtà¥u¤ÅùÏ/VG çï…óÃLß'gÊ}^Ït4µ«Qô @IözŠKÝZ{&ðm‰]ªØ:þõ_~úÙU,ú×Þ°¾g·’™§Ë&“Ôí[œu~?®öµOßæa%SÃBgn›¨§5”N³ÕÕâm¢MØ~±¯¥%C¥þ³NKUÛ |^ÕZg«^_ªUÿæoþ†ôvCÿ—¶€/M¹V†=àUÕÙýñ8Ãf ØÿÛŽ j›u\¦ëu87ð šóÇi½âøX©aÞMÚ8Vvîñ« ÆùÕ–Í8;¼Ã}Öä4•‚c…éQÿ¨jy|•ˆ=HÃ)0技„ùÇ<Ü~_í)r³!ÍÏ?1B±6QÉ~~þ^4?¼¸—[Gà5ø_ZÀ9 IDATT”—æçüüüùí4À\»!búû«o|þéo”Þ»ÒJ†ç?íøƒL @¸çŸ?´ç=0a"LÜOƒ?4ØŽd.QDëÂü;мÒJç?ýøƒÌÊ`ià^tþãTÏ€ûB,<ŒD1 b² ‰ÐãTѾ oÈZÏ*SY£æÚ|TuxÔ?Á­Dƒ"n¦9«}nI+Ý®ªte|U»]YQ1ó¹ YÄ: c›¶Üý…,Õ„ãËÏ"6®ÖxJªö¯;Ž«n&L®Á¼åH8üïf¢Yo6sáx;8~³ÍÌs.áâA;o..ú¦È[§ ­’S@:áDr£ÓÂS1<®`FTa–¬² Æ2b'õúý¾¬H‘´³­wû;Û;ý.C:šœ?GkSœE¾8/šTz¤;q›Ã®RœŸál~.Ìÿ¿ø‹7¯¾õçƒ[7ÒÕ·$q~ /†“S zGzßúÖ7¿óÇü½?–”V£ÑþÙÿÙ|¿ÑQtiý»,Iß‹A¾÷½ïŸ ùXü;þ“ó˜\³6`y¡F•@9– àϧ§±)Wç !/À̶íYûüþëûb²I¸ ü¥£7kË'*ü¼\Õ:Ý&xtûÝ£~ aÁŒ†±[Óg}öè—ÿÆŸ³uÄÇε’±ìÓ?È&Wâ,€‹Î?+ö¸ì&ôŽï„Ç Á'Å"”Yk±¿i×®:^TÄØûZp¤W«ZmФáÉR{¹Û¦óŠUofo˜þšÞn¯á3lÊ­6ˆWU¼ª¨Õ–iŒMn%À#Âk¤Ê´NB<í_m|§“jPª±Ó‘Ûbw6.2ñµ{Ѹ¨Žª‰¦oóbôlH%‹ÃÊì^EÀTðWV²D‰l‡u0 ÃX°€)k7¹OŒ ŽBÓ6$}› 'YÒ`KÛêèƒí-}«Û’fóóÇfѷЇºÖóó÷¢ùÁtÁp$Ñ< EIvµ]ççÿàMRÿ?üç,câü,%Ä’r:(•EE o}ë¾#Ißÿãlbù•—þ—ßÿÿáK+‰LAú×ë/¡$äA«;;-ôAsø¶«ÎDÔXŒRƒg@EY€Å>U‰ŸJd“‘™Þyý×ùÑr;4¬¡Ï¸kdø, °|Û¤WÅ-ßÓºr‡9-’¥kø,â´)'*sÁÈ~õËGW?½ž½Š¯¥÷ïϵ’aîÞiÇdSÌeYÌ3Àk™6.lþüGåžBjÀž0já3 4+ òËE¬Ýf£Û’µdÅwŠk–£J¥Ú\â[³^[©‹ÞF£Ûcmz»Q)‹4üšÀyklgMj×ël‘¦ö»†ûÿ™{×&·Îë\p>§ÊV³qÛ¸ß^Glܱ/¸v£fl‰2A¶øA_X­6°÷Ænô9“Y²$[–ÿ„SS‡™J¢‡”uKôåÔĉ‹‘»2SuÌ©©Êç)[vpÿ³žõ}A7»i“®P"›½zo€À»îk=OY¯•ƒPOŠƒЕŽ?+X™T”ÊZù÷(GÎ].M†,óÙdzbTäRy+’¤ÞtßÉd>Æóùb:šfôç|q™œm%ZRÅ@ÖaU3ÈxPø åñ ôk¨Z Ëô\ÄçÜ¢u±­S`:¢UÛ©ÔJUžÄmêdøüáüp}ŸÜ·ÅÓý§ÏïeçC6½nÇ€#ýŒyÇÂ)éï-Ïçñùo½ý=„þ8:ÿ”AöƒÃÁªx:X€ÿ°Äüú ß|YܼÙÔˈ³(*ÒéH¦nÀ< *pERƒTT ÿkj }Uf_V(´úXzðbF»Ð8nÈRæ2L7/à_ïvP_# ýþjº6Ö-»{&·Ú>¨WSú#íÀIU£Î6š2´}Äb€|øð‹·¾xôÖƒ·^ûEò“’÷>Â’~NçÓ¹T¸C]ÕP¡ß£|©ÇóÙx±XÌF£ýÀ|AJJšº //“Ïæ*¬€¡ÍÇ‹Ñ|²€^“|:MFãÉårÒýÑ”$š¡Uª5iŒX8¬©0\ôºé‹rypˆB‘}8êkRžÖ7éÓê ]ˆŠÀùC°Üj”+”ÿ»¢Qòü‘bóf÷Qsýü^r>˜+d«$  ù¦½Ž;t0¤†:á‰óßzçõ …þ"tþQ à xð–}€/Ö#€ÜqÀ¼×¾™âåëÍr泹Ÿå Ù«­Ùn‹ö®¸Á¼—DKøÑçe×b ]I Nç©ó<ÆäNG¥5`’f;ÀñÞã0ݼˆÝììwÁÇå—É¥·ÚÝE…µ×A§~—²o³_3(X¡H¯ŒøZR.‚'n¢ôô¾?øü‹Ï>{ðèõGo½öÉ'É~ôþûGT2xþóîß )MÔ;+9×€Ýý=¦¨ÀÖÚòùùdw˜ÐôUü Ì>ë>¥|d¦G–Q5­`¨HT,mq%+—A¬ ªÐ•³\òtª¡5ç³ …ÛFµZ1Ježˆ%k¡')1™æ‹ ô»³¹Ù‚ÿ‡a]O¦óÉtÁÙd1^Ìà$ÚïQ>Ÿ@•I¡àSòËÓÉx4& Ǘɧd&ì×IÁÇsD£ùb„ï.¦ãÙü2ùx>šÏèÕÐoF¥Ú@ìdè5XìVæ”Ìê@ýè°ì€,ɺƒC׺Àóê›î_tþDk§¥î´šªÀù£ïËógqAšÎÛ¾Õ¹ÌÙó{ñùXAª †T³R­°QlÎõøü·Þz7"¢cµ¾ûÎkßku%eÜaÏ]&§ @a øEYœ»&šñr¦\Îå3!j•ëm|÷å'Šb€ª140“TTÍü’äˆ=ð±ˆ@z¾pÊ”× €Ýe­0¹äEüë zéwò!ׄÝí÷tFt¼–USêÅ㘔€Š &©)ÜsÉÒ[o}öà‹Ï?ûü­G üäÃONPÉXÖù÷o…BØô·:+9Eˆò{.à,ì-ŸÿôÑ¡Se»üýACáž(Y‚FV ìS–R7»ùn.{ÕÍl]ÝÊÁéS¦Š@—Þjƒ\ût1S³iÅà¿ õ#VöZ5Õ0 #O²š^å·R©9}S×I·&ó é ‚í1ýF*FÊ÷û”O Ñ¤Ûd+ô©ãŒr>¢Ÿž/“SD@ ½˜À°Œ(@€*Ï96À³G—É'døö0ÓF£ÚlÔQ?¥cKéGØqêP-‹ê°¬‚JÔI0ŒØ+z¦¨’²ï‰Ö6E’Û-Càü‰²çkpì Šµ]lè¬ßKÎÇ TÕâô_‚ªáP‚ȉóßz÷Íh(õ°OýÖ÷¿'[ ˜èõy W:â`j°k¬ÿ7¯U˪hÊ<]ÍkíoŠ]ñrjÉ x±ÐXõåqG@…+ú\‚…j²øP.å.6 Øa,%RÕ]Á>û}›^¯çŠ×çõQ˜µ‚½fˆåWíïõpÝ8l¤¤ý½¥0µfW­Õu­Ž|“^g£O&4cx·Èä?xðù[Ÿ}ñèÁ£Ï^{íQ2yÿ£c*&£ûóàÕ©û·B~z1-òïòùû;=^äÞwÑÒ³œ;«ç_Â>Û]Ii0  ¨J‚Túv\¯UJTȤöòÙN/¿…ö_ŸçTî•B”ÀVkõ¶|T|Eƒ1½g\C›»Új¦âÕ ²oµZª+¥J¹¢Õ*uò‹¸ÚñdDn™v× ÍŽçмߟœþ6† &Ǽ U_†“s'=#äŸ_.Ÿ’“GN0žÑÏŽ ¦çBhAj?¾\Nör‚ €Ål:o·oÞ& RùèUyµ\Ì‹s­r‰Œ4Ù€%@rÒåó'pþÄŽÑ,7)–év(/|þó¿lîSn¿g­Ÿß“çƒÏÏúùx ¬:©¾kº=ë$ìyë;ïú£‰wßyDëûú6·!j9 xʨêinÀíöîÍk»zµ¦ˆš¦ÕDÍ+*zó¥Ý]Jž€0¾ô55fÕ´°•ÁÊ€+í×ËœWÕSÀX3àØÂ°^¤sà³Aü½Ðü+~ÏÆ•+ôEàˆøÂ$GÚ£w«»¬»î ú6øVúÎ>º‘=Ù]¥VÓšV1È4Í!oP0qÅáŸ?zøð­Ïýâµ_<úä“û÷?XQÉðóÓý[”íû¸{t {½-§³|~–úP mGɺ#Ÿ_?ô€cfïAõ™1”PŽ#I…03¢4 ‰®U~>w¼Ð’Éä|þL¡1*5ƒ´þÖ­›»»UµZ»UÀ¨#*~èn#妥ÕJÅQ¤d§ZªéÿrU«×›c²IÓH!G3xWò‡3̤9äiò4iý HÔç,œæâG.–“VS”° O?†1aAº<Ã@L(ȸDNgÈVÈ&-ô&ÖµjÌÐK‘0 `’ÉZÙþÁaß Ÿwxxpˆàð^{E<"¶›z±^/—EÏ¢Y3Jç{ÊhÙõÌþ>¶3çWžI_ˆ£±v>G¬B¡)À{ì“Ä.­?yçOü‘Ä»oø7ÅŸ†â¡×¾+˜ø•äû€-À)°ùÖkü+ýÂ7)þo‹jEø…RË+¤ÿJ³Vi¶oDFÿá"v`®¢T~PY’²¶Kpðÿ m}Ù x"zpõª¬„=¶cïã]¥ßl¦~Š• Ïã½råÊs_½²qeóé Hê+ØÊ¥å”õIþu´G:L]…>L£Ñ h˜L}ÛbA_6díÃÁ!jߊ|öè³O>I~|ÿÞ½%• ž÷o‘²û½d<¬ç-º+ *0¦üÉ<ô3^_ 4ôüÎòù™ú‰i Éý»îž»‡éM¦„“‘K걊Q,*¥Z%“µœl6õßÊ 2¹|ÎSBöÖÚíш_* WÙníàѤ`iJdjMú<¦Ó<%\-?àábÇ?“æþžäð)7 »@jʾ}Ì¢1BvJ.”ßžtnw:ÝùtŽÒý¤ÔðÙŒÒ Ø=ýò±´(c¼.úŠ~f4ŸVãtôJPÅ h6[Ä0éd;.ˆbðû¡K‘Ààð gQ‰mc»¡6uUôE³ &¯²ÀùðTŠÓÛs÷÷-ÿ=u~åù¼ƒ=ag‹ÿ§ÏÇc©Õp>q‹“Ôn°ïDè¸D4$×%õæ—†Îã €Ôÿ×Ò_¿&A’ª¢b{µ WÊäB† Àe5ò÷±24¼\@  G—0`œCƒ<¾°nÔ5@i²Õßwº{]&þµB4Á>¥›Ï}õ«Ï=·±±¹éõ3ù%¶)zh ðRÖÿzßdòJÊ£ªFs»n×lÖZMF[CÕô­áƒGdþä³×_c~Èû|r©døùqÿi€ @€€Ç Gá¾X~/ÀÿSVÐí`Š´?X>?ï‚õ`£ûΚ¿ô±ûœÞ1Hô’|´n ¸¤ÕŒêÖ£¾çj&ƒ-{ìý`Ø"|ݘÌðáM) ¸Añ?ªn”åµ² *µæ6ø¥všµj…•¿\E PkRdû›,u“Âtr¸ÝçËøþö÷"Ÿ²sž 1XŒÑ¡AáP{òÙãÓòsçYÌ#ùî¸m¦À<1éóxåâSøøÅÿ òþçËQ[OÑ]`#€¸`>›hVb6˜‚%%¶Š¤ðNÏÚÈ ) °AÅÔ’|TÔÕí¢¡©b_Iµ!*etPÄEK°@ûfwýüÊó‰¶sב§ÏÇ)rU ê+rUpÆ ²8IîJ௣ø ‹䝿´ïÜ Úy€|ÀˆöÍ—5¬“Š[y]/e3ÙB¹Fva\È 5¯q€býš.{<<Ä0€«Z”|<‰ÐÖØóÂbúoú(ú šEÖ0…ƒ!Àëóù6Éû_ynó«d®x’w%ÇeVƒóø×MKr1Ð[lԫͺ¡×æNÓ:8´Ñd±áŽ/>|ôè'þÇO@ÿÁ½À’~[>?Ý¿EÀ‹®ŸJîõù7=žP”4>öx6ý>oÀÙ¦QÅ}k;œÖ>ˆÈ­ã½nËÌXöB—­H¦‰`úqÒdÊÛ)Ъ6‘ô]å~.»•»çÏä‹Å¢fLFcÔÉeà6à\Õˆ 4̹ñQÖÊˇªëµrEÅ/Co4šÍJ%ý (®¡ä†˜53©¿H—IéP½{ærrêätQãÏ&ümrÜd1П;+o_Is¿½Ûï÷®“®Ð{Ô»¹Øo_ßß']Àô¤9#Õ^@Áé 29B } €Èî~$§…bƒÙ?3ÃÓ±m20š/„>Pó˜CÆŠ0 Û˜Bj踠Ž4Úgr©™ãwQ×vZ;¥F„¦ #  PÂ2–>\€Ïkçwy>Æ uMl>'éÕ‡½Á`Т|èÊ’Ñ ús¦woýàm‰ 'éÑ饺d³ÈˆÂ9ìÀ¤ü¯³@`Wè…?|>“Ï}­ iåìæŠeD/?A  ÕÊ:³þèµ ã/+€†¬TV !œ#<‰(ž¡_W+G¢¡ ÷êÝ õÇÿÞç6=¾@Èë·X@ÄežÏ¿¾,¢v(7ê:´J³åPîO‰À»gøðÑñð¡öÆ£?L¾ÿÑût¡)h«d®°÷W|0äè}þh  {‚M½®Å&ŠçÊf`scÓ(µ0ÙçºûòùŸì€Ð«*êz±bÔ·(uÈécþ/Ë›5¹ÀÅr¹‚9šä\!‡ g¤Ök<†…΋Q¯‘= ‡ü½Qßi-;­ú4ý O¿òÊùöü•ùšÊÁ6ªf”n£{Gî[ÊçÏLNÆa‚üd‚|1æ6j3”ïèßyû6d–ߘY7{Øñ¶oS*Ý›šíÒýÇè ¶w¥öÓóŒ8L¥X¬ääßǨ?œ”sizÑ‚å8ÍÈà?ärô>oå V.Ûïæ)s)#â—ÍÛî:%Œ¢i4[-Šœ1dšB=}ý'1ð”ÒùäjoŸ8=sÏ¥ÓÛØ: §¶ÃWÁ'ÌãçÝtÏj}÷Ý?áâ_X€tб–r¤”¾ü—ÿMΉ^—ô׿~-½+^®”ò™\‘cG2€”=ÖØ.ðÀÅ€¶¬ôë²Øeb@LZb¸ÌcÜdøу΀eˆLy…Dô{*Æ"¡` àóy=þ+ü幯|usÓ z¼¾eÍÄ›çñ¯S¢í‚‹Ân²*Y­FkÚXð1:˜&¶ÅÃÏl=ùáÉôÁ½~_\9 Ñ[FÐëUèéC~Ÿ?èó+þ€õ…7#WBìþ7ƒ>¯/D1€BÙ@£Ñ’Ïß]=ÿ„ˆJ½¨ŠèT([ï¡ô—e,ß–‡§ÜË` ”Y.ÅÊHzçðµUzïëÛͺ,ºðØœœ†5*FµÖØ‘ÊßÚiÔ'#2{ô€ KBv`ĵ3R ҚɄ}2'îÐaòÈÏN>&WFœõ™Ë(’îRDCòv»ç´oôû€×9 ·Mq¸=°ûƒùõ…u›Ž8iqû:ç¶Œ-f ôú(p±Ý¡;Èã£ä¿&ŸúóD½uüN¦3U/y‰`‹Q‘èSÊÝëØÀìµÀ"E"‚áw»ýÆú)ë×[-•2°u(èJhÇ'Hñ(hwÏFÍ g­y>!ˆ¾zØ!êÒù$•vŽžßáBRëÝ·€=€hœ™ ÂòÞ€lE/ÿEäÏ‰Ž ÀÿrMܨrû9 rë¨/i‹ë¨<ÁÜ}áÜÿC¨ 3ÀÓº, V`À pŠ8[¢ d‘ oáM~úP¤Ò‰x4RÈxýW®xý›ÿ¯lz•¨cÓ»*¢Yá_·d©\ ý~µ¦—(†¡´x»IVžL­;tݸwÅ[¶5‚kÝsEê¸H×j‘Ö‡ü~_ÜYz%J( ”K_$ðÓ!Ÿ?öú¼0­žðÎÑó?Y‘¨ŽA´r±fÔÌlד¸¥e橽Ç2¨µQ¨Øs÷™XU¿=Je:™‡Ðù åós+©;h}÷{ñ”ˆESJJðó¯^½hTEN;kÞx±H·o~¢•òŃÏþ£†yhUÿd÷æ ¤×\Rä*^¹Ò*èC˰õ€¢U—µý kZî =¸l“¡ sˆPlHú‹Ç¢(¡ÊìÏ]¹‚¼Í`8DG¶ÑlðíœË¿Îè»ØÀ`³‚á™2¸ •&ý(ùX~ ØNó¾#>þäý{Ÿ|xO|åD›® ÂJ@ •`ˆž7 ‘öûÑP_+!Èþ"„–M­ãç\›(›9nÑ»UÆx¶V½7Y¸-³Žw^Ë:šZ(x- ìãzæó1Ez³Q­VA]G‰WµjTé·ÿV«Vë¶’cœ2” ¼‚1=ž½ÁÉÉm“ÆŸ"zŸ=39\ÿ”Ëä‚9 à`ÂJ;æïÏÚ7ÑÄéÛ7ÉÞBDÛgsÝïš8 Îu-ò[œÃLÆrJ˜­ÍtL*Mî~1žI €âáº|Ì-9ž€—‡‰zŒtµ˜{/‡YËçͳí×£‚|èÒÓÚ69,ö±¯Iàv0U ÞÑ;(î=Q›×´Ïçéó<£Ó±éŸ:¤ßôÌ}RýƒÁp0 óyèÊçç©x{ÐúÁ·âÑh*Jù?ÿêõumz!‡‘/žH þÆ×¿N®þåkåBñ'ÞçþÀ÷Ï:óõ_~ù‰"fýæ•yÀdÛßÄn@™ƒ(BÐ* |rPµ˜?c–ƒ2ȆÈþÚBÄÓÑx4QÈý8Ý& @®6Q<Ï}eóhæ1üëLÁË\PD^)OKÅj½j†6=…m”v‘Ë7›o_ü(ùáûŸØà=Ôi…Rþ 1‘p¸YÙ®Ö#ô7;•P8‰DCaÄ¿¿Ì"³wôüÙ¢’éX9yjVa‹™È@Ö1p.WÈä(JÓô wÛ¦#Ч¡W˜sE9›¼ÜbZmîÃßn4›äý·IåÉïÓÛœœ²°´dxèfÆ}8–ÓפލÏM9ˆ~frúr$gƒñr¡ŒrH—3ò S«×»eÛ¬N·w±oߦoÙ·I#ÍN—ò´î¼½Û1Í1=Öfž¢g gÈ„^Ç´ÕªLG‹•|2AW‚ë<4Bl0fj1›bX>»Õç½=LÝ‘ž )ü<%\0«ãЧiŠmÄTÕjE6½ ÞçÖÛ“ z™{Gçsí|˜<ÅâðŽöB((è[EäüIŸéùÙ27ø8{­?y=ò¦bÁ Wàùåë£è€L€íˆR®|6xãÛoÐ#ýõoð,བྷµBñŸÍfBh:•û”À\¶ ˆšed]nÿj ÔW{€1ücÚÐË @9W:iò¹­’8•…öS ”©h¶EäĨn‹ÂhvF£¢W¹,Y5j ëK£ ´Üž»çÀÈç?gT4“ÏfúžýnÖ“½ÊíH«n Œ>Š*€ñ29×b »@W³XvÓè£i>sÐŒÖ)Ø¥5L½ÎfÕãG…~Mç <šGrW(÷žCÉ1…ƒ å3?+9T=9¶rχë€ÈÒ‘¤³–Û}‹¢нÉRœö.°“ú¤dvû&`Šo)4è[=~Ú7Íîþþ«ä"èý%HX·Õ0FMiÐ^@@J#Êè…NñgUèݦc—u³r4Œ”¹gTAF_Ø (4b§Ù@”UÓJÂêÍ=á¨÷ñùè|vÞWžO:/0¨:ôï’oúq8¤R¯½óÚ;­W[ßÿ–/èKÅ6ƒ\°gŒR»Ã,BËæÏèÿ·ßHƒ @[ˆE³TøÂ·±¡ü”ëFµO„ؽy3½ÿ*ÿº ˆ5s-†‰t­Âf W+•UPÇ’& …—€|öd0“¿šæêѲŒMÁ!¦D*’ ˆIé( +ˆÄC¾Í+ûþu“¢`ñº,±mªVMÇÜGÉPk |}²²¶ëÛI~?Nÿ %ïƒI¢V]-ë`òŸ{þ-ÓiUËuµªWÊßë¡fµZ®Öúû,åCãÑíöíÿûÙe0Ùo16m¦Ã²H¯j`;³LáIæ*ÌGØô»z˜ÀVfèÜ’;£ã;â2ûHÆøz2ã{<‚߯ooÏäNÈÝMÐ#…¹}«AñW–‰@÷•Sòãë)|ÇøÌÚõO#'ÿ‹¾öuÈ@ëyvˆ›õèͱüÖÂnßFHM¿÷G.ËÛ³îhÖÞ”þÁbA‚^ï:*eÖMrw)PˆŸŽ¢Œ½A˜:šR^èmíË€Û îG.8<™aÎh<á÷äÁ{t¦¼ü‹"½í’c)¨Gªï H wÐ?;z½±M¯\¶)‘?qRŸhÙkÿÔùÐè€dy>»Î/»æ`HÿìÃÔw_{çõwÉüà»ïü§Í@ÈÚx&€K¢ÆÝåÂé‹JiëŒøö·¿ýèÁ¿ñ®‹û×=â¾^(ÿÃÿáó‡eÒÿ5}â¾xùÆåôà(4ÇeÛ_Ó#L®•шéZ«ðüºªJ¶ðË ÀV©rÂäžÏ–òâÔº,Ù~ M’‰D8Q˜­€ÃÚï ø#¡ oÓƒJàeüë–îÝ®c’‘¤äÛì“ ·òØÑz¿j÷ÌÃk8´…çp˜üÎo^‡¢Üû €S[­ëò0ß¿U)S¼NŠ xɨ†Q«õš|~ü6ÐaÚÃ|jü笋f=¹­®“ÙZ®‹fÈ¢”Š:À€r’°ÂÉe7›õÐïùBAÍH›Æ³£ßÅ÷œáœç²2@ê…€˜‡é&pÅôs$oïÞº}ûv»=¥WÍ€WNÊ/»þ©ähbÝ™?¢ÔýǬ‹SÖEÈoìI»M1ÿMÑvבænh»¨’öo<¯×îšXî™Î°<4š¢Ú‡‚Tzƃ>$÷nL6<ÛzÃXÉ'<¥À[JôæM¹1ÒÊEnÿKìÐ\Îâb1¶ñ»æ!éÿ`èP б]JD1(êìÐGoTë˜ù“–'_÷>>Ÿ'Ï¥Pà±:Ÿr—MÖ÷Á º‡në»ßþþwÞ}ó»o¼ó§òÇo(›„žŸñÝzyE•"5g é(H‹{æÍrRv­DÉe­yÿÏ¢âeò´—€²ôåD£gºª—ËQžXþ¦BýãO”ä·Š''3_Û*•€ |0cx0´è•%ãñX,·L€rî`Bñù7½o@±/ã_gr ïõ ŒÞuµR^"ÃÀ¢Ÿã"ôo¦o^¿öÒ›dœj³> ØÁ÷‡þ—´R¥ÂÅ?¿¢Ò;P©X=ÿÜ}ÇÞc.(<ÿ9€9wËÉZ™L/›íXN®ªT :Žj±Lù Ó×åœ%{Å%¹r^-h(«M§Xi™Îäè 4x¨¸‘*‘û›Ž¦œsóô Æ_àŸI~ûæííë×ww¯§"€“òË®*9|¹@Öî9Z ÌOÈÛ7{}» *½Kÿ-›œ+LøM=\Ö¾mAåFäìglu&Ü9’Ó·¦23½æ©iK9E!ÓÉ‚`8G1Ôb‘B/3º.YW–H7{6˜¸,ûSù…ø!š€­ízEÇœE­ª aÛ=Ö=9àËcÎCÃ÷OžO €Xh@¸.p£Dø-1l½ûÆ÷¿÷ƒï¾û­wÞ|ç;ï|˧„¼AŸÀó[r¼°Ûúªµß¤¹«ÙÓ@²ƒ¿™~á2÷…çåRz)K>·PÖ*Íû5Ÿøàå›H~ð ºeRìÚ2ñç1‚Pe¹ ò:¬ÄT-\Ž\d¯æ*' ø™| 2ëÀ¥h y@,Ç"‘p(އÀÊC¿@Žv ÿº ÚVú8PµÁ¿ Ô`f™w™” €ndŸÏžŽ¤ ðµÔËâþ.0ŠÀpÓ«èÝÇÐMKý¿ˆ\&ùšDдU÷)AÛÅ1|;Z“Ó@z†~l ÿE€3 ™à_ÐçàZéT*žˆ&Ã1AÙ@(qì'à_G Ö–ÛûnGÆmd$ µ\ÖJjµbî*Î£æŽØ¬î4þ­6ÙÂtº3謃vÒýÁ±Ýln·"Á0›¨´ÕZ;-M©€/¦»ÓUy¹‡çWÏ€Fê:¼Ó+d­<åK™À€)þ/—ÚŠÎ*³ÄÕ§ ÕÃh×*Âÿñt •£8"Âöýt.§_°“7âëà ùdÌ›²“ÝëívûúÍ›rX>NÊ/»þ©ä‚»M1™‡Á?¬çMûóÞàR~kd/ì•üúØžÎÚ·è“ñ†|LÛ×A»Ëë†=âÙ",!³ÑÁ`1é;J$‹ÑÆtG›lŒª,Çn0E‹:(B uÐÔ9o–èí¦O ‡R´ýŽ»/QÝ·ßíˆ}È­ÖN­Tk6éϪÚlÐ'WXžôuÎ& Vç£{|>Î=Ÿ;¸C äã]ûîëo¿þý7¢q²o~ç?¾õÇï¼é%|a‰‰ë2$¦¼p,,iFæùÌ©@>؈ÿ*!›•#åÕ+ñ”Øýfº#€‡ú1 QÉ<C5=V©,‡Ð4ž "k«D.4ô íxú_`nÀ3°Ù+þu èa@ `Ø&sù$üëôA2 &1¬.Y°ÍjMÓ åb±@‰ŒY}Ðì7"ú òè?§oþøþfå<Øn¾«Y«Q(˜ öX›Ì@àèÕWëfUUÔŠ£Vk=¿ªV;j¿¦Ôt½¦õõª^·XÞU«W1K­uàL ƒülÿ1Ó%À@± øßñ8)¿ìú§“Ë!ƒåîðªyOW¢nJÞn“v@Þ¾Þ³—r2%·&]L¶o“ÂнQóû ’£ûOòQ]G‘q²1Ù)çõM–OD G”F³IL…|†Y( ë[Êе۷àP餘 §ÈcZ¢Aî-]Û–ˆ:ÕâöövM/mãü=9ì»é¾:4M›»ÔnïÎÙóé®Î'ÇÇ!«È|û{oEBD`¾õÎÁ˜N‰}yþõƒiçy°¬e¾–9ažœ÷³º4ôú´ò‰m@ðƒKrÐuâŒ#þuüK%t²äg—†ÒºŒÝÂ’‡aLÑß{° k[3Š%è@sý·ª”óW~ÝP#Ífç<⾫µMJ߈„ƒŠßïWÊBTŒ¦¡‘%Ñ«F p‰z’úªb`.§V……¬\ ¯ZÉPKôRÈ䘱®›•ÒÙô¬R€‰Ì_Ǭ7º@ºÍZ3FÍp¼#FÒ¢ÿ¡³”h4¿}s÷Öõë×oì¶o\ß½}V~ÙõO#ñŽ/é!ˆÓq4妘<-o·m›äí[]›åÈ è“Ÿ"j  –?æ~jL,–òVCÓXÞR!ß`ùÏ8C[’ì ª‘‹‰^. à"¸ÇÔ%J2PŽ7€ý}‡ŽK×q‘Ì›CåÁÀˆªÑÃÁ¡%ª¥ŠpDMÛ6ÛùÄÄ/èöS´o“ó¿ƒáäù¤ÈΧ»:Ÿ&ºÇ‡Nwpx(R±D$%œC‰Eb!ú¥ÄCbyþ×õƒ @±¤_ýÚÖ‘øm3Ïä>lÈáëÀc[èÿ’x:ëÿz·ÃõLNó³_Æ¿îô°Eq•‹˜Vž>W×Ý©Õëj]Ì"ÈëV °L?2•fç<ê.yÿV£N AÐëõ—€qÔ,èE”ÀEXQ+ÅšQÔ Š2 µJF§Hãñr²B˜K* ­VÝ¿LÖcfŽ{…«]ô)¦€GSž¦™ Fßað¬17ÔÈ "ðF E÷):àGòöîí›7¯ß¼qýæùòË®ÿ]åèYRÚ´žšó¬óFÐÅź¼=±ÇóöÂ^É\»¼#Öyt÷ô“&ŽÁ—rO«UQ!ß6 ß`)Þ¤—Mä4ÂhB¹0rèdsùçsÙ«|fúÊÃ}ô‘1ˆdû`àèü¹¢Ú®k¹y`GÔ·Qbê·µóÙY?Ÿ./¬Ðù„[3y‚³À‡‡ìN‡‡¦âÂñýbuþ×õc‰Pж¾¶2¿ýã|ðlîÃàk[ZáúŸ;f^#Ï<Á¿¯ß7×øÙ/ã_?ɯî`ǶíC×îU£QÓjÅ2L“wþœìªøÏ*nœKÞÉ÷oí4ëõíP(àó¸Z­ZSåM\fð-h¤íeµ@:N6.þ¾^¸@^ÖŠ›Õ*°= K€L½B€Ë`ÂúÏEož§eåXÎ3Œ–]FÐF‰Êê7™ˆ“òöÍë· }{ß»óÑùC(Àk%6zÓäÍmvýä¯O~>]K¶ Ñó8°zƒÁ sè:K[ Dÿøü¯éÇ $G S‚ø]ç€gtŸl¾”!ýςū¤ÿrÀ}öþõîi~öËø×Oò«wÑŸéZö«¶ÕA)¯iF苚bÔ@9ôG0ìðÚ›ßùÞyôÝ|ÿÖ6%üõpÈ/ùÁA^Qn‚\¢d€Û›ò¯ùyC344öTLø–Õ åÿ«Z¹Vƒ?Êf‚öerÒÿcà¯òżÆó¼Z3âr ãðLYýxÖNÇrƒ…)Vl'äí[»7n¸ñùe×ÿÎr¤ûœÍ/¸nªÁ ¸ƒ]ú%òéXÎòxܽÔ?H©V`zR¾1ÚØð´²”o<äW¦Ü|œŽˆ®ÕËN …-ÄY9+ŸÅ,ŽxxÈ¥£ˆ;½ÇçTyþ8ˆòüýôï¿ÕùD'IþÚù? »˜Ããó¿®Gˆ@9-ó5 ÿ®ß=x6_ ü?wŒ XÚ"ý?FZWðË ~ûb¾ó* é‡z oÓh?†½f”Éw7xª×¨©uÉßþGY‰Z<7VüígøÝºÕІ1üï÷”íæv !>ò…2©v™9n Ô“Îk”½Hã/ëô½¢V6jYjg·òOö2ÿìAr,l—Uîþ“G]ð¬í‚<—€'ÝPŸÉ¡wÚÑe_ÌÀÓöõ7$rëu`”HÊ/»þ ï?—°€³Åbíþó%Àˆ·y}}?žÛ—’‹år¯%9I ƒ.%bàX¾1ŸoŒÉh•cy8äõÃ< M1Eÿ`lhEI#œÉË.k&ƒU±A·+stàpâ¸t:&éö™Ï¿w<Ó…óÂOëU´Ýwõ©àVûœ>é\x=Ó{,éALÓ6 à7P›tùcÎ/xÅà¬Ë!Á(x¾üÿ?/?Oþÿ&`6£ž„;â_’`Ó‚â%»ß1ñ?ýB2ì’Ñ[Ã-Ýóù׫UEkh5£V­7*õa ézMØ"+J‚-€äo?ÃïÞBèÀÚï¥GÀÐfÅ(*ÀxË(ç#Ä× ôUQ/Ãßõ‹ä° šªV3[X(€)—2oÖ»¢ÿγþsÞÏX;²ÏÎíÀù4Ë\Dsk}1å¡Ü9k,Pz(>¦ßä^àî-2<4ìœÑL /¼þ ï/{€ùXœ¾ÿˆñ9¦SLOؽXéÊþëò“  åüó”»çëêýÖ¹EÀgtŸòóùòIPÐ\¾tšh­Íwi'8îÐNÿIfW,àRêåZýóXþõj×âî= ª¤ðA2Λ¤œ×`VüígøÝ[Íz#¬øêõyt½©ÕÛD0AéDU© ½ "y­Z«ëh’¼Û}œmÈZ5SÍä(>w§³¾|ޓ˾÷Þ{ 4›sÃþu,£l´½°;å]Xx[ÀÏ ¸“è3ÆåAd=e|îb}Iö‚xØ¥^výÓÞŸ§õ's‰Ñ‰Y~Ìâ ªà…Áñeò)îcчa}éIauP's-PÊ£AÏ;›QÒ?Úo«o«VÒHžLFþp"ªxx hla5¢ø®l总LàŠ-÷²]+‡Ù ×=(êùÖ@ñs‰9¼¹àÏ?áÛðùC+äIÐÓðù@'ßêìA×Wá&Ž_· Ëž.½¡«~î¥H}^Å¿˜½V©éª!"!ù…ÕM¦N¦’¿ý,¿{‹€P8T( ðûÕfM‚Ô úϬš†bU«¢ÔÕªbØJµ_“ƒuƒþëU÷Yn.噊R­ÒÛ“/äïPï¯vŽþ,™ö3ö:wOyý‹u¼I‡ôF€´`ŒŽÇå˜Ð°éÂDîÞOQ”ÇírE:™°‚^|ýÓߟ5|ƒ{|¹@=Úš“Ëä˜Þåv Üú§pÏ;B18à )‡¼›>/Ë7& c¾±1mÕIžNÅ¿?V¼þÑHŒ• ù6½-wm+Ç,¦SÈä°T;èɲMÅçõG^Èç÷ûðùG#~ÅçF‚^&M€ÏG»~ÿhÜÓµ°n-9ÂQñ5‘ŠF<žÐòütN]ß!µïŽly¸èÓqÀCe]|~âž–Ÿ49íð<«ûä¯j¹S@]c–$ëxÏu\—÷8±²ÈB(Äq=Ûí÷î`’ã,ÿzߺ‡–{@f³[Ó´¢øŠ?}]ÄÄ;‚ÒpÃu­¡mÎò»·šr Èçñá¡6Ï5*f£ çŽzvì=£âÔéàm]Íoe²?ÄÐ!€æTž­øÀ˜]¬öØbÉ‘»)Ào0D‡›‰ôHaPØ’ø›H&\x#õáZúÛzÜ®R—,®#Þ†îpŠ=¿ôú§¼?VynWzö1£2 ý0Ít‰\6ù§rŽoÊØ#!:àq–'Ÿ×ç#yh£QªÎ6FcO‹äÉD<èó*Åëàj¹…ðƒ(dd¯UÎfì ,©¥Œ"O N‰ˆOñù"Ñ^$ð+~%šúžóyѽƒšÝœ9NÖÁív±áÛÜÅy:Ÿd"™Šýž@\ž¦£8ºþ̨xw°´óPs~q{ÐåÐâÌù>ͼò8—¾›ùñ­8…©j­¯UšU½ò$ŸguŸÂÕÓìÀí´p16m“IEtsβúôj Eéßx£}¨@äü&é<þuÛ )tjlW„'"â?GȈfôÿÀ vÎ`ßð!Ìzp’-[ ûÉ£ˆ.fÀFÌÀòd,DÚ§,"~2‹ÅÆd¶‘ؘÎSIŠ 2@ÁÅèç¼äÛ•À½ú®r«s}Ž&ïéÐA xþ@8!Ò~ßfD‰Gcáx0ˆF‘P€>ó 6pÁïÓ…ë'çOî©Ôrw¯cbî´$X§—L¦èA¯ÂHÄC8?X2HP`¢ðõ§–ź}‰'lZ+ž+óÜó fkW²d®ËO³gŽ×xÏÒw3ºW¬²ñÓ+MÉõsñ:ðÓÞ§Ñ/¤‡ªa‡vµnyzÝ‘‹,§å]ûŽÓÐÎ _T Ñ?éq=Ð:Ÿ¤€=¬};CáS"ñŸGã¨H‰†øÞ+¯ÿÝC ‡CÒ~ì#»Ã!øÝûvßÝ·M8»ôï6š¦¯/7/\=»ŽŒN6{ï^ŽòNø tûŠùœ/ÏÍ)€ÓV]RÙ3Þ6†lGK0à9“æMµ à!WDäãeÙ~ŠBáTN /ñf Ñ=ÁJ̘wâ±v‡ZÛlzÙõO{ÎVøZ.èaâ‡wÇL8ž_*Ÿs0âD£(0"IÀ`ˆAYGƒþ`  ù6ª²1Å6 'ß •¥lM ‘]ÅÂa é}!Å—ÏcÐ"ÿ^þyôZMúeåÐ3FÍ>è øB‘˜H“UQÂq‘F"¡P<•La%x%bŠTðQƒ.ú!úä—MÎbÏ …B±D,Eád2•ˆE6CÉX0Dx+™H$¾ ¿¼þ̺8 Ĺ[à˜óÏ/NYsÈÝuùé`iΧï^é.»nÒÚZTÐûñàYܧ˜Ñ.4¤EdÛŽNXÀk8-_®pÑwúî“©Á À%ß8Ÿ}h ÜC÷ðà@D^‹Ä߈¤"WÈÐã¿~ëMifÅ4&}{ý.FEl6뵚*Å Xfæ…€gIð”ÏÕúüˆC1–J§´(Õ_¥¬ß@Þ?‘˜³q¿[†ØØ³Ÿ.·è¥;¤ÄyÌ…3îÇO%K¦ì§6af\LéŽ&˜"Ïe—Í´Å¥×?íý±øË˜¼SLã§rŸ‡šq:½L>e8q9"<ß‘ÄDÅX$O,å 8çP4ªx6šŸrõø…OåD"I¾v3 (ÁhIÕ#¡Ƀ±ÇÏÁ×ÖÕ›ö;6Ùiú;zfˆ IDATEc‰TH¡/‚)öS"EØï îaÖÇâ¦?zúô9;6<ÒadætBû~/KÒU"L“åñ!űœD"E/K zü¸þ<À˜Õpcw~.éÊ^kòÓ@îËïúî‰GüüÍŠJúüƒæã|žÕ}йÓ@i=+Ê È¥“Gñ›ërk¿Çøåß&·í\”@$ŽfÀüëðÅWb¿øcú˜"Þ 2öok?þü­øÀStŸþ la0ì"¼péùZ ,ÿ*]zéR¹R? 8 µIÖµV7—³è-¡÷ÀÉf¶°íýWÁ¨•uM5$Œ†ô{KH«¥7äâ8¦eÑä–sxSl#l06—#òã%ææAÕ5]ð¶>põPPšÞä²ëŸöþ(#ò¿eÆÕ Ï=æž>æôç—ÉoÈÞ±›s¡q*¡EgEÊEÀ‘‰Ïר0Èm'bá|!¥âÑ߯„òª¡(EòJ8òy(Þ"#2leÌÜ=;ÛM,»†”p4’ˆ“Ãö(J<&R¡€¤D4 †ƒ²(ž0%Þˆû(üî¡ïlî¹}ñºtdÈtƒ¾M :â éD"Oøý Å ÇÉt2™HZ^J!C~ 2É-Y—{‡ó\=þüò¶âºü”(äW”^çÑwK%f•Õ¢¹bÞ¯èc°üžÕ}ô|áBzðW): ºxô™ÏÈQýC3Äìܱºw…Á’© ø×Cw(‘Ø/DQÄ”+> *Ÿñà[o“ ×èš!Å dÓÉ ˜hC0ðvöÁ3_©/A!Fµº ¹J ~\ßf2V&w9@ùàÿª´ÅÊTÞ¡£.™r²6ÎK¯S^‚a2=ž–å@ìÝaå hÁÁ7ÓgÎn&ÊAíŽo„Kgãˆä/¹þiï™!”ø%`^p2ßÝÝE¹_bÌÏʗ퀥œÃ|ü[åÆ!›Š¹œý£WCnÖy\Ä“éx*ð¶7”`„‚B!‚É‚q2F2 L4ðx|xï=€]ÏÍrì­´â#U¢±H4‹'ã‘€PbñDœ"Šp<=a%KDŸ¯Ïkéï˜z (Hüzœ™6=ÅŽÄãÐÿý‘¤% oÉT2MÉDªëõzQÀ: ‹áæsöAf]|~Á4´&?er¥ÇyôÝËØt6¶Òeæ÷¼äéïS.å.4Lˆtvùö˜µ¬·.ï3xƒLuǾÓ# z@ŸÆ« aî9P®^Ì¿ÞïÑg”úW]Ä),„øâ‹/þþ¸1<=‰Û8ÌïÞë9Ëñ‹VótP*×%,t£l¨šV]ƒ…> KNÿ7'w½0íKiæPî•üRý¹¦íX.¿qp, ~s †5[ÉÆ…‚ †P/_òs‚g"挀ÍÃ:üCLÎî•‚l˜‘ Ïò`Kþ²ëŸöþ<³D!ty†Æ]zãv1§Ç`Ó3ò‰Äì9’ÏuyÅL.0Æý,ÂJ ‘<O&âéxÔ¯·õP˜¼w³QÏRÑ€ÏC€zBñë"F×h"èõ’ l+›yž1ÈôDR øüJ N$IK“© ¢„¢É„ #SØö‡ƒáX<Ghp§ßÙåœÕë`õ´GÉ!t:+re“ oˆÂˆ”H&“±4º{(à ÆRét9@<àùªbuNÀÆ£¡ØcØxÞ&Âþíâó».?eTuEèq}·ÌÛk’ä[¯¨ÌèS‰]<‹ûäUõ”0Ö ƒ«S†ß_/Hbs]Ža@TÙ™|Åv½f)É6ô# çMz|ÿ:ó·‹©p@‰|ë¿û»¿ð€CŠ«ɪ Ùì¸Ëºl½µ³Ý¬Õ@ºQ5hz‰!Z½d”Š…êibˆSÄ$]ÊÙ2ýÎL!ËY@&çóg E¥¬êϯOx/vÁ nHü™ycÎc7c滟±60>#][ây—ŽACDŒå,?¹PJα;-7õx_Î]výÓÞŸ€äÚ@ ww¹C¼ h 0ÜtJ¾XÈ8rŒÃbi$Iþ–x€Ž‰hÔOÞy<‡CáX’"m%ØÒŒ0¥ïõº•\¾Ÿ,@šÂîHÀ«$#ñþÃ&§ÏSØb4f`ÉZÙ¾ˆ‚A2”¬SêNj¡ˆô5$K %J ˆÆÈ4Ä1p‡|´xÒx:¡€1ç¹±¾Oñ*§PhIù~:H¥`‚0>DdBb)ÓÓó‡œÎIâ˜N‡tÄ1}æbât‘ Îïºü\zðóé»Wù»&ÑükÿÆþÿvAð,î³N^Y3L ªr:§¨–ÔHöºûÖ;ÀŒty“š²3Ô ”Š ¬¾¹×(š¸çó¯Kj®}ñ¯¿ˆƒ¯¿ ÿýƒüÉ]9ÈŽÈÁÀ’Ô5ô *F½µ]¯²â3!/E¥:¨¡šZ³R+r pšê$5Y¿‹ÒSPôØøÍ Ÿó€ŸM crmÄ`W̰-giÇØ`ƒ", °Z®Y¢q’žÌe-+s¬KŽn.ï1$t“sí±µŸq×n hžÑâÔõ¯¼ræú§¾ÿ‚ó°˜Ðï~ö¹4cÒ~ÃÐvç§å°5¼ß0^`~Ÿl # Nç’q$-$P~¤¢äŒƒ(ý‘뎫UBá–ZO¤â:é£@_„¯×‰t üŽZ,‹$A¿×ç÷~ê%ÌB.'P¤‡¹a@Æ$"¤Áñdˆž!E ˜ˆRNOÅ¢›~A;¸ÓA>€cÕëwºñ`ÀãUÐyð(adü ›H§É„É$‚Ñ´ˆÆ¡p2ÑôÉQÇq‰ÉÃrJ!lìõ‘ ¸}ë1çwM~®8Ÿ¾»RcG£“û§2¯Äÿ­raðÜgݨëþYÌóŸ"?\‘#Öåôߪô-tEº{–é’š=R@àÉIþu°¬¢ó×;t‡èÑñ¯KþvÇñ_üüµ_Ü¥ÇÏ~ö³»Ÿ~òÁ½?ÿ+:©ƒ!Î[!dª C¡  Ù`ÿÏ‚€J½g7õ¦¡WkeŠе“ä'ÉI¯f)²+`é—1)òä}Tfÿ#ÿ?c ©ü<õ"ñu¡ëØveºL•1=Ï›9«ø|ޱøÉR7¼"0G×SNÎ眞Ïe„ñ#5^]ΰ³×?Ýýù1e*ƒñb÷Áƒ’šƒ€ökZ©²{J¦QfZŒÆ¼]<á^Ï cµ& êïé\ˆD(N™z,T¼`Œ²öh¬U®' ]ÔEc§ǘ¾ÁN&ÓQLn–U•¢Ø(yhÌe€Ê\ì9+›ŠÆü´½Á /JN;Æ#±d*ÐmãqÊà22"NéêE.X^É[ý½>âpójd’üAºÛúzüóÝO~ô×ùgù#p)vp]˜êj¯¢·êZ§W]EeµaµŒf‹ÅÕÎuí=ô zr+KÆ+—½êRî¹µ•Ëz ù«[¹{þL¾X,jè¡»/ígl½Ž lÄu@®•Áù3ÿˆôÑB‚„ó¤îHŽ  `·¹r4{qžçvºõcY©Ÿ­6våõÀ ï¾Â׿râú§»?H:Ç“Y»Ý¦ø¿”Wè…ìƒb–ôÎUK9&ü€õµ^²-3ž$¡Ù8gÆŸ ³ŒbÖT$(‘ÌHÿIÁ“ÉH*ö|J8§G‹²Ô*ô_4´D" ù°³¤ü ôßËåó%úØ¢áb˹÷2[W·2[Ïg³éx¯§tû©¾¶Ìh$AÖ œ E“éX0J·M…_­ÀTœô?€©#ëÎrY yÏìÝAdžH¦"~å9²Ao–#!`Èe(¯ú§’©P€´=àGëî4}¼‹É¾Î1}üêüвöèïXwÐ [žßu¹(œÃ|>}·N®[Žðä{Y×Ó/j>‹ûœ6Å3ôà&ãt›v·#µhùüí}猋¿`q¶äö.%cÖÉÂŒ=æ_w,ù×ûGüëKþvÒÿDô_Ý¿û·~úÓ~øÅÝ~øÿûŸÿu\.X"‡Àl3j•’ÚÚÙÞ®-#ttŠš-}»ŠwD5ꎎ?@K’Á FC“w²ÙGu"Èú?ø X.WfLj‹(›ý&ªjhýaøuÎ;8Øšc®[hä˜“à‰¬L'LÏ'ûòîî£&Çó»h˜A{¹—0‘M…p1f<º3?¾~ÅB׿òÊÑõO{NÝÁMr£ÝÞÍgÕŸ×ð¾å·0ìd\K9¦$—ÏTNO‘;úÄsS†§ø ðóþ0I«è°GB‰„ˆGÀÚ¥¯-Ý¢^' P×S ÔÞƒ”{|¡$}­(ȹP|E0s»—”Šå³%©sÒqRFRø¹ðX<éP8–¦^‰DâiŠæc¤¾ŠÇëQüA«Ë¨ÿpPîžÅŽSÉpˆ²PXøCñX<™¤´!JÄÃAºG(N!)RQO0Ð (`„¸N´Gþ çU²Ÿt «ók ‡tÀ)ΧˆÿÕ£ó».…s"€óé»+`ñ©D!Ñ@o£¯5]} šï³ºÏiP8cú0ƒ}©•cJÌ”ÎþІ kVkr´cÌN§{@z¿ÿ*…&ÏÚtÍþÞ~‡³²#þu—éF‡Ìäbv;¯bˆ[ò·÷{"‘øãÿóÿþä=JÞ~øÓúéÝîÿÕ_¼w/ÛðÀº]£jh…V³V«ÈÀªPnéüMÍPu£Îu€Zw¯{"„ëÉÅ- ÑîdÍÌÕ òOI@<øŠuUf¹ Ô\,GþxíZSfºFCõ0ôç28Àº ƒd˜ù=ttÄ{z¼¼7fÈNŸÇ¼Ã‡)Ú1Oò±ÂŽ®?& }øÑõOp„·Œ¬”UòéQKZ–,çK×^K9‡äÕü`Љ}Àƒ çä ð$òzeì1«•…ˆÅƒþÅÔÑH(B¡y\×[冨ˆjµ.ôJ:™ŒE)•§€ßçUèÇb!e `¹÷ [ž\¥°òy2%É~À‹¥+*"‰°ß >œ¡h*JÅ"‘dŠœ¤ÃïA?Œ5™¢’žîwl×$K‘NÄ¢W”X4 E"1T&Ȥè¾áP$ŽQH@v$•R¾ Eÿ.Ã…ô`IúÎfWèïv·w&…+úJ3Ÿßu¹ÈŸœOß][j²dúo—¾ À3¸ÏipŠ8[‚@© QQ nýwä. ŠþÙþº¼×±² ÞESÐíw;Œ\¶k<Å¿>ÀúÀ€Ú¶]—ñ,ÉßÞÿñÛ¿øŸþÕý»ÿôÙÝ·~ñðŸüðýtL8CghÙ;Ãúv«T.ƒý«¹ŠªU]&?øVµªéeú… P¬AáŠ8Xe´-“§ €–;C¾oQª|ÕÙê ÑG„ÄmŠxèb]Nºuà’0l12`r_†Ûo+þu»xæGº``aÂ×vŽUò·Ûâµ·þö¯?ý³ï>ü»Ïaþ…B€¿üôS‘ˆ¢wZõ¦VÓÔV³ÁÅ¿å£ è»Û ãÞn6ê:?Z½læ¨cgrŽ•ë»™}'W eæ­«Ïã,’*Ê:7û0ü*+sc¾›cÖŽƒþÙx²à›‘¯EG=rÌ2óæ „åìþˆ÷„ÇrOÂ1äA­&Ê £ý¨( ä~|ý‘ ptýÓÞI~»Ý¾…²ŸQ© ö‡úWÃ×¾þ?¿xíEI#6žÈñ‚¿s¦òÁÔÏ\.b’-@"ðûü˜ŽŽµZ55– ƒÑ4ºö‰¸Q5ÑÒ´–0è‹j­ÜL"ˆ†a”äü>Åǘ‹`]Ë v˜Þü¬MZ›è½–’ KȤâ=º¤ß#åUúèäYÉ©v:O§DØë 6ý!ß¾bb—Çzü0ɳÃJD"A_8¦ø?&ïF~? G¢IÊ"’!Œ‹Tœb ŠoÉûsƒË¥c²×ßcDûÁ‰6òêüšý!f],ºÀÜGì l Àˆ¬ÉE¾xn p}÷’å¯"{CþL³¿0xêûœ2Åü°Õé J¼ L9hÑßß³»nÖ¹š]—›vï CïF;B-˜<£…ÏeÄÿúÐrKÎň?ó¯è”üí–øôGŸþ9ýú›»ÿùÝoýôó¹ûé§ŸÞûPÄÅÀv‡ÍæÎ6¹ûF¥†9À:EB¯Ñ/¡ËÖö¿oÿê×õj#B:ú[ôÑe:VNjd3Vaò<×á,4Ú+ç)dr0húDrýbɇ÷뾘qð\›3%­®$ÕÑ.× ¦ NÐÑqC9{8r ¤®pQxB¬Fh$ ¨±‹^]ûúâÈ€;ðèú§¼ÿd2ƒöïÊÎiIÓûG"×¾ñõ®]{éã@|Â\¾àäšã[l&K$Æd"@ʧL)óßníhñx4A®?™J$jÛ¤õõúŽjþS ÔZˉš%ô¤Ï>6/rË·™À#ú_ÕÊEJÝ“ñ¾b)¶Oã~ñ˜©ô•0FøÂ˜æÃH1ÒøX*.’Ao`?àÝ‹ô½>UTë›{¯RðýO Rü`(˜ŒDxê7%âáP8‹Å¢d‘Òd¢‘4—/¾&ÂxÄ…òRYÕë’;·§Éäùådµ'ƒ¦D³Žÿ´\”råsR€óè»™ãGúnôF–ýE“€Ïâ>' @9W:iò¹­’è[Ïçzý|\Š&Æ¡?g-kokÐÉv ùkr+ëáú߬Ù[<þÃ;ÚÀh4ÑaYñ¯’+¸ŽŒµú»mwVüíâ/?ýóO>ýÛ¿¹ûãwÿègÿü³ïݽÿÃ÷?&à`#à7ZÛ­:wÿ„V)S–³,è­ß|ùË_~ÙÄ–P­¦Ñ¿¸åÙïf=Ù«rTÓÊS ç#gbýá¿&à÷è_ž¥  ¬1ñ=ògÝ€óóL̘wïoêÀyÀ0fàÀ¤ {ÜÙLòÎ3´{€Óázæó1óOŒìÇ”Ëõ¼K?^^ûÆíöü8xåøú§¼ÿdÒ¾½Òþ’tþ_\{á/^{é¥ Æô•T!@>Àº¯¢Î0Á†1D²™ŠAEÇÂÍ– w™ÂiÌåÄʺh‰f];å:,@UÔJͺZ5¸1†I!C!O!Ï `õlea#§5Eú˜0•^€¾L Š,b”I8Étfú“éH:™JÅüû>¯ðûCA Ü“£êXËïtÿ§E,ÊíGpY‚g†Ì‡²ˆDIùYÿéG)/"ê÷vÝùw9JŽªž‹`‹™¾FÉ—ç—GÉ{]Ôû™Ë˜±‡:ër¡eóg»çÒw“s%_§Ä•uÂèE)À³¸ÏIÏžlfòWóEœL¿›³PéèìgÙ¾•Éföò[y7g[ùuyn¿cXYà0P˜Ö,Þ÷Ý=‚½Á1ÿzŸôž\ÿa’úºû’ùÛ»®xïÓ¿þô£?¼ûÙ|÷ÍŸ½}÷ã{?¼ÿ‘H Ë­7·«¿Ö+ì` –Tɨ«Àhýû—¿úå¯~õeµòå—_EµØ"'?C¿ÙÏÒÙ ý·²2èÜ¢?ö d=[…ÜÕ«ÙB¡¤M°ã2br»97Å/ŸûïóÙô¨¿`Vl8ÈfFr<ŽÉµxˆƒ1pÃßÅùæ_ðÂ>Wæà_¡\ ñæò;¾uûV»}óæxtD~týÓÞÞ¾¹khl&9ó¯V‘û_{³Í³—^bˆÏ£3q×|ÆÏ‚Û-§¼}„ÑÃQ2 Sæž'âÍÛÖHÇ(ø/—EClo‹Š!¶ÕG¢Ôª4 ˜¯¢gUÉPüÉemeùsá,ð ä.U‘H&cñ®Òí)V±^,ÑšƒxÝ;‘ǵ'é÷t* yoϧ(¡NHéÊe5Tïœ47üÈÑÇ!z¤éçS©´ˆ=~¯;”KÐȦ¤âÙˆ:äýWËdXsé1Õ…Ä÷ËdËó»\&söxص1¤».•ÒÖYp.}·&÷ÍõH¥\ÖèoQKy¬?‹ûœ4[¥Ê {>[Ê‹Öd2ý\¯ßÍNͼð›¬\'³o­Ës¹é>Y Cm ö±zo’•qDNò¯ÒO ]Ò~<\Ù=ÅNÌß.þöÓ÷ïßÿðîO~üÙÝÿõõ·?ýø¯QH‰®²³]©VzC¯ ͯÕ$U½¤«Ë Tný;Y€_ÿêßý›_ý¿¿ú@×Élñ:gž³ŸËgM´ýåºÙ^_‚ÜV›RùBA%ÏŽ…Úå~=tjÆì¿gúgò^g$p°ó;O2UÕéCŽÅ,¿ÒõÌ0<¶Pz ÓNÈ£SpHFIý“ðÚ‰D4¡äÝïï+Þ@, ˜r]Á GR©… "ˆF£ðþé„E¼oÅh!ÒÏÄÓ"îó&R‰?Jâ€KÚH&† 3Q¬>Z'?y~å:¹, ,¬×Gáá´œ”´H±ôZð\úîX”¼uT“û½¼èK‰Vj®?õ}Ž @~«xr0óµ­R)+®ÞÛÊQ ”Ý»jmõº¤ö3kç²W z®Ù̺ü‘' hð½lÖÅgᔡË`<(t¬3üëîж@¸„ÿ-úݺ=סäéý÷þâ½ÿvÿã»÷<¸ûŸîþí§Ÿ~úÞ}2üšR"ÿߨÑù­‘hÉM€¥0ŒUà7¿ùͯ¾üò—_þú—¿ü%×2™^6Û±èÈ‘ïÏä3 äØûäòô»/“Ïu²\.çÕ‚†\—”bÆÓï|yýÓÞ R}IeßÿÒ‹/|ã…_’³#¹Û*ÀT2~È Ë–#*âù€ŽÒÏ%ѰJäcÑúN:‘N4Õ%c ôýªØ¡ÿ´Z‹3€F¥¤Õ‹ˆ² õVè~ŽÉx²Ù%û2™rI$D’Âx¥ß§zý¤H¤»Š¶¬P"†b],šŽ£û¼¨v ‚èð'â!ŸBZÖ.š”>¤Q1H%âJ JvCÃODƒ^ߦŸ8I$° ˜N†7É|$1sÐé XB],Q§eN_Yp±øãîÛgη ÇŒ,ó´8—¾y;eOŒé’¼%ÐÇE]€gpŸ#½š«œ0à3Pæj'×íæ1§éÓÏez9¬ö"lÛëƒ6kMÎëO&ãR¨ÐÙÃÄ?@CùÄTй\®…ùÞ!e6åŽsè Áx>ñê?>øü!ýùþ½{÷"ô§U¯P$Y«V[Û¤ý:ˆŠú·…*# ¾üõo~ åÿÕ¯¿üÕ—†Þêôy…¼;½Þ‹6†ý_¾°{M†ûÐ]i6¨¸Z½ªaÏàó‘üA,6Êf2z.´Š¾Ù%# P ärä# À²|a@¡`™šþ,“F£@Ë“”á||0)Ë¡®Œ>§¥ 2þb-ˆÄ³t:“ÐÓy£ ÿ“À ŸÕ Í·È¢C“]EŠìjL²«‡)åg)-Ñîº8€þaýy}{è^ùn ð2ÇñÝíɃÃý±a '¸ÏÆÈ3öoì¿ €Â _!“BAùÐnwG¯ÿôš¯N^l"€ºŽeùë?ýáëÿÿøÓ·_ýMûµ×ªÍÚû®®o/qPð¿Eé?qÕœAc#þIÁ¯Å‚õÙc¿°NÕ1†‘°¦8rÄGËmγf2§ôy>Éž±üúú÷½?fw×?ý?ù¨ûÍ®EºHÿÉÒuC@²Ž-¶ÖñïüάX…¼®é9 ^[/^ضä'½–O àè£JååaEuhìn?“E³ïÇ©—/¶ÝD‰WÂk±êšD`Wm„éa!ÈjYG,½€3ZBûT60Šð†%!{©L¯Y’Ⱦ é™TV/—üTœá瞤ŸrJ•,Éø22ºŽ:Ùô³D2!þ%“OQ,•ËEÉ"’É¡Žv#Ô'dNt‹sü©,ö/f‚†îu Ù‡µçµ­.@\~´Úa3†7z¤ò°ó0ÛB¿ËŠòùÇ€@OpŸØÈÚ7Ã@°ÿµIÚ€ÐëhÔåøl‚<§^óãÖMmw½QõÀêþtFÿ·Yö¡õ 9·‡ô×%z:¿˜Œ/ÂIxŽ/¢J¦ò*ãJåbìõŽíŠY)t‰'ÑÖëv»‡¯_ûúääp¬*èJóm·ƒ€¿óM÷£?|û§opúÿñOü¦o¿>@Ž)Ï÷¥„ÿM 6¿Tº¿­š[o5¿lÖ"µ¶ÿ¤ÚýJÚ"Æ&I&s©z…ÎòlPn‚'“„¬´“,¥²)¥of¤ [“èg͘Å) íì"f«H}(±Ø\ÿîãß¼}ûé›O?Ù¹þ}ïÑõÿþþXS÷]Åñý‹…2÷Y¼¾œn­c P.y'öoZ†ßzÞzñêåËÔe_Ú'ö‹Wˆúa÷}Ôñ:hú§Ý¨·;ÏZ‰W/_ôlÙ@hþÓý*ÝÕ*JœØ†£¥2YO7$ÉGA@w´ÀÏ£egL*A¼V¹báƒYDÓ5í,W6ý¤¼A[︑W´ôWJÅBRÓó¹S¡ŒóDR\@F×’é\&o-´ô´–þ@÷ŠEÏu1Þ7áõè†ðÚ´@ûáý owÓ€]»öaí–x :狀rò|v÷Ö4 ôÁ•8è$§ª´M4mj¿©>o4nÀ·×åMj˜ÇN¬Àc üÓóXÓ_÷ýó(b5àb|9¹”$/YÁ¼Àd‚J¾Ò–ºƒV¿%í•úÓ·/¾>>þš5»ßØ•ãoÿp|tü‡ã¯þðÇoþô§¯ÿøí×üëõk?®27Z‰f€áßjO¨ _­~Y«&6)g~æŠi“¤H·çj~®t­Ñ¸ê’.e9Lä*¶ôæ¦OI2'ZOò÷9q9Ó#ÏÐ93òН;¿¾^ÀÏÞ¾ùøgŸn_ÿÞ÷Ç??A±A~„Ÿþä§3Ä fs¥,H³¢E.!ï²µŽÛ/çïV›…|Æ4­“W¶}øòå‹£Wâz½—/_UPx=8©Ä-1ÂT*#[¥ÙþàÇ­vûåË”¸€Z«Ö6³zC)¯²KÓ““]Îa_Ïè®—\D–øƒÀÍdCLu|n2 ÖÈ2Khæç 9Éèä°løzþF¸¦¬Þ¹XÔ´Œ–Ã(±¤° d K¾ žCË[ )Ìé)Ïå—)9á¡ €-ù#J^€7ì=ºCçÎþ¦hwúV¯À÷}=äžä>trà÷å)¯ì?V¾ÉaX³ù!¿|XÏ%~kÕàî¬Ë7\ÕCPvRŠé º œÂ €„õýuwl[¹gúUªVOˆïMÔ¯Ív½× ä€N¹Aöâüt=Œbä'&°ûÀAE ,#g– iZê¬\XWI×}Tþ—Íl&Ͳ‰ +#Ÿ×¼–Ã$p6“Ëæ´œ!@¾I—¤Cs©âÝ ø½"̵#vÇcùÿãû×v×c>€–]ýpã¾ÿë~ð4÷¡ø°j·nø`ÿ @íR ç{½ñ¼¹™žãðÆÎ:ås"É"ª2:®¢I RªKñÆ=úëØ¿/J  U&—ò //¼Š.€^éõ{­^¿}x<ðÄÒÿøÇ?}#ÿ«ß¼¾ùöÛ¯ÿ(vÿÍ¿}- ß¼ï0~=vF¾<Ø (‰ÿ_òñk˜A ÏÇÿXW)Àœö?g²Í]L¡äö B 0gbɲ£”7[.ëéh¹aŽ`µZĬ`Ÿ@¨»IJz]*—à Óº "4CåR‰ÿ„% @’‰Œ—ušV Ç×âµ@ J’Kè™\Þ*@U R4²9Ì#Ò©,xF¬b!«L3/1@ÎËŽRìÿcˆÅø»EPï´>dó’R~ÏþÝ]ß‚4ÄÔ:pÿ–×}à‰îSovjbÿkBÃöØ¿Bp'Ü—„áùõô\mwór¨F^¬ËLfh¹gä Û§¿TõÅ…+öÿA¡’¨\N¢ËñÅx\±r•“JªÒîv[íÃÁÀ‚³?}¸ãðßxüf£ï|ó­ÿÍ·ão¾ ¿…‰÷-E"<Ê;׫.ðü7Ó5GÂþ/·žÿÿi¶›6ñLz§,®ççŠÓu„ÖQ2SU¦ø"Ô³‰X+BÎ48€O½5ÒjRù¬c1…·A¡„¾ÑMXm®\ÿÍqov®ïû#·!ë¯üd˜óÇÎ~ì9®Kƒw×åì§øÀH"ïþî8€Wp‡öáÀîö»»×î3úïà±¹t6“ÉIÄÖ3õZJ<Àijg´*ùd*%¶¤Œx9>‚fÝGÜÀ"µƒ:šœŸ_ŒMû²iÈé–.héôÐ(œ=ó¼òF¾>_ ¿`rF:“`¡T, #®ϥ2¹BmB#Ÿ± y3­ùQ€ÑpÊæBNsWL<”x|ÿî®_35ìÚ‡­æ÷MÛo^?”)ØЇª^Õ™€V­Fj G…þ7M è°a¬ˆòV¤=€²¦µ"â`kLU ÍMËSfÇÌy¥†r¾ûú[y½yóöÓŸ}òî{^¿ïùÀïd¥BWv$WÀbJ1Иâ £ŠüE`:ªÍßá…7@£qñîÝk:ºÀÑÛÛ–œ÷¨÷ªRéöŽe˽31)€ ’Œ¿yÕNHðAóªRʧSϵªŠ.ºgÿøþ˜LPðHŸ›bÊâ*–)§zd™9@rys˜L Mƒ _ô\9â³¹¬)Ž''®=«ÍŠW0 …¼‘Lè¹<ʉˆ $(pÒyÙ6É,i-ÏÀ|årŠ=>Ç<Œö!œå´Px=-käAŒ'¾žç ¿¡“ày÷ߟhvŸËù‹°^ëݦk 7Vÿ}£‘JA(Oâc¸kâ¸ÀâN(×lé€ít„ØÕš…þÈÅ?ò›BHþ½pèñC¨ Gù ×//ñä|B[ì¿k÷zG˜E„(£ïŽüÿD,ÅÞ Ánî_ ª$hÑ}ª~ÆfµŽþ…<û•üÿJ’PÚ?ó~‚|UŸíÀ)YòpÒÏT/n±ÑßžQ‡k©Ù‡'b€,a€ÖÄšœˆ~àõ}χ †8NüÆ ¾8u˜ÇØÉ0ÞP… P´‡”ËÄþá$à 3Ø8€W'½ÞÀîô$9=ÈV:îu»8€wrJËyk ¥eeµ,éÜ®XYùR3AâÃ=ûgìŽÅö¡8/"èÝ xå_ 9ÛÍT*=´ Œy–áøý“I-%Ù¿‘7M]—÷.cxPÓ²FÒ’D"Ÿ“ÀÃ,bv¡\2µ¬YÈkºájîa¬-qÍü~];$´˜ž¨i!ÿzZè¶J®$ ™ê¨Ófßâ¬ÛÏ«âø·¼îôxšût«ÏíúmNÀÚ6'`Kr6TÎаm6S¿g-½¥ô3ª¤Ñ®£²£Òè&´uš£:ZþÁç#—'2ªå?ÀË&ÏïZD¹¬¤~T9ŸÄëáä%þÛ>î·íÞáÑQσ /Œ_ò ‰ß¼¹?p[÷G±ªŽò%Pô‰ÚƒÏ*¤iåé[â`ˆ÷]mÐïkêã®Ô80x?Q'ãkc_S%¾‡nàš}‚ð1Ï)øI. ìýàëûž¬¢œ$X±gspÌy©$ÀÀt„*€Í`m`5 ¹ø<À|õN½^¿ºvâº]1ÿ£ÞQç¸ûâ¤sû¯ÈwZb§E ª`©$Šþöñ›‹`FKåÚzF‡$Û•„÷íq$üäåå¥Y11Æ[,—ˆdúeËÒŸ9ºòAÚÑYë«T\-‘Jò£l¸©‘eb 0+Yˆ™ÔÊ%«I%rÉLÊ‹™´U,åŒBà¦4 •ëSðC#\މ‡!7gÁ.ë¹#w2¢F¦…¨H9J Ô‡8É1 4ª„3n“‚v›Ï%wo~ïê}õÞ"àݧû¼Ù½M Úhv¶JåJ$[ Z¹lÝJNÓ’†ñ@!ÁÊ%&V;øÜ ‡ò¥ì ¦uð  öwm£àsœÈo\­A/ ò ¼_ƒã#ÿèÖï}:ò…Çâë úÁÎÖýqþopŠWÕZ~p Cÿ\@ìvQÞç`.¸tE“mî#¶‹ûìk4ÐCk_VA5Ús3œ¸H½çä6W©kJ,þƒ¯ï{¾ÉÎW ýÑç‚•3£›0Whbý˜Ì•JðšÐgtæ<÷×*X# .àÕáñ‘=è¾èõN=ò®ÞÉA],àøÕSYÙjõ«f«ÑÁ°~> ºÿt:¡å“©À=ûÇõÅþ/áÆç—8÷Ë”ø òùÈéÒÜQ¶\ PÛ– ÀKœ\¥lôdX.êi#Ÿ.¹gÏ´Š¥§u+õãT:Ëy`ÉM2®¸É,ülò™LNOáüq°)*¹‘7Év ý3Á؇xùJg‘ ü(–κíZVïÿì{¿îï<Í}šÖ–Í»±åzÛ€šØxÁt:ðã’pP®æ6” ›~=lçð¤øã„$4B|ïóð(ðPÊÝuñ#¡·©½ ͺk…áZ÷\? O£ƒˆ$ µ&Ÿç•@4HÔÍ6æc2°mLÁOänò‚ È…Ód˜lǃ‰K±b#^fpN\.ÿƒüœ,ÙÏã’üœLÝ‹~}Ïó­c>ó™*hÌ)4@ÙrþRðoNÂcžüˆ!Jà p Ðå,–0ýÕÀk8I^)pòª3@âß=î¿êuíŽ=8"{Ê»´s@ø7›½AsÁ&¸½%)Ð+f&•Lêùb!ÀÝýãºQtyy>q.å@¸41Öy€balI_(˜bźV(”-ñ$’Qø™¬ã$µÑYzx&Y¾éjiÌ çJ•|JO¥ò…t>§¥% (X™ÔH%‹EI 52Š "Éjš> ƒÓSÏs&ÇÇç~ˆz^$ÉHà»#'ŒÃóÇE^‹A÷¡xo r´%tã‡òÙÛêÀ7C<ƒ~’^röaÞ&¤W¾Ê~ ðú°½Þ£Ã@ï}ŸÖÁ¶:pËÞvýëWìºín›µ>ÕF¯þYq[µ© \:˜ÕëÍsÿsI˜ˆ Ò€NäðûSÜYWÃàEˆ†T²ؾïzy>ç4Àq¾%Îä¡5B˜À$´L¯/ÿÇÔ ÞsêßDŽ\òÿQ$wJÞ-*m°®6x¦RlÒ±|0[SEiôœ#q‹Îü_ß÷|¨L9컀AóNëXÝcAά’nÿ€IÈaÌæ4âl¬èèÔÿºÇ½£žœÿ}ÔCEðpp‚‘„õ›ù¼ÏgrÙN£-ò°o•MSO'“i½X12º˜°™ËÉÇswÿÞE±üw9¹¼T$–$€YÉW±Ñ´4X¿¬\VKhÙ¢ákÙô™žÑµ¡ “ΦÄ1 N=Ëc8gf2â1 a,¹B¦¡£Pø •Í›a˜¯¥ôSŸáÇäh0¾<Ž#9í‡ÎxŒ úÁ©l:oäFÀ ³g!‹õ‚3à‘å„K°ào«×6†kSÔ§o ºÝ¾!v +.p޵×/ Žh² ó.ØÝGfžâ>­ZÿQyðýwÔWkÛîöä¤lrr® €3¬)%¡€Ø`(žÃ¦3:w!àëS¢M¢9Ös‚»ãÀ{Æ)w×ãqOGBŠÍÈÖDlyÙb¯6ܹ>ª×ó›FÐ8PϘHÔk©+Ô$‹AS ¡õä7…~þÊf ÛiLL’-PƒMÕPZg¤ãþ<˜C/ ˜`¢ED>‹Ëö Òh(T19ü~Ðõ}χ2> ˜Îq®SÞL:MYYDN°¢èßL!9HÑ0E.º±~y}ôú– è¾°_`&è£È?ŽOŠY ¬‚]J Ùíôób‚f>Mû‡|.•~¦çræ}ûGNóñ$º¸8sT±TªP+¬dâ ð{æÒbß¹S!/>%#ëžXð0 ® 9÷MËðry£RΦu=gVÊ™|>›5KPÊé™2Ù <@ÁÐPŠ0%0=ðÒšÆŒÞ £££ãKjQzž7ôY`sÎo4Ä6Dæ?Ây …ƒ¶á(ÜŽj×sü¶âîêõÔ G÷ûÔó’?1ÅoôĤAîó(À{ß§]³u»úï²Sl‰/º-ÀÕ.+çHÿå?ª$x••Æ0hºçþäs—R‰¯õÖ¿'¡Âîº"|ð&qŠ…0l¶ï Žö!NÄ__/W²Õô¤Á.`âŸåßþ¹Õh\á+ÕF ?ñ.¨…ÃNð÷Z…И³_(ÙmæÜË9•s97J`¶R]vÈqs°fͱÞ9gt¨¢³PøÚµ’×ýa×÷>ß’'Ô lœ+—`WòóUB‡fœ Bè³ &åBgðëÕMðú£Wʼ`Ÿô_~ôúåKØð‚‰ ™ÅP¸œJFÙÉg¡Ë—À=™ÒôL^²ù¼äã©l›ŸÐýF²m.Æ—c”M–íJVÉœø¡Y–¼Ý,gä>š‘ÍK†/‡=˜@6ÇX ±Q» d¼í^ÛÎñd·{†Øðc)Àܧßl=*¾­ÿŽÙêÇËýº,ÿWñÿÞ™VKpàÕ_¢ëa-8w>‡ÖšCŽ5Ìj»Á÷%UÜ]W¤r¼«6ëH·¹@*µ´£hs=$Þ¾{â²Æo¨Ô W”g¦ M£ÝéÈpý) -kßÀÍ‘÷B„‡Á˜%4¹—³¸+ÀoÄÒrePDâ(b.#uJ—¾Ä:³%H{àõ}ÏA ˜ žѾm>ßE,zÈ<ˆí€•Ê0b 1’q4(ÀGp*øjÍGv÷°G•;Îbf1´+g³JÁ¼;ù,•1$Og%@:ŸM'’Z ÙÎþõ¶18:_\嬆 €ZpEK,5½ Ÿ6/KçV!—ašFÁ2uy]×!Z¤4MC—дÌt:““u3“ÈéyÃ’ð#… C#›Ê¤SZ"¿ä:r}>ðågzÞi/Ï/'ýC =Æç†ž{£¿ˆÀ(á¿'‚3ô½Q€² ¤;@cË4:7l¾Ô|©Ý‰è}‡ˆ|sâ8%˶ ¶W”¿³¿ï}ºÆ£`Kÿ}>[]ëÇ#h7‰à®ÖžWWÅ,GÓsÂj£>–h;üÜ ]ÿ\’ú³SpEÐ ýž´Ê»ëŠöÙÝ­” “DaÀÊ?®ön!ó ‘×F Bóï@òþ@˜n²ë/æ ñвº$úU`³U£5( 0å( ©£Q‚d¸‰9±8˜¢Ÿ­6ú sôÓÕ@ÁŠ ÛëüÐëûŸOÝfI·ºç03 —²¸þ|—8ñW i„‘â)-“ )š1ÉV+F´þ$ó‡Të¡Ý;”®jŠ’Ê ³Ø»wÃ2 rö'´Œ!Gp®/¹cåR‰Üóöþ‘ðo|~1¹ 1,Z岨½Xr±$@<Äžž.˜VÆ(M¯àH*P*ƒ¢!‘A2íxy¹}0S•œ–ÉåJ–•KËûfÉ4$4(C+HËCDP®‘ïß`Y™:§žë‡N8‰.$«¹Œ&ƒ£óÍËê± IDATc9f< !U-Ù€{ ˆeÂ,ç ªá¶èõ6džÆ Ÿí ˜¡Û¶bõ©×E_îåZþkLè)îÓìõ¶ÀánpKÿ]6Ãò–~¼DÓj²»Ç™@í¹Xš 'QÜØ‡˜á^ø}…v×)ü0ŠÔÀf8Väín0Dá?ŠbDáHnqNè{5T'PkÕ™\5˜­Ô®š-uøÛ û‘ÿƒPžõ„ÕÌøó’l¦Ö•8’ë)%‚ãI<ñU~0WÏLña:kE*n~þÚ~èõ}ϧðþkq Ì銕~Ò/É'¼ù|S¨Š3@0%Ã8†tKÆ…è^G¯^‘^N¢£cð‰­bf1T$p­ïÈãe@"PÏäè6 rüW@õoeô4¦Ë¶öÏÄ?‡bsröÂ\N.$ 3PÑÄßz”òC-}iÙr)2½‘ëù[(¡ùÒ\Pˆ¸i •C²ýLF× Ù¢¥k9­`ÀP4Œl!/Î)›Á¤`:›×s¾¤A ˜³‘ÉÙÍcVáøäÅÅ1Þ<‰XåÄäH’¿u»CS VÝ+ñž^/%ùeµÐ¤Ãï¬G?$ì]œÍ,å=Æ ø÷Ù•ì:€[úÝÖïÛÝn§ÙlÖ«˜Äÿ«5 Sî±Zwšá:cê4‰@ÛîûJ+í®ÇÒO.å[œ1@Þò-‘’ubÅ0D›óú~MˆÎÅËä¢kÕW@üôûÏ©ì;#žJ¸@Ï0åK°Þá'ÚaL—™+ÏT~É®›ÔÌ‹Õ켪ÌÑþ–·„¿—Ó']WàÎúÞç£`{oIÝ@Š«ÁGNÜþ|I=>åüÐ܈ð1w@‰#åaÄÄIÀ«×q,x þ¤ŠÓ[ÌbKè“ÊRv–3AéW‘œ\²€l*[)æwöOè˜O qyq~~qi‰y›¨÷QÐþ’¡éQÆ,‡å²a:é‘îŠo°Ì°Ö+(•L==)ˆY/ä èd¬‚DfÉ(>ÂÔÛ$܆_;€ÞÀ„†7<%Žnù÷I|û˜¦”/faÊ2zë<Å}v@ï뿺Üѧ&g‹35˜¨W«´Êˆ7ŒjàvFC×s#(/;ßW\qw]‰?‚£ncò Œ(àFݯÍõ?pêrI‹`ß+LüÖ¡w N¡ó·ï¡ÞµŠi 懭Ïf1^&N…úsò;~I¶ ZÁ÷ÈÃÁ ~m†+ê,æóØ6au(Ê-7úÞO´ÑÀ»ë?º€¨ó¯ã?ôFlC¹ÓíÏqÕ€fœ>ž£HUE>ŽpUq/ h•à‡ÿ`F}RÌMÜa›OÁÕ-9zóÁæ»wPî’(@Oÿ8UÈË>ÚÙ? ºˆÂ`,N <ø‚(°)w›p%Ë”3<ÊdÍrÅ0 z. ¼rÅÌžC ÌwÈ„|ïÌÌ‹¯( Ð7€A’ ¿X¬˜Ô?Hi™|ÎÛ°$– ’.øžæ­¼¦%ïd*ò“b:!P “ìJA>`<‘|%¼ í ?¶£ðò8Œýh,aú=òà’˜Ç….,³ßëÙÒONmØê±’ôdÿ ÐSÜgÛØ;êÀÍÊþ;ÇLïêǃ›¼Å‚Êkµj3 Ì6Ü`Tû­ºÁ¹œJàù·åÁ¿›¼ò†¸·øÐ€ŠäCqYø÷0Ž á"yÓ‘¸æ .ΡQ?˜€†ZœSBœ‰ª×[qîÝÛ¥*ñ“wF.°)뀬…áð'ÿ” ¸’Â%õÛãªÛ>S•öåf"—‘3¹xÐFGÁn­.|Òuˆ8Ÿqý³[ë?ßl—ugœXbn) ýxÇ;Ÿ/}Ë’HBb×d%&c ’ ˆ½‹ù/—¯/Ž_“}tv/³« ¥òLÌ6‡‰ª,쟙g M¢lkÿŒÃ¨9† ´'AÀ@€‹ór¹l%“Ã@Ðû+Y†5iù|ºŸ9×M¦SËtË(ºA&¨x1ºVÊŠe#S0ÅyäôT­ÀZZZO¥t=—·J&ÈF‹¤ K_÷-ÓMË.œÈ9?¯¥D®s ®°ñäå«ð$‚dðXüBè_\^öÏGG²Y‡n8†jµ[iÝ£l’Ëö \ ˆ¼{èÔ)e_–õ~uýG"€§¸Ï¶hß‘'ç õß9J¾¾G?¾ßµ®%`v¨59ÒUŰP¢î ~­þ¹‡jæ©¡­„¿PÛ‡|€˜îçÁçhå©l~ï:‚6ÂÌG@\á»'n(¡EÜÙJ @GÆ ÕëW#l¬Z¢ÑÄÈ¿œþ8ï$áZ‚ø‹Âר’-löÌ”â.‹ hƒ0dàœí ykbrÔq¦~ Í)ß§úòsvßW±¦à®oDƒdý³Ï®×÷=_<°˜Òá¡¿» ‚0#u8œ»Ÿ/ú˜𢀨@<Ê‚©K ‘ /wó ¤‡ÇÀfš‰Å²˜'™D•rÞ.äJI-•EAikÿ¸NjÜé:¦Ñ` ž_ êå’Y0€FRÏz‰¢Ÿ¾ŸÖ„ ¯R4\=¨£èèšWÈWÊÙœ!ž£¨çó&B ˆƒè‰dBOgrô¨&ZØÀÐ=Ï0²®éçtP<Ì  ÇïzãÑðìDá“ðå8’Ã:Wræ£u&1êù_xJb°Òº'Pj^ÙÞF¶2žÝ>‚RRøÃ€Õ7bûGºOpŸmкÏ(ýwÌŽ!ž¼G?ÞÎôÚ­VýY“Á1Ý(†6°Û ˜|…€TËQ*tx=€P®ºŸC¹ôP½E¬Rˆy)ŽOâ ±_¢ˆŽß‚ñŒ³õ¨³Ž ˆoQ6r†y4‘x±má®s5cŒ€pÎéqxoödë7ªAÓÏäŸÍúÞ盪i!B}øSÍõÙŒ£„Ëû>_¡ÀŸH‰4 …¬,Œ$A%G—\­b›²ÀÇâj¾`Ms†¬Ý•ƒ¤µ½$Wk6{—˜º$`rQB³{ 4Ì´ˆ³#I Šùr©7óA:™öŠ•r86dž©1x–á¥Ó…\¹’-”Ê¥’‘Ï1 ,§}1ŸNkéd*+Lˆ‰Y–’-AÆ4‡n!ïP"H¢þñ؆#g"ŽAuwìt!¹€[(«d@cB@¨Jóž`Ð뢃/vÚSZïÇýü|Ð(‰ßd>qOpŸm°¥\ïÀ  ¤æÌ`Ó{õãíB bÔ®öü  îýÀ‰ªµÈ©6Àíû u¤ "‰îƒ pB¤‚‘bܳKW³>(ú×}¼8‚=¢ŠƒÇë1*n£Ùpµá uñä9'«MI”Òׂ&ƒmMÝ æ¦äú›«v RÈú}cW›zô¶¦<=Áȵ˜ÅY¶Ç`m+ê—È"Vdìæ ×o<€ã|v³¾ïùæd›*Ô#sv301õÏçKNqtà'§D±l‚_¦¦fJ§(…_<È,v½0[Ä"„Ð4ÐFC}wÿÔÇ^³“¹€~| p…rà— ±Ðrè¡(€Ÿ¹$Ãʹ<¤„S©”Øz¹hŒ’ãKf hš¤FJV9Ì0N$ƒX|ZK&SùT*í˜õÄM}D³àjZÖ*ºº;2{¢­„Ò³„ÿCotê:g šIæÿò…ÉžD‰Ð ü³Àúžã~~9•†}O `Чp‡‰rÀ»‡=Âwó,Ö!mï©ü½ow+>Å}¶€Ý¸#Ž—ó —ŸA9ö>ýxù!Û~«×™¾UsP«êu¯V êQÕõ= vˆÀ¼Îäb4º˜ £ ñ§ùЬWë>²ùpߺã`ÓA¼?ÂI?„VñAT¡¹% kâ-þ¤6Œ-ñ7ÔÅõ˜Kª­žÍ&˜‚ÄÀTÀuI¹%·*Zì>‰Š‚ž¡îJ)÷Åh°犊 Ðü©bàfdÁ¬QØ}5y;Ss:O¸~íÄ\¯ï{>¸(|†äüPð'¤ó!àཟᄅ*î¹>b©‚|ª PM|áŒ_h–²»êQó¢9>Ÿ„bü@DQÅ,@­ÀèܰPî7¬“r(ýf%+æªùš„óŠAÅ"Xÿƾ..I}©\ŠF Af¹TÉ%Sšö,M uᄸ"„GÌ,æ„,Ór´³Üé‘ÄÿbûÞ)@¿ggþÈ9s\GÂÔQt9ƾ¤Þ8HX"àÔ<7¬4Ûw@^©y¼DÂŽNšØ\E;%äÕ³)õ7@+¯÷Xð÷Ùríæ8é".‰£3v¿~|ßn‹@0Ñ ÐãŽZÿÿZ§ÙˆüºBœ9HÒ1àƒsÛéCPŸ¸p{îY÷FÁÐ÷‡§¡3©Gõêh„(ô ®ë….¯¯×8ú{Í߯¨qM]4Í&ˆ?z6JTJˆcopPý2ZE.M_ ¦d`W¨€¡û½&I„ÀDU „ÍèOyÄB1 <:n(Ç­Tq§%ðFó§Zÿôãõµp³¾çùæ Æ;‹ÑèõÏ–¤ Tºf÷¾QmµïúͰ=ÁœÌj‘+=ÈëvÏnµZ»û§YoJäݹ<ï^ p1)ºyVy…ü]’˜Ý×’Zƒ|EÐ…¦t ø'%#p¬’ìAÓKëf%µ@ñ@›`.f’ÚPKžæýdJ? ßP)å%,H?ÓÓ4ÝtΕ‡#ÊSÎÈsFþ©œÿ¾úˆEÓ±?‡>"Oyg€«zþÙØ÷*F÷n°›GäÞµcØ(0uèI ¨óÛï ä×CowMžâ>·@·Ñ¹íšj§Âï\MÆ¡ô€~<½7–nÎ)UOñi (Çq ‰~‘£ê°ø§’x“Ñ…3£‡îx´wù}=N«ãQ}ËÞðüfC|E ¨›P(”h$‚rùUÃ…2#Âÿè¿/A^Þ¬·̧¬j±p@(rÐ_2ÃU:ÝKæ ȃÀšHÚSÖ@ñZ(’`¤ÞØå‰»\*ÂÞ9EÅ\H§ÃzÂÓ¬úæÓ·«›à³›õ=ÏÇ©æÙÎHþ#?ÔÄ”è!QF÷~¾Lí§œ@~üúëa!:C„Sj´xªXÓç¯YÚjÜÙ?uì‡èC(4@©äaˆ 0†Y’c¿`ùÚY*€aƒ!¤”óôtz8ô%8ÈGaNó-3ÐÎ=™› iI ßWÎKQ2Ò˜ÔÒél&­åÊò &$ˆA ò,™ÐSVÑô=W;=œ< ÷Ð($õl4ôǘLeHÀ»X#âXGÁYÅ®7漣‚ŠuÚvÿØ=ãI<à`Ål Sqî9 BŽ?rÿÆäì« :Õ{Š€hÎÙýB¿€Ò=[u¬ß#dçY>h£ ×³{ýÇAžâ>·@µ3¸åÏëf x1rH0}@?Þ–üA¬«ÚhŒ›Uÿ€©Žüšç à5¦Û ¢zÔpÁ§æ×]ït$ ^ÍW|Ë{ÖIÎìµäP£ÚPÎu„ûbœúN ÌfMr¿Ñ¬»hû_5œºç+GШ*6Ðz¢m7 ²ØùÃù¿`ß|Îñ÷9ºX$Á„ÄÖ¨¶5)õ!ùçlPã á‚îq¶˜*¼ìŠ T±š)¶8°6Ë}’õO>ýôÓ·oß~|\¯ï{> ‘K˜e†~°åÙ|£rÿçË ë{®ßÂï•£FÐ%S ‹­Û½€ûöÏyÍ [јh€óŠ’#*æÜŒqîÍ RGÓ®˜°YaÏ4‡šœÐšVpßòri·h9‘1ö ƒàˆd¶ F>™A_Oj†¦»¥b¹\ÉjZúƒT:•r~J2 ×·,_‘ô$tÀø^Ï£JæéFÎP¢×@|TèŠ/ wˆjµçTìAгí€åú>«õìsz3½¬áI¾. ½2ñ<8€§¸ÏhVÛ·‘€µ«N½6xÉïÐãÌê!ýø~-ÀZí áÈ'ÔFpuŒß4|9­ÑÝ! l:A#8¨Œê’<ÕƒçU¶êj’ðÉU®³o=¹õ°Q?ÀÂÄÏÁoªpâÔO‚ªçPìOÎ~ýBÿÅFSþLÕš˜€APk۪ľ`ºÏI¸õl ¼ËJágçÄÿqÏS&|N™-ªqâ,\¢wãûÖ\ Æ€B‹üû€Þâ8Dþ Ú½µ*¬W`#ŠßwýÓ7o?þøg?û_Ÿ]¯ï{>qlœÜ@§³Û|+%núàç‹ogópÏõ;ÃBJCH)ñ.®÷l Jë÷îŸHi[C .@ñ_cÜtã<*•進Ð-Чxí¦ù ãËþfxý¼Ø½Ýî!¬ú‘à)îsíäW=¸å  e Å;üÂâAýø~¯Ý W0°@n¢U{Þh|‰y;$àè¿·(~Xk¤ïµèºL_Š¡k‘Ø·^ó-‘ç©¶ÑÁ¨á8˜J<ˆ$(hxÕFP;kÔaÿMˆsF¡QmV‘(Y–M]/ö¥‰ÿ=zZœrü'æÈçA5Ã…U’…Ÿ´Ú”Å ´nA¼,{uÔ RºÁ1Û‰:†ó|’õ·?ûø“O˟ݼn­?þ| é ô ˆZ˜ò× ú±‡¸÷ó…†vÁ¾ëw‡…€"FÔÂBÆrõðº ê÷ïŸF£yh<9ïHÔnZ¥r¨¾i –/K àf†gr—Í‚x„bÅ4<=éZ–å¦MYòiͪ.Žübdê¢ § –Øû³LÝ-ï{år>ŸÒÓÏÒIMClÕ$Â7L×±|ÿѼ Ÿ»æ¤":ÓB¾;#$ôˆQq\̯‹è֟׷€9PÄ}ŠÐgp³ùð܆M÷»€ðÚÃ~Œà)î³qòŒ‡ýûoÃ`ÜlÁQn®ÕCúñwÔžÁÔj1ó~X'i '„&õ†Suê~#¨ÖœFý7Z"¨^—éå‹ú«½ëµæ=’I-8­F,AÞC¦ä÷œù /q@–’–b-mÔ« 8ö™àøÁùÜ/Я81ç+•å®ÙkQüÙ¬œcŠãó¤Å›*_€àßÎHÓÁ³v¥úâ3ª÷ÀðPcœ")_<ÑúÛß½+ßz]¯ï{¾§T›“-ýÎ×±ú÷ô¡ÏI;²¥}×ï Í9^8W­²Ê=°Þëvqè?´M·îµÇ½šwfEÂp?c†@åt–ø_ŽùL©Xª@2È—ÿLEËO;–x Kˆd÷˜"¤¼pÙä×,ä¥é™”dÿ%?•p T{F±ð¤Ü1™KŸ,2qrúrzÃJ©˜Á‡¤ŒÀÈ_©CKÀç4 }X{^»íŽ91)榲uã1^l~bÕ†XµXoáqJ°§¸Oìä í›a ؋ڀ(€aü”Í(ÐQ? Øm£y#fêuë îÔªŽ~(ɉÞ$´Ãw¯”CßLç©2=( \àôÂÆ¾õ&È}ê5?$oMߎúF]Žÿ/%üoÖ›õ/‰ü‘=äÖ[Í/A^¬ô?kÐïØ+öù§¤Éà £wÕÉZp–ƒ€ Ô”ÐuÊÁWVÒÉÃ5e¡œ]$GŠ”‡" ’ ­;%xb¦^á”^=Õú»?~óöí§o>ýdg}ßóÁ‚ùãR+ƒÀâp8ùПï*¶ð½×ßš-”LᚣU®Ûv“„2íŸf«…¡-I|±\Ëg IãÁŒa ];Ó‚@/Šý—ÅZ2ëB) @G$È$ƒ<¸ÄŒI% Ÿ %ße€7ÄÌç%ä×ÎreÓOø–‘O¥³)1üTFOjºx’QAóâèÔ’XÁ‚³Ô‹Ü膗†Ñ×óœ3/ôFA}`^ÈW@¯ÓµkÖn9€O´àÊÈóÙÝ[Ó€ÐWâ KÔ@\ ›> ß';ŽÜ]õ½ƒº‹ö\Òó°å4Z ¹óõtž*Óã T«®±o˜þßÜ,­Õ”4a“×pAã€%p€·¤ú%l_¾R«wì9)1gjÈ€Î5ü˜ LÀËß„êzKŽ,Q' ?re’rbº…¼¡ÀÛÎùKšCncŠ„ iü”Ä"s¢ù–O¶.àgoß|ü³O·×÷>ˆ=¯ÇôÀzNÂÖfT¼ÿó³=ÀõG¯¿oXh¥šŠ Š?¸n äìŸVÔñÚ]Œë=@´ÊP‚\63Ìšù b ‚ “òJø[w‹Å¢“ÉžU*ŽÏM ƒ–x PÌr)]w’òáë .O§rrúƒh4£Ž{ff<£XrrV)ƒØÞ§Í2(€X Ü¡œR§2”£ÎýOîÈS„ íNÿàÃêµø¾¯‡À“܇@ü¾<åµ€ýÇêÀ+•‰²Ì’òSpÄ‹xpƒ/XŽèúA³•¨‡ÕjB>Ë&—¯Ô\®šÎc™Þ9شꮾÃ:¨«`ô†¸ùÕdó4ŸËáß„øwµVmÕ fòe•‘¨Ö¿<@¬§sΫ`´ÈVHmàðøuÆqxlfôþ%À8'Ù.XYŸsâvÊc—áȯ§”!Ÿ /€cÁ¡ÅGx@Øÿd럾{÷ñ»7¿Û^ß÷|øü8ôÇ¢þT1,/føœâ¡™¯÷^ï°Á•SJ¬?¸Þ낲åàñýÓK³Û r¦X½…x_¢ù¢™‡Ä`Þ1$©GÐðòº–ÔÅl­@×]³hxT| Xh–àŒ²eäÌr!§'Ó#MK•ËWdA §Á)îén&0G¹QÖ3Jþ0WF/rHQ{Kì–¨i@ÀQǧsîŒÆÃ˜ eW?Ü8€ïÿºß<Í}è>¬Ú­>ØcÀ™PHb¬U­üAýøC 8šøÈjš4J0´¦¦ñ¾!q{:O•éãxŸc {ן77ëMŠzÞ¬SaF_rY 4¿l47ñ?¦U °X+ëŽÅrg+TÉ%']òÔCœuk¶â°ë©¬A6Í’$ —ÍPAÑ,héôÐ(œ=ó¼r`³ä*IçszV˘©éúÙH7‹ùB±`¤ò!zWŸ,6aLYœQxzÃ2d{ƒ IDATÔ°k‚øßúú¡äÁëÍ–äÿNÀNUìÿ6-ø¶>ü޾ü~<éÔ I¥Ä‘_!Œ‹…™£·£sý ÁÎ#Ä…Ÿ3S‡iÔ®×ÉÛqçzœðø«bP´¥”K Ԡ¹o½!_Å00y€0ùÆ“~6_ÅKÒÊr”Ù0ó]©¡Ø7”<(g±[Ç ½˜ª²Â‚‰8í—§ø‚ƒ…ì—1™Úwýw\—ó`@twÖøÅ\ó”?”i‚ Wÿïý|{¯Wj(]líÍþyèþ=@曵ïº?\ç—Ðï­·{Ý^»+N C^¼#qò›ü›hH:YfP€\Þ&SCIt«`hEÏÍûަçbð,;Êh…¢aD>ä ½tt›Ìß AÿwÆÌ?"@ç¾ç´æßR’ày÷ߟhvŸËù‹°^ëÝ¡»¥¿¥/¿¥/Ÿ`»Õ¼Š3Š7h×€yNÕW1#/êsò·”ªÈïâ10Ù»×תäó•¯ÿ"+¯«Iñ{ |ß:’Ènf¾RÃoK–¶UÀ¼b°Ê"ÕL‘€²ó?551ö6UÙ›çþTåÃ3N ëºX«9Cå.É­‹™9õv{®ïu…-Õðº¶ü|@|ñ°õû?ß¾ë§äAÌÛûCíŸïOÐüûãñõ&õª5Ùn·×µAY×éõÅ÷ŽÍR™(À2t† «P2S©ô£ZÚ³ Д–ÌdRsÌôÐq S׳§™¬aJÒO¦º‘sêú·3T‘ùCŒ~¤h«}÷¼ ÑmVàºý¼ !ŽËë>A§¹O·úܮ߿¬msîêÃoéËoëÇÛ‡oT©½qU‹=t«Þ:À±,oÙl¦x„«õ‹tb­0Ø&BùðR¿ßºTãõÖoèá[ÊÏKÈ×¢‘C©z¥°î½‰¯î_ïvmœIkÎÿ‘9òkŠ'¢ŸM4¦ÁËP\Ú ÎSã²+Òê1â&±öz¾ºVê½ÎÉé]”QªCOîí¹þ}×1¸œÆ'9ˆ(c*âßõâýŸoÏõÔà¬0æ ·ö÷Ï#÷·»$•س?ö¬3Îl±€Ôiv[ ¬CÎѱ˜ÿÑ‹Cñ/Ð,WФ3$°,ý™£C$íèf…,å®–H¥Îòº®• 75:é9ÑùJœuÀ{2ÿ‘ ´0„+ð‚5ä6)h·ù\r÷æ÷®ÞWï->Ñ}ºÏ›ÝÛ¤ fgËìêÃoëËoéÇã—ÝúFÜïASuàê°Ã&øB›ò¹Ô$i“/`æ«Ñ /¿:¤õ”È­•â X0 ª|Þ‰‚ð+gº!u1B–¾ìï[owå1ɘÆÉtò]ò““í·°7H•~6°µJsª0µ´°%á+B#Ï.–ÊB˜­7Å;J qà/Ó>~ýû®+.pÂì^6Ça¢Þûùö\Ï!«°–8ÝÝ?ÝŸÕ¾ý±o½©ôªëUì6|Ú9•zÝÁ``šâŽOpî—匯T =³Oiî( ÔðÄè#ºi ¼ôÈÉVÊÖHO:ùLÖ™@þk„¡s €ÃD£Ðõ©L/ÙÀ©EÃPIWA+ &î+p«ÃêýŸ}ï×ý]€§¹Oó ÓÚ’ïìD«m}x Çúò;úñ¨¶ˆÅÿ§o£ÑÏ,'p»Ûîâ#êʧqñvã÷ÌÑ[Ê<å…¯ãc”¶Õ®Çm@xø¶²Z\NŒH½ž¸Â÷vÚžð?õ:êú.M~ߺÝé÷DË’—£³jk«;zýh ’•óúÔ›²µ®x‘–ª·TöÂá¹Ì@òpœQŸ“ÚÝìÁcléõ…ö\ÿ¾ëd&†Îß³ÄìÜÏ75¸e,#ò>Ï·çzµ?f¬,wöÇ‚CßßF°gì[o·ël4 –MÍ1ùÃ#äÁ J%£$f/}åô°Äþ-Ï–Jùl:§çKÅ‚î$ӧ錛.Šð“𿏥§Æ#”ÿÆaj7ܱ?–ðŸ^ rd!{°ËÁÁ`›¼a?!€ç©îÓ<°[ ·£¼£¿­/¿£ß³í–ÖjëÊÑî%Ç*‰A³­‰?nÿ¾ÕÍpæCüD›äv/~ásìtÚñìóWððl^vÔÇ‹h@üȦ¡Ñ‘—˜·ìž>ïz0N„ýë=4´—j“ié,6Œÿ͘¨×z—Ôç¬UC(½CêfR‡kÍÙ}”Ps#PCñs¢] @™>¸œÇ{®ßuõù°cOY@øK­Nðéû>߾뱈W'ÿlwÿw÷ÇüñûÛö¾ý±o]¶L·M8bõNý ÙnÉxq$àH‚€b¥X®@M d…fºÅBFÓÒ™TΰrY-¡e‹†¯eÓgÀ Ýb ÊAšžMG¡ØûÈ÷ÝÜÓ ¹ó5” N5 ÆãBd°ž€Úb[¸¶ôÀ–<´•¦wAŒÕÎ÷ìAap\èqœ¤>dòéÙ?Á}Zµmuà]yðåŽ>ü޾üýxù ÄØ¯‘è›Í· hÍ´ñ)ÊS¡«¾TÅ)’N¬Ö«5®ív3ª€Ç©8y:®¸‚.ÉçäüÆG¾"Bä‹U<aï__æo¡Øè– áž9uAÿL•ýÜø LsLŸyîp{ÅœMÄŽü1Ÿx9>~ô•"Ý\PUWÍÞëßs"¾óš)Cvü\¬^ „yïçÛs=à†üݱqgì¹ÿÞý±o{]¾x ”Tk8özfÿÅÑàèÈ<P*U¬"çqF©TÊö›Ëè…J!¯§“Y÷|-5L‚+(Wª˜–áåtG÷0à3r¨S Ú R’·¨SŸrè8nJ<0 ãQí vMæÙ/€¿[Ü}Ðö÷ ùWãGzb°æ®î£¤ ï}ŸvÍ~ÜìêÃßÖ—¿G?>¶sF‹k²È+Þ'öÙñMÈzÝC[‰PqÏ€„­+U¹’Xì½Gå¾>l÷Pyxü%8uíWj4mNH‰0`ÀªK÷ø:~ p]Ý´ ˜³ùC±ÉIÜ, Wœ X(.źTŒ98í0=ÀAim ± ð " ì ¨×©ÒØ¹Í¨ôÈõï¿>›/ÉË»˜’Ü“‰ÌL!šhx¿çÛw=›Žlý­çßÙ?{î¿oì[—wænG?¡Ö¨vk¬ûöåð?”4À| U,Y%sâ‡fÜb匜ðš‘ÍCo,¥¥%ì/y†žÖ’à 2+å¢é§¼Ñ™ORð0‚S=Ø¢Ér7Aè@¡z UºÈ—0Êæ¾üµ46dž¶:ÛxtúyÌï÷º9ùwc@Û…¨‡NßG•žâ>íÆvй«¼¥¿¥/~<ûƒ NOñIÆ‚¤±AsZv¾…~{¦È À2±VÔ“ ÜP¾dÿÌú}4ëÕ(ÛzMb\E'¹Púÿn¥To€[eAoÏ:ÌY Í+Ø '[¤¼\ÇÊØÆ÷OïyÕdn±À±¥à. q«Ž!Yª%·ÜtÌìÎö_ÿ~ë þ_°”G¬FÔßÌ Þÿù¿=3ƒ%ôDîÙ{î¿oì[çû÷mÉÑPª?—ÝiŠ¿8:>~qÈ( ,Æ/ÿÔ¢aQÎ{ßÑW3B«R4ózbA%S†p(˜A:u掆ÁpôƒÑd„®?ñ¡\/G¾7Ž cíEax1žwîºá–hmHås¨¬7oæìB/ßÏ÷ Iâ P{ØJϯ'‘|÷±à îÓGçíyp:Ý[úð[úò÷èÇS‹š¢Zè÷µ¾T©â”ª"œ0ÁG­&ÞÖ±Pc[ì_ÊV¬‘Æ+]Ú§YAŸ‘h Ml5ÃG4ÉŠ¼Wr„S¹øþÇ×ù8+•Ã2úäìÜL\“gÜ’<€÷½ÿB!agJ)}‘ …³˜Ž9+nü)ô€ãw‡Îû®ßužÁkÖÝ8Ú&Ϲ¢ufð¾Ï·ïúyìEcQÅ;ûcßý÷í}ëñû#ím×¢*q@+Ó³ ˜Pÿ„ €ZÖÿýYÿhýWËÌç´tO›—¥s«K‹ƒ0M£`™zVKëº^©Xf’h€ð0‚þK¬:¡b¡ß½ GQäMô]orî…—ç[ ÑÙPyÉ×oÄèb¬¶}${¿À¢u>zÝ>™=£{ÿût;GÀ®>ü–¾ü=úñ 郸*=q L\ôB¤·Î˜t¦ ø+rŒ¨Ðš;Ï.TÞp6OãÆ‡ÜðÆKBÉŠà}%¤˜ÑçÌo2ÝÇ×ÜSöyåøÿTÅsÂ}4?–(\)ƒb‘$Zñ¹DL¼Ð\­?'—0NÀ“““=×ï»ÿ¾õ…jÓ’º  þ÷ŒÒ>æíy¾ÕÞûï»~­Uɺ¼gì»ÿ¾ý±w}óþÈ8 Š†AÛî‹8<6%8¶Êe Ö¯ÿþÿô‹üÅ-šžž.˜VÆ(M¯àä¡ R ІDÉ´ãåÅH çµÐ½-.À 'þâN0r¨jáøž?B/ÀûÁáÑ–èõ”áv©àI ?¥ëiCÉOLUÂuc¥ðÙ;‚¾Ç-ÃåëÆ<Å}š½Þ–8Üu;úðÛúòäÜÖ'c%%gªñïàPP RÐéføž€ìS89‰ÉLM"ðŠGÇ5iú©[‡ä~J ߸1M\ïj3÷öè:I1ä·^²ŸÅf4ùAÈlñèûg¯ÀlC$4Ã¶Ž‘qwY‡ÿºä<²¦býâ°åÏÇ®ßwÿ}ëó¸.OH‡5|i°ä~øùð¡ÎßãçS×3`–¿^Ü·?öÜßþØ·~ëýeïw.zƒÃ#‰ÿ1dBTÄ(šæ¯ÿáïñâJù¡–¾´Œl¹™ÞÈu¬r±R(‹–¯¥4"nZÓ} ïcF°Ð÷\4šü~"@Žä÷>em|è×Dãh„G[Pà[œ€(ÎÉÁLå^±Ýìܾ½Pz¾v¬ï Öÿ[†¿n8Ÿà>»òàƒ°«?e¿7Ö—¿G?~¡4µ02®H·˜V®ÉX?eâ4ÛDóYL6³d â‘éLÍå­TåMMßÜÞ&% l›¹ÞLAÍ—s5•>#Ýľõt­FY§4Œ¥jNìyÿÅ\6‹xœ`ÍÝË[SŒ8¦?JïBAŽ×keý­VKù€¯ßwÿ½ï¯ ŠÔRþlµTÊkudo?ßo¿Rχà‹ÃB;÷ÿíWÿ|»×Ï”ÇGY@‘ùo¿ÚÝ?ÿ¾ý±wÿÜ~ÿ>ZÝ®Ýí´ì‡à,1oõ¾Ê¯ÿþÄþ ùwM2f9„Þ°“é.D†ÌP|Dè”J¦žy4ÉyÁDÎ}÷j‰C¡ª ˆõOBÏ ‡aèù1sïuª7w¨:õñ$wWj^<´{`òëíÜïÚpo=OwŸ]ÐÛuë}ø%cºµÒ—¿«Oýy~yX©21fêfì½ÍX &N”"u¾ÿ‚Cç›a1Ðu͇,E½¥’’[’íB¹ø äé ôŒÍtÏ:= T'X'C\Ϧ²~¾Þ÷þK–W„»#ÿ\Æ¢ ‚ÂÈ&@šÃ­â¶õ·ZÍ8xàú}÷|‰Ö÷Õì³ÏÐû(öÙ"žÁŸ-§wžï·_}E{œ/•Ñîý¹þÈÏ·{=ñ F0ÂÖoï}Ï¿wìYßy°eA5«c¾8<p؇(°)Y~©\–à,ЈgSQ&k–+†YÐsédà•+föܰ,ÃwhÈ}ï, f !ZåûîÕÕïA(pDãÐCEÈw7¼¨Ø÷;€MãŽm:ˆz zùAAûmzz’ßçä_”á¶¶@ýOwŸm`ßq»úðè¼,(@©Œ»úñrè#Ø›ÇXtœ·$ ˆÔBqÃ1“ L}†è¾à\ŽÞò˜bäH.›%q§`’¡F1?pR\B+ƒV‹Ðv¦*ýÌA÷¬/Éž { Ôa„m *8¨»çýIŒèg¡Úëü>V°¦1su»öötÛú·|À=×ï»ÿþ÷Ç€ß/4CåßÊŸá/V;Hhûù~ûÕo¿Âœ æðëß½?Öûùv¯Ÿ±,@û— ×oïÍýÅ1È뾟ïñý±o}çýß¼½8e~W¢Êå²”L ýú׿þG£dÖh¤åóñ¥œë&Ó)§X‘Cß2Šn *žD †®U4hÐ’Èu#Wÿáÿü»«ßìMü‘'‡~ºa8'XEn0qîuÄèÊ|å•©w^NìBLæ?@Mß6·o\¼km½žî>{Àj[^A/–ë˜þŽ~¼¢_R’~­Èö8¾VüÒþ†Ì(:ƒhÂs³)j>¢óÔö¥üûîÓy fQš}äí…uâ$"ŒoÉÔsÁ ¸ùþujYQI“B™e%¶à`V­ÖÖqÎU'|ªR¡™’áC¨ ¨*î ê\Ýoý±¸çú}÷ß³Žƒ™ ÍGNˆ|F½?VhvŸO T,˜¹C¦Ýû«õ‡¾ÝëUKo¥DÖñõ·öG||Y^_}¥žÿ«ëûïÛûÖwÞÿÍÛ·oOŽ^ÊK‚c1ŒãÃËJYŽôŠU)–K¿îÿý?Ë–aI=gèòõb>çûi=H˜ñ*EÃÕƒJÑ1ŠŽ®y–ÄŒ`Á©ã]9WÁÏÿU€£ð€¡9n(a€L\ÉBߟx•Ãû"&èÔí4b*äCQ:Ÿ1•wнÝw_OuŸmp¸í  ´«ÏÏ~ÎZнúñÄö­T”ÆØ5.±1¼®ÕÙŠ¬ÐÑIÈ30b‡ N9‡Çv ‹3¦3Îè!øœ©§f”ö®¯IE?³H°Ù¯qœ»zÏûC"kN1,t°¨’…ø“8£¸ÝŽ$IÁä·4?yÈú[ÍæÉÝë÷Ýß:Ò-8«X3L\ÀgP^¨ê,«êùÔ,¯ßþ·ß~5g…€ºg›ûo­ßóó=p½ô›ÜZ¿ÙñýÅü7yÁÈóõÕÍý÷í½û'~ÖCßÀ¼yaÃdröññ‹Áq €b©\,þºÿË_üâW%I÷³£T!_Ì—K…¼™ÒÉ´W¬”ñ96L-ˆÁ³ /ö f ¼àê_Oÿåç?ÿïzÏ•?G.š ƒZý0¨ôïqSöà:×ûJʻЗÿèõÙ׋üv×èn0üµÛ¯§»Ï¶èo;€.¤ÁvõáÕH(ù`î×9k1ñt*çÁXÿ0 #ËØÖÖÜÓxÎQbV龑ÍQ㊌·srέâÞ"*AòÅ©ó£Ë8ó‡G×Q™B­ˆ°Œ«æ9N¼u|Ô?þþĭͧpzS…v](äî‡9wéT!Üä'—Hÿä^ë—=yrÏõûî¿o}…bçêF8X^J;Œ3ÂëÍóÅG0^b‡_ùÔÓæþ;ëw~¾¯'–b±ÞY¿Ùêþ±  X|µ}ÿ½ûcß:ÞŸ;`¹¦x÷Bûe(‰Éábùׇ¿üÅÏÿî—¿’D?3r5H†•sy²B©T®\)±Qr|É äMÓÓy'fsƒàüKð7?ÿÑϯFND¦@Å$ÅIà{Ã`x ¯ÎeDéÌÞcu/#Vö£Û ËÿÊp;µ[s½µ§»ÏãEÀneWž1T]VÔºO?~>ü”dÒü<ÁVÄsƒ¾~¦8dˆÌÃhNáE¬h9‹±E3ÞçÈzU°<ÓkìGשm?å­ùøJ±’¹ÍFì‘÷NcCÐá@l­jî³¥‚ÆAÒ =Œ„NN”زþ£#ñÐÝ“»×ï»ÿwyä×`# ޾ùL!øñ|רÌð+bøW³ëŸow}÷ç{èzEø1¿³¾Ùñýo-3I¸õüï½ëñû¾!ÀÛO^í ‚öqÅ,@¤\üõàïÿÏ¿û«ÿòÅ/eY'äPú7ÌJÖðÍ×$˜˜—P *- ƾžI»1#˜? þÇïþæï¾øÝ¿ ®®^¾‘ ¯QU€‰ãúþy¥{Pòô¾)¶)É(ÐnœË÷Ôü OvZul¸[üÞµ§»Ï¶èn;€z·²«O°ÏšSgRl˜xïÇ×U¥n~çËÙ†ô#&§Ø÷þŒ;QSsK5ŒB¶Î¹b@ÿüø©bp¤âZ¯ß‹ÀÝë÷Ýï:Éå'o—ëòg·\€óY™ˆùåæùvOèß®øisÿ»ëÛ?ßC×]5›Ý]ßìëû£<°q_]?ÿ¾ý±o}óþD=a*pt,F`÷ŽK%ßCÉÿ~õË_ü—¿úËÿü—õ׿ü•¯¥‚Ši’!¤”óôtz8ô‹–™ÂœæCô<Г™Œ;ò&’T^V~÷Å¿ûÑï¾øÑïþåêwÇ/_V"0‚ù‘’¯ƒ0 F#oxo ´ûb«8¶û´ãXÙK‰{1oïðÌýáv>Œ_[)À{ßçÑ °£¿f…€ù}úñûôáWJ¨ Ð:t gT瘭cúú%ì"Ȉrܘ>›Q×j!s?\;‹kÑ Ök~ûžõ˜…#Kزx"rÕ­ÈZµÞûþ@ ¢q¸dáoóð¯P’=iFèia~ãŽNð±ñ½x-àîõûî¿o¿ÝÅ'ŸüìíjQ¦ ˆke´Ú«éæùvrôßÎ)ƒŽSXÝw}÷ç{èzþ¾§ÓÕõxàþŸ|úæãOUà·7 ¾ÿžý±w=~5ýù.àx Q—=8<2+¦†F™öÿ·?ÿù_ÿÕßüí’Œ’š®S7¸"9€‘àN±\‰ÆfÚó,#{~.™A¹á$Uìnûø÷¿ûÑ~ô7¿ûÑÿý »Ý²+`;sÇž8î¤Á'£ ¸7‹¥ž/ŒöǶMsÝØî@q|¨¬¾go ×~.¯åûÚ<Á}ªâvõáÙD_Pìñ>ýø}úð+¥w§Xæ1¼–˜)Y$f¬qapPÍÝ"Þ¥” bÃ9ŽbÖ¼fŠNrÁ„“u=ä€sES½]é~bò‡rÀØ‘Œ¦ aœï}Ô4ñ¸¨PA Sƒ¼n­˜31õ¤Ü;%ÿ‹¹7kn+Í®ßi»ì[JÍJ )P"Fbà$*K·R$$Šew?8˜ÎÁ”]®!35¤r¨üŽŽûâwtDßÇuݸa·]áèìˆîÛý#Îüz¯µ@$ñQVºÒD‘Ü8ÎÞß× c€÷·iÖ·Ç»ÿ“'0§7­o’£¼¹ßØ?hî7}š­܈}ÊiB=¿™,ý7ìÚEùo²þ¬üôû;çxÍü„p¦å“ûCÖ?¨Ô[û­ÃÐÿf\ øvjýÅ÷‡Yž¼>áÉôþ„Ø„R¬o®?Jݽuçöݯ7?ñâ—Ÿ~ü?ÿÅŸýìöéË×—¬k×nÜ ƒÚW»ÝÎÕ«ï¦Rwîön^;èàöÐÆ˜ÏGÝ®“ÚÚÚJýË/—–`þßÿçÓÕbe#D0¸v_lD¿m ;íN?µzvzZFó®ÒyWÊ,ém`†¿Â~”ñÂOw½ z›[Ÿ„ßÁ:³`uÎZ¯Ê IDATäR§øáÙà+öäüñ&~xöo€›ù6Ü@Ü¡“¨-ÇÈ/„ÚÄþ\øàC'/< ùØC´ŸŠÕIvk“Yßt<³­ýƒýùôý5¸~³ÕúfâLÖ7Ýf9G‹´#j|ŠØD3(ÉæÝ»·ï½ûõÖç¯^<ÿô“O~õ«Ÿýù¯~ñó¿øä“×W¯Ý¾y Äbwo¿‡B€Ý½m_pÐçŠå¤î9ƒþÐ9î÷Ÿ ú½ãT*õ¯ÿòÏ¿õÿ¿ñÏ¿üåÆã÷îl`P°í·ƒîGâ/Ì€âÄÝМýš–î+kï&U{¢Y±†÷®<ãÎÆÄuO¯ToÓ'ÀÛ¯c4süðô¡Å¬‚‡Æ;ƒ?ÞÄïëhN >Ü·Š»Í#µIŒ»&yyµï ñ›úî<,ï¢ q<44sZøóäa’c&Ei©‡D²Ä©ã=ÑCYüú°&´ñ“~gö3Õ|Xo&d}Í2Äá¼x,à’€ÉñÕÐNç3׿¸\T¢)Ú'>vë@þo ð¾¢…%VxpÐYë`_”zß(Àƒ;žæãQ?7¢qÐlR>ýú†ã¡˜b€æ~}ÿPŸ¾?·9^ÿ$Løv²¾éþ0ÉÕ0 „Zéøþl4Ð'ê°µuÿ½;w ÿ¯^}üéÇ¿úÕ/>ùÙÏ~þñ_üÅÇÏ__¾zÿλïÝOÝKݹݻöŽu÷î]ëÊû÷ï:·®\½›£`÷ƒg£ÔãÇ×?þ×ý—_ÊãÿúÏ¿H­U§ ÖíÈߎcw»ÒX©ÂY!ZôÖ°moÞæ_e·k o¥TAa[úÆFù${½[ôöÄ|ë̀Œ(ÃÌñÃ+kÉã sJnà‡÷©Th f«ÇíO<øñkwfòày{ü‡œ>ê^Ì4úêJjß8>e_›=Âx˜åÈLÐßÇrØðBrH=6½>F‹}Ƹp ¨ürKƒ©¯):#»cò@ä±}¶Ð2àv$òP½cÅ×;½~øFr$ÆöEýe“nîƒ:P¢€ûiŽVÞÚlÁˆŒ¡¤ˆÃ™¿ØŸ«ëïËáû¢âò¤&äÓ뛎Çl® u9ZäzüÔý!ë7’õ'A‚øG}eBߟ²%®”‹[ë©ûb¾øüå«—/~õé§Ï?ùů^üâq>}õÚ¹þÞ=ðÞ½c_»våÊí{wí+Ý»b1î^»víΛ—¯FCç#$*+ÅÏÅøçùüþý{ÿ,û¬üjØîvÛ0v·ß‹¨x©üYGxµf\¿2yþ õ;í¢³+ã›*ß­ ëSeÀïbYŸ6¹âÃÕÔ)~x¤Åôó?% üð¾ö¯A-CêØ(®,Âq{,ð'Nã=MÑ'˜tÄér™èÅíŽê‚¦…b’P&¹› )ñ~ÑQ_¶ ÇLYédá볯9àœ°ø™x A“:ˆÌ @K¾=܇A8hŠöom%5€÷g «k•ííõ ­@”Pž±>£ò‹ÊQzˆÅµöái7[âÜon‹Ï4ËÑäüêÜáëax2…‰õ[òFZPaÑT‘7g×_|<ªŸâß·ysúþõ›ÉúßL=NÖ7Ý&y¨¹ñ´N5¾?×Ë)”ÖR÷îŠðÙ«çÏ?yùÉó—/~ùé§òßÇòòµã\»'ú_,Àí«ïܰÀpňsýçîÍ›7G½ápˆÀã‚Dý›[•JêÞÆãTêþÆZ¹PxûðØ ‹‰C¤œë´S»p\€­À ôF%£•¯HŸ|+ú®ÂØôú!›×ÃH«M@¸†q£â€®n·Üùýìý[›ëSU5¯& ´ZöííÊÚvlo³þ¬õYS%‡I® :%‡¢ÄÍû÷ëhÝfß «ŒŽÉùÉÄEÿæ@ù@"r¤p}Q𦨰ìòuÈg×7/×Û?Ø?lñ ‡È´fîùõ[ùó7Ü&y€;N¡]ã©ûs´C…U±ï}ýÕ¯^¾|ùüÅ󯞿üôå‹O^¼Ÿ^ƒô.Ò€÷ï8Îõ˽÷ðÿ5ëÞ½{Ýë7n÷î´-ktÜÿhepÿl6u,¿(­>¶‡@ îÝîHb€~Çê¦ò«§ À›>ÎkúNÖÉæ‹'`uÆÈçÒÅÔ¬‹áÁ…h‰ К½ÆçÞúÆûà WüV×W¾AÂQñõ×6¡«_?úòóÏ>{õÙ«O?{.‘À«O_>õâÅóÏ^}qûÞ½w‘¼Ý»uíê;×nß{ï®síšuçÞíˆÿÀüÕ³»»?¿o<–S,¤†ÃûºÃá G>€hC:NÏ…›7oþ˜oàÑY€ïf›.fò+ùÕT4Ç3j‹€7ä‡g1=f‡\>XW+;Ú©Ç8ƒx}Áÿ>½ód³V:OwŒÄãå±PS@ÄêdA¤ž¯0È\À=ÿõr±´ÈÈ-¹ZªTà€ãb}½¸ŠñÀs×]¥e5È‘ ÿÁ:áìΈÀÆÚ1‚ó&å–¯˜ý~¬MB\_|ø†ìÐÈkίo8ÍÓ±ÄÿûöååçóOÖoήoº?Lr¾¾¯™*ŽE%¯¿.@`á™Ü×[_}õÅb^¾üì\Ï^½x.__Ñ»óÞ]щêoY×®\½ý^êέÝ[ ÿFŽO4ZvöÞÀ<Í·†ü¤Ä ­^ßâvü`6ºÄT8©µl¡4cþ-(îôã;\çĬ– Ùéa ÜCœp4Ïï%E>ŸÛ ËKoÄx‰÷ÊÍµì ˜as©µQ¬€ž$t¤N=FŒZ:ó&ý´;lÃpŸQÎÞäH-“ö.kZ÷…Yôú2À÷ˆ’¨5Áü "ñk“½sÐæk뛥bIt|kÚ ‡<Öþõ±öËý)æb;^´~ÈÊÂb9;ƒà·àý¶E±EºJæ‘N4Òã²ìí#®f‚ó9µ>û‰æ×7ÏÏP‹žØ‘z}þþ™¬ß:¨kÖdþü ÷‡ñþ9y}×sÝñ믑{˜ûɯ¿þê«/¿xõúóÏåË«×_|ùÙË/¾|µÝûè£á³>ºwç–}íê5çÚ»3äß–Ówº¢ÿv» LQq{`õ]{èôFŽƒÄØ=2€<Üî:©ÍbzÆøöÈæË™•‡4r¦›' ³œ.³)W›é<ŽÏ»¼‡ƒuÜéêÆB|=%†X,W+¡ea¤¦ç×'Œ·"FEºu¡#ŸÈþÜ‹‘áQÍVµöµ]ñ¥. 7­&=¼óüõ.çØ#”#LúNP‹ÒT`³¾½.Ú_*–×EûËEÑòõÍMXµµ±XƒçŸìùSÚ/>ÁÖv¤3ˆ°›¼JÊJâo—Ng‚y™è¥ôœ]RþÄãw4>ÿýVýŒóŸ¬/§|j}vLÃIo¿y¨.IÜôé×órêõMòñýÑB„ nDãð;¾¦ÎßG·Çøü+Åb.·7×}’zòå“í}þå“_ÿú³'Ÿ¿~òäsViîñÀü…ª[Žeõ-låøjp«oåý¶mu:^¿×_°Zß\•`ú?´H<€|zuóÄH/¯”‹ Ñk^zLaÁ’^?ÔÏÓeé#ZjóMr~€, ÅI*kn}ÀKá ì9ŠÇM_À~qCêÎíqo'Šñüø9ÇF¹iýsùë=Pø }A z²ýQVѰßÜÞ\Wí_ZeM´Z¾–XmÝûUëgµ}k³¼%§—٥ĒDÌ.7öR‡Ê§Å áöˆbBœ,—u—©óo4Nÿôú‡ÍÓë -ÔáÍæ~D¼vé‚ ·S¯ß¨Ÿ~}“<¹?P)A¾´Þª·÷Ïôù®x|þÕ’VxE ÀÌãý'O~ôhýѶlëâ[]îà]€ü‹B÷­ÀÀí¾Ýo÷-Ë$wúƒN¿7ìY½¡˜‡®•*onÈ‹Ž @ò(•òßócê ’ »’ÛØœŒCÿWa¦xH)Á!,_qq;…ôáu&BмQOZ£dven}Ô ž³¡([u|°Ä¨ŠÙ $Š:MÌoÜǨOäæõ£óøë#MQ…ÊÄ:ë€Ø¿±¯™ÿúááöæZ¹¼¾µí—½¿œh?þ§U8Gû×8¬­nÓE!¬ªº3ÔνGZÓˆOr8ÏF)ÒMŸoxúü›§Ïzýƒ³ÖGsq¨™“ƒHSv>ÓŠ§é×?8ëõ òïûgúüãéó¯Á’Ï­ä»TúÇ›?z¼õþãíí­ííG¯åÓØŠÂÊöݳÚòxPüæ£=H`Ývú&y¯Ýà ;C}`^[Ï>̪(%Ÿý¬ô=?¦Î@ €œãúÚú_ÈÂD:0Ë"nÀ™zv•».ñ6Hù“gsЃœ•B¶êaÞžÖyf}¶Ïß%$ÿ³s ç´„ÅY~ìØí}” ÷W3Q®k–Ö_Ä_¯mJÆI-°ý×Å£­ï# XßÞ=€·ÖA‘šh?óÓÚ_žÕþ8Ü–çm‡¬ãÇDR ÙËžT.ÉY#SÆÉIÓqšÉô#­Ç->ãúŠ¥®õR~Xž> Û-Ó&†×7Èÿ½ïŸsÏ¿R*dÁ^Èä¨ÿ[›ëëeU*È×äV+Ûv™~«×KÐýЈ’[ΰÛo#œ8 ï›äNW<{0„ 6b@ÊzF, @|éç?ýË~ÑÿŸÿ¼”×h23ë•@åt9Ÿ¦‰ºˆB˜âOøLærâ W¶SvÞùdxQ¬Ý…rnÈÜ]Ù½ϯ“ˆxýHº£Ã3Vˆ3|ÄÀóØ0ÄÜîjëW@8£Ü´þ"þzîdè@}[‘«ÈAâÖëÐ1²¡`7_æÇ‰Ø8/î‡öƸ4^R`áNáî¦ã_R›{ä.gÚqýؤ¹øüë+H/:è#æöª6ÎᆠMÊ¢×7Èÿ½îŸIöyç_)#ø«\f8àñ£­õЉÊHE¹Z(–·?§¾#jm-{è g9Ž5Ä€ïÀ€õK „Aîôí!;Žâ-¨TÉ,gaP|Ï—>~þOß|òg¥7®ãgÒŸ}òÍ?=ÿ¸”çO0™åL¥t‚ý_I @H&Wùðrçj–%]ÐA!*%L¤‡Z¹çzF¹6iŠ>fÕff}/àOLüXÞx.»¡{úéãfbLÏt~’ò÷YkÌrÓú‹øëá*xì6dö æýP³yXoÒhН5œþŸXqîÅ œµ÷onnñF# À¸á–åV9"b¨j±˜>»­m°AL¾76¿q}7)çÀ׉‰­ÉW‰ÕƒpM¯žüök7\Ò ü»Ý?ðÄêÍóοRZÍe ™|.“àñ£Íu`„qÿ/V ùÕò¶=è;„üïŠs?@²Ï‘¯Ã>~üd±À·XŽBá´ À´Õ¬×V–%)®¬¬PÿÿéX€•ïéýÿæŸhð£€Âò zQýÏM±£WžŒÚï‰nÎ !€yÕ/ =ñ& r'fwa;»>q·ÈäÁ¼ö×Ó7ÚÁcæçØ€JŸ“ D‰ñrÓú‹ùë#í•ñ<Å;„[Jâ;×ç  ÀvÄ’Ài+p:î§ÏÚ¬mo>]ÝÞ ‰Zæ'5Ïc4ÄRry)Q–4tµq‘'êæñŸ¿i}…÷™ˆ#¨"@ß•ä 9¶Ðøú*¿}sF~÷Þkï\½ü{ß?è~:çüÅä³—²Ùÿr àýGÖÕ÷/¯• èX-mw{v¯cÛVºÛíôœ#[ñ~-ÇêÀÁwº–IŽ™Àá‡N·Ó Fƒö˜¼PI/‹³½¼ ýÿíßýö›¥o>ùYiù{y”~ö‰¼¼œ,pA²¢ñ•Âp}uEô?IZfS°­„Yñµ©7áŽ!”û,?»ï%­W å~èèªO­ï%ÝŠèO¶@3ÇçL‚iDä GRJ+YÚÕ«ºErÓúþúHïÂ@“¡q¬£„©/ Ùôb5+qA3Ž˜dš²ë«…òXû} i„«ýøÇìì>U7°¢Ú¥@»„Ò¾ÒfD §¢û ½˜1õ\õû¿q}’øú:ò•À|‘ ¢­̯ùÝ›—¯OËA¶{ùòÕ›áÉýs÷Ö5Ü·ß½qíòõïìþÑ8à¬ógKþ³Ù\zåÉ“½ÿ°l¥òã'Oä¿UñŠ¥mÙÙm§Ã¿íøžÝ^׉‡ï 잎I®ÂÁQ·3ê €dÕsb2â”>þô~ó·ÿÛo¾ø^öÑÿo¾ùíßÿíoþáÓå ²%èn (qJyš0Áõò"ë³ô#9–r&?»IŽüxÜ8Šíhn}Do@ñÓÑ1 ry‘Véaû™OŠ9ÖËò{hJŽýŸ±Ò/–×çñ¹üõ4Ì5é0ñÑyƒ]\L€« JÆùq[|õÍÍuXf5îÇ«À$pµêÓ§;»Uª3¿ÏØ%ÐÖˆd·Æ¢è¦‚h‚x‰Ðá4"΂ó7­‹)/v|ç;1cÌ?Ãë«ü½k—®\÷öX~÷ÞÝ›7.¿sõzxëêe½?Þ»wùÒÕÈ¿wïöõ+?|çúwuÿ°$ƒ©Ìæüù'39ùtáI 5±ÇÅ2«€•òªÄâlKÐïô]B{ÚËîFûÈéY(þÀõcƒ4Ô:’Ð@"‚Ží &¨À¹Jf¹ÍˆøÇßüÍÿûßþ @æ{xÀüÓoÿþ¿ÿÍoþQ @&[(HüŸ;áÌfÊÓÄ p;ÅUï­ùÛQÓq“r®2ÛÌ®ÏÆ}Ñ«¿ {ïû Ø¥ë+./í»ë*.žnÂîãöž/7­¯åÃóùë™DàûžÉÁ¥éDâ '9€íPXþXÜ>hm·ñW}PkÂ*€˜m„ÓðÇDY e®zÒãŠ&2šÓþ;…üv•Ópþ¦õ”ÄÊ›+€ë%„zuL¯/òû·¯]¾zýú¥«ïÆáÝ{·ïÞ½{ûÝëW¯\½zK7¢Ôõ[òÛ«—¯ÞKïËWÞ¹rãæwsÿ4ëÍzH4Óç_)— yÔþ >ˆÏ [4kÅRÀWÛNo¨²µ·Îh4ê[ö‘=u­áQ{Ý6É%þUðàx0tF=Ë9àa)‡àþÛßüíß}óüç¥Ü÷ò(ýüù7÷·óßþ!€üøPöÿ)XðÌ,3ê±ʹd]x[þvru³êÄÓx~}0O»$z‰ÙÍ× ¼vh:@öížÖ#åýsÉ9";ºLrÓú&þzâƒ$ijb aüˆû)Þ ÀÎöθQÀu·‹årq»~x Á£m6â5Ñ4â%ûpLâ;OÆãM‘è„PÀ$s‘ŽŸ5P˜M$Ìèš›Îß´>aýX"%xÜlßWØ]žŸéõ‰‚p_ÔýÆ•ËW®Þx<¼wïÝïöÍ«¢ç7ß½{ãö{W®Šæ‹â߸}÷½»·n_¾zåòµkßÉý#>X3õF³E\†Éù‹Èe³—²_ÿ:…`}­T|L°)@A<€¯íÙë×uF½úÝ þín»; nåäk[~)®Á/@¶ò0-/ ð›ß WøžHCþæ7ÿÀ3È•Ò+Ù)^€\¾8KÎ*1ÁŽÔ¾5;v1EpÖÔÒÜú![Ù‚@ñ;‘fóø™ é·hÌ s8OmöŒØC‚ ÅrÓúFþzlØ®›l­ è?Q¬ú8ØÙyúôÇOk“F=¨øö¶zò+¦ú“F¼fsçéæê.ñè;³tF7>Œu!!b ÷Ð{ “[d5æt®G$@bƒ„ ¡ çoX_»…ã@9Ðo¥‘ñù™^ÝÍ»b®^ Ãëï¾{ç½wïÞ~ï½;wn^½öΕkïÞ»ûî½Ûï\~÷ö×ÅxïîÍ›·nÞ¸rE¾Wf÷»`U%;h5Í}ÌBògž¹¸šÉ‰ @°½½±V)½O°¨€üjY @Ïîw²[mqånÿ,?`ýè÷ºm’;]|ßéŽèrFÓÄ ¥üÃb±XýûÇí+~oñ”‹ÅLéa¾4M ’/–ç €~âè Èïóvüí$"¥¨&#Óë£ÇÁ9ª=ÚÓ)”Þ˜Äñ= ±ù*Î@bC2ñ˜ä¦õMüõD×c”ÛžšSzAÉÃêîÎOwùôí„dm½¸ @®€Öú&~»OwvÈJ@…rÕ¨D h#¦íCWíW’awiT™‡8 Ë/Ü29…é›Îÿœõk²h­JšNä7kõZX®aÖªQ-ª{þ^mW®l­×¢jÍ­÷ú°k­»÷o߸†vË›7nݺ#:ÿî­[ׯ_yçÚ›wî¦n_¹*–àæÍë×ÅC¸ kqÍuoܨ „³owÿ4—逰cò÷P¬Œ€ï—QȤ3êlm΀Õ|~mãÓצ}DɾݳìvGâxQpùm”À]Dÿ&9GÇÎñ¨=8¶f˜ Å•2¥åQ*“3Ȭ­ 3 ¿>á\صÃð<Ò8Þ5æ°à<ÝŽÙÏê'¨•&ùôú­¯_µf×7ñ×{I³«g!w!ŸX¶&pÜù×Öž<©ÖÊux úifñà’Äš‡àä‹Ò¯« ïzJTàÅ¡: !Ñ·œÅ~ßK¨—üP ŒLç?¿>¶ôZä×bht-öÄ ðê{õºUÝj­*¿«‰mûP«zòý¬N-’_†ðj{ó¯~mÅ%ƒ¶âútÏùH,d$þ˜kÓw€vÇÓ÷…ßôþÁ+¡€É Ìî‹GЪ¤áÑz9ŸËfÓ_¿þòëí÷o®W¾xòùg_|ñÕWåòj©øêÕóç¯ÉûƒÍ¼/Þ»m·­î¨?rìö {„Íß–ß ‡ŒÐ%<³0ëd++…dþ^÷ylŒÏ`U À 7àêú¬}WÁf¢(¶ßÛð·Gˆ³Ý‹ nsësÛ@Ež½Vë=ÍÔ‡ã‰^"¿ë'¤º\Çz/ Ÿ^¿õÕ§­ÙõÇüõä1Nó×3œàà‰'u·˜ÄÔTÂZ¹Hþ¯cëôäÉ£¥¢èug§º‹aa€¤/ÁKÑ $i^é×Cm:òHKÉîbÅÖsIÿ%©8x´>„ʉç?½>Ú üú^U.}uoWôßuE©ëº„ðôEý«»ü¯&Òz}¯!ÿUyÔ$6À¤žWÛû1õú±â(ác¨ïh4ô£ÉÐ%ùÖôŸ¼<5Ò*{ùú«_o¬•ž¼|ñ¼X|´^)¿xñâÓ_ew;˜õ=¶Öh0h#´·ûÖ 7²;½žm;&9"‹ÃNÏqެ°–ù~GÏz¬eÖf ÀÆœèÉs „”yÑ[ò·“bø €ÜsëlE— äGËñäP1£Y¼öÉí¥u}?iöP ŒCOy¾‰É’ÌÀ¦Ëd>ÿñúUY_üþZcO”Ú¹UʾWM”È_TW¶Ñ~Ñüj£YßË Ïÿ`¯.†Až.+xµ¸^;y}ž'•9„EoDw~m%öü™VaZ$®$]q§îŸ1gˆœÿ·ß^øþas È&ÐYð4h´Òô¿åÒ}±b^¼¿ø÷ésùóòå'¿zýu·×t•áó¨Ûs É–%[¼öûõ¡Û&yÏ: œQçxà;¶3ë$`uQ~õwûœµÌ¬Pœ3êŇlÀ u_y;þv)xÜôÜBç×gÝ–é3|p.YiÓ¨™VCãQ0ŠäœŸ§Üy²>ÔTÖoµ;­Ùõþz”ÌÅŸE£@pÂ_~v < û H;™x'(Œ8m¬¯¯oËkk ;'rЧ*ºâÇVÛ Hþ’8‰a0ú†rW¤è‹Ä7 ”õšQ¼–¿]FH»dc%Å5Ÿ¿®/jí×àìÕÅ—¯ïŠJ‡¨ª…µjM´ß«Q£†P¯Q¯×¡õUÕi?¨á{ò1Ö $4¿þ¸•˜“U˜îv âís8ÓçŸRŒ¦TĤšj%žÜ?'¤!ñ·´¼ÉõE7öA3A/mµ$ØZGÉ?—K¥¾üê‹—¯^¾|ñòùsü}ñâ“O_¼Ã'v÷^¯‡–~{p$Û8Ê}ǢݘöïIÞéÚ£¾cƒ*œt¡³@Ž:™O/lÕ[ý>g-7ë”æ {@·‘T™oËßÏhnä'Ó›³ëGIm'â<(4ê°p¦Yü˜ý]¡½„ÊêÊ¿¯˜„y¤míns·zè·Ž¬V*x²¾ò×#妠2Z>㜠¢¸TÕ€¯àé{DêPqk}h"€Ç·6åQY+—w?8iÔc KÃ!°g”ìš œ§é׵܅0ÙÃu1wéûZ` ÚÑ3v>°KÑpþº~Mþbã—×ÜÛƒûSÛ«†ì öªòfªP뺛ªò1TÕmW@8dªâþ×¼šï×&$¯ÿ¦­Äþ\+ñäþ9± ¹øýC’y^_ZÀ“" Ø?ØÚB÷_qµÅøâË/¿xù2U~þéZêù'‹ú§¾î¢ƒ¡ü‡`{õœÑèhtäX}•>è6FþMòÁ`Ô¶-Ûê ;àhƈT¥|ðàÁÒ<–——åÿÇ·òÛ•ÌÉsèó–õïòŸ"ßÍ>géArüƒ‰\žzjñ+,ãùKú“Y:CÂ…kBøáúÜZîìVÅ<ÞÜÜÝýàéN´»»“4Êš7 "QÈòVHÆRtÀ!oŽtÅ4ý:û_ £ÙOmé •ÊÅ C\%ÇEv,6ž?hº±Ã×÷|7ÅÆ. X¯©¯T+z½QE¯Šú˓ܪìÿUñÄ&ÈŸPYs«»žÇ(AžWs0rñù½Áý3±$½àý3s}ÙÔ¬ƒŠ¨Õlˆþß,æË•RêýÔ@”ãóÏ?O•Ê•Jêùkèåû¿þŸìî½Ý³GN¿$_GèûöFöaLr¸N¿kÙÇò¥?ë䊺‹Ü•—V®LMèr?~ ¿¥â&ÏYáó’ÇCÂÇòÌs–VN?–æž³<‘à5Æ?â9¥bnƬŸ2Ú„™ÌŒyoËßëð}¬š ëéõ“‘tÔ¬bÅ@ç>œB7NPkàsÎË%l’~·Tèå¸Eª»¬˜­~çî´Dy«»Q²>^5§Œ¡c¶‰xÂ_¯pƒ¬ûé¦Ë¦{†µè‘‘Û±ŠìÜÎîîÓäïNíìLå´›9GŸsÄ:¹À|=|+Â]žÐ¯“ûWÛ‘ÄŸ)ñ¶cà)½¼|ösðãùZùryÆlœ6Œ7ÅǤ`};þvm—aÈ:žã˜YŸýùd½‘0[“æÆ…€‘cé`Æ]aÿàô±Šî)u¸Q.ëï°Ní< Ÿþç?Uäâdý1½§s¡£zë†Ê^žÜ‡AÒÏ$E¯@ 1;²ýïz>…ƒC:t {”Ã=AcÎjâ1ý:ÔÄ i*è$×WÕMç”SÓùWãš(¥¨ùÞ^\s¾×äUD‰«—Þß«“ýâìïí5öšF½ÑdþO @οÆbêbIj!ÔØ.:¿ Þ?²„áIpáûgþújW Û[ëk•B¹T.¥¾L=“½g½ªa›±Ôë¯ í­{º¶Ø‚ãQÔí:Ž=ìöD©Á `”Ú.¬A¨Á3Ü€ª”˪ÌÙ¬6ç&&`9=ofô_žŸÎ΀gB~,Ϭs¶þËO03Ô`åYrPÉ`1–ÃpoÉßð}"j`‹ö‰ç>³¾¯ö™™c®;`"™hÜG™hâ”?Ú Ü~+!ãMr¬ÿT‚j¼VugG+…N~¬NÂI>¢ÙСHTùë}"ˆ)jÓ!g9Pà%9ðÚL£^¶€r|3>÷ØÛ#5veÙ˜ÈPù„~=Ö–·(™vã­ï³&Î{„f3š>ëHJb—_|þ‘hê®/š^ßÅ€DH’wÙÛë6ÕÐìëÜסîÚžX€fk¯Ñl6äËõš KP¯!3[ˆý`iÐt~»ê‡ÍúLèÃ/~ÿœ¾¾DlݧX+_/ÿï×EùË©ÏSφ#õlùÅ(õ꫞#yÄÿÿ°;êô-›“þ½®sd øa2TlÄhä¸omõßâCL•|ƬÌèÿ>?‹'Î{ GbâLÌ{Ó¾¾8Ÿ‰çdM À Wš¤ºØIÞŠ¿=dê1ùËÔÔüúÊ*ˆtuŠ;…Ë.:ÞüŠ5ÃOŽL(.Ü@fã|£ëïD [1®“#I¸“¬œNy2Laª‘Ø¿û Yºx4&]*‰³Ñà×¥6Õ¨'¢/á)ˆpHÚOKƱ,¯†bò"29¡_§ãËö¾ 'þà5èmïjù/Ò$&Ý#Ž÷.>ÿ]¨ª_õöê1»$]è0jûð ªõº{{Am¯ZÛçøa$PmÏrkðÄFÈïäíÁ_¨Â#è¢Ê`áù]èþ!¡I³qpâ\øþ9ûú ØÚX_+¯UÊe(/õYê£ããTQ‚ÍãĈÇÞïvÄ»4àˆ¨¿Ý£QgØ8ÔòÑ¿Ë*Ö¿À9Öˆ¨Àg€‡cýßÚZâCL@¢¼'Ê=­ÿéDÿ·äOö,/¢@w"ÑÿôÙ`¼b–&çÁE @Ì Š;1“ÑÑÛñ·ë@®Ö¼|"ÖÍ®ãäòHÓCô._Ÿ‹˜øýhÝEq9¸ƒeíöXw6ɱ¾÷ôƒ`<ÑÎÄ~ôÁN˜¬Ïüƒ2i‘ÙˆY&z$„Д'pcPC^¶±ºê­c52¨G×Ð^ï±|´¼’’j6/Ьœ«›Ý–ÁõgL¿®ýº¸‹‰­iÏ~âm†œìÓBÁ~95³ðük»¼}7¨Ö$lª$ö„Ç`qèÛ«‹y¨"(ÿŽ/O”¨_´É~¸ bP"'¿óÅ2âÄUW,‹éü.tÿÔÑÁ¿?M~áûç¼ëÛln–Eù‹¥Âª#Ê~¿þ³cx¥M°üôR/¿ñO§Ó“È €>¶äW}»ëØX?ƒ¾IrL wä—ýžc¥ÖÎ TEý“ï–¶ ÌîîSÑ=}º g€%¶ /ðÉ˪ܧ À8^€é9ÓXŸ5Å|ÊGŠÕå•·æogf>æäXÂ(4»>~Ã1°@Gs1‰haÐ#Їî*dì Æ´G OåŒJrË‹åð£Ý§;žÖÙ½èé;¬/q}ÜY^HèOí7 ÉjùëÕÏ„+*7.Ç ¸ýˆ­qèn…ç‰Ü¸6êÁ*ôBˆaUÆ\kZObtz¸Á ýºÚÔÈ8Àâ$Ï“%ˆ3½?†Ñ`/‚éüEíÑ3'»¹â¤HìdòåO£†ÞõÄ `”ü¾ü:¨‹Ó_õC‰ PÿÛP? f¹P,–ÙÛC´_3ßÅîñÙëÓôá¾ν¾•;å\9WÌvr©W©Ñp4 )@4n:mì_}Ùµœ~·ÛýþÓ?uîÙ¶lñýv¯ßŠïïÌ$ïÚ] þmôØg°À,Q[“|é´r?˜S\ Î1r<ÿÆÇiõýÏ.må¬Í€ @¨ééHçê|*Õ¿¿õmN·ËÓìú ;ê’ÈÙ:}mÞõóiwfþ¨SžÄù¬{q’t±êªÆÀ î²xì©CH  bÞå“õOêäÓ볯”ÔTç]RO²7Ô”?ëYú|EÉç¨ÜMºu0T€ä=îWØŸ4Å+.?C"vjC{˜”øÝ`†~¶%ñÞ•™3" â"ÅÓîEºñɤéüãjĪ_«Wý*Jý^£N´0Døµýúž¸óáÞA‰Àz8»ûFC BmG<þ}‚»ð‹‘¢-ß¡&ŸÊ5Óù]ôþÙ߯K Þðþ9ïú®U —ä/^ÊäR/R½g£g#$‘èwÅWÿìË^Mþ–èm×:ÂHDñC«ëŒÚ€Ût•û<šâ¯g÷oR®@%À‹uÞ¾JÄŒëеʀÕ÷”…Í©!+b.‰¶¸ÏónÕt¼dÜý§è×ñ‹€‘ö0ó0 „ÆÂ+‡ã7ļáüwÑ[H¥«6Å…Gׯlõ.¢ßg™¯¾×¬Õ{Õê^cooO,†ÿj»ò»fC¾?À—ÿØÇ`6 ‰ö‹M¸Ât~¿Zd[=|³ûçÜë É\Êå®S¯SyQWktœ*–+¥M{àR¯¿B›oÏîö:.?ï5ôºv»?´-«;Ê_“|Ðë[à ³G#»3²£($ê/:yÚ8Qìä‘]š3ÉRã'&¿™ &Ú/мV‹6s@F îÍPçüpß–¿=Jfß‘™ ’yØéõ£(©[sþ…lI#Çp´ññ6GÚ°Íê0Á¯µúgÖâ„¿žC8®íNñ×k 0Õ:¥KÔ®&ßcË-çÌ$ª€nÐÔiÁˆe€ô´§-VæJR®7O¿œ@îjÿ¼«e ^Lv(뙟´É™Î¿º‹®¤ã¸;ÈåïÕ|äòˆö}QàfÃÅìO}¯Ñ88Øß#!f .DÙ÷Ä`.ص߫FQ½^é>ÔØR È…ªéü.|ÿÔ÷Ùÿø†÷Ïy×·R^½”^ɦ3Ùt*•rz²×àÃÞÀI¥¾ˆ@]ôúdwï$Ðï‹_ÐZ–ìïÌäv‡ô!]À‡lÛdDÿ¹÷Ì{§ô¿ýMO€‰ÞKdðpÚÌ6©ö/­¤‹ú”â’ÑHçSè’›{DîwÀß®;%ÓæÊS=·> ½ª–Ô1ò]xJ‘@Úž¶ûÅÄ‘‹® “ܰþ„¿žØšŸ†íEÑlÙ*Iú{‘SŒ5æ×è”~ðxT7i|×4;sà—IÃ:·@×SÛ*Bˆ› ór'__¨ ýzâ'"ìp¬pÄÍ ->ÿˆý>ËCÙè÷Å‹ ÀæôžèøÞwûýŸ´ZûÍ}ùv¯VoV%mG *>Úþªl ®²åÚõª{bùª¦ó»èýÓ†Gý°%ÀÞ?ç\ßJ9ŸÉgŠ‹äÓµVjh¥lûCGÜøáвmK”¶ÝÅ̯ÃN>ôóÙ–¨öpè°Äg;¶Ý#s@„]'µºÐ §Ê¿¥5ÁS`‰.ýœHÏ” —’>"ôlÁ$ 0“Ô†#jÿÊŠªñ¡¼Üi°:gr)^l²èÅ“ŽÎ3ù×7S§cÆäŒ´Ú3Çk5)7W»ö4I—D©º~ š©j©Í°:¤Ùð N¯"Ý:]ûÑÅäÓüõðaOó×G »È8mΉÂܸc|¾ErÓû7ÉM×Wpä}8 ë a…b>Óñ„ùSý …‡ð}Ùô›âô4[­=ü3´‡â‰´ÄØ#€h¿|A·€¼iN Ökl„‡|w‘×7ÈÉUŠÝœ@ý‚ˆX àLº†OW 3  EiòL²?>—=á×å¦ÝùãÑ߃|]¨aæ›8ðøXÙÀ¯m¶žî­Ê†¡•4Ó{ì_aCH¢ß®—(_`”Oó×Çgð×£c á„êuQÅ´"p Anzÿ&¹éúÂÅñN`…ÑFïw!Òš†éøZ$šÚÜW@.Tµ¶+z/{ûÞ^M£€ƒÖAK¼ÿŸþ‰X€ŸþñOÒ:<˜vï si€ó @vb²§ õ%=ž^î °™Ÿ6¹âÃÕ”&£}BXº ›|ŸÏï8p9 í8¼BSRïȬ¡m:(ìºÌË1.bs-!|B§†šIÎöjâÃ`'FMÓ9dˆQľÅr}è)™kû$Õ  XsB‘Á 7½“Üt}µÎ}+¬G)ƒÔ¹¦ãkUTÆš{{u€*MR~íi쉀ܖìþ|(ÊÿÓ?ÁCŒ@«ÕÜCY©Àÿ«yKH(g@§}ijˆÍço¼8 *ì a(±·Ç¬æø&ù¹ëWJðõ¶øV/ƒ-zd€ïÓíƒñsÔ¸Ÿìÿ€÷µÚmÈS©ÔýyW~rFGGÐõ1G¸ž€­RðïLòsׯ”üU6“äínfÐvF(>FÖ`4YÝvg$šÜÇ„ÿ‘¨²ÝFÿò.]O© õšu Š£Oý稰T%v¨Æœ¼”5ø,~}“·fc‚ÁxÊÄÀA‹ f‹A~Þú]Êæ²—rÝL~¶{ý^»/ÚkK`ßîýéŸî‹ß }ÚCÈS7~¸ôÃä©¿LQncPžÖõŽlgö1#äŨ zb Ä‹H§çÙÝT%›?ËpËæ *µè/Ë€§’€S]¾é³òªî'àaò›©uV´¡p\n„öoј7ùleÊäf‹ùT‚CÏi.…`7€>’nžÃ5¬L:žƒí¨ÿ*¼~Ò‡ÇZ \jTØ€2Ïv;¦µ´Ò°6ýwW@HqÌ@qRŸ[$7ðÛ3Ù€Ùd‰“~’÷gd”ß¿Qnº¾13–nÒìàé‚Ã’H“䋎¯Öâº_÷é´TÃ@‚~¦õµ–·ƒ¸^óbZû ØÄ¸ ònPµ¾G\Ðe‰½†·»&Ц’º IDAT1ÝÆó7€†ÆèÇÂü |íä`ÛLòóÖg(þÃl&“Íåœl>ÛîÚÀöµŽ- t~¢ù¢ÚNÐñð~EžJ]þ?ÿÇÿ%òÔ_þuŠrÑw«&€pÿÄývÔíÑ~Èîßa ö  áÔF1}Ž($î=-À$¾ŸIžcN…S}…ÓúŸdôÄ̇éâÆ‰È,§‹ÅlÊ×Ñ6D¾Z†6ðÇkÇG æïæŽ'¡^H¦*BºäÊÓ¢8A®ugå ëù²|±€† >Ô”Ô@ØŽ´ÊO`žÈ$7ñÛÙ–¹,"F’®Š¶¶›å¦÷o–®/a…cïd„nI³ÕRÖóçSók{Õ: w²kñ5~ô{uØ_û`¿y ú^çDZüe—¯‡ ¬í1ä¯*üϨbÐeÚCÌߢ”£ÝÌxà“½€ü¼õ“\\v%¿’ͧ³yqՇѱ½Ç—¾?êòC<÷v¯#»ù0•ºyû×SâÈ‹þ«Üî¢Ç#€—}ŸIÙí;C«ÝÒ%sžh÷SåU ¦Ï*RUÅ Ù¥$˜mòQOO¼¥%"ÍxI¥pª ž5I`I[VV`–N€|zujP܃²¸)f¼eÿe³\»˜?> ÇÕ½ñƒ > ÿÊV¿W«»E0¾«îÖCô€æ îE»b «nu¯êG5Óë_@)|?|Vg=û ½ ÉÏ^¿R.òÙÌ¥¬xiQt;GZß~gÔýPTV¶î¾mwÄ÷ ¬#§+Û{;õÃß»zùÎËïQÿU>ìÙàþ=’í~Ôé;ݸ]иÝu˜D‘ÑéŒF#€ÜJö¬$à’*vvâêÏáý-ñ3€ÀÏvNŠ“ï¦ @¢ÿ'˜=…i]Émœèÿ* €vµ²½”?þxæ`¹%ò¹ã‘è ÈYAœE?§}4· •p.PXÂí"šC¨úÄ¿'3µK¢!8¼œ2…±O‡OgÁ 7ñÛ<€~±JÉL¢¸”ÜoÙ…n›Þ¿Inº¾qâ%øÌWvîæ~«\Ú®oÃt|X-ôLÀ.u\UÚ(x­Zõi@±WE¿+N€²áƒ$†%@/A­éW=:/ÕZ-ÞÝŒ¯o|®:{œÍªúˆ 0×$?}19Ñ~G @6›±''^<¸ýމí@/Â[F|†vjéêí¥«?¸óŸ ÿyÏ"ö÷‘5ì´;úžmÛ  +¡?{AÒõaÖÖ³³g!! Osû?ió;5|ÊD-4Ëöߪllï ƒÏ÷LÇs§æÕ¼zŒ?híèÿ]Uz·¦©Áš‹gÀ,ÀeàSê€ GdÀž0”@¡JX?ªnՋ̯¿XîiÏ—Òì#4b£–ÇFÉ5¤Åòó×/W3¹l.ïä¥;v¦g%ò‡Û/^¼uÔè7 }À>J]YºýŸnýÁ+·ÿ—¿N¥ÆrG‚ƒ¶m·äýeÙŽc:è @€ÕïP\l˲3‘ ²žy˜9úk²‡ë¦|ÚŒwë1|XzéÔs¦c±EÏ™Vjø×LW0´góiDm5ÉMïß$7]_¼±1èh«ÒjÅž¸ÿåGOwZëë•b+2öpÀðb¤OyþâS¹ÝÛ£&&Ù=ŒÂ„õzcϯ)EpÕ(4ÑШ€,rC1®bô^ß(W®އ(¯ï%” q˜t=/”Ÿ·~e1ƒ|f˜IçÓòC63rFöQ¿/_ÑëÛÜÀ~PÛO-]¿}ó÷®þàö_þõýøe*‘w»¢ñvl€x@–@`ÐTF0 €>Áže·Å6¨TÉ,gN€ª­KK'º}Å]>÷9¦`Íë<Ðç¨ó«”N ÿ+jbzY¼®ä€NZ5tÞm<ŸMñÛûZ¤õ’†ÛSÇkÖT\l¬'¤+€\™I‹&È1ÌüqÉpŠœ.ùCè}³4dÕ½(dÃX,µá|Û;ž•›ð۳ɞ¯ÄM½ÁŠUk³ŸInzÿ&¹éúÊ>q×—ï*¥RË“ï·?ø µ!ú_n™ÇU®E@ï¬!Äß«×ÔØ¿úý½º_lL"&È}~u?IÜïÖÜZ„Pˆjbzý…r¢—¡ˆK†cäEdÇ`»Lfåß~{J~þú¨bH1 È;Ì$°Q·ûþƒ¾hwÌ_£Û?¸}óïÜþ¿~ýç©DŽ–`Eüê¢×ׯƒÍÿXN§'»?º½£Q» 3…Z-®­ˆj΀gRz¬¬÷Á¹Ï¡þÄ.¼ €xükr–‰€þç&Ü€HÖð ñrh»A÷’Ç*À®ÙÄð^Çrcæ¦ÔL,–‡Ü§ÑuĦ5žß>™ðX]““ ùC°“„Ó"¹ñý䋯o3jmm®·Zû-Ùí+k­Õ‚„]ÑñßJ•JËøù¨¼ {çViè_#È?}$ú‘ûG‹ ƒê@ qµk°@V૲çV÷ä™a "ä4-y±×?_Nºt²£K P±ãëDÒÀyùùëWJeŒØé¬…tf´’Íu» Çgã» ïÅ™ïá«8wîÜùüå¯^ÿ×Ë_ùç©Dn÷œN]>Èù¡1áöva€†àNWÜ´ˆSa·­13P¡"Ê91ùsõÕÉsΜñœý¿ø:4ËéJaŒ ¸¾º"úÏœ“€Ìd eL›štó*ƒ:Ù±òÛ³ÁF[Â]ªáÜñänu“Auäö’Ží8gåh#.Y¸m»:Ö˶/l½,°ŠRžfú5†_,Oêî, ÛOóÛ3Ü 6©§Ðiíubr±Üøþ ò³®o³ÙÄõm6ƒ&¶þ­VkíÑ£ò#yl®–+;;ÍÖ#0‘®W]Óç3%¯‰Úo_6ÿªùèc›(€ \fJ׌Uq«>B·F< ¸j¸ZA˜l¼{ý3åä6Åçãq’i=0 °L‚‹;%iyƒ#ïÛ‰|Áúåòjn%É­ä²…X ëdry©»^÷ˆ•;ÙÕ-ù+^€ÝI]ÿ£¥wnüàë_ý?\ºöÙ/S‰Ï—¯8<á# ý;ÊÞ±ÙL‚€lÃ4' S€…ßís²ùbFô?7á,¦EÿOx"¼±yÂýeU.T:›ß^1«xŒ§œ³ÇÇ ¢Ç,¦¼\EåŠØÈèÇGz?­û'ä8®â]ßÖ!dá@ÛþIºÚíg£õnLºáŸ€vLøíu°:­u¬¸ ÈHX¾Hn|ÿùÙ×—W‡ÊquÐj=Òdž|·Çß§(¯ï> MŸÏ´<à`CÞ?غǮ!jÿÈ`æ'¨V™Ù©Õöõª\:EC5%®Ö9žãMÐH_üõÏ’'}þ«.k¾¯h¡ÖU>& ù·ßNä ÖPø«ôJ®—Í¢0—IK,б2ÑßxÌüwèÑëØ£Þ uûöï-ÝúÕ×_¿þúëOž§Ærñ0÷‡ap„[ƒð_B‚ž3¨Ç‘üÛ0AÎU2ËUÊÅÔïö9Ù|AâÿÜ 7`6S.ÌLë„™³'üë ÄÍ<¿= ¸¬ðÏÏšæòvõhÒŸ ïJ-$Zg¼™ïæœ,æ9è±®ë1eÀAƉìÔŸI-õ>9O[¸ûrWöõ]ù@žÆÀžüu‰Qê]èxÒB¹áý›äç_ßf«>6åõ½ßÚÚ¨¬½ÿþÖÆZE€GõÍòO×ôùLÉeçŽwk àÈæ¡àj}¯Q¯‡Ðý𲳋ðk{Íú¾çUYñ¼j\­yò -öY¡Üþ¯öýãê4”œN¨(Ü1lM8‘Ÿ°ß‚<4‘/X¿"¡Ž)‰.Ù™+Ó·ígÇÇÏÄo•íXÁþŽºƒvê÷oßþâëTêåç¯~žê'ò.¬€ÿƒ9‡|~Çé$Q¨¾…ùBØÆ'°àâ<,A)?R©ßísò¥‡²ÿOÁ‚gf™ &´¨> xèÖœð¯«ç>Ëo%m~>q¹‘³=ÞS”K4t³‹ütŠƒå³·†Œ/:e- ‰ªGD_•Gãð€­Þ¸C#M*rpÐ(góHB³… ?d§%8¸.oÅ€gà±øÈ^BÈ8÷¿PnzÿFù¹×·yÐ@ƒ^³µ^,n=ÞÞØ~üxs½²ñ¨%N@©UÝ}ú´J"Ãç3#¯Õ"ÒýÊ'R÷µhwww¯ZcNÇ%k!‚„Æž¸b±ÄøÄWù[" s™y7^–7yý3äìIF§”Nøй„’áNä'à1ùùë—Ñ é;mÌà\ZÉe²ûg3½¼3êž}ôQ¿ Þ³ã6(ÛýÔ]yçæ•Ô(õúÓ¿Hõ@n;C~XäïûýžÝ"w0èô„qº6mƒÝsF££)^€låaZ”rñƒœÅ¿Ëç”Ò+Ù)^€\¾8K †»US1ÑEøíÕOAî¥@[sÇ{‘"àò£ 9ÀÔ—›pçàY‰*ýXÑ·tòÓÓ9á"í×v³Â‡ªµ;Aø^(MüõxaŸÌ_!áÄ5á|qMRä¦÷o’/¸¾Í&€òZ­òæöãíõ­Öæž¼¿Y^[ߨØXoµQÓçsZ.>€W %ë^-®6T¶›¯O½^@ýU‹j5qUªâ÷„WÝ¡¶V‡oþú§äÐd%¢ 60"„l‘O,èçäç­/úŸ (»o kç´'ðøxðìøY7ë ž že²½ž“úƒ¦R·~ê€8ÌîÃ* H÷e÷:Åî•#ÜrÚÄ`ûïÓ6Œœ~ÿH¾Nƒ”òSÿ᥇ùÒ41H¾8OŠè™œ¸‘†Í‹ùí]l¦Ð[—¬îüñˆwµßŽsl:HõSb 8ä‘!bÖ"5äœ ÉrÉ©;l¡š:ˆØ‡†Š¶›ä&þz…#d]/éËaC"½Wì&F¹áý›ä ¯o³uØz´õ豸ü[›ÅÒÚúû­m±äòãÚ˃lo\øùœ'G¯ü  &oEþíâÄnW¦Eõˆja¼\ ³ŒÂK`•öŽÛë劎B”Ze öpt4t“Ε7blÄLä Ö/—.ç³—8ÜÎd²¨ö3™®öŽ†Ï†Ðýãòq¿(ÛxÚrîÿ¾lÿ·/Õ•=½×È Û8 ,Ñ~r„÷œA§ßA'‘ÃÚ íH4pdÙ£~Wmô(WTéV3 «¿ÛçäWŠ…Pž5â]_˜ßC­Zmc Ÿ:>"þPt=ù •/­u>Ãq¸É è L‡H«ÄžR{³¿Ÿ¬7âDt©#¥ƒvµ±Î gÁi=çMÂ1ˆ÷eö£G £Üðþ×gáõmŠª?z|óÑ£ÊêúÆövmw{C~±V.•V‹•R †ÏÇ$7]¦=NDôþYáUñ‰²ò–¯b d€ÇK“Í÷‚«]bïÔ[ÓQÀD~þúkåÕB~%ÇyàK+ÝÜJVl@7“מÀž F×GýÁq¡t½4h_²3ÙÔRÝ~ê×R€‘Ý_b{Ýøb&Ôév>ìv{NÇêuqm°g©m8Ç£þhÚä*jòé•EÕßésò+•Ü´(Tf ñná÷^ߟћ¯õö¹ã±Krð>ô ã Hg7\L3Ï™n¢Àgp9;ç2Þg#AÌfö˜Ë¸™òéydà¯ç^ÌúJs¼8F½Ø(7½“Üp}ÑõóøÖövåQi{{gwçÑ£R±òøÑ6œ‚VÀ4ÂâÏÇ$7]è`Œ$(Ñ”˜AM È›ßÅëcÀÝ{¾Zæ"° ø UÞh¶šû À$ ÊÏ_ cp9²þ‹ø¹¤'0süìx0:.¯Å–Š=ÍR?¼ž²R×—R§-P¿Hû[]Ç t,‡¨À£¾eRáÛîwØø!Æ…f¸Õ€úû>–ù…ýÿaÁ§Ÿ³$ÆÏÀó——äÏôsÆ«ôÁÒr²ôÌ:ËXO\Ö×| ßËs +•nÀÕõ¹€ioNï^ˆß> O;Ñ;˜%›?žu/†ÏÌ'Ñ>s/ÐãÁô$ƒµä¸t5;à3Ûæ'¸ûÈ%#!Ói:æ&ydà¯çHMt’C¥œ "v¡k’›Þ¿Qn¸¾ÍÖæ£ëë¥Gå§?þ Z­UÖjO?x´±Ñòôú›>ƒÜt}8¹!oy´\¡„ÁÈ,ÐAˆ·~ý aõ‹˜BwÒÂÐm 4 ?h2¨^?ñ&òóׇÈrœt3Y-fsIO #ÑÿÿW~6<<[->¬–¯¿wýSƒû—î[Ýd¾wÐïôû€þôF=«=KŽðP„ö ŽðnG¼1Öˆ¨Àݰ<Õ7;§CÅÅst‹^^YšÝ³—W:|ê9''Ïž{ÎÒÊòéýÏ)dÖf ÀFy> øFüöïÔòkäj‰oæøH]8TÜàŸÇ ©ƒ´Xäê€ZwaΉ2Ã!!&í|/ F(;ª*¿Ê¯ƒ`žY¾˜¿ž%y¼%åÿBÇf•èù¤ÅrÓû7ÉM×7j­o^»µVZ/ðãêNÉÿêÓ?]Ûh%¬‹7ÉMׇFKó¸dæAü¢!Jço|²‰³Q:iíu½ !ø6CùaõPèòøv"?}Ts™ç³Y¹½Q\Ig’žÀœs¹Û픎û¥Ò¨X<.=[}ïʦº©?J‰n%Èérߊßß0Qå›N·ßï Ú¢úHä†!؆ckÖÈŒ[³ç>f)½–çI‚ù‹ó˜¤O† fŸ³tŠl8Ì ¬ff=€â¼ˆßß>JxZ¡YÛéãÅGF¢ž@|ÀÃÐð‘\¤ít× PRV´*gT¨m‚ÚŽƒ½4àžhᳬ&y’©ŽÏã¯gúž&†Ù¬ÈíD4KrÃû7ÉÍ×·UÛ¼~ëÝê?õ÷ãê®Wu½ïì„ä7Ïoñõ ˜TGQÖך‹Q‚‹úÖ¯'ÃÙUªd  |EKPy«qÐ:üæÛ“ÇD~îú•›eqÈ" Ȳ#8L “‚y'gåìN¹\*¯‹S£Ü3$ºÊì @nÖ(2oÆo›†Ì˜¸%bÖÌñ.ás™¤Éø”$ý¡IT‡X9=\â=rÇö”ÍÒEPÏŒt†9_”…ñD6ÿz„1ɹ³,à¯Ç81Ò À, ó†!O & ¶&¹éý›ä¦ë‹HfgçÇ<Ýù ê&½OhØÁÒ^27½àxãú†ëƒ¶{Ÿ±Ú5XO 9‘À•¾íëã+xáÅkw15šÐppùacÿà›©ÇD~îútò™\`·%.@.w)-þ?“ðò¹\.mçÚ™Êq©Ð{VIýѵT·—zç]vEµ4tm”þ- À€ÌöùSQûâHd0ìY°‚`ª`6 ˜-Œ‡N‘þ ÿO+îXÿ ú}¢ÿéi°s'  }/̽’¢­å ÀóÛûlºG‡ wâSÇ«—W=`¼àJ‹þÈñ–'~hè)èžÏÌŽ¦Ìé(h8ÁJZ(hË<¡Qnà¯g=PŸ?Fá‡Ù€‚j™Ò 7¾“Üt}ÅÈTwwªþޏþ;;ÕÝjuÇgøír!ãñFùâëã¹êl¬´hs#yOĉðm_î%°­˜ãô¯ÿé‰Ê› 0îï6æäç­¿V)\Â0`£@¹•Ì8 Èÿ2Ìgìl'—îe3Årá™uï÷RCËIýá•ÔÝ?ÖжíA‡µ@ÙÚ=Qv1^Ïî÷,Mö¶eY£N»k £5š rE³˜ãO °0åL @ö¼…N À4èÒôc«@P*æf Àú¼xC~û€ žDïeùíÔñL~‡Êuàœ d“j &~Ù-èYJóä³\0àú µ†ƒI÷Ž#ÇAnâ¯çY1±t³bvÄœÝ!jxl’›Þ¿ñú®/™¢öíݧ»Ê¾MäÄD 7É ×'dó% Ÿä(SØvîFoýúì%ê12ì‰tv<Ô ®DÞXéAãðpN~Þú 3H!—4Œ³€øO”+7ìç-1Z(øhúÁ¥Ôà™“zç–.ÝÿP¶û¡è¹Õ±Cƒ~ÀäØÏ -¡¿ü W ã ¬~1A¿‹ùÁÁŒ(—Ç9¥ýËÎ*äÒÖ œ÷ä1ç~ÚXÚZžÑê¥åd­)0Y}šg,=6ùryÆllœ*’e#º ¿=Áa˜Ìuf·æŽ‹Žœ·&èþº:¤4ž‚¸Á“ }tÍ¸ÌæÂ¹Eâ·d0¦›'®NŊͯ|y åœÅ™Ö 9þzv÷&=K¸³<ž( ”•Ö$7½“Üt}¡eaÂBZBvV3àa„c<Þôù®’mEÄEtLŽ‹NU+åï[½>Ê$úcXá*’C¨¸?>™Çòƒƒf½~ØÜŸ•Ÿ»> @&s)›\ZÉ¡xœÄ„ Φó\ÎÉçVV²ÝRô{KWî§R©û©”¸þ²¹÷:ä£`Ûe?æÁ†ÿú—hÀîÊÓØ. ¿ìÌ€<€ÄÌh¹€Ó >:Têl‚$> ú^Ó’á0ˆL­£¨Â³ 3ef¹˳ä ðÔ Ô‚×…øí¡åÜäÉn0<¼1ŽàQ#Uµa¬Ùz¢aÓ¿Èæ¹ ¢kŸˆO>»(JêÎ8"Œ#wŒÄã’VžErWË |Õø þz$ppšA^¤«qjÏ$7½£Üx}ñkøÁÔVV;Hã¤Öp¼In¸>ä?p9¢Ó­b†.b{žv9¿Ýëü1$˜sH¶ÔHm ˜ˆž0–7ëƒýV³>+?wýJyõRz%Ëü¿VàqP6|Mˆ9Èãwmùg÷UóŸ†ýãÑ1Ù>ôÛ]Ñý¶ÄÀ®o[»ù =L vƒó -À˜(Qsý>í(<ø²b~oÍz jÆOL'(¢³à! Ǧ ÀÊÖ€8zC~{ßKÒ¯“QÛÙãCíú °;t‹µŒ$“rª§í€¸ëÐu£\œÖÌŽ¶åºÔÁçãùfy´˜¿ž©Øã $¢è#‹/Sø‹åÆ÷o’›®/ç¦8|ƒ(l¾‚EŠc:Þ$7\4qã³ðÉ«Ù{M¹ ݾƒ×4õøú bÞAS4¾\”7­}‰êÍæœü¼õ+å|&zàœxügÅ$0"¸Ð.`YélײœaÔwl«o¡Ý‡P¿Hð‹î÷ñ³ætûÈ ~8°;}Û")¸…6°ÃQ:°Vo‘˜vÆÀièÿC~£ÞüŒ8Á Ô"à© °¼¼|kÀ?ð`ôÆŽÂÉýa’ߟáóczÏš­úa«Ùl6šn‚ÄüŠ{îõ)ƒ¡+H°\Vô,—V-ifåŸÈòYT †¢ÿç{0û’Ÿ®huÛtÀÆ qóåoÿÃ>0ÃôI/î OŽO­Ÿd—¦\€ ïÇ© 1éœðPHÏêÿ¸jð`Ap¾XŸ5Å|Šmê!û÷Ѧ6¡ñнö4¿½˜µè IDATIŽ‘¾€<Ìœ1ªãDb<ÂB¼•<2ñÓ'HáFYõ#û2Ë>ýñS1öœ %ðUPð œñÈÕÁâFl aúà-„,yO^K¤ÈG ¯AY!+rUH½ªcX9€¿ÊÕ‘oº€Üt}ˆÏªo sÍó÷‡In|†ÏèDäþA‚XAÕ&JþxÞõ¡Èçò²Ç‹ú‹3€x '[¾ø錺pDs2Ö°ë úDz‰Ä“w˜ ]P~ˆïô ²ÛÛm«ßïˆ$ì3TTããSkçuѹ3ÿa&À”rÏ0eOéÿ)°¼uQ°6k䄱e‘Ÿ‡lšnòžÏoo’Ç' ÁVÞ´ZýÒÝ÷måÑbþyN!…œÚU÷RýX‚¾Þý@‘ ðh> …m ¢ˆB6¨(”hÄzlJbÆìi&-€u@BÑoL×Ï‹Np‹°›]¸ ßѱO¸%Ì' L—Ð0³ d›®º6d0Âk:{˜äÆ÷gøüôÌØ4è+:ˆÂKrŽC¹ÀÏ»>•Òj!m¾PÈ"ÿ/Ûÿ ý\®¿|:·’p`:j[¢y þb´ ²å‹%è[][6÷ÑÈîŒÀ"HV! 8  »òײìaŸÇ/ Æ`ZÿOuù)ièò™Ê=M-<¥ÿöÌ9€R‚„ÐÞe^ÝÄoo’ûÏÇ;£ –<}öÿíåþyäჄOÒe_¿§ý5A⮎Qåpç+°Ÿ€Ý©CÀ»2Ò'¸¬ú‘"Zdö4”1“ÝŽ‹¯„‡A'jBóõƒ[ Áapá½61Pv]L“ö +¾†&A¬sLF¹éú(ô'í®6nÏÝ&¹ñý>?‘ê öq†Ë6d"˜`TpÁõ)Áä³HdDõW²ŒùEàd.]ë;mñ¬^->#ëx| õ­¾3ÝÇœ_4èŽFÖ ÛÇ”`ÏžôØýn·'û¿ÓvúV»ÍãS¥E@¹ý5>ÓhÀL¨0[H8ÆÓÓ¥¹*ÀÅ=€Ò¬ÈŠÐF¶Øë º˜ßÞ$çæí‘¿“ø€¬Ñcì‰:Å};¹ÂtžË?ï%hŒÔ9‘£?…c𘂳O>ž:<5ð­Ÿ}*¬®Š³ÏRZœ€<ý\’@—0ðBòv73hcØZ¢é£ÁàÈê¶;#˲ú²ý÷Ú¨øõz£n¿ç´û¤ÅŒ@×vz}gè´0<Ðæñ©â‚`û¿?,{ÒlÐçž fõÿ-rÅ9O%TÔ°ËÆ1ñÛ›äØ `ãÿæÞ­·‘ôLÌkuÛ®JåIIQR¨DÆAF’80¼?’Æï„ë§­£¤eÃä“9…ª±Ô½v~|ßsÅ °PüŽ?‹ã¨šyÒlÙ9Ç><Ø·±ƒÈZ>d³ïÎîуÐUa€Ç[q àƒ­Ãc1v¶··ìí¡kˆç_ἪÀú·Æ† €8oxÞ8·þ¯6£ÏIöLǸ·£@Õ^—©Õëõí“Æ) ®@OÆyZ\ õX³ßpa<áTÐJ¤¤Èi];zãñýù>9nð‡qSïé~ˆh¸ בãRCQYå·àŒÒ¯Äæ“Uk¡¡-œL£ÕûLjgf0B PlKðN¬[4Ü*ÑîÀd7ÕMÚ”ÂdˆÁ¥Ð%‘.·Ñ~Œ‡ û*I˜4ž8?ÔUƒù#cCûÂý‘4žxÿ\ýˆ"gד2’’C‚ *HBªe½f~|1ž¸}â 䈶o› ±Æ-å !0P‚}è€?ÚAˆŸDû[[Gìí€ÿü€;{û‡2º 5±c · x¸·¯çnRàÌç `¬vê)(@Ký#`l¤%ȺÂÔòêJðY|¡K’€î9`ѰpÓWíËõí[}ûKÇqÑ”òŠä¿cÍŒñß)˜-|ãñ(I>î˜×Œ¹|X#HÜ~]úûtéYÇ혥.Rꦙ*¶7carc MÚ„ ¶CR!‘ï†q…ö°uçOÛõá9 ¶ 5<ö¼†TSJ†>¹Ï¸0ôÓ×>i(é=º·×@‹o(ŒsS„wÚm+wNÜ7Ç^T¢ý®¦4‹ S]vÕt˜ gù*Òåö ó©l÷»XLªDÀ't)®Ëd35²(Çâ x)q»‚éÚ¿éx¢þ<ûUBÕ ×Ñm—¥¸¸HKóì"“‘ñ¨«¤mm˜t/&ÍìqsbAŽÁ(.D‘z8Ió£ç 0ýŸÇǸ|‚+ ƒmË2•+xwûøàø¹½$ÜßÙÝÚÙ9|°EÈŸ,v@}··ÀÌŒŸ8 ;Ð Ù:<<ÜÝ>ÚÛß:ÞÝ6œë À§)üK¡À—€±üüùÀ ‘ý@pZ<ÍûŸK\bœ³À..¸†^¶úUzôí/Ó§ïŸ×^Ù^Œ=SƒNîldg»Ú¶­w@D:¬¾Z7W4ìÕúóm­nõc˜òæÊD¢_Ùj¨<‰[Œ`µvïæM߸ß^—ϧK­Xw;p­¹YHÍž;8­aü+Ûaâüµ™‡èµ»º úmJ3ÁÆÄ‘ùí¸Ž&EøCFø1¶éúñ¤ù!ïg—J €î_¸?’Æë×g×#R ¨6e²½×.q~ç“IÙaKpÞ\È“*Ô·lgŸyå Þ;’ÈßÇ{;÷Á¶³*p–ö¶¡¾§ö÷Ä ìmx° áñûG†í¿^/À™*ÀUëÿŒðÎY £—€øÈ×Fû†ÇÞQò‘àÛ Kµ@Û æî“`ý}zlQ„¾M‚U Àt/nðŽVyzªÌƒÿ!?q3×ýéMÇ“ôçûD©`AªWßUÂ0²yÅ Ø63üYáMùè2jo«pÓÙJ6I½6(vµ£°Óë„M(jÑŽyïcÇ»G¦!•@Jš?âäÕî 5N\*¤Ã‘ýìj¬Ý¡4 ¡ËÇÚ÷Ce!¤6aÂxÂüÀâÓÓ7÷ÏßIãI÷OâõcÈ(_™J)ËÐ¦Š´Ä‰ósz¾xr«ÛÔ #À²Æó¶uè˜Gf>æ Æ2¾p Ÿ>€ Ú$Dx$Ÿö¡üºÀ;ÿ÷öà -p¼{„jÀ±á¸XPùKÛã5ûŽZ€ eÀË Öÿ;ó—{—€'qšüÏ“,ÎäaÜÀ±óEƒ.­2S$“š¤Oß%\#ÔV2–Å» Ù§L_DÈÞàèÅ;yBTÏ­ßsòuã ç'éÓ IyV‰ØD§tÜùEBœ¥©úíñ^û-Å‘‹ÕijðvéÚòvë±M‡?AÚ * E# HûBヿïúùI}¢°ÅõؤrcεI°£ë Öòd_Á~Iã óƒ£{ÊÁ¡31r$'Ý? ׯi_L%Áب^UÀÏÎDCÏÍÏÈùüÐϵò »P° й‚ïK´/{¼xüßC‡?¶þÃã{;{ðö÷¶ïCdƒÇÈ &QƒåÑýûÇFÑ. @ñ<€ï`3–¯ù.Ï€&ì5 2€ ñ?& ì „Å¡(ÙgË€¦“sÄèÇÀX(7õúô+Wêӓȉ¸rî˜&ƒA˜…x½GP‰òá‹Â‚¯úÐÝP=åPõLâñ'zûY¾²Êýùˆô’ôéYp×þc6ÂPžE¿Njª£íç­ÎÛã]z `‘x>ÜœÚyÀ¤á(Øpz‰â¦}B´’ÍjáýSŸ5iþîßgߦ]:áÆÊší”7TæU@¢äñ„ù¡—Ï‚a+ó™û#JO¾^áúÉ  ÚŽpƘkèR¾£¨ Óùhà…ù=ß÷ è°#ÐAaÐÚc—`!æ ÞŽ5>wÑý·»ÝŸ=t ˱Üï÷wîC3|ë{¤-À.Föö!3hø²Â®2²¿£îx¡p‘èRÀ¦€ÑÅ­ÿµFðÎ…$ ®ÿùá?þ=ï‚p,ÿŒ°ðÃËmµZØås¢>}K´Ã· ¬anŽ½šƒ”¬Ša¢‚Êë­¬Éι¼¼ütùdñ¤ß7N ã¿™b¢åå'Ë'OžœœÄV ÏLÔèùzçêÝ;ç w¥þ|ÌÇßSrÉ“E¼½ÚìgŸa'Æ ÉÍûö7¢·n)ú†$€¡–ûC*Žãm2÷ö*¹it­¼9¥nP§Œá Éd›8à ‹T¼“åqe(!:nYhk^Põî玬„ÍIãIóC¹ŽvLÐã9'=¶ _2þ:÷OÒõë0 •›¹¯=M²õ³†*ï#<3? €z¸<ë«áü\8_¯àP+»Þ´s¶õž%NÀö¾i;XÛû»Û÷YèCcàÎýãÃýã­G‡û‰“Ì ï "a’ÀôÁÛû 1*ÅüEp5'àé½zý_H^8ò£ýç’c£@ S/VN €9Ÿ/-C›»©O¿±ÒZÛX_½VŸ>RÎ-jÞwzº^É”2ûÓýv´ö­­¯o´d—úäéòÓÅ“¥¥å¥Žìþ^>ÿߌþ‰…ÅÅO?~üÑGòI,C»³çwOI5z¡ö–'êÓóöd¡ŸÛÓa«aáAÜ ûs d²;öÛo¿5~säƒW+;z7nV鄊^ ©b×SyB6§ôHF*£Û|õ‰DRÆ\hXúªÙuzô1’æ/bß/¦!ö‹–‡ô‚ìƒ ‰À&õÚ1GAL˜~ÝxÒüô•‘] Ú(ØVÖ5ØŒN÷ŠñW¿’®Ÿ–ô¢ž¢|tü¬>Ÿhüq_}u:?ÏJ>Âæ-{\ @ÎÞµ@LÂ@skÇ4÷wwÄÉß§2ø‚÷·èìß—Ç¿³ÿ¨© R»»[{Ç»b€0¼Š‹`úÈ_0g*€§Çy´àÙ$Ý'ïžé7Ã+‚DÅ+䣯Vkmó:}zÒ{Ò+æZê«Û%r~@!K<Øy ¡ÿwhö!ޏŸd€÷·ˆ{@Zð úG“€—-Ú8YŸ;]¸cç1þƒ#wÖ\rX#Ϲàœ¾ÌÙ$ •³+§ëß-’¼óPµÖVWW×Å…»NŸ¾Å0°.ñ—īˮ"rKt™Åo­¶ž<ùè£ßüæ·OžüæØecY€º-뿾 @ÿú1׿JñvÏœO 0`PkÇôçY {Ì­¦ûç ÖI8AôŽòLGÝ·¿q«ÝI2u4J's=+tÑÀõ`Ç|¤ôÙ¸•ãÝ+ŒÓ,äÑg€tùî(5ÎG©0̲aÂü×!¡PIs´¼ÕeÞŒ1e"Åd€Á”x :ÈÂ%_??e!Ej\{§÷G›IΫÇ_ñþI¸~û‰{ W€’7&i‡ósjNN¾ÎÏ¥çkšmó`ÿõã9Û<4¡&¾ëì߇$ÀÖÁÎÑ Àx÷Åûß?ÞÞAУýG6r‚ûä|(‰?88<@*@ @P¶FË€W®\îÿè眷1#Ï–¬rP¬ÿ‚€¬«ºôãx¯Õ§W?¸K•Œa“7ºIˆ#׸“¸·Í5,ÿ>úõGtòóä“åå“哦ƶ€O?³€§œŸKÏ÷½¢Kº0kýÀ€Y°¶÷ìˆU}¸m¡ððøÑÖÖ£ã‡vË#;{»yK<ƒ=YþÛ‡Û‡¦µ+ÞÂÎñÞñ A¼¼DÔg ÀµÇ¿ìsb ¿üC ?9y€¾ò²)“g«µž O’ R¼õÚTåh#?5Ò•KIZíAÙ`jï£'œÈÿèñÓ§“ÆÉI½"æøI}ùéÓO?}¢Þ?ãÿ .ëß^Zl`)v¶#ƒ;èzýyÄ÷à§b›þÇ*ÔZfŒÍ•*²¯ÿÖx'zë ЄžÛÚÚ£¦ síëíX%ŒÚ” ®hÇ,ad+§cÀÄ€Ò…hj‹4»!ùü i•0øÕ(®‡l  ìÄ´ÇL(*ëzêiôƒ×OOœ2õÃÏî¸7r0,ºfüë‡*_ŸŠRª$ÖVôÿ¨5˜ŸÍ•þP=êÁƒù¹üüÀwÅ 8Î~àX:À>0Íí(»?4A ù‡Z€D,÷ïQ7p{ûpß:Þ9Þ•çîo™2G%pï8V†8Í\ÿ²Ïa _<5Xÿ¹ØtаÅr¯×§'ñ›6겦b>dÚÐ^  ×øàú“ÖZ+Š­€±Ÿ>^^ö\Ó|úäq£ñ® èÓËŸüßíÿ¾TëDZöýúýyjsL ÊÁëýÚ×&°„TÝèFÇŽµÇߎÞ?5Ì¥C,*!¨„Ú•ŒUËDår9¢ÒaŠ \[U¢X ˆI56ªQƤC¹IóÇ´]½=zÿ .£w©Zim XT³B¢¤ð:ýÄñ„ù¡·À¾Ffbú£÷GÒxòý“týºªH¢¨ Bµ{£PmŸÍÕÕµ^t ççŠó ×£n ûÇsÛ” 3·M'ÆnïŽïn©>èÞöVþµ¿ww÷xëÑö±ko+?g[ÛÆ÷LË1·¶òà^~Ì€ëÃø—} €iå½BA @ÙÃú·¬ÀlZÓ„òRâÁ]¯Oß#ˆ•¨q­×Óñm#aǸµ¹¾±¾.×V¾Y[Ym=é<é>ùõ“_?ýÕ¯~EPêXj·Ÿ~úX·Xø>½qbÈ…*×y›^¢_Ÿ¨?µÇZAGã²`EÃÏ&#!¯”Ãw=>½uûl³ Úx:Ý?¡r{°¾ÑnþÿåÉá.R ÈÁ4}ÝóI0Bä nãí¤ùCˆ¿=dàË h¼@¡0¥@Š<äÍ#&] óýŽò~]7ž4?ðH¼€òyFî(a<ñþI¾~1[8Û`:#ÍZ:?«+›këëíÎÉ 0œŸ+Îä:vÁŠûILå0ÓŽ1&X¿wAö ÃüË£ýãÝëx{ëxKü{owÏÚ>° À¢ÇøØÉßßHƒÑ¨¸þ æ_ò9blgÌô{`Üœ¬ÿ¸j@¤$,½Wѧ§° @dݸž¬4Z]v‡Ò…cåS.Îjke½ÕÚØ\Wýù““Çºê—Ÿè ¹wþþHO¼’®ØèøÁ|Jd´]›ó³¾²±¶±¹¹vÆÎÏçûž Êà¼Í~àÃí¡r˜cA÷ñÒ>Øu¶Âþ¡uhï@hÏÚÙ½¿uüèЃԶóæá6ú ö¶Ž‡¤ bæm€m‡åzî¼íÙì(7üb^¼[{Ô0Õ23+Füz}úˆÔ,Zž%÷Ëó€·w‰Ð’; µFU·ÖÊ*¨Ý7´~ß®Çëÿi½Ù\\n>= 8yÓ§çߌi¬]¡_¯¼«õçÕµF»›ƒÉòK^ŽH{yXçC’¬GÄá[7#€S ÙÚ‹¥’?8˜³êù§ÆJ¥ Ç)¸.,@  ,‚Eù‡;‘Cí"`ÇQ‡uL쯽Äù£QÐ<iÉù2ÀêÞ‰P¦l®ïÈ^a>íúñÄù¡|–(Š‚ô?Fî¤ñ¤û'éúEÚÚ£Pa¶kœ'lÜx}eu­µü¿_ ççªóƒRÁfØÏ~`Ë*‡™1&ÐÞ¿-þ¿uàlK˜ŸË綬]äò;ß±vÄPìÝ7÷Ímñ¬]TóÇööîöØ P(Íe‡QÐõ¯Í@eÿ†o™øƒ‡@ƒFe«Ðvµëôécܧ¶“vb1©¶j¿©×܇ï¿ÞZ__ÛX_“‹tª?ÿ¸ý¸þ¸þ´¹x²Ølžœ\®Oo3õJÙ0Ê•ÊåúõIúó]dºTžÐÊw[Sb”Ö$"¹¦ˆø•›åwÚo¡œ %×ó&duÓËWKà•¸À‹ò¹ìÝ…Ì´}ˆ @d+‹î]ôd»rhQ%h¿N7&Èï’*ëæ¯Ç¤8: Œ Å‚¯'÷Iñð™×C°‰4`GOÂxÒü´I€Ò!#%DÎÝIãI÷OÒõc‚±¯¿¢MLæ9Ê6Îx—k«'_Ãù¹êüÀsmrÛ •à ¦Í¤ ³oïØ{[ÖkëHÖ÷–i›í‚ØóÄ {û[»ãì%ßÝ‚œ¹·u`nY‘u¶·s¹_^<¾¼ôåøå‡ÿEGžûâô›/^¼|ù‡—_^òÊç~ÏË‚óâ[ßú·ßÿdž›[@ÄPv¼®éÇÀ,å]@µ»µ¯A!—ÂUúôlq#c3ŠIÊÚÀäq›òÙ¬ÊM´²¾¾±¾ººÚZ_muû£úóõæ“æ““æ“+İ•È IDATôéeëÏ@¿¥Àæ¥úõIúóL3À–U¦|BÜ×_&ãE[sɤɔmì·¢·†¤“žïîÜ{nÁqKn©ø:™fmǽë:®û‡Â„m;,_¹â]–ÅOI 1 r¶üP™Aòêu;VÞj'Îr„å26'H¨Þ¯rú…¤¿$´—-¸¶)’-´ ãIóÓ&}0p ¸àȦÞIãI÷OÒõc—¢²6,tž´5žŸuq3OÎÃù¹êü T2o˜ÀíÉ(‡Ù*ˆÞ8ÛÎïíçÍmÛzÏ6Ç÷óù—9[ÝKa‰µØ³_Úö1Va÷ÐÙß?8ÚÞ5‚By€­wìK Ž¿“ã¿âøå‡\×bùÿò—Xùö_|ašÿeÜ~ñ….ù_Èw\ø/¾|)Ç—…»wïÞ¹sçŸN;wnßÛÁ—þðÅ ê!ãtûö­[·þøÇ?Þ|û¦of¾³àÚq;pÙöoØnÙWàærbÚÆ‚ß:TÞéî•úôªo–'HIõT+O“Þ‘ÒBF‘lþâl®µPXiµxœêÏŸÔõ¸iOØÚ3¸C†ã†1WpÇcn°Ù¼0%êÓë2c<ŒÏ ÜCAÕ’ÖX%ï¼5þöø€vÚ Š¾åþ K[®»+>#–7,¬ì’îð–cCt^¢Ç‚É…ï8†ð„\‚ñ—~1ðtjÈ;€f9õ_•í§0D)ôHn¡œ¸²Í´¹òáÀïÖ¤}?¦ ”?Î|½Âøµó£l$ôëÑ/:$'Ý? ×Õ_.÷N|œ£mÌDkëk+›ë+£ósÕùå’+«TÖ{­@vÎdù4!G޹gm½„9°Ñ5dÂEx)nV>·ƒv 6bc¹Ãíí½£}Ã/È­sáÔÈíR(Œlù/^¾x¡»þ‡ºõãxñÅÏŸ[ãüÖ·þxË9n߯g-çß‘u-wîÈò¾}Gm|ç|èÜú«¿ú«Ûìy6oßúÖDZò‰úì31Þ‚90%Ç,‰0cP\pÑ 2ù ÎzgÊõr‰>}¬oߣg—:=VËB¥‚”£µº!®«…•¿ŠLÀæfkµ?ª?ßÜ?Ü980v·¾kÝwM,( àïdùß¹£Á!7~{àÉI7`4øá/¤Fó„¿TA] ÿ‡‘Ä‹² Ýfœ¯¡>yõ<žþìsÏž}1FìñD!*ûG¹±¾æ’Æç'TT/â4¿?’ƓëG[E5Ò¬{t4HˆørƒùYÛXG²™Tgççªójƒ€RA¦=ÌZ6­q€yºûã| G#×xÀÖx@>vòhA¶½gmo}Ïå‚*8 †ñ?ýÓù=/†Kðóá¡fE¾ŽéÂ}1úyþüùHJúbäpì¡Y1o#èÇ–ÿùó/žþ9\~yíçÏhžósœùb~Þ’ XPðó#¯8,y1$…íÕ‰ênÌKÕR¾hÿY¼–¨®v¯Ò—íYºÿl§~| H°¿&_>:ùôÉ'ŸþêW¥ÿc6;sœœÈ5Ä8<Œ‹ÿ°>gŸ|òñož4Ÿ|òÛß>~¬<OÎê×3mFîš8¯Fâ>âéÚCÕ©¾¶õ:Z_V…Ÿhp?ê Â,y»Y¯yNUn\¯d33|æÉÉ òÞ¹Ä:wƒ8¸Aì³7ˆ| „@‡´9ä ‚/Oõ0lWÍ_Hõ@´±¸ÖQAß6ßìÙ÷Ÿ4ž8? ãIÇðúj,¯UýX ôÍÆ#ª¶ JI«©iZ4uócÞ’Xs  ³õM$ ÚÊÆ ÊŸ1ðdT_ÇÀÃÄ­üÅgH8(—ùhÁýíCãáþñÖŽ« Ê0yW ÀèÊÇr´âÃ4™~ÐoŸqÍ>GpøÄ/FŽs?â‘ÏŸc“ç©rèjÿìs]ÿŸ©ðL•À–@ €˜~A €åçm¿èòŽX-#öÐÚqŠšŽ*ûa#]4T~A‹MG»g2qy{ ÿ¾"Á¾\ñËV6[êhÊ ¯´Vqˆ§¶ú‘¬ÿO?þtcÝ2™ôT&u/•Íf ÈÇWÖääÙ™9ãéÓO>þm³)a€X€>þøÉÉÇ|ª_sè*1€ûL ü¿˜Íª¡Â÷Tþ[Ãh’d’€+¤ NW%ûºÍFÕ+U«Ýž_´s¸2²€åÎ`Ã8bFGã fçÆEŽkÅN£'²tÔE4m‰|¶îvû±1¬À]51ƒ_/Ts,±êÙ'ÿÝéûOO˜Ÿ¤ñ¤¿=ƒëÛŽTp5d`~½7'ÓB¨¬Äj”@A¥‹<±OŠ©~ Vtµÿb$ÄS Ñk„x/å§O2ä™òL{ð ½oîmï<ÚÞ;|øðàø€ *çär0wãÈßFDÏúwó‹.Áà8õt‰¬Ü9ó~``äi°g—;L Àð¥ÅäåïZ¿Ï·ó¾eßp­bÑôM‰ $0‹†Ò½ôU@’¼6 „go%X´ÇuDXWé¿·67WVVÄ+æ{¨¿B—`U<µÖ>ýxåÓ•ÙÌ´¬ÿ‰É‰©ôôôtÇ[k³³³YùxúôãŸ4›'Oÿú£ÿöñÇ¿ù˜õëÑ@ÖjÚ´ÉNévtIÈŒV_ï±®úÔXÿôTÁò…$ÒQôÚíæb­äÙfµê»– YYdr9M)dÌ)àøã±ã/«Þ‰åfqïX•ädǰY4äÊq]?ÖºQ޶`$¡—Î_DjAàÝ ÙšDŒ ÇèQ" 8q†P©NÙëGˆä™$n·kj€ˆ,qï|’—‚×Iò"¯{ú$“–}|˜æC}!oîo?ØÛÞ=4j>ÜómYP¶3oÆ ûÎí;ÿ$±9̲€qFAW>²sß7ϯüÓPày¼…Çëúùhlp$ˆµxþÝ|.}=ûË_|®~œŒ·$Àœwì…â¼,úbÑroÍB)_’¨`ÁÍÓ ¶£­®=%¢êª.v®ÈW;Êõ¬ª+ôß7µõ¹­µõ¡~üŠxiØá×ÖÅe[Ù\]Å"Ϥ'ïÝ›˜˜J¥RéÔT*=•ÊfZk³3™”Œ63ËéfóÓ“ß~"Îÿ'¿‘ã9žˆÐlS‡@帧(s^=¥¼Õ¾½¾ÆÂÔ¡<_F"ÓpwP'î7—jÞbP† 0Íœ9,óÐOƒˆÂ\0¡5/VƒHYã§‹Þu!1ínÑóÙ6ƒ½ßŽ•3zÒ‰^:½˜*2Ãæîõ4v¶Q¡LÓ‰ãIó“4žô÷)õf|}Û*)DEê#µßx\E!ùËú´Ö¨Œ²Jz¶Œ ¦Ã¶–Á1F%²Ñ2oWU†_¹Ì‹ì„¸š¦¥O‚¸ð Ð{°ÿ@®øÎ® ¥ ãGFµ”÷ö¹ ,€Ï­[,éýÛo!?„êsãLÎsmæóßÔã¬|mý³¡{ÿÙ³ÏáØâ€sÇ—øƒx./R×§›ã·¾E`¹Hù˲/˜Å%Ûôò³àdI_#T·Ò‹õ!ÆU!ñ(ìÞêE±4×uúï2ÿCýx´´˜ì[m­­­ÍÎÍÍÍLOŠc41u÷ö]=îML¦¦ggfg3©É©æDs\AÍtyÄ(/0œê×$ÛÄä} žà@¹¶U =c½K€$Ltbýá^·¹T¯×½ØÀ«@úüÈ °ÜoÚÕ*°òsµnŽ—5:ÐEWè5ÒdØN¡Tò9}¡6kÆ;TÒÿë篳)ð$v )dwðþ“Æ“æ'iü®oox} @œÒˆLâ½áxŒ˜ I.ÕÓÊ~7¾‚pÆÙK¹`ïØü×#”¨GíÂ3ãm­U¾Ћ‡3D{ÙÜ^ÆY› CgoÛ<|Nà#£”ÊΓ+æ¾»µ`Æ]YvÃ:=>ßÏ¿=(Ëë*ý`‘ö;»Ç;ú™õ?°Ö-X"ó4Ÿ€ÌÀóVPŽ/_À xþâ9Ýš‘çÏ_|!7,BqZd±/8AÞ3íÒÀ5=3ð …|a>ï.„îQ¼SÓHœ¾AÑH›ÈúT€@Ș¤ÿ~N?+csu}u¥µ¾fÌÍÙÔÄÝ;wïÉ1)ßܺ#ßOLd¦§ggS©fSÞM³)ÀE½ð¤y‰~=§I³C±C^ †/Úiˆ8PjŒž‡’*nDÂ/ù‹›KKņ˜Ÿ&€¬ñ²èXtdiÛV¾RÍV*òcPÍV«…À<]ôà™s/wò°¹ÇJ^@Ô+“Z!ýìØÆ&Íß%É€¾øþ“Æ“æ'iü5¯/V.•Ö!—@Ù°7ï3¹!ÙÕñï*E4,Æ”»«Š*lL&gð¨7™Ä_ê-Ë­®3ÎB¡‰¤nž>€ÜyÙ À'âØ‡û6<€cñJåÃÃûÅïní¾“w C6^Åôüq|pÜ|;ºÙüx‰GüÏ>ÿìÌþÿ{ `Ñ_0O-À ?€_~ùÅKîþb ä±/¿|yZ{´,àÄÓ—Å^ðYønp#(!ðÝùBnÁ* È2õ:ñÙ! V;Ôt4¿cF˜Ž"ºXI-}­þ{G‹9ýøõ•uñþÚ4ŒÙÙéÌÔ$€ ÙøSSbîMÜ»›žÎHhm6›?üá_,ÿÙŸ={73EçõëéŽv:*ÚÑéh3š–•biMl¼à”»ê’f’ûCÑ G;·Ô¨Õ+òOL@)6hö×"ðt4í÷l+U«d+U»POW¦ƒ ¨V*ƒE_µø¤ hÛè» PCN‹ zÝ8c×Nœ?¦®}uCjw\xÿIã ó“<¯u}8F·AŸÅÇHÕ~ßd¼¯t d6 ³ 8›_î p¤™ 9Šã!¤6Øn5ÚìÕ!ÊúÕ›½ÐÓ=!weÉ[ù¼YÀN ™TLò‰˜ÎaÝÁ¶ p½C÷{[ÛGÇ9« à7cXÞp?‰ú©»/4hÇbg8OËðûÿÇïã$ÿóoŽÃ…8]ü¦–âeÎÐ_ۇХvKã ìdŠ,Y ¹Â¼ë#(7¼ÀK›ŸG+€€>›¸¤ ¶2;NéPö{ ¼  „zê0&éÛêǯ¯±Š(`Ý0¦30XüSét&³?5-_Ò©T&=•ÍÎΤeý¿ø~óÅßÿýß¿o¾ÿ—Ù4 £?¢_ÏKÙ¥°_§?ð†û*ÜßÀa[™ÿú—µ“Fàëµ¢Öo//ÕõZ½^.ŸšË¢ÀÀ¿,n@P^{ H•+©‰ R­füZ¥V©¸UY÷ã#@KnŽËÅ’ÏvðØÄ5,ˆ³çúùë“ tZzº_Ÿ¾ÿ¤ñ¤ùIž¿×»¾q ™µ<†o:N^–®úqÉpÖ9hÅ{çÚ¹UPX=þžò*ŽŒãÂw^§Ý›ç£Ãû. Â&a¶ýò¶i‰e·Æµ,DààöÞÁþî¾áUÝ¡»³u¼ÿ0ïž1/À4O‘¹ºèŸ=û|˜ãûâÅ—_ÄVàósëhž?¦qÂÀR˜ß<{XÖÐØ·«_ÌÀøÍ¼¬ôùùœ,{'ðn­y· €…*ê¼”UÑpPÍpÅU„¤™xzpe™8JзÑoõ×[+ëŠ0Œ™éLFÖ»¬ùš™ž9Ú›ÙÍf2²þ'''î5› Yÿfó²úá 4›ù£ïÿ€ ïõëÉ Ü'[/,ùb,ékH‘”øYlº %ØŒFJ|lgËU,üZ¹\)Wkò]‘&@‹>âîåáT«AP fëÕòìlyºZ-Uy¨)@O®“y'Çš!rÞ1?}#o2Z,µ];|NœþRà_G%¸â÷Ÿ4ž4?‰ó÷:×—À¬AátBÝ7'y:Y¨Ç p*5#„.!“» „2kïÜx§ÃÝàu_âóŸ¤tìüKñèþ“|ýOãtùÀ'âä-gïp÷hëø¾Q¬•ŠG•ƒãƒ‡KÖ™€ëßüât‹¿x|Î:ÿçÌÙ=Ôõ†ëŸ€.ÀóÏäø³ß!0øÝ3‚ ä/X€qšöCïÅ/äÏTд˜ àÎ[EDÅâúØb æ]kÁuM¸Q‘Ÿ’„Ù¤µ©tÝüQ˜mVT‚¾½’Þ÷ظ ­òƒ¬¶¸Žg²Xù²ÜÅçŸÎÎ<|7{°•͈/ÎÀß—ÅÿÃFãâüè‡?ü<ð£ý ùƒÌsSïÆúõl&ë°é²la뵕þ•Jsºäãë^F)ç 9=jÜ,ת4õjP®ð;5XÈøÛð2^¶–©U+Ùt½<+ÏŸÎ5¿¤åÛTÇ0§Ží‚Ÿ´¡$ª†'*Nèúù#g*aÌe+GV¤Ér}ÿIãIó“4þª×·Ïöý6ïî,è÷ÞtIH2 ²¤¡Býʨ`„Òòj€þGÎç<å³$¯Aù68ß‘pÁ¶ yqeó7YÈ›äÉomÇ\Â;†[ñ‹Gµ½‡šE×4æ¾5ζ¼qdÿÍѵ?òÓïrƒC–õï>»àœu¾>8~ÿ…˜ ¤4{òÿÜgrºŠ3¦Q`‡¨¸î‚%À3mÏso¸¥ÀÎ{âXó HÀ˜ [!X”]£Ã¬°,pcº£húýëôíOõãaÖWVVWäD3³Ù™,œ€©Ét&{´ŸzWŽƒéƒô£G©Ãø1SlóEó‡?j6ÙüAóGbšsò¬w›ïöcýú>ECU·» Z°©+Æ}ˆ*ëª_{ ©dWÎ` °þkÕZ­F/ TÔxe;˜ ü™jÐhd«5?닳,Ö‚ÊŒ_õ+©²üX¼`Ú«’â Â{ϹS,NúaG3Ö½z€u&Í__›î•z˜E$©À¥ýP ãIó“4þê×—z>mæ5éÅ#±‚ùfãJNƼ‹mž¢óÆOgI];TVe“€+ባ¤¯È$D¯Aúzz¾ø Å¢‹mÀvÆÍþìú<9r¿»x|6D¢†æ­»@Z…y ¬@y;(‰(Vl\RÂcÚý¸E_[G5 ‹ÔoȲ ‚¬Æ^¯oª¿ÒÚÜØÇnfŽËߘ™ËNÏd³ÙéÔDjúèáÃýìììqðîÁ£ qfÒÙlã‡âȺÿñá4xÌÍÍξûîþλj™‰ünwZ,X¡É ÆŸÍw1ö'd>®0Álçi¥µ¨Ëwè`åcáW*AÙ$ðóJM«Z–E^ÍTªY)SÏŠP6Ä[h”Fµ²Ø€gåa^ö çn±ä#åû.Œëó}­¶“æ¯ÝŽŸÊÛ®};fâÐ÷Ÿ4ÏOÿÊùIõë‹~R soÖ:g§û¦ã=-ãÅiÊc„ƒ)ª3´î¤Ra3B‡@Å5†QÚ÷¾6E¾íûéù`€#0RBÀTÔÙ'"0æ>2\¿Zr|S @à’õù»ß3 °€>6_ç맇Xƒßå†À€†"—{+÷¶8oçŸåóo¿-!¾|Æ\E\[`‚á4_HÿÀ Ч `ÍÑ Ä)G©V‘ù*,°LÆ[w]*1P“!QßþT?¾÷ßU/‡„ü™9cf:kdŒéi1â2=c0—/àQ:ý(“14 ˜nÎ4›±˜p`FŒÃLóÝwÛ§< ¢øÃI.-‚?£¾jëÁ­c¢¹w™°7ºŽ2g-×°ó—+µr¹Œ @ÉŸr9_ ĈK_®f«F¦ŽRáb#U©ÏÔµÅj½V­Wë‹ÙÅJ­ˆY°¬2¸€l"„$úreÂBíÀ­Du‹ÔÁ=nÛ‰óG” ö\ZºZ£i+©¯ÎOtéü$¿Æõ¥9U¨@Ð 2&¿éx7®UèS†kTX¢Jˈ°K¬ZK&ôCí8W¤îk¿Œžï«Pp^š€çí—¶uè˜Gf>æ>6\¯VtK¹’ýNÉüîÖ\¼«ŸîÎÏ?ÿ,~Lvéó;÷é1¬üþà¹|óoÎg×ùŃ0bvæÌbì\4 E£­¤u0€1‘<ÿH²´ ƒ M… GE×êÛÇà[ÕomlN!è¿wïîÄä À´xÆ£ÌÌñþÇšȼûh&õèÑ##ݼ ÀlÀ›Sø27'–õ‚Lvæƒ5DÒè1$— `‰hö5ŽLj† ¬dß¿(-ÕU¸ <ÇeñÿiÈ×À ¨z%¯$ & o‰]Ý+¿!O­gkåÊt­6“­Õ«©L}1¨ef37XöK36á€0‘Òr*zahuUf“ëç¯KÛ.k| %Ì7ÒœÞÒø™ùÁ¦x~~Ç_çú2rG,Av»ðßt\'í'$•!ór(Xò/ÒLJW¥Ýz¤\ëtU¹œJç¤ßº“¯h^YúíüëcS(Ý—ãfAë6A1—°€R½dÌï/|wk÷_)hqázÀ7  jªè@ŠçT£§ŒS‡)!²@&éÛèÇ÷Û™)$ýîMMMNMÏŠ/03cLËb´ÿpf&“NMM¢-`jzzêî›ÍæÍÛ÷RÓ²îg²ÍÌìÌÜ\jrbâÞÝ» dÞ_§‚}Üó†(¥¶[¡2_¨‚xÅ¡"›{—‰KÎ>¬ÿjµJP©ú± Jy/pý²X€ ZmÔ« ‰ü«Õút³>ݨM׋õ¥lm±±´Xm¦å,Î-WƒØÁqX‚ñÛ®ëMø`úï(ŠEÙv)þÙï$ÍQ¢û™e£åYÔì$Ÿ™4ÙœŸŸ¤ñ׺¾Ñ ¯;ª®Â7Çûë27Ùa†–˜ADqèI¢xk{ Z—uÑ6uÃNÅ_¿úJv.1_½ŽøëÅ×ñ­X¡ IDAT÷IûìXT°­=òb.ámÃ-V‹fùà¡Fsi©âWÊõr½ÞlÖ‘#–ÛªZoÖ,e½dêé2©â£R‘ï«õ§ËrÕÓU}´ZM×+ˆM‘”’»²’©ËÓp›U3ÈYËã©zº‚S*Ð]“W©¦‡?Èxe ^K•`éZà¨DK—UÌ&«~}ØQQ˶J®u©ÈÜïªâ–‘.×·êÇkÀù³Ù™éŒ,óɉ{÷ x´ÿH>Í=”å/«­S“™LjâÞm fnÞº;9=—5fæffçfç2S÷îÊ÷ÄL¦ßÿÛZ‘Ò2â·V+lµ (CLÞH„`눹 /ÃD¡Ìµ\“U@L@­È·HzùªëýŠX€ºL¸8•T¥²X›©.5³ZsqQ¢“åL³^ŸÉõZ­šnÔËå vÓÌåóãN±Xô»šñë7Õ¹“Qa˜4±^@O‹èÂTÖ…Â%N™6#ž›Ÿ„ñW¾¾Úyv%ÉHq¤o:—Ÿ-ÜÕ²~‹¬Á¢6hHO(?SfŒlQá©ü;4CÅ`œ|uòÕ%òïJ)|Qþý²×G. —Ûó&ºEßCN`{_l‚án}·d‡û¡†,úR)˜tQ:º ¬CÅ+ÚŽ[£pÏ[ À‚kÁ ¶BHH®JFeÚóËälOmnÔS¾k¤×êÛõã»d¥øùÌlµYê·ïN øðáþ#8ö|Pv÷{²¹gº{ à @&#Oœ“3³rÚÍ·Çoß½7‘JýôÛô ažqÏ’_¢ÚQ|«txðú7 —ÚôÖ>sœ^àåjEV½8ÿA¥Öh`'¯Êwõ|àÅúˆªÙF£"›}¦Ñ¬Í,6g³K‹Í¬xy3K˳b2˵%ñ—S ìèÃb70JØ=]G,L}ÐŽ’çOŸÓ;˜ c#·?s1!BýëÇÏÍãó“0þÊ×—yâ8ö ì‘JŽ¿é8Û”CvÁhGq %¢Ð:7æ¡ç·Du»Ê€œâ`€fˆ¬òŃÏl` ŒÂXR@¼V¿"*š.} €rÜÌçì] TÂ$4·Œ¢·»õÝòÖÁ¾BšKu;ã¸eË.±­ äL^î:ò3xÉä!W>Ë?'-ŸìI+íNÿtŒk»ø5èFÈà©ø'YèQuÝ”|räÅ]7í¤ìLɵ-Åý¦ÝI•A;cóuðÚ7—óa,wX(² håýØ   úvìU‘kìÛ‚ ŽUcyM`&Â$}{d×U?ž`ì7#ë}R–zjfÎx÷‘ÿŒýÓôïee3³3ÙÌô½Û7oÞº}oj:“DÐOàФ¸·nßQ  #@Á"H@Iü1½n‹H­ ]@&Yn:ïÒÅ[–}_ÖY<«J­ÒX¬‰'U.{y¿Rùþÿ^)V0Õ Ò|•åf¹Iè‹sbHŒ2ט–kz)»8]K‰á°wÌ´Å8à!⩤¶–šØcb"aþBRßébYŽg%†Hq$_˜pâÁëŽç'qü•¯/HBBUh³ÞN—âMÇᎷU†X)„´þÐÓì]¿&„#ßÉÓ@ð†ƒ0Ö ÃFõàÓáöÉ鸜ðÕW$$d3ùå¯O `[`p˜@ƒÀž™3J8¶ì@, ê>Èá åZY<‡Œ‹,bQ¾-–qÔ廲OqiÙ¹Ó?ò‹þô4*òxZv|lú1X±TÄg×-¸iù_ÊàÔÔiàfúôä(øEWž4ÍÓä—É—’Ÿñ»ÜŒ«b|ñPôKf>`P' |eHE ªÚ£Z5øf˜AÁ~ÓÙ`¨Ü˜¤oV?í/fäÈf3Àÿ¥³³³³Íiò/VTpzfn:›žž¸}K¶ú‰´ ø_ |ùŸ‘/ÈýWKWøuº‚gySÞTòk™J%ø‡ÅJ%ƒ1 üÚkâ–Åu•WÍÈSåñšÄy5ù~†#ž$˽! _P@7K¾€ŠcZž9_pr¹" A¢AY¹´€«¿UÛ­A žïtb?2¼RßþT?¾ËšÜæÿyÅÿÌÔT*Éþâÿy66ð¦äGñCvòžìôS Íiˆá€‘@óÀ?ÿlƒw¦Ü—]b“(P§ V­V‡ Jƒymh8¾,“D€ý¿Æ@`q±HPùßþý¨ÚV±.y¾z¦Ù\œ^Z’oÐ1¡!XZ®ÕËËKér…^„Ø „;N¡è‡”'Í{ö zhix¸Ââ§e`Ô´Î 3FP "íJºôõ}˜@ÛÙG?0ã=°B[ÛF œ@æQ`;-Àt±ìù¸¹j^)øi,t‰ðua{ª%`½ü$k5H#}—©,ÊbÆúÅCAZ^f¤VK‰¨áeäc}(ô]å©5¯€‡+銇W-´.ò»*ò¤Å hs ŽØÏ2Š{£˜yÏÌÏç 2+¤©`ÅªËÆÌHËTЉoõ©z¥i'éÛõã•U&ÿå©j&þyåÛ}&ñµá›Ÿ”5“+%Á)8€ Ãñeà~Ê5ìûµ*²°•²ø^¾Z¬}_B€B¡€J@ÝÂи¯-Õªbˆ †~±:›­ÕƒFPõªU;¨ªÕ<ø£$VD ½.ªÁI”.:ë Ç“0È_€kxbO·w¦Ì€O¿j~bW'qüU¯/¼A ³4a§TÃý7G:ZŠlÙä»ÂêWM˜… WÄÔ]‡­… ò‰†@0¬ëQ öãbg_G㴦اK__ 8DœqöÇÒöiȪ'Ðþ‘o/¸E£Y­úå`:ð2>÷ø „îQY”²0ýÀû´ü$+ .3ù²Ü±S²pÅ ðýI>AþÉŒ_[”S*¤®¤qƤók~ŒH¥T¤Ã!/+Ïàë{^Qb $‹.vÓôòfPºMˇŒAÁÎåä&²œ%ú÷JÑ̶R@æYéiݤÏH1QßþT?)·;àÿÝ/à|ð>~ÇÚÌcšù”,ý»·¤233³ÝU&$Zk?7?d~úÓŸü¿ÿ3÷ŒXŠ.†Õö5‹ÔÕ?¹Å# ªãž¿Œ%”kö}‰Ê„õ7ò^P¨ü‡J¥èPXɨV³l­™­.6š³ÍZs©1›Å7t=U©AU–¡ZH\¢€;ðú1 8sXÀî+Â¥›8ÔçÓÙ!õb–;MÜuû‰ãWÎR„äñW¿¾(†ôÉÚ÷ÆãðD¾§HMÖ‹AÀ¨V =åÖì=yJˆbâÞÅÚ:Öø ^¡à„;ÉÛf`h¾Ò¶Äž²#_öú¾çZ±Ù<žÛ¦¤˜¹m`/nx{¶kC°¹Äв¬ldèìÅ^AÔîaUz.W,ÖrFn»”¬ØŒ§›¸Ä>1ü¾W®úbjØÛÅ.Tè0X„× ç§biZñú±ùÃà‘RI,CJœR.À¸¹» Þò-³Üð fÑ4K´ 9[<C+jyÁý×UX)Õ¥{eÂikÇÍj½èz}û³úñHVñæYÝüàƒ~±Éeýíµ÷åàòÏfggÑZ€;ãâ JÈf)qË”d˜Íb7BŠÙªnö"¢aQšŠ4²îÑŠ‰ €YëÚfx”¸Ë ÿUügôï—ËAµ–÷‹Ž,ÿBA lÁleþz­\›©eëÙåÆòb³Q˲¼,V ;Q/›0u/P©é÷lT=b´Î„}u¡Äùƒ;ÜÑÈiz²›p*ÁÊÐO¿n~úåýºnüu®o›¼>L¯…qÞ0zÓqb&i˜ Z¤¡'LBƒâ÷l3}¢Ó´ALõúzkµß>¡ ˆs'q3 VЯÚÐ"À@^œÆNÝþË__ %à(cÅÊbFÍ/Õ‚âÑáîöîÖýûFS–£¬äÅÁŠõ$:÷%€à!9;0©‹+¶,¡@ßÈî=UA0OçA"ÙøÅ”B0ÀäAQVv)€©(•É€³å üÅåÈày%xȈ]‚·_2eáü%[”ç]¬ÀrDÀ$këk[¤î‘ÒÃR®F÷4x­¾ýYýx8 ÿª6°pç© Xm½ÿ³Ÿ½ŸÍÎÌÍÍÎÓÌôÝCP?ÿ€D¾ð9;ߌ¾ÉØ =ª¤¯¡ Õ6¢žvŠ¢(F\d«!K ž@ëŠvÐ(D™´T$ ¸†Ø¿,–¸±4E¹êåý‚«Ë¿R(Ö¬¦ÄV—½™šX‚ÙæL½º´8½8#ÁbµÖhÌÎLTÐT­zP&«´]@‚ŠX{S=õˆÛ^òüõÁliì Ñ9AÄ$¸Gn3qüºùé·“Æ_çú¢C¬G, ù{‘aë¼é8e}ÒCÚU´ J¢øü‘vnB4,#bˆÉ­%bY]ÛØØl­·i´ p2h—ß êµUq;'ƒb= r®z}ß'E û·Êb†çºRíÈ:<:ÚÚ6¦X;z€ÔŸaÀÔ#(ÀYOIœNÿ_W¯ŸÂW˜Æ~åÿN?ž–H¿’©UàÓ„”^"ãŸÂCÂ徆²)ùWôŠôÄûwç‘0}Ë.Ý(ÚŽgzö|!É@x±½(¶$¾ß#&‚E:›aƒÏPÖê&ëÛêLJ1á„n1@Ÿ·»³ÈMÜ"%ð¬1;-A~ ¡rüóO[ßfwJ¨¸^ÒûD¼AQÇ’t;ÚjÑ’”V*$ÜŽZŒXkm¬©¸„á‘»\Ǿ€HKœàáH¨‡å/;®_uòFPÍÎdldËËKËÁìl½žÍTç²õrmn¶!BΪ‚€ Á–íJ@&’(¦âìlÙ»vþÔõîÑåá].ùPc‰®ÎµãIó“4þz×—ì ™¹o·ßt\3´ÆÿÚ«vÜ™ÊGw”а Œ³_±¾ÉA·¾‰}9¾“!! Ž pÔ =¼1킽êõ}×!€Í~`ê QYÌ(:NÅ©™þñþñÎ}äJê¬K,.ñ¼¬Dnè²öÇÆ°„å rÈß#sˆï?&ŸäQ.þé@ì†ü˜¦06V RbPä,ÍÀHÈ06æ¬&àv¼1_k4~씼¢0¿`»ù¼-ËÞ±A êyfÉ™wó•ÚFØ6äù^É-‰aò jÓSiy$þ´i$"µ]t½¾ýYýøžø~ÿÉ6Ö‹»Î{”—F ãˆvà§?{?-‘ÁOúmBNÙ¡ UiÊ£ÖíÇûŠ`øÌþ—ÖO1­ÖêÊt#66Z­Ø œ¡„"lÊ-,K T)k †4>»ĸXþUWvÇôÊ…¼!J-› ªõÊÌÒòRP®W³sKà¨g²u J àv5O*ÑBÁ-ùZ˜lÙäN™ Ð^?P‰()¨Mó争NßÒxâü$¿Æõ¥D ß)ö – ßtœ·G7R±Æm4SÑ€@à¥[H¼p_›VB•·ØXÙÜ\!åÚfkHÀ“SJ81f››ë-± НXâ¢`ëU¯ïbÞÔMk ,f”œRÅ)T>ûþ¬|–U^#0Y«8\ócþâÒXº¶$ÏÀódÉÁ‘sáÁŸCÕ¯V…À ÏÂjˆà‡œº€\®àxâ 0N qnn~AœÛ,÷Ò“†Áµo@^™ ¨;½8£ÊrBEY„¹^ß¾N?1eÈkÌœõ+‘ÓëPÅŒ¸Úp0º´.h›c~šu_µ=¥-ëjŸbD¼>‘$¸áÆÙîk ŸdÉ#¸¹¶²¾ºÚRwoSâN 8r¦a ‰GVÿ+ò¥ˆç¿R”‰qk(ºŽ'‹BlÀìÔ¤œ R™ [of+ØòëÕFµ¾TËÈ·A£jB#ŒÊON±èy]«œôÊäË:aþ4,ç³Y—ŠºÓ9óþ“ƯŸ¤ñ×½¾m¯#ìzþ›Ž“²ˆ! ¼ÏžÞ èê¢3Ò;GëÞ%Ê¢¯¸¯vÜÞÛ‘~eM6ù€“Vÿ -|­mB¦B À™£+‹FW¾¾_”o’&Î.Ø*n¾4 ïþý­ÝíÝC;xX)ÚÛÍF¹4éKÔ­ß›’@ lØ€L}LÿÉÞ½ÔðÇäci.ó—b¼úX­.?4*1xò“ìç5ß—‡äÁ±ÊXM\‡†!›þX£îa| ­ë´øl&ðÓâU°Ðàº0y[¶ÿ…ù\Á·§tClSÎW)³(Á¦gÓ÷ff±þ!“‰í«TŠˆ¼ŒzZû IŸ­Í2Ôõúö—èÇ÷T™}­)áèý#]XWEëDlEÒÁHŸõTü®?Ô¯ç«ÒùEºV9+úй§R…!Zë­U¹ÂØÖÄ!+Ùæú†öUèl°·OÌÈæ]§OµŠ@uÙZ¾Q+ÖÊ€{Ä8•ŠÓShezëkcÙú¸Ùú3µòL5@ÛV |X“k'€i–© „9ôúÌ´‡¸ÂÅznû¤ù£˜¼NzßÈ„+ýÇðý''ÌOÒøÿë‹â 8E´š÷¦ãýŽVýú}¢r D¿HÜ9'ìBTö©‘Î'ÒºK«µ8_VúÊÉÉÚYa\ º„xÂÊZku GéÔ+_ß/Z Œ—Ń4 hxÁöÖÑ‘{´ïÛg#åÏÊ>3H9)¯(þ~}¬Œ•díʇ,Ú@ü{iÌ«,É®ï×ød˜lŒˆ €õ±Œ âh𳼂†2Šjc ü 1ð4È0@«¦[ 6 ë›~©x£Tv,ϵç) `L&3;eÌÈÊ7œÁ’4°èõ‰Õ¶02ÆG×迟×µª@Ú¡8  v`h:ƒDy'·DÌNÑ5Äoé“êi _OôjŒIÁ•éhߢŒ3õÚ!¹A:îôõãÀ²Çs¶JÇ! h÷wŽ÷ æAÉ,xŽ8€4JüXˈþ+^Q–fJöþ>dy{,qlóð$ ƒAÇü¸š¿4¦h@Û}Í \y¨^­‹Á¨5êrËŽbL-B¶qr†Zw’pJ~*¥r…æm׳r醄(&Š{k/H°j¤ÐÏÜžµ Û°ÛA@ôG¤0·X¦8åõúï×èÇ3C »*§|–Ãp?Ò^W¬€ÿbT¿^Õ")©À_µ ¡âU€ëÚ’o…ªã6ÖVWZ5tÿùEÃøÚí{“c¹¹¼È¾ër¹ZP(åËŽlýÅ‚ËB`¡X ÿ&ð%5ñµ±±;²à³§{µ*.A½žà† VPl `ñàõô ³msõ“掮ÂõXCA¶üìûOOšŸ¤ñ7¸¾}Õ~³qE9õ4ÿÈ¿µ­à4»çÅ]{çÅ_OÅac°2:ÅÜ2.&@ À`TЦ¸òõã€ùÿ=@5 hÔw<|è{»Û{Ä4Ä xÿ“Á‚¬x“WA¤ßµ]“$ý%˜÷d_j%‰ÆÐ5\“¨¾â§|$ÿh ä ž‡T)XBôï׿ ÆèoŠ]Àî# ¯‹óÉ6€º4êtEn½’/›‡ç!Ÿ„Ú¡×@m ¥¾ ¬Ê7$¦o :ç5uŒ4–dÅ˽ì/Õƒ% /¨ù^%†á+ˆ^Æ÷Š…zÓ(¹ ²ÔççÅ1õå½Q6ƒ²WB×±ãÌÛž‘™6 Ì7;53“7lpA6Yv0vŠ3¯Š]8R$0·gݧ•â’ù!l"(§‡ƒGqpKƒ ” äþ•úóíÅ¡Ž Ö¶52g=@…‘¡ä ý¸<¤5#€¸z§|ä+²ý#ü—ê`£ÄâÝýš˜€)€EùX^bK æ.¸²XþE~šžœL¥¦'ÆÆRc_»Ç4"år½‹¨ °òT’“›ýÀÒÞà})ÙE‡E/î§,uµÊº3k%*pߤø£õïKoàˆñ:Jõ£70ËÖýNœrï±á†évò^ôÏÙKöOGnpÌy§Ÿ|ýOÏÿÉ¥ç+¢X©>œ¯§­aa”thI «ºàPEÍz± ÛHµaºž\|þ ”4 •ìD»[Q+ÜÐ ¢sÿôúÚ÷ªHWMyØ9ކ`H Æ!kçbuac»Ò¨?¬@£\.{…"[õ*¿20;¨í ˆ¤}& üo’˜,`–øÑ?KúŠ à TB‹lí)¤Bß÷â¾8)¾BFì JMóç‰`tK^9°Ê7 fP,XyñòöB®hdÿ|Ý33³÷fm (z™³‹~¯K=žÉã{]Õag–NùEȘ>ö´fÛ5¤ƒ×ÑÒN/Q^©?ÙÒ¯jZ+FˆÛU´Ç@]Úý¾Jdó®ƒã¦‰Ü¡ '`£µ²)ážØ€zÅ›p‹EcÚ˜¼ý§o§2F³ÚlÖÍæòÒRÄ@~¾ìº`ù‹–#߉H‰÷?9•»ykòOnÝtƒò_Œ€¦šÖ Úl™d¡w·ëxõVó­+Ç.1f$À`jJ5¿èf ]Xí>ï➺°ÀY+eÎYVÂ'mm«KÐ…µOÕIÎ×ëÈfØ.Ùøc.> ¹òúÿdpý£è/¢<ª_D IDATÿo/=Ÿ®Ì‘ ·òmâÕðæï¶6v€LŒup¸ó«WAÐ" ,CCÛª<~ê_â@NI6ÿÕ• †ˆíAÇ{ˆp7!‘{"ḑ0Nüˆóxo¾€å$^6ÊÜÅýC'Æ4«¾Ü•4¼qæÑf´Žr@­Â`]»Ó1ø=BŽ ƒ.æŠúõ©8¶'Ò7~¾¼à @;€äÛI¼j†VEÁŠ˜™R!@nÁ΋· ÅÀ*Ü0«eß#ÙqÁuÄxÙ¹”¸ÄÙì샨ݚ–ë3‚Ä27 ̨ j–…Ê…) CÉ6Ë]±êB‡÷®1·rTV°RR0‰sxGç“\ƒ$ 5QHÌH‹Ù}ü:àùèÑ(Oy$ ¿ΧÐBâŠ!ÓHH.†ØD¯ºþ;¸þ¿þõ_üÏÿùß\z~ŸíNð{ôþR@DÙwî :1¯æ˜Äà}UaÑØ±Ã©Ä®²ÝˆÝNln$ŸADÊ!@³• R}U »,+oR— ‘qñ]ðüÒ4 &‘xâº;Æ4ê%/S*Á_Ä6žfƒNøsË3Ív¬`lê-Ø)¾ÇÓ-ŸÈÞ 9äyß­ƒAÿÈ(AŒŠ"‹Ùè³ö ¯šª qМstµË“ÊUë†Yö\y óbLg¾ddþ´æÝÍfgÄX8-r¦éúD_¡Âð{ÀPÙë’EEå§ŽSÓðzôܺÊHTVØÌ’b0LÒŸG¼ë=¶‰[goÌX©l¶!Åâéç"mg׸áF£eœÖê&Ð â B€Tv2===q÷OßžHSŒ¨NöÔåšx.vÿ²+ë±Yq,W €‘2RcrûÎ[·oÝû:ÐØÃª²þë^­\#¤°Œ~ÂÊ€3Öq‹ aÿJ¿‘>ßV—Ä´TÛAíý–êz#P¢ûÔ9mÈV5ßîù2V‡Vl‘éž+c!ÒÂFÙ!ç?Ú P 5cdÛµçãIˆ¯äª"§`ÙÍ;è¹üúÿüoôúüë—á—_þù_Èù3óóËÎww2Ï¡±™ÌÆqa’åÁ„ûBˆªâÒ&+JÈÛ­§êÁìa#Ú¨Gexv8â·ab¬P>‘Ú—N›š˜ÄjôÀŠÒik°aÊ:£ãXÃnA¬|q²-Û¿,+#pËrÏb©–-w»Y¯ßõY¢Ó„ï.*ÂVüô³ Ä"Nk{_…€Þ Û4‹WIË“ø÷Y Œ·xööýhüÀ–b—¼ ê1àœiv—‚RF<€Òüp¹»^  دüñbæ‹ÆäT&;“¹3›ž5¦ß„òTíÜD/}.nS4»Dl*.œ-ã@ôroµìé­ÅúÍsˆ.>úÀÄjw’ôçiKyAVò Õm}%0µ‘ “ÃmöÒFœr@˜`si€µÕÖ ˜ÈNM€¢X¼€¯MfêµzMš€J^{ŠNeQ €\ÞŠQ7 Yþ7¿qëÖ׿®,ÙZ+HÚxår­Z©yU»j½—'RÔ)H4Ç·¯< qŠ3Œ™Ui6 v¦ÑjÓV‡hyJeP¢-v“ÏY@¡ÃT. ª$ŽÇ@daXèìbw&!bL°Õ ϲÛ5…€åÍÉ¥½Á—]ÿ™÷~¦×ÿ×_þæÏÿðçrþÏ~ò·?»ìü¤Ebï@EÒe„ßeðqýý¡ÑK_£ ˜D3qLa,hg‡š<‚wu«.•€h¨„‰›z|ólÕî¶ãì*¦¾{nÜ Š~Áµÿ 0¶0qÄmåíBàÚHJöJi¹52åÏ“®McYbÏN{^EÛÉ?7óàùZFœà«k_Ñ%÷`Ðé£i¾JŠÛ¿ç§ÅÍçË2(åËLVÐ[ŽÀ|$‹ó]íâxTÅØ…rɵPÈ»V^B€Å©ììÜ­Š‘‘ ÀÖ P*kk6B„R”h›ÍшüH¿˜p·ã«~ÌëÊéÓ¢M)M8,BÞ$ýyÍ^‘›<$ K‰Y°u²ÿ•-ÁíP_3†h£—nC(g‹ÍÁâýonˆXo B€ ГÝKg2ÓwþôkAµF' ¾´ØlVl"ÅÈ÷nQ<€æØŸ¼}ó7oŽß~ë-€)P€“¾A¢tÔàH0P/W  Ó'{DA&[ù9hDéçÇ6‚ÛØÆ¸Øéš÷4|Ò>=бó­iê…PÊÎ(”•9Ovì -€LçÝ!”•òšØ{X4!ý„XrÅSëúó©M¼Kø?ÿÓ1“_O}µË®ÿÌOÞçõÿäñ÷ÿ×¾üó¢?™ù?òþ¥çãò1ýˆ‘¢ õæ!,¹Ÿx§ ŒŒ ÂÊàLŒ4ã–S2„³ß£©EB;A U¬j¼y2ÿöIF $í¸Vi„ö…q`ø'þ¿ö¾í7Ž;KOÏmÙºoͦZ]íîº_»ºÙ¤˜—@Y^?l6³²-J"E» ­iÚ¢g1 â‡Å™ 0ïIÖ´-KœìëôE&Á"Ç»˜¿ °ûÆþr¾ïT“ÔÅìI0çAeYj²ºª»ª~çþ&|:ÞF8 ÕXœ¾ÈòÉ0S §&Û¢ýb““]ýâBœ‘…ÅQ¾מÕ|­â+] ¹ö…Tƒ é‚0hãà “~$–sE>I‡‘'”CΪH@ÛG@âú°æ9';S¦ï›^h|?{CÀÈlzAŒ"sÜ Ö–0"°à¥µùÌ2Þ`(oÔð{$©ÒÙ4ZÏgΫ§é|¾bF—Ž.ºLI=}ìüyÍ?µàæqö) HôžÚ6-<§Æt2rwe®Ñ£Í—Q¼tå2ÀåËsI„êäóùé‰Iq}àd@^@ÝAñϨD¸¹ÎN:{2wǫ̀¸c¹§)ü¤ŽÀœ˜~´mË“š ÆßÁp0Ç ¢6–IwR,£É(mÊ ÚÚ §e/­jd 1 XÉjkfARš\ZZ@Kë ™¥£oð‚ñˆb‘y7ÛÝ¡ÇïóŽÓwËFïBRvÜ>ëù‹Ÿnã­Ÿïìnîl¬ï¼û^·øàÃûõ‡íõÇ33ô'ÐÜF„ÉkÎ.ÿ¸õA†wº¥œŒB²ÒÌÆkØ¿ÐÓæS4óò—~ØÍÐ jÓö£®rG2®ÈšÅØ¡€ä}zâûaè9©Ub â€n¹åª÷²yõhÁÿ£c:Ÿ6>Hˆ39Ÿ‚@L³í¬Þ!+qæÀ£a‘<Ó„‘šþB†ìgQJx …ù#=O‰†È…@| ’P¾A0¨“0\Àƒ¤g ÀvN¸vPµË M#ÍG¢ÎÌΊ `Lÿ™CÑבëé¶3xõ>ÑæˆÿÚìGë·5e ¡C‰±R‹ÃÛHĘªM­«#†»}uxŸ?O.RÎy Ñ몽D´ÌQÚ£®À0f±éV“­˜©ÊÇÛ9Å ¸´ô*À€—áLFTS“S…üÔôÄôù™Ù 3"Ê ¨¾“)[¬ÿ©3cgr¹ÉÅGO½4:ž;›Ÿw?i(ïbŠü_S^Öç’”}b· °˜„1Ûi`òXu'“w‹ƒ0n‹D¶íC܃2Ò ä¯¥Ì†}€ö¨ÎÑvÖ~V(D΃N°æ²vVBf¡‚;!¡¸£J ælÈñä~…êq8¸úšx/Z‚Ÿõü‹þþ®nowç«õovÖ?÷½í{Å­/÷n®?ÚÜ8z<{Œé_ƒm 9Ej…6GsP¿>ö™ èꔕ¬_áJb@µt¸Ûll"W‹'Ù¾‘iæ‘Ä€4Ÿb>˜Uâ/2v…‘æú£Ìå@¦¢@ç"ðg Ÿ’B0:ÌF¤ȳÐGÚ ¹äüɲÊAB€‰ À @fèX] Jl÷„ï¸ €ïØS5¾¿-Ñit*@<“¬È@«*“ gE˜ÑÍ€"¬Û´FÎFÈù~†xaÄ éáV§Û¢sÕÚ2~?ëß× såd‡Áóîè^PÆb:|õt¸¡˜XMî´­¤G*ö:ülèÑmß×Ê,q’ KÿàèuZúñû¬ðhôLÂÎn@ö뙄2=ÄQX‹³ó´¶f[ $ àûZ@¾.´¦ ©8‰³I4ãaí‘` Ýd‹§E¶ÇOfÛ8 >5„™„¤Ì"{¢âIÌ}a? 1¢ó'UB‘X»ˆÉ6'¤D¢tVÀv|*‰ü‘c… 2 ya—ÙÙBáBiò»ð;q&Œó¶c:ž;æEq¿Q[v85^Û÷:ÄfÃâ"¤këAhGï9Ô}&¯²T¶¤m‘ÆŠ2^½ê\Ÿ–ÒP)^˜‹é1v«0H ï+éïS”N—//!˜kÔÈÉ"HF§¦óâòâ H *`f±. €-€¢šSâüŸ{éÔ ¹  O޼4:zî…S³µ”%À¤6GBFmŽX¯z{¦ •(ÏCK¥V¦XG3sƈBD%äE}ÎÀêh< ûûª2X̦Àv8£G'f”V]%®Ô2"‡Ùô)­4%K£Ù‚‡Z[»Ÿ•ë{C×™Ìò“É=@qìbð¬ç¿½]¼·õɃþç»_IpzçÝ·?ß(noo~¹³»¾·þè½ 9þ£ìø¬p‡*Û>)¼ºZ~ÛWu¿?t}P´Û$B¢ëGYï*¾¡££4@hë•áƒú:#,#zÚéÜjýeÜq”rOžy®Ö|+\‹}‚PÚü̧°ÆF{v×:1HYü¢Üé±Óò¿ü5FÁ¶^” «þ±vøà]ò¶”AÖ5ó¡¦ÐÀ4 ÒùÙtFVäŒ|^A@Ù-´@Ö[Nt"öM;òìr5tåi$·ëç‹¥ÉU¸lÇl/ŒE¨»ýŒÜϬ­Á{t[ds õ6rA$§ð¢ð¬ Ì£ˆ ¬¢YøûÇÍŸ×åÊr»¯ÓþýwX*–¬§,H>‰ˆòYíg:˜)€(Dpzrbjrz‚¤yŒ£°È€x1RS“££c/ˆW àâ™s££§&^|ɨÑôã`tq)€fÚh8uyv¶ÜsA0÷ÚÊÖÍfÛdDœ2¥ÀcBššAœ¦[ ZKã삃!"*ógGH- ǃîkk<; µdž«ßÒ©¿Œ’PêSv'DÅÃŽïrZ4kä œ£9Û*üÏxþÅ¢Hûoä]Ÿï}óùæÞÎÆæ{{›ßÜ{¸»ûåÞgëv66ÚÛfÇÓ‹c{8ÉÉ{í¬~ßÏ2úÃÖGÖZJr88Ìî÷5ÙÌ"ã>ç‡p ³ÎR'œ‡T‡$=Κ3õ =ŽTöñóõh.v×BW ŸÆŒ¸þÓÊðQŸñÅÇ÷c@†›xŸ8 rät®^kˆh‡H¢L@0ÕÊzD†ñ\nnn¡y:7ƒbõÄþ~¨xCqÄëO$†Ð6B"C$M7¬ÊŸ²íÅŽéÇ'ÂÐ1=ñýËn`•C׈ ·f¿»0¹úýÍïÒÙ FÉ‘5Ýìó\w~Üà˜,óäùÐ wmåz͉E÷K„Î) êP¯Oóik±@š†1†s¡W‡èö§ç^|A¶sòŽñ“'ÇE¢k'OŠÀr8ùr‚ÓhNòO¡ÝœÃ3Ä ²€H®ÐY”¥âYP_ \ƒ/€™UqN˜>Ì¿¿hß,¢­ SIþ»ïg¿?‰7èf9çù^ ÊŠHq¨cÎãÈZÙ³žmÖç18¦£TÁÝö€h^yit ;ßM6áÙʰ|üüy±0òU2\èÜ"zž¦|ÀQÉlM'‹È´e‘9¶íT“(Lü¥ 2Œ¼(€B>Ÿ/p:©„ðò³â¼`!õ7:yztôôK/QÄéâB´82:6i úW¯Guy%ß·½Ô K~œËâˆV®ÛGƒ g´›Šæƒa†¥¿Ÿ3c+`~øêv†¡b?Y‹³Ïô™ ÄêéÑqO–ûúÛÇö?y~#ðÜÄVkž™¶o,Î%áLš'ú?Ç7êx©Å V0à'(0(Ì…¢E–¿OžI!ã/äìG±„ýÍf¢=Dpô‘(Ð!RÏû]¥ª’ÆL’ {®(€²H|µlz¡8"ñ',fÄù·Ä3c&ºq#únö»éhæ»Ô˜)ÖZ4@àÃa¤EÞFªØv[‹ôÄTf‚„6â8¨u"‰>ër­õ»ª,Q•gJ‡qã°ùóJAŒÄ³‹l”áx:ÅX„¢'‡|bÞ6ë’ÕœPÓî“a‚‘Œ†Q(Lç§ffÄöËŸ|^ÄjjæÂùéñr¹scgGe;•y L{[<óé!5äuki Ìâü;¡ãˆç– /…ý·GÆ|?šŒÁ¤OãÚUÏšå}µÿ}-÷C¿µÚ=eæ@ì¢z¶íìíCA˜ÎÃ-aÈ)#ÚÈ×;2Ü2‹6ìî¨À÷á·9÷bèñ„ eM‹L¢ øÞ¥èh©r‰ ‹ûmýw©ÓcðÞöVow@ ¾ø¢øðáîæîÞæg›ë½[¼ÿ÷ŸdÇs~s»“‰ ¦q}úã×GA §Õr–oOYCq¯;ZüËG±Ó>èrÄAŠýåE·‰3¤xjxì‘á²ß~‹ý˜ |dÿ“ç7@oܸåøþ˶g,6ò!¦L3Þd‰X@õ‚ÒÅ: Œ•þ†‚‡"úA˜^8+:$?EA£.ÿŒAÞãæ¼â|q¨ü<Ë’PÄi£ ÆEØ@¬„``H³Oˆg´(Xßr£JÙö"Û:a™näÛÕ²ï9•²å×f¾›‰óÓ7–—%0¦K(cóB1R¶L£d£õEŸ­ÂhÙÕܰ÷ìteÛ ¡š=Î`JU­FWy›ÚÃæÏg|ô]Eôu©‚Ÿiõ²ÙolûÔ®6kRS·5¥!c‘ç™ãƒñÀ÷¼ÈŸ4DâQ˜„#ŸbJ 0S( ¹Ü¨þé3£##¢Æ_X\¬/Ök‹%ˆ#®+@ÉKSãNéÎ;Nœ"PwܪiޏÜÚa¬Mõ£J&ð)SL[‚üžî¼/{ww¾0º³±÷Eñ³/wwv>ÛÛØÛ\ßAðï´YÙã :&¬‘°ôÖ#ðþ°õA4® ZŒ<µý])[&•Úg7*¼H[LÁ£ú—ÊÜz|üáxyЉË~° Ù?8¿2ж÷4Z¾¶vÛ½eºfÙuá8!:qâ0˜Mi•Ñ£WБiʆ €xXÂO“±B€8¾x:· fŸ„_A3n4DÚs§1[Nù'¿Y@ù9ç[†“´®@b…©¦ÀlÀ)Ž  |«\‘%Y©Jàš¢ªÐnµRõ<×4Ùï®MMÏb‹Œ¼xD³ŽŽ¸ µ’ÏÅééX ¢ÒcÂwíµ3r*°ß·uºP†Ôžß¶&[q‹YYÆ*k³Æzìüyš,©ϙ.C(Ë\&–YŒzZ)‚]ƒÝEºÕcrà æŒùÈ€ ÈÏf¦'òùÉiÑòrúüÔD.79zzäŒè‰N‹Hk‹õ†!ÂߨɃ­ãO£^Šr¹Ÿ÷mæK¥;¹\R*mY¦i³$–݇ͺc¡¬ ÅÔUšUÈ^*ˆÚG]€(œ>c×ÚÊúè%:†rÕebzˆ ;¾¯`?¶è´t,¼õ~ki)#í_ºj€ñ¡üß/~òѧËóßÝûjo÷Œx3ßl>Úùl÷Ë/7÷vÖßáþG÷î+#(2§Èõʉ}¥(=n} 3C`[³/š/x†‚(ào9PèÛ.ç îÏΟ1Šû­ èÜZq-˽zm±Yó¦â©i¸F¬Ø¿(-ü.Õl~žMû ¡½Ìÿ‘û$LEøE“d!GB`$áÄÉÙ“5í–›Š?S%Éç¼ 0Õ‘b!xØMqxXdºž'âîBþ«PeÓ! \ö|ߘJŒçóSçN¡xºdš€Úòö€¹Hü^Ö‘C‰~Ÿº™{e–AÔ–ùŒXgÖÌÄsVmÆeæ«7lþ|ÙšnWÑïŒûA‹¼Ã\@¬aÊXFóÐî0ù¸ »`tžóÜÀ çc¤ÄþOLH00 lÐ4º"Çs¹±3£/18ùâKgkõEè9L¢8±ÿì×J%ß½S*åJï—BqDþ½8½s'.9¢\ð DÖîÔ–"ßaQ5°ÊA.RÇ'qꢦžŽ¸ ½g¹¨Ý̱ègM5è”AÌÏ«Í8ìxxèñLA ³‚h[*ç†IÀ¿D ÏÿµŒÏåWD<(~ºuoKÞº³»clì}óÕ›{ëv6wv÷>ù_gðAG±Úhê€zÓq`m&¹¾ìéõ‡­á!àˆD |fˆ•…`ÙÈ ¯³ÉB‡ûõüƒ‘B_ ¡€žµúºyõÚ xÍ4™ÊK²–½$ÁLg$(€j ßdšú~ºprtüìɳãò×øÂ™›g^x!}A€Ex<úGM‘` IDAT[¢æÙÜÄÄÄÈ‹Lÿ×€) 9J“Ù<ÈóB¤ùd6™Æ\€pnŒß"ê–„ŽYVP–E¾S©TLã†Ýff&ÎÍ‘1- â³% P‹É\±ßÎ&ÈR9râ~™cé(u"*·}zîšA½îØX”ãüÑãçϳ ê‘}¥Éhgþ'ƒQ}Ó±I …ÛdIä'8–-ð÷F,@ùJ-,ˆ\òS@ò€IL0=ž;; Ü¿h€Ó/¾ôÒÄ‹‹†!bßLÅüÏ¡å§VƒÁ¿#jÖ½#J —Í/…^êÙ¾_bÐ÷Ð Ì/Ý'kA :…O«pÊyëÚíêTÉÛ¬R?™„z"IÕÕì*:ûZ!SU ó —gHlØñÊý„¬w€`À–üÈ/ÊËCŒÏ•KˆŠŸ|xïþV·÷ù7»ß|~ýË/®o¼~uwóáÃͽGëÖ7‹÷¶ïgÍÜm^H_ùö8ªüp}±ìxüúš–e¿qWÓØÏL²RŒÌ<دç?د”`u¯z‹P@Œ ³Ù"’³4Ö I~*ÒØiAÐõ4Ø¥sÄ÷a›l,NŽÊ¿sb~Dq,,ÀH¢Íõ%QcDßFXq ¿0Ð|)N”N4hfj0L ÞŒx"授 @Ų½c€”cÒ˜NŒó…™ÙYã{cB<€*HOmST\€Z+BCýqÞäPÙ4óš¹Íòô{dVo±X ßYµÛ™ŸûÃóç1’ÝDA„ñqœYÑëgd’Ç–/ã “ÈÅr%´1=K´€è¿h~~QT@~zjzrj™€©É©éÂÄÔÙÜè$NÉÃ8u*ÿ¢¨‰‹Môt5›Í¹$O )…"ö¥ÀÈåJašË‰F „2n ” .¦ƒ_‚FŒS<÷9Ô xh8é®ÒaL•1¤¡Mº«7oXJé°óJ“Ú¹DÂð2Øã´ìp±ïg¼LYÞB5T@wé(ÆçÊåâe‰ñ?ùøþo¶––v÷.Œî|ùæ›×ׯîíì<ÜÌïìí|³Î:a[ ¹èijgEÂáÇ××°õ1´ <¤ JB‘Ž–g”YeØ ?2?X˰<ÿ¡XŽ›Ñ[i`Æ„Š!§õ¦t1¦ODøƒ0O?Y~ª008kb“¿çÒz£i,@)nžÎÁƒíò¢±0×hÄiÆüA’*xœäI º‡IÁ’Ën@ÙDØý@ì_ À2-7 ]Q¢ªÆòwFQÖœüxìüyÄ¿˜Q„·÷3@Ÿ–n»ûÀ GˆýÁ†âZ ê²$\gK~òý/L."0==ƒ’ @A… ` `ìÌÙé’Qo.Ì-.,Ì/,,ˆ"h6“TbþóçÿZd~½”ÆÎñîiê!/Î8ÇEA(.ݵŒsµáœ)©!%¼•¤ÚÃ(ûí¦§›UdaÉP‡e pfÈñ:%Œ³9æhe,arË3 pãóêÒR±øé½ûÛºs·øÕ—o®nìl¼¾!1ÀÃ/v6Ö‹;›l}¸EÇ‚‰¥!ÒG9ÞÖ×°õ1 6 ²ßÖtHïY@«ëë#ÃÅUpv~Í@$A#tãÀJ ¸8W #8’zÌ(˜?œž‘³h ãI@‚‰2Í‘ëæâ‚„ð͹¹f*: N$(˜@©” =! °*{PM'ƒÎ68K3†E7Pñ Ú*_çTEÌÁ¡·”ø>ôÀV¹ÆJdLÅÆìD^Bñü¬!& óD0K ´èß@¼Öf·¦°ØÓÒk«¯Î:Ø Í.©¨öµÉ‘î°ùóœM fhm4aûŠ2e`‘vúá (§˜}DZL\ô€YÁX;qçê¡ …)€‚‘€˜÷Ç‹/‰øŸ10J Ùh^¬/ÎÏ/,ÊsºØœ+‰üŸ—àßaP¢Ð~ßÏÿ,—‹PE=@¹Àtf'{3²¦:;äé²×ú Ùk†AQ©m™^ÝgGQK)Gzìà'Vúx(ì°ãëQ«Õ’D}6hJ\ÌZ| ãsùÊRñ“íO?Ù¾¯;¯ŸÿêÖ£¼øþëW7öö¾Øûæ‹âÛÛÅ>üd{KËh$Hc· Qdm…lµ2¤Ø1ëc|º£TRÊô ¨µº^¯\8¢Dàuÿàü:wøëÿnÄ^êÛa`'ªââš8ÏÚØÇéqò»´à縗~ÐH/ΣoW)Bh°åå¼VúRÅô 0˜LÔcЉÂ|Åšš×þ@¶¢'q¡¦ã4Îc”pšdoƒ @ê€¨Š‘8Ö ±¡[®Ø¡n†n¥ü„1=:3; œgUÓE>;ª°ŽE‘¦\Ée+MíÊ È~шMî?eS&º”ÍüðE[ZH |dÈüy6Y¨çÜWŸ^1ò‚팭áØfA3‡hNqýÛ“‹qGFD«™œäbC½Uk^4—Ì5“æüÂE£€éÄ“ù´*”O½tzüÌè7õtnaq¡)Ò¿Ðl^œŸ¿È¬¿øþâúË«ŸÅq©”¼_Ê•Jw\¿T²·ß ˆÎö fW%CNI‡,w™˜ãõéðdÐb´†5£¬®5³ Òá·}roµ‡6à ;Æ/þgö¡'‰ ™‡E¾è\¹reé(ÆgC€âƒí­­û–0«°»óæªDo¬¿ñÈxøð›w7¿`’à~ŸÍ<]2’¡"¯ð.øÑWž‘!ëch3Øf¨ýl}î³Ûòéf+Öl®\Äékª€,ðõ`¿œ3…^Ó"À׆o%žøftÓIÜŠ±¸P túgcBú¢öéÅÓJÛ+ñA{IY LÉÄß‹/hãð\¡E™ñœÇBùƒÓ´î«tÀYT‘¤ó[°@–JB3AíJÙ û"Ö²é7ß7ÆGfÎMùÑI#~frÚYá T Z-Û#ïóÈ ÆF ÒÓtZÊîNŽ5®c\}Õá 2é¾A»Ð4xüüù>½ûê;ktL-©¤H.y|;hÖÎ)÷KÄ_Œ¿cî‰[þ_åßÿŠéÎ6Ó€–—Ô=Þ³ù‹Í‹ œ–0€ àœÄÿ§ÇÆÏŽ‹ùO£ZT @SŒãâÅùRè—~¦¾I^y%ÇGàŽ+OY~ •(ͼéìNdöµž]o$Ÿ¥†dÅ«“E"ëÍvTíƃZzÖÑÃûÎÌâãíªO¶Ã;¾ßÂOÛ$ô|¿¦œ35:¯â¼úÆgór±øà“â§Ýô­ËWò;×oíÝÜ\Þ`gæáNúî¦hˆ?Þ~À±5™…ï*8ØŠE€¶'dtèúÚ>¤Z¡½®BTŸn·F;öxd/_iQhàëƒýû½%=y¹Ý—ý†wíªk~5\³b  ŽzŸ¹žŸG ¬¼“Ö5ûÿï§N f_P¨ÔÁŠå¾2Þ䀴¦!€"‹Å½÷ý8eéDíÿWRh•˜ ƒl$™â‚C(1½7Ë"û'œÐ8øÚU»jºxἨg#?6i\˜š(à;±­Ûã /ÛàÀ h+hKbŸŸ,—ûÉ y±«P_ƒ‚û¯!H2^]?´£).¶ÕÒÊÃá}¥’9†"f ™ÿq‡Ü&– ÀÞ˜eCµ€òжkuìþiZo6ç/Î/†(€é©éB~¹×e[ϯ®î¬|uýÑÎúÞÎ#csGŽ/n? p+ƒþ`òÊü\GS{p5Xe¶>† !DÙ׌‘Ž>yáŠ|×.-½reé’ø:¯½ 3?Gö·0wøÊÒåW^¹ò5<ûƵ«Áë•`­˜eôDtÖÅrÀmŽkȘKØI8ùïµègõ(zóìû©I@‡5yA^ÖjÒ…€=¤–À$ÐpçÄÔÙ9F9º&ï=Òõ8Ÿâ'9D>#Ê×xľK išÖÕk>z¼ð„Väß“o]~]€„³³²þÅù/LÏLÏŠðÄ[!qM‹¦ Øœ »A[PSÞ„žx¯Å§Ž¾;Û[‡¢ù”°‹l+µƒÌrÿÙóçÁ7£ c%Õ,6+ãl/:žJ‡$NÊŸñÇ´åÊ9Õ ±ÍvWÔZÍ©ÕÒºü'îÖÂÅz“…ñé³âŒŒŸó/ÒßP×*_˜¯_\hÌ5çæKýÓRég¹X|~¼*á•ü_ZÏoþõùó%hL?Œ¸°¯¸ˆbëžB¡ÉŇ¿YDéÑü+ÒBÃ÷c)©äÙEѤH'½ô0z (Ò½µ')«·ˆÛ°ã£qäJœQEMþøã´ Ø<‚ñyw§¸ý›âo~³ýéÖGrG×濺¾r}ogãÍå½GÆNŠ:áÖùt{K4SÒâds‚rÚZþ〩Àôv·>†Q £DSÅ@~)”#Ÿ¢\ÛçÌ×.½úÚÒ•¥W^ãtañÿöËùÅA¸,1À¥%è#pn¯-_{óÖ\·Š@Í róEA>å+äëÂ@›t?ü7Jtr(ÈøN‘«hJÄgÆÏ'qàX‡"ô!ñw^næ ¤LtÄl"—„3É”fAÞŸ}Ý,ü‘c}‚úöë/_½vÃt}Ï®˜þ ‰\Y!¿µ(€éÉ©ÂcfjÖ8?=3c ÿWu*›6ÊÀŠB¨ˆÔ`xÞÀR º +L”ÊI>Œ3ÆA{€ÔJKéeÑK ot0àl›?Ï”3@&£MBš„A9\~Ž¿,m¬Xw„ÓÛüÇ('Q¸&×2ÜX##V­Z«“л67'Þ}õ€ÂøDî…±³âýF:Ø/ÿÏ7‹MÔš¥÷šKKwR“( §„Wè L/üló|I>ªR¬ï¾š£6g×ÕPSÿü¦„þ2–i³5FGúqÙ!¥ôÆ=ÂÉ«z)ßw\Üw}ÿ·Þ“¤•q”m!ä7ð‡?é8î–ܺ$5z XI¥ güì³/‰ñ¹·³¾¾ñèËbýŠÿðß¶Ó[xóÖß\”_^^ßYt͸¥Qô$HøèãÛ[8>‚Éð—¨0V–6j<4l} #…FŠÊ.Se]fVê)ÒUy ˜*ˆÉÒ`” þ‘ýr¦ËK¼úê%ÆFì½µrûÚuëå5Ï.›Æb3túQÌÊ-žüB­ žOŒEÓ`‡ú¢‡ù€NW~p~ÞzßEöå‰y5ya <ÄþÀM+„~^/>½îŵi:oyyc\sË)x "É¡g•íaµúŽY¹o @‚ßYcv|¸P˜½ðÝÈ^U$‡`@‘þªç9ô¤a>« ˆœƒ5Ãqê²ßBöÈÂsð£˜^Š/JnÁÊ€fô:ýaóçu0IÞSH óå|F­ýãi¡u1Ë&kuÀa]Ž·Ç Ÿlæ³ëKEÆk ïÆ|½¹ØD=`b<÷â¼ =Ü_“†Dà \@êÜë³Q‰‰@¼J½óïnbƒžZ{0!‹”¶Ù±æ¼•5úYû;[™© OñC´Ô"ٿŽÞȱ!^d‘ÌÏÆ’¶Z6OŸøqŽç;Þ>¸už…´V€ïÞÈε+‚þpsç#b|jIT,†¿©ÛÛ)ðù•/®ïH°¼^ÜðŒ3â;†^±øÉ§÷Ããc1UúèYn#¹Ho ¾û­aëc(-ü0ZônWŹy—OÒ®£[ñÊe¤3_{õÀ×—îït–®¼²´ôÊ+—˜ 4â[o½µz{õåêšÙP3<+¿6ûdØ BЏüÆŸ©‰$gÍ@Öy>p³Ä5s$Æ/м:ž¼E¬ øVäM¤üA>C êñ¡&çb°XÄÄ> 5vˆrAè:yQ. â û‘]©ž?tíê­Û¢VDŒÆ…¢aÌÎLÎ_ø~ÆâxKXH«j:¬ŸÃž`‚%ÜˉsÒt‰ùÞ9—ºÁ OµÊ/k·=ÖœX EoÈüyÓ q_ë=¥!|½×=v0D¨\›Mdº¨jŠýß³pQÈØ–[³lO|¥FØ^ òE¼/ÖELMæ^œ2”í7=º?ỉK6q®Ù¼x¢^úéÛš,•Þ;-](•Lí6¨Z4¾ÚJÓkëØ[æÚ22ý~6ÜeÊy‘õ ªfØ` º5žxizi"Ï ÑPåô<Ëjp…>pÇöä« ;·ŽÇË.p›F¢M÷[ ôîñù#ðpûCb|öv7­F=”,?öÅ?®®l^ß[Þ¨;Æiñ"M§øÉƒûoßÇñ>´JW•;F¸‘¼«¦¼Ëáã×ÇÐÁ0C£TÄì!oêSƒW´ns Í+¯¾v岈ù+í—óËŽË—ÄA¸tåÊ’!.FUVnÞàtà‹ÈÍÍ„ñt-š‰gã‹ÄSèCÊ˺üÕÃ|Œ4+É~cH»xûq4…B­èïQö ud#¶XĻ⠈´# N¨©_æê %»û âû PüÇ`šJ ß¾}û†(€Ùï£Â¬1=>e”JõÐÅÇà3‚¿åÙ"/Õ0…‹ |)²¬`E˜(Âú¢ÀCÅ@#Gòá)ÛlƇÓm F·›?ŸµÄIaê.Ùþ™+k3 ¨—Ñ>us~Óòþ¢¸Ú=DÿŽh/ñD°=VSäy$1L}ŠZŸ1“3Œ¸Q¯Ï=±¿I.àšD M‰J¥,ÈssuÈ\%WË‚ª¥“ašöé¼p=#Þïfù?Eø´µo y*­ÿ Mupi:œÒ³½*l;³5cPß®B>@~ç²:áŽývèñ‡ûmL8’@ó¾ÿ3€‘k“çÿÙžh€‡Å·Ö‹; û_5’ø·ø@ã§«_ýãFñ‘mçLÛÛ*n?½¿õÑýOy<ÈHÄ$ФPqŠD8tüú6®?d4\þq¬}B&ö3ȳ&#³Å&íµ+ÙxñW^yl4ÛàüK—^ípå²ùkÌÝ\Y}kíÚ²# sâêÌ¿ {\bú|=‰$œ¬Õu°IDµæj…9ùW~œrW4j,è6€à«gºײÖÓQø¥¸¥òÛ(–ó¢/•SG‘FN× 5×äWâgÔÒzRÐ0@Á3MS¹Y9Qµ¡åË6„øÆÊŠa`ü•nÀïkò°D@Ì-˜Ï*rf´#à»'–NŒ,+?K„úÌîÃK°H‹-ŠÓÅ)¥ì#S^ùžb?èI;žYMÆp™ ÷ *šP"ò ÿÀpÈ®øÿ"ÿ{{{¶µ‡/‹ Ž¢µ†ö"[‡¹&†z{uØxLø)'×7j‚ ÔþRÌúÀ¿Oì˜ÜW¹ù ÑsŒJˆæb¯”ÍÁJœ¯®“¯P&Kð~WKT-B|ÐrgOñ.į N9¸47âê\`ðh,{Ìvž^é`Z‰ý[ǃvö†¸ßsŠä£¢¾1øTžÿîÇ{{Å?úâöo$󫇸À›ù÷ß_žIç›Æn\±øéVñÁ‡÷19I“ÉY#Gý!ÄWi'tjÈú6vØpTN›îèø¥íP(ŠˆíXÙpÖL\z|8ëáù_Yº#ÄJÖ’ô-|qqîâüܼl …fS¬Ê|s~qa¤…Œ¡\̦Ñ-4gš˜F³85¿(ï¡]iÎ/ÌɱyËœD¥òkyƒ¼IÎÖÄÁ]½ ïš+,8ÑnAöÎ/Èñ ór€½8×ÄÜjœ}a¾6µ(ÊÍDÖ¿‰š_`WOT­ÐCROìÁê-qÝÒX´Gýûº7eÿgfËÕ °³,pÁP²Š0ydD–Œ­{Ü#Žÿ¡°\®e{ï±KŸQGÁ¼ÃæÏ÷³jl ³0ÄÜŽ>Ïý ù—ÅŒ Ìpvì=“È&fpø!‹§u⬠cÈø‹´ó×54Uüà~ÑŨèbB8þȭΪ²žgóá-?Š:Šzêö³à6#Ä&V‚“ˆ²Ó¬':+تG—fÈxjG/ml¬úŒ«óž_-¯Bÿ_¬jýÞÇóR~‹ÖFuÈCy¨%{ŸÿýÏ$®¿÷îì߈çâóŸ•Þ¿þï~ò†1[ôŠÅû¢ê[Ÿ|Š“ºÈuÅÚcÚÒ‰ß$ íqúïÐõ1t<üÐñèÙøx¦þØÞH˜PG‘ýÇÆ³/‰xr<ûàü—zÕÝ(zt- ÓÿÿÚŒ$Z_3MëšZð Dß;+«7nÜDØ¢ÛÕkÆóíùö|û¿Ü"ÑÊ••5qŒ ¸úc£Û5|Qå¯Ýºuýu/„ @箆Þm [ ý_·®^»a[On}ϱÊåJfð¡þ«Õ2& ¢¥8ð䧪é…4O^G)?£ûP̱„ò6´á?;Âòå­MAèáËEîÞÆ›B¿üÿ¼UL'é“xQãîú;²Ý¹ó—õÎÝX'$ûÉóý?îþÈu­jåÿõùú¡¬©ªí¡ÌkV+UÓEŠ (YN²ÊªÇ­<‰|+¬<= ]¶Æg+¯\…çŠZeK‹]ºiñ©Ìl#ªN‚M VÔ k‘ïÙ+«‰øfon¸ìÛV¥lêt(VÁ¦ââÝ?û°(ò i¾kÉûlO® )SŸ×I!$ÕFž-pm* ÈòOÛò¡n•‹®ìcÙ«ÖÚêíëoˆâx 1òo96jËrsQ)¼}DèÄ-vD)aÚ½h ÊàÔ(¾€šG~…o ÷"{^¦…ƒl\8î´éüÍÛo¯¿}çNîÎ߸¢ õ±[|vƒ²ùš“]‘€Ó´ãD±',^¦~hóÒT³®G¹%.Z˜c¬¿›«ÜnÿÅuY!º#žïÿq÷ÛùW«˜å‘ž±¹ƒs¶ü Lb«J¼WžÉ•caÙÓÆº¿çʳ2A™÷°³•¸~:”ÒÈôrª&°Œïª›¥ç’Ǫå&ñjl¿ Ö ”ÞEîpž޲‰ú™üO-ãÐxg ÿAŒ?úò<^ °cÔ£kå‡W-h3'»½{ÐXœh ¨ÕÎ4Õ¿w-«j¢Tµoy·ßº~ôB€ewQ^µÀ›W«ÕüâÖñ§¯!R_µå;;UP‰0‹wƒ¶ŸJNîf™}Å¢-ÖÀ;‚§‘EÁÛw³íNy*xZΊ„ê H¿EM&ßY´ ýü{»…rã×òMãèý»ë«ËoÞ”ë;ùYëwßO"@äžïÿq÷ÃPeVQlšóûU±ôvU”}UM¡E€Å/î>$ë÷_yîÑ•WÎVž…†y§b‹‘òÓ'‹¿‚Ï tƒÀLÃò…Öªë%·äÛ¼læVËH¶S «ââÈç…Å¥ËU;ÁWÞ“DÔdWyÎ*qfTÛ˜Èu#Ôvé°@îÆq(…UȦ(!ø1â¥d*À´à߯¬¬8¶B”ªÎÚòÍÕ×ÞTP1ëx.±ukÅônË \6ÍüÅûEëï™"[zM6¼Á-@0EA«ÈÍ”k¥ƒ"jˆù媗•ÁáµÄ?ÀÑ.¼\P ˆì!bÀáž;¶¿&_K4}bÈ?"vH"hõB×Ñn<9ÚL€%Q¹*ºðüzpyì,ƶéæTçˆzLÍU!Ÿ¢EMoàˆAÿÞòWW<Ÿ¥j²ÿº/4+'̲·‡þmÏŒW—yósåFL?| þЫ¢ËåûóZl'ú5¶@¿·h>uFD)¶þ* çIDAT¡²æíS‘W¡€*L¾Ã-àS¸´´ô'ò'oã9ú{zQð!½0à!xK‘‚¨X¾m2; ¤¦a~psåË—1ôÀuéU›Åøo߸x’ Уóù|ÿ» þb©}Êõ‡£€, Š,³+"fá#h~˜Øû®<‘"(¬%]å ÑSñ—yöï°¡Ä+º5v¹È7Ú¦½¶BDDÖ§¶Ža:×áâ³Ã£‡IÏ7-(—²§Lðªæ4TÙTÔø{¡—EqŒ9Ì€â!ð~È^’”Z¶h¹wvpÓ[¹y}åú›p¼²yÂ+Û±c>&^¿µvkÅY]Y½ƒD§åPóUËŒPL\Wýêï~õ«_ç~Ðs‘¸…¾3äü]è¼CŸ½ ·›<…·/ý›}éJnð°©·“½U¾¿h˜Ï“ë¶«¸yƒ;rœ_èhjWžÒ#ÖAêHà;ËoüÙŸ¿±Œ¸ Ô¡ð|ÿ»_sâÈ1û4›¦ë mŸ±1#mƑȺÍT_¶òð{û¹ò*ëÝô2#ëeÖ½•òÿïþ9dq飇ÂCêªÎ*K œ·;DFP¸Ù4@QÀK aÃl.qBBç ñ9pàùû,5 "&ù•œˆ(þJDTטù¡Ë§j#ï8IJ} H’ÕÕØ·ìxe5¹}{ù:j'vÙ;áWËÀàÈZY»¹&âïÿ(ˆÎ?Ó ˆ/D_ɽÇùó_üòW¿†Os­ŠêÜ,šC"ÏCÃä£AÁ± på)Ü}ûÒ¿ú×¢ˆß‘‡pw}}ýw²ŠoCÖ<]¼­ò åùDžs|?’e ªifHI€ëàüÉŸ¾Á¨ ×÷|ÿ¾Ÿ1º‡ SÖü¸8ãÖ#cq±!ßÿãî?ˆrIHç«ÿtÀn?W$¤59γdhýGZy"øXy–Cþ[ݘöÏò¢þÇ?© g yýÀ œ*|¿JùT ô "öÙª ¸²”z3èhyìûùE” (<èy B~C7dÖцԂÄÇgúÜ‚5ñûoˆ”‹î¨½%Qÿ2³å'ñý`ù}Ï?*ÕjePuœñ”K狨ÇbZ>LWn¼Y»¶AÐ8rRùf® ¨“¨tûHs: $&|šñ¾PFÖ åzuUGÛv³–MÖøÌ¿! O(O•ùäy´0ŸˆÔ¨G˜SUo; Ôpp.$HªÌ†°BŒ"‹E.o—Ê|ŠqœÔt"ÑeUö-«^ö¼¸¥ÉþÀ—å$Ô–A”Öäö<ßÿ£î÷޵: x¬Ggœ€ —”ޤq°Ð¢ìƒl³¬a²õZy^fúŸXyjä²l”Ê'ñíXf{Øl7<¸¥>ñ¦méÂzÚ@¥Ê?ÞøÙ.{ºæÉä廊|Ã…Ñ~âû…™]<ü~óTj ƒòÀ@VMfCÈ‹ˆ^{Sì== 9wÕtØÍ€ƒÂ&Zx¢„öªVXýÁ@6µŠiÔ'7QÂ^ù'l!>ÛÕ5Á)g¶ÕewTSñmìÒ"N„má BóOð‹NæãƒÒ3|¼…*ÂÃ-"*hÓ£Ãß„QRÓ©‡Hâbõm²Ÿïÿq÷“ò:{Xx*œè0ðVÑxü‰t£)V×þ0+/Çæ“+ïP!{“ +¯¦õÜàˆü3SèVL/+Hy®U¶ÃÇÞ ¡Lèeìy*äƒ6Ū~[¦;0°Ã2~¿è™ßϵÄAÆË¤·~ º”+ùÆ®WѼ"ȱ`p?þ%è«wG€-?IEND®B`‚fheroes2-1.0.12+dfsg/docs/json/000077500000000000000000000000001456075706000161425ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/docs/json/lang_be.json000066400000000000000000000001071456075706000204220ustar00rootroot00000000000000{"schemaVersion":1,"label":"Belarusian","message":"19%","color":"red"} fheroes2-1.0.12+dfsg/docs/json/lang_bg.json000066400000000000000000000001111456075706000204170ustar00rootroot00000000000000{"schemaVersion":1,"label":"Bulgarian","message":"92%","color":"yellow"} fheroes2-1.0.12+dfsg/docs/json/lang_cs.json000066400000000000000000000001021456075706000204340ustar00rootroot00000000000000{"schemaVersion":1,"label":"Czech","message":"58%","color":"red"} fheroes2-1.0.12+dfsg/docs/json/lang_de.json000066400000000000000000000001061456075706000204230ustar00rootroot00000000000000{"schemaVersion":1,"label":"German","message":"91%","color":"yellow"} fheroes2-1.0.12+dfsg/docs/json/lang_dk.json000066400000000000000000000001061456075706000204310ustar00rootroot00000000000000{"schemaVersion":1,"label":"Danish","message":"93%","color":"yellow"} fheroes2-1.0.12+dfsg/docs/json/lang_es.json000066400000000000000000000001041456075706000204400ustar00rootroot00000000000000{"schemaVersion":1,"label":"Spanish","message":"68%","color":"red"} fheroes2-1.0.12+dfsg/docs/json/lang_fr.json000066400000000000000000000001051456075706000204410ustar00rootroot00000000000000{"schemaVersion":1,"label":"French","message":"98%","color":"green"} fheroes2-1.0.12+dfsg/docs/json/lang_hu.json000066400000000000000000000001111456075706000204430ustar00rootroot00000000000000{"schemaVersion":1,"label":"Hungarian","message":"94%","color":"yellow"} fheroes2-1.0.12+dfsg/docs/json/lang_it.json000066400000000000000000000001041456075706000204450ustar00rootroot00000000000000{"schemaVersion":1,"label":"Italian","message":"44%","color":"red"} fheroes2-1.0.12+dfsg/docs/json/lang_lt.json000066400000000000000000000001061456075706000204520ustar00rootroot00000000000000{"schemaVersion":1,"label":"Lithuanian","message":"6%","color":"red"} fheroes2-1.0.12+dfsg/docs/json/lang_nb.json000066400000000000000000000001111456075706000204260ustar00rootroot00000000000000{"schemaVersion":1,"label":"Norwegian","message":"87%","color":"yellow"} fheroes2-1.0.12+dfsg/docs/json/lang_nl.json000066400000000000000000000001021456075706000204400ustar00rootroot00000000000000{"schemaVersion":1,"label":"Dutch","message":"13%","color":"red"} fheroes2-1.0.12+dfsg/docs/json/lang_pl.json000066400000000000000000000001061456075706000204460ustar00rootroot00000000000000{"schemaVersion":1,"label":"Polish","message":"89%","color":"yellow"} fheroes2-1.0.12+dfsg/docs/json/lang_pt.json000066400000000000000000000001211456075706000204530ustar00rootroot00000000000000{"schemaVersion":1,"label":"Brazilian Portuguese","message":"15%","color":"red"} fheroes2-1.0.12+dfsg/docs/json/lang_ro.json000066400000000000000000000001041456075706000204510ustar00rootroot00000000000000{"schemaVersion":1,"label":"Romanian","message":"7%","color":"red"} fheroes2-1.0.12+dfsg/docs/json/lang_ru.json000066400000000000000000000001061456075706000204610ustar00rootroot00000000000000{"schemaVersion":1,"label":"Russian","message":"99%","color":"green"} fheroes2-1.0.12+dfsg/docs/json/lang_sk.json000066400000000000000000000001061456075706000204500ustar00rootroot00000000000000{"schemaVersion":1,"label":"Slovak","message":"91%","color":"yellow"} fheroes2-1.0.12+dfsg/docs/json/lang_sv.json000066400000000000000000000001061456075706000204630ustar00rootroot00000000000000{"schemaVersion":1,"label":"Swedish","message":"99%","color":"green"} fheroes2-1.0.12+dfsg/docs/json/lang_tr.json000066400000000000000000000001031456075706000204550ustar00rootroot00000000000000{"schemaVersion":1,"label":"Turkish","message":"7%","color":"red"} fheroes2-1.0.12+dfsg/docs/json/lang_uk.json000066400000000000000000000001061456075706000204520ustar00rootroot00000000000000{"schemaVersion":1,"label":"Ukrainian","message":"52%","color":"red"} fheroes2-1.0.12+dfsg/docs/json/lang_vi.json000066400000000000000000000001121456075706000204460ustar00rootroot00000000000000{"schemaVersion":1,"label":"Vietnamese","message":"92%","color":"yellow"} fheroes2-1.0.12+dfsg/fheroes2-vs2019.vcxproj000066400000000000000000000046061456075706000203430ustar00rootroot00000000000000 Debug-SDL2 Win32 Debug-SDL2 x64 Release-SDL2 Win32 Release-SDL2 x64 {DD8F214C-C405-4951-8F98-66B969BA8E08} Win32Proj fheroes2 fheroes2 Application v142 MultiByte fheroes2-1.0.12+dfsg/files/000077500000000000000000000000001456075706000153435ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/files/data/000077500000000000000000000000001456075706000162545ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/files/data/resurrection.h2d000066400000000000000000000732071456075706000214100ustar00rootroot00000000000000H2D?00barbarian_castle_captain_quarter_left_side.imageo!graphics_icon.image$!hotkeys_icon.image“E‚knight_castle_left_farm.imageNŠpetrification_spell_icon.imageŸ`#petrification_spell_icon_mini.image¯hØ 0sorceress_castle_captain_quarter_left_side.image"##!!#57€#—"!"!""4564>##®®#:!"7#<1#:8#9«#:8*>!!-+#h+#!'#:1&#! # 7#:­/)>"9(##! #"-#9=# "# #!!.#! >#!#"8#"«#7!2#>ô+ AAzxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxzx‘‘‘‘’‘’‘’‘’’’’’’“’“’“’““”“”“”“””””•”•”••••••–•–•–•––––––––––qx‘‘‘‘‘‘‘‘‘’’’’’’’’“’““““““““”“””””””””••••••••–•–––––––––––––qx‘‘‘‘‘’‘’‘’‘’’’’“’“’“’““““”“”“”“””•”•”•”••••–•–•–•––––––––––qx‘‘‘‘‘‘‘‘‘‘’’’’’’’’““““““““”“”“””””””””••••••••–––––––––––––qx‘‘‘‘‘‘’‘’‘’‘’’’’“’“’“’““““”“”“”“””•”•”•”••–•–•–•––––––––––qx‘‘‘‘‘‘‘‘‘‘’‘’’’’’’’’““““““““”“””””””””•”••••••••–––––––––––qx‘‘‘‘‘‘‘’‘’‘’‘’’“’“’“’““““”“”“”“””””•”•”•”••–•–•–•––––––––qx‘‘‘‘‘‘‘‘’‘’‘’’’’’’’’““““““““””””””””•”•”••••••–•–––––––––qx‘‘‘‘‘‘‘‘‘’‘’‘’‘’’“’“’“’““”“”“”“””””””•”•”•”••–•–•–•––––––qx‘‘‘‘‘‘‘‘’‘’’’’’’’’“’““““““““””””””””•”••••••–•–––––––––qx‘‘•–•–•–•––––––qx‘‘•••–––––––––qx‘‘”–•–•–•––––––qx‘‘’”•••–•–––––––qx‘‘“” !! !!!!!!!••–•–•–•––––qx‘’” !!!!!!!!!!!!!!!"""""""""""""!•••–•–•–––––qx‘’” !!!!!!!!!!!!!!""""""""""""""!••••–•–•–•––qx‘’” !!!!!!!!!!!!!""""""""""""""" ••••–•–––––qx‘’” !!!!!!!!!!!!"""""""""""""""" ••–•–•––––qx‘“” !!!!!!!!!!!"""""""""""""""""!••••–––––qx‘“” !!!!!!!!!!""""""""""""""""""!–•–•–•––qx‘’” !!!!!!!!!"""""""""""""""""""!••–•–––qx‘’• !!!!!!!!""""""""""""""""""""!–•–•––qx‘“• !!!!!!!"""""""""""""""""""""!–•–––qx‘“• !!!!!!""""""""""""""""""""""!•–•––qxŽ‘“• !!!!!"""""""""""""""""""""""!••–––qx‘“• !!!!!"""""""""""""""""""!"""!•–•–•qxŽŽ‘“• !!!!!!"""""""""""""""""!!!""!••••–qxŽ‘’• !!!!!!""""""""""""""""!!!!""!•–•–•qxŽŽ‘’” !"!!!""""""""""""""""!!!!!""!••–•–qxŽ‘“” """!""""""""""""""""!!!!!!""!•–•–•qxŽŽ‘“” !"!""""""""""""""""!!!!!!!""!••••–qxŽŽ‘““ !!""""""""""""""""!!!!!!!!""!•–•–•qxŽŽŽ‘““ !""""""""""""""""!!!!!!!!!""!••••–qxŽŽ‘“” """"""""""""""""!!!!!!!!!!""!•••–•qxŽŽŽ‘“” """""""""""""""!!!!!!!!!!!""!••••–qxŽŽ‘“”!""""""""""""""!!!!!!!!!!!!""!•••••qxŽŽŽŽ‘“”!"""""""""""""!!!!!!!!!!!!!""!••••••qxŽŽŽ‘’”!""""""""""""!!!!!!!!!!!!!"""!••••–•qxŽŽŽŽ‘’” """"""""""""""!!!!!!!!!!""""! ••••••qxŽŽŽ‘’“ !"""!!!"""""""""""!!!!!!"""! ”•”••••qxŽŽŽ‘’“ !!!!!!""""""""""""""! ”•”•••••qxŽŽŽŽ’’ !!!!!!!!""""! ”•”•”••••qxŽŽŽ’“ !! ”””””•••••qxŽŽŽŽ““ “””•”•”•”••qxŽŽŽ““  ”””””•”•••••qxŽŽŽŽ‘“““””•! ÑÑ “”“””•”•”•”••qxŽŽŽ‘’’’““•–••——"!  “””””””””•••••qxŽŽŽŽŽ’’““““””” !!##"! “”“”“””•”•”•”••qxŽŽŽŽŽŽŽ’’“ !"""""! “““”“””””””•”•••qxŽŽŽŽŽŽŽŽ !"""!!!!! ““”“”“””””•”•”••qxŽŽŽŽŽŽŽŽ‘  !”“““““””””””””•••••qxŽŽŽŽŽŽŽ‘“““”“”“”“””•”•”•”••qxŽŽŽŽŽŽŽŽ‘’’“•“““““““””””””””•••qxŽŽŽŽŽŽŽŽ‘‘’““””•••““““”“”“”“””•”•”•”qxŽŽŽŽŽŽŽŽ‘“’’’“”””””•••••••““““““”“””””””•”•••qxŽŽŽŽŽŽŽ‘‘’’’’“““””””•••••••••”••••“’““““““”“””””””•”••qxŽŽŽŽŽŽŽŽŽ‘‘’’’“““““””””•”•••””””’’’““““““““””””””””•••qxŽŽŽŽŽŽŽŽ’’’’’’’’““““““““‘’’“’“’“’““”“”“”“””•”•”•”qxŽŽŽŽŽŽŽŽ‘‘‘‘‘‘‘‘‘‘’‘’’’’’’“’““““““”“””””””•”•qxŽŽŽŽŽŽŽŽŽ‘‘‘‘‘‘‘’‘’’’’’’“’““““““”“”“””””•”•”qxŽŽŽŽŽŽŽŽŽ‘‘‘‘‘‘‘‘’’’’’’’’“’““““““”“””””””•”•qxŽŽŽŽŽŽŽ‘‘‘‘‘’‘’‘’‘’’“’“’“’““”“”“”“””•”•”•”qxqqqqqqqqqpqpqnpnqpqqpqopppppppqqqqqpqqqqqqqqqqppqppqqqqqqqqqqqqtAAzxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxzx‘‘‘‘’‘’‘’‘’’’’’’“’“’“’““”“”“”“””””•”•”••••••–•–•–•––––––––––qx‘‘‘‘‘‘‘‘‘’’’’’’’’“’““““““““”“””””””””••••••••–•–––––––––––––qx‘‘‘‘‘’‘’‘’‘’’’’“’“’“’““““”“”“”“””•”•”•”••••–•–•–•––––––––––qx‘‘‘‘‘‘‘‘‘‘’’’’’’’’““““““““”“”“””””””””••••••••–––––––––––––qx‘‘‘‘‘‘’‘’‘’‘’’’’“’“’“’““““”“”“”“””•”•”•”••–•–•–•––––––––––qx‘‘‘‘‘‘‘‘‘‘’‘’’’’’’’’““““““““”“””””””””•”••••••••–––––––––––qx‘‘‘‘‘‘‘’‘’‘’‘’’“’“’“’““““”“”“”“””””•”•”•”••–•–•–•––––––––qx‘‘‘‘‘‘‘‘’‘’‘’’’’’’’’““““““““””””””””•”•”••••••–•–––––––––qx‘‘‘‘‘‘‘‘‘’‘’‘’‘’’“’“’“’““”“”“”“””””””•”•”•”••–•–•–•––––––qx‘‘‘‘‘‘‘‘’‘’’’’’’’’“’““““““““””””””””•”••••••–•–––––––––qx‘‘‘‘‘‘‘’‘’‘’‘’’’’“’99999999999999””•”•”••••–•–•–•––––––qx‘‘‘‘‘‘‘‘’’’’’’’’9**************9””””••••••••–––––––––qx‘‘‘‘‘’‘’‘’‘’’’’9(((((((((((( ((*9•”•”•”••–•–•–•––––––qx‘‘‘‘‘‘‘‘‘’’’’9-(((((((((((%((%*9”””•”••••••–•–––––––qx‘‘‘‘‘’‘’‘’‘99-((((((((((%(((&*9””•”•”•”••–•–•–•––––qx‘‘‘‘‘‘‘‘‘‘949-())))))))%)))(&*9”””•”•”••••–•–•–––––qx‘‘‘‘‘’‘—949-(888()888))888&*9””””•”••••••–•–•–•––qx‘‘‘‘‘‘‘“—949-( 8/( 8,), 8,%*9”””””••••••••–•–––––qx‘‘‘‘‘’“—949-() 8,( 8,( 8,(&*9””•”•”•”••••–•–•––––qx‘‘‘‘‘‘•—949-(()8,,(8+,,8,(&*9”””””””••••••••–––––qx‘‘‘‘‘‘”—949-((( 8,8 8,8,))%*9”“””•”•”•”••–•–•–•––qx‘‘‘‘‘”—949-((( 8 8 8 8,)(%*9”””””””•”••••••–•–––qx‘‘‘‘•—949-((%(88,, 88,((%*9”“””•”•”•”••••–•–•––qx‘‘‘‘”—949-( (( 8,( 8,(((&*9”””””””••••••••–•–––qx‘‘‘•—949-((((( ((( (((( *9”“””•”•”•”••••••–•––qxŽ‘‘‘–—949-(((((((())((((&*9“””””””””••••••••–––qx‘‘‘“—949-(((((((((((((((*9”“”“””•”•”•”••–•–•–•qxŽŽ‘‘–—94900000000000000009”“”“””””””•”••••••••–qxŽŽ9999999999999994999999999999999999“”“99999999999999–•–•qxŽŽ9**************099/***************“““9**************9–•–qxŽŽ9((((((((((((((%*59-(((((((((((((((*“9((((((((((((((%*9–•qxŽŽ9-(((((())((((( %*09-((((),,,,,,((((%9-((((,,,,,,,(((&*9•–qxŽŽ99-(((((( 8+((( %&*09-((()8888888,(((&9-(((8888888(,((&*9–•qxŽŽ949-((((() 8+((%%(&*09-(()8, )8)((&9-((( 8,)) 8)((&*9•–qxŽŽ“949-(((() 8,8+%(((&*09-(()8,((((()8)((&9-((( 8,(()) 8((%*9–•qxŽ“”949-(((() 8)8+((((%*09-(( 8,((((((((((%9-((('8,((() 8,(&*9•–qxŽ‘”949-((((+8,+(8((((&*09-(( 8),,,,,,((((&9-((('8,(((((8,(&*9••qxŽ““949-(((+ 8,()8+(((&*09-((''88888888,((&9-((('8,(((('8,(%*9••qxŽ“”949-(%(+8888888(((%*09-(((( (8,((%9-((('8,(((('8,(%*9–•qxŽ’”949-%() 8, 8+((%*09-(())((((((+8,((%9-(( '8,(((((8)(%*9••qxŽ‘“949-())8,(())) 8((%*09-(()88,,,,,+8,((%9-( ('8,,,,,8))(&*9••qx“”949-()888+((((888(&*09-(( 8888888)(((&9- %(8888888())( *9••qxŽ“”949-) ()) ) *09-((( (((( 9-%(( (())&*9••qx””949-())(()))))))))&*09-(((((((((((((((&9-((((((((((((()(*9••qxŽ””949-(((((((((((((((*09-(((((((((((((((*9-(((((((((((((((*9••qx““949000000000000000014900000000000000009900000000000000009•••qxŽ””94999999999999999999499999999999999999999999999999999999•”••qx“”•944444444444444444994444444444444444494444444444444449•••••qxŽ“””•99999999999999999••9999999999999999——999999999999999•”•”••qxŽ“”•”••••••••••••••••••––—–—––—–—–——————————————————————”•”•••qxŽŽ””””•””••••”•••••””–••”•–”””••••••”••–•––••—–•——•————”•”•”••qxŽŽŽŽŽŽŽŽ‘‘‘‘‘‘‘‘’’’’’’’’““”“““““””””””””•••••qxŽŽŽŽŽŽŽ‘‘‘‘‘’‘’‘’‘’’“’“’“’““”“”“”“””•”•”•”••qxŽŽŽŽŽŽŽŽ‘‘‘‘‘‘‘’‘’’’’’’’’““““““““””””””””•••qxŽŽŽŽŽŽŽŽ‘‘‘‘‘‘’‘’‘’’’’“’“’““““”“”“”“””•”•”•”qxŽŽŽŽŽŽŽŽ‘‘‘‘‘‘‘’‘’’’’’’“’““““““”“””””””•”•••qxŽŽŽŽŽŽŽ‘‘‘‘‘‘’‘’‘’’’’’’“’““““““”“””””””•”••qxŽŽŽŽŽŽŽŽŽ‘‘‘‘‘‘‘‘’’’’’’’’““““““““””””””””•••qxŽŽŽŽŽŽŽŽ‘‘‘‘‘’‘’‘’‘’’“’“’“’““”“”“”“””•”•”•”qxŽŽŽŽŽŽŽŽ‘‘‘‘‘‘‘’‘’’’’’’“’““““““”“””””””•”•qxŽŽŽŽŽŽŽŽŽ‘‘‘‘‘‘‘’‘’’’’’’“’““““““”“”“””””•”•”qxŽŽŽŽŽŽŽŽŽ‘‘‘‘‘‘‘‘’’’’’’’’“’““““““”“””””””•”•qxŽŽŽŽŽŽŽ‘‘‘‘‘’‘’‘’‘’’“’“’“’““”“”“”“””•”•”•”qxqqqqqqqqqpqpqnpnqpqqpqopppppppqqqqqpqqqqqqqqqqppqppqqqqqqqqqqqqt/+--,-,+,,+,))2/7996+0./^^_^2âãxzã\]4)))'-+,,,-.,-,,,+0-2äxã9667^å^_]^ååá\/ä]]äã-,))'))+-,,.*-ÈÈÊÈã}}àãã860548åä^^^/4ä\\\0}àä244)+)).,.ÈÉÊÊÊ)x}äãâ8.55864]^^^ã44â\[1/â{àß}2,2ÇÊÉÊÊÉãäy2xá1904/5664]\][__]6-áääâY\[Y\[-1ÇÉÊÉÉÊyã}{xàã7//2864/][XY^[åa`ââ}ââ[[\22nÈÈÈÊÊ4äãàxâàä164469842^XYY[^acå74ã]\0)2ÇÈÇÉÉË/yããàxââá1022878444XY[[[a]`^8888ó-2ÇÊÆÆÉÊÉäãäãààââá745/4788259,[[]][]^]^`^2)1ÈÈmÉÉÉ4ää4}àââââ662466842269[^]XY\^^\_)1ÇÇm+É,Ê-ã3ààààâáâ061102552556\XYYXY['0ÆmÇËÉÉ,äãäâààßâã286126855/065782[[]Y[)0nnÇÉÉÉ+2ã6ßààààâãäã05/062400527561^]Yó)/ÆmÇÊÇÉ,4ãyãàààßàâää0564.24/5144787444ó.nÆÆÈÇÉÉ+ãâââßàáâãäã6/444/660./22246óó'.ÇÆÆÈÉÊÊ/äáâàààâßâáäã50/.247420/6761óÇ-nnÆÇÊÉ-+/ãäáààààßßäää655/12744-/0565Ç/ÇnnÊÊÉ'/,ãäyßààáßáããä!:640//4545244óÈ0+nÆÇÇÇ)È-äãâxäâââãßáâä!<54242501.42óÇ/nÆ.ÈÈÈ),4åäã444 "!00É2224!'''))+,,,*,40)Ê**)))7+rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr**ʱ±±±±±±±±±±±^dig!i>k >!ik !kkk!! !!±±±±±±±±±±±±±±±±±±±[idb_bddhdd"digik±±dg±gd±±g±±±±±±±±±±±±±[Wb^ihibk"«bfejig#igi±±±±±±±±±±±±±±±±±±±±UX\ikkifkkid>ihi>gki±d±±±g±±±±±±±±±±±±±±±±X\hd#dk:dk>kig##k#gd±±±±±±±±g±±±±±±±±±±±±±[Xg^dk"ek"idk#gk#kd±±g±±±±±±±±±±±±±±±±±±±±UW_biik#kieijikigk#i±±±±±±±±d±±±±±±±±±±±[[Xkii#k#i"kk!digkdg±g±g±±±±±±±±±±±±±±±±±±XW[gcbkd>"hg##"=igkg±±±±±±±±±±±±±±±±±±±±±WUXiki>:kdkik#ii##gk±g±dg±±±±±±±±±±±±±±±±±[X_d"ic­kkiifigi"dkg±±±±±±±±±±±±±±±±±±±±±WWX_giki"hkk##ig:gkgkhd>±±±g±±±±±±±±±±±±±±VX[_ibkhekhdikek#jgkg#;gi±±±e±±±±±±±±±±±±XVXW^idb!fk>g"##;kkkdkg±±±±±±±±±±±±±±±±±±U[W[bgbhikkgkiiikg#iik#dg#g±±g±±±±±±±±±±±WVX[__kidk#hæk#kki®kgk#kgh±±±±±±±±±±±±±±±±XXV[\hcdkkikjigkgkgh#kg#ikhg±gi±±±±±±±±±±WUY[[dghbkkk:#k##>"hkggkkh"kh±dg±±±±±±±±±±VXWWX\iikhki"gk#kjhhk#kik±gdgi±±±±±e±±±±±WW[VX\_g=`hkek#ghk#dkikkdh±±g;g±g±±±±±±±±±VVVXWWggkkbggkigiik#jkikkk±±±±g±±±±±±±±±XWXV[W\c^ig«kbgg##eig#kdkgk±±±±±±±±±±±±±±VVXXWWWWhkhk=kg ki####"kkkk#±g±g±±±i±±±±±XVWU[VX[fh>e>k kkkigiikgk#ggdg±±±h±±±±±±±±VWYUW[WWeifkke>ig "i¬"jgdgh®#­±±g>±±±±±±±[WY[VX\W\`ig^j#"ah"h#h#":#kd­#h±i±!±±±±±±[V[VX_[XW`g e`ebfe dfdkkf®kihkhkk±k±±g±±±&% lVWV|.46 7¬7>­4>­ 4­ ­4­±±±±±±±±±±±±± &&&&Í,.Í,,.Í..,,,Í,.Í,.Í.±±±±±±±±±±±±""""########"""####!""=#### ±± ±±±±±±±#!""### """""##""##!±± ±±±±±±±±±######56<9#!#"=8#=<‚>#= #;±±±±±±±±±±±###5####<###!";#;>6# #>#±±±±±±±±±±±#6####<#####6>7>;>! ;#±±±±±±±±±±±##:####5######6#;>6>##=#±±±±±±±±±±±#####|####<#!#—:#6>6>Õ><<>#±±±±±±±±±±±###4####5#!"#!#;#=>;>"!;#±±±±±±±±±±±###Õ####<###" #7#6>Õ=!!=#±±±±±±±±±±±# 4555=<# "#"#66Õ#;="">;;±±±±±±±±±±±## ########"#"#Õ>;#Õ"#"##±±±±±±±±±±±±±######!! "=!# "";>Õ#;"!# ±±±±±±±±±±±±±±±#####  ######### # ##±±±fheroes2-1.0.12+dfsg/files/images/000077500000000000000000000000001456075706000166105ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/files/images/platform/000077500000000000000000000000001456075706000204345ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/files/images/platform/psv/000077500000000000000000000000001456075706000212445ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/files/images/platform/psv/background.png000066400000000000000000010171641456075706000241030ustar00rootroot00000000000000‰PNG  IHDRHôp ü pHYs  šœ OiCCPPhotoshop ICC profilexÚSgTSé=÷ÞôBKˆ€”KoR RB‹€‘&*! Jˆ!¡ÙQÁEEÈ ˆŽŽ€ŒQ, Š Øä!¢Žƒ£ˆŠÊûá{£kÖ¼÷æÍþµ×>ç¬ó³ÏÀ –H3Q5€ ©BàƒÇÄÆáä.@ $p³d!sý#ø~<<+"À¾xÓ ÀM›À0‡ÿêB™\€„Àt‘8K€@zŽB¦@F€˜&S `ËcbãP-`'æÓ€ø™{[”! ‘ eˆDh;¬ÏVŠEX0fKÄ9Ø-0IWfH°·ÀÎ ² 0Qˆ…){`È##x„™FòW<ñ+®ç*x™²<¹$9E[-qWW.(ÎI+6aaš@.Ây™24àóÌ ‘àƒóýxήÎÎ6޶_-ê¿ÿ"bbãþåÏ«p@át~Ñþ,/³€;€mþ¢%îh^  u÷‹f²@µ éÚWópø~<ß5°j>{‘-¨]cöK'XtÀâ÷ò»oÁÔ(€hƒáÏwÿï?ýG %€fI’q^D$.Tʳ?ÇD *°AôÁ,ÀÁÜÁ ü`6„B$ÄÂBB d€r`)¬‚B(†Í°*`/Ô@4ÀQh†“p.ÂU¸=púažÁ(¼ AÈa!ÚˆbŠX#Ž™…ø!ÁH‹$ ɈQ"K‘5H1RŠT UHò=r9‡\Fº‘;È2‚ü†¼G1”²Q=Ô µC¹¨7„F¢ Ðdt1š ›Ðr´=Œ6¡çЫhÚ>CÇ0Àè3Äl0.ÆÃB±8, “c˱"¬ «Æ°V¬»‰õcϱwEÀ 6wB aAHXLXNØH¨ $4Ú 7 „QÂ'"“¨K´&ºùÄb21‡XH,#Ö/{ˆCÄ7$‰C2'¹I±¤TÒÒFÒnR#é,©›4H#“ÉÚdk²9”, +È…ääÃä3ää!ò[ b@q¤øSâ(RÊjJåå4åe˜2AU£šRݨ¡T5ZB­¡¶R¯Q‡¨4uš9̓IK¥­¢•Óhh÷i¯ètºÝ•N—ÐWÒËéGè—èôw †ƒÇˆg(›gw¯˜L¦Ó‹ÇT071ë˜ç™™oUX*¶*|‘Ê •J•&•*/T©ª¦ªÞª UóUËT©^S}®FU3Sã© Ô–«UªPëSSg©;¨‡ªg¨oT?¤~Yý‰YÃLÃOC¤Q ±_ã¼Æ c³x,!k «†u5Ä&±ÍÙ|v*»˜ý»‹=ª©¡9C3J3W³Ró”f?ã˜qøœtN ç(§—ó~ŠÞï)â)¦4L¹1e\kª–—–X«H«Q«Gë½6®í§¦½E»YûAÇJ'\'GgÎçSÙSݧ §M=:õ®.ªk¥¡»Dw¿n§î˜ž¾^€žLo§Þy½çú}/ýTýmú§õG X³ $Û Î<Å5qo</ÇÛñQC]Ã@C¥a•a—á„‘¹Ñ<£ÕFFŒiÆ\ã$ãmÆmÆ£&&!&KMêMîšRM¹¦)¦;L;LÇÍÌÍ¢ÍÖ™5›=1×2ç›ç›×›ß·`ZxZ,¶¨¶¸eI²äZ¦Yî¶¼n…Z9Y¥XUZ]³F­­%Ö»­»§§¹N“N«žÖgðñ¶É¶©·°åØÛ®¶m¶}agbg·Å®Ã“}º}ý= ‡Ù«Z~s´r:V:ޚΜî?}Åô–é/gXÏÏØ3ã¶Ë)ÄiS›ÓGgg¹sƒóˆ‹‰K‚Ë.—>.›ÆÝȽäJtõq]ázÒõ›³›Âí¨Û¯î6îiî‡ÜŸÌ4Ÿ)žY3sÐÃÈCàQåÑ? Ÿ•0k߬~OCOgµç#/c/‘W­×°·¥wª÷aï>ö>rŸã>ã<7Þ2ÞY_Ì7À·È·ËOÃož_…ßC#ÿdÿzÿѧ€%g‰A[ûøz|!¿Ž?:Ûeö²ÙíAŒ ¹AA‚­‚åÁ­!hÈì­!÷ç˜Î‘Îi…P~èÖÐaæa‹Ã~ '…‡…W†?ŽpˆXÑ1—5wÑÜCsßDúD–DÞ›g1O9¯-J5*>ª.j<Ú7º4º?Æ.fYÌÕXXIlK9.*®6nl¾ßüíó‡ââ ã{˜/È]py¡ÎÂô…§©.,:–@LˆN8”ðA*¨Œ%òw%Ž yÂÂg"/Ñ6шØC\*NòH*Mz’쑼5y$Å3¥,幄'©¼L LÝ›:žšv m2=:½1ƒ’‘qBª!M“¶gêgæfvˬe…²þÅn‹·/•Ék³¬Y- ¶B¦èTZ(×*²geWf¿Í‰Ê9–«ž+Íí̳ÊÛ7œïŸÿíÂá’¶¥†KW-X潬j9²‰Š®Û—Ø(Üxå‡oÊ¿™Ü”´©«Ä¹dÏfÒféæÞ-ž[–ª—æ—n ÙÚ´ ßV´íõöEÛ/—Í(Û»ƒ¶C¹£¿<¸¼e§ÉÎÍ;?T¤TôTúT6îÒݵa×ønÑî{¼ö4ìÕÛ[¼÷ý>ɾÛUUMÕfÕeûIû³÷?®‰ªéø–ûm]­NmqíÇÒý#¶×¹ÔÕÒ=TRÖ+ëGǾþïw- 6 UœÆâ#pDyäé÷ ß÷ :ÚvŒ{¬áÓvg/jBšòšF›Sšû[b[ºOÌ>ÑÖêÞzüGÛœ499â?rýéü§CÏdÏ&žþ¢þË®/~øÕë×Îјѡ—ò—“¿m|¥ýêÀë¯ÛÆÂƾÉx31^ôVûíÁwÜwï£ßOä| (ÿhù±õSЧû“““ÿ˜óüc3-Û cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅFŸIDATxÚìýk¯-[’†Å˜kísî­êbu·ÄG‹O5_mš4È¢ü¶lÃþ †ÿ9ÿA0ä "`à HY¢  ­¦Ñm«›¬îºuÎÞ+‡>dÎ̈˜3g®ÇÞûÜÚ«p뜳÷Z¹ò11"FŒ?ûoüœòñúx}¼>^¯×Çëãõñúx}¼>^/)·àãõñúx}¼>^¯×Çëãõñúx}¼>ÒÇëãõñúx}¼>^¯×Çëãõñúx™×ùã¼þ »?øx½Ê‹Ý~¼ÞËüxË9Âqññúx}¼>^¯×@úx=>èCó½M Èë]Þ¿bØCýã#(~ç‡[çß~²üõX%~´ûÊÇkì¦ðÕ¦ÍÇëãõñúH¯ÝõÚ}Š@ÿûcg¼ëVűwRD@Ð4ÿìcã{óùáðÉ[%&þ(gÈ>BÕsä#yðþâþ7šü–îуöN}ð™¸óy@Ô|ù7¿Õñðˆ?^émgéøµàÔ¿ï'ª™N~¦‹~ôK¯Z!\t.G©'1¯÷•ð 6²:?ÄÍ ‘Èk…à·àá_Ò%–·ë¿?…·O"èDB8xã$ñŸ§ÚËøîˆÖ—¿ã^“#‚ho+Ôaxprà¾ç³@'_¬bœ·L¤téÙ#ó! ¦hñÚ»ÁÇ:û~-6¸aùoYn‘‚“£sþ×cj1ù;Ÿ E¦ ” 0Q¨+J¯WL Ìó£`G~Þ\»YÞô<É7|ı’ŃAaNÔ`|þÝÇëuÁ‘ž#:¹&ÙÜÀ=¾8ù7®»¼÷(f|p¿îý¬{Šö;ÖÃ,Y4 ^ aôù>脆ðED‘æ†À P<4f÷æv8_´Ìÿ wºqKö+$ª];Ñ ƒ+FÖ7ŒT°W0|[ X–›»Ð'މ¸ãä¸çÓyÔDåàBLŠL™Ôß)I柉,YòwZEâqŽó£þ¼¨÷È[Æ.$—~Ç5hâ–40,õ¥ôºs„.i°%ØD%Ü®|Ô°íe𮦦eÕ#ižd9ð…ìçLàÊ@nø5mŒ<„”JºxáÚ¹Š^¥hd,Ñ‚¦î¹¸g®1¼Ux•Éy|ž À€à^ ãô¿‹²£§Z‚DPAñ)¡ÞÌS7_YKÒTÇÑÌý„xâ]¤=p´wxÕüÆÑŸºf[=’ (Ai®cqAöëñbgáaœc4w"“¦?jZÂ;GüQO¹’êi§EýçëzÝñÞóá83¼×éª(…É{©ö¦®V¬>zóÞx‘8Þ7Õ¤£ „á€;Èn‹N(uÌ/ÅðxNªGÌFÿ¶˜SïI‚ÖÇm!æÃ:ìÑqÝãåýÖ­½DÈÕE†½S…]§Q«ÏëÙ2–GΗG€ø·ÚtMTbRÝ¿nòj$hÁðÔUlÀ¥¡0!žŸæ\hZýÃŽ€Rt}µf«®ñQxé0@º0úñGòÀ14;Ìá lÖkÏ;Ç^vç×ìÅ`N2Hj4-TZ„ÙóWðúŽÁ_§€ÔRPÙPQÑ þ²¥÷žÌzN|ÜGMxduÅ$ì kÕ[²ï&~­æEòg Q H|Í÷«5èØçÎf„(¶»w¯ vfÇ2¦Ù'¤ý1u<ªæÞ'®™×ä‰q÷7öâ=YWc±]¢®Ý[DMU‘ÎÆÄ½×ç£Ì·GÎãë¾ÃÓ&À:8;ÀÉïá€:˜o² š~°˜ìš»ÃÉä÷}ªSÜÔA?2î_Y:›,989±_‡ª:× @ʈêe¡ÕMˆ³ƒi0ˆ‡¬‹ßHVÅ­Ë.›À€^À Û·m—yWÞÍÍ仾áwœ3&àÛDLJ™çD)œÿD Œš Ô·<þqì¶›”ÊÕFý=øpGvnÔý Íá#½áíXOèuhdÏ8 RÀŽFGiýGx¸uy b<úJ”ìI;ó±m.Í‹ëü9|[SúIot¾j䔽$R!« w®¼Ó¬}­X|/„MFèD a)"Ó"ÂP–jRY>0-î= Uþº€J"ÔªêJA•:?¬ô7Â$ÄûKð÷˜þQ³9Û}FS]tuH' 'ªž0¡LÄüï*^òÞh“©É˜}A÷'µŸ£ÇI6®ÃÈÐ\e;òßsp¾íbú­\ @……möG¦2ؗĽЩ³…6Fø0Õdר]Ž6Hbÿ8k2ŸÇ“-Ã8É¥H‰a¥r±½Qê½uTñ®@nøö¢–"¼ÑûÜë´Å£v¥Ê¡J ÝÁs‰¦ïorô¸uª›ÑdûUeJàÞÇ}d¯8\/,èªýV·>“óÑ™ È!pôcFGüüsÕY¿RhªHkVPlfÕ.iô{-/ˆ·¾ál÷ç(é KÐG™#ï‰5;n.à½F7¾~LÀ(ú–ŠZUQ·Š¬H\m}Ë{ÄkÇô “"4BvIñ™æB$¯ó3J"°öåA…Šlo³®5 –°&MÑFÀªAwË‹{à䊱 ‡Hæ@™ …¯^7{ ¾BÄ1Úi—ñOË.XÑKºk]í»P³;岕Á§„\Õ‹3”ï`»ždƒ…;±£C÷¨xJx­5ݪXOL!dx4P D ¿ö¶¥+Fx!©&ùMVÏÝHlÁÌ!dë£f@õY¶ØžÛ’Ø»…v7Ü÷øKÜU£k€‡ñt©2Æ¢èu‚U¥«@Z$í϶Mð޾07¨€ñš9~×X ÅNKw¯YòeM‘iiF?_©B@ÜtVßÌ‹ïa^ÍAžÀ‰3È\=:•íYé¤÷JÂ0Wþ†kÇyƒÁ$w³l:VI.êù±þ{³š£ž3¯êø’Þf߉=ôŠª¸öÌÇ‘rŒ:Žzº"?HÍkŽâ~À;ç…´C]{êOØo¨Y°]×èr€cz¥\pÖCƒUV:a HÀsÝ2Q>»ÇòUß"Ÿ Ô¼wŒš~"õ÷â2å'°©5¦²h{’v/w¼ 2€o€“-0ÑYñ)è­˜ÿ\¿"ÂiPµÇWžîGœx‡1Õƒ´Ì‘RD%\õAF}W"ÿõPv^ Ì8ÀEa’˜ÔüX)w59X—å¼ÊÔùa@)å£zäÇ%‘˜Žü?’Fç4£/£~_(Ø@ë¶÷0Æ pl1tÀ ­u\c¯Ç޶øÝ$k°w fês®ô>¼QœõCðÙ‚E½¢úD|¶='µ¯‰ ]pØæû& QÕH[ðMüðtHö·¡—É1;ÂóÉÔìôßw ‘/wÝç¦ Î,‰Ñœ¨‰4)TFÞ]ˆ‚þ÷pð!‹¨$|WUÑb0Çts¹±šžGE·{@:ýî#`qÏuLêÑ[#ì%£`ÇÀë¥ÝøÖp¹%¸×Í÷*׊WŸLQQëd °¹Pêt 7ÿnβM˜Å6Ô&µÞ ý ®&!PÇ}ÑóÂ+ûÄ(A‡á \ÂoÃÁgzE¿FH¦Ý 0•8I‘­’JÓOE¦e.L2‹—LE¤LÛÏm¢ß&Zâ}Þ„&ÎC gmš¨;Ÿs&ŒÑ\±’ø´ŠaAÂV\â®W˜ä Jª&½‰xYì˜vE´*ÄUYÚg'tjcd$Æ”HšPšزƶP8â´-P&,·1f"…®&eY³Ñ•JÅáúz” t9ؘ:0G<¿Ã¢îk\‰)]ä̰=E©‘`Ë]u ÛÌG›Ÿ*œaÆG&´7ÇPࢢs x–Ãk†÷×ïA V?&¡òýS¼ùî¤~õh1"P` ï©Ø¹ ‘X™o­d·©…†×º§»µë @e§šzãÔ{䄌†Åõ‰íA‚, Ió¢ÕºÐz¨á­RT(ØP rR  –JD¯sëÑèüàØdà#ox÷Þ6_çÇIƒ#hÅ+Øq©°N«É~“$>àIµ â%„ÂÎ8c·×$oà–ÜQLúµ£W‘nKwnSSC/0>H¥pP0î¾HTöºS•:º¡½Nhóp¨9ÿuP;|UalЇ$tL{E4rª i‘xïIª-ÔX†jeŒ:0ŽpVT)*Q›Ðgéx]]È"¹Ï£U{uxÿƒ¤7J¥4Š9ØÁtï0œxü÷y3²`aóÉ'•ÈÀA¯¤óÍ'ÚGl"ÚÁ¬#{*9ïáÐ{·YûÕ[]AZƒ?ý>XÙ]¿Çí¥ðÐ{ñ*Õ$Ë×òiYt&Q`i‚à$«A]}_Y$¾'©`&SÞ+ÉâÛ[YÞgLª²@+õ´öÙúó hS€4#‰•@w—Þÿ¡ò@ÀkV„FÄäúÖjÑoÕ1^+¨+Uý\æG¥ÿ³ô²Šzñ£„WL@ïbž¯S=k²žÞô‘mõ@Ó™$ïá6æ•"ª(ê«FæBÉÞê:ú:‘Ž=PòVõNš8ÉÆï½N¤¶bú±ËTÌèDN¸ÆªM«ƒÊú¯ôÁu^8 {ѹнÁø¹z fØTq+ Õw4AdjlRAëpöµZ4 N0ˆŽ]‡ÕñîÔ 2ìkÚ;B%&ÍÝM Øg ’¹Ü4r«ž–¬N7¦U*!az©37ñ¯òAz8ʼÞQÕwø|i@’ê±€ÓX’ÄVRÅ"\…í–Hïq_ï¤f>×ÊõF1˜ê{ʶhOË"Uˆ96Õû«¦óš^/¿®à(ÂIµI¦âè¨Qo^‰”ì »=e¼óZÁÁ_’7o Ñ=-1aØŽ¸Ì™"[î}]s¸­Q$¾ ¿NÀ(\-Ñ6¯Ĥ¤ àà¢ùŒ·Rh*bé§fì‰4¤éBÌ↪ª(G$ÐÐ )ӀΠ4½J®ç’Æ]oŒïk]9nŸ‡ î ‚IÐø`fÍ[Ú—QLª9®:ÙQ8[ç bæ}n¢Hjä©+`è|µV³”PQñw=£=ÚÊÍkpˆl·y’Š£p€9“.ˆžI=uÙQ"Ó(ƹäXº~L@ÉP‚1-ŽÒÖVïÒÓÃ0ÚG»þ øf‘m*oýŒÌç@ÓÚ„^Ó’4YéÉ·¶»Wï¾ñNךƒìû<# ÏFÚ[6Á•N7ŸÇ,­Ž RdqñÖ•º×Xx>@Q Ž$ðA2JvÖ@V'ă¦Îøä—‹Ýä‚ë­Þ;¢ß°ÿ}¾=H,“º¿Ó*Ô0Q•ŠºU¶ab¶RÿkŒüÆÌDýìŽÞßj ÕÄÚy¥`§Ìt@¨³Õèxèù8H2–è¯Ã;þ‘:6@ P€`ººRÌПÓë× -< e¶µGhCeb¡³E¢ãhãÁÅ5)ŒÀ¤S]73]ÿŒN²±(ÊÒÇEÿA€Háäû‘Ô9]$Ñ)Ù›‚X€áMNUðàË tÌ’4µ¸ÆâuÕv™ß›‘Ëo)ÄxXN ®¼œÈ}oÙ•ñ3ôAŠ·làHpÀÝnnÚ¶ç÷3È¥y%Pë©Ú`Ðÿç…*}¨*yÕ×)ñDJoÙ Ü&sŽˆ@Ü›Âdb®_ý“>QFlæ±S+·†Bš™Ðà(ÌX‘—hh¨\'®â*2N¯{µG¤½†®9/¶QáÄ1{bïQ®³ðsUu¦ éפ¨\(\.'|O£‹¯=â}¯žŒ‘j] !¼~ÎŒû›¼á"¬º ]•["-0'!—SgÓåVí ™Ìò I«= ËÐ%í=ê%VÙÔÍ3îeµv°4»ÍÆe1H&×jk•ÓžÒÚDF<ý‡ÁÆëÇ÷a+ÛØ+"2(¼(Ú_tGƒ²P¥òKY|͵ SÄ¢@_$+wY# Æ;èØò”Q©—pý_ÔB,H?è±l®Ë6"! Ò©«Üp[ÞcˆÁWY’—R$Ñ Ì8½ í#†Ñp'0w»õH¥± Žp×;|}ŸàN†¼8™o_9:9JÝ –\(¢š%¯Dw¡·Ý f±¯Œ¶§D³þÖ52+nMjYÖí•^W–õΫê¿=F¯yYZ¥Z­³XÃØÐOu5 ØDäîz-{½G¼ñXzy=uªA|‘ôVA`ê3EŪ¶b„Ç墮_\ùÐg,×,¿ë\×yqÙ_ ôZ :cˆ« éù „!ŠŽewªªkþ›5´Mg¤lw÷z{:Í“fL†Jòªn”(²]¤Of¥B¬9xûVYi¨ó{‹@.Õƨc'kàüpŒ*—R¡ô`{¨{ñ׎z®"Ó KR°†èJ¥C©@à#¢6+~‘EiÖ¸½Ëo&4O¯Ðó0(†gªr‘ “,=kAU‹®è{ú¢½¡ÇÓ­Öïõõä"_/¯$xìFHÊ™6œ,;à(¨™‚£kªFã4¼½Ù|dXÍüª!©¢K®Ÿ‘2V›Zq=HESì°oýæ³àÀÈgȵŠNÙÖ¿ir‰¬¥zTAM]”åyÈ‹äH‰o/ÞÓBw' bŽÞ5ø©”:-û­ö““—÷.b¼’Žœç½÷Þ¦[š;B3òÞg›ˆ*İÌ­ö;-&z+Eµ`MÄ­”SÐÊc*¼áä]Ÿ'ÒÑ‚<#!ƒ–\•tÁ®(#PëmT«Ú#O%ÿ-V+Ò5’õ¢‘`„ìôÿ4 '÷&6^>ËX*Á}:é@ à0Ý9 QX*C"šÎWÀ‹<,gZ½…eŽb’-˜nÌ“ü†„ª>Ð3 +Í^Tï`‹H·ŠGYcôY´bíS£¬DL•$þšÃAýlÙC¿&ÛÂ]ß­‘äZ–éEcÈeÏô˜ Õ )s…<}L³ƒÞ.W¢ŒehI÷V ¦l®tÛ ¬‰“ÃïÝx8°9{(V Áiò~ÛF«¨ïÖ€Æ#èØ¦u¸)G½Í+™-7£7FÙ€Ú?¡šùEB¡­-‘I þN:k^œ¬ñH’säQÜ)y¨¼·kõkBMºÖfÑ 7)Z ¡ˆ”ih(KB¡ñ×Ézâ~ÄM‘ï!εÙð­zºUûØ€'M¿ó}L‚ÔΣ›Ó¹åþíMo’ÿÆØqŒxÉòçäú´¦i\EfPTEJŠGÆf@{*±Wmç燹q‘zÖ$õ–Œ( ÄÊZVÍn  Q°©›V…3Ý3ƒÄÆÒn¨T3Úù˜Ïp@ú˜»v¨þz½ußåWU¢€MžTÉaß@6;’c'->Ñía¿mjêZ§Ú‚ÌÜR\©7 ä)4rÏX>< ç9)$Öû¹ü¼˜5†]Wô³Cßóhîad¸~’#iŸÖI·õŒñz*k>ÿŠ*¸Æk¸ßc‹<àA›ÏÐÄJFç›™BïÉÒEï<~¨è½D+ƒMI¨½Œ=¸ÂÐâf ÕZàé”(Pñêu>fÁ=ÇñŒÞ.@òšáùØç´ë€£a°seÕè®Àèútcî4AßZZ@R¦fg¶QìÓ‰ö7ÜÅy\0Ρ€Ô,eþN¢äºe¦å‘dÕ.Þ| èN‘(°i¨çtRÕÕ“«°J`²œf¥MÚßz0°,LG T+%ÆWš(<ÒÔ¹“Ø^(-ù­Ídµ ïÚ Þ4µóÝ ">ü„‚ÊOLX–¼Ì†äIímœ:5 ã­G9ÁÉ|»½‰°Ïz`çú{ãì'áU4%’¬Ø÷y€.l+ÝTÙ²H¾ÞT›¢ˆ³³"Iœ\½Dûs—¦ªZ™]{ßu¾jo¸¦Áå 8z\Õhàüï_ì½ hÚ¨v5“SÄf|½ì.Ô{G{Mº¾‡xørÞÆC-I#³D­}uϘ–û¸2¶¸U"&#Í•J‡hl¾Š¹Ó8Ò™DÙí{¾ˆ3M^ûôÐ7RÌeÜr—Ä:»KçÀøëõD=ℬæ¯s€§ò në–ž“Vöe¾ù&žñ½‘ýŽ *$,JzžçËäãí&ÖdmÕ… ªb-0Ü (m H(ì=°Øš> ´Õ} dø —W³Þ{ʺÚ(xÖoH-”lƒ2™pA®§Öé ArÁÌ6ô¤í¤8iªÝü¶Ë¤z†¨ú™ “„½&Ìy¾lÕܶïƒa½Ðä4˜tÁ5„½NgÞTeöÌiÑY¡Ú1Ãp©“ 2§¬×£çI\êmŽÈÂ×M‚pO:u?Hß ‹-(]Æ&î±’ÓÅxñÛ}Oì³ï%’në„k'¸¯ò¬ÊPZ½NÑ—÷â¼_*Ñ=Öš7í•áðí¥Û$«C­Q»pIÝcšARUí2RÑR“f©÷嘿åÞÑylUÕzÐRí*H*vÎø9$N¸A¢= sñw-óûàaÀnDE’¿_ûíW –‘j¯Q) uRIÒ²üvÕb1¢5o‘^etº,#÷­Š6Ñà«@„ .©ž £fñ†×EC l)B4=H5H­j¬: ¾,W~2A³×ðÈ_Ó”E[€‡nâ‰XÄU³SW8‚{¬š“²‹Ú®ÃvšS=?ßWĸˆG3R$ Ë…™ˆùï&±ÑQ·ý9‘ˆ[ËT_`Ó[©Æ1gÞAs*me®ö½^]MB@ÃôÕÂì|ho¸)¬º?8Ú¯½0j‚@˜­1{­µöé=ň4h<·é•#>Fhqí›ÌH®jÏþU@‰4É¥¬ÉRgkûšWGˆÃ‰û€#Ü6wźүYY#ñí¨vTìt†|Äó%kðÖ wó{º&0:¥ ©jóžÔ:3-¶2Í‹õjœW ¸8yh— …| ¤×…HÆbQùi)v®"%E™víÆ6s~œ\6w÷ë£!uÍ  ®³™'’'ûpQÁç´ðR ÖX{M.Úb“Ly›ùÁ7©YYí©>PBgq£ä}}:^ÔWgû(µü·=[=Ú‚¡µ/¥Ù;¡•‰[{§= Üônl Ñ‘ÏÄ_%‚ÉH ¾·hõ’Y™ìÄØré嘦-ÓqZ ÇfÑKiA=ýèÚïV&A5"¡ Ñ*¸hﱿ§ ¨0Ûlg5å®Í$¬ãFÏGûbC9@œÕ6ÁþR«‹ª3^úÝÏïÛÖÜä@´ÉZ=HžÛžö'ò÷{Æi‡@m2NoHá\’»ƒ£«ú®GF•ÜóùÑ©¡™béwÙ-CHíD[·ÏFï9¢øžBXíEª¶›ù¥Ô °l²²±Hæ!…¤#ñãBb¸Äƒ$´Õ¿â“Øë»LJù}‡u4q쳓U`Ú¤î$Ñ«÷"-› I‘Yñ1š#UòjoMLNá­'¢z²Ü{ÇhÿäÁë´U# à xü¾WÕ€£ÖB‚¦²nÂ,¶ž¹MÈíÍšf 14ÑÂÚÆ’©Ð²ï™Ú‚›õéèµu™ V 29±eöÌ©VŠÐ~…ÉÇЦ؀M°zïu¡+¸Ië]Cfc5 J c3—06@.Xq}8è$$ŒRÙàÚø;8V$jÑdÕ×ëÕ`úLkD—”DM¯•¡·ðâôæƒõ îK˜f¶ƒÅß—FôÁsf·•²P°ÏbU•ƒ{6ØtÇBŒØÒÎÛ®Ü®î Žb`Äݬëuû-}¶ç•ƒ`ÝÛ‚8µq8Q†U¼<¸lT¢‘ì¼çuÛ8t.ו²˜÷ÉL««-•.T–9Q°ý> ´yÁ7« }S…«€¼‚Ú põBÒsÀyuxyï"mrôR®ª@'¾|»±³à;èü²žF“ÚÇKYæG "QiwÛ}¤\€8É7ÜòcJ¼ò¡\¿ªé;UAÉZ /[S>ÄášÛôÔÊC "Ø;ç éÛéÒBÄP®bDÑ1œh:º`KBWU¢›L* å*LhÆ}AzæÇ£(wPUÉE™’Àîz4¹{™zÞ.ÃìPC9 Ь§~…Oj>Df¬zÀyÁÆÌÐÒ°e¡ÑÄ–ÏS%µ<7пƒ Å”40‰­ˆCÛt'ø îC#hÑ‘û^?D$¹Œ®ïÖäXƒµýý¸Ké½#†‹áëF N¸I©tyžF|Aì*pÚjGÀ˜P逃×r¹äc¦ɭ§EW¸Éz›$)©tbWÿ¼‰J×7dÞù¹ÂEr@ ÜóÒÕU Ó¡à ý4Ôëô¿Sø+1ÈŽÍWÁµ8ÔÏLê`…[ÅuªtFG­7jeNåq’_“ßæPPwÛÔ¤:ÐÔ£$çšÊeRW ‚¶ÛŠ;q®?s.B>P›N·™‰­ “ÁÏ4Y¼‰lô(±þA5È«ãMl%ô¨7’µÀÀYád-Ñ  o¸â³Ëˆ˜6#áö²^±nÊÁÑ]>Ïሒ;æŸËï$Ñ ðàEIWÃÕ~yo¤)ð—H{ŽÚß'Gš £â² ÚùX¨ûYy1ì"àÔ:•\eÍ›¶êJ™0÷-ÄÊ© 8 Ó"÷H·e?:ce4Þ7Â!ìšìíÉÁ"›Ì«çþG¶µžFT)DUOïu=Ï£ \,ɇ€Qã`}ߨ¼d IX3L†Š¼Ý»J+r•hÜÏçðÜx 8âcÀîAØiG1õ ޾"(GÒH9Óp箢dtAå¼;‰²,¡ºOt´õš8¸«’\´9©¼Ùüxàˆ¯87¡¸½¤¡Z'´ns7»Àñíïùå±9KÛ¯E V¬‚n6RÇñ÷¾_ ØmAòÛ¶PÔ¾ &`å½×¾#Ù‰Ø|)iä GŠi{îù.nž‘TÓÑîÚIJ>ÑôÈè(g`Þj¸Ž%—Èè¡î;^¾707¥ÆC_Ñ=añ:ÎýgÀ‹V; ûÏ×ß‹_D†A61Ý®Q”Ë2!–¦ŸË«42ˆ'ï5ºž-‘+¨vQEll¨ž¯Y¦ý÷G×ÓqÃXÀh”O‚Ū ®Ì¢ëÏŠ ö|0¨‘–@VësÁ(2Iš}Üyxg烚©éªÞ&³©çû4ÿû²ÌÅâÖ@ÝsÅi;ÞÝܤÞÂ<í]ß•ßi²ÕV¢¸á*ã^Ê65‹ëWÒ–åV…ÆäÚ»Ovöwbhõx2ÁÚ†¢.Û:S÷Û"Ê"3M‚^g›Œ~°ßÙ÷CÌ4YÁ:7`îMmw³Þ™Bf r·úHeêsÙª@ £~%£¾‚Ví Ð6›¯Ü:*×`\;¤ß‘b‡£ïyuptÉ”À'ZuF‹’¶Ò¬›ò–Vîþ.ÒJSc°F•q¿GÐ&PøÐ‡ŒÅTÄU…t¢¢VÖ. •)ﵘ^7ßÂ<æÈW¹£ßž/_½ª”›‘ÏèuWh¯¹lìÝV‘Áe”Ñæ#Žnÿ)b{ ê=¼°Iƒ½ËèunpßÈë®tº;L5/k8ßÖPüÛÕç¹ö'qSWuA+ÄÒÞ$à Ҋ4hJ_ʦ6º~-Úmµñ»[Ó”l4l;žˆ·n{h2  ¥z‘.¨×÷‚´în>á:8½õ<’r™ ¥P"‡µiIÒl (yèY œi³{×_Ý_Ñ´šF}.4¦«AµÀÿ&^1ƒ.Hcv30n¿ÏbîD™ÍÑÚ*…s­¦1,Ùдt¶y|Œiwú<°“Šs²Û ° ´fiÀzŠ“°Ý¨˜xƒ3][Ë=0:ðº+Åî5ÁÑõU£+î3ÚW=ˆz§š,yíy©‹«¸^$±/8Ó½åµQTÀèŽé/]ã‘„NÈ( ,½®0ZÆî{Y‚…I›c^À¯Œ©î2>²btï*AæÁƹšð÷ ‡uŽÜ vÇò¶+_ÿ •GßΑïfÒûÄðŸ}åVZ[ CAEåïCU+iæoI˜GLœ»Ö¤×ËV'q•O¼Ý3N#UFÔÉu=9ßR§˜Ý ×9šfzâD=G¸I•د¶O¸l)°‰epK\’±Eís7º… ú¥móT¹F’:2œ_Èè‚Nì ò´ª6²&Gu‡–ªjÐT¶¶ëaàŽL ¬ØVb mÎŒ+õ–\hP"lͱ÷< ’™k'eî×ïˆöÛFÁÛ(‹‚(ÇD ?SÄKüþâ)¶ï‰ãç @zˆù1ÚðÍÀ:§u-0ŠÎ' Ìeu¼„ÄNéÞ[Ì«{ag«Fç>òš²ÎÎñ%šÀþn ê×R5Šà3aÕU¼Ó™éSeBwæÏ›‚#þHD•MЗ¿eí Æ[­¶j£Ù2º¼· á0ÖS멳³¶LQÞñ–ÀÎbé«QS`xõÆÈU-6¶Ž’â&ÅöE©à7â~"ȰÍLÙÛGw_·{C/‡½öZÁÞS¯8§«mØ¥[šú¢)_ÊÊ’.®òºçŸs ÏÔI`x{åBz€jw¾ÇÚµî­À¢?n¯eÁû-ªPA“öyÑ*\TÞH: D'V)&™è{ÃÎÙk×ÇÆWôv­b§,“lRß+(ëSeÀ7!ؤO'Iñ£®½>Rƒ­Ê7óAÚ„—ø–L‰qòÁdJ*í-‡û±½ŽM ެI¢lRfÊ´ÀŠîüŠª±v¯˜}ñUFã»Eo»`i¤DÙ$òWH‹¸dØ´'@÷5!öjZôS±‘ @k¿­AiÛÃF± š,Ë@5_¢Á‡yö#µÉt»GÞp®Â+‡1ûƒi'>¼jqðÜ]×åMð?À‘ý®l¯È¡ÏP'ÁÕ᥆­‘$cñùØH0Y™eëh¨W¶7Èn’ô‹zCQr¸(;ß÷ÐÜpª®±AÝ›”V{ÉÍ,m—5-Õ/NŠ“Žˆž‘5‰!Z²[¤m’×Í—‘q¨£ëFÞºõµb¼C÷é¢HSÞÃösîÈf{sTFs Qê0×mp’×qz‚aPIwÏØ~´Át汉QløYg˜Rê ñè<$¸?èT ͯν¸ ƒapäÊwGýkOzVp8LÀÚ˜ŠE-ËN#ø»ñûBlI›<ÁPÊCcR)àÈGéæíäú³ØþQ„͘òôŠV¾%®Â@Ñ1JÚ…&èÓ…îuÜy#;‹Ýžr“µ!Cã£ö;ß?ìæ‡AEÛ‹·(¿F hg~¨=7²Vˆ,Nôf‡Àؾ©âìŒÁpN­ äv6&ö“c`OgåuU£Õµ×v>âÛZY¡áœ*½3v¢ŒZUºoSÛmà(©:l§3«ñ­Z­C‚û H^)‘ àv²iþót‹·¾ߪù©~ *A F ÅzÑ$P46-5ÞÄ•PØ­y04èÄ$¶qÄ0Iú75>M[—za*qk`¬F=Íy1ª EI¯°öI@½ó•CÚG}(‰ƒ@MïV€tnjðÁƒŽ‚£}ÆŒ£Ôñº‹ÅÕ ÂÍÉ…ÈS$‘ú–ýª‘^CòÞƒNì>b8{Ïð"R±c€Xï™cq÷ÆðÓ~PÐô†í#ÃÀ¸ÿ‹¦jûïYÿ',€Ò?«£ÜéždëL“æñ¼[Tˆt¥JÝwiMa‹ˆ\`Å_ŠmÆ3¤¨ÎuÛjx}.€7·×Øñî=#uÜSü~"®'5H¶I1˜ Þ·}DwÜS1a¾UÕ‚BÛ»,Å.²‘‘£iÛYE T•*¶(ႃ.±&U·–‰!í¾½u¾Në‰oÕ%¤¾AQ€‚U&3mu†§:H]€_¿Ó$ò•Ú…—¤i¸—{Š[ ©=ß]iŒJé\g4êƒ{¸û =XÐV3éƒ|ˆ`1R“Äg 9 kTtòŒF @CJb@y[ç_R©ò‰¯LVÁ‰.0ò¯jT ™P1( ¹ÊP9· hóa þ|—=¡[=Êk{²ß#àh,1Œö9¾0²@,XÜê CгöYH+ùÝ»+Ab0?ó€zH ünUìˆj•ýçQ&ähôÞ¤µ¿Ý¯'ƒ~½0 i0´wGÞ2$·™€óH„¬‚£0ˆ•qk írõäï@eÄP¼’IAuðµ‡Ü$É[¸ZQŒóXà ÷쨞œ!J‘‰ö®¢£°é1ã©w¦* 4âD”QeB²ÌT pgäŸéâJº³7¢ˆ+RYÉý0#@­êMÖVjt=·åÑô_IßÀ/¡ôz4<½HG$œÑ ¢ÙšÄúqÉS× 4áyï²n‡à¨S^¸8âp ú÷çÚ®FØû2_eÖÏÒ™ÿetl$ÇØ›öì] oŒÂœZ/27Æ~XùãFï"ìLú¿¼xT 2åHö,ŒÌÃ×ă÷po.mvÀÂp!*3s_þ›<•_M ½oMâæG¤H‹ût#‘ßÔ}'‰‹^or›à]ŸF0þT·÷ùÁ´ý´«³ï}ísê PѾèÖðí»0´ÆDàßJ5‹U¢ µ¥®'uc9û"™$Có¼"#5ž¡PïÛÐ1χh”¶Bd  ³q™T•¼Qpœ:•ÒL@"[=µ*`s-HÍÄàùmÀÑu¶H7øÅ#NÛÆ2¾K%I&=ÓÒJñ®¹0Â|ä‰ÂHÜe _öp ëKý {îkr³TcØ6y*^¥û~ÅÛ߈½óÁ‘¡<ªÃË7" IBQ½æš×骹€îøî=¨ͯÙbàbZúáj10‰é®Ç(ø.ÞkÜáÎ,y¨¼eÝ>w6Pßú$ÃÖ7¯Éc9¢Ï<õ¶ÄsÜÿäwzÉŽ,L0¢hMŸÔÚ^[®•žœ’—OD6»ae{lMKa"b™XXiŠhƒþ‰ÁCp'bøŠ2œ®æ4Þ4Cb•±`u }¿ëSñrÖÞ¼3Z¸É8 ³"GØJ_dJÄ ¡ â}eKZÅ:à…¶kdTÁʤ·ÃE1}€L7%šgæú¢à?3ôÒÏß»€32àu`IßIc$Túèž[#o©åö_+7F½¢PÃu´‘8îA±Û lp`GØGYôÄa0å³ ¯S5B‰@lÿÏp#Á# ]Ì 4t„ERÚ½JçFö*šÙ:4üXÜû^õŒ'qÁâþÌú¹m}C D×¹xåɾûlô»ÁFn¼6Þõñû1L¤r]­Óht$Ž–½­çG㾓£ß£¯Û't²Rrá¥ Í ˆ î;öø>bš¡í,Ú÷ÆlkìÂX*¨T0‰Ykãx*”LF¿ãš+_Nƒmb©wÁ¾Í%Ò_^ତè]HÚ4údjð·6ݨ¯eœÙÄâ¼`Wp†½…A÷89u¶fBFÕ À¼)ú{CÇòÇÁ ×ðÄFUŸµabøUv°Sð=S=h€ZÛè2¦Œ7Œµ‘ŸÁ7ø-pГ!”êö€“Í1˜Ý‡F›ß=#0Êlc G”oÆCÜ£dLv/6ˆ†™§ÎJ‡t4ãÉáŒßù>Ëx(àú}ªþkŒ&Ö;ÂcÁF¾ˆ;g‰ÖF÷k‰À‘Þ½j“´[Ëþ}¤Ëß+œy`†Jâú©o&hTZ¡¶þFü„8mõË7 ‡ØŽ¢éu7ùèÕ gK˜]Ä#ŽGWŽl,©u܉âï Ž®¤Ôåý ûÈ´ .hJj6óҢ̋Bºa½lçPÒ5œ»I×ÛÑŒüµ Ö]̩֔©®+“ÈT•¸êËöž‘)õGÄ*¾…×û?Í6)Pd{N'e [i_§úž¢Œ”±Í-zƒñ;÷•cžQ2/ B98²ó¨ô¿>åY–eªš+S+ÙÁ ÂP€²N¼û ´ð}NìüüÎv7pPò½ ×=.©¦ã&K½ÛT׈$Á*-k« »ìvJU΀17/C‹ ¥زӄÜA0îàÙŽ½ùå`:¦"µêóe½Ì*¨›Ðï‘Jm"´“)ë7S“J·+_œ)×xª\ ƒéW˜Ñw÷£N†Q?Þ}•ª>#£6”íG&á=bÒ£±ìR)4Ò×ÁFÞ«÷¢óuð2ˆkMU7ê e¤ ØÛ áÀHG€¡¿’]ØkÆÄðÐë_2¨0×v0Ñ}¯°óÛQpÄ4ôÖàh¿j´Ž".6$¤ôFcXSîzÆr`zÜû™#hÄž<Êz#˜‡^º6ôÌ„´©0úV[ÛìÙ~H%¿%!í›”¶¾,S‚Þ‰Þ1ÖÐVÆÄ¤/2AöçÃ*¼Àx~H°ç{§Vîº×¸{'’‰™ûP2òÕæh¥ÚÔ 0©„6mÂ>ŠGë³W##b¶d›2CŽ¦Û ¶’Ev›}ÛqúÖ`m^§†dµ'ÈÉ%oÉn˜9mzRãM²`­Á`K¸kL5YŠ#¥l¦¤ ÂÓ7­'U¸Hx¶ÈœÁƤù>¼v"Ábgª`tª§æ¹€X¼”tP±Š‚p²¥!†Þ^ñ$uC"ˆ4ÂêÞmtÍ ÿE¡šEÖobJZ¼Qzt46Fž@¥$ÔÈæ¹38%V°"“Í—ÀÑ7}Í÷¾7¼£pÏyä;ÉñscòçÑûÆ·™·ßø=ôÚà(C-t½;Gcst5‡wÁn¨^éf<4øã‡Nàl³„ œ“_vßÁhú ”éæ¼×Ëá嘕¨CÔb‡6tÀF½,¸ÉÚ,°ÓSãŠNùÌ8OcßðÍKPK+ùŸ¢ÌÐÔÄ*+‘i$i1’*sgt0 ô:ªqï³µÁ3ŒN“Æ·XQ‹0ë첄4J}ôéB}o@…LÛ¹­ä:nô±ý½cÛPI¯{¼^ m†”8æ3ɬ}÷§ØPëŽl»ªi·£Ã—õµ±ÐÖŸk"¢`›Ðyó)ÞRåñëÕ]M$;Ç$âõ\\/ì–Z7‘dN’dË7vÞÖÇýгã¿k3Ì(“€íÙýÑVF(¡ìÄ‚@ÉÚRLBùÈs;𔜠ÿ˜ÊR’àƒ!eóWŒ»÷Û ô¨uÞ*»9¼7°{^2 ÍT'¹÷ú7}R›¦ [qßT=A|žWí-©ªÉÑü¬öÑÑB‰º`î/*/®±µÁ¼M!8W‘T׎%´£§.õð¦—Ã/LL’ÒØ^¥Ó{ã788@ö¿%ÿ½©ú‹£oQb’HVšl³ÆÙ$…ßOØòÓ¼Œxãä8«‚¤_íÀÖ½WM…D+aÕhÁYà×ÌsWbôlÔ½ŽD9"59§ö =P êȨ6;m .^#®pAޱ¼ÒíéVj]÷—8¶™<ážÛ¢s@×€Gï=è3=Ä¢£§Üãlj¨×pÉßë·Î5¶ûJãU}€ï}“/$”u‰%¿{c4¢Ú ÕãØQqŒ+0H?Ðûú©7Ù ›$Œ |ol²žìÆ=ßÐ(fOî:$ à¨{K·à"áSÍ×fÍìmÌDS¶ì8=í&è¹a’¡`ÒBâõ+[)î ›ÑM6Ub ÉüoÅ@mÓ+«Ä7²‰É õÈMß ßO²·‘7Mc¾& ¡‘ °r¦¯ v²4 šË¢@›*‹ÅD0 ¹ÎÄD—Á÷­àzëÑkcÇlW"V$u"™SˆÇè2U°ÒãÍ3옦2P dO…ÉxD¼F…SSÔ¢$E÷ÅWœ¢ïtj£QbTÉ|ͤ¡Húîàß8Š|Z|k«ª^cç\¶ ‘‡ö®}®¼³¢ØA`–‚!–¦Èü²yÎ6{Î7*ż{pôÀ†-$KgØçôýfRù½`9LäòúÛ²{ŒÔ7ô~—˜n"Ïù†I; žÒ*°ÊŒsx’âú ¼ö}»oqªUìxìé?CFµË:ßk =À‘?Ñ(éêÛ,ÐKF»˜^˜h ¬ ª7më‚IÝYv ºc7´þ6ÀŠ6À°R6¢øøb)P‹¨©bll´µ†EÔVÂv†Ýþ´Yz ü~Ò¹€0º1³ „Í—F¬Â™6ÓD0^%2M¦U—ªib6U£€5Àú*’Š¥·Hò<ÌPa‘ñ“£ëD‡<ÐϪ` ×z}ìÌ΢‡ŽÙª¸õ'D øÌƲj‰ÅíFËèw»RGéŽÃ(@e2çpËÆøÚàè Ã](uhÑ ä Í 3åõ, mLÝ'„ñ'ù°uoN¾‰ŠÑ#4ÝGûi0's,!@Þ÷þO;×EÁ®w“µ—vÞçI û+ñÆqê÷yì º0’—qy¿þfÐn§„5'ôf‘&›+™·¨ÝõeøàÀ|>ó?É": Hù—:¢k‡hN1äõ¨ïVMè.Í—S,îµcœ–lf1BTª÷•›J]wõ¨Ô²zM»l0ghRØ«(ðô2ÛÏL&õÝyþÖìûó¤gJË^ïQ'±ŠŸMùã@oà óÔ™%”²òøÕ¦µ^SÚÚ914ÀL8&èñÆœné }$´àú%TcûίõÞ73ÒòýË‘ì;6QA­]n¬0j*L³»vರí¦7õ$òĤ9µŒ¾§0P'+SÝåYÁ¥H­ëhÎ=í2ä#©Ð$Õ/-’½~§¨Y^§\½yk4`³öi%DlÕ‚AàÜTØÕFñ/X/Rjk¶8U%F®- úÑ  «}}°œKz¼êƒrOolþ®CÏslñ?zQBé ƒUqf¹Ž‰ÈÏ‹6NC²ø#øL”5¤÷À1™V ©¶> ]vÛàˆƒ`Á¯·YŸ_+³¤©ö@êZx܈+®H_ Šd§G½ÎÛÉÎ'—’6cà^÷éÇŒøvW§$·îú<£7úŰw¯É%án¾}Õ žZ^¯ã ÚãÉ‹4BI®—:R"z8åÊÚpêÑïn¢ÒE÷ÔS†ÜêÉL Ê/8LÎE¥¡ãEÍèAæ>:ûHe cùÇZ÷R­û1S3À`Ÿÿ*XÀþ* 17™k EªQíªÆ¸º'.ìòQƒ…`…Ñ7’=zkµÊ<6u®Õ|ÌŒ·d G¦±‘zJ 5b.Ð* ¯ž&hÙ¯º Ý*‹Œ|•mu ÷ˆæ—¼vÕ*0IÇ÷بÕ$·bÃ&þPš…š^¸RÿWËÔ}!2iMGÉŒ=f\Öïè+âÍsëšmʺªº°QVbSŒz´¢>+߇uMfŽ;Žìùëv$Üøû#àèªõã*G¾ÚsÈ@ßÐ2{_˜ùÕÁ}<ÐùïÑñô[÷ }+Çü1#Þñ˜ìϹ›£–<üÎqÀèuôKùã Y¡{ž|dÅU®Bâ³Lb=вō*q ”ˆ˜–?"¬îZ$c—ô êaW~Ž<Àdrés¢ÉJÛ ˜þ8¦O¨½¯šõÆ É  Íó’uÉ­tLœðQ=?´4b@z3ZŽTº¢qœ¨xÁf§l9--e;žz}ÜÓ ÆþpFÕ{å(º#ý@` kªèabÇUKEuŽ ÓGÇDTÓµ±+’õ€~Œ"¦Ë˜þ6 €H’aéeñ©ï£X@F7‡]ï$3 ‚hò&—Ãd2F°#Qy0hÌïݹ‚„û¿7ée9ü#ÀÑѪQOJnm®c¿ó©9Ǥ_¯ôôÆ@m¨â~çãdQòmMè©õÒ+×ù Žƒ²P4vÌÈj'£Ì5ϱ*l¦Þ5ºÄî¿fï!·Eì´¡¾l÷=™Çczˆ@ÁW\¿õedOÁ^=¸hâÀïî9Â¼à‚§ô žÂœ¨ñˆ /Åšƒ‚yï¹ÄITPD Ýüàºw¸ÄþúµXN&%Š ÒcÓ"U+5T"6û½ CÐú DaËBô=G5üZéM@Òw¡|b’þ¹mþÚæ;¬Ÿe–iž£4Vóûú<5[XlØ|‰ ·ý¹ø»È`¼"q;îª4mïaÓíú^²@LWÇBS6ç ÔT„œÝZmÙ¤ö½2Æhn®f=1W±hh%J^;蕪£ù4)(AYßGGÁ¤ô"PÙÈW‹«p«JRÓË%ɽªtÑÞ ã…vâ6‰Ÿàúc(Êc’MNX$RYA°{¢×ùEi,5z@Â>˜Úl#¢{‚££Ju‡ÁÑæRѽL’q™€Ã ÔÑŒ÷~|¸±ÇˆB 28M £ŸM,48ž ·]ÿC’àï]'*¶3Wо½¡¤ÂÒíFŸ]&öÆ.ßèóŠùa’Ú“c4uT®%Tp2$(q< ‡3@â˜x â@Àž¨±Ü»ÜÏjfÙÍÀ4VÇ8>ѵˆ š V{;»ží°U‚í}M2¾ˆë ÅyÅô<¨DtÝf]…Ì)ÉÕsf„BÝÄ×Ô¨…M3¥ $—&ØÖ%’*ѰãifI¿RS€Ý¸×¹íûÛtlpž)Q"A«Ûùj Fëˆhv.F,p@l‚Z™±B²ûë|y$s=ãb,Aµ¦ë×$\)§“È—g‘i²ó [:½Î`ÕóHÙh'¶§W"7HòÏ$R@’휂€¡(‡H£ºhâàí€c¿ ‰Ç^w¬ í-±ªIsã[€£‘Þž!`Î 6ÇߣĹ$à]kv#‰áQ yüüŽÄ9p빞OQu]Ä&HFN®ãšðãx½50’f… (s_85W±TÓ=yûÆŽ×Í­^ZÛc´ì‘odÐCa/&½t™ó²ÜÛÏø``¡1Ÿ…3 M”nÔÝìK RÄ·èDÝÞÛ¾DO؈ø@PØN™å`ž×ı åN´‚P]G®ª :_Pi*SÆö¶ÂoßB§jŸC0ÄÜ@Ÿ1 “J›r¢VËûz›Dí|ä ’Ú ,èµé¹“EÁBSR”ÃÜ]¼´˜öû%2Ù’H4ëþ’ÈyÝ#ß;ÔMxÏ0}L V@ÏB ¬jÚ\yµóŸÞv…Auœa©©½SùþiH'ˆüéW §ŽÏ$a©—Q6{}Fh•çüsŒª=FhAŒÈ–z™Ì؆Ÿô™Ì^eÑ# ÆŽOÐô•›ª‚´^[¯/Ò°ŽF$¤®Çh÷Gip3Žv¸Ð ý¨RîÐÏŒXÉ]ˆŽï5ªEoqÐ0º³Áë›yFE¾.{€zïx¼ò…°»ÞçQrðÄÃ9JÇ-· áHb–+a62¡«ìJéjs‘1¤÷މz×3äŒÑ´Íà¬êåºà° 3z­,4ÑŽ!ŽŽfãað@Œ3ñ1ƹsÔArõ²©Ô©X™¯؆ÛJÛ{~rP S-IÀŸRW2M|i%Á=pCИJ^à$”GPnF>œßNÔ´r_ ü† &èzpÖ4`¢)ÞASêT|ïdÕá«®¥Æcóy|:‰|>CN ûtùÕó|ÌÉO²Ä&97ìe—07‚A hÔ¸Ñ (øãûJ'¢M™±ðFW^ÞI›gô»qc—¨a¯;$Üö1v„F«GzŽzìVHä‚p໚þßFûfDÁÆÑ¡Ûx,òÀ›1x¼Fº?aÒÄÏnÁ8z%p—A® d Øë—…´)í3K‚g Ÿ .t¯À~Bf×ï(©*1 ¥Ì=q>ad»Ž!š[k9’1ã+ |xäÃxmº¬Å)uŽOÄÕ©xxÈ .k’}Û {¼µ¶4*Æ"¹©h…QALäñCjh샷µc“Õ&ƒ“ ¢s@4áTÏ—l¹ø~£¨¬‹¶™j†&:¸Õ˃å&õžZQ5+*7z€…ŽY¨ûNMsdÔÔÙä»Õ©$ )µÎ L¿‹ÞèP=pÀÖ×ï+j\9+cD¾HX¡Ó ’Î…¨2W>Ÿ ŸŠýÎç‘irÕ /‹N),îŽæ–%Lк"ûãÉZqvH*ç ¤àϱݪk.x… Žo@¯ u«G]ÊÈ1ptè|ÿîŽÖy—ëþÀ¯¾ì4ZO¤G‚¢a©â{Ô<>æ½_§ÛÖŸMlƒgéÐ÷Ú@tw>Ûë‚¢¡¤ybôšõ Éàœ¨{OÚ{´s½ÍçôŠr'ƒ>zã§^µ‹ñÞ¶?(!Њ Øù±åf‡úŽ(c½5ØPAòswA2±âäÍ ‚(*™Jzƒƒë½Ï’‘MA×}TIrO؉gИ?šd†Ø¡ ]2”Ìrµom(np¦®hÀ»™E˜r-› Ü>7Ôž« ö^.JZsŸ­ÔÐTP`½}"õ ƒ,#ð²Ð1¿V+iãáã6J‰zšzÏ+Ã!8—yÉ‚ó½L–ÁÁ„wäðNŠÆŒ%'½µè,âìZ,`]ûhç E(¥ˆœ äÓ òTj¿d¢Èב_½Ìb&Óä =“OÈhú¨÷[` ‰p¿½dƒ½T¸/ÛGUKi«¬¸®Dz qb'Èûðô\a{UÍ# ÉÑ?z€„;¿ïÚ£RëŽŽÐØ²ãÂóa¥54õ{ö#@Ñ‘rðû‰21è }Œ=„šÃ“ ?—GþKHâ2ŽH?ä+icY í¬¶çôzöz<øºi(2Êû`?îçøZ,°>Qﻎ?&#{++ÿ<ôŒæyg~`5†—6†t}~³×¼ºœAe¨ë•…vÌa“÷CKF7¨Û¼ý‚eXÁ¸ü o XbêŸÉ%ŠÓYW…˜ì3†Qù5PÑñ-Nž•î¶}âÜhULª ‘ƃ²©M=•Fžo.·,ZP1kЙé«6¢jü„ØJƒ‡^H>«tí¾ˆzn¬Â_0{ùÁð3ÔÆ$¡ûQuµFÀÓò(‡´æ¨a“2ß(}Ÿ”"ãÚJ3‰RæVpôT ßëÏeC_ž!_^(áUÓŠœˆ{†^q*­Pûþ¶@Â+šã%ôš¨ºéXéƒ.ÆàG?O&Rôf± ’HªU£‘Fd _=¼‚t]H†C¸ëpÄÑsºI’ÉÀˆH3é{¢×ô z¤¡ò]5„öñއýfht|›3}³>£_~¥misÓƒQ£zçföÆrf%ÁKaçg 8ïÁ&Œë>ÝfgŽoú8vÌÿ I@ 8ç-Ä®1;5ƒ÷ ¨‚ŒfhV©3ò:ÛêžTcz@5§a b “Ñö´øXËóLgââLmn|Ù¢z~To »—ôªk:äϼ-×D1”¯Š Ôªdô*óMû̘ñmùïÈ` q#Ê8„Ab$œ€¬g= Q™8 15ã-ècR?[éˆ5Ï®"ÉÜðs&Pï L‘­4¸ Ô%˜ Ð" kº«á0–Š)‰œNïÎ"O€¨^µås“›dTUKpuvï$ õ†ëd}ôkZèE™3&›*óqÌ€kæJÔ'I1Ö¤×S>¯Q£‹ÊËÑþq€ôˆêÑýûŽà&ÍЙ¥Ô]¡Á󀃊‘®|W¥®ÔïåPÄ1 ÅÁ§|ÈòèJ¡ìüΔ‰D®ªôð8ýq£·_زG ËI]yêÌ] üàžŽÒ?ýøÌÌÝ3À7?§( èézCæÌN´šƇÉ\›àÕK¹"1g…4ž(¾B„ ³¨*1ä^%I$¯õžb²ÚxŽoXBð¾$—´ƒÀ„þ;¹Ø¯ ï?ØÚ6‰â(¾°ŠÀº~-Á $ ï$!PŸc\Ó¢™+[P ³ïêB›ÛjeÝwŸ¸ô>™Í#†ñz ƒ|š.÷Xé-;á0ÁΊ"­ Œü‘Tp}…˃ŠzËÈгÉÒï¥iêñÑPyãUùïÑHAOš˜ÐÉkl‡HÈé$òý r^¨è ¤ÈóEäªì>+Ø-}´=( å¨‹Ì+Â3¨ÌK@Ws•PE՜̻+SØ‹&OÚÛ)q¯Y#Aì'x0ƒ‡Ža¦< ½¼º8â‘ó¹38êysG~<=¥ºÞç†@Q”<¸Eî‘£½qï7d Œ4ýÎ:^ï7¬¼9dº38âkÌÍ». vnæv’¸Ïíå½ä¨GûŽx‡ab¼aü86Z9Íü°ën8µ$Èi”‘¢Î¸œêHh3§."J\)’8ÃÞÙ€›>™4ó*1…ªYx£¾î?shqÿÜèYWݘÂ[´fA£s`g„ü‰¦Š¨+E™z–!~I8Zb°Wçò Ñ5ÄU曪·Ñ¸Õ§K¯3ÕZ&¬‘~on‰4&¬aP’ɾçG³”DtÁÓÃlM0¿àúCÂ$†HcäšîvR©”¤¢U¡Ñ6_°öÍòÝO'ý}eýþ‰"_.iÚ ›uå£I8øþ­HÞ f°ó¾(2>_Åòk›ƒ¬y´UÄLMi£À—NÌÆ8FgYÂÄÔ[®ÈTŠßØ1^ =¨z4ZÑÀÁ³G¼òUåèyì½w¸‰y`yË•ç•Ã/ 1‘onzóu]G@ã½Íµª5péÐ@6 ¾ŠtTê"–Hg0QœÛï­Gß﨓 Õïïyqçš÷M1ÑË&¡öU6}E¦¡*Çšì/L2|†9“ ÔmÚþ‰6ɶ_37yÌ@Röþèå  O£‘ûîI»~ž¨*°¨É°z´8ùc: „]ªö&îÐþ2®zd„.º¹Hjð£'¯5ߥ˒ÃÅM“Á—›œ9}ONóx—û¯ÆVµªñó:.¸äªÄU­,Z¾a+„°ÕƒHnÞKµvG´Pº[‚^Õ°&ž*BÈ@kTIs 8ZYcÞÜÓì ¨@ÁZUŸÁ‘Ì‚ 'ȧÓP¨À‘äùEä2M2]ê´ßî9'&r‘u¾ºæi‘€úå‚zW)·IÄÈ•9’¶F¼;ë,Ú1ê)NYuÇ)f­;=’@:C×H|‹í3ýT§ØÝ6Ý™Zwßl9âÄ®¿î¬‚êͰ£@>ª ô{\q®²¼ŽT+¥~öF˜ã}Ë/ŽÝ'ú<ö“Û<ø}<Ÿ¨‡-Ë}(¦@q'ó:š‰N> аG•Gßa V'-¢»sE—ЏT7tqçhè2ž¸=(¦ÞJù­â!)¸3TîÞ#ššö«˜fÔ÷HÚ^+Co z×èû‚Ö³mÿD’ÛD þè€Ú*6Ž)OµË.ÖMúÆtÓeiBæ˜RÄ“ CÕ*ÁÖû%KÓø~™¦‘Ÿ*ØE \X²ÄEm¢|/Ü öv1;Ÿ€ªJ¶ýc Õ—k\Y„r:9H)Ú€\UϹL”çšdeš°&YAêšê“$Ñ|H®Ê¸ÊǺi“3 W¬ÙjO¤!óBŠèyº"¤: éA¯º–ˆ2Ü<Ϧ˜…Ý‚ònÀê{ZߣHn€)‡üŽpð-ØG¼î”ýƱ¼3³X¸ÌÇQåº[0Q/ÓHß$ï‰!xï/K$ñŠn¾`îõáEÔ6Žß;Þã±bð;Ì-^17ˆÖµ=û‚’k0L Š¥=5ÈÖOìSÒÌæëƒ°ìÜr…8p‹…Â炬ž’6ÃG”5“E÷YÒ )=‚Áà"µ<ƒ†í­ÈƒGš?EšÞ#ñJÚöžÆ#)¡¶DªVJ{ÕáãÓ*^Vœa2Z@œºÁÚ3b‘”´$7ÈlÆ=5/‰}'|UÕ`÷]l´;’.ºg0“÷¨æ¦¹{Õ IÅ I™ÌxÐ ç&›eöoD¾;QžÎËó^‰²-R^&‘ 9Kz3’Ç „bà…@ØOÀU™Ã~²íhª8¯«ŽH½#y©ûâè*€á@>¢ž°Äˆ°‘sz6HuyúĈa¡¼;ù㎇¼8âuÇoßîÕtÆ?‹^&7Ú Š> “÷dªE«†6„$0U ¢–íé "^Àè¦Û¦+Áh˜$”;/î[]2•ÐÑñÊT!]wAšо±æ®Ù­¤p@eó©«íd“Å„mPÊâyW&q´àxcJ—%YL¼÷FÒsr¤·§Uæ %“ë÷‰Ã¼´|33n#S.‚3Ûç &ÜqÚ vœ˜Ž¡ #×(ž“Dç€ÁsÖ@VZdEåÇ:,KjŸ5V¡‡åÞNš¶pã’øªÁÄ;‹ª±^Ì7¦§›š»ñbÔw·—±öòâið)Ž‚”‰Q•h%j* ®MÚŠE¶xèçàMYa׿¶_¼U¬ÿª}G§S%A¤ˆªmßó2‰ÈR5šêœX=±¸ã²ì*J‚n¥ž^ø"T»‹PÁzå%¹Ú¨¯8Eª†²(AI¬]jÑ€C4&xmDæ•Ç£»×H=ûÃ8ê(ò¹38º¦wa7ï!P¥‹ÚˆpÅøÁà½JEUö(÷ÄÉ:ÏÀù]3]$õBÆkö}€£±¹‹<1)hçA¢®)’4ïá ×À$a=2Æwž7w®o“°e[ßPû¦ñ¢AÀˆë]WxýÎÄP"tšGB†›ÄšÈ|ÏûH$oê¦åÅGÉ>˜Ü3‰e_Î{W~–ýŸêË-z¿±Mè¦5AÐÔ›ÂoTÏÜøs~¨¹sëD"Zà•à‘²A3ÒNº&lû_˜Å²pÍ«ºB3“ÅW¥LïV Z£@’*²ÑѼñÁ¸´Æ¢>ènæIâmÓ˜¬²¿ì®hÝD¬˜COºhçÙRB¢œEWÑ¥ƒþ/æêðŸs‰´6 Œ«F!ÓIû+›~°ž L0&B@Ý‹í­I±Y|pæ¥/Ó ;PJ¼*…}…4ø«¤Ý{@õè–€L=XŒ¼÷ Fkä„iba7;°7"1WDiØÿW£|pÆÓaF+Fb,9v¾>B·æl ÞÙ·0vVºŸM£ßýH*Ý0zØ¡Œù{’l¥O¦2Éõ$Æ™e#/$û>ö’…Ù1öÎ7HÓeЙÿdœ ¥W-PçàÉßll£È6S°“VY)­±ó'ã쿌RA²û!’Ò¬ôÞ,ânJRbÅ7t@ à)Ûø@r_\ÇÎÊÇPþ³4ýHù3€’ÿÎü0·~{bÕc†^&¸Á?hÕ÷Ö W}f4[@·Ò¬š,¿êUf<ôýI(R")¥…À!Sð߉и€×ɆGOYÀ+­àо®æšÑšÆi…H><ì™bkŽå3ªõi•åÒa!—¹¶õåÕËžòà å墕ë‚mòsß][p¾”z³ìTõ/ Œp­ô¶?¦dæþmƱU èû‰vÖy#’‘Ȳ €Ño)ÌŒã +HCFÃÕ£ôµŽ¼Ì£ÀQžü³Y:½vñ!å.wÐn®nÊìô;t‰##2v#1ß$4¼b*½oÞÎÅ!­h\ Û?ÀÑñ• ž™Y¬}CÇã+ù#Pk.Í~xOb›ÉÜa „ÀÐïËæoÇ,Y+:ê–É5\YvÖäì ¹/0Љ›¶]_“U«Ü“ãîQîzv¿Q5,oÚöN?¹xD\þFøtÙ(Ó¥¥®ÁbƒR¼‰kS<%bÅÈôÉjX\«GV¬Á€œâÖr£@anšGÍà{o©¨b ‹EítF®X,c¯7Ò ‰XémºÞ%/oHÁM{ÑzCl>%mÖÉô¸ dÙ|Ä)î\ƒ*qÇl°Fäë°-„D×± Œ‰ø<ÒJ¡|w9¹¦eÈÙãˆËÌZ …'¿†·¥H†7.’7‰A;†@M“;ÈO¼å´ÏPìÓ ƒßd[Y&MH(ï›bÉX¨âˆ× Ü<áØ~ô6ÉÓ`F°ö#4Œ–5^Òܺ*97 NËïŠä„>Ó–U‹À>(ÂÃDÕ{[?ëŸôA"HäÁdëÏ‘Ê?ãÚÁ}– Áûa~÷@Xá8DìÿâA/(ï2Hºù1"¾°_­iQHv¼iäûv’©Òn¢Ó·`Þ&—4œæ7Ozœ¯Œ‘æ@Wî.£«!éM¿&,%>[ž|?Ͻ”Uþž¸×`Dip·Ê0Zí\ÄÞ!k²¸%–ü7z )e2åÖVä;ê;ê%4ÁrÓªl:U_T•Çd ½J‰/¢ÐV†áh+[°Tàz-ku©o2ï…+“ŸŽ*z3ð¢ÈÔìܳ‰2¥ -ØÀeï7нʟGÑú¼T¯¹¦²À —ÄÂF½çlzª@S‘ÕÆÀôìÕ$»ú9Á‰«0ç¤,Ôm*`b¡ÖQ¾¾ˆL—yÜLÔôot6 ?ÖØVÍè³¾;†¼èdô"zÏ:Ïèè›’Tw$>I¨€økTù¤Ý/%¬Ô½ŠÔ‡C²` Zïv·  û0ŽvÏ€½¯Î€[+Gñõ· œÀ È^IÜÊ3L Ö U€cqðQZÔ-ʼnHͨ6¥Ó5Ås×Dóz1Žk -x©Æ±ÄŸß4›5l¹Qh¡SŒsêx€‰ô{š dÆ5¥*’ïn†c`Äš¶&¦£úó>Ña‚òäú þT×–&/¨BIKý+ù|†|ZÀ’ݧçs›(ò«g‘Š\j~uÚž•饛ða¨Š.qäû»UÏ FE ÝŽDý½ìôx®ÝyôrñŽn§“M‚,²DfÒí³´ÏXOò†¥Ÿ°| ÅW€£«ðÏÑáWƒîHÔTÍ¢3˜dcxñrk´Ή^¯_â“8ô „ È}Ï=ƒ‰[AQ¸õS ÖPmðºOÒ)‰z˜x9e7”¹và¿K\Ä+Þ†ÛßVúk„Yç€ÆÒ¤* v~pÊkGoËÔ{²ÇÞwvúŽ˜€¦FQÉód"–%*qŒ$YUzW–Ñâ¢÷d¾£éƒÑl‚®å )¿_Þ¯I×£¶ Œ ”#´ ãPÀ‡îžÒWÝzÚ‹E,¨°Ï˜šòã®pM¶éwDµïõ`R+É•¹:0…óŽ~d»o‘%V½^€þT!ÂÄ*ŠgÈi{Lšçâ«© €† ZÅeüé܃É$ãêd¥›'v2ÂÊ—NºÜ&2àWs‡åšY9ÓIÈÜsT„‚"r>!©0+É{ˆðBá´Pë`{ä¶2'Z`ëªWf@­OKÈÕeˆ"ô ±39v‹hÙT¬Œ? 8üX ²—)rdN«£´ÀÏSL 4ù$}@º'¨GO·EÙ£ ‡/*'†gÅÀLRS¬uüAµ–NË„-`7CnŒƒ/瑽üŠEÞ_v¹™ú´òÅú¾]–ïŸ8:œÉ¯Ÿ×F7Ozvóâ#@AÿlšÕºeý›(r¥jEïЬ»£.²óÙ {Ù¿d ^öî}OH"ª"‘›/Ǥ²êÔU×)éÉê-!hÙ#ÄŠÄ„À#ݲS‰y'S'SgKϲ{“±*Ö\”ælÒTÁ]¥Ã|Â$­¯1û~iÝ…ü§«Pù«¤ƒ2Åïöœ[, Cd¯AÚ~ßù‘mA>§Å+lIhRKÃ{Å--…ìA÷ññ€FXF aÌ<‰²ÊybÀ Õ±”1²X‹4 mwµ{r(’©¡ª¨áädTüGß+'‘s)«b›Dé|Ìç åBåwD%Ž¢M•@•x€Å⤶Ÿ@X"­úIÇ|5¤ %»‡©ÊEA£÷åBÜ%ñ„òßïÄR wN£&2ZàþGû Ý/­íç|hG¸ñ±ÿOï[à~ßÙ¥©¬®I-èÊ’ÛlLæ[$eš …y¨l{œ½ÝÝ“Æq& ]DêZDE©° F´â=ôKý¼„šÐþwÄ–<A”b„Õù1=ŽöÀ[×°î²À+æÅQj+{ç­TÃB*XÃBA£ˆ©?]š¬ö(¨É²ã‘¹¬È}Œcß l*Q¶×ÞŸ˜Â`ï«s*¾P²Âîû›>§:?ôR P®cê2Tv:+ìðý!ÅÆ¼Hìf¹~óNr’P´rŠgžÊ­ýþh™Jd¯%΢ª'x ¹°:S…T"?š  €,!íÆ¤Wyòeæ¦÷% `Ä‚š”>-ººŠ¢e¼é¤|õi!¥> Ì•ÊS)òéD9­ªXöèON«ßÑ&ñnÔ>]Q.3?‚q*" eº*¡?Nèeæ=X…±xh_A'ÝÜðÄ$ÓØ£ò†sßçk±Ç+«Øášßí©äHñ¡çì}ðŒµG§Vô\°4F¦’ë|àœýƒ*-•²&פ,¾uÈLÑz*ñ×§ô ÷=+4/€òˆ@= 'ÕOq™\PÌ@=l’À‰Ýîmø1£wŠÒù‘ S8{‹&a«æÌEÍù¸hhð{gwÌÖf®ÙÍî'ßÃñ;ç=ôzóÃVM¹VX':!í%Äê“ ¨WMôzŒ¢&õž'’ËJv*.qjñ§ãq1.PÑ ¢[inÖEL÷¤-òÄdN£‚EXš±´¦µ²¢ª «€ƒ“öµ \>ž+@± Î_3=MÏ÷+ØZ[-QUªå²&ª>©¥Ášèâzˆ¹ “Äãs œgñ“¨2TœºáE0Ç¢føFA•ÒÒ›ŒÊ7šÒ¨ÂÓÈžâåŽi||ÚkL}«š÷gÎjÄ“ëú5+Á²ò<;G: MŒHž&†«^ü £×zæàœ®‹í…¬®b²ŸÚ%ŒOP †âå×áUM"J®§{?š بké{¾rbw}¦M\€¯/Òp%†HÇnܽQënG"{Ùp“õžMĪ|ií,K°S3SdFõó&IÎHÖºçà1œ8½%tfýÑô ‰¶oKƒ&,N¥K½nöë ŽFÖ´,ñ´Š ¨ÄÙäz”Zî}gWpAç¯9zF2ïäþÜ %‰Cø±,Sm4«lWç‡ï;Š‚Z†óÒ7Tí¥GVyøŽ7œ:ì]Çh%mÇOI'D?1WÉl­Â+Y|Ä@:ða6îhƒæÐ/+¸Tr©þC†Ê¤7ÖÍ[¶FŠ»yJë:¡is é±èªZ€!ÖWÖÆb6µ•"B‘‚ȉÜ˯"ÎLFf Q½ì:í}ÔêwÈÆ0$¢ŽÕqiÚoÀ™ät4Có`Nï -YœÂ\e”Ê\(<•¥¢¤œ äËr¯VšÜ´ÉË[À“©íùž*/vãÄš bàä{å$ -ªÁN;‰’¼V^‚>5¬ýƒL „[ê'–~ÈÖ{K÷µÉš(Á€ƒ• ÐŸë+HxÄf4Ü/„+Î ÷=ÿS¯­:3÷gaÍzSX–*‘ÌSÍ+*(™ìîM†§]ŽTtî1FYÚ _ƒ¡‰öçëû¨ 2±”€¢×Fz 謳ï%¢[ï§É²TŒ"–l,°ÍEöÇÎÕyO|¡ù{&|Aº÷S™`nÍè“®‰ý³ötLb´ä:k\¯áõˆ*w³×¥Ù‹jp ±éWÖö”êdM­¡¨~ Þ[ì=Å·¼kªJjú†¶ïjÔÞŒ C0‡"ïÓâ+¼Ú¤ÇýJqòÀ¹ÃÎÒm2æøKV¹p®&¿¦‚¶U/*„äÕ?A]T r-h"t¶4ËâFÔ¹¤‚Ž?dfNÈÅ%TÀMHÞ wÎÁŸ 쾪8×{dkšóq(VérŒ¢‚±Úï]8÷¥­T~m¸ÊØ{bºZ_º¢{ÈúÍ¢ž0c©å “€ñ¢'þ}ZSW 騤m¿=ï;ZçŒt3b€I^°'>yÜ_í¤ûÙÆ7¤»äïGÍîÔwÔü8¨ì@b·  ©ÊQª¬5•茧&!ÌþæÀè*µ9ëó!óµ(º,x îèƒAõ¡¢nºN¦Ù§Ù~£ûÍï½PÙ4½—–`/˜Ü³•4˜¤íÅÜ›6* à¿£t>àJÜ·ÀÈÞ.4-}Oð2JŠ i˜ŠÖh:¼Ã#wwO ;˜·E]s¡N÷D+v®Ö‹ë]_ü^J›z¼qb€¤Ü*xÁ*¯Ê·£ªØVDmNX*LBgLĺ~®Â PƯbå2|¯Ql°=EXTµ.란§†Få²¥þù7ïõ•ò€‚Ây°Ñ¨é>¨Bøó_1 öÑš‹¦T[ت\ŒÚµ‹ÏŠÈwg‘§Sm7óàkþ¿z¦¼LlÖZ@…›dåP=”ÔÈ@ilÉ}‹°ˆ\h‰0 £F4P8wòÄ7 aéÚÝ3Ú9wÔ.(ô“û¤{"#܈ª†ªG¦Ö b-tžDãWMõªè§¹°m›Ý™|Y`ðò¹¹6=VTØJ‹îĶÆ Y!`Ûx{x\¼m±åȼá;¼ìÌË•y$4'*ð„` ¾`¯Rا÷õWÕ=:]O‰n´Êµ%ÒwÀ‘ "uæ¼*sMÙ÷ø>ôlž#ëʪ*£mþ‘_Æ•à莢vQÀ ôEž-íeJUà‚H?ºù'»ë¦ñ¶óÕ#fi \LÌ福²WÜ”¾;îÕ½"ÛÉLÀEZ˜FœO³ƒ¤JMo#7úÊ:¿6ñxlSˆŒ6b+G%¯Î¹ GòËx…c)£Ð?34bøy– ¹°¥â5 z‰ÈDD»Z·Á_ˆÈ©@ÎeÓ∯t5ÓUB&ç7eŽ—õÆÏ§¡îy‡ä@n¿$:¹Á¤ú­m•’É:Ž3OØD öã]ë£;¨ã –ù¾Q2ûm:ˆ`@ËË÷90ûà5ãÒ«jùé&ÀEDÃuxEB'Z‘]S!@ßÚV´ðçÈÓIäó³8Cv¼å«/"òõE'Œ(#4í©Õ*eµ¦Â-ÒÇB’iLæ9¼ÇQrÏSú ƒu)ÈIfö)ÕÁmdQ_™®dôÏÑ8ž‡vÇPì¾ñ¯…ŠW\Ƶ½Vèœ×Tév㈠"yíÌb g ãž±†®E÷P”¸á—´ŸQêzmò£Eow!LP ´rã¤@œ€GdÀ½¦Kßõÿ꼑;avïVïõ$Å@i Œ©T¿o(;ŒdžbpDDàÉWV2‘‚^õiç Þ“J—êwRúfÉû€ÃöÒRR’€ UÈð*ÐÁ„Aq½5«èBü~:&QdÇB/cLšï Bé$»‡’lU-ËRÎ|eŠ.Â[o!ãÐÅ/o»­Unz“Wi»Ùš¾Î3 Ç”÷ɨRâÔg‚þ£N—(%2‘dö³ ZÙïQKVA˜N‰ ¸9«¯p¸¥"eyveGaÊ}yÒ˹ — €U¦:äa;“]ÿ€è"Íä¶€Þëh»Ï[1…ý¥»‘Yú¯‰í]a™h¬f †IFs€Q¦[$º›E£©Šíï » {ìÿ|÷s`·*: Tšv‘Pò–Œˆ‡Hï]§b扊Àc¾Î<[¬T»Içõ¸L+^îJwP•ƒ§­ÐT}ìgæ#ð§üâÖ¿Èõ·x͇rcR_ûê­Ï1%U‚= b{i fùwØìöFd=#€.Hƒ1þè\ëšÄ.kÅ Ôüˆ„Òº¼g¸¾Çå/Å-б$xᢓŽÊ”à®ôÞ”=6¶dŒut ˆ¨ñ=K\û´¨@P!z!`¡GǰÙ„µ‡BU^n'£„sbÿ­Ö¤iwA¦]ÝÌvY?o«£æz¡ÎFTÄ‚6—©Ù+gž7010¼Ä™¨Ò­ožç(ÌÔÙtÆÜ©Š±“±„CÈW.ƒšFgÊ€6’ßÑ÷[éïÈEèQ„6`ÖúFµŠf_™ë(§ÓÜ{¤Ÿ WŠ^û:Aƒm,ÝüEˆªa"ŸHHÌz£”÷Ø6…Ú[3&<#I€‹kD`(Ýœt¹0¾GâÆDf$ñåv»ÃÉÈc,cÞ èüL\pmFíÏiŽG·Ä™ÄC O–ÆúQÀc7Wìä=› øl -ÚúT(O'‘s™u÷‹{¼E.l³ví`ƒ=$‘-9—¼¶ ¸iFï˜*’±¡DÎ'‘©6 ƒw§ >êæ¼kµ=öçͧBy*S¡œ°Íº&L"rY\Kõ¡Š—»‘ D0ÙgŠ4à[†m (jÑ€©køý¢ ;óÀx¹÷L&<Þ²œ˜Mz~ÔžÇEйˆÈ¹Î“nZîQIú7‡ÇïÎŒ|ØT4Ä)GEæ–‚|uã–ÀJX%µÈü”:×|¬•²%­NáÜW†–màXE;†e׉ ´"ÀÍþ8eÔ‹­ïi¥ª­Ÿi]=Rö õ;°dUHw €¸(¨®wÐÒº¨è – v}Þe.)DµIjMQ|Ó7”U ¶ØÈPщCX¹e‹.Ÿ÷Ï"ó 2T !²=u$šʘ¸tÀõ¶JÚ3øîù>¡”"òÝä´ÌO]9œË¯.¤|½Ôçô¿ŒpyÓ>,]ÁðÔ7± y’õ)f”Fi¿«·ú(Ê‚óíØ€a—ÚËÄG & [·¡׃Ÿ?gàø ¨Ø´E´;T9Ä÷I*q±ˆ©ò#ƒ*Ê:a†³‘Q)K¹í`–“|´(§p6§ÈDÈ4‰¼/“\:is$]18âYQ›¹xƒªÐŽ"—xÿ¾‰"/S‘Ëe ¼µ‰ó –Iä\“< ½"âyŸài;«§"òT&9c›² —©HÊWĵ]îYÿü¯¹o `AGJCãÔž=ÃÝ^¥ìsuu+Mö_KåO—²%n0¯õ'YLH1‰œæùá)#¼r2H”ŽÜD†oIO¦\•'n•$¢/ŒÇöÎpÛ?ÊäYx^©“3lb¨šÂ†–¶Ö랺ÀÓÿÎÞªZ5*®ôc@ÛãT]Ÿø+EV+áãÑXI´glžŸ>›œ!µ9V¸h@È …7ê]eþQ•ìLÕ Y[0`à`@/-0l 7]ЈžÐˆØÊæjÚË ò™ôDs$ª(¸ ѱMõDWb|J‹$žKÙæƒ`ãS¤" ‚¹ Ú´´Ä„jűô4G´Ò’!kÒÇfÖ¬q{ª›ÁÁ PêÑDÙìèy†½x™=NÁ„?¤ò©;ŽEÀ}Š]ÍV…Àgþ¿‚ b›jf­UfŒ j@2Ð:Ÿ‰þ‚!`°S: VÇà{€¹rt."'Ð$'м,j[®6žI•î@I'7ÀÐÛÆAØU ã/ì~?ç Ž¸&rË'pièœf³Ñ#àì=‚™»-Þñ)©Eè4ÓfªY Îe¡)šìà—1ßš!a…ª‰4êo©xèë¸hR+ýÛÑü›”KUzÔñ !KÐÁÉÞï@%öèýÊ 3Gu6Lß„Ûh3‘‚€WOJ1…¤;3\%ÊôrèÓtê!€ÑF¿k_Á)ÀMœJ=Ë‹ Ô¨é^ž"¥¨-tËHl;‡Ùž/.š…]´» ¾yÄ{Œ4·F^\¹öAÒ*f)ÉæZ9 C+FƒéÓ2* ¹¶˜êSU¨òQT‰»Ó4¾t“Q$Ü \ÀwÄ}ôª]mJˆØKHZ ¶q¦ÑÙ}´R»5É"lî=}µéõžÂ´!u–š› µ$0CK’ß÷µl×y*"Ï/´F"puLxe?q ¼³¦ù{Ùxš!˜f 0¬=V3ƒµL(Wëä¦"{}ºe@­çÛƒI›7Õ¸è ;:cû(@ªy–"²,¦7ªªã@’~ÿ<-P‚ùQMtyîµ ,.™ìŽDŸée›Ñö!PY5I@nê0Þ¥ÞOCºiy†’Þ=ˆ>©4Dç}@éûÐØ0ÁÜ>ˆ\Ž.œ+\D0Ö±[D gAŒÓ@uâ›®òðœ*…h¡C¨ù1‰'Ë\ÑHò];ç‰!&ÄÑ~Ïd¶›‚aK0ºáÚ ‚Q¦£Ù é’¨ñ1÷»Ð¶‡BßGŸep²sÞR,=&ÅÊ™Œ9¬~öH3EtI j±?ΖªypÝ`Ð\o¤ŽP¢uüÉÙ¯È7Å7Ì4 ð†°µ 5»=–Î(¡*™qê*ØÚª³˜÷®(Ó„ôfžÑÚ ?Cu°Ln»½¯ýÅ&h1€Cñ®éå3õ0æS-xÙ„E¸ßøíýŸÀ&ÆçRIœdÞÓëœZq• ^ÕLÕ|~y¾ˆ&K¯½ÜºïŸIU ‹ª3¾Ú$ „.œ {L¯€·:lj€ÖÍú~!õ>ÐIqo÷ØVÊüSC¿ßÒ¬Ö´V”ÓÒŒöq¿j5šœk˜Öüg¨MrkØ¥z¯EµYÑ 7áêb]K4ùcð̜ڋ^àpm@sÍþ)ÐaÍŒ‹¼ÔLÝ ä*8¨q¨¨ÄýêÙûªŠàXå ʶÏíÉe¡Ù‘"'ŠLåÚÊ×;׿|gç¥ç‡NŠÕ¤Áåâr­<úoà®™>Vï&»%âø#Û‘ æH@›]3[¿EKp¡`¡ŒBx“àPC¹ÜEx‡-ÙræÔõc? 0ýc‘³<Ž_C ’Å\ÝÃ&£]­CQ©ÕµÖö² øZwáUe¢¨^¦¥áMeŠ·}*n†¹CºŠ†UÍNõ*Š2w}«¥¨þnõ ¹ïœê³‡*žp‹íŠWšh{C¬Ey/Hz/$(%FºËð£-.3 )í‰sÂ"E¬i½Ž›9Æ ÇyP¯ëuMÑÖ÷µp%žb•úV±&¶½åk(NYÀ¬®€Q_CKõIoîë\8ï(‡Ò‘¨ò¥j¿ß{Fù$J >èèXìøž)SIÔ7½oŠñ¡ T-Eâþ4hÒ5½XdzØ@n~`û=÷bk¨Š‡:E¦õßp É}n½p|\fLÜ÷øŠ–Q„ëU‘ÌS$9›@§šdA'ëãr˜[ç¦yâMÒwßh3<ãéÚ‘¾¤·-½XÐÕOß_WÇõ¼WOM3üÈœz«ž%¾É³àU‡Ž Ð#sÉ‹N`±ÌóãVpÇý÷ð÷+ögâý‚ó& c³þÜ12eRµA›ý¬˜5ÒPrzïZéëK±´/“kcŠ€°‡¹úÀjg£d5c‡Ö°f<®%Ïîý*·~˜ìÞ¢Ð! ‰©`ì4H3¡*š;y]n Àõïii[lwz1})ªtÀêë+-«ì· ꢔ)íC6êåÓ ŽÒqJ`Ô83œ4™QgT±‚jn’äh?¤6e¾² IAd¼6a!l,.MÍÁW†Ì„ xiîmL´÷èȧÓBnÖ={-kWÙ$òù4ïK¿|^ªFø ^‚l•ƒ‚ù%Ä6>2¯ ðKƒ÷ŒJ9SuAl =cz= nÑò÷Ø\_#Íf¬@¬½› ÛÒYã³Ý©'jO¨£=H QU"M)пŸ« âêD¤[1² ‰¶bT‰à?'¹BÞžÄx«Â‡ôwe`_09ïJYáBïªjQ—iþûEÊ×Þ<è†IAà º'NâÞyÏ×i¡uaùSdÎ0Í.Ûó8®‹iÒõïêL÷;bY¢r‘iÚ*GÓ’­›¦9qpáöû»žß`ïðe)ÕŽxèþ ÊËïJÙ’E1 ¶ÄÂ:?œŒ:B`¨®ÓHßö®-¦‘9­NË]÷jNÔe™Î9DTøVø)3ÊÀšÄ‚€~ýÖžw*:ç†Ö^.Ê›ÆÃóE*¡m¶úLùgC£i7#²˜— "¨ÊÂ&¸}Ã<B ûrÇi¸&–ÜõñâZ¥˜¨ãŽª'a{kHl¯ÕÖ¶û;SùôÍhÏÓH’7éÈÒWgÌõåáJ¥„EÙ€Ï*ÓI_;PAÀÂôKmP‚^Vk…ÇØ¡™— åÓ k|{O­>ÍÇàòÕ\n멸1ÄHJÛUqÀ6«A¹êÉaO¦A…Å«†þVAUFøJ‘¶"–˜óš>"ÀK[GÞ%æµÒ>À¦À¹ÚÔµïT2b,7¨Ø©q#KKƺ!Ù6ͺYÕƒT Õûõ&Ë&K¿Qì6•$¸ï/°ë£©>ùÊS§b 82ÛÑ%MC(ì),•vë¹L[7-žF— «RÔ !/—üAM2ªNwê «/¼cϵ  7ÎeN.VA†ó:gc¸3汌}h`ü ùf=K|åsÍÐçåRä¥Ìs¢þnšfÕ¨K#Ó1ÑŒaÊ$•㾺btçgLÞiDpÿñ¦EVzÙTWp]ãO¥Íê祟¸aw|°w“Ññ³ ¯a`øÖ0RÄÕSÆùpÛÏÄIàý‹œ7ŠÇª¬´j•˜9'ßÄþ.]ºB[æÕ ‰”ºü³VU  ñMcòt½Q©úwëµÊÆrÅHo0–rw‘Ír¶®©\¶Œ5±Ñ¼è+¤ÖÒФ¨~½\!¨Îxõ2T•8U@TÀd¬Ñ€@Š_úÊRSæ°>‹lƒa_ã¹A4í–FWY·½Ið“9¸ŸãÝûµ=ë‰h¡ZUKŒÜ÷ 9Ó»E¦Õ„8ÎúH[!õs é¯ì±e;ð )-2læÂiÏ”gjFÁ3ÙÊ€ÇX‡‚:*Q-RçÓ²×|>‹¼LsOØ´2KáQÓ¿Å}A<­ \ÁMÁtNk–‘0X0U°ygè¬cÑÕ#W­‚εºJRYV%õdÚÍŸ"§ùDYŸ£á}¤:7q‘ò^jŸ—Àïy‚¼LEž§¾Ñäí¡ë•Iï°’qôáhãÀO2ËJ×ê§,”÷ZY:‘§BU’ï|^ïšï}ûî Ú¦è,Ù>‡HdàY8›‘é"«âïED^&È3—ùÁÛnÎîÇùØp÷gØË>òÈw*õ®"BNr:m(Í 8nômȶ ¨Å`' Úw{]œÇd^‘ÖGœ@g.0;7äó©± `DiÓ•)TðÃ5;^óë\Ì^×~VÚi޶ýù! h|—øäh@§p%m­‰Øé!è4±é+iªC.þÙâØˆƒb#?dçÆv2\óÙ(h—"NýÍRÒÐx@¹*Ñ$ÿ½?¤9Ó“ƒvhÙö}ëÈC/E4Ô³¹´¸Õ<¯ÊUКŒ¿}n[©j&çro>Ÿ(Ÿ °\|³"§J/æ<ç.DÓõ³ùzZµ‰¶*ãÍs$è}ØèÌ|#ÐÑMðýw˜ÉŠõeÊúwãlïÑ“øÒ5~OoÅe8ß=‰üöO /åÿdëÓdÉÅæ»ƒ>7ç^ôz”rQ ©, ¨^TQX¤­×Šþ³HÛÛ¤Öµ•vW¶‚L­F™!Pî¢*¥­ ® íóŠaªr:T©¯'Ò(2Óçø2­ôººbL²Tަ"_.ç bÉ»‚…cFF÷9Þñ]·ÝˆOœ}\d’‚šôœ¤€r.”sùt¦œ|È…Ø@½+ox®½QLæ{#ÑÙšÝ#9«ÕA„Ë p™ ÏS‘¯—9‘ðÈJÜao–}Åï9ŽT`pÞ€NE0M²"µÂz*3H*~mE«O¶yf¨¾ ºjDmêÝ¥#boì1[´™uF€‰&CɬÉygÞ5,/URä$¡#(’TjMÓB©ƒ”Báë$ªf¯B.KµÕ|Yãá¼\ )‹Èo4ÙA´QC\Ai]åȃZ˜N(e0ÒU ÔqA‰[µDDéNz‡¥€Ë3,Pu à”tÙ¨¬iÚþRÅJ<[“˜¦t-*E¶xU·¥DŠ®ÐA—8Pý¡ñ+Ôø¬UŸq¦ÿD4€05,"^ªy¾t„õI˜k\=&«ðäé, µNViû"ð‹‘íuQŠÂ“ª®aÉ€ôÚÀŽWÀÜø?éçô1©$¡ãSå³ÿÍ÷ù*Ú £ î²%™­E$uJ'¿[,8¢ȹ@~òi®"@¾{¢üéW®±ø°OlÇ^AÒF§¨KV©’ßRû*pZ€Ò\Ne3µ›]¿·ß­/–v×T*€ F¼zž7Èsµ©TšE&E¾Båk^%±àðYº /êÌ.2S‡^."Ï“ÈóòeBJû¼p¸þÅë¿ä¶7Ü=rO}¾•âbQ#ÕF*'Ìàè\˜Vq°Úøèê͸<ü)w="Ñ0(‹|ºzã‹ÌT[ßCw™æ²úË$òDžNKå¢üØV«–çV Vj—¨>3;NÙœOü5ÀÂO Al ±­ß‡Nüd`'œ›Aé4“uöëKæ«E¥è¦Áu3¶€”Rä§Ÿ(?}y:ÏŸ¿PïA=j¯™CÑñ~I‚’ ,X1B Ĉª*¹jT«†g}‘tÅh­4ÉV~Còì°'ÑVTIö …ÏVZ©œ³áû[Äé?õÃá[gßBzœ!‹úc±à)·]÷RU7t£3w€ÄùwuÖ[0ã½cü‘< £ùrà;›%Hqùõ6Ê-³­:™ÿdûsºY%‚¯9ÔöK?AÛıSÌŒL3Ù„ Û®ú½èBD(©7¤+lá]Ñ9bt|ä|lœ1¥\ÌA{® ¡”1¨ñ×m'ÛD‡’p±–•#†Ó§§RÔ¬4ßï$Ná‘D`îªå¼]åGª£)ò½qâ IäÞÊaâQ¯W^\K ¨Q•¯viØó-ðútܲ»nF½+Ž> ýLô‹Ð޹)T SÐP=pÖH4ìr ¬f†²°€;ÀÄ(¹©ì%Æ¢¦¿MßSÓ$Qì¸Pä0óÌÚ·Fq;‡ ëp9—9¡75fÅAu6¨ÅT¶÷C¨ô9àTò6Õ98S_]Ý ú.™YG:¸ îAX9Gmó½KêºÊ<–jqå´H³~âzË/g_ªz¼i4Û˜l‡{v¾ÃPÙçŒà€øê3—5™}iŠÚ( b@ÔüwÀ“~¦e5tÉ(Þ¨ýU‹²MS½ÂóØàèž#Þsƒ|#`tmUFgÍ `B’GÀm§7r«9´p¨MêÖïÔTîÉÅ#TYøúqŸjÊ[€£»2‚Xìîg˜eÌÐɬqàZÙ¬‘Vr×n¾EQ”Š{$LãC8Ç Þp{s›#ܨd›ä1ÅÓê õÐĉ¹ézÔc$[cúvn4`«öEqa‹ ±~zG­ÂÜж÷FÀý©€/Hghe÷¤z²Dv¦2Žlø½_WI\²úÙ3X#[J ¥îm=*šÎW!€ÝÆd ¾ÑªÎÅ 6ˆ2õ#]—õRÔ²Š$¬²M&ÌçùýM}#enƒO6ýHmàl*w Mƒi"©½W>Ÿ©ZŸNU­¶­A¬)eV³Ý*»Ëû@ÁTMV‹ ¡q6‚ª=L½’Œ pš?"æ¼W:‘Ø› ºš/žØ©/¾_©V~ë|ç–@ÎçYuÓfê;ÉÂ@»¨g9L"®U‹±êÓyd4 ÇmˆE¿–å7SAۿ䊕 ¯£cíw,®jµŽƒXNSgù‘ ŒœÚ\(ô Ü°µw–7Q”šÝ†æÆÎç.—±d8^D>0ôÁž ¶WÃS¼ãyyÁœd?@O¥Š ¨zË€¨*¢Dè… àLOŸ&ð››ýïk¢ú¥”qé¤Ò«ü5éhp¡¨|nNl¨z´Î¿ Œ¢…ñ6ó® MS±p>E¾L[IA#ä°èy6ø¦´óºžÔ4Q^&‘ÓÙ sègZ*à "§äåÙåPÅÜzÚTÁ˜«F¢gëv&ú„Gr×kg2d}ì¥ôÛÞ’dma²¾ø}ÆUt Ûq §Bùù'‘Ÿ|æló±Ï •Ø‘Í FØÎûU#6êrÕ žbE‹C$÷mEt5 KƒVq•¾ŠaŠ7óŒ*P½ªV’¡ARÉÚÆ0Ó R´•¢ºUºl¨×Ή7F]è&rR÷FoR\z@ÉD{ZO¬c–­üüà͹?•îØwâžß)3AÇ|5ˆ›(-íE5¢ç]ù jÑ›²B÷hvWmàFt¾«ÝèhÚ'ƒ#e .uí.´ÁGP¡1áŽS-¶Œ(MYۚȱ«Ú›A‘ÅŒ3:…iiõ6Ö ÚÐ%G”\°´‚­JÚz¹TºÝ¤ÞÌ Ne¶Hí:ðåÁ …}€Ò«ù@ 7€¡½* ü¸4oY@–g6aQÄ’l6@ß”EeÅ+°˜‰Û8‰p¥N"Ϧ#¸¾º/h‹ÎIIøqæVŒVikãô¬+«Ò|¯Xy~í*4 Ê™5‚Ot£Y9ÂÃê˜d|1’åXE€ê<Õ=ì(´q¡*¶*ºÌ¨ç‰U¤AÄ\¥-«Že¥R#Þà%¹i•&C Ã$PhÖcçgÔÂ:EÃÉv,€ÒÖ÷¹®á¥`é?âªU µ/4ö;ÖÏë 5Ÿúª ’Dô·/úOÈbÀ¹üì¤~wÒ€I¤1•Õ}KQv^›Õz* Š$ V"©8I+üc”Ë‚@nÊiy̺—oNIrÏ·é~à芨 ‡ï#À¼ñe€¿šHE]”DzÚj’È®ŸÖUàwº%G@Ï5ß Õ3´üèÂÃ]–utRÁÇ:oö*D£è飻ۻíæo@Ä›åG3¨f”ºÉv¼S"h/¦knsÖ”V–m®´J\ú„é+2Ò!&¶M4ß«ѨUµÄ©šÆ==”œàh„ÐØ›¬˜¼‘9:ë>Ç“b#½ÊGhv·$øpðs™#qTuXJ!”t:VºZPᎽV%=o[Ó¸nô‡VqkÎ9vòiñ­¾%#„V”^0ÁyÑU~u-·‘ËtDŽ`½ÄBÃj¼«e¢=y‘>ÈŒ³vMXáê{m0b,3KÉDÙ=ààÚêWKÛ*Rs#·-ÈD £ID}YWqoØ j×*C™óÀ*R¶l2J5UШ’ ¾Psžë\\U!¶ÙwgÈÓi»geytßD~UdU´ätl_l¤Ë¯5ŠÍª0ú^DÿUå:m0 Xp„bŸO)›š1“Õt&´’àQß“ÿ7×]R TZÐY¼ê¼Y~Qt®…X~R2àS”Ô» Ép‹ºF*F?&P4rXFã]6õÆFd$Ûw9§QÖè±n©,Ñ—š\׳¢<$ëÏ´Ãíàïý£Ý)ŠkN°EXL"E³TzaûQ ¾Q Ë,^–ôë´ÄÂ8ºšî-5éÓ©hUCMââ“$§FL¤áÿð$®õG¬@#¯T¯n<¢F9^‡š†}F ¬õ€ñÍü ›½ãâG@ÍpÙP½Ïgÿf¢ìàÒËš I%¡^HÅROÙ¤Ô]2›B!:ý:æÉucíý¡´Mébc‰†z†Þ g°Ã3/ºÏÛ:«`Fú–Ö†Ü]=݃M1FÈÝÝ…ˆ(ƒ]6ÊoÁ %±½O)E>Õ焨×RQùTµxc B¾š•"'rÛ¼ Lvéd$6¡³õIÚ‚$FýS¶Š“í‹9ñ „-®Êà+Lz‚ðT6­R¸î/µïhV ütùÉ“¬ Âú’ ÄBF“ÿ1·yïçѽÜ+ØyóŸáʦ>§lçŽ]œBžˆmà{$ÉÜQE,_«£ŠT¸¦v®N&Ó·Ì*"ȯ Ùı&^ TŒÞ…6sLš¬Pß…ÝKVézX_¯fü—ëÏùž•¥Ñc]#ëV&£Âk*¯·`zZ÷‚9€™%¥éóäÆ?ñGŽîrBhö–÷lÎݺ†Ÿ«éý¿l4%½V¯Õ¥âú=ÑÆdÞkï­ŽdKO˜õ,êífO¥Ò‰’ãüW§Äj*€ yi >øQºç²”S¯×´O4š /)Žî¤ï‡»×’ïìÐÆ¶L¶l½,ÔA§m¼&’Ķ¿Ó™<¹ "5¨UõÌP—Ei’JáÁ†³·Àfª½J:Ÿ+mV¦éñ©8Û _é‘€JÈtœ‘šóA\1îÉký·ïÐ6‚>18Êž*3Bvèn!߀瑱ì »‰ØÐh³úMé,CZ´R“­W—«€SyA]ŽÌP~j,Uá°FVZÓŒ i‘hÔ¯/Ó–Ô[U×tœÍhDý½ûƒ¤j”Ñ.¾ z}ôÉEÂ!zã§Ór÷‰sy(>«ãýР{,À¼`î9*è*ãòúùϧYÀáÙXȵóVŠÙô²5SŸ€)E±Ó6!¬§ ¨ ‰mØÒ€,]‡ÃãHãבUœÜâT†N€IÔ&ô¹K¹,U#(AàJ0Â+>s%0zàˆïàpª–*C×ôÒi‡w·ÖáôE<£7V•šýº^«` l‚I¦ç0ê×øÆ{Œ†æÝ<ĘEöâ2–dýŠI–¼Vg|¹ÀЃ#ìxÍ‘qÕÈx%ºê"uó|„†Ðš¹Z¯Wm²‰5À ,[*i+lž®·ªù ”™þ.{ngÑõhjPf½R=GOeC=KWyË*9£™š‘>¢…<îTŸRyc¿ÙSRÉ;u?ŒÊÔýzëÓ‘F¦;ö÷©ý¬™ðõãÓ$¢©@»¹pîm=X«îÖAZO&¨Ùã„lo g£2IÙMÊÐ÷昞@÷ ±5®®¯0â¬V S_Óähûn.õGEq é7^+[e¹sœåÒ!"\|žŠÈs)Y ŸõÒƒ¤†Š&nÓ‹)¡ðB“…Ò½Gh³ï¡A‰6óÞÓ ªÑ\¥å£„€¥ðio„‹1›}|ÅU‘FUø€™u±}4–æÈ š°Q ½;R\ûeÜ$šªS K«Ð¸·…íX[PõVUuJSCÖ7i¿|¢'Ô±ØXKÝJÄb‚U¥¸ˆ”iKŽŽÊJ‰ [›aã„w~v²pÑé¸+ÃsŒõϼ±«6ͬ(ؾºñk­N² lÕ<Þ9EN <-½G§b |§iö?úå‹ÈŸ~y¹l 'ÚtýuŒ}ú<ºÜŠ«yt¤ƒŸ­\f+Jœ×ä®ÕÀŠ$þ2â|$P£K*A!˜ @8’…žöª¤¼×¦ób“Gz¯ùh€ÐùÞgň÷=Ü•ot(Î@Û“—T7µßÁçv=p&ߊ£U/†{ßÒ«hVS“Y—»(4ÜoÚàqc¼[Ab7äf7å7#‘­ÈžÐ Ŧ>Kµf°«µD#–®JÂͰÕ¢k5Jy"Y‡MqÏ÷ý4) ÍhrZñÆkHû6Š’§íék×YÍ$sŽ[ Æ7Q$,!Óœ¼êG2ÒäÒ²‡‘J0L”‰ENò[O/ZƒÎEÕöù"ÏåË×¹ofš°z©m‚"]IVúg‚Ƽ֘ȢãºìUþüµiSÖÞ\õ;Œ¡ ºþ£¦çIƒ¨ ?Ìеj0aÊ©@Î'Š¥–iÿr¡üð,ò2A¦ea\|‡¨.~¾Žƒ¤óá°!˜ß0é$OA@yC¬r$Lz”ÔÆ[Ï¢ÈË(Z{±ÃK÷þM&ÒÓ+O‚nSº¡—:Ûyc<ˆ‘ ûýÒépßÃõá€1ÝkT†à¤çÕX)e«Eòó²Èo©Ÿ¡ïäm/óLêžM2{\&i*úñ,¨ªP0‹:ãâ½WŒ˜*qeW`?ÀôN;þáj‘H¿öï(Y+µÅùí7oUÊ6‡|¢ŠÁ^ÂÆ÷ÕŽªJ¦lt'–cŠèþm*:Œ‰±I)¯òÉn>ñäËÈö{(ú`®*_eë—@Q”$šZEûäŒhCdÖ)Ó¸€¥‚5³K¡G»8ô*A½r[”dç=#ÛÝnß"Ú ¶5†‰Yµÿìö|]?ƒ~¡Q6Ín.Þ÷]¸ñi•ÚœYsPÒ¡m“ L”!í+a‡…nf$J± >䪅®š¢“80cªw‰è†¾˜ŒV«T«ªp©ífÀÉy«$m¸\0ùt¢LEä‰iùú²ý®ìlî‘ÜxtVú;©*éEªI"ÀÍù5%ËC2øÏ9Ÿ#ÔØ%A°©9ƒòŸ)¿ñIäÜT½*;rY£§ð’{ûZpãµÐËÏždˆó%ÇI·‘˜Ê‘Kœ4´;i+>Em€¾‚®Œ~.ŠPü’¬¤(DU›–‘”¡Fù#ýH퇰ƛ‚£7ë3£h±StÓ"R¶þ8É+G÷ªa\U¸#ÇM|Òª›Èz‘^=n˜c'líå;Ê:~£”H4Š:o˜Xj´ÉÒ­½U«½úVv“zŒxÒPÑV^¬ß»,Æ"-â e£š›Ž-ì×[cO-»Ü¸TUÿÙ3½ AÞ Î½¸Ìö_4÷@5QVÁ0Åç#qTf2ú÷¬ÊtÇ×$"˜dë¡ë\Î6^£¾'•”­¹i4M#Ôšeü¨Ùú.¢H:z¾ØÖËÉKÙ£™ï¦OºlL»Äþ¼É2pMF˜j£ÙŒvÕ 5P°b óƒÏDÈË4WxNBÌo4a,‰Ó ¶¨žéO>‰\¦åwÚõšp" ¶Wp·_èI$.ÈuGÆ›*ržÑGÑô…F±áZÑVbW£^Qv)EäóiV ´Œ³íMBy¹`Uþäw²¨?Mî Òôæ ¥!@7êßL@n„hЩ†ø`”#gìÿšO㣵¶ "¼´4¼˜H36Çòö{àˆŸß(Ú¶ý©0êj‘6©FKEL¼W#ÒˆÚÜU>FÇÛlÖ¡*c\(r‘E‘r²÷rrÉ_9Þž:•oìñȆ‡ƒŽ""zH&“$ó˜Ê^h {DÕ!¸f28z]QsE_r (qÞ€%45¢_ŸÔéUUuÏÒv0›L[š³ý³ˆõ:ø@NXƒEAB(3»Q·§àçŒÒxÀΆPe|ÁScr“ùȰ÷@:²£?»ÃB`Ä»V¤¤ôF‚6&4~4qÕ5ì8F«=aÊ’>J[iÍz®ØŠè³QúVˆq''½ª.˜HD>«ßÐ$e›ˆÆcÀw¾â¥bÇåÜWa“…ööÏ{¥e²²š£Um~…q’,uöYõš´ãŽí|oþPÛMŸ0žÃ£ÀE¯mS“så¨è3L„& *#ÐÒïµÚ¡Îy£’kÊó¯ix’êº#êDÚðçiu‘é0TÖù WEñP`P¥pô¹#Ѥ¦ÉsK¤ŠÈϾùÍïE¾;u ¤y¹TuOnÒò·Lö´$¯óèñ£Ì8R‰þÖ½ïÚšïÛ=‡ÁLJèñž.£´Ëì˜÷s'ñÛ­ ’²ê‡Ö£÷ ŠBÉãühdGVñº½—|ýûq×ïä1ðˆNœ8¢B³/gý¾ñÐÞ<&(õEGI”íÉå1FÁ:r Œ÷¨JŠ]»'Çð™²;È6“ïýÙÓ­¨ëêZ!¦9 :¾<³;H£wÇaÁ‹iŒrõ…%@Ù‚PvÖxw7£À+GYCv44:Éì.xÚpw~Ö«=hO *IK,Z`ãå’ôi5bX„f]dv¹2᪹àDãLÇÒT+èihð”ÞÄxŽZçMÝ2˜‹QU™Öä3m 71t2R1àJR^¸1ÖjQ•œö|µ‚NVúNPKˆ2÷Æx‰ñ˜þçÍUi½èšï#`Ó A üôß5é—íúŽluÏ×m£‚ˆ/Q‰,$U„ít’YÖÛ]ÔXx¡ÈË› ý(3Ž&'õö?_Ž,&1à"yŽîðwdÂ8”ªß5ßÏŒ:ö)Tzk#ÆÊNÄþ5½ p4RÏÀ=ËN®yÿ{»gc(t´ Ž:ÐHó J%G’í£ÌIf,ÝËÚ{®·ü÷ (‰>ÎV¸(ŽåÑZâ5÷PÂVߟ‡ÅZ!a/Dö aN‰h÷ÿ¯1Ÿš|dºæ)cõùèølÛIJª }àn °Nš )Én6ZcRÑe¨¤•ŒÊÞºq›p\GßáÞG¸ý@μHì€@ˆb™_Û&70½ša;e7´u¾”6X_=/³ýBÅ–4YnÆI"I3±Áa3<ÞßEMóÉ•éL䌬„¢µ’Áò沩¬£7HÇŸ8ÉËe44².ËWÇ)4•-3×ÊæEµû p‚O êŠ ü‚‡@O[‰Z)ä DNm̉ [é ´uÿÑÎ)Ò_-àT?k¸µ®ÏSA±ÍŒ QÊiöýÕ3䇯êf•m­æ"óóȗ …2-à芞Zý,ôBŮӋsM¼µ·”=.³uôÚcÕßL¯œ]   4¦½8B²Þö„®s·µ¼™·îäý€ ïɽ¾óçïKçïRwÿ³â΂Gf)siS@oyÃv÷!¬™î½Þ:ÀÁ†ì)wcÒW¥¢y†X)AŒ¿—ûa˜Ç5.ð!Û$WûísÙŒô2w2ž:GI¤ñP‡„=ÑPµ3@Ï«—Àɞ؉×K¼®F{ˆuw¦ÊÎÃ7mQš±Öom3¶—4ÖêŒq‚A@I7G÷º*%FÇhƒf6MåšæEû<5x‰¥¯ ÌÃ+‘%^¼ÎtÅû…Xª<±êR« ÁˆjrT("‹ ÀO>S>ŸD€²˜* ë‚¥ŸÆQ\‡öß~*ZVÛ‹4€íþ¡Uü´`„oè§ÁF¢{;& {jÙ Ö– å*ÌP ä‘r¢|÷D9¶ê¡N3!¿|ù“/"ÓÒãÅZÆp8øùòÐu'€i>?ÌIky)‰â¸Ã5òHp= †£ ÷«ƒ# ŠFNrìj®Û¬î°A|)¥õx»dÝ3„¿'9Ρç4(KÇ×}âeL‚úm¿Úá.éà†o '¹7¹dúØÅVì–è‹Ø_ã{ãÇ~ À*Áª¦ #=þ90'i²Âl¾ÊýƒÝË„»gêßDr÷ÑÁf|u)œ‰"]€kÒãb—éwA°  ‚4Mßhê¡Ûço˜D—¹¿d¢t*lÁªë¹) œVÃYÉ) °EýQ´6-¬Ï$¶hï €¨?KèiáæÆºçž"ùŒ±ŸiEc˜ñ5«ä¾¡ºqAÆ}PºR³¹ÉJeÒÌQ3¾©}!‡sùtžkJ)R¸ªuö"ˆxý}èÑ’ŒÜe ¸q} 6îíí³…7¸Í*ÖJ‚«ÈMÒ„ÊÆX¥Ì`¨œæþ£Ÿ<‰üô,òé´h8*©ˆÈ4ÍB«@ƒ_Ûy°©ZO§›®2Hx÷×î@s=·°R{D¸;*IÄgß 8:n=!¿M²œG¥äÞGŒî»|ßY$ Þ×}@¢(T¢ê)!¼-8âÐ39¿ð¾Qñ8XyLåEç÷3vÇ"“d¾3[€ÖÈîu÷.½1Ë´ л£ôØ{Á/í+ÊVûJQô¬<% >K¨Î‰)UH²,Õˆžæ}Nž²9ESWecîЩMUyb„gÎäySÚ{ÚRq}…€¡U€N±]†ü 3!s£*O£‚|Ýyꬣ¯NjžEª?Ú€x9îÓ K_ÙBë[¤V¡Ç#\â Ž*V¶?Efßžó R ¥œ8÷Ö! m°Ȭ‡A;vVcDÕ=SIZi{꥗dŽª×tk¥b?dÅ<” @¡PJ9 ¥ˆ|wžÅ¾?CÎg„}!?,æ°©t»ÄŽNø B&cl§ó­ ÍðíIÒôÎø½· ­Ñeƒ`G€ƒküûTöz (zËëåßwO)÷ŠŽ£Þ1£J“üìµô=°ˆ”eQŒ LÖò•KjWÝÊj”]ÿÍü1P€A°ã{¬zH£HG–pk”:q£ŸŸö#Ò=GµB—)s|ãŧ(7IÕFÚ`7Z‡¢É;ͯ$ååB3 ýú æGãP«Bse¨ȧå7¾›«"ŸÏr‚¶ã" ².‘ŸB½÷t ‹ˆL¾VtωÀ¯ïÕŸ'Ý!ögê³+8Z|òJ™étgˆœOïŸ8ƒ£UÑΫvbUi„lÀhM4ð†É݃¥­ú¯óÕ›åHug0­NæòÉGW;x0ŒŽ[Žßh³~EeFŠ„êªW!˜·GW#þ†W{¨ÑRD pˆƒyOŽßQÃØ¡Gu£Y¬^/SJí±T(µoÝSûðnýS½5Q«¥z 8½ñšŒáãѯ™ e¾žBXû}«ƒÀkµ†’Ì‚%VCÖqTýŽiýº‹°a‡Ñe’Åù@Å˾÷§]Sè厣uÉU(¨'J“ÉGŒáºrˤ¼Ì®þ#°^MZCIá$ò"O‘'8*Õ:VQÎeN½\ rZd¿—·LË€îó½|̓`î)–³F`Än‘jKt.‘4œ*\ÅÄXÁÑ\5*RŠÈ§BùïmßÒ*h¡*eäÜ#ö«—Y˜á‡¯µ÷¨ÊÊ/óÁ÷M úLŸÕXvø|xWÍNÈØ7ƒqþg¯xÇ@:YûÑ÷JÔŒmYL+ÝÑKe+ß$:Øi‚Nªú{.„”\91ifj8‡Ž„½ñLEí’\²ÚÓ}zñßèf×{ïf@ef!ÌREåòt&Èä{Œö¼S4—T!“’Oåhó2rßZ¹*zS~|2-8Š.ØÓ9ý°hŒ%á–P þ\#+O·ÌŸ}žDÊD9ŸÔØnÀü6&¡Ô´J¥ö‘3XÔT½åy>Ïâ_ž!_8K†Oœ+JSêD}HÙLß -ýÍ÷n1fp­¾Œhi´@ ¡í–æ½ `î5:a?ýNägŸ"6fQ “ XÊ–µ›õW/”//ËD™D¶9îòX€¤+£`+ªs3@:¸ñ⊵+:nÙ+¤àösMGÙ·Bœqznúõ«£0ºcn஑*ïs˜ûƒ1îMoa™9óðoè¢íIËxcøÈ»zŽQZO Ai¯M0ÜAÆYá¨W‡ƒàhXý4\lËàqìÞô¾ÇPõ‚@•éÑ=ÅÍ_¿’O2M;Gbö ÚdOÁÓ÷û™Î6(óowö…QãÙÀÑúöÚS²\ÆæšPW)ÓíáÕÌEYËw›¦x§Gì—°)[ îå›-lðE@`ðaÚù–%¶Çc#o™–&`P¥@¦åî=_°“nñVݵÏo“k߀/´R% Z)8‰`)gÈS™öõ2—&Ε•©VÅþúÌRMƒ -Üç™?cÏ«»iZZ£tWï…JÝ”MÑïTD>?Q~ó;‘§²â®u`³ÕÞ#¡¬ÔºiZÌz©MŽ¯ØœéÎÿCã:€„cÁ#¦ŽfòŽ#“1%=_»Æ¸«ÁYbŠˆø8 “n>÷ïÝt¯ ²ëº2cµ¨ùX°ã†À{¤¶0\à}¾ÏÏ Dþtž¶."n޳†‚¥K¸Hl·chP¸WÈgPäâšð†6¥ü,PÈšÔ_9£CÈÌG‘Õ㣜–—ʯ)ÀúlŠVŸè†¢ªYgvF¢ð=ð T,ø•ëÏFzLUc C¤Ôž,f'ˆ À’o °XñµBZu·î9.Ï€ 3ÓLÓzAFÚܲL q»’  à`íã°jݪBOQöâYðAIÕóÍ—ž*.gŠH°URˆˆœDNœ×?f'ô’QD:·'¯O”°­ddÓ‹DÆÉË,÷i„À¾ ~è«ç•­<ùn„ûoâjD#œd<Ù"luÎ\”ë *G”ð¿(àð,ë“Q¢€M‚ zù‘§2ÿûëe>išÕîH¬â6Fo]LèÜfiE´q+ø‰×]`I…=tå·%Û_ÊÒOT(')e®Ð}Zz² WG­õÞGÙ‘ç å_¡üò+äBÎó†Ê/Êë`p¯4´¼cñëYô +|>3zÇE²–ïÑé¢Å1’?RÙª8'#.ÚòÍ¿î*!qgÜkVŽVŒRo1X£vÓž]Ôš>UZ–RQòëüð¹½p”°iÒyÀzgpÄÁ‡«Ÿc@"'ÌÆˆö¿SÙ2êp 9~¦ž*!ªÂ±¯ôE—(í­aèo3£B»÷¢ýAlõÆÇÆi ¦¼ŒšÙ zlöÝÞkî Œ¥éP8}$¯2Ò¾˜@k ‰9ÑT |¢-óãdn+ -eÀäøÚQÑš6#<ÆØw›N£gÇñùŠ:kN§÷H#'GÅbÓ¸‹¶Ò¨‚þ(øŽ²dÜçfÔÑÐØ™àßcçÅU]²ö¸ @ÎÁÄ¥_H¶~V“W:€a¾gÄúóÿîi>÷¯'‘?ù L2Mi™Æ… zý S©tÕ9´óÛö6‰$ouÿo¤Èc íçL" µj4ÿý‘ÓiV©ûüiGuH—I¬ ˜Ø¬.—Z5ª€v>µWª*Б Šn“âz®Ø ye`ìeAŽfŽÂ6#AGÓ}~iúëxiøáü€Ñ–‘^$Žô"½CPt+0Ò2ø€[sÔ«*šM¨%s›Ù/˜=´g¥ÈPz]ñ…ÃÀ¨‰3)eyu9cÕ¤Ÿ#PTÿ«†—ç2Ó%žÊü÷Ó” ]”,ªak,ßÕh;pÄ©‹¹M”ö•ÑÁ•”lH¯KD¡ØSmÚ3´³3õ¤¥w´Ô½´3]$ë3m¶!®é?mh|¢ª´³£'zyi•¡­‡-ëúÀµŸëÏm µ*UM",êß'%j«Fœn€wÎ34ÐQWTÆßhb~öÒÉvoGrò7_YÃ;~`;œü † }5 ñsÕ½G-ÕÆÎ®ò!“æ¼à$º]ÿ 4}F 5Š3†×„Ýb¤J<¿Ì ºÓ'„V+ª²·Ê‹²_àL‰Ø6ïÚ›ÌIB_DžN”ç!T‘šIijz«H1¯ð„òëÔZÁŸ¹ˆ„€toŽÜX«ÿ[R@'ÐJ™ûŽÎ§ù^|÷ùÙgʧWQ†Z”g™)_."ÏÓOà¼P䥮‰Ó –ØÎ÷{#›€;Œ·FQpݲ£îO›»ùSÖà¨VN z:-ÿ-Àè¼È¶žŠHu/-*†&d™Dæ) _ñ $ºr ï ÍÔK®r¾§E¹ª¨>žâöÚ‰8š&›`a©¢ *Û1S˜ü¨ ú0Ülª'€Fj¹H+}Ï$k€­û_èAþJcjÜeôõ\å!º4XSU}¦ZzQ+ö([z+Ô‰á@‘ŒÁ~¼ºÊ 76êu"_/"ç—9˜_O«lóÖœ¦®â()hW ¤Oe Ê/Ë¡ŸN"/™ …Å'iªÊžÓR·¼š÷+ß ¢ˆW†L*C¦ú(©*ìr^˜ý‹f¥:¬×õÓÏ”ïÎsAèói6å}Ž6`µÞc#ã¹Pe« qéÙâeçQEö12c“ÜC¤áH`8ª^Çâ ã×sW¨¡ûÕ7 †]9÷¢Ž~߯8jL¹áPžþw©Û*=*GóÕàqÕH¢¢¨X$éµ»ªö\¦ù¿2-ï™Dp™SNs T}#N˜ÿ‹KN¿ˆ¼ûtJ‘vyRGõåŸé©zW‘OeHŸ‹Èù´U’N§Mh bê-óOGbKËJð/÷âP T\œÐƒY‘¦Ó³A%¨@aw§ ÎÀªªÍæ÷Êíò×T2¶@XŸ§:1dÓ\=M &:c$jš…¹ó l£ARFE(§Uöw«:žà Ó|_.Ó¼.\0¯ —å4.²%½‰¦óü*ã´K¢Ø@¿–¶ŸQ’ dÎ iªÑŸ²¨¦Ùf47Ì*¸5.Çl«AÌb)ÄU,F½Æ¾¿(¢°ªãÁ©:zùû¨ŠA ²Ðf_uåÄn~G[IHÑì&È —„Ùè‚H肜Ô"×ø_©‡«uö|ß=Í”³Ë$òËÈt™þ\0?e©˜ÈÚŸÄÕ#h©xsWÙCÚênT5aÌÂ3¸’Û*Vû¡Tè Ì^ñÓÏ"?ÿNÅ51âlOd™Ó¾â8QäË åO¾,W#.â¯MܳÍ2ÃH´·F»wª ÝÖ«àÀ®Ÿ¨xË=cùÏ‘cžŽ7|âÆ‹Ùæ^ «½uÕh41jçE¿?\[1â«ßаj¤«Gk]\J zJPEª$Ι¡—I¤\¶½äÌ-(¾¸ªÔˆŒú[£þ‚‘ý;‹ÛDÍ;ƒI_ßoT©tŸ*Ÿ|ùûùDy:-¤ÓÐÕù1M– ·R¥´ËzÍ2§™u—ªå—“Oà 1Pôçv‡zËF9¦²Y*]Tq‚²Ç2ü*ð[y7Y5!JBš˜Z*< †S îhAPœP…z>N)s0¥ÇS­FÖªãÙU“ÖªQyYÖƒË,°<ì‹,ýLQë_aj/Ô¢-¾ÏöË /\S^4[eé! UˆÒK¨¨R\³¹ ‚©ƒ¿„)'+|áh”–ºç¨~ˆÀ‘–ÖÞ8Ãê…¨ÞV9Ù“Œ7÷Ë+z$÷V¥nžÓ–ꯃ­fÞ l [pm¬ÕD iüU+Œ¢à9-^I•~ùåe$˜¸˜‰X+IT‰ÀùG…Jc _{ËŸ]'*д:µÿ—j€;÷}:ÏrÞõyT»-ÁŒfM©Õ<Ý“|™ ÿê åWÏ”i¢\¦­’oL‹Ó}õ HÃÁíö,ïñd²³JÌÏUȶß%@{0³r“ü‘€£TŽztÝzzäëÞŠL}у£©@§þWݲu5¢HeGrÙh'5@=Ñf• ö‡à»G‘çÑîo?Á[*Fѯ÷¼ºžptùî\Ò\MšÜùøë3–å2™â…ÊJ;u¶k(¦S7Òäðm•' –HÜËN/µM¯äÆ´jÔž7‚€®Óß±…·ÍþÁîw|³DD b½Kñ¬óq™ÙêcRi9E£§ú'æ1´õ·mÇ¿L38:M"/˜*‘çå¡×%ð »æ^9Õ7eúc¢¾.ÕPÞȱ›6Ü,D›¹3ýkà dê^¦U‚èXpÜÞ[_•òTYÚ ´¡.mïÛŠœžæ$ÎŵIk¬,ŠþÁæè1Í2Óo/2ƒ“S‹PÀÖÇÔ4iµR¶.p_Õ$«‘ìŠ]çc–Ó¼8²ˆ|ÿiîMºL"¿|†<¿Ì•Çi9‡iR\Uø–ªœ¢úaq½9G–VðÛ„¶u جç>ïçE™¿Îóø'ŸD~úD)§ù½k²C,å~›.\Õmõó¸\ òBùú"r!dš´¸ÚꨫVL ÜÚcóó‘ˆwšGüÛöÚ“öÞ×(K£çMW2jî³ß„E¸ïÊ~-zû–ÀÑ¥³ 28Ö¥ó[©Eó܃# ŠN ÝÊd†K^˜ÒÉîz«/ÓòYÐî‰÷2ì'zûPš{¾¢ÇH”á¼Rêf^ù÷O3Hú|^~·>K'ÊK¥P–YÒ¸þ}­$-·›Ó=Åw]fÕ#cWÄÎX†_ RäÚó±7'HË÷߀’­þXÿ£lÀ^›SýÚÔc¿$›EM*ÑsoD¬€Œz§Ô¦…Шtùç"Šêaû´êOËØ© ©V‘D­Ïõ¿—m=$á¢NazE`´ÀVLs¿Èœ ê%7@qqã¸ùG#B£R5Þ‹ Åõ)‰4Ôf|RÀe×}‚ ¥Í$ÕC´Ò¢ Ï01½GÎ0¸FLuJ:ªk/ÑZY&eÂ4û"•z /œ¸>olRõ eTT: š¨ÖT5±z+§í£à2NS¡üBf)ð D¨”JÖj ÄUƹ&o¾K ¬º‡Ü$ÉE÷ÕªØÒ_ˆ¡<·êïO>‰|ž+b²ô"ÍÉš é U¿ÙÚ&FÊtùÅ å¿‚¼¼P&NsÖUê©Q9¨ EkõkÅ^“UæóMÇ®†Ò‘ v—*r- gà5ÛÀ7Žx×·Ù`d;qŒvùÞ€Å^tY݃£Øè¿¯@i@?ý9å'?Ûö«?ø—‘çå÷ˆršw’å“Zé&ÿFÀÈÜK$YHGø1 ÷hZ]'në3~:Í}GŸ@ôyñ³øþÓÌ¥ÿ[û"?ý‰È?ÿgþ”2•%㙳ÿX²þ—IÓîL¦‘ëD’˜hžÍÈ‘ÜKtLqÂHºPXÁ©òú±J_ÙYü~j÷òXžtø>¨Å'A<¢Ê’÷¸‰ b;€[6@í©šOç¹*ù{¿÷"?ý åÿù_žåÿò<‰|¹,Õ¥Ëö­Ó´¨aŽÇ5w™gt~‚N&DÝâϱ=0Ð!0Y'D<í­-ÑŽR«°줃(%9´ E¢ª˜ˆ5=¶àŠI•Ö¿T·Ìö•I‹=š{à¼ÀBÅÐÄL•e>ç— är^‚ü… ãí´%E°Tq é‹†[UdcÚÃ\žÖ;Y–ky:‰üƧ¸}½ˆF(¥9`$˜ ÓÚVq‚ÙÆÅìya“Mf$v@+eìïå¾4ƒt¢`#øvŸ1¿.8iž_«ÎK0vV•£ïN[òóYä?ü_ÿRþÎßy‘ßùËz¨ÿê¿:ËÿéÿüIþ£ÿä³”uvÓÒ“´ŒÓé Öìú¦¹2Ê­Ú‹mËÊßø=Êßø½I‘ø=­üùÅ{q¡JÌ›í¤ô³{€…É%æ2ßÚÄÔ‚:ú S`ä«E5Á¨£é4T—*æÂ–àôX`´¯ Eε2P+IâÅ}ÓfzK~Ô˜›”â[c,‹°DޏÛÞV éÈk‹­üç±@aÌ:“I£z(Q…3ΘPo°’BTÁ.ªÌ¸§R8AP–{¸ô‘"_/”'B>A=ÓZí[hÁPô[œD| Èt*‚”•r¶öi8Q•>)«Ãi¡µ³ÁËDùÕ³¬"›ˆv,3¯ÙجU®Yv»ªK~*óùÕ$(…òiHùî Ëß)ŸÏ˜ÕJ•Ñ4ÊôPeº‹˜*ôJ¤Zߪò× å_ ú…òr‘™ZÇJ‘v=Uð?Œv>Þ×iø4pü´V—T”,Ö:c½W…j“ZÝw™>Æçx:‰ÎãY´w‹î)õ|ïSÜï‘NVDUJ[=z:o™¿þ÷&ù{ÿã¹¢Àr–çŸþùú¿Û›/rþåïËçýOåßþ{“üäÏPþ³ÿø$ÿÿ?\ª KßÁ—“Èù²(¤Õʆ,}KÓ¦žçƒÆGŒƒ…7 ´!MfžyØŸž- ºÌ-ýàíÕë>-Õ£ïÎ"ÿƒ¿ÿ,¿÷w^d:}/?üÿî |Á9ýêå»?þ'ò×~w’ÿÇ?< _öÄeS®.¹ô*\=Ý ø2 ô€þ=ŒÀ‘Iº¶YIËVfÏ-¸fºª¡¦ÄìCʀĴ–ùö›£¾]µ¢{VJQŠñ)o¸v˘zZ@Ò祊ô×þ­IþÁÿ𫈈üòßü÷ezúÙˆüð/å»?þ'ò?ú_åÿøø< L"_±QoáZ-Fç.ÐÏ}ö} 7sPªÌ¿M†))wÔ¦|1.<ú¤Wϵ^Lkc¿-]®2ó€— „†ÅÄrÀ\ SXµ ™F›Eðb…Rè‚Ò4!@›…0T6}|­¼¹­NÙ*Ù¶ÑMU6ž”/_EΟf•Ä*Ö°šׯ\zÏHE'ó“C÷uy3SwNð}5 Ð"¸¦ï?m÷tÅè[>öü2ÿà´$ÎEäå²(Á)ª Ûò»³Èo|šÏc‘§2÷ýœÎ³äö©ÌŸüT°=Ú²UÔ ”êêïdõ?S=µ4m ô2‰üâ ä—_µÊeÿ—‰N±Ó#í½eJø ]át á–hz0Ú“\lgŠS‰’öpN#h±·èF@ÅhbUq‚S²ÔA(®Îf=wà=¢£7F£ £ãù½VŒÜó/ª‡ (%* Ž>=Íàèïþ38úú³ß•çŸþ!⥄˜ÁÓåÓoÉwôäÏÿÕäïÿÏ.òŸüïÎ2ÉÜ¿òtQ”½2××ËЦ¤Uµ4¶™ü5Kµ¸Ž­q¾r¤Œ$˯×bÜçùöL¯Ì¨=H'‘Ï'Ê÷g‘¿ñ7çÊÑóOÿ²© çõß¿ùÛ\}>."òršÙhüPЯ”ü¹JÚ× —5ÕÆšÞÅVKK`ÇiCÐVŸ‹ Ú ÞhVm$0ÑÅU «]%np½%›k¯MHÏžHUFûåÓ 0ÎeHOrÊ—‘¯Ï\­9("ç9aNT”SÝï—ç{š¯ñ¤¿JÆ\õAâ*Ȱܔi¹¿“ÙeQ¡œÅzž/”Ë™.›ÔúDÚd‚Ák9ÆN³AC51ª qŒ¶gFÎòÞe F¢Êëus¯2¡ëÏJpxWÑ5¶è‹–!zû ‹š“K6Öˆ4È=¼0úEÇÆÿÕóÑ­ LMý÷Uˆád+H¿ûw7pô«ßüïÎÍÀkzú™üðgÿ}ùÉü_ä/üÛ/ò{ÿÎ$ÿøÿ^äÓYäùb¾^D>ç,Ó· ¹J‚Ó­/K/«aåûdâ(8ò:±!nýÖß_sªºq)Ž"QoÍ=fÔ:zÒòÞòùLù‹é²>»Þ1ž– æ‹“k‡Ê¦³3fü÷.ûÆAÅöÙ  ðhVz¸Ÿ [Ùb')¨Ÿ±±ÄE+ưö¥øÍpÙ"ïVŸ‘ä®{®3p•V­¬é=jdÁÙ)·HÜž¤L¥åVÐýÝÓœ¹þónO<ׂõç_ˆˆÈÿ÷¿>­}pk’¤l{ý\•´L-¯~ Ÿ@õÉQ¶ówR˜š¢L}ÕÃ`LB)•VU´ž=W†ÖEõwÔ//Š’¥V†àHW•9-wrRÀ³ä7ÓWë•äü¼•³‚RÝwÄåß—©öÍÓYœAM¿ø¦€Ç7x_y ÀÏ÷ Öp0©FÁÝÀ.”U6¸&_Šªæ+Ý_ª~7ùu?Ú”aûKÌûõÜ^z0ÏuM8m|ü‰m©­k²Ü÷ ¸ßMõè=ƒ#y] âk*áé½¼¸¾£'Ž~ç¯q¥ÕyptþÕÊù‡ß—ó¯þPXÎòòùÏÉןÿ-SY"ÎòåÏümùîÿ‰ü­w’ÿ×?.r¹ˆ‰”—ö>A– ð"ûU¤›‹ðùí-ê=¢ò3™ 6ë¨ÄwŽ‘·¤ T»¤³ z¯,åHêˆ*CªújJUzwsô“}Õ…ÀŒ3¢"nGÜšaÁH]Cκñ$ò“Ÿ,Ï¡´a¦ }ýaRŸNõÓ'*À@uSSƒ‹¥ CYê{Jµn\–êg]W.²)|MʪDRÆh{J6`7Cû>\•àÚ¨^×7,ÒÇå¥Ò̼™«¡¶ÑVC;É)ª"pSY¢´UZ&™å¢€š®XéìÏEW v¢ H|/Õ[â7­½ãŠQÔ7ZbHüÁJéËEëdîò|N²Í¡æ Rk(½JæË ¸ÿð÷Okåè䀑¸ª®(0V°™ŸÔVܾ+ªº<-kÅË´ý‡Ež„\¦¹ŠS²eö œûïrre‘>.RéO³ hÑçYfÃQ¨LË|.\Ö,ëåR›ó9o(#>6½OSÍšàk[]šÊO¨!­ßŠ‘~7ý!â–(óÖE §—JW[\ejEÍÂÕ\*?µ'†M•Jœi©?¸J~¹@žž–û=U´sß°0u± úvM30Ÿ…°î]²T7'EÍ£ÉXY®êZMšfúÜËEäOžE¾|Å,Áí»ÖJÿäAZ_wË1¸ä<éäÚ¤fÜ9Ãn‚çÍbþS¿÷oÍ2—i ®ª$--ç˜ R\¥3z_š–ªT}ÿËeîQ:QU¼`«azó~í¦þ`Ó½o'÷}ÆîZä¹çýrT^Dsìd`þÛ”¿ô·jÝÏ7°ƒË¯äó¿þ§""ò/ÿo'ùÓߟ7—_Aþÿ¨È_ùŸ_äüåä‹X€tYzZ~þor¥ñÕêÑeC÷;¿ˆ|}y>Qž Ö€¦£çi6•|^¼x¶”û–c×M£@4 l©lx‹}—JñÌÌçvYh8!/KVø,á:¹ºf¥Rõ¶†×y]ñY»ÛG˜1 ×ëýü}­¼œ»ëóÉ­ÏU ÍŒ1JR;õÑJ÷Øb5˜C²QuÓ%dÔïéŒÛ0d/{GŠíO¡Ûtéú4ج/AàÊM¤)G¶¯)Úë\³ŽL™× BOª’dl–ùTÊþqЉ)hûŒŠê'>«yú´Ó® üͤ€Ñó4'`ž§Yæ«," e«XWz[í9æÚS‚5Ð¥¶·‚Ä"råÈÓ‰ëß+µŠ 5i]×8+‘ÉbfÊ©¶:m~:¤n½1£cCÕ ®=IM‚jo Ëlúʧ¨^$£ ­yáuÍb 38µóRý[žÍì“e ÅV1þL°ª\+eë1-‹ò3!__fE·ó –WAX¥M³ì*'Î h¿óóÇ2¸ŒéÙdµµ8¨qí¼Ût¥ÐWÇ µo{Sæ@ù0m+^í š–¿OË»,~DX<‰ÊIÎã¯Vž”8µ‡ÛÚ#5 ?ýùÓgÊ×—Ùz5„¥ÒÓ±Ô-- *£¤»[ÃvwBíyõÈ<¢‚tg–O˜‡Z‚¸ª]éE¸œ F,Çø²U/Õí} ¤&± ^0BÓúVÙa8@¦€ÖÄyå«Ó‹¥ïà7òƒJw+0Ú)88˜ß+0ŠæXY¨ªÁºÒëþÖ¿7I9K£>õéÿoùWÿò¯þ?XJ„"/¿€LÏ"åéEÀÛ‹´( }ÿ3®ß÷iQ®ºTúëŽ>‘¯‹Àó© j¾^–Íè"òU_Ü´TeŒbW³¼=¥ª§îIQq—¹þRêÚB¹pJeÚzDl{Í/ý3¥ë&²Q•äò_õ\t@ª³ï¿ýçs‘¼üJDD~ù¯aGQŸŠš·U‘›ºÎ.;­Á3Öl.¹9ÕW9g{­DõÚlLtÀR\LÙ“ jé61*¡0¦ §=Ó [;X|sÕFßüޱº1\à ¹ô:lR–çU«IÚCíwþÚæ§•§_üñi£Í£­Ò뢆ދŸa‡*ðPAÒ§“ß5a:q[G¾\D¾–åÏÅÙø³Ëñ‹p®- ÛÉ×±‰3­I™¥r´öq®÷òT¸$K±b ±Ú|]zkpáF¿¬UY¼’<®§R¡òÔ:/îàܸa“i­B:Ø ÏtùªÛ2ª1WäÊB_œ«Äœ+mºj¼hÓÀ³VÛdIްªÊ&«NÕsòßb“Ë=~¹Pþdši|?y¢|:cÑ‹„ëYÇ¢C±[(O˜s."çø­•Í‚­WN·€k5ŒˆT€³¬}sÛÐR‰T’Üë¼!UB‚kìÜÃËeRŃE¬b+ ÌÀhBe1píiš÷VÝ_»ÝÃi‚üê+å_(_+ãã2-Ï­zâ9f½R“5ê€Q,‚Êç{¤Ø Å“hÅ:Q<·ù¬™Ó²jÕH²feæ±Ls¶—‘V¤Á7mŸŠí:׬˜ªT½Ô ÒÅîugõ9_AºG°}•m^ñ!¿cpt7ÌsO°ËÇ#]9Ð=v¦bR{žD~ë/Ìùú³ßUÁÐÜg4]Dþà?/«x‚>ﯿ„|÷s ^~&Jhëw]æÀ¦öžO›¢ÝeÉ· æëeuøÕË<¿ ½5Ó<íÜ jPõ¤d¯ë¿ÏŠÆK•…þú2W‘¾®”%SVdu¯›ëV˜®]Ä/‹ÒϼiÍÑÀ„9ãXû xgR®¡Û©$Ôy'Û_.?ˆˆÈÿAóîÎXFP¼p暌` š¶Üý*{Ì¥rW0<)îþZ‘óšÙ™0Ãáù«Í;i1n¬;ÏLg…ádÃës™š0>ˆ =ƒ uÏd°÷>÷óˆz¬i—§ãéT¶ý>Jè¨ÊâIÙTCÚϧÙVƒ¥³Iõ9_8'X¾\D>]æuäTƒö VjÉlRk¥ítc¾ò§ÅÇm;y*X”!E>-ÿ>-ï™þy­x¾ˆœ&.K‚[_ …K<¡ý…óU úý@iKA¢ ÉÒû̇h™P0iY­Ž,jh•–V* ”->š«Ù$qíåâB»ˆJzW Äêÿ£+=âª`í¦XIõì‘Â@Ì#áR ¸Åz“Nm—¶uj©ž0÷™U`ü©ˆœO“œ-V-Xª‰Ë].uêcY¯ “Ló¾ªD-&¥j7ïEÈ:ÁŠ–­tdnàw"ärYD*çË$ò¼Ü·—ik¸LsïÛLyœÿmšÌÔFUIz¹ˆüòYä_Dž/XÙœ6!£ÍÜ[rÿ?îDWx]Ú÷¢C’ÃçákDâ˜Ò¨] ïÎZ]…C7~>·lÑ©ˆüì·Dþì_¤üäÏÌþÇùƒÿò§²d›/ã€EUœœ)ÛZX”0Äédi(?ÿoÙûÓ(˲ó: Üß9÷¾÷bÎÈȈ̈œÇš«€ª IŒ@H!’(PÔDIK–W//Û½ÜÝn¯ö/ÿ‘¹,·¯Õ¦e™mÊRs )Ä$ˆPªP•U••cdfDfÆð"â ÷Þóõ3ÜsîðÞ¬@.+°™•ù†ûÎ=çÛßÞßÞ Œs3–Žéç»upé{«×É«Œ= Uç54*jÿ,±FŠAÑ~™¥&8¶ézþa2Fu‹J@‚ÿ}üa…δI½÷²p¢=mʰ³FèmQ™` 3Ç• „ðïÞ%×ôˆeþþ€–Ò™: ÀÊ©GOe†¦v÷.^ŒñÚ¥Hß÷iY!ïæ€²°†(κ®ŸS;Æ4Â:pÅðÁëãÂ…‡e¸sGâÕ‹1>ÿ…öHËd¦Á‘e”¬I›€=aНb';cr3U)L¸ªž–5ìŒÆÞO0lÕ>âK–D…¬i_M.*åR—œÉ««µƒL9øœÏXY y‰ò©@Ǧ?›mR¼r%9K•Åp{Ä>8 »ínˆ>¯n+é®Íuâ»Æ~~S±h¨A,TáVI$ÑàÃUP=€"à ?·È“o¾ÕÆ]1?Txж±1“±þµå ifšqìDŠ“gS§öØÝ%¼òjŒ‹¯KÓÕgOŽd%Edf siUŽË çP&Í _[êy¾N´¥J’ñþ÷'8>ÅÂ!ÆÝ;„‹#|þ 1ö†9“äw # œiY—œì¨ú¦â‚³ p‹VÛTá—MZ1[¹žò×2¾üqE i¤u`¢É7V“’”ƒ$¦°d }RSä[Ë“g¢‚Í}Ȳ5ƒ0¤ ƒÒr»î@–í˜K¸0YϤÂ0.0{VD@D„˜mI¦™¯gnc$EB;Ü‘‡jÙ0a™Ùãœ2¯×o(™u#4@Ó ýž…ðFIXDêë–0e„„|vL_KEv¾Ófæ²:çZ§ØíQÔ°;`ì&ŒTi—ÁLé™#?£Qæ[!á¤âæ‚RcbA±mhJ‚ƒ´/ FÿlÕ¼!P‰¥–å´"àÔÃŒãç GÓÕOxë2áë(°¹6ØœÆWlî½"Jx’#é-ðC‡ýLé]ÅÃÇO½Gá—>ûkRÔ¦#.‹,ÒXü  ªÿ´3FÜl-ï·)ñ}fŒ$8b”%¬u³}G/èK&W£¿¸wIä`Àk6L.Tƒ#éŽHw((tXê~ùh†c2>‘áðñ´ò=|À‹/´ñ¿üÒlp/9‡;'ðd´Ui aÛ‰¶iÂ|ÿ¿³gß1pÿöüùï~wûñ=üÓÿi—®J !Q¤¥vJ3BÅMPž‹áf•”>T†JË&eOjÊ J–¯h©°;ˆšî}éß“†a?lŽ)¯RÌANTñeûMé\%  ¢ˆ,K`$8&èЫcróOÖL*F O@ÊŠb’ëq'WqìËêò«YmªAcš•äugGd2qÝpuú)ƒGwÕêÎâ¦îu £hj2nŸôM˜ËUÑ4õ%±m©0S-àÉÇ<ùö'ϦXZÎ*Ÿ÷'ÑÃÿö?Oá?<ß*¨LQš±›iKàçÚä€l(¨ÖtÓ–0éÐåNÌX^büÂßÙé3ùk9wxîÝ >òãüÓÿio\–Ƥƒ‘)mF chPå*… @¬í"ØJùAAW£" ¶ÀŽÖ5g¸ advd,¯­¡žÇÉܱ»~ìd‰ö¾·òºTéBHùža%`ÒäF°«EVÖ8±i–ÛK*‘wÛú $0ÕZ‘gù “SesúÌ^oÍnZé:U²c5[¦vHAJ@pÊ?¶"uñdÌ ò}„Dõ'vL©;KžŒtvž1w˜u®Q!Vu§áîUMÙ+×éÒß  UÍĆ}º·J.ïâØC–Ï(,UŽýµ_YkY;R)¢Þ*žzf€Ÿ¾·‹ßüÍ©¹L$¥enYV-WöAKä¥v,0Š©øÌgvðì;`¡sœXv@YñÞ*Ž,oàïÿ'Ûøoÿ_ÑO4K˜"ÝÎ$’cE¬Þ^YX•¶@fŽŠ(—SÊ¥Vfò ï *4ƒšºk:›ïuá$Á·Ç²¨Q˜.È!7ÜL¦Iλ×(³à(5ƒõ0À2GÊŠr \2EB“û°®Ö,ûcxá°ì‡GÖÈëªÀ{ᛞ›sàDQšÕbcîz ×X˜5ˆO¹‰ÕãÞ ? J AÀÒÊèÐakóÝïå.ì5:‚µçÕ¾¶?òî!Þùl‚sR˜Ïa‹*š)í#ñîUüµ¿½ àË_m¹‹­2†Šò}„¡÷ˆp†Í˶šå•Ð…}[ˆÝ2?÷s=œ:“i'Щ¥}ä¯ÿ|ÿÝ7éÀY–1R©×®°XGèÙ(.°ŽÅµQùùRUaçüÒ»·pTèn”!UÏ…@Ñ@Öí™Ã´ ¨lE@K°ù\sÙÎ¥Fb-3 °„y@[Ä+"(?\·ÖŽ1\Ìì0´ç»ò\~˜»C=; c a¤fìfŸ$€ÈÈòZ’qú˜ÂÜŒÒ)²’;Oú)r&Ò±=Þù©|½~%Ff !ŽŸJœ­~`‰Où^iúln '&“h¨€$ÕgÕÖŽÀ¥«"7 ÉÌ|“5ŽqCûp& –¡ßK€>#IV Yf%uù<$ç€@©Š (¬¿Ièþüz à¬a‘öCŸAª{«T±ùÚEpä(ã'ÿz¾ù©xÉÄ2ÒÎR ò»“k_ÂáS)}'ã»ÏSPÐI¡$EÆBî’M›%pþ …Îvã¬5´³ˆ‰»/`ù„¦TSg»à&mÞÆËøÜŸ*ÈÆüÑF±F2ÇèA#FuAƒ‚®(°G Gõ¿LÛK•àhg¦¹ù‰R¹Êö¦Ùò³(ë»û²½ù2äp;„+ßÓ›òÛ?šàè…ð~¶÷QÀàî±gßÕÇ—?7ìžp]7ûzRcª`ó“¨à ¯Ëmµá-‘Ï1LÇÀLxè!mÿÐ;øLPà¥˘¸ûÙÀ ñÒ÷b}Ø«¨± ºe¬Éð>ÕÃ… TxíµŸýw|åù†f(™@ÊAø­4.w…ŒŒJÙ¡ûèÜø³gÊ»d*ͤ¡àmG';$+Ad·_Æ’‚AÿH'Ï(YQ¸ü¦Àõ«©‘N1¯ +`í­CUùäýåq÷®F[BGD… À­ÆÅjEH, LTßésöcAÓˆ9±¦{Oùì$Q3GÄ€‰4À(u-€$ßT$Àý_íà©'“`I;‹HÛ‹µÀLÉÚÛñþ ðíoÆÈ em ²T‘J[i$kæj(‡ÃÈh»b_çˆu"B'bLÆÀ‰ÆÃ§`¡·ðL)núæâ̹“±G‰2sJ7 „a‘^S$”€‰I³šL¥|v¢Úš=tL3J"cÊÁ¤€3ý¤z€Úæ=Hän†`Hh8I„Œÿê¿èâô©ôÕ w%>û›SøØ§v1¿=°Ç½r%ÂÿgòzÛèÈ, "]#›ùÚ4vÂîÐH꘡”Ò³[VJÇÚ‚ók:$À°U…¯²÷«Š*È“÷‘¥ô§ ÅTû3ÙÏÔ ×‰yy(Â`î1t6^ÄÒ1FëëÚÍ#UÚ„¤dœÀ!(s3 ž9Ä¢™9J'Vj»«[ëz(Oª‚¼£ƒØ@àÿ,àˆàÛ|P¬ÿ¯½Ë©™A:¸lX ö|%@Ú¾­ggc€’¦¹ËÜÍk„þ.ÐÁ:¢þ:T<ãî‹,žÿ]‰$Zmàè†Ógj›Å/  ¦°Ž·?=Äöw\g1‘¹xª€Ô˜,¼wÛÅŒ(ìDOÄÀd xöéεæk‹,Àüã¢e9@t7îøq…ÿû½…8Î?‘óçœ?Ÿ 3øòWÛAb»u¶ÖÕÊXYó÷á.¡Ò™DhO*×yo²¦}+õÀЏˆœÅ°W¬™Ž¹6ÁÐÖºÒLv¿m à½ïOñ¶wdXXÊ÷ÆßýÍW®ýA[›tk)«ÛÝ"—6VÞ'„‘ª yCv¯*·ªòÞ÷2Z¸©#SáÔ¨•åíG¦ð€â=|Ó߈h”ÄÎî'·¯D€£"‹„PüÐù O=™@É $SÇï#ÉÔ Ä½›8vª‹ g3¼tQê}Di©kKj ¡X»ÉqÁ”Äc…m}в3HMÆÀÛŸN\³iHlGÀ0cÄ™–ó egpò9§!gWÏaZNq©²ï 6†ªxçû˜•»1¶%bçµr– #“$LÄŒ¿ÿ÷öðŽçroÒƒ ï~÷‡—þÑ?žÄêštI8ZzgÂ~Íç¡T‹Æab´+Èó…ÊfÁÙ¹$÷™›QŒžiÂIɘné½ Ä[-ˆðÓ?ÕÇéS)T<3¶1Ðôk~aüØæ²Úæá¾ï]Nqòd?ù‰~ý·' Í<,¬ÙqYd#óÛ2vûŒi†*ÅPY¾Î#I¡KC“ÞUD߸0½q»“›¥+Êúþ ¤@kîäGNä]ä&› _äÍ/­f–»Ò Óiö‡…}'Qè®GRvu„ Q†6§ä'ŽSUaògý©7_@Ùz7D=¬û-0F¯ÙbŸØkñúöùþïçŽTËäœÅóœ¦9É4@JÌàìÞø½á™dZ;kf nþä¶ïèƒå UŠfLØ×'v–õ×ñèüÉW:d@ß|GY!{ eù³ÏJ;öÈÌMiÌcO Ü^S¹GeúZô¶:±v¯”dºß á¯þÕ.☑L¬`8s,;ˆw¯¢½}ïïuÑÛøÆ·#4›*ÖÒ1“lîføÁl¬<‚ ‹G2÷¹Ô5€¶ï˜ƒÓ—Y­¹§—×Moo7÷˜f=g%­«•+1à¨éó| Åû?žÛD«xr¸£'»ø»ÿÙÿŸÿ±Ë—¤ÆÑʸ5…‘T/Ÿ³p1¬)˜áZ°ÂÅâ±àÐÍEf«À¤„›ˆ½†¢¢ØkØ­¡?sŸ› ¡Y°öHöˆC6Rq¡ù_¨·í÷ck’u÷½d­yˆ¤‹'Þ–àò›R;d ’H aF&5› ŒÔ‰+š-’رžmÉèÂdÄ˜Š =ž¿¾Q_m Œ¡ƒuC#3cB¾–¹lÖQÅTUœŽÚ ÖÞÁSL|“¿#ïm²\•eÂÈY$3_$}ÂH›Zü¥O÷ñŽç†ZÖ<÷ÒÎ"(ëcbãEœ9ÛÅßú=üƒ_œÊgNÍÌg¢òºˆ N+TÙT”rU™ø#3ÖN³KŒ!1€î‘ešíÊØÌNFz=œ?§?óÁì…f¦oþ!Óû`oá™ò˜r¸‰»/àìéTïÓ*ÐefmÆ{0Ltø«u©SÌzN…{ÕÞ… ß€M*.Þâš¡âßñ[hèp‰Yý¾å ý Jz•Áõ°ƒÁV·,’.ˆS(9Q ˜˜"°ˆµRt&­fVp–#/ÀBïDÁb¼ °±Ò­•­¾ûàè½²À³qs·ÆöÊàcùkPPÙ)rz–ÑžBå=e-yû;ä‹Ù\¢ÔÌáô†Àý–NY8ÂØ¸W6–•MÕý¶ØX9•`ªeÀQ ô%00Ò¡ o:U»£‚]°?ì}üLâÀXå=ní®·¤³)·Ê[—¼ë}}œ>«÷'_Ž›LqŠV÷>úã=|÷¥$¦sm‡“%1Rg=üàî¿Ö¢‹Dcºk¶” (?1‡ÃôJùköÌÎ󃊈*Bì<˜ý~èqT<ÿTVÛÛï^ÅÙ³V¯E¥ÊØ«ò P%8â÷‘Wr±ÀªH“劃œ+Ô¨Ü*5’¤àyJVzTO RÕÓÐH"¬ºcÛxª2¼Û߃Ï©ÂÚb®~»pùJ€÷ý5Zï^ÅÉs)&[„~ÆèepŽc±•ïŸz]KcCïf1C'æVNe#-ö«e ¤qqó³ƒ7O(c$J8Ð>Ÿ¸"'R¤Š·Ò«(B=‰esÉÌêY26,>»&ÕC¥î~·à‚e½…g0¹ö%<þd‚ãË oÞº–³.ÁÈ-³s·=ªq^$ot* s6A.t:ƒ™Kbdæš÷™´û(´IŒ$„®_—8w. Öäý%˰گ;«­¤îÏ™¢‘ꆺ=ܾ.ûëÕ(__*7eÈ2 —›=F’æ.uZVº•r`눸…¢ƒ„räÁ[šA*J›‡­GêÀmú³t?è³HÌÚJûÄyF{ë%t6B}§ŠgJ_÷w‘î:Î/2öv)g À•8, ýAv{€O̘ηœ¨H¢œ2ÿVÝëþ#0ú~>ëè@N®iØþ°£&?ÆUÞb3å©3 ö~š®Ý°7oç)Ër&)I5›äL ¸yBpf@É+„í»„Ù… ´·/:S{/÷ç­ÝüíÁ£‹ÇSì]аí4·‹µCÀEaR¥ùŠ´ÃÕZbwêTŠé9¥%c˜ê¶ôduƒDžxJËDü)$µº—pö|’Ô v{†Íèx+ìQ#áÖ}ÇøÅk>XO(qQfæýy±Iä«ÈÝB[8~Laù¸r3%Áór27ÐI8{Gª¾\fÔF›_APÞfçÂ;ô¥G\ˆçB®NÁ9dÙg3À滛á :§Ùýî— ¤²ZbÆ&T—ö~¿ùfäö 5bþÈSöÏ×Öt+•²>Ú›/Cd=÷¸igÃé3µûˆ-ZœL1ÝÒƒçè§„`$‘f(Rç30Ä0!òºyʈcY;"LÆŒGžHG‚#[ Þ½%ÑŠQÊ4adì8ÉYÈ7`­Ý;‹Ö*¹\aí‡Ø 9 ”çŒB”Vèüç¶üÌa, Ò¹Qxâ±gÎ¥Îp«¸ç§í%ĽU<üPŠë7[ˆ$!².–‚Aм•#;oTz©Õ2Ã\V©Ø,ÊŒôg’’Òî™ a(tc°-€×^‹ðþ÷ÐÞ¾˜× rÃÙ c²½/ìZö¿Ö®i€4q÷…` f/4JQï&ÚÛ¯:‚Á~]z#r÷^’»‰nvv@š0’Ä:´*(‚®¼uÁ•ݬWlªT3KÅŒ¯€ÞlG*€¬æôytß(f?çö“&çXÊÝ~ë+„¥e©Gô¼»AàhO3âÉ.¢½Õ‘4»ðŠ'X :_†S´±ªpr9/w(I(¦Í‹[¾Vñ?‚¢†UÞ˜5L Œ85þ Ù"®kûúæ'(¯ó™CÕ6Ý–=È1dïMgŒå2»Ì“ÈP¡­,IŒ(|áwbü¹¿>D¼{µtu6¾½¥÷Ö³H‘žm:z<Åå«‘Î,a6še&²Btyï×…í™ÝÏöwýZ‘1ˆ°M/“X.ˆpêl½¼†)‚’ètz8¼¤pyU„N—Ä$=軤„M ]ìÁšôsÖH&¨‘LÒº—P¯¼V¬lÝhrƒÌZH3}7J&¤#I€½xþùÞøn SÀ¡I‡~z+ ÑÙ|iä Q¼{ ¤R¬¿ÞÂ7~ ½”L„„V¹|ó ˆ%Љ “-àŸÜÆì‘Z;—0œ>SßÈ3lÖ+_žÀ·þC›}Âf±Ý#¤)£;`lô»‰FCkÀ¤”ž9‚6ª°Ì$%…‘å g”oè¸Ɇµ/UÅýåŠVùÊM÷½^Ô&+ú=xIÃ@:Ø_·«j&¨ÔX£j)R£‹[`ûH¨gÝõÄ ¤ ìûøÄC¿±üH­üã¾^ùºö>®–Ùù¼h„ IPÐxÖÞî;ÍŸš™¤¡Ùlû{c¡kÿìæuï|5­ËIjŠŸEð¨ÃréD‚N”³@±>(©l¯O¾œËË@²ßË),i™G8sn•k‘{òª g‡¬ÑÃÝ»¢ºðä¡}¶¡¼Î3@0» £¢N+®U_âig=c“ÇT˳ìgpoMê.¿ðÃxÙ³9qoøª¸š8k¯ÍŃ™‹yÈÁ‹>kÅÞ¿gïEûʨ©q”õ¨àד7\ ’*Öä¨5eYÀoF9HBî9ªQškÿ”unÌí[/|µ…«¯GHRB—0èŠà>µ,ŸNщ5ÔŽ–Ô@É;/CÈÃAó°S6 ž\ÒÃO¥ˆÛÜÈÉÌ—Y&„¨Â55Ä¡¹±+–¼Ù"‡à¹6Œóµ¬ó"è)æ×PY&ÀÞ…÷jû-ósôx½AŒϯ^“ÀHPCÛ@¨ðùÂ>À3?Ç `eä®”;ff‰>÷^*Æ †CÂpÈøÖg'‘ r¸1ò\³òÐ+_Ÿ0 6ÍGmïš#K×¾¡÷ÆhïæÈsFd= vÞ|aJiP´³l÷€ÍÐíÄ1Li–Çx(E®Î¶.líÞ Ècëì  ö»Ä‚sõcqÁ÷#9/lÈ æ%DÓ‡ýA3 þúô?åå­Xio Ú¸j »/vÛ÷ õqj€ÑŸ ƈÿÔ]~¯œp¯©.äÑ¿'³KÕvƒ¤ò0G¥Ê`)õ |·»â÷0ãÜ OÙ Õ†Ÿ•ì@É LÌ*ZÊ\Zy,sp¸ÙyÒZ[LÛŸ·ÿ¶-gV¾ÿ¦ÀÂÊSTƒOÅjÌï¬ ObÄ.øðûqo-˜Ã OÂÌú™‘J@nz“ç€TÏSù jßiPx²H+±›9À9ÓVÉàéoiܱJ²ö&Ý®o8–åB(Œgx ‡¸¤$ä±T ^Ñ9¯É'ÝÄ£üû´JŽÉûÜv®ÁDŠËR¥ŒLip¤ N´É‰."3Û"¸ÏF¤CÇSLÆlöF,‘ÐkKZGI‘:lÚ˜ HÍXçy>À©‡G³Ð€/Y&p¶¢Ø¬"iÄASd.©|Ó•€5À•ÿó7O¨“Ìrå" ÿ[¹[‰Ì?9pvýš6ö±Luéz™û~wO¯—Ìì“nVÏËßa.PCURV*€!s*Í&ù³UúZ(EÔg¤×ŸR”+)2Fªô×ëºë28KG »›Ê8Æ¥ ë35’”Ìcë³òîµhìwæJ]4côŒÐO½¡–Õ MØyšêלš†§s+Ua3,æ\¤k™$„6¡TW-ùÒO g³ T^ÔÌ&ü0Hßl·!"Ô$ß^1æ>ÀýlæðTq-} tŸ Ї±×퇌øýØŸi`´Ÿg{ Îtûdì$CUdL‹LRÜ©g| { <  Œ´ÎÊí²,7pHkRê#+ÑËöáng‹›ã§“ƒdíõKyO^‚» €ŒŽÔÎS'6©Á0m@Œ˜¦ ›®ŸRÀíÕú1ê¯Þ|#Ò«ÍðøµÙ=÷{—„Òï¼0ÝñQ3#Öæ}w“B§1¿Æà Ë?¤ùNŸ‘gˆÓjWKíõÜ\ÆÐÂ6¯r—J&Øcjªç} udð‡Ìò÷îÙ,Ü7¸â1só…¬OMxl±þ­=ø¿_[*¥Qï1n‹R2»¢£í¨û ÒT‘'óÍARªhßûȉs©×h!'Á €¼gj 2µF–'m¡åu‹'¬{ÝR}f€ÀÖmáí˹›!W°~\Y̰èÕ :‹?ΤŽ*® C‡–Pò¨ &eÂj›AÆü"c`Ýn¤ÕŒŸeI^»i{oe÷L.ØäWèãKÃúSª+Ã÷Äd$cî^‡•œç`&uLco'Ãtw²ŒÜã %;3¤$Ó@I58/} ÞïV+’ÌÜ/©n6$J;Õ%)›¹]6ŸéÀmÔ—‹Ü✡ ¨Ê=Ñ?ø<ÖˆöðF¥3hà[H.Œ ‡$UÔ{3jŸ›?UÚ"°ñŠH+·ëŒq°³ôèÆM*KÌåá‡ôVë÷?ûÏÚ  ‘›4n÷«-}‹W¡©”®øCþùW'±«›?ªzÏÊo^dl¿í Ræ53UÁ*)ŸMÊC^›^R›qvøÔ0˜?²3ƒ‘%ÉGé.qK°“ØÍÎ(,KÀ"jä6ä_j½?ÙN¶>Ô.½â^Yþ`)¾õ­–)ìØt¿©T$Þo[Å‚“W°ˆÂœÖ¸£Çu7·É°úák-*”';«,ïís‹A²ŸÑÜbaˆÊ$kl~§¡KqîZÕß8Eé[)´³åÂc¿;Í£÷F•¿_ݽ[åÞÔ`øl§U¯¢ÈPάg$-@¸}C Ò?óýBÚfÒdÌÚÅ[jö™0ÌIÆn/©kòÔí#+§S¯ÉÂÎUÑš'H3(#ϿĂ™Æ–É1Z>žabVŒ©>r§4ß-N³,T£¾-®ABébRQW1Äh;C÷·€.>/q 2÷"›4ÊÇëÈO² ¾URÖ’®W_‹$„¸·Zú¬äp"ëáÒë¾ó݉bsÆØ|*£l¨ì(ÞsÕûÒÁQ”«gΞ›¥“œ[°ýÚ#ËL¾Ý¸ZÁ®À aôSF/Ñ€%É€Þ€°××Ö ”Ùp´$Ýâúß0†) ¥fŒ¡Ò¶Þ© Úæ¼.÷Ögµ“b•ÙE•óÿ’ÆØk‡‰Êݸ&øÃï^9L? éAu„¬*h³,/¶l‡:õX¤}7¿¸úЦz2#ì‚÷  ïïãÚüÀÑÿ9ž•p²¶î=߸‚¹"õžÆHHmW¯ß-;MîSEÙÑd+o¨:â©|É2JUo¡Š…Éå1ƒ$s“é}ÛÂ[ÂèÌlP¬Î6”×ÙNßÞ–pRCûž,à¤ÀW¾Òq`(ò@R{û"äp·oI|ö³“¦°##m`#Ê÷ µÙu1߈| %BÇBé]±YT>{ÄU=ÙÊ3¬’=BAæ‰(ë¼o®çÁ'Dy';0Jà ÒfÔýÉÕ'[ÄU~ØTùG£›-#&t ;.Å.ÿ~K-¢šÇJ6«ÎL t{{ÈåsaÔ‰( lƤ{6÷’þ}±qjsëö‘¥“©6Z𤺑—×£A‘‰ ñØ¥Hh›êV´%;—]Ÿ…54MA†¥ð$cu¾´V}‹dßµŽK€~¤¬©jþˆ*  E¹ÿYëí<ðZ›[À»W©4Ovã¦À«ßÓlqçÞ‹î³’Ã t6^¼új„A 3ˆÝ9SeÁÕïË»\c]U¨y¯ƒT5û›U‡¤L¸³c4›Ö~J{}`§GØé1º}Âv±ÕclíÝ=`§ÇØíý!°Ó@ºg75,TªÅz&ØÉäÎlf3oÅTØÀjd‹ ò¶æä›U!usbTø¼¸ô16ߤ¨Œh|÷ʲ•ûÝ ™Çk÷˜ Ž~ãvWªm¤äàOig¨âÜQq@|ÿìA=¢*PKû»bªpMìLUÕ½7ês÷´ÔWÝ6ù ìÄil@FõP÷õúï§›Þäï«X–’éÆ~BˆÞLãq×¹ÎBáy‰ª9o™;ÂAGµª»Õß©~çòXGÑdÅ2Ĭª³˜Pª‘ÌÊáF‰IÈçzXXÌp¯/s;QEJÏ Dð‚Ÿ½ ¤XÇÒÉ—ÅDýõ›d¯ÅÞ– ¤–9²‡äÅ×%~å—§ñ™ŸßAgó%¨î%'®0ü¥ÿy»C Ÿ2V·îÏVz `B©±;~ÿò5Að%†–e“^.T“}NqØ¥æ‚óUsMÓ©Â$ÇJ«Jö¨îa&(+©c‹¸º¶,y 8‡º\êH¥€Ênµ¨Nß ËÂ"óä×x¹ Ïµ“Éz±å.•k×P‹HxQÅ,5î¯iCµ8ä›_ ˜5±vßäf%R Íý—3¸ÕïQ6J÷³H ÇScÐ`'.+4ó‘éæŠ3¡|öÈ5Z"ÂÒÉÑùG£º¶ÊAâ †¨ „3„„fžÈÏ}ñF7 P\L…™’`1s‰p± ‹(´gb³¸Ix³v}š|2kBaÙ7g´ÅØþò/Oà?ÿ/Ž,o`êÖç¡âWøÿÉó-üËÿßúžÜ,ÍH³*ŸM³E=g)—[‘ð›^h©´kgA ÒB¶†ï¦aÂTP8Yyœ}}F±yKâàqÔ:ª §&„Aì ½¡ax”ë™ù¬[‚ÑFs•²glÊF¢ÇHÓ¼1—²äÜ•”}pÍ¡[UT’U„W9 ÂâüZ“»q,H>Û¬ þáÙ|iœÙûÛTÊ[³ÊïN{ rܱÇî]*iÓyDCñ~ƒ-éòËÅßÝ¥« 38êó¦¤ÔÄ»êï¹0øÈ\î¬ò@Tì>ïçõ *w¬ƒK5ÎnFöWFùsãïcT´¢ºv\Ý­¦ÌBÕ7.h|£0X /Ë$Ëtž*]l$I¸Ü‡*€Te៵æ!z==•àÊ é˜¡Ès³‹2vsH‘Ž„M°× éЉ0·ˆ85{ÇbíuPœç ÙÎu¢ô¡Ãþàs¤ xï{8w^«×_‹ñ+ÿb¯]’è§Œ¾'1tiå…{ZTlF•ë¢ðá­})r׾ȼw,Ê1k ·ù¥,N¶Ç†2{²ÙÎê=E¢lž#èLس-ª,ràÎu‘³,UûnÍZâºÀÔ[PÞ!'ßš€ŠÁeöG¯©˜Ù7È6Ë´½±;«"¡È;`X,á?'éì­&7o].Sc{xoïöQzM‰È³n¯Û£-£²³)‚ù#†¼ë¦¥šZfGÐ5fYÜ.²ŒrpdŠÓª3±î~¶yHÇΤ¸÷R„X’¶§œ=^s̲œ±`D¤g–Zca1ÃÁcXD®¶ÌUV£<±Ù2ÁždÌ[,¼ºÄ×Ù…D¡U·bÒ›“c?ÄÙϲÉ7b:2¹l2¿aˆÂ»À6›¬Y$ý+ὄÌÌí X]“øïqíç÷pþ|Šº¸ôF„—_Žð¯~uƒ”1H ½ @œe@-±scˆÊ¾ †ìßy3)#6?§-½y,1k?32¡²Ê˜†X ¹ÝÓµ{+cÐïÎjR?eô‡Œ~BØKu.›×߉ môEsfJËþCe瘀¹"KYReëoÊg,©‚! Ø CaíïK9]\!ûÌ-Sqcö‡¨ëL Æ` 7Wös¨3Ý>~VyO¦VŸPŒh:¶%;ð†/l¿£Ê°ŠHB¿9áU§ÅΛ_¸Vu@¹À~_LÌ8ÖgÄŒpÉÍ«ÊMŠ=g.åŸ0:Ï\Ï¢ù¯QPØÉ,¾ÇÖy]ø £°8Åþ˜…ðÐT È á}uöûFù½w=8?©bí&H(Ë­¨bþ¤3=zÆÎÿÀ‹«‚l/7’ì=Læ›õA–™ß[æ¥ê"‰d§zÓ7yHK'Ä_íä;ä ¸L&ûÙZöÈÅž{b€¨ÚòÊþúÈkaå>Å<¨aÆÞl$ã÷ÿ¨ƒ?ø\‡—2ìíº;äæ&ÖÍÏ€£ÄÌWøÌƒ rçÞ¿JóÈ>Ó$òŸuù."gÍŠÒÄ‘2K³û"0xpÌ\›sÅ^F%…5" [ÿmÙ ©‘vó·¯âþèÝ'•³ETþsÛ&ï z¨æïÈvïKÅnø+yû›òö~f8i÷Aß`ÏÀ B_WLæØ_!(  ¯Û¿©æ÷Ø ‚[§¿Iñˆ"“*6™25T̘«–Éìƒ./~È”7Þ2@zP€g?¬×±I^j{ÑÙ†û·Äå1Ò¥ ¦pì³gƒ¸srõgæKHäïWxÚݪ‚³X pÃN`ÕZ+Í)ö£Ö¡_(QØ× Šߩȗ©r¿ ôY„€¼æTvæÏI0v¬ù• ÷óŸ¾U´ÐÅð>åìQ”?S³\¥RqBÕï=£¢ü¹q!?€äÀ…Ó~·ÌØA–ÏžASU$E`áköª ߤ¦ë/²(ë—^›Šô‹>p8Õ 1bÊAô‚iÉËÙiy3HÚ–WËë|[^9بMuwë¹ “2®Z¶“mAºÍç¹vS—ZÊÌY`Tp˜Tºî(~@(Á«š ±©- ŠÛ‘úÛ7¸8xdtI‘^WX²þ•=¦Ð_ŠÃÛ…ÄÚûÑ{oóKÕ >û°·iÃ;s›r·ØRõù¨¡«±.ú„×­6ÿƸŒ ó$ÂEþýdžVfä < oÏCnÁæ&QÞ¹í›7iŽ ”¾&™w]…)¸¤)lˆ+œÁ}6 á¾[rŒåò9#FÈ #ïÞi{ëŽÉk{À›(”GìØޏ¨ì0E,yÊ¥Š­£˜¾¿P#Õ­l´´æï^Åá“)bÙAdLÈÃÆB”‘DŽ҅~$I¾Hàð©òüQÜ»‰Á©¨rïǸ%šÑ#²PØ5ª?Ê Ø"Þ4‘ͬ 26ÍöÚ(Yœ“yr°³y\áe4åŸ?RÁ6 < ¸÷؇äØEi®­½Æö±3„bdBK½ÀÅKÊä %)aÈŒ¡›5cs‘wÎ1„ña×Ý€¶óc0É`F‹sÉa&Ø0(œ{»˜&ˆ Æ?ȰLyNš²çÒLŒµ›Ï˜)ü{/Qúýiã2³©€2ï/5 'QÍÃaò×”Ÿ[Ê>®¹™Ø™ë²8ßEŽªôo,R¡ŽcThÅ‹ºÑàßsY·ïÜ×Z·Ì!ÒÚݬj@‘ Nwãäe¥¿÷P,Gmö¾ rÝ|ŠR)€ØH€‚Û–(3ÌeyO-ý[sÝ* ª[¯*¶¹¢9@ðbƒî¨꼡LUHR(Åqáp® ­zýþók 0 8ùA£^žŽ¦ûm€’;DTh¬¢Š‡yMöÙ8pTº®E9&U³jÅkí;Â\ w-hÍÖ·J[fؤÙiÆ!cïm²þØáy fØÝÿvíCÖÍF!ßMhÉx¿ÀdxeŠ ÌìJ¦¡4{”edlØ"Ã"I^ soB2çl›Ÿ/«^`\ì¬TÍ2_Ò„ïZ é»‹ á9øÙõm@@Æ~«± 3ëm™Þ|ÑZÕ9“ r@ÏE¸šÐ€lëBGúsά­¼z/1 ‰`F1œÇäÂX#¯AšÖ©JYÀŸËö”Òk?Ê8—£«æmxÞšqO’É\AìT±:U,Šr¯Å^qQ:GuU'UËêö%纴òÛA*€-ú¾½ÅzMYÑÅKx.~®:æQ c‹‡qŒ˜_\ØCU)¯ k Q(°0¥Yª*—Å')®GUƒœEÖŽëÁ‹ô6OQ˜³óY»Á%F^U”PâøÒ7_ï.¼Ã»Ši³É.Áü‰×=wö®‘™Íð$GÒH‚<òY„–פÂMÑÝßÔLnGTž ª’R@ò׉ý1)Êß(Úõ]1÷aùþ&•þ½(-Á…÷âk{Ÿ“‚þÙqË£S¼˜µæõ×qìL‚›÷Ú¥$É!‹{@8Àò±´dËõפ¬N³v5òî ò\¸òÃÍÞHLäF‚µË»5_e•ìq+ì' œ"£T¸O#³Î'b`Ê|k°ÄèHBÛ«¬ùü»w„[‹þú‰eŒí ’ò¤v~NÅug^ÒÔìx¹§4Ë> %ü_ óâË׬ƾ4ÃgÃ@­ÇžÎQ¤éFë‘*¼¬>»¯°˜ ¯‰BåÄ>ÞÞÇ©ÒxJŒdrW,ãB®¸«ûìë›ÇЦF ì¬ø‘› Qa/ Î%oÿµ{§Fö{*ÖàÛ²”bH€›—£@ ¢¢ü}ÁcŠêƒ%©pfi°49Wm1^˜"¨xº8|<ÃÝ‹’ÈÙ}Ǧ8ÎL“ÌgBZ£% ÇÎ&ˆZŒ´³˜ËtÍû5”ï;/jCÃz˜5, ™ßß¡ÁŠ19¨bÇj†Eš†LÆú¼5ƒöšU5ÁÔVzv Aa¥pÂJã8WVMÐÒú+Ì(i€dÿ½y䙘sUäÃv¦n`gÈ‘)vk™ËRò˜+eš%‚$ÒžUäÖ­¾Ç9ol˜µ¢ ಒ8+ÕÂcó–×*9õ[ –¶Çý6òK j.0ݹMísJÞ1´ì;ª(EðâýqÈQÁ†Q Ö­TNcí¡ŒÀ­-äà>\ixߠ⇚£ï>^÷EdTv;Øï°›ÿž^¨—}` ƒHR™CJI†\)¤íZ…º¨–¤UUl[¯nÖ ì‘âj»aá ¸\z`;BAë#«–’þ½ „#‡ ÁN¾KYäÞ…b¥X(Î7¬å}ËØ´¶" í$i~/Ï"X`Tš‰3ïÊ—`•÷n(SE RE!X|=±âpýÇÒ°eæWi¾'Œ_ßîçEþï¢`Ò œ÷ÍÜzBEñ4F.;Jõ×±|*Aüv`ÔÐ’¦ÐDhÒà3‰§-‡ÃÖ=WIãl˜óC!–†›y^Ô"è>ºÏ^ä טñŸ¿ö‹L’ òZòKHÛ8±’ᑇRܺ"1Ü–“T×ñ· b2È÷H{}­,Rd@Bá=gÁI–…ó¢¾L+–!“K`jŽ+åžNÊ|ׯæWIà²{‚wcovî^A4 ¹ÂÔ}ï±3ˆ„ÕM]8úl¯?à쳈.Ô\¡iyï‹b©ov–-5R¢,ÓÅ.ç÷œw˃™Uœfþ¾«ôç¥×ZªÊgr A.›ØÏ¬å hÚ‚#ómÒä5è²s³ÙZa@dÞ!/X–2Þ8`£8€T·†ªö‘t±|*Á«¯µóyɈÍgIlÎõ!&ÆÉG’ŠúkãkçDeà±f- H…^SÒ09±›}‘€c}ÈYð[öNÎ+I»ª Iÿ|Jp Î }¹ôßJ‘¥Z"g}4@cǤæ{yìk._³T©0gîÃR\¿&±»G^É6. L3‡–òäWìXÓüÏÈËv“’³¢ÞOŒÜZšy2žÜ¹i(¥Lv‘Z°×+#ã®ÇFh^ëÇPAQjÊ]4/ÕÆôŽ©)o ø¡¿Žõ£8íãÁ›ã˜..„U]§˜°/S 61¿ÐáB×å°Þªì×àù¸ÂäûGEо¾z i_’»¦¨¨áÏígö¨Jb7ýTj°bŸW ŠÌa],’÷û¹xŸ§ß‹„–£(ÒÏm@[^¡d”ÔÀ(H’¸†Y*þ·Bu!U%Ï€ÇXe…Ç·ÝVŸö÷“Ç…V8 µ!¡CaÀ‘µÄ¤°°ò‡¶íP¬@ÛÁ`÷¼¢ýÂ@ØÇyz÷m×€+úÙImå@‘~Æ¢Yo¤p¶îE9-1¡°Á>x—¢ DŠlR¥”1C§C@Š=€dÙFÔ³'«rÿÞH¬RóÚ2 T~nUË縰.ÕˆEd=§%†Ë$‹'Ò°ˆ3ߙȯq\`.Z8rÊÊë<€Ô_oDI燘-lrÙ‹\p-eO:§m©±s|“›vÁ~!ôº´÷€“þˆ‚Õ2•¥¹ö³°|8Ãùßl!Žõu~ýÛ-ü‡ß˜ÁD´#ïN oŽËkfH@dVïò™+ QÞ¹hïA=Ú÷Gc¤žÙ\ñjÁ‘•;ER7_É?Ÿ…a{=%™Ùa˜)á “Gˆ#öÖ ó‰ )£Ô06ö÷Ì€’”?¯aÔªÝA3¥-ƒ3˜yIHMç…}'M„2\t·çš_É Ä'FI…óTzE¸ßl°û®9²lät+J>Hš˜­ÏAª’ï0éy"þ¨À+{ÔTÒîZ—ÚÑåè¸9¤#§RÄ¢ƒXjÖ$6²°–d(³®ZîºËAZ4ò:+Ó%N5ÚGò›žØØ`9[ º¹gŒAˆÌ™5: Ïß´ ¶.ø‡†…0 ²ì™`NášÁf®ŠØ}Þ-³þ5`¢ÂÞc$®׺¼ÈÖÅ®”ÀóßvqdY_§¾ÖÂ?ÿ¥I/›Ý¼”2Ñð˜Nzî;—<'5À¹» !–ìöL+™Œ lk6àÜÞ£Cã†(a(”yrA«¹Ì.ìê>~«4€/»ðºMo!ó%ˆ(©ÛŠ~Ú¼ÕJ’G¿3WxÏû«Æ§»rV©‚•òí×÷éDWb¬ÆJ4HM'Ê›ê〾@D£™a|0îKм ‹˜_Y"22,{øGaØ4#¤jsdézv4¥€Ù„  Î-À…×ôhš3‡Fç ±é Û½ ’úž–*IY…·8òœ sã#Ÿs,3#–d€ ¿‰åß™'¯K³‚Û–ËìËOéZQÁäkY‘ÉUQ&PU…âm"ʇø Vå=ßì¹ý¬:sÜäzŸ pNÏ_{–!šòÒt ˜jåÒÍNÔ°”ðæMÊ™jvKåž1 €ï7Úú˜”΢)®-;7´xÆ6ÿî-aѬ¹½ß—±Û×ÑŠ(pŒ„õùlŒ7Ò IÂ@j{l™1DJHÈȘÉ£ +ѳìKÛä:å.}æ^ Ö÷i`fa™Ï£”aCxòé!Ž,gš©K»xæCÄø•6©¥£NiBÈ2Æ0Ó@ÑÊì2#ÉFcÏyá®DÚkÓ‰õçÔ6{E,ìkf7‹¨Àî^JÝD†Ás³‡™Ïø±iL'@Ê™ f98}Þos=[mÐ}Èö ÆÏ7ªPÙ…f E{M”‡'} â…lT˜™ÅÕª šmR{SIí×t0üû.±ã}0A÷«·Ûï?³ÝÍÔüÚŠò®•#ÙN½•[µ¢¼Ë)Äþ_Ÿ˜+Ý™)Z/Û°¹Ž=„Ìóž;7Äʱƒ¾Àín­FáP» i‹Ì-˜—¥X½9påúEyÎԔ©³ –&èõ.½ãêå8׫3ƒÿþl“˜™V8}.ÁòÑÔ½7/ŸøJKƒ£ H —Qš–]ò3`Â"ª-Ã!`{xOO1ŽO°r4ÅÆ=‰ÕÖ×¥;$Rÿþ¥Ü±Îþ µòç’˜›gÌ·Žê0÷nîÞ"WðX© Mæ}…+XëŠ,"éëËEÒã(— IS :ÊXXη¾{«„;7YŠj³ ×·ÿøûHI…ÁEd®…4±$3ðJå|6®é‚Œsý“ÃÊÆâRËË9b©‹Dòì½í¯ÇÏö¨í›3¬àkÜØˆÙÖìä[Òë즊³€î^JåͲ™b92Œˆ¿æýBõäÉgϧ˜˜ÔJô­{«—#lÝ•eëoS`9kÞïä2ÒÉeLÜ}¿·‡tCbëVžR,欴íŽÐîdR©ï±éŒÅc Óë7 7® ìîPìg,½{:˜ ”€¬™‡s³p÷(gA+$ˆV"”)3Ïå 4™ƒ#ݬQ~_èk­gLZ$µ¢ü¹¯(ÌÍ3—õN¼ypý²Äu òP¬…}ZhºJ_N oΑƒ‡vw ;»Fú“‘3Wp!±ì¹l°´¤pê\Šƒ‡2Ü»#ñÚknÝE ’BÆ’Ù¿ü}Éîm¯)eןÝ{'ͼÑÁ9Æé“™Þ?ÍüQïV„Žà‘@Ï ÁM¿˜¦¡5\PáÐ{ÑK~O¡à`¦gÔ>R žöæ–Odؼ(Œ.ñœÌ< l¾Ï<šî›…¶3Žƒ=á^±0`×îODÚÈI€ÌÚct$£mŠþ¶ÔŒÒ±c)GަîÞ%\zCöƒ ˆS8ð/ˆ1 ÌêL?ìZ"†”9(êDœö‘e± §Nezï™P 2{Ï›fïñ™>ïszè¡üe­ ˜¸÷ž|fˆõU‰/ýaÇÌÃÒŒ‘’¨OÖb­Ð Ú° ݬ 5“ÐÍf¤:›}Fƒ¿“'RLÏ0NMñæº]ÂÕ«ÃŒ0H½Ô3hÊòCÛ²šLÖõ­‚qçÜY3Åí›õ)êÏøþ ßʪ9Ô þÜÒTtšã‚´•ˬe U3UŽtE S5‰~¿¬×Åþ$voUv÷ f‘¤(düÙ +}ƒTI’ÆÓö[·(ì–ŸbÖ—þüÉ..<ÞÇÔŒ žóWÚøìoMãöméæ |Iž=$žyWO>ÛwÏéý(€íM‰«—büöoLãÞ¦ç„.®#6øŽImoøw"&[Àû?¸‡GžT^¯aŸpõÍŸûì4®šz»çÈ‚“Y;Òݦ‰8¸ÀxøY…CG³‡ªWs÷.áÞM·¿(0©Mºñ OvÃ5¶×(8nùàÈvÄ[2é­8|’qæI…ËŒ(.¼°g€þ°y‹ð½/IôöÊ2;'GòäM“ †â1’…ïÌ+– 8J} à˜¬â\ÝýÈc¢þ:–O¦h}£í>W;_a¾\(ÀéG†A÷XÏ ¬7?nœM¼•c‘{>áÏðã?ÞÃùó)z=Âk¯Åøú -|î môBbd¶Êc9Áž°àvt9ÃG?Ñé³ fçUåkºu%µWZøÎ—;eFJ†×l0{íí‹8ùt/¿ÙÂÌrÈ‹Ò6NtwÖɯÌ~Úžžx‚#g²Òë¹}Mà‹ÿ&Æí[º(K PqÖãQ>h›!#±ã„ŒŒ¦Bîiï!+UµvòFrÇDÎü9ÔÒ½¶ ÿìDä>‡–$œ}(Ãcoϰ|2Ãô\õ‚½uUàåoDxñO" 2ðeÚ‰ÎoþØý×þúô»R<ôxŠã§”Ë‚zý{~ÿ·ÚX¿-t1[{eÒÝp&ÚŒOüÅ=<üd‚(_Ó•7"ü꿘ĥ+QRp¼0@n¬áËëü†ÔDl$uF†<Õ>ø‘>*Á±ÓÕf&ÝÕñe†Rä â(—€ªsK8ìTkK§ÔdæcdðtÒÅÊɯ¿.œ<+U¹rÀÉC¥a+ãÈI›´è5ZÖ°ŸÄÊÛ¤ £2É´ì5J œ=“á½àÄÙ ³ª÷‚k—"¼ñºÄïüV12ÑL0z¾&5ò6ËDF’µÔ\&¥=G—>ú‰>NMGî=·/Gøæu‚,B€±r,sM'Ï`0÷:/â‘'|ûKm¤µ>òÓ=?“bn^aõ†ÄÅW#|þ m¼|Q:ï8¡4ˆÊ¼sUƒ#mÉ®×,c2Ö é‰ÇR¼ã=Cœ:«·øµµ!ðæëþÕ¯Lb³ Ä<Ò0¯R•ñ ÈpuÓCf¾±0—Ão± .[¹”í²÷Ë^p±çBZµ'g ,> È'Ô—)(¦r…ºP`ƨ&‰Qãx7æâù]Ù}\³h<wŸŸå[ I#%ˆ<©…Ù,¬k-@j:ƒT¢ÐimAܱ“íÏNñÑOìà¬Éc±›µŠg•B¦]=@z2ÁòÉ-<òD_üÙ@F·|fˆGžÛÃñ Ã’éèÉ.ŽžLpî¿ûËóH¹5îÌ\†|z KÇòY Ï€T Ù_ÇÙ‡{ø»ÿÅÿßÿñ îÜ‘%€4 Ç?ÕÅ©‡ôóZ¹’ß¡‹úë˜=ÐÅãOg8}~ˆû›3xá…vÞíM>år{ßIß}+Ê»–Ï>3À‡>±‹CK©{Né—²D²ƒV§‡s qî‘{xþsSøâ¿›tàÅÎw¹N±)žûñ'Ë ÍbÍœm«J!‡˜YèbfqèãÕ箼J9?Ú^å¶AЦ?'s`n×àÜðäû3:ÁÁñ‹{9Ø@gzGÎ1®¤¸ô‚ÀõWD(³£ðqãè4È@´Ó]”ys(10w„1H€koÓ}K)7lð縊]Ø·€¹¥¬–½±©ôEfÁþìÁå4`‹Éêè#ÌÎ(>i«Mç·‰-oAà“Ð Œ m«û̳üí¿›?ÖÄãÉ'‡xòÉ!>þ±þÿÏy #sùº±²¹–ÇMFÀÏüì.žy×ÀÀ*žAÖšw®š”ö!‡8r²‡#'SœylˆW¿8‰µË1ÆÌ¼Ââ‰%S'ÐÞ¾ˆ™•´–=*Ê”ÛQ˜Yµôp†óïK´LYDHÛK਑tõ×qø¸Âûÿ\‚ÏÿVŒ Í&Y™]ä³H¦û{`Qeï,›éÏÆLM23^yU Ÿ„3H–¡Q~-ãYY;Kg#›jY–:"ÝE€'ïùÐ'ÎgÁºS²ã^«û NôpäÄ'Ïeøì¿jc(arMòJ à)<û¾GOe8t$/Þ”œq‚s¤8u.Å¿ü'Ó¸}Sh[o/£féˆÂÇþÊ.æÍ}“L¬€£(í#¬áäÙÿÙÿ­‹üfðÆeYÊMòùO'E98š4{£+zöC|ôÏõqèpì½E2³’6n“j™rq_Ã\(Øà¥†¹™Ç‡gÖ6ZâÄfFªÅhI}êpU ™ïƒ‹ gPr­9ܨuÁ¬ÜIŒ¡G™(ÈI=‹þåE…OýåÎ=šŒ]‡ÇÏh°qô˜Â¯ý‹ ÜXóœÍwfÖ#Y#©™"ý™kñç~ª÷~`Ø|ïy<Á+_è`õ•” Tž=v¤E°ˆ°r*ÅÒ@²]õv¬7%Û¹ojÒPeÓ =¨ ëªÄùÁþÞ÷ôð“©ë6áÌ™J;cˆw¯¢µs §ïcî`†oþö,âØc}4Ç)YgÑQþr¸ööE>ÞÅG~j_ûí9×ÝÏ'»X:–@Å3èÏ=&4s­­‹ˆ±Š²‹ÏþïOÿC‡SüÄ_ÛÄä´‹ƒÙ‡*_ûpú (룽ý*¦°ŽŸùù-HšÃ×_hça­¤åw-Ákl (+áøô_êâïë¹B/™:Q)»"NÑê^B¼{Ï}`‡SüѯOiö‘`Dpàãâð)劌dêxm‘&’.ÚÛ1u`O4Hâò+#ÎÝTE3ƒ Þ>8ò£v,,1ž|¿ÂÜa‹ÉÔ $S'Ê32Óp×¹…u<ü# ‘n¾*«y n–Î)8Ƙ8Ôì.?ñõ‡þÖ]ÂüšÀêÍ0áÝ>§Ê(ÿ³ª™7Ëž´¸R^åƒÀâçm þCÇ—éã$é Û¼«“gÄíЖ·‰ë”ظéBÕX×ZvF =Gð“ŸÜÓšÙ H¦N€X7â½U,/oàÏ|ÿæßN8ÀàÏÉIÊÍC–—>ýWvpθd%+Μ©³Q­í‹X8ÖÃ{~v«/v°þZ ÇŸé»_¼vr¸Ù£Éø÷Nº±rÀCe8ñ>ïuÍ](}n“wžÇ¡£]|àS ¾ø¯clmPPŒ[GÉÅ…³Og8`äjUŸ?¥ú=,=•bé©êµxgð¿ÿ¯-\º¢YjkᇗœE¥ÞvsŽž”àÌ#©c‚U<ƒáÔ dfM§nÿýèÏôð{¿4í\%.)|üït!#FÖšGÿÀcÁkàQ´7_FŒUü§ÿW ’^S†QFÞ¬ÌÆhåŠ/ßÈÎýµ_ØÅÏ Çî½ö´º—Ü<_“sŸ]íd P<;S¯M…Žy5Ù8ËÞ‰ÉC5sHfY:‘j ,í 9§B°•vjÉüùÇ«Üërz\,ˆ®;ØÉ;#i©*gFìÜ×G>:À{>0Äô¬>oÓ‰ §NÔ®C9Ü@gãEœ4Á_ü ã—þÉTÖ m™ms~¤U´æe"~þoíâñ§›í=r¸V÷f6ðŽOíbíÕ7¿ÝÆòLÎ* ª¼k™ÿÓg3,žNpøD Ï 7ÿXê&KÜ»‰¨·Šw½kˆC¿<‰®G±Û3 d\÷Hƒº£K~þoïᘠïΜ)ß³¦1HYÍ—pèð>ó »€¯~-f(m~]*XËÈÀ™×}ó]ÒÀôVq\‘5Ä÷%= áŠ.>ECT„ÇU:É…¯œs!•¯ÆHç,`bòò™P~nÚ/+Gå‹Ð ¿]ƒ™ë}1HÆuŠúwµþ24-°ìNfSïÍ0|, ÒÑü³,½=ka!t` í?÷ž>þü§»®›;œ93r0<™:¬5ÎÖ˘_éâéwqàäЫtr¹²pÎZóèÍ?…©µ/aéD‚ƒú5\x‡Ï  äz Ï”þS„áÜDƒ5??ÄÑ#¶îéJpn1ÅGÿæd¤‹ÌÁÇFµËúóO!Þ½ŠööE|ê¯nakã .]Šr¦(Ë$x¦¶{ösc¿½žµÏG³vÑÙxg€‡„þ`B²æ@˜™g¼çSLÔY8ýù'Çv¯U<ƒÞÂ3î½¼íƒ6×$îÝÕsÒ›+±z\pÃôÁ´ð ¬dÈ2< KŒ÷þtåÏÛä:G½›èl¾„sïÑ iý5ážóÐ)Æò íYòº÷í Òn©3jÿnnaïû„¯þ/ÒuïYrMN‹•!"°p-˪²!A¶xä’nàØ™ÝïÅ΂°áfÞÏ€g– ›&öÞöýûÖÅQ˽Øèâ3Î?œàð‘Ì‘v=¦ËEà © 2ȶ/*|æovqìTÚxm¦¦1ÒÚ¹„V÷Vžêc婾ûŒ‡3gÂëÖžÛñ·ï9æÌsg^áØ»uáÔ?ðXmS§·ð &î¾€…•.ÞýÑ_øõ–ž 4EYKÏý¹+²ñkQD`Uvç-“qhiŸút‚ø‹í ŒR ÌC"3TnvΨm€‘-ò/qþ‰¬ñ éÄ2T4‰{/àôc)Þ½•àOþ}ì†Ðç—>õ·{Ž©®kÈ0Ex2íbñX§Neؼ%A LPxî/îêaû‰ ÒÃ'>9ÀïýV„µÆ™aÇÌkˆM“@ƒ à?ý/wpìTúóOÌsru†9[;—°ôÐKž²d0{¡Ìü÷×±t4Å’aŽús:£â âÈþ:)æ}‰¡bx«Ø*HçÖ‰€•ÅU5~«ÎLÿ<ÿÌ/ì¢×›Æ /FH2ÝÜNëYKåE“XZÈCý|z–qŸí~ªYžÝ7ïËCºZ®¤X†ÓëÌ*.?ø•‹ò’IBeÂ[å±W(J÷ Ùo´?Ê'û¹ÜX™~ÍO#®W# r+;ˆûÎwð‰ŸÙ[`Ôç“k_“ñ¬“¿ @g&Ãt˜=:ÄÊSºËÝŸ²v³eÒŒTÜ[Å±Ó xG¢=“áG>³9öp®y"ë#Þ½ŠOþì6~ùÍCîh;ô’²‹ Ò§þb¿½zŸi”Gå6íƒÏ`òÎó8óä«ßnco“œ­ésŸècú înõ>5öÐ)½—dqoOü¨Â—þµÌ³Š¼ù÷íIˆËâÍÉ‚)ÃìðäûD„}_çtb}Í—pì)…Þº@² 8Î8ñž,ïO®¸ŽuÝWoá™±Ï7¹öe9ÖÃÜ«õžO ±xªYG|0{¡Td¿¦n'Ϥ˜šfô7='Aò@¸‘ååŸ_, ­ˆŒÝ?kpô³5-‹ûtþ)LÜ}'Êðò—b$)ã#Ÿ@Fã;òþ&’.ŽIA›ZŠðÄÇö0½ Aø¸}apàQÝ<8ÝÃ;Þ1ÄWžo9à Š|wÁ"{䃣ýì½vÖîÞžpL‘6d o ÝpúÃÝÖJ:ïjFÉv¯™1wptþ’HµsH½ŽMqë¶DKjPKÆ€†9oxÎÎxóG YLÍTeªkóß’ÚµM ì-?û×5‹Ã"Â`î±ZÖ®¶QxàQLÜ}>•à?Û Ø1H©ržš¦æ!LDŒÏüÍ=;•5UçbÚYBk÷ªfêäDeÀ®J³£¤Õ†…ÜݘˆôLVšYy C@³yY ÁÑ~öMËô·º—ðþðÒK‘±Èg7—$ŒUxæ»P“'§«œ—¹O“W0{V~K”TÁhÁìÛk² @¨’ªbЍˆƒ|©,…Ðwr"*3@Dûc\hà¼o€Ä PPÍõ­mW !®‘–䯆»ÌÃ{O×à=w§?™áA|»Ù\r÷ºXR`%¿pHᣟܭG–VIig±r×ìÈCèl¾¤¥t Á•í²Ï,qÄ€£áÌ™±›Gú?p0ýðÄǺhO)ÓÉ*ΔõGúƒÙ Ió‹ø‰ŸîâwþÙ@‚Þùžž}¯Þ$‹tÔ_×&N‚Ž}±XI&V÷VñÈ{{xíßO‚ xèC}ÌÑö¬Epï^…n@$;n©ŠÎ]€n`ñDÇÎ1.}ϳݵ,9—e´.w…ÊÁÖDàÉ÷g˜;̺“v×9XFÚ_C„uœx6Ãõ¯H,=¬1pû¾/dÈz˜=¬­…ÆÌá~àX#é5A0~ïφ‰îÈ<¤Ã'gm÷ÕÄ04–‘’T²»­KˆúëN&”L,×®ûqÅ³È #»-Ö+ïJ0¹¨=¾Ý¯ìýwìB†7¾®MSVÎgX<Õœ¹m¶'@ª‹ƒ÷¶( _u÷¡Fvö(&“)CÆK?þÉù{qª÷ãÄ–µç‘¶+_{ÖšGÖšÇÌÂ.¼-Å/FX8žaîjl|v ÐslXztˆƒ§uÁÜ?øTÃâï8ÚÛñØS ¾õV`ÞQîpjç÷ì Ç_ý…½‘àH7Ü^é˜ÆÖ¼f2'WÆœ—œ»²"EÈÿ¶˜Ç¢?HåŸ'Ù!y3óãó—F5ZâÞ*VN§h=ßB;²’PvaÇZŠF¸ðxE†šyÜîj3€ä0%#b«4Ém«ìÃŽF­C»ŒZ‡—6ð#àÿ°e1ŒLr“ˆv|ì'ûxÌ:Àïü^Ç1k$¹ž§v­£J¦ÅK¦¾oÚÙÉýÉë‚ǤÙUÕ¼S!Ö8ÅÙ#®=E`僤°@¬ $ ±ôZî qÝ .ª$25W…¡yóTåvîŸÊó– B*†Kk 3¯l;ε‡érIψAÉíûØOí!2y$>¸±²(ÉNåAšN,ƒ·_uÝݨ¿n~¾X9À8—áÀÉéì4/’%çß»‹…Sš¹x,_æó"ûë®cN.WnžN­} gàØr†ÛkÒéôä‘€ùƒ~ì£9˜ô7ëöæËˆ{«Xª»^Ù3ˆ{«˜[N1³ÇR:—þFÚ¹÷bx€f=Èᢽ›è->lªL‘+>Ž2Þ|…J:¿qÃ~˜§ðØ£‚µ÷…gOè™#ŸÁ©ºÎYg±Vú38ðäÚ—0u$ÅÜ2£sP® Ž(ÓCº;ÛÀ+0¿¾›‘SÅ0U6UÆÝϼŒ›Cšh!˜³^Ø’Àãf'×™BãÞ¥V#€dí†mäFm¡QûïÌÍÞ–•ïä,’½ÇÎ=œàé÷ö+ÙÝâšIí¤[»îG¦Ó½a¸-‚b¼î iÍ2æÏçr˜¦_Ygqo‡Ž*\ý¦^ G•+ÞKÝå ¦ s]‡Qoµöz'S' ‡8ÿX‚?ù|;g¾Ù†Uk7ÇÇ3<÷cC³÷<5ò¼I­¤ Jûc×mݽڽawMŒQ7.GèD&Œ;RAÆÌ‚!Hß¿üHKÜ™éŸéw_ xäpQ½Ô|bŠ0œ½€ÎÆ‹xìÉŸý·0kBãó%á¼U…8ÞÛ£×3á¨Ñ¬í0TÉì¸ÖŠb†Q¡Š÷Ù%*#0Ð¥bWøE;ö‚q«X£¦×Ô|*„ÏÞ7ƒT÷âQ .ƒÏ¬üø¡¸SˆƒÈކU(Œ¼°X­T<ƒ½CÏ=ÎdcVÏs¯³3%¶YÎÞû¡N]HœäÅm>Ûï^Üþζo´pöÃÛˆ{«ºKY±éYFhêöçK›eÑIÎ?”çÏôhh²©ÙÇN„ãïÚu² ÿßú bØ•hÍôÐê^¥ýÊÂeÇu•ŸûÐ.>÷ë³ Ùnº àã¡‹©ÍVù×ÀnÖ*%\~ ƒ®t׫RÏoôå­éâi…CÝÁáÿ¬}ÃmÂÆËƒ- žæÎ+Lu±ô~ì¿?pÈ ü#Ôöá1H¢à^'…–ׄÝÁ°[Ý!•ö€h¢a:¸usdÉÔ ´º—0wF¹uõn"ê¯9g>ûçE95ø`Š´«_Ôq™!¤Rˆ´‹hO·®j—²,3ÀÈwôCžkBd‡:Ã}/6ÖÆuEúÎZ„§†F¦6bét‚7_‹s»x3„nÒŠÜMü愱åݾ7¿ßm³ =Cn²PÏØèNi[ËEüèûxïüÑ~%»ï^ukþ{¿3àÜï"î­‚£Nµ¤)ëëùCjçCÖ_ncrQ_“qM£ÃoË¥uûÙWm“inYiÓ»w€ "ÙAgãEˆdÇ]Ÿ*†súæºÆVqNIo¼&²~¶HASMwìý<'+9jIÆ>‘ ŠÊï±³ñmJ±}MâÎK:]üà…ÎꬷðL $ÙÙ”ù£ú}ïÞ•ØX˜_ébòÎó%×DOl^}gFÜT9àï3z8²¤Ün>2r‰ÍÌ'a~ øÐO˜u8}¦ŽlÁ9ظõR ·¾Ù†êHÏ&X|tˆùÓ â½ÕÚ׿[(/Θ8w5rµ[Þuf Fö™§–Úõ•b2¶ò=±e‹°Xý =÷œEã »÷\úÜî¼ÔÂìÉÿy½÷ bƨnï‰úëhoéFðöµ³G«ãÂk¦ bìýšªÜ¤A80«ðî Jg¦¿V·oF¸ù6²áø»û˜YîVžçöZ<›êl(¡çõ6ŸŸñŽ%²‡Õ¤Áßg+?')<ƒ†À¸á¾*Ô2]Á5ìJ`Ñ]g5`ƒPM…øa­¾Ñ“\±†Š2Æ&‡s‘q#jäØÜ¤¡Bçr^ €ÖŸ#SÖV•mYFVØãrW1ïq,…©¨ÀRq(×ëŠGøŽ|É:…E’{ïYp0ÈÌäÀ›4ƒµ—µv:jMãô»hu/U2*žq¼îÍ—ÚhÏf8üD¯rÕÆNê®`ÖPËlÿÝÔR •7ÏööE] oDxãg±{;ÂÜñ!.|bËó£X¥ f;À É%GÌÀѳCœ|H¿VŸ­ò7ëïþêùJ7¿Û‚ÀÖÕ/ýÚ4žøË]³÷THüD„ɵ/åDyïyµ…«_é`îxÖ¨®êÄ¥Zf—™×‚¾?þÓ{˜žUΤ¦¸V·V#¼ô»SHw4÷¿·%ñìߨª<Ï=¿&2 ’»7„u#câÍT2 *14Eº}k vÚŒ Ûíû€\Ar;œñP`‡Š¯‰ o‰FèÆJår IøJé²\f¡*] š¾å‚]ùƒŠõ 4ƒÐ5®ønð²ëþ>ÔDY’çËêØ7ñÀ[—Ø=PPd Ƚ{äºARébW‰|˜ï™÷pàPæ4Ánóé^2FÇ#X{y'Þ·Ùê”œÜø“)\~ pì¹Ý ¨©•\L„‡¨Hºn¶ÁÌ´ ¡·áà™Aå†mÁÝ«ÿNƒ#)€­k-|÷Wçñø§7÷V‘N.—:D,;ÈZó˜˜ÝÀCoïáÍoMä À“ïÞs~À¢˜ÍúúóSÀ­oM:pXÇ(Êá&ô†œ¶—ÂÇ5ŸÃÎ Bº"º„Á¡=Ç%‹XJu×{o› P\#A-ÚÀ @HÏÐ#ŽœåÒuÖöÝú}ßúºÄÞ=Mû÷7 k_—8òî ñÌ¢ÞÍrWÔ›¹÷’®Æ[sŒþ]B²«ßäò{GRý»„îu´d´=Çô{k„oÞx0©™#K,Y}·šJºîkûFÈ.ª -IЉ¼†… ²€³†9LÛKàö? dˈ‰z&ÌÞ—jH9@âœAzÛûú•k^ö×A*ÅK1n¿ªg 1[×"ܹØÂ¡ C´º—J>ËÒÎb ‡ê®F¸ù­î¾Þ{|Ûýmñ‰Ô±G>³¶÷ösI’ne7ÙèYÆÞ6¡·MxáWÛ8°Ìà”‘l œú‘S‡Uu'@²K¸öÅÁˆ( 2`˜7® \¾"pÝÒ&ʬC¤°¶û&Ù>PvèâZAÆùG³êýÑ0[Û«ÒɬìYuó…Ó˦÷{_õ¾'çö¶$ºwžÿW˜[TØ»#I`éd³YËùÏa­ýíçk¥Cu „ãgR¼qQ»ê bD¬‹M=£Ç8|Xá¡'s‡Â PÞ¹9Ü@÷ŽÄWc ƒ®€´p`(¡çs¢võ=`¯ãΆðIÖ¹ÎÔµr}“ä`ÈÞƒ…ŸõR­îȱÄõçÄ4ÐÓyH—_Ì«¡ìyN8v¶‚…61—âfÕ‰×4'nlÍZ¬}xR0-±¯yóštŸ™ýºù6žN1³ÒCÔ_/$e¬³RؼÍ.èÙòùOÿH¿ríGFνy#Â寵!Œ{›$`ç¶Ä­Û8òÔíí‹¥uç›<ù{Ͻ7b¬¾Ð ìÞ³qKj7áˆ!i—XS.,*\xÂ4žg*5cH È¿JlV•qÄ>}±iDøâý¤Ò¬çéÀ 䛘Ï*Yé›Ù@`ä,ÊÐÒ$Ì&Hú*c“è§S+·ÿY/t¸Ü ¥–(uçï!QÖ/e3X©P“¯ª­• ©„t'HäÙ>,IkV%pêárGH›2h6äÆ×¦JÏ·w'ÂÌJ¢©û}¸(ûz]Á¢¦Y$Zþ1˜½×c~"-½+¯ºòµ)Ü[´‡{w#ÜüÖ$Ž>»‹V÷R5»a4úK'¬¿<á$G³‡S9Wî¤ÛNn÷fŒ›ßœ ‹xÓý¯ËÞpÃÎ ]Æl¶ý¦: vsQÕ²)ÓÝF 1S¾ÔLyû’Ì’ Pfwöm9{€hB·®6¯ê ÉÞG¼l½N8ôv®eí Hgqû«!KÒ9Ôì0ߺ,°;zC`wì =óßI¢ U'“4 )c€¹û<—{Áp ÏVšEšYI*5ûßóÇ´¥W@‘Épôtëw~ï]j¡=›¹â¸Ã†L&‹0û• [lràï®Eˆ ƒ™ù‹Î$\xm±ko‹¯Û¯i2ႉ-\ÿê$žI÷V+çlæÈÍ;¸òÅI';ÛÏ×Ôᬸ۽Ã6âÝ«#Ýè"+`oƒ0ØÊÁêï1}O»„{×%vŒ¡Dw@Øîݰ3†#É´t(UÚv?Ëò¹Ffr¡¸dy²¡¿ ?­™M•ªB1AXŠ›S‡•vB,¬KÛ˜™YT¸sMk®ÀöºÈ \¿ffenÏô÷Ćî’w²aâàFeìÖ» 5f&HÒ×J» ZxÿÇûNf冾ÒáŸ@wS€dÄ.Ë«%uQ0µ8šEå@á“ϹÆlÑ2ÙkÙZ*>[a7ÇÏÔ»H2ýMaæz#çާèHŽÌuË2½‰-ŸÊJ²0 ^î]Š1¹Ð,(–½s@;·êwqìt6fšàZ¶xÍsG˜YÑ™k%€dlµW2¬¾ki¯Ô×8b@ú°°÷Øýñök±Þ{PBgŸü… ÌŸIО٨¾žÆøâÎ+-¼þ“àŒÐ4Æ5”„¶ÌRÊų0Þ÷Ñ^åzµ ÏÕWbt7%DÁ8A:ÓM=xýª šžùÚ­šš¯(Æï3Ø5C.€$æ·À p=»ã?~Qöæ¨J–È v%ª–ÓåǪrÈóMØ{L>i_ìQ¾›o®a‡|­°x¾ÌκÍùkÁÎÝ(ø:žÈ;¦äÉŽ¬ ϲJʺØÙ.´ ×!30{˜k7C‘tÑÙøv£°º¦_YkÙw_2™BXpæñ³ åîŸê\ÿ^gòž·öåˈS§i¾ûZ—¿0cïÜÅá'zޱèmh¦ ·ãáb2³×¾ÓÁ Ë—¡5¨¸üÕ)<;ÀÄüÆÈp¾¹•fe*ξc¯²›ïéNÔïuöý¾í ~k¦ÚåÈ®˪X¹«2 ©=Ç•ò{€¬]'WŒÙ¹eÀ{sU0²%';ŽœæÊÙ=Ç+I–³/Òdm^˜>Áè,TwíýaÁÐýÌ Iͯ¥Z™dÀÐdDdl$²ŒòDØù€Ü˸ØD×ïov~´U9“v ›2»kÑÈ&! îHB*òzûø#CL$ƒÅ3þæÅà ¹ÜPhÖ–ùõ¬ºïí½4sÔß{öÇ™Â4Q¥eX9V6näp{[ßùã °²ç»«<ªéiï¹í-CÌì¿÷ï¹*¨ÈØÐ[3jpÅq@9Þ¯ÌΗ™~õm¸ŽÑõYŸâï ÿÎw® Þù,LÌÇ„ìh¦T0;¸€T°”«r® š:”ÏÙΪg`Á“¹.ׯ ðû„½Þæ÷™Ù_„ëØ!w$â\W>®È#NKEº v(—@1€“ñaÙòjƒ’;‹Œ‰1v'›"†4zl»OYÊ|r¥²ð]ÿÞD3¶Õÿ«tÿ½EY@¼{UÓçWZxýßÍ.aÛ7Z˜Y¢»ª+–óÛ‚ŠgJÛ¤Rl܈qïŽtAz$&Bg@m\11Ÿ"îÝĠΗŒoZâ>›}4…ÓÉ倱°ŒÎåÏO"íQÃYÏÀYÖH Ã& àñwÖ°Gœê}.!|ãßO`˜å'A–=bÇÈ5¼÷%v°á°ðY¥°KíÌ癃fS!¢}¨™;k‡dÒ­CŠúë8|:Å﵌ً¾¿žÉÊ,´a6.EûR±W‘Úß3€™ÕM<{=Í)6æ i •½u0 u{O+ÒvÙÊ\÷¥“eÉ ýÜI¥H„­»Â bm” ýw^õ\WE¾”}íU3C3þbic c ô3á¡g˜žS¥ÆÝ×ׯF$ì$¶Ììò“&æªë;KyéõÃT7•™×²®nN.`KŠÀ¸?‰·æýy¡R†Ñ}/g(áÏëB[ëÀZð*,ȃ?òe¾´®b©dõ]`hŸ º”P_̹©’¤pÈÍqžHîK ÙÜl(PAáçœð$œ®Î€*å™9x@HÙÃ9PòAS“â®·N¸ñy‰ yQ7ê"/µÌåÎs­HwZº;I`åDzFŠ•K1kY  `rVáàÑÌ„!.]‘õ0ìJw°’–¥3ËÚ¶²ê É3Æw~gÓÇ´ì­']¢X{­;¯·!8û‘íÊÍÓYp^l£Ÿiò×Hm\ùÎVžêÔ|‹¬‡ÉC)ÛÏ ÐšÉJÝ,?«¦ª ¾ôh¯¶¸¢¬¯e(;¤âácµÒ g¸õUéÖ¾RÀî=㸕vÑÙxÑ1¶³ÿí/ Ü]'$‰H)6Éu©J»f–ϰcͪ®óö-Ò›5‡€\ ö}ï*añIÝñàÑJvE7К R3V,L‡‰ŽÌ¯CÛ¹Oô{N”yïLŽEË 5gÆ*)@«ÂÅÎÊ]á’Mª ›x÷*KÐþƄۅŽTÈ“×ímç–åœË{ˆÇ»W¥CÒÀØí_ú X8^]TZ°±·%5øôf–R¥]–÷nF8…jc,`«éøû³Œ>p·vó°þR„,ÑŸjï®(í7UìQÑÈÐ{ñXö›5s4H€ABè'ŒAôSF/5€ÝH=¼¡Â18€ÏÌ„V›K3Ÿö3™]Îe bB&Ù±H;·äÈu ˜ÙûW©ÜœE5,¨oo°×ÿÆ×:ع­Ÿ¿¿%‚û¦®vˆ„.òÙIÀQÓ÷D†¹¿ùf¤÷9•œöÜdÖÕÅÂâøF,0²ò:¯ñ6ög*æ:HX/»¼éÒŒAÒÙ;£ö‘¨¿Žùã)î¾ë:FdÊ8j¤ò¼ÎÍ1J4ròP¢›Áâ¼™tç¦Ä¡å¬¤º°ÅüäÙa¨'o‚nöú H@ÒÍ_vuÔü‘êÆ„½·L ‡2 Š gö"SçÝ]5ÀÜ8IÖ1ûS‹)v×=[m0TÕÇÆõH7•„v’Ë H9ùhuãÙîë—_Ž1HseC¦)3–3L(3fþ¾ö•/·0P„!{êeçŒ=ÖÓVõT˜È§û0¨$\Äq3yb=ð*2F\&J|ÁeàGÄàƒ*BO+Íü¹® ñç•x÷UqÞŠ¨Þip?ÉïbÀˆrI–Ï妕…o À9öÜNeÁl7;‹ÓDšç´¼r"pÝ!•bóJ ›×Z¤p,e5"f–Gw¶×oDè›bÙÞÂHì2n_ QMȧÕFO-¦Ø¸ÔÆüéA5 ³›å¥véýµ¦3y{¯Ä:9¦Ì ‰îÜŠ0}$EkZ•ôÒÙ3ˆk˜9‘bçºÀÎ\´‘ö»o ,œNõæk6àþ.𽯠¼øU¡;Ø*ŸÃI¼y6÷KѬÁɲ8¸b¹B÷ßu'o’ëܲ×·‡”Ú ô7 i¥œQÙÏ}²°Å¨•ø™™{”#ïþé›Î½ý»ÔtÚR3ƒ•¹ÂPw;Ùß:ÞErç¶l<={4…$B;bb`’±p, š–áß»#±»Ö\Íagº•bœí¯„­ ¡RÖîIk¸ÒÆ:Ï%¸õÝ6¤;³«²£ª>FDEùõž?6šñcÖ͆D±s¾LˆCIàžUàÔÍÿZƾ£ß¹™¸–¥¿XšáÔƒíZf‘3_YÍ™yéåØÕ&IÆú\V@ÿ–m$ôÐÞ|YgÑEVˆ¬‡—¾ãþ¨eÎ76l4›æŽ‹%öˆ÷ÇŽe*œ$Îûïû}\¦j) W€'w¦í²+X£Bó0FÅ ¢*]}…$¯4»T°ÓÞG#3¼nã¿"Œ#8½á ˜r;NEò¥òÀ¢ûLÍ T^?¸ù#E´‡¤ð€y—cŽ ãcev#¯Mö6î~¢ÓþЪ)\GÖQÏR•ûÉëÔ‰Q./#ó¦˜ Dzê›x°áu³Âbÿôû»8pR²VYºZËß»o´÷E¸ú¯Áþo\jc/É»®¶ ´fhÏd°*nB×/·Ð·][¯èŠE>½u;ÂÂñj³ ÛkÏèë4»R Ȥ)wÖâÓvêG»˜˜O+óAüá⻯¶ÐšVhM+ˆ¬Ì“±Ô92íí‹8ò\ŠëŸ‹ÑßÈ×øë,qñËSK óŒ{· «W¶» ™îu’é +³ó‡;B‰¯Gí0¿Ì•l¡íÊmÞÖÏ‘yxA@*€Ø¬×½{@ç Ò2@2ÿÝ>ÀaŽÖþE©îÜ[Öhä€)5Qy¶ÊÊQD¶±ámtBkÙ…Ù DÃEÜ]Õ©ÊÊÞf4It1µ”bw-B[O$èÌÖÙòÆû›Ëñ3Šn«Z,rد/ X,åÿf”‹¤ÿµ}#Âüé¤vf…Þg¢¶B:hKÆ¡ ì‘ktFRÁ|)f0d@ƒqõR„³&ˆw¯Vd-ivëX†[ß-È}tȸ4õ]g®Þ˜Æ¾·õ«RXã ¬k³« ظaþXZ;ªՌĘöXyßdÆÑr“Ð`Z1cñTЏmæ"}ÇOóšo_‰ÐO`˜eÝ<égÚå2í/|­…gÞ9DÜ[ \ö¾óí¿ôÏ&°—úN-ANB®?+rõmIùUbŽîÓÉŽü|/®ÒÝÇc¤ìâðJÉb¶"£‡ Qbм°X_rWõú© €Ø ýÙù‡Ë>k#‰]ƒäº>léQß“tWáÌ‘?Jœ"ŸMbFîCãÑK¹ Bƒ'Çtíf¼w—œ,£?Ô7LϦ4Ë X!€DzÁË¥ÊýÃ'2¯uîHµkZ©K”Î\†Cô±ôHñ”î&÷çŸ,u·Ú›/C$]l®Æ¸øü¤3Dh7h^¹î¹•œu%®|{=O–’Ë0€£G‡•¥<ÏBêÙtiR°$åù.DÀÚµŽ'õ–šÚ³:s™–×U2g§~'rÀèÈS{8ôPßm°ýƒO•Þ³$¿÷zÝ1. #Õ2”dêD²ƒ¸·ŠãN°öõw_ÏÖ’>pû’0² 2#©Kmç:#—Ãb§cD@ÊëaoóK\[ ;kÜMmÐX©HäFÛwžÉjµõ¡¼¥ùW¦€$% ‡¹QÃÐû5±Í;s¥r¶ÀÎ!ÊBíàÏ b,ŸÌFv1í×Ö‡ŸìWêÝí¿I³+‰“®.”Ø9Û ¸óFkßGM˜­WÔs7Ù)BÎ~œû]þ¹é=×/¨µAŽ.Pï]ppL‘’fÓ*¨ÁsÒEÖÃîmáØ£ ƒ¡ƒ°(æ>hr 2ÅHRÆ0%Ç$ œ´NKP‡>{oÎW覚v^ÓŽ«ÊJ™¾÷?úã b„Á»"éºõ²¹*ܬ=ÜZÓY°—ÕCV!@™à‘—ÍÞÿNmÐ]á±GlöP5¨YÖ™‰:×Ρ؂óÖåënzâu²É’²pçЉ´Ñ}\Îì(8V¹[‹ƒâŽ<åé,$äͦ¦_Û× @ª˜Ù³{±n`êp†­«Ú'oñ|¹ygÙµ­ë‘{ÍMpÈ5~­|+SŒÿð¹6ž|Ç ‡uP·µÍ÷×áÝëQ €t™¬‰’21“5¦E¥µk# ÌHƒDääzcJ;·ÎL =Ò°„ÚÞœpøTµ[¡S¶¬jY n¢ý”ÐÏØŒþßÿdK¿6§Ÿâ¡ )®\•xå•ßüNd'¿é¢]1Sfd¶ôgmP•„·è8W8SØx yH~1O¾–«Ày’¶b®k}`¬oÑ]`ÕjA›ÚŸ €Æ7E@‰9yKrÞW„ÒX»JÀÄáµ­O\«µÒ8Áyx¬-ê\ʽ÷fm.’òœ¾˜«[›ÎÁt톦ÐOµ-±í,Ø¢ÎnY”/€ž$++´ €Éƒ ²Å.¤±êà™=šàØs»NÊf ¹ÁÇJÿƆ…f)ákÿf;‰>ì:²YsÁn&vˆqk5Æž)ö}€duñŽ&#ÁÝΦpú>@Ðy9€–émßÕÿa%꾬[^ v%޽k³G‡ÁõJ¦NTf¯´7_69No|~Zfwð¼vÜ©ƒ"B¼{KϦ˜X¸ö|„4c·&È;¿­dHŒD,ûé$°…˜›A:¦Fnö;÷tQì›?ÀïöþïšÙ¢ª{M'q hr)ÎeKöðfº£h_SÂ98Rf³°ÝmaºÎyŠè¾{acçâÄ@çëÊÌV´óBȺ3m^½¨…ý€.R˜?œŽéˆæ‡6ÉI\ÚóOF^’>nf]»q;Bšå÷yÖàCðïÇÈt[·®Ê@Î:ÕØnp¼szvY-¬’Ì8Õe6óˆùÌК2æœlÝmçY-»™šïD-¬­ üúÿÖÆ_ú…ZÝKhu/9i"¨ؼ. #ﯙÉ~£/•»Œ¦Q÷ŸÇ|Zæ¿»åî\æ»=W_ìæOéTö\›šW˜[ ÙŠ„—"d¦@¶ëTOÀÄcn) ‚˜k›‰ž 0—¤>¹±—]eî¡ßµÞ“ɜՌ¹ 8û¶oDîÁ·¯–¤Úܨ¹c)¶®Ú0òPÒE¬¥¾é°v1B$Øw’nt'+&w^¤™ýoÆoýÊþÖ®CXãÝ«Á:ÌR½«ÒÕ'nŸ[NÆÞCöå Ó0Uæ³rø•ƒñ‡&5RjäjÌeVm`¥ÃœsKrfÆ¡“Õ@ܪú=BšÅè§„^ʆb'›ÛºJxíjÇÑösH˜¤šír{Œ™»³ÌW<] @­ü¬yû- rõ-®ïÛÅ®"Ç©Ž €Œ>oœ”ª”ÝZ´åJÎxÅÞ`mDÁèAùq¸ò=óØ$Ý×çÕ~$\¶ø®J\hú† ÖòÛþÞf ö\ïTØ<*_(ØÈHR\f“Æՙù£^¢3\IH]ø)•wöí|SIøóU˜±îg#¤3Gß±›ú+•¡ªÄ)Z[üÅߚŭÕB-a7`v¥šñ±ópÙ®×Í×ÚÚ Ê€E{í#³‹Îš"SEÓ•€¥»)ÝÐxÆ@"€ÌrÂÀöæxm<L.&•ò ûïZ3Ž>»ë:NéÄJeæ e}—’¥„~{½BêGßEˆ°^À;˜½€,žA{ûUÌœHqz2Áê‹ëW…q c7+ç¾tÈ—î8{oT¬w/ìÐÎÕmöéÀ~†¥²×¦l'UûÚ‚›KAŒù‚›ïð¾m“AÏQ`« ´…½u²3E;À4–±ööîF˜\HGZÇ[°Ý™Ë0y( Š?9Ü©[7bìnhçĘöwØ„}·¼€‹mG´Öªº 'kØö¶V·™b°Ð6¿»Î9Ë1’ãþìl°\A˜õõבì6^Ó23;°¯ß|}7Ø´­5ÏET…b]Çÿš4ÏIÈ,shÞÓØÂ²"·ëæ7Û`Ê€.æÆ}þÙ@›Â܇°t²¾°¶bõÍȇ„fj5 „S ±GáÜaå°W”Û €áYQ+soº³–ÙÛk(ŸARÞ¼œ@ÅÜÑ}ÎÌø÷€ï¨´¸¾_™—ІÎbšBDBEiUä?y÷w`^[@YZ9¯´O] ík^ùDµÝPóFiЪ606Žt¹ö—ØDµÛVr «UL%FÒ7i ³@ÝL’VÂtùüÔy7‡Ô`¡dŠ04Úu ŽzžYƒ-¤íêym‘rðH†8Ò][¡Œ†ß gG&h¸N.ç;襜÷í$–[Ú×\džwnJ v…qvÊïùLÑx€ÖîzçLZ3  ˜´•1±M™Ì»'²,Ÿ ”"éçŒ`d3Ly $€Ë¯EX»¡‡1ŠZ’°°’áð¹³Ë  Z!ШÉ`Î+/ÿ‡šm”–ùtæ<«vnKg¬cC3ÓFCúi¬½mí2dÄÜÙë7¸±M""‰IàˆuÁ+ÌVSš–g÷ùX o»&È“× òØ"ØH 3OcXi1œ¸[³º«&XuDðôÌJ ’ìØ#_‚h×[P¬›‰hw°[ÛV@¬¯Åê5ÿãŸN£1–Ò®@;"´%cé˜Âò…sGSL-fcס“¨¯Ë°)1âŸÙFGk‚IÓ@U¾%¾hc–]C[÷„§b/ÞwÌše ÂõqE˜Y?·¤  ±*‡Ä°Iƒ„0PŒa:\Ú“2¹G6ÿ(U0e`ÊUjv_¥e\]øï—E²Es=tß_ÅÇ- ‡ËF¼†Ú›€*þyÁ편ÌLÑ>ß3Xènmüà\ä~¦Ký ?Ê °ÙX5àacÙM®¨öÏ…WLÊ¥I¶˜#ò,mGÙtù†=’9ÐÐJõœPéŽv“áêTý!aoHØ»Ã$ Ó<##’@'2 ’ÊíÈ#i;¤<æ3nÈ„ÌA4y°ºðÍZóØ[zïˆ A§a[`W.ÆøÜïNbã®!s¶è‚ôç•Ùl2Û=7ÅáæjŒ Ê 9{de’óGÒ±›ˆ!„Ònl¼ä…VpoM]些ªN"vk¤qoÕ¹ÐÀëßiá ¿;…tHB!Ê-ÓQºúRŒƒ'S´v.!í,Õê¯Yv°wè9´v´ÜfùÉ yà;Ÿ‹Ñ»—rJ…´¡Ç¬Ø™,)m¸+R9Õd:)ã¶@š m³æLÎÂÉC”£†û‡Ê„wHŽ¿‰”"$©Ð³FÆ ahMOŒ9Eê³fÞÝbYXÛ„H2B$‘$°Òƒ†ÄÕÌ‹ín­Jd¤\Óbç–É1lÔæ˜ˆ^³Ç‡9@ªÈ-Y¿¤ïi˜îFCÎ0k^¬rËñ-Ï"Ö‡=ã>V%AÍ‹…v¤ô^èopîÀ¹PÓUuv½·%)×ø´ïyj±ÞùË‚¶Ìóæ›ýTKX23pM ‘?÷~Æ®‘‘a©æºt7„k”%fÞ/UŒ¡_ 2=üîbþ(‹03’™” /Ô™æ—ìm:-àá÷%X8žan%+}>öºí†Ã$]+ë~eÎTEµç˜Hº\ÍîÝoÆF*d-·Íuj©ZÏ=¦°y]¹üd®&ƒÌÞ'kW#3¯Æ9G€$]8ÇBç çŠN¡EÉ™-Û|=·l3¦ÈØçÀ΂$!tÁl]@¥ùï…ÅÑ3X4ëKý¼Û×ë’}r¸Ùcie¶“›Ïº齜ôënÒt â¹”‰ Ñ(Â42ö¶$&[Œ'>ÐÃâ‰ÌÉr«ÖaeÔ…iP$2@½<ºPÚ÷­­øœBË:"—™Í ªmòøVáiæg?éæP³ñó³æJM°Ÿs§¶ÔÛËïgË63ϬÁü¹R¡¤KKÄÉ5ãÝ+Ff\m=LÚŃcyPi ˆö-5ÀŸé+È3Šº’T®ŠÅá2cT ê±V…Y$*hɨFØpR+ÝEª•رÕhs>tgµîv£bcÄ 2†rº_[4ÀØnæÚz ‚Š®u°wº¢¶?#͈D.Ýn@ÝZ5¦j¼vÛv5û‰Ð.vžÄnÏcìsd™.zlA µô†›v„\sŒÀ,jCIëŠ|9Ü@Ô_sn¸y%Âw¿ÑÂ7žoÃÎDÀ¥Ö:wćo7v[tÞz#BÂÊ#å³§hO¨1R!ÛÕË’´›Ç $ òhŸT´Hº:,±w3Iܺá«ÔÁ›¯ÅNÆ+…B$-¤1ðúË1–ΦXy(Agó%ôžù\Ãé3ÈZóho¾Œùã=<õQÆ×~¯…ÞÊ-é 3 C¯». ˆ‘îÔ R]X?{ÁÆ$!ñÝñ¬„ÕsL’¦‰‚Ç×u¼w[ õ†àu@g>Û7 Ì#(p¸œ3‘éÜ+E`©÷ )ÇÝÚy#Ö÷c÷æøù¸·ŠÙ•3Ëi S²%ƒ®ÀÖmÍ€æÒÝfkPÒE½Ec‰‹ T½íWÝ·óGSLĺXʔξRžÝ{g¯î\‹ô`5$l²<Õæ ùìQž%F¸ýz„aBÎvÜ:¶fxì~—Ù€Joïf‚ ÀÕÞÞAÆ–ßñ´ñáÜ”‰\Ñim†£çÅ›N=ñþ!Ž?”bz!ßË3Ó°©š-­ÿÔýpfaj}®FT^Ÿùò¹¹µ×b·Çèh]˜O謻L±-á²úlsò@M°«³åî‘;«…‘zZyg,±d;›a¢àI*©HÈ%qö×PNâuÚœB…Ýg'lC€{ï<8ÚpÀJÁIiiúÖG1"xÚÌ!Í®$˜<”•ÌJH¥Ø¼a°'\¡—˜h˜•×A:AÕŽñÆ:]è`Ô'ß;Àù§˜9ôÖסsxÄèñ–°ˆ01›bfØé"ذU{?1"Áµ9nž1†°ñ¢!Ù&¸öæËÚœaÔ:jÍ£·ø&î¾€¹Ã]<ûñ!¾ô¯;ؽãü¬HJ8´MϬ+£Ç49I5• WZi“ðä½ÖÑnØéo(å3H dQ{‰¦9ÁÎJy ò_ÓÌHA!¤‹(iŠéÔHQ”„Ó=5ÚÁމ]1© »dB{•]j;37¹àÈ—ÅlÞfC÷ÍgšØäæyg0AÖnï¢ýöÙÆ|ÚNöч‡¸õjì´ð¶°¨Ÿ#± —µ«:0›Àîøz8þÐ[[‡~©ç „@¸{mL³É„æ½0ÄÕÛFÊj”Df+›ž¯oŒØÇ½y)ÂPå JxëüžK. ÜdÎ3·¶• ×Î&%F2¶hó}•½=–½f›àsòåoÌAC¦zvh?ú°‚ƒ¸ÂEî~VaÎR-ñ@!öb*Ca¹<€íKç¨2ý`WBÁ© É¦Ûàý7Hì–,ç† öÐ@îBGP²’:_.ÇÈÈXo’€t7$# †@TŠŒü2èÇŠE> î[(:Ú û΄4•n |˜™ #²nJÊòÃH%D”XG¤ñÒ-"Ò’ ÙÙ|)ø³ÁŽÀ½·®DxýåÛ„Ý!0PÊu m!Â"·O¶‹’GÐv£&ÖÞٰ³Å`Êô½!t±'Í!$%0=¯7dŽ:µ…³J¢(CÌyÀ.q>a±&Ò£Q RÜ[uÙök÷®ÄÝ«—%^ý®qãK€~¦‚Ù‚&™ÈÝælÇî«¿=üÜ®3¿’˜"ôžÁÄÝ0¿ÜÅ{~r€ÿëØ[£|îPåÖô™ÊeV>jg”QY 3!ËD°þSÎ’€–×¥b|¡– Hƒy¯ù1¾É dÂÝ?Ifl¾í· Tb0‘´„GûÉR`dæ(#èB¢wW3´2é"-$“c2±Pž‰Èåu”›RÖj´ÁAã$vðš<`3c5)ïÀ »Û¤Ã”[½Jã´³9ÜÀÒ¹[—"SàøNÆÁ#Õy&–ùÙÛ¸ù¦³Êçp‘;RHXÞ»[m`¨Èå£Þº]{IŸrÉž:,ܹ3þÒkfÞ…L{dŸAÊ<|wVu€y=@"ŒrV6&€¥# Ÿü›»˜œa(9ÁGk7r¸‘»ÜUÌ’ºfÒMí:§ühEŽ…{ýëØ36Cäùü‘ 9xâÜ=–€©ãmžR²ŽÝ°32`ÄRKëZX8nÑóy©:Y4y,›="”¿Jñ,€"7É‘¦¾9Zxl>Ÿî¦þ¹¡¹ùDìÝ‘˜<” ž®×™¦ÃÝ«‘i1ŸÙë­žá‘æõ3é±"FL„#GS|âçz8°¨sU^SâÔ5ZíPó 2ÊÐ7ÞκÄôbõ5°¹K‡/$X{©åœ }ÖõÈ项îÓå×eó߈M¼€³ZÙ0b2JÄ#™Ò£()~”›a´võ\PSø B³öz%’ß•ZÈû_•–Üö¦ Q|ÐfˆP-­ãж¿lÓV`¼PñsMßhÑ]¯Ù¿F|.Ý97öà`¸•¼ßš–Eâr`l1RxúG*Ì¢ùÚ[;ü.  eíÙgWÖŠØËkØ(kÚ!Ð2 @â¾ «8“®¬_kf¼¼n÷¶ÄÚ‹mlÞØêv`g ç¤œùÎeΕCúÙÚE×i4‹òºÍ›Ò]_»¸…ÇöØßû’B3oVä $’úï¤F àÛ twMbêÈèXíµ[عaý†ÄîØ;Cv|®þ 6y™(©¾D}D ܺ%ñù_™Æ>£³DÖCÿàS#;S>HZXéâ=ïã7y2Ä%œw° ž œç¶uàðxkd6ó ¹çvÚ†¡Ê<ûïQlP:¤@VÑä²ë0)|ûóV‰¹Wо3’uCJ›œ¥‡è Àìq÷ f ë9¤¹“imމí|úëŸ8EÔ_G6$l¬ =2è@,7Þ{³¥s¸dÓàGŸáª(Ü:6oH:­™åbq“v–ÐÞ¾ˆ™å “¬?C š-ž«N•·Àfýšt.‡Îö5áêÞZ/Þ§«¥Î!Jõ³ÕH1¾0tRnëeÎÑpAùs;ýüÛA>pe¸™Me-çöAžú›°¼’áÓÿÉ¢¸:n8E¼{5˜w´_£¤ºîl…—3æI×™È8iôk‘¶¶L,|ª¬ÙžíuœSÖ¬(#€$öZKëÚ8õXŠ™ÅLéxóRUÑ (°ËäiÓ}0”×6+L#"v¿L3Ï6â&¦F37zNˆ¬q1ÝU ÆO³ˆB™nÖC[àÞjäÎ>6 ˜¦%ƒ¤eÇ‚½öE?¨ð©_ØÃäŒBÖš¯<—|'ÿ«?ÿTí9{ïz•±cZÉ4=¦«¯ÝO控˜9¨Ðwεùüáá 5áîF†xíÕ[ݼé-ɘ.¨æ`2W5ƒÖ¼£\4[ÇÀŠ„Pˆ„ÒÎj‘¡“)f—³ ˆsìëî5ÀÕW¥¶­5÷‡œ£dYö>·ò®;»]‰AJzÏ̆)¹}u|¯”Ìà´¾7T–ÿ7T3 Aíöå(ûäÍ#‘жÎL¹{¢•7A[^G‚Є™IÆGº‡(Ö‘ ýù°(z71¹ö%W”»w^1슱 Ö&º;mÞƒy}u_¬:I×eÉ–2¹ÏR™†æâñÑÙ0ÙÐJçYÏб R•RÀ˜cô·…kxµ$¡-v¤MÚ1áèCº@¶o"鎌t ‚òD»×±)ú8pv$7cdÌEC’6V±aôZbǘ›=g™×Ìî3°q=n´„.˜ºé°q]ƒ£ÌXÍÛº¤ ;nmûcÄ’Ð@+Ò×µ?ñ ŽÒ΢>küuØ_ÇäÚ—ÑÙ|É)B6ÞŒÑ]ƪRޤ¬ÝI×®„&,ų.™Xˆ§~¬ç\lä•3OT7glf×ë/Ř1ÛLK2é¼^+«2)ªe&&Ù‰ø÷<;‹ÝåìT>«HÎió ˜½„¼¹œœ`÷÷÷#…Ë™/ B1#løˆü& S™á)½F?ã¯`®BP ìŽÿú«BÁJJ?ßÒŸBç3S¼ké¿v+Ml˜ 4‚AòÕŠ,“ËPÈö.[]fÀ0 Ðiø í®©ÙôØ„+ ÓyN<+×T™á9nVÜ%ŠòtdÃ2 ²|NÄ‚g)rëbÛ1o,±Sös$°h€FÄPÈr‹Y74lO¿0›»#)ÿ¶Öª‘Ì; µˆÙ4rIä7¶ Ãõ¬ŠÇ œÛM¨=ÁˆežªN&_ÄÎI9iDgݾ¦Þº ðÚÓ¶”õ-¶õ`¬ë±ç¢ˆÜJVkY`”– S‚°vSâË¿>·¸‡ÅÓ &î¾€dê†3gjAS„ÁìCèl¾„‡Þ6Äó_i»{)3ßÖYËþ¹^†[ÃÍ›"ªÐÄpL®éž/¯O±÷‹¥šÙ|[íþÆ]é‚9‡†³lÝÐ -®yáíñ’€–´³3*Ÿ}aB«ƒJ;/é+`C;f½Ôå˜Øâ²*CfsÕØÎæU›úóZiqhÒ Ú„ÛîfàÞu•j9épîBéZô<†©µ/aþ\Š{¯EèÞÐóŽ€å'µÄ¥hYõ4˱~Mâ—"gì!ÉX|ƒ éÕ×Ð×58ÒL|~ß ³ÑÏ“¾xk׿Ôvÿ{å+ÂÕwoXiB.á´™|$Â{ÆJ>#“ à'~¦‡#'4R”ÛF½›N½y9­oµ±u-FÔf<ów¶À"Ù ³`ÔîíVµ0Êi¹8ÿµyC³w ©RnpœHÇhð& v×…‘¾³³º¦‘u†><$´Œó›2ôœéìŒÂ‰PÊ:®Áä›0isAd2Ñ<Ç:³f¬Cª]· aÿLKÔæf#›’–eTv>=cl¬Ú}d§úߘàiŸ±ÌôÍ‹Úx ¹º†ˆ3H±–¦S2˜ñ#Ÿìáð‰Ô¬ÃÇj×aw5ÂÚË-¬}· Œwý_6k÷·r†œ)͵—c<ö!‰ÙnåLçpî¢ÁæO'Xz|€µï¶L·qþ{#™X)8‘tqëJ„o|¥åX6)ôù›:C5&îÚéG3Z޼’áΦpìbäâôڊ̨Ì=ä<ˆMØ€ÐÎ*EAa $ÕqE‘~_:¸b> ´oC+½zE{`øPpƳ‚]á*-¸ËL]é½ч`˜ŒøNwÜPhGFPk]> ùLšµ ÞÀvqt|)acF­‹ÙˆTü g’ìûˆD8Øn)Y~³yî9©\ÊåËTå¢Cþ½uLj| +‘ƒ}®/øí6¹¤‚á;+•Fpm3N,ÅkÝõZR»Vµ ‰ Pš:T-ײWÞ‘@Ê ¾æÎ99°iBáF‚ÑŽ•+#+ó2ï!úuv&êçll>êùì†O+Pnø-·BÍg|d!cȸZ,‚‚%5@¸w—ð•_›Á“?ÖÃéwöï^…n ?÷hmÁ—N,ƒ·_Å‘)VŽeè¾¹C]©²4Éôzko”£†SsàQ®Õ æ$Ù]£Ø ÛÑíoKdf‰ht‰äò+´ð…ï%+|„Ò3þ°]KÆl:úf :2¦°¶ îÞЭb`÷¶ÀÔa52ÇÄÎDø…ÛÝËŠU8|Ëûl•줛¹ó*4Éðw;kÍOœwËö6 ×%Nehu/•äI,;ΜA«{ g?ÞÇícìÞ8x!ÅÜ Íù‰8E{ûUÀk/FÚÒÖÛ£3ÎÝç–«M2\ ¦µ{¾;Cœh?gÕOûNV6 ¼tV¢Y†°Á î¼ç ÁƦq–÷áØ‹“0Λ& Hš_c <ôHŠGŸÖ€ W(Éá†+J/ýÑÖ^j»óoæxê ³ÑŸ;•¦¹Šcô{w.„W£<—Ÿ Ï‘°‰ÄŽs)Û˜ì Ïýu\Nqëõ’ظ j Ó’„óï¢5ÅH;‹ù>mXË‘÷€‘Z a‚käÑÊÎ\H;´22…­´û¼‘iùó,³ Rn($]«^ìúénJ ºí™j×"ƒdçjÒ!áÆ+-W“À¸ÐQæ—€fŽìõˆÌ9· ðÐÓºáÑŸ{´$sµëðÊ'°úBÛ_³ÇÒ±à¶yg™4+ÛWŒ»W#{|¨÷žBsÀožýð¦3Ü{½…¥Ç˜?€E„áÜ…`ïim_¼òí½LÛcƒR1baÁRÃ=w¿JäuQKè¹WgGOŒTæà¬£&çE»‘t &(¡U(~'nèÜ ÌìÃIƬXØgDÈcÀö+­ËÍ\Ξ¸_Ü,âЭνf |7;‡¨`<’å+)'nöVËàÈ¿†ûeÜÆ]–æ„ *e‘à Y¹Ê£ÏOêÿ,£a¤Q¦³|‹ãÔ3À˜®ÙÕØH|гäì‰í‚Ž„g#Î@–±ÉÅh ±Ë„Húælv¦bTŠ¨Ã TÛî’Pˆmqmf£ØË;±T|+Ö¿oE@2F­”8©Y¤C-·q ¶³ ÷ü4‚õ±ÅÀì¢B;6CÚB¿N屇‘Ð îÀR5p³ZîtFÎoØ/žf÷ºóL äí·ÍÛñÙ1+Í!Ò IÌ‹fFˆ5RüÎOáÎm<üÌébòÎóèx¬vè5XA¼{'Îdxõõ–»™s¶ÓvÌ`¶l¨±‡õÔœ±pö¶Ef×ÞSsãÝ„î]k&"ª´IKëò@N{ß$^£!©H‘™ÿ h[p£`*Y]ôLϱ‡vvÀ"нE˜:ŒÚkëË~z÷{[T¾ˆ6•º@ŠMÁvðòâ­ k,†ÜëË àò×#,œÊï^E2±\º_†Óg´t¥{ ‡ŸJ€§ò½¤w0”á´7_©×_—xéÄtzHz¿MæVìüÑåØí›ÃÌ—¢YWÓ… „V¬ f !(oŠ@‚‰‹UäÇ:…*#ë”жŒÌééç48Μ)ÉÖ æÆŸt°öÝvpP<“”˜Êbó§¿-<·63å ”ñ×ͲìkW¤ËoqùSDí’ê>‹M)€ŽÔ®j™)%qÄ8h¢çãäuöƒÐU7Ù¤ÐÅ«5@è¢Trž‰Y×:Aîgµ’B?ÆL…á„3éÁݯ‚òÜÆ$clÝXzX7ZŠû¼•·eº×"c–¡=Àä‰fl(Жæ½f„ÌtiŸù`2ÖŒLñþdíÆ×:8ÒŸæÁ³ÆÅ²=?öTÌnŽÈî ßûbË'ˆ{«H'—ËY„Ëèèl¾„#O pä©AíÞc%¨—^‰ñÇØÖl³YûåŸA¦¸1ƒ»# ±f_9âõW#- Só¹s*Ò÷MÄg®¤ë —É`#‰È5²}ÖÄ—6Ÿšò™²BTŽ[”‡Óþ4œãòä†ÁæÉEPSØ—©‚EªÊGª´ ç‚쌪ÁL1Ù;`žxoÚ·˜³û’ØUûÜ{…B¡€P2oÞ{Î>{ë[ë[+³—oëŽô<+po],0.C&cžngiŸ?gíìþ{"l¾ë ·Åª´’Ï­¹n­mÙ–(l\q¹FvV§9j´•Ûݾ¬ððsugމdTœ ØíËÊÌîÙvëeËuc@â$—ÇÍZ›Õ9¶7Ø€_y\¬]%\>žáágk n½Üš=p ÉP¨r u‡‘·ˆkW¬ŸE>ZÁÆÂþ À¸–ŸÕPG™2ÒåYûÕ#®0¼­°v›ư1 á¹BŠ{$sc©îóuæ’$ÙßnvC™‚ËÊ6„YÙ®A3o„ »ëîA®E,À¡c¶^Øßzv²É*†72œûê¢X#æUÃY< .÷þámeddl0·)`;kÍT®Ù—¡Ô]±$'5µ¤dm2'딓¹F¥ Ñ%ÂÁ÷”XÞoò\AšcI½/'61NemÖ•bÓp³…5 ¦E9Se‘€»¦›M° ùÔL¨‰ÍlŠeŸ®¿™cïSegðôdùX³¬¼îÚÙÌ[ËWvÁÅÎg] rcS_(ö9`{©Z?Ó=‡ùh“u…‹ßê·ƒ ²•ŒyWƒ&ê;A3°v#ÙoôñèGF¬¾€½k»i.ìÃF¾ä³u±ŒÉ–ÃÁÞ“/y ÷™?\ÀFiÏ@»çè̼ïÌfâÝKp$ïÿÖƒŒ0È›ì¾ÒÖt`†ª-w¹Nl%Ü03eÎð…¸qÓ˜%âkj²ÖqCböGÌ”Xßv}B™;™ù¥æë)g½“©’E] ÆiÆ-˜‘|f³Í d›;0Zþß1ÀšÖ¬£»´K§HJH¼y‰¦|Üø/‰__P÷gcù€ràˆ×÷Zu4xFø{^¶ ‘v=5Ë!ÔudV1×dK3ù;Û1®7®jÒQ)ÏØwxÿB¦ STd†5*rËžä³­´]~çµ®£—c[×®QgáîÃ彌~ÞÌL5eJüÝÂÖ´a…+܇×< ;~©Ÿ™V°üö€Ò%ªgPèFV£Eï#SMfsSd?öt‰ý˜O~åÍo¾2[;pÇTžüÜÖWrûqcË>¢ö¼‹ƒ;´–Ç]à&„Yü¶j8´Lc"D—)«y¿ÂÖ¥ú¶ò –ššëì䌻NË}¼´ìlás¿Üó¼| œ)Íð®a:á$¦Ãg ¯e#J^û¾·w6¾¸\síVÔàgç7.RðùºÖ)ÐÌ \;£šY„¸4—ÌËJPÑ8‘iš?(ÖÉZa× àDœùJå=Œ-{×°pýù$Hª{;:ïW>¼„¾•·|ù?¸všÂ†™¦Üöòì×ĸyÅ„5[ÀÌvý™n§žãÃ;f ÅAêfK¦I½²É*öì¯qêõƒœ1¬È ´gŒIÞ4»ŠŒ½L\3{9—²3š™r˸°<þD…åí:` âusãdQ¤{ß5F9 GíÕŠÌž3/äösE„íÕÉç6¨.ÿ©HŠ„mú\3pÜ}Xw›½­M®ö?—ñ°Í>’Å|n#fé sDöµ 9Y;qMF* £p †Š`¥vìÙÀ»¦Û•3Œ±…±)'ë b€ùµKÖð¥Z›½Ø°Þs¯Ø|BßDe°š¿éD0¬œÎ\—±ã‘ª¶ë›öºÞ>—£†îl»Ÿ´‚l§SWx³lì8ñÅl¨Æ®ceçÞ£‹eŒ;~†œúäo-âõ7”¿F€¼î8Èuû̘Ö.äØz J*\ÞŽ‡k r`Ñæ¥•ÎÚÜ2?yŒ•aÒ\‡s-a@¯rñ*ÙZœØðb[p{ëyÕ€Çrfj~¶Ç*÷\äĨU¯“»¦ÚðRbv]A䆩F¦JääÊ$Â^#‰ wÊhÏ2q~8Á !dW¦ˆÌÑfr(sæ9ÞÑ_|×ßä"Ð[û‘ý7uüý[ýuçrŽ¥‡ÓrÕ߃ÞÚ)lÙ«‘Q†"3|ž1 9„_Y{U8Êöhžqe;òÃ5kÏ)6(™uæþn2¦ÎÂÝm”ª\ö‡·®š ·Òr>@Úú'‹p׮Ʒº™HÛ1®±¬‘eÒj»ñ1­¨élÛŽ3„ìfÇnýô{4®ïþÁ1>ÿ»„s/‚š«/õ5ùè:z·O îï w íÙ_ù9º8$YÚ{kb[ä\½·u}îýGKŒ÷} ¬Ïy¢&$v§Hut€¹"oåÍÂÈ i6ƒä@Çå7‹`(>´(N?'ò™ ¸ùs_2lß9+‡…ý5ón¤Öæ´, ÃUÂÂNfxH™*×P—Àµ7-@ªÆ‹ ´a~;·{Ïó¾)UÔÄqŸužÃ*Wr) hV¼ù…G>^bqIÓæàägìÝ:á ©/üAW^È0®„ьզ)kR«Ùɇ…^ÎPÙâ¼²E‡ã24šþ™Ÿ=#(‹š=‹Äó™Â,,hãžVFcR …±¼®t#ÿb6¹\Î<ÂçÍ8ù–²Œ-HŽ>Ñ-OrûÑ­sí£sï3 DæIP-X~Íš‚§tÛS»FÁú-j¦Y¬]6s;Z»Õ3››I˜wÏä`«öæÌnj><ÆÒ¾:ÈÜɇ—:Ï‹`o±òF£€`¯„èe@Q&V®]Ûf‚cg31£«²fŽ)WÀCûÓ2nwÿn\ÎìžÙ¬Y·&¯_ÊMÖ’9dòµHWX[ɰz]ù™ÊJ³W½äsfû4€Åšd„]‡¹§s^?Y´þmÏ3ΤåÐÔ½|íbn6–ÃßJœYoüÉz Œå}kXXùZgXü«û„gŽ>õÛ‹øÒ—z–„Iͨ-Ëéå20êÌe1m¦ˆ3k·u×&ºX†Î°uï{ªQ_VM³ÛJÓ2 Ûè˜æ,Þ½Gãü%¤¬´‘Ä“0ébjd­P  !y¾Ùµy Csˆ;…IO8F 7{çÀ"3à©—mx9u’2l»›Álš¹f_ÔnfJ¶…¬™Ž0°¤kÔ~d'’"º*OѼS׼ϼk†6¬Þ€ô@Q Vé(ç¼`)Ö‰½Ä€B–(òŸ¯¯œ~/ãÛ K§ç܃<Ø>ÄÖ½Œú² .k X%Ü—öìc<ñ½‡L÷ü•/g(,“”es2H×(Qì²w˜rÿ¶z 3»óª\îG4F7”yu;ãfyw\ªbr‹0¹efž&·½mÜX׿}‹{½¼éúhÁXe_$€'Þ«ñÐAÃ'­œW¸x:ÃOþê [ºXö¶´ýÛ'°ÿH‰Ë¯ <à”±—¾µˆmKldˆ|ãb Ýr‡i¯Ïl¨s×”)æî÷uB9&ý*y8û$ó''¸|¢ïÙ¢RlÜDÀö]5¶lÓ-7-:¼­pùtÑ5×ÈM°Vb03F3%Qà£“Ãøaö[II—vžmA©5Ù`VòŽBËî…é ˆß¾¬ŒÕµ˜ctCªzFL¬{í…föBé(ʆÀÌ‚H1r{¸É,åÙ@šÏ O£|ã"zwN™à øÊ.ðÂ×sŒì¬k†(kc›Yƒ…yØq?Ëy)o tm:Þ$¤lóu2=Ũ‰¼ÞÜÔñ- · PÕ¶Ÿ–-°Šýï­qþ[ÞÈÛß–QÒVZsìÝŒ÷ÿDs½Ž<[ãÊië¸äÂôÜ&ûÐ#•Ø4„LÎþºúÊÛ™ ­I°Ç\nÌ2Ô²óo•6Åç¾#¥a9b¦ÑÃK,䦠rß_‹Å¹ï¨ïïØ#zÖ8yöˆ­ËÜáž’)‰Ù$o«ÌÝÍ gµêå1" skGv‰»/·W”uì²Æhä•V/zÆÜ‡²£“ï¯Á)e28´“Û°×fëMìY s›1%WZX6ϵ¢6À2² Æ¡-±¸ÇHm¤Ì¥wçòK;“÷ëò)…?ÿtËWLkYÙùÍš›`ojÅ>R`&@²ûÆ­™0Ê1ybÄä»Ñzä²3°ƒâ³‚©Ý3±ûp……ŒQõ`Y# ,Œä¯{àVÛ¢jöòGRÎÜ…';“Ã5_ã?Gqè#æº8ã U®uæÏ¸Îpm€œôÏ›&LùìΞÿæ5Õ¸E:CÛïUŒ¹¼TñPÏR d Põ[öÖX¿jγG>>B±höQwoT¹ffµVvè©,loÀ¦±§„\¼jdvÎz][)ª¿op9HîÞ”5:ؽozà6£iŠh«yv÷!SÀõK Æ´\ØFË›¯X7GëæiÒ­y…æ©{‰d¢c¾=ëOºMýrë°ÜrL¹Ù¢Ïà%ñû+S¼“1Vɨ1pÅò£ŸXÇò¾ªµ÷ä£ônŸhöËÊ»_—Îäøôï-àÄF5cTF6€©™}«j3‡Hœg­VåæCÍuZ¿:]uQn9„bx»ŽTX˜klæÍÈÏx*`T‹[kü·ÿè¶mÓÐÙ>ø¾!þ›ÿýþ»ÿ~‹§˜Ù[Ûwv°g cÔÌBþ¿Åí³TMÝ×sYc¹9£`ë4¥ƒÌHŠ ð¢†y¦|6sOFNlX¤F”Õl%‚U3j7›äƒ¿©måÍ1ëC‘›]\ÕsØœiY¡‹¯‘à‰YCsos)Iß&ƒbp4÷×GìÍ|ïD‹oL07ÙÞéé.À¬].‚vüËI3¶>¢±óc =`±lé›?ûÀ®]Œ~¢öáÆîƒUއÓ,˜d°<Ý ¬&?X0ŒÖÿ¾y¹{Æ£îí€Î°°›±}?£_Ø»ÂüîÙù¨‡ÓÉiÕo¿I^–6²sO©°L Ñº/d,.ƒ°P0ûÀba¯WxèaÆs·6ïÛŸÅÈfIì: m×óÙ3°mO…å%^&@’¸§Õhú£#¥s ¥±F:üo.œé+äl],£¿¬qä¹ #/ÚR‹90°×ýà““$ã,wß|µçìKáÙµ¼]Á4R L±I©òKÊ =ý&¤§‚Î僧d¯„B×FFµzÅI3ïÌìÂ_zCG¥ÚÒ:2~™µ§ø™Œ%ö$®È˲éM ÷ùzKͰµÏöO`køõÿåÛ¶Ù@Þ=¶ ©Ä#سmJ5†bΔƛ(¨f&N ã'iÛ̬¡Ù#zVj×Wìg-{Ò +–ö̈ǯëž)ïIÙšì!ˆ(2Oã¶\Dq˜.ÄΑ K?·Ì¨ýÚ"”wóÀ‚î h¼ã Ò]úQÁ€šç.Îó_s»ûˈäk;…gPHÍ!X¶þmc%ÃäŽBo)± ³Mö¼§Æh5mǽ²öƳŸ¨±ý!# sá¥Îoy7cã¡·„ÎCÞm"«dåJ!ô]lmÎ]zã"áÀ”î|µ¸½µSØþ˜ÆøZ†,ºn {[ö°Ñ© ÊßiÕ7®n¯ï¤nX€”¯bŒg’׺îí@oyû>Xãâ×2»Q™Í¬¬ ý%ÆS¯‘寑Ê3w7#ï±ÄŽÝy{ÛK/ÿ’µK¹žÊt\¹‡…idöÁÌU"`uR1Þ=ÆÊËF(¥6ým5ztVyfîŠè«g œ|±ï»I™²a‰SÎ'ïœu)÷¦ì3HÓ€…ÁœÞžY0.{g°À1ð$te†›Q·V ã;„~Ç3–MVAºÂ­Ë„ ²î•F £]ÇH›û=kOálÔC,ìb”w™”ì—éPZrIŸ)ԳźelU‘õ€ñaq»ÆîgÍ:o}"È!rúþ3ŸéùXÞ!ÜZ%¬õId0ƒ¦Këžo%¢ t$‡Æ:¯ßRÍzÕæ9«m•Èíðû¬®yoàf%rÞ44ÊáΕnÙ gèlýå!v¬Pž3lVÙ3÷³XÔxöp‰=û+|ëx?ÿfŽZ‰¬wðS³™s|ÀO)ê³É*¶ì­pë\†Ã?4Âî'ͳæ/TP /NµXVʹvÙ™ñÞ0‡ÄîÂ©ÜØë׆½“y…› Üô… Æbb×qWÅdËaëg±ë‰YŸ=8’ÖÎîóë ¸üB{ß]Îfá` ôÌÌ~Ô7 ë+ÃrgŸ32!™}Eäš½}'£è›s¦•ÁeÏ…Ó™ Œw`Òd9éÍ“ÝM*ù÷WÞÌ ûPÆvg4ÿ¹"ܲ©ÓÁÖ²rƒí£›V¡¿Ü ÔReç£%nͱ÷]oµ=ÚöŒo.äã«(óöYícEúŒ|䲬HÐŒív/o&¼·vïyóK –E$T#`åR†›#àÖ¸QF˜†‰mÊØ=ˆ-u]û\šˆóaúæYݲ§Æí 9wwK«'Ëǰpýyúð7Nõpõ|æàò̃üµ_ÜÀSï*=Øgʽ<ï=ÏT8{±@©©Š«3´ T§9ö*‰Ì4ûÿž…z gî!¸r2=ÿ€Ÿóü—ÎU1Pòç•Ot ¡šñëb¬rl=PbÛ¡ nŸïa°­ÆÖC,ï+±~9Gï± ŠáÅdñ[n9Œ|´‚Á®5ø¡ W¾aã¦y ·`x®Æò^:ÊTMV1^'ܹ:{ÐgAŒC›tïÜg¯¥.tZ×/êȱ’œ)íá¹øp…åG4ÖÞTž`<ô.íT Q²]®ko&UÓÑ­o†× »«Nõhû³X¼öU,ª°Ÿj\þzfV<ü¤ÆáÕP™uÛ±úø|†õl5Ø‹|´‚Cïáú+ß™”Å˶ƒ†¡é²Ð>óFÑšëV #¿¦¬MòäkîÜRXÚ–N2¯öA¯Âòþ!ö½ˆKß\@Ï2•ÞóÓk^^çQ˜â ‘×¹ÐÖÜNhv…{¶X%˜±YÃT&)êÞ» pÏ”®¯c°&c²–ûöÙµ6ά­ÃU ܾFسÄFÊÚáBvý¼Â¤b›ÝD¨l@ ¶ÕZ¦€‘þípR¬{Œóä±·Ïd´åaŽW6®(ŒV÷ê–\Ò}ÆÁF–ËkìzªÆ²epÖ.(Ü>£ 2ìå÷ú9”s¬_V-ŠÍAÐ ×.”6n8¡yÎg±k7•é[pTŠÐYPQ;+Õ5ß²§‚"ÆŽÃ%¼Œ]6Cèò+=¼ô‡K¨'„¬ÇéY<+G~äƒ#\¿¸ƒ ( àŸØÀÓœ`ÙvßþçFøGÿý2¾òÍܬÍÚˆÆÖ–­¥»Æliš³ñß÷þ1¶¬°ÃJYÇ[Ÿl‚Q-ž*ˆë^SØ^{½[+èþvÑ-³†f=ƒ€)ÈK͘×-WZ;çÍ«g3ì=\·LÖBûsÿL°í‘ªu¯SàÔíóÉgÖí3«§ Œo©™ ì`«‘ =ñþ‡Þ5ÁØ›ô¯øþ§?ªÄ¬DÇÛuñÝž²g¿ÛG–:véÀemÊØ#m­[1®œÍñÐá´©’“éž}#ðFš1* ›§á V²–ݘ¹¦Ôpˆ#?:Ä«Ÿ2R®]O°q-Ãâî:-i·¦N{Ÿ ¿Uûu8Úþ¬ÿÚbýl·TÒ6T·.Q½Þóª|À¨ÇÀÎG*ÏF…îŸæs_{µg³—dÉLaÞ#üÙéœGÝ¿ˆ• $eŒûÛ8öns¦;°ï€þdBxõµÌg—‘bËV´ £Bk\3Å–E²îuÖð€0ûùöûãîÚÏg>Òƒ¼]aÝsds $'ÕÌìApæ2$©ùß™GÊ€Áššî&wfŠæâ.Ol.Ba°ªœAâDq;h^Š…c=Þü.xßUIJy|WÔöÕœÉÉtlßh.)frkUaß‘t—É$Ø÷ÖN቟ºë9–÷µ;hùÆ¥$@bÊ1Üñ®?҇8òW*ŒVÍ–³¸»18”·³Ò¼}…ü\Á´ËẲk×)è*Ë?]á D'h¸ndv»1²ÑJ;DO$lï~ŸFÖnT,1¶=¥±°‹úßU±v•pñ5å—­²ý› »ùðb q6Àpç°pãy,¬pdg…rƒ@90Øn®W¹°ß'{W3eÕÂ>èõ³Øºo ïþ›·púó˘Üiúm‡&ØýäØ¿¶ž®x}ý„É›™Ô²“ÖNf ‚¡³ËÚh×ËÌHÆ•1O¾wÜ ¤'[ŸÀ`õ<òÑu#ûæú‹OüÈ:¶9™…H3wRˆÕ• þ™Eoï­l÷ÉíaÛöVIâ¬ÑBËû„sVóσ£È Å9 Ö‰¡dï>u…Ðiúìš­LÑtíaÏó™ã5ãîÏù“ cÝ„C;g·ÌÎ9äÚü¬ir£jq?ŠõsXÚ?ıŸ2‡k»ûD5Æ· õˆ¦U®áñŸŸ ØÒÌ0X>0D¹FÉBÏ–;uëâ6ÝÀ0&ÀÛÍÊyYÕ/s\±9Û{A>oH‘‘ÓÌ”V¨ðCÿÕMd½pÖâá§'¸ðâw®fØv°JÎâM¶F>¼ˆ=ONðÑ¿Wcã–ÂŽC•/>\(poí~þ§Gøú KFʣ؆1›˜íªÅYrñLŽýG:ÜF{P ö ‡ôŽ”ÑŽçü×W¾Ãž*ò¨ÞùX‰ë¯÷°°µÆcŸbÇÑ7Nxõ3‹Ípþ¶R³—r¹³É_±ž»:Ô:[@Öâ=¿¾ŽÞR¸ö8ê \o}uo¨¢ZÜzª\óìÑ…¯›ÜÉšBoY·š=’ üɸ†¼×ȉKüÀ‡JØ×Çɳª™«B¿™Ð%?Oìö‘iR£Š›5ìi"ǤšÝðê…‡#¹þÜ>òÊwzØ( à ›ü7nL@Œ¼&kkÝýk²í äã«Øq´Ä»þöd=’»™i±Œra?ŠáEoÊ0Þú¤?“ƒu˜ö¹u¸ç™ ®¿ÞÃâVc?nÖáêéÂÏ—Åû§»¦Nþ'g;\ðkæ‹x#דN¥ì÷gCOAÓíÊ…)9€vßÛv¸ÂÁß ÞÓò¾5¬]ʱ¼omêºÍ&«XÞ·†÷üÛXyµ‡Ñ°ãp…¥]µ¿†î3öìõûÆ_¸|EyS›Ði­~D‘äÓæ¹†•R„Á=—Î1|ž*ñú‹ E¨23¿¥íž›mʈ°ïñÉTå…¼~‹;4¶lcÜ‘0boûmæûÌëº$–¡±>ƒ2»ôÈY$î’ÕQ[~ç—•]|—!±ÎT"ú™¾Í÷]$ñ»®Å<Â&$vNØ[KiY”Qã†à݆zóÆôÐ×ÉÒ1¨r 9V<8*ö£îïÀàæq¬_ɰå¡!òᥤYg ÷|ØÛô.ìn°rËá`ŒVMV±q‹pê«&áÒŽó0H5§ Z{›ë3 õÖN%ß»LØÞñ¬Æ¶'µ)Â2›²mÍÜûèß~ÍtOÆÂIi3‡qåu…ÝOhXI²)0îüú·O Ç*òÅæ , Þg±~ªâÖ… ÛԊѶg°pãyì82ÁÒßZÅÕWÝʰí`Ž&ËÇZœ¤+œ|¥‡·•Т ‰;DmŽOm%Œ.¼nT£xáÖÏúÛø€mƒ›ÇñÈG×qð6¼d‹UŽÑ¶g‚ïéß|Ù·£[ç9vÏÝïY úê¥<×ɽÍ>ÄqÃãÀÑév°Ï|e¯«”Lyö¼.½©ðôGÛ÷Ö;ø­®^"Ï •"°Ö1(ÕM¦“­O wûúÛ‡Á3šWÑߺŠÑ”Âhé«/ ØÂþûÊ-‡‘V0¸yƒiÇÇz°¬r,¨pð‡'Xy!ÇxÍ\7¿TØ<´B«ä~Þœ ªvvБC#¦Ï®8™å•ó™™Õp,’n€ê<׎)÷Râ¬g%·‹û|‡··v ÛV¸}5ïHœ 0Þö,zwNaiÏš/:êÞL–Mx®k†<ût…œ¥bcß®\Am£¬Ã]míÁW¯eؤꔙ·?‹Ê2‘õ`OÈÒÞ:áQªÈ«{Ñ»s »ŸœÆqo…Æ{çÑ!Þû‹wfîÛ£¡5°GŸ„Ʀ¹PÓŸI×`é-±uÞÛÉò1dãU V_ðgKWÁÕåŠæ2·®½ÒóìÑxÐ[6çeíß“åcX¹³Ü¯û7_F1¼ˆ§ž¨qúœ²’Ë~FÒ:±>f6ÅöÁ).„nO8}2óÁÖ¥¶ 4™nyn7¨3's¼û#“–¤ÜÉt/žÉqí&°Qš½{\¹{a¤O° °Ó¯gSåzL9&KÇлsÊ ëlÁš \J®¬Ü^ÙZ‡k§ü:Lå9U‹ûÑ»s ;Ž–xü¯­ûuÈ*ÇŽ£%¶JgèÕ½fïÙ_áéŸ[ÇéÏ/`tÓÈìzÛßÎ}L.YT™5Á Ø\¶ ÂÝ¿f~nVãryßZëYßråóXÞWùæs×ºÝØýaoA¾ç©Ið¹Æ[Ÿð{­”~úÓo€ÂZÖVÞÞ[‚"Ç‘Gïû«Ã©Ï<ú·Oàñáö:aãDaæ\Ô03•ÙUãȇÆ3_—³ªÁô±‚üôÖ~7Î+ãVgß§t–…–x†š†¦.Òæ; väÉèÐÅ.E>nGq‡UÚ"oFb×rÊû.t±›[Vg‹*çäSÖóÉÊnß!ŽÜŒHKfgïi ¯Ó Pºt>ŸÚY€ÑŽçš¼)oX;…- ý&ol³ýL¶=áNüÐS=Bÿ–a®œ Lì¼Û¢»óZØŽÒ[ðr%ÉrhíÜYš‚ªR†å8{\aÿŒmS^µ°#ÊÑ»s k –[€Â×ÏÎW ›Ýhª ¨+àöe®£ŒÁÍãîú@gWz¸ëÞÉ‹)o–ÙdÕ³G—^Ê¡rÆòC:)ßÓÅ26ö~ ý›ÇQ`>¸Ñ:Ø%P•»/nÁ³GH²zlwoèœÔæ@ ôêËμÚѧ&(ÖÏ&Y$y3{«ÁT ûƒÏÞ¿}ªâ™úG‹¾™ Èιë3‡e‹ÏŸt¬›6¿DÔÞϤã›Ù¤»°qÅùÕK*Ȫpc#™0ør>Íüx÷º „QÙ™•—*Óc°=fînç~îìJ:FÁý,ß}Ä)d“U+±«; ÷`}ïÇüáÕ05åL`æXÓmG*,š`í¬ÂêÖ/)ô2ÃFösár&nNe%‰ö™Ç\¡b4¶Æ6ø‘­ [yR®Œoy»N66&KÇ ûªòäl–ê1®Ÿ+pèýSfŵWõ¨•IF•Ùg¯®(ÛáµÇÎR\jÅþ•š0ÑÀ™Sžý ™WI6°(Oþ}6Yµì‰±žÏ°Úž=Ë ËþTÓõí|ƒ/`q×êLo¸A@uÏ™3œÎN¹[F¼ëÐUÐ4ý²Ñ °õ‰¹Ïæbý¬w®{ã‹èzvôFµ°ù8ç…ˆÍ3°1l2f¤3$ '-m%rµ=·™ÿ4*›ý¹ò{²µg¶×ớΰâNR¦{êõ̰Gc\5lÁ„Å2 ƒ2,ɈÐpg®’oŽLVƒ³K•w:××:Tåš?‹ê !Kä9jhÊü´”ƒÞpç0Xý¶â}Ï0N+/÷pýõú9cP ×<¡`"³æ¬­%6šÙ^ÍÀÆÆtYóÆî·öbóÙ *èŠP /¢ZÜ×yÿÇ[ŸÀÄ^kÒ¥1˜Šj§ÊùúW{8u*‘'fïÐ>ÈÊî,‹¤ØPüâ/ñìÓŽ>VìPê<×C>ZÁ–]«ø‘_½ƒ±éLŽ/|r?úsØw¤jÕ,ÎÚ½ë×xû³È®~û/ñ+ÿMi›9Ž¿’ã_þÖÀgÐIwOS· ÇÏ.R˜/06ÜHE'ÙIˆ¥”Žé3æ¶±cŽX'ÚþwZb7{îuî—Bç%W`:vstÀÏžÍý×{€¤Û2;÷3*ÝfÎË’gjk%;KFRv>ÇòÁ!zk§0žr¥^Ãm «/€t…«§o¾h6s]›îäe)×1_wnS /âȸB¥jkÇYe¦ ¼x‚°í!FÿökÏ/n£‹¿&^òRŒWÿLaR…ƒ÷.LQ3ðêŸexÿÎ ÛV;A™,@êc ×½ðB†ëçƒm–ÒèÝ>‘–ï‘‘Ï8yU#p>@¹°¿Uðõoé ¯¿ÜÃK/÷0¤q‹ ïÙ^àšŒ”Ð}ݨ2Ùà øê<@ªú{’÷r®ëì:aŸÜ‚‘]÷ÎÎV‹àE=Ç€þê å‹–ÑI*0–gHemqº}§ÆÖíÆZµå`W@ºÂúmÂúºÅã'VR"ÆX9OØs06~>ì5…q /m”FJÐfpãŠÂÎýug7–žÈŽ$ ¬Åq×Ö|xÉwá§Õb»½µS(†±í¨Æ¶££UÂÆU…ν¦ìl¡ÍsÝìyFf麃4ÝbÚèwÖ £Š1ªL!3j ô¼nÞ0)Ť>» —¼üfçr¼×uì§vêb¹uýóá%_ä¼úZfe4ȸvf°r4“ÏRÖ¦Ð=ùšÐÏÇW1á'fÚæz6ܲ´—¾ÙÇâž;Ž–I)rµ°ë½ÈGWƒâl–³k\<ŸaT1Æ–M­íCU[›tôÈ8w:Ãþ#u'¸gÊ,O®½;—2,ív~ojíÝ1ͧ7þ¬µû½üæ• ËSž¡øÞõo¾Œ|´‚[·¯È’Æ/nï`em—-ûð¤5„˜Æ; €olï+ígÿ͸pYá֪¶¡á‹s¡}á[=¬—°{=y  k“í^¯ÈçOç8öt95W)õ¾êdÚú×áà–Y‡—_èûù¤8¯oÚ:Líû­f¤U¸8iàŽ£%ÆkC¬]Èqᵯ¿Xxswë”òšÌšµ7ÔȺ¬”ŒKç³™Íç-\ªâÆéw®f8üáú·OcI6¥£ŽèÝ1Í­+—3üæo,@[fÝ5ŽkY/ Ëe­¨Ýìç¯üí~æoŒZçõdéØL§9í|ÅúYä—|ͶïHÕG¬rTý½˜l›½W1åþÜÈÇWAÚ€·£U×Àÿï7¾©áYZëèCò—€Ãé\ÇÓånq.Q&‹ˆõ ²—¸m5>õ¡_°O³¿ÿ»KbIÁ$p)ç¼V£ÊvŽu˜š¬]p÷³\BöÄ·\¾šáüé΀P=Âàæqd“UL6o~£‡w4¬C],O-ú“àèúóPån]%¼ò¥ UuësÌ6vxêÙZt¢xyåÏTcE…Vªy_ç^$<ñ1Êt¾4×\û[/ƒt…•7N~=C]o|3ÇöG4¶ïb°ú‚w®ImÈÓî±;ÐïÜVøÝßY6C»ö·GòKvÏ­á\7ÉÙv¹À«ßîã©÷Ž1¸õòô ?ñ÷ÙdÕKÿðß-áÔéeí2Sœ‹V3›²e{Ú™O‚êÕëY°Nbƒ† ˜áé -O½{2³°¹ô&ykòªÏ+¹ð<ãh.žm$×ù=wZalÁQ̰8MÙõ~ùá±÷uÿ&7Õá%Ýo_6 R6^–¦?Ó½µS \úZŽb cqo÷,gÃ2/C1¼ˆ|ã;†쨱óɇ?DX½¤páu…S¯(›¾n¦–“í2cïaÝ)stEË›g3ŒjW`2*3Å Bã—ãôɇŽUS¥Z ¸8îïÏçg o¾\ 'Â¥“ö=–]¿zwNy¦øOÿd€ú/…ýµ-vì¢Q TšQjŽÆpþ’ÂÉW <ötÙÉÞ¶÷€ãPõ7Ï8û•ö¾k‚GËN•€3;k&­`tGa°4]ÛYêfo©4Ù3ˆ³ŒL.œ]l¦®ÙÚÇ`+ci_ÞÚ©NÖ>.ÊIW¸ðJ“/ÆÌˆMgýæ …Ch2´æ9#Ëøÿ[pó–eÂHHyEжg ­mô‘ÇêN©‘ÛGÞxݬßQ”•É—©´yQ¥¹S®sàÂ9…m;´7|q …›7^;™aX3Æ¥© °jž3 Xzãu ¦„¶¦9:g5R¥*C•fçôç°óq^Šõs­y±®uèXP•sgf£W¸ðÈ7.¢^By ý§&ØýÔOþ˜ÂÕ7s¼q¼À‹ß*¼ƒ[¯2’»²ö– È”5ȰõÈÊ¥ {ö¥Í)ZçÛÍ—¡ê!V/äxþ?-b¼FØz°ÂökèÝ:Ï»w»çà?üöîlîÐ8îÕ, wS°¤¬€§Ÿ²Œï®l*tZ^ãÉR¨TYºôYŽîìû‰»«£í¹áœ³É*®?§Ÿ¬ g*ÉþHš‡:‘IØ-M b™8Í&ÿ±‰ X™Ó$%zs|ÿ•ƒ4 AcAÀ¦í¼:VΤÕ˜âU ÏW:ì KKo)ÃsÃó£–À)k:«ÛKµéfl¹úed“Uܹ‘á¿·W.d8ñ…¾§sg9«ÉMA‚£ï|6ÃhdнILJÛ׳ـµÄÎÿ®íl—p¶óCg&P/}!Ãí«ä™™®yê vàèøÞxQ™Â¤´¿«ð÷¨FÊ®¾A ]aáÆózîÖ†7ZÁⵯšëu…ðÏd•æKàÛŸía¼NÈG+X¸þ|çÜAga°ú‚¡ôKÂ?ÿÇÛp骲¯ÍWæw)†©k™~Ø¿sëx\›5º>1ÚößùW˸ôfU®™÷7çu.ÖÏš¯×^úf_ùò%a©GØÒ3¹e½Œ‡X6÷òܩƑ®³¡rû„gŽ.¾^àOg .­nŽ/ÿÞÔVj·™3SÖ&¿û›‹xñxa3œ¬Ä1¶bž˜½ÂŒ½Iˆ›…cÆwկШ,N¹‡`ŽÐHÜä‡ t¤>–© Ž|¶QœUDí×–@Š6óɨ…Ûff*Ü_é­ií8bu\Q9¶Ce7n7ÚUWŒdåšÑ§[iÂÉ“9†ncŒæ4§]ì|ñj_‡¥‘~ýþ'qìñ YCÿæË­ý|´‚l´âµ¼pæxñépmº&Ãoç(µÆ³?Vbpó8ªÑÕÀ.¶ÕaÚà[Wßþl†;·B‰ »çk7í÷U#äÃKÁµ ]áÚEÂê òÝx鲿æJ<‹jí“!m™o/~Aá~ºF+Pן÷îF]qoí”?h^²àȩ޺Y°JuÖ8~ó3Þ‡=jåÉò±¤A܀˯+|çsƘi~æK Ÿþ×[ðÑŸb×5l¹úe?/ÕÕ½róFΔáÎm…þ·áÔÙëÆF1H: ŒÕ‚Á#{°+2‡«csœkðoÿç­ø¥¿wûYÃÂÊ×0ÞþÌÜ×ùKŸYÄ~j1h H@Jmu÷S OX¹˜¦µž "H‹1sdŸ«23@EÊû$øus 7V/|=ó`ÆÏÉY§E"ó'³‘,^¼Æúîñ+ÊvŽC v/!!· J९gøÑŸ«Ð»s exWæ9߸èÆú*áì νl.òK„}cÙÑÌG+þóÀ« 羞c|Ãd[¬_VØXQXÜ3ôfóHït±ŒrËaP=B>ºŠbývìbÇ~×8ób†—¾š#S„ZH?53n­ª`ßPõÙxÕwί\Vxñe“ý2,]У9N•*"Ëb0NœÌpæG5Ëls×ÌÝÀéW üñXÄÍUå÷aEÀ7¾Òá§&xäÉ ןÇpÇsI ¦“: Õ|jßüNŽÒ©Z§:fhLIÖ «­Ó–uu|áÅ¿ÿÛ‹øÙ¿³ÁÍãë²Õ§z„…ÕŒt¬"üéo,áÆ•Ì|ñ“ ø‘¿ÅØsÈ47&KÇ‚µ?«_ü½E\9Ÿá]A?Zªì¾]Þñ×êåW3ÿ~ðç‡èß>VE‹IPå·^†*×°r>ÃgÿÝnŽ £X[ëf°vJáÒ›ö=®ãÔ:xé›þÝo pý¶Ü¥Ësg¿&_¾ùlëè—ccAडΜç•ã9^x17€ÞÆ*¸ 4W„9À*N¼²pî5¿þë•Ch×¶0&0J"ôlc\pö¢Âÿá?õóCôo‡ÎBktù¬z¶þ|Ž7Ÿï£è3žýkÓס;ëêŠðÇÿt7®d^ÆöÅO.âGÿÖ:v‹u(çõœ<Ò·7>»ˆ«/õ°óQÇ>ÍÇ¢:6*Þ{öbïá Op‚ãßèáOÿÓÀÎÐÙçÒÃ&¸—ñͯöðþŽ‘/¢\Ø' /öåÏn€?ÿô‚e® ë±vAá÷þÉ2þÊ/¯cÛÓ€‘N“i¥Å ÿão-â‹_ìa$Ã]¨­Ûï˜Ìs®Mžš”ÛiÅÈñWr<û´©Uîå¯ó§sV.9ß}\1z¹F?×xü½žüH…þbó:î ¥j„Ì’ +àÌw^ùªjäA‰"oy;ã¯üZ-%æþ×µ „¯}Faå ù®H%ì¨]gÄþÌ¥›çöúç@¿0Cà[·ïûÉÛö6ïÝ9Ýø÷oJ`ý&áø—.1™G“ºa¼b‰²IêEfvn~æÓ?¨qô} Eæ,}ݵ—ÅÄú*áì+ ¯}Ã^/{ –µB]4+€ŠðÃ?=Æ‘§Kdyã‚ÇÙÀË\R·,à_¹‡ßý·Ë¸xÕtÝ fÞ$ &5¾o×ôBn:ü[zf½-Àþ½?ÿËwpÌÔêlÁ\g›Ã_çëW3|ö?-âÑ7ÝæºÎ媟7?oK<õt‰Ÿÿ·P÷v²Çp\8]àŸÿOÛp{lÂoOà»ÄxÙUl àŒrÕ|Ξû¬°T0þ?»_þ¥´+ä5¿ûg=\¾d]»üåÆ Æ¶:#.™+à×þ·<ÂÍZÔ~ã¸p–ZUò½’jžýË´ü•_¬pìíe«:_ö Á¿Ï„Ko(¼öÕÌK`iãÇ­D–7kJ®¡õUÂµÓ gŸÏ›õnÿÜzXãàÛŒ“ÏÍ-Ï™œ¯œVxþÓ=Ü\u…¢‘-mgüÊÿaŒ¼h¿Æ©“9þç=ÀÉ3¦À•fßdæ dö³®O?^áÿünûëÆÙB°?ž;ãÌÉŸýà œ9ØÇrìÞ¥ñ‹wûi¾êÁP=„²Î„n¯}þ«=ü»ßZÄ’°>1LìzŶ1FÞ–œ£ç¯Ÿ™ç`!–zæ™[êÿø?÷w6게|(ôÙŸÿ½\»œY†ª9È·íÐøë¿²}"cÈ}¿ûuãj†?ûô¯¾P Ò„_ÿ¯×pðHû{ãdŽO}²o}'7ŸÃ7³œÍ0ûuÓÏ͹s«ÆýbÏCÚ¿yýWWN¿œãëŸíÙõêÜáŒaÁ3¬ðCvŽBžKª\óõ•³þè·pý†jä›6WŠ`,„ßõÞ ¿ðw‡„WåÑ:Èð­¯õðå/UÀzi~oL€¡e…]÷hl¤ kFböN¯þÒ?û7ÆÉgàÊ%…ÿñØ‚‹+ª±¨¯å` '²¶ÇEFÀÿóÿ¾†ûµ·^¾y“ðüï¶âÚj#¿/µ3‹hÖn/#ôsÆbNX*K}Âÿ곎ç>8 ö×´t¿VÎæ8õíN~³çïÇcïãC£½åyts%çkÑ®C™EFؾ£Æ_ÿÕ <|8\‡òg×ÞüÊ×-k»íp…g~Á4˜FÛŸÝÔ@¼÷ëgýz9{¢Àù÷ X¹¦ C#ç$aBVåïoàÝhd×ñ^ûÆ+¾ô_86³Êwï\íFع«Æ_ù…!yªLž™q­pþLŽOÿÁßy©0ûFÉf Z#ŽQmj†kÝÌÎ8W¹žjêÓÅãý«#¼ûÙ O>QÝóæ9>õ;‹ø›¿´áUoõ×k'r¼x<Ç?ý7 Ø(¹ih3Ÿg\^-kqK¾BçH&ÇÑ|RËš;6lHƒÈ.\ÎZbÛ-P~×^êáµ—úX½® Tíž<Ó(£È5…F?glݦñ¾WعŸ±°Ü¾%ÕX½DxéË ·oPà<ç:úq¨ñ¶Œ‡3öd¬^%¬^%\¿B CÖ¨Šdv1ØrŽ^Î>ؤ^Þ€¦g~Pc×Aö@)þuó2áúE«_S¼ÉwòëÅš ÷³{(=üãØ{4¶ïãd17ºc‚m¿õ'†#jæM¬ëÖ¤RF¿¯•õ¹&+ìØ¥ñþNpàH…ݧ7 ñˆpöT/}~ÇF²W•¾¸µeg×Âì©é\÷íµ• Éý¹X‹9ðS?»£•8x´ìè.8}²À§ÿÓ"ÖÇæ=y¶”âp›×\´Åà'þú>ü‰–CŸ›OøÚŸ.âþ`·ÇÀšIcàÎbX¹a–ä³ìžßž(þÐÏÙJ¾4~áo ñÌS55\àø›§¾úÅ ÃuÃpx£ ¤Ú¬Ó&}|ß~Æ_ÿù˜wóò ÿñßäþ¹©t[(Á\Ïí-…ùï~´Æ‘'5öl>]9®W8ÿºÂé—ê:ʲgÃŽ=À‡~²Äñ½×.(\>¥pæ…,hÈboSìzºÆCï·2ÇèÞlöW>ZAÿ–‘®^RøóOX]U¦[jÆ-Û ûh8RãÚ5…“¯ç¸p‰pþ²aà‡ÂXÆ•UZ7|³?3úùuüÔãþæßâY;@?‘qNz¡‡¯~¥ç w4»ÐȦ9“Û!÷åEÆÏýòŽý‡ ¸pÙwn”쥪æy$/w­¢ý̓:»&ݳ¶Ø3~øC|è#y¬BÀÁÏ=w*ÇýûŒFʇûº‰«ßþøO½§ÄácÍžröTŽ3's|î¶ø'û„ù‰Ž>ZáÜÙ gÏå8sFáÒ²N—UÂrØ›ÀÕÜ~Ž^ÆX(Û—4þö¯LpôÑ Ûv°Ý'2¼~<Ç7¾TXVƽ–Ubû<›ç>\âÙ”xøpè„´¾F8ñbŽÿüf/ŽÚ†.“µsΈñ¾Tø¡Oðȱگƒ7Oexù;¾úåÂÏVì^êöÕ±•¯Uu3ã¦,ðÊsSôì^ÒSÀ¯þÒO?Yùaù …×OdøÏŸîa41û±s_4Å.l6–ÙH5ë"S„ÇŽÔøßýƒž~²Â+¯åø'ÿlo¼i@VUJf“±åŸyÛ,Èš¦×¢ØÇíׇxô± h®çí› Nçxã%3ÃåïƒßC¿§ÄÓ˜`ÿÑ ½>ß{Þ®ÃáPÙ9™æZÉŸ¥?ôã#}¼Â¡£zv-¯^Épý\Ž7¾¼UšÚ*Wæßö¾k‚G³rËá¹æ æ™ºr6Çüæ"®^S¶qa€‡[ÍŠ¿ðKC<úd…½›ëtsUáÌÉϵ‡×_-ìñ3#<òhåc$â_çNç8ýzŽ?üäʰ1alTd÷ÂFåæÌÈžåö~ÛÊjW›£¯Ì¹±r¶k€0(Ì÷3BO™5’QSk5Qk“ÕUqòÕòžÚÕgLY€–!Ë\²uë´,kÅV†[sóœ•ÍØÈ°"/353úlxòx#EܶînžTÞQlÌ þ>`¨€‰CÂ…™çÆEáü“ ÄD€ÄI`7@r’«œ\@¼ß¾,, û`(ñºÂÌ¡M\g¿ÔMwŠuS¸YØùäfá÷íº`S‘9T\¾ =§éˆÕöá5É1Hƒ\cP0ú£—iä™)þ÷4ùA··¯VWÈ»ÂÈp\–á£;áòO©1•@HÎI°åa7VHŠASž;ö2v`¦ …5àúÂÚ*5²*—e“÷I“A—îõ‹¼ù¹¹‹ÀžýŒåݦ‹zóaõ pç¦aÇ&þg‘b•&Œ+…²&Ô¬ µñF'·¥° °c§Æ®Ý5Ûù¶áPáÂùo¼nºœã’=àvC¿$xà]‡³6ò>‘I¹eËï ÖÒu¶yþ<üP£•ØiÃBoÜP8}²À•«Y˜©dßdJz¢c¾(XªŸø©4@rîbŸûÃ-øÜgpkd€ÑÚİHkˆMf¤L4 ›™±˜ …y¶ô€^®Ñ˹bÛÉf+c`arÁ¡gŽ—%²¿žÊ&˜÷íüáz¸Î»ÌY$[í›bÙ²Øý°Æú-ÂÚ- ç$‘ ÍurŒ­;[·+ˆCšA䇨‚½ùáÛŽ˜{\Ú¬š”wSû¶˜Y½¤ð™ÝÇFÅZ01©F~¶Â!Ñ‘• MªæðŒ …^FèÛ¢u›ÿÞºÄØ»»ÆëT‡ukÝH©Ì} d`ä™ DÌíõ?üHc—X½žáƵ çÏ*”âðGŠf ß0^Ž!7k3P äò™cÏPä 8z´Æ±Ç*œ~#Çù3™Èo‰ƒÇÉÛþ’`ã•"<öx‰7^/Låd,Ä~43R j¤ºir”Ž=⦛KlC)¡  ÈÙ7ûÙõË8tHãÊÅÌ–MMñÂaÒc³ï{»_àÑ'k¶EæÅ NŸÌp{ÃÕ«Êžáf~rhgûÜüب4,ÊÄjçV(îQF {‰Ù÷…iV ,³ÛË wg~¸e›Ïå›%Ö ¢ ç¼Æ:TÓ€Á›Fûy_²ŽnÎÖî—µÛçÍOì}¨ì:¯­œ°}V&æ‰:QGL“˜e¢h ÑœQËb“)9m€ÖÚ¯1@"’Ý]ò@È=®ÐpòL0x¡]w­Å&é˜8©Z#±#ßE²?ñI…ùS¬…MŒ6oö€“Š)ªíƒÛ0H½ÌHì Æ Çè)íÑf << sìÑ MGÀÂwÓÅõ¥¨hó96Qx©y0œ§™j³:¨YS Lü<ÉV9iƒH,äg2x-þ™î¿ÝßËñ>ÍÉD%äeeå„v£—ÊZ+Ôµ2+{«Í%Öšý0¿[C#ÁL6še™}ïê³dJXšç*(¹"­/ õL5÷Ù99¹\%i6àš®øïeBÆg/õ€ŸùÅu¼÷‡†o}"˜wY¸nL1~ûÿ» /½\Y×Ý™4’B÷|Åɯ![@»ç´ç Qr Wh{¨±Møf(;`ÔÔr°O8i…+Hå l‡Në¶ K0˜ÊâuÄ&%oy&³ƒÚ{¯î°€s¶óˆÜ.fòA”®P•ùFrè\JîºÕn]^æ ÐYà Ljסácˆn¶x0\˜£ìf—YÓéÉ2ö‡’»­.ÔÓ˜Z·í•)•ñÅãh±KPçÚHŽuèòäQ)JÜAJÞ/–¹UÒ-¯ƒ¹rßçŠÐÀõM›n^¥›Î˜44Ñl;÷úN>)hJm‹¼ZJ߸qJ6…pÓ‰‰X’ IºÖÕQÆT$ƒÙõ ;4î·Ë\ð6ë™±!î©FJà¤hîúUºé”Nt8¤¬U®¢4{²Ç&‘ÇæÎëæuÜÍ¡¡ÉtsöÝ¿Ú%t+cþ‘Éuî¯ÉØ»9Ë"fFÑH»îo“¢]7ÜÕ‚q§÷„Íà>ÀÖ$¤VæÚfµHO4§Ü°·;ÔÈDg/Sæ­µÙ¼\B0©}¼GÃá@·´d.ÖÏ‚UnÒÞ¹ ù®?ƒOÔøÖr»w_G>ûÍvSGµ‘=•µ“<Éý±é¤6û-ùg «9ìîƒ!9Ü‹Ä<“± 7…cF@^7ÒÉLÜwWLTVúRÕÀIJ,“ÊýwÜ+‘gâ烩#ÞM'µÈ…²Å²ø%’{)ùÎn¸ÉSöór!°6ÙL #ÅbÚ Tͨ}©3F1î$Êv‹ÍscÀŠÎL1W1¡¨ pRÔä›8D`fù(:e•m60B"LØg˜ÙBµ´ÕȤɿ÷¦Á¾85€ÃåÅps>ëÐ1vb›-‡ì‘¶²v›"/ÍÒö:fŠQ ´Ín!SHWölpÆuݼO- fˆT7÷NÙ³r½Õ®ñÚ°zóS'x)˜y*2ò ÅÌ…r:p¤v'ýü»Øð,_ÂB’ꮡ/†ÅŠv×ÝéŒ*·väöó aL)*M8ö±!ö<9«¼enлsÊ›$b¸ì5ó=s0LL9†;žÃ–«_ÆÁG+lݪ1º¡¬M½NÊVéÐíšcf?§9V°­µÈèܲ«þ<×ÏÙü—š,ƒÒ4ZÊ:bSа8°¹o°Ï(‘3Ú4‚P5Žvuf>OnÅrÕÜw¸‰g^{ ÎQÉ(º†‹ÛW$ BÄÖ³e’œû#£¬©™Ç³ doÖeÛv”\ƒA¥›2R`a€ب!šWŠÿ>På‰Â–S¯ÑÉEÿ˜0”À÷@ @  PÛö”“-¹ ËJ› ¢¢EÚ*kZ,먘w ÊÕª,„Õ¸-šrŽböÙ׉.»à Ǫ›¦ ;ÕÞ4‘,X‘żXq˜q×µÔ‘M3D¯Û Ns=u³æ=¸²Ÿ«VMGƒ(d]ä{­[3GÌ¡C4‹ü‹Ì~MƦ$Ëž(ù³x” BC˜3XöC‡97ÿ$Ù8ÏÊ1‡ŸM¡U Í}¥R7kQG K>È~=ÛÍÍ1Pm@Z³yý¢nš.³‡H‘E†qÒ°sÏbRB÷zñÆ׳`^Dþ)Aú´mKæ¹…«jû³k{”ù§Ü6´u¨“F:ôH†ÔuèÙê¯I6  $©Ñš#-ž3ûœ»õ]©Énåä Æ: tyðô"CÐm®”ÉÂÒ z®ÆîGÓàÈYùÀ­Ó Å`q¯qBv.UÙ&³lªdh˜ºæ,², fLØÎ”é&šÃóŽ“ Á¦@žÕòÈàŒmÓÃìK¹SXæ¨õ,xd÷@a#Îþ¹“k¢ÉŸ#jÖ%fý]«iÚ’gîeó¨r¬•vû‘»¾,y—öœPÜ'ê 7¢y#.w­l¤¸¥È)o&,j£$J˜>|/$ R™›Î¥" ˜ˆ¡Üƒ‘ÊÎ1 X ´»û²Ðq…«_¬hºC•j$x²KHl‘-jÅ,SÏ-F˜.C&$eNþXÈ‹zlœ c™¢,ȶ,õßqGd®Seƒ\•3)Šd•k¥@•öêuT°ò@í¥¨kø.´JÌXi²U¾Y‡zƒnä~²ø¯¬³N, •Œ£@rƒ©#Áà(þ}¢½¦d}˜õ3Qíƒ5x )gÔ1>¼0%ƒ%öig‰y‹ïKyÓÍ‹ï,`ßWMl[›qU»4r{,HÒ™eгh椸¦«éĹN#…Ý‘ÌTGà(˜}_£dãE¬q ’N#dÔâf˜úÖJ˜tDN”[÷2ÿˆu•Úölé8ó'n]Tù€±ým‡5–Ò¾`Ñk§0Y>6µX)·F6YÅÇ5²/5ÀÅ9‘™Ád ˜ÒJÃÎêÙ‚bešLtEÉgŸ˜Â”ÂC[´“*ò÷¾ ñ¯>ÃMÌÞ@ÁhÖ^6êæq´g†Ø< Š ‹n¥W¥ãSDÑžf ;Û%šb¸®»ûn0ì%kFÖ9ÏʬH3˜šÙ ÏXº}\7É\ºŠR“l°0H5¯ãÏݨTüŒ¯ýèº6Ì7ì}E†ÅRVþå÷p€âCöZ:a)döVÔ±+l¿^Ùë[öH³Y#™e²z2P,0žý±‘—ÕÉs X?ë3–^úä\?S€a¾gÏãìZÁÁc•a½t“=s=®{îWrÞˆ›gƒ@ÒF²–ù†s#ƒSÀ»ðhÙ\q ‹vŒ­—º’?`ßul^O;:ºfS„Úý1ÓŒZЧsHäšé|Nœ®QË›ˆµIMSÃíM®1'e‰ &hwu m濵³/ÀDd¦àixqp¢±>¡CŠ—Ê3¢ð îœ_Bø:Ï %À×ßž7£zl¹ï"›.³½ÖyVh3ò°L°ÜR=P†¦«]Û"'Õw¯Ud@¡M‘ç > Ì­µfϬìF02,æˆyœ‘‹]Wø0G QÑÌePÐTk$)Ô€#÷Þ‰B`ƒˆ â„„M‹bU#ÁjØ‚R*÷w®S$ÓÕuÝ–›Å²¾J˜E”¢S^G3jîС˜ñátØkü÷S×VtËcéŠ5-¤?®óXéÆÒÚoöÔfÝ5ÕÂêÕÈÍÿ¹ßYGd´ªŒÍëÕ‹YÀÀz „ùã)d·\kZtîItÀU ä£9#%Ø" Š›ñr¶4sB©¼®Ìmb„û°ª’1ª£5ލ™ Ÿ­ÅlNmué€Ù3rÎEÀ®|ñ g‹r›©¦kà¥Oöpç:5vÕcÂêw2¨3 ½Ç4v?^cqÇQo\ÄhçsIéû»jˆA¾5¬…ü}%†Ü9*äÜ> ”)j d”gSš'ŠEáï\ cG$ˆ±gÈÓn¢HtבÐ.åË5”b@,›Zµ•i¹¢Li[pÅl3+à™0Ùáe,RÃ"€CèüZﱡ‹ý~f«2aÁ€Ib·ªkÃTÖ4æÊuØÃæ¯^ÀÃÊt¡¹6ûY¹kÕ²drïp÷wï‘4‹ëm 5ƒnše”"gF7L­í¨¨6ÀÝ<Û÷¯®€Eƒ“ÝÜ‘æ¦3ÐÅÏmT½)"Û§Eþ Û÷{Œ²svf%³ ²’ —“I‘œ1JÍmØû ËdÖ,š ÂÉÍ1¾~7à¨ÊÌ¢`ÖE•YÉXÎþyùÈÏl`aIZ4UòÑ ú·O¾ý‡‹8"·* †„+_ï!ÿ‹z9pô¹ ¼{‚­û‡&dy´’Ì.“¸}G*äY¾kÝ0cÍׯ¬¤Ò°²i{ïìº $Xh@¸ò ¨i (q­=˜‡0Dàf&Òv+åslŠGbfHÞ'­Ä~d%ŵÝ¤\‹ùD´C미óHÚï SæväÛsk£Ép’óE¼W4m|ε{—ñXÌ)!’ÄQ(¿Kµõí>ËÂñ†8’nÌ’ŸQÂMoŽïÏ¿kÀQÔÙ%‡œÑè~ý´¾ С ÃnÆëÛ£ÍÃvíœN¡a$HP£±T2íNy¥yŠÖ‚{îÈZ67…}ó8xïé¡_†ÄT‹"©HøZNNÓdø"¨q™sr"_#ºö€&(E˜0Û"Ü6W(*8ˆD‘‡f¦±z†šDæÄ³ÄºF:¬Š b:@Ä¡Ä ÏÁ}#ÿ¬ºÙqùýž 1›Èj)I¢l5ï¥!s«™ã¿Ûvµ˜ñ¦ö ò3sÐÀÑv~…´3¿÷ušXk >*ø„—sl¥d™‚•›ó£ðRÓfÏ~ä± ½gâ a䯞G/ýÉ^ÿNafs<ëÐ(Hz°ñ>N»‡ƒV8úÑ–÷­añÚW±±÷cÉË;|¬ÂêK…¯¯Ì’©Œ‰‚7sÑä3ð‚CE¼¤ØPÇn‹g›=G‚¤Ø‰Ò`í›RP,w.ò Ú­gm7%÷ ’”¿Éf‹᎒ÍNÅBBE/ ºÕŽuùœ4-˜Í3À\“ÅuaËØ2SXÃPôóäÛZˆJ-|:ìÕ=ƒÜ4Ñkß=¦ ÁwµÄ.8ÔKÇX¦è$[¢„\/–ÔuÿÔZ^Ìb$ ôŽÛµ©Ÿçe)óŽÄ Ô¶ ­­ËPmÍ\×ܹê (Êd³ÏuÊ]W6Q§O›¹ž<ƒ¡lÉ>)”èµ×ÆâD‰î»\Ûºy6Y¾Êa[‰§!I6ü¬Ž‡·káÞUŠì ”\ÅñfÑ4 Q JvÝ(!é”ý„xn@°@ÖFܹ(Qƒ"àái;—Ò~êd\/§×–Á˜wó,±]Vw_`“•£T–QõŒDеŸµög¸Äó¬;nиá¿fÑ9w •P°w?c÷ -´f†òÑU#s7ë©üeæV£ÈÉŒ7>YÚÓECŽ7”ДFaß´Å[×ܬµdôHXM6.°ÔÁ$»L͇±˜ÁòŒ—/>™#ÜõçAˆ¤Ãy£˜Ö–]û¤q´°拃¬3–å§c­fº‘UrxNêxAÇÒŠf<ó:l‹:G„£>vÁþ¹g¯Æ±§K°Êƒxל€s¯6PÔ¨ŽjãøV[{ô\}ïhè~§þl8Zš½gù؇Mg³›9ãp¦6hvØûÉžZÊÇ©a]b¶ƒI"ûøx¹l0%ˆ‡ø¤¥¬ÙÓH:JQÇ¡Üë¨JãDœ#@‰®w‚¥dŽÀ‰l~Ä,¥E²¾Ô¡Ìa!çÍ”„.Pî™qwt3,ÓÐø=fx<¿ƒßÌ"µëçÅúõY,¶À(‹­´#ëaç\ÔbŠ`W3>;æü쮳$ÉÆÓ¯9qº7‘”Mù‚®‚3õï”à¥fÉXG®x,‚<#g9~ÞàA¸ÐuÕ³®Ï=[ÛBâ©D‘Q¸WË¡ÏX~—‹,VVMpàÞý•gbptþtaä‡ºÉ ¬¾ùN›yv[h; ß§–3B9"#ÌŸgñ…ÔÁ¤šxÁºåîç?µNe—* ¢b„÷+#àð“†Sª™FÎ!êòÊÏÎKÌë\òØÚË7W!SŒkç]Uز·B6YmÉ(‰Í½Ÿ …Ô‚€ ¦»*:ú@(/‰KwSpÃâåÓáõrEh²“Ãww>Ìú?“¢M1Ã-z;=(ÝBÉ-FL.Òè5¢!æV²‡µQòý³3Íåóõ´dîÈvµF>š(’`WHüšÂ'*&[†‚ÍšAÞ^Û1g¤²œs¢{LçD'刞æÿ¢.ýú‚ñ‚`âÜJâ%¤Y">oMÄÁ_ç8H ‰  ”èBúb;¢Fã,šè u’Pf9ÉÆÂUÐ̦}ÜäUý½-“MsæÌ+=Lj¨b““Všü¿Êî=Î@H;ëpkD±r!í 9¶¨ ÊµÖÞ#WQ`¼ ¯®kZÜD¡ã³`”ìu')9’?™#æZîM,%J©âZ>ËDÇŒG+·‡R‹«¹—:åð†´Ѝ½÷ˆ¦AÈ<%:±ÆÏzÔ’댨£(ë¢8]l¶ds‘|Îí£í©´ÙMžg³ZßKé~¯·òµÜ¤sGEY ãòý˜PÙìûæ{ó¹x3]ø9Ø©»)z‚ù‚ȉ×µžZµ¥s›)úß®_z aÝdë4ÖðY –,@r h7Ç}›©” €Tm€ôæ…uãfö%Á1îH µŽn:šv2õ|$·nN¯[Þ 8ŽôzMáIvþ¢9[HÚÏ*à¡Cæ›âÐWwn]RÉËA'‰±²¨Y)STÀZÉæ™ ÕtñS¿œŒòêeeÁÙ¹’‰¹«”;L+ËBÄÝ™®…Ím‚†:—Æ=ÞûCùEŠ<ê¨rÔ庛á¬HKsŽÉjTªæ¾G VM梈†˜µ¡vC€SÀ EÕ‘ïØ±rÒjä“{ãAN¦R'þÛ ’@ í9ˆTÑÜ’:$ܸäìJ<´MSºƒ‘,¨;Ú€Cf'.襖75wK3„T K–¢ÒI õscÙ®š°êŒG·{¿Ý8!]¡n]7F ¥6ÀhT3†¥qfƒ•A×¹‘±ëp °!¹ÓÎPלnˆ- ç` ‚¥ƒûÍaùÏ,ŸQŽ€|ÒR:–«Å’·Ô†Å¡Ö:è6PúÙc øi±$Â+Ñ!Š×Œüþàˆ;r¿ „Ñ~VäœI{y§µçèSÔe÷­ë Ñ€ösÀ›(h[¬Ý÷0ƒtOƹ©—{ÂxÎ÷5KÂ4¯Þê¹Wµ†ÞÌ5ŸñÃyÆßͺŽ1ð™ýùãîèƒÒ1+®s‘Sy1NvÕÈêú-æÀBaþw/ö쩱¼ÝÈ»¤þ<Ýë¹9ë„ICdŸ|÷Ÿ—§BÙÈ/4’‘çÐÖÖ [˜¹Ï‡ÞId]¼lþ ( ñ5yµúËéŸá@ðÕs™ÍÍ!o‡¯m®†c DÑLña¥±áY)™ÚµùýyFû`˜*Fv`‰.gÄI¹H PBÚÒ`/J€É(lòMor炻㜴&òH!{aîjÙr§YRêæ˜)RµÀ"8¶4n·´,Á†(þ2šVàXˆ–sW¢hµÙ¸Ö'œ÷ˆ>Å] &Ú|ÿ-ÎfŸecƦ­ÛMs&eÅÝ`xËÙ,-2É´UhîZO)R8ºv)C嬵µµŒ\1CœîÜ%›êb ¨=ËBÜÁ´ r âÈP Ñψÿ;f<<°J0Xr°½ëC0'X™6Øjƒ#Š@yš ÷­øù`¹˜"€/ õÜv>‡~nsàJ0ë]‡ôÔí=Ú“~P_‹ïõ§¤{òžˆÀ‹50¢Í\ï»dŒî¦èíüºï`$÷0)sÌQ?3à§çX"û¿÷?Tc44²¬ÜÊëzY#­ëgÍß=ö¬Ñ K‰…Î=U`\™‚{R7 ©Š²¬Þ¶§†ßú uôõ§t°§¨ÍS®¤hφ4/EÖiÊvv—·ñÔ“ l~3X5F¾a)¬YóÐ_b°Ê[?ÃIø.žQÞÉÑ»ÖlµÿɈd8#% BBg`ŠÙL3þmE®G)î-ŒÀ‰â7:ÌcWŸ 3°øbê^™$®¼¬™‚&1Mží|ž¨mq- )/ ÀQ›IažõdÑì{)¯[vÚò<1ç$ ´XZÅÔÑÍl”ü¹É.lÇ<ˆ¼oÔ¾_áúN þVð_ ”b ÒÅb\\KFA¼|rÝ„ÈÎ<{`äÛ¹•æ)߈cÔvïqáíþSúPRÂ΃i†Êí=çNåÖ‰ìÄ>œ5=ÂPäé(7ÊMÞÜ à›‡$AHðõÔÝEëÊéʾ †l¥Üï¨ |¸` 1OÉ”n ¥÷ÈN¦{ÚÆ¾d…„é ˜ÍàNë2wv7߀ô@Õù|/_óÞ½³¹Xš  ¸cžr\ÝuÁ9wí:GxÆh^ 6KÄó¼þ}¢ïæ§ø aÁõ|.þê'Fø‘ñ¸e…¾ù>þí¿Z6Ê©^Ö0NKKûŽX)&5ù/§‡²n²¢dþÎݰG<ǚ㮸ٮ;u¿\0ÀÞµŽ³] 9Q³!žW ÿEfÅf\ÕºÀQfe1…"™‘ÑÕ™ àd;„œyÑC{ŽÕÉEáó§3”Ú2úœ#gåÍHOÖ¦d&‘Eq§1fwbï×ó»F1Oqà!$uý­ :E,ó)¸«¨î(2¨c¯–R¢ˆ´ê¾žÎ€Z3ã~Æ(КYæ(Øî(.…óî²S-®`“¢×H°“²øJ0Yí&”Ws*HPúÁ·˜‚È4‚ÑfSƒû—b ‘øš[IÑ#•p>KÙ%Ëù晀ձ͋Û5 ÅæüÈ€~ƘdAƒ‰2`ô¡PfÉd=1=mΖØŠá%Àé×s›IÈ 5ò:Üc#‹x)zàR³<­9Ÿè îküzñsÉ¡S]˱-õ>"æv¬’%#$ØR !ÅEŸƒåþ•šÏàö>–å›Í8¨)Ý8cNÄØÑ”ï›S&—¾Öøî·ùæ¿/vO~߫ךÌÌ-§Û8â»|Óoù³ÓwÇÚ–sE5€Ç£¿þ‰þþß¿ã7âïÿ1.Ÿ.pü/P©Fr§xì]}F5ØãŒ¨!­àÎm…_è™Ì› Uڀݚä¹$–sÜŽþ²#e¦=/KÝj-Ü8ŠÏ…éÔrsSÇûáÆ‚yÚuq fùam@­ŠœÑ·™J F¦ÉŸ%yÆ(2Ã* ’ý}ckØÖ–ûÀrÏ5 rÆã﵊çbxÑvö ¬Þ"ÛÝk çØ aƒÄ›YSYæ¶e|øº©çY ¦KJ#lß“/@%9§ŠI1…”r¤0²?çқʖd'×v_<[Û.®q‰êç„AAÍ·C×5ÖITìwå #=§ˆGîxA`jÔl‰Ã4ˆ:B&9ÊÌ¢öÚGb[ ÜùÈÉèpËÀgj?u_ÃÔî<² %æ0£ÏÁ©¦Ñ¼…Ún¢s<ƒÄ÷ì‹Þ‰7v_¿Í’³ÍØhÏ˾Ì´ø>Þ ¾Gú~­ ÑÜ ²Ü°¬žx²=GãíÏžýØÞ÷ãëX,€…8üÌOþÐ0ø(ÖÏ‚t…×_îá•—‹•–]¨„{ž30øî>7Í5 7khn*Ó8C *sÈÒŒ¼ëÒÎÖ”JKt÷»Ò„Z3®ž§NÉÝå‡4ÿá EFè[9ËÀ‚#’œùÆÖíŒþôK;uoG‹=êß|ðÆË†¥éÞNj`R™l“ªnÜðšÇÄ@1S÷îÑ=–rß¶WžÙYŒuÿ”.`§¦°Gò¦8ñ=©Ó ìh‡¤„”E‰'ÞG«Vïðhð·Ø ôLE¢•ái*çA©2w)Δ™h;ÇÉûÉ”à€›íN‡pN\Tâî.zRûšEìAòÞ¦d†±S_<ãÇ–bvöÙì›2®áuö ^òñJëJÔ½¨{;°´«Æ~zˆ~NX°gÈ–‚={äΕAnš7Ÿø¥ ì9TA˘,…Í™þí€o­‡ —Fc\“± 8­©›ñEfâEÎÓ¼À;J  “¦€ÚÔ¿K€œ2®‰š )˜ºlñ(ñ±8ÝIEbÇ@O‚?î:®wÁ,WÀ'aÜ÷‚‘x>äûœµ0‘1ƒs€¬w ½í×Îñ= šûÝðü¹@Óþ~^`Äú-£9.õ¼wƒ7Áª½Ý æ^2F©È•x»ÌX\´…ƒÊ[Ühû³€Ç>2ÄOüÃøÁ_¹…÷ýìšïî¹9É}ésŒm©Iu·¼Žïõ“Ú5ûChé(,ÓÝ 9‘¯³J¶àà©içá^ ­l­Òäe‹¥&\ ³ú·O Ƕ¨À*ÇdéJ+ߌ.ܱG//p$Y$'¯ÓHÌݯ-! )̈§7ˆ[5Ë,‡¹¤ÙSG­ïfÈ+&(—MÄìel“xù› O¼Wa÷þ5ä£T¨‹eŒ¶?‹ÁÍãØ÷l-»_Ípé¤ÂxÃtÅwî×8ôlýOÕþ{†»>È[òá%‚¿øG KÓÁ5 ˜…å. –0•Ù’èpù¾ßVݼ™>)iŠ!y)êè·†ò¥óTÇÜHWg6fs8eÚÐ%¿À…Úõ6u=;‘4wà8l“¨£Sß:s“…qN uu7$«–^TŠáÎăÖô­'’RÒ(7¸fqÝØE‘¦‚‚£û—h°·åx†ÃJ3J1Nì3ÓÌÜ‘5@pÓ5a¢¯¼œãüéQ¬Ÿ ÎÀ˜5 w~ 7žÇg&ø‰mç^)pö•n\3Ò½]*<ó‘1yºDV0t¶€ÑŽ÷îuùð’Ÿ=úý¿€õ‰Eà W潸¦‘wÐôª, ’Ï&§sŽ8ÅÅyYHLj{M$| “IäÍÏKd‡wÔFe ƒ$²XâI‰uÉœ0‚騼9±Ž‰»È##MA‚÷Ññ„R¢É” snýxóå¦-Åb¼7|wºØ}¯£Mø!ÓìŸGÀœ2¤»EóšfÜÍ0þ[]÷„½õ+¾OK?ihà“>ôƒcdãÕ–œÊ1I»? â ª\kY>ç£_8ÿ±Õ…ío){ìQ÷LÎæ?r'q>­Yd 1mжY@I°Ó€"‡×©ÏKܨm¹×¥Ÿ--ê>mÝ™jÝ$Ñ5cRμ¢°{¿FÿÖqèì-kÜja6ò% V¿ƒ­±õa§>ž¾®å–Ø,k£ÁÍã€Ïý~gßT†5ªÈ»HM*4ÀH0§9.ž£ÔùTRú}´ë¾û ¢é+”bÄÇÝNcÜ5 ÞU ÇÅ CNd¡Rúåb ŽURÐ7|¯L “`9X:Ï ¦­ËD³P>C1  –ã°]šÒÉhíó°¡©N¦GÑüº³1-œc¦'u^» 1aÃì”^lm̉¼¥àÄ–ãñg¦Æ˜ÁÎÕÖF{lg~za˜1¾òù>þöÑ ½;§P÷v´ö],c¸óÜz;¬aÇ ïù‰azïYØÉ¶'‚½'›¬¢û5À§~{/¾”c£"lTÀ¨dŒ+FY‘mÎØÐX÷”%]1# @ ëlJIçd1ßåL'š'âºs ´cÀÒ œMd§¤oÜãÔQ“H—¾PНG"z …C(zÿH3”ˆÀ|ª©ÑÚ'£ç-Ùt¤î=0 ÷Ÿ{>™]°¿$ÁâìS#Û²8ø¿Üí¡Dñ›ò…ÔÑÎ¥¹@øÓ€Íÿ:¿lî÷ÅÓ¿ƒì¬H¦¸ XSlgHØÿï<3¡rÎDþI‘H h¡’ê9–½Ðw_Ù¢)??˜”ìËé®C^3ÙÄïð·¶>l‰€ØxöÈe!q |üÇGPzˆº¿ œõ;^Lm¾…û¥Ê5 n¾bOj_ûF%°QšîÞȹ›¹<$7›‚ô Òf>s3Sež ¥Ìgt¨±}~ÂYÎ æëI‡j£däÏîrs"=Gi]':öJ¾?"(bŸhO®]Tؽ‡±cw|tÕ–ƒ&¢>èæöQn9 ›{Hz²´nÝÛz°£ïFµðPð½ÅúY n½ øüï÷ðͯfØ( Ê ƒT¹c’&vF¡ÖÆN\k@[,fø¤ûÙû⃌:$è˜'Iuz;Šû–Zªˆ‹_‡)tUb Š‚¦†¡`‘ìyá~Ú#3 ‡à”§>Å…3â샛B=Š òˆ:¥NH¥O!§Ã[tdèr'YìˆEBz(凒iKìïÁxOlÈ 2GY`Da‘ÜŒ¤ÃYËñY:¬‘]’ÑP<¡{ ÆÃýQVs=5¦>d÷[ öUE„Õ+ û÷iì}¸D>º’õ{lTÀzi~k¨&Œj¶y{dU œáí¦@lh‚„vdXA”P‹E÷—5­ Œn5 -Æ“ëž:ØAJcû®àV泉õ°krŸJYÇÆÎÔ˜{HÒE¯‚¨|ÅyO· °;7Ù»ñÍ÷í|PìƒMÝã×¢·ü%ó½ÅyäG4ßLϽbEîökø..â#¥ã»û';¨a‚f\¼”áÛÑÇ{?8ÆàÖË-iU×/U®aáÆó ]áÅoöñ™O/x]¸cJ‘› Ü·‹»Ú`Ô0½<\´wéË0Gaú .w”5ˆÙJ]Ì ôÄ5E,€úÌ(ðS}ÆG+,\£mÏ$C«…}I¦°õ–¹Bÿæqä6öó¿_à›_Ë0œÀÏ+¸f”µ Šu ÈÛ™S‹ikµï38º'?" ^óCq±ÂqP*B»èT0즂_yÚ £½eÓE(ñøÄ£*œZ³H‡ÙBSošÓa‘Üœ™Óï.K„ÙsÒA,ÕÕžI6uœÜ¶zæÄ½áhîCÚys\˜RÇù”’bÅ2½è†·‹y×$cÐPk "ÃÒL”iˆä>|øÍ±¿à™÷O°pãyŒv<— —®{Z஽§wë„øü§øƒO.`4alØ=È4fŒ1LY7N{Ì‚¡stÐ0Ú ï˜û¢˜Œç“âÜ©T -…Ú-¶0±îR{CëþE@­«ÓÊ.š±ÏQŠõ¦4³’ZsrMÇYb”¸vf9q‚ãŒ;5á<ÇlT2[jÞâ!·ý>ƒ„”ô…Þ*ƒ´‰7+¤,Á eŽARÝ \H…tBÛ‘hNq©Ð÷{ÅöÜ p…ù$‚¡ü§L æd¸±bÖ0¬‘d*ñ§î`îû|‘2‹¤\Э/^þNÏ>[bûÖòá°*’ŵ?¼ÖÏ`pó%k¼ôÍ>~ãŸ/áN lTæ÷P€¤± ˆ½öˆ»Ù1E@öìQ¦Øv9CIÉÆ.™ÍU¡adZôŽ\ÓYëœyö›.稻̳ïQžÝjþN<××Î)ì;ÄØ²eŒbã¨A÷w´Ø¤™ÅÉú n¾ˆ¬¼ƒ;„?ù½¾ý|Žá„0¬aIÖˆƒ=k亷Qƒ)¬j8 óÆî›¹£¾} Qàg¤òžâÂ*Ù¥ö°8ECÌŒt† ' ×çCdØ3JEOQwMHMáLɳ+b…ë‘~,"æ¬Å*…/Ýr$‹ƒ(ㆇ­Ø½Ù@¼°pã–ÄHN Æ›%˜9eH‘é!M¨-köàú:Ca ‰ï‹NYa,#s ;5û¥Ý'ƒ‚È| ¯¼X`߯I*†—ÌÞS,*ßÔÞ“®`áÆ·•·P•„OþÛEüñ§û¸S’a*£PUäeÜ“¨àš3v~*6ÅhÍþD@„8í<³E¢áA³žOy¿(2 J„ý&:îÊ‹o|ü³¸½^ZŒ¡ÌlK¼n+yó|ihPÒbÆcƘ#f³uÑöɈ÷’s_î$´9¨ÝØäM}ÿ_ €Äó¼ì}HŠØ£.€$×è4€4Änj7‰Gzg£yÀ‘ô+ŠR` 0 ULvhžP£‘×ÅéG5À™»o%~Ÿ:ÑÃþý5vï#­ ¯–®@zbf6. ûäã뀯y€ßþ×!8UÀÐÍ éùü£{Žäùb@‘HðI)#½SâŒô¿í_•¨tª%â^Í[¸ã9Odr{ω÷>’’¨øÕÀdLxý… KKŒ{¹^C±qªÚqÎ’`‰¸B>¾ŽâΛþþk¼yBá~³Ó§ÌÌ‘‘M’wŽWÖÞÛ"Ën+wûF‡ÜKVJÖ Ü¬•FºOÞÎÿÕ ô{ÀÃ5 ½†ÞúY¨jÍœ¸{`çXíÞS ¯€XãÄñÿòŸlÁw^ÌÍÙâΘ’|þѤj©]¼KÙf«ç´4np¥Ä}‹ÀRpÏ:epÔ–>ÆÏ'#Ôñ ù[Ž7íñ%tÿ ‹o °R!Ùro fžRŒèRܘˆŸ?òra¹ý)Wß*²[£GþœOìßóî÷1·³†÷ÝWîÙµßÂ1#jš†gæH)yîÝ$Niï3@Rô2F/Ó(2Fßþw/cô3mÓ©59+í ጚ"Q) @™KHŸÑ4ÔežÎ/m²(y›¤tÜÑâj3!÷jÂeŽK“©`di„q¥l÷ŠPÖÊÌ^heÿ·ù»‰V§+;”bädfŽzª±u^(€Å6¼ÏäRüÈñÁŒpðHÕùº'_îáKŸàÅã†V¾QòaFÖÉ·‡ yßÔ6çgvਧ´•z0ŠL7óUÊ<7=ÅÈ0ô Ѿˆò#·ö¤énlÚÀ³ ‚Îs{t~*;å6xd3eå-9ÐW&»ÈYu/ä@Ïþ÷ž=Œúk%>Ö¶Ã0³¦³«ª5ïõÙ×¾õ•oœÈ Kä‘‘ÔyuªY8[5©öŽMò³zŽU²vå÷îûž¾§:ŠSŠp¢h‰m⸑Äô­.vjœ;æH8`’V@ÌáE’º›3Œ9dJlÄ‘±dz2®!É3îe‡ÜS´«Ô¤f)õ=ÂÌ!pÖãPº“8lÒYÇ)VŒÁZï[¦CGÆ){ääC$áL}]¼‘­¿¹íàH23œdÏÛ¨íg6GÍÚv/æÎ²›±˜î«ñ?3ÂÑÇ+,mÕ{Õ#¨:4l8q¼À‹ß.ðå/õ1*ÙÁš¦Ì†;[Jã\7©ÉưmXºÜ&çp‚k1Äœ.†b³„WJÈ&%¤DiYAËù° Hp$L¬é’3tÔDZ›? ›ƒõáìZ+”¶w6"5òù>þìË}<ýL‰c•Øw Æ`qùB†‹çsœ:™ãÒå̃ ¡øÏ9päÁæTg¹{ñ‘»-|)^¿-?ãtÝÚùÌó`’ÑñK§†p½¦xšÃ‚EăvD™ë‰Ê4>äû©5ÐwÁ¼— Ÿúvíf<ªqä©Ò(úf~Lþ*ÇÀ¥³ N+¼þr†ë+ÔØ´[†ÌŸÀ¤fS˜XC†J73nÌ]­Ï·îúxQÄÎp‡å3R…»YHDÔ¸iR—ÄŒÖä#QxÄ`)Õéâ.¦%v½²ƒLác…ûR[WÁœ”vûO„´;ß<ëLNHr¸U –ÑvÙòò¿ØeŽÃ×”j]Ä €tË)aßÏa£„:Àu2­7F÷…kÊ>¶3GÚA¸1¡ˆ%J‘n^3 ˜P+¶F`„R3P….„®QŸW¸ðϱ}ã=Ï•xú¹‡U踵÷ÀÙS9^~¡Àw¾]àâeeç 8‹†ÛÈÁhöÍ·’MãÅ1Ô:Õí¥Ø}ÊîAk=ih?wÜmØ@Œ©Vî‡ÒŽiÔ¥YM<—7n(²¨¢Ê3X¦Ä¦B¢ú™ <Edüwò=I | mº€R)ò䀛Ã#°¬‰‘ž,žWÛόί ÇMøÁ?U 5%gnVÑ?ïeº—ÙC÷Œ1Â[û\¼‰÷ü`­Ã4H¨µ¹ÿe¢|u¬ÙD›é›ß)ðí dâ‹j[„;g:pU­÷ØÌV‚“¹÷ñü/r7tWeû<Ø›tÛ­M;ðˆ Ö9)ë']ÃÐ:åZ\ïJ›aê^Œ.®\Íðí¯gFnHÀî‡5úó>W.66œëœË1Nt 9@4±vº͘xûv²ì [[o %Oæï–GgJ‘¥M9ñ†©9\éBÝMñŒA’%àvæHí£e÷L3Š‚ ëÝt‡)83¸ Z ‰øÅöî$ s¤3‚fS­ëœrŽHÚ! pËkâÿàäNœ\m±@¢à Ëa  âZ|Ç=MÙ!ÇŸ]3(”_­L6Q ²vùÄfOøÈ‚cmφ*3ài¢ Ó<¼NXù|_übÏ*ZûÖX\ÔÐ ¬^#\[QäxC Î’±Ý{|lDe^ßäÁ5¡°,î§H,-Œ‹øÖ½à¦#Õyˆ_ƒk—Âç•yzÓ£lˆûÛÅV@(ºçD3½Ž¼¥Ä§~>B@ÃH€½Ä¾3]AîÚóîd9WeÜ•2J˜ý[@a\1ί2H m {jfqî†+ÒÆ3Nœw ñ êá/(Ú ÑS‚ê[͈»›i÷â3Íó=Ìs½8Ϫâf°Šú^­¥·ÉÚNJÀ´l6êpŸd{À•ÚÈ'ÆVJá \ÃU[å‚_'"ïÈ˺¹#—}ÔõîîÕóG)ÂS™ FÓiš§N €ÏZQqÌ£u“\WÇ )˱6 ‰k£•°5Pfæþ¹?[^ó{ý¬ò÷Úv‰57AUmREkŠŒ|´q%¬µq®«œ$®•̦I¤kòýß ß 0ju¢\Šñá°h<‰ å„ÕÑN14£•ÀäÖ›â Å1Í«ÅÝïXOΉÂ #ç"yG螘vÆSÌE -#¼¾AC? æ£(Ï&i 7ERâY'v Nå½´ö®(#&–itþ –ùI‘|‡F‚ý{וI{ŸÍþß¼¦¶÷XÛœ$Ó`1,O/3’¼^ÆÈ™ùP0ÖÞÈl‡ÇÐÉmÅì÷.ni'^®Ív戭 kã–©uÀ[†ÏZWv€)|q"£(ašÀ1óFá3‡BËõɉ¦¸ ËQí<·~Ú JHvãkCH«Xo™x&[™LH¸q¦ßVö¹½åÆ#’'¬ËÌHÉÎ…^Æ~¦2ËM·×Èì¬yÞʱµ¿…ï) üÀ$ÆwË©úŽ_Æ]ˆYÞ&pÄo“ùÂ[G<ƒ‚b~ ?ÿG©2ÏÙDC¨›Âßý½ëâù¢ÚβA€¨š [1ñ©ê `’Ì‘?¼8aÎð6ilç{ÙÙ2¼yY#N£t—)ž;JÌ’8©(}†[€Jv\­L’)º‡ ÈØ$Ý€£Œ"Ók…Ë6€Ö˜*˜"Å…ÑV6ˆ¶ò÷–=»T3i DîËk1%÷ç¹×½ýÛx<žÔálHq'uÊJ“vЩŽg– Ò,,6¨£± ZO³Ín3Œt˜-6ÆHúa79Èâ`B²ù þRw ^sÀ I«+vRÖΉ,ay€îß¼¹ŸF]FÜls.)˺X"äÆ’ÍˆB¬" tfš TÛ‚Ó¾Ú©Ì>RZzR73K¹5šòsož  Ù¨»Ml£fR“ÿ»Ê²XUmÝ1µkþ±—#·Øަ”—%³ªâ=v×- [ô¢DŒµÿš¢{AœÈhâpÆ*åúÖzÞ)½¾Z³j1 Í Ês¸¿PÆ #î0W¡öÞÊ\Ý2%oÆDÈaiþÌ\‘ŸE#¿ÞîâÐáÄgAôܼãé{\þv¯®ƒ»_šŒ1C×kpG3kVsw–±ÐÛŒî)(Šÿ‰)É.ÜÕûæwbMó|_Á ¡õeeZNžU)nœcb&Üï¤ÔÐI¸rl‘ëèU:´I—òº·ë¹ˆ¥<Õ”­Î?̆9SmÀé9îæI*É»#‡µÅÀ·(àX†AvÌjqé`Y$'c¬aîc¦ÛÌQ&œ,•xõÚ°:©JÍ„Z‚!wŸ+ÓµÕÕö¿åýÕÑÞ¦³Æ´ã€S]aL-Ñ|HpÀF3$ó1IÖ):8üž I€ C!ûOQK¦&àB“çpM EœÃM}ŠMÂâ•9.ÔµDw\´:þ„öÜRô~"£†¤‹K°÷¤¤^‰kꜷäv©à[´æ®Z…y׺•K±l*xÝpî‚mEì¼A#wìµb£BPæ.Œœ‘e¦&Q6\V2ŠŽ•ªÅ¹RÕ„²f”•µù™53꺉‹`û^80eH]‡”'zÂð p¥¤($•ÓÅ<·gÐB&(šÍ fl3†)ö1À‹‹9N7ÞI3”æg'5Jd%Ý”óaË0‚æ6Üñ< «ctÍ=q¦e™9‡2{]”vo®¯“=›8±ëÍßk=‹TPì_bp´Veƈgä‚Í1—ž¶äŸ£Ó~·Àhæ×1Ï|Á4˜ e–~ÿÎòàã·^促´“;Œ,yÔh˨¶…°àHÚ³<ÍÌ‹g‹„û_ã hå`÷á9áë"¿Ê9g1Êžs;ÎDs;Š¡ÙÛy(‰î¢fnqã“DZ5lWG³ÍY@‹³C;ÉJíœOi Ì‘w¸Tæ}(Q0i+‰kÛy$òö¹µ”Âh6ÀÈÊ™4‹!{¢¨;ëÑyºýñƒv$SE@”­ÀQ”Rà§«SÙEÇ.RÒ("ÐR…9'Ø.ž"ýjã¡Ô†1E‚“`”Zs â:0:qtT#²¼'ŽR…3B÷#ê€7Ï0'XçˆÅ‰Õ¯­u+öJ|VN]C·&¸¯Kpב©;ä²úIhɺÄp{Ëc?f;%ó$®ÙóÏ1I†5&@1´õ¨Ùtõ+[°:æHÕFbGp{“ã24Ø ÔGæµ*ÀK÷j д}?ìXky;LDÚmÖiÖÜxþ"£ )‡¤]>Ñ ´%’± 5qè*‡s%‹²ÖüStx§ÌaRÀ1û5;’lJúèÀ¥TÁšÚ_ÌRÊ8)îZ¤à(2nŠ73‚ª5´”6Œ'Aå<()bH‰Ý÷ð¹^NóÌõ0º› ›)HÞŽ¢¹ Þ»GÜÉuq¢ÈíØ-Ãiåèó]} äzÉnè_P»BZ ¦Ú€YË0]ÁÔAv›ÍûQÜF-çö^kóŽ€lfw®Å®@¼¨ÅÍr˜;±>¹ëÌžó.{,¹Û¹ùÅMΓ²G M`8$´mha¨¡uãŠçÍ6|‘b ÚJÀ¨S`ÑœL‘ÑæÄ;Ö#ãDj0ÅŽbÜÞ'ZôGÇŽÖiëËiFõvÉÆ]m2šÓ^§£v >'>Ü¥÷ƒîp¢€ëÚ¢SyP±Û‘ÀœÎ¤IRSHÛ §F[´.u_sNRD, ö€i:0í:´“’NÌ×*Mº&Ʋ;¹Þ-<¯¯¹ ˆu„©&#±Ëˆi¯sKõ6óì÷ wþz[n `±µfÔ®qã"`÷*Š¶×®Î­´øFz®‹cFeŽ Šº?´ ’ÛNyÍèE8éü˜hâ îø‰×uk-ÞÃä¡Ò5£˜àã°)Ôz}j7É´|0ví”ðMY¶;SÆ!t¡m¡æ30QÖò[™ Kcû}÷MºdïçûéÞ@¾7 “¹ FB:Oé÷3+þè^£wŽ1¢Ž>|0²ÜXžF?ŠãîgìÎu¯>ü}xŽ\‚MÒâìP{”d¢$CaßÉéîqKÁÚf Êxˆ#F¦óÔJIGìT[Hž¾{Ü®Ä3RNßõøï‰¶½WZÔ|*’Hª VkN=/QA胥 ÛY%-B_ã[¢JV¸[iÝ;.HuA[Y#rh—žO‰ØNH™(QäM÷AÒ¹­³âk_’ñ¿]+ºkI·ºSoÌBØ J‚ýä}O°Z€±ŸOsùÚ’‹s¾ôÍ6Ë(1 àr 9¯ÏÛbž]zŦ(«ÝÅQÂ4!ñœÇk 6_Ï8¹÷L ƒ—H.ÚfÞïÀ&åTÈÑ:i9]&¼ìiš 1f¡âpÛ#”ü¢+j¿%š³9z€݃+¤7s7Ž¢B6²¥å„qºeOo*|JEÉ&ÕÜ>?꘱ïß)0yߣy»<³ºä³æå“nt¡l¨=Ó–¨qZÍb Óic׳(ß…NÔ R*†ÇÇö®0qÙK|NÎÅVíœl8P¨O͈ÙÔ;¯¬NØú&B ÛnQÔ.vbi^+üµÛ"<Ý뉜åºrG’{ Ýi•(ÄZXOÔ÷VæPÍ q‡§B§kx, KZ/S²Ö’ºÚÿÍ„Nv†)ÁBDL·ãÐf„(ÕÙ§Äç£t^‘\/[!'B‡“æ <”EFÅ+uI9°®çHêY[0 kލ}ž¸n>‹Î¾Ùcì½Ñf®ˆY†~"ÚC⢟ÛlSZRJÝÍ„$ížÄ®u æ!ãÂÔa·ÝjÈ ì´2ºlá#ÀO‰YŸÔ,$w ¿X _»øÓ“äÜ*H›0ZÙÈpk(#@YgÖå>a×B—}=ÐSÀÖau`Å(!¥vmÿ‰éµtÇþý}€ô€"ÆÜ!¶óÊ׿™3º?ç-³Fo‰1’`ÜMmËë¸ëÇ1Mýlü>ÿ;U"2ÚM¬TœMyWÌiìñŽ?]hœpZd@ë5¬|¤µï iHrž }}¸Õ—¯“n30ÒÏ}<ÎÃQcŸ ;æ¸Pä cÈÞø<|´ÕîyøãJ|v »çsʼqÓ¸#Ïíbˆ0ýÐL¶æ’8aÞt¬IšÈB…;äXÉì’Æ‘ŒZLW4}°(ÜØ}Ç÷²5ÎÒ.À8Š,÷”¥i‡Y"Q °M)€ØH’8ÉêNqs*°•Úl\k‰$ ÷¸;ŽŽ¢”ÃÏŒ-ÀcÃêªèåʤ¯%¬ÜIØ*g—„#‹(a{ñœé¹=O3ˆ8šÛ ×Ö¸ZG!&×¢™IQ‚w#%ï+%\Ñ‚ƒ.Àѵ¡H9r}#nK/I‚™)Ý䎿ãÔÀ·2¢}7f!%x¤ÔáEèžÛê0­ˆ¯ÉTg̰m¾—²Î)öóÑ™2óm> Ö~}ìt¾ÜcÜ4+d¬Q+F!ÙÄ C:ŠÖZÚ÷ÿAH÷f¢gN‘܃ÇÁ+ÎÃÝ ªgžÏN÷àÇ0orF£]ø1Ç~šù:Ü9Yñlè= Àh€ÇüÀ¼Õ»júËÎrybYŠclbÒ€€j¹²M±‰y å:¤H6”¨5çyÎ8±9ýô~ƒÎ3u>'š£r2ÈtJyµÆ!™J¼1áàÇÚ2J€ J9ˆÉn4'²Dº$(èØæŽ¤ùYÜ ÚJ¹"²àsÏ€(LXpŠÍsA!·Î ûG4£ãNñþ›èdwšŽF®tÜqBËëOSd‹ÌçÔ¿…q"£•æ‰Sk¨óçt $7#îõf#rw-ÂûE*Æ^R"«µkÅãùÈFÞ»‚‚) öPÏÚEK€uô|øu&¤YJ#1Û•4*H|qºÀM²HÌÖ¥š!]°h39©FS\-cäF$YíP#áb˜úL©\¤ÔŸtvkùðÇ®|ò`Žg½$HÌ‘P:øXŠÌ˜GåDÈ3B®Ä¾h+;'Gdätš,è&F¡È T1jEÞ Óдs;bÅx¾9Úü9ÔÞöWáûùvî¢ |@êÓ{dâàõB3_+¥ÇpE_[ús7·¯5;’(Š™ïÕýçï’'æl÷·9Žk´k¬°iÎu:“ò&ŸV!Ø‘‡¬Õ9.;Íwkºø ¥_ø>xÚÞb‹Qn¥´ÏÑuŠf~`Öwd‚p‡õ'OùزФDNÂüŽ˜…Ö¼HǼu¹Í¨Ÿ§Õ æ±ùW)¥r…n ò¢5ͬÒ1г™£ÒX‚3‹™4&°b›ÍÇÍ2anЄñ…±F2:êhúwÍüEmž`µ¶¶èçP¼ç%Ü å› nejo®Dm¯ýàyîØµ´Ä…àh]Æ9R±õyWWg¾B÷·èmRô|9¶H‘Ys½\a±0¿}`;EÍ J1Œâg(r™ý2ä¥Éõ–ŒªF¸¶ÍÈÕšÆÕå÷ñ({ë'ëwy¥Ès|AgTK÷Ñ5õß§½â·v™YÏÇr²Ê8¾7°yfç퉻¾Qß-ŒÑôÜL»Ë<Ýr¤Iã„R&~pRꇔ,}3—+ÖôÃÌñ0”SÀͺ ÔÍ"s×›‰†ì݌ˮ ӔϔîŽ&¥;·’ýx炦wß§Aæ h•ÃZ'~!Jž¤Ó\Ɇ$ØB‡‡Á¨Ñ§p)æve?œ@œP펺nܲ"3;V˜a~MHZùèêǃõQ‘)\¿ؤYg@9ža¡x†iwµàŽÇöÌöut¢sϲñüMJ:†¶š©%%{DP~à]ùÂÔ½\žÙ¹Û¡ÏU\z›DêL‚o2…k^ÿ ™n¶ÍìY§‰a¨˜ “ÊÈžœY+øà>)&’3jÜñ©¼Ÿ` w¸ŸÙõÄ)IY«»œÊ±J°?]†Ì Å €ÞaŒ¯çÖšA˜‹QgÂØ£åüJíÙËäÛ1ï'–”ÛZÿmeŸL1rä™B?cì^$dYÆ"6?3t'©À±Éž-9c!'¬—•ÍŨ5FI#©0iK$if«6¿/gÝû3ñ» ÍÓaž&£›âPM©&è]\ÆÍ°E<[DSàùù"¾ „çúÐoYN÷}`ôö±Ei›zR%pS³S{ÅEg/cö†Ó æø“EŽ`s~NžÇš4¹GH+ ñLsÉÏj “–„. <ÿöΣc” ‹L DSšÙèœg¢.‘$¥íEáÙ½ê¹.ÔÕ1ÃÔÁkE‰¨‘ÀD¶Åà¶eTË:˜ÃYž™[K¥ÃAñÅAá‚ F¢+/Ë©.·"N aŽÀÒJ6pŒŠòTS!˜×èšI‹çQ¦´) ‰BY±©!¬¶™‚t'so)rC'1ëâòÒd—ÞYw;Ö‡8õü$l•y냂‚UWi ·2*¡Òf´¦ma¢­üOº‡†ûvì*6‹_¢J³L+¸í€ØD¦wdÅfÔÁ褲Õ(áLîXqNQ”ÿ¯½Ô>ÖÚ¯:Ú휸ö]²;b?×eŽ82F ;Ûä+B ^½$ž…¡Œ;™<'‚Ù…„qKÀÕ;áíÓ­³žÒŒîŒ_ùý<ÏþR‚£{øÑÞŠªï0’ëÙàH®èhF‚73}ò ,ïϽ-à¨K3mˆ>•Áɲ“žfjæa§]?NØkÍLšïy›&ûL~ŽŠEæè¹š"×Këõx:ƒFS”dH“oûS‘–¿§FJ*FܦdñÐ KÜì®K3þŠ:¬™§ ©óä¬%&É(r'K,.šÚonÖZ«¥HAG-£îši Ô\%ØâØŽ{öP5wuf=S!¬ÝR û¨#«)Ä;¤ÄÆ /0'‰Ãn§Þ$$(*n·ÉûÏ휼.j“€é™ÜÎ>I2 Ò‚9Þp€tÐ&56ß@·ajfÒIèÐ0EFf_#³ÖÈάαC$˜0)S#Á"˜¯m`"1üHˆânìl% 9Qq_Äf¯aFfd$€}bd Ô™™I•f~I×dë…ßBïÑ2í@Ê–<8¡Ds%ÉEû@ žc Ô™§”*{³ŽÁ3„ö>ÐZÑg‹ë¬øýßÎíÜ2êZüóKÜÅ~A€z³c]NŒÝ[€å>¼1ŒªbÔDXumú~Zsc O„k¥™qS øfA&e…þq5îxyF¨40*\§ãr7óë Šý>|bL[híã3~ÎÔLçDÐtØœ‡â¢ŽaÖf>‚ùí¿n¾ÝÄÏšþ¾älÔŒ¾—@‘{%Ž ŠK´Ö¿ð*C—DÝ yfιŽ >#ÏÁ;Îd¿:$mgRQœrp#g…¨óçÄìP*€7Äk$òÂÄÎÄ÷ñ‰˜µoiaKI`Û.R9 ˆE°•˜KHš5PȨÈ⬤Z3H ?§¬¡yúµ‰¥_Ü$†ºçмôŽ…é…]a)ICÛÕ»Xt€9C”8t˜“-ïh6.¾§Q##%%¥èYu5´È*â–¤’Ú¿œ”^Ÿ°Æk'Ü%säÁ‘ezìï"·Æ l OòlP¸’“ÎiQ°-ûfÃþ‰“,.¶ñ±yY{¤ŠÒ™;î6e àpð<Ðˬ½xE@f”4 6œ©qUdJÜ“„Ë\ª©2Ü&õÌqûZ´\(yÊŒSìØr¦ìfˆÀL²Š‹ÜSŒ%d}qîRj掶è”ú•³idenÝf.|8dØìóÅW†Ö¦»6Y\n>œY®X»z#ÕUÀR±Ükf-…b”Ô8*º†§LSĤï CoÓ¹Ÿügmw®‡¦7TgÛO¼¹B—åT˜y0÷TÎûuï4Þø>[4ǽ¡)ŸWbRj”ewÃÁD³ÁQ‹1JÉ @ ¦´-qæY·)1»9NZ$7ŸY†Åò&ÞOòmp÷×1qúy¾§.v<½ˆImt”.†CV„‘ÌBHéêSrž–ÛÚæ(ÇDJ>B¶$RI³$3é¦q—Œ'fXHðÄß…ÒéV€* À=ƒÆ‹C3}aZør²ÃG‚)HÍÓÙþV¸*Z£Aéëí vï`ñ³xÚ eP'sÁqh§,09! c)¦jdjî’™B¡¬[j¤‘®(õ.` xp…vÝdIëð0È–"€èײÌÒŠ:OÄÝÝÈß>#då‚.0x¡0„Ó¤J{™C¥&¨6‘ljhø¾iñ´;iŸã–®¶¥ŒBºÌXR¬v< CSöÁ„£qÚk[Æ·X±®fQ(Ël…Z[vϽ¾[«eDÈs¾e¶y[ðf ’¨Ö¸x‡PVŒªfÔÚü¶kÀ=One)´˜44kc f`ûme‡]£K}óýkc²yÖM@Ó|Àèûé®;禙«¨On·ï9ôvÓ‹ÝÔŒ%ÀÑý»<ç|Ñ]eÔ|Ÿ1º¬ÑŒuÙÊFç9ÂänÕñ]GâfÖRXRµÓô<ëqFmrÞ»…ÖØçŠ„ЩQN­+“l¶w‚©hæŠïÝsÞ);êL4£3DÔÝ%neÍ ‘·’øáqáÄ)‰…Ô íš;I~]g¸lBš“0F YˆÇn|,¥6 g¬Ž&@çãI¡‘Bzž<."C ÓR @4—Ç´Í£#`ú 5%vÞ*Þ¢NÖÁ‰ÎÂø6àÈHÒg:ŽëÓêê‡×Û1Q’$XdNI?’ˆ± RË œ=ÓΈšL¡:`ð Ëzl˜®›9!vŲ§âN‹úîý¡p“°ö¦Ø¡Ã% jÞSº€U+K‰ÃÙ – yÔÀ (š€àyn¯{jËCƒÃ’‡`þέIó÷ÊJCUfÀQ¡€\¶' hA×Àê˜T@U2*&èÚ|…Öñ¹h>£f@i€•ý3†ó6·÷Óϳk(Õ¸µjí•âk§ÕßH¸¿¡³³}žâdïÅó:a¥€]9FQ'. Œ¢ü—ûÅÍQÌjL—ÜÍ“ƒÄ÷áóüedŒfþ šÂh ±‰õµÌ>‚.N¯îx>R ™:†Êù6å–’Â>ªB_x¶m¼YtÏy*k;ïÜ¡dª§¾½Õ “»ÁÏ ”’›V¼Gu¸:$ç}º+Úi÷Í2QZb~†8˜‘™Dâ'‰Û²6öÝê°OH@Èh[{'‹<Õ<ÿ‰¸Ÿf™ n]aŒ@± ›Úk­Š‹©Ïw{ÄŽB‰^«’æŽS!¼^2Ó)uJÈ'§HÏIHœ=·p'ô23oäºõÒª=WÉm‰Â5I²˜¦ œ¼Ì (ÓhNÉ?Ùæ-y¦¡a~6âÔÈüè1À=SoØ99­šÔÍp=ù€_îBBÓw?¦–±@[f•2\‰fî8š‹A O±ô§Ä9IÐÂ׎€[гˆºú¸«YÓ~˜IHIH2“)×­‚™ ÊíºUJ<¯Jzš™ ;%ac¨JŠ uÍÐLÍ9ÛÊ(2ß[[W:(jÂÄÖx91úÙÙ¼p¯)2“»ÄÂŒƒ½¥8ZM˜®:ú» Ý+<Ãïetoëaž÷/yZA1Ïk·bæî8š—š @ÍÔs}½Sà(ÙïJÙ“NèØ˜jÖ æ ÚÅ'‹¸éMŒYX¡»U.-•9jn¸Ï2{'äi%¢¡èà"¸—Mé&×U4«”ìô¤­©cMÅA°Ó21â"$1—е¿%Sç›KÕ(‘¾2Æt­ZŽ#‹ïD‘?K]$^'éÔ–t Cg—8yc9ÕéèR¤dD©ÌË®‡¼òÌ_ê\ ‘Çí2м¼ŽºlФg[“¿BÎý«P†Y1ØØЬq)¤È®º™¹Ì%Ì #Ùߤ[átPK!óyM¸+AŸñ²üD*Êë!˜ÂXeŒ… ¨5¡ÔfÅû]dV Y£y/Ψ„À£«QÂéÂVC"ñŒ¦ö®à{#÷¼Ø“»²Ó¸ý,IJÌÀt$Ö”†÷$ÌnŠ?~›#7f?0qL¦ '¶ 5S@/z™YÓƒÈ2¶Žò$ÞcXwÆÆœÁ€£Æ˜!¬'Cy´k(h6³Á™bjÖJ È*k7#Š X.€ ãÚ.k&1–ÑÈCS‘o÷ ñfιûX+ò$U’…ýÔY£DXkkH8ÄMÁ䡞@œHŒç·áf¼eƈg3>¼ VˆïÁÚ÷ÍÞžýƒÛ'ü|׎濮ÜIó4×"é0Ç›Û_xör^¤±Þæ¹vÕ¤[¢QÎÌí¹«)pô®vs¹ßPg¿¥½_Í#½k1&‰Îk*½¾åtÑœ8myg[²uØiÊu X) ©[‰ò¿¤æ@"0ª_G š.Œ3I¼O¤»ë@Û¸@8î±dµÐáðÇ©:u€•Ô³ÌóN1l-×AJ| ¢°[sä4Éù!7“C^Rgæ8H¸z Bj&‹ÙfQ,ÿRíuKÌÉëFAÌ“ýÿb¾.Å,}"8ÕÍŽ´—°v¶KV aŠÞ~TŠPÕ„ŠM¬5A+C°˜•bÌûœ:°-ÁZ¢[gUõµm†cÇL¢t£¢ ¼¡+»)Õ€éêRԛʾ7³e@Æ-?[‚Êœ{¢u“Ë & ˜È¸ÆíZl€}+XØ>'•†%¡ª5jv3Gʇ[Îy;†ÇÃÚý]Ý>+ɹ/²yŸK¨“ø¹>œV‹= émHü ¾Ö}¬õæ†~Ÿš£›=ógMÉ »èLbgn·9|¿ÍlÑÜ~  ¦|©žãºypú}¶èþ£ûÐx=ÇÀĦn¦±8]äj(p âN×¢ùÑ>c½MÌ2ß]Ó!p9j×¶Â9¯kãN×¼tÁÑñ&S*8šr“3G›_׬ ÇîL2@´ËÞ; ”°ï:T©‚{ úëd÷âÍÏÛµÌ($É%Ã’)HE¢Ôû“C"LÝçKdÌ­ôejE„ £)ÖY§¨¨ë\¿$LíEã€wIÁšÿÍœ‘ÂÁ#ÉÜÁ.4òÃd‘í×JSГ2ßž+…"3sGÊÿ‘*„3š3= ÛUmÜí¢œ+ÉÍSb#V²ÍÕÇ÷žšÆÌj³*ˆºBšlgx¡¸fŒì¥Ü(í°6’;ÍÁÜ·îqlzßÙèÊW’¯Ïü -Mhí”Ä@-ÇxV(%妎æGWÄ'¯SÛN„(`( Þ.##ÿÌ2 `lé™|#/õ™FNFÙnà\ãÐ5Œ)ƒ4dˆ;×ÔfÐ¥áŒææÒèX2–zdi¹Ý#`P0§™ ™1©€QÕ,atŽìÛ¾ŽÞá·CíB¯û‡§Zà18º?ŒÑÝ€£·jp1U#õ—1ºïàhZ14•éL¨²fÙBÏó6g,šëjÌá?uMsR6DÓrðà¹ÞWê•7ak?¯ƒ a¾Ù¡©¼ó4‹: œÖû•YD²0I™Ö 3GÚ¡¨ø¤ÄZåT4ý· —ØD¢#/á{KDAËÁ(JÒq*>Y<Ç®ua¦KxšŽo p ܲ”ÎM挰ASZ•‘;—¼–Üš+I¥) "F’= ä3bcãM ÆŠ™#ju{Äí?~û…p4´R%JÈ[Y Kjìî5Pɹ RÔ<à°\ê&;IÁÄu<ë[ŸfÆ„ ¨v™¡ -Ç %eÊrã”YÒj¢¶„Žc1Œ-æ ÓYñYW+_©«s”pÛô¦µ3žÅTŒƒ;2¿&ž"Ñ„´ëAÁÏ&ÁÊñ ã×\[­ZkÔÙ›Vî8sûýE£vàˆÚÚ‡¾6nôúö¶Yc´RkBU•°ÇÛÅ =xàèþ¹/Ü«cZÓ5šÃœMÐŒ.7ÇÁ›Eó¨Òæ íœÇ†¼³ˆÒÑÜÁ¤ HÞôçþ>côö~^îŒÝ±=u7Ý5ÐùÉž¨öâ.­zü>¹£ã>ÄÏšßãNÉNœÜ¾ pÄÝrÓöˆˆM1Çü ‰©…mr³K²E‰‡ŸS᪉ÎjÊ5Îë)¨[FÖšµér Ku±î`KêE©9 ºA¢ûM©\'q)ñð¶+–¸9uß“H¦Èq。F“”QÛz§Àmôïñ܈gDäur¦%©î9·-¹9úYqŽQt™ŒDj­·äE"§Èuæ™å@–5²;Uœõ€p­£èÆ484‘Ù…¨@óF,"E(üoçP|öpp;BÀT«–År ×ß¾ff)”†ëŠœõ<†3³ïm!' 2`£Fö½Ö©ãˆÀ:!wClHbyåg½>£¿ ²‡f5}âL%ît½l? âºÉÆF¼¶ºöä¾›ØZÌ›œmƒeŽ[ŠèÙˆ}NÜ,£‘uZ@%å;Z¸7YGÑgNÞ†÷–ízc+¹Ô(µš‘zÍSâñÈì3ZA˜{5^÷«2ºŸÕàýýy³»Ìó"lfvaš;§;¨ñ.^üÖ?w××pÄ1oîrò…æìèÏø;þ>8z‡À§‘üo/ÅÔ$âU¸ë¹ ÎÃxþ.:”æàš¹Ôxx‹‘A$y£yÙ¢ÔsNâg81ž¯“ÑE×1ß]ª‹Ý£9úZ)9JtVcÆœ;1¸=OµiZ3ª¥Õ¥6CM]›µ-ãLQÈ4À<á@¢°³-ƒ“²â„ò/Ln#@8¾Á¬Ñ4: ›3't™æ8<¨{w«9UhsØ”†€)} qÔçf†,Ë€~NÈÅŒšJÌžL‚,ùEí“T€Ë{Ì +æÇû{‹mfS²eœÜoŒ¬äÙI¦ìÓÐ&4Vï.óÉc]2 [ô3òiÈH¿DÞVQ…Õ&ÕDé}«U'Åî„H6¦ÂÏ9PvícÌQP5÷Èg„E,\‡$€WR6‹„>‡YRÞÀí&³•³vßÏY X2HnŽemÁf4ÿv”¸’©ãø“üÿÙû×(=®ì:ÜçFÄ÷ÈDâI¼ Ið¥z«J¥RI%ɲ¦%[¶eɲeÙ¯¶»—3ÿÆkÍü¯ù5³z¼zÜ«gµ»[~µÛ–-KÛ²,[RÉ*Kª’¨*VY|‚$H@0$2ó{DÜ3?î½÷qn|‘Èb• µP™ñÅãFÄÙgï³w·¦·—ÀRw'Üô„ïJß°Þ”½ ýýy]ìÞï*ðý¯:‡Hlúßó¹Õ“Hxsk9sÊàÅ%z%ljåœ{å¿g heMÅ{߯jºˆ™VjÛW Ž}Ç|á=<æl cø=Ú>Ä|Õwmª”1¢Ô)XÜ¥@9‘ȯˆ¤t½Í Î&Îä9yÃîa¦D÷:¤ ³¾Ú?},yr¨\z½üÚ;©¿J‚—ÊPê6ÇÛ“þ¹µØuYt¦ÊÙsÿ~ÅI}r™îë! EKC"28þœÂu““÷Έçw¢Ï æ–|küt.ˆc{:’ÖhÏÂ¡È ¿omTHøý­9ËÀ°Û/!|˜Ó¡wrò1¯Ûn ýÐ|Á™,¸,#' ’æT(™¡Çê9ûo‚y;ŸáGš–EÒ0s"Ê;U% ŒË\¡ºó¡ڠϪtvË6w†­e9uÁÃäUî\SüÐRd¾ŽJóqÛ  &2æ Ü™B´ÒAßyt E¾xE¹. l(B¥Ž¿Öƒu'ìK"¡C:J­½˜%ù½ß‚|á,)s [ën¤ëÀŠ6FÍÀÖܽ[#cÿÞN¥Y ø˜ÀÊÔÏZ[s¡iߙۣXžß3€ôíŠîÕî0¯.þt¨Vå‹èñØ÷ƒƒÎ_øT,‡}/ïýÔJì—$¡“º¤{bÊø;Àè›s¼,7ùW 1+7à{‰JÊtY.ÖI}©Tìc‹Òw|'²?Ÿ!t1³Ä[Ttùä»âEa³¼³ÂTP|h­" hÈö)Íùˆgˆâ™€xð9.nãÖ# €(v¹¢È™ r½¼ÑËJ!w$8¬s^9Ã߈a·m0û»7² Ú½pGÉEKo g=$pãd{¢Ü(×Ge|ÏœQØ1oei™{BS€“0Q—Käå)#¡SP*j mòdMÈ y+] r­<ðîã ~ïš6ö\vЩn¸žÉÌdÌ–Üm@I„¥f4ÖfÙm£PŒF›ï[4lrœ Ã~ìÖÀزeZ3Ták ­„W±P`[ knì£ ®_z(^ÒzŠ,K È9$Rºnb—E’$x1K Á"FK=M öØ—ÄÄþé¾ÛcÐŒó• „K“ýd4Eïëï2¯F¥Y Š“Ê;~{ Ã9°îWcÃ]kê2½t4—˜Ø–çòœHœKr ƒQɼ8î²$ƒ¶›vŽzÌi-Y~À*£=v¿uÁÑÊ¢o…³®{ÇÎ+݃ÏÏrá»Èü¹“ïY™ÏÄwø9¼šE4ÎÄ«Ú=˜_û64_ =^Ÿ;ÿ”½ ë‰åçŠ¡Ø ~FØ0sÒ$åÖªeÂýÌ•ÌnI3HÜ^8w›Ù‰ä"r,®¼´zQHÌíaaI?ÏðíLJ³ŠÄðXa˜;Á J ‰-㨛Ÿ³÷–fú.`bR"\{ç4&"©(’Ù…·`ȬJOêÍ.cp[ÔG&q§>ÈŒŠäm”3çð>##éë /;ù1˜ôgÕâ¼(’µ›;2@I@…/}õE¹FþߣµKJè¨Ã2¾´,0#h™ FÓ :2˨5Iý×…0tÓ¿eÝ¿æçTm»ú0Û0ƒðÀÆ:ºf;$ÎõŒý†­×X '5#`ZÀµ³4Žh€)FÃ~à§5oàx~-5&IUûÂ#ãû‡W4&2?ŸË¡¤ðYƾ¬ÌŸ¡ÎÜO'.œBµ 241– áÚ¶1QDP 80aì¹Çбÿn•›ÎIÖ* ”Ž®—n1^ïªÃ¬Òü'¥M¡è:Q`H¸V1öU* âð¯vù`hј߭q§£.÷ ‰;ïeAùAª<‡‡çèžÚ†#`ÏSk’„»n‘çâݸ¾±+çÞÊCæuhïÅ.ßá7qOƒµ[R^ ’ø‚ö:p¶D\ÕÿöeŒ¤ycqy@q.±}+Àã`ôuW§hjQ¯A©X&€0@°6¸Ò1rhçþW® $Ý‹=ïÉÊIW´ßx"TÑøvå݇ÿñp´Å’MFšCÁÀpI}&xæ>“D‘µ³ô*ßJ “!k–™®Ü ‘K®÷]µ8f°VÎ4Î(i¾ºsCCðu0Α¤.}90Ir˜®àNž«m†‡Ê‚ý2sÏ9K\ñr@‘"`äÏrøëšÂ:¦âŽõ Šž«”Ô?-8²ÖÛdÝÞJ"”ŠÛ&y0ÖA@#Ž¾Ì¡dϵ?ÛÆ=‡HûŒèdùÞy².ufµÍ¶!F¥€Z›9Žº±3"LÝÜNØg:È«9lèg0ª±aiÍ^4ª‚±¬ º eX¡‘²²i{ÒˆÃûÓ]>·¯†M2/­Í1ieç ’wg¬e¢{=®ßH±™‡×0 hÖ̯»(OBè –ò¿pcËtä#mÄò pjëåý1› *&6A½ i( \ßVØÜa… =2aLGSckša·¦@fv ØW™YCºŸïÎÆÛÍó)˜\¦B­xfÚûawÉX6ܱE-XâìHñݤ¤§÷7Ý›ãCàÙ3…Ag‘è<÷¥C¯ØåÞ Í=FÐ`Æ&ÖåÆ/b)|KðÌò ûþö^ÇRh8Å ˆ€òM2?ìØLa±Îwà¿Ð rsòþJ”åÑ,2åqmpZ\ñj žG3É6.x¡±ÔaGW\aóÐ÷ Õ.»¤ ¥ÖÈÓj<”3Ì,.Æj«îUIâ 5Gÿ&ÌjpäÄå§Ô's2ˆŠŒ¾É=k¥&¡\ä¦çåó„üAt¢bæVÎÅ,È~ìz‰gÌýÝbIÒØb îgEÆŽ2Í'E! Y/‡¬“0çå‡ÑRO'÷­Á8#(:o"»(|¯/ZóMäÈo%eiŠÅäÉà]Eò¹$¯xdÇd)©¡I^œtwàº&,kóÕÆæÁ˜afJkçôG­SX_Æ7ë èáALlpÎóÚK³@;¥KƤ0uUÙ] ‚ #%`TYUÝËš°k™7oiضÎU\ ” mÓÅ‘8= …éÀ\!¶¿sÑÊ3ãY¤ä¹AÑy »„ä1…DØI,Ì+Ùü/²ªdÙÿ::CB› ¯w™P.ÌŸ­ EœýlU k£¯‰½Ó*ùŸÇLn8ñäæöHÙk Ã(r$[jàöÜ®3mšÍ¸ÌÅ%ý{lÒðÁ*(ùÎ^ù÷!m>¡§ˆóŸMÚvRÜ>°µÆL¿ŸòïÜÜ; rDRî°÷ìÊݲ×üÍuìsG1{ôí°†ï”5"òe½îyëºy­T>÷3K¢9BŽ©ÓžB%ž ¬£)œpô$8Fò E ¨‚ýDޤη)3ÕãN—Wä4[­2qRxåýDGP8ÞImÙÓ~sPÐÆÝvÕðPÔ½â{rè‘ÐßÁ ^ø‚}®än'/Ó²f,w²|W³xhž8ÿ ¤øŠ ê‚Mq’¼cî&;$«ê(bΑt¥HÞW“9‡ÐT(í’{ë:¾Ž™¢;÷ö—'gäCNúçuûY`ãp`ÿ^ ßXê~_9‰œ£! RNRÖo+%ó„ˆóL¡&òØÞHòè¤ôÌáS’rf5c©á9Ôqô.×ݳÁg—Y(æƒ {ëÂ8ï¹óÖÍ`ÙŸÒdÝŸ5´=7`„ecÖÕ:l(©›-ñÁ€§«¡*€é(f@ìävVòEû³”þþ'Ç[ÄÇso$œSì (äŽ% ‚ˆE¼¯‘Œ(ÛÉf3LœéF¤Œ¹Ž“ɱ}’3¸Rœ°11R5+£¬ ¸ÕÌvLŽ‚nôÆ„±Ü1²½¦° +õ$êy¯y³JdÑš²ÀM)b» Œ Õ~GÎÆš Ÿë˜#Ë‚EÏ–@ú–Á`÷v¾Š… É_×ý‹“ i[´²û³I °ÛÏÃ:ÝèiT¬b‹î32GW«6§ãÖsª †î$ÖÈ·–åo})]Q†%òÿ«¨CÊ~½ýš”¢z&è÷DÍfýºkàœ‘4R—lYº RJŒU4ª ÂûI,œa^ukPÁ‚…Î#CRy`ÖÝÀ¡%®’äl’îàCsŽ]½â'•¡r¦[ƒG!‹cUCd€¢¬ØÉˆHH¨'™¿ 2A‹çD “;¡ËÏÑÄòƒ– ™¾ü'’(ºA~¢nn£½‡=K[òÃV“‘~X¸sZ?¡`&á™êçyÀ= ì©Ûo»{Qbû’ü¥yqàgTXrÂs&ðžaDÊ"ºÙ "€A1£PÔ>GÝER(#ëòF±yDPˆ;Ö·u`°‡Ñ=96õÐæ³œývÝšµ550…iwHq*BUŠ3ÊHæ¿Ì÷(ò9«î±ö•Ö—Á2KPÔÐh,Kv› Ó‘iüŽ ôÏüYWÓB™sÃÚ²­<Ïgãû<6Tô"É¼ì¥æ‹`ž°ªû+2´©ä/4×—rª¢%"üI¬´ -&t3\ìŒ4º‚‹h"(Í`Åà’F¡Rþi ÏÃÆˆ±¨75P¶áBlçÜ,HÒ¥G2K;_¦”ae ò,ówóö‘9£«µS¥L-÷H÷*Þ•ïé½·Û1÷l×l½ý÷©fx Á,¼ìVÞ«ÌÞÃ÷ó]¬è{ýqNn;Ö Žø6a†0FŠBTPŒ ÷[u@©PPж_;ýnØRÚîÏÔ ìjÿ=ްàC"‰â•®Ñ5rk3h€)cŸ'âRà¼duÕüS¬ ŠËè'd›{r"ˆ¼±ï8Á\lج¨ i`S!Žöh€˜±èµð¥¨Èwdá|–ØaÎ_ 4fr¶º6©üÓîntÈö®PÄí`÷ç®hñY`€_EråBI:§RSK*Æb _*“dîAŃ2Êß“96.êÞs`¸Àò:“.4ùÞnäaoj 83GÃPa\:0` Ý€˜ &%sV^³ß~nËy‹—ýõîÉñÝ  7ÖñÍñDmžQ{+Súê?X®ŒtÖ:ùŸ£ŒgO&Ív>K»]Â’š¹É>ræÔÑkn8çÁJùq)ÔÞ¡ýurÓæ‰JZ±$&‘9oÒ¯Kb„m<ô6hXÜAc"Û•Â’Ý{}ƒæÜ1JÞq1ØÎÙM4@F]ÀºF&~h(¨´¬“ûŸ}iûŒd ¡EÞ0±sÑ#EÖ ÒI2£ u ߬{²²ºŽ5âHÒW{~  ¶ÃïÃÁuö¥ì»Bzn"0iÜøæáÁG-ŒrC÷<àðyE³7=}Rö9Ù¢3’s¹ÒìÉî8QŠ7Åïó’x/€‘jöÇF åþ[: Ûc˜|ÉtÙ²DDúuÿ­”ö€’;¥ŠâµÙÒS÷p}x,ÓÊÑ@Ýźû«Îçþî38Ìâ|KÜ{Pȱ¯§ŽôØÌ¾ƯQêf¼î>ÇÌŸ•:€rè¬Þ{e/Ìúš©Q!ÑLºŽ”y¸ÅÇÙ€û†iB )î`‰yöÎ~ˆ%…vÊþG¯fÎØ6'lU`‚cëg"NœçXª²Ïh›~Ñű É.;ANnÔ™w÷tŒæ2€”%Ûv‰læ+bûuö ÔtX’(z²ÆŽ¢a„ÆÜ] ¥¥²v¾3rj4E´CœS¡ÏßzÝPŽŠíÝ‹Ú8Ò馳ôgMy^Ý(õÝÅÙ)±-qµ‚%snØJ¯Væˆç cZÚ¹¢ÄΟ¡X+#¶Àœ¶ÛÑm픂<’röÒÅ=X6káHŠëËJcûïÈŒ 5sôæ,5 8eÁ†ÌЀ£˜A µ¶ÏæºòÝéУ b`^3.Ý"œÚ02ÉXÙq`¨ Â5º*ëÆþ*ŽKïÎuAfÞ¨t3}pdŠ”Ý´¿f ÆÖÜøVÈiStUcþ>xsí|Çïÿ½n‰{ (Sä›—¢“8kMo›>}32‰V³6´‡2Ó³jæi¥ÜnUqIc{×w€É…Zî9s, é ý³ìöÁ‘²/-+TYšº²]ºRu_+-P*©c‘ Iò 3_Z×x²ºZû®Hæ¿K ¨Æl«Ö–‘âΑŽlaGQG¾»N,‹ì= )ÌëKf…âµGé= ñ ±4-­äþ¦A–A‰ÙP ŠAFXðÆ÷·AId’ÞƒQÇR¤ºÔª|# `rµeÞôei$–¹™Ì `!êú²0„-   .´Ju¹-P¢nÕ@Eü”„wÒ-œøsÃèÙ‘RÔ²¹ä/ ÕÙf7Êcc…ìü3Õw§ƒà0ÚEC ³\ R"–Ž! ŠG×\Œü"lµ¢4_áI­bÙ…E>‡nHØL†óÑ1%n3v€½,ªÂc?ÈFðTÔJ¨Zàt÷ãC¥!ö%Á`,—fÎCk·.mäP`tà(vâãTΘ‚À\—33wÇéºuÑ#´Ìv;3isw´„ZƒA˜- µfŒ Ƹ‹ò$weÁØ–aw©À¤­É…x™:Йô>“8 ”ä´ì?Eq3Grµ Ú0b€ÁLí`¦^rÏ#¡í„ë®h{G±eÎíPkFCdÞëÊkE?>)'6€··Ìç*Ö¨-.ꬶÝ´­ÞlÝ«P 1&#àÐÄØÆkáÁææ1µe8Ùsª ÍV×Qå7¿ØûÖGw¼¿”®_´$½kv‹€ƒ†¦Aï^!¨Óڀ߃‹°2ãy¨Lî./zÙBÓ7ðY# ]JÒœÊw¯¿Å,» !kD8òAѨèˆGÞý’b€ä~ùÌ]£š;yÇÒý׿ dþÛæ>j»ŽÝC(VnÌÔ>0!ÔWÁ;!Îê¼}²KF»Æq°W<ªáƒª8ô’¢N°¸^„kåƒ6Ħ—BH¬Fþ’—±Á1«ÄBQk‹Ael¥Ð=«”›##kÜ:J†ZOÁ–9xŽq ¨ÛwvóOæöœ/MÇ-8â@‰JŠ¢y™ØÀBl2÷Qú},–r2«–%6ë cÎd3™#åKU)é,•0)¹f0+k<Á²S# 3(Rˆªÿ§Ì³‰„çH áò½²y&¡Œ‘ƒn‰Ä²Jûȱ±•=¯–‰(N:käv­ ¿¾C801õEüsŠã8¶qí6£¶ËF['NíÍ¿Y ãšAEaž}k°>2 ˜ud®a××ÎØ]úàèLýAzÏñÎ(ï“3ûD~ÝÂÆ7ÞuÆUäP¾٠Ë7>ß«ð³ÃMz%v+¤vܳ=íìðèT4i¯òÁo`$‚%_N瀲 ç…yˆK`l“û]EŒR µnuö¿µ'«[Xp´¨yc~«Út‚Tc´ËdÁ®ÿîIj3 2Fð’&22l¿Ð§=,gŽ ¹¨ÃÊÉ÷¬E}Ÿ-~ÿàÖÉaŽ©Çù1ÔÃöÆÆ Nf?;8lO2X°eˆÚãÿÙDòËVÞ  góÅ…L.$2çX7¼:ùÁ¬íШpnϱ£d‡ Ù²Ú:îhjèÉÍØ{¸tî^–ARÊk¤iîæÁh¼5 ­ÃŽÎ™Î^+Žy»ANŠ°Ø¸‚µ“Þ”ìá,Ê¿"vÅ`Éðš'ó…$0VÒ;Yxpp†@éêÍ.˜P¡ógÛIf'Añ…Çã³gM Ƽ6ì¢f`Ñp'©#7¾©„Ÿy–³¾²D’•kBĶÙÂvIÊýéžµä@ÍÐÊvЄ² Å•CŸ»QŒj`^˜9«¦vsÝÈÌ#I ‡Žuèq1Ì…NÇì³?(N‚‹OWü2æ =4½ÔP ¡KO1èï›$©ÔN ë†P“q\YƒLÇ´Ln-ì\rè¢3kp Ö©_F¥a ¦•±zwQQ=«5M§ªbÞ[__÷ Ý+c…=õ^ÍEåì½É+ÒÖξ¥0O×Iê ødÎ×0{9ö»bƒ¤€ó=œþËž=`&öMrξ0ÂèÿÛ›;*=4)íoûç±ýóØ&¾; T†¼ÿÜJ§ªÛÏ9z²Áxªqõ­»»„ÝÂå7 ¼òr…ÙÒ£Âüw§¶@mé-tFqá êØð¨®ä@kNk#A˜Òo(áÞΙßÚ’ÃYdù4âÐ]JAAX#ö8g#òŽÀ² îÏŠ°='Œ+ÆH™U¸oŒQAÀÜ1GkJÄÖó,ZÄÇÀ-}£pôÜA4Ÿ„èëÜóÜ‹” ­»$£‹‹9jç€Réh̾ `(»”wAU|Ž:c!m›0vgŒ7©ý¦á)·ç[Ä8¾®Zø?oWv¸m8[W]~ŸFêxúí…ÆíP7¶ù¤Óº8›cõ^¤oWpôMÛå$[Z‰„?ˆÎ0C¢\^°†dZ}j’"ÚÛ)¦! çNØ©·;iÀÞyö·6Ò} ×Â=GÞis]hE¤X–hbÁÑÖxò‰?ZãÄée0ÔŒ™ÿŽuÌ]¹V%Ôr Ä5H×PË-Œ°‰sO,qî‰%>óC3üæ¯Lñû¿?jíÄÝlF{y­í¬æ¨Áæer8KPŸUoƒ ¥æž[ÓZvlÍÊÛ‚«¹éy7çH ô¸Õ1²lT˜uäÿ{4gd$G®k,Há×(Ëú¤®Ï›eêa‘¤?¯pèNŽ…î5zíE…\,IÉå<‰ª©¸éÚÜnVº­Á‰*2ÉòUÑÉZ[Ëb 8œ™I£ Is'¯SÊtÀ ;„>*€Ê²ÀE{ÿØÎ+DzZwsP¦¨ Ïͳ+ò»:•v1eÑÈ3(ˆ‹aŽ€¼ìýy×f¡ûîYÝ Íƒ¤€Ï̲1EÝʤߒ°¨™R  [è)ƒxœS¡‘R*( çÜ(®C•‡!¤Y¬sQ<_èØ#70l¡e¸uQ4OAPjŠüE$~šù]«^绞ýݺ\ß Ü|®›¿)ËŠßÃO rŽ`е§iüõ¿²óÕ˜LÂÏןÚ]m ‚.¦àbÒû9«¬pæ gj´¹K˜×Ææ»ˆŠÃÙ<2YI`BC@Óp÷ØVQ3Æ>Xf5c¶vÆ1·im½cöTZË«;úwh–ÒÒ3OjðôÿPüæ÷£÷Ç(_ÚßÜ ~ÀÏ’æçi”I(zü—ûÓš:  ~»Ó×A§LþNÏ^Êþ;˜eY”Èb•çG5»tzîrtÞC´M÷Pú³G~æ‘r®tÊ ;þµ¿ºxiÞ75£Chª•`hБQ‰zzõô$ªí‹ݾ€'?¶À_ø¯oãŸüü¾ÎE—» 4 mç°{†eºvqsÙþŸ\ºG¾²2‹(¢X8z ²H ÞƒÇ3µ[÷Ñi‡Û)5Ž`!‡)ü ŽJ-f.ÈcâØÊ@N¢³åÎÀ‡Nϳ<š Üœ8‘†%ÒºÞ!r¤ç0ÊúðI¢¼ lÌZ}e^þÆÜ„0¶Œ³'ÀŸú‰žx¬Æ4¸vMá¹çKü“1Åla¬Œµê^êd%[Ž}rf*?úC üùŸÛE5rû5Ç…W üüÏOñêë sÕÝ 3J6O4R jH’rd¤Y+÷’ñ$ ìŸÏžpݬ €ÂàWîí`î!Ê£àøºi’:8b¿ìúe$ÜŒñl•w^óã‡ø’bë8H~ë¡ûyïºR qîæpî•äÍ]pcU—Úþ;¡ îd¡œ¡‰ 1¤ˆÍ¦4ÜT¼WEKé> uøÉ«c-TdOy+@Ï“аoÜ5 f¬ËÀ¤ ÅB'Š„‚®µ^ç(Ä™dó—( +9þy/ŸUZ6d;¸„º!“Ù£•ÍìQæk ¹ üüʈHZö¥´ë‘² ¥™3:wZãïþw7¡‹)v~˜Þ{Y-·0}÷i®ñåßãÿénÎ[s`kɘ-€¹6Î3µ†—àÎ(HLµ ‰óÝ{s‹;ûâ=zÏ»Ÿ….È2eÄ*c“x>(›¡Õé(°)uþQ`$ûRBó}&h‚­|Bk3롵‘ï4Ì=OŽ_çÄL˜aYËræ =lÀ*ðŠíSðÝÆ„Py@ÈÍò+a\2þok² ÿ×¥Kþ/ÿ× ì.¼ðd7{d×¼»ä‡ø‹ÿÇSLOË ÊÙ5¨å^½Pàÿñ·×1oŒÌd§f 0«M÷v®ËÆ4šÀÙ,׋2±zÏw׸cq\Œ$㸰 ½NƱ5% a(üŒŸU×5,š9×m÷Ÿ/Êk££ BQÆŠm@fgûmÂ}ˆ¸§ñƒ »ûÓ¹z9­ä²v­•· g+&¡£”™«ÊIÇb•cs‘—ÒöÞs·Ë{ƒàßÉ+È}[}E ª`”6&†5* Ã¾Ö ¼»,–vÅ2 ­Lq%í½â!íËws†â×… /ŠÕ,œ6i_3˼Âê»·•žaÑó³®aà&ÊÖ³JYÎÂÌe[73d ƶЬ\¡ 5šÆ­ingô—µi|lî;oÝpkô¤uÄ!šY’ž]w ù$`Šñ5$ÂëòXúËEÿÀÊŸÄüîa½H|¬®,¡‚®0HÎÐïFæÄ(ì cB¬Ì@™Ë#ÊÐVQñ—8 Eô*ÇtwÅ ú~¾7ŸÉÒ âÜ¿‡7M£Éí³ÚvŸTWXv²”»?)wÊ’íåc|€TQøê~W Ø|Ç‚–fwÏàhzýi‹ÍäëÍè–k§POOŠ?§« ìþÖÞù">ö™9^üz‰¯|­l©pûK)9É)ÔH™¶”\­(óàМiNAßyZz€Hú$£„攋p!¤8þ¾“ƺÎ)ÿ‹*j ÈNYîyã;C²×9%ÇpøV  §0•:«H³p%6‡]Y²f;+ÑI£Þ¨n¹ÓofOi6ûf\2F%™fDL”ÆÏþÙ9>ôá%t1Åbÿy4ÕT³‹ÑÖœ:µ‰Ÿý3»øg¿0Å’Ùº9u p¡ÌÐù™S þÜŸßÌ>ÕÞg‹}ç°öÎñй-ü¥¿4Ã?ø‡SǪ̂Ø4|”Õ ¸÷‹ KäÀÎŽ“l-–fJ’y ®´æ3ÙÙªvdEh$%aQ¶Q€rPÎô.HfIbãf:µöë£Òzv{Êg¿£ìB”²-–Ý}È>Ü4+ Úç¦9mnˆBù(“`3m™¢˜"9ž˜$1$B1 ø#4Q ¨(«ÁÛ÷®EäÐÝÃÚØKßš™ûåà´sœ”„Å2šH‚)ÇùJÉÊ<'@ù‹xƒœ;ŸÖŽÃÍ©m/›;nv³sîÔVnŠtœÒù–$¤î¹ºéµ9…À‚{uwý¹¨{Êt QLY%e ÷P#”ë1åûœ”Gý´"ɧ±q2òÖk°µ´yŽš)$<¸¸¿a£Ô>¼×ÓE% =±ïtÎèN ¢»ù&^ñ†G®;ÞÚ§A±aü å•}þ{ÅÝmD1±7wä X*(ewˆkP3“džÈÿ%#÷õb± ½u³C†®6D´Ø8‡ÑÖüÀÿaŽW^,퀹{r;\¹`WújVړΓœMB1ß)ÔÛ–aéå˜ ´Qr[rïܧt…ûÒeÀPb³ÇÑÐU;›È’ ­†¹÷qÂPQ÷‰ê 7ø\Ä ¤.0§2‡ÄE ^Æ ëÜôØÙRÎDƒ¢Ò86p L‡…ÂÙˆÖ!Ry÷•=2cŒ©"¬€Ç7óƒOvF'Å‹s˜^O<Ö`R2TCmŽqgí=*€ïþÄUÅXNÓ&ÄìÀ“X{ç‹xô|cæŸÜ»†JuiölçÌÚDyI~"\?öCMå¯æÌ«%<éQ¨2ð’™!–†ÝcŠŠû‚7cŒóój’ GbX”¯èsyW}·—e¥R¦+mÂGsƉôÍ 6’¢ºéV°Ž¨_¸ðlˆ³y709D7œ…¤)¥¾ 2Ž·3ÅÙ¹-’šuÀ¨ Bn×¢RИÕ0T&¬ £ºXZ–#åÒ±rð.å2‹à„üŒ)’s«d×´èüÅÖã “F‹hû9òfs×+‘]„s>!(’Ø·ˆõ·VNòf-è5ƒ+4Œ[3£Seœ4Eƒœ36wMÆ×|iÀQݘ¯7c”d]J 4Yßwâ›"F(¾}ʈ¤¾k•<ÂE#ÎJ"½áWõc€ECŠKêƒ:9p7LÔQ¸$tX»öÌ n_%DÁžŠN¸ƒ@Œ­!Ô3ܨí…åÎxwÓRˆdá1Äü…;{Í+â˜aôrjŒËµ7Ê„)˜Ì)EFë|o¤°ÆÞ+“6ùï¤]áVyY,°EÝ+/xø‘û.ÿÇdËé)Ì>™n»™™®õm…¯þÃ}Ý×päñ%î{l}'w1}÷iìþ„’ûΡܹŒ“ìâ£[à÷¿ÂB›]]ùHSØ Yi3]¨Qˆå·NïmœHŒÒóæœT)³îI`1|[qŽ@{PãQ&Š`Eàu‹­ÎHÁ6„põ¹מ+ñðÎpø‘%¦ï>cŸ%|Ëõ3ßzç?Tãk_)1ÕÀ²°,´ni­¹K“Žea,×Ð+Û!Z:Ïýk\ Ép‘ß5—ªm]¥Š>q¡“ï#Nìˆ)•§´.uÅdÔMæi±Þ¢ï £¨„! ×q* ‰ºëm©D).¬{Ø;–ÝÄFƒ= vfnÃYç(e*…2ÙD¥M{Yƒ†q?f€MŸñ‰‚a‰@m‹<æx•œ>c’Ðth·ßìâ¾#o\*ÌYñã^|ÿí j!3(.À‚Gš³6…qÞËá§Üu·Û¬“˜Åàh :™Y‰ÍH ibÛcŸ¦ÔnZðË'oÉ‚yr6éÄá»Ò†üv§ÒY·¯ÎÐh%Þ;‡5Ç:[lºwR'­ã¨áÙFÇYN1 Žsq²n{BWJb®²ÎœÎñŠY)ĬI'¡v”‚S;PF¸¾Ã(A˜k+íÒ”.$Í^D™iR&’`´8¾ Ϭ@G©Y õ¼àÛ{€#…ÌZ'ŽE²ïYÁö›‘ÉÍŠ¯µ0¯¨&½&zc5c{nê¬}eÇál‘ïÖœQ׆=ml3UknY)ŽM½u( ’Ê=Ì IRyöÙ"ùï)x2tä¼FÔË QuP”žcŒúJt’n~α;fïÆI–íQ×Hé?í[S³dÞx'hˆzŸZÝ­úì{1g4ìÛyÀæz¦€… )…ØHÉœmEœ¬%Í8J¯P<ìǹ…×ïêºo­:Ž­mÝëœÁu]ï·/þÞ?Å ph '€ÒÀñïZ¹\¦sK¬ñ(nÍÆâøåÿ0Æc#Ƴ5F7_™¨zr ã[/âä ¦0×f ¼Ò@Ù¤Œ³†Ñƒå5ïe­e°.ïyÙ’üâIÀ–lÏý$&o‡3éõQ GL”'M(î[h]¹Æ™ßÝ%©³øŽ =Ÿajkˆx~‚‚©Ô—…ÑÙN:jÒñø]|ï,èܾçÈdÁÈÙJ ’ªÂ3LJÂþu»ß*msà ½ùjJqà`§µÙ¾RFö{ð^ ´bê̹C¶ŒCdý‚÷~ÀœÎnôÜ1QAÎL™9 N[è*ù™0 >>-¸)üGQ¾—k~qk§ïXN·ÍBÊëÒ3¼Ù_ﺚ"t¹Óžm·_ü°'uª50_Â3eà6ÓŠEwK– (Ÿ¢Šï±äú 7¹é3^ý¾­~\ü !£Iˆ©<‚wÎlc“½w5±¨e+Aô#R:ûé¥áÅ£Ô•“ò¢¢º2vW$©ùÐ#!ÄÅ™Ji¡œé¦öeP†Ù#!7+çÊò£Ãmè–5±µ¦„‘d¿A`e÷jîÀn£yu=ÛW'î¡À.s "?ä1JRƒ$fïŒÁ$GAûsKñÍÛ¨‡¤¸cÒW‡Òꎀ Ž:IùsËÁ3„½aËYg(÷2ÔŽ…±v¬š‡©©†lk/‰ïɼ ïñ;h‹_*ÓÅb2Kº bòfåÌÜœsÔƒ¨_e±Ú^È£¡çÌʱ ² R—˞Ċ»¢‰Ì¼ÉræžÉuïg5¤ëŸ´’+ÍÀëÿ¹Â‡ÏÖ(çW±àó ‹ÄźÚÀú-œ:Ó`÷U¤$Ì—ÀLeA( i3¬\»¬b°ØÁæžIRaÿå# û7Ñ{q8îÐô†ÕR¾.$Ê€0_Z£ Ïi¤…u+5E4Kmÿ_yŒ+M ޳s¤Hޏ+ÆåAqíJ .Õ¬{# ‡'xîWéú`ÑÖ0Ìãèl¢SdmÞs–i¶®U–ùŒû-ó#5Ü=6ßQ( Bc÷l¡¹“Ç%ÌCæÉg·µ³còpÚDç>h‹ü®ëJòâö‹H²H(Ò’ùŒè¾Ìʹ €áÙÏBË0¢ 4¡éð9GºÄ| vl ò¶C*gÎÐÖ66ÄYyK(Γ‰&I’k]&ÙtÊk/ôÜï™pÜ“r|¤™&ßZÆf\‚D²ÆÌs•2 $h à—3ÃZ^`x®³Èì·pü¬7/gK Me‡œ—æÀ Kç…e;t/<95O葌 E Ú%¡‰Fy€Š˜!R#àžŒŽ=f,ý +ËzúÍ]šÆ\©YcŒmâר¢1j3+nMô±èÑÛ @¢à·_4"I„¶ÈlÁ‘ê,ÿÝ×L–C÷oã«íó6JÖrÚØàLOÏgp…V¦ÊEû.¸šhMhÛ™#jÁ‘¶ÁZ6Ž]m±–Ë ˆ| CD™ÞCäÈù†(°?wÁ¨ç¼J£ Îˆ¢ ??ˆÛ{€½Aô,lÜ£&ÉxIˆw ›ZG°±ðîK¯€rƒ¾MðµÕR7¶Kç=° ”ØÚ$ܸXààY+9Û5åÔr 'N×x㣚Qfv¢°f.QÛPMçúˆÀzÝnBq©7T ¥´4£7líñ=_Ü.÷Ý‹ùaáë$ÑŽL…l­´Z HrÖ¬ä?TIaö8 ÎM2˜µ³ƒq±ÇâËŸÂ=¶² Ò\¤ŽµjYZ Í+r& #ìºæ$Ajª–[€ëo+”Ê[Ú [Ò 76Ò­‘J ŽÔr ³ᵋšÆf‚؆¸“!q+Ϻø‰=t0ÜyöÃZƒÂ;²’§xÎbE·ŽWÜžL™°¾XV$ó`y»±„2É(Kç\^‹3i›¦¹žÂuW>> Óܬ6¶ì­ÒD£•‘É BA1 æcX¨©s9cñ¹‹w 'iî'ÞO‰ˆŸññÙg 3·s–Œ¾kLÃÞå2+Ÿ –Ÿú$ÆøzPϺ– 0HÒeo”v»Ô6y¼ ͸@‘ lŽÝû$¦WúygöÙ©-ÏÐØš– ãÔ>Ÿœ1uñí£®É̵ΜJC÷Ô>Þ[·¿\µ–»€¼¹çšÝ=«ªÂËø¡TØ‘³—‰\TcÓŠZŠþŸq'a³´FļôXX™dgØ ÞÀäñ4LX"RÎ6Õl´ô@¡ôv8¶PøñÞ¶AòŸ÷\WRþËÚ>,KåÙ¤’±ìUVZ*Î;å@æÎÏJ׺›ãè¡Ôi³Ã¹#wLŠBC÷œ0ëpÀ1ØOZ4ŒECXjcMÌÌ(ØùA3®¿ªpðlƒb¾)$.M‘wà°Æ¨dÛy×(•jŸ)Ýu7Ô|ÛIâ ch¼VùU2õ¼@åû^¶¡žlvK_;•2M(ïì ‹ÈÎÜþóÜï¾µà›z:Ÿ›YpXg#mF¶_S>ü‰Í¢:å‰b«ñ”Ñ`;—˜05vÿ¦\E‘QH´Ž‘„²4àh¤€ã§dÉ1>‹9YI)w.­Ö%N»CÖŒ /øø§4ÊÙU,×φ/ðK€ /6IžŒ…ƽwÜ@³ã-|{xD¬I_w‰)­5´Ïþ Íí/CêéÆçºíœ¹Éá$¸ñ …¬8àa]X7>P"#}«Š¨»¥(´¨FäÞË…‰²8?ÅŽ™gmí’ÙÔmÂvt‘À¯édÉgš¢óNá¼Y2§Á [!µ®ŽŸebŽ ‰ýéö70˜ÓÜ“´ùV9ÆJ’úŸï Ià…3CÂüWÒWd0‰~ œž¤…4ƒä}a©I€Ì|V¼±ó G×(cÍݘpìeMÙðXÿíW3°µ0Ï0—Æœàö$kŠfiø¤6Ë“ Iò#3Ó`A“û3ù ÊÌ%‡cÔоÿÐ/s(ß|RÂÙw¯Æ(Ÿ ýû ª.¢°[ñywô¸V€nÈk²Zæˆ ‹F¡¾#úpH}½ºh\å xç{@w¾ãw“—Ĺû‚Q2…¶ù F™¬¡H¡ @RílóÒÏ´PvÖ|·×2*ƵLý·áíÊåBInÿ¬&Ø4-Mèç}Ó “änÓÜk»Ø••ïPÜ|Ûì[QoÉÛ(MÁxø8·Ý÷¢€Í”Q°Ê•¶kÔ0lAfCø®W,ÝÁ÷­JŸ¥”ùÉ·‡Vì‡$·£, Ä}ûÊi—¤…Üe¹†Íc"O¸áK¨YvÅŽì~‰úhT{õ·ª¨#ožÊÛ>û¦ Á×½¾>Åk$~2úŒ=q+¨ŠìȉÈfÚuc… f.m0bUr¶éÖ²G—U+ƒu §(`mî[n€¯|¹ÄÇ?µDµýêµS-#E\ctûà?aŒE,´):j«×׺˴2 I•ѵ B‡?éúÇÙ,RÈ(—VÅ,UÌêd¬ EkjéÞˆ·‘iRˆVß¡Ã[ÑBÆÊJ)Ëa{rÈØ½,þìÈ1<9æVVÔJÄÜ*Î6w"÷7–\Ç¢ùE¢Œ½½w­™Ãù¥d[‚¤Ä$‘ä~Ð$08BqÍS±·þ‚ûœ¤f €@"³c4˜(ÊÏâLCKb~†4†ãy, ¼p8 Bœ¿(´ !V‚‘ÊV½û1a•rK1ZÍ”1ƒ•}6‘1`¸½ÐX«”•n›Ÿñƒ_9>YÌý¬VýÙý Rn¶\Å Q,©óþ½hç”Øfi謤„¦žÙ¿K´ŠeJ²›¼›-'ë‹+nΩ{.sÚ¹B8ÂÐ(²6¡!hÒ Ô¬L¡©¥&dMÓ€;v±K°2LÛ=Efto6s 8Rºë†;Ê×}²130@É/Ä8ÃxrT)j¤c0ï&rf¢-iò 1ù} žÑ q×Ñ×Ú$Pk¶k­áÂ^$`Ù õÊiï°y‚B0Ù†ZM¸e· EPÞ,‹{þ6@›YÕ0ešÏtÇk‡ùÎà Vó|‚$U ž‡d²xœtXqhÒãš_¾©BŽýRNü¹Ì}LT4EFáÌŠ6ìÏ7Å/Ôö™-dMuïhnÍ6þ‚Lû5×8tÖùüL$óçûNj‘AJá=µ)@­¹‹w!à÷¾4§¿o‰'¿kÓëOc±~¤ `"]ãë_-ñ;_¬°[óÚÞ—M¨¨ƒN~ßzOåhL)˜è2nX(Ês7Ršm­‰»Wdˆ÷2Ü#]Š×~l1Ê=2+³ÀŠ$æÆ³×Ì(¶àe¿ÃrÌŒ…6²#íîËÆ;ž˜E稓„ó.±ñ‹$_‹™A˜2mBðpâ¼–aö@‚Uø*†‰={Z(QžÍ'áó!;gŠòà äŠPç>æ&øü\ÑOá\’¸6 _(}’ØAγ[1d"Cê ¬Ô—M“v¾T¸r›qö€!SXs+fî lîü}*ÈQïœAZÍäHŒ¸? Nò]í”ÐÈÉìH*,¥ç/å2”b0@ùï‘q…ï -ýýánJoÿž Ö¬ƒ»qMæþkvGeßñO¾ÿ%à{¿›­†ÛÙ¦ºÎjÜ ÜVIPDvÃ!82–á½@Ý»ßÕl%>½ÌG…¦FÆYa„‰¶€Ã9Ã5lŒ 8Æn±ý~÷3¶¶ÄÎè©IÃhûž/ä¿ä¼b¢Ë(4çŠ4³ŸCÀ ÷€ d.4 yÀœÅr¢Õ€lÏ÷H.Ô>Žê“¤€PÃAi‰S P`ß1‘ìY°'I2ŒôsEqS8žõŽ—I~îsŠ1´eyXêrq"ìïuÛ½ÙY“8´‘Ú¼÷»T¥~ádwÖ¦Ë3H.éí׋®–±Í wo5ºSm-Æßÿûüµ¿¾‹soarãÙv[_ÿZ‰ÿåï¯awiÀѼ1Î ·îrÚâÝÎ'@Q É›± žƒ†›ü¿>:º,°Au­¹m¨H]z ¤u4$ãè^e›ÈÑË;˜ÇˆJs„È+ô]må2ß”JUãdbvaŸïÆ®ÛÌ-jÝZÍ.Äk¶ 3/þÍ;§A²àŽª’X‰Mƒ0ã gþ*Ý!™Q–äâ“gfÿb¿`m  †"qQƒ"@u"æ†8¾=àId 3`Ï™¢;¯x§HFÈ̪EŸ)1‡I³¤»¿] fÖ¹n À„…f\¾å\ë ‹Îb¶×Ç7b&óN$d€“oë-Õv³$±9(òÿ V"^žºg :6±ðŠfNÙw›T~ «ðýÀï+àøVGwN¾yï5 Aº?R”ùÁ=–É™93ÿpæ¥íŸ.…|´©æ.à³uO´CÛMÓ9Ãè=Ìy9på~; I¯k4pó²Â“Åb3 ÉrÉ)n¾Ä‘%™™EzÜ3›ƒ­> Ä±[*ú²Ëd—(Î&Μ ÀHHdaý=™çÁQ´M¥¸µ[w¡Ãíß­¾¢¸y°ªKœKîNbí5%2Æe*gØå L¸ö‚ýÚ¼sRMÝÖŒÒA….}ÛƒPZׂ#²† æÜ¬­…l©xyu·ï –´“”ZÛbûî¼tµÀÿýo¯ã‡>·Ä£5ØÝž¡Äï}±2 ¨6CýsËì6Áf娴ú`¸54ò7¾½u·>˜¬iIá1 žì¦:nAûÁÁŠ# KѳU(ªT'–gïfi¯Êk(²ï:è²$ß…ÒŽxlò!IœììÙ¹Ô²ÙnbŽØÔL(È™!ï¿ýwí€Qé»(vvî0§.«,–Qw'ÆIDµ7o£™ÁÊÙõ¨H_®dåHÏ ÕW ¸ÝPÖ”¡*€ª´s¶Ê˜(kó}Â4”‚Åwmüô·6 ;ËÕ§ŽfP׌Z¿ö›#ü‡ß4Ûñ™Üy@öZkxÎO]¡Nö]Üš+yRs¿1Ëð•öÜ*{og¶›)ÉûÑ—/†Ý~ò ÀŽÉBð=n?ØÏ·±Ï]f¬ê9¶°&É¥™¢ -‹„=pÜÁ oÕîµ~ö‘Úì  µÇй$¢t@?–JÖѹaXâ<-e\8/%J€K<—#Ì‹ˆ®~ÒöY˜%X)ñú FÃAB¡› KìX cI™¨çJ­p¢CÂÒ"µÛ—æÿ!3) ¿%‰¢à¾á¤Hf‘Âg’BÌ,§s^ÜÊ» •0[˜PX3CÉ-áÌ" 3D’y Þ­ÄÎݪ¨àÎÒ;*º|ðäËìXyà•:ËØt%YT+$/¹.2GrZQx€2E{Ç/Í!¶Z½¸?ì$Vž–®}áó{¾•Ø¢cÔ¾d!„ûyuçç 8ëêEeväÉçzÈ£ì9¡ tKO™4F™<[#!(~¢&„ â•—OÈöá(ø8{ºS nߤ,°×é÷¥-…F^¯<§»‚d¦ÇrŽõŠÁŒï¶YxÀ¨ nzÔ¼\‡ÈE…ŒuÇ«u0—-^W´·1 Â>Qßûž!2öþþ b΀¢èL=ÊÂwD¤VJí>@;©ï^çŒ2£{Q8‡®öüêŹy¹|ÔY.û‰'ܤ֎-Õ!`êÖ<µl˜P(ꀱçÖ*]‚H£˜À¨4v¿++·ËýRÍ®Yÿ·¨m(¿ôlíÛõjüqMוg£Í€ó‚ HZúÀÈ«ËH‘}f™s¦È_ãfþÒDðvk²ñ¤ÈZ̤± •_Ô;S!°¸~[Iù€ØÏ4ëöI[àë³âl4¬­¼2vì’Œ@â¼—˜Yˆ1Ea~¾f²ñÝ|ž¡¸92–ˆ óÔ2Ûg>5³µgg Rb•€¢Ä"„=Ž™‰3š¯+ä´€g¤óB’Íz<ãËò²ùCÞr,€6A Éïü¢_2;`!#'‹Y2¦!R°ª”½Û©{™mbw?0™ˆÁolIž‘ºR†Ùóþâ!}–ºñuˆlÿEÖF˜Mrï W ÅuÎ’:+;ж›­0IX‰mJ" H(Æb(d‹ë„ÕÉ<î|=ÌVb™žÓü ®v¹Ë¼sˆ•æ¸rU,­‹ömUÉ&d¡•< Œ›ðs‚…8Ûç¸{f“¦s°5 jñ\Ðèà=z€eÌDüZiÒ'ΉþKÄËY!„ónÈWb€)Ãù2U%¬UîâÀV+Ë!_X$NŒ‚iƒFhѦÔM9Iƒî‘“?£:˜%üf‚Œo¯#^õH $&Ž"'«‹‚€•­B|Y¥Bgœ[ƒ~qª ¯þ@yA¿ë(¸®1áØ~& ùH+9Êœåíyz¾ûò^Nbtë†é‚»|#¿ëíÀ‘ëijÇìTþ`<…!Ö~×ÛÍV5^aÅœJÖýmµÿ¥PnçŸoörjüÙ-Ž\Dýâ.Þ¯†Ó˜c ´NŸã sä h'ñ’L˜âg¯PÛ% ìSa̹0VìªèÌüó­ƒÚÚóLùçÑv%µF7¬ëº‰mµsI®ñP8öC`xà] 3SGXZ{­J´2L[v•˜:à Ï¿Tݹ.„ëß¾8í÷0­Ó˜–À¤bŒ+ààaûœ)')0²ö÷·¯( 2R=Å&[¬öÖª[ íòpÜ=Óø²O\(2LSÁ *l³Œ;çHQ ÌJå$wá܃aiL€%Û%,7Ûkíey7“ÙèÚ›Wdûr7 —k r[ã“ên¶UÃݹ1àÐÌŒ5Úe>9`æ[c Ýó˜bMæŽ-¬Ua€ÅÅ0ùšÝÅÅ¥?ðï‰ØF€ö玢NVàT¶ê…Jƒ$tÞ‰ú­Òý™!‰uâÜÃ&×~” UÁ5 Ö”17à Ã!Yc h àtVŽ2Ž8,Íd Æ ‰%8e²„uI¹"*œYÈÉBúˆ¨8$8`Í23hñ~ù¬fÎÒ<>Ç쇣)dYÅ p< ö¤˜Å»Û¤ ¨ ¡‰AÀã÷’T ×K™=iˆ ¯Q¶©ãRS¢7ô5Δ£Ì},lCq4ÊÖåú%3dî%F‘½·xñîµð;ŒÑ^vƒWÎe$Ï/ï…<·“ª€…á~ê({u¾«3V1² @hú²´"wxîœóÖK¶Û­-â:ðR¸¸b”#w(e–AÚºÑ ¦)ì³)X©-†K[¸Žì ˆû­(Ê6D7èÞ‚2Jðí6è“Âm ‘œÌvÓ{ñëB§oþZ÷?–èæ¿¡Aä³d¥\Býõç«òX'[YgcoYfòKå¬×m®cì”W;¦Á£VaÎog~¯¼tô`øƒdز’©ÎáÈí;·¾ Hò2›¨“\5Þ\Ž+¢ëƦjíØ ²úºµDíŸ kÝÝI¾ÂkãÖʸÆc½"¬UŒiaX¥¢è¹gµ ŠåY`4²’½ECh s°Çj÷¼sÖì##v½£PÑ DVõ ‹Ä13#3KÃÚÀÖLPj\8ÆÇ“¸V{V’c€hFI`‰6J0Œ*=‰¥HÒ£‘¡È1G²ZG söçèãàz¥^ÈɹŽíõY¿ù &Îþê‘øQ¾Ë›ÙÇ‚QnF¬û³‹ºq°îë™ ç š3¹ˆ9h_›Õóƒ$ ²|rV†<¼¡ì_t\ȵz㎎$ïæwZæàӼϖò™(šß“lÉûޝ-“¾¿íBÛù-XÁ3! © ¨Þâû÷å;lÑ"y]µ_2òšÌ„„ëŸÃç·í"ζqæÇVÆ?Ϲã`Éâ>•Ò)Jÿõåæ&`-핵âtÎ^eì³®Þ\¤ò¢rv píM…em °Ža¡ö¼è6ˆš`4.€qëf>—È[ôÀ‘–‹Ô‚5Õ¤¶@ŠÆ€øó :$•.‡¶aǹAúycŠ»‚¼ÁzíE¥xìžÛ¯€Ù¢Ôu—(”ªè:ÇóKÁºd¥-–ËB·çÆåü¸¹KOÌÔž;rgª ¼á_öLQÚ1ÝH_†Eð÷«‚…òÁ‹É‘©ÙÌ-­›ÛB›@ãEm¾ÖÍ¡y¬”ç@7Rf]•dÜéÊxÆ-š+­åó¤dL,HZ«k%a<±÷®’3n]*0VÀ¢2¬Ñ²`Ô¥qnb+7)ÈgÌÉК-“Ó=å4PØW6ï$íb'Z€M[@6)iÉöï–M*Ü,µu“ƒÙŸ‘ÿ-›°1@6d[)+³sÀaÓÂ]Ó&¸×¹c)dmÛ¦–²Ödª»uÇ&¢îλÌ'ûÚ ¬+sÝO¿‹3^„¢‘aæ„”"ϬB(.Y §Œ†€Œc—×1oç”(NmŠ“‚WÐ'0˸ǟÓI°ƒ0»Ái>ýþžù¦€å‰çÁ$0K I)¸‡Ù Yj•°H”ÌþDY¬ÉÜ4?D©Œ­Ï4#õˆ\ÌüµÊQ×^š¡ãl û“.û¹oB'-±^Ì3‚óH©ˆ‘d˜°0CG™è%hbA—‰V|, ¾¬%èÄûÇÞQˆqìæ ÏŠ˜©½H“Vþ=ÊÎ’füHXÞäØs–/ÞªËÃߑҽwàh€kSƒóŽ)å„å–¤Ýòc‘¯U±ÿÄ+Î?÷lÚÃuS¶Óî6X¸l(7øMÀñs¦{ÞŒÓü£Â¤‹/(Û‘ö2`<0Z:™Át¿a\p4±ó'kœ|‰sÔ8tDãàaï*¼þJ‰—_,ñÒóæµçðe;òÚ“Žû²:ç@vâ¸Æ'¿wŽ©12ŽYËæ«— ¼q¡Äk/—xî+UP(‚Ó׬fàðQ‡©ñÀÃ54/¼Xâ×s„Ù²ë‚ûR@x…eQt pdÁç‰cÚ÷c5^|±Äs_/¤“RY ûœ¯qÿ™‡Žh¼ör‰ßÿQð|i]-zôñûž9Μk°ÿ ÆÖ …K¯øêïT¸vIY`Ã8óˆÆã_âôƒ f»„«—Þ|µÀ3_*í .Eó™Gj<õ‰§j°ÿ ãÖ Â |å÷+¼ô|aÖs/Òžt°èìÆï@㡇œ9×`öDs4ØÞ!<ÿ|‰_.Â#ËHRÓ±'þÖ˜–„iÕ™K¬€ïù¾>_ãs5öÒíZ¿x¡Ä+/–øÊV¨u·VÜŽÙÉüüÜ,’66€‡Y¶kîÂË%~÷ #OæhgËZ£²RNƧ¾o'>¼Äƒ)âk/›ûù?ÿçÞÙtÏgÞáY»kÓTÑ u’h¿ðJìáý¢i!æ†É=¹’”y¾Uù—"[÷K»©¹¶F) T Xò)Ñ,WM”ë¬ À"™Iaç­Êäñ;Ù®\œÄR"6Äb^–éÅ&RõÈò¬Kß‹‘cf,÷=B~÷½Œ3×-゘Jã(Ìlâ˜ÅCÊB& Ÿ =Mò’ š³/÷f~:×éß!3FÉù­QWצÛᯖoÑÑ#8dŠØÓÿ3Fìʪ.U¼€uÁQˆ¾ßlÓH¸£ý½#Ak'ÆÆR<îŠØ,Ë’c‡(Œúf˜biJ×5¥$ŒÓ}½f`Y+,4aÖꆰԄE£°h5–²• uwýë^lð[Œ1r¶Åi”c¤UÁ¶‹¬Û¿ 6ÕH6â-v]R|>¹¬uÐ+G‹t$Dg‚ë”ÛB©Ö*`m l¸ßàãŸkðè§,6Îa±ï\(º}£­ xóË^ýR‰eÞdÚù—RŸþs ¬aìÜ÷=Iqµ«_éÿüïŽñÖeÂö¸5'lÍ ; ÂîXje™›+C„qA—„µ¸ïã'fO}d‰²ÊŸ¨ëW üÞçÇøÒ{’+Ï-Ý|OUýøÿô>¹\y_¾ÂïýÇ ®¼Q×ÂÉ ?öÙ9~j‰ÓÕÉϾz¡Äÿø?¬á­«ªp¸Áëú;§´Ï~ï?ô œ?_c2 ÷øïöáÅÊ`.Æ­Õ}l‰O|z®[F£Ý‡ç+üê?Ÿbë†òdpæ¿ä'wðáï]dýóÿÛo¿VàØƒ ~øçvÅïyåk%~ã_Œ Ëh-¤¿ë3K|÷g·ûKÿë^,:°ÈaÐøé³Ÿù£ <øXÝ{m.½^à·~u„çŸ/ÚUsí;w6Çv`L+c×ýƒ?²À®qê¦÷3Þ|©Ä—þí³Û •bL ¸` >ÿo¶PŒÛ'~0˜Ã+›˜^[— <ó/Ö°µ lÏÛKàö’0[rüºÔæÉrà ÆOþì =÷Ë/•øí/Tøµ_aV“i0P7n߸@š÷ï´$sß—Œµ ø‰Ÿœá©,qäxÿ±¾õj‰ÿô«¼ø|……m44Ì­Ä®ðäŽç_âãŸ^àÜùûö‡O©7.”ø×¿0ÅÛ—Š AáöñÄÉ?úÓ»â=[·þçÿ~/¿Vàö¸½¶„𱻤–™]Ôh CõŠ$#˸¾Í|'õ,aRy.žŠ:s oV'1eC'ŸëžÏæ/‹š1_ZG>ÜPC•¾";‘ _‹˜†ÕÄ=¢LæQ†ecJÁS› ÎÅHÌ …qV³X¹k__é+>ŒÉ-ð`n‚„ *᳂ù –ÒªÓïËž{dö3wÙ…ãg í±P”lO“–mtƒ r×VºoýSÊØSÕ•qŸ¿·)A]÷¨  ë¬CF¹,ã‚[™†ß‰gêg„bi sF4ùk[²›GFŽ'È!ý"–=í<{IGi® ó>€Ô(×ùÀ¤o59Ý €Tc ùsv¾dIµFa#=a¢Ë!‡—!Ì"箇xÉíCá$d0±i} ì³àhc |â<² ½ý•¯ÿAÙZÒÖšÚw1pú©ç°†.¦Ø9ö}!]½{“Ïâ­—þõ?áöØZ; Âí™"§&›böÜ0(„II˜–„Ï}nŽýã³¶øZNO¡èb Õì¢Xl¢˜o¶r¦¾2¯þó5ÜÞ¡ÀÝÎg]þô_ÚÆ“[´²¨z| õÚI0•-ÈSË-‹MŒn_híÿÓ/¬ãõç*ÀÆaüà Ǩ±~@ÛrlÚhû"Ôr Ï}­Âßù;ë^ñî$}æÓKüÍ¿¹Ý­Ÿb .&hƇ0Úº€×_)ñ÷þξÎþœ€frû½B³ž…®6 ‹)F[ š]ìl)üÒÿgóÝÎî~z~Èœ‡ÅÆ9,§§ÀÅÔÌ0Ú¾ˆjû"àé1Å'~z7ø>ÒK¨ú6Æ·^é¿ýO'xçõŠ€G>µÄS?(o·Ú½„ÑÖÀ¿ûS\|¹h™¶ó>ßó# |ìsÞõ™žB3:VæúóM¨z åìÔÒ¸Ä}åw+üÊ¿˜`ÖiØ¢¡ÎΙ_cTŽaüÄŸ™áÜæœéjËéÉÄ…±œ]C¹{ ¤kÌnžû ®¿Tš"e¤y§<ö'v·OþHðóÕöEŒo½ˆ+_¡žwá®n¾æàño¼ZàwsÜÊ ÿ쳃Gž0÷T½v¤k³k­ÙÉ?øù5üÚoŒ0· icüŒ„PÀMK`}Ä8}Lã§nÈÆ·^Àc] TÀ¯ýÓõvÅý:p°Áÿ…œ8[ƒU‰Å¾s¨×N‰î{ºÚ€®6P¯Âhëªí‹øŸÙÆþò:Ž?ºÄ‰Ç–(JîŠÎõ³¨m‘×îßä(Ö®~O~h‰c‡ïÜ ÌÉdßøÍ97õÔã¦àœï?Ÿì×hëx¸Æþ‘ù™‡«ñ™?:éëL-×Ï$?×LŽb|ãY¬á>ù¹9žÿ­ zŽ?´«»‡?€.&˜ï?V%F[ðð'Mq½\?Û‚i.&ÐÕT³‹ÑÖÜÿPƒæJSßUã¼G³C Î “öçG[ðÑï[bó¢›qaàûj†žª»ÏÛ8—\³Í£Xì;ׂù~ï¿öKÌjn³‚´½ç E(‰pú~ûéN>ЀU‰ù§’ëÖž»Ñ!,6Îa|ãYLp ÿ»xùW§¸u±À?8ÇÆÉ£|›‹ØÇñ纟¨ñèwÕø…ÿq“5Æ#O˜µ¹{ô{ºãÞ¾m8<ñx/üö% ë<§ítãR7-MSä܃õÿtûökèbŠùÁ'Íý’YëËõ³íùü#?µƒq|íK#+±c<õ©ÿø'½5W¯lp{}¦'1Ù|븆Ïüà_þõIÛH½ï„Æù+·Q”ŒåôæŸL®m=9ŠñçPáþâ_ÙÁÿëoï73†6\ui³¯j;‹UXsg„Xû®`­£š÷5ó÷† E$usvÅ]Ç,#9æMÁ¾½}T0r´Ø*š£L8ŸDšñ÷ƒ3òµôeæB•l_úUOO¢ÂÚ;¿‡‡?¼Àçn*üá¯OM÷ÛÊÿØ_ÜÁÑ35tµÙ'Åܦä’P‰ùþóÐÅã[/â»þè.F룵Ø8'T¸ŸmF‡Pήá‰ÇküÁT†ÕÔ^¨eƒª8{¶i VPc²¤¶n(¬€³Öø‰ÿúv[¤.6ÎeÏS‰åúY”³k8þ@«S`ÿ‰Oüa æžÊž‡åúYŒ¶.`ã¤3ëɹ¢…#G€‡¬\ov0<–ëgQm_ÄÉGj>À˜ïšbñ»ÿä ÷[€0;ô‘•k€¹ã£X{ç‹øÐ§—¸ø|‰ /mÆ3ÉRdòŠþ컃õ †®6°{ä"8ŽÏßìÐGZ6èô÷Ìq⣄}Ö¹®B3>$:ØÕ㣽ÛÖåF·/àÄÙ-œ9Ó`ãP¸ãýrkì¾£ÓÒÊ2˜™>–ÝYöèä±Õ“£˜|j屺ó©‹)&7žÅgÿÔvÞ)qõÍ|ïµLÙª5ç®q9»†ÓÕx}b˜—ñ˜ñÉŸØÉ‚#ÿ×üà“PÍ.ŽßÄû»øÅ_œ`Q’‘Õ)ÆRÁüÖVj’œÙ¢.| xÅ ›ES*„Nnþ6HzïJlnãëæÔÕ®}~"1 )c"÷¾g¡ÈA†Qð JRVbÞÐU”1nð;úȯ²4ão&."…î§.Y6 I«…(û).èYvP ˜>XKê«Q0®oÒÜ_üöZÆ{ÇJœg%IÈŠ"y#‘™·üùÙR±ÁCbá¾ûçØ„î;HrÐZª]ô­)ú€]kV:0Rìç¥ó7rV1 5aÈb·¬àwþ ²,üÚÌ…1â»9^á@ÃnÑp3,q¨é÷ Ú=î9¾ÈÆ9°|öo†ÚØElRa­oïÿìÏiSÈ‹µÜjÙ£§«Ä¢¾]Ð¥‚Ñø;€0*€IÁøì÷×½àH-·ÀåT,ö¸˜`vè#˜^<¹Äk¿;A]PöݲGRa\,6Aº†¶ÌˆTð‹MŒp º˜šmd€QÜÇìΞiðõ/y^áIì”êŽ¿ÈØ¯;ÙÓö …i œ=W·Ì],÷ÁZ°Ohœùô¬=¦ˆñ^Í H—ömq›pöûg¨Öذ=Å3S ]n XlâÄ)w.xì‡æ-8Š-w}bàèŸãÅÆ9Œ¶.àSdk¯O±´Ò0Ç~â/ìb}ÃìßìÐGÂ{¶™A5»ÙÏpÿôÐfû™»‡>Ò»tµÅ îdŒûö1ŽŸîrrÿY é›J¬U@É@U÷>ÍÆL  cå=-á¯l·à(>Ö!͆år ÕöE|òÇvð…¸ÑJ8‡6,Üš[?¨q`bØ£óÿÕœ2÷`8jÁÚÆ9L¯?‡­±6"ÌÆ\æ%0× VîOv&Ì•¡¨h‹m¡8tóV$1“‹À±§y¬J^-Äí;,Ì{µ¶éL¶þŒf^ü‚0ØEÅ*¤ÀT’٠ωR4uˆY‚ ‹ÝWØ#,rjÀrŽ…_ Ç O<Ø/u¡cùX.³³ARf gæ2d¢$t&öÝœ/bV‡óÀ86Çd[ ù\Å&'œ1z€dÛÛ“½l^šW‹çb2yZCª°=†ÌÞÄŽ{¨SòŸü‚­ Fk˜P2¯Êx’¯‘ä8‰ÕFšCކxØ&`š‡ÕÖï/ÆàÄ&>xŒÑêCÍåÙÆBwÕ‚Š¥´Ùí 7À*Ûwîù"½~þþ+Ù,f/pïÐ# >¢±vT‹…,qÉæWÏÿA /)ÌjÃ9g9íeÿ3J"T%0)§N4øãvÖ%.ÄÆ·^ f1rèftÍèÖlâüÇxó«#œøÐ÷?µh] ]³’¢xóýç“Bu~ð)”o\L‚˜š ÊÞ}i劰$ÃÈ9àé 5NôÈ€aAÆ6*¹JÍ ¤—âϺ}oh8[ãÐCK#1Ü8wÏî+"à€“­|jp,›8t\c²Î8óQ#ã‹×ÔøÖ‹íP_¾ØwÕöE=SãäqÆæõ00øs?5Ãñ3-Ο ×ì»Ï´Ä]ÿÙá$×s¾ÿ<ÖÞù"tµû¾çÞœ7+Ãã9‰×5Y`LJ@i“wT:742ó{£‚ñ§ÿì.î°NŽÕ5ªÝËv^îhv­Í÷Ÿ7×çô>ôC»Øgïb@¨–[+×\IÀ³5î{ܰ‹»[3:]Lqú¡]<ô`ƒ— ŒJFÕ˜cu†Æ*œwcÄòþöaíu˜9rì¢@5̹P9Õ}Ź<1+bÍAÚì—¨4 ã(Ów®ãxîGêð ]xDų:ìç!I2¯xþFÌ&ba4(²•‹öä׈˜žx?’Τ^Ðï¦'u(áu ™3ÌB$ýbe’Â79º&ÔSYúëÒ­‹\pP¬Æç,žíŠš‰ göß+lûŒâ™ô-0¡h­IÆPGèï¹Â‹Àß›oä|æYNjö¶Ðè.OcP!˜1i‘$¤{­ï3cšƒáàP†‚ìÔ½ßaŒîÕ!'Ï˨Â$sί€`†0F¼  ¸~ÒÝ»9ï‘'jŒ6€µc+’$jÄ5¦×Ÿ†jvqé…ÿôoŒíöºMÕnþavÏHuìÑOüÔ eeä78ºñªÝK€w LjT»—ÀåDdP–k§P,6qàþמáôw-lÑ÷X°ÏÓëO·…ñÖ¥õŒ09¨1=¼‰ÉÍç¦É1*Î  ˜]C9¿Úï»G>‘ÈÂ\'ýàaQÑe¨•ÎDyyO.O'´Èš9ìÞ4-mªgݾ˜¬b N~lÞ2"C$W+™E{þŽ}h¹§íºýo*<øÙy[”»ë—Û— ì;Ù`rãY̨™¯z| Õî%Ü®ÆrËØU7 ãÔ5ú®eÛ5»ÜÂbKa¾EX»O£À&&ï>ƒÝ#ŸH@3# ®[³ÀÈåâ}*w/· Õ7qŸ]Øëvý­[W OäûÜkÖY­1 Û5œõþc×øn{>ãf€¿Þ3Ö'u›ï?éõ§qüü7.–Àý5Ôr«Yî.×Ïb¾ÿ|o“Å­»>Iª’&G¡¶/âÜ#K¼üŠBU*›Yæ¢ZW9¿àŽåoÄ)û ÉÁ"™k—Ô5ÂÜ|5åŠöRºýðÌÎνnX.Nýb”¤Y›ˆÕMä»è°0ÀJ™Ù$ß™c@äÏsHy’zÌäPšQ‘H{b縌{Z"3“Üêe±P·Æðè±·“70:¦}Åîd¼—‹òžã‘oÆèýF+û œº,®ðìIžUysÌ­Ùs‡îY¯‘†³ïåasôÃMRìK³ÔÌ0Ý|Æ{—¾ðoªÖ¾Ø$ß¹MÙb¦h¥uÀ§?»Ä#OZó„]±Um_Dµ{ º&<ûÏ7pûJg—xòOßFµ}Q,Èu¹°~´Á‘G—Ø8e]Á¼ý.w/£Xlb÷ݯ~~Š›mѬ€§þÌmlœÜB¹s Ëõ³aÁ6>„b±‰Éæ3]!¹¥L§|çr\1xÀ¤Æ¾´ê].Ó§²RÀõ7K45µ f·ª £Ä5×Á9q…ìÎ;EËÅÇ•2¬ú娣¡ÛusU‡ª19¤[£‹¶x¿ù"ŠÅ&f› ¯þúÛ× œøè÷ï£ÛD€ÔŒ¡Ú½„£g\yndææ à±O-[–É`m].ðÒ¿Ÿ¢Ù&Œö1Îÿ‰LgÄðVm¥¬£aãÂ}ªv.€$÷뵯ŒVÊ^Ý5¼ùv‰qi –…±&Äú™ê€HÌĹõ~ó ³6Ž=µ@µ{ ªÙ3ÇÆNoâÒŽÛíÄ¿ªí‹ @r×w¾¥pàìrÏë®Ý‡j€“÷7mÀo©ÌÌ¢R^ vœµ"…ºæÒÚo£DÅdÃb jsF(ø†˜qÓ=Ö¢RŒ9ˆ­:AžÌ6Äf’q€½}³/4c¸ „~" mçmróCÓ&ÍI,FÌrI¦ RømÀH·>˜ •¥ оÂÀq0«ƒ¼-7qz®‚C¬’2æ11Fñ€tbÁ™Ù2®nÙx£'œ˜2CMñ1rN.ÈÀÄ=¼!X†áaàh@™ì ʹ„î"^=·Jp誮yd˜°2=@Â}™*UòhaÎÏ»ðP”ö¶è}â‹Ò{;~Ò8÷I߸ Bîc³ÿÎrS'»ÖHXŸñûuU@=3–Ê况î5nq’”¦œ]Ãøæ³ ]ãÝK ¿õ/+\˜Õlç¨u“coÓ8·)ŒJ>ú©Žåñ;ü£Û¦}ù߯áöƒ"n^¬°ùj…C-QÌ®%€ÍíçÚ} ?ì˜3Á÷Œo½¸ô‡ãÆLãòޱñÇj‹ÍlA·y¡Â­·J¼ûJ…ñ†Æ“?u»•ÿI…âö e€ ›d.Øóô™¼¼ÎÇ·®˜°Ðíw~ýÚÀ}gjì;¤qó­·ß,ñ‰?wûO›.¿_Ø;W=§,ËS,6ÛŸó­ÎIר½®0=²Úe¤žž·[,69—›÷YÜ&y|Ù²þ93 xéßM0·ŒÙÛ_áÈù%¦G¶Di—£ÓŒiiò{Ž>QãðéÆºü صÜÂÍ˾ñ+S,·Ä­Ù"l¾RbúÝ ”³k)àµÇ×,[— ì\U8õÉE/¸ÜºT 1ÖîëÎárN¸öF·ž¯ðêW*37„±+ŒUh”uD,ÌZ:w¾Æ¹'R Rήµ³NÏÿêv¯ ®|m„ó?¾ƒéáMŒ¶.ˆ,cc?¼Ä‹ÿv§ÍZÚz«Äõ—Fø®ŸÙÂÆ©:“î^˜ßRYÖ’¸6 틨§§ÄÏ÷ •2a³Æ.œÚˆ²ì q”ñ<(ãâSšuI+¶  Ì Õ7J@q¡ß 2Ÿ¯mƸլÈò]Õ(5lˆ–Irï…\ÎŒPŒ'¬ # |ÊIêrR©¸ó32ˆX/î©Q Ö@ŸhQÏÅ–çHÙ/êã " \‘•ÉåB‘Ì<%@4b’$£ŠÀÚ]¦b±• Àëc•$ÆÈû,Ê­1JW|âcØ 8ŠïË{Ë Ý»zš™Ð4€*xð1ñfé+V‡Ú÷â¾=ÐNýÅ.Ý Æü8º›ã¥ľC†°g½ÉàÈu»Ñ{½D„An}ROÑð§5hv¡ËDbþ“϶òË/øÝ_©pãc^5·–6ódËŠÒ›»™À'?³Ä雄婶/‚tÍW+\iÁöUÆH Äÿå ºzr,¤kì¼Sàê×Gi¡?³Î›ºÁ#\¹ÂµçÌÏŽ{¬ž}ƒ…’€Zºl¦ÒÊæö­çåu>À!»†¶ÞUØÞ™ ¥¢›oêû5öò¤ümÇ38óýç±\?Û²XåÀßâ¶a^^QN\ctóÅ–åm]h¥XœÞ¾\àð£5–ÓSØ™ÜxÖ€ÐW+ÌoÁÃx¾eÀšjf½.„¥b” œzjé1<³€á¿>Æí›¦Ø®aþ¯¿^àÔwwÀTúõÎó.þö§?5Ëß#vý<÷/×±³t§lUjë@½fó´œ,Sú5R@¥ŒÄ«Ö@aÚ @øäÈ@dd¯ã ŸŸâÝË%J2¶ç;× ¼ükkøÐŸ3† ‹õ³©•¾eêöŸ®ñò¯­áúKUð<M"±Gj¹…ÉæWÛû£Ú¾ØZïǬ5ì?¤Q(6ìQÁPŠZyB”*G”KÌi礹Ù€á"’¶u5#{ yŸÍˆXtß«™ uÌbdfŽûCÔÿ¤ç ÈaΩX U΅¥ÆR üEy)”{ÑE{,„ZRfÛ,°Mœ‰Â68:L+ ÓP ¬[<ŸÃ^B6™W”*¢dT ‹Ã䥇½$ Eç‡SǵØã>ëÖHiA%I‘89»ùžºç^WÐ< v3f =΄Þ3…qwƃB7yÅpÏñ°½ÉBbÜíVøžlåÞ€"ö\}Þs`$šipprˆea­pôo½ŒQ»žÍû‘ÞA׈W\@î9\'­‹e¬ñq¯ÂéÅb³-€ã_ÓëOcýêŒêá•ß)ñÿ¶Âlׄtº ÎZ» Kó’·Î¹ÆÖÛ¤Q |ø»Ó"ÖMpùËãädl_+v% 6t¸Ú–YÛÝŸ!•]+ªÏ‹aöÖoróFNRWÙÿ6Ð÷àáÕű¿þPíý^ 6tꮥ‘^)\ùª|·^ÄÚ;_4_ßT+ÁßhŸ±Íö·;y÷™–ºöõª½žkï|åì–;Ô2*>»GÍ Åb‹Û„‹¿5Bœ{²óŽ Ø™ÞÂüá'àmAÛ[ÏU¸~¹À¢f5°kHw›üÝáÀj37Wc´ŸW^·Y Ìj¬fÌ–„y ,íïZ›ÀW0= !øÇ{ëJa¤f0Qfvo\ŽkðÀc2QÍ.n]+ðò'˜×ŒÙ²cxo])ðÎóæºìý3fõä(ŠãÀý5îLî’²‹“›ÏA5»¸ñZ…çþ¥‘Ä–;—³ Ò¾ƒÚ8iºÀY° ðvÁ­6 ˜£—u¢áŒ…ºâÛc˜É>OíÛË*”³6¥(ƒÅû9²¹\â,:E²2òç|3a6'hRT4ljâ>ƒ&tâHr¸#o¶ òÌÅ3.<¬½-βdzb‘lÏJ%šï"NI$ud€6 à-`¯(‰7FjI>d¼…? †I…]ßXÊ‘Æ7¬‘)ûà?½Æ¡co´ŸAàqÄœQÄT‘‹õ€´'BBÝË"t¯»Ý01½…æ^@Ï{\\ïù³V èó]îÄ·aôþ2F<¤³?[¼âÛyÀ ß‹È Iæ7·ôx(*Š~ù3.Áf-p¸qAá¹5ÂkX¢ÖŒ¥&ûÛ ×Ú€%ݦÔ[YŒ2ÙJ£xà“šù•`Fhv ¤kl]* Ü 0âÍ?ˆg$9óOW­éQYîV,6¡š]Ü|«ÄöæÞ§Î˜¢°u\ ”X€Äådå5× hÝ×ÍÐÆ ŒdÊ nçÿj ¯ýÖ/ý»)n¿]@-·pûí¯üê°ú¥0ß5ÛTxáâÆøÆ/®aûŠÙîö•o}iÔÎù繜]5ôrfIዎû°od÷=.Ë+]!þúW+j`wÉØY2¶ ]æ²c¸vìaŸñÀ¬fÌjƼ!–Â\3Mh[ëþ[Þ/ÍÎH÷ÇQa >>ú}†=ªÇÇ Ríšc}çb‰…6öØ36 mG;KÂ8±×M¾ßtΰ±}Bø³W.5{ç¶@ä¸ðä´(¥ˆiÚ ðh°Î äól’Áéô\t…t 4 ëBÂvׂN[Nv‡” ~ŽR74Ž^R,±ð²€|8øvàRÒ;Ç ’¬DÛÏ ed‘9†$úᜓ?ÄœCȾä ?#åÄk•$é"y¤¬‹öÉ̯x`¥ ÆXâ'3#ºÎáù‹Ý“<¬ð¹ë$F6âkå/Í‘¼³¯Hä»(Tó¸[ÞÎ åŸøÌŠá!ö·(zßÑ] zÿ¹,7®ú,3@B‹ïd pÿ»‘{nÒö½C€[}¿—;—E—¸zz åìFû€fIm®‡ft¬‘W¸³•ŠÈæ)‘±$VÀ‡,{T{²/(f×ï¾R‰ûvàL-‚˜€åy«Äøñt>Ä7qˆŸQ jÔÉݜᛵ€k¯—ØYZ¬¾T}ïÏ%W3Hl™"çû÷‚€Z»ßœdÊŸïºüå1vn*h.¿PáêK%ÓØ}G ~ÐûFg`på«U;?´sMáù_šbí>k üø,l~AãÕ*\§ö¼~Ôž§²çº_*PNÐÜÖÞg´ŒÊU…·.Xj3wÖ0A³)^÷Ÿj`àoÇŠm].ôٖ¼v,’Kµ¶*0ó¹€].a¡Ì³•ÓlÐvaœx Á ;'¾2¬î>  BÅ ÆÕ7 Ü|«ÄÓuÖ˜ök0XóëRÄZúòÆ7~·•ó[ÆèD5»hzÀ¦“é’k¸xŽr$8‹œX5S4Ã)“Dn‹6hºìžçä±0¤}{Ò‘?vŸz»žaA,]Êi¦Y`­8uÙ3½A^è›$$L•X‘»UÌÄy¢I§YHÉ\Mô½±[Y\øÅ¼IØl,ËŒ%kˆÜüü®£°çZ‘<“ÛÃsÑ‚ä‹&ÍêÇK}¦Ob)åÞLXX"9ø%K‘ì/,4\’Ù1ê­(9ûŠìÉ÷2ƒ´¸U ˆ= –“hš®'A)¾3Û¾ø\®5«¾WoƒW"ÐáïÕ ò0‚ã;ÀèÞ#äàú‘ø€ešê53²?ÝÍ= yHÛòƒ¶!Ì&ßéITÍ®X0Õ“£ÐÅkÇv±ÿ´ÆÍ—U[¼Ç¿ÙëB8=¥€ª`ŒJ©›„ð ; 64?bÕ8u3s,ÄÍ7Ë6{%.øtµµû¶ðÐíâÕßœ¶g¤Ñ¦ÛþÀ'æßz1qízík#üá¯O0*l¶“öéaKÑ$öf' Ë&­ZÍD¸ý«=I]©Ì~Œ¬„Q,ª]¸©cÊKí|×[ÏKvç>\Àâ²Â¸0×)»>¬¼Ñ—ו»—¡š]Ü~»À;ß0VÛνpûªBµÆØgÁKã¹Ñ[ƒ‰aó•",Nˆ±ÿ~§Ãf|(=N ª·.8t®F12¡°>£âÎÅÕ7 #usl'³•#Ȫ ömj ß}¥Âüf¿íœ“îÜ$Ìj#«›-3 Ôµ)S“]ÞœÎLŠ.wããàq#%Œ­ÄÕr Ë9áò›MÐö™¤”5¦hÌš?pº)ZéT½²w¯¶.€tw^Yfx/>²Íè 34mMañ/9~Ånp~mœmç ´Îpá¬9%3:±B‹mܳ4'Až„¡˜¢Œ³Ë—¨ § &Š^D®m ’¸ù â²{üw™dT‘‘EvØ(É^ëïÌù”2œya ãz/§¥>'¬OÎ81ÈåjÞgI£M1+—Û>ÇN€ñÜG9ÁHó¯$›zž…†5tƒÚËÿ|ºç9HïMA«‘1Ó‚׆ÎþÐðýéF½‚¬!¢«%R¨4¤oOp”Ya+Ù<^±>)¿ŽÃ³Aw},«À¸ßó'ý;íé4JÖÕ¦+£­ ¸ïÑo¼¬Ä† ¼w•²vÒ²’€Óg4×iag]Îæ[ ³*Gç|£uÝfÒÄûrv ‹mÕ²Ò¯Ù'±öÎqâ#s¼ûJeŠ5»ßo~£Â¼YÇÙ/pì£1ãòk%^ûF…¯}alÀQƒ6×hÑôÔsVµ˜QûÜWÔ%w._íbwãí¢ešöÝnf1êbõ5]oÞúúÛ ÃnÔÌÖY0.M ìëYÂí<Ž·>ÜËõÊ6˹BÛc=وॳ#WaŒÑ>Æ©Oå3œJ"|ûrí«Eë,ç3[>@zûUæ‘P-I±l/¿Tà‰/1¹ñ,øÖ àbÚîÛæ¥_ûc”ÚŒ¬ú¥˜5ŒÝ˜i²ÎŽæ)PÚëWªü;Ü}öÖµ¢]3 ›ÜRpÆ6âû´Ýç· Ì­äµÖŽE Öàƒ Æ¥W*<ü½³ìLŸ.¦PÍ.Æû5æ·VÇM»µ°–-Gxãw§wþð·`‡b+bñáÇr¡Í$„|ZÖH3Hu_q’V”^@¬uÎssI]=Ëùη ëh¹xÏuÓPÌ Îg­èü$æ,Û¸²t.cI›(f“H–rÅC›“P$£?* 9Šé8Ç*WàÇoÖ,;!ÉïzŒ6$°‹ \<{A‚!‚HøÏ=„Påœ ‘{ z^±Vã9*渖}T»ô~¤Á£4ÞÅÒÈçʬš¡ž­ØgB}!÷lò‡C ¬®^Y¦~“q·"(º'Lá¾æ2…´\Ó#6F w•ò»>q^NÇ{˜•£@Ú:|†@7fiqà|R.§§0Úº€C6˜(qs‰lð<º ÇRq7{SgNY¿û›(yt‰“Ÿcãd ]m`~ð©dŸÚ­7JÌ\ ª0× « ,6Îa´uOþéÛxå?®áÊ×G ÆìûÅç*¼ðL…™5h<)TÃ@ã’eƒÔ©Ö¢[y¬diMÖÖå˜Áª”]ì,Țό`ÃdŒ/`æ: E+gXüÚÉ̶¯xõÙ ·f§±…geY)fc"áÌr®q.“È…ˆî^W¸ôõ »Ë.ÿÊÂQìËHØüPX¿ ;ñ±%Ždj͘AIJOâºujÛ|ÕÝ“™µµ¼mÒ[…™É©ÑÎ-3?÷ùߪ0«O~z‰®Az ‹9áÊëžþÕ ô6a­š’1Þè7i`5™\°¥ÍcÓd+,T¡Òù†Šïb¨¼ûËɤŽYy]|¬­Ãäk¥fŒZSˆ”Œ WÀ›¯–ÁÏ$ÇQL€f“ æ·ÊÁd»™Æw_®lãcµ`¿´¯—iÛ/´‚‚9>ˆåmPOà(STîRämÁZlÕM!8"»_¥b,Ê_’mQ 4B±päâ&¸ØÅÃý"’9wqqL’Y€àTFR n¬!Óý§„ÀhHŒ‘Aï…™°eÂùM2·0€ì©j)¶¾ŽM"p”0€Bq#Ùp§<šíÊ2›”ºà‰’AázàK˜A#i›Þ½Ïæä˼ÌõÐ ~¿ŠîlØ.Ж­îÀèÎíµ°¦;;Þ¬DŠÐ¯Åc ŽõÆèý<`âz@ùÇ×Ð+Ǽâ'Vd€ñÄ}OöžEÝðéø4l_Vظ_‹©\L°œžBµ{ §Ÿlpåj e ”JÙ 4À‚#jAA¥€SôÊ[o•˜Ô8üð‡^¶] òLŒo<׆a^ø)š¡YŠÑnž ˜àPÒ&‹åáÙÁúÑÏÿÇ) eY{:5L9n€¸WI­‡­cçæZ ›?*8rÚ²GåÆÊµe¤‹ŒÚIêuŒRßšQe pÜœÏõ7Ì,Õ¶u5kì°xU0 bŒj å-Èýëç¶{ërÑÎÜ´‰€Ê ^âg€p“0>¨qßc56N5­¯žAñôúÓÆ<ãb‰·¿Raý˜a¨|Ia ¾o¼«°h ƒ´[»5£ ˆøµßá7>_áà>ÆÉŒ›WÖGÀz¬U]±€t,`£àês# ‹àö>_£s“³3ÉS¦Û•䍒®0‹‡“7†ƒ9 JÀÄC«2IÊf¿—:ªÝ5RQVåÅ€Ê øð þ\F€ÙaŸ' m%&WÌ Œ…7Ó¶5%78ŽÎ yBb­Wô®L SÊ4# ¿`ƒÙ1¡9IñüZÎv‰„Y+Aú(åqA˜OòÎÇ ƒœ BÂ$ö߈Œ ÈòmŸX|û^]e Þ…tw ÒÝ×öÆ% Ñš‘˜ºGõrfèiôU›äö™{Ï=íÉÙì;ŒÑ{uÀaŽ…Øyj˜¥¦d/PÏ< €¯ÂÚ¼âÀÑO‘¤–C3…X5î/z`ó•÷kTÛoˆ©õÚIT»—pè~ÝüÊ:µ)@i7TnÍ{T•">a åX&g“ûžX93&ìóŒ¸/ãϡڽ„¦&|å—×°³m !n½UâÐCKä&ó§©60¹ñ,N|dŽñçþã‹+ÊJ˜ãh¼w  ¿®µeÆy×3ÿ2¹™#Wè*{>Z@˜«‰ï=g†ÁÌ,Q+].'ò5%]`ÄÍê¼òå¶—ÀÎÒØP×Ì&H›Ï* ÷Y^׳p»Ø^úze$eËÎÂZ@LFŒõûtØbðr泋”¹ë¾Ø¾eª|fezýéÖqïµÏOP7ß”‘œÝ¸¢Zç¸e,Ü|PCÝ>[ ÜâakGa}d›z9ÉRß;Ù~¡nl‘ è:6pˆÇ† Öp'”09Р±±²Ï€Á›7 #¯k tlh£Z›í– ¸}Ã$É(AWÀìÖÖx÷ån6«\ñq¬–“@WJóóµf Q«ûïÜN¾›nîþHgpb– zÀ‹ìH8»ÁdL<ФN™gÍM vÓ—·ÿf÷‡buCò.Šg@½˜Š¡Lë¤Bˆó@H|ŸqÊü\F®|O®h¥@Ê‘zuEtËÑI7hNGóA¢]ÈëÒù•xþF{ñÇú†! ÓF)ávÎ7sˆÙEŠ€kŒD˜„¦@ fHft)&‰÷_8Sщ.9vØ  Ò¬³áM˜ÁiðãŸSùjòì¡T–$±GÌCŒ˜WãÇ­È7kèùɘúÜ“Ýï°E÷p³ÜÃð¥Ï‰dmÇïi—9z™ìeA¶øæl qúÐé’9¢‡ 5ô~fßî ŸÌ¯fNعª°vL6kpÍNlá܇|í÷ „Š ¹}uw»}ëŒõVVc®o˜BºC39šÈî{'7Ÿ3Ãý5á?üÏû‚YןáÐCKT»—E€õô$vÊ}˜l~‡ÜÅ÷þå/üö_ûÂMÓÉé$¦ˆ8Ø3?°M^¨¥ò,¾Æ u%A3……#€Œ=¹ZnµàËÉë¶®¸ôFÝ%°ë$6Á˜”",cÙô˜44»ív©1ó+ó-µK ó¥a,³¥ ÓIès¡sY?…-ø›Ñ!Ôk'Ÿb±‰ñ “¥sërW~cŒÅ ÀX;šÞÜ̓5v–kiY…•ÙÍ›ŽõR4EO¡`,ê]–W¥VX¼s¹è\µË¯²´»šaÍ€$Ë@ºGÍ¡Nþ¸O¨ùÌñîîùdÍfƪ±sHZ™cªB] §ß òäZ»/c‰?ß MAœ¼î• ³58éž—j´ºaÃQÓ‘Ùå«E¶Åâ<õ:Ïq$Kn4caŸc­e²ˆYŽûõAÈŠ€¦; ¸ª¨ð%†õåb&”PûÎQG?gËË™$Æ#™pYÎ1±3úà2Ìœ80ƒˆ ûˆy\âHŽ¢Ä}š±YHLg,s$dm¯ ²*KªÇp‚¢Žo|®I¸FþgRÃÕ‚(îéæ©$N¯éàÚ07Ût¤ÁDÑŠo¼ ‚æŽvŠüý›Í±^²•òŸï€£÷‚Aê.÷¬©ì?q¦1p‹mˆQ.óГ–³¸ãEäk釯Çí+„µcy³†ÅúYLn<‹3O5øÆ— Ë™Y“FwºÜŸ‚€ûNæAÁríç’€×àj63Œ¶.´s W.–øü/Mñî•• ˜}µÂŸ,qèôÊÙ5d¹o÷è÷`tóET»—ðÄíbÿ±¿ûÿ›BïPËú¸ÂÉI WÉ„]Ñóí¢HÛVÀþcaA/ÉÛw‹”ú0 Ï];Çò\{½´ù?Fb6oÌõ*”)ØËš±,`dw¶Û:^*̼‹6²2g ]hs쇬œPbËûεƒ¹B¹Xlb´u¡s£{µÄ7þÃõ6¬„“1¶á­ñÚñg¹Ø³¢¯mvײaÌ3«£ÙÚhsÇþ9@ëXÐ¥^(;ߥvÈßqvµ¯öÂyÀ}©¢nëÆ±¼Ý½iWß*¡-[¤=ƒh Q¼1®_*pæ±¥]³G‡=O{~Õž¥|9»†fA¸úܨulZ€ÁØ—÷n~pkSÙÐV›FÒ~gй ! ‹ ìè‚¿3‘qÉ‹”hAÇ;zWRJ:ÖŽÔÂ>ý…œì>s “caV é,Süyb§;zÙ‰mJY‘©ó÷…3Ç-ÝÉ)œwùaÈrº¬d6ÙvŠ¥CÓ«ÖX/}›óè ¿Æ~‡Œ‘Pû¼‡s¾ùr#O6Y³†zz|ë:Uãþs/?¯0*­±H{Ž] (íÒÑ“y«Ž,¿ý¢³˜]C9»ŠÒÊÄê%á«_áßÿb7$¯<06/WŸ­pètñÍgÑŒ?›-¾™JÌ>‰frã›ÏâôS üðAßþ…5lnš977⎧Xaè廨žB¢TÀHiLöë æÀQ †¹ý@€äÎÛ¥WË–1q e3_ê–ñXaäa¯_kð’u¯«í¼‹×xÔ*/eøŒ3áUT»—Û¢ÿæ; o>_âÙß· ¼TÆt 7ãäÀê»o+Ëä¸VÃh,5É]í±3P£"7KDXjìÕÍ,Sm‘Àì»HzÅ416ÉYXm¦Ò-e®½êžBkûWçg¹ÏÓìÿ×w›=…¿›AjA¹]s·Þ*±µ¥0kØÎBu÷kK ·6U;Ë9@1ƒ ¿Ðl„)ìzSôàV]ÁËÊÈ›¦Û_ŠØ'Ž;à6匪4_«›ÎœÃ™(ãF(~ƒ| À ³ˆ` 8vÇŠþ8e’Ï‹¥™L«ô}gAÉ„‚òà‘Á²¤Šr¹]Έ‰¦œ2[Á?KÖåñfâ™.ΛˆÄÇÍHgÊ6$Î-lØ™SKv³,³DññÛðï© a@©= ²Ì¸K-ÉBélì‚ܧá…þ{kóÍŸ›œÉ–^²xM|øYûøX|{/¸¾ŽÞÃã;òÒ`i™4ÇߊXÌjb¯ð²Zù =:Có€cs…äì]B³è̪í‹bpìrý,F[ðèÇk¼þÒãXZ l”µvøñdõÜ53Ë-¨z Å|3pØÚÞRxíÅ¿þ+¼}Ey™KÖ‚€±ðÅÏqìÁ÷Ÿ_b|ãYÌ}de绩>éæ38|z ßÿ3;ø_ZûהéüRÇ•ªÿYÖ2ª[>Ãuè”ëœïk»þS'5o‡²G>mƒCŸ5}NR¶Ð¦xws1n˜¿o6ÆgzÜv¯¼V´F˦{O*Ë¢8ÖïˆçÎjf(æšûÖÓWß(ðæ+þà7F1FaTÖ’Ôž hPg|áæ’´™ rel‹îeéœà5ó<„ç„-"ö ZWÇ-‚ò²{Y1&Vçgù|ûÛšh eë-µ†³›Åàd æ–í{©Â¢á`mÜ ükåÏýjëà莵à gÏ-µÅ0 æŒË¹™³NÊÅ cI@iç_ Ñjf¹åæ}¹•‘Q²È‚zÖ#²˜%—BýÅ:KfI¤2R7@»uçË`èœÆL†ôΤ!¶Ôé9—íe3¹DÁ Góf’$RæO´ò–æ%¤0ÖxÓÒÏG׉â [œS†!Ø ‰œC¦1;l‚ä\¨¬¢DxÅÉܹXþþRxž ·‡-Q>@ø}Hþþ÷É–â™É;*B3k53O{J¹šyÈŽñŠm|+0Gü~îåû¤Ø£ìì;’qs$ÖßIƒ/xÖ„ƒ„~¯fÕáj‚¥ã\ØÍq •=NcO;¤€t¬G37Ûºþ\‰û(w.÷¤ûÎjÜw”±|›0.í×ݾ‘×þ¾ƒy{äéõ§E»áåœðök%žÿZ…¯~¹Â;7Í܈c«8ÊZ]¨êoþÒ?ý7Lq ãÏa~ðÉþõSL°{ä˜^‡OoáÇþÚ~ýÙ‡wß.ÐSа"ÕMh·ù3£‚qßÙePLö¤è7¨‡Ñ̸yµÀliΛû]7ÖîU¥5àxmöl—tÛï(lß6FŽ!Ð033Z¼bí­½óE1‡gç–±Ø~éÙßx¦lß”bŒ”1þqùØY™=ÊÞFN×hÃ,FÓj;UaÙ¹áR§¿Z©=Ç(ó§Èz ¥+yóP áN°Ú##¯~á9¶ØRƒVœ_—s#¿|çbi8Ë ik“]•CŸéÆ(¡åÃ9œÍYsß,’¤ˆ†ï]žÉÉÆ†ürñÉHøÜ„Ït¡¶aޏ‘ð1KÅ+„<2‘ð%αCA€‚êd`"–,[I'r¯hû±+›;Ñ9óƒø%ƒ41Ä³á ø@˜É doÚÇN‰ñ9§>¹#d÷:ÄÛX$â4«*W'3XR0,çÁd<Ô•ú!ÿŠRîŠSƒŸî-@²}Î*‹äÖ=Lbûö²£™ûY( "3 Ä%¹i{e”<Æ‘¥ñ~#¦þÎßNlëß&׆9• xÍÁìºOôèvaéûÄœYȾ…¬],,„À1GÁ·~Ða;ÄLâÌ"íñ)pûaçŠÂÚñ]”»— SÙZ~?õ}5Þýå MÙ''K+•1p(Øw0ßùvàh¾¥°}­ÀæÛÞzµÄ«/–Øœ[sàöØñ¬¤Ý}^AŒÕeWà_¹¦ðo~~úoÜFµ{ ÄKÌ>Õ?N%v|¢KúäßÅïþïëXØÌ'Ç"U6ÌÇ»o–(©›Çqò¿ª ¼xAïÞ$£¾BYÛsìÏ-8²ÖõeL¸è€¯˜‹qÇ«j“-tóŠ² Œ]:Íãìc8Ú½®0»¥ðÎ.¿Vàâë [s`§&Ìk¶`Î̵9 *Í9¨ºÁùò¨ÖèÂþƒ†‚ÕØ9ë4ר¥³G¡ÌˆˆAŠÁÚ› ¤ÆA¦ÅáՆت^¹~ZI˜b¦– Ž,“ïÕK—“<_+pãó†ÍÚ° œ8qþsusSY£6ß^TQdv X>'…&§]ÄŠtõ¯ùym.JHuƒ[£†Xâåé]·´"`K)÷Î ÎuqÆO ½‹‹Žrh)Äc·3 b>‚¥äKªsˆ„Q@$£5#JçX`…&ƒÒw¼]i8zaJ3Z‰{^lV½£Ì¹ÊìO²–¥ %¡B¡ˆFlQ´%Ö‹ò¡ÁRÝï2Z ,‹Ï Éì©m¸=gmsaÕ ÿØ|öö^Ù|0`X% ´³/‰ŒVdƒ©Íÿß÷Ýó;ð -¿_J»AÇó_ 8ÚÃÏ Y¸R£ÒºÄñ€}bưîF0ˆG™Ÿ÷‘à°Hƒê™•¼ùJµãÕÎ%qFh±qÕî%œ|´Á‘£%ê+Ô‚Ìš;àºÛå oÒ5¾ú7°µM¸½n.,(²¶ÔÛÖ\`æ |»ÎzA@]„€©TÀ[oøWÿß}øñ¿¼ ®A]û"f‡>Ü+÷rsIE½…C§·ð±ßÅ3ÿz­Ý¶3kXu •”}áWŠ1*€GŒ ˜.¦Á,Ova³nè ’Û¶ I½úVagn 0j4ZW<ÒݬÊÐàY'¡zç€.Ïl˜#×íb/ÿì?[ÇN l/·æÀî’°[; nñ˜ % ¢,XªŠaŒ¨“8‘Ö¦¸+lÁ!‹Ýe"àÈÉÕrÁ”;2ÎÍ‘ûÚÐ~`ÛЦá÷3yŠVr{ì~1ÒwêœìvûªôÔmgÞæfþèÝ7JTë߯…~ùq8ƒäjtj[G®Á%æÏe‹;©¸¤°ÑÙ9³‹L€ÉMÑT]wîšu‚5² ú¨dÌk²õ‚9Rh%wb)unI` Iâ¼#¬¨Ö„y‰˜‰ ¨ðÌZw ýÜ IÌYÌY\&Á³ò¥¡Þ’ ÔW½§ï Ýcb [àòû¸wŽ5ò Ä7ë×J@ù_c4p?¸‡Åìs•ëþ‰V¼ÜóP5|8hß™cœ¸ùpø¢»3âÒc­´1k€jû ñ»—ëg÷јŒ`@RLŒ B¥†ÍN¸Â}©ƒXg½ð ü¥WŒ·¿­9ëέÌýüõë ¿ñ×ñÚÓcS0ßzÓëO·naâÕj ãÜgf˜æØÆ°ïXYÉ_¥€qIYí°HÖYš» àÊì|©]n¶ÌÙMûÝøë×TkHPkê’ ¡…s —YßmÏæk—;‰]ígÿx×fsTK;«bã‹Æ˜$,Æ¢ñöÝÎ Ù¾;†Ã'c;oç_Jåÿ&”Äæ·µ /Y@eÂs 2̇˜‡¡lþWa›ío»BùÛÐû‘4ƒ™‚›_Ua?Ûå‘‘qT4¿ÙmÆhÚ/ݺT¾yÆØ¼ãÒ«•GÚ\ÛnÍñ àì7`ål~öZÑ}Aš»×EŽkqÄsaæÀ˜ö 67g×zŒNªÍZÆJf@kHyÏA»ÜZ\wà:–Þ‘ ¡‰$bÈ8´Å(§È(˜k‰ªµÄ¸Aê€#ÜF¤€ŒÁYªÒ‹ä_ì5íü´„ß­¦PÙK’ Ó\]êγº έÏpE9N½fO±›"çEÊü ñѼ]ò1nö|s,WŒåœÉ>ÆMÄô‹hgö‚mÐðÒŽdçªÇ̽HŒ~¿ë=ªìu7ú`ýÝ;1dW5º¼Š{žÞC]ÝÊsÁQ1þÍc¯>(á?Ç8“úš<IÚõ‚§®9$u:)ãoFmbøsÖ))ØYáþ~êÐåèfQܰýµçJèf†GºÚ@3:„õÃŒÿð“Ò€$ÆÖ˜ *úeE®]?Ú„!›®ðö$\þ{Ð1*…×-wEœ+ ]?o€ßÿ•)þð—×±ónb±‰µw~¯µÁ–~-6ŒAÅÆé:¸’E&䲩¹V´`jlY´ÉASµ ÂõRoØl|Œƒtì¬<Ǫ×.–ž¼®;·ZskÌàXJ0šB<¶Ýò@WÓ‚¯Î$cSà˜‹µûh87=3˜ßX@Ôî³ YE˜:di+; W•ÀˆUÁ)ÂØæwUeÇxŽÜ×ìï¢0IEÆQnågµvðΠ¤ à –…cJ‘ ] ˜!5,ÏÌ­…ûÏÕ6›Œ­“$·vû&¼Ù°c9FÌ]ã­+EহÿôR\sñÏݺZ`±dÔvþ¨±×Skx\M ³øØ]SÀÜdYT²ƒŽ‘ 1§:ZÅÃ5 ýÄÎ2—û»ìSÊÜ1Eþ`|«±oç‹(p…W«]Xã^Æ£e„šŽ­™×ÀÖ&áækÆÅÍ´Æ¿æûÏN?ÙàðQnÒ´° ©$Œ d*0±/‰´‘ºÂB);ÒæàØ‚Vuÿõ[@àX©‹ÏUøâ?Ú‡w^tÉæ3(w/gY$]L1ÞÐï_ýÄrìM³èŠQwØŽ=ªÇÇ‚N}_‘èØ†BJ+k— ®u™Ópi@ÐäM{S¯ÃíZƒ†Í·•g“,56 T3aç–Y "À¶Îg£ {/8JS®C䆮?rÓ‚õQI˜”À¤dŒ+`R0¦a\’ùZid‘ãv]±ÍÀbCÀuë³²?臘•bÔ£À Q$Ø›µ3…ƒTÀ¨¤ö8Æ¥q¬¼Ï?|BfBÝìÚ­+%jÍv^mõþ:PþÎÅÒ˜0¡aBc™?Í ÍŒÃ'òa·î¸–3²`“ì=À–…ã0y²ÆÎÖ"3¨Ì²ä‹c† kHi[lqk ÙU³ß”DÒ wûU•Œii€ywí³Ñ7Yðåp±e²³E%…?GÌP®<`[53nß àç¤s»aÏ-±)€‰J[ïB%±'óäk 3ÜÚ:~ƒia×fIX«ë°V™õÓý™1­È®#2Q7K7à\VZ9*͵G Þm«(× ß‡ŒíkE€@86€ðäCu' -ÍñN '{5’×C‡ó³ca¯1jh:`õÎ[ê†Ú¹£š ¬1È*¾tá¾Eàž…Kà¶Q ¥rOvKÚ|ô…lmävlƒC7QÎv_õ-ÝÛ8åf29d¼|&)f5bàÈ‘ú‚7)íâûÒ.Žj•>+߀‘ãô%”̦D?#å²Dë:¼d‘\2{>âýÉt÷‰ò÷•ÌË$*f“Iɤ) KkÒWŽÀ)¥nybîMlÐwùI°ç†úc¥ ÅMi^.–ê!o4ÒÓ’¯YO8Ù—aÒ‚;ËA¢|7¿Ç›£ÿ%;T²q ^ø=`®‚Wœ÷>ùÔž e^QˆSt/pO¶ üÐo&jùÂ^Ɉ±ü`ìËï–I¦v, w$£NÇî,‡%`Ä{8)ñl¨ßtä=8ú:ÀÏ!¼òºÂñ‹ ‡ÎÖ(w.‰ŽkÎÑîèù÷}­ÀÍ+E+¯"õí|¡ì:ýNט®3¶—†qr§‘-º‰LÞvêUg¡íþ»±Æø“?»‹ÎÕØÈ|óKÏŒðù_Xó?óûÍ?£žþ‘TÛM`l$ÒÕ0»†õ£ Þ}¹º£•zàlS5t1måuU†µrEkM¬Ô†q°¼þqjX¡yírѹÌiëÈeÁ”—Än=®8<7„ÿöëE"±k´Ù¿%J+«Zìº9šºçº7(¬¬PNÔÖ!AÛwºcF\±\°ùfõÒg¨åN=¤qí­Â뺛Y 2a¦¡PŒqÁ˜x3t.8¶™çÙ4ö4Ì”¶L˜* …=©…[Ï¥1ðô2¦LA"­+Y±ÀþCº˜‚U‰G?²À‘ûtÛµ'(]ýúo}ɉ9Éÿµ~t¨Õs÷ëäÇææ×:»ô¢göÉeʬÔ)óLcÇ"­’ØY§¹+oÀ´ yl>{ÏhÎÈD‘аyÝ2¾›  ·{çmìO >u1Åú}GN4¨ZI&0­ÓŠ1õÖ‚cAî?Ûࣟ]àØéÆØÊ÷ÈÎ;ùèÒ0E%°¯ö¨]Kc†ýû¾ øè‡üÄŸœãñ'TŠQ8mYË뜫£H &ªÛ×IÅ–Í!LJ2‰ŒÌ­ 0²ÎÈ5fë­2 »&o0-í}S™ûÆ1d“ÊH OŸ«Ö©íªÎLÈëõ7J,5WuÓÿPò]fë4h‚Võ KLÙcU«÷RÙÙ.eÀ-Áw%¤Ôi‡}Æ&2-§¤¾œË6–EÓÚ¸)¶rg e= N;é>p"¶’@“kª®b`à3L‘Õµ'—£ PXB4/ a‡Èò¹ý#… §uÅŽ:Þ$˜ 0‡ó:¹.5ÅväÒl EàÏŸ¤Ðó’{—R|(J!Ž®G¼!Žó·â% g±Äpæ°äóYÜyÏ8ȱ`‚ˆkE28Èû‘ì#G¦Ök¦ RÉÁùq†çrYîƒ4€iJ~ôeI@5 ,ïæs†ÔÄ|·Åû°â‚ïø¿ý€Ñûr¸‰§‚çÏ<Æ,gÒ¤ ¨ÉæwLÒN ³ª2R½ Ó˜=g=VÞ|ý˜Ö Ú«Ë Þ|¡Àýnpà¤ëX¤r÷öŸ©qüC5®~­Tç¶ûŽ+.ogÒ1”³k8õ¡^fØ8 [°ÔÝóïÔ©ýoÝFY1ê%á—~ŸüÜGŽ7hF‡0;ø¸˜`|ã9T»—pê¡¿:êõ¼ówñw&8tn‰µû¶ –[½à§dõ²G‡Z‚UÙ‚HµÜ™´î™o¶]M̽cÚB”V3HŽ±Ù¾m€Œ3¬ÐVZÇ0³%,½@°77UkÊP{ò:eß–ÊdS]»¤zq39 µ}~÷ï¾=ES²qü²ûR)êÈów}´Æÿ¹îÜýÎ?ZÃö5…§¨åVÂÖ“£`Uâàé'ïopùͲ½a ;¨¬m¼ðØã þêÿyÇ­j\¾Xà_ÿ½uTÊ€¤•ç‡ ˜sP¥jeβP„ã"¿-¶ÌÜ[h¶{ÃÎtÕ3ñçtµÉþ-¿¿Áµ· ”ªsta·Š'’M>³ùÆó9çÚ>ûð‘»¾7®XÔÆ‘ÐÈ- cYØàÇ¡»#ŨÙ†€›Ý²†$-µ™Sbå­åL3›c’]Ö’Ú0«D]8ƒ¡5C7UZsokÆÀÊÒCkÁ erÍn»(j̶¢y@äCÞþùõu6û… OØÑçȉÍ?W”²-̲n óBÈÙ&`LÊÜ‘ qï<²3Æ@‚J”é"•d$î…œ_ Ƚ÷…pÞüåk’`Ÿ|†33öö+΀Š%S¢9…A¶sÇù1©¤zö$„ÃFÂ÷%ìXl6"¬Ï ãi`3%ž½¢á•Ï )h"ÜEÕ:´ëÍ=EŸ³éåƒ;5Ðd‘q× ;­ávxÏG,_€o:>zÌðþ|D"cî{ÎõFÞ¾J髞 fkûJBö{§6ílðÀëßg/ï³T¹Ñ×UŒÀl‹ZiéÍ!¹ü¡7ž3•úhëB¶¨Ÿx pôC5ªµðSç[fÀ\5»b§¿žž«ûOÕ8ýpm»üÀþ1p` ˜ÇæïÖ-8ZNO¡¬?ýßÞÆ-¡‹)f‡?Ò™Ž9pD·ÃònžÄŸ¶³õmhÔóÕ Wl:×;8ó½æü.×ÏvÀÇ“×9ÉZ̬¶ªiëGªû½j†Åuò¯¼^vÌ‘Ë?BÔÉ(Ôë<×ÁWUküÐ5´,ƒ±VwVÝo¼šg<`aA㑇k¹¯1³A£Ž)Ü72_«€{àÈÍOÿìn]ê—ÙÕÓS€‡>¹Ä¾Š±oÄØï­­ýc`c ÞÇø¯~bÞ~]màäÙßóGg&C¨‡Šu×þàé¦upœZ3ˆ–+|oTš"Öc`!w [—ªÞcuûñÐ'ç–=¢–EZ³¬ÜÆã¾3)@¢fµÜÂÎM…W¿^YöÈ\ÇeÓÉ[Å爽§ç32VíšÚµaÀ¹14¨ð)Y>cl0²³hUA( 3i{e 2#¢Ôè –H3+0Šf ˜k29bm eFJ”öÍlRåÍÏ‘¿Žé’2‘ qǶÈ,˜S$ ïØÅŒRƒß¼ 5åôºóKÔ(vŠ;’1¨£U’BhÙÉØö;¶öŽÁGuˆºdk¬"æQ$̇öå‚ ât¾K”§QjÑN‘dŽb -Å`MÈ&òÏ Sº–[ÀËHˆYXó1«¸+2’’Êg8‰÷8リ½åœÜó¤¾bôn+æAa³w _ã=‹CQ+¿·Nïÿ¶¿=ÁÓ‹Iwp¸ÁŽRÚÙˆQtà Ӣ?3éŽ×‘¯b༒·s¾£•ŒÀî-êÀQÓ­:€ôò36/)¨f7ëúVOŽ¢žEµÆ8óÙe²Ó·m®RN^æŠÞ>3ú“?LñzÀ±§iüÉŸÛiÁÑüà“­7`dl>»ãæ2Ž?T&E´<ʱ{pË–Ç;@Ínö¸²Y`³ïxƒÉAGl'kèj‹}Ý>–G@ ¹Y¬vþ¦0lÚþãù,&êf;ÔfGµ 6“Æd q뢨aØ¥Ã'óBW/v»µR{¬c+µk%wŒ­-ÂæÒõÊÀáG~paÁ¹ÛØüÞgî×øÑŸ1ku¾ÿ<æûÏ·?çX•œe»aG[â乯€¯1ccìäuýäÏÎqÿƒM{½œC㉚õÉùÜ9»ïTã"'4z‡wEþìV~.O¬ÓÁ˜Ý Ì·Ì}(­Aw¬ÎÔØ·Ï8§­Ud䮥qç{üÓóö~ X üöë%æ5aÞ0fÖª{ÞÖŽæ×œl—_+°l¬Ú©5Hiô wY+/P É]‚£Â~¼ïŸ’®ª¾Yî_9ê‚6ïdO÷r9 Eæ…­<‹Êëjoþ§°s0ϹÀ‰‡?W£Ú¾°"~q¿{ø˜¾û4=\£¨æ¸òL‰Ñ~àÈùºíô×âÏìÀ“˜¾û4î{lï3ÞþúÛÛ„gj<ò™YÛÁÞ=ò 0• 6ûÒ÷k¹~£­ xäûgøú/®u(Å9òèk÷5`UÝqgÅ}ûZíjäú~-œGQo¡À–‘ûúp°ÍjÇ¡WŸaãTŸÍ‚ÚÝű'¸ø;¬ÔØ8]£œ0n¼Ñÿhw ÒÎaIà[¨Ìй›óÒº cÍnׂ¹Ý Œœtã5F …LÏþA…'?µ4Ìáþóâš™x “Ígpæ3s¬mpékšpâÉ%N=i zË:Æ¢Ú½„¦®_,pøl£i”³«²Óâ¾s(g×°ïÄžøS;xûËc¼þL…£Ö8ûñNƒ´@P¬æŽ9š{Á¢ ÐÝlàó¿_âø¹ªí‹XNO%6Á-€± iÿÙûϦB9€å~v²ùU|p\&`g±q®W£›/‚tk¯T8úðÅb3a‘–ëgQήaÿÉ-|è§¶ñÆïNpóbÙ‚£p·-¢Ã}4®}×ß(±½ì2™¶¯X?Úˆ†L%vîû‹M‹Mì¾[àÚs#œüèk÷ÉÛ©×Nëô§¨ç„3ß;ƒ*»Upñ÷&Ùké@×¥7;©Ë'²vÜŠÐh†.̺¯½ïYµÝ+—”aмܬ¥ Ñ„1PTa!/¾^àÊÅÇÏæM>êÉQÌ}ã›Ïâð£5?ÅÆ¹àú8ðòÊ—F¸q]áí—J}l‰jû 4Àî‘O`òî3˜ÚÄC?¼‹‡~¸cY`ï®C¹{ªÙÅÛ¯øÆ”ŒECؼRàØ#µeéŽ&ìÇÒ‚¤?;ß ÏâÿÏÞŸ5k¶]ר˜ûûÎ9™y\4$Á¾;QT¥RI¥ Jå°Qª7ázp8Â/vØ~ô/°~ñ£ÃovØUa»dõ%É*«TTS¢È0‹A ˆDGÄpÛ¼™yÎ÷íᇽöÞsÍ5æÚû;™y :| .2Ïùš½×^kŽ9Æã?ƒó͇ðÊ7þ9>øÃçeîMäb^ñ3åqµŽçgçÝoRSàéÞø$~ò/¿‹Wø„7¿vćòn1 yúá_Xk–óÓ¥Éð¯~ùOOë=œ€“­nï&P~{ž®×¹îs³åî<åe ó|ý!\=þ ~î¿õ¤ú³»W~^ÿ8þüŸ»Ãßø{78˜á€y.Œµ{4ð`Át7¤†¡óÙr‡å÷Ï#ñäv > ÇCáŽÆÕš>nØæ åÃ0Í…½=Ž°Ñœíw)ì”™‚!ào«G´† ¡P7õ‰M3„‰@űµŠn F1»£b†hÚ¤"V}Öé[°à¶`±Ý˜$.&®Ÿ7U°2Q!Ër°¶­3†‚éEø,1ë0Íè;tDGMæ7Â]ÃÜRdP¢KV°·ÅB?'—Ç|o#0÷Eé9„ؾ"Uxeqÿ< Z’B–gÔ†xjÖ Y ‚]Øãl÷ÿg‹^ð»„ovÀü€<ßkîEÖÝ‚–ƒ¢mĺe³} ®1H¼GÖÁ¨Çëµì}Üyöh.xÏ%¿d×÷øÒç|ìSüÈÏpóÖgðôC¿v‚ßý¾¿ˆë·¿8q‡‡8ß|§‡?€÷}ýŸLË4 é{ÿ=ßýƒe®d<<ÄéÑTàge _ÿ̤»—M²©‡¯ïÿ·ñ'ÿûïà|k8=3ܼ\ ôJrt~º¼ö'ífHWÃäÔõÊ÷ž1œÞIA§ú^sQÿÏ_#ðÖïñè{Î8ܾ޼ÎùúC“T ¯áÇþÒ“…AÁpÄÕã¯àÁ«ÛÕê|?ïÎX$qçÙÅnœŠËÓÈeFi¤ßf¦Þyl 8º+Nvw%”¥i`gâx7 Ú?½#>õ›Wøèžqýö%@šïÁùê/àúñW06^½·¯ühį‡Û×ñÖ·|ü—opðåOñ#æ€þPÂhG<ùÈŸÅñÉ×—÷¯Þ¿Üûùž§µ Ÿþ­ëÉÕ±öægp¾þO_Ãáô6ÎÇ÷/ëÞßÛ#^ÇÛßðêAºïݾÿcÀx‚ñ®¬…ï¦5ÿø+‹iÈõ;_”Ïàéáà)¦äïù·õôC¿P­Ï›·>OøÔo_ãÓŸ:Vî‘›œ÷ÎcŸAše_ùÊ0Í/ž'é\6¹c鿯 ¸{ÀÝåþ<®Þýœo>T1]ãÕûñÓ?õ6>öãg|âs‡i†gœÍ(jÜèÞ& A£LEƒÕÎk,áŒã0$ÏŒ`xh€|3Ùü[J:Ëè¦ãe(Žvæj ¶ùWMd"ìÓØñŸÝšï¢å w‚QŽoU¡‹Ö/2-\à|hkóï,(F)ºµyw¿ê;ˆîhóy‘XX³eSdQ}1p&Èn«ÆQëY€¥ÈÄ©÷¡˜¿ë^SU”Xÿ;Ð깫vø$§fÌòçåwAŽÎ×³ÂØ.sr•º²Y÷bîU–>/ÂòH¯~›ehí‘ÜíaI«Ž<£dŠûX‰±_x-^0(y·èc´çç7™Òêì¨v]óçÌÂ"7~§3^5ƒ£Sq‘ºÑ-z=ƒt[ræ¢z.n†‹,Æ øµÿêˆïýÑðŽO_k†½}AúLHªÆ«÷c¸{×o~v‘L©ß½{åGS6Àxƒ×?>±¿~ƒ×¾rÄùd¸zò š»êï~Ï¿‡ëw¾ˆã»_Çáú ׇ#nß÷±æ}¼ñÉ ~ò _ÿÆ€2&€«a’\ýПºÅáÙëi±ßt«ž|}a¾ZØŸ·¾vÄ÷ÿ³\*õÁŸÇéék8>ý&N§ß»tû?Pœò[‹ÕC‘;9‰ÝynØ2{tæôçwÅÀ!=š‹ÄîÍw ÏN6Í—8c†óheÆiªŽñô4eØüƯñ'þ¾ÿÇž,àXÞ÷ùf<‹5çUýÖ?»Áãg«w€/}â ú‡&v~ð½©ûéátïÛÌJ~éÓG|üׯ`ÇiHÿ+_8âî™á ¯§2ÑÙ¬D¾ö;_Äáöu¼óíϞ憴cólLÀmúîo|íˆq×$k:ÏÇ×Âñé71ܽ3Žp]®ǧ¯á·ü¿ÿöC<¹³t§Ñ0Çq n=Üä÷3x~ãíOO\ÀóXRÞÏ¥XZCyóÔý9>ù:†»·ñùÏñ{_:¶,‚3TL CçÉ®™¤ŽmȪ-©a0’€É¹ï`SÀjdi@œÏ›Ë’/T+¶`B`}Ô|‰ü.–|Žø9“ùŒ‚²Zšÿ&¤g‚mRŸÍƒ¸ RüLMPy>Ð!ºì ÆÃ½§-¶ÐF-‡Œ /^ÃêrŠàW À†}²NW6nK~Gæ›Ä×1èà]ˆ ^T2.K×®•½Æ/Á‰ÂâœßÀüÔãnË¥>/õ8kωðuïmÒÀE&¶ÀFçwü¿½×Ú!“%óS¿DV´Žðqkõƒ?;<­Áž—Ôä/-þô»bÞéÎ|aßñ {î›4ÃYt¸¦M"@‰ùg߄Еûö¸÷JÑ˨XØâ¼ +ƒt[äQO‹iÓbÖðøxýMÃïþz1lxó“6Å¢È.ÅÔÕ“?Hñ¶^ãá·6žðŸ»ÂoýÓøök>÷ßܔ޸gS‰Û÷} ï~ß/âñ÷ÿxü}?ú4ÅúÌP¼þÍþñßyˆwžo?Þz¼} üÞgËü³o¦ïÓ¶#ñ¥uƒ7Ÿo<¾ø;WxúVÇ\ÀŽð¡_X îùuÞ-nz=»×ß²Iwçg…Ö€×sa ïÆUõþqaì8€/ù€Ûq2a¸[‚b§×˜ÀqwædôqG<=ïÞ¿õ«× HÈf¯zÿ woãáw~ðÉ_¿Âï~üˆwËš|çøyo|áˆáü×o~ö^»ÍÕã¯L ûø'ÿÁäävâ´þOÀãg‹ôÃ~Õ?7oüîÂ"~ü—â;_ï‡'«5?ܽ×ÿˆOü×ðÕs½¼n¶gÙß³þÉÉJß›Ãíë àú‡û!¾õÆ€wOÄ“;àÝ;û§é¿ŸœˆwïˆïùÁÜÅna§N˜ðNijbð0&ó‘xv>û™¾U¹º'sÃâŸÿòu1Y«1’àÈâ2geóºÁZñDÔœµ,È©§ ™œ§YÃx&îN\ê _t3Ȳ×Êpuexåjr6[:êæ;ì•û—’:ùOÜÃÊ÷³ÞDc·-CüÑ=Íc`¹K6åJò x¢c‡ÍäZ B¸Ä5,Úßs…=[g 'Ûsá«–Ñlê3¨àUÖÆ¤F@)¾g´[—®‚º›Ñ,D}ÖÙb8Ûü6³¼Sv™' ¯Ùd:t ³}ÿ¡äÅ Äñ`¸:Ç#p<²„o]×Çéwn®¦@ùWn ¯\Àôaj>ÌÍÍø-¶Ò…îÞ'w£Ð€xoÜ‘C´Y[G‹ËøT9óÉïî‚ÿ#dk^4ù®aø>lkDt1¸‡ñÙ2aÜ-¿ ldmÈåu“TŽ»¾Ã³»•AZ€Òlõ}7¤'·À»·ÀoþÊ_þÝl<áaarö°(¾ù«ÎOðæ—KÁI±ì Å×¾zįþý‡S‘| üËüßúÚq*¤¿ý›]ðB;Êù©ëw¾¸¿ü_>À·ß6¼u ¼u;£7Ÿ_ûú€¯|æ 6nDXÀÜ~þ ŸüW×xý)ðÆSàõ§å«­ûøô5<}gØzß½›îÝ\\Pì©tÚîF[@Íí xõCãRXgÿ<;OÏT†»s‘ê«í÷<›ôôdxRÒ¿þ­>ûñã´f¾ó›Ý÷ÙxÂg?~…_úÏàÝÓŽÞ¾Þº5¼õŒøÕðçÓ¾oÞøÝ‹v›ã“¯/÷þ¿økñ?¦5<¾%ßÞ¹>ÿÉ2ÿ–d‚©Ïþè[¿¾H6ÿéö ¾ô¹¾ú{‡]@áøôµé¹¹{ßþÚ¿ò·^ÁÛÏ ¿õOà¿p…áüds½«×œØñKð¿qwï¦ïøîÉ Pš€áã˜Î;:5O˜œ£&gÌ (?¹›ö¯|ñ¸|Ž­g}¾'ÿ÷ÿÛ#üãv3IAœÏ\˜mº"“d»™«YntÛüp´sØšY¥‘P¬íF›š On§ÆÀytàBnU>ÂÑ&³†ǹxä$3¬R Ù Ø2/°™„¯c˜ËÙ!‘ZpÓZѰWñÏ­Ú¬Bm½Å5BAGÍj¥°¡õ\Nx—ÛhHÅ69iD¸i¥%þûP%ĵé­ê#‹CÎ ÷ ÙI£Î ªB“•E¼L @v•Ç™ÍAÈ“Óä0ÃÓŒ¸2›òüކãÁp¦ÿ}u®À+7À‡_^}8Ÿ?2| Ä>|à¦Ñ0G)‡)Xýæ8¤ëƒMÑ.w̧Ã+üo*p]@Þ°ü‡ËŸ©ÿL”Vÿ¹»®`Fý3>*ñ¦šÖΫUYVâ>ùÆšgòö›33d¦c·g{gºÅ9 ,0 ÄœRîþSÏœ~ïŒéÏÓ Ùò’ü2+‚|'y^™Ýw•”.sr«ÖöÔlz@F€E1„Fáêò9ý+[¤õÝ ŠµÛE—\ ¢}f—Œ[û…û©?ú1.šþ¹ø8ܽ9ÉÍNoãw~ë€Ov¾¿=¯ÌÀ<¸?3Y>Œþ¾xÀlÄû^y†ã³×pzøý«…S(<¼þopõä`ñíß;âSÿø®_!Þ÷‘®ÞýýÉAîúÕîõ¼~ç‹xðÆ'0œŸâ¿rÄ?ü«ðíï Eî5}î¯}îˆïÿ±3Þ÷¾§8>ùCp¸êv¼—µs~Ї¯|±¢þ[ÿé+øí߸ªŠ½Ûój—ýôñ€Ÿÿwo§ù¡ãÃ47èáw~‡»7ñ­¯ñÏÿÎ#¼þvéÌÐùÕÏñ?£ïNƒßB*eç§xðæ'qõôq¾3ü£ÿä}øÁŸ>á}qzôƒàáár?¯Š”ï Ÿ;âŸþóë…ù› Õ9i^—CtŸžÃ_üKwøðGؼîñék8>û6~ï ü‹q]˜+@z’ÚJ¾ÇuNo°Òo-{ç?yÄG¿ÄG¾ï„ãÓ?Äxý*(تŠ}{ûóxðæïÂ8â3¿ÂßûÏà‰³¡Ÿ˜¬)oçñcÃW?}ÄÏýÙ;Ç·§uyó=@gþÎxƒ7>ëw¾4±)ó~ó_^MLÈ8Y]ßÑpâÄÆ~ókGüÄÏœñWoa<á|ó‘ôu¯ ÞøØx‹o~õ€ÿê¯>Âïé0±²OüðOð¾œå²óSܼõYܼýyG|õ3Wøgû^ûöabvÏÀ7¾tÄG¤¬÷gßÞõ]¯ 7o~ÆŸøÍküÍÿç#¼[ÀÐüº+ÛHœ9™-üâ_¼Å‡?Ò®¹ëÇ_ÅáîM|þsGü³_¾Á³;♦-{0Õ¯¾Ÿø™Ÿ;ÁÆ[œý`rÝ>áô.¾ùþú_}„þ/nðänZw·³ùggÆÂèÐÖæ¥ÅðÖ°›:lƒË‰p"'»³02Å–Lßñè ›ÁÐa}æf–Ù¯Õ¨ÁyšœŠ™?ÖÆÞDg·ù5£t+2þ=b˜lEVDižµ¿·\[ÁâXÈzB`Bh-À­\ù’\åd\ «?oÜî,¸&!¤˜/FæI¥þ‰U·ºÏá:Å7Ó,£…ßóFÑL"*43…z6f©9flQj‘a0l’ÐÃap8ÃÁ–L³ëcÉø;ï0ƒ?¼2<8®ŒÐƒc±ø?¸of‰¬]#4)8ÞçHæ’?ž)õ3Dí V­pÝH=ѵÔïEÖú?2n³TËfÙ–-ûÙ׿ÒÄx.›õ™ ±ÿa ÷öh{á é[ÔHÕÛšƒãåë hZºóöÉÝF¨0gveõ{åã †òýÖï578†ƒáOÿ»gØù)ŽO_ÃÍÛŸÅõÛ_œŠéÓÛ8Ý÷o]áÍ·WIÖ­ =h²Î¶;Ÿ€oýþ€ïûAâ}ï{†ã“?\ºØ‡»wpõîïãæÍO`t·¿vÀ§õÿúŸÜàíǾò©+Ü<">üƒgŸ}{ꤛLJ Ð:ܾ>u»ßøb>â÷>u…ð×á[ß>,æiï<1|ù³G|ÏGG|èÃϦÂþÉ×1œŸb I)¤Ÿâøìµ x½ùi ç§øÖð×ÿÓWðÛÿúja>ž9€4›|çµOÞð3ên±-‡ Káx|ú‡xðúïàpz¯ó€ô7á÷¿~ÀãòšOî¦Ïýî­áõ× Ø:>{ 6Þ–ïþ®ž|7o} ‡»wðîÛþîÿé}øýß?à}ñÃ;ápzWï|7o~Gðù¿>—À“³cÇÕÅnY7ôîg óß?âg~æÜ¼îñÙ·ûoÝàK_=¬×dnφ»ÂZŽ®P5W£È,àó¿s…~”øžžÖÏ<\‡À† ì=xãËwúíÿÏþÞ_{°€Ë§Ë}™ &6l §ýò§¯ðý?4âýïŸÌ6 ca o*‰Ùõã¯âæÍOâp÷¿eøÇÿ~íW¯§ûs¶%0õĉA9—}ýÛßðgþý;îÞ\×íp5}öÓÛËëÎ×íÿÍ5þÁÿãÞxc•OÏÓg53|ìOžp¸}<Ü”µóÚÞüty> ¿ò_>Ä/ýçñÆ;CYï“líw _ýÜ?øcçå»ã-8\Wßu¸{WO¦çqfm~éï=Äßù›ðî‰x|7?G†Û³-tó÷ |ô£#~ögÅš»{ðø?¾‚¯¿V@åy >;ßèÿÊï]áÏýù;¼ÿá»ËýîÞÂÕã¯âæ­OáøìÛ0Žøôï\á?ù?¿‚ßýÌOî&6òé8³¢Vö§:ô˜ªP§«yBAã S?‹ì‹M²åsÌM‡e’¨ªY­a ÀÈaqË«'¸áõÆB:“ÙY[l7¿ŠsS¹E™13ÇZ9Z]ÕÜÕ ‡%΂ÍÜJd…Á†%×FJëÔÚØr×Àh¥fñwß[\g ³s"_ª¹y‰¹D°Ñ®› s?s†,lJ!`ְ㹦×CaŒfPs8 “”ô01=¯ÜLò¹›+Ã+׆WL`èý×…õ°Ì&ÍתYÞpW Lóø A À¾÷#¯ÖÙ‹î%z¬ÀRýßó¿ÚS}1`gkAÖÂ(U²õö›¡tw¢dt‰â¹í­íúQÎ\C<@ª­X(ß9ŽãÚ=1ýï‘«íHÃíÉp;Ó}ËÛé™þît¶TNx9ré•Õ/ ½·3N|!ÆlN]çD·q<ŒSgˆ«qe#Ž%¬ÏskÕ0@4 Fk€Žm|Ì*(¹ÁØ•«•$& ðà‚D¯†«qsX;3ÿÓÿÅ~æçVèöìéd×üå/øyÀ׿a‹{Ýí¹N½·bÔp€ë¡h~ ­}s¾ï{ˆ¿üß»Ãþ´†ßüÊŸûøŸþÍ#Æq(•éyhøÉ?uŸýËO—¼“쟯ýÞ¿òà3Ÿ>–`RºÌrÿ‹‘ÂÕøs¿x‹_øs·ø¡ŸØ–½óÖ€/|öˆ¿ùWâ­wlDÞúz¾Î×C =é/Ýâ¿û?z7}Ý/~ú ÿÅ_ˆog¨çü™˜îÏÏþÜ ÿí¿ò?’|ÖOýö5~éï>Äã·Ë ÿÙÿêåçŸ>5|í+|æ³G|â“GüÆÇxr‹%ðóÙ™«a§=ýpn“®ûaù÷×Àÿîý.~ò'Ïëë~uÀW¿:೟9à×þåq‘aÞŽæG.,aZ‹Wf¸>ŽÓzytexåxå xtEüwþ£[üì¿u‡¾·ßvøâ§øåt/æåéie'FÇT‡i=Îkô€ÿè?~ŠŸýÓw›÷ÿ Ÿ:âÿõ7à¿5,ÌÔÝ8ƒà8 ÌpeÓ3õàüäOžñ?ø?Á÷|4ÿü_üÔÿêWnð…Ï&öñ<YÌçÏâø?y‚Ÿÿwîäšü½ÏñÏþá|뛜9`Ìy9Ã09¾òˆø+ÿñü‰?s»ù]?ÿ©+ü‹rƒO|ò ï¸J1 ØœÃ\mYó†£Msÿûÿí;øébÁþä‰á+_9à3Ÿ9â×ÿÕ~÷óÇIª9ç³á b§fÓd‘?Í<¸2<º"~êÇGüÏÿ—ñÑïoŸûOÿÎ~ùŸÞà¿sœ¤y'ù'€\æœ&y¨-Ì蔨¬ªcñèvtR‡ì°fÅDUjƒÃ|ކðÊq2+Aiºükž$žÜM`Æ3flž¯âÂ~¹B°>ÁQ­ë§êoAA5fÜQTˆœ)`íÛz:7ÆäóbÃ&¹÷¾y«hº€˜Ûä@]t®“ŸÇZ=*°Ëê£Èމï^¥v-ë׫糑Hy J–;´4©‡ií_&Vèp˜®Í+WF'NÀèPÞc,Ÿ™åß½œuޛΣùêܘsçã$½½«fm¹üý –ºé0W÷H 5,kŸ Xš ‹-€„¹«äš²1‚üÏ-<”ænpc“ok¤œ· „KÍžÒyîNqµÂÒ³N[é~¸æ°åþ éÕ|Q F”F\•?»2àê0â0ÇPÒʸ8æÔ}ë¤ÔŒ!Æ&\øÙFþŸ¹a1}g+Z\›ËäÕ|ß÷ùñ?0áÇ~ò„gO _úü¿ûo®ðk¿r½Ì‚Ýn3Ÿm” ëw,àëçþä ?ý³'|ì§OxãÛ¾þµ¾ôù#¾ö•C0á˜^ãäŒwÞ1|ãµ5›hb(æ"ŒŸßfåY¹:LkåÁ¸>L`ææHüä¸}|ëµa1±™ÖÇd >ÍC†bó=žK&¦ÎùUÑ„_€‡GÃÍ‘“tâhxxœ å‡WÄ/þwø±Ÿ:ã‡~âŒëâö™áµ?˜ÖÎï}þˆÏ|êPß—YþÉÙ]Ñ`—æÀõ¸9X¹?ÄÏþÜ?÷oßáG~âŒïýÌüþ—øòçøäÇø½/JÑ <=w'ÏŠ•¦ Ç¢mŸåÍÑðïÿâm¹÷S¡ÿæw _þâŸú7Wøê—‡‰…9Ï÷n-àg}þÁ á/Þâc?uƇ>2â¾vÀ?Àç?}ÄÛo8ÏÅÁŒtp– ó}œ4ü?þãgüÙ¿ð ?ú±¾ïWàñÍ?8àË_8âÿú ¿û»WtLù»'[îéþŽãdQ=çÃ05˜‡)èõ§â„'O ¯½v(ÅÊŽ&¯ÈÞVÖ¨|Öò\Þ&€ùðjš%øÿÃgøéŸ9áéÃ>wÄç>{Ä7¾15o‹ÙÇ7Ïö´HønφÓÌî5 "×­eqZ|íྠËäZ2¤éº&0}}\›+3£j¡€žëãS ؽ:”̨»r?æ8­š‡&U±+$d® ·Ùi2 žeÏ)¯vum¯…å§$!$vÀ*¹rå«$‘¬^ÛJ¨ïò]húí䘙_rr÷&üu®\뀠&‚®ªˆc’R»f“Ú9+su:g§Y¾&Ы܂ f™a}®ŽÀ£kàѵ-·èáÕÌ‚NØafyl¶Ï·µ 07¿|Óc,ÄgÙ.YBÎkp4–àûóÒìå¢6ÒÜm›’Ð4”÷pO€´šx<'@ÊØÞ ܤ:EÛ8¿UðÈPÛWrùYTi,–4#Úax Pûe£nÒT°LÉ @²çH—"†ËAÒgp$Ra®àh“t=Ìie–‡ÀzfŽ™ÞÙ“×Í`;Œ:—f"Í,½B<ŽM€£ŽG,ZßeXÑu]N³¡ÃyF³Ã™?£f›ëà Œ3[cÓµ>NÅÞPtµCé‹}ô´‘ ;Ûꈹn²ó÷;/–å\ƹ6­[¿»IG÷Ýç[;¦Ù{fž°8ºupp¯9{3¯/œÝèÙ}λSyOw<-ŸÙŠa‘†®¯eÃúšÞ–{–Ì÷Ï_Ÿgg.ìÎiœægN㺉`Ñ~s|ÌLß­äÖØêò¹Üƒó<›‚…ÉKQ<Vsa}U@ؼ^æÚ`ßQVÝûÑ™Œxà7;çÝŽ+HœŸýƒÙúnf sä²F§ÆàtXŸK1=Ÿù¾?sÕ³íùT{ƒ 8 Óë] 6³•Wƒ­÷l9Äç5`%ÊJ¦«˜a07ÏkKq0ŽSq0Ͳbùobe"†ëZ·ésÛupEî™\Šïù»> X ¹+çÕù¼›‡Y‚97_ 8 kS‰…áXò´ÆÂæ”Ù¥l=Üæù⪠h³c¨qaÛîÆòyÇIêøìn–ðÑ™ÐÌr-ªxéA"‰À–)@=#4ÏÜ™k6_¦µsßž'÷±«a.ðY‚õŸsup—µì%å&ª™v:¶`* Îå¾OÝÓÕaur–f@ùÈ/†YX¥(³lôÙ¹°33è\XÀ"q)2±S b=q*ÀkpM$ïžy:Oëo‘º°¶3:Œ†óe–kP·gÉóTd,ßµ4f†{d™b¹7¶„Ï2Ú*Íi»ò½‚›IGÖ³ ûs”¦èT+¯ø¹°}Ï8ÂÌð“›—ŸÑ‹™­óÇ8 Ä£ã´ÆŸ”Ü2€å{Òãµh‘Í„ÝPÅ,ä Ä&4½1-ð×Z±rRžçž XÚZ áfà­ Ðé´T…¿ª×Q€‚aV)¬&{HÈ«9.^P!ÑÎ\Ý#®Ä3°ó”dÑmÒžÝÊ5˜äse/<¸šž¹ƒW½ïšxt\ÁÁ8Î^³j§Ôá þÐj-é¡O‹Ï«YgûŽÏ[Îr#Àµ÷˳›…¢pçd—ùp(”¶R…`øy.›‚=·ÙNPÄöUÕ cïËìE-/ÌÜvÖ?ŽlÑ{ÆÌ7Û²ûædŽêm±-ÿØiî÷å½!²UnÓ°º qœŠÎÌÕ@œÏ†s0òÝ—³Kx¬ lf’§pÄ©€)£gÏ[UÔ/lž¡ð`Áñºóö9,Ìî â¸0 §qJ¾ƒäAœgÐVò,HcUˆ­ì5˶‚¤ãÏÅyç´‚>gæyfãFÔƒ£ËÜf‘‚ɈÏo€c±G[w's:V€…PÉÅQsty‹pKG°ç…õWíøP¬hFLîtçYÒ€bîîö°t ‡éEoKlÂHâŒés݆ۃ³{jG_@6ßç»"§òRųe³sÒÁ Wc¹/g[䥇a5mÁÂ|LÀîD+÷œ‹íù̈€†Ñ9BJ1kSsàê¸ÎôNºå娋¼´üï:Ãe¿ ®Ø›º¤,×nf]¹(–õn³;ÔČϮƒ;ïÆò—é™)37sh8óg˜ŸG[d÷Óz™ÁÌ CÊçmY‡£“ÅÀ}Öó@ÎÀ8ΧéggÉê`SPê´fÖ×:•ëvwšYÐyöpb¦çùœÚ!Ú·–Ìߘ¨,Ê<+IÚªF¡e¡¯kgžÅŒ„(] #FÇ?+3Ça’.»8p_¾ÍÕ‘…íŸ4ŒV²—PÎ(†úÚÚ®Óh{!]ËL°HØ2ÿ*ˆ¶7/Ù&ì‘$l›L>¬µauÜ\”Y¥ßƒÐlç¾*’ÌZURèçM„â*‹ñfv«|kžÙ1DEmÈ¥”~Ί€ \NèŲ»°É®€~`€aÄíyÚ]•Qs’Ä"è¥frƒ„”ݹ´—ž·,ßSSté¹YÄ×Sï¬zÿÖt¸«ê|žï«ó(Ù¶® _ÒÝèh·ìe¯†÷œ ƒŠ:¦ÙjÝ6Ìu¯£¤×|ÇŸ|9—b8QßZi, ÓŠ´«$c–™k†.™Š¥p÷nZÍtÌEÎ`‰a´©Ø)ÅÙm‘èbv¼t×p]³ k sW|©Y¸@ÏvRlÌè$vp Ì\xN+Cã›…p¦b¨ÆõÏÆÞ;Æçp^™# ªß ÅkÍÛÙ`8ׯÀùë²|>ÙÁT××nRk\dq͉sù¼ïÌ ÂÆ: Žœ];Wǰ‘«Y!¡M….m0ΫìÓ0±WC «~Æœ`ËíÝyeüÎ0xø0LsQÇq WãòŽEÒ4Øz.Œcù>¥À¿­+Ûê¼6¢*¼[ä•nYälågæY_´/æ £áì~ºÖ€YÝUôù>çÑINB©09Nßu²Ó†‡K8ã,‘dͳu÷Ýi2¡˜¥„ë­å胳ª>p2ÂF[Ú“+pŸçe  ¦ïj†3p¶õzŸÊüÕa¤k.8`Y^su³Tq^㈱ |ZܨUaouÁÎÕIŒ$Å Q7J.æ6–J9V6ÓÑ0ÙßÄ8L³WåþUÍWó»-kêºTs'îÊž<–F®?Ç1ɯÙÓ|¥š·IØ$"I¶jÈM# ¤e=pï‹4ˆÌ‘õëfõ¾MAJ =à–N{ `Ýû…Úq1Ô­lQ7!ÿ‹võÍ,bmÞ0ïe˘Ìô¿çFÌa˜[?ð€xåÊ0ØÔJ;“œYƒ[/Q5¡+Lðr¬sd\ö“–yò—Ng£jÐt|uç½¥°Þ²™¶(÷l\ýpPõ­› ÷² kø‘‘FX%{­$ŠAŽ·ñ./°l0Y/ü=ߣÔÜ  ‹W”ó)¤ê–EÎÏäGÍú›_e¼ß¥àÓg5ûBºr˜6*³iކRå'j¶h@'Ñ£ƒ•S Rfœ±:TÁK„ [;¿æÙ»Éx„µØ÷{´ù÷ƒ(ôÙÊÕ $kÎá¯2âp·†ÁôÀàŒ,š+Jm˜:êÔxœ 7oÙ.†~®8ÂÚ¤ï*;°È­à Ôó¸Îu­ïW~ÎÁ朇Îe ÿàš‘Ž4Âv³„br™‡q̆;­0/ã8îãHF`<†j˜˜ÛðýýÍ9c¼=Ó–ÌKëæï»È’8Í¿ž8ƒ÷YV8=ËSŒF¬ëqf%ªõÅ:²b(÷ý4‡¢«? åçff…k»€‡%&ÂóL+Hš£$–°ÓroV¹š­ÏcQL˜Íód“QÅÀéÚζí^M4r•ùG.¶å#WÙÛôÒæê$[3‡ P)é1ç¹) `¨\Öæ×lZg†ÂvO¯mƒ–÷çb?¯qËX~†ã* ¤©¢ÂŒ î¥~€¡¸®cž%h¤PíYBßí.Eè]±âz˜î;àèèÃ0 »ßKÓ‹€qÀjE.²‹”¶ê£¶,W°é6çYæGpSäQ“ÊÂ;b³ù½|‘Oý; ^d€\@­Eæë% váL óU•bm„½mÆ6É{a‚e›"á;7sZÎú;>Î4ä29Öø0”`×ðÁ‡˜ÀVißÕAÕêl/r2s&Iü'yÆV„s©AŠTuI³2,ÖòÜ–ð¼h€´§¾ÏLcô,{“ä/üÿ7$éÀ¦_}×ûÇŠØvÔçÜËê¼S@[zºíœÙ?FàZ^çºâ [GMpgkÊÀˆR»ìYÂÚ- .´òÆhh!í¿õçòë{7XÙ£9Èðàšxßµáý׆«Á9BÕÞŠà 9¿üíyÚ7·£áÝglÎþÃÀe–'v“÷T¤Ö#mÙ;Y3ZÐÔ«¹Ž—–„ž]ìýžÿïѬ{ šCeÞF ÕoÌß™°Ù…E? îUQD[슰$«†µ?ô{ñ/…÷Jß=À¨‡4Èä³úk'*…Èû>4I6é9Ì˜Ý €‹íÏÓ,”ÎE¦ô½Y‹ùµæÎ#×9…àT_'Ÿ/²:qÍR:éBg~¶¢öú3W—¸‘¹{©²<§(üýÙ;•Jc$î]ì~7`Õ‹ñ}TS®2ÒØzÆ$†"î´¬SÃã:©T‡iÅM÷ì<Ï@Í…›ëΛ?´×ßf ®Ífó[ŠÛYü1úpÁ±¬W{¬n`UþÜ2kb Cw¨®™Vuß—YkØÓx¸ÖõHÉe˜¾tƒ—®g#Yœ+Ö׎s²0qõiGQ|3t¡G,r;[:ÕVäVõ~@¬Žk,×~iް¶Û]ºÞ~í-á‚Ú 4ÙÉ™?ÏZÿ•¢¨ -†`K?—0±”N®Ö<3AB„°±Ðªö¶uR®ÆM„nº™ŠjƒˆØÊ³I° ÐÜž&ÉÝÍKNLd2ÐÄÃk`¸nY:A ‹l е”SØi‡y†j®&󺵤r{UœAQF Ûi‚$‚¡˜õÆx6*šDc $ÌKÏa.ºMG¶Ú«ñˆ†ñ3±­2q¬Ù6+—ƒMŒü£+àÁ5ðêÃÃãÄò,ÏìÐÞ—y.îLà­§\²ßzÆE5ñøvšƒôáׄSc`•ÿ6ÅÜžËnQØåãU1L¯swvìÒ²±k»õãË*F¥õ€røÚTvE¶§Õe²™ AÝ­ê}^v˜Ë¤b½4Ìs»j¿%t_‰/øÅ7dß•ÀHòþº‚õÓl™ŽnÓVƒ÷¿g;¯[)%Uò¶{j–g µLŒZãè:¶âá˜ÿÿXŠ ®<ÇüLsÍ [æ.Îr†È3 ‰³„‚6ߺîÑ}Õ6Ìm* ÔÙ6)Q,øó¬»V<øR‡YbyCŠáWXUÏRÆêûÿéŒf0d~ðÜ)Õ=„v ÌƒŽQs¦'¿ˆVÄnÁ.ò47°¶æº¼2/0O!f³ÓÕý óÔµ¤6•¥+\lÎçCß‘ŸÁšo6Ðüæ:âë³µ0Vó|Ùò}ÍÕ[uáeŽ=óÀpA¡©á×]g¢1°YXCºZKt²Ý®“ÎúT3êVYO.9ν¯XV@­2`Ø2Y7™ÄÎö#4&ÌCeIqªŠÆ5Gf‘R–br¦@\Ô±5¹:²-–]á„a¸ž²Äß§Ó0I‘Œ‹¬\eœhÀ<ê"Þ1UºAÓ)zÖ¤*À£œ1æ Éý5¬%‰„­aЍg‚(ny:ƒÁdfIÀp¢a©¤˜&BDêõÏüf¾ ÕþÚ°[[ù:@py,´.­Hǧ̲ïû€-¹r šjaÎ:‡Æ7žŽ¸ ŸxV\2 àtšš>œç_¹F,+i´ežùt^ßäÙ]k½>Ý•Ÿv®ó{ÿìH ÛÿGRàÙF²Ê&kФˆfgñ‹‘lÞ°ÂÉÎ/™Ì³40ïþ˜a¡Çû¼ª]ôãË‘ú^šðÝû-¸ñ·Vý»»ý2i$ÉË>Wº"õnuáÆ¬y7Ƥ37¦M·5|-ŠF‰$•m!9uãhõLP™[ðÎu5Hbå®Vi‰_Àò"E­´wÕD\b÷`ݬkûЕâ‰0ru±Æ¬‰0°NÚ©A™ïx[]d† =6«ÀëfåYuðdF˜(DyášPË7[j#ØãÒbîbæl¿¿gß%„†?S†…T:ÊAÓä’÷óV¼ÕP²pœbo¿°¶`j(3ºb-»SÝËÌÖVP±Á`ìÎ"Q9TLR=wb1[­asü® ¨ Ì-¾™ý¿ª§bj™œ9ÃTç³af ..V&‘ûƒ5׈j-ÍóN;Ká{ðÒSÈTá‹ÑÂÚ}a*XÏgAb¤µ’¼ê̳È|dŒ† ¾ 3ù’b–EºLÞ+°YÎMc¯Ü:oUÏ”6Ì+}㨑Û)-4üõ1Q­ÌóÕ3Hr,ïhõðãÓò•dÉÚª¿³¹ÁúcùÝÃa9/«Ö M0+M4’"È ~nýÒb¨¼fUÈíÌ„zøïf}hö»6jP W“5öíÙë¡}fˆ–}m>_V0d@ úºúÏ·rë\Su­3–9Ïaõxç™áÙì|œ½§Ù¹y~r\ä¢,lhQ ŒëžÇ*ëˆ4©ŸgzIs(Œ¼ÇÅZ×Vêæœ-™4a/fˆÖ4!«O)ØêÊÁ£34Ë-YbY ¼¨»œñêýõÛïxa€wÛ5½=ÂçþÃ&ñÂéž—þêÖÛváÇîÚ¨×wVãr5£J_jܘ…Ù%½Ë¤ÉkTó¾KT%&wÒÙ0WÅ6-ñtì™ûCÝ‘e8…Ù¼røÞµô)ãhJ7»Ý— @e¿DõTW%Ë‚)@Š˜À6XSù_e1B'N°u“5±Mv-Àv-PP·u1Á(-©€´fÚ6TÑÕ¹™UIä6ðŦ„oDx‰ ƒ[HSбs¿öП½GiÏÕÚ­Áá›u.$6ã¨``-š¹Ó-¿Y¨™£…YL¤Ï‡‰ëÍ>±Ñ Q˜6›r÷{ÍuÍa,‹Ù‰“xzJ õ4ø>Kï›ùò¹gðwÜÞù¿°ÉÅ|t¤\²!X»X›ëÇ  MŸêŒ×5;äÍô¼¦±e{ÌÆF§¬;¬L QûEÌ"[€MäT1kÌò¸²¹1!¹1kÃ¥ÁðôŽxvš,éoÏtÆb\gˆèp˵_š°Õ\W[¹N¨Í’-l~|Wb•3\¥}«ý©¹V¶¯©+»ÖݤëøÖÚ)ËB·º:ïFl; P³e ×ók`¯›Ï•‡ø»‹Sxy½v˜DÜãã¼¼–ª¶ãòo')SJÖ0¹—AtÝß½–É|M×ãJ½¢*×±èš4ôL§Aå¯q× ì1*=3ÙÓ‰œH›uØý¨îЛ_¾Ž«{ÒcÁݲ¼DEtlßÂmÏ~ [_„¹Sv®U;/E‰XY½(“§þìÞÏ,eÔu-²ÉrïÓ*ºÅ°ðY ØŒPà˵eçGë&¬’ú(ûj Õ|Z@Íð÷T„Ñz¯×Ùˆ+Ç4Ä×4õWnÜãâ¡ 4Ë6ôÀLTa›ÐŽ.쟒hxtMÉÅáÎãC÷± wñ6W?ƫÔ6ØÚy}˜óÙlr·€×OB^¼I}ûÂ;Ë–ìuš×I’êuÔìr«L„ô"äY5æ]²î÷V¦"Á°Bug£¿ù^Hd>B­;Köçð™å<^pÏC”¤Š¡ËÄ``,³=sƒÜ˜¬]FýqDQû×eGhæIcŒýµCb¬ÖÝ—AªC uè Õ³bú{³Ÿû`PÉÉvQÑßœkbð¶iT© Û!{˜Ìÿt±R:S‘У/ C¼pæê½þ'íÆáŽ´Kʤñ×0:v?P´uM©ŠyáPºÅ8QÊq(6YÂ0¶à1<;{¯{ú®^³Kí9™™‘]nöü;”­y¦¤x —]ÄÂ,ÛTcaVIx…œ¬ó鬠»Å¹’ pcU±ìÅ¥l×ÚÕ³C½a¼&ÑŸxqP^#Ù¸­CTÃ{dÙ3Šho0?yƒÚêÚ$l¨fã™3Sì”0½ KÕÈâå7›]e1ö4ÿì²m»Ä9ïé­lÃk[…ÚÓ'ÛFoûØ©-´wJ!± ‡§¶]IÙMµû=}ÆäM(CGšŒî£=jÛáÙѸÁÏQ„26$qòA²å} …sz ×¶ÂT "v‡{›ÙïŒÄ/Ñ“µ7&¡«ŒS­K€@‡±í8FÊ6Ð) åþuÚÌ€dkׄ©#%£hæXùÌîÚ¬ 䦥¹î1 ó0s–\@°ÙœEð¼æ~ FœŠÅñÝxršš§Ñ…GSÆfV*] >™ éZye[@ÏN<'ìõ³7KÁZcé\¬=;R´ÊzÞ“Ãqîr I­æhªæÁÇØÔ©@L ìÚ˜àdÝù—|&¡½‹ÊP2"£jÁêç`ò\–Z‘ÜÏaàu×{Ë®w£ù­À™Ë«ñ\9Hìü•@ؼĪ¿êjÒ@`4ÜÑ—ìã£tI]ÝC–Ù%h†Í½1}HcVw€¤÷qwýÏ^gë v:ù•s˜kñöÁ–Ø}1iÌï•›u×ÖV¾škp\o¶ú[œ>ërUAÍ.(Êž†fb•é“îãEåºä.$[‹;ÑÔ3N·˜ó‘/jD+õúì!¢ ï,ZUôΉFš“àwòðquÑJ‘ž5¦¬ aª/Âþó/ OEý[Û- F©³`v4`-l›!vÇjÐ’L²n§4ùÕÚR'v–Âëy0nªó¯^+Ìb¤šfU(b»Ó­B¨Ló¬ÛlI‡Ü43ÑX…G*‚X—Oe‘]c[°ÆÀMÆ·ÓÍWsMVuæö´É¹(ql>ʸ<½­³³Of0¸JR[²1 ‡B±æ,ùûÈ&ùïoÉLœ²#ÛF…?£-k¼¨æ^ò*´=^ A ɃËT·_lF‘Åó`ÃI¸¼Cíúwn¿bx•AK”¹×@“ ”†¨ü°2wÖ¬÷‚™ÃiåþwAƒOY Ëí¥‰Š®f²ŸÛÒÉh7¼¬£b ê¶è‘ý­Á¬ÞkÈ#¸ÞGv²Ÿ|1˜Í_DgnËÿå¥Kå‰/Ÿ×ºçûmuë/z­Ü§Šœs뎱žæFît\ ÷4€{uðYJÆTYp{6˜‰•è2wdÛFÔÏ•Ïò”™_Ý1á,Ö“Ù)&CnúÔ{›‰CRvAÜïJç6ªn5³ÁŒ©Ïª†ç{iôäÏJp¹¡²^‹LÁAëUëÔŒdplcdŒÂ}­2¯·rÜrk?{KJv°]AÆX¼7wk ÿ4:ë2|Áå kZ8tº3Át3Ã"%uΊó`ÔmÓ O,žQƒ#¸öÝl êóSŒ¦xsÝt ze²]3TøÂ°`Ó†2¸¤U‚™’ÀQÌŠõÄÆ¡ÐíלgBJþ–ÍaÎ\ò·ØÛGÛACm!ÎP¸š°¯˜[ÁÇŽ¸õC®«Ƙ3!ÈšDl…ïÉ(é Æ2 ¾io™ŒO°…Õ>¯­h˜˜I Ä™eÊŒ¡~žZ1¾é ææ3E'Ýx¶ݨgG.­Üwe‰y‡öØS‘n—³ÖÈ1£Õ—®…ë^$…W¯ó¾åú[ɤÜsÉ9l2ya†ÿŒEì2€&öGÿþûX„û;Û½(Vè½Qþéw±½\›ïMÙÝ’ZÅJˆûïÍîëÖQX20,›ÿ¡Õîo´9´š'Û3Fíç±Ýß+µ½ïÒƒ™'ÒHñ]³-`l†m¥èš3»ßÖÆQ˜‚}¢ÏÑZän± U›ÒZ‰ØVØÚ^KÏÎØü›šÛÐ2X±Àìå_‰÷‹ŽWþ,t§c{Y,fìÛ­rÙ¨µÏt5Sá&ncF“’Åë!™²ÂL.ÌÒö­çõPtUØYk+ßÜ`†Ü\Ò;£>Æ…ÒÕÀ€(œ¹¿±¢ÑzÒ-‡f›0mۈ輶Ü7Û8 b>’|É“™ser°Š¼i:7JþŒ¡:'Ö}œÂ†Wã ÝLmúÝd|þd¿ÝºÎ—•C\ó¼‹F3Ù¼†ev—Cµ² ‹¥N]G%Šß:Í™DÀÄpÉb£yóP=?¦>ëÊN눚pÍåZqM)öÎdÛ¿Oí¯óŸÛÙ,<^VP»Tïeão~3«e¸ŠDú/-ߨqJìæþ°_Øm±Cþ슶€3Ãäÿˆñ™¤ÂÆ—šˆ¿xt‚ó%C3Ͱ\dµœ þ“±`c-“*˜’.´x`EéÏl Îô»±-‚ØèßõEjæÍ›Žûúf¬ÜÔYqÜÝäõdo$šMmjv¶à®–ÑÙÚØð9MÙ³¡Üldb{èN7VÁñ5-?pzŽDÑ6Û¬³0Åœ4 ˆ]\•™b;˜CW2Õ»«~†È8ÉtÌ™8$;f¬õˆ®-ò<ëêR™’ܰøLCãóÅ{n„ó ¹Å"QzÆL¦(~ Ϭ¸¦ÕïÉY èó„lQ½¾Í¬r¼l¥DVw…Õ™ea³°Ä­$æÖø‡ŠÝ³ÐÇaôžqBU¤'n-&̬3M~ïµµBÕý·ÖiÏLt827´^ç¯ ÔåX³CKÍ5!(€Bœ³QùjUÿ'Ø®ïûʬ¿£«a“e¤š§‰d%?KpLæ¦g&êkÞ„keVÈR±‚h N¢%¼eU\0“ð 3ƒä%±Íq»+ OX™ GHeѯšTf{# :Ï)å½’\+lŠ«Ø©ˆ4kV¤h,7::Köi˜÷‹˜8mvÔŽ²–VÆT\u]¾Öâ~÷ÝDzÎK`΋†,ï8JÿÖ|Qï\br`º($BÀ˜µyDY#RÍK1>4Ê@ÇtÀpœ¦ÅM(xùÓ¨¦·ïVpÝ6Y!ÛAÖõ šçôâõ÷'ë,$ÁwÍß%#õ »>°EQ‡ÏäÝìE]±‰Î‘ü¼½M?ºùoY c¿ù7ýŒ­î|Kº{]ph™¨­æX01Ù_-@4e°±Õ(Û”ÑèNcwÃÖÐŒ=’Ìa³™0‹«Å®”D65ÒÆZ2Ñ}61De‰ùƒ’iùyŠß […hDû0U’)ë&& –wÐÊ IµNÔ”¡uý‰ìB3\.òm¼Q]“å«;sŒk¸··ªb<›y³ÜºšÈ³/£Õ;Cáí‘Õµ÷×–ƒøjÍfÀ:ض™ÕIò”y;}›NR#EKÖX3gÅÖ / D¦m4ƒÔü,ÃK×JáRi4‘qXhK ·Æ1Êì¶—DRkž—¤ØY¶0¯ ¯“Õ¿3»¼ÐØt™Ìn’9Ó蛿´¤ˆs{Çž’‹H…jƒLº›VuÉñ(.ª%,R¢éÝĬ—‚ÛõÏžîù {ÁÎÞ@NµyÅ'k…Z“J÷P2œMtû`#›K«ƒÊ¬Ý¨çOÁö\Â1ôMüN{1Ùk¾$¦[T Êkʘ{Ð^†æÐ…{n¹1Xba—±E©Ã™XDrÖÁrkT$ÕfNÃ’Ny&ãA;hÛ´h _G¹Rmvê&7Ž:îIã«ÞÎX»„U=TC~3É”ÌP9Ù½Û‚õ”²BÝý¤3Jqh÷|&nz#ØçK·ÇP2F&tùŒ\u»ŒF N~ÇNã·ç2Ö•[ݳìðŽÿ#±õf¿N½ ã±³aeÁ’ ç#)˜™Ù y¶…줞°Û¡F дn¾\o¬0YË·ÙO•|›­S§‚ªäÆ1›¯ ¦oæð¼ã3Ç ¢«„šeÃ4À de.=ñß­×±P™Ø9Í$µÌ´É¡ƒŸtî­ ôæ%E«©•Z)Ó6Õ+H§c×¹öCäÍáµn¶„²‰F3UÎH˺$•# “S¶™ÈªËæ!œ!áI|)ØöºùI@.ÙÙÉ™¬Hæz”¬,+n,—¼ZçTI…Ä^î‹i‹e¹OHÀšÌn еš±0Á|Æ"§—á#3œê"| Y…gSx'kÂTᮽҠ’Åæ<0Š »˜ì‹2ë§St«¹ãªqÖ²+Ô¨ä¨ÑU°êV†æ™b6Óîhìü‹¦NŸ£­k>¢/ïeÚ\B~ç]`&ÌaÓo²’,v)³ö°’VNLîh§K'ƒù2d¸÷±0/ëe¾á’MB®àè°¸̃º¡•3Ôz)Ô@˜Q»…gÄÏÐYxö­ÎDJº*—[ïg,z¶Êt¨Ù¼ÌÜUep^¿³7Ž»8äºéÄd@!ƒº†Vª™ìv¾à´¬Ù î!dpî*­RÌoÇmÇÿ‘X@蘓æG²œ\3C"q³ö=M4ÔݧÌŧ ŒEE–ºiRuªÄ†Q¹‚Õž£ —~'‚ÈÏò‡„ t5´]3™ÖÌ\ú}„"œ7‚=õð'ÍÒxý¢®>Í…é?T.†A¢X5û¬edªûâwÓ¤¡h¦Y=ïªØ³¾§õ+K*íÆ„¡Ãl|Hj{4 2+ÿ}è›»$ñ,Á䯆CY˜SÃÄõÛF|E|šuŠÎÁýŽ m“XŽk0®Dæ±Éeêä#@F`tLpö­-Oµ¥}ïþñ¾õaø£L¢ñ°ËĤØè¥e¦f'”Ídâöá;ÐKÖãEÛÏçWü3v¯Kûy(:º Ô2åwYZ.®Q~FccsáÎ:[5¢‘Z£†¹‘Kïª<&YdE‰Jþb½ÐLx$6Ö½‡'¦Ë3붉Í=j·fÆ4(Ìàx´E–á­½Á¾dhV) 3”\1½—[ê–L%¤‰v6÷Œ’ÒšÛ6­ ‚º ÞX‡™f¿ãjú o…»ú ´uŠ—•Μ’·†Åè4šïß‘qÉ "Y”q€ÜôÀ¶gÛbw¿y 3ëd/¡Ï€ ª,Ï ½Æ4K;ú*8• ‹Ò0j îqdÀ«°[kYšXW™‰…$02> @%`BæM™˜-i4ËTKÀ«%ê‚h‰­fÀÉv5VàÔæ<³TŒ€%a.iús4³ÄÙ™.Âý}j΄Œ¥Dš9Xn‚A ¬u‚y-c‹¹#%¦;~÷¸ÕY”󱎾4cåGV*“)ea¦Ý|$vÀ_’.šõ«ƒ°Îx‚EÀÔ³¼f¹¼î9­¬Ý ;תétÌ,Îj¨3Ù:ÝË›1¦®'Óç)#¿g?„Ĥ¹Ý€Å<Ã\H6¿ÓÈ,éî²EBÒ‘o¬¤3Ã"!#eRÐÅÎi“‘åÁµì¸¹¨–ï-ÍóÉ0Ì/  i£IFÞœEe¢@ކ•yÛBGí7Q&iɳdáL²Î>iÉ^ÊΙ½Z䬶š­ˆîeÂÁPIý¢Ó ¸6ˆæV”²q{¬AΊæ•ÜTØŒ›²|À]6Ñgˆ‘<»JJ—Êì¬ß$HÙ½÷Ûª?C.Ó¿zÜF]Y-n k¸‡9#¿ÏlÑ ˆÌÏÅ}%<ïr672£Ž}T)HI7wPYnßÙ2ã‹A êu¶YÍÝm •½ );X¥ †2ÌÝqxLç Éor@+;GYµùú‰uÑVÂôlfabt[S&Šæcà 9:…–wHFêtyÊÓ#J*a§¿ó†G9ý!Öê‰Xø9½”y2HæU"å8 kç+¨Ð›u³DúH+º %ûdºfÜ6º•ŠÅ¡*B“ 4Í’1äæ=7«ú=½±‚ܧYÿí2·Ð€}Ý9`V[0dhî¡uX `âàl©ë°äès¿6 [igìþ²Z–m:´’¼!o—2ªP˜x]Åõ­œ²L0ª¨r9Kƒn³µŠVK:ò±Õ$)Î+™åÍ’¬±»¦Ö{î!ºzŠ cSœš_{U¤×é§Î iôEWõ½Bf7¬Â+§¶¨¢€6¦¡z.Ådw³ÿR€±P,«y³¼#gb°½™Ó.x +í²¡×s×Õ.°«=V2inÔ?—ÍÈDóßÜØˆ`§>R¦‘ýUæCqÃ7FýVâYŸ’,X¯ûßipÄus¹‹Eý³v¹+†Ò¼i<æäsóÅ”!ìóÏͯ3ØvV±B¨ |±*jÈFzÊäïý¾%§Êò¹&c8~èdCãÊ-‹ÂöðÀ# ©·€#«+^$K”7nyi r‡ZE’bÓWÛ0øÎä¶TF îu«Zؘû£›U!|bÈ’•t‚®ahB¢$˜c†I1>I½Ì$‚(%B!–7]謗—VCå · ¡… ›=³íNVVH©Jßб¨ÝØh#刔bd[H2“É (m¨·d–ÖÒFÓ1ŽQâÆöš“ùûQI Ýw¤Yb䡇ökm°è²)×k¶YRuL}ã$sŒÆ²ÛÑé8º›¡uˆa4LI@ãý6Õ¶¼HŒ€(²b*Û¨Ù[…|T±»Í(ò*oÅç(CêŸ+åþX^Ϫ ´ud»¬£©vl+'ºµ 7¸Œ7?/%g‘Ä5ŽE‹bÌØAtñ2¨Ò6t’M•2Ì c™˜è&fÍâL‡2×éeÔ5Y`â;QI­Ù²rNý»³×¦.”ál°œ¬¨æiMœqä@†dÚúZ9"‘ÞYÒ¤„{‡u¤œð`-0“g±.‰½b'¶êKìDAÀÈ„çl0xð“)SÜ~çÿl 5Åüë£u¾3×Õ,Yȉwäø¶™6å'ÆÄ¬AI(úÉìû@ÈîÜ8ÂÇNƒ|[uoЄï±–¢ÆW ¡*W1Æ‚F? s1αq}¾lMW‘¦@Õëǽƒ‚ê-©l¾T1}Ón*çOvÏGh–j»HÌÌö3…Ü(v½l˽™’_úµz†JÝN855‡så ½ñ6M©Ð$ŠööŒ ºPí‰Õ¯(DWß°žì‰+XȶÈÔVÏÄx»uvnì6Û›IñÊ D1V•Ô¨—Md¥Kb±snºxÚEM*M¼Ð§§y*® MØöBÚd§Åø¥Æ%–%–«Œ¶m÷âªËÞ°¦t^³yl‚Ãëùú1•óeQ2§71ÝPÏ©bd ä)õ§4cˆŽr5ÍJÖÐöó—ÄaiIGßçæDøF aðƒ‚Y…4C™‰”áoaÀa[E2VA3רf¸X;G¢†’+£€ŽqIfö¢¤FªÀW×9³ŽÝKÞSªC’dîªYÀEhyU´mî4 ýL.Bö!g³›Puxod¿B) ˜øì(\Z+ôB„; +ztÙþ:­RÚ×+Øû…p5µŸ"ý¨ •ó§´Âqï´¸"?ƒšÙ¡ l¾Ð’ÌÉ… ‚5¥ˆ˜™"ÏÅ5°üL‘Ï ¶, s[þçet±Íލöã1ŒfªØ«÷3ià šQc\2¶Ñ¨–bKoµm•fƒÌ$({h² ik]á‚ÎÏ¥ìD’9ÄE¦GfAÖ³fHÌ‹ƒRæí{©U‘쌻Ë3Ê4 ¨þwžÃÈØ|ˆ&ÓÓµÛ–Z_Bo‡`Åœv>Å’î„ߦ «½Ö×Ê%ÎÐeÓî+D·1¦Q÷®“qƒUò˜À$6}W¾XDÊÌ\¶3Å™¾»Ñò£ô†ºt”E¯1ðDôæ Ô]~cØÇÓêCY™3†¤mn’¤Ðì§,s‚4}HS1Áá©ëI²å¢È&×fãàa⚥2Fh5£lâ3ôæ*@(ŠÜ”áÅ=×Gä` ‰ ©‹Åè ‡ŒÁn÷ãú{V[å$et>)\éD²½rÀ4èl_l*“„eSrBåiØgŠ H%OÉÃÍæÆ‹u¤ŒàBÉý⇜› f^_1B;$C@Ûígt£L‘ƒ`™“‚\6²ü™P˜I3kÿ7‘;³Ê^RžY¶iv³ê ZIOœãõ6K>âG,ÒMÌl&²2Ù”@›9dtL 0XIÎT& C—s=™¦Õa˱èì`¹šááD‚ØÌ•YRì£eÝ25Az~ §AižHÛt$ŒËEÔQ¼Nè+Oö2H¬®MmO:+LJòŽc9ʆ2”=8@0T $b0ùüÌ ­ëáš-nuƒ"3ÔÈš;$ŠVu®ÓFútŸÇmÿõÏTF˜¯ÃJ .´Y ’ÃCÒ=H,7±nu€$ŸØ |Yd7û®Ø– ˆÉ!”uÑr˜Ã0òƒM÷¡§0Œ ÆPm±×ŒcbÞ„}¡«Lv\oÚ"­Ó…S»HÅêÙ a•½Í†r•'BIR£Ë˜-첺Æf1%¬Ó—W“ú¿/¡ùä:Üz~2Ý*u—¾÷¾Öáç³DùX*n Òj7ffw9Ò€%бujl-HŒªs9X•6]hï«XñlĚƂDåÈÂÖX1ºgÂwÚ……(}È¥¿È‰Ú›“P5‚““c !¤‘q' !¶zm3m(I&Dh([iÙJdÖŽr›²Nv^근ϕؘr…Z¼Ç2);s¢T)qa‹Î_æœï²Ì7 ÉÇQï™5,Ø{Աђ4|Ts’Ö±{쎴J%´Ç®~ì:[È¢eÍÞìd¾4sÑkj¹Œ}Dͦچµ¯šKmöÙ¶FÆ'6(£$³yN#«“IV-7.Š{[T„¤FF;ç š#¶ÿ÷²^ªØæé…Fg’ Krƒq¶ò.?O[2ƒÌ1&£û\3sdcý ›çZ*¹[͸XÔÈ}Õ:̧iv|ÂUEN×`tÙ óulÚC™!¨†¤Øldf{ AuԞ˖tóÕÁ":Ìß¿×yhªð¤ëBº¥ÿ9ØtÝÍuÞçšhf<ÏœU†I?dU_Ê( çêýkãçT>b¨,; ¼÷lArv.-‚²ÇÚ Ü¿×–ùÅv6ê²°¸çŸå̯ÖhÇ3úßM1ܾIʵÉŒÜøðÑžVéïÅÞ1ã†AšgQà ù¨oví¬@Jø¹ÑíU¡„ÖÐ Hg èL‘8¯2"¤Ø–ó©LÊôÚoœµò‡záEeHkd•±Ê†ýªtª‚¼o"0Ìeȸ85+'M‰:NY™@̬–•œÊ,óMS‡íwŽ3œ¹•–ƒ~F¦DõoYƒš4Ô¹¶eoÍs‡¦òÀzk[=/¯Ê?̨tñ˲¨”¹J`ˆbÛô"’¹-²ÍâRyUŠÅhHšæ÷`¤|ó9¤ùò2 ý™ìŸFBf†gzÃa¹V3Væ0ópüOÎNÎá›L°„t6ÍìyÝvK­ßBYçN”&º¢×Äè˜Ìó|.¥÷?>L´ÛtÇpˆZξ_ôÝãg7­Vwü\ïç{EþóØŒoÔfÓ57caX=70àD[×rö6Mݶv©»a¡+Kîxȹ¢?‘†oû†tæBW› HЍޒÚp ³œ £{¿ùy°© š}Y¤R¾¾•‘…?l­]޶sýDZ“”öNæ+R–(¡Ê€bÉ[i÷Aêµfj÷ÌUèµÊ±ªõQ¨Eƒƒ ÍÖÛr´ŒdZqˆ¤vôläçÏÈXxÄ5O›¢sˆë<†à*©ÙºÚêiÙÖP¹+)u}]ÙsCìt¶Œ¢l†Eæ!s¬‘–öH²‰ØÆTdÁ‘¦Þ<‹{ðMÌ$Í€ÔÖØ榒õlca„K³Ó8´ŽÆ{l[Ä™Œ8iȘlΉJ™4½‰ü¯{+†'fØek,4gÐ \udÑI¨=›ÐÍÑèi -{a_‰Ï)Ãþ«ärHr÷"&µ½™ƒìí%Ê /0ú]7f¶QØìi^ØY¶ïýÈ«Mmº€›¹ˆ4VÍ‹å{vØœ4c¶{ueˆÏºÕŒ”?€gîȬ•°HöÚ¡jó©4 PÓ”m.,š.`Lĵ ¯½¶̲tÙI`œÿÝêÚûá ý&[êy>Gû:÷{5óó1a]TˆÍ@µùPj€e#œ»K*ÛN¯Üþð(æö`XèߥVÏç¼{³¹(Öy˨ufa²Ø"»Ü;«5ì^ÓsôÈ® v>øMÛz’¨^Mwÿ<{KŽp‹=2ÝŒj¦M¦­¿^6®•º§¥h¡/L¢e³‰ Q5&h÷{Ò5BI›÷´4X˜©Jyg ’·oš’mŸS,ŸÞfvE(¨fJ´#Vy! pͺÈŸõ¦ð¬U±Êpz²nv©¿ë8J§µì¹ã†ÁLÊæ¡#ëÊåi=.èÒ›°VqнY/v õä>Ks˜=>}ºa”3cÁV­ìõ}˜¸øvŠØ‹ÆÔs˜®1Hkƒ*ËÍÒÎ&;á³@>ÖÁê+_„À¸X>¶óZ˜èÌ[8CUnÏö¶l}*£s¶1ùuîþ‚›žŸ!¤¾ÿ"`ÅÎ÷Ø\…®^¹¾ žWA;ûö í¶Öˆ™:y‘Î^fÄÖjû–¼ÀV„ånl–äÑ ß’Å^îG+骯•©9ˆÝCnÁ='І…%qoM’ý.5:ÑñBÍŒóò”`BÚlM±ÓZüw@Š,.Øá&1äçÁr’¬ Îum>÷&PU ˆÂÒIºU¨}δ}u“»d&öÐÙkôDÿçX ]—9I®w P…{%FQ(¥†;„ê‘bf6Ó_—««³§9’ìÖ©käß况ú–ÝÍ\-‘ÖŸ³Î± {“bê¢5tª&@b:±ÕdT nȘ»„= …þýRa€L°G•oŽÕ€ª.@@2TŒŽ©÷ÏE/cÌvªŽÐ êÚ·‰áh,57AŠâÅ@à2¹Ø}î)·³eîÑ\CÄ&»D1h¼ãO7XœÄVm ÈDj–ti˜B­‚m‹½Ï=Àl§ïÓå;²Üª½f£è—gS§æ‚í8²Î§åód ÄZ×ÕYm!‘÷‰}L#p 3G&ž_&YH©ÔÌú÷â4ÿ÷=W-eGì~†¼`†ºG-jzÑ2mÞÖk^ðs€Ã0NöÒp§ž8Øj;=X ¨åý°6`lÜÃjz—MúF—ž›ü¶ph,£{²¦Íèjó½£Ñ&;ïδéßÇ ,—£~üÃÍ5ÐcÁî«{sfÉaéVdvž]Åb3?CÔò‘b% ''ùÙ "Ÿ—Q£µ :m S™¤~At±¼-^ÞG”Âs4ËO¹A°tkÖûA#á´ŽÎ=J1w´ƒ˜0¹´Þ3än­ÛK@o ÞêÌ´ŽTOeÒô¤ƒ1« ÎïÝæv²'½q›Ezo3[äèFhLB^¾n°rÅ÷`˜C3ë ð€®‘/%΋™Y¹QK™`Qö|çp2Ñî·äyT¹H«\Ù ÍDÞ“œe¬#ôÁ$ÃÓv4ÕÀ --”{’`~2Ó‡ÌC™7Dæ8iF4Íšž»^ïLÉ€™Üû¡}ï¼\Ρ½I‹pû·½"ˆI»|µ‘ò<ë‰Ê-Ê86pÉCj»|Ë Ûb¢˜_‡.ÃÓa€q€ ãâ–¶€§Ò9? cccYÖÌêÛº8¨Ý8ªë.^DC éëúaêL¶‡ýßûäðd¨‘-ãÄ„L1Î0Ø@Ø8{KOþ‰38:`ÚZ¤»ºÿ™}ëVî.°ßã¢ÛõU`&ØwŸ–=…ÃÅ„á„LŒÞa`ã]çòÔ˜g ­äODóF0mñ ›¸ BÀ¶½TÉ”£ûe(þ¤Ëú`4™¿Ïš—k½[?“~9Zë,ë•ó°‰–*9¹g¾@ÑA&Ú¶Ÿ“'LA3SŠª¥ÐRr_œY(x¬5›ò@¤´$‘ÃÀܹÎNF(rªÅ±››x¢íqe.rZ¤¥0û Q™(ÉšÏGŒÄ Ú¿¶²6öÚ2÷`}Ç8H,Z‰›˜£é ÒËü'Ÿ¯j•9è×|øÌ¶%)͘¿Þ¼U>#RåGY6‡äÞCe,Æõê” :g(†'1 –¶þ3WÏ¿mœæù ÷³Ê ìKc‘ß1ÿŽ|†,+ÈŸ¥Ôî^4IbÈl´&—̆dÙ?cM¹qËCÓûšÃÇ=¯Ùýû8´)_ÃD£Ž ±=ÒaPÝ´ ,ÄCÖ²›úóïm5—¤\€ó:âåŸ!b¬ëhu…2o»›±«ìš¢‡íh7nƒ± ÝÃwGµú¹ÆÁÔšKšÍ®q1¢6Ãðëp°)Ê¿ç$±caŽˆó8àŸ{[êÙ…}?#~g›e¶ÿÎn}ƒd¶‹×Ô›£‡"sœ™¤%̾LŠÉ'Ý9`RÍƦƒÿ¬A9ä!S9´-fýŽ …j%?b(”’ka¾YP²Æ:'fÓ´a™«ÿž5õ+JÁ½þD]ä¶îWVËëàevl&Ýö×^{Y’Œ‰¤IÈóÆöžTku¸÷™[þ"Á&äªi0¨\ÀA»nÍ®-º¬pE\m¹·àm–4£<:.Ñí¶É]F;ñš×_L´¾bGR_‡vk&Lu·À %Ù¯¬›ªûoÎ%ìˆ-ñÊ,À6ÌŒ¸ˆ3ÕBâ}ÏàSv2±ÜôsmU“Gãä~¹YÍ‹&ƒþj./c°|h±±}³ô¨4Q‰ôÞSÄæyDÞ¸ŒšpB´-f,•fíYAÍV7ë»rº×QÚ>‹ bCµ—Ôj vÌ„E ¾‡2±äuxO‰{´òè3çÊüy+H>ýi.³b•…³*ûêmUL"'|;õ®!{X?j匧Ô?F¤ïfD²}epÁðÉżŒ%)7رªìÝôz)~(8îWR-¦HUA¦l(]KœK]ô7¾Û4uÛÇä>€R=SÄ\š€:×,q(Ss)n®ªÍoeýˆúR©0Â*k@_×lèƒ 5`©Ÿ6ÖÖ¨æ(Zb+JM¤ööfÿÉܪl£qâ-\­¼LbC & ÒEñ!{&÷ŸL[RX§3_8ÆB¿Y³èdCø‚†;daâ|Üjr<ÏžÑ0éÀXô,¥›‡5 `î`R3"žuc°³Eg÷¬ˆ³¹@žNY@5ËÙ¾*^82ú@Rf²ÇL.;Ûm8*eCIXofKÏ„5Šl™—O„Z‚Ã)ÃÂÁ0³6jP%08 (X*_í(†Æ:סjœ(¢åzyÖ<ïS±fÂeQ],ãÇ(L] óóXÖîYhþÙ•bxfÔoÊRÔqMGv<†FÇy8¢¬ l¯ç®îSqÆýlû5†—R¡m )¾—Íãz¿][OXBgØE ýæÏ¸ÿKRý/Nnjë™n58²õ¹0N’±Òßk +²¢rq3È[ä~Õßoñî5âëf¿(ÿJ¼†üOù½¡ÿ3ÚZ8˜¾¿ ¨?NàhßÇŒ#÷Jn‘8±;V·B–EÀN…Iÿ,²Ùèdà»ÈåäaX²&·b/Ìl5Ž0õû®±Áð=6ɬûÖV½¾1¯Ð-oTz³À®D="· 7u7œ Íòæ€u‡*C×q>Üü¿GzË)ªf#…¥4UÓ˜ß=Ï;ãZB>YØÜ¡MLí¬ƒú™°ª, Ïoêå³ôíÀų¸z°BÑ\Y’ªŸNX®¹µ•Y˜“ẳeü(ªNoݱ3Ïãý0TTzÔÎJ¹]AqMc#£yÆÐ{ÚÄÓÏÓ‚hÚ¼žFØ-Ï=ÀŠl1M0-lïYP¯Øþ(_±Òôò ƒ ¬!B½ao²ûgí^¦6"_ÄÄ [Ò0b¬T¯gõµk†ù-?÷-¨Ãƒ*BÑK‹… €½-ãY[¡ÒñÅìÞ[!Ÿh“h›VµëÔ„¥0Ceòé>{’:ð«“}²GmÐÔ¢ëjV‡¾™a²«¨aÝV¶mÜ5î×5Õ=ÛXB–‘z•lÆr“¹ ³¶Œ hBó›*ÐêJ†?ÊÚKi,:O{àzJß0-u|Hò`D6ÖÚù~cÉ#¥çRªyÑk7ÆnWU(úàD\äØo÷¬‡3¥¤Í¶×ž×ÕûŽx“âôžl.v+ÕfdUEod,TW8se‰’)Ÿ'E5ë`u!hŠû.øÇb±Œ%[¬V ìàN‡±Ew)Dm nâÙ®Šqk»¶M†Àæ(P£ç<,8“vY̰&RV(ËÛQŒNj4 XÚ=”ž}`¸/ÙŒÂwè’Åif ÑäØÈÌQ™CijTõœ 93¶Ê׿V„ Åì“:™­îg¹PêU‹ç¨flŽ÷ʰUq],Ì~5@LwÜáš³£'oØšA ùw¤Î8óχÿ¾ÕÏ$6ô¦f6÷ì¡¥²}¿{9@z+;>çOlÛWÛîI‰´Nc.²š[élÝÍ´"‡±0ÿÓ“¼u¿OGBDö~²‚»,f³úy]5ànæ£êÒÔ›jÛX³ÆvÄç í²ïݺ~¯€‘ÚcЋI– r€ZlŽVSìëÿYg‘u'Q5¨•Ä.36êÜ—ö¼ñ›Ýƒ¢ a®[*]J­%`|e–6ÍkY þ¢úÈ—‘&B¾5c+%dd4ª¢Êò°ÏJaÐntÔÅh5Ç…U ¦ºæl‰ j …vŸPž—ôèZ,B!¾—rkŠ.T@7c©aJbq²¾ö:÷g:¼5t®¬$Ç P’š×0¸Ú©¬”ÜgZØÍÆä{›`¼ÔÞD¤ô eljn)ÞgP»Bnå×dAMª(ÍÜöLÄPÄëeŒiJg”‹r#o§^ëÓúýW2Ú­SMÕ¥M䃕l::$B°‘ÉÌ“%EmX°'skAߊRT¿‡*‰s¶/uìÃ{QjÑxÁ’B:“ó]Üå´<¦ç…$ÞsS¯AëðN´Ísiëèê²â[ìy®ŸØ'Ö¿eDÓð»q}è­`²!÷Fƒ¨]™˜Œ°s‹¢Š€ª&NšZì[–u©c}Õ¢Ï7Ãü ôÓPò¢—XôôÀ·u0ή‡Ats³k*‹VÛÁ¬Ôù9Í»¯–tº*g5nŠN(r”‰w?zbüш^«ežËÐ+ªhë3;“MÓËCîèÖøw©Üft”ó »xÜŠƒZÑd—„õÚ³5[ŒÝ_ëÈ% *5o•¦¶0H•›Vö³®(J2iR—IŠ ÕAÃÞ&¤‹ñ—“M-³tWnÙF ÒÞN°w!T†@n)NM¬u˜eZu,#­³É5î‰hƒN¥ù·»Kª1N÷}õ<úB½j±sö¯÷ÃÜýµÁßÚu2ÃüÒúf93ɶ(7Å^…s–mC¡&-×ãVƒ3îñ!ÄÖL7ž¢ŒØ6B°³kbIÝÞ¬XËü4µÄÚÙ¢\-ZZ &'ÝsEÓÁÄ€·‘÷¡Èìjöˆ<€Ìý-æ<…͘Ù£˜¶T^ˆŽ-´U“äV†’÷UFNëod(‘9–ÀÖ:ëÏêïfA^Leó é6ZoÓÖ4*3 ÿ&îe#e§Ù!ÖbOÖ)×MlªÕgÎJ7-€,Yàð<] “En–ÁõrÒ=x¾|iïo¾,!… È5ÆúÜÕ7-Žÿ ¡³Z{×<”ipÿ~‹Ó‘¯½s&˲ËÔ4+Ï£-3Z4Àɩ—mêL;SÑûà/«JÊÂ5Ña1l—ØŽ.* ºéL^&¯gŒ”å?&T*›Ø«:—­=éÕbˆòµ´(ê&å¢2¢ê›fpV¾æç§v²M˜ è&ÆïŒð>î³Um¢ñ»_Ħ4Öé$ s”eÕ¹ÿÂ+ íƒ™µFU<: dÔÑÊ0Í£úG;%X]Y‹²&4*ƒ¶Å0ÛµÅdv€Êb=QÀKLó¼åú³ f8œ"ÆõÏ©&}åœbýsó3Ç|° ª‹Ï½÷\tÀV:¦fBÔu3Š®eült{ÿ~î~EÁôpö}´×h->SÐÀ~'^uª¢;d“7PK)W)“™9àÜBÌS>`Ù–(‚‘¿7³>¡è±ŽºB€i'3ƒŠýÚhØ$1P­˜™Gš„`Œ“ .kL4’>æˆË¼…‰5¼ÚßE³Ç¨%Y{ÎWàâÙ¥—Ë ½ì3fo»C÷žÌåvteËQ­%V 3]7Ë[¾Nº7ª(c|¿k9Ê÷Lykɨ­ãâÀÅ¥o%8t°$Õi/:+K9á˸÷;#,Òõ"Qq4³ïeËlr(Üg•LÍqx©£ g“ù>…òƳÝ‹#Á‘=4âgW–×Ïú•ÚÎnø,¡j‰@öúc‡eQ?ÔŒA¬í¬5òÖÞs&Ò<óaVÕÚõ³5t²'&¦–3•Ùr#ó¼ÛêÚŠ+vaünUè$ÒY„¿Ç¬ÐæÏ)€_x.c¦æä|è44sel÷ȦYÓJí ÀqnŽSàÝ™¸;¯…:—¤± Qjؾ¹ø žj-º“5Ï~Ö­ßw‹]4êB[]o&ŽD<Ñþ(×kL)âÀ{õ‚áˆìIØcž2£ˆ°ùVšÈ†a6Ù0²²ð†aÊÍàjðýXwÕ¯¿EœG‚c© *h‚aíÍ:)VM˜eÈûŸ‰ð²èûh iÎî¿éׯΦ@Ï”#;0¥-  ;À–ÝŒî}&òÇ’ã&gFƒŒû½H÷c‘º³´MÝyO©¸HÝbŸûê$Å^å­Öö®aem o—§¥ ^`[j—ÍHdV›¤ŒµŒ…—ÍJwQÀÇ.•ûÉØÑ$[hÄ vÆ‘UÑxïGakfÈv0Eéó¢©i¦wq+™û»¨ÆAšfi«Þs×tR*-;îiëZrÓ>ŒÖÕ`gè²Va³M7ÙºD/Ø)Ã?Õ!«;—¶81Y8\luÖÚˆjÖ˜J=—ŒhÌÓ`-ÍjòjÔfVÊP¸D9`šoƒZÙ¼— Œv¼¼¥ƒöîžÆÐ[KX1ͱã:ÕZùÆáí¤±ßkú,ªOçÈj†ã`ÏÄÙ 5r‘Ñ.{/Ûs°žaó–Ôhg‹²õ#÷4?(œ»üó.óÀ$¥CÈYG–…¢ žž×eÖôÊŠã-y!DžB¨ÏÀÃÄI0€OZß@C{0Ì&UÄ`À0 ¸¾®ëz #Ó:à ä,\br)òÄç3h¦²Z̻ƼYã÷Múóaè4E œL³|®x2²Ò¢ØmÎÅ¢èô§Àz{¯ºÍl%mÜÙ99H/‹dJ*[òÂ7Ú²(ã ünÅ vf)¡•úFß,}ÊÆÄ²\¬Ð`5ñ︫b)2+ÿ†|@æŒÕJ,H$½ÈïÌ4ºmß?Áð“TðRûç\¦RØ»æÔ¾Ux``åvSRꪪΪûc1í½ú˜ÌH73R¢]ÝÏlo`Ζ1æœT›v- ºæU±ÊuýóJxW²4. u×÷£å~æŒÏJöMaǶk¹Ž=‹Ã3t5™z¢àlºE|:¹„óU”ß)û±.Ç(9z‰žÝ㇣ ´š'JŠ­Œi‡Ça«˜¦´ÙfòŒÝg 8^ f‡Ö!z(PÇ (Pa‰Ý=³Ž·5ìÖ$Õk.6²È> iºßV?WLlð³& ÃÉl@žÑÅlÏÑF:µÏi «•2 ¥×ÄDj9Û^7„iú|DÃćº0îÒ<ÜÚ2‹ß?Ì£Ñꌥ¨á&‚M8 +B˜+Ù¹g^rç×°uš7rÜÈtóc©¬ˆ7ÏôYýZ… Ôœgü»D®OÑd‹E}dyéÞœ÷œCºðŸáå¼ì „OÄEÁ«÷X/°íù¸]å»Û˜– ¡³K~í'ª¢’𩟕 )ìÙ4™jÈï8¸ÌÎ}Kp-›7™Â:M–Þ³ÔË,“úm…ÐØýÖVƒØ€ÌH¡¦sJ‘°Û[]¶ñâ&Öé"&¿gÖ–ã9ÐdN“ж¯¥jJ6—Áß1—ô,«²9·¶˜òÎH"Ä^ËŠÚὬ‘|ù X¢N–h¬e{\”ç­ù„CÆt@Ÿ‰5®-g'Π®†ÉœxGÎFÓ¨71ìmÙ—~±2;¾˜!2Ké$…}2£åt6gˆº{LKdwá½M{<¬?fÍÐù`óméðqŠÝþ<³¸Ò‘•™Ž ð²åîÑQ®xÔN²(æb¨©™lÖ×φ[H)#ˆŽ cÁŠð<ˆBtùlgm¼wfåìèö"Â’à°Ged ›|f¶ìáƒÃaúÏR÷p2îxt \ ƒ†!†¸äy‹s/f}†Ïb“²3sƒV—ëbý=´‘¬fî °Ä5źqá µ,ÿ®a•Dƒ´ hìÃYAËLâF˜ìåÏÓĺK ÏóšmÓ–÷úÀ/ûðÅf˾¶ñŸ-   ì§>‰¾„%ò³ºéÓ‚½aZIa¿Çú¢pì¢ÚÄÙä‡hy#“ÏL3‡1Ï!©îÄ®\ªn ´B!;׈íòtnœI.„]qÊS²Šl£VEo`\C{úÏ%Öèܱ–L“f•Æu#P¡µµˆÙú»Í| ´í¢—pUrµ‰Ù}·]ø•±CnlȹîÂIUÒ¹NU÷(ºáªÛ,Öµ±ù=úôw ¢RÈ‘â{XËXöl¨CBX‚[Ò}é,:ºæ¸ç´Åç¥ùëUfž!t™¡Ù^óvéÂS?‹®h6+ì`88ÉÔŠyL<[½ü†àTÇ„ ÷Ýuµý‹š­¦»íþY*Ò¹, ;>0¶ôŒ÷²MVߪˆE+å]êhU¸'&1¬µ—uA×\À…2ÃqÞwƒð‘‡Äƒ«i>©ZWÁ”„Õ¼+ƒÑB2Ûf;ž÷ªP´ÐÈIlê™0¢ s‡Tú!Q|X‡R$[FMÆ2ˆ†i^L7°, §µv¯R²Bs{‰…†™4ë²3Ô‚ôsß<¼è³ /„üyVÊöð^ïRÕ Ü.)/†/ä뜓 G¦Ð+üI&ëLþ³‹+vrKdOÁJ™{¾‚$È}[g‡u™ aÝ-’â¹lûÌÎŽ‡Äv¼Nó¿¹Ñ51è1™H Åu‹…ƒï†ÒRNp{µš$Òg– @%r½¥;h¢an@q¬ûW2+çµt3Í̱;¯Á|iXï7÷ÁÎO ˆ{zèØl•ÄM'&›F:–X.7sthiBÛ‹î­ÕÊ{ IUD¿ÇN ]™÷3_ƒ[ÓìéW3êU1\¯ºÊ÷ ¿õÏ1»ä œµX:“¸;®.2 Ü€ã0T9Å óO/£•±èJ[»4ÚŽâLÓr/,y@Y[br« ÌÖTC¹äUÏ߯ÀW´~Ž{BõìXÛèòL‡Q\³ 7³Ú‡q‰ ¨kží$‹«úî±JXwc¦õs ¸1\×ûë†a0Ø0ýÙ™ç4ã%’ bçÎ,›9¬r’gÖ{ötöêäžÇ±µk>[Û&î#ƒhlO05“ZÝnkŒÎ™¾ßàØÆ AŽa;õ¹LlϰÓe®74¿÷ØÚb‘”Ãi]:Ùó©,öæjJ £?üX\Ùåˆh4")_L¦h'Ÿ}+*#ý»,©ÛÏù¨‚)ƒ]Ùpx[zå.iLÀ;+ˆFô˜›ÎÇV¬ðV|Pö:¶Õyû¬eC‡‘GÝ%²ìðŠaÑ.QüþÅ)Üý¿Ù&(¨…½ÜGóÌH“¨åähJFK ²K`C6¶Áz–%Ë㥻m`³VªY!Û–Ö‹Ðl6oPamb¸ùWg!m‚Ygf ò­†‡cn—7uØ£ÃÔEvëvŠCå2Ö-ø˜?à¤lÈÈ ©y=WvÀj@ŸÜ7ÃϦƒŸ-ZQ¯ÅÓ‡ö;3ý2‹ÉßÒ`,1gû Àa$®Ž†óœØÈrV£eµEWªŒ5S–ò×5k$ìO“ÝÝÄ‚ë\5·ƒŽa„Ÿµ±0{Ök‹&ë4:!ª@ÏÆ%2Ëìq³@ñŒ¤r¡ŒƒÂ.º 9µÜeSKÞñ›ì*×Îðh¾ïÚðìŽm²Kç@X±˜ç£Öס…ðìæþ‹ëÈNˆaŸ4ÿÜ™£e š%LŽZâ>X0°àL›4½'6†%Ðk× ­“c±­'ZgCR¼qÝ´Vê¸Yw«p¾\i/ãó< 8¾@‰t_Î×OœÒ94 gÛ8dJë8sâL»oìʾ¶³OL°MaØqq®³ÐsaÍq'’¿BPæ)Í=âÆ²ñ¥;ª 3°ø¾÷!©Î3¶¥F¶ Ôg°œ€8(«BëEwÜÛÁjnê9™ü~ÒQ£ð®.» ÎËJDzÍd›@Ýõcˆ Ïk¾ý >E#-Ÿ¹P¼zvÆæ¶ ÿuCÂB¢žÅ°0[QÍF$spÕìBö²v½ÊfŽšL%ÿ™ÔÌ’Îoâìh_(ÜqlÚ‰6(¤’œ'¢Û£æSšÏËíf  ]7ÕU]Þi0\l’ÜE®ŠÐo æˆÁïßÏó4úø’}$·Ò*XÈã«bÁúÄ{n[4cø\]šèž2!âg»çS'¶¯‘šÅ(S¤À±|§_Ì`8L3m×ÃnœÖÄê³ãæH<¸†ei5k°è~E«šl­Iƒ 6¯žë|ÏìxS 0ÙѪ©ùÝöžWIwÕÞ¨lÛ“‚Dzp¨šBÚ* ºykâ¼’Å0õÉ.„”9çáù’]p_ ^„ï’ßÑ?—~rßë…†s5Ú`Mÿ¥+û^þe‘¹ mXå$g!pý™äpöÇ›¤tDnùLä30XX›vÚ*È8ˆŽÁƒ¼Ô¥«´–ªÐ°ûAâ0{H蘀ôß³‘+Û^„èR1ÚÆšpq3Þëu×QCßtwí¾°‹öI¯ üØio$6ÔJ›ör“YIp挭¦£¿³9’Js3I«w™£%ˆœ¡ÇøäŠB2\X¹)/Éü†EIf’;Q=4Vo† aLýÏñe̽ù$>ÏW$Jœu#cì>L¿ÙŽŸÝQ$’ eìvÉ3Î%HïMÒ’õüF8ÐIÝj½=0Æ.æ’# &q‚×%j]”pó€@WãnŒë†FýU0ÅDu­˜ .{NÇ[Á®{Â`·Âç\X3Gi‡4tŸ«WЄ«töh‚ðþ©ì ä4I—ÉÔoqპY€ª M/ûup≡€£Yn¸±s×PY…¾€!²ùÿ¼VÄX±ªtŽ@ŒŒ ÃìŒZèRaE~¢fÔ É¿Œ—‰†% é<†èB(–„"µÙ”lHïqnWÝ>ÙL͆™¬æmBH¥E„ƒd&RØ)K:鸅kÆ”EÚéCýùkuC8/8…~r—zlä<#ÇVÚêy·x/Ô¼O%«ìh¬›Y£øìA°“áúFKûXàÆý\Îå ¶&®ÓÆi-œ ñ»3dI™ µš™`q§VßÖ²}Í󸮋êe†é}‡Å"~r;â`†Ã¸>VæÈ"@¯Ë<¼ªÚ3§r #¦¹¤ël/Iͨy Ÿ<~Ä,›YÍ@1Yjn­a¯ÅÅVó@q–TÍZ" è¦ Âl¡Íͯ™†C|[OÀçÀÙ€÷výþ\& »ÑGd_7uŸë—ÎÛ Óiyxß’+±ö-âL²…×`3`þ>™ð±H—g(÷¤KÅs&N¼"ÇËî·’Ë‘â]im#ÝË—XgÈp뜿oØ+ï» •k˜Ñ)CuÈ,© )Ö ˆÄŒÞàé–žt0μ®ï`¾Ü_ÓO‚}Ž…wТ¥L%剙áu-«¹“y¨ÀÉ6>ëàáê–e p½ÿ„è6WMK­Ÿ?b#K:¼éÆË¶;,éP4ÏAÚ?‹%ìMµ/&Özd‡ò`98^¿¶¾GYSÍÄGÖ½ ³T–01„T4RKåGeFÒî–D°TŹկuUfKFÃŒ‘õÞjB1z1 Êz—Ë‚ñà¡Ra/dư+¦_°¸j©ùØ4¿Æm8j¦Ì²Ôra¥ÎĵRæã ¹òwMÍŒ®çÊüÿçË=— ×Ùáú‡ ½r$^{æÕ²öo ¸šþ÷S®Äݸ;8œÖYù¾ãH§*óÀ>Øt30ÕÙ}Š’ˆ¹>[Å„u˜ÇøÇÂfb-øšÁ2wÑÄ —¦×‚vŒ– x½ö’›¦9™ýÌÞB‹ÈcS^(@ÚY$™½˜×i+$ULä?²ùþT|‰éQ{ÎëÄæØhΧx ¯Íß¹Ÿ†ðºX"1ÙÈoa0ô°"/[²»Ö´þÁ]‰a¿ÇVöÜ^ì½ KÞ~Pê4pˆnPx g¡wÛ|v‰˜—Ð`ARܹI:_ÄÆ‚x.ê¹· ehÂ#;…N`~U¿‰’»[×V+A²glì,k<«‚Ï»öu¹…î^LTOÉUÊÂk t¦S$ k–kÄx 'ÏÑ’egç¢ü©ŒÊ¶‹A§eôè€R¹EΟiØÑÒFäͦÅû»þÜ9Á¥Ã?è10ŽŒÏn>Q¹æYìòSÐØÙkšKlÙ°x½åžä\°¶ðkd "ä°²Ž6=£"e”‘¥Uà–â=¼­ve[›90kä âÚ™6cXü† lÀ`Äq0|äâê R‚Ú2%VfÝÌ;Í9ô¾£áÑû‰ÛðÚcÃpNãÖGãÈ¥Ö¢gÔÙÑ G3ånȶsoùYײ„ÖžÕbŸÍdùíþ$æÒ0k$D[a…l¦±÷Œkjíso÷Ñ¡)×Ê}¯³mÒ`—ÿÝ꬙UÓÔÚîïÚ§ÇL¥W¿Œ¸Z>ï ´{—ËÊVaªõœE <°YedíÊÝ+øzÆ {¿.7–Á¥ï‘½ÓÖk0G[»~ÿÅñB½´XÕ‚¶Óµ¤8Šïnþ†[pÂphì‡ú–ùWÊ\L¡t_„L)ZlºÞ4€XkæØ‡åYT9XÔoÍ-‡±eL«=Ü­gË>ÖŽŽÀ\Ô…[t* š*ë):_Š5m¤Ñ…& òÄŽhÔ¸†ÙaG‡ñèøÈ+Äq˜_Û|ðŠ­.û Ó¹æ9{keúÞ÷7Gàx(Æ Ãä–gªb1ü´£´ÈœÛT×ÁƒY5·ÉYCW­Ý£‘Vãàé?kÇÆ¸/ 6i°Ê9—èY»>È–Å©¶Ž¡A«O÷F ©ïΞCbÇ´^é{÷iŸKÞºþkºBÏû&›8´û’ú6;Ç›¨[†–OGÉkÕµÝý¬Ó}ïÍ${»¨¹¶WAÇc \î}¯­Ï±ž¶ κO•=¿ÿ|ú¤Xr¨ÛÆMAÇÂ6† †Lx¿þâA Z¢>zªfü ªìÀž*lµVƒÜ,_ Õe»²CY©OjåJǤܰFþmw8¢Å\¢ôH,}Ã5gÆÈ4@ÚÍ2vÒúæ›èè7ÃÄ ·Õ33jj˜9clc#·&Ï T0cX?ìÍ¢ll”¶‘VÍÄê¹aïü­cËt! „M’ÖåguóJ,Mƒñ·#psˆCÙëw1#C ô, =«LDg{a.¸Q{tÜ+°6‡.ëVχ`\L”DËuÉxf÷'ɵ³lÞm0°r5T1f}V@vþƒ-tc*"‘au‘›œê&àòð xÿáæˆE.×ÌqÍ`»¼ç³Óº&c¶³}îߦó‡ o?#žÀp:M!ãEÍ$—:°oÙuŠ œ19»“=Í1{¬\[î¾qõBlÜà/]F©2ÏT ‡-«iÁ //D&Û3Æ76šÌ&ì’È16ØvÄîH›bëŒ!r™Mêí«c%2îªêžp"QšlfŒªY&aµÖ­Ž®d5Êÿ·r ª×3; Ø5[†{°öök˜&C'ž ‘Àð®¶Î±ëö"y$všÖ+o*û¥<] S´†ŸS`ÛQ6aø@QÌÙ›FWÖÓvÎ+ V2»ùþY ß—Ù<¶³Ô©ã“Ôöh¦¢äDì‘Û#$Q@ kã¿ÀpE6ƒqE|S¦ûÔz_‹þpU'i²Œ/–³/ñP6½«<–L†J&EI ÷zëŠÅLè†ÆÙ•LȸËón@“=Ù¡Ü8š¸\Y@ÂlëtkΛ…¼.úmaÚÆå»/'\%«RMšl.ÊÚuIÓr§ÅüD˜ÒduGõÕT. ‚„Ï:T²Ætua»#¬ÕKz€Zåö9 ¹(kÎÎ`Q)ɦ×Á&Vg87GàÕ6±ˆYaKàÄ•é9†7žp9'%3ižÝ=Xtž\ïÅ+×ÅdføÎÓ÷çSż5}¦8âþöÆH™ éõ•ͧ14LFÐYˆj¯Ž³›U“´“æÛ4Ð,‘Û­ *W¿êZG@àBÕ™iEî=ƒ”7²÷€%ŠúíE;¶ÅFÙÞ/õâ@R¹£j¸ê‹;>–/e0wM¢bš³”ƒVvÜb3Ü@¼ Sd kq9,ÓH!aÒÀìNòe¸ ô‹îí5ha¹Xg–'v¦éÕ&]k¢›]2ϽûѲ¾² &º\Õ#Ph̦hsOóÞyâ€zžMÃ.˜ï±(•ˆ3&­­Ã:´?ÃÕÅŽI+%±¦c˜±6"›F2FèÓl–]PŠsO5 d«G#X—'m)*º½vË&º¶É>ÑÔñÑJ9H´k`Óì’¥zÎl¢mÉýlSX‡,Y0 Æ =ðès0ÃhĹZ6Ö/=;nÁM±°M6‡.™¨LbÇÚEL"ÄÍÊé4‹d Ô²Y‹{»˜é *pÙëDßà*±¯.sPs¤é0 âÑ•áðX|Ws Õ÷ Þxb¸;ci ÏntfÀ7Þž 7â#Daìþõáp}$^{x÷¶4Mƒ¬  l‹U j %·m,™ü*Ì:qЋ†&œê°ÒÃÚÜçDQ±ž"À;e♸ZÂvF`´˜Üƒ|ÙìÔcÿ{–B–<£Bÿ2‡¹’Ô؆¼¬ªáJ˜«R•ÊÁ.PLsvéî%Æ[ºŸy§Ün×8Ɇ{Cÿ5ꮉšk’Y–Ê <Ú¡GfêÞ y·9|# ØÉÍîsR˜-Qtû:DÞ1Œ–½ ™»µàÕ5lÖ¢™­³J2kÎTErϬ€ÁÒ–ûö€Œ­Q& Ér`Å4dÝ/:LŠ! fÇÆÕXyoåɰz<‚ÔBl j«4þ¾)°$lœcþ’ªÀ»û¥µŸ³éÚ ë_ˆfE7ƒÃÚ‚ÊÏÊÛ¸Ì,bÏÑTueã=³ð¨ ¹Ú²V:á¸BNe3ПARùóóÙpàúìWã«pš¿êPκȨJy1µû eò d^Ñí‰.qLÊŪ=2RL:ûÂÏpoiæ:·ÕLNŒTQâÕêý›Ç7SEp9Ñ—Ù£xxÅŽÖ÷³0óæ3ó3pwÎçÉXaRŒŒî±&‹ð‘ |ó1ñè xßMx&VK+xp4<;< ƒ>6ÄL½`NÀ¢ç1­`ÔÀŒeéùLYÑ &ÍEŒû4rù¾dÂÌ(´i˜¼F±9c-«XçŽ%Μ÷­Ø 1+ö< Ì©– ÀžB{©“ê–ÃúgîËð)ÎAybQ˜™'©Po Ë9i|£DÏÞŽ’ö½àèb0™…Œo¾FxAñ‹iŠæÄ=Ù¢ÚzÝ4ŸKŠm¢®ð›|…°¡[Û稕‡µ9!2éØBö¨†kÅ ¼V µaˆ¼ä³¥–·ëàx”,ÐÔâ7®p¤<7â(‚h´§t“)½ºzˆ×‹Éèœÿ=PÛÜÛbgÝZâ¬dŠÚâè9f©ƒ[ØxK²¾OúhŠ™’´d2ÂÐ=v¯aÞM4-N}ý$@{]L•kfT7\bø(‹¼¨ÉiÊæXØ÷µ¤sK9yz»9­vØZ,…|®™—yBÍ.„µ-qíHæÄ_¶Ž…¶Ã–“qÏBÈ)Êáœ* XÍXxÇWóZÐ,³ÐÚW³!ͬ”ÕˆÆòZ]‡é>NVtÓŸóÜQa^}X悚åm {ôÖ3âÉpwš˜Å±€˜‘ õ91aiÏ\€Ô«7leÅåõ_¹žþø§V^{ú»‘ml{í rݳ?}æ$B#e§8 )ymÖ>µ ^|æãzk¨hý.X3õ2if#ÛŒu ¥5! ¾´øTÏá‹b^³‡…Úš‘Í…¼3ÊñRþÙwß„…°7ñvâªÀ‚Wή‹'‹m$5`céYñJ 3!¯×ØENÔç=wþ>Å3àÁŽ´#¯þ,J묕Bîú0¾ÈÞúÙ®hQþI{L9’?©‹ÕX‰àIJkýà­ñœšúͧғ[Ê4òÔ,hrk³ÙÑ 5ó„aæ…Jî§æ2è˜&•ÏØQµÀ@ ÉZp*M$ÖiÑÄÂT²¢Í‰›h•;¤108vä=Ðö@+g²5q7E Ý-dí$H膅ŒÌÙØ›4VÌAÖ™V€q¶gžƒíÞõur¬Áòù'³“ƒ™dg‡;ƒáÊÜÚœ{F‡‹’Z© -aIÍ’paüÌô€½êâ7¾g`EÓˇÓˆR×S±¦m#I7¼â~^ß|=EqM+:ÖDêR5#l°iVhŽfxp ŽÁ¬b7ÏNÀÛÏ ç3q§3Ž¡Ï/_‰K8p xgGÃÕÀ)OiÞy]áu} Î×Àñ ÀFØ8ͼ®æmŠEv3k!ûŠÞU‘ì S‡ˆ‰u$9ÿj¯öµ´Ic͘ŒHjin¡ž]¢”È GÚ®cߌùn€Ä–➤’§é¦C5 ïƒÊÞKíY!—{Ç݃)âh14 b® •T´ùõzðÉÎY–ÙæÇŸå¥ sÍöãÓF&L­m¢u&]ö6F™Æž/Tߊæ¶úRÓžÚζº Iælg#Ll§8{dª+Û¹ï–Ñ$Ø öäJ6(Ï ?‹#ˆè=SÍ'·Ò˜Ö‰ŒâÆRÝ‹Lwy{¥î˜Ÿ­Ó]{ÊNPdF-|0ÆÐH0±ÄÙ¶ŽUwÕ±B+‰‹›Š%l©éîsZ˜y;p‹’«(a…i”J©påÞ³‹(Ã2Q”Æ.5ËÓ Igu¶™&%FFÆq*cÿHªÂa—1¬p´j ïÒHšóçÄݸÎ\,Ч¢t8”16>“¦à‚9QZÑõˆ,Žr ‹òUû±g¤â½çÆ,”rzTÅó€°Î„,GòZBý[ÝJŠÀÜ`°k]?Ø0YxàpÍŠ9/«¬—ggâõ'+8:Ó^Í30Šk@N°e»rgÀwÞ¾çàúàç(k`7}çç’FÙ]1à <<Ëå#W¢ƒ`ÌAbçž[÷&¬MKÀ• lØpv";Dƒ@6ãâ)BìMt «Ú=8¡’És¢” Ô¤M3í=`dq»£éÄд3l€$j0-ïáSÅîÕ»ÉdëPÒà(½¶ln{ìȽÑî–¡ÁÖüÉžÁ…Û}–…“…+wd.õ@ˆ~¢jƈ×IÍ2£³N,ç¦ôõÞ@µÍæeÐ6™[Z_áæÄ]òÆìÆs,Ú0™j‡{9Ÿ#H'çé9þ4]TÓßh7u¯Ü¤l yV îêÜ×7‚ {Ò“TÅe4ûð¯Ý„g Í`ÌÝ›U‰oÔ]Vëi»….6²8QbdÙ:ÛT:)£¶ìA³Q+÷ɤƒ¨:¦ÈžcoÔCÁÞºF“Ì' Ìs,:”3õ¤v‰mmuY×ß?Ÿq ®Ân~þ<ç֬‡¡7A˜2 VÕ€Õ†w›z~ªâzÀ‚ðä.¢Á%3jÉ$P*Ý \ö2Ö ð¯–òœmTØ`˜2†Êü‘6#÷Ÿuæh}‹''àÍg†Ó8:ãX€Ñly-:¾órG·¦Ž€À·z<<úæÉr‰Wo€7ŸM¯6˜álÀÈ Í!óœY%0–ýÖªÕ)܃•£õß5 6€•—˜i¹©2c’¬¬åŸ-<ŒógՙͶyÓ5‡bt%S~ :'/’Aº7b2Ý\¹ä…{‡©N_¿k‘wœ,Üî®7œU m¥f_EX†S›S³JÉù–d«é®¿õ‰ ÔçÕåÎu±¾â^¶R¬lzÇ1Û -‹Û‰ÈÍbí™ÍÞáÓ‚/­÷ÀÅ!ÇùÏÝе´RÞq#“q‡ýH|kU Ù!>ŒPÅ„% ˆŽRb¦Šï`y¼çAë4‚Ô½cÇÒŽ››eZÏø`*ÛcI}Ös¬ê!º”¤ÁÜ@)´­¡áª`y'6 9Arx?†s$ó-ê;tidë_ Ú°H⾇ü†Ù§à¹Ìqøæƒ`P¶7%ÍjÉÈõs³i×la¯÷ s Œ¹ žnßlúó–“ÊÔüNªØ²ÌÑPFí_PW3 *c† WèçK®ûÉc5Ý~çpßâlRjñ-¶šÀlºC1Z° \Ør'9ÝPú¯>àÄâ`G3›3â™9•Y£3'æhG…¡i Çé³p¶ð6ƒ3¦ù'³²ÎÊ÷“ñ¾àÁxóé4ót€ó85ÆÅøÉvj,÷c,×€œs‹JˆmùYß³^÷ ^Yb®’‚Rá,#¬5‚õTs|iÞ:Í¥br²J1cÂo§±Aó÷‘ǽeͽþQÁ¼ûq.¶‰åb_Áš ÉdMÀšnc' O¸^™Ø¸|"ËÞùœM`ÙŽãh #N{Œ »•;g™ÒÂÑwÙó€XBGçV5cÈÚ²5O1wtëA¡ÃŽzy5àÈ^Ç&q‹‘Ýÿ’»e98Êšòy°Dç/—±hì×× )áj®Ñ"äE—[Ef÷uzFûιÌâ6Ú\{D0 ºxëkÂÐhîÛEaÁá(ëÖ‹ ê"1ç)ZÀS€f @N~oÛ°¼í•PI-­ÓQŠ’À6Ä*P3»4¢N¯2H«š<”ëm~†&É.íÄŠï_UÔìFÄ­N_#¸Ììϯg¬YÚã0ýÝ“‘Ž=E”uܕ짤â>6¢™ƒÝŽÌ®ÌØAÎB²µQŽá¾Yö—QuK2sš]ÌL¬ý Û²5àw¶×žÜé&à1 Äõxåff–ˆ›¡€–®Þ©¯Û;πǷSÎÑ8ram`Åè#XÇÇéõYö¿‘ÀÙ¼ólz­GWÓÏTëp$®À‡žÞ­ìæÓ[àí» q˜X¥‘äX.íàÂóÙHÑ̳WÜûýÂ=8™HÖDCm¥¶j²UMV&ðYv˜` Ó¬œxp+æòÒ¦¼áÒ‘šý9HÏÉ"]J.¤]Rtö 5ª±ål—u”î|KÛwÍAóù_ó‹qëZ„F¹¼ßžãÞôäÜ^›óiÉuÜ_X&no„똋¹$îóY蹡íe¶¸7&$²4–à7­–’Ó¾.³âó~‹ÕìÅ®õ*FA9dòºØI£°\–Y2Öf¯à‚Hñ@r³˜7ÁˆÌÏk{0ïò>éŽä5&ÎU4,hºn¢Ûu¡ES²¤>ª™#vh\oH"èž¡Æm‰’N7WQÌ“¢¨„*ÒØv Ó¡]K$ˆa7JSLHm-ZMCu69IwCD«gË®)Ð hÇgPÚ—*'4÷½ä,ƒÕCÍôëÒËÓ æÎzd.J2hLÿ{d=SdFç,³y¿ê.Äà0Ls'M|W3­­ýë¦×»cV7@¼S)ºŠA&e¡Ö‚òr MËÖ`¬ ýÚ°š‡`Ū ^Ñ¥^úº úðÃIN·.qsá;02³¥ f×Ï?³àl®…éÂØí]VΈÉàdädR†¡ì²Õ’g4?ð Þ÷>pcxýé€Ó8Äé<gà :™6´{¬1Oh2¾ëÃVr›gf%;™³Á–é¯öZ$ëPͱuê”yaÔŸ—ú¬Û××a›õB¤—€™îÍ"a+ù2‹Å:ßg‚ò÷d²¾rÎHÙ€š(SÊ&¢tÄÊP©cbà ™æ®%¢r‘ö‚î`cª¦U"ÇÊ´•qÓHššì2^ò mœA{Ô]™Ë•ËKÇ#p*`Õ:›¿–[éÚ÷zÀ•ô¤6 ¨‚Õàªë¸Ý‹Øê‚`aÂÁðì˜m²µ¼ä05«±ÅžúæI´ÖE%û!;ÚQrÍG2ÊW|é8ÿ+ “ÊÞ¸·Çñ ]öœG™¢œ;RÅÛåiýX{%'gAšdvކÓ0µç­€¾j)3lúÑÕ°Úç+0ÉŒŠkŒ &,–2xÈì£v,dÒðÏn-ay¹o›ljÌb}Ê9²Ù&\† ®ÏzÄ—ë%˜æ†ÎgàtF›ÜãüìÏc¼…1oνüñÈ ÈœI Ž“l€%Ðæ9€üàŠø«u/~v6|á[#lD Ž"{l.:sÊB´Õ$æõL?wš‡Ü+²Å\ÿ‹8p=ÌdƒµûPÅì 3¦æÏÓUªÕIU€MÙþw­ã†dr PÊpýÙs E~õÈYè†ç*Î[õÖ©ÕE¸Õfm2l­™ÍF1†×ŸwçµèνÄþU·:Vnúî·§)6³šµ^Óh9rëýûP@",î+ÑÇâ_­•ê^ˆ¹’†Á±¶í—ùUÏJYÂòËÁàº1DqÆÙŽtxCýš¦™kj4·ç )ÄhìÄ*^è?ÏCUÜ·+‹¾«”/¤îÛ¯‰ìÀH=ûM—ÁB×QŒ]ª€IÖ>ÿǽ6ñïý3ìqŠ’)@„aŽû±lcéÈxý,Ü7µ±Z(r;W8ºåÅ÷è ßájæÚì2 ¿ûyP.7逽‰ƒÓ’Ác‘)ãgr’n²íbb˜tS;œ)ùÖúŽš(ÄõÇÙ" %·³¤Èìì5Qæ–|6‰ŸŸËÖm]3vú]q4YÍö¼ÿE°¤~ÒSYy3)¼™I”Xw(È–µÌ‚M¤Ã{‰“Ov‡¹PsOEaLh°Ãx]MרëÙ1Ê—³$H–58$u¯Âƒ‹-©‰‰BM±r› û ­Yƒó°þH'±óEsiÜX):ýŒ†5£ ™›”fÞèÄDFegeößÕ°¼Ê§‰{-ÚC;¼eX2uaÌŽ}7:Au%²ã²ÐäücÙ‹J>Ñwž·çµÁ3ªâ6ÑCË÷¨‹ ÎL"MƒÔø¬›˜«‰òEqMÎgŠÙ¹°T×b6cùn“»ñäŽøöã‰ñZ¬5g¡~ÀÍBÀg4FX@¹ÕMNÏÌ“½86ñBAóïzŒPlšVË0Òæ›µ•*ÕÙLÑ฀¸¡gÑö3û%vÜÃÕÍ=Ú÷†NQ7¡7²–.J™äŽý[—8ÎÔ8n=„f¹Ùl Ic3ÈÈ­º:|˜‘}NÓÙ.†þ^K Å@¯&nÞƒùŒˆp¹ò9kà¥5ui¡È´pÎÇL‰ÁÚ¯ÃíB}_Ñ‘}WÔ‡¢,ôLëçé»»â 3Q(4L¾¾ûYQªJê]¡5NrèûyóóÐéå[Ætd²kŸÁèš*‘p 0 Zô÷80±0¦/vMèëmí~êKøF£ì¹-¡ ÝÚhÎYÖÌøñ̒ʺ0WÈ›²xM€lS蛞uj–¤°|ex¦š L„ıbmbsÇ ´þscÉmrV‹/Å`½1P‹çŸéµc“#1ýùa°+Îchl…,I¾泞«Hò“âÌU”ÅÆbZÙWss øG¼ìЬâ¬5Y x3ˆ¬œ#» ´Œ©%`õ ,ƒ¡<~fxzË%ÖÌðêÃcÐì5Iò¢[þoˆï<î†ÉÈÁ†ù CÍdÆ­‹Õ©nu ¾ ~„0³î?dpÊ,¯yw6¼{G¼óÌ0r² ÏĉÅm†Ñ&ã‘ÆÄ#ʃŒ¼¼&²+äOfª!¬Ù‘°Îf‚™ôìŽ2an‚Íó¡dÖw“¶ø¬ƒA’º]ºšÝÓæ{»ºëu>kÚ{’dÞ¦ V¶Øžó÷ƒCìÞéÄ™Ö ¾ òŸÍÂg¨;I;ãWZf|ë¾€¬t{:yŸ*ÝÜ­‰’ÚmƒêðŽn}f®Ùz†=ÓøÿÄL þ-lšbÓ‹§šEò$e}zI¢ï®˜,P‰NYõ”» “ý»š9ÖJ6Ív<\lYŵbø9_ ÐNV Â39Ö±|×QuúØôPNjÀfà*HrÄA¹cÅÜ ´3 2ÐõÚ¶ÄÜ2@—ÌxÈà6kÀšE:;N©TR:;Ϙƒaý;™QÍ’V ,É{Gq‘IõšÄrŠÃUϸÑê{ê™øÁÜ=ÉÊx©°H§Ñ`c-ÕkË‹l»ý8q<ÌUecM/,—)fè,>§J:ºmZ”Ë-Ÿi Â1ƒõݲ-9é“`­Ìøß‰›£-½‘GWÄÍÕd™mæŒqÊ÷»>NVÛ·§éÎ 6œƒ¾3—¶xÖõ7 ÈßJÓƒkâèçäæSÁjæáÝ[àÝ»iÏ$§§’ÇTŒ#Î…Õ™ÿlô¬ù<“Ýh#ègåËl%/LÌLœ¹–±ƒÔfŠy·û–‰&¢ º÷¾hý½id—þDãrW}÷âLšÎØÅÃm£nHRv‡Ü"Ÿ¶¡wpF{ 7r¶ŒÌÙKfh.êJE0r﫼°L«^)Ü«I2ÔÛd9)º³ iŠ…Ù«WÔ©@©ÂúÉÀ^¯zn&R¸VÉ”kA6?'\£Òà'nØb'3”¤¹tH綈€;´B6âv´æöf@.ÎqXªufNO“$Ëò¹b¸â¶ÈsxÒz†¹¨×6oEÒ3:ð1¡¥Õaœ`Ät ˜w) dzæPÊaqšrƒÿ#rƒ”(!Q…ZÚ‰S ¸÷ïÙr2qj:ëØ«ƒÖVÓÍFφÃ:Ž9=å™#Û8ÛF‡¢ß°3®÷dd07´«* g ]Kšq/Múú[‡­ËlfSÿ‡Àòp56hÕ4Ömd7ós ÊŒ‹Õ®~d±m· Ø 8JNÒa’äŠAÂÃë’‰5ϸŒ£Áˆ“Mì¡V}7ŸE+lX÷Å`f p«…¤gàÛõµ^fÏ\À9-E—ºy<¢šQ]­ë¢÷’Œuf²8 Œ<2çØ¹‘1ø ×&|;2ñ±)YKšÀSÛ ’D<ÊΰØ{¤d %Ù$v±H|&i/¨soq“të6§+±0T¬›?lM@º×gâ…×jWóÜßz•ÝŤ²b-i£mHÇ’<&ÿЭÒ:ðʵ0Ãá芰9ýºÆ¡œ¶LŸGìηڿH­=ö^RU;Š¡Ôî¶c™ØÍæñ ÉN§¬'—†€Ua±³dÌ:õEk@™iÊ™t- h0Á®,Ò‹ˆÌ芾P+Q? #:)Ù£Õ>šÛ9IŒù-MŸÀ G£ciMK g% ‰Ýuèœ!ô¨þ ©àaK’ÞS€Ñ² ¦ÁoÓ`I\¬—hÞ¥‘Ý¿m8çII kMôæ]à­ÀQÞ;î¬~‹&›ÀÍ¡R#Ûå¡F,Ýù™ñ³1f%Ùpò9Ýè8‡#ÙœÀH59/aO'´ŒD…fšÊ2BÈÉLº;çFæ9;MµN8ûQ„…:ùˆ¹¤Ærh,8–KòlZ0Ø4³3œ‰'œæ”†xx´òruäÕxv†‘«ÆÜ|M«)ügtÙ`lú,ÃÜ\W¸Ï¿þΙÀ[wSíy4œN#Î#‰ aJNÁ2–¹œÅ.º™Ò8ÇŠgÞvJÊ$;Â6€žL–݆Â?¼m0e.ŸÍ³ßé2ǰ{ë_v)ƒ«ÈÈŽ{6ë¼:Þ³ùoHí¸ç‹õêÏ=PA¼ÓQûÜù ÷.e‰´³Ñ6ü¬aŠäÜ=„*ˆB}qÁõæ–“mç5Գ쎮®¹7ѵJ­ú!L¡[ÂÎwe^ð³ÓȵÝH5 ²çúxé|yGÛ:LRÿù×tG‡Pzd¶Ea6ƒ7áŠRh™`Œºs3p°6TŽÁ ÍÔ"b²Õfìû—æÉ×Uj™ccU@d3PóŒIŽÖêĪ¡ÏVÊVu<i ÚCÐzë2<ëÆVŽëÀ¦ W¹9 'É,oÄÿo»ìb1c‰Go]äôüaþùTâ¼õî9õû¸µ™¸³ÚÅq„áPIÙ°0"•ψ§™‘g¥2í¾kvV] "cˆ#ñþ©™±Q`g±ÙX¨Œ§£¬ÑïéœJfµ*¤’ÛZ=O ~¾èž9xž¶"m‡qй†ä¥‰d0p@µw¿ÿ8“œmv¼åL$Nº›î^/qK1 {u€Ü¥F#f x6œÎ†»ó$¡£“ÔEcF›3T  c"³ÚU2À øÉŸŠ#. ÆfªVŸÝïÍСÊÑd¡ŠPy_Q†­ÌuBv£jØz&zoVMlæì,»ŽÏAÉì(ºvXˆ>µÿ§“™‰1_}ž‡Mbv¬>Íj Þ§df‚À}ï>Îk”@K©î‚…ŽmN¤pßZ˜^Îú‹Šò¤ÄnëBXÒ¼MjßöŠ ZÙ^0ÚwO0²¼¡~/@º–m¨ðlÆY©­‡nCy¦ðhËàÔ{7ï­EÁ}~ñ{‹à¿®ƒn¦P +º’ Åξx!RË&¦ jÿ.Ò0gP7NÂëšåg‹%Ç„¹ùCø¢?¸KfÏ:c¿zl¯„Ãқʣ#»_±X·.KÖÃìí¿'Ô¢œÅA½Þz99ÁáíDâÀ©£ßº׆)s÷¿)œ¶ØÆf.¨—þÚknm"Ù^Èl˜¢c.ŸíØé’¸f­ˆll°ÿ cº¡}ï(Ž3€ÁÀqÒ±žÌ`‡é÷-0Ûf†ëñt0ŒÃû1…‰嘹¯š›uÃŽÃÑ€áP×0Õ¼7§Ÿ;qZŸç±˜0ŒŽ-’iפjf× ‘È®³9µ`"‘2$ÜïN !eàj¼¬áŸ~?ÓŒe–¯¦²ÀÐc…7ºþf÷b¡Ž›çù½›Ü¶ŸEÚâi¢î¿*s­á«ú/]­°ê˜½6)„\»!§É±Úa–ÒBñ–ádž&Ë&W'v1‚û—È4!ó/Ò’@ÊO[¦·…º <Ž4’)´§t®y/ÿÉtÈ{†Æ{` ;6-a7*ö±†„9"Üp§ôŒQæFæ@ц%KkÓ›}MÑ1÷ëØv\®¬Á{3:T…®6ãg1¶ŒRÅ”Ô]óm€‰ÖÕ iQÈÁ‘À´™G Åz:€™´•¬ 3ü{Òª^êv áD Þ¨:ð;sóüm˜'˜úùœ™³…©¹‘å]èV*†¾1H¹>d=çæº¹÷´yîÈwÆÐ]ŽKQ «—?1¬hŽQHšÊ™Qf-{¢ÁWtoônrb½Zv6°Î.rëÖŒ­Ëh¢ÌX™‹Èf±íçYºæTÌLše,’-ïZîÝX€•ß?Àw¯ÞÌ’·ú~<üxpÍšòÕž o>ƒjgpd“pp\%;,Žr€ì™Õlue}ÖafŽe[ Y¬1Zk¦@ ß 8gç»dN£Õíb;/Ç^ìÂjÖtmºçŸaÀ¹¤øR3êÞshćÛÍ!4¿ËÆÎ—ÝÊ#¸Dñr$x_°ÁCÒ+‚z#uÝã7Kyå¾×ØZ‹W¦‰‡`Kö‘6ýk'›%–zP¡¶óöfÕzðÁ´ðšañ„X—ŒP8µYÖm|/ÁQÏe"£)T‘iz!mÒn;\.Œ›ôf|>r£ ˆ¢€¥‰YTk—½Ì€°ÎëàÙäK¨™(æišØ`-Ž9s'7Cd c³J[Ùmœ‹ý{¹uöx&›—uÀ½‰ X¹ÎÔ<]xÉ2ÛÂr ZÂí°å6êü•Kž…Ý›N/ËÂÙ¯7ì\öò‚’4å¨)’é{6à® ³ÅÅΖÍó_Ž‘2ksÙlÀ¢˜S‚¦){TócËÚ–¹3aÏ‘{wvÐn°™=Í/˜Ü§¬XVCùQ’DqÞGÇÒàÙµ¢¯\2òÂÚ¿©™Ø'¸ì!Õg§SqSƒ†ñ¤f’פX( µÁv†ˆeE@ÅjdC˜½¥—ùO^iÀH='[6ÄÙëj°e–±¹tϮռ{ÏtçÍ3Œ†þ`,ã†ÎŒ›ÂöÐ= y1·“þšY"•¤è˜‡ïÌxke9³bÊb‹²¸©¥7‰-/3s¤ë/Œ1$)˜,kÎme#/aÁ¦ƒÃb³Æ€þ®¼qvf˨úJÊ| ^wÓ‡;=Œ¥;Ï–eðss4±ù¬9•»R;ÝÙ€…ų"5•)€:çºQ¸%ƒü’B˨ʽßê}ža/ókÛbãL̵U ÊU5+×i TÏ`0ˆ…³ ÙÑ–¨¦™É‹Žfa®0° SžÕô>ƒ»3pq8´ þ0¯\w#ðôvzŽÏãä0bµ’'V£¡¬§Á&Pu=n ￉ìÞZ½œFàîd8Ä8ã•s'Óù<$y\¤ÊòÈ¢0' ýg̘SŤbô•)_ÿ {Bt¯“ìÚºÈ+éQª§f‡.Éë‘5ÞKÉAz>n…’ Ø[àQ ·ÛæÓ‚¤Îç¥]’¸”Jèë̆PÈÝÈÎõÉfK/¸¼÷-k‹V¦Ü"Kf+PÔ‡dº¢ÈÆb4|T¢ûwÑ>œªãÍ Lí±OÌfKD¶… É|O/ü{Ûxz{Ý‘=iA² um\&·ë¦AgÕÝö—›’2à‰ ïæÎµççúÕfÀçïz5dj/0kÁ/¹o™lÌÐ1B šsiíí êa~Ï4°wøC‡¢†âѺפ¸feZYœÙ*½œ¸ÚçºÈd„¸[OÄ&c«"$\ˆ6è&ŒF æ§XKl,¼æRw1(®¼}¶˜rE8Ú€ãÀÕÂ[Òy­Œ†ÀÍÁðä<®ó"ÑÂWõÖ'ïšu‹Z:dâFÛáXä76C11ðk„YÒ½ÝYÈ k¶cêˆ MÔ¾™¢Ñ©¯¬—²®æLÑ<ë£6Ž8›áõw zD¼rh×êü$~èðº¸#ì<ýþÈ3›Mëņià•ë×Ô,I¹no=3¼õdš;"§°×‘™PèÚE™q”®Å€àt£‡Î³ì~ Òܘ7(M‡¥Wçˆo„`KÒ -´Ð“.‰hÏ‘- ý6Ýuß Ãª±{¤¼f´¦\V]Ëö÷mÇë+êŸØÎmaÊq´'…/.6ì—¹Å(¹aù/sÎà(sٔƞ¸Îç--)~ÈúA[‡ ©‹N;J£Û²ŠcVx©Â4yF}'œ`[ ÒŠ=g~+Y` ¶$ÿgéˆjÛT·&Zº¾ˆc#m®…žÓ6¦µÉS60 äsqÖ§w8[jššV›zÖÛÈúm×ûBaš L4úÀ< Ÿ³GÑnl#{²8ÌÖ~m½vs阯»ODóeÆP¬UÅ rcП‡n3‘"¸…Å;Ö°Ézˆ-efêì,7IˆÆ\h E¦Êz(žû6cëüA4Q¨ryÖ{°ÌÀ’1ÂZW×É,—·¹oºÒaˆÜÂõ3.Ò¸a®ŽÄàgüCmÁmk 0N ÃõqÀ³óˆq\3“HUî˜í€ên2ïŽÇÒ’šÐ #„£¤’c*ƒ†.º%!ÀÖ“;Šù¸ÊMS0º›@? ó @‰íNÖlãh8ðô ¼âÖ·™5·äƒ7n ¯=F1P°â.·J.­XŠÃFû‘‡Q‹à¾Y‰^áh¸=³°G†qtŸ½j¼ ˜Ld2%R·F=!º~›JÁØ›bH³($&4ÉlQÖ”SPx{M€ª‘ ¾gÓ¼¦N»öTõ}Ù2¾,‰Y1Ò9ðÍ],îaÝgùäã*ó® ÄM°ÝgÜjN`µ„(íˆëþºR=4Ýä{0YÝÈæݺéLêOΦ8¬”bŒâ)±'lj2ßsý¬ ¤N¼ª®ºÉ‚Ð|H)L'C7¸“dcéübçDQiMvƒ„œ&“q4ìI­bžT´igÓõƒÏêaÖ ÎÍÉDŠ" îñÖî)9 ½´. 8Sq³o´ì`fš[Sÿ\¶#·£ l%y¾8fvÇtf€ ’Ö+öÏÄktr]ª|L#²­bõÑX¾ÛFãbçßYççL¢1ëÈüüG2 ÝHi¬_p˜ø ÀÀEF…<+%×”Ú3†06(Äž²4£¬’­ÈÜt[£d²2Õ‰²!¶?W]ræ÷J9x1~ä£i!-†T ÙÈ.“ÂÚ¬³'ë•âY”¦+B®<_»àP\gIØhxütK|\BæœÍkgúý=Z#ŽÄwž®_çÃVgÄÁˆ¡±ž®›—Oî€7žLÿ}"׺Qæ¡ÅB>Ìñ0i`e 8KXüÑAÍÁÍØdæPF/’uÆèh˜žMHa=¸²Nm,¼×¤!‘:Æ&™U9f¯GîcJº7«7O„m³¼¾Â!0K!ÑS¯cN׺èÎdÝó¼›å Ê`­1¥b‡áõͦ6ÖÿCÓù¤~Z%xÖñ@^X”Fö¯!wzæ*^0óÁκ¥{^³.Ÿš^ÙV¿ Y]rØÉì‚-Ð$fzlB#áÆë«öuV(7eõªÂA*:æè"<ösÁ%(ÉlöF9EÅy£d†0]Ú‰'o?ÝûI ‘dg 1)¢Õµoº8h×<“àC57Õ„tŠæŠE© ½÷‘w.£|,t“m4P‡HìÈ»û 7š [çzØS\4‚m0jÌŒæË)–ü] Å—cb%›e_{Œ¸= xpœ_Ž:€–uõBîÊ|Hóü«N~ Úô{9Eó*mÐd.ÉÏ> ìÑ\\ïÇ‘mÍäÛ3.Ö¯ºlË¡´³Hde°ÖÀd¥¯¦}øÉ-§™£+àÁUÂpí÷ï£áUX\­ÏªUóaÖ€û''à­gÀ“;Ãé<î‘u‡ªšÍ"Yê´ÂÙ X ’½,ø±&r4 ‚˜af48çYræfRJ%›µ Ó “éuauÔ •MÜÚÂqõBg¬9H³°ýâ°m&i}жl„[v¡ÀÄpgšÉþï«HµÏ)á^b§öaf[´Â"‰1ÅCTÁKöeŠìlŠË‘=€³Ï_ž‚8¡gˆ0wŸ.ŒQ~!ø-Â(\ÝX ÷¤gvó';VÊ’Ð6Ü÷.‚š\‹žtžŠî¹ß]å3J5CÁ&O1^¶58ëB…Õðñe–Ÿ”\_,à=µi—¸Šæ)}4:®XMöK½A°aõ w Å Ž·éœ3éiºö)ÈNž'•Ýuþ*ô·ìµÃíàç5mí™ 1¤4ä‚ç`Ïš°,G(®É KA’b¯®oV˜[ØøÌ’¶Ž 5›d†Ã@ØPÎs 3ÈçÑÀ1"I×¼+%B–Uƒ!CÅ’è†XäyÉ}˜AQ™:–œѾÑR:~ŽX ‡áy…ã-æ-Ä"žº3Ÿ±"ê»0üÞLâ´Â3¸)q£ƒYîžrW†q^ïžÆ*Ûà(U–ü¢+’ü|Y¯Õ½ €¯÷!+×®:÷½pfî`й“ÝP.’ô³fGß®pŒ „­vb3”¢éƒuv´‹Ÿ‘2¯˜MÕx´8 ‰Ü¼ +x¡YÄ” àÙYPlSÞÍÕA€¹÷mnŠÎ½ýa@[äG† ¹+peLöÆ•YHnúúu‘jŒê h$î`ªÛ8OÎw”¸š‚}ÝhêbH@$…Œ1š²Xë’‡ÕöÛŠyÓ8ºÊÄ€'Ï wwÀ÷¼RN iÀP‡Â ì;´…Òݼö8&G¼3ñ\a­ao ø|A-¯6QˆÄ&‡§‘‰Q®¿ ’žk¤ªI+§9Kš¬‚ÁnŸ;Œ¸íÜêöÙhx!L ×ñùŸÁo?µz§œîã(Dä=RŸûY¿-R­ûV%TT¸x‘s³ï#ÙÎ7ï ±×L)ç•i£Œ×utë1žÁŒ–øÜsŸT§míV¢P4eë=?—Ø6³ýæ]I¸$¶_ƒ=¶L`ô …³q™Ùb.!:$*Ü+Å¿{öh˺wCÚã.®š{ö"̽_wÃéKâUŠ|á¼X j_1¼Múùº{VöðM0 õYÇ”¡BÓÈ®‹7vz¹:ó)¦(›¥ú IÄBÌu,eÉÂÕ0µ OŠ}µÏZì8„Y85äþ®quüƤnpÒc±ɳÈ{>7ÖH26©óïÚk˜„(w>˜ Wo$Fõ¢ñ5Ï\Sðó$«« Íb,…Žs0ÃPF÷\R²·í­t#•Ûžšs£è³†µÖtr´DØ,!™ É–Vvë*³ƒI¥hbÝdkÂûM¦’é¢Y1¶ËΙ͌“ÆÅÖ8®÷çTrˆ¾õîd¸ðá‡Àƒ£ 6§`°š1§ F£|ÆÇw†7žãœNÄiœˆç0X:³Ê猭´-†JËàÔÐÜŠæ2í<§—°YÒÜAÙaÆ9=§Må‚hÂm1k°`Üx6QXÙri ˜­ûCÓãòogõ@ÍP­5bVp̺v?i;®©9š:2Y÷kD[ëbÃN¾l uí}?…ÊaNV˜ßMnqq=ø® Çzý94(¦ÈuDƒœ¬"9÷é„e7çÍ\/hf×j£¾Ïåz~zãJSg«ahÞ«\DoÞÀÌmÅ’/g*½ÔÚ¢ ¾¼„OÈ:¿÷PâaN À­9‰Ê Õà¤gó]3¿&™*­x8ä¸T]‚Eά³}÷˜ÊØ¥sñÕL’mÜÕmlÀ5=OfT8ZUo4;ƒy·?Íê4,t3œìÙ —s£¼Ü«™5ggŽL¸¥iW»Ì‰¢ÁúK¨Áè@WÃÜ(›ïç}(ÂõQE*¢ðÙêy6Ï%²Õ5î| j÷À$+nPƒµp>Y)ql𙈫´ÑfIì@QTw£7ÍI檮6ëãDfDE°-ú_Ùª«^7RLXOËìE8¥7@ÊXARHù5O!ÏbÛXPEqc8ómD(ªg@°XlVm(ó?°Iæb<F¼ñÔpuœþýÕ›º)bFÑÛ\ïßHà§S¾Ñí™xv7…¿žÇ©P—÷ ,¸)ývçšoe#UÌSGÍ3ª¥¬™¥,è›Y¢D66n–(¢|´AcYž!”b0Q‡«ÿ®Ï® ±º|Þ\Ô\3s®Âl˜À¯lúÏyÎ%蘥Á7â%Î ]T ÑöÛôí§Ž ôŒ&m†•¦Ö›âÚ ÿÒ]×ðÈ`S7s)dÈ3xª;¥uAåutE‡ï¯1H&2¥òäzt¨œÞE¹Ï…UÑ—Ø5¦'ÐRgØHÛ5›ïBÎûŒ˜ ¸ï’’j î{,väÑž`½ÔÂúÎ@ågØm–'TLtSRÏ[†µ0¨|¶tuìŒõdj¿hqÝì¡Ñv:Šc³¦“ýxƒ>šY4Šø¨éô‡2—$•Ü+îÍ’’¬ªó\ “°ïÝ&HM©×( çN*SÍ‚8 ez‰]î9æ>éž ëgÙ Š ólñ‡ú ®©ßX„V¦2GS ©r“’Š•FÒO¤à$éÑb9ºëIF'ÉHZ®¿µŒ–rúÛ2Š2Á<¥³B¡øEt§cg»,‡z«•+m-$h†' nφ«ÃÔýðMÍ’-Ëõìl¸=ïž ÏîˆÓyDÇR$\rªØí²‘Rç…O67¬£ d¨a³z¹qFT‘Šýõk79`å,—’Úfò“ç ï cN°¹Êjã¾¾ŠÖŸ; Ó;\¦—: æx@1wY§s§”:îÞ·3¶ÏÓ¾nåçJô JP2KXÔ<ï§7 Îíª;líœ:êæËÌ©"a1YÔCt€Âg•užc“Z{ì--É6óHÉؾ¤d±÷Ãê&oSˆÒ­‚ý¡ð‚pl$N­x¸¹Õa¶Ž=¡:„ÅÝr¬ëݲÞlàžgÒ.~ìCnÝ©J„ÏåP4sÍ̹hŒ=v¶ìHr@Ðg€¸®E#©«Á[ÕÁi—\W`JDD”…éÏF·mhg…½«›M“¥ÊïŠ&˜B—PÙt 6Û‚ÐÅ|¼Ö HP Xn€ ³»Up7“ÝÞ¤x4{AXifR¬qðLuˇ '´Df#“¡ ›gêaòÊ+Zÿªq›ùV Óùt8†"§³¬ðlaÜÐ9®g™OF¶_Í…#©ªë®Š{´¯Aà¼5§¨»üÉLY0 ¨»Í¬hÔ`¹®d©m^“ ©Œ’%ùR–ñ4ÏÕüj ;KãÖÙ)*…Cѱœ×ºéæÜ¸bQ¿D‡·2[ò'“±Çé\Xƒ±¸Ô'`4ºÆpåÞl,Í> 2‰€e!TÂú=:D†÷ D­ñŠ…‚ JßÈ`ŠÏ…|†„V¼±ÍG1 Ö…òaXÑÃëzXÌvhxåzʰ: Vûé`Ê´šôqgû’¤^¤l[Ú œ·4†§÷q.?ï×I4Áf°­ÇóF²uƒviùÆË:Iâ*;Þ½ÆI†ÄÎå˜gN©Ú«ªí‚Ù ãß—Òzñaè0MkÞ‚Ë_›@;â;µÂõʰÕMeÞœ¬6çä±²{=‚iõ’€ê.k…NC áÀÉã‹«cåÌ_®tü‚ÃÃà9Ó¡iè—‰´õ¹»˜e3ÅŠÍc”/wÁø1’1†–S:ºîÚÒ6‚—züh3qm^ãNhYj¹1OÀeGîØØXªNvO_þpä*ãJªs‚#u‡ü…ü# ˜€—`ù-XØ,saЃþbÒíÌr°¯ä X X¦âeˆy“±«‚ÊKCÏÕô­ãcgþ¸q ƒ–9Ån>…¤º1ÁI@s3G!š,Í=cs,iÊ™˜Sª 3¦TòÈpsžž€3 ƒWåwNã¼ÅO ›£8ÏÕÞËçHûòºš‰ îݬÔ^ÐbHÃ_‰D3·`{_Ïûï|…ÀD^=ç½Î+cê+kïÌ7=ƒ¤ŒÇLФ¹q]œªÎ„½µø½Š´&ª±³Ùª›Ì­ÁIµÞ|!$ÍÞºLÆL˜:0“J*äl†ô;ùF[5š]ðxrç2G'2ƒ;ÀX&­rgjÙÍ’P?#RIÇlåŒÒ®Ädd@L‡3€4ŽAÉn+LÌWâP.•ƒ’ú¬žG ¹%ì°¨jM‡=…¨µâ¥œ©Yê)dÏFœÒ‚LS¡£MQ¥f wŠ0Í!IKéçFíùd&r„ôxc¾~¢TE²Ã¦ñãp¹4ÉØm=·éÆm˜^o,ÏíÐd…QânSç=ÛŠÛÛ9™®®šÜÁîÈéd¾ êFBåê(Æk^ÏÊÕT) 6óÌ ¥š×²íŨ>é⺤ƴõÛ49*Çú Ô`œfÂήڙ•ѵªžÀùœ9öÈ–ù&ÅرV:ÝH˜ÿ¿ì}Û’äÈŽœU={$“™L&Ûÿÿ½}Ñ®ieÒîLwÑõÀpD™Y=Õ3äÃ9]5•™Ì`\àÀ–„6±yÎQôìY.a‹Ï@ŠëXލ²[¬ ÀôXs…E½‘Y£^æšÅû³-•m@~%”r€&€öíÝì¿þ¶üî¿L«ÀÆú•g§–~t6«1_ï—ï çÂAêf÷t<‘ð’r»â 5ÜÒ‘FÉǾâó#°¾†Z«Æþ>Q½j[·¹æÖa°¡û6õKU†v6àëËMì&L= ëI%8Ì—0 ×®Î4þ* :~FdþHëø:}£÷œ¶%Ø ~ÍÈ«°ª…ÝÍÃÉáÉ ¼5"h-8Âv+Ë©8ÊXÊn†1V²ø”,í+:+õ˜«@Ä««ùà bohÇœí)šþ㘨ô¥B"@K&™±o$îÙÊP–`• i‚AÝ÷ˆ˜noñ]áðþ§Ižyň¥F]R3LåG‹ÌY4Q [‚p?ÌM"¡ ¿†×g<öm2›V©î½Á:ΙØë¹Iº6ûˆ{ªcQ),©’ Jðc ’ì±<5h!„2£i„†èy5€G‘¹ê•`Kñ@6…f5§cÉÇñlaUûC^_œ¹+–s ªg®óqÖëd^˪>hkæh“ï>Þ3ß—{j]0Ký¬,<¡JO-dÀ[ö‹>údÇ€ñ$+8˜ÏâòÌf&£(ÀUÅÀÊÌ­(·(ÊšØÚï?– âûÛV:·‚­€ ·ØšE–è9€„þþ}>Ë ™^0¯)·‹ ”EÖ¹ó…À3‰¡óSôT³ì‹[忹Š9 ê¶ûõ=®fV—ÎZâ}eeêHf:õÄ€º”Aî–â·öÏh©$Öê‡Z2„ߪNY#:ª+FÜý7–LQ1ô(‰å|<ÒÓE>ž%ÀG¼@ "ÔLì¡X;~]D¤cÏ g ¤º %¹;J…Â* f¾ ÜŠÃ1g"*Õv%ò˜UˆÜÁKÖÒûñ†満º# Ûu’WD!ˬžÆŽU g‡Á°§©&Múg‹ôÂCÂÑ[„r«ølO”Š›Uoïã°*ÈŽÎXÇh²£© ½IÍ~2A®—a>Ì܉ûlE¶¸Ê:¦vüž¢Ìv¼wåÍ…ÑFh²Y+íéa°Ê¶ùXEôL5’ñb.V¢?^_ê £rñ.ü =q#K÷ˆ@¨rÍ̤ýñö¯¿›ýjÛ6`fÿñöû‹ñ¬GLK€}ùi*?µX2©|ˆ³Ì‘¶Ê¶X–÷ŽÇìIlC"ø1dÎ ÞCN-PRZšø3(_jX²Eÿëÿ.ûÊÛD{ŸÌ~{;b½·†äoåŠ>D‹ø‡«4òÜ÷C=H#pSÅÞ¦ö’NÉ:=,WÂÃÝJ|_`éŸ ,:Y‰³Wp4q ¸™ÍxY ª[X/B…N”­ùy"ºcÑòAÏjÇ&ј±‚‰Zì1€“÷$¢Ša=ôJèŽsªJmCê¬Pô»âetf^B€MÔ{Ýå)\•ùG3¼fíýXßgg¤L•6Pk6|–kŸ}fÌzFÊV—a< €:¬e‚µ»s,ƒ41÷É2uÆ%?/$ò©ÛûÌEã¸äC}zíŒ]¯]Ô¯}H™€KG°'z*Ï›ñ.d«ö Å{R¸¬£N• )Øä|± )Ô&Ì'w¦NËóØoo«XÃvî¬ÏÞ·á$z÷*ßw?‡rO4ÞF‘åcC¥ÊûGcУH¥†Á³Ê_Jå+ú/ùûsY]]ù Y õ¤pI§DA¬ï½oE•Da6Ÿ'to[òAk}Ig³?~¬òvŒg3û?¬Y¤T«ÙœÉ»Ô4ý: ™R˜–ˆ§(‘îXèì[‘øSã#Íx­/ôDEº 3ÎPcc9äªjð§¾çž`ɾþÍ~˜ÙÚRV7añ1‚Á¦‰ömÍ$,ÒÞXL¥w#Úùú†-@ûûÌpÎÍ,±ƒ÷¼„êc³šXWç'NgÆN¦z*YµÇ âº+¬0= ó‚Zº ¬Øå¸Y5µªM9÷Ïø`3²ŽËŒMï‰äöäc»È¨œù[Gq-:•®=ÁO ˜£*0-˜ü¤ªƒï¡hØ´gâÊ™%^¨c\‹£{Îó©´„…|±È¸Q¼.X9²HV—T}¬ƒ=ƒ1 ŠŽ(ÀI"‡½$CÃÅ׈>†®ÙW1̤tx²Ni²¸q(ôÝŠá4ª›{°§²ì•J R”ß{L™ðÚ0w/1P>.ººáŒºÉÈ„ÑDóµ:(¡—”Úþ:JuÅ}ÉŒ(„oJ` pÖ€XÁÐÂô~{ƒ½¯å1>Pò’7ðÌJ]yïCŠ0Eîk”ÿR¯„Ø!ú1ÚgŠÌË^¢HA ˆ=ÐijK•€l÷*ãQd‚5Ù¤KŸ,G’‡Qû#4°o…‚#›ÎEzÎæiéEÛúO¦þÿ Í3wPÝxªÒÞžy)ŠýJºÙ#J•—E_¢´G lj&A._–’Z^Ñ+)‰:+ ×Hc¹פR´3!æ¡|ƒmÅú‘ß?Ï«ùcÝcf³ÛÞ³ |‚ö¶JyoýæÛ\™ÌìÛÁ‘Zv'3H,}Xœp Uçš0³R¥\qÝ’ìûgôö*£„'×e°Ôêô—Üu³/lEK™ÂVˆF›Ù:dœ¼á„ƒ'[O}>U0dìd‹(³RánÓ¥šícñïó Úmºu¹†>áÕ4ØÉ«µXùãÔè¬Ð#³O=v iY˜]=)Utü>­þ¤=œ5Ë+›iÝÇô¬M—¸=B¦ *k*TxxfZYïªN¶ê#t¯pT®’ÙE¸ž¿ÂS©’”MeE¼fAEFÆs^ñpâQ&¾ÄDÚ¼2‘ìV^Z˜‚¦‚.«PÞWhÍÈQ0ÇmJZL€Tl7š¼‘4,b„QQâ™~£S¿O%wxzʨ\ƒvÔ-[q4L Ô‡ 2”}œ\¼üCð=t–Å2ˆFû*àŒ²ÚY]qëG¥SÞˆå7ÂÙASšÖBÎØyp,½|¡D 5Kw†½’¦NÙ‚¢V5~“Eº¦W:ç X=ëX+;b+P|©²ÑßCV¨ù3ÐîÙ 41+ÓK—Õßß*ScK °‰‡É`Y>~%“¨ü­¼RÙe&Tô¨A¶4qE_<%”J³ÐNléXM@ßÞ`ÿôF—=²#‹´…+{¿— †ÜØy”ÊÈrÁæ+Ñ{tŠ9…ö"ÛS ,øI£l$°`EF‚’j‡3@Õ àg̪J•¡ÀBIº¨_)Yóœ…Ûú—1/`Óòïíoÿøaöoÿû±–ÞÍæ«Œ”÷˜Û)ûUešùÖ”Y)VP× G¨YG¡ö8ëö²PøYUõùU¾êÁR}R…-•ã7²á[ìË2¶³àÄY]2!”†Æ˜\\çÒ©f”ÁT?Lݰú”¸Üg_줭®Tñ‡Ñ¤&M©žw¨¥kƒ{ã¹Û}ú{³0‚ä+ž…9§æÆ ]½Îk䫎¨%ùq!ýõYh¼\d34Z×UyÀYwÚTt²%Jv£Xç»k¼j‹ñJ9NN˜@™ª® !XEVÒâ•^ƒês0-ÿk›^›#u²ƒ¹Œ‘¾)3ÂÝ?Ò«Å¥xɰŽ~ §z¤ì"4mh"ëU&"˜`IîýS—¾Ì9Þ}uÁ‰å€!SQ4ÐËr¹ª4Ì“4¼µóBBZfc6ź¥iú·éP”7Á‹`ÐK´P[oÒ®,µœ€³ç¹<Ãô^+dâcŸÃiS·"kgA)³$=D©¤X(A¯ Γ§Dúä÷Œüžìó<%°`¹¯K¬^·xnPà¬O ¸ª¿ÃøAûýÇê}ôaöcžózÎlªœ&bf"içzÄËæ,žš<#ë€#V™Θã<(°ÌÜ…EKê ýerÝÅ^JWÎ>êÔéØÏl*¬l’ –ˆÆ^dö€Šó´/1SÐí¤êŒ?\žÊ&)¿ŒŠ­­ˆX0}“ˆ¶ijPHW~pœspû+ÚÍùåYˆe7Øa,<+út‰…r—í‚”ÀN'_™{*£LŠfé3YœÏFÖO–Öóhz°Ø€Qêê6G ­blÛ >i`ˆóX+mnì´e™v¸¢fsýMÆŒ~eÝÕMÈ:kUÚ¢ÀäBÞc›Ôa-ƒåU•XGçtxöº?Õœˆ¦µ¡‘7ä¾ôª™‹] A¾™hœ>¾7„º[ī݊ÚQõ·Ï>G6Yn-¾Oq¾³Cœd#S†€= nˆ@=”lar@fhÿxŸÖŸáª‘U;°"u,‹@ÌŠ­g0ÃUÒıt¢2Å*›"U­ôkÉÀRùæ1˜7«, 윗ŒÈ<‰=8ñÝʼ zbM-Š=U_ãÖ\Ü—Âæ‹2ÅUls{ù¼Î«ùwÿVXÊð¬ð¥”‹š²_žØáA!OIê+0² Üg抔„iÐâ÷ØXÅs1⸟ô2»,H´LòQ°ëgS3T5ãͰò8z `y*±Tží“ Ck¦´i n‰U©„ó ¸:6j®LõœHÙ ¬ë¡ õ8ª‰Š·=Žº—мöýñòbdÄc¡icœâöOêû=E °€=œŸ‘5ê1ž¼°JP0xƒª¼®ú,uà÷†C¬ÀÃi(ï+ás¨ï(Èô*Ã^yP ï+>Ce¹Š’Hno:õ•šêiÖ[¹¡2Þ¦x>[_ ) iM¦+©&Y‘¾‹þÁ¡˜c:ZïF&2ÊÇÆ¬§/ÙqÙ1¸r-Nîi‚Y‹à€ñˆ×O$19®Ìs+©¯•êá;&ÀP™?Fá]`{+Ã鋨Å4M6¡õ?J¥•Ñ Öƒmºþ¶•äúÏ97û2+D˜¨»õ›„9¡c!eJŠP– QýK½ª’(?p@ « WÕ?#Äe0òêb}ló´ë)ÔíTYƒžÚ«Kɲ'O‚ºyJ[=³"›ÝÄ~,P+Ð äÌ$N°#,üàÀñÙNtÌ}ÃóQÀñ…´À½†i`«ìR… Df)GÅä É~?'Y„#O¤Òu¾Ç(^ŒíFž0WÇ£J߇0ƽð¥_yQ¨Ú9éNÚ!Ô°¬EçÛÓI/®ÍªÞá>0+/Îæ×@ Ê›§ ôØLªävU÷"YÈ%Sµ0ø `Ô›˜ŒO°68~ÕñÕ)KõNˆE$Uád¿ZTE¤4Yî²³ŠAŠLz¯X"€ÜÆ3ù*kÂÑ_›p­“-ªn„¡9Udoc@€N T‰!k– JFVݦXkfƒjœþ!Ð C¸Z<árï"™T¤3&Ú@gÕë3;f9Û±—ƒ\£FpD–‡FÑ|_öÛ¹ìé€Ð’A:0 ׯ·J\L!£I¸–èщÑõáÒBÓ}TAüžJF:j/!ë/ÓÒ¹¬-K]‹à“øGñ•ý”û¤å@5JOKÉeñý*-)`GÞÖ¯ ÔŠfª¯W‰…(b#ey[ùúy9™œl5` Y”+*éëž/”Ý „‚…ÌR¯‡2n ª<³Ìªq)âZFUÅ(€#ÖQU–Ñ;°V§MÕY,2\| ñ.ÏÎÒÄ÷Üg>•ARv†—Òa j/R^(³I2. }:W¥3/亊lˆmuÃúXŒÏpež¿A(=’i,a”× õàˆ’gdÊ á ý1Íý ƒýÔŒúžzY&qø¡(§ÀpeÕ9G©ê˜Gáv}€ó…™ž=©Xør†¾›&>WR´ªÔḊѦÔ=kèØ×ÄÉҳѼØÖNíæ­J_‘͈{'Šô?M³ª´ZA 'Ë’òÕ B!õõÄ?_Pq(±í=Å Ÿ¦Õ•½êIy›¥ ‚5éÑø@Xm‚Û+«Ë_•dÆ/®÷ŸèÛÖä4™½MæÛš©s$àÊã´ef.þIT©8öÆw KÍž—T”–=Ÿa}WArãýÅ:óÇ¿'¢ÐxÎiÿ 9@±_0<$  ™Ä´ ïõµP ÍP¥Æ{Ð+«.g…ÚÈ*‹V”'¦¬r5 ¢B© • BÐ&€OÊ)N ÂÛ¹=G…ÊÜrTF›€bŒ ¢©? Àg)‡KÌB*¸Ü? )–ÀR¹©ù"Õ…î[)í}¨ŸFcE¼¸?;Ÿ ¦îAL>ù”âú©ÌÖ+ž²I™ÑîlÆñ`6 ‹»WÂÂØœ1tnÜ>fR¥C¨¾ƒh­ù³åA*Õ’ž^z׸"³ÝYvBó¾zéÝ寕h•‡Ä<ÜÆ]îµnÁ#U6•Ó´«ZsQF4ÓŠ›,7ë ά—öÐdÏT8½R0Š3ÛlKz”!¸YÇ>(еóQìB*¦Æå¤²xnMYš•Lí"ŒÉe\ÖÏÃäDqÄ;éö¸ ’ÔÐMQ«¹ÎØóc¹·ˆö6{ˆÆËJ(ÂÔüÆÑ?µÅ>ÓRZ‡7³·7Øû´f•¶Iër›Ø°þ¼:Ýÿáÿ»R†«Ñÿ ŇèDo%Ù˜^°ñrÿ£PÑ Ù­ÄÇ(%QŸ1ˆý …÷ )ÊÁ¨ ¢”B…jb™= ½6T=aèCØ= Ð1Ä®„&š~™p‚Ö³ºh3…=?§Ð×W ƒRöËdÙlåg ð}ÌEFá–¸_ûyÑ­v(d„Æ¥ÀÑ¥Še¶ô}N<’Ì’ÎK÷.~!ž1›9E 'b"pÓìîÕ£™û•ˬÎ@l¬— yÞÁǺn²<å6Ï2@mÒåI£ä‰m³Gl6Õ¦Îü/øe”«¾/¯ƒ/¾BTFs]ùü|l~+Ó+™y¯zK·â¸{3Îз'‘aqýPÉøRºÂ¢4ø$QŽCÑòÄ$ ¡ Ø-æ²™f¬…·,?ÊÁaSa€ ¸³e?÷¥G ƒz š1@”`6+=AãÞ9>V0ÞL$þ¥è`fÌQ4ë™ÚÄj§rK4€hJ䎌†7Ø· öm}ä“ö]^w°5S ç]CÚÇlö}^ÎÙ—ÃÍqPÔÿÅà)%SWÖ†Ï(CÏa+„¾’C™i>JªÔ¯gøÉ˜éä…ïq3¥¨ÖSµRûa .¤oŽÊ¨˜(A4A¦T&¿èû)1.Öð:ÆçA±öBÙu•ÉO ÉDO¤ ËD{øN¥°ˆ@VXÄÌáÖ¯JED}ræçå)‘BAòå¤è||1C`ˆ«h”Ί§ÙvûÄçÁ’ò3qFU³ÏDù»ba•!ýåÀ˜ç‡ƒ~ÉU÷;åX‚¤Î—Ý÷sjÅ#x`Ãx[¨*â×E’ 2Ñ$ü‹]L DŠò죒 ¦ØãÑ3µ•Y/XiÀ$;±b-}ÑçnظØsáƒÖS ÁEÈCAÉ6ûà2òKYʉµe_"q=»>š%1ƒç ú:K3f!cµ*_áÐ¥V&Û`‹3;ÌÖ2½Ü5q)ãÅA(Ö”6 rVôxâ>öF• èe8Ú±%ÍÞÖÞ öffß°úÒ4†~Ç}±ÌèŃã9Ò¸È6«}Z‰!$`"ʈQ©´Y¡¢f‚m1\‹(˜ýN ï™Ì °PY;T“µÊiÞê •K¶ªg(í¢'‰¦‰˜¨¦‡°_$•¹ÂK‡R2Þª¿F YHÏ!ÉWd¢0Nܪ‹Œé%í X0†êwüÚhCPƒæ°8x2£õ~íT­yþ”Eû¼&4ƒvÿ§ÅvŽk’Š©¼ù«gýÚÒ d&äÒÐ!@yE/ÒÕ¡SVÒõÞmì+™U3γüdë˜Ü§k¿—“†eØ •lú—ÿPfèU›Ý¶6*ŒÐÇIÍ^Z¿=„´ùH¸9·çt‹­˜$%~Ã3‹V Lp”yè,R6ån… •ÙTºTóµT6‚èÕ°¶ …!kÔ3í)éu¯2;®å ‰‘3=%½ƒµ|n¤n—2 "øaÈ\€*jÛ‚IÞ¿’Dw“É£fÛC{OÑÚW´Š2LX²Fo o› 3V3î&ÇÇàvÍdζ«Ñhó¾_Ó•IG%1ÇF‚BD¥â¥²™)£î…¤7*rD”P©Þ/9ÍdI_Þ!û#Å%²‰ïW%èŒ|Ú|CI1+—B EöÈBˆ ¥Be|•ò¯z>fEyŸ™ÔûoöòPÆ·{1‰M¹'jUþð¯{ô¿âÛUÙÀWfò9žCŽÒf:Ê®;™Ð‰_5¾†½ld˨ââ#âoô¡ÄÏUeº+Ùí‹Ñ³d%“6} ›lsþˆô?Bн¤T²ã ×Ç/˜1ú²HîÂW*âÚÃï¬%oþÛÚl1Ãèv(Êö¸aZ¨Ù2¬0–[?»æ¢?C)îUL :Ë—U€©×Ë ”o¼§!fí=Dîfy$ Ävd+ÂK7¬çLm¯üMï•^ ùuP±ó‡ì; X­Ñ)bÅ0Æ»LŠw?$ñÅ“î×ÄH=-L˽ýâåºý®ÀH¤ìjÒ¦ëìl…âü±8ç&3ÅóJ¯–Öï5Ü~ž ÂÔ1ÌT’Ú,ˆA\‘-HÌ=G•ô|0y5Õ,/[®Tï«@hÝ›‰ÕÀ5ïx!¿ÙRnçKìšV¬F:PßJcÉzÝ3õ™‰g,{„ "ÅùìÆ+…™M€½M°iZE9Ö{|#·å2©ŽÛ8glÓwíGò½£‹q5•Q¥˜‹”ÍŒ¢êZœ'J= E©W%Å aà{ŠPÙ(èuÛ~—޼~ÜKbI°òÍRžTiz…„ꛩ²@Áö6D˜ÿN¬YßÔ'ÅV”-P’á*Šeƒ,ÊÒ$ÉõC/eú@?§ÂÙ3z@¡<FVH€©<]žm€?ßÛ%^…ŽJјáEéC_9áã UÊ|:Ó0A<ñˆÎd®å ýø¢ÈÁ¾TþܘÁþü~¹§ ýu€¿BÙ48bÚ©TQšžüF¤¡cð‰¯»È>É’ïKó I¼%Ùa zg“²yÈì4=)nÃiƬJ'7ì뇢XdŠ ¼iĦ6ð…e¯)•]L.h£Ï¤'ÑüïµM2³!Nõ.lŒÍèݬV7+ÆÐúå‡S—ý“!¦ï•r$ÃêÉ·¤ß¦ãìšöogG9ŸËTaàOp§MÌ„‹¼÷RfgM M‹Áqd”ÃøÄr¶ ¨W®ÓŠ‚ŽFP{X$ZЂX E8Bg#˜a Õ\`K²Ò´ô¶"¸›õ©À¯ "$ÓHUì¢ä¬™Y4'fÁ®Ad¸Ÿý¶ÚÒëô7÷õµ X¬Nh* ŠÞ«pëé ¬êbZlÝJ/à5ГO¼Ï}ÔåXUX”3†À­2żtÓgþm":û9™Bá¥l»(ä>ÈPÜïòÀ TÁ}U!U$#N/¾øºžbö_‚{ˆKÍ%â¦b0ÕckžŠæhP0CøÔ-jô†² ¯PûæÐÇ8(smì=•9®B^ǾT}Âl³Aö±QÜr/G,S=€õMŠ™Ÿ¹/Y aÆ’ ÂhÓ$åö6ï¶e?<î„K¹²/¢é}ÊYÑÃb, Ë…ªÏ$³f{/V¨ÝXÞö6™½OfÓÀÙT˜ž6²ÝÕALg_±ª³Ùï3ìÇÇ¡®¸U,=J… ydùLGâ+C]}l îÊïWyŒ’äxTõlû,ûÖ•%rÔ§8ŸPÉx‡õÙœx_îù$/öÐŽ„½ñ%€†¾vC¨Ï Ø rÀV"8Bâ«R•-*˨8L, ?ç‘ò¹gb2œ_¯cÆÅW[ä)Ì ûóg€ñ¡¿Äå7æKï哊µW¿ç•:S’(Ù¯@¾G½Lß_\ áÌШáÌqªïIäŠR®$;ûs¶§GÏ ¦pfÏshŠ*Ë+|ËÀ:€i@Žpˆ's‰K’õ­H¤àsAÍÒ&š5C“y‘_>…v„Z—áv¶OëÿÐU¸‡‚PÓ| €ʨ¢]y&å†qµªvAÛúŠlñ72Þ;½-Àq¿…ÉexçU¢Û–”Rô@ªÇ;ªkÿÖǼ”ÖÍfëïTïŽòABî±£(U #•ŸùÒ:kçLû®¤Ã›Z–=\Åõ¤¢ÓšÏ€q¿T^fÖîñp+8H> `~5zï¾`þFV‹ÖñT„„µU<¬K=H {Ñ–Ýaÿ ¦1}G+_F <Ø®.—cCOÒÒ›b’kšeu_E.^¸¨¯ë;àâ3À8¨{ÑæÄ‚H]>×7è=Œ—£{T‡²Qþ‘éà r­Œ5{Æu§×À‰§ŠJ}l°91Õ‰úrÈ¾š£· VR!WlVs¸‹!ìÇ1¨1D×í HŠPjP€Ã™þiv‡D Tß ÊoÉÞî@ÍüÄ/’ŒKÅß…Ù¢ð’AÇDç‰:é¿,*ãâÚöiZÄf˜a>&s‰ûƒðâûëÔ¨1’ÅqELÜ«ä¦irßÞÂ:ܼ§¼Ìp¡œÅ+ÙcG?f³™.kÔ5oEZª¤ ÂQõíxVZ0§²Hj"¦• ¨³ÄXôQY'²…kû‹È öW=ð(oŸÎ…^&Øtj·2&CÕ8]¿òÄûWS@Þ‹ @P¶"¦näÙÕ~0Šò*ÕÄxΰÅ‹x¹²À0@ ™ÊlªýÁ"Cý`Œù> qËÿ. ¡zoz û¥=TÃÖ… •ô‹rZî w˜'¹{AªëAJ5à¨6>‡½x Œ€J¤¾‹«7üPå€åçpãxtä*çnÔ H7zîæKëàÁ|r Ö^p ¶ÊDíôiRqìÔ®ÓƧKaO=j^+á~ æ$‚·‹÷WQ÷%•W¼dm±+Í?z­2ãž;É£0ÜTF) ùÍ -¿HÏËyôD…Ue—b}“íãoоvÏ(a÷:–†"ÇâøUĈ2Üd­õVȺ¢£\r‘í~;pC¬b˜¦"žŒ·=XÇû#™Šíï?2knøUú!а½(Ü{)ªàƤZúÒÅ^Þx©`Ø—Ü©ÌôM'ÓdÅ.‡³Y­íŒ`ô™ÌO”»ö­ÂE3e“•Ri"ÎdqnŽLS¿„dQì<•z §7Öªæ* cF¯£ý ŠÃ~ã…7h…i¯½¼,âˆ(¤%㣯VÇÔÖbLÕÑÈÛ?CÆãŸÿçg?Øä¥€ô<»ýìÕ“…îîgYø3`àxÔqÛ!‡;¹u5Õª³™àyÃ<‚1yi+<ÿÇü· ¸€W~z•ØT yQ~^ú¼§š1ª!Áëa#÷þ”eJzZ>6—ï$’ﻈß*ä5zå…ÔLþçb±6S@væ9®Ü™?d% õ.`°Ù©R²h„‰þ¦³ÈW¥Ï•°ÚÜZrsõ÷q1ëÇö§sà BË\}Î^˜¥ªÂÊ¿ýtÄ$Û9„Éì›;˜¦ÔEòP¹ÀR_ï}QfÿãcFÌ´ùcydóüÜÜ×õ ;É¡Ê9£ïH bTZ Ž$‚,.͸;åƒj-÷úM{#ÀK”_d£Ôy³(íOJ56¥¼Êà‡fÔ‚DUŸ¢ò²Jg7uY¦ÿ\f¤2c–¯g¾ ìØšøÆq¦ÞO½ÞŸ^léFp‚?Ž–võ>#^½–'>O½¿Ù¹²½øéïÌæÌf‘Lú¡òSF0¤ÊÑ,+(Xlè¾ðp½$î)è§È#ÕŒ3i¾ô&&2C›j; ²­·eY±VfŠ3ÊŠ¾êoép9<œ4„0Û,©[Y½ ¸}†ÿbÁ)Cö”'Uñ:c?/@a^qBÎb k+ kzÓLÓZÞ;™l'¬!p^!Ää¤4ÀÞ±ùù•ºe׸øoîÌ/'’RÖ'žŸG‰ó‘IÃZ^·ˆ4Ë3¸¯×#Yj%)ýÏ\þY•¨•f°Öf);D\Yʘûoz‹(ºaíýUMà ¤!£æÞG £‘ä·I%P¿§%iR]´Ctv“­B¥´-LJˆTZìÅû1†S Îóò×ûËŸDlDá¸è ½âbœÜ&¬LCCWî)”ƆQUx¨²9\ˆ–þ¬Ø…O½r` Ú{Ë€¶À³ÈÃù4Ô—»ø9ïÕ™z˜#÷oªæ­†ÅRë"?Ì!tà(hþ¬!ôuÕ)eh w¯© a+Òå Í¢_`-°ê‡¨”²öó\  `Y ‡É4üž±e€P Y"ß×,µëï™ç5Â2æ9øª†)…‘Ö‰£oûL`é-y›\^Ľðpë‘…@É»ˆˆA×¶oâ_ÿ9ÓæÙìcö½¡öw´•ùIë\õ‡PÑͯcYœåÞSé3WøÑyiöØ£ %²a"[Ò,jKem*øVÙ®nÿ® ¿eÅ<3Ó­Œo£‰ubBB“*9(IG.Cfç¬e=–kßd˜ú¯(Ê—cŒ…<>ÊܬÓgfVÕÙóÅiæ÷è¹úy襶€f“??ÅêŒë ²Î8ÿÑ»Àë /Ž?9báð =BÁưN.nê’ Ý#±ßßM¿Óƒ˜xèÅr %KÙ„ï7”Ñ”áéß⧯EaˆC.²¼þìä¿Ñ§‚*O›À¸+0 .€ñ¾=(}tâŸCF}@X?ËÅ2Ó´¼fŽImœà ½¤ NÓk&°>ãvøÃ†HÂÞ¬ü¼gx&¯"Ø¡‡U»ùÒù™Ÿ·–òc«»>ð eï‡dG¶»#Q‰²¬’O”赫¹RKaBkUß ù±P@!ÔãA‡Õï#L÷­ +—*Q†(¬zh¹ÌÑÚõÕU3T~\‰¤°0ÝËî*sq²nb¤`ƒ zÂðz`t =s%àÉÁRõ¢‡¥©üAîµ|Ú  Œ¤ÈU<ê{¾Rh˜Ôjÿ†¹WOÎW~Ðg¿ ;¤Ñß(8Ì1ÖñV†‰ñb?‚ÖFl > ±Gëý r²zRÕ ŸõFò'£¿+I Fi+¿=b&sö,Ë¡·ØA —eÜûf:Ãê£×ÞàP©¬Cƒ#U=Àì Ö~Š ¾ØW õ%ò‚r^ÌQR»w^1”Î- ñƒ\¥½Ûï|÷aw-ô¦@»HSâfRG#Úš,³«”"£amTâ³ Dp‎÷‹ª_6‘Âh·v~3Q”T#ŠÈDõAëD‚-3w è±èå)JõâØW¥oèØ$(”=Œ}CÅ\l„ âó~^xáRhÔi¨Áǽc=Jâ}œscý*Áoôæ°Wk¤Ðöu] Ú+ï±ÃÈ–oˆ×Ù‡Ô?Ó,ëg¬ÉtUM°=+ÙQ)g5hó²glp;W~?ÒxxArêÞí…°Á‰mþ{ªë§ÅpÈøÀÃb7Mö«ˆÿ Î(ôÿ¶;ÇÎl;Ìê¼ó¼ömò繬’.ÊnRßä@̦"uª…Õˆ¨`š‰ƒbMªD´îÞö+Ç_™ky´Kþ¾>o¬~Ö8ŸHç€SÁlü-§+ÅËÃãq¿§þù|/Ò«s¢Cì¾þüÕs_ÊÚ¸‡ I_fÞÆÞŽn.ÊJl}Yh~2x⻯ï1sIT’çD–¿î’#UÛhFŒNÃ|t n@Nï^r(wgÁîë?¿Þõ2;½‹^P¸°§¾Pâ÷g†{®¬ý.‘É_Fðgä1cvš <ûLî°üëèøEïû¾þbs2T®üéÏÿª?͉¦³T)‚ÇÖºåQGfe«¼oml¢¤ E›XCá‰/-T8S)(ÊP¥„Yîá¾îóûoþ4¸ñ9Øë'N¡œÇ¿ïD,¿Ø¨7ˆ Ä}lÝ›í}Ý×ÃÛÒ‰Iù%%\ÆL­?&DqXž÷u_÷u_7@º¯;¼¯ûº¯ûº÷õ_ò¦ð¢¼Ï½ûº¯ûºÒ}Ý×}Ý×}Ý×}ÝÀï¾îë¾î븦{îë¾îë¾îë¾îë¾îë¾îë¾n€t_÷u_÷u_÷u_÷u_÷u_÷Õ\ÿÜñ<+ìãIEND®B`‚fheroes2-1.0.12+dfsg/files/images/platform/psv/sce_sys/000077500000000000000000000000001456075706000227145ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/files/images/platform/psv/sce_sys/icon0.png000066400000000000000000000654061456075706000244450ustar00rootroot00000000000000‰PNG  IHDR€€ôà‘ù<|zTXtRaw profile type exifxÚ­k’,7’^ÿÇ*f €–ƒW˜iZ¾ÎA^rº{z$™IÍ&«X7+¸‡¼öÿüßõÿñá)9])¿µ´Rnþ—Zj±óM½ÿëçŸáNçŸçýÿþO?¿þþƒÈ¾>~¡üyý_?¿ÁïKç»üo4ÿüÁøç?héÏû×y£?ô8¢È7ëϵ?oôÄß„?oÐu—Vß|„±_×_OR_þ£¶3ßì÷gÿòïéeöVæsž÷ž›ÆßŸòÕ¿Ãõt¾yÎ?™^ôò=/:?©F„ü»yºÿaT׿®Êßß…ÿæçÿ²(Oùýüâÿ<™åï¯ÿöç!ÿûÉ¿ÎÿÃ'?óïOþ§Ÿ‡ô¤}œ¿þþ¾U¯ïÛ¿§ë©0¥åÏCýõ(ç;^8˜òçüZᯗ¿3ß¿ç¯Æ_õ"z'K¾îyþš¡…Ȳ|!…zøÂ>_g˜ 1Å_¾Æ8Y(VŸ7¶8Ÿûb’…/¾O{ÖSY±Éò>ü4þ=–p>·›¡òÁ+ðÊx³à*_g©ÿ?üõß¾Ñ÷ò!Üõï¹b\Ñ d®œÿäU,AøþŠ£|&ø¯¿þõ®ëà æ3Í•ì÷ø½ÅÈáOlGÏYè‡f¾þ9¼ëÏ0E|vf0áaîžJ¸ßß@PÄÊútÞ¨Æ'ÅÁ„œãb”1=Oaqjô³ù7œ×Æ?³XˆüÒ©²@µ؈Ÿ7Ub¨ç'§œsÉo®¹å^ž’J.¥¼Eðëïó¦7¿å}ßú¶·×§¦šk©o­Wmµ·ØÀ1·ÒÞV[k½ó¡wîüvç½8ž‘Fe¼£Ž6ú$|fšy–ùÎzÍ6ûŠëYàÄ*ë]uµÕwØ„ÒN;ï²ß]wÛý#Ô¾çK_þÊ÷~õk_ÿ{ÕÂõ[Öÿò×ÿýª…¿V-ž•ò…ï߫Ư¾ï_o„“ìš±b1VüuèèšÝ5¤/—Î5»[$+rd”ÙÅYÁcÓ1áïµûÏ•û§u»RúZ·ø×Ê].Ýÿ•»\ºÿfåþëºý›U[²Í¼Ÿë¬iè¤Þé÷Á"_¾G-3„4F {|Lë7+Ýcí0rî1Œ²ö)—š®ç~Û(©ÏÉk;Ÿ]æ—vé¡íæî=óãÔÍ’ùí«é7,t¾þúæß|-cÂCi§‡hßž;1ù,ë÷¾ýKÏ|K¯mnð´¶«b©¿ß ›1æ º¦èÎ>ó‹©}¬Ú·‹SWòüvøVêwýæ*õùXÆÎÛ_yó&s¥D„Ý­ÔµÚ×,žÚS>XŒá¼ÄQˆÉóÌñ¯_¯ÿüÁèyÁFáém™Æ»¿„à›Öߊ#ö5^‚ò#šXäÐóežÑÇÉq3v˜÷ê%7Æ]x8×}ñO&u}L÷d²f 5ÇIšm µë½Œ¶ÓxÓŨóå˃"XÂvÍyÆú’Ùj /‰XœuÌ>ü“ÑT™%Ýñiwª»lDÆïŽeJU_ ¥›W¯cgøð}¾/Î ¶û|2 Tê$ðwoÏ7Vh+]Õ×Ìwü>Sò#g² ¸>£¦5}Ów^ú–gõÎ[º~­ÕÓtUÃ/ªí4(#g\߈y†¼ñÊÊ>¥÷ÕÓ[gí%’^’«½aE²h¼/Ë?Ã[÷"¥'3¿ ÖÜÛƒYäÓOˆÆØÆxHEòŒßïH1ñ<'ʾµù0|}Ÿ"4òêaå6¬×ë\¬1@†L]{±8½­B€3a@ß¼‰öÅüï‘K¸>Öy Ôøø¯Åo­‡w›Äç½Þü •˜g]a„5 ðÚÄ|”4ßø¢u½_[ïÅõ¸Áñ‡`†AЪ¦!kY‰P(“ļm³¤dia)¿¯¡åîùôAˆ}×ç¯=Ok @|¾g1Q é‹{µˆ¯¶Ë–#xñÓw¬]únr3C è¯ôñ&oO_%ÕÉägï_"©ó€Âû%¡vd ÛX+.Õ7“’Û;÷Nõ¹d޾Ż÷Œ ü*1™°?ów/å› Pˆ¦½A' b[ ‘…‰ê{.Ða5¦€cº×Îï .#Ì.¤ÓÚ3 Äó’Æ¡'€_³‰ð-~×Þ„9\»‘²ä^Ù)Àõ{*8yoT,qZ!z¢€aÜQ~:郦~ŸDHÁõÃÔ,þý#K+ܹÇ"ßÞvFñ¤¾JÚŒ÷ù 6Ð{óõ#µß?¾ë$Ñ{1ÇÁÆäÅži Ø‚±¬:ŒF*­òL<Ú7J›õ…®IÄ0¶¡–@µŽ ØŒª±üäJYsjvÐÕ÷ÂíéŒÏ$(H‘×ó¢¬`)?lØ€  %üîk­»…]yPÜçæoýv€ä»6H¢òW}8ÖùèhÚ+‘ëC´#" z·[¤x¤k2¤H·±;(V7QÑÐÏFã“gøÕk=Ö }êSB"S¿ÒRfófæV˜á„*nDÒbŠÛø‘w<|\&]°3C‘¬4÷mÖœ¤|Ü$ÙC\Ýßy.æmt\iŽäf\53Ýhó(w¦ \'Lw”ä3“ÊWÞÓÙ»½+Ão ï{f7”Z²«ï‹À LÒÿBÁ'ÈC&`5Œ¸AÆåê—Í̇µV„†@pUÔ“¥-v3G™mtÃÀƒÀ3ÉF¢¿Ð5kòɼ‰«¼8C& mïD :Éß B²´LJ^Ä;ÀÔp2{×GôÅIþ©¼@aÐÃ`bЉ” À—0yòB³ ØXw~¤X¢ò@ 'š£šV/ÙUï Ó§Ò¿³ä @@ ÀhóLüº3ñsÁº¬ìÊ4ª¤}wcj—Œ9&äëÂÝ,ãíWRWÍ.2’|¤Ž@0ïOî]/ÈL~¬ðÎe`f4ÆìÒ¨óÊïqQ&ò êüÐkÀÁ *HÄâKÎÁU0ÄŃòD»ËG ‡‚VxÕÁ“AÓpÁ5n¬âa„Oô )0DgÑBŸ6 ÄE²ž•·m©/ìGØŽ$ D:ñ»]ÇÍï—qeDÄR›aÒ_”X¯%|·LÆ F†ÐyJ¢öóµ³£º¾Ö?bŽ÷ªÝd@¥fµœùÓX‚kýÚ“O‘Ú[:!¼𩂬€»b–pA^æË‹* ÈT«¤]D?Š Ðâ½qOØ–[[ôG²!w×ÃS£&r`‰_!›÷!"Ï¢« ( |ÜW¯#4 ÙLJòáÏü¥w|p0”dö€P¨Ê¤ØGÝ*¬ <³ ¸{Å?¬(ãÏbs½ŠK¼­ä²¬Ê_’¹÷‹;8ÖÁc`™VA'œ³Ï£ñ7~àÐÞ³ë“ܥÔöã¤7J_—&kÊ*“v7C1¦Ñ*$ˆ¿Ç29—âð#þV¬|è";·•$Ë@ˆ%¨P‚plÿÉIŸ4×ÚIýJ$óFª7Ñ‹ÀkÏJG¸œ…Ýñ|bí$5œKž bZé+˜×ÐÝRZ‹a3Ùh%,$¨tfÚÏbžüÊ"÷ÉÕ½p¸8|ÀŸó˜T[Pü¡……„¼x¨î÷F“…Ãw ’‘[„«Z*1ðbŠIø ‚S`ý L”)6‹bᘸ ‚­;XÖÖÄE¿P&¿÷Å›0` ±hòì¬æüá;"6k?dÎb\²£Ë…à׈þC"Öú€Jà{A2ãÝ&©Ëýj6µžX† [Œ~ÆNH¡W@!XÂpïø±j`Ó¼3NåÛØÜóƒ2üZpyÌ9Z‡”º/‰y/¤%àevëŸY—SŒ ûÖpâ\u²N¨i,ñYŽ„7 çrWÄV&ý8Ì Œ‚È{JpB¡3¿8bLü޵ SfŽ Ä÷F‰ ¹è1Ïû7Àçïð´3±Êä,Ü6hEÆU “àãÄUxóe­`Ü•8Er`}ˆh°è„ñœËXd¢¡ÑãS‰îÈDÁÌÍ—òÞ÷Îá‚–À]ìÁ@ìGt=þÙ!<˜© Éúæ)?b¡7ˆ@Òh,âIõWg—Âe‰•ÃÞ¥±’œ\ö¾ÁOPx` C‘èsu†ÊZ¼ªf’­?ßÅT``rì fåß‘´Z ª™DGS!$ۛ℔ã?i’ð .{\ ÄäH Ø'ýÝ€.Ÿt˜Icýo¢›È‡¡üemL%€ ÷A$,¬éÚ‚¤è- ^'ÿïgX–’Ÿ»µJÂò ÕuþºU€˜9ɉìk ZÄþ¶¸Ñ£n.))Lû¹ÑuO%Ÿzq/]Ý  mD ¼éÉï}á€E€ËV1(h ¤U¢3ñŽ l L`»£B•׎{"ÿzÃ.„$Ô¯™X„DÑ"&zðM¢¼š­ÁÞèb54YÅ;ÑÒ=0îß:/F¾Ì(ˆjRÜíšÎ†Ç˜iŒ%çUS¾[U‘ÿâËôãËûþ1æu4‘¤ùeׇÉe¹*J Þ¿5äX0£OË{à¹>ô2 ŽëIŒ¨_ífÀ óÔ^"ùA·Û™@˜õ~¬ÂLÐõEæ¿k 0¬q!æ¡Ñ¶7>E³0ÑPIÌV¸Çô °<úö$›2̓•;TÊB]ØÌc¯bùS8[ÀOý»B†±jþ†\ Ñàìq§£ 5Ø:e8:!ð÷Hi€ ³ºD&üõDïU÷Ù ¾ñÜx´ð†éSƒï˜û„Bø5;®1ç|¯æxû™ú/¸èóòfböዉrxT¤ ª&y Þ…¯¼ÑÓáUz ft<: ºñõÜî ÁX•=|WZ¸Îùư ß!´%0:]|‡¼Q}÷f=°Pê¬5‰úüi±Šø¢ÉáD¼æÂ™ä `-¾@¡oxP½!×ô ÒÅÝš¬A=À,Êè½®]IYà)b´°é 3t)òoŠ~I dý!``vˆˆ³`ÙQ°FŠâQ`¾úöÎs€íêHÜäåIVóñ_vÏqINÿø %,ã« ªeã÷ñUxªÔxK5࣋¯òKüEÑÞq[$Úñʪ¬UHã€V&H°KW‡ À^ •û a¡ßÿÏ (̶‘¬è.çð‹Ëê~@«¢€€yÃÐnfñƒK¶”ŒZ˜@˜çü°ZŒCk‡U0d;~Äë?ôìcå¥%ùªø ¯£_f~Ê•ëÞV»·x2T t‡7Øó)†„xÊ_YÓÐi_ÅûAm n@J! #Öc€ÀV@i\«ññ¬s¿Ì¸néßd Kƒ_ƒ ~ç" ±ŠŸOÀÀaŽŽÒC?2ñDC@Û¢qAm‘;81˜ŒÂŒ¹G°_E1¢s?,?* óÔ#Þ–Bj‰¨Á€n Þj5ϪæÍ¿Š¶PÎ5³«ã×à-ˇ‘‘’†îx€{‚ê0ÅDAá‹FjÌ4-Ÿ) ¶Š_ACy gÎùàaÄIj1„ÃEì¡ý:kÓB'áE_t+h¯8²#„Á¿£[ÿÔ›D4Ñ„V{pL óåî'Fõ:f¶ î– a§_Hóì*ó3å-¶2ây[ùy•TáÆ‡QK½—^³ZÕƒÖ~õþ†GÒ |pЭúæÉ˜Ñ~ÏZ$1ðõãi ýb0,¥ûB!Â4* “ãCx⪣ƒYæ5a^±4g# AtÄ&ÃFíc³x˜Èx î9d¶£ã‰¿u€ vê0aÃ/ç§]ºú‚AJJ¦š®xxo)âuÀõs÷Ã=Ÿ®2I8ÛÎ`àÈYr{Z=Æ5X†}îˆ4oà$¬ ç’b@T0u ßÄ ¶ÂjƒŒ÷ܰâ½?Rä›È{Q'¤àÎÆó¢x72 ¦¨+ö ”N©øR„*êxtBsÞ]ƒ;ŸTÒ}!pJ;E*ho£ÌÖZõ$ÊÂëÓ–E14zK’ʃ`ج=#hòòänª½ïp7÷ •¤˜Aº,$a‡Í9ãÅn_ûL, ‘§#ïBån>}_VªŸ«†‡ÜH\܉ó„h¦¢Ðf"X…ßÌl"VDãQH7¦¡_Ýjo±†ƒ–Üd0 è-ÈÙ¦‹Ùo:FªA¤$öBþή?¤íÒ×’˜Fxü"•À¹¸€wT½ 볦 ìÌ&û‘ÐBE8å|j¼!‰ Ü"®*¥+ãv»Ù_ø8Ò.Dã"Ϙo_×ð@9ˆ©ÿ4ž`v¶.YTon ñÖä˜Áø•czëú™†/C¦ÆbAbBTˆÐã&—Èò§½wÂÎ:/çg£‰hmÁIéF/¬‰4çmÜq!TpBù!Ì„rƒýðÁ#\ëÙ/+=X3(„#¡;äþì}ă<GbÎZá¬J‡(‡·Y#æPPsŸM¿ïêØ^j‹L@êhúï#°=‡·ê ÃßHú¬Š˜e„³ûÝö‘xM¾ƒ(nPOP ,rëncyÖ@ñºJ½ÇÂ_ºÝÑÇq—™Õ\U—®Í"Çij"•)‡÷°Ûv„5ßmÙÒ>4ªagENfÂxtR¥.è½õe±¢Ú æe­è{„i -àg&p•H˜ _"36ëBPãEÀ­Ï7_xì¯Á @b ‰J‰àE½ˆßF …X¤*Gê)þ$xøq ›‹hlQl6ɬ~_‰Ó‹œæÙʱ©ãï¼Kˆ ôÂÆä\îE0ó[-­`µÿXö_‰Žp:#}H,¦2`ØÚÑ,ï"ÛÕØUå f£ÒŸÑŒ~*˜L4WìøÖ0j“ϳ¢¿PØ+&[¿QúüUÁ¯¨ÁáéàrHý\ÏF–,Á±¥áP¡ pÞŠR,¹Ñ#—c±°¿é–ç3®¦=âaËwwjÁR?R*Ãô+$±a®±(°zEåûd<5×:›íE)ï›v0ÙU‰L%\$k-Å&\t„PˆèÀð1Uø¡ô®+öÄp“ÝtA)D„Áx@Kö×`\é(]X5ÿgXžfÝy¬³Á‚ÎŒ°È°ŽŸ;D…Yáb¬ øë{C$JxÆÏÐÑù°OÁyÚóMbp’Nùe­“\Í–µI9bܲ-IÒN; M«…ߺð*«løÖTC·N%G Vбî™Y7‚ùêx´vîʦýy €ä³"³’[ƒ³®û`þô¼ëÓ÷Áâ3½Üäbý¢äî[[VìXA*BØ ­Œf¶ÇÙ6:×®¡]9Ú0P:m–Ý,®9¸÷©3t̼†å玲•IòàÄ‚ÒvŒVy#Âà2 Bèø„AR¶5$ç)ÜÌ×Çn¼æ­ed5?Òïcîb£:ªÙý¹'šñfüâTØ¥Œo’øÄ*Õ $}P 5Õy Àé„z0€¼LY[‹–Üpd¡²C$‚« Áȃ½` ϲÞÙçÙ!ļ‹D |YÜä½ábV9ï§”‡Ô³å?È5!»«žySkNøÂ“:H¾ç²ù¥%°ýT=9JvÌOSǸ’$@@·‰aÏ‚zKC”/»Ãé ; :^•{4 B!ñ.$"É °ºÁì9éªßnm'äGU¸üX®"_Ì×õNä°|&vLËOˆÜѶ/øˆgš7V·Ùæ“^D4MV'­Ì©Üíú€ Q#`Ŷ25úÿ=HÜøþÖÛ_%M·í©„ˆQÑ…Ddt÷ˆŸr‘z\dóÚ(íŽõ~N<›H.;ƒ±ÝÀ<à·ºky§RnŽ:~ †·ù 6œ¶!âžÉ…DZ0•K˜“çüŽüd#|‰â^ö0ž9®#pQÄ(ï‘!ªn"}ÚË¡–<‰.Ñz½íW§ãBö&¨eÌ÷•B±`®jˆñÆçÄüâ„`7q"`t»@¤Û¶åÊj‹ÑV¨o]užÍr & |o ´à_A1f…ˆD ¥Mªw·¼–¶vú³9Þzm{¶¬>í‰1½ÃÜ:}^núLÿ}Û»¯Ì$xL÷ÂÛsÊgèV¼ÚQ¾_‰þÂ&hnNÔHL8ẍÖ}ºÒÿBÄ|Š®±L¹W"îî'bI·aw R °þ€ùø¡ÿ`Žz}òm³ìpë…ÃÙ‰'7¸“-&irVwú~7–G#,aÐ)ïÜ‘ÉvË}H¸©N>ACîVWÜólº‰66^o}þÍvÈ F³üIÝŽ¢r¯Œ,Àk¹÷‰6á€Çvÿì[ú sEءݖžÉƒú†+ÛÂMF¦Ùº‰iÓ‚ ƒÍOoܯ ¼ã ‘×›í<*󷉲 ŒN(ºÝŸÝnò÷ŒØgDW‰O·UFxµ Sܶ°¬k»×ýÄcÇõ01@ý“ɧ{ÎèI†ÝPPh  ФªÌëFè‡/›ÒÐt¤áæ,øÚ™÷&<^px9½ÀÐc ]4H…¼³ÏHžB8·Ì¡xœâ‚€ºŸÖš—LT8 ”ôë;Ö8\Ç‚ápl±‡¤ƒÄ×¹}Š—Còð .d+JC³ÌX…„!¥àPt2öµüºƒ> Äþûs̼–vÏz¼ØXåþpWxI¶}`t£: 0âŽæzèë;Ïû‚+æ½N(ñ†ö¢Ñ Ca‚ç§UµÏLi•NÄ=yñ·X®_W(ãs{©±Ò§AÝã~q¶Å‰xƒÂðbiHÛ·–‡^°MjºP ƒ»V­N âܨK©°ƒäAì#^¸ÿÀkùÖð¾‡.“Õ‰7ÛÏ9j½nŸ_©Ÿ31µÎÐþé(~“XGë¸÷#¡ 8Ð.922â¶æ¸Õy=é]PŒÅ¦Û¶KO |‰, û‰&œ½p¤{ã·k9%”Û¶1OÄ’n÷ºKÍÞ~@é6Ôçe Kªõ•m¥¢vºÍ¼î» %!æÔLjÚàÿˆ|¨àoC¸9‚›ËêV3Î+ww†”5Êdœ¡`ßšBK.ó£ñFÎdjÙ€K\#ËÏ$„‰¹轓d~¾U=ÙÖwôaÿ@eAÁ*L²Ë*u@1ê¹_PÉ<Ü·â-Â([—™—[õÕRÞeÉPŸ•¿ p[j¶3Z+‚5Gg%—eôZ>ãK‡@lja_ܼrKÁÏT|TB¯DÅø÷ ‘þ|vAn›,c5Û kÔ« ¬×Çïºwv£‘ûõ|ÑšhZO†È7CtÓ‚‰UÂ{œS,Ö²Åç< H>ÊžˆæâûXŸ*(…èŽÝ© `e·(Øñð ’ÀÁöh0™@{bì­9[$/ÖLÍ!\[~t÷écôƒn"è¿×í·.í¿>=¨Á-ðÒ~‘ïrS¹®r~;XìIé`-Öj#$Š<‘px¬uBônÔ¨ýw¸®.ÏX ¾Pk jœÿY a¨¼9ŠÊñ¾¿Ã •4n’'PÜCƒy’™Vlt¹,X´o~ÙMÌÒî]„ß²‘Ne7‚}9­N>ò5+_@}Û•”m—6ô~lH)zÙcX:·  $Æe£d¸]°—À}”ó mèî Ü;6— 2Õ<·;rkO’âF#Ø©î~ž˜gÒLH÷`*ßâ+6=Ãñ}G˜^©Øã6Ý3 wôˆ'UPn¢ŠÌRš½BÈK\xRÿø;YÂÝðPã²ÆÑ'*Ú]æDÖÊ`•ˆ(Âr…;¶­àë«"K“í-kÖ¸<ˆ¡ŽäT˜`$óÙ<Ä9E EÜ*«JÆéH‘uÁF¦'òÅþSìŒç6œ.æúEԇг?‘¬®ÐøÞàO¯#7†ÆÎ“,O¶ÆÅ¿“ˆz6*·=vqjÙ®€¹–ÜX¹Ñ ÏÜßù¼•5é_<Ò˜éGo{ÒˆðÛ0… ¦•)tÔÄåÆ/hA€ ùL èßáMmè®ÑFŠb¿;Ë“‡šÛw¦\<ðÔäÝ틲…½2"Ä`–¬ÑNL¨¦ÈfvmÁ¶ Cãa—ë{2€ Ùm–Gg EÜ éë:},ˆs<ÖŠ7Gi°VÆPî‡ák2£Âmm‰ao·i ²œb§Ÿxe> çaÌ#–ÈHþì†Dg³V­2èôžîsê:éø¡ÂÞ5±¦ ×¹“3ÿ°\³mˆ‰Ì¼:þ~÷LvAâ¸Oþˆ ¤Ùc3­&Éž%›tP+Ç53&k·Zr©ÿ—ø¦i _ƒûjg•ã—˜­Øô5ì¹{šÙrŽzT\²{RðRh,R(0ñ+ ¼ž°ºðÚÚ3#¾‡ €Ñø‚ÍÆ‚S¢—Þ¬÷Z]JÌóALÛA7†…[ƒžoXÙᇾC¥êæN'RѨøƒïí5iZ°Oëfh©‘2’³‘Ç¢äÓ ‘Y¬ÑŽÇÜ rѱ@4±üaFìÏzåèí<ÁÔ×»ðo ÐÝé²7Hpã0¹ÓŠ™#ð\Övô$Bßå·>ÐÄôÃc—ÎÁSDD|_Èm;ì<ª674ì¯r§qô¹NÌêÀ:üz‹0›'rø?ˆÄá?ÖñêÙŒ<;,<Ù²F΀êÑã^äÏÙŸ[xnË:Üg‡Œ-j<’½5ÍîeíR¾A­’ξáîgÿ ‚¤ÄÎVK‚.ªGžNÏJž>•AÙ=R%úÚîÑ´ˆYú$ _d½ÅÝxúCz)zF*ýö ³¶r…a˜¾[玀-å”}ž°YOÏI`Yt:„Iº—þ+‰êŠªË®CÔd›#j£$DÄöGáåXGMë± î#Ëßša€‘éugj(h6†ÈG‹aEt&Èh"úòLËWd/¬Ý:§á6aµì¨8ñm(γˆ£¶{•õ`ÔƲàÂò6ÛÏÌd½Ñie²Öñ$÷üøýæQ;»×9°…(ˆ”‰JÁß RRð@ïddKàò®whtOAà‘ê=xm_ô°BÓÁÎa+Ȭ™‚çÈv‚&X„jϵÎi€SÅñÚÞ¦5½ ž±ÈÀÛ8•„{#áÈøvÛÞ±rˆŒÓ €Ì¹j鞥°Éïù›Y0£q&¨½„UþZC¸Åíš>©y–Ö=A{ð,ub!Æ‘DŸe,¢ÝS¢¯ˆ›ÈÍLÔížèg×ÃmŸZ·O ˆùª¾7úó¹ÜêBV¥ËñÀQl‚` ì¯]n2EóчåGR̦K•u%‰€+œÃ•ŸõÇcïG‚¹T¢‡,Ì>ä¸Ý4±•{xnôèÂ3£} _V+›=*€•ÆŸðµeD¹`óÅŠ)þ³b‘Fú¥ó‹MbÀ‹ bùO%¨í˜0áÇ…@'ä~Å„ñèCæ­°î6:ÛfÞÎY†ú^Â÷ÆdØÛßH„¹·gñè4A+ðHϧ!;äH±æõˆS´ŒúâÒ¯î¿/!±mÊóÆò‡îF¡Jwr''‹òMAü<¿TzHržÒ^·Œ;O—5ùe»NÎHûÏ^ ìÁâ]RÚÊÐÏñšÍ:ÚàeY¯>¿j7Ì›-gäŠ"d×"n×Çbµï½[²!ùàæß9U‚‰w÷©ÄáHQŠž„ã25“‘%2™itvSº£GÚ†-#n,¼ÉEPzP‡mÖp ÿœ!«vÙ`ÏõŽÌc`Q80Àéa‰eç<>Q+U¯‘¡@UèŒGê;ÿ6F®Š)ŒD’Ìž1{û⠶ƒÖYäõFÛ³ÎIëHŸ»(ÄP&²rœˆ“t}ˆà>>tìvOùçà^=ÿ´Ôxîý¯šØ__ ðà/Hw‰`ë…­n–h΄Ù;RTWÙO>TÈ–lˆ{¡?Oð,‰;x½‹„&?¹‚íèžêÖŒVŠYý[Ar™j_ÀÆËû£—DœÂÉæÖuCÐÀ§GKžlÛJÐ|Ì2Âr=–ñ»{#LoLVz#FÛ¢ÔÔÛvÛMx4žƒˆt/Xè¯KxˆäêÉ_;ÖµÙ³cš< AÎ!ò»š@ õ \ZºŽncáZ (, OøÄ;º-W” ãÖ £x=éxùBüS­µTøÞè×lHs t #Â>—{`cO‡Dµ´àéÏ›ž8ã~ªšBò9á‚Õ+©‡ ívM!”ƒò8Y°–BzÝXJëiËbÖ!T± ÐÒ#³dÂYV[Î^ðÌoœç¤ƒÅF€aÂlÀÈ€ö³ÙdÍÕ·È'ÜõJnô¹=“ÿ§Š¼þuƒ‘†Ãó[ˆÑñª”gÙTšŸÞ§ç‹ÐoukóòÊ\² nê%°0Ü>>b‰0 %½žCíÆ=÷ãvhk<ÛÖÛr8¯íá›%±ºy\ÉîO´høÉ^pN"¡ÄãÅH ;·Ð‚à„G=hÊ]ͳ©ÍÍ·²Ÿn‹énjX ö‹ºMžáKžÙ@Ëd d÷[¬¢KHr0*š°½VK‹pŒˆÞ½œ*!T$Âl…oÙpàFLEÞå[¦}‰”y}ùö¶¢Ùat´÷lçÀ9†ü–nk§7,xÝ )x²}ÿzÉàÇ{/W[¤jómÿÚ-¿=£wåɇ5~»åÌî žêÓ^‘Èô%»ˆ˜ßçyÇ{©]ÜóÈ$¦æžÇÙÆjÅÍ—e޶¬=ªW°‹É“§Çe][ø/{ÀÝKŽ&TTíA.¿"rt%ãSH: pQ•8ì^yÑ,Ö}žM-øº'x€ì“’¦‡°–÷xpµövXLá¼ÉøßX…âÖ©*,¤ršÄX6;E€¶Y2–¿,”÷Øt·ÿ§çÅ`cÏwÛG¡ý; ƺ_öí~è#+ß–5ª«€º4¡ÎÛßþ¶Ÿ,Äê9ÛJ:Aÿ Jc]ãì7¯vúìåÇ ­rª4»Ý||Ü\§°Ì ÔãMž'îŽ{Úe8&Èú5Þ_ß>CÓ[ŒðÙݹƒ§”"jéK§L‚üP-´+º‹BsóÎÐ9”Íû’+ýŽHÞŸ;Õ•ĹÍ~cíÜ!Ý~§y9Ïð@%ªs—LŠþÏÃ9ñVœÙG¨ÈK6 Ï{Ê5Þ*3yXاΉßlÆXè*rÀIŽkuÖÜMƒ<â) fÝž÷ÕõÂL§ŽÉ¦iÒþ¶BnÆéôBgw"Žâ\Ë£ot–çuo˜ËJyÙ¬ìö øm=<‹¥žÍãñÜ#-ž#›:û³´;,® NÀs 6£yVbq¬,¼Ã[XÇäù î-0ŶFñ®ëýP€h:Oiïá<–O8uõ5æ§½Ýc~!ŒùSJ‚ŽÍjöDÄy®dˆrÐ;•ÉÏgõ˲˚ ö øˆå~læ÷ƨí©È{¡{vt4ÏA¢Ž†mÞ8ƒ—çØ é`_‚çµ9/Ðø†¾.O¨|Mڞ˲Á2|‰Kø?ŽöÚ7}"Ô‹\áJ~X5´e{”û0>gƒaßÔÞYPxÞ+èlÏݘ%³üv¶käsT#¤£ö77‘vŽxÚÝØÃ¾–½“WüQC#ÏadÜvNÏ ¢mW̯ÇPWš§O`y’©‹ˆ€J…4v¾€½ÀÌ™ýÌ1q^2¡`ÛPàáÉùZ ~BPŽyì„Gó% T8L¾Þ¢–G¦,*^{<‚½¾¼iö¬º­ŸóÔ¤o;y½qª¹'N@¹YJ@Ú ÔaQ˜€OKçÐéð6•zŽî{TŽûÓ*Ê[ÍýoIþú'‰E3˜!3ÀqÅ`g(ôØ’–'{úЬ¼”6A4òàb'¼¹žÏ9íÔÎÝåõžúÀ$Ûot®Ý«=¼FÅ‚ÔnDxNæˆ\÷;¯5k±ÙV9ˆÙâ»5 Ýû}¼Â[ëm5ÒdÙ!³ñÏ…n.òsÝ#{Y™±Š­,V½,ÁœÝßd»F,.WïlàÛšøz*צH[ýã¼ì§Ô-{Á‚;8ï¹Ùu=škW6a÷ƒ}h³Z¼¶ªB…^¤óä¹{iælœÞ'7î¯7<í3ÁÍC™Š2øi<8·µyãÍijõqN½ïWY,BÕå|l`~‹‡”_‹øÛ= Uâ:ÉI6Ù{ÞÝJF¿KIŸ=KdäÅpçÙš«Þ¸!h@Î5zLF£mSE2<Þw<ßû»ù¨÷s~Ñú*éEÞßEN€ç˜÷m 6~^@¯m/{ÍKñ"8‘+Pô®¼çœ½Ñ§Ý]^ó;KV|®3˜d[V WðÒ³þë`–mj¶9/Û`êAmñâ³ÏìÔÁ…c›¼Ó"ÃÞ±ßâ£E}m>ÎÊBšw³4 ¨[Ííy§vñ.‚.®¦bw:vlb&‚pœ[rCÛ'(½tÏóÚ.ï&é“°Jq¢Ø¾âÝZ Ø àµ@‘h·¡ýR§LQô"6o' 0^G#›Ò€Ï¾¼Eè¶“ƒH"Y]òÍp®Œ×«íì¯úô–_ïªÛÆÞàÄâI Ûvº4ú»\Vn Ô…¹b"ÜHY6ÍlU¶³Èd«^Ÿ=-)¥e£Û_¾ºpÎÛ3[µÆN©ÆŠª7ó­s`}xZn`ûÂáÁdèÒR·}7yÖ6.ÏG PØœE–Îè ±é^¤c¯±¼‰ySë£9Šwò¢óI¾ÛjÝURS|ž²îSW²å.œëNà[K™;5>;ÔŽ0kµˆ•7s÷yOÞ‘œ8ÏÞPã–Ûï;/æZv‘fÏ`44·yzvõ´ë}ÎgÛÏ‘¯MöÚOOM‚#?Vï«G}M“Õ½¨ïõ,O&oÏþ¿ç¦`"'Y*;ëp…ç‚ÂÈnÏ…(0IV[¿~ÚÕ8Ôà@-¨–T5dO½=¸Qµ3ÚØmc f‡á•œ¼î™ %cà i…m³y / s+ܶÕ(÷×G"¼Ú‰ägÑf|?î"–žÕÁó¹©ðô;Jú؇úý.и­ ”ëLaïù»LéÖC‡6 ’Σ7÷œìÕsá;5ß9,h½ý>',M;Ù‹5È5ŸËû *ñ÷+ª.dvúo½îñ¾ÚÐ2ý-ÍcŠÑ_¸Y2‘·*©x7LÝ–Yù¬Í“Kq_âõšJc”|v2t Vo[,Ô^Vãǘ²lO±€ º-áNË%Áë ׄ;b;›pÉs\öëGÏ(Ù0žž’.ØÍ«½º,ØùÞ<Ó€§Å‹K¼ öz0ÖÓÓ4k_.˜5(ܽeXË„„Êwàµ,^mFÄñ ½õÊDÓDhâ`¶ñ¸<ͽy/ Y ?póòâA\DyQw?=[Ë­›©/]Èi-')Û‘ú¨)@"{RuWa¤¬'È*Mê"?mÐf=ÎöwÝ Û¯ì’ÓRÜjFí¿^Œ:•9zSã)È)(?b«¦ƒ·×ÛÄáu³×9½çí}p&zÑ 6ˆØn6ŸÙÚ‘>ÑNò«xÄü&öº†‚½´‡<–®ž{˜<îg„»Cçs“»Ï¹ï4!x­‘º¡Uí0Ë6Â…ë)!Û{ÂüB4/ÞÚN—Yâ­–V½ÏÞG·×ñÓçàê“»˜íÆ~æžl èªç|_ÀÛ€×·à_·L†l©ÑP~¸‡Ížoºáµˆ-0'Èf^ÏBÞèèÆ}j÷ˆŸ‡¦H0ïˆï‹±’^è‡;³à½Õp•K%ìËË vLúAK¾t[y¶ÕÎZÀ\hùJ³ ì–âmŸÄ‹î°–°¯s.Úî+†¨½§BÊn~{éì+}{°?Ýž&èu¦“ñ’=¦IpHo¾ÜAõÚ3LNõ*AäËäVOЧNP§–Ä« ]Þ5µ?øÆ“›kNã'Ïp Q^%¶Ý^ÇË'ïð²–$Ÿë”ªÔƒ¬!(-! §Š9…×´aA¥à^&’ÓczÞy´¼ÿD¡¹bܨÜîã}%Lk;E³½0ñ̾OžüdD@ßðþ³Žs'lA‡ ЫgâýîÙÚÁãM$óC›×}±ËsC ãW‘ÚíÅ¢þ}€/{*KüÈyª,4ú¹JæÑtÍý;ùXNc¥5ë¤ÈckÓ“êçM3³|d½—«yù©t›7 l/¼LÅ–‘s’ùs{à=®bÌ‹å“ ã8Ì“¼jÎÛÖ­-x¨¢°ÖYàn¯ô†Äˆ ­Ñ{”3»lMÄ_ý˜¶ÀG§éæú7·3ÿ¾>¿Ë>j[§¶HŠâŽ€»Í¼®È¹ =<™U!ø×º€ÇÛæ…Øž; `¡†ê”½ ×2´§šÜðƒh=æè}©{¸‰°¡}ô}ò||·ÔYH?"Ë HŸÝÅ@K$²{VW‹ž¯¶ l„Ÿu`¼”-2ß¾sp8K~÷kÚíàÔ^ô ë$EKÏonüó¢i{Ö‡—¾2.qÿÞÁ³Öü.éFý}6ìyU>bøYÉ‹”*)ÂHV²K¶E4Æ^¤`°›„)»ßåŃä›ES/Ö"7ÊÝŽ…!ÙkÌäbeÃ9Èã6q€°’7lXq¿ì[^·ùØIB2w¦¶{³ °’J°e­#®Âe ôó:…Ëm¿·[ý^TôxQîuSJ¼ÏÝœgœýå7j“Y½”ÿ€‚ÇH’fÏé¹±do[Óìå‘PS—>ëËg+üñ—ЭfCÆ8V5ÆsNõã"Y¶tÃvâŽ\†­¡ðUù{ âgwô°I‘ød±/ñåÜYà='Ã*cµ¦^p[ùTýPkÀy Ñ;Á›rÊÑz¨Fþ·Χ3G-œ»qvÀ®½^G³AªÛë$½ÓpŸxþsšä,aã%ÂÝ[·×•z¹iYŸ¿½fÿã¸9x~ÁsNÁ‹¢G¯4=Æj¼6qðª©ÎCTF%¢¢iïz¬\°Ô^VØP7÷og$%]¸‚P÷`–Op//)D4]ÍG²t+É­aT‘ËF”¼‚‡àñ ÷`â,všzEË÷wòËÉËíÚµòîê¹ôÕ{;=í_Þp}gÛŠ`ðz_ 2œÃr ¯‡Eey³Lôº£ž~÷Ͼùô'—:¼ÎǤ¹/Phþ®O{¼²³Ð0ä½MÑëY$Ð×õ1­ª›sÏÿžW–ÞM;ºQ‚”^ï÷CÆ4¥Cñv ¬.b±8ÂD:{ÞÇ› Ü£xŒÖYÔ› ß¿Ÿs™‹ÿɘŽÛÆÍ =Zófë „y½AÉÿ¬P¿U{ëµÙ¦xÉÝÛ!Œé6ñsE/†}BñnªåeÕ[N-ÉÛ zÔÀ¶@ý†Û~èÛ.n@{Ó·%9¾‹q­ ¯™„Ž14A?æ;á£yPp÷Ò $«-­º&ïðúSÀÑòj²Mi\F(Íûô-MYmV'qŽ=>eÔô¼áô·{ÂÓ†Lö†Ñ`›Q™üÿzŸ÷\:é‰òji¶Û¤Úd75{Á9yaΑëNìc`½Ü²œ:#â‹ '@/7®Š\_q2Ê'ƒ¤ÀÎë>ˆ`‹öA媱|û-jÞ^kðxû8÷ôËŠ´÷“Ü`Jì^Á.X—Øý×V´¤¯›âhxì Û˜HÄøò¼«ç¿?»}¶7¸øßò£•˜Hpa{@3Û$0RB‘®sÑ–·ØÝV°cëÞüG\/RÏË`´dÈdÿs$û³ }²¤rTá¯ùyïÿÍ:£_ÿ¢Æ>ï ¾þ<¡õÃí8fa„iCCPICC profilexœ}‘=HÃP…OSE‘J+ˆ8d¨NDE:jŠP!Ô ­:˜¼ôš4$).Ž‚kÁÁŸÅªƒ‹³®®‚ øâææ¤è"%Þ—ZÄxáñ>λçðÞ}€Ð¨0Íêš4Ý6ÓÉ„˜Í­Š=¯"€0—™eÌIR ¾õuO½Tw1žåß÷gõ«y‹‘x–¦M¼A<³iœ÷‰#¬$«ÄçÄã&]ø‘ëŠÇoœ‹. <3bfÒóÄb±ØÁJ³’©OGUM§|!ë±Êy‹³V©±Ö=ù Cy}e™ë´FÄ"– A„‚ʨÀFŒv i:Oøø‡]¿D.…\e0r,  ²ëÿƒß³µ S“^R(t¿8ÎÇ(г 4ëŽó}ì8Í ø \émµÄ?I¯·µèÞ.®Ûš²\îCO†lÊ®¤% Àû}S¸úÖ¼¹µÎqúdhV©àà+RöºÏ»{;çöoOk~?\ ržÉk‹iTXtXML:com.adobe.xmp ¡›xÉPLTEU  #   )53 - 3 ) 3 /'  * 45! ?!++&F!>$;C/*! J6"#%"7.R +X '/7"FFR8%/*.(#N"(+-:'#Q#5*!4*(+-*Z"I(R&:/X&4-=b$ 1.K+2;?.2p#b(D0"a)]+ 841g(j*\/ a0_1R5U8G<m2h4 n3;@MV> IDFAYUA3t;CFI{: p@MGA„<z@KO^HGIp~@&|B{CvFMLiuG'eP‰HDVRWtPdPA‚J#„JjP7‘H…N‰LZYrS+šQ“S!f]_W5q^Nxe \1”^—_ \ge‹b>Œf§\.–a5¨^'¡f¥b/ŠhMxop„m^²g2³kªp±p:«r@¸p1¨y «uJ¢}'¿xžÀv/²y?º|©{W³€§zv”uŒ‚‘žfÀ}E¿ƒSÅ‹³ ¹ˆYÊŠ¿Ò‰2șϗà ϓb×’Z¾™xÉ Ê—gÙ·œŒÐ Sצµ¤©Ð©Û©Ö­ß¡nÚ¨xϬɯ à¹ç¸ܽê°l趆ìÃçÇè»—ß½£×¿¹ùÆöÃQýÈFñÑîÐ$ûÑïÓ>øÉ˜ÿÕöͧêÏÂñ×TðÑ´Ñ×åóÖuýׇܸúÞJ÷Þjúä'úåOùÜÁüç>ÿêþÝ´öÞÎûå€þá¿ûérüè”ûîfûê©ÿæÊþóZþë¼þóŒýôœþø{ýóËýõÂüö×þùµþüåþüðúþÿýÿü û°ZtRNS@æØfbKGDˆH pHYs  ÒÝ~ütIMEål^GüÐIDATxÚí› TSgÚǹl± ŠÅ‚lR™Ê¨K…!V¨u¡ÑÒ²$ ‰m£òYÁV«¢hU@Q‰VÄŽ­eª²4Ã2 %Ò’ De>À¢ÐF–(PTî<ï½7 í2ç;ç{<ê©=—çwÿÏò®×ÀàÿMiØÛŸÑë_¨àdòjPQž¯ãÍÎÉòyÖ°ížý¡¢âüZg66Ï¡ŸËâGÙ=ÛsEÂÂ/„º¯Î³±{ÆŸÄç-t]Îçïž=æ'‹Šò²}T¨ àžâm9ÉÍèÙÜûï>·{å ntâÆÔUczô|Q^ÎÒÏûô˜K ´´s"€ûÈÀÝ%%_o ^˜˜È~á—ß>gíò®tlב²wåËfvcÉ„ .7È×¹?— ä–^é¾$zãAêzߟÎFo¿ö W®\½¡`Šùñ”ÁzoK›)t£_|ù `ïÝçr Î~‚Ef–CIå‘•ÞK6®Ù‘ž¾“þ’î3û‹„¼ì¥ïƒ{°ëLr¤¤¤¦Z1ÝÆÉ>Š–ðò³æ…îÎÍ-8±ó°‘öInɹsç._>²Ò?rëÖ;32ÖGD+ø`¼zΩÍó¶ÿ€¼W»WwèÜ»ÓwGJÒ¡´@×INnnöze€w_Àt]y8÷ĉ¬5i½¸µA€Y§ýó20”TžÙê³>1uÇ£¾ý6ïÛ/¿ÌËËË9•±Ɍ姑÷«W`˜^›w{1lÕÞC;$„:ØLqòt²ÆÀBÞ}üWŸ÷_'¾lC€Ù ••Õ¥G6…ÎXÁN2;û7¯}#tùgnîKƒŒh#,¤Ã±Üq(++3mõtûÉS̹t'KKK?P~s¶ûêÃgÁ}î ¿Ç{Ó&©À\ .WV]zæóMÈ>ÿæ2ìèåϸbF4KKìGÅȶ0!d%Ãdggcîâų1¾3B·d!ï¹Yl#¬:˜ØaÚf½"÷rue%DZi•W* ï_û˜b4èu6 W@Á^Kò‡ÌNÏÌÊ: «çy[û»ZÚXÚϳ ]p"3 ÜŸ³Ãh4³zï_˜›Z+++kcc"2­àòeAuié–H{ÌÄÌ̵[½“¦ ©^Ç)þhX&XV²gÏÔ‰¬¯.„ ÚØÑÜd8Þ};ÃÄ“°rðp°¢ÌAÀ?¹:؇úÚA¡ÑÌlìœÙÙÛ»Ž0Åúø!M=gï<ÞUYŸ{ö` b*Ú˜„ÜÆñ¦¶U&n˜ñ8¥)1@ cãéÓÝíÝÀètº…`ã:ÀÑCÃjDÿ$.îøñãðö™IIn4#DøMK¼‡ã×ïÑ8îjçì¬}gùxúø8ÑIs¢`{ÑXÔ$& £ Í’ðNˆi’ˆ¸“Âä{Ø:èåßÚÇçM¦'“îæÆð$žTGfCy‡· ¥Öãø@_Ž ßAÀÖz¼¡¡)˜±yøÂùLT`Îs£T “… :¶#Ø#Ùáõí(í¦˜¤Jñ¡¡öKÎ: €wëÙÿ³aφ¥ÓL Ãç²ü—Áp£Ba‡ÝèØW¦ à>2òM9GÞé>Á‰®C"®‡µ–SÃ×OVˆÅâqMmNÀŸ}Y,?ÄÀðõcp2ÊtËÐýhæHvTþîß æE0SÚð¡Þ–ëÎ< R©oj¸G\#­»ÞÒÞÒR'F{ùú²€@ø`¹H7Üeñ~Tð}+’Ëæ°`°¥)lƒ©1FDœ0 ¬®k'íI}ß… a1™LÄàçéI¼#jÔ°JËÀÎŽîÂáqÀ˜)u8>ÔԷ¯”Þêîï¾uëßßelI.olïÁÕöDšÍÜÅy—9a‚³HàIw2¹Ñ±\|ÓYŒ@¼?Åf…p8`lVX¼çýöüü°¯«³»»¿¿«ïö™ôú}ð;x¯ýÞƒ'ƒáž$':ZP&k“U ƒ€ÁY  ».°À ž¢€–àØ!<ŽW~;’ «çAw÷ýA|`àÑ£žÖëMMÈÿ=ñ±½‚c;_s!òWõ…`KÖQýþý‚XdôÙ…$èŒK>x0Y› )>v‡~€ÃŠ»ú†X È’lB0‡Ë"30׊€Àyh­ÝÝ ²äd­0ÀL+]€#Š‹›ô8‡žÍr³$Ú ƒˆ›œ€!ðÐõ…X Cr÷­X%@òp= |ß{ñ‹†iz¶_\ÏfùÑH<P"`“VÚ%Hšã”„=€6Á0%vE±½¨Qß&:Ï„T€IC¥€¾XD·Àò µ'†€Dà1`ÖÑ»¯¨QïfžÃ‰¬82ýT9ÈÀßñÏ0 v?=¸í Ò4$@I0ŽÚ=Pô¾#–é°G€P€A*@pxDzèDÀû•—=@Ò^ü'~²Š`DªBX^#Ö €'¤€²(t¼-ÖWÈqS½3Uê4Ð €mQôëmD#È"ú€R@•ƒ$ÀðxÃ*Œœˆs m†çTáµµzÛ”AèÙ\Ou‹ `xò·HDþ{šˆ©.~FOèø^qqý³ü¡- |”E0€÷k0Áµ¶HÛ‘øOÑÉ:ézÆa7û„2ý[Á³lÏfÅ€ÌQü-Ra$îiíûî/ç¡=LŠ€1¦P|Tqg„-uTDÂX¤àŒpþ Ú&+šúæ{Ðóôûx4Ô àzŠ`$_(šQ~ê`€÷k0 ¶Ô¼;5áÓxÔý8)éà°RԀ톱°¡xæ¦Ì\;”…žê2äi—áLª…A ´MôNH*B«¾'•qÉcvŠ@¶g€¡™Y~6T# Pè4"ÿ©‚:Hº/¦&Üû­»ûw%éS`X€‘àôIÙ‡#Ùž=K³ bÄVìýZ^;Ž·6&x&Z‡fçC÷ûu$H×mÅÆØ}ÅvÑ‘ÎC‚^ý:÷ ÍÎ ÇJôø;FŠãx}Ã;`W šš=éß•¬ÝÓu#c×G½ËëFÊAƒ ß„ÜL7X—- ç“¶fÍVs[XiºSæïïï=ñŠ€D8ÑÝ;íË£þ냡§s'-ý(a_Îsw·µ&ÍÖÖÚþB98¯A:âÁcSîY CÛÌÊåÜxd®°Òtð@KNø5oÞ¼—߬è‡"”$¼êèè8ub*”"þð3CG•Ϲϙƒ%ö²ÑnúKÖЯm€š›ëƒG¬Ê•›æ[ùh±‰Öœé;` zàÓr} ¾mסãYÓѼäÅ´q{Ä\=T¢`M߆֪„Qb†Út(N瘃P®¹¹É6 `ü^ ¹ØB{IIë²ëp¼»­MQN@Oßg¶Zþg:®ßF-ÒâÑþ‡Ã¥ûw(>ßù`2höáÜ\Ì$§A0~MˆrO¬K‘ö«Ò]»’ÓíA‘î¾EîZþ`§r¡ŒüGpØNïÔÉF9enÊý'†éÛR@Ü:I:°êúyÝA Àbóc|¨ý-üuˆí¤Ø¸¨º:{ÂÅͥţ@Oà©–e·¡yX烯×%S!€É9~ÿöwÚþQÈÇâÑ;‚çGkFmhÃ(‹=rKÒ ¬Ô@œ2 ÂDOÐ<ðÁ­¨¸$Àb-ŽwÖm°ÐöˆÕ ¯€¡°êÎhgÓá³èÄð¨hÆÄÆ ]âûÐyÑŠô`20l„¶X•`¡í¦cì6µ4®N>¬è]T7*€Á¬Õ%%ÖZ1°"¨D…å´£á÷Áw\Ôìvî"@VIöDo-ÿ3M×oSù8 'ü#Qcþ¨‹ýKJ>Ñ*D+sEùÔÁü`¹$ß5X  âƒu5Á¯hùŸiKÈE@,°ëPì+–½=êÑ<×çë’\íX DÅÛ£)Ò'ÄØ×J®È æOEGX=2áD ÷3­(Â? Ë(šÑ#`Àgo*ùç°<X–R,%¶Ÿ{–PÛ2H´Wó£Öšw§kø‡Jä@ŒÒ?"psé(Õ‹KJ"µ% B»l]±ô)d`oç½[Ê)À*2s@–b¡ñþ ^6èÃ~@•¦„¼Ø°bé#ôúM}?¯‹Š§B0ÍTÜ_Ìeêò&z¨ß–Å+€ à¸ìVÈ1ƒ M%%/J LùÄY¶MŠ6›[[úŠÁ|R< €Ü¹[ƒ JÝ·îÔ¼‰˜˜G@±Í 5È¢]•ß\Z'ýÅ*Aþ%çÒ´$ Â²ÑEk}ÛWaQñĨ¤˜i±bpûüŸÜ5ü;˜GÅ+K€ÃaN¶S(öýrP ç–`š!ñ*–@ê¥ÙaQDO&(Gz Ž·—Ï™®”Ÿˆ‰"ý‡ø1ÌÎŒ¡ˆ„¯T¦!¦©@H˜D:ˆ?•Ö‡-£FD°‹LüéhÛ¶Kì¨9 6çÅS%Àò p¶T(.nhŒášRøÌ‚Ü $0壹±WŠô:Ž÷Ö] ‹UÄo‹s$ÜÀQ¤£æ)ªù'¤!èÄpB‚BþQ•ìí1ðƒ>Ci¨pPPv·å‡Qc|œ&Ú¯ëEšZ[››ÃR°ÑÉiÃäªünð¨S ü/Ÿ;¡!)ŸÃêëaÔÚ¶m€ÎNvR{Šw×m654q²11omKlãqØ,´ã0!R°¸qϘ¸‘GÎ]öP BfÕßî†åp½$Ludqˆ3%óÄ0µèjØ{¾VÖPqŒnhn‹ú—ÍaAƒö½\¾HzglÕøáj 0 ‚±­ 5ëP±j‚˜ø8rÑÁƒŽ°ðǨQ¶‰6š‚±÷©Ÿ>ÙpiŒ7å6. \)‚q(.9mPƒ¤î²X ‹‰5¥ÎN¦qˆ÷ \m„a~( ³+ @Ý ÆhÑ—K¾&9(»=]XZì¥>·Š_OX›VàÁÀýGÄ©už ›äÅpf ƒûIŸäùÖcbÄç%—ƒÑô $tÞFgdÒ|/^”Büz*¶†‚øPwkSK}}'q€*-ÌÈÉNLŒqvös6k&2`Ì *+KH ÆaÆ.ûÛ »¥9^<eK8®‚tÖ75Š+ªê‰ÃêÇ-õuR©D=Áä°\%ÿ FyŸUV&Œ3æsx VIëaê !àq£¢bø1ñ11Q\.?J `nS1ˆ?%b؆b‘„Úº"ð«„©rùÍà±–€R‚ù¥•—_ –‰ÆÆ,f@€¯F‚§ÒbçX0‡‘ÄÒy¡òhp¼ab Ž—-@Ç{ÊÊÊÐEêLûá9¹ü#‘lÏ3$F¿_YyÎŒÊ|a*öãO«NªnkPŽ#©sÊ­áÑR—.æóçJÀšîhøÅ«ò‹{Æ0kÛ§K¾ ÐÜètF¸1Z1§ ãƒ`¥aý³ù¼6›ÒlÐ…åâ],ikojúùÖ þSióÝwkdÓžõÎr"¡’nF§»-6ÿôÔ²Aü‰x4©ÙQðΆV Si ƒ‹áÂÍiyøðaW^Ú,ÿ¸¸ñÒ3_šNåCJ,'ÑéA`cN p?Ôp°glEnêC«jÇEÑ ê?YŒ ³^Ûò]ßÃR"²ç¸6¾5âóÊêÃ&Nô¹ÆÈ?Œ×‹§™ZS¾­Æ‘·¬(€È‘þ2.¹»"ø¡ÿâjóÍ@iÛ4ƒç°­¡gª«·Ðœà¥<ä¿KRþ:º#fÐÓ%U|»yÚp€F† ª@àÅD'þhà…]PÈ/®m¸óöó:‘ºäHuõ9 3:Õ®¾/Ó)üp‹ iòX’³ŠÁB÷Öœa,¿6O*{ã×|º±cÅ7ÕÕÕ6·üžº½ãÝõU¢Š¢´ °D}")d1ˆ»sΓÀÿÍКçL@¥=°4¨´Æ‚N¯­8þ¼˜”b°«^T‘=M ‚‘XÕ…ÃÌ4ÕüÏ< ô¿(i”üÊOWŽî]‚4°FWK‰û¥{Å÷Q‘(Ìû£q)ÿ–WNLɤB¦'ÌÁÌ`xóÑs€ÚN –|~å º“Në”°ª…ˆGKÕùý/¼ëN*ý§Ò ëRêdÆ$Ï«èýE —~ƒOwNB?¦©KoÂÆŒ²zJ„ì Q÷`Ω"X;IŠY´0º6Oò\X×¾=¾~º¯qÏ7hc†íH/å0,ªò^…ð×E©°—_\QÕ ù>]úÇ©ýï£/l5ï „h·b@*Éf0‰ÆãÇò›ŒÎ1ðëçàýO¯•6ÔÿfŸNå/€ &ÑÐ`¦.Jtö£.®z2Ënã?]¸Ù|÷ã=ÒFÉoùéVNƛ߀¥Æ2ÐÕ#-czW~à×dFNÁ÷ÍþùÒ_YÿzÖnjn¾ñã æ)¤xSUŠ3yo—élfVz³Y~qžH*Ýð[º–—³ßû‡æ7*5¾©Ø(DH²‰×wž@;#—Ëï~¼¡¦¡ñíßþÓ¹"á©àÏàÆn-Í“ê !ŒÉ´È«Šfù5Ô ñ_ó`Îæ÷¯Bu°Š U+Á2¦3cÍÆ^X€,­’Êò¯/‹òrfoo¾ÑÜQíA¸äôÃrÄņf_Ú!‡è¿¯/›fð»Y‘0/{ÑHÆŽÒ9¤0Nhgû›¯7´ü}?œ,*+Üð>JÆŽRÑŽ?Í?Ò’o_°°¢ñÒ¿÷ךååå¥˯6+?®Æ¼„~ºpµ¹YÞ{zŽP,–ü#¾-//o¿)WÈWO|Qú=Àôž~÷¤XÜøÅõÙ* ä,]~­W.o¾ çî狲ÅâÚâ?òËaqyaá‹N÷*½×>~+_$®¿ý¹\.•í_´}ûò„à¾xšÁo€ Ê?Y^.ª=‰üw¬¸V ±?ù‚ÁÑÄo`ÿ§í?â?JE§EH¼IEND®B`‚fheroes2-1.0.12+dfsg/files/images/platform/psv/sce_sys/livearea/000077500000000000000000000000001456075706000245045ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/files/images/platform/psv/sce_sys/livearea/contents/000077500000000000000000000000001456075706000263415ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/files/images/platform/psv/sce_sys/livearea/contents/bg.png000066400000000000000000004101401456075706000274370ustar00rootroot00000000000000‰PNG  IHDRHô0®ýÎ÷zTXtRaw profile type exifxÚ­›k’%)r…ÿ³ -çÍrÀ3í@Ë×w"«JÓ=3f2“*»nfÝŒþ8 Ãý¯ÿ|á?øÓFm¡Ô>Úl-ò§Ì2Óâ‡þ¬ïÕbù^¿?eÄüëÝ¿¼^ùõcâ».ù¹,ŽöóÝ~¿ÿë¿¿Ûâ§ú7þëû¯¿˜åç{»Ñ¯e(ñÃùu£ùëF9ýüÂ~Ý`ýL+¶9ú?Naߟï¿>ÿþ½äþÝûÏMþþïÒ‰Þ©¼™SºÙrä5åô3€¬¿òâ‡ü½.ªü\óäÕòø5ò¯âÿaTáïYùóÓß²2gòoIÉíçŠÀ fûóý_¾oõ_?|!þ‡'gÿSyT›ŸÎï¿ïÞ»?³[¥ÒökR¿§øýÄ…›çïc¯ÎßÊÏýûš|@õ:Ù9ÑãæËmZ"-ÏŠ[öì~ßÝœ!–tSç{JN¢ôÞÈ=Íä9rSôe/uruò ™Nz3ï¦?c±ï¹ó{œÛàÁǸ273•BÐËÿÇ׿½Ñ{*y3sŽ/VŒ+©†2§W®"!ö~×Qýüûëï”×LëæÁWÜ?·ØÕ~Õ–ê(‰Î\XùþÓÖϯ"ž]Œe2ÅoÍbO©›ÇA~7)—´IÕš£L%çFrFÒ³ùL·ïÚTÓÏÛ`‰¨¹åNjh&rU6ê§—A ­šk©µ¶Ú먳®–[iµµÖ›ÀoõÜK¯½õÞGŸ}<ʨ£>Fs¬™fël³Ï1ç\‹‡.î¼øô₵vÚy—]wÛ}=÷rÊÇ‹WoÞ}Ÿ¾N:ù€§~Æ™g]»”Ò-·ÞvûwÞõ(µ—_yõµ×ßxó­?Y³ð“ÖúúßgÍ~g-}™Ò…ýOÖøhï¿oa‚“ªœ‘±TŒŒwe€‚NÊYVJ Jrg¢+jb”UÉ9¦Œ‘Ár-Õgr÷?™ûKÞB)ÿ§¼¥ß™ JÝÿGæ‚R÷o2÷ÏyûY;b9|R*¨1Ó~(‚\Ààb“!š§Ðävj~%¯FÔ2¨s¶ÓÞç* é!´i¼ë'×—Ðë¤É˜íNFÖO»õø=oí•§çÕfLkú³fycÝ=—Ù.Ϫ߶ÆÜí&®‡ø1;¢+a¥Ýáo¥ytUÏ K;uÐü¹Sjk×^òê”(Ho}õî‹ Ð¦ÈËu&sO²ã›B|„âR‹fJ»Õ͘CÞ(ŸYÉùñ¤Ùt-]J”û4L݃ÙãÐýµ‚ãŒêýuxòµ8ߨ6E;ß.£ƒ }¦J±qËÜæ"cv”óA—W/õcµ³‹Z5`‡¬uèüòËoÊy‘ø8Ý™‹b³Ä»Hóëƒ4\®ú~DÒ­LÕõº×Œ÷æ^ì—ÂQ¥ÜíÍ}ê^$vvËi@ÇòUÇŒ[uHø(|Û‰$€pft65Ph;ƹß9«Q~—¸ þeïÔìóåe0KªâÐű¶î]|™ú¦*0a!.'}¾È„ê”a5õgîu[®¼°.ñj5‚fkÒ—H™‹ð;û~öaSÔ—ËÙ£‰I å<ÕÚ%‘’Mæ<ÒaçN`„öA‘ðžÌ}¨…ç>çµ'¶J;ð)À7½äÑQ=ÀKp%ü(tc·:‰Þˆ ¶‘F*íÎÈ%ü‘Ê:|²öi o§ÌÎ&”|z’@$e1€Æ@‰B&挄*}ËÙ÷…ª{AÐõ*)–¤–sÛx<À¸7„‘ Í}d··*ki ¨ ™ç§…½HÖÎðˆÚs½î/¹†wˆ*MX•‹s‘¹ún®J¼kˆËÑžÁ.Yãþ‰zžTRjp—¤còšyÐnÀ9½Ö}¢FàS‰ë*–ÜjÙǽ$.Ö°&¤Fåƒ9—R>.X¿DøÜM­;€ïá9¼xçÖµKoOÑd~%МÛÇ]¼á›O§ ߀¨P" !ÃöDŠÍ‚±mTíh»æSAæ .Ã+ެ‰u¥û¸"ÅF/Wïè«Ö¼÷Iº,)z›FžÄñ6ƒ^Z'yæâìì¶wîðaMù6„ö¾nôzú©ð»÷(7¨ Tñ•On(X….NñžËïI\ÄY$¸°©Mô=°ŠÉ¥~f€«_GTÿè~aî€ëÚiˆýCøt‹£–Jo$êñX›Ë[b£‚AÈð\€?WñÆ÷Õ…±·Å#˜ ÈtF>ˆ.Ðí®ó& qAA‚OÕÑ}7ÜQ+Çóãá0S‘jº0KVCN}wDìì(E*(ÝbfÌÓHZºE3•}™1 ¾i×@óºÄ ,Eu'BB~ÐZÿÔ¦˜O¸ªèOiM>š¤Çp«‹ñLIç\…=]&ù¾ÀÚœÆPK"“8ò”BJG%ƒJÉiaH‘q­°Ú5ö%ns:eõûˆR[™t|;˜©¼öBÇ3 ¶>ñ´Ì@v”p?Ïš½L¥‚sÎp'¥xñ>ânPaTÏ@ý¸tÖl$šLä\3H(š<À÷ê¿C'€ˆ²Ï蜑7*ª¿˜ñŒh'bJ ƒQßk⃡Òv 2ê(!&nV²G÷|j/ #†Ç+!j’fsåÚ¦J–Ñ ”¡•“šæHIƒG›ÊAçœ-¤R ?TIëÌOÖ7â…‘Á9ï¨ølà=n«zèBtv™K¤ak÷šÑ´ ™ò'Eñ@Ð+ÐjÐZDÉ’Ú'«ŸP¹{÷œh±CyÊr¦Ô ÙˆN~“î@€x¾ÇQëîˆlj/IFK­bðÊ(% ‹:Õ¿Å´¿ÔŽ…Šs{) ÑÙÐ^Û(àÛ Š«õ¯ÅÖ‹mŸ‚ö„4̇µMFQ"x ¢µ ¶„a~©zÍ]ƒó™£³æ$E©@²~½Ü–åa¯põA§6oG¨[¨¥Gª’\ Tw™QGó ›ÈI€ÞLônµôÔO¦© ‘ªW:²BpÝ‚º,Ùˆar¿‘Ò…j(ìdl=•& {O´‘›7¿L»dš¨ˆU…?4rgøh ® ð‚/ KâË 1ƒ+¥‡´¤¢t3-Þesœ‚ª(x~·`ý^(fhŠ3ç VlÀóÇ==BA6E­'…³@Á^aHðÃHÄ9OùSx0 ¶…N"˜r`h0È ìs8ÏDQÓ¬9]RC†`Í@!Ò™;Bê°ÿ8¤‹ ßµ):~¹Ä‹=™iFªÌÁ~¥Ì $ƒlcüVÊK 죤Uí'Èß­@4(.Löhâ'H¡z؈‹Ê¨ðC•lz‹Jq9–nç-’ŠF„䙇+DÊ«ŽÀ¶z·BGhœ,-n€”‰Y‘ô¬û’º„‡VœHÕ‰Q…÷‘G/é} r㣊r^3=ûìf÷¨–ša·ÑSܘ!¤ûVDv¦u;#¥¦(ÝÀL3â÷D&é”_»{<^âš¹ÅæŽÑÓû)"nHUü.ž|ÌJHÃU AMTÖX¿p3‰HV àNùƒÄ@!ÅqSÜ]Ts+@¸è‚@±°ø~^Ï’†¨È/0íÀÙ¶b²cdºwR‚¸H ”Ï3FŽæ½áPluÞ‘p gSóDÛÿü5‰:êt&Rƾ¬;–O 8æãlÎYR“»ƒmÛêÓ«æ ¸èqwÔE»ÁÒ8àç¨q"ÏÑÃ܈ÊÛèz¹Òž/d êxIc.‡½§ŒY†ß¨·´Ñ‘ÔÀFwAœÂtu¤0ßeĘ.ËCÅ|\’ë—Ð4DÚ¥¾.Ê #BX¯7hy¡vjÒRÕš> ï@|†#„‚Ô(/QÔõ˜T¥Ê¾~~`{ˆ2„cÊ ¡e^`Ó†—åþÆEãuÈ–Ç^‚>yꈇ^T=ÞyQ $П¤¨5®è}°¼ €è-üÂÜÒ@•ÜóDª)óØŽ`G«l|;¡Õ"7Bžá- P®?ºÇУ°þëù™?Å¥¾c¨Üà  Òa¾v‚¤‚ƒ À'å ¾·N".îêÇþÄÃÃîm(@yWß=¡µ/E*øéWt`Ǽ¼hãÝ=Å-Ft‹ùC]!]éxf…ûƧF¹ ܦ·_€ƒ/£‡zœA‚É’%°aÇè›`YãaÉT g0T@]UŠX4ÎD‡ã¸ oŸ(†ä°ð< ‡ÎÏR³$÷êY‡’$þÄW4rUÓk)dJFrñ‚NèÍ@;Ì $'ë7GFñ7-a´¼°x {Ž71ˆä׊¨ÆŸ9²¦ö@!ƒÞq·qäᧃíX£®…µþ>—´.QDÒÁe`>…ßÁTüºþ£±.)hIÈ©tEŒµ»{ðrÈ&ØÞ˜{ZT™`R4"C‘PáQFì+¹Êgº¨ºÑ àâÛZÐZÀ¡ü-{ä¢ÑFðÒ“7î±Î@þã˜^f­#¡üM*ÀÊÆjIl¢µù wÕåbظ)/Z 5ä,Ú,á5ñs¡~K´,`-+–妒1Dß&¨Øh ….l¸ öÒ„Œ‰qS œ`›B×ÒãÖ*F’´èi”re¦2’½tDn‹!C£I«}æZŒ‘{ÙO è§nÌZ®áö%µõ¸àá—º2É ‘(dúu+â^Í1 ð…ü â>à°­h~0Wú'uÝ$Úidh»¼‹z¦©ÕŸò\ÔóÄSj™a"#žoÏ„x¬ …z—²²x^¢á!¸EÃÔƒ¬ûâ2,® ñ/e¹‹) +H¿‰B:öŒ´ž˜-;žNþ2î–Oõ, ILž›¨·’?þ[CËŹÄ–Ö:zG#róÛP¶˜ý#½\ጣ•ЇjÆ k)ê Û Îz™9€†5ž)à´}k˜2ßÀ øOܧÓç¢^„ØDO—…bCðÃ{ŽE¹ ›°3í…ËMF«Ê-6ÔØá/R‘&ԺíÆM´$y—$ Dr¾uâ®`’…½E¬7hqR—B‹Ðe1L Ç1bWà ªÂóÀ1 ‘V~¦?´þƒ†‚Ñj@6®!Ã[Œû _iÎR´ˆIÙao u9 u„ ©Æ.!ëmhùŸ²cÆoåpŠdöº*’ca‚(F¨öá°vªx™ÈãÚ™µ¦1 –B32ÛW’"Ü„šQÍ©ô(Š7“Â#Iy§R´¤9D©SË…§`XkA`CùŒ’jÚBÔ¦Ì"«r €›Ö¡2µjsœ…,Úñ$©ÍÐ'¡ƒqÒê*“GÀ磘5xxP¤Jä—->ðWU\Þ£Ušµªo')LKUÌPϳbØà-ö2ž‰Ü„=¸×Jp)5¦€@‡•¥Q¤ê"ƒp Ì øwF 4SÕj U*ò¯›òHò²Z‹¤swÂíkEí*Ÿ;èCgB(¢wíË—PœâhQ­zé0ÿ nÅCyku‚•oqšK£ÀÍZÁJë¡1<­`v”è’Ü*SG„ˆ¤cÒˆÒI6‘?3ÉÛû„ú(_ðTLºvƒ×äàt`ÅÈë%u­#]番@ÙPhN±âU\™OwÁì1ÂT‘5HdÈ„Æ2Våqd ¹Æ${*ù€ 5*]0‹”—ÇCŒY£æâµ„¿›0ä- M"-™sÄõ^ô:8|QHÜx~ÈŒƒ^±´'à® + ÉÁþc'Ð3sËÁߢ¶©^(¸ÏFYÐ(¦M‹}ýØ€`ñа¡p´†ŸZƒ)‡–s'Ój=¶›>Éø„üŠ&cPIdh BG¢¾¾æÕºîµHÔ?ÐõÕêà(í{³Rfó0v)ˆŒ—=~‚Ô~4†jr,*Z¯‚`¿ï²Õ};yð®´4òî µM¿H¿—ÅfY{Uº¡ÈE¥r±Y€‹–ß7¾Íp"GÁ£„¶!$švkE€ò½Z³§À.Ìë\Ýåe°ÌÈåXê€/¿ÎÌêzþÖ³žºÇÕ™=\äîó!ì‡$½¬¢Ï’j9â3›ì…iÖ’­‘è ¦­í€ö–'ÜÅ È;­2à‚°2Gz5ùÆ¡ Í„mKšr"a;zi„^ŸÚÝ$2]ÂýHŒ¯¼(¼)üº0k÷N“"BEÎ[åN-"}Ñ>Åá ­ '-8p‚¤‘åÌ[V´¯${ͷðߚ÷*n7íoK¦É”«ÿ¾õvâ’´¬Cy^£ §,ÂN“ê@û“éshÁÄÜðŠï§ s %*ê®ÊÉÆ\i­£\,ÍuÈšxhCÓÆEALæŽõW.´K‰Â‚ƒÒF?4¸=ì= Å¢×ðTÛã¡ ùt/Ih1{I.âiq}Ï颅ÏñÛéߦËÞp#Nì~V@’ªTÉS‘~A."C ã»E>ÜU:»P¨7ÈàCQGS!@EHß­€Q€X??IK©@96Rv/ ôA(3¬¸ˆHÞm—ÒT%NŒVä;ÂÂÖcâ Íkéø-.O-ÑžRXMÀ9X¸÷é ’&,£’ËÎt  48*LZChsÒƒáDq]:ñ4¶@BÐpÉ'ìÒÓ ¶»´¹\µ% «Â„xWª¬œÏóçÏæä’ íÆôÒ¢¹E€AkþU'¡ º‘bæehw°GaÎÃwí/­p¨'´ä†7*'¤wÚ×Y?`¦äqI wFAÜ ”!¸WÕ‚ì‰áƒ'ÓÎ ¢‡š«þ'M昒¸&²Ct(bmΠ-R¦sÊÛ‰‡CÚÀ «:ûPbò¨Å+é"Y]¥ü°Ù¡ õæ.¾´ïÞ¸‰3Aåk%ñdÔÌÃ30Œ€H@ÞaÀ$®YkþyPl„s kÒÌjs—\Øè2è¬@X0O‡’9‚*˜^¤›¡©>A\ûRàöCÝ,-åJ1nªZI¨˜|í>åŽAYéœ LGÂ$ Œ¨Ö>¿¡~jÔÂú^IÚ”D–¦¦¯UZê†eä´¸Õd'7B‹jÌÚþÕb}¥…øx´ž(öÉ€o?uÙÑIgjsjCC®Åº¤­­UWW:µÀl:1¢½›Ñ–à)ÊVÅ»"u ƒ.P• 5[†äÁÇnÐr'5 °²KD·–>¨>ªAëV]{‹Ú°­S€ôÐ2GújáA»n3Zp xfT ¬”r¹Ö¦‘[Ô¨;À±rt ‘Iça(«Œšuz›Ï¬[ †Á½<4 }¢ >KDÙEì¶ùá†&³…ùש¼º” hìK¾4ÍÕ¾ãhz‚Z½_ØðÎFn‘ u|‹œ«ú“œîŒBž-k™*Š?X,a÷M÷O|ƒ²€k»Î6vc* ¥k ó  “?ØéH\Õ²BйHC«¹ZY{Z³_¸«dm%Á§ÈUm1|µ—ö@°¬ÄÔš “¾¨xÿ8˜ Àh[â*ï Ul:³ñiþõ~ö®—A©—rªéÛ{6„KƒÓ2Ô+(8g‰Úü4 0h+ ¸m0 X#µ‰ ¯=¥®ÈY¥×p:•ßàBðB["kÑÄòÆsf,Y¢z§z⣹¨óñMùÔ†ð[Aç¹êßù‹‚Ý ²hÄ&!£S"v–ΫxKÔ ì8AKÊù;ºBx‚¹Ý¡àŒµäXýùD+qFhÈÎçƒlý,è76ÌïÛoÅGmÀ#y`ûž¡$åŠðŒB¸«:ªã¥#+€!yä¾PK´BKbáðç Ù¢sz°9]QRàË‚N ÄM2äK‡’ož¢IÞÓjÕÄt RŽ"¬¢ƒ¹é[<ßêNàý¿%®£0[cü.R!D^úFº%@ˆrIP_»pÃB, ‚~tEÕGÄèvdÙÓÆkÒQ, õ|G=©îl“*•&¡ pG,œçÏb¶p0мë˜aÓK‚»P9T¹“¤ÃÊ Ñ#éÔ32\®Œ›Md êvdæ®´ˆNÞÖÒ»ÎWc-`pàÇA šA[¬Y[ÇKç?ÑÆ4{Æ ´]KhV"‰\ :ü12ȨÓ0´ $ÛôÿU 5Jú° TlM Ôt Çq?í×¾³ ô snðšÎÿAéh^D™ív~káдN ÞsµuÉD]ÿ?…iǃ0!;´¢­qGÕ‚0s~knÚ˜¥ÄàÎþõ5MÃië ÆòÀØbk@ܬ3¶/»þLþˆ!£¯uгH’{™DìdˆQ'$Çw‘‡ÛGç̽< )+uùˆ]”?®®h»‚xÚ….x_ý;5]˜Žeݦêࢳ‹tÿD«æšŽ C_Šîõç~úÎB'êÜK¨âù7þ0WÆÚ`˜q`ÒãÞ¾Çæ³<9ÿ| D·RŸSV=˸ëm #ð3ßú}¦ýÜ/Äú‰#þ‚È?ßxcøo\b‚ ·R*…iCCPICC profilexœ}‘=HÃP…OSE‘;ˆ8d¨N¤Šˆ“V¡B­ÐªƒÉKÿ IC’ââ(¸üY¬:¸8ëêà*‚? nnNŠ.Râ}I¡EŒïã¼{ïÝ Ó¬®q@Óm3LˆÙܪØóŠ cq™YÆœ$¥à[_÷ÔKuãYþ}VXÍ[ ˆÄ³Ì0mâ â©MÛà¼Oa%Y%>'3é‚Ä\W<~ã\tYà™3“ž'Ž‹ÅV:˜•Lx’8ªj:å YUÎ[œµJµîÉ_Êë+Ë\§5Œ$± "ÔPF6b´ë¤XHÓyÂÇ?äú%r)ä*ƒ‘cUh]?øüž­U˜ˆ{I¡Ðýâ8#@Ï.Ь;Î÷±ã4O€à3p¥·ýÕ0ýIz½­E€þmà⺭){Àå0ødȦìJAZB¡¼ŸÑ7å€[ oÍ›[ë§@†f•ºÑ"e¯û¼»·snÿö´æ÷¤ur»œûf‹iTXtXML:com.adobe.xmp ƒDÀŽýPLTE        " $!& # "!!('#*&*/ ., )! . '!2)!. *#4! 0"/"1# 6"4"-%8$2%8$5&4&1(4':& <';'A'8)4+>)=)B(D)>*D* 8.B,<-A, G+J*A,G,C.I- H-E/K/E0O.K/ O. O.L0C3N2I3N2S1R1U2R4Q4 L6R5 W4 \3V4[3 X5T7[7[8Z7_6\8b8a8 ^:U<a<e;^= e;d;j:g=l<i> j?n= j?aBlAlAr@q@sAoCoCrEiGwDvDwE yFrH uH}E|H}IG †F xK„JM†KMƒN‹K„O~RŠO O †S ‰ST™P–SˆZ˜U ŠYT—Y–Y ¢XŸ[Ž` ›]ªX ¥Z ¤^ža ®\ª^¢c©c¯b ´a »a l šo ±i«k½e©k ¶hÄh¹m Ál ÊmÁqµu½sÑsËt´|¯~Ïw¿| Éz¬ƒ€×}Ѐ ʇ×…È¿’ÓŽÇ’݌ؑߔќØ›áœÏ¥Ù¢ä¥Þ¨Ú¯í­ë°ã¶éµèºì½ò¼ñÄôÇúÉùÎþÓýØüÝøàùáöåøç0ôê1ñî3óðAü¶EbKGDˆH pHYs  šœtIMEå ;²’0 IDATxÚì½ipÜwzçÇ7ƒúÝvu‹dãh·hÀz@B0„(ˆh.DR;ÿÙÔÑlùùi\ÄíüÿIÜ 6ùžò?? ’®¾ü&èEüŸúÝAü)!°ñ›ÁÝÕ:ü|ùHî¯EÈÙ¾…G˜ÚÊß~ÐÞ² ·„Ž­†è?–;BG ×¹½ŸÆ2ŒøÖÿA‚k¯X6§k“Žþ„Å"\Á[z âÏ¡~¢ýƒ$œîâzŽÁ/Â-åhÍ^A:,ø¾´èXŸSh8oƒ¦?ÃQ¶ß@À裪 mÛ¶Íz¸mÛú¿>r¤.¾^8}xvoÒé“ÓÛÛU”¶’$ú9Šå®^>}k>½Ùî Eú1‚-¶¬tGh‹pô6ípJ neÜ~À•îÉ»=Ï ÒìS ç2ÙS ,ªÐ˜ég²fü²CÛ9¿Ãþ`Ÿil$áhŸõcÜÁ×^xQ×}"DIùÀÛJàâSmÚ}À¾Kå-©#Rw̦ËE²òmZQ §í.Ii–ÈxIžZ1[´V´©b²þt Š/_Š"H„6)é&2I Eõºõëò~‡#7ˆßæÛ1‘ß!†Ê×z_‡V#Rü»m[Áy±y¾þ4DSêÜðÏ¢aÚC—`!HMIl´®€o»Õ L*33Ågý·ÆJ¡ôBLRwqKA ´ú#¹¡š«‡V‰hUÐhUW£)"‰MRG4:óðOVÞ®/*æãy©õY$àŽ Zjè0Îr⌘gY) µàèspüµ Fž\‚“0çàÁC­Î´ub0ÑŨLÒne’ yoë.ÀíÖ !ò£´<-a!–Ä$m†hÒ‘i€€vôD7Íl°*ÚPé¬^~»ŽkÄUÂkOS”Ã'ßX‡}{^TKg0ö+ Ò£ ØÑ‘“ÏQÚ: .¬´oÊ/y›ý Αbçsú¬ÉRyù‚ò"T`1¢/½CÓÍ9}ŽÄÎáÃ‡çæ²>ŽzG³yè¤>ï„I[ R@âûA(ÿ¶L+Ñ>*š¤òf€Ô‘éÙA6¬ÏQbÜÝJZŒ9–+A•Ai»€$ñ‡ÉÈK¹d+ì{;lH9Ñ'â 'õ‰Ú·IòYÖÏ‘1˜Ú@Ù`&HvJVcä¯Íãó”GTQ×ßž—ˆ¢é鹿‘£›r7çàRVWâK£ê¬¨ Ékq6’òìаjS„¤ÌEùN8ÓÎ1˜¹(]zqL’ÉÿÜZNzÅšo'Nèò ’Òõúž$õ~wY}»c‘•‚‰Â&ñ½c‹Ì¥ K`j¼Àö¯,]GB!Ï×vF»>ƒÒF«PÂO)ÎÑ6›£5NÒÈ8£¹ÃÁñ b Q:8¥LÒ`k6f‘4H¯D …¹¹Ã‡[±n4ºŒó§@ª#HÛ×RFéŒ)ë@À 1z!]š—dX:L`¦ K²J¨ÜI¢¨Ã© À')8sÉF ¤\æÿÙZ\»€ñÍÀH ~GBB9$òé6†‘€TW îà€Ññã'ݳ¸¶#ÿŒi–Ž¿D¾þî·#ü9zûù†0}øpô†°“Ê|šf³ô¨&$mTlÐB-ÇÕF•‚dg±ˆÒ`åÂÔL—“í’´2<¥8~½%öí|'b—PÈrÚsë°-ñ¡ÞY( %õÞÖEÄ™%ƒÊ{Lóò6Ž’Næv¤ç‘’9Z—Ž º‘¾4u¬`tš™X†³;ËÑÿ& /ßÃÓ‡¦ÆG[äo$¾!œf»zìEÑ‹/îqÓ^#É@ÙLÕ3"¨ :‰o˜*Ñ»\-0Wz„#‚¬TR)ׯeÑ``—÷Y*½Ú:Ž,»ä~û1 FÛlŒÖ¬‡yðìê#ãêê;Jæè4e¤5>ÎG”+“4Rß. ¤ÿÒ¼|YßЊNŸ>ÙæItOÑ2MIhX!HÚŒf‡¥‡™ÿx *w“K÷âÈhR9†þIˆ¡¢6©"%tît¥S$OƒSýãÛINð<•W§8je4Hq€øïr‰Éÿn9Šˆ Û’8ú\Œ£µ^}Ê1À#:8GæH]ÿ …sÙç²9©fé4¡4§næ$åó[Äܺ(Ô;„Ö¨}Ÿô´ñFÉb–œ†Xï…Á µ•ùŠTŠÙåe"AÅWšHjyÿø~ÉòK«+%ˆŠ–J-~w7[$í¹z—¯/ùNôÿäç¿8‚Óé>t$»v M&½•Úÿ±y 9u¬) QÆHsÔ^z¶#F€Thtdê š£ÅÓKËK(ë>Ì’thjÿh½Vý{+(‚ÿ¶k&§Å?=MVÓ";Ép²jeŽà¤>ã‘ÃÓÓ,šlŽEò­¦Sà ¡gÇ^×Z`fH²«.l˜^ŒTu®vÔŒ”´Þát'tXÕ¦I¤‚kn7„äÉ*%…KIž^˜›U%"*NHÆYm˲<î õÉ© <»©iºú–£• bI]¥p!NOoWé)å#_~ÓÝöìŸ:¨ Ò?Àdù\›ÇAkiѼyšµôO$LˆõvÛχ¥”i˜„¼]š‰Y"‰SÉïÖ1Qµ ëñ½ 9Ó¤m$V[‹uÍuàõ¹ÞQ€ËAtÄ3H&wÓ½üMµ‘íØ-P áêõ. ©%q_ýÓ•ã‡æ"VíÓs†éˆàHÆ›³0ÚÖ6F6L¹¤<;p†àþmQtEk×®DÎ5:W²°¤>¸wÇœb’ãvNkø†@?ÈRÔM´™ÙhA ‡Fõ$‰&Óã/€hRÌo#«L &É£Ž¿[¬QV‘ðKû­ª@]{«-Ó‹{(%†É DAR`©Z6„oG]9W_CŠòäԋŦbíà*nF¼–²]Ò2|€“‡/Bz €…êeCz´ªÌ”RR‰­#!‰@šP7òã'O/.Ã¥'ü\»vcsmuuõjʹ?ˆ“túøÑƒS{¤­ Èizú(Ö–µÉ•2©èã‘MšÂÌr»)ƒ¢],`÷X;µV­U Ž»%ƒ/éJÀ—\)„M/QP²lƒØò{‡1øe|µ+ONq“·Œ‘©S¤”xÐñÑqS’fGÆcBa‚F>„Ð…d·ÂÄЪÃë%€dÔîp‹4¸GtTq´D­(Š®25«Ö±ÐYÍ>Œ’¤®Ä“*HR õõó[ÄQ”P´S–uÇ%æ(Áß\#Ràá IÊ·[HYõ·V·oà„lC$ŠŽÜÚÛxึI£ÒõØÇCÏjý'õÛ÷;"4™$,ñ.ò¤ÑYGq”7Ž]Š®¢ É-QšØÀŽ‚¼xìÓyl€(põ€'Ÿ"ïÏK±HŽÜ`ädZpÄ‚‚9¹èÉ—zúUh¡näÌ#ÅÌêõ œU2SLÒÒÉ£s$ÕH¹-:ðs(‹„–uqQ0ºr¥¥ÿ¹Òš(ôîÀ¹›S ½ˆù°`ÝYݾowRóÅœ§öD("&Œ¦¢g<(°K/EDrê«zÑñXn¨P[Uηmƒ„…r* ˜|-º a~ ®èŽ?rZW\lmÌmt’útð$Õ²ZÀ ɹ#„ •(2bk†„|;^B¸¤ÛÓc }. ¤,޲0R?j±2Pß3>ÛP%DõíÜ>µ”eŽ"×bÒH“ ¤E¼üV¯2GŠ‘»ðÌ¡¹Ûî¹é¤@ZT íßSÈ©m¸ò) )Ëzøä2{vW¯¶/˜\ˆVÝHJ`B›„&é4Š{v÷eÔ*áå·’túNMœ†¾Cع;-MŒðy¿)nå`éE-7ôVÊå˜E ìIÀ¦>!Z]©TDŽ’.7?¥Ú÷¸T. [ƒd ˜äW’¢­ ƒRßäi)*òø+‡Œ7ŠánÍP´ÛÜ)¡M¶MŽÂØ•H ͪÐâòå+W´c§(º»¡ƒ¶J›¤¥ã¨¯¤¶1ÊçË좞\Z3GäéE5 4OW¼²ú)¦ F( RÛíT±Ë,3y;ߣ‰eJ±K#ã"ˆ’Y²\;!íZ¤Ž”™Ùž—gá»Ô™Oü ('Êñ?Àyž]":¤¤,’õ8䜢;‘çL8ä™J;ߨw’¹%Ï¡½#¥hUz—×ÃQç¨AÚ?Û³PÚO±Òþ—Ø¿Óúøv”‘-Ò›>”%vP^—a|*!%#AÆB‹ R>#é¨è¨v˜"Æ'‚ÂÿÎ'Ò¸ ÉçÜAÊÓ¢7˜zR@Jã¨Ms¤#H³ÍãK!)È­ƒsKÛ·-HÔ[IÇúý7d“@¹S—âååãGH}6Yþ.Wvï™õûœ%š|˜%*f„N?aZœ»s%>4I ¤uŒlk³¯Ê€”(§îPszº9“d¦)׿;(‰Ú„ºÖ—ƵnÍÞe«Åb¡ù|TÅ ©”:ó\œ­P²£| j¯‡@âÿò‚k‡e“(-À…v¡.âˆ-†M!G\GÄžO%Dñ0)’5Ùn-±9Š*ù¥òÀà(€´|¯>Šî"÷Öx\¶îÞ¥0éêUÒIÎ:ä„V Ö$ëƒÔ>E 8i’ #ûâ ˆ ™ÙáöªC, ÒK2 »fs®)Ž¢q’‰–¢é$"f1C98úvÐ$»½ÐÝ¡…>¸þ"-H×±GGq(ýËø1ÕÎp”+ÑïΗ⠓­!Y%þš”I ¹FÕ(z~h³çÅz‘ã ÙôÚöR9J°F”~ Â†¥Ë˜C"ÇNatoC¼›Ò €4 íh£µ¤NuC@N‹Aj_ÄO'j•@’ÎCkÕQ´H•žRç†9JÑ*¤G8DaÆÎ5›0*иxFÀK+r Ã¡ìŽ îúú*ríZßBuC ~»å2•4ø¡‰ôÉB„Z…–žP‚ ¤B’h'‚>ç¤ÞÎÈ f·Ù“l§0ÅŽµE…q/6ö,©úÛî“m“#2G±¼²éPóÌÒ Ô4ÀÕ§|:eSîÝ'$î¯ç(ãtç6ôᇴ¯,´-Ò¦[#²¬Cê†0w|c½«Úµé3-Ôêu”ï0« wG§§FöÔ+YÅ·°F¤Áú(Ìœ–¹b2"lŽ'ïÙ“-§ê”ÒxÌ(ÙýJ4Ûs²k)“T.c©*Dx™k_Š`ò¹H_ÁK+ÕZ§TœG8ä¨ô#ð-1¡ ©zMP(©YQ µÞ|EÍ$€¤C¤„N_›£˜9JÀHƒtA¢ ¤Âè!ýØÅå¡96DÚ(s‡…B+PÚ @îÙ*Ž:Ó@Âp¯eæ+K–¤‰=õ¾ 6È‘GªiÜiÚ™µG MkR»ý(;Hƒ…UÜ€ ÛÉÓ ¡:ͽA!…'„ š.*åú¼è[V¬F†¢P„„ yº^Az‘X ä:Vx¸&œ”:ŸåòxåjGHŽa²i yiæÈ©¬Ñ ]¥ ;›¢‡÷¢=Ì8¶wg|;¬B¶„"uHÔ´H¢‰pt÷N»Yäd“„ò[¤%i?€”#m"ÒžÑýã¯"HÓl‘Ž(Œ´A:ÔŽNù¤ýã£#V#:« • Ú˜V ᵪ Ež"¤œŽå¹•.ä.UrÀ|ØX™{€P!É Ú¿£Ir±¡Ïå ê³5 Eeçï‰2GÌ»üä‡ÎÔwYH¦³Êåȳ8ÊçÍ‘€4Å ã î?Œ;ḻÿ&ѹ¢ˆ$ÒÊdd'ëÃÕRö)dŸV®Ý~ҥØ£;q¥^ T{~å—®n·xtzv|O½–¬â›&ÃuÍÂX§QÖ¾¤#ñáÊñåqžp¹Õx$Üû )‹ÔZ‘W&Õe«kŠK±¥Ô€0?Љ|++ÔÚ(”Œ0«J:ƒèÙ!»”ž¸z„ph ±£l©Ú¹žÕqáÈÓ¥GÁ"ÁåwóÖm+4zg(rRä{÷nߺIIÙ+·RaýåÑ"í?täøòå«W¤›(?Æ´ú»k2L¤ÞýC4I'¤Ñ6‡#‰&Æ¢d7×<Ò|b$ݾ}—ª®]Ö ípO©ÝÓ*¶âXïÈåÚ][]åzAeî8c|‰æ[©f)®7P* ]»æÁDÌt³ QÏ!áÊo~w4~ËNÖÜHúýø|õ’eãþW6/¡n‰%7OIRÄaT9;Õ"Å„SD_Qâ£tsÄ3«Ã’ŠÑ¤ÛR2JùUä$£d´øfsv²^«îÜŠ¤) ÔG²H’Sw¡_Ú¥L™(!HÒ2ŸR;œ¸4y,Y˜jÐŽD <;åÚ> /ã ¼ƒÉíFq áŽpåÚíÔ®]NjBŇ =É®†ZŸãz6̳-ðEêæRƒP·2Pl®]•ƒËìrœCÒV&4]ºDˆ¾ˆn;çi]l#ñ[ £bCJBÖOlI7)´8 38*Èd˜(H·n'[$äåqÒIµI •¶£(H«¤Hõ¾6%‰6)®€›ÙqÖBQ¦JN _,FB+¤:räHG6H‰µàû¹}D W ¤^–¿}=‘Q4äPJH: eÌùK”Ù’B¶I¡¯õ2Ü‚°U?RÉ!1G ]ð$“Pëì¡8w¡o²G¡Ô­r¥é'E›CZ€å(LçÈuëÌ€%iùòµë(~ß¹ ‚BážÇë<ˆÒ=ÊÉ^_U®ÝÜìÄ^ ¤Òær$ =yn…“H‘úŒÛ¤H}àÝ»ÙnÞªž=±xœ@ª–ÖÉQ`¬RŠRJs_6L±AÂ5O¼*QV)6›¼Í)»Û8¼H1ªv0´®X27a¨kBí%£[¥¥9®:)ò4&a6HäÚ:‡¤ï.¨y“JÒH*(Èhúž.ÒZªÀ3w„h§bk,޼v9*XƒÊ¤Y´HÒ=àhC‘™›tK@:£,’i“)²@Z´Aº}‡õÇ{ @Å Ö煉tíê ÒH}H@ ÛÆ(¨¨UÖVÔU©ÕYþ>tHÔÄíG`)†I’¶K Ù¤H-¸:z€@¡Y«’YÍ>¾o¬ž©` M]¨{é|Ý[Çúš·¶Hº§CHŸ’H–³&MÈ Yÿö¹0)áAËPyÑnóVI ’ÅQ˜ÂQ²9"&-îÞ¾}Ÿ@z¼¡Ã ÝMió9Êé^[ Ý\+Hk°F<²­uõCÀ ¸ 9, 5-‹t0KnˆÎCá¬TlÇ• y¬íK>†Ê¬CÑì$Ê Q_º_y"–/Ó©ºA†ba—ª—-68­¦Ÿ/𹄛Æ×±Kéù¦9 0. ðEü6% Ü~á%™›TÇŽ;cZsTpçfFAºƒ’÷F9"’nßæ”,ƒÔ2!»Ó3, ‰ø‘ÔÖ½¤ö» 8“(à\Û@®ÝPu;Tþ’™úÈB¹«×²HèÙ)Šh} IwÆ µŠ,ƒd:)¬„l^ú·¥ÖÚ3MSÒž`•bøŒRè鞆Ð3õoÚœ…nE«„¬Ö4ɳ»GH$(†:„cR¾°ÏZ£'¢||nƒ3 v[KÅÎå(—ÎQäd FÂ,ƒôøñÆIRéŽ c¤þ­‰b=Òîò½©Kc)­1'éTŽ[:~Aªì\#GA¦Èà¨ß°dž-Ò4i °~ð0o“ÖYY«Ï/6ÃÁ%i?HZk@ú$ß—õTec8 ¹ìÆ7ÃFLöU¦"6x¦œU RMRT®ê³dç »Ü¹nuÆú"&ˆBâëv«^P? IDATîœÔ@­Å"َݺ9*Uk ¤9ÒªéÁæp„ QÝ*‚¤\»þÙ4ìhó´jßÊ"Ýs‹-Ò ³”6V @Z$†+¥Xón>é‘¶Ga£ò€ ä?Þé ß•8¡©Çuíš!)çN…IGÀQ­±I‡²Gtí'¿Žc$.ÿÕ.šÐÂí¨Út:ÅÄÝÔá@»[È‘.îû)m© E´ïÐ|C:)¤m ÷!ùº¤!ä–äutí5϶H®AJåÈ™Ðåh\€³sg¤;·¤Ç[Òp&H;6€»vMtíV$jIóìÚâ($Pí\ˆòÕÏæù±-‘Jz È`PXÚ)x‹>"©‰®òíÔ"!H®j7•"5è¡Qǵëƒ1BTý­¥.Ý*ý z÷W]s/u¡”Ýy‘FUÇ•6ÚέgµfÉׇèùZCó’äWhŠ–Dñcå߉k]ÚrìR9J¸v¤¦é*…H÷6 ¤Ç¬6 H+#e´1Žt>ŒJ Bƒ,’{" Ák‡£˜§]»‰ºAª;óNÁºà$OÑ*v,øA8$³ð„áßã`Êi¹ÁI¢$GþN‘ì iBý±ªVõ<®‚L©ÓÓ$Y²gG=Þ:+]©¾é; µÚ¬×Fp½ÖÚéÚ·ìu¡•Y4¡VBN éI_¾žÌ 3Vµò%æK’]ÑIé e8vírWåÎH·ooHîßã”,‚4‘ÒŽ bT"Ë ]aîÞAòa¬QÊiã½~ ”¸A’ÑËÕq·Nh"ëärþ€–H” xH\‘­B«”C‹ÔW´U;È#ÕîËvNZ6³®,Ò§‘öH‡¬äÊñõ-ß7LuMw¨Õf]Ë&óåDªãËÙX »±/¥V•U» ß4Òúnô7%U Ò'ë‹Uàùùh¶•É‹9·­Á±k‡£N¹xw¢Ø€®Ý‡¤Ö”|¬NK’Ø"ÝÐ ¥ÅH¦ˆA Òí˜f!ÊòðÒÉÈÒ ó#0z¹V)ǤYï!’© -ƒÔh¤uäít‹o¡Ö‰«Ü9zøžKU4T²ý;#6\^:# 9Ó6;ó8^Áë5DJ”:ÉÆâSž¼=_Ê¡ â€TÙ‰b$(ZSO\ÿÜ¢+í&¤DèE=E_Ëß¼OÈj%W®îž“N#½ÝË7{Å÷g†“ð"WðØ¢1R8RnÝÞ½½e˜Ëß)cй`Ž}ËàˆÐÌVñu}·^É$…á"Üû~ —4ÛÃO²Ä†(H’éë‰s 5\¤buý}òqHͶAZW]«€´”’=Ø2ÂÑdŽnÆU»H €¢¤Aâº;xC©hÈ1OÚ£cƒd,åg# G íˆ#N#%%dc @Ú?12¢|;³"©îNZÍk}Yœ"ñêBK—0IFZC(Ša Nñ]q¡ »ru¨¾W‘¤@*H´ã¹ùv•‘¦:4 `U:ÝM¿”IɆ"ƒD«Vý–3|Û ¥q¤›ßJñ‹U@ZZYån$í6À‘D Ý.œ=Õ&Hë¬7骀tÿ^ÒLK+5kEKIâ]ܵ‹Y$©dèD3ÔIψS'¾t,ˆˆ©¹ãT-´‚ ငSíæÙ£$5AÒÓí²Ä† ¿­J»_¤I}Æ"v¸TÀ³ WõÜ+ßË…†!`–Âзšh=½L/í ®@­«E½µjñöìp§»|:Ó/jËgöÉZ}º¡½nÌ·“K±>ŠÖ­æ¤ˆA²9êÌâHƒtqÓHw¤éã ‚ô1ƒtÔDZõj ¤Už+³'âs,c%‰•àÑ¥ÒI q(jHh—òù£Hñqü¢ ‘jG1ÒaÎ!aˆ$mq¥Á ³±#$Û™Mc}¼h,/#—éÇæŠ4;[t vhu(ñæñPæÛ±B½1 Êõ GõF½¿ÓHzº*%‚¸`Uñ°/mŠBÉü†¾5%Âóu™û)¼°ÝI«Ö’§LƒTèÌæˆ@Z8icq”D ÝEO4'F[´Õ®x¯Õ‚ E†!¥•8ÜKÔÁµwG¹®éi¤®·³“ 'mç¡[ÿR.|-çÂ<™©|Ɉ ‡š‘ 4$Efr‘Ü0;;5k`wK¿Gy³ùˆëÚQÉî8•jn™Î¨ëÝÜÒÖ ó?þûІó’—(H&>Rž]£Ö’]g` …äöV=\\÷ixºä[z“B§É'„ë¡]¡·¶¢U¤ˆÒЙ°=Ò8FîÜéÆÝ{ÒF9ŠZ¤v@*m)HVC™î$‰äo±He-3 +Gžë y»?)q´1÷ŒQé…F´öÅAš;LI‡çâ ‰MšMhꛀ3:jÖQˆE’Æ>ßhvv„ÖàëkÒ3Mà2Þ«ÂeêGN,4ØÎJϨm`x/4Hê7¥>DöO„òù%'…¤'™_ÑÂ}ñôðÊ<ýávƒGdûeËVs Rªc×’#Ò°€i$í>Þ(Gq²AÚh©mbɸvw“šü椪m‘@¦Ó ¡ø ‹ð¬‘¨ iåAT$©þ†Z;)6¶8i—%}C+Ò¨X$ kíºxø‰\ÿžæÀJÉœF»H;…)*õMVÔx[!ÑÏNOù3Årm¯ú€¤áªcŒ¶á…ù…RîSbIîy/„ïk«Ê‚ƒ;D¿c-®]ªÒÐ’#iÆéÁ¦X$2IÒMPí#HÏnF ÷UqûÃDÏî¾áè~Ì"%”ÜáÖsšµŠ ¡kG—¡(2Êwš_z>NR@… ÜÄ0E2BIŠ€„Åv‡I¶;*#,õû ŒÿNèŽÝ¿_OâÉß 5°Eê.ƒh–܉VŠ^Ôj·ð`ïÐ|˜À'Bµ3;! £ª]¡X®’^‡Ú·ŠÝóf‘‹NãJ´¦§{ÉX_VÔJ’ˆå¾µñ< CkÊq;Cô­}©5G Ò› Ò)ƒ£'k’ þd»HobBöÙ­âHWhp˼ŒÓS–åÄ RbŸßM+F‚˜8ýÛÄHmÂPW~wæ³W=…âËé^Fø&ÑεHÓÔ!Ky$NÈÊL§duvÖñìXþVöjVíú")ª” <­‡:¤[é)¬2,?´Ö>H!AhÍBÑÝçØjžs&ªHC{ɳS©à5øR%kÍÈ×{Êå[âiaV’‹gØIË’ñ=#m­AòÛ4HiÒ²€t@º› Ò“'OÖ¬6Hg¤/´Víø¬½·B\»½œ†@z”µ…Æ™zi–ÕF MÒµ•[l(±\Ç’·[Ñ’4Q°.¥«äê™gÉÍ#5çŽBÙêaõò𜒹… Gzì÷ÄvPŒŽ8 ²¶Ø„ÜË#¾™omÊ Íàz6Iž§+!¬1 ¡Þíj¤ h£p‡³Ò ŒR½Ví_ .ûæ FVqlhlg`õXržŸl !ÚjÉ@–hBÖ_HkâˆA:eô(¤'O„#óhË@ZG’Ž‘2AŠsd,RG7-×ÎIônVî:£E¥¦Hž¥£Ogjùa,FBÙ)™Ž¢ó¿yÊêl‚d‡uv#<ö[‡Üí´š‡†Y[‰Ö)`»"£‰ÍÎ OïžðD—65;”Våýc™} HuŒJhtQCà[óÀÌÜQ²Í$=Ýyä[‰ãPšþ¸PÏl³¦Úµ RkŽ’³/; H«°=– bÕAOž'Ož8eeedHËo6g_H‘6H‘”:!H´Pb$‚çÑ£’ìÄ,ÆI·bã" ÁÏ É~’Ïgnñ”¹A¢×éݾ’­ÕuxaBŒtäðají3šÝ´6Hñú K²&Ä Y©O\»¼ôksþFÚ€B3\X²æoS„DÐùRÑ#*¸´©³ØX5v¤3(’È ¹æ63:_å{Nx¨ý7O«†¦ÁC4Ï™ýÝ¢úÛR¤o;@*¥qÄ -BŒ¤ArÓHÌÎCÑ“(P‰$±HÇÛi ³;±øVƒtó.ÍËÜ/h8½áÖ[·î¦Éß×¢ ±Lg Rl¬]Ôå¤XU*¬7EÀㄬU´ŠéW¨:z¤ÉMÍhé÷lò¶>(iÀÖØgbÃnIF‡z'[GçÖk¼tgoq1k+dZ—Ï­µòïqFR^—Û•Šå r¤X€*æ…H{iÁÔ&1a¡Þ¦ÎÝ„¾“¸A7´:;b;d;Ú©•AJçˆU»ÅËÒ­{Ii$×9&*ã8®l×îÙ-âÈiõCžØ’H îÞèÇîf,!«@Z&†*;KzWglÄ]ÞUì¸^5ÔõA¶u’’VM›©bƒ„Å 3=’´²/q—¬ì›Üev–Ab¸C¶D ñe$SÂ0ôæs’Â.! um«ÕΧËw¤P‚Úzr%ÙUÀ¯ç®Þ_.@úƒÄz»žyìYÂÏò&tØÝót׬ÔAØ*ƒ)lð[͵³@Ê…Öÿœõßš]½¶3ÒÝD±A€ù–:ŸDˆj¥Ú)‹t÷Æu©Ñ¾Ø°æ ˆÜ‚ Á Ë;·“¶<%xvf·ìÝ$ÏŽ];ªþ&†+=)ÿ;©<¿¾X¹bÃA™ÙpxÎÚ3Öt—#Å 2jƒ‚irÿˆk‘hÐj—ÄHÒà£+Ö|i‡Ð…¢Ö;B™ÅÅÙz Œ´q;ºª] 'oImHeŠxe¬'‰`ÞXkÚ´céiƒÉVSRÆÜGaÖÉz¦%0‘£L ÛÒ€´FŽH£ÒE±H˜M‘¿× Ú}üXƒ„y$¶H[Å‘ Òu^Eq/©­/ÞÝ—ÐŽdωDõ[Ú(Ä"õ”ò=q}œ©E@š›t·öIÿ„Yt(}0ä~RíÆ÷Oâ!’t…Z¤R-/N¡ÙÉ¢,óúÈPcä‡F¦“ _&ÈIR'¨¸Õ7 ‘A†©ÑØ[¯TËÏÐОR,¥>ZÃæY\¡]ú …´4I¾-yè]4„•/#/í²mƒ”`’" s-Ê£þ/(Þ\”„ì½M)Z•¡ѵ»~ @šmOlXSÒ vú’jw};ÐîÇ9j]Ôà+ÏîšR¹°qŒ¢ãÁjgËß0ü[·N¸³!G~›,$e‘&FÍU ZEù›|<3µ;”¡”]‡Æeóu嵨áZàí.©v c7D © ;¨’©žéõbe]+ΤY@¡‡IúU†¤„ºÏÕóÖ¶ A ƒ”f…¬x$ Ò}iìÛH[ŸtQ(ŽîAõÖ¤nP4I÷cÎ\$›ìÏ9®_%­áœvív6ƒ¢0êÚJÍæ\rlsëÜÖXåÕí7…v£¼dlp·X$JÈêÄ«½ü¯Î@¶ŒÉ¢X]-†¦}ÈÓcP$©D×±i‘-”z0«,Òp#$«¨AFpÉ·=ZO&SêÀŒFÎ%éݲzT¸,µ S0jR˜R[íøiAº¡,ÒÃVgßzºd¥ÓüÑ£Gˆ ÒäÖ´S:}qÐ%„¬U_ î’– -Ñ‹tȶ•GòÖ΃tŠAº $mÂâK4Gî?„[þO¤U\|‰$)”nG6¹Üºee‹÷O؋ͯc€­à¢±3'6Rg ޤî> $BéP+Sdw íŸà’†Q°§oDÔï݃ÜEÁª]`ñ¦'ôMÌašú¤ÜN* –ºl)Ý‘®<é-ÅA` Fª–‹y³ì2­ ¾bažÝ4Ý?ˆßLà[º/»ouu„3ü„JÖ’»Rµ%HÜ¡ýlÿ¾ÑýMiõ†Ž,Òvš›¿yÔâRv‡Á8.Tí&÷m5HG¢)Bôè㢓m€ ëæ¬^_]ŽxŠÐäúAj‰d.õÈâ©©ƒb•Úͦ°£¡AÐ̧ó°&F"­ R_¥Gƒ$»É©AÖÌ?àRl{F°.€`ëä³RZý´ÒQy$©²«âø­1íÊèÙÉ0;m¢c!³“O·‡Òpê´%Öˆˆ\N]†¾3E¨Ão'äRg+9&g›àÚÁÌ -î'd`¢˜‡H2CÏ×{Íx¯K¨wƆVpf6úé6^kðSÞN­]‡m,R+4G$Ç%$ݽuÏ´½Eä‡Ö¹Ÿ¾ÃKjnè Î`Œ´kK("ŸAº¶ºŠ$1KÉÁOŒ„æªzx Εöë\‹´©YÛ,´Eš˜Ê:öDbjÝ·zÊYî¶ZÌ-#~R*æƒÀÔ¢†<8Û·¶¨x²ÆE+Û2OûP9Ž’!úžlÇ$,GRCO…@RÏ{¿P…Âù@kßz‰eÀȬsñeÒ–ÎøêoRV z¦Ñòz5LûZ€Ô>G $¾ƒ i’”;DṃF2i G^_y7¤Í©½ÛÙ?4:£@ºpùª‚‚@úðƇ1z’LBhõš>àÓ­h–––Il¨· R;É$IýÇò÷þD~¢¦hdb„«s6<£{ CRØ€•v}½#jG%çÖ``¶0¦ïÔL¾òÍŒÈ@ÿ Ê!ñÂJ-·áßH…gÊbJØ+icæ;;Â8dó%'̽‡Ü7hš y K¨PèD±éíh’×6HrUZWçN¤Ël‘td:nYåÜ¿ŽÙS ‰=b1¤Í¡È äo I½ø0éD\7c‚®EŽ@„=:¿FÚ²F–)â^ )É"E¹‰âhttÔuèœQ¤º¨ 5R¿ËÅ"T˜-ʲ”\7õÈà®"’A$:Äu]ƒ5`Xjá¨o˾‡9@: @Váe—v¾P7ÕJº6= Ú£)=OïʵBz2FKFŠ™mIøl/ª«xÇÿ_J­¥:bÇ9ÒÒªv† ¨HàâV‚þ•‘=<„AªWwíÚõìÎ.9æ¯è‡ÚÕ?49Ó|“@bZ˜DAÓC¿‰ô>+6F’âˆ@šÜ[¨òè;:w¤h¤ÎÉǶdF{õÚR¹1ÒèH ŠFœ#Ý{{2N]@Ú]«è‰†Ž‡fÙ2§WCÙþ˜:p ëY––ÙŒî G½—œ+ÔÏ3*ƒ²G ¤FÝ€ˆi‘ÎvÝÞfÖªg¡ø¾58’ ¨öé¬âÙhÉ bÒÝ}-e˜M®NžªœþÖg_cTã ¤ãù(),¿™šsI?}$×4€tjaæ‹û†“OÿÆÏ¾XÖ³°ÖEquõšco´p`ÎJ¹,à\ÖK·´¼¬ž–—iÎNŒÖkýêw«~Ïe|R/Ú:Å=ãÙe‚ÅæÊÕ‚4žxF Al‡¨u4Ž Z!|®ÛgpH[¤7¯ÚC³ò°fôßPÍ:=ú”õ˲ bOµÒ§@ßÎm ³ØÑe –•ah + *Ô]|¨ÐNMqÔ m/¤– ê[5ï&œ…äA×h8@ùÌUA‚*;åÕ8€©@bkÄ´¼P†s ‹Ëa-^V úžé;7![³0Ô{Ù}/Ûµë@޶«C uÑ`__Ò²~MõÈÙëªxgzrrrFÆ`É IDAT]§ÔüÂe¼¤ÔË•M;pÁªWï¾{aù¬iavf2ëŒ&œFË#?ʬúANœ9»¸|AƒÐXZJ Dóqö,<Ÿ=?ÇÍ9ªâhv¿ú>ëõ¡¡Á¡ÁA¸)EnYúô ôõ©—}ð_4÷¸JòéægD°BÕQsKvÎ,:¬³{÷n|IE@ôÖÐnõMáÓnu¯Q§Üº€D©XZkñm ÇQewÀ ÿÉœàqg^¤†äÙ5†¡pžâzQã|kØ·ÞÞ¢ûuÍu^èÇ5² óù²Ñ…ÓN²šÓw}»¸j1EŠ¡^HÐoŒnJõº} NØg_Ψþöþý4,cN³Ùô¬¹ýRê#EtT—„ÒåÄáµ*0GÐç6x\lk©8*F½Ìú…ãÝk„È¡ÔÜ”¦Ä9 xøšsÍSê¼)Xìªs¯É³gÛ¸PãŸC=¿ù¦ú2'š[zà'9¥ pÿVF*fà;³³øÂ9ø¥°ßœqZP6èZ:òë]»ã‚­³Ooo_o_¥Oû(ô}~Ä/Ñ‘7 ,ÑQá¼à»i¼²‘ ŒãÇ Hd³p°9(``‘ÆÆÄÑØp¸v¹P欰³¦‡åÉRsßtÉJœD™Co`Ç@ä¸åêK 8êR¿4 LÆa±dÁqéýÜœéd‘×x9Ÿ:ÏpÔ­øÌ›'Nœ‚¿mãÍ<-þí›o2QgÞ̰ 1#áœú$¼Ó| ‘“mì¦f“sžPnƒ==“ÐÛäìç[ÔÔ,O¿’Y¦vyÎþ‰ýb%ÑÚÓÊ_£xFÁ“ÀL%ótë`­ÒâXQYQ?¤”¡*–˜,P³Š-™ Uº)ðêÌJ¨5H“{•k×™Ëqõ¶L{ĉ¦™Â³Jú$•Äýì,tû24ÙMÖÖƒµµvqí¢k]Ôwº¬£Á:æ Fˆ Yi\¡ÿ£GÝk2b^¬z¼®³ê"l.¬Ûíj}Àé:uæÍ³Ëë8‹­ÏY vÎ?ÎãÂìÔLÂawmš9{G!ÀœŒ9Ó°`Ì©‹fr-à699AOé„KɸGKÝ"Tð“V*@¸%Ѹ$ƒØjn%Mh,ÒsK*’*âK¡©)Oh¸€¤gJ=Ãõ±r¢ÝØ kU­aZ4—QEb,M\”=ߟ'‡ö¸!ª¤€/îeîGÏŽÍÑ `ôP$»¯ç0FjN: Øœ‹æ¨·.«h¦!`C3g—/¼ ¾r3¸{>%ò„žž©ZÇ;Œ&%”#©[n»ïÛÛbdï²î€åMfÓmhB£qËuÊÕþÆÌÌð3퀤ìÑ‹{€£ƒ§aŽíÉ“§O/Av!ºª25̦(ƪ6»qŠ9õ›R™Ýøü®çÖrv¥ŸØ‡~¾19{êÌò váÆ+%Ú¯•¸‘Ò Ž7„‹Òd½?R<Ô²šoçŽÔ¿èzÍéèñÅH GÉ*ž3'O&Ø,^Ðr„ ´VŽ#85¡@ªÐœþ”Aâ‰{—¬zLñì™gJÏ<ó Z0±R=Êq8Ò,aÖU…öÙ¨³³Œ´Nð Ó Sg¡ÔÓ¯b$ñì@©„;/Ý{¾™òeOÊ“ f4.õ3KdùQ®³TšœW  yÏiìëðc ±A{4 W -.1GÊ]¹†µc«Q„n$tP/5·×ûŸ³jÚ¸¦4¥âígÚ;òáÏ}~ ëù4Hɵ|éE{7³°c’®"HS£CýÏÆ Z[—Öø9{º+¬S˜‹´ˆ)ÞX1„“”Áé¨ð$ +ž±…#ˆ%šj`x®x'muÖU¹ø ¨ãTviâKüÇ„Q¡³Tz¦ó8êU)õ W׋,1CôÅ ©;âã QˆÅLêE¡HÖ/¦4 Mõ»&!ˆ zO±“‡ëe´Ü"«‰A⼄Éç)°zЪ®°õôæŠ9*÷×ççg&«¥BàeLZµ@BÇ£#á@BŒ4D– r‹æ"·ó$²8ú™5béæ5p”‰–32+¤öªÊKmŒIV Õ$Äfÿ,-Sí„ÔOD4E4M–Øo9zGduÎ|dUbbDÔ ™ö ‹aEJà„]­£iC×JN2HôÌ¡hé®"9àÜY&©«[HJ°JEÇ$)† 8¢“-ƒú=&!JÕ2ºv¦@ÕÓâ‚YrJ¦È·—2á®KéïÓ¦ˆ’K¡²Â»ªÃ“³óó“*ËgMb@iОÝIå­“=º‚Qh^ÿ0‘¡›vQ·îp¸‘R:FXÒ=?û1H$êxj£«É[;ºáI@ZdÖ<O(J*ܶݼ*ÔD×è9wa‰jö–°¶•p"ž5P'Ù8Y©³£†§9X2A(Hãh‘ªäÚ HR=5L,ufšØ6Ñè* hŠ0ͪ];EK•íPÅuÀ»c”ºã(iÝA!DŸ°TŒÛ$ ×ÊÃZkÀDR®ò ç[m¾=\K6¨aW·* .!´;7Hу409¿0;¯Xµ,RB­€Ô%)#åÔ­^K­Û¦ö"l1¢‹Pîç¤Ï?gÓÒÒePî?3é#™I8×nÛ­‹ÙF IB±a} ÁšdŽÜ` @š˜:4 !:窣\*ny|KË/ϦɊ›`¶÷4»vã#HäÞåÄÒ "þ]`½Ìå´I ü×@Ré™RÉ’ó Î¹¢:ë¿Ñ]­ÄÝ;"P‹â¥íÈ©„¥¼c’8+K… \ÖÀ²–çr¡$Ue椊®ŽxJq(k^dæÙ)ˆ9¤ÎruxæØü<‚”÷"-².Hy EHÇOž&­n…¬cd D·nݹzphH"^ƒ7S@Jãè¹ö Šqä€$­ì­gAÜo}îÁx:Ù±ra‰üºVÖˆ@šgÁ;wñ\rU¹zò´$Ç ›Îœ±Â&&I47}hZ@Œ‚X6'Б “e°:åÞa¤ÔÙ‰$‘ý HZµë³©‚n…,R·+85¼´½ˆ6 GÔ†Àˆ c¶kw 1“¿f5K-¸r;ðd3¹µe=”õ2V18wOø\U}cóÇæ”k±Hq±A@âéøqì.ƒ=ºvU8²Zón@w¨aܺùãÍþÎm"IT; R+k´FŠ¢ GIƒˆÒF¥pöð>î«D’® H_XH`“v´ÂÈX¤3‹À‘6GÑ>&´Lç.FËÎ#Q“Ò‘ÃèÝH"6äE®3Œäc&È tóMUž­QT’ÌT™Ûè ON¬¤PêêJôí¥í`ލ2/VñÀ­…RµæÄH£±þ]°<–æiyVm7m{f&Ò»9o+ú]È3”Qi`ƒtlaaf²ß©#6DŸAÚà ‘A"{ÄiŽnÉÌ…ÛñØØoâ›ùuiƒÔÂ=·vŽHÔ…»²úÑMtë>$вæK¶DëÎã‘_ ã/žjîÏØœže‘@JÐÀ{@þžÂ‰n`Òð”\¦qŽaÒæ ÒN§!õDNžÒaï¡j‡bC™v™‘¶¸±QØVHb"¦'!~£“#%Râ®'©_÷U¨êU=$A¼›2µHÔÆRIn~˜•ðxñæc¤I£6€ž <¡ç#û2É[*dF‘¯'éÂoѽeÚ¤™…cÊ"‘j—\ ¤A*ví„Ò ’ìNž<½Lñ‘òëlkÑùíˆ!ŠzM÷h,·iÅ)Ó=·Œ,®H×>Ë5ÍjI ÒYRcÍ •v$lØMÞh¦,Ò^Ò ¿ ¤ËÉév “ÓTâúx/q«S³©-Ò [$Iq¹;—,„Ûti£„s'mŽÊØ‚Ø7 õàR>Þ§M”¢©«‹jòº£Ú‘À¹|»BBãR¡XqU»£8!„;^^¡[úôL×PK 2ÌçÞskI .G+†‹‡¤wægfжüÝk5'êÒ¡#BZ<Žp$ÁÑMÐxêaô€v©Ú—%^‚'}tÍ)Ë«[EŽE¢‰Ü÷È꘬ááíÐ¥~Œ{÷ô4×u””zMÛ È®Ý #?wÞ.¼º–BÓùóêÏ/%fšX}`£tÊ€¤ñÑ:Ôi’­,sQ`ãá‡ÎLzª # ÜRÝ7ÐgÔGÉ.%¯võdTöqýÕ • ®g'k(ªõ:‹ 3l’†«X%”“þq]äIc¬/ó"ôâòPÔq3ÆRFöã˜qev LÎSGÅHýÏĪVc õj­á0‚Dé£kWW5H8úÇ2EØE/AXUG ½ëÆHë6G϶Ò#ZTû±µÁb=D¡wwﶸ¨ë©ä2“¾_³:0D I=–`t-Ý*A¸$ýº®ö 2Ù¤ÃXâ R`r¼º\®‹ åzÂqD$‘Ô0PƒV%ôèúzÙ(é@©—JÆ« ¼h³DVex$+Ë]–Eš909н}¹€g­p£¬g¶ZxooÒÆ9rAºÅéã ¬13d=ztŸ–¢E:Ñœh¬q¾8Õ•ÚâAš„<ÒƦèÒ]KMdIyçÎ]<§+ ¬XI—r' ¡E*kBNEƒP=[ #»!°’±A.:øÇöí ¤Œ ô¡× c<;jªís•»ª²H]±‹¢‘íÀaz*ÂQ ©Ð3¤²_"ïnr¬Ñνo)4‹[¦ÝÎ gÍ{jÙ·»Žò÷+ëI¤ï¡ü ©{ÂÒE IZMFÉM,ñ|¼h9ž‰’¤HŒ„+‚qO³m‚´öØÊC`@ÄI*Ð$½2[$©R¡aB’eŽ”s×Ó•X ®ë„ ä0б²U…Š‚·X¾ô¥™ÒÌäÑÆpêI£Ó›Ã<Ï®ªÓ×P:ødÜÙAFJJvCóóÊ5ÍUK¸õ)cE2HØ7a6‰Eb!ÐÇø¤ƒ¤›7V/3HÏmGÒk¤{¤d’Új“@*I©%G%hHJi5$“.j––£ ºvR.Ǧ(pR°*®yJæhl’ 8s}”Jª¸S!  KÙ£®¤Ž A*°oçÈßœ…­æ…R?ˆ “dII†Ô ñ¤a] ä™Nsç/YW½——ÍÌ"úAª6æ•=Z8Ö<¶0V-ÆAêH±HœFZ$‹éžåÚÅ9R—Ÿ ÒµtZrÔÞœaÒ¤éV; ­Õ"ÝÖèÚ%•~gLîéŽ׎|»Õ¸EŠÏk½xíE<;ôí zµI k×' "![ \àê I ¤ô±¨´| g¹bEA"rXgËäôùÅ"¤Èì!hí+%¦‘Dm,”EâéKÊ A”49 ubÁÌA–G2iU6ôÑp½¬<Ô]¾¬O‡R±z»…æ±cóÃåg/{øI» añLšg§Ãôt+7).oGÒló,[¤‡Djx²) =0éÄZ-’ô!•øE)“#Y¤ãK Ú HIž]”£‹v5«k$,ÂDY$ 8t¹p¡b¥€<=xâ#¤ ímp ‚0¤\žSI=Òp wŽõööÆ$»nðìªñ¾$¤•‘/´ƒ A”í8} LÒɱj@ã8‡›eMÔ#;893«§}s[’¨ÞXšsú«PôŽ ‘æ'¡M#´-’»²/ ¤kÒ‡HSC$Kïj R+ŽÚž|¿FÖ#­$m‚vПlŒÈ"MH’FJbæèܹó.,%Ö7HGšH¬Ú¡­QP @H ¤Ñ¢7ô_#G‚Räf$P†kƒ5Â'>í‹FeÎZAG‘•·)‹„3ôûë rþHà=¥Nt[=YHn¦8XEÝ"óºsÖ÷Â@ÏÇñâåþáóóó§@üÏÎÙjîû­AZfV#é¡IÛéxHÙ­e…Ī¥‘¶Bl¸°VX®Û!’]Øj©X$ªµËér‚=‚)ÊÉ…BìÙiÎH#d‘Š« 94Bd‹Èòà‰Þ—ãw„)ñæCÔ7Pƒõ}âÚ©P©×ªþ–dlGÛyVW"¥B´<(`×Djð(&¡ÕIÒ[œ%MÄ!‘èy’¬åXI6T¤•Ck€ò}ìÔ©…É~eê¼è.æÄiçcmpo%›¤lîe´yE@ºŸ Ò'?]´cHµÒ(´tî²$etùÜÅ‹h–ı3’ôΊg‡i©»\,¡c—ÃôQc{f(´,TrX„¢^BR(Ða”D]}`‘tdw#qåw‚A*FAIC)u4\åyÈÈ*ßnòÀCÒŒŠ’ªå“dF˜ðL|vêDüöBYÂrï’,qÉápñêðäÌ<øu§æg†vó9®#J”í¢ ´@º" AïD6HÇAúhue­ ­u©QÛ ­5•äŠ Ö*6”JZ³ wÀSiG[ ÍqeÃ勤•+i•«`‘ÎÛœ©C'¹nUY$¬šÙSß-‰­d=8" õ_kARl$á‡vj‹“À‡Hþ¶f6K]]•.nHŠV4H„´AŠvVxç;óåZ½Ñ˜”‰Aš™ëï!’½ô—;ëue¤%È´QÓªÇó}Ô×{ª_˜YxgçìÎU öò=ÏK,ýn$,º}ÏRíR@úØ€Õž ÃO6“£¶AZkNöñÆ@Ú¡| [ûv6H‹´r%¤s#_’‰KË1¥Á6ü- € )GŠCˆöæ@¿ Q¨ !²9ߎ@êë€%5ë¤?¶W—á܆ˆÔP¤ªÓiŽ •Š)C„@cÌÅ*ÛAéK¯¿NâÝÌLcl`W _Øú>kÞ¾ç›ÐÖÜ.öÙXqŽvõ({Eß £cD*çéú-jí¤q‰*®±ÁéñƒTÏAºÍ Iž"ÔzC›G@ºÎ ‘ØðdƒbÃ'Qù;£²!aŠ˜£¥ÖG†ŸX -Ec¤XUûïG¸5óüw³Ld*ŠífmPO°-‘„L¹04¡RŽÄ;´QA¬ÇOOu` GŸØ ,’úJRo¤í¼«Ò “üºº2"$¬’HŠÌ¾2ø‚…rŠíØ ½Žš¥¡ž]ÅØ$YÌ{/CŸŸ<³Ò<ÔËÒC–Ê=œ©Â£±IÄHÙ£™1˜š— ½èêËT‹$ -&€¤KV“T»8H7ÓAÚ$Šb =ä<Òµ†HÔF1“Râ@!Žv´‹‘ ’˜$ÄyíJb2±Ê ]ˆ·ÊždŽwÕï ©L)K~2.\ˆüÀPq¸ÄV*H(]uŠW %iìƒ/µ»vnaP¶šwuõ”FrY3ÁK2a?i 8ØPÜ‹Ccc“ìÚ½þ¥×gˆ¥™ÉI˜ßPÞŒŒàY³¹ÐÑŒUž¿ªKÂ#zP®£sìÀ@Ï®ÎÏúJ^” ÒåT’äo§: ¤ƒ´>Œ" ÝËiÍAå‘nJc_H)ƒ¹¸oGÛ!H{'Ä"—¡¶ÎêæG—q- Ûsnüº3'Mkßœ6LX)R˜$¤ ,¤t‡%qã{SÑ$­[¿š’¤áaŠ‘z#£„”_£„ºº2ªJœD*Å¥†ÀêþÈå;‹=ÃÆ(Ëv¯Ó¡8irt §3…r¡ì;2F<žAòGÒ Kï|\H½|Àš¤e ù@’S~bxrŒ ©[âªGÊÉV´i“¬Q¤;·ïÃweƒô-}ÖãÝaÍàÍ›Ú"Eg6¤·K›Ô5G¨:\‡MµG¡Dèâ»Ìv#]IÛD{ñ⹋Žô½é¡H‰²Ú"‘¬c r¡vìÂ0r®u:ΫÀ©D¨¢<»Á¡A©Síu&ra±jOWjUÃö+Æ/[ˆnL d]’úf:K°Õ|LÄâèu6Jµê®ÕoH‘mÛ“¥Ì!/„•i«Ô _¤ðŒòëóˆÐ;ï¨WócÕgyýÐ]ÆÜ$ ”R‚üý‰ ’‰‘>ÿ\¶AZ7F-@ZGøŸh’ÔÏ1ÒB ¤Ô1‘鳃R{ewö×'fšÇ/ØSÖQh¸’ÒÚ§;H"-'€ŠÖŽÂ! ‘FöpB6¢Ù ó:ïª^çɱ„Dî"ÇõéZñš ìU”HEn5W®Ýnõ•ú"#O(FªTÊÑîX#Ùmçº loŠæYïÌeµùb?ìu™±áõ×çµMBÅ¡1„28&žC38„¤kh†GRÒVÞÂjõC Í(‚Þy9:µ0ĉ–+Ev_¶ ²Qî·IwöÙ E@Ú§ÿ©­Ú!HƵ[/EßþôÛxÔGƒ“zëî¤1¤Ì¡Åm( nýÝΞþ}{'huºî޽B®$}£A:wáüòr¢AÒóO@êAØ&)år&+æ¤É¨Œ‹zpqzIšÓyldHE¶HCƒƒìÚõ¹ûʺªq¯Î¬y)õL¢R1Yût_o€û˜An˜’£ù×çç%Å$”°r¾g³kŒË­-éRR-¬ÂhxlþÒ±™áj©—FwÈÆ‡èÇ@:ŸÒ¬.ŠíZ;NÈÆf6Ä8Z¿W“¿¡6Ý€´>Œ¾õé·?å(©Ÿå€tA21RF;w˜ºï¶(Žªµ½£³Íg–/Úƒ®\I‘¾Ï¡=:^s´èHglŽŽ HS$´$V 8—×…Ä‘¢¢o–q÷ T» ¥@÷ø%Dà)új»9!Ûë* ] ³´Ð°]/–Áú ØŒUñìr¸Ý7Êʹã”ìëú(š`ÜØ°òîÊ%e•À|ÉB1é•ðy±¹/Ëf=\¨ùL¹g`lffþØ;r$˜Â•cyÂÙá§–¥ƒÄÕßÉ]}Pú½@£çÍ IDATfžÝ GN­2˜’GZŸÖ ¬Ñ·•=úô;p¥‡Š¤ÕËË',±aGKŽv¬•£þ}W`=ÍÅiœ@ÕîÊ•„ÆØwAü†Ù'çÏ/'ß'­)BŠ$§?§B¤Ù©q R)Ï…©P½y"f‰kî‚ÚÇË=ݵl±z¡®Xz¾ŠùŽ$H±BÏ-¶Vcvu%Î=¡i{Q¦¶–´oÞÒŒ)$U`'š¤3FmP6iAš™l T€¥9xž”…¾x‚\Å®24–Ï¿cSÇæ‡°–»#úwz… ’­Ú!HÙÇ ér$7Bzvý%­ÞÛHB1RöHAô]8ÀÒ·Ÿ|üèá½›ê†ð¦)#že—R 4ÅÑóµÆÄ‰³aM®dZ%’®­D%;€žÏ_<Yðî$m’$Û"”‰¥J»ÀÔ7°Ø GÊÕ÷Ò¾¿—ëõÚÕ¥õR ­(JŒTÛ=4Ô×Û¯©|»r¹Ëé (Ú ü¶ËÔ^á—°¶B$¸ôËUªn`ŽæÑ¹›–&¡ÎP’"]n¤°†5€n‡QŸ¢RaÔ?<ä‚tª)’,ˆq=»ö,ÒV€´ ¥U§‚Ô>G@ÑïÐQ,}ûÛŸ|¬L‚Ä ÙVíÌ®öNèMRRmßËÀÑû0çòæMW1t%ÚaŽ >Á]<©ØÄ餽ŽC¤¤q<$_š&€µpH®ö)ŒF&^yÎ+ $©ÀŠYÒr;ý=Pª%¬tVñQV[ìvÙ6–XgGbC@…  TÔW¬5”MRAÒëöÑ(añ]OO©TÐÃ^¤V†IV'Ùà~0Gó1H ªà†ž ;ö“÷ºd‚t5æÚI±ÝãDì1B7²@ŠÕú¬# ¤ë¤ÇQÚ»É­Ž¢õ¯% éñû*HbZQd7ƶ‡HµÆ+`>øèîÝ_ƒ_ÜGÊ.]»U¾Ñ)ŽÞ=þܹ vóÄrt³Ÿ8vÔËùØýR4IÈRéjŽú!°Ú"îZ½11ûê‰o½õö‰7f'&IÊIJȪ„ º²aph F:våÛ-VÝ^ârÕ¤ºoYpŽóʈWr% 7Æ&'™¤y ’\”ú«=»@,éÌ;s'¡ÎBëm†‡fÞqpÔS†Ü®tìgÏþŽ‚t.¤ÌòïÈ<®8Hš£g×ÍQZ­IÈF@j3kÄ¡9úÁ÷ð?ý›óè‡HÒ=ÒÒ°)»Z5%‰”Ò)»³»·/ýÝýÞïÿÆ£a’™)jŽHdΟ;îÂy£ØÅf¬Z[)Ž@©i,Òn²HRýcªƒB«8H¤j_ýå‰WO¼õ•÷ÿ»ï}ý+oPF©^ë†gjÍü;m‘”gg¦AZƒu†²5]•ôºRú Ù€c$¿baWµN”6Ì3KóóøP‘„05¨;ì´&òw’7 óñvî¤Ò¦Ø€åèÔ<ä: ÙvĆ“) Y}3‡Ÿd”T|ºÉ }²Æô«ÅÑ~ðÃ?{ ç/~òC"éá• g›³¤¶:ÌÛ²F´õR9v¯¸ôþ¯ýñŸ«/úãßJáuX]åˆu†‹1BJìå‹pt”v÷:4¥Aê‹ÄU@a ­{”Wâ‘[Ù¯íU}ùÒÔ÷ôôÇßûŠ"éåZliÍj¥(¤ZŸdì™'•®ôn¾í<²¤Œ%®6x<,\×T„0ÐSQÒ—áîu‰”t´T€`©¼s—”•w¢ÊA]µÚ?0<<6¥@šŸîyçqk“çêviã¸2Aºg¹vëéÙõs”Qý}]}‡1Ú®bÇ8sôßEõW¥^þ{EÒ§OþéÝÕw$rív®‹£ô¹ *Bj¼òÆÛïýÇê«þßê‹*’ ¾Œ×ªF—Ï¡gg&§stœ–QÀξ)Þ3¶»B®]¨G?ù0¤œ<åÙõÕG_yÿGü=}ïÒ‰W'^P&)Ÿ^€kÇ%BµšÃQWwç÷d€T¢5}Zû.$a„†H(@8W,5F)';?ó:¥ù(K ¦Ædc÷dï¢ÐŲº]TACC1…š²FI?Ç5 ÈŽªU%Ÿë§” Òr$îGʶG¦ÖG+F@JåèÙõr”’VÚ/­Cƒ„}ÿÔeó7ÿž¿yúôOï»ßùôåÛ=%cŲ8Âw–àO[^äb_~ííK|ïéÓ¿Ç/úÇŸV®–Ô¡önGáŽðy)‘‡g±4?3666400¬þTÉ A3ôþw¢© ’º²ГéÇn__Gö€ÈdR…W¶{üàá½{HýYk]Úå¨U?R¤OÖÄѧßþÎwÀý‘ÆH¿S$ýÎw?}òàæÊ…ã “Ê$U«)[Í#ß×Î¥6G¬*Ï®® Ò{ßø}á¿{ú§ßúäŸBW¤$½«1R ÙK(–Ò»£œŽm6uˆDë‘ȵ ¹ )'¯BS ÃÜj¯¾uéƒßú×ø‹xúôƒKo½:R{žAÊÓ2fú Èxèh•yúÉ@­Ïš¢_¡Æ¤î®®nwSŸU«º##nFÊ'JßAÞxv WÀÎ\¹ÊeBFlHtò&ÐÄ![;<64ÖÐ Í[ý“¤~ôfesRhk îœÕT–R@zàÈ ;%Bºrú’q6Héíy랢Ÿ ÒšÎҽɎð ú¡rîݽþþYå.ï«=ß'ŽüMµŸOõ¹ªA©Õ0.Rã•—>ø•?Å›?€ôée’P«{Wo¡¸p>ѳ;s&np§ •¬ŽÓV´Hv˜ç¸oÏLµCg/OžÝWÞûº†û'_{ï˯NÔûàöÌ+‘(Bï7©ÄA|ú7:qÊ$aQÄîÁÁÁúàn ™z+] ›\ì\´;6©ì[Ï’D-zU$¥jÿ2Iüõ×ßyý²3¯+Pzß1®:e8{tìØ LÉ dp+oúk5×Îi1lCką̈ÿ8þtsµ=ÖÏÑæ€„pôƒG«Â‚¿fïîÏÑ$ýòÊ…³Çf¿ØØ·o_m˜Pr1Šž~þùááÏ?¯hCËe”¼Œ£@zyAEH¿ªAúɧßúøÁݶk'iX ‘Î;·Òé(÷"É–1°HuI·ñår¡é17©¶àþÆŸþ-Ãý÷.Jˆ‘âyeij5œºÕ]e”пQlèS6iX1T¯¿0 ÅÌRwZÑ·TÑÞØ|Òæ>òíp2‹(@;Ť23äÍAzgþõw’Î)]ˆªý쩟¼ûç#áèØ ¿+Yþ'FÉ©þne‘¤UGl{$¹K,ì|¢'؉PõH‚üËÙƒÔæìoe2¤5“$}ÿÏ£¿FÑî)’ôWOÿð·¿óíGêG9{öÔk¯¼Œ,)”ª6IÏ>«0®í“S–ž«¦xz­áù}//¼õÞ7þ+éožþ‰ŠÊ¤Õh•[¥w/_¼p^o8O ŽHsäÙäi];jÂî]iÕË—zj/Ͼõþ/ ÜûôÇßøàI=%À¨çy¨z¨ï}Y½õº‚ P*±ER ÷ E£££###£ ¦ÁZ­ÒÛOÄÊ.ê‰eÍ!‚¨<¨\É<æci1¥´Õ@o”Àмþº…FÛGÌë ï,ÀŒ<èžðÌTÏ­þöS׺DAº¶j5öÝø@áBIaç·5ItkÇÒéOKʽCNµiÓ¶QÜ6½æˆt»}‹±?øÁ¿úWx)ÿåÿñïþ·ÿ@6éoŸþéï*“¤HzÿÂÙ·ßXxíµW€¥çÁ(Ù=¯0j4¾¨®¬/¾Ü`”´«WíI'©§ßÄ—þñ¯ü3 Ò*Ù qA¸T…#TÒÐçi8s\*ƒd[ß$)RÀS„ F(”UbÒh”/u«éËï}ã›ú{ú·ßüÚWßzÔåÓuCÑÃ˯̾úêì+/+”ºËê"Çb;ˆ‘”5R)ˆ¦fáŒOLJ•ÞîØ†> Ê‚ˆ”bÄ á±r±[}êîž2¯gçŽ&,(WVF4FÁ&}éuÃóñìósÿÈ}›þÒIÛ#ÈÄROاªWwÒjG ×[Í/» ="޾ÃGÚ >yŒ =шa¹ç£{7V¤±þõ¤µl£p@Zƒg÷))ßßÿרQýýÓ§ÿûüÁü;2Iÿô/¾ÿÛxeÿò/¼é­·¥—‘$m“À¯V}ñ•×^{M‘¦ìVc¯2[ÃÊÕV‡<½4”zú민qéƒ_ýg¦Aú—îë«.GnSÒÅ‹™‰íŠv°÷rŠ@Rv¡{ ô” Ðl5 «µ‰7¾òe%ÿí7‘@R¾2G¬}õo©sâW_Q()’Tü„U«Ý8ŽK}ÈÄÔÂÜÜ܉ss³´£ÏÉŽxL¾r¥†Rw¥w‹n¡X”n÷UJ©VWáÈ׿ùß°/ù×Ohƒ$ËÍiª”†CÛ,t!ºÂÅ Xü>"i|jœ.æn–¿M¹30z¸‹Ïו•üàWþ¹þžþÇoþ—ïHÏ÷”«Š£‰W^=ñÖ¥K_Uç”=€Qzú,Hþ® ×`µô«s'¾üå¯|ùËgOœX˜U_|wŸ$1Efrk ±49u÷B‚ίهPb’tÝäpµ§Ä3Ä$ŸF …~ÊZó$xõ%€tUƒD ®¹ßýþïýðOþŠÑE’‘ïò“?ü½ï(K…‰Ì…ÉuÔî\;Gl¸¿±¤†|ÿ8Bú³?ø_àÐEôwOÿüûßÿÝùÝßúô[ßú~ô£ýÏÿý×ß»¤Hjìf“¤ Ò0t)ŒÞÿÞ~ü£ï½ ­’rñ^££ì”()^{ûý¯ÿ×H“jwÙ€]¿?úˆrµHg¢5 o²g7g¥‘&,òÔ kLQ`|¼B©ï…WN|õµ•4 õ)ÇM9mŠ£÷¾ö_ùuüüæ{—¾<6©ÊS„”c72¥ÌÑÅ÷>øý?þñÿþ¿|bnjä…šÑ휉†æã0»bµ9K¬+bL!Ed ŠÈòyš×6Hw¥bul¹vʽÿKÈÌ¿ÐGØ¡·~ó7ó7à/…$锲G8­5•ª´<)´ö9§l¾ÌéêUé®éw~ç‡ÿ/oïÕ™&üþ—Үĥgr/~h‡§{dP?NËôÒ&½#«Ç,H˜îXù>9©2ŽE›£)<øÔJc¤Ô"L°RD¤¹£ €"4xC×jËÍEÌ:Ïó¼—½wQ“™óíéé–ªbóî·Þßû\Þç2âd~­á+$“ŽçòIÒêÊSzoðT~Û?ü—ÒŠÿe !ÿh!µ0—Ý„Ò×Ù× ¸jJaw`Ϭ¢8¾ÚR‰]ÑÑ`D .è»Ò½Hò&Ình@’ŠÝÇ»w%£ë4šó¢#â#ƒz»uvlÏÕú4ª®ž–ky ù‹|ü‚¢âS$HŠR(@ÂEgµÆ”s½>#çMñQ@Òj`”‚V×øþÝúÀ`àhç—Ã|F ± ÿ­_-¤… åÒkÌÕÀÃU-œ¯sÙ-| 8òóó›,¯@ iéB< “ma)‡#~ü@&a­ÿÎ9úò«“_Ÿ— ÿ]|º.zñ0’¤‚ôÏah-&Ϧlá¬æ/irdÉe÷bgÃlö¦<øÅQØ»¯ESÓÓ“xî$1‘ ’ù¾œÊÔÌÌ”¢ôæÝîí?¤Piõ?$Hn±U£(ˆQ[Ûcæµ›P64ðcbzz¬H ß¼qïÍŒ c|R{ezfzBq¤Ñ–ŠŽŽŠBU¬ª 4"^ $E©ÕÝßjn9ãh/µ 8r„ŠA#Ij2ßVW×÷;B Qk¤·™f‡YØgŒäÒIÓiÁbŸ€ (cеšm&0¦ãPÆ= HrwûÎ;*G ÙQ)®`j'Aš§mcÎrsdE)AjÔ€”¸@"ôˆVÁŒIÆS¦`Ø+–{£jç Ÿ ÛjØ™ògùL);ãµåzï·ô4,=úÌ×GÙ-\¼Ü÷MÿàðˆXÃÍAÅÑóIlTH¿ŸÏÒE X¯"ª¤ååù^¸ä éŸÃ~÷Ç|øÉ°2ûrœw¸yuE€ôß±¦*ÕLyÕ:O^éΓ÷þóäeÄõ^» ÒÔìò{•©ç2€æ™2Ú Dhf‡áÓÊ@Ùñc‡w%H~?¤?¤´zHª«ÛFH³›V†£t=(ŠòôFC õ™¿›T®¥§‚r·‘évè²´#1ý*_vÊ9sª)®¯˜–{~:¶6ø®Zê^"!HYÅ. íV%‚òèÈ—G)©aø‘´}û>p›Ì÷ŽN Q`Ã&H‹XbŸkÃeWÌÖ+ì‰f”áëŒl@Ö†¥Ÿ‘ ¥"ÅŽœyc^Ïj¿ Á¡±;“-Ý2To¿É°5,Ðï§Ëµav UÏ·p7Hº³ØÅËV.ÉôæáÃäÚ‘DM¿Xo0*›ŠÎ;t9¬Ù@ %ô`¿ë¥Œº}µçóÿù!ç7ª‡çÁ;À°¦™¬ º'ëè>EöH¤cÇO*3ÚP4gCM5ZItC‡1êqHÅ©Ü#?¤\EÕÿ9 ¤è¶#Az±±ù즔‡mm- €ê°cWjjnq@¾ð`VRBô»AL·[AYð˜‡é)J1ù÷RS‡q×™V”Ó;¢Â7¸Õí^ ÉEë8 DþU’ôñ®OÓŽdä†) ûü¡´½¤YŽoy…R¹5®AÒ¼ŸÌvÛ‰kÁb_)K ’).œ~“¯)<¯5DÄñ^º@ É`ÚŸÉ7U˜KÊNÔíV/w—»ˆ¹ëa>á–Ða„göÝó eЈæ–ï²Å æ©ý$é?@‘é½¥OHh" °GRœh Ñù‹&Ì?uꊢ‰b¥Ø[zbT8év+V­Ícå }GQ r²¬«Õ)7ä¯bÃ6èAzõUÒÆ°ØD Ò3e¤´ø8žÇ~¼ý£?HÍnïŒÜünáH¼š›qHc ©éÝÅ-¤·åq¬¿^"i‘ôׂ¥¤»lN$(± Pù,Y‰šVä n;…]‘ƒ ½±|ùßfƒÎó¾J@båHþ~Ë5Í'„}´HfÆÎÎBB(Ë„'tà.bdá³ æ³äoG?ñäs¿@P—Ï÷*Ês•£±±±ÞŠÞ±1‡ªß=S”ÑáÞkŸG¿\»fÕÒ¯¾*”1içÅê° ï K¨Ý+ž? ¤C_ä,mm}Ò®¾ƒ ã„0U4¤6yr@ÊЂô_^ÒŠ Ò† ãˆTRÜÆió‚Í@âNý|ótåî­–v‡"½TyÒí|WÑ!jvYcâ}Åy"D’Å*¾¯ å´Ñ$†Ñ«,‹B†ÚS`Ãvê8A íÚÉžßËÓ„Òh?zd/—H[çé7¿Ù‚é­·UÕn½¤yš-óÜë’1–Îc½}HíûêÚ•òKr= Q÷‹Þ ½á‡¨íÜo+ ”lˆ Y 邾_ã-rãùæ ­Ñ€4ƒz2‚ä½h¾ì`éÉËâãó)¿wí&ø‹½dDÂ>¥Œ4ÕTUTTTÕðC¶Ç=¬*Î>¼ïÃw½vÍò¥ Ù!,/¸ÏëìS¤‡—Ì5w zivq¸‘„‹®ã¤F)ã¾Ë\@ªÈÿr¯éõÿ27H+~ü똔ü©é‹—¤ˆæçÓ¸Ry"óù13¹hoQ„Ðî?nÿ×mØJŒ@)ÿÔYö'Ш!Hšè:Òo8GR³S½ßëEçË/Â@ÂÒ'q’Ô%ó¤ t•Q(Fp”1%³ R;#󬩆èàõÒ›±&kÞ … ¶ûSdƒô4¼Æ›/ñ®Ëî*pa`¨’‘ñ_=å1Äæx¬±Œ@âF‹/oORd)„¾ƒ Í(ãÈ‘Ýn/(®j Œ¬<Ïj2D @¢Ô OéÀðÔÄy‰°>O}ÍÏ«vs€ô§Ï¤‘4§D ’ƒ”ñ¿¤_þ Žº€Ä„ĸñ<–L¤\,*vð³dæ¢H"ù2Ãw·B[9À׬CÍ΄šÝ¤ŠYIN–ÅlN:M ¦G⎨ð ´0´Ö‘ZCKŸh@*£À†?nÿ@2 Hë@富ôálô›ßÈ$ÁÑÛ"@@ZÍ%Ò¼qD ÁC™mòì&Çšd`%XÉ;̱È;ƒ‹T,Ó1ÐíÈoçÄl- «ç³tHÚ¢ù¯ñ¾Ë‹x=H °ÒÉ¡QqÆAü‚f¥Çì¿%’jyq/3’æ- ­ õO HU…v[¶Åb³ÖŠWad% JÑ!éM%û¨ë˜'oàç¥öòž~,“Â}:’¤ÿ6¤Ý<# û #i6HÇgƒ„Ά} ¤a½œ[\€Äœ t {tn„’§HÒi7 Ö1}RÆÁƒ>ûÜ”Hâ M«FRIëØilV¹zypÔâkzjj¢éóŸÜËC„´îoVo¥/ÜÀ˜dUAºAB»vnÿWl%F¾†ä´#`"©îTíöîÚnxßbÇ…Rh¨ ýfÞorÿã¯.˜»>ÖÚZÄóú´ í‰'Ð-@®Ó~¶HgDìB‘ÍlŠ ÿ5f"áanfÍ—)3@ZK ‰ÖËp½Ær(1G»XåbR÷‘I†¥“cã;(dë‘C„Öú.aŽVlÿ“ Ôt9ÙŠKoßxyè æt‹­ \RÑ¿›Ik]¼[z2X„bÇt9/OQ!BÄÞ}9®ºÝÜHRqé@Ž»©dH?ÿ¯€Å*íõ2…ìØéÃz¾4·Yµf-Èþë.æ §"úGçÄÈ}Ȫ H$‘Ž‘@úSÞ5#;7¿4)¤t X»BNÑ øŽc†ŠŒÍb6%Œçe8ŠÆ Õ5Xº¯¥üòöö^IçöRÞ]¡FÕœé#v"Ë} ¥Ü Æ–íݽ}Û,þå_tZÞoH(ý*8˜‘„i@¬äòšïÒ_‚yÊüÈ1—Ã@z®ü5ÇÂ@Âö(ê"%Äâ<2’6ø­E*Ý^ƒòsFD¹äa J$°‘–Àsc%ð%ÞÂZb#Aˆ´ÅQ–.ËÇS(""ã ‡ÊðÍøØ¨`?*U)äè1 H$yÎ_¼ L0’@.NK™™g5§¦&›­yE (÷MŸÅóYùVÖÜ’w<÷gI^ó„¹äZ³á‡ƒÄ‚ð$I'w`Ý’ 5º‚ô9‚´ X­ú¹z­šu¹¾áúaÑ ï¿jÞ¥®Ûð˰߱¶.‰#ÎÇÜéëëÓñ<ш‹D:†é³ý{a9p$㘽°”&_‘F’%àÙiì |ìi¤ôÔÄ„8ŠÿNeÙék}WyszØ#®\¹Ò×—•ý6¥gô 0Ô.c?Ènç)æ ‘$®=råÏ Hø¿[ßu/†;óXbßÛ<ü{XI~h%y³TV*HÇÊ;Jаêã╼ôIÞ0÷nP¨€D‹ç£neHÉ<[ËÌ„8¤Î³‚ÐÙàLÖ y!§g$e(‘üW/Ǫw¬Ž—/µôó$Í_H-0½—/YæóÖgÀ­ðÁ7)‡":6.Þhü ÛÃÑmãCÀ´äQA°yDƒç<*ߊú§ Òh‘-=Åd2¥XrŠFUñ¸ð@ß% ð7™ÀÑØ]"RˆµõãfØ÷t5 ¸‘ÔØ0H±Å!Aúõ†€e¿îþaæú¸àUXMÈ„xUÒ£ý$CF )í»ð¯¿&’Q¯C.7÷¸›úùB¡ËUK‰Q5±ãÂû=&R HÇs5 ÊÈþº¸”>ÁQðt¸ r&hst\ª%Ú”Ø$c\\å#ÅFG„o†Úk Àø‘º°ÞÛ†€ÍᱨÙnøL‚”›aÙ«–,þx×§‡³íÅ¥µ+èŽÀ¢ïÿVÃaôÛ÷ß§jwô_Ào4¶…GÜú¯Åܾ7¼aÞa™®\¹d)_ÄÑ‚¬ÁX™m™*H6jGg7óq# † )ÂýUiÆÀÕ@†äÌBzž $Ù@làƒû¼éç·ûcb‡ZPo¯þ)½ïï¿ùæÚ ¬·Ldl\`Jií+iXì½Ä‡]Ë|¼—Е×÷˜¿È{5j§Ö¼^$ Éh4šÌ™*H½ÜD&sy{ñ€ ^Zežó€{±6ÌT–Ò%Ïü•R©A Rƒ Rµ¤±Rí¶ÇlùõF\66º¹®?³6oÆÿÞ€|iD¿¯{Ef·÷bÄêQD‚]‹çˆ7AR»ÄòÒù -m$=xt»Ïþ$@Ê #©ÔFº]Ðæ02‘ÈÕ+Âud™M qјÚ¹²,Ù| Õ¾óâ]ì 2‚@Ê+öHMi¾ëÚ%ïbÝû>ÞÅ} ¥òŸÈHÛÍÜ߼РR´uëûlû‚óð?†?xÿ}ùº%x뾇ûŸ•K³.\ #o– IlÜ’Ù¸Aò„ÝM(i”@šáá`$#C‚ƒBÑù]ŠºîsEŠø³ëùãûâX|–Q¯gÐø|xiÿ@õ ¡M‘±”Ö‰òª…Þ¬ºžßÚµpV\Š¢`íXîÁ×šÉ YóT@šbCü}ñXÊ“‡/xò„A~nÄÐÇ@’$°Ê_4b_ CŠ€8.{Ów«Šƒ\â¤øÙÉ"%’hÔìÉ»·°¼XQÍŽÇOx½¼Dú ÒÝTÝ ÷ÚUÓîN&’ H_bÍìß¡l‰ŽŽÙ±#®tQ5¼¨¶AT¼Bÿf|xûl ¢$•>Â(?M÷ÀO‚<Ú•v8#Wí[É)³Š{Ž8FÕj11ºP i@êê Ð.’æ Œ$#þý¨`"²÷4 qÕŽxŹ#*=`4&&&šðJúú©¢ ”TÖ© Õ”Âèí0òŒŒ/&a ÝÙ& ØÄª:좪 Œ$G”¸´7ÍbÁ¾;-iiŒ%¼L;ñm7bbc"iÂq)GñN|>ä?󦵉[pT'®¤|µyx‘w\’4’c$,êØø+×u5 ¥ÄG††¡¸Á"]ˆÆN£Á&Opàzìšî»šuÚÜkÀ^¦=&v¥:åúL;ñq‘L"aýc?D>”l'.«ø“P t)ÅDÊQAâÁªò¿øa,ï~îÁΑfyí\AzM‚ô–é£í$^lÕ-H ,y‡B?;†ô)í“cÈVØ‘˜ª»Õ+Aý§,>„i¦¸µ‡£~·m#lÑq;ù-à“ÛMŸ#GÇ©Ý^MM¦ven®‹^Ç[[666Àÿ žP ujAbB6IJƒÝO§TžiŒ$ZTœ1)«‚/)$r6ÀsÇÆÅFãðIƒEƒ(d<˜ÙœN—ÅÚ£L)Êúz RÚ°„f^FÆ‘Œ Hü8>ÑÚtöÔפÛaE.¬S¼¾/ÃG„|<ûX®=÷XvÆ¡Ãi{“±^2\ûàJÞmÚ™€¢)ÁÈ®ø¸¨ð`\+½Ñâà…{Gõ(fÓ»¤eñ=^»Ù ýa.´6RGyVÓÅ]R~6l›ÐΣôôs7¯¶dY,éò:žW*þó«ó=7O' Ìà…ìó===çÓ÷JL¿Û ÅÑŽx;ëüÍó'ÓÓñ£ûö¶€Ä8Uv¶úÊíÇ{oáxxÁ½\éöV[òCÝ?¾ÝÒ€ |@?»K EåX!Hxô'HÂHºž9«;Í,àtZ³ÿf¥'q÷7/¶9 küÆ%š’ÒO_ëé}Ž.ôŒÁb¼Ó<Îí‘& éliYcwïÕŠüü\~Öu¶©E†Õ¶2Ý)>ÜFÑ ‚#ØQ2ríù¯Â¨°çfgX,¨îßoA1õçc–ý)É0e¸ŽO^ë¹öõc<ž¯E]É×w-~„•–n¿xåFGs³HæÁßxÖ‰•TAFR£ j\l\‚i¿í1‹é“ZÒÀhˆ›'Ö°Ó”b¶Ðœ4'›°N©|$ éMíôäô*Óð'º­Vë~s’É ’Çßµ:Œw0%™OŸë9ÿg3P†OBá#è8YJÉòR EraËÓ™UÄ,&O/y¦ÄƒX]2ûfȺ‚Ät»ï‰6p¶¹ã‚ì),(‘²Ó’?A 0ºÆ‹º _…é±ÁUÜ ¯ ÷dáe³õRÁ«ÁÓŒ2‹E|Üñè«ôT,¹HÖhu;à^7Ù›ÎÁsøQKvö×` ]àbÕÜpËå IŽºÇXU>çȺpØ÷t µ€HyöÅn@šFÚ®;vSÓmä˜Ð€dK§ÙóƒŠóŒC  °Æ’N>âÃî. ¿Óie¼Y`ÒßÚÒÔÔte„>3ÚXœük9¤Vù‰ö–Š¶Û½R’@"½ÿãj¿:ðWöF»%Tó@HÙ® Âc[-˜rhµ=¦I>¹‡J  ÙÁ#À<âÓ«ŒKXð7‚äåÉÝß*HÒHʱ&Aàa¥òÉÒ€d6âbA‚ )Åô˜÷Qʇ„n7¥¨Ós—¦=ðÏ”á¼;üÒhÏÉX PBì°Þ×9þqÇ#Ë#+TDfÒ"·)e¶t7ÇJ -a‰åðÉn±j–KØ`EÀu͘g?yíou ýÖ H{ÝÔ‰$ÁÞÞØØ ²KRþ±Ãûv™>1¥¦ž¤’VSÔ˜µ®+ôeÁ£Å£ð}`…4g… ÃONOOÜ׀¤äZÀb¦©éTQib [›*=6[¶Ýž_\V3ð”ÕYÃ?ñ×îNR®Z'ŸqtcŒ—cÃ;?l£ †Ý'è>Ñí[Õ¤Û‘ ÙÉŸ+×K€þ†êh’¥˜¹z%HW0²!)Õtžƒ€‡IQ Õ%¦š{°8ÑUƒSÆnTV29£(*Híí—Zï?ÅÊð„c§ŠY>TK—ˆê¿º]ÅÕc!¾íý÷ñìhã(Û~çub’ýT³Û+D1GçE{N^^^õÞçûÏIŒ$ôã1ëˆM/ë¡+A¢ò>KæÃÎ Öô›:ä!5Òbƒk…î-ØD ;Mæ¯5#ÌMÞiˆ$A¤TJíô8nUVVr[«´ˆ×ç|vRxH–¹ƒ?ÔCCƱ:΃͊vŸ¥`$q,9w]m$WgCNºH,¼Ûƒ×&bT 7ž/ÍórM5Å]3fi½+Hÿ¦éS· uu0’pcïèêúV‚ÔRS}ŒQ7O¿¦kÌ:Ör—ó˜3Ê@UUÕO£ŸR†«‚‹Nùñç°š€$ª^‡â¨æî™ÈgP ‹ ‹KKaýÈÊs0­Ý\&åÓéu ·ÆtwF’:::ûÇŸÊЩ À^R@â+^I…@ i¡YW™«Wµ lY ÕïsàŸbo&èaHLú ‘(g„ÒßÉ ÷O(]¤ëׯ÷ rH*…ëlÍ­§«#|x_ºåš¢©ß34¢ró_Wׯ¨yŒ ­œ2”š} Á¥’c8zäÒäu ´´ªæŠCÑ_šVžÞ`Ùydc0Ï8Ê£[OµõoàÎý}O¨t,[¹¬Á„2J~;tš8B1B $§\âWr€–¤Tsº»zå²ay}h'ó.]IXæÎ”j>9ì2L¼˜‡¹ýö”Ò÷—ë]ê#Â\Õœ=[S3"‡(@šP†Y%¡€#É£cöíDãìô––6>U7™ ¥­®®Å!?5…ÅÑâ aõ¼uÓûÝ´¦`q¨v°šæ-fÎKi\R×äXÍf0¿0Hïm:$›ÞIN±~NW,fD=I²YáQ÷g Y¥‚”cµ˜ÍéÈÑ3—fZÍèëþiÁÀMvŠ×·û ÜÊœdÚ‰>IÈ‘K½AóÎ8,ë½hÁ"· 1ÕΤt!‘¼Xˆ·‡Z4ÈC+Qè+ áRÒ½Ä@ å ý@ÒI˜GAåO$Hn.=HÙÙÙŬ欩ÝMiËtvލ\jS7¿™çêGX}+’m8Ϧ&§eM‹K—øÊ£Bü l¢L"É®vklåSÿ\­š:ÞõD™•êa íuÉ©IyTâ„ÕášF1+AÊCCO‚t7Ëjଽ\}›˜E.L‚ ÒvL)›°ÆÀŒ›žæµí@ íÞgɰóML†i)€ºkoé…pÿåÚ"‰ÓÊc7èl‹×p43©(š¹Äl¢( þ†%ªÝZ¬¢~‘e«FRQN¦=\×í— •Ø3­0WÝr„ªˆì’ð²öˆ7瞸 ½=¦=)f¾+áÓŠqÞ4ñ¶‚èl@¯]úK€¤9Gâ®nOž"+ê4ðެl±‡—K•U7 ­fΆ·Y…HÒ¿Q[.’>›%‘¸š!/°âŸË”íš²âã_ÛíƒÄÍ´ª?}¿ýL`5¡h¿X˜ý~¡ähÕ¿ Úã³8“Sê½à½ö{ì˜ÐÍ‚¿ÏJåÊêÞÕ ½ê§änÛç"%äŸ+¼9ÇôÌH§&–¢D¡g6[ºÅ„I n•„½×ÁHZÀArc#ÅÏáþFϾ';†e¦’§ŒµãÿMbÉÓµ…¬;~ʽv¶ëD’ÌìÎ7Eaeš(€Tqª8ßÞCO óÓç)ªIÍ—Ó´fî{*¨x"çsJ(,(ÈÉ)$a5¥<í»óT*ã¼Ë“¾{}¼õ+|yI"åò±(þÊ •¢6!Æ9Žî± þÂ̺Œfžõƒh#üSÚ§{™D*ã õËs“LòäpWo» R!”“§ÖàÉËÃ#‘A^\üiW³œ ¾ë(ºD²Ñ½!ý*Rœ­-÷òQòe/ôèõ`l‚ôáGèi°Ó/*Êø=¸‡<&mªS&4šB¿~çP”b+¨K¨[ÙbÄÎþöææ{\Ðk@ |žéiH5’ŠàIm9y\×íÒ‚„{ËáHW×êµaò;‡í·Ï”§÷›»žŠm’{eúºÔš¯6ÂÎNL¢7æ^׸¬ç‚ Q"ï~Žt÷{B„t É^,F<§‚Ukp±‘Üä#1¨Ôê[l÷þÿÅIÚõÇÝšb˜üLË ô[IYRH„A•H÷ù71¥ÜÆÉË»Áu]¹úGË$V,{N\5ÓÍe3À//0âš;åFÀ§§S‚3ž ×cÚRa¨@¼º…aÙ3çâE‹¤ÈÙ Í ’ÎjJËK{pSÉÃKñóX//× ‘n%¯YL ýV€ôo»þÈ"Àÿ_Ì(ø$ÒK€4©Üo£Nw¥½ôÑ)˜ ¸œ¤~$A€4Å´ò Êò”M¶Ò‰ó)Ĩ ••è%Ÿ–sÝ.kØöqò·ß|#;V‚ËK)«}[þ*[-ß»wOèD8Ê IDAT7Sʷߎ÷÷õIïIÿ·CcïR¡ƒöHûu 5Ë“¤ñÊD†»zû›µ ¯a!r¯”(Ý.FêëaøC2D6•;Í$…o$èüÅÖÖŽû÷»ÆyºªóÞýþÞÞ«‡˜÷;M¤] Ùõp ºÿò_þrýú¸øã·.45u\¸¤žBÑ?Ð÷?!×èVÒ­˜ÑÑTY‡ƒìc¶­ìEÁÒ‘Xep—HÓJ_»ªNŸ’ XŽâ¤”²vWŒ—B3«È‰®í¢¢¢Â¢.Æqvš…ØDEo¼¯Y€4­ô‘x/s²eâh©¯¿P/ö4V G‰´h©{‰4§ûÏ‘DQ"t0ð^ÏjDhÕ†½âÖkG ý=U?áºÝûøƒð€s”Ðm7K"=ŸëÙ´¼ž³gºßÚ†îJLqÚÎ&oÆ HÏ…(;³Ÿ ¤þººzu>§•¡ººº6±çÒ!¿¾!Ž×= H /séèQ^—EÍZ-÷Ë`KúY‚äÀóX°î(ÙÈa)Wi\ê”u@RQÑsõvjT;\M' eŃ3å%%%Ul™TîГµ?Õì)Ížàt<'Åö¹éÒ¥öë"o¼ýRSi¡*v¶aöi˜{ŠûÛ™Hš’¾Š¦ ¤IEèÁN‡è¿s´.fÙ0¤Ü8s¦PØŸ© ²¡Ò< R<ž†NHÍN™îXwæ P!t]-H ’òÄ67KyG…|p~D3ûõ´ÑÈò´« =,"&øæù°¯q1Ó‡©¶Þ2 ÒË:ÈýíÉe÷{yŠÖbÜ™ÇbVgUšC"ý7êG"é÷Û(Üî£ÿ½]ÀN’>ì’ŒñvsÍZ±gd7_¾brÒÛ‰Ûam+OØñž¢´Ð ]z"NûžÈ]|•D¤ eœÿÊ7ß I¡ÐK ÃJ(˜M^ÓÀ ωVË×ÑŸº€ÔÑŠ¹Šù¹G¿<ò§OHG´ ©FRG]e9䄸®9H' Šdª^eå™3åwD‘þ&|°úfî´˜áÛ‚‰þ4ÜëC2ùýRë¥Ööv!jÇ/5Õbã—Ow£Ëヶjwè˜&4@º'%Ð-H ·XÇÛåBæ›|…XÊyr¦R£UAš[5™Qñ{TT#éÎÈ–œ5~Ù$—xº­ØÀù¹%ž¿…M¿'N¬$غ@¼ÿ•m7Ϊ3xÉ™ÞC6Òʹ$RÊ l$‰ëq²}9¯˜'Šž°2ßSEh‚´šƒºÝ;t$ËeJ ÛÌ8–J’Ó1666>×зß~ëЀ„ÊÝZ¦ }#ðîþ;LÔ=SÆ@‘«‚m±_Ú‘ Ñž°®ãljUD3ò{Ò~ƒ§W0°«<‚DÊ/«®¹%,)ìlÙ‰!BuêÓ‚4ÞAå\ÁŒÚû))´XýÄNz .û.©uôÕ×UVÝf®Þ! K4 Á5Àõ¤1ú©®¾O>™ HÏðvO0¼aJ€„W»ð[c¡C{Æ~âè=а©ÍÞCÙ2¦µ½ý²¤ÚZ IiškÎÚM>O*^0½5ãVR^Y'=eXü@òF¯«ziÐH¤>©¦Þ9ÓÍtÝ> H%’|ÒÞ.‹“Té¿Î:œºú.~¢Ÿø0ˆ÷¢¢ÛÜÜ(G€a…Mnç»Dž#½¬ÄƒVyÃXá¼Ó¤S°’ú<öNÇ‘|~úÓŸ!I¿úÕ¯¤w~ûûßo#0-éÿ‘Húì&=« ªPk[KCChò°>µ µ¶41mŒ ²!U,úùÊbÁT]ŒŽ!îÞîÆ]±¤ª²¯ÎIå^ó>{#ôI^~ü¹8…M–T;ŒÑ —¹ì°èð1¬îÝð˜+èØYê’6Ö·]+l|IІ!GØI|?iškjN3¬áêÕtâĉB)‘œ¸PXÐþ¬„rØïÕÓãHÅ‘ú® ¤–––K—Ä2k:[šŸmÁÙVódÛG¦dKSa>ºË—¯KÕT[[[§é9mÒÈd›ü JÓ‹ºSAQQy›ê)³`ɆÕK0A–ƒ¿']‚Ô)ðþæ uuc\×Õ„úؘ°ò.ivB’Ì"á še]«Ü';5 L>^%%#\?½AŠ^…Ø„®‰DõõÌe#Å¿à‰÷ra•<½ø’¨jçÁsü¾¯fü× €¤·ÿ‰ü ¿ÿ%’JÒŸ=®‰8âQÔH’ R’ôP,_š:X†b¿YƒƒT9®“k®’’J{B@Fš]ûáÏb¿ƒ^©ƒ‚iœ½Õd }‘qè î±gß":AðjíÉåpƒ.MˆàP+;Hþ‚ä&ó|ºk6Øù²µÄ!]Ë0Ž:UélVK£$‘ JDŃq|²&uÙº…«Ón*ªaÏ2Ư_¿®‚V$ÉÅÎ~hï?Ú¶õwB·„¾óIÉiƒÂIxAR÷ûZ¼THAuè2îñEâ\æùîóŠJn‹ok€I¤ÕÞ‹À ŸÏ@2îId³OúØ}/ñ°†Ë. ÙmƒÂ½p§^û÷¾úz±MŒžÁé)I…³@ê =ÙÉ=|è6µY³1nO,(þ–¢VçRíÜ8ÔÈVÆNT)öä½]D-¼êéùâ!iI$’ÞbÊÄ…¢„]0Ó~¡ ˆ Ž(™®¦¡HWAB’FøÑ .þö99}¦Èì©#‘Þ*| tl”SpBèGˆâùóÆ…@RîIÃëéƒoº@ÖÜyÜÝHiǾ8täÏE”Îç[(>[@*ˆÍÀ*¡Ù„ =ßi ÖíF1Œ阔HªqÇFÒÉ]½zN¨ªI«äŸ@Õ©R¿R$Hd§÷ýEÒí&¼$HKK©ôÉ[ßùÍ–·ß~+ô­Ûð@¶GØ& ч—i“åXä®¶‡'fšcí“XcФ>iov!UL×ÕJ$’™6 "„Þ™’Ιp¢@7=õp=ž—œÌ‡ü$¡—baMFãá›nžÆDô@?ÐìDbßË­FÊ!Ø';@î‘êšÇüÎŽô%Ö4h‹\º€„MS€yôÇÉ0ü4í$õÜdˆëy¤žéLëBH"5|\ýîù1.\Eª/âl+õ}ª|žD é/ª³tKÓ? = ÛÞ ¥êø¿z{Ë;¨Ûíϸʼ¦OÛP"i@*/’„WlBžéu ‡ø-Üä[„¥Ù—“iÍ´å”ÊÇêWí|°ö"€ä`æVW³ÆHz±ª =ì<)ª¥^ë?ès =5Eå£b!uÔ×Ëé#¬Ð ›Yyæ£!.22 ëO„S þÑß/£ÚY´å¸ØÙWä¼¼´Ù²¡8Ûê.µ:Õ¶Šãz ÎÊLñÏŽd j'KðŒ!G¨‡6Üv ÒãøÝ£ £û$»¤f5‡AÅ\ØZŠ e.H$© ‘¿W+Ÿ×¯···K¯f º jUð ÉdØúö¦M¿Ø„Ç~ïl5ìÝÎ~uÓƒÛ.DRÃ[µå¥åå¤iîB½P¯š8€;⛪F²þ=¯è¶ÜøÄbCÕÎs+Ù Ñó1ÍT=¤f.!æÒ€”Çê*†eìqK:íÜôLaîm ùÃ++ŠÉhFù$ó¤r¬ºJ‘‚©$’7æùHé?DµcùH¢a¹óôÒ¤$ñX]`ƒkˆÐ¼ù —s‘Dw Ü…"J¨Þ½ÿ{¦áLÂàÈüR$ʃ=~,ã_¯Mz†øò˜˜VsÅœWaÇ)¬ M‘_«ª<ÆR}qûü³‰Éé5EðJUUm“¬‡ù]­TrÒ,]>~ª¢ÆÉï<‚a•Ù[Фjõ@-\É4ü#KàÎgŽ $õÜ„‰)åáHHRQ‰Î+õPX•#({‹JJÊeÛ0r÷ösm…ÝåöK—.IÈÈÑth¤-TÏÐA·ûÈ´Û,”6ÔÖ]èìwŠ˜'© $’ Ò sõ¨Ê&Ivp9²#33'¯h@¶öº‹%«Â©aÃO~²@„ÚiAR©™®;Éã \A‚ûÖ”WÖuhF8I[©•s#Ix ‚táB}«ù•LK&˜‡ð*}~~¾Ë¼bA®?"²ÁK #‘ÜÇò&˜˜òä‡Kú< —YOéo—!I«ö÷ëQ(múYJÿÄ‹Jÿ–¥ú}œœ–¡ýÄuWQŒÕ aÅ9ŠŽq¤ë×e Š6Uk¸{ÔVÕré3M!Ê¥…x©ý l6Éf+¨’ûº6$Á{ Ã/•ÖÞQ[îRß 4sr£OwïÕ¥©ŽöXtgk܃tö”=Ãò)†ƒnÃë£í»?µ D˶ûR±ì£8X‹.¨ËÖBIaaI¹¤;2˜¶®Š–¶|°±ÚÚºº Ì^gòù`tá‚{@÷*´Yv³åWÿèï¿~½¿ÿ/6½õNŒÁ”lɪjûÇx@¾èSN urûœ™v ÕÝ¥Ä;µîgZóÈQ9º#ŸkËúú-[8Ÿª!l‰Ž7™k@ê“þ—.®ë^ª»0$Ô‹ÊŽóNI»J€Ò¸²œÎƒÏŒ©þp¸TÔ£‚ ¥:Ël2Da]¡7ߤ±X•JD.ø!²2DÈC-Òà)*ð"ú¬i’——‡ë¬^"aSŒ×I ÝýÔïïFÑ« &M¡¡дí£Ý{3rejZwùÙ‡?ÅÊž{¢¬ÒJ¤ë×ê ާ;Êð5[vv¶¾rÒsLæ«*-D&nHŠíÙxÙ‹KÝ4ƒ”Ñ«€¤=¿°´F›©†Ý«¯œ=uüØ‘´ä]»¨%ʱü2yg8TH’ ñ¤Iê—‡Ql†ޡ⥘3—‘ ]1ŠNba ÔØøê”’ÂrHnIEòB]]mUU,÷¸0.¶Ì ¹t¡©¶iÈ-H° ãa¬aë–Më†×zÿMoGl#‰@¢Ú S3š à0‰-¾™I¼a+¹HåÚleé‚lbVD‘êyE4c3*H¡~Ë$êêoJ?Çc0:Ui,2}Ÿ¡:[/|R^&HEݳFˆŠ¦zªÖTI¾J¹ó}@’Üb°~`\Dˆ?+¾ì-¤b\¢ŸôlÕÎͬUªv¢Ä*á4ÏÓCÓ#‰×sp)Úà餅‹ÿöo—3”ð"c `BƒCo‡†¾ƒÕê/¤^¾î°ùHZ†ö%³%’Xq´ðçp7ãá M‘'­ (’xUXÎ…v;N‰íYTÝî5pÕn£[Ù‹ËjúTÁ±ÆSöìÃi¬:brZö×Å*¢¥eÅùö|,—"îüÚ/»ÁoR¯÷¨k׿P×CÙ,B}Ñh¨Áe<¬aH¥¥z4Œw.\À$7@;• Íûú’# «Ö¢C³ê† RfšiÛ;Á?óóó[í÷³õÁ¡`$HyÕ`‚è“L $P”ªØJœ™Æ^¨ƒµZ©[›ÒýÜKG§y…å¨ÙÍH(h›{-X¼œ¼ß)–s<¶ -žËê!5Óuï«vŸÆÙ`Ë)™=ÂÎf 2 ŽÐ8?&•[( Êç)“j’ø.[²d‰·÷âŬ·æzÌéµ›û‰‡v³h OOÞK“Žg¹8òÔG¿2;DˆŒ¤×.\¼ .b‰ÃäÈ5ñM›‚ƒCQ‡^Jåé(ìÙi¦„bbpOÌÎ×€ÔÞ.@b‘D#ÝQÎdX2´ ¡ñ묭Á‚Ol¶Ul·ç’¸qišܺ]J÷Ê€{åÂÍnR÷ˆa>’èp)ÒÌE=!~Ž$:¾èU»WfƒôõÖ@©„(ýtõj? 4q¼ÿñ›¶¼÷Áö}öB-HX–xëÖ˜w¥eÛUîµ··wɵz¯£…jZgg>lÉЀ„ÆïHmUYqq™¤[l¹ç"úðüǽVŒ6ËÏŽ,‡%;#iBKÒ0(œû¶'|óar¯¡ØG”n-´=Hð‹ÉÛ?xo uÚLO uó€Ù[M’\ÆÂZ›.4i$R-ÉZÝ‘¨ðˆ OŽ| ,†ÃÅÐ;ݤn+°Å“ÇZ€4:¤ä„˜-Ø¡|¹ïj¿õ›"`3[+œÒϦÉ^ªBJ8HtÂs«ŽîØ«]›÷e(vIQ!zŸÉ§©hÛ„m ðÅö+K}ý6a6’õ.¢¬D’ÔCj¦ëÖ¡E©„âbóÊœRTD=&tĩΎïxš@½L:9QP0H ‘(obébjE»ˆZfΛç!%Òì"ú/þo/oç)ýÞ^<ôÛËëŪ'µ™z±´XZF}ž'Ù®&À”ˆ˜„d‹$~­¿Û²%:œ|‡tOºÜ®cQCê²pøð—Xµ†»%ž£ ©¬¬L:§”‡dÉ€þU*UßÊ\ÇJ£ÅXüðða¬}˜KÀéê)J·Í²Š¼Æ$|€« Øm¶l›Tñ þòe Ò{ö>Ø~ÂÉvà|âl»X6­M-­íªI0Á“Á›°°‚ ]$t_7uËCË!ø{b–––Jf(¡ŠÌqž;Zî̸! ŸeËaÏóõ[™j± åÉ a \"±ìº¡ ÔhK`®’׬¼ -´E×6à§Þ ±92ü=ØÖ3 *É—"©™®[‡ñr¤1ý ëwR"sŽHêQ•»-C)”²õ)÷pwãï(êu R!k7ãGiá"j›$Áj–²ßÛ"Ǫ‹µ“MbeçXÖ‰;ת?GréÖöYz·A[Î.ÖÉÉ×÷ï`ïƒue0Yl*Hö ó'1¿ Û_¦M©K=^GZ9H¼”u™t”£Ý€eÕ5g`MYY1FKÜëçi¨@ª(>~,ãËŒŒ/¿dÊ]aiˈ&ÓZb¢#"àÈú²ÙíW„ì ’¢TÐïm þ%Û>Ö†E'|rX¶pSS‹ˆÄf=M{šô =†7kkÛx5ýå/—ÇùŽ;ZZj`­œ Q•l-‹óÜQJ×ÑDM»|—{/Áf(~þÁ¨j=–ºïH—4Z$r'—«ëÆëà†E*¨ã&•Û̥؄fà´*‘¤ÿÕË—À»:0d«a÷þÌaž½R^^©š€Ó,¬áve¹j…¡ç ÄbÁ‰^9ÂñûÍjd=™lÂ/¥`”ÊÄ‚9øw× HxŽd1ÅGøúx/]•;ÖËsî¶..©["ä)À<5yí<5~ˆÕl`"Ie‰zN˶‚ÞËa©7üÖb+7s¦È”r׎ë.,(0ÏÒ³ ÜDéÒ-ä“>–‘ÄO­iǨµSs¶¢¢´Fw<…/U×ÔH]ùšèÐÚ@P#)AB’jjìXE¹ˆ‹7‚¿+ï\X@µ\å ©©À–n2D‡ýÒÏw \¾~¿‘d‘6Ê— ‚ÔÚ.ÏMh‹ïhr©‰dMØ¥jz÷†¤@B~© Ž–ry ”hbJõ åXS Q!ëWSƒ®%`²„° ´g(§¿Y Û¨¤C- z QÙ+TA‚Ç¿Ü'OØëêЋدLŠfØ8-3ňË}–@)Ò˜l±9xÞx9‘$+ ®‹¬JWæŸ)))ªqò¿HG°]r„ÀQyù™ÍôtªÓ“I¾¾‚9@Ê£°ØÀg¥u[DÞoTíÜ6‹Ÿ«?Ò’ù󸻎%ïÉôr|Á“·6çÑá/¨Ù d’‡Ú’—õaµt•olÐûDsQ¶=¦ÆEoÞ¸aC`¼“¥¾ÓÕÜ|YM›èÄ ê²bì‘û5ÊóP˜võ*#@Yj£@žš Z·š6P6œÅ Tq¡ï °°¼ªC-£ ˆ[R¢Ãð{…)]€Ó)Aêgù—\<ª‰qáA|Wyc·WßµïÆ$¦g狤'©U˜“äõk¥X8¤FŽ H·ÔÒ>„™T†Kóó¤³Mr6”Z¬æ¾¤D²b×M؆Y/ìælØcy,”Ó~5…Îeô‰–Ô¹@"—"ù>f¤'¯Ûº„nòóE§.˜d±èÚàù]¤yVÖ‰CjÒuG*)„[©…΀ðÉöid S5KJδ8gMÏ”2H9ð™9yî@BQ™Iþï@jÙGíǰÑôüy,D=î%:öõªÆØA¬¨éÅ…oyéÁ{Rxz¹ÑeHº3&¸ 7¤^%°ð,Î/(bGªhwÍÚ´Áº \»n݆ÍÑ;R-9Z¤‚ŠÆäU°þ_¬Ùd«ÚjA:‹Á2;Ã!S3˜Ñù Cí4uûY|u®<~œœ,Q߯VyÄF`ïRá}c«*LÔò¢s9|“ÃRÕXVò¶aݪØH € "ÝVÀóªœM MM­­÷Õ*JÔB/‘Z[ZZ4Q¦ý ¬VY¯+ü±£Ng5F)—ž þ«oà ’F"åQI{Þ‹_g)ÒhÎrpDjö÷8T"¤‚ŠFÓœ õ5_ºpáÂ}E‘Í$`…¶ý Mnò_f³?ª÷æÌ2  S.œqF@×-׺ÚP{Ä9‘îâ$w‚C“±OXŠ ¢–ÍdÓ£œ6›SÌ«mŠr@$ÅRçZÿêX¶„É$)Ü¥ó {È.Y0Ÿ—…”>;áüöâÙ~¢«£W^q’æòâ£hþ«¯.Z¼ @ŠÓâr%+=1*$`ïº Az:5 ‘aC1ÙÇy1û†6U«’éŽL‹ìhmkkÁl~Ú¤|{O¦øõQßL$‡5ˆ>‰ü«½½W 1~ëñ îa+€Æ›¼ZÏI kKX¾ ï~¬Ë+c íˆÞ¼Á×ûuÄ+V­Û•˜n»È—Í‚ÔÔÚ¡žO(wZ\AÂL¬ÖV} P®¥A•«äŠÏ?UZ«ÄÐø@›Ñf9€êsCF6 ö¶¿ßÞK½Wú…‹ÚX<"pÒ":C‘<Ó@-|Hè ¹Ì,8EŽ ÈnM6¼è·ÚwµŸHD¼if£He w¦M}6EiÁwHZ;…ÌÞSEp=¨v˜Ú>2kz®™SL¦=fkVޤ>i{•äX÷P¯iÖÒÂE ‘¬d £]ºšï1MIVÛì®æX!‰Àò\¼ëo—Ä;Ë2gøYß Ò<v¸i{øúÄo”Í:’ÕhšMNÁŽ ïT H·erÄ$ ð„²üà Þ¸z/j¹×HA1W $YåIq¢hywsPIÊyàw»Šç݉Â[Ú™a[:´Aú ›c[Xã,óŠ9­O¤‹ßM „ª =ioo”ÚUPDÁ ]ía$løÝùöcð ÜÕʧ{XÄòT(جò†zÒƒ–•‹÷Yõ†Ïº”鲈^'Û³U×Y •ò›-ÇÐôÂÂ"y4¡‰ÿÔϲ-U™*ÔöX<ºZí™°­Î)~‚ ÷**¯“‘à ËY’­¤>áÏèb9žr„õU7 IDATuÊP–§¢›žkæ=;±yŸÙê Ò”$6ÏfI1b]ýˆÿIË@&asó…Øz&>%ó„ Òˆ/óº[‘êÚyX•‚5û–-¦(vìÊr%DÍÞrŒ"X=µ5^™F1'HÒ`b ùpFUÌÒÆuk|×ÂÆ8KµSÃ;ï£e2©¬ìÊ€c@Ól ¡†_Ô`IÂ5„!¯ð¡;|?åpOíÄF5  Vßèntb›åqI‚Ô,[3‚6øÒmêyheéJ5BY£ z‚¤¿ù›¿Õn=–]ê h¸¡š&Ë/·2sN&¿~ýzGkûu Hx]º® –éÓnæ0’$H“ʼœL›MôWv¨‰'r,{â£1‡ ®7ÁD2&Yum÷ÏÄ2Õ< [ø,tA’j±À×É\™Ð€Ät»`0Eü7¡¯ÁjëånÇ»`ÑáÁ“HîC …j^ž HC"ƒ·‹Õõ“#lf#Ô„_C“&ŽŒ=HjÕŒþJ,ÏŸbÚÿù#åÑÿÈ›_.¢’ QFsŽ ½…¿S’—eMÑš™W2®‚Äš±ScjãÌËCŠÐ:’?^ž¼…‡K¦ù+®­/¿$PðH ©¬¤5RºmXS'¨ùrŸ ï§Œ;Xr7P|Œ´u¨][o!HÔ˵Á!öMGWW'²$ªs uuKsyˆH‚›uÓ½2ùÀQôW‘jÆNP@ÀæpÐͲ†%†¸.«*yá4u•©¬Ô‚´v•÷"i 3ýì|Ù Ô4-÷EÔ¤r¿É Hj| {Tûu/:ÁZ^Œ4Ûa°£RZ.8¢L,¹Œë) ¿Cz-+KÈÆŽ¦N{ëÖ¢‰Ä“ƒ8H—"¼‡ÌJÓegnGv©ÐE"‰M¾ ;æD#¨*²ãÙÕ[¿Xï·ÞŸùì¢ÄÃ]Î¥Øú©’Õ[-I™¯­‹‹ÌçÓšYª«ÄêÖ°ÖLà wãbccãw¦Xm:.KóÚQW‰ÚÕü%Fð=¼>j-½˜÷bΑÈÀsž9•ü- Î84 ‰¬E $œ'’b=dA!žVá!BÃuuí<_qãµ{H^v¸€dŒ&Ö¹©KÚÐÊV$åʘ25ƒ3Õf“T’µŒºéÈKûtuv:x ­X‰Ž›³ žà^ àn=U@‰üýZŠUiaÎÃhì„mÜôntbRÖ]‰áÌ!¨¬Å&1–«ªj”WÃÌû#^ìD† ñôXOÏ·¾|!H?ùÉO´ ¦c“_ÛE)*l‚ÆD¢‰‰sÞ‰¦PÄ´Ý¢,hÉs¡'m ¬Á8ˆ¤‡2È…•qìã%!A@ÉbÄXÉ›2µÞ¢&-2­åqÔü@èîdìlØ@2åœ FÀÕ×µ§V")) ÜÍB/$€´‚@Zå Á2µKOpÍY¸À"’n±1:òª®©Q ¼¾o¾ùæúõožj‚b;»®³âø°‰Ü¿w¯ ›îš %Ùg¡Ëצ‚ëìÒ%Ñ1u%¶ìó7ÿ ÊÝÚ A˜÷¥ÖE3óÉÕ-\’¢Pñ`¤éz§ºã1ë¹òTÝ êÊ Š¿4mrýµN;r"Úè~Ej½Õ‚œLx„̼+rwh×ôìo¾ÏJSLË:Xr\Âí쬯DCpg\dDuæÓƒÔÙ/õ5>Z^”×˼ŒçQÝE¼ÉDÊU0m ì3sN”ˆÊ*èÛ)§ø'Œ×[@º,ÌÀ[$yq™ä¥jw?Ækç¤tU"CKdó†u«Ö¬ ‹K4 Š l]ïRþø½®Žû¤ÝàY Ò÷ H7ÔSÚ~VÅ‘‰~Véë™ü²¤~…åb ÛaL[ÐkR¹ŠDlÞ¸nÝÆÍDø_åÉ9ùµÆxìÜ]Jû–©¹wé)P‚„^}ã>ËEáë={¶¢ Iâ&Á”ò˜Y~gkn©¨~ó̓o|#×îõãXx—þ-‰+j¨ !D ֶδbÓ¥,µ \s爢 Õ;¥ýúÌ]LŒû [V…GŠtU\Î^)¯¼{pÓ,‚B¥ÀnËÌÈ´É`.Åy¹k\-À>%Ïp†¸…Ú0¶û}dx/,00pմ˼*r‚®Ø±wfŽp)O* L¬f³ÕšsEösOªíOÝŒð7|¸O@ÂhQQ‘x„Ú-Ÿ`Ÿ—MªÐd謯+§¾P€þçT³a¥÷Ò%¾ÁQñ)Ö<©Ä¡›½œRDòŠÊ%H¨ðñø'_oò›{ n<4m]Ô%]];ÙÙ凃´ÊXéª"qŽ9¹|W­pïVó*ãš6+¸úŸ:¢ŽèHG‡¬ê>@&yóÆä¯+ßÞ{0Î'×ÑÁì¥I™rätð^ŠZe¤]­N¯(Ùx´aݺ aQšaoƒæö!®:н$Ss§”n)@icXT¾¬nY?¡)«8[Ó+_©©À] L& bÙaìøDjS¬ùó7³ÆUÈFÊЀº%õê2§[s¤b¿.ê3ÁD:FœX½l‚ @)$*ap6P-qƒ!j aõ¦Y^r,8²ddÚódEe§¶Ìwϸ]óôúe¯Ç>q824R($EÄ9l|ò'G†Ãgbw>³%ë¢ìT Œ;ä_ýzäGh„¢h+<Ëì@êr‹ ]«eœšÊ„Qì‰M¸)Të 0AZâ〇k™µš^·Ï` –¼‚¢3ByÀM¢Ã6"é|{†éyzŠÒùÌi'j¡Pf…‡§—‡‡›c×Rl¢Y šÖ¬ZåËT(a‹Ð7:5-#rí—®‚@Ò‚Ô(ÜâªÚ«òô©€‚š&wtÉ4ÁÙ§dZ&ü!]ý†Ä˃é‰`"¬[ç " –~Nsg§S¬¶»6r5 EåœUÕªjO•jPó;N±ŠIlåÃ!|ú2Qb2±Œ9ÿ¶ŸÑP§) ÆÑ†jmÁÂvîµË—¹Áw³,æÔ¤T³5SíÜA¿þPè3ÏðáÑû§8ÃaaH»ñXºyô©,W6„kØ!«¦Ú2-xe žQ%™g.-‰&”o/_–ž>(N‘ Ûmb Y%†p?¿…Ë(ì`dÁ|èfÖL9ΈÛ~ÇGøTƒÓ~X_~"‡5@‹3ª !8ðèšJ´ª&˜RøUdx €´Ä[œRgÈF›°ß ’²E%r»ÆU:R‘i–Í`æ{zð€ ŠR{œ{‰°ðY%‹=”jr‡œ­¤ÓP³AÚ°…ÕUµEèŒkÓ–±¥ö·I¦”‡\ ‘Hj“ψëeŠ)c¬Gm›æÄîù ×”;cÂï:©íu.51*tN_<*G¥sP{çIVno˜:…ÉC®ª ^_!¶‡Ø›«OaÅ$ä†[o#Õ§Ž•_ƤóvfF-îc}ΉCVÆ0‘TLçH¹ZzX³¢$³ðÉJoË™gÎ-ðx×t €@qªNHW6H¼LË~Z÷v!Ãx>¹.“LÚËj%®²ÙD íL±Hy ؤ$'ƒ*æÎl »“ú–eɳ%š‘©IÝݧuzF@ÙCÏ@úžFóWȨöWŸÊ¹~>=Íÿ#ì °Úgå)öæ°FÐò–dÃÕÃjg2Q—gø&´d1ºíHwãqª²ž¾Œ±ãgInËŸÌ ’§Ð=Ÿ`J—Ó˜Žë.`€Dn;£¹X_Z»L¹ö1A*ú…nt»‘5©tIšÆÊ·x%º§Kç:ìÅܪvrž™žb½0&¹@ \ë»f¥ïº!’f\Ã~ŒYòÐÔfóù®ZiÑëR\êIi-Pá1↰ƒ€j‘a³ç }_½0fÏ"énŠòîIÔ›Còe(ú5³)ãXRÒ‡õ§“·jõ3wìÈH‚½í¸î]P_…ôùôŒÚ)@¥u¿ÏlÉìq?<±?]Ps„x#ßÏ#C¤·¢¶íL>/m:‹9À7%›Ùn£(§±¹r\\üÎ$ë ®aì´¢[ýºNiÂ$±¡;gÐdž5ëŒ;A¸ÙôM´Fú]û ÀÙ‡åÞôõY²dž®ÝDF&]›;»ô¤oC¹èç»ÓÔYì/Â%”¾kCÝôÝýNqݧÙ1_}%Ióx¨7ŠSF‹óEãôâ2UõF#n¤UØtøSß7ºȬ|ËØc@©TÒéDCØ@ÒiÕµ¤8/œèVo€w¼fÀà¿ux’iL:§h¬QgK&t õ¶TM¦ÛÛMÉûsµÃ»SwÁ!¾£òҢ¢+Çñg“á½·6m 1˜d±¬óÉ; Û¶Åh»™Ví4ÄFFDD I'R$MkúÆRóÒV—jÁ‡­†ªÛÜÍÏÂÞË)fld-Û *#5%%5N—/óšÉɼßèkÕî„©+1JHvsÀI§cGK¼’çI/ÉcHo¬ƒý/ö+0ï¨É`B\FÚH`?ázf埡ÌÄ@ÔÖ&ŸËY#E­±v+ºÅãè8^¨Ü5ÞrÊ@üŠ6^¬fð^ˈڡ×*ÅJœÅ`»)Mp ã($hú5,bä;Í_KñÎ=é©©‰‰©©éj(Ý0#Eo^·rÅ"Òº@€ð‘òlfffRqP‹Œ£È :/¦”»ØÊì J²Ê?ÕÍ#fqhmí¸~ý¢3„àj)--̯fæá¯`Ò*’Nò» ÷X­hËYéONaçhgt   BTkQ¹#’`ÞàÝÑ‹¸Ôòʆù Wsll ŸŽß‰$aX ½}—ÂÁ »i afGËì¶ÌÌŒÌÌ«b×ö§ì4D†n Äb8![ |F_&"##BCC#"" £øÊ'‘!Ì™$%=32zȹ-Fèà#åo;©ù§kïvl¸8ÓŠnAË#~£ÁŠLVÙ«x€}™Tåí\ ŠÄ¸ïÅ ñ<6 0ôæ ŽN»!ið&óö-ÄY&‡XÕo/ÑSVTBñtMGzåÇ‚º]Àf éæ`ÏÍÓ©q±ÀQ_¯dÕI‰_=BwæpwqyÛ‹ËÎ6ްuâ½!T««¯Üè½Ú²†¬uЙ¤S)4ÀŠ*c쌩L¸Êà½Æî1~¯‘Æj&̾³¥‚ ºá˃çÍ©Fàh3×믯@a¹q3’”šÞÃçq¸Ç’DÆH\BbR: šVîZØyìÊ×HðPâãn÷œÌÀÔX™?ã‹£çàVƒˆÑ‘pÁúà§îޱªcÿP}öìüq¬ÃÅ‘¤þYK…ÔÓÔî*®=ê9ÿ¬Öˆ X…ÁáѱƓƒø<×P¹I2íIJ1gÓÌ*ŽG'Í SGPjèoøÂCP„tÄö,+»® ÷›oÂý¿4"·‚¼‹ˆÜkØ™|o7Üm§DG˜ÄîQÖ=äûÍûÌæýxÅœL#ûV¤ Ü `pðÑM“!vkDh,}>¨ ~â(x ðdÆÑ5p•ÀÉ»ê:Ân|aìqm=å~¸ú¥P«’ðdÍÜãÀ'?‡ÿeϼߚ©~™f*¸ŠŠ÷âE /%Ý."2Þ„ZafNöö$Ñ–’‚¿Kµ*áUxÑ &Î$Õ _€Ávò‰g'yŠrw¼4Šç'²ß’——¤•¾kA&…GGÅ¡…´,½… ©ÆÔÔ“'ÓáÓ-KV:©¨©¹Õû¸ûV©iD!Aø¯Sœ£c*IàãR4k5û„°¡¥+ÝwßÀ&ÖL «rÁ_:y®§çæùô}h­#Gk}×°ì<"‰,)ý䵞žk'Á:J܇xq¦”nK’1 ÏcH Û¡43lOÞ—–vøÈ4v8rPú󟣃ø+ØðTõÕÆ2z*v5^­¨Àxqi\Ô,wqbŽì–}»·¶†þĽt”å÷¿|×sßsgN˜3Ñ2™dÎì !C3H ‰adI4GÂ&À‚ bÓ„† „C6‚€AáÁ  g[«.ti׺ŠmÝnÑÕÝJkëž•í×=ÝÕõy­­|¥*géP{¾×ç×u]÷Ì=ùvŸKÉï÷L®×ýùq}>ï$â §¶áÀð½ß†Œ¤®úû¡¡`ïg^{ñõÇ¿ ö5Ü€g%ã³*RóI*&Ù¹÷™×žÁí²sçØÎ°ižÁ½£"ö‘áÁþžµÊztt,iï"£´ÿ™qk?®žÂsï¿w·úú‘‘±±Ý÷~zëöŽGÍu™ ‘Ô݃boOg{G+8|ÍÍ`G¿¤0jÆ~U(Tapdçƒ/|}ü¨Yϼö‚²0{ï†+„èç«/<õͧ¿K 0—ƒžž÷uøõ{Ÿù+ø&Ì­ŒíT@üµúc¾ð HA¯#tuC»#üúž|Îìá9ïUÏ'| ?ªž˜ab1ÿóÊ E°ØNûxNŸ§0{2=É7ÉH°,À$At{늕]]+W®¸8J%!4WwïYóp¿þùð]¸Æî¹÷Þƒ{„M¡ßèÙg@öÅ^§˜# Ø!„½ôO˜ñþÁ$Žaöƒ—^Bç\BÐ%:xðÞ{ï½çž»Õº1;9"·?úâP™ÆWà•Á×Ýsb´R­u[Hoñ¸¸{ëº[çs[_‰X³®Ûa×íÞsðà¾=°ö)páKˆÖ¾}ø‘#Ãu½´· òÈgaTuÍôqîVyëô'=³,»©mZ=Åj¬\Û70Ð߯¬f__ÏÚu=}ýÃwXýêâÛ0"ˆ•IjÛ  +v šÁÁþÁa^ƒƒIëêj[…­KÚ;{z‡†ÆFGwïVÿï…7Ü ;n÷!€DýSûU-H'ÜÞµ¤8R$Õ56·)•ƒ¨0ZÝ©ókçÖBôÔÚÖÚÚX›™S §Ú&­íkáš`ëš5†Û¹Ÿ.Ì9ùäÓßÿ‡÷äé¹’ûô¼ûž­Ñã„§£§§¯ΗŽ s"á€”š£ž’öµðeÃøFø›Ô38LlN|תg²²æ%Ø‘äi­b*S¥þ$UP¨9É#]HÅ ¤Ø¬ÊT¦aþâ[om½u±ÂH9镸í”IR6I‘´b¥ºÏÓÚºõ/þâÞƒGý2ð¾U«-ÿ› wêr„$Q\–1X?NÛ9ûdÌ}ô/¼wÇŽ±¿Øºuë][ïÞúçk•9‚øH8BÒŠ‡ÅùÀŸoUåÔ­[·âÖ[.Iqž¢JW©äseÉy _ÚÁ;nt£¤– ¥¥‡!£ëÑGåÁ­áÙ3gÊ GÌ^ýÖàtñÙÓ'¿w òËÐÛ°ŒR—²™àŠ­„¸ieÀ«o-Ä! h*¡°"©UVka‹õ¬]»¾ ßíÏfžPŒ‡ sš›-A«²uëÐÐðzò†Ô`ƒFF†¶ö÷÷ÜÞÝÙÙÙ}{_Ú¾îή6´GÕÕÕ™¹uŠåÊ©ÕÎg ¤â'åÓÍAñ„DyyEu¦Q=skñšûi Ýp_ +„ò8$zì›OÿmßÓcY¦O²—ž:z`§Bo€=x°Š’A¼Iô€?ÉE¤$’Ô Àtx/ê‘ߊÏÊ@©¾³‡Á$ѳ£!I.ÍœµbnžÐ³eý²×R¶V¥ºI+”`5dæGe´_?—¬TÞ]Ãb…­u ¿:´[‘Äwjìç{µuã†}÷5Úwd”lêà´óñÇ5f§Œ%ƒQËG:¸cÇúÛܾn퀂h`4¿ÁâH“į[‡_Ö»þl ‰Â4š#H•*Y¬¢‚Þ~@itȜÀ÷}èÑF»vÁG®C=ôÐ1ZáúKe¤ÔãyS—Wy„Ó‡9,]ξýÄ©¯õu­7¿"™¬PÚê¯Ö­‹iØ‚ŠœLà ¬„";îa… )_ÞÆ«ã,zw%n¼V,‚¨­›?·®¶R͋ڗwv÷Àºývõ¿20}86àvQ—òÿ::TØ£pêìQ{wEksm]¦z¶B¼Ý»Æfe~šá'f2³«á¿jR?¬†P‹Çùšð`¯•+ézÔ£AçkïÇa†Œ<=ïýšGÖiÆ›ÇHH‹ú¶V~2’µ°Ôm¦•lJ eíÂðŒ¨›zÌ$<€<= zµ¯\ lÃAs3Ì6Oçié|®aeüºvS«µ )B{Ký™çÁRU&™#ܯÉYéysÕþ¤uëWß¾i+ôðÃxöÂîÎãt„‰\ðÝûk¸ˆ$Ôu0naŸd”ÀGa²å}w}åK›n¿}±Ûv+b4.L8"’þó€œ[ÛV¬hSÆhñŸ4@‹Ä=JÒN¾U=Údz^]s[W÷¦/mÚ®ÖŽÛF$Z»vŒî@¶l;%¡ÔAÎAœ†™êT”S:~þ‹cª|œýà4 ã·Ãß4‰â@IõgæÎ§¥|=X ó'œ`2'…l°s"èËTÀ—Ó«…oÁo–MW+fCmz”Ï­”–B² nñ¼x§·w¬hmmFÑ÷ÖÖjµ¶.@`Ô¯$m×L¦¶íOÝ|ÁÒ‡øf"Å¢ÑX4 ê•èóå.¸œ¹sÑ}Ž“§/q¶ç7?ÿ‡xù?ß»d==P½w`l³0ôÈ@2b0¼µà-î*å0ˆôA¦§$š„gdŽy äWç¾@Eš1RÄs¨™\YåÑy¤i'é<ŸBä5€Ä°ƒWY©ž¶Y•„‘Ù¯¸aÓŠ0µGæÎý“Å+I0)ø°ŠÍ&“c¼ËR=üð×–ÇÿÚ4£‡áÓüYð Õ~H(ûöìúÒí·wu(Ó¸x1ê€Ñ,È#êë"ÆÁ(Ík Ý5_Qw‚é.œûåÑ»íì7‘T¦|ÙÆÖERïæ¡¡¡-Š¥mh”`Eêß6|‰píÓ„íï¡cƒ`¿fÿã_°ïüÿýҦâ+›…©°Øï“IÅ?âg@ð,Úš°;«*«ª`§Â-?Ã{—1‚C·`ørøl5omöáƒÕz·«¢v.¤ß´B¸DÚÌ9Ñjn^{]‘RÚ© IDATkv¾Bp¶únÐßM!KsLø¹¤$Z®>[^N¢§ØËˆ]‰õÎÉÌÁï#â0o¿v`XùvT[ªžžÝ®ï~ÿùÿüФ Þdl°ÇñáÃ(§@Ï'²À F£, Ï!Éx…ï×g2Ÿÿ¼z">ÿy&Ñ<“qÔÃïÝ-–lн®bœüµvSN[ …Yì®1V'ɲ2…‘½_‘¤Yé4r æñíÙ3:´yS÷ê*b˜7­døþ#ûº€$@iV-éܹsá Ó¤¸wõõîÞ R.•¾Ç¤¼;˜ø°¼»—&€n‰˜;ØíÀ·wà‹úƒ¼Fwí‡\Ëw^—¤_‘t×Ù³?ý—_ü—黸ihS{óüT"&J‚°’Éx</cθ‚Eñ¤þáäôEh㨿J¾ >«¼æ2À_¦L½ŽÏŠƒt"}HýÑâ(›7 ¬R-Ý¢Õk´$àÔ¡¡Áu£ú2ÜúÕŒ¢¾´wS àO(‘ˆ—ª¥¸)£hX<¬(\ ˜!p=ñx6;4®‘Ø—¤Ìÿu˜iþM{þçšFΗP6©pk‚ÅKÆ‘j¼A¨§(JO‡rÃHì´º¤8ÈÑVÌ‚¥ž Z³à ÄÅËãð´ðS‰m}<<–•í(cç9ò†›#é7IÓ `7{Ì¿[eÃòªLgšš;ºÖoÞ>ªœžû&–„ ýÆ×ö-?dæ|–´–Þ¯nþ»¶)ƒ´º£µ ¢"µÈJþÑå]£¤ fšhÐçbíÐÝ\v†M!¾\­2õ@ê,jï\ÓËóÝÁÁÛf)Ú®Ò£ßÚ¾æf>Š®, ÿã³gàŒéÌE’¥BñÊ©c÷ݾjùc¹_N i¡PÈhß²Êu(d zLj‡œ<©`ã€o)Ay|»,– É=`•æÜ( f%Àûuð&Z5õ¨°‡×l2gå PIV?…¬)(.…oD£Q}eÈÞwyáõ«;]`4YAbŽžçöš÷Ÿ~ò[¡†Ò“ß{þå_kµô—ŽŽÁX™T2 ÏEó üƒãe*c©âpÈ¥®!£qj?ôDš§S>†OxØsäêƒÑý¦Ú ‹Â\͆kI6øVI¡…7¼@§ti ¯WNÏÁû†€‡§°4KÁŸ>vìˆâh¿²G»F·)ƒÔ±´¥.S‰Ò¨p2üGù|‹½ƒ ,Qˆ ëþܬu ]V®AHªªžÛؼ´«sS/ŽÒÚ¶ç4ïÀ±çÛ˜›-Û·à«-[†xmÙŽ ýõß¾"Ã~Þ>{æYèh:}úÌ?þø_þ—‘¾8ql÷Pw[c5*„ÞhÝ¢Þ­q"Öß>ä_øü‚¾›ÞDÔV.ÕÃFª3A ÀQæf6ºrz•—+|JÕ¬^–*¤þO&“#´fDÂ|sw}%×oÈ•²Âˆt (àÉ“=úÀG>¦H2:S/í'­ü?püa)àÿ<RnºÏsùY ɃéÍò½KOŸ'•©ú8Vʽ"nRrýýH×Mã@6¤b\òV‰õh‡Ó7 ÁE»iÄæï; (M‰¤ xdAŒî»oÿ¾=*FÙ¾y}g$éð5!Û*UR§ü»†?Y¬ ÒðÝ|‹RUPû†H™TYη%R™Zšwö"HʹÛBìC@ÆG­Í´††vì9CÕßuäþGP@§ö¥³ É­4ÿ¤@êïl­­Vž]¸È+RXõ¤óŸ~ý—.uïÌùHÎû°1Ôæ›&©f6-„†W _¤4;`vh( …@‰d)bó¯(ÅP42EáukóU¸nÀ%‡qÔ®Ÿ“§çÌ·`LùÎ{=ùäw`Ë @è§:Žq UŽòc´ž–Ìâ ›+EÛ䡉[†Q{mÔªG½å:2ÐÜ‘þ É:Ð`Љ§Q|J"$CÔ¹i3æ‹Þwß}× Ò1kF8v£ RS&]9!Bxi ÄF 2ñâ[ÛT„¤ éÁ_*Ö­kA°¡HËi$µ)€£Íö‹lõ̰³Øûøã˜É;ûS£HrâÈîÍ­uÕàÙ©§»¨ˆ_0R ­"øŸ>’¿ÝÕI—º …¦'°¢îÜà$$©B”lŒ$b—2Gå¥eey ¡?‰>6ŸX´æé[¸3\O‹‡¤Á±£ÒpéI{äã'Ÿþ¾éï £bVIÈÕ?ÕeIT¶!ºiˆ½+µ¦8M9D9l=…ªº­C"táh]°”2`å6ý*ʇ»n®fÃgh‘Ô…AêöígŽ} ŒŽÀvM¤àÂ8 ÞаøÖ[W´­\¹8zà«2öT‹›ŸÊýQ¤õd’†¾¼Å’Å‘MÒЃ¤ÎÒHŽGAòÌ?þT6êÄ‘Qe‘êRR²DEòô#TXôXÀ™²Q­æ1ŽÔÀÊ»a¶H4ùŠý;<"[d;s8ÎG‚„‚²EàÛ••r0”Cpþ$W‡ZÞrŽðSáXÅ\”(Ùò÷Ÿ< 'F<1âÉïêûÌWIñ,æ!GxDãž›\«}ˆ$µ\—­Žëxú“®ž_îz‰@RY*æj5.jL"ÚœÏ$kGçI>ði€tìØ§¢ˆ@:tßÁé€tà r³+×­[ uvÐô*ɨ\¿;0D*Y&µ ¤MìÛùl’íÖù ÒÐö{Æ=þÔ?Ù =u NŸ•Izö,kz}œ½pêÑqÌÄ#׎ÈñBðš Q¨DhÞ¤ ÊwVÇhugà aŒÄCä!ä¨bvEMM…%´9*/·. ¹9?GѨ ¢œ¡I¼".µq¤7AŽeB ¤ùmF‰ABuÕ‘½Gûæ÷´!?Šò$*D ¹DK)¸¶Ïæ4ZÉ‘ªµíÚ9®˜Óm°®…[p ¸1vNH+6е_KÑjH^>Háß/HM¤x•@…ÃHái´GƒtìÓP„©iÏT@ºÁ^ÐOIþ¥â.|‹åŠÑ³{çÀØŸãE©˜t3€¾€„FÉŸ÷öq¤œ@L?~êïôÈ/~ ùï§žzêoO?{æuÑ{ +í:—òy¬G‰S7‘OÇdžœµ]Úy#•(d‘j,f§ ˜F,RB^+~¥òd2^š °DäÜ¡AŠF¸@€-’ø[ŸÌx2;’- ÄHsÛÖjq·²o†KŒíóoýLß_Ø9¸¶çE„ãeñ~çázT°-¬zVNƒ*·4,Žè.Г$²[ôéVî>ìzì‹#éL_ŽKƒŠàpcZ 5å4“t¬0E¤û¤…Aº!gAŸÈ7 ¼Æ"­oýý÷¿Ï“â²Ù•Aêºu~Š=»ââ-Q ÉIÛ¤àó#¤Aڥ茶Ã2#W²(“z'§Gÿ|‘ ÒÅS'Žìê]ÞZW  o\!øŸAš#‰­eÛ²¯ãH['o—-ÇH5Æ»ƒÂ·œ„® Ó™oÎ{Çóc$k Óc"a„ó‡Ž>ìädA e»úw53Hž{ú›';~ôøñÇN>ù>?=—¾­ dž9Þ3÷ìãñ£õ´wK| ÀÚ-‡o.Ž1Tgït²NXôtí·Kw¦œiÓqíäO†âki»i*¾Ý„å‚´E@J~n2ŽHTÞÉÒh˜=DüW²ç•AZg<»â<Ð"ù@ÚâisG ¤í£X"ô®²ñtj„^aݱ,¤ñÑ¡žöæºêrútlŠ&LÖÉÝÍÕwR37A'…ñ]àx4ŽŒ{—Êqë&Bâø(®B¤²X°Eb"*FÒçHEtÌù:ÏÄK”àÕD±DhìÀ[ºTêÝï>ýäI¨øä»!½¥"¤µmµ(òÃŒH²N:ÁÅŒh)- ƒËÅ _ëœÀ|= ¹ú4ב:;“ w­A/ÂßSIþfø—-’>™ ©7¤c׌Q!Ò9 ÝQ JƒœØy­Å åÙi¶)ç´ µƒ\;iÈÏÑæ<Œ$ðíN<õ¦žD™}ïÇgÏ>wæÌs?û@$!ß?õèøž›;;Ü8A ñS=qÊ[ûÚ|×ä@À`%Ht*¯~ ç0ùS \`j!%R9víà‰svØÊ€g[&ïåˆAð$+¯<©p4^Ã^0£0€¤'¿õ­oþ@÷A^<º“…1D’jl1/ü¿+^wA¨7BbšéFÂG³ü6U³˜ªUñ 3‹:èñQ‚ëJtç×âšNÑ*ýÕøù™€TØ&MŠQ HÍy rÄ ¿#cA.sßËG`Ô}$úÊr8²Aê5‰I*H*HÚ}ð!e’DVY¡ûÁÛÿ|öì¿ýZÑ€ò ƒ£àÙÝœABX"„F)4EŽDX׸3®kݘùüÒÅú6ŸEª Ò |{T^îsìÉd¬@¾`‚ÜwIĜƆ©xÍst±€§súàGχ´G/˜1-¼~æé'ÿéÝKºaö…½#TùGen}+§¬)ø¹!GF…qa›@–¢Z!1?ººNàÒgG2‰oŽÔ¹9­æ®;m‹ }f ù;xóX@A]á5)"PuÕÊ!ùgîîCå¡<ƒ¤AZ²|•€Ä&©@ºÎg’>ôõo(“¤ÇôÈbíÖìëʱÛ=Ò¿¾½¹–@‚r ‰c£•û1­›Zg@ëT[þÞo‹HQµ’R³kR¹H’d0••EHº¬¡$Â]ÞTT’6Î’é-çxPµ:Ä-AHìª5A?=Wqþ%èšÌ©H–P“„TJ8Ú)#ÇÍõß6 %ÙYò¹æCÿ@WÊêôivHÚ)äÌÊ?±oº%Bü×ûT 2HùÒ ‚MFG&\ £C‡îß_¤ ad”SÿÊ/}¬þ^¯ IIe>ççîâRc³ié©ÀáC„™r,[}£$QøD¶Ë}¢9úúøžÑ¡MKÔf*е›¨(#‘ãÐý›ú®ª©òt6·ˆ’v”Ê?†õ••J¥eAÑQÔªûŽX!R˜®RnëpA!W¦¤èÑ]PµZ­LRÿØwÌT™|OÏ;0$i%ê G Vr”œ¤z ™\ö9ˆÒ. Òò©â’ßÇwrÚÅØKM„'n$»ÊŽg·ÈNUûûÚ@*™¤C6HÆþ ašF ¤‡&©0FF9uì[ïö*þ½ Ó!…sAê½c£¤Í Ý }ehÇŽ}‡þR‘tÁhn$½_?qâˆrìúW-o]pcu*A M!Å Aâ<¾Þ”ý‹³ÂT9R4¤ŠTEIÚ±+G”E ¨ùÎi À\Cؤ¿%n“[:¿çèBS$•Ä•IRQÒÎñ_fó†Roìi_\›)/+AE>fÕµ’rž¤ãÍI›#ÇoœG½HÚü!—R%^núû÷’½¤õÒý Ò MÏ1zè¡ ê¤&ÂH+§ ßsÁh 8h˜ß ÚË>ƒÄ½W±¸iI›'áèŽÞM›zïøÊDI‡Ž=~êäÊBªãÕ9¸{hso§òìnœ]A MÕñ¼Œ ÖÎ GÍrú(²ÕƒT*ùmL7Ø#{•–B½j"Vp€dqTBAÖ9ׯq¢Ž®J÷oã> o—šß ½} ¤êýƒ§Gùuý}íiH’x#×ø’ P çJ!Íd1ÂYräi3ÍÕèžeÁ]G×Ùéƒ.“§`Æñ189%«ÿý í³@ÊãèÈ”0:Ò<‡¤}¤ÿ¡-Ò$‘r*$ÝõWçA©æã«¨^üÖ÷lè‚!0ÀQIGl‘n.R°g׫8ÚÔû%&éð±§žúåo@a÷w–sáïN|8ê^®<; ÒÔ9²ËÁ]h&ðäü> ©b2”I‚ÿrã£(%ë¤%‘¨xvPiWë! 1øŸSÉû‚;}8š¬HÕ‚ÞòÎÞ¸dI+áÓsþÛ{•=ZÙÚ8'kâó0OgøMÁcTÊ›’®”3iGW·ê<'14Æïã/ &ÕÆç*ž¢ãwí|Ú'A ýaHAY;ïšA²P:rä˜|d*){D Ab–3H“PÄ2u¤« éÔKô/_¦ˆú«'?vøàîÀQû¢uiÊYEÉ:HâÂ}Âþ¸‘$_Ú.Ï««Hø2vPô‚ E º1òß’X$Oâ69ûáÑà!Úøê^òG0Ê´|ãKöÓsþjpµ·ÖfhàJÈãE:U ë8Ø‘ZÇÑÝ“q•\£ªÏ‘¼¹Áè37“Ɠٗ°ûI}¢$\¬î^žø÷„Ɇ襤ð”@J HÛ HDÒ±#S2@¹©õÐa ¤Û·ôHàÚ•L†úv•©†Æ[WöAÅ‹oœ¿ðƹoƒxqp4ÏâÈ×Vo@RAÒ& Y 4G_º£@«ô¥CLܽôú.¿ðæ`îåƒûv ©Gо¨yÁƒä/>="šBÚs‘ MÏÊm  †¤DEyaÊu'_y)ô“CÉwYG¾TCÔöìÂ!R£×™3iDâŠlÞÏ!l7GNTŠüú¹ó<Ïö¹oïÝ9Òß·¶m1蛋v£ãé“Wi_uN·;îH6αªüôDeÏÔ.1eâK)ŸÂ^Ý~fÆ’ep E¡¥ÕÉ4ü yù …¯$m’ŽL #âA:lƒ´­ HzÍA9HZ7°õ®{î9pài¯ŽÒ•qŒÄ ­Ÿ¤;`m"‹¤`B›´{ßÁ#8+öÔSOýÓ«¯~çˆË;rpÏèÐPÿúÎöŽÉ@*€‘£Ïg´æ»”²¸&úæc¯H’À$%&pìtÉ7¥íJsо™ >ŠåLƒiê£s$ÇŽõu8‡oA©¯ŠDQð´I¦{ü™^üˆr’ñœTE:v:% K±Íñ®£[!¤Éó$S.uŽ>Kòtg cG˜’Ÿw¸º•â»"õœ%Ó«W/ƒzâ"/GD’Vs$®‡´@’ 6mÚ ]#EÒ>T8%–6׿€TXµ¡ :û€¤>/þs/nDޤpÎ’¬ƒÄ'IèÝ$…Ñ—z$ “à£{ކ)J'NàÌ¿G¿¦0RnŠÖ«i颿ZR@šFZ¢ÀÓá‚uèoZâ?)>¡k‡•ªå†¦D`óDÔdf lQ$j%í0ý­:è È1ew˜$$ FW¬%5o”Qn”ebPÚ’ž+έ‰ ƒg¹RZçyú™pùƒ®i+’Ž&ilr$çÉ„z®”Ûâ=J¤’™‰– V'Ê¢áP®ŠÐ$ýH$Êlþï)—# i¿€¤£Ûò@š@þäs¨A 5ºÖ­ƒëºV¬Xü'sS•…8 —”(nœH½½Ì¼ÑÛ½²£ ¥ƒCûµ¯£S{P™#àhMçòö%Ê iŠ¦Ê‘ið4eaºœSºÛ\Ó¹û–]»òò S$½|Úkj'…ªŒR4êïŽ‘Üørfj”¤|ÖåîUe’Iå(¸2áýƒƒ¤øÏ¥püI ê&W’gR ®Ÿ ®wuBú’DñÄ3É)CäÚ=IééÏ#¶ ÒK¦7l¸su:!Å~LÒ ™éæ× R£€4º‹@:|øSQöèð}÷Ýo´*¤‰PHÍ{Œh[±y£T¸Š€£pÀ*)‰WHK¤Þ›ƒ×|€>aÄ$õ+æ%Ô WDÃw*·íÑmKM’Wx¹V¢ïµž©ùwì U‹k§ )ÀQB;våìÕ% ôòÙ¹†²I9ɱº¼M§!CÀù‚˂˄(ã¯íéAÅk‘Õir[Å žÖ6‘4‚N×éé`ŽkÒäüe®‰¥tBŽ¥µÅÒ Ž>¯R]_VU³Z´,Œ†sŠVÝ©eíÜß H‡sê&ƒÈ‡$‰úc@šLJHÙ$AéOP¼'TV¢ô²µ| %¤E ¤õk6Y&ÉoŽîà‚J3Hëׯï]¶‰Ä¹vŒîÞBÆ{v‚ôÿæÞÞîÎå·¨©y€”J(7&\4EŒ%qy<Ôèèц IúÉ#¢±xé •k‘ÎÚ•••Æã…HÒÖ(ê/kIªcÒœ‘K£M aR$+C”je ̘?çó)rëÂÔ¬5ëü¥¦Žaȳª\z쬼Åa‘§%tO…Ðãè£&}œ_XTŽ%ê—)6¬®IÌÄ^÷àãØÏ$ ©<îÏéð r41H%SY Œ? ôv-‰c4GÁH~ š£µGÇ ­_£þm¢oRv u‡¶Cž¢_™#å×u,ƒ„)7Ù0)ERµ‰E1ºÎ[D£LLBŸ Ržo—ÀÌ·ï kUË‚ã#,VsDU &Ù kV]ÓèãH‚QŸÚp*ŸaD©´¼kkI)ŸTñI‡X=WCÖýHlíø,Ö±k¤QIwçɉ‘£5S=nVòD&Hb$ÎÕ@„«ªWž ’ê«ÊJŠ='§®a*Y»k)\\¤=÷ßwa°ëOO‡" ‘î»ï`0H%S[Ê(••¡æj%ÍÀA0²@ºE´-ÒÆ avÁÇ­5,‰,Ê_Ú´iýªÎåË1@bR~&±FÜÒ&EþZ¥‡Å>t)§”Èx:k§@ ÊÙù+¾¹Æ.¸ÜÛÍ€º†hÄ ‘¨°“‡®.#uM‹,w>@n˜jbñD•ió!pÐJ•„¥ÓŠÇzä®ññtˆ¶$˜â`L7/ùÄL›¡+äa§ß{ ¡8šH/ÜpçÊ"¡ožV?Ò5ƒž"H‡5FÁ =T#@éþûmº¤é,Ðd-£Eò¬1Ê ƒ¤|6æsd´ _n2K½×­ÌÑò[G‹Èµ» §ô×nRŽ\Q»råvlZútÖN„`ó`åŸ U”k®ë¥zLE"E,ÏNQ䆸âM§Ï¤FÎÚ×E"ÖÇØQÎ5ï(h|£qs=éˆp,ÇŒyѹkѹӖP‹­HÅ!Ÿ' u:çÉΚ©çŸGŠÇÑdUËê `’V¯n©*»e#|R«Ÿ½E’7íʆº@€ÍÅ4¬g¤ýxŒ¤@Ú4}òâ¹È HµÒªõº')#_ ƒ´†)’@áÝêÿÎNä¨2 Rõl’7ùrŒT§Ót®k>à¸!éD#s2 %‚8²DU Ñ,습òuTk—“kÐåÔ®-\éÊQî%óCÿVëEù/B§QŠGW`q2Ü5ÌØ–Ó2Æ*G¯N£µˆõ—¸ZƒÈrh¿C#d"Ý @Z®Œ]"²¦¯oZ /ÖÊÜyZHÈ:à iËŽ\¬Å^Þ”Œž!Rñy¬€T7uŠóVx’ UHí«Ö`8ƒô•ܾ ¡ˆ0ÊåˆèéD‚pÝÖÑѾd©¶H•±¸€ IDATÕ©rH6L#Q¥æHÚdÇêDõŒ‰ÂH3°Ø®œ§³TäéäÛA÷ÄÆ'©Çe(Ï.ìI×pzÚÑz"Üú«@Áº‰~„Í'Ð=£–eÊtšßÕ§¯Z7U*„t…¶gæ„iíLNLjh ËœMXa™9³2]O )’:jÒ ìG(]7iÅæÔ®æ‚ä1H² ­ÍÒÆÉAžšØ©;$ íc¶!H ¦Òô92©£ÌJlŽ6[—Rww·"ɲH³Ùµ›Š1²õ£Dü#ÄÕ@¦ÍÑΆÐR„@ H7HÆ..]HÒ:Q$É3øe¿ÉµÓZ'ú\Ö£bàÈìpæÌÏD”Š©@T-I¸¦ì[Ú\«qÏrÏä¡»Ž%“éê:pWäO\íºÒ|¨ —Dx™!õT%«ÒõËVßIkõêeõé*˜ XròJV§«rù Qa–.gP"2$áiJºï~it´|ê MŸ"õˆ HËkHÜn£ÉæÈD@yI¬‘¤Ž%¤Ìì ´H“‚¤…=$©«]ùs‰â‡ø,|¢b©ÕRÒ}ô× %lµüR8‹-ØZ.ÊA3D…Ë׋ÄíHÒE¡<0* q%5NF«LVU¥kR•ÉdeU²*UY™H"Ma€‰ #ÐôIREçYDRÔ~\-yΧG®­¾âšV?Ï1A¦)q¥¬ìQI,Y™nY¦9R¾Ý—µ4¥“X+|^çN/Fòƒä]H; ƒÄ¯¦†Ñ§©øÌQ¸8$%9v ±=Znq´üÅ‘$e‘"“‚ähQOÇ ž‘—2ÞˆdsS*­* (Ù¨±óõN·–Ó)¬‘"lŽõU¬Òðé—ÀÊØâáþP? ¬2•N×ÔÔÔgjR5™Œú§Þ̤Óee0*ízLØ¡(Z œXÑÝ!—Oµ¸Œ5âÀÐÈpIŠ‚{ޤ|Áò‡Ô vò© ½^EG ›VCšAƒ¤VGK}: *žŽÔ~_HÆ}ŸHÛ¶‰E:„Ük"ŒÙËÒ–Í RUò÷àÔQ¥{IIÄH Y ®Iš¤öù‘`´Fs´ž¼ºîî<{ä©¶6S •L ’®¤–Z]È %¬µvƒç:~Åp©¸‚ R¹Ub—ðµO$‰²Bv&BšÞ]„cœˆÏ"¹"¼åòàU Þ‹#p£¯¬J$Ò©šzXü¿A½Õ ^Ìm€¹‹eU‰èõ%Êr£‡Ç…<º5`0í!.ëáK*Û(›Hø¨Âå4—óÞ®=†ÏH¡ =J¤ë!]g@“Ô±bÅŠ¦*¸ßAœäÏ6L$;4-†¤=ûáDöPp2n*Æ($È5L¤kÆAJ0HÒ&i£1Gr »ÞÊy3GÝÝ$£å·HK—Bª¡yÁÀ"%&I¿’HAKŒÅãù8žcCæ¸ÆµCëCqœUUss yE QG$gæä=iŸ8Ö%ã]Xat}´¬*•ªóSSC)ˆ!xÉ«¾*•©T†ézåâA°ähÅaמÿå²ú°}ë™{‰VúvÍ4 ϳóáÒ‡äÙº”g(‰)·Ž0R!Lh‘:IË–UÁÙ¤k‰‘B×ÒmH'i Aòû@º¦àˆW@Ê@Ön9å¿7‰o—ÃQ/&LÞ›;Ë ù=»[$DR Ý\G Eá‰s ò÷u+™¶eU¬ZJ‰®v=J6DIµìfê0RJØaº!PÒµ!qÁjHVƒœ·xˆ"×'UÊÍËÔ‹1€Ö/\¸P½R¯Ô[ sêÓ‰t%Ì›-F£DbXFbLºFBöŒ×tnp†Ûµu ]-¡ïÊd$ê6Ñç²|è ¡=Z¶Ú€´º¥¥=;4IËZÒe*–urªV§ Rèš@j¶AÚwHÈáç°4ìM…¢ n› ¤Oa4H`‘:¤UÀË7ÚYj>Žüç°ÂQgc§8êX* Q;RZA' H&˜è€HÂn•åhÇ…¤  H‰rAÑB'Qi©NØÅ²2ߨÂÅ«¾ƒ8ì¢pMÀ÷xeÔ/Ƹ¨Þ,ÎÂziáÂ&þ§^Ô55©Oeªª0ÛŒ$IK†éa÷Ìå9.:B29®ƒpM¹¬çYݬk¬œ$GC9éáhòeËZT0§XZ¶ªœ•Ijª×fšOA³á÷Ò}>‚âC`Ä íÑ õN Ò§äH,ƒ´J@ÚèI8ZŸÃÑ*ƒQHííK%×ð…É"E €¤Gu™þ9Û×ú"|$+оR ®O†xsLÐ)gEUvëÊ9: pÄù¨j€Š:C²’ßa¬l0“‡\4GKs¤1B®a!­¦¦…²šÔ;jÕ©ÏgjÒe1åß¡{'ÇHÜÝdžÉCp©”¨nié]%ÄvG‹ëy:Ãõ!ø««&«–màµúÏêÓéªD•Zé†eËÐ"µ¶¶TÅ`,áHUy ÝÏ <‘’I^y7}Å¡BK´o_>HeŸS—ó Òl©“A¢|CGVˆ”—ööHPÕÐ.§Hºf5 ’¯x…sv\§É»ŠóSžn 5¥n®n#•ÉX$:P (±+ 8>ŠÆjJ¸ªÁçÙQaG÷x´y}e:•ò[£…õ ®–¦–¦&åäÕ)’J¨xO¢!Ï ~•T¸tãyrFäJ±£[ŸDfUŸZKs¬–Ä#ö)@ªÑ -T¡ÎlY2Y•nú"Z$e¢ Í/4EÉâϤU½Ò.é˜"ã¡|_®0EÒAiûd zŒÔ·ˆEZ¢@êV ­‡ò½þ¬w¯¯LÕT3tva„æH@Z°À€àÚY¢®èZ±Sãé±Â¢šO¹â‘ѱ¤")k‡3ûK¾øó¥ñü^>><2‚'”÷–ò »«EàD«±ˆÜºd:eY#0GÀ‹¥ë%¤V”åD0P²´‰Q’ÈÓÃr変kÁ=_NÛÒ‰4Å÷¤ªœ‹ÔñœëúX2½š(ºs5ÁÂCT^ìÌd"½ð‹RKY¦6±oê ORwHhˆåEL‡&_÷çÔQ¤O“bðƒ ;ûV­Á´&îzíƒØÞMA­ J{+Œ¤]× Eó-’ñX¬ó®ï–nSÓëXh:»Ç·cm‘J¥Š¡<‡"rí’ £üt]4¯+–R %~‹BÝòtºcx¤nñU~s´0Èý)ÐÓò§-Š%´(ÌR}’ÊrŠBzü‘Udé§zzš„k‚-ÑL]IäxV-¹+Å´\rQ ¼¤k¤Õ ÓÐË!#W”{š®(n¥>Š‹'µHttð~ER>.ÓˆAÚ% u"He¿kd,R‚Ô‰(aºw¹Ç°¹ ©©;£`b¹®>‹ôK[›RMÝçî]Óþ)-£|ç6 Qœ[a}0•Æ¥Éé:d‰Ë¾gD%aW’{Š„e·¼çÑ­Ñ ÅÑ×LÌÁô§ ,Áÿ€ؤšt,VRŒÅEþn$×Ì#Ôà"þåØ%4½ÒÕš(œ°žÐPˆÒŠP³T™®¡¤ _lŽà‘ü-zé¦eËJ-uUʹƒ$xèÿH@6HSÅ(¤<‹ô™qT$Œ“L;ù¦ü,ƒiÈã@Z"I;©<$×aQ»Z.ßœimH©}‘ÑÜ®™ÆŒmx$ •[cb¦$½U¨â;§äMWšZ«+éaÛžÚ}i9eŽò@j!ŸîOÑ,ý)rD Pª,SÎÈÊ9> Oz=}Z&“×<ýŒyRø!S:ôÞ¡`ìf¤df,–¬¬J×7µ,Ãt݆–š:pôËIÅ×—AÙÐ e‘ZÒI–“`’ ƒô‡y Y0§ÒvÎ6ø@¢´Â¡É’ ¹Aò›¶maææ‚ô™Q”R7‚Ô›’fðì €´TJ¿Ar];Ç Ëdt·ëYƒç\™ÞCg2>ܳä#Í9RiÂX¤œIjìây"vQ“ûžA3ÌeºXØÆ(Ä .%<<òûueпC»Ä4)’jÒ•3aG#Iæ¸Y—:®´æéºUk4‡ž +ÂFŽ+RšR”vÒ UÊ5‘5‚Ó£úª$V¨zTáAÃlYU 9wMMéeB”QšH>Ï=gsHðš@ZÞÅI‡5N“‚tÿý†#iTƒÔz³¤âÏ#(õg–.ÇlÉönòõ­2HÁyäÈÒò@’|·kþ™Ã#X ÏiWë¸GªjèGQõ7¦í¸¸+ƒÊå ¶TZùâB'þ“XL4ðRÎ1¹vÔ© G2i;>ZX_?G‚“½Ð½«IÆ®/†<†§§/»¶Œ‰ŒÓ=IRnæ™!¾Ý¸¬l™ºH0Zȧ°´ @â8ˆ0x‚-7*vÉÒx)ºuñBóùDR4! #_å7[$Ïa#±*ˆþ¸~êIay†& $4J é …‹\]®‹;´óæxÖÑ’ŸõpdW3È„f=ƒLšâXrõêÕPNgY¤ú*t]ê]ÊÄ’E“¤@Âôxعn"8ˆš6HRìY& mÞ²]ŸÈÞ¯ "SüO#2H¤!©ºªì÷ÆT€Ôkq”ïØMÊQ‡âh‰æˆA"‹dú;Ž/ÏàèÝ¢}89{¤(H4B]3YË$Pc´ÈÆQÁ2G'•Æ™£B£´Ž] éË= Ñ0 h£p!Ó 8JAU R¡©ªìêêšh娤¦¦L%éo‡<ãÀqÌ%ÏM#Ò¡+K8»¾ÙcØ0AV @*C†,6,SF°¸H‚Ó/ €q ¹vR [ýàDéZ@²ûíó@21:Ôiƒtð~;SwŸZÓ°F&Bb}©‘AúL)*¶4‹+f“ø7ƒ´†-’%eâ«÷.ŒÑòÛŒ=êX B\‹¤jI»@6Q®Ubæ¹2m›‡ {&E.é<=ÿÔ“©­&ÛPšÈÞG±…,QN­*w!a‹9¤r/(k§ˆRNI*ËÑBJÚÕÓ 4QØE‘Q/k2™ŒzÙÐÐÔPßüØ6I‘4S=?a<›µÎÉ<פ$ªÔÕº"ä庖‹Ö"ÑZF`‘6è… ý4Ñæg½ifYUMHTä0mr„+A"Aj× í"îGzˆ¢É@ÊLj ÌFÊé÷aŒ,H« ÙÈɧ4‘_wÛm·I¢¡*V—È)ÒTŒ¤A²ÈïεB$‘Ô‘@Jo$-ÃèØw*ã`…0‹6ÄŒcg"¤x¢´4¸%ÖØI¶Ó ù‡±,ÆUä*° ;_€Ä%ªôBWÜÕ×c-[eYe²²ª2YUÓ©ªij°-¼Y£6íõФrD;Õs­¡ZèN”È\}ru"Ü̹‡öñ±HÚ°0ˆQ+¬œ[üdf²j¡åÚ¥ð<)4u×Îõq4 Há @ºŸéQ/Ž©:tzùAZS¤k«bmÈ ¥µÖo’Öòœd]gAƒ$ 1GjXD©Ö)À¡Ó!ÿ!4'|–¯O&=®…ö¬Z=ÙËX¤ÒR_Í7•'Ö)»ØÊgŸ$Eüª'xŒ¤@©d&tÁúÏaÙ(Á?læ«Og`¥“¾Á´Ih?O§2ÚÏk¢‚¡2(Á©.’\·£ëéLÇŸ£ë:<-^ᘑcZåFNÌŒA–¾…¢$jä«W …eÞ ÿ HC6pÖ®®!“JW•Eб éò@2ª\Òr>Hzÿ H›$öíöÝO$1E÷ãkDëþ‚ËGb„CÆ0D‚9c­uy M"Vó tErÆQ(naßn ɧN”òðê %¶](bލbU¤:Û"ùDÖ|zkž«Ïޏ{BSõ\Ó ë™odÇ€)VJýäZ™8žL”•d¾£>Ù Ú±+‘éËþIÎcá)‚;˯ÓR=v;—©©¬Jg õ¤ðG¡ž–({VYÓP/A’ ”ÞQµ—¾KúRzêÁYº®ŽoÊ¥$ûäs±_*¹>–L$à i"m¸sCCÕÌì„u¤Â¨ÈÍ`Òß*¨-ÑG²¹ ýÁääN¤°¤VH˜n˜ò:˜·öH» ®a"¦lŽÂÂnÐ'Lª …š‘Z5R@Z Q+EnìúU«r² Iï[n» KUÛ;,{Äž©ÂÉñ%î$2…Bžk·Pëc#iGÒãÅ×js ‘ùv@ @*'Ï.Ð;a!YºªQQ)Fs ûNcÃÜÇʪü©^»…ÜØ—N$¨ žiùkà+¨¹'¡Êdºa®ER&¥\©b“t/Ÿg*†$ª4£Ž<Ým!uw¤ .“£È€±æTx«K$“µ aœÒÇ#§T0-RC¦Liä ùt"ý qö.xãÚI|;e’!°H…É!xpíÑkN½DÏ.¤iP$ )~*+«ªR)–Ì5+®p"–" Ò‚E·-gçnÕú@kÔ\ê-·µÓZiï%K5GìÙÕfª%kç÷ê\£HÌÙ8íí‹,½+‚¦þ̱ªÉô™eH„íâìÛ•Jž;'rÆ·D£ù#.±m"Bi;zn"y©°Hapì’éT:Û9Áþ&ÔFŒQ‘ü-ŠxgÑðcøëLɪª¹Æ*µ´Ô`‘C‘ä¾%†weT…c&½;¦ÑB-\ét{,=¼ (¡~]M=%-«A“T¤{˜Š°w¶©jíZ p)bÛ];H: >H Z¤í;Ø$©up’µÏ¬=k×.‡dÚý M#6EQ%HNÏËdæÎ§573OÑ4kk‚ûA"’V‘ ñªÉ#©Ø8ZBKA´„9âú RÉÌŠ´£#^O=ž©aöÌ<<‘ÒÜI 'åzu²˜‰—–šH)Q¸‘ÏHÍÀÎ )lt]nª@ñǶÃþ±#k©¸¨FÅ;%d„Â$­C „‰%hi@˜fÆb•U :)žII!K¹OŸ`kdÏÊÓðüÂi³5w’ÉÁ†uN¤¨5ö΄„˜*„I£+½lÕÚ%c(ÜP°huª ySéËŠ$‰XòÓ2-Žv!G;¤Í›{sAš–5BŠªCsç76.^¼øVœ*Ò¶ÆŠ(˜*¥ :½1$ÿê,Xt[Çmí¤¥b|©.£]»àô·_Qo›ºSÈJРw¥‡CÐ{Ô‹¥vÄ‘˜¥v–þ–;)X¤ àÄÖƒ”©¯OƒÒ‹ú3„¸²ºÈÑ–Iñ_ß°•]‰9ᨩ>M"!nnÔͯržæhE!=fS— é®$y iB9;,VY¿š@Z¿ Ó.`Ód“UõXkÇ IŽ¥Çum ‰ÄÝ„ UH½›‡$E’fÉ¿ Xž`†È¯cŽ6÷®2 ÒÔ3u8KDaT¡(ˆÚV¬„~kq­ëZÙÖÖˆãJêÏ 5.XÔ~ “ÔÙI4uvZ}äg¯|dQ‘Ø¢E‹,Ѝ9W.Hn^ÚÎõY- ªÜÌ1J(DÆ!g›ë6s,¯+ǘˆæÃBÿQ|¢² þ¢o® Ê­ QaC8„sW±6(˜¢úúšÊX›È±=¢ˆ•€xB‹”›†È.…‹K¢eÉz QJ—ÍÃf$$Ïçzz¬uÄÆY¹ZÝÙ± ¸´Ÿsß Ô¥&ÓD‚ Ž®0¥P¿®ªåOZHØu©]HºU67ý½ µ£æc”¤@ÝõéÖ(a¤8ú2NpÝ´jy€¤,Òt2Þè×)Ž2 ·®èZ×708|רØÝ¼ÆÆîÆ1¦‹•QªœO¤ª¤Åƒ2‘!j׫ZŠ–È¢¨Y[#ÌØi8ÙàË3X}HæK{œ»ódÖ½ãòè.HA)33+ž”§,®(ÂSˆÇ*02Šj©Ú¦h†Ï颠<¯.dòv 2åØåfì´=ª©¯©Š‘f]¨ˆ'(kŽ<=Yq)â.ÛdŽ“ðtvn‹…L°'jùRëM߯'”É{V «LE’8|  ­4ÊÏ’þ {Íù ¬L7вe U3áøh"¥Õk)'Ú7 ÁŽ×$NJÓhÁµÖveŽˆ£½ Rbrü=FÈѼ†Æ¶•ëú‡Çî>pà£z=pàî±»Ö®¼µ1I¼ÔìLi‰bC£d„,†òâ!Ë¥ƒSXˆAªÃâï\üÇHFØ?¿ËÈIÑ×…pV¸¢H¦vͪˆQ劢90¯±¶vNfvª"Q>Q¡wLc4Ž@/H[£p98èÚ«Ï&!Búã@{T“HW[G¯ºrРzf;ó×G“5 RËšNÎÄtšN· |kr:ÀJvŽU,<Ï1*ü3æªÀ/]FIÅI-5(R™¨ªªoZ¶zõ L6@mÈp…é§R(¤â@šÑ"m&›;ò ÙQpmËY £-4Q|ãFe‘–¶6M¤Üf=°Ó©¹­]}Ãc ¢¯>òØÉ—^yóÍ7Ï_xëÍ×_øêvÞ5з²µqî¼Tuu-ÒR©½=èˆÈ@„qÑ’%ùIT„¶(—£r Uí{­#·p×±ÚÓ¤Ò ’5IrnïUfNCccsk[k³B fK&âA#Ä¢y°ÑÉÖQdT$ñîJ ,!8@ª¯‡âRöÖBÃZ¹ÅŸ·ò`?°I•u\0ÔúÛ8Bf^è©~<{ÌD"š©i±9¢³)MJÒDùªoØÐ²pa½z4êÒQ$t„ZšpÂ4±ˆþ”@Òí²A›4×"­Aì!Ò¶mtä,ƒÑvZÛøµ‚HY#áh#­6OR~×+:vóo]¹và®»8úÔ«o^¸”µÖ¥óçÆŒ ÷uµ5ÎGoHÔ¼TÌÍ-h—žÛn³­ÐRÊÌbHó#¡_WWw#‚TžˆÍˆ„57Ž$'*lZµC‚ -…¡P$ª¬Qõ˜5­Víü9ôèæ76·µ·ÃÄp®>˜°ŠT£ >Ï.bV0B!¶HÅÜ;Q @‚è=‚s[®ö|~£ZŽj¤ ÉVïŠ4›T®Ó¼&ÈvtŠÕCÌol¤\~+ŒnŸTµµ¯íééëëéîjW$áÈZ?D†!ݺ:v’_ó(°|B¶A*+œi¨IÇ"¬Ø%§¤²ÝÙ…Ï eÃÓO,™ªc“„Q’§5ë¹Æ(A^ÏÓR0,œâÉü13"ZÄàœÃ"¬L­_v'%ÁÑÅÛ°zÃjBé‹ËVÔWéÔ÷"ú“‚äìß§yµvs H8Õ{™`@4*yÐhx—=b wH`ºþç;dˆ~{åê'¿³×ÅÒ;G¤¶ÆººùuÒ’29¾HˆŽXsÌЛ¢Žn¾Y(RÝD ³vVÿ‹¯ÊÎ*µtíˆÜp„;jÜ’Š£Ö¶®µ=k×v­”ª®ž¾_|í™þþžžveh«¡µÝ?5ç4ÀP/9]c\U|ùË_Î!ãËù Ç7´U ØH7gR•V}u¨â˜¨ËTžÝÜÆ¶uï(;ôÑÇ~†¥lö]ERßÊ6åÕBeâ%z-5§« åúsÍA®ÜÈ›£Uk-0HÕ³«ÊM‰ë;;Ò"†R[æˆ@ï—II@ p¤ÌOßkï\<ÿÚørmmí]kûú_„»Æ…¿ìëloV&‰\;©¢àh†ß"ù"£ppªN†"ñ.(‰A]0H©*¬ÞÖÜ®g)-H½H÷‰ª#Kƒw.y¢†LR,R\äHó`$1]»8ÊʧEó=ƒ°Ó5žÃ5¬Q82ú¢$4HËZÂI2NuÁœ] k÷ÿ¨%ÇâEùäÅö_?ÿŸ£¥nžžÈÌ­mV ­2¿›¯amÌ]F«·w}§iA]&•„ß›ê*KV¥2+ºþg6û»‚ëj6ûÖÑ{†oïêh†{ú‚fß1P¡’ ê±' g¯óûv:Ùò!„a¦µõâöé]<É+O6ÕÆ«èUM¦®±¹µ£½S×xrC\¯­¼ôwÈ :ÔÝݹ¼@R$U% /Èÿêk† ôÐ<ÞÜv{¿¤Ë*]1$½0¾{k_W[óe•,ÈA%/ê1&G‘â3:èÂÕÕ‚gVFVuõl<Ù-þ”«$^1GqÔ7øF6ûñ'W/g_ìïëëèþ«ßd¯àƒ|c¤¿»}ÁüÙå12Qÿ"\Bg'ÂÓsv‚Á˜¥©«TBiPp„”j×\©U]-ÏbF²ÒbHÌqHËEf*·«iñ²š(tx»R­jèZN/W/¸ÅHÜd#½¯¥Y1ã+ƒRð?Û`LÒ²tU²lfD‘õMHb4B3p©§³´”õqUä­š”½jjjÀ3®6+£ôÆææ%í·ùÎ/ ,{hCÁ†ž€Õ¾´µyAD¿UýóWUΪ˜]­ oíêV ]½zå*“£“vWÅ»»ðÈøØÖE¸w73._X”X €©“·ên¬½1e2`w,tpÍÖ Çæ±q˜avu$âϘEr²Ïyû<+OÕ¶võ Ž]ÀGq5{~dp°ppxìµìGt{xl°§½µvv"6+Q9"Â7QnÎïÎiŠLQ5ÄÒyrùAW6H!UéJΙç(±,¨|sIßlsQ‹A9«^™¤JÒnäƒè'cÇ GÂ3 M“H n )é+\=ž™u.èŒk…"8¶¯eL5o©OW¡’]ÈŒFÊK6\‡1C`ÊKÙ¾…td˜’ V½¬mT5·Bª æwÓ‹}踬OÙ_/ßµ4wµ*¤I—ºjÍx¦¢ñ¾‘ î°ß}’½ôµþχš¤löÕã÷Žõw¶«€PLêŒSfQbÿoUÏÛì¼ÅϬºý˜)®å\=53Mð2Cö»¯è-ñc¦Þ/ƒ¤8úÛ+Ù7öŽ Œí=Ÿý˜Ô_ ÷uµÖVƒü×ÄÔèb;ÒðXŸ¢þlÞB9È?ÎO3Ô§¡ Ž&,ë^VϪ$•E:…t>Τþ,URSK&‰¿¤*\ǧw„z¢ýây¦Ó;=£ÖjÄÒéjBTâ€Ç@v –°¢ ¹£AÊëEñÒršÖ;»úÆ‚«N/ñö›šàÌ¢¶‘Þ»Ñj…%¯ /bb*«9 Y€,ùVm]îâ访œÎöÎÞ¡o_È~ðþ{¿!p®f?ü ®÷Äã»’}÷Äøîµí@’º0¢Öòñ2ébç³BšUK' Ç’b8-¯®kîÞûbö2ƒt`çØØÈØÎ½³Ÿ°û ¤õç§Êcä ÒB>7ÎT–QÙ©‰ "˜jH%¾)ÑP䚬¾iûð̉ËÇÍ®ž;î™#%Ro-)IÎ"Qn9f5“sµ„¦ëê¡Gz¶˜±z–€‚c¤÷­Ñ1¨®HªiYÇç0ƒŒ Cn@Œ¤ž2F ‘ZsnºÑ8/èøß¬ÞÈ hë—Ö2{Ó½9É_µÔk¢Žß1ŸQ«1oÝ|sSþ‚ºÏ@®W£á\YÊ®îþ‘{œxâ¹¾‡ÁƒôòË/ÿäCc£N;¨LÒÒæÚ˜<ë2éâi^ùªŒ¥¥¥v_PyˆÌñ‡b4ª(+T Óì¯È4®èé;ðjö·DÍ û÷*’Æv»ƒ)›Èm Ù9mI'XüYüÐr±\S A EQ Ÿ$+y˜+|kyX'ë‰<¾g¦¯@jß# n(B*|q‘c×sK£‘dµö'C)L[­«K†dº¡Ñ¤³«™eiòí6@çy4¬)Ê ‘¤ëÀ FäÆÝxãnöñBkQ•ãp)¬½}é"…„éuùk®µ?èÿLA/²®/.÷÷ON£‡±´£ãÜÞ?„ ý•é_^Æõs³é^R&ièvH&Õ}Šôœâ€ÄFÜ"`J)¥ŸgýH-Ó-ò ¥Ø)Žýâ±|û•P µ÷ ï=ú&=¦ËÙ¿;z`¯i¯Ò·‡ûÚ3ñVš€³¡oÎ44M¼\ ±K’U9Šù¼þ8]‰öÈ3®çZ®LVÖÂfž«eP;þ 0¤¢\]ñ¡OÚt>Ý‘¾ :Bð+Õgâ49Û•QJóEb•-X-´á‹5ÉXسAr*À E£#pênú“í]²È>D±N$ÛI§@" CíãFhZ£ JÅ(þÐÄ®Ràâ¬F IR\`<Õ‘}q‚éL…fbli{ÇòÎõý#»HÏZ ½üòÏžýá_þ>ò»²¯Ÿ8vïPo{s]u Z²“‰x²4™È϶ǧšz-øaá1~!³Ç9ïc´ºKÅ¢Åíß^>»¶¹K¤ãoÑ#Èf9º¯"i¯ví²—Æ{ÚçTÄ"8÷¡`=: D&çTMŠ'Ø`žR¦¾&…BU£‘Êá‰ÆéÔ³É{¦m^ŸÜÒìA¸ª"–ê÷4ô«ƒ'Úȉ®9)U… kn¨#Ïš¸å²aåÛ-ßnCK•rì(ä ,ê¶@*U¢ Âè&åÌ-X²ä–œ• ´·I[ÐÂÔt£²&U“­ŠI? ÿ$ÛÓŠ¤Í‹–´/Ÿj¾ïjRWß½ió¶= ¤Ó6H?<ûÜsgϾü öí®dß:ubÿXoWsmuEbŠT€vˆ(©%wÁñ\vW¾Š"H¹é¡*ÏS¬¨0ùTËV•––Wdj[•g7~œ’vŠšÇ÷ïÝ &éz”W²çÇ»Û3åP06ý¥ULmEŠ* ¨P ‰âTŽ]ª úøB®cF‰v yZœ8 ÄË㸠Ü.âkØÑy “Gpt9ªc0‹¢c¦æX½ÅÜo«R œBݪBÕÝjT¹£ µ«ò@ Ï(%st“ ÅU<´`Áá˜X³jÍõùXcž÷< ê\ÞѼ 6“©,û½,8{ö¹3gI?§PýwgýĉñÑþ®æù)˜±=IŠëÚH) ‚Db!\¹t™ÐKÝ3 Ú{FvŸì÷…ÇÇÁµ{‘|»löEHÚ©)JbSrSçÄ0Ïúv­<–d¼<žJë茛gê`XC1d¿­©AK4TqÏ6ßûµÄ§(»ÀCi´l½>ReÅ£rEái©:ÏÕ%â´yfpŸ.¯òd”ü@α3ƒ®c©ñÃ;èÛÕÿÔ A×y—Y(å¹váÄQµ²FÀQÛ¢%ËD§×Eœ»Lé×Áu4ß,CÀn¸¡ä÷°Êªª3MÍK;:ñÂP•»÷ŽMwl쥹”8·ˆtáߨõkz7mÛsøQ?HÏ=«Ö™³?æ€âjöý'NQ µf0阞îÊŸ4ÍÙ’zþ›!Oxv0R³s­†X³ë´÷8~òRöwH#Ã##cç³—¯\ÉfϬÏ.Žk]O_GëT´:9®É3£¾<£s½I½jƒ”4”•DB4(Ì5ò#²kÍtdݦh w1K~õ--ãqØå‰Ýᬂ+@"1äðj.ÆÇê½2Y™³ä`›_͆;×­®JbAƒëcd@‚ ru–-_µ*¢ ~‚kz¤¦ùܽZP¢þÓÌ~%iR#)ï¸ã»–ÈL“‡AVU}õÆÍÛ¤3¿4 yöôéÓÏ>wVƒôÁi©y~4dʨ"Ö§¦™)GÐø„Iv>ÁõŽjVgªµÛ§YR¯©éH¤ÚÖ®^"”c¤7•k·{dkÿàƒxjvþAàrv%:¯€—®Þtý±«‡ÄRtpuѵÔÖz<¾œ Vküž]¦>ÒU$p¢ã#ÏLÕà!íd–d8šiý+"s—¹éAOXã6&™¦Çò‘z$m~OOwñ]ت%ÿ´@ «e8és$´aÃj4HEô“MÖ.¯É€¤;iÉ¢[–O¢Àú9iTf)\¤iˆxÛ yGPYžUuÔÛ+åFHÒæí»;uúYr } GKï H)ôìòÕX¥õLyl8_;gI“¼ŒÆ’ EÁœ±˜ÈWZTwÓM7išàlŽ.+šm‚*NÐdjÛ:ûÇöJƒtòø±‘þÞ¾¾Þþ‘ßxãÅAÅQkíœÌ óBåÔH‰]/!õu«Ò?¢ð’êÖ¦çyêj)!NeŒ=jæ‰=²‹ÍOtY%Vg¢ÙéãB« ÞÕÙRm–önƒp´0¦öÉ[Ó]‘–`¦ž.á‰ñrèQáçCžnÇpŠŠg&+7ܹ,YV¦Éê¶k—W"¤AªV56H·¬š"€ÅÇi‘øAê. ’ŧ™ýŠ Õ*–ãü¯M…|ÎzÄž1O½ÒèÁc'þöÙ·r@:}&¤ÚÈ`ÀqNóÚšR£i˜¥>Q¦eáµ=õ†ä-k1êQá èh…>ð€³¯¦ÒòðàO‡uÖSÙ.ù’­]=_Þ=þÈ+ä}”}]4:Üß×s{O_?¬¾nh¢HU”ÁD TÇÕ…ÇÕ…£"ª¥Ø'Tq*DŽ—‘” œí‡×(Ixºo`4H’jÈd¤V¢•P­Z–]á`¢Î-uÜFõGG@"VâX‚@žu>äê~|q=ëêa²rú¤ƒ'«ÆÏµd À·‹•­XÝ„æ[gÈ]«´!$p°gßt$Hv#H4»{b”ÓD¤Yê‹ËÉϧ¡ÌÚ‹Ú—ãÔ"}…óëÆ­ä›Xÿ~#ôTHkt%ûîé‡!Fª­®Àj× -ÇZÌ(ˆ¨“F.¨=‚Y<¨ú‰Ã¦Å“%ÉkË«-A#ø’%KZ-]º¨µmQ+&êéXÏò47·J¥ˆ¢‰MÓsj@9oï†ä÷Iérö•“Ç÷+zº»»{úúzÖB×ïœT9ò‘„K¯¦+§û}É#ˆ+ÂÊgÍÒﲦúwÜF§µ ‰5‰Ž]Nö;“Â";Qã0ÅdûtO’'’Yœ_“Vq–é’=>ŠÒs÷\íÎyÚ¬¹zĬéBצâÁÕ…±"¼É¿SÄÃáî0S=Q׃â8!û\»¼dÃŒ(åì0Bj[B MìÇm3ðlƒÎWì~’¤W¨i~*¤pA¦='ŒAZª@«)õaônìõù{Á öôeíZ¡¥xÁfÓÆùóE Áú`#}fþ\h©Åõùl>Ê/ZâÎÃÿðí:EQ[G;dñù ®«« ^wu¡¢×R<Çk‡Vñ.øPÇ¢æpÐ}cB¤TŸÂOw÷(ƒtòuÊßHŒïQIÔÓ×ÓÓÕ:cêÁÑxíT×U‹=é¼ø® ŒžºØ\ç\€§ IÂñÜù ëà)\\RÔ[S…#Â,U>žÈÉ ‹º»ÕÓ™<_¸¤ lñ$WÖ®ÉX“.õäK‰wdx…§³†®Iv»ºÚ娣DeI'GŠâ }t]0H”³ƒ“Ø%·Ü²ªp(ä3AÛ}²$ÐØjY¤Æ‹”ϧþj,Òªõ¾–§¡ Ûš˜£CÛwï×./F:s–’^àR õvµ6ÂFš Z’ÒÁÍj$0ÙÖ¶~ ÖâV p+‡U4ZsH¹6¯þ©µ­½«çöÛûz{ûéÚzÔÐr¥^ß){ÀªS¡@ŸìéiDz÷Æ›¢¥í]ð©>Xýý#£ûŸxâm9}î‰*HR&Iùvô™Ï$ÊÉ.ÆK§‡T›s}ÊÖñ=b1ÈÏ€”¥ðÌ!£¤¶\,éïD¢i^:Zæ¡F:3­ç¥y¬Í§»‘tfNÀqMë¹®6u]KÂÂÓýFZçÛèŸf>›=L,¡(8°(ÙTNïcg. ÈÉ/²lR¾kÇ Ýt“öìnY՛ї+›·m dÈ'ñã)•°@ 隦VRŒ„®'¤§=°1ÝÓÆÍCÛöŒ?|Jƒt%ûËç(kwF²v—³?ÃÙÎ% °$·­­kíÚl•kk[L»m1~°OùM}}ô¹®µh8H¯5Ã7yÚ‘­‹ÛZE. Á×]]ÀlQkhdþ‚—ðîŠk®þþ¡/«â‡@y¡‘]ÒÞÙ­¾y¿yddl/pô= Òß?yòøøî±~hJRÿÀ" J5^S-< BP¹|íx Ëƒëj“µ’ a:…ñ’ “8ù (@7\,O¬A¡4o^e4 é¨J‹é HfV†$ îºÖLXqçbF«‰>0¸&¡U6G!rþB”Áw]=ìÝs|’Ï"®ß‚sl‰r†þÎI6\g,”4€EºÅ€dìk.0DÛ ( IèÚA#¦)Ш|šQä–EBƒ”‡Qp§:sµyhtÏ!?HgÏœs¤çþ™AÊfOŸ: µ·6+³±²«g@äXû@vtŸÂáÐsD}¦V?Æ#ímèΙwùÖ6¦X„Ý;0€ð©ƒÃ#c;wPkÿý÷îÞ½{TýÛ½^ìA¬ÆÆÔGïUk÷èÈPow'8sí·÷õ«ïVŸÚ{ï½{ìW=ý÷¿¦ž‰löùï=qâèÝÊ&mݺUáÔ£îµèTÎol]Ùâ˜Ãpáƒ=ÈÿÚµkÙ¼õ ‚sO\à@ÿ|ïbpq¹)Ç–»B¡ŸH„!ó<» „H!sÃ7r ž4IÚÀuL™*÷׳–‚äÍ­™•ú8I’x u„¸®žÒÌÆÏ5Í®f?âúS"P”Dz,7ã÷ì®Ë‰’ߘ³ƒ´|y¯†ÈŸNP¬l/¬¤µÔ„9ýݘ‹TÈ;û4#”­dƒ€¤%Vü*†¡¯|Et6oٱ٠½{V‘¤ÖÙ§XýãìOœ8vp¤·§}z_Ãc;ïÞ»wïÝ;Ç€%Øi°Ç@a’ÖÞ½;iÁæìïƒmK ìmPµ-±GH¯½Š€£GÃ:vüØ‘#ã°ŽÀR¯÷#ZŠ¡ýûÕ;>¾_‘¡P BoÿÐØî½ûñ;ÆÕ÷?rò‰ïýýóraÇÏ+“ôDœ÷÷uwµ‘׈ éêÑ…ß= Ö`?\ÜÈ0ßà:é ô€Ò:A…ø¶îÊÞ0FÊíEªI•AïpOk’˜oÖ‡F–‚ŸÃq”Ãä|I6I¡[­¶¶&­kRyަKµ zT¦g$¡=Ï’Ó>¢#‡SR*î+rs‹V €´F@Úâ#I´|ËÒBÝÎ&ÉRÁ0çÓŒ"/‰Å¤M{¤ )¢ £´y{.H¿úñY…Ò?ž=û.}ä£ì›§N¹wîþ]ˆÑÞoœ{çÂÅ o¼0®vl5¸ï=ð7.\Ìf/¾õêq-×úísï¼sNm½.rÚÁ{{ð™×ž¦¹wïø‹o¼sn¿ÚÂðÕ/¼s1{ñÝ—žxâ‰WÞ½xñ—çNÀ:ùê»ê£ï¼pœÁ:~ü…×Ï_¸tþŽ+4F0=46z`|üÕ·.¨õæK'O>ñä÷ž{þe Ò•IRÎÝ~eÝö>óÆkßP$µ’KÚÕ§Ðø¹wðÒÏ€~ 0¬ê^ñ \ܸ)ìÚsç/]:ÿÚƒcƒ=*ZÌT$Ñ$yŽÝœ§îÛ$Uì—=ÉÔ+Ïʃt9 Q–¤ÿð¹©ãaQãòdÜIŽÚÑM† «ãÈÕ£úô,Lªa•S$“²p=KeR¦<ì†WêÊ­ôw@?’é&ðì i…v,ÿ““YØ`…|ªÂÛ·3HÝSéSÍPŽz{7j!ÊY¯!""î9¬@:c@úéOÏÂúñE.ù̾t Ú(TH¢<¨Áá_Gt$/ž;ªvÞÝwß½÷Àø¹óºIý]µ•a½N½·ìCIaô"}ï3Êú(v^<ïê1µN¾Åßþ³ïýŒŽBêô›üÑWT¨/éßsPF£ýãüÛàg”9úá~"Bþèù¿ÿÞ·N>r||üøÝç_ì‡8 Ð^;0¼óÀÍϺï𣧾cô/?ýéOüÓŸþo© ¸ <»ý£Ê‹êUÈØÞs—@ˆçê'WAöîÝd~Ž¿ÍêWyW¯ O?}æ]؇]Q_u^ùC}Ê£|‘„U²à[Î]@1=õõ—N?ùäÓïf³W>ùÝ'e³ÿoÁÏù·çž{›>ªÞyå XÀÕG_ýÝÇðÛßzä˜òð“ûÇÿú<üà«j©péßøòO~òò~õ«_½ýÖ›ï¾ùÈ#ÇÕW]Q_}q°ïö.ÒÚû8~ë'|é¯>öÈñ¯äUsqO©{·^RF=AÿH\Û­L™®jªà°Ô.%®]¯ª@ ³,–5Í4ä‰b–ÔûxzÚ†gfYÊ85©öq“~±LWª&Li+vñ -7|0°’îæîXéè3!W˰Àù];×g’& މØm)äÔíÒ/ H½Óiº£È¤Ú±dï¾¶ç;ïg³W´‘Ú©ÿ]}„U˜?¨¢Ž±7äg>öîTúžßfß~þù·E$âJ–{s?T4Ìýõ÷ÔzW´%è·¿uâøqŒŠÀ^|,Ÿøm6ûŸ?y￲’¬÷OꢡwÆ`pMŸ2°wÿÍ%-O_üú·Ø–^ù„~Ø[O?ý½_’ßûQöâøXW[muE ŽU\ØÆ®<Ôç®Êâ$â„k®]×óIÿ“?seMÐeqR9s2Ú²מ•Sgá™Æs3^ŠC:O&…ºŽîÐe¹ÏܳÆÈ|%iãu½œZ»<×.nƒ´@Zµ %_€ @‹´ E½wPD ÕM¤iR”’xv9Ê­Sx’>}U ©õ/¿½ƒ÷Ÿ:qìðÁQøNåB³‰ùÝ'ÙìÛO?ùä“ïg/_õéJ‚ÕúDïëìÉ„óøK>É^<ùJÖü p¾~n@PßÌÁÙÿù×Kú‹²Ù³Ï=ÿkkÏ£TØ+'1úÎ%nùßÿ_‚•ÁŸxõ ­/gÿégÔ{~5{ÔPú·B¼w‰>fHzåé×ý÷üó?üÀð{9ûÆÞá>0I ÇÖYpAê>\R™—¶Ëd’4bÚ³¥ì¬óVÇšŠ+-®/áÉ ‘ÈÕ±Ò‚4}èY†œºð¤#Â|£¥,Ñ'ÓáÍsÑRç´§9öÄ+”7ý¾] Eºé&Ë"­ZAÒ˵ È2ˆ%R!G£&m7]¦ÏQq8¤ÍH SÁ\øPÞËr¤ž}U¤ƒ{FG|]ÕÛìâóÏÿ]ÞÆW?¾ªmÂ'ÖÞ|ã(„ì*.Ò BO¾iñøIöÒ~mïTº”ËÙÿøÐlòËÙŸÿð]þÌUý“ß?ßûôuŸ\5?âržtìåì+o‰Ù}c?dèÆÆö½àãðýàùws.îGïYœª«?ºõ’äÛ鬚ڄP´šL[m}LRr žN¸®Q(ñ,Ž•Îóä»~цë&H60H«r³ Êpç§v!F ¤×Ò5P$ ©i‰•þöÇHC[¶ð7d!>Òê]jwþïý'}1úPl™pã}"ÛýMÁ+ŒŽ•ðÿý§ñ°ô­øõ–J¥éYý(û þ={‘Íþø{ß;}ú])ªË Ñ>¥îßÖ ‡,ýñ·è+¯~tÅ@ø6§ûäwüè?ìû‚úÀßîQ¾vº:Aà¸TýKÙ©ABèäˆU p±$—–vt­=hEÊ\3bÍu=KîÄY:#ŒG9}À%Ýž.quµìªÎëê~]W>#éD×ù·ÅOü½æ…-RçªU½HÛt¾Î¶K£Û$EÑ.‰’ ÓéÚ8²@Z…%« Rpˆ”—¸ é^\æ½|á‰SÞ¿G4ºwÿÑóÈÚÉÃ{ùJö½}Èê¥Ùß»È;øröãDdòJö—‡fd¤ïþ;*³5ÈfmÙ€Ëâ}ýÿÒ¿GüÀ²ÿùüsÏý;}ÃeåЉ‹÷Û+¿ûø ¹vW®\åËøàûo‹Óøú#_=ztÿøÑä[Í­AÙ_e!GÂP ÙW‡ßýÀØÀJðí"!ךìR! ôëkGj€±™ª(·PèPÅÑ2Å<ùÖtD¸žÎU3®s£ å\Ç3î£kæ\;‡áyQôrEÿD$Œ8sì‚VnÖÃb¤Ì‚ëÍ ”ÚMûv9 á¨"ÞŽç„)šp.Ø´A _#F~$H'”1b}þ ò†¡í;ú];\¸q>¹œýà¥S;¸ÏB÷ŸCþâÞÈÊêÌ÷m«t×»ÞÕ]tßn뺻鮮ê@·ŠîiJC° MñQ]¢c„BÄ+ j# V,Š;HUªÌ$í<œ™é 3ÍŒr˜ž`&8Ò˜ &cü¯ñ+FMÄNp#hRw=k=_ëÝïîÞ çÜW„fÓ»áýíçcýŸÿs¾ôÑËG_á©¿Ó¿ÄGO¿rô(B>Ͳ5=±Ë÷övîþzzg(BÖ¶É%)~ô?=$çJ¿}åØ;È‹ÃøÈ‘Ñ°SÏ{þ·úÛ8/Ìp½N yë{N;ßÃ{úÝw?¢(æ‚,~¦v¯Ä¦Îî…aÇæu·.t Õq{Å@j‹d¤.XB^KK¤ñ€‡nÕ” $ÚòÂÚK;$†±f5‘9RºâªXšsM¸ými×nÂFä+É} µ>Ó²(–ÖSqðDÖÜ)бAZ¾ª¬^V'Aò$¹*‰Nd×{eCu ]4EÒ\Ò IRÔhÈW HQD:W:KYÎ'¥Ò‡ÿöèðûÖƒÒƒÛÀL伿¥鈡WéRé}|ôM÷èÑ_† ãîðwž}…úÇwï¹Á®§éß<øã_¼ýòQ>2ÅÊ ¾2`E ãGÝýì-,ç(>vìØi±³üIèÓ»Úæ™£G½K¿3§Þ¡á)8&:sòH{v;”†~î?Ô}£/=úâi6{9ùËwßý¥<¹“ôâpŽ‹¤­¤k|ÛÎ’áAx­† ÙIe©ÝÌîv6X›èM|!ÔX^®Ç›‘QæÀ£æ4ÍÎv\d®ÏÖø‚ ÍáW1²TuãðŒ sJ™ÜÀ~‚Zò´TÓ$âVKVÇùø£Ã¾@òQ@: ļà ÂÝþðèÑWh˜îM~&TwíÚµ‹„ΗÞ>¸ÿàÁÑQ¾Wý§ð±ã¸å¥Ðv¡@]úŸ;}‘Ô5Ñ«çŒ(N½ùIc'ú~Ï ©Ĥn‘B‚”bÀLvQZмɠЛ,#ŒxmÉ }*nÓœ¡¡)>Ò¯"jbƒØ ÌØh+"7òp‰zï‘ü{ìfÃ<‰ª$­ù¾¿|Oe¼/™AZüÙAš0F Ûïê™Ö·0€t—?Hºo}FTI´êÞ5{ú–~ûöþyïð#ƒßtß’Ë릀ôJ æãß«ìW+©‡K¥g5H{vƒtaÏ«.H@ÒaºWý;ºç]÷´®£G?úOxôÌoC@:ó,€$æaÇÞD¼Âõ}Íw޽O ŽFFFøbÝ¡}##{÷ü "rztôðaèÍÞã/cÁ'_aïÐöMkÍîjo çÆ°çBZ7Fd³›‘º ý]Ks§dVg-w¿-;má|ͱZŽ)–ä$« É Q‹¤ø=JöH,Áµ’2é²zY<½ ˜Ý?ÃTž±QÞ_U‰VƒDHƒBÒ×ïAº¿ Vg@ªªº€h”i‚´~|PÕ^’¾ÿâ]@úîÃxpÛÃCO#1§417ÎbÓ®<}ê߉AzuÄK†F¸ûüö~¸HçýAìóÇ^?U:õrüèo_>öŠ{ôWôeŽ)>) é÷^qãÔ±SDãhdïÞ½Ž¤衃ö9”~ƒ½“ãGG_DP\8Ó ùÀx¾£"á<ù;›îñ Õñ‹ Yj½õw÷ÔXÚ0³Óýû×òR´DMâá[t¯Ó´¹0àY+û,)Š—e²‘Uë›i΂¥uÔC'A rÐäöî`—Ej,/1b/‘ˆÍ„GcÔW’Îîüjdbˆv++ŽòA*ëÍMøìHÒ*I‰íÖÉ‘{çû¾ñ n6œÅ›Éñòì=þè_}Û¤‡\@z o»w<1ïጅR;Ÿ<¹¢ãíÃe =6r\´o߃’¿=_‡Oðò±cüèïàÑ_8¯£Öà›¤S!µsOæù3!ª|ø$o{«®’zþgˆÀÈ|4HðÞû>Ä–ÝáC9@âËÀQçã0£¹k´íÒðpxŸ´=úì™ð~—Þ¤Cx?ü Ô›îw÷åËÒàhßÔR{û€‹ûöã Ãyx?(½ ,9z,z—c^~–Γ$ë•_ùt¾ô›.èìÞýäí}áñCÏÑ. ‰A=xðÀE·È3 ÎÙ½îx3Hç}`<简4 }Ѳ0Cf#a´¯­+´íf‘ûI0lྂ85šÈ’Ž<)‡SN(X¥´*)!~5§Îs°Ò‚ã(v2d1.«Ìî…[µÿ½°÷G»Ð‰]„ªiuEî'ð`öëüì[:€ Õ•TÅÙQ5f#H,ZO ½J3$ê]½¯@Ü92;Ðï8:Å™ÆOýköáƒ/À‡@Žqð‰P²ÿ¥ã| X/`<¼X0H>Â=›i`ö[H<ÀŠz€Úº —OÌÚCNäABùM¹&j ÿXÐeàÈ^2ž²Ô°FX3¬?¢:Y> Õ°?¸µ*#LøÈHv9˰­Çì5Æë–«ÉûÝ™Ò}þ8IÄy7Íꪊ ;¡*Œ4HàÇ…óS>&¤oÇ •PóÑÏžD¶<ôðÎá7𶃴痧øàõýg¹²(½yô°K£|?l#éEŠU/Œ<¶Ç¤C2»î@r¤{õSsCC`äÀè‘3¨,õ;rdôÈ‘#!+„fܱcïœáþœ•(•žZ·mMù™O=ò[_Ñ8NFàOàkÏ õƒ+´þ¸²{ä?ÁWHsHhžØNdCjgù>ô[ŒƒH(³Ô¥;€„³«4ÇÇKÔÝn­ÌqVÅ ª[¨ž±ú •ZÓªGAUA¼™ùaE‘ßòG“ƒè d¹0ãudæ-£¨¤¥Aº@úzH÷î¬ RÚº*1¤q0rß×}§UI–P÷5ÒCp†ôÈð£tÛ½óì;§HŠsÎÝèG1Î@ŸúÝ££ûG -º‘}/H>wzés@®·ŸîUÿÕ@%µ×‘DmŸQ=tÈQqÕA¿zùÝS$ˆpÏõÆÃR 8Ú¶õM›ø¾† ‡FùôwL"mt!))ôíG†÷Ò©Î>PßÉ©Šá£  @šèAêw ­ ‘úŸŒÁ¼çê»ïóg5¿ÄJÀ½ùªé‘Á‡|xxøqºí`°.ænëï?xð¬uuÒGoïLÉ4ò¶úZþ$ÎC)•:{r¸}»Ýý¼ÓÝ͇øQW9µ'^¢Àãƒôµ>|ñyŸÿÛ6}míÊ;V®E’(°½µ{'¬GÚžéuâüé!‹Ý±c×n|†$|g!B¹ß>|ø4´s{ˆH )‹µ)Ç‚#ÙË‹°˜;JíÈfÕXe,’ZMJ,Û&XölÄ !´co\àbHäÃÚ D,¼,/°<±ÁUÍ›'l±"½DkeäBVÜ’yX´Œ"ßDßÔQ’w›Ï)ߺ@ÊHUqaÅ qnwïx ÁÒ7}j' ý'·>eÃÃß„€Dª„OQ £¬<½zÊL{ðøž—Ú‰Ö¾‘}ûöžáÃ^i?Þ«ŸB×md׎Áíƒ;†ˆÿèû£ì}üqrÖb‰ö§ »xÆoCû7Ï nݸÆ^ïX»iûà°€ôÆîƒ^é­@:räè~z.b}¦Ë‡v¿AýyÒy/u8|Ø¡GŸñ¸©·«¥)åP@rh€7³‘ ò[*£ùK‡;Ôø³|¾Cmn>bùÛ®:i¹/qøO­ävàWI¸Fþ™„¡ÄQYžžGâXmg¬ÉÌšç:­–tà U²ãºÿþû3 uTÒ„ Äȃ4…@ZT‘£ÕÁpuõ]wý1ÕHk4K¥— $=üð#ÃÃ{_•!¸™>v%ý[CC üyU‰:A ÷äÈÈžÇFHö9ŽþXöNRj‡÷*<ôÒî05îîöüèÇ¥×AWûñ¿‰Æ Aš}ú-WQagpËú5Ënì_¶f„¤‘þ§G[¬|pËÛ4ìÌ x>yxô »ø3îDeCCŠÆÚhÎ÷IW´weÜOºaæ€<#+î@$¶G,¼s•¿>ÈUC‘6ª9A>i¥3'ZãlØN…Coœµ¢Ð0F%Y~Ã*ë»ñR»‹éþ ­¸P.œ£¤Õâ¶~}eŽüÕ’Ú1H¥Òÿûü)‘8ù¸#é¯ùëáGñ¤nco—P:󛿇ÊÃÕ»öœTÃq rŸ®‰J¥ýì?èS©Oó@:ï³o׎í[6oÝÆ ùGtÀîÑI*Ìžƒ¯þÁ{÷Ž@¯\ä2»;ÎÛ¿Ô;ï͉]Žêf¶ÉýùŽ]oИÅë’ÿíრÅP mX)Ì¥blý¢§ »ô> Rg£ÛQÁÃrí—Š¶©¥Šßh7TÞ£dÈm]¸7.ÓBê Õh®è˜W­mÉ-O4óDUµóõ­Ùî÷8 ?®°˜‹oÏ,H÷ßOsŸH.#K Ícî¼÷Þ{Ç>=ZBÒÝw¯þ\ÕH¥ÒóÏó<]食>þè£0˽÷ñãz(áÌÉŸn›·Í®öØõƒ3:\ùÒÉ]Ù ~3z´çóAú´ôáÈß[^·+\át( ¿úñŸÃ„¹{øKëYËLe¶T»Nb"çxd‡blRv;6Hó¤å ¤{³ ÝÇ(q×N§v7 ÌZš*ƒdíE#øwCú¥{dGüÁíÇ ¤Ç¤Oݽù‹Ý. mܸiû ¾~Àq4Ì8øê?ÿaï §EYÌvÿxwHOËGˆH;Æ <*%"¹/Ã%Òñƒþ¬˜ÏÅþmÇV 摸§E.%P%ù…Ìû¯¶«¥®œš7¢Á!++_й4å$AÕ†sÎÈ1Åð¤žRÆôÝ3V¥k2R®R<òF9}2œ°R_#I²Sc¥v8GÑtg…eH>&Q’÷²©Há˜ùâ)‚n*®[ºÛvwŽEÑ«eOÒª»s#Í™†CÙÇ÷Bߌl_>¼d׎­×­Oì5÷lØäBÒî'O*{!à3zÁO)À‰Ï§g¤={F$øªO§ܦ§’?Ò°éˆËö††s¡Á¡Ý# Òoö9n¹~îìÞ¹+!µû©iÛÖ­Û*F¤=»¼+䎻ŽS2ªþÜ=÷D =í@º•@²4ñjhÈï¿lïî‘isR·ÏíjiI¬U~r´û˜=i¿õ» »:pÐâ%å6‘±²gÝðfYQ§ú¯—r ý¼øüO„jÅÓÀ¬±ÑÐn’Õý/éþû«HHÃgá(ÒŠîÌ]†ÖåF¤çßçaÐÓ?:K^FìÚ¶yÝ=~ãÄÊ5ë6m…³L°œcçž ñd§Sö9 ‚íóx'þ,€ô„€ô˜ß»²@ú7:—*•G.·ãg÷ìè#Ã;Á|Rµ½/Ðà{Dz…}³.[³qû™"|cxhp0€—ÒÈî¿Üñí;ô9“©T:¸oä1÷|ªAò <ÈñÑŠy „¤Æî®©Ú#²³Ø8ÁïuvBÊ’ÔÂ1Y)Ásæ²Z3”<)Jw ,ÏH„è–ê]œâN® t0exÓ2Ù©J¡ÄvC†ü#DôM"÷x«ùX©ŽÈ"H+¤;+ƒt¿´ª‰‹œºÏ€~\þ𸠤\ŒE~«yHl›s¶tüG‡ýó¡C‡NÑ(ú›Ùmßämˆ!"¹›BÒÈþ_œfoº mnÛÙáæ¯Dš!ÇdvëÖ­s¡„нÜth˜:p ‚ƒÌnpû–¶l@úЯ@r!iá¢;`¬ $BÛ]*8´'$0pØåíb‡vˆb†weš >7Èž\*úàþ›o6\ãñSYÚÀ©Ix”4ëÚY”º'¹X--Öã•y"K¥ÉVªŒx9nnÁQ\L¸p”"厷­-{GºßQS‘†ÛñÏ0µ£9 ì…“i5Odû&-Œ‘ÚM¿`бçëÊHîÞ HYˆÊåQ5!H“=H¾Ã@º³F¾Ë€ÁÅI„ÏÏ?ûì³Ïã‹»‹>úÙ``îÈ)RϼK ­„e'k×¹ D#ûy_ùd=+ ½FÓ¡ÿu^çJ ”8îE¬\H°rÓᱺ x– H:"ùà5äBÒê¥Ë–­\í÷õ1Hoy9+HíöFÏè KhG@T±chF¬Ðý~…ŸÆoöùyª=ÜŠxdfwµà L+r…ÐL®­ÐP¤ÜR;0jw¹_ nyƒU(döÈgCÔµŽ7² àG[Ë‘Ý50l$`¥u€äm¢}O¨±—ðy/öñÄœ}’òwÅã¨üÄ®lÑX.Hê…þ¾œ˜tlÈ@Z]$i^W¶ÙªŽ#©- ÒAŠ9’ˆô’L<û“Ÿ<ûz‰•¨ïÿäÈŽ9ò>wîÛ³sûæ kïYV9޾7¼æ&-<­ýJ$Èì>妨Áýûöíã±…s¥WwípŸqu¤·¡=÷ð#;‡÷²î8€ôìxq©7 ;·mÙ›Ë]Õæ>Dú#{ÇÃø¥ám/jÚµk7Ê J¥gŽHçJ¿‚öǸኪMk—Íîi…u–¡á`íŪ¤ÿBkͯŽzºŠ~«Œ å3‡ãZÅ€a 1H’$:âso ˆP\Dºtk„)Ò×ZB…GϹÖC.Ü6#~^¬D·¦‚H(³ú’w1+V¯’ÛÓôõ*AZ®S;k+0r!K]¬ÍɧvýÒê\ÂFiĈ÷𝾷 ¤Ÿ<óã?ó uÀa4ô'î¢án÷G Ù°ö¹øm.Û‡`%ÊqéÈÑ7KJö©o[/—8Çç£þŒöEÆà§Cã֬ݸ…‹€#ôçlvrzÔ¯ßÛ¶Í/#eªû”oÃæ–mlüÚ×Ö»Bkh÷G¤ãîOvA‚¸/¤OJ'÷„Þð!./=쪺—åi=ûn š-n]x I ."¥,·Á¥à¸:Á7îz|DºÖWI“¨Ý€ 2º)IóÃ"˜—µFF‚$·¢;]æÅ Ë$jéÑÀ­adÖ ‘ó®)C¯b½£Œ: ¤™°2S›Œ”iZH«VH~ß½’%>ËZ› R‘4H1GÔ«[5Ha©‹ I§ž¿‘7ñ¤èãÒë¡ýíMˆÿî¹Øñ÷a'Ê“¤ÃÁ‘ùÈÑ—åMtï*ñÆ /¹}‘¿èÞÛ7Þã@Ú @‚£a`fEpî±AûÛ§jO¿úS…}¶ßƒ[~øÚSÃ{¨“á‡ÐýäÞÞ–ß½¯|`}¿4DO ï£r˜æ{Atáh¡$—ÛnPG±BRâ+Lòð X%A»Á‘4«§sb£ I)OyæRvæÅ†tu)4Šœ›ºÙ,jMØä• Fö±?wÈ=Ÿø2–?4±¼*Ãòú˜Äð©-íˤ2_»òeÌ Òòå1Hù ‡ õH6€T…Jõ(âˆä÷ºx>o·ŠÏ4Òzßí9Z­0ÊÉQtèÐ~ô¡”;ïüû¿;ö<Ïï:¼àíØ1øý3¥3O¿Í:—ÿ€xA ¼#– ï`@úTPu¯B‰tÏÊ•k¾ä9Ú’¬ó!Ó’J`JWúÙ)žŒ·‰mƒÝ1'xÃ/ìÛ7âõÌ–ïʽ‰ièÙÒ³£^¹€ú‰³¥—àùeîcÀ|ÇÎ!ÒLjlOÇİSiç°¼pURS,À{%iÖÔö¢¯’T·@Siá—1<¨di"‚œÉv8@g“”:ÜHd½%Ÿ»²$-+KÈÏÊ:çˆJÚ': ¶ççKQÓ®PHáî,éÞœ†Ã}H¾×‰N_+ƒTwaQ³aF¤ Ý *­R ­(«‘ÜËóÁâKXLD2Ô3Ï;Žán0œ$Á‹ô÷ΔΡ8lyŽP zN@ò·í©`| †‹ïÕ»wl^+”6lÝA@¡·më–­Û‡þIæv_’\"æ8:Wúè]a¼dôÀÈîás¢tþlé- rÑуìƒ1ö384{ŠûˆØ«÷ò Ó{ià‹üä=lÖ> IDATö8ŽvìT ¹’[×þ JJÉ?Qþv>$arç@‚* BR]mšÒže™eåsGâÍ–×äQSÁ²·–1rïK~hmªu=Ô„ öð&©›…ª–P1d”œ}ºl’1ˆ,\8H*$•£¤í¶óA²ù Õ]l4’ö·i5·"ŽÊAZåAò.BLóù¥æO>É!éãÒ©?†>Œ!$=|pßÈž|à^Òƒ7,šõÖpÒ'8¬M7jé£gƒÓ{Ü߇£GÂTžûàã®z_·rÙÊ5ëzƒœŽ?Ø;4èÝ)·íÄÆ´Ï4Â&˽OƒŸÿ9²^ 6?Þ.õÝÝ`”»r/ŽÂTÓ¿ŽÊVÙ£¡m÷;|ñxÆFßöa÷\Á·ÿu2á{#tÇwŠîáÇûvÿ%ˆ„f±ÕE™4•Ía¤ ^'Q ÜÊÎì.^ZlÐ0,Ž[qmäûÜЊ?² ’…¡èÀ#x8ÎãXi©3Y²…唲Y+p«ædY³Ç«ÎL¬þ6siE9HåÙ‚Þô7qiö´)ã€d/6D’AêîºË?WÎëþø?ËpDÉ›ÉýŠätgáüàþŸ~l~H:ôòwžF¯¡c|Pã‡j? Ä´¹ˆ²ô‚¹„{nY¶låÚM>ú–Ëì¶lÚ¸ñíƒÃ¿!Y¬Ë4IO¼ð<à@ú-žx-ž{üÀ[p†Q>ùzì‰^=Y:ùcÖ©=úò;ï¼/~ðl~BOýéÇFdÄ=9 •;·¿_Ü·çÕ÷þc øèûÌÔ—¦!ßQ•ÄW&uBrG:£Íx¢ŽD×dilª›àVXSì”jDh„ílC³¸dÆjDR.žX‰)ß"*ºRM°ÉŽš¢-²ã´â3€´`|*6!Æç¨¶–Aš[’À†Žw†£`4æ"Ò·þêÑ”“MÇÑ£>ú½ÇŸärWÛœzåW§,÷Û“¯¼üv‰¢ íz8¼¿Kìr'¬=Þd´Þ9úì›ï¼ø./‡©@˜ÉÛ¶“AzVnn\¿~£ IOK¦Yzÿ•—Þ÷}öOK=C4€Q믎¾V‘•J¯@oˆUï¼øú)ÈAO2H¿|ßwH8š½éû!Á‚ò½`Þò*û„ïÑwv ñÅÛoCƒå? ·ë€–Ü< ªŸºó~ÝS{zȹxæ¬noËUË‚lÒØáz0“Cd¯¥ì†hÙ—Q > Š„ÛF›ŒÓÞ$¤Œ$AìwG›Ïò<Á8Ÿxq§ƒÜQt—z3I&)ir‰Ý¿cÆ$éþû¾ãA×PHáÁ‹Jê£$ßYåPb´èåËv÷Þ¿å¡ÿ6ü’O=ôÞíÑV OKjGÿ6ØEžd#†S¯8ŽhÒñQšÌ–]§²÷ý™w>rŸá]ºWŸò™ÝÀ2?I„÷ëÇ¥Ÿ»€´q=ì,ßþððôÕ?‘çr¶ôö(·ií„}'FÐT­7÷F¥3¤àéŸ#·~«ï¿y+¤ÒÓ»þ»jvƒ h[Øîô*e¢€à'¥÷n]ÔÛÕ1Ñ¡¡œ„ ;ZùTteRÏUÁݲ;?rîw8s}O½eel$¯ÃâH¶S&¤‹àCÕ„GöÔ’Kìß©m~lõX¸HM®w\ÐV?™´I$•MÒ:çÁ·R íÿIbÚ² -&è s,ÍÝ}¼iB¤Þ1@«p?Uéî»|´Z®Üxï|7€ôÈß<ú·~ðÿÍw¿û7Ž$5¯wîì¹ó¿—AØógÏžC?_,%¼éþ™3ÁàÇUÿêÊõãÑZ¡³. ½ËÉ”¿‘ßg¶o¼gÙÀ¢?ÜÊwðSp.´võޤm;%ÓüÝ9÷Õäëé'Ê¥ógKgiïí^9¬ýý'þÙ{Kbëãó¿ó®Þ³m †ÖKÇý š!©ˆ`òÊÏ]=E'ÅgaäýléÏüJæÆº:ÒN'4¨í_¾S˜ðs$ͤ˜ä*¥nÇ?•åbЦ/øYˆÄÑŠghÿ2Õ+äØM&á6Úv.²)” »åY¢3Ixƒ ,¼P§Ë´o,ñ=”Ú$Ü— uð_¸Ò$ôù‚´ÞÛ`¹÷[ ÍË‚ds)¹¸h”  ¾f8 ­Z• Ò=ü]Ò£Ãß}Ä]ß}ô{®žÿ$^yw.³¯TzûÇg(îœW+‰ŽC•ñº~ÿóíý’îíóâmÃñ^ýëÍV,Z´h`åºÍÛ^c\@Z»ò–[I:ˆþQˆ+½àÏëñö"pÞƒc#bRÏàmý9WüÜwþæ“g†wø‘Š¿|‹žÜÇј»zJï<iv8!ä0ˆ~Ûßt“&ýÒ, u¶cH’”]²XC ¼Ùò›M|T3üVy/­Ì3´t@ÉÛáJö,Ùê'ä¦]ŒÉ !u©j¸Ýêšš®htW±Xô?760I¦Å ¡ÿɪ$óâ©“Š RÐ7$jwxPCX¶í¡QXŒ?ÊRÒ,£ (-³ñY‹Q-MkXµ‚׉±¾•äDF ë¢~R8°ªohh¼¢±8±½½­­­³»»­½³»«sb±Ñ½6Êk¤ ­.©BXZ¿AZ±bù⊠e»ØÉQ©AZ ­úUÜëVñèîŒÜu7t\n' ?òðCît$ÑrVºuNÅ÷òÙÒOºÀóafa«{ÇŸƒ¶ Ü­Î+æö8p@}Âs¥3lÅí*¤…³gÏ^´lÍmå;xÛ–õkWÞ0àJ§Õ.ã:ž]i~<ÌX<ýQìË‚_~hX›²€wØ moÒ²ÿÉè¹»_ƒÉß 7m ìðä6À#Ì]àíòØçîÁfC]Ši™¨Qñà3©…-/;½|u–K³¦ºWpP ÕR4J=ŠÒmõ.†Úβˆº²"f¬¬~ G·¬ð¡Æ-މÆXí›ÇÝxØç ßGĦ¦Æ+&:ˆ:§Î™5sάsæÌê™zUOOw8)I@š·€Aòn«Ëû 1Hë…£ H-Mq§¨RI]侻)VÝG€tIþHéÓs¥×GßܲeË7r$ýCyÛÉgÞW1É=òÁ?Aƒëñt¤‚ZéçÁ•gÇÓ%i©9æöì~lïiÒð}\:ó´GæS—$mX³lÑìÞÞÙsV®Ýø>zbË&àhÁÂEKAÍ:¬Iß//EÝ9ã¹²ÃÜ•_ÏíÜ1¸mppç®L<Ù§ýúóóä%vòñ={Fžø@}¨{§_»ð³öž{@ž†K7ܳfå­·®¼gݦÿ`9méôá©NdëRªPŒ8¨†ìÉÛE^^ìĘä7SLòÉ]­—¯òr Ü@n,š7òÁ©ãÚÏÇ H˜ÊÞs¼®Å%˜Ã! nò±kƒµª©ðo¹§Æ®Šš\çšäÂÐÔùK–Üxã’—,™?gΜWMÚ݆1©"Hý $š¥(›?ÍKìÂqìŠåyÉÖÅLŒÕ„;i¾D ¹Øy7ªTbw·—e(ò¹ÝCHzø‡Þ{¾T:óý?èA¿ ÷A(žþбð©_¬üáëO<ñÄoJ¡žÿÀzï{~ëÐ÷Þ󾿃RÃÝǧŸr¯êîÚ¼}ÇsÁÏѽó‡?Øå쟆Oðéy÷ ÏüËî7Â×,}8ºæš+{]HZ¹ö5|ô‡×®¾læ.¸H‚˜töúêoyùÐv°"þ@ùÓðBP:óÜà¶­0¯´mððÅ~÷;x´ôÖîÝÃC ‡p_¼Ä^ݵ ÌÄþé´œôŸó¹í›6ÜÔ¬\³î?ðiüíš•Ë}eÙ­÷¬Û|Ú}}xî¥÷þbã=¡D/bm£mdìöt¥PSLìêá†COw»×Œ×ú~Fµ$¡£†½,ü)—ÇD­¥m« ¯káUO†F)è, O™ ‰`ÑšÈ@k4ƒP5‹!÷=8Šn¾éæ›nZ×|‡’KW»Û&Ž Rµ E+ò°Bº«"H&ÆêAŒÏ‘©KE¤å}Ú=º|„ܵ}ûs¿>ñÚÙ¾õO6CÐtO}ðûþCO¿õ”Këà¹@±öåE‹–Ýúg¶Æ%u_zgÏ^袥Ëî~ýëçþn3|‹_ùr¯6H–šÞJ%‡·}­' Ž“zf…øÔî©Ý”ÄkºU3BÊ.˜ÌÄ—ÁŸ—¥T,Øç[±ç¶VyÜm¡n{Ò䆇0›R¸ò¥‘«ð®(BQä Zr{¸nv?n»Ùƒ$]{-‚T' Õ9:ª)[%y˜0,S÷Òq¬©±®"G’.#éê¹>¡ „ä(öîyõÑòåPRÝ ™Øò tú¦KëÜó÷†]ë!R ~û‘Gvþõð_Ã\жm€ ºlõq„¦~¼okN[ý=v+´²Ý­è$@g3çß׿3”ò›7y°ÖÁ£Ë¹×öž®÷—ßuÍì¹ýËnq8ŒnY6°`îìÞiîßHrÙÝ&‡’¯‹\R·eÓ†µðoYˆq5ÑÎÁÁA||Ù²¥ËîX±•ž— SîÚä¯þK»kí†M›ÿ„Ÿú:Ž5×\sÍlg`Ù²e_Yä“Ξ÷Ø¢¯Ü O|/ðŽî9Ò4µ‰RÚ±º_ú}rÔXœäP É…¦™.&5…B‰R’Ù‚Ô,†w$³–@‰ú MØIC\-Z «#Pn¨MÁ^‘4b¡û⾂xç‰b{[÷Ôk—|õö¯Þ®.n¦ÜB’—ãV ÒòŠ [S|TÒRib Rm|I¢êC¤i} ¤$º*ôGKIì˜ùÆw=ðÀ×]8 '·«ÿ+%ضm0GçÊâ­wÓÊ[—1Þ`òh¸Gݽ‡¯áÞÔÁݲkð}]Ê4¹ÇüƒîQÀ¨÷šžŽÖ–––ÖŽžk€™¥p9ŒÎ=cZûo†ãk`éšµë7=Qeë–M›Ö¯]éÞe ``™Ciæ¶ü韺Ç]4Zé’±‹^?°ì–5k×ùç ܬ]³ÆÛ‡¹ëˆ4àÝâžÍ=÷øEÿÔou¡qöUWBEs%°ôå…_vßÈU=-»Ü# ý÷ºf¥Ïöf_ÙÕN…¬XÜÓòC»ðIà–ìv1 ¯™ÝÝ]zWKb%dXf ×’³³¸¡tÙ…i9ýÁ]8p.S|–Îa© bŒø1¥ o@ÃØ—¤é„Ë‹íÝÝSg.¹ýޝ~õ«ÿ×í “Oî¤kÇiZo’?’A*/”ÂúÚ‘—M튤ڲˎwU¢H@ººO@Ž˜¤U•8ò·«`ÿžüýßøG÷º'¾Ê+_>mÄ͸›@«³zM¸=Ælÿ°ý„»ñðGLï Ý ÷±[s#Ž&ÝÕÚÕÓÛÛ·ÐÑ1pý"W¹xÔ3eÊdO’û°[\¤Ú¸Þ¥}\ÀZ9ÐïÞ£oîBÿÕaËÆ _ƒ|pÙõ ûfû²h`)<­ÕpÏ}Z÷¥é ¹ÂǽËW¼ƒK@ÝÑÏ®®)W^yMo/£.÷ȾàqßÔ~óÖH>ØA}€wѪõÍ»â$’üPÚ Az§&0,­#})µ®¹:o†ë Oñ.MK«ÍÙÁž–ÎÙO24Ý’þùª«Ö¯ÂmÄt‡çH²;¨kWH}e ­X=H$Ò iú”pl—»ó/”¤Ú±0b¾´bEFHK$îóñ¼’û£µ4ì·X±zÕKá^œ;;äláFìÁïŽ.—…Ggû`äî±–‰'¶ø[ϱù×õŽVÿàä®[ð°»#\õí*‰ÖÖ®É=ŽM›ÖñHrAÉ3wÑ¢þÆÉfôøKþ8îóO™<ÅÑÏjnŸÈ=z•û’ðu¯ºªçÊ­¹ë‹W^…Ͻ×åpðdŠÅ¦bK±ÅÃX¹ß€Æ¥žî[ýb~3îñl¢ÌKuM-‚ö×÷íuwcëg§ú>xm@‰$ëa!œoáŽ'ŒL®’Š| m;2ÒC7²G"aQ >ÑĤX á$® GG.‚vN]r³gˆAºí6ŒÜµäæÛn$°k7H¤¶[ž)'$Ý«9R M ¥£|Ò‹åHE$î¯Èiy9HÀŠG é[áC•‡hñ úrõŠ»C{Ýên>ÿÃ=×âó±‰þë wu—¥nllhhÀ›¯jõhÁ»B%äÞÝߤ­ð8(¶PüØPliëè˜ p8†#QÇd¹{!ú„h5;<™ðÇÀR/’( "Ù•×\5Ã!texæxu@EÖΑ§‹ž9<›¦&÷Ô›ÜUtÁÕØÔj,áý[[¿ÐÏúnupØbi”Gö¾âƒºmßI†B©Ø>5„#_)Mí*ú ”¦äÃh,õÞŒ^YŽÇ¦z/…²Ñ¶ 9^¢C^Œc©ÌîÉ`µ< tëpSuvÏÇ`t‡ÿÅ´äZ‡Î7-™DZKæ‡s¤žšsAúCnÛ­¾³Œ¤;£˜¤V\ÚÚ¦†äÀs¼w[k\ÿº¦Àè¶mmð [‘F`£!ÐÑB{žMMxÑàÅ™@Uiœáð™ûÇÁ@ˆÅ¢´õ8cÐm¹A„w“º©w7kÖŒ au÷CÊÇ9†µÈV ¶ÉâˆEžÚ“yVË&C´ ÚêD«h© G:Èë!¯»ƒ‘ÿšÞó§vÞé©ÝsPsf†ï£'È–ƒä'ûúB‘@ºA@Ê$wwV^ý°nÝHÅ1@b$.£`~¬V¤(&-Ϲ£œkñüçº~Aÿ¼y¾^™æ1‚bºøö¯ 4 ,ñÆZÞ®þbêêê››!bv:ð‡_ð&dlþ\<Ž—L0=qž æf‡Js{ÕÖÖ^Tô6EOprßV#P#ÏßRÏ#´‚ -7¦Uw4Áú‡"nç,¼g@÷nbñr°ªeÅ6••T¨ÚçQÆÍÉMÒðòY5AÅ• ™'§/c´w z‰Ms6|^·ä¶Û®3§»­}"¨íÚ;]¼ê섦Y=­ŒD¨H7Hq•T¡Xʂԓ)Í娜¤j0ÊiÅØ Uæ(Ä ¸®ï_°ÀAÔï1êû¼´¶€ûT*wÍ«û¬N=þ€®¼Ç܃ð£þ=HEW'¹ ÛÑÔ xt†_èr¡eêÔ©”º«¹ÙÁâþóÄøŸšÛ £,úÌÌÅÿ# T½˜Ôßtµ)Åza }›z>í mÁÉ?œ²~ZÛ, í/*h?r¸åq¡ØM]ð¥žIÿg“—ƒSH1Ü‘“l j(¥ƒásKÉ£¡`–|·¸JÄTD¾ÀÝp2ã§1¿Ú´¡Ø¹„1ºý«.‹ƒBÈ¿5^~yÓE˜žð™E[g×ĶÎdÖ<$ò‰¼óαj¥¤Å¤¶bSm’?ò.ƒ$—£±1* MÏŽByT1ñÕß¿`ÞE}W_í8r !€›ÏzØÄÉÏûÃteÁß?î_ Þýó84%QÌQx´µ#dir5»ðÓÜÒÜŒU rã„Ë}Z÷£¡ža§ÓM“\f‰éÔðÞoeùK7‰´«íæØ…‘|$~°‡qHÆ|vWw9Tñp¤4c4³}bh9ðáœÒ€lÂ{+ÓDmgç}6·ÇCØÄZÕËŽDÞ*"ñ¸mp¤¤Q÷`QÚÌÀÐí·CGÁÅÐ5]ác6ÎrCnë^—.‡TÒü 鮸Nª@ŽÇ:úó@ WyŒÙÿǽܷíAšÁ E$åu¼óâÑâp1EŽ#‡‚Ô‡ Á4¨ù@ &NÞíVlh|DråV±™BR`IƒÔáøAˆü/­mÍî4CL¾5¸€TÏõ˜Ú¨À.o ­"[+žJÔê –ÐXôZ§lhé—ŒDðVÈ™|Ë¡Ð;{føìnÎŒYÝa®‚Ü·S-“°T ñ ŸU “ArtdgH£f9ð‹ÿÔʖ٠↠]m%Ò¬›o^r“cÈÕCÝmŽ"Èéë@ hýì/vÙ ½$H IBdwwUÒ išÉjXü÷;F˜ï„ØãU—‰H(²õ³«ò1›£þÀ‘€Ä©þ3ƒDæMHSdÂîAj QåÓÙÉ‘ØbŽB× ÿFÍ-- ¥Fþ ~ñJõ4VH G\†X%°ä(*'›!g}¶aw†ÐN¹È@Þ&ö©ó£~.½sˆCi†ÏíæÌ™s:¢ä5 †Ús Yw“ôÔ9>¢FË«Ëj²‡ƒ¡Å,ÔÑ 9WbþQÐ4À¸ÄÌ™“¢‹¼QOžxêGþ|D(dAš<]/v¡¸¿5/¤åqD²šÿ÷?fÆ– @y$ù\"¤ÐÑ^uáñ¨/(¤¤V)ýŒI9Î œá!IqDò=„ÖŽÌê¢P µ6·ùxTlö?W¸Co°±A5cXp–ˆ!läbmÔݰ©0îÞBå§e1œåÝaD ¸ònc )õA©'4IÝí>Ûä䑚æñéÀbw!!¬ä=Hlè­» 9{£«±e×bYiiÃŒ<ü;´ûË·ÿ'Í {MÒß]¡v]ô¨yHó4H|–t×]Yî¬T"E Ù²€“^\L2œØUŽH¤\Žò› œ£Ï¤‚ß\à/Ù…] ˆäëë‹ÅÐ>hë(£¨£-p¤Š£¶± Ò]v¸êëòS;VSÓ”œå•utpC) xÔØŽaµÈ>-;x[Þ‰!Ã¥ šÝ]ÚÝÙýÞÍ„¸ôääD*ät©^\Äö ¼a‚[y¸yg]e»-—BjD‚Fø…ì&ÑlfÊ‹W\™>‘-Kø"Ñ?©ÁMSk= IDATÒ,HZµÚŸ‰Hޤ,HeQéÎ,H¤œªçb2; F¶¤Ð¶[Z±ã]™£ë®¿îºë£×…ÎwRÝg GÜÞK¹øtúfä~d8 u‘Nì0 ‹Í™ ¤ndê¹ë-)A )d4ˆô£lÙ£gV±ø`'­ð¾)ï4Ôµ [ó~a²Â]•Þ>©{VOwƒ¯=¨®º ooMÔÎgöâ¢fÍ’ý>Ù…£HHë#¸D}­™%§¶V@^v#–^6ãY Ë\8H«« z +–ß0Hö‚9J¹Ñ`𜚠W#HH $ÇÑu*- æ"H½ŸCTÀW/S0ªm§.©!tíÚ|Û®5&vm­X …xÔÜ’“Ï©7.…ÿ›ñŠ[vDO”ÖÕû2)dvi‘¨»`阒6¹Ê¦rÕËN¸ÁGfA2kjÈyÛŠå< ߸ UN­¯8JüÑÖ?ãG¦>Ê+•?% VF.xêO9t$â<:Æ‚ Wph–_@Œš^§[¤Ëyª!2{]ÊMôAkW ¤¥Ò*?¦ ÒŠ#ß´c*ÉR«À(¾Œ6l µ„u’²$M„£ë¹]·@s4w$)Íìóà #,Å,¡ » òÎÙÔ®¡¾èOd1ú(£Vn×!E-Åò"©QZßEG˜Ø©„ ±…h"FV‰¶õ% U@‰Þ}ÂÖq(aPž$ÔN¢1T™?å9"ú~xê•«–—ÃX²Ö§ÆŸl±Ð4¡†£Ã½Ãiµ'øœWé÷h œŠ…ÒPz,¬ÒHd]:£•»¯/$ÛQoMR ¬^”dD0lp×?AªJU¥Ôf‚p_רÉ¥výyÉÝÒ±„ ‹=HúôHÅ£y’Ùõ¸Ì.ŠHx Z}¼RüpG 2[{ «(» 5’oÛù¸£º T!é>‚”ƒ‘t¾¥Ó0 Òž:Jí,ƒÄ½úð².‹v&jƒn>¶¨1À—n7/‚¥¢­1/4”âQˆ »£²™MCuøJýñ&R“„ë,ÕÑ9‘e©ùoQÑo{‘ |Veeý_´i¬P0å«/ëê ¤é’$±@YæGóAËíUž£*@”ªˆFFª\îªêŠÍ“{úî¹HÄ’R¢ær4€gG×_½êÕÑE§H¤6©@ÿ©8—“·j)(|ˆ3Ü%tŽÔHmð_[«â¨U(ò­†æÜF]¦aç"1HüwšÒžo^­"{ëp/™l‘5µŒ¾C*Ç4œï£6éáì¯Dj¥¸AÄ-%™¼‰:ˆ6±rÎÊçUtä…ïÁ^CtrÌk÷´–¾ Cy¡µ¢g‹<«d}4·—H•@ Ò†«£ˆDé’ŸKZ••Ý•ƒ´Ê·n¸aÞÜ/!HFà°Õ^QeDBÛ!H°ûòê¾yúÙúç+cGù²:QÖHIÊAR)/·&. 1'ݰ“ŠªïÖ)’y’XºþkŠZéÖ—GtÇ®ˆåQ=$ÉF=;:Å”E+4AR“È6=µoȈc»œÞŽ2#†ŠÏo {Ž@k›.)I¸LÂÈEë&¨(cxšEgµ¸Øí;ñZFjr ­‘ý6‘c©ð=¥–‡äq ‰^ðЉÎϤB*_}YWßÄ!Iä ,¾ (U¢iVH‘$“ä]•Ï_ËÞÕ¨Ÿñ×”#Ò´ÒÀu<”7æ™ÑâëFªá½À¿ÁQË@šáS;îÚIŠ&>]Åv¨ôTt¢¢‰ÞAN“0"ùÔ®¹™zÝT-…‰€¢––Ub'moê}Óql-‚$«µ”¼ÓX5um¢ÐDÍñ™‘¥Bx9’^ˆjw’`¿H9q‰Á)Ž×Ymg¬–¦SÇ-´I҄֡ӽΒ\ÔÈö™`§Þ_¢Vb²2´‚ø·bÕ tÚSi…lj„T»ú DuŠ`ÈO•ÆH­ŽnèïòHʧ«üÝL’$ñ'àNžH ’{®‹+gr!/bpïêc¯‰ÂÉQ ¨ß}žys £¤Z¾ÿU «¥LGA©À‡‚ŠKr4‚j5€D(µb>çtÅæ O95f»uþÿz=ÌQ‹©,æKR#µ¸¼p±…*½\keµQBm6¶2$"ÀŠÝò)•%¯ÖÇf§ÂS }<“Ä/·t¤ÔT‹cyá‘ê!ª}Ë,í6²&‰ŽœXwkY®?—!WZº¯Õý")EÂhËír@"å´ÂÏg‡õ^wÁНU«ð0îݱA2Õp¤2Ô0ÒÖÁjÕŸÈêøY$Ï&sܦ[G ÒÕ¨ì¡Æ¯ˆ—Pd˜!Vz«C$‰VLUxSG¤fÌä(»kUBOQ@¨y<1CÔjPãO,Ë6Ê]}ñ6;9¶+“¨UCTñb!Q”b\§uØ c?C;‹T陎MXúÒ©Ðeþ̦’Ôõ0ƪ1CÃs†°ÝÈZñee S¼0QͶT…TÔ¶« äv’-ÂÄgq&½ &Œ“ O«V¯ÆiTŸV-ž×wõ´É’ÉaÄŒ”²MºD·^ý’H  Ä$-ÖŒìõ«+Û[(£8ºÚ%vÓ¸kW«²3.4 úïTÊ#n>è¦?nZø÷Á®]cÏI.Ä$:~õô´”WGˆê©>ªwÕ«Þ·ï5¤|ö©zV´ó‘ï2c%®HS0‘"yh‰h-á ’ìG¢å®líȧYdEdé>çåtdÙs:ræíI¬â¼Í(3ÊêêãxTç?S>gáY^ÊÂûäÂÊC¦ÂrÈBš>ñ¡Û¼ÎZê¤óŒ¸[qËb‰â[c”»>÷Ì(TY=Ea­lœHD#¨¬ˆ²T±jý2E=¶…P •(í5Òlç.ÓPfAjäÜNwôŠ-¯í¥?ÌÔLá¼àOá]¤éSÜ‹9d²5’+£Óý¢òBšê¡Ù0A¨¤¸pyˆ|#a^Y&×—s]ê£Þé¤éSh@ViÝgzÜW0]GQ$ŠÿP—¬†R»b@¦¥5b¨E À¶D-†ÆÆLU„±>Y½¿êG)ö¿©h¨HI ðߊÌñIm@Rp)b¼ÉAB =Þ‚Ç}éTD¦j0¬(+Ôù“U+ay·„‘-z´n–Orù„Xe’‰žÎ`[pÞN‹‘”NyøVF/,K -sžÑ¬V‰I 1©Ïßw–*ÔóËuV‡ —ÉD'¬†Ç•3DeûÝiÂoøJɃ©]ß¼8U‹cP…jˆ†`óH¢x©]GG[³‹HiÔAÐêßB”®)¬¢^CA>žÃ>ÀÊ’BàÁ3W0´dŽ]ƒy@¦·€“åá©Îgv* ÿ+lî’ÈF¤Õ†GäxÉQ¯êV&Q­¨­yó¤t¡•†g„X3Š[býD hù£üì)¨%–õÜ¡ô2¼õR·ãÙŽÄ•Œ:°e7qÚ˜¨†8ó›(Ù9³%¼ÇÌÑX¶ÛPR 4Ý]”ÝõÏË KctÇTiÂ’äÉIç"!ª¸GÎìZ_çI #U‘¦õ^­!~*4·"<¾½W/»4Nó½•ÚiIj!Ó„‹ÚÜQó;:A*ÔÁSF"Ô¨iF’ÐŽ¡œ£ŸúzÊéBuTð©c“ØÆ(4èœÞrw!Üç)Kg9„åéRú‡`6ΙË0Wì¤Ç\uC<`gu~Ë[ó¨ÏfÔn –-1ï,<°rÈ#âZ#Ÿ+Ñv¬< hÉ»ÕÐY4vÓ“ÌIR,—, wÓ)»ë5…tð2DÝ ¥½ÿ_ý‘€”dK¤4“°é®¶‘’H‰ë –Gp\­>¼®±Ø6¤ siXrÑØøôÂÞèr Ù/.ŠHq2§ûqFetJ;t™‚ë2u~¤§Í Á BRKQ:Ü–d¸HÔtR¡.•ßà9ª¯ƒ©®>Çü‹÷ñ ƒáƒY’©ÅBÄ…¼›RqÑXýºhø˜•” Fð2×Ê#H²ï2¡ù%Ë›&¸·‡ Ž(íã¸Øð‹x"ÍëÏ¿WºúH“¡FHdº(3×W…Ø¡L „©Ýä’ä~Ó7æ•D)ˆEÀQˆH$¥Rmï ;j`BÈê@%<Ò'ŠgõÊ‹!¥`øP,ªkð¿i¨Ïq¤¤é^!“kÆ’ H´1X´«…––ºg¾ÈxÑ6NÁ›%HÔ¨0VÎÕ­‘1'ZeÑލ«~H<‹*á6œ6|´^¬øqñZö$2¢¢ó­§E™8%°HŒè@2Ê“©Î…$j7JÓ°P jïæÍË­èó¯yèQÚÜX÷¿ìr Αf\ÝWÍU1üÌEr‘ý”)¤"8<n—T{aú3»Ð‰kÑ T=0®‹"lυ湟ëêêÑ6×à5EQþÐý«A,Aµ&n¬Zî Xk"eš”wTJÍHJ‘§ºÕUàKøDVF*réˆKpá1,Ø’W¾UK\(ÎE.}ô~èL”ÐïizÝéæ‹LÔl(—Q÷5tîd=§&%ꘔçYM"2U´RMÈt 'p©oÅÊáÀðæL2£ÝeeIe©ô_Óð/רÒÂq©œ(¾àæ ÿã÷ñ´+4xëÊ+{x_Öÿ/Wà¬#lvèÌÓ3öEºëbó)¦VPP¿Ëú<Þ(œùÀÖ€Á(Ä„¦>Ô<¹í‚,8´8B32ÉT…¢4•Ù*.%á4ñœÝdd`jUౌ iôÈR„wÜ_ÃÆtìÉ@{õró²²5íŠÄ2ÏÐQ|vO¶ûVZ<±„¢(Jij_ld¹B:DéX#¡LÇ!€t©JÔ‘¦æf—‘•ÝVrCxè ÅÇ´žiqõè²%a2F™‘VyUÌã²¥ÈJ·òÔ2o:µqÌ¢ŽË^>[Ã|±Œü×ÑÅŽŽ6©Gi]%Z`²û‚.šÉæ–°áÙ©"ÊÃS$'32v”F»LÕ&‘4Õª[œíRÈõ‹ÍNAÐóPîGrIå™Ì+F‰Þ6ª‹.óÀÖ’Û¬µÙѾ2.½T-ê nÕ‚iÌF¯>`5Wy£ú`0åÊiÓ1ßëá·žÎ襷tÏ,§…׸¨MŸ6%`¤žá%ŸgÙ=Oß–»¡[ª¹Ý±k¥^­yðUmˆíƒ+ô°ýW«+ ,uŸc£P×cdܬ±Æ¼LTSQ­@“z(×6j×.¡¬HÚ ’»Ùojá&oS=ô£äFÉâÔÒršàµVOýò†è%fš€çèËY)}:Jí²Ò†2€¤pñß •Ôt•wÌÂ]©ÏàƒÑÞÌ~߬™ËéI\À)·ã°ÇÑ -ûn’'rrËÍs&O¶akä|>$û[È=_9YöôK .à0€}À²[’Ûr.hyrÄÆZ»<óå#piærñßã¥U¿â…ÃRWQõÌ~ ýaˆ!{œë'hÖÛ:D*no¢ü,R²¹«½}£÷øÑÄ«b"•7R<à¹àðú¢€ä×QF ÿskw's¹RU™§ªµ˜q.©o¬Gݰ¼ þ"…VŠzRî]áÖ=’ÊgPªËvòÂñVe|å@]5%J;#“­FædYw”DÛSh9ŸÇ¢Q9ðº>ªExäýÅÕÇcO ¨ "_ÏÚÈlŸþRl¢¶?Ñ“2rÞŒ \ž]²r°lظE5í å Õä_®è÷$Ðt÷ߥJ$¿zÕ56ƒ *Dz"÷3ü)Ñâþê/OüO$H )M“BµWž3÷¥b/l"aœ{0…ׄ涎ñ{„²õµ5¯#ˆ–Zq«#$È’‚€G»¨è ö‚òœ4ÚŒŸ~&rü‹§¸Âꘀ‰ît/C:{ãv$h\Õò‰y¡÷ø{5ÅjÉ$¢´£ùYÃÇ»ìŽLÏ}‰yLÈÏÒ¹¯ae“È‚øÈõt$Å3:2sÈ+oy@©[ÈCQ4‰AJ@>®àXL yJ.qW ¾~©‰~Ë¿€¹OsYæîÔ÷_ÚØÜ:yJðS@o’Êʼ@½Qi¨HOEHS‚Ÿp>#UîÐ#I·û¾”)1 ¸}Hjnk­¶ã>Ym4Š“?å¤\©Ë"’ÒñE9x”H¢‰weX9Öbuã˜Í¥zå¨Á`dð7ð+ïD±t8cy—ŠÕ¿üÛH>ÁnŒh¡‡eoS¾F~'%MÅ“>Á&ªy{ãã±±a-:þQ«/ 8Ó²åI>Úð”èÙ#“Øhº"¶*äˆVË@Bd²\Å 9Ž.»ÌD¾­Š«¤úi¼ª«lΕÆ_5IR‡)½8ŽôèLÚ)/Ç‘ê‹ÍÒù÷i.ƒ¿ûîW&s‰¤çÔÙЮã&, 5zî}üï>1QÁõ×å¨ zeY$cŒÑÚ†‚Ôâây¤(\S;#¯úƨ™…²Qššß ÈÕŠa͆“²Ñ£ÿÊèüŠŠ$òW¥’‡7YXv12y•ZeƒÉMÚåjíÊAº$"ǽqÉ%ê¤ËüUÈ[`bò@{î¢âu,Œð)àâþ|ZAš}qáH ¼Öhû1P ÉÕ{­zÜšÛŒh+‰Ç‹p+zAý5ÔÔ‹ØJä7ŒÚøß,UI¹åKUjg*.I‘°q±‘È [ã¶©cóEì²i×ÉD¹) cjE¢†öBÀe¤E{Hlcãæhj³áf[ù•”Mí–Pkž¤=H1” x‡Íx6ÄñHR •tI¤ËèªDhüTH”×åÕfߎü'|¤é“¤‹Ä(k ¬]ç$"å4¥ªü¥Fžª6åK¨ö†ˆä·‘f¦/#ëV1Æ+"²ª Ç$¥¦eŽ4]>³ Ù]Ô7ܹ³¢÷4Ú¡d©tC¿°]—a#S6›@í?“ÈâgŒp¤ÆCR|‚ƈ¥OèZUD±G—Èl-$2’µâäJøªH¨ ­„ýLî$EM…^C¦*ªž$´í¼ÃÏ<æè†±¯Å•\¹3±˜I ];©ÒZJúöü55ºá/Yj¢1¶vÄ“¢tíÂÕ@ÊÕ?uhÍk+þ·ôØó1œÅyA,$NÇÊð+Š¡z÷’1U¾†„Wò¼ä.j‡s‘TAbˤl¤r1Ãõ“%›a#B£lDxU,¯êK,šÒV,þ<äæã7ÙצÐ}Y÷¬YKúZs ¼tøªN|©3Ú"9­O©©(SÁ¶ø¢Aª¤FiÔqàÊ ” ÒØÛôj%úŽCT®q?`µòÅ’Y=F-D¤¸HB™Òä :Bìg·)ǵA'› ’]/…T(d÷_Ùb‹1H"¥Q̦©jˆÑC&Ô½p곓fNêàˆ­}–…Sdb7~‰Z£Lå¬ŸŽ–E³Æïûkjj‚þ¢a߉Dm’Ñ«dQæ€qLjažx‡‘S±å ÁáŸÌ‡ŒQš†ì€lõ ]|HÒé³p”µTa’ª)¤ªQ…\!ZÂ"–>Fo?2¦¤)S&™ù”žñf0PIÎq-‡h†ù’¢”H’¬URV¬ÂT„Wùv‘Š—Å)ª”´•šàuŠMe ÉŸ®ˆU©’´±ÚÆRÉÏÝ(C û;qòOä¾Nر‡~‰Z;^lt(¡¶V¼ùøÃ¹03<>›ˆ®‚»ÊÛòÚ'c¬ôElAUU¶Ž¤š Õ”…¤ššš±#Ò¢ÈD\oŒ¸ai檎¥€Ò¸ Õ;55r^ŽL4ø­mçÊîÍ‘ZHÀOŽÝ»Ë‹K**©fF¤èlÈhgVÙYª6£‹gÄ…5ÒxJ‰5âá ©7'M*Ö5D ñ <ÚAK/ü28§v7Рk(ÜE“Ê.âÚ˜$|ÿ³Å0vÜ, Þà¦uMÅöùóç·êjÉé;’DÐ>O+3GVÙQ±$\¦mM±Ëj«">¢5 ÷mùÉIu ©öCŒQ ß3"õWrãë-'ÄÆMðÐnKT—f q¨&•º³.sz;‹Î•d _@ BtI,FÏ/—h¼I'iÜÁ¯Í²ÌȟǦzs™ÞR&­º‘W(Œ©.¯óˆ(µË¢¥5zµµ\@jëlojh€!ѬAI´çv8Ÿ541„5†Wþé6 YÕçKdŸ¥•^œh©ñ¨½û¦›nšä¢eZ+GP¼½‰'7ŒíŽÙ‚ˆ+&£–£±XÂð¦i+Ï”\YËûªH:$eÿˆ9Ê!é2é_mËãhé¸k*#ލq§@ª/É·¤»°‚®}PgÀ±/YAËô+|H“'G0@8Å—I•&ÃTÎx"KÜÿY IDAT]y2u¿“TâcÙ!¬Lw#ªj5ø[1ª}"mƒ25Í©©­sR{S“Îîøõ^üOS™02«-›X¬:º¥Žý…È Å°‡«Lšˆ'$Í9¤u—Û—ÜxÓ’Îö¢‹žÖpŸ[<½£½›t2dQjù¹X:]eq ÚéG|³—®¨6™Qó.¤±/¹…áM_9H‘Æ HåK_+¥AšW)"Õ˜šlp o½þl¢†rAí€ÍÜ›¤Ö6‘¦„Y«)S¤)•Úá9ýY5áÛ !&5PD*d: zºü&{pª–F%ÁQ ­­Ó•QvÜ6e…Ð_#9”ŠÅIè’ð„Éå$DoEKÉͱï©QË*mÂnã¬;H•_™.PT;¡©Øyí|—Ú]?ÓUIµµl² z]VüÉ$Ÿ®D™|á`"É”H2›àÜ&² -tÿÊõe•A*Ëä.·t*OùtDºÚçvU€TEËIš×š R›‰ù‘‚¿¹ŽFþž«Ñ …ðÚï;àØ ¯©RÔµ›ìN©,cͤÜÁ¯¡×€S‘¢Q‹!ÞRV([‰0Æ’IT·õu"³#…P˜ö«­u¿i*~¡³£ê$ÚQÆæ=²ÜŽ=¥ñÆb‡$Ò³¸ÁŠ(œûã F,²býˆçKîUàŠöî%."Ý|óÌÎ+.¯K™´å‰\Ù#Èq }Yq—T¦ÉÒþ`éxèú¥–¶[ÐKÁØ‹ÆHmÃ8©¼•Ç$ H󪩊¾*’HWH~UDŠY›ÿá«·ÐQ¤7Âojj è;`/œþà`ÖÿAHå QG~ËÎÃÔ&KØ´k©]ŠÆ¬º§Ö*nE(Å·ÚH[0ñ2j8sá#R¹ÛG'Lë ,y:ÝÕÝÙŽ1‰3-r>Qª$ˆ6ZÒ²%öãådseØXÁ²®Ç&Ê•˜„?áÓ×Ö5LœÔí8ºéæ›çwûÆ]ÈSòü+ï„äAè„oXŽÄ2XQ”Ëj12†µl*!¥“‰ºvy‘ù }6’$aDòÏë2ÉÜTe¤záªeÄn®êV cq‘”©Œ¤0„ Úv¤îîIÐp€âïix‘½ŠÍ‰åeJ†æø¬œÈ°§)”Ñ6vVÙGWî5`‚ Hóo¼É_³:Û/wO˜´ ð¹Sl6kÕËûôô„W¥å•oÈXã§áXtÇAÜü‰¤Ê];ª™2)^LÍ%I"`SDÅnÃÒˆ£ªô $·[”(ÒÕQqt¡ØÖ K†“¼š¾÷j*€TߘQ6Pj7Î|iW[©e—¡•îwCHí yM}töÍOH·!Z±^PS—&™Ô.%‘j]ùߥ$´óuR]CC»ç¨{ôÀý éÌi=-$Ç +UÛ¨üPû”-‘ûé‡;>%Å(7-°o±ÔUH“fG7ß|Óõ7Mm/^>اÑUШ£YV ÑD’5‘2»mémžƒ±ÅZÜC¦WÝÉa­áM–W0£#£1´.“bƒl,#oKÃólh,¶wÏŒü5³{Rñ O"˘S*e|ÆÇ‹g-¹<ˆ[8Z@(öÐîŽ *C´bG”Ií ûjÆÎíj.ÉËä*pTSS†¤¥È½ t-]^­Ú®¿2H….ŒT:§Ã@S¨á“Yßz¨ µ‘§ªÂÉ2F¤ÛõLg¼O St´æp$²†"7íÒ$^ëDX[6ƒ‰f}¹ ÒmüŒ‡!mU1B Rõç²$‘Α|DZü™1Òé‚A ÷c W5¡ŽÕøcZŸþÕÔhy^ØJYý°9ì o\i|‚òºVYµQÏ in ÆÄJïŒÌ!>[/¨b)¶ÄhÒ¬<¨6ÍZraÜaÔÕ= tµ6QeNYc“h°¥ŠÖ¦È†04g£ #÷'êXy—øì)¹‹ÓP-+\ÁnEVÇŠ‹8¯ò4¦¬û=6Hx*Å.Ò¼  ߯¢%Ê<.‘TDª#,‰ !g3ލd*Ôˆ0Oi".4"A<‚¡ßÝ ¾©>b7Ö¤$-¨€¤F<Ô¦L5_Ö7QàŠÍ…‘Mã3Á!2kiƒX M}»¡««³Ú –—J&¼îËgG)ysžö–Yåô¨F¥½I”ê5±Ò1ƒw«­8‚änÉüù7ëRi’ïƒÓ##ß²´Ž\GH÷òÉe•RTT¥,v°&ë´š+CÕpÁ]’i^Õ ï)äAZ”éÒ*"’†¤_;‚þ.tÃkj²£L^ò@ŠÛvUžÀò25Î×ÖÜÆë-š…#ŒH—ŠUFF”Yn!c UˆLïÊDBÞï E—ÌŒ×*jì2–vî&úÌÎýuC­‘Ár+ö=ìtÏí8Kâ´á))‡ÔøQ’Èbe™ ÷ÎGP¹òèFü<¿{êÔ%îâ Ô Cк£T,T;Üëæ¼Ž,YX|JÇËF¶˜a“Û’P–WCa#Ò¼RDºäó‰ÜOú¤ÅŸ#Ù] HØ=ÀQ¿*‘X'Ž£ p …I~kTÇ…qÔ ÊºVt…¤µN´'&“Ù5¨s$q0*DÓºÒò.dN eÿºqÿŽÎ&³ƒåìqW›ç¹];€ä®î‰Ž6ºÉQÙF†Z–c2Äæ èÌ@U“å`@ûaȉÎÊ.NÑm'µuuÅö?n–G7ÍìîîœÔçLŽI·Ílk¹Që<-/Ø4¼ ÍË«.y© ë4B´bŸc±ü'å²Ù\>LjD5€4yzo_•!©Ü‰ëº<ÇHz§UÞ‘á× pð …T£Z 5HƒTO µV R«pÔF’ 6ØAËŽOc•ÔN˜Kß!’Ùé/W›"™Ë¸­krÝ¿SÖו»5ÃG©r;:ú”“™¨ãûP™”r£\,耕§“¨¿Lض¦“—عôrŽ£È¤YS»'µ·»¿·‰ímÝ39Á›ÓÙ~Å„ðìü#­`ŸWµ¸ÝpÏ;áy=Ë^ãdqgõZ$µ>ÆÆž y ]òùVIRs˜ì‹@Z\n„?Ê9ºÎ³”õ]íW åG¤K*€ξã€:Cª¡BÆÂÏtJ¡ýíS»ŽŽq; j…Y‡—Öq4 ¿àA,¤–F.‘|DÒFvFiO 9î•‘Å­vâóåð­$¥h“¼8¢“®®6Çö¼¡¡=¤v¾Heê (K²,±Sr;#ë‰Ä‹QY¶ÒæÝE¾IÄd$ùxZ.±›³ÄƒtÓMsºÛE—7545‹íS‰¤%s|r†Êò$RzJVÙ¦’¿øœÓ%+e­2 “•¶È˜Í¸‹ŽÒ%ùª†K.´uç@*úˆÎv’Û-Žœì£W– F‹ûs ŒUDjõ]»K•Qyþ«@•ßÔðãGdmn´KŠ—»” :µË'IÛoµæøèë*‰}ô#·âúŒ‰PV² eu­yPØiÌøÜÉ[ÓPT #¡43‰v%Å-;Îí&ùs$R»ïÛ%r ÒösÚ’ÌIìS1lÀ£’,£¼î±`AÕ«‘¡ 륳ܘØÞ¹$dvKæ»b¨ØBiƒ¬³{‰.“|z\›šÔ²‰7á†AÞÃ[0•ì%z$õì«°™®]Á*«¿Ã/—”#]rɦ}˜ÚF¨OIÕ˜«^—[‰7ä".‘¨ý}iM 'h+¤à¿U£ž¬)Èpm6 HhÆÏër(E«­j{‡¤4D$mœû„)«ÈHæ•ñÖ/è#K\&¯«´¼¯V#ályÞ"Ž4m˜ØîcRwW'”!r¢-•d³~ËVùº¨:3¢ 9jbâán±µ ƒ y&uBÇn‰o2-áù»§ëÒÎIDÒ’ ª›úÁÏ(c!î©SÄÐ~1#‹1¥$[UqêØJ‘9 eBRLPõˆKË@ê_|W%;ýE‹¤)Ò¥5ãTI…pT@ Z P*aÒ¬Ë|Ü¥ ¤²Ã$ F­“E :ŽÛwk«Z“7ÔQ,㎈]±?W®—ÑìÄe—9”.SI˜Ú¥z–/ 7À‹!&u!ävh»¨ çù<“$ž–œöÙ•'ƒÄ™GÜz耖åp ­Ë—¾Mœ8©»{&D£%£:ï±êÞÛg}“IP<5y˜‚§:²è >qU¥zÉvF- JþÐåA¯¯ÈÈŽ9!{Iì˜Ç&ô¯ê°–@šþyo¢ 5c­Ò8Qk.삲Á„‡¥Fª)?™&Z|‘ÔZqåód\åRÉ7_ïåäí†Ôk¨ÓF«e¥¯²ºh[EA9ᙂ^gQÀ[Ã(•…$:NÊß0F}»´¡©“»6¯µ¼ú}­ÈéG­.6XN`ª'¿¸›lô–16ý Ÿ#F jŸÔÙí£Ñh{_ë²K/¯ñ£68 H·»ëæ9S»;KSÄpÀlh4"Md• dK`äsijdÍoyËgœÛEU+‚tYHðó%± T¡É>Š M õ_(HjýDvÙevÞB¿µ¥k¤1;?n0*`ITƒ½oåÔwŠ›úƒ¤–жë¨Ü`EÔ¸ Ei:F 5’âcX-f(÷(dÄvRfaJâRjH:*eǰÚ;¿®ÖeW“BÛ®«írínÁòV-ïm°j)2›Üá¼…µ1}Ôx°þ0‚œn>œÝ´dÉm7-™#±i­±-€¤â¤™KG·ßìx›ÓÝÝÙÖ6±x…—¦Ò¢OØ Å$‰:a -p# ë)¸ Nþ{¶lE¹úû²œÔMª%ý>XÞŠ(£ËDªÕ>7TÍ@$ EõÏ›KéB@òd"&³Já¡È¤/$˜£hÎIíd³X«œÀ¶æLµ–mN×»Ïeí¥nÊgú4K™íI‘:¨À†'¾@ây$‘jëBcí¼„ íMò¹¦a†&Üb¦ÞðÎÙ-k¨?žðN%ÚlÄ#wذHk'44uÏì™éÅup|tÛmî-Ð/„Ü’jI.$Í÷ y˜n¾Ý‘7«»­­Ý·$RÆÒð+{y{¶ÐR n?²ÉJRaÏXe.) ’j²ÔäÇ(i:‚¤—»\wÝuyþø´mlAb’…ˆÈúîw5  NžggþŸ I³#’©C‰S¥YÖŠµvd§%Ê–¸`^ç×X—ƒd zI­î+D.PÙÅ/ªÇ¹]{—‰ÔHµ4|4Vf—&.É*vv…ø$/ÿÿÚ»¶Ý¶ŽdKb£{7 Š ˆ¢DÈ”a bì“r06Žac’yqC/~2æÿ?âìêºvï %KÎÌh'v|‰EæbU­ZµVTƒ“ úl¹C ×*Ì*š¤È¢sŽl=YOH@y'}I@ªëÑ·º*Ýn@8Ë^u P¹¶ïj }#$¥¯¾Ü¾Þo¢hþÔ®…UÁÌw* š&#pñ/4Šp eiX\ö5ûÚf $›6V2s9”Hí0ÂçH7÷nW¥ôÐŽÈvþÒQ 1Ù›(é}Qðáæh]T£"¹Hù0”_P¸lƒ”¥Ñ„lÑ”»ÑÈH”i\UI—”Ô¶=3ê„.O§i'Ë®ŠÃQìÕfx Ë ¬—äò‘ Šì’dP"µ÷Éy’Ö%Ý}†“‰oŸw›å¤Š:óÕ¿f\Ao' ‹RzîP3$—RªT!ŠÔÖT(¶{°*'“S²_”Hƒ> sãâáòXfR*Iûš»ô£\“(›™ú¸ec÷V¨†7ÉÕîb~ÖÎT¢Ñ@¾ ðKŒêŸ¦ÖNh;œ“.^Õ LŒ]¯‹¦ŽN:P$·HzEadªùî5»•È|‹5 ¿úKß–rĤU¤Öìå¬ ¥1äßÀÛá°/«"–¢z]¯–(êHÂlWE—åZÄØ¥'xc çG Rýý •¤º}ƒÞîv»9Mõ0J¢º÷cøèÞg@B$}…6p,nÉAœðùîUœÁ¢ñŽÄ{¥ÊçWJQWQEôeh‘ž  ŽÍݽ¬3 „@j„÷q>ó?)cö’f®Èù¨ù±|cNœÝýÄ•±#“Ô©¾ÿ“JT«L($½ø[6ý­e{dq”Dª£UÎ} i× ˆuÇ[—ËX—3³yš=‹ä)|ãq÷€ ê…éܺ»ËÙ´sŠWeœ û+>–³:5œã¶)5¦–|È[(ƒóÄÖáˆô žÏ;PûigÅ¿*Òþ7[’íðõËoóYš‘(È¢bV±í÷z7Ä^ßTL²^Õ Z6=o$§6”FFã·r\¬fH¯HJnÎéÛi&úç?ÿÑÞÉÙò¤8úµÆÑ Iíi¤}Ü`dZ»Áh4îÃ*Z$)rOï‹6)Ê‚ ŒÎF¢Lú-k$S_Œ£A–3÷Ùn=3vÅ5'µC:#ÌúªÂ¸²r´ßívWW›9ˆÄh•ßÕÉ;U-Á±9 ä¤É’|»-®sAƒ'yŠ©ÿ­äÏp¾Þƒäf¤o_¡$i7öb­‚BH`â€ýX£* h›Y×¹ÈÎ)ôÀITm’‚iVCéü¼ÆÒ§OÀ7|¹»ÝžŸLª(?Oô÷;äêêÊU—¢õæ|³\žž@c7¶n“QÂ[TjUäÕë.°OKŠ4 ×ÀŽkTŠS‡ i]ǶÓn5é U«€¤?ŠšDƒOAÌåéc‰«?ÞÚ I*RGkçî±(¢Öî˜nd©&ÉÚ¨uùº‰·Q2 ïÝ æÇ¨X%©]åŠ ’f"^ÜŠ¿½‡®Ð{mþ\‡!x+úPL¦ó-ŒG¯_¿ÞíP>Ž6)ê+1Ѥ‰¥qéa…ƒHë ûÀÆá´e‚J ‚Y(KÛý.Y5ܽSbVû½¾­+R ¥¯uG·>?¯?«'“)Y]†|Oõd w`ÜÂU’Ah}»ÌÉ®~<¶;è·©¼í8­:(ñþøØ€N³•ËWÒ[û ]0 ˆ{|123Ò1’ k­J(²‰å€¢ ¾…E8­0Ä$ ¦µ›ˆeƒËn‘‚ iVŸr žÔ4<HS—¡"]B_·ÿi·Ûï®6 nDÚi²A!ŽAr6uI›JMEZÖ¼zScǦ’Qþýˆ¡hSxᄂŠÏ)ÉȺP¯ê iCZ»}}]ãl¾<é.H®'!¨q±dŠq²˜ž¾ê™¹*¿@‹°˜‡È†‡éàÝìÐ>=@zûLJ;ü¾Vù…F ¤k´v"»P9=’x ް"Í-^:¥@´‚ÍuuK# Z!Í‘v<#ùJjÍ·ÔF{¾úOy2™&:ÎjÁ[{» ®¡ÿA×ú)Cw»×»[ .Oø”B¥«ÌˉKwÖ4±‰W7oùyÝÑMRæ_PªëRÂJ]‘ê¿Y´Šw®ÐÿÖ(ãCŠýzy‚V±UÅ%FWWž÷^ͱ²™ð–Ý÷Yß8ÒEOßc¿Wƒ-ö")û¹a;”HÀ6¤Þ¡óá©Ðœ‹>4`”–H $líä¿.î@O£¤FE"ΡE T*²n(†•!ìG†þÎ-ˆµD¹" ð#ÒdÆî`ÀK|Pk—ˆ¹q5;cóÝëz쯧¤ÍøïXy¯ÚO#ª3*jã-GÚ8RŠÂ/&ûí`²½"­Dœ·¦vËãJ x¹ßk ÝÝ¡ü{ÊᜠYUñ÷oI@”4ßb·¢ßQ͈ùX×˔ƪïÀñ/^½BT_YÐö¤Q4i·>$•?׊¤Þbyù(`úx?0½}Ûè뤵{e*Ro<ñ< ¨µ›_H*l¶ZDuÅP¤u(5`Ab7.gR(lüz0 ²öN¢þ•P‘¼;D6„í](b—áu ô÷nWè.IW›Ä6È]ÛóÄh^Ë}œÜ—r®¥Ì ´•í(1â;’\h³…ÎîîË×tØw:#ó–tØ''²uc7£981Ã1hðk™X*YœbÂom—CÔ÷ Zaå.Bí@èäºÚ¸¶¢4 ÍÞpI2½]sêBч–‚Däw$ú/;ãøøÒ 0VEIj+H‡@dú:Si'3’ ™ÏªÓ[$Ù´Ê7=V$@’+ü¸Zp$ö'=H #ØÓTIØêÑmšš²õŽ´<"çæÙ@ŒÙƒãTCëQ~ S kd¿UUç.øÖxº8¿LI.ðå=œï.³ÙIvjþõ]ÂSԬġ™ð&6,fKUÓI ±`ŽæEä%¾Æ‡ÂE¨©lèÒ÷ ɜȓ´€„Hº) ¤·m0]× QAÒ”¤§ ¾mEZ­ÊÞ®q‹ 9ót ÍñK³±©]eLƒŒ¨(QÂ=àˆ„@ò=„ŠãüA!ŒYƒhÐ5@5ªKÒÏWûºµWT~ôJÂä¿ê¼D$d‹¢&FLm(µL¾Ÿ\×âûÄ´îà>Ý¡¥ÝŸïwÛËÍù2Ý*í-ޏ ±7 AAnw=•ë™_n¹5ÑeMAl•Ãëð‡,€4,Ií–}Q/”²ýËññYº£` ½íGM¯·-(²ÝõÍÍË´F£Õ#.FŽû9÷©5j7IDAT4œ¶®[QÄâTÛÜ•hY’ yk'@*ì2É+OË‘´Ú.¿8o+HøN{¨¥ ´AJCËx2]Ö8z½Kéöö§«Ëå´É˜ÆÆ'h.³W8ñ=Ñ3}“÷™HGf%H+Ò@öF.s#~²a•prv±n!é@™ŠA!´*–GŠ"V¬NreCæsb ’Zê‹~޵¼ï#옑nmíšà¢7ê1¨«H·ôü¼ÛÖ@ªÔL1èV§uã¬%ÉbËÎ( â9EŒèMßÕÏÖËÛÏw_Ñ®Î*vÉ RÌ‹?ßnO¯D¸íeôÑILˆCÙèÒtÆ6•bÇå5W™F.Ÿeê/e3ƒÈÜùdؤöF®AWå­Ý¯$˜œsø¨Cч>uT$'®bŸ¦ iE" 5ªQÓ ¹îU£$5¤‰+’$/;«ærÙq… cçnxñ4Ð3§UúȆÀ ¨Âˆ4™Ÿ_%®!Õ¤Ÿë!)¹l‹C=Þ¼“}§)t³-.‘õu!Ø9a)Š}+óôZNW€¤º³ãN.³,þ|{y¾¬?¬*zqf^ŽcÖÊb TyµÔ÷ìÜ@f Q‚(DȆC¤äeŒŸO„RMyPgMö¼“±ÝK¤íHåZ–—²MЇN)e'@:F ñÕáPå±ç:®è¹"!’ÎZÍÊŠÔÊ4Ìù²¼Hz1Ê4AÆÇÁʸ"MxHr]5‰%g÷à¿i@€sÀ„D@ºÚ.Ñ“KC–£:}ËÄÚÕè˜_¨|ÌÀŠAÌ·»}úêðtsyûéóŸ~--ô¿~…E-\*ñŽ::’ʼnҔs5)Ü™“ØÙ^ÂhY%Qs§eT¢_«q¼t­"-@jß…$Rý‚C;ÙB»zï­Q#ÒKÒÑ‘|´æ^jøôŒa—¤=iÂ@RCA<ÊΖ´=ZuàhÕ‚£HÍøyg5 ÖÇ¡þ㜲]ª/õÈeg‡€ºÇ‰‹Jý:9¹ªpt{uµœMÑ8hV™:jà¥D»D“dÎÄ7e¸š‚˜çŽšDÔ9¡(Ó%¤#qEúöíÛWcü}~š6±,èóÙ·½`ÅWËêPÉ_\‘4´Ùôwºyý¼Í†GeWîýÝ’Æ7ìذ2ÛÞÚ ’ì˜dï>ÄÞ¶R -刷±/_؊ĉ ˃¼b{ÞRk@ZžiIZç0b1™’¤¡K<ݼÙÕÉ)’ÁQe´ua42NùÎ5ÓÊuFJš>¸N‰ H8¼H¢;ðôž¯¯v¯¹ Õ% X»1_™W^ýIƒ`ódc¨ÄÓ>ÈÞF2“•®ð’¡$§uAìYqä¯0÷rÏ[£o_GŸwêÌwbT},»$‹‡*]n° ÉÎbQÁ¢¾ßFG¾éçª ½qÍ'¸œCLGåå#õè‚Ø- ’p’9¨œ{{¨±K8º~Å@šT´G’¢‚Lø@Ôž+@â!)á¥Õ);&¤³[å§G9ÑÐR‘HFañìþ‘H”JpÒ$ˆt¼ GéN5ôòÞU%Y¬É[d1ß^qA²U«Aï´%Šª·¡êÄý•!ïȯ”_¥ä)$~w#õHÕ&‘G” ûúö¥oÿ" ½»Ý]Êé‘.Zu™Ä9¹‰ ¶=Ê*)¨o>ÿû²dñ‚é× ÃëÉ"!h³Ù¯·ûõùr^ÿáR'L:…ŽHiuú†aðö>ϯ=¤@)­bAF¥û3vöz¾HËÕÙÙY‹ÍÖ…RuËb@êÆQ¡È¶v!Á§.F#£°+£a¥@ q’E{cT/ÖC×…ŠBÊ<®¦³Ó½P P“¼ì€B Š /³•Æ¿RyâÕPPý7{ú„¨m_Ðò*HÖT O™æÛÝ{©HŸ!Œù<)Æ8×ENBHiø%qÚÑD/ñlÔÔ'šÒdë&6^ |P¢O„6@¿ÿÏÕºÒðñ}ålaÔ¤_?oÞ¼1mЏ³k½4¿ÿ*ö œúÊi÷vÊß5AtÖ`ê:ú:•ÙŽ ×ÖhTȾsV?DR;Ó©{×E~Ó{¬þ›w§iJQz©V$ó'e€Ú•z•®òu9N>•dRõbSc{ý-ªWüh*d@þåf{+©H`­ºL©McŽo‰²ðá¯é-Dnø”0zÐGÃ3D1ÿ?ÁÁ±~c™o÷õ_¿üôÛ»wï~ùŸŸ~ÚÝHƒÇ)Ñv×7ÒÜ)èÛ‡!dø]Àòäºþûš  ÊXDµl¤Ú0œÞ §÷zþçÝ Ò™° ¹qIש+Cƒ­;Nû#¾1/„㑘lå‡åAOÉÓÏd)à´’ôVÛЩ¨S:õe@úér>áL½5Ò}lžÄ‡´¶:¯Pó9üˆÊdKŒH|÷wº1…Ù.ÙFnö¿1¾ÛžÏSWW‰ºG Á°C­Ì*5kfL‹åº;P/ÈzÅ1ý?}ùôéÓï¿údTÒði€tsÝ(I÷ªB„úú†žC×77,žÖH ryR¬¬¼2 Ano¨ú(rÔáEÒ6Èáþn¹\BïyÿslžÉĬ‘°•é–Š(g¬OHR’¦ \í¢¾c`vDYI!’oÞ¿@0(¿¯öŠ£Ýn“.¸C”{$}{rÄi~Ü5EÉ{±4˜òdìz§~xFûjŒ…’¶ï Hï·)\ \8ý‰ •R`e0Yh8©¨IàÅz‹±&$:N¦ó=ðí‹Qé·HWM uÍÇôE@º=xñò%övuMzÓ‰›¿ÿýï×"}H/H¦"9NÞs²9– HkÒ s¥èmêp¹K‹$¨!©»83öw G ¤U?†R9š·à¨bŪéälEÃ/Fž©–¤QƒP`©"ô¶v!ð=ArâÚÚ‚´»ÝoÀë_t©l•-î;j2nÕ7‘‚#$‘óyÄÏv)F¢]q¿< \'ÝÒ*éör¹¨«žhƒÉSÖ”Åý^Þ›üh‰6VéÇv',¾ˆõ{Ö|ÿœ+±"uiØAfõ.÷î‘j ­ ndÒiNBz¨»¾ÎQTÿå+ƒ#%ï¥ ÕÍ2ßÓ¹Æ`Ø_sY#á*ÒŠ]Q™JsÞ¿"––+89êÅѼRS!d/cñ41ˆbBK W]÷-ËÓÚ…RħßdQ²¬;»jV£ÙL‰€Áˆi¢Œ-êÓ䕊ˆZ´yÁu[`X8Ø­käû?q}´›;‰^6Î?BUˆÌ‡e|”¤é}^pÎÜž,½ô‚‰‹Y3ãþö—E¤+R‘ [–ý]Cú¡•¬ ´ 7×vL:< ý’pt-ÏÍõ͵ùžéâìÈ'ú øÊ(oâ†KÞßÔ‰wÊ‘iu¶ZfF jt–JôT#ñiPÂnbI;½@Ê-‹ƒ1ärÒöÕ£øÔòvò í§rô1U­*‰˜H¦p[¤¼"]¥»:ËpÔÆúDic¯nWQÒ`B ¦ä›UVZG¾i¨xÜR]N:Ñ® ÈëÆºîñæ<[D¯¶_BË‘0ÈGjÈ< ’—¿ô‰‘më—Ár¾ÜlàÓ²û­öëíö|2ñ®#Œù>Y²ùë´ HÇ«ä:OUå vwo¤}kà§ÑÊ]·<7¯Ò q†ÅÈÚ1–ÜÀjn³ùh0̹î~‡K$,Rg—€´‚ŠtV)A‹R «e9Êæ#´kÖ.Ï•¨2¿'[­TlWéTùöæN•ô®k=‰3«>¬K+Ðb‹HÚ£í" š[}â< Ý$ÛËèøŠÅJ<õQ¶’DÆ… ˆá~úÝêêô|Ÿz»ýårŽþzÄMò_YA*DKq¼mb;òÀF(,kåph`vf'¸D:߬7çÀÇÖ8ªÚ€4lrÃ¥S~®eë|MÉF„5 ÁÔÄ |UCä UŸkƒ#âèn´Ç{õê…LHóÕq¦YnỜ·¨s*~MÞË ¤ùry–YÕ-—:­ ϰ:L2 Šr֎ׯΙÿ‰`–XY’¦Uðí,ÅÆÊÔe¾Å|F ÏæçÛ«×··û’Í£‘Îa´µ˸MMbyP· „L;Œ öºárÿ€tŽ)È/˸¯Ü‚^FÙ×FÝ#R”p×Ý“=ä#¦«;™ÍN‹úo´Z!ç'Ò°K#4TùÍC4 5 !)!éåK‹¤Q7%•p!É”¢„¢—/_Z á>Ö9e**Ú9>Á (ՉɆI«I\‹Vùîuu$MÊ=€ ,ÝÈå¢ —qxZ *¤I[IòâÒ[Ñë$ƒàHo©á2¶š-–àÕiw‰’U¢ÈìëÞëÛ6‘Ø&‰ /‚È2(è«V… öü[¸…`ÚJ«éò|ûùëW _æ–-è-SÎ-ˆ²Ö›³Ûhƒ]Øœ/ãøN$êoØÄ+E´Ñ“,"gÃÙª:n=IÃ錅,m@8‘k@¦õy¥å(5v˜¾zäôƒ}gïÄÇˉ÷„HmXæ©xqtTn_ÛPtÅ@pЋϹ0=¢pጛ}T›!q3F í’>ý|y 6,ÑÛJ(ÇDtœ¼å| ·àsn!c>ÔÜ.ðퟬĒY¨}U9š÷H9†™>õðÝD£µ¶áÍI‰4h+@m“ýü«üyib¾ Žš±èììVÊ%ùw¥iu&&uRÒdÔq)ÑÙ×— ÙD6¤¸WgNÆœ‰¿tÁ•‰cºIšw!‰}?èEÕšà‚½ …Ä|¯¯2u~då—Ið=×Gfݽ„ˆé¥;ÿ÷£&ñ‘'PðýT.½cC×Hì Mïos'1l½0/8ææ7i®@º~us“ÑØ H½*ŸC0J\Cjí«¯–$á°²JE‚гLFø‰®[Ù[òù!9C!hP¡]ÁÚ>¡´ú.ó1†¯ðF¶þÍ‹Y’È¿À‹5bÌ‹‘ðjX˜ìrªá6™q¹êDãW§¬>«l®èEVL+eäMOÇVü6î"j¤fºšÍ’··”Ð`%´QMë¸RÉÅ»†Û ÑDB„,ñRǦV¹}]ȇÍX¤aãiØ7äܶÃ'ŽZfŸàiélŽ×H5Sð£–Ñȉ§—ãÎŽg$*I+ä¹WÌÖ­zØîLcבr$Õ2p;²Õ T^°©Hœ[ùiÒl‘x¤IA)T½×ða,Y1 f¥T“Ùâü*%¹d#Ò~³HW}Áß:š}‚I=¦n-µ,°âw"'§(‚W,C"¾j§h[бhœÓ™½¡ã‚Áf4Ôa ölWè¸h,Æ¢: ùÜ¥!‡Qho톭%© I¹½Ð°õèÄv(’çUÇóâ1d@z ŽìæÈåš=R* H8¤ÛºÕªoi„5©†c9ãk,cí]ÌHnä¬âÛŠV­þY’µ]]Žùœ¤ê \ÏÐ6T’d˜'"zœÆu2Ê—HûDÚE‘àÈï„ ÖsR¾ŸÕYŸS0¹àxnã¨LJ'…rÖ(X ì$ŒŒ"?Ñ‹Ë}bs/4]0L¼øø“"‰î$xÿUE¶n Ø ÖúnÛÚFº|„¤.ó»DwIöê‹'{.^Øk ùÜp ÃÇŒH=¬‰«¨&’æ½øieêPÔÝØiEJÒo“š­½œËx2=% Ac·XÌËšä%iNÕp¸JÂA îXù„4½ƒÆz:;M6ÅyAz}µ/p0c*Ñ­©h•÷.쩯lVE¾õcÆ(€‹†žŽ™üTïŒq—"ß £'²8W2¹ïÙÕ_Ov5rð¡k›‘†¶[ËÁÓîÕ0ìQˆSE‚ÉâIT^Õ­Žs ~ŽlMbúnÕÝΓ2õ˜)†¹i"d-GYkgeu†n¨Ä᮲&¬Žñ&““Ù ¡4Ï䫿f€/éèEé™hð,QHb6Ø’,N7åRàh·My}t޳C<–!˜`V¾‘Ëj¦³òŽ˜a¤ ãØfðrÕó:Uålýèu1åuÜñQ7Lte.wʰ„’“;ˆ•ã·iØ¢>˜ñ.’`‘tŒN¿OœGI”±ß?°"M&Ç]ör:q!:ÎJÑD€d'$U» IŽ5Ë#Gä”ô<ÐÙÁôb1•šÄò6 ©‹j£C8Šª$ð㔤rºü„˜çÒîj{šF$2X`]ËЕwr´}3`Rù "¦±ÅY?…‘yÞ±3Šº±V³7Á°—%@aƒÿ½s®y‹\É\ =÷×÷<桬ô:û)1Úëùéè( ~àC@ª&Ç“†¾çøxÒ ­ãÎÝQþT#ÈvèÖàòàX ¼”S$U9$ bå„€$HòA£ædÛÂÃuÚ*Æ ÛŸt9äòær_2 »4!Qʘ´PÆ\•ê„9òãQËidñƒÌ@"Yõz¨ØE–¹ÁÆGpùÔ B9tåzµ™ \ƒU6´!éû[&Òqfsð=˜iR #|BEú‘@r$|Ì.±”ý0~{Ò°I£*[#ÕMÛÈe!.®‡³¼·Ò Õt@’gŠŒW¯>‰JꦠºÈGÕiES0Óh›Hâ´«»}=!¥H‰=ª5¦…TÇ-½9g‹,ºšUÖOb»ªDS*úŸvÁVÄ\È‹â`¾©\ÞSDåd6Ó’4“ab £à‰—9Ñ„êQtIú;%wÁ"ör»½Ê`”žýþêr"Mëìk„t¬iÁˆFYý Ñ.AbˆDñ£H L$…T?ñÈy”áßôXÙ–k  ë á}}[BdH«äÒ4În ¸+ ¯ÎZ" AÓ @ê¿á-±ô õBé^QxÅX”~¨Ò=’ ÉeW|Áš8˜?’Æ‚£ù‚4%or½Øx‘ö˜H4&ÍÏ×Û-¤=„Æ„véû›Å ¬!E&¬Ë‹XZ(m2Šd¡,¯„äVÖXæË‰¸œ ²5tKsþ}@2Mv+Ù0´§_¼%íi˜ìE<m¶pxÁ÷ Ø¦H]®A’«Ê×=–“ÊŒ9%8þHš˜ž—ç®ù™¤ÁiÖ îdg‹ÔÛœàN Â5áÞ›eà¥%Ÿà[7™ÖåH€dËQýÀm¬52~‹Q;4–dû1æbÃÅȃÇ"¹…r¡ÿß Çh¶^í@ê47ù.ÚypJZQ'–Ðt‘ïSÓ5`²¢UMóɽ繳{lðDHª úÓ£Îøƒø©<*ŠÒyš›s=éË$ßÁˆ¿áÖ«ÉKÒü¤~À©ŠÄö#dA'^8¶ Äg‹ùåZpT>—'õï¥px³Ê ¿üµk3áærÔGö[ê|JTD ­Ðq!Ó»çòOŽÝ èg&d5=¸L6¯Ýq0Nê.êì;™f4’®]+Â`»ä$޵5óïÚ9°ÉÓIùdp±HH‚Â''}~þ uy;Ú¥’Õ:8ó³!ä@âAi FÕUÅÜVWhl„+ôá_œo6ëízÝŽ¤í|–ΙŸŽšjNÙ•²"ß{TÞ¦œ9 㼺º¶klègÆ”Ùh—Y¢›ÆØ^(áioŽÙ*¨´ÙÓ™;eÃ.8ךùñã4)÷–ß™>5í!|ëê™|4¥g¾Q©¦þé#éȹ{wéæ+%ÄÇS"îNÒSW%ØÏ±_¢2B‹(œŒP¦ Ô;­G%”¶ÛÍù øæýCH07ÁΤš„µ\o˜!-„,NʸÔZ$p8SÚ,z­äÞÜfÿø‹T• • ¥B:Ý=²"¥ãî#È| ¾óWU¤NÛî.áqÁÆÒUÒÉŒ€4‡Ò4‡³ÙI P¨|óŒ%›õfs™pT–$ø Þ þHJÙú¹¬ 3µ¥Ç ¾á)!ÔÂ¥>L7‰’;¾8Ǿî„ÿ®2±é¸id7NªñÅò¼~J[zö[û€uÛØÿ0™å¦vع;fñŠ6è1{jg=h‹úÝ>¨]­+â*ËEΚ v<ƒz¼V路R~#/$½X9°ÁlëŠ[‘CÚÎieB ý8 ¹ÇáÈÜÎ /ÞIr¹™£ 6´r}P¥/ðWªEð0Ö—ëË-—%…Ѻ.Hã'.H™žÐTžÆM3@-of' Å¢BZŒÂ͹2­ÚV@;Œº`Œgœ!=º³Eû€Äv\òwsIÓI î³Ë”ÿÃIäGV$íä:Ò×áƒ@:ú¡0z4’\òƒ­&úIž±ÌÎI'p;›`d?Ó ŒÊJXJi³®¥ËËõv­(ªq´ìóCþ2¹èÆBƒ#3ó‹iò,I`‡—‘˜Yº»õÚÌÁ2åH0ÒFEfpÙ"Ïõ¶ßóͧ’ÜÑ_0\`A £âA€Ssƒöÿíû=ðIÏ‘ë|²Äsîìס˜ŸrRk­¤í&Ë(ãÎà+Y}™ôÆY§S\£àIßYP¸£Îø¦r¡dÂôÕì‚ t=oçÿOãõ„8J@:úá8úþÓù‹s®¯ µ‘’;fšÂ\j” ÚUé!äï ¹ô¢ÁYÿ·ý©<ì­èàÁÃÞÈŽò÷𼡹çs” Á@¿ÅÏ3z:°tïGPÕªƒª’âó;ÕSéùùïÏpÏŸ¦g =?ÏÏ3žŸççHÏÏóó ¤ççùy~øüÃÕf CɉIEND®B`‚fheroes2-1.0.12+dfsg/files/images/platform/psv/sce_sys/livearea/contents/startup.png000066400000000000000000000003131456075706000305460ustar00rootroot00000000000000‰PNG  IHDRžÓÞ‡¾PLTE§z=ÚtRNS@æØfbKGDˆH pHYs.#.#x¥?vtIMEá /1.€QÈtEXtCommentCreated with GIMPWIDATXÃíÁ1 õOíi  à8ÒÇa›IEND®B`‚fheroes2-1.0.12+dfsg/files/images/platform/psv/sce_sys/livearea/contents/template.xml000066400000000000000000000003641456075706000307010ustar00rootroot00000000000000 bg.png startup.png fheroes2-1.0.12+dfsg/files/images/platform/switch/000077500000000000000000000000001456075706000217355ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/files/images/platform/switch/icon.jpg000066400000000000000000000603271456075706000233770ustar00rootroot00000000000000ÿØÿàJFIFHHÿásExifII*2&i‡:@2021:08:03 22:21:55jÿØÿàJFIFÿÛC   %# , #&')*)-0-(0%()(ÿÛC   (((((((((((((((((((((((((((((((((((((((((((((((((((ÿÀxx"ÿÄ ÿĵ}!1AQa"q2‘¡#B±ÁRÑð$3br‚ %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ ÿĵw!1AQaq"2B‘¡±Á #3RðbrÑ $4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ?òR§šŠhÃ:Õ×]ÃÞ³îîÙâp®H'Ò“vÜi7¢+AÁëHFF Y–0ë¹1ë‘UˆÁ昈$Lö¨ÈÈÁ«Dd`Ô»MPš=‡Ž•_eÞ1ŒžÕé÷*»Ä.WÛ’? —%ØÔ[ÙØdb«ºí5jšË¸U­Uç¸ëVYpi¤d`ô  †º Ã:†¿s¶Æ,B§\?§Ôúûk¯Ð¾-®·pú™ó­-äÛ 0ÇâaÙ}»ý:æ«AÏÙ§©\.khSøuà·4z¦±ú ù¡‡úßöˆþï·§R½V z)ü…¡'œº•<Ö^ž¢oG€É ƒÅA—d’CÌ9À¸ýMx?Ž´åƒÅº’ÌI|ùIfêO˜Ü×=ªUp–¥T›‚÷t±ÔÛIàÿñ¡´íÿ,¯×Ë?÷ðqüêYþO*ù–níèñ²Ì§é‚+Î"·ÞëQ–v;Ud“èz>ƒð{Ç“Û ­>ÝìCsƒ3£~;AÇçZUP¡oÞrßúÛþ’©í>(Ü‚…·ó¶iÔgñèÃ#ñ"¶ì¾i:2‹~âPsºúáSó} ®_Z±ñN…9ÓõÛÍJ;•ÉÃÜ»ê <×7qŽÅä,ìz–95‹”§½Fו—åcxR®¢{…|Má»­mt]ÞK¶X%qtñˆ¢j’qÿSÈõ¦^ ]Kþñ® àÔ8ñ±s÷c²¸f>ƒg5Ýj-³QœvÝZaãVJYþhʽùuî6Š@r2(¯Hã8ªÅ³;*²“ý¸Ïþ<+*ÊôÚ4¢í4pú¥¼‰ª^ G;fuéþѪâÞbp"ÿÀMuºì&?jF1;ÌçúÔöQ ½ë‰åŠÐêXU&õ1ô0žaºaq€Çë]=ª[ e€ÀÅe눠ǣJ­n˜îhö^ÝsÞ׫ì³Jö=Çà‡‰­ü=âø-ÚxZÛQ+m(óŸ”þ|~5íÞ!Õ|àTFÌ¥õÙ,ÒÅk¸¿?ÄÃÓ?­|ahÏkd¬†Ô‚~Ÿýjò_|iñ}ö»#øa®4Ý*?–…¿,=NGéTûª\CžW­sSp„œ¹oEù$uË ´uúÅ» kGK?‰še¦¡2‰Y8²:ƒ[–ž/øG«ªé—zFk ;Qñ±‡ü·æ+çŒkW?ðýV,ëógÖ»> ª.u+_]“üÕÿŠuy…¶õ>®»ø]§øGW›]ÐnŒºUÖŸqÅ)Üѹ\®ppzש0:•Ð¥aúÕ‚>%ÕµOêºV¡p÷šrB¶ÅÎJ‰$ ·Ü8ú⡼˜BæE<4¬ß™4ð°”k¸Ët¿7ÿ𲿂e˜ßo¨¨‘ƒ Š+Ò9Œb5Éø•<­FÝÇõë°‘0r:W9⸲IýÖÅL•Ó*.ÒL¥ãñâ9eÛ:$£ê?Â’È ¥KâÖOìýõÛà01÷CëYözª¨Ý0zòœ$â¬Z3Šz±uáó[\ÿJ‚’?:ŸPÍû@ÖJóÎíŠN:Síìo2?Ñgôû†»èIFšRvg".UŠº7<á}GÅúìZV’ŠfpYÎ5Xšú÷Cð}¥ŸÃÃá;[Õy"’Yþz0<ãÓ5å³¶‘6‰áýÄWÑI’?&-ë‚@î?ÖoÂŤxÒþoÜIês•"Prªvùgè:WëûJ®6¼Wü7æŸÉy„`â¯Ôýµ’Úæk[”Û4.Ѻú085œëµˆ5èß-l´?[Þ¼ËêXŒYq“ø‚¿­y]Ƶ§oȺ_ûäÿ…rû)]Ù¤+A¤Û8o(Õ×ÕôX3’º?Ã5ö£=Ŝ͕Û"¡Á€QX—Wj¸6Óþá¯b”¢¡ßCÈ«9É¥Ôõï}—ÂZÝû¶{Ø¢É8Š6ÿ1úU`r'&·ü'jÚ7 ! )-Ìsܺô9‘ÄqŸÅ®j7ÁÁéXáýêÕ'è¾íP©¤b‹Q¹CíE3­Øb!5â}ú|˜Ûó Ù¨î#@ñžŒ SPƒûGáõâ(Ý-ŒÉr¾»OÊß—ZàíÛ+Šô¿ ÷ëc‚¬lïýv:ýU]Ù@ÿCj€Å-åÌp@7O;ˆÑ}YŽýj®›pDµV8oý ×ið’ÄßxžMRX÷Úi1ùûOñÌ~X“>çùW$×ï$ßvz±•¨¯Du¥NÓlô»cû¸vZ¡õHñÞÌ? àâ8÷­?êm×ådóÔy%ÿ¾à’ì}ËùV ±S‘]Ø(Ú’“ÞZýû~<ªÒ¼(ß”TÈ{Ñ]fFœÑ•$Ž•_#5RXÊ:P/­EöME.Usýáüéß4v×ôH5ë òÕD7 utþ ¡ÿëVÕýªÝÛ†±<%âxt½sû>b’)ÌLî8=c?__Z殜Z«›údšörٜDޝcѯíÞüqçÆá@ݸ28¯¨þ?ëq¯ÃÍédh­îÐ#¯ó¼nßš^—ãýS¾_ MpCHåÝÏD“Ó'Ú½ïâW„´_ˆ––šl^#°·‚­i ¼ë»… dç©â¼œSŒæ¤úÚÿ}ôô·âŽ”Ü,—M™WÄš~9•‰ÿpÿ…cêK&¯v÷6I¾d¼ïQ]/Žþ ø¿ÃFY`‹íÖ‰Èh†Nÿϵrž¶3¬Á„‹17Pp8®ÈºPNt]Úèúz­)λöužGÓ?³ÿ‰®,~êÖó[‰fÑ¥Ebdo›çÎi,¼wá?‰šyÓ|i¥­¤Å¿tÌ˹}aÁþ~Õða?¶< âÿØÊ‘ê× ó¯øyñÛľ»[Húž›’År2ëØõô¯Kø±á]Xð¢øÓÂ;/‡céÐdqëÿÖ®pMàωíUõ¿ñóm¶µÿ}q; ëü!¢éÞð¥ái5JÆòMĤ,’ªuÚ7p§ÉúW›]Ñv”=Ö·OKÀ{vÖý.tR拾ç“|5ÔuKé×%´÷— ß #ï&yÉè¹â½³ÆCÃVÞ"¸ñEå¼ÞÄ ·EnÞ¤Lç 2G¨â¸]g⇼%dÚg‡máÏCo`ÿxúË?Sô^{Ey.»â-CÄ—k&¥(GÄ6Ñ ±D=¯ZÒ W—55eüÏô_«ÓÔ*×KÔéâ¹>hØnR; †Š( ˜À©ÈåOQO¢€ ‘1ó†½Ãöuýž%øsˆ|C–þ‚L,y(ú“©æ4#‘<;u_›%OÙïö|—âMÔzî½¶þ†O–0J>¢êyD=V0xg›Wœ”ûV(¢¶·†ÞÞ­­ b†Þ H£ Š£€à@0:R¬2 Qš6ÉÝùþ*¿MxÖAÈúâ€3(©d‰lŒ0äÐZäu>‘â‹…pd´)²(ä|€îù'ëùVs𦓑­:n£j'I4E‰u'v0W³Cþ{ÖLÐyczó8ç¨>„zÖ½½ÄWP¤ÐȲÄÝzõ¾”ˈüÁCFåÀù‡øóZne¶ŒÆ¢¤ž ú¡û§×ÿ¯QÐA<Š(  SõÀ$*œá±ßÐÕr0px5¨@a‚†¨Éo±‚î¿‡Ž´ 5ÑdR¬2§¨§Q@÷»› •$ü¯÷³ÛÖy$zzVì¥6.p£©¤‹Ã–¥‘-ö…+Jv4¾˜ùþµœêBš¼Ý—™¤)Σ´ß‘‡EX¿Ó®´¹„Wvò[9è$R3ôõªõi©+§tCN.Ì(¢Šb+IŒ‹¹O{ýj£¦Óê§¡õ­J«4~Plcn£=?ÏNŠR¥zþb’€{«<[Kg&0sœÕªFAìhâÝ¡bq˜Éù[±¨zVͶ›s©ßǧYZO©]\6!´µV’flò¨Ÿ×Ö¾ªøû!C¤Ïi¯xþº»Öh4wD„A¸aßúf=Krµãæy¶*¥í1×¢[¿Eúìuáðµ12å‚ùô><=9¯fø ðþ ͽ¯¤¶þ†B”“Qu8(‡¨ŒÇû«ÎJu>ýåñWÅŸê!„YxbÃ]¾…míŽÏ·¼w¦8ˆÆØg›Wœ”úEU"Š(aŠ;{xQbŠP$q"Œ**ŽÀ½:UZq¨º¤þóšQå“cNÕà¶·‚;XÒÞÊ¥¼Q…HQF*¨á@ÇJº@>¾µ…n_¡¡†´`˜˜ÿÕ’ /÷3éíŸåZ’|×EX’Ý#î·\û¯@ž-ÔßJÓ ñà•# ×5áÛ[OxöÊÚã+ þ"Ü Ê7—€Ø‚3ŒŽ•µñg@p99þ¢²~ B.> 蛲L{å= ‚#ñ¯??gAϵßܙ߃\Õ9W_óD¾ ðÖ·ðÇQ$?N”å$墑}{r3ŒŒÆ@ÈKH×-µ•ÄGdøÉþðú{ðý+Òµˆž_]øK[dö#¯ÛX%,¹Â¾~Fðxäñ\ÏŒ¾9f½ðá?ßû° §?ÂÜø`Œ 9¯™N G¹'èú~^}±‰À)ÝÓw·mתëù˜WŽÀ ­Ëд=ë.hZÁå{7b=j›jzdž¦6º­¤›•¶‘8*ê}3ëìFjâëZ}ò$0±ÉÛ0Æ\ç‘Ϧkèሧ>¶<)áªGT®¼†QRyOÈ<Áýèþ`yÇo¥4D劄mèÇ5ӹ˶Œm5Ð:@?Z‘¢uûÊWýáŠp…\m%‰=“ü} 穈¥Kã‘ÓK Z·ÁòûÌ©ad`8'¶;öéW-4Iî /ˆuß×ò®ŸÃ^ Õ¼A!^—-ˆ‘W… gævùTãžHÏjõÏ|µ°O¶x—QC )s£íBþ)HÎ:ä pCW“[1kH._]þïé­¹=fù¼–ß7ý3È<9á//£·ÓíeÔõ•c]Ìq¸ŠycÓ¹¯eÐ?gÛ©íÚ]gU]>fRc‚Ö1;+ǘą#“•^¸8äWÑß gOx³O…ü;áÈ|%ái1+ë:¬f32 |ñÁþ¶v*rö«þ°×Å߶ƹã/…üQàË?êËD6‹ Ö ö#!–ÚY˜Fs÷¤`c€~kÍTê⤛ëÖ]W’Ù/é3Ðuháâã#ú½Ûþš;m_àÆ¿¥C)еëwÇg†r ðZä“ÇʆCï^aª|0Ò5©cˆË¤^ÆvË®Lgý¸[ §Û¥sßÿlÏxI¡‹WK_Ù!j"¸ ÇUxêêÆ¾€Ð¿jÏ„ÿá¶¶ñE¢é·`„5«a"FǼs¦|°?¼J'„ÅaŸ5;¯8ê¾q{þ…ˆÃb—,š~RÑü¤¿à³ç}Gá&»h Úyš’!}’ߌþ×#{es¦Ì!¼¶šÒSÈKˆÚ2yÇšûʃžñfžº‡„|@aµ“>[Ç2jVg‡x“9óÔ÷éŒcàߊ­ÐÃ>eâ<“þ‡p’œ ¼´7=I—?)öÍÓÍ14ôœTׯ+ùßO¸Æ¦[FZÁ¸þ+ð×ï>%¤##‘_Iëÿü1iíkÃWÂgΖ+9Gç,G°ÈÝI5Î]|ðDðƒmâû‹%;ã2ÞÚÌ}0UIêw~uÕý¹‡ŠN¬'X¿Òú2Ë*ÞДeèϸŒG•?uŽG·¯áÒ«‘ƒƒÁ¯¤áøðö8cšëÅw Þ©Z">8">âk®ðÇÀ߇²Inšo‡/¼U0¤‰æ§»-òœB¾QÂçpFåŸÐ_Ã¥9zGþÂYm[ÚRŠùŸ"i¶zÕòÙi¶—•ëçmµœM,‡ì¨&½³Á_²G‰uÖŽ_ÞÃáËV ›XqsxËÎFìBF:’Fyb¾´ðŸÃGh¶ú7„¬¼%§º†Ý©Ë ¤}âÚÛÌž@Äž^yÉ®¾÷áσ|¥6£ñÄßÚVƒå‘.å]3JÏaä«——¿É,Ò+wNÃʯ™fx¯v„Uú»J_%²ôgu, z͹?¹}ý~G–ü$øg¥øh^hÿ ô5[²þ]þ¦×"09ÛuxFÕÀÁòb ýÄg“^£â†ÒøEÑu+ýjM[[¿ÖÂQkºÞÂÞgy1Ž7Ç|L²eΕ>EñSþ áÿ iGDø]¡G¨ÇkË{©mþǦÚà ­Ô)a× 95îž0Õ®µß„ÿ õ+÷Yooµ+K‹‡U GÑubéÉŽèë…U9Æz+h´[þ?ÖÇŒ^±mKPÉ'ý2ã¯ýuzЦ¿]º®¤A{p?ò+T5úNøô_‘ò•>9z°§Ç+ÄÀ£`Ó(®“3ÅC©d¡4N Éì½ZÙÌB¹#ÜP ñ‘ >3רíYæX> è…ßV`@þyü+câ4EtWN™#éÚ¹ÿƒi¿â‡÷gÝÿ¡ë^fc<<¢ú¦¾ôÏC'—^_š2¿i[Uµø¥sƒÌ–лzgn8ü…RðÇŸøc¶Y×TÓTöKÒ[hã„~«ÀÀéZŸ´øâ”  g¯%¥…¥ ØJq¨®¬:õjÐÅT”%gwùŸ`h¼ñÑ-5±rFß'SUë÷&(üTÖÇÁ x„-Þ“{%¬RÁ­ÊÏŸ“zu%° ã¦>.«Zv«{£Ü ‹ Ë‹ÇIm¥hØqŽ ƒÐŸÎ¹UìÿÝê8®ÏUý|™ÙÍKøôÓ}ÖŒúÅÿfÛèüƵ×m0±ü¢d‘ßþ¤(팟\óUaøâ°cY5+Ëgmä羇ë^§|iñÖ— x|S©1 ³yÃv|Œñ×­tzÇ¿ß\H²øŽV ›€û<#¸ÿb¹¥ÅSNNQzüŽºxì<Ú¤ŸÉþg¼é¿³Ô‘HÍ{­ÂÌÏú4 &åààçf \äcç¦>øyðÚ´ë0I*®Göœêäç<”ù9À!å_3_|Cñ^¨’¥×‰5)V%–9Œc‘Œ¸Àäñ\ðƒ|­#$¯÷¤îfú“Ö¹Õ â½ë¹ÔêÃhÆþ¯ôØú7Å?µm…¤mgá"KÍ‹¶;‹¿ÜÀ¾XÇÌG·ËÒ¸ŸøÓž2øOÖ5Y^Ú}f×6vàǪ¾öùTóò©1?ZòÕƒHÚ½kö^²’Ûã§„/Z"#Ši^6‘8wò\(™®ldc‡ÁשE{ÊiîïÊÉsIEMéu¦Ës÷;\¼>ð™l Y|¨`#8 ó|Ø®5¹¡Žy†Ç×ý˜Å~øxæÉ5ÿÙ^oh¶$s€2Góø_ñËÂÖþñoì ‚4[Mrâ0å~r¿h$}ò*0’œsgJÿ»Téòy«ëøžb·Õ¤í­åSç;}*îèf8¯«| þuz/1ËsúùëŠê(¯¼<"Ž…=ÿ…nþÕ¢ê·ú]Þ1ö‹;³ ã ã(Gp+ؼ5û`üQðÒªÜj6Zü ü©ªÙ€Øçñ”'¯Sž‚¼®¥Óô{Ï_‹2ÂëR½qŸ"Ê•ñêBƒÇ¹®jÔ¨ÍsUKN¯üÍéÖ«OJrhú£Ã_ðP鬰5_:º‚Dú^¥Õ·p6:8ï¸ôsÇQÿ ÿðîù’[ÿ øŽK×HZÊÖ`õÚ`HÏ| úWÍV¿²ßÅk脱ø"þ% °Yd‰>…ó\׋~ øïÀÐGu¯xKUÓ¬÷©ûSÁ¾ ¬¹ñ¯Ê+TQ£Z<ÞSWüÎ÷ˆÆF7švó_ð´öèðv‘´é^×»ò|¸ ·ÛÇ\‰=‡ù®ÁC¥”•±ðeÍïÉ„—RÔ¶ÝÆÐÇãøWË×6Ìÿ(l9ç>õBm?`'iüË*t.žïÎç·jý§õýj{‹m¿‰ž"YbÓ§Ó¼7 1Ølm¼ÉBóÁyK„rr3ÇJðÏëÚÏ‹µ{¯j×ÚåÚŒ,ú…ÃLÊ=qùzž*Ô–¿)Ç<ŒƒÃëTÞÜŒmÉõ´e˲±2§Í»¹…“Àsùý\ÕÓÌø+ðÚîÈÿå Q¯ËKØÉ”>6ì óŒWê½Ò…ø/ð¦)Ü&²%yè4{Õ?‘eüÅy¹Ô¯–Ö·òËÿIft¢ã]|¿4x–£6ÝsUVÀ_¶Ü`çþš·ZmE¯‰5•¨¿¹=ÿzÕ*†ázg?¯±ÂÿŸ¢ü˜«üIz²ÍQ]FgŠÑEÈüD„>’Xð2:w<Ÿé\¯Á땵øƒáàÀþöé r HH®¿â“¢1êôéÁ®#áÌZ¼?q0%"¿FÀçÜc§|W57EÛúÑ£· üKy~Õv«oñ.Þ@I3éÑHsØï‘öQ^7^ëû\Y¬~2Ñ®·ÒXyEqÀ #sÿ?•xUN]%,-6»ÿzŸ¨QEèœ[¾¯nÿž_û2Öv_t9®u “.`O# °äüëÐW6%òÑ“gVsWŠ5R>@E$ž€ “Z¶ž¹œæQötw ·åþ8®’ÃLŠÛýLd8þ3É?SZðY(ÆqÄkå^&+¡õë'ÔÊÓü9kdÊþ_˜Ãø¤9#ß+Òþ ]Ûi¼-wxH.ŠŒ€ÌŒ«ù’ã\½½˜v½kzÓL0‘Ë™hÙ@ùH9ëŸòkÎÄOë4åJnÊIÇïM_ås¡aì½Õç÷Yïò?koPkm^у¡µŠA±¸ÀQ‘ǧ?•~WþÓ²ÏüYñ3Å«á_ ]kiª\Á ë$qF»Øo‚Œ2·9è3_sþÄ߇ÄïˤÞ65-8l–ü'¹ì¶wøí^óeák )n$¸(ñ.†C´Dlçõ§„¥ˆÅNŽ:£8Ç’iì¥ôèõkºiõºùÉN8oiBjéê¾×Þ~Lxoþ Sã½RÝ«ãé—L2m¬ín/6ŸBÇËç·ûf¤Õà”¾8³…äµñÞƒr€ö3ûñ±_¬QxƒÂöRý–=KLI&1:øóU5_ø3Km·Ú®–… ²·={f½Iâ«FÓÆÓ‹ÿ·mø»œª0z*-ý÷?ôïø'/Ä&×bµÔµ½ÛN<û»IežULÿ F5ËÓ$zý ø ûx[À~·´°³m>ßh2Ýno^F#<út¯mƒ[ð Æâ;Ý0•;òdÙ’}Ž3_:~Ù¿¶~—ð¯À×6^¼Ž]NáZJ ¸ä þ&ôàuùzÑ–6QþÓÅGû”©»s˼­Ñu{-üŸ\?wü8>²—D{¹ð—Ãm*wµ¸šÇí1œ8žäönzýk;Äßüñ Gk;KˆJ@6ò$¨zýåy¯Ã-oãõ}Z]BóÆZÁº•·“mzñF;|¨„(Ø­¯~ÒŸ< ­ÚÞØx·Q» 23[_ÊfG†FOÌ¿ëK‡ß&¸JÊ<Ñ)Ú÷ìì¾F1Ťô«$ûè×Ü}UûBþÀÞ-ð­«xnØjú3»J¶q­y"3ÐŽNàŽÙ¯ž,~ xÛWÔÎÏÂÔ“ÚVKcƒœ}æÀÇ#œ×éWÁ¯Û’ÃÅ:lvšôqÍx>ImÙ±*rF:aúuþµëüwð= k›-*8îǬQ£ÿ´3^ ÌèÒ\•1 6éVçŠõƒåŸ“V¿VÞ§±ˆ¥§³¿£Vzº?(5ÏÙ⾇f—sø9æ‰×v ¸‰˜~† ׋øƒE¹Ð櫓Ôlî4ÛØÉV·¼‰¢‘HëÃþû¹¥|xÓ5 ­Õ”‘ÂÇi+ó`{Nj/|*øcñÃF’ÃRÑôûÕ”nШ*}B‘ŒñÔsï]¸,|1nØLE:¯ùm*rù)9)y/wÔÎXŠ”ÿ—}ü,~k ÿÛ£ŽDMü~¯|Mi‹ü7‰w :”Žà°ŠÑUàî¿ð#^ ûbÿÁ>ï>Z¿‹<š‡’Pn´åS ytÜIÀ%xÉàôGÅDqãoÙ Ý´­flm#-Æ‹øm‘…^g‰L¾®Ž2Š’qjÒMÇfµù4ÚkTÚ*Rœd¶vôÝŸ5ës‹{V˜—·í™XÕTm§žTõëNº•'¾¼–3º7¸•”‘Œ‚ìEG_¤P‹…EôKò>R£¼Û]Ëv×!t ?•Z¬Ä}‡  ðAïW!ŸqÁû½½~¾õ¹™ã„H#v4Uû»v’L¢BNÒ?ˆz}GB€9ÿ¦ï\7\)þUæ>¹6Ý¥ÂD&‘.$dàÀ 3ŸÂ½_ÅQ´;•‘ûBiÚuÕ¬66Z´håb ‹È<î?!_¦_žŸI/‰5³OôØpÆ×‡[®Ýè£âOŠ´íwàߌ-gxÍÌš=ÄHŽ7‡‘£+yÞÉô<öÍ|Ññ&I⦒¬Û¡²ðÚ?–k×ÜOûâÑG'þYqÞ¾2ýŠ?h¿jºÔ? µýFmJÒí¡:DÓn’XöM’@Ou1,Œ }ݤt ¯¾$Þ4¼[s1=‡‡t¨·ÈpŒÉý¥rÓí*9ô÷¯‰ÎgþίC—Õúßÿèø%¨ü1ø\ú¾ªá.µ’fû1‡cÄNÐÊ[ø±±F}Aô¯1’”¨ÐÅ)Åú(µ&ý,¹íëu:¨¦”çÑ+zßeúüÏÛsK‚ëᆳ3)o5¤ªGš«ü˜þµð{Ø)Q€ ö8ÿòkôKö­ð.­âï…^)û Fy­|‹ä‰ZD‰ÕØÆ~Uo^•ùîŒC)Ê‘kãñçN­IIYJRkï>Ç-œeA$õV¹‘4 Æ;Ÿo΢­©#)FGZÉš„òý«îzè§qoæ¸*9îÏùâ¹_Û…ðÆ¿‚ž3òœþ5ÙW9ãÈGü":ëãŸìùÿÿõëZK÷‘~hίðåèÏ™îP‡Gßç§CÜ~u^¾Œí~§cn:7ÿ\Uý(üÄ«x™PÀtäŸóþxªd# ö5¥p¡¢ ‚}õíY´ïŸðOÍ!µ/ڧ“ªJÇN³¾žCʨû<†cŽeÇÔ¯ã÷'Å9ü‹ïŒ÷“yw×Ê‘†FÝÂ2£¯É¾IË7rkåßø%¿†¤Ôþ9ø§Q Yè¢Õw1<÷°< ˆß9õÞûñ'Q…þxÂö2³®­âmD£ù»¼Èα0•±óÃýžœ(¯‚â'í+aèÿ5Jqÿɹ¿CÝÁ{”¥/)?ÂLJРpGqE÷§„hÚÝ–à³wPzõäçWAî+ àŽâ´,î·`*ü㪀}ÇlûP4FŠšHÉ$᯸¨h¶¡›lýxÆx#¾kÁu˜D^$u‡r¹ç’r?¾‘wÆËê¯ñå³A­ùU޽¹¨œy¢ãܸK’j]™í_=gáÆµ¤É±œâB±+,{sÇü²<Øõ¯›ìÑ£*Œ¥Yr¤ ŽµôìåxWWÖ¬m“Û‰Ç\|®¡@ç‘#œc<{WŽøÛKþÃñæ½dW`ŽöBŠ8Xî_Ñ…||ªI.©?Ñþ'Ù¿y6¿_ÈM-°à A®ŽÃøHÈ>ÃÞ¹­(þóñ=½«¤Óþúã·8&¹êîvSØÔL\ò3\çÄŸøôҿߟùG]}~¢¹Ï‰?ñ饿?ò޶ËÿÞ¡óü™Ë˜ÿºOåù£•ÓÔFîdÇþ:kF³l$QOIž}ÔÖ•}©ðæš.ÕÓ¥;žÀ±<:“è)± 8ÆyÆ}k±øGáÙ|Qñ+ÃÖQÀn.’âT²3¸ç=²~5ψ¯5מÑMýÊæ”麳PŽíŸ¢ß°·ìw¡øgž4ñe’]ë·ÌQ8ù`\d¨  êH9ãŠúÄÿ´/Éajd¶µ‚iÖÚ5ÄJçû‘Ž9Ú·J]FAá_„bam0Än#óÍ~S|Rø©üPý¢£³¼»y4½âêËO„‘b‘^P:n$zà(¯Ï#[:«Bj5V¬íÌõÚ >‹d¶KÎ÷ö£ siÉ^<ܱ_¯ùŸ³1OͲÜFDÈÕ‡FR2:ûüÑý¤¾ÁðËâæ«ac•¤ß©X¯ð¬r½Ù\0úb¾Þý™üh¾9øA¢ÜÉ/›s"Ãu 8l÷ƒ¼ûöÀøa/‹üý¡k›XзÝBà ÉnÜËþ:{®;×»‹Ä,^y+7¿”¶kïºù€o Š•;é·ù?ë¹ðU½ˆy{³òîƒÓÓ>¾•eX2†‚2*+¥-Æx9ã?Óšù´}‘X^:\ø/^É8ûùÇû†·H#¨ÅaøägÁzð##ìŸÊ6®š_Ī"·ðåèÏ›¯òOFAú7ÿ^³+Jø®&änڣߩ8¬Úý$üÈB~µ•Zr>Ä$c>ý+2€?Bà–:lðÄŸÞ³ 9/#Ž\ÈKwžLdŒdOÔœqÛ®|Cè~és‚/¤†ÖkÂe[LÍ»Œ©ófÈQï[?³¯†ÛÃ_ðOyâh ½ç‹£’ÁŒ›‡Íª^­„nŽs2¶P¤ƒÎköÕÍÿŒ´›Pêˤ·,«•¦—ëqàú‚:×Áâ¿Ús¼-.‰ÎOþÜ£øÈ÷RöxIú%÷»³ÍÕÆT‚:qKTaƒžœõ¿ýzº¬A ×Þ´¨íS† %ÉbY— ùŠ£4l¬K}î¤cõ­ŽhË€Táב@Õå4ñö앸;Gaé^²É€úzƒ\'Ä›öÉ8:’?AùPµ/±xóA•òLíöGÇ%™Õ¢èÎnœœ Éý§Ö? |MK—†FQ³Žà:‚êÌŒ9>Š¿døzâKY#–ÜùsÃ.èÜŒ…sÊ·9èÜôí^¡ûdè±xƒá÷†×z`¿TáljW®– ˆOþÏ^ïtÄÈA=OJÌÕ¦1Z qøW³ŒCû+ñÿ3Ùy.ù¥÷¯ò<6ïÁZôQ0:pbr0·1?ñêȸð^¾éå&˜í,¤DŠ%Œ–f8a¹$‘^ÅpÜþ•Õüð—ü'_|¥˜šHc¾[Û€pòàw ñ‚Ê«ÿ®º9j’´’·£ÿ3޶WBœn›û×ùuø‹Ãx+Àÿ¾ÛÍ!K)’ipÜI›bÊã¿Ò§±nSá¶×ËŸ5EÕ¾)kΡ‚Ú¼v ðbCuøË×Ö^8Õ-î>/êRMp[Mð®…ohìFß.K—{»°Tœq :{dx䞃á¬K«^ÝßÎKMw3ÜËÓ†v,[s|÷õ²IœXÉráãö›%¡f¬[ÌAÀäõ+Üûýj½÷§ˆj+PAÈ4µJ Ø0ÎHïÏëW 4´QEE:L¯QÛÔwËxÆÀ^hò€9QœßZ몖­l.,¤\g#§¯ùâ€<'In¤€w Ž:‘ÿÖÍ} o§IñOöoÖt4S=ô0ËoŒòÆVxT{ ѧüýkçÍFÒõ|à~íòFzÿÖ¯zýŸuï³øŽïJfS£n DÿÏX·:ªýQ¦$ÿ°£Ó9˜Å¢œzëóüè²ù©Òp—M>Rÿ‚|6ÑÁëÇj×¶›sHÄ£pú‚+ªø÷àføñW\Ó–3”òýºÏåÚ¦I`“¶äÏûÅYHJ”_¾­½FqžÄ~UïÓš©8ìϤ9¸=ѪÒ¥zà wO¬m ‚–ùüå¯9ŽA*+¯B3]_ýÈ?œ•ÙºÏåù£ÐË?Þáóü™ëš4ÙV^Š0ÏùÍi³… rXð “ô뜰¹²–9#c<þ½}Óûü·ñ­–¿©üÏwº~>äYùU} Ï×¥~w‰¬è¸BæœÚŒV×oÏ¢KVúsR¬iSs“Ñ?ì)ð.ÿK»“Åú½¹‰¤ $È(%G^¹;Ð}+êωÚõ¯‡´;›û¹6Ago%ÔƒùUKŒòp§ÍsÅúW‚,ÛNÓâ†#l›Šglq/RIõÆIýM|)ûK~Ô‹ãëK¯ xraq§HÃûCU&ÁÏ•û9-ß ã9Ù×£‡¥S/„½¥i»Ô’øck.Tü’I/Ví{9N•\muY«Eméÿñ/x§Pñ¿ˆuT”Ë}¨Lg|’B÷QsÑUp z ó¯ŒN[º`'p]I@?öÂ^+°²ÁgžGç\7ÆYü¯ iãnOö‚œÿÛƽ½%‰§ê{Šÿdš]¿QŸ³&¿oáÚ ÀºÙU¶Žõ”–é¹¢uO͈R+ö ã·†Ç^ŠæÆ_:ÎhŠ?–&7R7ÿý~µó‚­b•:H§”`r÷¿e`ïŠ×~ [Ûêÿ¼ž(0$z”qÐn÷«¯=Á,Tý„åh׊‚Ë8sNÑÞIú.çÉa*ºqçŽðwù;'÷i÷Ÿ‘.ðµßÃÏj~ÔÆÛÍ6S 8ǘ˜Ê8öeÁükž¸¸26àzWÓ¿ðP Ç£|VÓoÒ1ÜÃ-œF2йÁ?@øü|´ò,c,p3ï^æOŽ–c€¥Šš´¤µõNÏñLäÄÒT*Êœv[zn…f¥ š·àùGü';|Âú€Ê»ûûž+æì)RÉóŽU éèO¿µXðl…r~Ýw¿¼Ízu‡/FgKø‘õG¾K Ë9àg>µÎêÍw.V5à/øÖ–­p©P r8`~ï?þ¿Ê¹ùj×ç1Gérd°.Nx¾¥ÿ‚vxõ¿x›ÅÒnKk(H·€G˜åe˜çý•X |™ª^Eeg$³>Äœ§ØWè7ÃÜü'ýlô$‘ì/¼ÿêÝ[±bì×.#µÏp³mà„¥|¬¬Qƒ9îßµV½o ÿ†ü'a¶³±¶ûq lÅcÙc°=W„W_ SæÁK%g^rŸ¢nÑ_$•½O˜ÇÉ{_f¶ŠKüÍ;[µaËÔžŸOoåWk£9V*}AÅiÚ]‡ch‘ýß§·òþ_`yÅÊžÞm„ß‚=}ê (VŠ’xŒ2•퓎sÆj:) =´P‘xóL6š™©㑎>€Tþ ÖntË?Pµbך}²®q½îE'û®ÃìZºŸˆ:GÛtó:¯ï¾3ÿê¯;ЮÛR Ä*\b2dz þcñ¯;M΋”wŽ¿çø†¢…e=%§ù~'ª~Úþ ³ñ'€ôéΊÛbI0]¥­'ù£fÈår Þi¯ŒÑÚ6 § :ýø=qeãë^×#K¨RKOÇ›m!ýòŽùIX¶î0&Œ»_üCð=÷ÃokÔ9¸Óç1‰0–3Ìr ÊTã¶py®\²²pto¶«Ñÿ—S§1¤ù•nú?Uþ} V³ä~쇎z7q×ñükÐþ6Ùµ~q•ƒ§^²W–Á?–æ¸#úzô¯…NZMT³`ÃŒýÿë]Yû¬þ_š0ËÞ¡óü™ëžÑnüM­Xi6e¼½•`Œ/©8'ŽÀdŸa_°_³O…‡t ¶¹xb†HOqŸ¾ý<oâë´ð%ĶI•¨“G—qvûTÄ×éG®âð€¥ŠÁJ³%¾XrÇß·â=+ó:r__ž>¯ð°‘mùÎQÑ/Dþö}aQÎ+ ä|;ûkøÒæÝ-ô‹k‡5»¹g¹Uc–‚<§ý–wäwÛ_(Då$;WkñçÇçÇõGiÌ–Úh|}ÒT“!ÿ¾÷¸zæÊ(º88ó¯zW“õ“¾¿+/‘íÓ,R4⺿)+ž9ã?ç5Æüf¸Ã{1 AsÜãÉ–ºr‡ à×ñ–éÇ„ôḂu%žL½ÿÏzú\¶*§.a®§¡ç?h‹þz§ýô+ô›þ ­‹­/Åv>qsoq*„'€¬!ql—ýÌ/µMÇï޵÷üoÇ £üf×4;‹‰¨[G2#]†èÏêÑÿ“_O›+Q§WùgMü¹Ô[ù&Ùðøme(÷‹ÿ?ÐÑÿ‚¡Á>"²ºŒl•5i£,9hó€=~LþUðt÷¹|¡Á#Û¿N½«÷ããWìµà:|v~(²kˆc»[ÕÚJ‘ Ü2 yî0Mq±~ÁàÓŽ—…´ô}¤.ø yÆ3ÊdŸ©Íxùtq¹^á¥C›Þ“Oš)5&ÚJî÷×k%æo]ÒÄIOžÚ%³è~’I$œ“ÜÖŸ…Nœ×°™w0Š)œß§ã_3o9ž…û=|4oŒŸü?¡\B×:lý¡©)?)·ˆƒµ¿Ùw1ÇöëôC⪺×Ű2%¾‹à‹3Êü'öÜAܶz{-„‘ÛPoJòŸØ»ÀÖŸ¾ë|Aa,·ú¤ x–qãÍ– í²µˆ`þòy$P õ3 <­aünñ ×Ã߃­¥_ÜÅsâßO;j3ÅŒ<Ó7›"ùæ7 ÇðWpçR•<º‹÷ë>OE¼ß¢Žþº$ê.iU{G_ò_yà>7ññ÷µÍ|’öä›xØm)n $*Gb#TϾMcVts²‘ß§¥_GÞ¹Á¸=«ôš4¡‡¥4Õ£’ôJÈù9IɹKv:•£`Êpᤢ¶§kxpFÐ1‘ý=¿•]¬b§#ƒZv—ŠÉƒ…çžz{þyü~´ÚÝÀ fo¸ÝHÛÇ_ÓùVt±Ÿiäc úŠÝªwª©CòÂÇ!‡ðŸè(.Št‰åÈËœí$f›@ÝÛ­Õ¼‘0Èa^'®i­§j@˵wJ÷*â¾ ø|]À/"¼Qó`uÿ ›áÿ‹.´û­3[³õ-:aº6m¢^ ²ÎˆYsü%³Œ¨®·öÃø[Å_é_¿u:&¯0û4Œs7OÀÇÜ›ÁÿYƒ‚d$|}XK/Å% ž±ÿÛ£ú¯ó>ž”㌠Ô÷Ú_¤¿GþGçjÙ\¾6ÛNsé…w .£Ñÿ´Eü‰`&ò¼³v| ø-»±œdgë^ÍûFü¸øAâß¶ÙÆWÃ:¬¬Ön™Å¼Z{¥}T²kÂ|w;Kg¦ï$ó “ízŽ¿×mGe.¾šþ‡4h,ûBwqéë§ê}Ýûü[Ó¼;ã/iñjV2›ˆí¯c_´!ÉÊ8=q"×éWÆ{˜oüe©‡Tž9FãÆ×\õ¯ÇÏØ+à‡ˆ‹oèD~ïWÓ[ýûØ—ú×ñ/Äšö‹§ÚÙ_ZêüH-ìeY¤Ú!—' I=}+É ú@Ik”CÜÉü¹­¿‡úì?ð›é[ ÊPÊsÐÂã©ç¿¥j²Øao^2»ßÜ'™ÏjŠJVWõb‹Ò24ëâ?ëÒ_þ&½'önñïÂÿÞñÚß[Û$ÆÞy ´ª¡$RN:o“ô­–×/ÐŒ#¹ù‰úçŠ4Û;kv˜÷ Zöê+pÒ1*7¸Çã^n/0úÎ¥Ñ\²M=z4uÓÊáJjq›ºò?i>!øÎ[…V^-ÓVâT Ò[Ûg{+|®£ÜÖ¿9—þ [tÿÁCfðM¤ßð˜øvk¸¢Œ»_ø}ÕeàrLN@Ïœ}+Gzq£‹¥U5¢mòÔý½×ï»L'³½Jp~[}ÇØúÿüóáαiqiöQ<¹äY*òã†+õæ¾SÖÿàœß´+KídÜxbiÜÚsï,•,‚@r™‘NFì1ÇL£> Á@þüP”Ç£xšî}Mc-ý‡¨Äb¼œã;"BJÈÜrN5â'5 AýªŸf“â?‹dkm&ÚVóaÓ`;¥7Aj¼ð³H‹ò†=º8|>Ú—´¢âõŒ¤ÜtW»çæ²K[ůš8#[ß,¤¤Ÿmý49¯ˆZݧ‰¼e…°Áá¯?Ÿ0!a›UîDcŒl³…Œ‚G›4}Ü×Á_~!ÿÂÒñõþ»a¦F¢ÏL‰)j„•b;3³3Ÿ÷€þ^·ûM|Bµð7…ìþøzy¦ºº‡ÎÕî甼ënîÒ8•³–žæFi$cÕY³þ°c渦*ܤñžßupîxÊÓÎk+).Ziô…þ/Y½|–ÎÍXêª)aâöÖ^½¾_Ö¥ú–ŒYÇòëõ¨#J¹Æ_zyœr ÇQÔ}gÁ1FäôžÞŸJ»‚UÈÈö4úTvƒ)Æ’ŠõZB{Z(Õ¾ü†ëÿ,Üž¿ìšÍ ‚A#±­öPÑTnm ;ð0äõ翾0 Ψ®mÒê‰À*Ã:T´ª¥˜*‚I8w ñG‡¤ÒµT•Û#×éW<ñJ¸Ö?á Ô%YåZ8šB<’ç­±lõ=±üD/SÆ7Ç_Š6ÖÎþѤó5(˜¥åê–ß±‰w빿‡ ç;| ¸ÚJràƒë\x¼,qt9;=ÓêŸFtáëËQN?5Ýv?Q<+¨éÿ|wðûÆ!î5²‘Û=ÜIŒJ ä\G….wq'9uOƒ¿hŸ…úçÂ=~×EÖ#ób-3Ùê•wØ7F{v ŸSøñsþd¶Ò5+·³ñm‘Ylïâ}²Ü•é"ù샨çzç Ùú¸Ûxsö—ð”þñͬPëÂ32½˜}  ÿË<ƒ±Ôž.vüÜÎOÉáqÂâ=uiGuÝ4UÓ˧ÑUŒq}Ǥ¶}¿ºÿGý?¨¿`};ÿð¨ìo4*IVÚTªò‘ˆÆ1õÆMtßþø»â.«¨‹¥4ù‘<Á‘£äŒ÷Ðu5ù—៌Ÿ¿àžZ‹h—–O¯ø^IXiº¬NR9¢É+¬¡À¼¦éÎ .+»¿ÿ‚Ðx‡vm<&`|²KsßøZ¼Z™G·ÂG^•IB2””©¸%;¶Ó—3Nêöýz.yÕp­*Ñ”Sj֕ºoÅø%ƃwwee©h7[Ë,ÚUÇ›;n³ìvúWÃÿÿcˆž¸•­mO‰ìÓŸ6È8x›æÿ¾wWéìáÿ`ð×ÄÍDi^0±O_°,¥äP¬ds…sþÉ yã8Í}C þ5lk+›7¿‘Nß$ùRç§Ý<1úf®•\v§²Ãb›—üû¯¤¿íÙ­%~‰;ʪGš¥=?š|×Cùƹ¶šÎâH."x'мr)VSèAäè¾ÿÈí§ÛOýÕû7ûJ~ÁZ7´iôèõ3Eb¾^Áܶïât9ùâïÙÃÄ_¾ ØÜÝ#_øy¤’8µ(ÔáÆØIWø·¡íè=ê9äq xLe7FµŸ»-¥§Ù–ÏúÜž¤*Ò—4.µí¯TlÖ¿ƒoâÒ¼káËÙÊ­½¾¥nò;tUó,~™ÏáXFéA=ùÇ ÄâhÞ=¿+ žÕäΟ´‹ƒÙè}û-ñ›Cºø‰û6$z<ƒÎ[ »‡!HŒÆX× ƒô¯ÇoHÔ¼)xtÍRÊK ÈNÒ’ r§¡Ú¿F`¯ÚV?øjÓÃZ´ÒKq [9„Ÿ6×Û„q×å`9ü}0iþߟ³õµþ†÷º5[œ›«P¹¼ïŒ}@éþÐô¯¥ˆtÄMmÉNªë%ˮ𚷣ﭼêØÔxyu»‹îº¯T~lM>åààã¦+]Io4Û‹;h¥¹¼¹ŒÃ ¡y%wùUG,I8sZeµïˆuK=/G±ŸSÕo_Ë·²·MÒ»úüO@9Å}ýû;þÌ~ýœ<8ß~%]A7ˆ“hTy¢ÑÝH[kT3\>Jü¹$ð£îû:pq´ÚЪõ¢¢âßC/öGý–t?Ù+À2üGø‰¸ñµÂ¤QÚÛÆ.%µip#±¶N’\ÊHV#’ÛWâçÅgøK¡êž;ñOÙo(Úx~Ýþ"|Gt[k?2 ÃвÊöe‘³íÈ–öUÈy)’ªÛL’Iùùñâ6¯ñsÆW>)ÖãH%‘ KØ´vvÊIH”žORY¸ÜÅx°çâŒK¦¿Ý ýù-¦Óº„_ò­äÖý:7ãÔqÀÃO‹¢íÝ¿?ë¸5åî­wyªjwO}ªj5ÕÕÔ‡-$ŒrOÐpìKYvw[ ;œgÿ¯ÿê­(ÜH†@=ˆäWéé(¥«$|ûm»²xf1¶rI8'ƒW‘÷®pAîjÌ©a”¡à…=Ž?Jb4*XçeÆÌ8úzT "È2¤oJuiÇ*Ê2àƒÔSë> J0éè3éWc“x9Xu_Jõ·P>aoJe]¸·ÜK®r~ðõªÏ ¸·8ç¨úÐtÙH»Nqx8ïN¥U,ÁTIÀ½fOhdm¹&~ûøï“ßüx‡Æ  ›øjçv¥ƒî§Ç·¬qü}‹AÎvØøíñ×û$ÜøgÃw#íÜÅ}¨ÂÙv1FÃøýX}Þƒœ•ù´  @ ”§©( Ã4¶ÓÅ4¼Äáã–6*ÈÀäG ƒÞ¾±ø+ñÂ×â Vz½pÚŠ (lµæ0µÌÀ’Dã;‘Œ›œuÚ~K£Ó’äÔW—Ëéãà”¤µŒ–éÿ—tuáñ3÷mSÝtgë>Ÿã-ân|ñGN³º–ù¼˜/¦‰RÏRrHT ö[®F0B³sV!Ç¿´ÿì#â„÷šÿƒ ¹ñ„—-5¸R׺qÏ̲ tC¯ gpnl¯ƒÿ´ÔsF¾ø„VòÂXżZÄ‘‡*¸ ²éqûÅ#«òßÞÝœ¯Û>ø¯«ø6ÎÖ§›Åþh÷Á$2 ‹ûHûyîÅÜ cÇÌP>Gpuò”±õòÊʆ1(·³ûôìû­<­¢~ÜéÒÆS熩àKü×õæ~Ft¯JømûBxËá{¥jhWîÅtYÂsŸ”ç#é_|ühý‰þ~ÑÞ2øo¨XhÚ´ï&nìA6SàÄ@n·”óaUÁf.ŒkóÓâ¯ÁOüÖ¿³¼]¡Ï¦³±]c}µÈã•r­ÁŒäddÅ}[ú®cMÒ­.éžC|æƒÓºØýQý’à¤Vž,‡MѼIsçؚܾX ã;yØIù×ÐCÒ¾ªø»ðSÃÿ´«i¶°Ý¤ñ’(º”އŒàÿ“_ξ‘«]èZ•¾¡a;Û]Û¸xåC‚¦¿b?àžŸ¶1ñôV~Ô’-«na–@Í ¡z¯}§üŠøÜÏ-§†‚Ãâ[–Z)7ïQ—Fžü¿•»^þ… ηïh®Z‹uÒKüÏ><ü'›áŒdµ„JÚ5Ùg²’S–Lclõ#ù}+ÍF'¯~Õú?ÿ/ð •—…nµ8•aVùÞEp¬×qÿ¾…~iYKu«ß[éú}¬÷÷÷'d6–q4“JÝ‚ª‚IúÕÏ–ýbP• Fµ)ÉÁ¾öÙüÕ™ïÆ´gQlÕÎûàïÄsð·â.“¬Ë)M<È"¼\œydðÜtàþuúëâO‰øµðÂÉRðßkƯoibD’3‘÷‰*ÆzïcÛ-€?~Á?uÿ]Ûê?e—GÓÛ º”Šnåé,ƒ+ú…É÷ZúzÏÅú‚´–ðwÁí'I¼6èÒk :6œËB²á¯f ư6D²+ 1Xl=i[4©Ê<³Of“¼]ÓMJ.öµÛÙ+ÚÜ5j*²*wOO×äVð×߇¿³!—[—Jûg|Lå­¬4Ô_ß7 ñÛ£0@¹Ì’±H× »r¢¸ÿŠ?¬<oÄŠWM«ðèº&œÆHìÕ¶‡‚Énù16é€' òЬuÄücøñáïÙöMAî®fñÏÅ QCÜ›ÉÔÏ3º*‚Ý”¶Œ(ê¹,~ñoµ™RÃ7–  Øã¨ô  –Ll0OçÅB¼gsŽihè ¯,^VXd¡ûÉŽ*Å*©f  ’Nè7Ébê©óîû¸kÁ~=|x]n|/á{Ú‰+ýNÿQÙ¢ˆâìÌ:r9ÄŸþ=¦†·~ð­Öëö&;ýNâÆˆþ.Ìã§AÎHùr€ŽJ6ÓÉÊö59‘õé@(¦®Tí=;uQEkÒ>üyñ/“®¥¡»ï›H»cå’z´mÖ'çªðHƒŠóz+›†£‹¦è׊”_Gý~&”êN”¹àìÏÑÿƒÿü=ñ*ùµ¯kW>ñ€Œ%Í£yhš4ä,ÐÇwÉÁ ²Œccš÷Xþ)è>&Ò'о*xzÂÓN¸ù&Ôã®ôK‘†š&Ý-¡ÉÝ—ßã>p¯Æû{‰m."¸‚W‚âG,LUÑÈe#Aî+èß…¿¶¿‰|3-½ŸŒa>*ÓT…7Á„zŒK€3æ}Ùq×÷ƒsãñ²|v]ï`%í)¯±'i/ðËòOEÙ½Ovž:•m+{¯ºÙú£èÿŒŸðLøžÖ=sáV»‹ÌB[{ ÙšïO¸.+•.ê¸ÉÏïrHÆÑ^}û&~Îþ |p•ü[¢]höpÆ£í© \Z̧<¬ÑPG)ù¾nƒ·®|'ø­àÿ\4ÿ ¼o?†u«¦2Ï¥@šB o;N˜4¾-$A›þš^Ñÿ £Çzo‘׃<7â+ÅfÔ-ué´eQœå­dµ¹)èBJùÇt¯?œPÇaªeØÙ:R’³S\­.é½ìï#¢žÙÕUé+Û¶Û~yûNxjÚŸB²ðÅ„šŽ‘¢ùª×Z”–æ)î:¶Èbq¸giùF3÷[¥p^ð¯ÂŸÙ„M¡x{HŸYñxŒ ì4´K½^Lƒu;ŽÙ÷¯õÚ¤ñX¾6ñ¾¹w¥ÏyãOZø7Ã2d ñ´›YÈ÷ÎËupØlb#nñö¯šžñ÷‹oõÏ]êµ3Â>Q¶mÂí’Ò\äùWwgl—¤‚ÃÉR6û¦90 |›ñƒöÞšöÊ_|*´:•}˜kÓF#œÄݶÐcàsµ‰,0Šù›Æ¾:ñGÅ=hjÞ*Ö®õ»ÁÄorØŠã+C ñÑŸk…QÅ{¸Nö•'6©ífµQÚœ}#Õù½ú¦õ<Ú¸ÿw’‚å]ú²Õ•ˆ¹y$w,ÌŤv9wcÉf'©>¦µÀì+6ÎèªíÜ2 ü­€úúõ­uq•9þ•÷'H²²Œdàtö«ñL$ÈÝè+:ì`rxôí@tTPÊ\aˆÜ=;Z–€ ½k}ŽüÞ§ø¿úþÿQ¢€7Õƒ ŽE-gYÞB±Ëÿè_ýçWã‘e]ÈÁ‡µO ¥Xrr=ñŸcW’Ep~£¸¬Ê–J0„ð+—„Ãâ⡈¦¦—I$ÿ3HU'xI¯C[Ä>'Ö|s¨jx‹W¿×¯äóõ+†”H]Ç =†E ™8géýÚ®ŒQjÔÀQÐŽÞÆ·„#N*VK¢!·'vY(ÀAKEb ¿gvÌvž_ÿBÿëÿ“ëT(  õ`Ã#‘KY¶— ºÿ ëïZë"†S•= H„‚p=qÈ«ðÊ$PÞŸzΧÇ&Þ v+ÔÒ¢¢‚_1pÄnÇ­K@$AÁÅ]´»;€$—'×ïöïßùÕ*(}X0ÈäRÖm¥ãUnOCþ×§ãüëAdPÊr§¡ ÿÙfheroes2-1.0.12+dfsg/files/lang/000077500000000000000000000000001456075706000162645ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/files/lang/Makefile000066400000000000000000000057741456075706000177410ustar00rootroot00000000000000########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2024 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### ICONV = iconv # TODO: consider converting game fonts and texts to UTF-8 in the engine instead MSGFMT = sed -e '1,20 s/UTF-8/$(1)/' $< | $(ICONV) -f utf-8 -t $(1) | if msgfmt --help | grep -q no-convert >/dev/null 2>/dev/null; then msgfmt - -o $@ --no-convert; else msgfmt - -o $@; fi .PHONY: all clean merge all: $(patsubst %.po, %.mo, $(wildcard *.po)) merge: ../../src/dist/fheroes2.pot for i in $(wildcard *.po); do msgmerge -U --no-location $$i $<; done # Czech, Hungarian, Polish and Slovak versions use CP1250 cs.mo hu.mo pl.mo sk.mo: %.mo: %.po $(call MSGFMT,CP1250) # Belarusian, Bulgarian, Ukrainian versions use CP1251 # Russian versions from "Buka" and "XXI vek" use CP1251 encoding (supported) # Russian version from "Fargus" uses Russian keyboard layout as encoding (not supported) be.mo bg.mo ru.mo uk.mo: %.mo: %.po $(call MSGFMT,CP1251) # German, Danish, French, Italian, Dutch, Norwegian, Portuguese, Spanish and Swedish translations use CP1252 de.mo dk.mo es.mo fr.mo it.mo nb.mo pt.mo sv.mo nl.mo: %.mo: %.po $(call MSGFMT,CP1252) # Turkish uses CP1254 tr.mo: tr.po $(call MSGFMT,CP1254) # Lithaunian uses CP1257 lt.mo: lt.po $(call MSGFMT,CP1257) # Vietnamese uses CP1258 vi.mo: vi.po $(call MSGFMT,CP1258) # Romanian uses ISO-8859-16 ro.mo: ro.po $(call MSGFMT,ISO-8859-16) # All other languages: drop accents transliterated with `"` (which breaks translation file format) # and transliterate the rest with default iconv rules %.mo: %.po sed -e 'y/äëïöőüűÄËÃÅÖÜŰ/aeioouuAEIOOUU/' $< | $(ICONV) -f utf-8 -t ascii//TRANSLIT | msgfmt - -o $@ clean: rm -f *.mo *.po~ fheroes2-1.0.12+dfsg/files/lang/be.po000066400000000000000000006261471456075706000172320ustar00rootroot00000000000000# Belarusian translation of fheroes2 # Copyright (C) 2022 fheroes2 team # This file is distributed under the same license as the fheroes2 package. # msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-03 03:27+0000\n" "PO-Revision-Date: 2022-09-23 16:01+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" "Language: be\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" "X-Generator: Poedit 3.2\n" "X-Poedit-SourceCharset: UTF-8\n" msgid "" "BATTLE\n" "ONLY" msgstr "" "ÐСОБÐЫ\n" "БОЙ" msgid "" "NEW\n" "GAME" msgstr "ÐОВÐЯ" msgid "" "SAVE\n" "GAME" msgstr "" "ЗÐÐ¥Ð-\n" "Ð’ÐЦЬ" msgid "" "LOAD\n" "GAME" msgstr "" "ЗÐГРУ-\n" "ЗІЦЬ" msgid "INFO" msgstr "ДÐВЕДКÐ" msgid "QUIT" msgstr "" "ЗÐВЯР-\n" "ШЫЦЬ" msgid "CANCEL" msgstr "ÐÐЗÐД" msgid "OKAY" msgstr "ДОБРÐ" msgid "smallerButton|OKAY" msgstr "" msgid "ACCEPT" msgstr "ПРЫÐЯЦЬ" msgid "DECLINE" msgstr "ÐДМОВІЦЬ" msgid "LEARN" msgstr "" msgid "TRADE" msgstr "" msgid "YES" msgstr "ТÐК" msgid "NO" msgstr "ÐЕ" msgid "EXIT" msgstr "ВЫЙСЦІ" msgid "smallerButton|EXIT" msgstr "" msgid "DISMISS" msgstr "" msgid "UPGRADE" msgstr "ПÐЛЕПШЫЦЬ" msgid "RESTART" msgstr "ÐÐÐОВÐ" msgid "HEROES" msgstr "" msgid "" "TOWNS/\n" "CASTLES" msgstr "" msgid "S" msgstr "" msgid "M" msgstr "" msgid "L" msgstr "" msgid "X-L" msgstr "" msgid "ALL" msgstr "УСЕ" msgid "SELECT" msgstr "" msgid "" "STANDARD\n" "GAME" msgstr "" "ЗВЫЧÐЙÐÐЯ\n" "ГУЛЬÐЯ" msgid "" "CAMPAIGN\n" "GAME" msgstr "КÐМПÐÐІЯ" msgid "" "MULTI-\n" "PLAYER\n" "GAME" msgstr "" "ШМÐТ-\n" "ÐСÐБОВÐЯ\n" "ГУЛЬÐЯ" msgid "CONFIG" msgstr "ÐÐЛÐДЫ" msgid "" "ORIGINAL\n" "CAMPAIGN" msgstr "" msgid "" "EXPANSION\n" "CAMPAIGN" msgstr "" msgid "HOT SEAT" msgstr "" "ЗРÐДÐЫМ\n" "КÐМПУТÐРÐМ" msgid "2 PLAYERS" msgstr "2 ГУЛЬЦЫ" msgid "3 PLAYERS" msgstr "3 ГУЛЬЦЫ" msgid "4 PLAYERS" msgstr "4 ГУЛЬЦЫ" msgid "5 PLAYERS" msgstr "5 ГУЛЬЦОЎ" msgid "6 PLAYERS" msgstr "6 ГУЛЬЦОЎ" msgid "GIFT" msgstr "ÐДДÐЦЬ" msgid "MAX" msgstr "МÐКС" msgid "DIFFICULTY" msgstr "ЦЯЖКÐСЦЬ" msgid "VIEW INTRO" msgstr "" msgid "MIN" msgstr "МІÐ" msgid "RESET" msgstr "" #, fuzzy msgid "START" msgstr "ÐÐÐОВÐ" msgid "CASTLE" msgstr "" msgid "TOWN" msgstr "" msgid "" "D\n" "I\n" "S\n" "M\n" "I\n" "S\n" "S" msgstr "" msgid "" "E\n" "X\n" "I\n" "T" msgstr "" msgid "" "C\n" "A\n" "M\n" "P\n" "A\n" "I\n" "G\n" "N" msgstr "" msgid "" "S\n" "T\n" "A\n" "N\n" "D\n" "A\n" "R\n" "D" msgstr "" msgid "Warrior" msgstr "ВаÑÑ€" msgid "Builder" msgstr "Будаўнік" msgid "Explorer" msgstr "ДаÑледчык" msgid "None" msgstr "" msgid "" "A few\n" "%{monster}" msgstr "" "Трохі\n" "%{monster}" msgid "" "Several\n" "%{monster}" msgstr "" "Ðекалькі\n" "%{monster}" msgid "" "A pack of\n" "%{monster}" msgstr "" "Крыху\n" "%{monster}" msgid "" "Lots of\n" "%{monster}" msgstr "" "Шмат\n" "%{monster}" msgid "" "A horde of\n" "%{monster}" msgstr "" "Ðрда\n" "%{monster}" msgid "" "A throng of\n" "%{monster}" msgstr "" "Ðатоўп\n" "%{monster}" msgid "" "A swarm of\n" "%{monster}" msgstr "" "Рой\n" "%{monster}" msgid "" "Zounds...\n" "%{monster}" msgstr "" "Безліч\n" "%{monster}" msgid "" "A legion of\n" "%{monster}" msgstr "" "Легіён\n" "%{monster}" msgid "army|Few" msgstr "Трохі" msgid "army|Several" msgstr "Ðекалькі" msgid "army|Pack" msgstr "Крыху" msgid "army|Lots" msgstr "Шмат" msgid "army|Horde" msgstr "Ðрда" msgid "army|Throng" msgstr "Ðатоўп" msgid "army|Swarm" msgstr "Рой" msgid "army|Zounds" msgstr "Безліч" msgid "army|Legion" msgstr "Легіён" msgid "All %{race} troops +1" msgstr "" msgid "Multiple" msgstr "" msgid "Troops of %{count} alignments -%{penalty}" msgstr "" msgid "Some undead in army -1" msgstr "" msgid "View %{name}" msgstr "ПраглÑдзець %{name}" msgid "Move the %{name} " msgstr "" msgid "Move or right click to redistribute %{name}" msgstr "" msgid "Combine %{name} armies" msgstr "Ðб’Ñднаць войÑкі: %{name}" msgid "Exchange %{name2} with %{name}" msgstr "" msgid "Select %{name}" msgstr "Выбраць: %{name}" msgid "Cannot move last troop" msgstr "" msgid "Move the %{name}" msgstr "ПерамÑÑціць: %{name}" msgid "Set Count" msgstr "" msgid "%{name} destroys half the enemy troops!" msgid_plural "%{name} destroy half the enemy troops!" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "%{name} has turned off the auto battle" msgstr "%{name} выключыў аўта-бой" msgid "%{name} has turned on the auto battle" msgstr "%{name} уключыў аўта-бой" msgid "Spell failed!" msgstr "Загавор нÑўдалы!" msgid "" "The Sphere of Negation artifact is in effect for this battle, disabling all " "combat spells." msgstr "" msgid "You have already cast a spell this round." msgstr "" msgid "That spell will have no effect!" msgstr "" msgid "You may only summon one type of elemental per combat." msgstr "" msgid "" "There is no open space adjacent to your hero where you can summon an " "Elemental to." msgstr "" msgid "" "The Moat reduces by -%{count} the defense skill of any unit and slows to " "half movement rate." msgstr "" msgid "Speed: %{speed}" msgstr "ХуткаÑць: %{speed}" msgid "Speed" msgstr "ХуткаÑць" msgid "Off" msgstr "Выкл" msgid "On" msgstr "Укл" #, fuzzy msgid "Turn Order" msgstr "Парадак войÑку" msgid "Auto Spell Casting" msgstr "" msgid "Grid" msgstr "Сетка" msgid "Shadow Movement" msgstr "" msgid "Shadow Cursor" msgstr "" msgid "Audio" msgstr "Ðўдыё" msgid "Settings" msgstr "Ðалады" msgid "Configure" msgstr "Ðаладзіць" msgid "Hot Keys" msgstr "Ð“Ð°Ñ€Ð°Ñ‡Ñ‹Ñ ÐºÐ»Ð°Ð²Ñ–ÑˆÑ‹" #, fuzzy msgid "Damage Info" msgstr "Урон" msgid "Set the speed of combat actions and animations." msgstr "УÑталÑваць хуткаÑць баÑвых дзеÑннÑÑž Ñ– анімацый." msgid "Toggle to display the turn order during the battle." msgstr "" msgid "" "Toggle whether or not the computer will cast spells for you when auto combat " "is on. (Note: This does not affect spell casting for computer players in any " "way, nor does it affect quick combat.)" msgstr "" msgid "" "Toggle the hex grid on or off. The hex grid always underlies movement, even " "if turned off. This switch only determines if the grid is visible." msgstr "" msgid "" "Toggle on or off shadows showing where your creatures can move and attack." msgstr "" msgid "" "Toggle on or off a shadow showing the current hex location of the mouse " "cursor." msgstr "" msgid "Change the audio settings of the game." msgstr "ЗмÑніць налады аўдыё гульні." msgid "Check and configure all the hot keys present in the game." msgstr "ПраглÑдзець Ñ– наладзіць ÑпалучÑнні клавіш у гульні." msgid "Toggle to display damage information during the battle." msgstr "" msgid "Exit this menu." msgstr "ВыйÑці з меню" msgid "Okay" msgstr "Добра" msgid "The enemy has surrendered!" msgstr "Вораг здаўÑÑ!" msgid "Their cowardice costs them %{gold} gold." msgstr "" msgid "The enemy has fled!" msgstr "Вораг уцёк!" msgid "A glorious victory!" msgstr "Ð¡Ð»Ð°ÑžÐ½Ð°Ñ Ð¿ÐµÑ€Ð°Ð¼Ð¾Ð³Ð°!" msgid "For valor in combat, %{name} receives %{exp} experience." msgstr "За мужнаÑць у баі %{name} атрымлівае %{exp} вопыту." msgid "The cowardly %{name} flees from battle." msgstr "%{name} баÑзліва ўцÑкае з Ð¿Ð¾Ð»Ñ Ð±Ñ–Ñ‚Ð²Ñ‹." msgid "%{name} surrenders to the enemy, and departs in shame." msgstr "%{name} здаўÑÑ Ð²Ð¾Ñ€Ð°Ð³Ñƒ Ñ– Ñышоў з ганьбай." #, fuzzy msgid "Your forces suffer a bitter defeat, and %{name} abandons your cause." msgstr "Ð’Ð°ÑˆÑ‹Ñ Ð²Ð¾Ð¹Ñкі былі знішчаны, а %{name} пакідае ваÑ." #, fuzzy msgid "Your forces suffer a bitter defeat." msgstr "Ð’Ð°ÑˆÑ‹Ñ Ð²Ð¾Ð¹Ñкі былі знішчаны." msgid "Battlefield Casualties" msgstr "Страты на полі бою" msgid "Attacker" msgstr "Ðападнік" msgid "Defender" msgstr "Ðбаронца" #, fuzzy msgid "Click to leave the battle results." msgstr "ПÑтрыкні, каб ухваліць абранае раздзÑленне." #, fuzzy msgid "Click to restart the battle in manual mode." msgstr "ПÑтрыкні, каб вÑрнуцца Ñž галоўнае меню." msgid "Restart" msgstr "" msgid "You have captured an enemy artifact!" msgstr "Ð’Ñ‹ захапілі варожы артÑфакт!" msgid "As you reach for the %{name}, it mysteriously disappears." msgstr "" "СÑрод вашых трафеÑÑž апынуўÑÑ Ð°Ñ€Ñ‚Ñфакт - %{name}! Ðле Ñк толькі вы " "дакрануліÑÑ Ð´Ð° Ñго, той міÑтычна знік." msgid "As your enemy reaches for the %{name}, it mysteriously disappears." msgstr "Як толькі вораг дабраўÑÑ Ð´Ð° артÑфакту - %{name}, той міÑтычна знік." msgid "Necromancy!" msgstr "" msgid "" "Practicing the dark arts of necromancy, you are able to raise %{count} of " "the enemy's dead to return under your service as %{monster}." msgstr "" "Практыкуючы цёмнае майÑÑ‚ÑÑ€Ñтва некраманціі, вы можаце адрадзіць %{count} " "паўшых ворагаў, ÑÐºÑ–Ñ Ð´Ð°Ð»ÑƒÑ‡Ð°Ñ†Ñ†Ð° да вашага войÑку Ñк %{monster}." msgid "%{name} the %{race}" msgstr "%{race} %{name}" msgid "Captain of %{name}" msgstr "Капітан %{name}" msgid "Attack" msgstr "Ðтака" msgid "Defense" msgstr "Ðбарона" msgid "Spell Power" msgstr "ÐœÐ°Ð³Ñ–Ñ‡Ð½Ð°Ñ Ð¼Ð¾Ñ†" msgid "Knowledge" msgstr "Веды" msgid "Morale" msgstr "Мараль" msgid "Luck" msgstr "Удача" msgid "Spell Points" msgstr "Ðдзінкі магіі" msgid "Hero's Options" msgstr "Ðалады героÑ" msgid "Cast Spell" msgstr "" msgid "Retreat" msgstr "" msgid "Surrender" msgstr "" msgid "Cancel" msgstr "Ðазад" msgid "Hero Screen" msgstr "Экран героÑ" msgid "Captain's Options" msgstr "Ðаладкі капітана" msgid "" "Cast a magical spell. You may only cast one spell per combat round. The " "round is reset when every creature has had a turn." msgstr "" "Прамовіць магічны загавор. У Ð²Ð°Ñ Ñ‘Ñць магчымаÑць прамаўлÑць загаворы што " "раз, калі ÑžÑе ўдзельнікі Ð±Ð¾Ñ Ð·Ñ€Ð¾Ð±Ñць Ñвае хады." msgid "" "Retreat your hero, abandoning your creatures. Your hero will be available " "for you to recruit again, however, the hero will have only a novice hero's " "forces." msgstr "" msgid "" "Surrendering costs gold. However if you pay the ransom, the hero and all of " "his or her surviving creatures will be available to recruit again. The cost " "of surrender is half of the total cost of the non-temporary troops remaining " "in the army." msgstr "" msgid "Open Hero Screen to view full information about the hero." msgstr "Разгарніце Ñкран героÑ, каб праглÑдзець уÑÑŽ інфармацыю пра Ñго." msgid "Return to the battle." msgstr "Ð’Ñрнуцца да бою." msgid "Not enough gold (%{gold})" msgstr "ÐедаÑтаткова золату (%{gold})" msgid "%{name} states:" msgstr "" msgid "Captain of %{name} states:" msgstr "" msgid "" "\"I will accept your surrender and grant you and your troops safe passage " "for the price of %{price} gold.\"" msgstr "" msgid "View %{monster} info" msgstr "" msgid "Fly %{monster} here" msgstr "" msgid "Move %{monster} here" msgstr "" msgid "Shoot %{monster}" msgstr "" msgid "(1 shot left)" msgid_plural "(%{count} shots left)" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "Attack %{monster}" msgstr "" msgid "Turn %{turn}" msgstr "" msgid "Teleport here" msgstr "ТÑлепартаваць Ñюды" msgid "Invalid teleport destination" msgstr "" msgid "Cast %{spell} on %{monster}" msgstr "" msgid "Cast %{spell}" msgstr "" msgid "Select spell target" msgstr "" msgid "View Ballista info" msgstr "" msgid "Ballista" msgstr "" msgid "Enable auto combat" msgstr "Уключыць аўта-бой" msgid "Allows the computer to fight out the battle for you." msgstr "" msgid "Auto Combat" msgstr "Ðўта-бой" msgid "Customize system options" msgstr "" msgid "Allows you to customize the combat screen." msgstr "" msgid "System Options" msgstr "" msgid "Skip this unit" msgstr "" msgid "Skip" msgstr "" msgid "" "Skips the current creature. The current creature ends its turn and does not " "get to go again until the next round." msgstr "" msgid "View Captain's options" msgstr "" msgid "View Hero's options" msgstr "" msgid "View opposing Captain" msgstr "" msgid "View opposing Hero" msgstr "" msgid "Hide logs" msgstr "" msgid "Show logs" msgstr "" msgid "Message Bar" msgstr "" msgid "Shows the results of individual monster's actions." msgstr "" msgid "Are you sure you want to finish the battle in auto mode?" msgstr "" #, fuzzy msgid "Are you sure you want to enable auto combat?" msgstr "Ð’Ñ‹ ўпÑўненыÑ, што хочаце выйÑці?" msgid "%{name} skip their turn." msgstr "" msgid "%{attacker} does %{damage} damage." msgid_plural "%{attacker} do %{damage} damage." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "1 creature perishes." msgid_plural "%{count} creatures perish." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "1 %{defender} perishes." msgid_plural "%{count} %{defender} perish." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "1 soul is incorporated." msgid_plural "%{count} souls are incorporated." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "1 %{unit} is revived." msgid_plural "%{count} %{unit} are revived." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "Moved %{monster}: from [%{src}] to [%{dst}]." msgstr "" msgid "The %{name} resist the spell!" msgstr "" msgid "%{name} casts %{spell} on the %{troop}." msgstr "" msgid "%{name} casts %{spell}." msgstr "" msgid "The %{spell} does %{damage} damage to one undead creature." msgstr "" msgid "The %{spell} does %{damage} damage to all undead creatures." msgstr "" msgid "The %{spell} does %{damage} damage, %{count} creatures perish." msgstr "" msgid "The %{spell} does %{damage} damage." msgstr "" msgid "The %{spell} does %{damage} damage to one living creature." msgstr "" msgid "The %{spell} does %{damage} damage to all living creatures." msgstr "" msgid "The %{attacker}' attack blinds the %{target}!" msgstr "" msgid "The %{attacker}' gaze turns the %{target} to stone!" msgstr "" msgid "The %{attacker}' curse falls upon the %{target}!" msgstr "" msgid "The %{target} are paralyzed by the %{attacker}!" msgstr "" msgid "The %{attacker} dispel all good spells on your %{target}!" msgstr "" msgid "The %{attacker} cast %{spell} on %{target}!" msgstr "" msgid "Bad luck descends on the %{attacker}." msgstr "" msgid "Good luck shines on the %{attacker}." msgstr "" msgid "High morale enables the %{monster} to attack again." msgstr "" msgid "Low morale causes the %{monster} to freeze in panic." msgstr "" msgid "%{tower} does %{damage} damage." msgstr "" msgid "The mirror image is created." msgstr "" msgid "The mirror image is destroyed!" msgstr "" msgid "Are you sure you want to interrupt the auto battle?" msgstr "" msgid "Error" msgstr "Памылка" msgid "No spells to cast." msgstr "ÐÑма загавораў." msgid "Are you sure you want to retreat?" msgstr "" msgid "Retreat disabled" msgstr "" msgid "Surrender disabled" msgstr "" msgid "Damage: %{max}" msgstr "Урон: %{max}" msgid "Damage: %{min} - %{max}" msgstr "Урон: %{min} - %{max}" msgid "Perish: %{max}" msgstr "" msgid "Perish: %{min} - %{max}" msgstr "" msgid "" "Through eagle-eyed observation, %{name} is able to learn the magic spell " "%{spell}." msgstr "" msgid "Human" msgstr "Чалавек" msgid "AI" msgstr "Кампутар" msgid "Start" msgstr "" #, fuzzy msgid "Start the battle." msgstr "Ð’Ñрнуцца да бою." msgid "Exit" msgstr "Выхад" msgid "Reset" msgstr "" msgid "Reset to default settings." msgstr "" msgid "Please select another hero." msgstr "Калі лаÑка, выбÑрыце іншага героÑ." msgid "Set Attack Skill" msgstr "" msgid "Set Defense Skill" msgstr "" msgid "Set Power Skill" msgstr "" msgid "Set Knowledge Skill" msgstr "" msgid "%{race1} %{name1} vs %{race2} %{name2}" msgstr "" msgid "Monsters" msgstr "" msgid "N/A" msgstr "" msgid "Left Turret" msgstr "Ð›ÐµÐ²Ð°Ñ Ð²ÐµÐ¶Ð°" msgid "Right Turret" msgstr "ÐŸÑ€Ð°Ð²Ð°Ñ Ð²ÐµÐ¶Ð°" msgid "The %{name} fires with the strength of %{count} Archers" msgstr "" msgid "each with a +%{attack} bonus to their attack skill." msgstr "" #, fuzzy msgid "The %{name} is destroyed." msgstr "У горадзе ўжо Ñ‘Ñць %{name}." msgid "%{count} %{name} rises from the dead!" msgid_plural "%{count} %{name} rise from the dead!" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "Dwarven Alliance" msgstr "ÐльÑÐ½Ñ Ð· гномамі" msgid "Sorceress Guild" msgstr "Ð“Ñ–Ð»ÑŒÐ´Ñ‹Ñ Ñ‡Ð°Ñ€Ð°ÑžÐ½Ñ–Ñ†" msgid "Necromancer Guild" msgstr "Ð“Ñ–Ð»ÑŒÐ´Ñ‹Ñ Ð½ÐµÐºÑ€Ð°Ð¼Ð°Ð½Ñ‚Ð°Ñž" msgid "Ogre Alliance" msgstr "ÐльÑÐ½Ñ Ð· ограмі" msgid "Dwarfbane" msgstr "" msgid "Dragon Alliance" msgstr "ÐльÑÐ½Ñ Ð· цмокамі" msgid "Elven Alliance" msgstr "ÐльÑÐ½Ñ Ð· Ñльфамі" msgid "Kraeger defeated" msgstr "" msgid "Wayward Son" msgstr "Сын Ð’Ñйуорда" msgid "Uncle Ivan" msgstr "ДзÑдзька Іван" msgid "Annexation" msgstr "ÐнекÑÑ–Ñ" msgid "Force of Arms" msgstr "Сіла зброі" msgid "Save the Dwarves" msgstr "Уратаванне гномаў" msgid "Carator Mines" msgstr "Руднікі Каратора" msgid "Turning Point" msgstr "" msgid "scenarioName|Defender" msgstr "" msgid "Corlagon's Defense" msgstr "Ðбарона Карлагона" msgid "The Crown" msgstr "Карона" msgid "The Gauntlet" msgstr "Рукавіца" msgid "Betrayal" msgstr "Здраднік" msgid "Final Justice" msgstr "" #, fuzzy msgid "" "Roland needs you to defeat the lords near his castle to begin his war of " "rebellion against his brother. They are not allied with each other, so they " "will spend most of their time fighting with one another. Victory is yours " "when you have defeated all of their castles and heroes." msgstr "" "Роланду патрÑбна, каб вы перамаглі лордаў ÐºÐ°Ð»Ñ Ñгонага замку, каб раÑпачаць " "паўÑтанне Ñупраць брата. Яны не хаўруÑнікі, таму вÑлікую чаÑтку чаÑу будуць " "змагацца адзін з адным. Ð’Ñ‹ Ñкончыце паÑпÑхова міÑÑ–ÑŽ, Ñк толькі пераможаце " "ÑžÑÑ–Ñ… лордаў Ñ– захопіце Ñ–Ñ…Ð½Ñ‹Ñ Ð·Ð°Ð¼ÐºÑ–." msgid "" "The local lords refuse to swear allegiance to Roland, and must be subdued. " "They are wealthy and powerful, so be prepared for a tough fight. Capture all " "enemy castles to win." msgstr "" "МÑÑÑ†Ð¾Ð²Ñ‹Ñ Ð»Ð¾Ñ€Ð´Ñ‹ адмаўлÑюцца прыÑÑгаць на вернаÑць Роланду, таму Ñ–Ñ… трÑба " "ўціхамірыць. РыхтуйцеÑÑ Ð´Ð° жорÑткай барацьбы, бо Ñны Ð·Ð°Ð¼Ð¾Ð¶Ð½Ñ‹Ñ Ñ– ўплывовыÑ. " "Захапіце Ð²Ð°Ñ€Ð¾Ð¶Ñ‹Ñ Ð·Ð°Ð¼ÐºÑ–, каб перамагчы." msgid "" "Your task is to defend the Dwarves against Archibald's forces. Capture all " "of the enemy towns and castles to win, and be sure not to lose all of the " "dwarf towns at once, or the enemy will have won." msgstr "" "Ð’Ð°ÑˆÐ°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° - абараніць гномаў ад войÑкаў Ðрчыбальда. Каб перамагчы - " "захапіце ÑžÑе Ð²Ð°Ñ€Ð¾Ð¶Ñ‹Ñ Ð³Ð°Ñ€Ð°Ð´Ñ‹ Ñ– замкі. Сачыце за тым, каб не Ñтраціць адразу " "ÑžÑе гарады гномаў, іначай вораг пераможа." msgid "" "You will face four allied enemies in a straightforward fight for resource " "and treasure. Capture all of the enemy castles for victory." msgstr "" "Ð’Ñ‹ ÑутыкнёцеÑÑ Ð· чатырма ворагамі-хаўруÑнікамі Ñž бітве за Ñ€ÑÑурÑÑ‹ Ñ– Ñкарбы. " "Ð”Ð»Ñ Ð¿ÐµÑ€Ð°Ð¼Ð¾Ð³Ñ– захапіце ÑžÑе Ð²Ð°Ñ€Ð¾Ð¶Ñ‹Ñ Ð·Ð°Ð¼ÐºÑ–." msgid "" "Your enemies are allied against you and start close by, so be ready to come " "out fighting. You will need to own all four castles in this small valley to " "win." msgstr "" "Ð’Ð°ÑˆÑ‹Ñ Ð²Ð¾Ñ€Ð°Ð³Ñ– аб’ÑдналіÑÑ Ñупраць ваÑ, Ñ– Ñны побач з вамі, таму рыхтуйцеÑÑ " "адразу ÑžÑтупіць у бой. Вам неабходна авалодаць уÑімі чатырма замкамі Ñž гÑтай " "маленькай даліне, каб перамагчы." msgid "" "The Sorceress' guild of Noraston has requested Roland's aid against an " "attack from Archibald's allies. Capture all of the enemy castles to win, and " "don't lose Noraston, or you'll lose the scenario. (Hint: There is an enemy " "castle on an island in the ocean.)" msgstr "" "Ð“Ñ–Ð»ÑŒÐ´Ñ‹Ñ Ð§Ð°Ñ€Ð°ÑžÐ½Ñ–Ñ† ÐараÑтону звÑрнулаÑÑ Ð´Ð° Роланда па дапамогу Ñž абароне ад " "нападу хаўруÑнікаў Ðрчыбальда. Захапіце ÑžÑе Ð²Ð°Ñ€Ð¾Ð¶Ñ‹Ñ Ð·Ð°Ð¼ÐºÑ–, каб перамагчы, " "але не дапуÑціце Ñтрату ÐараÑтону, іначай вы атрымаеце паразу. (Падказка: на " "воÑтраве Ñž акіÑне Ñ‘Ñць варожы замак)" msgid "" "Gather as large an army as possible and capture the enemy castle within 8 " "weeks. You are opposed by only one enemy, but must travel a long way to get " "to the enemy castle. Any troops you have in your army at the end of this " "scenario will be with you in the final battle." msgstr "" "ЗбÑрыце Ñк мага болей войÑка, каб за 8 тыднÑÑž захапіць варожы замак. Вам " "ÑупрацьÑтаіць толькі адзін вораг, але вы муÑіце прайÑці доўгую дарогу да " "Ñгонага замку. УÑе войÑкі, ÑÐºÑ–Ñ Ð·Ð°Ñтануцца пад катролем вашага герою " "напрыканцы ÑцÑнару, будуць прымаць удзел у рашучым баю." msgid "" "Find the Crown before Archibald's heroes find it. Roland will need the Crown " "for the final battle against Archibald." msgstr "" "Знайдзіце Карону хутчÑй за героÑÑž Ðрчыбальда. Роланду неабходна Карона Ñž " "рашучым баю Ñупраць Ðрчыбальда." msgid "" "Three allied enemies stand before you and victory, including Lord Corlagon. " "Roland is in a castle to the northwest, and you will lose if he falls to the " "enemy. Remember that capturing Lord Corlagon will ensure that he will not " "fight against you in the final scenario." msgstr "" "Тры ворага-хаўруÑніка разам з Лордам Карлагонам ÑтаÑць паміж вамі Ñ– вашай " "перамогай. Роланд знаходзіцца Ñž замку на паўночна-заходнім кірунку, Ñ– вы " "прайграеце, калі Ñго захопÑць. ПамÑтайце, што захоп Лорда Карлагона гарантуе " "тое, што ён не будзе змагацца Ñупраць Ð²Ð°Ñ Ñƒ фінальным ÑцÑнары." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Archibald to end the war!" msgstr "" "ГÑта рашучы бой. Ð’Ñ‹ Ñ– Ð²Ð°ÑˆÑ‹Ñ Ð²Ð¾Ñ€Ð°Ð³Ñ– ÑžÐ·Ð±Ñ€Ð¾ÐµÐ½Ñ‹Ñ Ð´Ð° зубоў, Ñ– Ñны ÑžÑе аб’ÑдналіÑÑ " "Ñупраць ваÑ. Захапіце Ðрчыбальда, каб Ñкончыць вайну!" #, fuzzy msgid "" "Switching sides leaves you with three castles against the enemy's one. This " "battle will be the easiest one you will face for the rest of the war..." "traitor." msgstr "" "Калі вы перакінецеÑÑ Ð½Ð° іншы бок, у Ð²Ð°Ñ Ð·Ð°Ñтанецца тры замкі Ñупраць замка " "ворага. ГÑты бой будзе Ñамы лёгкі з тых, з Ñкімі вы ÑутыкнёцеÑÑ Ð´Ð° канца " "вайны… здраднік." msgid "Barbarian Wars" msgstr "" msgid "First Blood" msgstr "" msgid "Necromancers" msgstr "" msgid "Slay the Dwarves" msgstr "" msgid "Country Lords" msgstr "" msgid "Dragon Master" msgstr "" msgid "Rebellion" msgstr "" msgid "Apocalypse" msgstr "" msgid "Greater Glory" msgstr "" #, fuzzy msgid "" "King Archibald requires you to defeat the three enemies in this region. They " "are not allied with one another, so they will spend most of their energy " "fighting amongst themselves. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Роланду патрÑбна, каб вы перамаглі лордаў ÐºÐ°Ð»Ñ Ñгонага замку, каб раÑпачаць " "паўÑтанне Ñупраць брата. Яны не хаўруÑнікі, таму вÑлікую чаÑтку чаÑу будуць " "змагацца адзін з адным. Ð’Ñ‹ Ñкончыце паÑпÑхова міÑÑ–ÑŽ, Ñк толькі пераможаце " "ÑžÑÑ–Ñ… лордаў Ñ– захопіце Ñ–Ñ…Ð½Ñ‹Ñ Ð·Ð°Ð¼ÐºÑ–." msgid "" "You must unify the barbarian tribes of the north by conquering them. As in " "the previous mission, the enemy is not allied against you, but they have " "more resources at their disposal. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" msgid "" "Do-gooder wizards have taken the Necromancers' castle. You must retake it to " "achieve victory. Remember that while you start with a powerful army, you " "have no castle and must take one within 7 days, or lose this battle. (Hint: " "The nearest castle is to the southeast.)" msgstr "" msgid "" "The dwarves need conquering before they can interfere in King Archibald's " "plans. Roland's forces have more than one hero and many towns to start with, " "so be ready for attack from multiple directions. You must capture all of the " "enemy towns and castles to claim victory." msgstr "" msgid "" "You must put down a peasant revolt led by Roland's forces. All are allied " "against you, but you have Lord Corlagon, an experienced hero, to help you. " "Capture all enemy castles to win." msgstr "" msgid "" "There are two enemies allied against you in this mission. Both are well " "armed and seek to evict you from their island. Avoid them and capture Dragon " "City to win." msgstr "" msgid "" "Your orders are to conquer the country lords that have sworn to serve " "Roland. All of the enemy castles are unified against you. Since you start " "without a castle, you must hurry to capture one before the end of the week. " "Capture all enemy castles for victory." msgstr "" msgid "" "Find the Crown before Roland's heroes find it. Archibald will need the Crown " "for the final battle against Roland." msgstr "" msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Roland to win the war, and be sure " "not to lose Archibald in the fight!" msgstr "" msgid "Arrow's Flight" msgstr "" msgid "Island of Chaos" msgstr "" msgid "The Abyss" msgstr "" msgid "Uprising" msgstr "" msgid "Aurora Borealis" msgstr "" msgid "Betrayal's End" msgstr "" msgid "Corruption's Heart" msgstr "" msgid "The Giant's Pass" msgstr "" msgid "" "Subdue the unruly local lords in order to provide the Empire with facilities " "to operate in this region." msgstr "" msgid "" "Eliminate all opposition in this area. Then the first piece of the artifact " "will be yours." msgstr "" msgid "" "The sorceresses to the northeast are rebelling! For the good of the empire " "you must quash their feeble uprising on your way to the mountains." msgstr "" msgid "" "Having prepared for your arrival, Kraeger has arranged for a force of " "necromancers to thwart your quest. You must capture the castle of Scabsdale " "before the first day of the third week, or the Necromancers will be too " "strong for you." msgstr "" msgid "" "The barbarian despot in this area is, as yet, ignorant of your presence. " "Quickly, build up your forces before you are discovered and attacked! Secure " "the region by subduing all enemy forces." msgstr "" msgid "" "The Empire is weak in this region. You will be unable to completely subdue " "all forces in this area, so take what you can before reprisal strikes. " "Remember, your true goal is to claim the Helmet of Anduran." msgstr "" msgid "For the good of the Empire, eliminate Kraeger." msgstr "" msgid "" "At last, you have the opportunity and the facilities to rid the Empire of " "the necromancer's evil. Eradicate them completely, and you will be sung as a " "hero for all time." msgstr "" msgid "Border Towns" msgstr "" msgid "Conquer and Unify" msgstr "" msgid "Crazy Uncle Ivan" msgstr "" msgid "The Wayward Son" msgstr "" msgid "Ivory Gates" msgstr "" msgid "The Elven Lands" msgstr "" msgid "The Epic Battle" msgstr "" msgid "The Southern War" msgstr "" msgid "" "Conquer and unite all the enemy tribes. Don't lose the hero Jarkonas, the " "forefather of all descendants." msgstr "" msgid "" "Your rival, the Kingdom of Harondale, is attacking weak towns on your " "border! Recover from their first strike and crush them completely!" msgstr "" msgid "" "Find your wayward son Joseph who is rumored to be living in the desolate " "lands. Do it before the first day of the third month or it will be of no " "help to your family." msgstr "" msgid "" "Rescue your crazy uncle Ivan. Find him before the first day of the fourth " "month or it will be no help to your kingdom." msgstr "" msgid "" "Destroy the barbarians who are attacking the southern border of your " "kingdom! Recover your fallen towns, and then invade the jungle kingdom. " "Leave no enemy standing." msgstr "" msgid "Retake the castle of Ivory Gates, which has fallen due to treachery." msgstr "" msgid "" "Gain the favor of the elves. They will not allow trees to be chopped down, " "so they will send you wood every 2 weeks. You must complete your mission " "before the first day of the seventh month, or the kingdom will surely fall." msgstr "" msgid "" "This is the final battle against your rival kingdom of Harondale. Eliminate " "everyone, and don't lose the hero Jarkonas VI." msgstr "" msgid "Fount of Wizardry" msgstr "" msgid "Power's End" msgstr "" msgid "The Eternal Scrolls" msgstr "" msgid "The Shrouded Isles" msgstr "" msgid "" "Your mission is to vanquish the warring mages in the magical Shrouded Isles. " "The completion of this task will give you a fighting chance against your " "rivals." msgstr "" msgid "" "The location of the great library has been discovered! You must make your " "way to it, and reclaim the city of Chronos in which it lies." msgstr "" msgid "" "Find the Orb of negation, which is said to be buried in this land. There are " "clues inscribed on stone obelisks which will help lead you to your prize. " "Find the Orb before the first day of the sixth month, or your rivals will " "surely have gotten to the fount before you." msgstr "" msgid "" "You must take control of the castle of Magic, where the fount of wizardry " "lies. Do this and your victory will be supreme." msgstr "" msgid "Blood is Thicker" msgstr "" msgid "King and Country" msgstr "" msgid "Pirate Isles" msgstr "" msgid "Stranded" msgstr "" msgid "" "Capture the town on the island off the southeast shore in order to construct " "a boat and travel back towards the mainland. Do not lose the hero Gallavant." msgstr "" msgid "" "Find and defeat Martine, the pirate leader, who resides in Pirates Cove. Do " "not lose Gallavant or your quest will be over." msgstr "" msgid "" "Eliminate all the other forces who oppose the rule of Lord Alberon. " "Gallavant must not die." msgstr "" msgid "" "Overthrow the entrenched monarchy of Lord Alberon, and claim all the land in " "your name. Gallavant must not die." msgstr "" msgid " bane" msgstr "" msgid " alliance" msgstr "" msgid "Carry-over forces" msgstr "" msgid " bonus" msgstr "" msgid " defeated" msgstr "" msgid " will always run away from your army." msgstr "" msgid " will be willing to join your army." msgstr "" msgid "\"%{artifact}\" artifact will be carried over the scenario." msgstr "" msgid "The army will be carried over the scenario." msgstr "" msgid "The kingdom will have +%{count} %{resource} each day." msgstr "" msgid "\"%{spell}\" spell will be carried over the scenario." msgstr "" msgid "%{hero} can be hired in the scenario." msgstr "" msgid "" "%{hero} has been defeated and will not appear in the subsequent scenarios." msgstr "" msgid "The dwarves recognize their allies and gladly join your forces." msgstr "" msgid "The ogres recognize you as the Dwarfbane and lumber over to join you." msgstr "" msgid "" "The dragons, snarling and growling, agree to join forces with you, their " "'Ally'." msgstr "" msgid "" "As you approach the group of elves, their leader calls them all to " "attention. He shouts to them, \"Who of you is brave enough to join this " "fearless ally of ours?\" The group explodes with cheers as they run to join " "your ranks." msgstr "" msgid "" "The dwarves hail you, \"Any friend of Roland is a friend of ours. You may " "pass.\"" msgstr "" msgid "" "The ogres give you a grunt of recognition, \"Archibald's allies may pass.\"" msgstr "" msgid "" "The dragons see you and call out. \"Our alliance with Archibald compels us " "to join you. Unfortunately you have no room. A pity!\" They quickly scatter." msgstr "" msgid "" "The elves stand at attention as you approach. Their leader calls to you and " "says, \"Let us not impede your progress, ally! Move on, and may victory be " "yours.\"" msgstr "" msgid "\"The Dwarfbane!!!!, run for your lives.\"" msgstr "" msgid "campaignBonus|Animate Dead" msgstr "" msgid "campaignBonus|Chain Lightning" msgstr "" msgid "campaignBonus|Fireblast" msgstr "" msgid "campaignBonus|Mass Curse" msgstr "" msgid "campaignBonus|Mass Haste" msgstr "" msgid "campaignBonus|Mirror Image" msgstr "" msgid "campaignBonus|Resurrect" msgstr "" msgid "campaignBonus|Steelskin" msgstr "" msgid "campaignBonus|Summon Earth" msgstr "" msgid "campaignBonus|View Heroes" msgstr "" msgid "campaignBonus|Ballista" msgstr "" msgid "campaignBonus|Black Pearl" msgstr "" msgid "campaignBonus|Caster's Bracelet" msgstr "" msgid "campaignBonus|Defender Helm" msgstr "" msgid "campaignBonus|Breastplate" msgstr "" msgid "campaignBonus|Dragon Sword" msgstr "" msgid "campaignBonus|Fizbin Medal" msgstr "" msgid "campaignBonus|Foremost Scroll" msgstr "" msgid "campaignBonus|Gauntlets" msgstr "" msgid "campaignBonus|Hideous Mask" msgstr "" msgid "campaignBonus|Mage's Ring" msgstr "" msgid "campaignBonus|Major Scroll" msgstr "" msgid "campaignBonus|Medal of Honor" msgstr "" msgid "campaignBonus|Medal of Valor" msgstr "" msgid "campaignBonus|Minor Scroll" msgstr "" msgid "campaignBonus|Nomad Boots" msgstr "" msgid "campaignBonus|Power Axe" msgstr "" msgid "campaignBonus|Spiked Shield" msgstr "" msgid "campaignBonus|Stealth Shield" msgstr "" msgid "campaignBonus|Tax Lien" msgstr "" msgid "campaignBonus|Thunder Mace" msgstr "" msgid "campaignBonus|Traveler's Boots" msgstr "" msgid "campaignBonus|White Pearl" msgstr "" msgid "campaignBonus|Basic Archery" msgstr "" msgid "campaignBonus|Advanced Archery" msgstr "" msgid "campaignBonus|Expert Archery" msgstr "" msgid "campaignBonus|Basic Ballistics" msgstr "" msgid "campaignBonus|Advanced Ballistics" msgstr "" msgid "campaignBonus|Expert Ballistics" msgstr "" msgid "campaignBonus|Basic Diplomacy" msgstr "" msgid "campaignBonus|Advanced Diplomacy" msgstr "" msgid "campaignBonus|Expert Diplomacy" msgstr "" msgid "campaignBonus|Basic Eagle Eye" msgstr "" msgid "campaignBonus|Advanced Eagle Eye" msgstr "" msgid "campaignBonus|Expert Eagle Eye" msgstr "" msgid "campaignBonus|Basic Estates" msgstr "" msgid "campaignBonus|Advanced Estates" msgstr "" msgid "campaignBonus|Expert Estates" msgstr "" msgid "campaignBonus|Basic Leadership" msgstr "" msgid "campaignBonus|Advanced Leadership" msgstr "" msgid "campaignBonus|Expert Leadership" msgstr "" msgid "campaignBonus|Basic Logistics" msgstr "" msgid "campaignBonus|Advanced Logistics" msgstr "" msgid "campaignBonus|Expert Logistics" msgstr "" msgid "campaignBonus|Basic Luck" msgstr "" msgid "campaignBonus|Advanced Luck" msgstr "" msgid "campaignBonus|Expert Luck" msgstr "" msgid "campaignBonus|Basic Mysticism" msgstr "" msgid "campaignBonus|Advanced Mysticism" msgstr "" msgid "campaignBonus|Expert Mysticism" msgstr "" msgid "campaignBonus|Basic Navigation" msgstr "" msgid "campaignBonus|Advanced Navigation" msgstr "" msgid "campaignBonus|Expert Navigation" msgstr "" msgid "campaignBonus|Basic Necromancy" msgstr "" msgid "campaignBonus|Advanced Necromancy" msgstr "" msgid "campaignBonus|Expert Necromancy" msgstr "" msgid "campaignBonus|Basic Pathfinding" msgstr "" msgid "campaignBonus|Advanced Pathfinding" msgstr "" msgid "campaignBonus|Expert Pathfinding" msgstr "" msgid "campaignBonus|Basic Scouting" msgstr "" msgid "campaignBonus|Advanced Scouting" msgstr "" msgid "campaignBonus|Expert Scouting" msgstr "" msgid "campaignBonus|Basic Wisdom" msgstr "" msgid "campaignBonus|Advanced Wisdom" msgstr "" msgid "campaignBonus|Expert Wisdom" msgstr "" msgid "" "The main hero will have \"%{artifact}\" artifact at the start of the " "scenario." msgstr "" msgid "" "The kingdom will receive %{amount} additional %{resource} at the start of " "the scenario." msgstr "" msgid "" "The kingdom will have %{amount} less %{resource} at the start of the " "scenario." msgstr "" msgid "" "The main hero will have %{count} %{monster} at the start of the scenario." msgstr "" msgid "" "The main hero will have \"%{spell}\" spell at the start of the scenario." msgstr "" msgid "The starting race of the scenario will be %{race}." msgstr "" msgid "" "The main hero will have additional %{count} %{skill} at the start of the " "scenario." msgstr "" msgid "The main hero will have %{skill} at the start of the scenario." msgstr "" msgid "Roland" msgstr "Роланд" msgid "Archibald" msgstr "Ðрчыбальд" #, fuzzy msgid "Price of Loyalty" msgstr "Кошт вернаÑці" msgid "Voyage Home" msgstr "" msgid "Wizard's Isle" msgstr "" msgid "Descendants" msgstr "" msgid "The %{building} produces %{monster}." msgstr "%{building} - пабудова, дзе штотыдзень падрыхтоўваюцца %{monster}." msgid "Requires:" msgstr "Ðеабходна:" msgid "Exit this menu without doing anything." msgstr "ВыйÑці з меню, нічога не змÑнÑючы." msgid "Cannot build. You have already built here today." msgstr "Ð’Ñ‹ ўжо будавалі ÑÑ‘Ð½Ð½Ñ Ñž гÑтым замку." msgid "For this action it is necessary to build a castle first." msgstr "" #, fuzzy msgid "Cannot build %{name} because the castle is too far away from an ocean." msgstr "Ðельга пабудаваць %{name}, бо замак занадта далёка ад вады." msgid "disable build." msgstr "" msgid "Cannot afford %{name}." msgstr "Ðельга пабудаваць." msgid "%{name} is already built." msgstr "У горадзе ўжо Ñ‘Ñць %{name}." msgid "Cannot build %{name}." msgstr "%{name}. Ðельга пабудаваць." msgid "Build %{name}." msgstr "%{name}. Можна пабудаваць." msgid "Blackridge" msgstr "" msgid "Hillstone" msgstr "" msgid "Pinehurst" msgstr "" msgid "Whiteshield" msgstr "" msgid "Woodhaven" msgstr "" msgid "Blackwind" msgstr "" msgid "Bloodreign" msgstr "" msgid "Dragontooth" msgstr "" msgid "Greywind" msgstr "" msgid "Portsmith" msgstr "" msgid "Atlantium" msgstr "" msgid "Middle Gate" msgstr "" msgid "Sansobar" msgstr "" msgid "Tundara" msgstr "" msgid "Vulcania" msgstr "" msgid "Baywatch" msgstr "" msgid "Fountainhead" msgstr "" msgid "Vertigo" msgstr "" msgid "Wildabar" msgstr "" msgid "Winterkill" msgstr "" msgid "Brindamoor" msgstr "" msgid "Lakeside" msgstr "" msgid "Nightshadow" msgstr "" msgid "Olympus" msgstr "" msgid "Sandcaster" msgstr "" msgid "Alamar" msgstr "" msgid "Burlock" msgstr "" msgid "Dragadune" msgstr "" msgid "Kalindra" msgstr "" msgid "Xabran" msgstr "" msgid "Algary" msgstr "" msgid "Basenji" msgstr "" msgid "Blackfang" msgstr "" msgid "New Dawn" msgstr "" msgid "Sorpigal" msgstr "" msgid "Avone" msgstr "" msgid "Big Oak" msgstr "" msgid "Chandler" msgstr "" msgid "Erliquin" msgstr "" msgid "Hampshire" msgstr "" msgid "Antioch" msgstr "" msgid "Avalon" msgstr "" msgid "Roc Haven" msgstr "" msgid "South Mill" msgstr "" msgid "Weed Patch" msgstr "" msgid "Brownston" msgstr "" msgid "Hilltop" msgstr "" msgid "Weddington" msgstr "УÑдзінтан" msgid "Westfork" msgstr "УÑÑтфарк" msgid "Whittingham" msgstr "УіцінгÑм" msgid "Cathcart" msgstr "" msgid "Elk's Head" msgstr "" msgid "Roscomon" msgstr "" msgid "Sherman" msgstr "ШÑрман" msgid "Yorksford" msgstr "ÐркÑфард" msgid "Blackburn" msgstr "БлÑкбёрн" msgid "Blacksford" msgstr "" msgid "Burton" msgstr "" msgid "Pig's Eye" msgstr "" msgid "Viper's Nest" msgstr "" msgid "Fenton" msgstr "ФÑнтан" msgid "Lankershire" msgstr "Ланкершыр" msgid "Lombard" msgstr "" msgid "Timberhill" msgstr "Цімберхіл" msgid "Troy" msgstr "ТроÑ" msgid "Forder Oaks" msgstr "" msgid "Meramec" msgstr "" msgid "Quick Silver" msgstr "" msgid "Westmoor" msgstr "УÑÑтмур" msgid "Willow" msgstr "Уілаў" msgid "Corackston" msgstr "КаракÑтоўн" msgid "Sheltemburg" msgstr "ШÑлтÑмбург" msgid "Cannot recruit - you already have a Hero in this town." msgstr "" msgid "Cannot recruit - you have too many Heroes." msgstr "" msgid "Cannot afford a Hero" msgstr "" msgid "There is no room in the garrison for this army." msgstr "" msgid "Fortifications" msgstr "" msgid "Farm" msgstr "" msgid "Thatched Hut" msgstr "" msgid "Archery Range" msgstr "" msgid "Upg. Archery Range" msgstr "" msgid "Blacksmith" msgstr "" msgid "Upg. Blacksmith" msgstr "" msgid "Armory" msgstr "" msgid "Upg. Armory" msgstr "" msgid "Jousting Arena" msgstr "" msgid "Upg. Jousting Arena" msgstr "" msgid "Cathedral" msgstr "" msgid "Upg. Cathedral" msgstr "" msgid "Coliseum" msgstr "" msgid "Garbage Heap" msgstr "" msgid "Hut" msgstr "" msgid "Stick Hut" msgstr "" msgid "Upg. Stick Hut" msgstr "" msgid "Den" msgstr "" msgid "Adobe" msgstr "" msgid "Upg. Adobe" msgstr "" msgid "Bridge" msgstr "" msgid "Upg. Bridge" msgstr "" msgid "Pyramid" msgstr "" msgid "Rainbow" msgstr "Ð’ÑÑёлка" msgid "Crystal Garden" msgstr "" msgid "Treehouse" msgstr "" msgid "Cottage" msgstr "" msgid "Upg. Cottage" msgstr "" msgid "Stonehenge" msgstr "" msgid "Upg. Stonehenge" msgstr "" msgid "Fenced Meadow" msgstr "" msgid "sorceress|Red Tower" msgstr "Ð§Ñ‹Ñ€Ð²Ð¾Ð½Ð°Ñ Ð²ÐµÐ¶Ð°" msgid "Dungeon" msgstr "" msgid "Waterfall" msgstr "" msgid "Cave" msgstr "" msgid "Crypt" msgstr "" msgid "Nest" msgstr "" msgid "Maze" msgstr "" msgid "Upg. Maze" msgstr "" msgid "Swamp" msgstr "" msgid "Green Tower" msgstr "ЗÑÐ»Ñ‘Ð½Ð°Ñ Ð²ÐµÐ¶Ð°" msgid "warlock|Red Tower" msgstr "" msgid "Black Tower" msgstr "Ð§Ð¾Ñ€Ð½Ð°Ñ Ð²ÐµÐ¶Ð°" msgid "Library" msgstr "БібліÑÑ‚Ñка" msgid "Orchard" msgstr "Фруктовы Ñад" msgid "Habitat" msgstr "Жыллё" msgid "Pen" msgstr "Вальера" msgid "Foundry" msgstr "Ліцейны завод" msgid "Upg. Foundry" msgstr "" msgid "Cliff Nest" msgstr "ГнÑздо на кручы" msgid "Ivory Tower" msgstr "Вежа магаў" msgid "Upg. Ivory Tower" msgstr "ÐбÑÐµÑ€Ð²Ð°Ñ‚Ð¾Ñ€Ñ‹Ñ Ð¼Ð°Ð³Ð°Ñž" msgid "Cloud Castle" msgstr "Воблачны замак" msgid "Upg. Cloud Castle" msgstr "" msgid "Storm" msgstr "" msgid "Skull Pile" msgstr "" msgid "Excavation" msgstr "" msgid "Graveyard" msgstr "" msgid "Upg. Graveyard" msgstr "" msgid "Upg. Pyramid" msgstr "" msgid "Mansion" msgstr "" msgid "Upg. Mansion" msgstr "" msgid "Mausoleum" msgstr "" msgid "Upg. Mausoleum" msgstr "" msgid "Laboratory" msgstr "" msgid "Shrine" msgstr "" msgid "" "The Fortifications increase the toughness of the walls, increasing the " "number of turns it takes to knock them down." msgstr "" msgid "The Farm increases production of Peasants by %{count} per week." msgstr "" msgid "" "The Coliseum provides inspiring spectacles to defending troops, raising " "their morale by two during combat." msgstr "" msgid "The Garbage Heap increases production of Goblins by %{count} per week." msgstr "" msgid "The Rainbow increases the luck of the defending units by two." msgstr "" msgid "" "The Crystal Garden increases production of Sprites by %{count} per week." msgstr "" #, fuzzy msgid "The Dungeon increases the income of the town by %{count} gold per day." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð°Ð²Ñлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ на %{count} штодзень." msgid "The Waterfall increases production of Centaurs by %{count} per week." msgstr "" msgid "" "The Library increases the number of spells in the Guild by one for each " "level of the guild." msgstr "" "БібліÑÑ‚Ñка павÑлічвае колькаÑць загавораў у Гільдыі на адзін на кожны " "ўзровень гільдыі." msgid "The Orchard increases production of Halflings by %{count} per week." msgstr "Фруктовы Ñад прываблівае дадатковых %{count} хобітаў штотыдзень." msgid "The Storm adds +2 to the power of spells of a defending spell caster." msgstr "" msgid "The Skull Pile increases production of Skeletons by %{count} per week." msgstr "" msgid "Thieves' Guild" msgstr "Ð“Ñ–Ð»ÑŒÐ´Ñ‹Ñ Ð²Ð°Ñ€Ð¾Ñž" msgid "Tavern" msgstr "Карчма" msgid "Shipyard" msgstr "Верф" msgid "Well" msgstr "Калодзеж" msgid "Statue" msgstr "СтатуÑ" msgid "Marketplace" msgstr "Рынак" msgid "Moat" msgstr "Роў" msgid "Castle" msgstr "Замак" msgid "Tent" msgstr "Шацёр" msgid "Captain's Quarters" msgstr "Жыллё капітана" msgid "Mage Guild, Level 1" msgstr "Ð“Ñ–Ð»ÑŒÐ´Ñ‹Ñ Ð¼Ð°Ð³Ð°Ñž I" msgid "Mage Guild, Level 2" msgstr "Ð“Ñ–Ð»ÑŒÐ´Ñ‹Ñ Ð¼Ð°Ð³Ð°Ñž II" msgid "Mage Guild, Level 3" msgstr "Ð“Ñ–Ð»ÑŒÐ´Ñ‹Ñ Ð¼Ð°Ð³Ð°Ñž III" msgid "Mage Guild, Level 4" msgstr "Ð“Ñ–Ð»ÑŒÐ´Ñ‹Ñ Ð¼Ð°Ð³Ð°Ñž IV" msgid "Mage Guild, Level 5" msgstr "Ð“Ñ–Ð»ÑŒÐ´Ñ‹Ñ Ð¼Ð°Ð³Ð°Ñž V" msgid "" "The Shrine increases the necromancy skill of all your necromancers by 10 " "percent." msgstr "" msgid "" "The Thieves' Guild provides information on enemy players. Thieves' Guilds " "can also provide scouting information on enemy towns. Additional Guilds " "provide more information." msgstr "" msgid "The Tavern increases morale for troops defending the castle." msgstr "Карчма павÑлічвае мараль вашых войÑкаў Ð¿Ð°Ð´Ñ‡Ð°Ñ Ð°Ð±Ð°Ñ€Ð¾Ð½Ñ‹ замка." msgid "The Shipyard allows ships to be built." msgstr "Верф дазвалÑе будаваць караблі." msgid "" "The Well increases the growth rate of all dwellings by %{count} creatures " "per week." msgstr "" #, fuzzy msgid "The Statue increases the town's income by %{count} gold per day." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð°Ð²Ñлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ на %{count} штодзень." msgid "The Left Turret provides extra firepower during castle combat." msgstr "Ð›ÐµÐ²Ð°Ñ Ð²ÐµÐ¶Ð° забÑÑпечвае дадатковую агнÑвую моц Ð¿Ð°Ð´Ñ‡Ð°Ñ Ð°Ð±Ð°Ñ€Ð¾Ð½Ñ‹ замка." msgid "The Right Turret provides extra firepower during castle combat." msgstr "ÐŸÑ€Ð°Ð²Ð°Ñ Ð²ÐµÐ¶Ð° забÑÑпечвае дадатковую агнÑвую моц Ð¿Ð°Ð´Ñ‡Ð°Ñ Ð°Ð±Ð°Ñ€Ð¾Ð½Ñ‹ замка." msgid "" "The Marketplace can be used to convert one type of resource into another. " "The more marketplaces you control, the better the exchange rate." msgstr "" "Рынак дазвалÑе мÑнÑць адзін від Ñ€ÑÑурÑаў на іншы. Чым болей рынкаў вы маеце, " "тым лепей будуць умовы абмену." msgid "" "The Moat slows attacking units. Any unit entering the moat must end its turn " "there and becomes more vulnerable to attack." msgstr "" "Роў запавольвае Ð°Ñ‚Ð°ÐºÑƒÑŽÑ‡Ñ‹Ñ Ð²Ð¾Ð¹Ñкі. Любое войÑка, Ñкое ўвайшло Ñž роў не можа " "рухацца далей Ñ– павінна Ñкончыць Ñвой ход, а такÑама горш абаранÑецца." #, fuzzy msgid "" "The Castle improves the town's defense and increases its income to %{count} " "gold per day." msgstr "" "Замак узмацнÑе абарону горада Ñ– павÑлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ да %{count} " "золата штодзень." msgid "" "The Tent provides workers to build a castle, provided the materials and the " "gold are available." msgstr "" msgid "" "The Captain's Quarters provides a captain to assist in the castle's defense " "when no hero is present." msgstr "" msgid "" "The Mage Guild allows heroes to learn spells and replenish their spell " "points." msgstr "" "Ð“Ñ–Ð»ÑŒÐ´Ñ‹Ñ Ð¼Ð°Ð³Ð°Ñž дазвалÑе героÑм вучыць загаворы Ñ– папаўнÑць Ñ–Ñ…Ð½Ñ‹Ñ Ð·Ð°Ð¿Ð°ÑÑ‹ маны." msgid "Recruit %{name}" msgstr "%{name}" msgid "Month: %{month}, Week: %{week}, Day: %{day}" msgstr "МеÑÑц: %{month}, Тыдзень: %{week}, Дзень: %{day}" msgid "" "You must purchase a spell book to use the mage guild, but you currently have " "no room for a spell book. Try giving one of your artifacts to another hero." msgstr "" msgid "Click to show next town." msgstr "" msgid "Show next town" msgstr "" msgid "Click to show previous town." msgstr "" msgid "Show previous town" msgstr "" msgid "This town may not be upgraded to a castle." msgstr "" msgid "Town" msgstr "Горад" msgid "Exit Castle" msgstr "Пакінуць замак" msgid "Exit Town" msgstr "Пакінуць горад" msgid "Show Income" msgstr "Паказаць прыбытак" msgid "View Hero" msgstr "Даведацца аб героі" msgid "The above spells are available here." msgstr "" msgid "The spells the hero can learn have been added to their book." msgstr "" msgid "A generous tip for the barkeep yields the following rumor:" msgstr "" msgid "Recruit Hero" msgstr "РÑкрутаваць героÑ" msgid "%{name} is a level %{value} %{race} " msgstr "" msgid "with %{count} artifacts." msgstr "" msgid "with 1 artifact." msgstr "" msgid "without artifacts." msgstr "" msgid "Recruit %{name} the %{race}" msgstr "РÑкрутаваць %{race} %{name}" msgid "" "'Spread' combat formation spreads the castle's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" msgid "" "'Grouped' combat formation bunches the castle's units together in the center " "of the castle's side of the battlefield." msgstr "" msgid "Spread Formation" msgstr "" msgid "Grouped Formation" msgstr "" msgid "Set garrison combat formation to 'Spread'" msgstr "" msgid "Set garrison combat formation to 'Grouped'" msgstr "" msgid "Exit Castle Options" msgstr "" msgid "Castle Options" msgstr "Ðаладкі замка" msgid "Not enough resources to recruit creatures." msgstr "" msgid "You are unable to recruit at this time, your ranks are full." msgstr "" msgid "No creatures available for purchase." msgstr "" msgid "Recruit Creatures" msgstr "" msgid "Max" msgstr "" msgid "Hire all creatures in the town." msgstr "" msgid "Available" msgstr "ДаÑтупна" msgid "Town Population Information and Statistics" msgstr "" msgid "Damage" msgstr "Урон" msgid "HP" msgstr "" msgid "Growth" msgstr "" msgid "week" msgstr "тыдзень" msgid "View World" msgstr "" msgid "View the entire world." msgstr "" msgid "Puzzle" msgstr "" msgid "View the obelisk puzzle." msgstr "" msgid "Scenario Information" msgstr "" msgid "View information on the scenario you are currently playing." msgstr "" msgid "Dig for the Ultimate Artifact." msgstr "" msgid "Digging" msgstr "" msgid "Arena" msgstr "" msgid "" "You enter the arena and face a pack of vicious lions. You handily defeat " "them, to the wild cheers of the crowd. Impressed by your skill, the aged " "trainer of gladiators agrees to train you in a skill of your choice." msgstr "" msgid "Attack Skill" msgstr "Ðтака" msgid "Defense Skill" msgstr "Ðбарона" msgid "Shots" msgstr "СтрÑлы" msgid "Shots Left" msgstr "ÐÑтатак ÑтрÑлаў" msgid "Hit Points" msgstr "Здароўе" msgid "Hit Points Left" msgstr "ÐÑтатак здароўÑ" msgid "You can't afford to upgrade your troops!" msgstr "" msgid "" "Your troops can be upgraded, but it will cost you dearly. Do you wish to " "upgrade them?" msgstr "" msgid "Are you sure you want to dismiss this army?" msgstr "" msgid "Upgrade" msgstr "" msgid "Upgrade your troops." msgstr "" msgid "Dismiss" msgstr "" msgid "Dismiss this army." msgstr "" msgid "" "A group of %{monster} with a desire for greater glory wish to join you.\n" "Do you accept?" msgstr "" msgid "Followers" msgstr "" msgid "" "The %{monster} is swayed by your diplomatic tongue, and offers to join your " "army for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" msgid "" "The creatures are swayed by your diplomatic\n" "tongue, and make you an offer:\n" "\n" msgstr "" msgid "" "%{offer} of the %{total} %{monster} will join your army, and the rest will " "leave you alone, for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" msgid "" "All %{offer} of the %{monster} will join your army for the sum of %{gold} " "gold.\n" "Do you accept?" msgstr "" msgid "(Rate: %{percent})" msgstr "" msgid "off" msgstr "выкл." msgid "Music" msgstr "Музыка" msgid "Effects" msgstr "Эфекты" msgid "MIDI" msgstr "" msgid "MIDI Expansion" msgstr "" msgid "External" msgstr "Вонкавы" msgid "Music Type" msgstr "Тып Музыкі" msgid "3D Audio" msgstr "3D Ðўдыё" msgid "Toggle ambient music level." msgstr "ЗмÑніць гучнаÑць фонавай музыкі." msgid "Toggle foreground sounds level." msgstr "ЗмÑніць гучнаÑць гукаў на пÑÑ€Ñднім плане." msgid "Change the type of music." msgstr "ЗмÑніць тып музыкі." msgid "Toggle 3D effects of foreground sounds." msgstr "Укл/выкл 3D Ñфекты гукаў на пÑÑ€Ñднім плане." msgid "Build a new ship:" msgstr "" msgid "Resource cost:" msgstr "" msgid "Total: " msgstr "" msgid "Need: " msgstr "" msgid "Load Game" msgstr "Загрузіць гульню" msgid "No save files to load." msgstr "" msgid "New Game" msgstr "ÐÐ¾Ð²Ð°Ñ Ð³ÑƒÐ»ÑŒÐ½Ñ" msgid "Start a single or multi-player game." msgstr "Пачаць новую адзіночную ці шматаÑабовую гульню." msgid "Load a previously saved game." msgstr "ПрацÑгнуць захаваную гульню." msgid "Save Game" msgstr "" msgid "Save the current game." msgstr "" msgid "Quit" msgstr "Скончыць" msgid "Quit out of Heroes of Might and Magic II." msgstr "ВыйÑці з Heroes of Might and Magic II." msgid "Language" msgstr "Мова" msgid "Graphics" msgstr "ГрафічныÑ" msgid "Black & White" msgstr "Чорна-белы" msgid "Mouse Cursor" msgstr "КурÑор мышы" msgid "Color" msgstr "КалÑровы" msgid "Text Support" msgstr "ТÑкÑÑ‚Ð°Ð²Ñ‹Ñ Ð¿Ð°Ð´ÐºÐ°Ð·ÐºÑ–" msgid "Change the language of the game." msgstr "ЗмÑніць мову гульні." msgid "Select Game Language" msgstr "Мова" msgid "Change the graphics settings of the game." msgstr "Ðаладзіць графіку." #, fuzzy msgid "Toggle colored cursor on or off. This is only an aesthetic choice." msgstr "Укл/выкл калÑровы курÑор. ГÑта ўплывае толькі на візуал." msgid "" "Toggle text support mode to output extra information about windows and " "events in the game." msgstr "" "Укл/выкл Ñ‚ÑкÑÑ‚Ð°Ð²Ñ‹Ñ Ð¿Ð°Ð´ÐºÐ°Ð·ÐºÑ– з дадатковай інфармацыÑй пра вокны Ñ– падзеі Ñž " "гульні." msgid "" "Map\n" "Difficulty" msgstr "" msgid "" "Game\n" "Difficulty" msgstr "" msgid "Rating" msgstr "" msgid "Map Size" msgstr "" msgid "Opponents" msgstr "Супраціўнікі" msgid "Class" msgstr "КлаÑ" msgid "" "Victory\n" "Conditions" msgstr "" msgid "" "Loss\n" "Conditions" msgstr "" msgid "First select recipients!" msgstr "" msgid "You cannot select %{resource}!" msgstr "" msgid "Select count %{resource}:" msgstr "" msgid "Select Recipients" msgstr "" msgid "Your Funds" msgstr "" msgid "Planned Gift" msgstr "" msgid "Gift from %{name}" msgstr "" msgid "Resolution" msgstr "РаздзÑленне Ñкрана" msgid "Fullscreen" msgstr "ПоўнаÑкранны" msgid "window|Mode" msgstr "РÑжым адлюÑтраваннÑ" msgid "Windowed" msgstr "Ðконны" msgid "V-Sync" msgstr "Ð’ÐµÑ€Ñ‚Ñ‹ÐºÐ°Ð»ÑŒÐ½Ð°Ñ ÑінхранізацыÑ" msgid "on" msgstr "укл." msgid "FPS" msgstr "" msgid "System Info" msgstr "СіÑÑ‚ÑÐ¼Ð½Ð°Ñ Ñ–Ð½Ñ„Ð°Ñ€Ð¼Ð°Ñ†Ñ‹Ñ" msgid "Change the resolution of the game." msgstr "ЗмÑніць раздзÑленне Ñкранa." msgid "Select Game Resolution" msgstr "РаздзÑленне Ñкрана" msgid "Toggle between fullscreen and windowed modes." msgstr "Пераключыцца паміж поўнаÑкранным Ñ– аконным Ñ€Ñжымам." msgid "" "The V-Sync option can be enabled to resolve flickering issues on some " "monitors." msgstr "" "Ð’ÐµÑ€Ñ‚Ñ‹ÐºÐ°Ð»ÑŒÐ½Ð°Ñ ÑÑ–Ð½Ñ…Ñ€Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ‹Ñ Ð¼Ð¾Ð¶Ð° вырашыць праблемы з мігаценнем на некаторых " "маніторах. " msgid "Show extra information such as FPS and current time." msgstr "Паказваць FPS Ñ– бÑгучы чаÑ." msgid "Hotkey: " msgstr "" msgid "Category: " msgstr "" msgid "Event: " msgstr "" msgid "Hot Keys:" msgstr "Ð“Ð°Ñ€Ð°Ñ‡Ñ‹Ñ ÐºÐ»Ð°Ð²Ñ–ÑˆÑ‹:" msgid "Evil" msgstr "" msgid "Good" msgstr "" msgid "Interface Type" msgstr "" msgid "Hide" msgstr "" msgid "Show" msgstr "" msgid "Interface" msgstr "" msgid "Slow" msgstr "" #, fuzzy msgid "Normal" msgstr "ЗвычайнаÑ" msgid "Fast" msgstr "" #, fuzzy msgid "Very Fast" msgstr "ШпаркаÑ" msgid "Scroll Speed" msgstr "" msgid "Toggle the type of interface you want to use." msgstr "" msgid "Toggle interface visibility." msgstr "" msgid "Toggle colored cursor on or off. This is only an esthetic choice." msgstr "Укл/выкл калÑровы курÑор. ГÑта ўплывае толькі на візуал." msgid "Sets the speed at which you scroll the window." msgstr "" msgid "Select Game Language:" msgstr "Мова" msgid "Click to choose the selected language." msgstr "ПÑтрыкні, каб ухваліць выбар мовы." msgid "%{name} has gained a level." msgstr "" msgid "%{skill} +1" msgstr "" msgid "You have learned %{skill}." msgstr "" msgid "" "%{name} has gained a level.\n" "\n" "%{skill} +1" msgstr "" msgid "" "You may learn either:\n" "%{skill1}\n" "or\n" "%{skill2}" msgstr "" msgid "" "Please inspect our fine wares. If you feel like offering a trade, click on " "the items you wish to trade with and for." msgstr "" msgid "" "You have received quite a bargain. I expect to make no profit on the deal. " "Can I interest you in any of my other wares?" msgstr "" msgid "I can offer you %{count} for 1 unit of %{resfrom}." msgstr "" msgid "I can offer you 1 unit of %{resto} for %{count} units of %{resfrom}." msgstr "" msgid "Min" msgstr "" msgid "Qty to trade" msgstr "" msgid "Trading Post" msgstr "" msgid "Your Resources" msgstr "" msgid "Available Trades" msgstr "" msgid "n/a" msgstr "" msgid "guarded by %{count} %{monster}" msgstr "" msgid "guarded by " msgstr "" msgid "(available: %{count})" msgstr "" msgid "(empty)" msgstr "" msgid "already learned" msgstr "" #, fuzzy msgid "treeOfKnowledge|free" msgstr "Веды" msgid "already claimed" msgstr "" msgid "not claimed" msgstr "" msgid "already knows this skill" msgstr "" msgid "already has max skills" msgstr "" msgid "(already visited)" msgstr "" msgid "(not visited)" msgstr "" msgid "%{color} Barrier" msgstr "" msgid "(tent visited)" msgstr "" msgid "%{color} Tent" msgstr "" msgid "Road" msgstr "" msgid "(digging ok)" msgstr "" msgid "(no digging)" msgstr "" msgid "penalty: %{cost}" msgstr "" msgid "Defenders:" msgstr "" msgid "Unknown" msgstr "" msgid "%{name} (Level %{level})" msgstr "" msgid "Attack:" msgstr "" msgid "Defense:" msgstr "" msgid "Spell Power:" msgstr "" msgid "Knowledge:" msgstr "" msgid "Spell Points:" msgstr "" msgid "Move Points:" msgstr "" msgid "Uncharted Territory" msgstr "" msgid "Cost per troop:" msgstr "Кошт за адзінку:" msgid "Available: %{count}" msgstr "ДаÑтупна: %{count}" msgid "Number to buy:" msgstr "КолькаÑць:" msgid "Recruit selected monsters." msgstr "" msgid "Select maximum monsters to be recruited." msgstr "" msgid "Select only 1 monster to be recruited." msgstr "" msgid "Select Game Resolution:" msgstr "ÐбрÑры раздзÑленне Ñкранa:" msgid "Click to apply the selected resolution." msgstr "ПÑтрыкні, каб ухваліць абранае раздзÑленне." #, fuzzy msgid "Click to apply the entered text." msgstr "ПÑтрыкні, каб ухваліць абранае раздзÑленне." msgid "Click to open the Virtual Keyboard dialog." msgstr "" msgid "Open Virtual Keyboard" msgstr "" msgid "How many troops to move?" msgstr "" msgid "Fast separation into slots:" msgstr "" msgid "Map: " msgstr "" msgid "" "\n" "\n" "Month: " msgstr "" msgid ", Week: " msgstr "" msgid ", Day: " msgstr "" msgid "" "\n" "\n" "Location: " msgstr "" msgid "Click to save the current game." msgstr "" msgid "Click to load a previously saved game." msgstr "" msgid "Are you sure you want to delete file:" msgstr "" msgid "Warning!" msgstr "" msgid "File to Save:" msgstr "" msgid "File to Load:" msgstr "" msgid "Accept the choice made." msgstr "Ухваліць выбар." #, fuzzy msgid "%{color} %{race} hero" msgstr "%{color} гулец" msgid "Terrain object" msgstr "" msgid "Select Skill:" msgstr "" msgid "Select Spell:" msgstr "" msgid "Select Artifact:" msgstr "" msgid "Select Monster:" msgstr "" msgid "Select Hero:" msgstr "" #, fuzzy msgid "Select Treasure:" msgstr "Мова" #, fuzzy msgid "Select Ocean Object:" msgstr "Мова" #, fuzzy msgid "Castle/town placing" msgstr "Ðаладкі замка" msgid "doubleLinedRace|Neutral" msgstr "" msgid "race|Random" msgstr "" "Выпад-\n" "ковы" #, fuzzy msgid "Click to start placing the selected castle/town." msgstr "ПÑтрыкні, каб ухваліць абранае раздзÑленне." #, fuzzy msgid "Click to select town placing." msgstr "ПÑтрыкніце Ñюды, каб выбраць ÑцÑнар" #, fuzzy msgid "Click to select castle placing." msgstr "ПÑтрыкніце Ñюды, каб выбраць ÑцÑнар" #, fuzzy msgid "%{color} %{race} %{townOrCastle}" msgstr "%{color} гулец" msgid "race|Neutral" msgstr "" msgid "You will place" msgstr "" #, fuzzy msgid "Click to select this class." msgstr "ПÑтрыкніце Ñюды, каб выбраць ÑцÑнар" #, fuzzy msgid "Click to select this color." msgstr "ПÑтрыкніце Ñюды, каб выбраць ÑцÑнар" #, fuzzy msgid "Select Dwelling:" msgstr "Хобіты" #, fuzzy msgid "Select Landscape Object:" msgstr "Мова" msgid "Mine placing" msgstr "" msgid "" "Resource\n" "type:" msgstr "" #, fuzzy msgid "%{mineName} appearance:" msgstr "%{race} %{name}" #, fuzzy msgid "Click to select %{object} as the resource generator to be placed." msgstr "ПÑтрыкніце Ñюды, каб выбраць ÑцÑнар" #, fuzzy msgid "Select Mountain Object:" msgstr "Мова" #, fuzzy msgid "Select Rock Object:" msgstr "Мова" #, fuzzy msgid "Select Tree Object:" msgstr "Мова" #, fuzzy msgid "Select Power Up Object:" msgstr "Мова" #, fuzzy msgid "Select Adventure Object:" msgstr "Мова" msgid "Map Type:\n" msgstr "" msgid "The Succession Wars" msgstr "" #, fuzzy msgid "The Price of Loyalty" msgstr "Кошт вернаÑці" #, fuzzy msgid "Resurrection" msgstr "РаздзÑленне Ñкрана" msgid "Lose all your heroes and towns." msgstr "Страціціь уÑÑ–Ñ… героÑÑž Ñ– гарады." msgid "Lose a specific town." msgstr "Страціць акрÑÑлены горад." msgid "Lose a specific hero." msgstr "Страціць акрÑÑленага героÑ." msgid "Run out of time. Fail to win by a certain point." msgstr "Вычарпаць чаÑ. Ðе перамагчы за акрÑÑлены чаÑ." msgid "Loss Condition" msgstr "Умова паразы" msgid "Defeat all enemy heroes and towns." msgstr "Знішчыць уÑÑ–Ñ… варожых героÑÑž Ñ– захапіць уÑе Ð²Ð°Ñ€Ð¾Ð¶Ñ‹Ñ Ð³Ð°Ñ€Ð°Ð´Ñ‹." msgid "Capture a specific town." msgstr "Захапіць акрÑÑлены горад." msgid "Defeat a specific hero." msgstr "Перамагчы акрÑÑленага героÑ." msgid "Find a specific artifact." msgstr "Ðдшукаць акрÑÑлены артÑфакт." msgid "Your side defeats the opposing side." msgstr "Ваш бок перамагае Ñупрацьлеглы." msgid "Accumulate a large amount of gold." msgstr "ÐазапаÑіць вÑлікую колькаÑць золата." msgid "Victory Condition" msgstr "Умова перамогі" msgid "Map difficulty:" msgstr "СкладанаÑць мапы:" msgid "N" msgstr "" msgid "No maps exist at that size" msgstr "ÐÑма мап абранага памеру" msgid "Small Maps" msgstr "ÐœÐ°Ð»Ñ‹Ñ Ð¼Ð°Ð¿Ñ‹" msgid "View only maps of size small (36 x 36)." msgstr "Паказваць толькі Ð¼Ð°Ð»Ñ‹Ñ Ð¼Ð°Ð¿Ñ‹ (36 Ñ… 36)." msgid "Medium Maps" msgstr "СÑÑ€ÑÐ´Ð½Ñ–Ñ Ð¼Ð°Ð¿Ñ‹" msgid "View only maps of size medium (72 x 72)." msgstr "Паказваць толькі ÑÑÑ€ÑÐ´Ð½Ñ–Ñ Ð¼Ð°Ð¿Ñ‹ (72 Ñ… 72)." msgid "Large Maps" msgstr "Ð’ÑÐ»Ñ–ÐºÑ–Ñ Ð¼Ð°Ð¿Ñ‹" msgid "View only maps of size large (108 x 108)." msgstr "Паказваць толькі вÑÐ»Ñ–ÐºÑ–Ñ Ð¼Ð°Ð¿Ñ‹ (108Ñ… 108)." msgid "Extra Large Maps" msgstr "ЗвышвÑÐ»Ñ–ÐºÑ–Ñ Ð¼Ð°Ð¿Ñ‹" msgid "View only maps of size extra large (144 x 144)." msgstr "Паказваць толькі звышвÑÐ»Ñ–ÐºÑ–Ñ Ð¼Ð°Ð¿Ñ‹ (144 Ñ… 144)." msgid "All Maps" msgstr "УÑе мапы" msgid "View all maps, regardless of size." msgstr "Паказваць уÑе мапы, незалежна ад памеру." msgid "Players Icon" msgstr "Іконка гульцоў" msgid "" "Indicates how many players total are in the scenario. Any positions not " "occupied by humans will be occupied by computer players." msgstr "" "Паказвае колькаÑць гульцоў у ÑцÑнары. За не занÑÑ‚Ñ‹Ñ Ð°Ñобамі пазіцыі будзе " "гулÑць кампутар." msgid "" "Indicates whether the map\n" "is small (36 x 36), medium\n" "(72 x 72), large (108 x 108),\n" "or extra large (144 x 144)." msgstr "" "Паказвае памер мапы:\n" "малы (36 Ñ… 36), ÑÑÑ€Ñдні\n" "(72 x 72), вÑлікі (108 x 108)ці звышвÑлікі (144 Ñ… 144)." msgid "Size Icon" msgstr "Іконка памеру" msgid "" "Indicates whether the map is made for \"The Succession Wars\" or \"The Price " "of Loyalty\" version of the game." msgstr "" "Паказвае, Ð´Ð»Ñ Ñкой верÑÑ–Ñ– гульні зроблена мапа: Ð´Ð»Ñ \"The Succession Wars\" " "альбо \"The Price of Loyalty\"" msgid "Map Type" msgstr "Тып мапы" msgid "Selected Name" msgstr "Ðазва мапы" msgid "The name of the currently selected map." msgstr "Ðазва абранай мапы." msgid "Selected Map Difficulty" msgstr "СкладанаÑць мапы" #, fuzzy msgid "" "The map difficulty of the currently selected map. The map difficulty is " "determined by the scenario designer. More difficult maps might include more " "or stronger enemies, fewer resources, or other special conditions making " "things tougher for the human player." msgstr "" "СкладанаÑць мапы вызначаецца ейным аўтарам. Болей ÑÐºÐ»Ð°Ð´Ð°Ð½Ñ‹Ñ Ð¼Ð°Ð¿Ñ‹ могуць " "утрымліваць больш ворагаў, менш Ñ€ÑÑурÑаў ці Ñ–Ð½ÑˆÑ‹Ñ Ð°Ð´Ð¼Ñ‹ÑÐ»Ð¾Ð²Ñ‹Ñ Ð°ÐºÐ°Ð»Ñ–Ñ‡Ð½Ð°Ñці." msgid "Selected Description" msgstr "ÐпіÑанне" msgid "The description of the currently selected map." msgstr "ÐпіÑанне абранай мапы." msgid "Jump" msgstr "" msgid "Hero Speed" msgstr "" msgid "Don't Show" msgstr "" msgid "Enemy Speed" msgstr "" msgid "Auto Resolve" msgstr "" msgid "Auto, No Spells" msgstr "" msgid "Battles" msgstr "" msgid "autoBattle|Manual" msgstr "" msgid "Change the speed at which your heroes move on the main screen." msgstr "" msgid "" "Sets the speed that A.I. heroes move at. You can also elect not to view A.I. " "movement at all." msgstr "" #, fuzzy msgid "Change the interface settings of the game." msgstr "Ðаладзіць графіку." #, fuzzy msgid "Interface Settings" msgstr "Ðалады гульні" msgid "Toggle instant battle mode." msgstr "" msgid "Att." msgstr "" msgid "Def." msgstr "" msgid "Power" msgstr "" msgid "Knowl" msgstr "" msgid "1st" msgstr "" msgid "2nd" msgstr "" msgid "3rd" msgstr "" msgid "4th" msgstr "" msgid "5th" msgstr "" msgid "6th" msgstr "" msgid "Oracle: Player Rankings" msgstr "" msgid "Thieves' Guild: Player Rankings" msgstr "" msgid "Number of Towns:" msgstr "" msgid "Number of Castles:" msgstr "" msgid "Number of Heroes:" msgstr "" msgid "Gold in Treasury:" msgstr "" msgid "Wood & Ore:" msgstr "" msgid "Gems, Cr, Slf & Mer:" msgstr "" msgid "Obelisks Found:" msgstr "" msgid "Artifacts:" msgstr "" msgid "Total Army Strength:" msgstr "" msgid "Income:" msgstr "" msgid "Best Hero:" msgstr "" msgid "Best Hero Stats:" msgstr "" msgid "Personality:" msgstr "" msgid "Best Monster:" msgstr "" msgid "Map filename" msgstr "" msgid "" "Are you sure you want to load a new map? (Any unsaved changes to the current " "map will be lost.)" msgstr "" msgid "" "Are you sure you want to create a new map? (Any unsaved changes to the " "current map will be lost.)" msgstr "" msgid "" "Create a new map, either from scratch or using the random map generator." msgstr "" #, fuzzy msgid "New Map" msgstr "ÐÐ¾Ð²Ð°Ñ Ð³ÑƒÐ»ÑŒÐ½Ñ" #, fuzzy msgid "Load Map" msgstr "Загрузіць гульню" msgid "Load an existing map." msgstr "" #, fuzzy msgid "Save Map" msgstr "Ð’ÑÐ»Ñ–ÐºÑ–Ñ Ð¼Ð°Ð¿Ñ‹" #, fuzzy msgid "Save the current map." msgstr "Ðазва абранай мапы." msgid "Quit out of the map editor." msgstr "" msgid "Objects cannot be placed outside the map." msgstr "" msgid "Monsters cannot be placed on water." msgstr "" msgid "Choose a tile which does not contain any objects." msgstr "" msgid "Treasures cannot be placed on water." msgstr "" msgid "Heroes cannot be placed on water." msgstr "" msgid "" "A maximum of %{count} heroes of the same color can be placed on the map." msgstr "" msgid "Artifacts cannot be placed on water." msgstr "" msgid "Mountains cannot be placed on water." msgstr "" msgid "Rocks cannot be placed on water." msgstr "" msgid "Trees cannot be placed on water." msgstr "" msgid "Ocean object must be placed on water." msgstr "" msgid "Landscape objects cannot be placed on water." msgstr "" msgid "Towns cannot be placed on water." msgstr "" msgid "Mines cannot be placed on water." msgstr "" msgid "Dwellings cannot be placed on water." msgstr "" msgid "Power-ups cannot be placed on water." msgstr "" msgid "Adventure objects cannot be placed on water." msgstr "" msgid "Used to place %{object}." msgstr "" #, fuzzy msgid "Select object type" msgstr "Мова" #, fuzzy msgid "" "Click here to\n" "select a monster." msgstr "ПÑтрыкніце Ñюды, каб выбраць ÑцÑнар" #, fuzzy msgid "" "Click here to\n" "select another monster." msgstr "Калі лаÑка, выбÑрыце іншага героÑ." msgid "" "Cell\n" "Details" msgstr "" msgid "Roads" msgstr "" msgid "Streams" msgstr "" msgid "Erase" msgstr "" msgid "Mountains" msgstr "" msgid "Rocks" msgstr "" msgid "Trees" msgstr "" #, fuzzy msgid "Water Objects" msgstr "ВадзÑÐ½Ñ‹Ñ Ñлементалі" msgid "Miscellaneous" msgstr "" msgid "Artifacts" msgstr "" #, fuzzy msgid "Dwellings" msgstr "Хобіты" msgid "Mines" msgstr "" msgid "Power-ups" msgstr "" msgid "Treasures" msgstr "" #, fuzzy msgid "Towns" msgstr "Горад" msgid "Heroes" msgstr "" msgid "editorErasure|Terrain" msgstr "" msgid "editorErasure|Castles" msgstr "" msgid "editorErasure|Monsters" msgstr "" msgid "editorErasure|Heroes" msgstr "" msgid "editorErasure|Artifacts" msgstr "" msgid "editorErasure|Roads" msgstr "" msgid "editorErasure|Streams" msgstr "" msgid "editorErasure|Treasures" msgstr "" msgid "" "Draws terrain in\n" "%{size} by %{size} square increments." msgstr "" msgid "" "Erases objects in\n" "%{size} by %{size} square increments." msgstr "" #, fuzzy msgid "Small Brush" msgstr "ÐœÐ°Ð»Ñ‹Ñ Ð¼Ð°Ð¿Ñ‹" #, fuzzy msgid "Medium Brush" msgstr "СÑÑ€ÑÐ´Ð½Ñ–Ñ Ð¼Ð°Ð¿Ñ‹" #, fuzzy msgid "Large Brush" msgstr "Ð’ÑÐ»Ñ–ÐºÑ–Ñ Ð¼Ð°Ð¿Ñ‹" msgid "Area Fill" msgstr "" msgid "Used to click and drag for filling in large areas." msgstr "" msgid "Clear Area" msgstr "" msgid "Used to click and drag for clearing large areas." msgstr "" msgid "" "Costs %{rate} times normal movement for all heroes. (Pathfinding reduces or " "eliminates the penalty.)" msgstr "" msgid "Traversable only by boat." msgstr "" msgid "No special modifiers." msgstr "" msgid "Toggle the erasure of %{type} objects." msgstr "" msgid "" "Objects of this type will be deleted with the Erase tool. Left-click here to " "deselect this type. Press and hold this button to deselect all other object " "types." msgstr "" msgid "" "Objects of this type will NOT be deleted with the Erase tool. Left-click " "here to select this type. Press and hold this button to select all other " "object types." msgstr "" msgid "Terrain Mode" msgstr "" msgid "Used to draw the underlying grass, dirt, water, etc. on the map." msgstr "" msgid "Landscape Objects Mode" msgstr "" msgid "" "Used to place landscape objects (mountains, rocks, trees, etc.) on the map." msgstr "" msgid "Detail Mode" msgstr "" #, fuzzy msgid "Used for special editing of monsters, heroes and towns." msgstr "Знішчыць уÑÑ–Ñ… варожых героÑÑž Ñ– захапіць уÑе Ð²Ð°Ñ€Ð¾Ð¶Ñ‹Ñ Ð³Ð°Ñ€Ð°Ð´Ñ‹." msgid "Adventure Objects Mode" msgstr "" msgid "" "Used to place adventure objects (artifacts, dwellings, mines, treasures, " "etc.) on the map." msgstr "" #, fuzzy msgid "Kingdom Objects Mode" msgstr "Прыбытак каралеўÑтва" msgid "Used to place kingdom objects (towns, castles and heroes) on the map." msgstr "" #, fuzzy msgid "Monsters Mode" msgstr "МайÑтры мечнікі" msgid "Used to place monsters on the map." msgstr "" msgid "Allows you to draw streams by clicking and dragging." msgstr "" msgid "Stream Mode" msgstr "" msgid "Allows you to draw roads by clicking and dragging." msgstr "" msgid "Road Mode" msgstr "" msgid "Erase Mode" msgstr "" msgid "Used to erase objects from the map." msgstr "" msgid "Change between zoom and normal view." msgstr "" msgid "Magnify" msgstr "" msgid "Undo" msgstr "" msgid "Undo your last action." msgstr "" msgid "Create a new map either from scratch or using the random map generator." msgstr "" msgid "Edit map title, description, and other general information." msgstr "" msgid "Specifications" msgstr "" msgid "File Options" msgstr "" msgid "" "Open the file options menu, where you can save or load maps, or quit out of " "the editor." msgstr "" msgid "View the editor system options, which let you customize the editor." msgstr "" msgid "Create a map that is %{size} squares wide and %{size} squares high." msgstr "" #, fuzzy msgid "Cancel back to the New Map menu." msgstr "Ð’Ñрнуцца Ñž галоўнае меню." msgid "Cancel back to the main menu." msgstr "Ð’Ñрнуцца Ñž галоўнае меню." msgid "From Scratch" msgstr "" msgid "Start from scratch with a blank map." msgstr "" msgid "Create a randomly generated map." msgstr "" #, fuzzy msgid "Random" msgstr "" "Выпад-\n" "ковы" #, fuzzy msgid "Cancel back to the Map Editor main menu." msgstr "Ð’Ñрнуцца Ñž галоўнае меню." msgid "No maps available!" msgstr "" msgid "Warning" msgstr "Увага" msgid "difficulty|Easy" msgstr "Лёгка" msgid "difficulty|Normal" msgstr "Звычайна" msgid "difficulty|Hard" msgstr "ЦÑжка" msgid "difficulty|Expert" msgstr "ЭкÑперт" msgid "difficulty|Impossible" msgstr "Ðемагчыма" msgid "and more..." msgstr "" msgid "Easy" msgstr "" msgid "Hard" msgstr "ЦÑжкаÑ" msgid "Campaign Difficulty" msgstr "" msgid "" "Choose this difficulty if you want to prefer game story over challenge. AI " "is weaker in comparison with normal difficulty." msgstr "" msgid "" "Choose this difficulty to enjoy the campaign as per the original design." msgstr "" msgid "" "Choose this difficulty if you want challenge. AI is stronger in comparison " "with normal difficulty." msgstr "" msgid "" "Congratulations!\n" "\n" "Days: %{days}\n" msgstr "" msgid "" "\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" msgid "" "Score: %{score}\n" "\n" "Rating:\n" "%{rating}" msgstr "" msgid "Start the selected scenario." msgstr "" msgid "View Intro" msgstr "ПаглÑдзець уÑтуп" msgid "View Intro videos for the current state of the campaign." msgstr "" msgid "" "Select the campaign difficulty. This can be lowered at any point during the " "campaign." msgstr "" msgid "Restart the current scenario." msgstr "" #, fuzzy msgid "Difficulty" msgstr "СкладанаÑць" msgid "" "You have changed to a lower difficulty for the campaign. You will not be " "able to revert this after this point. The high score will be calculated " "based solely on the new difficulty. Do you want to proceed?" msgstr "" msgid "Are you sure you want to restart this scenario?" msgstr "" msgid "Campaign Scenario loading failure" msgstr "" msgid "Please make sure that campaign files are correct and present." msgstr "" msgid "Days spent" msgstr "" msgid "The number of days spent on this campaign." msgstr "" msgid "Project Coordination and Core Development" msgstr "" msgid "Development" msgstr "" msgid "Visit us at " msgstr "" msgid "QA and Support" msgstr "" msgid "Dev and Support" msgstr "" msgid "Special Thanks to" msgstr "" msgid "and many other contributors!" msgstr "" msgid "and many-many other supporters!" msgstr "" msgid "Support us at" msgstr "" msgid "local-donation-platform|https://www.patreon.com/fheroes2" msgstr "" msgid "Connect with us at" msgstr "" msgid "local-social-network|https://www.facebook.com/groups/fheroes2" msgstr "" msgid "Need help with the game?" msgstr "" msgid "Original project before 0.7" msgstr "" msgid "Heroes of Might and Magic II: The Succession Wars team" msgstr "" msgid "Designed and Directed" msgstr "" msgid "Programming and Design" msgstr "" msgid "Executive Producer" msgstr "" msgid "Producer" msgstr "" msgid "Additional Design" msgstr "" msgid "Additional Programming" msgstr "" msgid "Musical Production" msgstr "" msgid "Music and Sound Design" msgstr "" msgid "Vocalists" msgstr "" msgid "Art Director" msgstr "" msgid "Assistant Art Director" msgstr "" msgid "Artists" msgstr "" msgid "QA Manager" msgstr "" msgid "QA" msgstr "" msgid "Writing" msgstr "" msgid "Manual and Helpfile" msgstr "" msgid "Scenarios" msgstr "" msgid "Heroes of Might and Magic II: The Price of Loyalty team" msgstr "" msgid "Design Lead" msgstr "" msgid "Designers" msgstr "" msgid "Programming Lead" msgstr "" msgid "Art Lead" msgstr "" msgid "Playtesters" msgstr "" msgid "Designer" msgstr "" msgid "Producers" msgstr "" #, fuzzy msgid "QA Managers" msgstr "Стралкі" msgid "Sound Design" msgstr "" msgid "Town Themes" msgstr "" msgid "Alto Sax" msgstr "" msgid "Harpsichord and Piano" msgstr "" msgid "Basso Vocal" msgstr "" msgid "Soprano Vocal" msgstr "" msgid "Recorded at %{recordingStudio}" msgstr "" msgid "credits|Manual" msgstr "" msgid "German Consultant" msgstr "" msgid "Map Designers" msgstr "" msgid "Package Design" msgstr "" msgid "Your Name" msgstr "Вашае імÑ" msgid "Unknown Hero" msgstr "" msgid "Standard" msgstr "" msgid "View High Scores for Standard Maps." msgstr "" msgid "Campaign" msgstr "КампаніÑ" msgid "View High Scores for Campaigns." msgstr "" msgid "hotkey|default okay event" msgstr "" msgid "hotkey|default cancel event" msgstr "" msgid "hotkey|default left" msgstr "" msgid "hotkey|default right" msgstr "" msgid "hotkey|default up" msgstr "" msgid "hotkey|default down" msgstr "" msgid "hotkey|toggle fullscreen" msgstr "" msgid "hotkey|toggle text support mode" msgstr "" msgid "hotkey|new game" msgstr "" msgid "hotkey|load game" msgstr "" msgid "hotkey|highscores" msgstr "" msgid "hotkey|credits" msgstr "" #, fuzzy msgid "hotkey|standard game" msgstr "Ð—Ð²Ñ‹Ñ‡Ð°Ð¹Ð½Ð°Ñ Ð³ÑƒÐ»ÑŒÐ½Ñ" #, fuzzy msgid "hotkey|campaign game" msgstr "КампаніÑ" #, fuzzy msgid "hotkey|multi-player game" msgstr "ШматаÑÐ°Ð±Ð¾Ð²Ð°Ñ Ð³ÑƒÐ»ÑŒÐ½Ñ" #, fuzzy msgid "hotkey|settings" msgstr "Ðалады" msgid "hotkey|quit" msgstr "" msgid "hotkey|select map" msgstr "" msgid "hotkey|select small map size" msgstr "" msgid "hotkey|select medium map size" msgstr "" msgid "hotkey|select large map size" msgstr "" msgid "hotkey|select extra large map size" msgstr "" msgid "hotkey|select all map sizes" msgstr "" msgid "hotkey|hotseat game" msgstr "" msgid "hotkey|battle only game" msgstr "" msgid "hotkey|choose the original campaign" msgstr "" msgid "hotkey|choose the expansion campaign" msgstr "" #, fuzzy msgid "hotkey|map editor main menu" msgstr "КампаніÑ" #, fuzzy msgid "hotkey|new map menu" msgstr "Ð—Ð²Ñ‹Ñ‡Ð°Ð¹Ð½Ð°Ñ Ð³ÑƒÐ»ÑŒÐ½Ñ" #, fuzzy msgid "hotkey|load map menu" msgstr "Ð—Ð²Ñ‹Ñ‡Ð°Ð¹Ð½Ð°Ñ Ð³ÑƒÐ»ÑŒÐ½Ñ" msgid "hotkey|new map from scratch" msgstr "" #, fuzzy msgid "hotkey|new random map" msgstr "Зводка аб каралеўÑтве" #, fuzzy msgid "hotkey|undo last action" msgstr "Ðаладкі замка" #, fuzzy msgid "hotkey|redo last action" msgstr "Ðаладкі замка" msgid "hotkey|roland campaign" msgstr "" msgid "hotkey|archibald campaign" msgstr "" #, fuzzy msgid "hotkey|the price of loyalty campaign" msgstr "Кошт вернаÑці" msgid "hotkey|voyage home campaign" msgstr "" msgid "hotkey|wizard's isle campaign" msgstr "" msgid "hotkey|descendants campaign" msgstr "" msgid "hotkey|select first campaign bonus" msgstr "" msgid "hotkey|select second campaign bonus" msgstr "" msgid "hotkey|select third campaign bonus" msgstr "" msgid "hotkey|view campaign intro" msgstr "" #, fuzzy msgid "hotkey|select campaign difficulty" msgstr "СкладанаÑць мапы" msgid "hotkey|restart campaign scenario" msgstr "" msgid "hotkey|world map left" msgstr "" msgid "hotkey|world map right" msgstr "" msgid "hotkey|world map up" msgstr "" msgid "hotkey|world map down" msgstr "" msgid "hotkey|world map up left" msgstr "" msgid "hotkey|world map up right" msgstr "" msgid "hotkey|world map down left" msgstr "" msgid "hotkey|world map down right" msgstr "" msgid "hotkey|save game" msgstr "" msgid "hotkey|next hero" msgstr "" #, fuzzy msgid "hotkey|start hero movement" msgstr "Ð—Ð²Ñ‹Ñ‡Ð°Ð¹Ð½Ð°Ñ Ð³ÑƒÐ»ÑŒÐ½Ñ" msgid "hotkey|cast adventure spell" msgstr "" msgid "hotkey|put hero to sleep" msgstr "" msgid "hotkey|next town" msgstr "" msgid "hotkey|end turn" msgstr "" #, fuzzy msgid "hotkey|file options" msgstr "Ðаладкі замка" msgid "hotkey|adventure options" msgstr "" msgid "hotkey|puzzle map" msgstr "" msgid "hotkey|scenario information" msgstr "" msgid "hotkey|dig for artifact" msgstr "" msgid "hotkey|view world" msgstr "" #, fuzzy msgid "hotkey|kingdom summary" msgstr "Зводка аб каралеўÑтве" msgid "hotkey|default action" msgstr "" msgid "hotkey|open focus" msgstr "" msgid "hotkey|system options" msgstr "" msgid "hotkey|scroll left" msgstr "" msgid "hotkey|scroll right" msgstr "" msgid "hotkey|scroll up" msgstr "" msgid "hotkey|scroll down" msgstr "" msgid "hotkey|toggle control panel" msgstr "" msgid "hotkey|toggle radar" msgstr "" msgid "hotkey|toggle buttons" msgstr "" msgid "hotkey|toggle status" msgstr "" msgid "hotkey|toggle icons" msgstr "" msgid "hotkey|transfer control to ai" msgstr "" msgid "hotkey|retreat from battle" msgstr "" msgid "hotkey|surrender during battle" msgstr "" msgid "hotkey|toggle battle auto mode" msgstr "" msgid "hotkey|finish the battle in auto mode" msgstr "" #, fuzzy msgid "hotkey|battle options" msgstr "Ðаладкі замка" msgid "hotkey|skip turn in battle" msgstr "" msgid "hotkey|cast battle spell" msgstr "" msgid "hotkey|dwelling level 1" msgstr "" msgid "hotkey|dwelling level 2" msgstr "" msgid "hotkey|dwelling level 3" msgstr "" msgid "hotkey|dwelling level 4" msgstr "" msgid "hotkey|dwelling level 5" msgstr "" msgid "hotkey|dwelling level 6" msgstr "" msgid "hotkey|well" msgstr "" #, fuzzy msgid "hotkey|marketplace" msgstr "Рынак" msgid "hotkey|mage guild" msgstr "" #, fuzzy msgid "hotkey|shipyard" msgstr "Верф" #, fuzzy msgid "hotkey|thieves guild" msgstr "Ð“Ñ–Ð»ÑŒÐ´Ñ‹Ñ Ð²Ð°Ñ€Ð¾Ñž" msgid "hotkey|tavern" msgstr "" msgid "hotkey|construction screen" msgstr "" msgid "hotkey|buy all monsters in well" msgstr "" msgid "hotkey|split stack by half" msgstr "" msgid "hotkey|split stack by one" msgstr "" msgid "hotkey|join stacks" msgstr "" msgid "hotkey|upgrade troop" msgstr "" msgid "hotkey|dismiss hero or troop" msgstr "" msgid "" "Do you want to regain control from AI? The effect will take place only on " "the next turn." msgstr "" msgid "" "Do you want to transfer control from you to the AI? The effect will take " "place only on the next turn." msgstr "" msgid "Default Actions" msgstr "" msgid "Global Actions" msgstr "" msgid "Main Menu" msgstr "" msgid "World Map" msgstr "Мапа Ñвету" #, fuzzy msgid "Battle Screen" msgstr "Экран героÑ" #, fuzzy msgid "Town Screen" msgstr "Экран героÑ" msgid "Army Actions" msgstr "" msgid "Editor" msgstr "" msgid "The save file is corrupted." msgstr "" msgid "Unsupported save format: " msgstr "" msgid "Current game version: " msgstr "" msgid "Last supported version: " msgstr "" msgid "This file contains a save with an invalid game type." msgstr "" msgid "" "This save file requires \"The Price of Loyalty\" game assets, but they have " "not been provided to the engine." msgstr "" msgid "This saved game is localized to '" msgstr "" msgid "' language, but the current language of the game is '" msgstr "" msgid "Hot Seat" msgstr "За адным кампутарам" msgid "" "Play a Hot Seat game, where 2 to 4 players play around the same computer, " "switching into the 'Hot Seat' when it is their turn." msgstr "" "ГулÑй за адным кампутарам з адной-пÑццю іншымі аÑобамі, перадаючы кіраванне " "па чарзе." msgid "A single player game playing out a single map." msgstr "Ð“ÑƒÐ»ÑŒÐ½Ñ Ð´Ð»Ñ Ð°Ð´Ð½Ð¾Ð¹ аÑобы на адной мапе." msgid "Standard Game" msgstr "Ð—Ð²Ñ‹Ñ‡Ð°Ð¹Ð½Ð°Ñ Ð³ÑƒÐ»ÑŒÐ½Ñ" msgid "A single player game playing through a series of maps." msgstr "Ð“ÑƒÐ»ÑŒÐ½Ñ Ð´Ð»Ñ Ð°Ð´Ð½Ð¾Ð¹ аÑобы на ланцугу мапаў." msgid "Campaign Game" msgstr "КампаніÑ" msgid "Multi-Player Game" msgstr "ШматаÑÐ°Ð±Ð¾Ð²Ð°Ñ Ð³ÑƒÐ»ÑŒÐ½Ñ" msgid "" "A multi-player game, with several human players completing against each " "other on a single map." msgstr "СупрацьÑтаÑнне некалькіх аÑобаў на адной мапе." msgid "fheroes2 Resurrection Team presents" msgstr "fheroes2 Resurrection Team прадÑтаўлÑе" msgid "Greetings!" msgstr "" #, fuzzy msgid "Welcome to Heroes of Might and Magic II powered by fheroes2 engine!" msgstr "ВыйÑці з Heroes of Might and Magic II." #, fuzzy msgid "" "Welcome to Heroes of Might and Magic II powered by the fheroes2 engine!\n" "Before starting the game please select a game resolution." msgstr "ВыйÑці з Heroes of Might and Magic II." msgid "Please Remember" msgstr "" #, fuzzy msgid "" "You can always change the language, resolution and settings of the game by " "clicking on the " msgstr "ЗмÑніць мову, раздзÑленне Ñкрана Ñ– налады гульні." msgid "door" msgstr "" msgid " on the left side of the Main Menu, or with the " msgstr "" msgid " button from the " msgstr "" #, fuzzy msgid "NEW GAME" msgstr "ÐОВÐЯ" msgid "" " menu. \n" "\n" "Enjoy the game!" msgstr "" msgid "The Editor requires \"The Price of Loyalty\" expansion files to work." msgstr "" msgid "Quit Heroes of Might and Magic II and return to the operating system." msgstr "ВыйÑці з Heroes of Might and Magic II." msgid "Credits" msgstr "ЗвеÑткі пра каманду" msgid "View the credits screen." msgstr "ПраглÑдзець звеÑткі пра раÑпрацоўшчыкаў." msgid "High Scores" msgstr "ÐÐ°Ð¹Ð»ÐµÐ¿ÑˆÑ‹Ñ Ð´Ð°ÑÑгненні" msgid "View the high scores screen." msgstr "ПраглÑдзець Ð½Ð°Ð¹Ð»ÐµÐ¿ÑˆÑ‹Ñ Ð´Ð°ÑÑгненні." msgid "Change language, resolution and settings of the game." msgstr "ЗмÑніць мову, раздзÑленне Ñкрана Ñ– налады гульні." msgid "Game Settings" msgstr "Ðалады гульні" msgid "" "Required video files for campaign selection window are missing. Please make " "sure that all necessary files are present in the system." msgstr "" "Бракуе відÑÑ Ñ„Ð°Ð¹Ð»Ð°Ñž Ð´Ð»Ñ Ð°Ð±Ñ€Ð°Ð½Ð°Ð¹ кампаніі. Праверце, ці ÑžÑе Ð½ÐµÐ°Ð±Ñ…Ð¾Ð´Ð½Ñ‹Ñ Ñ„Ð°Ð¹Ð»Ñ‹ " "былі ÑкапіÑваны." msgid "" "Either Roland's or Archibald's campaign from the original Heroes of Might " "and Magic II." msgstr "" msgid "Original Campaign" msgstr "ÐÑноўны ÑцÑнар" msgid "Expansion Campaign" msgstr "Ð”Ð°Ð´Ð°Ñ‚ÐºÐ¾Ð²Ñ‹Ñ ÑцÑнары" msgid "One of the four new campaigns from the Price of Loyalty expansion set." msgstr "" msgid "Loading video. Please wait..." msgstr "" msgid "Host" msgstr "" msgid "" "The host sets up the game options. There can only be one host per network " "game." msgstr "" msgid "Guest" msgstr "" msgid "" "The guest waits for the host to set up the game, then is automatically added " "in. There can be multiple guests for TCP/IP games." msgstr "" msgid "Battle Only" msgstr "ÐÑобны бой" msgid "Setup and play a battle without loading any map." msgstr "Ðаладзь Ñ– згулÑй бітву, не загружаючы мапу." msgid "2 Players" msgstr "2 гульцы" msgid "" "Play with 2 human players, and optionally, up to 4 additional computer " "players." msgstr "Ð“ÑƒÐ»ÑŒÐ½Ñ Ð´Ð»Ñ 2-x аÑобаў Ñ–, па жаданні, да 4-Ñ… кампутарных гульцоў." msgid "3 Players" msgstr "3 Гульцы" msgid "" "Play with 3 human players, and optionally, up to 3 additional computer " "players." msgstr "Ð“ÑƒÐ»ÑŒÐ½Ñ Ð´Ð»Ñ 3-Ñ… аÑобаў Ñ–, па жаданні, да 3-x кампутарных гульцоў." msgid "4 Players" msgstr "4 Гульцы" msgid "" "Play with 4 human players, and optionally, up to 2 additional computer " "players." msgstr "Ð“ÑƒÐ»ÑŒÐ½Ñ Ð´Ð»Ñ 4-Ñ… аÑобаў Ñ–, па жаданні, да 2-x кампутарных гульцоў." msgid "5 Players" msgstr "5 Гульцоў" msgid "" "Play with 5 human players, and optionally, up to 1 additional computer " "player." msgstr "Ð“ÑƒÐ»ÑŒÐ½Ñ Ð´Ð»Ñ 5-ці аÑобаў Ñ–, па жаданні, аднаго камутарнага гульца." msgid "6 Players" msgstr "6 Гульцоў" msgid "Play with 6 human players." msgstr "Ð“ÑƒÐ»ÑŒÐ½Ñ Ð´Ð»Ñ 6-ці аÑобаў." msgid "Dragon city has fallen! You are now the Master of the Dragons." msgstr "" msgid "" "You captured %{name}!\n" "You are victorious." msgstr "" msgid "" "You have captured the enemy hero %{name}!\n" "Your quest is complete." msgstr "" msgid "" "You have found the %{name}.\n" "Your quest is complete." msgstr "" msgid "Ultimate Artifact" msgstr "" msgid "" "The enemy is beaten.\n" "Your side has triumphed!" msgstr "" msgid "" "You have built up over %{count} gold in your treasury.\n" "All enemies bow before your wealth and power." msgstr "" msgid "Victory!" msgstr "" msgid "" "The enemy has captured %{name}!\n" "They are triumphant." msgstr "" msgid "" "The enemy has built up over %{count} gold in his treasury.\n" "You must bow done in defeat before his wealth and power." msgstr "" msgid "You have been eliminated from the game!!!" msgstr "" msgid "" "You have lost the hero %{name}.\n" "Your quest is over." msgstr "" msgid "" "You have failed to complete your quest in time.\n" "All is lost." msgstr "" msgid "Defeat!" msgstr "" msgid "" "Base score: %{score}\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" msgid "Defeat all enemy heroes and capture all enemy towns and castles." msgstr "" msgid "Run out of time. (Fail to win by a certain point.)" msgstr "" msgid "You must defeat the enemy %{enemies}." msgid_plural "You must defeat the enemy alliance of %{enemies}." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "" "The alliance consisting of %{allies} and you must defeat the enemy " "%{enemies}." msgid_plural "" "The alliance consisting of %{allies} and you must defeat the enemy alliance " "of %{enemies}." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "Capture the castle '%{name}'." msgstr "" msgid "Capture the town '%{name}'." msgstr "" msgid "Defeat the hero '%{name}'." msgstr "" msgid "Find the ultimate artifact." msgstr "" msgid "Find the '%{name}' artifact." msgstr "" msgid "Accumulate %{count} gold." msgstr "" msgid "" ", or you may win by defeating all enemy heroes and capturing all enemy towns " "and castles." msgstr "" msgid "Lose the castle '%{name}'." msgstr "" msgid "Lose the town '%{name}'." msgstr "" msgid "Lose the hero: %{name}." msgstr "" msgid "Fail to win by the end of month %{month}, week %{week}, day %{day}." msgstr "" msgid "%{color} player has been vanquished!" msgstr "" msgid "Major Event!" msgstr "" msgid "Scenario:" msgstr "СцÑнар:" msgid "Game Difficulty:" msgstr "СкладанаÑць:" msgid "Opponents:" msgstr "Супраціўнікі:" msgid "Class:" msgstr "КлаÑ:" msgid "Rating %{rating}%" msgstr "РÑйтынг %{rating}%" msgid "Click here to select which scenario to play." msgstr "ПÑтрыкніце Ñюды, каб выбраць ÑцÑнар" msgid "Scenario" msgstr "СцÑнар" msgid "Game Difficulty" msgstr "СкладанаÑць" msgid "" "This lets you change the starting difficulty at which you will play. Higher " "difficulty levels start you of with fewer resources, and at the higher " "settings, give extra resources to the computer." msgstr "" "ДазвалÑе змÑніць пачаткувую ÑкладанаÑць гульні. З падвышÑннем узроўню " "ÑкладанаÑці вы пачнеце гульню з меншай колькаÑцю Ñ€ÑÑурÑаў, а ÐºÐ°Ð¼Ð¿ÑƒÑ‚Ð°Ñ€Ð½Ñ‹Ñ " "гульцы атрымаюць Ð´Ð°Ð´Ð°Ñ‚ÐºÐ¾Ð²Ñ‹Ñ Ñ€ÑÑурÑÑ‹." msgid "Difficulty Rating" msgstr "РÑйтынг ÑкладанаÑці" msgid "" "The difficulty rating reflects a combination of various settings for your " "game. This number will be applied to your final score." msgstr "РÑйтынг ÑкладанаÑці улічваецца Ñž фінальнай адзнаке." msgid "Click to accept these settings and start a new game." msgstr "ПÑтрыкні, каб ухваліць налады Ñ– пачаць новую гульню." msgid "Click to return to the main menu." msgstr "ПÑтрыкні, каб вÑрнуцца Ñž галоўнае меню." msgid "Astrologers proclaim the Month of the %{name}." msgstr "" msgid "Astrologers proclaim the Week of the %{name}." msgstr "" msgid "After regular growth, the population of %{monster} is doubled!" msgstr "" msgid "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgid_plural "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "%{monster} growth +%{count}." msgstr "" msgid " All populations are halved." msgstr "" msgid " All dwellings increase population." msgstr "" msgid "New Month!" msgstr "" msgid "New Week!" msgstr "" msgid "Beware!" msgstr "" msgid "" "%{color} player, this is your last day to capture a town, or you will be " "banished from this land." msgstr "" msgid "" "%{color} player, you only have %{day} days left to capture a town, or you " "will be banished from this land." msgstr "" msgid "%{color} player's turn." msgstr "" msgid "" "%{color} player, you have lost your last town. If you do not conquer another " "town in the next week, you will be eliminated." msgstr "" msgid "" "%{color} player, your heroes abandon you, and you are banished from this " "land." msgstr "" msgid "Lord Kilburn" msgstr "" msgid "Tsabu" msgstr "" msgid "Sir Galant" msgstr "" msgid "Thundax" msgstr "" msgid "Lord Haart" msgstr "" msgid "Ariel" msgstr "" msgid "Rebecca" msgstr "" msgid "Sandro" msgstr "" msgid "Crodo" msgstr "" msgid "Barock" msgstr "" msgid "Antoine" msgstr "" msgid "Astra" msgstr "" msgid "Agar" msgstr "" msgid "Vatawna" msgstr "" msgid "Vesper" msgstr "" msgid "Ambrose" msgstr "" msgid "Troyan" msgstr "" msgid "Jojosh" msgstr "" msgid "Wrathmont" msgstr "" msgid "Maximus" msgstr "" msgid "Next Hero" msgstr "" msgid "Select the next Hero." msgstr "" #, fuzzy msgid "Hero Movement" msgstr "Экран героÑ" msgid "" "Start the Hero's movement along the current path or re-visit the object " "occupied by the Hero. Press and hold this button to reset the Hero's path." msgstr "" msgid "Kingdom Summary" msgstr "Зводка аб каралеўÑтве" msgid "View a Summary of your Kingdom." msgstr "ПраглÑдзець зводку аб каралеўÑтве." msgid "Cast an adventure spell." msgstr "" msgid "End Turn" msgstr "" msgid "End your turn and left the computer take its turn." msgstr "" msgid "Adventure Options" msgstr "" msgid "Bring up the adventure options menu." msgstr "" msgid "" "Bring up the file options menu, allowing you to load, save, start a new game " "or quit." msgstr "" msgid "Bring up the system options menu, allowing you to customize your game." msgstr "" msgid "" "One or more heroes may still move, are you sure you want to end your turn?" msgstr "" msgid "Are you sure you want to quit?" msgstr "Ð’Ñ‹ ўпÑўненыÑ, што хочаце выйÑці?" msgid "Are you sure you want to restart? (Your current game will be lost.)" msgstr "" msgid "Are you sure you want to overwrite the save with this name?" msgstr "" msgid "Game saved successfully." msgstr "" msgid "There was an issue during saving." msgstr "" msgid "" "Are you sure you want to load a new game? (Your current game will be lost.)" msgstr "" msgid "Try looking on land!!!" msgstr "" msgid "" "Searching for the Ultimate Artifact is fruitless. Your hero could not carry " "it even if he found it - all his artifact slots are full." msgstr "" msgid "Digging for artifacts requires a whole day, try again tomorrow." msgstr "" msgid "" "After spending many hours digging here, you have uncovered the %{artifact}." msgstr "" msgid "Congratulations!" msgstr "" msgid "Nothing here. Where could it be?" msgstr "" msgid "Try searching on clear ground." msgstr "" msgid "A miniature view of the known world. Left click to move viewing area." msgstr "" msgid "Month: %{month} Week: %{week}" msgstr "МеÑÑц: %{month} Тыдзень: %{week}" msgid "Day: %{day}" msgstr "Дзень: %{day}" msgid "" "You find a small\n" "quantity of %{resource}." msgstr "" msgid "Status Window" msgstr "" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date." msgstr "" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date. Left click here to cycle through these windows." msgstr "" msgid "" "This lets you change player starting positions and colors. A particular " "color will always start in a particular location. Some positions may only be " "played by a computer player or only by a human player." msgstr "" "ДазвалÑе змÑніць пачатковую пазіцыю Ñ– колеры гульцоў. Ðд абранага колеру " "залежыць Ð¿Ð°Ñ‡Ð°Ñ‚ÐºÐ¾Ð²Ð°Ñ Ð»Ð°ÐºÐ°Ñ†Ñ‹Ñ. За Ð½ÐµÐºÐ°Ñ‚Ð¾Ñ€Ñ‹Ñ Ð¿Ð°Ð·Ñ–Ñ†Ñ‹Ñ– могуць гулÑць толькі " "аÑобы, альбо толькі ÐºÐ°Ð¼Ð¿ÑƒÑ‚Ð°Ñ€Ð½Ñ‹Ñ Ð³ÑƒÐ»ÑŒÑ†Ñ‹." msgid "" "This lets you change the class of a player. Classes are not always " "changeable. Depending on the scenario, a player may receive additional towns " "and/or heroes not of their primary alignment." msgstr "" "ДазвалÑе змÑніць ÐºÐ»Ð°Ñ Ð³ÑƒÐ»ÑŒÑ†Ð°, але Ð½Ñ ÐºÐ¾Ð¶Ð½Ñ‹ ÑцÑнар дазвалÑе змену.У " "залежнаÑці ад ÑцÑнара гулец можа атрымаць Ð´Ð°Ð´Ð°Ñ‚ÐºÐ¾Ð²Ñ‹Ñ Ð³Ð°Ñ€Ð°Ð´Ñ‹ Ñ–/альбо героÑÑž з " "клаÑам, Ñкі не адпавÑдае аÑноўнаму." msgid "Handicap" msgstr "Перашкода" # msgid "" "This lets you change the handicap of a particular player. Only humans may be " "handicapped. Handicapped players start with fewer resources and earn 15 or " "30% fewer resources per turn for mild and severe handicaps, respectively." msgstr "" "ДазвалÑе перашкодзіць аÑобе, ÑÐºÐ°Ñ Ð¿Ñ€Ð°Ð· гÑта пачне гульню з меншай колькаÑцю " "Ñ€ÑÑурÑаў Ñ– будзе атрымліваць на 15 альбо 30% менш Ñ€ÑÑурÑаў за ход з лёгкай Ñ– " "Ñур’ёзнай Ñтупеню перашкоды адпаведна." msgid "%{color} player" msgstr "%{color} гулец" msgid "No Handicap" msgstr "" msgid "No special restrictions on start resources and earning them per turn." msgstr "" msgid "Mild Handicap" msgstr "" # msgid "" "Mild handicapped players start with fewer resources and earn 15% fewer " "resources per turn." msgstr "" msgid "Severe Handicap" msgstr "" # msgid "" "Severe handicapped players start with fewer resources and earn 30% fewer " "resources per turn." msgstr "" msgid "View %{skill} Info" msgstr "Даведацца пра %{skill}" msgid "Keyboard|123" msgstr "" msgid "Keyboard|SPACE" msgstr "" msgid "Keyboard|ABC" msgstr "" msgid "Kingdom Income" msgstr "Прыбытак каралеўÑтва" msgid "Kingdom Income per day." msgstr "Прыбытак каралеўÑтва штодзень." msgid "For every lighthouse controlled, your ships will move further each day." msgstr "" msgid "English" msgstr "" msgid "French" msgstr "" msgid "Polish" msgstr "" msgid "German" msgstr "" msgid "Russian" msgstr "" msgid "Italian" msgstr "" msgid "Czech" msgstr "" msgid "Norwegian" msgstr "" msgid "Belarusian" msgstr "БеларуÑкаÑ" msgid "Bulgarian" msgstr "" msgid "Ukrainian" msgstr "" msgid "Romanian" msgstr "" msgid "Spanish" msgstr "" msgid "Swedish" msgstr "" msgid "Portuguese" msgstr "" msgid "Turkish" msgstr "" msgid "Dutch" msgstr "" msgid "Hungarian" msgstr "" msgid "Danish" msgstr "" msgid "Slovak" msgstr "" #, fuzzy msgid "Vietnamese" msgstr "ПраглÑдзець %{name}" msgid ", FPS: " msgstr "" msgid "%{object} robber" msgstr "" msgid "%{object} raided" msgstr "" msgid "Ector" msgstr "" msgid "Gwenneth" msgstr "" msgid "Sir Gallant" msgstr "" msgid "Tyro" msgstr "" msgid "Dimitry" msgstr "" msgid "Ruby" msgstr "" msgid "Crag Hack" msgstr "" msgid "Fineous" msgstr "" msgid "Jezebel" msgstr "" msgid "Atlas" msgstr "" msgid "Ergon" msgstr "" msgid "Jaclyn" msgstr "" msgid "Gem" msgstr "" msgid "Natasha" msgstr "" msgid "Carlawn" msgstr "" msgid "Luna" msgstr "" msgid "Arie" msgstr "" msgid "Barok" msgstr "" msgid "Kastore" msgstr "" msgid "Falagar" msgstr "" msgid "Dawn" msgstr "" msgid "Flint" msgstr "" msgid "Halon" msgstr "" msgid "Myra" msgstr "" msgid "Myrini" msgstr "" msgid "Wilfrey" msgstr "" msgid "Mandigal" msgstr "" msgid "Sarakin" msgstr "" msgid "Charity" msgstr "" msgid "Darlana" msgstr "" msgid "Ranloo" msgstr "" msgid "Rialdo" msgstr "" msgid "Zam" msgstr "" msgid "Zom" msgstr "" msgid "Celia" msgstr "" msgid "Roxana" msgstr "" msgid "Lord Corlagon" msgstr "" msgid "Lord Halton" msgstr "" msgid "Sister Eliza" msgstr "" msgid "Brother Brax" msgstr "" msgid "Dainwin" msgstr "" msgid "Joseph" msgstr "" msgid "Mog" msgstr "" msgid "Solmyr" msgstr "" msgid "Ceallach" msgstr "" msgid "Drakonia" msgstr "" msgid "Elderian" msgstr "" msgid "Gallavant" msgstr "" msgid "Jarkonas" msgstr "" msgid "Martine" msgstr "" msgid " gives you maximum morale" msgstr "" msgid " gives you maximum luck" msgstr "" msgid "You cannot pick up this artifact, you already have a full load!" msgstr "" msgid "To cast spells, you must first buy a spell book for %{gold} gold." msgstr "" msgid "Unfortunately, you seem to be a little short of cash at the moment." msgstr "" msgid "Do you wish to buy one?" msgstr "" msgid "%{count} / day" msgstr "" msgid "one" msgstr "" msgid "two" msgstr "" msgid "A whirlpool engulfs your ship. Some of your army has fallen overboard." msgstr "" msgid "Insulted by your refusal of their offer, the monsters attack!" msgstr "" msgid "" "The %{monster}, awed by the power of your forces, begin to scatter.\n" "Do you wish to pursue and engage them?" msgstr "" msgid "Ransacking an enemy camp, you discover a hidden cache of treasures." msgstr "" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with these resources, " "come back next week for more.\"" msgstr "" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there are no resources currently available. Please try " "again next week.\"" msgstr "" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with this gold, come " "back next week for more.\"" msgstr "" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there is no gold currently available. Please try again " "next week.\"" msgstr "" msgid "" "You've found an abandoned lean-to.\n" "Poking about, you discover some resources hidden nearby." msgstr "" msgid "The lean-to is long abandoned. There is nothing of value here." msgstr "" msgid "" "You catch a leprechaun foolishly sleeping amidst a cluster of magic " "mushrooms.\n" "In exchange for his freedom, he guides you to a small pot filled with " "precious things." msgstr "" msgid "" "You've found a magic garden, the kind of place that leprechauns and faeries " "like to cavort in, but there is no one here today.\n" "Perhaps you should try again next week." msgstr "" msgid "You come upon the remains of an unfortunate adventurer." msgstr "" msgid "Treasure" msgstr "" msgid "Searching through the tattered clothing, you find the %{artifact}." msgstr "" msgid "Searching through the tattered clothing, you find nothing." msgstr "" msgid "" "You come across an old wagon left by a trader who didn't quite make it to " "safe terrain." msgstr "" msgid "Unfortunately, others have found it first, and the wagon is empty." msgstr "" msgid "Searching inside, you find the %{artifact}." msgstr "" msgid "Inside, you find some of the wagon's cargo still intact." msgstr "" msgid "You search through the flotsam, and find some wood and some gold." msgstr "" msgid "You search through the flotsam, and find some wood." msgstr "" msgid "You search through the flotsam, but find nothing." msgstr "" msgid "Shrine of the 1st Circle" msgstr "" msgid "" "You come across a small shrine attended by a group of novice acolytes.\n" "In exchange for your protection, they agree to teach you a simple spell - " "'%{spell}'." msgstr "" msgid "Shrine of the 2nd Circle" msgstr "" msgid "" "You come across an ornate shrine attended by a group of rotund friars.\n" "In exchange for your protection, they agree to teach you a spell - " "'%{spell}'." msgstr "" msgid "Shrine of the 3rd Circle" msgstr "" msgid "" "You come across a lavish shrine attended by a group of high priests.\n" "In exchange for your protection, they agree to teach you a sophisticated " "spell - '%{spell}'." msgstr "" msgid "" "\n" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" msgid "" "\n" "Unfortunately, you already have knowledge of this spell, so there is nothing " "more for them to teach you." msgstr "" msgid "" "\n" "Unfortunately, you have no Magic Book to record the spell with." msgstr "" msgid "" "You approach the hut and observe a witch inside studying an ancient tome on " "%{skill}.\n" "\n" msgstr "" msgid "" "As you approach, she turns and focuses her one glass eye on you.\n" "\"You already know everything you deserve to learn!\" the witch screeches. " "\"NOW GET OUT OF MY HOUSE!\"" msgstr "" msgid "" "As you approach, she turns and speaks.\n" "\"You already know that which I would teach you. I can help you no further.\"" msgstr "" msgid "" "An ancient and immortal witch living in a hut with bird's legs for stilts " "teaches you %{skill} for her own inscrutable purposes." msgstr "" msgid "As you drink the sweet water, you gain luck for your next battle." msgstr "" msgid "You drink from the enchanted fountain, but nothing happens." msgstr "" msgid "You enter the faerie ring, but nothing happens." msgstr "" msgid "" "Upon entering the mystical faerie ring, your army gains luck for its next " "battle." msgstr "" msgid "" "You've found an ancient and weathered stone idol.\n" "It is supposed to grant luck to visitors, but since the stars are already " "smiling upon you, it does nothing." msgstr "" msgid "" "You've found an ancient and weathered stone idol.\n" "Kissing it is supposed to be lucky, so you do. The stone is very cold to the " "touch." msgstr "" msgid "The mermaids silently entice you to return later and be blessed again." msgstr "" msgid "" "The magical, soothing beauty of the Mermaids reaches you and your crew.\n" "Just for a moment, you forget your worries and bask in the beauty of the " "moment.\n" "The mermaids charms bless you with increased luck for your next combat." msgstr "" msgid "" "You come upon the pyramid of a great and ancient king.\n" "You are tempted to search it for treasure, but all the old stories warn of " "fearful curses and undead guardians.\n" "Will you search?" msgstr "" msgid "" "Upon defeating the monsters, you decipher an ancient glyph on the wall, " "telling the secret of the spell - '" msgstr "" msgid "Unfortunately, you have no Magic Book to record the spell with." msgstr "" msgid "" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" msgid "" "You come upon the pyramid of a great and ancient king.\n" "Routine exploration reveals that the pyramid is completely empty." msgstr "" msgid "" "A drink at the well is supposed to restore your spell points, but you are " "already at maximum." msgstr "" msgid "A second drink at the well in one day will not help you." msgstr "" msgid "A drink from the well has restored your spell points to maximum." msgstr "" msgid "" "\"I'm sorry sir,\" The leader of the soldiers says, \"but you already know " "everything we have to teach.\"" msgstr "" msgid "The soldiers living in the fort teach you a few new defensive tricks." msgstr "" msgid "" "You've come upon a mercenary camp practicing their tactics. \"You're too " "advanced for us,\" the mercenary captain says. \"We can teach nothing more.\"" msgstr "" msgid "" "You've come upon a mercenary camp practicing their tactics. The mercenaries " "welcome you and your troops and invite you to train with them." msgstr "" msgid "\"Go 'way!\", the witch doctor barks, \"you know all I know.\"" msgstr "" msgid "" "An Orcish witch doctor living in the hut deepens your knowledge of magic by " "showing you how to cast stones, read portents, and decipher the intricacies " "of chicken entrails." msgstr "" msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, the Druids turn you away, indicating they have nothing " "new to teach you." msgstr "" msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, they teach you new ways to cast spells." msgstr "" msgid "" "You tentatively approach the burial ground of ancient warriors. Do you want " "to search the graves?" msgstr "" msgid "" "You spend several hours searching the graves and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" msgid "Upon defeating the Zombies you search the graves and find something!" msgstr "" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the shipwreck?" msgstr "" msgid "" "You spend several hours sifting through the debris and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" msgid "" "Upon defeating the Ghosts you sift through the debris and find something!" msgstr "" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the ship?" msgstr "" msgid "" "Upon defeating the Skeletons you sift through the debris and find something!" msgstr "" msgid "Your men spot a navigational buoy, confirming that you are on course." msgstr "" msgid "" "Your men spot a navigational buoy, confirming that you are on course and " "increasing their morale." msgstr "" msgid "" "The drink at the oasis is refreshing, but offers no further benefit. The " "oasis might help again if you fought a battle first." msgstr "" msgid "" "A drink at the oasis fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" msgid "" "The drink at the watering hole is refreshing, but offers no further benefit. " "The watering hole might help again if you fought a battle first." msgstr "" msgid "" "A drink at the watering hole fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" msgid "" "It doesn't help to pray twice before a battle. Come back after you've fought." msgstr "" msgid "A visit and a prayer at the temple raises the morale of your troops." msgstr "" msgid "" "An old Knight appears on the steps of the gazebo. \"I am sorry, my liege, I " "have taught you all I can.\"" msgstr "" msgid "" "An old Knight appears on the steps of the gazebo. \"My liege, I will teach " "you all that I know to aid you in your travels.\"" msgstr "" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he says, \"I would give you an artifact as a reward, but " "you're all full.\"" msgstr "" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he rewards you for your act of kindness by giving you the " "%{art}." msgstr "" msgid "A leprechaun offers you the %{art} for the small price of %{gold} Gold." msgstr "" msgid "" "A leprechaun offers you the %{art} for the small price of %{gold} Gold and " "%{count} %{res}." msgstr "" msgid "Do you wish to buy this artifact?" msgstr "" msgid "" "You try to pay the leprechaun, but realize that you can't afford it. The " "leprechaun stamps his foot and ignores you." msgstr "" msgid "" "Insulted by your refusal of his generous offer, the leprechaun stamps his " "foot and ignores you." msgstr "" msgid "You've found the artifact: " msgstr "" msgid "" "You've found the humble dwelling of a withered hermit. The hermit tells you " "that he is willing to give the %{art} to the first wise person he meets." msgstr "" msgid "" "You've come across the spartan quarters of a retired soldier. The soldier " "tells you that he is willing to pass on the %{art} to the first true leader " "he meets." msgstr "" msgid "" "You've encountered a strange person with a hat and an owl on it. He tells " "you that he is willing to give %{art} if you have %{skill}." msgstr "" msgid "" "You come upon an ancient artifact. As you reach for it, a pack of Rogues " "leap out of the brush to guard their stolen loot." msgstr "" msgid "" "Through a clearing you observe an ancient artifact. Unfortunately, it's " "guarded by a nearby %{monster}. Do you want to fight the %{monster} for the " "artifact?" msgstr "" msgid "Victorious, you take your prize, the %{art}." msgstr "" msgid "" "Discretion is the better part of valor, and you decide to avoid this fight " "for today." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold pieces." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold and the %{art}." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it, " "only to find it empty." msgstr "" msgid "" "After scouring the area, you fall upon a hidden treasure cache. You may take " "the gold or distribute the gold to the peasants for experience. Do you wish " "to keep the gold?" msgstr "" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "%{gold} gold pieces." msgstr "" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "ancient artifact %{art}." msgstr "" msgid "" "You stumble upon a dented and tarnished lamp lodged deep in the earth. Do " "you wish to rub the lamp?" msgstr "" msgid "" "You have taken control of the local Alchemist shop. It will provide you with " "%{count} unit of Mercury per day." msgstr "" msgid "" "You gain control of a sawmill. It will provide you with %{count} units of " "wood per day." msgstr "" msgid "" "You gain control of an ore mine. It will provide you with %{count} units of " "ore per day." msgstr "" msgid "" "You gain control of a sulfur mine. It will provide you with %{count} unit of " "sulfur per day." msgstr "" msgid "" "You gain control of a crystal mine. It will provide you with %{count} unit " "of crystal per day." msgstr "" msgid "" "You gain control of a gem mine. It will provide you with %{count} unit of " "gems per day." msgstr "" msgid "" "You gain control of a gold mine. It will provide you with %{count} gold per " "day." msgstr "" msgid "" "The lighthouse is now under your control, and all of your ships will now " "move further each day." msgstr "" msgid "You gain control of a %{name}." msgstr "" msgid "" "You come upon an abandoned gold mine. The mine appears to be haunted. Do you " "wish to enter?" msgstr "" msgid "You beat the Ghosts and are able to restore the mine to production." msgstr "" msgid "" "A group of %{monster} with a desire for greater glory wish to join you. Do " "you accept?" msgstr "" msgid "As you approach the dwelling, you notice that there is no one here." msgstr "" msgid "" "You search the ruins, but the Medusas that used to live here are gone. " "Perhaps there will be more next week." msgstr "" msgid "" "You've found some Medusas living in the ruins. They are willing to join your " "army for a price. Do you want to recruit Medusas?" msgstr "" msgid "" "You've found a Sprite Tree City. Unfortunately, none of the Sprites living " "there wish to join an army. Maybe next week." msgstr "" msgid "" "Some of the Sprites living in the tree city are willing to join your army " "for a price. Do you want to recruit Sprites?" msgstr "" msgid "" "A colorful Rogues' wagon stands empty here. Perhaps more Rogues will be here " "later." msgstr "" msgid "" "Distant sounds of music and laughter draw you to a colorful wagon housing " "Rogues. Do you wish to have any Rogues join your army?" msgstr "" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. The " "tents are unoccupied. Perhaps more Nomads will be here later." msgstr "" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. Do you " "wish to have any Nomads join you during your travels?" msgstr "" msgid "The pit of mud bubbles for a minute and then lies still." msgstr "" msgid "" "As you approach the bubbling pit of mud, creatures begin to climb out and " "position themselves around it. In unison they say: \"Mother Earth would like " "to offer you a few of her troops. Do you want to recruit Earth Elementals?\"" msgstr "" msgid "You enter the structure of white stone pillars, and find nothing." msgstr "" msgid "" "White stone pillars support a roof that rises up to the sky. As you enter " "the structure, the dead air of the outside gives way to a whirling gust that " "almost pushes you back out. The air current materializes into a barely " "visible form. The creature asks, in what can only be described as a loud " "whisper: \"Why have you come? Are you here to call upon the forces of the " "air?\"" msgstr "" msgid "No Fire Elementals approach you from the lava pool." msgstr "" msgid "" "Beneath a structure that serves to hold in heat, Fire Elementals move about " "in a fiery pool of molten lava. A group of them approach you and offer their " "services. Would you like to recruit Fire Elementals?" msgstr "" msgid "A face forms in the water for a moment, and then is gone." msgstr "" msgid "" "Crystalline structures cast shadows over a small reflective pool of water. " "You peer into the pool, and a face that is not your own peers back. It asks: " "\"Would you like to call upon the powers of water?\"" msgstr "" msgid "This burial site is deathly still." msgstr "" msgid "" "Restless spirits of long dead warriors seeking their final resting place " "offer to join you in hopes of finding peace. Do you wish to recruit ghosts?" msgstr "" msgid "" "The City of the Dead is empty of life, and empty of unlife as well. Perhaps " "some undead will move in next week." msgstr "" msgid "" "Some Liches living here are willing to join your army for a price. Do you " "want to recruit Liches?" msgstr "" msgid "" "You've found the ruins of an ancient city, now inhabited solely by the " "undead. Will you search?" msgstr "" msgid "" "Some of the surviving Liches are impressed by your victory over their " "fellows, and offer to join you for a price. Do you want to recruit Liches?" msgstr "" msgid "" "You've found one of those bridges that Trolls are so fond of living under, " "but there are none here. Perhaps there will be some next week." msgstr "" msgid "" "Some Trolls living under a bridge are willing to join your army, but for a " "price. Do you want to recruit Trolls?" msgstr "" msgid "Trolls living under the bridge challenge you. Will you fight them?" msgstr "" msgid "" "A few Trolls remain, cowering under the bridge. They approach you and offer " "to join your forces as mercenaries. Do you want to buy any Trolls?" msgstr "" msgid "" "The Dragon city has no Dragons willing to join you this week. Perhaps a " "Dragon will become available next week." msgstr "" msgid "" "The Dragon city is willing to offer some Dragons for your army for a price. " "Do you wish to recruit Dragons?" msgstr "" msgid "" "You stand before the Dragon City, a place off-limits to mere humans. Do you " "wish to violate this rule and challenge the Dragons to a fight?" msgstr "" msgid "" "Having defeated the Dragon champions, the city's leaders agree to supply " "some Dragons to your army for a price. Do you wish to recruit Dragons?" msgstr "" msgid "From the observation tower, you are able to see distant lands." msgstr "" msgid "" "The spring only refills once a week, and someone's already been here this " "week." msgstr "" msgid "" "A drink at the spring is supposed to give you twice your normal spell " "points, but you are already at that level." msgstr "" msgid "" "A drink from the spring fills your blood with magic! You have twice your " "normal spell points in reserve." msgstr "" msgid "" "Recognizing you, the butler refuses to admit you. \"The master,\" he says, " "\"will not see the same student twice.\"" msgstr "" msgid "" "The butler admits you to see the master of the house. He trains you in the " "four skills a hero should know." msgstr "" msgid "" "The butler opens the door and looks you up and down. \"You are neither " "famous nor diplomatic enough to be admitted to see my master,\" he sniffs. " "\"Come back when you think yourself worthy.\"" msgstr "" msgid " and " msgstr "" msgid "" "All of the %{monsters} you have in your army have been trained by the battle " "masters of the fort. Your army now contains %{monsters2}." msgstr "" msgid "" "An unusual alliance of Ogres, Orcs, and Dwarves offer to train (upgrade) any " "such troops brought to them. Unfortunately, you have none with you." msgstr "" msgid "All of your %{monsters} have been upgraded into %{monsters2}." msgstr "" msgid "" "A blacksmith working at the foundry offers to convert all Pikemen and " "Swordsmen's weapons brought to him from iron to steel. He also says that he " "knows a process that will convert Iron Golems into Steel Golems. " "Unfortunately, you have none of these troops in your army, so he can't help " "you." msgstr "" msgid "" "The captain looks at you with surprise and says:\n" "\"You already have all the maps I know about. Let me fish in peace now.\"" msgstr "" msgid "" "A retired captain living on this refurbished fishing platform offers to sell " "you maps of the sea he made in his younger days for 1,000 gold. Do you wish " "to buy the maps?" msgstr "" msgid "" "The captain sighs. \"You don't have enough money, eh? You can't expect me " "to give my maps away for free!\"" msgstr "" msgid "" "You come upon an obelisk made from a type of stone you have never seen " "before. Staring at it intensely, the smooth surface suddenly changes to an " "inscription. The inscription is a piece of a lost ancient map. Quickly you " "copy down the piece and the inscription vanishes as abruptly as it appeared." msgstr "" msgid "You have already been to this obelisk." msgstr "" msgid "" "Upon your approach, the tree opens its eyes in delight. \"It is good to see " "you, my student. I hope my teachings have helped you.\"" msgstr "" msgid "" "Upon your approach, the tree opens its eyes in delight. \"Ahh, an " "adventurer! Allow me to teach you a little of what I have learned over the " "ages.\"" msgstr "" msgid "Upon your approach, the tree opens its eyes in delight." msgstr "" msgid "" "\"Ahh, an adventurer! I will be happy to teach you a little of what I have " "learned over the ages for a mere %{count} %{res}.\"" msgstr "" msgid "(Just bury it around my roots.)" msgstr "" msgid "Tears brim in the eyes of the tree." msgstr "" msgid "\"I need %{count} %{res}.\"" msgstr "" msgid "it whispers. (sniff) \"Well, come back when you can pay me.\"" msgstr "" msgid "" "Nestled among the trees sits a blind seer. After you explain the intent of " "your journey, the seer activates his crystal ball, allowing you to see the " "strengths and weaknesses of your opponents." msgstr "" msgid "" "The entrance to the cave is dark, and a foul, sulfurous smell issues from " "the cave mouth. Will you enter?" msgstr "" msgid "Except for evidence of a terrible battle, the cave is empty." msgstr "" msgid "" "You find a powerful and grotesque Demon in the cave. \"Today,\" it rasps, " "\"you will fight and surely die. But I will give you a choice of deaths. You " "may fight me, or you may fight my servants. Do you prefer to fight my " "servants?\"" msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points." msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points and " "%{count} gold." msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and find the %{art} in the back of the cave." msgstr "" msgid "" "Seeing that you do not have %{count} gold, the demon slashes you with its " "claws, and the last thing you see is a red haze." msgstr "" msgid "" "The Demon leaps upon you and has its claws at your throat before you can " "even draw your sword. \"Your life is mine,\" it says. \"I will sell it back " "to you for %{count} gold.\"" msgstr "" msgid "" "Upon defeating the daemon's servants, you find a hidden cache with %{count} " "gold." msgstr "" msgid "" "As you enter the Alchemist's Tower, a hobbled, graying man in a brown cloak " "makes his way towards you." msgstr "" msgid "He checks your pack, and sees that you have 1 cursed item." msgid_plural "" "He checks your pack, and sees that you have %{count} cursed items." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "For %{gold} gold, the alchemist will remove it for you. Do you pay?" msgid_plural "" "For %{gold} gold, the alchemist will remove them for you. Do you pay?" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "" "After you consent to pay the requested amount of gold, the alchemist grabs " "the cursed artifact and throws it into his magical cauldron." msgid_plural "" "After you consent to pay the requested amount of gold, the alchemist grabs " "all cursed artifacts and throws them into his magical cauldron." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "" "You hear a voice from behind the locked door, \"You don't have enough gold " "to pay for my services.\"" msgstr "" msgid "" "You hear a voice from high above in the tower, \"Go away! I can't help you!\"" msgstr "" msgid "" "The head groom speaks to you, \"That is a fine looking horse you have. I am " "afraid we can give you no better, but the horses your cavalry are riding " "look to be of poor breeding stock. We have many trained war horses which " "would aid your riders greatly. I insist you take them.\"" msgstr "" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, he will " "grow tired in a week. You must also let me give better horses to your " "mounted soldiers, their horses look shoddy and weak.\"" msgstr "" msgid "" "The head groom approaches you and speaks, \"You already have a fine horse, " "and have no inexperienced cavalry which might make use of our trained war " "horses.\"" msgstr "" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, his " "endurance will wane with a lot of heavy riding, and you must return for a " "fresh mount in a week. We also have many fine war horses which could benefit " "mounted soldiers, but you have none we can help.\"" msgstr "" msgid "The Arena guards turn you away." msgstr "" msgid "" "You have your crew stop up their ears with wax before the sirens' eerie song " "has any chance of luring them to a watery grave." msgstr "" msgid "" "As the sirens sing their eerie song, your small, determined army manages to " "overcome the urge to dive headlong into the sea." msgstr "" msgid "" "An eerie wailing song emanates from the sirens perched upon the rocks. Many " "of your crew fall under its spell, and dive into the water where they drown. " "You are now wiser for the visit, and gain %{exp} experience." msgstr "" msgid "" "In a dazzling display of daring, you break into the local jail and free the " "hero imprisoned there, who, in return, pledges loyalty to your cause." msgstr "" msgid "" "You already have %{count} heroes, and regretfully must leave the prisoner in " "this jail to languish in agony for untold days." msgstr "" msgid "" "You enter a rickety hut and talk to the magician who lives there. He tells " "you of places near and far which may aid you in your journeys." msgstr "" msgid "This eye seems to be intently studying its surroundings." msgstr "" msgid "You come across a giant Sphinx. The Sphinx remains strangely quiet." msgstr "" msgid "" "\"I have a riddle for you,\" the Sphinx says. \"Answer correctly, and you " "shall be rewarded. Answer incorrectly, and you shall be eaten. Do you accept " "the challenge?\"" msgstr "" msgid "" "The Sphinx asks you the following riddle:\n" "\n" "'%{riddle}'\n" "\n" "Your answer?" msgstr "" msgid "" "\"You guessed incorrectly,\" the Sphinx says, smiling. The Sphinx swipes at " "you with a paw, knocking you to the ground. Another blow makes the world go " "black, and you know no more." msgstr "" msgid "" "Looking somewhat disappointed, the Sphinx sighs. \"You've answered my riddle " "so here's your reward. Now begone.\"" msgstr "" msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "As you speak the magic word, the glowing barrier dissolves into nothingness." msgstr "" msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "You speak, and nothing happens." msgstr "" msgid "" "You enter the tent and see an old woman gazing into a magic gem. She looks " "up and says,\n" "\"In my travels, I have learned much in the way of arcane magic. A great " "oracle taught me his skill. I have the answer you seek.\"" msgstr "" #, fuzzy msgid "No spell book is present." msgstr "ÐÑма загавораў." msgid "" "This spell costs %{mana} spell points. You have no spell points, so you " "cannot cast it." msgstr "" msgid "" "This spell costs %{mana} spell points. You only have %{point} spell points, " "so you cannot cast it." msgstr "" msgid "The spell was not found." msgstr "" msgid "Only heroes can cast this spell." msgstr "" msgid "This hero is not able to cast adventure spells." msgstr "" msgid "Your hero is too tired to cast this spell today. Try again tomorrow." msgstr "" msgid "This spell cannot be cast on a boat." msgstr "" msgid "This spell can only be cast near an ocean." msgstr "" msgid "" "There are no boats available and no ocean adjacent to the hero where this " "spell will work." msgstr "" msgid "There are no boats available for this spell." msgstr "" msgid "There is no ocean adjacent to the hero where this spell will work." msgstr "" msgid "" "You do not own any town or castle that is not currently occupied by a hero. " "This spell will have no effect." msgstr "" msgid "This hero is already in a town, so this spell will have no effect." msgstr "" msgid "" "The nearest town is %{town}.\n" "\n" "This town is occupied by your hero %{hero}." msgstr "" msgid "This spell is already in effect." msgstr "" msgid "" "No opponent neither has nor can have any hero under their command anymore. " "Casting this spell will have no effect." msgstr "" msgid "" "No opponent has a hero under their command at this time. Casting this spell " "will have no effect." msgstr "" msgid "" "You must be within %{count} spaces of a monster for the Visions spell to " "work." msgstr "" msgid "" "You must be standing on the entrance to a mine (sawmills and alchemist labs " "do not count) to cast this spell." msgstr "" msgid "You must first defeat the ghosts guarding the mine to cast this spell." msgstr "" msgid "" "There are already at least as many elementals guarding the mine as this hero " "can generate. Casting this spell will have no effect." msgstr "" msgid "%{name} the %{race} (Level %{level})" msgstr "" msgid "" "'Spread' combat formation spreads the hero's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" msgid "" "'Grouped' combat formation bunches the hero's army together in the center of " "their side of the battlefield." msgstr "" msgid "Are you sure you want to dismiss this Hero?" msgstr "" msgid "View Experience Info" msgstr "" msgid "View Spell Points Info" msgstr "" msgid "Set army combat formation to 'Spread'" msgstr "" msgid "Set army combat formation to 'Grouped'" msgstr "" msgid "Exit Hero Screen" msgstr "" msgid "You cannot dismiss a hero in a castle" msgstr "" msgid "Dismissal of %{name} the %{race} is prohibited by scenario" msgstr "" msgid "Dismiss %{name} the %{race}" msgstr "" msgid "Show previous hero" msgstr "" msgid "Show next hero" msgstr "" msgid "Blood Morale" msgstr "" msgid "%{morale} Morale" msgstr "" msgid "%{luck} Luck" msgstr "" msgid "Current Luck Modifiers:" msgstr "" msgid "Current Morale Modifiers:" msgstr "" msgid "Entire army is undead, so morale does not apply." msgstr "" msgid "" "Current experience %{exp1}.\n" " Next level %{exp2}." msgstr "" msgid "Level %{level}" msgstr "" msgid "" "%{name} currently has %{point} spell points out of a maximum of %{max}. The " "maximum number of spell points is 10 times the hero's knowledge. It is " "occasionally possible for the hero to have more than their maximum spell " "points via special events." msgstr "" msgid "%{name1} meets %{name2}" msgstr "" msgid "Enemy heroes are now fully identifiable." msgstr "" msgid "Identify Hero" msgstr "" msgid "Select town to port to." msgstr "" msgid "Town Portal" msgstr "" msgid "The creatures are willing to join us!" msgstr "" msgid "All the creatures will join us..." msgstr "" msgid "The creature will join us..." msgid_plural "%{count} of the creatures will join us..." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "" "\n" " for a fee of %{gold} gold." msgstr "" msgid "These weak creatures will surely flee before us." msgstr "" msgid "I fear these creatures are in the mood for a fight." msgstr "" msgid "The hero's attack skill is a bonus added to each unit's attack skill." msgstr "" msgid "The hero's defense skill is a bonus added to each unit's defense skill." msgstr "" msgid "The hero's spell power determines the duration or power of a spell." msgstr "" msgid "" " The hero's knowledge determines how many spell points the hero may have. " "Under normal circumstances, a hero is limited to 10 spell points per level " "of knowledge." msgstr "" msgid "Current Modifiers:" msgstr "" msgid "skill|Basic" msgstr "" msgid "skill|Advanced" msgstr "" msgid "skill|Expert" msgstr "" msgid "Pathfinding" msgstr "" msgid "Archery" msgstr "" msgid "Logistics" msgstr "" msgid "Scouting" msgstr "" msgid "Diplomacy" msgstr "" msgid "Navigation" msgstr "" msgid "Leadership" msgstr "" msgid "Wisdom" msgstr "" msgid "Mysticism" msgstr "" msgid "Ballistics" msgstr "" msgid "Eagle Eye" msgstr "" msgid "Necromancy" msgstr "" msgid "Estates" msgstr "" msgid "Advanced Archery" msgstr "" msgid "Advanced Pathfinding" msgstr "" msgid "Basic Archery" msgstr "" msgid "Basic Pathfinding" msgstr "" msgid "Expert Pathfinding" msgstr "" msgid "Advanced Logistics" msgstr "" msgid "Basic Logistics" msgstr "" msgid "Basic Scouting" msgstr "" msgid "Expert Archery" msgstr "" msgid "Expert Logistics" msgstr "" msgid "Advanced Diplomacy" msgstr "" msgid "Advanced Scouting" msgstr "" msgid "Basic Diplomacy" msgstr "" msgid "Expert Diplomacy" msgstr "" msgid "Expert Scouting" msgstr "" msgid "Advanced Leadership" msgstr "" msgid "Advanced Navigation" msgstr "" msgid "Basic Leadership" msgstr "" msgid "Basic Navigation" msgstr "" msgid "Expert Navigation" msgstr "" msgid "Advanced Wisdom" msgstr "" msgid "Basic Mysticism" msgstr "" msgid "Basic Wisdom" msgstr "" msgid "Expert Leadership" msgstr "" msgid "Expert Wisdom" msgstr "" msgid "Advanced Luck" msgstr "" msgid "Advanced Mysticism" msgstr "" msgid "Basic Luck" msgstr "" msgid "Expert Luck" msgstr "" msgid "Expert Mysticism" msgstr "" msgid "Advanced Ballistics" msgstr "" msgid "Advanced Eagle Eye" msgstr "" msgid "Basic Ballistics" msgstr "" msgid "Basic Eagle Eye" msgstr "" msgid "Expert Ballistics" msgstr "" msgid "Advanced Necromancy" msgstr "" msgid "Basic Estates" msgstr "" msgid "Basic Necromancy" msgstr "" msgid "Expert Eagle Eye" msgstr "" msgid "Expert Necromancy" msgstr "" msgid "Advanced Estates" msgstr "" msgid "Expert Estates" msgstr "" msgid "" "%{skill} reduces the movement penalty for rough terrain by %{count} percent." msgstr "" msgid "%{skill} eliminates the movement penalty for rough terrain." msgstr "" msgid "" "%{skill} increases the damage done by the hero's range attacking creatures " "by %{count} percent." msgstr "" msgid "%{skill} increases the hero's movement points by %{count} percent." msgstr "" msgid "%{skill} increases the hero's viewable area by one square." msgid_plural "%{skill} increases the hero's viewable area by %{count} squares." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "" "%{skill} allows the hero to negotiate with monsters who are weaker than " "their army, and reduces the cost of surrender." msgstr "" msgid "" "Approximately %{count} percent of the creatures may offer to join the hero." msgstr "" msgid "All of the creatures may offer to join the hero." msgstr "" msgid "" "The cost of surrender is reduced to %{percent} percent of the total cost of " "troops in the army." msgstr "" msgid "" "%{skill} increases the hero's movement points over water by %{count} percent." msgstr "" #, fuzzy msgid "%{skill} increases the hero's troops morale by %{count}." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð°Ð²Ñлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ на %{count} штодзень." msgid "%{skill} allows the hero to learn third level spells." msgstr "" msgid "%{skill} allows the hero to learn fourth level spells." msgstr "" msgid "%{skill} allows the hero to learn fifth level spells." msgstr "" msgid "%{skill} regenerates one additional spell point per day to the hero." msgid_plural "" "%{skill} regenerates %{count} additional spell points per day to the hero." msgstr[0] "" msgstr[1] "" msgstr[2] "" #, fuzzy msgid "%{skill} increases the hero's luck by %{count}." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð°Ð²Ñлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ на %{count} штодзень." msgid "" "%{skill} gives the hero's catapult shots a greater chance to hit and do " "damage to castle walls." msgstr "" msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot has a " "greater chance to hit and do damage to castle walls." msgstr "" msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot " "automatically destroys any wall, except a fortified wall in a Knight castle." msgstr "" msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 1st or " "2nd level spell that was cast by an enemy during combat." msgstr "" msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 3rd " "level spell (or below) that was cast by an enemy during combat." msgstr "" msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 4th " "level spell (or below) that was cast by an enemy during combat." msgstr "" msgid "" "%{skill} allows %{count} percent of the creatures killed in combat to be " "brought back from the dead as Skeletons." msgstr "" msgid "" "The hero produces %{count} gold pieces per day as tax revenue from estates." msgstr "" msgid "Blue" msgstr "Сіні" msgid "Green" msgstr "ЗÑлёны" msgid "Red" msgstr "Чырвоны" msgid "Yellow" msgstr "Жоўты" msgid "Orange" msgstr "Медны" msgid "Purple" msgstr "Пурпур" msgid "barrier|Aqua" msgstr "" msgid "barrier|Blue" msgstr "" msgid "barrier|Brown" msgstr "" msgid "barrier|Gold" msgstr "" msgid "barrier|Green" msgstr "" msgid "barrier|Orange" msgstr "" msgid "barrier|Purple" msgstr "" msgid "barrier|Red" msgstr "" msgid "tent|Aqua" msgstr "Блакітны" msgid "tent|Blue" msgstr "Сіні" msgid "tent|Brown" msgstr "Карычневы" msgid "tent|Gold" msgstr "Залаты" msgid "tent|Green" msgstr "ЗÑлёны" msgid "tent|Orange" msgstr "Ðранжавы" msgid "tent|Purple" msgstr "ФіÑлетавы" msgid "tent|Red" msgstr "Чырвоны" msgid "Experience" msgstr "" msgid "" "Experience allows your heroes to go up levels, increasing their primary and " "secondary skills." msgstr "" msgid "Hero/Stats" msgstr "" msgid "Skills" msgstr "" msgid "Town/Castle" msgstr "" msgid "Garrison" msgstr "" msgid "Gold Per Day:" msgstr "" msgid "View Heroes." msgstr "" msgid "Towns/Castles" msgstr "" msgid "View Towns and Castles." msgstr "" msgid "luck|Cursed" msgstr "ПраклÑтаÑ" msgid "luck|Awful" msgstr "ÐÑздольнаÑ" msgid "luck|Bad" msgstr "ДрÑннаÑ" msgid "luck|Normal" msgstr "ЗвычайнаÑ" msgid "luck|Good" msgstr "ДобраÑ" msgid "luck|Great" msgstr "ВыдатнаÑ" msgid "luck|Irish" msgstr "БагатаÑ" msgid "" "Bad luck sometimes falls on the hero's units in combat, causing their " "attacks to only do half damage." msgstr "" msgid "" "Neutral luck means the hero's units will never get lucky or unlucky attacks " "on the enemy." msgstr "" msgid "" "Good luck sometimes lets the hero's units get lucky attacks (double " "strength) in combat." msgstr "" msgid "morale|Treason" msgstr "ЗдраднаÑ" msgid "morale|Awful" msgstr "ÐÑздольнаÑ" msgid "morale|Poor" msgstr "КепÑкаÑ" msgid "morale|Normal" msgstr "ЗвычайнаÑ" msgid "morale|Good" msgstr "ДобраÑ" msgid "morale|Great" msgstr "ВыдатнаÑ" msgid "morale|Blood!" msgstr "ЛютаÑ" msgid "Bad morale may cause the hero's units to freeze in combat." msgstr "" msgid "" "Neutral morale means the hero's units will never be blessed with extra " "attacks or freeze in combat." msgstr "" msgid "Good morale may give the hero's units extra attacks in combat." msgstr "" msgid "Knight" msgstr "Рыцар" msgid "Barbarian" msgstr "Варвар" msgid "Sorceress" msgstr "Чараўніца" msgid "Warlock" msgstr "Ð’Ñдзьмак" msgid "Wizard" msgstr "Чарадзей" msgid "Necromancer" msgstr "Ðекрамант" msgid "Multi" msgstr "" msgid "doubleLined|Knight" msgstr "Рыцар" msgid "doubleLined|Barbarian" msgstr "Варвар" msgid "doubleLined|Sorceress" msgstr "" "Чараў-\n" "ніца" msgid "doubleLined|Warlock" msgstr "Ð’Ñдзьмак" msgid "doubleLined|Wizard" msgstr "Чарадзей" msgid "" "doubleLined|Necro-\n" "mancer" msgstr "" "Ðекра-\n" "мант" msgid "doubleLinedRace|Multi" msgstr "" msgid "doubleLinedRace|Random" msgstr "" "Выпад-\n" "ковы" msgid "speed|Standing" msgstr "Ðа меÑцы" msgid "speed|Crawling" msgstr "Ледзь поўзае" msgid "speed|Very Slow" msgstr "МаруднаÑ" msgid "speed|Slow" msgstr "ÐÑÑпешнаÑ" msgid "speed|Average" msgstr "ÐіштаватаÑ" msgid "speed|Fast" msgstr "ХуткаÑ" msgid "speed|Very Fast" msgstr "ШпаркаÑ" msgid "speed|Ultra Fast" msgstr "ІмкліваÑ" msgid "speed|Blazing" msgstr "ÐÑÑтрымнаÑ" msgid "speed|Instant" msgstr "ІмгненнаÑ" msgid "week|Squirrel" msgstr "" msgid "week|Rabbit" msgstr "" msgid "week|Gopher" msgstr "" msgid "week|Badger" msgstr "" #, fuzzy msgid "week|Rat" msgstr "тыдзень" msgid "week|Eagle" msgstr "" msgid "week|Weasel" msgstr "" msgid "week|Raven" msgstr "" msgid "week|Mongoose" msgstr "" #, fuzzy msgid "week|Dog" msgstr "тыдзень" msgid "week|Aardvark" msgstr "" msgid "week|Lizard" msgstr "" msgid "week|Tortoise" msgstr "" msgid "week|Hedgehog" msgstr "" msgid "week|Condor" msgstr "" msgid "week|Ant" msgstr "" msgid "week|Grasshopper" msgstr "" msgid "week|Dragonfly" msgstr "" msgid "week|Spider" msgstr "" msgid "week|Butterfly" msgstr "" msgid "week|Bumblebee" msgstr "" msgid "week|Locust" msgstr "" msgid "week|Earthworm" msgstr "" msgid "week|Hornet" msgstr "" msgid "week|Beetle" msgstr "" msgid "week|PLAGUE" msgstr "" msgid "week|Unnamed" msgstr "" msgid "Desert" msgstr "" msgid "Snow" msgstr "" msgid "Wasteland" msgstr "" msgid "Beach" msgstr "" msgid "Lava" msgstr "" msgid "Dirt" msgstr "" msgid "Grass" msgstr "" msgid "Ocean" msgstr "" msgid "maps|Small" msgstr "" msgid "maps|Medium" msgstr "" msgid "maps|Large" msgstr "" msgid "maps|Extra Large" msgstr "" msgid "maps|Custom Size" msgstr "" msgid "Ore Mine" msgstr "" msgid "Sulfur Mine" msgstr "" msgid "Crystal Mine" msgstr "" msgid "Gems Mine" msgstr "" msgid "Gold Mine" msgstr "" msgid "Mine" msgstr "" msgid "Burma shave." msgstr "" msgid "Next sign 50 miles." msgstr "" msgid "See Rock City." msgstr "" msgid "This space for rent." msgstr "" msgid "No object" msgstr "" msgid "Alchemist Lab" msgstr "" msgid "Sign" msgstr "" msgid "Buoy" msgstr "" msgid "Skeleton" msgstr "Шкілет" msgid "Daemon Cave" msgstr "" msgid "Treasure Chest" msgstr "" msgid "Faerie Ring" msgstr "" msgid "Campfire" msgstr "" msgid "Fountain" msgstr "" msgid "Gazebo" msgstr "" msgid "Genie Lamp" msgstr "" msgid "Archer's House" msgstr "" msgid "Goblin Hut" msgstr "" msgid "Dwarf Cottage" msgstr "" msgid "Peasant Hut" msgstr "" msgid "Stables" msgstr "" msgid "Alchemist's Tower" msgstr "" msgid "Event" msgstr "" msgid "Dragon City" msgstr "" msgid "Lighthouse" msgid_plural "Lighthouses" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "Water Wheel" msgid_plural "Water Wheels" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "Monster" msgstr "" msgid "Obelisk" msgstr "" msgid "Oasis" msgstr "" msgid "Resource" msgstr "" msgid "Sawmill" msgstr "" msgid "Oracle" msgstr "" msgid "Shrine of the First Circle" msgstr "" msgid "Shipwreck" msgstr "" msgid "Sea Chest" msgstr "" msgid "Desert Tent" msgstr "" msgid "Stone Liths" msgstr "" msgid "Wagon Camp" msgstr "" msgid "Hut of the Magi" msgstr "" msgid "Whirlpool" msgstr "" msgid "Windmill" msgid_plural "Windmills" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "Artifact" msgstr "" msgid "Mermaid" msgstr "" msgid "Boat" msgstr "" msgid "Random Ultimate Artifact" msgstr "" msgid "Random Artifact" msgstr "" msgid "Random Resource" msgstr "" msgid "Random Monster" msgstr "" msgid "Random Town" msgstr "" msgid "Random Castle" msgstr "" msgid "Eye of the Magi" msgstr "" msgid "Random Monster - weak" msgstr "" msgid "Random Monster - medium" msgstr "" msgid "Random Monster - strong" msgstr "" msgid "Random Monster - very strong" msgstr "" #, fuzzy msgid "Hero" msgstr "Даведацца аб героі" msgid "Nothing Special" msgstr "" msgid "Mossy Rock" msgstr "" msgid "Watch Tower" msgstr "" msgid "Tree House" msgstr "" msgid "Tree City" msgstr "" msgid "Ruins" msgstr "" msgid "Fort" msgstr "" msgid "Abandoned Mine" msgstr "" msgid "Sirens" msgstr "" msgid "Standing Stones" msgstr "" msgid "Idol" msgstr "" msgid "Tree of Knowledge" msgstr "" msgid "Witch Doctor's Hut" msgstr "" msgid "Temple" msgstr "" msgid "Hill Fort" msgstr "" msgid "Halfling Hole" msgstr "" msgid "Mercenary Camp" msgstr "" msgid "Shrine of the Second Circle" msgstr "" msgid "Shrine of the Third Circle" msgstr "" msgid "City of the Dead" msgstr "" msgid "Sphinx" msgstr "" msgid "Wagon" msgstr "" msgid "Tar Pit" msgstr "" msgid "Artesian Spring" msgstr "" msgid "Troll Bridge" msgstr "" msgid "Watering Hole" msgstr "" msgid "Witch's Hut" msgstr "" msgid "Xanadu" msgstr "" msgid "Lean-To" msgstr "" msgid "Magellan's Maps" msgstr "" msgid "Flotsam" msgstr "" msgid "Derelict Ship" msgstr "" msgid "Shipwreck Survivor" msgstr "" msgid "Bottle" msgstr "" msgid "Magic Well" msgstr "" msgid "Magic Garden" msgid_plural "Magic Gardens" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "Observation Tower" msgstr "" msgid "Freeman's Foundry" msgstr "" msgid "Reefs" msgstr "" msgid "Volcano" msgstr "" msgid "Flowers" msgstr "" msgid "Rock" msgstr "" msgid "Water Lake" msgstr "" msgid "Mandrake" msgstr "" msgid "Dead Tree" msgstr "" msgid "Stump" msgstr "" msgid "Crater" msgstr "" msgid "Cactus" msgstr "" msgid "Mound" msgstr "" msgid "Dune" msgstr "" msgid "Lava Pool" msgstr "" msgid "Shrub" msgstr "" msgid "Barrow Mounds" msgstr "" msgid "Random Artifact - Treasure" msgstr "" msgid "Random Artifact - Minor" msgstr "" msgid "Random Artifact - Major" msgstr "" msgid "Barrier" msgstr "" msgid "Traveller's Tent" msgstr "" msgid "Jail" msgstr "" msgid "Fire Summoning Altar" msgstr "" msgid "Air Summoning Altar" msgstr "" msgid "Earth Summoning Altar" msgstr "" msgid "Water Summoning Altar" msgstr "" msgid "Unknown Monster" msgstr "" msgid "Unknown Monsters" msgstr "" msgid "Peasant" msgstr "СелÑнін" msgid "Peasants" msgstr "СÑлÑне" msgid "Archer" msgstr "Лучнік" msgid "Archers" msgstr "Лучнікі" msgid "Ranger" msgstr "Стралок" msgid "Rangers" msgstr "Стралкі" msgid "Pikeman" msgstr "Кап'ёўшчык" msgid "Pikemen" msgstr "Кап'ёўшчыкі" msgid "Veteran Pikeman" msgstr "Бывалы кап'ёўшчык" msgid "Veteran Pikemen" msgstr "Ð‘Ñ‹Ð²Ð°Ð»Ñ‹Ñ ÐºÐ°Ð¿'ёўшчыкі" msgid "Swordsman" msgstr "Мечнік" msgid "Swordsmen" msgstr "Мечнікі" msgid "Master Swordsman" msgstr "МайÑтар мечнік" msgid "Master Swordsmen" msgstr "МайÑтры мечнікі" msgid "Cavalries" msgstr "Коннік" msgid "Cavalry" msgstr "Коннікі" msgid "Champion" msgstr "ЧÑмпіён" msgid "Champions" msgstr "ЧÑмпіёны" msgid "Paladin" msgstr "Паладзін" msgid "Paladins" msgstr "Паладзіны" msgid "Crusader" msgstr "Крыжак" msgid "Crusaders" msgstr "Крыжакі" msgid "Goblin" msgstr "Гоблін" msgid "Goblins" msgstr "Гобліны" msgid "Orc" msgstr "Орк" msgid "Orcs" msgstr "Оркі" msgid "Orc Chief" msgstr "Орк правадыр" msgid "Orc Chiefs" msgstr "Оркі правадыры" msgid "Wolf" msgstr "Воўк" msgid "Wolves" msgstr "Ваўкі" msgid "Ogre" msgstr "Волат" msgid "Ogres" msgstr "Волаты" msgid "Ogre Lord" msgstr "Волат уладар" msgid "Ogre Lords" msgstr "Волаты ўладары" msgid "Troll" msgstr "Троль" msgid "Trolls" msgstr "Тролі" msgid "War Troll" msgstr "ВаÑўнічы троль" msgid "War Trolls" msgstr "ВаÑÑžÐ½Ñ–Ñ‡Ñ‹Ñ Ñ‚Ñ€Ð¾Ð»Ñ–" msgid "Cyclopes" msgstr "Цыклопы" msgid "Cyclops" msgstr "Цыклоп" msgid "Sprite" msgstr "ФеÑ" msgid "Sprites" msgstr "Феі" msgid "Dwarf" msgstr "Гном" msgid "Dwarves" msgstr "Гномы" msgid "Battle Dwarf" msgstr "Гном ваÑўнік" msgid "Battle Dwarves" msgstr "Гномы ваÑўнікі" msgid "Elf" msgstr "Эльф" msgid "Elves" msgstr "Эльфы" msgid "Grand Elf" msgstr "ШлÑхетны Ñльф" msgid "Grand Elves" msgstr "Ð¨Ð»Ñ„Ñ…ÐµÑ‚Ð½Ñ‹Ñ Ñльфы" msgid "Druid" msgstr "Друід" msgid "Druids" msgstr "Друіды" msgid "Greater Druid" msgstr "Старшы друід" msgid "Greater Druids" msgstr "Ð¡Ñ‚Ð°Ñ€ÑˆÑ‹Ñ Ð´Ñ€ÑƒÑ–Ð´Ñ‹" msgid "Unicorn" msgstr "Ðднарог" msgid "Unicorns" msgstr "Ðднарогі" msgid "Phoenix" msgstr "ФенікÑ" msgid "Phoenixes" msgstr "ФенікÑÑ‹" msgid "Centaur" msgstr "Кентаўр" msgid "Centaurs" msgstr "Кентаўры" msgid "Gargoyle" msgstr "ГаргуллÑ" msgid "Gargoyles" msgstr "Гаргуллі" msgid "Griffin" msgstr "Грыфон" msgid "Griffins" msgstr "Грыфоны" msgid "Minotaur" msgstr "Мінатаўр" msgid "Minotaurs" msgstr "Мінатаўры" msgid "Minotaur King" msgstr "Мінатаўр кароль" msgid "Minotaur Kings" msgstr "Мінатаўры каралі" msgid "Hydra" msgstr "Гідра" msgid "Hydras" msgstr "Гідры" msgid "Green Dragon" msgstr "ЗÑлёны цмок" msgid "Green Dragons" msgstr "ЗÑÐ»Ñ‘Ð½Ñ‹Ñ Ñ†Ð¼Ð¾ÐºÑ–" msgid "Red Dragon" msgstr "Чырвоны цмок" msgid "Red Dragons" msgstr "Ð§Ñ‹Ñ€Ð²Ð¾Ð½Ñ‹Ñ Ñ†Ð¼Ð¾ÐºÑ–" msgid "Black Dragon" msgstr "Чорны цмок" msgid "Black Dragons" msgstr "Ð§Ð¾Ñ€Ð½Ñ‹Ñ Ñ†Ð¼Ð¾ÐºÑ–" msgid "Halfling" msgstr "Хобіт" msgid "Halflings" msgstr "Хобіты" msgid "Boar" msgstr "Дзік" msgid "Boars" msgstr "Дзікі" msgid "Iron Golem" msgstr "Жалезны голем" msgid "Iron Golems" msgstr "Ð–Ð°Ð»ÐµÐ·Ð½Ñ‹Ñ Ð³Ð¾Ð»ÐµÐ¼Ñ‹" msgid "Steel Golem" msgstr "Сталёвы голем" msgid "Steel Golems" msgstr "Ð¡Ñ‚Ð°Ð»Ñ‘Ð²Ñ‹Ñ Ð³Ð¾Ð»ÐµÐ¼Ñ‹" msgid "Roc" msgstr "Птушка Рух" msgid "Rocs" msgstr "Птушкі Рух" msgid "Mage" msgstr "Маг" msgid "Magi" msgstr "Магі" msgid "Archmage" msgstr "Ðрхімаг" msgid "Archmagi" msgstr "Ðрхімагі" msgid "Giant" msgstr "Гігант" msgid "Giants" msgstr "Гіганты" msgid "Titan" msgstr "Тытан" msgid "Titans" msgstr "Тытаны" msgid "Skeletons" msgstr "Шкілеты" msgid "Zombie" msgstr "МÑрцвÑк" msgid "Zombies" msgstr "МÑрцвÑкі" msgid "Mutant Zombie" msgstr "Хваравіты мÑрцвÑк" msgid "Mutant Zombies" msgstr "Ð¥Ð²Ð°Ñ€Ð°Ð²Ñ–Ñ‚Ñ‹Ñ Ð¼ÑрцвÑкі" msgid "Mummies" msgstr "Муміі" msgid "Mummy" msgstr "МуміÑ" msgid "Royal Mummies" msgstr "КаралеўÑÐºÑ–Ñ Ð¼ÑƒÐ¼Ñ–Ñ–" msgid "Royal Mummy" msgstr "КаралеўÑÐºÐ°Ñ Ð¼ÑƒÐ¼Ñ–Ñ" msgid "Vampire" msgstr "Вампір" msgid "Vampires" msgstr "Вампіры" msgid "Vampire Lord" msgstr "Лорд вампір" msgid "Vampire Lords" msgstr "Лорды вампіры" msgid "Lich" msgstr "КашчÑй" msgid "Liches" msgstr "КашчÑÑ–" msgid "Power Lich" msgstr "Магутны кашчÑй" msgid "Power Liches" msgstr "ÐœÐ°Ð³ÑƒÑ‚Ð½Ñ‹Ñ ÐºÐ°ÑˆÑ‡ÑÑ–" msgid "Bone Dragon" msgstr "КаÑцÑны цмок" msgid "Bone Dragons" msgstr "КаÑцÑÐ½Ñ‹Ñ Ñ†Ð¼Ð¾ÐºÑ–" msgid "Rogue" msgstr "Ліхадзей" msgid "Rogues" msgstr "Ліхадзеі" msgid "Nomad" msgstr "КачÑўнік" msgid "Nomads" msgstr "КачÑўнікі" msgid "Ghost" msgstr "Здань" msgid "Ghosts" msgstr "Здані" msgid "Genie" msgstr "Джын" msgid "Genies" msgstr "Джыны" msgid "Medusa" msgstr "Медуза" msgid "Medusas" msgstr "Медузы" msgid "Earth Elemental" msgstr "ЗемлÑны Ñлементаль" msgid "Earth Elementals" msgstr "ЗемлÑÐ½Ñ‹Ñ Ñлементалі" msgid "Air Elemental" msgstr "Паветраны Ñлементаль" msgid "Air Elementals" msgstr "ÐŸÐ°Ð²ÐµÑ‚Ñ€Ð°Ð½Ñ‹Ñ Ñлементалі" msgid "Fire Elemental" msgstr "Вогненны Ñлементаль" msgid "Fire Elementals" msgstr "Ð’Ð¾Ð³Ð½ÐµÐ½Ð½Ñ‹Ñ Ñлементалі" msgid "Water Elemental" msgstr "ВадзÑны Ñлементаль" msgid "Water Elementals" msgstr "ВадзÑÐ½Ñ‹Ñ Ñлементалі" msgid "Random Monsters" msgstr "" msgid "Random Monster 1" msgstr "" msgid "Random Monsters 1" msgstr "" msgid "Random Monster 2" msgstr "" msgid "Random Monsters 2" msgstr "" msgid "Random Monster 3" msgstr "" msgid "Random Monsters 3" msgstr "" msgid "Random Monster 4" msgstr "" msgid "Random Monsters 4" msgstr "" msgid "Double shot" msgstr "" msgid "2-hex monster" msgstr "" msgid "Double strike" msgstr "" msgid "Double damage to Undead" msgstr "" msgid "% magic resistance" msgstr "" msgid "Immune to Mind spells" msgstr "" msgid "Immune to Elemental spells" msgstr "" msgid "Immune to Fire spells" msgstr "" msgid "Immune to Cold spells" msgstr "" msgid "Immune to " msgstr "" msgid "% immunity to %{spell} spell" msgstr "" msgid "% damage from Elemental spells" msgstr "" msgid "% chance to Dispel beneficial spells" msgstr "" msgid "% chance to Paralyze" msgstr "" msgid "% chance to Petrify" msgstr "" msgid "% chance to Blind" msgstr "" msgid "% chance to Curse" msgstr "" msgid "% chance to cast %{spell} spell" msgstr "" msgid "HP regeneration" msgstr "" msgid "Two hexes attack" msgstr "" msgid "Flyer" msgstr "" msgid "Always retaliates" msgstr "" msgid "Attacks all adjacent enemies" msgstr "" msgid "No melee penalty" msgstr "" msgid "Dragon" msgstr "" msgid "Undead" msgstr "" msgid "No enemy retaliation" msgstr "" msgid "HP drain" msgstr "" msgid "Cloud attack" msgstr "" msgid "Decreases enemy's morale by " msgstr "" msgid "% chance to halve enemy" msgstr "" msgid "Soul Eater" msgstr "" msgid "Elemental" msgstr "" msgid "No Morale" msgstr "" msgid "200% damage from Fire spells" msgstr "" msgid "200% damage from Cold spells" msgstr "" msgid "% damage from %{spell} spell" msgstr "" msgid "% immunity to " msgstr "" msgid "Lightning" msgstr "" msgid "% damage from " msgstr "" msgid "The three Anduran artifacts magically combine into one." msgstr "" msgid "View Spells" msgstr "ПраглÑдзець загаворы" msgid "View %{name} Info" msgstr "ПраглÑдзець інфармацыю: %{name}" msgid "Move %{name}" msgstr "" msgid "Cannot move the Spellbook" msgstr "" msgid "This item can't be traded." msgstr "" msgid "Invalid Artifact" msgstr "" #, fuzzy msgid "The %{name} increases the hero's knowledge by %{count}." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð°Ð²Ñлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ на %{count} штодзень." msgid "Ultimate Book of Knowledge" msgstr "" #, fuzzy msgid "The %{name} increases the hero's attack skill by %{count}." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð°Ð²Ñлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ на %{count} штодзень." msgid "Ultimate Sword of Dominion" msgstr "" #, fuzzy msgid "The %{name} increases the hero's defense skill by %{count}." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð°Ð²Ñлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ на %{count} штодзень." msgid "Ultimate Cloak of Protection" msgstr "" #, fuzzy msgid "The %{name} increases the hero's spell power by %{count}." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð°Ð²Ñлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ на %{count} штодзень." msgid "Ultimate Wand of Magic" msgstr "" #, fuzzy msgid "" "The %{name} increases the hero's attack and defense skills by %{count} each." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð°Ð²Ñлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ на %{count} штодзень." msgid "Ultimate Shield" msgstr "" #, fuzzy msgid "" "The %{name} increases the hero's spell power and knowledge by %{count} each." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð°Ð²Ñлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ на %{count} штодзень." msgid "Ultimate Staff" msgstr "" #, fuzzy msgid "" "The %{name} increases each of the hero's basic skills by %{count} points." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð°Ð²Ñлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ на %{count} штодзень." msgid "Ultimate Crown" msgstr "" msgid "Golden Goose" msgstr "" msgid "The %{name} brings in an income of %{count} gold per day." msgstr "" msgid "Arcane Necklace of Magic" msgstr "" msgid "" "After rescuing a Sorceress from a cursed tomb, she rewards your heroism with " "an exquisite jeweled necklace." msgstr "" msgid "Caster's Bracelet of Magic" msgstr "" msgid "" "While searching through the rubble of a caved-in mine, you free a group of " "trapped Dwarves. Grateful, the leader gives you a golden bracelet." msgstr "" msgid "Mage's Ring of Power" msgstr "" msgid "" "A cry of pain leads you to a Centaur, caught in a trap. Upon setting the " "creature free, he hands you a small pouch. Emptying the contents, you find a " "dazzling jeweled ring." msgstr "" msgid "Witch's Broach of Magic" msgstr "" msgid "" "Alongside the remains of a burnt witch lies a beautiful broach, intricately " "designed. Approaching the corpse with caution, you add the broach to your " "inventory." msgstr "" msgid "Medal of Valor" msgstr "" #, fuzzy msgid "The %{name} increases the morale of the hero's army by %{count}." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð°Ð²Ñлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ на %{count} штодзень." msgid "" "Freeing a virtuous maiden from the clutches of an evil overlord, you are " "granted a Medal of Valor by the King's herald." msgstr "" msgid "Medal of Courage" msgstr "" msgid "" "After saving a young boy from a vicious pack of Wolves, you return him to " "his father's manor. The grateful nobleman awards you with a Medal of Courage." msgstr "" msgid "Medal of Honor" msgstr "" msgid "" "After freeing a princess of a neighboring kingdom from the evil clutches of " "despicable slavers, she awards you with a Medal of Honor." msgstr "" msgid "Medal of Distinction" msgstr "" msgid "" "Ridding the countryside of the hideous Minotaur who made a sport of eating " "noblemen's Knights, you are honored with the Medal of Distinction." msgstr "" msgid "Fizbin of Misfortune" msgstr "" #, fuzzy msgid "" "The %{name} greatly decreases the morale of the hero's army by %{count}." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð°Ð²Ñлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ на %{count} штодзень." msgid "" "You stumble upon a medal lying alongside the empty road. Adding the medal to " "your inventory, you become aware that you have acquired the undesirable " "Fizbin of Misfortune, greatly decreasing your army's morale." msgstr "" msgid "Thunder Mace of Dominion" msgstr "" msgid "" "During a sudden storm, a bolt of lightning strikes a tree, splitting it. " "Inside the tree you find a mysterious mace." msgstr "" msgid "Armored Gauntlets of Protection" msgstr "" #, fuzzy msgid "The %{name} increase the hero's defense skill by %{count}." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð°Ð²Ñлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ на %{count} штодзень." msgid "" "You encounter the infamous Black Knight! After a grueling duel ending in a " "draw, the Knight, out of respect, offers you a pair of armored gauntlets." msgstr "" msgid "Defender Helm of Protection" msgstr "" msgid "" "A glint of golden light catches your eye. Upon further investigation, you " "find a golden helm hidden under a bush." msgstr "" msgid "Giant Flail of Dominion" msgstr "" msgid "" "A clumsy Giant has killed himself with his own flail. Knowing your superior " "skill with this weapon, you confidently remove the spectacular flail from " "the fallen Giant." msgstr "" msgid "Ballista of Quickness" msgstr "" msgid "The %{name} gives the hero's catapult one extra shot per combat round." msgstr "" msgid "" "Walking through the ruins of an ancient walled city, you find the instrument " "of the city's destruction, an elaborately crafted ballista." msgstr "" msgid "Stealth Shield of Protection" msgstr "" msgid "" "A stone statue of a warrior holds a silver shield. As you remove the shield, " "the statue crumbles into dust." msgstr "" msgid "Dragon Sword of Dominion" msgstr "" msgid "" "As you are walking along a narrow path, a nearby bush suddenly bursts into " "flames. Before your eyes the flames become the image of a beautiful woman. " "She holds out a magnificent sword to you." msgstr "" msgid "Power Axe of Dominion" msgstr "" msgid "" "You see a silver axe embedded deeply in the ground. After several " "unsuccessful attempts by your army to remove the axe, you tightly grip the " "handle of the axe and effortlessly pull it free." msgstr "" msgid "Divine Breastplate of Protection" msgstr "" msgid "" "A gang of Rogues is sifting through the possessions of dead warriors. " "Scaring off the scavengers, you note the Rogues had overlooked a beautiful " "breastplate." msgstr "" msgid "Minor Scroll of Knowledge" msgstr "" msgid "" "Before you appears a levitating glass case with a scroll, perched upon a bed " "of crimson velvet. At your touch, the lid opens and the scroll floats into " "your awaiting hands." msgstr "" msgid "Major Scroll of Knowledge" msgstr "" msgid "" "Visiting a local wiseman, you explain the intent of your journey. He reaches " "into a sack and withdraws a yellowed scroll and hands it to you." msgstr "" msgid "Superior Scroll of Knowledge" msgstr "" msgid "" "You come across the remains of an ancient Druid. Bones, yellowed with age, " "peer from the ragged folds of her robe. Searching the robe, you discover a " "scroll hidden in the folds." msgstr "" msgid "Foremost Scroll of Knowledge" msgstr "" msgid "" "Mangled bones, yellowed with age, peer from the ragged folds of a dead " "Druid's robe. Searching the robe, you discover a scroll hidden within." msgstr "" msgid "Endless Sack of Gold" msgstr "" #, fuzzy msgid "The %{name} provides the hero with %{count} gold per day." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð°Ð²Ñлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ на %{count} штодзень." msgid "" "A little leprechaun dances gleefully around a magic sack. Seeing you " "approach, he stops in mid-stride. The little man screams and stamps his foot " "ferociously, vanishing into thin air. Remembering the old leprechaun saying " "'Finders Keepers', you grab the sack and leave." msgstr "" msgid "Endless Bag of Gold" msgstr "" msgid "" "A noblewoman, separated from her traveling companions, asks for your help. " "After escorting her home, she rewards you with a bag filled with gold." msgstr "" msgid "Endless Purse of Gold" msgstr "" msgid "" "In your travels, you find a leather purse filled with gold that once " "belonged to a great warrior king who had the ability to transform any " "inanimate object into gold." msgstr "" msgid "Nomad Boots of Mobility" msgstr "" #, fuzzy msgid "The %{name} increase the hero's movement on land." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð°Ð²Ñлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ на %{count} штодзень." msgid "" "A Nomad trader seeks protection from a tribe of Goblins. For your " "assistance, he gives you a finely crafted pair of boots made from the " "softest leather. Looking closely, you see fascinating ancient carvings " "engraved on the leather." msgstr "" msgid "Traveler's Boots of Mobility" msgstr "" msgid "" "Discovering a pair of beautifully beaded boots made from the finest and " "softest leather, you thank the anonymous donor and add the boots to your " "inventory." msgstr "" msgid "Lucky Rabbit's Foot" msgstr "" #, fuzzy msgid "The %{name} increases the luck of the hero's army by %{count}." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð°Ð²Ñлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ на %{count} штодзень." msgid "" "A traveling merchant offers you a rabbit's foot, made of gleaming silver " "fur, for safe passage. The merchant explains the charm will increase your " "luck in combat." msgstr "" msgid "Golden Horseshoe" msgstr "" msgid "" "An ensnared Unicorn whinnies in fright. Murmuring soothing words, you set " "her free. Snorting and stamping her front hoof once, she gallops off. " "Looking down you see a golden horseshoe." msgstr "" msgid "Gambler's Lucky Coin" msgstr "" msgid "" "You have captured a mischievous imp who has been terrorizing the region. In " "exchange for his release, he rewards you with a magical coin." msgstr "" msgid "Four-Leaf Clover" msgstr "" msgid "" "In the middle of a patch of dead and dry vegetation, to your surprise you " "find a healthy green four-leaf clover." msgstr "" msgid "True Compass of Mobility" msgstr "" #, fuzzy msgid "The %{name} increases the hero's movement on land and sea." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð°Ð²Ñлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ на %{count} штодзень." msgid "" "An old man claiming to be an inventor asks you to try his latest invention. " "He then hands you a compass." msgstr "" msgid "Sailor's Astrolabe of Mobility" msgstr "" #, fuzzy msgid "The %{name} increases the hero's movement on sea." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð°Ð²Ñлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ на %{count} штодзень." msgid "" "An old sea captain is being tortured by Ogres. You save him, and in return " "he rewards you with a wondrous instrument to measure the distance of a star." msgstr "" msgid "Evil Eye" msgstr "" msgid "The %{name} reduces the casting cost of curse spells by half." msgstr "" msgid "" "While venturing into a decrepit hut you find the Skeleton of a long dead " "witch. Investigation of the remains reveals a glass eye rolling around " "inside an empty skull." msgstr "" msgid "Enchanted Hourglass" msgstr "" msgid "" "The %{name} extends the duration of all the hero's spells by %{count} turns." msgstr "" msgid "" "A surprise turn in the landscape finds you in the midst of a grisly scene: " "Vultures picking at the aftermath of a terrible battle. Your cursory search " "of the remains turns up an enchanted hourglass." msgstr "" msgid "Gold Watch" msgstr "" msgid "The %{name} doubles the effectiveness of the hero's hypnotize spells." msgstr "" msgid "" "In reward for helping his cart out of a ditch, a traveling potion salesman " "gives you a \"magic\" gold watch. Unbeknownst to him, the watch really is " "magical." msgstr "" msgid "Skullcap" msgstr "" msgid "The %{name} halves the casting cost of all mind influencing spells." msgstr "" msgid "" "A brief stop at an improbable rural inn yields an exchange of money, tales, " "and accidentally, luggage. You find a magical skullcap in your new backpack." msgstr "" msgid "Ice Cloak" msgstr "" msgid "" "The %{name} halves all damage the hero's troops receive from cold spells." msgstr "" msgid "" "Responding to the panicked cries of a damsel in distress, you discover a " "young woman fleeing from a hungry bear. You slay the beast in the nick of " "time, and the grateful Sorceress weaves a magic cloak from the bear's hide." msgstr "" msgid "Fire Cloak" msgstr "" msgid "" "The %{name} halves all damage the hero's troops receive from fire spells." msgstr "" msgid "" "You've come upon a fight between a Necromancer and a Paladin. The " "Necromancer blasts the Paladin with a fire bolt, bringing him to his knees. " "Acting quickly, you slay the evil one before the final blow. The grateful " "Paladin gives you the fire cloak that saved him." msgstr "" msgid "Lightning Helm" msgstr "" msgid "" "The %{name} halves all damage the hero's troops receive from lightning " "spells." msgstr "" msgid "" "A traveling tinker in need of supplies offers you a helm with a thunderbolt " "design on its top in exchange for food and water. Curious, you accept, and " "later find out that the helm is magical." msgstr "" msgid "Evercold Icicle" msgstr "" msgid "" "The %{name} causes the hero's cold spells to do %{count} percent more damage " "to enemy troops." msgstr "" msgid "" "An icicle withstanding the full heat of the noonday sun attracts your " "attention. Intrigued, you break it off, and find that it does not melt in " "your hand." msgstr "" msgid "Everhot Lava Rock" msgstr "" msgid "" "The %{name} causes the hero's fire spells to do %{count} percent more damage " "to enemy troops." msgstr "" msgid "" "Your wanderings bring you into contact with a tribe of ape-like beings using " "a magical lava rock that never cools to light their fires. You take pity on " "them and teach them to make fire with sticks. Believing you to be a god, the " "apes give you their rock." msgstr "" msgid "Lightning Rod" msgstr "" msgid "" "The %{name} causes the hero's lightning spells to do %{count} percent more " "damage to enemy troops." msgstr "" msgid "" "While waiting out a storm, a lighting bolt strikes a nearby cottage's " "lightning rod, which melts and falls to the ground. The tip of the rod, " "however, survives intact and makes your hair stand on end when you touch it. " "Hmm..." msgstr "" msgid "Snake-Ring" msgstr "" msgid "The %{name} halves the casting cost of all of the hero's bless spells." msgstr "" msgid "" "You've found an oddly shaped ring on the finger of a long dead traveler. The " "ring looks like a snake biting its own tail." msgstr "" msgid "Ankh" msgstr "" msgid "" "The %{name} doubles the effectiveness of all of the hero's resurrect and " "animate spells." msgstr "" msgid "" "A fierce windstorm reveals the entrance to a buried tomb. Your investigation " "reveals that the tomb has already been looted, but the thieves overlooked an " "ankh on a silver chain in the dark." msgstr "" msgid "Book of Elements" msgstr "" msgid "" "The %{name} doubles the effectiveness of all of the hero's summoning spells." msgstr "" msgid "" "You come across a conjurer who begs to accompany you and your army awhile " "for safety. You agree, and he offers as payment a copy of the book of the " "elements." msgstr "" msgid "Elemental Ring" msgstr "" msgid "The %{name} halves the casting cost of all summoning spells." msgstr "" msgid "" "While pausing to rest, you notice a bobcat climbing a short tree to get at a " "crow's nest. On impulse, you climb the tree yourself and scare off the cat. " "When you look in the nest, you find a collection of shiny stones and a ring." msgstr "" msgid "Holy Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to curse spells." msgstr "" msgid "" "In your wanderings you come across a hermit living in a small, tidy hut. " "Impressed with your mission, he takes time out from his meditations to bless " "and give you a charm against curses." msgstr "" msgid "Pendant of Free Will" msgstr "" msgid "The %{name} makes all of the hero's troops immune to hypnotize spells." msgstr "" msgid "" "Responding to cries for help, you find river Sprites making a sport of " "dunking an old man. Feeling vengeful, you rescue the man and drag a Sprite " "onto dry land for awhile. The Sprite, uncomfortable in the air, gives you a " "magic pendant to let him go." msgstr "" msgid "Pendant of Life" msgstr "" msgid "The %{name} makes all of the hero's troops immune to death spells." msgstr "" msgid "" "A brief roadside encounter with a small caravan and a game of knucklebones " "wins a magic pendant. Its former owner says that it protects from " "Necromancers' death spells." msgstr "" msgid "Serenity Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to berserk spells." msgstr "" msgid "" "The sounds of combat draw you to the scene of a fight between an old " "Barbarian and an eight-headed Hydra. Your timely intervention swings the " "battle in favor of the man, and he rewards you with a pendant he used to use " "to calm his mind for battle." msgstr "" msgid "Seeing-eye Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to blindness spells." msgstr "" msgid "" "You come upon a very old woman, long blind from cataracts and dying alone. " "You tend to her final needs and promise a proper burial. Grateful, she gives " "you a magic pendant emblazoned with a stylized eye. It lets you see with " "your eyes closed." msgstr "" msgid "Kinetic Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to paralyze spells." msgstr "" msgid "" "You come across a golem wearing a glowing pendant and blocking your way. " "Acting on a hunch, you cut the pendant from its neck. Deprived of its power " "source, the golem breaks down, leaving you with the magical pendant." msgstr "" msgid "Pendant of Death" msgstr "" msgid "The %{name} makes all of the hero's troops immune to holy spells." msgstr "" msgid "" "A quick and deadly battle with a Necromancer wins you his magical pendant. " "Later, a Wizard tells you that the pendant protects undead under your " "control from holy word spells." msgstr "" msgid "Wand of Negation" msgstr "" msgid "" "The %{name} makes all of the hero's troops immune to dispel magic spells." msgstr "" msgid "" "You meet an old Wizard friend of yours traveling in the opposite direction. " "He presents you with a gift: A wand that prevents the use of the dispel " "magic spell on your allies." msgstr "" msgid "Golden Bow" msgstr "" msgid "" "The %{name} eliminates the %{count} percent penalty for the hero's troops " "shooting past obstacles (e.g. castle walls)." msgstr "" msgid "" "A chance meeting with a famous Archer finds you in a game of knucklebones " "pitting his bow against your horse. You win." msgstr "" msgid "Telescope" msgstr "" msgid "" "The %{name} increases the amount of terrain the hero reveals when " "adventuring by %{count} extra square." msgstr "" msgid "" "A merchant from far away lands trades you a new invention of his people for " "traveling supplies. It makes distant objects appear closer, and he calls " "it...\n" "\n" "a telescope." msgstr "" msgid "Statesman's Quill" msgstr "" msgid "" "The %{name} reduces the cost of surrender to %{count} percent of the total " "cost of troops the hero has in their army." msgstr "" msgid "" "You pause to help a diplomat with a broken axle fix his problem. In " "gratitude, he gives you a writing quill with magical properties which he " "says will \"help people see things your way\"." msgstr "" msgid "Wizard's Hat" msgstr "" #, fuzzy msgid "" "The %{name} increases the duration of the hero's spells by %{count} turns." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð°Ð²Ñлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ на %{count} штодзень." msgid "" "You see a Wizard fleeing from a Griffin and riding like the wind. The Wizard " "opens a portal and rides through, getting his hat knocked off by the edge of " "the gate. The Griffin follows; the gate closes. You pick the hat up, dust it " "off, and put it on." msgstr "" msgid "Power Ring" msgstr "" msgid "The %{name} returns %{count} extra spell points per day to the hero." msgstr "" msgid "" "You find a small tree that closely resembles the great Warlock Carnauth with " "a ring around one of its twigs. Scraps of clothing and rotting leather lead " "you to suspect that it IS Carnauth, transformed. Since you can't help him, " "you take the magic ring." msgstr "" msgid "Ammo Cart" msgstr "" msgid "" "The %{name} provides endless ammunition for all of the hero's troops that " "shoot." msgstr "" msgid "" "An ammunition cart in the middle of an old battlefield catches your eye. " "Inspection shows it to be in good working order, so you take it along." msgstr "" msgid "Tax Lien" msgstr "" #, fuzzy msgid "The %{name} costs the hero %{count} gold pieces per day." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð°Ð²Ñлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ на %{count} штодзень." msgid "" "Your big spending habits have earned you a massive tax bill that you can't " "hope to pay. The tax man takes pity and agrees to only take 250 gold a day " "from your account for life. Check here if you want one dollar to go to the " "presidential campaign election fund." msgstr "" msgid "Hideous Mask" msgstr "" msgid "The %{name} prevents all 'wandering' armies from joining the hero." msgstr "" msgid "" "Your looting of the grave of Sinfilas Gardolad, the famous shapeshifting " "Warlock, unearths his fabled mask. Trembling, you put it on and it twists " "your visage into an awful grimace! Oh no! It's actually the hideous mask of " "Gromluck Greene, and you are stuck with it." msgstr "" msgid "Endless Pouch of Sulfur" msgstr "" msgid "The %{name} provides %{count} unit of sulfur per day." msgstr "" msgid "" "You visit an alchemist who, upon seeing your army, is swayed by the " "righteousness of your cause. The newly loyal subject gives you his endless " "pouch of sulfur to help with the war effort." msgstr "" msgid "Endless Vial of Mercury" msgstr "" msgid "The %{name} provides %{count} unit of mercury per day." msgstr "" msgid "" "A brief stop at a hastily abandoned Wizard's tower turns up a magical vial " "of mercury that always has a little left on the bottom. Recognizing a " "treasure when you see one, you cap it and slip it in your pocket." msgstr "" msgid "Endless Pouch of Gems" msgstr "" msgid "The %{name} provides %{count} unit of gems per day." msgstr "" msgid "" "A short rainstorm brings forth a rainbow...and you can see the end of it. " "Riding quickly, you seize the pot of gold you find there. The leprechaun who " "owns it, unable to stop you from taking it, offers an endless pouch of gems " "for the return of his gold. You accept." msgstr "" msgid "Endless Cord of Wood" msgstr "" msgid "The %{name} provides %{count} unit of wood per day." msgstr "" msgid "" "Pausing to rest and light a cook fire, you pull wood out of a nearby pile of " "dead wood. As you keep pulling wood from the pile, you notice that it " "doesn't shrink. You realize to your delight that the wood is enchanted, so " "you take it along." msgstr "" msgid "Endless Cart of Ore" msgstr "" msgid "The %{name} provides %{count} unit of ore per day." msgstr "" msgid "" "You've found a Goblin weapon smithy making weapons for use against humans. " "With a tremendous yell you and your army descend upon their camp and drive " "them away. A search finds a magic ore cart that never runs out of iron." msgstr "" msgid "Endless Pouch of Crystal" msgstr "" msgid "The %{name} provides %{count} unit of crystal per day." msgstr "" msgid "" "Taking shelter from a storm in a small cave, you notice a small patch of " "crystal in one corner. Curious, you break a piece off and notice that the " "original crystal grows the lost piece back. You decide to stuff the entire " "patch into a pouch and take it with you." msgstr "" msgid "Spiked Helm" msgstr "" msgid "" "Your army is ambushed by a small tribe of wild (and none too bright) Orcs. " "You fend them off easily and the survivors flee in all directions. One of " "the Orcs was wearing a polished spiked helm. Figuring it will make a good " "souvenir, you take it." msgstr "" msgid "Spiked Shield" msgstr "" msgid "" "You come upon a bridge spanning a dry gully. Before you can cross, a Troll " "steps out from under the bridge and demands payment before it will permit " "you to pass. You refuse, and the Troll charges, forcing you to slay it. You " "take its spiked shield as a trophy." msgstr "" msgid "White Pearl" msgstr "" msgid "" "A walk across a dry saltwater lake bed yields an unlikely prize: A white " "pearl amidst shattered shells and debris." msgstr "" msgid "Black Pearl" msgstr "" msgid "" "Rumors of a Griffin of unusual size preying upon the countryside lead you to " "its cave lair. A quick, brutal fight dispatches the beast, and a search of " "its foul nest turns up a huge black pearl." msgstr "" msgid "Magic Book" msgstr "" msgid "The %{name} enables the hero to cast spells." msgstr "" msgid "Dummy 1" msgstr "" msgid "The reserved artifact." msgstr "" msgid "Dummy 2" msgstr "" msgid "Dummy 3" msgstr "" msgid "Dummy 4" msgstr "" msgid "Spell Scroll" msgstr "" msgid "" "This %{name} gives the hero the ability to cast the %{spell} spell if the " "hero has a Magic Book." msgstr "" msgid "" "You find an elaborate container which houses an old vellum scroll. The runes " "on the container are very old, and the artistry with which it was put " "together is stunning. As you pull the scroll out, you feel imbued with " "magical power." msgstr "" msgid "Arm of the Martyr" msgstr "" #, fuzzy msgid "" "The %{name} increases the hero's spell power by %{count} but adds the undead " "morale penalty." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð°Ð²Ñлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ на %{count} штодзень." msgid "" "One of the less intelligent members of your party picks up an arm off of the " "ground. Despite its missing a body, it is still moving. Your troops find the " "dismembered arm repulsive, but you cannot bring yourself to drop it: it " "seems to hold some sort of magical power that influences your decision " "making." msgstr "" msgid "Breastplate of Anduran" msgstr "" #, fuzzy msgid "The %{name} increases the hero's defense by %{count}." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð°Ð²Ñлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ на %{count} штодзень." msgid "" "You come upon a sign. It reads: \"Here lies the body of Anduran. Bow and " "swear fealty, and you shall be rewarded.\" You decide to do as it says. As " "you stand up, you feel a coldness against your skin. Looking down, you find " "that you are suddenly wearing a gleaming, ornate breastplate." msgstr "" msgid "Broach of Shielding" msgstr "" msgid "" "The %{name} provides %{count} percent protection from Armageddon and " "Elemental Storm, but decreases spell power by 2." msgstr "" msgid "" "A kindly Sorceress thinks that your army's defenses could use a magical " "boost. She offers to enchant the Broach that you wear on your cloak, and you " "accept." msgstr "" msgid "Battle Garb of Anduran" msgstr "" msgid "" "The %{name} combines the powers of the three Anduran artifacts. It provides " "maximum luck and morale for the hero's troops and gives the hero the Town " "Portal spell." msgstr "" msgid "" "Out of pity for a poor peasant, you purchase a chest of old junk they are " "hawking for too much gold. Later, as you search through it, you find it " "contains the 3 pieces of the legendary battle garb of Anduran!" msgstr "" msgid "Crystal Ball" msgstr "" msgid "" "The %{name} lets the hero get more specific information about monsters, " "enemy heroes, and castles nearby the hero." msgstr "" msgid "" "You come upon a caravan of gypsies who are feasting and fortifying their " "bodies with mead. They call you forward and say \"If you prove that you can " "dance the Rama-Buta, we will reward you.\" You don't know it, but try " "anyway. They laugh hysterically, but admire your bravery, giving you a " "Crystal Ball." msgstr "" msgid "Heart of Fire" msgstr "" msgid "" "The %{name} provides %{count} percent protection from fire, but doubles the " "damage taken from cold." msgstr "" msgid "" "You enter a recently burned glade and come upon a Fire Elemental sitting " "atop a rock. It looks up, its flaming face contorted in a look of severe " "pain. It then tosses a glowing object at you. You put up your hands to block " "it, but it passes right through them and sears itself into your chest." msgstr "" msgid "Heart of Ice" msgstr "" msgid "" "The %{name} provides %{count} percent protection from cold, but doubles the " "damage taken from fire." msgstr "" msgid "" "Suddenly, a biting coldness engulfs your body. You seize up, falling from " "your horse. The pain subsides, but you still feel as if your chest is " "frozen. As you pick yourself up off of the ground, you hear hearty laughter. " "You turn around just in time to see a Frost Giant run off into the woods and " "disappear." msgstr "" msgid "Helmet of Anduran" msgstr "" msgid "" "You spy a gleaming object poking up out of the ground. You send a member of " "your party over to investigate. He comes back with a golden helmet in his " "hands. You realize that it must be the helmet of the legendary Anduran, the " "only man who was known to wear solid gold armor." msgstr "" msgid "Holy Hammer" msgstr "" msgid "" "You come upon a battle where a Paladin has been mortally wounded by a group " "of Zombies. He asks you to take his hammer and finish what he started. As " "you pick it up, it begins to hum, and then everything becomes a blur. The " "Zombies lie dead, the hammer dripping with blood. You strap it to your belt." msgstr "" msgid "Legendary Scepter" msgstr "" msgid "The %{name} adds %{count} points to all attributes." msgstr "" msgid "" "Upon cresting a small hill, you come upon a ridiculous looking sight. A " "Sprite is attempting to carry a Scepter that is almost as big as it is. " "Trying not to laugh, you ask, \"Need help?\" The Sprite glares at you and " "answers: \"You think this is funny? Fine. You can carry it. I much prefer " "flying anyway.\"" msgstr "" msgid "" "An old seaman tells you a tale of an enchanted masthead that he used in his " "youth to rally his crew during times of trouble. He then hands you a faded " "map that shows where he hid it. After much exploring, you find it stashed " "underneath a nearby dock." msgstr "" msgid "Masthead" msgstr "" #, fuzzy msgid "" "The %{name} boosts the hero's troops' luck and morale by %{count} each in " "sea combat." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð°Ð²Ñлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ на %{count} штодзень." msgid "Sphere of Negation" msgstr "" msgid "The %{name} disables all spell casting, for both sides, in combat." msgstr "" msgid "" "You stop to help a Peasant catch a runaway mare. To show his gratitude, he " "hands you a tiny sphere. As soon as you grasp it, you feel the magical " "energy drain from your limbs..." msgstr "" msgid "Staff of Wizardry" msgstr "" #, fuzzy msgid "The %{name} boosts the hero's spell power by %{count}." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð°Ð²Ñлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ на %{count} штодзень." msgid "" "While out scaring up game, your troops find a mysterious staff levitating " "about three feet off of the ground. They hand it to you, and you notice an " "inscription. It reads: \"Brains best brawn and magic beats might. Heed my " "words, and you'll win every fight.\"" msgstr "" msgid "Sword Breaker" msgstr "" #, fuzzy msgid "The %{name} increases the hero's defense by %{count} and attack by 1." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ð°Ð²Ñлічвае гарадcÐºÑ–Ñ Ð·Ð±Ð¾Ñ€Ñ‹ на %{count} штодзень." msgid "" "A former Captain of the Guard admires your quest and gives you the enchanted " "Sword Breaker that he relied on during his tour of duty." msgstr "" msgid "Sword of Anduran" msgstr "" msgid "" "A Troll stops you and says: \"Pay me 5,000 gold, or the Sword of Anduran " "will slay you where you stand.\" You refuse. The troll grabs the sword " "hanging from its belt, screams in pain, and runs away. Picking up the fabled " "sword, you give thanks that half-witted Trolls tend to grab the wrong end of " "sharp objects." msgstr "" msgid "Spade of Necromancy" msgstr "" msgid "The %{name} gives the hero increased necromancy skill." msgstr "" msgid "" "A dirty shovel has been thrust into a dirt mound nearby. Upon investigation, " "you discover it to be the enchanted shovel of the Gravediggers, long thought " "lost by mortals." msgstr "" msgid "spellBonus|selected by user" msgstr "" msgid "Wood" msgstr "" msgid "Mercury" msgstr "" msgid "Ore" msgstr "" msgid "Sulfur" msgstr "" msgid "Crystal" msgstr "" msgid "Gems" msgstr "" msgid "Gold" msgstr "" msgid "" "There are seven resources in Heroes 2, used to build and improves castles, " "purchase troops and recruit heroes. Gold is the most common, required for " "virtually everything. Wood and ore are used for most buildings. Gems, " "Mercury, Sulfur and Crystal are rare magical resources used for the most " "powerful creatures and buildings." msgstr "" msgid "" "Causes a giant fireball to strike the selected area, damaging all nearby " "creatures." msgstr "" msgid "Fireball" msgstr "" msgid "Fireblast" msgstr "" msgid "" "An improved version of fireball, fireblast affects two hexes around the " "center point of the spell, rather than one." msgstr "" msgid "Causes a bolt of electrical energy to strike the selected creature." msgstr "" msgid "Lightning Bolt" msgstr "" msgid "Chain Lightning" msgstr "" msgid "" "Causes a bolt of electrical energy to strike a selected creature, then " "strike the nearest creature with half damage, then strike the NEXT nearest " "creature with half again damage, and so on, until it becomes too weak to be " "harmful. Warning: This spell can hit your own creatures!" msgstr "" msgid "Teleport" msgstr "" msgid "" "Teleports the creature you select to any open position on the battlefield." msgstr "" msgid "Cure" msgstr "" msgid "" "Removes all negative spells cast upon one of your units, and restores up to " "%{count} HP per level of spell power." msgstr "" msgid "Mass Cure" msgstr "" msgid "" "Removes all negative spells cast upon your forces, and restores up to " "%{count} HP per level of spell power, per creature." msgstr "" msgid "Resurrect" msgstr "" msgid "Resurrects creatures from a damaged or dead unit until end of combat." msgstr "" msgid "Resurrect True" msgstr "" msgid "Resurrects creatures from a damaged or dead unit permanently." msgstr "" msgid "Haste" msgstr "" msgid "Increases the speed of any creature by %{count}." msgstr "" msgid "Increases the speed of all of your creatures by %{count}." msgstr "" msgid "Mass Haste" msgstr "" msgid "Slows target to half movement rate." msgstr "" msgid "spell|Slow" msgstr "" msgid "Mass Slow" msgstr "" msgid "Slows all enemies to half movement rate." msgstr "" msgid "Clouds the affected creatures' eyes, preventing them from moving." msgstr "" msgid "spell|Blind" msgstr "" msgid "Bless" msgstr "" msgid "Causes the selected creatures to inflict maximum damage." msgstr "" msgid "Causes all of your units to inflict maximum damage." msgstr "" msgid "Mass Bless" msgstr "" msgid "Magically increases the defense skill of the selected creatures." msgstr "" msgid "Stoneskin" msgstr "" msgid "" "Increases the defense skill of the targeted creatures. This is an improved " "version of Stoneskin." msgstr "" msgid "Steelskin" msgstr "" msgid "Causes the selected creatures to inflict minimum damage." msgstr "" msgid "Curse" msgstr "" msgid "Causes all enemy troops to inflict minimum damage." msgstr "" msgid "Mass Curse" msgstr "" msgid "Damages all undead in the battle." msgstr "" msgid "Holy Word" msgstr "" msgid "" "Damages all undead in the battle. This is an improved version of Holy Word." msgstr "" msgid "Holy Shout" msgstr "" msgid "Anti-Magic" msgstr "" msgid "Prevents harmful magic against the selected creatures." msgstr "" msgid "Dispel Magic" msgstr "" msgid "Removes all magic spells from a single target." msgstr "" msgid "Mass Dispel" msgstr "" msgid "Removes all magic spells from all creatures." msgstr "" msgid "Causes a magic arrow to strike the selected target." msgstr "" msgid "Magic Arrow" msgstr "" msgid "Berserker" msgstr "" msgid "Causes a creature to attack its nearest neighbor." msgstr "" msgid "Armageddon" msgstr "" msgid "" "Holy terror strikes the battlefield, causing severe damage to all creatures." msgstr "" msgid "Elemental Storm" msgstr "" msgid "Magical elements pour down on the battlefield, damaging all creatures." msgstr "" msgid "" "A rain of rocks strikes an area of the battlefield, damaging all nearby " "creatures." msgstr "" msgid "Meteor Shower" msgstr "" msgid "Paralyze" msgstr "" msgid "The targeted creatures are paralyzed, unable to move or retaliate." msgstr "" msgid "Hypnotize" msgstr "" msgid "" "Brings a single enemy unit under your control if its hits are less than " "%{count} times the caster's spell power." msgstr "" msgid "Cold Ray" msgstr "" msgid "Drains body heat from a single enemy unit." msgstr "" msgid "Cold Ring" msgstr "" msgid "" "Drains body heat from all units surrounding the center point, but not " "including the center point." msgstr "" msgid "Disrupting Ray" msgstr "" msgid "Reduces the defense rating of an enemy unit by three." msgstr "" msgid "Damages all living (non-undead) units in the battle." msgstr "" msgid "Death Ripple" msgstr "" msgid "Death Wave" msgstr "" msgid "" "Damages all living (non-undead) units in the battle. This spell is an " "improved version of Death Ripple." msgstr "" msgid "Dragon Slayer" msgstr "" msgid "Greatly increases a unit's attack skill vs. Dragons." msgstr "" msgid "Blood Lust" msgstr "" msgid "Increases a unit's attack skill." msgstr "" msgid "Animate Dead" msgstr "" msgid "Resurrects creatures from a damaged or dead undead unit permanently." msgstr "" msgid "Mirror Image" msgstr "" msgid "" "Creates an illusionary unit that duplicates one of your existing units. This " "illusionary unit does the same damages as the original, but will vanish if " "it takes any damage." msgstr "" msgid "Shield" msgstr "" msgid "" "Halves damage received from ranged attacks for a single unit. Does not " "affect damage received from Turrets or Ballistae." msgstr "" msgid "Mass Shield" msgstr "" msgid "" "Halves damage received from ranged attacks for all of your units. Does not " "affect damage received from Turrets or Ballistae." msgstr "" msgid "Summon Earth Elemental" msgstr "" msgid "Summons Earth Elementals to fight for your army." msgstr "" msgid "Summon Air Elemental" msgstr "" msgid "Summons Air Elementals to fight for your army." msgstr "" msgid "Summon Fire Elemental" msgstr "" msgid "Summons Fire Elementals to fight for your army." msgstr "" msgid "Summon Water Elemental" msgstr "" msgid "Summons Water Elementals to fight for your army." msgstr "" msgid "Damages castle walls." msgstr "" msgid "Earthquake" msgstr "" msgid "Causes all mines across the land to become visible." msgstr "" msgid "View Mines" msgstr "" msgid "Causes all resources across the land to become visible." msgstr "" msgid "View Resources" msgstr "" msgid "Causes all artifacts across the land to become visible." msgstr "" msgid "View Artifacts" msgstr "" msgid "Causes all towns and castles across the land to become visible." msgstr "" msgid "View Towns" msgstr "" msgid "Causes all Heroes across the land to become visible." msgstr "" msgid "View Heroes" msgstr "" msgid "Causes the entire land to become visible." msgstr "" msgid "View All" msgstr "" msgid "Allows the caster to view detailed information on enemy Heroes." msgstr "" msgid "Summon Boat" msgstr "" msgid "" "Summons the nearest unoccupied, friendly boat to an adjacent shore location. " "A friendly boat is one which you just built or were the most recent player " "to occupy." msgstr "" msgid "Allows the caster to magically transport to a nearby location." msgstr "" msgid "Dimension Door" msgstr "" msgid "Returns the caster to any town or castle currently owned." msgstr "" msgid "Town Gate" msgstr "" msgid "" "Returns the hero to the town or castle of choice, provided it is controlled " "by you." msgstr "" msgid "Visions" msgstr "" msgid "" "Visions predicts the likely outcome of an encounter with a neutral army camp." msgstr "" msgid "Haunt" msgstr "" msgid "" "Haunts a mine you control with Ghosts. This mine stops producing resources. " "(If I can't keep it, nobody will!)" msgstr "" msgid "Set Earth Guardian" msgstr "" msgid "Sets Earth Elementals to guard a mine against enemy armies." msgstr "" msgid "Set Air Guardian" msgstr "" msgid "Sets Air Elementals to guard a mine against enemy armies." msgstr "" msgid "Set Fire Guardian" msgstr "" msgid "Sets Fire Elementals to guard a mine against enemy armies." msgstr "" msgid "Set Water Guardian" msgstr "" msgid "Sets Water Elementals to guard a mine against enemy armies." msgstr "" #, fuzzy msgid "Random Spell" msgstr "" "Выпад-\n" "ковы" msgid "Randomly selected spell of any level." msgstr "" msgid "Random 1st Level Spell" msgstr "" msgid "Randomly selected 1st level spell." msgstr "" msgid "Random 2nd Level Spell" msgstr "" msgid "Randomly selected 2nd level spell." msgstr "" msgid "Random 3rd Level Spell" msgstr "" msgid "Randomly selected 3rd level spell." msgstr "" msgid "Random 4th Level Spell" msgstr "" msgid "Randomly selected 4th level spell." msgstr "" msgid "Random 5th Level Spell" msgstr "" msgid "Randomly selected 5th level spell." msgstr "" msgid "Petrification" msgstr "" msgid "" "Turns the affected creature into stone. A petrified creature receives half " "damage from a direct attack." msgstr "" msgid "You have no Magic Book, so you cannot cast a spell." msgstr "" msgid "No spell to cast." msgstr "" msgid "Your hero has %{point} spell points remaining." msgstr "" msgid "View Adventure Spells" msgstr "" msgid "View Combat Spells" msgstr "" msgid "View previous page" msgstr "" msgid "View next page" msgstr "" msgid "Close Spellbook" msgstr "" msgid "View %{spell}" msgstr "" msgid "This spell does %{damage} points of damage." msgstr "" msgid "" "This spell summons\n" "%{count} %{monster}." msgstr "" msgid "This spell restores %{hp} HP." msgstr "" msgid "This spell summons %{count} %{monster} to guard the mine." msgstr "" msgid "The nearest town is %{town}." msgstr "" msgid "This town is occupied by your hero %{hero}." msgstr "" msgid "" "This spell controls up to\n" "%{hp} HP." msgstr "" msgid "The ultimate artifact is really the %{name}." msgstr "" msgid "The ultimate artifact may be found in the %{name} regions of the world." msgstr "" msgid "north-west" msgstr "" msgid "north" msgstr "" msgid "north-east" msgstr "" msgid "west" msgstr "" msgid "center" msgstr "" msgid "east" msgstr "" msgid "south-west" msgstr "" msgid "south" msgstr "" msgid "south-east" msgstr "" msgid "The truth is out there." msgstr "" msgid "The dark side is stronger." msgstr "" msgid "The end of the world is near." msgstr "" msgid "The bones of Lord Slayer are buried in the foundation of the arena." msgstr "" msgid "A Black Dragon will take out a Titan any day of the week." msgstr "" msgid "He told her: Yada yada yada... and then she said: Blah, blah, blah..." msgstr "" msgid "An unknown force is being resurrected..." msgstr "" msgid "" "Check the newest version of the game at\n" "https://github.com/ihhub/\n" "fheroes2/releases" msgstr "" #, fuzzy #~ msgid "Castles" #~ msgstr "Замак" #~ msgid "vs" #~ msgstr "Ñупраць" fheroes2-1.0.12+dfsg/files/lang/bg.po000066400000000000000000014047141456075706000172270ustar00rootroot00000000000000# Bulgarian translation of fheroes2 # Copyright (C) 2022 fheroes2 team # This file is distributed under the same license as the fheroes2 package. # msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-03 03:27+0000\n" "PO-Revision-Date: 2022-11-08 16:44+0000\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" "Language: bg\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.0.1\n" msgid "" "BATTLE\n" "ONLY" msgstr "" "СÐМО\n" "БИТКÐ" msgid "" "NEW\n" "GAME" msgstr "" "ÐОВÐ\n" "ИГРÐ" msgid "" "SAVE\n" "GAME" msgstr "" "ЗÐПÐЗИ\n" "ИГРÐ" msgid "" "LOAD\n" "GAME" msgstr "" "ЗÐРЕДИ\n" "ИГРÐ" msgid "INFO" msgstr "ИÐФО" msgid "QUIT" msgstr "ИЗХОД" msgid "CANCEL" msgstr "ОТКÐЗ" msgid "OKAY" msgstr "ДОБРЕ" msgid "smallerButton|OKAY" msgstr "ДОБРЕ" msgid "ACCEPT" msgstr "ПРИЕМÐМ" msgid "DECLINE" msgstr "ОТКÐЗВÐМ" msgid "LEARN" msgstr "ÐÐУЧИ" msgid "TRADE" msgstr "СДЕЛКÐ" msgid "YES" msgstr "ДÐ" msgid "NO" msgstr "ÐЕ" msgid "EXIT" msgstr "ИЗХОД" msgid "smallerButton|EXIT" msgstr "ИЗХОД" msgid "DISMISS" msgstr "ОТПРÐТИ" msgid "UPGRADE" msgstr "ЗÐСИЛИ" msgid "RESTART" msgstr "РЕСТÐРТ" msgid "HEROES" msgstr "ГЕРОЙ" msgid "" "TOWNS/\n" "CASTLES" msgstr "" "ГРÐДОВЕ/\n" "ЗÐМЪЦИ" msgid "S" msgstr "" msgid "M" msgstr "" msgid "L" msgstr "" msgid "X-L" msgstr "" msgid "ALL" msgstr "" msgid "SELECT" msgstr "ИЗБЕРИ" msgid "" "STANDARD\n" "GAME" msgstr "" "ÐОРМÐЛÐÐ\n" "ИГРÐ" msgid "" "CAMPAIGN\n" "GAME" msgstr "" "ИГРÐЙ\n" "ИСТОРИЯ" msgid "" "MULTI-\n" "PLAYER\n" "GAME" msgstr "" "ИГРРС\n" "ПОВЕЧЕ\n" "ИГРÐЧИ" msgid "CONFIG" msgstr "" "КОÐФИ-\n" "ГУРÐЦИЯ" msgid "" "ORIGINAL\n" "CAMPAIGN" msgstr "" "ÐÐЧÐЛÐÐ\n" "ИСТОРИЯ" msgid "" "EXPANSION\n" "CAMPAIGN" msgstr "ЕКСПÐÐЗИЯ" msgid "HOT SEAT" msgstr "ОБЩ СТОЛ" msgid "2 PLAYERS" msgstr "2 ИГРÐЧÐ" msgid "3 PLAYERS" msgstr "3 ИГРÐЧÐ" msgid "4 PLAYERS" msgstr "4 ИГРÐЧÐ" msgid "5 PLAYERS" msgstr "5 ИГРÐЧÐ" msgid "6 PLAYERS" msgstr "6 ИГРÐЧÐ" msgid "GIFT" msgstr "ДÐРИ" msgid "MAX" msgstr "МÐКС" msgid "DIFFICULTY" msgstr "ТРУДÐОСТ" msgid "VIEW INTRO" msgstr "ВИЖ ИÐТРО" msgid "MIN" msgstr "МИÐ" msgid "RESET" msgstr "" #, fuzzy msgid "START" msgstr "РЕСТÐРТ" #, fuzzy msgid "CASTLE" msgstr "" "ГРÐДОВЕ/\n" "ЗÐМЪЦИ" msgid "TOWN" msgstr "" msgid "" "D\n" "I\n" "S\n" "M\n" "I\n" "S\n" "S" msgstr "" "О\n" "Т\n" "П\n" "Р\n" "Ð\n" "Т\n" "И" msgid "" "E\n" "X\n" "I\n" "T" msgstr "" "И\n" "З\n" "Ð¥\n" "О\n" "Д" msgid "" "C\n" "A\n" "M\n" "P\n" "A\n" "I\n" "G\n" "N" msgstr "" "К\n" "Ð\n" "М\n" "П\n" "Ð\n" "Ð\n" "И\n" "Я" msgid "" "S\n" "T\n" "A\n" "N\n" "D\n" "A\n" "R\n" "D" msgstr "" "С\n" "Т\n" "Ð\n" "Ð\n" "Д\n" "Ð\n" "Р\n" "Т\n" "Е\n" "Ð" msgid "Warrior" msgstr "Войн" msgid "Builder" msgstr "Строител" msgid "Explorer" msgstr "ИзÑледовател" msgid "None" msgstr "Без" msgid "" "A few\n" "%{monster}" msgstr "" "Малко\n" "%{monster}" msgid "" "Several\n" "%{monster}" msgstr "" "ÐÑколко\n" "%{monster}" msgid "" "A pack of\n" "%{monster}" msgstr "" "Глутница\n" "%{monster}" msgid "" "Lots of\n" "%{monster}" msgstr "" "Много\n" "%{monster}" msgid "" "A horde of\n" "%{monster}" msgstr "" "Орда от\n" "%{monster}" msgid "" "A throng of\n" "%{monster}" msgstr "" "Тълпа от\n" "%{monster}" msgid "" "A swarm of\n" "%{monster}" msgstr "" "РоÑк от\n" "%{monster}" msgid "" "Zounds...\n" "%{monster}" msgstr "" "Стотици\n" "%{monster}" msgid "" "A legion of\n" "%{monster}" msgstr "" "Легион от\n" "%{monster}" msgid "army|Few" msgstr "Малко" msgid "army|Several" msgstr "ÐÑколко" msgid "army|Pack" msgstr "Глутница" msgid "army|Lots" msgstr "Много" msgid "army|Horde" msgstr "Орда" msgid "army|Throng" msgstr "Тълпа" msgid "army|Swarm" msgstr "РоÑк" msgid "army|Zounds" msgstr "Стотици" msgid "army|Legion" msgstr "Легион" msgid "All %{race} troops +1" msgstr "Ð’Ñички %{race} войÑки +1" msgid "Multiple" msgstr "Многократни" msgid "Troops of %{count} alignments -%{penalty}" msgstr "ВойÑки от %{count} подравнÑÐ²Ð°Ð½Ð¸Ñ -%{penalty}" #, fuzzy msgid "Some undead in army -1" msgstr "ÐÑкои немъртви в група -1" msgid "View %{name}" msgstr "Виж %{name}" msgid "Move the %{name} " msgstr "ПремеÑти %{name} " msgid "Move or right click to redistribute %{name}" msgstr "ПремеÑти или щракни Ñ Ð´ÐµÑен бутон, за да разпределиш %{name}" msgid "Combine %{name} armies" msgstr "Комбинирай %{name} заедно" msgid "Exchange %{name2} with %{name}" msgstr "Размени %{name2} Ñ %{name}" msgid "Select %{name}" msgstr "Избери %{name}" msgid "Cannot move last troop" msgstr "Ðе може да премеÑти поÑÐ»ÐµÐ´Ð½Ð¸Ñ Ð¾Ñ‚Ñ€Ñд" msgid "Move the %{name}" msgstr "ПремеÑти %{name}" msgid "Set Count" msgstr "Задаване на броÑ" msgid "%{name} destroys half the enemy troops!" msgid_plural "%{name} destroy half the enemy troops!" msgstr[0] "%{name} унищожава половината вражеÑки войÑки!" msgstr[1] "%{name} унищожи половината вражеÑки войÑки!" msgid "%{name} has turned off the auto battle" msgstr "%{name} е изключил автоматичната битка" msgid "%{name} has turned on the auto battle" msgstr "%{name} включи автоматичната битка" msgid "Spell failed!" msgstr "Заклинанието е неуÑпешно!" msgid "" "The Sphere of Negation artifact is in effect for this battle, disabling all " "combat spells." msgstr "" "Ðртефактът Сфера на отрицанието е в Ñила за тази битка, дезактивирайки " "вÑички бойни заклинаниÑ." msgid "You have already cast a spell this round." msgstr "Вече Ñи направил Ð¼Ð°Ð³Ð¸Ñ Ñ‚Ð¾Ð·Ð¸ рунд." msgid "That spell will have no effect!" msgstr "Това заклинание нÑма да има ефект!" msgid "You may only summon one type of elemental per combat." msgstr "Можеш да призовеш Ñамо един тип елементал за битка." msgid "" "There is no open space adjacent to your hero where you can summon an " "Elemental to." msgstr "" "ÐÑма открито проÑтранÑтво до Ñ‚Ð²Ð¾Ñ Ð³ÐµÑ€Ð¾Ð¹, където можеш да призовеш Елементал." msgid "" "The Moat reduces by -%{count} the defense skill of any unit and slows to " "half movement rate." msgstr "" "Ровът намалÑва Ñ -%{count} умението за защита на вÑÑка единица и Ð·Ð°Ð±Ð°Ð²Ñ " "наполовина ÑкороÑтта на движение." msgid "Speed: %{speed}" msgstr "СкороÑÑ‚: %{speed}" msgid "Speed" msgstr "СкороÑÑ‚" msgid "Off" msgstr "Изкл" msgid "On" msgstr "Вкл" msgid "Turn Order" msgstr "Ред на завъртане" msgid "Auto Spell Casting" msgstr "Ðвтоматично заклинание" msgid "Grid" msgstr "Решетка" msgid "Shadow Movement" msgstr "Движение на Ñенките" msgid "Shadow Cursor" msgstr "КурÑор в ÑÑнка" msgid "Audio" msgstr "Звукови" msgid "Settings" msgstr "ÐаÑтойки" msgid "Configure" msgstr "ÐаÑтрой" msgid "Hot Keys" msgstr "Бързи клавиши" msgid "Damage Info" msgstr "Инфо за щетите" msgid "Set the speed of combat actions and animations." msgstr "Задай ÑкороÑтта на бойните дейÑÑ‚Ð²Ð¸Ñ Ð¸ анимациите." msgid "Toggle to display the turn order during the battle." msgstr "Превключи, за да покажеш реда на ход по време на битката." msgid "" "Toggle whether or not the computer will cast spells for you when auto combat " "is on. (Note: This does not affect spell casting for computer players in any " "way, nor does it affect quick combat.)" msgstr "" "Превключи дали компютърът да прави Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ Ð²Ð¼ÐµÑто теб, когато " "автоматичната битка е включена. (Забележка: Това не влиÑе по никакъв начин " "на правенето на Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ Ð·Ð° компютърни играчи, нито заÑÑга бързата битка.)" msgid "" "Toggle the hex grid on or off. The hex grid always underlies movement, even " "if turned off. This switch only determines if the grid is visible." msgstr "" "Включи или изключи шеÑтнадеÑетичната мрежа. ШеÑтнадеÑетичната мрежа винаги е " "в оÑновата на движението, дори и да е изключена. Този ключ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»Ñ Ñамо " "дали решетката е видима." msgid "" "Toggle on or off shadows showing where your creatures can move and attack." msgstr "" "Включи или изключи Ñенките, показващи къде твойте ÑъщеÑтва могат да Ñе " "движат и атакуват." msgid "" "Toggle on or off a shadow showing the current hex location of the mouse " "cursor." msgstr "" "Включи или изключи ÑÑнка, показваща текущото шеÑтнадеÑетично меÑтоположение " "на курÑора на мишката." msgid "Change the audio settings of the game." msgstr "Промени звуковите наÑтройки на играта." msgid "Check and configure all the hot keys present in the game." msgstr "Провери и конфигурирай вÑички горещи клавиши в играта." msgid "Toggle to display damage information during the battle." msgstr "Превключи за показване на Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° щетите по време на битката." msgid "Exit this menu." msgstr "Излез от това меню." msgid "Okay" msgstr "Добре" msgid "The enemy has surrendered!" msgstr "Врагът Ñе предаде!" msgid "Their cowardice costs them %{gold} gold." msgstr "" msgid "The enemy has fled!" msgstr "Врагът побÑгва!" msgid "A glorious victory!" msgstr "Славна победа!" msgid "For valor in combat, %{name} receives %{exp} experience." msgstr "За храброÑÑ‚ в битката %{name} получава %{exp} опит." msgid "The cowardly %{name} flees from battle." msgstr "СтрахливиÑÑ‚ %{name} бÑга от битката." msgid "%{name} surrenders to the enemy, and departs in shame." msgstr "%{name} Ñе предава на врага и Ñи тръгва заÑрамен." #, fuzzy msgid "Your forces suffer a bitter defeat, and %{name} abandons your cause." msgstr "" "Твойте Ñили претърпÑват горчиво поражение и %{name} изоÑÑ‚Ð°Ð²Ñ ÐºÐ°ÑƒÐ·Ð°Ñ‚Ð° ти." #, fuzzy msgid "Your forces suffer a bitter defeat." msgstr "Твойте Ñили претърпÑват горчиво поражение." msgid "Battlefield Casualties" msgstr "Жертви на бойното поле" msgid "Attacker" msgstr "Ðападател" msgid "Defender" msgstr "Защитник" #, fuzzy msgid "Click to leave the battle results." msgstr "Кликни, за да приложиш избраната резолюциÑ." #, fuzzy msgid "Click to restart the battle in manual mode." msgstr "Кликни, за да Ñе върнеш към главното меню." msgid "Restart" msgstr "РеÑтарт" msgid "You have captured an enemy artifact!" msgstr "Ти Ñи заловил вражеÑки артефакт!" msgid "As you reach for the %{name}, it mysteriously disappears." msgstr "Когато поÑÑгаш към %{name}, той миÑтериозно изчезва." msgid "As your enemy reaches for the %{name}, it mysteriously disappears." msgstr "Когато врагът ти поÑÑга към %{name}, той миÑтериозно изчезва." msgid "Necromancy!" msgstr "ÐекромантиÑ!" msgid "" "Practicing the dark arts of necromancy, you are able to raise %{count} of " "the enemy's dead to return under your service as %{monster}." msgstr "" "Практикувайки тъмните изкуÑтва на некромантиÑта, можш да Ñъбереш %{count} от " "мъртвите на врага, за да Ñе върнат под твое командване като %{monster}." msgid "%{name} the %{race}" msgstr "%{name} %{race}" msgid "Captain of %{name}" msgstr "Капитан на %{name}" msgid "Attack" msgstr "Ðтака" msgid "Defense" msgstr "Защита" msgid "Spell Power" msgstr "МагиÑ" msgid "Knowledge" msgstr "Знание" msgid "Morale" msgstr "Морал" msgid "Luck" msgstr "КъÑмет" msgid "Spell Points" msgstr "МагиÑ" msgid "Hero's Options" msgstr "Опции на героÑ" msgid "Cast Spell" msgstr "Ðаправи заклинание" msgid "Retreat" msgstr "ОтÑтъпление" msgid "Surrender" msgstr "Предаване" msgid "Cancel" msgstr "ОтменÑм" msgid "Hero Screen" msgstr "Екран на героÑ" msgid "Captain's Options" msgstr "Опции на героÑ" msgid "" "Cast a magical spell. You may only cast one spell per combat round. The " "round is reset when every creature has had a turn." msgstr "" "Ðаправи магичеÑко заклинание. Можеш да правиш Ñамо едно заклинание на боен " "рунд. Рундът Ñе нулира, когато вÑÑко ÑъщеÑтво е имало ход." msgid "" "Retreat your hero, abandoning your creatures. Your hero will be available " "for you to recruit again, however, the hero will have only a novice hero's " "forces." msgstr "" "Оттегли ÑÐ²Ð¾Ñ Ð³ÐµÑ€Ð¾Ð¹, изоÑтавÑйки Ñвоите ÑъзданиÑ. ТвоÑÑ‚ герой ще бъде на " "разположение, за да го наемш отново, но героÑÑ‚ ще разполага Ñамо ÑÑŠÑ Ñилите " "на начинаещ герой." #, fuzzy msgid "" "Surrendering costs gold. However if you pay the ransom, the hero and all of " "his or her surviving creatures will be available to recruit again. The cost " "of surrender is half of the total cost of the non-temporary troops remaining " "in the army." msgstr "" "Предаването ще Ñтрува злато. Ðко обаче платиш откупа, героÑÑ‚ и вÑички негови " "или нейни оцелели ÑъщеÑтва ще бъдат на разположение за наемане отново." msgid "Open Hero Screen to view full information about the hero." msgstr "Отвори екрана на героÑ, за да видиш пълната Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° героÑ." msgid "Return to the battle." msgstr "Върни Ñе в битката." msgid "Not enough gold (%{gold})" msgstr "ÐÑма доÑтатъчно злато (%{gold})" msgid "%{name} states:" msgstr "%{name} заÑвÑва:" msgid "Captain of %{name} states:" msgstr "Капитанът на %{name} заÑвÑва:" msgid "" "\"I will accept your surrender and grant you and your troops safe passage " "for the price of %{price} gold.\"" msgstr "" "\"Ще приема капитулациÑта ти и ще оÑÐ¸Ð³ÑƒÑ€Ñ Ð½Ð° теб и войÑките ти безопаÑно " "преминаване на цената на %{price} злато.\"" msgid "View %{monster} info" msgstr "Виж Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° %{monster} " msgid "Fly %{monster} here" msgstr "Прелети %{monster} тук" msgid "Move %{monster} here" msgstr "ПремеÑти %{monster} тук" msgid "Shoot %{monster}" msgstr "ЗаÑтрелÑй %{monster}" msgid "(1 shot left)" msgid_plural "(%{count} shots left)" msgstr[0] "(ОÑтава 1 изÑтрел)" msgstr[1] "(ОÑтават %{count} изÑтрела)" msgid "Attack %{monster}" msgstr "Ðападни %{monster}" msgid "Turn %{turn}" msgstr "Ход %{turn}" msgid "Teleport here" msgstr "Телепортирай Ñе тук" msgid "Invalid teleport destination" msgstr "ÐеÑъщеÑтвуващо мÑÑто за телепортиране" msgid "Cast %{spell} on %{monster}" msgstr "Ðаправи %{spell} върху %{monster}" msgid "Cast %{spell}" msgstr "Ðаправи %{spell}" msgid "Select spell target" msgstr "Избери цел за заклинание" msgid "View Ballista info" msgstr "Виж Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° Катапулт" msgid "Ballista" msgstr "Катапулта" msgid "Enable auto combat" msgstr "Включи на автоматична битка" msgid "Allows the computer to fight out the battle for you." msgstr "ПозволÑва на компютъра да води битката вмеÑто теб." msgid "Auto Combat" msgstr "Ðвто Сражение" msgid "Customize system options" msgstr "ПерÑонализирай ÑиÑтемните опции" msgid "Allows you to customize the combat screen." msgstr "ПозволÑва ти да перÑонализираш Ð±Ð¾Ð¹Ð½Ð¸Ñ ÐµÐºÑ€Ð°Ð½." msgid "System Options" msgstr "СиÑтемни наÑтройки" msgid "Skip this unit" msgstr "ПропуÑни тази единица" msgid "Skip" msgstr "ПропуÑни" msgid "" "Skips the current creature. The current creature ends its turn and does not " "get to go again until the next round." msgstr "" "ПропуÑка текущото Ñъздание. Текущото Ñъздание завършва реда Ñи и не може да " "отиде отново до ÑÐ»ÐµÐ´Ð²Ð°Ñ‰Ð¸Ñ ÐºÑ€ÑŠÐ³." msgid "View Captain's options" msgstr "Виж наÑтройките на героÑ" msgid "View Hero's options" msgstr "Виж наÑтройките на героÑ" msgid "View opposing Captain" msgstr "Виж Ð¿Ñ€Ð¾Ñ‚Ð¸Ð²Ð½Ð¸ÐºÐ¾Ð²Ð¸Ñ Ð³ÐµÑ€Ð¾Ð¹" msgid "View opposing Hero" msgstr "Виж Ð¿Ñ€Ð¾Ñ‚Ð¸Ð²Ð½Ð¸ÐºÐ¾Ð²Ð¸Ñ Ð³ÐµÑ€Ð¾Ð¹" msgid "Hide logs" msgstr "Скрий региÑтрационни файлове" msgid "Show logs" msgstr "Показжи региÑтрационни файлове" msgid "Message Bar" msgstr "Лента за ÑъобщениÑ" msgid "Shows the results of individual monster's actions." msgstr "Покажи резултатите от дейÑтвиÑта на отделните чудовища." msgid "Are you sure you want to finish the battle in auto mode?" msgstr "Сигурен ли Ñи че иÑкаш да презапишеш запазената игра Ñ Ñ‚Ð¾Ð²Ð° име?" msgid "Are you sure you want to enable auto combat?" msgstr "Сигурен ли Ñи, че иÑкаш да активираш автоматичните битки?" msgid "%{name} skip their turn." msgstr "%{name} пропуÑкат ÑÐ²Ð¾Ñ Ñ€ÐµÐ´." msgid "%{attacker} does %{damage} damage." msgid_plural "%{attacker} do %{damage} damage." msgstr[0] "%{attacker} нанаÑÑ %{damage} щети." msgstr[1] "%{attacker} нанаÑÑÑ‚ %{damage} щети." msgid "1 creature perishes." msgid_plural "%{count} creatures perish." msgstr[0] "1 ÑъщеÑтво загива." msgstr[1] "%{count} ÑъщеÑтва загиват." msgid "1 %{defender} perishes." msgid_plural "%{count} %{defender} perish." msgstr[0] "1 %{defender} загива." msgstr[1] "%{count} %{defender} загиват." msgid "1 soul is incorporated." msgid_plural "%{count} souls are incorporated." msgstr[0] "1 душа е включена." msgstr[1] "%{count} души Ñа включени." msgid "1 %{unit} is revived." msgid_plural "%{count} %{unit} are revived." msgstr[0] "1 %{unit} е Ñъживена." msgstr[1] "%{count} %{unit} Ñа Ñъживени." msgid "Moved %{monster}: from [%{src}] to [%{dst}]." msgstr "ПремеÑтен %{monster}: %{src}, %{dst}" msgid "The %{name} resist the spell!" msgstr "%{name} уÑÑ‚Ð¾Ñ Ð½Ð° заклинанието!" msgid "%{name} casts %{spell} on the %{troop}." msgstr "%{name} Ñ…Ð²ÑŠÑ€Ð»Ñ %{spell} върху %{troop}." msgid "%{name} casts %{spell}." msgstr "%{name} прави %{spell}." msgid "The %{spell} does %{damage} damage to one undead creature." msgstr "%{spell} нанаÑÑ %{damage} щети на едно немъртво ÑъщеÑтво." msgid "The %{spell} does %{damage} damage to all undead creatures." msgstr "%{spell} нанаÑÑ %{damage} щети на вÑички немъртви ÑъщеÑтва." msgid "The %{spell} does %{damage} damage, %{count} creatures perish." msgstr "%{spell} нанаÑÑ %{damage} щети, %{count} ÑъщеÑтва загиват." msgid "The %{spell} does %{damage} damage." msgstr "%{spell} нанаÑÑ %{damage} щети." msgid "The %{spell} does %{damage} damage to one living creature." msgstr "%{spell} нанаÑÑ %{damage} щети на едно живо ÑъщеÑтво." msgid "The %{spell} does %{damage} damage to all living creatures." msgstr "%{spell} нанаÑÑ %{damage} щети на вÑички живи ÑъщеÑтва." msgid "The %{attacker}' attack blinds the %{target}!" msgstr "Ðападението на %{attacker} заÑлепÑва %{target}!" msgid "The %{attacker}' gaze turns the %{target} to stone!" msgstr "Погледът на %{attacker} превръща %{target} в камък!" msgid "The %{attacker}' curse falls upon the %{target}!" msgstr "ПроклÑтието %{attacker} пада върху %{target}!" msgid "The %{target} are paralyzed by the %{attacker}!" msgstr "%{target} Ñа парализирани от %{attacker}!" msgid "The %{attacker} dispel all good spells on your %{target}!" msgstr "%{attacker} разÑейва вÑички добри Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ Ð½Ð° Ñ‚Ð²Ð¾Ñ %{target}!" msgid "The %{attacker} cast %{spell} on %{target}!" msgstr "%{attacker} направи %{spell} на %{target}!" msgid "Bad luck descends on the %{attacker}." msgstr "ЛошиÑÑ‚ къÑмет Ñе Ñтоварва върху %{attacker}." msgid "Good luck shines on the %{attacker}." msgstr "КъÑметът Ñе уÑмихва на %{attacker}." msgid "High morale enables the %{monster} to attack again." msgstr "ВиÑокиÑÑ‚ морал позволÑва на %{monster} да атакува отново." msgid "Low morale causes the %{monster} to freeze in panic." msgstr "ÐиÑкиÑÑ‚ морал кара %{monster} да замръзне от паника." msgid "%{tower} does %{damage} damage." msgstr "%{tower} нанаÑÑ %{damage} щети." msgid "The mirror image is created." msgstr "Създава Ñе огледален образ." msgid "The mirror image is destroyed!" msgstr "ОгледалниÑÑ‚ образ е унищожен!" msgid "Are you sure you want to interrupt the auto battle?" msgstr "Сигурен ли Ñте, че иÑкате да Ñе отеглите?" msgid "Error" msgstr "Грешка" msgid "No spells to cast." msgstr "ÐÑма магии за правене." msgid "Are you sure you want to retreat?" msgstr "Сигурен ли Ñте, че иÑкате да Ñе отеглите?" msgid "Retreat disabled" msgstr "ОтеглÑнето е деактивирано" msgid "Surrender disabled" msgstr "Предаването е деактивирано" msgid "Damage: %{max}" msgstr "Щети: %{max}" msgid "Damage: %{min} - %{max}" msgstr "Щети: %{min} - %{max}" msgid "Perish: %{max}" msgstr "Загиват: %{max}" msgid "Perish: %{min} - %{max}" msgstr "Загиват: %{min} - %{max}" msgid "" "Through eagle-eyed observation, %{name} is able to learn the magic spell " "%{spell}." msgstr "" "Чрез наблюдение Ñ Ð¾Ñ€Ð»Ð¾Ð²Ð¸ очи, %{name} уÑпÑва да научи магичеÑкото заклинание " "%{spell}." msgid "Human" msgstr "Човек" msgid "AI" msgstr "ИИ" #, fuzzy msgid "Start" msgstr "РеÑтарт" #, fuzzy msgid "Start the battle." msgstr "Върни Ñе в битката." msgid "Exit" msgstr "Изход" msgid "Reset" msgstr "" msgid "Reset to default settings." msgstr "" msgid "Please select another hero." msgstr "МолÑ, избери друг герой." msgid "Set Attack Skill" msgstr "Задайте умение за нападение" msgid "Set Defense Skill" msgstr "Задайте умение за защита" msgid "Set Power Skill" msgstr "Задайте умение за Ñила" msgid "Set Knowledge Skill" msgstr "Задайте умение за знание" #, fuzzy msgid "%{race1} %{name1} vs %{race2} %{name2}" msgstr "%{name1} Ñреща %{name2}" msgid "Monsters" msgstr "Чудовища" msgid "N/A" msgstr "Без" msgid "Left Turret" msgstr "ЛÑва кула" msgid "Right Turret" msgstr "ДÑÑна кула" msgid "The %{name} fires with the strength of %{count} Archers" msgstr "%{name} ÑÑ‚Ñ€ÐµÐ»Ñ ÑÑŠÑ Ñилата на %{count} Ñтрелци" msgid "each with a +%{attack} bonus to their attack skill." msgstr "вÑеки получава +%{attack} Ð±Ð¾Ð½ÑƒÑ ÐºÑŠÐ¼ Ñвоето умение за нападение." msgid "The %{name} is destroyed." msgstr "%{name} е унищожен!" msgid "%{count} %{name} rises from the dead!" msgid_plural "%{count} %{name} rise from the dead!" msgstr[0] "%{count} %{name} възкръÑна от мъртвите!" msgstr[1] "%{count} %{name} възкръÑнаха от мъртвите!" msgid "Dwarven Alliance" msgstr "Съюзът на джуджетата" msgid "Sorceress Guild" msgstr "ГилдиÑта на магьоÑниците" msgid "Necromancer Guild" msgstr "ГилдиÑта на некромантите" msgid "Ogre Alliance" msgstr "Съюз на Огре" msgid "Dwarfbane" msgstr "Джуджеубиец" msgid "Dragon Alliance" msgstr "Драконов Ñъюз" msgid "Elven Alliance" msgstr "ЕлфичеÑки Ñъюз" msgid "Kraeger defeated" msgstr "Крегер е победен" msgid "Wayward Son" msgstr "Своенравен Ñин" msgid "Uncle Ivan" msgstr "Чичо Иван" msgid "Annexation" msgstr "ÐнекÑирам" msgid "Force of Arms" msgstr "ÐаÑилÑтвено" msgid "Save the Dwarves" msgstr "СпаÑи джуджетата" msgid "Carator Mines" msgstr "Мините на Каратор" msgid "Turning Point" msgstr "Повратна точка" msgid "scenarioName|Defender" msgstr "Защитник" msgid "Corlagon's Defense" msgstr "Защитата на Корлагон" msgid "The Crown" msgstr "Короната" msgid "The Gauntlet" msgstr "Под кръÑтоÑан огън" msgid "Betrayal" msgstr "ПредателÑтво" msgid "Final Justice" msgstr "ПоÑледна ÑправедливоÑÑ‚" msgid "" "Roland needs you to defeat the lords near his castle to begin his war of " "rebellion against his brother. They are not allied with each other, so they " "will spend most of their time fighting with one another. Victory is yours " "when you have defeated all of their castles and heroes." msgstr "" "Роланд Ñе нуждае от теб, за да победите лордовете близо до замъка му и да " "започне ÑвоÑÑ‚ бунт Ñрещу брат Ñи. Те не Ñа Ñъюзници, така че през по-" "голÑмата чаÑÑ‚ от времето ще Ñа в битка един Ñрещу друг. Победата ще е твоÑ, " "когато победиш вÑичките им замъци и герои." msgid "" "The local lords refuse to swear allegiance to Roland, and must be subdued. " "They are wealthy and powerful, so be prepared for a tough fight. Capture all " "enemy castles to win." msgstr "" "МеÑтните лордове отказват да Ñе закълнат във вÑрноÑÑ‚ на Роланд и трÑбва да " "бъдат покорени. Те Ñа богати и могъщи, така че бъди готов за тежка битка. " "Превземи вÑички вражеÑки замъци, за да Ñпечелищ." msgid "" "Your task is to defend the Dwarves against Archibald's forces. Capture all " "of the enemy towns and castles to win, and be sure not to lose all of the " "dwarf towns at once, or the enemy will have won." msgstr "" "ТвоÑта задача е да защитиш джуджетата Ñрещу Ñилите на Ðрчибалд. Превземи " "вÑички вражеÑки градове и замъци, за да Ñпечелиш и бъди Ñигурен, че нÑма да " "загубиш вÑички градове на джуджетата наведнъж, или врагът ще е победил." msgid "" "You will face four allied enemies in a straightforward fight for resource " "and treasure. Capture all of the enemy castles for victory." msgstr "" "Ще Ñе изправиш Ñрещу четирима ÑъюзничеÑки врагове в директна битка за " "реÑурÑи и Ñъкровища. Превземи вÑички вражеÑки замъци за победа." msgid "" "Your enemies are allied against you and start close by, so be ready to come " "out fighting. You will need to own all four castles in this small valley to " "win." msgstr "" "Твойте врагове Ñа Ñъюзени Ñрещу теб и нападат отблизо, така че бъди готов да " "излезеш в битка. Ще трÑбва да притежаваш и четирите замъка в тази малка " "долина, за да Ñпечелиш." msgid "" "The Sorceress' guild of Noraston has requested Roland's aid against an " "attack from Archibald's allies. Capture all of the enemy castles to win, and " "don't lose Noraston, or you'll lose the scenario. (Hint: There is an enemy " "castle on an island in the ocean.)" msgstr "" "ГилдиÑта на магьоÑниците от ÐораÑтън е поиÑкала помощта на Роланд Ñрещу " "нападението от Ñъюзниците на Ðрчибалд. Превземи вÑички вражеÑки замъци, за " "да Ñпечелиш и не губи ÐораÑтон, или ще загубиш ÑценариÑ. (Съвет: Има " "вражеÑки замък на оÑтров в океана.)" msgid "" "Gather as large an army as possible and capture the enemy castle within 8 " "weeks. You are opposed by only one enemy, but must travel a long way to get " "to the enemy castle. Any troops you have in your army at the end of this " "scenario will be with you in the final battle." msgstr "" "Събери възможно най-голÑма Ð°Ñ€Ð¼Ð¸Ñ Ð¸ превземи вражеÑÐºÐ¸Ñ Ð·Ð°Ð¼ÑŠÐº в рамките на 8 " "Ñедмици. Ти Ñе противопоÑтавÑш Ñамо на един враг, но трÑбва да изминеш дълъг " "път, за да Ñтигнеш до вражеÑÐºÐ¸Ñ Ð·Ð°Ð¼ÑŠÐº. Ð’Ñички войÑки, които имаш в ÑвоÑта " "Ð°Ñ€Ð¼Ð¸Ñ Ð² ÐºÑ€Ð°Ñ Ð½Ð° този Ñценарий, ще бъдат Ñ Ñ‚ÐµÐ± в поÑледната битка." msgid "" "Find the Crown before Archibald's heroes find it. Roland will need the Crown " "for the final battle against Archibald." msgstr "" "Ðамери короната, преди героите на Ðрчибалд да Ñ Ð½Ð°Ð¼ÐµÑ€ÑÑ‚. Роланд ще Ñе нуждае " "от короната за поÑледната битка Ñрещу Ðрчибалд." msgid "" "Three allied enemies stand before you and victory, including Lord Corlagon. " "Roland is in a castle to the northwest, and you will lose if he falls to the " "enemy. Remember that capturing Lord Corlagon will ensure that he will not " "fight against you in the final scenario." msgstr "" "Трима ÑъюзничеÑки врагове ÑтоÑÑ‚ между теб и победата, включително лорд " "Корлагон. Роланд е в замък на Ñеверозапад и ти ще загубиш, ако той загуби от " "врага. Ðе забравÑй, че залавÑнето на лорд Корлагон ще гарантира, че той нÑма " "да Ñе бие Ñрещу теб в поÑÐ»ÐµÐ´Ð½Ð¸Ñ Ñценарий." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Archibald to end the war!" msgstr "" "Това е поÑледната битка. И ти и врагът Ñте въоръжени до зъби и вÑички Ñа " "обединени Ñрещу ваÑ. Хванете Ðрчибалд, за да Ñложите край на войната!" msgid "" "Switching sides leaves you with three castles against the enemy's one. This " "battle will be the easiest one you will face for the rest of the war..." "traitor." msgstr "" "СмÑната на отбора те оÑÑ‚Ð°Ð²Ñ Ñ Ñ‚Ñ€Ð¸ замъка Ñрещу ÐµÐ´Ð¸Ð½Ð¸Ñ Ð½Ð° врага. Тази битка " "ще бъде най-леÑната, Ñ ÐºÐ¾Ñто ще Ñе ÑблъÑкаш до ÐºÑ€Ð°Ñ Ð½Ð° войната... предател." msgid "Barbarian Wars" msgstr "ВарварÑки войни" msgid "First Blood" msgstr "Първа кръв" msgid "Necromancers" msgstr "Ðекроманти" msgid "Slay the Dwarves" msgstr "Убий джуджетата" msgid "Country Lords" msgstr "СелÑки гоÑподари" msgid "Dragon Master" msgstr "ГоÑподар на драконите" msgid "Rebellion" msgstr "Бунт" msgid "Apocalypse" msgstr "ÐпокалипÑиÑ" msgid "Greater Glory" msgstr "По-голÑма Ñлава" msgid "" "King Archibald requires you to defeat the three enemies in this region. They " "are not allied with one another, so they will spend most of their energy " "fighting amongst themselves. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Крал Ðрчибалд изиÑква от теб да победиш тримата врагове в този регион. Те не " "Ñа Ñъюзници помежду Ñи, така че ще изразходват по-голÑмата чаÑÑ‚ от енергиÑта " "Ñи в битки помежду Ñи. Ще Ñпечелиш, когато притежаваш вÑички вражеÑки замъци " "и нÑма повече герои, които да Ñе биÑÑ‚." msgid "" "You must unify the barbarian tribes of the north by conquering them. As in " "the previous mission, the enemy is not allied against you, but they have " "more resources at their disposal. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "ТрÑбва да обединиш варварÑките племена на Ñевер, като ги завладееш. Както и " "в предишната миÑиÑ, врагът не ти е противник, но има повече реÑурÑи на Ñвое " "разположение. Ще Ñпечелиш, когато притежаваш вÑички вражеÑки замъци и нÑма " "повече герои, които да Ñе биÑÑ‚." msgid "" "Do-gooder wizards have taken the Necromancers' castle. You must retake it to " "achieve victory. Remember that while you start with a powerful army, you " "have no castle and must take one within 7 days, or lose this battle. (Hint: " "The nearest castle is to the southeast.)" msgstr "" "Добрите магьоÑници Ñа превзели замъка на Ðекромантите. ТрÑбва да Ñи го " "върнеш обратно, за да поÑтигнеш победа. Ðе забравÑй, че макар да започваш Ñ " "мощна армиÑ, ти нÑмаш замък и трÑбва да превземеш такъв в рамките на 7 дни " "или губиш тази битка. (Съвет: Ðай-близкиÑÑ‚ замък е на югоизток.)" msgid "" "The dwarves need conquering before they can interfere in King Archibald's " "plans. Roland's forces have more than one hero and many towns to start with, " "so be ready for attack from multiple directions. You must capture all of the " "enemy towns and castles to claim victory." msgstr "" "Джуджетата Ñе нуждаÑÑ‚ от победа, преди да могат да Ñе намеÑÑÑ‚ в плановете на " "крал Ðрчибалд. Силите на Роланд разполагат Ñ Ð¿Ð¾Ð²ÐµÑ‡Ðµ от един герой и много " "градове, така че бъди готов за атака от различни поÑоки. ТрÑбва да превземеш " "вÑички вражеÑки градове и замъци, за да победиш." msgid "" "You must put down a peasant revolt led by Roland's forces. All are allied " "against you, but you have Lord Corlagon, an experienced hero, to help you. " "Capture all enemy castles to win." msgstr "" "ТрÑбва да потушиш ÑелÑко въÑтание, водено от Ñилите на Роланд. Ð’Ñички Ñа " "Ñъюзници Ñрещу теб, но имаш лорд Корлагон, опитен герой, който да ти помага. " "Превземи вÑички вражеÑки замъци, за да Ñпечелиш." msgid "" "There are two enemies allied against you in this mission. Both are well " "armed and seek to evict you from their island. Avoid them and capture Dragon " "City to win." msgstr "" "Има двама врагове, обединени Ñрещу теб в тази миÑиÑ. И двамата Ñа добре " "въоръжени и Ñе ÑтремÑÑ‚ да те изгонÑÑ‚ от Ñ‚ÐµÑ…Ð½Ð¸Ñ Ð¾Ñтров. ИзбÑгвай ги и " "превземи Градът на Драконите, за да Ñпечелиш." msgid "" "Your orders are to conquer the country lords that have sworn to serve " "Roland. All of the enemy castles are unified against you. Since you start " "without a castle, you must hurry to capture one before the end of the week. " "Capture all enemy castles for victory." msgstr "" "Твойте заповеди Ñа да завладееш ÑелÑките гоÑподари, които Ñа Ñе заклели да " "Ñлужат на Роланд. Ð’Ñички вражеÑки замъци Ñа обединени Ñрещу теб. Тъй като " "започваш без замък, трÑбва да побързаш да првземеш един преди ÐºÑ€Ð°Ñ Ð½Ð° " "Ñедмицата. Превземи вÑички вражеÑки замъци за победа." msgid "" "Find the Crown before Roland's heroes find it. Archibald will need the Crown " "for the final battle against Roland." msgstr "" "Ðамери короната, преди героите на Роланд да Ñ Ð½Ð°Ð¼ÐµÑ€ÑÑ‚. Ðрчибалд ще Ñе нуждае " "от короната за поÑледната битка Ñрещу Роланд." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Roland to win the war, and be sure " "not to lose Archibald in the fight!" msgstr "" "Това е поÑледната битка. И ти, и врагът Ñте въоръжени до зъби и вÑички Ñа " "обединени Ñрещу теб. Хвани Роланд, за да Ñпечелиш войната и бъди Ñигурен, че " "нÑма да загубиш Ðрчибалд в битката!" msgid "Arrow's Flight" msgstr "Полетът на Ñтрелата" msgid "Island of Chaos" msgstr "ОÑтров на хаоÑа" msgid "The Abyss" msgstr "Бездната" msgid "Uprising" msgstr "Ð’ÑŠÑтание" msgid "Aurora Borealis" msgstr "Северно СиÑние" msgid "Betrayal's End" msgstr "КраÑÑ‚ на предателÑтвото" msgid "Corruption's Heart" msgstr "Сърцето на корупциÑта" msgid "The Giant's Pass" msgstr "Проходът на великана" msgid "" "Subdue the unruly local lords in order to provide the Empire with facilities " "to operate in this region." msgstr "" "Покори непокорните меÑтни лордове, за да оÑигуриш на ИмпериÑта ÑÑŠÐ¾Ñ€ÑŠÐ¶ÐµÐ½Ð¸Ñ Ð·Ð° " "работа в този регион." msgid "" "Eliminate all opposition in this area. Then the first piece of the artifact " "will be yours." msgstr "" "Премахни вÑÑка Ð¾Ð¿Ð¾Ð·Ð¸Ñ†Ð¸Ñ Ð² тази облаÑÑ‚. След това първото парче от артефакта " "ще бъде твое." msgid "" "The sorceresses to the northeast are rebelling! For the good of the empire " "you must quash their feeble uprising on your way to the mountains." msgstr "" "МагьоÑниците на Ñевероизток Ñе бунтуват! За доброто на империÑта трÑбва да " "потушиш Ñлабото им въÑтание по Ð¿ÑŠÑ‚Ñ Ñи към планините." msgid "" "Having prepared for your arrival, Kraeger has arranged for a force of " "necromancers to thwart your quest. You must capture the castle of Scabsdale " "before the first day of the third week, or the Necromancers will be too " "strong for you." msgstr "" "След като Ñе е подготвил за вашето приÑтигане, Крегер е уредил Ñила от " "некроманти да оÑуети твоето търÑене. ТрÑбва да превземеш замъка СкабÑдейл " "преди Ð¿ÑŠÑ€Ð²Ð¸Ñ Ð´ÐµÐ½ на третата Ñедмица, или некромантите ще бъдат твърде Ñилни " "за теб." msgid "" "The barbarian despot in this area is, as yet, ignorant of your presence. " "Quickly, build up your forces before you are discovered and attacked! Secure " "the region by subduing all enemy forces." msgstr "" "ВарварÑкиÑÑ‚ деÑпот в тази облаÑÑ‚ вÑе още не знае за твоето приÑÑŠÑтвие. " "Бързо, Ñъбери Ñилите Ñи, преди да бъдеш открит и нападнат! ОÑигури региона, " "като подчиниш вÑички вражеÑки Ñили." msgid "" "The Empire is weak in this region. You will be unable to completely subdue " "all forces in this area, so take what you can before reprisal strikes. " "Remember, your true goal is to claim the Helmet of Anduran." msgstr "" "ИмпериÑта е Ñлаба в този регион. ÐÑма да можеш да покориш напълно вÑички " "Ñили в тази облаÑÑ‚, така че вземи каквото можеш, преди да те ударÑÑ‚ ответни " "удари. Ðе забравÑй, че иÑтинÑката ти цел е да вземеш шлема на Ðндуран." msgid "For the good of the Empire, eliminate Kraeger." msgstr "За доброто на ИмпериÑта елиминирай Крегер." msgid "" "At last, you have the opportunity and the facilities to rid the Empire of " "the necromancer's evil. Eradicate them completely, and you will be sung as a " "hero for all time." msgstr "" "Ðай-Ð½Ð°ÐºÑ€Ð°Ñ Ð¸Ð¼Ð°Ñˆ възможноÑтта и ÑъоръжениÑта да отървеш ИмпериÑта от злото на " "некроманта. Изкорени ги напълно и ще бъдеш възпÑÑ‚ като герой завинаги." msgid "Border Towns" msgstr "Погранични градове" msgid "Conquer and Unify" msgstr "Покори и обедини" msgid "Crazy Uncle Ivan" msgstr "Ð›ÑƒÐ´Ð¸Ñ Ñ‡Ð¸Ñ‡Ð¾ Иван" msgid "The Wayward Son" msgstr "СвоенравниÑÑ‚ Ñин" msgid "Ivory Gates" msgstr "Портите на Ðйвъри" msgid "The Elven Lands" msgstr "ЕлфичеÑките земи" msgid "The Epic Battle" msgstr "Епичната битка" msgid "The Southern War" msgstr "Южната война" msgid "" "Conquer and unite all the enemy tribes. Don't lose the hero Jarkonas, the " "forefather of all descendants." msgstr "" "Покори и обедини вÑички вражеÑки племена. Ðе губи Ð³ÐµÑ€Ð¾Ñ Ð”Ð¶Ð°Ñ€ÐºÐ¾Ð½Ð°Ñ, праотецът " "на вÑички потомци." msgid "" "Your rival, the Kingdom of Harondale, is attacking weak towns on your " "border! Recover from their first strike and crush them completely!" msgstr "" "ТвоÑÑ‚ Ñъперник, КралÑтво Харондейл, атакува Ñлаби градове на твоÑта граница! " "ВъзÑтанови Ñе от Ð¿ÑŠÑ€Ð²Ð¸Ñ Ð¸Ð¼ удар и ги Ñмажи напълно!" msgid "" "Find your wayward son Joseph who is rumored to be living in the desolate " "lands. Do it before the first day of the third month or it will be of no " "help to your family." msgstr "" "Ðамери ÑÐ²Ð¾Ñ Ñвоенравен Ñин Джоузеф, за когото Ñе говори, че живее в пуÑтите " "земи. Ðаправи го преди Ð¿ÑŠÑ€Ð²Ð¸Ñ Ð´ÐµÐ½ на Ñ‚Ñ€ÐµÑ‚Ð¸Ñ Ð¼ÐµÑец или нÑма да е от полза за " "твоето ÑемейÑтво." msgid "" "Rescue your crazy uncle Ivan. Find him before the first day of the fourth " "month or it will be no help to your kingdom." msgstr "" "СпаÑи Ð»ÑƒÐ´Ð¸Ñ Ñи чичо Иван. Ðамери го преди Ð¿ÑŠÑ€Ð²Ð¸Ñ Ð´ÐµÐ½ на Ñ‡ÐµÑ‚Ð²ÑŠÑ€Ñ‚Ð¸Ñ Ð¼ÐµÑец или " "той нÑма да е от полза за твоето кралÑтво." msgid "" "Destroy the barbarians who are attacking the southern border of your " "kingdom! Recover your fallen towns, and then invade the jungle kingdom. " "Leave no enemy standing." msgstr "" "Унищожи варварите, които атакуват южната граница на твоето кралÑтво! " "ВъзÑтанови падналите Ñи градове и Ñлед това нахлуй в кралÑтвото на " "джунглата. Ðе оÑтавÑй нито един враг." msgid "Retake the castle of Ivory Gates, which has fallen due to treachery." msgstr "" "Върни Ñи отново замъка Портите на Ивъри, който падна поради предателÑтво." msgid "" "Gain the favor of the elves. They will not allow trees to be chopped down, " "so they will send you wood every 2 weeks. You must complete your mission " "before the first day of the seventh month, or the kingdom will surely fall." msgstr "" "Спечели благоволението на елфите. Те нÑма да позволÑÑ‚ да Ñе изÑичат дървета, " "така че ще ти изпращат дърва на вÑеки 2 Ñедмици. ТрÑбва да завършиш миÑиÑта " "Ñи преди Ð¿ÑŠÑ€Ð²Ð¸Ñ Ð´ÐµÐ½ на ÑÐµÐ´Ð¼Ð¸Ñ Ð¼ÐµÑец или кралÑтвото ÑÑŠÑ ÑигурноÑÑ‚ ще падне." msgid "" "This is the final battle against your rival kingdom of Harondale. Eliminate " "everyone, and don't lose the hero Jarkonas VI." msgstr "" "Това е поÑледната битка Ñрещу Ñъперничещото ти кралÑтво Харондейл. " "Елиминирай вÑички и не губи Ð³ÐµÑ€Ð¾Ñ Ð”Ð¶Ð°Ñ€ÐºÐ¾Ð½Ð°Ñ VI." msgid "Fount of Wizardry" msgstr "Изворът на магьоÑничеÑтвото" msgid "Power's End" msgstr "КраÑÑ‚ на влаÑтта" msgid "The Eternal Scrolls" msgstr "Вечните Ñвитъци" msgid "The Shrouded Isles" msgstr "Забулените оÑтрови" msgid "" "Your mission is to vanquish the warring mages in the magical Shrouded Isles. " "The completion of this task will give you a fighting chance against your " "rivals." msgstr "" "ТвоÑта миÑÐ¸Ñ Ðµ да победиш воюващите магове в магичеÑките Забулени оÑтрови. " "Изпълнението на тази задача ще ти даде ÑˆÐ°Ð½Ñ Ð´Ð° Ñе биеш Ñрещу твойте " "Ñъперници." msgid "" "The location of the great library has been discovered! You must make your " "way to it, and reclaim the city of Chronos in which it lies." msgstr "" "МеÑтоположението на великата библиотека е открито! ТрÑбва да Ñтигнеш до него " "и да Ñи върнеш града ХроноÑ, в който Ñе намира." #, fuzzy msgid "" "Find the Orb of negation, which is said to be buried in this land. There are " "clues inscribed on stone obelisks which will help lead you to your prize. " "Find the Orb before the first day of the sixth month, or your rivals will " "surely have gotten to the fount before you." msgstr "" "Ðамери Кълбото на отрицанието, за което Ñе казва, че е заровено в тази земÑ. " "Има улики, изпиÑани върху каменни обелиÑки, които ще ти помогнат да разберш " "ÑтойноÑтта Ñи. Ðамери кълбото преди Ð¿ÑŠÑ€Ð²Ð¸Ñ Ð´ÐµÐ½ на шеÑÑ‚Ð¸Ñ Ð¼ÐµÑец или твойте " "Ñъперници ÑÑŠÑ ÑигурноÑÑ‚ ще Ñа Ñтигнали до извора преди теб." msgid "" "You must take control of the castle of Magic, where the fount of wizardry " "lies. Do this and your victory will be supreme." msgstr "" "ТрÑбва да поемеш контрола над замъка на магиÑта, където Ñе намира изворът на " "магьоÑничеÑтвото. Ðаправи това и твоÑта победа ще бъде върховна." msgid "Blood is Thicker" msgstr "Кръвта е по-гъÑта" msgid "King and Country" msgstr "Крал и държава" msgid "Pirate Isles" msgstr "ПиратÑки оÑтрови" msgid "Stranded" msgstr "ЗакъÑал" msgid "" "Capture the town on the island off the southeast shore in order to construct " "a boat and travel back towards the mainland. Do not lose the hero Gallavant." msgstr "" "Превземи града на оÑтрова край ÑŽÐ³Ð¾Ð¸Ð·Ñ‚Ð¾Ñ‡Ð½Ð¸Ñ Ð±Ñ€Ñг, за да поÑтроиш лодка и да " "пътуваш обратно към континента. Ðе губи Ð³ÐµÑ€Ð¾Ñ Ð“Ð°Ð»Ð°Ð²Ð°Ð½Ñ‚." msgid "" "Find and defeat Martine, the pirate leader, who resides in Pirates Cove. Do " "not lose Gallavant or your quest will be over." msgstr "" "Ðамери и победи Мартин, водача на пиратите, който живее в ПиратÑÐºÐ¸Ñ Ð·Ð°Ð»Ð¸Ð². " "Ðе губи Галавант или миÑиÑта ти ще приключи." msgid "" "Eliminate all the other forces who oppose the rule of Lord Alberon. " "Gallavant must not die." msgstr "" "Елиминирай вÑички други Ñили, които Ñе противопоÑтавÑÑ‚ на управлението на " "лорд Ðлберон. Галавант не трÑбва да умира." msgid "" "Overthrow the entrenched monarchy of Lord Alberon, and claim all the land in " "your name. Gallavant must not die." msgstr "" "Пребори утвърдената Ð¼Ð¾Ð½Ð°Ñ€Ñ…Ð¸Ñ Ð½Ð° лорд Ðлберон и поиÑкай цÑлата Ð·ÐµÐ¼Ñ Ð½Ð° твое " "име. Галавант не трÑбва да умира." msgid " bane" msgstr " проклÑтие" msgid " alliance" msgstr " Ñъюз" msgid "Carry-over forces" msgstr "ПренаÑÑщи Ñили" msgid " bonus" msgstr " бонуÑ" msgid " defeated" msgstr " победен" msgid " will always run away from your army." msgstr " винаги ще бÑга от вашата армиÑ." msgid " will be willing to join your army." msgstr " ще има желание да Ñе приÑъедини към вашата армиÑ." msgid "\"%{artifact}\" artifact will be carried over the scenario." msgstr "Ðртефактът \"%{artifact}\" ще бъде пренеÑен в ÑценариÑ." msgid "The army will be carried over the scenario." msgstr "ÐрмиÑта ще бъде пренеÑена по ÑценариÑ." msgid "The kingdom will have +%{count} %{resource} each day." msgstr "КралÑтвото ще има +%{count} %{resource} вÑеки ден." msgid "\"%{spell}\" spell will be carried over the scenario." msgstr "Заклинанието \"%{spell}\" ще бъде пренеÑено в ÑценариÑ." msgid "%{hero} can be hired in the scenario." msgstr "%{hero} може да бъде нает в ÑценариÑ." msgid "" "%{hero} has been defeated and will not appear in the subsequent scenarios." msgstr "%{hero} е победен и нÑма да Ñе поÑви в Ñледващите Ñценарии." msgid "The dwarves recognize their allies and gladly join your forces." msgstr "" "Джуджетата разпознават Ñвоите Ñъюзници и Ñ Ñ€Ð°Ð´Ð¾ÑÑ‚ Ñе приÑъединÑват към " "вашите Ñили." msgid "The ogres recognize you as the Dwarfbane and lumber over to join you." msgstr "" "Огритата те разпознават като Джуджеубиеца и Ñе натиÑкат да Ñе приÑъединÑÑ‚ " "към теб." msgid "" "The dragons, snarling and growling, agree to join forces with you, their " "'Ally'." msgstr "" "Драконите, ръмжащи и ръмжейки, Ñе ÑъглаÑÑват да обединÑÑ‚ Ñили Ñ Ñ‚ÐµÐ±, Ñ‚ÐµÑ…Ð½Ð¸Ñ " "\"Ñъюзник\"." msgid "" "As you approach the group of elves, their leader calls them all to " "attention. He shouts to them, \"Who of you is brave enough to join this " "fearless ally of ours?\" The group explodes with cheers as they run to join " "your ranks." msgstr "" "Когато Ñе приближаваш до групата елфи, техниÑÑ‚ водач ги призовава вÑички да " "внимават. Той им крещи: \"Кой от Ð²Ð°Ñ Ðµ доÑтатъчно Ñмел да Ñе приÑъедини към " "този наш безÑтрашен Ñъюзник?\" Групата избухва от възглаÑи, докато тичат да " "Ñе приÑъединÑÑ‚ към твоите редици." msgid "" "The dwarves hail you, \"Any friend of Roland is a friend of ours. You may " "pass.\"" msgstr "" "Джуджетата те приветÑтват: \"Ð’Ñеки приÑтел на Роланд е и наш приÑтел. Може " "да преминеш.\"" msgid "" "The ogres give you a grunt of recognition, \"Archibald's allies may pass.\"" msgstr "" "Огритата ти изÑумтÑват в знак на признание: \"Съюзниците на Ðрчибалд може да " "преминат.\"" msgid "" "The dragons see you and call out. \"Our alliance with Archibald compels us " "to join you. Unfortunately you have no room. A pity!\" They quickly scatter." msgstr "" "Драконите те виждат и викат. \"Съюзът ни Ñ Ðрчибалд ни принуждава да Ñе " "приÑъединим към теб. За Ñъжаление нÑмаш мÑÑто. Жалко!\" Те бързо Ñе " "разпръÑкват." msgid "" "The elves stand at attention as you approach. Their leader calls to you and " "says, \"Let us not impede your progress, ally! Move on, and may victory be " "yours.\"" msgstr "" "Елфите ÑтоÑÑ‚ мирно, докато Ñе приближаваш. ТехниÑÑ‚ водач Ñе обръща към теб " "казва: \"Ðека не пречим на прогреÑа ти, Ñъюзнико! Продължавай напред и нека " "победата бъде твоÑ.\"" msgid "\"The Dwarfbane!!!!, run for your lives.\"" msgstr "\"Джуджеубиецо!!!!, бÑгай за да опазиш живота Ñи.\"" msgid "campaignBonus|Animate Dead" msgstr "Ðнимирайте мъртвите" msgid "campaignBonus|Chain Lightning" msgstr "Верижна Ñветкавица" msgid "campaignBonus|Fireblast" msgstr "Огнена екÑплозиÑ" msgid "campaignBonus|Mass Curse" msgstr "МаÑово проклÑтие" msgid "campaignBonus|Mass Haste" msgstr "МаÑово бързане" msgid "campaignBonus|Mirror Image" msgstr "Огледална картина" msgid "campaignBonus|Resurrect" msgstr "ВъзкръÑни" msgid "campaignBonus|Steelskin" msgstr "Стоманена кожа" msgid "campaignBonus|Summon Earth" msgstr "Призови ЗемÑта" msgid "campaignBonus|View Heroes" msgstr "Виж героите" msgid "campaignBonus|Ballista" msgstr "Катапулт" msgid "campaignBonus|Black Pearl" msgstr "Черна перла" msgid "campaignBonus|Caster's Bracelet" msgstr "Гривна на КаÑтер" msgid "campaignBonus|Defender Helm" msgstr "Защитен шлем" msgid "campaignBonus|Breastplate" msgstr "Ðагръдник" msgid "campaignBonus|Dragon Sword" msgstr "Драконов меч" msgid "campaignBonus|Fizbin Medal" msgstr "Медал Физбин" msgid "campaignBonus|Foremost Scroll" msgstr "Ðай-могъщиÑÑ‚ Ñвитък" msgid "campaignBonus|Gauntlets" msgstr "Ръкавици" msgid "campaignBonus|Hideous Mask" msgstr "Отвратителна маÑка" msgid "campaignBonus|Mage's Ring" msgstr "ПръÑтенът на магьоÑника" msgid "campaignBonus|Major Scroll" msgstr "Огромен Ñвитък" msgid "campaignBonus|Medal of Honor" msgstr "Медал на чеÑтта" msgid "campaignBonus|Medal of Valor" msgstr "Медал за доблеÑÑ‚" msgid "campaignBonus|Minor Scroll" msgstr "Малък Ñвитък" msgid "campaignBonus|Nomad Boots" msgstr "ÐомадÑки ботуши" msgid "campaignBonus|Power Axe" msgstr "Брадва на Силата" msgid "campaignBonus|Spiked Shield" msgstr "Щит Ñ ÑˆÐ¸Ð¿Ð¾Ð²Ðµ" msgid "campaignBonus|Stealth Shield" msgstr "Стелт щит" msgid "campaignBonus|Tax Lien" msgstr "Данъчен залог" msgid "campaignBonus|Thunder Mace" msgstr "Гръмотевичен боздуган" msgid "campaignBonus|Traveler's Boots" msgstr "ПътничеÑки ботуши" msgid "campaignBonus|White Pearl" msgstr "БÑла перла" msgid "campaignBonus|Basic Archery" msgstr "Ðачална Ñтрелба Ñ Ð»ÑŠÐº" msgid "campaignBonus|Advanced Archery" msgstr "Стрелба Ñ Ð»ÑŠÐº за напреднали" msgid "campaignBonus|Expert Archery" msgstr "ЕкÑпертна Ñтрелба Ñ Ð»ÑŠÐº" msgid "campaignBonus|Basic Ballistics" msgstr "Ðачална балиÑтика" msgid "campaignBonus|Advanced Ballistics" msgstr "Ðапреднала балиÑтика" msgid "campaignBonus|Expert Ballistics" msgstr "ЕкÑпертна балиÑтика" msgid "campaignBonus|Basic Diplomacy" msgstr "Ðачална дипломациÑ" msgid "campaignBonus|Advanced Diplomacy" msgstr "Ðапреднала дипломациÑ" msgid "campaignBonus|Expert Diplomacy" msgstr "ЕкÑпертна дипломациÑ" msgid "campaignBonus|Basic Eagle Eye" msgstr "Ðачално орлово око" msgid "campaignBonus|Advanced Eagle Eye" msgstr "Ðапреднало орлово око" msgid "campaignBonus|Expert Eagle Eye" msgstr "ЕкÑпертно орлово око" msgid "campaignBonus|Basic Estates" msgstr "Ðачални имоти" msgid "campaignBonus|Advanced Estates" msgstr "Ðапреднали имоти" msgid "campaignBonus|Expert Estates" msgstr "ЕкÑперт имоти" msgid "campaignBonus|Basic Leadership" msgstr "Ðачално лидерÑтво" msgid "campaignBonus|Advanced Leadership" msgstr "Ðапреднало лидерÑтво" msgid "campaignBonus|Expert Leadership" msgstr "ЕкÑпертно лидерÑтво" msgid "campaignBonus|Basic Logistics" msgstr "Ðачална логиÑтика" msgid "campaignBonus|Advanced Logistics" msgstr "Ðапреднала логиÑтика" msgid "campaignBonus|Expert Logistics" msgstr "ЕкÑпертна логиÑтика" msgid "campaignBonus|Basic Luck" msgstr "Ðачален къÑмет" msgid "campaignBonus|Advanced Luck" msgstr "Ðапреднал къÑмет" msgid "campaignBonus|Expert Luck" msgstr "ЕкÑпертен къÑмет" msgid "campaignBonus|Basic Mysticism" msgstr "Ðачален миÑтицизъм" msgid "campaignBonus|Advanced Mysticism" msgstr "Ðапреднал миÑтицизъм" msgid "campaignBonus|Expert Mysticism" msgstr "ЕкÑпертен миÑтицизъм" msgid "campaignBonus|Basic Navigation" msgstr "Ðачална навигациÑ" msgid "campaignBonus|Advanced Navigation" msgstr "Ðапреднала навигациÑ" msgid "campaignBonus|Expert Navigation" msgstr "ЕкÑпертна навигациÑ" msgid "campaignBonus|Basic Necromancy" msgstr "Ðачална некромантиÑ" msgid "campaignBonus|Advanced Necromancy" msgstr "Ðапреднала некромантиÑ" msgid "campaignBonus|Expert Necromancy" msgstr "ЕкÑпертна некромантиÑ" msgid "campaignBonus|Basic Pathfinding" msgstr "Ðачално намиране на пътÑ" msgid "campaignBonus|Advanced Pathfinding" msgstr "Ðапреднало намиране на пътÑ" msgid "campaignBonus|Expert Pathfinding" msgstr "ЕкÑпертно намиране на пътÑ" msgid "campaignBonus|Basic Scouting" msgstr "Ðачално ÑкаутÑтво" msgid "campaignBonus|Advanced Scouting" msgstr "Ðапреднало ÑкаутÑтво" msgid "campaignBonus|Expert Scouting" msgstr "ЕкÑпертно ÑкаутÑтво" msgid "campaignBonus|Basic Wisdom" msgstr "Ðачална мъдроÑÑ‚" msgid "campaignBonus|Advanced Wisdom" msgstr "Ðапреднала мъдроÑÑ‚" msgid "campaignBonus|Expert Wisdom" msgstr "ЕкÑпертна мъдроÑÑ‚" msgid "" "The main hero will have \"%{artifact}\" artifact at the start of the " "scenario." msgstr "ГлавниÑÑ‚ герой ще има артефакт \"%{artifact}\" в началото на ÑценариÑ." msgid "" "The kingdom will receive %{amount} additional %{resource} at the start of " "the scenario." msgstr "" "КралÑтвото ще получи %{amount} допълнителни %{resource} в началото на " "ÑценариÑ." msgid "" "The kingdom will have %{amount} less %{resource} at the start of the " "scenario." msgstr "" "КралÑтвото ще има %{amount} по-малко %{resource} в началото на ÑценариÑ." msgid "" "The main hero will have %{count} %{monster} at the start of the scenario." msgstr "ГлавниÑÑ‚ герой ще има %{count} %{monster} в началото на ÑценариÑ." msgid "" "The main hero will have \"%{spell}\" spell at the start of the scenario." msgstr "ГлавниÑÑ‚ герой ще има заклинание \"%{spell}\" в началото на ÑценариÑ." msgid "The starting race of the scenario will be %{race}." msgstr "Ðачалното ÑÑŠÑтезание на ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ Ñ‰Ðµ бъде %{race}." msgid "" "The main hero will have additional %{count} %{skill} at the start of the " "scenario." msgstr "" "ГлавниÑÑ‚ герой ще има допълнителни %{count} %{skill} в началото на ÑценариÑ." msgid "The main hero will have %{skill} at the start of the scenario." msgstr "The main hero will have %{skill} at the start of the scenario." msgid "Roland" msgstr "Роланд" msgid "Archibald" msgstr "Ðрчибалд" #, fuzzy msgid "Price of Loyalty" msgstr "Цената на лоÑлноÑтта" msgid "Voyage Home" msgstr "Пътуване към дома" msgid "Wizard's Isle" msgstr "ОÑтровът на вълшебника" msgid "Descendants" msgstr "Потомци" msgid "The %{building} produces %{monster}." msgstr "%{building} произвежда %{monster}." msgid "Requires:" msgstr "ИзиÑква:" msgid "Exit this menu without doing anything." msgstr "Излез от това меню, без да правиш нищо." msgid "Cannot build. You have already built here today." msgstr "Ðе може да Ñе Ñтрои. Ти вече Ñи поÑтроил тук днеÑ." msgid "For this action it is necessary to build a castle first." msgstr "За това дейÑтвие е необходимо първо да поÑтроиш замък." msgid "Cannot build %{name} because the castle is too far away from an ocean." msgstr "Ðе може да Ñе поÑтрои %{name}, защото замъкът е твърде далеч от океан." msgid "disable build." msgstr "Ñпри изграждането." msgid "Cannot afford %{name}." msgstr "Ðе мога да Ñи Ð¿Ð¾Ð·Ð²Ð¾Ð»Ñ %{name}." msgid "%{name} is already built." msgstr "%{name} вече е изграден." msgid "Cannot build %{name}." msgstr "Ðе може да Ñе изгради %{name}." msgid "Build %{name}." msgstr "ПоÑтрой %{name}." msgid "Blackridge" msgstr "Блекридж" msgid "Hillstone" msgstr "ХилÑтоун" msgid "Pinehurst" msgstr "ПайнхърÑÑ‚" msgid "Whiteshield" msgstr "БÑл щит" msgid "Woodhaven" msgstr "Удхейвън" msgid "Blackwind" msgstr "Черен вÑтър" msgid "Bloodreign" msgstr "Блъдрейн" msgid "Dragontooth" msgstr "Драконов зъб" msgid "Greywind" msgstr "Сив вÑтър" msgid "Portsmith" msgstr "ПортÑмит" msgid "Atlantium" msgstr "Ðтлантиум" msgid "Middle Gate" msgstr "Средна порта" msgid "Sansobar" msgstr "СанÑибар" msgid "Tundara" msgstr "Тундара" msgid "Vulcania" msgstr "ВулканиÑ" msgid "Baywatch" msgstr "Бейвоч" msgid "Fountainhead" msgstr "Глава на фонтан" msgid "Vertigo" msgstr "Световъртеж" msgid "Wildabar" msgstr "Вайлдабар" msgid "Winterkill" msgstr "Зимно убийÑтво" msgid "Brindamoor" msgstr "Бриндамур" msgid "Lakeside" msgstr "ЛейкÑайд" msgid "Nightshadow" msgstr "Ðощтна ÑÑнка" msgid "Olympus" msgstr "ОлимпуÑ" msgid "Sandcaster" msgstr "ПÑÑъчен колело" msgid "Alamar" msgstr "Ðлмар" msgid "Burlock" msgstr "Бурлог" msgid "Dragadune" msgstr "Драгадюн" msgid "Kalindra" msgstr "Калиндра" msgid "Xabran" msgstr "ЕкÑабран" msgid "Algary" msgstr "Ðлгари" msgid "Basenji" msgstr "БаÑенджи" msgid "Blackfang" msgstr "Черензъб" msgid "New Dawn" msgstr "Ðова Зора" msgid "Sorpigal" msgstr "Сорпигал" msgid "Avone" msgstr "Ðвон" msgid "Big Oak" msgstr "ГолÑм Дъб" msgid "Chandler" msgstr "Чандлър" msgid "Erliquin" msgstr "Ерликвин" msgid "Hampshire" msgstr "Хемпшир" msgid "Antioch" msgstr "ÐнтиохиÑ" msgid "Avalon" msgstr "Ðвалон" msgid "Roc Haven" msgstr "Рок Хейвън" msgid "South Mill" msgstr "Южна Мелница" msgid "Weed Patch" msgstr "ÐšÑŠÑ Ð¢Ñ€ÐµÐ²Ð°" msgid "Brownston" msgstr "БраунÑтън" msgid "Hilltop" msgstr "Хълм" msgid "Weddington" msgstr "Уедингтън" msgid "Westfork" msgstr "УеÑтфорк" msgid "Whittingham" msgstr "Уитингам" msgid "Cathcart" msgstr "Каткарт" msgid "Elk's Head" msgstr "Елкова глава" msgid "Roscomon" msgstr "РоÑкомън" msgid "Sherman" msgstr "Шърман" msgid "Yorksford" msgstr "ЙоркÑфорд" msgid "Blackburn" msgstr "Блекбърн" msgid "Blacksford" msgstr "БлекÑфорд" msgid "Burton" msgstr "Бъртън" msgid "Pig's Eye" msgstr "СвинÑко Око" msgid "Viper's Nest" msgstr "Гнездото на уÑойница" msgid "Fenton" msgstr "Фентън" msgid "Lankershire" msgstr "Ланкършър" msgid "Lombard" msgstr "Ломбард" msgid "Timberhill" msgstr "Тимбърхил" msgid "Troy" msgstr "ТроÑ" msgid "Forder Oaks" msgstr "Фордър ОукÑ" msgid "Meramec" msgstr "Мерамец" msgid "Quick Silver" msgstr "КуикÑилвър" msgid "Westmoor" msgstr "Западно ТреÑавище" msgid "Willow" msgstr "Уилоу" msgid "Corackston" msgstr "КоракÑтън" msgid "Sheltemburg" msgstr "Шелтембург" msgid "Cannot recruit - you already have a Hero in this town." msgstr "Ðе може да Ñе наеме - вече имате герой в този град." msgid "Cannot recruit - you have too many Heroes." msgstr "Ðе може да Ñе наеме - имаш твърде много герои." msgid "Cannot afford a Hero" msgstr "Ðе мога да Ñи Ð¿Ð¾Ð·Ð²Ð¾Ð»Ñ Ð³ÐµÑ€Ð¾Ð¹" msgid "There is no room in the garrison for this army." msgstr "Ð’ гарнизона нÑма мÑÑто за тази армиÑ." msgid "Fortifications" msgstr "УкреплениÑ" msgid "Farm" msgstr "Ферма" msgid "Thatched Hut" msgstr "Сламена хижа" msgid "Archery Range" msgstr "Стрелба Ñ Ð»ÑŠÐº" msgid "Upg. Archery Range" msgstr "ÐадÑ. Ñтрелба Ñ Ð»ÑŠÐº" msgid "Blacksmith" msgstr "Ковачница" msgid "Upg. Blacksmith" msgstr "ÐадÑ. ковачница" msgid "Armory" msgstr "Оръжейна" msgid "Upg. Armory" msgstr "ÐадÑ. оръжейна" msgid "Jousting Arena" msgstr "Ðрена" msgid "Upg. Jousting Arena" msgstr "ÐадÑ. арена" msgid "Cathedral" msgstr "Катедрала" msgid "Upg. Cathedral" msgstr "ÐадÑ. катедрала" msgid "Coliseum" msgstr "Колизеум" msgid "Garbage Heap" msgstr "Купчина боклук" msgid "Hut" msgstr "Барака" msgid "Stick Hut" msgstr "Колиба" msgid "Upg. Stick Hut" msgstr "ÐадÑ. колиба" msgid "Den" msgstr "Бърлога" msgid "Adobe" msgstr "Глинена поÑтройка" msgid "Upg. Adobe" msgstr "ÐадÑ. глинена поÑтройка" msgid "Bridge" msgstr "МоÑÑ‚" msgid "Upg. Bridge" msgstr "ÐадÑ. МоÑÑ‚" msgid "Pyramid" msgstr "Пирамида" msgid "Rainbow" msgstr "Дъга" msgid "Crystal Garden" msgstr "КриÑтална градина" msgid "Treehouse" msgstr "Къща на дърво" msgid "Cottage" msgstr "Вила" msgid "Upg. Cottage" msgstr "ÐадÑ. вила" msgid "Stonehenge" msgstr "Стоунхендж" msgid "Upg. Stonehenge" msgstr "ÐадÑ. Стоунхендж" msgid "Fenced Meadow" msgstr "Оградена полÑна" msgid "sorceress|Red Tower" msgstr "Червена кула" msgid "Dungeon" msgstr "Тъмница" msgid "Waterfall" msgstr "Водопад" msgid "Cave" msgstr "Пещера" msgid "Crypt" msgstr "Крипта" msgid "Nest" msgstr "Гнездо" msgid "Maze" msgstr "Лабиринт" msgid "Upg. Maze" msgstr "ÐадÑ. лабиринт" msgid "Swamp" msgstr "Блато" msgid "Green Tower" msgstr "Зелена кула" msgid "warlock|Red Tower" msgstr "Червена кула" msgid "Black Tower" msgstr "Черна кула" msgid "Library" msgstr "Библиотека" msgid "Orchard" msgstr "Овощна градина" msgid "Habitat" msgstr "Хабитат" msgid "Pen" msgstr "Кошара" msgid "Foundry" msgstr "ЛеÑрна" msgid "Upg. Foundry" msgstr "ÐадÑ. леÑрна" msgid "Cliff Nest" msgstr "Скално гнездо" msgid "Ivory Tower" msgstr "Кулата на мечтите" msgid "Upg. Ivory Tower" msgstr "ÐадÑ. Кулата на мечтите" msgid "Cloud Castle" msgstr "Замък в облаците" msgid "Upg. Cloud Castle" msgstr "ÐадÑ. Замък в облаците" msgid "Storm" msgstr "БурÑ" msgid "Skull Pile" msgstr "Купчина черепи" msgid "Excavation" msgstr "Разкопки" msgid "Graveyard" msgstr "Гробище" msgid "Upg. Graveyard" msgstr "ÐадÑ. гробище" msgid "Upg. Pyramid" msgstr "ÐадÑ. пирамида" msgid "Mansion" msgstr "Имение" msgid "Upg. Mansion" msgstr "ÐадÑ. имение" msgid "Mausoleum" msgstr "Мавзолей" msgid "Upg. Mausoleum" msgstr "ÐадÑ. мавзолей" msgid "Laboratory" msgstr "ЛабораториÑ" msgid "Shrine" msgstr "Светилище" msgid "" "The Fortifications increase the toughness of the walls, increasing the " "number of turns it takes to knock them down." msgstr "" "УкреплениÑта увеличават здравината на Ñтените, увеличавайки Ð±Ñ€Ð¾Ñ Ð½Ð° ударите, " "необходими за ÑъбарÑнето им." msgid "The Farm increases production of Peasants by %{count} per week." msgstr "Фермата увеличава производÑтвото на ÑелÑни Ñ %{count} на Ñедмица." msgid "" "The Coliseum provides inspiring spectacles to defending troops, raising " "their morale by two during combat." msgstr "" "Колизеумът предоÑÑ‚Ð°Ð²Ñ Ð²Ð´ÑŠÑ…Ð½Ð¾Ð²Ñващи зрелища на отбранÑващите Ñе войÑки, като " "повишава морала им Ñ Ð´Ð²Ðµ по време на битка." msgid "The Garbage Heap increases production of Goblins by %{count} per week." msgstr "" "Купчината боклук увеличава производÑтвото на Гоблини Ñ %{count} на Ñедмица." msgid "The Rainbow increases the luck of the defending units by two." msgstr "Дъгата увеличава къÑмета на защитаващите Ñе единици Ñ Ð´Ð²Ðµ." msgid "" "The Crystal Garden increases production of Sprites by %{count} per week." msgstr "" "КриÑталната градина увеличава производÑтвото на Фей Ñ %{count} на Ñедмица." msgid "The Dungeon increases the income of the town by %{count} gold per day." msgstr "Подземието увеличава доходите на града Ñ %{count} злато на ден." msgid "The Waterfall increases production of Centaurs by %{count} per week." msgstr "Водопадът увеличава производÑтвото на Кентаври Ñ %{count} на Ñедмица." msgid "" "The Library increases the number of spells in the Guild by one for each " "level of the guild." msgstr "" "Библиотеката увеличава Ð±Ñ€Ð¾Ñ Ð½Ð° магиите в гилдиÑта Ñ Ð¿Ð¾ една за вÑÑко ниво на " "гилдиÑта." msgid "The Orchard increases production of Halflings by %{count} per week." msgstr "" "Овощната градина увеличава производÑтвото на Хобити Ñ %{count} на Ñедмица." msgid "The Storm adds +2 to the power of spells of a defending spell caster." msgstr "БурÑта Ð´Ð¾Ð±Ð°Ð²Ñ +2 към Ñилата на заклинаниÑта на защитаващ заклинател." msgid "The Skull Pile increases production of Skeletons by %{count} per week." msgstr "" "Купчината черепи увеличава производÑтвото на Скелети Ñ %{count} на Ñедмица." msgid "Thieves' Guild" msgstr "Ð“Ð¸Ð»Ð´Ð¸Ñ Ð½Ð° крадците" msgid "Tavern" msgstr "Кръчма" msgid "Shipyard" msgstr "КорабоÑтроителница" msgid "Well" msgstr "Кладенец" msgid "Statue" msgstr "СтатуÑ" msgid "Marketplace" msgstr "Пазар" msgid "Moat" msgstr "Ров" msgid "Castle" msgstr "Замък" msgid "Tent" msgstr "Палатка" msgid "Captain's Quarters" msgstr "КапитанÑка Квартира" msgid "Mage Guild, Level 1" msgstr "Маг. гилдиÑ, ниво 1" msgid "Mage Guild, Level 2" msgstr "Маг. гилдиÑ, ниво 2" msgid "Mage Guild, Level 3" msgstr "Маг. гилдиÑ, ниво 3" msgid "Mage Guild, Level 4" msgstr "Маг. гилдиÑ, ниво 4" msgid "Mage Guild, Level 5" msgstr "Маг. гилдиÑ, ниво 5" msgid "" "The Shrine increases the necromancy skill of all your necromancers by 10 " "percent." msgstr "" "Светилището увеличава умението за Ð½ÐµÐºÑ€Ð¾Ð¼Ð°Ð½Ñ‚Ð¸Ñ Ð½Ð° вÑички твой некроманти Ñ 10 " "процента." msgid "" "The Thieves' Guild provides information on enemy players. Thieves' Guilds " "can also provide scouting information on enemy towns. Additional Guilds " "provide more information." msgstr "" "ГилдиÑта на крадците предоÑÑ‚Ð°Ð²Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° вражеÑки играчи. Гилдиите на " "крадците Ñъщо могат да предоÑтавÑÑ‚ разузнавателна Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° вражеÑките " "градове. Допълнителните гилдии предоÑтавÑÑ‚ повече информациÑ." msgid "The Tavern increases morale for troops defending the castle." msgstr "Кръчмата повишава морала на войÑките, защитаващи замъка." msgid "The Shipyard allows ships to be built." msgstr "КорабоÑтроителницата позволÑва да Ñе ÑтроÑÑ‚ кораби." msgid "" "The Well increases the growth rate of all dwellings by %{count} creatures " "per week." msgstr "" "Кладенецът увеличава ÑкороÑтта на раÑтеж във вÑички жилища Ñ %{count} " "ÑъщеÑтва на Ñедмица." #, fuzzy msgid "The Statue increases the town's income by %{count} gold per day." msgstr "СтатуÑта увеличава приходите на Ñ‚Ð²Ð¾Ñ Ð³Ñ€Ð°Ð´ Ñ %{count} злато на ден." msgid "The Left Turret provides extra firepower during castle combat." msgstr "" "ЛÑвата кула оÑигурÑва допълнителна огнева мощ по време на битка в замъка." msgid "The Right Turret provides extra firepower during castle combat." msgstr "" "ДÑÑната кула оÑигурÑва допълнителна огнева мощ по време на битка в замъка." msgid "" "The Marketplace can be used to convert one type of resource into another. " "The more marketplaces you control, the better the exchange rate." msgstr "" "Пазарът може да Ñе използва за преобразуване на един вид реÑÑƒÑ€Ñ Ð² друг. " "Колкото повече пазари контролирате, толкова по-добър е обменниÑÑ‚ курÑ." msgid "" "The Moat slows attacking units. Any unit entering the moat must end its turn " "there and becomes more vulnerable to attack." msgstr "" "Ровът Ð·Ð°Ð±Ð°Ð²Ñ Ð°Ñ‚Ð°ÐºÑƒÐ²Ð°Ñ‰Ð¸Ñ‚Ðµ единици. Ð’ÑÑка единица, влизаща в рова, трÑбва да " "завърши реда Ñи там и да Ñтане по-уÑзвима за атака." #, fuzzy msgid "" "The Castle improves the town's defense and increases its income to %{count} " "gold per day." msgstr "" "Замъкът подобрÑва защитата на града и увеличава приходите до %{count} злато " "на ден." msgid "" "The Tent provides workers to build a castle, provided the materials and the " "gold are available." msgstr "" "Палатката оÑигурÑва работници за изграждане на замък, при уÑловие че " "материалите и златото Ñа налични." msgid "" "The Captain's Quarters provides a captain to assist in the castle's defense " "when no hero is present." msgstr "" "КапитанÑките квартири оÑигурÑват капитан, който да помага в защитата на " "замъка, когато нÑма герой." msgid "" "The Mage Guild allows heroes to learn spells and replenish their spell " "points." msgstr "" "ГилдиÑта на магьоÑниците позволÑва на героите да учат Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ Ð¸ да " "попълват магичеÑките Ñи точки." msgid "Recruit %{name}" msgstr "Вербувай %{name}" msgid "Month: %{month}, Week: %{week}, Day: %{day}" msgstr "МеÑец: %{month}, Седмица: %{week}, Ден: %{day}" msgid "" "You must purchase a spell book to use the mage guild, but you currently have " "no room for a spell book. Try giving one of your artifacts to another hero." msgstr "" "ТрÑбва да купиш книга ÑÑŠÑ Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ, за да използваш магий, но в момента " "нÑмаш мÑÑто за неÑ. Опитай Ñе да дадеш един от твойте артефакти на друг " "герой." msgid "Click to show next town." msgstr "Кликни, за да покажеш ÑÐ»ÐµÐ´Ð²Ð°Ñ‰Ð¸Ñ Ð³Ñ€Ð°Ð´." msgid "Show next town" msgstr "Покажи ÑÐ»ÐµÐ´Ð²Ð°Ñ‰Ð¸Ñ Ð³Ñ€Ð°Ð´" msgid "Click to show previous town." msgstr "Кликни, за да покажеш Ð¿Ñ€ÐµÐ´Ð¸ÑˆÐ½Ð¸Ñ Ð³Ñ€Ð°Ð´." msgid "Show previous town" msgstr "Покажи Ð¿Ñ€ÐµÐ´Ð¸ÑˆÐ½Ð¸Ñ Ð³Ñ€Ð°Ð´" msgid "This town may not be upgraded to a castle." msgstr "Този град не може да бъде надграден до замък." msgid "Town" msgstr "Град" msgid "Exit Castle" msgstr "Изход от Замъка" msgid "Exit Town" msgstr "Изход от Града" msgid "Show Income" msgstr "Покажи приходи" msgid "View Hero" msgstr "Виж герой" msgid "The above spells are available here." msgstr "Гореизложените магии Ñа налични тук." msgid "The spells the hero can learn have been added to their book." msgstr "Магиите, които героÑÑ‚ може да научи, Ñа добавени към неговата книга." msgid "A generous tip for the barkeep yields the following rumor:" msgstr "След щедър бакшиш бармана ÑÐ¿Ð¾Ð´ÐµÐ»Ñ ÑÐ»ÐµÐ´Ð½Ð¸Ñ Ñлух:" msgid "Recruit Hero" msgstr "Вербувай герой" msgid "%{name} is a level %{value} %{race} " msgstr "%{name} е ниво %{value} %{race} " msgid "with %{count} artifacts." msgstr "Ñ %{count} артефакти." msgid "with 1 artifact." msgstr "Ñ 1 артефакт." msgid "without artifacts." msgstr "без артефакти." msgid "Recruit %{name} the %{race}" msgstr "Вербувай %{name} %{race}" #, fuzzy msgid "" "'Spread' combat formation spreads the castle's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "Бойната Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ \"РазпръÑкване\" Ñ€Ð°Ð·Ð¿Ñ€ÐµÐ´ÐµÐ»Ñ Ñ‚Ð²Ð¾Ð¸Ñ‚Ðµ армии от горе до долу на " "бойното поле, Ñ Ð¿Ð¾Ð½Ðµ едно празно проÑтранÑтво между вÑÑка армиÑ." #, fuzzy msgid "" "'Grouped' combat formation bunches the castle's units together in the center " "of the castle's side of the battlefield." msgstr "" "\"Групираната\" бойна Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ð±ÐµÐ´Ð¸Ð½Ñва армиÑта ти в центъра от твоÑта " "Ñтрана на бойното поле." msgid "Spread Formation" msgstr "Ð¤Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð Ð°Ð·Ð¿Ñ€Ð¾Ñтиране" msgid "Grouped Formation" msgstr "Групирана формациÑ" msgid "Set garrison combat formation to 'Spread'" msgstr "Задай гарнизонна бойна Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ \"РазпроÑтиране\"" msgid "Set garrison combat formation to 'Grouped'" msgstr "Задай гарнизонна бойна Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ \"Групиране\"" msgid "Exit Castle Options" msgstr "Изход от Опции на замъка" msgid "Castle Options" msgstr "Опции на замъка" msgid "Not enough resources to recruit creatures." msgstr "ÐÑма доÑтатъчно реÑурÑи за набиране на ÑъщеÑтва." msgid "You are unable to recruit at this time, your ranks are full." msgstr "Ðе можеш да набираш в момента, твоите редици Ñа пълни." msgid "No creatures available for purchase." msgstr "ÐÑма налични ÑъщеÑтва за покупка." msgid "Recruit Creatures" msgstr "Вербувай ÑъщеÑтва" msgid "Max" msgstr "МакÑимално" msgid "Hire all creatures in the town." msgstr "Ðаеми вÑички ÑъщеÑтва в града." msgid "Available" msgstr "Ðалични" msgid "Town Population Information and Statistics" msgstr "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¸ ÑтатиÑтика за наÑелението на града" msgid "Damage" msgstr "Щети" msgid "HP" msgstr "Живот" msgid "Growth" msgstr "РаÑтеж" msgid "week" msgstr "Ñедмица" msgid "View World" msgstr "Виж Света" msgid "View the entire world." msgstr "Виж Ñ†ÐµÐ»Ð¸Ñ ÑвÑÑ‚." msgid "Puzzle" msgstr "Пъзел" msgid "View the obelisk puzzle." msgstr "Виж обелиÑка Ñ Ð¿ÑŠÐ·ÐµÐ»Ð°." msgid "Scenario Information" msgstr "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° ÑценариÑ" msgid "View information on the scenario you are currently playing." msgstr "Виж Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° ÑценариÑ, който играеш в момента." msgid "Dig for the Ultimate Artifact." msgstr "Изкопай най-Ð´Ð¾Ð±Ñ€Ð¸Ñ Ð°Ñ€Ñ‚ÐµÑ„Ð°ÐºÑ‚." msgid "Digging" msgstr "Изкопаване" msgid "Arena" msgstr "Ðрена" msgid "" "You enter the arena and face a pack of vicious lions. You handily defeat " "them, to the wild cheers of the crowd. Impressed by your skill, the aged " "trainer of gladiators agrees to train you in a skill of your choice." msgstr "" "Влизаш на арената и Ñе изправÑш Ñрещу глутница жеÑтоки лъвове. Ти ги " "побеждаваш убедително, под бурните възглаÑи на тълпата. Впечатлен от твоите " "умениÑ, възраÑтниÑÑ‚ треньор на гладиатори Ñе ÑъглаÑÑва да те обучи на умение " "по твой избор." msgid "Attack Skill" msgstr "Ðтака" msgid "Defense Skill" msgstr "Защита" msgid "Shots" msgstr "ИзÑтрели" msgid "Shots Left" msgstr "ОÑÑ‚. изÑтрели" msgid "Hit Points" msgstr "Живот" msgid "Hit Points Left" msgstr "ОÑÑ‚. живот" msgid "You can't afford to upgrade your troops!" msgstr "Ðе можеш да Ñи позволиш да надграждиш войÑките Ñи!" msgid "" "Your troops can be upgraded, but it will cost you dearly. Do you wish to " "upgrade them?" msgstr "" "Твоите войÑки могат да бъдат подобрени, но това ще ти Ñтрува Ñкъпо. ИÑкаш ли " "да ги надÑтроиш?" msgid "Are you sure you want to dismiss this army?" msgstr "Сигурен ли Ñи, че иÑкаш да разпуÑнеш тази армиÑ?" msgid "Upgrade" msgstr "Подобри" msgid "Upgrade your troops." msgstr "Ðадградете войÑките Ñи." msgid "Dismiss" msgstr "Oтпрати" msgid "Dismiss this army." msgstr "РазпуÑни тази войÑка." msgid "" "A group of %{monster} with a desire for greater glory wish to join you.\n" "Do you accept?" msgstr "" "Група от %{monster} Ñ Ð¶ÐµÐ»Ð°Ð½Ð¸Ðµ за по-голÑма Ñлава желае да Ñе приÑъедини към " "теб.\n" "Приемаш ли?" msgid "Followers" msgstr "ПоÑледователи" msgid "" "The %{monster} is swayed by your diplomatic tongue, and offers to join your " "army for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "%{monster} е очарован от Ñ‚Ð²Ð¾Ñ Ð´Ð¸Ð¿Ð»Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÐ½ език и предлага да Ñе приÑъедини " "към твоÑта Ð°Ñ€Ð¼Ð¸Ñ Ñрещу Ñумата от %{gold} злато.\n" "Приемаш ли?" msgid "" "The creatures are swayed by your diplomatic\n" "tongue, and make you an offer:\n" "\n" msgstr "" "СъзданиÑта Ñа повлиÑни от твоÑта дипломатичен\n" "език, и ти правÑÑ‚ оферта:\n" "\n" msgid "" "%{offer} of the %{total} %{monster} will join your army, and the rest will " "leave you alone, for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "%{offer} от %{total} %{monster} ще Ñе приÑъедини към твоÑта армиÑ, а " "оÑтаналите ще те оÑтавÑÑ‚ на мира Ñрещу Ñумата от %{gold} злато.\n" "Приемаш ли?" msgid "" "All %{offer} of the %{monster} will join your army for the sum of %{gold} " "gold.\n" "Do you accept?" msgstr "" "Ð’Ñички %{offer} от %{monster} ще Ñе приÑъединÑÑ‚ към твоÑта Ð°Ñ€Ð¼Ð¸Ñ Ñрещу " "Ñумата от %{gold} злато.\n" "Приемаш ли?" msgid "(Rate: %{percent})" msgstr "(Оценка: %{percent})" msgid "off" msgstr "изкл" msgid "Music" msgstr "Музика" msgid "Effects" msgstr "Ефекти" msgid "MIDI" msgstr "MIDI" msgid "MIDI Expansion" msgstr "MIDI Продължение" msgid "External" msgstr "Външна" msgid "Music Type" msgstr "Източник" msgid "3D Audio" msgstr "3D Звук" msgid "Toggle ambient music level." msgstr "Превключване на нивото на околната музика." msgid "Toggle foreground sounds level." msgstr "Превключване на нивото на звуците на преден план." msgid "Change the type of music." msgstr "Промени вида на музиката." msgid "Toggle 3D effects of foreground sounds." msgstr "Превключване на 3D ефекти на звуци на преден план." msgid "Build a new ship:" msgstr "Изгради нов кораб:" msgid "Resource cost:" msgstr "Разходи за реÑурÑи:" msgid "Total: " msgstr "Общо: " msgid "Need: " msgstr "Още: " msgid "Load Game" msgstr "Зареди Игра" msgid "No save files to load." msgstr "ÐÑма запазени файлове за зареждане." msgid "New Game" msgstr "Ðова Игра" msgid "Start a single or multi-player game." msgstr "Започни игра за един или нÑколко играча." msgid "Load a previously saved game." msgstr "Зареди запазена игра." msgid "Save Game" msgstr "Запази играта" msgid "Save the current game." msgstr "Запази текущата игра." msgid "Quit" msgstr "ÐапуÑкане" msgid "Quit out of Heroes of Might and Magic II." msgstr "ÐапуÑни Heroes of Might and Magic II." msgid "Language" msgstr "Език" msgid "Graphics" msgstr "Графика" msgid "Black & White" msgstr "Черно-бÑл" msgid "Mouse Cursor" msgstr "Показалец" msgid "Color" msgstr "Цветен" msgid "Text Support" msgstr "Доп. информациÑ" msgid "Change the language of the game." msgstr "ПромÑна на езика на играта." msgid "Select Game Language" msgstr "Език на играта" msgid "Change the graphics settings of the game." msgstr "Промени графичните наÑтройки на играта." msgid "Toggle colored cursor on or off. This is only an aesthetic choice." msgstr "Включи или изключи Ñ†Ð²ÐµÑ‚Ð½Ð¸Ñ ÐºÑƒÑ€Ñор. Това е Ñамо еÑтетичеÑки избор." msgid "" "Toggle text support mode to output extra information about windows and " "events in the game." msgstr "" "Превключи режима на текÑтова поддръжка, за да изведеш допълнителна " "Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° прозорци и ÑÑŠÐ±Ð¸Ñ‚Ð¸Ñ Ð² играта." msgid "" "Map\n" "Difficulty" msgstr "" "ТрудноÑÑ‚\n" "на Карта" msgid "" "Game\n" "Difficulty" msgstr "" "ТрудноÑÑ‚\n" "на играта" msgid "Rating" msgstr "КлаÑиране" msgid "Map Size" msgstr "Размер на карта" msgid "Opponents" msgstr "Противници" msgid "Class" msgstr "Вид" msgid "" "Victory\n" "Conditions" msgstr "" "УÑÐ»Ð¾Ð²Ð¸Ñ Ð·Ð°\n" "Победа" msgid "" "Loss\n" "Conditions" msgstr "" "УÑÐ»Ð¾Ð²Ð¸Ñ Ð·Ð°\n" "Загуба" msgid "First select recipients!" msgstr "Първо избери получатели!" msgid "You cannot select %{resource}!" msgstr "Ðе можеш да избереш %{resource}!" msgid "Select count %{resource}:" msgstr "Избери брой %{resource}:" msgid "Select Recipients" msgstr "Избери Получатели" msgid "Your Funds" msgstr "Твоите ÑредÑтва" msgid "Planned Gift" msgstr "Планиран подарък" msgid "Gift from %{name}" msgstr "Подарък от %{name}" msgid "Resolution" msgstr "РезолюциÑ" msgid "Fullscreen" msgstr "ЦÑл екран" msgid "window|Mode" msgstr "Режим" msgid "Windowed" msgstr "Ð’ прозорец" msgid "V-Sync" msgstr "V-ÑинхронизациÑ" msgid "on" msgstr "вкл" msgid "FPS" msgstr "" msgid "System Info" msgstr "СиÑтемна информациÑ" msgid "Change the resolution of the game." msgstr "ПромÑна на резолюциÑта на играта." msgid "Select Game Resolution" msgstr "Избери Ñ€ÐµÐ·Ð¾Ð»ÑŽÑ†Ð¸Ñ Ð½Ð° играта" msgid "Toggle between fullscreen and windowed modes." msgstr "Превключи между цÑл екран и прозорец." msgid "" "The V-Sync option can be enabled to resolve flickering issues on some " "monitors." msgstr "" "ОпциÑта V-Sync може да бъде активирана за разрешаване на проблеми Ñ Ñ‚Ñ€ÐµÐ¿Ñ‚ÐµÐ½Ðµ " "на нÑкои монитори." msgid "Show extra information such as FPS and current time." msgstr "Показване на допълнителна Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ ÐºÐ°Ñ‚Ð¾ FPS и текущо време." msgid "Hotkey: " msgstr "" msgid "Category: " msgstr "" #, fuzzy msgid "Event: " msgstr "Събитие" msgid "Hot Keys:" msgstr "Бързи клавиши:" msgid "Evil" msgstr "Зъл" msgid "Good" msgstr "Добър" msgid "Interface Type" msgstr "ИнтерфейÑ" msgid "Hide" msgstr "Скрий" msgid "Show" msgstr "Покажи" msgid "Interface" msgstr "ИнтерфейÑ" msgid "Slow" msgstr "Бавно" msgid "Normal" msgstr "Ðормлано" msgid "Fast" msgstr "Бързо" msgid "Very Fast" msgstr "Мн. бързo" msgid "Scroll Speed" msgstr "СкороÑÑ‚ на Превъртане" msgid "Toggle the type of interface you want to use." msgstr "Превключи типа на интерфейÑа, който иÑкаш да използваш." msgid "Toggle interface visibility." msgstr "Превключва видимоÑтта на интерфейÑа." msgid "Toggle colored cursor on or off. This is only an esthetic choice." msgstr "" "Включване/изключване на цветните курÑори. Това е Ñамо еÑтетичеÑки избор." msgid "Sets the speed at which you scroll the window." msgstr "Задава ÑкороÑтта, Ñ ÐºÐ¾Ñто превърташ прозореца." msgid "Select Game Language:" msgstr "Език на играта:" msgid "Click to choose the selected language." msgstr "Кликни, за да предпочетеш Ð¸Ð·Ð±Ñ€Ð°Ð½Ð¸Ñ ÐµÐ·Ð¸Ðº." msgid "%{name} has gained a level." msgstr "%{name} Ñпечели ниво." msgid "%{skill} +1" msgstr "%{skill} +1" msgid "You have learned %{skill}." msgstr "Ðаучи %{skill}." msgid "" "%{name} has gained a level.\n" "\n" "%{skill} +1" msgstr "" "%{name} е придобил ниво.\n" "\n" "%{skill} +1" msgid "" "You may learn either:\n" "%{skill1}\n" "or\n" "%{skill2}" msgstr "" "Можеш да научиш или:\n" "%{skill1}\n" "или\n" "%{skill2}" msgid "" "Please inspect our fine wares. If you feel like offering a trade, click on " "the items you wish to trade with and for." msgstr "" "МолÑ, прегледай нашите изиÑкани Ñтоки. Ðко иÑкаш да предложиш размÑна, " "щракни върху артикулите, Ñ ÐºÐ¾Ð¸Ñ‚Ð¾ иÑкаш да размениш и за които иÑкаш да " "размениш." msgid "" "You have received quite a bargain. I expect to make no profit on the deal. " "Can I interest you in any of my other wares?" msgstr "" "МолÑ, провери твите фини Ñтоки. Ðко иÑкаш да предложиш размÑна, щракни върху " "артикулите, Ñ ÐºÐ¾Ð¸Ñ‚Ð¾ иÑкаш да размениш и за които иÑкаш да размениш." msgid "I can offer you %{count} for 1 unit of %{resfrom}." msgstr "Мога да ти предложа %{count} за 1 единица от %{resfrom}." msgid "I can offer you 1 unit of %{resto} for %{count} units of %{resfrom}." msgstr "Мога да ти предложа 1 единица %{resto} за %{count} единици %{resfrom}." msgid "Min" msgstr "Минимално" msgid "Qty to trade" msgstr "КоличеÑтво за търговиÑ" msgid "Trading Post" msgstr "ТърговÑки щанд" msgid "Your Resources" msgstr "Твоите реÑурÑи" msgid "Available Trades" msgstr "Ðалични Ñделки" msgid "n/a" msgstr "без" msgid "guarded by %{count} %{monster}" msgstr "охранÑвано от %{count} %{monster}" msgid "guarded by " msgstr "охранÑван от " msgid "(available: %{count})" msgstr "(наличен: %{count})" msgid "(empty)" msgstr "(празен)" msgid "already learned" msgstr "вече научен" #, fuzzy msgid "treeOfKnowledge|free" msgstr "Дърво на познанието" #, fuzzy msgid "already claimed" msgstr "вече научен" msgid "not claimed" msgstr "" msgid "already knows this skill" msgstr "вече владее това умение" msgid "already has max skills" msgstr "вече има макÑимални умениÑ" msgid "(already visited)" msgstr "(вече поÑетен)" msgid "(not visited)" msgstr "(не поÑещаван)" msgid "%{color} Barrier" msgstr "%{color} Бариера" msgid "(tent visited)" msgstr "(поÑетена палатка)" msgid "%{color} Tent" msgstr "%{color} палатка" msgid "Road" msgstr "Път" msgid "(digging ok)" msgstr "(добра за копаене)" msgid "(no digging)" msgstr "(без копаене)" msgid "penalty: %{cost}" msgstr "неуÑтойка: %{cost}" msgid "Defenders:" msgstr "Защитници:" msgid "Unknown" msgstr "неизвеÑтен" msgid "%{name} (Level %{level})" msgstr "%{name} (Ðиво %{level})" msgid "Attack:" msgstr "Ðтака:" msgid "Defense:" msgstr "Защита:" msgid "Spell Power:" msgstr "Маг. Ñила:" msgid "Knowledge:" msgstr "ЗнаниÑ:" msgid "Spell Points:" msgstr "МагиÑ:" msgid "Move Points:" msgstr "Ход:" msgid "Uncharted Territory" msgstr "ÐеизÑледвана териториÑ" msgid "Cost per troop:" msgstr "Цена на единица:" msgid "Available: %{count}" msgstr "Ðалични: %{count}" msgid "Number to buy:" msgstr "Покупка на:" msgid "Recruit selected monsters." msgstr "Вербувай избраните чудовища." msgid "Select maximum monsters to be recruited." msgstr "Избери макÑимален брой чудовища, които да бъдат наети." msgid "Select only 1 monster to be recruited." msgstr "Избери Ñамо 1 чудовище, което да бъде наето." msgid "Select Game Resolution:" msgstr "Изберете Ñ€ÐµÐ·Ð¾Ð»ÑŽÑ†Ð¸Ñ Ð½Ð° играта:" msgid "Click to apply the selected resolution." msgstr "Кликни, за да приложиш избраната резолюциÑ." msgid "Click to apply the entered text." msgstr "Кликни, за да приложиш Ð²ÑŠÐ²ÐµÐ´ÐµÐ½Ð¸Ñ Ñ‚ÐµÐºÑÑ‚." msgid "Click to open the Virtual Keyboard dialog." msgstr "Щракни, за да отвориш Ð´Ð¸Ð°Ð»Ð¾Ð³Ð¾Ð²Ð¸Ñ Ð¿Ñ€Ð¾Ð·Ð¾Ñ€ÐµÑ† за виртуална клавиатура." msgid "Open Virtual Keyboard" msgstr "Отвори виртуална клавиатура" msgid "How many troops to move?" msgstr "Колко единици ще премеÑтите?" msgid "Fast separation into slots:" msgstr "Бързо разделÑне на Ñлотове:" msgid "Map: " msgstr "Карта: " msgid "" "\n" "\n" "Month: " msgstr "" "\n" "\n" "МеÑец: " msgid ", Week: " msgstr ", Седмица: " msgid ", Day: " msgstr ", Ден: " msgid "" "\n" "\n" "Location: " msgstr "" "\n" "\n" "МеÑтоположение: " msgid "Click to save the current game." msgstr "Кликни, за да запазиш текущата игра." msgid "Click to load a previously saved game." msgstr "Кликни, за да заредиш предварително запазена игра." msgid "Are you sure you want to delete file:" msgstr "Сигурен ли Ñи, че иÑкаш да изтриеш файла:" msgid "Warning!" msgstr "Внимание!" msgid "File to Save:" msgstr "Файл за запазване:" msgid "File to Load:" msgstr "Файл за зареждане:" msgid "Accept the choice made." msgstr "Приемете Ð½Ð°Ð¿Ñ€Ð°Ð²ÐµÐ½Ð¸Ñ Ð¸Ð·Ð±Ð¾Ñ€." msgid "%{color} %{race} hero" msgstr "%{color} %{race} герой" #, fuzzy msgid "Terrain object" msgstr "Режим терен" msgid "Select Skill:" msgstr "Избери умение:" msgid "Select Spell:" msgstr "Избери заклинание:" msgid "Select Artifact:" msgstr "Избери артефакт:" msgid "Select Monster:" msgstr "Избери чудовище:" msgid "Select Hero:" msgstr "Избери герой:" #, fuzzy msgid "Select Treasure:" msgstr "Избери герой:" #, fuzzy msgid "Select Ocean Object:" msgstr "ОкеанÑки обекти" #, fuzzy msgid "Castle/town placing" msgstr "Опции на замъка" msgid "doubleLinedRace|Neutral" msgstr "" "Ðеутра-\n" "лен" msgid "race|Random" msgstr "Случаен" #, fuzzy msgid "Click to start placing the selected castle/town." msgstr "Кликни, за да приложиш избраната резолюциÑ." #, fuzzy msgid "Click to select town placing." msgstr "Кликни, за да покажеш ÑÐ»ÐµÐ´Ð²Ð°Ñ‰Ð¸Ñ Ð³Ñ€Ð°Ð´." #, fuzzy msgid "Click to select castle placing." msgstr "Кликни тук, за да избереш кой Ñценарий да играеш." #, fuzzy msgid "%{color} %{race} %{townOrCastle}" msgstr "%{color} %{race} герой" msgid "race|Neutral" msgstr "Ðеутрален" msgid "You will place" msgstr "" #, fuzzy msgid "Click to select this class." msgstr "Кликни тук, за да избереш кой Ñценарий да играеш." #, fuzzy msgid "Click to select this color." msgstr "Кликни тук, за да избереш кой Ñценарий да играеш." #, fuzzy msgid "Select Dwelling:" msgstr "Избери заклинание:" #, fuzzy msgid "Select Landscape Object:" msgstr "ОкеанÑки обекти" msgid "Mine placing" msgstr "" #, fuzzy msgid "" "Resource\n" "type:" msgstr "РеÑурÑ" #, fuzzy msgid "%{mineName} appearance:" msgstr "%{name} %{race}" #, fuzzy msgid "Click to select %{object} as the resource generator to be placed." msgstr "Кликни тук, за да избереш кой Ñценарий да играеш." #, fuzzy msgid "Select Mountain Object:" msgstr "ОкеанÑки обекти" #, fuzzy msgid "Select Rock Object:" msgstr "ОкеанÑки обекти" #, fuzzy msgid "Select Tree Object:" msgstr "ОкеанÑки обекти" #, fuzzy msgid "Select Power Up Object:" msgstr "ОкеанÑки обекти" #, fuzzy msgid "Select Adventure Object:" msgstr "ОкеанÑки обекти" msgid "Map Type:\n" msgstr "Тип на карта:\n" msgid "The Succession Wars" msgstr "Войните за наÑледÑтво" msgid "The Price of Loyalty" msgstr "Цената на лоÑлноÑтта" #, fuzzy msgid "Resurrection" msgstr "ВъзкреÑÑване" msgid "Lose all your heroes and towns." msgstr "Ðко загубиш вÑичките Ñи герои и градове." msgid "Lose a specific town." msgstr "ОÑвободи определен град." msgid "Lose a specific hero." msgstr "ОÑвободи определен герой." msgid "Run out of time. Fail to win by a certain point." msgstr "Времето Ñвърши. Загуба в този момент." msgid "Loss Condition" msgstr "СъÑтоÑние на загуба" msgid "Defeat all enemy heroes and towns." msgstr "Победи вÑички вражеÑки герои и градове." msgid "Capture a specific town." msgstr "Превземи конкретен град." msgid "Defeat a specific hero." msgstr "Победи конкретен герой." msgid "Find a specific artifact." msgstr "Ðамери конкретен артефакт." msgid "Your side defeats the opposing side." msgstr "ТвоÑта Ñтрана побеждава противниковата Ñтрана." msgid "Accumulate a large amount of gold." msgstr "Ðатрупай голÑмо количеÑтво злато." msgid "Victory Condition" msgstr "УÑловие за победа" msgid "Map difficulty:" msgstr "ТрудноÑÑ‚ на карта:" msgid "N" msgstr "С" msgid "No maps exist at that size" msgstr "ÐÑма карти в този размер" msgid "Small Maps" msgstr "Малки карти" msgid "View only maps of size small (36 x 36)." msgstr "Преглеждайте Ñамо карти Ñ Ð¼Ð°Ð»ÑŠÐº размер (36 x 36)." msgid "Medium Maps" msgstr "Средни карти" msgid "View only maps of size medium (72 x 72)." msgstr "Преглеждайте Ñамо карти ÑÑŠÑ Ñреден размер (72 x 72)." msgid "Large Maps" msgstr "Големи карти" msgid "View only maps of size large (108 x 108)." msgstr "Преглеждайте Ñамо карти Ñ Ð³Ð¾Ð»Ñм размер (108 x 108)." msgid "Extra Large Maps" msgstr "Много големи карти" msgid "View only maps of size extra large (144 x 144)." msgstr "Преглеждайте Ñамо карти Ñ Ð¼Ð½Ð¾Ð³Ð¾ големи размери (144 x 144)." msgid "All Maps" msgstr "Ð’Ñички карти" msgid "View all maps, regardless of size." msgstr "Виж вÑички карти, незавиÑимо от размера." msgid "Players Icon" msgstr "Брой на играчи" msgid "" "Indicates how many players total are in the scenario. Any positions not " "occupied by humans will be occupied by computer players." msgstr "" "Показва колко общо играчи има в ÑценариÑ. Ð’Ñички позиции, които не Ñа заети " "от хора, ще бъдат заети от компютърни играчи." msgid "" "Indicates whether the map\n" "is small (36 x 36), medium\n" "(72 x 72), large (108 x 108),\n" "or extra large (144 x 144)." msgstr "" "Показва дали картата\n" "е малък (36 x 36), Ñреден\n" "(72 x 72), голÑм (108 x 108),\n" "или изключително голÑм размер (144 x 144)." msgid "Size Icon" msgstr "Размер на карта" msgid "" "Indicates whether the map is made for \"The Succession Wars\" or \"The Price " "of Loyalty\" version of the game." msgstr "" "Показва дали картата е направена за верÑиÑта на играта \"ÐаÑледените войни\" " "или за \"Цената на лоÑлноÑтта\"." msgid "Map Type" msgstr "Тип на карта" msgid "Selected Name" msgstr "Избрано име" msgid "The name of the currently selected map." msgstr "Името на текущо избраната карта." msgid "Selected Map Difficulty" msgstr "Избрана трудноÑÑ‚ на карта" msgid "" "The map difficulty of the currently selected map. The map difficulty is " "determined by the scenario designer. More difficult maps might include more " "or stronger enemies, fewer resources, or other special conditions making " "things tougher for the human player." msgstr "" "ТрудноÑтта на картата на текущо избраната карта. ТрудноÑтта на картата Ñе " "Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»Ñ Ð¾Ñ‚ дизайнера на ÑценариÑ. По-трудните карти може да включват повече " "или по-Ñилни врагове, по-малко реÑурÑи или други Ñпециални уÑловиÑ, които " "правÑÑ‚ нещата по-трудни за играча." msgid "Selected Description" msgstr "Избрано опиÑание" msgid "The description of the currently selected map." msgstr "ОпиÑание на избраната в момента карта." msgid "Jump" msgstr "Скок" msgid "Hero Speed" msgstr "СкороÑÑ‚ на ГероÑ" msgid "Don't Show" msgstr "Ðе показвай" msgid "Enemy Speed" msgstr "СкороÑÑ‚ на Противник" msgid "Auto Resolve" msgstr "Ðвто" msgid "Auto, No Spells" msgstr "Ðвто, без магий" msgid "Battles" msgstr "СражениÑ" msgid "autoBattle|Manual" msgstr "Ръчно" msgid "Change the speed at which your heroes move on the main screen." msgstr "Промени ÑкороÑтта, Ñ ÐºÐ¾Ñто твоите герои Ñе движат на Ð³Ð»Ð°Ð²Ð½Ð¸Ñ ÐµÐºÑ€Ð°Ð½." msgid "" "Sets the speed that A.I. heroes move at. You can also elect not to view A.I. " "movement at all." msgstr "" "Задава ÑкороÑтта, коÑто И.И. героите Ñе движат. Можеш Ñъщо да избереш да не " "гледаш движението на И.И.." msgid "Change the interface settings of the game." msgstr "Промени Ð¸Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Ð½Ð°Ñтройките на играта." msgid "Interface Settings" msgstr "Ð˜Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ ÐаÑтройки" msgid "Toggle instant battle mode." msgstr "Превключи режима на незабавна битка." msgid "Att." msgstr "Ðтак." msgid "Def." msgstr "Защ." msgid "Power" msgstr "Сила" msgid "Knowl" msgstr "Зна." msgid "1st" msgstr "1ви" msgid "2nd" msgstr "2ри" msgid "3rd" msgstr "3ти" msgid "4th" msgstr "4ти" msgid "5th" msgstr "5ти" msgid "6th" msgstr "6ти" msgid "Oracle: Player Rankings" msgstr "Оракул: КлаÑиране на играчите" msgid "Thieves' Guild: Player Rankings" msgstr "Ð“Ð¸Ð»Ð´Ð¸Ñ Ð½Ð° крадците: КлаÑиране на играчите" msgid "Number of Towns:" msgstr "Брой на градовете:" msgid "Number of Castles:" msgstr "Брой на замъците:" msgid "Number of Heroes:" msgstr "Брой на героите:" msgid "Gold in Treasury:" msgstr "Злато в хазната:" msgid "Wood & Ore:" msgstr "Дърво и руда:" msgid "Gems, Cr, Slf & Mer:" msgstr "Кам, Кр, СÑÑ€ и Жив:" msgid "Obelisks Found:" msgstr "Ðамерени обелиÑки:" msgid "Artifacts:" msgstr "Ðртефакти:" msgid "Total Army Strength:" msgstr "Обща Ñила на армиÑта:" msgid "Income:" msgstr "Приходи:" msgid "Best Hero:" msgstr "Ðай-добър герой:" msgid "Best Hero Stats:" msgstr "Ðай-добри ÑтатиÑтики за герои:" msgid "Personality:" msgstr "ЛичноÑÑ‚:" msgid "Best Monster:" msgstr "Ðай-добро чудовище:" #, fuzzy msgid "Map filename" msgstr "Размер на карта" msgid "" "Are you sure you want to load a new map? (Any unsaved changes to the current " "map will be lost.)" msgstr "" "Сигурен ли Ñи, че иÑкаш да заредиш нова карта? (Ð’Ñички незапазени промени в " "текущата карта ще бъдат загубени.)" msgid "" "Are you sure you want to create a new map? (Any unsaved changes to the " "current map will be lost.)" msgstr "" "Сигурен ли Ñи, че иÑкаш да Ñъздадеш нова карта? (Ð’Ñички незапазени промени в " "текущата карта ще бъдат загубени.)" msgid "" "Create a new map, either from scratch or using the random map generator." msgstr "" "Създай нова карта от нулата, или като използвате генератора на произволни " "карти." msgid "New Map" msgstr "Ðова Карта" msgid "Load Map" msgstr "Зареди Карта" msgid "Load an existing map." msgstr "Зареди ÑъщеÑтвуваща карта." msgid "Save Map" msgstr "Запазване на картата" msgid "Save the current map." msgstr "Запазете текущата карта." msgid "Quit out of the map editor." msgstr "Излез от редактора на карти." #, fuzzy msgid "Objects cannot be placed outside the map." msgstr "Това заклинание не може да Ñе направи на кораб." msgid "Monsters cannot be placed on water." msgstr "Чудовищата не могат да Ñе поÑтавÑÑ‚ върху вода." msgid "Choose a tile which does not contain any objects." msgstr "Избери плочка, коÑто не Ñъдържа никакви предмети." #, fuzzy msgid "Treasures cannot be placed on water." msgstr "Това заклинание не може да Ñе направи на кораб." msgid "Heroes cannot be placed on water." msgstr "Героите не могат да бъдат поÑтавени на вода." msgid "" "A maximum of %{count} heroes of the same color can be placed on the map." msgstr "" #, fuzzy msgid "Artifacts cannot be placed on water." msgstr "Това заклинание не може да Ñе направи на кораб." #, fuzzy msgid "Mountains cannot be placed on water." msgstr "Чудовищата не могат да Ñе поÑтавÑÑ‚ върху вода." #, fuzzy msgid "Rocks cannot be placed on water." msgstr "Чудовищата не могат да Ñе поÑтавÑÑ‚ върху вода." #, fuzzy msgid "Trees cannot be placed on water." msgstr "Това заклинание не може да Ñе направи на кораб." #, fuzzy msgid "Ocean object must be placed on water." msgstr "Това заклинание не може да Ñе направи на кораб." #, fuzzy msgid "Landscape objects cannot be placed on water." msgstr "Героите не могат да бъдат поÑтавени на вода." #, fuzzy msgid "Towns cannot be placed on water." msgstr "Чудовищата не могат да Ñе поÑтавÑÑ‚ върху вода." #, fuzzy msgid "Mines cannot be placed on water." msgstr "Чудовищата не могат да Ñе поÑтавÑÑ‚ върху вода." #, fuzzy msgid "Dwellings cannot be placed on water." msgstr "Чудовищата не могат да Ñе поÑтавÑÑ‚ върху вода." #, fuzzy msgid "Power-ups cannot be placed on water." msgstr "Чудовищата не могат да Ñе поÑтавÑÑ‚ върху вода." #, fuzzy msgid "Adventure objects cannot be placed on water." msgstr "Героите не могат да бъдат поÑтавени на вода." #, fuzzy msgid "Used to place %{object}." msgstr "Използва Ñе за поÑтавÑне на герой." #, fuzzy msgid "Select object type" msgstr "ОкеанÑки обекти" #, fuzzy msgid "" "Click here to\n" "select a monster." msgstr "Кликни тук, за да избереш кой Ñценарий да играеш." #, fuzzy msgid "" "Click here to\n" "select another monster." msgstr "МолÑ, избери друг герой." msgid "" "Cell\n" "Details" msgstr "" msgid "Roads" msgstr "Пътища" msgid "Streams" msgstr "Потоци" #, fuzzy msgid "Erase" msgstr "Режим на изтриване" msgid "Mountains" msgstr "Планини" #, fuzzy msgid "Rocks" msgstr "Скала" msgid "Trees" msgstr "Дървета" #, fuzzy msgid "Water Objects" msgstr "Обекти от пущинака" #, fuzzy msgid "Miscellaneous" msgstr "ФиниаÑ" msgid "Artifacts" msgstr "Ðртефакти" #, fuzzy msgid "Dwellings" msgstr "Хобити" msgid "Mines" msgstr "Мини" #, fuzzy msgid "Power-ups" msgstr "Сила" msgid "Treasures" msgstr "Съкровища" msgid "Towns" msgstr "Градове" msgid "Heroes" msgstr "Герой" msgid "editorErasure|Terrain" msgstr "" msgid "editorErasure|Castles" msgstr "" msgid "editorErasure|Monsters" msgstr "" msgid "editorErasure|Heroes" msgstr "" msgid "editorErasure|Artifacts" msgstr "" msgid "editorErasure|Roads" msgstr "" msgid "editorErasure|Streams" msgstr "" msgid "editorErasure|Treasures" msgstr "" msgid "" "Draws terrain in\n" "%{size} by %{size} square increments." msgstr "" "Ðачертава терена\n" "%{size} на %{size} квадратни Ñтъпки." msgid "" "Erases objects in\n" "%{size} by %{size} square increments." msgstr "" "Изтрива обекти\n" "%{size} на %{size} квадратни Ñтъпки." msgid "Small Brush" msgstr "Малка четка" msgid "Medium Brush" msgstr "Средна четка" msgid "Large Brush" msgstr "ГолÑма четка" msgid "Area Fill" msgstr "Попълване на облаÑÑ‚" msgid "Used to click and drag for filling in large areas." msgstr "Използва Ñе кликване и плъзгане за запълване на големи облаÑти." msgid "Clear Area" msgstr "ИзчиÑтва облаÑÑ‚" msgid "Used to click and drag for clearing large areas." msgstr "Използва Ñе кликване и плъзгане за изчиÑтване на големи облаÑти." msgid "" "Costs %{rate} times normal movement for all heroes. (Pathfinding reduces or " "eliminates the penalty.)" msgstr "" "Разходи %{rate} пъти нормално движение за вÑички герои. (Откриването на Ð¿ÑŠÑ‚Ñ " "намалÑва или елиминира наказанието.)" msgid "Traversable only by boat." msgstr "Преминаване Ñамо Ñ Ð»Ð¾Ð´ÐºÐ°." msgid "No special modifiers." msgstr "Без Ñпециални модификатори." msgid "Toggle the erasure of %{type} objects." msgstr "Превключи изтриването на %{type} обекти." msgid "" "Objects of this type will be deleted with the Erase tool. Left-click here to " "deselect this type. Press and hold this button to deselect all other object " "types." msgstr "" "Обекти от този тип ще бъдат изтрити Ñ Ð¸Ð½Ñтрумента Изтриване. Щракни Ñ Ð»ÐµÐ²Ð¸Ñ " "бутон тук, за да премахнеш отметката от този тип. ÐатиÑни и задръж този " "бутон, за да демаркираш вÑички други типове обекти." msgid "" "Objects of this type will NOT be deleted with the Erase tool. Left-click " "here to select this type. Press and hold this button to select all other " "object types." msgstr "" "Обекти от този тип ÐЯМРда бъдат изтривани Ñ Ð¸Ð½Ñтрумента за изтриване. " "Щракни Ñ Ð»ÐµÐ²Ð¸Ñ Ð±ÑƒÑ‚Ð¾Ð½ тук, за да избереш този тип. ÐатиÑни и задръж този " "бутон, за да избереш вÑички други типове обекти." msgid "Terrain Mode" msgstr "Режим терен" msgid "Used to draw the underlying grass, dirt, water, etc. on the map." msgstr "" "Използва Ñе за начертаване на оÑновната трева, мръÑотиÑ, вода и Ñ‚.н. върху " "картата." #, fuzzy msgid "Landscape Objects Mode" msgstr "Обектен режим" #, fuzzy msgid "" "Used to place landscape objects (mountains, rocks, trees, etc.) on the map." msgstr "" "Използва Ñе за поÑтавÑне на обекти (планини, дървета, Ñъкровища и др.) върху " "картата." msgid "Detail Mode" msgstr "Подробен режим" msgid "Used for special editing of monsters, heroes and towns." msgstr "Използва Ñе за Ñпециално редактиране на чудовища, герои и градове." #, fuzzy msgid "Adventure Objects Mode" msgstr "Опции за приключениÑ" #, fuzzy msgid "" "Used to place adventure objects (artifacts, dwellings, mines, treasures, " "etc.) on the map." msgstr "" "Използва Ñе за поÑтавÑне на обекти (планини, дървета, Ñъкровища и др.) върху " "картата." #, fuzzy msgid "Kingdom Objects Mode" msgstr "Обектен режим" #, fuzzy msgid "Used to place kingdom objects (towns, castles and heroes) on the map." msgstr "" "Използва Ñе за поÑтавÑне на обекти (планини, дървета, Ñъкровища и др.) върху " "картата." #, fuzzy msgid "Monsters Mode" msgstr "Чудовища" #, fuzzy msgid "Used to place monsters on the map." msgstr "" "Използва Ñе за поÑтавÑне\n" "на група чудовища." msgid "Allows you to draw streams by clicking and dragging." msgstr "ПозволÑва ти да риÑуваш потоци чрез кликване и плъзгане." msgid "Stream Mode" msgstr "Режим на предаване" msgid "Allows you to draw roads by clicking and dragging." msgstr "ПозволÑва ти да риÑуваш пътища чрез кликване и плъзгане." msgid "Road Mode" msgstr "Пътен режим" msgid "Erase Mode" msgstr "Режим на изтриване" msgid "Used to erase objects from the map." msgstr "Използва Ñе за изтриване на обекти от картата." msgid "Change between zoom and normal view." msgstr "ПромÑна между мащабиране и нормален изглед." msgid "Magnify" msgstr "Увеличи" msgid "Undo" msgstr "ОтмÑна" msgid "Undo your last action." msgstr "Отмени поÑледното Ñи дейÑтвие." msgid "Create a new map either from scratch or using the random map generator." msgstr "" "Създай нова карта или от начало, или като използваш генератора на произволни " "карти." msgid "Edit map title, description, and other general information." msgstr "Редактирай заглавието на картата, опиÑанието и друга обща информациÑ." msgid "Specifications" msgstr "Спецификации" msgid "File Options" msgstr "Опции за файлове" msgid "" "Open the file options menu, where you can save or load maps, or quit out of " "the editor." msgstr "" "Отвори менюто Ñ Ð¾Ð¿Ñ†Ð¸Ð¸ за файлове, където можеш да запазваш или зареждаш " "карти или да излезеш от редактора." msgid "View the editor system options, which let you customize the editor." msgstr "" "Виж ÑиÑтемните опции на редактора, които ти позволÑват да перÑонализираш " "редактора." msgid "Create a map that is %{size} squares wide and %{size} squares high." msgstr "Създай карта Ñ ÑˆÐ¸Ñ€Ð¸Ð½Ð° %{size} квадрата и виÑочина %{size} квадрата." msgid "Cancel back to the New Map menu." msgstr "Отказ обратно към Меню за нова карта." msgid "Cancel back to the main menu." msgstr "Отказ обратно към главното меню." msgid "From Scratch" msgstr "От начало" msgid "Start from scratch with a blank map." msgstr "Започни от начало Ñ Ð¿Ñ€Ð°Ð·Ð½Ð° карта." msgid "Create a randomly generated map." msgstr "Създай произволно генерирана карта." msgid "Random" msgstr "Случаен" msgid "Cancel back to the Map Editor main menu." msgstr "Отказ обратно към главното меню на Ð¡ÑŠÐ·Ð´Ð°Ñ‚ÐµÐ»Ñ Ð½Ð° карти." msgid "No maps available!" msgstr "ÐÑма налични карти!" msgid "Warning" msgstr "Внимание" msgid "difficulty|Easy" msgstr "ЛеÑна" msgid "difficulty|Normal" msgstr "Ðормална" msgid "difficulty|Hard" msgstr "Трудна" msgid "difficulty|Expert" msgstr "ЕкÑпертна" msgid "difficulty|Impossible" msgstr "Ðевъзможна" msgid "and more..." msgstr "и още..." msgid "Easy" msgstr "ЛеÑно" msgid "Hard" msgstr "Трудно" msgid "Campaign Difficulty" msgstr "ТрудноÑÑ‚ на КампаниÑта" msgid "" "Choose this difficulty if you want to prefer game story over challenge. AI " "is weaker in comparison with normal difficulty." msgstr "" "Избери тази трудноÑÑ‚, ако предпочиташ иÑториÑта на играта пред " "предизвикателÑтвото. И.И. е по-Ñлаб в Ñравнение Ñ Ð½Ð¾Ñ€Ð¼Ð°Ð»Ð½Ð°Ñ‚Ð° трудноÑÑ‚." msgid "" "Choose this difficulty to enjoy the campaign as per the original design." msgstr "" "Избери тази трудноÑÑ‚, за да Ñе наÑладиш на кампаниÑта Ñпоред Ð¾Ñ€Ð¸Ð³Ð¸Ð½Ð°Ð»Ð½Ð¸Ñ " "дизайн." msgid "" "Choose this difficulty if you want challenge. AI is stronger in comparison " "with normal difficulty." msgstr "" "Избери тази трудноÑÑ‚, ако иÑкаш предизвикателÑтво. И.И. е по-Ñилен в " "Ñравнение Ñ Ð½Ð¾Ñ€Ð¼Ð°Ð»Ð½Ð°Ñ‚Ð° трудноÑÑ‚." msgid "" "Congratulations!\n" "\n" "Days: %{days}\n" msgstr "" "ЧеÑтито!\n" "\n" "Дни: %{days}\n" msgid "" "\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" "\n" "ТрудноÑÑ‚: %{difficulty}\n" "\n" msgid "" "Score: %{score}\n" "\n" "Rating:\n" "%{rating}" msgstr "" "Резултат: %{score}\n" "\n" "Рейтинг:\n" "%{rating}" msgid "Start the selected scenario." msgstr "Стартирай Ð¸Ð·Ð±Ñ€Ð°Ð½Ð¸Ñ Ñценарий." msgid "View Intro" msgstr "Виж въведение" msgid "View Intro videos for the current state of the campaign." msgstr "Виж въвеждащи видеоклипове за текущото ÑÑŠÑтоÑние на кампаниÑта." msgid "" "Select the campaign difficulty. This can be lowered at any point during the " "campaign." msgstr "" "Избери трудноÑтта на кампаниÑта. Това може да бъде намалено по вÑÑко време " "на кампаниÑта." msgid "Restart the current scenario." msgstr "РеÑтартирай Ñ‚ÐµÐºÑƒÑ‰Ð¸Ñ Ñценарий." msgid "Difficulty" msgstr "ТрудноÑÑ‚" msgid "" "You have changed to a lower difficulty for the campaign. You will not be " "able to revert this after this point. The high score will be calculated " "based solely on the new difficulty. Do you want to proceed?" msgstr "" "Промени кампаниÑта на по-ниÑка трудноÑÑ‚. ÐÑма да можеш да върнете това Ñлед " "тази точка. ВиÑокиÑÑ‚ резултат ще бъде изчиÑлен въз оÑнова единÑтвено на " "новата трудноÑÑ‚. ИÑкаш ли да продължиш?" msgid "Are you sure you want to restart this scenario?" msgstr "Сигурен ли Ñи, че иÑкаш да реÑтартираш този Ñценарий?" msgid "Campaign Scenario loading failure" msgstr "ÐеуÑпешно зареждане на ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ Ð½Ð° кампаниÑта" msgid "Please make sure that campaign files are correct and present." msgstr "МолÑ, увери Ñе, че файловете на кампаниÑта Ñа правилни и налични." msgid "Days spent" msgstr "Прекарани дни" msgid "The number of days spent on this campaign." msgstr "БроÑÑ‚ дни, прекарани в тази кампаниÑ." msgid "Project Coordination and Core Development" msgstr "ÐšÐ¾Ð¾Ñ€Ð´Ð¸Ð½Ð°Ñ†Ð¸Ñ Ð½Ð° проекта и оÑновно развитие" msgid "Development" msgstr "Разработка" msgid "Visit us at " msgstr "ПоÑетете ни на " msgid "QA and Support" msgstr "Гарантиране на качеÑтвото и поддръжка" msgid "Dev and Support" msgstr "Разработка и поддръжка" msgid "Special Thanks to" msgstr "Специални благодарноÑти на" msgid "and many other contributors!" msgstr "и много други Ñътрудници!" msgid "and many-many other supporters!" msgstr "и много-много други поддръжници!" msgid "Support us at" msgstr "Подкрепете ни на" msgid "local-donation-platform|https://www.patreon.com/fheroes2" msgstr "" msgid "Connect with us at" msgstr "Свържете Ñе Ñ Ð½Ð°Ñ Ð½Ð°" msgid "local-social-network|https://www.facebook.com/groups/fheroes2" msgstr "" msgid "Need help with the game?" msgstr "Ðуждаете Ñе от помощ Ñ Ð¸Ð³Ñ€Ð°Ñ‚Ð°?" msgid "Original project before 0.7" msgstr "Оригинален проект преди 0.7" msgid "Heroes of Might and Magic II: The Succession Wars team" msgstr "Екипът на Heroes of Might and Magic II: The Succession Wars" msgid "Designed and Directed" msgstr "Проектиран и режиÑиран" msgid "Programming and Design" msgstr "Програмиране и дизайн" msgid "Executive Producer" msgstr "Изпълнителен продуцент" msgid "Producer" msgstr "Продуцент" msgid "Additional Design" msgstr "Допълнителен дизайн" msgid "Additional Programming" msgstr "Допълнително програмиране" msgid "Musical Production" msgstr "Музикална продукциÑ" msgid "Music and Sound Design" msgstr "Музика и звуков дизайн" msgid "Vocalists" msgstr "ВокалиÑти" msgid "Art Director" msgstr "Ðрт директор" msgid "Assistant Art Director" msgstr "ÐÑиÑтент арт директор" msgid "Artists" msgstr "Художници" msgid "QA Manager" msgstr "Mениджър по качеÑтвото" msgid "QA" msgstr "ÐÑиÑтент по качеÑтвото" msgid "Writing" msgstr "Стил на пиÑане" msgid "Manual and Helpfile" msgstr "РъководÑтво и помощен файл" msgid "Scenarios" msgstr "Сценарии" msgid "Heroes of Might and Magic II: The Price of Loyalty team" msgstr "Екип на Heroes of Might and Magic II: The Price of Loyalty" msgid "Design Lead" msgstr "Ръководител дизайн" msgid "Designers" msgstr "Дизайнери" msgid "Programming Lead" msgstr "Ръководител програмиране" msgid "Art Lead" msgstr "Ръководител Ðрт" msgid "Playtesters" msgstr "ТеÑтери на играта" msgid "Designer" msgstr "Дизайнер" msgid "Producers" msgstr "Производители" msgid "QA Managers" msgstr "Mениджъри по качеÑтвото" msgid "Sound Design" msgstr "Звуков дизайн" msgid "Town Themes" msgstr "Музикални теми на Градове" msgid "Alto Sax" msgstr "Ðлто ÑакÑ" msgid "Harpsichord and Piano" msgstr "КлавеÑин и пиано" msgid "Basso Vocal" msgstr "БаÑов вокал" msgid "Soprano Vocal" msgstr "Сопрано вокал" msgid "Recorded at %{recordingStudio}" msgstr "ЗапиÑано в %{recordingStudio}" msgid "credits|Manual" msgstr "РуководÑтво по игре" msgid "German Consultant" msgstr "ÐемÑки конÑултант" msgid "Map Designers" msgstr "Дизайнери на картите" msgid "Package Design" msgstr "Дизайн на опаковката" msgid "Your Name" msgstr "Твоето име" msgid "Unknown Hero" msgstr "Ðезнаен герой" msgid "Standard" msgstr "Стандарт" msgid "View High Scores for Standard Maps." msgstr "Виж виÑоките резултати за Ñтандартни карти." msgid "Campaign" msgstr "КампаниÑ" msgid "View High Scores for Campaigns." msgstr "Вижте виÑоките резултати за кампании." msgid "hotkey|default okay event" msgstr "удобрение по подразбиране" msgid "hotkey|default cancel event" msgstr "отказ по подразбиране" msgid "hotkey|default left" msgstr "лÑво по подразбиране" msgid "hotkey|default right" msgstr "дÑÑно по подразбиране" msgid "hotkey|default up" msgstr "нагоре по подразбиране" msgid "hotkey|default down" msgstr "надолу по подразбиране" msgid "hotkey|toggle fullscreen" msgstr "превключване на цÑл екран" msgid "hotkey|toggle text support mode" msgstr "превключване потдръжка на текÑÑ‚" msgid "hotkey|new game" msgstr "нова игра" msgid "hotkey|load game" msgstr "зареди игра" msgid "hotkey|highscores" msgstr "резултати" msgid "hotkey|credits" msgstr "учаÑтници" msgid "hotkey|standard game" msgstr "Ñтандартна игра" msgid "hotkey|campaign game" msgstr "име на кампаниÑ" msgid "hotkey|multi-player game" msgstr "мултиплейър игра" msgid "hotkey|settings" msgstr "наÑтойки" msgid "hotkey|quit" msgstr "напуÑни" msgid "hotkey|select map" msgstr "избери карта" msgid "hotkey|select small map size" msgstr "избери малка карта" msgid "hotkey|select medium map size" msgstr "избери Ñредна карта" msgid "hotkey|select large map size" msgstr "избери голÑма карта" msgid "hotkey|select extra large map size" msgstr "избери много голÑма карта" msgid "hotkey|select all map sizes" msgstr "избери вÑички карти" msgid "hotkey|hotseat game" msgstr "двойна игра" msgid "hotkey|battle only game" msgstr "игра Ñамо за битка" msgid "hotkey|choose the original campaign" msgstr "избери оригиналната кампаниÑ" msgid "hotkey|choose the expansion campaign" msgstr "избери кампаниÑта за разширÑване" msgid "hotkey|map editor main menu" msgstr "Ñъздател на карти оÑновно меню" msgid "hotkey|new map menu" msgstr "ново меню на карта" msgid "hotkey|load map menu" msgstr "зареди меню за карта" msgid "hotkey|new map from scratch" msgstr "нова карта от начало" msgid "hotkey|new random map" msgstr "нова Ñлучайна игра" msgid "hotkey|undo last action" msgstr "отмени поÑледното дейÑтвие" msgid "hotkey|redo last action" msgstr "повтори поÑледното дейÑтвие" msgid "hotkey|roland campaign" msgstr "ÐºÐ°Ð¼Ð¿Ð°Ð½Ð¸Ñ Ð½Ð° роланд" msgid "hotkey|archibald campaign" msgstr "ÐºÐ°Ð¼Ð¿Ð°Ð½Ð¸Ñ Ð½Ð° арчибалд" msgid "hotkey|the price of loyalty campaign" msgstr "цената на лоÑлноÑтта" msgid "hotkey|voyage home campaign" msgstr "ÐºÐ°Ð¼Ð¿Ð°Ð½Ð¸Ñ Ð¿ÑŠÑ‚ÑƒÐ²Ð°Ð½Ðµ до дома" msgid "hotkey|wizard's isle campaign" msgstr "ÐºÐ°Ð¼Ð¿Ð°Ð½Ð¸Ñ Ð½Ð° оÑтрова на магьоÑника" msgid "hotkey|descendants campaign" msgstr "ÐºÐ°Ð¼Ð¿Ð°Ð½Ð¸Ñ Ð½Ð° потомци" msgid "hotkey|select first campaign bonus" msgstr "избери Ð±Ð¾Ð½ÑƒÑ Ð½Ð° първа кампаниÑ" msgid "hotkey|select second campaign bonus" msgstr "избери Ð±Ð¾Ð½ÑƒÑ Ð½Ð° втора кампаниÑ" msgid "hotkey|select third campaign bonus" msgstr "избери Ð±Ð¾Ð½ÑƒÑ Ð½Ð° трета кампаниÑ" msgid "hotkey|view campaign intro" msgstr "виж въведението на кампаниÑта" msgid "hotkey|select campaign difficulty" msgstr "избери трудноÑÑ‚ на кампаниÑта" msgid "hotkey|restart campaign scenario" msgstr "реÑтартирай ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ Ð½Ð° кампаниÑта" msgid "hotkey|world map left" msgstr "карта на Ñвета влÑво" msgid "hotkey|world map right" msgstr "карта на Ñвета вдÑÑно" msgid "hotkey|world map up" msgstr "карта на Ñвета нагоре" msgid "hotkey|world map down" msgstr "карта на Ñвета надолу" msgid "hotkey|world map up left" msgstr "карта на Ñвета нагоре и влÑво" msgid "hotkey|world map up right" msgstr "карта на Ñвета нагоре и вдÑÑно" msgid "hotkey|world map down left" msgstr "карта на Ñвета надолу и влÑво" msgid "hotkey|world map down right" msgstr "карта на Ñвета надолу и вдÑÑно" msgid "hotkey|save game" msgstr "запази играта" msgid "hotkey|next hero" msgstr "ÑÐ»ÐµÐ´Ð²Ð°Ñ‰Ð¸Ñ Ð³ÐµÑ€Ð¾Ð¹" msgid "hotkey|start hero movement" msgstr "започни движението на героÑ" msgid "hotkey|cast adventure spell" msgstr "направи приключенÑко заклинание." msgid "hotkey|put hero to sleep" msgstr "Ñложи Ð³ÐµÑ€Ð¾Ñ Ð´Ð° Ñпи" msgid "hotkey|next town" msgstr "ÑÐ»ÐµÐ´Ð²Ð°Ñ‰Ð¸Ñ Ð³Ñ€Ð°Ð´" msgid "hotkey|end turn" msgstr "завърши хода" msgid "hotkey|file options" msgstr "опции за файл" msgid "hotkey|adventure options" msgstr "опции за приключение" msgid "hotkey|puzzle map" msgstr "карта на пъзела" msgid "hotkey|scenario information" msgstr "Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° ÑценариÑ" msgid "hotkey|dig for artifact" msgstr "копай за артефакт" msgid "hotkey|view world" msgstr "виж Ñвета" msgid "hotkey|kingdom summary" msgstr "резюме за кралÑтвото" msgid "hotkey|default action" msgstr "дейÑтвие по подразбиране" msgid "hotkey|open focus" msgstr "отворен фокуÑ" msgid "hotkey|system options" msgstr "ÑиÑтемни опции" msgid "hotkey|scroll left" msgstr "превъртете налÑво" msgid "hotkey|scroll right" msgstr "превъртете надÑÑно" msgid "hotkey|scroll up" msgstr "превъртете нагоре" msgid "hotkey|scroll down" msgstr "превъртете надолу" msgid "hotkey|toggle control panel" msgstr "превключи ÐºÐ¾Ð½Ñ‚Ñ€Ð¾Ð»Ð½Ð¸Ñ Ð¿Ð°Ð½ÐµÐ»" msgid "hotkey|toggle radar" msgstr "превключи радар" msgid "hotkey|toggle buttons" msgstr "превключи бутоните" msgid "hotkey|toggle status" msgstr "превключи ÑÑŠÑтоÑние" msgid "hotkey|toggle icons" msgstr "превключи иконите" msgid "hotkey|transfer control to ai" msgstr "прехвърли контрола на ai" msgid "hotkey|retreat from battle" msgstr "отÑтъпление от битка" msgid "hotkey|surrender during battle" msgstr "ÐºÐ°Ð¿Ð¸Ñ‚ÑƒÐ»Ð°Ñ†Ð¸Ñ Ð¿Ð¾ време на битка" msgid "hotkey|toggle battle auto mode" msgstr "превключи Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡Ð½Ð¸Ñ Ñ€ÐµÐ¶Ð¸Ð¼ на битка" msgid "hotkey|finish the battle in auto mode" msgstr "завърши битката в автоматичен режим" msgid "hotkey|battle options" msgstr "опции на битка" msgid "hotkey|skip turn in battle" msgstr "пропуÑни ход в битка" msgid "hotkey|cast battle spell" msgstr "направи бойно заклинание" msgid "hotkey|dwelling level 1" msgstr "ниво на жилище 1" msgid "hotkey|dwelling level 2" msgstr "ниво на жилище 2" msgid "hotkey|dwelling level 3" msgstr "ниво на жилище 3" msgid "hotkey|dwelling level 4" msgstr "ниво на жилище 4" msgid "hotkey|dwelling level 5" msgstr "ниво на жилище 5" msgid "hotkey|dwelling level 6" msgstr "ниво на жилище 6" msgid "hotkey|well" msgstr "кладенец" msgid "hotkey|marketplace" msgstr "пазар" msgid "hotkey|mage guild" msgstr "магичеÑка гилдиÑ" msgid "hotkey|shipyard" msgstr "корабоÑтроителница" msgid "hotkey|thieves guild" msgstr "Ð³Ð¸Ð»Ð´Ð¸Ñ Ð½Ð° крадците" msgid "hotkey|tavern" msgstr "кръчма" msgid "hotkey|construction screen" msgstr "екран за изграждане" msgid "hotkey|buy all monsters in well" msgstr "купи вÑички налични чудовища" msgid "hotkey|split stack by half" msgstr "раздели Ñтека наполовина" msgid "hotkey|split stack by one" msgstr "раздели Ñтека на един" msgid "hotkey|join stacks" msgstr "Ñъедини Ñтековете" msgid "hotkey|upgrade troop" msgstr "надгради войÑките" msgid "hotkey|dismiss hero or troop" msgstr "отпрати герой или войÑка" msgid "" "Do you want to regain control from AI? The effect will take place only on " "the next turn." msgstr "ИÑкаш ли да Ñи върнеш контрола от ИИ? Ще има ефектът на ÑÐ»ÐµÐ´Ð²Ð°Ñ‰Ð¸Ñ Ñ…Ð¾Ð´." msgid "" "Do you want to transfer control from you to the AI? The effect will take " "place only on the next turn." msgstr "ИÑкаш ли да Ñи върнеш контрола от ИИ? Ще има ефектът на ÑÐ»ÐµÐ´Ð²Ð°Ñ‰Ð¸Ñ Ñ…Ð¾Ð´." #, fuzzy msgid "Default Actions" msgstr "дейÑтвие по подразбиране" msgid "Global Actions" msgstr "" msgid "Main Menu" msgstr "" msgid "World Map" msgstr "Карта на Ñвета" #, fuzzy msgid "Battle Screen" msgstr "Екран на героÑ" #, fuzzy msgid "Town Screen" msgstr "Екран на героÑ" msgid "Army Actions" msgstr "" msgid "Editor" msgstr "Редактор" msgid "The save file is corrupted." msgstr "ЗапазениÑÑ‚ файл е повреден." msgid "Unsupported save format: " msgstr "Ðеподдържан формат за запиÑ: " msgid "Current game version: " msgstr "Текуща верÑÐ¸Ñ Ð½Ð° играта: " msgid "Last supported version: " msgstr "ПоÑледна поддържана верÑиÑ: " msgid "This file contains a save with an invalid game type." msgstr "Този файл Ñъдържа Ð·Ð°Ð¿Ð¸Ñ Ñ Ð½ÐµÐ²Ð°Ð»Ð¸Ð´ÐµÐ½ тип игра." #, fuzzy msgid "" "This save file requires \"The Price of Loyalty\" game assets, but they have " "not been provided to the engine." msgstr "" "Този файл е запазен за \"Цената на лоÑлноÑтта\", но Ñъответните активи на " "играта не Ñа предоÑтавени на двигателÑ." msgid "This saved game is localized to '" msgstr "Запазената игра е локализирана на \"" msgid "' language, but the current language of the game is '" msgstr "\" език, но текущиÑÑ‚ език на играта е \"" msgid "Hot Seat" msgstr "Горещ Ñтол" msgid "" "Play a Hot Seat game, where 2 to 4 players play around the same computer, " "switching into the 'Hot Seat' when it is their turn." msgstr "" "Играй игра на \"Гореща Ñедалка\", при коÑто от 2-4 играчи играÑÑ‚ на един и " "Ñъщ компютър, превключвайки на \"Гореща Ñедалка\", когато им дойде реда." msgid "A single player game playing out a single map." msgstr "Игра за един играч, играещ на една карта." msgid "Standard Game" msgstr "Стандартна Игра" msgid "A single player game playing through a series of maps." msgstr "Игра за един играч, коÑто Ñе играе през поредица от карти." msgid "Campaign Game" msgstr "КампаниÑ" msgid "Multi-Player Game" msgstr "Мултиплейър игра" msgid "" "A multi-player game, with several human players completing against each " "other on a single map." msgstr "" "Игра за много играчи, Ñ Ð½Ñколко човешки играчи, които Ñе ÑÑŠÑтезават един " "Ñрещу друг на една карта." msgid "fheroes2 Resurrection Team presents" msgstr "fheroes2 Отбор възкрeÑÑване предÑтавÑ" msgid "Greetings!" msgstr "ПоздравлениÑ!" msgid "Welcome to Heroes of Might and Magic II powered by fheroes2 engine!" msgstr "" "Добре дошъл в Heroes of Might and Magic II, задвижван от Ð´Ð²Ð¸Ð³Ð°Ñ‚ÐµÐ»Ñ fheroes2!" #, fuzzy msgid "" "Welcome to Heroes of Might and Magic II powered by the fheroes2 engine!\n" "Before starting the game please select a game resolution." msgstr "" "Добре дошъл в Heroes of Might and Magic II, задвижван от Ð´Ð²Ð¸Ð³Ð°Ñ‚ÐµÐ»Ñ fheroes2! " "Преди да започнеш играта, Ð¼Ð¾Ð»Ñ Ð¸Ð·Ð±ÐµÑ€Ð¸ разделителна ÑпоÑобноÑÑ‚ на играта." msgid "Please Remember" msgstr "МолÑ, запомни" #, fuzzy msgid "" "You can always change the language, resolution and settings of the game by " "clicking on the " msgstr "" "Винаги можеш да промениш разделителната ÑпоÑобноÑÑ‚ на играта, като щракнеш " "върху " msgid "door" msgstr "вратата" msgid " on the left side of the Main Menu, or with the " msgstr "" msgid " button from the " msgstr "" #, fuzzy msgid "NEW GAME" msgstr "" "ÐОВÐ\n" "ИГРÐ" msgid "" " menu. \n" "\n" "Enjoy the game!" msgstr "" msgid "The Editor requires \"The Price of Loyalty\" expansion files to work." msgstr "" "Редакторът изиÑква разширителни файлове „Цената на лоÑлноÑтта“, за да работи." msgid "Quit Heroes of Might and Magic II and return to the operating system." msgstr "" "Излез от Heroes of Might and Magic II и Ñе върни към операционната ÑиÑтема." msgid "Credits" msgstr "За играта" msgid "View the credits screen." msgstr "Виж екран за играта." msgid "High Scores" msgstr "КлаÑиране" msgid "View the high scores screen." msgstr "Виж екрана Ñ Ð²Ð¸Ñоки резултати." msgid "Change language, resolution and settings of the game." msgstr "Промени езика, резолюциÑта и наÑтройките на играта." msgid "Game Settings" msgstr "Игрови наÑтройки" msgid "" "Required video files for campaign selection window are missing. Please make " "sure that all necessary files are present in the system." msgstr "" "ЛипÑват необходимите видео файлове за прозореца за избор на кампаниÑ. МолÑ, " "уверете Ñе, че вÑички необходими файлове приÑÑŠÑтват в ÑиÑтемата." msgid "" "Either Roland's or Archibald's campaign from the original Heroes of Might " "and Magic II." msgstr "" "Или кампаниÑта на Роланд, или на Ðрчибалд от оригиналната Heroes of Might " "and Magic II." msgid "Original Campaign" msgstr "Оригинална кампаниÑ" msgid "Expansion Campaign" msgstr "ÐšÐ°Ð¼Ð¿Ð°Ð½Ð¸Ñ Ð½Ð° продължение" msgid "One of the four new campaigns from the Price of Loyalty expansion set." msgstr "" "Една от четирите нови кампании от комплекта за разширение на \"Цената на " "ЛоÑлноÑтта\"." msgid "Loading video. Please wait..." msgstr "Видеото Ñе зарежда. ÐœÐ¾Ð»Ñ Ð¸Ð·Ñ‡Ð°ÐºÐ°Ð¹..." msgid "Host" msgstr "Домакин" msgid "" "The host sets up the game options. There can only be one host per network " "game." msgstr "" "Домакинът наÑтройва опциите на играта. Може да има Ñамо един хоÑÑ‚ за мрежова " "игра." msgid "Guest" msgstr "ГоÑÑ‚" msgid "" "The guest waits for the host to set up the game, then is automatically added " "in. There can be multiple guests for TCP/IP games." msgstr "" "ГоÑтът чака хоÑтът да наÑтрои играта, Ñлед което автоматично Ñе добавÑ. Може " "да има нÑколко гоÑта за TCP/IP игри." msgid "Battle Only" msgstr "Само битка" msgid "Setup and play a battle without loading any map." msgstr "ÐаÑтрой и изиграй битка, без да зареждаш никаква карта." msgid "2 Players" msgstr "2 Играча" msgid "" "Play with 2 human players, and optionally, up to 4 additional computer " "players." msgstr "" "Играй Ñ 2 човешки играча и по избор Ñ Ð´Ð¾ 4 допълнителни компютърни играча." msgid "3 Players" msgstr "3 Играча" msgid "" "Play with 3 human players, and optionally, up to 3 additional computer " "players." msgstr "" "Играй Ñ 3 човешки играча и по желание до 3 допълнителни компютърни играча." msgid "4 Players" msgstr "4 Играча" msgid "" "Play with 4 human players, and optionally, up to 2 additional computer " "players." msgstr "" "Играй Ñ 4 човешки играча и по избор Ñ Ð´Ð¾ 2 допълнителни компютърни играча." msgid "5 Players" msgstr "5 Играча" msgid "" "Play with 5 human players, and optionally, up to 1 additional computer " "player." msgstr "" "Играй Ñ 5 човешки играча и по избор до 1 допълнителен компютърен играч." msgid "6 Players" msgstr "6 Играча" msgid "Play with 6 human players." msgstr "Играй Ñ 6 човешки играча." msgid "Dragon city has fallen! You are now the Master of the Dragons." msgstr "Градът на драконите падна! Вече ти Ñи гоÑподарÑÑ‚ на драконите." msgid "" "You captured %{name}!\n" "You are victorious." msgstr "" "Ти хвана %{name}!\n" "Ти Ñи победител." msgid "" "You have captured the enemy hero %{name}!\n" "Your quest is complete." msgstr "" "Ти залови вражеÑÐºÐ¸Ñ Ð³ÐµÑ€Ð¾Ð¹ %{name}!\n" "ТвоÑта миÑÐ¸Ñ Ðµ завършена." msgid "" "You have found the %{name}.\n" "Your quest is complete." msgstr "" "Ðамери %{name}.\n" "ТвоÑта миÑÐ¸Ñ Ðµ завършена." msgid "Ultimate Artifact" msgstr "Ðай-Ð´Ð¾Ð±Ñ€Ð¸Ñ Ð°Ñ€Ñ‚ÐµÑ„Ð°ÐºÑ‚" msgid "" "The enemy is beaten.\n" "Your side has triumphed!" msgstr "" "Врагът е победен.\n" "ТвоÑта Ñтрана триумфира!" msgid "" "You have built up over %{count} gold in your treasury.\n" "All enemies bow before your wealth and power." msgstr "" "Ðатрупал Ñи над %{count} злато в Ñъкровищницата Ñи.\n" "Ð’Ñички врагове Ñе прекланÑÑ‚ пред твоето богатÑтво и Ñила." msgid "Victory!" msgstr "Победа!" msgid "" "The enemy has captured %{name}!\n" "They are triumphant." msgstr "" "Врагът е пленил %{name}!\n" "Той триумфира." msgid "" "The enemy has built up over %{count} gold in his treasury.\n" "You must bow done in defeat before his wealth and power." msgstr "" "Врагът е натрупал над %{count} злато в Ñъкровищницата Ñи.\n" "ТрÑбва да Ñе поклониш победен пред неговото богатÑтво и Ñила." msgid "You have been eliminated from the game!!!" msgstr "Ти Ñи елиминиран от играта!!!" msgid "" "You have lost the hero %{name}.\n" "Your quest is over." msgstr "" "Ти загуби Ð³ÐµÑ€Ð¾Ñ %{name}.\n" "ТвоÑта миÑÐ¸Ñ Ð¿Ñ€Ð¸ÐºÐ»ÑŽÑ‡Ð¸." msgid "" "You have failed to complete your quest in time.\n" "All is lost." msgstr "" "Ðе уÑÐ¿Ñ Ð´Ð° завършиш миÑиÑта Ñи навреме.\n" "Ð’Ñичко е загубено." msgid "Defeat!" msgstr "Победен!" msgid "" "Base score: %{score}\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" "ОÑновен резултат: %{score}\n" "ТрудноÑÑ‚: %{difficulty}\n" "\n" msgid "Defeat all enemy heroes and capture all enemy towns and castles." msgstr "" "Победи вÑички вражеÑки герои и превземи вÑички вражеÑки градове и замъци." msgid "Run out of time. (Fail to win by a certain point.)" msgstr "Времето Ñвърши. (Ðе уÑÐ¿Ñ Ð´Ð° победиш в определеното време.)" msgid "You must defeat the enemy %{enemies}." msgid_plural "You must defeat the enemy alliance of %{enemies}." msgstr[0] "ТрÑбва да победиш врага %{enemies}." msgstr[1] "ТрÑбва да победиш вражеÑкиÑÑ‚ Ñъюз от %{enemies}." msgid "" "The alliance consisting of %{allies} and you must defeat the enemy " "%{enemies}." msgid_plural "" "The alliance consisting of %{allies} and you must defeat the enemy alliance " "of %{enemies}." msgstr[0] "" "Съюзът, ÑÑŠÑтоÑщ Ñе от %{allies} и ти трÑбва да победите врага %{enemies}." msgstr[1] "" "Съюзът, ÑÑŠÑтоÑщ Ñе от %{allies} и ти трÑбва да победите вражеÑÐºÐ¸Ñ Ñъюз от " "%{enemies}." msgid "Capture the castle '%{name}'." msgstr "Превземи замъка '%{name}'." msgid "Capture the town '%{name}'." msgstr "Превземи града '%{name}'." msgid "Defeat the hero '%{name}'." msgstr "Победи Ð³ÐµÑ€Ð¾Ñ '%{name}'." msgid "Find the ultimate artifact." msgstr "Ðамери най-добриÑÑ‚ артефакт." msgid "Find the '%{name}' artifact." msgstr "Ðамери артефакта \"%{name}\"." msgid "Accumulate %{count} gold." msgstr "Ðатрупай %{count} злато." msgid "" ", or you may win by defeating all enemy heroes and capturing all enemy towns " "and castles." msgstr "" ", или може да Ñпечелиш, като победиш вÑички вражеÑки герои и превземеш " "вÑички вражеÑки градове и замъци." msgid "Lose the castle '%{name}'." msgstr "Губиш замъка '%{name}'." msgid "Lose the town '%{name}'." msgstr "Губиш града '%{name}'." msgid "Lose the hero: %{name}." msgstr "Губиш героÑ: %{name}." msgid "Fail to win by the end of month %{month}, week %{week}, day %{day}." msgstr "" "Ðе Ñпечелвай играта до ÐºÑ€Ð°Ñ Ð½Ð° меÑец %{month}, Ñедмица %{week}, ден %{day}." msgid "%{color} player has been vanquished!" msgstr "%{color} играч е победен!" msgid "Major Event!" msgstr "ГолÑмо Ñъбитие!" msgid "Scenario:" msgstr "Сценарий:" msgid "Game Difficulty:" msgstr "ТрудноÑÑ‚ на играта:" msgid "Opponents:" msgstr "Противници:" msgid "Class:" msgstr "КлаÑ:" msgid "Rating %{rating}%" msgstr "Оценка %{rating}%" msgid "Click here to select which scenario to play." msgstr "Кликни тук, за да избереш кой Ñценарий да играеш." msgid "Scenario" msgstr "Сценарий" msgid "Game Difficulty" msgstr "ТрудноÑÑ‚ на играта" msgid "" "This lets you change the starting difficulty at which you will play. Higher " "difficulty levels start you of with fewer resources, and at the higher " "settings, give extra resources to the computer." msgstr "" "Това ти позволÑва да промениш началната трудноÑÑ‚, при коÑто ще играеш. По-" "виÑоките нива на трудноÑÑ‚ започват Ñ Ð¿Ð¾-малко реÑурÑи, а при по-виÑоките " "наÑтройки дава допълнителни реÑурÑи на компютъра." msgid "Difficulty Rating" msgstr "Оценка на трудноÑÑ‚" msgid "" "The difficulty rating reflects a combination of various settings for your " "game. This number will be applied to your final score." msgstr "" "Рейтингът на трудноÑÑ‚ отразÑва ÐºÐ¾Ð¼Ð±Ð¸Ð½Ð°Ñ†Ð¸Ñ Ð¾Ñ‚ различни наÑтройки за вашата " "игра. Това чиÑло ще бъде приложено към ÐºÑ€Ð°Ð¹Ð½Ð¸Ñ Ñ‚Ð¸ резултат." msgid "Click to accept these settings and start a new game." msgstr "Кликни, за да приемеш тези наÑтройки и да започнеш нова игра." msgid "Click to return to the main menu." msgstr "Кликни, за да Ñе върнеш към главното меню." msgid "Astrologers proclaim the Month of the %{name}." msgstr "ÐÑтролозите обÑвÑват меÑеца на %{name}." msgid "Astrologers proclaim the Week of the %{name}." msgstr "ÐÑтролозите обÑвÑват Ñедмицата на %{name}." msgid "After regular growth, the population of %{monster} is doubled!" msgstr "След редовен раÑтеж популациÑта на %{monster} Ñе удвоÑва!" msgid "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgid_plural "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgstr[0] "" "След редовен раÑтеж популациÑта на %{monster} Ñе увеличава Ñ %{count} " "процента!" msgstr[1] "" "След редовен раÑтеж популациÑта на %{monster} Ñе увеличава Ñ %{count} " "процента!" msgid "%{monster} growth +%{count}." msgstr "%{monster} раÑтеж +%{count}." msgid " All populations are halved." msgstr " Ð’Ñички популации Ñа наполовина." msgid " All dwellings increase population." msgstr " Ð’Ñички жилища увеличават наÑелението." msgid "New Month!" msgstr "Ðов меÑец!" msgid "New Week!" msgstr "Ðова Седмица!" msgid "Beware!" msgstr "Внимавай!" msgid "" "%{color} player, this is your last day to capture a town, or you will be " "banished from this land." msgstr "" "%{color} играч, това е поÑледниÑÑ‚ ти ден за превземане на град или ще бъдеш " "прогонен от тази земÑ." msgid "" "%{color} player, you only have %{day} days left to capture a town, or you " "will be banished from this land." msgstr "" "%{color} играч, имаш Ñамо %{day} дни, за да превземеш град, или ще бъдеш " "прогонен от тази земÑ." msgid "%{color} player's turn." msgstr "Ход на %{color} играч." #, fuzzy msgid "" "%{color} player, you have lost your last town. If you do not conquer another " "town in the next week, you will be eliminated." msgstr "" "%{color} играч, загуби поÑÐ»ÐµÐ´Ð½Ð¸Ñ Ñи град. Ðко не завладееш друг град през " "Ñледващата Ñедмица, ще бъдеш елиминиран." msgid "" "%{color} player, your heroes abandon you, and you are banished from this " "land." msgstr "" "%{color} играч, теоите герои те изоÑтавÑÑ‚ и ти Ñи прогонени от тази земÑ." msgid "Lord Kilburn" msgstr "Лорд Килбърн" msgid "Tsabu" msgstr "Сабу" msgid "Sir Galant" msgstr "Сър Галант" msgid "Thundax" msgstr "ТъндакÑ" msgid "Lord Haart" msgstr "Лорд Хаарт" msgid "Ariel" msgstr "Ðриел" msgid "Rebecca" msgstr "Ребека" msgid "Sandro" msgstr "Сандро" msgid "Crodo" msgstr "Кродо" msgid "Barock" msgstr "Барок" msgid "Antoine" msgstr "Antoine" msgid "Astra" msgstr "ÐÑтра" msgid "Agar" msgstr "Егар" msgid "Vatawna" msgstr "Ватуна" msgid "Vesper" msgstr "ВеÑпер" msgid "Ambrose" msgstr "Ðмброуз" msgid "Troyan" msgstr "ТраÑна" msgid "Jojosh" msgstr "Джоджош" msgid "Wrathmont" msgstr "Вратмунд" msgid "Maximus" msgstr "МакÑимуÑ" msgid "Next Hero" msgstr "СледващиÑÑ‚ герой" msgid "Select the next Hero." msgstr "Избери ÑÐ»ÐµÐ´Ð²Ð°Ñ‰Ð¸Ñ Ð³ÐµÑ€Ð¾Ð¹." msgid "Hero Movement" msgstr "Движение на героÑ" msgid "" "Start the Hero's movement along the current path or re-visit the object " "occupied by the Hero. Press and hold this button to reset the Hero's path." msgstr "" "Започни движението на Ð³ÐµÑ€Ð¾Ñ Ð¿Ð¾ Ñ‚ÐµÐºÑƒÑ‰Ð¸Ñ Ð¿ÑŠÑ‚ или поÑети отново обекта, зает от " "героÑ. ÐатиÑни и задръж този бутон, за да нулираш Ð¿ÑŠÑ‚Ñ Ð½Ð° героÑ." msgid "Kingdom Summary" msgstr "Резюме за КралÑтвото" msgid "View a Summary of your Kingdom." msgstr "Виж резюме на твоето кралÑтво." msgid "Cast an adventure spell." msgstr "Ðаправи приключенÑко заклинание." msgid "End Turn" msgstr "Краен завой" msgid "End your turn and left the computer take its turn." msgstr "Завърши хода Ñи и оÑтави компютъра да направи ÑвоÑ." msgid "Adventure Options" msgstr "Опции за приключениÑ" msgid "Bring up the adventure options menu." msgstr "Покажи менюто Ñ Ð¾Ð¿Ñ†Ð¸Ð¸ за приключение." msgid "" "Bring up the file options menu, allowing you to load, save, start a new game " "or quit." msgstr "" "Покажи менюто Ñ Ð¾Ð¿Ñ†Ð¸Ð¸ за файлове, което ти позволÑва да зареждаш, запазваш, " "Ñтартираш нова игра или да напуÑнеш играта." msgid "Bring up the system options menu, allowing you to customize your game." msgstr "" "Покажи менюто ÑÑŠÑ ÑиÑтемни опции, което ти позволÑва да перÑонализираш " "играта Ñи." msgid "" "One or more heroes may still move, are you sure you want to end your turn?" msgstr "" "Един или повече герои вÑе още имат ход. Сигурен ли Ñи, че иÑкаш да приключиш " "ÑÐ²Ð¾Ñ Ñ…Ð¾Ð´?" msgid "Are you sure you want to quit?" msgstr "Сигурен ли Ñи че иÑкаш да напуÑнеш играта?" msgid "Are you sure you want to restart? (Your current game will be lost.)" msgstr "" "Сигурен ли Ñи че иÑкаш да пуÑнеш отново играта? (ТвоÑта наÑтоÑща игра ще Ñе " "изгуби.)" msgid "Are you sure you want to overwrite the save with this name?" msgstr "Сигурен ли Ñи че иÑкаш да презапишеш запазената игра Ñ Ñ‚Ð¾Ð²Ð° име?" msgid "Game saved successfully." msgstr "Играта е запазена уÑпешно." msgid "There was an issue during saving." msgstr "Имаше проблем по време на запазването." msgid "" "Are you sure you want to load a new game? (Your current game will be lost.)" msgstr "" "Сигурен ли Ñи, че иÑкаш да заредиш нова игра? (Текущата ти игра ще бъде " "загубена.)" msgid "Try looking on land!!!" msgstr "Опитай да погледнеш на земÑта!!!" msgid "" "Searching for the Ultimate Artifact is fruitless. Your hero could not carry " "it even if he found it - all his artifact slots are full." msgstr "" "ТърÑенето на Ð’ÑŠÑ€Ñ…Ð¾Ð²Ð½Ð¸Ñ Ð°Ñ€Ñ‚ÐµÑ„Ð°ÐºÑ‚ е без резултат. ТвоÑÑ‚ герой не би могъл да " "го ноÑи, дори ако го намери - вÑичките му Ñлотове за артефакти Ñа пълни." msgid "Digging for artifacts requires a whole day, try again tomorrow." msgstr "Копаенето на артефакти изиÑква цÑл ден, опитай отново утре." msgid "" "After spending many hours digging here, you have uncovered the %{artifact}." msgstr "След като прекара много чаÑове в копаене тук, ти откри %{artifact}." msgid "Congratulations!" msgstr "ЧеÑтито!" msgid "Nothing here. Where could it be?" msgstr "Ðищо нÑма тук. Къде може да е?" msgid "Try searching on clear ground." msgstr "Опитай да търÑиш на чиÑта земÑ." msgid "A miniature view of the known world. Left click to move viewing area." msgstr "" "Миниатюрен изглед на Ð¿Ð¾Ð·Ð½Ð°Ñ‚Ð¸Ñ ÑвÑÑ‚. Кликни Ñ Ð»ÐµÐ²Ð¸Ñ Ð±ÑƒÑ‚Ð¾Ð½, за да премеÑтиш " "зоната за преглед." msgid "Month: %{month} Week: %{week}" msgstr "МеÑец: %{month} Седмица: %{week}" msgid "Day: %{day}" msgstr "Ден: %{day}" msgid "" "You find a small\n" "quantity of %{resource}." msgstr "" "Ðамираш малко\n" "количеÑтво %{resource}." msgid "Status Window" msgstr "Прозорец на ÑÑŠÑтоÑнието" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date." msgstr "" "Този прозорец предоÑÑ‚Ð°Ð²Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° ÑÑŠÑтоÑнието на Ñ‚Ð²Ð¾Ñ Ð³ÐµÑ€Ð¾Ð¹ или " "кралÑтво и показва датата." msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date. Left click here to cycle through these windows." msgstr "" "Този прозорец предоÑÑ‚Ð°Ð²Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° ÑÑŠÑтоÑнието на Ñ‚Ð²Ð¾Ñ Ð³ÐµÑ€Ð¾Ð¹ или " "кралÑтво и показва датата. Щракни Ñ Ð»ÐµÐ²Ð¸Ñ Ð±ÑƒÑ‚Ð¾Ð½ тук, за да преминеш през " "тези прозорци." msgid "" "This lets you change player starting positions and colors. A particular " "color will always start in a particular location. Some positions may only be " "played by a computer player or only by a human player." msgstr "" "Това ти позволÑва да променÑш началните позиции и цветовете на играчите. " "Конкретен цвÑÑ‚ винаги ще започва от определено мÑÑто. ÐÑкои позиции могат да " "Ñе играÑÑ‚ Ñамо от компютърен играч или Ñамо от човешки играч." msgid "" "This lets you change the class of a player. Classes are not always " "changeable. Depending on the scenario, a player may receive additional towns " "and/or heroes not of their primary alignment." msgstr "" "Това ти позволÑва да промениш клаÑа на играч. КлаÑовете не винаги могат да " "Ñе променÑÑ‚. Ð’ завиÑимоÑÑ‚ от ÑценариÑ, играчът може да получи допълнителни " "градове и/или герои, които не Ñа от оÑновното им подреждане." msgid "Handicap" msgstr "Ðедъг" # msgid "" "This lets you change the handicap of a particular player. Only humans may be " "handicapped. Handicapped players start with fewer resources and earn 15 or " "30% fewer resources per turn for mild and severe handicaps, respectively." msgstr "" "Това ти позволÑва да промениш недъга на определен играч. Само хората могат " "да бъдат инвалиди. Играчите Ñ ÑƒÐ²Ñ€ÐµÐ¶Ð´Ð°Ð½Ð¸Ñ Ð·Ð°Ð¿Ð¾Ñ‡Ð²Ð°Ñ‚ Ñ Ð¿Ð¾-малко реÑурÑи и " "печелÑÑ‚ Ñъответно 15 или 30% по-малко реÑурÑи на ход за леки и тежки " "уврежданиÑ." msgid "%{color} player" msgstr "%{color} играч" msgid "No Handicap" msgstr "Без недъг" msgid "No special restrictions on start resources and earning them per turn." msgstr "" "ÐÑма Ñпециални Ð¾Ð³Ñ€Ð°Ð½Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð·Ð° Ñтартовите реÑурÑи и печеленето им на ход." msgid "Mild Handicap" msgstr "Лек недъг" # msgid "" "Mild handicapped players start with fewer resources and earn 15% fewer " "resources per turn." msgstr "" "Играчите Ñ Ð»ÐµÐºÐ¸ недъзи започват Ñ Ð¿Ð¾-малко реÑурÑи и печелÑÑ‚ 15% по-малко " "реÑурÑи на ход." msgid "Severe Handicap" msgstr "Тежък недъг" # msgid "" "Severe handicapped players start with fewer resources and earn 30% fewer " "resources per turn." msgstr "" "Играчите Ñ Ñ‚ÐµÐ¶ÐºÐ¸ ÑƒÐ²Ñ€ÐµÐ¶Ð´Ð°Ð½Ð¸Ñ Ð·Ð°Ð¿Ð¾Ñ‡Ð²Ð°Ñ‚ Ñ Ð¿Ð¾-малко реÑурÑи и печелÑÑ‚ Ñ 30% по-" "малко реÑурÑи през ÑÐ²Ð¾Ñ Ñ…Ð¾Ð´." msgid "View %{skill} Info" msgstr "Виж Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° %{skill}" msgid "Keyboard|123" msgstr "" msgid "Keyboard|SPACE" msgstr "" msgid "Keyboard|ABC" msgstr "" msgid "Kingdom Income" msgstr "Приходи на царÑтвото" msgid "Kingdom Income per day." msgstr "Приходи на царÑтвото за ден." msgid "For every lighthouse controlled, your ships will move further each day." msgstr "" "За вÑеки контролиран фар, твойте кораби ще Ñе придвижват вÑе повече вÑеки " "ден." msgid "English" msgstr "ÐнглийÑки" msgid "French" msgstr "ФренÑки" msgid "Polish" msgstr "ПолÑки" msgid "German" msgstr "ÐемÑки" msgid "Russian" msgstr "РуÑки" msgid "Italian" msgstr "ИталианÑки" msgid "Czech" msgstr "ЧехÑки" msgid "Norwegian" msgstr "Ðорвежки" msgid "Belarusian" msgstr "БеларуÑки" msgid "Bulgarian" msgstr "БългарÑки" msgid "Ukrainian" msgstr "УкраинÑки" msgid "Romanian" msgstr "РумънÑки" msgid "Spanish" msgstr "ИÑпанÑки" msgid "Swedish" msgstr "ШведÑки" msgid "Portuguese" msgstr "ПортугалÑки" msgid "Turkish" msgstr "ТурÑки" msgid "Dutch" msgstr "ХоландÑки" msgid "Hungarian" msgstr "УнгарÑки" msgid "Danish" msgstr "ДатÑки" msgid "Slovak" msgstr "Словашки" msgid "Vietnamese" msgstr "ВиетнамÑки" msgid ", FPS: " msgstr "" msgid "%{object} robber" msgstr "%{object} разбойник" msgid "%{object} raided" msgstr "%{object} нападнат" msgid "Ector" msgstr "Ектор" msgid "Gwenneth" msgstr "Гуенет" msgid "Sir Gallant" msgstr "Сър Галант" msgid "Tyro" msgstr "Тайро" msgid "Dimitry" msgstr "Димитрий" msgid "Ruby" msgstr "Руби" msgid "Crag Hack" msgstr "Краг Хак" msgid "Fineous" msgstr "ФиниаÑ" msgid "Jezebel" msgstr "Джезабел" msgid "Atlas" msgstr "ÐтлаÑ" msgid "Ergon" msgstr "Ергон" msgid "Jaclyn" msgstr "Жаклин" msgid "Gem" msgstr "Джем" msgid "Natasha" msgstr "Ðаташа" msgid "Carlawn" msgstr "Карлон" msgid "Luna" msgstr "Луна" msgid "Arie" msgstr "Ери" msgid "Barok" msgstr "Барок" msgid "Kastore" msgstr "КаÑтор" msgid "Falagar" msgstr "Фалагар" msgid "Dawn" msgstr "Доон" msgid "Flint" msgstr "Флинт" msgid "Halon" msgstr "Хелан" msgid "Myra" msgstr "Майра" msgid "Myrini" msgstr "Майрини" msgid "Wilfrey" msgstr "Уилфри" msgid "Mandigal" msgstr "Мандигал" msgid "Sarakin" msgstr "Саракин" msgid "Charity" msgstr "Чарити" msgid "Darlana" msgstr "Дарлана" msgid "Ranloo" msgstr "Ранлу" msgid "Rialdo" msgstr "Риалдо" msgid "Zam" msgstr "Зам" msgid "Zom" msgstr "Зом" msgid "Celia" msgstr "СилиÑ" msgid "Roxana" msgstr "РокÑана" msgid "Lord Corlagon" msgstr "Лорд Корлагон" msgid "Lord Halton" msgstr "Лорд Холтън" msgid "Sister Eliza" msgstr "СеÑтра Елайза" msgid "Brother Brax" msgstr "Брат БракÑ" msgid "Dainwin" msgstr "Дейнуин" msgid "Joseph" msgstr "Джоузеф" msgid "Mog" msgstr "Мог" msgid "Solmyr" msgstr "Солмир" msgid "Ceallach" msgstr "Калег" msgid "Drakonia" msgstr "ДракониÑ" msgid "Elderian" msgstr "Елдериан" msgid "Gallavant" msgstr "Галевант" msgid "Jarkonas" msgstr "ДжарконаÑ" msgid "Martine" msgstr "Мартинa" msgid " gives you maximum morale" msgstr " ти дава макÑимален морал" msgid " gives you maximum luck" msgstr " ти дава макÑимален къÑмет" msgid "You cannot pick up this artifact, you already have a full load!" msgstr "Ðе можеш да вземеш този артефакт, вече нÑмаш мÑÑто!" msgid "To cast spells, you must first buy a spell book for %{gold} gold." msgstr "" "За да правиш магии, първо трÑбва да Ñи купиш книга за магий за %{gold} злато." msgid "Unfortunately, you seem to be a little short of cash at the moment." msgstr "За Ñъжаление, изглежда, че в момента нÑмаш пари." msgid "Do you wish to buy one?" msgstr "ИÑкаш ли да Ñи купиш такъв?" msgid "%{count} / day" msgstr "%{count} / ден" msgid "one" msgstr "една" msgid "two" msgstr "две" msgid "A whirlpool engulfs your ship. Some of your army has fallen overboard." msgstr "" "Водовъртежът поглъща кораба ти. ЧаÑÑ‚ от армиÑта ти е паднала зад борда." msgid "Insulted by your refusal of their offer, the monsters attack!" msgstr "Обидени от отказа ти от офертата им, чудовищата атакуват!" msgid "" "The %{monster}, awed by the power of your forces, begin to scatter.\n" "Do you wish to pursue and engage them?" msgstr "" "%{monster}, възхитени от твоите Ñили, започват да Ñе разпръÑват.\n" "ИÑкаш ли да ги преÑледваш и вербуваш?" msgid "Ransacking an enemy camp, you discover a hidden cache of treasures." msgstr "ПретърÑвайки вражеÑки лагер, откриваш Ñкрит тайник ÑÑŠÑ Ñъкровища." msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with these resources, " "come back next week for more.\"" msgstr "" "Пазачът на мелницата Ñъобщава:\n" "\"Милорд, работих много уÑилено, за да ти оÑÐ¸Ð³ÑƒÑ€Ñ Ñ‚ÐµÐ·Ð¸ реÑурÑи, заповÑдай " "Ñледващата Ñедмица за още.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there are no resources currently available. Please try " "again next week.\"" msgstr "" "Пазачът на мелницата Ñъобщава:\n" "\"Милорд, ÑъжалÑвам, в момента нÑма налични реÑурÑи. МолÑ, опитай отново " "Ñледващата Ñедмица.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with this gold, come " "back next week for more.\"" msgstr "" "Пазачът на мелницата Ñъобщава:\n" "\"Милорд, работих много уÑилено, за да ти оÑÐ¸Ð³ÑƒÑ€Ñ Ñ‚Ð¾Ð²Ð° злато, ела Ñледващата " "Ñедмица за още.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there is no gold currently available. Please try again " "next week.\"" msgstr "" "Пазачът на мелницата Ñъобщава:\n" "\"Милорд, ÑъжалÑвам, в момента нÑма налично злато. МолÑ, опитай отново " "Ñледващата Ñедмица.\"" msgid "" "You've found an abandoned lean-to.\n" "Poking about, you discover some resources hidden nearby." msgstr "" "Ðамери изоÑтавен навеÑ.\n" "Ровейки Ñе, откриваш нÑкои реÑурÑи, Ñкрити наблизо." msgid "The lean-to is long abandoned. There is nothing of value here." msgstr "ÐавеÑÑŠÑ‚ отдавна е изоÑтавен. Тук нÑма нищо ценно." msgid "" "You catch a leprechaun foolishly sleeping amidst a cluster of magic " "mushrooms.\n" "In exchange for his freedom, he guides you to a small pot filled with " "precious things." msgstr "" "Хващаш леприкон, който глупаво Ñпи Ñред група вълшебни гъби.\n" "Ð’ замÑна на Ñвободата Ñи той те води до малко гърне, пълно ÑÑŠÑ Ñкъпоценни " "неща." msgid "" "You've found a magic garden, the kind of place that leprechauns and faeries " "like to cavort in, but there is no one here today.\n" "Perhaps you should try again next week." msgstr "" "Ðамери вълшебна градина, мÑÑтото, където леприконите и феите обичат да Ñе " "разхождат, но Ð´Ð½ÐµÑ Ñ‚ÑƒÐº нÑма никой.\n" "Може би трÑбва да опиташ отново Ñледващата Ñедмица." msgid "You come upon the remains of an unfortunate adventurer." msgstr "Попадаш на оÑтанките на нещаÑтен авантюриÑÑ‚." msgid "Treasure" msgstr "Съкровище" msgid "Searching through the tattered clothing, you find the %{artifact}." msgstr "ТърÑейки в едни дрипи, намираш %{artifact}." msgid "Searching through the tattered clothing, you find nothing." msgstr "ПретърÑваш дрипите, но не намираш нищо." msgid "" "You come across an old wagon left by a trader who didn't quite make it to " "safe terrain." msgstr "" "Попадаш на Ñтара каруца, оÑтавена от търговец, който не е уÑпÑл да Ñтигне до " "безопаÑен терен." msgid "Unfortunately, others have found it first, and the wagon is empty." msgstr "За Ñъжаление други Ñа го намерили първи и вагонът е празен." msgid "Searching inside, you find the %{artifact}." msgstr "ТърÑейки вътре, намираш %{artifact}." msgid "Inside, you find some of the wagon's cargo still intact." msgstr "Вътре намираш чаÑÑ‚ от товара на каруцата вÑе още непокътнат." msgid "You search through the flotsam, and find some wood and some gold." msgstr "ТърÑиш из отломките и намираш малко дърва и малко злато." msgid "You search through the flotsam, and find some wood." msgstr "ПретърÑваш отломките и намираш малко дърва." msgid "You search through the flotsam, but find nothing." msgstr "ТърÑиш из отломките, но не намираш нищо." msgid "Shrine of the 1st Circle" msgstr "Храм на 1-Ð²Ð¸Ñ ÐºÑ€ÑŠÐ³" msgid "" "You come across a small shrine attended by a group of novice acolytes.\n" "In exchange for your protection, they agree to teach you a simple spell - " "'%{spell}'." msgstr "" "Ðатъкваш Ñе на малко Ñветилище, поÑещавано от група дÑкони.\n" "Ð’ замÑна на твоÑта защита, те Ñе ÑъглаÑÑват да те научат на заклинанието - " "'%{spell}'." msgid "Shrine of the 2nd Circle" msgstr "Храм на 2-Ñ€Ð¸Ñ ÐºÑ€ÑŠÐ³" msgid "" "You come across an ornate shrine attended by a group of rotund friars.\n" "In exchange for your protection, they agree to teach you a spell - " "'%{spell}'." msgstr "" "Ðатъкваш Ñе на богато украÑен храм, поÑещаван от група закръглени монаÑи.\n" "Ð’ замÑна на твоÑта защита, те Ñе ÑъглаÑÑват да те научат на заклинанието - " "'%{spell}'." msgid "Shrine of the 3rd Circle" msgstr "Храм на 3-Ñ‚Ð¸Ñ ÐºÑ€ÑŠÐ³" msgid "" "You come across a lavish shrine attended by a group of high priests.\n" "In exchange for your protection, they agree to teach you a sophisticated " "spell - '%{spell}'." msgstr "" "Ðатъкваш Ñе на пищно Ñветилище, поÑещавано от група първоÑвещеници.\n" "Ð’ замÑна на твоÑта защита, те Ñе ÑъглаÑÑват да те научат на Ñложното " "заклинание - '%{spell}'." msgid "" "\n" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "\n" "За Ñъжаление нÑмаш мъдроÑтта да разбереш заклинанието и не уÑпÑваш да го " "научиш." msgid "" "\n" "Unfortunately, you already have knowledge of this spell, so there is nothing " "more for them to teach you." msgstr "" "\n" "За Ñъжаление вече имаш Ð·Ð½Ð°Ð½Ð¸Ñ Ð·Ð° това заклинание, така че нÑма на какво " "повече да те научат." msgid "" "\n" "Unfortunately, you have no Magic Book to record the spell with." msgstr "" "\n" "За Ñъжаление нÑмаш магичеÑка книга, в коÑто да запишеш заклинанието." msgid "" "You approach the hut and observe a witch inside studying an ancient tome on " "%{skill}.\n" "\n" msgstr "" "Приближаваш Ñе до хижата и виждаш вътре вещица изучаваща древен том по " "%{skill}.\n" "\n" msgid "" "As you approach, she turns and focuses her one glass eye on you.\n" "\"You already know everything you deserve to learn!\" the witch screeches. " "\"NOW GET OUT OF MY HOUSE!\"" msgstr "" "Докато Ñе приближаваш, Ñ‚Ñ Ñе обръща и те забелÑзва Ñ ÐµÐ´Ð½Ð¾Ñ‚Ð¾ Ñи Ñтъклено " "око.\n" "\"Вече знаеш вÑичко, което можеше да научиш!\" изпищÑва вещицата. \"СЕГРСЕ " "МÐÐ¥ÐЙ ОТ КЪЩÐТРМИ!\"" msgid "" "As you approach, she turns and speaks.\n" "\"You already know that which I would teach you. I can help you no further.\"" msgstr "" "Докато Ñе приближаваш, Ñ‚Ñ Ñе обръща и заговарÑ.\n" "\"Вече знаеш вÑичко, на което можех да те науча. Ðе мога да ти помогна " "повече.\"" msgid "" "An ancient and immortal witch living in a hut with bird's legs for stilts " "teaches you %{skill} for her own inscrutable purposes." msgstr "" "Древна и безÑмъртна вещица, живееща в колиба на кокоши крака за кокили, те " "учи на %{skill} за ÑобÑтвените Ñи неразгадаеми цели." msgid "As you drink the sweet water, you gain luck for your next battle." msgstr "Докато пиеш Ñладката вода, печелиш къÑмет за Ñледващата Ñи битка." msgid "You drink from the enchanted fountain, but nothing happens." msgstr "Пиеш от омагьоÑÐ°Ð½Ð¸Ñ Ñ„Ð¾Ð½Ñ‚Ð°Ð½, но нищо не Ñе Ñлучва." msgid "You enter the faerie ring, but nothing happens." msgstr "Влизаш във пръÑтена на фейте, но нищо не Ñе Ñлучва." msgid "" "Upon entering the mystical faerie ring, your army gains luck for its next " "battle." msgstr "" "С влизането в миÑÑ‚Ð¸Ñ‡Ð½Ð¸Ñ Ð¿Ñ€Ð¸ÐºÐ°Ð·ÐµÐ½ пръÑтен армиÑта ти печели къÑмет за " "Ñледващата Ñи битка." msgid "" "You've found an ancient and weathered stone idol.\n" "It is supposed to grant luck to visitors, but since the stars are already " "smiling upon you, it does nothing." msgstr "" "Ðамери древен и увехтÑл каменен идол.\n" "Предполага Ñе, че дава къÑмет на поÑетителите, но тъй като звездите вече ти " "Ñе уÑмихнаха, не прави нищо." msgid "" "You've found an ancient and weathered stone idol.\n" "Kissing it is supposed to be lucky, so you do. The stone is very cold to the " "touch." msgstr "" "Ðамери древен и увехтÑл каменен идол.\n" "Предполага Ñе, че целуването му ноÑи къÑмет, така че го правиш. Камъкът е " "много Ñтуден на допир." msgid "The mermaids silently entice you to return later and be blessed again." msgstr "" "РуÑалките мълчаливо те примамват да Ñе върнеш по-къÑно и отново да бъдеш " "благоÑловен." msgid "" "The magical, soothing beauty of the Mermaids reaches you and your crew.\n" "Just for a moment, you forget your worries and bask in the beauty of the " "moment.\n" "The mermaids charms bless you with increased luck for your next combat." msgstr "" "МагичеÑката, уÑпокоÑваща краÑота на РуÑалките доÑтига до теб и Ñ‚Ð²Ð¾Ñ ÐµÐºÐ¸Ð¿.\n" "За миг забравÑш тревогите Ñи и Ñе наÑлаждаваш на краÑотата на момента.\n" "ТалиÑманите за руÑалки те благоÑлавÑÑ‚ Ñ Ð¿Ð¾Ð²ÐµÑ‡Ðµ къÑмет за Ñледващата ти битка." msgid "" "You come upon the pyramid of a great and ancient king.\n" "You are tempted to search it for treasure, but all the old stories warn of " "fearful curses and undead guardians.\n" "Will you search?" msgstr "" "Попадаш на пирамидата на велик и древен цар.\n" "Изкушаваш Ñе да Ñ Ð¿Ñ€ÐµÑ‚ÑŠÑ€Ñиш за Ñъкровища, но Ñтарите иÑтории предупреждават " "за Ñтраховити проклÑÑ‚Ð¸Ñ Ð¸ немъртви пазители.\n" "Ще търÑиш ли?" msgid "" "Upon defeating the monsters, you decipher an ancient glyph on the wall, " "telling the secret of the spell - '" msgstr "" "След като победи чудовищата, дешифрираш древен йероглиф на Ñтената, " "разказващ тайната на заклинанието - '" msgid "Unfortunately, you have no Magic Book to record the spell with." msgstr "За Ñъжаление нÑмаш магичеÑка книга, в коÑто да запишеш заклинанието." msgid "" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "За Ñъжаление нÑмаш мъдроÑтта да разбереш заклинанието и не можеш да го " "научиш." msgid "" "You come upon the pyramid of a great and ancient king.\n" "Routine exploration reveals that the pyramid is completely empty." msgstr "" "Попадаш на пирамидата на велик и древен цар.\n" "Рутинното проучване разкрива, че пирамидата е напълно празна." msgid "" "A drink at the well is supposed to restore your spell points, but you are " "already at maximum." msgstr "" "Една глътка от кладенеца трÑбва да възÑтанови твоите точки за заклинаниÑ, но " "те вече Ñа на макÑимум." msgid "A second drink at the well in one day will not help you." msgstr "Втора глътка от кладенеца за един ден нÑма да ти помогне." msgid "A drink from the well has restored your spell points to maximum." msgstr "" "Глътката от кладенеца е възÑтановила точките ти за Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ Ð´Ð¾ макÑимум." msgid "" "\"I'm sorry sir,\" The leader of the soldiers says, \"but you already know " "everything we have to teach.\"" msgstr "" "\"СъжалÑвам, ÑÑŠÑ€\", казва водачът на войниците, \"но вече знаете вÑичко, " "което можехме да те научим.\"" msgid "The soldiers living in the fort teach you a few new defensive tricks." msgstr "Войниците, живеещи във форта, те учат на нÑколко нови защитни трика." msgid "" "You've come upon a mercenary camp practicing their tactics. \"You're too " "advanced for us,\" the mercenary captain says. \"We can teach nothing more.\"" msgstr "" "Попадаш на лагер на наемници, който упражнÑват Ñвоите умениÑ. \"Твърде Ñи " "напреднал за наÑ\", казва капитанът на наемниците. \"Ðе можем да те научим " "на нищо повече.\"" msgid "" "You've come upon a mercenary camp practicing their tactics. The mercenaries " "welcome you and your troops and invite you to train with them." msgstr "" "Попадаш на лагер на наемници, които упражнÑват Ñвоите умениÑ. Ðаемниците " "приветÑтват теб и твоите войÑки и ви канÑÑ‚ да тренирате Ñ Ñ‚ÑÑ…." msgid "\"Go 'way!\", the witch doctor barks, \"you know all I know.\"" msgstr "\"Заминавай!\", излайва врачката, \"вече знаеш вÑичко, което и аз.\"" msgid "" "An Orcish witch doctor living in the hut deepens your knowledge of magic by " "showing you how to cast stones, read portents, and decipher the intricacies " "of chicken entrails." msgstr "" "ОркÑкиÑÑ‚ знахар, който живее в колибата, задълбочава знаниÑта ти за магиÑ, " "като ти показва как да хвърлÑш камъни, да разчиташ Ð·Ð½Ð°Ð¼ÐµÐ½Ð¸Ñ Ð¸ да дешифрираш " "тънкоÑтите на пилешките вътрешноÑти." msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, the Druids turn you away, indicating they have nothing " "new to teach you." msgstr "" "Ðамираш група друиди на поклонение в една от техните Ñтранни каменни Ñгради. " "Мълчаливо друидите те отблъÑкват, показвайки, че нÑмат какво ново да те " "научат." msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, they teach you new ways to cast spells." msgstr "" "Ðамираш група друиди да Ñе покланÑÑ‚ в една от техните Ñтранни каменни " "Ñгради. Тихичко те те научават на нови начини за правене магии." msgid "" "You tentatively approach the burial ground of ancient warriors. Do you want " "to search the graves?" msgstr "" "Ðеуверено Ñе приближаваш до гробището на древни воини. ИÑкаш ли да претърÑиш " "гробовете?" msgid "" "You spend several hours searching the graves and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" "Прекарваш нÑколко чаÑа в претърÑване на гробовете и не намираш нищо. Подобно " "презрÑно дейÑтвие намалÑва морала на твоÑта армиÑ." msgid "Upon defeating the Zombies you search the graves and find something!" msgstr "След като побеждаваш зомбитата, претърÑваш гробовете и намираш нещо!" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the shipwreck?" msgstr "" "ГниещиÑÑ‚ ÐºÐ¾Ñ€Ð¿ÑƒÑ Ð½Ð° голÑм пиратÑки кораб Ñкърца зловещо, докато Ñе блъÑка в " "Ñкалите. ИÑкаш ли да претърÑиш оÑтанките на кораба?" msgid "" "You spend several hours sifting through the debris and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" "Прекарваш нÑколко чаÑа в преÑÑване на отломките и не намирате нищо. Подобно " "презрÑно дейÑтвие намалÑва морала на вашата армиÑ." msgid "" "Upon defeating the Ghosts you sift through the debris and find something!" msgstr "След като побеждаваш призраците, преÑÑваш отломките и намираш нещо!" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the ship?" msgstr "" "ГниещиÑÑ‚ ÐºÐ¾Ñ€Ð¿ÑƒÑ Ð½Ð° голÑм пиратÑки кораб Ñкърца зловещо, докато Ñе блъÑка в " "Ñкалите. ИÑкаш ли да претърÑиш кораба?" msgid "" "Upon defeating the Skeletons you sift through the debris and find something!" msgstr "След като побеждаваш Ñкелетите, преÑÑваш отломките и намираш нещо!" msgid "Your men spot a navigational buoy, confirming that you are on course." msgstr "" "Твойте хора забелÑзват навигационен буй, потвърждаващ, че Ñте на Ð¿Ñ€Ð°Ð²Ð¸Ð»Ð½Ð¸Ñ " "курÑ." msgid "" "Your men spot a navigational buoy, confirming that you are on course and " "increasing their morale." msgstr "" "Твойте хора забелÑзват навигационен буй, който потвърждава, че Ñте на " "Ð¿Ñ€Ð°Ð²Ð¸Ð»Ð½Ð¸Ñ ÐºÑƒÑ€Ñ Ð¸ повишава морала им." msgid "" "The drink at the oasis is refreshing, but offers no further benefit. The " "oasis might help again if you fought a battle first." msgstr "" "Ðапитката в оазиÑа е оÑвежаваща, но не ноÑи допълнителна полза. ОазиÑÑŠÑ‚ може " "да помогне отново, ако Ñи водил битка." msgid "" "A drink at the oasis fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" "Едно питие в оазиÑа изпълва войÑките ти ÑÑŠÑ Ñила и повдига духа им. Ð”Ð½ÐµÑ " "можеш да пътуваш малко по-далеч." msgid "" "The drink at the watering hole is refreshing, but offers no further benefit. " "The watering hole might help again if you fought a battle first." msgstr "" "Ðапитката във Ð²Ð¾Ð´Ð¾Ð¿Ð¾Ñ Ðµ оÑвежаваща, но не ноÑи допълнителна полза. Дупката " "за поливане може да помогне отново, ако Ñи водил битка." msgid "" "A drink at the watering hole fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" "Едно питие на Ð²Ð¾Ð´Ð¾Ð¿Ð¾Ñ Ð¸Ð·Ð¿ÑŠÐ»Ð²Ð° войÑките ти ÑÑŠÑ Ñила и повдига духа им. Ð”Ð½ÐµÑ " "можеш да пътуваш малко по-далеч." msgid "" "It doesn't help to pray twice before a battle. Come back after you've fought." msgstr "" "Ðе помага да Ñе молиш два пъти преди битка. Върни Ñе, Ñлед като Ñи Ñе бил." msgid "A visit and a prayer at the temple raises the morale of your troops." msgstr "ПоÑещението и молитвата в храма повишават морала на твойте войÑки." msgid "" "An old Knight appears on the steps of the gazebo. \"I am sorry, my liege, I " "have taught you all I can.\"" msgstr "" "Ðа Ñтъпалата на беÑедката Ñе поÑвÑва Ñтар рицар. \"СъжалÑвам, гоÑподарю мой, " "научих те на вÑичко, което мога.\"" msgid "" "An old Knight appears on the steps of the gazebo. \"My liege, I will teach " "you all that I know to aid you in your travels.\"" msgstr "" "Ðа Ñтъпалата на беÑедката Ñе поÑвÑва Ñтар рицар. \"ГоÑподарю мой, ще те " "науча на вÑичко, което знам, за да ти помага в пътуваниÑта ти.\"" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he says, \"I would give you an artifact as a reward, but " "you're all full.\"" msgstr "" "СпаÑи оцелÑл от корабокрушение от Ñигурна Ñмърт човек в безмилоÑÑ‚Ð½Ð¸Ñ Ð¾ÐºÐµÐ°Ð½. " "Благодарен, той казва: \"Бих ти дал артефакт като награда, но нÑмаш мÑÑто.\"" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he rewards you for your act of kindness by giving you the " "%{art}." msgstr "" "СпаÑи оцелÑл от корабокрушение от Ñигурна Ñмърт човек в безмилоÑÑ‚Ð½Ð¸Ñ Ð¾ÐºÐµÐ°Ð½. " "Благодарен, той те възнаграждава за проÑвената доброта, като ти дава %{art}." msgid "A leprechaun offers you the %{art} for the small price of %{gold} Gold." msgstr "Леприкона ти предлага %{art} на малката цена от %{gold} злато." msgid "" "A leprechaun offers you the %{art} for the small price of %{gold} Gold and " "%{count} %{res}." msgstr "" "Ð›ÐµÐ¿Ñ€Ð¸ÐºÐ¾Ð½Ñ Ñ‚Ð¸ предлага %{art} на ниÑката цена от %{gold} злато и %{count} " "%{res}." msgid "Do you wish to buy this artifact?" msgstr "ИÑкаш ли да купиш този артефакт?" msgid "" "You try to pay the leprechaun, but realize that you can't afford it. The " "leprechaun stamps his foot and ignores you." msgstr "" "Опитваш Ñе да платиш на леприкона, но разбираш, че не можеш да Ñи го " "позволиш. Леприконът тропва Ñ ÐºÑ€Ð°Ðº и те игнорира." msgid "" "Insulted by your refusal of his generous offer, the leprechaun stamps his " "foot and ignores you." msgstr "" "Обиден от отказа ти на щедрото му предложение, леприконът тропва Ñ ÐºÑ€Ð°Ðº и те " "игнорира." msgid "You've found the artifact: " msgstr "Ти откри артефакта:" msgid "" "You've found the humble dwelling of a withered hermit. The hermit tells you " "that he is willing to give the %{art} to the first wise person he meets." msgstr "" "Ðамери Ñкромното жилище на изÑъхнал отшелник. Отшелникът ти казва, че е " "готов да даде %{art} на Ð¿ÑŠÑ€Ð²Ð¸Ñ Ð¼ÑŠÐ´ÑŠÑ€ човек, когото Ñрещне." msgid "" "You've come across the spartan quarters of a retired soldier. The soldier " "tells you that he is willing to pass on the %{art} to the first true leader " "he meets." msgstr "" "Попаднал Ñте на ÑпартанÑките квартири на пенÑиониран войник. Войникът ти " "казва, че е готов да предаде %{art} на Ð¿ÑŠÑ€Ð²Ð¸Ñ Ð¸ÑтинÑки лидер, когото Ñрещне." msgid "" "You've encountered a strange person with a hat and an owl on it. He tells " "you that he is willing to give %{art} if you have %{skill}." msgstr "" "Срещаш Ñтранен човек Ñ ÑˆÐ°Ð¿ÐºÐ° и бухал върху неÑ. Той ти казва, че е готов да " "даде %{art}, ако имате %{skill}." msgid "" "You come upon an ancient artifact. As you reach for it, a pack of Rogues " "leap out of the brush to guard their stolen loot." msgstr "" "Попадаш на древен артефакт. Когато поÑÑгаш към него, група мошеници изÑкачат " "от храÑтите, за да опазÑÑ‚ откраднатата Ñи плÑчка." msgid "" "Through a clearing you observe an ancient artifact. Unfortunately, it's " "guarded by a nearby %{monster}. Do you want to fight the %{monster} for the " "artifact?" msgstr "" "Ðа една полÑна виждаш древен артефакт. За Ñъжаление, той Ñе охранÑва от " "%{monster}. ИÑкаш ли да Ñе биеш Ñ %{monster} за артефакта?" msgid "Victorious, you take your prize, the %{art}." msgstr "Победител, ти вземаш ÑвоÑта награда, %{art}." msgid "" "Discretion is the better part of valor, and you decide to avoid this fight " "for today." msgstr "" "ДиÑкретноÑтта е по-добрата чаÑÑ‚ от доблеÑтта и ти решаваш да избегнеш тази " "битка днеÑ." msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold pieces." msgstr "" "След като Ñи прекарал чаÑове в опити да извадиш Ñандъка от морето, ти го " "отварÑш и намирате %{gold} златни парчета." msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold and the %{art}." msgstr "" "След като Ñи прекарал чаÑове в опити да извадиш Ñандъка от морето, ти го " "отварÑш и намираш %{gold} злато и %{art}." msgid "" "After spending hours trying to fish the chest out of the sea, you open it, " "only to find it empty." msgstr "" "След като Ñи прекарал чаÑове в опити да извадиш Ñандъка от морето, ти го " "отварÑш, Ñамо за да го намериш празен." msgid "" "After scouring the area, you fall upon a hidden treasure cache. You may take " "the gold or distribute the gold to the peasants for experience. Do you wish " "to keep the gold?" msgstr "" "След като претърÑи района, попадаш на Ñкрит тайник ÑÑŠÑ Ñъкровище. Можеш да " "вземеш златото или да го раздадеш на ÑелÑните за опит. ИÑкаш ли да запазиш " "златото?" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "%{gold} gold pieces." msgstr "" "След като претърÑи района, попадаш на Ñкрит Ñандък, Ñъдържащ %{gold} златни " "парчета." msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "ancient artifact %{art}." msgstr "" "След като претърÑи района, попадаш на Ñкрит Ñандък, Ñъдържащ Ð´Ñ€ÐµÐ²Ð½Ð¸Ñ " "артефакт %{art}." msgid "" "You stumble upon a dented and tarnished lamp lodged deep in the earth. Do " "you wish to rub the lamp?" msgstr "" "Ðатъкваш Ñе на вдлъбната и зацапана лампа, забита дълбоко в земÑта. ИÑкаш ли " "да потъркаш лампата?" msgid "" "You have taken control of the local Alchemist shop. It will provide you with " "%{count} unit of Mercury per day." msgstr "" "Пое контрол над меÑÑ‚Ð½Ð¸Ñ Ð¼Ð°Ð³Ð°Ð·Ð¸Ð½ на Ðлхимика. Ще ти оÑигури %{count} единица " "живак на ден." msgid "" "You gain control of a sawmill. It will provide you with %{count} units of " "wood per day." msgstr "" "Получаваш контрол над дъÑкорезницата. Ще ти оÑигури %{count} единици дърва " "на ден." msgid "" "You gain control of an ore mine. It will provide you with %{count} units of " "ore per day." msgstr "" "Получаваш контрол над мина за руда. Ще ти оÑигури %{count} единица руда на " "ден." msgid "" "You gain control of a sulfur mine. It will provide you with %{count} unit of " "sulfur per day." msgstr "" "Получаваш контрол над ÑÑрна мина. Ð¢Ñ Ñ‰Ðµ ти оÑигури %{count} единица ÑÑра на " "ден." msgid "" "You gain control of a crystal mine. It will provide you with %{count} unit " "of crystal per day." msgstr "" "Получаваш контрол над криÑтална мина. Ð¢Ñ Ñ‰Ðµ ти оÑигури %{count} единица " "криÑтал на ден." msgid "" "You gain control of a gem mine. It will provide you with %{count} unit of " "gems per day." msgstr "" "Получаваш контрол над мина за Ñкъпоценни камъни. Ð¢Ñ Ñ‰Ðµ ти оÑигури %{count} " "единица Ñкъпоценни камъни на ден." msgid "" "You gain control of a gold mine. It will provide you with %{count} gold per " "day." msgstr "" "Получаваш контрол над златна мина. Ще ти оÑигури %{count} злато на ден." msgid "" "The lighthouse is now under your control, and all of your ships will now " "move further each day." msgstr "" "Фарът Ñега е под твой контрол и вÑички твой кораби Ñега ще Ñе движат вÑе по-" "далеч вÑеки ден." msgid "You gain control of a %{name}." msgstr "Получаваш контрол над %{name}." msgid "" "You come upon an abandoned gold mine. The mine appears to be haunted. Do you " "wish to enter?" msgstr "" "Попадаш на изоÑтавена златна мина. Мината изглежда обитавана от духове. " "Желаеш ли да влезеш?" msgid "You beat the Ghosts and are able to restore the mine to production." msgstr "" "Ти побеждаваш Призраците и Ñи в ÑÑŠÑтоÑние да възÑтановиш мината за " "производÑтво." msgid "" "A group of %{monster} with a desire for greater glory wish to join you. Do " "you accept?" msgstr "" "Група от %{monster} Ñ Ð¶ÐµÐ»Ð°Ð½Ð¸Ðµ за по-голÑма Ñлава желае да Ñе приÑъедини към " "теб. Приемаш ли?" msgid "As you approach the dwelling, you notice that there is no one here." msgstr "Когато Ñе приближаваш до жилището, забелÑзваш, че тук нÑма никой." msgid "" "You search the ruins, but the Medusas that used to live here are gone. " "Perhaps there will be more next week." msgstr "" "ПретърÑваш руините, но Медузите, които Ñа живели тук, ги нÑма. Може би " "Ñледващата Ñедмица ще има още." msgid "" "You've found some Medusas living in the ruins. They are willing to join your " "army for a price. Do you want to recruit Medusas?" msgstr "" "Ðамираш нÑколко медузи, живеещи в руините. Те Ñа готови да Ñе приÑъединÑÑ‚ " "към твоÑта Ð°Ñ€Ð¼Ð¸Ñ Ñрещу определена цена. ИÑкаш ли да наемеш Медузите?" msgid "" "You've found a Sprite Tree City. Unfortunately, none of the Sprites living " "there wish to join an army. Maybe next week." msgstr "" "Ðамираш града на дърветата на Фейте. За Ñъжаление никой от живеещите там фей " "не желае да Ñе приÑъедини към армиÑта ти. Може би Ñледващата Ñедмица." msgid "" "Some of the Sprites living in the tree city are willing to join your army " "for a price. Do you want to recruit Sprites?" msgstr "" "ÐÑкои от Фейте, живеещи в града на дърветата, Ñа готови да Ñе приÑъединÑÑ‚ " "към твоÑта Ð°Ñ€Ð¼Ð¸Ñ Ñрещу определена цена. ИÑкаш ли да наемеш Фей?" msgid "" "A colorful Rogues' wagon stands empty here. Perhaps more Rogues will be here " "later." msgstr "Ðамираш празен катун на Мошеници. Може би те ще бъдат тук по-къÑно." msgid "" "Distant sounds of music and laughter draw you to a colorful wagon housing " "Rogues. Do you wish to have any Rogues join your army?" msgstr "" "Далечни звуци на музика и ÑмÑÑ… те привличат към катун, в който Ñе намират " "Мошеници. ИÑкаш ли нÑкой мошеник да Ñе приÑъедини към твоÑта армиÑ?" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. The " "tents are unoccupied. Perhaps more Nomads will be here later." msgstr "" "Група оръфани палатки, развÑвани от пÑÑÑŠÑ‡Ð½Ð¸Ñ Ð²Ñтър, те примамват. Палатките " "Ñа празни. Може би номадите ще бъдат тук по-къÑно." msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. Do you " "wish to have any Nomads join you during your travels?" msgstr "" "Група оръфани палатки, развÑвани от пÑÑÑŠÑ‡Ð½Ð¸Ñ Ð²Ñтър, те примамват. Желаеш ли " "нÑкой номад да Ñе приÑъедини към теб по време на пътуваниÑта ти?" msgid "The pit of mud bubbles for a minute and then lies still." msgstr "Ямата от кал бълбука за малко и Ñлед това Ñтой неподвижно." msgid "" "As you approach the bubbling pit of mud, creatures begin to climb out and " "position themselves around it. In unison they say: \"Mother Earth would like " "to offer you a few of her troops. Do you want to recruit Earth Elementals?\"" msgstr "" "Когато Ñе приближаваш до бълбукащата Ñма от кал, ÑъщеÑтвата започват да Ñе " "изкачват и да Ñе позиционират около неÑ. Ð’ униÑон те казват: \"Майката Ð—ÐµÐ¼Ñ " "би иÑкала да ти предложи нÑколко от войÑките Ñи. ИÑкаш ли да наемеш земни " "елементали?\"" msgid "You enter the structure of white stone pillars, and find nothing." msgstr "Влизаш в Ñтруктурата от бели каменни колони и не намираш нищо." msgid "" "White stone pillars support a roof that rises up to the sky. As you enter " "the structure, the dead air of the outside gives way to a whirling gust that " "almost pushes you back out. The air current materializes into a barely " "visible form. The creature asks, in what can only be described as a loud " "whisper: \"Why have you come? Are you here to call upon the forces of the " "air?\"" msgstr "" "Бели каменни Ñтълбове поддържат покрив, който Ñе издига до небето. Когато " "влизаш в Ñтруктурата, мъртвиÑÑ‚ въздух отвън отÑтъпва мÑÑто на въртÑщ Ñе " "порив, който почти те избутва обратно. Въздушното течение Ñе материализира в " "едва видима форма. СъщеÑтвото пита, което може да Ñе опише Ñамо като Ñилен " "шепот: \"Защо Ñи дошъл? Тук Ñи, за да призовеш Ñилите на въздуха ли?\"" msgid "No Fire Elementals approach you from the lava pool." msgstr "Ðикакви Огнени елементали не Ñе доближават до теб от баÑейна Ñ Ð»Ð°Ð²Ð°." msgid "" "Beneath a structure that serves to hold in heat, Fire Elementals move about " "in a fiery pool of molten lava. A group of them approach you and offer their " "services. Would you like to recruit Fire Elementals?" msgstr "" "Под Ñтруктура, коÑто Ñлужи за задържане на топлина, Огнените елементали Ñе " "движат в огнен баÑейн от разтопена лава. Група от Ñ‚ÑÑ… Ñе обръщат към теб и " "предлагат уÑлугите Ñи. ИÑкаш ли да наемеш Огнени елементали?" msgid "A face forms in the water for a moment, and then is gone." msgstr "Лице Ñе Ð¾Ñ„Ð¾Ñ€Ð¼Ñ Ð²ÑŠÐ² водата за момент и Ñлед това изчезва." msgid "" "Crystalline structures cast shadows over a small reflective pool of water. " "You peer into the pool, and a face that is not your own peers back. It asks: " "\"Would you like to call upon the powers of water?\"" msgstr "" "КриÑтални Ñтруктури хвърлÑÑ‚ Ñенки върху малък отразÑващ воден баÑейн. " "Ðадникваш в баÑейна и лице, което не е твоето, Ñе оглежда там. То пита: " "\"ИÑкаш ли да призовеш Ñилите на водата?\"" msgid "This burial site is deathly still." msgstr "Това мÑÑто за погребение е вÑе още ÑмъртоноÑно." msgid "" "Restless spirits of long dead warriors seeking their final resting place " "offer to join you in hopes of finding peace. Do you wish to recruit ghosts?" msgstr "" "ÐеÑпокойните духове на отдавна мъртви воини, които търÑÑÑ‚ Ñвоето поÑледно " "мÑÑто за почивка, предлагат да Ñе приÑъединÑÑ‚ към теб Ñ Ð½Ð°Ð´ÐµÐ¶Ð´Ð°Ñ‚Ð° да намерÑÑ‚ " "покой. ИÑкаш ли да наемеш призраци?" msgid "" "The City of the Dead is empty of life, and empty of unlife as well. Perhaps " "some undead will move in next week." msgstr "" "Ð’ Градът на мъртвите нÑма живот, както и неживот. Може би нÑкои немъртви ще " "Ñе заÑелÑÑ‚ Ñледващата Ñедмица." msgid "" "Some Liches living here are willing to join your army for a price. Do you " "want to recruit Liches?" msgstr "" "ÐÑкои Личове, живеещи тук, Ñа готови да Ñе приÑъединÑÑ‚ към твоÑта Ð°Ñ€Ð¼Ð¸Ñ " "Ñрещу определена цена. ИÑкаш ли да наемеш Личове?" msgid "" "You've found the ruins of an ancient city, now inhabited solely by the " "undead. Will you search?" msgstr "" "Ðамери руините на древен град, Ñега обитаван Ñамо от немъртви. Ще търÑиш ли " "още?" msgid "" "Some of the surviving Liches are impressed by your victory over their " "fellows, and offer to join you for a price. Do you want to recruit Liches?" msgstr "" "ÐÑкои от оцелелите Личове Ñа впечатлени от победата ти над техните ÑÑŠÐ±Ñ€Ð°Ñ‚Ñ Ð¸ " "предлагат да Ñе приÑъединÑÑ‚ към теб Ñрещу определена цена. ИÑкаш ли да " "наемеш Личове?" msgid "" "You've found one of those bridges that Trolls are so fond of living under, " "but there are none here. Perhaps there will be some next week." msgstr "" "Ðамери един от онези моÑтове, под които троловете обичат да живеÑÑ‚, но Ñега " "ги нÑма. Може би ще има друга Ñедмица." msgid "" "Some Trolls living under a bridge are willing to join your army, but for a " "price. Do you want to recruit Trolls?" msgstr "" "ÐÑкои тролове, живеещи под моÑÑ‚, Ñа готови да Ñе приÑъединÑÑ‚ към твоÑта " "армиÑ, но Ñрещу определена цена. ИÑкаш ли да наемеш тролове?" msgid "Trolls living under the bridge challenge you. Will you fight them?" msgstr "Троловете, живеещи под моÑта, те предизвикват. Ще Ñе биеш ли Ñ Ñ‚ÑÑ…?" msgid "" "A few Trolls remain, cowering under the bridge. They approach you and offer " "to join your forces as mercenaries. Do you want to buy any Trolls?" msgstr "" "ОÑтават нÑколко трола, Ñвити под моÑта. Те Ñе приближават до теб и предлагат " "да Ñе приÑъединÑÑ‚ към Ñилите ти като наемници. ИÑкаш ли да купиш нÑколко " "трола?" msgid "" "The Dragon city has no Dragons willing to join you this week. Perhaps a " "Dragon will become available next week." msgstr "" "Ð’ Градът на драконите нÑма дракони, желаещи да Ñе приÑъединÑÑ‚ към теб тази " "Ñедмица. Може би ще има Ñледващата Ñедмица." msgid "" "The Dragon city is willing to offer some Dragons for your army for a price. " "Do you wish to recruit Dragons?" msgstr "" "Градът на драконите е готов да предложи нÑколко дракона за твоÑта Ð°Ñ€Ð¼Ð¸Ñ Ð½Ð° " "определена цена. ИÑкаш ли да наемеш дракони?" msgid "" "You stand before the Dragon City, a place off-limits to mere humans. Do you " "wish to violate this rule and challenge the Dragons to a fight?" msgstr "" "Ти Ñтоиш пред Града на драконите, мÑÑто, забранено за обикновени хора. ИÑкаш " "ли да нарушиш това правило и да предизвикаш Драконите на битка?" msgid "" "Having defeated the Dragon champions, the city's leaders agree to supply " "some Dragons to your army for a price. Do you wish to recruit Dragons?" msgstr "" "След като победи драконовите шампиони, лидерите на града Ñе ÑъглаÑÑват да ти " "дадат нÑколко дракона за твоÑта Ð°Ñ€Ð¼Ð¸Ñ Ñрещу определена цена. ИÑкаш ли да " "наемеш дракони?" msgid "From the observation tower, you are able to see distant lands." msgstr "От наблюдателната кула можеш да видиш далечни земи." msgid "" "The spring only refills once a week, and someone's already been here this " "week." msgstr "" "Изворът Ñе зарежда Ñамо веднъж Ñедмично и нÑкой вече е бил тук тази Ñедмица." msgid "" "A drink at the spring is supposed to give you twice your normal spell " "points, but you are already at that level." msgstr "" "Предполага Ñе, че едно глътка от извора ще ти даде два пъти повече точки от " "обичайните за заклинаниÑ, но ти вече имаш толкова." msgid "" "A drink from the spring fills your blood with magic! You have twice your " "normal spell points in reserve." msgstr "" "Глътка от извора изпълва кръвта ти Ñ Ð¼Ð°Ð³Ð¸Ñ! Имаш в резерв два пъти " "нормалните точки за заклинание." msgid "" "Recognizing you, the butler refuses to admit you. \"The master,\" he says, " "\"will not see the same student twice.\"" msgstr "" "Разпознавайки те, икономът отказва да те приеме. \"УчителÑÑ‚, казва той, не " "приема един и Ñъщ ученик два пъти.\"" msgid "" "The butler admits you to see the master of the house. He trains you in the " "four skills a hero should know." msgstr "" "Икономът те допуÑка да видите гоÑÐ¿Ð¾Ð´Ð°Ñ€Ñ Ð½Ð° къщата. Той те обучава в четирите " "умениÑ, които един герой трÑбва да знае." msgid "" "The butler opens the door and looks you up and down. \"You are neither " "famous nor diplomatic enough to be admitted to see my master,\" he sniffs. " "\"Come back when you think yourself worthy.\"" msgstr "" "Икономът Ð¾Ñ‚Ð²Ð°Ñ€Ñ Ð²Ñ€Ð°Ñ‚Ð°Ñ‚Ð° и те оглежда от горе до долу. \"Ðе Ñи нито извеÑтен, " "нито доÑтатъчно дипломатичен, за да бъдеш допуÑнат до гоÑÐ¿Ð¾Ð´Ð°Ñ€Ñ Ð¼Ð¸\", " "подÑмърча той. \"Върни Ñе, когато Ñтанеш доÑтоен.\"" msgid " and " msgstr " и " msgid "" "All of the %{monsters} you have in your army have been trained by the battle " "masters of the fort. Your army now contains %{monsters2}." msgstr "" "Ð’Ñички %{monsters}, които имаш във твоÑта армиÑ, Ñа обучени от бойните " "майÑтори на форта. ТвоÑта Ð°Ñ€Ð¼Ð¸Ñ Ð²ÐµÑ‡Ðµ Ñъдържа %{monsters2}." msgid "" "An unusual alliance of Ogres, Orcs, and Dwarves offer to train (upgrade) any " "such troops brought to them. Unfortunately, you have none with you." msgstr "" "Ðеобичаен Ñъюз на Огри, Орки и Джуджета предлага да обучава (надгражда) " "вÑички войÑки, които Ñа им доведени. За Ñъжаление нÑмаш нито една ÑÑŠÑ Ñебе " "Ñи." msgid "All of your %{monsters} have been upgraded into %{monsters2}." msgstr "Ð’Ñичките ти %{monsters} Ñа надградени до %{monsters2}." msgid "" "A blacksmith working at the foundry offers to convert all Pikemen and " "Swordsmen's weapons brought to him from iron to steel. He also says that he " "knows a process that will convert Iron Golems into Steel Golems. " "Unfortunately, you have none of these troops in your army, so he can't help " "you." msgstr "" "Ковача, който работи в леÑрната, предлага да преобразува вÑички донеÑени " "Ð¾Ñ€ÑŠÐ¶Ð¸Ñ Ð½Ð° пикермани и мечоноÑци от желÑзо в Ñтомана. Той Ñъщо така казва, че " "знае процеÑ, който ще превърне Железните Големи в Стоманени Големи. За " "Ñъжаление нÑмаш нито една от тези войÑки във твоÑта армиÑ, така че той не " "може да ти помогне." msgid "" "The captain looks at you with surprise and says:\n" "\"You already have all the maps I know about. Let me fish in peace now.\"" msgstr "" "Капитанът те поглежда изненадано и казва:\n" "\"Вече имаш вÑички карти, за които знам. Сега ме оÑтави да Ð»Ð¾Ð²Ñ Ñ€Ð¸Ð±Ð° на " "ÑпокойÑтвие.\"" msgid "" "A retired captain living on this refurbished fishing platform offers to sell " "you maps of the sea he made in his younger days for 1,000 gold. Do you wish " "to buy the maps?" msgstr "" "ПенÑиониран капитан, живеещ на тази обновена платформа за риболов, предлага " "да ти продаде карти на морето, които е направил в младоÑтта Ñи за 1000 " "злато. ИÑкаш ли да закупиш картите?" msgid "" "The captain sighs. \"You don't have enough money, eh? You can't expect me " "to give my maps away for free!\"" msgstr "" "Капитанът въздъхва. \"ÐÑмаш доÑтатъчно пари, а? Ðе можеш да очакваш от мен " "да дам картите Ñи безплатно!\"" msgid "" "You come upon an obelisk made from a type of stone you have never seen " "before. Staring at it intensely, the smooth surface suddenly changes to an " "inscription. The inscription is a piece of a lost ancient map. Quickly you " "copy down the piece and the inscription vanishes as abruptly as it appeared." msgstr "" "Попадаш на обелиÑк, направен от вид камък, който никога преди не Ñи виждал. " "Гледайки го напрегнато, гладката повърхноÑÑ‚ изведнъж Ñе превръща в надпиÑ. " "ÐадпиÑÑŠÑ‚ е чаÑÑ‚ от изгубена антична карта. Бързо копираш парчето и надпиÑÑŠÑ‚ " "изчезва Ñъщо толкова внезапно, както Ñе и поÑви." msgid "You have already been to this obelisk." msgstr "Вече Ñи поÑетил този обелиÑк." msgid "" "Upon your approach, the tree opens its eyes in delight. \"It is good to see " "you, my student. I hope my teachings have helped you.\"" msgstr "" "Когато Ñе приближаваш, дървото Ð¾Ñ‚Ð²Ð°Ñ€Ñ Ð¾Ñ‡Ð¸ от щаÑтие. \"Радвам Ñе да те видÑ, " "ученико мой. ÐадÑвам Ñе моите ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð´Ð° Ñа ти помогнали.\"" msgid "" "Upon your approach, the tree opens its eyes in delight. \"Ahh, an " "adventurer! Allow me to teach you a little of what I have learned over the " "ages.\"" msgstr "" "Когато Ñе приближаваш дървото Ð¾Ñ‚Ð²Ð°Ñ€Ñ Ð¾Ñ‡Ð¸ от щаÑтие. \"Ðаа, авантюриÑÑ‚! " "Позволи ми да те науча на малко от това, което Ñъм научил през вековете.\"" msgid "Upon your approach, the tree opens its eyes in delight." msgstr "Когато Ñе приближаваш, дървото Ð¾Ñ‚Ð²Ð°Ñ€Ñ Ð¾Ñ‡Ð¸ от щаÑтие." msgid "" "\"Ahh, an adventurer! I will be happy to teach you a little of what I have " "learned over the ages for a mere %{count} %{res}.\"" msgstr "" "\"Ðаа, авантюриÑÑ‚! Ще Ñе радвам да те науча на малко от това, което Ñъм " "научил през годините Ñамо за %{count} %{res}.\"" msgid "(Just bury it around my roots.)" msgstr "(ПроÑто го зарови около корените ми.)" msgid "Tears brim in the eyes of the tree." msgstr "Сълзи бликват от очите на дървото." msgid "\"I need %{count} %{res}.\"" msgstr "\"Имам нужда от %{count} %{res}.\"" msgid "it whispers. (sniff) \"Well, come back when you can pay me.\"" msgstr "шепне то. (подÑмърчане) \"Е, върни Ñе, когато можеш да ми платиш.\"" msgid "" "Nestled among the trees sits a blind seer. After you explain the intent of " "your journey, the seer activates his crystal ball, allowing you to see the " "strengths and weaknesses of your opponents." msgstr "" "Сгушен Ñред дърветата Ñеди ÑлÑп гледач. След като обÑÑнÑваш целта на Ñвоето " "пътуване, гледачът задейÑтва ÑвоÑта криÑтална топка, което ти позволÑва да " "видиш Ñилните и Ñлабите Ñтрани на опонентите Ñи." msgid "" "The entrance to the cave is dark, and a foul, sulfurous smell issues from " "the cave mouth. Will you enter?" msgstr "" "Входът на пещерата е тъмен, и от него Ñе ноÑи неприÑтна миризма на ÑÑра. Ще " "влезеш ли вътре?" msgid "Except for evidence of a terrible battle, the cave is empty." msgstr "ОÑвен Ñледи от ужаÑна битка, в пещерата нÑма нищо." msgid "" "You find a powerful and grotesque Demon in the cave. \"Today,\" it rasps, " "\"you will fight and surely die. But I will give you a choice of deaths. You " "may fight me, or you may fight my servants. Do you prefer to fight my " "servants?\"" msgstr "" "Ð’ пещерата намираш мощен и гротеÑков демон. \"ДнеÑ\", изхриптÑва той, \"ще " "Ñе биеш и ÑÑŠÑ ÑигурноÑÑ‚ ще умреш. Ðо аз ще ти дам да избереш как. Можеш да " "Ñе биеш Ñ Ð¼ÐµÐ½ или Ñ Ð¼Ð¾Ð¸Ñ‚Ðµ Ñлуги. Предпочиташ ли да Ñе биеш Ñ Ð¼Ð¾Ð¸Ñ‚Ðµ Ñлуги?\"" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points." msgstr "" "Демонът крещи Ñвоето предизвикателÑтво и атакува! След кратка, отчаÑна " "битка, ти убиваш чудовището и получаваш %{exp} точки опит." msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points and " "%{count} gold." msgstr "" "Демонът крещи Ñвоето предизвикателÑтво и атакува! След кратка, отчаÑна " "битка, ти убиваш чудовището и получаваш %{exp} точки опит и %{count} злато." msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and find the %{art} in the back of the cave." msgstr "" "Демонът крещи Ñвоето предизвикателÑтво и атакува! След кратка, отчаÑна " "битка, ти убиваш чудовището и намираш %{art} в задната чаÑÑ‚ на пещерата." msgid "" "Seeing that you do not have %{count} gold, the demon slashes you with its " "claws, and the last thing you see is a red haze." msgstr "" "Виждайки, че нÑмаш %{count} злато, демонът те нарÑзва Ñ Ð½Ð¾ÐºÑ‚Ð¸Ñ‚Ðµ Ñи и " "поÑледното нещо, което виждаш, е червена мъгла." msgid "" "The Demon leaps upon you and has its claws at your throat before you can " "even draw your sword. \"Your life is mine,\" it says. \"I will sell it back " "to you for %{count} gold.\"" msgstr "" "Демонът Ñкача върху теб и впива ноктите Ñи в гърлото ти, преди дори да " "уÑпееш да извадиш меча Ñи. \"ТвоÑÑ‚ живот е мой\", казва той. \"Ще ти го дам " "обратно за %{count} злато.\"" msgid "" "Upon defeating the daemon's servants, you find a hidden cache with %{count} " "gold." msgstr "" "След като победи Ñлугите на демона, намираш Ñкрита кеÑÐ¸Ñ Ñ %{count} злато." msgid "" "As you enter the Alchemist's Tower, a hobbled, graying man in a brown cloak " "makes his way towards you." msgstr "" "Когато влизаш в кулата на алхимика, към теб Ñе приближава куцащ, поÑивÑл мъж " "Ñ ÐºÐ°Ñ„Ñво наметало." msgid "He checks your pack, and sees that you have 1 cursed item." msgid_plural "" "He checks your pack, and sees that you have %{count} cursed items." msgstr[0] "Той проверÑва багажа ти и вижда, че имаш 1 прокълнат предмет." msgstr[1] "" "Той проверÑва багажа ти и вижда, че имаш %{count} прокълнати предмета." msgid "For %{gold} gold, the alchemist will remove it for you. Do you pay?" msgid_plural "" "For %{gold} gold, the alchemist will remove them for you. Do you pay?" msgstr[0] "" "За %{gold} злато, алхимикът ще го премахне вмеÑто теб. Ще платиш ли?" msgstr[1] "" "За %{gold} злато, алхимикът ще ги премахне вмеÑто теб. Ще платиш ли?" msgid "" "After you consent to pay the requested amount of gold, the alchemist grabs " "the cursed artifact and throws it into his magical cauldron." msgid_plural "" "After you consent to pay the requested amount of gold, the alchemist grabs " "all cursed artifacts and throws them into his magical cauldron." msgstr[0] "" "След като Ñе ÑъглаÑи да платиш иÑканото количеÑтво злато, алхимикът грабва " "Ð¿Ñ€Ð¾ÐºÑŠÐ»Ð½Ð°Ñ‚Ð¸Ñ Ð°Ñ€Ñ‚ÐµÑ„Ð°ÐºÑ‚ и го Ñ…Ð²ÑŠÑ€Ð»Ñ Ð² магичеÑÐºÐ¸Ñ Ñи котел." msgstr[1] "" "След като Ñе ÑъглаÑи да платиш иÑканото количеÑтво злато, алхимикът грабва " "вÑички прокълнати артефакти и ги Ñ…Ð²ÑŠÑ€Ð»Ñ Ð² магичеÑÐºÐ¸Ñ Ñи котел." msgid "" "You hear a voice from behind the locked door, \"You don't have enough gold " "to pay for my services.\"" msgstr "" "Чуваш Ð³Ð»Ð°Ñ Ð·Ð°Ð´ заключената врата: \"ÐÑмаш доÑтатъчно злато, за да платиш за " "уÑлугите ми.\"" msgid "" "You hear a voice from high above in the tower, \"Go away! I can't help you!\"" msgstr "Чуваш Ð³Ð»Ð°Ñ Ð¾Ñ‚Ð²Ð¸Ñоко в кулата: \"Върви Ñи! Ðе мога да ти помогна!\"" msgid "" "The head groom speaks to you, \"That is a fine looking horse you have. I am " "afraid we can give you no better, but the horses your cavalry are riding " "look to be of poor breeding stock. We have many trained war horses which " "would aid your riders greatly. I insist you take them.\"" msgstr "" "ГлавниÑÑ‚ конÑÑ€ ти казва: \"КонÑÑ‚ ти е Ñтрахотен. Ð‘Ð¾Ñ Ñе, че не можем да ти " "дадем по-добър, но конете, които твоÑта ÐºÐ°Ð²Ð°Ð»ÐµÑ€Ð¸Ñ Ñзди, изглежда Ñа от лош " "разплод. Имаме много обучени бойни коне, които биха помогнали значително на " "твойте ездачи. ÐаÑтоÑвам да ги вземеш." msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, he will " "grow tired in a week. You must also let me give better horses to your " "mounted soldiers, their horses look shoddy and weak.\"" msgstr "" "Докато приближаваш конюшните, Ñе поÑвÑва главниÑÑ‚ конÑÑ€, водейки добре " "изглеждащ боен кон. \"Този кон ще ти помогне да уÑкорите пътуването Ñи. Уви, " "той ще Ñе умори Ñлед Ñедмица. ТрÑбва Ñъщо така да ми позволиш да дам по-" "добри коне на твоите кавалериÑти, техните коне изглеждат калпави и Ñлаби.\"" msgid "" "The head groom approaches you and speaks, \"You already have a fine horse, " "and have no inexperienced cavalry which might make use of our trained war " "horses.\"" msgstr "" "ГлавниÑÑ‚ конÑÑ€ Ñе приближава към теб и ти казва: \"Ти вече имаш добър кон и " "имаш опитна кавалериÑ, коÑто може да използва нашите обучени бойни коне.\"" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, his " "endurance will wane with a lot of heavy riding, and you must return for a " "fresh mount in a week. We also have many fine war horses which could benefit " "mounted soldiers, but you have none we can help.\"" msgstr "" "Докато приближаваш конюшните, Ñе поÑвÑва главниÑÑ‚ конÑÑ€, водейки добре " "изглеждащ боен кон. \"Този кон ще ти помогне да уÑкориш пътуването Ñи. Уви, " "издръжливоÑтта му ще отÑлабне Ñлед тежката езда и трÑбва да Ñе върнеш за нов " "кон Ñлед Ñедмица. Имаме и много добри бойни коне, които биха могли да бъдат " "от полза за кавалериÑта ти, но нÑмаш такава, ние можем да помогнем.\"" msgid "The Arena guards turn you away." msgstr "Охраната на Ðрената те отблъÑква." msgid "" "You have your crew stop up their ears with wax before the sirens' eerie song " "has any chance of luring them to a watery grave." msgstr "" "Караш Ñ‚Ð²Ð¾Ñ ÐµÐºÐ¸Ð¿Ð°Ð¶ да запуши ушите Ñи Ñ Ð²Ð¾Ñък, преди зловещата пеÑен на " "Ñирените да има нÑкакъв ÑˆÐ°Ð½Ñ Ð´Ð° ги примами във Ð²Ð¾Ð´Ð½Ð¸Ñ Ð³Ñ€Ð¾Ð±." msgid "" "As the sirens sing their eerie song, your small, determined army manages to " "overcome the urge to dive headlong into the sea." msgstr "" "Докато Ñирените пеÑÑ‚ зловещата Ñи пеÑен, твоÑта малка, решителна Ð°Ñ€Ð¼Ð¸Ñ " "уÑпÑва да преодолее желанието да Ñе гмурне Ñтремглаво в морето." msgid "" "An eerie wailing song emanates from the sirens perched upon the rocks. Many " "of your crew fall under its spell, and dive into the water where they drown. " "You are now wiser for the visit, and gain %{exp} experience." msgstr "" "От Ñирените, кацнали по Ñкалите, Ñе ноÑи зловеща плачеща пеÑен. Много от " "Ñ‚Ð²Ð¾Ñ ÐµÐºÐ¸Ð¿Ð°Ð¶ попадат под неговото заклинание и Ñе гмуркат във водата, където " "Ñе давÑÑ‚. Вече Ñте по-мъдри Ñлед Ñлучката и придобивате %{exp} опит." msgid "" "In a dazzling display of daring, you break into the local jail and free the " "hero imprisoned there, who, in return, pledges loyalty to your cause." msgstr "" "Ð’ оÑлепителна демонÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ð½Ð° ÑмелоÑÑ‚ ти нахлуваш в меÑÑ‚Ð½Ð¸Ñ Ð·Ð°Ñ‚Ð²Ð¾Ñ€ и " "оÑвобождаваш Ð¿Ð»ÐµÐ½ÐµÐ½Ð¸Ñ Ñ‚Ð°Ð¼ герой, който в замÑна Ñе заклева във вÑрноÑÑ‚ към " "твоÑта кауза." msgid "" "You already have %{count} heroes, and regretfully must leave the prisoner in " "this jail to languish in agony for untold days." msgstr "" "Вече имаш %{count} герои и за Ñъжаление трÑбва да оÑтавиш затворника в този " "затвор да тъне в Ð°Ð³Ð¾Ð½Ð¸Ñ Ð±ÐµÐ·Ð±Ñ€Ð¾Ð¹ дни." msgid "" "You enter a rickety hut and talk to the magician who lives there. He tells " "you of places near and far which may aid you in your journeys." msgstr "" "Влизаш в разклатената колиба и разговарÑш Ñ Ð¼Ð°Ð³ÑŒÐ¾Ñника, който живее там. Той " "ти разказва за близки и далечни меÑта, които могат да ти помогнат в " "пътуваниÑта ти." msgid "This eye seems to be intently studying its surroundings." msgstr "Това око изглежда внимателно изучава заобикалÑщата го Ñреда." msgid "You come across a giant Sphinx. The Sphinx remains strangely quiet." msgstr "Попадаш на гигантÑки СфинкÑ. СфинкÑÑŠÑ‚ оÑтава Ñтранно тих." msgid "" "\"I have a riddle for you,\" the Sphinx says. \"Answer correctly, and you " "shall be rewarded. Answer incorrectly, and you shall be eaten. Do you accept " "the challenge?\"" msgstr "" "\"Имам гатанка за теб\", казва СфинкÑÑŠÑ‚. \"Отговори правилно и ще бъдеш " "възнаграден. Отговори неправилно и ще бъдеш изÑден. Приемаш ли " "предизвикателÑтвото?\"" msgid "" "The Sphinx asks you the following riddle:\n" "\n" "'%{riddle}'\n" "\n" "Your answer?" msgstr "" "СфинкÑÑŠÑ‚ ти задава Ñледната гатанка:\n" "\n" "'%{riddle}'\n" "\n" "ТвоÑÑ‚ отговор?" msgid "" "\"You guessed incorrectly,\" the Sphinx says, smiling. The Sphinx swipes at " "you with a paw, knocking you to the ground. Another blow makes the world go " "black, and you know no more." msgstr "" "\"Отговори неправилно\", казва СфинкÑÑŠÑ‚, уÑмихвайки Ñе. СфинкÑÑŠÑ‚ замахва Ñ " "лапа, ÑъбарÑйки те на земÑта. Още един удар кара Ñвета да почернее и ти не " "знаеш нищо повече." msgid "" "Looking somewhat disappointed, the Sphinx sighs. \"You've answered my riddle " "so here's your reward. Now begone.\"" msgstr "" "Изглеждайки малко разочарован, СфинкÑÑŠÑ‚ въздъхва. \"Отговори на загадката " "ми, така че ето ти наградата. Сега Ñе махай.\"" msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "As you speak the magic word, the glowing barrier dissolves into nothingness." msgstr "" "МагичеÑка бариера Ñтои виÑоко пред теб и блокира Ð¿ÑŠÑ‚Ñ Ñ‚Ð¸. Руни на арката " "глаÑÑÑ‚,\n" "\"Кажи ключа и можеш да минеш.\"\n" "Докато произнаÑÑш вълшебната дума, Ñветещата бариера Ñе Ñ€Ð°Ð·Ñ‚Ð²Ð°Ñ€Ñ Ð² нищото." msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "You speak, and nothing happens." msgstr "" "МагичеÑка бариера Ñтои виÑоко пред теб и блокира Ð¿ÑŠÑ‚Ñ Ñ‚Ð¸. Руните на арката " "глаÑÑÑ‚,\n" "\"Кажи ключа и можеш да преминеш.\"\n" "Казваш и нищо не Ñе Ñлучва." msgid "" "You enter the tent and see an old woman gazing into a magic gem. She looks " "up and says,\n" "\"In my travels, I have learned much in the way of arcane magic. A great " "oracle taught me his skill. I have the answer you seek.\"" msgstr "" "Влизаш в палатката и виждаш възраÑтна жена, коÑто Ñе взира в магичеÑки " "Ñкъпоценен камък. Ð¢Ñ Ð²Ð´Ð¸Ð³Ð° поглед и казва:\n" "\"По време на пътуваниÑта Ñи научих много от Ð¿ÑŠÑ‚Ñ Ð½Ð° тайнÑтвената магиÑ. " "Един велик оракул ме научи на умениÑта Ñи. Имам отговора, който търÑиш.\"" msgid "No spell book is present." msgstr "ÐÑма книга ÑÑŠÑ Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ." msgid "" "This spell costs %{mana} spell points. You have no spell points, so you " "cannot cast it." msgstr "" "Това заклинание Ñтрува %{mana} точки магиÑ. ÐÑмаш точки за магиÑ, така че не " "можеш да го направиш." msgid "" "This spell costs %{mana} spell points. You only have %{point} spell points, " "so you cannot cast it." msgstr "" "Това заклинание Ñтрува %{mana} точки магиÑ. Имаш Ñамо %{point} точки магиÑ, " "така че не можеш да го направиш." msgid "The spell was not found." msgstr "Заклинанието не беше намерено." msgid "Only heroes can cast this spell." msgstr "Само герои могат да правÑÑ‚ това заклинание." msgid "This hero is not able to cast adventure spells." msgstr "Този герой не може да прави приключенÑки магии." msgid "Your hero is too tired to cast this spell today. Try again tomorrow." msgstr "" "ТвоÑÑ‚ герой е твърде уморен, за да направи това заклинание днеÑ. Опитай " "отново утре." msgid "This spell cannot be cast on a boat." msgstr "Това заклинание не може да Ñе направи на кораб." msgid "This spell can only be cast near an ocean." msgstr "Това заклинание може да Ñе направи Ñамо близо до океан." msgid "" "There are no boats available and no ocean adjacent to the hero where this " "spell will work." msgstr "" "ÐÑма налични лодки и океан в близоÑÑ‚ до героÑ, където това заклинание ще " "работи." msgid "There are no boats available for this spell." msgstr "ÐÑма налични лодки за това заклинание." msgid "There is no ocean adjacent to the hero where this spell will work." msgstr "ÐÑма океан в близоÑÑ‚ до героÑ, където това заклинание ще работи." msgid "" "You do not own any town or castle that is not currently occupied by a hero. " "This spell will have no effect." msgstr "" "Ðе притежаваш нито един град или замък, който в момента да не е зает от " "герой. Ðе можеш да направиш това заклинание." msgid "This hero is already in a town, so this spell will have no effect." msgstr "" "Този герой вече е в град, така че не можеш да направиш това заклинание." msgid "" "The nearest town is %{town}.\n" "\n" "This town is occupied by your hero %{hero}." msgstr "" "Ðай-близкиÑÑ‚ град е %{town}.\n" "\n" "Този град е окупиран от Ñ‚Ð²Ð¾Ñ Ð³ÐµÑ€Ð¾Ð¹ %{hero}." msgid "This spell is already in effect." msgstr "Това заклинание вече е в Ñила." msgid "" "No opponent neither has nor can have any hero under their command anymore. " "Casting this spell will have no effect." msgstr "" "Ðикой противник вече нÑма и не може да има герой под Ñвое командване. " "Изричането на това заклинание нÑма да има ефект." msgid "" "No opponent has a hero under their command at this time. Casting this spell " "will have no effect." msgstr "" "Ðикой противник нÑма герой под Ñвое командване в момента. Изричането на това " "заклинание нÑма да има ефект." msgid "" "You must be within %{count} spaces of a monster for the Visions spell to " "work." msgstr "" "ТрÑбва да Ñи в рамките на %{count} интервали от чудовище, за да работи " "заклинанието ВидениÑ." msgid "" "You must be standing on the entrance to a mine (sawmills and alchemist labs " "do not count) to cast this spell." msgstr "" "ТрÑбва да Ñтоиш на входа на мина (дъÑкорезници и алхимични лаборатории не Ñе " "броÑÑ‚), за да направиш това заклинание." msgid "You must first defeat the ghosts guarding the mine to cast this spell." msgstr "" "Първо трÑбва да победиш призраците, които пазÑÑ‚ мината, за да направиш това " "заклинание." msgid "" "There are already at least as many elementals guarding the mine as this hero " "can generate. Casting this spell will have no effect." msgstr "" "Вече има поне толкова елементали, които пазÑÑ‚ мината, който този герой може " "да генерира. Изричането на това заклинание нÑма да има ефект." msgid "%{name} the %{race} (Level %{level})" msgstr "%{name} е %{race} (Ðиво %{level})" #, fuzzy msgid "" "'Spread' combat formation spreads the hero's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "Бойната Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ \"РазпръÑкване\" Ñ€Ð°Ð·Ð¿Ñ€ÐµÐ´ÐµÐ»Ñ Ñ‚Ð²Ð¾Ð¸Ñ‚Ðµ армии от горе до долу на " "бойното поле, Ñ Ð¿Ð¾Ð½Ðµ едно празно проÑтранÑтво между вÑÑка армиÑ." #, fuzzy msgid "" "'Grouped' combat formation bunches the hero's army together in the center of " "their side of the battlefield." msgstr "" "\"Групираната\" бойна Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ð±ÐµÐ´Ð¸Ð½Ñва армиÑта ти в центъра от твоÑта " "Ñтрана на бойното поле." msgid "Are you sure you want to dismiss this Hero?" msgstr "Сигурен ли Ñи, че иÑкаш да отпратиш този герой?" msgid "View Experience Info" msgstr "Виж Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° опит" msgid "View Spell Points Info" msgstr "Виж Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° магичеÑките точките" msgid "Set army combat formation to 'Spread'" msgstr "Задай армейÑка бойна Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ \"РазпръÑкване\"" msgid "Set army combat formation to 'Grouped'" msgstr "Задай армейÑка бойна Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ \"Групирана\"" msgid "Exit Hero Screen" msgstr "Изход от екрана на героÑ" msgid "You cannot dismiss a hero in a castle" msgstr "Ðе можеш да отпратиш герой в замък" msgid "Dismissal of %{name} the %{race} is prohibited by scenario" msgstr "Отпращането на %{name} %{race} е забранено от ÑценариÑ" msgid "Dismiss %{name} the %{race}" msgstr "Отпрати %{name} %{race}" msgid "Show previous hero" msgstr "Покажи Ð¿Ñ€ÐµÐ´Ð¸ÑˆÐ½Ð¸Ñ Ð³ÐµÑ€Ð¾Ð¹" msgid "Show next hero" msgstr "Покажи ÑÐ»ÐµÐ´Ð²Ð°Ñ‰Ð¸Ñ Ð³ÐµÑ€Ð¾Ð¹" msgid "Blood Morale" msgstr "Кръвен морал" msgid "%{morale} Morale" msgstr "%{morale} Морал" msgid "%{luck} Luck" msgstr "%{luck} КъÑмет" msgid "Current Luck Modifiers:" msgstr "Текущи модификатори на къÑмета:" msgid "Current Morale Modifiers:" msgstr "Текущи модификатори на морала:" msgid "Entire army is undead, so morale does not apply." msgstr "ЦÑлата Ð°Ñ€Ð¼Ð¸Ñ Ðµ немъртва, така че моралът не Ñе прилага." msgid "" "Current experience %{exp1}.\n" " Next level %{exp2}." msgstr "" "Текущ опит %{exp1}.\n" " Следващо ниво %{exp2}." msgid "Level %{level}" msgstr "Ðиво %{level}" #, fuzzy msgid "" "%{name} currently has %{point} spell points out of a maximum of %{max}. The " "maximum number of spell points is 10 times the hero's knowledge. It is " "occasionally possible for the hero to have more than their maximum spell " "points via special events." msgstr "" "%{name} в момента има %{point} точки за заклинание от макÑимум %{max}. " "МакÑималниÑÑ‚ брой точки за заклинание е 10 пъти по-голÑм от твойте знаниÑ. " "ПонÑкога е възможно да имаш повече от макÑималните Ñи точки Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ Ð¿Ñ€Ð¸ " "Ñпециални ÑъбитиÑ." msgid "%{name1} meets %{name2}" msgstr "%{name1} Ñреща %{name2}" msgid "Enemy heroes are now fully identifiable." msgstr "ВражеÑките герои вече могат да бъдат идентифицирани напълно." msgid "Identify Hero" msgstr "Идентифицирай героÑ" msgid "Select town to port to." msgstr "Избери град за приÑтанище." msgid "Town Portal" msgstr "ГрадÑки портал" msgid "The creatures are willing to join us!" msgstr "СъщеÑтвата Ñа готови да Ñе приÑъединÑÑ‚ към наÑ!" msgid "All the creatures will join us..." msgstr "Ð’Ñички ÑъщеÑтва ще Ñе приÑъединÑÑ‚ към наÑ..." msgid "The creature will join us..." msgid_plural "%{count} of the creatures will join us..." msgstr[0] "Създанието ще Ñе приÑъедини към наÑ..." msgstr[1] "%{count} от ÑъзданиÑта ще Ñе приÑъединÑÑ‚ към наÑ..." msgid "" "\n" " for a fee of %{gold} gold." msgstr "" "\n" "Ñрещу такÑа от %{gold} злато." msgid "These weak creatures will surely flee before us." msgstr "Тези Ñлаби ÑÑŠÐ·Ð´Ð°Ð½Ð¸Ñ ÑÑŠÑ ÑигурноÑÑ‚ ще избÑгат преди наÑ." msgid "I fear these creatures are in the mood for a fight." msgstr "Страхувам Ñе, че тези ÑъщеÑтва Ñа в наÑтроение за битка." #, fuzzy msgid "The hero's attack skill is a bonus added to each unit's attack skill." msgstr "" "Твоето умение за атака е бонуÑ, добавен към умението за атака на вÑÑко " "ÑъщеÑтво." #, fuzzy msgid "The hero's defense skill is a bonus added to each unit's defense skill." msgstr "" "Твоето защитно умение е бонуÑ, добавен към защитното умение на вÑÑко " "ÑъщеÑтво." #, fuzzy msgid "The hero's spell power determines the duration or power of a spell." msgstr "" "ТвоÑта Ñила на заклинание Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»Ñ Ð´ÑŠÐ»Ð¶Ð¸Ð½Ð°Ñ‚Ð° или Ñилата на заклинанието." #, fuzzy msgid "" " The hero's knowledge determines how many spell points the hero may have. " "Under normal circumstances, a hero is limited to 10 spell points per level " "of knowledge." msgstr "" "Твойте Ð·Ð½Ð°Ð½Ð¸Ñ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÑÑ‚ колко точки за заклинание може да има твоÑÑ‚ герой. " "При нормални обÑтоÑтелÑтва един герой е ограничен до 10 точки за заклинание " "за ниво на знание." msgid "Current Modifiers:" msgstr "Текущи модификатори:" msgid "skill|Basic" msgstr "Ðачален" msgid "skill|Advanced" msgstr "Ðапреднал" msgid "skill|Expert" msgstr "ЕкÑперт" msgid "Pathfinding" msgstr "Ориентиране" msgid "Archery" msgstr "Стрелба Ñ Ð»ÑŠÐº" msgid "Logistics" msgstr "ЛогиÑтика" msgid "Scouting" msgstr "Разузнаване" msgid "Diplomacy" msgstr "ДипломациÑ" msgid "Navigation" msgstr "ÐавигациÑ" msgid "Leadership" msgstr "ЛидерÑтво" msgid "Wisdom" msgstr "МъдроÑÑ‚" msgid "Mysticism" msgstr "МиÑтицизъм" msgid "Ballistics" msgstr "БалиÑтика" msgid "Eagle Eye" msgstr "Орлово око" msgid "Necromancy" msgstr "ÐекромантиÑ" msgid "Estates" msgstr "Имоти" msgid "Advanced Archery" msgstr "Стрелба Ñ Ð»ÑŠÐº за напреднали" msgid "Advanced Pathfinding" msgstr "Ðапреднало ориентиране" msgid "Basic Archery" msgstr "Ðачална Ñтрелба Ñ Ð»ÑŠÐº" msgid "Basic Pathfinding" msgstr "Ðачално ориентиране" msgid "Expert Pathfinding" msgstr "ЕкÑперт ориентиране" msgid "Advanced Logistics" msgstr "Ðапреднала логиÑтика" msgid "Basic Logistics" msgstr "Ðачална логиÑтика" msgid "Basic Scouting" msgstr "Ðачално разузнаване" msgid "Expert Archery" msgstr "ЕкÑпертна Ñтрелба Ñ Ð»ÑŠÐº" msgid "Expert Logistics" msgstr "ЕкÑпертна логиÑтика" msgid "Advanced Diplomacy" msgstr "Ðапреднала дипломациÑ" msgid "Advanced Scouting" msgstr "Ðапреднало разузнаване" msgid "Basic Diplomacy" msgstr "Ðачална дипломациÑ" msgid "Expert Diplomacy" msgstr "ЕкÑпертна дипломациÑ" msgid "Expert Scouting" msgstr "ЕкÑпертно разузнаване" msgid "Advanced Leadership" msgstr "Ðапреднало лидерÑтво" msgid "Advanced Navigation" msgstr "Ðапреднала навигациÑ" msgid "Basic Leadership" msgstr "Ðачално лидерÑтво" msgid "Basic Navigation" msgstr "Ðачална навигациÑ" msgid "Expert Navigation" msgstr "ЕкÑпертна навигациÑ" msgid "Advanced Wisdom" msgstr "Ðапреднала мъдроÑÑ‚" msgid "Basic Mysticism" msgstr "Ðачален миÑтицизъм" msgid "Basic Wisdom" msgstr "Ðачална мъдроÑÑ‚" msgid "Expert Leadership" msgstr "ЕкÑпертно лидерÑтво" msgid "Expert Wisdom" msgstr "ЕкÑпертна мъдроÑÑ‚" msgid "Advanced Luck" msgstr "Ðапреднал къÑмет" msgid "Advanced Mysticism" msgstr "Ðапреднал миÑтицизъм" msgid "Basic Luck" msgstr "Ðачален къÑмет" msgid "Expert Luck" msgstr "ЕкÑпертен къÑмет" msgid "Expert Mysticism" msgstr "ЕкÑпертен миÑтицизъм" msgid "Advanced Ballistics" msgstr "Ðапреднала балиÑтика" msgid "Advanced Eagle Eye" msgstr "Ðапреднал орлово око" msgid "Basic Ballistics" msgstr "Ðачална балиÑтика" msgid "Basic Eagle Eye" msgstr "Ðачално орлово око" msgid "Expert Ballistics" msgstr "ЕкÑпертна балиÑтика" msgid "Advanced Necromancy" msgstr "Ðапреднала некромантиÑ" msgid "Basic Estates" msgstr "Ðачални имоти" msgid "Basic Necromancy" msgstr "Ðачална некромантиÑ" msgid "Expert Eagle Eye" msgstr "ЕкÑпертно орлово око" msgid "Expert Necromancy" msgstr "ЕкÑпертна некромантиÑ" msgid "Advanced Estates" msgstr "Ðапреднали имоти" msgid "Expert Estates" msgstr "ЕкÑперт имоти" msgid "" "%{skill} reduces the movement penalty for rough terrain by %{count} percent." msgstr "" "%{skill} намалÑва наказанието за движение по неравен терен Ñ %{count} " "процента." msgid "%{skill} eliminates the movement penalty for rough terrain." msgstr "%{skill} елиминира наказанието за движение при неравен терен." #, fuzzy msgid "" "%{skill} increases the damage done by the hero's range attacking creatures " "by %{count} percent." msgstr "" "%{skill} увеличава щетите, нанеÑени от атакуващи в обхвата ÑъщеÑтва Ñ " "%{count} процента." #, fuzzy msgid "%{skill} increases the hero's movement points by %{count} percent." msgstr "" "%{skill} увеличава точките за движение на Ñ‚Ð²Ð¾Ñ Ð³ÐµÑ€Ð¾Ð¹ Ñ %{count} процента." #, fuzzy msgid "%{skill} increases the hero's viewable area by one square." msgid_plural "%{skill} increases the hero's viewable area by %{count} squares." msgstr[0] "%{skill} увеличава видимата облаÑÑ‚ на Ñ‚Ð²Ð¾Ñ Ð³ÐµÑ€Ð¾Ð¹ Ñ ÐµÐ´Ð½Ð¾ квадратче." msgstr[1] "" "%{skill} увеличава видимата облаÑÑ‚ на Ñ‚ÐµÐ¾Ñ Ð³ÐµÑ€Ð¾Ð¹ Ñ %{count} квадрата." #, fuzzy msgid "" "%{skill} allows the hero to negotiate with monsters who are weaker than " "their army, and reduces the cost of surrender." msgstr "" "%{skill} ти позволÑва да преговарÑш Ñ Ñ‡ÑƒÐ´Ð¾Ð²Ð¸Ñ‰Ð°, които Ñа по-Ñлаби от вашата " "група. " #, fuzzy msgid "" "Approximately %{count} percent of the creatures may offer to join the hero." msgstr "" "Приблизително %{count} процента от ÑъщеÑтвата може да предложат да Ñе " "приÑъединÑÑ‚ към теб." #, fuzzy msgid "All of the creatures may offer to join the hero." msgstr "Ð’Ñички ÑъщеÑтва може да предложат да Ñе приÑъединÑÑ‚ към теб." #, fuzzy msgid "" "The cost of surrender is reduced to %{percent} percent of the total cost of " "troops in the army." msgstr "" "%{name} намалÑва цената на предаването до %{count} процента от общата цена " "на войÑките, които имаш във твоÑта армиÑ." #, fuzzy msgid "" "%{skill} increases the hero's movement points over water by %{count} percent." msgstr "" "%{skill} увеличава точките за движение на Ñ‚Ð²Ð¾Ñ Ð³ÐµÑ€Ð¾Ð¹ над вода Ñ %{count} " "процента." #, fuzzy msgid "%{skill} increases the hero's troops morale by %{count}." msgstr "%{skill} повишава морала на войниците на Ñ‚Ð²Ð¾Ñ Ð³ÐµÑ€Ð¾Ð¹ Ñ %{count}." #, fuzzy msgid "%{skill} allows the hero to learn third level spells." msgstr "%{skill} позволÑва на Ñ‚Ð²Ð¾Ñ Ð³ÐµÑ€Ð¾Ð¹ да научи магии от трето ниво." #, fuzzy msgid "%{skill} allows the hero to learn fourth level spells." msgstr "%{skill} позволÑва на Ñ‚Ð²Ð¾Ñ Ð³ÐµÑ€Ð¾Ð¹ да научи Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ Ð¾Ñ‚ четвърто ниво." #, fuzzy msgid "%{skill} allows the hero to learn fifth level spells." msgstr "%{skill} позволÑва на Ñ‚Ð²Ð¾Ñ Ð³ÐµÑ€Ð¾Ð¹ да научи Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ Ð¾Ñ‚ пето ниво." #, fuzzy msgid "%{skill} regenerates one additional spell point per day to the hero." msgid_plural "" "%{skill} regenerates %{count} additional spell points per day to the hero." msgstr[0] "" "%{skill} регенерира една допълнителна точка заклинание на ден на Ñ‚Ð²Ð¾Ñ Ð³ÐµÑ€Ð¾Ð¹." msgstr[1] "" "%{skill} регенерира %{count} допълнителни заклинателни точки на ден за Ñ‚Ð²Ð¾Ñ " "герой." #, fuzzy msgid "%{skill} increases the hero's luck by %{count}." msgstr "%{skill} увеличава къÑмета на Ñ‚Ð²Ð¾Ñ Ð³ÐµÑ€Ð¾Ð¹ Ñ %{count}." #, fuzzy msgid "" "%{skill} gives the hero's catapult shots a greater chance to hit and do " "damage to castle walls." msgstr "" "%{skill} дава по-голÑм ÑˆÐ°Ð½Ñ Ð½Ð° катапултните изÑтрели на Ñ‚Ð²Ð¾Ñ Ð³ÐµÑ€Ð¾Ð¹ да уцели " "и да нанеÑе щети на Ñтените на замъка." #, fuzzy msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot has a " "greater chance to hit and do damage to castle walls." msgstr "" "%{skill} дава допълнителен изÑтрел на катапулта на Ñ‚Ð²Ð¾Ñ Ð³ÐµÑ€Ð¾Ð¹ и вÑеки " "изÑтрел има по-голÑм ÑˆÐ°Ð½Ñ Ð´Ð° уцели и да нанеÑе щети на Ñтените на замъка." #, fuzzy msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot " "automatically destroys any wall, except a fortified wall in a Knight castle." msgstr "" "%{skill} дава допълнителен изÑтрел на катапулта на Ñ‚Ð²Ð¾Ñ Ð³ÐµÑ€Ð¾Ð¹ и вÑеки " "изÑтрел автоматично разрушава вÑÑка Ñтена, Ñ Ð¸Ð·ÐºÐ»ÑŽÑ‡ÐµÐ½Ð¸Ðµ на укрепена Ñтена в " "рицарÑки замък." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 1st or " "2nd level spell that was cast by an enemy during combat." msgstr "" "%{skill} дава на Ñ‚Ð²Ð¾Ñ Ð³ÐµÑ€Ð¾Ð¹ %{count} процента ÑˆÐ°Ð½Ñ Ð´Ð° научи вражеÑко " "заклинание от 1-во или 2-ро ниво, използвано Ñрещу него в битка." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 3rd " "level spell (or below) that was cast by an enemy during combat." msgstr "" "%{skill} дава на Ñ‚Ð²Ð¾Ñ Ð³ÐµÑ€Ð¾Ð¹ %{count} процента ÑˆÐ°Ð½Ñ Ð´Ð° научи заклинание от 3-" "то ниво (или по-ниÑко), използвано Ñрещу него в битка." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 4th " "level spell (or below) that was cast by an enemy during combat." msgstr "" "%{skill} дава на Ñ‚Ð²Ð¾Ñ Ð³ÐµÑ€Ð¾Ð¹ %{count} процента ÑˆÐ°Ð½Ñ Ð´Ð° научи заклинание от 4-" "то ниво (или по-ниÑко), използвано Ñрещу него в битка." msgid "" "%{skill} allows %{count} percent of the creatures killed in combat to be " "brought back from the dead as Skeletons." msgstr "" "%{skill} позволÑва на %{count} процента от ÑъщеÑтвата, убити в битка, да " "бъдат върнати от мъртвите като Ñкелети." #, fuzzy msgid "" "The hero produces %{count} gold pieces per day as tax revenue from estates." msgstr "" "ТвоÑÑ‚ герой произвежда %{count} златни монети на ден като данъчни приходи от " "имоти." msgid "Blue" msgstr "Син" msgid "Green" msgstr "Зелен" msgid "Red" msgstr "Червен" msgid "Yellow" msgstr "Жълт" msgid "Orange" msgstr "Оранжев" msgid "Purple" msgstr "Лилав" msgid "barrier|Aqua" msgstr "Воден" msgid "barrier|Blue" msgstr "Син" msgid "barrier|Brown" msgstr "КафÑв" msgid "barrier|Gold" msgstr "Златен" msgid "barrier|Green" msgstr "Зелен" msgid "barrier|Orange" msgstr "Оранжев" msgid "barrier|Purple" msgstr "Лилав" msgid "barrier|Red" msgstr "Червен" msgid "tent|Aqua" msgstr "Воден" msgid "tent|Blue" msgstr "СинÑ" msgid "tent|Brown" msgstr "КафÑва" msgid "tent|Gold" msgstr "Златна" msgid "tent|Green" msgstr "Зелена" msgid "tent|Orange" msgstr "Оранжева" msgid "tent|Purple" msgstr "Лилава" msgid "tent|Red" msgstr "Червена" msgid "Experience" msgstr "Опит" msgid "" "Experience allows your heroes to go up levels, increasing their primary and " "secondary skills." msgstr "" "Опитът позволÑва на твойте герои да Ñе изкачват нагоре по нивата, " "увеличавайки Ñвоите първични и вторични умениÑ." msgid "Hero/Stats" msgstr "Герой/СтатиÑтика" msgid "Skills" msgstr "УмениÑ" msgid "Town/Castle" msgstr "Град/Замък" msgid "Garrison" msgstr "Гарнизон" msgid "Gold Per Day:" msgstr "Злато на ден:" msgid "View Heroes." msgstr "Виж геройте" msgid "Towns/Castles" msgstr "Градове/Замъци" msgid "View Towns and Castles." msgstr "Виж градовете и замъците." msgid "luck|Cursed" msgstr "Прокълнат" msgid "luck|Awful" msgstr "УжаÑен" msgid "luck|Bad" msgstr "Лош" msgid "luck|Normal" msgstr "Ðормален" msgid "luck|Good" msgstr "Добър" msgid "luck|Great" msgstr "ЧудеÑен" msgid "luck|Irish" msgstr "ИрландÑки" #, fuzzy msgid "" "Bad luck sometimes falls on the hero's units in combat, causing their " "attacks to only do half damage." msgstr "" "Лош къÑмет понÑкога ÑполетÑва твойте армии в битка, което кара техните атаки " "да нанаÑÑÑ‚ Ñамо половината щети." #, fuzzy msgid "" "Neutral luck means the hero's units will never get lucky or unlucky attacks " "on the enemy." msgstr "" "Ðеутрален къÑмет означава, че армиите ти никога нÑма да получат къÑмет или " "злощаÑтни атаки Ñрещу врага." #, fuzzy msgid "" "Good luck sometimes lets the hero's units get lucky attacks (double " "strength) in combat." msgstr "" "ДобриÑÑ‚ къÑмет понÑкога позволÑва на твойте армии да получават къÑметлийÑки " "атаки (двойна Ñила) в битка." msgid "morale|Treason" msgstr "ИзмÑна" msgid "morale|Awful" msgstr "УжаÑен" msgid "morale|Poor" msgstr "Слаб" msgid "morale|Normal" msgstr "Ðормален" msgid "morale|Good" msgstr "Добър" msgid "morale|Great" msgstr "ЧудеÑен" msgid "morale|Blood!" msgstr "Кървав!" #, fuzzy msgid "Bad morale may cause the hero's units to freeze in combat." msgstr "ЛошиÑÑ‚ морал може да накара армиите ти да замръзнат в битка." #, fuzzy msgid "" "Neutral morale means the hero's units will never be blessed with extra " "attacks or freeze in combat." msgstr "" "Ðеутрален морал означава, че твойте армии никога нÑма да бъдат благоÑловени " "Ñ Ð´Ð¾Ð¿ÑŠÐ»Ð½Ð¸Ñ‚ÐµÐ»Ð½Ð¸ атаки или да замръзнат в битка." #, fuzzy msgid "Good morale may give the hero's units extra attacks in combat." msgstr "ДобриÑÑ‚ морал може да даде на твойте армии допълнителни атаки в битка." msgid "Knight" msgstr "Рицар" msgid "Barbarian" msgstr "Варварин" msgid "Sorceress" msgstr "МагьоÑница" msgid "Warlock" msgstr "МагьоÑник" msgid "Wizard" msgstr "Вълшебник" msgid "Necromancer" msgstr "Ðекромант" msgid "Multi" msgstr "СмеÑен" msgid "doubleLined|Knight" msgstr "Рицар" msgid "doubleLined|Barbarian" msgstr "Варварин" msgid "doubleLined|Sorceress" msgstr "" "МагьоÑ-\n" "ница" msgid "doubleLined|Warlock" msgstr "" "МагьоÑ-\n" "ник" msgid "doubleLined|Wizard" msgstr "" "Вълшеб-\n" "ник" msgid "" "doubleLined|Necro-\n" "mancer" msgstr "" "Ðекро-\n" "мант" msgid "doubleLinedRace|Multi" msgstr "" "Комбина-\n" "циÑ" msgid "doubleLinedRace|Random" msgstr "Случаен" msgid "speed|Standing" msgstr "СтоÑща" msgid "speed|Crawling" msgstr "ПълзÑща" msgid "speed|Very Slow" msgstr "Мн. бавна" msgid "speed|Slow" msgstr "Бавна" msgid "speed|Average" msgstr "Средна" msgid "speed|Fast" msgstr "Бърза" msgid "speed|Very Fast" msgstr "Мн. бърза" msgid "speed|Ultra Fast" msgstr "Суп. бърза" msgid "speed|Blazing" msgstr "ПламтÑща" msgid "speed|Instant" msgstr "Мигновена" msgid "week|Squirrel" msgstr "Катерицата" msgid "week|Rabbit" msgstr "Заека" msgid "week|Gopher" msgstr "Гризача" msgid "week|Badger" msgstr "Язовеца" msgid "week|Rat" msgstr "Плъха" msgid "week|Eagle" msgstr "Орела" msgid "week|Weasel" msgstr "ÐевеÑтулката" msgid "week|Raven" msgstr "Гарвана" msgid "week|Mongoose" msgstr "МангуÑтата" msgid "week|Dog" msgstr "Кучето" msgid "week|Aardvark" msgstr "МравоÑда" msgid "week|Lizard" msgstr "Гущера" msgid "week|Tortoise" msgstr "КоÑтенурката" msgid "week|Hedgehog" msgstr "Таралежа" msgid "week|Condor" msgstr "Кондора" msgid "week|Ant" msgstr "Мравката" msgid "week|Grasshopper" msgstr "Скакалеца" msgid "week|Dragonfly" msgstr "Водното конче" msgid "week|Spider" msgstr "ПаÑка" msgid "week|Butterfly" msgstr "Пеперудата" msgid "week|Bumblebee" msgstr "Земната пчела" msgid "week|Locust" msgstr "Скакалеца" msgid "week|Earthworm" msgstr "Ð—ÐµÐ¼ÐµÐ½Ð¸Ñ Ñ‡ÐµÑ€Ð²ÐµÐ¹" msgid "week|Hornet" msgstr "Стършела" msgid "week|Beetle" msgstr "Бръмбара" msgid "week|PLAGUE" msgstr "ЧУМÐТÐ" msgid "week|Unnamed" msgstr "БезимениÑ" msgid "Desert" msgstr "ПуÑтинÑ" msgid "Snow" msgstr "СнÑг" msgid "Wasteland" msgstr "ПуÑтош" msgid "Beach" msgstr "Плаж" msgid "Lava" msgstr "Лава" msgid "Dirt" msgstr "ПръÑÑ‚" msgid "Grass" msgstr "Трева" msgid "Ocean" msgstr "Океан" msgid "maps|Small" msgstr "Малка" msgid "maps|Medium" msgstr "Средна" msgid "maps|Large" msgstr "ГолÑма" msgid "maps|Extra Large" msgstr "Много голÑма" msgid "maps|Custom Size" msgstr "големина по избор" msgid "Ore Mine" msgstr "Мина за руда" msgid "Sulfur Mine" msgstr "Мина за ÑÑра" msgid "Crystal Mine" msgstr "Мина за криÑтал" msgid "Gems Mine" msgstr "Мина за Ñкъпоценни камъни" msgid "Gold Mine" msgstr "Мина за злато" msgid "Mine" msgstr "Мина" msgid "Burma shave." msgstr "Като не щеш мира на ти Ñекира." msgid "Next sign 50 miles." msgstr "Следваща табела 50 мили." msgid "See Rock City." msgstr "Виж Ñкален град." msgid "This space for rent." msgstr "Това проÑтранÑтво е под наем." msgid "No object" msgstr "ÐÑма обект" msgid "Alchemist Lab" msgstr "Ðлхимична лабораториÑ" msgid "Sign" msgstr "Знак" msgid "Buoy" msgstr "Шамандура" msgid "Skeleton" msgstr "Скелет" msgid "Daemon Cave" msgstr "ДемонÑка пещера" msgid "Treasure Chest" msgstr "Сандък ÑÑŠÑ Ñъкровище" msgid "Faerie Ring" msgstr "Приказен пръÑтен" msgid "Campfire" msgstr "Лагерен огън" msgid "Fountain" msgstr "Фонтан" msgid "Gazebo" msgstr "БеÑедка" msgid "Genie Lamp" msgstr "Джин от лампа" msgid "Archer's House" msgstr "Къщата на Ñтрелеца" msgid "Goblin Hut" msgstr "Хижата на гоблините" msgid "Dwarf Cottage" msgstr "Вила на джуджета" msgid "Peasant Hut" msgstr "СелÑка хижа" msgid "Stables" msgstr "Конюшни" msgid "Alchemist's Tower" msgstr "Кулата на алхимика" msgid "Event" msgstr "Събитие" msgid "Dragon City" msgstr "Драконов град" msgid "Lighthouse" msgid_plural "Lighthouses" msgstr[0] "Фар" msgstr[1] "Фарове" msgid "Water Wheel" msgid_plural "Water Wheels" msgstr[0] "Водно колело" msgstr[1] "Водни колела" msgid "Monster" msgstr "Чудовище" msgid "Obelisk" msgstr "ОбелиÑк" msgid "Oasis" msgstr "ОазиÑ" msgid "Resource" msgstr "РеÑурÑ" msgid "Sawmill" msgstr "ДъÑкорезница" msgid "Oracle" msgstr "Оракул" msgid "Shrine of the First Circle" msgstr "Светилището на Ð¿ÑŠÑ€Ð²Ð¸Ñ ÐºÑ€ÑŠÐ³" msgid "Shipwreck" msgstr "Корабокрушение" msgid "Sea Chest" msgstr "МорÑки Ñандък" msgid "Desert Tent" msgstr "ПуÑтинна палатка" msgid "Stone Liths" msgstr "Телепорт" msgid "Wagon Camp" msgstr "Вагонен лагер" msgid "Hut of the Magi" msgstr "Хижата на влъхвите" msgid "Whirlpool" msgstr "Водовъртеж" msgid "Windmill" msgid_plural "Windmills" msgstr[0] "Ð’Ñтърна мелница" msgstr[1] "Ð’Ñтърни мелници" msgid "Artifact" msgstr "Ðртефакт" msgid "Mermaid" msgstr "РуÑалка" msgid "Boat" msgstr "Кораб" msgid "Random Ultimate Artifact" msgstr "Случаен върховен артефакт" msgid "Random Artifact" msgstr "Случаен артефакт" msgid "Random Resource" msgstr "Случаен реÑурÑ" msgid "Random Monster" msgstr "Случайно чудовище" msgid "Random Town" msgstr "Случаен град" msgid "Random Castle" msgstr "Случаен замък" msgid "Eye of the Magi" msgstr "Окото на влъхвите" msgid "Random Monster - weak" msgstr "Случайно чудовище - Ñлабо" msgid "Random Monster - medium" msgstr "Случайно чудовище - Ñредно" msgid "Random Monster - strong" msgstr "Случайно чудовище - Ñилно" msgid "Random Monster - very strong" msgstr "Случайно чудовище - мн. Ñилно" #, fuzzy msgid "Hero" msgstr "Герой" msgid "Nothing Special" msgstr "Ðищо Ñпециално" msgid "Mossy Rock" msgstr "МъхеÑта Ñкала" msgid "Watch Tower" msgstr "Стражева кула" msgid "Tree House" msgstr "Къща на дърво" msgid "Tree City" msgstr "Град на дърво" msgid "Ruins" msgstr "Руини" msgid "Fort" msgstr "Укрепление" msgid "Abandoned Mine" msgstr "ИзоÑтавена мина" msgid "Sirens" msgstr "Сирени" msgid "Standing Stones" msgstr "Кромлех" msgid "Idol" msgstr "Идол" msgid "Tree of Knowledge" msgstr "Дърво на познанието" msgid "Witch Doctor's Hut" msgstr "Хижата на врачката" msgid "Temple" msgstr "Храм" msgid "Hill Fort" msgstr "Укрепление в хълмовете" msgid "Halfling Hole" msgstr "Дупка на Хобити" msgid "Mercenary Camp" msgstr "Лагер на наемниците" msgid "Shrine of the Second Circle" msgstr "Светилището на Ð²Ñ‚Ð¾Ñ€Ð¸Ñ ÐºÑ€ÑŠÐ³" msgid "Shrine of the Third Circle" msgstr "Светилището на Ñ‚Ñ€ÐµÑ‚Ð¸Ñ ÐºÑ€ÑŠÐ³" msgid "City of the Dead" msgstr "Градът на мъртвите" msgid "Sphinx" msgstr "СфинкÑ" msgid "Wagon" msgstr "Вагон" msgid "Tar Pit" msgstr "Катранена Ñма" msgid "Artesian Spring" msgstr "ÐртезианÑки извор" msgid "Troll Bridge" msgstr "МоÑтът на троловете" msgid "Watering Hole" msgstr "Дупка за поливане" msgid "Witch's Hut" msgstr "Хижата на вещиците" msgid "Xanadu" msgstr "КÑанаду" msgid "Lean-To" msgstr "Ðаклонена" msgid "Magellan's Maps" msgstr "Картите на Магелан" msgid "Flotsam" msgstr "Сал" msgid "Derelict Ship" msgstr "ИзоÑтавен кораб" msgid "Shipwreck Survivor" msgstr "Корабокрушенец" msgid "Bottle" msgstr "Бутилка" msgid "Magic Well" msgstr "МагичеÑки кладенец" msgid "Magic Garden" msgid_plural "Magic Gardens" msgstr[0] "МагичеÑка градина" msgstr[1] "МагичеÑки градини" msgid "Observation Tower" msgstr "Ðаблюдателна кула" msgid "Freeman's Foundry" msgstr "ЛеÑрна на Фрийман" msgid "Reefs" msgstr "Рифове" msgid "Volcano" msgstr "Вулкан" msgid "Flowers" msgstr "ЦветÑ" msgid "Rock" msgstr "Скала" msgid "Water Lake" msgstr "Водно езеро" msgid "Mandrake" msgstr "Мандрагора" msgid "Dead Tree" msgstr "Мъртво дърво" msgid "Stump" msgstr "Пън" msgid "Crater" msgstr "Кратер" msgid "Cactus" msgstr "КактуÑ" msgid "Mound" msgstr "Могила" msgid "Dune" msgstr "Дюна" msgid "Lava Pool" msgstr "БаÑейн от лава" msgid "Shrub" msgstr "ХраÑÑ‚" msgid "Barrow Mounds" msgstr "Гробни могили" msgid "Random Artifact - Treasure" msgstr "Случаен артефакт - Съкровище" msgid "Random Artifact - Minor" msgstr "Случаен артефакт - незначителен" msgid "Random Artifact - Major" msgstr "Случаен артефакт - оÑновен" msgid "Barrier" msgstr "Бариера" msgid "Traveller's Tent" msgstr "Палатка на пътешеÑтвеник" msgid "Jail" msgstr "Затвор" msgid "Fire Summoning Altar" msgstr "Олтар за призоваване на огън" msgid "Air Summoning Altar" msgstr "Олтар за призоваване на въздух" msgid "Earth Summoning Altar" msgstr "Олтар за призоваване на земÑ" msgid "Water Summoning Altar" msgstr "Олтар за призоваване на вода" msgid "Unknown Monster" msgstr "ÐеизвеÑтно чудовище" msgid "Unknown Monsters" msgstr "ÐеизвеÑтни чудовища" msgid "Peasant" msgstr "СелÑнин" msgid "Peasants" msgstr "СелÑни" msgid "Archer" msgstr "Стрелец" msgid "Archers" msgstr "Стрелци" msgid "Ranger" msgstr "ЛеÑничей" msgid "Rangers" msgstr "леÑничеи" msgid "Pikeman" msgstr "ПиконоцеÑ" msgid "Pikemen" msgstr "ПиконоÑци" msgid "Veteran Pikeman" msgstr "ÐŸÐ¸ÐºÐ¾Ð½Ð¾Ñ†ÐµÑ Ð²ÐµÑ‚ÐµÑ€Ð°Ð½" msgid "Veteran Pikemen" msgstr "ПиконоÑци ветерани" msgid "Swordsman" msgstr "Фехтовач" msgid "Swordsmen" msgstr "Фехтовачи" msgid "Master Swordsman" msgstr "МайÑтор фехтовач" msgid "Master Swordsmen" msgstr "МайÑтори фехтовачи" msgid "Cavalries" msgstr "КавалериÑти" msgid "Cavalry" msgstr "КавалериÑ" msgid "Champion" msgstr "Шампион" msgid "Champions" msgstr "Шампиони" msgid "Paladin" msgstr "Паладин" msgid "Paladins" msgstr "Паладини" msgid "Crusader" msgstr "КръÑтоноÑец" msgid "Crusaders" msgstr "КръÑтоноÑци" msgid "Goblin" msgstr "Гоблин" msgid "Goblins" msgstr "Гоблини" msgid "Orc" msgstr "Орк" msgid "Orcs" msgstr "Орки" msgid "Orc Chief" msgstr "Орк вожд" msgid "Orc Chiefs" msgstr "Орки вождове" msgid "Wolf" msgstr "Вълк" msgid "Wolves" msgstr "Вълци" msgid "Ogre" msgstr "ЧовекоÑдец" msgid "Ogres" msgstr "ЧовекоÑдци" msgid "Ogre Lord" msgstr "ЧовекоÑдец лорд" msgid "Ogre Lords" msgstr "ЧовекоÑдци лордове" msgid "Troll" msgstr "Трол" msgid "Trolls" msgstr "Тролове" msgid "War Troll" msgstr "Военен трол" msgid "War Trolls" msgstr "Военни тролове" msgid "Cyclopes" msgstr "Циклопи" msgid "Cyclops" msgstr "Циклоп" msgid "Sprite" msgstr "ФеÑ" msgid "Sprites" msgstr "Феи" msgid "Dwarf" msgstr "Джудже" msgid "Dwarves" msgstr "Джуджета" msgid "Battle Dwarf" msgstr "Бойно джудже" msgid "Battle Dwarves" msgstr "Бойни джуджета" msgid "Elf" msgstr "Елф" msgid "Elves" msgstr "Елфи" msgid "Grand Elf" msgstr "Велик елф" msgid "Grand Elves" msgstr "Велики елфове" msgid "Druid" msgstr "Друид" msgid "Druids" msgstr "Друиди" msgid "Greater Druid" msgstr "Велик друид" msgid "Greater Druids" msgstr "Велики друиди" msgid "Unicorn" msgstr "Еднорог" msgid "Unicorns" msgstr "Еднорози" msgid "Phoenix" msgstr "ФеникÑ" msgid "Phoenixes" msgstr "ФеникÑи" msgid "Centaur" msgstr "Кентавър" msgid "Centaurs" msgstr "Кентаври" msgid "Gargoyle" msgstr "Гаргойл" msgid "Gargoyles" msgstr "Гаргойли" msgid "Griffin" msgstr "Грифон" msgid "Griffins" msgstr "Грифони" msgid "Minotaur" msgstr "Минотавър" msgid "Minotaurs" msgstr "Минотаври" msgid "Minotaur King" msgstr "Крал Минотавър" msgid "Minotaur Kings" msgstr "Крале Минотавъри" msgid "Hydra" msgstr "Хидра" msgid "Hydras" msgstr "Хидри" msgid "Green Dragon" msgstr "Зелен дракон" msgid "Green Dragons" msgstr "Зелени дракони" msgid "Red Dragon" msgstr "Червен дракон" msgid "Red Dragons" msgstr "Червени дракони" msgid "Black Dragon" msgstr "Черен дракон" msgid "Black Dragons" msgstr "Черени дракони" msgid "Halfling" msgstr "Хобит" msgid "Halflings" msgstr "Хобити" msgid "Boar" msgstr "Глиган" msgid "Boars" msgstr "Глигани" msgid "Iron Golem" msgstr "Железен голем" msgid "Iron Golems" msgstr "Железени големи" msgid "Steel Golem" msgstr "Стоманен голем" msgid "Steel Golems" msgstr "Стоманени големи" msgid "Roc" msgstr "Рух" msgid "Rocs" msgstr "Рух" msgid "Mage" msgstr "МагьоÑник" msgid "Magi" msgstr "Влъхва" msgid "Archmage" msgstr "Ðрхимаг" msgid "Archmagi" msgstr "Ðрхимагове" msgid "Giant" msgstr "Гигант" msgid "Giants" msgstr "Гиганти" msgid "Titan" msgstr "Титан" msgid "Titans" msgstr "Титани" msgid "Skeletons" msgstr "Скелети" msgid "Zombie" msgstr "Зомби" msgid "Zombies" msgstr "Зомбита" msgid "Mutant Zombie" msgstr "Зомби мутант" msgid "Mutant Zombies" msgstr "Зомбита мутанти" msgid "Mummies" msgstr "Мумии" msgid "Mummy" msgstr "МумиÑ" msgid "Royal Mummies" msgstr "КралÑки мумии" msgid "Royal Mummy" msgstr "КралÑка мумиÑ" msgid "Vampire" msgstr "Вампир" msgid "Vampires" msgstr "Вампири" msgid "Vampire Lord" msgstr "ГоÑподарÑÑ‚ на вампирите" msgid "Vampire Lords" msgstr "ГоÑподари на вампирите" msgid "Lich" msgstr "Лич" msgid "Liches" msgstr "Личове" msgid "Power Lich" msgstr "Мощен Лич" msgid "Power Liches" msgstr "Мощни Личове" msgid "Bone Dragon" msgstr "КоÑтен дракон" msgid "Bone Dragons" msgstr "КоÑтни дракони" msgid "Rogue" msgstr "Мошеник" msgid "Rogues" msgstr "Мошеници" msgid "Nomad" msgstr "Ðомад" msgid "Nomads" msgstr "Ðомади" msgid "Ghost" msgstr "Дух" msgid "Ghosts" msgstr "Духове" msgid "Genie" msgstr "Джин" msgid "Genies" msgstr "Джинове" msgid "Medusa" msgstr "Медуза" msgid "Medusas" msgstr "Медузи" msgid "Earth Elemental" msgstr "Земен елементал" msgid "Earth Elementals" msgstr "Земни елементали" msgid "Air Elemental" msgstr "Въздушен елементал" msgid "Air Elementals" msgstr "Въздушни елементали" msgid "Fire Elemental" msgstr "Огнен елементал" msgid "Fire Elementals" msgstr "Огнени елементали" msgid "Water Elemental" msgstr "Воден елементал" msgid "Water Elementals" msgstr "Водни елементали" msgid "Random Monsters" msgstr "Случайни чудовища" msgid "Random Monster 1" msgstr "Случайно чудовище 1" msgid "Random Monsters 1" msgstr "Случайни чудовища 1" msgid "Random Monster 2" msgstr "Случайно чудовище 2" msgid "Random Monsters 2" msgstr "Случайни чудовища 2" msgid "Random Monster 3" msgstr "Случайно чудовище 3" msgid "Random Monsters 3" msgstr "Случайни чудовища 3" msgid "Random Monster 4" msgstr "Случайно чудовище 4" msgid "Random Monsters 4" msgstr "Случайни чудовища 4" msgid "Double shot" msgstr "Двоен изÑтрел" msgid "2-hex monster" msgstr "2-шеÑтоъгълника чудовище" msgid "Double strike" msgstr "Двоен удар" msgid "Double damage to Undead" msgstr "Двойни щети на Ðемъртвеца" msgid "% magic resistance" msgstr "% магичеÑка уÑтойчивоÑÑ‚" msgid "Immune to Mind spells" msgstr "Имунитет към умÑтвени магии" msgid "Immune to Elemental spells" msgstr "Имунитет към магии на елементал" msgid "Immune to Fire spells" msgstr "Имунитет към огнени магии" msgid "Immune to Cold spells" msgstr "Имунитет към ледени магии" msgid "Immune to " msgstr "Имунитет към " msgid "% immunity to %{spell} spell" msgstr "% имунитет към %{spell} магиÑ" msgid "% damage from Elemental spells" msgstr "% щети от магии на елементал" msgid "% chance to Dispel beneficial spells" msgstr "% ÑˆÐ°Ð½Ñ Ð·Ð° отблъÑкване на полезни заклинаниÑ" msgid "% chance to Paralyze" msgstr "% ÑˆÐ°Ð½Ñ Ð·Ð° парализиране" msgid "% chance to Petrify" msgstr "% ÑˆÐ°Ð½Ñ Ð·Ð° вкаменÑване" msgid "% chance to Blind" msgstr "% ÑˆÐ°Ð½Ñ Ð·Ð° заÑлепÑване" msgid "% chance to Curse" msgstr "% ÑˆÐ°Ð½Ñ Ð·Ð° проклÑтие" msgid "% chance to cast %{spell} spell" msgstr "% ÑˆÐ°Ð½Ñ Ð´Ð° направиш %{spell} заклинание" msgid "HP regeneration" msgstr "ВъзÑтановÑване на живот" msgid "Two hexes attack" msgstr "Ðтака в два шеÑтоъгълника" msgid "Flyer" msgstr "Летец" msgid "Always retaliates" msgstr "Винаги отмъщава" msgid "Attacks all adjacent enemies" msgstr "Ðтакува вÑички ÑÑŠÑедни врагове" msgid "No melee penalty" msgstr "Без наказание за ръкопашен бой" msgid "Dragon" msgstr "Дракон" msgid "Undead" msgstr "Ðемъртъв" msgid "No enemy retaliation" msgstr "Без вражеÑко отмъщение" msgid "HP drain" msgstr "ИзÑмукване на живот" msgid "Cloud attack" msgstr "Облачна атака" msgid "Decreases enemy's morale by " msgstr "ÐамалÑва морала на врага Ñ" msgid "% chance to halve enemy" msgstr "% ÑˆÐ°Ð½Ñ Ð´Ð° намалиш враговете наполовина" msgid "Soul Eater" msgstr "ДушеÑд" msgid "Elemental" msgstr "Елементал" msgid "No Morale" msgstr "Без морал" msgid "200% damage from Fire spells" msgstr "200% щети от огнени магии" msgid "200% damage from Cold spells" msgstr "200% щети от Ñтудени магии" msgid "% damage from %{spell} spell" msgstr "% щети от %{spell} заклинание" msgid "% immunity to " msgstr "% имунитет към " msgid "Lightning" msgstr "Светкавица" msgid "% damage from " msgstr "% щети от " msgid "The three Anduran artifacts magically combine into one." msgstr "Трите артефакта от Ðндуран магичеÑки Ñе комбинират в едно." msgid "View Spells" msgstr "Преглед на магиите" msgid "View %{name} Info" msgstr "Виж Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° %{name}" msgid "Move %{name}" msgstr "ПремеÑти %{name}" msgid "Cannot move the Spellbook" msgstr "Ðе може да Ñе премеÑти книгата Ñ Ð¼Ð°Ð³Ð¸Ð¹" msgid "This item can't be traded." msgstr "Този артикул не може да Ñе търгува." msgid "Invalid Artifact" msgstr "Ðеработещ артефакт" #, fuzzy msgid "The %{name} increases the hero's knowledge by %{count}." msgstr "%{name} увеличава знаниÑта ти Ñ %{count}." msgid "Ultimate Book of Knowledge" msgstr "Върховна книга на знанието" #, fuzzy msgid "The %{name} increases the hero's attack skill by %{count}." msgstr "%{name} увеличава умениÑта ти за атака Ñ %{count}." msgid "Ultimate Sword of Dominion" msgstr "Върховен меч на гоÑподÑтвото" #, fuzzy msgid "The %{name} increases the hero's defense skill by %{count}." msgstr "%{name} увеличава твойте ÑƒÐ¼ÐµÐ½Ð¸Ñ Ð·Ð° защита Ñ %{count}." msgid "Ultimate Cloak of Protection" msgstr "Върховно наметало за защита" #, fuzzy msgid "The %{name} increases the hero's spell power by %{count}." msgstr "%{name} увеличава Ñилата на заклинанието ти Ñ %{count}." msgid "Ultimate Wand of Magic" msgstr "Върховна магичеÑка пръчка" #, fuzzy msgid "" "The %{name} increases the hero's attack and defense skills by %{count} each." msgstr "%{name} увеличава твойте ÑƒÐ¼ÐµÐ½Ð¸Ñ Ð·Ð° атака и защита Ñ Ð¿Ð¾ %{count} вÑÑко." msgid "Ultimate Shield" msgstr "Върховен щит" #, fuzzy msgid "" "The %{name} increases the hero's spell power and knowledge by %{count} each." msgstr "%{name} увеличава твоÑта магичеÑка Ñила и Ð·Ð½Ð°Ð½Ð¸Ñ Ñ %{count} вÑеки." msgid "Ultimate Staff" msgstr "Върховен жезъл" #, fuzzy msgid "" "The %{name} increases each of the hero's basic skills by %{count} points." msgstr "%{name} увеличава вÑÑко от вашите оÑновни ÑƒÐ¼ÐµÐ½Ð¸Ñ Ñ %{count} точки." msgid "Ultimate Crown" msgstr "Върховна корона" msgid "Golden Goose" msgstr "Златна гъÑка" msgid "The %{name} brings in an income of %{count} gold per day." msgstr "%{name} ноÑи доход от %{count} злато на ден." msgid "Arcane Necklace of Magic" msgstr "ТайнÑтвена огърлица на магиÑта" msgid "" "After rescuing a Sorceress from a cursed tomb, she rewards your heroism with " "an exquisite jeweled necklace." msgstr "" "След като ÑпаÑи магьоÑница от прокълната гробница, Ñ‚Ñ Ð²ÑŠÐ·Ð½Ð°Ð³Ñ€Ð°Ð¶Ð´Ð°Ð²Ð° Ñ‚Ð²Ð¾Ñ " "героизъм Ñ Ð¸Ð·Ñщна огърлица ÑÑŠÑ Ñкъпоценни камъни." msgid "Caster's Bracelet of Magic" msgstr "МагичеÑка гривна на ЗаклинателÑ" msgid "" "While searching through the rubble of a caved-in mine, you free a group of " "trapped Dwarves. Grateful, the leader gives you a golden bracelet." msgstr "" "Докато търÑиш Ñред отломките на пропаднала мина, оÑвобождаваш група хванати " "в капан джуджета. Благодарен, лидерът им ти дава златна гривна." msgid "Mage's Ring of Power" msgstr "МагьоÑничеÑки пръÑтенът на Ñилата" msgid "" "A cry of pain leads you to a Centaur, caught in a trap. Upon setting the " "creature free, he hands you a small pouch. Emptying the contents, you find a " "dazzling jeweled ring." msgstr "" "Вик на болка те отвежда до кентавър, хванат в капан. След като оÑвобождаваш " "Ñъзданието, то ти подава малка торбичка. Изпразвайки Ñъдържанието, намираш " "оÑлепителен пръÑтен ÑÑŠÑ Ñкъпоценни камъни." msgid "Witch's Broach of Magic" msgstr "МагичеÑка брошка на вещица" msgid "" "Alongside the remains of a burnt witch lies a beautiful broach, intricately " "designed. Approaching the corpse with caution, you add the broach to your " "inventory." msgstr "" "До оÑтанките на изгорена вещица лежи краÑива брошка ÑÑŠÑ Ñложен дизайн. " "Приближаваш предпазливо Ñ‚Ñлото, ти добавÑш брошката към инвентара Ñи." msgid "Medal of Valor" msgstr "Медал за доблеÑÑ‚" #, fuzzy msgid "The %{name} increases the morale of the hero's army by %{count}." msgstr "%{name} увеличава знаниÑта ти Ñ %{count}." msgid "" "Freeing a virtuous maiden from the clutches of an evil overlord, you are " "granted a Medal of Valor by the King's herald." msgstr "" "ОÑвобождавайки добродетелна девойка от лапите на зъл владетел, ти получаваш " "Медал за доблеÑÑ‚ от кралÑÐºÐ¸Ñ Ð³Ð»Ð°ÑˆÐ°Ñ‚Ð°Ð¹." msgid "Medal of Courage" msgstr "Медал за храброÑÑ‚" msgid "" "After saving a young boy from a vicious pack of Wolves, you return him to " "his father's manor. The grateful nobleman awards you with a Medal of Courage." msgstr "" "След като ÑпаÑÑваш младо момче от жеÑтока глутница вълци, ти го връщаш в " "имението на баща му. БлагодарниÑÑ‚ благородник те награждава Ñ Ð¼ÐµÐ´Ð°Ð» за " "храброÑÑ‚." msgid "Medal of Honor" msgstr "Медал на чеÑтта" msgid "" "After freeing a princess of a neighboring kingdom from the evil clutches of " "despicable slavers, she awards you with a Medal of Honor." msgstr "" "След като оÑвободи принцеÑа от ÑÑŠÑедно кралÑтво от злите лапи на презрени " "роби, Ñ‚Ñ Ñ‚Ðµ награждава Ñ ÐœÐµÐ´Ð°Ð» на чеÑтта." msgid "Medal of Distinction" msgstr "Медал на унищожението" msgid "" "Ridding the countryside of the hideous Minotaur who made a sport of eating " "noblemen's Knights, you are honored with the Medal of Distinction." msgstr "" "ОÑвобождавайки провинциÑта от Ð¾Ñ‚Ð²Ñ€Ð°Ñ‚Ð¸Ñ‚ÐµÐ»Ð½Ð¸Ñ ÐœÐ¸Ð½Ð¾Ñ‚Ð°Ð²ÑŠÑ€, който Ñе забавлÑваше " "Ñ Ð¸Ð·Ñждането на рицари на благородници, ти Ñи удоÑтоен Ñ ÐœÐµÐ´Ð°Ð»." msgid "Fizbin of Misfortune" msgstr "Медал на нещаÑтието" #, fuzzy msgid "" "The %{name} greatly decreases the morale of the hero's army by %{count}." msgstr "%{name} значително намалÑва морала ти Ñ %{count}." msgid "" "You stumble upon a medal lying alongside the empty road. Adding the medal to " "your inventory, you become aware that you have acquired the undesirable " "Fizbin of Misfortune, greatly decreasing your army's morale." msgstr "" "Ðатъкваш Ñе на медал край Ð¿Ñ€Ð°Ð·Ð½Ð¸Ñ Ð¿ÑŠÑ‚. ДобавÑйки медала към инвентара Ñи, " "разбираш, че Ñи Ñе Ñдобили Ñ Ð½ÐµÐ¶ÐµÐ»Ð°Ð½Ð¸Ñ ÐœÐµÐ´Ð°Ð» на нещаÑтието, което значително " "намалÑва морала на твоÑта армиÑ." msgid "Thunder Mace of Dominion" msgstr "Гръмотевичен боздуган на гоÑподÑтвото" msgid "" "During a sudden storm, a bolt of lightning strikes a tree, splitting it. " "Inside the tree you find a mysterious mace." msgstr "" "По време на внезапна Ð±ÑƒÑ€Ñ Ð¼ÑŠÐ»Ð½Ð¸Ñ ÑƒÐ´Ñ€Ñ ÐµÐ´Ð½Ð¾ дърво и го разцепва. Вътре в " "дървото намираш миÑтериозен боздуган." msgid "Armored Gauntlets of Protection" msgstr "Бронирани ръкавици за защита" #, fuzzy msgid "The %{name} increase the hero's defense skill by %{count}." msgstr "%{name} повишава умениÑта ти за защита Ñ %{count}." msgid "" "You encounter the infamous Black Knight! After a grueling duel ending in a " "draw, the Knight, out of respect, offers you a pair of armored gauntlets." msgstr "" "Срещаш проÑÐ»Ð¾Ð²ÑƒÑ‚Ð¸Ñ Ð§ÐµÑ€ÐµÐ½ рицар! След изтощителен дуел, завършил наравно, " "РицарÑÑ‚, от уважение, ти предлага чифт бронирани ръкавици." msgid "Defender Helm of Protection" msgstr "Защитен шлем на защитата" msgid "" "A glint of golden light catches your eye. Upon further investigation, you " "find a golden helm hidden under a bush." msgstr "" "ОтблÑÑък на златна Ñветлина привлича окото ти. При по-нататъшно проучване " "намираш златен шлем, Ñкрит под един храÑÑ‚." msgid "Giant Flail of Dominion" msgstr "ГигантÑка бухалка на гоÑподÑтвото" msgid "" "A clumsy Giant has killed himself with his own flail. Knowing your superior " "skill with this weapon, you confidently remove the spectacular flail from " "the fallen Giant." msgstr "" "Ðепохватен великан Ñе е Ñамоубил ÑÑŠÑ ÑобÑтвената Ñи бухалка. Знаейки " "превъзходните Ñи ÑƒÐ¼ÐµÐ½Ð¸Ñ Ñ Ñ‚Ð¾Ð²Ð° оръжие, ти уверено отÑтранÑваш зрелищната " "бухалка от Ð¿Ð°Ð´Ð½Ð°Ð»Ð¸Ñ Ð’ÐµÐ»Ð¸ÐºÐ°Ð½." msgid "Ballista of Quickness" msgstr "Катапулт на бързината" #, fuzzy msgid "The %{name} gives the hero's catapult one extra shot per combat round." msgstr "" "%{name} позволÑва на Ñ‚Ð²Ð¾Ñ ÐºÐ°Ñ‚Ð°Ð¿ÑƒÐ»Ñ‚ един допълнителен изÑтрел на боен рунд." msgid "" "Walking through the ruins of an ancient walled city, you find the instrument " "of the city's destruction, an elaborately crafted ballista." msgstr "" "Разхождайки Ñе из руините на древен ограден град, намираш инÑтрумент за " "унищожаването на градове, Ñложно изработена балиÑта." msgid "Stealth Shield of Protection" msgstr "Стелт щит за защита" msgid "" "A stone statue of a warrior holds a silver shield. As you remove the shield, " "the statue crumbles into dust." msgstr "" "Каменна ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° воин държи Ñребърен щит. Когато премахваш щита, ÑтатуÑта " "Ñе разпада на прах." msgid "Dragon Sword of Dominion" msgstr "Драконов меч на гоÑподÑтвото" msgid "" "As you are walking along a narrow path, a nearby bush suddenly bursts into " "flames. Before your eyes the flames become the image of a beautiful woman. " "She holds out a magnificent sword to you." msgstr "" "Докато вървиш по Ñ‚ÑÑна пътека, близкиÑÑ‚ храÑÑ‚ внезапно избухва в пламъци. " "Пред очите ти пламъците Ñе превръщат в образ на краÑива жена. Ð¢Ñ Ñ‚Ð¸ подава " "великолепен меч." msgid "Power Axe of Dominion" msgstr "Мощна брадва на гоÑподÑтвото" msgid "" "You see a silver axe embedded deeply in the ground. After several " "unsuccessful attempts by your army to remove the axe, you tightly grip the " "handle of the axe and effortlessly pull it free." msgstr "" "Виждаш Ñребърна брадва забита дълбоко в земÑта. След нÑколко неуÑпешни опита " "на твоÑта Ð°Ñ€Ð¼Ð¸Ñ Ð´Ð° извади брадвата, ти ÑтиÑкаш здраво дръжката на брадвата и " "без уÑилие Ñ Ð¸Ð·Ð´ÑŠÑ€Ð¿Ð²Ð°Ñˆ." msgid "Divine Breastplate of Protection" msgstr "БожеÑтвен нагръдник на защита" msgid "" "A gang of Rogues is sifting through the possessions of dead warriors. " "Scaring off the scavengers, you note the Rogues had overlooked a beautiful " "breastplate." msgstr "" "Банда мошеници преглежда притежаниÑта на мъртви воини. Изплашвайки " "чиÑтачите, забелÑзваш, че те Ñа пропуÑнали краÑив нагръдник." msgid "Minor Scroll of Knowledge" msgstr "Малък Ñвитък на знанието" msgid "" "Before you appears a levitating glass case with a scroll, perched upon a bed " "of crimson velvet. At your touch, the lid opens and the scroll floats into " "your awaiting hands." msgstr "" "Пред теб Ñе поÑвÑва левитираща Ñтъклена витрина ÑÑŠÑ Ñвитък, кацнал върху " "легло от пурпурно кадифе. При твоето докоÑване капакът Ñе Ð¾Ñ‚Ð²Ð°Ñ€Ñ Ð¸ Ñвитъкът " "изплува в очакващите ти ръце." msgid "Major Scroll of Knowledge" msgstr "ГолÑм Ñвитък на знанието" msgid "" "Visiting a local wiseman, you explain the intent of your journey. He reaches " "into a sack and withdraws a yellowed scroll and hands it to you." msgstr "" "ПоÑещаваш меÑтен мъдрец, и му обÑÑнÑваш целта на твоето пътуване. Той бръква " "в един чувал, вади пожълтÑл Ñвитък и ти го подава." msgid "Superior Scroll of Knowledge" msgstr "Превъзходен Ñвитък на знанието" msgid "" "You come across the remains of an ancient Druid. Bones, yellowed with age, " "peer from the ragged folds of her robe. Searching the robe, you discover a " "scroll hidden in the folds." msgstr "" "Ðатъкваш Ñе на оÑтанките на древен друид. КоÑти, пожълтели от възраÑтта, " "надничат от дрипавите гънки на неговата роба. ПретърÑвайки мантиÑта, " "откриваш Ñвитък, Ñкрит в гънките." msgid "Foremost Scroll of Knowledge" msgstr "Ðай-важниÑÑ‚ Ñвитък на знанието" msgid "" "Mangled bones, yellowed with age, peer from the ragged folds of a dead " "Druid's robe. Searching the robe, you discover a scroll hidden within." msgstr "" "Ðаранени коÑти, пожълтели от възраÑтта, надничат от парцаливите гънки на " "мантиÑта на мъртъв друид. ПретърÑвайки мантиÑта, откриваш Ñвитък, Ñкрит " "вътре." msgid "Endless Sack of Gold" msgstr "Безкраен чувал злато" #, fuzzy msgid "The %{name} provides the hero with %{count} gold per day." msgstr "%{name} ти оÑигурÑва %{count} злато на ден." msgid "" "A little leprechaun dances gleefully around a magic sack. Seeing you " "approach, he stops in mid-stride. The little man screams and stamps his foot " "ferociously, vanishing into thin air. Remembering the old leprechaun saying " "'Finders Keepers', you grab the sack and leave." msgstr "" "Малък леприкон танцува веÑело около вълшебен чувал. Виждайки, че Ñе " "приближаваш, той Ñпира. Той крещи и тропа Ñвирепо Ñ ÐºÑ€Ð°Ðº, изчезвайки във " "въздуха. СпомнÑш Ñи поговорката на ÑÑ‚Ð°Ñ€Ð¸Ñ Ð»ÐµÐ¿Ñ€Ð¸ÐºÐ¾Ð½ \"Ðамерители, пазители\", " "грабваш чувала и Ñи тръгваш." msgid "Endless Bag of Gold" msgstr "Безкрайна торба злато" msgid "" "A noblewoman, separated from her traveling companions, asks for your help. " "After escorting her home, she rewards you with a bag filled with gold." msgstr "" "Благородничка, отделена от Ñпътниците Ñи, моли за твоÑта помощ. След като Ñ " "придружаваш до дома и, Ñ‚Ñ Ñ‚Ðµ награждава Ñ Ñ‚Ð¾Ñ€Ð±Ð° пълна ÑÑŠÑ Ð·Ð»Ð°Ñ‚Ð¾." msgid "Endless Purse of Gold" msgstr "Безкрайна кеÑÐ¸Ñ Ð·Ð»Ð°Ñ‚Ð¾" msgid "" "In your travels, you find a leather purse filled with gold that once " "belonged to a great warrior king who had the ability to transform any " "inanimate object into gold." msgstr "" "По време на пътуваниÑта Ñи откриваш кожена кеÑÐ¸Ñ Ð¿ÑŠÐ»Ð½Ð° ÑÑŠÑ Ð·Ð»Ð°Ñ‚Ð¾, коÑто " "нÑкога е принадлежала на велик цар-воин, който е имал ÑпоÑобноÑтта да " "превърне вÑеки неодушевен предмет в злато." msgid "Nomad Boots of Mobility" msgstr "ÐомадÑки ботуши на подвижноÑтта" #, fuzzy msgid "The %{name} increase the hero's movement on land." msgstr "%{name} увеличава движението ти на Ñушата." msgid "" "A Nomad trader seeks protection from a tribe of Goblins. For your " "assistance, he gives you a finely crafted pair of boots made from the " "softest leather. Looking closely, you see fascinating ancient carvings " "engraved on the leather." msgstr "" "ÐомадÑки търговец търÑи защита от племе гоблини. За твоÑта помощ той ти дава " "фино изработен чифт ботуши, изработени от най-меката кожа. Вглеждайки Ñе " "внимателно, виждаш очарователни древни шарки, гравирани върху кожата." msgid "Traveler's Boots of Mobility" msgstr "ПътничеÑки ботуши на подвижноÑтта" msgid "" "Discovering a pair of beautifully beaded boots made from the finest and " "softest leather, you thank the anonymous donor and add the boots to your " "inventory." msgstr "" "Откривайки чифт украÑени Ñ Ð¼ÑŠÐ½Ð¸Ñта ботуши, изработени от най-фината и мека " "кожа, ти благодариш на Ð°Ð½Ð¾Ð½Ð¸Ð¼Ð½Ð¸Ñ Ð´Ð°Ñ€Ð¸Ñ‚ÐµÐ» и добавÑш ботушите към инвентара Ñи." msgid "Lucky Rabbit's Foot" msgstr "Заешки крак на къÑмета" #, fuzzy msgid "The %{name} increases the luck of the hero's army by %{count}." msgstr "%{name} увеличава къÑмета ти в битка Ñ %{count}." msgid "" "A traveling merchant offers you a rabbit's foot, made of gleaming silver " "fur, for safe passage. The merchant explains the charm will increase your " "luck in combat." msgstr "" "Пътуващ търговец ти предлага заешки крак, изработен от блеÑÑ‚Ñща Ñребърна " "козина, за безопаÑно преминаване. Търговецът обÑÑнÑва, че чарът ще увеличи " "къÑмета ти в битка." msgid "Golden Horseshoe" msgstr "Златна подкова" msgid "" "An ensnared Unicorn whinnies in fright. Murmuring soothing words, you set " "her free. Snorting and stamping her front hoof once, she gallops off. " "Looking down you see a golden horseshoe." msgstr "" "Един впримчен еднорог хленчи от Ñтрах. Говорейки уÑпокоÑващи думи, ти го " "оÑвобождаваш. Смъркайки и оптечатвайки предноте Ñи копита иведнъж, той " "галопира. Поглеждаш надолу и виждаш златна подкова." msgid "Gambler's Lucky Coin" msgstr "КъÑметлийÑката монета на комарджиÑта" msgid "" "You have captured a mischievous imp who has been terrorizing the region. In " "exchange for his release, he rewards you with a magical coin." msgstr "" "Ти залови пакоÑтлив имп, който тероризира региона. Ð’ замÑна на " "оÑвобождаването Ñи, той те възнаграждава Ñ Ð¼Ð°Ð³Ð¸Ñ‡ÐµÑка монета." msgid "Four-Leaf Clover" msgstr "ЧетирилиÑтна детелина" msgid "" "In the middle of a patch of dead and dry vegetation, to your surprise you " "find a healthy green four-leaf clover." msgstr "" "Ð’ Ñредата на парче мъртва и Ñуха раÑтителноÑÑ‚, за Ñ‚Ð²Ð¾Ñ Ð¸Ð·Ð½ÐµÐ½Ð°Ð´Ð° откриваш " "здрава зелена четирилиÑтна детелина." msgid "True Compass of Mobility" msgstr "ИÑтинÑки ÐºÐ¾Ð¼Ð¿Ð°Ñ Ð½Ð° подвижноÑтта" #, fuzzy msgid "The %{name} increases the hero's movement on land and sea." msgstr "%{name} увеличава движението ти по Ñуша и море." msgid "" "An old man claiming to be an inventor asks you to try his latest invention. " "He then hands you a compass." msgstr "" "Старец, който твърди, че е изобретател, те моли да опиташ най-новото му " "изобретение. След това ти подава компаÑ." msgid "Sailor's Astrolabe of Mobility" msgstr "МорÑшка аÑÑ‚Ñ€Ð¾Ð»Ð°Ð±Ð¸Ñ Ð½Ð° подвижноÑтта" #, fuzzy msgid "The %{name} increases the hero's movement on sea." msgstr "%{name} увеличава движението ти по море." msgid "" "An old sea captain is being tortured by Ogres. You save him, and in return " "he rewards you with a wondrous instrument to measure the distance of a star." msgstr "" "Един Ñтар морÑки капитан е измъчван от огрите. Ти го ÑпаÑÑваш, а в замÑна " "той те възнаграждава Ñ Ñ‡ÑƒÐ´ÐµÐ½ инÑтрумент за измерване на разÑтоÑнието до " "звезда." msgid "Evil Eye" msgstr "Окото на Злото" msgid "The %{name} reduces the casting cost of curse spells by half." msgstr "%{name} намалÑва разходите за правене на проклÑÑ‚Ð¸Ñ Ð½Ð°Ð¿Ð¾Ð»Ð¾Ð²Ð¸Ð½Ð°." msgid "" "While venturing into a decrepit hut you find the Skeleton of a long dead " "witch. Investigation of the remains reveals a glass eye rolling around " "inside an empty skull." msgstr "" "РиÑкуваш да влезеш в порутена колиба, намираш Ñкелета на отдавна мъртва " "вещица. ИзÑледването на оÑтанките разкрива Ñтъклено око, което Ñе Ñ‚ÑŠÑ€ÐºÐ°Ð»Ñ Ð² " "празен череп." msgid "Enchanted Hourglass" msgstr "ОмагьоÑан пÑÑъчен чаÑовник" #, fuzzy msgid "" "The %{name} extends the duration of all the hero's spells by %{count} turns." msgstr "" "%{name} удължава продължителноÑтта на вÑичките ти Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ Ñ %{count} хода." msgid "" "A surprise turn in the landscape finds you in the midst of a grisly scene: " "Vultures picking at the aftermath of a terrible battle. Your cursory search " "of the remains turns up an enchanted hourglass." msgstr "" "Изненадващ обрат в пейзажа ти открива ужаÑÑваща Ñцена: лешоÑди, човъркащи " "Ñлед ужаÑна битка. Беглото ти търÑене на оÑтанките открива омагьоÑан пÑÑъчен " "чаÑовник." msgid "Gold Watch" msgstr "Златен чаÑовник" #, fuzzy msgid "The %{name} doubles the effectiveness of the hero's hypnotize spells." msgstr "%{name} удвоÑва ефективноÑтта на твойте магии за хипноза." msgid "" "In reward for helping his cart out of a ditch, a traveling potion salesman " "gives you a \"magic\" gold watch. Unbeknownst to him, the watch really is " "magical." msgstr "" "Като награда за това, че Ñи помогнал на количката му да излезе от канавка, " "пътуващ продавач на отвари ти дава \"магичеÑки\" златен чаÑовник. Без да " "знае, чаÑовникът наиÑтина е вълшебен." msgid "Skullcap" msgstr "Кепе" msgid "The %{name} halves the casting cost of all mind influencing spells." msgstr "" "%{name} намалÑва наполовина разходите за правене на вÑички магии за влиÑние " "върху ума." msgid "" "A brief stop at an improbable rural inn yields an exchange of money, tales, " "and accidentally, luggage. You find a magical skullcap in your new backpack." msgstr "" "Кратка Ñпирка в невероÑтна ÑелÑка Ñтранноприемница води до размÑна на пари, " "приказки и Ñлучаен багаж. Ðамирате магичеÑка шапка в новата Ñи раница." msgid "Ice Cloak" msgstr "Ледено наметало" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from cold spells." msgstr "" "%{name} намалÑва наполовина вÑички щети, които твойте войÑки понаÑÑÑ‚ от " "Ñтудените магии." msgid "" "Responding to the panicked cries of a damsel in distress, you discover a " "young woman fleeing from a hungry bear. You slay the beast in the nick of " "time, and the grateful Sorceress weaves a magic cloak from the bear's hide." msgstr "" "ОтговарÑш на паничеÑките викове на девойка в беда, оказва Ñе млада жена, " "коÑто бÑга от гладна мечка. Ти убиваш звÑра навреме, а благодарната " "МагьоÑница изтъкава вълшебно наметало от кожата на мечката." msgid "Fire Cloak" msgstr "Огнено наметало" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from fire spells." msgstr "" "%{name} намалÑва наполовина вÑички щети, които твойте войÑки нанаÑÑÑ‚ от " "огнени магии." msgid "" "You've come upon a fight between a Necromancer and a Paladin. The " "Necromancer blasts the Paladin with a fire bolt, bringing him to his knees. " "Acting quickly, you slay the evil one before the final blow. The grateful " "Paladin gives you the fire cloak that saved him." msgstr "" "Попадаш на битка между некромант и паладин. Ðекромантът взривÑва Паладина Ñ " "огнена Ñтрела, карайки го да падне на колене. ДейÑтвайки бързо, ти убиваш " "Ð·Ð»Ð¾Ð´ÐµÑ Ð¿Ñ€ÐµÐ´Ð¸ поÑÐ»ÐµÐ´Ð½Ð¸Ñ ÑƒÐ´Ð°Ñ€. БлагодарниÑÑ‚ Паладин ти дава огненото наметало, " "което го е ÑпаÑило." msgid "Lightning Helm" msgstr "Светкавичен шлем" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from lightning " "spells." msgstr "" "%{name} намалÑва наполовина вÑички щети, нанеÑени от твойте войÑки от " "Ñветкавични заклинаниÑ." msgid "" "A traveling tinker in need of supplies offers you a helm with a thunderbolt " "design on its top in exchange for food and water. Curious, you accept, and " "later find out that the helm is magical." msgstr "" "Пътуващ калайджиÑ, нуждаещ Ñе от провизии, ти предлага шлем Ñ Ð³Ñ€ÑŠÐ¼Ð¾Ñ‚ÐµÐ²Ð¸Ñ†Ð° на " "върха му в замÑна на храна и вода. Любопитен, ти приемаш и по-къÑно " "разбираш, че шлемът е магичеÑки." msgid "Evercold Icicle" msgstr "Вечноледена виÑулка" #, fuzzy msgid "" "The %{name} causes the hero's cold spells to do %{count} percent more damage " "to enemy troops." msgstr "" "%{name} кара твойте Ñтудени магии да нанаÑÑÑ‚ %{count} процента повече щети " "на вражеÑките войÑки." msgid "" "An icicle withstanding the full heat of the noonday sun attracts your " "attention. Intrigued, you break it off, and find that it does not melt in " "your hand." msgstr "" "Ледена виÑулка, издържаща на топлина на обедното Ñлънце, привлича вниманието " "ти. Заинтригуван, ти Ñ Ð¾Ñ‚Ñ‡ÑƒÐ¿Ð²Ð°Ñˆ и уÑтановÑваш, че не Ñе топи в ръката ти." msgid "Everhot Lava Rock" msgstr "Вечногореща Ñкала" #, fuzzy msgid "" "The %{name} causes the hero's fire spells to do %{count} percent more damage " "to enemy troops." msgstr "" "%{name} кара огнените ти Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ Ð´Ð° нанаÑÑÑ‚ %{count} процента повече щети " "на вражеÑките войÑки." msgid "" "Your wanderings bring you into contact with a tribe of ape-like beings using " "a magical lava rock that never cools to light their fires. You take pity on " "them and teach them to make fire with sticks. Believing you to be a god, the " "apes give you their rock." msgstr "" "Твойте ÑÐºÐ¸Ñ‚Ð°Ð½Ð¸Ñ Ñ‚Ðµ Ñрещат Ñ Ð¿Ð»ÐµÐ¼Ðµ маймуноподобни ÑъщеÑтва, използващи " "магичеÑка Ñкала от лава, коÑто никога не изÑтива за да палÑÑ‚ огньовете Ñи. " "Ти ги ÑъжалÑваш и ги учиш да палÑÑ‚ огън Ñ Ñ‚Ð¾Ñги. Ð’Ñрвайки, че Ñи бог, " "маймуните ти дават ÑвоÑта Ñкала." msgid "Lightning Rod" msgstr "Гръмотевичен жезъл" #, fuzzy msgid "" "The %{name} causes the hero's lightning spells to do %{count} percent more " "damage to enemy troops." msgstr "" "%{name} кара твойте Ñветкавични Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ Ð´Ð° нанаÑÑÑ‚ %{count} процента " "повече щети на вражеÑките войÑки." msgid "" "While waiting out a storm, a lighting bolt strikes a nearby cottage's " "lightning rod, which melts and falls to the ground. The tip of the rod, " "however, survives intact and makes your hair stand on end when you touch it. " "Hmm..." msgstr "" "Докато изчакваш бурÑ, гръм поразÑва гръмоотвода на близката вила, който Ñе " "ÑÑ‚Ð°Ð¿Ñ Ð¸ пада на земÑта. Върхът на пръта обаче оÑтава непокътнат и кара " "коÑата ти да наÑтръхва, когато го докоÑваш. Хм..." msgid "Snake-Ring" msgstr "ЗмийÑки пръÑтен" #, fuzzy msgid "The %{name} halves the casting cost of all of the hero's bless spells." msgstr "" "%{name} намалÑва наполовина разходите за правене на вÑички твой благоÑлавÑщи " "магии." msgid "" "You've found an oddly shaped ring on the finger of a long dead traveler. The " "ring looks like a snake biting its own tail." msgstr "" "Ðамираш пръÑтен ÑÑŠÑ Ñтранна форма на пръÑта на отдавна мъртъв пътник. " "ПръÑтенът прилича на змиÑ, захапала ÑобÑтвената Ñи опашка." msgid "Ankh" msgstr "Ðнх" #, fuzzy msgid "" "The %{name} doubles the effectiveness of all of the hero's resurrect and " "animate spells." msgstr "" "%{name} удвоÑва ефективноÑтта на вÑичките ти Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ Ð·Ð° възкреÑÑване и " "оживÑване." msgid "" "A fierce windstorm reveals the entrance to a buried tomb. Your investigation " "reveals that the tomb has already been looted, but the thieves overlooked an " "ankh on a silver chain in the dark." msgstr "" "Свирепа Ð±ÑƒÑ€Ñ Ñ€Ð°Ð·ÐºÑ€Ð¸Ð²Ð° входа на заровена гробница. Твоето разÑледване " "разкрива, че гробницата вече е била ограбена, но крадците не Ñа забелÑзали " "Ðнх на Ñребърна верига в тъмното." msgid "Book of Elements" msgstr "Книга на елементите" #, fuzzy msgid "" "The %{name} doubles the effectiveness of all of the hero's summoning spells." msgstr "%{name} удвоÑва ефективноÑтта на вÑичките ти магии за призоваване." msgid "" "You come across a conjurer who begs to accompany you and your army awhile " "for safety. You agree, and he offers as payment a copy of the book of the " "elements." msgstr "" "Попадаш на фокуÑник, който моли да придружи теб и армиÑта ти извеÑтно време " "за да е в безопаÑноÑÑ‚. Ти Ñе ÑъглаÑÑваш и той предлага като заплащане копие " "от Книгата на елементите." msgid "Elemental Ring" msgstr "ПръÑтен на елементал" msgid "The %{name} halves the casting cost of all summoning spells." msgstr "" "%{name} намалÑва наполовина разходите за правене на вÑички магии за " "призоваване." msgid "" "While pausing to rest, you notice a bobcat climbing a short tree to get at a " "crow's nest. On impulse, you climb the tree yourself and scare off the cat. " "When you look in the nest, you find a collection of shiny stones and a ring." msgstr "" "Докато Ñпираш за почивка, забелÑзваш Ñ€Ð¸Ñ Ð´Ð° Ñе катери по ниÑко дърво, за да " "Ñтигне до гарваново гнездо. ИмпулÑивно ти решаваш да Ñе качиш на дървото и " "изплашиш котката. Когато поглеждаш в гнездото, намираш ÐºÐ¾Ð»ÐµÐºÑ†Ð¸Ñ Ð¾Ñ‚ блеÑÑ‚Ñщи " "камъни и пръÑтен." msgid "Holy Pendant" msgstr "Свещен медальон" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to curse spells." msgstr "%{name} прави вÑичките ти войÑки имунизирани Ñрещу магии за проклÑтиÑ." msgid "" "In your wanderings you come across a hermit living in a small, tidy hut. " "Impressed with your mission, he takes time out from his meditations to bless " "and give you a charm against curses." msgstr "" "Ð’ ÑкитаниÑта Ñи попадаш на отшелник, живеещ в малка Ñпретната колиба. " "Впечатлен от твоÑта миÑиÑ, той пркъÑва Ñвоите медитации, за да те благоÑлови " "и да ти даде Ð¼Ð°Ð³Ð¸Ñ Ñрещу проклÑтиÑ." msgid "Pendant of Free Will" msgstr "Медальон на Ñвободната волÑ" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to hypnotize spells." msgstr "" "%{name} прави вÑичките ти войÑки имунизирани Ñрещу хипнотизиращи магии." msgid "" "Responding to cries for help, you find river Sprites making a sport of " "dunking an old man. Feeling vengeful, you rescue the man and drag a Sprite " "onto dry land for awhile. The Sprite, uncomfortable in the air, gives you a " "magic pendant to let him go." msgstr "" "ОтговарÑйки на виковете за помощ, откриваш речни духове, които Ñе готвÑÑ‚ да " "потапÑÑ‚ един Ñтарец. Решаваш да ÑпаÑиш човека и за наказание влачиш един " "речен дух на Ñушата за извеÑтно време. Духът, който Ñе чувÑтва неудобно във " "въздуха, ти дава магичеÑка медальон, за да го пуÑнеш." msgid "Pendant of Life" msgstr "Медальон на живота" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to death spells." msgstr "%{name} прави вÑичките ти войÑки имунизирани Ñрещу магии за Ñмърт." msgid "" "A brief roadside encounter with a small caravan and a game of knucklebones " "wins a magic pendant. Its former owner says that it protects from " "Necromancers' death spells." msgstr "" "Кратка крайпътна Ñреща Ñ Ð¼Ð°Ð»ÐºÐ° каравана и игра на кокалчета печели магичеÑка " "виÑулка. БившиÑÑ‚ му ÑобÑтвеник казва, че предпазва от Ñмъртните магии на " "некромантите." msgid "Serenity Pendant" msgstr "Медальон на ÑпокойÑтвието" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to berserk spells." msgstr "%{name} прави вÑичките ти войÑки имунизирани Ñрещу полудÑващи магии." msgid "" "The sounds of combat draw you to the scene of a fight between an old " "Barbarian and an eight-headed Hydra. Your timely intervention swings the " "battle in favor of the man, and he rewards you with a pendant he used to use " "to calm his mind for battle." msgstr "" "Звуците на битка привличат вниманието ти към битка между Ñтар варварин и " "оÑемглава хидра. ТвоÑта навременна намеÑа обръща битката в полза на мъжа и " "той те възнаграждава Ñ Ð¼ÐµÐ´Ð°Ð»ÑŒÐ¾Ð½Ð°, който е използвал за да уÑпокои ума Ñи за " "битка." msgid "Seeing-eye Pendant" msgstr "Медальон Ð’Ñевиждащо око" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to blindness spells." msgstr "" "%{name} прави вÑичките ти войÑки имунизирани Ñрещу Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ Ð·Ð° Ñлепота." msgid "" "You come upon a very old woman, long blind from cataracts and dying alone. " "You tend to her final needs and promise a proper burial. Grateful, she gives " "you a magic pendant emblazoned with a stylized eye. It lets you see with " "your eyes closed." msgstr "" "Срещаш много Ñтара жена, отдавна ÑлÑпа от катаракта и умираща Ñама. Ти Ñе " "погрижваш за Ð½ÐµÑ Ð¸ и обещаваш подходÑщо погребение. С благодарноÑÑ‚, Ñ‚Ñ Ñ‚Ð¸ " "дава вълшебен медальон, украÑен ÑÑŠÑ Ñтилизирано око. ПозволÑва ти да виждаш " "ÑÑŠÑ Ð·Ð°Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð¸ очи." msgid "Kinetic Pendant" msgstr "Кинетичен медальон" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to paralyze spells." msgstr "%{name} прави вÑичките ти войÑки имунизирани Ñрещу парализиращи магии." msgid "" "You come across a golem wearing a glowing pendant and blocking your way. " "Acting on a hunch, you cut the pendant from its neck. Deprived of its power " "source, the golem breaks down, leaving you with the magical pendant." msgstr "" "Срещаш голем, ноÑещ Ñветещ медальон и препречващ Ð¿ÑŠÑ‚Ñ Ñ‚Ð¸. Интуитивно, " "отрÑзваш медальона от шиÑта му. Лишен от ÑÐ²Ð¾Ñ Ð¸Ð·Ñ‚Ð¾Ñ‡Ð½Ð¸Ðº на енергиÑ, големът " "Ñе разпада, оÑтавÑйки ти магичеÑката Ñи виÑулка." msgid "Pendant of Death" msgstr "Медальона на Ñмъртта" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to holy spells." msgstr "%{name} прави вÑичките ти войÑки имунизирани Ñрещу Ñвещени заклинаниÑ." msgid "" "A quick and deadly battle with a Necromancer wins you his magical pendant. " "Later, a Wizard tells you that the pendant protects undead under your " "control from holy word spells." msgstr "" "Бърза и ÑмъртоноÑна битка Ñ Ð½ÐµÐºÑ€Ð¾Ð¼Ð°Ð½Ñ‚ ти печели Ð½ÐµÐ³Ð¾Ð²Ð¸Ñ Ð¼Ð°Ð³Ð¸Ñ‡ÐµÑки медальон. " "По-къÑно вълшебникът ти казва, че медальонът защитава немъртвите под твой " "контрол от Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ ÑÑŠÑ Ñвещени думи." msgid "Wand of Negation" msgstr "Жезъл на отрицанието" #, fuzzy msgid "" "The %{name} makes all of the hero's troops immune to dispel magic spells." msgstr "%{name} прави вÑичките ти войÑки имунизирани Ñрещу разÑейващи магии." msgid "" "You meet an old Wizard friend of yours traveling in the opposite direction. " "He presents you with a gift: A wand that prevents the use of the dispel " "magic spell on your allies." msgstr "" "Срещаш Ñтар твой приÑтел вълшебник, който пътува в обратната поÑока. Той ти " "предÑÑ‚Ð°Ð²Ñ Ð¿Ð¾Ð´Ð°Ñ€ÑŠÐº: жезъл, който предотвратÑва използването на магичеÑкото " "заклинание за разÑейване на твойте Ñъюзници." msgid "Golden Bow" msgstr "Златен лък" #, fuzzy msgid "" "The %{name} eliminates the %{count} percent penalty for the hero's troops " "shooting past obstacles (e.g. castle walls)." msgstr "" "%{name} елиминира %{count} процента наказание за твойте войÑки, ÑтрелÑщи " "покрай препÑÑ‚ÑÑ‚Ð²Ð¸Ñ (напр. Ñтени на замък)." msgid "" "A chance meeting with a famous Archer finds you in a game of knucklebones " "pitting his bow against your horse. You win." msgstr "" "Случайна Ñреща Ñ Ð¸Ð·Ð²ÐµÑтен Ñтрелец ти открива в игра на кокалчета, коÑто " "Ð¸Ð·Ð¿Ñ€Ð°Ð²Ñ Ð»ÑŠÐºÐ° му Ñрещу ÐºÐ¾Ð½Ñ Ñ‚Ð¸. Ти печелиш." msgid "Telescope" msgstr "ТелеÑкоп" #, fuzzy msgid "" "The %{name} increases the amount of terrain the hero reveals when " "adventuring by %{count} extra square." msgstr "" "%{name} увеличава терена, който твоÑÑ‚ герой разкрива при приключение Ñ " "%{count} допълнителен квадрат." msgid "" "A merchant from far away lands trades you a new invention of his people for " "traveling supplies. It makes distant objects appear closer, and he calls " "it...\n" "\n" "a telescope." msgstr "" "Търговец от далечни земи Ñ€Ð°Ð·Ð¼ÐµÐ½Ñ Ñ Ñ‚ÐµÐ± ново изобретение на ÑÐ²Ð¾Ñ Ð½Ð°Ñ€Ð¾Ð´ за " "провизии за пътуване. Изобретението кара отдалечените обекти да изглеждат по-" "близки и той го нарича...\n" "\n" "телеÑкоп." msgid "Statesman's Quill" msgstr "Перото на държавника" #, fuzzy msgid "" "The %{name} reduces the cost of surrender to %{count} percent of the total " "cost of troops the hero has in their army." msgstr "" "%{name} намалÑва цената на предаването до %{count} процента от общата цена " "на войÑките, които имаш във твоÑта армиÑ." msgid "" "You pause to help a diplomat with a broken axle fix his problem. In " "gratitude, he gives you a writing quill with magical properties which he " "says will \"help people see things your way\"." msgstr "" "Спираш, за да помогнеш на дипломат ÑÑŠÑ Ñчупена Ð¾Ñ Ð´Ð° реши проблема Ñи. Ð’ " "знак на благодарноÑÑ‚ той ти дава перо за пиÑане Ñ Ð¼Ð°Ð³Ð¸Ñ‡ÐµÑки ÑвойÑтва, което " "Ñпоред него ще \"помогне на хората да видÑÑ‚ нещата по Ñ‚Ð²Ð¾Ñ Ð½Ð°Ñ‡Ð¸Ð½\"." msgid "Wizard's Hat" msgstr "Шапката на вълшебника" #, fuzzy msgid "" "The %{name} increases the duration of the hero's spells by %{count} turns." msgstr "" "%{name} увеличава продължителноÑтта на заклинаниÑта ти Ñ %{count} хода." msgid "" "You see a Wizard fleeing from a Griffin and riding like the wind. The Wizard " "opens a portal and rides through, getting his hat knocked off by the edge of " "the gate. The Griffin follows; the gate closes. You pick the hat up, dust it " "off, and put it on." msgstr "" "Виждш вълшебник, който бÑга от грифон и Ñзди като вÑтъра. Вълшебникът Ð¾Ñ‚Ð²Ð°Ñ€Ñ " "портал и минава през него, когато шапката му е Ñъборена от ръба на портала. " "Грифонът го поÑледва и портала Ñе затварÑ. Вдигаш шапката, избърÑваш праха " "от Ð½ÐµÑ Ð¸ Ñ Ñлагаш." msgid "Power Ring" msgstr "ПръÑтена на Ñилата" #, fuzzy msgid "The %{name} returns %{count} extra spell points per day to the hero." msgstr "" "%{name} връща %{count} допълнителни точки Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ Ð½Ð° ден на Ñ‚Ð¾Ñ Ð³ÐµÑ€Ð¾Ð¹." msgid "" "You find a small tree that closely resembles the great Warlock Carnauth with " "a ring around one of its twigs. Scraps of clothing and rotting leather lead " "you to suspect that it IS Carnauth, transformed. Since you can't help him, " "you take the magic ring." msgstr "" "Ðамираш малко дърво, което много прилича на Ð²ÐµÐ»Ð¸ÐºÐ¸Ñ Ð¼Ð°Ð³ÑŒÐ¾Ñник Карнаут Ñ " "пръÑтен около една от клонките му. Парчета от дрехи и гниеща кожа те карат " "да подозираш, че това е Карнаут, транÑформиран в дърво. Тъй като не можеш да " "му помогнеш, взимаш магичеÑÐºÐ¸Ñ Ð¿Ñ€ÑŠÑтен." msgid "Ammo Cart" msgstr "Количка Ñ Ð±Ð¾ÐµÐ¿Ñ€Ð¸Ð¿Ð°Ñи" #, fuzzy msgid "" "The %{name} provides endless ammunition for all of the hero's troops that " "shoot." msgstr "" "%{name} оÑигурÑва безкрайни боеприпаÑи за вÑички твой войÑки, които ÑтрелÑÑ‚." msgid "" "An ammunition cart in the middle of an old battlefield catches your eye. " "Inspection shows it to be in good working order, so you take it along." msgstr "" "Количка Ñ Ð±Ð¾ÐµÐ¿Ñ€Ð¸Ð¿Ð°Ñи в Ñредата на Ñтаро бойно поле привлича вниманието ти. " "Проверката ти показва, че е в добро ÑÑŠÑтоÑние, така че Ñ Ð²Ð·ÐµÐ¼Ð°Ñˆ ÑÑŠÑ Ñебе Ñи." msgid "Tax Lien" msgstr "Данъчен залог" #, fuzzy msgid "The %{name} costs the hero %{count} gold pieces per day." msgstr "%{name} ти Ñтрува %{count} златни монети на ден." msgid "" "Your big spending habits have earned you a massive tax bill that you can't " "hope to pay. The tax man takes pity and agrees to only take 250 gold a day " "from your account for life. Check here if you want one dollar to go to the " "presidential campaign election fund." msgstr "" "Твойте лоши навици за харчене Ñа ти Ñпечелили огромна данъчна Ñметка, коÑто " "не можеш да платиш. ДанъчниÑÑ‚ Ñе ÑмилÑва и Ñе ÑъглаÑÑва да взема Ñамо по 250 " "злато на ден от Ñметката ти за цÑл живот. Провери тук, ако иÑкаш един долар " "да отиде във фонда за президентÑките избори." msgid "Hideous Mask" msgstr "Отвратителна маÑка" #, fuzzy msgid "The %{name} prevents all 'wandering' armies from joining the hero." msgstr "" "%{name} не позволÑва на вÑички \"Ñкитащи\" армии да Ñе приÑъединÑÑ‚ към Ñ‚Ð²Ð¾Ñ " "герой." msgid "" "Your looting of the grave of Sinfilas Gardolad, the famous shapeshifting " "Warlock, unearths his fabled mask. Trembling, you put it on and it twists " "your visage into an awful grimace! Oh no! It's actually the hideous mask of " "Gromluck Greene, and you are stuck with it." msgstr "" "Твоето плÑчкоÑване на гроба на Ð¡Ð¸Ð½Ñ„Ð¸Ð»Ð°Ñ Ð“Ð°Ñ€Ð´Ð¾Ð»Ð°Ð´, извеÑтниÑÑ‚ магьоÑник " "променÑщ формата Ñи, открива легендарната му маÑка. С нетърпение Ñ Ñлагаш и " "Ñ‚Ñ Ð¸Ð·ÐºÑ€Ð¸Ð²Ñва лицето ти в ужаÑна гримаÑа! О, не! Това вÑъщноÑÑ‚ е " "Отвратителната маÑка на Громлак Грийн и ти Ñи оÑтаваш Ñ Ð½ÐµÑ." msgid "Endless Pouch of Sulfur" msgstr "Безкрайна кеÑÐ¸Ñ ÑÑŠÑ ÑÑра" msgid "The %{name} provides %{count} unit of sulfur per day." msgstr "%{name} оÑигурÑва %{count} единица ÑÑра на ден." msgid "" "You visit an alchemist who, upon seeing your army, is swayed by the " "righteousness of your cause. The newly loyal subject gives you his endless " "pouch of sulfur to help with the war effort." msgstr "" "ПоÑещаваш алхимик, който Ñлед като вижда твоÑта армиÑ, е поразен от " "праведноÑтта на твоÑта кауза. ÐовиÑÑ‚ лоÑлен поданик ти дава ÑвоÑта безкрайна " "кеÑÐ¸Ñ ÑÑŠÑ ÑÑра, за да ти помогне във войната." msgid "Endless Vial of Mercury" msgstr "Безкраен муÑкал Ñ Ð¶Ð¸Ð²Ð°Ðº" msgid "The %{name} provides %{count} unit of mercury per day." msgstr "%{name} оÑигурÑва %{count} единица живак на ден." msgid "" "A brief stop at a hastily abandoned Wizard's tower turns up a magical vial " "of mercury that always has a little left on the bottom. Recognizing a " "treasure when you see one, you cap it and slip it in your pocket." msgstr "" "Спираш набързо в изоÑтавена кула на вълшебник и намираш магичеÑки муÑкал Ñ " "живак, в който винаги оÑтава малко на дъното. Разпознаваш това Ñъкровище, " "затварÑш го и го пъхаш в джоба Ñи." msgid "Endless Pouch of Gems" msgstr "Безкрайна кеÑÐ¸Ñ ÑÑŠÑ Ñкъпоценни камъни" msgid "The %{name} provides %{count} unit of gems per day." msgstr "%{name} оÑигурÑва %{count} единица Ñкъпоценни камъни на ден." msgid "" "A short rainstorm brings forth a rainbow...and you can see the end of it. " "Riding quickly, you seize the pot of gold you find there. The leprechaun who " "owns it, unable to stop you from taking it, offers an endless pouch of gems " "for the return of his gold. You accept." msgstr "" "Кратка дъждовна Ð±ÑƒÑ€Ñ Ñ€Ð°Ð¶Ð´Ð° дъга... и ти виждаш ÐºÑ€Ð°Ñ Ð¹. Яздейки бързо, ти " "грабваш гърнето ÑÑŠÑ Ð·Ð»Ð°Ñ‚Ð¾, което намираш там. Лепреконът, който го " "притежава, неÑпоÑобен да ти попречи да го вземеш, предлага безкрайна кеÑÐ¸Ñ " "ÑÑŠÑ Ñкъпоценни камъни Ñрещу връщането на златото му. Ти приемаш." msgid "Endless Cord of Wood" msgstr "Безкрайна купчина дърва" msgid "The %{name} provides %{count} unit of wood per day." msgstr "%{name} оÑигурÑва %{count} единица дърва на ден." msgid "" "Pausing to rest and light a cook fire, you pull wood out of a nearby pile of " "dead wood. As you keep pulling wood from the pile, you notice that it " "doesn't shrink. You realize to your delight that the wood is enchanted, so " "you take it along." msgstr "" "Спирайки, за да Ñи починеш и да запалиш огън, вадиш дърва от близка купчина " "дърва. Докато продължаваш да дърпаш дърва от купчината, забелÑзваш, че те не " "намалÑват. Разбираш Ñ Ñ€Ð°Ð´Ð¾ÑÑ‚, че купчината дърва е омагьоÑана, затова Ñ " "взимаш ÑÑŠÑ Ñебе Ñи." msgid "Endless Cart of Ore" msgstr "Безкрайна количка Ñ Ñ€ÑƒÐ´Ð°" msgid "The %{name} provides %{count} unit of ore per day." msgstr "%{name} оÑигурÑва %{count} единица руда на ден." msgid "" "You've found a Goblin weapon smithy making weapons for use against humans. " "With a tremendous yell you and your army descend upon their camp and drive " "them away. A search finds a magic ore cart that never runs out of iron." msgstr "" "Откриваш ковачница за Ð¾Ñ€ÑŠÐ¶Ð¸Ñ Ð½Ð° гоблини, коÑто прави Ð¾Ñ€ÑŠÐ¶Ð¸Ñ Ð·Ð° използване " "Ñрещу хората. С огромен вик ти и твоÑта Ð°Ñ€Ð¼Ð¸Ñ Ñе ÑпуÑкате върху лагера им и " "ги прогонвате. При претърÑването откриваш магичеÑка количка Ñ Ñ€ÑƒÐ´Ð°, в коÑто " "желÑзото никога не Ñвършва." msgid "Endless Pouch of Crystal" msgstr "Безкрайна кеÑÐ¸Ñ Ñ ÐºÑ€Ð¸Ñтали" msgid "The %{name} provides %{count} unit of crystal per day." msgstr "%{name} оÑигурÑва %{count} единица криÑтал на ден." msgid "" "Taking shelter from a storm in a small cave, you notice a small patch of " "crystal in one corner. Curious, you break a piece off and notice that the " "original crystal grows the lost piece back. You decide to stuff the entire " "patch into a pouch and take it with you." msgstr "" "Прикривайки Ñе от Ð±ÑƒÑ€Ñ Ð² малка пещера, забелÑзваш малко парче криÑтал в " "ÐµÐ´Ð¸Ð½Ð¸Ñ ÑŠÐ³ÑŠÐ». Любопитно, ти отчупваш парче и забелÑзвате, че оригиналниÑÑ‚ " "криÑтал възÑтановÑва изгубеното парче обратно. Решаваш да напъхаш цÑлото " "парче в торбичка и да го вземеш ÑÑŠÑ Ñебе Ñи." msgid "Spiked Helm" msgstr "Шлем Ñ ÑˆÐ¸Ð¿Ð¾Ð²Ðµ" msgid "" "Your army is ambushed by a small tribe of wild (and none too bright) Orcs. " "You fend them off easily and the survivors flee in all directions. One of " "the Orcs was wearing a polished spiked helm. Figuring it will make a good " "souvenir, you take it." msgstr "" "ТвоÑта Ð°Ñ€Ð¼Ð¸Ñ Ðµ нападната от заÑада от малко племе диви (и не твърде умни) " "орки. ЛеÑно ги отблъÑкваш и оцелелите бÑгат във вÑички поÑоки. Един от " "орките ноÑеше полиран шлем Ñ ÑˆÐ¸Ð¿Ð¾Ð²Ðµ. СмÑтайки, че ще Ñтане добър Ñувенир, го " "взимаш." msgid "Spiked Shield" msgstr "Щит Ñ ÑˆÐ¸Ð¿Ð¾Ð²Ðµ" msgid "" "You come upon a bridge spanning a dry gully. Before you can cross, a Troll " "steps out from under the bridge and demands payment before it will permit " "you to pass. You refuse, and the Troll charges, forcing you to slay it. You " "take its spiked shield as a trophy." msgstr "" "Стигаш до моÑÑ‚, минаващ през Ñухо дере. Преди да можеш да преминеш, трол " "излиза изпод моÑта и изиÑква плащане, преди да ти позволи да преминеш. " "Отказваш и тролът напада, принуждавайки те да го убиеш. Взимаш щита му Ñ " "шипове като трофей." msgid "White Pearl" msgstr "БÑла перла" msgid "" "A walk across a dry saltwater lake bed yields an unlikely prize: A white " "pearl amidst shattered shells and debris." msgstr "" "Разходка през дъното на Ñухо Ñоленоводно езеро ноÑи невероÑтна награда: бÑла " "перла Ñред натрошени черупки и отломки." msgid "Black Pearl" msgstr "Черна перла" msgid "" "Rumors of a Griffin of unusual size preying upon the countryside lead you to " "its cave lair. A quick, brutal fight dispatches the beast, and a search of " "its foul nest turns up a huge black pearl." msgstr "" "Слуховете за грифон Ñ Ð½ÐµÐ¾Ð±Ð¸Ñ‡Ð°Ð¹Ð½Ð¸ размери, който плÑчкоÑва в провинциÑта, те " "отвеждат до неговото пещерно леговище. Бърза и брутална битка ликвидира " "звÑра и при претърÑването на мръÑното му гнездо откриваш огромна черна перла." msgid "Magic Book" msgstr "МагичеÑка книга" #, fuzzy msgid "The %{name} enables the hero to cast spells." msgstr "%{name} ти позволÑва да правиш магии." msgid "Dummy 1" msgstr "Манекен 1" msgid "The reserved artifact." msgstr "ЗапазениÑÑ‚ артефакт." msgid "Dummy 2" msgstr "Манекен 2" msgid "Dummy 3" msgstr "Манекен 3" msgid "Dummy 4" msgstr "Манекен 4" msgid "Spell Scroll" msgstr "Свитък за заклинание" #, fuzzy msgid "" "This %{name} gives the hero the ability to cast the %{spell} spell if the " "hero has a Magic Book." msgstr "" "Това %{name} дава на Ñ‚Ð²Ð¾Ñ Ð³ÐµÑ€Ð¾Ð¹ ÑпоÑобноÑтта да изпълни заклинанието " "%{spell}, ако твоÑÑ‚ герой има магичеÑка книга." msgid "" "You find an elaborate container which houses an old vellum scroll. The runes " "on the container are very old, and the artistry with which it was put " "together is stunning. As you pull the scroll out, you feel imbued with " "magical power." msgstr "" "Ðамери натруфена кутиÑ, в коÑто Ñе ÑъхранÑва Ñтар Ñвитък от пергамент. " "Руните по кутиÑта Ñа много Ñтари, майÑторÑки изработени и е Ñглобена " "зашеметÑващо. Докато издърпвате Ñвитъка, Ñе чувÑтвате пропити Ñ Ð¼Ð°Ð³Ð¸Ñ‡ÐµÑка " "Ñила." msgid "Arm of the Martyr" msgstr "Ръката на мъченика" #, fuzzy msgid "" "The %{name} increases the hero's spell power by %{count} but adds the undead " "morale penalty." msgstr "" "%{name} увеличава Ñилата на заклинанието ти Ñ %{count}, но Ð´Ð¾Ð±Ð°Ð²Ñ Ð½Ð°ÐºÐ°Ð·Ð°Ð½Ð¸Ðµ " "за морала на немъртвите." msgid "" "One of the less intelligent members of your party picks up an arm off of the " "ground. Despite its missing a body, it is still moving. Your troops find the " "dismembered arm repulsive, but you cannot bring yourself to drop it: it " "seems to hold some sort of magical power that influences your decision " "making." msgstr "" "Един от по-малко интелигентните членове на твоÑта група вдига ръка от " "земÑта. Въпреки че липÑва Ñ‚Ñло, Ñ‚Ñ Ð²Ñе още Ñе движи. Твойте войÑки намират " "разчленената ръка за отблъÑкваща, но не можеш да Ñ Ð¾Ñтвиш: изглежда " "притежава нÑкаква магичеÑка Ñила, коÑто влиÑе върху вземането на решениÑ." msgid "Breastplate of Anduran" msgstr "Ðагръдник на Ðндуран" #, fuzzy msgid "The %{name} increases the hero's defense by %{count}." msgstr "%{name} увеличава твоÑта защита Ñ %{count}." msgid "" "You come upon a sign. It reads: \"Here lies the body of Anduran. Bow and " "swear fealty, and you shall be rewarded.\" You decide to do as it says. As " "you stand up, you feel a coldness against your skin. Looking down, you find " "that you are suddenly wearing a gleaming, ornate breastplate." msgstr "" "Попадаш на знак. Той глаÑи: \"Тук лежи Ñ‚Ñлото на Ðндуран. Поклони Ñе и Ñе " "закълни във вÑрноÑÑ‚ и ще бъдеш възнаграден.\" Ти решаваш да направиш както е " "казано. Докато Ñе изправÑш, уÑещаш тръпки по кожата Ñи. Поглеждайки надолу, " "откриваш, че нÑкакÑи ноÑиш блеÑÑ‚Ñщ, богато украÑен нагръдник." msgid "Broach of Shielding" msgstr "Брошка за защита" msgid "" "The %{name} provides %{count} percent protection from Armageddon and " "Elemental Storm, but decreases spell power by 2." msgstr "" "%{name} оÑигурÑва %{count} процента защита от Ðрмагедон и Елементна бурÑ, но " "намалÑва Ñилата на заклинаниÑта Ñ 2." msgid "" "A kindly Sorceress thinks that your army's defenses could use a magical " "boost. She offers to enchant the Broach that you wear on your cloak, and you " "accept." msgstr "" "Любезна магьоÑница ÑмÑта, че защитата на твоÑта Ð°Ñ€Ð¼Ð¸Ñ Ð¼Ð¾Ð¶Ðµ да Ñе нуждае от " "магичеÑки тлаÑък. Ð¢Ñ Ð¿Ñ€ÐµÐ´Ð»Ð°Ð³Ð° да омагьоÑа Брошка, коÑто ноÑиш на наметалото " "Ñи, и ти приемаш." msgid "Battle Garb of Anduran" msgstr "Бойно облекло на Ðндуран" #, fuzzy msgid "" "The %{name} combines the powers of the three Anduran artifacts. It provides " "maximum luck and morale for the hero's troops and gives the hero the Town " "Portal spell." msgstr "" "%{name} Ñъчетава Ñилите на трите артефакта от Ðндуран. ОÑигурÑва макÑимален " "къÑмет и морал на твоите войÑки и ти дава заклинанието Портал на града." msgid "" "Out of pity for a poor peasant, you purchase a chest of old junk they are " "hawking for too much gold. Later, as you search through it, you find it " "contains the 3 pieces of the legendary battle garb of Anduran!" msgstr "" "От Ñъжаление към беден ÑелÑнин купуваш Ñандък ÑÑŠÑ Ñтари боклуци, които той " "продава за много злато. По-къÑно, докато го претърÑваш, откриваш, че Ñъдържа " "3-те чаÑти от легендарното бойно облекло на Ðндуран!" msgid "Crystal Ball" msgstr "КриÑтална топка" #, fuzzy msgid "" "The %{name} lets the hero get more specific information about monsters, " "enemy heroes, and castles nearby the hero." msgstr "" "%{name} ти позволÑва да получиш по-конкретна Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° чудовища, " "вражеÑки герои и замъци в близоÑÑ‚ до героÑ, който го държи." msgid "" "You come upon a caravan of gypsies who are feasting and fortifying their " "bodies with mead. They call you forward and say \"If you prove that you can " "dance the Rama-Buta, we will reward you.\" You don't know it, but try " "anyway. They laugh hysterically, but admire your bravery, giving you a " "Crystal Ball." msgstr "" "Попадаш на керван от цигани, които пируват и подÑилват Ñ‚Ñлото Ñи Ñ Ð¼ÐµÐ´Ð¾Ð²Ð¸Ð½Ð°. " "Извикват те напред и казват: \"Ðко докажеш, че можеш да танцуваш Рама-Бута, " "ще те възнаградим.\" Ðе го знаеш, но вÑе пак опитваш. Те Ñе ÑмеÑÑ‚ иÑтерично, " "но Ñе възхищават на ÑмелоÑтта ти, давайки ти криÑтална топка." msgid "Heart of Fire" msgstr "Огнено Ñърце" msgid "" "The %{name} provides %{count} percent protection from fire, but doubles the " "damage taken from cold." msgstr "" "%{name} оÑигурÑва %{count} процента защита от огън, но удвоÑва щетите, " "причинени от Ñтуд." msgid "" "You enter a recently burned glade and come upon a Fire Elemental sitting " "atop a rock. It looks up, its flaming face contorted in a look of severe " "pain. It then tosses a glowing object at you. You put up your hands to block " "it, but it passes right through them and sears itself into your chest." msgstr "" "Излизаш на наÑкоро опожарена полÑна и попадаш на огнен елементал, Ñеднал на " "върха на една Ñкала. Той поглежда нагоре и пламналото му лице Ñе Ñгърчва в " "израз на Ñилна болка. След това Ñ…Ð²ÑŠÑ€Ð»Ñ Ñветещ обект към теб. Вдигаш ръце, за " "да го блокираш, но той минава точно през Ñ‚ÑÑ… и Ñе врÑзва в гърдите ти." msgid "Heart of Ice" msgstr "Сърце от лед" msgid "" "The %{name} provides %{count} percent protection from cold, but doubles the " "damage taken from fire." msgstr "" "%{name} оÑигурÑва %{count} процента защита от Ñтуд, но удвоÑва щетите, " "причинени от огън." msgid "" "Suddenly, a biting coldness engulfs your body. You seize up, falling from " "your horse. The pain subsides, but you still feel as if your chest is " "frozen. As you pick yourself up off of the ground, you hear hearty laughter. " "You turn around just in time to see a Frost Giant run off into the woods and " "disappear." msgstr "" "Изведнъж хапеща Ñтуденина поглъща Ñ‚Ñлото ти. Сгърчваш Ñе, падайки от ÐºÐ¾Ð½Ñ " "Ñи. Болката отшумÑва, но вÑе още Ñе чувÑтваш ÑÑкаш гърдите ти Ñа замръзнали. " "Докато Ñе повдигаш от земÑта, чуваш Ñърдечен ÑмÑÑ…. Обръщаш Ñе точно навреме, " "за да видиш леден гигант да Ñе изгубва в гората." msgid "Helmet of Anduran" msgstr "Шлем на Ðндуран" msgid "" "You spy a gleaming object poking up out of the ground. You send a member of " "your party over to investigate. He comes back with a golden helmet in his " "hands. You realize that it must be the helmet of the legendary Anduran, the " "only man who was known to wear solid gold armor." msgstr "" "Виждаш блеÑÑ‚Ñщ обект, който Ñтърчи от земÑта. Изпращаш член на твоÑта група " "да разÑледва. Връща Ñе ÑÑŠÑ Ð·Ð»Ð°Ñ‚ÐµÐ½ шлем в ръце. Разбираш, че това трÑбва да е " "шлемът на Ð»ÐµÐ³ÐµÐ½Ð´Ð°Ñ€Ð½Ð¸Ñ Ðндуран, единÑтвениÑÑ‚ човек, за когото е извеÑтно, че " "ноÑи Ð±Ñ€Ð¾Ð½Ñ Ð¾Ñ‚ маÑивно злато." msgid "Holy Hammer" msgstr "Свещен чук" msgid "" "You come upon a battle where a Paladin has been mortally wounded by a group " "of Zombies. He asks you to take his hammer and finish what he started. As " "you pick it up, it begins to hum, and then everything becomes a blur. The " "Zombies lie dead, the hammer dripping with blood. You strap it to your belt." msgstr "" "Попадаш в битка, в коÑто паладин е бил ÑмъртоноÑно ранен от група зомбита. " "Той те моли да вземеш Ð½ÐµÐ³Ð¾Ð²Ð¸Ñ Ñ‡ÑƒÐº и да завършиш започнатото от него. Когато " "го вдигаш, той оживÑва и размазва вÑички наоколо. Зомбитата лежат мъртви, а " "от чукът капе кръв. Закачаш го на колана Ñи." msgid "Legendary Scepter" msgstr "ЛегендарниÑÑ‚ Ñкиптър" msgid "The %{name} adds %{count} points to all attributes." msgstr "%{name} Ð´Ð¾Ð±Ð°Ð²Ñ %{count} точки към вÑички атрибути." msgid "" "Upon cresting a small hill, you come upon a ridiculous looking sight. A " "Sprite is attempting to carry a Scepter that is almost as big as it is. " "Trying not to laugh, you ask, \"Need help?\" The Sprite glares at you and " "answers: \"You think this is funny? Fine. You can carry it. I much prefer " "flying anyway.\"" msgstr "" "При изкачване на малък хълм Ñе натъкваш на нелепо изглеждаща гледка. Ð¤ÐµÑ Ñе " "опитва да ноÑи Ñкиптър, който е почти толкова голÑм, колкото неÑ. Опитвайки " "Ñе да не Ñе Ñмееш, ти питаш: \"Имаш ли нужда от помощ?\" ФеÑта те гледа " "ÑдоÑано и отговарÑ: \"МиÑлиш, че това е Ñмешно? Добре. Може да го ноÑиш. И " "без това предпочитам да летÑ.\"" msgid "" "An old seaman tells you a tale of an enchanted masthead that he used in his " "youth to rally his crew during times of trouble. He then hands you a faded " "map that shows where he hid it. After much exploring, you find it stashed " "underneath a nearby dock." msgstr "" "Един Ñтар морÑк ти разказва приказка за омагьоÑана Ñфера, коÑто използвал в " "младоÑтта Ñи, за да Ñъбере екипажа Ñи по време на проблеми. След това той ти " "дава избледнÑла карта, коÑто показва къде Ñ Ðµ Ñкрил. След дълго проучване Ñ " "намирате Ñкрита под Ð±Ð»Ð¸Ð·ÐºÐ¸Ñ Ð´Ð¾Ðº." msgid "Masthead" msgstr "Мачтов връх" #, fuzzy msgid "" "The %{name} boosts the hero's troops' luck and morale by %{count} each in " "sea combat." msgstr "%{name} повишава Ñ‚Ð²Ð¾Ñ ÐºÑŠÑмет и морал Ñ %{count} вÑеки в морÑки бой." msgid "Sphere of Negation" msgstr "Сфера на отрицанието" msgid "The %{name} disables all spell casting, for both sides, in combat." msgstr "%{name} деактивира вÑички заклинаниÑ, и за двете Ñтрани, в битка." msgid "" "You stop to help a Peasant catch a runaway mare. To show his gratitude, he " "hands you a tiny sphere. As soon as you grasp it, you feel the magical " "energy drain from your limbs..." msgstr "" "Спираш, за да помогнеш на ÑелÑнин да хване избÑгала кобила. За да покаже " "ÑвоÑта благодарноÑÑ‚, той ти дава малка Ñфера. Веднага щом Ñ Ñ…Ð²Ð°Ñ‰Ð°Ñˆ, уÑещаш " "как магичеÑката ÐµÐ½ÐµÑ€Ð³Ð¸Ñ Ð·Ð°Ð¿Ð¾Ñ‡Ð²Ð° да Ñтруи от крайниците ти..." msgid "Staff of Wizardry" msgstr "ПерÑоналът на магьоÑничеÑтвото" #, fuzzy msgid "The %{name} boosts the hero's spell power by %{count}." msgstr "%{name} увеличава Ñилата на заклинаниÑта ти Ñ %{count}." msgid "" "While out scaring up game, your troops find a mysterious staff levitating " "about three feet off of the ground. They hand it to you, and you notice an " "inscription. It reads: \"Brains best brawn and magic beats might. Heed my " "words, and you'll win every fight.\"" msgstr "" "Докато плашиш дивеча, твойте войÑки откриват миÑтериозен жезъл, който " "левитира на около три фута над земÑта. Подават ти го и забелÑзваш Ð½Ð°Ð´Ð¿Ð¸Ñ Ð½Ð° " "него. Той глаÑи: \"Мозъците Ñа най-добрите муÑкули и магиÑта бие мощта. " "Ð’Ñлушай Ñе в думите ми и ще Ñпечелиш вÑÑка битка.\"" msgid "Sword Breaker" msgstr "Разбивач на мечове" #, fuzzy msgid "The %{name} increases the hero's defense by %{count} and attack by 1." msgstr "%{name} увеличава защитата ти Ñ %{count} и атаката Ñ 1." msgid "" "A former Captain of the Guard admires your quest and gives you the enchanted " "Sword Breaker that he relied on during his tour of duty." msgstr "" "Бивш капитан на гвардиÑта Ñе възхищава на твоÑта миÑÐ¸Ñ Ð¸ ти дава омагьоÑÐ°Ð½Ð¸Ñ " "Разбивач на мечове, на който е разчитал по време на Ñлужбата Ñи." msgid "Sword of Anduran" msgstr "Мечът на Ðндуран" msgid "" "A Troll stops you and says: \"Pay me 5,000 gold, or the Sword of Anduran " "will slay you where you stand.\" You refuse. The troll grabs the sword " "hanging from its belt, screams in pain, and runs away. Picking up the fabled " "sword, you give thanks that half-witted Trolls tend to grab the wrong end of " "sharp objects." msgstr "" "Трол те Ñпира и казва: \"Плати ми 5000 злато, или Мечът на Ðндуран ще те " "убие там, където Ñтоиш.\" Отказваш. Тролът грабва меча, виÑÑщ на колана му, " "крещи от болка и бÑга. Взимайки Ð»ÐµÐ³ÐµÐ½Ð´Ð°Ñ€Ð½Ð¸Ñ Ð¼ÐµÑ‡, ти благодариш, че " "малоумните тролове Ñа Ñклонни да хващат Ð³Ñ€ÐµÑˆÐ½Ð¸Ñ ÐºÑ€Ð°Ð¹ на оÑтрите предмети." msgid "Spade of Necromancy" msgstr "Пика на некромантиÑта" #, fuzzy msgid "The %{name} gives the hero increased necromancy skill." msgstr "%{name} ти дава повишено умение за некромантиÑ." msgid "" "A dirty shovel has been thrust into a dirt mound nearby. Upon investigation, " "you discover it to be the enchanted shovel of the Gravediggers, long thought " "lost by mortals." msgstr "" "МръÑна лопата е забита в земна могила наблизо. След проучване откриваш, че " "това е омагьоÑаната лопата на Гробарите, дълго ÑмÑтана за изгубена от " "Ñмъртните." msgid "spellBonus|selected by user" msgstr "" msgid "Wood" msgstr "Дърво" msgid "Mercury" msgstr "Живак" msgid "Ore" msgstr "Руда" msgid "Sulfur" msgstr "СÑра" msgid "Crystal" msgstr "КриÑтал" msgid "Gems" msgstr "Скъпоценни камъни" msgid "Gold" msgstr "Злато" msgid "" "There are seven resources in Heroes 2, used to build and improves castles, " "purchase troops and recruit heroes. Gold is the most common, required for " "virtually everything. Wood and ore are used for most buildings. Gems, " "Mercury, Sulfur and Crystal are rare magical resources used for the most " "powerful creatures and buildings." msgstr "" "Има Ñедем реÑурÑа в \"Герой 2\", използвани за изграждане и подобрÑване на " "замъци, закупуване на войÑки и набиране на герои. Златото е най-чеÑто " "Ñрещаното, необходимо за почти вÑичко. За повечето Ñгради Ñе използват дърво " "и руда. Скъпоценни камъни, живак, ÑÑра и криÑтал Ñа редки магичеÑки реÑурÑи, " "използвани за най-мощните ÑъщеÑтва и Ñгради." msgid "" "Causes a giant fireball to strike the selected area, damaging all nearby " "creatures." msgstr "" "ПричинÑва удър от гигантÑка огнена топка в избраната облаÑÑ‚, увреждайки " "вÑички близки ÑъщеÑтва." msgid "Fireball" msgstr "Огнена топка" msgid "Fireblast" msgstr "Огнена екÑплозиÑ" msgid "" "An improved version of fireball, fireblast affects two hexes around the " "center point of the spell, rather than one." msgstr "" "Подобрена верÑÐ¸Ñ Ð½Ð° огнена топка, Огнена екÑÐ¿Ð»Ð¾Ð·Ð¸Ñ Ð·Ð°ÑÑга два хекÑа около " "централната точка на заклинанието, а не един." msgid "Causes a bolt of electrical energy to strike the selected creature." msgstr "" "ПричинÑва удър на Ñтрелба от електричеÑка ÐµÐ½ÐµÑ€Ð³Ð¸Ñ Ð² избраното Ñъздание." msgid "Lightning Bolt" msgstr "Светкавица" msgid "Chain Lightning" msgstr "Верижна Ñветкавица" msgid "" "Causes a bolt of electrical energy to strike a selected creature, then " "strike the nearest creature with half damage, then strike the NEXT nearest " "creature with half again damage, and so on, until it becomes too weak to be " "harmful. Warning: This spell can hit your own creatures!" msgstr "" "ПричинÑва Ð¼ÑŠÐ»Ð½Ð¸Ñ Ð¾Ñ‚ електричеÑка ÐµÐ½ÐµÑ€Ð³Ð¸Ñ Ð² избрано ÑъщеÑтво, Ñлед това ÑƒÐ´Ð°Ñ€Ñ " "най-близкото ÑъщеÑтво Ñ Ð¿Ð¾Ð»Ð¾Ð²Ð¸Ð½ щета, Ñлед това ÑƒÐ´Ð°Ñ€Ñ Ð¡Ð›Ð•Ð”Ð’ÐЩОТО най-близко " "ÑъщеÑтво отново Ñ Ð¿Ð¾Ð»Ð¾Ð²Ð¸Ð½ щета и така нататък, докато Ñтане твърде Ñлабо, за " "да бъде вредно. Предупреждение: Това заклинание може да удари твойте " "ÑобÑтвени ÑъщеÑтва!" msgid "Teleport" msgstr "Телепорт" msgid "" "Teleports the creature you select to any open position on the battlefield." msgstr "" "Телепортира избраното от теб ÑъщеÑтво до произволна отворена Ð¿Ð¾Ð·Ð¸Ñ†Ð¸Ñ Ð½Ð° " "бойното поле." msgid "Cure" msgstr "Лек" msgid "" "Removes all negative spells cast upon one of your units, and restores up to " "%{count} HP per level of spell power." msgstr "" "Премахва вÑички негативни заклинаниÑ, хвърлени върху една от твойте единици, " "и възÑтановÑва до %{count} HP на ниво Ñила на заклинанието." msgid "Mass Cure" msgstr "Ð’Ñеобщ лек" msgid "" "Removes all negative spells cast upon your forces, and restores up to " "%{count} HP per level of spell power, per creature." msgstr "" "Премахва вÑички негативни заклинаниÑ, хвърлени върху една от твойте единици, " "и възÑтановÑва Живота до %{count} на вÑÑко ниво Ñила на заклинанието." msgid "Resurrect" msgstr "ВъзкреÑÑване" msgid "Resurrects creatures from a damaged or dead unit until end of combat." msgstr "" "ВъзкреÑÑва ÑъщеÑтва от повредена или мъртва единица до ÐºÑ€Ð°Ñ Ð½Ð° битката." msgid "Resurrect True" msgstr "ИÑтинÑко възкреÑÑване" msgid "Resurrects creatures from a damaged or dead unit permanently." msgstr "ВъзкреÑÑва ÑъщеÑтва от повредена или мъртва единица за поÑтоÑнно." msgid "Haste" msgstr "Бързина" msgid "Increases the speed of any creature by %{count}." msgstr "Увеличава ÑкороÑтта на вÑÑко Ñъздание Ñ %{count}." msgid "Increases the speed of all of your creatures by %{count}." msgstr "Увеличава ÑкороÑтта на вÑички ваши ÑъщеÑтва Ñ %{count}." msgid "Mass Haste" msgstr "Ð’Ñеобща бързина" msgid "Slows target to half movement rate." msgstr "Ð—Ð°Ð±Ð°Ð²Ñ Ñ†ÐµÐ»Ñ‚Ð° до половината ÑкороÑÑ‚ на движение." msgid "spell|Slow" msgstr "ЗабавÑне" msgid "Mass Slow" msgstr "Ð’Ñеобщо забавÑне" msgid "Slows all enemies to half movement rate." msgstr "Ð—Ð°Ð±Ð°Ð²Ñ Ð²Ñички врагове до половината ÑкороÑÑ‚ на движение." msgid "Clouds the affected creatures' eyes, preventing them from moving." msgstr "ЗамъглÑва очите на заÑегнатите ÑъщеÑтва, като им пречи да Ñе движат." msgid "spell|Blind" msgstr "Слепота" msgid "Bless" msgstr "БлагоÑловиÑ" msgid "Causes the selected creatures to inflict maximum damage." msgstr "Кара избраните ÑъщеÑтва да нанаÑÑÑ‚ макÑимални щети." msgid "Causes all of your units to inflict maximum damage." msgstr "Кара вÑичките ти единици да нанаÑÑÑ‚ макÑимални щети." msgid "Mass Bless" msgstr "Ð’Ñеобща Ñлепота" msgid "Magically increases the defense skill of the selected creatures." msgstr "МагичеÑки увеличава защитните ÑƒÐ¼ÐµÐ½Ð¸Ñ Ð½Ð° избраните ÑъщеÑтва." msgid "Stoneskin" msgstr "Каменна кожа" msgid "" "Increases the defense skill of the targeted creatures. This is an improved " "version of Stoneskin." msgstr "" "Увеличава защитните ÑƒÐ¼ÐµÐ½Ð¸Ñ Ð½Ð° набелÑзаните ÑъщеÑтва. Това е подобрена верÑÐ¸Ñ " "на Каменна кожа." msgid "Steelskin" msgstr "Стоманена кожа" msgid "Causes the selected creatures to inflict minimum damage." msgstr "Кара избраните ÑъщеÑтва да нанаÑÑÑ‚ минимални щети." msgid "Curse" msgstr "ПроклÑтие" msgid "Causes all enemy troops to inflict minimum damage." msgstr "Кара вÑички вражеÑки войÑки да нанаÑÑÑ‚ минимални щети." msgid "Mass Curse" msgstr "Ð’Ñеобщо проклÑтие" msgid "Damages all undead in the battle." msgstr "ÐанаÑÑ Ñ‰ÐµÑ‚Ð¸ на вÑички немъртви в битката." msgid "Holy Word" msgstr "СвÑто Слово" msgid "" "Damages all undead in the battle. This is an improved version of Holy Word." msgstr "" "ÐанаÑÑ Ñ‰ÐµÑ‚Ð¸ на вÑички немъртви в битката. Това е подобрена верÑÐ¸Ñ Ð½Ð° СвÑто " "Слово." msgid "Holy Shout" msgstr "Свещен вик" msgid "Anti-Magic" msgstr "ÐнтимагиÑ" msgid "Prevents harmful magic against the selected creatures." msgstr "ПредотвратÑва вредната Ð¼Ð°Ð³Ð¸Ñ Ñрещу избраните ÑъщеÑтва." msgid "Dispel Magic" msgstr "Премахване на магиÑ" msgid "Removes all magic spells from a single target." msgstr "Премахва вÑички магичеÑки Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ Ð¾Ñ‚ една цел." msgid "Mass Dispel" msgstr "Ð’Ñеобщо премахване на магиÑ" msgid "Removes all magic spells from all creatures." msgstr "Премахва вÑички магичеÑки Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ Ð¾Ñ‚ вÑички ÑъщеÑтва." msgid "Causes a magic arrow to strike the selected target." msgstr "Кара магичеÑка Ñтрела да удари избраната цел." msgid "Magic Arrow" msgstr "МагичеÑка Ñтрела" msgid "Berserker" msgstr "БерÑерк" msgid "Causes a creature to attack its nearest neighbor." msgstr "Кара Ñъздание да атакува най-Ð±Ð»Ð¸Ð·ÐºÐ¸Ñ Ñи ÑÑŠÑед." msgid "Armageddon" msgstr "Ðрмагедон" msgid "" "Holy terror strikes the battlefield, causing severe damage to all creatures." msgstr "" "СвещениÑÑ‚ ÑƒÐ¶Ð°Ñ Ð²Ñ€ÑŠÑ…Ð»Ð¸Ñ‚Ð° бойното поле, причинÑвайки Ñериозни щети на вÑички " "ÑъщеÑтва." msgid "Elemental Storm" msgstr "Стихийна бурÑ" msgid "Magical elements pour down on the battlefield, damaging all creatures." msgstr "" "МагичеÑки елементи Ñе изÑипват на бойното поле, увреждайки вÑички ÑъщеÑтва." msgid "" "A rain of rocks strikes an area of the battlefield, damaging all nearby " "creatures." msgstr "" "Дъжд от камъни ÑƒÐ´Ñ€Ñ Ð·Ð¾Ð½Ð° на бойното поле, увреждайки вÑички близки ÑъщеÑтва." msgid "Meteor Shower" msgstr "Метеоритен дъжд" msgid "Paralyze" msgstr "Парализиране" msgid "The targeted creatures are paralyzed, unable to move or retaliate." msgstr "" "ÐабелÑзаните ÑъщеÑтва Ñа парализирани, не могат да Ñе движат или да отмъÑÑ‚ÑÑ‚." msgid "Hypnotize" msgstr "Хипнотизиране" msgid "" "Brings a single enemy unit under your control if its hits are less than " "%{count} times the caster's spell power." msgstr "" "ПоÑÑ‚Ð°Ð²Ñ Ð¿Ð¾Ð´ твой контрол една вражеÑка единица, ако нейните удари Ñа по-" "малко от %{count} пъти Ñилата на заклинанието на заклинателÑ." msgid "Cold Ray" msgstr "Студен лъч" msgid "Drains body heat from a single enemy unit." msgstr "Изцежда телеÑната топлина от една вражеÑка единица." msgid "Cold Ring" msgstr "Студен пръÑтен" msgid "" "Drains body heat from all units surrounding the center point, but not " "including the center point." msgstr "" "Източва топлината на Ñ‚Ñлото от вÑички единици, заобикалÑщи централната " "точка, но без да включва централната точка." msgid "Disrupting Ray" msgstr "Разрушаващ лъч" msgid "Reduces the defense rating of an enemy unit by three." msgstr "ÐамалÑва рейтинга на отбраната на вражеÑка единица Ñ Ñ‚Ñ€Ð¸." msgid "Damages all living (non-undead) units in the battle." msgstr "ÐанаÑÑ Ñ‰ÐµÑ‚Ð¸ на вÑички живи (не-мъртви) единици в битката." msgid "Death Ripple" msgstr "СмъртоноÑна вълна" msgid "Death Wave" msgstr "Вълна на Ñмъртта" msgid "" "Damages all living (non-undead) units in the battle. This spell is an " "improved version of Death Ripple." msgstr "" "ÐанаÑÑ Ñ‰ÐµÑ‚Ð¸ на вÑички живи (не-мъртви) единици в битката. Това заклинание е " "подобрена верÑÐ¸Ñ Ð½Ð° СмъртоноÑна вълна." msgid "Dragon Slayer" msgstr "Убиец на дракони" msgid "Greatly increases a unit's attack skill vs. Dragons." msgstr "Увеличава значително умението за атака на единица Ñрещу дракони." msgid "Blood Lust" msgstr "КръвожадноÑÑ‚" msgid "Increases a unit's attack skill." msgstr "Увеличава умението за атака на единица." msgid "Animate Dead" msgstr "Ðнимирана Ñмърт" msgid "Resurrects creatures from a damaged or dead undead unit permanently." msgstr "" "ВъзкреÑÑва за поÑтоÑнно ÑъщеÑтва от повредена или мъртва немъртва единица." msgid "Mirror Image" msgstr "Огледална картина" msgid "" "Creates an illusionary unit that duplicates one of your existing units. This " "illusionary unit does the same damages as the original, but will vanish if " "it takes any damage." msgstr "" "Създава илюзорна единица, коÑто дублира една от твойте ÑъщеÑтвуващи единици. " "Тази илюзорна единица нанаÑÑ Ñъщите щети като оригинала, но ще изчезне, ако " "понеÑе нÑкакви щети." msgid "Shield" msgstr "Щит" msgid "" "Halves damage received from ranged attacks for a single unit. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "ÐамалÑва наполовина щетите, получени от далечни атаки за една единица. Ðе " "заÑÑга щетите, получени от кули или балиÑти." msgid "Mass Shield" msgstr "Ð’Ñеобщ щит" msgid "" "Halves damage received from ranged attacks for all of your units. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "ÐамалÑва наполовина щетите, получени от далечни атаки за вÑички твой " "единици. Ðе заÑÑга щетите, получени от кули или балиÑти." msgid "Summon Earth Elemental" msgstr "Призови земен елементал" msgid "Summons Earth Elementals to fight for your army." msgstr "Призовава земните елементали да Ñе биÑÑ‚ за твоÑта армиÑ." msgid "Summon Air Elemental" msgstr "Призови въздушен елементал" msgid "Summons Air Elementals to fight for your army." msgstr "Призовава въздушни елементали да Ñе биÑÑ‚ за твоÑта армиÑ." msgid "Summon Fire Elemental" msgstr "Призови огнен елементал" msgid "Summons Fire Elementals to fight for your army." msgstr "Призовава огнени елементали да Ñе биÑÑ‚ за твоÑта армиÑ." msgid "Summon Water Elemental" msgstr "Призови воден елементал" msgid "Summons Water Elementals to fight for your army." msgstr "Призовава водни елементали да Ñе биÑÑ‚ за твоÑта армиÑ." msgid "Damages castle walls." msgstr "Поврежда Ñтените на замъка." msgid "Earthquake" msgstr "ЗеметреÑение" msgid "Causes all mines across the land to become visible." msgstr "Кара вÑички мини по земÑта да Ñтанат видими." msgid "View Mines" msgstr "Виж мините" msgid "Causes all resources across the land to become visible." msgstr "Кара вÑички реÑурÑи в земÑта да Ñтанат видими." msgid "View Resources" msgstr "Виж реÑурÑите" msgid "Causes all artifacts across the land to become visible." msgstr "Кара вÑички артефакти по земÑта да Ñтанат видими." msgid "View Artifacts" msgstr "Виж артефактите" msgid "Causes all towns and castles across the land to become visible." msgstr "Кара вÑички градове и замъци по земÑта да Ñтанат видими." msgid "View Towns" msgstr "Виж градовете" msgid "Causes all Heroes across the land to become visible." msgstr "Кара вÑички герои в цÑлата Ñтрана да Ñтанат видими." msgid "View Heroes" msgstr "Виж героите" msgid "Causes the entire land to become visible." msgstr "Кара цÑлата Ð·ÐµÐ¼Ñ Ð´Ð° Ñтане видима." msgid "View All" msgstr "Виж вÑичко" msgid "Allows the caster to view detailed information on enemy Heroes." msgstr "" "ПозволÑва на Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ñ‚ÐµÐ»Ñ Ð´Ð° вижда подробна Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð·Ð° вражеÑките герои." msgid "Summon Boat" msgstr "Призови кораб" msgid "" "Summons the nearest unoccupied, friendly boat to an adjacent shore location. " "A friendly boat is one which you just built or were the most recent player " "to occupy." msgstr "" "Извиква най-близката незаета, приÑтелÑка лодка до ÑÑŠÑедно мÑÑто на брега. " "ПриÑтелÑка лодка е тази, коÑто току-що Ñи поÑтроил или Ñи бил поÑледниÑÑ‚ " "играч, който Ñ Ðµ ползвал." msgid "Allows the caster to magically transport to a nearby location." msgstr "ПозволÑва на Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ñ‚ÐµÐ»Ñ Ð¼Ð°Ð³Ð¸Ñ‡ÐµÑки да Ñе транÑпортира до близко мÑÑто." msgid "Dimension Door" msgstr "Врата между измерениÑта" msgid "Returns the caster to any town or castle currently owned." msgstr "Връща Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ñ‚ÐµÐ»Ñ Ð²ÑŠÐ² вÑеки град или замък, притежаван в момента." msgid "Town Gate" msgstr "ГрадÑка порта" msgid "" "Returns the hero to the town or castle of choice, provided it is controlled " "by you." msgstr "" "Връща Ð³ÐµÑ€Ð¾Ñ Ð² град или замък по избор, при уÑловие че е контролиран от теб." msgid "Visions" msgstr "ВидениÑ" msgid "" "Visions predicts the likely outcome of an encounter with a neutral army camp." msgstr "" "ВидениÑта предÑказват вероÑÑ‚Ð½Ð¸Ñ Ñ€ÐµÐ·ÑƒÐ»Ñ‚Ð°Ñ‚ от Ñреща Ñ Ð½ÐµÑƒÑ‚Ñ€Ð°Ð»ÐµÐ½ армейÑки лагер." msgid "Haunt" msgstr "Обладаване" msgid "" "Haunts a mine you control with Ghosts. This mine stops producing resources. " "(If I can't keep it, nobody will!)" msgstr "" "Обладава мина, коÑто контролирате Ñ Ð¿Ñ€Ð¸Ð·Ñ€Ð°Ñ†Ð¸. Тази мина Ñпира да произвежда " "реÑурÑи. (Ðко аз не мога да го запазÑ, никой нÑма да го направи!)" msgid "Set Earth Guardian" msgstr "Задай Земен пазител" msgid "Sets Earth Elementals to guard a mine against enemy armies." msgstr "Кара земните елементали да пазÑÑ‚ мина Ñрещу вражеÑки армии." msgid "Set Air Guardian" msgstr "Задай Въздушен паззител" msgid "Sets Air Elementals to guard a mine against enemy armies." msgstr "Кара Въздушни елементали да пазÑÑ‚ мина Ñрещу вражеÑки армии." msgid "Set Fire Guardian" msgstr "Задай Огнен пазител" msgid "Sets Fire Elementals to guard a mine against enemy armies." msgstr "Кара Огнени елементали да пазÑÑ‚ мина Ñрещу вражеÑки армии." msgid "Set Water Guardian" msgstr "Задай Воден пазител" msgid "Sets Water Elementals to guard a mine against enemy armies." msgstr "Кара Водни елементали да пазÑÑ‚ мина Ñрещу вражеÑки армии" #, fuzzy msgid "Random Spell" msgstr "Случаен" msgid "Randomly selected spell of any level." msgstr "" #, fuzzy msgid "Random 1st Level Spell" msgstr "Случаен замък" msgid "Randomly selected 1st level spell." msgstr "" msgid "Random 2nd Level Spell" msgstr "" msgid "Randomly selected 2nd level spell." msgstr "" msgid "Random 3rd Level Spell" msgstr "" msgid "Randomly selected 3rd level spell." msgstr "" msgid "Random 4th Level Spell" msgstr "" msgid "Randomly selected 4th level spell." msgstr "" msgid "Random 5th Level Spell" msgstr "" msgid "Randomly selected 5th level spell." msgstr "" msgid "Petrification" msgstr "ВкаменÑване" msgid "" "Turns the affected creature into stone. A petrified creature receives half " "damage from a direct attack." msgstr "" "Превръща заÑегнатото ÑъщеÑтво в камък. Вкаменено ÑъщеÑтво получава " "половината щети от директна атака." msgid "You have no Magic Book, so you cannot cast a spell." msgstr "ÐÑмаш магичеÑка книга, така че не можеш да направиш магиÑ." msgid "No spell to cast." msgstr "ÐÑма Ð¼Ð°Ð³Ð¸Ñ Ð·Ð° правене." msgid "Your hero has %{point} spell points remaining." msgstr "ТвоÑÑ‚ герой има %{point} оÑтаващи точки магиÑ." msgid "View Adventure Spells" msgstr "Виж приключенÑки магии" msgid "View Combat Spells" msgstr "Виж бойни магии" msgid "View previous page" msgstr "Виж предишната Ñтраница" msgid "View next page" msgstr "Виж Ñледващата Ñтраница" msgid "Close Spellbook" msgstr "Затвори книгата Ñ Ð¼Ð°Ð³Ð¸Ð¹" msgid "View %{spell}" msgstr "Виж %{spell}" msgid "This spell does %{damage} points of damage." msgstr "Това заклинание нанаÑÑ %{damage} точки щети." msgid "" "This spell summons\n" "%{count} %{monster}." msgstr "" "Това заклинание призовава\n" "%{count} %{monster}." msgid "This spell restores %{hp} HP." msgstr "Това заклинание възÑтановÑва %{hp} живот." msgid "This spell summons %{count} %{monster} to guard the mine." msgstr "Това заклинание призовава %{count} %{monster} да пазÑÑ‚ мината." msgid "The nearest town is %{town}." msgstr "Ðай-близкиÑÑ‚ град е %{town}." msgid "This town is occupied by your hero %{hero}." msgstr "Този град е окупиран от Ñ‚Ð²Ð¾Ñ Ð³ÐµÑ€Ð¾Ð¹ %{hero}." msgid "" "This spell controls up to\n" "%{hp} HP." msgstr "" "Това заклинание контролира до\n" "%{hp} живот." msgid "The ultimate artifact is really the %{name}." msgstr "ВърховниÑÑ‚ артефакт наиÑтина е %{name}." msgid "The ultimate artifact may be found in the %{name} regions of the world." msgstr "ВърховниÑÑ‚ артефакт може да бъде намерен в %{name} региони на Ñвета." msgid "north-west" msgstr "Ñеверозапад" msgid "north" msgstr "Ñевер" msgid "north-east" msgstr "Ñевероизток" msgid "west" msgstr "запад" msgid "center" msgstr "център" msgid "east" msgstr "изток" msgid "south-west" msgstr "югозапад" msgid "south" msgstr "юг" msgid "south-east" msgstr "югоизток" msgid "The truth is out there." msgstr "ИÑтината е нÑкъде там." msgid "The dark side is stronger." msgstr "Тъмната Ñтрана е по-Ñилна." msgid "The end of the world is near." msgstr "КраÑÑ‚ на Ñвета е близо." msgid "The bones of Lord Slayer are buried in the foundation of the arena." msgstr "КоÑтите на Лорд Убиец Ñа погребани в оÑновата на арената." msgid "A Black Dragon will take out a Titan any day of the week." msgstr "Черен дракон ще унищожава титан вÑеки ден от Ñедмицата." msgid "He told her: Yada yada yada... and then she said: Blah, blah, blah..." msgstr "Той й каза: \"Яда, блÑ, дÑ...\" и тогава Ñ‚Ñ ÐºÐ°Ð·Ð°: \"Бла, бла, бла..." msgid "An unknown force is being resurrected..." msgstr "Ðепозната Ñила възкръÑва..." msgid "" "Check the newest version of the game at\n" "https://github.com/ihhub/\n" "fheroes2/releases" msgstr "" "Проверете най-новата верÑÐ¸Ñ Ð½Ð° играта на\n" "https://github.com/ihhub/\n" "fheroes2/releases" #~ msgid "Terrain" #~ msgstr "Терен" #~ msgid "Castles" #~ msgstr "Замъци" #, fuzzy #~ msgid "Ocean object" #~ msgstr "ОкеанÑки обекти" #~ msgid "Ocean Objects" #~ msgstr "ОкеанÑки обекти" #~ msgid "Grass Objects" #~ msgstr "Тревни обекти" #~ msgid "Snow Objects" #~ msgstr "Снежни обекти" #~ msgid "Swamp Objects" #~ msgstr "Блатни обекти" #~ msgid "Lava Objects" #~ msgstr "Лава обекти" #~ msgid "Desert Objects" #~ msgstr "ПуÑтинни обекти" #~ msgid "Dirt Objects" #~ msgstr "Земни обекти" #~ msgid "Beach Objects" #~ msgstr "Плажни обекти" #~ msgid "Used to place objects most appropriate for use on %{terrain}." #~ msgstr "" #~ "Използва Ñе за поÑтавÑне на обекти, които Ñа най-подходÑщи за използване " #~ "на %{terrain}." #~ msgid "" #~ "Used to place\n" #~ "a town or castle." #~ msgstr "" #~ "Използва Ñе за поÑтавÑне\n" #~ "на град или замък." #~ msgid "Used to place an artifact." #~ msgstr "Използва Ñе за поÑтавÑне на артефакт." #~ msgid "" #~ "Used to place\n" #~ "a resource or treasure." #~ msgstr "" #~ "Използва Ñе за поÑтавÑне\n" #~ "на реÑÑƒÑ€Ñ Ð¸Ð»Ð¸ Ñъкровище." #~ msgid "%{race1} %{name1}" #~ msgstr "%{race1} %{name1}" #~ msgid "vs" #~ msgstr "Ñрещу" #~ msgid "%{race2} %{name2}" #~ msgstr "%{race2} %{name2}" fheroes2-1.0.12+dfsg/files/lang/cs.po000066400000000000000000007305601456075706000172440ustar00rootroot00000000000000# Czech translation for fheroes2 # Copyright (C) 2022 fheroes2 team # This file is distributed under the same license as the fheroes2 package. # msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-03 03:27+0000\n" "PO-Revision-Date: 2023-09-24 16:05+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: Czech \n" "Language: cs\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" "X-Launchpad-Export-Date: 2010-05-27 09:12+0000\n" "X-Generator: Poedit 3.3.2\n" msgid "" "BATTLE\n" "ONLY" msgstr "" "VOLNÃ\n" "BOJ" msgid "" "NEW\n" "GAME" msgstr "" "NOVÃ\n" "HRA" msgid "" "SAVE\n" "GAME" msgstr "" "ULOŽIT\n" "HRU" msgid "" "LOAD\n" "GAME" msgstr "" "NAHRÃT\n" "HRU" msgid "INFO" msgstr "INFO" msgid "QUIT" msgstr "KONEC" msgid "CANCEL" msgstr "ZRUÅ IT" msgid "OKAY" msgstr "OKAY" msgid "smallerButton|OKAY" msgstr "OK" msgid "ACCEPT" msgstr "PŘIJMI" msgid "DECLINE" msgstr "ZRUÅ IT" msgid "LEARN" msgstr "UÄŒIT" msgid "TRADE" msgstr "ZAMĚŇ" msgid "YES" msgstr "ANO" msgid "NO" msgstr "NE" msgid "EXIT" msgstr "PRYÄŒ" msgid "smallerButton|EXIT" msgstr "VEN" msgid "DISMISS" msgstr "PROPUSŤ" msgid "UPGRADE" msgstr "VYLEPÅ I" msgid "RESTART" msgstr "RESTART" msgid "HEROES" msgstr "HRDINOVÉ" msgid "" "TOWNS/\n" "CASTLES" msgstr "" "MÄšSTA/\n" "HRADY" msgid "S" msgstr "S" msgid "M" msgstr "M" msgid "L" msgstr "L" msgid "X-L" msgstr "X-L" msgid "ALL" msgstr "VÅ E" msgid "SELECT" msgstr "ZVOLIT" msgid "" "STANDARD\n" "GAME" msgstr "" "STANDARD\n" "HRA" msgid "" "CAMPAIGN\n" "GAME" msgstr "" "HRÃT\n" "KAMPAŇ" msgid "" "MULTI-\n" "PLAYER\n" "GAME" msgstr "" "MULTI-\n" "PLAYER\n" "HRA" msgid "CONFIG" msgstr "CONFIG" msgid "" "ORIGINAL\n" "CAMPAIGN" msgstr "" "PUVODNÃ\n" "KAMPAŇ" msgid "" "EXPANSION\n" "CAMPAIGN" msgstr "" "DATADISK\n" "KAMPAŇ" msgid "HOT SEAT" msgstr "HORKÉ KŘESLO" msgid "2 PLAYERS" msgstr "2 HRÃÄŒI" msgid "3 PLAYERS" msgstr "3 HRÃÄŒI" msgid "4 PLAYERS" msgstr "4 HRÃÄŒI" msgid "5 PLAYERS" msgstr "5 HRÃČŮ" msgid "6 PLAYERS" msgstr "6 HRÃČŮ" msgid "GIFT" msgstr "DARUJ" msgid "MAX" msgstr "MAX" msgid "DIFFICULTY" msgstr "OBTÃŽNOST" msgid "VIEW INTRO" msgstr "ZOBRAZIT ÚVOD" msgid "MIN" msgstr "MIN" msgid "RESET" msgstr "" #, fuzzy msgid "START" msgstr "RESTART" #, fuzzy msgid "CASTLE" msgstr "" "MÄšSTA/\n" "HRADY" msgid "TOWN" msgstr "" msgid "" "D\n" "I\n" "S\n" "M\n" "I\n" "S\n" "S" msgstr "" "P\n" "R\n" "O\n" "P\n" "U\n" "S\n" "Ť" msgid "" "E\n" "X\n" "I\n" "T" msgstr "" "O\n" "D\n" "E\n" "J\n" "Ã\n" "T" msgid "" "C\n" "A\n" "M\n" "P\n" "A\n" "I\n" "G\n" "N" msgstr "" "K\n" "A\n" "M\n" "P\n" "A\n" "Ň" msgid "" "S\n" "T\n" "A\n" "N\n" "D\n" "A\n" "R\n" "D" msgstr "" "S\n" "T\n" "A\n" "N\n" "D\n" "A\n" "R\n" "D" msgid "Warrior" msgstr "VáleÄník" msgid "Builder" msgstr "Stavitel" msgid "Explorer" msgstr "Průzkumník" msgid "None" msgstr "Nic" msgid "" "A few\n" "%{monster}" msgstr "" "Málo\n" "%{monster}" msgid "" "Several\n" "%{monster}" msgstr "" "NÄ›kolik\n" "%{monster}" msgid "" "A pack of\n" "%{monster}" msgstr "" "Banda\n" "%{monster}" msgid "" "Lots of\n" "%{monster}" msgstr "" "Mnoho\n" "%{monster}" msgid "" "A horde of\n" "%{monster}" msgstr "" "Horda\n" "%{monster}" msgid "" "A throng of\n" "%{monster}" msgstr "" "Zástup\n" "%{monster}" msgid "" "A swarm of\n" "%{monster}" msgstr "" "Dav\n" "%{monster}" msgid "" "Zounds...\n" "%{monster}" msgstr "" "MraÄna\n" "%{monster}" msgid "" "A legion of\n" "%{monster}" msgstr "" "Legie\n" "%{monster}" msgid "army|Few" msgstr "Málo" msgid "army|Several" msgstr "NÄ›kolik" msgid "army|Pack" msgstr "Banda" msgid "army|Lots" msgstr "Mnoho" msgid "army|Horde" msgstr "Horda" msgid "army|Throng" msgstr "Zástup" msgid "army|Swarm" msgstr "Dav" msgid "army|Zounds" msgstr "MraÄna" msgid "army|Legion" msgstr "Legie" msgid "All %{race} troops +1" msgstr "VÅ¡echny %{race} jednotky +1" msgid "Multiple" msgstr "Vícero" msgid "Troops of %{count} alignments -%{penalty}" msgstr "" msgid "Some undead in army -1" msgstr "" msgid "View %{name}" msgstr "Zobrazit %{name}" msgid "Move the %{name} " msgstr "PÅ™esunout %{name}" msgid "Move or right click to redistribute %{name}" msgstr "PÅ™esuň nebo pravým klikem pro pÅ™eskup %{name}" msgid "Combine %{name} armies" msgstr "SlouÄit vojska %{name}" msgid "Exchange %{name2} with %{name}" msgstr "ZamÄ›nit %{name2} s %{name}" msgid "Select %{name}" msgstr "Vybrat %{name}" msgid "Cannot move last troop" msgstr "Poslední jednotku nelze pÅ™esunout" msgid "Move the %{name}" msgstr "PÅ™esunout %{name}" msgid "Set Count" msgstr "Zadat poÄet" msgid "%{name} destroys half the enemy troops!" msgid_plural "%{name} destroy half the enemy troops!" msgstr[0] "%{name} niÄí polovinu nepřátelských jednotek!" msgstr[1] "%{name} niÄí polovinu nepřátelských jednotek!" msgstr[2] "%{name} niÄí polovinu nepřátelských jednotek!" msgid "%{name} has turned off the auto battle" msgstr "%{name} vypnul automatickou bitvu" msgid "%{name} has turned on the auto battle" msgstr "%{name} zapnul automatickou bitvu" msgid "Spell failed!" msgstr "Kouzlo selhalo!" msgid "" "The Sphere of Negation artifact is in effect for this battle, disabling all " "combat spells." msgstr "Artefakt Sféra Negace znemožňuje v této bitvÄ› použít jakékoliv kouzla." msgid "You have already cast a spell this round." msgstr "V tomto kole si již kouzlil." msgid "That spell will have no effect!" msgstr "Toto kouzlo nebude na nikoho uÄinkovat!" msgid "You may only summon one type of elemental per combat." msgstr "V této bitvÄ› můžeÅ¡ pÅ™ivolat pouze jeden druh elementálů." msgid "" "There is no open space adjacent to your hero where you can summon an " "Elemental to." msgstr "" "Vedle vaÅ¡eho hrdiny není žádné volné místo, kam by se dal Elementál pÅ™ivolat." msgid "" "The Moat reduces by -%{count} the defense skill of any unit and slows to " "half movement rate." msgstr "" "Příkop redukuje obrannou sílu útoÄících jednotek o -%{count} a zpomaluje o " "polovinu jejich perimetr pohybu." msgid "Speed: %{speed}" msgstr "Rychlost: %{speed}" msgid "Speed" msgstr "Rychlost" msgid "Off" msgstr "Vyp" msgid "On" msgstr "Zap" msgid "Turn Order" msgstr "PoÅ™adí tahu" msgid "Auto Spell Casting" msgstr "Automické Kouzlení" msgid "Grid" msgstr "Mřížka" msgid "Shadow Movement" msgstr "Stín pohybu" msgid "Shadow Cursor" msgstr "Stín Kurzoru" msgid "Audio" msgstr "Audio" msgid "Settings" msgstr "Nastavení" msgid "Configure" msgstr "Konfigurovat" msgid "Hot Keys" msgstr "Kláv. zkratky" msgid "Damage Info" msgstr "Å kody Info" msgid "Set the speed of combat actions and animations." msgstr "Nastavit rychlost akcí a animací v bitvÄ›." msgid "Toggle to display the turn order during the battle." msgstr "Zap/Vyp zobrazení poÅ™adí tahu jednotek v bitvÄ›." msgid "" "Toggle whether or not the computer will cast spells for you when auto combat " "is on. (Note: This does not affect spell casting for computer players in any " "way, nor does it affect quick combat.)" msgstr "" "PÅ™epni, zda za bude poÄítaÄ kouzlit, když je zapnutý automatický boj. " "(Pozn.: Toto nijak neovlivňuje sesílání kouzel pro poÄítaÄem ovládané hráÄe, " "ani rychlý boj.)" msgid "" "Toggle the hex grid on or off. The hex grid always underlies movement, even " "if turned off. This switch only determines if the grid is visible." msgstr "" "Zap/Vyp hexagonální mřížku. Mřížka vždy ohraniÄuje pohyb i když je vypnutá. " "Tento pÅ™epínaÄ pouze ovládá, zda bude mřížka viditelná." msgid "" "Toggle on or off shadows showing where your creatures can move and attack." msgstr "Za/Vyp stín možného pohybu resp. útoku tvých jednotek." msgid "" "Toggle on or off a shadow showing the current hex location of the mouse " "cursor." msgstr "Zap/Vyp stín aktuální pozice kurzoru myÅ¡i na mřížce." msgid "Change the audio settings of the game." msgstr "ZmÄ›nit nastavení zvuku." msgid "Check and configure all the hot keys present in the game." msgstr "" msgid "Toggle to display damage information during the battle." msgstr "" msgid "Exit this menu." msgstr "Odejít z menu." msgid "Okay" msgstr "OK" msgid "The enemy has surrendered!" msgstr "Nepřítel se Ti vzdal!" msgid "Their cowardice costs them %{gold} gold." msgstr "" msgid "The enemy has fled!" msgstr "Nepřítel prchnul!" msgid "A glorious victory!" msgstr "Slavné vítÄ›zství!" msgid "For valor in combat, %{name} receives %{exp} experience." msgstr "Za stateÄnost v boji získává %{name} %{exp} zkuÅ¡eností." msgid "The cowardly %{name} flees from battle." msgstr "%{name} zbabÄ›le utíká z bitvy." msgid "%{name} surrenders to the enemy, and departs in shame." msgstr "%{name} se vzdal nepříteli a odchází v hanbÄ›." #, fuzzy msgid "Your forces suffer a bitter defeat, and %{name} abandons your cause." msgstr "Tvá armáda utrpÄ›la hoÅ™kou porážku a %{name} tvé Å™ady opouÅ¡tí." #, fuzzy msgid "Your forces suffer a bitter defeat." msgstr "Tvá armáda utrpÄ›la hoÅ™kou porážku." msgid "Battlefield Casualties" msgstr "Ztráty na bojiÅ¡ti" msgid "Attacker" msgstr "ÚtoÄník" msgid "Defender" msgstr "Obránce" #, fuzzy msgid "Click to leave the battle results." msgstr "Kliknutím se rozliÅ›ení zmÄ›ní na vybrané. " #, fuzzy msgid "Click to restart the battle in manual mode." msgstr "Klikni pro návrat do hlavního menu." msgid "Restart" msgstr "" msgid "You have captured an enemy artifact!" msgstr "UkoÅ™istil si artefakt nepřítele!" msgid "As you reach for the %{name}, it mysteriously disappears." msgstr "Když saháš po %{name}, tak záhadnÄ› zmizí." msgid "As your enemy reaches for the %{name}, it mysteriously disappears." msgstr "Když protivník sahá po %{name}, tak záhadnÄ› zmizí." msgid "Necromancy!" msgstr "Nekromantie!" msgid "" "Practicing the dark arts of necromancy, you are able to raise %{count} of " "the enemy's dead to return under your service as %{monster}." msgstr "" "Nácvikem temného umÄ›ní nekromantie si schopen oživit až %{count} usmrcených " "nepřátelských jednotek a povolat je do služby jako %{monster}." msgid "%{name} the %{race}" msgstr "%{name} z rodu %{race}" msgid "Captain of %{name}" msgstr "Velitel %{name}" msgid "Attack" msgstr "Útok" msgid "Defense" msgstr "Obrana" msgid "Spell Power" msgstr "Síla kouzel" msgid "Knowledge" msgstr "Znalosti" msgid "Morale" msgstr "Morálka" msgid "Luck" msgstr "Å tÄ›stí" msgid "Spell Points" msgstr "Body Kouzla" msgid "Hero's Options" msgstr "Hrdina Možnosti" msgid "Cast Spell" msgstr "Seslat kouzlo" msgid "Retreat" msgstr "Ústup" msgid "Surrender" msgstr "Vzdát se" msgid "Cancel" msgstr "ZruÅ¡it" msgid "Hero Screen" msgstr "Obrazovka hrdiny" msgid "Captain's Options" msgstr "Možnosti velení" msgid "" "Cast a magical spell. You may only cast one spell per combat round. The " "round is reset when every creature has had a turn." msgstr "" "Seslat magické kouzlo. MůžeÅ¡ použít jenom jednou za kolo. Nové kolo zaÄne až " "když vÅ¡echna stvoÅ™ení provedou svůj tah." msgid "" "Retreat your hero, abandoning your creatures. Your hero will be available " "for you to recruit again, however, the hero will have only a novice hero's " "forces." msgstr "" "Hrdina ustoupí a opustí své bojovníky. Hrdina bude dostupný pro opÄ›tovné " "najmutí. Hrdina bude mít pouze minimální armádu." #, fuzzy msgid "" "Surrendering costs gold. However if you pay the ransom, the hero and all of " "his or her surviving creatures will be available to recruit again. The cost " "of surrender is half of the total cost of the non-temporary troops remaining " "in the army." msgstr "" "Kapitulace stojí zlato. Pokud ale zaplatíš výkupné, hrdina a celá pÅ™eživší " "armáda bude k dispozici k opÄ›tovnému najmutí." msgid "Open Hero Screen to view full information about the hero." msgstr "Pro zobrazení kompletní informace a hrdinovi otevÅ™i Obrazovku Hrdniny." msgid "Return to the battle." msgstr "ZpÄ›t do bitvy." msgid "Not enough gold (%{gold})" msgstr "Nemáš dost zlaťáků (%{gold})" msgid "%{name} states:" msgstr "%{name} říká:" msgid "Captain of %{name} states:" msgstr "Velitel %{name} říká:" msgid "" "\"I will accept your surrender and grant you and your troops safe passage " "for the price of %{price} gold.\"" msgstr "" "\"Příjmu tvou kapitulaci a umožním tobÄ› a tvým jednotkám bezpeÄný průchod za " "cenu %{price} zlaťáků.\"" msgid "View %{monster} info" msgstr "Zobraz info o %{monster}" msgid "Fly %{monster} here" msgstr "PÅ™eleÅ¥ s %{monster} sem" msgid "Move %{monster} here" msgstr "Projdi s %{monster} sem" msgid "Shoot %{monster}" msgstr "VystÅ™el na %{monster}" msgid "(1 shot left)" msgid_plural "(%{count} shots left)" msgstr[0] "(1 výstÅ™el zbývá)" msgstr[1] "(%{count} výstÅ™ely zbývají)" msgstr[2] "(%{count} výstÅ™elů zbývá)" msgid "Attack %{monster}" msgstr "ZaútoÄ na %{monster}" msgid "Turn %{turn}" msgstr "Pohnout %{turn}" msgid "Teleport here" msgstr "Teleportovat sem" msgid "Invalid teleport destination" msgstr "Nesprávné místo teleportace" msgid "Cast %{spell} on %{monster}" msgstr "SeÅ¡li %{spell} na %{monster}" msgid "Cast %{spell}" msgstr "SeÅ¡li %{spell}" msgid "Select spell target" msgstr "Vyber na koho seslat" msgid "View Ballista info" msgstr "Zobrazit informace o Katapultu" msgid "Ballista" msgstr "Katapult" msgid "Enable auto combat" msgstr "Zapnout automatický boj" msgid "Allows the computer to fight out the battle for you." msgstr "Umožňuje poÄítaÄi vybojovat bitvu za tebe." msgid "Auto Combat" msgstr "Automatický boj" msgid "Customize system options" msgstr "Upravit systémové nastavení" msgid "Allows you to customize the combat screen." msgstr "Umožňuje upravit obrazovku bojiÅ¡tÄ›." msgid "System Options" msgstr "Nastavení systému" msgid "Skip this unit" msgstr "PÅ™eskoÄit tuto jednotku" msgid "Skip" msgstr "PÅ™eskoÄit" msgid "" "Skips the current creature. The current creature ends its turn and does not " "get to go again until the next round." msgstr "" "PÅ™eskoÄí stvoÅ™ení na tahu. StvoÅ™ení tím ukonÄí svůj tah a k boji se pÅ™idá " "znovu až v dalším kole." msgid "View Captain's options" msgstr "Zobraz možn. velitele" msgid "View Hero's options" msgstr "Zobraz možn. hrdiny" msgid "View opposing Captain" msgstr "Zobraz nepÅ™. velitele" msgid "View opposing Hero" msgstr "Zobraz nepÅ™. hrdinu" msgid "Hide logs" msgstr "Schovat logy" msgid "Show logs" msgstr "Zobrazit logy" msgid "Message Bar" msgstr "Pruh správ" msgid "Shows the results of individual monster's actions." msgstr "Zobrazuje následky poÄínání jednotlivých stvůr." msgid "Are you sure you want to finish the battle in auto mode?" msgstr "Jsi si jist, že chceÅ¡ dokonÄit boj v automatickém módu?" msgid "Are you sure you want to enable auto combat?" msgstr "UrÄitÄ› chceÅ¡ povolit automatický boj?" msgid "%{name} skip their turn." msgstr "%{name} nebudou táhnout." msgid "%{attacker} does %{damage} damage." msgid_plural "%{attacker} do %{damage} damage." msgstr[0] "%{attacker} způsobil %{damage} Å¡kod." msgstr[1] "%{attacker} způsobili %{damage} Å¡kod." msgstr[2] "%{attacker} způsobili %{damage} Å¡kod." msgid "1 creature perishes." msgid_plural "%{count} creatures perish." msgstr[0] "1 stvoÅ™ení umírá." msgstr[1] "%{count} stvoÅ™ení umírájí." msgstr[2] "%{count} stvoÅ™ení umírájí." msgid "1 %{defender} perishes." msgid_plural "%{count} %{defender} perish." msgstr[0] "1 %{defender} umírá." msgstr[1] "%{count} %{defender} umírají." msgstr[2] "%{count} %{defender} umírají." msgid "1 soul is incorporated." msgid_plural "%{count} souls are incorporated." msgstr[0] "Jedna duÅ¡e je zaÄlenÄ›na." msgstr[1] "%{count} duÅ¡e jsou zaÄlenÄ›ny." msgstr[2] "%{count} duší je zaÄlenÄ›no." msgid "1 %{unit} is revived." msgid_plural "%{count} %{unit} are revived." msgstr[0] "1 %{unit} oživen/a." msgstr[1] "%{count} %{unit} bylo oživeno." msgstr[2] "%{count} %{unit} bylo oživeno." msgid "Moved %{monster}: from [%{src}] to [%{dst}]." msgstr "PÅ™esunuto %{monster}: z [%{src}] na [%{dst}]." msgid "The %{name} resist the spell!" msgstr "%{name} kouzlu odolali!" msgid "%{name} casts %{spell} on the %{troop}." msgstr "%{name} sesílá %{spell} na %{troop}." msgid "%{name} casts %{spell}." msgstr "%{name} sesílá %{spell}." msgid "The %{spell} does %{damage} damage to one undead creature." msgstr "%{spell} působí %{damage} Å¡kod na vybrané nemrtvé stvoÅ™ení." msgid "The %{spell} does %{damage} damage to all undead creatures." msgstr "%{spell} působí %{damage} Å¡kod na vÅ¡echny nemrtvé stvoÅ™ení." msgid "The %{spell} does %{damage} damage, %{count} creatures perish." msgstr "%{spell} působí %{damage} Å¡kod, %{count} stvoÅ™ení bylo zahubeno." msgid "The %{spell} does %{damage} damage." msgstr "%{spell} působí %{damage} Å¡kod." msgid "The %{spell} does %{damage} damage to one living creature." msgstr "%{spell} působí %{damage} Å¡kod na vybrané žijící stvoÅ™ení." msgid "The %{spell} does %{damage} damage to all living creatures." msgstr "%{spell} působí %{damage} Å¡kod vÅ¡em žijícím stvoÅ™ením." msgid "The %{attacker}' attack blinds the %{target}!" msgstr "Útok %{attacker} oslepil %{target}!" msgid "The %{attacker}' gaze turns the %{target} to stone!" msgstr "UpÅ™ený pohled %{attacker}' promÄ›nil %{target} na kámen!" msgid "The %{attacker}' curse falls upon the %{target}!" msgstr "%{attacker} uvalili kletbu na %{target}!" msgid "The %{target} are paralyzed by the %{attacker}!" msgstr "%{target} jsou paralyzovaní úderem %{attacker}!" msgid "The %{attacker} dispel all good spells on your %{target}!" msgstr "%{attacker} zruÅ¡il vÅ¡echna kladná kouzla na %{target}!" msgid "The %{attacker} cast %{spell} on %{target}!" msgstr "%{attacker} seslal %{spell} na %{target}!" msgid "Bad luck descends on the %{attacker}." msgstr "%{attacker} postihlo neÅ¡tÄ›stí." msgid "Good luck shines on the %{attacker}." msgstr "Na %{attacker} se usmálo Å¡tÄ›stí." msgid "High morale enables the %{monster} to attack again." msgstr "Dobrá morálka způsobila, že %{monster} může zaútoÄit znovu." msgid "Low morale causes the %{monster} to freeze in panic." msgstr "" "Chabá morálka způsobila, že %{monster} zpanikaÅ™ili a neumůžou se hnout." msgid "%{tower} does %{damage} damage." msgstr "%{tower} působí %{damage} Å¡kod." msgid "The mirror image is created." msgstr "Zrcadlová jednotka byla vytvoÅ™ena." msgid "The mirror image is destroyed!" msgstr "Zrcadlová jednotka byla zniÄena!" msgid "Are you sure you want to interrupt the auto battle?" msgstr "Jsi si jist, že chceÅ¡ pÅ™eruÅ¡it automatický boj?" msgid "Error" msgstr "Chyba" msgid "No spells to cast." msgstr "Nic ke kouzlení." msgid "Are you sure you want to retreat?" msgstr "Jsi si jist, že chceÅ¡ utéct z boje?" msgid "Retreat disabled" msgstr "Není kam utéct" msgid "Surrender disabled" msgstr "Nelze se vzdát" msgid "Damage: %{max}" msgstr "Å kody: %{max}" msgid "Damage: %{min} - %{max}" msgstr "Å kody: %{min} - %{max}" msgid "Perish: %{max}" msgstr "Úhyn: %{max}" msgid "Perish: %{min} - %{max}" msgstr "Úhyn: %{min} - %{max}" msgid "" "Through eagle-eyed observation, %{name} is able to learn the magic spell " "%{spell}." msgstr "" "Za pomoci orlího oka byl %{name} schopen odpozorovat a nauÄit se kouzlo " "%{spell}." msgid "Human" msgstr "ÄŒlovÄ›k" msgid "AI" msgstr "AI" msgid "Start" msgstr "" #, fuzzy msgid "Start the battle." msgstr "ZpÄ›t do bitvy." msgid "Exit" msgstr "PryÄ" msgid "Reset" msgstr "" msgid "Reset to default settings." msgstr "" msgid "Please select another hero." msgstr "Vyber jiného hrdinu." msgid "Set Attack Skill" msgstr "Nastav útoÄnou sílu" msgid "Set Defense Skill" msgstr "Nastav obrannou sílu" msgid "Set Power Skill" msgstr "Nastav sílu kouzel" msgid "Set Knowledge Skill" msgstr "Nastav znalosti" #, fuzzy msgid "%{race1} %{name1} vs %{race2} %{name2}" msgstr "%{name1} se stÅ™etá s %{name2}" msgid "Monsters" msgstr "Monstra" msgid "N/A" msgstr "N/A" msgid "Left Turret" msgstr "Levá věž" msgid "Right Turret" msgstr "Pravá věž" msgid "The %{name} fires with the strength of %{count} Archers" msgstr "%{name} střílí silou %{count} LuÄiÅ¡níků" msgid "each with a +%{attack} bonus to their attack skill." msgstr "každý s +%{attack} bonusem k jejich útoÄné síle." msgid "The %{name} is destroyed." msgstr "%{name} byl zniÄen." msgid "%{count} %{name} rises from the dead!" msgid_plural "%{count} %{name} rise from the dead!" msgstr[0] "%{count} %{name} vstává z mrtvých!" msgstr[1] "%{count} %{name} vstájí z mrtvých!" msgstr[2] "%{count} %{name} vstájí z mrtvých!" msgid "Dwarven Alliance" msgstr "Å křítÄí Aliance" msgid "Sorceress Guild" msgstr "Cech ÄarodÄ›jů" msgid "Necromancer Guild" msgstr "Cech Nekromantů" msgid "Ogre Alliance" msgstr "Zlobří Aliance" msgid "Dwarfbane" msgstr "Å křítÄí prokletí" msgid "Dragon Alliance" msgstr "DraÄí Aliance" msgid "Elven Alliance" msgstr "Elfí Aliance" msgid "Kraeger defeated" msgstr "Kreager poražen" msgid "Wayward Son" msgstr "Scestný syn" msgid "Uncle Ivan" msgstr "Strýc Ivan" msgid "Annexation" msgstr "Zabrání" msgid "Force of Arms" msgstr "Síla Armády" msgid "Save the Dwarves" msgstr "Zachraň Å křítky" msgid "Carator Mines" msgstr "Caratrovy Doly" msgid "Turning Point" msgstr "Bod zlomu" msgid "scenarioName|Defender" msgstr "Obránce" msgid "Corlagon's Defense" msgstr "Corlagonova Obrana" msgid "The Crown" msgstr "Koruna" msgid "The Gauntlet" msgstr "Hozená Rukavice" msgid "Betrayal" msgstr "Zrada" msgid "Final Justice" msgstr "Poslední zůÄtování" msgid "" "Roland needs you to defeat the lords near his castle to begin his war of " "rebellion against his brother. They are not allied with each other, so they " "will spend most of their time fighting with one another. Victory is yours " "when you have defeated all of their castles and heroes." msgstr "" "Roland potÅ™ebuje abys porazil Å¡lechtice poblíž jeho hradu a tím zaÄal " "povstání proti jeho bratrovi. Protivníci nemají mezi sebou smluvené " "spojenectví, proto asi budou po vÄ›tÅ¡inu Äasu bojovat mezi sebou. VítÄ›zství " "bude tvoje když dobijeÅ¡ vÅ¡echny jejich mÄ›sta a porazíš vÅ¡echny hrdiny." msgid "" "The local lords refuse to swear allegiance to Roland, and must be subdued. " "They are wealthy and powerful, so be prepared for a tough fight. Capture all " "enemy castles to win." msgstr "" "Místní Å¡lechta odmíta odpřísáhnout Rolandovi svou vÄ›rnost, proto musí být " "potlaÄena. Panství jsou mocná a bohatá, musíš být pÅ™ipraven na tÄ›zký odpor. " "Zmocni se vÅ¡ech nepřátelských hradů a tím vyhrajeÅ¡." msgid "" "Your task is to defend the Dwarves against Archibald's forces. Capture all " "of the enemy towns and castles to win, and be sure not to lose all of the " "dwarf towns at once, or the enemy will have won." msgstr "" "Tvým úkolem je ochránít Å křítky pÅ™ed Archibaldovými vojsky. Zmocni se vÅ¡ech " "nepřátelských hradů a vesnic, zároveň dej pozor abys neztratil vÅ¡echny " "vesnice Å¡kÅ™itků najednou, jinak zvítÄ›zí náš protivník." msgid "" "You will face four allied enemies in a straightforward fight for resource " "and treasure. Capture all of the enemy castles for victory." msgstr "" "BudeÅ¡ vystaven přímému souboji o suroviny a zdroje se ÄtyÅ™mi spojeneckými " "protivníky. Zaber vÅ¡echny nepřátelské hrady a vyhrajeÅ¡." msgid "" "Your enemies are allied against you and start close by, so be ready to come " "out fighting. You will need to own all four castles in this small valley to " "win." msgstr "" "Tví protivníci uzavÅ™eli spojenecký pakt a jsou umístnÄ›ní poblíž, takže buÄ " "pÅ™ipraven vyrazit do boje. Abys vyhrál, budeÅ¡ muset dobít vÅ¡echny ÄtyÅ™i " "hrady v tomto malém údolí." msgid "" "The Sorceress' guild of Noraston has requested Roland's aid against an " "attack from Archibald's allies. Capture all of the enemy castles to win, and " "don't lose Noraston, or you'll lose the scenario. (Hint: There is an enemy " "castle on an island in the ocean.)" msgstr "" "Cech ÄarodÄ›jů mÄ›sta Noraston požádal Rolanda o pomoc proti útokům " "Archibaldových spojenců. ObsaÄ vÅ¡echny nepřátelské hrady, zároveň nesmíš " "pÅ™ijít o Noraston, jinak bude naÅ¡e tažení zmaÅ™eno. (Tip: Jeden nepřátelský " "hrad je na ostrovÄ› v oceánu.)" msgid "" "Gather as large an army as possible and capture the enemy castle within 8 " "weeks. You are opposed by only one enemy, but must travel a long way to get " "to the enemy castle. Any troops you have in your army at the end of this " "scenario will be with you in the final battle." msgstr "" "Nashromáždi co nejvÄ›tší armádu a dobij nepřátelský hrad do 8 týdnů. Proti " "tobÄ› stojí pouze jeden nepřítel, ale musíš urazit dlouhou cestu, aby ses " "dostal k jeho hradu. VÅ¡echny jednotky, které zůstanou ve Tvé armádÄ› na konci " "tohoto scénáře budou s tebou i v závÄ›reÄné bitvÄ›." msgid "" "Find the Crown before Archibald's heroes find it. Roland will need the Crown " "for the final battle against Archibald." msgstr "" "Najdi korunu dříve, než ji najdou Archibaldovi hrdinové. Roland bude korunu " "potÅ™ebovat pro poslední bitvu proti Archibaldovi." msgid "" "Three allied enemies stand before you and victory, including Lord Corlagon. " "Roland is in a castle to the northwest, and you will lose if he falls to the " "enemy. Remember that capturing Lord Corlagon will ensure that he will not " "fight against you in the final scenario." msgstr "" "TÅ™i nepřátelé se spojili a stojí mezi tebou a vítÄ›zstvím, a to vÄetnÄ› lorda " "Corlagona. Roland je na hradÄ› na severozápadÄ›, pokud padne do rukou " "nepřítele tak prohrajeÅ¡. Pamatuj, dopadení lorda Corlagona zajistí, že proti " "nám nebude bojovat v závereÄné bitvÄ›." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Archibald to end the war!" msgstr "" "Je taty poslední bitva. Ty i nepřátelé jste po zuby ozbrojení, vÅ¡ichni jsou " "ve spojenectví proti tobÄ›. Poraž Archibalda a ukonÄi tuto válku!" msgid "" "Switching sides leaves you with three castles against the enemy's one. This " "battle will be the easiest one you will face for the rest of the war..." "traitor." msgstr "" "PÅ™ebÄ›hnutím k nepříteli získáš tÅ™i hrady naproti jednomu nepřátelskému. Tato " "bitva bude ta nejjednodušší, které budeÅ¡ Äelit po zbytek války...zrádÄe." msgid "Barbarian Wars" msgstr "Barbarské Války" msgid "First Blood" msgstr "První Krev" msgid "Necromancers" msgstr "Nekromanti" msgid "Slay the Dwarves" msgstr "ZniÄ Skřítky" msgid "Country Lords" msgstr "Pánové Krajiny" msgid "Dragon Master" msgstr "Pán Draků" msgid "Rebellion" msgstr "Povstání" msgid "Apocalypse" msgstr "Apokalypsa" msgid "Greater Glory" msgstr "VÄ›tší Sláva" msgid "" "King Archibald requires you to defeat the three enemies in this region. They " "are not allied with one another, so they will spend most of their energy " "fighting amongst themselves. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Král Archibald potÅ™ebuje abys porazil tÅ™i protivníky v tomto regionu. " "Protivníci nemají mezi sebou smluvené spojenectví, proto asi spotÅ™ebují " "vÄ›tÅ¡inu energie vzájemnými souboji. VítÄ›zství bude tvoje, když zabereÅ¡ " "vÅ¡echny nepřátelské hrady a nezbyde žádný hrdina k boji." msgid "" "You must unify the barbarian tribes of the north by conquering them. As in " "the previous mission, the enemy is not allied against you, but they have " "more resources at their disposal. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Musíš sjednotit barbarské kmeny na severu tím, že si je podrobíš. StejnÄ› " "jako v pÅ™edchozí misi, nepřátelé nemají smluvené spojenectví proti TobÄ›, ale " "mají k dispozici více zdrojů. ZvítÄ›zíš, až budeÅ¡ vlastnit vÅ¡echny " "nepřátelské hrady a nezbude žádný hrdina k boji." msgid "" "Do-gooder wizards have taken the Necromancers' castle. You must retake it to " "achieve victory. Remember that while you start with a powerful army, you " "have no castle and must take one within 7 days, or lose this battle. (Hint: " "The nearest castle is to the southeast.)" msgstr "" "DobrodÄ›jové obsadili hrad nekromantů. Abys zvítÄ›zil, musíš ho znovu dobýt. " "Nezapomeň, že i když zaÄínáš se silnou armádou, tak nemáš žádný hrad a musíš " "ho do 7 dnů dobýt, jinak tuto bitvu prohrajeÅ¡. (NápovÄ›da: Nejbližší hrad je " "na jihovýchodÄ›.)" msgid "" "The dwarves need conquering before they can interfere in King Archibald's " "plans. Roland's forces have more than one hero and many towns to start with, " "so be ready for attack from multiple directions. You must capture all of the " "enemy towns and castles to claim victory." msgstr "" "Je potÅ™eba podrobit si Skřítky, než budou moci zasáhnout do plánů krále " "Archibalda. Rolandova vojska mají víc než jednoho hrdinu a mnoho mÄ›st, takže " "buÄ pÅ™ipraven na útok z více smÄ›rů. Abys zvítÄ›zil, musíš dobýt vÅ¡echna " "nepřátelská mÄ›sta a hrady." msgid "" "You must put down a peasant revolt led by Roland's forces. All are allied " "against you, but you have Lord Corlagon, an experienced hero, to help you. " "Capture all enemy castles to win." msgstr "" "Musíš potlaÄit rolnické povstání vedené Rolandovými vojsky. VÅ¡ichni jsou ve " "spojenectví proti tobÄ›, ale Ty máš po boku lorda Corlagona, zkuÅ¡eného " "hrdinu. Zaber vÅ¡echny nepřátelské hrady a vyhrajeÅ¡." msgid "" "There are two enemies allied against you in this mission. Both are well " "armed and seek to evict you from their island. Avoid them and capture Dragon " "City to win." msgstr "" "V této misi proti TobÄ› stojí dva nepřátelé. Oba jsou dobÅ™e vyzbrojeni a " "chtÄ›jí TÄ› ze svého ostrova vyhnat. Abys zvítÄ›zil, musíš jim v tom zabránit a " "dobít DraÄí mÄ›sto." msgid "" "Your orders are to conquer the country lords that have sworn to serve " "Roland. All of the enemy castles are unified against you. Since you start " "without a castle, you must hurry to capture one before the end of the week. " "Capture all enemy castles for victory." msgstr "" "Rozkaz zní dobít zemÄ› pánů, kteří odpÅ™isáhli vÄ›rnost a slouží Rolandovi. " "VÅ¡echny nepřátelské hrady se spolÄily proti TobÄ›. Protože zaÄínáš bez hradu, " "musíš si pospíšit, abys nÄ›jaký zabral do konce týdne. VítÄ›zství Ti zajistí, " "když zabereÅ¡ vÅ¡echny nepřátelské hrady." msgid "" "Find the Crown before Roland's heroes find it. Archibald will need the Crown " "for the final battle against Roland." msgstr "" "Najdi korunu dříve, než ji najdou Rolandovi hrdinové. Archibald bude korunu " "potÅ™ebovat pro poslední bitvu proti Rolandovi." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Roland to win the war, and be sure " "not to lose Archibald in the fight!" msgstr "" "Je tady poslední bitva. Ty i nepřátelé jste po zuby ozbrojení, vÅ¡ichni jsou " "ve spojenectví proti TobÄ›. Poraz Rolanda a ukonÄi tuto válku, v bitvÄ› ale " "nesmíš pÅ™ijít o Archibalda!" msgid "Arrow's Flight" msgstr "Let Šípu" msgid "Island of Chaos" msgstr "Ostrov chaosu" msgid "The Abyss" msgstr "Propast" msgid "Uprising" msgstr "Povstání" msgid "Aurora Borealis" msgstr "Aurora Borealis" msgid "Betrayal's End" msgstr "PotlaÄení zrady" msgid "Corruption's Heart" msgstr "Srdce korupce" msgid "The Giant's Pass" msgstr "Obrův průsmyk" msgid "" "Subdue the unruly local lords in order to provide the Empire with facilities " "to operate in this region." msgstr "" "Podmaň si neukáznÄ›né místní pány, tím poskytneÅ¡ království potÅ™ebné zázemí " "pro fungování v tomto regionu." msgid "" "Eliminate all opposition in this area. Then the first piece of the artifact " "will be yours." msgstr "" "Zlikviduj veÅ¡kerou opozici v této oblasti. Pak bude první Äást artefaktu " "Tvoje." msgid "" "The sorceresses to the northeast are rebelling! For the good of the empire " "you must quash their feeble uprising on your way to the mountains." msgstr "" "ÄŒarodÄ›jky na severovýchodÄ› se bouří! Pro dobro říše musíš vyrazit do hor a " "potlaÄit jejich chabé povstání." msgid "" "Having prepared for your arrival, Kraeger has arranged for a force of " "necromancers to thwart your quest. You must capture the castle of Scabsdale " "before the first day of the third week, or the Necromancers will be too " "strong for you." msgstr "" "Kraeger se pÅ™ipravil na Tvůj vpád a sestavil celou armádu nekromantů, aby " "zmaÅ™ili tvou výpravu. Musíš dobýt hrad Scabsdale do prvního dne tÅ™etího " "týdne, jinak už budou nekromanti k neporažení." msgid "" "The barbarian despot in this area is, as yet, ignorant of your presence. " "Quickly, build up your forces before you are discovered and attacked! Secure " "the region by subduing all enemy forces." msgstr "" "Barbarský despota v této oblasti o Tvé přítomnosti zatím neví. Rychle sestav " "své vojsko, dříve než budeÅ¡ odhalen a napaden! PodmanÄ›ním vÅ¡ech " "nepřátelských síl si podrobíš a zabezpeÄíš tento kraj." msgid "" "The Empire is weak in this region. You will be unable to completely subdue " "all forces in this area, so take what you can before reprisal strikes. " "Remember, your true goal is to claim the Helmet of Anduran." msgstr "" "Říše je v této oblasti slabá. NebudeÅ¡ proto schopen si zcela podmanit " "vÅ¡echny vojska v této oblasti, takže si vem co můžeÅ¡, než udeří odvetným " "útokem. Nezapomeň, že skuteÄným cílem je získat Anduranskou pÅ™ilbu." msgid "For the good of the Empire, eliminate Kraeger." msgstr "Zabij Kreagera, pro slávu impéria." msgid "" "At last, you have the opportunity and the facilities to rid the Empire of " "the necromancer's evil. Eradicate them completely, and you will be sung as a " "hero for all time." msgstr "" "KoneÄnÄ› máš možnost a prostÅ™edky zbavit říši od onoho nekromantského zla. " "Když je zcela je vymýtíš, budeÅ¡ navÄ›ky opÄ›vován jako hrdina." msgid "Border Towns" msgstr "PohraniÄní mÄ›sta" msgid "Conquer and Unify" msgstr "Dobyj a sjednoÅ¥" msgid "Crazy Uncle Ivan" msgstr "Šílený strýc Ivan" msgid "The Wayward Son" msgstr "Scestný syn" msgid "Ivory Gates" msgstr "Ivory Gates" msgid "The Elven Lands" msgstr "Elfské území" msgid "The Epic Battle" msgstr "Epická bitva" msgid "The Southern War" msgstr "Jižní válka" msgid "" "Conquer and unite all the enemy tribes. Don't lose the hero Jarkonas, the " "forefather of all descendants." msgstr "" "Dobyj a sjednoÅ¥ vÅ¡echny nepřátelské kmeny. NeztraÅ¥ pÅ™i tom hrdinu Jarkonase, " "praotce celého rodu." msgid "" "Your rival, the Kingdom of Harondale, is attacking weak towns on your " "border! Recover from their first strike and crush them completely!" msgstr "" "Tvůj protivník, království Harondalu, útoÄí na slabá mÄ›sta na hranicích! " "Vzpamatuj se z jejich prvního úderu a kompletnÄ› je rozdrÅ¥!" msgid "" "Find your wayward son Joseph who is rumored to be living in the desolate " "lands. Do it before the first day of the third month or it will be of no " "help to your family." msgstr "" "Najdi svého umínÄ›ného syna Josefa, o nÄ›mž se povídá, že žije v pustých " "konÄinách. Musíš ho najít do prvního dne tÅ™etího mÄ›síce, jinak to rodinÄ› již " "nepomůže." msgid "" "Rescue your crazy uncle Ivan. Find him before the first day of the fourth " "month or it will be no help to your kingdom." msgstr "" "Zachraň svého šíleného strýce Ivana. Musíš ho najít do prvního dne Ätvrtého " "mÄ›síce, jinak to Tvému království již nepomůže." msgid "" "Destroy the barbarians who are attacking the southern border of your " "kingdom! Recover your fallen towns, and then invade the jungle kingdom. " "Leave no enemy standing." msgstr "" "ZniÄ barbary, kteří útoÄí na jižní hranici království! Získej zpÄ›t svá padlá " "mÄ›sta a pak vtrhni do království džungle. Žádný z protivníků nesmí zůstat na " "živu." msgid "Retake the castle of Ivory Gates, which has fallen due to treachery." msgstr "Získej zpÄ›t hrad Ivory Gates, který padl kvůli zradÄ›." msgid "" "Gain the favor of the elves. They will not allow trees to be chopped down, " "so they will send you wood every 2 weeks. You must complete your mission " "before the first day of the seventh month, or the kingdom will surely fall." msgstr "" "Získej si přízeň elfů. Kácení stromů je v tomto kraji zakázáno, ale každé " "dva týdny Ti nÄ›jaké dÅ™evo poÅ¡lou. Svůj úkol musíš splnit do prvního dne " "sedmého mÄ›síce, jinak království jistÄ› padne." msgid "" "This is the final battle against your rival kingdom of Harondale. Eliminate " "everyone, and don't lose the hero Jarkonas VI." msgstr "" "Je tady poslední bitva proti nepřátelskému království Harondale. Poraž je " "vÅ¡echny a neztraÅ¥ pÅ™i tom hrdinu Jarkonase VI." msgid "Fount of Wizardry" msgstr "Pramen kouzelnictví" msgid "Power's End" msgstr "Konec moci" msgid "The Eternal Scrolls" msgstr "VÄ›Äné svitky" msgid "The Shrouded Isles" msgstr "Zahalené ostrovy" msgid "" "Your mission is to vanquish the warring mages in the magical Shrouded Isles. " "The completion of this task will give you a fighting chance against your " "rivals." msgstr "" "Tvým úkolem je porazit bojové mágy na kouzelných Zahalených ostrovech. " "SplnÄ›ní tohoto úkolu ti zaruÄí výhodu v boji proti tvým protivníkům." msgid "" "The location of the great library has been discovered! You must make your " "way to it, and reclaim the city of Chronos in which it lies." msgstr "" "Místo, kde se nachází velká knihovna, bylo objeveno! Musíš se za ní vydat a " "získat zpÄ›t mÄ›sto Chronos, ve kterém se nachází." #, fuzzy msgid "" "Find the Orb of negation, which is said to be buried in this land. There are " "clues inscribed on stone obelisks which will help lead you to your prize. " "Find the Orb before the first day of the sixth month, or your rivals will " "surely have gotten to the fount before you." msgstr "" "Najdi Sféru negace, která je prý pohÅ™bena nÄ›kde v této zemi. Na kamenných " "obeliscích jsou vepsány indicie, které TÄ› dovedou až k cíli. Najdi sféru do " "prvního dne Å¡estého mÄ›síce, jinak aftefakt jistÄ› získají Tví protivníci." msgid "" "You must take control of the castle of Magic, where the fount of wizardry " "lies. Do this and your victory will be supreme." msgstr "" "Musíš ovládnout hrad Magic, ve kterém se nachází pramen kouzel. Dokaž to a " "Tvé vítÄ›zství bude to nejslavnÄ›jší ze vÅ¡ech." msgid "Blood is Thicker" msgstr "Pokrevné pouto" msgid "King and Country" msgstr "Král a jeho zemÄ›" msgid "Pirate Isles" msgstr "Pirátské ostrovy" msgid "Stranded" msgstr "Uvízlý" msgid "" "Capture the town on the island off the southeast shore in order to construct " "a boat and travel back towards the mainland. Do not lose the hero Gallavant." msgstr "" "Dobytí mÄ›sta na ostrovÄ› u jihovýchodního pobÅ™eží Ti umožní postavit loÄ a " "doplout na pevninu. NeztraÅ¥ hrdinu Gallavanta." msgid "" "Find and defeat Martine, the pirate leader, who resides in Pirates Cove. Do " "not lose Gallavant or your quest will be over." msgstr "" "Najdi a poraz Martine, vůdkyni pirátů, která sídlí v mÄ›stÄ› Pirates Cove. " "NeztraÅ¥ pÅ™i tom Gallavanta, jinak bude vÅ¡e ztraceno." msgid "" "Eliminate all the other forces who oppose the rule of Lord Alberon. " "Gallavant must not die." msgstr "" "Zlikviduj vÅ¡echny, kteÅ™i se staví proti vládÄ› lorda Alberona. Gallavant " "nesmí zemřít." msgid "" "Overthrow the entrenched monarchy of Lord Alberon, and claim all the land in " "your name. Gallavant must not die." msgstr "" "Svrhni zkůrnatÄ›lou monarchii lorda Alberona a prohlaÅ¡ celou zemi za svou. " "Gallavant nesmí zemřít." msgid " bane" msgstr " zkáza" msgid " alliance" msgstr " aliance" msgid "Carry-over forces" msgstr "PÅ™evedené vojska" msgid " bonus" msgstr " bonus" msgid " defeated" msgstr " poražení" msgid " will always run away from your army." msgstr " vždy uteÄou pÅ™ed Tvými vojsky." msgid " will be willing to join your army." msgstr " se budou chtít k TobÄ› pÅ™idat." msgid "\"%{artifact}\" artifact will be carried over the scenario." msgstr "\"%{artifact}\" si poneseÅ¡ s sebou do další hry." msgid "The army will be carried over the scenario." msgstr "Armáda bude pÅ™enesena do další hry." msgid "The kingdom will have +%{count} %{resource} each day." msgstr "Kralovské příjmy budou vyšśí o %{count} %{resource} za den." msgid "\"%{spell}\" spell will be carried over the scenario." msgstr "\"%{spell}\" bude pÅ™eneseno do další hry." msgid "%{hero} can be hired in the scenario." msgstr "Ve hÅ™e bude možné najmout hrdinu %{hero}." msgid "" "%{hero} has been defeated and will not appear in the subsequent scenarios." msgstr "%{hero} byl poražen a už se v dalších krocích kampanÄ› neobjeví." msgid "The dwarves recognize their allies and gladly join your forces." msgstr "Skřítci poznají své spojence a rádi se pÅ™ipojí k Tvým silám." msgid "The ogres recognize you as the Dwarfbane and lumber over to join you." msgstr "" "ZlobÅ™i poznávají, že jsi ten kdo porazil skřítky a pÅ™idávají se k tobÄ›." msgid "" "The dragons, snarling and growling, agree to join forces with you, their " "'Ally'." msgstr "" "Prskající a funící draci souhlasí, že spojí své síly s tebou, jejich " "'spojencem'." msgid "" "As you approach the group of elves, their leader calls them all to " "attention. He shouts to them, \"Who of you is brave enough to join this " "fearless ally of ours?\" The group explodes with cheers as they run to join " "your ranks." msgstr "" "Když se pÅ™iblížíš ke skupinÄ› elfů, jejich vůdce je vÅ¡echny vyzve k " "pozornosti. ZakÅ™iÄí na nÄ›: \"Kdo z vás je dost odvážný, aby se pÅ™idal k " "naÅ¡emu neohroženému spojenci?\" Skupina propukne v jásot a běží aby se " "pÅ™idali k Tvým Å™adám." msgid "" "The dwarves hail you, \"Any friend of Roland is a friend of ours. You may " "pass.\"" msgstr "" "Skřítci tÄ› vítají, \"Každý Rolandův přítel je i náš přítel. MůžeÅ¡ projít.\"" msgid "" "The ogres give you a grunt of recognition, \"Archibald's allies may pass.\"" msgstr "ZlobÅ™i na tebe uznale zavrÄí: \"Archibaldovi spojenci mohou projít.\"" msgid "" "The dragons see you and call out. \"Our alliance with Archibald compels us " "to join you. Unfortunately you have no room. A pity!\" They quickly scatter." msgstr "" "Draci TÄ› vidí a zvolají. \"NaÅ¡e spojenectví s Archibaldem nás zavazuje " "abychom se k TobÄ› pÅ™ipojili. Bohužel už nemáš místo. To je Å¡koda!\" A rychle " "se rozprchli." msgid "" "The elves stand at attention as you approach. Their leader calls to you and " "says, \"Let us not impede your progress, ally! Move on, and may victory be " "yours.\"" msgstr "" "Elfové stojí v pozoru, když se pÅ™iblížíš. Jejich vůdce zvolá a Å™ekne: " "\"Nebudeme Ti bránít v postupu, spojenÄe! PokraÄuj, věříme ve Tvé vítÄ›zství." "\"" msgid "\"The Dwarfbane!!!!, run for your lives.\"" msgstr "\"Skřítkobijec!!!!, běžte jako o život.\"" msgid "campaignBonus|Animate Dead" msgstr "Vzkříšení mrtvol" msgid "campaignBonus|Chain Lightning" msgstr "Kulový blesk" msgid "campaignBonus|Fireblast" msgstr "Ohnivý výbuch" msgid "campaignBonus|Mass Curse" msgstr "Hrom. Lék" msgid "campaignBonus|Mass Haste" msgstr "Hrom. spÄ›ch" msgid "campaignBonus|Mirror Image" msgstr "Dvojník" msgid "campaignBonus|Resurrect" msgstr "Oživení" msgid "campaignBonus|Steelskin" msgstr "Kůže z oceli" msgid "campaignBonus|Summon Earth" msgstr "PÅ™iv. Půd. Elem." msgid "campaignBonus|View Heroes" msgstr "Zobraz Hrdiny" msgid "campaignBonus|Ballista" msgstr "Katapult" msgid "campaignBonus|Black Pearl" msgstr "ÄŒerná perla" msgid "campaignBonus|Caster's Bracelet" msgstr "" msgid "campaignBonus|Defender Helm" msgstr "" msgid "campaignBonus|Breastplate" msgstr "" msgid "campaignBonus|Dragon Sword" msgstr "" msgid "campaignBonus|Fizbin Medal" msgstr "" msgid "campaignBonus|Foremost Scroll" msgstr "" msgid "campaignBonus|Gauntlets" msgstr "" msgid "campaignBonus|Hideous Mask" msgstr "" msgid "campaignBonus|Mage's Ring" msgstr "" msgid "campaignBonus|Major Scroll" msgstr "" msgid "campaignBonus|Medal of Honor" msgstr "" msgid "campaignBonus|Medal of Valor" msgstr "" msgid "campaignBonus|Minor Scroll" msgstr "" msgid "campaignBonus|Nomad Boots" msgstr "" msgid "campaignBonus|Power Axe" msgstr "" msgid "campaignBonus|Spiked Shield" msgstr "" msgid "campaignBonus|Stealth Shield" msgstr "" msgid "campaignBonus|Tax Lien" msgstr "" msgid "campaignBonus|Thunder Mace" msgstr "" msgid "campaignBonus|Traveler's Boots" msgstr "" msgid "campaignBonus|White Pearl" msgstr "" msgid "campaignBonus|Basic Archery" msgstr "Zákl. LukoÅ¡tÅ™elba" msgid "campaignBonus|Advanced Archery" msgstr "Pokr. LukostÅ™elba" msgid "campaignBonus|Expert Archery" msgstr "Exp. LukostÅ™elba" msgid "campaignBonus|Basic Ballistics" msgstr "Zákl. Balistika" msgid "campaignBonus|Advanced Ballistics" msgstr "Pokr. Balistika" msgid "campaignBonus|Expert Ballistics" msgstr "Exp. Balistika" msgid "campaignBonus|Basic Diplomacy" msgstr "Zákl. Diplomacie" msgid "campaignBonus|Advanced Diplomacy" msgstr "Pokr. Diplomacie" msgid "campaignBonus|Expert Diplomacy" msgstr "Exp. Diplomacie" msgid "campaignBonus|Basic Eagle Eye" msgstr "Zákl. Orlí oko" msgid "campaignBonus|Advanced Eagle Eye" msgstr "Pokr. Orlí oko" msgid "campaignBonus|Expert Eagle Eye" msgstr "Exp. Orlí oko" msgid "campaignBonus|Basic Estates" msgstr "Zákl. Majetek" msgid "campaignBonus|Advanced Estates" msgstr "Pokr. Majetek" msgid "campaignBonus|Expert Estates" msgstr "Exp. Majetek" msgid "campaignBonus|Basic Leadership" msgstr "Zákl. Vedení" msgid "campaignBonus|Advanced Leadership" msgstr "Pokr. Vedení" msgid "campaignBonus|Expert Leadership" msgstr "Exp. Vedení" msgid "campaignBonus|Basic Logistics" msgstr "Zákl. Logistika" msgid "campaignBonus|Advanced Logistics" msgstr "Pokr. Logistika" msgid "campaignBonus|Expert Logistics" msgstr "Exp. Logistika" msgid "campaignBonus|Basic Luck" msgstr "Zákl. Å tÄ›stí" msgid "campaignBonus|Advanced Luck" msgstr "Pokr. Å tÄ›stí" msgid "campaignBonus|Expert Luck" msgstr "Exp. Å tÄ›stí" msgid "campaignBonus|Basic Mysticism" msgstr "Zákl. Mystika" msgid "campaignBonus|Advanced Mysticism" msgstr "Pokr. Mystika" msgid "campaignBonus|Expert Mysticism" msgstr "Exp. Mystika" msgid "campaignBonus|Basic Navigation" msgstr "Zákl. Navigace" msgid "campaignBonus|Advanced Navigation" msgstr "Pokr. Navigace" msgid "campaignBonus|Expert Navigation" msgstr "Exp. Navigace" msgid "campaignBonus|Basic Necromancy" msgstr "Zákl. Nekromantie" msgid "campaignBonus|Advanced Necromancy" msgstr "Pokr. Nekromantie" msgid "campaignBonus|Expert Necromancy" msgstr "Exp. Nekromantie" msgid "campaignBonus|Basic Pathfinding" msgstr "Zákl. Hled. cest" msgid "campaignBonus|Advanced Pathfinding" msgstr "Pokr. Hled. cest" msgid "campaignBonus|Expert Pathfinding" msgstr "Exp. Hled. cest" msgid "campaignBonus|Basic Scouting" msgstr "Zákl. Průzkumník" msgid "campaignBonus|Advanced Scouting" msgstr "Pokr. Průzkumník" msgid "campaignBonus|Expert Scouting" msgstr "Exp. Průzkumník" msgid "campaignBonus|Basic Wisdom" msgstr "Zákl. Moudrost" msgid "campaignBonus|Advanced Wisdom" msgstr "Pokr. Moudrost" msgid "campaignBonus|Expert Wisdom" msgstr "Exp. Moudrost" msgid "" "The main hero will have \"%{artifact}\" artifact at the start of the " "scenario." msgstr "Tvůj hlavní hrdina bude mít na zaćátku hry artefakt \"%{artifact}\"." msgid "" "The kingdom will receive %{amount} additional %{resource} at the start of " "the scenario." msgstr "Tvé království bude mít na zaÄátku hry o %{amount} %{resource} víc." msgid "" "The kingdom will have %{amount} less %{resource} at the start of the " "scenario." msgstr "Tvé království bude mít na zaÄátku hry o %{amount} %{resource} míň." msgid "" "The main hero will have %{count} %{monster} at the start of the scenario." msgstr "Tvůj hlavní hrdina bude hru zaÄínat s %{count} %{monster}." msgid "" "The main hero will have \"%{spell}\" spell at the start of the scenario." msgstr "Tvůj hlavní hrdina bude umÄ›t vykouzlit \"%{spell}\" od zaÄátku hry." msgid "The starting race of the scenario will be %{race}." msgstr "Hru zaÄneÅ¡ jako Älen kasty %{race}." msgid "" "The main hero will have additional %{count} %{skill} at the start of the " "scenario." msgstr "" "Tvůj hlavní hrdina bude navíc ovládat %{count} %{skill} od zaÄátku hry." msgid "The main hero will have %{skill} at the start of the scenario." msgstr "Tvůj hlavní hrdina bude ovládat %{skill} od zaÄátku hry." msgid "Roland" msgstr "Roland" msgid "Archibald" msgstr "Archibald" #, fuzzy msgid "Price of Loyalty" msgstr "Cena za vÄ›rnost" msgid "Voyage Home" msgstr "Cesta domů" msgid "Wizard's Isle" msgstr "Kouzelníkův ostrov" msgid "Descendants" msgstr "Potomci" msgid "The %{building} produces %{monster}." msgstr "%{building} produkuje %{monster}." msgid "Requires:" msgstr "Požadováno:" msgid "Exit this menu without doing anything." msgstr "Odejít z menu bez provedení zmÄ›m." msgid "Cannot build. You have already built here today." msgstr "Nelze postavit. Dnes si zde již stavÄ›l." msgid "For this action it is necessary to build a castle first." msgstr "Pro tuto akci bude nutné nejdříve postavit hrad." msgid "Cannot build %{name} because the castle is too far away from an ocean." msgstr "%{name} nelze postavit, protože hrad je moc daleko od moÅ™e." msgid "disable build." msgstr "zakázat stavÄ›ní." msgid "Cannot afford %{name}." msgstr "Na %{name} zatím nemáš." msgid "%{name} is already built." msgstr "%{name}, již dostavÄ›no." msgid "Cannot build %{name}." msgstr "Není možné postavit %{name}" msgid "Build %{name}." msgstr "Postavit %{name}." msgid "Blackridge" msgstr "Blackridge" msgid "Hillstone" msgstr "Hillstone" msgid "Pinehurst" msgstr "Pinehurst" msgid "Whiteshield" msgstr "Whiteshield" msgid "Woodhaven" msgstr "Woodhaven" msgid "Blackwind" msgstr "Blackwind" msgid "Bloodreign" msgstr "Bloodreign" msgid "Dragontooth" msgstr "Dragontooth" msgid "Greywind" msgstr "Greywind" msgid "Portsmith" msgstr "Portsmith" msgid "Atlantium" msgstr "Atlantium" msgid "Middle Gate" msgstr "Middle Gate" msgid "Sansobar" msgstr "Sansobar" msgid "Tundara" msgstr "Tundara" msgid "Vulcania" msgstr "Vulcania" msgid "Baywatch" msgstr "Baywatch" msgid "Fountainhead" msgstr "Fountainhead" msgid "Vertigo" msgstr "Vertigo" msgid "Wildabar" msgstr "Wildabar" msgid "Winterkill" msgstr "Winterkill" msgid "Brindamoor" msgstr "Brindamoor" msgid "Lakeside" msgstr "Lakeside" msgid "Nightshadow" msgstr "Nightshadow" msgid "Olympus" msgstr "Olympus" msgid "Sandcaster" msgstr "Sandcaster" msgid "Alamar" msgstr "Alamar" msgid "Burlock" msgstr "Burlock" msgid "Dragadune" msgstr "Dragadune" msgid "Kalindra" msgstr "Kalindra" msgid "Xabran" msgstr "Xabran" msgid "Algary" msgstr "Algary" msgid "Basenji" msgstr "Basenji" msgid "Blackfang" msgstr "Blackfang" msgid "New Dawn" msgstr "New Dawn" msgid "Sorpigal" msgstr "Sorpigal" msgid "Avone" msgstr "Avone" msgid "Big Oak" msgstr "Big Oak" msgid "Chandler" msgstr "Chandler" msgid "Erliquin" msgstr "Erliquin" msgid "Hampshire" msgstr "Hampshire" msgid "Antioch" msgstr "Antioch" msgid "Avalon" msgstr "Avalon" msgid "Roc Haven" msgstr "Roc Haven" msgid "South Mill" msgstr "South Mill" msgid "Weed Patch" msgstr "Weed Patch" msgid "Brownston" msgstr "Brownston" msgid "Hilltop" msgstr "Hilltop" msgid "Weddington" msgstr "Weddington" msgid "Westfork" msgstr "Westfork" msgid "Whittingham" msgstr "Whittingham" msgid "Cathcart" msgstr "Cathcart" msgid "Elk's Head" msgstr "Elk's Head" msgid "Roscomon" msgstr "Roscomon" msgid "Sherman" msgstr "Sherman" msgid "Yorksford" msgstr "Yorksford" msgid "Blackburn" msgstr "Blackburn" msgid "Blacksford" msgstr "Blacksford" msgid "Burton" msgstr "Burton" msgid "Pig's Eye" msgstr "Pig's Eye" msgid "Viper's Nest" msgstr "Viper's Nest" msgid "Fenton" msgstr "Fenton" msgid "Lankershire" msgstr "Lankershire" msgid "Lombard" msgstr "Lombard" msgid "Timberhill" msgstr "Timberhill" msgid "Troy" msgstr "Troy" msgid "Forder Oaks" msgstr "Forder Oaks" msgid "Meramec" msgstr "Meramec" msgid "Quick Silver" msgstr "Quick Silver" msgid "Westmoor" msgstr "Westmoor" msgid "Willow" msgstr "Willow" msgid "Corackston" msgstr "Corackston" msgid "Sheltemburg" msgstr "Sheltemburg" msgid "Cannot recruit - you already have a Hero in this town." msgstr "Nelze najmout - ve mÄ›ste se nachází hrdina." msgid "Cannot recruit - you have too many Heroes." msgstr "Nelze najmout - máš příliž mnoho hrdinů." msgid "Cannot afford a Hero" msgstr "NemůžeÅ¡ si ho dovolit" msgid "There is no room in the garrison for this army." msgstr "Pro tuto jednotku už ve Tvé posádce není místo." msgid "Fortifications" msgstr "OpevnÄ›ní" msgid "Farm" msgstr "Farma" msgid "Thatched Hut" msgstr "DoÅ¡ková ChatrÄ" msgid "Archery Range" msgstr "LukostÅ™elba" msgid "Upg. Archery Range" msgstr "Upg. LukostÅ™elba" msgid "Blacksmith" msgstr "Kovárna" msgid "Upg. Blacksmith" msgstr "Upg. Kovárna" msgid "Armory" msgstr "Zbrojnice" msgid "Upg. Armory" msgstr "Upg. Zbrojnice" msgid "Jousting Arena" msgstr "Rytířská Aréna" msgid "Upg. Jousting Arena" msgstr "Upg. Rytířská Aréna" msgid "Cathedral" msgstr "Katedrála" msgid "Upg. Cathedral" msgstr "Upg. Katedrála" msgid "Coliseum" msgstr "Koloseum" msgid "Garbage Heap" msgstr "Halda hnoje" msgid "Hut" msgstr "ChatrÄ" msgid "Stick Hut" msgstr "DÅ™evÄ›ná Chata" msgid "Upg. Stick Hut" msgstr "Upg. DÅ™evÄ›ná Chata" msgid "Den" msgstr "DoupÄ›" msgid "Adobe" msgstr "Adobe" msgid "Upg. Adobe" msgstr "Upg. Adobe" msgid "Bridge" msgstr "Most" msgid "Upg. Bridge" msgstr "Upg. Most" msgid "Pyramid" msgstr "Pyramida" msgid "Rainbow" msgstr "Duha" msgid "Crystal Garden" msgstr "KÅ™išťálová zahrada" msgid "Treehouse" msgstr "Dům na stromÄ›" msgid "Cottage" msgstr "Chata" msgid "Upg. Cottage" msgstr "Upg. Chata" msgid "Stonehenge" msgstr "Stonehenge" msgid "Upg. Stonehenge" msgstr "Upg. Stonehenge" msgid "Fenced Meadow" msgstr "Oplocená Louka" msgid "sorceress|Red Tower" msgstr "ÄŒervená Věž" msgid "Dungeon" msgstr "Kobka" msgid "Waterfall" msgstr "Vodopád" msgid "Cave" msgstr "JeskynÄ›" msgid "Crypt" msgstr "Krypta" msgid "Nest" msgstr "Hnízdo" msgid "Maze" msgstr "Labyrint" msgid "Upg. Maze" msgstr "Upg. Labyrint" msgid "Swamp" msgstr "Bažina" msgid "Green Tower" msgstr "Zelená Věž" msgid "warlock|Red Tower" msgstr "ÄŒervená Věž" msgid "Black Tower" msgstr "ÄŒerná Věž" msgid "Library" msgstr "Knihovna" msgid "Orchard" msgstr "Ovocný sad" msgid "Habitat" msgstr "NaleziÅ¡tÄ›" msgid "Pen" msgstr "Kotec" msgid "Foundry" msgstr "Slévárna" msgid "Upg. Foundry" msgstr "Upg. Slevárna" msgid "Cliff Nest" msgstr "Útes" msgid "Ivory Tower" msgstr "Slonovinová Věž" msgid "Upg. Ivory Tower" msgstr "Upg. Slonovinová Věž" msgid "Cloud Castle" msgstr "Zámek Mraků" msgid "Upg. Cloud Castle" msgstr "Upg. Zámek Mraků" msgid "Storm" msgstr "BouÅ™e" msgid "Skull Pile" msgstr "Hromada Lebek" msgid "Excavation" msgstr "Vykopávky" msgid "Graveyard" msgstr "HÅ™bitov" msgid "Upg. Graveyard" msgstr "Upg. HÅ™bitov" msgid "Upg. Pyramid" msgstr "Upg. Pyramida" msgid "Mansion" msgstr "Zámek" msgid "Upg. Mansion" msgstr "Upg. Zámek" msgid "Mausoleum" msgstr "Mauzoleum" msgid "Upg. Mausoleum" msgstr "Upg. Mauzoleum" msgid "Laboratory" msgstr "LaboratoÅ™" msgid "Shrine" msgstr "SvatynÄ›" msgid "" "The Fortifications increase the toughness of the walls, increasing the " "number of turns it takes to knock them down." msgstr "OpevnÄ›ní zvyÅ¡uje odolnost zdí, zvyÅ¡uje poÄet zásahů než budou zniÄeny." msgid "The Farm increases production of Peasants by %{count} per week." msgstr "Farma zvyÅ¡uje produkci Rolníků o %{count} za týden." msgid "" "The Coliseum provides inspiring spectacles to defending troops, raising " "their morale by two during combat." msgstr "" "Koloseum poskytuje inspirující podívanou pro obranu hradu, tím vylepÅ¡uje " "jejich morálku bÄ›hem boje o 2 body." msgid "The Garbage Heap increases production of Goblins by %{count} per week." msgstr "Halda Hnoje zvyÅ¡uje produkci Goblinů o %{count} za týden." msgid "The Rainbow increases the luck of the defending units by two." msgstr "Duha zvyÅ¡uje Å¡tÄ›stí bránících jednotek o dvÄ›." msgid "" "The Crystal Garden increases production of Sprites by %{count} per week." msgstr "KÅ™išťálová zahrada zvyÅ¡uje produkci Víl o %{count} za týden." msgid "The Dungeon increases the income of the town by %{count} gold per day." msgstr "Kobka zvyÅ¡uje mÄ›stské příjmy o %{count} za den." msgid "The Waterfall increases production of Centaurs by %{count} per week." msgstr "Vodopád zvyÅ¡uje produkci Kentaurů o %{count} za týden." msgid "" "The Library increases the number of spells in the Guild by one for each " "level of the guild." msgstr "Knihovna zvyÅ¡uje poÄet kouzel ve věži o jedno na každé úrovni." msgid "The Orchard increases production of Halflings by %{count} per week." msgstr "Ovocný sad zvyÅ¡uje produkci PůlÄíků o %{count} za týden." msgid "The Storm adds +2 to the power of spells of a defending spell caster." msgstr "BouÅ™e pÅ™idává +2 na síle kouzel obránci hradu." msgid "The Skull Pile increases production of Skeletons by %{count} per week." msgstr "Hromada Lebek zvyÅ¡uje produkci Kostlivců o %{count} za týden." msgid "Thieves' Guild" msgstr "Cech zlodÄ›jů" msgid "Tavern" msgstr "Taverna" msgid "Shipyard" msgstr "LodÄ›nice" msgid "Well" msgstr "Studna" msgid "Statue" msgstr "Socha" msgid "Marketplace" msgstr "TržiÅ¡tÄ›" msgid "Moat" msgstr "Příkop" msgid "Castle" msgstr "Hrad" msgid "Tent" msgstr "Stan" msgid "Captain's Quarters" msgstr "Kapitánovo obydlí" msgid "Mage Guild, Level 1" msgstr "Věž Kouzel, Level 1" msgid "Mage Guild, Level 2" msgstr "Věž Kouzel, Level 2" msgid "Mage Guild, Level 3" msgstr "Věž Kouzel, Level 3" msgid "Mage Guild, Level 4" msgstr "Věž Kouzel, Level 4" msgid "Mage Guild, Level 5" msgstr "Věž Kouzel, Level 5" msgid "" "The Shrine increases the necromancy skill of all your necromancers by 10 " "percent." msgstr "" "SvatynÄ› zvyÅ¡uje schopnost nekromantie o 10 procent u vÅ¡ech vaÅ¡ich nekromatnů." msgid "" "The Thieves' Guild provides information on enemy players. Thieves' Guilds " "can also provide scouting information on enemy towns. Additional Guilds " "provide more information." msgstr "" "Cech zlodÄ›jů poskytuje informace o nepřátelích. Může také poskytovat " "Å¡pionážní informace o nepřátelských mÄ›stech. S každým dalším stavením je " "informace pÅ™esnÄ›jší." msgid "The Tavern increases morale for troops defending the castle." msgstr "Taverna zvyÅ¡uje morálku jednotek bránících hrad." msgid "The Shipyard allows ships to be built." msgstr "LodÄ›nice umožňuje stavbu lodí." msgid "" "The Well increases the growth rate of all dwellings by %{count} creatures " "per week." msgstr "" "Studna zvyÅ¡uje tempo růstu ve vÅ¡ech obydlích o %{count} stvoÅ™ení za týden." #, fuzzy msgid "The Statue increases the town's income by %{count} gold per day." msgstr "Socha zvyÅ¡uje mÄ›stské příjmy o %{count} zlata za den." msgid "The Left Turret provides extra firepower during castle combat." msgstr "Levá věž zvyÅ¡uje palebnou sílu pÅ™i obranÄ› hradu." msgid "The Right Turret provides extra firepower during castle combat." msgstr "Pravá věž zvyÅ¡uje palebnou sílu pÅ™i obranÄ› hradu." msgid "" "The Marketplace can be used to convert one type of resource into another. " "The more marketplaces you control, the better the exchange rate." msgstr "" "TržiÅ¡tÄ› slouží ke smÄ›nÄ› surovin navzájem. Čím více tržišť kontrolujete, tím " "lepší je smÄ›nný kurz." msgid "" "The Moat slows attacking units. Any unit entering the moat must end its turn " "there and becomes more vulnerable to attack." msgstr "" "Příkop zpomaluje útoÄící jednotky. Jakákoli jednotka zde musí ukonÄit pohyb " "a je zranitelnÄ›jší pÅ™i útoku." #, fuzzy msgid "" "The Castle improves the town's defense and increases its income to %{count} " "gold per day." msgstr "" "Hrad zlepÅ¡uje obranyschopnost mÄ›sta a zvyÅ¡uje jeho příjmy o %{count} za den." msgid "" "The Tent provides workers to build a castle, provided the materials and the " "gold are available." msgstr "" "Stan poskytuje dÄ›lníky ke stavbÄ› hradu, pokud je k dispozici dostatek " "materiálu a zlaťáků." msgid "" "The Captain's Quarters provides a captain to assist in the castle's defense " "when no hero is present." msgstr "" "Kapitánovo obydlí poskytuje velitele, který bude asistovat pÅ™i obranÄ› hradu " "když v nÄ›m není hrdina." msgid "" "The Mage Guild allows heroes to learn spells and replenish their spell " "points." msgstr "" "Kouzelná Věž umožňuje hrdinům nauÄit se kouzla a doplnit jejich kouzelnou " "energii." msgid "Recruit %{name}" msgstr "Najmout %{name}" msgid "Month: %{month}, Week: %{week}, Day: %{day}" msgstr "MÄ›síc: %{month}, Týden: %{week}, Den: %{day}" msgid "" "You must purchase a spell book to use the mage guild, but you currently have " "no room for a spell book. Try giving one of your artifacts to another hero." msgstr "" "Pro vstup do věže si musíš zakoupit knihu kouzel, teÄ ale nemáš pro ni " "místo. Zkus podarovat jeden ze svých artefaktů jinému hrdinovi." msgid "Click to show next town." msgstr "Klikni pro zobrazení následujícího mÄ›sta." msgid "Show next town" msgstr "Zobrazit následující mÄ›sto" msgid "Click to show previous town." msgstr "Klikni pro zobrazení pÅ™edchozího mÄ›sta." msgid "Show previous town" msgstr "Zobrazit pÅ™edchozí mÄ›sto" msgid "This town may not be upgraded to a castle." msgstr "Toto mÄ›sto nemůže být povýšeno na hrad." msgid "Town" msgstr "MÄ›sto" msgid "Exit Castle" msgstr "Opustit hrad" msgid "Exit Town" msgstr "Opustit mÄ›sto" msgid "Show Income" msgstr "Zobraz Příjmy" msgid "View Hero" msgstr "Zobrazit hrdinu" msgid "The above spells are available here." msgstr "Nacházejí se zde výše uvedená kouzla." msgid "The spells the hero can learn have been added to their book." msgstr "Do knihy byla pÅ™idána kouzla, která se hrdina může nauÄit." msgid "A generous tip for the barkeep yields the following rumor:" msgstr "Vrchní po Å¡tÄ›drém spropitném postil tuto zvÄ›st:" msgid "Recruit Hero" msgstr "Najmout hrdinu" msgid "%{name} is a level %{value} %{race} " msgstr "%{name} je level %{value} %{race} " msgid "with %{count} artifacts." msgstr "s %{count} artefakty." msgid "with 1 artifact." msgstr "s 1 artefaktem." msgid "without artifacts." msgstr "bez artefaktů." msgid "Recruit %{name} the %{race}" msgstr "Najmout %{name} %{race}" #, fuzzy msgid "" "'Spread' combat formation spreads the castle's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "'Rozvinutá' bojová formace roztáhne tvoje jednotky shora až dolů po celém " "bojiÅ¡ti, s minimálnÄ› jedným volným polem mezi jednotkami." #, fuzzy msgid "" "'Grouped' combat formation bunches the castle's units together in the center " "of the castle's side of the battlefield." msgstr "" "'Seskupená' bojová formace srazí tvoje jednotky do stÅ™edu bojiÅ¡tÄ› na tvé " "stranÄ› bojiÅ¡tÄ›." msgid "Spread Formation" msgstr "Rozvinutá formace" msgid "Grouped Formation" msgstr "Seskupená formace" msgid "Set garrison combat formation to 'Spread'" msgstr "Nastavit bojovou formaci pevnosti na 'Rozvinutá'" msgid "Set garrison combat formation to 'Grouped'" msgstr "Nastavit bojovou formaci pevnosti na 'Seskupená'" msgid "Exit Castle Options" msgstr "PryÄ z Možnosti Hradu" msgid "Castle Options" msgstr "Možnosti Hradu" msgid "Not enough resources to recruit creatures." msgstr "Nemáš dost prostÅ™edků pro najmutí stvoÅ™ení." msgid "You are unable to recruit at this time, your ranks are full." msgstr "Tento krát nemůžeÅ¡ nikoho najmout, Tvé Å™ady jsou již plné." msgid "No creatures available for purchase." msgstr "Nejsou zde žádné stvoÅ™ení k najmutí." msgid "Recruit Creatures" msgstr "Najmout StvoÅ™ení" msgid "Max" msgstr "Max" msgid "Hire all creatures in the town." msgstr "Najmout vÅ¡echna stvoÅ™ení ve mÄ›stÄ›." msgid "Available" msgstr "Dostupných" msgid "Town Population Information and Statistics" msgstr "Populace ve mÄ›stÄ› Informace a Statistiky" msgid "Damage" msgstr "Å kody" msgid "HP" msgstr "Živ" msgid "Growth" msgstr "Růst" msgid "week" msgstr "týd" msgid "View World" msgstr "Zobrazit SvÄ›t" msgid "View the entire world." msgstr "Zobraz celou mapu svÄ›ta." msgid "Puzzle" msgstr "Skládanka" msgid "View the obelisk puzzle." msgstr "Zobraz skládanku obelisků." msgid "Scenario Information" msgstr "Informace o HÅ™e" msgid "View information on the scenario you are currently playing." msgstr "Zobraz informaci o hÅ™e, kterou právÄ› hrajeÅ¡." msgid "Dig for the Ultimate Artifact." msgstr "Vykopej ultimátní Artefakt." msgid "Digging" msgstr "Kopání" msgid "Arena" msgstr "Aréna" msgid "" "You enter the arena and face a pack of vicious lions. You handily defeat " "them, to the wild cheers of the crowd. Impressed by your skill, the aged " "trainer of gladiators agrees to train you in a skill of your choice." msgstr "" "VstupujeÅ¡ do arény kde se utkáš se smeÄkou rozzuÅ™ených lvů. Lehce je " "porazíš, za bouÅ™livého jásotu davu. Ohromen tvými schopnostmi se starý " "trenér gladiátorů uvolí, že TÄ› vycviÄí v dovednosti, kterou si sám vybereÅ¡." msgid "Attack Skill" msgstr "ÚtoÄná Síla" msgid "Defense Skill" msgstr "Obranná Síla" msgid "Shots" msgstr "VýstÅ™ely" msgid "Shots Left" msgstr "Zbývá ran" msgid "Hit Points" msgstr "Životy" msgid "Hit Points Left" msgstr "Zbývá životů" msgid "You can't afford to upgrade your troops!" msgstr "NemůžeÅ¡ si dovolit vylepÅ¡it své jednotky!" msgid "" "Your troops can be upgraded, but it will cost you dearly. Do you wish to " "upgrade them?" msgstr "" "Tvé jednotky mohou být vylepÅ¡eny ale bude TÄ› to stát balík. PÅ™ejeÅ¡ si je " "pořád vylepÅ¡it?" msgid "Are you sure you want to dismiss this army?" msgstr "Jsi si jist, že chceÅ¡ jednotku propustit?" msgid "Upgrade" msgstr "Povýšit" msgid "Upgrade your troops." msgstr "VylepÅ¡i své jednotky." msgid "Dismiss" msgstr "Propustit" msgid "Dismiss this army." msgstr "Propustit tuto armádu." msgid "" "A group of %{monster} with a desire for greater glory wish to join you.\n" "Do you accept?" msgstr "" "Skupina %{monster} s touhou po velké slávÄ› se chce k TobÄ› pÅ™idat.\n" "PÅ™ijímáš?" msgid "Followers" msgstr "Následovníci" msgid "" "The %{monster} is swayed by your diplomatic tongue, and offers to join your " "army for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "%{monster} jsou uchlácholeni Tvou diplomatickou Å™eÄí a nabízí Ti, že vstoupí " "do Tvé armády za %{gold} zlaťáků.\n" "PÅ™ijímáš?" msgid "" "The creatures are swayed by your diplomatic\n" "tongue, and make you an offer:\n" "\n" msgstr "" "StvoÅ™ení jsou uchlácholeni Tvou diplomatickou\n" "Å™eÄí a davají Ti nabídku:\n" "\n" msgid "" "%{offer} of the %{total} %{monster} will join your army, and the rest will " "leave you alone, for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "%{offer} z %{total} %{monster} se pÅ™idá ke Tvé armádÄ›, a zbytek TÄ› nechá na " "pokoji za sumu %{gold} zlaťáků.\n" "PÅ™ijímáš?" msgid "" "All %{offer} of the %{monster} will join your army for the sum of %{gold} " "gold.\n" "Do you accept?" msgstr "" "VÅ¡ech %{offer} %{monster} se pÅ™idá ke Tvé armádÄ› za sumu %{gold} zlaťáků. " "PÅ™ijímáš?" msgid "(Rate: %{percent})" msgstr "(Hodn.:%{percent})" msgid "off" msgstr "off" msgid "Music" msgstr "Hudba" msgid "Effects" msgstr "Efekty" msgid "MIDI" msgstr "MIDI" msgid "MIDI Expansion" msgstr "MIDI Rozšířené" msgid "External" msgstr "Externí" msgid "Music Type" msgstr "Typ Hudby" msgid "3D Audio" msgstr "3D Audio" msgid "Toggle ambient music level." msgstr "Nastav úroveň hudby v pozadí." msgid "Toggle foreground sounds level." msgstr "Nastav úroveň herních zvuků." msgid "Change the type of music." msgstr "Změň typ hudby." msgid "Toggle 3D effects of foreground sounds." msgstr "Zap/Vyp 3D efekty herních zvuků." msgid "Build a new ship:" msgstr "Postavit novou loÄ:" msgid "Resource cost:" msgstr "Náklady:" msgid "Total: " msgstr "Celkem: " msgid "Need: " msgstr "PotÅ™eba: " msgid "Load Game" msgstr "NaÄíst Hru" msgid "No save files to load." msgstr "Žádné uložené hry k naÄtení." msgid "New Game" msgstr "Nová Hra" msgid "Start a single or multi-player game." msgstr "Spusti hru jednoho nebo více hráÄů." msgid "Load a previously saved game." msgstr "NaÄti dříve uloženou hru." msgid "Save Game" msgstr "Uložit Hru" msgid "Save the current game." msgstr "Uložit aktuální hru." msgid "Quit" msgstr "UkonÄit" msgid "Quit out of Heroes of Might and Magic II." msgstr "UkonÄit Heroes of Might and Magic II." msgid "Language" msgstr "Jazyk" msgid "Graphics" msgstr "Grafika" msgid "Black & White" msgstr "ÄŒernobílý" msgid "Mouse Cursor" msgstr "Kurzor MyÅ¡i" msgid "Color" msgstr "Barevný" msgid "Text Support" msgstr "Textová podpora" msgid "Change the language of the game." msgstr "ZmÄ›nit jazyk hry." msgid "Select Game Language" msgstr "Vybrat jazyk hry" msgid "Change the graphics settings of the game." msgstr "ZmÄ›nit grafické nastavení hry." msgid "Toggle colored cursor on or off. This is only an aesthetic choice." msgstr "PÅ™epnout barevný kurzor zap/vyp. Toto je jenom volba vzhledu." msgid "" "Toggle text support mode to output extra information about windows and " "events in the game." msgstr "" "Zap/Vyp textovou podporu - výstupem budou extra informace o oknech a " "událostech ve hÅ™e." msgid "" "Map\n" "Difficulty" msgstr "" "Mapa\n" "Obtížnost" msgid "" "Game\n" "Difficulty" msgstr "" "Hra\n" "Obtížnost" msgid "Rating" msgstr "Hodnocení" msgid "Map Size" msgstr "" "Velikost\n" "mapy" msgid "Opponents" msgstr "Protivníci" msgid "Class" msgstr "Třída" msgid "" "Victory\n" "Conditions" msgstr "" "Podmínky\n" "pro výhru" msgid "" "Loss\n" "Conditions" msgstr "" "Podmínky\n" "pro prohru" msgid "First select recipients!" msgstr "" msgid "You cannot select %{resource}!" msgstr "" msgid "Select count %{resource}:" msgstr "" msgid "Select Recipients" msgstr "" msgid "Your Funds" msgstr "Tvé zdroje" msgid "Planned Gift" msgstr "Plánovaný Dar" msgid "Gift from %{name}" msgstr "Dar od %{name}" msgid "Resolution" msgstr "RozliÅ¡ení" msgid "Fullscreen" msgstr "Celá obrazovka" msgid "window|Mode" msgstr "Mód" msgid "Windowed" msgstr "Okno" msgid "V-Sync" msgstr "V-Sync" msgid "on" msgstr "zap" msgid "FPS" msgstr "FPS" msgid "System Info" msgstr "Info o systému" msgid "Change the resolution of the game." msgstr "ZmÄ›nit rozlíšení" msgid "Select Game Resolution" msgstr "Vybrat rozliÅ¡ení" msgid "Toggle between fullscreen and windowed modes." msgstr "PÅ™epnout mezi celou obrazovkou a zobr. v oknÄ›." msgid "" "The V-Sync option can be enabled to resolve flickering issues on some " "monitors." msgstr "V-Sync může pomoct s blikáním na nÄ›kterých monitorech." msgid "Show extra information such as FPS and current time." msgstr "Zobraz extra informace jako FPS a místní Äas." msgid "Hotkey: " msgstr "" msgid "Category: " msgstr "" #, fuzzy msgid "Event: " msgstr "Událost" msgid "Hot Keys:" msgstr "Kláv. zkratky:" msgid "Evil" msgstr "Zlý" msgid "Good" msgstr "Dobrý" msgid "Interface Type" msgstr "Rozhraní" #, fuzzy msgid "Hide" msgstr "Horda" #, fuzzy msgid "Show" msgstr "Sníh" msgid "Interface" msgstr "Rozhraní" msgid "Slow" msgstr "Pomalá" msgid "Normal" msgstr "Normální" msgid "Fast" msgstr "Rychlá" msgid "Very Fast" msgstr "Velmi rychlá" msgid "Scroll Speed" msgstr "Rychlost posunu" msgid "Toggle the type of interface you want to use." msgstr "" msgid "Toggle interface visibility." msgstr "" msgid "Toggle colored cursor on or off. This is only an esthetic choice." msgstr "PÅ™epnout barevný kurzor zap/vyp. Toto je jenom volba vzhledu." msgid "Sets the speed at which you scroll the window." msgstr "" msgid "Select Game Language:" msgstr "Vybrat jazyk:" msgid "Click to choose the selected language." msgstr "Kliknutím vybereÅ¡ zvolený jazyk." msgid "%{name} has gained a level." msgstr "%{name} jde o level výš." msgid "%{skill} +1" msgstr "%{skill} +1" msgid "You have learned %{skill}." msgstr "NauÄil ses %{skill}." #, fuzzy msgid "" "%{name} has gained a level.\n" "\n" "%{skill} +1" msgstr "%{name} jde o level výš." #, fuzzy msgid "" "You may learn either:\n" "%{skill1}\n" "or\n" "%{skill2}" msgstr "MůžeÅ¡ se nauÄit:" msgid "" "Please inspect our fine wares. If you feel like offering a trade, click on " "the items you wish to trade with and for." msgstr "" "Prohlédni si prosím naÅ¡e kvalitní zboží. Pokud máš chuÅ¥ nabídnout nÄ›co na " "výmÄ›nu, klikni na položky, které chceÅ¡ vymÄ›nit za jiné." msgid "" "You have received quite a bargain. I expect to make no profit on the deal. " "Can I interest you in any of my other wares?" msgstr "" "Nakoupil jsi opravdu výhodnÄ›. Myslím, že na tomhle obchodu skoro nic " "nevydÄ›lám. Zaujalo TÄ› jeÅ¡tÄ› nÄ›jaké jiné zboží?" msgid "I can offer you %{count} for 1 unit of %{resfrom}." msgstr "Mohu Ti nabídnout %{count} za 1 díl %{resfrom}." msgid "I can offer you 1 unit of %{resto} for %{count} units of %{resfrom}." msgstr "Mohu Ti nabídnout 1 díl %{resto} za %{count} dílů %{resfrom}." msgid "Min" msgstr "Min" msgid "Qty to trade" msgstr "Mn. k výmÄ›nÄ›" msgid "Trading Post" msgstr "SmÄ›nárna" msgid "Your Resources" msgstr "Tvoje zdroje" msgid "Available Trades" msgstr "Možno zamÄ›nit" msgid "n/a" msgstr "n/a" msgid "guarded by %{count} %{monster}" msgstr "hlídáno %{count} %{monster}" msgid "guarded by " msgstr "hlídáno " msgid "(available: %{count})" msgstr "(K dispozici: %{count})" msgid "(empty)" msgstr "(prázdno)" msgid "already learned" msgstr "již to umíš" #, fuzzy msgid "treeOfKnowledge|free" msgstr "Strom poznání" #, fuzzy msgid "already claimed" msgstr "již to umíš" msgid "not claimed" msgstr "" msgid "already knows this skill" msgstr "tuhle dovednost již máš" msgid "already has max skills" msgstr "již má maximální dovednosti" msgid "(already visited)" msgstr "(již navÅ¡tíveno)" msgid "(not visited)" msgstr "(nenavÅ¡tíveno)" msgid "%{color} Barrier" msgstr "%{color} Bariéra" #, fuzzy msgid "(tent visited)" msgstr "(nenavÅ¡tíveno)" msgid "%{color} Tent" msgstr "%{color} Stan" msgid "Road" msgstr "Cesta" msgid "(digging ok)" msgstr "(lze kopat)" msgid "(no digging)" msgstr "(nelze kopat)" msgid "penalty: %{cost}" msgstr "odebere: %{cost}" msgid "Defenders:" msgstr "Obránci:" msgid "Unknown" msgstr "Není známo" msgid "%{name} (Level %{level})" msgstr "%{name} (Level %{level})" msgid "Attack:" msgstr "Útok:" msgid "Defense:" msgstr "Obrana:" msgid "Spell Power:" msgstr "Síla Kouzel:" msgid "Knowledge:" msgstr "Znalosti:" msgid "Spell Points:" msgstr "Body Kouzla:" msgid "Move Points:" msgstr "Body Pohybu:" msgid "Uncharted Territory" msgstr "Neprozkoumané území" msgid "Cost per troop:" msgstr "Cena za jedince:" msgid "Available: %{count}" msgstr "K dispozici: %{count}" msgid "Number to buy:" msgstr "Nakoupit:" msgid "Recruit selected monsters." msgstr "Najmout vybrané stvůry." msgid "Select maximum monsters to be recruited." msgstr "Vybrat maximální poÄet k najmutí." msgid "Select only 1 monster to be recruited." msgstr "Vybrat pouze 1 stvůru k najmutí." msgid "Select Game Resolution:" msgstr "Nastav rozliÅ¡ení hry:" msgid "Click to apply the selected resolution." msgstr "Kliknutím se rozliÅ›ení zmÄ›ní na vybrané. " #, fuzzy msgid "Click to apply the entered text." msgstr "Klikni pro uložení aktuální hry." msgid "Click to open the Virtual Keyboard dialog." msgstr "" msgid "Open Virtual Keyboard" msgstr "" msgid "How many troops to move?" msgstr "" msgid "Fast separation into slots:" msgstr "" msgid "Map: " msgstr "Mapa: " msgid "" "\n" "\n" "Month: " msgstr "" "\n" "\n" "MÄ›síc: " msgid ", Week: " msgstr ", Týden: " msgid ", Day: " msgstr ", Den: " msgid "" "\n" "\n" "Location: " msgstr "" "\n" "\n" "Cesta: " msgid "Click to save the current game." msgstr "Klikni pro uložení aktuální hry." msgid "Click to load a previously saved game." msgstr "Klikni pro naÄtení dříve uložené hry." msgid "Are you sure you want to delete file:" msgstr "Jsi si jist, že chceÅ¡ smazat soubor:" msgid "Warning!" msgstr "Výstraha!" msgid "File to Save:" msgstr "Uložit do souboru:" msgid "File to Load:" msgstr "Soubor k naÄtení:" msgid "Accept the choice made." msgstr "" #, fuzzy msgid "%{color} %{race} hero" msgstr "%{color} hráÄ" msgid "Terrain object" msgstr "" msgid "Select Skill:" msgstr "" #, fuzzy msgid "Select Spell:" msgstr "Seslat kouzlo" #, fuzzy msgid "Select Artifact:" msgstr "Artefakt" #, fuzzy msgid "Select Monster:" msgstr "Vybrat %{name}" #, fuzzy msgid "Select Hero:" msgstr "Nejlepší hrdina:" #, fuzzy msgid "Select Treasure:" msgstr "Nejlepší hrdina:" #, fuzzy msgid "Select Ocean Object:" msgstr "Artefakt" #, fuzzy msgid "Castle/town placing" msgstr "Možnosti Hradu" msgid "doubleLinedRace|Neutral" msgstr "" msgid "race|Random" msgstr "" #, fuzzy msgid "Click to start placing the selected castle/town." msgstr "Kliknutím se rozliÅ›ení zmÄ›ní na vybrané. " #, fuzzy msgid "Click to select town placing." msgstr "Klikni pro zobrazení následujícího mÄ›sta." #, fuzzy msgid "Click to select castle placing." msgstr "Kliknout sem pro výbÄ›r scénáře." #, fuzzy msgid "%{color} %{race} %{townOrCastle}" msgstr "%{color} hráÄ" msgid "race|Neutral" msgstr "" msgid "You will place" msgstr "" #, fuzzy msgid "Click to select this class." msgstr "Kliknout sem pro výbÄ›r scénáře." #, fuzzy msgid "Click to select this color." msgstr "Kliknout sem pro výbÄ›r scénáře." #, fuzzy msgid "Select Dwelling:" msgstr "Seslat kouzlo" #, fuzzy msgid "Select Landscape Object:" msgstr "Artefakt" msgid "Mine placing" msgstr "" #, fuzzy msgid "" "Resource\n" "type:" msgstr "Zdroje" #, fuzzy msgid "%{mineName} appearance:" msgstr "%{name} z rodu %{race}" #, fuzzy msgid "Click to select %{object} as the resource generator to be placed." msgstr "Kliknout sem pro výbÄ›r scénáře." #, fuzzy msgid "Select Mountain Object:" msgstr "Artefakt" #, fuzzy msgid "Select Rock Object:" msgstr "Artefakt" #, fuzzy msgid "Select Tree Object:" msgstr "Artefakt" #, fuzzy msgid "Select Power Up Object:" msgstr "Artefakt" #, fuzzy msgid "Select Adventure Object:" msgstr "Artefakt" #, fuzzy msgid "Map Type:\n" msgstr "Velikost mapy" msgid "The Succession Wars" msgstr "" msgid "The Price of Loyalty" msgstr "Cena za vÄ›rnost" #, fuzzy msgid "Resurrection" msgstr "Oživení" msgid "Lose all your heroes and towns." msgstr "" msgid "Lose a specific town." msgstr "" msgid "Lose a specific hero." msgstr "" msgid "Run out of time. Fail to win by a certain point." msgstr "" msgid "Loss Condition" msgstr "" msgid "Defeat all enemy heroes and towns." msgstr "" msgid "Capture a specific town." msgstr "" msgid "Defeat a specific hero." msgstr "" msgid "Find a specific artifact." msgstr "" msgid "Your side defeats the opposing side." msgstr "" msgid "Accumulate a large amount of gold." msgstr "" msgid "Victory Condition" msgstr "" #, fuzzy msgid "Map difficulty:" msgstr "" "Mapa\n" "Obtížnost" msgid "N" msgstr "" msgid "No maps exist at that size" msgstr "" msgid "Small Maps" msgstr "Malé mapy" msgid "View only maps of size small (36 x 36)." msgstr "Zobrazí pouze mapy velikosti malá (36 x 36)." msgid "Medium Maps" msgstr "StÅ™ední mapy" msgid "View only maps of size medium (72 x 72)." msgstr "Zobrazí pouze mapy velikosti stÅ™ední (72 x 72)." msgid "Large Maps" msgstr "Velké mapy" msgid "View only maps of size large (108 x 108)." msgstr "Zobrazí pouze mapy velikosti velká (108 x 108)." msgid "Extra Large Maps" msgstr "Velmi velké mapy" msgid "View only maps of size extra large (144 x 144)." msgstr "Zobrazí pouze mapy velikosti Velmi velká (144 x 144)." msgid "All Maps" msgstr "VÅ¡echny mapy" msgid "View all maps, regardless of size." msgstr "Zobrazí vÅ¡echny mapy, nezávisle na velikosti." msgid "Players Icon" msgstr "" msgid "" "Indicates how many players total are in the scenario. Any positions not " "occupied by humans will be occupied by computer players." msgstr "" msgid "" "Indicates whether the map\n" "is small (36 x 36), medium\n" "(72 x 72), large (108 x 108),\n" "or extra large (144 x 144)." msgstr "" msgid "Size Icon" msgstr "" msgid "" "Indicates whether the map is made for \"The Succession Wars\" or \"The Price " "of Loyalty\" version of the game." msgstr "" #, fuzzy msgid "Map Type" msgstr "Velikost mapy" msgid "Selected Name" msgstr "" msgid "The name of the currently selected map." msgstr "" msgid "Selected Map Difficulty" msgstr "" msgid "" "The map difficulty of the currently selected map. The map difficulty is " "determined by the scenario designer. More difficult maps might include more " "or stronger enemies, fewer resources, or other special conditions making " "things tougher for the human player." msgstr "" msgid "Selected Description" msgstr "" msgid "The description of the currently selected map." msgstr "" msgid "Jump" msgstr "" #, fuzzy msgid "Hero Speed" msgstr "Rychlost" msgid "Don't Show" msgstr "" #, fuzzy msgid "Enemy Speed" msgstr "Rychlost" msgid "Auto Resolve" msgstr "Automaticky" msgid "Auto, No Spells" msgstr "Auto, Bez kouzel" msgid "Battles" msgstr "" msgid "autoBattle|Manual" msgstr "" msgid "Change the speed at which your heroes move on the main screen." msgstr "" msgid "" "Sets the speed that A.I. heroes move at. You can also elect not to view A.I. " "movement at all." msgstr "" msgid "Change the interface settings of the game." msgstr "ZmÄ›nit grafické nastavení hry." msgid "Interface Settings" msgstr "Grafické Nastavení" msgid "Toggle instant battle mode." msgstr "" msgid "Att." msgstr "Útk." msgid "Def." msgstr "Obr." msgid "Power" msgstr "Síla" msgid "Knowl" msgstr "Znal." msgid "1st" msgstr "" msgid "2nd" msgstr "" msgid "3rd" msgstr "" msgid "4th" msgstr "" msgid "5th" msgstr "" msgid "6th" msgstr "" msgid "Oracle: Player Rankings" msgstr "PoÅ™adí hráÄů" msgid "Thieves' Guild: Player Rankings" msgstr "PoÅ™adí hráÄů" msgid "Number of Towns:" msgstr "PoÄet mÄ›st:" msgid "Number of Castles:" msgstr "PoÄet hradů:" msgid "Number of Heroes:" msgstr "PoÄet hrdinů:" msgid "Gold in Treasury:" msgstr "Zlato v pokladici:" msgid "Wood & Ore:" msgstr "DÅ™evo & Ruda:" msgid "Gems, Cr, Slf & Mer:" msgstr "Drah., Kryst., Síra:" msgid "Obelisks Found:" msgstr "Nalezeno Obelisků:" msgid "Artifacts:" msgstr "Artefakty:" msgid "Total Army Strength:" msgstr "Celková síla armády:" msgid "Income:" msgstr "Příjem:" msgid "Best Hero:" msgstr "Nejlepší hrdina:" msgid "Best Hero Stats:" msgstr "Statistiky nej. hrdiny:" msgid "Personality:" msgstr "Osobnost:" msgid "Best Monster:" msgstr "Nejlepší Stvůra:" #, fuzzy msgid "Map filename" msgstr "" "Velikost\n" "mapy" #, fuzzy msgid "" "Are you sure you want to load a new map? (Any unsaved changes to the current " "map will be lost.)" msgstr "Jsi si jist, že chceÅ¡ naÄíst novou hru? (Aktuální hra bude ztracena.)" #, fuzzy msgid "" "Are you sure you want to create a new map? (Any unsaved changes to the " "current map will be lost.)" msgstr "Jsi si jist, že chceÅ¡ naÄíst novou hru? (Aktuální hra bude ztracena.)" msgid "" "Create a new map, either from scratch or using the random map generator." msgstr "" #, fuzzy msgid "New Map" msgstr "New Dawn" #, fuzzy msgid "Load Map" msgstr "NaÄíst Hru" msgid "Load an existing map." msgstr "" #, fuzzy msgid "Save Map" msgstr "Uložit Hru" #, fuzzy msgid "Save the current map." msgstr "Uložit aktuální hru." msgid "Quit out of the map editor." msgstr "" msgid "Objects cannot be placed outside the map." msgstr "" msgid "Monsters cannot be placed on water." msgstr "" msgid "Choose a tile which does not contain any objects." msgstr "" msgid "Treasures cannot be placed on water." msgstr "" msgid "Heroes cannot be placed on water." msgstr "" msgid "" "A maximum of %{count} heroes of the same color can be placed on the map." msgstr "" msgid "Artifacts cannot be placed on water." msgstr "" #, fuzzy msgid "Mountains cannot be placed on water." msgstr "Toto mÄ›sto nemůže být povýšeno na hrad." #, fuzzy msgid "Rocks cannot be placed on water." msgstr "Toto mÄ›sto nemůže být povýšeno na hrad." #, fuzzy msgid "Trees cannot be placed on water." msgstr "Toto mÄ›sto nemůže být povýšeno na hrad." msgid "Ocean object must be placed on water." msgstr "" #, fuzzy msgid "Landscape objects cannot be placed on water." msgstr "Toto mÄ›sto nemůže být povýšeno na hrad." #, fuzzy msgid "Towns cannot be placed on water." msgstr "Toto mÄ›sto nemůže být povýšeno na hrad." #, fuzzy msgid "Mines cannot be placed on water." msgstr "Toto mÄ›sto nemůže být povýšeno na hrad." #, fuzzy msgid "Dwellings cannot be placed on water." msgstr "Toto mÄ›sto nemůže být povýšeno na hrad." #, fuzzy msgid "Power-ups cannot be placed on water." msgstr "Toto mÄ›sto nemůže být povýšeno na hrad." #, fuzzy msgid "Adventure objects cannot be placed on water." msgstr "Toto mÄ›sto nemůže být povýšeno na hrad." #, fuzzy msgid "Used to place %{object}." msgstr "Nalezení ultimátního artefaktu." #, fuzzy msgid "Select object type" msgstr "Artefakt" #, fuzzy msgid "" "Click here to\n" "select a monster." msgstr "Kliknout sem pro výbÄ›r scénáře." #, fuzzy msgid "" "Click here to\n" "select another monster." msgstr "Vyber jiného hrdinu." msgid "" "Cell\n" "Details" msgstr "" #, fuzzy msgid "Roads" msgstr "Cesta" msgid "Streams" msgstr "" msgid "Erase" msgstr "" msgid "Mountains" msgstr "Hory" #, fuzzy msgid "Rocks" msgstr "Skála" msgid "Trees" msgstr "Stromy" #, fuzzy msgid "Water Objects" msgstr "Pustina" msgid "Miscellaneous" msgstr "" msgid "Artifacts" msgstr "Artefakty" #, fuzzy msgid "Dwellings" msgstr "PůlÄíci" msgid "Mines" msgstr "Doly" #, fuzzy msgid "Power-ups" msgstr "Síla" #, fuzzy msgid "Treasures" msgstr "Poklad" #, fuzzy msgid "Towns" msgstr "MÄ›sto" msgid "Heroes" msgstr "Hrdinové" msgid "editorErasure|Terrain" msgstr "" msgid "editorErasure|Castles" msgstr "" msgid "editorErasure|Monsters" msgstr "" msgid "editorErasure|Heroes" msgstr "" msgid "editorErasure|Artifacts" msgstr "" msgid "editorErasure|Roads" msgstr "" msgid "editorErasure|Streams" msgstr "" msgid "editorErasure|Treasures" msgstr "" msgid "" "Draws terrain in\n" "%{size} by %{size} square increments." msgstr "" msgid "" "Erases objects in\n" "%{size} by %{size} square increments." msgstr "" #, fuzzy msgid "Small Brush" msgstr "Malé mapy" #, fuzzy msgid "Medium Brush" msgstr "StÅ™ední mapy" #, fuzzy msgid "Large Brush" msgstr "Velké mapy" msgid "Area Fill" msgstr "" msgid "Used to click and drag for filling in large areas." msgstr "" msgid "Clear Area" msgstr "" msgid "Used to click and drag for clearing large areas." msgstr "" msgid "" "Costs %{rate} times normal movement for all heroes. (Pathfinding reduces or " "eliminates the penalty.)" msgstr "" msgid "Traversable only by boat." msgstr "" msgid "No special modifiers." msgstr "" msgid "Toggle the erasure of %{type} objects." msgstr "" msgid "" "Objects of this type will be deleted with the Erase tool. Left-click here to " "deselect this type. Press and hold this button to deselect all other object " "types." msgstr "" msgid "" "Objects of this type will NOT be deleted with the Erase tool. Left-click " "here to select this type. Press and hold this button to select all other " "object types." msgstr "" msgid "Terrain Mode" msgstr "" msgid "Used to draw the underlying grass, dirt, water, etc. on the map." msgstr "" msgid "Landscape Objects Mode" msgstr "" msgid "" "Used to place landscape objects (mountains, rocks, trees, etc.) on the map." msgstr "" msgid "Detail Mode" msgstr "" msgid "Used for special editing of monsters, heroes and towns." msgstr "" #, fuzzy msgid "Adventure Objects Mode" msgstr "Možnosti hry" msgid "" "Used to place adventure objects (artifacts, dwellings, mines, treasures, " "etc.) on the map." msgstr "" #, fuzzy msgid "Kingdom Objects Mode" msgstr "Příjmy království" msgid "Used to place kingdom objects (towns, castles and heroes) on the map." msgstr "" #, fuzzy msgid "Monsters Mode" msgstr "Monstra" #, fuzzy msgid "Used to place monsters on the map." msgstr "Nalezení ultimátního artefaktu." msgid "Allows you to draw streams by clicking and dragging." msgstr "" msgid "Stream Mode" msgstr "" msgid "Allows you to draw roads by clicking and dragging." msgstr "" #, fuzzy msgid "Road Mode" msgstr "Cesta" msgid "Erase Mode" msgstr "" msgid "Used to erase objects from the map." msgstr "" msgid "Change between zoom and normal view." msgstr "" msgid "Magnify" msgstr "" msgid "Undo" msgstr "" msgid "Undo your last action." msgstr "" msgid "Create a new map either from scratch or using the random map generator." msgstr "" msgid "Edit map title, description, and other general information." msgstr "" #, fuzzy msgid "Specifications" msgstr "ZkamenÄ›ní" msgid "File Options" msgstr "Soubory" #, fuzzy msgid "" "Open the file options menu, where you can save or load maps, or quit out of " "the editor." msgstr "" "Zobrazit volby pro uložení, naÄtení hry, stuÅ¡tÄ›ní nové hry anebo ukonÄení " "stávající." #, fuzzy msgid "View the editor system options, which let you customize the editor." msgstr "Zobrazí systémové menu, s volbami pro úpravu chování hry." msgid "Create a map that is %{size} squares wide and %{size} squares high." msgstr "" msgid "Cancel back to the New Map menu." msgstr "" msgid "Cancel back to the main menu." msgstr "" msgid "From Scratch" msgstr "" msgid "Start from scratch with a blank map." msgstr "" msgid "Create a randomly generated map." msgstr "" #, fuzzy msgid "Random" msgstr "Náhodné mÄ›sto" #, fuzzy msgid "Cancel back to the Map Editor main menu." msgstr "Klikni pro návrat do hlavního menu." msgid "No maps available!" msgstr "Žádné mapy k dispo!" msgid "Warning" msgstr "Varování" msgid "difficulty|Easy" msgstr "Lehká" msgid "difficulty|Normal" msgstr "Normální" msgid "difficulty|Hard" msgstr "Těžká" msgid "difficulty|Expert" msgstr "Expert" msgid "difficulty|Impossible" msgstr "Nemožné" msgid "and more..." msgstr "a víc..." msgid "Easy" msgstr "Lehká" msgid "Hard" msgstr "Těžká" msgid "Campaign Difficulty" msgstr "Obtížnost kampanÄ›" msgid "" "Choose this difficulty if you want to prefer game story over challenge. AI " "is weaker in comparison with normal difficulty." msgstr "" "Zvol tuto obtížnost pokud preferujeÅ¡ příbÄ›h víc než výzvu. UmÄ›lá inteligence " "bude slabší v porovnání s nomální obtížností." msgid "" "Choose this difficulty to enjoy the campaign as per the original design." msgstr "" "Zvol tuto obtížnost a vychutnej si kampaň stejnÄ› jako v originální hÅ™e." msgid "" "Choose this difficulty if you want challenge. AI is stronger in comparison " "with normal difficulty." msgstr "" "Zvol tuto obtížnost pokud cheÅ¡ pořádnou výzvu. UmÄ›lá inteligence bude " "silnÄ›jší v porovnání s nomální obtížností." #, fuzzy msgid "" "Congratulations!\n" "\n" "Days: %{days}\n" msgstr "Gratulace!" msgid "" "\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" msgid "" "Score: %{score}\n" "\n" "Rating:\n" "%{rating}" msgstr "" msgid "Start the selected scenario." msgstr "Odstartuj vybranou hru." msgid "View Intro" msgstr "Zobraz Intro" msgid "View Intro videos for the current state of the campaign." msgstr "Zobraz Intro k aktuálnímu pokroku v kampani." #, fuzzy msgid "" "Select the campaign difficulty. This can be lowered at any point during the " "campaign." msgstr "Zvol si obtížnost kampanÄ›. Nemůže být pozdÄ›ji zmÄ›nÄ›no." msgid "Restart the current scenario." msgstr "Restartuj aktuální hru." #, fuzzy msgid "Difficulty" msgstr "" "Mapa\n" "Obtížnost" msgid "" "You have changed to a lower difficulty for the campaign. You will not be " "able to revert this after this point. The high score will be calculated " "based solely on the new difficulty. Do you want to proceed?" msgstr "" msgid "Are you sure you want to restart this scenario?" msgstr "Jsi si jist, že chceÅ¡ zaÄit hrát od znovu?" msgid "Campaign Scenario loading failure" msgstr "NapodaÅ™ilo se naÄíst hru kampanÄ›" msgid "Please make sure that campaign files are correct and present." msgstr "Prosím ujistÄ›te se, že jsou soubory v pořádku a k dispozici." msgid "Days spent" msgstr "UbÄ›hlo dní" msgid "The number of days spent on this campaign." msgstr "PoÄet dní od zaÄátku této kampanÄ›." msgid "Project Coordination and Core Development" msgstr "" msgid "Development" msgstr "" msgid "Visit us at " msgstr "" msgid "QA and Support" msgstr "" msgid "Dev and Support" msgstr "" msgid "Special Thanks to" msgstr "" msgid "and many other contributors!" msgstr "" msgid "and many-many other supporters!" msgstr "" msgid "Support us at" msgstr "" msgid "local-donation-platform|https://www.patreon.com/fheroes2" msgstr "" msgid "Connect with us at" msgstr "" msgid "local-social-network|https://www.facebook.com/groups/fheroes2" msgstr "" msgid "Need help with the game?" msgstr "" msgid "Original project before 0.7" msgstr "" #, fuzzy msgid "Heroes of Might and Magic II: The Succession Wars team" msgstr "UkonÄí Heroes of Might and Magic II a vrátí se do operaÄního systému." msgid "Designed and Directed" msgstr "" msgid "Programming and Design" msgstr "" msgid "Executive Producer" msgstr "" msgid "Producer" msgstr "" msgid "Additional Design" msgstr "" msgid "Additional Programming" msgstr "" msgid "Musical Production" msgstr "" msgid "Music and Sound Design" msgstr "" msgid "Vocalists" msgstr "" msgid "Art Director" msgstr "" msgid "Assistant Art Director" msgstr "" #, fuzzy msgid "Artists" msgstr "Artefakt" msgid "QA Manager" msgstr "" msgid "QA" msgstr "" #, fuzzy msgid "Writing" msgstr "Hodnocení" msgid "Manual and Helpfile" msgstr "" #, fuzzy msgid "Scenarios" msgstr "Scénář" #, fuzzy msgid "Heroes of Might and Magic II: The Price of Loyalty team" msgstr "UkonÄí Heroes of Might and Magic II a vrátí se do operaÄního systému." msgid "Design Lead" msgstr "" msgid "Designers" msgstr "" msgid "Programming Lead" msgstr "" msgid "Art Lead" msgstr "" #, fuzzy msgid "Playtesters" msgstr "2 hráÄi" msgid "Designer" msgstr "" msgid "Producers" msgstr "" msgid "QA Managers" msgstr "" msgid "Sound Design" msgstr "" msgid "Town Themes" msgstr "" msgid "Alto Sax" msgstr "" msgid "Harpsichord and Piano" msgstr "" msgid "Basso Vocal" msgstr "" msgid "Soprano Vocal" msgstr "" msgid "Recorded at %{recordingStudio}" msgstr "" msgid "credits|Manual" msgstr "" msgid "German Consultant" msgstr "" msgid "Map Designers" msgstr "" msgid "Package Design" msgstr "" msgid "Your Name" msgstr "" msgid "Unknown Hero" msgstr "" msgid "Standard" msgstr "" msgid "View High Scores for Standard Maps." msgstr "" msgid "Campaign" msgstr "" msgid "View High Scores for Campaigns." msgstr "" msgid "hotkey|default okay event" msgstr "" msgid "hotkey|default cancel event" msgstr "" msgid "hotkey|default left" msgstr "" msgid "hotkey|default right" msgstr "" msgid "hotkey|default up" msgstr "" msgid "hotkey|default down" msgstr "" msgid "hotkey|toggle fullscreen" msgstr "" msgid "hotkey|toggle text support mode" msgstr "" msgid "hotkey|new game" msgstr "" msgid "hotkey|load game" msgstr "" msgid "hotkey|highscores" msgstr "" msgid "hotkey|credits" msgstr "" msgid "hotkey|standard game" msgstr "" msgid "hotkey|campaign game" msgstr "" #, fuzzy msgid "hotkey|multi-player game" msgstr "Spusti hru jednoho nebo více hráÄů." msgid "hotkey|settings" msgstr "Nastavení" msgid "hotkey|quit" msgstr "" msgid "hotkey|select map" msgstr "" msgid "hotkey|select small map size" msgstr "" msgid "hotkey|select medium map size" msgstr "" msgid "hotkey|select large map size" msgstr "" msgid "hotkey|select extra large map size" msgstr "" msgid "hotkey|select all map sizes" msgstr "" msgid "hotkey|hotseat game" msgstr "" msgid "hotkey|battle only game" msgstr "" msgid "hotkey|choose the original campaign" msgstr "" msgid "hotkey|choose the expansion campaign" msgstr "" msgid "hotkey|map editor main menu" msgstr "" #, fuzzy msgid "hotkey|new map menu" msgstr "Zobrazit následující mÄ›sto" msgid "hotkey|load map menu" msgstr "" msgid "hotkey|new map from scratch" msgstr "" #, fuzzy msgid "hotkey|new random map" msgstr "PÅ™ehled Království" #, fuzzy msgid "hotkey|undo last action" msgstr "Zobrazit následující mÄ›sto" #, fuzzy msgid "hotkey|redo last action" msgstr "Zobrazit následující mÄ›sto" msgid "hotkey|roland campaign" msgstr "" msgid "hotkey|archibald campaign" msgstr "" msgid "hotkey|the price of loyalty campaign" msgstr "" msgid "hotkey|voyage home campaign" msgstr "" msgid "hotkey|wizard's isle campaign" msgstr "" msgid "hotkey|descendants campaign" msgstr "" msgid "hotkey|select first campaign bonus" msgstr "" msgid "hotkey|select second campaign bonus" msgstr "" msgid "hotkey|select third campaign bonus" msgstr "" msgid "hotkey|view campaign intro" msgstr "" #, fuzzy msgid "hotkey|select campaign difficulty" msgstr "" "Mapa\n" "Obtížnost" msgid "hotkey|restart campaign scenario" msgstr "" msgid "hotkey|world map left" msgstr "" msgid "hotkey|world map right" msgstr "" msgid "hotkey|world map up" msgstr "" msgid "hotkey|world map down" msgstr "" msgid "hotkey|world map up left" msgstr "" msgid "hotkey|world map up right" msgstr "" msgid "hotkey|world map down left" msgstr "" msgid "hotkey|world map down right" msgstr "" msgid "hotkey|save game" msgstr "" #, fuzzy msgid "hotkey|next hero" msgstr "Zobrazit následující mÄ›sto" #, fuzzy msgid "hotkey|start hero movement" msgstr "PokraÄovat v pohybu" #, fuzzy msgid "hotkey|cast adventure spell" msgstr "Seslat kouzlo." msgid "hotkey|put hero to sleep" msgstr "" #, fuzzy msgid "hotkey|next town" msgstr "Zobrazit následující mÄ›sto" msgid "hotkey|end turn" msgstr "" #, fuzzy msgid "hotkey|file options" msgstr "Možnosti Hradu" msgid "hotkey|adventure options" msgstr "" msgid "hotkey|puzzle map" msgstr "" #, fuzzy msgid "hotkey|scenario information" msgstr "Informace o HÅ™e" msgid "hotkey|dig for artifact" msgstr "" msgid "hotkey|view world" msgstr "" #, fuzzy msgid "hotkey|kingdom summary" msgstr "PÅ™ehled Království" msgid "hotkey|default action" msgstr "" msgid "hotkey|open focus" msgstr "" #, fuzzy msgid "hotkey|system options" msgstr "Upravit systémové nastavení" msgid "hotkey|scroll left" msgstr "" msgid "hotkey|scroll right" msgstr "" msgid "hotkey|scroll up" msgstr "" msgid "hotkey|scroll down" msgstr "" msgid "hotkey|toggle control panel" msgstr "" msgid "hotkey|toggle radar" msgstr "" msgid "hotkey|toggle buttons" msgstr "" msgid "hotkey|toggle status" msgstr "" msgid "hotkey|toggle icons" msgstr "" msgid "hotkey|transfer control to ai" msgstr "" msgid "hotkey|retreat from battle" msgstr "" msgid "hotkey|surrender during battle" msgstr "" msgid "hotkey|toggle battle auto mode" msgstr "" #, fuzzy msgid "hotkey|finish the battle in auto mode" msgstr "Jsi si jist, že chceÅ¡ dokonÄit boj v automatickém módu?" #, fuzzy msgid "hotkey|battle options" msgstr "Možnosti Hradu" msgid "hotkey|skip turn in battle" msgstr "" msgid "hotkey|cast battle spell" msgstr "" msgid "hotkey|dwelling level 1" msgstr "" msgid "hotkey|dwelling level 2" msgstr "" msgid "hotkey|dwelling level 3" msgstr "" msgid "hotkey|dwelling level 4" msgstr "" msgid "hotkey|dwelling level 5" msgstr "" msgid "hotkey|dwelling level 6" msgstr "" msgid "hotkey|well" msgstr "" msgid "hotkey|marketplace" msgstr "TržiÅ¡tÄ›" msgid "hotkey|mage guild" msgstr "" msgid "hotkey|shipyard" msgstr "LodÄ›nice" msgid "hotkey|thieves guild" msgstr "Cech zlodÄ›jů" msgid "hotkey|tavern" msgstr "" msgid "hotkey|construction screen" msgstr "" msgid "hotkey|buy all monsters in well" msgstr "" msgid "hotkey|split stack by half" msgstr "" msgid "hotkey|split stack by one" msgstr "" msgid "hotkey|join stacks" msgstr "" msgid "hotkey|upgrade troop" msgstr "" msgid "hotkey|dismiss hero or troop" msgstr "" msgid "" "Do you want to regain control from AI? The effect will take place only on " "the next turn." msgstr "ChceÅ¡ pÅ™evzít kontrolu od AI? Bude zavedeno až v dalším tahu." msgid "" "Do you want to transfer control from you to the AI? The effect will take " "place only on the next turn." msgstr "" "PÅ™ejeÅ¡ si odevzdat kontrolu AI? Bude to platit pouze pro následující tah." msgid "Default Actions" msgstr "" msgid "Global Actions" msgstr "" msgid "Main Menu" msgstr "" msgid "World Map" msgstr "Mapa SvÄ›ta" #, fuzzy msgid "Battle Screen" msgstr "Obrazovka hrdiny" #, fuzzy msgid "Town Screen" msgstr "Obrazovka hrdiny" msgid "Army Actions" msgstr "" msgid "Editor" msgstr "" msgid "The save file is corrupted." msgstr "Soubor je požkozen." msgid "Unsupported save format: " msgstr "Nepodporovaný formát: " msgid "Current game version: " msgstr "Aktuální verze hry: " msgid "Last supported version: " msgstr "Poslední podporovaná verze: " msgid "This file contains a save with an invalid game type." msgstr "Soubor obsahuje údaje z neplatné verze hry." #, fuzzy msgid "" "This save file requires \"The Price of Loyalty\" game assets, but they have " "not been provided to the engine." msgstr "" "Hra byla uložena pro mapu z datadisku \"The Price of Loyalty\", nebyly ale " "poskytnuty odpovídající zdrojové soubory." msgid "This saved game is localized to '" msgstr "Tato hra je uložena s lokalizací pro '" msgid "' language, but the current language of the game is '" msgstr "' jazyk, aktuálnÄ› máš ale nastaven jazyk '" msgid "Hot Seat" msgstr "Horké kÅ™eslo" msgid "" "Play a Hot Seat game, where 2 to 4 players play around the same computer, " "switching into the 'Hot Seat' when it is their turn." msgstr "" msgid "A single player game playing out a single map." msgstr "" msgid "Standard Game" msgstr "Standardní hra" msgid "A single player game playing through a series of maps." msgstr "" msgid "Campaign Game" msgstr "Kampaňová hra" msgid "Multi-Player Game" msgstr "Hra pro více hráÄů" msgid "" "A multi-player game, with several human players completing against each " "other on a single map." msgstr "" msgid "fheroes2 Resurrection Team presents" msgstr "Tým fheroes2 Resurrection uvádí" msgid "Greetings!" msgstr "Vítej!" msgid "Welcome to Heroes of Might and Magic II powered by fheroes2 engine!" msgstr "Vítej ve hÅ™e Heroes of Might and Magic II běžící na enginu fheroes2!" #, fuzzy msgid "" "Welcome to Heroes of Might and Magic II powered by the fheroes2 engine!\n" "Before starting the game please select a game resolution." msgstr "" "Vítej ve hÅ™e Heroes of Might and Magic II běžící na enginu fheroes2! PÅ™ed " "samotným startem je potÅ™eba nastavit rozliÅ¡ení." msgid "Please Remember" msgstr "Prosím pamatuj" #, fuzzy msgid "" "You can always change the language, resolution and settings of the game by " "clicking on the " msgstr "Vždy můžeÅ¡ zmÄ›nit rozliÅ¡ení hry klinutím na " msgid "door" msgstr "dveÅ™e" msgid " on the left side of the Main Menu, or with the " msgstr "" msgid " button from the " msgstr "" #, fuzzy msgid "NEW GAME" msgstr "" "NOVÃ\n" "HRA" msgid "" " menu. \n" "\n" "Enjoy the game!" msgstr "" #, fuzzy msgid "The Editor requires \"The Price of Loyalty\" expansion files to work." msgstr "Jedna ze ÄtyÅ™ nových kampaní z datadisku Price of Loyalty." msgid "Quit Heroes of Might and Magic II and return to the operating system." msgstr "UkonÄí Heroes of Might and Magic II a vrátí se do operaÄního systému." msgid "Credits" msgstr "AutoÅ™i" msgid "View the credits screen." msgstr "Zobraz obrazovku s autory." msgid "High Scores" msgstr "Nejlepší výsledky" msgid "View the high scores screen." msgstr "Zobraz tabulku s nej. skóre." msgid "Change language, resolution and settings of the game." msgstr "Změň jazyk, rozliÅ¡ení a další nastavení hry." msgid "Game Settings" msgstr "Nastavení Hry" msgid "" "Required video files for campaign selection window are missing. Please make " "sure that all necessary files are present in the system." msgstr "" "Chybí potÅ™ebné video soubory pro výbÄ›r kampanÄ›. Prosím ujistÄ›te se, že " "vÅ¡echny potÅ™ebné soubory jsou k dispozici." msgid "" "Either Roland's or Archibald's campaign from the original Heroes of Might " "and Magic II." msgstr "" "Rolandova nebo Archibaldova kampaň z původního vydání hry Heroes of Might " "and Magic II." msgid "Original Campaign" msgstr "Originální kampaň" msgid "Expansion Campaign" msgstr "Kampaň datadisku" msgid "One of the four new campaigns from the Price of Loyalty expansion set." msgstr "Jedna ze ÄtyÅ™ nových kampaní z datadisku Price of Loyalty." msgid "Loading video. Please wait..." msgstr "NaÄítání videa. Prosím, Äekej..." msgid "Host" msgstr "" msgid "" "The host sets up the game options. There can only be one host per network " "game." msgstr "" msgid "Guest" msgstr "" msgid "" "The guest waits for the host to set up the game, then is automatically added " "in. There can be multiple guests for TCP/IP games." msgstr "" msgid "Battle Only" msgstr "" msgid "Setup and play a battle without loading any map." msgstr "" msgid "2 Players" msgstr "2 hráÄi" msgid "" "Play with 2 human players, and optionally, up to 4 additional computer " "players." msgstr "" msgid "3 Players" msgstr "3 hráÄi" msgid "" "Play with 3 human players, and optionally, up to 3 additional computer " "players." msgstr "" msgid "4 Players" msgstr "" msgid "" "Play with 4 human players, and optionally, up to 2 additional computer " "players." msgstr "" msgid "5 Players" msgstr "" msgid "" "Play with 5 human players, and optionally, up to 1 additional computer " "player." msgstr "" msgid "6 Players" msgstr "" msgid "Play with 6 human players." msgstr "" msgid "Dragon city has fallen! You are now the Master of the Dragons." msgstr "DraÄi mÄ›sto padlo! Stal ses Pánem Draků." msgid "" "You captured %{name}!\n" "You are victorious." msgstr "" "Dobyl si %{name}!\n" "Jsi vítÄ›z." msgid "" "You have captured the enemy hero %{name}!\n" "Your quest is complete." msgstr "" "Zajal si hrdinu %{name}!\n" "Tvůj úkol byl splnÄ›n." msgid "" "You have found the %{name}.\n" "Your quest is complete." msgstr "" "Nalezl si %{name}.\n" "Tvůj úkol byl splnÄ›n." msgid "Ultimate Artifact" msgstr "Ultimátní Artefakt" msgid "" "The enemy is beaten.\n" "Your side has triumphed!" msgstr "" "Nepřátel je poražen.\n" "Tvá strana triumfovala!" msgid "" "You have built up over %{count} gold in your treasury.\n" "All enemies bow before your wealth and power." msgstr "" "Nashromáždil si %{count} zlaťáků v pokladnici.\n" "VÅ¡ichni nepřátelé se klanÄ›jí Tvému bohatství a síle." msgid "Victory!" msgstr "VítÄ›zství!" msgid "" "The enemy has captured %{name}!\n" "They are triumphant." msgstr "" "Nepřátel dobil %{name}!\n" "Jejich strana triumfovala." msgid "" "The enemy has built up over %{count} gold in his treasury.\n" "You must bow done in defeat before his wealth and power." msgstr "" "Nepřátel nashromáždil %{count} zlaťáků v jeho pokladnici.\n" "Příjmi porážku a zkloň se pÅ™ed jeho bohatstvím a silou." msgid "You have been eliminated from the game!!!" msgstr "Byl jsi vyÅ™azen ze hry!!!" msgid "" "You have lost the hero %{name}.\n" "Your quest is over." msgstr "" "PÅ™iÅ¡el jsi o hrdinu %{name}.\n" "Úkol nebyl splnÄ›n." msgid "" "You have failed to complete your quest in time.\n" "All is lost." msgstr "" "NepodaÅ™ilo se Ti dokonÄit úkol vÄas.\n" "VÅ¡e je ztraceno." msgid "Defeat!" msgstr "Porážka!" msgid "" "Base score: %{score}\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" msgid "Defeat all enemy heroes and capture all enemy towns and castles." msgstr "Porážka vÅ¡ech nepřátelských hrdinů a zabrání jejich mÄ›st a hradů." msgid "Run out of time. (Fail to win by a certain point.)" msgstr "VyÄerpání Äasu. (Výhra není dosažena do urÄ. doby.)" msgid "You must defeat the enemy %{enemies}." msgid_plural "You must defeat the enemy alliance of %{enemies}." msgstr[0] "Musíš porazit %{enemies}." msgstr[1] "Musíš porazit nepřátelskou alianci %{enemies}." msgstr[2] "Musíš porazit nepřátelskou alianci %{enemies}." msgid "" "The alliance consisting of %{allies} and you must defeat the enemy " "%{enemies}." msgid_plural "" "The alliance consisting of %{allies} and you must defeat the enemy alliance " "of %{enemies}." msgstr[0] "" "Aliance je složená z %{allies}, musíte porazit nepřítele %{enemies}." msgstr[1] "" "Aliance je složená z %{allies}, musíte porazit nepřátelskou alianci tvořící " "%{enemies}." msgstr[2] "" "Aliance je složená z %{allies}, musíte porazit nepřátelskou alianci tvořící " "%{enemies}." msgid "Capture the castle '%{name}'." msgstr "Dobytí hradu '%{name}'." msgid "Capture the town '%{name}'." msgstr "Dobytí mÄ›sta '%{name}'." msgid "Defeat the hero '%{name}'." msgstr "Porážka hrdiny '%{name}'." msgid "Find the ultimate artifact." msgstr "Nalezení ultimátního artefaktu." msgid "Find the '%{name}' artifact." msgstr "Nalezení artefaktu '%{name}'." msgid "Accumulate %{count} gold." msgstr "Nashromáždéní %{count} zlata." msgid "" ", or you may win by defeating all enemy heroes and capturing all enemy towns " "and castles." msgstr "" ", nebo můžeÅ¡ vyhrát i porážkou vÅ¡ech vÅ¡ech nepřátelských hrdinů a zabráním " "jejich mÄ›st a hradů." msgid "Lose the castle '%{name}'." msgstr "Ztráta hradu '%{name}'." msgid "Lose the town '%{name}'." msgstr "Ztráta mÄ›sta '%{name}'." msgid "Lose the hero: %{name}." msgstr "Ztráta hrdiny: %{name}'." msgid "Fail to win by the end of month %{month}, week %{week}, day %{day}." msgstr "" "Nedosažení výhry pÅ™ed dovrÅ¡ením mÄ›síc %{month}, týden %{week}, den %{day}." msgid "%{color} player has been vanquished!" msgstr "%{color} hrÃ¡Ä byl poražen!" msgid "Major Event!" msgstr "" msgid "Scenario:" msgstr "Scénář:" msgid "Game Difficulty:" msgstr "Obtížnost hry:" msgid "Opponents:" msgstr "Protivníci:" msgid "Class:" msgstr "TÅ™ida:" msgid "Rating %{rating}%" msgstr "" msgid "Click here to select which scenario to play." msgstr "Kliknout sem pro výbÄ›r scénáře." msgid "Scenario" msgstr "Scénář" msgid "Game Difficulty" msgstr "Obtížnost hry" msgid "" "This lets you change the starting difficulty at which you will play. Higher " "difficulty levels start you of with fewer resources, and at the higher " "settings, give extra resources to the computer." msgstr "" "Umožní Ti zmÄ›nit obtížnost hry se kterou budeÅ¡ hrát. Vyšší obtížnosti Ti " "dávají na záÄatku hry míň zdrojů a Äím vyšší obtížnost je, tím více zdrojů " "dostane protivník - poÄítaÄ." msgid "Difficulty Rating" msgstr "Hodnocení obtížnosti" msgid "" "The difficulty rating reflects a combination of various settings for your " "game. This number will be applied to your final score." msgstr "" "Hodnocení obtížnosti odráži výslednou kombinaci voleb hry. Tento koeficient " "bude použit i pÅ™i výpoÄtu finálního skóre." msgid "Click to accept these settings and start a new game." msgstr "Klikni pro potvrzení volby a spusti novou hru." msgid "Click to return to the main menu." msgstr "Klikni pro návrat do hlavního menu." msgid "Astrologers proclaim the Month of the %{name}." msgstr "Astrologická pÅ™edpovÄ›Ä, mÄ›síc %{name}." msgid "Astrologers proclaim the Week of the %{name}." msgstr "Astrologická pÅ™edpovÄ›Ä, týden %{name}." msgid "After regular growth, the population of %{monster} is doubled!" msgstr "PÅ™i pravidelném nárůstu doÅ¡lo ke zdvojení populace %{monster}!" msgid "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgid_plural "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgstr[0] "" "PÅ™i pravidelném nárůstu populace %{monster} narostla o %{count} procent!" msgstr[1] "" "PÅ™i pravidelném nárůstu populace %{monster} narostla o %{count} procenta!" msgstr[2] "" "PÅ™i pravidelném nárůstu populace %{monster} narostla o %{count} procent!" msgid "%{monster} growth +%{count}." msgstr "Růst %{monster} +%{count}." msgid " All populations are halved." msgstr " Celá populace je na polovinÄ›." msgid " All dwellings increase population." msgstr " VÅ¡echna obydlí pÅ™ináší novou populaci." msgid "New Month!" msgstr "Nový mÄ›síc!" msgid "New Week!" msgstr "ZaÄal nový týden!" msgid "Beware!" msgstr "Pozor!" msgid "" "%{color} player, this is your last day to capture a town, or you will be " "banished from this land." msgstr "" "%{color} hráÄ, máš poslední den na dobití nÄ›jakého mÄ›sta, jinak budeÅ¡ " "vyhoÅ¡tÄ›n z této zemÄ›." msgid "" "%{color} player, you only have %{day} days left to capture a town, or you " "will be banished from this land." msgstr "" "%{color} hráÄ, máš pouze %{day} dní na dobití nÄ›jakého mÄ›sta, jinak budeÅ¡ " "vyhoÅ¡tÄ›n z této zemÄ›." msgid "%{color} player's turn." msgstr "Táhne %{color} hráÄ." #, fuzzy msgid "" "%{color} player, you have lost your last town. If you do not conquer another " "town in the next week, you will be eliminated." msgstr "" "%{color} hráÄi, pÅ™iÅ¡el jsi o své poslední mÄ›sto. Pokud do týdne nedobijeÅ¡ " "nÄ›jaké mÄ›sto, tak budeÅ¡ vylouÄen ze hry." msgid "" "%{color} player, your heroes abandon you, and you are banished from this " "land." msgstr "" "%{color} hráÄi, Tví hrdinové TÄ› opouÅ¡tí a Ty jsi byl vykázán z této zemÄ›." msgid "Lord Kilburn" msgstr "" msgid "Tsabu" msgstr "" msgid "Sir Galant" msgstr "Sir Galant" msgid "Thundax" msgstr "" msgid "Lord Haart" msgstr "" msgid "Ariel" msgstr "" msgid "Rebecca" msgstr "" msgid "Sandro" msgstr "" msgid "Crodo" msgstr "" msgid "Barock" msgstr "" msgid "Antoine" msgstr "" msgid "Astra" msgstr "" msgid "Agar" msgstr "" msgid "Vatawna" msgstr "" msgid "Vesper" msgstr "" msgid "Ambrose" msgstr "" msgid "Troyan" msgstr "" msgid "Jojosh" msgstr "" msgid "Wrathmont" msgstr "" msgid "Maximus" msgstr "" msgid "Next Hero" msgstr "Další Hrdina" msgid "Select the next Hero." msgstr "Vybrat dalšího Hrdinu." #, fuzzy msgid "Hero Movement" msgstr "Stín pohybu" msgid "" "Start the Hero's movement along the current path or re-visit the object " "occupied by the Hero. Press and hold this button to reset the Hero's path." msgstr "" msgid "Kingdom Summary" msgstr "PÅ™ehled Království" msgid "View a Summary of your Kingdom." msgstr "Zobraz pÅ™ehled o svém království." msgid "Cast an adventure spell." msgstr "Seslat kouzlo." msgid "End Turn" msgstr "UkonÄit tah" msgid "End your turn and left the computer take its turn." msgstr "UkonÄi svůj tah a nech táhnout poÄítaÄ." msgid "Adventure Options" msgstr "Možnosti hry" msgid "Bring up the adventure options menu." msgstr "Zobrazit možnosti hry." msgid "" "Bring up the file options menu, allowing you to load, save, start a new game " "or quit." msgstr "" "Zobrazit volby pro uložení, naÄtení hry, stuÅ¡tÄ›ní nové hry anebo ukonÄení " "stávající." msgid "Bring up the system options menu, allowing you to customize your game." msgstr "Zobrazí systémové menu, s volbami pro úpravu chování hry." msgid "" "One or more heroes may still move, are you sure you want to end your turn?" msgstr "" "Jeden nebo více hrdinů mohou jeÅ¡tÄ› popojet, urÄitÄ› chceÅ¡ ukonÄit svůj tah?" msgid "Are you sure you want to quit?" msgstr "Opravdu chceÅ¡ skonÄit?" msgid "Are you sure you want to restart? (Your current game will be lost.)" msgstr "Jsi si jist, že chceÅ¡ hru restartovat? (Aktuální hra bude ztracena.)" msgid "Are you sure you want to overwrite the save with this name?" msgstr "Jsi si jist, že chceÅ¡ prÄ›psat soubor pod tímto názvem?" msgid "Game saved successfully." msgstr "Hra byla uložena." msgid "There was an issue during saving." msgstr "BÄ›hem ukládání se vyskytla chyba." msgid "" "Are you sure you want to load a new game? (Your current game will be lost.)" msgstr "Jsi si jist, že chceÅ¡ naÄíst novou hru? (Aktuální hra bude ztracena.)" msgid "Try looking on land!!!" msgstr "Zkus hledat v přírodÄ›!!!" msgid "" "Searching for the Ultimate Artifact is fruitless. Your hero could not carry " "it even if he found it - all his artifact slots are full." msgstr "" "Hledání ultimátního artefaktu nemá cenu. Tvůj hrdina si ho nemůže vzít, ani " "kdyby ho naÅ¡el - vÅ¡echny jeho sloty pro artefakty jsou plné." msgid "Digging for artifacts requires a whole day, try again tomorrow." msgstr "Kopání vyžaduje celodenní úsilí, zkus to opÄ›t zítra." msgid "" "After spending many hours digging here, you have uncovered the %{artifact}." msgstr "" "Po nÄ›kolika hodinách strávených kopáním, jsi koneÄné objevil %{artifact}." msgid "Congratulations!" msgstr "Gratulace!" msgid "Nothing here. Where could it be?" msgstr "Nic jsi nenaÅ¡el. Kde to jen může být?" msgid "Try searching on clear ground." msgstr "Zkus hledat na volném místÄ›." msgid "A miniature view of the known world. Left click to move viewing area." msgstr "" "Miniaturní pohled na odkrytý svÄ›t. Levým klikem pohneÅ¡ zobrazovanou oblast." msgid "Month: %{month} Week: %{week}" msgstr "MÄ›síc: %{month} Týden: %{week}" msgid "Day: %{day}" msgstr "Den: %{day}" msgid "" "You find a small\n" "quantity of %{resource}." msgstr "" "Nalezl jsi malé\n" "množství %{resource}." msgid "Status Window" msgstr "Stavové okénko" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date." msgstr "" "Zobrazuje informaci o stavu Tvého království nebo hrdinech, dále zobrazuje " "datum." msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date. Left click here to cycle through these windows." msgstr "" "Toto okénko poskytuje informaci o stavu Tvého království, zobrazuje datum. " "Levým kliknutím můžeÅ¡ cyklovat zobrazované informace v oknÄ›." msgid "" "This lets you change player starting positions and colors. A particular " "color will always start in a particular location. Some positions may only be " "played by a computer player or only by a human player." msgstr "" "Umožňuje mÄ›nit výchozí pozice a barvy hráÄů. Konkrétní barva bude vždy " "zaÄínat na urÄitém místÄ›. NÄ›které pozice může hrát pouze poÄítaÄový hrÃ¡Ä " "nebo pouze lidský hráÄ." msgid "" "This lets you change the class of a player. Classes are not always " "changeable. Depending on the scenario, a player may receive additional towns " "and/or heroes not of their primary alignment." msgstr "" "Umožňuje zmÄ›nit kastu hráÄe. Kasty nejdou zmÄ›nit vždy. V závislosti na " "scénáři může hrÃ¡Ä získat další mÄ›sta a/nebo hrdiny, kteří nepatří k jeho " "primárnímu zaÄlenÄ›ní." msgid "Handicap" msgstr "Hendikep" msgid "" "This lets you change the handicap of a particular player. Only humans may be " "handicapped. Handicapped players start with fewer resources and earn 15 or " "30% fewer resources per turn for mild and severe handicaps, respectively." msgstr "" "Tímto můžete zmÄ›nit hendikep pro konkrétního hráÄe. Pouze lidé mohou být " "hendikepovaní. Hendikepovaní hráÄi zaÄínají s menším poÄtem zdrojů a " "získávají o 15 nebo 30% ménÄ› zdrojů za den v případÄ› lehkého resp. těžkého " "hendikepu." msgid "%{color} player" msgstr "%{color} hráÄ" msgid "No Handicap" msgstr "Bez hendikepu" msgid "No special restrictions on start resources and earning them per turn." msgstr "Žádná omezení zdrojů pÅ™i startu nebo získávání zdrojů bÄ›hem hry." msgid "Mild Handicap" msgstr "Lehký hendikep" msgid "" "Mild handicapped players start with fewer resources and earn 15% fewer " "resources per turn." msgstr "" "Lehce hendikepovaní hráÄi zaÄínají s menším poÄtem zdrojů a získávají o 15% " "ménÄ› zdrojů za den." msgid "Severe Handicap" msgstr "Těžký hendikep" msgid "" "Severe handicapped players start with fewer resources and earn 30% fewer " "resources per turn." msgstr "" "Těžce hendikepovaní hráÄi zaÄínají s menším poÄtem zdrojů a získávají o 30% " "ménÄ› zdrojů za den." msgid "View %{skill} Info" msgstr "Zobrazit informace o %{skill}" msgid "Keyboard|123" msgstr "" msgid "Keyboard|SPACE" msgstr "Mezerník" msgid "Keyboard|ABC" msgstr "" msgid "Kingdom Income" msgstr "Příjmy království" msgid "Kingdom Income per day." msgstr "Příjem království za den." msgid "For every lighthouse controlled, your ships will move further each day." msgstr "" "S každým dalším obsazeným majákem budou Tvé lodÄ› moci doplout každý den o " "kus dál." msgid "English" msgstr "" msgid "French" msgstr "" msgid "Polish" msgstr "" msgid "German" msgstr "" msgid "Russian" msgstr "" msgid "Italian" msgstr "" msgid "Czech" msgstr "ÄŒeÅ¡tina" msgid "Norwegian" msgstr "" msgid "Belarusian" msgstr "" msgid "Bulgarian" msgstr "" msgid "Ukrainian" msgstr "" msgid "Romanian" msgstr "" msgid "Spanish" msgstr "" msgid "Swedish" msgstr "" msgid "Portuguese" msgstr "" msgid "Turkish" msgstr "" msgid "Dutch" msgstr "" msgid "Hungarian" msgstr "" msgid "Danish" msgstr "" msgid "Slovak" msgstr "Slovensky" msgid "Vietnamese" msgstr "" msgid ", FPS: " msgstr "" msgid "%{object} robber" msgstr "" msgid "%{object} raided" msgstr "" # Heroes names section - translate not needed msgid "Ector" msgstr "" msgid "Gwenneth" msgstr "" msgid "Sir Gallant" msgstr "" msgid "Tyro" msgstr "" msgid "Dimitry" msgstr "" msgid "Ruby" msgstr "" msgid "Crag Hack" msgstr "" msgid "Fineous" msgstr "" msgid "Jezebel" msgstr "Jezábel" msgid "Atlas" msgstr "" msgid "Ergon" msgstr "" msgid "Jaclyn" msgstr "" msgid "Gem" msgstr "" msgid "Natasha" msgstr "" msgid "Carlawn" msgstr "" msgid "Luna" msgstr "" msgid "Arie" msgstr "" msgid "Barok" msgstr "" msgid "Kastore" msgstr "" msgid "Falagar" msgstr "" msgid "Dawn" msgstr "" msgid "Flint" msgstr "" msgid "Halon" msgstr "" msgid "Myra" msgstr "" msgid "Myrini" msgstr "" msgid "Wilfrey" msgstr "" msgid "Mandigal" msgstr "" msgid "Sarakin" msgstr "" msgid "Charity" msgstr "" msgid "Darlana" msgstr "" msgid "Ranloo" msgstr "" msgid "Rialdo" msgstr "" msgid "Zam" msgstr "" msgid "Zom" msgstr "" msgid "Celia" msgstr "" msgid "Roxana" msgstr "" msgid "Lord Corlagon" msgstr "" msgid "Lord Halton" msgstr "" msgid "Sister Eliza" msgstr "Sestra Eliza" msgid "Brother Brax" msgstr "Bratr Brax" msgid "Dainwin" msgstr "" msgid "Joseph" msgstr "" msgid "Mog" msgstr "" msgid "Solmyr" msgstr "" msgid "Ceallach" msgstr "" msgid "Drakonia" msgstr "" msgid "Elderian" msgstr "" msgid "Gallavant" msgstr "" msgid "Jarkonas" msgstr "" msgid "Martine" msgstr "" msgid " gives you maximum morale" msgstr " dává maximální morálku" msgid " gives you maximum luck" msgstr " dává maximální Å¡tÄ›stí" msgid "You cannot pick up this artifact, you already have a full load!" msgstr "Tento artefakt nemůžeÅ¡ vzít, již jich máš plno!" msgid "To cast spells, you must first buy a spell book for %{gold} gold." msgstr "Abys mohl kouzlit, musíš si koupit knihu kouzel za %{gold} zlata." msgid "Unfortunately, you seem to be a little short of cash at the moment." msgstr "Bohužel to vypadá, že Ti momentálnÄ› schází penízky." msgid "Do you wish to buy one?" msgstr "ChceÅ¡ ji zakoupit?" msgid "%{count} / day" msgstr "%{count}/ den" msgid "one" msgstr "jeden" msgid "two" msgstr "dva" msgid "A whirlpool engulfs your ship. Some of your army has fallen overboard." msgstr "Vodní vír pohltil Tvou loÄ. Část posádky spadla pÅ™es palubu." msgid "Insulted by your refusal of their offer, the monsters attack!" msgstr "Zamítnutí nabídky stvůry urazilo a obratem na Tebe zaútoÄili!" msgid "" "The %{monster}, awed by the power of your forces, begin to scatter.\n" "Do you wish to pursue and engage them?" msgstr "" "%{monster} ohromeni sílou Tvé armády zaÄali utíkat.\n" "ChceÅ¡ je pronásledovat a rozdrtit?" msgid "Ransacking an enemy camp, you discover a hidden cache of treasures." msgstr "Po vyplenÄ›ní nepřátelského tábora objevujeÅ¡ skrytou skrýš s poklady." msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with these resources, " "come back next week for more.\"" msgstr "" "Správce mlýna pronesl:\n" "\"Pane, pracoval jsem tvrÄ› na tom, abych vám mohl poskytnout tyto zdroje, " "pÅ™ijÄte si příští týden pro další.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there are no resources currently available. Please try " "again next week.\"" msgstr "" "Správce mlýna pronesl:\n" "\"Pane, je mi líto, ale momentálnÄ› nejsou k dispozici žádné zdroje. Zkuste " "to prosím zase příští týden.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with this gold, come " "back next week for more.\"" msgstr "" "Správce mlýna pronesl:\n" "\"Pane, pracoval jsem tvrÄ› na tom, abych vám mohl poskytnout toto zlato, " "pÅ™ijÄte si příští týden pro další.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there is no gold currently available. Please try again " "next week.\"" msgstr "" "Správce mlýna pronesl:\n" "\"Pane, je mi líto, ale momentálnÄ› zde není žádné zlato. Zkuste to prosím " "zase příští týden.\"" msgid "" "You've found an abandoned lean-to.\n" "Poking about, you discover some resources hidden nearby." msgstr "" "Nalezl jsi opuÅ¡tÄ›ný přístÅ™eÅ¡ek.\n" "Po prošťourání se okolím nalézáš poblíž zdroje skryté." msgid "The lean-to is long abandoned. There is nothing of value here." msgstr "PřístÅ™eÅ¡ek je již dlouho opuÅ¡tÄ›n. Není zde nic hodnotného." msgid "" "You catch a leprechaun foolishly sleeping amidst a cluster of magic " "mushrooms.\n" "In exchange for his freedom, he guides you to a small pot filled with " "precious things." msgstr "" "Chytil jsi skřítka, který bláhovÄ› spal uprostÅ™ed trsu kouzelných hub.\n" "VýmÄ›nou za svou svobodu TÄ› zavedl k malému hrnci naplnÄ›nému vzácnostmi." msgid "" "You've found a magic garden, the kind of place that leprechauns and faeries " "like to cavort in, but there is no one here today.\n" "Perhaps you should try again next week." msgstr "" "NaÅ¡el si kouzelnou zahradu, místo, kde se rádi prohánÄ›jí skřítci a víly, ale " "dnes tu nikdo není.\n" "Možná bys to mÄ›l zkusit příští týden." msgid "You come upon the remains of an unfortunate adventurer." msgstr "Narazil jsi na ostatky nešťastného dobrodruha." msgid "Treasure" msgstr "Poklad" msgid "Searching through the tattered clothing, you find the %{artifact}." msgstr "PÅ™i prohledávání roztrhaného obleÄení nalézáš %{artifact}." msgid "Searching through the tattered clothing, you find nothing." msgstr "PÅ™i prohledávání roztrhaného obleÄení jsi nic nanaÅ¡el." msgid "" "You come across an old wagon left by a trader who didn't quite make it to " "safe terrain." msgstr "" "Naražíš na starý vůz po obchodníkovi, který to oÄividnÄ› nedotáhl až na " "bezpeÄný terén." msgid "Unfortunately, others have found it first, and the wagon is empty." msgstr "Bohužel, jiní ho asi naÅ¡li již dříve, vůz je zcela prázdný." msgid "Searching inside, you find the %{artifact}." msgstr "Prohledáváš uvnitÅ™ a nalézáš %{artifact}." msgid "Inside, you find some of the wagon's cargo still intact." msgstr "UvnitÅ™ nalézáš Äást nákladu, který je stále neporuÅ¡ený." msgid "You search through the flotsam, and find some wood and some gold." msgstr "Prohledal jsi trosky a nalezl trochu dříví a zlata." msgid "You search through the flotsam, and find some wood." msgstr "Prohledal jsi trosky a nalezl trochu dříví." msgid "You search through the flotsam, but find nothing." msgstr "Prohledal jsi trosky ale nic jsi nalezl." msgid "Shrine of the 1st Circle" msgstr "SvatynÄ› První úrovnÄ›" msgid "" "You come across a small shrine attended by a group of novice acolytes.\n" "In exchange for your protection, they agree to teach you a simple spell - " "'%{spell}'." msgstr "" "Narazil jsi na malou svatyni, ve které slouží skupina zaÄínajících akolytů.\n" "VýmÄ›nou za tvou ochranu souhlasí, že tÄ› nauÄí jednoduché kouzlo - '%{spell}'." msgid "Shrine of the 2nd Circle" msgstr "SvatynÄ› Druhé úrovnÄ›" msgid "" "You come across an ornate shrine attended by a group of rotund friars.\n" "In exchange for your protection, they agree to teach you a spell - " "'%{spell}'." msgstr "" "Narazil jsi na zdobenou svatyni, ve které slouží skupina obtloustlých " "mnichů.\n" "VýmÄ›nou za tvou ochranu souhlasí, že tÄ› nauÄí kouzlo - '%{spell}'." msgid "Shrine of the 3rd Circle" msgstr "SvatynÄ› TÅ™etí úrovnÄ›" msgid "" "You come across a lavish shrine attended by a group of high priests.\n" "In exchange for your protection, they agree to teach you a sophisticated " "spell - '%{spell}'." msgstr "" "Narazil jsi na honosnou svatyni, ve které slouží skupina velekněží.\n" "VýmÄ›nou za tvou ochranu souhlasí, že tÄ› nauÄí složité kouzlo - '%{spell}'." msgid "" "\n" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "\n" "Bohužel nejsi dost moudrý na to, abys kouzlu porozumÄ›l a nejsi schopen se ho " "nauÄit." msgid "" "\n" "Unfortunately, you already have knowledge of this spell, so there is nothing " "more for them to teach you." msgstr "" "\n" "Bohužel toto kouzlo už znáš, takže tÄ› už nemají co nauÄit." msgid "" "\n" "Unfortunately, you have no Magic Book to record the spell with." msgstr "" "\n" "Bohužel nemáš knihu kouzel, takže si nemůžeÅ¡ to kouzlo zapsat." msgid "" "You approach the hut and observe a witch inside studying an ancient tome on " "%{skill}.\n" "\n" msgstr "" "Vstoupíš do chýše a vidíš uvnitÅ™ ÄarodÄ›jku, jak studuje starobylý svazek o " "%{skill}.\n" "\n" msgid "" "As you approach, she turns and focuses her one glass eye on you.\n" "\"You already know everything you deserve to learn!\" the witch screeches. " "\"NOW GET OUT OF MY HOUSE!\"" msgstr "" "Když se k ní pÅ™iblížíš, otoÄí se a upÅ™e na tebe své jediné sklenÄ›né oko.\n" "\"Už víš vÅ¡echno, co si zasloužíš vÄ›dÄ›t!\" vyjekla ÄarodÄ›jka. \"A TEÄŽ " "VYPADNI Z MÉHO DOMU!\"" msgid "" "As you approach, she turns and speaks.\n" "\"You already know that which I would teach you. I can help you no further.\"" msgstr "" "Když se k ní pÅ™iblížíš, otoÄí se a promluví.\n" "\"To, co bych tÄ› mohla nauÄit už víš. Nemohu Ti nijak pomoct.\"" msgid "" "An ancient and immortal witch living in a hut with bird's legs for stilts " "teaches you %{skill} for her own inscrutable purposes." msgstr "" msgid "As you drink the sweet water, you gain luck for your next battle." msgstr "" msgid "You drink from the enchanted fountain, but nothing happens." msgstr "" msgid "You enter the faerie ring, but nothing happens." msgstr "" msgid "" "Upon entering the mystical faerie ring, your army gains luck for its next " "battle." msgstr "" msgid "" "You've found an ancient and weathered stone idol.\n" "It is supposed to grant luck to visitors, but since the stars are already " "smiling upon you, it does nothing." msgstr "" msgid "" "You've found an ancient and weathered stone idol.\n" "Kissing it is supposed to be lucky, so you do. The stone is very cold to the " "touch." msgstr "" msgid "The mermaids silently entice you to return later and be blessed again." msgstr "" msgid "" "The magical, soothing beauty of the Mermaids reaches you and your crew.\n" "Just for a moment, you forget your worries and bask in the beauty of the " "moment.\n" "The mermaids charms bless you with increased luck for your next combat." msgstr "" msgid "" "You come upon the pyramid of a great and ancient king.\n" "You are tempted to search it for treasure, but all the old stories warn of " "fearful curses and undead guardians.\n" "Will you search?" msgstr "" msgid "" "Upon defeating the monsters, you decipher an ancient glyph on the wall, " "telling the secret of the spell - '" msgstr "" msgid "Unfortunately, you have no Magic Book to record the spell with." msgstr "" msgid "" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" msgid "" "You come upon the pyramid of a great and ancient king.\n" "Routine exploration reveals that the pyramid is completely empty." msgstr "" msgid "" "A drink at the well is supposed to restore your spell points, but you are " "already at maximum." msgstr "" msgid "A second drink at the well in one day will not help you." msgstr "" msgid "A drink from the well has restored your spell points to maximum." msgstr "" msgid "" "\"I'm sorry sir,\" The leader of the soldiers says, \"but you already know " "everything we have to teach.\"" msgstr "" msgid "The soldiers living in the fort teach you a few new defensive tricks." msgstr "" msgid "" "You've come upon a mercenary camp practicing their tactics. \"You're too " "advanced for us,\" the mercenary captain says. \"We can teach nothing more.\"" msgstr "" msgid "" "You've come upon a mercenary camp practicing their tactics. The mercenaries " "welcome you and your troops and invite you to train with them." msgstr "" msgid "\"Go 'way!\", the witch doctor barks, \"you know all I know.\"" msgstr "" msgid "" "An Orcish witch doctor living in the hut deepens your knowledge of magic by " "showing you how to cast stones, read portents, and decipher the intricacies " "of chicken entrails." msgstr "" msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, the Druids turn you away, indicating they have nothing " "new to teach you." msgstr "" msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, they teach you new ways to cast spells." msgstr "" msgid "" "You tentatively approach the burial ground of ancient warriors. Do you want " "to search the graves?" msgstr "" msgid "" "You spend several hours searching the graves and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" msgid "Upon defeating the Zombies you search the graves and find something!" msgstr "" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the shipwreck?" msgstr "" msgid "" "You spend several hours sifting through the debris and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" msgid "" "Upon defeating the Ghosts you sift through the debris and find something!" msgstr "" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the ship?" msgstr "" msgid "" "Upon defeating the Skeletons you sift through the debris and find something!" msgstr "" msgid "Your men spot a navigational buoy, confirming that you are on course." msgstr "" msgid "" "Your men spot a navigational buoy, confirming that you are on course and " "increasing their morale." msgstr "" msgid "" "The drink at the oasis is refreshing, but offers no further benefit. The " "oasis might help again if you fought a battle first." msgstr "" msgid "" "A drink at the oasis fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" msgid "" "The drink at the watering hole is refreshing, but offers no further benefit. " "The watering hole might help again if you fought a battle first." msgstr "" msgid "" "A drink at the watering hole fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" msgid "" "It doesn't help to pray twice before a battle. Come back after you've fought." msgstr "" msgid "A visit and a prayer at the temple raises the morale of your troops." msgstr "" msgid "" "An old Knight appears on the steps of the gazebo. \"I am sorry, my liege, I " "have taught you all I can.\"" msgstr "" msgid "" "An old Knight appears on the steps of the gazebo. \"My liege, I will teach " "you all that I know to aid you in your travels.\"" msgstr "" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he says, \"I would give you an artifact as a reward, but " "you're all full.\"" msgstr "" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he rewards you for your act of kindness by giving you the " "%{art}." msgstr "" msgid "A leprechaun offers you the %{art} for the small price of %{gold} Gold." msgstr "" msgid "" "A leprechaun offers you the %{art} for the small price of %{gold} Gold and " "%{count} %{res}." msgstr "" msgid "Do you wish to buy this artifact?" msgstr "" msgid "" "You try to pay the leprechaun, but realize that you can't afford it. The " "leprechaun stamps his foot and ignores you." msgstr "" msgid "" "Insulted by your refusal of his generous offer, the leprechaun stamps his " "foot and ignores you." msgstr "" msgid "You've found the artifact: " msgstr "" msgid "" "You've found the humble dwelling of a withered hermit. The hermit tells you " "that he is willing to give the %{art} to the first wise person he meets." msgstr "" msgid "" "You've come across the spartan quarters of a retired soldier. The soldier " "tells you that he is willing to pass on the %{art} to the first true leader " "he meets." msgstr "" msgid "" "You've encountered a strange person with a hat and an owl on it. He tells " "you that he is willing to give %{art} if you have %{skill}." msgstr "" msgid "" "You come upon an ancient artifact. As you reach for it, a pack of Rogues " "leap out of the brush to guard their stolen loot." msgstr "" msgid "" "Through a clearing you observe an ancient artifact. Unfortunately, it's " "guarded by a nearby %{monster}. Do you want to fight the %{monster} for the " "artifact?" msgstr "" msgid "Victorious, you take your prize, the %{art}." msgstr "" msgid "" "Discretion is the better part of valor, and you decide to avoid this fight " "for today." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold pieces." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold and the %{art}." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it, " "only to find it empty." msgstr "" msgid "" "After scouring the area, you fall upon a hidden treasure cache. You may take " "the gold or distribute the gold to the peasants for experience. Do you wish " "to keep the gold?" msgstr "" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "%{gold} gold pieces." msgstr "" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "ancient artifact %{art}." msgstr "" msgid "" "You stumble upon a dented and tarnished lamp lodged deep in the earth. Do " "you wish to rub the lamp?" msgstr "" msgid "" "You have taken control of the local Alchemist shop. It will provide you with " "%{count} unit of Mercury per day." msgstr "" msgid "" "You gain control of a sawmill. It will provide you with %{count} units of " "wood per day." msgstr "" msgid "" "You gain control of an ore mine. It will provide you with %{count} units of " "ore per day." msgstr "" msgid "" "You gain control of a sulfur mine. It will provide you with %{count} unit of " "sulfur per day." msgstr "" msgid "" "You gain control of a crystal mine. It will provide you with %{count} unit " "of crystal per day." msgstr "" msgid "" "You gain control of a gem mine. It will provide you with %{count} unit of " "gems per day." msgstr "" msgid "" "You gain control of a gold mine. It will provide you with %{count} gold per " "day." msgstr "" msgid "" "The lighthouse is now under your control, and all of your ships will now " "move further each day." msgstr "" msgid "You gain control of a %{name}." msgstr "" msgid "" "You come upon an abandoned gold mine. The mine appears to be haunted. Do you " "wish to enter?" msgstr "" msgid "You beat the Ghosts and are able to restore the mine to production." msgstr "" msgid "" "A group of %{monster} with a desire for greater glory wish to join you. Do " "you accept?" msgstr "" msgid "As you approach the dwelling, you notice that there is no one here." msgstr "" msgid "" "You search the ruins, but the Medusas that used to live here are gone. " "Perhaps there will be more next week." msgstr "" msgid "" "You've found some Medusas living in the ruins. They are willing to join your " "army for a price. Do you want to recruit Medusas?" msgstr "" msgid "" "You've found a Sprite Tree City. Unfortunately, none of the Sprites living " "there wish to join an army. Maybe next week." msgstr "" msgid "" "Some of the Sprites living in the tree city are willing to join your army " "for a price. Do you want to recruit Sprites?" msgstr "" msgid "" "A colorful Rogues' wagon stands empty here. Perhaps more Rogues will be here " "later." msgstr "" msgid "" "Distant sounds of music and laughter draw you to a colorful wagon housing " "Rogues. Do you wish to have any Rogues join your army?" msgstr "" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. The " "tents are unoccupied. Perhaps more Nomads will be here later." msgstr "" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. Do you " "wish to have any Nomads join you during your travels?" msgstr "" msgid "The pit of mud bubbles for a minute and then lies still." msgstr "" msgid "" "As you approach the bubbling pit of mud, creatures begin to climb out and " "position themselves around it. In unison they say: \"Mother Earth would like " "to offer you a few of her troops. Do you want to recruit Earth Elementals?\"" msgstr "" msgid "You enter the structure of white stone pillars, and find nothing." msgstr "" msgid "" "White stone pillars support a roof that rises up to the sky. As you enter " "the structure, the dead air of the outside gives way to a whirling gust that " "almost pushes you back out. The air current materializes into a barely " "visible form. The creature asks, in what can only be described as a loud " "whisper: \"Why have you come? Are you here to call upon the forces of the " "air?\"" msgstr "" msgid "No Fire Elementals approach you from the lava pool." msgstr "" msgid "" "Beneath a structure that serves to hold in heat, Fire Elementals move about " "in a fiery pool of molten lava. A group of them approach you and offer their " "services. Would you like to recruit Fire Elementals?" msgstr "" msgid "A face forms in the water for a moment, and then is gone." msgstr "" msgid "" "Crystalline structures cast shadows over a small reflective pool of water. " "You peer into the pool, and a face that is not your own peers back. It asks: " "\"Would you like to call upon the powers of water?\"" msgstr "" msgid "This burial site is deathly still." msgstr "" msgid "" "Restless spirits of long dead warriors seeking their final resting place " "offer to join you in hopes of finding peace. Do you wish to recruit ghosts?" msgstr "" msgid "" "The City of the Dead is empty of life, and empty of unlife as well. Perhaps " "some undead will move in next week." msgstr "" msgid "" "Some Liches living here are willing to join your army for a price. Do you " "want to recruit Liches?" msgstr "" msgid "" "You've found the ruins of an ancient city, now inhabited solely by the " "undead. Will you search?" msgstr "" msgid "" "Some of the surviving Liches are impressed by your victory over their " "fellows, and offer to join you for a price. Do you want to recruit Liches?" msgstr "" msgid "" "You've found one of those bridges that Trolls are so fond of living under, " "but there are none here. Perhaps there will be some next week." msgstr "" msgid "" "Some Trolls living under a bridge are willing to join your army, but for a " "price. Do you want to recruit Trolls?" msgstr "" msgid "Trolls living under the bridge challenge you. Will you fight them?" msgstr "" msgid "" "A few Trolls remain, cowering under the bridge. They approach you and offer " "to join your forces as mercenaries. Do you want to buy any Trolls?" msgstr "" msgid "" "The Dragon city has no Dragons willing to join you this week. Perhaps a " "Dragon will become available next week." msgstr "" msgid "" "The Dragon city is willing to offer some Dragons for your army for a price. " "Do you wish to recruit Dragons?" msgstr "" msgid "" "You stand before the Dragon City, a place off-limits to mere humans. Do you " "wish to violate this rule and challenge the Dragons to a fight?" msgstr "" msgid "" "Having defeated the Dragon champions, the city's leaders agree to supply " "some Dragons to your army for a price. Do you wish to recruit Dragons?" msgstr "" msgid "From the observation tower, you are able to see distant lands." msgstr "" msgid "" "The spring only refills once a week, and someone's already been here this " "week." msgstr "" msgid "" "A drink at the spring is supposed to give you twice your normal spell " "points, but you are already at that level." msgstr "" msgid "" "A drink from the spring fills your blood with magic! You have twice your " "normal spell points in reserve." msgstr "" msgid "" "Recognizing you, the butler refuses to admit you. \"The master,\" he says, " "\"will not see the same student twice.\"" msgstr "" msgid "" "The butler admits you to see the master of the house. He trains you in the " "four skills a hero should know." msgstr "" msgid "" "The butler opens the door and looks you up and down. \"You are neither " "famous nor diplomatic enough to be admitted to see my master,\" he sniffs. " "\"Come back when you think yourself worthy.\"" msgstr "" msgid " and " msgstr "" msgid "" "All of the %{monsters} you have in your army have been trained by the battle " "masters of the fort. Your army now contains %{monsters2}." msgstr "" msgid "" "An unusual alliance of Ogres, Orcs, and Dwarves offer to train (upgrade) any " "such troops brought to them. Unfortunately, you have none with you." msgstr "" msgid "All of your %{monsters} have been upgraded into %{monsters2}." msgstr "" msgid "" "A blacksmith working at the foundry offers to convert all Pikemen and " "Swordsmen's weapons brought to him from iron to steel. He also says that he " "knows a process that will convert Iron Golems into Steel Golems. " "Unfortunately, you have none of these troops in your army, so he can't help " "you." msgstr "" msgid "" "The captain looks at you with surprise and says:\n" "\"You already have all the maps I know about. Let me fish in peace now.\"" msgstr "" msgid "" "A retired captain living on this refurbished fishing platform offers to sell " "you maps of the sea he made in his younger days for 1,000 gold. Do you wish " "to buy the maps?" msgstr "" msgid "" "The captain sighs. \"You don't have enough money, eh? You can't expect me " "to give my maps away for free!\"" msgstr "" msgid "" "You come upon an obelisk made from a type of stone you have never seen " "before. Staring at it intensely, the smooth surface suddenly changes to an " "inscription. The inscription is a piece of a lost ancient map. Quickly you " "copy down the piece and the inscription vanishes as abruptly as it appeared." msgstr "" msgid "You have already been to this obelisk." msgstr "" msgid "" "Upon your approach, the tree opens its eyes in delight. \"It is good to see " "you, my student. I hope my teachings have helped you.\"" msgstr "" msgid "" "Upon your approach, the tree opens its eyes in delight. \"Ahh, an " "adventurer! Allow me to teach you a little of what I have learned over the " "ages.\"" msgstr "" msgid "Upon your approach, the tree opens its eyes in delight." msgstr "" msgid "" "\"Ahh, an adventurer! I will be happy to teach you a little of what I have " "learned over the ages for a mere %{count} %{res}.\"" msgstr "" msgid "(Just bury it around my roots.)" msgstr "" msgid "Tears brim in the eyes of the tree." msgstr "" msgid "\"I need %{count} %{res}.\"" msgstr "" msgid "it whispers. (sniff) \"Well, come back when you can pay me.\"" msgstr "" msgid "" "Nestled among the trees sits a blind seer. After you explain the intent of " "your journey, the seer activates his crystal ball, allowing you to see the " "strengths and weaknesses of your opponents." msgstr "" msgid "" "The entrance to the cave is dark, and a foul, sulfurous smell issues from " "the cave mouth. Will you enter?" msgstr "" msgid "Except for evidence of a terrible battle, the cave is empty." msgstr "" msgid "" "You find a powerful and grotesque Demon in the cave. \"Today,\" it rasps, " "\"you will fight and surely die. But I will give you a choice of deaths. You " "may fight me, or you may fight my servants. Do you prefer to fight my " "servants?\"" msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points." msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points and " "%{count} gold." msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and find the %{art} in the back of the cave." msgstr "" msgid "" "Seeing that you do not have %{count} gold, the demon slashes you with its " "claws, and the last thing you see is a red haze." msgstr "" msgid "" "The Demon leaps upon you and has its claws at your throat before you can " "even draw your sword. \"Your life is mine,\" it says. \"I will sell it back " "to you for %{count} gold.\"" msgstr "" msgid "" "Upon defeating the daemon's servants, you find a hidden cache with %{count} " "gold." msgstr "" msgid "" "As you enter the Alchemist's Tower, a hobbled, graying man in a brown cloak " "makes his way towards you." msgstr "" msgid "He checks your pack, and sees that you have 1 cursed item." msgid_plural "" "He checks your pack, and sees that you have %{count} cursed items." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "For %{gold} gold, the alchemist will remove it for you. Do you pay?" msgid_plural "" "For %{gold} gold, the alchemist will remove them for you. Do you pay?" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "" "After you consent to pay the requested amount of gold, the alchemist grabs " "the cursed artifact and throws it into his magical cauldron." msgid_plural "" "After you consent to pay the requested amount of gold, the alchemist grabs " "all cursed artifacts and throws them into his magical cauldron." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "" "You hear a voice from behind the locked door, \"You don't have enough gold " "to pay for my services.\"" msgstr "" msgid "" "You hear a voice from high above in the tower, \"Go away! I can't help you!\"" msgstr "" msgid "" "The head groom speaks to you, \"That is a fine looking horse you have. I am " "afraid we can give you no better, but the horses your cavalry are riding " "look to be of poor breeding stock. We have many trained war horses which " "would aid your riders greatly. I insist you take them.\"" msgstr "" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, he will " "grow tired in a week. You must also let me give better horses to your " "mounted soldiers, their horses look shoddy and weak.\"" msgstr "" msgid "" "The head groom approaches you and speaks, \"You already have a fine horse, " "and have no inexperienced cavalry which might make use of our trained war " "horses.\"" msgstr "" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, his " "endurance will wane with a lot of heavy riding, and you must return for a " "fresh mount in a week. We also have many fine war horses which could benefit " "mounted soldiers, but you have none we can help.\"" msgstr "" msgid "The Arena guards turn you away." msgstr "" msgid "" "You have your crew stop up their ears with wax before the sirens' eerie song " "has any chance of luring them to a watery grave." msgstr "" msgid "" "As the sirens sing their eerie song, your small, determined army manages to " "overcome the urge to dive headlong into the sea." msgstr "" msgid "" "An eerie wailing song emanates from the sirens perched upon the rocks. Many " "of your crew fall under its spell, and dive into the water where they drown. " "You are now wiser for the visit, and gain %{exp} experience." msgstr "" msgid "" "In a dazzling display of daring, you break into the local jail and free the " "hero imprisoned there, who, in return, pledges loyalty to your cause." msgstr "" msgid "" "You already have %{count} heroes, and regretfully must leave the prisoner in " "this jail to languish in agony for untold days." msgstr "" msgid "" "You enter a rickety hut and talk to the magician who lives there. He tells " "you of places near and far which may aid you in your journeys." msgstr "" msgid "This eye seems to be intently studying its surroundings." msgstr "" msgid "You come across a giant Sphinx. The Sphinx remains strangely quiet." msgstr "" msgid "" "\"I have a riddle for you,\" the Sphinx says. \"Answer correctly, and you " "shall be rewarded. Answer incorrectly, and you shall be eaten. Do you accept " "the challenge?\"" msgstr "" msgid "" "The Sphinx asks you the following riddle:\n" "\n" "'%{riddle}'\n" "\n" "Your answer?" msgstr "" msgid "" "\"You guessed incorrectly,\" the Sphinx says, smiling. The Sphinx swipes at " "you with a paw, knocking you to the ground. Another blow makes the world go " "black, and you know no more." msgstr "" msgid "" "Looking somewhat disappointed, the Sphinx sighs. \"You've answered my riddle " "so here's your reward. Now begone.\"" msgstr "" msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "As you speak the magic word, the glowing barrier dissolves into nothingness." msgstr "" msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "You speak, and nothing happens." msgstr "" msgid "" "You enter the tent and see an old woman gazing into a magic gem. She looks " "up and says,\n" "\"In my travels, I have learned much in the way of arcane magic. A great " "oracle taught me his skill. I have the answer you seek.\"" msgstr "" msgid "No spell book is present." msgstr "Nemáš Knihu kouzel, bez ní nemůžeÅ¡ kouzlit." #, fuzzy msgid "" "This spell costs %{mana} spell points. You have no spell points, so you " "cannot cast it." msgstr "Nemáš Knihu kouzel, bez ní nemůžeÅ¡ kouzlit." #, fuzzy msgid "" "This spell costs %{mana} spell points. You only have %{point} spell points, " "so you cannot cast it." msgstr "Nemáš Knihu kouzel, bez ní nemůžeÅ¡ kouzlit." #, fuzzy msgid "The spell was not found." msgstr "Toto kouzlo nebude na nikoho uÄinkovat!" msgid "Only heroes can cast this spell." msgstr "" #, fuzzy msgid "This hero is not able to cast adventure spells." msgstr "Seslat kouzlo." msgid "Your hero is too tired to cast this spell today. Try again tomorrow." msgstr "" msgid "This spell cannot be cast on a boat." msgstr "" msgid "This spell can only be cast near an ocean." msgstr "" msgid "" "There are no boats available and no ocean adjacent to the hero where this " "spell will work." msgstr "" #, fuzzy msgid "There are no boats available for this spell." msgstr "Seslat kouzlo." #, fuzzy msgid "There is no ocean adjacent to the hero where this spell will work." msgstr "" "Tvému hrdinovi nepÅ™ipadá žádné místo, kam by se dali Elemantálové pÅ™ivolat" msgid "" "You do not own any town or castle that is not currently occupied by a hero. " "This spell will have no effect." msgstr "" #, fuzzy msgid "This hero is already in a town, so this spell will have no effect." msgstr "Nemáš Knihu kouzel, bez ní nemůžeÅ¡ kouzlit." msgid "" "The nearest town is %{town}.\n" "\n" "This town is occupied by your hero %{hero}." msgstr "" msgid "This spell is already in effect." msgstr "" msgid "" "No opponent neither has nor can have any hero under their command anymore. " "Casting this spell will have no effect." msgstr "" msgid "" "No opponent has a hero under their command at this time. Casting this spell " "will have no effect." msgstr "" msgid "" "You must be within %{count} spaces of a monster for the Visions spell to " "work." msgstr "" msgid "" "You must be standing on the entrance to a mine (sawmills and alchemist labs " "do not count) to cast this spell." msgstr "" msgid "You must first defeat the ghosts guarding the mine to cast this spell." msgstr "" msgid "" "There are already at least as many elementals guarding the mine as this hero " "can generate. Casting this spell will have no effect." msgstr "" msgid "%{name} the %{race} (Level %{level})" msgstr "" #, fuzzy msgid "" "'Spread' combat formation spreads the hero's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "'Rozvinutá' bojová formace roztáhne tvoje jednotky shora až dolů po celém " "bojiÅ¡ti, s minimálnÄ› jedným volným polem mezi jednotkami." #, fuzzy msgid "" "'Grouped' combat formation bunches the hero's army together in the center of " "their side of the battlefield." msgstr "" "'Seskupená' bojová formace srazí tvoje jednotky do stÅ™edu bojiÅ¡tÄ› na tvé " "stranÄ› bojiÅ¡tÄ›." msgid "Are you sure you want to dismiss this Hero?" msgstr "" msgid "View Experience Info" msgstr "" msgid "View Spell Points Info" msgstr "" msgid "Set army combat formation to 'Spread'" msgstr "" msgid "Set army combat formation to 'Grouped'" msgstr "" msgid "Exit Hero Screen" msgstr "" msgid "You cannot dismiss a hero in a castle" msgstr "" msgid "Dismissal of %{name} the %{race} is prohibited by scenario" msgstr "" msgid "Dismiss %{name} the %{race}" msgstr "" #, fuzzy msgid "Show previous hero" msgstr "Zobrazit pÅ™edchozí mÄ›sto" #, fuzzy msgid "Show next hero" msgstr "Zobrazit následující mÄ›sto" msgid "Blood Morale" msgstr "Krevní morálka" msgid "%{morale} Morale" msgstr "" msgid "%{luck} Luck" msgstr "" msgid "Current Luck Modifiers:" msgstr "Å tÄ›stí ovlivňují:" msgid "Current Morale Modifiers:" msgstr "Morálku ovlivňují:" msgid "Entire army is undead, so morale does not apply." msgstr "Celá armáda je nemrtvá, morálka se nevztahuje." msgid "" "Current experience %{exp1}.\n" " Next level %{exp2}." msgstr "" "AktuálnÄ› zkuÅ¡enost %{exp1}.\n" " Další level %{exp2}." msgid "Level %{level}" msgstr "" msgid "" "%{name} currently has %{point} spell points out of a maximum of %{max}. The " "maximum number of spell points is 10 times the hero's knowledge. It is " "occasionally possible for the hero to have more than their maximum spell " "points via special events." msgstr "" msgid "%{name1} meets %{name2}" msgstr "%{name1} se stÅ™etá s %{name2}" msgid "Enemy heroes are now fully identifiable." msgstr "" msgid "Identify Hero" msgstr "Identifikuj hrdinu" msgid "Select town to port to." msgstr "" msgid "Town Portal" msgstr "" msgid "The creatures are willing to join us!" msgstr "" msgid "All the creatures will join us..." msgstr "" msgid "The creature will join us..." msgid_plural "%{count} of the creatures will join us..." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "" "\n" " for a fee of %{gold} gold." msgstr "" msgid "These weak creatures will surely flee before us." msgstr "" msgid "I fear these creatures are in the mood for a fight." msgstr "" #, fuzzy msgid "The hero's attack skill is a bonus added to each unit's attack skill." msgstr "každý s +%{attack} bonusem k jejich útoÄné síle." msgid "The hero's defense skill is a bonus added to each unit's defense skill." msgstr "" msgid "The hero's spell power determines the duration or power of a spell." msgstr "" msgid "" " The hero's knowledge determines how many spell points the hero may have. " "Under normal circumstances, a hero is limited to 10 spell points per level " "of knowledge." msgstr "" msgid "Current Modifiers:" msgstr "" msgid "skill|Basic" msgstr "Základ" msgid "skill|Advanced" msgstr "PokroÄilý" msgid "skill|Expert" msgstr "Expert" msgid "Pathfinding" msgstr "Hled. cest" msgid "Archery" msgstr "LukostÅ™elba" msgid "Logistics" msgstr "Logistika" msgid "Scouting" msgstr "Průzkumník" msgid "Diplomacy" msgstr "Diplomacie" msgid "Navigation" msgstr "Navigace" msgid "Leadership" msgstr "Vedení" msgid "Wisdom" msgstr "Moudrost" msgid "Mysticism" msgstr "Mystika" msgid "Ballistics" msgstr "Balistika" msgid "Eagle Eye" msgstr "Orlí Oko" msgid "Necromancy" msgstr "Nekromantie" msgid "Estates" msgstr "Majetek" msgid "Advanced Archery" msgstr "Pokr. LukostÅ™elba" msgid "Advanced Pathfinding" msgstr "Pokr. Hledání cest" msgid "Basic Archery" msgstr "Zákl. LukostÅ™elba" msgid "Basic Pathfinding" msgstr "Zákl. Hledání cest" msgid "Expert Pathfinding" msgstr "Exp. Hledání cest" msgid "Advanced Logistics" msgstr "Pokr. Logistika" msgid "Basic Logistics" msgstr "Zákl. Logistika" msgid "Basic Scouting" msgstr "Zákl. Průzkumník" msgid "Expert Archery" msgstr "Exp. LukostÅ™elba" msgid "Expert Logistics" msgstr "Exp. Logistika" msgid "Advanced Diplomacy" msgstr "Pokr. Diplomacie" msgid "Advanced Scouting" msgstr "Pokr. Průzkumník" msgid "Basic Diplomacy" msgstr "Zákl. Diplomacie" msgid "Expert Diplomacy" msgstr "Exp. Diplomacie" msgid "Expert Scouting" msgstr "Exp. Průzkumník" msgid "Advanced Leadership" msgstr "Pokr. Vedení" msgid "Advanced Navigation" msgstr "Pokr. Navigace" msgid "Basic Leadership" msgstr "Zákl. Vedení" msgid "Basic Navigation" msgstr "Zákl. Navigace" msgid "Expert Navigation" msgstr "Exp. Navigace" msgid "Advanced Wisdom" msgstr "Pokr. Moudrost" msgid "Basic Mysticism" msgstr "Zákl. Mystika" msgid "Basic Wisdom" msgstr "Zákl. Moudrost" msgid "Expert Leadership" msgstr "Exp. Vedení" msgid "Expert Wisdom" msgstr "Exp. Moudrost" msgid "Advanced Luck" msgstr "Pokr. Å tÄ›stí" msgid "Advanced Mysticism" msgstr "Pokr. Mystika" msgid "Basic Luck" msgstr "Zákl. Å tÄ›stí" msgid "Expert Luck" msgstr "Exp. Å tÄ›stí" msgid "Expert Mysticism" msgstr "Exp. Mystika" msgid "Advanced Ballistics" msgstr "Pokr. Balistika" msgid "Advanced Eagle Eye" msgstr "Pokr. Orlí Oko" msgid "Basic Ballistics" msgstr "Zákl. Balistika" msgid "Basic Eagle Eye" msgstr "Zákl. Orlí Oko" msgid "Expert Ballistics" msgstr "Exp. Balistika" msgid "Advanced Necromancy" msgstr "Pokr. Nekromantie" msgid "Basic Estates" msgstr "Zákl. Majetek" msgid "Basic Necromancy" msgstr "Zákl. Nekromantie" msgid "Expert Eagle Eye" msgstr "Exp. Orlí Oko" msgid "Expert Necromancy" msgstr "Exp. Nekromantie" msgid "Advanced Estates" msgstr "Pokr. Majetek" msgid "Expert Estates" msgstr "Exp. Majetek" msgid "" "%{skill} reduces the movement penalty for rough terrain by %{count} percent." msgstr "%{skill} snižuje zpomalení na nerovném terénu o %{count} procent." msgid "%{skill} eliminates the movement penalty for rough terrain." msgstr "%{skill} kompletnÄ› ruší zpomalení na nerovném terénu." #, fuzzy msgid "" "%{skill} increases the damage done by the hero's range attacking creatures " "by %{count} percent." msgstr "" "%{skill} zvyÅ¡uje Å¡kody způsobené jednotek útoÄících na dálku o %{count} " "procent." #, fuzzy msgid "%{skill} increases the hero's movement points by %{count} percent." msgstr "%{skill} zvyÅ¡uje body pohybu hrdiny o %{count} procent." #, fuzzy msgid "%{skill} increases the hero's viewable area by one square." msgid_plural "%{skill} increases the hero's viewable area by %{count} squares." msgstr[0] "%{skill} rozÅ¡iÅ™uje viditelnou oblast kolem hrdiny o jeden Ätverec." msgstr[1] "" "%{skill} rozÅ¡iÅ™uje viditelnou oblast kolem hrdiny o %{count} Ätverce." msgstr[2] "" "%{skill} rozÅ¡iÅ™uje viditelnou oblast kolem hrdiny o %{count} Ätverců." #, fuzzy msgid "" "%{skill} allows the hero to negotiate with monsters who are weaker than " "their army, and reduces the cost of surrender." msgstr "" "%{skill} Ti umožňuje vyjednávat se stvoÅ™eními, které jsou slabší jako je " "Tvoje družina. " #, fuzzy msgid "" "Approximately %{count} percent of the creatures may offer to join the hero." msgstr "" "PÅ™ibližnÄ› %{count} procent ze stvoÅ™ení Ti může nabídnout, že se pÅ™idají." #, fuzzy msgid "All of the creatures may offer to join the hero." msgstr "VÅ¡echna stvoÅ™ení Ti můžou nabídnout, že se pÅ™idají." msgid "" "The cost of surrender is reduced to %{percent} percent of the total cost of " "troops in the army." msgstr "" #, fuzzy msgid "" "%{skill} increases the hero's movement points over water by %{count} percent." msgstr "%{skill} zvyÅ¡uje body pohybu hrdiny na vodÄ› o %{count} procent." #, fuzzy msgid "%{skill} increases the hero's troops morale by %{count}." msgstr "%{skill} zvyÅ¡uje morálku u boj. jednotek hrdiny o %{count}." #, fuzzy msgid "%{skill} allows the hero to learn third level spells." msgstr "%{skill} umožnuje hrdinovi nauÄit se kouzla tÅ™etího stupnÄ›." #, fuzzy msgid "%{skill} allows the hero to learn fourth level spells." msgstr "%{skill} umožnuje hrdinovi nauÄit se kouzla Ätvrtého stupnÄ›." #, fuzzy msgid "%{skill} allows the hero to learn fifth level spells." msgstr "%{skill} umožnuje hrdinovi nauÄit se kouzla pátého stupnÄ›." #, fuzzy msgid "%{skill} regenerates one additional spell point per day to the hero." msgid_plural "" "%{skill} regenerates %{count} additional spell points per day to the hero." msgstr[0] "%{skill} regeneruje jeden bod magické energie za den." msgstr[1] "%{skill} regeneruje %{count} body magické energie za den." msgstr[2] "%{skill} regeneruje %{count} bodů magické energie za den." #, fuzzy msgid "%{skill} increases the hero's luck by %{count}." msgstr "%{skill} zvyÅ¡uje Å¡tÄ›stí hrdiny o %{count}." #, fuzzy msgid "" "%{skill} gives the hero's catapult shots a greater chance to hit and do " "damage to castle walls." msgstr "" "%{skill} dává katapultu vÄ›tší Å¡anci trefit se a více tím poÅ¡kodit zdi hradu." #, fuzzy msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot has a " "greater chance to hit and do damage to castle walls." msgstr "" "%{skill} dává katapultu stÅ™elu navíc, kdy každý z výstÅ™elů má vÄ›tší Å¡anci " "trefit se a více tím poÅ¡kodit zdi hradu." #, fuzzy msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot " "automatically destroys any wall, except a fortified wall in a Knight castle." msgstr "" "%{skill} dává katapultu stÅ™elu navíc, kdy každý z výstÅ™elů okamžitÄ› zboří " "zasažené místo zdi hradu, výjma zesílených hradeb v Rytířském hradu." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 1st or " "2nd level spell that was cast by an enemy during combat." msgstr "" "%{skill} dává hrdinovi %{count} procentní Å¡anci nauÄit se jakékoliv kouzlo " "prvé nebo druhé úrovnÄ›, které protivník použil v boji." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 3rd " "level spell (or below) that was cast by an enemy during combat." msgstr "" "%{skill} dává hrdinovi %{count} procentní Å¡anci nauÄit se jakékoliv kouzlo " "tÅ™etí (nebo nižší) úrovnÄ›, které protivník použil v boji." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 4th " "level spell (or below) that was cast by an enemy during combat." msgstr "" "%{skill} dává hrdinovi %{count} procentní Å¡anci nauÄit se jakékoliv kouzlo " "Ätvrté (nebo nižší) úrovnÄ›, které protivník použil v boji." msgid "" "%{skill} allows %{count} percent of the creatures killed in combat to be " "brought back from the dead as Skeletons." msgstr "" "%{skill} umožnuje aby %{count} procent nestvůr padlých v boji mohlo být " "vzkříšeno jako Kostlivci." #, fuzzy msgid "" "The hero produces %{count} gold pieces per day as tax revenue from estates." msgstr "" "Tvůj hrdina produkuje %{count} zlaťáků za den jako daňové příjmy z majetku." msgid "Blue" msgstr "Modrý" msgid "Green" msgstr "Zelený" msgid "Red" msgstr "ÄŒervený" msgid "Yellow" msgstr "Žlutý" msgid "Orange" msgstr "Oranžový" msgid "Purple" msgstr "Fialový" msgid "barrier|Aqua" msgstr "Aqua" msgid "barrier|Blue" msgstr "Modrá" msgid "barrier|Brown" msgstr "HnÄ›dá" msgid "barrier|Gold" msgstr "Zlatá" msgid "barrier|Green" msgstr "Zelená" msgid "barrier|Orange" msgstr "Oranžová" msgid "barrier|Purple" msgstr "Fialová" msgid "barrier|Red" msgstr "ÄŒervená" msgid "tent|Aqua" msgstr "Aqua" msgid "tent|Blue" msgstr "Modrý" msgid "tent|Brown" msgstr "HnÄ›dý" msgid "tent|Gold" msgstr "Zlatý" msgid "tent|Green" msgstr "Zelený" msgid "tent|Orange" msgstr "Oranžový" msgid "tent|Purple" msgstr "Fialový" msgid "tent|Red" msgstr "ÄŒervený" msgid "Experience" msgstr "ZkuÅ¡enosti" msgid "" "Experience allows your heroes to go up levels, increasing their primary and " "secondary skills." msgstr "" "ZkuÅ¡enosti umožňují tvým hrdinům postupovat na vyšší úrovné a zvyÅ¡ovat " "jejich primární a sekundární znalosti." msgid "Hero/Stats" msgstr "Hrdina/Stat" msgid "Skills" msgstr "Znalosti" msgid "Town/Castle" msgstr "MÄ›sto/Hrad" msgid "Garrison" msgstr "Posádka" msgid "Gold Per Day:" msgstr "Zlata za den:" msgid "View Heroes." msgstr "Zobraz Hrdiny." msgid "Towns/Castles" msgstr "MÄ›sta/Hrady" msgid "View Towns and Castles." msgstr "Zobraz MÄ›sta a Hrady." msgid "luck|Cursed" msgstr "Prokletí" msgid "luck|Awful" msgstr "Hrozné" msgid "luck|Bad" msgstr "Zlé" msgid "luck|Normal" msgstr "Normální" msgid "luck|Good" msgstr "Dobré" msgid "luck|Great" msgstr "SkvÄ›lé" msgid "luck|Irish" msgstr "Irské" #, fuzzy msgid "" "Bad luck sometimes falls on the hero's units in combat, causing their " "attacks to only do half damage." msgstr "" "Nízké Å¡tÄ›stí obÄas dolehne na Tvoje jednotky v bitvÄ› a zapříÄíní, že jejich " "útok způsobí pouze poloviÄní Å¡kody." #, fuzzy msgid "" "Neutral luck means the hero's units will never get lucky or unlucky attacks " "on the enemy." msgstr "" "Neutrální Å¡tÄ›stí znamená, že tvoje jednotky nikdy nezasadí nepříteli Å¡tastný " "ale ani ménÄ› povedený úder." #, fuzzy msgid "" "Good luck sometimes lets the hero's units get lucky attacks (double " "strength) in combat." msgstr "" "Vysoké Å¡tÄ›stí obÄas způsobí šťastný úder Tvých jednotek (dvojitou silou) v " "bitvÄ›." msgid "morale|Treason" msgstr "Zrada" msgid "morale|Awful" msgstr "Hrozná" msgid "morale|Poor" msgstr "Nízká" msgid "morale|Normal" msgstr "Normální" msgid "morale|Good" msgstr "Dobrá" msgid "morale|Great" msgstr "SkvÄ›lá" msgid "morale|Blood!" msgstr "Krev!" #, fuzzy msgid "Bad morale may cause the hero's units to freeze in combat." msgstr "Zlá morálka může způsobit, že Tvé jednotky v bitvÄ› ztuhnou." #, fuzzy msgid "" "Neutral morale means the hero's units will never be blessed with extra " "attacks or freeze in combat." msgstr "" "Neutrální morálka znamená, že Tvé jednotky neboudou obdaÅ™ené útokem navíc " "ale ani v boji neztuhnou." #, fuzzy msgid "Good morale may give the hero's units extra attacks in combat." msgstr "Dobrá morálka může dát Tvým jednotkám jeden útok navíc." msgid "Knight" msgstr "" msgid "Barbarian" msgstr "" msgid "Sorceress" msgstr "" msgid "Warlock" msgstr "" msgid "Wizard" msgstr "" msgid "Necromancer" msgstr "" msgid "Multi" msgstr "" msgid "doubleLined|Knight" msgstr "" msgid "doubleLined|Barbarian" msgstr "" msgid "doubleLined|Sorceress" msgstr "" msgid "doubleLined|Warlock" msgstr "" msgid "doubleLined|Wizard" msgstr "" msgid "" "doubleLined|Necro-\n" "mancer" msgstr "" msgid "doubleLinedRace|Multi" msgstr "" msgid "doubleLinedRace|Random" msgstr "" msgid "speed|Standing" msgstr "Stojící" msgid "speed|Crawling" msgstr "Plazící" msgid "speed|Very Slow" msgstr "Velmi pomalý" msgid "speed|Slow" msgstr "Pomalý" msgid "speed|Average" msgstr "PrůmÄ›r" msgid "speed|Fast" msgstr "Rychlý" msgid "speed|Very Fast" msgstr "Velmi rychlý" msgid "speed|Ultra Fast" msgstr "Ultra rychlý" msgid "speed|Blazing" msgstr "" msgid "speed|Instant" msgstr "" msgid "week|Squirrel" msgstr "" msgid "week|Rabbit" msgstr "" msgid "week|Gopher" msgstr "" msgid "week|Badger" msgstr "" msgid "week|Rat" msgstr "" msgid "week|Eagle" msgstr "" msgid "week|Weasel" msgstr "" msgid "week|Raven" msgstr "" msgid "week|Mongoose" msgstr "" msgid "week|Dog" msgstr "" msgid "week|Aardvark" msgstr "" msgid "week|Lizard" msgstr "" msgid "week|Tortoise" msgstr "" msgid "week|Hedgehog" msgstr "" msgid "week|Condor" msgstr "" msgid "week|Ant" msgstr "" msgid "week|Grasshopper" msgstr "" msgid "week|Dragonfly" msgstr "" msgid "week|Spider" msgstr "" msgid "week|Butterfly" msgstr "" msgid "week|Bumblebee" msgstr "" msgid "week|Locust" msgstr "" msgid "week|Earthworm" msgstr "" msgid "week|Hornet" msgstr "" msgid "week|Beetle" msgstr "" msgid "week|PLAGUE" msgstr "MORU" msgid "week|Unnamed" msgstr "Bez jména" msgid "Desert" msgstr "Poušť" msgid "Snow" msgstr "Sníh" msgid "Wasteland" msgstr "Pustina" msgid "Beach" msgstr "Pláž" msgid "Lava" msgstr "Láva" msgid "Dirt" msgstr "Hlína" msgid "Grass" msgstr "Tráva" msgid "Ocean" msgstr "MoÅ™e" msgid "maps|Small" msgstr "Malá" msgid "maps|Medium" msgstr "StÅ™ední" msgid "maps|Large" msgstr "Velká" msgid "maps|Extra Large" msgstr "Extra velká" msgid "maps|Custom Size" msgstr "Vlastní velikost" msgid "Ore Mine" msgstr "Důl Ruda" msgid "Sulfur Mine" msgstr "Důl Síra" msgid "Crystal Mine" msgstr "Důl KÅ™išťál" msgid "Gems Mine" msgstr "Důl Drahokam" msgid "Gold Mine" msgstr "Důl Zlato" msgid "Mine" msgstr "Důl" msgid "Burma shave." msgstr "" msgid "Next sign 50 miles." msgstr "" msgid "See Rock City." msgstr "" msgid "This space for rent." msgstr "" msgid "No object" msgstr "" msgid "Alchemist Lab" msgstr "" msgid "Sign" msgstr "ZnaÄka" msgid "Buoy" msgstr "Bóje" msgid "Skeleton" msgstr "Kostlivec" msgid "Daemon Cave" msgstr "JeskynÄ› démonů" msgid "Treasure Chest" msgstr "Truhla s pokladem" msgid "Faerie Ring" msgstr "ÄŒarovný kruh" msgid "Campfire" msgstr "OhniÅ¡tÄ›" msgid "Fountain" msgstr "Fontána" msgid "Gazebo" msgstr "Altán" msgid "Genie Lamp" msgstr "Džinova lampa" msgid "Archer's House" msgstr "Dům LuÄiÅ¡tníka" msgid "Goblin Hut" msgstr "Gobliní chatrÄ" msgid "Dwarf Cottage" msgstr "SkřítÄí chata" msgid "Peasant Hut" msgstr "ChatrÄ Rolníka" msgid "Stables" msgstr "Stáje" msgid "Alchemist's Tower" msgstr "Alchymistova věž" msgid "Event" msgstr "Událost" msgid "Dragon City" msgstr "DraÄí mÄ›sto" msgid "Lighthouse" msgid_plural "Lighthouses" msgstr[0] "Maják" msgstr[1] "Majáky" msgstr[2] "Majáků" msgid "Water Wheel" msgid_plural "Water Wheels" msgstr[0] "Vodní Mlýnů" msgstr[1] "Vodní Mlýny" msgstr[2] "Vodních Mlýnů" msgid "Monster" msgstr "Stvůra" msgid "Obelisk" msgstr "" msgid "Oasis" msgstr "Oáza" msgid "Resource" msgstr "Zdroje" msgid "Sawmill" msgstr "Pila" msgid "Oracle" msgstr "Věštírna" msgid "Shrine of the First Circle" msgstr "SvatynÄ› První úrovnÄ›" msgid "Shipwreck" msgstr "Vrak" msgid "Sea Chest" msgstr "Lodní kufr" msgid "Desert Tent" msgstr "PouÅ¡tní stan" msgid "Stone Liths" msgstr "Monolit" msgid "Wagon Camp" msgstr "TáboÅ™iÅ¡tÄ›" msgid "Hut of the Magi" msgstr "ChatrÄ Mága" msgid "Whirlpool" msgstr "Vodní vír" msgid "Windmill" msgid_plural "Windmills" msgstr[0] "VÄ›trný mlýn" msgstr[1] "VÄ›trné mlýny" msgstr[2] "VÄ›trných mlýnů" msgid "Artifact" msgstr "Artefakt" msgid "Mermaid" msgstr "MoÅ™ská panna" msgid "Boat" msgstr "LoÄ" msgid "Random Ultimate Artifact" msgstr "Náhodný Ultimátní Artefakt" msgid "Random Artifact" msgstr "Náhodný Artefakt" msgid "Random Resource" msgstr "Náhodný zdroj" msgid "Random Monster" msgstr "Náhodná stvůra" msgid "Random Town" msgstr "Náhodné mÄ›sto" msgid "Random Castle" msgstr "Náhodný hrad" msgid "Eye of the Magi" msgstr "Oko Mága" msgid "Random Monster - weak" msgstr "" msgid "Random Monster - medium" msgstr "" msgid "Random Monster - strong" msgstr "" msgid "Random Monster - very strong" msgstr "" #, fuzzy msgid "Hero" msgstr "Hrdinové" msgid "Nothing Special" msgstr "Nic zvláštního" msgid "Mossy Rock" msgstr "OmÅ¡elá skála" msgid "Watch Tower" msgstr "Strážná Věž" msgid "Tree House" msgstr "Dům na stromÄ›" msgid "Tree City" msgstr "Stromové mÄ›sto" msgid "Ruins" msgstr "Ruiny" msgid "Fort" msgstr "BaÅ¡ta" msgid "Abandoned Mine" msgstr "OpuÅ¡tÄ›ný důl" msgid "Sirens" msgstr "Sirény" msgid "Standing Stones" msgstr "Stojící kameny" msgid "Idol" msgstr "Socha" msgid "Tree of Knowledge" msgstr "Strom poznání" msgid "Witch Doctor's Hut" msgstr "ÄŒarodÄ›jova chýše" msgid "Temple" msgstr "Chrám" msgid "Hill Fort" msgstr "OpevnÄ›ní" msgid "Halfling Hole" msgstr "Díra PůlÄíků" msgid "Mercenary Camp" msgstr "Kemp žoldáků" msgid "Shrine of the Second Circle" msgstr "SvatynÄ› Druhé úrovnÄ›" msgid "Shrine of the Third Circle" msgstr "SvatynÄ› TÅ™etí úrovnÄ›" msgid "City of the Dead" msgstr "MÄ›sto Mrtvých" msgid "Sphinx" msgstr "Sfinga" msgid "Wagon" msgstr "Vůz" msgid "Tar Pit" msgstr "Dehet" msgid "Artesian Spring" msgstr "Artéský zdroj" msgid "Troll Bridge" msgstr "Trolí Most" msgid "Watering Hole" msgstr "Napajedlo" msgid "Witch's Hut" msgstr "Chýše VÄ›dmy" msgid "Xanadu" msgstr "" msgid "Lean-To" msgstr "PřístÅ™eÅ¡ek" msgid "Magellan's Maps" msgstr "" msgid "Flotsam" msgstr "Trosky" msgid "Derelict Ship" msgstr "Vrak Lodi" msgid "Shipwreck Survivor" msgstr "TroseÄník" msgid "Bottle" msgstr "Láhev" msgid "Magic Well" msgstr "Magická studna" msgid "Magic Garden" msgid_plural "Magic Gardens" msgstr[0] "Kouzelná zahrada" msgstr[1] "Kouzelné zahrady" msgstr[2] "Kouzelných zahrad" msgid "Observation Tower" msgstr "Rozhledna" msgid "Freeman's Foundry" msgstr "Svobodná Slévárna" msgid "Reefs" msgstr "Útesy" msgid "Volcano" msgstr "Sopka" msgid "Flowers" msgstr "KvÄ›tiny" msgid "Rock" msgstr "Skála" msgid "Water Lake" msgstr "Jezero" msgid "Mandrake" msgstr "Mandragora" msgid "Dead Tree" msgstr "Suchý strom" msgid "Stump" msgstr "Pahýl" msgid "Crater" msgstr "Kráter" msgid "Cactus" msgstr "Kaktus" msgid "Mound" msgstr "Kopec" msgid "Dune" msgstr "Duna" msgid "Lava Pool" msgstr "Jezero Lávy" msgid "Shrub" msgstr "KÅ™oví" msgid "Barrow Mounds" msgstr "" msgid "Random Artifact - Treasure" msgstr "" msgid "Random Artifact - Minor" msgstr "" msgid "Random Artifact - Major" msgstr "" msgid "Barrier" msgstr "Bariéra" msgid "Traveller's Tent" msgstr "Stan pocestného" msgid "Jail" msgstr "VÄ›zení" msgid "Fire Summoning Altar" msgstr "Oltář pÅ™ivolání OhnÄ›" msgid "Air Summoning Altar" msgstr "Oltář pÅ™ivolání Vzduchu" msgid "Earth Summoning Altar" msgstr "Oltář pÅ™ivolání ZemÄ›" msgid "Water Summoning Altar" msgstr "Oltář pÅ™ivolání Vody" msgid "Unknown Monster" msgstr "Neznámá stvůra" msgid "Unknown Monsters" msgstr "Neznámé stvůry" msgid "Peasant" msgstr "Rolník" msgid "Peasants" msgstr "Rolníci" msgid "Archer" msgstr "LuÄiÅ¡tník" msgid "Archers" msgstr "LuÄiÅ¡tníci" msgid "Ranger" msgstr "HraniÄář" msgid "Rangers" msgstr "HraniÄáři" msgid "Pikeman" msgstr "Kopiník" msgid "Pikemen" msgstr "Kopiníci" msgid "Veteran Pikeman" msgstr "Kopiník veterán" msgid "Veteran Pikemen" msgstr "Kopiníci veteráni" msgid "Swordsman" msgstr "Å ermíř" msgid "Swordsmen" msgstr "Å ermíři" msgid "Master Swordsman" msgstr "Mistr Å ermíř" msgid "Master Swordsmen" msgstr "MistÅ™i Å ermíři" msgid "Cavalries" msgstr "Kavalír" msgid "Cavalry" msgstr "Kavalerie" msgid "Champion" msgstr "Obránce" msgid "Champions" msgstr "Obránci" msgid "Paladin" msgstr "" msgid "Paladins" msgstr "Paladini" msgid "Crusader" msgstr "KÅ™ižák" msgid "Crusaders" msgstr "KÅ™ižáci" msgid "Goblin" msgstr "" msgid "Goblins" msgstr "Goblini" msgid "Orc" msgstr "SkÅ™et" msgid "Orcs" msgstr "SkÅ™eti" msgid "Orc Chief" msgstr "SkÅ™et Velitel" msgid "Orc Chiefs" msgstr "SkÅ™eti Velitelé" msgid "Wolf" msgstr "Vlk" msgid "Wolves" msgstr "Vlci" msgid "Ogre" msgstr "Zlobr" msgid "Ogres" msgstr "ZlobÅ™i" msgid "Ogre Lord" msgstr "Pán Zlobr" msgid "Ogre Lords" msgstr "Páni ZlobÅ™i" msgid "Troll" msgstr "Trol" msgid "Trolls" msgstr "Trolové" msgid "War Troll" msgstr "VáleÄný Trol" msgid "War Trolls" msgstr "VáleÄní Trolové" msgid "Cyclopes" msgstr "Kyklop" msgid "Cyclops" msgstr "Kyklopové" msgid "Sprite" msgstr "Víla" msgid "Sprites" msgstr "Víly" msgid "Dwarf" msgstr "Skřítek" msgid "Dwarves" msgstr "Skřítci" msgid "Battle Dwarf" msgstr "Bojový Skřítek" msgid "Battle Dwarves" msgstr "Bojoví Skřítci" msgid "Elf" msgstr "" msgid "Elves" msgstr "Elfové" msgid "Grand Elf" msgstr "VzneÅ¡ený Elf" msgid "Grand Elves" msgstr "VzneÅ¡ení Elfové" msgid "Druid" msgstr "" msgid "Druids" msgstr "Druidi" msgid "Greater Druid" msgstr "Věštec" msgid "Greater Druids" msgstr "Věštci" msgid "Unicorn" msgstr "Jednorožec" msgid "Unicorns" msgstr "Jednorožci" msgid "Phoenix" msgstr "Fénix" msgid "Phoenixes" msgstr "Fénixové" msgid "Centaur" msgstr "Kentaur" msgid "Centaurs" msgstr "KentauÅ™i" msgid "Gargoyle" msgstr "ChrliÄ" msgid "Gargoyles" msgstr "ChrliÄi" msgid "Griffin" msgstr "Gryf" msgid "Griffins" msgstr "Gryfové" msgid "Minotaur" msgstr "" msgid "Minotaurs" msgstr "MinotauÅ™i" msgid "Minotaur King" msgstr "Minotauří Král" msgid "Minotaur Kings" msgstr "Minotauří Králové" msgid "Hydra" msgstr "" msgid "Hydras" msgstr "Hydry" msgid "Green Dragon" msgstr "Zelený Drak" msgid "Green Dragons" msgstr "Zelení Draci" msgid "Red Dragon" msgstr "ÄŒervený Drak" msgid "Red Dragons" msgstr "ÄŒervení Draci" msgid "Black Dragon" msgstr "ÄŒerný Drak" msgid "Black Dragons" msgstr "ÄŒerní Draci" msgid "Halfling" msgstr "PůlÄík" msgid "Halflings" msgstr "PůlÄíci" msgid "Boar" msgstr "Kanec" msgid "Boars" msgstr "Kanci" msgid "Iron Golem" msgstr "Kovový Golem" msgid "Iron Golems" msgstr "Kovoví Golemové" msgid "Steel Golem" msgstr "Železný Golem" msgid "Steel Golems" msgstr "Železní Golemové" msgid "Roc" msgstr "Ruch" msgid "Rocs" msgstr "Ruchové" msgid "Mage" msgstr "Mág" msgid "Magi" msgstr "Mágové" msgid "Archmage" msgstr "Å edý Mág" msgid "Archmagi" msgstr "Å edí Mágové" msgid "Giant" msgstr "Obr" msgid "Giants" msgstr "ObÅ™i" msgid "Titan" msgstr "Titán" msgid "Titans" msgstr "Titáni" msgid "Skeletons" msgstr "Kostlivci" msgid "Zombie" msgstr "" msgid "Zombies" msgstr "" msgid "Mutant Zombie" msgstr "" msgid "Mutant Zombies" msgstr "" msgid "Mummies" msgstr "Mumie" msgid "Mummy" msgstr "Mumie" msgid "Royal Mummies" msgstr "Královské Mumie" msgid "Royal Mummy" msgstr "Královská Mumie" msgid "Vampire" msgstr "Upír" msgid "Vampires" msgstr "Upíři" msgid "Vampire Lord" msgstr "Lord Upír" msgid "Vampire Lords" msgstr "Lordi Upíři" msgid "Lich" msgstr "KostÄ›j" msgid "Liches" msgstr "Lichové" msgid "Power Lich" msgstr "Mocný KostÄ›j" msgid "Power Liches" msgstr "Mocní KostÄ›jové" msgid "Bone Dragon" msgstr "KostÄ›ný Drak" msgid "Bone Dragons" msgstr "KostÄ›ní Draci" msgid "Rogue" msgstr "LupiÄ" msgid "Rogues" msgstr "LupiÄi" msgid "Nomad" msgstr "Nomád" msgid "Nomads" msgstr "Nomádi" msgid "Ghost" msgstr "Duch" msgid "Ghosts" msgstr "Duchové" msgid "Genie" msgstr "Džin" msgid "Genies" msgstr "Džinové" msgid "Medusa" msgstr "Medůza" msgid "Medusas" msgstr "Medůzy" msgid "Earth Elemental" msgstr "Půdný Elementál" msgid "Earth Elementals" msgstr "Půdní Elementálové" msgid "Air Elemental" msgstr "VzduÅ¡ný Elementál" msgid "Air Elementals" msgstr "VzduÅ¡ní Elementálové" msgid "Fire Elemental" msgstr "Ohňový Elementál" msgid "Fire Elementals" msgstr "Ohňoví Elementálové" msgid "Water Elemental" msgstr "Vodný Elementál" msgid "Water Elementals" msgstr "Vodní Elementálové" msgid "Random Monsters" msgstr "" msgid "Random Monster 1" msgstr "" msgid "Random Monsters 1" msgstr "" msgid "Random Monster 2" msgstr "" msgid "Random Monsters 2" msgstr "" msgid "Random Monster 3" msgstr "" msgid "Random Monsters 3" msgstr "" msgid "Random Monster 4" msgstr "" msgid "Random Monsters 4" msgstr "" msgid "Double shot" msgstr "" msgid "2-hex monster" msgstr "Tvor pÅ™es 2 Å¡estúhelníky" msgid "Double strike" msgstr "" msgid "Double damage to Undead" msgstr "Dvojnásobné zranÄ›ní nemrtvých" msgid "% magic resistance" msgstr "" msgid "Immune to Mind spells" msgstr "" msgid "Immune to Elemental spells" msgstr "" msgid "Immune to Fire spells" msgstr "" msgid "Immune to Cold spells" msgstr "" msgid "Immune to " msgstr "" msgid "% immunity to %{spell} spell" msgstr "" msgid "% damage from Elemental spells" msgstr "" msgid "% chance to Dispel beneficial spells" msgstr "" msgid "% chance to Paralyze" msgstr "" msgid "% chance to Petrify" msgstr "" msgid "% chance to Blind" msgstr "" msgid "% chance to Curse" msgstr "" msgid "% chance to cast %{spell} spell" msgstr "" msgid "HP regeneration" msgstr "Regenerace života" msgid "Two hexes attack" msgstr "" msgid "Flyer" msgstr "Létající" msgid "Always retaliates" msgstr "" msgid "Attacks all adjacent enemies" msgstr "" msgid "No melee penalty" msgstr "" msgid "Dragon" msgstr "DraÄí" msgid "Undead" msgstr "Nemrtvý" msgid "No enemy retaliation" msgstr "Žádná odplata nepřítele" msgid "HP drain" msgstr "OdÄerpání života" msgid "Cloud attack" msgstr "" msgid "Decreases enemy's morale by " msgstr "" msgid "% chance to halve enemy" msgstr "" msgid "Soul Eater" msgstr "PožíraÄ duší" msgid "Elemental" msgstr "Elementál" msgid "No Morale" msgstr "Žádná morálka" msgid "200% damage from Fire spells" msgstr "" msgid "200% damage from Cold spells" msgstr "" msgid "% damage from %{spell} spell" msgstr "" msgid "% immunity to " msgstr "" msgid "Lightning" msgstr "" msgid "% damage from " msgstr "" msgid "The three Anduran artifacts magically combine into one." msgstr "TÅ™i Anduranovy artefakty se magicky spojí v jeden." #, fuzzy msgid "View Spells" msgstr "Hrdinové" #, fuzzy msgid "View %{name} Info" msgstr "Zobrazit %{name}" #, fuzzy msgid "Move %{name}" msgstr "Zobrazit %{name}" msgid "Cannot move the Spellbook" msgstr "Nelze pÅ™esunout knihu kouzel" msgid "This item can't be traded." msgstr "" msgid "Invalid Artifact" msgstr "Nesprávný Artefakt" #, fuzzy msgid "The %{name} increases the hero's knowledge by %{count}." msgstr "%{name} zvýší tvou morálku o %{count}." msgid "Ultimate Book of Knowledge" msgstr "Ultimátní kniha vÄ›domostí" #, fuzzy msgid "The %{name} increases the hero's attack skill by %{count}." msgstr "%{skill} zvyÅ¡uje Å¡tÄ›stí hrdiny o %{count}." msgid "Ultimate Sword of Dominion" msgstr "Ultimátní meÄ nadvlády" #, fuzzy msgid "The %{name} increases the hero's defense skill by %{count}." msgstr "%{skill} zvyÅ¡uje Å¡tÄ›stí hrdiny o %{count}." msgid "Ultimate Cloak of Protection" msgstr "Ultimátní plášť ochrany" #, fuzzy msgid "The %{name} increases the hero's spell power by %{count}." msgstr "%{name} zvýší tvou morálku o %{count}." msgid "Ultimate Wand of Magic" msgstr "Ultimátní kouzelná hůlka" #, fuzzy msgid "" "The %{name} increases the hero's attack and defense skills by %{count} each." msgstr "%{skill} zvyÅ¡uje body pohybu hrdiny o %{count} procent." msgid "Ultimate Shield" msgstr "Ultimátní Å¡tít" #, fuzzy msgid "" "The %{name} increases the hero's spell power and knowledge by %{count} each." msgstr "%{name} zvýší tvou morálku o %{count}." msgid "Ultimate Staff" msgstr "Ultimátní tyÄ" #, fuzzy msgid "" "The %{name} increases each of the hero's basic skills by %{count} points." msgstr "%{name} zvýší tvou morálku o %{count}." msgid "Ultimate Crown" msgstr "Ultimátní koruna" msgid "Golden Goose" msgstr "Zlatá husa" msgid "The %{name} brings in an income of %{count} gold per day." msgstr "" msgid "Arcane Necklace of Magic" msgstr "" msgid "" "After rescuing a Sorceress from a cursed tomb, she rewards your heroism with " "an exquisite jeweled necklace." msgstr "" msgid "Caster's Bracelet of Magic" msgstr "" msgid "" "While searching through the rubble of a caved-in mine, you free a group of " "trapped Dwarves. Grateful, the leader gives you a golden bracelet." msgstr "" msgid "Mage's Ring of Power" msgstr "" msgid "" "A cry of pain leads you to a Centaur, caught in a trap. Upon setting the " "creature free, he hands you a small pouch. Emptying the contents, you find a " "dazzling jeweled ring." msgstr "" msgid "Witch's Broach of Magic" msgstr "" msgid "" "Alongside the remains of a burnt witch lies a beautiful broach, intricately " "designed. Approaching the corpse with caution, you add the broach to your " "inventory." msgstr "" msgid "Medal of Valor" msgstr "Medaile za stateÄnost" #, fuzzy msgid "The %{name} increases the morale of the hero's army by %{count}." msgstr "%{name} zvýší tvou morálku o %{count}." msgid "" "Freeing a virtuous maiden from the clutches of an evil overlord, you are " "granted a Medal of Valor by the King's herald." msgstr "" msgid "Medal of Courage" msgstr "Medaile za odvahu" msgid "" "After saving a young boy from a vicious pack of Wolves, you return him to " "his father's manor. The grateful nobleman awards you with a Medal of Courage." msgstr "" msgid "Medal of Honor" msgstr "Medaile cti" msgid "" "After freeing a princess of a neighboring kingdom from the evil clutches of " "despicable slavers, she awards you with a Medal of Honor." msgstr "" msgid "Medal of Distinction" msgstr "Medaile za zásluhy" msgid "" "Ridding the countryside of the hideous Minotaur who made a sport of eating " "noblemen's Knights, you are honored with the Medal of Distinction." msgstr "" msgid "Fizbin of Misfortune" msgstr "Fizbin neÅ¡tÄ›stí" #, fuzzy msgid "" "The %{name} greatly decreases the morale of the hero's army by %{count}." msgstr "%{name} zvýší tvou morálku o %{count}." msgid "" "You stumble upon a medal lying alongside the empty road. Adding the medal to " "your inventory, you become aware that you have acquired the undesirable " "Fizbin of Misfortune, greatly decreasing your army's morale." msgstr "" msgid "Thunder Mace of Dominion" msgstr "" msgid "" "During a sudden storm, a bolt of lightning strikes a tree, splitting it. " "Inside the tree you find a mysterious mace." msgstr "" msgid "Armored Gauntlets of Protection" msgstr "" #, fuzzy msgid "The %{name} increase the hero's defense skill by %{count}." msgstr "%{skill} zvyÅ¡uje Å¡tÄ›stí hrdiny o %{count}." msgid "" "You encounter the infamous Black Knight! After a grueling duel ending in a " "draw, the Knight, out of respect, offers you a pair of armored gauntlets." msgstr "" msgid "Defender Helm of Protection" msgstr "" msgid "" "A glint of golden light catches your eye. Upon further investigation, you " "find a golden helm hidden under a bush." msgstr "" msgid "Giant Flail of Dominion" msgstr "" msgid "" "A clumsy Giant has killed himself with his own flail. Knowing your superior " "skill with this weapon, you confidently remove the spectacular flail from " "the fallen Giant." msgstr "" msgid "Ballista of Quickness" msgstr "" msgid "The %{name} gives the hero's catapult one extra shot per combat round." msgstr "" msgid "" "Walking through the ruins of an ancient walled city, you find the instrument " "of the city's destruction, an elaborately crafted ballista." msgstr "" msgid "Stealth Shield of Protection" msgstr "" msgid "" "A stone statue of a warrior holds a silver shield. As you remove the shield, " "the statue crumbles into dust." msgstr "" msgid "Dragon Sword of Dominion" msgstr "" msgid "" "As you are walking along a narrow path, a nearby bush suddenly bursts into " "flames. Before your eyes the flames become the image of a beautiful woman. " "She holds out a magnificent sword to you." msgstr "" msgid "Power Axe of Dominion" msgstr "" msgid "" "You see a silver axe embedded deeply in the ground. After several " "unsuccessful attempts by your army to remove the axe, you tightly grip the " "handle of the axe and effortlessly pull it free." msgstr "" msgid "Divine Breastplate of Protection" msgstr "" msgid "" "A gang of Rogues is sifting through the possessions of dead warriors. " "Scaring off the scavengers, you note the Rogues had overlooked a beautiful " "breastplate." msgstr "" msgid "Minor Scroll of Knowledge" msgstr "" msgid "" "Before you appears a levitating glass case with a scroll, perched upon a bed " "of crimson velvet. At your touch, the lid opens and the scroll floats into " "your awaiting hands." msgstr "" msgid "Major Scroll of Knowledge" msgstr "" msgid "" "Visiting a local wiseman, you explain the intent of your journey. He reaches " "into a sack and withdraws a yellowed scroll and hands it to you." msgstr "" msgid "Superior Scroll of Knowledge" msgstr "" msgid "" "You come across the remains of an ancient Druid. Bones, yellowed with age, " "peer from the ragged folds of her robe. Searching the robe, you discover a " "scroll hidden in the folds." msgstr "" msgid "Foremost Scroll of Knowledge" msgstr "" msgid "" "Mangled bones, yellowed with age, peer from the ragged folds of a dead " "Druid's robe. Searching the robe, you discover a scroll hidden within." msgstr "" msgid "Endless Sack of Gold" msgstr "" #, fuzzy msgid "The %{name} provides the hero with %{count} gold per day." msgstr "%{name} tÄ› stojí %{count} zlaťáků dennÄ›." msgid "" "A little leprechaun dances gleefully around a magic sack. Seeing you " "approach, he stops in mid-stride. The little man screams and stamps his foot " "ferociously, vanishing into thin air. Remembering the old leprechaun saying " "'Finders Keepers', you grab the sack and leave." msgstr "" msgid "Endless Bag of Gold" msgstr "" msgid "" "A noblewoman, separated from her traveling companions, asks for your help. " "After escorting her home, she rewards you with a bag filled with gold." msgstr "" msgid "Endless Purse of Gold" msgstr "" msgid "" "In your travels, you find a leather purse filled with gold that once " "belonged to a great warrior king who had the ability to transform any " "inanimate object into gold." msgstr "" msgid "Nomad Boots of Mobility" msgstr "" #, fuzzy msgid "The %{name} increase the hero's movement on land." msgstr "%{name} zvyÅ¡uje tvůj pohyb na moÅ™i." msgid "" "A Nomad trader seeks protection from a tribe of Goblins. For your " "assistance, he gives you a finely crafted pair of boots made from the " "softest leather. Looking closely, you see fascinating ancient carvings " "engraved on the leather." msgstr "" msgid "Traveler's Boots of Mobility" msgstr "" msgid "" "Discovering a pair of beautifully beaded boots made from the finest and " "softest leather, you thank the anonymous donor and add the boots to your " "inventory." msgstr "" msgid "Lucky Rabbit's Foot" msgstr "" #, fuzzy msgid "The %{name} increases the luck of the hero's army by %{count}." msgstr "%{name} zvyÅ¡uje tvé Å¡tÄ›stí v boji o %{count}." msgid "" "A traveling merchant offers you a rabbit's foot, made of gleaming silver " "fur, for safe passage. The merchant explains the charm will increase your " "luck in combat." msgstr "" msgid "Golden Horseshoe" msgstr "" msgid "" "An ensnared Unicorn whinnies in fright. Murmuring soothing words, you set " "her free. Snorting and stamping her front hoof once, she gallops off. " "Looking down you see a golden horseshoe." msgstr "" msgid "Gambler's Lucky Coin" msgstr "" msgid "" "You have captured a mischievous imp who has been terrorizing the region. In " "exchange for his release, he rewards you with a magical coin." msgstr "" msgid "Four-Leaf Clover" msgstr "" msgid "" "In the middle of a patch of dead and dry vegetation, to your surprise you " "find a healthy green four-leaf clover." msgstr "" msgid "True Compass of Mobility" msgstr "" #, fuzzy msgid "The %{name} increases the hero's movement on land and sea." msgstr "%{name} zvyÅ¡uje tvůj pohyb na moÅ™i." msgid "" "An old man claiming to be an inventor asks you to try his latest invention. " "He then hands you a compass." msgstr "" msgid "Sailor's Astrolabe of Mobility" msgstr "" #, fuzzy msgid "The %{name} increases the hero's movement on sea." msgstr "%{name} zvyÅ¡uje tvůj pohyb na moÅ™i." msgid "" "An old sea captain is being tortured by Ogres. You save him, and in return " "he rewards you with a wondrous instrument to measure the distance of a star." msgstr "" msgid "Evil Eye" msgstr "" msgid "The %{name} reduces the casting cost of curse spells by half." msgstr "" msgid "" "While venturing into a decrepit hut you find the Skeleton of a long dead " "witch. Investigation of the remains reveals a glass eye rolling around " "inside an empty skull." msgstr "" msgid "Enchanted Hourglass" msgstr "" msgid "" "The %{name} extends the duration of all the hero's spells by %{count} turns." msgstr "" msgid "" "A surprise turn in the landscape finds you in the midst of a grisly scene: " "Vultures picking at the aftermath of a terrible battle. Your cursory search " "of the remains turns up an enchanted hourglass." msgstr "" msgid "Gold Watch" msgstr "" msgid "The %{name} doubles the effectiveness of the hero's hypnotize spells." msgstr "" msgid "" "In reward for helping his cart out of a ditch, a traveling potion salesman " "gives you a \"magic\" gold watch. Unbeknownst to him, the watch really is " "magical." msgstr "" msgid "Skullcap" msgstr "" msgid "The %{name} halves the casting cost of all mind influencing spells." msgstr "" msgid "" "A brief stop at an improbable rural inn yields an exchange of money, tales, " "and accidentally, luggage. You find a magical skullcap in your new backpack." msgstr "" msgid "Ice Cloak" msgstr "" msgid "" "The %{name} halves all damage the hero's troops receive from cold spells." msgstr "" msgid "" "Responding to the panicked cries of a damsel in distress, you discover a " "young woman fleeing from a hungry bear. You slay the beast in the nick of " "time, and the grateful Sorceress weaves a magic cloak from the bear's hide." msgstr "" msgid "Fire Cloak" msgstr "" msgid "" "The %{name} halves all damage the hero's troops receive from fire spells." msgstr "" msgid "" "You've come upon a fight between a Necromancer and a Paladin. The " "Necromancer blasts the Paladin with a fire bolt, bringing him to his knees. " "Acting quickly, you slay the evil one before the final blow. The grateful " "Paladin gives you the fire cloak that saved him." msgstr "" msgid "Lightning Helm" msgstr "" msgid "" "The %{name} halves all damage the hero's troops receive from lightning " "spells." msgstr "" msgid "" "A traveling tinker in need of supplies offers you a helm with a thunderbolt " "design on its top in exchange for food and water. Curious, you accept, and " "later find out that the helm is magical." msgstr "" msgid "Evercold Icicle" msgstr "" msgid "" "The %{name} causes the hero's cold spells to do %{count} percent more damage " "to enemy troops." msgstr "" msgid "" "An icicle withstanding the full heat of the noonday sun attracts your " "attention. Intrigued, you break it off, and find that it does not melt in " "your hand." msgstr "" msgid "Everhot Lava Rock" msgstr "" msgid "" "The %{name} causes the hero's fire spells to do %{count} percent more damage " "to enemy troops." msgstr "" msgid "" "Your wanderings bring you into contact with a tribe of ape-like beings using " "a magical lava rock that never cools to light their fires. You take pity on " "them and teach them to make fire with sticks. Believing you to be a god, the " "apes give you their rock." msgstr "" msgid "Lightning Rod" msgstr "" msgid "" "The %{name} causes the hero's lightning spells to do %{count} percent more " "damage to enemy troops." msgstr "" msgid "" "While waiting out a storm, a lighting bolt strikes a nearby cottage's " "lightning rod, which melts and falls to the ground. The tip of the rod, " "however, survives intact and makes your hair stand on end when you touch it. " "Hmm..." msgstr "" msgid "Snake-Ring" msgstr "" msgid "The %{name} halves the casting cost of all of the hero's bless spells." msgstr "" msgid "" "You've found an oddly shaped ring on the finger of a long dead traveler. The " "ring looks like a snake biting its own tail." msgstr "" msgid "Ankh" msgstr "Anch" msgid "" "The %{name} doubles the effectiveness of all of the hero's resurrect and " "animate spells." msgstr "" msgid "" "A fierce windstorm reveals the entrance to a buried tomb. Your investigation " "reveals that the tomb has already been looted, but the thieves overlooked an " "ankh on a silver chain in the dark." msgstr "" msgid "Book of Elements" msgstr "" msgid "" "The %{name} doubles the effectiveness of all of the hero's summoning spells." msgstr "" msgid "" "You come across a conjurer who begs to accompany you and your army awhile " "for safety. You agree, and he offers as payment a copy of the book of the " "elements." msgstr "" msgid "Elemental Ring" msgstr "" msgid "The %{name} halves the casting cost of all summoning spells." msgstr "" msgid "" "While pausing to rest, you notice a bobcat climbing a short tree to get at a " "crow's nest. On impulse, you climb the tree yourself and scare off the cat. " "When you look in the nest, you find a collection of shiny stones and a ring." msgstr "" msgid "Holy Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to curse spells." msgstr "" msgid "" "In your wanderings you come across a hermit living in a small, tidy hut. " "Impressed with your mission, he takes time out from his meditations to bless " "and give you a charm against curses." msgstr "" msgid "Pendant of Free Will" msgstr "" msgid "The %{name} makes all of the hero's troops immune to hypnotize spells." msgstr "" msgid "" "Responding to cries for help, you find river Sprites making a sport of " "dunking an old man. Feeling vengeful, you rescue the man and drag a Sprite " "onto dry land for awhile. The Sprite, uncomfortable in the air, gives you a " "magic pendant to let him go." msgstr "" msgid "Pendant of Life" msgstr "" msgid "The %{name} makes all of the hero's troops immune to death spells." msgstr "" msgid "" "A brief roadside encounter with a small caravan and a game of knucklebones " "wins a magic pendant. Its former owner says that it protects from " "Necromancers' death spells." msgstr "" msgid "Serenity Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to berserk spells." msgstr "" msgid "" "The sounds of combat draw you to the scene of a fight between an old " "Barbarian and an eight-headed Hydra. Your timely intervention swings the " "battle in favor of the man, and he rewards you with a pendant he used to use " "to calm his mind for battle." msgstr "" msgid "Seeing-eye Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to blindness spells." msgstr "" msgid "" "You come upon a very old woman, long blind from cataracts and dying alone. " "You tend to her final needs and promise a proper burial. Grateful, she gives " "you a magic pendant emblazoned with a stylized eye. It lets you see with " "your eyes closed." msgstr "" msgid "Kinetic Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to paralyze spells." msgstr "" msgid "" "You come across a golem wearing a glowing pendant and blocking your way. " "Acting on a hunch, you cut the pendant from its neck. Deprived of its power " "source, the golem breaks down, leaving you with the magical pendant." msgstr "" msgid "Pendant of Death" msgstr "" msgid "The %{name} makes all of the hero's troops immune to holy spells." msgstr "" msgid "" "A quick and deadly battle with a Necromancer wins you his magical pendant. " "Later, a Wizard tells you that the pendant protects undead under your " "control from holy word spells." msgstr "" msgid "Wand of Negation" msgstr "" msgid "" "The %{name} makes all of the hero's troops immune to dispel magic spells." msgstr "" msgid "" "You meet an old Wizard friend of yours traveling in the opposite direction. " "He presents you with a gift: A wand that prevents the use of the dispel " "magic spell on your allies." msgstr "" msgid "Golden Bow" msgstr "" msgid "" "The %{name} eliminates the %{count} percent penalty for the hero's troops " "shooting past obstacles (e.g. castle walls)." msgstr "" msgid "" "A chance meeting with a famous Archer finds you in a game of knucklebones " "pitting his bow against your horse. You win." msgstr "" msgid "Telescope" msgstr "" #, fuzzy msgid "" "The %{name} increases the amount of terrain the hero reveals when " "adventuring by %{count} extra square." msgstr "" "Studna zvyÅ¡uje tempo růstu ve vÅ¡ech obydlích o %{count} stvoÅ™ení za týden." msgid "" "A merchant from far away lands trades you a new invention of his people for " "traveling supplies. It makes distant objects appear closer, and he calls " "it...\n" "\n" "a telescope." msgstr "" msgid "Statesman's Quill" msgstr "" msgid "" "The %{name} reduces the cost of surrender to %{count} percent of the total " "cost of troops the hero has in their army." msgstr "" msgid "" "You pause to help a diplomat with a broken axle fix his problem. In " "gratitude, he gives you a writing quill with magical properties which he " "says will \"help people see things your way\"." msgstr "" msgid "Wizard's Hat" msgstr "" #, fuzzy msgid "" "The %{name} increases the duration of the hero's spells by %{count} turns." msgstr "%{name} zvýší tvou morálku o %{count}." msgid "" "You see a Wizard fleeing from a Griffin and riding like the wind. The Wizard " "opens a portal and rides through, getting his hat knocked off by the edge of " "the gate. The Griffin follows; the gate closes. You pick the hat up, dust it " "off, and put it on." msgstr "" msgid "Power Ring" msgstr "" #, fuzzy msgid "The %{name} returns %{count} extra spell points per day to the hero." msgstr "%{skill} regeneruje jeden bod magické energie za den." msgid "" "You find a small tree that closely resembles the great Warlock Carnauth with " "a ring around one of its twigs. Scraps of clothing and rotting leather lead " "you to suspect that it IS Carnauth, transformed. Since you can't help him, " "you take the magic ring." msgstr "" msgid "Ammo Cart" msgstr "" msgid "" "The %{name} provides endless ammunition for all of the hero's troops that " "shoot." msgstr "" msgid "" "An ammunition cart in the middle of an old battlefield catches your eye. " "Inspection shows it to be in good working order, so you take it along." msgstr "" msgid "Tax Lien" msgstr "" #, fuzzy msgid "The %{name} costs the hero %{count} gold pieces per day." msgstr "%{name} tÄ› stojí %{count} zlaťáků dennÄ›." msgid "" "Your big spending habits have earned you a massive tax bill that you can't " "hope to pay. The tax man takes pity and agrees to only take 250 gold a day " "from your account for life. Check here if you want one dollar to go to the " "presidential campaign election fund." msgstr "" msgid "Hideous Mask" msgstr "" msgid "The %{name} prevents all 'wandering' armies from joining the hero." msgstr "" msgid "" "Your looting of the grave of Sinfilas Gardolad, the famous shapeshifting " "Warlock, unearths his fabled mask. Trembling, you put it on and it twists " "your visage into an awful grimace! Oh no! It's actually the hideous mask of " "Gromluck Greene, and you are stuck with it." msgstr "" msgid "Endless Pouch of Sulfur" msgstr "" msgid "The %{name} provides %{count} unit of sulfur per day." msgstr "" msgid "" "You visit an alchemist who, upon seeing your army, is swayed by the " "righteousness of your cause. The newly loyal subject gives you his endless " "pouch of sulfur to help with the war effort." msgstr "" msgid "Endless Vial of Mercury" msgstr "" msgid "The %{name} provides %{count} unit of mercury per day." msgstr "" msgid "" "A brief stop at a hastily abandoned Wizard's tower turns up a magical vial " "of mercury that always has a little left on the bottom. Recognizing a " "treasure when you see one, you cap it and slip it in your pocket." msgstr "" msgid "Endless Pouch of Gems" msgstr "" msgid "The %{name} provides %{count} unit of gems per day." msgstr "" msgid "" "A short rainstorm brings forth a rainbow...and you can see the end of it. " "Riding quickly, you seize the pot of gold you find there. The leprechaun who " "owns it, unable to stop you from taking it, offers an endless pouch of gems " "for the return of his gold. You accept." msgstr "" msgid "Endless Cord of Wood" msgstr "" msgid "The %{name} provides %{count} unit of wood per day." msgstr "" msgid "" "Pausing to rest and light a cook fire, you pull wood out of a nearby pile of " "dead wood. As you keep pulling wood from the pile, you notice that it " "doesn't shrink. You realize to your delight that the wood is enchanted, so " "you take it along." msgstr "" msgid "Endless Cart of Ore" msgstr "" msgid "The %{name} provides %{count} unit of ore per day." msgstr "" msgid "" "You've found a Goblin weapon smithy making weapons for use against humans. " "With a tremendous yell you and your army descend upon their camp and drive " "them away. A search finds a magic ore cart that never runs out of iron." msgstr "" msgid "Endless Pouch of Crystal" msgstr "" msgid "The %{name} provides %{count} unit of crystal per day." msgstr "" msgid "" "Taking shelter from a storm in a small cave, you notice a small patch of " "crystal in one corner. Curious, you break a piece off and notice that the " "original crystal grows the lost piece back. You decide to stuff the entire " "patch into a pouch and take it with you." msgstr "" msgid "Spiked Helm" msgstr "" msgid "" "Your army is ambushed by a small tribe of wild (and none too bright) Orcs. " "You fend them off easily and the survivors flee in all directions. One of " "the Orcs was wearing a polished spiked helm. Figuring it will make a good " "souvenir, you take it." msgstr "" msgid "Spiked Shield" msgstr "" msgid "" "You come upon a bridge spanning a dry gully. Before you can cross, a Troll " "steps out from under the bridge and demands payment before it will permit " "you to pass. You refuse, and the Troll charges, forcing you to slay it. You " "take its spiked shield as a trophy." msgstr "" msgid "White Pearl" msgstr "" msgid "" "A walk across a dry saltwater lake bed yields an unlikely prize: A white " "pearl amidst shattered shells and debris." msgstr "" msgid "Black Pearl" msgstr "" msgid "" "Rumors of a Griffin of unusual size preying upon the countryside lead you to " "its cave lair. A quick, brutal fight dispatches the beast, and a search of " "its foul nest turns up a huge black pearl." msgstr "" msgid "Magic Book" msgstr "" #, fuzzy msgid "The %{name} enables the hero to cast spells." msgstr "%{name} kouzlu odolali!" msgid "Dummy 1" msgstr "" msgid "The reserved artifact." msgstr "" msgid "Dummy 2" msgstr "" msgid "Dummy 3" msgstr "" msgid "Dummy 4" msgstr "" msgid "Spell Scroll" msgstr "" msgid "" "This %{name} gives the hero the ability to cast the %{spell} spell if the " "hero has a Magic Book." msgstr "" msgid "" "You find an elaborate container which houses an old vellum scroll. The runes " "on the container are very old, and the artistry with which it was put " "together is stunning. As you pull the scroll out, you feel imbued with " "magical power." msgstr "" msgid "Arm of the Martyr" msgstr "" #, fuzzy msgid "" "The %{name} increases the hero's spell power by %{count} but adds the undead " "morale penalty." msgstr "Socha zvyÅ¡uje mÄ›stské příjmy o %{count} zlata za den." msgid "" "One of the less intelligent members of your party picks up an arm off of the " "ground. Despite its missing a body, it is still moving. Your troops find the " "dismembered arm repulsive, but you cannot bring yourself to drop it: it " "seems to hold some sort of magical power that influences your decision " "making." msgstr "" msgid "Breastplate of Anduran" msgstr "" #, fuzzy msgid "The %{name} increases the hero's defense by %{count}." msgstr "%{name} zvýší tvou morálku o %{count}." msgid "" "You come upon a sign. It reads: \"Here lies the body of Anduran. Bow and " "swear fealty, and you shall be rewarded.\" You decide to do as it says. As " "you stand up, you feel a coldness against your skin. Looking down, you find " "that you are suddenly wearing a gleaming, ornate breastplate." msgstr "" msgid "Broach of Shielding" msgstr "" msgid "" "The %{name} provides %{count} percent protection from Armageddon and " "Elemental Storm, but decreases spell power by 2." msgstr "" msgid "" "A kindly Sorceress thinks that your army's defenses could use a magical " "boost. She offers to enchant the Broach that you wear on your cloak, and you " "accept." msgstr "" msgid "Battle Garb of Anduran" msgstr "" msgid "" "The %{name} combines the powers of the three Anduran artifacts. It provides " "maximum luck and morale for the hero's troops and gives the hero the Town " "Portal spell." msgstr "" msgid "" "Out of pity for a poor peasant, you purchase a chest of old junk they are " "hawking for too much gold. Later, as you search through it, you find it " "contains the 3 pieces of the legendary battle garb of Anduran!" msgstr "" msgid "Crystal Ball" msgstr "" msgid "" "The %{name} lets the hero get more specific information about monsters, " "enemy heroes, and castles nearby the hero." msgstr "" msgid "" "You come upon a caravan of gypsies who are feasting and fortifying their " "bodies with mead. They call you forward and say \"If you prove that you can " "dance the Rama-Buta, we will reward you.\" You don't know it, but try " "anyway. They laugh hysterically, but admire your bravery, giving you a " "Crystal Ball." msgstr "" msgid "Heart of Fire" msgstr "" msgid "" "The %{name} provides %{count} percent protection from fire, but doubles the " "damage taken from cold." msgstr "" msgid "" "You enter a recently burned glade and come upon a Fire Elemental sitting " "atop a rock. It looks up, its flaming face contorted in a look of severe " "pain. It then tosses a glowing object at you. You put up your hands to block " "it, but it passes right through them and sears itself into your chest." msgstr "" msgid "Heart of Ice" msgstr "" msgid "" "The %{name} provides %{count} percent protection from cold, but doubles the " "damage taken from fire." msgstr "" msgid "" "Suddenly, a biting coldness engulfs your body. You seize up, falling from " "your horse. The pain subsides, but you still feel as if your chest is " "frozen. As you pick yourself up off of the ground, you hear hearty laughter. " "You turn around just in time to see a Frost Giant run off into the woods and " "disappear." msgstr "" msgid "Helmet of Anduran" msgstr "" msgid "" "You spy a gleaming object poking up out of the ground. You send a member of " "your party over to investigate. He comes back with a golden helmet in his " "hands. You realize that it must be the helmet of the legendary Anduran, the " "only man who was known to wear solid gold armor." msgstr "" msgid "Holy Hammer" msgstr "" msgid "" "You come upon a battle where a Paladin has been mortally wounded by a group " "of Zombies. He asks you to take his hammer and finish what he started. As " "you pick it up, it begins to hum, and then everything becomes a blur. The " "Zombies lie dead, the hammer dripping with blood. You strap it to your belt." msgstr "" msgid "Legendary Scepter" msgstr "" msgid "The %{name} adds %{count} points to all attributes." msgstr "" msgid "" "Upon cresting a small hill, you come upon a ridiculous looking sight. A " "Sprite is attempting to carry a Scepter that is almost as big as it is. " "Trying not to laugh, you ask, \"Need help?\" The Sprite glares at you and " "answers: \"You think this is funny? Fine. You can carry it. I much prefer " "flying anyway.\"" msgstr "" msgid "" "An old seaman tells you a tale of an enchanted masthead that he used in his " "youth to rally his crew during times of trouble. He then hands you a faded " "map that shows where he hid it. After much exploring, you find it stashed " "underneath a nearby dock." msgstr "" msgid "Masthead" msgstr "" #, fuzzy msgid "" "The %{name} boosts the hero's troops' luck and morale by %{count} each in " "sea combat." msgstr "%{skill} zvyÅ¡uje morálku u boj. jednotek hrdiny o %{count}." msgid "Sphere of Negation" msgstr "Sféra negace" msgid "The %{name} disables all spell casting, for both sides, in combat." msgstr "" msgid "" "You stop to help a Peasant catch a runaway mare. To show his gratitude, he " "hands you a tiny sphere. As soon as you grasp it, you feel the magical " "energy drain from your limbs..." msgstr "" msgid "Staff of Wizardry" msgstr "" #, fuzzy msgid "The %{name} boosts the hero's spell power by %{count}." msgstr "%{name} zvýší tvou morálku o %{count}." msgid "" "While out scaring up game, your troops find a mysterious staff levitating " "about three feet off of the ground. They hand it to you, and you notice an " "inscription. It reads: \"Brains best brawn and magic beats might. Heed my " "words, and you'll win every fight.\"" msgstr "" msgid "Sword Breaker" msgstr "" #, fuzzy msgid "The %{name} increases the hero's defense by %{count} and attack by 1." msgstr "Socha zvyÅ¡uje mÄ›stské příjmy o %{count} zlata za den." msgid "" "A former Captain of the Guard admires your quest and gives you the enchanted " "Sword Breaker that he relied on during his tour of duty." msgstr "" msgid "Sword of Anduran" msgstr "Anduranův meÄ" msgid "" "A Troll stops you and says: \"Pay me 5,000 gold, or the Sword of Anduran " "will slay you where you stand.\" You refuse. The troll grabs the sword " "hanging from its belt, screams in pain, and runs away. Picking up the fabled " "sword, you give thanks that half-witted Trolls tend to grab the wrong end of " "sharp objects." msgstr "" msgid "Spade of Necromancy" msgstr "" msgid "The %{name} gives the hero increased necromancy skill." msgstr "" msgid "" "A dirty shovel has been thrust into a dirt mound nearby. Upon investigation, " "you discover it to be the enchanted shovel of the Gravediggers, long thought " "lost by mortals." msgstr "" msgid "spellBonus|selected by user" msgstr "" msgid "Wood" msgstr "DÅ™evo" msgid "Mercury" msgstr "RtuÅ¥" msgid "Ore" msgstr "Ruda" msgid "Sulfur" msgstr "Síra" msgid "Crystal" msgstr "Krystal" msgid "Gems" msgstr "Drahokamy" msgid "Gold" msgstr "Zlato" msgid "" "There are seven resources in Heroes 2, used to build and improves castles, " "purchase troops and recruit heroes. Gold is the most common, required for " "virtually everything. Wood and ore are used for most buildings. Gems, " "Mercury, Sulfur and Crystal are rare magical resources used for the most " "powerful creatures and buildings." msgstr "" "Ve hÅ™e Heroes 2 je 7 zdrojů, které slouží ke stavbÄ› a vylepÅ¡ování hradů, " "nákupu vojska a náboru hrdinů. NejběžnÄ›jší je zlato, které je potÅ™eba " "prakticky ke vÅ¡emu. DÅ™evo a ruda se používají pro vÄ›tÅ¡inu budov. Drahokamy, " "rtuÅ¥, síra a krystal jsou vzácné magické suroviny, které se používají pro " "nejmocnÄ›jší stvůry a budovy." msgid "" "Causes a giant fireball to strike the selected area, damaging all nearby " "creatures." msgstr "" "Způsobí, že vybranou oblast zasáhne obří ohnivá koule, která poraní vÅ¡echny " "bytosti poblíž." msgid "Fireball" msgstr "Ohnivá koule" msgid "Fireblast" msgstr "Ohnivý výbuch" msgid "" "An improved version of fireball, fireblast affects two hexes around the " "center point of the spell, rather than one." msgstr "" "VylepÅ¡ená verze ohnivé koule, výbuch působí na dva Å¡estúhelníky kolem stÅ™edu " "kouzla místo jednoho." msgid "Causes a bolt of electrical energy to strike the selected creature." msgstr "Způsobí zasažení vybrané stvůry bleskem elektrické energie." msgid "Lightning Bolt" msgstr "Blesk" msgid "Chain Lightning" msgstr "Kulový blesk" msgid "" "Causes a bolt of electrical energy to strike a selected creature, then " "strike the nearest creature with half damage, then strike the NEXT nearest " "creature with half again damage, and so on, until it becomes too weak to be " "harmful. Warning: This spell can hit your own creatures!" msgstr "" "Způsobí zásah vybrané stvůry bleskem elektrické energie ale následnÄ› zasáhne " "nejbližší stvůru s poloviÄním poÅ¡kozením a poté zasáhne DALÅ Ã nejbližší " "stvůru s opÄ›t poloviÄním poÅ¡kozením a tak dále, dokud není příliÅ¡ slabý na " "to, aby byl Å¡kodlivý. Pozor: Kouzlo může zasáhnout i Tvou stranu!" msgid "Teleport" msgstr "" msgid "" "Teleports the creature you select to any open position on the battlefield." msgstr "Teleportuje vybranou stvůru na libovolné volné místo na bojiÅ¡ti." msgid "Cure" msgstr "Lék" msgid "" "Removes all negative spells cast upon one of your units, and restores up to " "%{count} HP per level of spell power." msgstr "" "Odstraní vÅ¡echna negativní kouzla seslaná na jednu z Tvých jednotek a obnoví " "až %{count} životů dle dosažené úrovnÄ› síly kouzel." msgid "Mass Cure" msgstr "Hrom. lék" msgid "" "Removes all negative spells cast upon your forces, and restores up to " "%{count} HP per level of spell power, per creature." msgstr "" "Odstraní vÅ¡echna negativní kouzla seslaná na Tvé jednotky a obnoví až " "%{count} životů dle dosažené úrovnÄ› síly kouzel a jednotku." msgid "Resurrect" msgstr "Oživení" msgid "Resurrects creatures from a damaged or dead unit until end of combat." msgstr "Vzkřísí stvůry z poranÄ›ných nebo mrtvých jednotek až do konce bitvy." msgid "Resurrect True" msgstr "Trvalé oživení" msgid "Resurrects creatures from a damaged or dead unit permanently." msgstr "Trvale vzkřísí stvůry z poranÄ›ných nebo mrtvých jednotek." msgid "Haste" msgstr "SpÄ›ch" msgid "Increases the speed of any creature by %{count}." msgstr "ZvyÅ¡uje rychlost jakékoli stvůry o %{count}." msgid "Increases the speed of all of your creatures by %{count}." msgstr "ZvyÅ¡uje rychlost vÅ¡ech Tvých stvůr o %{count}." msgid "Mass Haste" msgstr "Hrom. spÄ›ch" msgid "Slows target to half movement rate." msgstr "Zpomalí cílovou jednotku o polovinu." msgid "spell|Slow" msgstr "Zpomalení" msgid "Mass Slow" msgstr "Hrom. zpomalení" msgid "Slows all enemies to half movement rate." msgstr "Zpomalí vÅ¡echny nepřátelské jednotky o polovinu." msgid "Clouds the affected creatures' eyes, preventing them from moving." msgstr "Zakryje oÄi zasažených tvorů a zabrání jim tím v pohybu." msgid "spell|Blind" msgstr "Slepota" msgid "Bless" msgstr "Požehnání" msgid "Causes the selected creatures to inflict maximum damage." msgstr "Způsobí, že vybraná jednotka způsobí maximální poÅ¡kození." msgid "Causes all of your units to inflict maximum damage." msgstr "Způsobí, že vÅ¡echny Tvé jednotky budou způsobovat maximální poÅ¡kození." msgid "Mass Bless" msgstr "Hrom. požehnání" msgid "Magically increases the defense skill of the selected creatures." msgstr "Magicky zvýší obrannou sílu vybraných stvůr." msgid "Stoneskin" msgstr "Kamenná kůže" msgid "" "Increases the defense skill of the targeted creatures. This is an improved " "version of Stoneskin." msgstr "" "ZvyÅ¡uje obrannou sílu u cílových stvůr. Jedná se o vylepÅ¡enou verzi kouzla " "Kamenná kůže." msgid "Steelskin" msgstr "Kůže z oceli" msgid "Causes the selected creatures to inflict minimum damage." msgstr "Způsobí, že vybrané stvůry budou působit minimální poÅ¡kození." msgid "Curse" msgstr "Prokletí" msgid "Causes all enemy troops to inflict minimum damage." msgstr "VÅ¡echny nepřátelské stvůry budou působit minimální poÅ¡kození." msgid "Mass Curse" msgstr "Hrom. prokletí" msgid "Damages all undead in the battle." msgstr "PoÅ¡kodí vÅ¡echny nemrtvé v bitvÄ›." msgid "Holy Word" msgstr "Slovo Svaté" msgid "" "Damages all undead in the battle. This is an improved version of Holy Word." msgstr "" "PoÅ¡kodí vÅ¡echny nemrtvé v bitvÄ›. Jedná se o vylepÅ¡enou verzi kouzla Slovo " "Svaté." msgid "Holy Shout" msgstr "Svatý VýkÅ™ik" msgid "Anti-Magic" msgstr "Antimagie" msgid "Prevents harmful magic against the selected creatures." msgstr "Znemožňuje použití Å¡kodlivé magie proti vybraným stvůrám." msgid "Dispel Magic" msgstr "ZruÅ¡ kouzla" msgid "Removes all magic spells from a single target." msgstr "Odstraní vÅ¡echna kouzla z cílové jednotky." msgid "Mass Dispel" msgstr "Hrom. zruÅ¡ení" msgid "Removes all magic spells from all creatures." msgstr "Odstraní vÅ¡echna kouzla ze vÅ¡ech jednotek." msgid "Causes a magic arrow to strike the selected target." msgstr "Způsobí zasažení vybraného cíle magickým šípem." msgid "Magic Arrow" msgstr "Magický šíp" msgid "Berserker" msgstr "Å ilhavost" msgid "Causes a creature to attack its nearest neighbor." msgstr "Způsobí, že stvůra zaútoÄí na nejbližší jednotku." msgid "Armageddon" msgstr "Armagedon" msgid "" "Holy terror strikes the battlefield, causing severe damage to all creatures." msgstr "Svatá hrůza zasáhne bojiÅ¡tÄ› a způsobí vážné poÅ¡kození vÅ¡em jednotkám." msgid "Elemental Storm" msgstr "BouÅ™e Elementů" msgid "Magical elements pour down on the battlefield, damaging all creatures." msgstr "Na bojiÅ¡tÄ› se snesou magické živly a poÅ¡kodí vÅ¡echny jednotky." msgid "" "A rain of rocks strikes an area of the battlefield, damaging all nearby " "creatures." msgstr "Déšť kamenů zasáhne oblast bojiÅ¡tÄ› a poÅ¡kodí vÅ¡echny stvůry poblíž." msgid "Meteor Shower" msgstr "Spŕška meteorů" msgid "Paralyze" msgstr "Paralýza" msgid "The targeted creatures are paralyzed, unable to move or retaliate." msgstr "Cílová jednotka je ochromena a nemůže se hýbat ani bránit." msgid "Hypnotize" msgstr "Hypnóza" msgid "" "Brings a single enemy unit under your control if its hits are less than " "%{count} times the caster's spell power." msgstr "" "PÅ™evede jednu nepřátelskou jednotku pod Tvou kontrolu, pokud jsou její " "životy menší než %{count} násobek síly kouzel." msgid "Cold Ray" msgstr "Chladný paprsek" msgid "Drains body heat from a single enemy unit." msgstr "OdÄerpá tÄ›lesné teplo z jedné nepřátelské jednotky." msgid "Cold Ring" msgstr "Chladný kruh" msgid "" "Drains body heat from all units surrounding the center point, but not " "including the center point." msgstr "" "OdÄerpá tÄ›lesné teplo ze vÅ¡ech nepřátelských jednotek kolem stÅ™edu, stÅ™ed " "kruhu vyjímaje." msgid "Disrupting Ray" msgstr "Paprsek rozruÅ¡ení" msgid "Reduces the defense rating of an enemy unit by three." msgstr "Sníží obranyschopnost nepřátelské jednotky o tÅ™i." msgid "Damages all living (non-undead) units in the battle." msgstr "PoÅ¡kozuje vÅ¡echny živé (nemrtvé) jednotky v bitvÄ›." msgid "Death Ripple" msgstr "Smrtící vlnka" msgid "Death Wave" msgstr "Smrtící vlna" msgid "" "Damages all living (non-undead) units in the battle. This spell is an " "improved version of Death Ripple." msgstr "" "PoÅ¡kodí vÅ¡echny živé (nemrtvé) jednotky v bitvÄ›. Toto kouzlo je vylepÅ¡ená " "verze kouzla Smrtící vlnka." msgid "Dragon Slayer" msgstr "Drakobijec" msgid "Greatly increases a unit's attack skill vs. Dragons." msgstr "VýraznÄ› zvyÅ¡uje útoÄnou sílu jednotky vůÄi drakům." msgid "Blood Lust" msgstr "Krvežíznivost" msgid "Increases a unit's attack skill." msgstr "ZvyÅ¡uje útoÄnou sílu jednotky." msgid "Animate Dead" msgstr "Vzkříšení mrtvol" msgid "Resurrects creatures from a damaged or dead undead unit permanently." msgstr "Trvale vzkřísí bytosti z poÅ¡kozené nebo mrtvé nemrtvé jednotky." msgid "Mirror Image" msgstr "Dvojník" msgid "" "Creates an illusionary unit that duplicates one of your existing units. This " "illusionary unit does the same damages as the original, but will vanish if " "it takes any damage." msgstr "" "Vytvoří iluzorní jednotku, která je duplikátem jedné z Tvých stávajících " "jednotek. Tato iluzorní jednotka způsobuje stejné poÅ¡kození jako původní " "jednotka, ale zmizí pokud utrpí jakékoliv poÅ¡kození." msgid "Shield" msgstr "Å tít" #, fuzzy msgid "" "Halves damage received from ranged attacks for a single unit. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Snižuje o polovinu poÅ¡kození způsobené útoky na dálku u jedné jednotky." msgid "Mass Shield" msgstr "Hrom. Å¡tít" #, fuzzy msgid "" "Halves damage received from ranged attacks for all of your units. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Snižuje o polovinu poÅ¡kození způsobené útoky na dálku pro vÅ¡echny Tvé " "jednotky." msgid "Summon Earth Elemental" msgstr "PÅ™ivolej Půdní Elementály" msgid "Summons Earth Elementals to fight for your army." msgstr "Do Tvé armády povolá Půdní Elementály." msgid "Summon Air Elemental" msgstr "PÅ™ivolej VzduÅ¡né Elementály" msgid "Summons Air Elementals to fight for your army." msgstr "Do Tvé armády povolá VzduÅ¡né Elementály." msgid "Summon Fire Elemental" msgstr "PÅ™ivolej Ohňové Elementály" msgid "Summons Fire Elementals to fight for your army." msgstr "Do Tvé armády povolá Ohňové Elementály." msgid "Summon Water Elemental" msgstr "PÅ™ivolej Vodní Elementály" msgid "Summons Water Elementals to fight for your army." msgstr "Do Tvé armády povolá Vodní Elementály." msgid "Damages castle walls." msgstr "PoÅ¡kozuje obranné hradby." msgid "Earthquake" msgstr "ZemÄ›tÅ™esení" msgid "Causes all mines across the land to become visible." msgstr "Způsobí zviditelnÄ›ní vÅ¡ech dolů v zemi." msgid "View Mines" msgstr "Zobraz doly" msgid "Causes all resources across the land to become visible." msgstr "Způsobí zviditelnÄ›ní vÅ¡ech zdrojů v zemi." msgid "View Resources" msgstr "Zobraz zdroje" msgid "Causes all artifacts across the land to become visible." msgstr "Způsobí zviditelnÄ›ní vÅ¡ech artefaktů v zemi." msgid "View Artifacts" msgstr "Zobraz artefakty" msgid "Causes all towns and castles across the land to become visible." msgstr "Způsobí zviditelnÄ›ní vÅ¡ech mÅ¡t a hradů v zemi." msgid "View Towns" msgstr "Zobraz mÄ›sta" msgid "Causes all Heroes across the land to become visible." msgstr "Způsobí zviditelnÄ›ní vÅ¡ech hrdinů v zemi." msgid "View Heroes" msgstr "Zobraz hrdiny" msgid "Causes the entire land to become visible." msgstr "Způsobí zviditelnÄ›ní celé zemÄ›." msgid "View All" msgstr "Zobraz vÅ¡e" msgid "Allows the caster to view detailed information on enemy Heroes." msgstr "Umožňuje zobrazit podrobné informace o nepřátelských hrdinech." msgid "Summon Boat" msgstr "PÅ™ivolej loÄ" msgid "" "Summons the nearest unoccupied, friendly boat to an adjacent shore location. " "A friendly boat is one which you just built or were the most recent player " "to occupy." msgstr "" "PÅ™ivolá nejbližší neobsazenou přátelskou loÄ na pÅ™ilehlé místo na pobÅ™eží. " "Přátelská loÄ je taková, kterou jsi právÄ› postavil nebo jsi ji použil jako " "poslední." msgid "Allows the caster to magically transport to a nearby location." msgstr "Umožní hrdinovi se magicky pÅ™enést na místo poblíž." msgid "Dimension Door" msgstr "Nebeská brána" msgid "Returns the caster to any town or castle currently owned." msgstr "Vrátí hrdinu do jakéhokoli mÄ›sta nebo hradu, který vlastníš." msgid "Town Gate" msgstr "MÄ›stská brána" msgid "" "Returns the hero to the town or castle of choice, provided it is controlled " "by you." msgstr "" "Vrátí hrdinu do vybraného mÄ›sta nebo hradu, pokud je pod Tvojí kontrolou." msgid "Visions" msgstr "Vize" msgid "" "Visions predicts the likely outcome of an encounter with a neutral army camp." msgstr "" "Vize pÅ™edpovídají pravdÄ›podobný výsledek stÅ™etu s táborem neutrální armády." msgid "Haunt" msgstr "StraÅ¡idla" msgid "" "Haunts a mine you control with Ghosts. This mine stops producing resources. " "(If I can't keep it, nobody will!)" msgstr "" "V dole, který ovládáte zaÄnou straÅ¡it duchové. Tento důl pak pÅ™estane " "produkovat zdroje. (Pokud ho nebudu mít já, tak nikdo jiný!)" msgid "Set Earth Guardian" msgstr "Zanech půdního strážce" msgid "Sets Earth Elementals to guard a mine against enemy armies." msgstr "" "Zanechá u dolu Půdní Elementály, aby ho bránili pÅ™ed nepřátelskými vojsky." msgid "Set Air Guardian" msgstr "Zanech vzduÅ¡ného strážce" msgid "Sets Air Elementals to guard a mine against enemy armies." msgstr "" "Zanechá u dolu VzduÅ¡ní Elementály, aby ho bránili pÅ™ed nepřátelskými vojsky." msgid "Set Fire Guardian" msgstr "Zanech ohňového strážce" msgid "Sets Fire Elementals to guard a mine against enemy armies." msgstr "" "Zanechá u dolu Ohňové Elementály, aby ho bránili pÅ™ed nepřátelskými vojsky." msgid "Set Water Guardian" msgstr "Zanech vodního strážce" msgid "Sets Water Elementals to guard a mine against enemy armies." msgstr "" "Zanechá u dolu Vodní Elementály, aby ho bránili pÅ™ed nepřátelskými vojsky." #, fuzzy msgid "Random Spell" msgstr "Náhodné mÄ›sto" msgid "Randomly selected spell of any level." msgstr "" #, fuzzy msgid "Random 1st Level Spell" msgstr "Náhodný hrad" msgid "Randomly selected 1st level spell." msgstr "" msgid "Random 2nd Level Spell" msgstr "" msgid "Randomly selected 2nd level spell." msgstr "" msgid "Random 3rd Level Spell" msgstr "" msgid "Randomly selected 3rd level spell." msgstr "" msgid "Random 4th Level Spell" msgstr "" msgid "Randomly selected 4th level spell." msgstr "" msgid "Random 5th Level Spell" msgstr "" msgid "Randomly selected 5th level spell." msgstr "" msgid "Petrification" msgstr "ZkamenÄ›ní" msgid "" "Turns the affected creature into stone. A petrified creature receives half " "damage from a direct attack." msgstr "" "PromÄ›ní zasaženou stvůru v kámen. ZkamenÄ›lý tvor pak utrpí pÅ™i přímém útoku " "pouze poloviÄní poÅ¡kození." msgid "You have no Magic Book, so you cannot cast a spell." msgstr "Nemáš Knihu kouzel, bez ní nemůžeÅ¡ kouzlit." msgid "No spell to cast." msgstr "Žádné kouzlo k seslání." msgid "Your hero has %{point} spell points remaining." msgstr "Tvému hrdinovi zbývá %{point} bodů kouzla." msgid "View Adventure Spells" msgstr "Zobraz kouzla svÄ›ta" msgid "View Combat Spells" msgstr "Zobraz bojové kouzla" msgid "View previous page" msgstr "Zobraz pÅ™edchozí stranu" msgid "View next page" msgstr "Zobraz další stranu" msgid "Close Spellbook" msgstr "Zavřít knihu" msgid "View %{spell}" msgstr "Zobraz %{spell}" msgid "This spell does %{damage} points of damage." msgstr "Toto kouzlo způsobí %{damage} Å¡kod." msgid "" "This spell summons\n" "%{count} %{monster}." msgstr "" "Toto kouzlo pÅ™ivolá\n" "%{count} %{monster}." msgid "This spell restores %{hp} HP." msgstr "" msgid "This spell summons %{count} %{monster} to guard the mine." msgstr "" msgid "The nearest town is %{town}." msgstr "" msgid "This town is occupied by your hero %{hero}." msgstr "" msgid "" "This spell controls up to\n" "%{hp} HP." msgstr "" msgid "The ultimate artifact is really the %{name}." msgstr "Ultimátním artefaktem je skuteÄnÄ› %{name}." msgid "The ultimate artifact may be found in the %{name} regions of the world." msgstr "Ultimátní artefakt je možné najít v %{name} oblasti svÄ›ta." msgid "north-west" msgstr "severozápadní" msgid "north" msgstr "severní" msgid "north-east" msgstr "severovýchodní" msgid "west" msgstr "západní" msgid "center" msgstr "centrální" msgid "east" msgstr "východní" msgid "south-west" msgstr "jihozápadní" msgid "south" msgstr "jižní" msgid "south-east" msgstr "jihovýchodní" msgid "The truth is out there." msgstr "Pravda je nÄ›kde venku." msgid "The dark side is stronger." msgstr "Temná strana je silnÄ›jší." msgid "The end of the world is near." msgstr "Konec svÄ›ta je již blízko." msgid "The bones of Lord Slayer are buried in the foundation of the arena." msgstr "Kosti pána PÅ™emožitele jsou pohÅ™beny v základech bitevní arény." msgid "A Black Dragon will take out a Titan any day of the week." msgstr "ÄŒerný drak sejme Titána kdykoliv a kdekoliv." msgid "He told her: Yada yada yada... and then she said: Blah, blah, blah..." msgstr "On jí Å™ekl: Yada yada yada... ona pak Å™ekla: Bla, bla, bla..." msgid "An unknown force is being resurrected..." msgstr "Neznámá síla se probouzí..." msgid "" "Check the newest version of the game at\n" "https://github.com/ihhub/\n" "fheroes2/releases" msgstr "" "NejnovÄ›jší verzi hry nalezneÅ¡ na\n" "https://github.com/ihhub/\n" "fheroes2/releases" #, fuzzy #~ msgid "Castles" #~ msgstr "Hrad" #, fuzzy #~ msgid "Ocean object" #~ msgstr "Pustina" #, fuzzy #~ msgid "Desert Objects" #~ msgstr "PouÅ¡tní stan" #~ msgid "vs" #~ msgstr "versus" fheroes2-1.0.12+dfsg/files/lang/de.po000066400000000000000000011512541456075706000172250ustar00rootroot00000000000000# German translation of fheroes2 # Copyright (C) 2022 fheroes2 team # This file is distributed under the same license as the fheroes2 package. # msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-03 03:27+0000\n" "PO-Revision-Date: 2023-12-22 00:32+0100\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.2.2\n" msgid "" "BATTLE\n" "ONLY" msgstr "" "NUR\n" "KAMPF" msgid "" "NEW\n" "GAME" msgstr "" "NEUES\n" "SPIEL" msgid "" "SAVE\n" "GAME" msgstr "" "SPIEL\n" "SPEICH." msgid "" "LOAD\n" "GAME" msgstr "" "SPIEL\n" "LADEN" msgid "INFO" msgstr "INFO" msgid "QUIT" msgstr "BEEND." msgid "CANCEL" msgstr "ABBRUCH" msgid "OKAY" msgstr "OKAY" msgid "smallerButton|OKAY" msgstr "OKAY" msgid "ACCEPT" msgstr "AKZEPT." msgid "DECLINE" msgstr "ABLEHNEN" msgid "LEARN" msgstr "LERNEN" msgid "TRADE" msgstr "HANDELN" msgid "YES" msgstr "JA" msgid "NO" msgstr "NEIN" msgid "EXIT" msgstr "ENDE" #, fuzzy msgid "smallerButton|EXIT" msgstr "OKAY" msgid "DISMISS" msgstr "ABLEHNEN" msgid "UPGRADE" msgstr "AUFRÜST." msgid "RESTART" msgstr "NEUSTART" msgid "HEROES" msgstr "HELDEN" msgid "" "TOWNS/\n" "CASTLES" msgstr "" "STÄDTE/\n" "BURGEN" msgid "S" msgstr "S" msgid "M" msgstr "M" msgid "L" msgstr "L" msgid "X-L" msgstr "X-L" msgid "ALL" msgstr "ALLE" msgid "SELECT" msgstr "AUSW." msgid "" "STANDARD\n" "GAME" msgstr "" "STANDARD\n" "SPIEL" msgid "" "CAMPAIGN\n" "GAME" msgstr "" "FELDZUG\n" "SPIEL" msgid "" "MULTI-\n" "PLAYER\n" "GAME" msgstr "" "MEHR-\n" "SPIELER\n" "SPIEL" msgid "CONFIG" msgstr "KONFIG." msgid "" "ORIGINAL\n" "CAMPAIGN" msgstr "" "ORIGINAL\n" "FELDZUG" msgid "" "EXPANSION\n" "CAMPAIGN" msgstr "" "ZUSATZ\n" "FELDZUG" msgid "HOT SEAT" msgstr "" "HEISSER\n" "STUHL" msgid "2 PLAYERS" msgstr "2 SPIELER" msgid "3 PLAYERS" msgstr "3 SPIELER" msgid "4 PLAYERS" msgstr "4 SPIELER" msgid "5 PLAYERS" msgstr "5 SPIELER" msgid "6 PLAYERS" msgstr "6 SPIELER" msgid "GIFT" msgstr "SPEND." msgid "MAX" msgstr "MAX" msgid "DIFFICULTY" msgstr "SCHWIERIGKEIT" msgid "VIEW INTRO" msgstr "INTRO ANZEIGEN" msgid "MIN" msgstr "MIN" msgid "RESET" msgstr "" #, fuzzy msgid "START" msgstr "NEUSTART" #, fuzzy msgid "CASTLE" msgstr "" "STÄDTE/\n" "BURGEN" msgid "TOWN" msgstr "" msgid "" "D\n" "I\n" "S\n" "M\n" "I\n" "S\n" "S" msgstr "" msgid "" "E\n" "X\n" "I\n" "T" msgstr "" msgid "" "C\n" "A\n" "M\n" "P\n" "A\n" "I\n" "G\n" "N" msgstr "" msgid "" "S\n" "T\n" "A\n" "N\n" "D\n" "A\n" "R\n" "D" msgstr "" msgid "Warrior" msgstr "Krieger" msgid "Builder" msgstr "Ersteller" msgid "Explorer" msgstr "Forscher" msgid "None" msgstr "Niemand" msgid "" "A few\n" "%{monster}" msgstr "" "Wenige\n" "%{monster}" msgid "" "Several\n" "%{monster}" msgstr "" "Einige\n" "%{monster}" msgid "" "A pack of\n" "%{monster}" msgstr "" "Mehrere\n" "%{monster}" msgid "" "Lots of\n" "%{monster}" msgstr "" "Viele\n" "%{monster}" msgid "" "A horde of\n" "%{monster}" msgstr "" "Eine Menge\n" "%{monster}" msgid "" "A throng of\n" "%{monster}" msgstr "" "Eine Horde\n" "%{monster}" msgid "" "A swarm of\n" "%{monster}" msgstr "" "Massen\n" "%{monster}" msgid "" "Zounds...\n" "%{monster}" msgstr "" "Ein Schwarm\n" "%{monster}" msgid "" "A legion of\n" "%{monster}" msgstr "" "Eine Legion\n" "%{monster}" msgid "army|Few" msgstr "Wenige" msgid "army|Several" msgstr "Einige" msgid "army|Pack" msgstr "Mehrere" msgid "army|Lots" msgstr "Viele" msgid "army|Horde" msgstr "Eine Menge" msgid "army|Throng" msgstr "Horde" msgid "army|Swarm" msgstr "Massen" msgid "army|Zounds" msgstr "Schwarm" msgid "army|Legion" msgstr "Legion" msgid "All %{race} troops +1" msgstr "Alle %{race}-Truppen +1" msgid "Multiple" msgstr "Verschied" msgid "Troops of %{count} alignments -%{penalty}" msgstr "Truppen aus %{count} Gattungen -%{penalty}" #, fuzzy msgid "Some undead in army -1" msgstr "Einige Untote in der Gruppe -1" msgid "View %{name}" msgstr "%{name} ansehen" msgid "Move the %{name} " msgstr "%{name} umstellen " msgid "Move or right click to redistribute %{name}" msgstr "%{name} verteilen: ziehen oder mit der rechten Maustaste tippen" msgid "Combine %{name} armies" msgstr "%{name} Armeen kombinieren" msgid "Exchange %{name2} with %{name}" msgstr "Tauschen %{name2} mit %{name} aus" msgid "Select %{name}" msgstr "%{name} wählen" msgid "Cannot move last troop" msgstr "Letzte Truppe kann nicht umgestellt werden" msgid "Move the %{name}" msgstr "%{name} umstellen" msgid "Set Count" msgstr "Anzahl festlegen" msgid "%{name} destroys half the enemy troops!" msgid_plural "%{name} destroy half the enemy troops!" msgstr[0] "%{name} vernichtet die Hälfte der feindlichen Truppen!" msgstr[1] "%{name} vernichten die Hälfte der feindlichen Truppen!" msgid "%{name} has turned off the auto battle" msgstr "%{name} hat den automatischen Kampf ausgeschaltet" msgid "%{name} has turned on the auto battle" msgstr "%{name} hat den automatischen Kampf eingeschaltet" msgid "Spell failed!" msgstr "Zauberspruch fehlgeschlagen!" msgid "" "The Sphere of Negation artifact is in effect for this battle, disabling all " "combat spells." msgstr "" "Das Artefakt \"Sphäre der Aufhebung\" ist in diesem Kampf aktiv und macht " "alle Kampfzauber unwirksam." msgid "You have already cast a spell this round." msgstr "Ihr habt in dieser Runde bereits einen Zauberspruch ausgesprochen." #, fuzzy msgid "That spell will have no effect!" msgstr "Dieser Zauber wirkt sich auf niemanden aus!" msgid "You may only summon one type of elemental per combat." msgstr "Ihr könnt nur eine Art von Wesen pro Kampf beschwören." msgid "" "There is no open space adjacent to your hero where you can summon an " "Elemental to." msgstr "" "In der Nähe Eures Helden gibt es keinen freien Platz, um ein Wesen zu " "beschwören." msgid "" "The Moat reduces by -%{count} the defense skill of any unit and slows to " "half movement rate." msgstr "" "Der Burggraben reduziert die Verteidigungsfähigkeit jeder Einheit, die auf " "den tritt, um -%{count} und verlangsamt seine Bewegungsrate auf die Hälfte." msgid "Speed: %{speed}" msgstr "Geschw.: %{speed}" msgid "Speed" msgstr "Geschwindigkeit" msgid "Off" msgstr "Aus" msgid "On" msgstr "An" #, fuzzy msgid "Turn Order" msgstr "Reihen- folge" msgid "Auto Spell Casting" msgstr "Auto Zauber" msgid "Grid" msgstr "Gitter" msgid "Shadow Movement" msgstr "Schatten-bewegung" msgid "Shadow Cursor" msgstr "Schatten-Zeiger" msgid "Audio" msgstr "Audio" msgid "Settings" msgstr "Einstellungen" msgid "Configure" msgstr "Konfigurieren" msgid "Hot Keys" msgstr "Hotkeys" msgid "Damage Info" msgstr "Schadens-Information" msgid "Set the speed of combat actions and animations." msgstr "Geschwindigkeit von Kampfhandlungen und Animationen festlegen." msgid "Toggle to display the turn order during the battle." msgstr "Ansicht der Reihenfolge von Truppen ein- oder ausschalten." msgid "" "Toggle whether or not the computer will cast spells for you when auto combat " "is on. (Note: This does not affect spell casting for computer players in any " "way, nor does it affect quick combat.)" msgstr "" "Umschalten, ob bei aktiviertem Autokampf der Computer selbständig zaubert " "oder nicht. (Hinweis: Dies hat keinen Einfluss auf das Zaubern für " "Computerspieler und auch nicht auf den Schnellkampf)." msgid "" "Toggle the hex grid on or off. The hex grid always underlies movement, even " "if turned off. This switch only determines if the grid is visible." msgstr "" "Hex-Gitters ein-/ausblenden. Das Hex-Gitter bestimmt immer die Bewegung, " "auch wenn es ausgeblendet ist. Dieser Schalter bestimmt nur, ob es sichtbar " "ist." msgid "" "Toggle on or off shadows showing where your creatures can move and attack." msgstr "" "Schattierung ein-/ausblenden, die anzeigt, wo die Kreatur hinbewegt werden " "und angreifen kann." msgid "" "Toggle on or off a shadow showing the current hex location of the mouse " "cursor." msgstr "" "Schatten ein-/ausschalten, der die aktuelle Hex-Position des Mauszeigers " "anzeigt." msgid "Change the audio settings of the game." msgstr "Audioeinstellungen des Spiels ändern." msgid "Check and configure all the hot keys present in the game." msgstr "Alle im Spiel verwendeten Hotkeys überprüfen und konfigurieren." msgid "Toggle to display damage information during the battle." msgstr "Die Ansicht der Schadensinformation während des Kampfes umschalten." msgid "Exit this menu." msgstr "Dieses Menü verlassen." msgid "Okay" msgstr "Okay" msgid "The enemy has surrendered!" msgstr "Der Feind hat kapituliert!" msgid "Their cowardice costs them %{gold} gold." msgstr "" msgid "The enemy has fled!" msgstr "Der Feind ist geflohen!" msgid "A glorious victory!" msgstr "Ein glorreicher Sieg!" msgid "For valor in combat, %{name} receives %{exp} experience." msgstr "Für Tapferkeit im Kampf erhält %{name} %{exp} Erfahrung." msgid "The cowardly %{name} flees from battle." msgstr "Der feige %{name} flieht vor der Schlacht." msgid "%{name} surrenders to the enemy, and departs in shame." msgstr "%{name} kapituliert vor dem Feind und zieht beschämt von dannen." #, fuzzy msgid "Your forces suffer a bitter defeat, and %{name} abandons your cause." msgstr "" "Eure Truppe erleidet eine bittere Niederlage, und %{name} verlässt Euch." #, fuzzy msgid "Your forces suffer a bitter defeat." msgstr "Eure Truppe erleidet eine bittere Niederlage." msgid "Battlefield Casualties" msgstr "Opfer auf dem Schlachtfeld" msgid "Attacker" msgstr "Angreifer" msgid "Defender" msgstr "Verteidiger" #, fuzzy msgid "Click to leave the battle results." msgstr "Klicken um die ausgewählte Auflösung anzuwenden." #, fuzzy msgid "Click to restart the battle in manual mode." msgstr "Klicken, um zum Hauptmenü zurückzukehren." msgid "Restart" msgstr "Neustart" msgid "You have captured an enemy artifact!" msgstr "Ihr habt ein gegnerisches Artefakt erbeutet!" msgid "As you reach for the %{name}, it mysteriously disappears." msgstr "Als Ihr nach dem %{name} greift, verschwindet es auf mysteriöse Weise." msgid "As your enemy reaches for the %{name}, it mysteriously disappears." msgstr "" "Als Euer Gegner nach dem %{name} greift, verschwindet es auf mysteriöse " "Weise." msgid "Necromancy!" msgstr "Nekromantie!" msgid "" "Practicing the dark arts of necromancy, you are able to raise %{count} of " "the enemy's dead to return under your service as %{monster}." msgstr "" "Durch Ausübung der dunklen Künste der Nekromantie, seid Ihr in der Lage, " "%{count} der gegnerischen Toten auferstehen zu lassen, und als %{monster} in " "Eure Dienste zu stellen." msgid "%{name} the %{race}" msgstr "%{name} (%{race})" msgid "Captain of %{name}" msgstr "Hauptmann von %{name}" msgid "Attack" msgstr "Angriff" msgid "Defense" msgstr "Verteid." msgid "Spell Power" msgstr "Zauberkraft" msgid "Knowledge" msgstr "Wissen" msgid "Morale" msgstr "Moral" msgid "Luck" msgstr "Glück" msgid "Spell Points" msgstr "Zauberpunkte" msgid "Hero's Options" msgstr "Helden-Optionen" msgid "Cast Spell" msgstr "Zauber wirken" msgid "Retreat" msgstr "Zurückziehen" msgid "Surrender" msgstr "Aufgeben" msgid "Cancel" msgstr "Abbrechen" msgid "Hero Screen" msgstr "Heldenbildschirm" msgid "Captain's Options" msgstr "Möglichkeiten des Hauptmanns" msgid "" "Cast a magical spell. You may only cast one spell per combat round. The " "round is reset when every creature has had a turn." msgstr "" "Einen Zauberspruch aussprechen. Ihr könnt pro Kampfrunde nur einen " "Zauberspruch aussprechen. Die Runde ist beendet, wenn alle Kreaturen an der " "Reihe waren." msgid "" "Retreat your hero, abandoning your creatures. Your hero will be available " "for you to recruit again, however, the hero will have only a novice hero's " "forces." msgstr "" "Euren Helden zurückziehen und Eure Kreaturen aufgeben. Euer Held kann erneut " "rekrutiert werden, allerdings hat er dann den Status eines Anfängers." #, fuzzy msgid "" "Surrendering costs gold. However if you pay the ransom, the hero and all of " "his or her surviving creatures will be available to recruit again. The cost " "of surrender is half of the total cost of the non-temporary troops remaining " "in the army." msgstr "" "Kapitulieren kostet Gold. Wenn Ihr jedoch das Lösegeld bezahlt, könnt Ihr " "den Helden und alle überlebende Kreaturen wieder rekrutieren." msgid "Open Hero Screen to view full information about the hero." msgstr "" "Öffne den Heldenbildschirm, um alle Informationen über den Helden anzuzeigen." msgid "Return to the battle." msgstr "Zurück in die Schlacht." msgid "Not enough gold (%{gold})" msgstr "Nicht genug Gold (%{gold})" msgid "%{name} states:" msgstr "%{name} erklärt:" msgid "Captain of %{name} states:" msgstr "Hauptmann von %{name}:" msgid "" "\"I will accept your surrender and grant you and your troops safe passage " "for the price of %{price} gold.\"" msgstr "" "\"Ich akzeptiere Eure Kapitulation und gewähre Euch einen sicheren " "Truppenabzug für den Preis von %{price} Gold.\"" msgid "View %{monster} info" msgstr "%{monster}-Infos anzeigen" msgid "Fly %{monster} here" msgstr "%{monster} hierhin fliegen" msgid "Move %{monster} here" msgstr "%{monster} hierher bewegen" msgid "Shoot %{monster}" msgstr "Schieße auf %{monster}" msgid "(1 shot left)" msgid_plural "(%{count} shots left)" msgstr[0] "(1 Schuss übrig)" msgstr[1] "(%{count} Schüsse übrig)" msgid "Attack %{monster}" msgstr "%{monster} angreifen" msgid "Turn %{turn}" msgstr "Runde %{turn}" msgid "Teleport here" msgstr "Hierhin teleportieren" msgid "Invalid teleport destination" msgstr "Ungültiges Teleporter-Ziel" msgid "Cast %{spell} on %{monster}" msgstr "%{spell} auf %{monster} zaubern" msgid "Cast %{spell}" msgstr "Zaubern %{spell}" msgid "Select spell target" msgstr "Ziel des Zaubers auswählen" msgid "View Ballista info" msgstr "Ballista-Infos anzeigen" msgid "Ballista" msgstr "Ballista" msgid "Enable auto combat" msgstr "Autokampf einschalten" msgid "Allows the computer to fight out the battle for you." msgstr "Erlaubt dem Computer, den Kampf für Euch auszuführen." msgid "Auto Combat" msgstr "Autokampf" msgid "Customize system options" msgstr "System-Optionen anpassen" msgid "Allows you to customize the combat screen." msgstr "Ermöglicht es, den Kampfbildschirm anzupassen." msgid "System Options" msgstr "System-Optionen" msgid "Skip this unit" msgstr "Diesen Zug aussetzen" msgid "Skip" msgstr "Aussetzen" msgid "" "Skips the current creature. The current creature ends its turn and does not " "get to go again until the next round." msgstr "" "Aktive Kreatur überspringen. Die Einheit, die am Zug ist, setzt aus und darf " "erst in der nächsten Runde wieder losziehen." msgid "View Captain's options" msgstr "Optionen des Hauptmanns anzeigen" msgid "View Hero's options" msgstr "Optionen für den Helden anzeigen" msgid "View opposing Captain" msgstr "Ansicht des gegnerischen Hauptmanns" msgid "View opposing Hero" msgstr "Gegnerischen Helden anzeigen" msgid "Hide logs" msgstr "Protokolle ausblenden" msgid "Show logs" msgstr "Protokolle einblenden" msgid "Message Bar" msgstr "Nachrichtenleiste" msgid "Shows the results of individual monster's actions." msgstr "Ergebnisse der Aktionen der einzelnen Monster anzeigen." msgid "Are you sure you want to finish the battle in auto mode?" msgstr "Seid Ihr sicher, dass Ihr den Kampf im Automodus beenden möchtet?" msgid "Are you sure you want to enable auto combat?" msgstr "Wollt Ihr den automatischen Kampfmodus wirklich aktivieren?" msgid "%{name} skip their turn." msgstr "%{name} setzt den Zug aus." msgid "%{attacker} does %{damage} damage." msgid_plural "%{attacker} do %{damage} damage." msgstr[0] "%{attacker} verursacht %{damage} Schaden." msgstr[1] "%{attacker} verursachen %{damage} Schaden." msgid "1 creature perishes." msgid_plural "%{count} creatures perish." msgstr[0] "1 Kreatur stirbt." msgstr[1] "%{count} Kreaturen sterben." msgid "1 %{defender} perishes." msgid_plural "%{count} %{defender} perish." msgstr[0] "1 %{defender} stirbt." msgstr[1] "%{count} %{defender} sterben." msgid "1 soul is incorporated." msgid_plural "%{count} souls are incorporated." msgstr[0] "1 Seele wird aufgenommen." msgstr[1] "%{count} Seelen werden aufgenommen." msgid "1 %{unit} is revived." msgid_plural "%{count} %{unit} are revived." msgstr[0] "1 %{unit} wird wiederbelebt." msgstr[1] "%{count} %{unit} werden wiederbelebt." msgid "Moved %{monster}: from [%{src}] to [%{dst}]." msgstr "Hat %{monster}: von [%{src}] nach [%{dst}] verschoben." msgid "The %{name} resist the spell!" msgstr "Die %{name} widerstehen dem Zauber!" msgid "%{name} casts %{spell} on the %{troop}." msgstr "%{name} zaubert %{spell} auf die %{troop}." msgid "%{name} casts %{spell}." msgstr "%{name} zaubert %{spell}." msgid "The %{spell} does %{damage} damage to one undead creature." msgstr "Der %{spell} verursacht %{damage} an einer untoten Kreatur." msgid "The %{spell} does %{damage} damage to all undead creatures." msgstr "Der %{spell} verursacht %{damage} an allen Untoten." msgid "The %{spell} does %{damage} damage, %{count} creatures perish." msgstr "Der %{spell} verursacht %{damage} Schaden, %{count} Kreaturen sterben." msgid "The %{spell} does %{damage} damage." msgstr "Der %{spell} verursacht %{damage} Schaden." msgid "The %{spell} does %{damage} damage to one living creature." msgstr "Der %{spell} fügt einer lebenden Kreatur %{damage} Schaden zu." msgid "The %{spell} does %{damage} damage to all living creatures." msgstr "Der %{spell} verursacht %{damage} Schaden an allen lebenden Kreaturen." msgid "The %{attacker}' attack blinds the %{target}!" msgstr "%{target} wegen des Angriffs von %{attacker} geblendet!" msgid "The %{attacker}' gaze turns the %{target} to stone!" msgstr "Der Blick des %{attacker} verwandelt das %{target} in Stein!" msgid "The %{attacker}' curse falls upon the %{target}!" msgstr "Der Fluch des %{attacker} fällt auf das %{target}!" msgid "The %{target} are paralyzed by the %{attacker}!" msgstr "Die %{target} sind durch den %{attacker} gelähmt!" msgid "The %{attacker} dispel all good spells on your %{target}!" msgstr "Der %{attacker} verbannt alle guten Zaubersprüche auf Eurem %{target}!" msgid "The %{attacker} cast %{spell} on %{target}!" msgstr "Der %{attacker} hat %{spell} auf %{target} gewirkt!" msgid "Bad luck descends on the %{attacker}." msgstr "%{attacker} wird vom Pech verfolgt." msgid "Good luck shines on the %{attacker}." msgstr "Glück fällt auf %{attacker}." msgid "High morale enables the %{monster} to attack again." msgstr "Durch Hohe Moral können die %{monster} erneut anzugreifen." msgid "Low morale causes the %{monster} to freeze in panic." msgstr "Aufgrund schlechter Moral erstarren die %{monster} vor Angst." msgid "%{tower} does %{damage} damage." msgstr "%{tower} verursacht %{damage} Schaden." msgid "The mirror image is created." msgstr "Das Spiegelbild wurde erstellt." msgid "The mirror image is destroyed!" msgstr "Das Spiegelbild ist zerstört!" msgid "Are you sure you want to interrupt the auto battle?" msgstr "Seid Ihr sicher, dass Ihr den Autokampf unterbrechen möchtet?" msgid "Error" msgstr "Fehler" msgid "No spells to cast." msgstr "Keine Zaubersprüche zur Verfügung." msgid "Are you sure you want to retreat?" msgstr "Wollt Ihr Euch wirklich zurueckziehen?" msgid "Retreat disabled" msgstr "Rückzug deaktiviert" msgid "Surrender disabled" msgstr "Kapitulation deaktiviert" msgid "Damage: %{max}" msgstr "Schaden: %{max}" msgid "Damage: %{min} - %{max}" msgstr "Schäden: %{min} - %{max}" msgid "Perish: %{max}" msgstr "Sterben: %{max}" msgid "Perish: %{min} - %{max}" msgstr "Sterben: %{min} - %{max}" msgid "" "Through eagle-eyed observation, %{name} is able to learn the magic spell " "%{spell}." msgstr "" "Durch die Beobachtung mit seinen Adleraugen ist %{name} in der Lage, den " "Zauberspruch %{spell} zu erlernen." msgid "Human" msgstr "Mensch" msgid "AI" msgstr "KI" #, fuzzy msgid "Start" msgstr "Neustart" #, fuzzy msgid "Start the battle." msgstr "Zurück in die Schlacht." msgid "Exit" msgstr "Verlassen" msgid "Reset" msgstr "" msgid "Reset to default settings." msgstr "" msgid "Please select another hero." msgstr "Bitte wählt einen anderen Helden aus." msgid "Set Attack Skill" msgstr "Angriff setzen" msgid "Set Defense Skill" msgstr "Verteidigung setzen" msgid "Set Power Skill" msgstr "Zauberkraft setzen" msgid "Set Knowledge Skill" msgstr "Wissen setzen" #, fuzzy msgid "%{race1} %{name1} vs %{race2} %{name2}" msgstr "%{name1} trifft %{name2}" msgid "Monsters" msgstr "Monster" msgid "N/A" msgstr "N/A" msgid "Left Turret" msgstr "Linker Turm" msgid "Right Turret" msgstr "Rechter Turm" msgid "The %{name} fires with the strength of %{count} Archers" msgstr "%{name} feuert mit der Kraft von %{count} Bogenschützen" msgid "each with a +%{attack} bonus to their attack skill." msgstr "jeder mit einem +%{attack} Bonus auf seine Angriffsfähigkeit." msgid "The %{name} is destroyed." msgstr "%{name} ist zerstört." msgid "%{count} %{name} rises from the dead!" msgid_plural "%{count} %{name} rise from the dead!" msgstr[0] "%{count} %{name} steht von den Toten auf!" msgstr[1] "%{count} %{name} stehen von den Toten auf!" msgid "Dwarven Alliance" msgstr "Zwergen-Allianz" msgid "Sorceress Guild" msgstr "Gilde der Zauberinnen" msgid "Necromancer Guild" msgstr "Totenbeschwörer-Gilde" msgid "Ogre Alliance" msgstr "Oger-Allianz" msgid "Dwarfbane" msgstr "Zwergenfluch" msgid "Dragon Alliance" msgstr "Drachen-Allianz" msgid "Elven Alliance" msgstr "Elfen-Bündnis" msgid "Kraeger defeated" msgstr "Kräger besiegt" msgid "Wayward Son" msgstr "Eigensinniger Sohn" msgid "Uncle Ivan" msgstr "Onkel Ivan" msgid "Annexation" msgstr "Annektion" msgid "Force of Arms" msgstr "Macht des Schwerts" msgid "Save the Dwarves" msgstr "Rettet die Zwerge" msgid "Carator Mines" msgstr "Die Carator-Minen" msgid "Turning Point" msgstr "Wendepunkt" msgid "scenarioName|Defender" msgstr "Verteidiger" msgid "Corlagon's Defense" msgstr "Corlagons Kampf" msgid "The Crown" msgstr "Die Krone" msgid "The Gauntlet" msgstr "Fehdehandschuh" msgid "Betrayal" msgstr "Verrat" msgid "Final Justice" msgstr "Gerechtigkeit" msgid "" "Roland needs you to defeat the lords near his castle to begin his war of " "rebellion against his brother. They are not allied with each other, so they " "will spend most of their time fighting with one another. Victory is yours " "when you have defeated all of their castles and heroes." msgstr "" "Roland braucht Euch, um die Fürsten in der Nähe seiner Burg zu besiegen und " "um die Rebellion gegen Archibald zu beginnen. Diese sind nicht miteinander " "verbündet, so dass sie die meiste Zeit damit verbringen werden, sich " "gegenseitig zu bekämpfen. Besiegt alle Burgen und Helden für den Sieg." msgid "" "The local lords refuse to swear allegiance to Roland, and must be subdued. " "They are wealthy and powerful, so be prepared for a tough fight. Capture all " "enemy castles to win." msgstr "" "Die örtlichen Fürsten weigern sich, Roland die Treue zu schwören, und müssen " "daher unterworfen werden. Sie sind wohlhabend und mächtig, macht Euch auf " "einen harten Kampf gefasst. Für den Sieg müsst Ihr alle Burgen einnehmen." msgid "" "Your task is to defend the Dwarves against Archibald's forces. Capture all " "of the enemy towns and castles to win, and be sure not to lose all of the " "dwarf towns at once, or the enemy will have won." msgstr "" "Eure Aufgabe ist es, die Zwerge gegen Archibalds Streitkräfte zu " "verteidigen. Erobert alle feindlichen Städte und Burgen, um zu gewinnen, und " "achtet darauf, nicht alle Zwergenstädte auf einmal zu verlieren, denn sonst " "hat der Feind gewonnen." msgid "" "You will face four allied enemies in a straightforward fight for resource " "and treasure. Capture all of the enemy castles for victory." msgstr "" "Ihr werdet vier verbündeten Feinden in einem direkten Kampf um Ressourcen " "und Schätze gegenüberstehen. Erobert alle feindlichen Burgen für den Sieg." msgid "" "Your enemies are allied against you and start close by, so be ready to come " "out fighting. You will need to own all four castles in this small valley to " "win." msgstr "" "Eure Feinde haben sich gegen Euch verbündet und starten ganz in Eurer Naehe. " "Seid also bereit für einen baldigen Kampf. Ihr müsst alle vier Burgen in dem " "schmalen Tal kontrollieren, um zu gewinnen." msgid "" "The Sorceress' guild of Noraston has requested Roland's aid against an " "attack from Archibald's allies. Capture all of the enemy castles to win, and " "don't lose Noraston, or you'll lose the scenario. (Hint: There is an enemy " "castle on an island in the ocean.)" msgstr "" "Die Gilde der Zauberinnen von Noraston hat Roland um Hilfe gegen einen " "Angriff von Archibalds Verbündeten ersucht. Erobert alle feindlichen Burgen, " "um zu gewinnen, und verliert Noraston nicht, oder Ihr verliert das Szenario. " "(Hinweis: Auf einer Insel im Ozean befindet sich eine feindliche Burg.)" msgid "" "Gather as large an army as possible and capture the enemy castle within 8 " "weeks. You are opposed by only one enemy, but must travel a long way to get " "to the enemy castle. Any troops you have in your army at the end of this " "scenario will be with you in the final battle." msgstr "" "Stellt eine größtmögliche Armee auf und erobert die feindliche Burg " "innerhalb von 8 Wochen. Euch steht nur ein Feind gegenueber, aber Ihr habt " "einen sehr weiten Weg zurückzulegen. Alle Eure bis zum Ende des Szenarios " "überlebenden Truppen begleiten Euch in die letzte Schlacht." msgid "" "Find the Crown before Archibald's heroes find it. Roland will need the Crown " "for the final battle against Archibald." msgstr "" "Findet die Krone, bevor Archibalds Helden sie finden. Roland wird die Krone " "für den letzten Kampf gegen Archibald benötigen." msgid "" "Three allied enemies stand before you and victory, including Lord Corlagon. " "Roland is in a castle to the northwest, and you will lose if he falls to the " "enemy. Remember that capturing Lord Corlagon will ensure that he will not " "fight against you in the final scenario." msgstr "" "Drei verbündete Feinde stehen zwischen Euch und dem Sieg, darunter Lord " "Corlagon. Roland befindet sich in einer Burg im Nordwesten, wenn sie an den " "Feind fällt, ist alles verloren. Denkt daran, dass Lord Corlagons " "Gefangennahme ihn daran hindern wird, im letzten Szenario gegen Euch zu " "kämpfen." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Archibald to end the war!" msgstr "" "Dies ist die letzte Schlacht. Sowohl Ihr als auch Eure Feinde sind bis an " "die Zähne bewaffnet, und alle sind gegen Euch verbündet. Nehmt Archibald " "gefangen und der Krieg ist beendet!" msgid "" "Switching sides leaves you with three castles against the enemy's one. This " "battle will be the easiest one you will face for the rest of the war..." "traitor." msgstr "" "Wenn Ihr die Seiten wechselt, habt Ihr drei Schlösser gegen eines des " "Feindes. Diese Schlacht wird für den Rest des Krieges die einfachste sein... " "Verräter." msgid "Barbarian Wars" msgstr "Barbaren-Kriege" msgid "First Blood" msgstr "Erstes Blut" msgid "Necromancers" msgstr "Totenbeschwörer" msgid "Slay the Dwarves" msgstr "Schlachtet die Zwerge" msgid "Country Lords" msgstr "Landesfürsten" msgid "Dragon Master" msgstr "Drachenmeister" msgid "Rebellion" msgstr "Rebellion" msgid "Apocalypse" msgstr "Apocalypse" msgid "Greater Glory" msgstr "Größte Ehre" msgid "" "King Archibald requires you to defeat the three enemies in this region. They " "are not allied with one another, so they will spend most of their energy " "fighting amongst themselves. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "König Archibald braucht Euch, um die drei Feinde in dieser Region zu " "besiegen. Diese sind nicht miteinander verbündet, also werden sie die meiste " "Zeit miteinander kämpfen. Ihr werdet gewinnen, wenn Ihr alle feindlichen " "Burgen kontrolliert und alle Helden besiegt." msgid "" "You must unify the barbarian tribes of the north by conquering them. As in " "the previous mission, the enemy is not allied against you, but they have " "more resources at their disposal. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Ihr müsst die Barbarenstämme des Nordens vereinen, indem Ihr sie erobert. " "Wie in der vorherigen Mission sind die Feinde nicht miteinander verbündet, " "aber sie haben mehr Ressourcen zur Verfügung. Ihr werdet gewinnen, wenn Ihr " "alle feindlichen Burgen kontrolliert und alle Helden besiegt." msgid "" "Do-gooder wizards have taken the Necromancers' castle. You must retake it to " "achieve victory. Remember that while you start with a powerful army, you " "have no castle and must take one within 7 days, or lose this battle. (Hint: " "The nearest castle is to the southeast.)" msgstr "" "Gutmenschen-Zauberer haben die Burg der Nekromanten eingenommen. Ihr müsst " "sie zurückerobern, um den Sieg zu erringen. Obwohl Ihr mit einer mächtigen " "Armee startet, habt Iht keine Burg und müsst innerhalb von 7 Tagen eine " "einnehmen, sonst verliert Ihr die Schlacht. (Tipp: Die nächstgelegene Burg " "ist im Südosten)." msgid "" "The dwarves need conquering before they can interfere in King Archibald's " "plans. Roland's forces have more than one hero and many towns to start with, " "so be ready for attack from multiple directions. You must capture all of the " "enemy towns and castles to claim victory." msgstr "" "Die Zwerge müssen unterworfen werden, bevor sie sich in König Archibalds " "Pläne einmischen können. Rolands Streitkräfte haben mehr als einen Helden " "und viele Städte. Seid also bereit aus verschiedenen Richtungen angegriffen " "zu werden. Ihr müsst alle feindlichen Städte und Burgen erobern, um den Sieg " "zu erringen. " msgid "" "You must put down a peasant revolt led by Roland's forces. All are allied " "against you, but you have Lord Corlagon, an experienced hero, to help you. " "Capture all enemy castles to win." msgstr "" "Ihr müsst einen Bauernaufstand niederschlagen, der von Rolands Streitkräften " "angeführt wird. Alle sind gegen Euch verbündet, aber Ihr habt Lord Corlagon, " "einen erfahrenen Helden, der Euch helfen wird. Erobert alle gegnerischen " "Burgen, um zu gewinnen." msgid "" "There are two enemies allied against you in this mission. Both are well " "armed and seek to evict you from their island. Avoid them and capture Dragon " "City to win." msgstr "" "In dieser Mission sind zwei Feinde gegen Euch verbündet. Beide sind gut " "bewaffnet und versuchen Euch von ihrer Insel zu vertreiben. Vermeidet sie " "und erobert die Drachenstadt, um zu gewinnen." msgid "" "Your orders are to conquer the country lords that have sworn to serve " "Roland. All of the enemy castles are unified against you. Since you start " "without a castle, you must hurry to capture one before the end of the week. " "Capture all enemy castles for victory." msgstr "" "Euer Auftrag lautet, die Landesfürsten zu bezwingen, die Roland die Treue " "geschworen haben. Alle feindlichen Burgen sind gegen Euch vereint. Da Ihr " "ohne eine Burg beginnt, müsst Ihr Euch beeilen, eine vor dem Ende der Woche " "zu erobern. Erobert alle feindlichen Burgen, um zu gewinnen." msgid "" "Find the Crown before Roland's heroes find it. Archibald will need the Crown " "for the final battle against Roland." msgstr "" "Findet die Krone, bevor Rolands Helden sie finden. Archibald wird die Krone " "für den letzten Kampf gegen Roland brauchen." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Roland to win the war, and be sure " "not to lose Archibald in the fight!" msgstr "" "Dies ist die letzte Schlacht. Sowohl Ihr als auch Euer Feind sind bis an die " "Zähne bewaffnet, und alle sind gegen Euch verbündet. Nehmt Roland gefangen, " "um den Krieg zu gewinnen, und passt auf, dass Ihr Archibald nicht verliert! " msgid "Arrow's Flight" msgstr "Flug des Pfeils" msgid "Island of Chaos" msgstr "Insel des Chaos" msgid "The Abyss" msgstr "Der Abgrund" msgid "Uprising" msgstr "Aufstand" msgid "Aurora Borealis" msgstr "Aurora Borealis" msgid "Betrayal's End" msgstr "Das Ende des Betrugs" msgid "Corruption's Heart" msgstr "Das Herz der Korruption" msgid "The Giant's Pass" msgstr "Der Pass des Riesen" msgid "" "Subdue the unruly local lords in order to provide the Empire with facilities " "to operate in this region." msgstr "" "Unterwerft die widerspenstigen Landesfürsten, um dem Kaiserreich die " "Möglichkeit zu geben, in dieser Region zu operieren." msgid "" "Eliminate all opposition in this area. Then the first piece of the artifact " "will be yours." msgstr "" "Beseitigt alle Gegner in diesem Gebiet. Dann gehört das erste Stück des " "Artefakts Euch." msgid "" "The sorceresses to the northeast are rebelling! For the good of the empire " "you must quash their feeble uprising on your way to the mountains." msgstr "" "Die Magierinnen im Nordosten rebellieren! Zum Wohle des Reiches müsst Ihr " "ihren schwachen Aufstand auf Eurem Weg in die Berge niederschlagen." msgid "" "Having prepared for your arrival, Kraeger has arranged for a force of " "necromancers to thwart your quest. You must capture the castle of Scabsdale " "before the first day of the third week, or the Necromancers will be too " "strong for you." msgstr "" "Kräger hat sich auf Eure Ankunft vorbereitet und eine Truppe von Nekromanten " "zusammengestellt, die Euer Vorhaben vereiteln sollen. Ihr müsst die Burg von " "Scabsdale vor dem ersten Tag der dritten Woche einnehmen, sonst werden die " "Nekromanten zu stark für Euch sein." msgid "" "The barbarian despot in this area is, as yet, ignorant of your presence. " "Quickly, build up your forces before you are discovered and attacked! Secure " "the region by subduing all enemy forces." msgstr "" "Der barbarische Despot in diesem Gebiet weiß noch nichts von Eurer " "Anwesenheit. Baut schnell Eure Streitkräfte auf, bevor Ihr entdeckt und " "angegriffen werdet! Sichert die Region, indem Ihr alle feindlichen Truppen " "unterwerft." msgid "" "The Empire is weak in this region. You will be unable to completely subdue " "all forces in this area, so take what you can before reprisal strikes. " "Remember, your true goal is to claim the Helmet of Anduran." msgstr "" "Das Reich ist in dieser Region schwach. Ihr werdet nicht in der Lage sein, " "alle Kräfte in diesem Gebiet vollständig zu unterwerfen, also nehmt, was Ihr " "könnt, bevor die Vergeltung zuschlägt. Denkt daran, dass es Euer " "eigentliches Ziel ist, den Helm von Anduran zu erobern." msgid "For the good of the Empire, eliminate Kraeger." msgstr "Eliminiert Kräger zum Wohle des Reiches." msgid "" "At last, you have the opportunity and the facilities to rid the Empire of " "the necromancer's evil. Eradicate them completely, and you will be sung as a " "hero for all time." msgstr "" "Endlich habt Ihr die Gelegenheit und die Möglichkeiten, das Reich vom Bösen " "des Nekromanten zu befreien. Vernichtet sie vollständig, und Ihr werdet für " "alle Zeiten als Held besungen sein." msgid "Border Towns" msgstr "Grenzstädte" msgid "Conquer and Unify" msgstr "Erobern und vereinen" msgid "Crazy Uncle Ivan" msgstr "Der verrückte Onkel Ivan" msgid "The Wayward Son" msgstr "Der eigensinnige Sohn" msgid "Ivory Gates" msgstr "Ivory Gates" msgid "The Elven Lands" msgstr "Das Land der Elfen" msgid "The Epic Battle" msgstr "Die heldenhafte Schlacht" msgid "The Southern War" msgstr "Der Südkrieg" msgid "" "Conquer and unite all the enemy tribes. Don't lose the hero Jarkonas, the " "forefather of all descendants." msgstr "" "Erobert und vereinigt alle gegnerischen Stämme. Verliert nicht den Helden " "Jarkonas, dem Urvater aller Nachkommen." msgid "" "Your rival, the Kingdom of Harondale, is attacking weak towns on your " "border! Recover from their first strike and crush them completely!" msgstr "" "Euer Rivale, das Königreich Harondale, greift schwache Städte an Eurer " "Grenze an! Erholt Euch von ihrem ersten Schlag und zerschlagt sie " "vollständig!" msgid "" "Find your wayward son Joseph who is rumored to be living in the desolate " "lands. Do it before the first day of the third month or it will be of no " "help to your family." msgstr "" "Findet Euren eigensinnigen Sohn Joseph, von dem gemunkelt wird, dass er in " "den trostlosen Ländern lebt. Findet ihn vor dem ersten Tag des dritten " "Monats oder er wird Eurer Familie nicht mehr helfen können." msgid "" "Rescue your crazy uncle Ivan. Find him before the first day of the fourth " "month or it will be no help to your kingdom." msgstr "" "Rettet Euren verrückten Onkel Ivan. Findet ihn vor dem ersten Tag des " "vierten Monats oder er wird keine Hilfe für Euer Königreich sein." msgid "" "Destroy the barbarians who are attacking the southern border of your " "kingdom! Recover your fallen towns, and then invade the jungle kingdom. " "Leave no enemy standing." msgstr "" "Vernichtet die Barbaren, die die Südgrenze Eures Königreichs angreifen! " "Holet Eure gefallenen Städte zurück und dringt dann in das " "Dschungelkönigreich ein. Lasst keinen Feind am Leben." msgid "Retake the castle of Ivory Gates, which has fallen due to treachery." msgstr "Erobert die Burg Ivory Gates zurück, die durch Verrat gefallen ist." msgid "" "Gain the favor of the elves. They will not allow trees to be chopped down, " "so they will send you wood every 2 weeks. You must complete your mission " "before the first day of the seventh month, or the kingdom will surely fall." msgstr "" "Gewinnet die Gunst der Elfen. Sie werden nicht zulassen, dass Bäume gefällt " "werden, und werden Euch daher alle 2 Wochen Holz schicken. Beendet Eure " "Mission vor dem ersten Tag des siebten Monats, oder das Königreich wird " "fallen." msgid "" "This is the final battle against your rival kingdom of Harondale. Eliminate " "everyone, and don't lose the hero Jarkonas VI." msgstr "" "Dies ist die letzte Schlacht gegen das rivalisierendes Königreich Harondale. " "Beseitigt alle Feinde und verliert nicht Euren Helden Jarkonas VI." msgid "Fount of Wizardry" msgstr "Der Zauberbrunnen" msgid "Power's End" msgstr "Das Ende der Macht" msgid "The Eternal Scrolls" msgstr "Die ewigen Schriftrollen" msgid "The Shrouded Isles" msgstr "Die Nebelinseln" msgid "" "Your mission is to vanquish the warring mages in the magical Shrouded Isles. " "The completion of this task will give you a fighting chance against your " "rivals." msgstr "" "Eure Mission ist es, die kriegerischen Magier auf den magischen Nebelinseln " "zu besiegen. Die Erfüllung dieser Aufgabe wird Euch die Chance auf einen " "Kampf gegen Eure Rivalen geben." msgid "" "The location of the great library has been discovered! You must make your " "way to it, and reclaim the city of Chronos in which it lies." msgstr "" "Der Standort der großen Bibliothek ist entdeckt worden! Schlagt Euch dorthin " "durch und erlangt die Herrschaft über die Stadt Chronos, in der sie sich " "befindet." #, fuzzy msgid "" "Find the Orb of negation, which is said to be buried in this land. There are " "clues inscribed on stone obelisks which will help lead you to your prize. " "Find the Orb before the first day of the sixth month, or your rivals will " "surely have gotten to the fount before you." msgstr "" "Findet die Kugel der Aufhebung, von der gesagt wird, dass sie in diesem Land " "begraben ist. Hinweise zum Ort stehen auf Steinobelisken geschrieben. Findet " "die Kugel vor dem ersten Tag des sechsten Monats, oder Eure Rivalen werden " "die Quelle vor Euch gefunden haben." msgid "" "You must take control of the castle of Magic, where the fount of wizardry " "lies. Do this and your victory will be supreme." msgstr "" "Ihr müsst die Kontrolle über die Burg der Magie erlangen, in der sich die " "Quelle der Zauberei befindet. Tut dies und Euer Sieg wird der Höchste sein." msgid "Blood is Thicker" msgstr "Blut ist dicker" msgid "King and Country" msgstr "König und Vaterland" msgid "Pirate Isles" msgstr "Pirateninseln" msgid "Stranded" msgstr "Gestrandet" msgid "" "Capture the town on the island off the southeast shore in order to construct " "a boat and travel back towards the mainland. Do not lose the hero Gallavant." msgstr "" "Erobert die Stadt auf der Insel vor der Südostküste, um dort ein Boot zu " "bauen um damit zurück aufs Festland zu gelangen. Verliert dabei nicht den " "Helden Gallavant." msgid "" "Find and defeat Martine, the pirate leader, who resides in Pirates Cove. Do " "not lose Gallavant or your quest will be over." msgstr "" "Findet und besiegt Martine, die Piratenanführerin, die in der Piratenbucht " "lebt. Verliert Gallavant nicht oder Eure Suche wird zu Ende sein." msgid "" "Eliminate all the other forces who oppose the rule of Lord Alberon. " "Gallavant must not die." msgstr "" "Vernichtet alle anderen Streitkräfte, die sich der Herrschaft von Lord " "Alberon widersetzen. Gallavant darf nicht fallen." msgid "" "Overthrow the entrenched monarchy of Lord Alberon, and claim all the land in " "your name. Gallavant must not die." msgstr "" "Stürzt die tief verwurzelte Monarchie von Lord Alberon und beansprucht das " "ganze Land in Eurem Namen. Gallavant darf nicht fallen." msgid " bane" msgstr " Fluch" msgid " alliance" msgstr " Allianz" msgid "Carry-over forces" msgstr "Übertragungskräfte" msgid " bonus" msgstr " Bonus" msgid " defeated" msgstr " besiegt" msgid " will always run away from your army." msgstr " werden immer vor Eurer Armee weglaufen." msgid " will be willing to join your army." msgstr " bereit sein werden, sich Eurer Armee anzuschließen." msgid "\"%{artifact}\" artifact will be carried over the scenario." msgstr "Das Artefakt \"%{artifact}\" wird in das Szenario übernommen." msgid "The army will be carried over the scenario." msgstr "Die Armee wird über das Szenario mitgenommen." msgid "The kingdom will have +%{count} %{resource} each day." msgstr "Das Königreich wird jeden Tag über +%{count} %{resource} verfügen." msgid "\"%{spell}\" spell will be carried over the scenario." msgstr "\"%{spell}\" wird in das Szenario übernommen." msgid "%{hero} can be hired in the scenario." msgstr "%{hero} kann in dem Szenario angeheuert werden." msgid "" "%{hero} has been defeated and will not appear in the subsequent scenarios." msgstr "" "%{hero} wurde besiegt und wird in den folgenden Szenarien nicht mehr " "erscheinen." msgid "The dwarves recognize their allies and gladly join your forces." msgstr "" "Die Zwerge erkennen ihren Verbündeten und schließen sich gerne Euren Kräften " "an." msgid "The ogres recognize you as the Dwarfbane and lumber over to join you." msgstr "" "Die Oger erkennet Euch als den Zwergenfluch und humpeln zu Euch hinüber, um " "sich Euch anzuschließen." msgid "" "The dragons, snarling and growling, agree to join forces with you, their " "'Ally'." msgstr "" "Die Drachen, knurrend und grollend, stimmen zu, sich mit Euch, ihrem " "\"Verbündeten\", zusammenzuschließen." msgid "" "As you approach the group of elves, their leader calls them all to " "attention. He shouts to them, \"Who of you is brave enough to join this " "fearless ally of ours?\" The group explodes with cheers as they run to join " "your ranks." msgstr "" "Als Ihr Euch der Gruppe der Elfen nähert, macht ihr Anführer sie alle auf " "sich aufmerksam. Er ruft ihnen zu: \"Wer von euch ist tapfer genug, sich " "unserem furchtlosen Verbündeten anzuschließen?\" Die Gruppe explodiert vor " "Jubel, als sie hinüberstürmen um in Euren Reihen aufgenommen zu werden." msgid "" "The dwarves hail you, \"Any friend of Roland is a friend of ours. You may " "pass.\"" msgstr "" "Die Zwerge grüßen Euch: \"Jeder Freund Rolands ist ein Freund von uns. Ihr " "könnt durchgehen.\"" msgid "" "The ogres give you a grunt of recognition, \"Archibald's allies may pass.\"" msgstr "" "Die Oger geben ein anerkennendes Grunzen von sich: \"Archibalds Verbündete " "dürfen durchgehen.\"" msgid "" "The dragons see you and call out. \"Our alliance with Archibald compels us " "to join you. Unfortunately you have no room. A pity!\" They quickly scatter." msgstr "" "Die Drachen sehen Euch zu und rufen. \"Unser Bündnis mit Archibald zwingt " "uns, uns Euch anzuschließen. Leider habt Ihr keinen Platz. Das ist schade! " "\" Schnell zerstreuen sie sich." msgid "" "The elves stand at attention as you approach. Their leader calls to you and " "says, \"Let us not impede your progress, ally! Move on, and may victory be " "yours.\"" msgstr "" "Die Elfen stehen stramm, als Ihr Euch ihnen nähert. Ihr Anführer ruft Euch " "zu: \"Lasst uns Euer Vorankommen nicht behindern, Verbündeter! Zieht weiter, " "und möge der Sieg Euer sein.\"" msgid "\"The Dwarfbane!!!!, run for your lives.\"" msgstr "\"Zwergenfluch!!!!, rennt um euer Leben.\"" msgid "campaignBonus|Animate Dead" msgstr "Tote beleben" msgid "campaignBonus|Chain Lightning" msgstr "Kettenblitzstrahl" msgid "campaignBonus|Fireblast" msgstr "Feuerstoß" msgid "campaignBonus|Mass Curse" msgstr "Massenfluch" msgid "campaignBonus|Mass Haste" msgstr "Masseneile" msgid "campaignBonus|Mirror Image" msgstr "Spiegelbild" msgid "campaignBonus|Resurrect" msgstr "Tote beleben" msgid "campaignBonus|Steelskin" msgstr "Stahlhaut" msgid "campaignBonus|Summon Earth" msgstr "Erdwesen beschwören" msgid "campaignBonus|View Heroes" msgstr "Helden anzeigen" msgid "campaignBonus|Ballista" msgstr "Ballista" msgid "campaignBonus|Black Pearl" msgstr "Schwarze Perle" msgid "campaignBonus|Caster's Bracelet" msgstr "Zauberarmband" msgid "campaignBonus|Defender Helm" msgstr "Verteidigungshelm" msgid "campaignBonus|Breastplate" msgstr "Brustpanzer" msgid "campaignBonus|Dragon Sword" msgstr "Drachenschwert" msgid "campaignBonus|Fizbin Medal" msgstr "Fizbin des Pechs" msgid "campaignBonus|Foremost Scroll" msgstr "Herausragendste Schriftrolle" msgid "campaignBonus|Gauntlets" msgstr "Gepanzerter Handschuh" msgid "campaignBonus|Hideous Mask" msgstr "Scheußliche Maske" msgid "campaignBonus|Mage's Ring" msgstr "Ring des Magiers" msgid "campaignBonus|Major Scroll" msgstr "Grosse Schriftrolle" msgid "campaignBonus|Medal of Honor" msgstr "Medaille der Ehre" msgid "campaignBonus|Medal of Valor" msgstr "Medaille der Tapferkeit" msgid "campaignBonus|Minor Scroll" msgstr "Kleine Schriftrolle" msgid "campaignBonus|Nomad Boots" msgstr "Nomadenstiefel" msgid "campaignBonus|Power Axe" msgstr "Power-Axt" msgid "campaignBonus|Spiked Shield" msgstr "Stachelschild" msgid "campaignBonus|Stealth Shield" msgstr "Tarnkappenschild" msgid "campaignBonus|Tax Lien" msgstr "Steuererklärung" msgid "campaignBonus|Thunder Mace" msgstr "Donnerstab" msgid "campaignBonus|Traveler's Boots" msgstr "Reisestiefel" msgid "campaignBonus|White Pearl" msgstr "Weisse Perle" msgid "campaignBonus|Basic Archery" msgstr "Einfach Bogen" msgid "campaignBonus|Advanced Archery" msgstr "Verbessert Bogen" msgid "campaignBonus|Expert Archery" msgstr "Meisterhaft Bogen" msgid "campaignBonus|Basic Ballistics" msgstr "Einfache Ballistik" msgid "campaignBonus|Advanced Ballistics" msgstr "Verbesserte Ballistik" msgid "campaignBonus|Expert Ballistics" msgstr "Meisterhafte Ballistik" msgid "campaignBonus|Basic Diplomacy" msgstr "Einfache Diplomatie" msgid "campaignBonus|Advanced Diplomacy" msgstr "Verbesserte Diplomatie" msgid "campaignBonus|Expert Diplomacy" msgstr "Meisterhafte Diplomatie" msgid "campaignBonus|Basic Eagle Eye" msgstr "Einfaches Adlerauge" msgid "campaignBonus|Advanced Eagle Eye" msgstr "Verbessertes Adlerauge" msgid "campaignBonus|Expert Eagle Eye" msgstr "Meisterhaftes Adlerauge" msgid "campaignBonus|Basic Estates" msgstr "Einfach Grundbesitz" msgid "campaignBonus|Advanced Estates" msgstr "Verbessert Grundbesitz" msgid "campaignBonus|Expert Estates" msgstr "Meisterhaft Grundbesitz" msgid "campaignBonus|Basic Leadership" msgstr "Einfache Führung" msgid "campaignBonus|Advanced Leadership" msgstr "Verbesserte Führung" msgid "campaignBonus|Expert Leadership" msgstr "Meisterhafte Führung" msgid "campaignBonus|Basic Logistics" msgstr "Einfache Logistik" msgid "campaignBonus|Advanced Logistics" msgstr "Verbesserte Logistik" msgid "campaignBonus|Expert Logistics" msgstr "Meisterhafte Logistik" msgid "campaignBonus|Basic Luck" msgstr "Einfaches Glück" msgid "campaignBonus|Advanced Luck" msgstr "Verbessertes Glück" msgid "campaignBonus|Expert Luck" msgstr "Meisterhaftes Glück" msgid "campaignBonus|Basic Mysticism" msgstr "Einfache Mystik" msgid "campaignBonus|Advanced Mysticism" msgstr "Verbesserte Mystik" msgid "campaignBonus|Expert Mysticism" msgstr "Meisterhafte Mystik" msgid "campaignBonus|Basic Navigation" msgstr "Einfache Navigation" msgid "campaignBonus|Advanced Navigation" msgstr "Verbesserte Navigation" msgid "campaignBonus|Expert Navigation" msgstr "Meisterhafte Navigation" msgid "campaignBonus|Basic Necromancy" msgstr "Einfache Nekromantie" msgid "campaignBonus|Advanced Necromancy" msgstr "Verbesserte Nekromantie" msgid "campaignBonus|Expert Necromancy" msgstr "Meisterhafte Nekromantie" msgid "campaignBonus|Basic Pathfinding" msgstr "Einfaches Pfadfinden" msgid "campaignBonus|Advanced Pathfinding" msgstr "Verbessert Pfadfinden" msgid "campaignBonus|Expert Pathfinding" msgstr "Meisterhaft Pfadfinden" msgid "campaignBonus|Basic Scouting" msgstr "Einfaches Spähen" msgid "campaignBonus|Advanced Scouting" msgstr "Verbessertes Spähen" msgid "campaignBonus|Expert Scouting" msgstr "Meisterhaftes Spähen" msgid "campaignBonus|Basic Wisdom" msgstr "Einfache Weisheit" msgid "campaignBonus|Advanced Wisdom" msgstr "Verbesserte Weisheit" msgid "campaignBonus|Expert Wisdom" msgstr "Meisterhafte Weisheit" msgid "" "The main hero will have \"%{artifact}\" artifact at the start of the " "scenario." msgstr "" "Der Hauptheld hat zu Beginn des Szenarios ein \"%{artifact}\"-Artefakt." msgid "" "The kingdom will receive %{amount} additional %{resource} at the start of " "the scenario." msgstr "" "Das Königreich erhält zu Beginn des Szenarios %{amount} zusätzliche " "%{resource}." msgid "" "The kingdom will have %{amount} less %{resource} at the start of the " "scenario." msgstr "" "Das Königreich hat zu Beginn des Szenarios %{amount} weniger %{resource}." msgid "" "The main hero will have %{count} %{monster} at the start of the scenario." msgstr "Der Hauptheld hat zu Beginn des Szenarios %{count} %{monster}." msgid "" "The main hero will have \"%{spell}\" spell at the start of the scenario." msgstr "Der Hauptheld hat zu Beginn des Szenarios einen \"%{spell}\"-Zauber." msgid "The starting race of the scenario will be %{race}." msgstr "Die Startrasse des Szenarios ist %{race}." msgid "" "The main hero will have additional %{count} %{skill} at the start of the " "scenario." msgstr "" "Der Hauptheld hat zu Beginn des Szenarios zusätzliche %{count} %{skill}." msgid "The main hero will have %{skill} at the start of the scenario." msgstr "Der Hauptheld hat zu Beginn des Szenarios %{skill}." msgid "Roland" msgstr "Roland" msgid "Archibald" msgstr "Archibald" #, fuzzy msgid "Price of Loyalty" msgstr "The Price of Loyalty" msgid "Voyage Home" msgstr "Heimreise" msgid "Wizard's Isle" msgstr "Die Insel der Zauberer" msgid "Descendants" msgstr "Nachkommen" msgid "The %{building} produces %{monster}." msgstr "Der %{building} produziert %{monster}." msgid "Requires:" msgstr "Erfordert:" msgid "Exit this menu without doing anything." msgstr "Dieses Menü verlassen." msgid "Cannot build. You have already built here today." msgstr "Kann nicht gebaut werden. Ihr habt an diesem Tag hier bereits gebaut." msgid "For this action it is necessary to build a castle first." msgstr "Für diese Aktion ist es notwendig, zuerst eine Burg zu bauen." msgid "Cannot build %{name} because the castle is too far away from an ocean." msgstr "" "%{name} kann nicht gebaut werden, weil keine Gewässer in der Nähe sind." msgid "disable build." msgstr "Bauen deaktivieren." msgid "Cannot afford %{name}." msgstr "%{name} ist zu teuer." msgid "%{name} is already built." msgstr "%{name} ist bereits gebaut." msgid "Cannot build %{name}." msgstr "Kann %{name} nicht bauen." msgid "Build %{name}." msgstr "%{name} Bauen." msgid "Blackridge" msgstr "Blackridge" msgid "Hillstone" msgstr "Hillstone" msgid "Pinehurst" msgstr "Pinehurst" msgid "Whiteshield" msgstr "Whiteshield" msgid "Woodhaven" msgstr "Woodhaven" msgid "Blackwind" msgstr "Blackwind" msgid "Bloodreign" msgstr "Bloodreign" msgid "Dragontooth" msgstr "Dragontooth" msgid "Greywind" msgstr "Greywind" msgid "Portsmith" msgstr "Portsmith" msgid "Atlantium" msgstr "Atlantium" msgid "Middle Gate" msgstr "Middle Gate" msgid "Sansobar" msgstr "Sansobar" msgid "Tundara" msgstr "Tundara" msgid "Vulcania" msgstr "Vulcania" msgid "Baywatch" msgstr "Baywatch" msgid "Fountainhead" msgstr "Fountainhead" msgid "Vertigo" msgstr "Vertigo" msgid "Wildabar" msgstr "Wildabar" msgid "Winterkill" msgstr "Winterkill" msgid "Brindamoor" msgstr "Brindamoor" msgid "Lakeside" msgstr "Lakeside" msgid "Nightshadow" msgstr "Nightshadow" msgid "Olympus" msgstr "Olympus" msgid "Sandcaster" msgstr "Sandcaster" msgid "Alamar" msgstr "Alamar" msgid "Burlock" msgstr "Burlock" msgid "Dragadune" msgstr "Dragadune" msgid "Kalindra" msgstr "Kalindra" msgid "Xabran" msgstr "Xabran" msgid "Algary" msgstr "Algary" msgid "Basenji" msgstr "Basenji" msgid "Blackfang" msgstr "Blackfang" msgid "New Dawn" msgstr "New Dawn" msgid "Sorpigal" msgstr "Sorpigal" msgid "Avone" msgstr "Avone" msgid "Big Oak" msgstr "Big Oak" msgid "Chandler" msgstr "Chandler" msgid "Erliquin" msgstr "Erliquin" msgid "Hampshire" msgstr "Hampshire" msgid "Antioch" msgstr "Antioch" msgid "Avalon" msgstr "Avalon" msgid "Roc Haven" msgstr "Roc Haven" msgid "South Mill" msgstr "South Mill" msgid "Weed Patch" msgstr "Weed Patch" msgid "Brownston" msgstr "Brownston" msgid "Hilltop" msgstr "Hilltop" msgid "Weddington" msgstr "Weddington" msgid "Westfork" msgstr "Westfork" msgid "Whittingham" msgstr "Whittingham" msgid "Cathcart" msgstr "Cathcart" msgid "Elk's Head" msgstr "Elk's Head" msgid "Roscomon" msgstr "Roscomon" msgid "Sherman" msgstr "Sherman" msgid "Yorksford" msgstr "Yorksford" msgid "Blackburn" msgstr "Blackburn" msgid "Blacksford" msgstr "Blacksford" msgid "Burton" msgstr "Burton" msgid "Pig's Eye" msgstr "Pig's Eye" msgid "Viper's Nest" msgstr "Viper's Nest" msgid "Fenton" msgstr "Fenton" msgid "Lankershire" msgstr "Lankershire" msgid "Lombard" msgstr "Lombard" msgid "Timberhill" msgstr "Timberhill" msgid "Troy" msgstr "Troy" msgid "Forder Oaks" msgstr "Forder Oaks" msgid "Meramec" msgstr "Meramec" msgid "Quick Silver" msgstr "Quick Silver" msgid "Westmoor" msgstr "Westmoor" msgid "Willow" msgstr "Willow" msgid "Corackston" msgstr "Corackston" msgid "Sheltemburg" msgstr "Sheltemburg" msgid "Cannot recruit - you already have a Hero in this town." msgstr "Rekrutieren nicht möglich - ein Held steht bereits in dieser Stadt." msgid "Cannot recruit - you have too many Heroes." msgstr "Rekrutieren nicht möglich - Sie haben zu viele Helden." msgid "Cannot afford a Hero" msgstr "Ihr könnt Euch keinen Helden leisten." msgid "There is no room in the garrison for this army." msgstr "Es gibt keinen Platz in der Garnison für diese Armee." msgid "Fortifications" msgstr "Befestigungen" msgid "Farm" msgstr "Farm" msgid "Thatched Hut" msgstr "Strohhütte" msgid "Archery Range" msgstr "Schießplatz" msgid "Upg. Archery Range" msgstr "Aufg. Schießplatz" msgid "Blacksmith" msgstr "Schmiede" msgid "Upg. Blacksmith" msgstr "Aufg. Schmiede" msgid "Armory" msgstr "Waffenfabrik" msgid "Upg. Armory" msgstr "Aufg. Waffenfabrik" msgid "Jousting Arena" msgstr "Turnierplatz" msgid "Upg. Jousting Arena" msgstr "Aufg. Turnierplatz" msgid "Cathedral" msgstr "Kathedrale" msgid "Upg. Cathedral" msgstr "Aufg. Kathedrale" msgid "Coliseum" msgstr "Kolosseum" msgid "Garbage Heap" msgstr "Müllhaufen" msgid "Hut" msgstr "Hütte" msgid "Stick Hut" msgstr "Stockhütte" msgid "Upg. Stick Hut" msgstr "Aufg. Stockhütte" msgid "Den" msgstr "Bau" msgid "Adobe" msgstr "Lehmsteinhaus" msgid "Upg. Adobe" msgstr "Aufg. Lehmsteinhaus" msgid "Bridge" msgstr "Brücke" msgid "Upg. Bridge" msgstr "Aufg. Brücke" msgid "Pyramid" msgstr "Pyramide" msgid "Rainbow" msgstr "Regenbogen" msgid "Crystal Garden" msgstr "Kristallgarten" msgid "Treehouse" msgstr "Baumhaus" msgid "Cottage" msgstr "Landhaus" msgid "Upg. Cottage" msgstr "Aufg. Landhaus" msgid "Stonehenge" msgstr "Stonehenge" msgid "Upg. Stonehenge" msgstr "Aufg. Stonehenge" msgid "Fenced Meadow" msgstr "Eingezäunte Wiese" msgid "sorceress|Red Tower" msgstr "Roter Turm" msgid "Dungeon" msgstr "Burgverlies" msgid "Waterfall" msgstr "Wasserfall" msgid "Cave" msgstr "Höhle" msgid "Crypt" msgstr "Gruft" msgid "Nest" msgstr "Nest" msgid "Maze" msgstr "Labyrinth" msgid "Upg. Maze" msgstr "Aufg. Labyrinth" msgid "Swamp" msgstr "Sumpf" msgid "Green Tower" msgstr "Grüner Turm" msgid "warlock|Red Tower" msgstr "Roter Turm" msgid "Black Tower" msgstr "Schwarzer Turm" msgid "Library" msgstr "Bibliothek" msgid "Orchard" msgstr "Obstgarten" msgid "Habitat" msgstr "Wohnhaus" msgid "Pen" msgstr "Verschlag der Eber" msgid "Foundry" msgstr "Gießerei" msgid "Upg. Foundry" msgstr "Aufg. Gießerei" msgid "Cliff Nest" msgstr "Felsennest" msgid "Ivory Tower" msgstr "Elfenbeinturm" msgid "Upg. Ivory Tower" msgstr "Aufg. Elfenbeinturm" msgid "Cloud Castle" msgstr "Wolkenburg" msgid "Upg. Cloud Castle" msgstr "Aufg. Wolkenburg" msgid "Storm" msgstr "Sturm" msgid "Skull Pile" msgstr "Schädelhaufen" msgid "Excavation" msgstr "Ausgrabung" msgid "Graveyard" msgstr "Friedhof" msgid "Upg. Graveyard" msgstr "Aufg. Friedhof" msgid "Upg. Pyramid" msgstr "Aufg. Pyramide" msgid "Mansion" msgstr "Spukhaus" msgid "Upg. Mansion" msgstr "Aufg. Spukhaus" msgid "Mausoleum" msgstr "Mausoleum" msgid "Upg. Mausoleum" msgstr "Aufg. Mausoleum" msgid "Laboratory" msgstr "Laboratorium" msgid "Shrine" msgstr "Schrein" msgid "" "The Fortifications increase the toughness of the walls, increasing the " "number of turns it takes to knock them down." msgstr "" "Die Befestigungen erhöhen die Stärke der Mauern, und es dauert länger die " "Mauern zu durchbrechen." msgid "The Farm increases production of Peasants by %{count} per week." msgstr "Die Farm erhöht die Produktion von Bauern um %{count} pro Woche." msgid "" "The Coliseum provides inspiring spectacles to defending troops, raising " "their morale by two during combat." msgstr "" "Das Kolosseum bietet den verteidigenden Truppen ein inspirierendes Spektakel " "und hebt ihre Moral während des Kampfes um zwei." msgid "The Garbage Heap increases production of Goblins by %{count} per week." msgstr "" "Der Müllhaufen erhöht die Produktion von Goblins um %{count} pro Woche." msgid "The Rainbow increases the luck of the defending units by two." msgstr "Der Regenbogen erhöht das Glück der verteidigenden Einheiten um zwei." msgid "" "The Crystal Garden increases production of Sprites by %{count} per week." msgstr "" "Der Kristallgarten erhöht die Produktion von Feen um %{count} pro Woche." msgid "The Dungeon increases the income of the town by %{count} gold per day." msgstr "" "Das Burgverlies erhöht das Einkommen der Stadt um %{count} Gold pro Tag." msgid "The Waterfall increases production of Centaurs by %{count} per week." msgstr "" "Der Wasserfall erhöht die Produktion von Zentauren um %{count} pro Woche." msgid "" "The Library increases the number of spells in the Guild by one for each " "level of the guild." msgstr "" "Die Bibliothek erhöht die Anzahl der Zaubersprüche in der Gilde um einen für " "jede Stufe der Gilde." msgid "The Orchard increases production of Halflings by %{count} per week." msgstr "" "Der Obstgarten erhöht die Produktion von Halblingen um %{count} pro Woche." msgid "The Storm adds +2 to the power of spells of a defending spell caster." msgstr "" "Der Sturm fügt der Zauberkraft eines verteidigenden Zauberwirkers +2 hinzu." msgid "The Skull Pile increases production of Skeletons by %{count} per week." msgstr "" "Der Schädelhaufen erhöht die Produktion von Skeletten um %{count} pro Woche." msgid "Thieves' Guild" msgstr "Diebesgilde" msgid "Tavern" msgstr "Taverne" msgid "Shipyard" msgstr "Schiffswerft" msgid "Well" msgstr "Brunnen" msgid "Statue" msgstr "Statue" msgid "Marketplace" msgstr "Marktplatz" msgid "Moat" msgstr "Burggraben" msgid "Castle" msgstr "Burg" msgid "Tent" msgstr "Zelt" msgid "Captain's Quarters" msgstr "Hauptmannsquartier" msgid "Mage Guild, Level 1" msgstr "Magiergilde, Stufe 1" msgid "Mage Guild, Level 2" msgstr "Magiergilde, Stufe 2" msgid "Mage Guild, Level 3" msgstr "Magiergilde, Stufe 3" msgid "Mage Guild, Level 4" msgstr "Magiergilde, Stufe 4" msgid "Mage Guild, Level 5" msgstr "Magiergilde, Stufe 5" msgid "" "The Shrine increases the necromancy skill of all your necromancers by 10 " "percent." msgstr "" "Der Schrein erhöht die Nekromantiefertigkeit aller Eurer Nekromanten um 10 " "Prozent." msgid "" "The Thieves' Guild provides information on enemy players. Thieves' Guilds " "can also provide scouting information on enemy towns. Additional Guilds " "provide more information." msgstr "" "Die Diebesgilde liefert Informationen über gegnerische Spieler. Die " "Diebesgilde kann auch Aufklärungsinformationen über gegnerische Städte " "liefern. Zusätzliche Diebesgilden liefern mehr Informationen." msgid "The Tavern increases morale for troops defending the castle." msgstr "Die Taverne erhöht die Moral der Truppen, die die Burg verteidigen." msgid "The Shipyard allows ships to be built." msgstr "In der Schiffswerft können Boote gebaut werden." msgid "" "The Well increases the growth rate of all dwellings by %{count} creatures " "per week." msgstr "" "Der Brunnen erhöht die Wachstumsrate aller Behausungen um %{count} Lebewesen " "pro Woche." #, fuzzy msgid "The Statue increases the town's income by %{count} gold per day." msgstr "Die Statue erhöht das Einkommen Eurer Stadt um %{count} Gold pro Tag." msgid "The Left Turret provides extra firepower during castle combat." msgstr "" "Der linker Geschützturm bietet zusätzliche Feuerkraft während der Belagerung." msgid "The Right Turret provides extra firepower during castle combat." msgstr "" "Der rechter Geschützturm bietet zusätzliche Feuerkraft während der " "Belagerung." msgid "" "The Marketplace can be used to convert one type of resource into another. " "The more marketplaces you control, the better the exchange rate." msgstr "" "Der Marktplatz kann dazu verwendet werden, eine Art von Ressource in eine " "andere umzuwandeln. Je mehr Marktplätze Ihr kontrolliert, desto besser ist " "der Umtauschkurs." msgid "" "The Moat slows attacking units. Any unit entering the moat must end its turn " "there and becomes more vulnerable to attack." msgstr "" "Der Burggraben verlangsamt angreifende Einheiten. Jede Einheit, die den " "Graben betritt, muss ihren Zug dort beenden und wird für Angriffe anfälliger." #, fuzzy msgid "" "The Castle improves the town's defense and increases its income to %{count} " "gold per day." msgstr "" "Die Burg verbessert die Stadtverteidigung und erhöht das Einkommen auf " "%{count} Gold pro Tag." msgid "" "The Tent provides workers to build a castle, provided the materials and the " "gold are available." msgstr "" "Das Zelt stellt Arbeiter zur Verfügung, um eine Burg zu bauen, sofern die " "Rohstoffe und das Gold vorhanden sind." msgid "" "The Captain's Quarters provides a captain to assist in the castle's defense " "when no hero is present." msgstr "" "Das Hauptmannsquartier stellt den Hauptmann zur Verfügung, der bei der " "Verteidigung der Burg hilft, wenn kein Held anwesend ist." msgid "" "The Mage Guild allows heroes to learn spells and replenish their spell " "points." msgstr "" "Die Magiergilde ermöglicht es Helden, Zaubersprüche zu erlernen und ihre " "Zauberpunkte wieder aufzufüllen." msgid "Recruit %{name}" msgstr "%{name} rekrutieren" msgid "Month: %{month}, Week: %{week}, Day: %{day}" msgstr "Monat: %{month}, Woche: %{week}, Tag: %{day}" msgid "" "You must purchase a spell book to use the mage guild, but you currently have " "no room for a spell book. Try giving one of your artifacts to another hero." msgstr "" "Ihr müsst ein Zauberbuch kaufen, um die Magiergilde zu nutzen, aber Ihr habt " "derzeit keinen Platz für ein Zauberbuch. Versuchet, eines Eurer Artefakte " "einem anderen Helden zu übergeben." msgid "Click to show next town." msgstr "Klicken um die nächste Stadt anzuzeigen." msgid "Show next town" msgstr "Nächste Stadt anzeigen" msgid "Click to show previous town." msgstr "Klicken um die vorherige Stadt anzuzeigen." msgid "Show previous town" msgstr "Vorherige Stadt anzeigen" msgid "This town may not be upgraded to a castle." msgstr "Diese Stadt darf nicht zu einer Burg ausgebaut werden." msgid "Town" msgstr "Stadt" msgid "Exit Castle" msgstr "Burg verlassen" msgid "Exit Town" msgstr "Stadt verlassen" msgid "Show Income" msgstr "Einkommen anzeigen" msgid "View Hero" msgstr "Helden anzeigen" msgid "The above spells are available here." msgstr "Die oben genannten Zaubersprüche sind hier verfügbar." msgid "The spells the hero can learn have been added to their book." msgstr "Diese Zaubersprüche wurden Eurem Zauberbuch hinzugefügt." msgid "A generous tip for the barkeep yields the following rumor:" msgstr "" "Ein großzügiges Trinkgeld für den Barkeeper führt zu dem folgenden Gerücht:" msgid "Recruit Hero" msgstr "Held rekrutieren" msgid "%{name} is a level %{value} %{race} " msgstr "%{name} ist ein Erfahrungsstufe %{value} %{race} " msgid "with %{count} artifacts." msgstr "mit %{count} Artefakten." msgid "with 1 artifact." msgstr "mit 1 Artefakt." msgid "without artifacts." msgstr "ohne Artefakte." msgid "Recruit %{name} the %{race}" msgstr "%{name} (%{race}) rekrutieren" #, fuzzy msgid "" "'Spread' combat formation spreads the castle's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "Die \"Verstreute\" Kampfformation verteilt die Armeen von oben bis unten " "über das Schlachtfeld, mit wenigstens einem Freiraum zwischen jeder Armee." #, fuzzy msgid "" "'Grouped' combat formation bunches the castle's units together in the center " "of the castle's side of the battlefield." msgstr "" "Die 'Gruppierte' Kampfformation bündelt Eure Armee in der Mitte Eurer Seite " "des Schlachtfelds." msgid "Spread Formation" msgstr "Verstreute Formation" msgid "Grouped Formation" msgstr "Gruppierte Formation" msgid "Set garrison combat formation to 'Spread'" msgstr "Garnisonskampfformation auf \"Ausbreitend\" einstellen" msgid "Set garrison combat formation to 'Grouped'" msgstr "Garnisonskampfformation auf \"Gruppiert\" einstellen" msgid "Exit Castle Options" msgstr "Burg Optionen verlassen" msgid "Castle Options" msgstr "Burg Optionen" msgid "Not enough resources to recruit creatures." msgstr "Nicht genügend Ressourcen, um Kreaturen zu rekrutieren." msgid "You are unable to recruit at this time, your ranks are full." msgstr "Ihr könnt zur Zeit nicht rekrutieren, Eure Ränge sind voll." msgid "No creatures available for purchase." msgstr "Keine Kreatur zum Kauf verfügbar." msgid "Recruit Creatures" msgstr "Kreaturen rekrutieren" msgid "Max" msgstr "Max" msgid "Hire all creatures in the town." msgstr "Alle Kreaturen in der Stadt rekrutieren." msgid "Available" msgstr "Verfügbar" msgid "Town Population Information and Statistics" msgstr "Informationen und Statistiken zur Bevölkerung der Stadt" msgid "Damage" msgstr "Schaden" msgid "HP" msgstr "TP" msgid "Growth" msgstr "Wachstum" msgid "week" msgstr "Woche" msgid "View World" msgstr "Alles anzeigen" msgid "View the entire world." msgstr "Die ganze Welt sehen." msgid "Puzzle" msgstr "Puzzle" msgid "View the obelisk puzzle." msgstr "Das Obelisken-Puzzle ansehen." msgid "Scenario Information" msgstr "Informationen zum Szenario" msgid "View information on the scenario you are currently playing." msgstr "Informationen anzeigen über das Szenario, das gerade gespielt wird." msgid "Dig for the Ultimate Artifact." msgstr "Nach dem ultimativen Artefakt graben." msgid "Digging" msgstr "Graben" msgid "Arena" msgstr "Arena" msgid "" "You enter the arena and face a pack of vicious lions. You handily defeat " "them, to the wild cheers of the crowd. Impressed by your skill, the aged " "trainer of gladiators agrees to train you in a skill of your choice." msgstr "" "Ihr betretet die Arena und stehet einem Rudel bösartiger Löwen gegenüber. " "Unter jubelndem Beifall der Menge besiegt Ihr sie mit Leichtigkeit. " "Beeindruckt von Euren Fähigkeiten, erklärt sich der alte Gladiatorentrainer " "bereit, Euch in einer Fähigkeit Eurer Wahl zu unterrichten." msgid "Attack Skill" msgstr "Angriff" msgid "Defense Skill" msgstr "Verteidigung" msgid "Shots" msgstr "Schüsse" msgid "Shots Left" msgstr "Schüsse übrig" msgid "Hit Points" msgstr "Trefferpunkte" msgid "Hit Points Left" msgstr "TP übrig" msgid "You can't afford to upgrade your troops!" msgstr "Ihr könnt es Euch nicht leisten, Eure Truppen aufzurüsten!" msgid "" "Your troops can be upgraded, but it will cost you dearly. Do you wish to " "upgrade them?" msgstr "" "Eure Truppen können aufgerüstet werden, aber das wird Euch eine Menge " "kosten. Wollt Ihr sie aufrüsten?" msgid "Are you sure you want to dismiss this army?" msgstr "Seid Ihr sicher, dass Ihr diese Armee entlassen wollt?" msgid "Upgrade" msgstr "Aufrüsten" msgid "Upgrade your troops." msgstr "Aufrüsten der Truppen." msgid "Dismiss" msgstr "Entlassen" msgid "Dismiss this army." msgstr "Entlassen der Armee." msgid "" "A group of %{monster} with a desire for greater glory wish to join you.\n" "Do you accept?" msgstr "" "Eine Gruppe %{monster} mit dem Wunsch nach größerem Ruhm, möchte sich Euch " "anschließen.\n" "Nehmt Ihr an?" msgid "Followers" msgstr "Anhänger" msgid "" "The %{monster} is swayed by your diplomatic tongue, and offers to join your " "army for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "Der %{monster} lässt sich von Eurem diplomatischen Geschick überreden und " "bietet Euch an, sich Eurer Armee für %{gold} Goldstücke anzuschließen.\n" "Nehmt Ihr an?" msgid "" "The creatures are swayed by your diplomatic\n" "tongue, and make you an offer:\n" "\n" msgstr "" "Die Kreaturen lassen sich von Eurem diplomatischen\n" "Geschick überzeugen und machen Euch ein Angebot:\n" "\n" msgid "" "%{offer} of the %{total} %{monster} will join your army, and the rest will " "leave you alone, for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "%{offer} von %{total} %{monster} werden sich Eurer Armee anschließen, und " "der Rest wird Euch in Ruhe lassen, für %{gold} Goldstücke.\n" "Nehmt Ihr an?" msgid "" "All %{offer} of the %{monster} will join your army for the sum of %{gold} " "gold.\n" "Do you accept?" msgstr "" "Alle %{offer} der %{monster} werden sich Eurer Armee für %{gold} Goldstücke " "anschließen.\n" "Nehmt Ihr an?" msgid "(Rate: %{percent})" msgstr "(Grad: %{percent})" msgid "off" msgstr "Aus" msgid "Music" msgstr "Musik" msgid "Effects" msgstr "Effekte" msgid "MIDI" msgstr "MIDI" msgid "MIDI Expansion" msgstr "MIDI Expansion" msgid "External" msgstr "Extern" msgid "Music Type" msgstr "Klangtyp" msgid "3D Audio" msgstr "3D Audio" msgid "Toggle ambient music level." msgstr "Lautstärke der Hintergrundmusik umschalten." msgid "Toggle foreground sounds level." msgstr "Lautstärke der Vordergrundgeräusche umschalten." msgid "Change the type of music." msgstr "Klangtyp ändern." msgid "Toggle 3D effects of foreground sounds." msgstr "3D-Effekte der Vordergrundgeräusche umschalten." msgid "Build a new ship:" msgstr "Ein neues Boot bauen:" msgid "Resource cost:" msgstr "Ressourcenkosten:" msgid "Total: " msgstr "Insgesamt: " msgid "Need: " msgstr "Bedarf: " msgid "Load Game" msgstr "Spiel laden" msgid "No save files to load." msgstr "Keine Speicherdateien vorhanden." msgid "New Game" msgstr "Neues Spiel" msgid "Start a single or multi-player game." msgstr "Einzel- oder Multi-Player Spiel starten." msgid "Load a previously saved game." msgstr "Zuvor gespeichertes Spiel laden." msgid "Save Game" msgstr "Spiel speichern" msgid "Save the current game." msgstr "Das aktuelle Spiel speichern." msgid "Quit" msgstr "Beenden" msgid "Quit out of Heroes of Might and Magic II." msgstr "Heroes of Might and Magic II beenden." msgid "Language" msgstr "Sprache" msgid "Graphics" msgstr "Grafik" msgid "Black & White" msgstr "Schwarz & Weiß" msgid "Mouse Cursor" msgstr "Mauszeiger" msgid "Color" msgstr "Farbe" msgid "Text Support" msgstr "Text-Unterst." msgid "Change the language of the game." msgstr "Sprache des Spiels ändern." msgid "Select Game Language" msgstr "Spielsprache auswählen" msgid "Change the graphics settings of the game." msgstr "Grafikeinstellungen des Spiels ändern." msgid "Toggle colored cursor on or off. This is only an aesthetic choice." msgstr "" "Farbigen Cursor ein- oder ausschalten. Dies ist nur eine ästhetische " "Entscheidung." msgid "" "Toggle text support mode to output extra information about windows and " "events in the game." msgstr "" "Textunterstützungsmodus umschalten, um zusätzliche Informationen über " "Fenster und Ereignisse im Spiel auszugeben." msgid "" "Map\n" "Difficulty" msgstr "" "Karten-\n" "grad" msgid "" "Game\n" "Difficulty" msgstr "" "Spiel-\n" "grad" msgid "Rating" msgstr "Grad" msgid "Map Size" msgstr "" "Karten-\n" "größe" msgid "Opponents" msgstr "Gegner" msgid "Class" msgstr "Klasse" msgid "" "Victory\n" "Conditions" msgstr "" "Sieg-\n" "bedingungen" msgid "" "Loss\n" "Conditions" msgstr "" "Verlust-\n" "bedingungen" msgid "First select recipients!" msgstr "Zuerst die Empfänger auswählen!" msgid "You cannot select %{resource}!" msgstr "%{resource} nicht ausgewählt werden!" msgid "Select count %{resource}:" msgstr "Anzahl %{resource} wählen:" msgid "Select Recipients" msgstr "Empfänger auswählen" msgid "Your Funds" msgstr "Eure Kasse" msgid "Planned Gift" msgstr "Geplantes Geschenk" msgid "Gift from %{name}" msgstr "Geschenk von %{name}" msgid "Resolution" msgstr "Auflösung" msgid "Fullscreen" msgstr "Vollbild" msgid "window|Mode" msgstr "Modus" msgid "Windowed" msgstr "Mit Fenster" msgid "V-Sync" msgstr "V-Sync" msgid "on" msgstr "ein" msgid "FPS" msgstr "FPS" msgid "System Info" msgstr "System-Information" msgid "Change the resolution of the game." msgstr "Auflösung des Spiels ändern." msgid "Select Game Resolution" msgstr "Spielauflösung wählen" msgid "Toggle between fullscreen and windowed modes." msgstr "Umschalten zwischen Vollbildmodus und Fenstermodus." msgid "" "The V-Sync option can be enabled to resolve flickering issues on some " "monitors." msgstr "" "Die Option V-Sync kann aktiviert werden, um Flackerprobleme auf einigen " "Monitoren zu beheben." msgid "Show extra information such as FPS and current time." msgstr "" "Zusätzliche Informationen anzeigen, wie die FPS und die aktuelle Zeit an." msgid "Hotkey: " msgstr "" msgid "Category: " msgstr "" #, fuzzy msgid "Event: " msgstr "Ereignis" msgid "Hot Keys:" msgstr "Hotkeys:" msgid "Evil" msgstr "Böse" msgid "Good" msgstr "Gut" msgid "Interface Type" msgstr "Interface" msgid "Hide" msgstr "Ausblenden" msgid "Show" msgstr "Einblenden" msgid "Interface" msgstr "Interface" msgid "Slow" msgstr "Langsam" msgid "Normal" msgstr "Normal" msgid "Fast" msgstr "Schnell" msgid "Very Fast" msgstr "Sehr Schnell" msgid "Scroll Speed" msgstr "Scrollgeschw." msgid "Toggle the type of interface you want to use." msgstr "Art der Schnittstelle umschalten, die verwenden soll." msgid "Toggle interface visibility." msgstr "Sichtbarkeit der Interface ändern." msgid "Toggle colored cursor on or off. This is only an esthetic choice." msgstr "" "Farbigen Cursor ein- oder ausschalten. Dies ist nur eine ästhetische " "Entscheidung." msgid "Sets the speed at which you scroll the window." msgstr "Geschwindigkeit festlegen, mit der das Fenster verschoben wird." msgid "Select Game Language:" msgstr "Spielsprache wählen:" msgid "Click to choose the selected language." msgstr "Klicken um die ausgewählte Sprache auszuwählen." msgid "%{name} has gained a level." msgstr "%{name} hat eine neue Stufe erreicht." msgid "%{skill} +1" msgstr "%{skill} +1" msgid "You have learned %{skill}." msgstr "Ihr habt %{skill} gelernt." msgid "" "%{name} has gained a level.\n" "\n" "%{skill} +1" msgstr "" "%{name} hat eine neue Stufe erreicht.\n" "\n" "%{skill} +1" msgid "" "You may learn either:\n" "%{skill1}\n" "or\n" "%{skill2}" msgstr "" "Ihr könnt lernen entweder:\n" "%{skill1}\n" "oder\n" "%{skill2}" msgid "" "Please inspect our fine wares. If you feel like offering a trade, click on " "the items you wish to trade with and for." msgstr "" "Bitte seht Euch unsere feinen Waren an. Wenn Ihr einen Handel anbieten " "möchtet, so klickt auf die Gegenstände, mit denen Ihr handeln wollt." msgid "" "You have received quite a bargain. I expect to make no profit on the deal. " "Can I interest you in any of my other wares?" msgstr "" "Ihr habt ein ganz schönes Schnäppchen gemacht. Ich werde wohl nichts daran " "verdient haben. Kann ich Euch an anderen meiner Waren interessieren?" msgid "I can offer you %{count} for 1 unit of %{resfrom}." msgstr "Ich kann Euch %{count} für 1 Einheit von %{resfrom} anbieten." msgid "I can offer you 1 unit of %{resto} for %{count} units of %{resfrom}." msgstr "" "Ich kann Euch 1 Einheit von %{resto} für %{count} Einheiten von %{resfrom} " "anbieten." msgid "Min" msgstr "Min" msgid "Qty to trade" msgstr "Menge zu handeln" msgid "Trading Post" msgstr "Handelsposten" msgid "Your Resources" msgstr "Eure Ressourcen" msgid "Available Trades" msgstr "Verfügbarer Handel" msgid "n/a" msgstr "n/a" msgid "guarded by %{count} %{monster}" msgstr "bewacht von %{count} %{monster}" msgid "guarded by " msgstr "beschützt von " msgid "(available: %{count})" msgstr "(vorhanden: %{count})" msgid "(empty)" msgstr "(leer)" msgid "already learned" msgstr "schon gelernt" #, fuzzy msgid "treeOfKnowledge|free" msgstr "Baum des Wissens" #, fuzzy msgid "already claimed" msgstr "schon gelernt" msgid "not claimed" msgstr "" msgid "already knows this skill" msgstr "kennt diese Fähigkeit schon" msgid "already has max skills" msgstr "hat bereits maximale Fähigkeiten" msgid "(already visited)" msgstr "(schon besucht)" msgid "(not visited)" msgstr "(nicht besucht)" msgid "%{color} Barrier" msgstr "%{color} Barriere" #, fuzzy msgid "(tent visited)" msgstr "(nicht besucht)" msgid "%{color} Tent" msgstr "%{color} Zelt" msgid "Road" msgstr "Weg" msgid "(digging ok)" msgstr "(graben möglich)" msgid "(no digging)" msgstr "(graben unmöglich)" msgid "penalty: %{cost}" msgstr "Strafe: %{cost}" msgid "Defenders:" msgstr "Verteidiger:" msgid "Unknown" msgstr "Unbekannt" msgid "%{name} (Level %{level})" msgstr "%{name} (Stufe %{level})" msgid "Attack:" msgstr "Angriff:" msgid "Defense:" msgstr "Verteidigung:" msgid "Spell Power:" msgstr "Zauberkraft:" msgid "Knowledge:" msgstr "Wissen:" msgid "Spell Points:" msgstr "Zauberpunkte:" msgid "Move Points:" msgstr "Bewegungspunkte:" msgid "Uncharted Territory" msgstr "Unerforschtes Land" msgid "Cost per troop:" msgstr "Kosten pro Truppe:" msgid "Available: %{count}" msgstr "Vorhanden: %{count}" msgid "Number to buy:" msgstr "Anzahl:" msgid "Recruit selected monsters." msgstr "Ausgewählte Monster rekrutieren." msgid "Select maximum monsters to be recruited." msgstr "Maximale Anzahl an zu rekrutierenden Monstern wählen." msgid "Select only 1 monster to be recruited." msgstr "Nur 1 Monster auswählen, das rekrutiert werden soll." msgid "Select Game Resolution:" msgstr "Spielauflösung wählen:" msgid "Click to apply the selected resolution." msgstr "Klicken um die ausgewählte Auflösung anzuwenden." msgid "Click to apply the entered text." msgstr "Klicken zur Übernahme des eingegebenen Textes." msgid "Click to open the Virtual Keyboard dialog." msgstr "Hier klicken, um die virtuelle Tastatur zu öffnen." msgid "Open Virtual Keyboard" msgstr "Virtuelle Tastatur öffnen" msgid "How many troops to move?" msgstr "Wie viele Truppen sollen verlegt werden?" msgid "Fast separation into slots:" msgstr "Schnelle Aufteilung:" msgid "Map: " msgstr "Karte: " msgid "" "\n" "\n" "Month: " msgstr "" "\n" "\n" "Monat: " msgid ", Week: " msgstr ", Woche: " msgid ", Day: " msgstr ", Tag: " msgid "" "\n" "\n" "Location: " msgstr "" "\n" "Ort: " msgid "Click to save the current game." msgstr "Klicken um das aktuelle Spiel zu speichern." msgid "Click to load a previously saved game." msgstr "Klicken um ein zuvor gespeichertes Spiel zu laden." msgid "Are you sure you want to delete file:" msgstr "Seid Ihr sicher, dass Ihr die Datei löschen möchten:" msgid "Warning!" msgstr "Achtung!" msgid "File to Save:" msgstr "Datei zum Speichern:" msgid "File to Load:" msgstr "Datei zum Laden:" msgid "Accept the choice made." msgstr "Die getroffene Wahl bestätigen." #, fuzzy msgid "%{color} %{race} hero" msgstr "Spieler %{color}" #, fuzzy msgid "Terrain object" msgstr "Gelände-Modus" msgid "Select Skill:" msgstr "Fähigkeit auswählen:" msgid "Select Spell:" msgstr "Zauberspruch auswählen:" msgid "Select Artifact:" msgstr "Artefakt auswählen:" msgid "Select Monster:" msgstr "Monster auswählen:" msgid "Select Hero:" msgstr "Held auswählen:" #, fuzzy msgid "Select Treasure:" msgstr "Held auswählen:" #, fuzzy msgid "Select Ocean Object:" msgstr "Ozean-Objekte" #, fuzzy msgid "Castle/town placing" msgstr "Burg Optionen" msgid "doubleLinedRace|Neutral" msgstr "Neutral" msgid "race|Random" msgstr "Zufällig" #, fuzzy msgid "Click to start placing the selected castle/town." msgstr "Klicken um die ausgewählte Auflösung anzuwenden." #, fuzzy msgid "Click to select town placing." msgstr "Klicken um die nächste Stadt anzuzeigen." #, fuzzy msgid "Click to select castle placing." msgstr "Hier klicken, um auszuwählen, welches Szenario gespielt werden soll." #, fuzzy msgid "%{color} %{race} %{townOrCastle}" msgstr "Spieler %{color}" msgid "race|Neutral" msgstr "Neutral" msgid "You will place" msgstr "" #, fuzzy msgid "Click to select this class." msgstr "Hier klicken, um auszuwählen, welches Szenario gespielt werden soll." #, fuzzy msgid "Click to select this color." msgstr "Hier klicken, um auszuwählen, welches Szenario gespielt werden soll." #, fuzzy msgid "Select Dwelling:" msgstr "Zauberspruch auswählen:" #, fuzzy msgid "Select Landscape Object:" msgstr "Ozean-Objekte" msgid "Mine placing" msgstr "" #, fuzzy msgid "" "Resource\n" "type:" msgstr "Ressource" #, fuzzy msgid "%{mineName} appearance:" msgstr "%{name} (%{race})" #, fuzzy msgid "Click to select %{object} as the resource generator to be placed." msgstr "Hier klicken, um auszuwählen, welches Szenario gespielt werden soll." #, fuzzy msgid "Select Mountain Object:" msgstr "Ozean-Objekte" #, fuzzy msgid "Select Rock Object:" msgstr "Ozean-Objekte" #, fuzzy msgid "Select Tree Object:" msgstr "Ozean-Objekte" #, fuzzy msgid "Select Power Up Object:" msgstr "Ozean-Objekte" #, fuzzy msgid "Select Adventure Object:" msgstr "Ozean-Objekte" msgid "Map Type:\n" msgstr "Kartentyp:\n" msgid "The Succession Wars" msgstr "The Succession Wars" msgid "The Price of Loyalty" msgstr "The Price of Loyalty" #, fuzzy msgid "Resurrection" msgstr "Auferstehung" msgid "Lose all your heroes and towns." msgstr "Alle Helden und Städte verlieren." msgid "Lose a specific town." msgstr "Eine bestimmte Stadt verlieren." msgid "Lose a specific hero." msgstr "Einen bestimmten Helden verlieren." msgid "Run out of time. Fail to win by a certain point." msgstr "Keine Zeit mehr haben. Nicht bis zu einem bestimmten Punkt gewinnen." msgid "Loss Condition" msgstr "Verlustbedingungen" msgid "Defeat all enemy heroes and towns." msgstr "Alle feindlichen Helden und Städte besiegen." msgid "Capture a specific town." msgstr "Eine bestimmte Stadt einnehmen." msgid "Defeat a specific hero." msgstr "Einen bestimmten Helden besiegen." msgid "Find a specific artifact." msgstr "Ein bestimmtes Artefakt finden." msgid "Your side defeats the opposing side." msgstr "Eurer Seite besiegt die gegnerische Seite." msgid "Accumulate a large amount of gold." msgstr "Eine große Menge an Gold ansammeln." msgid "Victory Condition" msgstr "Siegbedingung" msgid "Map difficulty:" msgstr "Kartenschwierigkeit:" msgid "N" msgstr "N" msgid "No maps exist at that size" msgstr "Es gibt keine Karten in dieser Größe" msgid "Small Maps" msgstr "Kleine Karten" msgid "View only maps of size small (36 x 36)." msgstr "Nur kleine Karten (36 x 36) anzeigen." msgid "Medium Maps" msgstr "Mittelgroße Karten" msgid "View only maps of size medium (72 x 72)." msgstr "Nur mittelgroße Karten (72 x 72) anzeigen." msgid "Large Maps" msgstr "Große Karten" msgid "View only maps of size large (108 x 108)." msgstr "Nur große Karten (108 x 108) anzeigen." msgid "Extra Large Maps" msgstr "Extragrosse Karten" msgid "View only maps of size extra large (144 x 144)." msgstr "Nur extragroße Karten (144 x 144) anzeigen." msgid "All Maps" msgstr "Alle Karten" msgid "View all maps, regardless of size." msgstr "Alle Maps anzeigen, unabhängig von der Größe." msgid "Players Icon" msgstr "Spieleranzahl" msgid "" "Indicates how many players total are in the scenario. Any positions not " "occupied by humans will be occupied by computer players." msgstr "" "Gibt an, wie viele Spieler insgesamt im Szenario sind. Alle Positionen, die " "nicht von Menschen besetzt sind, werden von Computerspielern besetzt." msgid "" "Indicates whether the map\n" "is small (36 x 36), medium\n" "(72 x 72), large (108 x 108),\n" "or extra large (144 x 144)." msgstr "" "Zeigt an, ob die Karte\n" "klein (36 x 36), mittelgroß\n" "(72 x 72), groß (108 x 108)\n" "oder extragroß (144 x 144) ist." msgid "Size Icon" msgstr "Kartengröße" msgid "" "Indicates whether the map is made for \"The Succession Wars\" or \"The Price " "of Loyalty\" version of the game." msgstr "" "Zeigt an, ob die Karte für die Version \"The Succession Wars\" oder \"The " "Price of Loyalty\" des Spiels erstellt wurde." msgid "Map Type" msgstr "Kartenart" msgid "Selected Name" msgstr "Ausgewählter Name" msgid "The name of the currently selected map." msgstr "Der Name der aktuell ausgewählten Karte." msgid "Selected Map Difficulty" msgstr "Ausgewählter Schwierigkeitsgrad" msgid "" "The map difficulty of the currently selected map. The map difficulty is " "determined by the scenario designer. More difficult maps might include more " "or stronger enemies, fewer resources, or other special conditions making " "things tougher for the human player." msgstr "" "Der Schwierigkeitsgrad der aktuell ausgewählten Karte. Die " "Kartenschwierigkeit wird vom Szenario-Ersteller festgelegt. Schwierigere " "Karten können mehr oder stärkere Feinde, weniger Ressourcen oder andere " "spezielle Bedingungen enthalten, die die Dinge für den menschlichen Spieler " "schwieriger machen." msgid "Selected Description" msgstr "Ausgewählte Beschreibung" msgid "The description of the currently selected map." msgstr "Die Beschreibung der aktuell ausgewählten Karte." msgid "Jump" msgstr "Sprung" msgid "Hero Speed" msgstr "Heldgeschw." msgid "Don't Show" msgstr "Nicht anzeigen" msgid "Enemy Speed" msgstr "Feindgeschw." msgid "Auto Resolve" msgstr "Auto-Auflösen" msgid "Auto, No Spells" msgstr "Auto, ohne Zauber" msgid "Battles" msgstr "Schlachten" msgid "autoBattle|Manual" msgstr "Manuell" msgid "Change the speed at which your heroes move on the main screen." msgstr "" "Ändert die Geschwindigkeit, mit der sich Euer Helden auf dem Hauptbildschirm " "bewegen." msgid "" "Sets the speed that A.I. heroes move at. You can also elect not to view A.I. " "movement at all." msgstr "" "Legt die Geschwindigkeit fest, mit der sich die KI-Helden bewegen. Ihr könnt " "auch festlegen, dass die KI-Bewegung überhaupt nicht angezeigt wird." msgid "Change the interface settings of the game." msgstr "Die Grafikeinstellungen des Spiels ändern." #, fuzzy msgid "Interface Settings" msgstr "Spieleinstellungen" msgid "Toggle instant battle mode." msgstr "Sofortigen Kampfmodus umschalten." msgid "Att." msgstr "Angr." msgid "Def." msgstr "Vert." msgid "Power" msgstr "Kraft" msgid "Knowl" msgstr "Wissen" msgid "1st" msgstr "1." msgid "2nd" msgstr "2." msgid "3rd" msgstr "3." msgid "4th" msgstr "4." msgid "5th" msgstr "5." msgid "6th" msgstr "6." msgid "Oracle: Player Rankings" msgstr "Orakel: Spieler-Ränge" msgid "Thieves' Guild: Player Rankings" msgstr "Diebesgilde: Spieler-Ränge" msgid "Number of Towns:" msgstr "Anzahl der Städte:" msgid "Number of Castles:" msgstr "Anzahl der Burgen:" msgid "Number of Heroes:" msgstr "Anzahl der Helden:" msgid "Gold in Treasury:" msgstr "Gold in Schatzkammer:" msgid "Wood & Ore:" msgstr "Holz & Erz:" msgid "Gems, Cr, Slf & Mer:" msgstr "Andere Rohstoffe:" msgid "Obelisks Found:" msgstr "Obelisken gefunden:" msgid "Artifacts:" msgstr "Artefakte:" msgid "Total Army Strength:" msgstr "Gesamte Armeestärke:" msgid "Income:" msgstr "Einkommen:" msgid "Best Hero:" msgstr "Bester Held:" msgid "Best Hero Stats:" msgstr "Status bester Held:" msgid "Personality:" msgstr "Person:" msgid "Best Monster:" msgstr "Bestes Monster:" #, fuzzy msgid "Map filename" msgstr "" "Karten-\n" "größe" msgid "" "Are you sure you want to load a new map? (Any unsaved changes to the current " "map will be lost.)" msgstr "" "Wollt Ihr wirklich eine neue Karte laden? (Nicht gespeicherte Änderungen an " "der aktuellen Karte gehen verloren)." msgid "" "Are you sure you want to create a new map? (Any unsaved changes to the " "current map will be lost.)" msgstr "" "Seid Ihr sicher, dass Ihr eine neue Karte erstellen wollt? (Alle nicht " "gespeicherten Änderungen an der aktuellen Karte gehen verloren)." msgid "" "Create a new map, either from scratch or using the random map generator." msgstr "" "Eine neue Karte erstellen, entweder von Grund auf oder mit Hilfe des " "Zufallsgenerators." msgid "New Map" msgstr "Neue Karte" msgid "Load Map" msgstr "Karte laden" msgid "Load an existing map." msgstr "Eine vorhandene Karte laden." msgid "Save Map" msgstr "Karte speichern" msgid "Save the current map." msgstr "Speichern der aktuellen Karte." msgid "Quit out of the map editor." msgstr "Den Karteneditor verlassen." #, fuzzy msgid "Objects cannot be placed outside the map." msgstr "Monster können nicht auf Wasser plaziert werden." msgid "Monsters cannot be placed on water." msgstr "Monster können nicht auf Wasser plaziert werden." msgid "Choose a tile which does not contain any objects." msgstr "Ein Feld wählen, das keine Objekt enthält." #, fuzzy msgid "Treasures cannot be placed on water." msgstr "Monster können nicht auf Wasser plaziert werden." msgid "Heroes cannot be placed on water." msgstr "" msgid "" "A maximum of %{count} heroes of the same color can be placed on the map." msgstr "" #, fuzzy msgid "Artifacts cannot be placed on water." msgstr "Monster können nicht auf Wasser plaziert werden." #, fuzzy msgid "Mountains cannot be placed on water." msgstr "Monster können nicht auf Wasser plaziert werden." #, fuzzy msgid "Rocks cannot be placed on water." msgstr "Monster können nicht auf Wasser plaziert werden." #, fuzzy msgid "Trees cannot be placed on water." msgstr "Monster können nicht auf Wasser plaziert werden." #, fuzzy msgid "Ocean object must be placed on water." msgstr "Monster können nicht auf Wasser plaziert werden." #, fuzzy msgid "Landscape objects cannot be placed on water." msgstr "Monster können nicht auf Wasser plaziert werden." #, fuzzy msgid "Towns cannot be placed on water." msgstr "Monster können nicht auf Wasser plaziert werden." #, fuzzy msgid "Mines cannot be placed on water." msgstr "Monster können nicht auf Wasser plaziert werden." #, fuzzy msgid "Dwellings cannot be placed on water." msgstr "Monster können nicht auf Wasser plaziert werden." #, fuzzy msgid "Power-ups cannot be placed on water." msgstr "Monster können nicht auf Wasser plaziert werden." #, fuzzy msgid "Adventure objects cannot be placed on water." msgstr "Monster können nicht auf Wasser plaziert werden." #, fuzzy msgid "Used to place %{object}." msgstr "Wird verwendet, um einen Helden zu platzieren." #, fuzzy msgid "Select object type" msgstr "Ozean-Objekte" #, fuzzy msgid "" "Click here to\n" "select a monster." msgstr "Hier klicken, um auszuwählen, welches Szenario gespielt werden soll." #, fuzzy msgid "" "Click here to\n" "select another monster." msgstr "Bitte wählt einen anderen Helden aus." msgid "" "Cell\n" "Details" msgstr "" #, fuzzy msgid "Roads" msgstr "Weg" #, fuzzy msgid "Streams" msgstr "Wasserlauf-Modus" #, fuzzy msgid "Erase" msgstr "Lösch-Modus" msgid "Mountains" msgstr "Berge" #, fuzzy msgid "Rocks" msgstr "Fels" msgid "Trees" msgstr "Bäume" #, fuzzy msgid "Water Objects" msgstr "Ödland-Objekte" #, fuzzy msgid "Miscellaneous" msgstr "Fineous" msgid "Artifacts" msgstr "Artefakte" #, fuzzy msgid "Dwellings" msgstr "Halblinge" msgid "Mines" msgstr "Minen" #, fuzzy msgid "Power-ups" msgstr "Kraft" msgid "Treasures" msgstr "Schätze" msgid "Towns" msgstr "Städte" msgid "Heroes" msgstr "Helden" msgid "editorErasure|Terrain" msgstr "" msgid "editorErasure|Castles" msgstr "" msgid "editorErasure|Monsters" msgstr "" msgid "editorErasure|Heroes" msgstr "" msgid "editorErasure|Artifacts" msgstr "" msgid "editorErasure|Roads" msgstr "" msgid "editorErasure|Streams" msgstr "" msgid "editorErasure|Treasures" msgstr "" msgid "" "Draws terrain in\n" "%{size} by %{size} square increments." msgstr "" "Zeichnet das Terrain in\n" "%{size} mal %{size} in Quadratschritten." msgid "" "Erases objects in\n" "%{size} by %{size} square increments." msgstr "" "Löscht Objekte in\n" "%{size} mal %{size} in Quadratschritten." msgid "Small Brush" msgstr "Kleiner Pinsel" msgid "Medium Brush" msgstr "Mittlerer Pinsel" msgid "Large Brush" msgstr "Großer Pinsel" msgid "Area Fill" msgstr "Bereich füllen" msgid "Used to click and drag for filling in large areas." msgstr "Wird zum Klicken und Ziehen verwendet, um große Flächen auszufüllen." msgid "Clear Area" msgstr "Bereich löschen" msgid "Used to click and drag for clearing large areas." msgstr "Wird zum Klicken und Ziehen verwendet, um große Bereiche zu löschen." msgid "" "Costs %{rate} times normal movement for all heroes. (Pathfinding reduces or " "eliminates the penalty.)" msgstr "" "Kostet das %{rate}-fache der normalen Bewegung für alle Helden. " "(Pfadfinderei reduziert oder eliminiert den Abzug)." msgid "Traversable only by boat." msgstr "Nur mit dem Boot befahrbar." msgid "No special modifiers." msgstr "Keine besonderen Modifikatoren." msgid "Toggle the erasure of %{type} objects." msgstr "" msgid "" "Objects of this type will be deleted with the Erase tool. Left-click here to " "deselect this type. Press and hold this button to deselect all other object " "types." msgstr "" msgid "" "Objects of this type will NOT be deleted with the Erase tool. Left-click " "here to select this type. Press and hold this button to select all other " "object types." msgstr "" msgid "Terrain Mode" msgstr "Gelände-Modus" msgid "Used to draw the underlying grass, dirt, water, etc. on the map." msgstr "" "Wird verwendet, um das darunter liegende Gras, die Erde, das Wasser usw. auf " "der Karte zu zeichnen." #, fuzzy msgid "Landscape Objects Mode" msgstr "Objekt-Modus" #, fuzzy msgid "" "Used to place landscape objects (mountains, rocks, trees, etc.) on the map." msgstr "" "Wird verwendet, um Objekte (Berge, Bäume, Schätze usw.) auf der Karte zu " "platzieren." msgid "Detail Mode" msgstr "Detail-Modus" msgid "Used for special editing of monsters, heroes and towns." msgstr "" "Wird für die spezielle Bearbeitung von Monstern, Helden und Städten " "verwendet." #, fuzzy msgid "Adventure Objects Mode" msgstr "Abenteuer-Optionen" #, fuzzy msgid "" "Used to place adventure objects (artifacts, dwellings, mines, treasures, " "etc.) on the map." msgstr "" "Wird verwendet, um Objekte (Berge, Bäume, Schätze usw.) auf der Karte zu " "platzieren." #, fuzzy msgid "Kingdom Objects Mode" msgstr "Objekt-Modus" #, fuzzy msgid "Used to place kingdom objects (towns, castles and heroes) on the map." msgstr "" "Wird verwendet, um Objekte (Berge, Bäume, Schätze usw.) auf der Karte zu " "platzieren." #, fuzzy msgid "Monsters Mode" msgstr "Monster" #, fuzzy msgid "Used to place monsters on the map." msgstr "" "Verwendet, um eine\n" "Monstergruppe zu platzieren." msgid "Allows you to draw streams by clicking and dragging." msgstr "Ermöglicht das Zeichnen von Wasserläufen durch Klicken und Ziehen." msgid "Stream Mode" msgstr "Wasserlauf-Modus" msgid "Allows you to draw roads by clicking and dragging." msgstr "Ermöglicht das Zeichnen von Straßen durch Klicken und Ziehen." msgid "Road Mode" msgstr "Straßen-Modus" msgid "Erase Mode" msgstr "Lösch-Modus" #, fuzzy msgid "Used to erase objects from the map." msgstr "Dient zum Löschen von Objekten von der Karte." msgid "Change between zoom and normal view." msgstr "Wechsel zwischen Zoom- und Normalansicht." msgid "Magnify" msgstr "Vergrößern" msgid "Undo" msgstr "Rückgängig" msgid "Undo your last action." msgstr "" msgid "Create a new map either from scratch or using the random map generator." msgstr "" "Eine neue Karte erstellen, entweder von Grund auf oder mit Hilfe des " "Zufallsgenerators." msgid "Edit map title, description, and other general information." msgstr "" "Bearbeiten des Kartentitel, der Beschreibung und anderen allgemeinen " "Informationen." msgid "Specifications" msgstr "Spezifikationen" msgid "File Options" msgstr "Datei-Optionen" msgid "" "Open the file options menu, where you can save or load maps, or quit out of " "the editor." msgstr "" "Dateioptionen-Menü öffnen, in welchem Maps gespeichert oder geladen oder den " "Editor verlassen werden kann." msgid "View the editor system options, which let you customize the editor." msgstr "" "Editor-Systemoptionen anzeigen, mit denen der Editor angepasst werden kann." msgid "Create a map that is %{size} squares wide and %{size} squares high." msgstr "" "Eine Karte erstellen, die %{size} Quadrate breit und %{size} Quadrate hoch " "ist." msgid "Cancel back to the New Map menu." msgstr "Abbrechen, zurück zum Neue Karte Menü." msgid "Cancel back to the main menu." msgstr "Abbrechen und zurück zum Hauptmenü." msgid "From Scratch" msgstr "Von Grund auf neu" msgid "Start from scratch with a blank map." msgstr "Beginne bei Null mit einer leeren Karte." msgid "Create a randomly generated map." msgstr "Erstelle eine zufällig generierte Karte." msgid "Random" msgstr "Zufällig" msgid "Cancel back to the Map Editor main menu." msgstr "Abbrechen und zurück zum Hauptmenü des Karteneditors." msgid "No maps available!" msgstr "Keine Karten vorhanden!" msgid "Warning" msgstr "Achtung" msgid "difficulty|Easy" msgstr "Leicht" msgid "difficulty|Normal" msgstr "Normal" msgid "difficulty|Hard" msgstr "Schwer" msgid "difficulty|Expert" msgstr "Experte" msgid "difficulty|Impossible" msgstr "Unmögl." msgid "and more..." msgstr "und mehr..." msgid "Easy" msgstr "Leicht" msgid "Hard" msgstr "Schwer" msgid "Campaign Difficulty" msgstr "Feldzugschwierigkeit" msgid "" "Choose this difficulty if you want to prefer game story over challenge. AI " "is weaker in comparison with normal difficulty." msgstr "" "Wenn Ihr die Geschichte der Herausforderung vorzieht: Die KI ist im " "Vergleich zum normalen Schwierigkeitsgrad schwächer." msgid "" "Choose this difficulty to enjoy the campaign as per the original design." msgstr "" "Wählt diesen Schwierigkeitsgrad, um den Feldzug wie im Original zu spielen." msgid "" "Choose this difficulty if you want challenge. AI is stronger in comparison " "with normal difficulty." msgstr "" "Stellt Euch der Herausforderung! Die KI ist im Vergleich zum normalen " "Schwierigkeitsgrad stärker." msgid "" "Congratulations!\n" "\n" "Days: %{days}\n" msgstr "" "Herzlichen Glückwunsch!\n" "\n" "Tage: %{days}\n" msgid "" "\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" "\n" "Schwierigkeitsgrad: %{difficulty}\n" "\n" msgid "" "Score: %{score}\n" "\n" "Rating:\n" "%{rating}" msgstr "" "Punktzahl: %{score}\n" "\n" "Bewertung:\n" "%{rating}" msgid "Start the selected scenario." msgstr "Das ausgewählte Szenario starten." msgid "View Intro" msgstr "Intro ansehen" msgid "View Intro videos for the current state of the campaign." msgstr "Die Intro-Videos zum aktuellen Stand des Feldzuges ansehen." msgid "" "Select the campaign difficulty. This can be lowered at any point during the " "campaign." msgstr "" "Schwierigkeitsgrad der Kampagne auswählen. Dieser kann während der Kampagne " "jederzeit herabgesetzt werden." msgid "Restart the current scenario." msgstr "Neustart des aktuellen Szenarios." msgid "Difficulty" msgstr "Schwierigkeitsgrad" msgid "" "You have changed to a lower difficulty for the campaign. You will not be " "able to revert this after this point. The high score will be calculated " "based solely on the new difficulty. Do you want to proceed?" msgstr "" "Ihr habt einen niedrigeren Schwierigkeitsgrad für die Kampagne gewählt. Ihr " "könnt dies danach nicht mehr rückgängig machen. Der Highscore wird " "ausschließlich auf Basis des neuen Schwierigkeitsgrades berechnet. Möchtet " "Ihr fortfahren?" msgid "Are you sure you want to restart this scenario?" msgstr "Seid Ihr sicher, dass Ihr dieses Szenario neu starten wollt?" msgid "Campaign Scenario loading failure" msgstr "Fehler beim Laden des Feldzugspiels" msgid "Please make sure that campaign files are correct and present." msgstr "" "Bitte stellt sicher, dass die Feldzugdateien korrekt und vorhanden sind." msgid "Days spent" msgstr "Verbrachte Tage" msgid "The number of days spent on this campaign." msgstr "Die Anzahl der Tage, die für diesen Feldzug aufgewendet wurden." msgid "Project Coordination and Core Development" msgstr "Projektkoordination und Kernentwicklung" msgid "Development" msgstr "Entwicklung" msgid "Visit us at " msgstr "Besucht uns bei " msgid "QA and Support" msgstr "QA und Unterstützung" msgid "Dev and Support" msgstr "Entwicklung und Support" msgid "Special Thanks to" msgstr "Besonderen Dank an" msgid "and many other contributors!" msgstr "und viele andere Mitwirkende!" msgid "and many-many other supporters!" msgstr "und viele, viele andere Unterstützer!" msgid "Support us at" msgstr "Unterstützet uns bei" msgid "local-donation-platform|https://www.patreon.com/fheroes2" msgstr "https://www.patreon.com/fheroes2" msgid "Connect with us at" msgstr "Verbindet Euch mit uns bei" msgid "local-social-network|https://www.facebook.com/groups/fheroes2" msgstr "https://www.facebook.com/groups/fheroes2" msgid "Need help with the game?" msgstr "Braucht Ihr Hilfe beim Spiel?" msgid "Original project before 0.7" msgstr "Ursprüngliches Projekt vor 0.7" msgid "Heroes of Might and Magic II: The Succession Wars team" msgstr "Heroes of Might and Magic II: The Succession Wars-Team" msgid "Designed and Directed" msgstr "Entworfen und geleitet" msgid "Programming and Design" msgstr "Programmierung und Gestaltung" msgid "Executive Producer" msgstr "Ausführender Produzent" msgid "Producer" msgstr "Produzent" msgid "Additional Design" msgstr "Zusätzliches Gestaltung" msgid "Additional Programming" msgstr "Zusätzliche Programmierung" msgid "Musical Production" msgstr "Musikalische Produktion" msgid "Music and Sound Design" msgstr "Musik und Klanggestaltung" msgid "Vocalists" msgstr "Vokalisten" msgid "Art Director" msgstr "Künstlerischer Leiter" msgid "Assistant Art Director" msgstr "Assistentin der künstlerischen Leitung" msgid "Artists" msgstr "Künstler" msgid "QA Manager" msgstr "QA-Leiter" msgid "QA" msgstr "QA" msgid "Writing" msgstr "Schriftsteller" msgid "Manual and Helpfile" msgstr "Handbuch und Hilfedatei" msgid "Scenarios" msgstr "Szenarien" msgid "Heroes of Might and Magic II: The Price of Loyalty team" msgstr "Heroes of Might and Magic II: The Price of Loyalty-Team" msgid "Design Lead" msgstr "Designleitung" msgid "Designers" msgstr "Designer" msgid "Programming Lead" msgstr "Leitung der Programmierung" msgid "Art Lead" msgstr "Künstlerische Leitung" msgid "Playtesters" msgstr "Spieltester" msgid "Designer" msgstr "Designer" msgid "Producers" msgstr "Produzenten" msgid "QA Managers" msgstr "QA-Manager" msgid "Sound Design" msgstr "Sounddesign" msgid "Town Themes" msgstr "Stadt-Themen" msgid "Alto Sax" msgstr "Altsaxophon" msgid "Harpsichord and Piano" msgstr "Cembalo und Piano" msgid "Basso Vocal" msgstr "Basso-Gesang" msgid "Soprano Vocal" msgstr "Sopran-Gesang" msgid "Recorded at %{recordingStudio}" msgstr "Aufgenommen in den %{recordingStudio}" msgid "credits|Manual" msgstr "Anleitung" msgid "German Consultant" msgstr "Deutscher Berater" msgid "Map Designers" msgstr "Kartendesigner" msgid "Package Design" msgstr "Verpackungsdesign" msgid "Your Name" msgstr "Euer Name" msgid "Unknown Hero" msgstr "Unbekannter Held" msgid "Standard" msgstr "Standard" msgid "View High Scores for Standard Maps." msgstr "Bestenliste für Standardkarten anzeigen." msgid "Campaign" msgstr "Feldzug" msgid "View High Scores for Campaigns." msgstr "Bestenliste für Feldzüge anzeigen." msgid "hotkey|default okay event" msgstr "Standard-Event: Okay" msgid "hotkey|default cancel event" msgstr "Standard-Event: Abbrechen" msgid "hotkey|default left" msgstr "Standard: Links" msgid "hotkey|default right" msgstr "Standard: Rechts" msgid "hotkey|default up" msgstr "Standard: Hoch" msgid "hotkey|default down" msgstr "Standard: Runter" msgid "hotkey|toggle fullscreen" msgstr "Vollbild umschalten" msgid "hotkey|toggle text support mode" msgstr "Textunterstützungsmodus umschalten" msgid "hotkey|new game" msgstr "Neues Spiel" msgid "hotkey|load game" msgstr "Spiel laden" msgid "hotkey|highscores" msgstr "Highscores" msgid "hotkey|credits" msgstr "Credits" msgid "hotkey|standard game" msgstr "Standard-Spiel" msgid "hotkey|campaign game" msgstr "Feldzug-Spiel" msgid "hotkey|multi-player game" msgstr "Multi-Player Spiel" msgid "hotkey|settings" msgstr "Einstellungen" msgid "hotkey|quit" msgstr "Beenden" msgid "hotkey|select map" msgstr "Karte auswählen" msgid "hotkey|select small map size" msgstr "Kleine Karten auswählen" msgid "hotkey|select medium map size" msgstr "Mittelgroße Karten auswählen" msgid "hotkey|select large map size" msgstr "Große Karten auswählen" msgid "hotkey|select extra large map size" msgstr "Extra große Karten auswählen" msgid "hotkey|select all map sizes" msgstr "Alle Kartengrößen auswählen" msgid "hotkey|hotseat game" msgstr "\"Heißer Stuhl\" Spiel" msgid "hotkey|battle only game" msgstr "\"Nur Schlacht\" Spiel" msgid "hotkey|choose the original campaign" msgstr "Originaler Feldzug auswählen" msgid "hotkey|choose the expansion campaign" msgstr "Zusätzlicher Feldzug auswählen" msgid "hotkey|map editor main menu" msgstr "Hauptmenü des Karteneditors" msgid "hotkey|new map menu" msgstr "Neue Karte Menü" msgid "hotkey|load map menu" msgstr "Karte Laden Menü" msgid "hotkey|new map from scratch" msgstr "Neue Karte von Beginn Menü" msgid "hotkey|new random map" msgstr "Neue zufällige Karte Menü" #, fuzzy msgid "hotkey|undo last action" msgstr "Standardaktion" #, fuzzy msgid "hotkey|redo last action" msgstr "Standardaktion" msgid "hotkey|roland campaign" msgstr "Roland-Feldzug" msgid "hotkey|archibald campaign" msgstr "Archibald-Feldzug" msgid "hotkey|the price of loyalty campaign" msgstr "\"The Price of Loyalty\"-Feldzug" msgid "hotkey|voyage home campaign" msgstr "Heimreise-Feldzug" msgid "hotkey|wizard's isle campaign" msgstr "\"Die Insel der Zauberer\"-Feldzug" msgid "hotkey|descendants campaign" msgstr "Nachkommen-Feldzug" msgid "hotkey|select first campaign bonus" msgstr "Wähle ersten Feldzug-Bonus" msgid "hotkey|select second campaign bonus" msgstr "Wähle zweiten Feldzug-Bonus" msgid "hotkey|select third campaign bonus" msgstr "Wähle dritten Feldzug-Bonus" msgid "hotkey|view campaign intro" msgstr "Feldzug-Intro anzeigen" msgid "hotkey|select campaign difficulty" msgstr "Feldzugschwierigkeit" msgid "hotkey|restart campaign scenario" msgstr "Feldzug-Szenario neustarten" msgid "hotkey|world map left" msgstr "Weltkarte links" msgid "hotkey|world map right" msgstr "Weltkarte rechts" msgid "hotkey|world map up" msgstr "Weltkarte hoch" msgid "hotkey|world map down" msgstr "Weltkarte runter" msgid "hotkey|world map up left" msgstr "Weltkarte hoch links" msgid "hotkey|world map up right" msgstr "Weltkarte hoch rechts" msgid "hotkey|world map down left" msgstr "Weltkarte runter links" msgid "hotkey|world map down right" msgstr "Weltkarte runter rechts" msgid "hotkey|save game" msgstr "Spiel speichern" msgid "hotkey|next hero" msgstr "Nächster Held" #, fuzzy msgid "hotkey|start hero movement" msgstr "Bewegung fortsetzen" msgid "hotkey|cast adventure spell" msgstr "Abenteuer-Zauberspruch aussprechen" msgid "hotkey|put hero to sleep" msgstr "Held schlafen legen" msgid "hotkey|next town" msgstr "Nächste Stadt" msgid "hotkey|end turn" msgstr "Zug beenden" msgid "hotkey|file options" msgstr "Datei-Optionen" msgid "hotkey|adventure options" msgstr "Abenteuer-Optionen" msgid "hotkey|puzzle map" msgstr "Puzzlekarte" msgid "hotkey|scenario information" msgstr "Szenario-Informationen" msgid "hotkey|dig for artifact" msgstr "Nach dem Artefakt graben" msgid "hotkey|view world" msgstr "Weltkarte anzeigen" msgid "hotkey|kingdom summary" msgstr "Zusammenfassung des Königreichs" msgid "hotkey|default action" msgstr "Standardaktion" msgid "hotkey|open focus" msgstr "Fokus öffnen" msgid "hotkey|system options" msgstr "System-Optionen" msgid "hotkey|scroll left" msgstr "Links scrollen" msgid "hotkey|scroll right" msgstr "Rechts scrollen" msgid "hotkey|scroll up" msgstr "Nach oben scrollen" msgid "hotkey|scroll down" msgstr "Nach unten scrollen" msgid "hotkey|toggle control panel" msgstr "Bedienfeld umschalten" msgid "hotkey|toggle radar" msgstr "Radar umschalten" msgid "hotkey|toggle buttons" msgstr "Buttons umschalten" msgid "hotkey|toggle status" msgstr "Status umschalten" msgid "hotkey|toggle icons" msgstr "Icons umschalten" msgid "hotkey|transfer control to ai" msgstr "Kontrolle zur KI übertragen" msgid "hotkey|retreat from battle" msgstr "Vom Kampf zurückziehen" msgid "hotkey|surrender during battle" msgstr "Aufgeben während des Kampfes" msgid "hotkey|toggle battle auto mode" msgstr "Auto-Kampf-Modus umschalten" msgid "hotkey|finish the battle in auto mode" msgstr "Kampf im Automodus beenden" msgid "hotkey|battle options" msgstr "Kampf-Optionen" msgid "hotkey|skip turn in battle" msgstr "Runde im Kampf überspringen" msgid "hotkey|cast battle spell" msgstr "Kampfzauber wirken" msgid "hotkey|dwelling level 1" msgstr "Behausung Stufe 1" msgid "hotkey|dwelling level 2" msgstr "Behausung Stufe 2" msgid "hotkey|dwelling level 3" msgstr "Behausung Stufe 3" msgid "hotkey|dwelling level 4" msgstr "Behausung Stufe 4" msgid "hotkey|dwelling level 5" msgstr "Behausung Stufe 5" msgid "hotkey|dwelling level 6" msgstr "Behausung Stufe 6" msgid "hotkey|well" msgstr "Brunnen" msgid "hotkey|marketplace" msgstr "Marktplatz" msgid "hotkey|mage guild" msgstr "Magiergilde" msgid "hotkey|shipyard" msgstr "Schiffswerft" msgid "hotkey|thieves guild" msgstr "Diebesgilde" msgid "hotkey|tavern" msgstr "Taverne" msgid "hotkey|construction screen" msgstr "Bau-Bildschirm" msgid "hotkey|buy all monsters in well" msgstr "Alle Monster im Brunnen kaufen" msgid "hotkey|split stack by half" msgstr "Stapel halbieren" msgid "hotkey|split stack by one" msgstr "Stapel um eins aufteilen" msgid "hotkey|join stacks" msgstr "Stapel zusammenfügen" msgid "hotkey|upgrade troop" msgstr "Truppen aufrüsten" msgid "hotkey|dismiss hero or troop" msgstr "Held oder Truppe entlassen" msgid "" "Do you want to regain control from AI? The effect will take place only on " "the next turn." msgstr "" "Möchtet Ihr die Kontrolle über die KI zurückgewinnen? Der Effekt tritt erst " "in der nächsten Runde ein." msgid "" "Do you want to transfer control from you to the AI? The effect will take " "place only on the next turn." msgstr "" "Möchtet Ihr die Kontrolle von Euch auf die KI übertragen? Der Effekt tritt " "erst in der nächsten Runde ein." #, fuzzy msgid "Default Actions" msgstr "Standardaktion" msgid "Global Actions" msgstr "" msgid "Main Menu" msgstr "" msgid "World Map" msgstr "Weltkarte" #, fuzzy msgid "Battle Screen" msgstr "Heldenbildschirm" #, fuzzy msgid "Town Screen" msgstr "Heldenbildschirm" msgid "Army Actions" msgstr "" msgid "Editor" msgstr "Editor" msgid "The save file is corrupted." msgstr "Die Speicherdatei ist beschädigt." msgid "Unsupported save format: " msgstr "Unterstütztes Speicherformat: " msgid "Current game version: " msgstr "Aktuelle Spielversion: " msgid "Last supported version: " msgstr "Letzte unterstützte Version: " msgid "This file contains a save with an invalid game type." msgstr "Diese Datei enthält einen Spielstand mit einem ungültigen Spieltyp." #, fuzzy msgid "" "This save file requires \"The Price of Loyalty\" game assets, but they have " "not been provided to the engine." msgstr "" "Diese Datei wurde als \"The Price of Loyalty\"-Karte gespeichert, aber die " "entsprechenden Spiel-Assets wurden der Engine nicht zur Verfügung gestellt." msgid "This saved game is localized to '" msgstr "Dieses gespeicherte Spiel ist lokalisiert in '" msgid "' language, but the current language of the game is '" msgstr "' Sprache, aber die aktuelle Sprache des Spiels ist '" msgid "Hot Seat" msgstr "Heißer Stuhl" msgid "" "Play a Hot Seat game, where 2 to 4 players play around the same computer, " "switching into the 'Hot Seat' when it is their turn." msgstr "" "\"Heißen Stuhl\" spielen, bei dem 2 bis 4 Spieler spielen an einem Rechner " "abwechselnd gegeneinander." msgid "A single player game playing out a single map." msgstr "Ein Einzelspieler-Spiel, das auf einer einzigen Karte spielt." msgid "Standard Game" msgstr "Standard-Spiel" msgid "A single player game playing through a series of maps." msgstr "" "Ein Einzelspieler-Spiel, bei dem man sich durch eine Reihe von Karten spielt." msgid "Campaign Game" msgstr "Feldzug-Spiel" msgid "Multi-Player Game" msgstr "Multi-Player Spiel" msgid "" "A multi-player game, with several human players completing against each " "other on a single map." msgstr "" "Ein Multiplayer-Spiel, bei dem mehrere menschliche Spieler auf einer " "einzigen Karte gegeneinander antreten." msgid "fheroes2 Resurrection Team presents" msgstr "fheroes2 Resurrection Team präsentiert" msgid "Greetings!" msgstr "Grüße!" msgid "Welcome to Heroes of Might and Magic II powered by fheroes2 engine!" msgstr "" "Willkommen bei Heroes of Might and Magic II (ermöglicht durch die fheroes2-" "Engine)!" #, fuzzy msgid "" "Welcome to Heroes of Might and Magic II powered by the fheroes2 engine!\n" "Before starting the game please select a game resolution." msgstr "" "Willkommen bei Heroes of Might and Magic II (ermöglicht durch die fheroes2-" "Engine)! Bevor das Spiel startet, bitte die Auflösung des Spiels wählen." msgid "Please Remember" msgstr "Bitte merken" # Needs extra whitespace to avoid yellow word splitting #, fuzzy msgid "" "You can always change the language, resolution and settings of the game by " "clicking on the " msgstr "" "Die Auflösung des Spiels kann jederzeit geändernt werden, durch klicken auf " "die " msgid "door" msgstr "Tür" msgid " on the left side of the Main Menu, or with the " msgstr "" msgid " button from the " msgstr "" #, fuzzy msgid "NEW GAME" msgstr "" "NEUES\n" "SPIEL" msgid "" " menu. \n" "\n" "Enjoy the game!" msgstr "" msgid "The Editor requires \"The Price of Loyalty\" expansion files to work." msgstr "" "Der Editor benötigt Dateien des Erweiterungspacks \"The Price of Loyalty\"." msgid "Quit Heroes of Might and Magic II and return to the operating system." msgstr "" "Heroes of Might and Magic II beenden und zum Betriebssystem zurückkehren." msgid "Credits" msgstr "Credits" msgid "View the credits screen." msgstr "Abspannbildschirm zeigen." msgid "High Scores" msgstr "Bestenliste" msgid "View the high scores screen." msgstr "Die Bestenliste anzeigen." msgid "Change language, resolution and settings of the game." msgstr "Sprache, Auflösung und Einstellungen des Spiels ändern." msgid "Game Settings" msgstr "Spieleinstellungen" msgid "" "Required video files for campaign selection window are missing. Please make " "sure that all necessary files are present in the system." msgstr "" "Die erforderlichen Videodateien für das Feldzugauswahlfenster fehlen. Bitte " "sicherstellen, dass alle erforderlichen Dateien im System vorhanden sind." msgid "" "Either Roland's or Archibald's campaign from the original Heroes of Might " "and Magic II." msgstr "" "Entweder Rolands oder Archibalds Feldzug aus dem ursprünglichen Heroes of " "Might and Magic II." msgid "Original Campaign" msgstr "Original Feldzug" msgid "Expansion Campaign" msgstr "Zusätzlicher Feldzug" msgid "One of the four new campaigns from the Price of Loyalty expansion set." msgstr "" "Ein der vier neuen Feldzüge aus dem Erweiterungsset \"The Price of Loyalty\"." msgid "Loading video. Please wait..." msgstr "Video wird geladen. Bitte warten..." msgid "Host" msgstr "Gastgeber" msgid "" "The host sets up the game options. There can only be one host per network " "game." msgstr "" "Der Gastgeber legt die Spieloptionen fest. Es kann nur einen Gastgeber pro " "Netzwerkspiel geben." msgid "Guest" msgstr "Gast" msgid "" "The guest waits for the host to set up the game, then is automatically added " "in. There can be multiple guests for TCP/IP games." msgstr "" "Der Gast wartet darauf, dass der Gastgeber das Spiel einrichtet, und wird " "dann automatisch hinzugefügt. Bei TCP/IP-Spielen kann es mehrere Gäste geben." msgid "Battle Only" msgstr "Nur Schlacht" msgid "Setup and play a battle without loading any map." msgstr "Einen Schlacht einrichten und spielen, ohne eine Karte zu laden." msgid "2 Players" msgstr "2 Spieler" msgid "" "Play with 2 human players, and optionally, up to 4 additional computer " "players." msgstr "" "Ein Spiel mit 2 menschlichen Spielern und optional mit bis zu 4 zusätzlichen " "Computerspielern." msgid "3 Players" msgstr "3 Spieler" msgid "" "Play with 3 human players, and optionally, up to 3 additional computer " "players." msgstr "" "Ein Spiel mit 3 menschlichen Spielern und optional mit bis zu 3 zusätzlichen " "Computerspielern." msgid "4 Players" msgstr "3 Spieler" msgid "" "Play with 4 human players, and optionally, up to 2 additional computer " "players." msgstr "" "Ein Spiel mit 4 menschlichen Spielern und optional mit bis zu 2 zusätzlichen " "Computerspielern." msgid "5 Players" msgstr "5 Spieler" msgid "" "Play with 5 human players, and optionally, up to 1 additional computer " "player." msgstr "" "Ein Spiel mit 5 menschlichen Spielern und optional mit bis zu 1 zusätzliche " "Computerspieler." msgid "6 Players" msgstr "6 Spieler" msgid "Play with 6 human players." msgstr "Ein Spiel mit 6 menschlichen Spielern." msgid "Dragon city has fallen! You are now the Master of the Dragons." msgstr "" "Die Drachenstadt ist gefallen! Ihr seid sind nun der Meister der Drachen." msgid "" "You captured %{name}!\n" "You are victorious." msgstr "" "Ihr habt %{name} gefasst!\n" "Ihr seid siegreich." msgid "" "You have captured the enemy hero %{name}!\n" "Your quest is complete." msgstr "" "Ihr habt den feindlichen Helden %{name} gefangen genommen!\n" "Eure Suche ist abgeschlossen." msgid "" "You have found the %{name}.\n" "Your quest is complete." msgstr "" "Ihr habt den %{name} gefunden.\n" "Eure Suche ist abgeschlossen." msgid "Ultimate Artifact" msgstr "Ultimativen Artefakt" msgid "" "The enemy is beaten.\n" "Your side has triumphed!" msgstr "" "Der Feind ist besiegt.\n" "Eure Seite hat gesiegt!" msgid "" "You have built up over %{count} gold in your treasury.\n" "All enemies bow before your wealth and power." msgstr "" "Ihr habt über %{count} Gold in Eurer Schatzkammer angehäuft.\n" "Alle Feinde verneigen sich vor Eure Reichtum und Eurer Macht." #, fuzzy msgid "Victory!" msgstr "Ector" msgid "" "The enemy has captured %{name}!\n" "They are triumphant." msgstr "" "Der Feind hat %{name} gefangen genommen!\n" "Der ist triumphierend." msgid "" "The enemy has built up over %{count} gold in his treasury.\n" "You must bow done in defeat before his wealth and power." msgstr "" "Der Feind hat über %{count} Gold in seiner Schatzkammer angehäuft.\n" "Ihr müsst Euch in der Niederlage vor seinem Reichtum und seiner Macht beugen." msgid "You have been eliminated from the game!!!" msgstr "Ihr seid aus dem Spiel ausgeschieden!!!" msgid "" "You have lost the hero %{name}.\n" "Your quest is over." msgstr "" "Ihr habt den Helden %{name} verloren.\n" "Euer Abenteuer ist vorbei." msgid "" "You have failed to complete your quest in time.\n" "All is lost." msgstr "" "Ihr habt es nicht geschafft, Euer Abenteuer rechtzeitig zu beenden.\n" "Alles ist verloren." msgid "Defeat!" msgstr "Besiegt!" msgid "" "Base score: %{score}\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" "Basispunktzahl: %{score}\n" "Schwierigkeit: %{difficulty}\n" "\n" msgid "Defeat all enemy heroes and capture all enemy towns and castles." msgstr "" "Alle feindlichen Helden besiegen und alle feindlichen Städte und Burgen " "bezwingen." msgid "Run out of time. (Fail to win by a certain point.)" msgstr "" "Die Zeit läuft ab. (Nicht mit einem bestimmten Punkt Vorsprung gewinnen.)" msgid "You must defeat the enemy %{enemies}." msgid_plural "You must defeat the enemy alliance of %{enemies}." msgstr[0] "Ihr müsst den Feind %{enemies} besiegen." msgstr[1] "Ihr müsst die feindliche Allianz %{enemies} besiegen." msgid "" "The alliance consisting of %{allies} and you must defeat the enemy " "%{enemies}." msgid_plural "" "The alliance consisting of %{allies} and you must defeat the enemy alliance " "of %{enemies}." msgstr[0] "" "Die Allianz, bestehend aus %{allies} und Euch, muss den Feind %{enemies} " "besiegen." msgstr[1] "" "Die Allianz, bestehend aus %{allies} und Euch, muss die feindliche Allianz " "%{enemies} besiegen." msgid "Capture the castle '%{name}'." msgstr "Die Burg '%{name}' erobern." msgid "Capture the town '%{name}'." msgstr "Die Stadt '%{name}' erobern." msgid "Defeat the hero '%{name}'." msgstr "Den Helden '%{name}' besiegen." msgid "Find the ultimate artifact." msgstr "Das ultimative Artefakt finden." msgid "Find the '%{name}' artifact." msgstr "Das Artefakt '%{name}' finden." msgid "Accumulate %{count} gold." msgstr "%{count} Gold ansammeln." msgid "" ", or you may win by defeating all enemy heroes and capturing all enemy towns " "and castles." msgstr "" ", oder Ihr könnt gewinnen, indem Ihr alle gegnerischen Helden besiegt und " "alle gegnerischen Städte und Burgen erobert." msgid "Lose the castle '%{name}'." msgstr "Die Burg '%{name}' verlieren." msgid "Lose the town '%{name}'." msgstr "Die Stadt '%{name}' verlieren." msgid "Lose the hero: %{name}." msgstr "Den Helden verlieren: %{name}." msgid "Fail to win by the end of month %{month}, week %{week}, day %{day}." msgstr "" "Nicht bis zum Ende des Monats %{month}, der Woche %{week}, des Tages %{day} " "gewinnen." msgid "%{color} player has been vanquished!" msgstr "Spieler %{color} wurde besiegt!" msgid "Major Event!" msgstr "Großes Ereignis!" msgid "Scenario:" msgstr "Szenario:" msgid "Game Difficulty:" msgstr "Schwierigkeitsgrad:" msgid "Opponents:" msgstr "Gegner:" msgid "Class:" msgstr "Klasse:" msgid "Rating %{rating}%" msgstr "Grad %{rating}%" msgid "Click here to select which scenario to play." msgstr "Hier klicken, um auszuwählen, welches Szenario gespielt werden soll." msgid "Scenario" msgstr "Szenario" msgid "Game Difficulty" msgstr "Spielschwierigkeit" msgid "" "This lets you change the starting difficulty at which you will play. Higher " "difficulty levels start you of with fewer resources, and at the higher " "settings, give extra resources to the computer." msgstr "" "Damit kann die Startschwierigkeit verändert werden, mit der gespielt wird. " "Bei höheren Schwierigkeitsgraden hat der Spieler zu Beginn weniger " "Ressourcen, und bei höheren Einstellungen erhält der Computer zusätzliche " "Ressourcen." msgid "Difficulty Rating" msgstr "Schwierigkeitsgrad" msgid "" "The difficulty rating reflects a combination of various settings for your " "game. This number will be applied to your final score." msgstr "" "Der Schwierigkeitsgrad spiegelt eine Kombination aus verschiedenen " "Einstellungen für das Spiel wider. Diese Zahl wird auf Euer Endergebnis " "angewandt." msgid "Click to accept these settings and start a new game." msgstr "Diese Einstellungen übernehmen und ein neues Spiel beginnen." msgid "Click to return to the main menu." msgstr "Klicken, um zum Hauptmenü zurückzukehren." msgid "Astrologers proclaim the Month of the %{name}." msgstr "Astrologen verkündigen den Monat des %{name}." msgid "Astrologers proclaim the Week of the %{name}." msgstr "Astrologen verkündigen die Woche des %{name}." msgid "After regular growth, the population of %{monster} is doubled!" msgstr "" "Nach regelmäßigem Wachstum verdoppelt sich die Population von %{monster}!" msgid "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgid_plural "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgstr[0] "" "Nach regelmäßigem Wachstum steigt die Bevölkerung von %{monster} um %{count} " "Prozent!" msgstr[1] "" "Nach regelmäßigem Wachstum steigt die Bevölkerung von %{monster} um %{count} " "Prozent!" msgid "%{monster} growth +%{count}." msgstr "Die Bevölkerung von %{monster} steigt um +%{count}." msgid " All populations are halved." msgstr " Alle Bevölkerungen werden halbiert." msgid " All dwellings increase population." msgstr " Alle Wohnungen erhöhen die Bevölkerung." msgid "New Month!" msgstr "" msgid "New Week!" msgstr "Neue Woche!" msgid "Beware!" msgstr "Aufgepasst!" msgid "" "%{color} player, this is your last day to capture a town, or you will be " "banished from this land." msgstr "" "Spieler %{color}, dies ist Euer letzter Tag, um eine Stadt zu erobern, oder " "Ihr werdet aus diesem Land verbannt." msgid "" "%{color} player, you only have %{day} days left to capture a town, or you " "will be banished from this land." msgstr "" "Spieler %{color}, Ihr habt nur noch %{day} Tage Zeit, um eine Stadt zu " "erobern, oder Ihr werdet aus diesem Land verbannt." msgid "%{color} player's turn." msgstr "Spieler %{color} ist dran." #, fuzzy msgid "" "%{color} player, you have lost your last town. If you do not conquer another " "town in the next week, you will be eliminated." msgstr "" "Spieler %{color}, Ihr habt Eure letzte Stadt verloren. Wenn Ihr in der " "nächsten Woche keine Stadt erobert, werdet Ihr aus diesem Land verbannt." msgid "" "%{color} player, your heroes abandon you, and you are banished from this " "land." msgstr "" "Spieler %{color}, Eure Helden lassen Euch im Stich, und Ihr werdet aus " "diesem Land verbannt." msgid "Lord Kilburn" msgstr "Lord Kilburn" msgid "Tsabu" msgstr "Tsabu" msgid "Sir Galant" msgstr "Sir Galant" msgid "Thundax" msgstr "Thundax" msgid "Lord Haart" msgstr "Lord Haart" msgid "Ariel" msgstr "Ariel" msgid "Rebecca" msgstr "Rebecca" msgid "Sandro" msgstr "Sandro" msgid "Crodo" msgstr "Crodo" msgid "Barock" msgstr "Barock" msgid "Antoine" msgstr "Antoine" msgid "Astra" msgstr "Astra" msgid "Agar" msgstr "Agar" msgid "Vatawna" msgstr "Vatawna" msgid "Vesper" msgstr "Vesper" msgid "Ambrose" msgstr "Ambrose" msgid "Troyan" msgstr "Troyan" msgid "Jojosh" msgstr "Jojosh" msgid "Wrathmont" msgstr "Wrathmont" msgid "Maximus" msgstr "Maximus" msgid "Next Hero" msgstr "Nächster Held" msgid "Select the next Hero." msgstr "Nächster Held wählen." #, fuzzy msgid "Hero Movement" msgstr "Schatten-bewegung" msgid "" "Start the Hero's movement along the current path or re-visit the object " "occupied by the Hero. Press and hold this button to reset the Hero's path." msgstr "" msgid "Kingdom Summary" msgstr "Zusammenfassung des Königreichs" msgid "View a Summary of your Kingdom." msgstr "Eine Zusammenfassung Eures Königreichs anzeigen." msgid "Cast an adventure spell." msgstr "Einen Abenteuer-Zauberspruch aussprechen." msgid "End Turn" msgstr "Zug beenden" msgid "End your turn and left the computer take its turn." msgstr "Beendet Euren Zug und lasst den Computer seinen Zug machen." msgid "Adventure Options" msgstr "Abenteuer-Optionen" msgid "Bring up the adventure options menu." msgstr "Ruft das Menü der Abenteuer-Optionen auf." msgid "" "Bring up the file options menu, allowing you to load, save, start a new game " "or quit." msgstr "" "Ruft das Menü \"Dateioptionen\" auf, in dem Sie das Spiel laden, speichern, " "neu starten oder aufgeben können." msgid "Bring up the system options menu, allowing you to customize your game." msgstr "" "Ruft das Menü \"Systemoptionen\" auf, in dem Ihr Euer Spiel anpassen könnt." msgid "" "One or more heroes may still move, are you sure you want to end your turn?" msgstr "" "Ein oder mehrere Helden können sich noch bewegen. Sied Ihr sicher, dass Ihr " "Eure Zug beenden wollt?" msgid "Are you sure you want to quit?" msgstr "Seid Ihr sicher, dass Ihr das Spiel verlassen wollt?" msgid "Are you sure you want to restart? (Your current game will be lost.)" msgstr "" "Seid Ihr sicher, dass Ihr neu starten wollt? (Euer aktuelles Spiel wird " "verloren gehen.)" msgid "Are you sure you want to overwrite the save with this name?" msgstr "" "Seid Ihr sicher, dass Ihr die Datei mit diesem Namen überschreiben wollt?" msgid "Game saved successfully." msgstr "Spiel erfolgreich gespeichert." msgid "There was an issue during saving." msgstr "Es gab ein Problem beim Speichern." msgid "" "Are you sure you want to load a new game? (Your current game will be lost.)" msgstr "" "Seid Ihr sicher, dass Ihr ein neues Spiel laden wollt? (Euer aktuelles Spiel " "geht dabei verloren.)" msgid "Try looking on land!!!" msgstr "Ihr solltet probieren an Land zu suchen!!!" msgid "" "Searching for the Ultimate Artifact is fruitless. Your hero could not carry " "it even if he found it - all his artifact slots are full." msgstr "" "Die Suche nach dem ultimativen Artefakt ist erfolglos. Euer Held könnte es " "nicht tragen, selbst wenn er es gefunden hätte - alle seine Artefaktplätze " "sind voll." msgid "Digging for artifacts requires a whole day, try again tomorrow." msgstr "" "Das Graben nach Artefakten dauert einen ganzen Tag, versucht es morgen noch " "einmal." msgid "" "After spending many hours digging here, you have uncovered the %{artifact}." msgstr "" "Nachdem Ihr hier viele Stunden gegraben habt, habt Ihr das %{artifact} " "gefunden." msgid "Congratulations!" msgstr "Gratulation!" msgid "Nothing here. Where could it be?" msgstr "Hier ist nichts. Wo könnte es nur sein?" msgid "Try searching on clear ground." msgstr "Versucht auf freiem Feld zu suchen." msgid "A miniature view of the known world. Left click to move viewing area." msgstr "" "Eine Miniaturansicht der bekannten Welt. Klickt mit der linken Maustaste, um " "den Sichtbereich zu verschieben." msgid "Month: %{month} Week: %{week}" msgstr "Monat: %{month} Woche: %{week}" msgid "Day: %{day}" msgstr "Tag: %{day}" msgid "" "You find a small\n" "quantity of %{resource}." msgstr "" "Ihr findet eine kleine\n" "Menge %{resource}." msgid "Status Window" msgstr "Statusfenster" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date." msgstr "" "Dieses Fenster gibt Auskunft über den Status Eures Helden oder Königreichs " "und zeigt das Datum an." msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date. Left click here to cycle through these windows." msgstr "" "Dieses Fenster gibt Auskunft über den Status Eures Helden oder Königreichs " "und zeigt das Datum an. Klickt hier mit der linken Maustaste, um durch diese " "Fenster zu rotieren." msgid "" "This lets you change player starting positions and colors. A particular " "color will always start in a particular location. Some positions may only be " "played by a computer player or only by a human player." msgstr "" "Damit kann die Startpositionen und Farben der Spieler geändert werden. Eine " "bestimmte Farbe beginnt immer an einer bestimmten Stelle. Einige Positionen " "können nur von einem Computerspieler oder nur von einem menschlichen Spieler " "gespielt werden." msgid "" "This lets you change the class of a player. Classes are not always " "changeable. Depending on the scenario, a player may receive additional towns " "and/or heroes not of their primary alignment." msgstr "" "Damit kann die Klasse eines Spielers geändert werden. Die Klassen sind nicht " "immer änderbar. Je nach Szenario kann ein Spieler zusätzliche Städte und/" "oder Helden erhalten, die nicht seiner primären Ausrichtung angehören." msgid "Handicap" msgstr "Handicap" # msgid "" "This lets you change the handicap of a particular player. Only humans may be " "handicapped. Handicapped players start with fewer resources and earn 15 or " "30% fewer resources per turn for mild and severe handicaps, respectively." msgstr "" "Damit kann das Handicap eines bestimmten Spielers geändert werden. Nur " "Menschen können ein Handicap haben. Spieler mit Handicap beginnen mit " "weniger Ressourcen und erhalten 15 bzw. 30 % weniger Ressourcen pro Runde " "für leichte bzw. schwere Handicaps." msgid "%{color} player" msgstr "Spieler %{color}" msgid "No Handicap" msgstr "Kein Handicap" msgid "No special restrictions on start resources and earning them per turn." msgstr "" "Es gibt keine besonderen Beschränkungen für Startressourcen und deren Erwerb " "pro Zug." msgid "Mild Handicap" msgstr "Leichtes Handicap" # msgid "" "Mild handicapped players start with fewer resources and earn 15% fewer " "resources per turn." msgstr "" "Spieler mit einem leichten Handicap beginnen mit weniger Ressourcen und " "erhalten 15% weniger Ressourcen pro Zug." msgid "Severe Handicap" msgstr "Schweres Handicap" # msgid "" "Severe handicapped players start with fewer resources and earn 30% fewer " "resources per turn." msgstr "" "Spieler mit einem schwerem Handicap beginnen mit weniger Ressourcen und " "erhalten 30% weniger Ressourcen pro Zug." msgid "View %{skill} Info" msgstr "Info über %{skill} anzeigen" msgid "Keyboard|123" msgstr "123" msgid "Keyboard|SPACE" msgstr "LEERTASTE" msgid "Keyboard|ABC" msgstr "ABC" msgid "Kingdom Income" msgstr "Einkommen" msgid "Kingdom Income per day." msgstr "Einkommen des Königreichs pro Tag." msgid "For every lighthouse controlled, your ships will move further each day." msgstr "" "Für jeden kontrollierten Leuchtturm bewegen sich Eure Schiffe jeden Tag " "weiter." msgid "English" msgstr "Englisch" msgid "French" msgstr "Französisch" msgid "Polish" msgstr "Polnisch" msgid "German" msgstr "Deutsch" msgid "Russian" msgstr "Russisch" msgid "Italian" msgstr "Italienisch" msgid "Czech" msgstr "Tschechisch" msgid "Norwegian" msgstr "Norwegisch" msgid "Belarusian" msgstr "Weißrussisch" msgid "Bulgarian" msgstr "Bulgarisch" msgid "Ukrainian" msgstr "Ukrainisch" msgid "Romanian" msgstr "Rumänisch" msgid "Spanish" msgstr "Spanisch" msgid "Swedish" msgstr "Schwedisch" msgid "Portuguese" msgstr "Portugiesisch" msgid "Turkish" msgstr "Türkisch" msgid "Dutch" msgstr "Niederländisch" msgid "Hungarian" msgstr "Ungarisch" msgid "Danish" msgstr "Dänisch" msgid "Slovak" msgstr "Slowakisch" msgid "Vietnamese" msgstr "Vietnamesisch" msgid ", FPS: " msgstr ", FPS: " msgid "%{object} robber" msgstr "%{object} Räuber" msgid "%{object} raided" msgstr "%{object} überfallen" msgid "Ector" msgstr "Ector" msgid "Gwenneth" msgstr "Gwenneth" msgid "Sir Gallant" msgstr "Sir Gallant" msgid "Tyro" msgstr "Tyro" msgid "Dimitry" msgstr "Dimitry" msgid "Ruby" msgstr "Ruby" msgid "Crag Hack" msgstr "Crag Hack" msgid "Fineous" msgstr "Fineous" msgid "Jezebel" msgstr "Jezebel" msgid "Atlas" msgstr "Atlas" msgid "Ergon" msgstr "Ergon" msgid "Jaclyn" msgstr "Jaclyn" msgid "Gem" msgstr "Gem" msgid "Natasha" msgstr "Natascha" msgid "Carlawn" msgstr "Carlawn" msgid "Luna" msgstr "Luna" msgid "Arie" msgstr "Arie" msgid "Barok" msgstr "Barok" msgid "Kastore" msgstr "Kastore" msgid "Falagar" msgstr "Falagar" msgid "Dawn" msgstr "Dawn" msgid "Flint" msgstr "Flint" msgid "Halon" msgstr "Halon" msgid "Myra" msgstr "Myra" msgid "Myrini" msgstr "Myrini" msgid "Wilfrey" msgstr "Wilfrey" msgid "Mandigal" msgstr "Mandigal" msgid "Sarakin" msgstr "Sarakin" msgid "Charity" msgstr "Charity" msgid "Darlana" msgstr "Darlana" msgid "Ranloo" msgstr "Ranloo" msgid "Rialdo" msgstr "Rialdo" msgid "Zam" msgstr "Zam" msgid "Zom" msgstr "Zom" msgid "Celia" msgstr "Celia" msgid "Roxana" msgstr "Roxana" msgid "Lord Corlagon" msgstr "Lord Corlagon" msgid "Lord Halton" msgstr "Lord Halton" msgid "Sister Eliza" msgstr "Schwester Eliza" msgid "Brother Brax" msgstr "Bruder Brax" msgid "Dainwin" msgstr "Dainwin" msgid "Joseph" msgstr "Joseph" msgid "Mog" msgstr "Mog" msgid "Solmyr" msgstr "Solmyr" msgid "Ceallach" msgstr "Ceallach" msgid "Drakonia" msgstr "Drakonia" msgid "Elderian" msgstr "Elderian" msgid "Gallavant" msgstr "Gallavant" msgid "Jarkonas" msgstr "Jarkonas" msgid "Martine" msgstr "Martine" msgid " gives you maximum morale" msgstr " gibt Euch maximale Moral" msgid " gives you maximum luck" msgstr " gibt Euch maximales Glück" msgid "You cannot pick up this artifact, you already have a full load!" msgstr "" "Ihr könnt dieses Artefakt nicht aufsammlen, Ihr habt bereits eine volle " "Ladung!" msgid "To cast spells, you must first buy a spell book for %{gold} gold." msgstr "" "Um Zaubersprüche auszusprechen zu können, müsst Ihr zuerst ein Zauberbuch " "für %{gold} Gold kaufen." msgid "Unfortunately, you seem to be a little short of cash at the moment." msgstr "Leider scheint Ihr im Moment ein wenig knapp bei Kasse zu sein." msgid "Do you wish to buy one?" msgstr "Möchtet Ihr eines kaufen?" msgid "%{count} / day" msgstr "%{count} / Tag" msgid "one" msgstr "1" msgid "two" msgstr "2" msgid "A whirlpool engulfs your ship. Some of your army has fallen overboard." msgstr "" "Ein Strudel verschlingt Euer Schiff. Ein Teil Eurer Armee ist über Bord " "gegangen." msgid "Insulted by your refusal of their offer, the monsters attack!" msgstr "Beleidigt durch Eure Ablehnung ihres Angebots, greifen die Monster an!" msgid "" "The %{monster}, awed by the power of your forces, begin to scatter.\n" "Do you wish to pursue and engage them?" msgstr "" "Erschrocken über die Macht Eurer Truppen beginnen die %{monster} sich zu " "zerstreuen.\n" "Wollt Ihr sie verfolgen und angreifen?" msgid "Ransacking an enemy camp, you discover a hidden cache of treasures." msgstr "" "Bei der Plünderung eines feindlichen Lagers entdeckt Ihr eine versteckte " "Schatzkiste." msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with these resources, " "come back next week for more.\"" msgstr "" "Der Wächter der Mühle verkündet:\n" "\"Mein Herr, ich habe sehr hart gearbeitet, um Euch diese Ressourcen zur " "Verfügung zu stellen, kommt nächste Woche wieder für mehr.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there are no resources currently available. Please try " "again next week.\"" msgstr "" "Der Wächter der Mühle meldet sich:\n" "\"Mein Herr, es tut mir leid, es sind derzeit keine Ressourcen verfügbar. " "Bitte versucht es nächste Woche noch einmal.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with this gold, come " "back next week for more.\"" msgstr "" "Der Wächter der Mühle verkündet:\n" "\"Mein Herr, ich habe sehr hart gearbeitet, um Euch dieses Gold zu " "beschaffen, kommt nächste Woche wieder für mehr.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there is no gold currently available. Please try again " "next week.\"" msgstr "" "Der Wächter der Mühle meldet:\n" "\"Mein Herr, es tut mir leid, es ist derzeit kein Gold verfügbar. Bitte " "versuch es nächste Woche wieder.\"" msgid "" "You've found an abandoned lean-to.\n" "Poking about, you discover some resources hidden nearby." msgstr "" "Ihr habt einen verlassenen Anbau gefunden.\n" "Beim Herumstöbern findet Ihr einige in der Nähe versteckte Ressourcen." msgid "The lean-to is long abandoned. There is nothing of value here." msgstr "Der Anbau ist schon lange verlassen. Es gibt hier nichts wertvolles." msgid "" "You catch a leprechaun foolishly sleeping amidst a cluster of magic " "mushrooms.\n" "In exchange for his freedom, he guides you to a small pot filled with " "precious things." msgstr "" "Ihr fangt einen Kobold, der dummerweise inmitten eines Haufens von " "Zauberpilzen schläft.\n" "Im Tausch für seine Freilassung, führt er Euch zu einem mit wertvollen " "Dingen gefüllten Topf." msgid "" "You've found a magic garden, the kind of place that leprechauns and faeries " "like to cavort in, but there is no one here today.\n" "Perhaps you should try again next week." msgstr "" "Ihr habt einen Zaubergarten gefunden, einem Ort an dem sich Kobolde und Feen " "gerne tummeln, aber heute ist niemand da.\n" "Vielleicht solltet Ihr es nächste Woche noch einmal versuchen." msgid "You come upon the remains of an unfortunate adventurer." msgstr "Ihr stoßt auf die Überreste eines unglückseligen Abenteurers." msgid "Treasure" msgstr "Schatz" msgid "Searching through the tattered clothing, you find the %{artifact}." msgstr "Beim Durchsuchen der zerlumpten Kleidung findet Ihr %{artifact}." msgid "Searching through the tattered clothing, you find nothing." msgstr "Beim Durchsuchen der zerlumpten Kleidung findet Ihr nichts." msgid "" "You come across an old wagon left by a trader who didn't quite make it to " "safe terrain." msgstr "" "Ihr stoßt auf einen alten Wagen, der von einem Händler zurückgelassen wurde, " "der es nicht bis in sichere Gefilde geschafft hat." msgid "Unfortunately, others have found it first, and the wagon is empty." msgstr "Leider waren schon andere hier, der Wagen ist leer." msgid "Searching inside, you find the %{artifact}." msgstr "Ihr durchsucht ihn und findet ein %{artifact}." msgid "Inside, you find some of the wagon's cargo still intact." msgstr "Im Innern bemerkt Ihr, dass Teile der Ladung noch intakt sind." msgid "You search through the flotsam, and find some wood and some gold." msgstr "Ihr durchsucht das Treibgut und findet etwas Holz und etwas Gold." msgid "You search through the flotsam, and find some wood." msgstr "Ihr durchsucht das Treibgut und findet etwas Holz." msgid "You search through the flotsam, but find nothing." msgstr "Ihr durchsucht das Treibgut, findet aber nichts." msgid "Shrine of the 1st Circle" msgstr "Schrein des ersten Kreises" msgid "" "You come across a small shrine attended by a group of novice acolytes.\n" "In exchange for your protection, they agree to teach you a simple spell - " "'%{spell}'." msgstr "" "Ihr stoßt auf einen kleinen Schrein, der von einer Gruppe Akolyten-Novizen " "besucht wird.\n" "Als Gegenleistung für ihren Schutz erklären sie sich bereit, Euch einen " "einfachen Zauberspruch beizubringen - \"%{spell}\"." msgid "Shrine of the 2nd Circle" msgstr "Schrein des zweiten Kreises" msgid "" "You come across an ornate shrine attended by a group of rotund friars.\n" "In exchange for your protection, they agree to teach you a spell - " "'%{spell}'." msgstr "" "Ihr stoßt auf einen reichverzierten Schrein, der von einer Gruppe rundlicher " "Mönche besucht wird.\n" "Als Gegenleistung für ihren Schutz erklären sie sich bereit, Euch einen " "Zauberspruch zu lehren - \"%{spell}\"." msgid "Shrine of the 3rd Circle" msgstr "Schrein des dritten Kreises" msgid "" "You come across a lavish shrine attended by a group of high priests.\n" "In exchange for your protection, they agree to teach you a sophisticated " "spell - '%{spell}'." msgstr "" "Ihr stoßt auf einen üppigen Schrein, der von einer Gruppe von Hohepriestern " "besucht wird.\n" "Als Gegenleistung für ihren Schutz erklären sie sich bereit, Euch einen " "raffinierten Zauberspruch beizubringen - \"%{spell}\"." msgid "" "\n" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "\n" "Leider habt Ihr nicht die Weisheit, den Zauberspruch zu verstehen, und könnt " "ihn nicht lernen." msgid "" "\n" "Unfortunately, you already have knowledge of this spell, so there is nothing " "more for them to teach you." msgstr "" "\n" "Leider habt Ihr bereits Kenntnisse über diesen Zauber, so dass sie Euch " "nichts mehr beibringen können." msgid "" "\n" "Unfortunately, you have no Magic Book to record the spell with." msgstr "" "\n" "Leider habt Ihr kein Zauberbuch, um den Zauberspruch aufzuschreiben." msgid "" "You approach the hut and observe a witch inside studying an ancient tome on " "%{skill}.\n" "\n" msgstr "" "Ihr nähert Euch der Hütte und beobachtet darin eine Hexe, die einen uralten " "Wälzer über %{skill} studiert.\n" "\n" msgid "" "As you approach, she turns and focuses her one glass eye on you.\n" "\"You already know everything you deserve to learn!\" the witch screeches. " "\"NOW GET OUT OF MY HOUSE!\"" msgstr "" "Als Ihr Euch nähert, dreht sie sich um und richtet ihr einziges Glasauge auf " "Euch.\n" "\"Du weißt bereits alles, was Du zu lernen verdienst!\", kreischt die Hexe. " "\"UND JETZT VERSCHWINDE AUS MEINEM HAUS!\"" msgid "" "As you approach, she turns and speaks.\n" "\"You already know that which I would teach you. I can help you no further.\"" msgstr "" "Als Ihr Euch nähert, dreht sie sich um und spricht.\n" "\"Du weißt bereits, was ich Dich lehren möchte. Ich kann dir nicht weiter " "helfen.\"" msgid "" "An ancient and immortal witch living in a hut with bird's legs for stilts " "teaches you %{skill} for her own inscrutable purposes." msgstr "" "Eine uralte und unsterbliche Hexe, die in einer Hütte lebt und Vogelbeine " "als Stelzen benutzt, lehrt Euch %{skill} für ihre eigenen undurchschaubaren " "Zwecke." msgid "As you drink the sweet water, you gain luck for your next battle." msgstr "" "Als Ihr das süße Wasser trinkt, erhaltet Ihr Glück für Euren nächsten Kampf." msgid "You drink from the enchanted fountain, but nothing happens." msgstr "Ihr trinkt aus dem verwunschenen Brunnen, aber es passiert nichts." msgid "You enter the faerie ring, but nothing happens." msgstr "Ihr betretet den Feenring, aber nichts passiert." msgid "" "Upon entering the mystical faerie ring, your army gains luck for its next " "battle." msgstr "" "Beim Betreten des mystischen Feenrings erhält Eure Armee Glück für die " "nächste Schlacht." msgid "" "You've found an ancient and weathered stone idol.\n" "It is supposed to grant luck to visitors, but since the stars are already " "smiling upon you, it does nothing." msgstr "" "Ihr habt ein uraltes und verwittertes Steingötzenbild gefunden.\n" "Es soll den Besuchern Glück bringen, aber da Euch die Sterne bereits " "zulächeln, bewirkt es nichts." msgid "" "You've found an ancient and weathered stone idol.\n" "Kissing it is supposed to be lucky, so you do. The stone is very cold to the " "touch." msgstr "" "Ihr habt ein altes und verwittertes Steingötzenbild gefunden.\n" "Ihn zu küssen soll Glück bringen, also tut Ihr es. Der Stein fühlt sich sehr " "kalt an." msgid "The mermaids silently entice you to return later and be blessed again." msgstr "" "Die Meerjungfrauen locken Euch leise dazu, später zurückzukehren und Euch " "erneut segnen zu lassen." msgid "" "The magical, soothing beauty of the Mermaids reaches you and your crew.\n" "Just for a moment, you forget your worries and bask in the beauty of the " "moment.\n" "The mermaids charms bless you with increased luck for your next combat." msgstr "" "Die magische, beruhigende Schönheit der Meerjungfrauen erreicht Euch und " "Eure Besatzung.\n" "Nur für einen Moment vergesst Ihr Eure Sorgen und sonnt Euch in der " "Schönheit des Augenblicks.\n" "Der Charme der Meerjungfrauen segnet Euch mit mehr Glück für Euren nächsten " "Kampf." msgid "" "You come upon the pyramid of a great and ancient king.\n" "You are tempted to search it for treasure, but all the old stories warn of " "fearful curses and undead guardians.\n" "Will you search?" msgstr "" "Ihr stoßt auf die Pyramide eines großen Königs aus alten Zeiten.\n" "Ihr seid versucht, sie nach Schätzen zu durchsuchen, aber all die alten " "Geschichten warnen vor schrecklichen Flüchen und untoten Wächtern.\n" "Werdet Ihr suchen gehen?" msgid "" "Upon defeating the monsters, you decipher an ancient glyph on the wall, " "telling the secret of the spell - '" msgstr "" "Nachdem Ihr die Monster besiegt habt, entziffert Ihr alte Schriftzeichen an " "der Wand, die einen geheimen Zauberspruch preisgeben - '" msgid "Unfortunately, you have no Magic Book to record the spell with." msgstr "Leider habt Ihr kein Zauberbuch, um den Zauberspruch aufzuschreiben. " msgid "" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "Leider habt Ihr nicht die Weisheit, den Zauberspruch zu verstehen, und könnt " "ihn nicht lernen." msgid "" "You come upon the pyramid of a great and ancient king.\n" "Routine exploration reveals that the pyramid is completely empty." msgstr "" "Ihr stoßt auf die Pyramide eines großen Königs aus alten Zeiten.\n" "Die routinemäßige Erkundung zeigt, dass die Pyramide völlig leer ist." msgid "" "A drink at the well is supposed to restore your spell points, but you are " "already at maximum." msgstr "" "Ein Schluck aus dem Brunnen soll Eure Zauberpunkte wiederherstellen, aber " "sie sind bereits am Maximum." msgid "A second drink at the well in one day will not help you." msgstr "" "Ein zweiter Schluck aus dem Brunnen am selben Tag wird Euch nicht helfen." msgid "A drink from the well has restored your spell points to maximum." msgstr "" "Ein Schluck aus dem Brunnen hat Eure maximalen Zauberpunkte " "wiederhergestellt." msgid "" "\"I'm sorry sir,\" The leader of the soldiers says, \"but you already know " "everything we have to teach.\"" msgstr "" "\"Es tut mir leid, Herr\", sagt der Anführer der Soldaten, \"aber Ihr wisst " "bereits alles, was wir Euch beibringen könnten.\"" msgid "The soldiers living in the fort teach you a few new defensive tricks." msgstr "" "Die im Fort lebenden Soldaten bringen Euch ein paar neue Verteidigungstricks " "bei." msgid "" "You've come upon a mercenary camp practicing their tactics. \"You're too " "advanced for us,\" the mercenary captain says. \"We can teach nothing more.\"" msgstr "" "Ihr seid auf ein Lager gestoßen, wo Söldner ihre Taktik üben. \"Ihr seid zu " "weit fortgeschritten für uns\", sagt der Söldnerhauptmann. \"Wir können Euch " "nichts mehr beibringen.\"" msgid "" "You've come upon a mercenary camp practicing their tactics. The mercenaries " "welcome you and your troops and invite you to train with them." msgstr "" "Ihr seid auf ein Lager gestoßen, wo Söldner ihre Taktik üben. Die Söldner " "begrüßen Euch und Eure Truppen und laden Euch ein, mitzutrainieren. " msgid "\"Go 'way!\", the witch doctor barks, \"you know all I know.\"" msgstr "" "\"Geh weg!\", schnauzt der Medizinmann, \"Du weißt alles, was ich weiß.\"" msgid "" "An Orcish witch doctor living in the hut deepens your knowledge of magic by " "showing you how to cast stones, read portents, and decipher the intricacies " "of chicken entrails." msgstr "" "Ein Ork-Medizinmann, der in der Hütte lebt, vertieft Eure Kenntnisse über " "Magie, indem er Euch zeigt, wie man Steine wirft, Vorzeichen liest und die " "Verwicklungen von Hühnerinnereien entschlüsselt." msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, the Druids turn you away, indicating they have nothing " "new to teach you." msgstr "" "Ihr habt eine Gruppe von Druiden gefunden, die eines ihrer seltsamen " "Steinbauten anbeten. Die Druiden weisen Euch schweigend ab und geben zu " "verstehen, dass sie Euch nichts Neues beibringen können." msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, they teach you new ways to cast spells." msgstr "" "Ihr habt eine Gruppe von Druiden gefunden, die eines ihrer seltsamen " "Steinbauten anbeten. Im Stillen bringen diese Euch neue Wege bei " "Zaubersprueche anzuwenden." msgid "" "You tentatively approach the burial ground of ancient warriors. Do you want " "to search the graves?" msgstr "" "Ihr nähert Euch zögerlich der Ruhestätte alter Krieger. Wollt Ihr die Gräber " "durchsuchen?" msgid "" "You spend several hours searching the graves and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" "Ihr verbringt mehrere Stunden damit, die Gräber zu durchsuchen und findet " "nichts. Ein solch abscheulicher Akt senkt die Moral Eurer Armee." msgid "Upon defeating the Zombies you search the graves and find something!" msgstr "" "Nachdem Ihr die Zombies besiegt habt, durchsucht Ihr die Gräber und findet " "etwas!" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the shipwreck?" msgstr "" "Der faulige Rumpf eines großen Piratenschiffs knarrt unheimlich, als er " "gegen die Felsen gedrückt wird. Wollt Ihr das Schiffswrack durchsuchen?" msgid "" "You spend several hours sifting through the debris and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" "Nachdem Ihr die Geister besiegt habt, verbringt Ihr mehrere Stunden damit, " "die Trümmer zu durchsuchen und findet nichts. Solch eine " "verabscheuungswürdige Tat senkt die Moral Eurer Armee." msgid "" "Upon defeating the Ghosts you sift through the debris and find something!" msgstr "" "Nachdem Ihr die Geister besiegt habt, durchsucht Ihr die Trümmer und finden " "etwas!" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the ship?" msgstr "" "Der faulige Rumpf eines großen Piratenschiffs knarrt unheimlich, als er " "gegen die Felsen gedrückt wird. Wollt Ihr das Schiffswrack durchsuchen?" msgid "" "Upon defeating the Skeletons you sift through the debris and find something!" msgstr "" "Nachdem Ihr die Skelette besiegt habt, durchsucht Ihr die Trümmer und finden " "etwas!" msgid "Your men spot a navigational buoy, confirming that you are on course." msgstr "" "Eure Männer entdecken eine Navigationsboje, die Euch bestätigt, dass Ihr auf " "Kurs seid." msgid "" "Your men spot a navigational buoy, confirming that you are on course and " "increasing their morale." msgstr "" "Eure Männer entdecken eine Navigationsboje, die Euch bestätigt, dass Ihr auf " "Kurs seid. Dies steigert Eure Moral." msgid "" "The drink at the oasis is refreshing, but offers no further benefit. The " "oasis might help again if you fought a battle first." msgstr "" "Der Schluck an der Oase ist erfrischend, bietet aber keinen weiteren Nutzen. " "Die Oase könnte helfen, wenn Ihr zuvor eine Schlacht geschlagen habt." msgid "" "A drink at the oasis fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" "Ein Schluck an der Oase gibt Eure Truppen Stärke und hebt deren Stimmung. So " "könnt Ihr heute etwas weiter reisen." msgid "" "The drink at the watering hole is refreshing, but offers no further benefit. " "The watering hole might help again if you fought a battle first." msgstr "" "Der Schluck an der Wasserstelle ist erfrischend, bietet aber keinen weiteren " "Nutzen. Die Wasserstelle könnte helfen, wenn Ihr zuvor eine Schlacht " "geschlagen habt." msgid "" "A drink at the watering hole fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" "Ein Schluck an dem Wasserstelle gibt Eure Truppen Stärke und hebt deren " "Stimmung. So könnt Ihr heute etwas weiter reisen." msgid "" "It doesn't help to pray twice before a battle. Come back after you've fought." msgstr "" "Es nützt nichts, vor einem Kampf zweimal zu beten. Kommt zurück, nachdem Ihr " "gekämpft habt." msgid "A visit and a prayer at the temple raises the morale of your troops." msgstr "Ein Besuch des Tempels und ein Gebet heben die Moral Eurer Truppen." msgid "" "An old Knight appears on the steps of the gazebo. \"I am sorry, my liege, I " "have taught you all I can.\"" msgstr "" "Ein alter Ritter erscheint auf den Stufen des Pavillons. \"Es tut mir leid, " "mein Herr, ich habe Euch alles beigebracht, was ich kann.\"" msgid "" "An old Knight appears on the steps of the gazebo. \"My liege, I will teach " "you all that I know to aid you in your travels.\"" msgstr "" "Ein alter Ritter erscheint auf den Stufen des Pavillons. \"Mein Herr, ich " "werde Euch alles lehren, was ich weiß, um Euch auf Eurer Reise zu helfen.\"" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he says, \"I would give you an artifact as a reward, but " "you're all full.\"" msgstr "" "Ihr habt einen Schiffbrüchigen vor dem sicheren Tod im unbarmherzigen Ozean " "gerettet. Dankbar sagt er: \"Ich würde Euch ein Artefakt als Belohnung " "geben, aber Ihr seid schon voll davon.\"" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he rewards you for your act of kindness by giving you the " "%{art}." msgstr "" "Ihr habt einen Schiffbrüchigen vor dem sicheren Tod im unbarmherzigen Ozean " "gerettet. Aus Dankbarkeit belohnt er Euch für Euren Akt der Freundlichkeit, " "indem er Euch die %{art} schenkt." msgid "A leprechaun offers you the %{art} for the small price of %{gold} Gold." msgstr "" "Ein Kobold bietet Euch die %{art} für den kleinen Preis von %{gold} Gold an." msgid "" "A leprechaun offers you the %{art} for the small price of %{gold} Gold and " "%{count} %{res}." msgstr "" "Ein Kobold bietet Euch die %{art} für den kleinen Preis von %{gold} Gold und " "%{count} %{res} an." msgid "Do you wish to buy this artifact?" msgstr "Möchtet Ihr dieses Artefakt kaufen?" msgid "" "You try to pay the leprechaun, but realize that you can't afford it. The " "leprechaun stamps his foot and ignores you." msgstr "" "Ihr wollt den Kobold bezahlen, merkt aber, dass Ihr es Euch nicht leisten " "könnt. Der Kobold stampft mit dem Fuß auf und ignoriert Euch." msgid "" "Insulted by your refusal of his generous offer, the leprechaun stamps his " "foot and ignores you." msgstr "" "Beleidigt durch Eure Ablehnung seines großzügigen Angebots, stampft der " "Kobold mit dem Fuß auf und ignoriert Euch." msgid "You've found the artifact: " msgstr "Ihr habt das Artefakt gefunden: " msgid "" "You've found the humble dwelling of a withered hermit. The hermit tells you " "that he is willing to give the %{art} to the first wise person he meets." msgstr "" "Ihr habt die bescheidene Behausung eines vertrockneten Einsiedlers gefunden. " "Der Einsiedler erzählt Euch, dass er bereit ist, die %{art} dem ersten " "weisen Menschen zu geben, den er trifft." msgid "" "You've come across the spartan quarters of a retired soldier. The soldier " "tells you that he is willing to pass on the %{art} to the first true leader " "he meets." msgstr "" "Ihr seid auf das spartanische Quartier eines pensionierten Soldaten " "gestoßen. Der Soldat erzählt Euch, dass er bereit ist, die %{art} an den " "ersten wahren Anführer weiterzugeben, den er trifft." msgid "" "You've encountered a strange person with a hat and an owl on it. He tells " "you that he is willing to give %{art} if you have %{skill}." msgstr "" "Ihr seid einer seltsamen Person mit einem Hut und einer Eule darauf " "begegnet. Er sagt Euch, dass er bereit ist, %{art} zu geben, wenn Ihr " "%{skill} habt." msgid "" "You come upon an ancient artifact. As you reach for it, a pack of Rogues " "leap out of the brush to guard their stolen loot." msgstr "" "Ihr stoßt auf ein antikes Artefakt. Als Ihr danach greift, springt eine " "Bande Schurken aus dem Gebüsch, um ihre gestohlene Beute zu verteidigen." msgid "" "Through a clearing you observe an ancient artifact. Unfortunately, it's " "guarded by a nearby %{monster}. Do you want to fight the %{monster} for the " "artifact?" msgstr "" "Auf einer Lichtung entdeckt Ihr ein antikes Artefakt. Leider wird es von " "einem %{monster} in der Nähe bewacht. Wollt Ihr gegen %{monster} um das " "Artefakt kämpfen?" msgid "Victorious, you take your prize, the %{art}." msgstr "Siegreich nehmt Ihr Euren Preis, die %{art}, entgegen." msgid "" "Discretion is the better part of valor, and you decide to avoid this fight " "for today." msgstr "" "Besonnenheit ist wichtiger als Werte und so beschließt Ihr, diesen Kampf für " "heute zu vermeiden." msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold pieces." msgstr "" "Nachdem Ihr stundenlang versucht habt, die Kiste aus dem Meer zu fischen, " "öffnet Ihr sie und findet %{gold}-Goldstücke." msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold and the %{art}." msgstr "" "Nachdem Ihr stundenlang versucht habt, die Kiste aus dem Meer zu fischen, " "öffnet Ihr sie und findet %{gold} Gold und die %{art}." msgid "" "After spending hours trying to fish the chest out of the sea, you open it, " "only to find it empty." msgstr "" "Nachdem Ihr stundenlang versucht habt, die Kiste aus dem Meer zu fischen, " "öffnet Ihr sie, nur um sie leer vorzufinden." msgid "" "After scouring the area, you fall upon a hidden treasure cache. You may take " "the gold or distribute the gold to the peasants for experience. Do you wish " "to keep the gold?" msgstr "" "Nachdem Ihr das Gebiet durchkämmt habt, stoßt Ihr auf eine verborgene " "Schatzkiste. Ihr könnt das Gold nehmen oder das Gold an die Bauern gegen " "Erfahrungspunkte verteilen. Möchtet Ihr das Gold behalten?" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "%{gold} gold pieces." msgstr "" "Nachdem Ihr das Gebiet durchkämmt habt, stoßt Ihr auf eine versteckte Kiste, " "die %{gold} Goldstücke enthält." msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "ancient artifact %{art}." msgstr "" "Nachdem Ihr das Gebiet durchkämmt habt, stoßt Ihr auf eine versteckte Kiste, " "die das antike Artefakt %{art} enthält." msgid "" "You stumble upon a dented and tarnished lamp lodged deep in the earth. Do " "you wish to rub the lamp?" msgstr "" "Ihr stolpert über eine verbeulte und angeschlagene Lampe, die tief im Boden " "steckt. Möchtet Ihr an der Lampe reiben?" msgid "" "You have taken control of the local Alchemist shop. It will provide you with " "%{count} unit of Mercury per day." msgstr "" "Ihr habt nun die Kontrolle über den örtlichen Alchemistenladen. Er versorgt " "Euch mit %{count} Einheit Quecksilber pro Tag." msgid "" "You gain control of a sawmill. It will provide you with %{count} units of " "wood per day." msgstr "" "Ihr erlangt die Kontrolle über ein Sägewerk. Es wird Euch mit %{count} " "Einheiten Holz pro Tag versorgen." msgid "" "You gain control of an ore mine. It will provide you with %{count} units of " "ore per day." msgstr "" "Ihr erlangt die Kontrolle über eine Erzmine. Sie versorgt Euch mit %{count} " "Einheiten Erz pro Tag." msgid "" "You gain control of a sulfur mine. It will provide you with %{count} unit of " "sulfur per day." msgstr "" "Ihr erlangt die Kontrolle über eine Schwefelmine. Sie versorgt Euch mit " "%{count} Einheiten Schwefel pro Tag." msgid "" "You gain control of a crystal mine. It will provide you with %{count} unit " "of crystal per day." msgstr "" "Ihr erlangt die Kontrolle über eine Kristallmine. Sie versorgt Euch mit " "%{count} Einheit Kristall pro Tag." msgid "" "You gain control of a gem mine. It will provide you with %{count} unit of " "gems per day." msgstr "" "Ihr erlangt die Kontrolle über eine Edelsteinmine. Sie versorgt Euch mit " "%{count} Einheit Edelsteine pro Tag." msgid "" "You gain control of a gold mine. It will provide you with %{count} gold per " "day." msgstr "" "Ihr erlangt die Kontrolle über eine Goldmine. Sie versorgt Euch mit %{count} " "Gold pro Tag." msgid "" "The lighthouse is now under your control, and all of your ships will now " "move further each day." msgstr "" "Der Leuchtturm ist nun unter Eurer Kontrolle, und all Eure Schiffe bewegen " "sich nun jeden Tag weiter." msgid "You gain control of a %{name}." msgstr "Ihr erlangt die Kontrolle über einen %{name}." msgid "" "You come upon an abandoned gold mine. The mine appears to be haunted. Do you " "wish to enter?" msgstr "" "Ihr stoßt auf eine verlassene Goldmine. In der Mine scheint es zu spuken. " "Möchtet Ihr hineingehen?" msgid "You beat the Ghosts and are able to restore the mine to production." msgstr "" "Ihr habt die Geister besiegt und könnt die Mine wieder in Betrieb nehmen." msgid "" "A group of %{monster} with a desire for greater glory wish to join you. Do " "you accept?" msgstr "" "Eine Gruppe von %{monster} mit dem Wunsch nach größerem Ruhm möchte sich " "Euch anschließen. Akzeptiert Ihr?" msgid "As you approach the dwelling, you notice that there is no one here." msgstr "Als Ihr Euch der Behausung nähert, bemerkt Ihr, dass niemand da ist." msgid "" "You search the ruins, but the Medusas that used to live here are gone. " "Perhaps there will be more next week." msgstr "" "Ihr durchsucht die Ruinen, aber die Medusen, die hier früher lebten, sind " "weg. Vielleicht gibt es nächste Woche mehr." msgid "" "You've found some Medusas living in the ruins. They are willing to join your " "army for a price. Do you want to recruit Medusas?" msgstr "" "Ihr habt einige Medusen gefunden, die in den Ruinen leben. Sie sind bereit, " "sich Eurer Armee für einen bestimmten Preis anzuschließen. Möchtet Ihr " "Medusen rekrutieren?" msgid "" "You've found a Sprite Tree City. Unfortunately, none of the Sprites living " "there wish to join an army. Maybe next week." msgstr "" "Ihr habt ein Baumhaus gefunden. Leider ist keine der hier lebenden Feen " "bereit, sich Eurer Armee anzuschließen. Vielleicht in der nächsten Woche." msgid "" "Some of the Sprites living in the tree city are willing to join your army " "for a price. Do you want to recruit Sprites?" msgstr "" "Einige der im Baumhaus lebenden Feen sind bereit, sich Eurer Armee für einen " "bestimmten Preis anzuschließen. Möchtet Ihr Feen rekrutieren?" msgid "" "A colorful Rogues' wagon stands empty here. Perhaps more Rogues will be here " "later." msgstr "" "Ein bunter Wagen der Schurken steht leer da. Vielleicht sind später ein paar " "Schurken hier." msgid "" "Distant sounds of music and laughter draw you to a colorful wagon housing " "Rogues. Do you wish to have any Rogues join your army?" msgstr "" "Ferne Klänge von Musik und Gelächter führen Euch zu einem bunten Wagen, in " "dem Schurken wohnen. Möchtet Ihr, dass sich Schurken Eurer Armee anschließen?" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. The " "tents are unoccupied. Perhaps more Nomads will be here later." msgstr "" "Ihr bemerket eine Gruppe zerlumpter Zelte, die sich im sandigen Wind wiegen. " "Die Zelte stehen leer. Vielleicht sind spaeter ein paar Nomaden hier. " msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. Do you " "wish to have any Nomads join you during your travels?" msgstr "" "Ihr bemerkt eine Gruppe zerlumpter Zelte, die sich im sandigen Wind wiegen. " "Wünscht Ihr, dass sich Euch Nomaden auf Eurer Reise anschließen?" msgid "The pit of mud bubbles for a minute and then lies still." msgstr "Die Schlammgrube blubbert für einen Moment und ist dann wieder ruhig." msgid "" "As you approach the bubbling pit of mud, creatures begin to climb out and " "position themselves around it. In unison they say: \"Mother Earth would like " "to offer you a few of her troops. Do you want to recruit Earth Elementals?\"" msgstr "" "Als Ihr Euch der blubbernden Schlammgrube nähert, beginnen Kreaturen " "herauszuklettern und umstellen Euch. Im Enklang sagen sie: \"Mutter Erde " "möchte Euch ein paar ihrer Truppen anbieten. Wollt Ihr Erdwesen rekrutieren?" "\"" msgid "You enter the structure of white stone pillars, and find nothing." msgstr "Ihr betretet die Struktur aus weißen Steinsäulen und findet nichts." msgid "" "White stone pillars support a roof that rises up to the sky. As you enter " "the structure, the dead air of the outside gives way to a whirling gust that " "almost pushes you back out. The air current materializes into a barely " "visible form. The creature asks, in what can only be described as a loud " "whisper: \"Why have you come? Are you here to call upon the forces of the " "air?\"" msgstr "" "Weiße Steinsäulen stützen ein Dach, das sich in den Himmel erhebt. Als Ihr " "das Bauwerk betretet, weicht die tote Luft des Außenbereichs einem " "wirbelnden Windstoß, der Euch fast wieder hinausdrängt. Der Luftstrom " "materialisiert sich zu einer kaum sichtbaren Gestalt. Die Kreatur fragt, was " "man nur als lautes Flüstern bezeichnen kann: \"Warum bist du gekommen? Bist " "du hier, um die Mächte der Luft anzurufen?\"" msgid "No Fire Elementals approach you from the lava pool." msgstr "Es nähern sich keine Feuerwesen aus dem Lavapool." msgid "" "Beneath a structure that serves to hold in heat, Fire Elementals move about " "in a fiery pool of molten lava. A group of them approach you and offer their " "services. Would you like to recruit Fire Elementals?" msgstr "" "Unter einer Struktur, die dazu dient, die Hitze zurückzuhalten, bewegen sich " "Feuerwesen in einem feurigen Becken aus geschmolzener Lava. Eine Gruppe von " "ihnen kommt auf Euch zu und bietet Euch ihre Dienste an. Möchtet Ihr " "Feuerwesen rekrutieren?" msgid "A face forms in the water for a moment, and then is gone." msgstr "" "Ein Gesicht formt sich für einen Moment im Wasser und ist dann verschwunden." msgid "" "Crystalline structures cast shadows over a small reflective pool of water. " "You peer into the pool, and a face that is not your own peers back. It asks: " "\"Would you like to call upon the powers of water?\"" msgstr "" "Kristalline Strukturen werfen Schatten auf ein kleines, spiegelndes " "Wasserbecken. Ihr blickt in das Becken, und ein Gesicht, das nicht Euer " "eigenes ist, blickt zurück. Es fragt: \"Möchtet Ihr die Kräfte des Wassers " "anrufen?\"" msgid "This burial site is deathly still." msgstr "Diese Grabstätte ist totenstill." msgid "" "Restless spirits of long dead warriors seeking their final resting place " "offer to join you in hopes of finding peace. Do you wish to recruit ghosts?" msgstr "" "Rastlose Geister längst verstorbener Krieger, die ihre letzte Ruhe suchen," "bieten Euch an, sich Euch anzuschließen, in der Hoffnung, Frieden zu finden. " "Möchtet Ihr Geister rekrutieren?" msgid "" "The City of the Dead is empty of life, and empty of unlife as well. Perhaps " "some undead will move in next week." msgstr "" "In der Stadt der Toten gibt es kein Leben aber auch keine Untoten. " "Vielleicht werden nächste Woche einige Untote eingezogen sein." msgid "" "Some Liches living here are willing to join your army for a price. Do you " "want to recruit Liches?" msgstr "" "Einige hier lebende Liches sind bereit, sich Eurer Armee gegen einen Preis " "anzuschließen. Wollt Ihr Liches rekrutieren?" msgid "" "You've found the ruins of an ancient city, now inhabited solely by the " "undead. Will you search?" msgstr "" "Ihr habt die Ruinen einer alten Stadt gefunden, die jetzt nur noch von " "Untoten bewohnt wird. Werdet Ihr danach suchen?" msgid "" "Some of the surviving Liches are impressed by your victory over their " "fellows, and offer to join you for a price. Do you want to recruit Liches?" msgstr "" "Einige der überlebenden Liches sind von Eurem Sieg über ihre Gefährten " "beeindruckt und bieten Euch an, sich Euch gegen einen Preis anzuschließen. " "Wollt Ihr Liches rekrutieren?" msgid "" "You've found one of those bridges that Trolls are so fond of living under, " "but there are none here. Perhaps there will be some next week." msgstr "" "Ihr habt eine dieser Brücken gefunden, unter denen Trolle so gerne leben, " "aber es sind keine hier. Vielleicht wird es nächste Woche hier welche geben." msgid "" "Some Trolls living under a bridge are willing to join your army, but for a " "price. Do you want to recruit Trolls?" msgstr "" "Einige Trolle, die unter einer Brücke leben, sind bereit gegen einen Preis " "Eurer Armee beizutreten. Wollt Ihr Trolle rekrutieren?" msgid "Trolls living under the bridge challenge you. Will you fight them?" msgstr "" "Trolle, die unter der Brücke leben, fordern Euch heraus. Werdet Ihr sie " "bekämpfen?" msgid "" "A few Trolls remain, cowering under the bridge. They approach you and offer " "to join your forces as mercenaries. Do you want to buy any Trolls?" msgstr "" "Ein paar Trolle bleiben zusammengekauert unter der Bruecke. Sie nähern sich " "Euch und bieten an, sich Euren Truppen anzuschliessen. Wollt Ihr Trolle " "rekrutieren?" msgid "" "The Dragon city has no Dragons willing to join you this week. Perhaps a " "Dragon will become available next week." msgstr "" "Die Drachenstadt hat diese Woche keine Drachen, die bereit sind, sich Euch " "anzuschließen. Vielleicht wird nächste Woche ein Drache zur Verfügung stehen." msgid "" "The Dragon city is willing to offer some Dragons for your army for a price. " "Do you wish to recruit Dragons?" msgstr "" "Die Drachenstadt ist bereit, Euch gegen einen Preis einige Drachen für Eure " "Armee anzubieten. Möchtet Ihr Drachen rekrutieren?" msgid "" "You stand before the Dragon City, a place off-limits to mere humans. Do you " "wish to violate this rule and challenge the Dragons to a fight?" msgstr "" "Ihr steht vor der Drachenstadt, ein Ort, an dem Menschen keinen Zutritt " "haben. Wollt Ihr gegen diese Regel verstoßen und die Drachen zu einem Kampf " "herausfordern?" msgid "" "Having defeated the Dragon champions, the city's leaders agree to supply " "some Dragons to your army for a price. Do you wish to recruit Dragons?" msgstr "" "Da Ihr die Drachenmeister besiegt habt, erklären sich die Anführer der Stadt " "bereit, Eurer Armee einige Drachen für einen bestimmten Preis zur Verfügung " "zu stellen. Möchtet Ihr Drachen rekrutieren?" msgid "From the observation tower, you are able to see distant lands." msgstr "Vom Beobachtungsturm aus könnt Ihr ferne Ländereien sehen." msgid "" "The spring only refills once a week, and someone's already been here this " "week." msgstr "" "Die Quelle füllt sich nur einmal die Woche, und diese Woche war schon jemand " "hier." msgid "" "A drink at the spring is supposed to give you twice your normal spell " "points, but you are already at that level." msgstr "" "Ein Schluck an der Quelle soll Euch das Doppelte Eurer normalen Zauberpunkte " "geben, aber sie sind bereits auf dieser Stufe." msgid "" "A drink from the spring fills your blood with magic! You have twice your " "normal spell points in reserve." msgstr "" "Ein Schluck aus der Quelle füllt Euer Blut mit Magie! Ihr habt das Doppelte " "Eurer normalen Zauberpunkte in Reserve." msgid "" "Recognizing you, the butler refuses to admit you. \"The master,\" he says, " "\"will not see the same student twice.\"" msgstr "" "Der Butler erkennt Euch und verweigert Euch den Einlass. \"Der Hausherr\", " "sagt er, \"möchte denselben Schüler nicht zweimal sehen.\"" msgid "" "The butler admits you to see the master of the house. He trains you in the " "four skills a hero should know." msgstr "" "Der Butler gewährt Euch Einlass zum Hausherr. Dieser bildet Euch aus in den " "vier Fertigkeiten, die ein Held beherrschen sollte." msgid "" "The butler opens the door and looks you up and down. \"You are neither " "famous nor diplomatic enough to be admitted to see my master,\" he sniffs. " "\"Come back when you think yourself worthy.\"" msgstr "" "Der Butler öffnet die Tür und mustert Euch von oben bis unten. \"Ihr seid " "weder berühmt noch diplomatisch genug, um zu meinem Herrn vorgelassen zu " "werden\", schnieft er. \"Kommt wieder, wenn Ihr Euch für würdig haltet.\"" msgid " and " msgstr " und " msgid "" "All of the %{monsters} you have in your army have been trained by the battle " "masters of the fort. Your army now contains %{monsters2}." msgstr "" "Alle %{monsters}, die Ihr in Eurer Armee habt, wurden von den Kampfmeistern " "der Forts ausgebildet. Eure Armee beinhaltet nun %{monsters2}." msgid "" "An unusual alliance of Ogres, Orcs, and Dwarves offer to train (upgrade) any " "such troops brought to them. Unfortunately, you have none with you." msgstr "" "Eine ungewöhnliche Allianz aus Ogern, Orks und Zwergen bietet an, alle " "solche Truppen, die zu ihnen gebracht werden, auszubilden (aufzurüsten). " "Leider habt Ihr keine davon dabei." msgid "All of your %{monsters} have been upgraded into %{monsters2}." msgstr "All Eure %{monsters} wurden zu %{monsters2} aufgerüstet." msgid "" "A blacksmith working at the foundry offers to convert all Pikemen and " "Swordsmen's weapons brought to him from iron to steel. He also says that he " "knows a process that will convert Iron Golems into Steel Golems. " "Unfortunately, you have none of these troops in your army, so he can't help " "you." msgstr "" "Ein Schmied, der in der Gießerei arbeitet, bietet an, die Waffen von " "Speermänner und Schwertkämpfer, die zu ihm gebracht werden, von Eisen in " "Stahl umzuwandeln. Er sagt auch, dass er ein Verfahren kennt, mit dem man " "Eisengoleme in Stahlgoleme umwandeln kann. Leider habt Ihr keine dieser " "Truppen in Eurer Armee, sodass er Euch nicht helfen kann." msgid "" "The captain looks at you with surprise and says:\n" "\"You already have all the maps I know about. Let me fish in peace now.\"" msgstr "" "Der Kapitän sieht Euch überrascht an und sagt:\n" "\"Ihr habt bereits alle Karten, die ich kenne. Lasst mich jetzt in Ruhe " "fischen.\"" msgid "" "A retired captain living on this refurbished fishing platform offers to sell " "you maps of the sea he made in his younger days for 1,000 gold. Do you wish " "to buy the maps?" msgstr "" "Ein pensionierten Kapitän, der die renovierten Angelplattform bewohnt, " "bietet Euch für 1.000 Goldstücke Seekarten zum Kauf an, die er in seinen " "jüngeren Tagen angefertigt hat. Möchtet Ihr die Karten kaufen?" msgid "" "The captain sighs. \"You don't have enough money, eh? You can't expect me " "to give my maps away for free!\"" msgstr "" "Der Kapitän seufzt. \"Du hast nicht genug Geld, was? Du kannst nicht " "erwarten, dass ich meine Karten umsonst herausgebe!\"" msgid "" "You come upon an obelisk made from a type of stone you have never seen " "before. Staring at it intensely, the smooth surface suddenly changes to an " "inscription. The inscription is a piece of a lost ancient map. Quickly you " "copy down the piece and the inscription vanishes as abruptly as it appeared." msgstr "" "Ihr stoßt auf einen Obelisken, aus einer Steinsorte gemacht, die Ihr noch " "nie gesehen habt. Ihr starrt ihn intensiv an, als auf der glatten Oberfläche " "plötzlich eine Inschrift erscheint. Die Inschrift ist ein Stück einer alten " "verlorenen Karte. Schnell malt Ihr das Stück ab und die Inschrift " "verschwindet so schnell, wie sie erschienen war." msgid "You have already been to this obelisk." msgstr "Ihr wart bereits an diesem Obelisken." msgid "" "Upon your approach, the tree opens its eyes in delight. \"It is good to see " "you, my student. I hope my teachings have helped you.\"" msgstr "" "Als Ihr Euch nähert, öffnet der Baum erfreut seine Augen. \"Es ist schön, " "Dich zu sehen, mein Schüler. Ich hoffe, meine Lehren haben Dir geholfen.\"" msgid "" "Upon your approach, the tree opens its eyes in delight. \"Ahh, an " "adventurer! Allow me to teach you a little of what I have learned over the " "ages.\"" msgstr "" "Als Ihr Euch nähert, öffnet der Baum erfreut seine Augen. \"Ahh, ein " "Abenteurer! Erlaube mir, Dich ein wenig von dem zu lehren, was ich im Laufe " "der Zeit gelernt habe.\"" msgid "Upon your approach, the tree opens its eyes in delight." msgstr "Als Ihr Euch nähert, öffnet der Baum erfreut seine Augen." msgid "" "\"Ahh, an adventurer! I will be happy to teach you a little of what I have " "learned over the ages for a mere %{count} %{res}.\"" msgstr "" "\"Ahh, ein Abenteurer! Ich bringe Dir gerne ein wenig von dem bei, was ich " "im Laufe der Zeit gelernt habe, für nur %{count} %{res}.\"" msgid "(Just bury it around my roots.)" msgstr "(Vergrabe es einfach neben meine Wurzeln.)" msgid "Tears brim in the eyes of the tree." msgstr "In den Augen des Baumes kullern Tränen." msgid "\"I need %{count} %{res}.\"" msgstr "\"Ich brauche %{count} %{res}.\"" msgid "it whispers. (sniff) \"Well, come back when you can pay me.\"" msgstr "" "flüstert er. (schnieft) \"Na, dann komm wieder, wenn du mich bezahlen kannst." "\"" msgid "" "Nestled among the trees sits a blind seer. After you explain the intent of " "your journey, the seer activates his crystal ball, allowing you to see the " "strengths and weaknesses of your opponents." msgstr "" "Eingebettet zwischen den Bäumen sitzt ein blinder Wahrsager. Nachdem Ihr die " "Absicht Eurer Reise erklärt habt, aktiviert er seine Kristallkugel und Ihr " "könnt die Stärken und Schwächen Eurer Gegner sehen." msgid "" "The entrance to the cave is dark, and a foul, sulfurous smell issues from " "the cave mouth. Will you enter?" msgstr "" "Der Eingang der Höhle ist dunkel, und ein fauliger, schwefliger Geruch " "strömt aus dem Höhleneingang. Werdet Ihr eintreten?" msgid "Except for evidence of a terrible battle, the cave is empty." msgstr "Bis auf die Spuren einer schrecklichen Schlacht ist die Höhle leer." msgid "" "You find a powerful and grotesque Demon in the cave. \"Today,\" it rasps, " "\"you will fight and surely die. But I will give you a choice of deaths. You " "may fight me, or you may fight my servants. Do you prefer to fight my " "servants?\"" msgstr "" "Ihr findet einen mächtigen und grotesken Dämon in der Höhle. \"Heute\", " "krächzt er, \"wirst Du kämpfen und sicher sterben. Aber ich lasse Dir die " "Wahl zwischen zwei Todesarten. Du kannst gegen mich kämpfen, oder gegen " "meine Diener. Willst Du lieber gegen meine Diener kämpfen?\"" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points." msgstr "" "Der Dämon schreit herausfordernd und greift an! Nach einem kurzen, " "verzweifelten Kampf erschlagt Ihr das Monster und erhaltet %{exp} " "Erfahrungspunkte." msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points and " "%{count} gold." msgstr "" "Der Dämon schreit herausfordernd und greift an! Nach einem kurzen, " "verzweifelten Kampf erschlagt Ihr das Monster und erhaltet %{exp} " "Erfahrungspunkte und %{count} Goldstücke." msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and find the %{art} in the back of the cave." msgstr "" "Der Dämon schreit herausfordernd und greift an! Nach einem kurzen, " "verzweifelten Kampf erschlagt Ihr das Monster und findet die %{art} im " "hinteren Teil der Höhle." msgid "" "Seeing that you do not have %{count} gold, the demon slashes you with its " "claws, and the last thing you see is a red haze." msgstr "" "Als der Dämon sieht, dass Ihr keine %{count} Goldstücke habt, schlitzt er " "Euch mit seinen Klauen auf. Das Letzte, was Ihr seht, ist ein roter Schleier." msgid "" "The Demon leaps upon you and has its claws at your throat before you can " "even draw your sword. \"Your life is mine,\" it says. \"I will sell it back " "to you for %{count} gold.\"" msgstr "" "Der Dämon stürzt sich auf Euch und krallt sich in Eure Kehle, bevor Ihr " "überhaupt Euer Schwert ziehen könnt. \"Dein Leben ist mein\", sagt er. \"Ich " "werde es Dir für %{count} Goldstücke zurückverkaufen.\"" msgid "" "Upon defeating the daemon's servants, you find a hidden cache with %{count} " "gold." msgstr "" "Nachdem Ihr die Diener des Dämons besiegt habt, findet Ihr ein verborgenes " "Versteck mit %{count} Goldstücken." msgid "" "As you enter the Alchemist's Tower, a hobbled, graying man in a brown cloak " "makes his way towards you." msgstr "" "Als Ihr den Alchemistenturm betretet, kommt ein humpelnder, ergrauter Mann " "in einem braunen Umhang auf Euch zu." msgid "He checks your pack, and sees that you have 1 cursed item." msgid_plural "" "He checks your pack, and sees that you have %{count} cursed items." msgstr[0] "" "Er prüft Euer Gepäck und sieht, dass Ihr einen verfluchten Gegenstand habt." msgstr[1] "" "Er prüft Euer Gepäck und sieht, dass Ihr %{count} verfluchte Gegenstände " "habt." msgid "For %{gold} gold, the alchemist will remove it for you. Do you pay?" msgid_plural "" "For %{gold} gold, the alchemist will remove them for you. Do you pay?" msgstr[0] "" "Für %{gold} Gold wird der Alchemist es für Euch entfernen. Bezahlt Ihr?" msgstr[1] "" "Für %{gold} Gold wird der Alchemist die für Euch entfernen. Bezahlt Ihr?" msgid "" "After you consent to pay the requested amount of gold, the alchemist grabs " "the cursed artifact and throws it into his magical cauldron." msgid_plural "" "After you consent to pay the requested amount of gold, the alchemist grabs " "all cursed artifacts and throws them into his magical cauldron." msgstr[0] "" "Nachdem Ihr eingewilligt habt, den geforderten Goldbetrag zu zahlen, " "schnappt sich der Alchemist das verfluchte Artefakt und wirft es in seinen " "magischen Kessel." msgstr[1] "" "Nachdem Ihr eingewilligt habt, den geforderten Goldbetrag zu zahlen, " "schnappt sich der Alchemist alle verfluchten Artefakte und wirft sie in " "seinen magischen Kessel." msgid "" "You hear a voice from behind the locked door, \"You don't have enough gold " "to pay for my services.\"" msgstr "" "Ihr hört hinter der verschlossenen Tür eine Stimme: \"Ihr habt nicht genug " "Gold, um meine Dienste zu bezahlen.\"" msgid "" "You hear a voice from high above in the tower, \"Go away! I can't help you!\"" msgstr "" "Ihr hört eine Stimme von hoch oben aus dem Turm: \"Geh weg! Ich kann Dir " "nicht helfen!\"" msgid "" "The head groom speaks to you, \"That is a fine looking horse you have. I am " "afraid we can give you no better, but the horses your cavalry are riding " "look to be of poor breeding stock. We have many trained war horses which " "would aid your riders greatly. I insist you take them.\"" msgstr "" "Der Stallmeister spricht zu Euch: \"Das ist ein schönes Pferd, das Ihr da " "habt. Ich fürchte, wir können Euch kein besseres geben, aber die Pferde, " "Eurer Kavallerie, scheinen aus schlechter Zucht zu sein. Wir haben viele " "trainierte Kriegspferde, die Euren Reitern sehr helfen würden. Ich bestehe " "darauf, dass Ihr sie nehmt.\"" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, he will " "grow tired in a week. You must also let me give better horses to your " "mounted soldiers, their horses look shoddy and weak.\"" msgstr "" "Als Ihr Euch den Ställen nähert, erscheint der Stallmeister und führt ein " "bestens aussehendes Schlachtross. \"Dieses Ross wird Euch helfen, Eure " "Reisen zu beschleunigen. Leider wird es in einer Woche müde werden. Ihr " "müsst mir auch erlauben, Euren berittenen Soldaten bessere Pferde zu geben, " "ihre Pferde sehen schäbig und schwach aus.\"" msgid "" "The head groom approaches you and speaks, \"You already have a fine horse, " "and have no inexperienced cavalry which might make use of our trained war " "horses.\"" msgstr "" "Der Oberpfleger tritt an Euch heran und spricht: \"Ihr habt bereits ein " "schönes Pferd und keine unerfahrene Kavallerie, die unsere trainierten " "Schlachtrösser gebrauchen könnte.\"" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, his " "endurance will wane with a lot of heavy riding, and you must return for a " "fresh mount in a week. We also have many fine war horses which could benefit " "mounted soldiers, but you have none we can help.\"" msgstr "" "Als Ihr Euch den Ställen nähert, erscheint der Stallmeister und führt ein " "gut aussehendes Schlachtross. \"Dieses Ross wird Euch auf Euren Reisen " "helfen, schneller voranzukommen. Leider wird seine Ausdauer bei viel " "schwerem Reiten nachlassen, und Ihr müsst in einer Woche wiederkommen, um " "ein neues Pferd zu holen. Wir haben auch viele gute Kriegspferde, die " "berittenen Soldaten helfen könnten, aber Ihr habt keines, dem wir helfen " "können.\"" msgid "The Arena guards turn you away." msgstr "Die Wachen der Arena weisen Euch ab." msgid "" "You have your crew stop up their ears with wax before the sirens' eerie song " "has any chance of luring them to a watery grave." msgstr "" "Eure Mannschaft muss sich die Ohren mit Wachs zustopfen, bevor der " "unheimliche Gesang der Sirenen sie in ein nasses Grab lockt." msgid "" "As the sirens sing their eerie song, your small, determined army manages to " "overcome the urge to dive headlong into the sea." msgstr "" "Als die Sirenen ihr unheimliches Lied singen, gelingt es Eurer kleinen, " "entschlossenen Armee, den Drang zu überwinden, kopfüber ins Meer zu stürzen." msgid "" "An eerie wailing song emanates from the sirens perched upon the rocks. Many " "of your crew fall under its spell, and dive into the water where they drown. " "You are now wiser for the visit, and gain %{exp} experience." msgstr "" "Von den auf den Felsen sitzenden Sirenen geht ein unheimliches Klagelied " "aus. Viele Eurer Mannschaft geraten in seinen Bann und tauchen ins Wasser, " "wo sie ertrinken. Ihr seid nun um diesen Besuch reicher und erhaltet %{exp} " "Erfahrung." msgid "" "In a dazzling display of daring, you break into the local jail and free the " "hero imprisoned there, who, in return, pledges loyalty to your cause." msgstr "" "In einem Anflug von Wagemut brecht Ihr in das örtliche Gefängnis ein und " "befreit den hier eingesperrten Helden, der Euch als Dank seine Treue schwört." msgid "" "You already have %{count} heroes, and regretfully must leave the prisoner in " "this jail to languish in agony for untold days." msgstr "" "Ihr habt bereits %{count} Helden und müsst leider den Gefangenen hier in " "unbeschreiblichen Qualen zurücklassen." msgid "" "You enter a rickety hut and talk to the magician who lives there. He tells " "you of places near and far which may aid you in your journeys." msgstr "" "Ihr betretet eine klapprige Hütte und sprecht mit dem dort lebenden Magier. " "Er erzählt Euch von Orten, nah und fern, die Euch auf Euren Reisen hilfreich " "sein könnten." msgid "This eye seems to be intently studying its surroundings." msgstr "Dieses Auge scheint aufmerksam seine Umgebung zu studieren." msgid "You come across a giant Sphinx. The Sphinx remains strangely quiet." msgstr "Ihr stoßt auf eine riesige Sphinx. Die Sphinx bleibt seltsam ruhig." msgid "" "\"I have a riddle for you,\" the Sphinx says. \"Answer correctly, and you " "shall be rewarded. Answer incorrectly, and you shall be eaten. Do you accept " "the challenge?\"" msgstr "" "\"Ich habe ein Rätsel für Dich\", sagt die Sphinx. \"Antwortest Du richtig, " "so sollst Du belohnt werden. Antwortest Du falsch, so sollst Du gefressen " "werden. Nimmst du die Herausforderung an?\"" msgid "" "The Sphinx asks you the following riddle:\n" "\n" "'%{riddle}'\n" "\n" "Your answer?" msgstr "" "Die Sphinx stellt Euch das folgende Rätsel:\n" "\n" "'%{riddle}'\n" "\n" "Eure Antwort?" msgid "" "\"You guessed incorrectly,\" the Sphinx says, smiling. The Sphinx swipes at " "you with a paw, knocking you to the ground. Another blow makes the world go " "black, and you know no more." msgstr "" "\"Du hast falsch geraten\", sagt die Sphinx und lächelt. Die Sphinx schlägt " "mit einer Pranke nach Euch und wirft Euch zu Boden. Ein weiterer Schlag, und " "Euch wird schwarz vor Augen. Das ist Eure letzte Erinnerung." msgid "" "Looking somewhat disappointed, the Sphinx sighs. \"You've answered my riddle " "so here's your reward. Now begone.\"" msgstr "" "Ein wenig enttäuscht wirkend säufzt die Sphinx. \"Du hast mein Rätsel " "gelöst, hier ist Deine Belohnung. Und nun verschwinde.\"" msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "As you speak the magic word, the glowing barrier dissolves into nothingness." msgstr "" "Eine magische Barriere ragt hoch vor Euch auf und versperrt Euch den Weg. In " "Runen steht auf dem Bogen,\n" "\"Sprich das Schlüsselwort und du darfst passieren.\"\n" "Als Ihr das magische Wort sprecht, löst sich die glühende Barriere in Nichts " "auf." msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "You speak, and nothing happens." msgstr "" "Eine magische Barriere ragt hoch vor Euch auf und versperrt Euch den Weg. In " "Runen steht auf dem Bogen,\n" "\"Sprich das Schlüsselwort und du kannst passieren.\"\n" "Ihr sprecht, aber nichts passiert." msgid "" "You enter the tent and see an old woman gazing into a magic gem. She looks " "up and says,\n" "\"In my travels, I have learned much in the way of arcane magic. A great " "oracle taught me his skill. I have the answer you seek.\"" msgstr "" "Ihr betretet das Zelt und seht dort eine alte Frau, auf einen magischen " "Edelstein starrt. Sie schaut zu Euch auf und sagt,\n" "\"Auf meinen Reisen habe ich viel über die geheim Magie gelernt. Ein großes " "Orakel lehrte mich sein Können. Ich habe die Antwort, die Ihr sucht.\"" msgid "No spell book is present." msgstr "Kein Zauberbuch vorhanden." msgid "" "This spell costs %{mana} spell points. You have no spell points, so you " "cannot cast it." msgstr "" "Dieser Zauberspruch kostet %{mana} Zauberpunkte. Ihr habt keine " "Zauberpunkte, also könnt Ihr den Zauber nicht sprechen." msgid "" "This spell costs %{mana} spell points. You only have %{point} spell points, " "so you cannot cast it." msgstr "" "Dieser Zauberspruch kostet %{mana} Zauberpunkte. Ihr habt nur %{point} " "Zauberpunkte, also könnt Ihr den Zauber nicht sprechen." msgid "The spell was not found." msgstr "Der Zauberspruch wurde nicht gefunden." msgid "Only heroes can cast this spell." msgstr "Nur Helden können diesen Zauberspruch sprechen." msgid "This hero is not able to cast adventure spells." msgstr "Dieser Held ist nicht in der Lage, Abenteuerzauber zu sprechen." msgid "Your hero is too tired to cast this spell today. Try again tomorrow." msgstr "" "Euer Held ist zu müde, um heute diesen Zauber auszusprechen. Versucht es " "morgen erneut." msgid "This spell cannot be cast on a boat." msgstr "Dieser Zauber kann nicht auf einem Boot verwendet werden." msgid "This spell can only be cast near an ocean." msgstr "Dieser Zauber kann nur in der Nähe von Wasser verwendet werden." msgid "" "There are no boats available and no ocean adjacent to the hero where this " "spell will work." msgstr "" "In der Nähe des Helden gibt es kein Boot und keinen freien Platz im Meer, um " "diesen Zauber zu verwenden." msgid "There are no boats available for this spell." msgstr "Es ist kein Boot verfügbar, um diesen Zauber zu verwenden." msgid "There is no ocean adjacent to the hero where this spell will work." msgstr "" "In der Nähe des Helden gibt es kein Gewässer, in dem dieser Zauber wirken " "könnte." msgid "" "You do not own any town or castle that is not currently occupied by a hero. " "This spell will have no effect." msgstr "" "Ihr besitzt derzeit keine Stadt oder Burg, die nicht von einem Helden " "bewohnt wird, daher kann der Zauber nicht wirken." msgid "This hero is already in a town, so this spell will have no effect." msgstr "" "Dieser Held befindet sich bereits in einer Stadt, sodass dieser Zauber nicht " "verwendet werden kann." msgid "" "The nearest town is %{town}.\n" "\n" "This town is occupied by your hero %{hero}." msgstr "" "Die nächstgelegene Stadt ist %{town}.\n" "\n" "Diese Stadt ist von deinem Helden %{hero} besetzt." msgid "This spell is already in effect." msgstr "Dieser Zauber ist bereits in Gebrauch." msgid "" "No opponent neither has nor can have any hero under their command anymore. " "Casting this spell will have no effect." msgstr "" "Kein Gegner kann mehr Helden unter seinem Kommando haben. Das Aussprechen " "dieses Zaubers bringt keinen Vorteil." msgid "" "No opponent has a hero under their command at this time. Casting this spell " "will have no effect." msgstr "" "Kein Gegner hat zu diesem Zeitpunkt einen Helden unter seinem Kommando. Das " "Aussprechen dieses Zaubers bringt keinen Vorteil." msgid "" "You must be within %{count} spaces of a monster for the Visions spell to " "work." msgstr "" "Ihr müsst sich innerhalb von %{count} Feldern eines Monsters befinden, damit " "die Vision wirkt." msgid "" "You must be standing on the entrance to a mine (sawmills and alchemist labs " "do not count) to cast this spell." msgstr "" "Ihr müsst am Eingang einer Mine stehen (Sägewerke und Alchemisten zählen " "nicht), um diesen Zauber auzusprechen." msgid "You must first defeat the ghosts guarding the mine to cast this spell." msgstr "" "Damit dieser Zauber wirkt, müssen zuerst die Geister besiegt werden, die die " "Mine bewachen." msgid "" "There are already at least as many elementals guarding the mine as this hero " "can generate. Casting this spell will have no effect." msgstr "" "Es gibt mehr Elementare, die die Mine bewachen, als der Zauberspruch " "erzeugenkann. Daher kann dieser Zauber nicht wirken." msgid "%{name} the %{race} (Level %{level})" msgstr "%{name} (Stufe %{level} %{race})" #, fuzzy msgid "" "'Spread' combat formation spreads the hero's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "Die \"Verstreute\" Kampfformation verteilt die Armeen von oben bis unten " "über das Schlachtfeld, mit wenigstens einem Freiraum zwischen jeder Armee." #, fuzzy msgid "" "'Grouped' combat formation bunches the hero's army together in the center of " "their side of the battlefield." msgstr "" "Die 'Gruppierte' Kampfformation bündelt Eure Armee in der Mitte Eurer Seite " "des Schlachtfelds." msgid "Are you sure you want to dismiss this Hero?" msgstr "Seid Ihr sicher, dass Ihr diesen Helden entlassen wollt?" msgid "View Experience Info" msgstr "Infos zur Erfahrung anzeigen" msgid "View Spell Points Info" msgstr "Infos zu Zauberpunkten anzeigen" msgid "Set army combat formation to 'Spread'" msgstr "Armeekampfformation auf \"Ausbreitend\" einstellen" msgid "Set army combat formation to 'Grouped'" msgstr "Armeekampfformation auf \"Gruppiert\" einstellen" msgid "Exit Hero Screen" msgstr "Heldenbildschirm verlassen" msgid "You cannot dismiss a hero in a castle" msgstr "Man kann einen Helden nicht in einer Burg entlassen" msgid "Dismissal of %{name} the %{race} is prohibited by scenario" msgstr "Die Entlassung von %{name} (%{race}) ist durch das Szenario verboten" msgid "Dismiss %{name} the %{race}" msgstr "%{name} (%{race}) entlassen" msgid "Show previous hero" msgstr "Vorherigen Helden anzeigen" msgid "Show next hero" msgstr "Nächsten Helden anzeigen" msgid "Blood Morale" msgstr "Blutmoral" msgid "%{morale} Morale" msgstr "%{morale} Moral" msgid "%{luck} Luck" msgstr "%{luck} Glück" msgid "Current Luck Modifiers:" msgstr "Aktuelle Glücksmodifikatoren:" msgid "Current Morale Modifiers:" msgstr "Aktuelle Moralmodifikatoren:" msgid "Entire army is undead, so morale does not apply." msgstr "Die gesamte Armee ist untot, also gilt die Moral nicht." msgid "" "Current experience %{exp1}.\n" " Next level %{exp2}." msgstr "" "Aktuelle Erfahrung %{exp1}.\n" " Nächste Stufe %{exp2}." msgid "Level %{level}" msgstr "Stufe %{level}" #, fuzzy msgid "" "%{name} currently has %{point} spell points out of a maximum of %{max}. The " "maximum number of spell points is 10 times the hero's knowledge. It is " "occasionally possible for the hero to have more than their maximum spell " "points via special events." msgstr "" "%{name} hat derzeit %{point} Zauberpunkte von maximal %{max}. Die " "Maximalzahl der Zauberpunkte ist das 10-fache Eures Wissens. Es ist " "gelegentlich möglich, durch besondere Ereignisse mehr als die maximale " "Anzahl an Zauberpunkten zu erhalten." msgid "%{name1} meets %{name2}" msgstr "%{name1} trifft %{name2}" msgid "Enemy heroes are now fully identifiable." msgstr "Feindliche Helden sind jetzt vollständig identifizierbar." msgid "Identify Hero" msgstr "Held identifiz." msgid "Select town to port to." msgstr "Zielstadt auswählen." msgid "Town Portal" msgstr "Stadtportal" msgid "The creatures are willing to join us!" msgstr "Die Kreaturen sind bereit, sich uns anzuschließen!" msgid "All the creatures will join us..." msgstr "Alle Kreaturen werden sich uns anschließen..." msgid "The creature will join us..." msgid_plural "%{count} of the creatures will join us..." msgstr[0] "Die Kreatur wird sich uns anschließen..." msgstr[1] "%{count} der Kreaturen werden sich uns anschließen..." msgid "" "\n" " for a fee of %{gold} gold." msgstr "" "\n" "gegen %{gold} Goldstücke." msgid "These weak creatures will surely flee before us." msgstr "Diese schwachen Kreaturen werden sicher vor uns fliehen." msgid "I fear these creatures are in the mood for a fight." msgstr "Ich fürchte, diese Kreaturen sind in der Stimmung für einen Kampf." #, fuzzy msgid "The hero's attack skill is a bonus added to each unit's attack skill." msgstr "" "Eure Angriffsfähigkeit ist ein Bonus, der zur Angriffsfähigkeit jeder " "Kreatur addiert wird." #, fuzzy msgid "The hero's defense skill is a bonus added to each unit's defense skill." msgstr "" "Eure Verteidigungsfähigkeit ist ein Bonus, der zur Verteidigungsfähigkeit " "jeder Kreatur addiert wird." #, fuzzy msgid "The hero's spell power determines the duration or power of a spell." msgstr "Eure Zauberkraft bestimmt die Länge oder Stärke eines Zaubers." #, fuzzy msgid "" " The hero's knowledge determines how many spell points the hero may have. " "Under normal circumstances, a hero is limited to 10 spell points per level " "of knowledge." msgstr "" "Euer Wissen bestimmt, wie viele Zauberpunkte Euer Held haben darf. Unter " "normalen Umständen ist ein Held auf 10 Zauberpunkte pro Wissensstufe " "beschränkt." msgid "Current Modifiers:" msgstr "Aktuelle Modifikatoren:" msgid "skill|Basic" msgstr "Einfach" msgid "skill|Advanced" msgstr "Verbessert" msgid "skill|Expert" msgstr "Meisterhaft" msgid "Pathfinding" msgstr "Pfadfinden" msgid "Archery" msgstr "Bogen" msgid "Logistics" msgstr "Logistik" msgid "Scouting" msgstr "Spähen" msgid "Diplomacy" msgstr "Diplomatie" msgid "Navigation" msgstr "Navigation" msgid "Leadership" msgstr "Führung" msgid "Wisdom" msgstr "Weisheit" msgid "Mysticism" msgstr "Mystik" msgid "Ballistics" msgstr "Ballistik" msgid "Eagle Eye" msgstr "Adlerauge" msgid "Necromancy" msgstr "Nekromantie" msgid "Estates" msgstr "Grundbesitz" msgid "Advanced Archery" msgstr "Verbessert Bogen" msgid "Advanced Pathfinding" msgstr "Verbessert Pfadfinden" msgid "Basic Archery" msgstr "Einfach Bogen" msgid "Basic Pathfinding" msgstr "Einfaches Pfadfinden" msgid "Expert Pathfinding" msgstr "Meisterhaft Pfadfinden" msgid "Advanced Logistics" msgstr "Verbesserte Logistik" msgid "Basic Logistics" msgstr "Einfache Logistik" msgid "Basic Scouting" msgstr "Einfaches Spähen" msgid "Expert Archery" msgstr "Meisterhaft Bogen" msgid "Expert Logistics" msgstr "Meisterhafte Logistik" msgid "Advanced Diplomacy" msgstr "Verbesserte Diplomatie" msgid "Advanced Scouting" msgstr "Verbessertes Spähen" msgid "Basic Diplomacy" msgstr "Einfache Diplomatie" msgid "Expert Diplomacy" msgstr "Meisterhafte Diplomatie" msgid "Expert Scouting" msgstr "Meisterhaftes Spähen" msgid "Advanced Leadership" msgstr "Verbesserte Führung" msgid "Advanced Navigation" msgstr "Verbesserte Navigation" msgid "Basic Leadership" msgstr "Einfache Führung" msgid "Basic Navigation" msgstr "Einfache Navigation" msgid "Expert Navigation" msgstr "Meisterhafte Navigation" msgid "Advanced Wisdom" msgstr "Verbesserte Weisheit" msgid "Basic Mysticism" msgstr "Einfache Mystik" msgid "Basic Wisdom" msgstr "Einfache Weisheit" msgid "Expert Leadership" msgstr "Meisterhafte Führung" msgid "Expert Wisdom" msgstr "Meisterhafte Weisheit" msgid "Advanced Luck" msgstr "Verbessertes Glück" msgid "Advanced Mysticism" msgstr "Verbesserte Mystik" msgid "Basic Luck" msgstr "Einfaches Glück" msgid "Expert Luck" msgstr "Meisterhaftes Glück" msgid "Expert Mysticism" msgstr "Meisterhafte Mystik" msgid "Advanced Ballistics" msgstr "Verbesserte Ballistik" msgid "Advanced Eagle Eye" msgstr "Verbessertes Adlerauge" msgid "Basic Ballistics" msgstr "Einfache Ballistik" msgid "Basic Eagle Eye" msgstr "Einfaches Adlerauge" msgid "Expert Ballistics" msgstr "Meisterhafte Ballistik" msgid "Advanced Necromancy" msgstr "Verbesserte Nekromantie" msgid "Basic Estates" msgstr "Einfach Grundbesitz" msgid "Basic Necromancy" msgstr "Einfache Nekromantie" msgid "Expert Eagle Eye" msgstr "Meisterhaftes Adlerauge" msgid "Expert Necromancy" msgstr "Meisterhafte Nekromantie" msgid "Advanced Estates" msgstr "Verbessert Grundbesitz" msgid "Expert Estates" msgstr "Meisterhaft Grundbesitz" msgid "" "%{skill} reduces the movement penalty for rough terrain by %{count} percent." msgstr "" "%{skill} reduziert den Bewegungsabzug für unwegsames Gelände um %{count} " "Prozent." msgid "%{skill} eliminates the movement penalty for rough terrain." msgstr "%{skill} eliminiert den Bewegungsabzug für unwegsames Gelände." #, fuzzy msgid "" "%{skill} increases the damage done by the hero's range attacking creatures " "by %{count} percent." msgstr "" "%{skill} erhöht den Schaden von Kreaturen, die aus der Entfernung angreifen, " "um %{count} Prozent." #, fuzzy msgid "%{skill} increases the hero's movement points by %{count} percent." msgstr "%{skill} erhöht die Bewegungspunkte Eures Helden um %{count} Prozent." #, fuzzy msgid "%{skill} increases the hero's viewable area by one square." msgid_plural "%{skill} increases the hero's viewable area by %{count} squares." msgstr[0] "%{skill} erweitert das Blickfeld Eures Helden um ein Feld." msgstr[1] "%{skill} erweitert das Blickfeld Eures Helden um %{count} Felder." #, fuzzy msgid "" "%{skill} allows the hero to negotiate with monsters who are weaker than " "their army, and reduces the cost of surrender." msgstr "" "%{skill} erlaubt es Euch, mit Monstern zu verhandeln, die schwächer sind als " "Eure Truppe. " #, fuzzy msgid "" "Approximately %{count} percent of the creatures may offer to join the hero." msgstr "" "Ungefähr %{count} Prozent der Kreaturen werden Euch anbieten, sich Euch " "anzuschließen." #, fuzzy msgid "All of the creatures may offer to join the hero." msgstr "Alle Kreaturen könnten anbieten, sich Euch anzuschließen." #, fuzzy msgid "" "The cost of surrender is reduced to %{percent} percent of the total cost of " "troops in the army." msgstr "" "Der %{name} reduziert die Kosten der Kapitulation auf %{count} Prozent der " "Gesamtkosten der Truppen in Eurer Armee." #, fuzzy msgid "" "%{skill} increases the hero's movement points over water by %{count} percent." msgstr "" "%{skill} erhöht die Bewegungspunkte Eures Helden über Wasser um %{count} " "Prozent." #, fuzzy msgid "%{skill} increases the hero's troops morale by %{count}." msgstr "%{skill} erhöht die Moral der Truppen Eures Helden um %{count}." #, fuzzy msgid "%{skill} allows the hero to learn third level spells." msgstr "%{skill} erlaubt es Eurem Helden, Zauber der dritten Stufe zu lernen." #, fuzzy msgid "%{skill} allows the hero to learn fourth level spells." msgstr "%{skill} erlaubt es Eurem Helden, Zauber der vierten Stufe zu lernen." #, fuzzy msgid "%{skill} allows the hero to learn fifth level spells." msgstr "%{skill} erlaubt es Eurem Helden, Zauber der fünften Stufe zu lernen." #, fuzzy msgid "%{skill} regenerates one additional spell point per day to the hero." msgid_plural "" "%{skill} regenerates %{count} additional spell points per day to the hero." msgstr[0] "" "%{skill} regeneriert einen zusätzlichen Zauberpunkt Eures Helden pro Tag." msgstr[1] "" "%{skill} regeneriert %{count} zusätzliche Zauberpunkte Eures Helden pro Tag." #, fuzzy msgid "%{skill} increases the hero's luck by %{count}." msgstr "%{skill} erhöht das Glück Eures Helden um %{count}." #, fuzzy msgid "" "%{skill} gives the hero's catapult shots a greater chance to hit and do " "damage to castle walls." msgstr "" "%{skill} gibt den Katapultschüssen Eures Helden eine größere Chance, die " "Burgmauern zu treffen und Schaden zu verursachen." #, fuzzy msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot has a " "greater chance to hit and do damage to castle walls." msgstr "" "%{skill} gibt dem Katapult Eures Helden einen zusätzlichen Schuss, und jeder " "Schuss hat eine größere Chance, die Burgmauern zu treffen und Schaden zu " "verursachen." #, fuzzy msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot " "automatically destroys any wall, except a fortified wall in a Knight castle." msgstr "" "%{skill} gibt dem Katapult Eures Helden einen zusätzlichen Schuss, und jeder " "Schuss zerstört automatisch jede Mauer, außer einer befestigten Mauer in " "einer Ritterburg." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 1st or " "2nd level spell that was cast by an enemy during combat." msgstr "" "%{skill} gibt Eurem Helden eine %{count} Prozent Chance, jeden gegnerischen " "Zauber der Stufe 1 oder 2 zu erlernen, der in einem Kampf gegen ihn " "eingesetzt wird." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 3rd " "level spell (or below) that was cast by an enemy during combat." msgstr "" "%{skill} gibt Eurem Helden eine %{count} Prozent Chance, jeden beliebigen " "Zauber der Stufe 3 (oder niedriger) zu lernen, der im Kampf gegen ihn " "eingesetzt wird." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 4th " "level spell (or below) that was cast by an enemy during combat." msgstr "" "%{skill} gibt Eurem Helden eine %{count} Prozent Chance, jeden beliebigen " "Zauber der Stufe 4 (oder niedriger) zu lernen, der im Kampf gegen ihn " "eingesetzt wird." msgid "" "%{skill} allows %{count} percent of the creatures killed in combat to be " "brought back from the dead as Skeletons." msgstr "" "%{skill} ermögicht Euch %{count} Prozent der im Kampf getöteten Kreaturen " "als Skelette von den Toten wiederauferstehen zu lassen." #, fuzzy msgid "" "The hero produces %{count} gold pieces per day as tax revenue from estates." msgstr "" "Euer Held produziert %{count} Goldstücke pro Tag als Steuereinnahmen aus " "Ländereien." msgid "Blue" msgstr "Blau" msgid "Green" msgstr "Grün" msgid "Red" msgstr "Rot" msgid "Yellow" msgstr "Gelb" msgid "Orange" msgstr "Orange" msgid "Purple" msgstr "Lila" msgid "barrier|Aqua" msgstr "Aqua" msgid "barrier|Blue" msgstr "Blaue" msgid "barrier|Brown" msgstr "Braune" msgid "barrier|Gold" msgstr "Goldene" msgid "barrier|Green" msgstr "Grüne" msgid "barrier|Orange" msgstr "Orange" msgid "barrier|Purple" msgstr "Lila" msgid "barrier|Red" msgstr "Rote" msgid "tent|Aqua" msgstr "Aqua" msgid "tent|Blue" msgstr "Blaues" msgid "tent|Brown" msgstr "Braunes" msgid "tent|Gold" msgstr "Goldenes" msgid "tent|Green" msgstr "Grünes" msgid "tent|Orange" msgstr "Orange" msgid "tent|Purple" msgstr "Lila" msgid "tent|Red" msgstr "Rotes" msgid "Experience" msgstr "Erfahrung" msgid "" "Experience allows your heroes to go up levels, increasing their primary and " "secondary skills." msgstr "" "Mit der Erfahrung steigen die Helden im Level auf und verbessern ihre " "primären und sekundären Fähigkeiten." msgid "Hero/Stats" msgstr "Held/Stats" msgid "Skills" msgstr "Fähigkeiten" msgid "Town/Castle" msgstr "Stadt/Burg" msgid "Garrison" msgstr "Garnison" msgid "Gold Per Day:" msgstr "Gold pro Tag:" msgid "View Heroes." msgstr "Helden anzeigen." msgid "Towns/Castles" msgstr "Stadt/Burg" msgid "View Towns and Castles." msgstr "Städte anzeigen." msgid "luck|Cursed" msgstr "Verfluchtes" msgid "luck|Awful" msgstr "Furchtbares" msgid "luck|Bad" msgstr "Schlechtes" msgid "luck|Normal" msgstr "Normales" msgid "luck|Good" msgstr "Gutes" msgid "luck|Great" msgstr "Wunderbares" msgid "luck|Irish" msgstr "Irisches" #, fuzzy msgid "" "Bad luck sometimes falls on the hero's units in combat, causing their " "attacks to only do half damage." msgstr "" "Eure Armeen werden im Kampf manchmal vom Pech verfolgt, so dass ihre " "Angriffe nur halben Schaden anrichten." #, fuzzy msgid "" "Neutral luck means the hero's units will never get lucky or unlucky attacks " "on the enemy." msgstr "" "Neutrales Glück bedeutet, dass Eure Armeen weder Glück noch Unglück haben " "werden bei Angriffen auf den Feind." #, fuzzy msgid "" "Good luck sometimes lets the hero's units get lucky attacks (double " "strength) in combat." msgstr "Glück lässt Eure Truppen manchmal mit doppelter Stärke angreifen." msgid "morale|Treason" msgstr "Verratende" msgid "morale|Awful" msgstr "Furchtbare" msgid "morale|Poor" msgstr "Schlechte" msgid "morale|Normal" msgstr "Normale" msgid "morale|Good" msgstr "Hohe" msgid "morale|Great" msgstr "Wunderbare" msgid "morale|Blood!" msgstr "Blut!" #, fuzzy msgid "Bad morale may cause the hero's units to freeze in combat." msgstr "" "Eine niedrige Moral kann dazu führen, dass Eure Armeen im Kampf vor Angst " "erstarren." #, fuzzy msgid "" "Neutral morale means the hero's units will never be blessed with extra " "attacks or freeze in combat." msgstr "" "Neutrale Moral bedeutet, dass Eure Armeen keine zusätzlichen Angriffen " "erhalten werden aber auch nicht im Kampf einfrieren." #, fuzzy msgid "Good morale may give the hero's units extra attacks in combat." msgstr "" "Eine hohe Moral kann Eurer Armee zu zusätzlichen Angriffen im Kampf " "verhelfen." msgid "Knight" msgstr "Ritter" msgid "Barbarian" msgstr "Barbar" msgid "Sorceress" msgstr "Zauberin" msgid "Warlock" msgstr "Hexer" msgid "Wizard" msgstr "Zauberer" msgid "Necromancer" msgstr "Totenbeschwörer" msgid "Multi" msgstr "Verschied" msgid "doubleLined|Knight" msgstr "Ritter" msgid "doubleLined|Barbarian" msgstr "Barbar" msgid "doubleLined|Sorceress" msgstr "Zauberin" msgid "doubleLined|Warlock" msgstr "Hexer" msgid "doubleLined|Wizard" msgstr "Zauberer" msgid "" "doubleLined|Necro-\n" "mancer" msgstr "" "Totenbe-\n" "schwörer" msgid "doubleLinedRace|Multi" msgstr "Verschied" msgid "doubleLinedRace|Random" msgstr "Zufällig" msgid "speed|Standing" msgstr "Stehend" msgid "speed|Crawling" msgstr "Schleichend" msgid "speed|Very Slow" msgstr "Sehr langsam" msgid "speed|Slow" msgstr "Langsam" msgid "speed|Average" msgstr "Normal" msgid "speed|Fast" msgstr "Schnell" msgid "speed|Very Fast" msgstr "Sehr Schnell" msgid "speed|Ultra Fast" msgstr "Ultra schnell" msgid "speed|Blazing" msgstr "Blitzschnell" msgid "speed|Instant" msgstr "Sofort" msgid "week|Squirrel" msgstr "Eichhörnchen" msgid "week|Rabbit" msgstr "Kaninchen" msgid "week|Gopher" msgstr "Erdhörnchen" msgid "week|Badger" msgstr "Dachs" msgid "week|Rat" msgstr "Ratte" msgid "week|Eagle" msgstr "Adler" msgid "week|Weasel" msgstr "Wiesel" msgid "week|Raven" msgstr "Rabe" msgid "week|Mongoose" msgstr "Manguste" msgid "week|Dog" msgstr "Hund" msgid "week|Aardvark" msgstr "Erdferkel" msgid "week|Lizard" msgstr "Eidechse" msgid "week|Tortoise" msgstr "Schildkröte" msgid "week|Hedgehog" msgstr "Igel" msgid "week|Condor" msgstr "Condor" msgid "week|Ant" msgstr "Ameise" msgid "week|Grasshopper" msgstr "Grashüpfer" msgid "week|Dragonfly" msgstr "Libelle" msgid "week|Spider" msgstr "Spinne" msgid "week|Butterfly" msgstr "Schmetterling" msgid "week|Bumblebee" msgstr "Hummel" msgid "week|Locust" msgstr "Heuschrecke" msgid "week|Earthworm" msgstr "Regenwurm" msgid "week|Hornet" msgstr "Hornisse" msgid "week|Beetle" msgstr "Käfer" msgid "week|PLAGUE" msgstr "PEST" msgid "week|Unnamed" msgstr "Unbenannt" msgid "Desert" msgstr "Wüste" msgid "Snow" msgstr "Schnee" msgid "Wasteland" msgstr "Ödland" msgid "Beach" msgstr "Strand" msgid "Lava" msgstr "Lava" msgid "Dirt" msgstr "Erde" msgid "Grass" msgstr "Gras" msgid "Ocean" msgstr "Ozean" msgid "maps|Small" msgstr "Kleine" msgid "maps|Medium" msgstr "Mittlere" msgid "maps|Large" msgstr "Grosse" msgid "maps|Extra Large" msgstr "Extragrosse" msgid "maps|Custom Size" msgstr "Individuelle Größe" msgid "Ore Mine" msgstr "Erzmine" msgid "Sulfur Mine" msgstr "Schwefelmine" msgid "Crystal Mine" msgstr "Kristallmine" msgid "Gems Mine" msgstr "Edelsteinmine" msgid "Gold Mine" msgstr "Goldmine" msgid "Mine" msgstr "Mine" msgid "Burma shave." msgstr "Burma shave." msgid "Next sign 50 miles." msgstr "Nächstes Schild 50 Meilen." msgid "See Rock City." msgstr "Siehe Rock City." msgid "This space for rent." msgstr "Dieser Raum ist zu vermieten." msgid "No object" msgstr "Kein Objekt" msgid "Alchemist Lab" msgstr "Alchemistenlabor" msgid "Sign" msgstr "Zeichen" msgid "Buoy" msgstr "Boje" msgid "Skeleton" msgstr "Skelett" msgid "Daemon Cave" msgstr "Dämonenhöhle" msgid "Treasure Chest" msgstr "Schatzkiste" msgid "Faerie Ring" msgstr "Feenring" msgid "Campfire" msgstr "Lagerfeuer" msgid "Fountain" msgstr "Brunnen" msgid "Gazebo" msgstr "Pavillon" msgid "Genie Lamp" msgstr "Antike Lampe" msgid "Archer's House" msgstr "Haus des Bogenschützen" msgid "Goblin Hut" msgstr "Goblinhütte" msgid "Dwarf Cottage" msgstr "Zwergenhaus" msgid "Peasant Hut" msgstr "Bauernhaus" msgid "Stables" msgstr "Ställe" msgid "Alchemist's Tower" msgstr "Turm des Alchemisten" msgid "Event" msgstr "Ereignis" msgid "Dragon City" msgstr "Drachenstadt" msgid "Lighthouse" msgid_plural "Lighthouses" msgstr[0] "Leuchtturm" msgstr[1] "Leuchttürme" msgid "Water Wheel" msgid_plural "Water Wheels" msgstr[0] "Wasserrad" msgstr[1] "Wasserräder" msgid "Monster" msgstr "Monster" msgid "Obelisk" msgstr "Obelisk" msgid "Oasis" msgstr "Oase" msgid "Resource" msgstr "Ressource" msgid "Sawmill" msgstr "Sägemühle" msgid "Oracle" msgstr "Orakel" msgid "Shrine of the First Circle" msgstr "Schrein des ersten Kreises" msgid "Shipwreck" msgstr "Schiffswrack" msgid "Sea Chest" msgstr "Seekiste" msgid "Desert Tent" msgstr "Wüstenzelt" msgid "Stone Liths" msgstr "Steinsäulen" msgid "Wagon Camp" msgstr "Wagenlager" msgid "Hut of the Magi" msgstr "Hütte des Magiers" msgid "Whirlpool" msgstr "Whirlpool" msgid "Windmill" msgid_plural "Windmills" msgstr[0] "Windmühle" msgstr[1] "Windmühlen" msgid "Artifact" msgstr "Artefakt" msgid "Mermaid" msgstr "Meerjungfrau" msgid "Boat" msgstr "Boot" msgid "Random Ultimate Artifact" msgstr "Zufälliges ultimatives Artefakt" msgid "Random Artifact" msgstr "Zufälliges Artefakt" msgid "Random Resource" msgstr "Zufällige Ressource" msgid "Random Monster" msgstr "Zufälliges Monster" msgid "Random Town" msgstr "Zufällige Stadt" msgid "Random Castle" msgstr "Zufälliges Burg" msgid "Eye of the Magi" msgstr "Auge des Magiers" msgid "Random Monster - weak" msgstr "Zufälliges Monster - schwach" msgid "Random Monster - medium" msgstr "Zufälliges Monster - mittel" msgid "Random Monster - strong" msgstr "Zufälliges Monster - stark" msgid "Random Monster - very strong" msgstr "Zufälliges Monster - sehr stark" #, fuzzy msgid "Hero" msgstr "Helden" msgid "Nothing Special" msgstr "Nichts besonderes" msgid "Mossy Rock" msgstr "Moosiger Felsen" msgid "Watch Tower" msgstr "Wachturm" msgid "Tree House" msgstr "Baumhaus" msgid "Tree City" msgstr "Baumhausstadt" msgid "Ruins" msgstr "Ruinen" msgid "Fort" msgstr "Fort" msgid "Abandoned Mine" msgstr "Verlassene Mine" msgid "Sirens" msgstr "Sirenen" msgid "Standing Stones" msgstr "Stehende Steine" msgid "Idol" msgstr "Götzenbild" msgid "Tree of Knowledge" msgstr "Baum des Wissens" msgid "Witch Doctor's Hut" msgstr "Hütte des Hexendoktors" msgid "Temple" msgstr "Tempel" msgid "Hill Fort" msgstr "Hügelfort" msgid "Halfling Hole" msgstr "Loch des Halblings" msgid "Mercenary Camp" msgstr "Söldnerlager" msgid "Shrine of the Second Circle" msgstr "Schrein des zweiten Kreises" msgid "Shrine of the Third Circle" msgstr "Schrein des dritten Kreises" msgid "City of the Dead" msgstr "Stadt der Toten" msgid "Sphinx" msgstr "Sphinx" msgid "Wagon" msgstr "Waggon" msgid "Tar Pit" msgstr "Teergrube" msgid "Artesian Spring" msgstr "Artesische Quelle" msgid "Troll Bridge" msgstr "Trollbrücke" msgid "Watering Hole" msgstr "Wasserloch" msgid "Witch's Hut" msgstr "Hexenhütte" msgid "Xanadu" msgstr "Xanadu" msgid "Lean-To" msgstr "Anbau" msgid "Magellan's Maps" msgstr "Magellans Karten" msgid "Flotsam" msgstr "Treibgut" msgid "Derelict Ship" msgstr "Heruntergekommenes Schiff" msgid "Shipwreck Survivor" msgstr "Schiffbrüchiger" msgid "Bottle" msgstr "Flasche" msgid "Magic Well" msgstr "Magischer Brunnen" msgid "Magic Garden" msgid_plural "Magic Gardens" msgstr[0] "Zaubergarten" msgstr[1] "Zaubergärten" msgid "Observation Tower" msgstr "Beobachtungsturm" msgid "Freeman's Foundry" msgstr "Freemans Giesserei" msgid "Reefs" msgstr "Riffe" msgid "Volcano" msgstr "Vulkan" msgid "Flowers" msgstr "Blumen" msgid "Rock" msgstr "Fels" msgid "Water Lake" msgstr "See" msgid "Mandrake" msgstr "Mandragola" msgid "Dead Tree" msgstr "Toter Baum" msgid "Stump" msgstr "Stumpf" msgid "Crater" msgstr "Krater" msgid "Cactus" msgstr "Kaktus" msgid "Mound" msgstr "Erdhügel" msgid "Dune" msgstr "Düne" msgid "Lava Pool" msgstr "Lavabecken" msgid "Shrub" msgstr "Strauch" msgid "Barrow Mounds" msgstr "Vergrabener Karren" msgid "Random Artifact - Treasure" msgstr "Zufälliges Artefakt - Schatz" msgid "Random Artifact - Minor" msgstr "Zufälliges Artefakt - Klein" msgid "Random Artifact - Major" msgstr "Zufälliges Artefakt - Groß" msgid "Barrier" msgstr "Barriere" msgid "Traveller's Tent" msgstr "Zelt des Reisenden" msgid "Jail" msgstr "Gefängnis" msgid "Fire Summoning Altar" msgstr "Feuerwesen-Altar" msgid "Air Summoning Altar" msgstr "Luftwesen-Altar" msgid "Earth Summoning Altar" msgstr "Erdwesen-Altar" msgid "Water Summoning Altar" msgstr "Wasserwesen-Altar" msgid "Unknown Monster" msgstr "Unbekanntes Monster" msgid "Unknown Monsters" msgstr "Unbekannte Monster" msgid "Peasant" msgstr "Bauer" msgid "Peasants" msgstr "Bauern" msgid "Archer" msgstr "Bogenschütze" msgid "Archers" msgstr "Bogenschützen" msgid "Ranger" msgstr "Ranger" msgid "Rangers" msgstr "Rangers" msgid "Pikeman" msgstr "Pikenier" msgid "Pikemen" msgstr "Pikeniere" msgid "Veteran Pikeman" msgstr "Meister-Pikenier" msgid "Veteran Pikemen" msgstr "Meister-Pikeniere" msgid "Swordsman" msgstr "Schwertkämpfer" msgid "Swordsmen" msgstr "Schwertkämpfer" msgid "Master Swordsman" msgstr "Schwertmeister" msgid "Master Swordsmen" msgstr "Schwertmeistern" msgid "Cavalries" msgstr "Kavallerien" msgid "Cavalry" msgstr "Kavallerie" msgid "Champion" msgstr "Champion" msgid "Champions" msgstr "Champions" msgid "Paladin" msgstr "Paladin" msgid "Paladins" msgstr "Paladine" msgid "Crusader" msgstr "Kreuzritter" msgid "Crusaders" msgstr "Kreuzritter" msgid "Goblin" msgstr "Goblin" msgid "Goblins" msgstr "Goblins" msgid "Orc" msgstr "Ork" msgid "Orcs" msgstr "Orks" msgid "Orc Chief" msgstr "Orkführer" msgid "Orc Chiefs" msgstr "Orkführer" msgid "Wolf" msgstr "Wolf" msgid "Wolves" msgstr "Wölfe" msgid "Ogre" msgstr "Oger" msgid "Ogres" msgstr "Oger" msgid "Ogre Lord" msgstr "Ogerlord" msgid "Ogre Lords" msgstr "Ogerlords" msgid "Troll" msgstr "Troll" msgid "Trolls" msgstr "Trolle" msgid "War Troll" msgstr "Kriegstroll" msgid "War Trolls" msgstr "Kriegstrolle" msgid "Cyclopes" msgstr "Zyklopen" msgid "Cyclops" msgstr "Zyklop" msgid "Sprite" msgstr "Fee" msgid "Sprites" msgstr "Feen" msgid "Dwarf" msgstr "Zwerg" msgid "Dwarves" msgstr "Zwerge" msgid "Battle Dwarf" msgstr "Kampfzwerg" msgid "Battle Dwarves" msgstr "Kampfzwerge" msgid "Elf" msgstr "Elf" msgid "Elves" msgstr "Elfen" msgid "Grand Elf" msgstr "Grosself" msgid "Grand Elves" msgstr "Grosselfen" msgid "Druid" msgstr "Druide" msgid "Druids" msgstr "Druiden" msgid "Greater Druid" msgstr "Grossdruide" msgid "Greater Druids" msgstr "Grossdruiden" msgid "Unicorn" msgstr "Einhorn" msgid "Unicorns" msgstr "Einhörner" msgid "Phoenix" msgstr "Phönix" msgid "Phoenixes" msgstr "Phönixe" msgid "Centaur" msgstr "Zentaur" msgid "Centaurs" msgstr "Zentauren" msgid "Gargoyle" msgstr "Wasserspeier" msgid "Gargoyles" msgstr "Wasserspeier" msgid "Griffin" msgstr "Greifvogel" msgid "Griffins" msgstr "Greifvögel" msgid "Minotaur" msgstr "Minotaur" msgid "Minotaurs" msgstr "Minotauren" msgid "Minotaur King" msgstr "Minotaurenkönig" msgid "Minotaur Kings" msgstr "Minotaurenkönige" msgid "Hydra" msgstr "Hydra" msgid "Hydras" msgstr "Hydras" msgid "Green Dragon" msgstr "Grüner Drache" msgid "Green Dragons" msgstr "Grüne Drachen" msgid "Red Dragon" msgstr "Roter Drache" msgid "Red Dragons" msgstr "Rote Drachen" msgid "Black Dragon" msgstr "Schwarzer Drache" msgid "Black Dragons" msgstr "Schwarze Drachen" msgid "Halfling" msgstr "Halbling" msgid "Halflings" msgstr "Halblinge" msgid "Boar" msgstr "Eber" msgid "Boars" msgstr "Eber" msgid "Iron Golem" msgstr "Eisengolem" msgid "Iron Golems" msgstr "Eisengoleme" msgid "Steel Golem" msgstr "Stahlgolem" msgid "Steel Golems" msgstr "Stahlgoleme" msgid "Roc" msgstr "Roc" msgid "Rocs" msgstr "Rocs" msgid "Mage" msgstr "Magier" msgid "Magi" msgstr "Magier" msgid "Archmage" msgstr "Meistermagier" msgid "Archmagi" msgstr "Meistermagier" msgid "Giant" msgstr "Riese" msgid "Giants" msgstr "Riesen" msgid "Titan" msgstr "Titan" msgid "Titans" msgstr "Titane" msgid "Skeletons" msgstr "Skelette" msgid "Zombie" msgstr "Zombie" msgid "Zombies" msgstr "Zombies" msgid "Mutant Zombie" msgstr "Mutierter Zombie" msgid "Mutant Zombies" msgstr "Mutierte Zombies" msgid "Mummies" msgstr "Mumien" msgid "Mummy" msgstr "Mumie" msgid "Royal Mummies" msgstr "Königsmumien" msgid "Royal Mummy" msgstr "Königsmumie" msgid "Vampire" msgstr "Vampir" msgid "Vampires" msgstr "Vampire" msgid "Vampire Lord" msgstr "Vampirlord" msgid "Vampire Lords" msgstr "Vampirlorde" msgid "Lich" msgstr "Lich" msgid "Liches" msgstr "Liches" msgid "Power Lich" msgstr "Meister-Lich" msgid "Power Liches" msgstr "Meister-Liches" msgid "Bone Dragon" msgstr "Knochendrache" msgid "Bone Dragons" msgstr "Knochendrachen" msgid "Rogue" msgstr "Schurke" msgid "Rogues" msgstr "Schurken" msgid "Nomad" msgstr "Nomade" msgid "Nomads" msgstr "Nomaden" msgid "Ghost" msgstr "Geist" msgid "Ghosts" msgstr "Geister" msgid "Genie" msgstr "Dschinn" msgid "Genies" msgstr "Dschinne" msgid "Medusa" msgstr "Meduse" msgid "Medusas" msgstr "Medusen" msgid "Earth Elemental" msgstr "Erdwesen" msgid "Earth Elementals" msgstr "Erdwesen" msgid "Air Elemental" msgstr "Luftwesen" msgid "Air Elementals" msgstr "Luftwesen" msgid "Fire Elemental" msgstr "Feuerwesen" msgid "Fire Elementals" msgstr "Feuerwesen" msgid "Water Elemental" msgstr "Wasserwesen" msgid "Water Elementals" msgstr "Wasserwesen" msgid "Random Monsters" msgstr "Zufällige Monster" msgid "Random Monster 1" msgstr "Zufälliges Monster 1" msgid "Random Monsters 1" msgstr "Zufällige Monster 1" msgid "Random Monster 2" msgstr "Zufälliges Monster 2" msgid "Random Monsters 2" msgstr "Zufällige Monster 2" msgid "Random Monster 3" msgstr "Zufälliges Monster 3" msgid "Random Monsters 3" msgstr "Zufällige Monster 3" msgid "Random Monster 4" msgstr "Zufälliges Monster 4" msgid "Random Monsters 4" msgstr "Zufällige Monster 4" msgid "Double shot" msgstr "Doppelter Schuss" msgid "2-hex monster" msgstr "2-Felder Monster" msgid "Double strike" msgstr "Doppelschlag" msgid "Double damage to Undead" msgstr "Doppelter Schaden an Untoten" msgid "% magic resistance" msgstr "% Magieresistenz" msgid "Immune to Mind spells" msgstr "Immun gegen Geisteszauber" msgid "Immune to Elemental spells" msgstr "Immun gegen Elementarzauber" msgid "Immune to Fire spells" msgstr "Immun gegen Feuerzauber" msgid "Immune to Cold spells" msgstr "Immun gegen Kältezauber" msgid "Immune to " msgstr "Immun gegen " msgid "% immunity to %{spell} spell" msgstr "% immunität gegen %{spell}" msgid "% damage from Elemental spells" msgstr "% Schaden von Elementarzaubern" msgid "% chance to Dispel beneficial spells" msgstr "% Chance, nützliche Zauber zu bannen" msgid "% chance to Paralyze" msgstr "% Chance auf Lähmen" msgid "% chance to Petrify" msgstr "% Chance auf Versteinern" msgid "% chance to Blind" msgstr "% Chance auf Blendung" msgid "% chance to Curse" msgstr "% Chance auf Fluch" msgid "% chance to cast %{spell} spell" msgstr "% Chance um %{spell} zu wirken" msgid "HP regeneration" msgstr "TP-Regeneration" msgid "Two hexes attack" msgstr "Angriff an zwei Felder" msgid "Flyer" msgstr "Fliegend" msgid "Always retaliates" msgstr "Vergeltet immer" msgid "Attacks all adjacent enemies" msgstr "Greift alle benachbarten Feinde an" msgid "No melee penalty" msgstr "Keine Strafe im Nahkampf" msgid "Dragon" msgstr "Drache" msgid "Undead" msgstr "Untot" msgid "No enemy retaliation" msgstr "Keine feindliche Vergeltung" msgid "HP drain" msgstr "TP-Abfluss" msgid "Cloud attack" msgstr "Wolke-Angriff" msgid "Decreases enemy's morale by " msgstr "Senkt die Moral des Gegners um " msgid "% chance to halve enemy" msgstr "% Chance, den Gegner zu halbieren" msgid "Soul Eater" msgstr "Seelenfresser" msgid "Elemental" msgstr "Element" msgid "No Morale" msgstr "Keine Moral" msgid "200% damage from Fire spells" msgstr "200% Schaden von Feuerzaubern" msgid "200% damage from Cold spells" msgstr "200% Schaden von Kälterzaubern" msgid "% damage from %{spell} spell" msgstr "% Schaden durch %{spell}" msgid "% immunity to " msgstr "% immunität gegen " msgid "Lightning" msgstr "Blitz" msgid "% damage from " msgstr "% Schaden von " msgid "The three Anduran artifacts magically combine into one." msgstr "" "Die drei anduranischen Artefakte verbinden sich auf magische Weise zu einem " "einzigen." msgid "View Spells" msgstr "Zaubersprüche anzeigen" msgid "View %{name} Info" msgstr "Info von %{name} anzeigen" msgid "Move %{name}" msgstr "%{name} umstellen" msgid "Cannot move the Spellbook" msgstr "Das Zauberbuch kann nicht umgestellt werden" msgid "This item can't be traded." msgstr "Dieser Gegenstand kann nicht gehandelt werden." msgid "Invalid Artifact" msgstr "Ungültiges Artefakt" #, fuzzy msgid "The %{name} increases the hero's knowledge by %{count}." msgstr "Die %{name} erhöht Euer Wissen um %{count}." msgid "Ultimate Book of Knowledge" msgstr "Ultimatives Buch des Wissens" #, fuzzy msgid "The %{name} increases the hero's attack skill by %{count}." msgstr "Der %{name} erhöht Euren Angriff um %{count}." msgid "Ultimate Sword of Dominion" msgstr "Ultimatives Schwert der Herrschaft" #, fuzzy msgid "The %{name} increases the hero's defense skill by %{count}." msgstr "Der %{name} erhöht Eure Verteidigung um %{count}." msgid "Ultimate Cloak of Protection" msgstr "Ultimativer Mantel des Schutzes" #, fuzzy msgid "The %{name} increases the hero's spell power by %{count}." msgstr "Der %{name} erhöht Eure Zauberkraft um %{count}." msgid "Ultimate Wand of Magic" msgstr "Ultimativer Zauberstab" #, fuzzy msgid "" "The %{name} increases the hero's attack and defense skills by %{count} each." msgstr "" "Der %{name} erhöht Euren Angriff und Eure Verteidigung um jeweils %{count}." msgid "Ultimate Shield" msgstr "Ultimatives Schild" #, fuzzy msgid "" "The %{name} increases the hero's spell power and knowledge by %{count} each." msgstr "" "Die %{name} erhöht Eure Zauberkraft und Euer Wissen um jeweils %{count}." msgid "Ultimate Staff" msgstr "Ultimativer Stab" #, fuzzy msgid "" "The %{name} increases each of the hero's basic skills by %{count} points." msgstr "Der %{name} erhöht jede Eurer Grundfertigkeiten um %{count} Punkte." msgid "Ultimate Crown" msgstr "Ultimative Krone" msgid "Golden Goose" msgstr "Goldene Gans" msgid "The %{name} brings in an income of %{count} gold per day." msgstr "Der %{name} bringt ein Einkommen von %{count} Gold pro Tag." msgid "Arcane Necklace of Magic" msgstr "Geheime Zauberhalskette" msgid "" "After rescuing a Sorceress from a cursed tomb, she rewards your heroism with " "an exquisite jeweled necklace." msgstr "" "Nachdem Ihr eine Zauberin aus einer verfluchten Gruft gerettet habt, belohnt " "sie Euren Heldenmut mit einer exquisiten Juwelenhalskette." msgid "Caster's Bracelet of Magic" msgstr "Zauberarmband der Magie" msgid "" "While searching through the rubble of a caved-in mine, you free a group of " "trapped Dwarves. Grateful, the leader gives you a golden bracelet." msgstr "" "Während Ihr den Schutt einer eingestürzten Mine durchsucht, befreit Ihr eine " "Gruppe gefangener Zwerge. Aus Dankbarkeit schenkt Euch der Anführer einen " "goldenen Armreif." msgid "Mage's Ring of Power" msgstr "Magierring der Macht" msgid "" "A cry of pain leads you to a Centaur, caught in a trap. Upon setting the " "creature free, he hands you a small pouch. Emptying the contents, you find a " "dazzling jeweled ring." msgstr "" "Ein Schmerzensschrei führt Euch zu einem Zentauren, der in einer Falle " "gefangen ist. Nachdem Ihr die Kreatur befreit habt, überreicht er Euch einen " "kleinen Beutel. Als Ihr den Inhalt ausleert, findet Ihr einen schillernden " "Ring mit Juwelen." msgid "Witch's Broach of Magic" msgstr "Hexenbrosche der Magie" msgid "" "Alongside the remains of a burnt witch lies a beautiful broach, intricately " "designed. Approaching the corpse with caution, you add the broach to your " "inventory." msgstr "" "Neben den Überresten einer verbrannten Hexe liegt eine wunderschöne, " "kunstvoll gestaltete Brosche. Ihr nähert euch dem Leichnam vorsichtig und " "nehmt die Brosche mit." msgid "Medal of Valor" msgstr "Medaille der Tapferkeit" #, fuzzy msgid "The %{name} increases the morale of the hero's army by %{count}." msgstr "Der %{name} erhöht Eure Moral um %{count}." msgid "" "Freeing a virtuous maiden from the clutches of an evil overlord, you are " "granted a Medal of Valor by the King's herald." msgstr "" "Ihr befreit ein tugendhaftes Mädchen aus den Fängen eines bösen Herrschers " "und erhaltet dafür vom Herold des Königs eine Tapferkeitsmedaille." msgid "Medal of Courage" msgstr "Medaille des Mutes" msgid "" "After saving a young boy from a vicious pack of Wolves, you return him to " "his father's manor. The grateful nobleman awards you with a Medal of Courage." msgstr "" "Nachdem Ihr einen kleinen Jungen vor einem bösartigen Wolfsrudel gerettet " "habt, bringt Ihr ihn auf das Landgut seines Vaters zurück. Der dankbare " "Adlige verleiht Euch eine Tapferkeitsmedaille." msgid "Medal of Honor" msgstr "Medaille der Ehre" msgid "" "After freeing a princess of a neighboring kingdom from the evil clutches of " "despicable slavers, she awards you with a Medal of Honor." msgstr "" "Nachdem Ihr die Prinzessin eines Nachbarkönigreichs aus den Fängen " "abscheulicher Sklavenhändler befreit haben, zeichnet sie Euch mit einer " "Ehrenmedaille aus." msgid "Medal of Distinction" msgstr "Medaille der Auszeichnung" msgid "" "Ridding the countryside of the hideous Minotaur who made a sport of eating " "noblemen's Knights, you are honored with the Medal of Distinction." msgstr "" "Ihr habt das Land von dem abscheulichen Minotaurus befreit, der sich einen " "Spaß daraus machte, die Ritter der Adligen zu verspeisen, und werdet dafür " "mit der Medaille für besondere Verdienste geehrt." msgid "Fizbin of Misfortune" msgstr "Fizbin des Pechs" #, fuzzy msgid "" "The %{name} greatly decreases the morale of the hero's army by %{count}." msgstr "Der %{name} senkt Eure Moral stark um %{count}." msgid "" "You stumble upon a medal lying alongside the empty road. Adding the medal to " "your inventory, you become aware that you have acquired the undesirable " "Fizbin of Misfortune, greatly decreasing your army's morale." msgstr "" "Ihr stoßt auf eine Medaille, die am Rande der leeren Straße liegt. Als Ihr " "die Medaille an Euch nehmt, stellt Ihr fest, dass Ihr den unerwünschten " "Fizbin des Unglücks erhalten habt, der die Moral Eurer Armee stark senkt." msgid "Thunder Mace of Dominion" msgstr "Donnerstab der Herrschaft" msgid "" "During a sudden storm, a bolt of lightning strikes a tree, splitting it. " "Inside the tree you find a mysterious mace." msgstr "" "Während eines plötzlichen Gewitters schlägt ein Blitz in einen Baum ein und " "spaltet ihn. Im Inneren des Baumes findet Ihr einen geheimnisvollen " "Streitkolben." msgid "Armored Gauntlets of Protection" msgstr "Panzerhandschuhe des Schutzes" #, fuzzy msgid "The %{name} increase the hero's defense skill by %{count}." msgstr "Die %{name} erhöhen Eure Verteidigung um %{count}." msgid "" "You encounter the infamous Black Knight! After a grueling duel ending in a " "draw, the Knight, out of respect, offers you a pair of armored gauntlets." msgstr "" "Ihr begegnet dem berüchtigten Schwarzen Ritter! Nach einem aufreibenden " "Duell, das in einem Unentschieden endet, bietet der Ritter Euch aus Respekt " "ein Paar gepanzerte Stulpen an." msgid "Defender Helm of Protection" msgstr "Verteidigungshelm des Schutzes" msgid "" "A glint of golden light catches your eye. Upon further investigation, you " "find a golden helm hidden under a bush." msgstr "" "Ein glitzernder goldener Lichtschimmer fällt Euch ins Auge. Bei näherer " "Untersuchung entdeckt Ihr einen, unter einem Busch versteckten, goldenen " "Helm." msgid "Giant Flail of Dominion" msgstr "Riesige Peitsche der Herrschaft" msgid "" "A clumsy Giant has killed himself with his own flail. Knowing your superior " "skill with this weapon, you confidently remove the spectacular flail from " "the fallen Giant." msgstr "" "Ein unbeholfener Riese hat sich mit seinem eigenen Peitsche umgebracht. Im " "Wissen um Eure überragenden Fähigkeiten mit dieser Waffe nehmt Ihr dem " "gefallenen Riesen selbstbewusst die spektakuläre Peitsche ab." msgid "Ballista of Quickness" msgstr "Balliste der Schnelligkeit" #, fuzzy msgid "The %{name} gives the hero's catapult one extra shot per combat round." msgstr "" "Der %{name} gibt Eurem Katapult einen zusätzlichen Schuss pro Kampfrunde." msgid "" "Walking through the ruins of an ancient walled city, you find the instrument " "of the city's destruction, an elaborately crafted ballista." msgstr "" "Bei einem Spaziergang durch die Ruinen einer alten, befestigten Stadt findet " "Ihr das Instrument ihrer Zerstörung der Stadt, eine ausgeklügelt " "konstruierte Balliste." msgid "Stealth Shield of Protection" msgstr "Tarnschild des Schutzes" msgid "" "A stone statue of a warrior holds a silver shield. As you remove the shield, " "the statue crumbles into dust." msgstr "" "Eine steinerne Statue eines Kriegers hält einen silbernen Schild. Als Ihr " "das Schild entfernt, zerfällt die Statue zu Staub." msgid "Dragon Sword of Dominion" msgstr "Drachenschwert der Herrschaft" msgid "" "As you are walking along a narrow path, a nearby bush suddenly bursts into " "flames. Before your eyes the flames become the image of a beautiful woman. " "She holds out a magnificent sword to you." msgstr "" "Ihr geht einen schmalen Pfad entlang, als plötzlich ein naher Busch in " "Flammen aufgeht. Vor Euren Augen formt sich aus den Flammen das Bild einer " "schönen Frau. Sie hält Euch ein prächtiges Schwert hin." msgid "Power Axe of Dominion" msgstr "Poweraxt der Herrschaft" msgid "" "You see a silver axe embedded deeply in the ground. After several " "unsuccessful attempts by your army to remove the axe, you tightly grip the " "handle of the axe and effortlessly pull it free." msgstr "" "Ihr seht eine silberne Axt, die tief im Boden steckt. Nach mehreren " "erfolglosen Versuchen Eurer Armee, die Axt zu heben, packt Ihr fest den " "Griff der Axt und zieht sie mühelos aus dem Boden." msgid "Divine Breastplate of Protection" msgstr "Göttlicher Brustpanzer des Schutzes" msgid "" "A gang of Rogues is sifting through the possessions of dead warriors. " "Scaring off the scavengers, you note the Rogues had overlooked a beautiful " "breastplate." msgstr "" "Eine Schurkenbande durchwühlt die Besitztümer eines toter Krieger. Ihr " "verscheucht die Aasfresser und bemerkt, dass die Schurken einen schönen " "Brustpanzer übersehen haben." msgid "Minor Scroll of Knowledge" msgstr "Kleine Schriftrolle des Wissens" msgid "" "Before you appears a levitating glass case with a scroll, perched upon a bed " "of crimson velvet. At your touch, the lid opens and the scroll floats into " "your awaiting hands." msgstr "" "Vor euch erscheint ein schwebender Glaskasten mit einer Schriftrolle, die " "auf purpurnem Samt gebettet liegt. Als Ihr es berührt, öffnet sich der " "Deckel und die Schriftrolle schwebt in Eure erwartungsvollen Hände." msgid "Major Scroll of Knowledge" msgstr "Grosse Schriftrolle des Wissens" msgid "" "Visiting a local wiseman, you explain the intent of your journey. He reaches " "into a sack and withdraws a yellowed scroll and hands it to you." msgstr "" "Ihr besucht einen örtlichen Weisen und erklärt ihm das Ziel eurer Reise. Er " "greift in einen Sack, zieht eine vergilbte Schriftrolle heraus und " "überreicht sie Euch." msgid "Superior Scroll of Knowledge" msgstr "Überlegene Schriftrolle des Wissens" msgid "" "You come across the remains of an ancient Druid. Bones, yellowed with age, " "peer from the ragged folds of her robe. Searching the robe, you discover a " "scroll hidden in the folds." msgstr "" "Ihr stoßt auf die Überreste einer alten Druidin. Vom Alter vergilbte Knochen " "lugen aus den Falten ihres Gewandes hervor. Beim Durchsuchen des Gewandes " "entdeckt Ihr eine Schriftrolle, die in den Falten versteckt ist." msgid "Foremost Scroll of Knowledge" msgstr "Herausragendste Schriftrolle des Wissens" msgid "" "Mangled bones, yellowed with age, peer from the ragged folds of a dead " "Druid's robe. Searching the robe, you discover a scroll hidden within." msgstr "" "Zerfetzte, vom Alter vergilbte Knochen lugen aus den zerrissenen Falten des " "Gewandes eines toten Druiden hervor. Beim Durchsuchen des Gewandes entdeckt " "Ihr eine Schriftrolle, die darin versteckt ist." msgid "Endless Sack of Gold" msgstr "Endloser Goldsack" #, fuzzy msgid "The %{name} provides the hero with %{count} gold per day." msgstr "Der %{name} bringt ein Einkommen von %{count} Gold pro Runde." msgid "" "A little leprechaun dances gleefully around a magic sack. Seeing you " "approach, he stops in mid-stride. The little man screams and stamps his foot " "ferociously, vanishing into thin air. Remembering the old leprechaun saying " "'Finders Keepers', you grab the sack and leave." msgstr "" "Ein kleiner Kobold tanzt vergnügt um einen Zaubersack herum. Als er Euch " "kommen sieht, bleibt er unvermittelt stehen. Der kleine Mann schreit auf, " "stampft wild mit den Füßen und löst sich in Luft auf. Ihr erinnert euch an " "das alte Kobold-Sprichwort \"Wers findet, dem gehörts\", schnappt euch den " "Sack und geht." msgid "Endless Bag of Gold" msgstr "Endlose Goldtasche" msgid "" "A noblewoman, separated from her traveling companions, asks for your help. " "After escorting her home, she rewards you with a bag filled with gold." msgstr "" "Eine Adlige, die von ihren Reisebegleitern getrennt wurde, bittet Euch um " "Hilfe. Nachdem Ihr sie nach Hause begleitet habt, belohnt sie Euch mit einem " "mit Gold gefüllten Beutel." msgid "Endless Purse of Gold" msgstr "Endlose Goldbörse" msgid "" "In your travels, you find a leather purse filled with gold that once " "belonged to a great warrior king who had the ability to transform any " "inanimate object into gold." msgstr "" "Auf Eurer Reise findet Ihr einen mit Gold gefüllten Lederbeutel, der einst " "einem großen Kriegerkönig gehörte, der die Fähigkeit besaß, jeden leblosen " "Gegenstand in Gold zu verwandeln." msgid "Nomad Boots of Mobility" msgstr "Nomadenstiefel der Mobilität" #, fuzzy msgid "The %{name} increase the hero's movement on land." msgstr "Die %{name} erhöhen Eure Bewegung auf dem Land." msgid "" "A Nomad trader seeks protection from a tribe of Goblins. For your " "assistance, he gives you a finely crafted pair of boots made from the " "softest leather. Looking closely, you see fascinating ancient carvings " "engraved on the leather." msgstr "" "Ein Nomadenhändler sucht Schutz vor einem Goblin-Stamm. Für Eure " "Unterstützung schenkt er euch ein fein gearbeitetes Paar Stiefel aus " "weichstem Leder. Ihr seht genauer hin und entdeckt faszinierende Gravuren " "auf dem Leder." msgid "Traveler's Boots of Mobility" msgstr "Reisestiefel der Mobilität" msgid "" "Discovering a pair of beautifully beaded boots made from the finest and " "softest leather, you thank the anonymous donor and add the boots to your " "inventory." msgstr "" "Ihr entdeckt ein Paar wunderschöner, mit Perlen besetzter Stiefel aus " "feinstem und weichem Leder, dankt dem anonymen Spender und nehmt die Stiefel " "an Euch." msgid "Lucky Rabbit's Foot" msgstr "Glückbringende Hasenpfote" #, fuzzy msgid "The %{name} increases the luck of the hero's army by %{count}." msgstr "Der %{name} erhöht Euer Glück im Kampf um %{count}." msgid "" "A traveling merchant offers you a rabbit's foot, made of gleaming silver " "fur, for safe passage. The merchant explains the charm will increase your " "luck in combat." msgstr "" "Ein reisender Händler bietet Euch eine Hasenpfote aus schimmerndem " "Silberfell für eine sichere Weiterreise an. Der Händler erklärt, dass der " "Talisman euer Glück im Kampf erhöhen wird." msgid "Golden Horseshoe" msgstr "Goldenes Hufeisen" msgid "" "An ensnared Unicorn whinnies in fright. Murmuring soothing words, you set " "her free. Snorting and stamping her front hoof once, she gallops off. " "Looking down you see a golden horseshoe." msgstr "" "Ein in einer Schlinge gefangenes Einhorn wiehert verängstigt. Ihr murmelt " "beruhigende Worte und befreit es. Es schnaubt und stampft einmal mit dem " "Vorderhuf, dann galoppiert es davon. Ihr seht auf dem Boden und findet ein " "goldenes Hufeisen." msgid "Gambler's Lucky Coin" msgstr "Glücksmünze des Spielers" msgid "" "You have captured a mischievous imp who has been terrorizing the region. In " "exchange for his release, he rewards you with a magical coin." msgstr "" "Ihr habt einen schelmischen Kobold gefangen genommen, der die Region " "terrorisiert hat. Im Austausche für seine Freilassung belohnt er euch mit " "einer magischen Münze." msgid "Four-Leaf Clover" msgstr "Vierblättriges Kleeblatt" msgid "" "In the middle of a patch of dead and dry vegetation, to your surprise you " "find a healthy green four-leaf clover." msgstr "" "Inmitten einer Gegend toter und trockener Vegetation findet Ihr, zu Eurer " "Überraschung, einen gesunden, grünen, vierblättrigen Klee." msgid "True Compass of Mobility" msgstr "Wahrer Kompass der Mobilität" #, fuzzy msgid "The %{name} increases the hero's movement on land and sea." msgstr "Der %{name} erhöht Eure Bewegung auf dem Land und Meer." msgid "" "An old man claiming to be an inventor asks you to try his latest invention. " "He then hands you a compass." msgstr "" "Ein alter Mann, der behauptet, ein Erfinder zu sein, bittet Euch, seine " "neueste Erfindung auszuprobieren. Dann überreicht er Euch einen Kompass." msgid "Sailor's Astrolabe of Mobility" msgstr "Seemannsastrolabium der Mobilität" #, fuzzy msgid "The %{name} increases the hero's movement on sea." msgstr "Der %{name} erhöht Eure Bewegung auf dem Meer." msgid "" "An old sea captain is being tortured by Ogres. You save him, and in return " "he rewards you with a wondrous instrument to measure the distance of a star." msgstr "" "Ein alter Seekapitän wird von Ogern gefoltert. Ihr rettet ihn und er belohnt " "Euch im Gegenzug mit einem wundersamen Instrument zur Messung der Entfernung " "eines Sterns." msgid "Evil Eye" msgstr "Auge des Bösen" msgid "The %{name} reduces the casting cost of curse spells by half." msgstr "Der %{name} reduziert die Zauberkosten von Fluchzaubern um die Hälfte." msgid "" "While venturing into a decrepit hut you find the Skeleton of a long dead " "witch. Investigation of the remains reveals a glass eye rolling around " "inside an empty skull." msgstr "" "Beim Betreten einer verfallenen Hütte findet Ihr das Skelett einer längst " "verstorbenen Hexe. Bei der Untersuchung der Überreste findet sich ein " "Glasauge, das in ihrem leeren Schädel herumrollt." msgid "Enchanted Hourglass" msgstr "Verzauberte Sanduhr" #, fuzzy msgid "" "The %{name} extends the duration of all the hero's spells by %{count} turns." msgstr "" "Der %{name} verlängert die Dauer aller Eurer Zauber um %{count} Runden." msgid "" "A surprise turn in the landscape finds you in the midst of a grisly scene: " "Vultures picking at the aftermath of a terrible battle. Your cursory search " "of the remains turns up an enchanted hourglass." msgstr "" "Plötzlich wandelt sich die Landschaft und Ihr befindet Euch inmitten einer " "grässlichen Szenerie: Geier rupfen an den Überresten einer schrecklichen " "Schlacht. Bei der flüchtigen Durchsuchung der Überreste stoßt Ihr auf eine " "verzauberte Sanduhr." msgid "Gold Watch" msgstr "Goldene Uhr" #, fuzzy msgid "The %{name} doubles the effectiveness of the hero's hypnotize spells." msgstr "Die %{name} verdoppelt die Wirksamkeit Eurer Hypnosezauber." msgid "" "In reward for helping his cart out of a ditch, a traveling potion salesman " "gives you a \"magic\" gold watch. Unbeknownst to him, the watch really is " "magical." msgstr "" "Als Belohnung dafür, dass Ihr seinem Karren aus dem Graben helft, schenkt " "euch ein fahrender Apotheker eine \"magische\" Golduhr. Was er nicht weiß, " "ist, dass die Uhr wirklich magisch ist." msgid "Skullcap" msgstr "Totenkopfkappe" msgid "The %{name} halves the casting cost of all mind influencing spells." msgstr "" "Der %{name} halbiert die Zauberkosten aller bewusstseinsverändernden Zauber." msgid "" "A brief stop at an improbable rural inn yields an exchange of money, tales, " "and accidentally, luggage. You find a magical skullcap in your new backpack." msgstr "" "Bei einem kurzen Halt in einem unwahrscheinlichen ländlichen Gasthaus werden " "Geld, Geschichten und zufällig auch Gepäck ausgetauscht. Ihr findet eine " "magische Schädeldecke in Eurem neuen Rucksack." msgid "Ice Cloak" msgstr "Eismantel" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from cold spells." msgstr "" "Der %{name} halbiert den Schaden, den Eure Truppen durch Kältezauber " "erleiden." msgid "" "Responding to the panicked cries of a damsel in distress, you discover a " "young woman fleeing from a hungry bear. You slay the beast in the nick of " "time, and the grateful Sorceress weaves a magic cloak from the bear's hide." msgstr "" "Als Ihr auf die panikerfüllten Schreie eines Mädchens reagiert, entdeckt Ihr " "eine junge Frau auf der Flucht vor einem hungrigen Bären. Ihr erschlagt das " "Ungeheuer in letzter Sekunde. Die dankbare Zauberin Euch webt aus dem Fell " "des Bären einen magischen Umhang." msgid "Fire Cloak" msgstr "Feuermantel" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from fire spells." msgstr "" "Der %{name} halbiert den Schaden, den Eure Truppen durch Feuerzauber " "erleiden." msgid "" "You've come upon a fight between a Necromancer and a Paladin. The " "Necromancer blasts the Paladin with a fire bolt, bringing him to his knees. " "Acting quickly, you slay the evil one before the final blow. The grateful " "Paladin gives you the fire cloak that saved him." msgstr "" "Ihr seid in einen Kampf zwischen einem Nekromanten und einem Paladin " "geraten. Der Nekromant beschießt den Paladin mit einem Feuerball, der ihn in " "die Knie zwingt. Ihr handelt schnell und tötet den Bösewicht vor dem letzten " "Schlag. Der dankbare Paladin schenkt Euch den Feuerumhang, der ihn gerettet " "hat." msgid "Lightning Helm" msgstr "Blitzstrahlhelm" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from lightning " "spells." msgstr "" "Der %{name} halbiert den Schaden, den Eure Truppen durch Blitzzauber " "erleiden." msgid "" "A traveling tinker in need of supplies offers you a helm with a thunderbolt " "design on its top in exchange for food and water. Curious, you accept, and " "later find out that the helm is magical." msgstr "" "Ein umherziehender Kesselflicker, der Vorräte benötigt, bietet Euch im " "Tausch gegen Essen und Wasser einen Helm an, auf dessen Oberseite ein Blitz " "abgebildet ist. Aus Neugier nehmt Ihr an und findet später heraus, dass der " "Helm magisch ist." msgid "Evercold Icicle" msgstr "Immerkalter Eiszapfen" #, fuzzy msgid "" "The %{name} causes the hero's cold spells to do %{count} percent more damage " "to enemy troops." msgstr "" "Der %{name} bewirkt, dass Eure Kältezauber %{count} Prozent mehr Schaden an " "gegnerischen Truppen verursachen." msgid "" "An icicle withstanding the full heat of the noonday sun attracts your " "attention. Intrigued, you break it off, and find that it does not melt in " "your hand." msgstr "" "Ein Eiszapfen, der der vollen Mittagshitze widersteht, erregt Eure " "Aufmerksamkeit. Fasziniert brecht Ihr ihn ab und stellt fest, dass er in " "Eurer Hand nicht schmilzt." msgid "Everhot Lava Rock" msgstr "Immerheisser Lavafelsen" #, fuzzy msgid "" "The %{name} causes the hero's fire spells to do %{count} percent more damage " "to enemy troops." msgstr "" "Der %{name} bewirkt, dass Eure Feuerzauber %{count} Prozent mehr Schaden an " "gegnerischen Truppen verursachen." msgid "" "Your wanderings bring you into contact with a tribe of ape-like beings using " "a magical lava rock that never cools to light their fires. You take pity on " "them and teach them to make fire with sticks. Believing you to be a god, the " "apes give you their rock." msgstr "" "Auf Euren Wanderungen stoßt Ihr auf einen Stamm von affenähnlichen Wesen, " "die zum Anzünden ihrer Feuer einen magisches Lavastein benutzen, das nie " "abkühlt. Ihr habt Mitleid mit ihnen und lehrt sie, mit Stöcken Feuer zu " "machen. Die Affen halten Euch für einen Gott, und schenken Euch ihren Stein." msgid "Lightning Rod" msgstr "Blitzableiter" #, fuzzy msgid "" "The %{name} causes the hero's lightning spells to do %{count} percent more " "damage to enemy troops." msgstr "" "Der %{name} bewirkt, dass Eure Blitzzauber %{count} Prozent mehr Schaden an " "gegnerischen Truppen verursachen." msgid "" "While waiting out a storm, a lighting bolt strikes a nearby cottage's " "lightning rod, which melts and falls to the ground. The tip of the rod, " "however, survives intact and makes your hair stand on end when you touch it. " "Hmm..." msgstr "" "Während Ihr ein Gewitter abwartet, schlägt ein Blitz in den Blitzableiter " "eines nahe gelegenen Landhauses ein. Dieser schmilzt und fällt zu Boden. Die " "Spitze des Blitzableiters ist jedoch unversehrt geblieben und lässt einem " "die Haare zu Berge stehen, wenn man ihn berührt. Hmm..." msgid "Snake-Ring" msgstr "Schlangenring" #, fuzzy msgid "The %{name} halves the casting cost of all of the hero's bless spells." msgstr "Der %{name} halbiert die Zauberkosten aller Eurer Segenszauber." msgid "" "You've found an oddly shaped ring on the finger of a long dead traveler. The " "ring looks like a snake biting its own tail." msgstr "" "Ihr habt einen seltsam geformten Ring am Finger eines längst verstorbenen " "Reisenden gefunden. Der Ring sieht aus wie eine Schlange, die sich selbst in " "den Schwanz beißt." msgid "Ankh" msgstr "Ankh" #, fuzzy msgid "" "The %{name} doubles the effectiveness of all of the hero's resurrect and " "animate spells." msgstr "" "Das %{name} verdoppelt die Wirksamkeit aller Eurer Wiederbelebungs- und " "Belebungszauber." msgid "" "A fierce windstorm reveals the entrance to a buried tomb. Your investigation " "reveals that the tomb has already been looted, but the thieves overlooked an " "ankh on a silver chain in the dark." msgstr "" "Ein heftiger Windsturm enthüllt den Eingang zu einem vergrabenen Grab. Eure " "Untersuchung ergibt, dass das Grab bereits geplündert wurde, aber die Diebe " "haben in der Dunkelheit ein Ankh an einer Silberkette übersehen." msgid "Book of Elements" msgstr "Buch der Elemente" #, fuzzy msgid "" "The %{name} doubles the effectiveness of all of the hero's summoning spells." msgstr "Das %{name} verdoppelt die Wirksamkeit all Eurer Beschwörungszauber." msgid "" "You come across a conjurer who begs to accompany you and your army awhile " "for safety. You agree, and he offers as payment a copy of the book of the " "elements." msgstr "" "Ihr trefft auf einen Zauberkünstler, der Euch und Eure Armee anbettelt, ihn " "eine Zeit lang zu beschützen. Ihr stimmt zu und er bietet als Gegenleistung " "ein Exemplar des Buchs der Elemente an." msgid "Elemental Ring" msgstr "Elementring" msgid "The %{name} halves the casting cost of all summoning spells." msgstr "Der %{name} halbiert die Zauberkosten aller Beschwörungszauber." msgid "" "While pausing to rest, you notice a bobcat climbing a short tree to get at a " "crow's nest. On impulse, you climb the tree yourself and scare off the cat. " "When you look in the nest, you find a collection of shiny stones and a ring." msgstr "" "Bei einer Rast bemerkt Ihr einen Rotluchs, der auf einen kurzen Baum " "klettert, um an ein Krähennest zu gelangen. Spontan besteigt Ihr selbst den " "Baum und verscheucht die Katze. Als Ihr das Nest untersucht, findet Ihr eine " "Sammlung glänzender Steine und einen Ring." msgid "Holy Pendant" msgstr "Heiliger Anhänger" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to curse spells." msgstr "Der %{name} macht alle Eure Truppen immun gegen Fluchzauber." msgid "" "In your wanderings you come across a hermit living in a small, tidy hut. " "Impressed with your mission, he takes time out from his meditations to bless " "and give you a charm against curses." msgstr "" "Auf eurer Wanderschaft trefft Ihr auf einen Einsiedler, der in einer " "kleinen, aufgeräumten Hütte lebt. Beeindruckt von Eurer Mission, nimmt er " "sich die Zeit, Euch zu segnen und ein Zaubermittel gegen Flüche zu geben." msgid "Pendant of Free Will" msgstr "Anhänger des Freien Willens" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to hypnotize spells." msgstr "Der %{name} macht alle Eure Truppen immun gegen Hypnosezauber." msgid "" "Responding to cries for help, you find river Sprites making a sport of " "dunking an old man. Feeling vengeful, you rescue the man and drag a Sprite " "onto dry land for awhile. The Sprite, uncomfortable in the air, gives you a " "magic pendant to let him go." msgstr "" "Ihr hört Hilferufe. Danach schauend findet Ihr Fluss-Kobolde, die sich einen " "Spaß daraus machen, einen alten Mann zu ertränken. Ihr fühlt Euch " "rachsüchtig, rettet den Mann und zieht einen der Kobolde für eine Weile an " "Land. Der Kobold, der es hasst an der Luft zu sein, gibt euch einen " "magischen Anhänger, damit Ihr ihn gehen lasst." msgid "Pendant of Life" msgstr "Anhänger des Lebens" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to death spells." msgstr "Der %{name} macht alle Eure Truppen immun gegen Todeszauber." msgid "" "A brief roadside encounter with a small caravan and a game of knucklebones " "wins a magic pendant. Its former owner says that it protects from " "Necromancers' death spells." msgstr "" "Bei einer kurzen Begegnung mit einer kleinen Karawane gewinnt Ihr beim " "Glücksspiel einen magischer Anhänger. Sein früherer Besitzer sagt, er " "schütze vor den Todeszaubern der Totenbeschwörer." msgid "Serenity Pendant" msgstr "Anhänger der Heiterkeit" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to berserk spells." msgstr "Der %{name} macht alle Eure Truppen immun gegen Berserker-Zauber." msgid "" "The sounds of combat draw you to the scene of a fight between an old " "Barbarian and an eight-headed Hydra. Your timely intervention swings the " "battle in favor of the man, and he rewards you with a pendant he used to use " "to calm his mind for battle." msgstr "" "Laute Kampfgeräusche locken Euch an den Schauplatz eines Kampfes zwischen " "einem alten Barbaren und einer achtköpfigen Hydra. Durch Euer rechtzeitiges " "Eingreifen wird der Kampf zu Gunsten des Mannes entschieden, und er belohnt " "Euch mit einem Anhänger, mit dem er seinen Geist für den Kampf zu beruhigen " "pflegte." msgid "Seeing-eye Pendant" msgstr "Anhänger des Sehenden Auges" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to blindness spells." msgstr "Der %{name} macht alle Eure Truppen immun gegen Blendungszauber." msgid "" "You come upon a very old woman, long blind from cataracts and dying alone. " "You tend to her final needs and promise a proper burial. Grateful, she gives " "you a magic pendant emblazoned with a stylized eye. It lets you see with " "your eyes closed." msgstr "" "Ihr trefft auf eine sehr alte Frau, die seit langem am Grauen Star erblindet " "ist und allein im Sterben liegt. Ihr kümmert Euch um ihre letzten " "Bedürfnisse und versprecht Ihr ein angemessenes Begräbnis. Aus Dankbarkeit " "schenkt sie Euch einen magischen Anhänger, auf dem ein stilisiertes Auge " "prangt. Er lässt euch mit geschlossenen Augen sehen." msgid "Kinetic Pendant" msgstr "Kinetischer Anhänger" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to paralyze spells." msgstr "Der %{name} macht alle Eure Truppen immun gegen Lähmungszauber." msgid "" "You come across a golem wearing a glowing pendant and blocking your way. " "Acting on a hunch, you cut the pendant from its neck. Deprived of its power " "source, the golem breaks down, leaving you with the magical pendant." msgstr "" "Euer Weg wird versperrt von einem Golem, der einen leuchtenden Anhänger " "trägt. Aufgrund einer Vorahnung schneidet Ihr ihm den Anhänger vom Hals. " "Seiner Kraftquelle beraubt, bricht der Golem zusammen und überlässt Euch den " "magischen Anhänger." msgid "Pendant of Death" msgstr "Anhänger des Todes" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to holy spells." msgstr "Der %{name} macht alle Eure Truppen immun gegen heilige Zaubersprüche." msgid "" "A quick and deadly battle with a Necromancer wins you his magical pendant. " "Later, a Wizard tells you that the pendant protects undead under your " "control from holy word spells." msgstr "" "In einem schnellen und tödlichen Kampf mit einem Nekromanten gewinnt Ihr " "seinen magischen Anhänger. Später erfahrt Ihr von einem Zauberer, dass der " "Anhänger die Untoten in Euren Truppen vor den Zaubern des heiligen Wortes " "schützt." msgid "Wand of Negation" msgstr "Stab der Aufhebung" #, fuzzy msgid "" "The %{name} makes all of the hero's troops immune to dispel magic spells." msgstr "Der %{name} macht alle Eure Truppen immun gegen Todeszauber." msgid "" "You meet an old Wizard friend of yours traveling in the opposite direction. " "He presents you with a gift: A wand that prevents the use of the dispel " "magic spell on your allies." msgstr "" "Ihr trefft einen alten Zaubererfreund, der euch auf dem Weg entgegengen " "kommt. Er überreicht Euch ein Geschenk: Einen Zauberstab, der die Anwendung " "des Zaubers \"Magie vertreiben\" bei deinen Truppen verhindert." msgid "Golden Bow" msgstr "Goldene Bogen" #, fuzzy msgid "" "The %{name} eliminates the %{count} percent penalty for the hero's troops " "shooting past obstacles (e.g. castle walls)." msgstr "" "Der %{name} eliminiert den %{count} Prozentabzug für Eure Truppen, die an " "Hindernissen (z.B. Burgmauern) vorbeischießen." msgid "" "A chance meeting with a famous Archer finds you in a game of knucklebones " "pitting his bow against your horse. You win." msgstr "" "Ein zufälliges Treffen mit einem berühmten Bogenschützen führt zu einem " "Würfelspiel. Er setzt seinen Bogen, Ihr Euer Pferd. Ihr gewinnt." msgid "Telescope" msgstr "Teleskop" #, fuzzy msgid "" "The %{name} increases the amount of terrain the hero reveals when " "adventuring by %{count} extra square." msgstr "" "Der %{name} erhöht die Menge an Gelände, die Euer Held bei Abenteuern " "aufdeckt, um %{count} zusätzliche Felder." msgid "" "A merchant from far away lands trades you a new invention of his people for " "traveling supplies. It makes distant objects appear closer, and he calls " "it...\n" "\n" "a telescope." msgstr "" "Ein Händler aus einem fernen Land tauscht eine neue Erfindung seines Volkes " "gegen Reiseproviant ein. Sie lässt entfernte Objekte näher erscheinen, und " "er nennt sie...\n" "\n" "ein Teleskop." msgid "Statesman's Quill" msgstr "Feder des Staatsmannes" #, fuzzy msgid "" "The %{name} reduces the cost of surrender to %{count} percent of the total " "cost of troops the hero has in their army." msgstr "" "Der %{name} reduziert die Kosten der Kapitulation auf %{count} Prozent der " "Gesamtkosten der Truppen in Eurer Armee." msgid "" "You pause to help a diplomat with a broken axle fix his problem. In " "gratitude, he gives you a writing quill with magical properties which he " "says will \"help people see things your way\"." msgstr "" "Ihr haltet an, um einem Diplomaten bei der gebrochenen Achse seines Wagens " "zu helfen. Zum Dank schenkt er euch eine Schreibfeder mit magischen " "Eigenschaften, die, wie er sagt, \"den Leuten helfen wird, die Dinge auf " "eure Weise zu sehen\"." msgid "Wizard's Hat" msgstr "Hut des Zauberers" #, fuzzy msgid "" "The %{name} increases the duration of the hero's spells by %{count} turns." msgstr "Der %{name} erhöht die Dauer Eurer Zaubersprüche um %{count} Runden." msgid "" "You see a Wizard fleeing from a Griffin and riding like the wind. The Wizard " "opens a portal and rides through, getting his hat knocked off by the edge of " "the gate. The Griffin follows; the gate closes. You pick the hat up, dust it " "off, and put it on." msgstr "" "Ihr seht einen berittenen Zauberer, der vor einem Greifen flieht. Der " "Zauberer öffnet ein Portal und reitet hindurch, wobei ihm der Hut von der " "Kante des Tores abgeschlagen wird. Der Greif folgt ihm; das Tor schließt " "sich. Ihr hebt den Hut auf, staubt ihn ab und setzt ihn auf." msgid "Power Ring" msgstr "Ring der Macht" #, fuzzy msgid "The %{name} returns %{count} extra spell points per day to the hero." msgstr "" "Der %{name} gibt Eurem Helden %{count} zusätzliche Zauberpunkte pro Tag." msgid "" "You find a small tree that closely resembles the great Warlock Carnauth with " "a ring around one of its twigs. Scraps of clothing and rotting leather lead " "you to suspect that it IS Carnauth, transformed. Since you can't help him, " "you take the magic ring." msgstr "" "Ihr findet einen kleinen Baum, der dem großen Hexenmeister Carnauth sehr " "ähnelt und einen Ring an einem seiner Zweige trägt. Stofffetzen und " "verrottendes Leder lässt euch vermuten, dass es sich tatsächlich um den " "verwandelten Carnauth handelt. Da Ihr ihm nicht helfen könnt, nehmt Ihr den " "magischen Ring." msgid "Ammo Cart" msgstr "Munitionskarren" #, fuzzy msgid "" "The %{name} provides endless ammunition for all of the hero's troops that " "shoot." msgstr "" "Der %{name} liefert unendlich viel Munition für alle Eure Truppen, die " "schießen." msgid "" "An ammunition cart in the middle of an old battlefield catches your eye. " "Inspection shows it to be in good working order, so you take it along." msgstr "" "Inmitten eines alten Schlachtfeld fällt Euch ein Munitionswagen ins Auge. " "Bei der Inspektion stellt sich heraus, dass er in gutem Zustand ist, also " "nehmt Ihr ihn mit." msgid "Tax Lien" msgstr "Steuererklärung" #, fuzzy msgid "The %{name} costs the hero %{count} gold pieces per day." msgstr "Der %{name} kostet Euch %{count} Goldstücke pro Tag." msgid "" "Your big spending habits have earned you a massive tax bill that you can't " "hope to pay. The tax man takes pity and agrees to only take 250 gold a day " "from your account for life. Check here if you want one dollar to go to the " "presidential campaign election fund." msgstr "" "Euer überschwängliches Konsumverhalten hat Euch eine Steuerrechnung " "eingebracht, die Ihr niemals werdet zahlen können. Der Finanzbeamte hat " "Mitleid und willigt ein, lebenslang nur 250 Gold pro Tag von Eurem Konto " "abzuziehen. Unterschreibt hier, wenn Ihr für den Wahlkampffonds des " "Praesidenten spenden wollt." msgid "Hideous Mask" msgstr "Scheußliche Maske" #, fuzzy msgid "The %{name} prevents all 'wandering' armies from joining the hero." msgstr "" "Der %{name} verhindert, dass sich alle \"wandernden\" Armeen Eurem Helden " "anschließen." msgid "" "Your looting of the grave of Sinfilas Gardolad, the famous shapeshifting " "Warlock, unearths his fabled mask. Trembling, you put it on and it twists " "your visage into an awful grimace! Oh no! It's actually the hideous mask of " "Gromluck Greene, and you are stuck with it." msgstr "" "Ihr plündert das Grab von Sinfilas Gardolad, dem berühmten " "gestaltenwandlendem Hexenmeister und grabt seine sagenumwobene Maske aus. " "Zitternd setzt Ihr sie auf, und Euer Gesicht wird zu einer furchtbaren " "Fratze entstellt! Oh nein! Jetzt habt Ihr die scheussliche Maske von " "Gromluck Greene am Hals." msgid "Endless Pouch of Sulfur" msgstr "Endloser Schwefelbeutel" msgid "The %{name} provides %{count} unit of sulfur per day." msgstr "Der %{name} liefert %{count} Einheit Schwefel pro Tag." msgid "" "You visit an alchemist who, upon seeing your army, is swayed by the " "righteousness of your cause. The newly loyal subject gives you his endless " "pouch of sulfur to help with the war effort." msgstr "" "Ihr besucht einen Alchemisten, der beim Anblick Eurer Armee von der " "Rechtschaffenheit eurer Sache überzeugt wird. Der neue loyale Untertan " "schenkt Euch seinen endlosen Beutel mit Schwefel, um Euch bei den " "Kriegsanstrengungen zu helfen." msgid "Endless Vial of Mercury" msgstr "Endloses Quecksilberfläschchen" msgid "The %{name} provides %{count} unit of mercury per day." msgstr "Der %{name} liefert %{count} Einheit Quecksilber pro Tag." msgid "" "A brief stop at a hastily abandoned Wizard's tower turns up a magical vial " "of mercury that always has a little left on the bottom. Recognizing a " "treasure when you see one, you cap it and slip it in your pocket." msgstr "" "Bei einem kurzen Zwischenstopp in einem hastig verlassenen Zaubererturm " "findet Ihr ein magisches Fläschchen mit Quecksilber, in dem immer ein wenig " "am Boden übrig bleibt. Einen Schatz erkennt Ihr immer sofort wenn Ihr ihn " "seht! Schnell steckt Ihr das Fläschchen in eure Tasche." msgid "Endless Pouch of Gems" msgstr "Endloser Edelsteinbeutel" msgid "The %{name} provides %{count} unit of gems per day." msgstr "Der %{name} liefert %{count} Einheit Edelsteine pro Tag." msgid "" "A short rainstorm brings forth a rainbow...and you can see the end of it. " "Riding quickly, you seize the pot of gold you find there. The leprechaun who " "owns it, unable to stop you from taking it, offers an endless pouch of gems " "for the return of his gold. You accept." msgstr "" "Ein kurzer Regenschauer bringt einen Regenbogen hervor... und Ihr könnt das " "Ende des Regenbogens sehen. Schnell reitet Ihr los und nehmt den Goldtopf, " "den Ihr dort findet. Der Kobold, dem er gehört, kann Euch nicht daran " "hindern, bietet euch aber einen endlosen Beutel mit Edelsteinen an, wenn Ihr " "ihm sein Gold zurückgebt. Ihr akzeptiert diesen Tausch." msgid "Endless Cord of Wood" msgstr "Endloses Holzbündel" msgid "The %{name} provides %{count} unit of wood per day." msgstr "Der %{name} liefert %{count} Einheit Holz pro Tag." msgid "" "Pausing to rest and light a cook fire, you pull wood out of a nearby pile of " "dead wood. As you keep pulling wood from the pile, you notice that it " "doesn't shrink. You realize to your delight that the wood is enchanted, so " "you take it along." msgstr "" "Neben einem Holzestoß macht Ihr eine Rast, um euch auszuruhen. Für ein " "Lagerfeuer zieht Ihr einige Holzscheite aus dem Haufen. Dabei bemerkt Ihr, " "dass der Haufen nicht zu schrumpfen scheint. Zu Eurer Freude handelt es sich " "um verzaubertes Holz, welches Ihr sofort mitnehmt." msgid "Endless Cart of Ore" msgstr "Endloser Erzkarren" msgid "The %{name} provides %{count} unit of ore per day." msgstr "Der %{name} liefert %{count} Einheit Erz pro Tag." msgid "" "You've found a Goblin weapon smithy making weapons for use against humans. " "With a tremendous yell you and your army descend upon their camp and drive " "them away. A search finds a magic ore cart that never runs out of iron." msgstr "" "Ihr habt eine Goblin-Waffenschmiede gefunden, die Waffen für den Einsatz " "gegen Menschen herstellt. Mit einem gewaltigen Schrei fallt Ihr mit eurer " "Armee über das Lager her und vertreibt die Goblins. Bei der Durchsuchung " "wird ein magischer Erzkarren sichergestellt, dem das Eisen nie ausgeht." msgid "Endless Pouch of Crystal" msgstr "Endloser Kristallbeutel" msgid "The %{name} provides %{count} unit of crystal per day." msgstr "Der %{name} liefert %{count} Einheiten Kristall pro Tag." msgid "" "Taking shelter from a storm in a small cave, you notice a small patch of " "crystal in one corner. Curious, you break a piece off and notice that the " "original crystal grows the lost piece back. You decide to stuff the entire " "patch into a pouch and take it with you." msgstr "" "Als Ihr in einer kleinen Höhle Schutz vor einem Sturm sucht, bemerkt Ihr in " "einer Ecke ein kleinen Kristall. Neugierig brecht Ihr ein Stück ab und " "stellt fest, dass dem Kristall das abgebrochene Stück nachwächst. Ihr " "beschließt, das gesamte Stück in einen Beutel zu stecken und mitzunehmen." msgid "Spiked Helm" msgstr "Stachelhelm" msgid "" "Your army is ambushed by a small tribe of wild (and none too bright) Orcs. " "You fend them off easily and the survivors flee in all directions. One of " "the Orcs was wearing a polished spiked helm. Figuring it will make a good " "souvenir, you take it." msgstr "" "Eure Armee gerät in einen Hinterhalt eines kleinen Stammes wilder (und nicht " "sehr schlauer) Orks. Ihr wehrt sie mühelos ab, und die Überlebenden fliehen " "in alle Richtungen. Einer der Orks trug einen polierten Helm mit Stacheln. " "Da er ein gutes Souvenir abgibt, nehmt Ihr ihn mit." msgid "Spiked Shield" msgstr "Stachelschild" msgid "" "You come upon a bridge spanning a dry gully. Before you can cross, a Troll " "steps out from under the bridge and demands payment before it will permit " "you to pass. You refuse, and the Troll charges, forcing you to slay it. You " "take its spiked shield as a trophy." msgstr "" "Ihr kommt zu einer Brücke, die eine trockene Schlucht überspannt. Bevor Ihr " "sie überqueren könnt, tritt ein Troll unter der Brücke hervor und verlangt " "einen Wegezoll. Ihr lehnt ab, woraufhin der Troll angreift und Ihr ihn " "erschlagt. Ihr nehmt seinen Stachelschild als Trophäe an euch." msgid "White Pearl" msgstr "Weisse Perle" msgid "" "A walk across a dry saltwater lake bed yields an unlikely prize: A white " "pearl amidst shattered shells and debris." msgstr "" "Bei einem Spaziergang über das Bett eines vertrockneten Salzwassersees " "findet Ihr eine unwahrscheinliche Beute: Eine weiße Perle inmitten von " "zerbrochenen Muscheln und Schutt." msgid "Black Pearl" msgstr "Schwarze Perle" msgid "" "Rumors of a Griffin of unusual size preying upon the countryside lead you to " "its cave lair. A quick, brutal fight dispatches the beast, and a search of " "its foul nest turns up a huge black pearl." msgstr "" "Gerüchte über einen Greif von ungewöhnlicher Größe, der das Land heimsucht, " "führen Euch zu seinem Höhlennest. Ein schneller, brutaler Kampf schickt die " "Bestie ins Jenseits. Bei der Durchsuchung des fauligen Nestes findet Ihr " "eine riesige schwarze Perle." msgid "Magic Book" msgstr "Zauberbuch" #, fuzzy msgid "The %{name} enables the hero to cast spells." msgstr "Das %{name} ermöglicht Euch Zaubersprüche auszusprechen." msgid "Dummy 1" msgstr "Dummy 1" msgid "The reserved artifact." msgstr "Das reservierte Artefakt." msgid "Dummy 2" msgstr "Dummy 2" msgid "Dummy 3" msgstr "Dummy 3" msgid "Dummy 4" msgstr "Dummy 4" msgid "Spell Scroll" msgstr "Zauberspruchrolle" #, fuzzy msgid "" "This %{name} gives the hero the ability to cast the %{spell} spell if the " "hero has a Magic Book." msgstr "" "Dieser %{name} gibt Eurem Helden die Fähigkeit, den %{spell} zu wirken, wenn " "Euer Held ein Zauberbuch besitzt." msgid "" "You find an elaborate container which houses an old vellum scroll. The runes " "on the container are very old, and the artistry with which it was put " "together is stunning. As you pull the scroll out, you feel imbued with " "magical power." msgstr "" "Ihr findet ein kunstvolles Gefäß, das eine alte Pergamentschriftrolle " "enthält. Die Runen auf dem Behälter sind sehr alt, und die Kunstfertigkeit, " "mit der er zusammengesetzt wurde, ist atemberaubend. Als Ihr die " "Schriftrolle herauszieht, fühlt Ihr Euch von magischer Kraft durchdrungen." msgid "Arm of the Martyr" msgstr "Arm des Märtyrers" #, fuzzy msgid "" "The %{name} increases the hero's spell power by %{count} but adds the undead " "morale penalty." msgstr "" "Der %{name} erhöht Eure Zauberkraft um %{count}, fügt aber eine Strafe auf " "die Moral für die Untote in der Armee hinzu." msgid "" "One of the less intelligent members of your party picks up an arm off of the " "ground. Despite its missing a body, it is still moving. Your troops find the " "dismembered arm repulsive, but you cannot bring yourself to drop it: it " "seems to hold some sort of magical power that influences your decision " "making." msgstr "" "Eines der weniger hellen Mitglieder Eurer Gruppe hebt einen Arm vom Boden " "auf. Obwohl ihm einen Körper fehlt, bewegt er sich noch. Eure Truppen finden " "den abgetrennten Arm abstoßend, aber Ihr könnt Euch nicht überwinden, ihn " "fallen zu lassen: Er scheint eine Art magische Kraft zu besitzen, die Eure " "Entscheidungsfindung beeinflusst." msgid "Breastplate of Anduran" msgstr "Brustpanzer von Anduran" #, fuzzy msgid "The %{name} increases the hero's defense by %{count}." msgstr "Der %{name} erhöht Eure Verteidigung um %{count}." msgid "" "You come upon a sign. It reads: \"Here lies the body of Anduran. Bow and " "swear fealty, and you shall be rewarded.\" You decide to do as it says. As " "you stand up, you feel a coldness against your skin. Looking down, you find " "that you are suddenly wearing a gleaming, ornate breastplate." msgstr "" "Ihr stoßt auf ein Schild. Darauf steht geschrieben: \"Hier liegt die Leiche " "von Anduran. Verbeuge dich und schwöre Treue, und du wirst belohnt werden.\" " "Ihr tut was das Schild verlang. Als Ihr aufsteht, spührt Ihr Kälte aufEurer " "Haut. Als Ihr an Euch herunterschaut, stellt Ihr fest, dass Ihr plötzlich " "einen schimmernden, verzierten Brustpanzer tragt." msgid "Broach of Shielding" msgstr "Brosche der Abschirmung" msgid "" "The %{name} provides %{count} percent protection from Armageddon and " "Elemental Storm, but decreases spell power by 2." msgstr "" "Der %{name} bietet %{count} Prozent Schutz vor Armageddon und " "Elementarsturm, verringert aber die Zauberkraft um 2." msgid "" "A kindly Sorceress thinks that your army's defenses could use a magical " "boost. She offers to enchant the Broach that you wear on your cloak, and you " "accept." msgstr "" "Eine freundliche Zauberin ist der Meinung, dass die Verteidigung Eurer Armee " "eine magische Verstärkung gebrauchen könnte. Sie bietet Euch an, die " "Brosche, die Ihr an Euren Mantel tragt, zu verzaubern, was Ihr akzeptiert." msgid "Battle Garb of Anduran" msgstr "Kampfrüstung von Anduran" #, fuzzy msgid "" "The %{name} combines the powers of the three Anduran artifacts. It provides " "maximum luck and morale for the hero's troops and gives the hero the Town " "Portal spell." msgstr "" "Der %{name} vereint die Kräfte der drei anduranischen Artefakte. Es bringt " "Euren Truppen maximales Glück und Moral und verleiht Euch den Zauber " "\"Stadtportal\"." msgid "" "Out of pity for a poor peasant, you purchase a chest of old junk they are " "hawking for too much gold. Later, as you search through it, you find it " "contains the 3 pieces of the legendary battle garb of Anduran!" msgstr "" "Aus Mitleid mit einem armen Bauern kauft Ihr ihm eine Truhe mit altem " "Gerümpel ab, das dieser für viel zu viel Gold feilbietet. Als Ihr die später " "durchsucht, entdeckt Ihr, dass diese die 3 Teile des legendären " "Kampfgewandes von Anduran enthält!" msgid "Crystal Ball" msgstr "Kristallkugel" #, fuzzy msgid "" "The %{name} lets the hero get more specific information about monsters, " "enemy heroes, and castles nearby the hero." msgstr "" "Mit %{name} erhaltet Ihr genauere Informationen über Monster, feindliche " "Helden und Burgen in der Nähe des Helden, der sie hält." msgid "" "You come upon a caravan of gypsies who are feasting and fortifying their " "bodies with mead. They call you forward and say \"If you prove that you can " "dance the Rama-Buta, we will reward you.\" You don't know it, but try " "anyway. They laugh hysterically, but admire your bravery, giving you a " "Crystal Ball." msgstr "" "Ihr trefft auf eine Karawane von Zigeunern, die gerade auf einer Weise " "feiern und sich mit Met stärken. Sie rufen Euch nach vorne und sagen: \"Wenn " "Du den Rama-Buta tanzen kannst, werden wir Dich belohnen.\" Ihr wisst nicht " "genau was gemeint ist, versucht es aber trotzdem. Die Zigeunern lachen " "hysterisch, bewundern aber Eure Tapferkeit und schenken Euch eine " "Kristallkugel." msgid "Heart of Fire" msgstr "Herz aus Feuer" msgid "" "The %{name} provides %{count} percent protection from fire, but doubles the " "damage taken from cold." msgstr "" "Der %{name} bietet %{count} Prozent Schutz vor Feuer, verdoppelt aber den " "durch Kälte erlittenen Schaden." msgid "" "You enter a recently burned glade and come upon a Fire Elemental sitting " "atop a rock. It looks up, its flaming face contorted in a look of severe " "pain. It then tosses a glowing object at you. You put up your hands to block " "it, but it passes right through them and sears itself into your chest." msgstr "" "Ihr betretet eine kürzlich abgebrannte Lichtung, wo ein Feuerwesen auf einem " "Felsen sitzt. Es schaut auf und sein flammendes Gesicht ist schmerzverzerrt. " "Dann wirft es ein glühendes Objekt nach Euch. Schnell schützt Ihr Euch mit " "Euren Händen, aber es fliegt einfach durch sie hindurch und brennt sich ein " "in Eure Brust." msgid "Heart of Ice" msgstr "Herz aus Eis" msgid "" "The %{name} provides %{count} percent protection from cold, but doubles the " "damage taken from fire." msgstr "" "Der %{name} bietet %{count} Prozent Schutz vor Kälte, verdoppelt aber den " "durch Feuer erlittenen Schaden." msgid "" "Suddenly, a biting coldness engulfs your body. You seize up, falling from " "your horse. The pain subsides, but you still feel as if your chest is " "frozen. As you pick yourself up off of the ground, you hear hearty laughter. " "You turn around just in time to see a Frost Giant run off into the woods and " "disappear." msgstr "" "Plötzlich durchdringt eine beißende Kälte Euren Körper. Ihr erstarrt und " "fallt vom Pferd. Der Schmerz lässt nach, aber Eure Brust fühlt sich noch " "immer gefroren an. Als Ihr vom Boden aufsteht, hört Ihr lautes Gelächter. " "Ihr dreht Euch um und seht gerade noch einen Frostriesen, der im Wald " "verschwindet." msgid "Helmet of Anduran" msgstr "Helm von Anduran" msgid "" "You spy a gleaming object poking up out of the ground. You send a member of " "your party over to investigate. He comes back with a golden helmet in his " "hands. You realize that it must be the helmet of the legendary Anduran, the " "only man who was known to wear solid gold armor." msgstr "" "Ihr entdeckt ein schimmerndes Objekt, das aus dem Boden ragt. Ihr schickt " "ein Mitglied Eure Truppen los, um nachzusehen. Er kommt zurück mit einem " "goldenen Helm in den Händen. Ihr erkennt, dass es der Helm des legendären " "Anduran ist, des einzigen Mannes, der eine Rüstung aus massivem Gold trug." msgid "Holy Hammer" msgstr "Heiliger Hammer" msgid "" "You come upon a battle where a Paladin has been mortally wounded by a group " "of Zombies. He asks you to take his hammer and finish what he started. As " "you pick it up, it begins to hum, and then everything becomes a blur. The " "Zombies lie dead, the hammer dripping with blood. You strap it to your belt." msgstr "" "Ihr kommt zu einem Kampf, bei dem ein Paladin von einer Gruppe Zombies " "tödlich verletzt wurde. Er bittet Euch, seinen Hammer zu nehmen und den " "Kampf zu beenden. Als Ihr ihn aufhebt, beginnt er zu summen und alles " "verschwimmt. Die Zombies sind tot, der Hammer trieft vor Blut. Ihr hängt ihn " "an Euren Gürtel." msgid "Legendary Scepter" msgstr "Legendäres Zepter" msgid "The %{name} adds %{count} points to all attributes." msgstr "Der %{name} fügt %{count} Punkte zu allen Attributen hinzu." msgid "" "Upon cresting a small hill, you come upon a ridiculous looking sight. A " "Sprite is attempting to carry a Scepter that is almost as big as it is. " "Trying not to laugh, you ask, \"Need help?\" The Sprite glares at you and " "answers: \"You think this is funny? Fine. You can carry it. I much prefer " "flying anyway.\"" msgstr "" "Auf einem Hügel bietet sich Euch ein lächerlicher Anblick. Eine Fee versucht " "ein Zepter zu tragen, das fast so groß ist wie sie selbst. Ihr versucht " "nicht zu lachen und fragen: \"Brauchst Du Hilfe?\" Der Fee starrt Euch an " "und antwortet: \"Findest Du das lustig? Gut. Dann nimm es. Ich fliege lieber " "davon.\"" msgid "" "An old seaman tells you a tale of an enchanted masthead that he used in his " "youth to rally his crew during times of trouble. He then hands you a faded " "map that shows where he hid it. After much exploring, you find it stashed " "underneath a nearby dock." msgstr "" "Ein alter Seemann erzählt Euch eine Geschichte von einer verzauberten " "Gallionsfigur, mit der er früher seine Crew in schwierigen Zeiten bei Laune " "hielt. Er gibt Euch eine ausgeblichene Karte, die zeigt, wo er sie versteckt " "hat. Nach langer Suche findet Ihr sie an einem nahen Dock." msgid "Masthead" msgstr "Gallionsfigur" #, fuzzy msgid "" "The %{name} boosts the hero's troops' luck and morale by %{count} each in " "sea combat." msgstr "" "Der %{name} erhöht Euer Glück und Eure Moral im Seekampf um jeweils %{count}." msgid "Sphere of Negation" msgstr "Zauberkugel der Aufhebung" msgid "The %{name} disables all spell casting, for both sides, in combat." msgstr "Der %{name} deaktiviert alle Zaubersprüche für beide Seiten im Kampf." msgid "" "You stop to help a Peasant catch a runaway mare. To show his gratitude, he " "hands you a tiny sphere. As soon as you grasp it, you feel the magical " "energy drain from your limbs..." msgstr "" "Ihr haltet an, um einem Bauern zu helfen, eine entlaufene Stute zu fangen. " "Als Zeichen seiner Dankbarkeit gibt er Euch eine winzige Kugel. Als Ihr sie " "ergreifet, fühlt Ihr die magische Energie aus Euren Gliedern abfliessen..." msgid "Staff of Wizardry" msgstr "Stab der Zauberei" #, fuzzy msgid "The %{name} boosts the hero's spell power by %{count}." msgstr "Der %{name} erhöht Eure Zauberkraft um %{count}." msgid "" "While out scaring up game, your troops find a mysterious staff levitating " "about three feet off of the ground. They hand it to you, and you notice an " "inscription. It reads: \"Brains best brawn and magic beats might. Heed my " "words, and you'll win every fight.\"" msgstr "" "Während einer Erkundung finden Eure Truppen einen mysterioesen Stab, der " "etwa auf Bauchhöhe über dem Boden schwebt. Sie geben Euch den Stab, wobei " "Ihr eine Inschrift bemerkt: \"Geist schlägt Muskel und Magie schlägt Macht. " "Beachte meine Worte, dann gewinnst Du jede Schlacht.\"" msgid "Sword Breaker" msgstr "Parierdolch" #, fuzzy msgid "The %{name} increases the hero's defense by %{count} and attack by 1." msgstr "" "Der %{name} erhöht Eure Verteidigung um %{count} und Euren Angriff um 1." msgid "" "A former Captain of the Guard admires your quest and gives you the enchanted " "Sword Breaker that he relied on during his tour of duty." msgstr "" "Ein ehemaliger Hauptmann der Garde bewundert Eure Mission und schenkt Euch " "den verzauberten Parierdolch, auf den er sich während seiner gesamten " "Dienstzeit verlassen hat." msgid "Sword of Anduran" msgstr "Schwert von Anduran" msgid "" "A Troll stops you and says: \"Pay me 5,000 gold, or the Sword of Anduran " "will slay you where you stand.\" You refuse. The troll grabs the sword " "hanging from its belt, screams in pain, and runs away. Picking up the fabled " "sword, you give thanks that half-witted Trolls tend to grab the wrong end of " "sharp objects." msgstr "" "Ein Troll stoppt Euch und sagt: \"Bezahl mir 5.000 Gold, oder das Schwert " "von Anduran wird Dich auf der Stelle töten.\" Ihr verweigert. Der Troll " "packt das an seinem Gürtel hängende Schwert, schreit vor Schmerz und läuft " "davon. Ihr hebt auf das sagenumworbene Schwert und denkt bei Euch, dass " "dumme Trolle oft scharfe Dinge am falschen Ende packen." msgid "Spade of Necromancy" msgstr "Spaten der Nekromantie" #, fuzzy msgid "The %{name} gives the hero increased necromancy skill." msgstr "Der %{name} verleiht Euch erhöhte Nekromantie-Fähigkeiten." msgid "" "A dirty shovel has been thrust into a dirt mound nearby. Upon investigation, " "you discover it to be the enchanted shovel of the Gravediggers, long thought " "lost by mortals." msgstr "" "Eine schmutziger Spaten wurde in einen nahen Erdhügel gestossen. Bei näherer " "Untersuchung stellt Ihr fest, dass es sich um den verzauberten Spaten der " "Totengräber handelt, der den Sterblichen schon lange als verschollen galt." msgid "spellBonus|selected by user" msgstr "" msgid "Wood" msgstr "Holz" msgid "Mercury" msgstr "Quecksilber" msgid "Ore" msgstr "Erz" msgid "Sulfur" msgstr "Schwefel" msgid "Crystal" msgstr "Kristalle" msgid "Gems" msgstr "Edelsteine" msgid "Gold" msgstr "Gold" msgid "" "There are seven resources in Heroes 2, used to build and improves castles, " "purchase troops and recruit heroes. Gold is the most common, required for " "virtually everything. Wood and ore are used for most buildings. Gems, " "Mercury, Sulfur and Crystal are rare magical resources used for the most " "powerful creatures and buildings." msgstr "" "In Heroes 2 gibt es sieben Ressourcen, mit denen Burgen gebaut und " "verbessert, Truppen gekauft und Helden rekrutiert werden können. Gold ist " "der häufigste und wird für praktisch alles benötigt. Holz und Erze werden " "für die meisten Gebäude verwendet. Edelsteine, Quecksilber, Schwefel und " "Kristall sind seltene magische Ressourcen, die für die mächtigsten Kreaturen " "und Gebäude verwendet werden." msgid "" "Causes a giant fireball to strike the selected area, damaging all nearby " "creatures." msgstr "" "Verursacht einen riesigen Feuerball, der das ausgewählte Gebiet trifft und " "allen Kreaturen in der Nähe Schaden zufügt." msgid "Fireball" msgstr "Feuerball" msgid "Fireblast" msgstr "Feuerstoß" msgid "" "An improved version of fireball, fireblast affects two hexes around the " "center point of the spell, rather than one." msgstr "" "Eine verbesserte Version des Feuerballs: Der Feuerstoß wirkt auf zwei Felder " "um den Mittelpunkt des Zaubers statt auf ein Feld." msgid "Causes a bolt of electrical energy to strike the selected creature." msgstr "" "Verursacht einen Blitz aus elektrischer Energie, der die ausgewählte Kreatur " "trifft." msgid "Lightning Bolt" msgstr "Blitzstrahl" msgid "Chain Lightning" msgstr "Kettenblitzstrahl" msgid "" "Causes a bolt of electrical energy to strike a selected creature, then " "strike the nearest creature with half damage, then strike the NEXT nearest " "creature with half again damage, and so on, until it becomes too weak to be " "harmful. Warning: This spell can hit your own creatures!" msgstr "" "Verursacht einen Blitz elektrischer Energie, der eine ausgewählte Kreatur " "trifft, dann die nächste Kreatur mit halbem Schaden trifft, dann die nächste " "Kreatur mit wieder halbem Schaden trifft, und so weiter, bis er zu schwach " "wird, um Schaden anzurichten. Warnung! Dieser Zauber kann Eure eigenen " "Kreaturen treffen!" msgid "Teleport" msgstr "Teleport" msgid "" "Teleports the creature you select to any open position on the battlefield." msgstr "" "Teleportiert die Kreatur, die Ihr auswählt, an eine freie Position Eurer " "Wahl auf dem Schlachtfeld." msgid "Cure" msgstr "Heilung" msgid "" "Removes all negative spells cast upon one of your units, and restores up to " "%{count} HP per level of spell power." msgstr "" "Entfernt alle negativen Zauber, die auf eine Eurer Einheiten gewirkt wurden, " "und stellt bis zu %{count} TP pro Stufe der Zauberkraft wieder her." msgid "Mass Cure" msgstr "Massenheilung" msgid "" "Removes all negative spells cast upon your forces, and restores up to " "%{count} HP per level of spell power, per creature." msgstr "" "Entfernt alle negativen Zauber, die auf Eure Truppen gewirkt wurden, und " "stellt bis zu %{count} TP pro Stufe der Zauberkraft und pro Kreatur wieder " "her." msgid "Resurrect" msgstr "Auferstehung" msgid "Resurrects creatures from a damaged or dead unit until end of combat." msgstr "" "Lässt Kreaturen einer beschädigten oder toten Einheit bis zum Ende des " "Kampfes wieder auferstehen." msgid "Resurrect True" msgstr "Wahre Auferstehung" msgid "Resurrects creatures from a damaged or dead unit permanently." msgstr "" "Lässt Kreaturen aus einer beschädigten oder toten Einheit dauerhaft wieder " "auferstehen." msgid "Haste" msgstr "Eile" msgid "Increases the speed of any creature by %{count}." msgstr "Erhöht die Geschwindigkeit einer beliebigen Kreatur um %{count}." msgid "Increases the speed of all of your creatures by %{count}." msgstr "Erhöht die Geschwindigkeit aller Eurer Kreaturen um %{count}." msgid "Mass Haste" msgstr "Masseneile" msgid "Slows target to half movement rate." msgstr "Verlangsamt die Bewegungsgeschwindigkeit des Ziels auf die Hälfte." msgid "spell|Slow" msgstr "Verlangsamung" msgid "Mass Slow" msgstr "Massenverlangsamung" msgid "Slows all enemies to half movement rate." msgstr "Verlangsamt alle Feinde auf die halbe Bewegungsrate." msgid "Clouds the affected creatures' eyes, preventing them from moving." msgstr "" "Trübt die Augen der betroffenen Kreaturen und hindert sie daran, sich zu " "bewegen." msgid "spell|Blind" msgstr "Blenden" msgid "Bless" msgstr "Segen" msgid "Causes the selected creatures to inflict maximum damage." msgstr "" "Bewirkt, dass die ausgewählten Kreaturen maximalen Schaden verursachen." msgid "Causes all of your units to inflict maximum damage." msgstr "Bewirkt, dass alle Eure Einheiten maximalen Schaden verursachen." msgid "Mass Bless" msgstr "Massensegen" msgid "Magically increases the defense skill of the selected creatures." msgstr "" "Erhöht auf magische Weise die Verteidigungsfähigkeit der ausgewählten " "Kreaturen." msgid "Stoneskin" msgstr "Steinhaut" msgid "" "Increases the defense skill of the targeted creatures. This is an improved " "version of Stoneskin." msgstr "" "Erhöht die Verteidigungsfähigkeit der ausgewählten Kreaturen. Dies ist eine " "verbesserte Version von Steinhaut." msgid "Steelskin" msgstr "Stahlhaut" msgid "Causes the selected creatures to inflict minimum damage." msgstr "" "Bewirkt, dass die ausgewählten Kreaturen minimalen Schaden verursachen." msgid "Curse" msgstr "Fluch" msgid "Causes all enemy troops to inflict minimum damage." msgstr "Bewirkt, dass alle gegnerischen Truppen minimalen Schaden verursachen." msgid "Mass Curse" msgstr "Massenfluch" msgid "Damages all undead in the battle." msgstr "Schadet allen Untoten im Kampf." msgid "Holy Word" msgstr "Heiliges Wort" msgid "" "Damages all undead in the battle. This is an improved version of Holy Word." msgstr "" "Schadet allen Untoten im Kampf. Dies ist eine verbesserte Version von " "Heiliges Wort." msgid "Holy Shout" msgstr "Heiliger Ruf" msgid "Anti-Magic" msgstr "Anti-Magie" msgid "Prevents harmful magic against the selected creatures." msgstr "Verhindert schädliche Magie gegen die ausgewählten Kreaturen." msgid "Dispel Magic" msgstr "Magie zerstreuen" msgid "Removes all magic spells from a single target." msgstr "Entfernt alle Zaubersprüche von einem einzigen Ziel." msgid "Mass Dispel" msgstr "Magie-Mas- senzerstrg." msgid "Removes all magic spells from all creatures." msgstr "Entfernt alle Zaubersprüche von allen Kreaturen." msgid "Causes a magic arrow to strike the selected target." msgstr "Bewirkt, dass ein magischer Pfeil das ausgewählte Ziel trifft." msgid "Magic Arrow" msgstr "Magischer Pfeil" msgid "Berserker" msgstr "Berserker" msgid "Causes a creature to attack its nearest neighbor." msgstr "Bewirkt, dass eine Kreatur ihren nächsten Nachbarn angreift." msgid "Armageddon" msgstr "Armageddon" msgid "" "Holy terror strikes the battlefield, causing severe damage to all creatures." msgstr "" "Heiliger Terror schlägt auf dem Schlachtfeld zu und verursacht an allen " "Kreaturen schweren Schaden." msgid "Elemental Storm" msgstr "Elementsturm" msgid "Magical elements pour down on the battlefield, damaging all creatures." msgstr "" "Magische Elemente ergießen sich auf das Schlachtfeld und fügen allen " "Kreaturen Schaden zu." msgid "" "A rain of rocks strikes an area of the battlefield, damaging all nearby " "creatures." msgstr "" "Ein Steinregen trifft ein Gebiet auf dem Schlachtfeld und fügt allen " "Kreaturen in der Nähe Schaden zu." msgid "Meteor Shower" msgstr "Meteorregen" msgid "Paralyze" msgstr "Lähmen" msgid "The targeted creatures are paralyzed, unable to move or retaliate." msgstr "" "Die anvisierten Kreaturen sind gelähmt und können sich nicht bewegen oder " "sich wehren." msgid "Hypnotize" msgstr "Hypnose" msgid "" "Brings a single enemy unit under your control if its hits are less than " "%{count} times the caster's spell power." msgstr "" "Bringt eine einzelne feindliche Einheit unter deine Kontrolle, wenn ihre " "Treffer weniger als das %{count}-fache der Zauberkraft des Zaubernden " "betragen." msgid "Cold Ray" msgstr "Kalter Strahl" msgid "Drains body heat from a single enemy unit." msgstr "Entzieht einer einzelnen gegnerischen Einheit Körperwärme." msgid "Cold Ring" msgstr "Kalter Ring" msgid "" "Drains body heat from all units surrounding the center point, but not " "including the center point." msgstr "" "Leitet die Körperwärme von allen Einheiten ab, die den Mittelpunkt umgeben, " "aber den Mittelpunkt nicht einschließen." msgid "Disrupting Ray" msgstr "Zertruemmerungsstrahl" msgid "Reduces the defense rating of an enemy unit by three." msgstr "Reduziert den Verteidigungswert einer gegnerischen Einheit um drei." msgid "Damages all living (non-undead) units in the battle." msgstr "Schadet allen lebenden (nicht untoten) Einheiten in der Schlacht." msgid "Death Ripple" msgstr "Kleine Todeswelle" msgid "Death Wave" msgstr "Todeswelle" msgid "" "Damages all living (non-undead) units in the battle. This spell is an " "improved version of Death Ripple." msgstr "" "Schadet allen lebenden (nicht untoten) Einheiten in der Schlacht. Dieser " "Zauber ist eine verbesserte Version von Kleine Todeswelle." msgid "Dragon Slayer" msgstr "Drachentöter" msgid "Greatly increases a unit's attack skill vs. Dragons." msgstr "Erhöht die Angriffsfähigkeit einer Einheit gegen Drachen erheblich." msgid "Blood Lust" msgstr "Blutdurst" msgid "Increases a unit's attack skill." msgstr "Erhöht die Angriffsfähigkeit einer Einheit." msgid "Animate Dead" msgstr "Tote beleben" msgid "Resurrects creatures from a damaged or dead undead unit permanently." msgstr "" "Lässt Kreaturen aus einer beschädigten oder toten untoten Einheit dauerhaft " "wieder auferstehen." msgid "Mirror Image" msgstr "Spiegelbild" msgid "" "Creates an illusionary unit that duplicates one of your existing units. This " "illusionary unit does the same damages as the original, but will vanish if " "it takes any damage." msgstr "" "Erzeugt eine illusionäre Einheit als Spiegelbild einer wirklichen Einheit. " "Die illusionäre Einheit verursacht die gleichen Schäden wie die Echte,löst " "sich aber bei der geringsten Verletzung in Luft auf." msgid "Shield" msgstr "Schild" msgid "" "Halves damage received from ranged attacks for a single unit. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Halbiert den Schaden von Fernkampfangriffen für eine einzelne Einheit. " "Beeinflusst nicht den von Geschütztürmen oder Ballisten erlittenen Schaden." msgid "Mass Shield" msgstr "Massenschild" msgid "" "Halves damage received from ranged attacks for all of your units. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Halbiert den Schaden von Fernkampfangriffen für alle Eure Einheiten. " "Beeinflusst nicht den von Geschütztürmen oder Ballisten erlittenen Schaden." msgid "Summon Earth Elemental" msgstr "Erdwesen herbeirufen" msgid "Summons Earth Elementals to fight for your army." msgstr "Beschwört Erdwesen, um für Eure Armee zu kämpfen." msgid "Summon Air Elemental" msgstr "Luftwesen herbeirufen" msgid "Summons Air Elementals to fight for your army." msgstr "Beschwört Luftwesen, um für Eure Armee zu kämpfen." msgid "Summon Fire Elemental" msgstr "Feuerwesen herbeirufen" msgid "Summons Fire Elementals to fight for your army." msgstr "Beschwört Feuerwesen, um für Eure Armee zu kämpfen." msgid "Summon Water Elemental" msgstr "Wasserwesen herbeirufen" msgid "Summons Water Elementals to fight for your army." msgstr "Beschwört Wasserwesen, um für Eure Armee zu kämpfen." msgid "Damages castle walls." msgstr "Beschädigt die Burgmauern." msgid "Earthquake" msgstr "Erdbeben" msgid "Causes all mines across the land to become visible." msgstr "Bewirkt, dass alle Minen im Land sichtbar werden." msgid "View Mines" msgstr "Minen anzeigen" msgid "Causes all resources across the land to become visible." msgstr "Bewirkt, dass alle Ressourcen im Land sichtbar werden." msgid "View Resources" msgstr "Ressourcen anzeigen" msgid "Causes all artifacts across the land to become visible." msgstr "Bewirkt, dass alle Artefakte im Land sichtbar werden." msgid "View Artifacts" msgstr "Artefakte anzeigen" msgid "Causes all towns and castles across the land to become visible." msgstr "" "Bewirkt, dass alle Städte und Schlösser im ganzen Land sichtbar werden." msgid "View Towns" msgstr "Städte anzeigen" msgid "Causes all Heroes across the land to become visible." msgstr "Bewirkt, dass alle Helden im ganzen Land sichtbar werden." msgid "View Heroes" msgstr "Held anzeigen" msgid "Causes the entire land to become visible." msgstr "Lässt das gesamte Land sichtbar werden." msgid "View All" msgstr "Alles anzeigen" msgid "Allows the caster to view detailed information on enemy Heroes." msgstr "" "Ermöglicht es dem Zaubernden, detaillierte Informationen über feindliche " "Helden einzusehen." msgid "Summon Boat" msgstr "Boot herbeirufen" msgid "" "Summons the nearest unoccupied, friendly boat to an adjacent shore location. " "A friendly boat is one which you just built or were the most recent player " "to occupy." msgstr "" "Ruft das nächstgelegene unbesetzte, eigene Boot an ein dem Helden " "angrenzendes Ufer. Ein eigenes Boot ist eines, das Ihr gerade gebaut oder " "das Ihr als letzter Spieler belegt habt." msgid "Allows the caster to magically transport to a nearby location." msgstr "" "Ermöglicht es dem Zaubernden, sich auf magische Weise an einen nahe " "gelegenen Ort zu begeben." msgid "Dimension Door" msgstr "Dimensionstor" msgid "Returns the caster to any town or castle currently owned." msgstr "" "Bringt den Zaubernden in jene Stadt oder Burg zurück, die er gerade besitzt." msgid "Town Gate" msgstr "Stadttor" msgid "" "Returns the hero to the town or castle of choice, provided it is controlled " "by you." msgstr "" "Bringt den Helden in die Stadt oder Burg Eurer Wahl zurück, sofern diese von " "Euch kontrolliert wird." msgid "Visions" msgstr "Vision" msgid "" "Visions predicts the likely outcome of an encounter with a neutral army camp." msgstr "" "Die Visionen sagen den wahrscheinlichen Ausgang einer Begegnung mit einem " "neutralen Heerlager voraus." msgid "Haunt" msgstr "Spuk" msgid "" "Haunts a mine you control with Ghosts. This mine stops producing resources. " "(If I can't keep it, nobody will!)" msgstr "" "Lässt Geister in einer von Euch kontrollierten Mine spuken. Diese Mine " "stellt die Produktion ein. (Wenn ich sie nicht behalten kann, soll es " "niemand tun!)" msgid "Set Earth Guardian" msgstr "Erdwesen-Wache" msgid "Sets Earth Elementals to guard a mine against enemy armies." msgstr "Setzt Erdwesen ein, um eine Mine vor feindlichen Armeen zu schützen." msgid "Set Air Guardian" msgstr "Luftwsn.-Wache" msgid "Sets Air Elementals to guard a mine against enemy armies." msgstr "Setzt Luftwesen ein, um eine Mine vor feindlichen Armeen zu schützen." msgid "Set Fire Guardian" msgstr "Feuerwsn.-Wache" msgid "Sets Fire Elementals to guard a mine against enemy armies." msgstr "Setzt Feuerwesen ein, um eine Mine vor feindlichen Armeen zu schützen." msgid "Set Water Guardian" msgstr "Wasserws.-Wache" msgid "Sets Water Elementals to guard a mine against enemy armies." msgstr "" "Setzt Wasserwesen ein, um eine Mine vor feindlichen Armeen zu schützen." #, fuzzy msgid "Random Spell" msgstr "Zufällig" msgid "Randomly selected spell of any level." msgstr "" #, fuzzy msgid "Random 1st Level Spell" msgstr "Zufälliges Burg" msgid "Randomly selected 1st level spell." msgstr "" msgid "Random 2nd Level Spell" msgstr "" msgid "Randomly selected 2nd level spell." msgstr "" msgid "Random 3rd Level Spell" msgstr "" msgid "Randomly selected 3rd level spell." msgstr "" msgid "Random 4th Level Spell" msgstr "" msgid "Randomly selected 4th level spell." msgstr "" msgid "Random 5th Level Spell" msgstr "" msgid "Randomly selected 5th level spell." msgstr "" msgid "Petrification" msgstr "Versteinerung" msgid "" "Turns the affected creature into stone. A petrified creature receives half " "damage from a direct attack." msgstr "" "Verwandelt die betroffene Kreatur in Stein. Eine versteinerte Kreatur " "erleidet bei einem direkten Angriff die Hälfte des Schadens." msgid "You have no Magic Book, so you cannot cast a spell." msgstr "Ihr habt kein Zauberbuch und könnt daher nicht zaubern." msgid "No spell to cast." msgstr "Kein Zauber zu wirken." msgid "Your hero has %{point} spell points remaining." msgstr "Euer Held hat noch %{point} Zauberpunkte übrig." msgid "View Adventure Spells" msgstr "Abenteuerzauber anzeigen" msgid "View Combat Spells" msgstr "Kampfzauber anzeigen" msgid "View previous page" msgstr "Vorherige Seite anzeigen" msgid "View next page" msgstr "Nächste Seite anzeigen" msgid "Close Spellbook" msgstr "Zauberbuch schließen" msgid "View %{spell}" msgstr "%{spell} Anzeigen" msgid "This spell does %{damage} points of damage." msgstr "Dieser Zauber verursacht %{damage} Schaden." msgid "" "This spell summons\n" "%{count} %{monster}." msgstr "" "Dieser Zauber beschwört\n" "%{count} %{monster}." msgid "This spell restores %{hp} HP." msgstr "Dieser Zauber stellt %{hp} TP wieder her." msgid "This spell summons %{count} %{monster} to guard the mine." msgstr "Dieser Zauber beschwört %{count} %{monster}, um die Mine zu bewachen." msgid "The nearest town is %{town}." msgstr "Die nächstgelegene Stadt ist %{town}." msgid "This town is occupied by your hero %{hero}." msgstr "Diese Stadt ist von Eurem Helden %{hero} besetzt." msgid "" "This spell controls up to\n" "%{hp} HP." msgstr "" "Dieser Zauberspruch kontrolliert bis zu\n" "%{hp} TP." msgid "The ultimate artifact is really the %{name}." msgstr "Das ultimative Artefakt ist eigentlich der %{name}." msgid "The ultimate artifact may be found in the %{name} regions of the world." msgstr "Das ultimative Artefakt kann im %{name} von der Welt gefunden werden." msgid "north-west" msgstr "Nordwesten" msgid "north" msgstr "Nord" msgid "north-east" msgstr "Nordosten" msgid "west" msgstr "Westen" msgid "center" msgstr "Zentrum" msgid "east" msgstr "Osten" msgid "south-west" msgstr "Südwesten" msgid "south" msgstr "Süden" msgid "south-east" msgstr "Südosten" msgid "The truth is out there." msgstr "Die Wahrheit liegt auf der Hand." msgid "The dark side is stronger." msgstr "Die dunkle Seite ist stärker." msgid "The end of the world is near." msgstr "Das Ende der Welt ist nah." msgid "The bones of Lord Slayer are buried in the foundation of the arena." msgstr "Die Gebeine von Lord Slayer sind im Fundament der Arena begraben." msgid "A Black Dragon will take out a Titan any day of the week." msgstr "Ein Schwarzer Drache kann es jederzeit mit einem Titanen aufnehmen." #, fuzzy msgid "He told her: Yada yada yada... and then she said: Blah, blah, blah..." msgstr "Er sagte ihr: Yada yada yada... und dann sagte sie: Bla, bla, bla..." msgid "An unknown force is being resurrected..." msgstr "Eine unbekannte Macht wird wiederbelebt..." msgid "" "Check the newest version of the game at\n" "https://github.com/ihhub/\n" "fheroes2/releases" msgstr "" "Die neueste Version des Spiels finden Sie unter\n" "https://github.com/ihhub/\n" "fheroes2/releases" #, fuzzy #~ msgid "Terrain" #~ msgstr "Gelände-Modus" #, fuzzy #~ msgid "Castles" #~ msgstr "Burg" #, fuzzy #~ msgid "Ocean object" #~ msgstr "Ozean-Objekte" #~ msgid "Ocean Objects" #~ msgstr "Ozean-Objekte" #~ msgid "Grass Objects" #~ msgstr "Gras-Objekte" #~ msgid "Snow Objects" #~ msgstr "Schnee-Objekte" #~ msgid "Swamp Objects" #~ msgstr "Sumpf-Objekte" #~ msgid "Lava Objects" #~ msgstr "Lava-Objekte" #~ msgid "Desert Objects" #~ msgstr "Wüsten-Objekte" #~ msgid "Dirt Objects" #~ msgstr "Erde-Objekte" #~ msgid "Beach Objects" #~ msgstr "Strand-Objekte" #~ msgid "Used to place objects most appropriate for use on %{terrain}." #~ msgstr "" #~ "Wird verwendet, um Objekte zu platzieren, die am besten für die " #~ "Verwendung auf %{terrain} geeignet sind." #~ msgid "" #~ "Used to place\n" #~ "a town or castle." #~ msgstr "" #~ "Verwendet, um eine\n" #~ "Stadt oder Burg zu platzieren." #~ msgid "Used to place an artifact." #~ msgstr "Wird verwendet, um ein Artefakt zu platzieren." #~ msgid "" #~ "Used to place\n" #~ "a resource or treasure." #~ msgstr "" #~ "Wird verwendet, um eine\n" #~ "Ressource oder einen Schatz zu platzieren." #~ msgid "%{race1} %{name1}" #~ msgstr "%{name1} (%{race1})" #~ msgid "vs" #~ msgstr "gegen" #~ msgid "%{race2} %{name2}" #~ msgstr "%{name2} (%{race2})" fheroes2-1.0.12+dfsg/files/lang/dk.po000066400000000000000000011226611456075706000172330ustar00rootroot00000000000000# Danish translation for fheroes2 # Copyright (C) 2021 - 2023 fheroes2 team # This file is distributed under the same license as the fheroes2 package. # msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-03 03:27+0000\n" "PO-Revision-Date: 2023-09-12 11:08+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: Danish (Denmark)\n" "Language: da\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Loco-Source-Locale: da_DK\n" "X-Launchpad-Export-Date: 2010-05-27 09:12+0000\n" "X-Generator: Poedit 3.4.1\n" "X-Loco-Parser: loco_parse_po\n" msgid "" "BATTLE\n" "ONLY" msgstr "" "KUN\n" "KAMP" msgid "" "NEW\n" "GAME" msgstr "" "NYT\n" "SPIL" msgid "" "SAVE\n" "GAME" msgstr "" "GEM\n" "SPIL" msgid "" "LOAD\n" "GAME" msgstr "" "INDLÆS\n" "SPIL" msgid "INFO" msgstr "INFO" msgid "QUIT" msgstr "AFSLUT" msgid "CANCEL" msgstr "AFBRYD" msgid "OKAY" msgstr "OK" msgid "smallerButton|OKAY" msgstr "OK" msgid "ACCEPT" msgstr "GODTAG" msgid "DECLINE" msgstr "AFSLÃ…" msgid "LEARN" msgstr "LÆR" msgid "TRADE" msgstr "BYT" msgid "YES" msgstr "JA" msgid "NO" msgstr "NEJ" msgid "EXIT" msgstr "UD" msgid "smallerButton|EXIT" msgstr "UD" msgid "DISMISS" msgstr "BORTVIS" msgid "UPGRADE" msgstr "OPG." msgid "RESTART" msgstr "OMKAMP" msgid "HEROES" msgstr "HELTE" msgid "" "TOWNS/\n" "CASTLES" msgstr "" "BYER\n" "BORGE" msgid "S" msgstr "S" msgid "M" msgstr "M" msgid "L" msgstr "L" msgid "X-L" msgstr "XL" msgid "ALL" msgstr "ALT" msgid "SELECT" msgstr "VÆLG" msgid "" "STANDARD\n" "GAME" msgstr "" "ENKELT\n" "SPILLER" msgid "" "CAMPAIGN\n" "GAME" msgstr "FELTTOG" msgid "" "MULTI-\n" "PLAYER\n" "GAME" msgstr "" "FLER-\n" "SPILLER" msgid "CONFIG" msgstr "INDSTIL" msgid "" "ORIGINAL\n" "CAMPAIGN" msgstr "" "ORIGINALT\n" "FELTTOG" msgid "" "EXPANSION\n" "CAMPAIGN" msgstr "" "UDVIDELSE\n" "FELTTOG" msgid "HOT SEAT" msgstr "LOKALT" msgid "2 PLAYERS" msgstr "2 SPILLERE" msgid "3 PLAYERS" msgstr "3 SPILLERE" msgid "4 PLAYERS" msgstr "4 SPILLERE" msgid "5 PLAYERS" msgstr "5 SPILLERE" msgid "6 PLAYERS" msgstr "6 SPILLERE" msgid "GIFT" msgstr "FORÆR" msgid "MAX" msgstr "MAKS" msgid "DIFFICULTY" msgstr "NIVEAU" msgid "VIEW INTRO" msgstr "VIS INTRO" msgid "MIN" msgstr "MIN" msgid "RESET" msgstr "NULSTIL" #, fuzzy msgid "START" msgstr "OMKAMP" #, fuzzy msgid "CASTLE" msgstr "" "BYER\n" "BORGE" msgid "TOWN" msgstr "" msgid "" "D\n" "I\n" "S\n" "M\n" "I\n" "S\n" "S" msgstr "" "B\n" "O\n" "R\n" "T\n" "V\n" "I\n" "S" msgid "" "E\n" "X\n" "I\n" "T" msgstr "" "U\n" "D" msgid "" "C\n" "A\n" "M\n" "P\n" "A\n" "I\n" "G\n" "N" msgstr "" "F\n" "E\n" "L\n" "T\n" "T\n" "O\n" "G" msgid "" "S\n" "T\n" "A\n" "N\n" "D\n" "A\n" "R\n" "D" msgstr "" "E\n" "N\n" "K\n" "E\n" "L\n" "T" msgid "Warrior" msgstr "Kriger" msgid "Builder" msgstr "Bygger" msgid "Explorer" msgstr "Udforsker" msgid "None" msgstr "Ingen" msgid "" "A few\n" "%{monster}" msgstr "" "Nogle fÃ¥\n" "%{monster}" msgid "" "Several\n" "%{monster}" msgstr "" "Flere\n" "%{monster}" msgid "" "A pack of\n" "%{monster}" msgstr "" "En flok\n" "%{monster}" msgid "" "Lots of\n" "%{monster}" msgstr "" "Masser af\n" "%{monster}" msgid "" "A horde of\n" "%{monster}" msgstr "" "En horde af\n" "%{monster}" msgid "" "A throng of\n" "%{monster}" msgstr "" "En skare af\n" "%{monster}" msgid "" "A swarm of\n" "%{monster}" msgstr "" "En sværm af\n" "%{monster}" msgid "" "Zounds...\n" "%{monster}" msgstr "" "Pokkers mange\n" "%{monster}" msgid "" "A legion of\n" "%{monster}" msgstr "" "En legion af\n" "%{monster}" msgid "army|Few" msgstr "FÃ¥" msgid "army|Several" msgstr "Flere" msgid "army|Pack" msgstr "Flok" msgid "army|Lots" msgstr "Mange" msgid "army|Horde" msgstr "Horde" msgid "army|Throng" msgstr "Skare" msgid "army|Swarm" msgstr "Sværm" msgid "army|Zounds" msgstr "Pokkers" msgid "army|Legion" msgstr "Legion" msgid "All %{race} troops +1" msgstr "Alle %{race} tropper +1" msgid "Multiple" msgstr "Mange" msgid "Troops of %{count} alignments -%{penalty}" msgstr "Tropper af %{count} tilknytninger -%{penalty}" #, fuzzy msgid "Some undead in army -1" msgstr "Der er udøde i gruppen -1" msgid "View %{name}" msgstr "Vis %{name}" msgid "Move the %{name} " msgstr "Flyt %{name} " msgid "Move or right click to redistribute %{name}" msgstr "Flyt eller højreklik for at omfordele %{name}" msgid "Combine %{name} armies" msgstr "Kombiner %{name} hærene" msgid "Exchange %{name2} with %{name}" msgstr "Byt %{name2} med %{name}" msgid "Select %{name}" msgstr "Vælg %{name}" msgid "Cannot move last troop" msgstr "Sidste trop kan ikke flyttes" msgid "Move the %{name}" msgstr "Flyt %{name}" msgid "Set Count" msgstr "Bestem antal" msgid "%{name} destroys half the enemy troops!" msgid_plural "%{name} destroy half the enemy troops!" msgstr[0] "%{name} udsletter halvdelen af fjendens tropper!" msgstr[1] "%{name} udsletter halvdelen af fjendens tropper!" msgid "%{name} has turned off the auto battle" msgstr "%{name} har deaktiveret automatisk kamp" msgid "%{name} has turned on the auto battle" msgstr "%{name} har aktiveret automatisk kamp" msgid "Spell failed!" msgstr "Magien mislykkedes!" msgid "" "The Sphere of Negation artifact is in effect for this battle, disabling all " "combat spells." msgstr "" "Artefaktet Negationskuglen er i kraft, hvilket deaktiverer brug af enhver " "kampmagi." msgid "You have already cast a spell this round." msgstr "Du har allerede kastet magi én gang i denne runde." msgid "That spell will have no effect!" msgstr "Dén besværgelse vil ikke pÃ¥virke nogen!" msgid "You may only summon one type of elemental per combat." msgstr "Du mÃ¥ kun hidkalde én slags elemental per kamp." msgid "" "There is no open space adjacent to your hero where you can summon an " "Elemental to." msgstr "" "Der er ingen Ã¥ben plads ved siden af din helt til at hidkalde elementaler pÃ¥." msgid "" "The Moat reduces by -%{count} the defense skill of any unit and slows to " "half movement rate." msgstr "" "Voldgraven halverer bevægelseshastigheden samt reducerer forsvarsevnen for " "enhver enhed, der mÃ¥tte stÃ¥ i den, med -%{count}." msgid "Speed: %{speed}" msgstr "Hastighed: %{speed}" msgid "Speed" msgstr "Hastighed" msgid "Off" msgstr "Fra" msgid "On" msgstr "Til" msgid "Turn Order" msgstr "Turorden" msgid "Auto Spell Casting" msgstr "Autobrug af magi" msgid "Grid" msgstr "Gitter" msgid "Shadow Movement" msgstr "" "Skygge-\n" "bevægelse" msgid "Shadow Cursor" msgstr "Skyggemarkør" msgid "Audio" msgstr "Lyd" msgid "Settings" msgstr "Indstillinger" msgid "Configure" msgstr "Indstil" msgid "Hot Keys" msgstr "Genvejstaster" msgid "Damage Info" msgstr "Skadeinfo" msgid "Set the speed of combat actions and animations." msgstr "Indstil hastighed for kamphandlinger og animationer." msgid "Toggle to display the turn order during the battle." msgstr "Vælg om kampens turorden skal vises eller ej." msgid "" "Toggle whether or not the computer will cast spells for you when auto combat " "is on. (Note: This does not affect spell casting for computer players in any " "way, nor does it affect quick combat.)" msgstr "" "Vælg om computerens evne til selv at kaste magi for dig skal være aktiv " "eller ej, nÃ¥r Autokamp er slÃ¥et til. (Note: Dette pÃ¥virker hverken AI-" "spillere eller kvikkamp pÃ¥ nogen mÃ¥de.)" msgid "" "Toggle the hex grid on or off. The hex grid always underlies movement, even " "if turned off. This switch only determines if the grid is visible." msgstr "" "Vælg om hex-gitteret skal være synligt eller ej. Hex-gitteret ligger til " "grund for bevægelse, selv hvis det ikke er synligt." msgid "" "Toggle on or off shadows showing where your creatures can move and attack." msgstr "" "Vælg om der skal være skygger, som bÃ¥de viser hvor dine væsener kan angribe " "og gÃ¥ hen, eller ej." msgid "" "Toggle on or off a shadow showing the current hex location of the mouse " "cursor." msgstr "" "Vælg om der skal være skygger, som viser musemarkørens nuværende hex-" "lokation, eller ej." msgid "Change the audio settings of the game." msgstr "Ændr spillets lydindstillinger." msgid "Check and configure all the hot keys present in the game." msgstr "Tjek og indstil alle spillets nuværende genvejstaster." msgid "Toggle to display damage information during the battle." msgstr "Vælg om kampens skadeinformationer skal vises eller ej." msgid "Exit this menu." msgstr "Forlad denne menu." msgid "Okay" msgstr "Okay" msgid "The enemy has surrendered!" msgstr "Fjenden har overgivet sig!" msgid "Their cowardice costs them %{gold} gold." msgstr "" msgid "The enemy has fled!" msgstr "Fjenden er flygtet!" msgid "A glorious victory!" msgstr "En glorværdig sejr!" msgid "For valor in combat, %{name} receives %{exp} experience." msgstr "For at udvise tapperhed i kamp, fÃ¥r %{name} %{exp} erfaringspoint." msgid "The cowardly %{name} flees from battle." msgstr "Den kujonagtige %{name} flygter fra kampen." msgid "%{name} surrenders to the enemy, and departs in shame." msgstr "%{name} overgiver sig til fjenden, og træder fra med skam." #, fuzzy msgid "Your forces suffer a bitter defeat, and %{name} abandons your cause." msgstr "Din styrke lider et bittert nederlag, og %{name} opgiver din sag." #, fuzzy msgid "Your forces suffer a bitter defeat." msgstr "Din styrke lider et bittert nederlag." msgid "Battlefield Casualties" msgstr "Slagmarkens ofre" msgid "Attacker" msgstr "Angriber" msgid "Defender" msgstr "Forsvarer" #, fuzzy msgid "Click to leave the battle results." msgstr "Klik for at godkende valgte opløsning." #, fuzzy msgid "Click to restart the battle in manual mode." msgstr "Klik her for at vende tilbage til hovedmenuen." msgid "Restart" msgstr "Genstart" msgid "You have captured an enemy artifact!" msgstr "Du har stjÃ¥let et af fjendens artefakter!" msgid "As you reach for the %{name}, it mysteriously disappears." msgstr "Lige som du rækker ud efter %{name}, forsvinder det mysteriøst." msgid "As your enemy reaches for the %{name}, it mysteriously disappears." msgstr "" "Lige som din fjende rækker ud efter %{name}, forsvinder det mysteriøst." msgid "Necromancy!" msgstr "Nekromanti!" msgid "" "Practicing the dark arts of necromancy, you are able to raise %{count} of " "the enemy's dead to return under your service as %{monster}." msgstr "" "Ved brug af nekromantiens mørke kunst, er du i stand til at lade %{count} af " "fjendens døde genopstÃ¥ under din tjeneste som %{monster}." msgid "%{name} the %{race}" msgstr "%{race}en %{name}" msgid "Captain of %{name}" msgstr "Kaptajn af %{name}" msgid "Attack" msgstr "Angreb" msgid "Defense" msgstr "Forsvar" msgid "Spell Power" msgstr "Magikraft" msgid "Knowledge" msgstr "Kundskab" msgid "Morale" msgstr "Moral" msgid "Luck" msgstr "Held" msgid "Spell Points" msgstr "Mana" msgid "Hero's Options" msgstr "Helts mulige træk" msgid "Cast Spell" msgstr "Kast magi" msgid "Retreat" msgstr "Flygt" msgid "Surrender" msgstr "Overgivelse" msgid "Cancel" msgstr "Afbryd" msgid "Hero Screen" msgstr "Helteskærm" msgid "Captain's Options" msgstr "Kaptajns mulige træk" msgid "" "Cast a magical spell. You may only cast one spell per combat round. The " "round is reset when every creature has had a turn." msgstr "" "Kast en magisk besværgelse. Du mÃ¥ kun kaste én magi per kamprunde. Runden " "nulstilles nÃ¥r alle væsener har taget et træk." msgid "" "Retreat your hero, abandoning your creatures. Your hero will be available " "for you to recruit again, however, the hero will have only a novice hero's " "forces." msgstr "" "Lad din helt flygte og dine væsener lades i stikken. Det kan blive muligt at " "rekruttere din helt igen, men helten vil, igen, kun have en begynderstyrke." #, fuzzy msgid "" "Surrendering costs gold. However if you pay the ransom, the hero and all of " "his or her surviving creatures will be available to recruit again. The cost " "of surrender is half of the total cost of the non-temporary troops remaining " "in the army." msgstr "" "Overgivelse koster guld. Hvis du betaler løsesummen, opnÃ¥r du muligheden for " "igen at rekruttere din helt samt tilhørende overlevende væsener." msgid "Open Hero Screen to view full information about the hero." msgstr "Ã…bn helteskærm for at se fuld information om helten." msgid "Return to the battle." msgstr "Vend tilbage til kampen." msgid "Not enough gold (%{gold})" msgstr "Ikke nok guld (%{gold})" msgid "%{name} states:" msgstr "%{name} erklærer:" msgid "Captain of %{name} states:" msgstr "Kaptajnen af %{name} erklærer:" msgid "" "\"I will accept your surrender and grant you and your troops safe passage " "for the price of %{price} gold.\"" msgstr "" "\"Jeg modtager gerne din overgivelse og giver Dem og Deres tropper fri lejde " "til en pris pÃ¥ %{price} guldstykker.\"" msgid "View %{monster} info" msgstr "Se info om %{monster}" msgid "Fly %{monster} here" msgstr "Flyv %{monster} hertil" msgid "Move %{monster} here" msgstr "Flyt %{monster} hertil" msgid "Shoot %{monster}" msgstr "Skyd %{monster}" msgid "(1 shot left)" msgid_plural "(%{count} shots left)" msgstr[0] "(1 skud tilbage)" msgstr[1] "(%{count} skud tilbage)" msgid "Attack %{monster}" msgstr "Angrib %{monster}" msgid "Turn %{turn}" msgstr "Runde %{turn}" msgid "Teleport here" msgstr "Teleporter hertil" msgid "Invalid teleport destination" msgstr "Ugyldig teleportationsmÃ¥l" msgid "Cast %{spell} on %{monster}" msgstr "Kast %{spell} pÃ¥ %{monster}" msgid "Cast %{spell}" msgstr "Kast %{spell}" msgid "Select spell target" msgstr "Vælg magiens mÃ¥l" msgid "View Ballista info" msgstr "Se Ballistens info" msgid "Ballista" msgstr "Ballist" msgid "Enable auto combat" msgstr "Aktiver Autokamp" msgid "Allows the computer to fight out the battle for you." msgstr "Tillader computeren at udkæmpe kampen for dig." msgid "Auto Combat" msgstr "Autokamp" msgid "Customize system options" msgstr "Tilpas systemindstillinger" msgid "Allows you to customize the combat screen." msgstr "Tillader dig tilpasning af Kampskærmen." msgid "System Options" msgstr "Systemindstillinger" msgid "Skip this unit" msgstr "Skip denne enhed" msgid "Skip" msgstr "Skip" msgid "" "Skips the current creature. The current creature ends its turn and does not " "get to go again until the next round." msgstr "" "Skipper nuværende enheds tur. Denne enhed slutter sin tur og kan intet gøre " "før sin tur i næste runde." msgid "View Captain's options" msgstr "Vis kaptajns mulige træk" msgid "View Hero's options" msgstr "Vis helts mulige træk" msgid "View opposing Captain" msgstr "Vis modstanders kaptajn" msgid "View opposing Hero" msgstr "Vis modstanders helt" msgid "Hide logs" msgstr "Skjul log" msgid "Show logs" msgstr "Vis log" msgid "Message Bar" msgstr "Beskedlinje" msgid "Shows the results of individual monster's actions." msgstr "Viser resultater for individuelle monstres handlinger." msgid "Are you sure you want to finish the battle in auto mode?" msgstr "Er du sikker pÃ¥, at du vil færdiggøre denne kamp automatisk?" msgid "Are you sure you want to enable auto combat?" msgstr "Er du sikker pÃ¥, at du vil aktivere autokamp?" msgid "%{name} skip their turn." msgstr "%{name} skipper deres tur." msgid "%{attacker} does %{damage} damage." msgid_plural "%{attacker} do %{damage} damage." msgstr[0] "%{attacker} giver %{damage} i skade." msgstr[1] "%{attacker} giver %{damage} i skade." msgid "1 creature perishes." msgid_plural "%{count} creatures perish." msgstr[0] "1 væsen omkommer." msgstr[1] "%{count} væsener omkommer." msgid "1 %{defender} perishes." msgid_plural "%{count} %{defender} perish." msgstr[0] "1 %{defender} omkommer." msgstr[1] "%{count} %{defender} omkommer." msgid "1 soul is incorporated." msgid_plural "%{count} souls are incorporated." msgstr[0] "1 sjæl blev indsamlet" msgstr[1] "%{count} sjæle blev indsamlet." msgid "1 %{unit} is revived." msgid_plural "%{count} %{unit} are revived." msgstr[0] "1 %{unit} blev genoplivet." msgstr[1] "%{count} %{unit} blev genoplivet." msgid "Moved %{monster}: from [%{src}] to [%{dst}]." msgstr "Flyttede %{monster}: fra [%{src}] til [%{dst}]." msgid "The %{name} resist the spell!" msgstr "Troppen med %{name} modstod magien!" msgid "%{name} casts %{spell} on the %{troop}." msgstr "%{name} kaster %{spell} pÃ¥ %{troop}." msgid "%{name} casts %{spell}." msgstr "%{name} kaster %{spell}." msgid "The %{spell} does %{damage} damage to one undead creature." msgstr "%{spell} giver %{damage} i skade til én udød enhed." msgid "The %{spell} does %{damage} damage to all undead creatures." msgstr "%{spell} giver %{damage} i skade til alle udøde enheder." msgid "The %{spell} does %{damage} damage, %{count} creatures perish." msgstr "%{spell} giver %{damage} i skade, %{count} væsener omkommer." msgid "The %{spell} does %{damage} damage." msgstr "%{spell} giver %{damage} i skade." msgid "The %{spell} does %{damage} damage to one living creature." msgstr "%{spell} giver %{damage} i skade pÃ¥ én levende enhed." msgid "The %{spell} does %{damage} damage to all living creatures." msgstr "%{spell} giver %{damage} i skade til alle levende enheder." msgid "The %{attacker}' attack blinds the %{target}!" msgstr "Angrebet af %{attacker} blænder %{target}!" msgid "The %{attacker}' gaze turns the %{target} to stone!" msgstr "Det skarpe blik fra %{attacker} forvandler %{target} til sten!" msgid "The %{attacker}' curse falls upon the %{target}!" msgstr "En forbandelse falder pÃ¥ %{target} forÃ¥rsaget af %{attacker}!" msgid "The %{target} are paralyzed by the %{attacker}!" msgstr "Troppen med %{target} paralyseres af %{attacker}!" msgid "The %{attacker} dispel all good spells on your %{target}!" msgstr "Troppen med %{attacker} fordriver alt det gode magi fra din %{target}!" msgid "The %{attacker} cast %{spell} on %{target}!" msgstr "Troppen med %{attacker} kaster %{spell} pÃ¥ %{target}!" msgid "Bad luck descends on the %{attacker}." msgstr "Sort uheld sænker sig over troppen med %{attacker}." msgid "Good luck shines on the %{attacker}." msgstr "Held og lykke skinner pÃ¥ troppen med %{attacker}." msgid "High morale enables the %{monster} to attack again." msgstr "Høj moral gør det muligt for troppen med %{monster} at angribe igen." msgid "Low morale causes the %{monster} to freeze in panic." msgstr "Lav moral vækker frysende panik i troppen med %{monster}." msgid "%{tower} does %{damage} damage." msgstr "Borgens %{tower} giver %{damage} i skade." msgid "The mirror image is created." msgstr "Spejlbilledet er skabt." msgid "The mirror image is destroyed!" msgstr "Spejlbilledet er ødelagt!" msgid "Are you sure you want to interrupt the auto battle?" msgstr "Er du sikker pÃ¥, at du vil afbryde autokampen?" msgid "Error" msgstr "Fejl" msgid "No spells to cast." msgstr "Ingen magi at kaste." msgid "Are you sure you want to retreat?" msgstr "Er du sikker pÃ¥, at du vil flygte?" msgid "Retreat disabled" msgstr "Flugt slÃ¥et fra" msgid "Surrender disabled" msgstr "Overgivelse slÃ¥et fra" msgid "Damage: %{max}" msgstr "Skade: %{max}" msgid "Damage: %{min} - %{max}" msgstr "Skade: %{min} - %{max}" msgid "Perish: %{max}" msgstr "Dræber: %{max}" msgid "Perish: %{min} - %{max}" msgstr "Dræber: %{min} - %{max}" msgid "" "Through eagle-eyed observation, %{name} is able to learn the magic spell " "%{spell}." msgstr "" "Med sit ørneblik observerer %{name} magien %{spell} og lærer den selv at " "kende." msgid "Human" msgstr "Menneske" msgid "AI" msgstr "AI" #, fuzzy msgid "Start" msgstr "Genstart" #, fuzzy msgid "Start the battle." msgstr "Vend tilbage til kampen." msgid "Exit" msgstr "GÃ¥" msgid "Reset" msgstr "" msgid "Reset to default settings." msgstr "" msgid "Please select another hero." msgstr "Vælg venligst en anden helt." msgid "Set Attack Skill" msgstr "Indstil angrebsevne" msgid "Set Defense Skill" msgstr "Indstil forsvarsevne" msgid "Set Power Skill" msgstr "Indstil magisk evne" msgid "Set Knowledge Skill" msgstr "Indstil kundskabsevne" msgid "%{race1} %{name1} vs %{race2} %{name2}" msgstr "%{race1} %{name1} vs %{race2} %{name2}" msgid "Monsters" msgstr "Monstre" msgid "N/A" msgstr "I/T" msgid "Left Turret" msgstr "Venstre tÃ¥rn" msgid "Right Turret" msgstr "Højre tÃ¥rn" msgid "The %{name} fires with the strength of %{count} Archers" msgstr "Borgens %{name} skyder med en styrke af %{count} bueskyttere" msgid "each with a +%{attack} bonus to their attack skill." msgstr "hver med en +%{attack} bonus til deres angrebsevne." msgid "The %{name} is destroyed." msgstr "Borgens %{name} er ødelagt." msgid "%{count} %{name} rises from the dead!" msgid_plural "%{count} %{name} rise from the dead!" msgstr[0] "%{count} %{name} rejser sig fra de døde!" msgstr[1] "%{count} %{name} rejser sig fra de døde!" msgid "Dwarven Alliance" msgstr "Dværgalliance" msgid "Sorceress Guild" msgstr "Hekselaug" msgid "Necromancer Guild" msgstr "Nekromantikerlaug" msgid "Ogre Alliance" msgstr "Jættealliance" msgid "Dwarfbane" msgstr "Dværgsmorder" msgid "Dragon Alliance" msgstr "Dragealliance" msgid "Elven Alliance" msgstr "Elveralliance" msgid "Kraeger defeated" msgstr "Kraeger besejret" msgid "Wayward Son" msgstr "EgenrÃ¥dig Søn" msgid "Uncle Ivan" msgstr "Onkel Ivan" msgid "Annexation" msgstr "Anneksering" msgid "Force of Arms" msgstr "VÃ¥benstyrke" msgid "Save the Dwarves" msgstr "Red dværgene" msgid "Carator Mines" msgstr "Karatorminer" msgid "Turning Point" msgstr "Vendepunkt" msgid "scenarioName|Defender" msgstr "Forsvarer" msgid "Corlagon's Defense" msgstr "Korlagons Forsvar" msgid "The Crown" msgstr "Kronen" msgid "The Gauntlet" msgstr "Kapløbet" msgid "Betrayal" msgstr "Forræderi" msgid "Final Justice" msgstr "Endelige Retfærdighed" msgid "" "Roland needs you to defeat the lords near his castle to begin his war of " "rebellion against his brother. They are not allied with each other, so they " "will spend most of their time fighting with one another. Victory is yours " "when you have defeated all of their castles and heroes." msgstr "" "Roland har brug for dig til at besejre herrerne nær hans borg, for at " "begynde sin oprørskrig mod sin bror. De er ikke allierede med hinanden. SÃ¥ " "de vil bruge det meste af deres tid pÃ¥, at bekæmpe hinanden. \n" "\n" "Sejren er din, nÃ¥r du har indtaget alle deres borge og besejret alle deres " "helte." msgid "" "The local lords refuse to swear allegiance to Roland, and must be subdued. " "They are wealthy and powerful, so be prepared for a tough fight. Capture all " "enemy castles to win." msgstr "" "De lokale herrer nægter at sværge troskab til Roland, og mÃ¥ undertrykkes. De " "er velhavende og magtfulde, sÃ¥ vær forberedt pÃ¥ en hÃ¥rd kamp. Indtag alle " "fjendens borge for at vinde." msgid "" "Your task is to defend the Dwarves against Archibald's forces. Capture all " "of the enemy towns and castles to win, and be sure not to lose all of the " "dwarf towns at once, or the enemy will have won." msgstr "" "Din opgave er at forsvare dværgene mod Archibalds styrker. Overtag alle " "fjendens borge og byer for at vinde, men undgÃ¥ at miste alle dine dværg-" "byer, for sÃ¥ vinder fjenden." msgid "" "You will face four allied enemies in a straightforward fight for resource " "and treasure. Capture all of the enemy castles for victory." msgstr "" "Du vil møde fire allierede fjender i en ligetil kamp om ressourcer og " "skatte. For at sejre, skal du erobre alle fjendens borge." msgid "" "Your enemies are allied against you and start close by, so be ready to come " "out fighting. You will need to own all four castles in this small valley to " "win." msgstr "" "Dine fjender er allierede mod dig og starter tæt pÃ¥, sÃ¥ vær klar til at " "komme ud og kæmpe. For at vinde skal du eje alle fire borge i denne lille " "dal." msgid "" "The Sorceress' guild of Noraston has requested Roland's aid against an " "attack from Archibald's allies. Capture all of the enemy castles to win, and " "don't lose Noraston, or you'll lose the scenario. (Hint: There is an enemy " "castle on an island in the ocean.)" msgstr "" "Norastons hekselaug har anmodet om Rolands hjælp mod et angreb fra " "Archibalds allierede. For at vinde skal du indtage alle fjendens borge. \n" "\n" "Du taber dette scenarie, hvis du mister Noraston (Tip: Der er en fjendtlig " "borg pÃ¥ en ø i havet.)" msgid "" "Gather as large an army as possible and capture the enemy castle within 8 " "weeks. You are opposed by only one enemy, but must travel a long way to get " "to the enemy castle. Any troops you have in your army at the end of this " "scenario will be with you in the final battle." msgstr "" "Saml sÃ¥ stor en hær som muligt og indtag fjendens borg. Du er kun oppe imod " "én fjende. Men du skal rejse langt, for at komme til fjendens borg. Alle " "tropper, du har i din hær i slutningen af dette scenarie, vil være med dig i " "det sidste scenarie. Du taber dette scenarie, hvis du ikke indtager borgen " "inden for 8 uger." msgid "" "Find the Crown before Archibald's heroes find it. Roland will need the Crown " "for the final battle against Archibald." msgstr "" "Find kronen før Archibalds helte finder den. Roland skal bruge kronen til " "det sidste slag mod Archibald." msgid "" "Three allied enemies stand before you and victory, including Lord Corlagon. " "Roland is in a castle to the northwest, and you will lose if he falls to the " "enemy. Remember that capturing Lord Corlagon will ensure that he will not " "fight against you in the final scenario." msgstr "" "Tre allierede fjender stÃ¥r foran dig og sejren, inklusive Fyrst Korlagon. " "Roland er i en borg mod nordvest, og du vil tabe, hvis han falder for " "fjenden. Husk, at fange Fyrst Korlagon vil sikre, at han ikke vil kæmpe mod " "dig i det endelige scenarie." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Archibald to end the war!" msgstr "" "Dette er det sidste slag. BÃ¥de du og din fjende er bevæbnet til tænderne, og " "alle er allierede imod dig. For at afslutte krigen skal du fange Archibald!" msgid "" "Switching sides leaves you with three castles against the enemy's one. This " "battle will be the easiest one you will face for the rest of the war..." "traitor." msgstr "" "Ved at skifte side, kommer du til at starte med tre borge mod fjendens ene. " "Dette slag vil blive det letteste du vil møde resten af krigen... Din " "forræder." msgid "Barbarian Wars" msgstr "Barbarkrigene" msgid "First Blood" msgstr "Første Blodsudgydelse" msgid "Necromancers" msgstr "Nekromantikere" msgid "Slay the Dwarves" msgstr "Dræb dværgene" msgid "Country Lords" msgstr "Landsherrer" msgid "Dragon Master" msgstr "Dragemester" msgid "Rebellion" msgstr "Oprør" msgid "Apocalypse" msgstr "Dommedag" msgid "Greater Glory" msgstr "Større Hæder" msgid "" "King Archibald requires you to defeat the three enemies in this region. They " "are not allied with one another, so they will spend most of their energy " "fighting amongst themselves. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Kong Archibald kræver, at du besejrer de tre fjender i denne region. De er " "ikke allierede med hinanden. SÃ¥ de vil bruge det meste af deres energi pÃ¥, " "at bekæmpe hinanden. Du vinder, nÃ¥r du ejer alle fjenders borge og der ikke " "er flere helte tilbage at bekæmpe." msgid "" "You must unify the barbarian tribes of the north by conquering them. As in " "the previous mission, the enemy is not allied against you, but they have " "more resources at their disposal. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Du skal forene nordens barbarstammer, ved at erobre dem. Som i det forrige " "scenarie er fjenderne ikke allierede imod dig, men de har flere ressourcer " "til deres rÃ¥dighed. Du vinder, nÃ¥r du ejer alle fjendens borge, og der ikke " "er flere helte tilbage at kæmpe." msgid "" "Do-gooder wizards have taken the Necromancers' castle. You must retake it to " "achieve victory. Remember that while you start with a powerful army, you " "have no castle and must take one within 7 days, or lose this battle. (Hint: " "The nearest castle is to the southeast.)" msgstr "" "Gode magikere har taget nekromantikerens borg. Du skal genindtage den, for " "at opnÃ¥ sejr. Husk, at selvom du starter med en stærk hær, har du ingen " "borg.\n" "Du taber dette scenarie, hvis ikke du indtager en by inden 7 dage er gÃ¥et. " "(Tip: Mod sydøst ligger den nærmeste borg.)" msgid "" "The dwarves need conquering before they can interfere in King Archibald's " "plans. Roland's forces have more than one hero and many towns to start with, " "so be ready for attack from multiple directions. You must capture all of the " "enemy towns and castles to claim victory." msgstr "" "Dværgene skal erobres, før de kan blande sig i kong Archibalds planer. " "Rolands styrker har mere end én helt og mange byer til at starte med, sÃ¥ vær " "klar til angreb fra flere retninger. Du skal erobre alle fjendens borge og " "byer for at hævde sejren." msgid "" "You must put down a peasant revolt led by Roland's forces. All are allied " "against you, but you have Lord Corlagon, an experienced hero, to help you. " "Capture all enemy castles to win." msgstr "" "Du skal nedkæmpe et bondeoprør ledet af Rolands styrker. Alle er allierede " "imod dig, men du har Fyrst Korlagon, en erfaren helt, til at hjælpe dig. " "Indtag alle fjendens borge, for at vinde." msgid "" "There are two enemies allied against you in this mission. Both are well " "armed and seek to evict you from their island. Avoid them and capture Dragon " "City to win." msgstr "" "I dette scenarie er to fjender allierede imod dig. Begge er godt bevæbnede, " "og de søger at fordrive dig fra deres ø. UndgÃ¥ dem og indtag Dragebyen, for " "at vinde." msgid "" "Your orders are to conquer the country lords that have sworn to serve " "Roland. All of the enemy castles are unified against you. Since you start " "without a castle, you must hurry to capture one before the end of the week. " "Capture all enemy castles for victory." msgstr "" "Du er blevet beordret til at besejre landsherrerne, der har svoret at tjene " "Roland. Alle fjendens borge er forenet mod dig. Da du starter uden en borg, " "skal du skynde dig at indtage en inden ugen er omme. Indtag alle fjendens " "borge, for at sejre." msgid "" "Find the Crown before Roland's heroes find it. Archibald will need the Crown " "for the final battle against Roland." msgstr "" "Find Kronen før Rolands helte finder den. Archibald skal bruge Kronen til " "det sidste slag mod Roland." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Roland to win the war, and be sure " "not to lose Archibald in the fight!" msgstr "" "Dette er det sidste slag. BÃ¥de du og dine fjender er bevæbnet til tænderne, " "og alle er allierede imod dig. Fang Roland for at vinde krigen.\n" "\n" "Du taber dette scenarie, hvis du mister Archibald i kampens hede!" msgid "Arrow's Flight" msgstr "Pilens Flugt" msgid "Island of Chaos" msgstr "Kaosøen" msgid "The Abyss" msgstr "Dybet" msgid "Uprising" msgstr "Opstand" msgid "Aurora Borealis" msgstr "Aurora Borealis" msgid "Betrayal's End" msgstr "Forræderiets Ende" msgid "Corruption's Heart" msgstr "Fordærvelsens Hjerte" msgid "The Giant's Pass" msgstr "Kæmpens Pas" msgid "" "Subdue the unruly local lords in order to provide the Empire with facilities " "to operate in this region." msgstr "" "Underlæg de uregerlige lokale herrer, for at kunne faciliterer imperiets " "operationer i denne region." msgid "" "Eliminate all opposition in this area. Then the first piece of the artifact " "will be yours." msgstr "" "Eliminér alle modstandere i dette omrÃ¥de. SÃ¥ den første del af artefaktet " "bliver dit." msgid "" "The sorceresses to the northeast are rebelling! For the good of the empire " "you must quash their feeble uprising on your way to the mountains." msgstr "" "Heksene mod nordøst gør oprør! Til gavn for imperiet skal du kvase deres " "svage opstand pÃ¥ din vej til bjergene." msgid "" "Having prepared for your arrival, Kraeger has arranged for a force of " "necromancers to thwart your quest. You must capture the castle of Scabsdale " "before the first day of the third week, or the Necromancers will be too " "strong for you." msgstr "" "Efter at have forberedt din ankomst, har Kraeger sørget for en styrke af " "nekromantikere, for at forpurre din mission, som er at indtage borgen " "Scabsdale. Du taber dette scenarie, hvis du ikke nÃ¥r det inden den første " "dag i den tredje uge. Ellers bliver nekromantikerne for stærke for dig." msgid "" "The barbarian despot in this area is, as yet, ignorant of your presence. " "Quickly, build up your forces before you are discovered and attacked! Secure " "the region by subduing all enemy forces." msgstr "" "Den barbariske despot i dette omrÃ¥de er endnu uvidende om din " "tilstedeværelse. Byg hurtigt dine styrker op, før du bliver opdaget og " "angrebet! Du skal sikre regionen, ved at undertrykke alle fjendens styrker." msgid "" "The Empire is weak in this region. You will be unable to completely subdue " "all forces in this area, so take what you can before reprisal strikes. " "Remember, your true goal is to claim the Helmet of Anduran." msgstr "" "Imperiet er svagt i denne region. Du vil ikke være i stand til fuldstændigt " "at undertrykke alle styrker i dette omrÃ¥de. SÃ¥ tag hvad du kan, før " "repressalierne rammer. Husk, dit sande mÃ¥l er at gøre krav pÃ¥ Andurans Hjelm." msgid "For the good of the Empire, eliminate Kraeger." msgstr "Til gavn for imperiet, skal du eliminere Kraeger." msgid "" "At last, you have the opportunity and the facilities to rid the Empire of " "the necromancer's evil. Eradicate them completely, and you will be sung as a " "hero for all time." msgstr "" "Endelig har du muligheden, og faciliteterne, til at befri imperiet for " "nekromantikerens ondskab. Udryd dem fuldstændigt, og du vil for eftertiden " "blive besunget som helt." msgid "Border Towns" msgstr "Grænsebyer" msgid "Conquer and Unify" msgstr "Erobr og foren" msgid "Crazy Uncle Ivan" msgstr "Skøre Onkel Ivan" msgid "The Wayward Son" msgstr "Den EgenrÃ¥dige Søn" msgid "Ivory Gates" msgstr "Ivory Gates" msgid "The Elven Lands" msgstr "Elverrigerne" msgid "The Epic Battle" msgstr "Det Episke Slag" msgid "The Southern War" msgstr "Sydkrigen" msgid "" "Conquer and unite all the enemy tribes. Don't lose the hero Jarkonas, the " "forefather of all descendants." msgstr "" "Erobr og foren alle fjendens stammer. \n" "\n" "Du taber dette scenarie, hvis du mister helten Jarkonas, forfaderen til alle " "efterkommere." msgid "" "Your rival, the Kingdom of Harondale, is attacking weak towns on your " "border! Recover from their first strike and crush them completely!" msgstr "" "Det rivaliserende kongerige, Harondale, angriber svage byer tæt ved din " "grænse! Vind styrken tilbage, ovenpÃ¥ deres første angreb, og knus dem sÃ¥ " "fuldstændigt!" msgid "" "Find your wayward son Joseph who is rumored to be living in the desolate " "lands. Do it before the first day of the third month or it will be of no " "help to your family." msgstr "" "Find din egenrÃ¥dige søn, Joseph, der rygtes at bo ude i ødemarkerne.\n" "\n" "Hvis du ikke gør det inden første dag i den tredje mÃ¥ned, hjælper det ikke " "din familie." msgid "" "Rescue your crazy uncle Ivan. Find him before the first day of the fourth " "month or it will be no help to your kingdom." msgstr "" "Red din skøre onkel Ivan.\n" "\n" "Hvis du ikke finder ham inden den første dag i den fjerde mÃ¥ned, vil han " "ikke være nogen hjælp for dit rige." msgid "" "Destroy the barbarians who are attacking the southern border of your " "kingdom! Recover your fallen towns, and then invade the jungle kingdom. " "Leave no enemy standing." msgstr "" "Knus barbarerne, der angriber dit riges sydlige grænse! Genopbyg dine faldne " "byer, og invadér derefter jungleriget. Efterlad ingen overlevende." msgid "Retake the castle of Ivory Gates, which has fallen due to treachery." msgstr "" "Genindtag borgen kaldet Ivory Gates, som er faldet pÃ¥ grund af forræderi." msgid "" "Gain the favor of the elves. They will not allow trees to be chopped down, " "so they will send you wood every 2 weeks. You must complete your mission " "before the first day of the seventh month, or the kingdom will surely fall." msgstr "" "Vind elvernes gunst. De vil ikke tillade, at deres træer bliver fældet. SÃ¥ " "de sender dig træ hver anden uge. \n" "\n" "Du taber dette scenarie, hvis du ikke fuldfører din mission inden den første " "dag i den syvende mÃ¥ned. Ellers vil Elverrigerne helt sikkert falde." msgid "" "This is the final battle against your rival kingdom of Harondale. Eliminate " "everyone, and don't lose the hero Jarkonas VI." msgstr "" "Dette er det sidste slag mod dit rivaliserende kongerige Harondale. Eliminér " "alle.\n" "\n" "Du taber dette scenarie, hvis du mister helten Jarkonas VI." msgid "Fount of Wizardry" msgstr "Trolddomskilden" msgid "Power's End" msgstr "Magtens Ende" msgid "The Eternal Scrolls" msgstr "De Evige Skriftruller" msgid "The Shrouded Isles" msgstr "TÃ¥geøerne" msgid "" "Your mission is to vanquish the warring mages in the magical Shrouded Isles. " "The completion of this task will give you a fighting chance against your " "rivals." msgstr "" "Din mission er at overvinde de genstridige troldmænd pÃ¥ TÃ¥geøerne. " "Gennemførelsen af denne mission vil styrke dine chancer i kampen mod dine " "rivaler." msgid "" "The location of the great library has been discovered! You must make your " "way to it, and reclaim the city of Chronos in which it lies." msgstr "" "Placeringen af det store bibliotek er blevet opdaget! Du skal rejse hen til " "det og genindtage byen kaldet Chronos, hvori det ligger." msgid "" "Find the Orb of negation, which is said to be buried in this land. There are " "clues inscribed on stone obelisks which will help lead you to your prize. " "Find the Orb before the first day of the sixth month, or your rivals will " "surely have gotten to the fount before you." msgstr "" "Find Negationskuglen, som siges at være begravet i dette land. Der er spor " "indskrevet pÃ¥ stenobelisker, som vil hjælpe med at føre dig til din " "belønning.\n" "\n" "Hvis du ikke finder kuglen før den første dag i den sjette mÃ¥ned, vil dine " "rivaler helt sikkert være nÃ¥et til Trolddomskilden før dig." msgid "" "You must take control of the castle of Magic, where the fount of wizardry " "lies. Do this and your victory will be supreme." msgstr "" "Du skal tage kontrol over magiens borg, hvor Trolddomskilden ligger. Gør " "dette, og din sejr vil være suveræn." msgid "Blood is Thicker" msgstr "Blod er Tykkere" msgid "King and Country" msgstr "Land og Rige" msgid "Pirate Isles" msgstr "Piratøerne" msgid "Stranded" msgstr "Strandet" msgid "" "Capture the town on the island off the southeast shore in order to construct " "a boat and travel back towards the mainland. Do not lose the hero Gallavant." msgstr "" "Indtag byen pÃ¥ øen ud for den sydøstlige kyst, for at bygge en bÃ¥d og rejse " "tilbage mod fastlandet. \n" "\n" "Du taber dette scenarie, hvis du mister helten Gallavant." msgid "" "Find and defeat Martine, the pirate leader, who resides in Pirates Cove. Do " "not lose Gallavant or your quest will be over." msgstr "" "Find og besejr Martine, piratlederen, som bor i byen Pirate Cove. \n" "\n" "Du taber dette scenarie, hvis du mister Gallavant. For sÃ¥ vil din mission " "være forbi." msgid "" "Eliminate all the other forces who oppose the rule of Lord Alberon. " "Gallavant must not die." msgstr "" "Eliminér alle de andre kræfter, der modsætter sig Lord Alberons styre. \n" "\n" "Du taber dette scenarie, hvis du mister Gallavant." msgid "" "Overthrow the entrenched monarchy of Lord Alberon, and claim all the land in " "your name. Gallavant must not die." msgstr "" "Omstyrt Lord Alberons forankrede monarki, og kræv hele landet i dit navn. \n" "\n" "Du taber dette scenarie, hvis du mister Gallavant." msgid " bane" msgstr " bane" msgid " alliance" msgstr " alliance" msgid "Carry-over forces" msgstr "Overførselsstyrker" msgid " bonus" msgstr " bonus" msgid " defeated" msgstr " død" msgid " will always run away from your army." msgstr " vil altid flygte i rædsel for din hær." msgid " will be willing to join your army." msgstr " vil velvilligt tilslutte sig din hær." msgid "\"%{artifact}\" artifact will be carried over the scenario." msgstr "\"%{artifact}\" artefaktet vil blive bragt videre til næste scenarie." msgid "The army will be carried over the scenario." msgstr "Hæren vil blive bragt videre til næste scenarie." msgid "The kingdom will have +%{count} %{resource} each day." msgstr "Kongeriget vil have +%{count} %{resource} om dagen." msgid "\"%{spell}\" spell will be carried over the scenario." msgstr "Besværgelsen \"%{spell}\" vil følge med til næste scenarie." msgid "%{hero} can be hired in the scenario." msgstr "I dette scenarie kan %{hero} rekrutteres." msgid "" "%{hero} has been defeated and will not appear in the subsequent scenarios." msgstr "" "%{hero} er blevet besejret og viser sig ikke i de efterfølgende scenarier." msgid "The dwarves recognize their allies and gladly join your forces." msgstr "Dværgene genkender deres allierede og slutter sig gerne til din hær." msgid "The ogres recognize you as the Dwarfbane and lumber over to join you." msgstr "" "Jætterne genkender dig som dværgbanen og kommer trampende over, for at " "slutte sig til dig." msgid "" "The dragons, snarling and growling, agree to join forces with you, their " "'Ally'." msgstr "" "Dragerne, snerrende og knurrende, er enige om at følge dig, deres " "'allierede'." msgid "" "As you approach the group of elves, their leader calls them all to " "attention. He shouts to them, \"Who of you is brave enough to join this " "fearless ally of ours?\" The group explodes with cheers as they run to join " "your ranks." msgstr "" "Da du nærmer dig gruppen af elvere, pÃ¥kalder elverlederen deres alles " "opmærksomhed. Han rÃ¥ber til dem: \"Hvem af jer er modig nok til at slutte " "sig til denne, vores frygtløse allieret?\" Gruppen bryder ud i jubel, mens " "de løber for at slutte sig til dine rækker." msgid "" "The dwarves hail you, \"Any friend of Roland is a friend of ours. You may " "pass.\"" msgstr "" "Dværgene hylder dig, \"Enhver ven af Roland er en ven af os. Du mÃ¥ gerne " "passere.\"" msgid "" "The ogres give you a grunt of recognition, \"Archibald's allies may pass.\"" msgstr "" "Jætter giver dig et grynt af genkendelse, \"Archibalds allierede kan passere." "\"" msgid "" "The dragons see you and call out. \"Our alliance with Archibald compels us " "to join you. Unfortunately you have no room. A pity!\" They quickly scatter." msgstr "" "Dragerne ser dig og rÃ¥ber. \"Vores alliance med Archibald tvinger os til at " "slutte os til dig. Desværre har du ikke plads. En skam!\" De spreder sig " "hurtigt." msgid "" "The elves stand at attention as you approach. Their leader calls to you and " "says, \"Let us not impede your progress, ally! Move on, and may victory be " "yours.\"" msgstr "" "Elverne stÃ¥r vagtsomme, mens du nærmer dig. Deres leder kalder pÃ¥ dig og " "siger: \"Lad os ikke hæmme din fremgang, min allieret! Fortsæt fremad, og mÃ¥ " "sejren blive din.\"" msgid "\"The Dwarfbane!!!!, run for your lives.\"" msgstr "\"Dværgbanen!!!!, løb for dit liv.\"" msgid "campaignBonus|Animate Dead" msgstr "Dødebesjæling" msgid "campaignBonus|Chain Lightning" msgstr "Lynkæde" msgid "campaignBonus|Fireblast" msgstr "Flammebrag" msgid "campaignBonus|Mass Curse" msgstr "Masse-forbandelse" msgid "campaignBonus|Mass Haste" msgstr "Masse-forhastning" msgid "campaignBonus|Mirror Image" msgstr "Spejlbillede" msgid "campaignBonus|Resurrect" msgstr "Genoplivelse" msgid "campaignBonus|Steelskin" msgstr "StÃ¥lhud" msgid "campaignBonus|Summon Earth" msgstr "Hidkald jord-elem." msgid "campaignBonus|View Heroes" msgstr "Vis helte" msgid "campaignBonus|Ballista" msgstr "Ballisten" msgid "campaignBonus|Black Pearl" msgstr "Den sorte perle" msgid "campaignBonus|Caster's Bracelet" msgstr "Magisk armbÃ¥nd" msgid "campaignBonus|Defender Helm" msgstr "Forsvarshjelm" msgid "campaignBonus|Breastplate" msgstr "Brystpladen" msgid "campaignBonus|Dragon Sword" msgstr "Dragesværdet" msgid "campaignBonus|Fizbin Medal" msgstr "Fizbin-medalje" msgid "campaignBonus|Foremost Scroll" msgstr "Magisk skriftrulle" msgid "campaignBonus|Gauntlets" msgstr "Handsker" msgid "campaignBonus|Hideous Mask" msgstr "Hæslig maske" msgid "campaignBonus|Mage's Ring" msgstr "Troldmandsring" msgid "campaignBonus|Major Scroll" msgstr "Magisk skriftrulle" msgid "campaignBonus|Medal of Honor" msgstr "Æresmedalje" msgid "campaignBonus|Medal of Valor" msgstr "Tapperhedsmedalje" msgid "campaignBonus|Minor Scroll" msgstr "Magisk skriftrulle" msgid "campaignBonus|Nomad Boots" msgstr "Syvmilestøvler" msgid "campaignBonus|Power Axe" msgstr "Kraftøksen" msgid "campaignBonus|Spiked Shield" msgstr "Pigskjoldet" msgid "campaignBonus|Stealth Shield" msgstr "Skalkeskjold" msgid "campaignBonus|Tax Lien" msgstr "Skattepant" msgid "campaignBonus|Thunder Mace" msgstr "Morgenstjerne" msgid "campaignBonus|Traveler's Boots" msgstr "Syvmilestøvler" msgid "campaignBonus|White Pearl" msgstr "Den hvide perle" msgid "campaignBonus|Basic Archery" msgstr "Begynder skydning" msgid "campaignBonus|Advanced Archery" msgstr "Øvet skydning" msgid "campaignBonus|Expert Archery" msgstr "Ekspert skydning" msgid "campaignBonus|Basic Ballistics" msgstr "Beg. ballistik" msgid "campaignBonus|Advanced Ballistics" msgstr "Øvet ballistik" msgid "campaignBonus|Expert Ballistics" msgstr "Ekspert ballistik" msgid "campaignBonus|Basic Diplomacy" msgstr "Beg. diplomati" msgid "campaignBonus|Advanced Diplomacy" msgstr "Øvet diplomati" msgid "campaignBonus|Expert Diplomacy" msgstr "Ekspert diplomati" msgid "campaignBonus|Basic Eagle Eye" msgstr "Begynder ørneblik" msgid "campaignBonus|Advanced Eagle Eye" msgstr "Øvet ørneblik" msgid "campaignBonus|Expert Eagle Eye" msgstr "Ekspert ørneblik" msgid "campaignBonus|Basic Estates" msgstr "Begynder adelskab" msgid "campaignBonus|Advanced Estates" msgstr "Øvet adelskab" msgid "campaignBonus|Expert Estates" msgstr "Ekspert adelskab" msgid "campaignBonus|Basic Leadership" msgstr "Beg. lederskab" msgid "campaignBonus|Advanced Leadership" msgstr "Øvet lederskab" msgid "campaignBonus|Expert Leadership" msgstr "Ekspert lederskab" msgid "campaignBonus|Basic Logistics" msgstr "Begynder logistik" msgid "campaignBonus|Advanced Logistics" msgstr "Øvet logistik" msgid "campaignBonus|Expert Logistics" msgstr "Ekspert logistik" msgid "campaignBonus|Basic Luck" msgstr "Begynder held" msgid "campaignBonus|Advanced Luck" msgstr "Øvet held" msgid "campaignBonus|Expert Luck" msgstr "Ekspert held" msgid "campaignBonus|Basic Mysticism" msgstr "Beg. mysticisme" msgid "campaignBonus|Advanced Mysticism" msgstr "Øvet mysticisme" msgid "campaignBonus|Expert Mysticism" msgstr "Eks. mysticisme" msgid "campaignBonus|Basic Navigation" msgstr "Beg. navigation" msgid "campaignBonus|Advanced Navigation" msgstr "Øvet navigation" msgid "campaignBonus|Expert Navigation" msgstr "Eks. navigation" msgid "campaignBonus|Basic Necromancy" msgstr "Beg. nekromantik" msgid "campaignBonus|Advanced Necromancy" msgstr "Øvet nekromantik" msgid "campaignBonus|Expert Necromancy" msgstr "Eks. nekromantik" msgid "campaignBonus|Basic Pathfinding" msgstr "Beg. stifinding" msgid "campaignBonus|Advanced Pathfinding" msgstr "Øvet stifinding" msgid "campaignBonus|Expert Pathfinding" msgstr "Eks. stifinding" msgid "campaignBonus|Basic Scouting" msgstr "Beg. udspejdning" msgid "campaignBonus|Advanced Scouting" msgstr "Øvet udspejdning" msgid "campaignBonus|Expert Scouting" msgstr "Eks. udspejdning" msgid "campaignBonus|Basic Wisdom" msgstr "Begynder visdom" msgid "campaignBonus|Advanced Wisdom" msgstr "Øvet visdom" msgid "campaignBonus|Expert Wisdom" msgstr "Ekspert visdom" msgid "" "The main hero will have \"%{artifact}\" artifact at the start of the " "scenario." msgstr "" "I dette scenarie vil din hovedhelt starte med artefaktet \"%{artifact}\"." msgid "" "The kingdom will receive %{amount} additional %{resource} at the start of " "the scenario." msgstr "" "I dette scenarie vil dit kongerige starte med %{amount} ekstra mængder " "%{resource}." msgid "" "The kingdom will have %{amount} less %{resource} at the start of the " "scenario." msgstr "" "I dette scenarie vil dit kongerige starte med %{amount} mindre mængder " "%{resource}." msgid "" "The main hero will have %{count} %{monster} at the start of the scenario." msgstr "I dette scenarie vil din hovedhelt starte med %{count} %{monster}." msgid "" "The main hero will have \"%{spell}\" spell at the start of the scenario." msgstr "" "I dette scenarie vil din hovedhelt starte med besværgelsen \"%{spell}\"." msgid "The starting race of the scenario will be %{race}." msgstr "Begynd scenariet med %{race}-klassen." msgid "" "The main hero will have additional %{count} %{skill} at the start of the " "scenario." msgstr "" "Hovedhelten vil i dette scenarie begynde med yderligere %{count} point i " "%{skill}." msgid "The main hero will have %{skill} at the start of the scenario." msgstr "Hovedhelten vil i dette scenarie starte med at have lært %{skill}." msgid "Roland" msgstr "Roland" msgid "Archibald" msgstr "Archibald" #, fuzzy msgid "Price of Loyalty" msgstr "Prisen for Loyalitet" msgid "Voyage Home" msgstr "Hjemrejse" msgid "Wizard's Isle" msgstr "Troldmandsøen" msgid "Descendants" msgstr "Efterkommere" msgid "The %{building} produces %{monster}." msgstr "%{building} frembringer %{monster}." msgid "Requires:" msgstr "Kræver:" msgid "Exit this menu without doing anything." msgstr "Forlad denne menu, uden at gøre noget." msgid "Cannot build. You have already built here today." msgstr "Byggeri umuligt. Du har allerede bygget her i dag." msgid "For this action it is necessary to build a castle first." msgstr "Til denne handling er det nødvendigt først at bygge en borg." msgid "Cannot build %{name} because the castle is too far away from an ocean." msgstr "%{name}et kan ikke bygges, da borgen er for langt fra vand." msgid "disable build." msgstr "deaktiver byggeri." msgid "Cannot afford %{name}." msgstr "%{name} er for dyr at bygge." msgid "%{name} is already built." msgstr "%{name} er allerede blevet bygget." msgid "Cannot build %{name}." msgstr "Umuligt at bygge %{name}." msgid "Build %{name}." msgstr "Byg %{name}" msgid "Blackridge" msgstr "Sortekam" msgid "Hillstone" msgstr "Voldsten" msgid "Pinehurst" msgstr "Fyrenskov" msgid "Whiteshield" msgstr "Hvidskjold" msgid "Woodhaven" msgstr "Skovhaven" msgid "Blackwind" msgstr "Sortrup" msgid "Bloodreign" msgstr "Bloddum" msgid "Dragontooth" msgstr "DraggÃ¥rd" msgid "Greywind" msgstr "GrÃ¥vinde" msgid "Portsmith" msgstr "Smedehaven" msgid "Atlantium" msgstr "Atlantum" msgid "Middle Gate" msgstr "Midborg" msgid "Sansobar" msgstr "Sansobar" msgid "Tundara" msgstr "Tundara" msgid "Vulcania" msgstr "Vulkania" msgid "Baywatch" msgstr "Bugtvad" msgid "Fountainhead" msgstr "Kildspring" msgid "Vertigo" msgstr "Snurrum" msgid "Wildabar" msgstr "Vildabar" msgid "Winterkill" msgstr "Barfrost" msgid "Brindamoor" msgstr "Fakkelhede" msgid "Lakeside" msgstr "Søsted" msgid "Nightshadow" msgstr "Natskygge" msgid "Olympus" msgstr "Olympos" msgid "Sandcaster" msgstr "Sandborg" msgid "Alamar" msgstr "Alamar" msgid "Burlock" msgstr "Burlock" msgid "Dragadune" msgstr "Dragadune" msgid "Kalindra" msgstr "Kalindra" msgid "Xabran" msgstr "Sabran" msgid "Algary" msgstr "Algær" msgid "Basenji" msgstr "Dingo" msgid "Blackfang" msgstr "Blackfang" msgid "New Dawn" msgstr "Nygry" msgid "Sorpigal" msgstr "Sorpigal" msgid "Avone" msgstr "Avone" msgid "Big Oak" msgstr "Stor-ege" msgid "Chandler" msgstr "Lysning" msgid "Erliquin" msgstr "Arliquin" msgid "Hampshire" msgstr "Hampdrup" msgid "Antioch" msgstr "Antiokia" msgid "Avalon" msgstr "Avalon" msgid "Roc Haven" msgstr "Rokskov" msgid "South Mill" msgstr "Søndermølle" msgid "Weed Patch" msgstr "Bakkestjerne" msgid "Brownston" msgstr "Brunborg" msgid "Hilltop" msgstr "Bakketop" msgid "Weddington" msgstr "Væderup" msgid "Westfork" msgstr "Vesterskille" msgid "Whittingham" msgstr "Viderup" msgid "Cathcart" msgstr "KasgÃ¥rd" msgid "Elk's Head" msgstr "Elgevir" msgid "Roscomon" msgstr "Rosenborg" msgid "Sherman" msgstr "Ulsakse" msgid "Yorksford" msgstr "Rodbæk" msgid "Blackburn" msgstr "Kulbrand" msgid "Blacksford" msgstr "Mørkbæk" msgid "Burton" msgstr "Skodby" msgid "Pig's Eye" msgstr "Svinerøje" msgid "Viper's Nest" msgstr "Bande-rede" msgid "Fenton" msgstr "Hegnby" msgid "Lankershire" msgstr "Langherred" msgid "Lombard" msgstr "Lombard" msgid "Timberhill" msgstr "Tømmerhøj" msgid "Troy" msgstr "Troja" msgid "Forder Oaks" msgstr "Egebæk" msgid "Meramec" msgstr "Meramek" msgid "Quick Silver" msgstr "Kviksølver" msgid "Westmoor" msgstr "Vesthede" msgid "Willow" msgstr "Virlund" msgid "Corackston" msgstr "Coraktun" msgid "Sheltemburg" msgstr "Sheltemborg" msgid "Cannot recruit - you already have a Hero in this town." msgstr "Umuligt at rekruttere - Du har allerede en helt i denne borg." msgid "Cannot recruit - you have too many Heroes." msgstr "Umuligt at rekruttere - Du har for mange helte." msgid "Cannot afford a Hero" msgstr "Umuligt at rekruttere en helt" msgid "There is no room in the garrison for this army." msgstr "Der er ikke plads i garnisonen til denne trop." msgid "Fortifications" msgstr "Befæstninger" msgid "Farm" msgstr "Mark" msgid "Thatched Hut" msgstr "StrÃ¥tækt hytte" msgid "Archery Range" msgstr "Bueskydningsbane" msgid "Upg. Archery Range" msgstr "Opg. bueskydningsbane" msgid "Blacksmith" msgstr "Grovsmedje" msgid "Upg. Blacksmith" msgstr "Opg. grovsmedje" msgid "Armory" msgstr "VÃ¥bensmedje" msgid "Upg. Armory" msgstr "Opg. vÃ¥bensmedje" msgid "Jousting Arena" msgstr "Dystningsarena" msgid "Upg. Jousting Arena" msgstr "Opg. dystningsarena" msgid "Cathedral" msgstr "Katedral" msgid "Upg. Cathedral" msgstr "Opg. katedral" msgid "Coliseum" msgstr "Amfiteater" msgid "Garbage Heap" msgstr "Affaldsbunke" msgid "Hut" msgstr "Hytte" msgid "Stick Hut" msgstr "Pindehytte" msgid "Upg. Stick Hut" msgstr "Opg. pindehytte" msgid "Den" msgstr "Hule" # Adobe-sten er ler, som bruges til at bygge adobehuse med. De findes ogsÃ¥ i Danmark msgid "Adobe" msgstr "Adobehus" msgid "Upg. Adobe" msgstr "Opg. adobehus" msgid "Bridge" msgstr "Bro" msgid "Upg. Bridge" msgstr "Opg. bro" msgid "Pyramid" msgstr "Pyramide" msgid "Rainbow" msgstr "Regnbue" msgid "Crystal Garden" msgstr "Krystalhave" msgid "Treehouse" msgstr "Træhus" # Oversættes normalt til sommerhus. Men kottage er faktisk ogsÃ¥ dansk ord. msgid "Cottage" msgstr "Kottage" msgid "Upg. Cottage" msgstr "Opg. kottage" msgid "Stonehenge" msgstr "Stenkreds" msgid "Upg. Stonehenge" msgstr "Opg. stenkreds" msgid "Fenced Meadow" msgstr "Indhegnet eng" msgid "sorceress|Red Tower" msgstr "IldtÃ¥rn" msgid "Dungeon" msgstr "Fangehul" msgid "Waterfall" msgstr "Vandfald" msgid "Cave" msgstr "Grotte" msgid "Crypt" msgstr "Krypt" msgid "Nest" msgstr "Rede" msgid "Maze" msgstr "Labyrint" msgid "Upg. Maze" msgstr "Opg. labyrint" msgid "Swamp" msgstr "Sump" msgid "Green Tower" msgstr "Grønt tÃ¥rn" msgid "warlock|Red Tower" msgstr "Rødt tÃ¥rn" msgid "Black Tower" msgstr "Sort tÃ¥rn" msgid "Library" msgstr "Bibliotek" msgid "Orchard" msgstr "Frugthave" msgid "Habitat" msgstr "Habitat" msgid "Pen" msgstr "Svinesti" msgid "Foundry" msgstr "Støberi" msgid "Upg. Foundry" msgstr "Opg. støberi" msgid "Cliff Nest" msgstr "Klipperede" msgid "Ivory Tower" msgstr "ElfenbenstÃ¥rn" msgid "Upg. Ivory Tower" msgstr "Opg. elfenbenstÃ¥rn" msgid "Cloud Castle" msgstr "Skyslot" msgid "Upg. Cloud Castle" msgstr "Opg. skyslot" msgid "Storm" msgstr "Storm" msgid "Skull Pile" msgstr "Kraniebunke" msgid "Excavation" msgstr "Udgravning" msgid "Graveyard" msgstr "KirkegÃ¥rd" msgid "Upg. Graveyard" msgstr "Opg. kirkegÃ¥rd" msgid "Upg. Pyramid" msgstr "Opg. pyramide" msgid "Mansion" msgstr "Palæ" msgid "Upg. Mansion" msgstr "Opg. palæ" msgid "Mausoleum" msgstr "Mausoleum" msgid "Upg. Mausoleum" msgstr "Opg. mausoleum" msgid "Laboratory" msgstr "Laboratorium" msgid "Shrine" msgstr "Helligdom" msgid "" "The Fortifications increase the toughness of the walls, increasing the " "number of turns it takes to knock them down." msgstr "" "Befæstningerne styrker ringmurene, sÃ¥ de kan modstÃ¥ mindst ét ekstra angreb, " "før muren væltes." msgid "The Farm increases production of Peasants by %{count} per week." msgstr "Marken øger tilvæksten af bønder med %{count} om ugen." msgid "" "The Coliseum provides inspiring spectacles to defending troops, raising " "their morale by two during combat." msgstr "" "I amfiteatret inspirerer gladiatorkampe de forsvarende styrkers kampgejst, " "sÃ¥ deres moral under kamp løftes med to point." msgid "The Garbage Heap increases production of Goblins by %{count} per week." msgstr "Affaldsbunken øger tilvæksten af vætter med %{count} om ugen." msgid "The Rainbow increases the luck of the defending units by two." msgstr "Regnbuen øger de forsvarende styrkers held med 2 point." msgid "" "The Crystal Garden increases production of Sprites by %{count} per week." msgstr "Krystalhaven øger tilvæksten af feer med %{count} om ugen." msgid "The Dungeon increases the income of the town by %{count} gold per day." msgstr "Fangehullet øger byens indkomst med %{count} guldstykker om dagen." msgid "The Waterfall increases production of Centaurs by %{count} per week." msgstr "Vandfaldet øger tilvæksten af kentaurer med %{count} om ugen." msgid "" "The Library increases the number of spells in the Guild by one for each " "level of the guild." msgstr "" "Biblioteket øger antallet af besværgelser i magikerlauget med én for hvert " "plan-niveau." msgid "The Orchard increases production of Halflings by %{count} per week." msgstr "Frugthaven øger tilvæksten af halvlange med %{count} om ugen." msgid "The Storm adds +2 to the power of spells of a defending spell caster." msgstr "" "Stormen øger en forsvarende besværgers magikraft med 2 point under kamp." msgid "The Skull Pile increases production of Skeletons by %{count} per week." msgstr "Kraniebunken øger tilvæksten af skeletter med %{count} om ugen." msgid "Thieves' Guild" msgstr "Tyvelaug" msgid "Tavern" msgstr "Kro" msgid "Shipyard" msgstr "Skibsværft" msgid "Well" msgstr "Brønd" msgid "Statue" msgstr "Statue" msgid "Marketplace" msgstr "Markedsplads" msgid "Moat" msgstr "Voldgrav" msgid "Castle" msgstr "Borg" msgid "Tent" msgstr "Telt" msgid "Captain's Quarters" msgstr "Kaptajnens kvarter" msgid "Mage Guild, Level 1" msgstr "Magikerlaug, 1. plan" msgid "Mage Guild, Level 2" msgstr "Magikerlaug, 2. plan" msgid "Mage Guild, Level 3" msgstr "Magikerlaug, 3. plan" msgid "Mage Guild, Level 4" msgstr "Magikerlaug, 4. plan" msgid "Mage Guild, Level 5" msgstr "Magikerlaug, 5. plan" msgid "" "The Shrine increases the necromancy skill of all your necromancers by 10 " "percent." msgstr "" "Helligdommen øger alle dine nekromantikeres nekromantik med 10 procent." msgid "" "The Thieves' Guild provides information on enemy players. Thieves' Guilds " "can also provide scouting information on enemy towns. Additional Guilds " "provide more information." msgstr "" "Tyvelauget skaffer informationer om hvordan andre spillere klarer sig i " "forhold til dig. Desuden udspejdes fjendtlige byer og borge ogsÃ¥. Jo flere " "Tyvelaug du har, jo flere informationer fÃ¥r du." msgid "The Tavern increases morale for troops defending the castle." msgstr "Kroen øger de forsvarende styrkers moral med 2 point." msgid "The Shipyard allows ships to be built." msgstr "Skibsværftet gør det muligt at bygge skibe." msgid "" "The Well increases the growth rate of all dwellings by %{count} creatures " "per week." msgstr "" "Brønden øger den ugentlige tilvækst af byens bosteder med %{count}. Brønden " "gør det ogsÃ¥ muligt at fÃ¥ en oversigt over byens væsener. Du kan desuden " "hverve dem herfra." #, fuzzy msgid "The Statue increases the town's income by %{count} gold per day." msgstr "Statuen øger din bys indkomst med %{count} guldstykker om dagen." msgid "The Left Turret provides extra firepower during castle combat." msgstr "" "Venstre tÃ¥rn frembringer et ekstra skydetÃ¥rn, som øger ildkraft under " "belejring af borgen." msgid "The Right Turret provides extra firepower during castle combat." msgstr "" "Højre tÃ¥rn frembringer et ekstra skydetÃ¥rn, som øger ildkraft under " "belejring af borgen." msgid "" "The Marketplace can be used to convert one type of resource into another. " "The more marketplaces you control, the better the exchange rate." msgstr "" "PÃ¥ markedspladsen kan man bytte én resursetype til en anden. Du opnÃ¥r mere " "og mere fordelagtige byttehandlinger, jo flere markedspladser du har i dit " "rige." msgid "" "The Moat slows attacking units. Any unit entering the moat must end its turn " "there and becomes more vulnerable to attack." msgstr "" "Voldgraven forsinker angribende tropper. Enhver trop, som træder ned i " "voldgraven, ender sin tur dér. SÃ¥ længe den befinder sig her, vil den " "desuden være mere sÃ¥rbar overfor angreb." #, fuzzy msgid "" "The Castle improves the town's defense and increases its income to %{count} " "gold per day." msgstr "" "Borgen forstærker byens forsvar mod angreb. Borgen øger desuden dit riges " "indkomst med %{count} guldstykker om dagen." msgid "" "The Tent provides workers to build a castle, provided the materials and the " "gold are available." msgstr "" "Teltet frembringer arbejdere til at bygge en borg, sÃ¥fremt de fornødne " "resurser og guld er pÃ¥ plads." msgid "" "The Captain's Quarters provides a captain to assist in the castle's defense " "when no hero is present." msgstr "" "Kaptajnens kvarter frembringer en kaptajn til at assistere de forsvarende " "styrker, nÃ¥r der ikke er en helt tilstede." msgid "" "The Mage Guild allows heroes to learn spells and replenish their spell " "points." msgstr "" "Helte kan købe en magibog og lære besværgelser, nÃ¥r de besøger " "magikerlauget. Desuden fornyes heltes maksimale antal mana automatisk, hvis " "de ender deres tur stÃ¥ende i byen." msgid "Recruit %{name}" msgstr "Rekruttér %{name}" msgid "Month: %{month}, Week: %{week}, Day: %{day}" msgstr "MÃ¥ned: %{month}, Uge: %{week}, Dag: %{day}" msgid "" "You must purchase a spell book to use the mage guild, but you currently have " "no room for a spell book. Try giving one of your artifacts to another hero." msgstr "" "Din helt er nødt til at købe en magibog, før denne mÃ¥ træde ind i " "magikerlauget. Men helten har desværre ikke plads til at bære en magibog. " "Prøv at afgive én af dine artefakter til en anden helt." msgid "Click to show next town." msgstr "Klik for at se næste by." msgid "Show next town" msgstr "Se næste by" msgid "Click to show previous town." msgstr "Klik for at se forrige by." msgid "Show previous town" msgstr "Se forrige by" msgid "This town may not be upgraded to a castle." msgstr "Der kan ikke bygges en borg i denne by." msgid "Town" msgstr "By" msgid "Exit Castle" msgstr "Forlad borg" msgid "Exit Town" msgstr "Forlad by" msgid "Show Income" msgstr "Vis indkomst" msgid "View Hero" msgstr "Vis helt" msgid "The above spells are available here." msgstr "Med nok visdom kan alle disse besværgelser læres af dine helte." msgid "The spells the hero can learn have been added to their book." msgstr "De besværgelser, den besøgende helt kan lære, er nu i magibogen." msgid "A generous tip for the barkeep yields the following rumor:" msgstr "En generøs mængde drikkepenge fÃ¥r krokarlen til at dele dette rygte:" msgid "Recruit Hero" msgstr "Rekruttér helt" msgid "%{name} is a level %{value} %{race} " msgstr "%{name} er en level %{value} %{race} " msgid "with %{count} artifacts." msgstr "med %{count} artefakter." msgid "with 1 artifact." msgstr "med 1 artefakt." msgid "without artifacts." msgstr "uden artefakter." msgid "Recruit %{name} the %{race}" msgstr "Rekruttér %{race}en %{name}" #, fuzzy msgid "" "'Spread' combat formation spreads the castle's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "'spredt' kampformation spreder dine tropper fra top til bund pÃ¥ slagmarken, " "med mindst én tom plads mellem hver enhed." #, fuzzy msgid "" "'Grouped' combat formation bunches the castle's units together in the center " "of the castle's side of the battlefield." msgstr "" "'gruppe'-formation samler dine tropper i midten af din side pÃ¥ slagmarken." msgid "Spread Formation" msgstr "Spredt formation" msgid "Grouped Formation" msgstr "Gruppeformation" msgid "Set garrison combat formation to 'Spread'" msgstr "" "Indstil garnisonen til at stÃ¥ opstille sig i 'spredt' formation under kamp" msgid "Set garrison combat formation to 'Grouped'" msgstr "" "Indstil garnisonen til at stÃ¥ opstille sig i 'gruppe'-formation under kamp" msgid "Exit Castle Options" msgstr "Forlad borg-vindue" msgid "Castle Options" msgstr "Borg-vindue" msgid "Not enough resources to recruit creatures." msgstr "Der er ikke nok resurser til at rekruttere væsener for." msgid "You are unable to recruit at this time, your ranks are full." msgstr "" "Du kan ikke rekruttere nye væsener til din hær, sÃ¥ længe dine rækker er " "fulde." msgid "No creatures available for purchase." msgstr "Der er i øjeblikket intet væsen at rekruttere." msgid "Recruit Creatures" msgstr "Rekruttér væsener" msgid "Max" msgstr "Maks" msgid "Hire all creatures in the town." msgstr "Hyr alle byens tilgængelige væsener." msgid "Available" msgstr "Tilgængelig" msgid "Town Population Information and Statistics" msgstr "Informationer og tal om byens indbyggere" msgid "Damage" msgstr "Skade" msgid "HP" msgstr "Liv" msgid "Growth" msgstr "Vækst" msgid "week" msgstr "uge" msgid "View World" msgstr "Vis verden" msgid "View the entire world." msgstr "Vis hele verdenen." msgid "Puzzle" msgstr "Puslespil" msgid "View the obelisk puzzle." msgstr "Vis obeliskens puslespil." msgid "Scenario Information" msgstr "Kortbeskrivelse" msgid "View information on the scenario you are currently playing." msgstr "Vis information om det scenarie, du er i gang med at spille." msgid "Dig for the Ultimate Artifact." msgstr "Grav efter Det Ultimative Artefakt." msgid "Digging" msgstr "Gravning" msgid "Arena" msgstr "Arena" msgid "" "You enter the arena and face a pack of vicious lions. You handily defeat " "them, to the wild cheers of the crowd. Impressed by your skill, the aged " "trainer of gladiators agrees to train you in a skill of your choice." msgstr "" "Du gÃ¥r ind i arenaen og stÃ¥r over for en flok glubske løver. Publikums vilde " "jubel lyder, idet du snildt besejrer løverne. Imponeret over dine evner, er " "den gamle gladiatortræner villig til at træne dig i en færdighed - Du vælger " "selv hvilken." msgid "Attack Skill" msgstr "Angreb" msgid "Defense Skill" msgstr "Forsvar" msgid "Shots" msgstr "Skud" msgid "Shots Left" msgstr "Skudreserve" msgid "Hit Points" msgstr "Liv" msgid "Hit Points Left" msgstr "Resterende liv" msgid "You can't afford to upgrade your troops!" msgstr "Du har ikke rÃ¥d til at opgradere dine tropper!" msgid "" "Your troops can be upgraded, but it will cost you dearly. Do you wish to " "upgrade them?" msgstr "" "Dine tropper kan opgraderes, men det vil koste dig dyrt. Vil du opgradere " "dem alligevel?" msgid "Are you sure you want to dismiss this army?" msgstr "Er du sikker pÃ¥ du vil bortvise denne trop?" msgid "Upgrade" msgstr "Opgradér" msgid "Upgrade your troops." msgstr "Opgradér dine tropper." msgid "Dismiss" msgstr "Bortvis" msgid "Dismiss this army." msgstr "Bortvis denne hær." msgid "" "A group of %{monster} with a desire for greater glory wish to join you.\n" "Do you accept?" msgstr "" "En gruppe %{monster} med drømme om magt og ære ønsker at forene sig med din " "hær.\n" "Godtager du disse betingelser?" msgid "Followers" msgstr "Følgere" msgid "" "The %{monster} is swayed by your diplomatic tongue, and offers to join your " "army for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "Et enkelt medlem af %{monster}-hæren lader sig overtale af din diplomatiske " "tunge og vil gerne forene sig med din hær, for en sum af %{gold} " "guldstykker.\n" "Godtager du disse betingelser?" msgid "" "The creatures are swayed by your diplomatic\n" "tongue, and make you an offer:\n" "\n" msgstr "" "Disse væsener lader sig overtale af din diplomatiske\n" "tunge. De giver dig et tilbud:\n" "\n" msgid "" "%{offer} of the %{total} %{monster} will join your army, and the rest will " "leave you alone, for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "Ud af %{total} %{monster} er %{offer} af dem villige til at forene sig med " "din hær, mens de resterende vil lade dig være i fred, til en sum af %{gold} " "guldstykker.\n" "\n" "Godtager du disse betingelser?" msgid "" "All %{offer} of the %{monster} will join your army for the sum of %{gold} " "gold.\n" "Do you accept?" msgstr "" "Alle %{offer} %{monster} er villige til at forene sig med din hær til en " "pris af %{gold} guldstykker.\n" "Godtager du disse betingelser?" msgid "(Rate: %{percent})" msgstr "(Grad: %{percent})" msgid "off" msgstr "fra" msgid "Music" msgstr "Musik" msgid "Effects" msgstr "Effekter" msgid "MIDI" msgstr "MIDI" msgid "MIDI Expansion" msgstr "MIDI Udvidelse" msgid "External" msgstr "Ekstern" msgid "Music Type" msgstr "Musiktype" msgid "3D Audio" msgstr "3D Lyd" msgid "Toggle ambient music level." msgstr "Indstil niveau for omgivelseslyde." msgid "Toggle foreground sounds level." msgstr "Indstil niveau for forgrundslyde." msgid "Change the type of music." msgstr "Ændr musikstil." msgid "Toggle 3D effects of foreground sounds." msgstr "Indstil 3D effekter for forgrundslyde." msgid "Build a new ship:" msgstr "Byg et nyt skib:" msgid "Resource cost:" msgstr "Resurseomkostninger:" msgid "Total: " msgstr "Total: " msgid "Need: " msgstr "Behov: " msgid "Load Game" msgstr "Indlæs spil" msgid "No save files to load." msgstr "Ingen gemte filer at indlæse." msgid "New Game" msgstr "Nyt spil" msgid "Start a single or multi-player game." msgstr "Start et enkelt- eller et flerspiller-spil." msgid "Load a previously saved game." msgstr "Indlæs et tidligere gemt spil." msgid "Save Game" msgstr "Gem spil" msgid "Save the current game." msgstr "Gem nuværende spil." msgid "Quit" msgstr "Afslut" msgid "Quit out of Heroes of Might and Magic II." msgstr "Forlad Heroes of Might and Magic II." msgid "Language" msgstr "Sprog" msgid "Graphics" msgstr "Grafik" msgid "Black & White" msgstr "Sort & Hvid" msgid "Mouse Cursor" msgstr "Musemarkør" msgid "Color" msgstr "Farve" msgid "Text Support" msgstr "Tekstsupport" msgid "Change the language of the game." msgstr "Ændr spillets sprogindstillinger." msgid "Select Game Language" msgstr "Vælg spillets sprog" msgid "Change the graphics settings of the game." msgstr "Ændr spillets grafikindstillinger." msgid "Toggle colored cursor on or off. This is only an aesthetic choice." msgstr "Vælg farvet musemarkør til eller fra. Dette er kun et æstetisk valg." msgid "" "Toggle text support mode to output extra information about windows and " "events in the game." msgstr "" "Tilvælg tekststøttetilstand for at fÃ¥ flere tekstbaserede informationer om " "spillets vinduer og hændelser." msgid "" "Map\n" "Difficulty" msgstr "" "Kort\n" "Niveau" msgid "" "Game\n" "Difficulty" msgstr "" "Spil-\n" "niveau" msgid "Rating" msgstr "Bedømmelse" msgid "Map Size" msgstr "Kort størrelse" msgid "Opponents" msgstr "Spillere" msgid "Class" msgstr "Klasse" msgid "" "Victory\n" "Conditions" msgstr "" "Vinder-\n" "vilkÃ¥r" msgid "" "Loss\n" "Conditions" msgstr "" "Tabs-\n" "vilkÃ¥r" msgid "First select recipients!" msgstr "Vælg først modtagere!" msgid "You cannot select %{resource}!" msgstr "Du kan ikke vælge %{resource}!" msgid "Select count %{resource}:" msgstr "Vælg antal %{resource}:" msgid "Select Recipients" msgstr "Vælg modtagere" msgid "Your Funds" msgstr "Dine midler" msgid "Planned Gift" msgstr "Planlagt foræring" msgid "Gift from %{name}" msgstr "Gave fra %{name}" msgid "Resolution" msgstr "Resolution" msgid "Fullscreen" msgstr "Fuld skærm" msgid "window|Mode" msgstr "Vinduestilstand" msgid "Windowed" msgstr "Vindue" msgid "V-Sync" msgstr "V-Synk." msgid "on" msgstr "til" msgid "FPS" msgstr "FPS" msgid "System Info" msgstr "System info" msgid "Change the resolution of the game." msgstr "Skift spilopløsning." msgid "Select Game Resolution" msgstr "Vælg spilopløsning" msgid "Toggle between fullscreen and windowed modes." msgstr "Vælg mellem fuldskærms- og vinduestilstand." msgid "" "The V-Sync option can be enabled to resolve flickering issues on some " "monitors." msgstr "" "V-synk. i aktiv tilstand kan løse potentielle renderingsproblemer pÃ¥ visse " "skærme." msgid "Show extra information such as FPS and current time." msgstr "Vis flere informationer som billeder i sekundet (FPS) og klokkeslæt." msgid "Hotkey: " msgstr "Genvejstast: " msgid "Category: " msgstr "Kategori: " msgid "Event: " msgstr "Begivenhed: " msgid "Hot Keys:" msgstr "Genvejstaster:" msgid "Evil" msgstr "Ond" msgid "Good" msgstr "God" msgid "Interface Type" msgstr "Interface-type" msgid "Hide" msgstr "Skjul" msgid "Show" msgstr "Vis" msgid "Interface" msgstr "Interface" msgid "Slow" msgstr "Langsom" msgid "Normal" msgstr "Normal" msgid "Fast" msgstr "Hurtig" msgid "Very Fast" msgstr "Meget hurtig" msgid "Scroll Speed" msgstr "" "Rulle\n" "hastighed" msgid "Toggle the type of interface you want to use." msgstr "Vælg hvilken interface-type du ønsker at bruge." msgid "Toggle interface visibility." msgstr "Vælg interfacets synlighed til eller fra." msgid "Toggle colored cursor on or off. This is only an esthetic choice." msgstr "Vælg farvet musemarkør til eller fra. Dette er kun et æstetisk valg." msgid "Sets the speed at which you scroll the window." msgstr "Bestemmer vinduets rullehastighed." msgid "Select Game Language:" msgstr "Vælg spilsprog:" msgid "Click to choose the selected language." msgstr "Klik for at vælge det valgte sprog." msgid "%{name} has gained a level." msgstr "%{name} har opnÃ¥et et nyt niveau." msgid "%{skill} +1" msgstr "%{skill} +1" msgid "You have learned %{skill}." msgstr "Du har lært %{skill}" msgid "" "%{name} has gained a level.\n" "\n" "%{skill} +1" msgstr "" "%{name} har opnÃ¥et et nyt niveau.\n" "\n" "%{skill} +1" msgid "" "You may learn either:\n" "%{skill1}\n" "or\n" "%{skill2}" msgstr "" "Du kan enten lære:\n" "%{skill1}\n" "eller\n" "%{skill2}" msgid "" "Please inspect our fine wares. If you feel like offering a trade, click on " "the items you wish to trade with and for." msgstr "" "Inspicér venligst vores fine varer. Hvis du føler dig fristet til at handle, " "skal du klikke pÃ¥ de ting du ønsker at handle for og med." msgid "" "You have received quite a bargain. I expect to make no profit on the deal. " "Can I interest you in any of my other wares?" msgstr "" "Du har gjort en god handel. Jeg forventer ikke at gøre profit pÃ¥ dette. Er " "der andre af mine varer, der kunne have interesse?" msgid "I can offer you %{count} for 1 unit of %{resfrom}." msgstr "Jeg bytter gerne %{count} for 1 %{resfrom}." msgid "I can offer you 1 unit of %{resto} for %{count} units of %{resfrom}." msgstr "Jeg bytter gerne 1 %{resto} for %{count} %{resfrom}." msgid "Min" msgstr "Min" msgid "Qty to trade" msgstr "Bytteantal" msgid "Trading Post" msgstr "Handelsstation" msgid "Your Resources" msgstr "Dine resurser" msgid "Available Trades" msgstr "Handlemuligheder" msgid "n/a" msgstr "i/t" msgid "guarded by %{count} %{monster}" msgstr "Bevogtet af %{count} %{monster}" msgid "guarded by " msgstr "Bevogtet af " msgid "(available: %{count})" msgstr "(tilgængelig: %{count})" msgid "(empty)" msgstr "(tom)" msgid "already learned" msgstr "allerede lært" #, fuzzy msgid "treeOfKnowledge|free" msgstr "Visdomstræ" #, fuzzy msgid "already claimed" msgstr "allerede lært" msgid "not claimed" msgstr "" msgid "already knows this skill" msgstr "kender allerede denne evne" msgid "already has max skills" msgstr "har allerede maksimale evner" msgid "(already visited)" msgstr "(allerede besøgt)" msgid "(not visited)" msgstr "(ikke besøgt)" msgid "%{color} Barrier" msgstr "%{color} barriere" msgid "(tent visited)" msgstr "(telt besøgt)" msgid "%{color} Tent" msgstr "%{color} telt" msgid "Road" msgstr "Vej" msgid "(digging ok)" msgstr "(mulig gravning)" msgid "(no digging)" msgstr "(ingen gravning)" msgid "penalty: %{cost}" msgstr "Skridttab: %{cost}" msgid "Defenders:" msgstr "Forsvarere:" msgid "Unknown" msgstr "Ukendt" msgid "%{name} (Level %{level})" msgstr "%{name} (Level %{level})" msgid "Attack:" msgstr "Angreb:" msgid "Defense:" msgstr "Forsvar:" msgid "Spell Power:" msgstr "Magikraft:" msgid "Knowledge:" msgstr "Kundskab:" msgid "Spell Points:" msgstr "Mana:" msgid "Move Points:" msgstr "Skridtpoint:" msgid "Uncharted Territory" msgstr "Ukendt omrÃ¥de" msgid "Cost per troop:" msgstr "Pris per trop:" msgid "Available: %{count}" msgstr "Tilgængelig: %{count}" msgid "Number to buy:" msgstr "Købeantal:" msgid "Recruit selected monsters." msgstr "Rekruttér valgte væsener." msgid "Select maximum monsters to be recruited." msgstr "Vælg maksimalt antal væsener til rekruttering." msgid "Select only 1 monster to be recruited." msgstr "Vælg kun 1 væsen til rekruttering." msgid "Select Game Resolution:" msgstr "Vælg spilopløsning:" msgid "Click to apply the selected resolution." msgstr "Klik for at godkende valgte opløsning." msgid "Click to apply the entered text." msgstr "Klik for at anvende den indtastede tekst." msgid "Click to open the Virtual Keyboard dialog." msgstr "Klik for at Ã¥bne dialogboksen Virtuelt Tastatur." msgid "Open Virtual Keyboard" msgstr "Ã…ben Virtuel Tastatur" msgid "How many troops to move?" msgstr "Angiv antal per trop" msgid "Fast separation into slots:" msgstr "Hurtigfordel:" msgid "Map: " msgstr "Kort:" msgid "" "\n" "\n" "Month: " msgstr "" "\n" "\n" "MÃ¥ned: " msgid ", Week: " msgstr ", Uge: " msgid ", Day: " msgstr ", Dag: " msgid "" "\n" "\n" "Location: " msgstr "" "\n" "\n" "Placering: " msgid "Click to save the current game." msgstr "Klik for at gemme igangværende spil." msgid "Click to load a previously saved game." msgstr "Klik for at indlæse et tidligere gemt spil." msgid "Are you sure you want to delete file:" msgstr "Er du sikker pÃ¥ at du vil slette denne fil:" msgid "Warning!" msgstr "Advarsel!" msgid "File to Save:" msgstr "Fil som skal gemmes:" msgid "File to Load:" msgstr "Fil som skal indlæses:" msgid "Accept the choice made." msgstr "Acceptér de trufne valg." msgid "%{color} %{race} hero" msgstr "%{color} %{race} helt" msgid "Terrain object" msgstr "Terræn-objekt" msgid "Select Skill:" msgstr "Vælg evne:" msgid "Select Spell:" msgstr "Vælg magi:" msgid "Select Artifact:" msgstr "Vælg artefakt:" msgid "Select Monster:" msgstr "Vælg væsen:" msgid "Select Hero:" msgstr "Vælg helt:" msgid "Select Treasure:" msgstr "Vælg skat:" msgid "Select Ocean Object:" msgstr "Vælg hav-objekt:" #, fuzzy msgid "Castle/town placing" msgstr "Borg-vindue" msgid "doubleLinedRace|Neutral" msgstr "Neutral" msgid "race|Random" msgstr "Tilfældig" #, fuzzy msgid "Click to start placing the selected castle/town." msgstr "Klik for at godkende valgte opløsning." #, fuzzy msgid "Click to select town placing." msgstr "Klik for at se næste by." #, fuzzy msgid "Click to select castle placing." msgstr "Klik her for at vælge, hvilket kort du vil spille pÃ¥." #, fuzzy msgid "%{color} %{race} %{townOrCastle}" msgstr "%{color} %{race} helt" msgid "race|Neutral" msgstr "Neutral" msgid "You will place" msgstr "" #, fuzzy msgid "Click to select this class." msgstr "Klik her for at vælge, hvilket kort du vil spille pÃ¥." #, fuzzy msgid "Click to select this color." msgstr "Klik her for at vælge, hvilket kort du vil spille pÃ¥." #, fuzzy msgid "Select Dwelling:" msgstr "Vælg magi:" #, fuzzy msgid "Select Landscape Object:" msgstr "Vælg hav-objekt:" msgid "Mine placing" msgstr "" #, fuzzy msgid "" "Resource\n" "type:" msgstr "Resurse" #, fuzzy msgid "%{mineName} appearance:" msgstr "%{race}en %{name}" #, fuzzy msgid "Click to select %{object} as the resource generator to be placed." msgstr "Klik her for at vælge, hvilket kort du vil spille pÃ¥." #, fuzzy msgid "Select Mountain Object:" msgstr "Vælg hav-objekt:" #, fuzzy msgid "Select Rock Object:" msgstr "Vælg hav-objekt:" #, fuzzy msgid "Select Tree Object:" msgstr "Vælg hav-objekt:" #, fuzzy msgid "Select Power Up Object:" msgstr "Vælg hav-objekt:" #, fuzzy msgid "Select Adventure Object:" msgstr "Vælg hav-objekt:" msgid "Map Type:\n" msgstr "Korttype:\n" msgid "The Succession Wars" msgstr "Arvefølgekrigene" msgid "The Price of Loyalty" msgstr "Prisen for Loyalitet" #, fuzzy msgid "Resurrection" msgstr "Genoplivelse" msgid "Lose all your heroes and towns." msgstr "Mist alle dine helte og byer." msgid "Lose a specific town." msgstr "Mist en bestemt by." msgid "Lose a specific hero." msgstr "Mist en bestemt helt." msgid "Run out of time. Fail to win by a certain point." msgstr "Undlad at vinde, inden et bestemt antal dage er gÃ¥et." msgid "Loss Condition" msgstr "TabsvilkÃ¥r" msgid "Defeat all enemy heroes and towns." msgstr "Besejr alle fjendtlige helte og byer." msgid "Capture a specific town." msgstr "Indtag en bestemt by." msgid "Defeat a specific hero." msgstr "Besejr en bestemt helt." msgid "Find a specific artifact." msgstr "Find et bestemt artefakt." msgid "Your side defeats the opposing side." msgstr "Din side besejrer den modsatte." msgid "Accumulate a large amount of gold." msgstr "Saml en stor mængde guld." msgid "Victory Condition" msgstr "VindervilkÃ¥r" msgid "Map difficulty:" msgstr "Kort-niveau:" msgid "N" msgstr "N" msgid "No maps exist at that size" msgstr "Intet kort findes i den størrelse" msgid "Small Maps" msgstr "SmÃ¥ kort" msgid "View only maps of size small (36 x 36)." msgstr "Vis kun smÃ¥ kort (36 x 36)." msgid "Medium Maps" msgstr "Mellemstore kort" msgid "View only maps of size medium (72 x 72)." msgstr "Vis kun mellemstore kort (72 x 72)." msgid "Large Maps" msgstr "Store kort" msgid "View only maps of size large (108 x 108)." msgstr "Vis kun store kort (108 x 108)." msgid "Extra Large Maps" msgstr "Ekstrastore kort" msgid "View only maps of size extra large (144 x 144)." msgstr "Vis kun ekstra store kort (144 x 144)." msgid "All Maps" msgstr "Alle kort" msgid "View all maps, regardless of size." msgstr "Vis alle kort, uanset størrelse." msgid "Players Icon" msgstr "Spiller-ikon" msgid "" "Indicates how many players total are in the scenario. Any positions not " "occupied by humans will be occupied by computer players." msgstr "" "Angiver, hvor mange spillere der er i alt i scenariet. Alle positioner, der " "ikke er besat af mennesker, vil blive besat af computerspillere." msgid "" "Indicates whether the map\n" "is small (36 x 36), medium\n" "(72 x 72), large (108 x 108),\n" "or extra large (144 x 144)." msgstr "" "Angiver om kortet\n" "er lille (36 x 36), mellemstor\n" "(72 x 72), stor (108 x 108),\n" "eller ekstrastor (144 x 144)." msgid "Size Icon" msgstr "Størrelsesikon" msgid "" "Indicates whether the map is made for \"The Succession Wars\" or \"The Price " "of Loyalty\" version of the game." msgstr "" "Angiver om kortet er lavet til \"Arvefølgekrigene\"- eller \"Prisen for " "Loyalitet\"-versionen af spillet." msgid "Map Type" msgstr "Korttype" msgid "Selected Name" msgstr "Valgte navn" msgid "The name of the currently selected map." msgstr "Navnet pÃ¥ det valgte kort." msgid "Selected Map Difficulty" msgstr "Valgte kort-niveau" msgid "" "The map difficulty of the currently selected map. The map difficulty is " "determined by the scenario designer. More difficult maps might include more " "or stronger enemies, fewer resources, or other special conditions making " "things tougher for the human player." msgstr "" "Det nuværende kort-niveau for det valgte kort. Kortets sværhedsgrad er " "udelukkende bedømt af scenariets designer. Vanskeligere kort inkluderer " "flere og stærkere fjender, færre resurser eller andre særlige vilkÃ¥r, som " "gør det sværere for menneskelige spillere." msgid "Selected Description" msgstr "Valgte beskrivelse" msgid "The description of the currently selected map." msgstr "Beskrivelsen til det valgte kort." msgid "Jump" msgstr "Hop" msgid "Hero Speed" msgstr "Heltes hastighed" msgid "Don't Show" msgstr "Vis ikke" msgid "Enemy Speed" msgstr "Fjenders hastighed" msgid "Auto Resolve" msgstr "Fuldt auto" msgid "Auto, No Spells" msgstr "Auto, ingen magi" msgid "Battles" msgstr "Kamp" msgid "autoBattle|Manual" msgstr "Manuelt" msgid "Change the speed at which your heroes move on the main screen." msgstr "Vælg hvor hurtigt dine helte skal flytte sig pÃ¥ hovedskærmen." msgid "" "Sets the speed that A.I. heroes move at. You can also elect not to view A.I. " "movement at all." msgstr "" "Bestemmer hvor hurtigt AI-helte flytter sig. Du kan ogsÃ¥ vælge slet ikke at " "se pÃ¥, nÃ¥r de rør pÃ¥ sig." msgid "Change the interface settings of the game." msgstr "Ændr spillets brugerfladeindstillinger." msgid "Interface Settings" msgstr "Brugerfladeindstillinger" msgid "Toggle instant battle mode." msgstr "Vælg øjeblikkelig kamptilstand." msgid "Att." msgstr "Angr." msgid "Def." msgstr "Forsv." msgid "Power" msgstr "Magikr." msgid "Knowl" msgstr "Kunds." msgid "1st" msgstr "1." msgid "2nd" msgstr "2." msgid "3rd" msgstr "3." msgid "4th" msgstr "4." msgid "5th" msgstr "5." msgid "6th" msgstr "6." msgid "Oracle: Player Rankings" msgstr "Orakel: Spilleres rangering" msgid "Thieves' Guild: Player Rankings" msgstr "Tyvelaug: Spilleres rangering" msgid "Number of Towns:" msgstr "Antal byer:" msgid "Number of Castles:" msgstr "Antal borge:" msgid "Number of Heroes:" msgstr "Antal helte:" msgid "Gold in Treasury:" msgstr "Guld i statskassen:" msgid "Wood & Ore:" msgstr "Træ & malm:" msgid "Gems, Cr, Slf & Mer:" msgstr "Juveler, kr, sv & kv:" msgid "Obelisks Found:" msgstr "Besøgte obelisker:" msgid "Artifacts:" msgstr "Antal artefakter:" msgid "Total Army Strength:" msgstr "Samlet hærstyrke:" msgid "Income:" msgstr "Indkomst:" msgid "Best Hero:" msgstr "Bedste helt:" msgid "Best Hero Stats:" msgstr "Bedste helt (info):" msgid "Personality:" msgstr "Personlighed:" msgid "Best Monster:" msgstr "Bedste væsen:" #, fuzzy msgid "Map filename" msgstr "Kort størrelse" msgid "" "Are you sure you want to load a new map? (Any unsaved changes to the current " "map will be lost.)" msgstr "" "Er du sikker pÃ¥, at du vil indlæse et nyt kort? (Alle ikke-gemte ændringer " "pÃ¥ det nuværende kort, vil gÃ¥ tabt.)" msgid "" "Are you sure you want to create a new map? (Any unsaved changes to the " "current map will be lost.)" msgstr "" "Er du sikker pÃ¥, at du vil oprette et nyt kort? (Alle ikke-gemte ændringer " "af det nuværende kort, vil gÃ¥ tabt.)" msgid "" "Create a new map, either from scratch or using the random map generator." msgstr "" "Opret et nyt kort, enten fra bunden eller ved hjælp af kortgeneratoren." msgid "New Map" msgstr "Nyt kort" msgid "Load Map" msgstr "Indlæs kort" msgid "Load an existing map." msgstr "indlæs et eksisterende kort." msgid "Save Map" msgstr "Gem kort" msgid "Save the current map." msgstr "Gem nuværende kort." msgid "Quit out of the map editor." msgstr "Forlad korteditoren." msgid "Objects cannot be placed outside the map." msgstr "Objekter kan ikke placeres udenfor kortet." msgid "Monsters cannot be placed on water." msgstr "Monstre kan ikke placeres pÃ¥ vand." msgid "Choose a tile which does not contain any objects." msgstr "Vælg et felt, som ikke indeholder nogen objekter." msgid "Treasures cannot be placed on water." msgstr "Skatte kan ikke placeres pÃ¥ vand." msgid "Heroes cannot be placed on water." msgstr "Helte kan ikke placeres pÃ¥ vand." msgid "" "A maximum of %{count} heroes of the same color can be placed on the map." msgstr "" msgid "Artifacts cannot be placed on water." msgstr "Artefakter kan ikke placeres pÃ¥ vand." #, fuzzy msgid "Mountains cannot be placed on water." msgstr "Monstre kan ikke placeres pÃ¥ vand." #, fuzzy msgid "Rocks cannot be placed on water." msgstr "Monstre kan ikke placeres pÃ¥ vand." #, fuzzy msgid "Trees cannot be placed on water." msgstr "Skatte kan ikke placeres pÃ¥ vand." msgid "Ocean object must be placed on water." msgstr "Hav-objekter mÃ¥ kun placeres pÃ¥ vand." #, fuzzy msgid "Landscape objects cannot be placed on water." msgstr "Helte kan ikke placeres pÃ¥ vand." #, fuzzy msgid "Towns cannot be placed on water." msgstr "Monstre kan ikke placeres pÃ¥ vand." #, fuzzy msgid "Mines cannot be placed on water." msgstr "Monstre kan ikke placeres pÃ¥ vand." #, fuzzy msgid "Dwellings cannot be placed on water." msgstr "Monstre kan ikke placeres pÃ¥ vand." #, fuzzy msgid "Power-ups cannot be placed on water." msgstr "Monstre kan ikke placeres pÃ¥ vand." #, fuzzy msgid "Adventure objects cannot be placed on water." msgstr "Helte kan ikke placeres pÃ¥ vand." #, fuzzy msgid "Used to place %{object}." msgstr "Bruges til at placere en helt." #, fuzzy msgid "Select object type" msgstr "Vælg hav-objekt:" #, fuzzy msgid "" "Click here to\n" "select a monster." msgstr "Klik her for at vælge, hvilket kort du vil spille pÃ¥." #, fuzzy msgid "" "Click here to\n" "select another monster." msgstr "Vælg venligst en anden helt." msgid "" "Cell\n" "Details" msgstr "" msgid "Roads" msgstr "Veje" msgid "Streams" msgstr "Ã…-tilstand" #, fuzzy msgid "Erase" msgstr "Viskelæder" msgid "Mountains" msgstr "Bjerge" #, fuzzy msgid "Rocks" msgstr "Sten" msgid "Trees" msgstr "Træer" #, fuzzy msgid "Water Objects" msgstr "Ødemark-objekter" #, fuzzy msgid "Miscellaneous" msgstr "Fineas" msgid "Artifacts" msgstr "Artefakter" #, fuzzy msgid "Dwellings" msgstr "Halvlange" msgid "Mines" msgstr "Miner" #, fuzzy msgid "Power-ups" msgstr "Magikr." msgid "Treasures" msgstr "Skatte" msgid "Towns" msgstr "Byer" msgid "Heroes" msgstr "Helte" msgid "editorErasure|Terrain" msgstr "" msgid "editorErasure|Castles" msgstr "" msgid "editorErasure|Monsters" msgstr "" msgid "editorErasure|Heroes" msgstr "" msgid "editorErasure|Artifacts" msgstr "" msgid "editorErasure|Roads" msgstr "" msgid "editorErasure|Streams" msgstr "" msgid "editorErasure|Treasures" msgstr "" msgid "" "Draws terrain in\n" "%{size} by %{size} square increments." msgstr "" "Tegner terræn indenfor en firkant med et størrelsesforhold pÃ¥\n" "%{size}x%{size}." msgid "" "Erases objects in\n" "%{size} by %{size} square increments." msgstr "" "Fjerner alle ting indenfor en firkant med et størrelsesforhold pÃ¥\n" "%{size}x%{size}." msgid "Small Brush" msgstr "Lille pensel" msgid "Medium Brush" msgstr "Mellemstor pensel" msgid "Large Brush" msgstr "Stor pensel" msgid "Area Fill" msgstr "OmrÃ¥deudfyldning" msgid "Used to click and drag for filling in large areas." msgstr "" "Bruges til at fylde omrÃ¥der, ved at klikke og trække musen henover dem." msgid "Clear Area" msgstr "Tøm omrÃ¥de" msgid "Used to click and drag for clearing large areas." msgstr "" "Bruges til at tømme omrÃ¥der, ved at klikke og trække musen henover dem." msgid "" "Costs %{rate} times normal movement for all heroes. (Pathfinding reduces or " "eliminates the penalty.)" msgstr "" "Koster %{rate}x normal skridtpoint for alle helte. (Stifinding reducerer " "eller eliminerer straffen.)" msgid "Traversable only by boat." msgstr "Farbar kun ved sejlads." msgid "No special modifiers." msgstr "Ingen særlige pÃ¥virkninger." msgid "Toggle the erasure of %{type} objects." msgstr "Vælg sletning af %{type}objekter til og fra." msgid "" "Objects of this type will be deleted with the Erase tool. Left-click here to " "deselect this type. Press and hold this button to deselect all other object " "types." msgstr "" "Objekter af denne type vil blive slettet med sletteværktøjet. Venstre-klik " "her for at vælge denne type. Hold knappen nede, for at fravælge alle andre " "objekttyper." msgid "" "Objects of this type will NOT be deleted with the Erase tool. Left-click " "here to select this type. Press and hold this button to select all other " "object types." msgstr "" "Objekter af denne type vil IKKE blive slettet med sletteværktøjet. Venstre-" "klik her for at vælge type. Hold denne knap nede, for at vælge alle andre " "objekttyper." msgid "Terrain Mode" msgstr "Terræn-tilstand" msgid "Used to draw the underlying grass, dirt, water, etc. on the map." msgstr "" "Bruges til at tegne det underliggende græs, jord, vand, osv. pÃ¥ kortet." #, fuzzy msgid "Landscape Objects Mode" msgstr "Objekt-tilstand" #, fuzzy msgid "" "Used to place landscape objects (mountains, rocks, trees, etc.) on the map." msgstr "" "Bruges til at placere objekter (bjerge, træer, skatte, osv.) pÃ¥ kortet." msgid "Detail Mode" msgstr "Detalje-tilstand" msgid "Used for special editing of monsters, heroes and towns." msgstr "Bruges til specialredigering af væsener, helte og byer." #, fuzzy msgid "Adventure Objects Mode" msgstr "Eventyrindstillinger" #, fuzzy msgid "" "Used to place adventure objects (artifacts, dwellings, mines, treasures, " "etc.) on the map." msgstr "" "Bruges til at placere objekter (bjerge, træer, skatte, osv.) pÃ¥ kortet." #, fuzzy msgid "Kingdom Objects Mode" msgstr "Objekt-tilstand" #, fuzzy msgid "Used to place kingdom objects (towns, castles and heroes) on the map." msgstr "" "Bruges til at placere objekter (bjerge, træer, skatte, osv.) pÃ¥ kortet." #, fuzzy msgid "Monsters Mode" msgstr "Monstre" #, fuzzy msgid "Used to place monsters on the map." msgstr "" "Bruges til at placere\n" "en gruppe væsener" msgid "Allows you to draw streams by clicking and dragging." msgstr "Lader dig tegne Ã¥er ved at klikke og trække i dem." msgid "Stream Mode" msgstr "Ã…-tilstand" msgid "Allows you to draw roads by clicking and dragging." msgstr "Lader dig tegne stier ved at klikke og trække i dem." msgid "Road Mode" msgstr "Vej-tilstand" msgid "Erase Mode" msgstr "Viskelæder" msgid "Used to erase objects from the map." msgstr "Bruges til at fjerne objekter fra kortet." msgid "Change between zoom and normal view." msgstr "Vælg mellem normal og forstørret visning." msgid "Magnify" msgstr "Forstør" msgid "Undo" msgstr "Fortryd" msgid "Undo your last action." msgstr "Fortryd din sidste handling." msgid "Create a new map either from scratch or using the random map generator." msgstr "" "Opret et nyt kort, enten fra bunden eller ved hjælp af den automatiske " "kortgenerator." msgid "Edit map title, description, and other general information." msgstr "Rediger kortets navn, beskrivelse eller andre informationer." msgid "Specifications" msgstr "Specifikationer" msgid "File Options" msgstr "Filindstillinger" msgid "" "Open the file options menu, where you can save or load maps, or quit out of " "the editor." msgstr "" "Frembring filindstillingsmenuen, som tillader dig at gemme/indlæse kort " "eller afslutte Korteditoren." msgid "View the editor system options, which let you customize the editor." msgstr "Vis korteditorens systemindstillinger, som lader dig tilpasse den." msgid "Create a map that is %{size} squares wide and %{size} squares high." msgstr "Opret et kort, som er %{size} kvadrater høj og %{size} kvadrater bred." msgid "Cancel back to the New Map menu." msgstr "Afbryd og gÃ¥ tilbage til hovedmenuen for Nyt-kort." msgid "Cancel back to the main menu." msgstr "Afbryd og gÃ¥ tilbage til hovedmenuen." msgid "From Scratch" msgstr "Fra bunden" msgid "Start from scratch with a blank map." msgstr "Begynd pÃ¥ et kort helt fra bunden." msgid "Create a randomly generated map." msgstr "Opret et kort automatisk." msgid "Random" msgstr "Tilfældigt" msgid "Cancel back to the Map Editor main menu." msgstr "Afbryd og gÃ¥ tilbage til Korteditorens hovedmenu." msgid "No maps available!" msgstr "Intet kort tilgængelig!" msgid "Warning" msgstr "Advarsel" msgid "difficulty|Easy" msgstr "Let" msgid "difficulty|Normal" msgstr "Normal" msgid "difficulty|Hard" msgstr "Svær" msgid "difficulty|Expert" msgstr "Ekspert" msgid "difficulty|Impossible" msgstr "Umulig" msgid "and more..." msgstr "og mere..." msgid "Easy" msgstr "Let" msgid "Hard" msgstr "Svær" msgid "Campaign Difficulty" msgstr "Felttogsniveau" msgid "" "Choose this difficulty if you want to prefer game story over challenge. AI " "is weaker in comparison with normal difficulty." msgstr "" "Vælg dette niveau, hvis du foretrækker spilhistorie frem for udfordring. AI " "er svagere sammenlignet med niveauet normal." msgid "" "Choose this difficulty to enjoy the campaign as per the original design." msgstr "" "Vælg dette niveau for at nyde felttoget i henhold til det originale design." msgid "" "Choose this difficulty if you want challenge. AI is stronger in comparison " "with normal difficulty." msgstr "" "Vælg dette niveau, hvis du ønsker udfordring. AI er stærkere sammenlignet " "med normal niveau." msgid "" "Congratulations!\n" "\n" "Days: %{days}\n" msgstr "" "Tillykke!\n" "\n" "Dage: %{days}\n" msgid "" "\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" "\n" "Niveau: %{difficulty}\n" "\n" msgid "" "Score: %{score}\n" "\n" "Rating:\n" "%{rating}" msgstr "" "Score: %{score}\n" "\n" "Bedømmelse:\n" "%{rating}" msgid "Start the selected scenario." msgstr "Start det valgte scenarie." msgid "View Intro" msgstr "Vis intro" msgid "View Intro videos for the current state of the campaign." msgstr "Vis intro videoer for felttogets nuværende stadie." msgid "" "Select the campaign difficulty. This can be lowered at any point during the " "campaign." msgstr "" "Vælg felttogets niveau. Dette kan sænkes nÃ¥r som helst under felttoget." msgid "Restart the current scenario." msgstr "Genstart det igangværende scenarie." msgid "Difficulty" msgstr "Kort-niveau:" msgid "" "You have changed to a lower difficulty for the campaign. You will not be " "able to revert this after this point. The high score will be calculated " "based solely on the new difficulty. Do you want to proceed?" msgstr "" "Du har ændret felttogets sværhedsgrad til et lavere niveau. Du vil ikke være " "i stand til at fortryde dette efter dette tidspunkt. Højscoren beregnes " "udelukkende pÃ¥ baggrund af den nye sværhedsgrad. Vil du fortsætte?" msgid "Are you sure you want to restart this scenario?" msgstr "Er du sikker pÃ¥ du vil genstarte dette scenarie?" msgid "Campaign Scenario loading failure" msgstr "Indlæsningsfejl af felttogs-scenarie" msgid "Please make sure that campaign files are correct and present." msgstr "Sørg for, at felttogsfilerne er korrekte og tilstede." msgid "Days spent" msgstr "Dage brugt" msgid "The number of days spent on this campaign." msgstr "Antal dage brugt pÃ¥ dette felttog." msgid "Project Coordination and Core Development" msgstr "Projektkoordination og kerneudvikling" msgid "Development" msgstr "Udvikling" msgid "Visit us at " msgstr "Besøg os pÃ¥ " msgid "QA and Support" msgstr "QA og support" msgid "Dev and Support" msgstr "Udv. og support" msgid "Special Thanks to" msgstr "Særlig tak til" msgid "and many other contributors!" msgstr "og mange andre bidragere!" msgid "and many-many other supporters!" msgstr "og mange-mange andre tilhængere!" msgid "Support us at" msgstr "Støt os pÃ¥" msgid "local-donation-platform|https://www.patreon.com/fheroes2" msgstr "local-donation-platform|https://www.patreon.com/fheroes2" msgid "Connect with us at" msgstr "Kontakt os pÃ¥" msgid "local-social-network|https://www.facebook.com/groups/fheroes2" msgstr "local-social-network|https://www.facebook.com/groups/fheroes2" msgid "Need help with the game?" msgstr "Brug for hjælp til spillet?" msgid "Original project before 0.7" msgstr "Originale projekt før 0.7" msgid "Heroes of Might and Magic II: The Succession Wars team" msgstr "Teamet bag Heroes of Might and Magic II: The Succession Wars" msgid "Designed and Directed" msgstr "Designet og instrueret" msgid "Programming and Design" msgstr "Programmering og design" msgid "Executive Producer" msgstr "Executive Producer" msgid "Producer" msgstr "Producer" msgid "Additional Design" msgstr "Ekstra design" msgid "Additional Programming" msgstr "Ekstra programmering" msgid "Musical Production" msgstr "Musikproduktion" msgid "Music and Sound Design" msgstr "Musik- og lyddesign" msgid "Vocalists" msgstr "Vokalister" msgid "Art Director" msgstr "Art Director" msgid "Assistant Art Director" msgstr "Assisterende Art Director" msgid "Artists" msgstr "Kunstnere" msgid "QA Manager" msgstr "QA Manager" msgid "QA" msgstr "QA" msgid "Writing" msgstr "Skribent" msgid "Manual and Helpfile" msgstr "Manual og hjælpefil" msgid "Scenarios" msgstr "Scenarier" msgid "Heroes of Might and Magic II: The Price of Loyalty team" msgstr "Teamet bag Heroes of Might and Magic II: The Price of Loyalty" msgid "Design Lead" msgstr "Design-leder" msgid "Designers" msgstr "Designere" msgid "Programming Lead" msgstr "Programmeringsleder" msgid "Art Lead" msgstr "Kunstleder" msgid "Playtesters" msgstr "Spiltestere" msgid "Designer" msgstr "Designer" msgid "Producers" msgstr "Producere" msgid "QA Managers" msgstr "QA Managere" msgid "Sound Design" msgstr "Lyddesign" msgid "Town Themes" msgstr "Byernes musiktemaer" msgid "Alto Sax" msgstr "Altsax" msgid "Harpsichord and Piano" msgstr "Cembalo og klaver" msgid "Basso Vocal" msgstr "Bass vokal" msgid "Soprano Vocal" msgstr "Sopran vokal" msgid "Recorded at %{recordingStudio}" msgstr "Indspillet hos %{recordingStudio}" msgid "credits|Manual" msgstr "Manual" msgid "German Consultant" msgstr "Tysk rÃ¥dgiver" msgid "Map Designers" msgstr "Kortdesignere" msgid "Package Design" msgstr "Pakkedesign" msgid "Your Name" msgstr "Dit navn" msgid "Unknown Hero" msgstr "Ukendt helt" msgid "Standard" msgstr "Standard" msgid "View High Scores for Standard Maps." msgstr "Vis højeste score for enkelt spiller kort." msgid "Campaign" msgstr "Felttog" msgid "View High Scores for Campaigns." msgstr "Vis højeste score for enkelt spiller kort." msgid "hotkey|default okay event" msgstr "Bekræft" msgid "hotkey|default cancel event" msgstr "Afbryd" msgid "hotkey|default left" msgstr "Venstre" msgid "hotkey|default right" msgstr "Højre" msgid "hotkey|default up" msgstr "Op" msgid "hotkey|default down" msgstr "Ned" msgid "hotkey|toggle fullscreen" msgstr "Fuldskærm" msgid "hotkey|toggle text support mode" msgstr "Tekstunderstøttelsestilstand" msgid "hotkey|new game" msgstr "Nyt spil" msgid "hotkey|load game" msgstr "Indlæs spil" msgid "hotkey|highscores" msgstr "Ranglister" msgid "hotkey|credits" msgstr "Kreditter" msgid "hotkey|standard game" msgstr "Enkeltspillerspil" msgid "hotkey|campaign game" msgstr "Felttog" msgid "hotkey|multi-player game" msgstr "Flerspillerspil" msgid "hotkey|settings" msgstr "Indstillinger" msgid "hotkey|quit" msgstr "Forlad" msgid "hotkey|select map" msgstr "Vælg kort" msgid "hotkey|select small map size" msgstr "Vælg lille kortstørrelse" msgid "hotkey|select medium map size" msgstr "Vælg mellemstor kortstørrelse" msgid "hotkey|select large map size" msgstr "Vælg stor kortstørrelse" msgid "hotkey|select extra large map size" msgstr "Vælg ekstrastor kortstørrelse" msgid "hotkey|select all map sizes" msgstr "Vælg alle kortstørrelser" msgid "hotkey|hotseat game" msgstr "Lokalt spil" msgid "hotkey|battle only game" msgstr "Kun-kamp spil" msgid "hotkey|choose the original campaign" msgstr "Vælg det originale felttog" msgid "hotkey|choose the expansion campaign" msgstr "Vælg udvidelsens felttog" msgid "hotkey|map editor main menu" msgstr "Korteditorens hovedmenu" msgid "hotkey|new map menu" msgstr "Nyt-kort menu" msgid "hotkey|load map menu" msgstr "Indlæs-kort menu" msgid "hotkey|new map from scratch" msgstr "Nyt blankt kort" msgid "hotkey|new random map" msgstr "Generer nyt kort" msgid "hotkey|undo last action" msgstr "Fortryd forrige handling" msgid "hotkey|redo last action" msgstr "Gentag forrige handling" msgid "hotkey|roland campaign" msgstr "Rolands felttog" msgid "hotkey|archibald campaign" msgstr "Archibalds felttog" msgid "hotkey|the price of loyalty campaign" msgstr "Felttoget Prisen for Loyalitet" msgid "hotkey|voyage home campaign" msgstr "Felttoget Hjemrejse" msgid "hotkey|wizard's isle campaign" msgstr "Felttoget Troldmændenes Ø" msgid "hotkey|descendants campaign" msgstr "Felttoget Efterkommere" msgid "hotkey|select first campaign bonus" msgstr "Vælg felttogsbonus nr.1" msgid "hotkey|select second campaign bonus" msgstr "Vælg felttogsbonus nr.2" msgid "hotkey|select third campaign bonus" msgstr "Vælg felttogsbonus nr.3" msgid "hotkey|view campaign intro" msgstr "Vis felttogets intro" msgid "hotkey|select campaign difficulty" msgstr "Vælg felttogets sværhedsgrad" msgid "hotkey|restart campaign scenario" msgstr "Genstart felttogsscenarie" msgid "hotkey|world map left" msgstr "Verdenskort venstre" msgid "hotkey|world map right" msgstr "Verdenskort højre" msgid "hotkey|world map up" msgstr "Verdenskort op" msgid "hotkey|world map down" msgstr "Verdenskort ned" msgid "hotkey|world map up left" msgstr "Verdenskort op til venstre" msgid "hotkey|world map up right" msgstr "Verdenskort op til højre" msgid "hotkey|world map down left" msgstr "Verdenskort ned til venstre" msgid "hotkey|world map down right" msgstr "Verdenskort ned til højre" msgid "hotkey|save game" msgstr "Gem spil" msgid "hotkey|next hero" msgstr "Næste helt" msgid "hotkey|start hero movement" msgstr "Fortsæt heltebevægelse" msgid "hotkey|cast adventure spell" msgstr "Kast eventyrmagi" msgid "hotkey|put hero to sleep" msgstr "Sæt helt i dvale" msgid "hotkey|next town" msgstr "Næste by" msgid "hotkey|end turn" msgstr "End tur" msgid "hotkey|file options" msgstr "Filindstillinger" msgid "hotkey|adventure options" msgstr "Eventyrindstillinger" msgid "hotkey|puzzle map" msgstr "Puslespilskort" msgid "hotkey|scenario information" msgstr "Scenarieinfo" msgid "hotkey|dig for artifact" msgstr "Grav efter artefakt" msgid "hotkey|view world" msgstr "Vis verden" msgid "hotkey|kingdom summary" msgstr "Oversigt over kongedømmet" msgid "hotkey|default action" msgstr "Standardhandling" msgid "hotkey|open focus" msgstr "Fokusér pÃ¥ helt" msgid "hotkey|system options" msgstr "Tilpas systemindstillinger" msgid "hotkey|scroll left" msgstr "Rul til venstre" msgid "hotkey|scroll right" msgstr "Rul til højre" msgid "hotkey|scroll up" msgstr "Rul opad" msgid "hotkey|scroll down" msgstr "Rul nedad" msgid "hotkey|toggle control panel" msgstr "Kontrolpanel (til/fra)" msgid "hotkey|toggle radar" msgstr "Minikort (til/fra)" msgid "hotkey|toggle buttons" msgstr "Knapper (til/fra)" msgid "hotkey|toggle status" msgstr "Status (til/fra)" msgid "hotkey|toggle icons" msgstr "Ikoner (til/fra)" msgid "hotkey|transfer control to ai" msgstr "Overgiv kontrol til en AI" msgid "hotkey|retreat from battle" msgstr "Tilbagetog fra kamp" msgid "hotkey|surrender during battle" msgstr "Overgivelse i kamp" msgid "hotkey|toggle battle auto mode" msgstr "Autokamp (til/fra)" msgid "hotkey|finish the battle in auto mode" msgstr "Færdiggør kampen automatisk" msgid "hotkey|battle options" msgstr "Kampindstillinger" msgid "hotkey|skip turn in battle" msgstr "Skift tur i kamp" msgid "hotkey|cast battle spell" msgstr "Kast kampmagi" msgid "hotkey|dwelling level 1" msgstr "Bosted niveau 1" msgid "hotkey|dwelling level 2" msgstr "Bosted niveau 2" msgid "hotkey|dwelling level 3" msgstr "Bosted niveau 3" msgid "hotkey|dwelling level 4" msgstr "Bosted niveau 4" msgid "hotkey|dwelling level 5" msgstr "Bosted niveau 5" msgid "hotkey|dwelling level 6" msgstr "Bosted niveau 6" msgid "hotkey|well" msgstr "Brønd" msgid "hotkey|marketplace" msgstr "Markedsplads" msgid "hotkey|mage guild" msgstr "Magikerlaug" msgid "hotkey|shipyard" msgstr "Skibsværft" msgid "hotkey|thieves guild" msgstr "Tyvelaug" msgid "hotkey|tavern" msgstr "Kro" msgid "hotkey|construction screen" msgstr "Borg-vindue" msgid "hotkey|buy all monsters in well" msgstr "Rekruttér alle væsener fra brønden" msgid "hotkey|split stack by half" msgstr "Del tropper i to" msgid "hotkey|split stack by one" msgstr "Tag én enhed ud af en trop" msgid "hotkey|join stacks" msgstr "Foren tropper" msgid "hotkey|upgrade troop" msgstr "Opgradér trop" msgid "hotkey|dismiss hero or troop" msgstr "Bortvis helt eller trop" msgid "" "Do you want to regain control from AI? The effect will take place only on " "the next turn." msgstr "" "Vil du genoptage kontrollen fra AI'en? Dette vil kun have effekt pÃ¥ næste " "tur." msgid "" "Do you want to transfer control from you to the AI? The effect will take " "place only on the next turn." msgstr "" "Vil du lade AI'en overtage kontrollen? Effekten vil først træde i kraft " "under næste tur." msgid "Default Actions" msgstr "Standardhandlinger" msgid "Global Actions" msgstr "Globale Handlinger" msgid "Main Menu" msgstr "Hovedmenu" msgid "World Map" msgstr "Verdenskort" msgid "Battle Screen" msgstr "Kamp-vindue" msgid "Town Screen" msgstr "Borg-vindue" msgid "Army Actions" msgstr "Hær-handlinger" msgid "Editor" msgstr "Editor" msgid "The save file is corrupted." msgstr "Det gemte spil er korrupt." msgid "Unsupported save format: " msgstr "Ikke-understøttet gem-format: " msgid "Current game version: " msgstr "Nuværende spilversion: " msgid "Last supported version: " msgstr "Sidste understøttede version: " msgid "This file contains a save with an invalid game type." msgstr "Denne fil indeholder et gemt spil af en invalid spiltype." #, fuzzy msgid "" "This save file requires \"The Price of Loyalty\" game assets, but they have " "not been provided to the engine." msgstr "" "Denne fil blev gemt som et \"Prisen for loyalitet\" kort, men de tilsvarende " "spilaktiver er ikke blevet leveret til spilmotoren." msgid "This saved game is localized to '" msgstr "Dette gemte spil er lokaliseret til '" msgid "' language, but the current language of the game is '" msgstr "' sprog, men spillets nuværende sprog er '" msgid "Hot Seat" msgstr "Lokalt spil" msgid "" "Play a Hot Seat game, where 2 to 4 players play around the same computer, " "switching into the 'Hot Seat' when it is their turn." msgstr "" "Spil et lokalt spil, hvor 2-4 spillere skiftes til at spille sin tur pÃ¥ den " "samme enhed." msgid "A single player game playing out a single map." msgstr "Et enkeltspillerspil, der gennemspilles pÃ¥ ét enkelt kort." msgid "Standard Game" msgstr "Enkelt spil" msgid "A single player game playing through a series of maps." msgstr "Et enkeltspillerspil, der gennemspilles pÃ¥ en serie af kort." msgid "Campaign Game" msgstr "Felttog spil" msgid "Multi-Player Game" msgstr "Flerspiller spil" msgid "" "A multi-player game, with several human players completing against each " "other on a single map." msgstr "" "Et flerspillerspil, hvor flere menneskelige spillere konkurrerer mod " "hinanden pÃ¥ ét enkelt kort." msgid "fheroes2 Resurrection Team presents" msgstr "Teamet bag fheroes2 Resurrection præsenterer" msgid "Greetings!" msgstr "Vær hilset!" msgid "Welcome to Heroes of Might and Magic II powered by fheroes2 engine!" msgstr "" "Velkommen til Heroes of Might and Magic II, drevet af fheroes2-motoren!" msgid "" "Welcome to Heroes of Might and Magic II powered by the fheroes2 engine!\n" "Before starting the game please select a game resolution." msgstr "" "Velkommen til Heroes of Might and Magic II, drevet af fheroes2-motoren!\n" "Inden du starter spillet, skal du vælge spilopløsning." msgid "Please Remember" msgstr "Husk venligst" msgid "" "You can always change the language, resolution and settings of the game by " "clicking on the " msgstr "" "Du kan altid ændre sprog, spilopløsning eller -indstillinger ved at klikke " "pÃ¥ " msgid "door" msgstr "døren" msgid " on the left side of the Main Menu, or with the " msgstr " pÃ¥ venstre side a hovedmenuen, eller med denne " msgid " button from the " msgstr " knap fra " msgid "NEW GAME" msgstr "NYT SPIL" msgid "" " menu. \n" "\n" "Enjoy the game!" msgstr "" " menu. \n" "\n" "Nyd spillet!" msgid "The Editor requires \"The Price of Loyalty\" expansion files to work." msgstr "" "Editoren kræver filerne fra \"Prisen for Loyalitet\"-udvidelsen, for at " "virke." msgid "Quit Heroes of Might and Magic II and return to the operating system." msgstr "" "Afslut Heroes of Might and Magic II og vend tilbage til operativsystemet." msgid "Credits" msgstr "Kreditter" msgid "View the credits screen." msgstr "Se kreditskærmen." msgid "High Scores" msgstr "Ranglister" msgid "View the high scores screen." msgstr "Vis skærmen for højeste score." msgid "Change language, resolution and settings of the game." msgstr "Skift spillets sprog, opløsning og indstillinger." msgid "Game Settings" msgstr "Spilindstillinger" msgid "" "Required video files for campaign selection window are missing. Please make " "sure that all necessary files are present in the system." msgstr "" "Nødvendige videofiler til felttogsvalgsvinduet mangler. Sørg for, at alle " "nødvendige filer er til stede i systemet." msgid "" "Either Roland's or Archibald's campaign from the original Heroes of Might " "and Magic II." msgstr "" "Enten Rolands eller Archibalds felttog fra den originale Heroes of Might and " "Magic II." msgid "Original Campaign" msgstr "Originalt felttog" msgid "Expansion Campaign" msgstr "Udvidelse-felttog" msgid "One of the four new campaigns from the Price of Loyalty expansion set." msgstr "En af de fire nye felttog fra Prisen for loyalitet-udvidelsessættet." msgid "Loading video. Please wait..." msgstr "Indlæser video. Vent venligst..." msgid "Host" msgstr "Vært" msgid "" "The host sets up the game options. There can only be one host per network " "game." msgstr "" "Værten opsætter spilmulighederne. Der kan kun være én vært per netværksspil." msgid "Guest" msgstr "Gæst" msgid "" "The guest waits for the host to set up the game, then is automatically added " "in. There can be multiple guests for TCP/IP games." msgstr "" "Gæsten venter pÃ¥, at værten sætter spillet op, og tilføjes derefter " "automatisk. Der kan være flere gæster til TCP/IP-spil." msgid "Battle Only" msgstr "Kun kamp" msgid "Setup and play a battle without loading any map." msgstr "Opsæt og spil en kamp uden at indlæse et kort." msgid "2 Players" msgstr "2 spillere" msgid "" "Play with 2 human players, and optionally, up to 4 additional computer " "players." msgstr "" "Spil med 2 menneskelige spillere og eventuelt op til 4 ekstra " "computerspillere." msgid "3 Players" msgstr "3 spillere" msgid "" "Play with 3 human players, and optionally, up to 3 additional computer " "players." msgstr "" "Spil med 3 menneskelige spillere og eventuelt op til 3 ekstra " "computerspillere." msgid "4 Players" msgstr "4 spillere" msgid "" "Play with 4 human players, and optionally, up to 2 additional computer " "players." msgstr "" "Spil med 4 menneskelige spillere og eventuelt op til 2 ekstra " "computerspillere." msgid "5 Players" msgstr "5 spillere" msgid "" "Play with 5 human players, and optionally, up to 1 additional computer " "player." msgstr "" "Spil med 5 menneskelige spillere og eventuelt 1 ekstra computerspillere." msgid "6 Players" msgstr "6 spillere" msgid "Play with 6 human players." msgstr "Spil med 2 menneskelige spillere." msgid "Dragon city has fallen! You are now the Master of the Dragons." msgstr "Dragebyen er faldet! Du er nu dragernes mester!" msgid "" "You captured %{name}!\n" "You are victorious." msgstr "" "Du fangede %{name}!\n" "Du har sejret!" msgid "" "You have captured the enemy hero %{name}!\n" "Your quest is complete." msgstr "" "Du har fanget fjendens helt %{name}!\n" "Din opgave er fuldført!" msgid "" "You have found the %{name}.\n" "Your quest is complete." msgstr "" "Du har fundet %{name}!\n" "Din opgave er fuldført!" msgid "Ultimate Artifact" msgstr "Det Ultimative Artefakt" msgid "" "The enemy is beaten.\n" "Your side has triumphed!" msgstr "" "Fjenden er slÃ¥et!\n" "Din side har sejret!" msgid "" "You have built up over %{count} gold in your treasury.\n" "All enemies bow before your wealth and power." msgstr "" "Du har opbygget over %{count} guldstykker i dit skatkammer.\n" "Alle fjender bøjer sig for din rigdom og magt!" msgid "Victory!" msgstr "Sejr!" msgid "" "The enemy has captured %{name}!\n" "They are triumphant." msgstr "" "Fjenden har erobret %{name}!\n" "De har sejret!" msgid "" "The enemy has built up over %{count} gold in his treasury.\n" "You must bow done in defeat before his wealth and power." msgstr "" "Fjenden har opbygget over %{count} guldstykker i sit skatkammer.\n" "Med dette nederlag skal du nu bøje dig for hans rigdom og magt!" msgid "You have been eliminated from the game!!!" msgstr "Du er blevet slÃ¥et ud af spillet!!!" msgid "" "You have lost the hero %{name}.\n" "Your quest is over." msgstr "" "Du har mistet helten %{name}.\n" "Fjendens opgave er fuldført!" msgid "" "You have failed to complete your quest in time.\n" "All is lost." msgstr "" "Du fuldførte ikke din opgave i tide.\n" "Alt er tabt!" msgid "Defeat!" msgstr "Nederlag!" msgid "" "Base score: %{score}\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" "Grundscore: %{score}\n" "Niveau: %{difficulty}\n" "\n" msgid "Defeat all enemy heroes and capture all enemy towns and castles." msgstr "Besejr alle fjendens helte og erobr alle fjendens borge og byer." msgid "Run out of time. (Fail to win by a certain point.)" msgstr "Løb tør for tid. (Du kan ikke vinde efter et bestemt antal dage)" msgid "You must defeat the enemy %{enemies}." msgid_plural "You must defeat the enemy alliance of %{enemies}." msgstr[0] "Du skal besejre fjenden %{enemies}." msgstr[1] "Du skal besejre fjendens alliance af %{enemies}." msgid "" "The alliance consisting of %{allies} and you must defeat the enemy " "%{enemies}." msgid_plural "" "The alliance consisting of %{allies} and you must defeat the enemy alliance " "of %{enemies}." msgstr[0] "" "Alliancen bestÃ¥r af %{allies}, og du skal besejre fjenden %{enemies}." msgstr[1] "" "Alliancen bestÃ¥r af %{allies}, og du skal besejre fjendens alliance af " "%{enemies}." msgid "Capture the castle '%{name}'." msgstr "Erobr borgen '%{name}'." msgid "Capture the town '%{name}'." msgstr "Erobr byen '%{name}'." msgid "Defeat the hero '%{name}'." msgstr "Besejr helten '%{name}'." msgid "Find the ultimate artifact." msgstr "Find Det Ultimative Artefakt." msgid "Find the '%{name}' artifact." msgstr "Find artefaktet '%{name}'." msgid "Accumulate %{count} gold." msgstr "Saml %{count} guldstykker." msgid "" ", or you may win by defeating all enemy heroes and capturing all enemy towns " "and castles." msgstr "" ", eller du kan vinde ved at besejre alle fjendens helte og erobre alle " "fjendens byer og borge." msgid "Lose the castle '%{name}'." msgstr "Mist borgen '%{name}'." msgid "Lose the town '%{name}'." msgstr "Mist byen '%{name}'." msgid "Lose the hero: %{name}." msgstr "Mist helten '%{name}'." msgid "Fail to win by the end of month %{month}, week %{week}, day %{day}." msgstr "" "Du taber, hvis du ikke vinder inden udgangen af mÃ¥ned %{month}, uge%{week}, " "dag %{day}." msgid "%{color} player has been vanquished!" msgstr "%{color} spiller er blevet besejret!" msgid "Major Event!" msgstr "Stor begivenhed!" msgid "Scenario:" msgstr "Kort:" msgid "Game Difficulty:" msgstr "Spil-niveau:" msgid "Opponents:" msgstr "Spillere:" msgid "Class:" msgstr "Klasse:" msgid "Rating %{rating}%" msgstr "Bedømmelse %{rating}%" msgid "Click here to select which scenario to play." msgstr "Klik her for at vælge, hvilket kort du vil spille pÃ¥." msgid "Scenario" msgstr "Kort" msgid "Game Difficulty" msgstr "Spil-niveau" msgid "" "This lets you change the starting difficulty at which you will play. Higher " "difficulty levels start you of with fewer resources, and at the higher " "settings, give extra resources to the computer." msgstr "" "Dette lader dig ændre startsværhedsgraden, som du vil spille med. Højere " "niveauer starter dig med færre ressourcer, og ved de højere indstillinger " "giver du ekstra resurser til computeren." msgid "Difficulty Rating" msgstr "Niveau-bedømmelse" msgid "" "The difficulty rating reflects a combination of various settings for your " "game. This number will be applied to your final score." msgstr "" "Sværhedsgraden afspejler en kombination af forskellige indstillinger for dit " "spil. Dette tal vil blive anvendt pÃ¥ din endelige score." msgid "Click to accept these settings and start a new game." msgstr "Klik for at acceptere disse indstillinger og start et nyt spil." msgid "Click to return to the main menu." msgstr "Klik her for at vende tilbage til hovedmenuen." msgid "Astrologers proclaim the Month of the %{name}." msgstr "Astrologer kundgør, at mÃ¥nedens væsen er %{name}." msgid "Astrologers proclaim the Week of the %{name}." msgstr "Astrologer kundgør, at ugens væsen er %{name}." msgid "After regular growth, the population of %{monster} is doubled!" msgstr "Efter regelmæssig tilvækst er befolkningen pÃ¥ %{monster} fordoblet!" msgid "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgid_plural "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgstr[0] "" "Efter regelmæssig tilvækst stiger befolkningen af %{monster} med %{count} " "procent!" msgstr[1] "" "Efter regelmæssig vækst stiger befolkningen af %{monster} med %{count} " "procent!" msgid "%{monster} growth +%{count}." msgstr "" "Tilvækst: +%{count}\n" "%{monster}" msgid " All populations are halved." msgstr "Hele befolkningen er halveret." msgid " All dwellings increase population." msgstr "Alle bosteder fÃ¥r øget deres befolkning." msgid "New Month!" msgstr "Ny mÃ¥ned!" msgid "New Week!" msgstr "Ny uge!" msgid "Beware!" msgstr "Pas pÃ¥!" msgid "" "%{color} player, this is your last day to capture a town, or you will be " "banished from this land." msgstr "" "%{color} spiller! Dette er din sidste dag til at erobre en by, ellers bliver " "du forvist fra dette land!" msgid "" "%{color} player, you only have %{day} days left to capture a town, or you " "will be banished from this land." msgstr "" "%{color} spiller! Du har kun %{day} dage til at erobre en by, ellers bliver " "du forvist fra dette land!" msgid "%{color} player's turn." msgstr "%{color} spillers tur." #, fuzzy msgid "" "%{color} player, you have lost your last town. If you do not conquer another " "town in the next week, you will be eliminated." msgstr "" "%{color} spiller! Du har mistet din sidste by. Hvis du ikke erobrer en anden " "by i løbet af én uge, vil du blive forvist fra dette land!" msgid "" "%{color} player, your heroes abandon you, and you are banished from this " "land." msgstr "" "%{color} spiller! Dine helte forlader dig og du bliver forvist fra dette " "land!" msgid "Lord Kilburn" msgstr "Fyrst Kilburn" msgid "Tsabu" msgstr "Sabu" msgid "Sir Galant" msgstr "Herr Galant" msgid "Thundax" msgstr "Thorøks" msgid "Lord Haart" msgstr "Fyrst Hjart" msgid "Ariel" msgstr "Ariel" msgid "Rebecca" msgstr "Rebekka" msgid "Sandro" msgstr "Sander" msgid "Crodo" msgstr "Krodo" msgid "Barock" msgstr "Barock" msgid "Antoine" msgstr "Antonius" msgid "Astra" msgstr "Astra" msgid "Agar" msgstr "Agar" msgid "Vatawna" msgstr "Vatavne" msgid "Vesper" msgstr "Vesper" msgid "Ambrose" msgstr "Ambrosius" msgid "Troyan" msgstr "Trojan" msgid "Jojosh" msgstr "Joshua" msgid "Wrathmont" msgstr "Vredeberg" msgid "Maximus" msgstr "Maximus" msgid "Next Hero" msgstr "Næste helt" msgid "Select the next Hero." msgstr "Vælg næste helt." msgid "Hero Movement" msgstr "Heltebevægelse" msgid "" "Start the Hero's movement along the current path or re-visit the object " "occupied by the Hero. Press and hold this button to reset the Hero's path." msgstr "" "Begynd heltens bevægelse langs den nuværende sti eller foretag et genbesøg " "ved det objekt, som helten stÃ¥r ved. Hold denne knap nede, for at nulstille " "heltens sti." msgid "Kingdom Summary" msgstr "Oversigt over kongedømmet" msgid "View a Summary of your Kingdom." msgstr "Vis en oversigt over dit kongerige." msgid "Cast an adventure spell." msgstr "Kast en eventyrbesværgelse" msgid "End Turn" msgstr "End tur" msgid "End your turn and left the computer take its turn." msgstr "End din tur og lad computeren tage sin." msgid "Adventure Options" msgstr "Eventyrindstillinger" msgid "Bring up the adventure options menu." msgstr "Frembring menuen for eventyrindstillingerne." msgid "" "Bring up the file options menu, allowing you to load, save, start a new game " "or quit." msgstr "" "Frembring filindstillingsmenuen, som tillader dig at indlæse, gemme, starte " "et nyt spil eller afslutte." msgid "Bring up the system options menu, allowing you to customize your game." msgstr "" "Frembring menuen for systemindstillingerne, sÃ¥ du kan tilpasse dit spil." msgid "" "One or more heroes may still move, are you sure you want to end your turn?" msgstr "" "En eller flere helte kan stadig ride. Er du sikker pÃ¥ at du vil ende din tur?" msgid "Are you sure you want to quit?" msgstr "Er du sikker pÃ¥ om du vil afslutte?" msgid "Are you sure you want to restart? (Your current game will be lost.)" msgstr "" "Er du sikker pÃ¥, om du vil indlæse et nyt spil? (Dit nuværende spil vil gÃ¥ " "tabt.)" msgid "Are you sure you want to overwrite the save with this name?" msgstr "Er du sikker pÃ¥, om du vil overskrive det gemte spil med dette navn?" msgid "Game saved successfully." msgstr "Spillet blev gemt." msgid "There was an issue during saving." msgstr "Mens spillet forsøgtes gemt, opstod der et problem." msgid "" "Are you sure you want to load a new game? (Your current game will be lost.)" msgstr "" "Er du sikker pÃ¥, om du vil indlæse et nyt spil? (Dit nuværende spil vil gÃ¥ " "tabt.)" msgid "Try looking on land!!!" msgstr "Prøv at søge pÃ¥ land!!!" msgid "" "Searching for the Ultimate Artifact is fruitless. Your hero could not carry " "it even if he found it - all his artifact slots are full." msgstr "" "At søge efter Det Ultimative Artefakt er frugtesløst. Din helt kunne ikke " "bære den, selvom han fandt den - alle hans artefaktpladser er fulde." msgid "Digging for artifacts requires a whole day, try again tomorrow." msgstr "At grave efter artefakter kræver en hel dag. Prøv igen i morgen." msgid "" "After spending many hours digging here, you have uncovered the %{artifact}." msgstr "" "Efter at have brugt mange timer pÃ¥ at grave her, finder du endelig " "%{artifact}." msgid "Congratulations!" msgstr "TILLYKKE!!!" msgid "Nothing here. Where could it be?" msgstr "Intet her. Hvor kan den være?" msgid "Try searching on clear ground." msgstr "Prøv at søge pÃ¥ et bart omrÃ¥de." msgid "A miniature view of the known world. Left click to move viewing area." msgstr "" "Et miniaturebillede af den kendte verden. Venstre klik for at flytte " "visningsomrÃ¥de." msgid "Month: %{month} Week: %{week}" msgstr "MÃ¥ned: %{month} Uge: %{week}" msgid "Day: %{day}" msgstr "Dag: %{day}" msgid "" "You find a small\n" "quantity of %{resource}." msgstr "" "Du fandt en lille\n" "mængde %{resource}." msgid "Status Window" msgstr "Statusvindue" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date." msgstr "" "Dette vindue giver information om status pÃ¥ din helt eller dit rige og viser " "datoen." msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date. Left click here to cycle through these windows." msgstr "" "Dette vindue giver information om status pÃ¥ din helt eller dit rige og viser " "datoen. Venstre klik her for at bladre gennem disse vinduer." msgid "" "This lets you change player starting positions and colors. A particular " "color will always start in a particular location. Some positions may only be " "played by a computer player or only by a human player." msgstr "" "Dette lader dig ændre spillerens startpositioner og farver. En bestemt farve " "starter altid pÃ¥ et bestemt sted. Nogle positioner kan kun spilles af en " "computerspiller eller kun af en menneskelig spiller." msgid "" "This lets you change the class of a player. Classes are not always " "changeable. Depending on the scenario, a player may receive additional towns " "and/or heroes not of their primary alignment." msgstr "" "Dette lader dig ændre en spillers klasse. Klasser er ikke altid " "udskiftelige. Afhængigt af scenariet kan en spiller modtage yderligere byer " "og/eller helte, der ikke er i deres primære linje." msgid "Handicap" msgstr "Handicap" msgid "" "This lets you change the handicap of a particular player. Only humans may be " "handicapped. Handicapped players start with fewer resources and earn 15 or " "30% fewer resources per turn for mild and severe handicaps, respectively." msgstr "" "Dette lader dig ændre handicappet for en bestemt spiller. Kun mennesker mÃ¥ " "være handicappede. Handicappede spillere starter med færre resurser og " "tjener 15 eller 30 % færre resurser per omgang for henholdsvis milde og " "svære handicap." msgid "%{color} player" msgstr "%{color} spiller" msgid "No Handicap" msgstr "Intet handicap" msgid "No special restrictions on start resources and earning them per turn." msgstr "" "Ingen særlige begrænsninger for startresurser og indkomst af disse per tur." msgid "Mild Handicap" msgstr "Mildt handicap" msgid "" "Mild handicapped players start with fewer resources and earn 15% fewer " "resources per turn." msgstr "" "Mildt handicappede spillere starter med færre resurser og tjener 15 % færre " "resurser per tur." msgid "Severe Handicap" msgstr "Svært handicap" msgid "" "Severe handicapped players start with fewer resources and earn 30% fewer " "resources per turn." msgstr "" "Svært handicappede spillere starter med færre resurser og tjener 30 % færre " "resurser per tur." msgid "View %{skill} Info" msgstr "Vis info om %{skill}" msgid "Keyboard|123" msgstr "123" msgid "Keyboard|SPACE" msgstr "MELLEMRUM" msgid "Keyboard|ABC" msgstr "ABC" msgid "Kingdom Income" msgstr "Kongerigets indkomst" msgid "Kingdom Income per day." msgstr "Kongerigets daglige indkomst" msgid "For every lighthouse controlled, your ships will move further each day." msgstr "" "For hvert fyrtÃ¥rn, der kontrolleres, kan dine skibe sejle længere om dagen." msgid "English" msgstr "Engelsk" msgid "French" msgstr "Fransk" msgid "Polish" msgstr "Polsk" msgid "German" msgstr "Tysk" msgid "Russian" msgstr "Russisk" msgid "Italian" msgstr "Italiensk" msgid "Czech" msgstr "Tjekkisk" msgid "Norwegian" msgstr "Norsk" msgid "Belarusian" msgstr "Hviderussisk" msgid "Bulgarian" msgstr "Bulgarisk" msgid "Ukrainian" msgstr "Ukrainsk" msgid "Romanian" msgstr "Rumænsk" msgid "Spanish" msgstr "Spansk" msgid "Swedish" msgstr "Svensk" msgid "Portuguese" msgstr "Portugisisk" msgid "Turkish" msgstr "Tyrkisk" msgid "Dutch" msgstr "Hollandsk" msgid "Hungarian" msgstr "Ungarnsk" msgid "Danish" msgstr "Dansk" msgid "Slovak" msgstr "Slovensk" msgid "Vietnamese" msgstr "Vietnamesisk" msgid ", FPS: " msgstr ", FPS:" msgid "%{object} robber" msgstr "%{object} røv" msgid "%{object} raided" msgstr "%{object} røvet" msgid "Ector" msgstr "Hektor" msgid "Gwenneth" msgstr "Lykke" msgid "Sir Gallant" msgstr "Herr Gallant" msgid "Tyro" msgstr "Tyr" msgid "Dimitry" msgstr "Dimitri" msgid "Ruby" msgstr "Rubin" msgid "Crag Hack" msgstr "Lars Hug" msgid "Fineous" msgstr "Fineas" msgid "Jezebel" msgstr "Izabel" msgid "Atlas" msgstr "Atlas" msgid "Ergon" msgstr "Egon" msgid "Jaclyn" msgstr "Jakobine" msgid "Gem" msgstr "Perle" msgid "Natasha" msgstr "Natasja" msgid "Carlawn" msgstr "Karlana" msgid "Luna" msgstr "Luna" msgid "Arie" msgstr "Arie" msgid "Barok" msgstr "Barok" msgid "Kastore" msgstr "Kastor" msgid "Falagar" msgstr "Falagar" msgid "Dawn" msgstr "Dagny" msgid "Flint" msgstr "Flint" msgid "Halon" msgstr "Halon" msgid "Myra" msgstr "Mira" msgid "Myrini" msgstr "Myrine" msgid "Wilfrey" msgstr "Vilfred" msgid "Mandigal" msgstr "Mandalei" msgid "Sarakin" msgstr "Sarakin" msgid "Charity" msgstr "Godemine" msgid "Darlana" msgstr "Darlana" msgid "Ranloo" msgstr "Ranlov" msgid "Rialdo" msgstr "Rialdo" msgid "Zam" msgstr "Zam" msgid "Zom" msgstr "Zom" msgid "Celia" msgstr "Celia" msgid "Roxana" msgstr "Roxanne" msgid "Lord Corlagon" msgstr "Fyrst Korlagon" msgid "Lord Halton" msgstr "Fyrst Halton" msgid "Sister Eliza" msgstr "Søster Eliza" msgid "Brother Brax" msgstr "Bror Braks" msgid "Dainwin" msgstr "Dejnvin" msgid "Joseph" msgstr "Josef" msgid "Mog" msgstr "Mog" msgid "Solmyr" msgstr "Solmyr" msgid "Ceallach" msgstr "Ketil" msgid "Drakonia" msgstr "Drakonia" msgid "Elderian" msgstr "Oldrian" msgid "Gallavant" msgstr "Gallavant" msgid "Jarkonas" msgstr "Harkonas" msgid "Martine" msgstr "Martine" msgid " gives you maximum morale" msgstr " giver dig maksimal moral" msgid " gives you maximum luck" msgstr " giver dig maksimalt held" msgid "You cannot pick up this artifact, you already have a full load!" msgstr "Du kan ikke opsamle denne artefakt, da du allerede har fuld last!" msgid "To cast spells, you must first buy a spell book for %{gold} gold." msgstr "" "For at kaste besværgelser, mÃ¥ du først købe en magibog. Den koster %{gold} " "guldstykker." msgid "Unfortunately, you seem to be a little short of cash at the moment." msgstr "Desværre ser det ud til, at du mangler kontanter i øjeblikket." msgid "Do you wish to buy one?" msgstr "Ønsker du at købe én?" msgid "%{count} / day" msgstr "%{count} / dag" msgid "one" msgstr "1" msgid "two" msgstr "2" msgid "A whirlpool engulfs your ship. Some of your army has fallen overboard." msgstr "" "En hvirvelstrøm opsluger dit skib. Noget af din hær er faldet over bord." msgid "Insulted by your refusal of their offer, the monsters attack!" msgstr "Fornærmet over din afvisning af deres tilbud, angriber væsenerne!" msgid "" "The %{monster}, awed by the power of your forces, begin to scatter.\n" "Do you wish to pursue and engage them?" msgstr "" "Disse %{monster} beundres ved synet af din mægtige hær og begynder at sprede " "sig.\n" "Ønsker du at forfølge og gÃ¥ pÃ¥ dem?" msgid "Ransacking an enemy camp, you discover a hidden cache of treasures." msgstr "Idet du ransager en fjendelejr, opdager du en skjult skat." msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with these resources, " "come back next week for more.\"" msgstr "" "Mølleren annoncerer:\n" "\"Deres excellence, jeg har arbejdet hÃ¥rdt for at skaffe Dem disse resurser. " "FÃ¥ mere ved at komme igen i næste uge.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there are no resources currently available. Please try " "again next week.\"" msgstr "" "Mølleren annoncerer:\n" "\"Deres excellence, jeg beklager, der er ingen resurser at hente i " "øjeblikket. Prøv at vende tilbage i næste uge.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with this gold, come " "back next week for more.\"" msgstr "" "Mølleren annoncerer:\n" "\"Deres excellence, jeg har arbejdet hÃ¥rdt for at skaffe Dem disse " "guldstykker. FÃ¥ mere ved at komme igen i næste uge.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there is no gold currently available. Please try again " "next week.\"" msgstr "" "Mølleren annoncerer:\n" "\"Deres excellence, jeg beklager, der er intet guld at hente i øjeblikket. " "Prøv at vende tilbage i næste uge.\"" msgid "" "You've found an abandoned lean-to.\n" "Poking about, you discover some resources hidden nearby." msgstr "" "Du har fundet et forladt shelter.\n" "Idet du roder rundt, opdager du nogle resurser gemt i nærheden." msgid "The lean-to is long abandoned. There is nothing of value here." msgstr "Shelteret er for længst forladt. Der er intet af værdi her." msgid "" "You catch a leprechaun foolishly sleeping amidst a cluster of magic " "mushrooms.\n" "In exchange for his freedom, he guides you to a small pot filled with " "precious things." msgstr "" "Du fanger en tÃ¥belig alf i at sove mellem en samling magiske svampe.\n" "I bytte for sin frihed, viser han dig vejen til en lille potte fyldt med " "værdigenstande." msgid "" "You've found a magic garden, the kind of place that leprechauns and faeries " "like to cavort in, but there is no one here today.\n" "Perhaps you should try again next week." msgstr "" "Du finder en magisk have, den slags haver som feer og alfer kan lide at " "boltre sig i. Men der er ingen her at finde i dag.\n" "MÃ¥ske du skulle prøve igen i næste uge." msgid "You come upon the remains of an unfortunate adventurer." msgstr "Du støder pÃ¥ resterne af en uheldig eventyrer." msgid "Treasure" msgstr "Skat" msgid "Searching through the tattered clothing, you find the %{artifact}." msgstr "Du gennemsøger det lasede tøj og finder %{artifact}." msgid "Searching through the tattered clothing, you find nothing." msgstr "Du gennemsøger det lasede tøj, men finder intet af værdi." msgid "" "You come across an old wagon left by a trader who didn't quite make it to " "safe terrain." msgstr "" "Du støder pÃ¥ en gammel vogn efterladt af en handelsmand, der ikke helt nÃ¥ede " "til sikkert terræn." msgid "Unfortunately, others have found it first, and the wagon is empty." msgstr "Desværre har andre fundet den først, og vognen er tom." msgid "Searching inside, you find the %{artifact}." msgstr "Indenfor kigger du søgende rundt. Du finder %{artifact}." msgid "Inside, you find some of the wagon's cargo still intact." msgstr "Indeni vognen er noget af dens last stadig intakt." msgid "You search through the flotsam, and find some wood and some gold." msgstr "Du undersøger vraggodset og finder noget træ og guld." msgid "You search through the flotsam, and find some wood." msgstr "Du undersøger vraggodset og finder noget træ." msgid "You search through the flotsam, but find nothing." msgstr "Du gennemsøger vraggodset, men du finder intet af værdi." msgid "Shrine of the 1st Circle" msgstr "Helligdom af den 1. cirkel" msgid "" "You come across a small shrine attended by a group of novice acolytes.\n" "In exchange for your protection, they agree to teach you a simple spell - " "'%{spell}'." msgstr "" "Du støder pÃ¥ en lille helligdom, der overværes af en gruppe præstelærlinge.\n" "Til gengæld for din beskyttelse, er de enige om at lære dig en simpel " "besværgelse - '%{spell}'." msgid "Shrine of the 2nd Circle" msgstr "Helligdom af den 2. cirkel" msgid "" "You come across an ornate shrine attended by a group of rotund friars.\n" "In exchange for your protection, they agree to teach you a spell - " "'%{spell}'." msgstr "" "Du støder pÃ¥ en udsmykket helligdom, der overværes af en gruppe runde " "munke.\n" "Til gengæld for din beskyttelse, er de enige om at lære dig en simpel " "besværgelse - '%{spell}'." msgid "Shrine of the 3rd Circle" msgstr "Helligdom af den 3. cirkel" msgid "" "You come across a lavish shrine attended by a group of high priests.\n" "In exchange for your protection, they agree to teach you a sophisticated " "spell - '%{spell}'." msgstr "" "Du støder pÃ¥ en overdÃ¥dig helligdom, der overværes af en gruppe højpræste.\n" "Til gengæld for din beskyttelse, er de enige om at lære dig en simpel " "besværgelse - '%{spell}'." msgid "" "\n" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "\n" "Desværre har du ikke visdom til at forstÃ¥ besværgelsen, og du er ikke i " "stand til at lære den." msgid "" "\n" "Unfortunately, you already have knowledge of this spell, so there is nothing " "more for them to teach you." msgstr "" "\n" "Desværre kender du allerede til denne besværgelse, sÃ¥ der er ikke mere de " "kan lære dig." msgid "" "\n" "Unfortunately, you have no Magic Book to record the spell with." msgstr "" "\n" "Desværre har du ingen magibog, at nedskrive besværgelsen i." msgid "" "You approach the hut and observe a witch inside studying an ancient tome on " "%{skill}.\n" "\n" msgstr "" "Du nærmer dig hytten og observerer en heks indenfor, som studerer et " "ældgammelt bind om %{skill}.\n" "\n" msgid "" "As you approach, she turns and focuses her one glass eye on you.\n" "\"You already know everything you deserve to learn!\" the witch screeches. " "\"NOW GET OUT OF MY HOUSE!\"" msgstr "" "Idet du nærmer dig, vender hun sig om og retter sit glasøje mod dig.\n" "\"Du ved allerede alt hvad du fortjener at lære!\" hviner heksen. \"OG " "FORSVIND SÃ… FRA MIT HJEM!\"" msgid "" "As you approach, she turns and speaks.\n" "\"You already know that which I would teach you. I can help you no further.\"" msgstr "" "Idet du nærmer dig, vender hun sig om og siger:\n" "\"Du evner allerede det, som jeg ville have lært dig at kunne. Jeg kan ikke " "hjælpe dig.\"" msgid "" "An ancient and immortal witch living in a hut with bird's legs for stilts " "teaches you %{skill} for her own inscrutable purposes." msgstr "" "I en hytte med stolper af enorme fugleben lever en udødelig og ældgammel " "heks. Af sine egne uransagelige Ã¥rsager lærer hun dig %{skill}." msgid "As you drink the sweet water, you gain luck for your next battle." msgstr "Du drikker det søde vand og vinder held til dit næste slag." msgid "You drink from the enchanted fountain, but nothing happens." msgstr "Du drikker af det fortryllede springvand, men der sker ikke noget." msgid "You enter the faerie ring, but nothing happens." msgstr "Du gÃ¥r ind i fe-ringen, men der sker ikke noget." msgid "" "Upon entering the mystical faerie ring, your army gains luck for its next " "battle." msgstr "" "Idet du gÃ¥r ind i den mystiske fe-ring, vinder din hær held til det næste " "slag." msgid "" "You've found an ancient and weathered stone idol.\n" "It is supposed to grant luck to visitors, but since the stars are already " "smiling upon you, it does nothing." msgstr "" "Du har fundet et gammelt og forvitret stenidol.\n" "Det er meningen, at det skal give held til besøgende, men da stjernerne " "allerede smiler til dig, gør det ingenting." msgid "" "You've found an ancient and weathered stone idol.\n" "Kissing it is supposed to be lucky, so you do. The stone is very cold to the " "touch." msgstr "" "Du har fundet et gammelt og forvitret stenidol.\n" "Det siges at bringe held, hvis man kysser pÃ¥ den. SÃ¥ det gør du. Stenen er " "meget kold at røre ved." msgid "The mermaids silently entice you to return later and be blessed again." msgstr "" "Havfruerne lokker dig lydløst til at vende tilbage senere, for igen at blive " "velsignet." msgid "" "The magical, soothing beauty of the Mermaids reaches you and your crew.\n" "Just for a moment, you forget your worries and bask in the beauty of the " "moment.\n" "The mermaids charms bless you with increased luck for your next combat." msgstr "" "Du og dit mandskab beskuer havfruernes magiske, beroligende skønhed.\n" "For en stund, glemmer du dine bekymringer og soler dig i øjeblikkets " "skønhed.\n" "Havfruernes charme velsigner dig med øget held til din næste kamp." msgid "" "You come upon the pyramid of a great and ancient king.\n" "You are tempted to search it for treasure, but all the old stories warn of " "fearful curses and undead guardians.\n" "Will you search?" msgstr "" "Du kommer forbi pyramiden af en stor og gammel konge.\n" "Du er fristet til at søge efter skatte, men alle gamle fortællinger advarer " "om frygtsomme forbandelser og udøde vogtere.\n" "Vil du søge alligevel?" msgid "" "Upon defeating the monsters, you decipher an ancient glyph on the wall, " "telling the secret of the spell - '" msgstr "" "NÃ¥r du besejrer monstrene, tyder du en gammel glyf pÃ¥ væggen, som afslører " "besværgelsens hemmelighed - '" msgid "Unfortunately, you have no Magic Book to record the spell with." msgstr "Desværre har du ingen magibog, at nedskrive besværgelsen i." msgid "" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "Desværre har du ikke visdom til at forstÃ¥ besværgelsen, og du er ikke i " "stand til at lære den." msgid "" "You come upon the pyramid of a great and ancient king.\n" "Routine exploration reveals that the pyramid is completely empty." msgstr "" "Du kommer forbi pyramiden af en stor og gammel konge.\n" "En rutinemæssig udforskning afslører, at pyramiden er helt tom." msgid "" "A drink at the well is supposed to restore your spell points, but you are " "already at maximum." msgstr "" "Drikken af denne brønd skulle gerne genopfylde din mana, men det er allerede " "fuldt." msgid "A second drink at the well in one day will not help you." msgstr "Drikken af denne brønd virker ikke to gange pÃ¥ samme dag." msgid "A drink from the well has restored your spell points to maximum." msgstr "Drikken fra denne brønd genopfylder din mana." msgid "" "\"I'm sorry sir,\" The leader of the soldiers says, \"but you already know " "everything we have to teach.\"" msgstr "" "\"Jeg er ked af det herre,\" siger soldaterlederen, \"men du ved allerede " "alt, hvad vi kunne have lært dig.\"" msgid "The soldiers living in the fort teach you a few new defensive tricks." msgstr "Soldaterne, der bor i fortet, lærer dig et par nye defensive tricks." msgid "" "You've come upon a mercenary camp practicing their tactics. \"You're too " "advanced for us,\" the mercenary captain says. \"We can teach nothing more.\"" msgstr "" "Du er stødt pÃ¥ en lejesoldatslejr, der øver deres taktiske evner. \"Dine " "evner overgÃ¥r vores egne,\" siger lejesoldatskaptajnen. \"Vi kan ikke lære " "dig mere.\"" msgid "" "You've come upon a mercenary camp practicing their tactics. The mercenaries " "welcome you and your troops and invite you to train with them." msgstr "" "Du er stødt pÃ¥ en lejesoldatslejr, der øver pÃ¥ deres taktik. Lejetropperne " "byder dig og dine tropper velkommen og inviterer dig til at træne sammen med " "dem." msgid "\"Go 'way!\", the witch doctor barks, \"you know all I know.\"" msgstr "\"GÃ¥ væk!\", bjæffer heksedoktoren, \"du ved alt, hvad jeg ved.\"" msgid "" "An Orcish witch doctor living in the hut deepens your knowledge of magic by " "showing you how to cast stones, read portents, and decipher the intricacies " "of chicken entrails." msgstr "" "En orkisk heksedoktor, der bor i hytten, gør din viden om magi dybere, ved " "at vise dig, hvordan du kaster sten, læser varsler og tyder forviklingerne " "af kyllingeindvolde." msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, the Druids turn you away, indicating they have nothing " "new to teach you." msgstr "" "Du har fundet en gruppe druider, i færd med en gudstjeneste ved en af deres " "sære bygningsværker af sten. Tavse afviser druiderne dig, hvilket indikerer, " "at de ikke har noget nyt at lære dig." msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, they teach you new ways to cast spells." msgstr "" "Du har fundet en gruppe druider, i færd med en gudstjeneste ved en af deres " "sære bygningsværker af sten. Tavse lærer de dig nye mÃ¥der at kaste " "besværgelser pÃ¥." msgid "" "You tentatively approach the burial ground of ancient warriors. Do you want " "to search the graves?" msgstr "" "Foreløbigt nærmer du dig gravpladsen for gamle krigere. Vil du gennemsøge " "gravene?" msgid "" "You spend several hours searching the graves and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" "Du bruger flere timer pÃ¥ at gennemsøge gravene, men du finder intet af " "værdi. En sÃ¥ foragtelig handling reducerer din hærs moral." msgid "Upon defeating the Zombies you search the graves and find something!" msgstr "" "Efter besejringen af zombierne, gennemsøger du gravene og finder noget!" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the shipwreck?" msgstr "" "Det rÃ¥dnende skrog af et stort piratskib knirker uhyggeligt, nÃ¥r det skubbes " "mod klipperne. Ønsker du at gennemsøge skibsvraget?" msgid "" "You spend several hours sifting through the debris and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" "Du bruger flere timer pÃ¥ at gennemrode vragresterne, men du finder intet af " "værdi. En sÃ¥dan foragtelig handling reducerer din hærs moral." msgid "" "Upon defeating the Ghosts you sift through the debris and find something!" msgstr "" "Efter besejringen af spøgelserne, gennemroder du vragresterne og finder " "noget!" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the ship?" msgstr "" "Det rÃ¥dnende skrog af et stort piratskib knirker uhyggeligt, nÃ¥r det skubbes " "mod klipperne. Ønsker du at gennemsøge skibet?" msgid "" "Upon defeating the Skeletons you sift through the debris and find something!" msgstr "" "Efter besejringen af skeletterne, gennemroder du vragresterne og finder " "noget!" msgid "Your men spot a navigational buoy, confirming that you are on course." msgstr "" "Dine mænd opdager en navigationsbøje, der bekræfter, at du er pÃ¥ rette vej." msgid "" "Your men spot a navigational buoy, confirming that you are on course and " "increasing their morale." msgstr "" "Dine mænd opdager en navigationsbøje, der bekræfter, at du er pÃ¥ rette vej " "og øger deres moral." msgid "" "The drink at the oasis is refreshing, but offers no further benefit. The " "oasis might help again if you fought a battle first." msgstr "" "Drikken fra oasen er forfriskende, men giver ingen yderligere fordele. Det " "hjælper mÃ¥ske, igen, hvis du udkæmper en kamp først." msgid "" "A drink at the oasis fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" "Drikken fra oasen fylder dine tropper med styrke og løfter deres humør. Du " "kan ride lidt længere i dag." msgid "" "The drink at the watering hole is refreshing, but offers no further benefit. " "The watering hole might help again if you fought a battle first." msgstr "" "Drikken fra vandhullet er forfriskende, men giver ingen yderligere fordele. " "Det hjælper mÃ¥ske, igen, hvis du udkæmper en kamp først." msgid "" "A drink at the watering hole fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" "Drikken fra vandhullet fylder dine tropper med styrke og løfter deres humør. " "Du kan ride lidt længere i dag." msgid "" "It doesn't help to pray twice before a battle. Come back after you've fought." msgstr "" "Det hjælper ikke at bede to gange før en kamp. Kom tilbage efter du har " "kæmpet." msgid "A visit and a prayer at the temple raises the morale of your troops." msgstr "Et besøg og en bøn i templet hæver moralen hos dine tropper." msgid "" "An old Knight appears on the steps of the gazebo. \"I am sorry, my liege, I " "have taught you all I can.\"" msgstr "" "En gammel ridder dukker op pÃ¥ trappen til lysthuset. \"Jeg er ked af det, " "min herre, jeg har lært dig alt, hvad jeg kan.\"" msgid "" "An old Knight appears on the steps of the gazebo. \"My liege, I will teach " "you all that I know to aid you in your travels.\"" msgstr "" "En gammel ridder dukker op pÃ¥ trappen til lysthuset. \"Min herre, jeg vil " "lære dig alt, hvad jeg ved, for at hjælpe dig pÃ¥ dine rejser.\"" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he says, \"I would give you an artifact as a reward, but " "you're all full.\"" msgstr "" "Du har reddet en skibbruden fra den sikre død op fra et nÃ¥desløst hav. " "Taknemmelig, siger han, \"jeg ville give dig et artefakt som belønning, men " "du er fuldt oppakket.\"" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he rewards you for your act of kindness by giving you the " "%{art}." msgstr "" "Du har reddet en skibbruden fra den sikre død op fra et nÃ¥desløst hav. " "Taknemmelig, belønner han dig for din venlighed, ved at give dig %{art}." msgid "A leprechaun offers you the %{art} for the small price of %{gold} Gold." msgstr "En alf tilbyder dig %{art} til den nette pris af %{gold} guldstykker." msgid "" "A leprechaun offers you the %{art} for the small price of %{gold} Gold and " "%{count} %{res}." msgstr "" "En alf tilbyder dig %{art} til den nette pris af %{gold} guldstykker og " "%{count} %{res}." msgid "Do you wish to buy this artifact?" msgstr "Ønsker du at købe denne artefakt?" msgid "" "You try to pay the leprechaun, but realize that you can't afford it. The " "leprechaun stamps his foot and ignores you." msgstr "" "Du prøver at betale alfen, men indser, at du ikke har rÃ¥d til det. " "Leprechaunen stamper med foden og ignorerer dig." msgid "" "Insulted by your refusal of his generous offer, the leprechaun stamps his " "foot and ignores you." msgstr "" "Fornærmet over dit afslag pÃ¥ hans generøse tilbud, stamper alfen med foden " "og ignorerer dig." msgid "You've found the artifact: " msgstr "Du har fundet artefaktet:" msgid "" "You've found the humble dwelling of a withered hermit. The hermit tells you " "that he is willing to give the %{art} to the first wise person he meets." msgstr "" "Du har fundet den ydmyge bolig for en visnet eremit. Eremitten fortæller " "dig, at han er villig til at give %{art} til den første vise person, han " "møder." msgid "" "You've come across the spartan quarters of a retired soldier. The soldier " "tells you that he is willing to pass on the %{art} to the first true leader " "he meets." msgstr "" "Du er stødt pÃ¥ en pensioneret soldats spartanske kvarter. Soldaten fortæller " "dig, at han er villig til at give %{art} videre til den første rigtige " "leder, han møder." msgid "" "You've encountered a strange person with a hat and an owl on it. He tells " "you that he is willing to give %{art} if you have %{skill}." msgstr "" "Du har mødt en sær person med en ugle siddende ovenpÃ¥ sin hat. Han fortæller " "dig, at han er villig til at give %{art}, hvis du har %{skill}." msgid "" "You come upon an ancient artifact. As you reach for it, a pack of Rogues " "leap out of the brush to guard their stolen loot." msgstr "" "Du støder pÃ¥ et gammelt artefakt. Lige som du rækker ud efter den, springer " "en flok slyngler ud af krattet, for at beskytte deres stjÃ¥lne bytte." msgid "" "Through a clearing you observe an ancient artifact. Unfortunately, it's " "guarded by a nearby %{monster}. Do you want to fight the %{monster} for the " "artifact?" msgstr "" "Gennem en lysning observerer du et gammelt artefakt. Desværre er det " "bevogtet af en nærliggende %{monster}. Vil du udfordre væsenet %{monster} " "til kamp, for at fÃ¥ artefaktet?" msgid "Victorious, you take your prize, the %{art}." msgstr "Sejrrig, tager du din præmie, %{art}." msgid "" "Discretion is the better part of valor, and you decide to avoid this fight " "for today." msgstr "" "Diskretion er den bedste del af tapperhed, og du beslutter dig for at undgÃ¥ " "denne kamp for i dag." msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold pieces." msgstr "" "Efter at have brugt timevis pÃ¥ at prøve at fiske kisten op af havet, Ã¥bner " "du den og finder %{gold} guldstykker." msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold and the %{art}." msgstr "" "Efter at have brugt timevis pÃ¥ at prøve at fiske kisten op af havet, Ã¥bner " "du den og finder %{gold} guldstykker og %{art}." msgid "" "After spending hours trying to fish the chest out of the sea, you open it, " "only to find it empty." msgstr "" "Efter at have brugt timer pÃ¥ at prøve at fiske kisten op af havet, Ã¥bner du " "den, kun for at finde den tom." msgid "" "After scouring the area, you fall upon a hidden treasure cache. You may take " "the gold or distribute the gold to the peasants for experience. Do you wish " "to keep the gold?" msgstr "" "Efter at have undersøgt omrÃ¥det, falder du over en hemmelig skattekiste. Du " "har to valg: Enten beholder du guldet for dig selv, eller ogsÃ¥ fordeler du " "guldet mellem dine undersÃ¥tter for nogle erfaringspoint. Hvad gør du?" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "%{gold} gold pieces." msgstr "" "Efter at have gennemsøgt omrÃ¥det, falder du over en skjult kiste, der " "indeholder %{gold} guldstykker." msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "ancient artifact %{art}." msgstr "" "Efter at have gennemsøgt omrÃ¥det, falder du over en skjult kiste, der " "indeholder det gamle artefakt %{art}." msgid "" "You stumble upon a dented and tarnished lamp lodged deep in the earth. Do " "you wish to rub the lamp?" msgstr "" "Du støder pÃ¥ en bulet og plettet lampe, der ligger dybt i jorden. Ønsker du " "at gnide pÃ¥ lampen?" msgid "" "You have taken control of the local Alchemist shop. It will provide you with " "%{count} unit of Mercury per day." msgstr "" "Dette alkymist laboratorium er nu under din kontrol. Den forsyner dit rige " "med %{count} mængde kviksølv om dagen." msgid "" "You gain control of a sawmill. It will provide you with %{count} units of " "wood per day." msgstr "" "Denne savmølle er nu under din kontrol. Den forsyner dit rige med %{count} " "mængde træ om dagen." msgid "" "You gain control of an ore mine. It will provide you with %{count} units of " "ore per day." msgstr "" "Denne malmmine er nu under din kontrol. Den forsyner dit rige med %{count} " "mængde malm om dagen." msgid "" "You gain control of a sulfur mine. It will provide you with %{count} unit of " "sulfur per day." msgstr "" "Denne svovlmine er nu under din kontrol. Den forsyner dit rige med %{count} " "mængde svovl om dagen." msgid "" "You gain control of a crystal mine. It will provide you with %{count} unit " "of crystal per day." msgstr "" "Denne krystalmine er nu under din kontrol. Den forsyner dit rige med " "%{count} mængde krystaller om dagen." msgid "" "You gain control of a gem mine. It will provide you with %{count} unit of " "gems per day." msgstr "" "Denne juvelmine er nu under din kontrol. Den forsyner dit rige med %{count} " "mængde juveler om dagen." msgid "" "You gain control of a gold mine. It will provide you with %{count} gold per " "day." msgstr "" "Denne guldmine er nu under din kontrol. Den forsyner dit rige med %{count} " "guldstykker om dagen." msgid "" "The lighthouse is now under your control, and all of your ships will now " "move further each day." msgstr "" "Fyret er nu under din kontrol, og alle dine skibe vil nu kunne sejle længere " "om dagen." msgid "You gain control of a %{name}." msgstr "Du fÃ¥r kontrol over en %{name}." msgid "" "You come upon an abandoned gold mine. The mine appears to be haunted. Do you " "wish to enter?" msgstr "" "Du støder pÃ¥ en forladt guldmine. Minen ser ud til at være hjemsøgt. Vil du " "ind?" msgid "You beat the Ghosts and are able to restore the mine to production." msgstr "" "Du besejrer spøgelserne og er i stand til at genoprette minens produktion." msgid "" "A group of %{monster} with a desire for greater glory wish to join you. Do " "you accept?" msgstr "" "En gruppe %{monster}, med et ønske om større ære, vil gerne tilslutte sig " "til din hær. Accepterer du?" msgid "As you approach the dwelling, you notice that there is no one here." msgstr "Idet du nærmer dig bostedet, bemærker du, at der ikke er nogen hjemme." msgid "" "You search the ruins, but the Medusas that used to live here are gone. " "Perhaps there will be more next week." msgstr "" "Du søger i ruinerne, men de medusaer, der plejede at bo her, er væk. MÃ¥ske " "kommer der flere i næste uge." msgid "" "You've found some Medusas living in the ruins. They are willing to join your " "army for a price. Do you want to recruit Medusas?" msgstr "" "Du har fundet nogle medusaer, der bor i ruinerne. De vil gerne tilslutte sig " "din hær, men for en pris. Vil du rekruttere medusaer?" msgid "" "You've found a Sprite Tree City. Unfortunately, none of the Sprites living " "there wish to join an army. Maybe next week." msgstr "" "Du har fundet en fe-træby. Desværre er der ingen af de feer, der bor der, " "som ønsker at tilslutte sig din hær. MÃ¥ske i næste uge." msgid "" "Some of the Sprites living in the tree city are willing to join your army " "for a price. Do you want to recruit Sprites?" msgstr "" "Nogle af de feer, der bor i træbyen, vil gerne tilslutte sig din hær, men " "for en pris. Vil du rekruttere feer?" msgid "" "A colorful Rogues' wagon stands empty here. Perhaps more Rogues will be here " "later." msgstr "" "En farverig slyngelvogn stÃ¥r tom her. MÃ¥ske er der flere slyngler tilstede " "her senere." msgid "" "Distant sounds of music and laughter draw you to a colorful wagon housing " "Rogues. Do you wish to have any Rogues join your army?" msgstr "" "Fjerne lyde af musik og latter drager dig hen til en farverig vogn, hvor " "slyngler bor. Ønsker du at lade nogle slyngler tilslutte sig din hær?" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. The " "tents are unoccupied. Perhaps more Nomads will be here later." msgstr "" "En gruppe af lasede telte, der bølger i sandvinden, lokker dig. Teltene er " "ubesatte. MÃ¥ske er der flere nomader tilstede her senere." msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. Do you " "wish to have any Nomads join you during your travels?" msgstr "" "En gruppe af lasede telte, der bølger i sandvinden, lokker dig. Vil du have " "nogen nomader med dig pÃ¥ dine rejser?" msgid "The pit of mud bubbles for a minute and then lies still." msgstr "Mudderhullet bobler et minuts tid og ligger derefter stille." msgid "" "As you approach the bubbling pit of mud, creatures begin to climb out and " "position themselves around it. In unison they say: \"Mother Earth would like " "to offer you a few of her troops. Do you want to recruit Earth Elementals?\"" msgstr "" "Idet du nærmer dig det boblende mudderhul, begynder væsner at kravle ud og " "placere sig omkring den. I kor siger de: \"Moder Jord vil gerne tilbyde dig " "et par af sine tropper. Vil du rekruttere Jord-elementaler?\"" msgid "You enter the structure of white stone pillars, and find nothing." msgstr "" "Du gÃ¥r ind i en bygning af hvide stensøjler, men du finder intet af værdi." msgid "" "White stone pillars support a roof that rises up to the sky. As you enter " "the structure, the dead air of the outside gives way to a whirling gust that " "almost pushes you back out. The air current materializes into a barely " "visible form. The creature asks, in what can only be described as a loud " "whisper: \"Why have you come? Are you here to call upon the forces of the " "air?\"" msgstr "" "Hvide stensøjler understøtter et tag, som strækker sig op mod himlen. Da du " "træder ind i bygningen, giver den døde luft udefra plads til et hvirvlende " "vindstød, der næsten skubber dig ud igen. Luftstrømmen materialiserer sig i " "en knap synlig form. Væsenet spørger, i hvad der kun kan beskrives som en " "høj hvisken: \"Hvorfor er du kommet? Er du her for at kalde pÃ¥ luftens " "kræfter?\"" msgid "No Fire Elementals approach you from the lava pool." msgstr "Ingen ild-elementaler gÃ¥r dig i møde fra lavasøen." msgid "" "Beneath a structure that serves to hold in heat, Fire Elementals move about " "in a fiery pool of molten lava. A group of them approach you and offer their " "services. Would you like to recruit Fire Elementals?" msgstr "" "Under en bygning, der tjener til at holde pÃ¥ varmen, bevæger ild-" "elementalerne sig rundt i en brændende sø af smeltet lava. En gruppe af dem " "henvender sig til dig og melder sig til tjeneste. Ønsker du tænke dig at " "rekruttere ild-elementaler?" msgid "A face forms in the water for a moment, and then is gone." msgstr "For et øjeblik formes et ansigt i vandet, og sÃ¥ forsvinder det igen." msgid "" "Crystalline structures cast shadows over a small reflective pool of water. " "You peer into the pool, and a face that is not your own peers back. It asks: " "\"Would you like to call upon the powers of water?\"" msgstr "" "Krystallinske bygninger kaster skygger over en lille reflekterende sø af " "vand. Du kigger ned i søen, og et ansigt, der ikke er dit eget, ser tilbage " "pÃ¥ dig. Ansigtet spørger: \"Vil du kalde pÃ¥ vandets kræfter?\"" msgid "This burial site is deathly still." msgstr "Dette gravsted er dødstille." msgid "" "Restless spirits of long dead warriors seeking their final resting place " "offer to join you in hopes of finding peace. Do you wish to recruit ghosts?" msgstr "" "Rastløse Ã¥nder af for længst afdøde krigere, der søger deres sidste " "hvilested, tilbyder at tilslutte sig din hær i hÃ¥b om at finde fred. Ønsker " "du at rekruttere spøgelser?" msgid "" "The City of the Dead is empty of life, and empty of unlife as well. Perhaps " "some undead will move in next week." msgstr "" "De dødes by er tom for liv og ogsÃ¥ tom for 'uliv'. MÃ¥ske flytter nogle udøde " "ind i næste uge." msgid "" "Some Liches living here are willing to join your army for a price. Do you " "want to recruit Liches?" msgstr "" "Nogle kraftlicher, der bor her, vil gerne tilslutte sig din hær, men for en " "pris. Vil du rekruttere kraftlicher?" msgid "" "You've found the ruins of an ancient city, now inhabited solely by the " "undead. Will you search?" msgstr "" "Du har fundet ruinerne af en ældgammel by, som nu udelukkende er beboet af " "udøde. Vil du gerne udforske den?" msgid "" "Some of the surviving Liches are impressed by your victory over their " "fellows, and offer to join you for a price. Do you want to recruit Liches?" msgstr "" "Nogle af de overlevende kraftlicher er imponerede over din sejr over deres " "kammerater og tilbyder at tilslutte sig din hær, men for en pris. Vil du " "rekruttere kraftlicher?" msgid "" "You've found one of those bridges that Trolls are so fond of living under, " "but there are none here. Perhaps there will be some next week." msgstr "" "Du har fundet en af de broer, som trolde er sÃ¥ glade for at bo under, men " "her er ingen. MÃ¥ske kommer der nogle i næste uge." msgid "" "Some Trolls living under a bridge are willing to join your army, but for a " "price. Do you want to recruit Trolls?" msgstr "" "Nogle trolde, der bor under broen, vil gerne tilslutte sig din hær, men for " "en pris. Vil du rekruttere trolde?" msgid "Trolls living under the bridge challenge you. Will you fight them?" msgstr "Trolde, der bor under broen, udfordrer dig. Vil du bekæmpe dem?" msgid "" "A few Trolls remain, cowering under the bridge. They approach you and offer " "to join your forces as mercenaries. Do you want to buy any Trolls?" msgstr "" "Der er nogle fÃ¥ trolde tilbage, som kryber sammen under broen. De henvender " "sig til dig og tilbyder at tilslutte sig din hær som lejesoldater. Vil du " "rekruttere nogle trolde?" msgid "" "The Dragon city has no Dragons willing to join you this week. Perhaps a " "Dragon will become available next week." msgstr "" "I denne uge er der ingen røddrager i Dragebyen, der vil tilslutte sig din " "hær. MÃ¥ske bliver en røddrage tilgængelig i næste uge." msgid "" "The Dragon city is willing to offer some Dragons for your army for a price. " "Do you wish to recruit Dragons?" msgstr "" "Dragebyens røddrager vil gerne tilbyde dig, at lade nogle af dem tilslutte " "sig din hær, men for en pris. Ønsker du at rekruttere røddrager?" msgid "" "You stand before the Dragon City, a place off-limits to mere humans. Do you " "wish to violate this rule and challenge the Dragons to a fight?" msgstr "" "Du stÃ¥r foran Dragebyen, et sted, som er forbudt omrÃ¥de for almindelige " "mennesker. Ønsker du at overtræde denne regel og udfordre dragerne til kamp?" msgid "" "Having defeated the Dragon champions, the city's leaders agree to supply " "some Dragons to your army for a price. Do you wish to recruit Dragons?" msgstr "" "Efter at have besejret dragemestrene, er byens ledere enige om at lade nogle " "røddrager tilslutte sig din hær, men for en pris. Ønsker du at rekruttere " "røddrager?" msgid "From the observation tower, you are able to see distant lands." msgstr "Fra udsigtstÃ¥rnet er du i stand til at se fjerne lande." msgid "" "The spring only refills once a week, and someone's already been here this " "week." msgstr "" "Springvandet genopfyldes kun en gang om ugen, og nogen har allerede været " "her i denne uge." msgid "" "A drink at the spring is supposed to give you twice your normal spell " "points, but you are already at that level." msgstr "" "Drikken af springvandet skulle gerne give dig to gange dit maksimale mana i " "reserve, men det har du allerede." msgid "" "A drink from the spring fills your blood with magic! You have twice your " "normal spell points in reserve." msgstr "" "Drikken af springvandet fylder dit blod med magi! Du har nu to gange dit " "maksimale mana i reserve." msgid "" "Recognizing you, the butler refuses to admit you. \"The master,\" he says, " "\"will not see the same student twice.\"" msgstr "" "Butleren genkender dig, men nægter at lade dig blive optaget. \"Mesteren,\" " "siger han, \"vil ikke se den samme elev to gange.\"" msgid "" "The butler admits you to see the master of the house. He trains you in the " "four skills a hero should know." msgstr "" "Butleren tillader dig at se herren i huset. Herren træner dig indenfor de " "fire evner, enhver helt bør kende." msgid "" "The butler opens the door and looks you up and down. \"You are neither " "famous nor diplomatic enough to be admitted to see my master,\" he sniffs. " "\"Come back when you think yourself worthy.\"" msgstr "" "Butleren Ã¥bner døren og ser op og ned af dig. \"Du er hverken berømt eller " "diplomatisk nok til at blive optaget til at se min herre,\" fnyser han. " "\"Kom tilbage, nÃ¥r du finder dig værdig.\"" msgid " and " msgstr " og " msgid "" "All of the %{monsters} you have in your army have been trained by the battle " "masters of the fort. Your army now contains %{monsters2}." msgstr "" "Alle de %{monsters}, du har i din hær, er blevet trænet af fortets " "kampmestre. Disse er nu blevet til %{monsters2}." msgid "" "An unusual alliance of Ogres, Orcs, and Dwarves offer to train (upgrade) any " "such troops brought to them. Unfortunately, you have none with you." msgstr "" "En usædvanlig alliance af jætter, orker og dværge tilbyder at træne " "(opgradere) samme slags tropper, der bringes hertil. Desværre har du ingen " "med dig." msgid "All of your %{monsters} have been upgraded into %{monsters2}." msgstr "Alle dine %{monsters} er blevet opgraderet til %{monsters2}." msgid "" "A blacksmith working at the foundry offers to convert all Pikemen and " "Swordsmen's weapons brought to him from iron to steel. He also says that he " "knows a process that will convert Iron Golems into Steel Golems. " "Unfortunately, you have none of these troops in your army, so he can't help " "you." msgstr "" "En smed, der arbejder pÃ¥ støberiet, tilbyder at omdanne alle pikenéres og " "sværdkæmperes vÃ¥ben, som bringes til ham, fra jern til stÃ¥l. Han siger ogsÃ¥, " "at han kender en proces, der vil omskabe jerngolemmer til stÃ¥lgolemmer. " "Desværre har du ingen af de nævnte tropper i din hær, sÃ¥ han kan ikke hjælpe " "dig." msgid "" "The captain looks at you with surprise and says:\n" "\"You already have all the maps I know about. Let me fish in peace now.\"" msgstr "" "Kaptajnen kigger overrasket pÃ¥ dig og siger:\n" "\"Du har allerede alle de kort, jeg kender til. Lad mig nu fiske i fred.\"" msgid "" "A retired captain living on this refurbished fishing platform offers to sell " "you maps of the sea he made in his younger days for 1,000 gold. Do you wish " "to buy the maps?" msgstr "" "En pensioneret kaptajn, der bor pÃ¥ denne renoverede fiskeplatform, tilbyder " "at sælge dig et komplet kort over havet fra hans yngre dage. Han tilbyder " "dig at købe det for 1.000 guldstykker. \n" "Ønsker du at købe kortet?" msgid "" "The captain sighs. \"You don't have enough money, eh? You can't expect me " "to give my maps away for free!\"" msgstr "" "Kaptajnen sukker. \"Du har ikke penge nok, hva'? Du kan ikke forvente, at " "jeg giver mine kort væk gratis!\"" msgid "" "You come upon an obelisk made from a type of stone you have never seen " "before. Staring at it intensely, the smooth surface suddenly changes to an " "inscription. The inscription is a piece of a lost ancient map. Quickly you " "copy down the piece and the inscription vanishes as abruptly as it appeared." msgstr "" "Du støder pÃ¥ en obelisk lavet af en type sten, du aldrig før har set. Du " "stirrer intenst pÃ¥ det, mens den glatte overflade pludselig ændrer sig til " "en inskription. Indskriften viser et stykke af et tabt ældgammelt kort. " "Hurtigt skriver du stykket af, og inskriptionen forsvinder lige sÃ¥ brat, som " "den dukkede frem." msgid "You have already been to this obelisk." msgstr "Du har allerede været pÃ¥ denne obelisk." msgid "" "Upon your approach, the tree opens its eyes in delight. \"It is good to see " "you, my student. I hope my teachings have helped you.\"" msgstr "" "Idet du nærmer dig, Ã¥bner træet glad sine øjne. \"Det er godt at se dig, min " "elev. Jeg hÃ¥ber, at min lære har hjulpet dig.\"" msgid "" "Upon your approach, the tree opens its eyes in delight. \"Ahh, an " "adventurer! Allow me to teach you a little of what I have learned over the " "ages.\"" msgstr "" "Idet du nærmer dig, Ã¥bner træet glad sine øjne. \"Ahh, en eventyrer! Tillad " "mig at lære dig lidt af det, jeg har lært gennem tiderne.\"" msgid "Upon your approach, the tree opens its eyes in delight." msgstr "Idet du nærmer dig, Ã¥bner træet glad sine øjne." msgid "" "\"Ahh, an adventurer! I will be happy to teach you a little of what I have " "learned over the ages for a mere %{count} %{res}.\"" msgstr "" "\"Ahh, en eventyrer! Jeg vil med glæde lære dig lidt af det, jeg har lært " "gennem tiderne for blot %{count} %{res}.\"" msgid "(Just bury it around my roots.)" msgstr "(Begrav det bare omkring mine rødder.)" msgid "Tears brim in the eyes of the tree." msgstr "Træet fÃ¥r tÃ¥rer i øjnene." msgid "\"I need %{count} %{res}.\"" msgstr "\"Jeg har brug for %{count} %{res}.\"" msgid "it whispers. (sniff) \"Well, come back when you can pay me.\"" msgstr "det hvisker. (snif) \"NÃ¥, kom tilbage, nÃ¥r du kan betale mig.\"" msgid "" "Nestled among the trees sits a blind seer. After you explain the intent of " "your journey, the seer activates his crystal ball, allowing you to see the " "strengths and weaknesses of your opponents." msgstr "" "En blind seer sidder og putter sig blandt træerne. Efter du har forklaret " "hensigten med din rejse, aktiverer seeren sin krystalkugle, sÃ¥ du kan se " "dine modstanderes styrker og svagheder." msgid "" "The entrance to the cave is dark, and a foul, sulfurous smell issues from " "the cave mouth. Will you enter?" msgstr "" "Indgangen til hulen er mørk, og der kommer en grim, svovlholdig lugt fra " "hulens gab. \n" "Vover du at gÃ¥ derind?" msgid "Except for evidence of a terrible battle, the cave is empty." msgstr "Bortset fra tegn pÃ¥ en frygtelig kamp, er hulen tom." msgid "" "You find a powerful and grotesque Demon in the cave. \"Today,\" it rasps, " "\"you will fight and surely die. But I will give you a choice of deaths. You " "may fight me, or you may fight my servants. Do you prefer to fight my " "servants?\"" msgstr "" "Du finder en mægtig og grotesk dæmon i hulen. \"I dag,\" rasper den, \"skal " "du kæmpe og uden tvivl dø. Men hvordan du skal dø, vil jeg lade dig vælge. \n" "Du kan kæmpe mod mig, eller du kan kæmpe mod mine tjenere. \n" "Foretrækker du at kæmpe mod mine tjenere?\"" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points." msgstr "" "Dæmonen skriger udfordrende og angriber! Efter en kort, desperat kamp dræber " "du monsteret og modtager %{exp} erfaringspoint." msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points and " "%{count} gold." msgstr "" "Dæmonen skriger udfordrende og angriber! Efter en kort, desperat kamp dræber " "du monsteret og modtager %{exp} erfaringspoint og %{count} guldstykker." msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and find the %{art} in the back of the cave." msgstr "" "Dæmonen skriger udfordrende og angriber! Efter en kort, desperat kamp dræber " "du monsteret og finder artefaktet %{art} bagerst i hulen." msgid "" "Seeing that you do not have %{count} gold, the demon slashes you with its " "claws, and the last thing you see is a red haze." msgstr "" "Da dæmonen indser, at du ikke har %{count} guldstykker, hugger den dig med " "sine kløer, og det sidste, du ser, er en rød tÃ¥ge." msgid "" "The Demon leaps upon you and has its claws at your throat before you can " "even draw your sword. \"Your life is mine,\" it says. \"I will sell it back " "to you for %{count} gold.\"" msgstr "" "Dæmonen springer pÃ¥ dig og dens kløer er om din strube, før du overhovedet " "nÃ¥r at trække dit sværd. \"Du er min,\" hvæser den. \"Men jeg vil lade dig " "gÃ¥ med livet i behold for %{count} guldstykker.\"" msgid "" "Upon defeating the daemon's servants, you find a hidden cache with %{count} " "gold." msgstr "" "Efter at have besejret dæmonens tjenere, finder du et skjult lager med " "%{count} guldstykker i." msgid "" "As you enter the Alchemist's Tower, a hobbled, graying man in a brown cloak " "makes his way towards you." msgstr "" "NÃ¥r du træder ind i alkymisttÃ¥rnet, kommer en humpende, grÃ¥nende mand i en " "brun kappe hen imod dig." msgid "He checks your pack, and sees that you have 1 cursed item." msgid_plural "" "He checks your pack, and sees that you have %{count} cursed items." msgstr[0] "Han tjekker din sæk og ser, at du har 1 forbandet artefakt." msgstr[1] "" "Han tjekker din sæk og ser, at du har %{count} forbandede artefakter." msgid "For %{gold} gold, the alchemist will remove it for you. Do you pay?" msgid_plural "" "For %{gold} gold, the alchemist will remove them for you. Do you pay?" msgstr[0] "" "For %{gold} guldstykker vil alkymisten fjerne det for dig. Vil du betale?" msgstr[1] "" "For %{gold} guldstykker vil alkymisten fjerne dem for dig. Vil du betale?" msgid "" "After you consent to pay the requested amount of gold, the alchemist grabs " "the cursed artifact and throws it into his magical cauldron." msgid_plural "" "After you consent to pay the requested amount of gold, the alchemist grabs " "all cursed artifacts and throws them into his magical cauldron." msgstr[0] "" "Efter at have givet samtykke til at betale den ønskede mængde guld, griber " "alkymisten det forbandede artefakt og smider den i sin magiske kedel." msgstr[1] "" "Efter at have givet samtykke til at betale den ønskede mængde guld, griber " "alkymisten de forbandede artefakter og smider dem i sin magiske kedel." msgid "" "You hear a voice from behind the locked door, \"You don't have enough gold " "to pay for my services.\"" msgstr "" "Du hører en stemme bag den lÃ¥ste dør, \"Du har ikke guld nok til at betale " "for mine tjenester.\"" msgid "" "You hear a voice from high above in the tower, \"Go away! I can't help you!\"" msgstr "" "Du hører en stemme fra højt oppe i tÃ¥rnet, \"GÃ¥ væk! Jeg kan ikke hjælpe dig!" "\"" msgid "" "The head groom speaks to you, \"That is a fine looking horse you have. I am " "afraid we can give you no better, but the horses your cavalry are riding " "look to be of poor breeding stock. We have many trained war horses which " "would aid your riders greatly. I insist you take them.\"" msgstr "" "Overstaldkarlen taler til dig: \"Det er en flot hest, du har. Jeg er bange " "for, at vi ikke kan give dig noget bedre, men de heste, dine ryttere rider " "pÃ¥, ser ud til at være af dÃ¥rlig avlsbestand. Vi har mange trænede " "krigsheste, som virkelig kunne gavne dine ryttere at bruge. Jeg insisterer " "pÃ¥, at du tager dem.\"" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, he will " "grow tired in a week. You must also let me give better horses to your " "mounted soldiers, their horses look shoddy and weak.\"" msgstr "" "Da du nærmer dig stalden, dukker overstaldskarlen op, mens han fører en flot " "krigshest. \"Denne hingst vil hjælpe dig med at rejse længere om dagen. Ak, " "han bliver dog træt inden ugen er omme. \n" "Du skal ogsÃ¥ lade mig give bedre heste til dine ridende soldater, deres " "heste ser sjuskede og svage ud.\"" msgid "" "The head groom approaches you and speaks, \"You already have a fine horse, " "and have no inexperienced cavalry which might make use of our trained war " "horses.\"" msgstr "" "Overstaldkarlen taler til dig: \"Du har allerede en fin hest og har ingen " "uerfarne ryttere, som kan gøre brug af vores trænede krigsheste.\"" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, his " "endurance will wane with a lot of heavy riding, and you must return for a " "fresh mount in a week. We also have many fine war horses which could benefit " "mounted soldiers, but you have none we can help.\"" msgstr "" "Da du nærmer dig stalden, dukker overstaldkarlen op, mens han fører en flot " "krigshest. \"Denne hingst vil hjælpe dig med at fremskynde dig pÃ¥ dine " "rejser. Ak, hans udholdenhed vil aftage, inden ugen er omme.\n" "Vi har ogsÃ¥ mange fine krigsheste, som kunne gavne beredne soldater, men du " "har ingen, vi kan hjælpe.\"" msgid "The Arena guards turn you away." msgstr "Arena-vagterne afviser dig." msgid "" "You have your crew stop up their ears with wax before the sirens' eerie song " "has any chance of luring them to a watery grave." msgstr "" "Du fÃ¥r dit mandskab til at stoppe deres ører med voks, før sirenernes " "uhyggelige sang har nogen chance for at lokke dem til en vandig grav." msgid "" "As the sirens sing their eerie song, your small, determined army manages to " "overcome the urge to dive headlong into the sea." msgstr "" "Mens sirenerne synger deres uhyggelige sang, formÃ¥r din lille, beslutsomme " "hær at overvinde trangen til at springe hovedkulds i havet." msgid "" "An eerie wailing song emanates from the sirens perched upon the rocks. Many " "of your crew fall under its spell, and dive into the water where they drown. " "You are now wiser for the visit, and gain %{exp} experience." msgstr "" "En uhyggelig klagesang udgÃ¥r fra sirenerne, der ligger pÃ¥ klipperne. Mange " "af dit mandskab falder under dens fortryllelse og dykker ned i vandet, hvor " "de drukner. Du er nu klogere ved dette besøg, og fÃ¥r %{exp} erfaringspoint." msgid "" "In a dazzling display of daring, you break into the local jail and free the " "hero imprisoned there, who, in return, pledges loyalty to your cause." msgstr "" "I en blændende demonstration af vovemod bryder du ind i det lokale fængsel " "og befrier helten, der er fængslet dér, og som til gengæld lover loyalitet " "over for din sag." msgid "" "You already have %{count} heroes, and regretfully must leave the prisoner in " "this jail to languish in agony for untold days." msgstr "" "Du har allerede %{count} helte, og du mÃ¥ desværre efterlade fangen i dette " "fængsel, for at sygne hen i smerte i utallige dage." msgid "" "You enter a rickety hut and talk to the magician who lives there. He tells " "you of places near and far which may aid you in your journeys." msgstr "" "Du træder ind i en vakkelvorn hytte og taler med spÃ¥manden, som bor der. Han " "fortæller dig om steder nær og fjern, som kan hjælpe dig pÃ¥ dine rejser." msgid "This eye seems to be intently studying its surroundings." msgstr "Dette øje ser ud til nøje at studere sine omgivelser." msgid "You come across a giant Sphinx. The Sphinx remains strangely quiet." msgstr "Du støder pÃ¥ en kæmpe sfinks. Sfinksen forbliver underlig stille." msgid "" "\"I have a riddle for you,\" the Sphinx says. \"Answer correctly, and you " "shall be rewarded. Answer incorrectly, and you shall be eaten. Do you accept " "the challenge?\"" msgstr "" "\"Jeg har en gÃ¥de til dig,\" siger sfinksen. \"Svar rigtigt, og du skal " "blive belønnet. Svar forkert, og du skal blive spist. \n" "Tager du imod udfordringen?\"" msgid "" "The Sphinx asks you the following riddle:\n" "\n" "'%{riddle}'\n" "\n" "Your answer?" msgstr "" "Sfinksen stiller dig følgende gÃ¥de:\n" "\n" "'%{riddle}'\n" "\n" "Dit svar?" msgid "" "\"You guessed incorrectly,\" the Sphinx says, smiling. The Sphinx swipes at " "you with a paw, knocking you to the ground. Another blow makes the world go " "black, and you know no more." msgstr "" "\"Du gættede forkert,\" siger sfinksen og smiler. Sfinksen stryger mod dig " "med en pote og slÃ¥r dig til jorden. Endnu et slag fÃ¥r verden til at gÃ¥ i " "sort, og du ved ikke mere." msgid "" "Looking somewhat disappointed, the Sphinx sighs. \"You've answered my riddle " "so here's your reward. Now begone.\"" msgstr "" "Sukkende siger den noget skuffede sfinks: \"Du har løst min gÃ¥de, sÃ¥ her er " "din belønning. Forsvind sÃ¥!\"" msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "As you speak the magic word, the glowing barrier dissolves into nothingness." msgstr "" "En magisk barriere stÃ¥r højt foran dig og blokerer din vej. Runer pÃ¥ buen " "siger,\n" "\"Sig nøgleordet, og du kan passere.\"\n" "Idet du siger det magiske nøgleord, opløses den glødende barriere i " "intetheden." msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "You speak, and nothing happens." msgstr "" "En magisk barriere stÃ¥r højt foran dig og blokerer din vej. Runer pÃ¥ buen " "siger,\n" "\"Sig nøgleordet, og du kan passere.\"\n" "Du udtaler noget, men der sker intet." msgid "" "You enter the tent and see an old woman gazing into a magic gem. She looks " "up and says,\n" "\"In my travels, I have learned much in the way of arcane magic. A great " "oracle taught me his skill. I have the answer you seek.\"" msgstr "" "Du gÃ¥r ind i teltet og ser en gammel kvinde stirre ind i en magisk ædelsten. " "Hun kigger op og siger:\n" "\"PÃ¥ mine rejser har jeg lært meget om mystisk magi. Et stort orakel lærte " "mig sine færdigheder. Jeg har svaret, du søger.\"" msgid "No spell book is present." msgstr "Der er ingen magibog at finde." msgid "" "This spell costs %{mana} spell points. You have no spell points, so you " "cannot cast it." msgstr "" "Denne besværgelse koster %{mana} mana. Du har ingen mana, sÃ¥ du kan ikke " "kaste den." msgid "" "This spell costs %{mana} spell points. You only have %{point} spell points, " "so you cannot cast it." msgstr "" "Denne besværgelse koster %{mana} mana. Du har kun %{point} mana, sÃ¥ du kan " "ikke kaste den." msgid "The spell was not found." msgstr "Besværgelsen blev ikke fundet." msgid "Only heroes can cast this spell." msgstr "Kun helte kan kaste denne magi." msgid "This hero is not able to cast adventure spells." msgstr "Denne helt er ikke i stand til at kaste eventyrmagi." msgid "Your hero is too tired to cast this spell today. Try again tomorrow." msgstr "" "Din helt er for træt til at kaste denne besværgelse i dag. Prøv igen i " "morgen." msgid "This spell cannot be cast on a boat." msgstr "Denne besværgelse kan ikke kastes ombord pÃ¥ et skib." msgid "This spell can only be cast near an ocean." msgstr "Denne besværgelse kan kun kastes i nærheden af et hav." msgid "" "There are no boats available and no ocean adjacent to the hero where this " "spell will work." msgstr "" "Der er hverken et hav ved siden af helten eller nogen tilgængelige skibe at " "hidkalde." msgid "There are no boats available for this spell." msgstr "Der er ingen tilgængelige skibe at hidkalde." msgid "There is no ocean adjacent to the hero where this spell will work." msgstr "" "Der er intet hav ved siden af helten, hvor denne besværgelse vil virke." msgid "" "You do not own any town or castle that is not currently occupied by a hero. " "This spell will have no effect." msgstr "" "Du ejer ikke nogen by eller borg, der ikke i øjeblikket er besat af en helt. " "Denne besværgelse har ingen effekt." msgid "This hero is already in a town, so this spell will have no effect." msgstr "Denne helt er allerede i en by, sÃ¥ denne besværgelse har ingen effekt." msgid "" "The nearest town is %{town}.\n" "\n" "This town is occupied by your hero %{hero}." msgstr "" "Den nærmeste by er %{town}.\n" "\n" "Denne by er besat af din helt %{hero}." msgid "This spell is already in effect." msgstr "Denne besværgelse er allerede trÃ¥dt i kraft." msgid "" "No opponent neither has nor can have any hero under their command anymore. " "Casting this spell will have no effect." msgstr "" "Ingen modstander hverken har eller kan have nogen helt under deres kommando " "længere. At kaste denne besværgelse, vil ikke have nogen effekt." msgid "" "No opponent has a hero under their command at this time. Casting this spell " "will have no effect." msgstr "" "Ingen modstander har en helt under deres kommando pÃ¥ nuværende tidspunkt. At " "kaste denne besværgelse vil ikke have nogen effekt." msgid "" "You must be within %{count} spaces of a monster for the Visions spell to " "work." msgstr "" "Du skal være inden for %{count} mellemrum fra et væsen, før visions-" "besværgelsen kan virke." msgid "" "You must be standing on the entrance to a mine (sawmills and alchemist labs " "do not count) to cast this spell." msgstr "" "Du skal stÃ¥ ved indgangen til en mine (savværker og alkymistlaboratorier " "tæller ikke med), for at kaste denne besværgelse." msgid "You must first defeat the ghosts guarding the mine to cast this spell." msgstr "" "Du skal først besejre spøgelserne, der vogter minen, for at kaste denne " "besværgelse." msgid "" "There are already at least as many elementals guarding the mine as this hero " "can generate. Casting this spell will have no effect." msgstr "" "Der er allerede mindst lige sÃ¥ mange elementaler, der bevogter minen, som " "denne helt kan generere. At kaste denne besværgelse vil ikke have nogen " "effekt." msgid "%{name} the %{race} (Level %{level})" msgstr "%{race}en %{name} (Level %{level})" #, fuzzy msgid "" "'Spread' combat formation spreads the hero's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "'spredt' kampformation spreder dine tropper fra top til bund pÃ¥ slagmarken, " "med mindst én tom plads mellem hver enhed." #, fuzzy msgid "" "'Grouped' combat formation bunches the hero's army together in the center of " "their side of the battlefield." msgstr "" "'gruppe'-formation samler dine tropper i midten af din side pÃ¥ slagmarken." msgid "Are you sure you want to dismiss this Hero?" msgstr "Er du sikker pÃ¥, du vil bortvise denne helt?" msgid "View Experience Info" msgstr "Vis erfaringsinfo" msgid "View Spell Points Info" msgstr "Vis info om mana" msgid "Set army combat formation to 'Spread'" msgstr "Sæt hærens kampformation til 'spredt'" msgid "Set army combat formation to 'Grouped'" msgstr "Sæt hærens kampformation til 'grupperet'" msgid "Exit Hero Screen" msgstr "Forlad helteskærm" msgid "You cannot dismiss a hero in a castle" msgstr "Du kan ikke bortvise en helt, som befinder sig i en borg." msgid "Dismissal of %{name} the %{race} is prohibited by scenario" msgstr "Afvisning af %{race}en %{name} er ifølge scenariet forbudt" msgid "Dismiss %{name} the %{race}" msgstr "Bortvis %{race}en %{name}" msgid "Show previous hero" msgstr "Vis forrige helt" msgid "Show next hero" msgstr "Vis næste helt" msgid "Blood Morale" msgstr "Blodsmoral" msgid "%{morale} Morale" msgstr "%{morale} moral" msgid "%{luck} Luck" msgstr "%{luck} held" msgid "Current Luck Modifiers:" msgstr "PÃ¥virkninger pÃ¥ held:" msgid "Current Morale Modifiers:" msgstr "PÃ¥virkninger pÃ¥ moral:" msgid "Entire army is undead, so morale does not apply." msgstr "Hele hæren er udød, sÃ¥ den pÃ¥virkes ikke af moral." msgid "" "Current experience %{exp1}.\n" " Next level %{exp2}." msgstr "" "Nuværende erfaringspoint %{exp1}.\n" " Næste level %{exp2}." msgid "Level %{level}" msgstr "Level %{level}" #, fuzzy msgid "" "%{name} currently has %{point} spell points out of a maximum of %{max}. The " "maximum number of spell points is 10 times the hero's knowledge. It is " "occasionally possible for the hero to have more than their maximum spell " "points via special events." msgstr "" "%{name} har i øjeblikket %{point} mana ud af et maksimum pÃ¥ %{max}. Det " "maksimale antal mana er 10 gange din kundskabspoint. Det er lejlighedsvis " "muligt, at have mere end dine maksimale mana gennem særlige hændelser." msgid "%{name1} meets %{name2}" msgstr "%{name1} møder %{name2}" msgid "Enemy heroes are now fully identifiable." msgstr "Fjendtlige helte er nu fuldt identificerbare." msgid "Identify Hero" msgstr "Identificer helt" msgid "Select town to port to." msgstr "Vælg den by, du gerne vil havne i." msgid "Town Portal" msgstr "Byportal" msgid "The creatures are willing to join us!" msgstr "Disse væsener er villige til at forene sig med os!" msgid "All the creatures will join us..." msgstr "Alle disse væsener vil gerne forene sig med os..." msgid "The creature will join us..." msgid_plural "%{count} of the creatures will join us..." msgstr[0] "Væsenet vil gerne forene sig med os..." msgstr[1] "%{count} af disse væsener vil gerne forene sig med os..." msgid "" "\n" " for a fee of %{gold} gold." msgstr "" "\n" "til en pris af %{gold} guldstykker." msgid "These weak creatures will surely flee before us." msgstr "Disse svage væsener vil sikkert flygte fra os." msgid "I fear these creatures are in the mood for a fight." msgstr "Jeg frygter at disse væsener er i humør til at slÃ¥s." #, fuzzy msgid "The hero's attack skill is a bonus added to each unit's attack skill." msgstr "" "Din angrebsevne er en bonus, der lægges til hver af dine troppers " "angrebsevner." #, fuzzy msgid "The hero's defense skill is a bonus added to each unit's defense skill." msgstr "" "Din forsvarsevne er en bonus, der lægges til hver af dine troppers " "forsvarsevner." #, fuzzy msgid "The hero's spell power determines the duration or power of a spell." msgstr "Din magikraft bestemmer dine besværgelsers styrke og varighed." #, fuzzy msgid "" " The hero's knowledge determines how many spell points the hero may have. " "Under normal circumstances, a hero is limited to 10 spell points per level " "of knowledge." msgstr "" "Din kundskab bestemmer hvor meget mana din helt mÃ¥ have. Under normale " "omstændigheder er en helt begrænset til at have 10 mana per level i kundskab." msgid "Current Modifiers:" msgstr "Nuværende modifikationer" msgid "skill|Basic" msgstr "Begynder" msgid "skill|Advanced" msgstr "Øvet" msgid "skill|Expert" msgstr "Ekspert" msgid "Pathfinding" msgstr "Stifinding" msgid "Archery" msgstr "Skydning" msgid "Logistics" msgstr "Logistik" msgid "Scouting" msgstr "Udspejdning" msgid "Diplomacy" msgstr "Diplomati" msgid "Navigation" msgstr "Navigation" msgid "Leadership" msgstr "Lederskab" msgid "Wisdom" msgstr "Visdom" msgid "Mysticism" msgstr "Mysticisme" msgid "Ballistics" msgstr "Ballistik" msgid "Eagle Eye" msgstr "Ørneblik" msgid "Necromancy" msgstr "Nekromantik" msgid "Estates" msgstr "Adelskab" msgid "Advanced Archery" msgstr "Øvet skydning" msgid "Advanced Pathfinding" msgstr "Øvet stifinding" msgid "Basic Archery" msgstr "Begynder skydning" msgid "Basic Pathfinding" msgstr "Begynder stifinding" msgid "Expert Pathfinding" msgstr "Ekspert stifinding" msgid "Advanced Logistics" msgstr "Øvet logistik" msgid "Basic Logistics" msgstr "Begynder logistik" msgid "Basic Scouting" msgstr "Begynder udspejdning" msgid "Expert Archery" msgstr "Ekspert skydning" msgid "Expert Logistics" msgstr "Ekspert logistik" msgid "Advanced Diplomacy" msgstr "Øvet diplomati" msgid "Advanced Scouting" msgstr "Øvet udspejdning" msgid "Basic Diplomacy" msgstr "Begynder diplomati" msgid "Expert Diplomacy" msgstr "Ekspert diplomati" msgid "Expert Scouting" msgstr "Ekspert udspejdning" msgid "Advanced Leadership" msgstr "Øvet lederskab" msgid "Advanced Navigation" msgstr "Øvet navigation" msgid "Basic Leadership" msgstr "Begynder lederskab" msgid "Basic Navigation" msgstr "Begynder navigation" msgid "Expert Navigation" msgstr "Ekspert navigation" msgid "Advanced Wisdom" msgstr "Øvet visdom" msgid "Basic Mysticism" msgstr "Begynder mysticisme" msgid "Basic Wisdom" msgstr "Begynder visdom" msgid "Expert Leadership" msgstr "Ekspert lederskab" msgid "Expert Wisdom" msgstr "Ekspert visdom" msgid "Advanced Luck" msgstr "Øvet held" msgid "Advanced Mysticism" msgstr "Øvet mysticisme" msgid "Basic Luck" msgstr "Begynder held" msgid "Expert Luck" msgstr "Ekspert held" msgid "Expert Mysticism" msgstr "Ekspert mysticisme" msgid "Advanced Ballistics" msgstr "Øvet ballistik" msgid "Advanced Eagle Eye" msgstr "Øvet ørneblik" msgid "Basic Ballistics" msgstr "Begynder ballistik" msgid "Basic Eagle Eye" msgstr "Begynder ørneblik" msgid "Expert Ballistics" msgstr "Ekspert ballistik" msgid "Advanced Necromancy" msgstr "Øvet nekromantik" msgid "Basic Estates" msgstr "Begynder adelskab" msgid "Basic Necromancy" msgstr "Begynder nekromantik" msgid "Expert Eagle Eye" msgstr "Ekspert ørneblik" msgid "Expert Necromancy" msgstr "Ekspert nekromantik" msgid "Advanced Estates" msgstr "Øvet adelskab" msgid "Expert Estates" msgstr "Ekspert adelskab" msgid "" "%{skill} reduces the movement penalty for rough terrain by %{count} percent." msgstr "" "%{skill} reducerer bevægelsesbesvær i ujævnt terræn med %{count} procent." msgid "%{skill} eliminates the movement penalty for rough terrain." msgstr "%{skill} eliminerer bevægelsesbesvær i ujævnt terræn." #, fuzzy msgid "" "%{skill} increases the damage done by the hero's range attacking creatures " "by %{count} percent." msgstr "" "%{skill} øger skaden forÃ¥rsaget af skydende væsner med %{count} procent." #, fuzzy msgid "%{skill} increases the hero's movement points by %{count} percent." msgstr "%{skill} øger din helts bevægelsespoint med %{count} procent." #, fuzzy msgid "%{skill} increases the hero's viewable area by one square." msgid_plural "%{skill} increases the hero's viewable area by %{count} squares." msgstr[0] "%{skill} øger din helts sigtbarhed med én firkant." msgstr[1] "%{skill} øger din helts sigtbarhed med %{count} firkanter." #, fuzzy msgid "" "%{skill} allows the hero to negotiate with monsters who are weaker than " "their army, and reduces the cost of surrender." msgstr "" "%{skill} giver dig mulighed for at forhandle med grupper af væsener, der er " "svagere end din egen. " #, fuzzy msgid "" "Approximately %{count} percent of the creatures may offer to join the hero." msgstr "" "Cirka %{count} procent af væsenerne kan tilbyde at tilslutte sig din hær." #, fuzzy msgid "All of the creatures may offer to join the hero." msgstr "Alle disse væsener er villige til at slutte sig til dig." #, fuzzy msgid "" "The cost of surrender is reduced to %{percent} percent of the total cost of " "troops in the army." msgstr "" "%{name} reducerer prisen for overgivelse til %{count}% af de samlede " "omkostninger for tropper, du har i din hær." #, fuzzy msgid "" "%{skill} increases the hero's movement points over water by %{count} percent." msgstr "" "%{skill} øger din helts daglige bevægelsespoint over vand med %{count} " "procent." #, fuzzy msgid "%{skill} increases the hero's troops morale by %{count}." msgstr "%{skill} øger din helts moral med %{count} point." #, fuzzy msgid "%{skill} allows the hero to learn third level spells." msgstr "%{skill} giver din helt mulighed for at lære magi pÃ¥ tredje niveau." #, fuzzy msgid "%{skill} allows the hero to learn fourth level spells." msgstr "%{skill} giver din helt mulighed for at lære magi pÃ¥ fjerde niveau." #, fuzzy msgid "%{skill} allows the hero to learn fifth level spells." msgstr "%{skill} giver din helt mulighed for at lære magi pÃ¥ femte niveau." #, fuzzy msgid "%{skill} regenerates one additional spell point per day to the hero." msgid_plural "" "%{skill} regenerates %{count} additional spell points per day to the hero." msgstr[0] "%{skill} regenerer ét af din helts mana per dag." msgstr[1] "%{skill} regenerer %{count} af din helts mana per dag." #, fuzzy msgid "%{skill} increases the hero's luck by %{count}." msgstr "%{skill} øger din helts held med %{count} point." #, fuzzy msgid "" "%{skill} gives the hero's catapult shots a greater chance to hit and do " "damage to castle walls." msgstr "" "%{skill} giver din helts katapultskud en større chance for at ramme og gøre " "skade pÃ¥ borgens mure." #, fuzzy msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot has a " "greater chance to hit and do damage to castle walls." msgstr "" "%{skill} giver din helts katapult et ekstra skud, og hvert skud har en " "større chance for at ramme og skade borgens mure." #, fuzzy msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot " "automatically destroys any wall, except a fortified wall in a Knight castle." msgstr "" "%{skill} giver din helts katapult et ekstra skud, og hvert skud ødelægger " "automatisk enhver mur, undtagen en befæstet mur i en ridderborg." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 1st or " "2nd level spell that was cast by an enemy during combat." msgstr "" "%{skill} giver %{count} procent chance, for at lære en given besværgelse af " "1-2.niveau, som en fjendtlig helt har kastet i løbet af en kamp mod din helt." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 3rd " "level spell (or below) that was cast by an enemy during combat." msgstr "" "%{skill} giver %{count} procent chance, for at lære en given besværgelse af " "1-3.niveau, som en fjendtlig helt har kastet i løbet af en kamp mod din helt." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 4th " "level spell (or below) that was cast by an enemy during combat." msgstr "" "%{skill} giver %{count} procent chance, for at lære en given besværgelse af " "1-4.niveau, som en fjendtlig helt har kastet i løbet af en kamp mod din helt." msgid "" "%{skill} allows %{count} percent of the creatures killed in combat to be " "brought back from the dead as Skeletons." msgstr "" "%{skill} tillader %{count} procent af de væsener, der blev dræbt i kamp, at " "blive bragt tilbage fra de døde som skeletter." #, fuzzy msgid "" "The hero produces %{count} gold pieces per day as tax revenue from estates." msgstr "" "Med sit adelskab producerer din helt %{count} guldstykker om dagen som " "skatteindtægter ." msgid "Blue" msgstr "BlÃ¥" msgid "Green" msgstr "Grøn" msgid "Red" msgstr "Rød" msgid "Yellow" msgstr "Gul" msgid "Orange" msgstr "Orange" msgid "Purple" msgstr "Lilla" msgid "barrier|Aqua" msgstr "LyseblÃ¥" msgid "barrier|Blue" msgstr "BlÃ¥" msgid "barrier|Brown" msgstr "Brun" msgid "barrier|Gold" msgstr "Guld" msgid "barrier|Green" msgstr "Grøn" msgid "barrier|Orange" msgstr "Orange" msgid "barrier|Purple" msgstr "Lilla" msgid "barrier|Red" msgstr "Rød" msgid "tent|Aqua" msgstr "LyseblÃ¥t" msgid "tent|Blue" msgstr "BlÃ¥t" msgid "tent|Brown" msgstr "Brunt" msgid "tent|Gold" msgstr "Guld" msgid "tent|Green" msgstr "Grønt" msgid "tent|Orange" msgstr "Orange" msgid "tent|Purple" msgstr "Lilla" msgid "tent|Red" msgstr "Rødt" msgid "Experience" msgstr "Erfaringspoint" msgid "" "Experience allows your heroes to go up levels, increasing their primary and " "secondary skills." msgstr "" "Erfaringspoint giver dine helte mulighed for at stige i niveau, og derved " "øge deres primære og sekundære evner." msgid "Hero/Stats" msgstr "Helt/Egenskaber" msgid "Skills" msgstr "Evner" msgid "Town/Castle" msgstr "By/Borg" msgid "Garrison" msgstr "Garnison" msgid "Gold Per Day:" msgstr "Guld per dag:" msgid "View Heroes." msgstr "Vis helte." msgid "Towns/Castles" msgstr "Byer/Borge" msgid "View Towns and Castles." msgstr "Vis byer og borge." msgid "luck|Cursed" msgstr "Forbandet" msgid "luck|Awful" msgstr "Elendig" msgid "luck|Bad" msgstr "DÃ¥rlig" msgid "luck|Normal" msgstr "Jævn" msgid "luck|Good" msgstr "God" msgid "luck|Great" msgstr "Fremragende" msgid "luck|Irish" msgstr "Svineheldig" #, fuzzy msgid "" "Bad luck sometimes falls on the hero's units in combat, causing their " "attacks to only do half damage." msgstr "" "Nogle gange bliver dine tropper ramt af uheld i kamp, hvilket fÃ¥r deres " "angreb til kun at gøre halv skade mod fjenden." #, fuzzy msgid "" "Neutral luck means the hero's units will never get lucky or unlucky attacks " "on the enemy." msgstr "" "Neutralt held betyder, at dine tropper hverken fÃ¥r heldige eller uheldige " "angreb mod fjenden." #, fuzzy msgid "" "Good luck sometimes lets the hero's units get lucky attacks (double " "strength) in combat." msgstr "" "Nogle gange bliver dine tropper ramt af held og lykke. Heldige angreb " "fordobler skaden mod fjender." msgid "morale|Treason" msgstr "Horribel" msgid "morale|Awful" msgstr "Elendig" msgid "morale|Poor" msgstr "DÃ¥rlig" msgid "morale|Normal" msgstr "Jævn" msgid "morale|Good" msgstr "God" msgid "morale|Great" msgstr "Fremragende" msgid "morale|Blood!" msgstr "Blodtørstende!" #, fuzzy msgid "Bad morale may cause the hero's units to freeze in combat." msgstr "DÃ¥rlig moral kan fÃ¥ dine tropper til at fryse i kamp." #, fuzzy msgid "" "Neutral morale means the hero's units will never be blessed with extra " "attacks or freeze in combat." msgstr "" "Neutral moral betyder, at dine tropper hverken velsignes med ekstra træk " "eller fryser i kamp." #, fuzzy msgid "Good morale may give the hero's units extra attacks in combat." msgstr "God moral kan give dine tropper ekstra træk i kamp." msgid "Knight" msgstr "Ridder" msgid "Barbarian" msgstr "Barbar" msgid "Sorceress" msgstr "Heks" msgid "Warlock" msgstr "Heksemester" msgid "Wizard" msgstr "Magiker" msgid "Necromancer" msgstr "Nekromantiker" msgid "Multi" msgstr "Flere" msgid "doubleLined|Knight" msgstr "Ridder" msgid "doubleLined|Barbarian" msgstr "Barbar" msgid "doubleLined|Sorceress" msgstr "Heks" msgid "doubleLined|Warlock" msgstr "" "Hekse-\n" "mester" msgid "doubleLined|Wizard" msgstr "Magiker" msgid "" "doubleLined|Necro-\n" "mancer" msgstr "" "Necro-\n" "mantiker" msgid "doubleLinedRace|Multi" msgstr "Flere" msgid "doubleLinedRace|Random" msgstr "Tilfældig" msgid "speed|Standing" msgstr "StÃ¥ende" msgid "speed|Crawling" msgstr "Kravlende" msgid "speed|Very Slow" msgstr "Sløv" msgid "speed|Slow" msgstr "Langsom" msgid "speed|Average" msgstr "Almindelig" msgid "speed|Fast" msgstr "Hurtig" msgid "speed|Very Fast" msgstr "Lynhurtig" msgid "speed|Ultra Fast" msgstr "Susende" msgid "speed|Blazing" msgstr "Stormende" msgid "speed|Instant" msgstr "Fantom" msgid "week|Squirrel" msgstr "egern" msgid "week|Rabbit" msgstr "hare" msgid "week|Gopher" msgstr "jordegern" msgid "week|Badger" msgstr "grævling" msgid "week|Rat" msgstr "rotte" msgid "week|Eagle" msgstr "ørn" msgid "week|Weasel" msgstr "væsel" msgid "week|Raven" msgstr "ravn" msgid "week|Mongoose" msgstr "mangust" msgid "week|Dog" msgstr "hund" msgid "week|Aardvark" msgstr "jordsvin" msgid "week|Lizard" msgstr "firben" msgid "week|Tortoise" msgstr "landskildpadde" msgid "week|Hedgehog" msgstr "pindsvin" msgid "week|Condor" msgstr "kondor" msgid "week|Ant" msgstr "myre" msgid "week|Grasshopper" msgstr "græshoppe" msgid "week|Dragonfly" msgstr "guldsmed" msgid "week|Spider" msgstr "edderkop" msgid "week|Butterfly" msgstr "sommerfugl" msgid "week|Bumblebee" msgstr "humlebi" msgid "week|Locust" msgstr "ørkengræshoppe" msgid "week|Earthworm" msgstr "regnorm" msgid "week|Hornet" msgstr "hveps" msgid "week|Beetle" msgstr "mariehøne" msgid "week|PLAGUE" msgstr "FORPESTET" msgid "week|Unnamed" msgstr "unavngivet" msgid "Desert" msgstr "Ørken" msgid "Snow" msgstr "Sne" msgid "Wasteland" msgstr "Ødemark" msgid "Beach" msgstr "Strand" msgid "Lava" msgstr "Lava" msgid "Dirt" msgstr "Jord" msgid "Grass" msgstr "Græs" msgid "Ocean" msgstr "Hav" msgid "maps|Small" msgstr "Lille" msgid "maps|Medium" msgstr "Mellemstor" msgid "maps|Large" msgstr "Stor" msgid "maps|Extra Large" msgstr "Ekstra stor" msgid "maps|Custom Size" msgstr "Tilpasset størrelse" msgid "Ore Mine" msgstr "Malmmine" msgid "Sulfur Mine" msgstr "Svovlmine" msgid "Crystal Mine" msgstr "Krystalmine" msgid "Gems Mine" msgstr "Juvelmine" msgid "Gold Mine" msgstr "Guldmine" msgid "Mine" msgstr "Mine" msgid "Burma shave." msgstr "Barbercreme." msgid "Next sign 50 miles." msgstr "Næste skilt 80 km." msgid "See Rock City." msgstr "Se Stenby." msgid "This space for rent." msgstr "Skilt til udlejning." msgid "No object" msgstr "Intet objekt" msgid "Alchemist Lab" msgstr "Alkymist laboratorium" msgid "Sign" msgstr "Skilt" msgid "Buoy" msgstr "Bøje" msgid "Skeleton" msgstr "Skelet" msgid "Daemon Cave" msgstr "Dæmongrotte" msgid "Treasure Chest" msgstr "Skattekiste" msgid "Faerie Ring" msgstr "Fe-ring" msgid "Campfire" msgstr "LejrbÃ¥l" msgid "Fountain" msgstr "Springvand" msgid "Gazebo" msgstr "Lysthus" msgid "Genie Lamp" msgstr "Magisk lampe" msgid "Archer's House" msgstr "Bueskyttes hus" msgid "Goblin Hut" msgstr "Vættehytte" msgid "Dwarf Cottage" msgstr "Dværge-kottage" msgid "Peasant Hut" msgstr "Bondehytte" msgid "Stables" msgstr "Stalde" msgid "Alchemist's Tower" msgstr "AlkymisttÃ¥rn" msgid "Event" msgstr "Begivenhed" msgid "Dragon City" msgstr "Drageby" msgid "Lighthouse" msgid_plural "Lighthouses" msgstr[0] "FyrtÃ¥rn" msgstr[1] "FyrtÃ¥rne" msgid "Water Wheel" msgid_plural "Water Wheels" msgstr[0] "Vandmølle" msgstr[1] "Vandmøller" msgid "Monster" msgstr "Væsen" msgid "Obelisk" msgstr "Obelisk" msgid "Oasis" msgstr "Oase" msgid "Resource" msgstr "Resurse" msgid "Sawmill" msgstr "Savmølle" msgid "Oracle" msgstr "Orakel" msgid "Shrine of the First Circle" msgstr "Helligdom for 1. cirkel" msgid "Shipwreck" msgstr "Skibsvrag" msgid "Sea Chest" msgstr "Søkiste" msgid "Desert Tent" msgstr "Ørkentelt" msgid "Stone Liths" msgstr "Monolit" msgid "Wagon Camp" msgstr "Vogn-lejr" msgid "Hut of the Magi" msgstr "Magikerens hytte" msgid "Whirlpool" msgstr "Virvelstrøm" msgid "Windmill" msgid_plural "Windmills" msgstr[0] "Vindmølle" msgstr[1] "Vindmøller" msgid "Artifact" msgstr "Artefakt" msgid "Mermaid" msgstr "Havfrue" msgid "Boat" msgstr "Skib" msgid "Random Ultimate Artifact" msgstr "Tilfældigt ultimativt artefakt" msgid "Random Artifact" msgstr "Tilfældigt artefakt" msgid "Random Resource" msgstr "Tilfældig resurse" msgid "Random Monster" msgstr "Tilfældigt væsen" msgid "Random Town" msgstr "Tilfældig by" msgid "Random Castle" msgstr "Tilfældig borg" msgid "Eye of the Magi" msgstr "Magikerens øje" msgid "Random Monster - weak" msgstr "Tilfældigt monster - svag" msgid "Random Monster - medium" msgstr "Tilfældigt monster - medium" msgid "Random Monster - strong" msgstr "Tilfældigt monster - stærk" msgid "Random Monster - very strong" msgstr "Tilfældigt monster - meget stærk" #, fuzzy msgid "Hero" msgstr "Helte" msgid "Nothing Special" msgstr "Intet særligt" msgid "Mossy Rock" msgstr "Mosset sten" msgid "Watch Tower" msgstr "UdkigstÃ¥rn" msgid "Tree House" msgstr "Træhus" msgid "Tree City" msgstr "Træby" msgid "Ruins" msgstr "Ruiner" msgid "Fort" msgstr "Fort" msgid "Abandoned Mine" msgstr "Forladt mine" msgid "Sirens" msgstr "Sirener" msgid "Standing Stones" msgstr "Bautastenring" msgid "Idol" msgstr "Stenidol" msgid "Tree of Knowledge" msgstr "Visdomstræ" msgid "Witch Doctor's Hut" msgstr "Heksedoktorens hytte" msgid "Temple" msgstr "Tempel" msgid "Hill Fort" msgstr "Bakkefort" msgid "Halfling Hole" msgstr "Halvlang-hul" msgid "Mercenary Camp" msgstr "Lejesoldaterlejr" msgid "Shrine of the Second Circle" msgstr "Helligdom for 2. cirkel" msgid "Shrine of the Third Circle" msgstr "Helligdom for 3. cirkel" msgid "City of the Dead" msgstr "De dødes by" msgid "Sphinx" msgstr "Sfinks" msgid "Wagon" msgstr "Vogn" msgid "Tar Pit" msgstr "Tjæresø" msgid "Artesian Spring" msgstr "Artesisk springvand" msgid "Troll Bridge" msgstr "Troldebro" msgid "Watering Hole" msgstr "Vandhul" msgid "Witch's Hut" msgstr "Heksens hytte" msgid "Xanadu" msgstr "Xanadu" msgid "Lean-To" msgstr "Shelter" msgid "Magellan's Maps" msgstr "Magellans kort" msgid "Flotsam" msgstr "Vraggods" msgid "Derelict Ship" msgstr "Forladt skib" msgid "Shipwreck Survivor" msgstr "Skibbruden" msgid "Bottle" msgstr "Flaske" msgid "Magic Well" msgstr "Magisk brønd" msgid "Magic Garden" msgid_plural "Magic Gardens" msgstr[0] "Magisk have" msgstr[1] "Magiske haver" msgid "Observation Tower" msgstr "ObservationstÃ¥rn" msgid "Freeman's Foundry" msgstr "Friborgeres støberi" msgid "Reefs" msgstr "Rev" msgid "Volcano" msgstr "Vulkan" msgid "Flowers" msgstr "Blomster" msgid "Rock" msgstr "Sten" msgid "Water Lake" msgstr "Indsø" msgid "Mandrake" msgstr "Alrune" msgid "Dead Tree" msgstr "Dødt træ" msgid "Stump" msgstr "Stub" msgid "Crater" msgstr "Krater" msgid "Cactus" msgstr "Kaktus" msgid "Mound" msgstr "Høj" msgid "Dune" msgstr "Klit" msgid "Lava Pool" msgstr "Lavasø" msgid "Shrub" msgstr "Buskads" msgid "Barrow Mounds" msgstr "Gravhøj" msgid "Random Artifact - Treasure" msgstr "Tilfældigt artefakt - Skat" msgid "Random Artifact - Minor" msgstr "Tilfældigt artefakt - Minor" msgid "Random Artifact - Major" msgstr "Tilfældigt artefakt - Major" msgid "Barrier" msgstr "Barriere" msgid "Traveller's Tent" msgstr "Rejsendes telt" msgid "Jail" msgstr "Fængsel" msgid "Fire Summoning Altar" msgstr "Ild-elementalers alter" msgid "Air Summoning Altar" msgstr "Luft-elementalers alter" msgid "Earth Summoning Altar" msgstr "Jord-elementalers alter" msgid "Water Summoning Altar" msgstr "Vand-elementalers alter" msgid "Unknown Monster" msgstr "Ukendt monster" msgid "Unknown Monsters" msgstr "Ukendte monstre" msgid "Peasant" msgstr "Bonde" msgid "Peasants" msgstr "Bønder" msgid "Archer" msgstr "Bueskytte" msgid "Archers" msgstr "Bueskyttere" # We don't have a proper word for a military trained "ranger" in danish... msgid "Ranger" msgstr "Skovridder" msgid "Rangers" msgstr "Skovriddere" msgid "Pikeman" msgstr "Pikenér" msgid "Pikemen" msgstr "Pikenérer" msgid "Veteran Pikeman" msgstr "Veteran pikenér" msgid "Veteran Pikemen" msgstr "Veteran pikenérer" msgid "Swordsman" msgstr "Sværdkæmper" msgid "Swordsmen" msgstr "Sværdkæmpere" msgid "Master Swordsman" msgstr "Sværdkampsmester" msgid "Master Swordsmen" msgstr "Sværdkampsmestre" msgid "Cavalries" msgstr "Ryttere" msgid "Cavalry" msgstr "Rytter" msgid "Champion" msgstr "Mesterrytter" msgid "Champions" msgstr "Mesterryttere" msgid "Paladin" msgstr "Paladin" msgid "Paladins" msgstr "Paladiner" msgid "Crusader" msgstr "Korsridder" msgid "Crusaders" msgstr "Korsriddere" msgid "Goblin" msgstr "Vætte" msgid "Goblins" msgstr "Vætter" msgid "Orc" msgstr "Ork" msgid "Orcs" msgstr "Orker" msgid "Orc Chief" msgstr "Orkhøvding" msgid "Orc Chiefs" msgstr "Orkhøvdinger" msgid "Wolf" msgstr "Ulv" msgid "Wolves" msgstr "Ulve" msgid "Ogre" msgstr "Jætte" msgid "Ogres" msgstr "Jætter" msgid "Ogre Lord" msgstr "Jætteherre" msgid "Ogre Lords" msgstr "Jætteherrer" msgid "Troll" msgstr "Trold" msgid "Trolls" msgstr "Trolde" msgid "War Troll" msgstr "Krigstrold" msgid "War Trolls" msgstr "Krigstrolde" msgid "Cyclopes" msgstr "Kykloper" msgid "Cyclops" msgstr "Kyklop" msgid "Sprite" msgstr "Fe" msgid "Sprites" msgstr "Feer" msgid "Dwarf" msgstr "Dværg" msgid "Dwarves" msgstr "Dværge" msgid "Battle Dwarf" msgstr "Dværgkæmper" msgid "Battle Dwarves" msgstr "Dværgkæmpere" msgid "Elf" msgstr "Elver" msgid "Elves" msgstr "Elvere" msgid "Grand Elf" msgstr "Storelver" msgid "Grand Elves" msgstr "Storelvere" msgid "Druid" msgstr "Druide" msgid "Druids" msgstr "Druider" msgid "Greater Druid" msgstr "Højdruide" msgid "Greater Druids" msgstr "Højdruider" msgid "Unicorn" msgstr "Enhjørning" msgid "Unicorns" msgstr "Enhjørninger" msgid "Phoenix" msgstr "Føniks" msgid "Phoenixes" msgstr "Fønikser" msgid "Centaur" msgstr "Kentaur" msgid "Centaurs" msgstr "Kentaurer" msgid "Gargoyle" msgstr "Gargoyle" msgid "Gargoyles" msgstr "Gargoyler" msgid "Griffin" msgstr "Grif" msgid "Griffins" msgstr "Griffe" msgid "Minotaur" msgstr "Minotaur" msgid "Minotaurs" msgstr "Minotaurer" msgid "Minotaur King" msgstr "Minotaurkonge" msgid "Minotaur Kings" msgstr "Minotaurkonger" msgid "Hydra" msgstr "Hydra" msgid "Hydras" msgstr "Hydraer" msgid "Green Dragon" msgstr "Grøndrage" msgid "Green Dragons" msgstr "Grøndrager" msgid "Red Dragon" msgstr "Røddrage" msgid "Red Dragons" msgstr "Røddrager" msgid "Black Dragon" msgstr "Sortdrage" msgid "Black Dragons" msgstr "Sortdrager" msgid "Halfling" msgstr "Halvlang" msgid "Halflings" msgstr "Halvlange" msgid "Boar" msgstr "Vildsvin" msgid "Boars" msgstr "Vildsvin" msgid "Iron Golem" msgstr "Jerngolem" msgid "Iron Golems" msgstr "Jerngolemmer" msgid "Steel Golem" msgstr "StÃ¥lgolem" msgid "Steel Golems" msgstr "StÃ¥lgolemmer" msgid "Roc" msgstr "Rok" msgid "Rocs" msgstr "Roker" msgid "Mage" msgstr "Troldmand" msgid "Magi" msgstr "Troldmænd" msgid "Archmage" msgstr "Ærketroldmand" msgid "Archmagi" msgstr "Ærketroldmænd" msgid "Giant" msgstr "Kæmpe" msgid "Giants" msgstr "Kæmper" msgid "Titan" msgstr "Titan" msgid "Titans" msgstr "Titaner" msgid "Skeletons" msgstr "Skeletter" msgid "Zombie" msgstr "Zombie" msgid "Zombies" msgstr "Zombier" msgid "Mutant Zombie" msgstr "Muteret zombie" msgid "Mutant Zombies" msgstr "Muterede zombier" msgid "Mummies" msgstr "Mumier" msgid "Mummy" msgstr "Mumie" msgid "Royal Mummies" msgstr "Royale mumier" msgid "Royal Mummy" msgstr "Royal mumie" msgid "Vampire" msgstr "Vampyr" msgid "Vampires" msgstr "Vampyrer" msgid "Vampire Lord" msgstr "Vampyrherre" msgid "Vampire Lords" msgstr "Vampyrherrer" msgid "Lich" msgstr "Lich" msgid "Liches" msgstr "Licher" msgid "Power Lich" msgstr "Kraftlich" msgid "Power Liches" msgstr "Kraftlicher" msgid "Bone Dragon" msgstr "Skeletdrage" msgid "Bone Dragons" msgstr "Skeletdrager" msgid "Rogue" msgstr "Slyngel" msgid "Rogues" msgstr "Slyngler" msgid "Nomad" msgstr "Nomade" msgid "Nomads" msgstr "Nomader" msgid "Ghost" msgstr "Spøgelse" msgid "Ghosts" msgstr "Spøgelser" msgid "Genie" msgstr "LampeÃ¥nd" msgid "Genies" msgstr "LampeÃ¥nder" msgid "Medusa" msgstr "Medusa" msgid "Medusas" msgstr "Medusaer" msgid "Earth Elemental" msgstr "Jord-elemental" msgid "Earth Elementals" msgstr "Jord-elementaler" msgid "Air Elemental" msgstr "Luft-elemental" msgid "Air Elementals" msgstr "Luft-elementaler" msgid "Fire Elemental" msgstr "Ild-elemental" msgid "Fire Elementals" msgstr "Ild-elementaler" msgid "Water Elemental" msgstr "Vand-elemental" msgid "Water Elementals" msgstr "Vand-elementaler" msgid "Random Monsters" msgstr "Tilfældige monstre" msgid "Random Monster 1" msgstr "Tilfældigt monster 1" msgid "Random Monsters 1" msgstr "Tilfældige monstre 1" msgid "Random Monster 2" msgstr "Tilfældigt monster 2" msgid "Random Monsters 2" msgstr "Tilfældige monstre 2" msgid "Random Monster 3" msgstr "Tilfældigt monster 3" msgid "Random Monsters 3" msgstr "Tilfældige monstre 3" msgid "Random Monster 4" msgstr "Tilfældigt monster 4" msgid "Random Monsters 4" msgstr "Tilfældige monstre 4" msgid "Double shot" msgstr "Dobbeltskud" msgid "2-hex monster" msgstr "2-feltsvæsen" msgid "Double strike" msgstr "Dobbeltslag" msgid "Double damage to Undead" msgstr "Dobbelt skade til udøde" msgid "% magic resistance" msgstr "% magiresistens" msgid "Immune to Mind spells" msgstr "Immun overfor sindsbesværgelser" msgid "Immune to Elemental spells" msgstr "Immun overfor elementalbesværgelser" msgid "Immune to Fire spells" msgstr "Immun overfor ildbesværgelser" msgid "Immune to Cold spells" msgstr "Immun overfor koldbesværgelser" msgid "Immune to " msgstr "Immun overfor " msgid "% immunity to %{spell} spell" msgstr "% immunitet overfor %{spell} besværgelse" msgid "% damage from Elemental spells" msgstr "% skade af elementalbesværgelser" msgid "% chance to Dispel beneficial spells" msgstr "% chance, for at fordrive gavnlige besværgelser" msgid "% chance to Paralyze" msgstr "% chance, for at lamme" msgid "% chance to Petrify" msgstr "% chance, for at forstene" msgid "% chance to Blind" msgstr "% chance, for at blinde" msgid "% chance to Curse" msgstr "% chance, for at forbande" msgid "% chance to cast %{spell} spell" msgstr "% chance, for at kaste en %{spell} besværgelse" msgid "HP regeneration" msgstr "Selvhelbredende" msgid "Two hexes attack" msgstr "2-feltsangreb" msgid "Flyer" msgstr "Kan flyve" msgid "Always retaliates" msgstr "SlÃ¥r altid igen" msgid "Attacks all adjacent enemies" msgstr "Angriber alle fjender omkring sig" msgid "No melee penalty" msgstr "Ingen nærkampsstraf" msgid "Dragon" msgstr "Drage" msgid "Undead" msgstr "Udød" msgid "No enemy retaliation" msgstr "Fjender kan ikke slÃ¥ igen" msgid "HP drain" msgstr "Stjæler liv" msgid "Cloud attack" msgstr "Skyangreb" msgid "Decreases enemy's morale by " msgstr "Sænker fjendens moral med " msgid "% chance to halve enemy" msgstr "% chance for at halvere fjenden" msgid "Soul Eater" msgstr "Sjælefortærer" msgid "Elemental" msgstr "Elementalt" msgid "No Morale" msgstr "Ingen moral" msgid "200% damage from Fire spells" msgstr "200% skade af ildbesværgelser" msgid "200% damage from Cold spells" msgstr "200% skade af koldbesværgelser" msgid "% damage from %{spell} spell" msgstr "% skade af %{spell} besværgelsen" msgid "% immunity to " msgstr "% immunitet overfor " msgid "Lightning" msgstr "Lyn" msgid "% damage from " msgstr "% skade af " msgid "The three Anduran artifacts magically combine into one." msgstr "Andurans tre artefakter forenes pÃ¥ magisk vis til ét." msgid "View Spells" msgstr "Vis besværgelser" msgid "View %{name} Info" msgstr "Vis %{name} info" msgid "Move %{name}" msgstr "Flyt %{name}" msgid "Cannot move the Spellbook" msgstr "Kan ikke flytte magibogen" msgid "This item can't be traded." msgstr "Magibogen er bundet til den enkelte helt." msgid "Invalid Artifact" msgstr "Ugyldigt artefakt" #, fuzzy msgid "The %{name} increases the hero's knowledge by %{count}." msgstr "%{name} øger din kundskab med %{count} point." msgid "Ultimate Book of Knowledge" msgstr "Den Ultimative Bog for Kundskab" #, fuzzy msgid "The %{name} increases the hero's attack skill by %{count}." msgstr "%{name} øger dine væseners angrebsevne med %{count} point." msgid "Ultimate Sword of Dominion" msgstr "Det Ultimative Herredømmesværd" #, fuzzy msgid "The %{name} increases the hero's defense skill by %{count}." msgstr "%{name} øger dine væseners forsvarsevne med %{count} point." msgid "Ultimate Cloak of Protection" msgstr "Den Ultimative Beskyttelseskappe" #, fuzzy msgid "The %{name} increases the hero's spell power by %{count}." msgstr "%{name} øger din magikraft med %{count} point." msgid "Ultimate Wand of Magic" msgstr "Den Ultimative Tryllestav" #, fuzzy msgid "" "The %{name} increases the hero's attack and defense skills by %{count} each." msgstr "%{name} øger bÃ¥de din angrebs- og forsvarsevne med %{count} point." msgid "Ultimate Shield" msgstr "Det Ultimative Skjold" #, fuzzy msgid "" "The %{name} increases the hero's spell power and knowledge by %{count} each." msgstr "%{name} øger bÃ¥de din magikraft og kundskab med %{count} point." msgid "Ultimate Staff" msgstr "Den Ultimative Stav" #, fuzzy msgid "" "The %{name} increases each of the hero's basic skills by %{count} points." msgstr "%{name} øger alle dine primære evner med %{count} point." msgid "Ultimate Crown" msgstr "Den Ultimative Krone" msgid "Golden Goose" msgstr "Den Gyldne GÃ¥s" msgid "The %{name} brings in an income of %{count} gold per day." msgstr "%{name} øger kongerigets indkomst med %{count} guldstykker om dagen." msgid "Arcane Necklace of Magic" msgstr "Den Magiske Mystikhalskæde" msgid "" "After rescuing a Sorceress from a cursed tomb, she rewards your heroism with " "an exquisite jeweled necklace." msgstr "" "Efter at have reddet en heks fra en forbandet grav, belønner hun dit " "heltemod med en udsøgt juvelhalskæde." msgid "Caster's Bracelet of Magic" msgstr "Kasterens MagiarmbÃ¥nd" msgid "" "While searching through the rubble of a caved-in mine, you free a group of " "trapped Dwarves. Grateful, the leader gives you a golden bracelet." msgstr "" "Mens du gennemsøger murbrokkerne af en nedgravet mine, befrier du en gruppe " "fangede dværge. Taknemmelig giver lederen dig et gyldent armbÃ¥nd." msgid "Mage's Ring of Power" msgstr "Troldmandens Magtring" msgid "" "A cry of pain leads you to a Centaur, caught in a trap. Upon setting the " "creature free, he hands you a small pouch. Emptying the contents, you find a " "dazzling jeweled ring." msgstr "" "Et skrig af smerte fører dig til en kentaur, fanget i en fælde. Efter at " "have sat væsenet fri, rækker han dig en lille pose. Da du tømmer posens " "indhold, finder du en blændende juvelbesat ring." msgid "Witch's Broach of Magic" msgstr "Troldkællingens Magibroche" msgid "" "Alongside the remains of a burnt witch lies a beautiful broach, intricately " "designed. Approaching the corpse with caution, you add the broach to your " "inventory." msgstr "" "Ved siden af resterne af en brændt troldkælling ligger en smukt udformet " "broche. Med forsigtighed nærmer du dig liget. Du tilføjer brochen til din " "oppakning." msgid "Medal of Valor" msgstr "Hædersmedaljen" #, fuzzy msgid "The %{name} increases the morale of the hero's army by %{count}." msgstr "%{name} øger din moral med %{count} point." msgid "" "Freeing a virtuous maiden from the clutches of an evil overlord, you are " "granted a Medal of Valor by the King's herald." msgstr "" "Ved at befri en dydig jomfru fra en ond overherres kløer, fÃ¥r du en " "hædersmedalje af kongens herold." msgid "Medal of Courage" msgstr "Tapperhedsmedaljen" msgid "" "After saving a young boy from a vicious pack of Wolves, you return him to " "his father's manor. The grateful nobleman awards you with a Medal of Courage." msgstr "" "Efter at have reddet en ung dreng fra en ond flok ulve, returnerer du ham " "til sin fars herregÃ¥rd. Den taknemmelige adelsmand belønner dig med en " "tapperhedsmedalje." msgid "Medal of Honor" msgstr "Æresmedaljen" msgid "" "After freeing a princess of a neighboring kingdom from the evil clutches of " "despicable slavers, she awards you with a Medal of Honor." msgstr "" "Efter at have befriet en prinsesse af et nabokongerige fra nogle foragtelige " "slavehandleres onde kløer, tildeler hun dig en æresmedalje." msgid "Medal of Distinction" msgstr "Udmærkelsesmedaljen" msgid "" "Ridding the countryside of the hideous Minotaur who made a sport of eating " "noblemen's Knights, you are honored with the Medal of Distinction." msgstr "" "Da du befrier landskabet for den hæslige minotaur, der gjorde en sport ud af " "at æde adelsmænds riddere, beæres du med en udmærkelsesmedalje." msgid "Fizbin of Misfortune" msgstr "Fizbin Ulykkesmedaljen" #, fuzzy msgid "" "The %{name} greatly decreases the morale of the hero's army by %{count}." msgstr "%{name} reducerer din moral markant med %{count} point." msgid "" "You stumble upon a medal lying alongside the empty road. Adding the medal to " "your inventory, you become aware that you have acquired the undesirable " "Fizbin of Misfortune, greatly decreasing your army's morale." msgstr "" "Du falder over en medalje, der ligger langs den tomme vej. Du tilføjer " "medaljen til din oppakning og bliver opmærksom pÃ¥, at du har erhvervet den " "uønskede Fizbin Ulykkesmedalje, hvilket i høj grad mindsker din hærs moral." msgid "Thunder Mace of Dominion" msgstr "Overmagtens Tordenplejle" msgid "" "During a sudden storm, a bolt of lightning strikes a tree, splitting it. " "Inside the tree you find a mysterious mace." msgstr "" "Under en pludselig storm slÃ¥r et lyn ned i et træ og flækker det. Inde i " "træet finder du en mystisk stridsplejle." msgid "Armored Gauntlets of Protection" msgstr "Værnerens Pansrede Handsker" #, fuzzy msgid "The %{name} increase the hero's defense skill by %{count}." msgstr "%{name} øger din forsvarsevne med %{count} point." msgid "" "You encounter the infamous Black Knight! After a grueling duel ending in a " "draw, the Knight, out of respect, offers you a pair of armored gauntlets." msgstr "" "Du møder den berygtede Sorte Ridder! Efter en opslidende duel, som ender " "uafgjort, vinder du ridderens respekt, og han tilbyder dig et par pansrede " "handsker." msgid "Defender Helm of Protection" msgstr "Værnerens Forsvarshjelm" msgid "" "A glint of golden light catches your eye. Upon further investigation, you " "find a golden helm hidden under a bush." msgstr "" "Dit øje fanger et gyldent lysglimt. Ved nærmere undersøgelse finder du en " "gylden hjelm gemt under en busk." msgid "Giant Flail of Dominion" msgstr "Overmagtens Kæmpeslagle" # ordet "plejl" beskrives som en "flail" typisk ser ud msgid "" "A clumsy Giant has killed himself with his own flail. Knowing your superior " "skill with this weapon, you confidently remove the spectacular flail from " "the fallen Giant." msgstr "" "En klodset kæmpe har dræbt sig selv med sin egen slagle. NÃ¥r du kender dine " "overlegne færdigheder med dette vÃ¥ben, fjerner du trygt den spektakulære " "slagle fra den faldne kæmpe." msgid "Ballista of Quickness" msgstr "Den Hastfulde Ballista" #, fuzzy msgid "The %{name} gives the hero's catapult one extra shot per combat round." msgstr "%{name} giver din katapult et ekstra skud per kamprunde." msgid "" "Walking through the ruins of an ancient walled city, you find the instrument " "of the city's destruction, an elaborately crafted ballista." msgstr "" "GÃ¥ende gennem ruinerne af en ældgammel befæstet by, finder du instrumentet " "til byens ødelæggelse, en omhyggeligt udformet ballista." msgid "Stealth Shield of Protection" msgstr "Værnerens Skalkeskjold" msgid "" "A stone statue of a warrior holds a silver shield. As you remove the shield, " "the statue crumbles into dust." msgstr "" "En krigerstatue af sten holder et sølvskjold. Idet du fjerner skjoldet, " "smuldrer statuen til støv." msgid "Dragon Sword of Dominion" msgstr "Overmagtens Dragesværd" msgid "" "As you are walking along a narrow path, a nearby bush suddenly bursts into " "flames. Before your eyes the flames become the image of a beautiful woman. " "She holds out a magnificent sword to you." msgstr "" "Mens du gÃ¥r ad en smal sti, bryder en busk i nærheden pludselig i flammer. " "Flammerne danner et billede af en smuk kvinde for dine øjne. Hun fremrækker " "dig et storslÃ¥et sværd." msgid "Power Axe of Dominion" msgstr "Overmagtens Kraftøkse" msgid "" "You see a silver axe embedded deeply in the ground. After several " "unsuccessful attempts by your army to remove the axe, you tightly grip the " "handle of the axe and effortlessly pull it free." msgstr "" "Du ser en sølvøkse dybt i jorden. Efter adskillige mislykkede forsøg fra " "dine mænd pÃ¥ at fjerne øksen, tager du selv hÃ¥rdt fat i øksens skaft og " "trækker den ubesværet fri." msgid "Divine Breastplate of Protection" msgstr "Værnerens Guddommelige Brystplade" msgid "" "A gang of Rogues is sifting through the possessions of dead warriors. " "Scaring off the scavengers, you note the Rogues had overlooked a beautiful " "breastplate." msgstr "" "En slyngelbande gennemsøger de døde krigeres ejendele. Efter at have skræmt " "de plyndrende slyngler væk, bemærker du, at de havde overset en smuk " "brystplade." msgid "Minor Scroll of Knowledge" msgstr "Skriftrullen om mindre Kundskaber" msgid "" "Before you appears a levitating glass case with a scroll, perched upon a bed " "of crimson velvet. At your touch, the lid opens and the scroll floats into " "your awaiting hands." msgstr "" "Foran dig dukker et svævende glasmontre op. Indeni den ligger en skriftrulle " "ovenpÃ¥ noget karmosinrødt fløjl. Din berøring Ã¥bner glaslÃ¥get, og " "skriftrullen flyver ind i dine afventende hænder." msgid "Major Scroll of Knowledge" msgstr "Skriftrullen om større Kundskaber" msgid "" "Visiting a local wiseman, you explain the intent of your journey. He reaches " "into a sack and withdraws a yellowed scroll and hands it to you." msgstr "" "Da du besøger en lokal vismand, forklarer du hensigten med din rejse. Op af " "en sæk trækker han en gulnet skriftrulle, og rækker den til dig." msgid "Superior Scroll of Knowledge" msgstr "Skriftrullen om overlegne Kundskaber" msgid "" "You come across the remains of an ancient Druid. Bones, yellowed with age, " "peer from the ragged folds of her robe. Searching the robe, you discover a " "scroll hidden in the folds." msgstr "" "Du støder pÃ¥ resterne af en ældgammel druide. Fra hendes forrevne folder i " "hendes robe ses der knogler, gulnede af alderen. Du søger i roben og opdager " "en skriftrulle gemt i folderne." msgid "Foremost Scroll of Knowledge" msgstr "Skriftrullen om førende Kundskaber" msgid "" "Mangled bones, yellowed with age, peer from the ragged folds of a dead " "Druid's robe. Searching the robe, you discover a scroll hidden within." msgstr "" "Fra de forrevne folder i en død druides robe ses der lemlæstede knogler, " "gulnede af alderen. Du søger i roben og opdager en skriftrulle gemt i " "folderne." msgid "Endless Sack of Gold" msgstr "Den Uudtømmelige Guldsæk" #, fuzzy msgid "The %{name} provides the hero with %{count} gold per day." msgstr "%{name} giver dig %{count} guldstykker om dagen." msgid "" "A little leprechaun dances gleefully around a magic sack. Seeing you " "approach, he stops in mid-stride. The little man screams and stamps his foot " "ferociously, vanishing into thin air. Remembering the old leprechaun saying " "'Finders Keepers', you grab the sack and leave." msgstr "" "En lille alf danser lystigt rundt om en magisk sæk. Da han ser dig nærme " "sig, stopper han midt i et trin. Den lille mand skriger og stamper voldsomt " "med foden og forsvinder ud i den blÃ¥ luft. Du husker den gamle alfs " "ordsprog: 'Det man finder, mÃ¥ man beholde'. Du griber sækken og gÃ¥r." msgid "Endless Bag of Gold" msgstr "Den Uudtømmelige Guldtaske" msgid "" "A noblewoman, separated from her traveling companions, asks for your help. " "After escorting her home, she rewards you with a bag filled with gold." msgstr "" "En adelig kvinde, adskilt fra sine rejsekammerater, beder om din hjælp. " "Efter at have eskorteret hende hjem, belønner hun dig med en taske fyldt med " "guld." msgid "Endless Purse of Gold" msgstr "Den Uudtømmelige Guldpose" msgid "" "In your travels, you find a leather purse filled with gold that once " "belonged to a great warrior king who had the ability to transform any " "inanimate object into gold." msgstr "" "PÃ¥ dine rejser finder du en læderpung fyldt med guld. Den tilhørte engang en " "stor krigerkonge, som havde evnen til at forvandle enhver livløs genstand " "til guld." msgid "Nomad Boots of Mobility" msgstr "Nomadens Syvmilestøvler" #, fuzzy msgid "The %{name} increase the hero's movement on land." msgstr "%{name} øger heltens skridtpoint pÃ¥ land." msgid "" "A Nomad trader seeks protection from a tribe of Goblins. For your " "assistance, he gives you a finely crafted pair of boots made from the " "softest leather. Looking closely, you see fascinating ancient carvings " "engraved on the leather." msgstr "" "En nomadehandler søger beskyttelse fra en vættestamme. For din hjælp giver " "han dig et par fint forarbejdede støvler, lavet af det blødeste læder. Ved " "nærmere eftersyn, ser du fascinerende ældgamle udskæringer indgraveret pÃ¥ " "læderet." msgid "Traveler's Boots of Mobility" msgstr "Vandrerens Syvmilestøvler" msgid "" "Discovering a pair of beautifully beaded boots made from the finest and " "softest leather, you thank the anonymous donor and add the boots to your " "inventory." msgstr "" "Idet du opdager et par smukke støvler med perler, lavet af det fineste og " "blødeste læder, takker du den anonyme donor og tilføjer støvlerne til din " "oppakning." msgid "Lucky Rabbit's Foot" msgstr "Den Heldige Kaninfod" #, fuzzy msgid "The %{name} increases the luck of the hero's army by %{count}." msgstr "%{name} øger dit held i kamp med %{count} point." msgid "" "A traveling merchant offers you a rabbit's foot, made of gleaming silver " "fur, for safe passage. The merchant explains the charm will increase your " "luck in combat." msgstr "" "En rejsende købmand tilbyder dig en kaninfod, lavet af skinnende sølvpels, " "for sikker passage. Købmanden forklarer, at denne berlok vil øge dit held i " "kamp." msgid "Golden Horseshoe" msgstr "Den Gyldne Hestesko" msgid "" "An ensnared Unicorn whinnies in fright. Murmuring soothing words, you set " "her free. Snorting and stamping her front hoof once, she gallops off. " "Looking down you see a golden horseshoe." msgstr "" "En fanget enhjørning vrinsker rædselsslagent. Mens du mumler beroligende " "ord, sætter du hende fri. Hun pruster og stamper en gang med sin forhov før " "den galopperer derudaf. Du kigger ned og ser den efterladte hestesko af guld." msgid "Gambler's Lucky Coin" msgstr "Hasardspillerens Lykkemønt" msgid "" "You have captured a mischievous imp who has been terrorizing the region. In " "exchange for his release, he rewards you with a magical coin." msgstr "" "Du har fanget en drilsk djævleunge, som har terroriseret omrÃ¥det. Til " "gengæld for sin løsladelse belønner han dig med en magisk mønt." msgid "Four-Leaf Clover" msgstr "Firkløveren" msgid "" "In the middle of a patch of dead and dry vegetation, to your surprise you " "find a healthy green four-leaf clover." msgstr "" "Midt pÃ¥ en død og udtørret eng finder du, til din overraskelse, en sund grøn " "firkløver." msgid "True Compass of Mobility" msgstr "Det sande Kompas for Bevægelighed." #, fuzzy msgid "The %{name} increases the hero's movement on land and sea." msgstr "%{name} øger dine skridtpoint til lands og til vands." msgid "" "An old man claiming to be an inventor asks you to try his latest invention. " "He then hands you a compass." msgstr "" "En gammel mand, som hævder at være opfinder, beder dig prøve hans seneste " "opfindelse. Han rækker dig et sandt kompas." msgid "Sailor's Astrolabe of Mobility" msgstr "Søfarerens Astrolabium for Bevægelighed" #, fuzzy msgid "The %{name} increases the hero's movement on sea." msgstr "%{name} øger dine bevægelsespoint til søs." msgid "" "An old sea captain is being tortured by Ogres. You save him, and in return " "he rewards you with a wondrous instrument to measure the distance of a star." msgstr "" "En gammel søkaptajn bliver tortureret af jætter. Du redder ham, og til " "gengæld belønner han dig med et vidunderligt instrument til at mÃ¥le " "afstanden til stjerner." msgid "Evil Eye" msgstr "Ondt Øje" msgid "The %{name} reduces the casting cost of curse spells by half." msgstr "%{name} halverer manaprisen for at kaste forbandelsesbesværgelser." msgid "" "While venturing into a decrepit hut you find the Skeleton of a long dead " "witch. Investigation of the remains reveals a glass eye rolling around " "inside an empty skull." msgstr "" "Mens du begiver dig ind i en forfalden hytte, finder du skelettet af en for " "længst død troldkælling. Undersøgelse af resterne afslører et glasøje, som " "ruller rundt inde i et tomt kranie." msgid "Enchanted Hourglass" msgstr "Det Fortryllede Timeglas" #, fuzzy msgid "" "The %{name} extends the duration of all the hero's spells by %{count} turns." msgstr "" "%{name} forlænger alle dine besværgelsers varighed med %{count} runder." msgid "" "A surprise turn in the landscape finds you in the midst of a grisly scene: " "Vultures picking at the aftermath of a terrible battle. Your cursory search " "of the remains turns up an enchanted hourglass." msgstr "" "Midt i et overraskende sving i landskabet oplever du et uhyggeligt syn: " "Gribbe, plukkende i kølvandet af en frygtelig kamp. Overfladisk søger du " "gennem ligresterne og finder et fortryllet timeglas." msgid "Gold Watch" msgstr "Gulduret" #, fuzzy msgid "The %{name} doubles the effectiveness of the hero's hypnotize spells." msgstr "%{name} fordobler dine hypnosebesværgelsers effektivitet." msgid "" "In reward for helping his cart out of a ditch, a traveling potion salesman " "gives you a \"magic\" gold watch. Unbeknownst to him, the watch really is " "magical." msgstr "" "Som belønning, for at have hjulpet hans vogn op af en grøft, giver en " "omrejsende eliksirsælger dig et \"magisk\" guldur. Han vidste ikke, at uret " "faktisk er magisk." msgid "Skullcap" msgstr "Kalotten" msgid "The %{name} halves the casting cost of all mind influencing spells." msgstr "" "%{name} halverer manaprisen for alle dine sindspÃ¥virkende besværgelser." msgid "" "A brief stop at an improbable rural inn yields an exchange of money, tales, " "and accidentally, luggage. You find a magical skullcap in your new backpack." msgstr "" "Et kort stop pÃ¥ en usandsynlig landlig kro resulterer i en udveksling af " "penge, fortællinger og tilfældig bagage. Du finder en magisk kalot i din nye " "rygsæk." msgid "Ice Cloak" msgstr "Iskappen" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from cold spells." msgstr "" "Modtager dine tropper skade fra kuldebesværgelser, halverer %{name} den." msgid "" "Responding to the panicked cries of a damsel in distress, you discover a " "young woman fleeing from a hungry bear. You slay the beast in the nick of " "time, and the grateful Sorceress weaves a magic cloak from the bear's hide." msgstr "" "Som reaktion pÃ¥ paniske skrig fra en pige i nød, opdager du en ung kvinde, " "der flygter fra en sulten bjørn. I sidste øjeblik dræber du udyret. Og ud af " "bjørnens skind væver den taknemmelige heks dig en magisk kappe." msgid "Fire Cloak" msgstr "Ildkappen" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from fire spells." msgstr "Modtager dine tropper skade fra ildbesværgelser, halverer %{name} den." msgid "" "You've come upon a fight between a Necromancer and a Paladin. The " "Necromancer blasts the Paladin with a fire bolt, bringing him to his knees. " "Acting quickly, you slay the evil one before the final blow. The grateful " "Paladin gives you the fire cloak that saved him." msgstr "" "Du er stødt pÃ¥ en kamp mellem en nekromantiker og en paladin. " "Nekromantikeren sprænger paladinen med en ildkugle, hvilket fÃ¥r ham i knæ. " "Du handler hurtigt og dræber den onde, før nÃ¥destødet nÃ¥r at indtræffe. Den " "taknemmelige paladin giver dig Ildkappen, som reddede ham." msgid "Lightning Helm" msgstr "Lynhjelmen" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from lightning " "spells." msgstr "Modtager dine tropper skade fra lynbesværgelser, halverer %{name} den." msgid "" "A traveling tinker in need of supplies offers you a helm with a thunderbolt " "design on its top in exchange for food and water. Curious, you accept, and " "later find out that the helm is magical." msgstr "" "En rejsende kedelflikker, som har brug for forsyninger, tilbyder dig en " "hjelm med et lynmærke pÃ¥ sig i bytte for mad og drikke. Nysgerrig accepterer " "du, og finder senere ud af, at hjelmen er magisk." msgid "Evercold Icicle" msgstr "Den Evigtkolde Istap" #, fuzzy msgid "" "The %{name} causes the hero's cold spells to do %{count} percent more damage " "to enemy troops." msgstr "" "%{name} fÃ¥r dine kuldebesværgelser til at gøre %{count}% mere skade pÃ¥ " "fjendens tropper." msgid "" "An icicle withstanding the full heat of the noonday sun attracts your " "attention. Intrigued, you break it off, and find that it does not melt in " "your hand." msgstr "" "En istap, som modstÃ¥r middagssolens varme til fulde, tiltrækker din " "opmærksomhed. Nysgerrigt brækker du det af og opdager, at det ikke smelter i " "din hÃ¥nd." msgid "Everhot Lava Rock" msgstr "Den Evigtvarme Lavasten" #, fuzzy msgid "" "The %{name} causes the hero's fire spells to do %{count} percent more damage " "to enemy troops." msgstr "" "%{name} fÃ¥r dine ildbesværgelser til at gøre %{count}% mere skade pÃ¥ " "fjendens tropper." msgid "" "Your wanderings bring you into contact with a tribe of ape-like beings using " "a magical lava rock that never cools to light their fires. You take pity on " "them and teach them to make fire with sticks. Believing you to be a god, the " "apes give you their rock." msgstr "" "Dine vandringer bringer dig i kontakt med en stamme af abelignende væsener, " "der bruger en magisk lavasten, som aldrig køler, til at tænde deres ild med. " "Du forbarmer dig over dem og lærer dem, at lave ild med pinde. I troen pÃ¥, " "at du er en gud, giver aberne dig deres lavasten." msgid "Lightning Rod" msgstr "Lynaflederen" #, fuzzy msgid "" "The %{name} causes the hero's lightning spells to do %{count} percent more " "damage to enemy troops." msgstr "" "%{name} fÃ¥r dine lynbesværgelser til at gøre %{count}% mere skade pÃ¥ " "fjendens tropper." msgid "" "While waiting out a storm, a lighting bolt strikes a nearby cottage's " "lightning rod, which melts and falls to the ground. The tip of the rod, " "however, survives intact and makes your hair stand on end when you touch it. " "Hmm..." msgstr "" "Ventende pÃ¥ at et uvejr stopper, slÃ¥r et lyn ned i en nærliggende hyttes " "lynafleder, som smelter og falder til jorden. Spidsen forbliver dog intakt " "og fÃ¥r dit hÃ¥r til at rejse sig, nÃ¥r du rører ved det. Hmm..." msgid "Snake-Ring" msgstr "Slangeringen" #, fuzzy msgid "The %{name} halves the casting cost of all of the hero's bless spells." msgstr "%{name} halverer manaprisen for at kaste velsignelsesbesværgelser." msgid "" "You've found an oddly shaped ring on the finger of a long dead traveler. The " "ring looks like a snake biting its own tail." msgstr "" "Du har fundet en sært formet ring pÃ¥ fingeren af en for længst død rejsende. " "Ringen ligner en slange, der bider sig selv i halen." msgid "Ankh" msgstr "Ankhen" #, fuzzy msgid "" "The %{name} doubles the effectiveness of all of the hero's resurrect and " "animate spells." msgstr "" "%{name} fordobler effektiviteten af alle dine genoplivende og dødebesjælende " "besværgelser." msgid "" "A fierce windstorm reveals the entrance to a buried tomb. Your investigation " "reveals that the tomb has already been looted, but the thieves overlooked an " "ankh on a silver chain in the dark." msgstr "" "En voldsom vindstorm afslører indgangen til et underjordisk gravkammer. Din " "undersøgelse afslører, at graven allerede er blevet plyndret, men " "gravrøverne oversÃ¥ en ankh pÃ¥ en sølvkæde i mørket." msgid "Book of Elements" msgstr "Elementalernes Bog" #, fuzzy msgid "" "The %{name} doubles the effectiveness of all of the hero's summoning spells." msgstr "" "%{name} fordobler effektiviteten af alle dine hidkaldelsesbesværgelser." msgid "" "You come across a conjurer who begs to accompany you and your army awhile " "for safety. You agree, and he offers as payment a copy of the book of the " "elements." msgstr "" "Du støder pÃ¥ en Ã¥ndemaner, som for sin sikkerheds skyld trygler dig om at " "ledsage dig og din hær et stykke tid. Du gÃ¥r med til det, og han tilbyder, " "som betaling, en kopi af Elementalernes Bog." msgid "Elemental Ring" msgstr "Elemental-ringen" msgid "The %{name} halves the casting cost of all summoning spells." msgstr "%{name} halverer manaprisen for at kaste hidkaldelsesbesværgelser." msgid "" "While pausing to rest, you notice a bobcat climbing a short tree to get at a " "crow's nest. On impulse, you climb the tree yourself and scare off the cat. " "When you look in the nest, you find a collection of shiny stones and a ring." msgstr "" "Mens du tager en hvilepause, bemærker du en bobcat, som klatrer op til en " "kragerede i et lille træ. Impulsivt klatrer du selv op i træet og skræmmer " "katten væk. Idet du ser ned i reden, finder du en samling skinnende sten, og " "en ring." msgid "Holy Pendant" msgstr "Den Hellige Amulet" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to curse spells." msgstr "%{name} gør alle dine tropper immune overfor forbandelsesbesværgelser." msgid "" "In your wanderings you come across a hermit living in a small, tidy hut. " "Impressed with your mission, he takes time out from his meditations to bless " "and give you a charm against curses." msgstr "" "PÃ¥ dine vandringer støder du pÃ¥ en eremit, der bor i en lille, ryddelig " "hytte. Imponeret over din mission tager han en pause fra sine meditationer, " "for at velsigne og give dig en berlok mod forbandelser." msgid "Pendant of Free Will" msgstr "Amuletten af Fri Vilje" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to hypnotize spells." msgstr "%{name} gør alle dine tropper immune overfor hypnosebesværgelser." msgid "" "Responding to cries for help, you find river Sprites making a sport of " "dunking an old man. Feeling vengeful, you rescue the man and drag a Sprite " "onto dry land for awhile. The Sprite, uncomfortable in the air, gives you a " "magic pendant to let him go." msgstr "" "Idet du reagerer pÃ¥ rÃ¥b om hjælp, ser du nogle flodfeer, som gør en sport ud " "af dunke en gammel mand ned i vandet. Med en følelse af hævngerrighed redder " "du manden og trækker derefter en af feerne ind pÃ¥ land. Feen, som ikke kan " "klare luften, giver dig en magisk amulet mod sin frihed." msgid "Pendant of Life" msgstr "Livsamuletten" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to death spells." msgstr "%{name} gør alle dine tropper immune overfor dødsbesværgelser." msgid "" "A brief roadside encounter with a small caravan and a game of knucklebones " "wins a magic pendant. Its former owner says that it protects from " "Necromancers' death spells." msgstr "" "Et kort møde i vejkanten med en lille karavane og et spil knoben vinder dig " "en magisk amulet. Dens tidligere ejer siger, at den beskytter mod " "nekromantikeres dødsbesværgelser." msgid "Serenity Pendant" msgstr "Amuletten af Sindsro" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to berserk spells." msgstr "%{name} gør alle dine tropper immune overfor bersærkbesværgelser." msgid "" "The sounds of combat draw you to the scene of a fight between an old " "Barbarian and an eight-headed Hydra. Your timely intervention swings the " "battle in favor of the man, and he rewards you with a pendant he used to use " "to calm his mind for battle." msgstr "" "Tiltrukket af lydene fra et slagsmÃ¥l overværer du en kamp mellem en gammel " "barbar og en otte-hovedet hydra. Din rettidige indgriben svinger kampen til " "barbarens fordel, og han belønner dig med en amulet, han plejede at bruge " "til at berolige sit sind til kamp." msgid "Seeing-eye Pendant" msgstr "Amuletten af Det Seende Øje" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to blindness spells." msgstr "%{name} gør alle dine tropper immune overfor blindingsbesværgelser." msgid "" "You come upon a very old woman, long blind from cataracts and dying alone. " "You tend to her final needs and promise a proper burial. Grateful, she gives " "you a magic pendant emblazoned with a stylized eye. It lets you see with " "your eyes closed." msgstr "" "Du støder pÃ¥ en meget gammel kvinde, for længst blind af grÃ¥ stær og døende " "alene. Du plejer hendes endelige behov og lover hende en ordentlig " "begravelse. Taknemmelig giver hun dig en magisk amulet med et stiliseret " "øje. Det lader dig se med lukkede øjne." msgid "Kinetic Pendant" msgstr "Den Kinetiske Amulet" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to paralyze spells." msgstr "%{name} gør alle dine tropper immune overfor paralysebesværgelser." msgid "" "You come across a golem wearing a glowing pendant and blocking your way. " "Acting on a hunch, you cut the pendant from its neck. Deprived of its power " "source, the golem breaks down, leaving you with the magical pendant." msgstr "" "Du støder pÃ¥ en golem iført en glødende amulet, som spærrer din vej. " "Handlende pÃ¥ din mavefornemmelse, klipper du vedhænget af dets hals. " "Frataget sin kraftkilde gÃ¥r golemmen i stykker og efterlader dig med den " "magiske amulet." msgid "Pendant of Death" msgstr "Dødsamuletten" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to holy spells." msgstr "%{name} gør alle dine tropper immune overfor hellige besværgelser" msgid "" "A quick and deadly battle with a Necromancer wins you his magical pendant. " "Later, a Wizard tells you that the pendant protects undead under your " "control from holy word spells." msgstr "" "En hurtig og dødbringende kamp med en nekromantiker vinder dig hans magiske " "amulet. Senere fortæller en magiker, at amuletten beskytter udøde under din " "kontrol mod hellige ord." msgid "Wand of Negation" msgstr "Negationens Tryllestav" #, fuzzy msgid "" "The %{name} makes all of the hero's troops immune to dispel magic spells." msgstr "%{name} gør alle dine tropper immune overfor magi-ophævelser." msgid "" "You meet an old Wizard friend of yours traveling in the opposite direction. " "He presents you with a gift: A wand that prevents the use of the dispel " "magic spell on your allies." msgstr "" "Du møder en gammel magikerven, der rejser i den modsatte retning. Han giver " "dig en gave: En tryllestav, der forhindrer brugen af besværgelsen " "magiophævelse pÃ¥ dine tropper." msgid "Golden Bow" msgstr "Den Gyldne Bue" #, fuzzy msgid "" "The %{name} eliminates the %{count} percent penalty for the hero's troops " "shooting past obstacles (e.g. castle walls)." msgstr "" "%{name} eliminerer %{count}-procentstraffen for dine tropper, der skyder " "forbi forhindringer (f.eks. borgmure)." msgid "" "A chance meeting with a famous Archer finds you in a game of knucklebones " "pitting his bow against your horse. You win." msgstr "" "Et tilfældigt møde med en berømt bueskytte fÃ¥r dig til at indgÃ¥ i et " "knoglespil om hans bue mod din hest. Du vinder." msgid "Telescope" msgstr "Teleskopet" #, fuzzy msgid "" "The %{name} increases the amount of terrain the hero reveals when " "adventuring by %{count} extra square." msgstr "" "%{name} øger mængden af det terræn, som din helt afslører, nÃ¥r han " "udforsker, med %{count} ekstra kvadrat." msgid "" "A merchant from far away lands trades you a new invention of his people for " "traveling supplies. It makes distant objects appear closer, and he calls " "it...\n" "\n" "a telescope." msgstr "" "Af en købmand fra fjerne lande køber du en ny opfindelse af hans " "rejseforsynende folk. Det fÃ¥r fjerne objekter til at synes tættere pÃ¥, og " "han kalder det...\n" "\n" "et teleskop." msgid "Statesman's Quill" msgstr "Statsmandens Fjerpen" #, fuzzy msgid "" "The %{name} reduces the cost of surrender to %{count} percent of the total " "cost of troops the hero has in their army." msgstr "" "%{name} reducerer prisen for overgivelse til %{count}% af de samlede " "omkostninger for tropper, du har i din hær." msgid "" "You pause to help a diplomat with a broken axle fix his problem. In " "gratitude, he gives you a writing quill with magical properties which he " "says will \"help people see things your way\"." msgstr "" "Du holder pause, for at hjælpe en diplomat med at løse sit problem med en " "brækket aksel. Taknemmelig giver han dig en fjerpen med magiske egenskaber, " "som han siger vil \"hjælpe folk med at se tingene pÃ¥ din mÃ¥de\"." msgid "Wizard's Hat" msgstr "Troldmandshatten" #, fuzzy msgid "" "The %{name} increases the duration of the hero's spells by %{count} turns." msgstr "%{name} øger dine besværgelsers varighed med %{count} runder." msgid "" "You see a Wizard fleeing from a Griffin and riding like the wind. The Wizard " "opens a portal and rides through, getting his hat knocked off by the edge of " "the gate. The Griffin follows; the gate closes. You pick the hat up, dust it " "off, and put it on." msgstr "" "Du ser en magiker, flygtende fra en grif, mens han rider som vinden. " "Magikeren Ã¥bner en portal, rider igennem den og fÃ¥r sin hat slÃ¥et af ved en " "af portalens kanter. Griffen følger efter, portalen lukker i. Du samler " "hatten op, støver den af og tager den pÃ¥." msgid "Power Ring" msgstr "Kraftringen" #, fuzzy msgid "The %{name} returns %{count} extra spell points per day to the hero." msgstr "%{name} returnerer %{count} ekstra magipoint om dagen til din helt." msgid "" "You find a small tree that closely resembles the great Warlock Carnauth with " "a ring around one of its twigs. Scraps of clothing and rotting leather lead " "you to suspect that it IS Carnauth, transformed. Since you can't help him, " "you take the magic ring." msgstr "" "Du finder et lille træ, der ligner den store heksemester Carnauth, med en " "ring omkring en af dens kviste. Rester af tøj og rÃ¥dnende læder fÃ¥r dig til " "at mistænke, at det ER Carnauth, forvandlet. Da du ikke kan hjælpe ham, " "tager du den magiske ring." msgid "Ammo Cart" msgstr "Ammunitionsvognen" #, fuzzy msgid "" "The %{name} provides endless ammunition for all of the hero's troops that " "shoot." msgstr "%{name} giver dine skydende tropper uendelig ammunition." msgid "" "An ammunition cart in the middle of an old battlefield catches your eye. " "Inspection shows it to be in good working order, so you take it along." msgstr "" "En ammunitionsvogn midt pÃ¥ en gammel slagmark fanger dit blik. Nærmere " "eftersyn viser, at den er i god stand, sÃ¥ du tager den med dig." msgid "Tax Lien" msgstr "Pantebrevet" #, fuzzy msgid "The %{name} costs the hero %{count} gold pieces per day." msgstr "Det koster dig %{count} guldstykker om dagen, at have %{name} pÃ¥ dig." msgid "" "Your big spending habits have earned you a massive tax bill that you can't " "hope to pay. The tax man takes pity and agrees to only take 250 gold a day " "from your account for life. Check here if you want one dollar to go to the " "presidential campaign election fund." msgstr "" "Dine store forbrugsvaner har givet dig en massiv skattegæld, som du ikke kan " "hÃ¥be pÃ¥ at betale. Pantefogeden forbarmer sig og indvilliger i kun at " "modtage 250 guldstykker om dagen fra din konto, i resten af dit liv. Sæt " "kryds her, hvis du vil lade en krone gÃ¥ til præsidentkampagnens valgfond." msgid "Hideous Mask" msgstr "Den Hæslige Maske" #, fuzzy msgid "The %{name} prevents all 'wandering' armies from joining the hero." msgstr "%{name} forhindrer alle 'vandrende' hære i at tilslutte sig din helt." msgid "" "Your looting of the grave of Sinfilas Gardolad, the famous shapeshifting " "Warlock, unearths his fabled mask. Trembling, you put it on and it twists " "your visage into an awful grimace! Oh no! It's actually the hideous mask of " "Gromluck Greene, and you are stuck with it." msgstr "" "Din plyndring af Sinfilas Gardolads grav, den berømte formskiftende " "heksemester, afslører hans sagnomspundne maske. Rystende tager du den pÃ¥, og " "det vrider dit ansigt til en forfærdelig grimase! Ã…h nej! Det er faktisk " "Gromluck Greenes hæslige maske, og nu sidder den fast pÃ¥ dit ansigt." msgid "Endless Pouch of Sulfur" msgstr "Den Uudtømmelige Svovlpose" msgid "The %{name} provides %{count} unit of sulfur per day." msgstr "%{name} giver %{count} mængde svovl om dagen." msgid "" "You visit an alchemist who, upon seeing your army, is swayed by the " "righteousness of your cause. The newly loyal subject gives you his endless " "pouch of sulfur to help with the war effort." msgstr "" "Du besøger en alkymist, som efter at have set din hær, bliver pÃ¥virket af " "din sags retskaffenhed. Din nye loyale undersÃ¥t giver dig sin uudtømmelige " "svovlpose, for at hjælpe med krigsindsatsen." msgid "Endless Vial of Mercury" msgstr "Den Uudtømmelige Kviksølvsflaske" msgid "The %{name} provides %{count} unit of mercury per day." msgstr "%{name} giver %{count} mængde kviksølv om dagen." msgid "" "A brief stop at a hastily abandoned Wizard's tower turns up a magical vial " "of mercury that always has a little left on the bottom. Recognizing a " "treasure when you see one, you cap it and slip it in your pocket." msgstr "" "Et kort stop ved en hurtigt forladt troldmandstÃ¥rn viser et magisk hætteglas " "med kviksølv, der altid har lidt tilbage pÃ¥ bunden. \n" "Da du kender et artefakt, nÃ¥r du ser en, lukker du den og lægger den i " "lommen." msgid "Endless Pouch of Gems" msgstr "Den Uudtømmelige Juvelpose" msgid "The %{name} provides %{count} unit of gems per day." msgstr "%{name} giver %{count} mængde juveler om dagen." msgid "" "A short rainstorm brings forth a rainbow...and you can see the end of it. " "Riding quickly, you seize the pot of gold you find there. The leprechaun who " "owns it, unable to stop you from taking it, offers an endless pouch of gems " "for the return of his gold. You accept." msgstr "" "En kort regnbyge frembringer en regnbue ... og du kan se, hvor den ender. Du " "rider hurtigt derhen og griber krukken med guld, som du finder der. Alfen, " "der ejer den, som ikke er i stand til at forhindre dig i at tage den, " "tilbyder dig en uudtømmelig juvelpose, for at fÃ¥ sit guld tilbage. Du " "accepterer tilbuddet." msgid "Endless Cord of Wood" msgstr "Den Endeløse Træsnor" msgid "The %{name} provides %{count} unit of wood per day." msgstr "%{name} giver %{count} mængder træ om dagen." msgid "" "Pausing to rest and light a cook fire, you pull wood out of a nearby pile of " "dead wood. As you keep pulling wood from the pile, you notice that it " "doesn't shrink. You realize to your delight that the wood is enchanted, so " "you take it along." msgstr "" "Mens du holder hvilepause og tænder et kogebÃ¥l, samler du brænde fra en " "nærliggende bunke dødt træ. Fortsættende med at trække træ fra bunken, " "bemærker du, at den ikke bliver mindre. Til din glæde indser du, at træet er " "fortryllet, sÃ¥ du tager hele bunken med dig." msgid "Endless Cart of Ore" msgstr "Den Uudtømmelige Malmvogn" msgid "The %{name} provides %{count} unit of ore per day." msgstr "%{name} giver %{count} mængder malm om dagen." msgid "" "You've found a Goblin weapon smithy making weapons for use against humans. " "With a tremendous yell you and your army descend upon their camp and drive " "them away. A search finds a magic ore cart that never runs out of iron." msgstr "" "Du har fundet en vætte-vÃ¥bensmedje, der laver vÃ¥ben til brug mod mennesker. " "Med et voldsomt rÃ¥b gÃ¥r du og din hær ned mod deres lejr og driver dem væk. " "En søgen rundt afslører en magisk malmvogn, der aldrig løber tør for malm." msgid "Endless Pouch of Crystal" msgstr "Den Uudtømmelige krystalpose" msgid "The %{name} provides %{count} unit of crystal per day." msgstr "%{name} giver %{count} mængde krystal om dagen." msgid "" "Taking shelter from a storm in a small cave, you notice a small patch of " "crystal in one corner. Curious, you break a piece off and notice that the " "original crystal grows the lost piece back. You decide to stuff the entire " "patch into a pouch and take it with you." msgstr "" "Da du søger ly for en storm i en lille hule, bemærker du undervejs en lille " "samling af krystaller i det ene hjørne. Nysgerrig brækker du et stykke af og " "bemærker, at der ud af den originale krystal fremvokser et nyt stykke. Du " "beslutter dig for at proppe hele samlingen i en pose og tager den med dig." msgid "Spiked Helm" msgstr "Pighjelmen" msgid "" "Your army is ambushed by a small tribe of wild (and none too bright) Orcs. " "You fend them off easily and the survivors flee in all directions. One of " "the Orcs was wearing a polished spiked helm. Figuring it will make a good " "souvenir, you take it." msgstr "" "Din hær bliver overfaldet af en lille stamme af vilde (og ikke alt for " "kloge) orker. Du afværger dem let, og de overlevende flygter i alle " "retninger. En af orkerne bar en poleret pighjelm. Med et indtryk af, at den " "vil være en god souvenir, tager du den." msgid "Spiked Shield" msgstr "Pigskjoldet" msgid "" "You come upon a bridge spanning a dry gully. Before you can cross, a Troll " "steps out from under the bridge and demands payment before it will permit " "you to pass. You refuse, and the Troll charges, forcing you to slay it. You " "take its spiked shield as a trophy." msgstr "" "Du støder pÃ¥ en bro, der strækker sig over en udtørret bjergkløft. Før du " "kan krydse, træder en trold ud under broen og kræver betaling, før den giver " "dig lov til at passere. Du nægter. Trolden angriber, hvilket tvinger dig til " "at dræbe den. Du tager dets pigskjold som et trofæ." msgid "White Pearl" msgstr "Den Hvide Perle" msgid "" "A walk across a dry saltwater lake bed yields an unlikely prize: A white " "pearl amidst shattered shells and debris." msgstr "" "En tur pÃ¥ tværs af en tør saltvandssø giver en usandsynlig gevinst: En hvid " "perle midt i knuste skaller og affald." msgid "Black Pearl" msgstr "Den Sorte Perle" msgid "" "Rumors of a Griffin of unusual size preying upon the countryside lead you to " "its cave lair. A quick, brutal fight dispatches the beast, and a search of " "its foul nest turns up a huge black pearl." msgstr "" "Rygter om en grif af usædvanlig størrelse, der terroriserer folk ude pÃ¥ " "landet, fører dig til dens klippehule. En hurtig, brutal kamp sender udyret " "af sted, og en undersøgelse af dens modbydelige rede afslører en enorm sort " "perle." msgid "Magic Book" msgstr "Magibog" #, fuzzy msgid "The %{name} enables the hero to cast spells." msgstr "%{name} gør det muligt for dig at kaste besværgelser." msgid "Dummy 1" msgstr "Pladsholder 1" msgid "The reserved artifact." msgstr "Pladsholder-artefaktet." msgid "Dummy 2" msgstr "Pladsholder 2" msgid "Dummy 3" msgstr "Pladsholder 3" msgid "Dummy 4" msgstr "Pladsholder 4" msgid "Spell Scroll" msgstr "Trolddomsrulle" #, fuzzy msgid "" "This %{name} gives the hero the ability to cast the %{spell} spell if the " "hero has a Magic Book." msgstr "" "%{name} giver din helt mulighed for at kaste besværgelsen %{spell}, hvis din " "helt har en magibog." msgid "" "You find an elaborate container which houses an old vellum scroll. The runes " "on the container are very old, and the artistry with which it was put " "together is stunning. As you pull the scroll out, you feel imbued with " "magical power." msgstr "" "Du finder en udførlig beholder, som rummer en gammel pergamentrulle. Runerne " "pÃ¥ beholderen er meget gamle. Det kunstgreb, der mÃ¥tte stÃ¥ bag dette " "hÃ¥ndværk, er storslÃ¥et. Lige som du trækker rullen ud, føler du dig " "gennemsyret af magisk kraft." msgid "Arm of the Martyr" msgstr "Martyrens Arm" #, fuzzy msgid "" "The %{name} increases the hero's spell power by %{count} but adds the undead " "morale penalty." msgstr "" "%{name} øger magikraft med %{count} point, men tilfører din hær den udøde " "moralstraf." msgid "" "One of the less intelligent members of your party picks up an arm off of the " "ground. Despite its missing a body, it is still moving. Your troops find the " "dismembered arm repulsive, but you cannot bring yourself to drop it: it " "seems to hold some sort of magical power that influences your decision " "making." msgstr "" "Et af de mindre intelligente medlemmer af din hær samler en arm op fra " "jorden. PÃ¥ trods af at den mangler en krop, bevæger den sig stadig. Dine " "tropper finder den afhuggede arm frastødende, men du kan ikke fÃ¥ dig selv " "til at slippe den: Den ser ud til at have en slags magisk kraft, der " "pÃ¥virker din beslutsomhed." msgid "Breastplate of Anduran" msgstr "Andurans Brynje" #, fuzzy msgid "The %{name} increases the hero's defense by %{count}." msgstr "%{name} øger din forsvarsevne med %{count} point." msgid "" "You come upon a sign. It reads: \"Here lies the body of Anduran. Bow and " "swear fealty, and you shall be rewarded.\" You decide to do as it says. As " "you stand up, you feel a coldness against your skin. Looking down, you find " "that you are suddenly wearing a gleaming, ornate breastplate." msgstr "" "Du støder pÃ¥ et skilt, hvorpÃ¥ der stÃ¥r: \"Her ligger Andurans lig. Bøj dig " "og sværg troskab, og du skal blive belønnet.\"\n" "Du beslutter dig for at gøre, som der stÃ¥r. Da du rejser dig op, mærker du " "en kulde mod din hud. Du kigger ned, og opdager, at du pludselig har en " "skinnende, udsmykket brynje pÃ¥." msgid "Broach of Shielding" msgstr "Beskyttelsens Broche" msgid "" "The %{name} provides %{count} percent protection from Armageddon and " "Elemental Storm, but decreases spell power by 2." msgstr "" "%{name} giver dine tropper %{count}% beskyttelse mod besværgelserne Ragnarok " "og Elemental Storm, men den reducerer ogsÃ¥ din magikraft med 2 point." msgid "" "A kindly Sorceress thinks that your army's defenses could use a magical " "boost. She offers to enchant the Broach that you wear on your cloak, and you " "accept." msgstr "" "En venlig heks syntes, at din hærs forsvarsevne godt kunne bruge et magisk " "løft. Hun tilbyder at fortrylle den broche, som du bærer pÃ¥ din kappe, og du " "accepterer." msgid "Battle Garb of Anduran" msgstr "Andurans Udstyr" #, fuzzy msgid "" "The %{name} combines the powers of the three Anduran artifacts. It provides " "maximum luck and morale for the hero's troops and gives the hero the Town " "Portal spell." msgstr "" "%{name} kombinerer kræfterne fra Andurans tre artefakter. \n" "Det maksimerer dine troppers held og moral og giver dig Byportal-" "besværgelsen." msgid "" "Out of pity for a poor peasant, you purchase a chest of old junk they are " "hawking for too much gold. Later, as you search through it, you find it " "contains the 3 pieces of the legendary battle garb of Anduran!" msgstr "" "Af ren medlidenhed køber du en kiste fyldt med gammelt skrammel af en fattig " "bonde, som han godt ved, han kræver for meget guld for. \n" "Senere, mens du gennemsøger kisten, finder du ud af, at den indeholder de " "tre dele af Andurans legendariske kampudstyr!" msgid "Crystal Ball" msgstr "Krystalkuglen" #, fuzzy msgid "" "The %{name} lets the hero get more specific information about monsters, " "enemy heroes, and castles nearby the hero." msgstr "" "%{name} giver dig mulighed for at fÃ¥ mere specifik information om væsener, " "fjendens helte og borge i nærheden af den helten, som bærer den." msgid "" "You come upon a caravan of gypsies who are feasting and fortifying their " "bodies with mead. They call you forward and say \"If you prove that you can " "dance the Rama-Buta, we will reward you.\" You don't know it, but try " "anyway. They laugh hysterically, but admire your bravery, giving you a " "Crystal Ball." msgstr "" "Du støder pÃ¥ en sigøjnerkaravane, der fester og fylder deres kroppe med " "mjød. De kalder dig frem og siger \"Hvis du beviser, at du kan danse Rama-" "Buta, vil vi belønne dig.\" Du kender ikke dansen, men prøver alligevel. De " "griner hysterisk, men beundrer dit mod og giver dig en krystalkugle." msgid "Heart of Fire" msgstr "Hjertet af Ild" msgid "" "The %{name} provides %{count} percent protection from fire, but doubles the " "damage taken from cold." msgstr "" "%{name} giver %{count}% beskyttelse mod ildbesværgelser, men fordobler " "skaden fra kuldebesværgelser." msgid "" "You enter a recently burned glade and come upon a Fire Elemental sitting " "atop a rock. It looks up, its flaming face contorted in a look of severe " "pain. It then tosses a glowing object at you. You put up your hands to block " "it, but it passes right through them and sears itself into your chest." msgstr "" "Du gÃ¥r ind i en nyligt brændt lysning og støder pÃ¥ en ild-elemental, som " "sidder pÃ¥ toppen af en klippe. Den ser op, dens flammende ansigt forvredet i " "et blik af alvorlig smerte. Den kaster derefter en glødende genstand mod " "dig. Du løfter dine hænder for at parere den, men den passerer lige igennem " "dem og svier sig ind i dit bryst." msgid "Heart of Ice" msgstr "Hjertet af Is" msgid "" "The %{name} provides %{count} percent protection from cold, but doubles the " "damage taken from fire." msgstr "" "%{name} giver %{count}% beskyttelse mod kuldebesværgelser, men fordobler " "skaden fra ildbesværgelser." msgid "" "Suddenly, a biting coldness engulfs your body. You seize up, falling from " "your horse. The pain subsides, but you still feel as if your chest is " "frozen. As you pick yourself up off of the ground, you hear hearty laughter. " "You turn around just in time to see a Frost Giant run off into the woods and " "disappear." msgstr "" "Pludselig griber en bidende kulde fat i din krop. Du forfryser og falder ned " "fra din hest. Smerten aftager, men du føler stadig, som om dit bryst er " "frosset. Du rejser dig fra jorden og hører en rungende latter. Du vender dig " "om, lige i tide til at se en frostkæmpe spurte ind i skoven og forsvinde." msgid "Helmet of Anduran" msgstr "Andurans Hjelm" msgid "" "You spy a gleaming object poking up out of the ground. You send a member of " "your party over to investigate. He comes back with a golden helmet in his " "hands. You realize that it must be the helmet of the legendary Anduran, the " "only man who was known to wear solid gold armor." msgstr "" "Du fÃ¥r øje pÃ¥ en skinnende genstand, der stikker op af jorden. Du sender et " "medlem af din hær over, for at undersøge sagen. Han kommer tilbage med en " "gylden hjelm i hænderne. Du indser, at det mÃ¥ være hjelmen tilhørende den " "legendariske Anduran, den eneste mand, der var kendt for at bære solidt " "guldudstyr." msgid "Holy Hammer" msgstr "Den Hellige Hammer" msgid "" "You come upon a battle where a Paladin has been mortally wounded by a group " "of Zombies. He asks you to take his hammer and finish what he started. As " "you pick it up, it begins to hum, and then everything becomes a blur. The " "Zombies lie dead, the hammer dripping with blood. You strap it to your belt." msgstr "" "Du støder ind i en kamp, hvor en paladin er blevet dødeligt sÃ¥ret af en " "gruppe zombier. Han beder dig om at tage hans hammer og afslutte det, han " "startede. Mens du samler den op, begynder den straks at summe, og sÃ¥ bliver " "alt sløret. Zombierne ligger døde, hammeren drypper af blod. Du spænder den " "fast i bæltet." msgid "Legendary Scepter" msgstr "Det Legendariske Scepter" msgid "The %{name} adds %{count} points to all attributes." msgstr "%{name} tilføjer %{count} point til alle attributter." msgid "" "Upon cresting a small hill, you come upon a ridiculous looking sight. A " "Sprite is attempting to carry a Scepter that is almost as big as it is. " "Trying not to laugh, you ask, \"Need help?\" The Sprite glares at you and " "answers: \"You think this is funny? Fine. You can carry it. I much prefer " "flying anyway.\"" msgstr "" "Da du gÃ¥r op pÃ¥ en lille bakke, fÃ¥r du dig pÃ¥ et latterligt syn at se. En fe " "forsøger at bære et scepter, som er næsten lige sÃ¥ stort, som den er. Mens " "du forsøger ikke at grine, spørger du: \"Brug for hjælp?\" Feen stirrer pÃ¥ " "dig og svarer: \"Tror du, det her er sjovt? Fint. Du kan bære det. Jeg " "foretrækker alligevel at flyve.\"" msgid "" "An old seaman tells you a tale of an enchanted masthead that he used in his " "youth to rally his crew during times of trouble. He then hands you a faded " "map that shows where he hid it. After much exploring, you find it stashed " "underneath a nearby dock." msgstr "" "En gammel sømand fortæller dig en historie om en fortryllet mastehoved, som " "han, i sin ungdom, brugte til at samle sit mandskab i vanskeligheder. Han " "rækker dig sÃ¥ et falmet kort, der viser, hvor han gemte det. Efter megen " "udforskning finder du det gemt under en nærliggende dok." msgid "Masthead" msgstr "Masthovedet" #, fuzzy msgid "" "The %{name} boosts the hero's troops' luck and morale by %{count} each in " "sea combat." msgstr "" "%{name} øger troppernes held og moral med %{count} point, nÃ¥r de kæmper til " "søs." msgid "Sphere of Negation" msgstr "Negationskuglen" msgid "The %{name} disables all spell casting, for both sides, in combat." msgstr "%{name} umuliggør kastning af besværgelser for begge sider i kamp." msgid "" "You stop to help a Peasant catch a runaway mare. To show his gratitude, he " "hands you a tiny sphere. As soon as you grasp it, you feel the magical " "energy drain from your limbs..." msgstr "" "Du stopper, for at hjælpe en bonde med at fange en løbsk hoppe. Han viser " "taknemmelighed, ved at række dig en lille kugle. SÃ¥ snart du griber om den, " "mærker du den magiske energi blive drænet fra dine lemmer..." msgid "Staff of Wizardry" msgstr "Troldmandsstav" #, fuzzy msgid "The %{name} boosts the hero's spell power by %{count}." msgstr "%{name} øger din magikraft med %{count} point." msgid "" "While out scaring up game, your troops find a mysterious staff levitating " "about three feet off of the ground. They hand it to you, and you notice an " "inscription. It reads: \"Brains best brawn and magic beats might. Heed my " "words, and you'll win every fight.\"" msgstr "" "Mens de var ude at fange vildt, fandt dine tropper en mystisk stav, svævende " "omkring en meter over jorden. De overrækker den til dig, og du bemærker en " "inskription. Det lyder: \"Vid slÃ¥r muskler og magi slÃ¥r styrke. Lyt til mine " "ord, og du vil vinde hver kamp.\"" msgid "Sword Breaker" msgstr "Sværdbrækkeren" #, fuzzy msgid "The %{name} increases the hero's defense by %{count} and attack by 1." msgstr "%{name} øger bÃ¥de din angrebs- og forsvarsevne med %{count} point." msgid "" "A former Captain of the Guard admires your quest and gives you the enchanted " "Sword Breaker that he relied on during his tour of duty." msgstr "" "En tidligere kaptajn af vagten beundrer din sag og giver dig den fortryllede " "sværdbryder, som hans liv afhang af under sin tjenesterejse." msgid "Sword of Anduran" msgstr "Andurans Sværd" msgid "" "A Troll stops you and says: \"Pay me 5,000 gold, or the Sword of Anduran " "will slay you where you stand.\" You refuse. The troll grabs the sword " "hanging from its belt, screams in pain, and runs away. Picking up the fabled " "sword, you give thanks that half-witted Trolls tend to grab the wrong end of " "sharp objects." msgstr "" "En trold standser dig og siger: \"Giv mig 5.000 guldstykker, ellers slÃ¥r " "Andurans Sværd dig ihjel, hvor du stÃ¥r.\" Du nægter. Trolden griber sværdet, " "som hænger i dets bælte, skriger af smerte og løber væk. \n" "Idet du samler det sagnomspundne sværd op, takker du for, at halvkloge " "trolde har tendens til at gribe fat den forkerte ende af skarpe genstande." msgid "Spade of Necromancy" msgstr "Nekromantikerens Spade" #, fuzzy msgid "The %{name} gives the hero increased necromancy skill." msgstr "%{name} dine nekromantiske evne." msgid "" "A dirty shovel has been thrust into a dirt mound nearby. Upon investigation, " "you discover it to be the enchanted shovel of the Gravediggers, long thought " "lost by mortals." msgstr "" "En snavset skovl er blevet stukket ned i en jordhøj i nærheden. Efter en " "søgen opdager du, at det er dødgravernes fortryllede skovl, som man længe " "har troet var gÃ¥et tabt for dødelige." msgid "spellBonus|selected by user" msgstr "Valgt af bruger" msgid "Wood" msgstr "træ" msgid "Mercury" msgstr "kviksølv" msgid "Ore" msgstr "malm" msgid "Sulfur" msgstr "svovl" msgid "Crystal" msgstr "krystal" msgid "Gems" msgstr "juveler" msgid "Gold" msgstr "guld" msgid "" "There are seven resources in Heroes 2, used to build and improves castles, " "purchase troops and recruit heroes. Gold is the most common, required for " "virtually everything. Wood and ore are used for most buildings. Gems, " "Mercury, Sulfur and Crystal are rare magical resources used for the most " "powerful creatures and buildings." msgstr "" "Der er syv resursetyper i Heroes 2. Disse bruges til at bygge og udvikle " "borge og byer samt rekruttering af helte og tropper.\n" "Guld er den mest almindelige resurse, som grundlæggende bruges til alt.\n" "Træ og malm bruges til de fleste byggerier.\n" "Kviksølv, svovl, krystaller og juveler er sjældne magiske resurser, som " "bruges til de mægtigste byggerier og væsener." msgid "" "Causes a giant fireball to strike the selected area, damaging all nearby " "creatures." msgstr "" "FÃ¥r en kæmpe ildkugle til at ramme og give skade til alle væsener i det " "valgte omrÃ¥de." msgid "Fireball" msgstr "Ildkugle" msgid "Fireblast" msgstr "Flammebrag" msgid "" "An improved version of fireball, fireblast affects two hexes around the " "center point of the spell, rather than one." msgstr "" "Rammer og giver skade til alle væsener i det valgte omrÃ¥de. Dette er en " "forbedret version af Ildkugle." msgid "Causes a bolt of electrical energy to strike the selected creature." msgstr "FÃ¥r et lyn af elektrisk energi til at ramme det valgte væsen." msgid "Lightning Bolt" msgstr "Lynkile" msgid "Chain Lightning" msgstr "Lynkæde" msgid "" "Causes a bolt of electrical energy to strike a selected creature, then " "strike the nearest creature with half damage, then strike the NEXT nearest " "creature with half again damage, and so on, until it becomes too weak to be " "harmful. Warning: This spell can hit your own creatures!" msgstr "" "FÃ¥r et lyn af elektrisk energi til først at ramme det valgte væsen, for " "derefter ramme dets nærmeste væsen med 50% skade, sÃ¥ det næstnærmeste væsen " "med 25% skade, og sÃ¥ videre, indtil det bliver for svagt til at være " "skadeligt. Advarsel: Denne besværgelse kan ramme dine egne tropper!" msgid "Teleport" msgstr "Teleport" msgid "" "Teleports the creature you select to any open position on the battlefield." msgstr "" "Teleporterer det væsen, du vælger, til en hvilken som helst Ã¥ben position pÃ¥ " "slagmarken." msgid "Cure" msgstr "Kur" msgid "" "Removes all negative spells cast upon one of your units, and restores up to " "%{count} HP per level of spell power." msgstr "" "Fjerner alle negative besværgelser fra en af dine tropper og gendanner op " "til %{count} HP per magikraftspoint." msgid "Mass Cure" msgstr "Massekur" msgid "" "Removes all negative spells cast upon your forces, and restores up to " "%{count} HP per level of spell power, per creature." msgstr "" "Fjerner alle negative besværgelser, som din hær er blevet underlagt, og " "genopretter op til %{count} HP per magikraftspoint, per væsen." msgid "Resurrect" msgstr "Genoplivelse" msgid "Resurrects creatures from a damaged or dead unit until end of combat." msgstr "" "Genopliver væsener fra en beskadiget eller død trop indtil slutningen af " "kampen." msgid "Resurrect True" msgstr "Ægte genoplivelse" msgid "Resurrects creatures from a damaged or dead unit permanently." msgstr "Genopliver væsener fra en beskadiget eller død trop permanent." msgid "Haste" msgstr "Forhastning" msgid "Increases the speed of any creature by %{count}." msgstr "Øger en af dine troppers bevægelseshastighed med %{count} point." msgid "Increases the speed of all of your creatures by %{count}." msgstr "Øger hele din hærs bevægelseshastighed med %{count} point." msgid "Mass Haste" msgstr "" "Masse-\n" "forhastning" msgid "Slows target to half movement rate." msgstr "Halverer en fjendtlig trops bevægelseshastighed." msgid "spell|Slow" msgstr "Forsløvning" msgid "Mass Slow" msgstr "" "Masse-\n" "forsløvning" msgid "Slows all enemies to half movement rate." msgstr "Halverer hele den fjendtlige hærs bevægelseshastighed." msgid "Clouds the affected creatures' eyes, preventing them from moving." msgstr "Skygger den berørte trops øjne og forhindrer dem i at bevæge sig." msgid "spell|Blind" msgstr "Blinding" msgid "Bless" msgstr "Velsignelse" msgid "Causes the selected creatures to inflict maximum damage." msgstr "FÃ¥r den valgte trop til at yde maksimal skade." msgid "Causes all of your units to inflict maximum damage." msgstr "FÃ¥r hele din hær til at yde maksimal skade." msgid "Mass Bless" msgstr "" "Masse-\n" "velsignelse" msgid "Magically increases the defense skill of the selected creatures." msgstr "Øger pÃ¥ magisk vis den valgte trops forsvarsevne." msgid "Stoneskin" msgstr "Stenhud" msgid "" "Increases the defense skill of the targeted creatures. This is an improved " "version of Stoneskin." msgstr "" "Øger den valgte trops forsvarsevne. Dette er en forbedret version af " "besværgelsen Stenhud." msgid "Steelskin" msgstr "StÃ¥lhud" msgid "Causes the selected creatures to inflict minimum damage." msgstr "FÃ¥r den valgte trop til at yde minimal skade." msgid "Curse" msgstr "Forbandelse" msgid "Causes all enemy troops to inflict minimum damage." msgstr "FÃ¥r alle fjendtlige tropper til at yde minimal skade." msgid "Mass Curse" msgstr "" "Masse-\n" "forbandelse" msgid "Damages all undead in the battle." msgstr "Skader alle udøde pÃ¥ slagmarken." msgid "Holy Word" msgstr "Helligt Ord" msgid "" "Damages all undead in the battle. This is an improved version of Holy Word." msgstr "" "Skader alle udøde pÃ¥ slagmarken. Dette er en forbedret version af Helligt " "Ord." msgid "Holy Shout" msgstr "Helligt RÃ¥b" msgid "Anti-Magic" msgstr "Anti-magi" msgid "Prevents harmful magic against the selected creatures." msgstr "Forhindrer skadelig magi mod den valgte trop." msgid "Dispel Magic" msgstr "" "Magi-\n" "ophævelse" msgid "Removes all magic spells from a single target." msgstr "Fjerner alle magiske besværgelser fra den valgte trop." msgid "Mass Dispel" msgstr "" "Masse-\n" "ophævelse" msgid "Removes all magic spells from all creatures." msgstr "Fjerner alle magiske besværgelser fra samtlige tropper." msgid "Causes a magic arrow to strike the selected target." msgstr "FÃ¥r en magisk pil til at ramme den valgte trop." msgid "Magic Arrow" msgstr "Magisk Pil" msgid "Berserker" msgstr "Bersærker" msgid "Causes a creature to attack its nearest neighbor." msgstr "" "FÃ¥r en trop til, pÃ¥ sin tur, at angribe den nærmeste trop, ven som fjende!" msgid "Armageddon" msgstr "Ragnarok" msgid "" "Holy terror strikes the battlefield, causing severe damage to all creatures." msgstr "" "Guddommelig rædsel rammer slagmarken og forÃ¥rsager alvorlig skade pÃ¥ alle " "tropper." msgid "Elemental Storm" msgstr "Elemental Storm" msgid "Magical elements pour down on the battlefield, damaging all creatures." msgstr "Magiske elementer strømmer ned pÃ¥ slagmarken og skader alle tropper." msgid "" "A rain of rocks strikes an area of the battlefield, damaging all nearby " "creatures." msgstr "" "En regn af sten rammer det valgte omrÃ¥de pÃ¥ slagmarken, som skader alle " "berørte tropper." msgid "Meteor Shower" msgstr "Meteorregn" msgid "Paralyze" msgstr "Paralyse" msgid "The targeted creatures are paralyzed, unable to move or retaliate." msgstr "" "Den valgte trop lammes og vil derefter være ude af stand til at bevæge sig " "eller slÃ¥ igen." msgid "Hypnotize" msgstr "Hypnose" msgid "" "Brings a single enemy unit under your control if its hits are less than " "%{count} times the caster's spell power." msgstr "" "Bringer en enkelt fjendtlig trop under din kontrol, hvis dens HP er mindre " "end %{count} gange kasterens magikraftspoint." msgid "Cold Ray" msgstr "KoldstrÃ¥le" msgid "Drains body heat from a single enemy unit." msgstr "Dræner kropsvarmen fra en enkelt fjendtlig trop." msgid "Cold Ring" msgstr "Koldring" msgid "" "Drains body heat from all units surrounding the center point, but not " "including the center point." msgstr "" "Dræner kropsvarmen fra alle tropper omkring træfpunktet, men ikke inklusiv " "dette træfpunkt." msgid "Disrupting Ray" msgstr "" "Erossions-\n" "strÃ¥le" msgid "Reduces the defense rating of an enemy unit by three." msgstr "Reducerer en fjendtlig trops forsvarsevne med 3 point." msgid "Damages all living (non-undead) units in the battle." msgstr "Skader alle levende (ikke-udøde) tropper pÃ¥ slagmarken." msgid "Death Ripple" msgstr "" "Døds-\n" "krusning" msgid "Death Wave" msgstr "" "Døds-\n" "bølge" msgid "" "Damages all living (non-undead) units in the battle. This spell is an " "improved version of Death Ripple." msgstr "" "Skader alle levende (ikke-udøde) tropper pÃ¥ slagmarken. Dette er en " "forbedret version af besværgelsen Dødskrusning." msgid "Dragon Slayer" msgstr "" "Drage-\n" "dræber" msgid "Greatly increases a unit's attack skill vs. Dragons." msgstr "Øger i høj grad en trops angrebsevne mod drager." msgid "Blood Lust" msgstr "Blodlyst" msgid "Increases a unit's attack skill." msgstr "Øger en trops angrebsevne." msgid "Animate Dead" msgstr "" "Døde-\n" "besjæling" msgid "Resurrects creatures from a damaged or dead undead unit permanently." msgstr "Genopliver væsener fra en beskadiget eller død udød trop permanent." msgid "Mirror Image" msgstr "Spejlbillede" msgid "" "Creates an illusionary unit that duplicates one of your existing units. This " "illusionary unit does the same damages as the original, but will vanish if " "it takes any damage." msgstr "" "Skaber en illusionær trop, der dublerer en af dine eksisterende tropper. " "Denne illusionære trop giver det samme i skade som originalen, men den " "forsvinder, hvis den tager skade." msgid "Shield" msgstr "Skærmning" msgid "" "Halves damage received from ranged attacks for a single unit. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Halverer skade modtaget fra afstandsangreb for en enkelt trop. PÃ¥virker ikke " "skade modtaget af skydetÃ¥rne eller ballister." msgid "Mass Shield" msgstr "" "Masse-\n" "skærmning" msgid "" "Halves damage received from ranged attacks for all of your units. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Halverer skade modtaget fra afstandsangreb for hele din hær. PÃ¥virker ikke " "skade modtaget af skydetÃ¥rne eller ballister." msgid "Summon Earth Elemental" msgstr "" "Hidkald jord-\n" "elemental" msgid "Summons Earth Elementals to fight for your army." msgstr "Hidkald jord-elementaler til at kæmpe for din hær." msgid "Summon Air Elemental" msgstr "" "Hidkald luft-\n" "elemental" msgid "Summons Air Elementals to fight for your army." msgstr "Hidkald luft-elementaler til at kæmpe for din hær." msgid "Summon Fire Elemental" msgstr "" "Hidkald ild-\n" "elemental" msgid "Summons Fire Elementals to fight for your army." msgstr "Hidkald ild-elementaler til at kæmpe for din hær." msgid "Summon Water Elemental" msgstr "" "Hidkald vand-\n" "elemental" msgid "Summons Water Elementals to fight for your army." msgstr "Hidkald vand-elementaler til at kæmpe for din hær." msgid "Damages castle walls." msgstr "Skader alle borgmure." msgid "Earthquake" msgstr "Jordskælv" msgid "Causes all mines across the land to become visible." msgstr "FÃ¥r alle miner pÃ¥ tværs af landet til at blive synlige." msgid "View Mines" msgstr "Vis miner" msgid "Causes all resources across the land to become visible." msgstr "FÃ¥r alle resurser pÃ¥ tværs af landet til at blive synlige." msgid "View Resources" msgstr "Vis resurser" msgid "Causes all artifacts across the land to become visible." msgstr "FÃ¥r alle artefakter pÃ¥ tværs af landet til at blive synlige." msgid "View Artifacts" msgstr "Vis artefakter" msgid "Causes all towns and castles across the land to become visible." msgstr "FÃ¥r alle byer og borge over hele landet til at blive synlige." msgid "View Towns" msgstr "Vis byer" msgid "Causes all Heroes across the land to become visible." msgstr "FÃ¥r alle helte over hele landet til at blive synlige." msgid "View Heroes" msgstr "Vis helte" msgid "Causes the entire land to become visible." msgstr "FÃ¥r hele landet til at blive synligt." msgid "View All" msgstr "Vis alt" msgid "Allows the caster to view detailed information on enemy Heroes." msgstr "" "Giver kasteren mulighed for at se detaljerede oplysninger om modstander-" "helte." msgid "Summon Boat" msgstr "Hidkald skib" msgid "" "Summons the nearest unoccupied, friendly boat to an adjacent shore location. " "A friendly boat is one which you just built or were the most recent player " "to occupy." msgstr "" "Hidkalder det nærmeste ubesatte, venlige skib til et tilstødende landsted. " "Et venligt skib kan være et skib, som du lige har bygget eller har været den " "seneste spiller til at besætte." msgid "Allows the caster to magically transport to a nearby location." msgstr "" "Tillader kasteren pÃ¥ magisk vis at teleportere sig selv og hele sin hær til " "et nærliggende sted." msgid "Dimension Door" msgstr "" "Dimensions-\n" "dør" msgid "Returns the caster to any town or castle currently owned." msgstr "Returnerer kasteren til enhver af spillerens byer eller borge." msgid "Town Gate" msgstr "Byflugt" msgid "" "Returns the hero to the town or castle of choice, provided it is controlled " "by you." msgstr "" "Returnerer helten til en af dine byer eller borge, efter eget valg, forudsat " "at den er under din kontrol." msgid "Visions" msgstr "Visioner" msgid "" "Visions predicts the likely outcome of an encounter with a neutral army camp." msgstr "" "'Visioner' forudsiger det sandsynlige udfald af et møde med en neutral " "hærlejr." msgid "Haunt" msgstr "Besæt" msgid "" "Haunts a mine you control with Ghosts. This mine stops producing resources. " "(If I can't keep it, nobody will!)" msgstr "" "Hjemsøger en mine du kontrollerer, med spøgelser. Denne mine holder op med " "at producere resurser. ('Hvis jeg ikke kan beholde den, er der ingen der " "vil!')" msgid "Set Earth Guardian" msgstr "Sæt Jord-vagt" msgid "Sets Earth Elementals to guard a mine against enemy armies." msgstr "Sætter jord-elementaler til at beskytte en mine mod fjendtlige hære." msgid "Set Air Guardian" msgstr "Sæt Luft-vagt" msgid "Sets Air Elementals to guard a mine against enemy armies." msgstr "Sætter luft-elementaler til at beskytte en mine mod fjendtlige hære." msgid "Set Fire Guardian" msgstr "Sæt Ild-vagt" msgid "Sets Fire Elementals to guard a mine against enemy armies." msgstr "Sætter ild-elementaler til at beskytte en mine mod fjendtlige hære." msgid "Set Water Guardian" msgstr "Sæt Vand-vagt" msgid "Sets Water Elementals to guard a mine against enemy armies." msgstr "Sætter vand-elementaler til at beskytte en mine mod fjendtlige hære." msgid "Random Spell" msgstr "Tilfældig besværgelse" msgid "Randomly selected spell of any level." msgstr "Tilfældigt valgt besværgelse fra ethvert niveau." msgid "Random 1st Level Spell" msgstr "Tilfældig 1.niveau besværgelse" msgid "Randomly selected 1st level spell." msgstr "Tilfældigt valgt 1.niveau besværgelse." msgid "Random 2nd Level Spell" msgstr "Tilfældig 2.niveau besværgelse" msgid "Randomly selected 2nd level spell." msgstr "Tilfældigt valgt 2.niveau besværgelse." msgid "Random 3rd Level Spell" msgstr "Tilfældig 3.niveau besværgelse." msgid "Randomly selected 3rd level spell." msgstr "Tilfældigt valgt 3.niveau besværgelse." msgid "Random 4th Level Spell" msgstr "Tilfældig 4.niveau besværgelse." msgid "Randomly selected 4th level spell." msgstr "Tilfældigt valgt 4.niveau besværgelse." msgid "Random 5th Level Spell" msgstr "Tilfældig 5.niveau besværgelse." msgid "Randomly selected 5th level spell." msgstr "Tilfældigt valgt 5.niveau besværgelse." msgid "Petrification" msgstr "Forstening" msgid "" "Turns the affected creature into stone. A petrified creature receives half " "damage from a direct attack." msgstr "" "Forvandler den berørte trop til sten. En forstenet trop fÃ¥r halv skade fra " "et direkte angreb." msgid "You have no Magic Book, so you cannot cast a spell." msgstr "Du har ingen magibog, sÃ¥ du kan ikke kaste magi." msgid "No spell to cast." msgstr "Ingen magi at kaste." msgid "Your hero has %{point} spell points remaining." msgstr "Din helt har %{point} manapoint tilbage." msgid "View Adventure Spells" msgstr "Vis eventyrmagi" msgid "View Combat Spells" msgstr "Vis kampmagi" msgid "View previous page" msgstr "Vis forrige side" msgid "View next page" msgstr "Vis næste side" msgid "Close Spellbook" msgstr "Luk magibog" msgid "View %{spell}" msgstr "Vis %{spell}" msgid "This spell does %{damage} points of damage." msgstr "Denne besværgelse giver %{damage} i skade." msgid "" "This spell summons\n" "%{count} %{monster}." msgstr "" "Denne besværgelse hidkalder\n" "%{count} %{monster}." msgid "This spell restores %{hp} HP." msgstr "Denne besværgelse gendanner %{hp} HP." msgid "This spell summons %{count} %{monster} to guard the mine." msgstr "" "Denne besværgelse hidkalder %{count} %{monster}, for at beskytte minen." msgid "The nearest town is %{town}." msgstr "Den nærmeste by er %{town}." msgid "This town is occupied by your hero %{hero}." msgstr "Denne by er besat af din helt %{hero}." msgid "" "This spell controls up to\n" "%{hp} HP." msgstr "" "Denne besværgelse kontrollerer op til\n" "%{hp} HP." msgid "The ultimate artifact is really the %{name}." msgstr "Det ultimative artefakt er i virkeligheden %{name}." msgid "The ultimate artifact may be found in the %{name} regions of the world." msgstr "Det ultimative artefakt kan findes i verdens %{name}-regioner." msgid "north-west" msgstr "nordvest" msgid "north" msgstr "nord" msgid "north-east" msgstr "nordøst" msgid "west" msgstr "vest" msgid "center" msgstr "centrum" msgid "east" msgstr "øst" msgid "south-west" msgstr "sydvest" msgid "south" msgstr "syd" msgid "south-east" msgstr "sydøst" msgid "The truth is out there." msgstr "Sandheden er derude." msgid "The dark side is stronger." msgstr "Den mørke side er stærkere." msgid "The end of the world is near." msgstr "Verdens ende er nær." msgid "The bones of Lord Slayer are buried in the foundation of the arena." msgstr "Fyrst Slayers knogler er begravet i arenaens fundament." msgid "A Black Dragon will take out a Titan any day of the week." msgstr "En sortdrage kan til enhver tid klare en titan." msgid "He told her: Yada yada yada... and then she said: Blah, blah, blah..." msgstr "" "Til hende sagde han: \"snakke, snakke, snakke...\", og til det svarede hun: " "\"Bla, bla, bla...\"" msgid "An unknown force is being resurrected..." msgstr "En ukendt styrke er ved at genopstÃ¥..." msgid "" "Check the newest version of the game at\n" "https://github.com/ihhub/\n" "fheroes2/releases" msgstr "" "Check den nyeste version af spillet pÃ¥\n" "https://github.com/ihhub/\n" "fheroes2/releases" #~ msgid "Terrain" #~ msgstr "Terræn-tilstand" #~ msgid "Castles" #~ msgstr "Borge" #~ msgid "Ocean object" #~ msgstr "Hav-objekt" #~ msgid "Ocean Objects" #~ msgstr "Hav-objekter" #~ msgid "Grass Objects" #~ msgstr "Græs-objekter" #~ msgid "Snow Objects" #~ msgstr "Sne-objekter" #~ msgid "Swamp Objects" #~ msgstr "Sump-objekter" #~ msgid "Lava Objects" #~ msgstr "Lava-objekter" #~ msgid "Desert Objects" #~ msgstr "Ørken-objekter" #~ msgid "Dirt Objects" #~ msgstr "Jord-objekter" #~ msgid "Beach Objects" #~ msgstr "Strand-objekter" #~ msgid "Used to place objects most appropriate for use on %{terrain}." #~ msgstr "" #~ "Bruges til at placere objekter, som passer best pÃ¥ terræntypen %{terrain}." #~ msgid "" #~ "Used to place\n" #~ "a town or castle." #~ msgstr "" #~ "Bruges til at placere\n" #~ "en by eller borg" #~ msgid "Used to place an artifact." #~ msgstr "Bruges til at placere et artefakt." #~ msgid "" #~ "Used to place\n" #~ "a resource or treasure." #~ msgstr "" #~ "Bruges til at placere\n" #~ "en resurse eller skat" #~ msgid "%{race1} %{name1}" #~ msgstr "%{race1} %{name1}" #~ msgid "vs" #~ msgstr "vs" #~ msgid "%{race2} %{name2}" #~ msgstr "%{race2} %{name2}" fheroes2-1.0.12+dfsg/files/lang/es.po000066400000000000000000010114211456075706000172330ustar00rootroot00000000000000# Spanish translation for fheroes2 # Copyright (C) 2022 fheroes2 team # This file is distributed under the same license as the fheroes2 package. # msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-03 03:27+0000\n" "PO-Revision-Date: 2024-02-05 05:47+0100\n" "Last-Translator: fheroes2 team \n" "Language-Team: Spanish \n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Launchpad-Export-Date: 2013-10-09 02:01+0000\n" "X-Generator: Poedit 3.2.2\n" msgid "" "BATTLE\n" "ONLY" msgstr "BATALLA" msgid "" "NEW\n" "GAME" msgstr "" "NUEVO\n" "JUEGO" msgid "" "SAVE\n" "GAME" msgstr "" "GUARD.\n" "PAR-\n" "TIDA" msgid "" "LOAD\n" "GAME" msgstr "" "CARGAR\n" "PAR-\n" "TIDA" msgid "INFO" msgstr "INFO" msgid "QUIT" msgstr "SALIR" msgid "CANCEL" msgstr "CANCELAR" msgid "OKAY" msgstr "VALE" msgid "smallerButton|OKAY" msgstr "VALE" msgid "ACCEPT" msgstr "ACEPTAR" msgid "DECLINE" msgstr "RECHAZAR" msgid "LEARN" msgstr "ESCOGE" msgid "TRADE" msgstr "COMERCIAR" msgid "YES" msgstr "SÃ" msgid "NO" msgstr "NO" msgid "EXIT" msgstr "SALIR" msgid "smallerButton|EXIT" msgstr "SALIR" msgid "DISMISS" msgstr "DESPEDIR" msgid "UPGRADE" msgstr "MEJORAR" msgid "RESTART" msgstr "REINICIAR" msgid "HEROES" msgstr "HÉROES" msgid "" "TOWNS/\n" "CASTLES" msgstr "" "CIU-\n" "DADES" msgid "S" msgstr "S" msgid "M" msgstr "M" msgid "L" msgstr "L" msgid "X-L" msgstr "XL" msgid "ALL" msgstr "TODO" msgid "SELECT" msgstr "ESCOGER" msgid "" "STANDARD\n" "GAME" msgstr "" "PARTIDA\n" "ESTÃNDAR" msgid "" "CAMPAIGN\n" "GAME" msgstr "CAMPAÑA" msgid "" "MULTI-\n" "PLAYER\n" "GAME" msgstr "" "MULTI-\n" "JUGADOR" msgid "CONFIG" msgstr "AJUSTES" msgid "" "ORIGINAL\n" "CAMPAIGN" msgstr "" "CAMPAÑA\n" "ORIGINAL" msgid "" "EXPANSION\n" "CAMPAIGN" msgstr "" "CAMPAÑA\n" "DE EXPAN-\n" "SIÓN" msgid "HOT SEAT" msgstr "" msgid "2 PLAYERS" msgstr "" "2 JUGA-\n" "DORES" msgid "3 PLAYERS" msgstr "" "3 JUGA-\n" "DORES" msgid "4 PLAYERS" msgstr "" "4 JUGA-\n" "DORES" msgid "5 PLAYERS" msgstr "" "5 JUGA-\n" "DORES" msgid "6 PLAYERS" msgstr "" "6 JUGA-\n" "DORES" msgid "GIFT" msgstr "DONAR" msgid "MAX" msgstr "MÃX" msgid "DIFFICULTY" msgstr "DIFICULTAD" msgid "VIEW INTRO" msgstr "VER INTRO" msgid "MIN" msgstr "MÃN" msgid "RESET" msgstr "REINICIAR" msgid "START" msgstr "COMENZAR" msgid "CASTLE" msgstr "CASTILLO" msgid "TOWN" msgstr "" msgid "" "D\n" "I\n" "S\n" "M\n" "I\n" "S\n" "S" msgstr "" "D\n" "E\n" "S\n" "P\n" "I\n" "D\n" "E" msgid "" "E\n" "X\n" "I\n" "T" msgstr "" "S\n" "A\n" "L\n" "I\n" "R" msgid "" "C\n" "A\n" "M\n" "P\n" "A\n" "I\n" "G\n" "N" msgstr "" "C\n" "A\n" "M\n" "P\n" "A\n" "Ñ\n" "A" msgid "" "S\n" "T\n" "A\n" "N\n" "D\n" "A\n" "R\n" "D" msgstr "" "E\n" "S\n" "T\n" "Ã\n" "N\n" "D\n" "A\n" "R" msgid "Warrior" msgstr "Guerrero" msgid "Builder" msgstr "Constructor" msgid "Explorer" msgstr "Explorador" msgid "None" msgstr "Ninguno" msgid "" "A few\n" "%{monster}" msgstr "" "Unos pocos\n" "%{monster}" msgid "" "Several\n" "%{monster}" msgstr "" "Varios\n" "%{monster}" msgid "" "A pack of\n" "%{monster}" msgstr "" "Un grupo de\n" "%{monster}" msgid "" "Lots of\n" "%{monster}" msgstr "" "Muchos\n" "%{monster}" msgid "" "A horde of\n" "%{monster}" msgstr "" "Una horda de\n" "%{monster}" msgid "" "A throng of\n" "%{monster}" msgstr "" "Una multitud de\n" "%{monster}" msgid "" "A swarm of\n" "%{monster}" msgstr "" "Un enjambre de\n" "%{monster}" msgid "" "Zounds...\n" "%{monster}" msgstr "" "Demasiados\n" "%{monster}" msgid "" "A legion of\n" "%{monster}" msgstr "" "Una legión de\n" "%{monster}" msgid "army|Few" msgstr "Pocos" msgid "army|Several" msgstr "Varios" msgid "army|Pack" msgstr "Grupo" msgid "army|Lots" msgstr "Muchos" msgid "army|Horde" msgstr "Horda" msgid "army|Throng" msgstr "Multitud" msgid "army|Swarm" msgstr "Enjambre" msgid "army|Zounds" msgstr "Demasiados" msgid "army|Legion" msgstr "Legión" msgid "All %{race} troops +1" msgstr "Toda tropa de %{race} +1" msgid "Multiple" msgstr "Múltiple" msgid "Troops of %{count} alignments -%{penalty}" msgstr "Tropas de %{count} alineamientos -%{penalty}" msgid "Some undead in army -1" msgstr "Algunos nomuertos en el ejercito -1" msgid "View %{name}" msgstr "Ver %{name}" #, fuzzy msgid "Move the %{name} " msgstr "Pierdes el héroe: %{name}." msgid "Move or right click to redistribute %{name}" msgstr "" msgid "Combine %{name} armies" msgstr "Combinar los ejércitos de %{name}" msgid "Exchange %{name2} with %{name}" msgstr "Intercambiar %{name2} por %{name}" msgid "Select %{name}" msgstr "Seleccionar %{name}" msgid "Cannot move last troop" msgstr "" #, fuzzy msgid "Move the %{name}" msgstr "Pierdes el héroe: %{name}." msgid "Set Count" msgstr "" msgid "%{name} destroys half the enemy troops!" msgid_plural "%{name} destroy half the enemy troops!" msgstr[0] "El %{name} elimina mitad de las tropas enemigas!" msgstr[1] "Los %{name} eliminan mitad de las tropas enemigas!" #, fuzzy msgid "%{name} has turned off the auto battle" msgstr "Daña a todos los Nomuertos en combate." #, fuzzy msgid "%{name} has turned on the auto battle" msgstr "Daña a todos los Nomuertos en combate." msgid "Spell failed!" msgstr "¡Hechizo fallido!" msgid "" "The Sphere of Negation artifact is in effect for this battle, disabling all " "combat spells." msgstr "" "El artefacto Esfera de la Negación está afectando a esta batalla, " "deshabilitando todos los hechizos de combate." msgid "You have already cast a spell this round." msgstr "Ya has lanzado un hechizo en este turno." msgid "That spell will have no effect!" msgstr "¡Ese hechizo no tendrá ningún efecto!" msgid "You may only summon one type of elemental per combat." msgstr "Solo puedes convocar un tipo de elemental por combate." msgid "" "There is no open space adjacent to your hero where you can summon an " "Elemental to." msgstr "" "No hay un espacio libre adyacente a tu héroe para convocar un Elemental." msgid "" "The Moat reduces by -%{count} the defense skill of any unit and slows to " "half movement rate." msgstr "" msgid "Speed: %{speed}" msgstr "Velocidad: %{speed}" msgid "Speed" msgstr "Velocidad" msgid "Off" msgstr "Desactivado" msgid "On" msgstr "Activado" msgid "Turn Order" msgstr "Orden de turno" #, fuzzy msgid "Auto Spell Casting" msgstr "Conjuradores" msgid "Grid" msgstr "Rejilla" #, fuzzy msgid "Shadow Movement" msgstr "Continuar movimento" msgid "Shadow Cursor" msgstr "Cursor Sombreado" msgid "Audio" msgstr "Sonido" msgid "Settings" msgstr "Ajustes" msgid "Configure" msgstr "Configurar" msgid "Hot Keys" msgstr "Atajos de Teclado" msgid "Damage Info" msgstr "Info de Daño" msgid "Set the speed of combat actions and animations." msgstr "Ajustar velocidad de las acciones y animaciones de combate." msgid "Toggle to display the turn order during the battle." msgstr "" msgid "" "Toggle whether or not the computer will cast spells for you when auto combat " "is on. (Note: This does not affect spell casting for computer players in any " "way, nor does it affect quick combat.)" msgstr "" msgid "" "Toggle the hex grid on or off. The hex grid always underlies movement, even " "if turned off. This switch only determines if the grid is visible." msgstr "" msgid "" "Toggle on or off shadows showing where your creatures can move and attack." msgstr "" msgid "" "Toggle on or off a shadow showing the current hex location of the mouse " "cursor." msgstr "" msgid "Change the audio settings of the game." msgstr "" msgid "Check and configure all the hot keys present in the game." msgstr "" msgid "Toggle to display damage information during the battle." msgstr "" msgid "Exit this menu." msgstr "Salir de este menú." msgid "Okay" msgstr "Vale" msgid "The enemy has surrendered!" msgstr "¡El enemigo se ha rendido!" msgid "Their cowardice costs them %{gold} gold." msgstr "Su cobardía le cuesta %{gold} piezas de oro." msgid "The enemy has fled!" msgstr "¡El enemigo ha huido!" msgid "A glorious victory!" msgstr "¡Una gloriosa victoria!" msgid "For valor in combat, %{name} receives %{exp} experience." msgstr "Por valor en el combate, %{name} recibe %{exp} experiencia." msgid "The cowardly %{name} flees from battle." msgstr "El cobarde %{name} huye de la batalla." msgid "%{name} surrenders to the enemy, and departs in shame." msgstr "%{name} se rinde al enemigo, marchandose avergonzado." msgid "Your forces suffer a bitter defeat, and %{name} abandons your cause." msgstr "Tus fuerzas sufren una amarga derrota, y %{name} abandona tu causa." msgid "Your forces suffer a bitter defeat." msgstr "Tus fuerzas sufren una amarga derrota." msgid "Battlefield Casualties" msgstr "Bajas en el campo de batalla" msgid "Attacker" msgstr "Agresor" msgid "Defender" msgstr "Defensor" #, fuzzy msgid "Click to leave the battle results." msgstr "Haz clic para volver al menú principal" #, fuzzy msgid "Click to restart the battle in manual mode." msgstr "Haz clic para volver al menú principal" msgid "Restart" msgstr "" msgid "You have captured an enemy artifact!" msgstr "¡Has capturado un artefacto enemigo!" msgid "As you reach for the %{name}, it mysteriously disappears." msgstr "Cuando coges %{name}, desaparece misteriosamente." msgid "As your enemy reaches for the %{name}, it mysteriously disappears." msgstr "Cuando tu enemigo coge %{name}, desaparece misteriosamente." msgid "Necromancy!" msgstr "¡Nigromancia!" msgid "" "Practicing the dark arts of necromancy, you are able to raise %{count} of " "the enemy's dead to return under your service as %{monster}." msgstr "" "Practicando las oscuras artes de la nigromancia, eres capaz de alzar " "%{count} de los enemigos muertos para que vuelvan bajo tu servicio como " "%{monster}." msgid "%{name} the %{race}" msgstr "%{name}, %{race}" msgid "Captain of %{name}" msgstr "El capitán de %{name}" msgid "Attack" msgstr "Ataque" msgid "Defense" msgstr "Defensa" msgid "Spell Power" msgstr "Potencia" msgid "Knowledge" msgstr "Conocimiento" msgid "Morale" msgstr "Moral" msgid "Luck" msgstr "Suerte" msgid "Spell Points" msgstr "Maná" msgid "Hero's Options" msgstr "Opciones de Héroe" msgid "Cast Spell" msgstr "Lanzar Hechizo" msgid "Retreat" msgstr "Retirarse" msgid "Surrender" msgstr "Rendirse" msgid "Cancel" msgstr "Cancelar" msgid "Hero Screen" msgstr "Pantalla de Héroe" msgid "Captain's Options" msgstr "Opciones del capitán" #, fuzzy msgid "" "Cast a magical spell. You may only cast one spell per combat round. The " "round is reset when every creature has had a turn." msgstr "" "Lanza un hechizo mágico. Solo se puede lanzar un hechizo por ronda de " "combate. La ronda de combate vuelve a comenzar cuando todas las criaturas " "han tenido un turno." msgid "" "Retreat your hero, abandoning your creatures. Your hero will be available " "for you to recruit again, however, the hero will have only a novice hero's " "forces." msgstr "" "Retirar a tu héroe, abandonando sus criaturas. Tu héroe estará disponible " "para que lo reclutes, sin embargo, el héroe tendrá los reclutas de un novato " "(como cualquier otro héroe)." #, fuzzy msgid "" "Surrendering costs gold. However if you pay the ransom, the hero and all of " "his or her surviving creatures will be available to recruit again. The cost " "of surrender is half of the total cost of the non-temporary troops remaining " "in the army." msgstr "" "Rendirse cuesta oro. Sin embargo, si pagas el rescate, el héroe y todas sus " "criaturas supervivientes estarán disponibles para ser reclutadas otra vez." #, fuzzy msgid "Open Hero Screen to view full information about the hero." msgstr "Permite al lanzador ver información detallada de los héroes enemigos." msgid "Return to the battle." msgstr "Volver a la batalla." msgid "Not enough gold (%{gold})" msgstr "No hay suficiente oro (%{gold})" msgid "%{name} states:" msgstr "%{name} declara:" msgid "Captain of %{name} states:" msgstr "El capitán de %{name} declara:" msgid "" "\"I will accept your surrender and grant you and your troops safe passage " "for the price of %{price} gold.\"" msgstr "" "\"Aceptaré tu rendición y concederé salvoconducto a ti y tus tropas por " "%{price} piezas de oro.\"" msgid "View %{monster} info" msgstr "Ver información de %{monster}" msgid "Fly %{monster} here" msgstr "%{monster} vuela aquí" msgid "Move %{monster} here" msgstr "Mover a %{monster} aquí" msgid "Shoot %{monster}" msgstr "Dispara a %{monster}" msgid "(1 shot left)" msgid_plural "(%{count} shots left)" msgstr[0] "" msgstr[1] "" msgid "Attack %{monster}" msgstr "Atacar a %{monster}" msgid "Turn %{turn}" msgstr "Turno %{turn}" msgid "Teleport here" msgstr "Teletransportar aquí" msgid "Invalid teleport destination" msgstr "Destino de teletransporte inválido" msgid "Cast %{spell} on %{monster}" msgstr "Lanzar %{spell} sobre %{monster}" msgid "Cast %{spell}" msgstr "Lanzar %{spell}" msgid "Select spell target" msgstr "Seleccionar objetivo del hechizo" msgid "View Ballista info" msgstr "" msgid "Ballista" msgstr "Balista" #, fuzzy msgid "Enable auto combat" msgstr "Combate automático" msgid "Allows the computer to fight out the battle for you." msgstr "" msgid "Auto Combat" msgstr "Combate Automático" msgid "Customize system options" msgstr "Personalizar opciones de sistema" msgid "Allows you to customize the combat screen." msgstr "" msgid "System Options" msgstr "Opciones de Sistema" msgid "Skip this unit" msgstr "Saltar el turno de esta unidad" msgid "Skip" msgstr "" msgid "" "Skips the current creature. The current creature ends its turn and does not " "get to go again until the next round." msgstr "" msgid "View Captain's options" msgstr "Ver opciones de Capitán" #, fuzzy msgid "View Hero's options" msgstr "Opciones del héroe" msgid "View opposing Captain" msgstr "Ver Capitán del oponente" msgid "View opposing Hero" msgstr "Ver Héroe del oponente" msgid "Hide logs" msgstr "Ocultar registro" msgid "Show logs" msgstr "Mostrar registros" msgid "Message Bar" msgstr "" msgid "Shows the results of individual monster's actions." msgstr "" msgid "Are you sure you want to finish the battle in auto mode?" msgstr "¿Estás seguro de que quieres terminar la batalla en modo automático?" msgid "Are you sure you want to enable auto combat?" msgstr "¿Estás seguro de que quieres activar el combate automático?" msgid "%{name} skip their turn." msgstr "%{name} se pasan su turno." msgid "%{attacker} does %{damage} damage." msgid_plural "%{attacker} do %{damage} damage." msgstr[0] "%{attacker} hace %{damage} de daño." msgstr[1] "%{attacker} hacen %{damage} de daño." msgid "1 creature perishes." msgid_plural "%{count} creatures perish." msgstr[0] "Una criatura perece." msgstr[1] "%{count} criaturas perecen." msgid "1 %{defender} perishes." msgid_plural "%{count} %{defender} perish." msgstr[0] "1 %{defender} perece." msgstr[1] "%{count} %{defender} perecieron." msgid "1 soul is incorporated." msgid_plural "%{count} souls are incorporated." msgstr[0] "1 alma está incorporada." msgstr[1] "%{count} almas están incorporadas." msgid "1 %{unit} is revived." msgid_plural "%{count} %{unit} are revived." msgstr[0] "1 %{unit} revive." msgstr[1] "%{count} %{unit} reviven." msgid "Moved %{monster}: from [%{src}] to [%{dst}]." msgstr "%{monster} movido: De [%{src}] a [%{dst}]." msgid "The %{name} resist the spell!" msgstr "¡El %{name} resiste el hechizo!" msgid "%{name} casts %{spell} on the %{troop}." msgstr "%{name} lanza %{spell} sobre %{troop}." msgid "%{name} casts %{spell}." msgstr "%{name} lanza %{spell}." #, fuzzy msgid "The %{spell} does %{damage} damage to one undead creature." msgstr "" "El hechizo %{spell} hace %{damage} puntos de daño a todas las criaturas " "nomuertas." msgid "The %{spell} does %{damage} damage to all undead creatures." msgstr "" "El hechizo %{spell} hace %{damage} puntos de daño a todas las criaturas " "nomuertas." msgid "The %{spell} does %{damage} damage, %{count} creatures perish." msgstr "" "El hechizo %{spell} hace %{damage} puntos de daño, %{count} criaturas " "perecen." msgid "The %{spell} does %{damage} damage." msgstr "El hechizo %{spell} hacen %{damage} de daño." msgid "The %{spell} does %{damage} damage to one living creature." msgstr "El hechizo %{spell} hace %{damage} puntos de daño a una criatura viva." msgid "The %{spell} does %{damage} damage to all living creatures." msgstr "" "El hechizo %{spell} hace %{damage} puntos de daño a todas las criaturas " "vivas." msgid "The %{attacker}' attack blinds the %{target}!" msgstr "¡El ataque del %{attacker} ciega a %{target}!" msgid "The %{attacker}' gaze turns the %{target} to stone!" msgstr "¡La mirada de la %{attacker} convierte a %{target} en piedra!" msgid "The %{attacker}' curse falls upon the %{target}!" msgstr "¡La maldición de la %{attacker} cae sobre %{target}!" msgid "The %{target} are paralyzed by the %{attacker}!" msgstr "¡El %{target} es paralizado por los %{attacker}!" msgid "The %{attacker} dispel all good spells on your %{target}!" msgstr "¡El %{attacker} dispersó todos los hechizos buenos en tu %{target}!" msgid "The %{attacker} cast %{spell} on %{target}!" msgstr "%{attacker} lanza %{spell} sobre %{target}!" msgid "Bad luck descends on the %{attacker}." msgstr "La mala suerte desciende sobre %{attacker}." msgid "Good luck shines on the %{attacker}." msgstr "La buena suerte brilla sobre %{attacker}." msgid "High morale enables the %{monster} to attack again." msgstr "La alta moral permite a %{monster} atacar otra vez." msgid "Low morale causes the %{monster} to freeze in panic." msgstr "La baja moral provoca que %{monster} se paralice por el pánico." msgid "%{tower} does %{damage} damage." msgstr "La %{tower} ha causado %{damage} de daño." #, fuzzy msgid "The mirror image is created." msgstr "Reflejo creado." msgid "The mirror image is destroyed!" msgstr "" msgid "Are you sure you want to interrupt the auto battle?" msgstr "¿Estas seguro de que quieres interrumpir la batalla automática?" msgid "Error" msgstr "Error" msgid "No spells to cast." msgstr "No hay hechizos para lanzar." msgid "Are you sure you want to retreat?" msgstr "¿Estás seguro de que quieres retirarte?" msgid "Retreat disabled" msgstr "Retirada desactivada" msgid "Surrender disabled" msgstr "Rendición desactivada" msgid "Damage: %{max}" msgstr "Daño: %{max}" msgid "Damage: %{min} - %{max}" msgstr "Daño: %{min} - %{max}" msgid "Perish: %{max}" msgstr "Mueren: %{max}" msgid "Perish: %{min} - %{max}" msgstr "Mueren: %{min} - %{max}" msgid "" "Through eagle-eyed observation, %{name} is able to learn the magic spell " "%{spell}." msgstr "" "Mediante la observación del ojo de halcón, %{name} es capaz de aprender el " "hechizo mágico %{spell}." msgid "Human" msgstr "Humano" msgid "AI" msgstr "IA" msgid "Start" msgstr "" msgid "Start the battle." msgstr "Comienza la batalla." msgid "Exit" msgstr "Salir" msgid "Reset" msgstr "Reiniciar" msgid "Reset to default settings." msgstr "Restablecer la configuración predeterminada." msgid "Please select another hero." msgstr "Por favor, selecciona otro héroe." #, fuzzy msgid "Set Attack Skill" msgstr "Ataque" #, fuzzy msgid "Set Defense Skill" msgstr "Defensa" #, fuzzy msgid "Set Power Skill" msgstr "Pot. de Hech." #, fuzzy msgid "Set Knowledge Skill" msgstr "Conocimiento" msgid "%{race1} %{name1} vs %{race2} %{name2}" msgstr "%{race1} %{name1} vs %{race2} %{name2}" msgid "Monsters" msgstr "Monstruos" msgid "N/A" msgstr "N/D" msgid "Left Turret" msgstr "Torreón Izquierdo" msgid "Right Turret" msgstr "Torreón Derecho" msgid "The %{name} fires with the strength of %{count} Archers" msgstr "La %{name} dispara con la fuerza de %{count} Arqueros" msgid "each with a +%{attack} bonus to their attack skill." msgstr "cada uno con un bono de +%{attack} a su ataque." msgid "The %{name} is destroyed." msgstr "La %{name} es destruida." msgid "%{count} %{name} rises from the dead!" msgid_plural "%{count} %{name} rise from the dead!" msgstr[0] "¡%{count} %{name} se eleva de la muerte!" msgstr[1] "¡%{count} %{name} se elevan de la muerte!" msgid "Dwarven Alliance" msgstr "Alianza de enanos" msgid "Sorceress Guild" msgstr "Gremio de hechiceras" msgid "Necromancer Guild" msgstr "Gremio de Nigromantes" msgid "Ogre Alliance" msgstr "Alianza de ogros" msgid "Dwarfbane" msgstr "Asesina de enanos" msgid "Dragon Alliance" msgstr "Alianza Dragontina" msgid "Elven Alliance" msgstr "Alianza Élfica" msgid "Kraeger defeated" msgstr "Kraeger derrotado" msgid "Wayward Son" msgstr "Hijo Descarriado" msgid "Uncle Ivan" msgstr "Tío Iván" msgid "Annexation" msgstr "Anexión" msgid "Force of Arms" msgstr "La Fuerza de las Armas" msgid "Save the Dwarves" msgstr "Salva a los Enanos" msgid "Carator Mines" msgstr "Minas de Carator" msgid "Turning Point" msgstr "Momento Crucial" msgid "scenarioName|Defender" msgstr "" msgid "Corlagon's Defense" msgstr "Defensa de Corlagon" msgid "The Crown" msgstr "La Corona" msgid "The Gauntlet" msgstr "El Guantelete" msgid "Betrayal" msgstr "Traición" msgid "Final Justice" msgstr "Justicia Final" msgid "" "Roland needs you to defeat the lords near his castle to begin his war of " "rebellion against his brother. They are not allied with each other, so they " "will spend most of their time fighting with one another. Victory is yours " "when you have defeated all of their castles and heroes." msgstr "" msgid "" "The local lords refuse to swear allegiance to Roland, and must be subdued. " "They are wealthy and powerful, so be prepared for a tough fight. Capture all " "enemy castles to win." msgstr "" msgid "" "Your task is to defend the Dwarves against Archibald's forces. Capture all " "of the enemy towns and castles to win, and be sure not to lose all of the " "dwarf towns at once, or the enemy will have won." msgstr "" msgid "" "You will face four allied enemies in a straightforward fight for resource " "and treasure. Capture all of the enemy castles for victory." msgstr "" msgid "" "Your enemies are allied against you and start close by, so be ready to come " "out fighting. You will need to own all four castles in this small valley to " "win." msgstr "" msgid "" "The Sorceress' guild of Noraston has requested Roland's aid against an " "attack from Archibald's allies. Capture all of the enemy castles to win, and " "don't lose Noraston, or you'll lose the scenario. (Hint: There is an enemy " "castle on an island in the ocean.)" msgstr "" msgid "" "Gather as large an army as possible and capture the enemy castle within 8 " "weeks. You are opposed by only one enemy, but must travel a long way to get " "to the enemy castle. Any troops you have in your army at the end of this " "scenario will be with you in the final battle." msgstr "" msgid "" "Find the Crown before Archibald's heroes find it. Roland will need the Crown " "for the final battle against Archibald." msgstr "" msgid "" "Three allied enemies stand before you and victory, including Lord Corlagon. " "Roland is in a castle to the northwest, and you will lose if he falls to the " "enemy. Remember that capturing Lord Corlagon will ensure that he will not " "fight against you in the final scenario." msgstr "" msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Archibald to end the war!" msgstr "" msgid "" "Switching sides leaves you with three castles against the enemy's one. This " "battle will be the easiest one you will face for the rest of the war..." "traitor." msgstr "" msgid "Barbarian Wars" msgstr "Guerras Bárbaras" msgid "First Blood" msgstr "Primera Sangre" msgid "Necromancers" msgstr "Nigromantes" msgid "Slay the Dwarves" msgstr "Da muerte a los Enanos" msgid "Country Lords" msgstr "Señores del Pais" msgid "Dragon Master" msgstr "Maestro Dragón" msgid "Rebellion" msgstr "Rebelión" msgid "Apocalypse" msgstr "Apocalipsis" msgid "Greater Glory" msgstr "Gloria Mayor" msgid "" "King Archibald requires you to defeat the three enemies in this region. They " "are not allied with one another, so they will spend most of their energy " "fighting amongst themselves. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" msgid "" "You must unify the barbarian tribes of the north by conquering them. As in " "the previous mission, the enemy is not allied against you, but they have " "more resources at their disposal. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" msgid "" "Do-gooder wizards have taken the Necromancers' castle. You must retake it to " "achieve victory. Remember that while you start with a powerful army, you " "have no castle and must take one within 7 days, or lose this battle. (Hint: " "The nearest castle is to the southeast.)" msgstr "" msgid "" "The dwarves need conquering before they can interfere in King Archibald's " "plans. Roland's forces have more than one hero and many towns to start with, " "so be ready for attack from multiple directions. You must capture all of the " "enemy towns and castles to claim victory." msgstr "" msgid "" "You must put down a peasant revolt led by Roland's forces. All are allied " "against you, but you have Lord Corlagon, an experienced hero, to help you. " "Capture all enemy castles to win." msgstr "" msgid "" "There are two enemies allied against you in this mission. Both are well " "armed and seek to evict you from their island. Avoid them and capture Dragon " "City to win." msgstr "" msgid "" "Your orders are to conquer the country lords that have sworn to serve " "Roland. All of the enemy castles are unified against you. Since you start " "without a castle, you must hurry to capture one before the end of the week. " "Capture all enemy castles for victory." msgstr "" msgid "" "Find the Crown before Roland's heroes find it. Archibald will need the Crown " "for the final battle against Roland." msgstr "" msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Roland to win the war, and be sure " "not to lose Archibald in the fight!" msgstr "" msgid "Arrow's Flight" msgstr "" #, fuzzy msgid "Island of Chaos" msgstr "Isla de los Elfos" msgid "The Abyss" msgstr "" msgid "Uprising" msgstr "" msgid "Aurora Borealis" msgstr "" #, fuzzy msgid "Betrayal's End" msgstr "\"¡Traición!\"" #, fuzzy msgid "Corruption's Heart" msgstr "Alojamiento del Capitán" #, fuzzy msgid "The Giant's Pass" msgstr "Los yermos reales" msgid "" "Subdue the unruly local lords in order to provide the Empire with facilities " "to operate in this region." msgstr "" msgid "" "Eliminate all opposition in this area. Then the first piece of the artifact " "will be yours." msgstr "" msgid "" "The sorceresses to the northeast are rebelling! For the good of the empire " "you must quash their feeble uprising on your way to the mountains." msgstr "" msgid "" "Having prepared for your arrival, Kraeger has arranged for a force of " "necromancers to thwart your quest. You must capture the castle of Scabsdale " "before the first day of the third week, or the Necromancers will be too " "strong for you." msgstr "" msgid "" "The barbarian despot in this area is, as yet, ignorant of your presence. " "Quickly, build up your forces before you are discovered and attacked! Secure " "the region by subduing all enemy forces." msgstr "" msgid "" "The Empire is weak in this region. You will be unable to completely subdue " "all forces in this area, so take what you can before reprisal strikes. " "Remember, your true goal is to claim the Helmet of Anduran." msgstr "" msgid "For the good of the Empire, eliminate Kraeger." msgstr "" msgid "" "At last, you have the opportunity and the facilities to rid the Empire of " "the necromancer's evil. Eradicate them completely, and you will be sung as a " "hero for all time." msgstr "" #, fuzzy msgid "Border Towns" msgstr "Tierras fronterizas" msgid "Conquer and Unify" msgstr "" #, fuzzy msgid "Crazy Uncle Ivan" msgstr "Uncle Ivan" msgid "The Wayward Son" msgstr "El Hijo Descarriado" msgid "Ivory Gates" msgstr "Puertas de Marfil" #, fuzzy msgid "The Elven Lands" msgstr "Siete Lagos" msgid "The Epic Battle" msgstr "" #, fuzzy msgid "The Southern War" msgstr "El Otro Lado" #, fuzzy msgid "" "Conquer and unite all the enemy tribes. Don't lose the hero Jarkonas, the " "forefather of all descendants." msgstr "" "Une las Tribus. No pierdas a tu héroe principal, el primer Descendiente." msgid "" "Your rival, the Kingdom of Harondale, is attacking weak towns on your " "border! Recover from their first strike and crush them completely!" msgstr "" #, fuzzy msgid "" "Find your wayward son Joseph who is rumored to be living in the desolate " "lands. Do it before the first day of the third month or it will be of no " "help to your family." msgstr "" "Encuentra a tu caprichoso hijo, del que se rumorea que vive en las tierras " "desoladas. Házlo en menos de 8 semanas o no será de ninguna ayuda para tu " "familia." msgid "" "Rescue your crazy uncle Ivan. Find him before the first day of the fourth " "month or it will be no help to your kingdom." msgstr "" "Rescata a tu loco Tío Iván. Encuéntrale en 4 meses o no habrá ayuda para tu " "reino." #, fuzzy msgid "" "Destroy the barbarians who are attacking the southern border of your " "kingdom! Recover your fallen towns, and then invade the jungle kingdom. " "Leave no enemy standing." msgstr "" "¡Destruye a los Bárbaros que están atacando la frontera sur de tu reino! " "Recupera tus ciudades y entonces invade el reino jungla." #, fuzzy msgid "Retake the castle of Ivory Gates, which has fallen due to treachery." msgstr "Reconquista el Castillo de las Puertas de Marfil." #, fuzzy msgid "" "Gain the favor of the elves. They will not allow trees to be chopped down, " "so they will send you wood every 2 weeks. You must complete your mission " "before the first day of the seventh month, or the kingdom will surely fall." msgstr "" "Consigue la ayuda de los elfos. No permitirán que se talen árboles, así que " "te enviaremos madera cada 2 semanas. ¡Tienes 7 meses!" msgid "" "This is the final battle against your rival kingdom of Harondale. Eliminate " "everyone, and don't lose the hero Jarkonas VI." msgstr "" #, fuzzy msgid "Fount of Wizardry" msgstr "El Cayado de Brujería" #, fuzzy msgid "Power's End" msgstr "Anillo de Poder" #, fuzzy msgid "The Eternal Scrolls" msgstr "La Isla Esmeralda" #, fuzzy msgid "The Shrouded Isles" msgstr "La Isla Esmeralda" msgid "" "Your mission is to vanquish the warring mages in the magical Shrouded Isles. " "The completion of this task will give you a fighting chance against your " "rivals." msgstr "" msgid "" "The location of the great library has been discovered! You must make your " "way to it, and reclaim the city of Chronos in which it lies." msgstr "" msgid "" "Find the Orb of negation, which is said to be buried in this land. There are " "clues inscribed on stone obelisks which will help lead you to your prize. " "Find the Orb before the first day of the sixth month, or your rivals will " "surely have gotten to the fount before you." msgstr "" msgid "" "You must take control of the castle of Magic, where the fount of wizardry " "lies. Do this and your victory will be supreme." msgstr "" msgid "Blood is Thicker" msgstr "La Sangre es Espesa" msgid "King and Country" msgstr "Rey y Patria" #, fuzzy msgid "Pirate Isles" msgstr "Ensenada Pirata" msgid "Stranded" msgstr "" #, fuzzy msgid "" "Capture the town on the island off the southeast shore in order to construct " "a boat and travel back towards the mainland. Do not lose the hero Gallavant." msgstr "" "Captura la ciudad de la isla en la costa sureste para construir un barco y " "navegar de vuelta al continente." msgid "" "Find and defeat Martine, the pirate leader, who resides in Pirates Cove. Do " "not lose Gallavant or your quest will be over." msgstr "" msgid "" "Eliminate all the other forces who oppose the rule of Lord Alberon. " "Gallavant must not die." msgstr "" msgid "" "Overthrow the entrenched monarchy of Lord Alberon, and claim all the land in " "your name. Gallavant must not die." msgstr "" msgid " bane" msgstr "" msgid " alliance" msgstr " alianza" msgid "Carry-over forces" msgstr "Fuerzas mantenidas" msgid " bonus" msgstr " bonificación" msgid " defeated" msgstr " derrotado" msgid " will always run away from your army." msgstr "" msgid " will be willing to join your army." msgstr "" msgid "\"%{artifact}\" artifact will be carried over the scenario." msgstr "" msgid "The army will be carried over the scenario." msgstr "" msgid "The kingdom will have +%{count} %{resource} each day." msgstr "" msgid "\"%{spell}\" spell will be carried over the scenario." msgstr "" msgid "%{hero} can be hired in the scenario." msgstr "" msgid "" "%{hero} has been defeated and will not appear in the subsequent scenarios." msgstr "" msgid "The dwarves recognize their allies and gladly join your forces." msgstr "" msgid "The ogres recognize you as the Dwarfbane and lumber over to join you." msgstr "" msgid "" "The dragons, snarling and growling, agree to join forces with you, their " "'Ally'." msgstr "" msgid "" "As you approach the group of elves, their leader calls them all to " "attention. He shouts to them, \"Who of you is brave enough to join this " "fearless ally of ours?\" The group explodes with cheers as they run to join " "your ranks." msgstr "" msgid "" "The dwarves hail you, \"Any friend of Roland is a friend of ours. You may " "pass.\"" msgstr "" msgid "" "The ogres give you a grunt of recognition, \"Archibald's allies may pass.\"" msgstr "" msgid "" "The dragons see you and call out. \"Our alliance with Archibald compels us " "to join you. Unfortunately you have no room. A pity!\" They quickly scatter." msgstr "" msgid "" "The elves stand at attention as you approach. Their leader calls to you and " "says, \"Let us not impede your progress, ally! Move on, and may victory be " "yours.\"" msgstr "" msgid "\"The Dwarfbane!!!!, run for your lives.\"" msgstr "" msgid "campaignBonus|Animate Dead" msgstr "Animar Muertos" msgid "campaignBonus|Chain Lightning" msgstr "Cadena de Rayos" msgid "campaignBonus|Fireblast" msgstr "" msgid "campaignBonus|Mass Curse" msgstr "" msgid "campaignBonus|Mass Haste" msgstr "" msgid "campaignBonus|Mirror Image" msgstr "Doble Imagen" msgid "campaignBonus|Resurrect" msgstr "" #, fuzzy msgid "campaignBonus|Steelskin" msgstr "Anillo del Mago del Poder" #, fuzzy msgid "campaignBonus|Summon Earth" msgstr "LLamar Barco" msgid "campaignBonus|View Heroes" msgstr "Ver Heroes" msgid "campaignBonus|Ballista" msgstr "" msgid "campaignBonus|Black Pearl" msgstr "Perla Negra" #, fuzzy msgid "campaignBonus|Caster's Bracelet" msgstr "Brazalete del Lanzador de la Magia" #, fuzzy msgid "campaignBonus|Defender Helm" msgstr "Defensor" msgid "campaignBonus|Breastplate" msgstr "Peto de Anduran" #, fuzzy msgid "campaignBonus|Dragon Sword" msgstr "Matadragones" msgid "campaignBonus|Fizbin Medal" msgstr "" #, fuzzy msgid "campaignBonus|Foremost Scroll" msgstr "Principal Pergamino de Conocimiento" msgid "campaignBonus|Gauntlets" msgstr "Guanteletes" msgid "campaignBonus|Hideous Mask" msgstr "Máscara Horrenda" msgid "campaignBonus|Mage's Ring" msgstr "Anillo del Mago del Poder" #, fuzzy msgid "campaignBonus|Major Scroll" msgstr "Rollo Mayor del Conocimiento" msgid "campaignBonus|Medal of Honor" msgstr "La Medalla de Honor" msgid "campaignBonus|Medal of Valor" msgstr "La Medalla de Valor" msgid "campaignBonus|Minor Scroll" msgstr "Pergamino Menor de Conocimiento" #, fuzzy msgid "campaignBonus|Nomad Boots" msgstr "Nómadas" msgid "campaignBonus|Power Axe" msgstr "" msgid "campaignBonus|Spiked Shield" msgstr "Escudo Picudo" #, fuzzy msgid "campaignBonus|Stealth Shield" msgstr "El Escudo Supremo" msgid "campaignBonus|Tax Lien" msgstr "" msgid "campaignBonus|Thunder Mace" msgstr "Maza del Trueno de Dominio" msgid "campaignBonus|Traveler's Boots" msgstr "Botas de Viajero de la Movilidad" msgid "campaignBonus|White Pearl" msgstr "Perla Blanca" #, fuzzy msgid "campaignBonus|Basic Archery" msgstr "Perla Negra" msgid "campaignBonus|Advanced Archery" msgstr "Tiro con Arco Avanzado" #, fuzzy msgid "campaignBonus|Expert Archery" msgstr "Maza del Trueno de Dominio" #, fuzzy msgid "campaignBonus|Basic Ballistics" msgstr "Perla Negra" msgid "campaignBonus|Advanced Ballistics" msgstr "Balística Avanzada" msgid "campaignBonus|Expert Ballistics" msgstr "Balística Experta" #, fuzzy msgid "campaignBonus|Basic Diplomacy" msgstr "Peto de Anduran" msgid "campaignBonus|Advanced Diplomacy" msgstr "Diplomacia Avanzada" msgid "campaignBonus|Expert Diplomacy" msgstr "Diplomacia Experta" #, fuzzy msgid "campaignBonus|Basic Eagle Eye" msgstr "Perla Negra" msgid "campaignBonus|Advanced Eagle Eye" msgstr "Ojo de águila avanzado" msgid "campaignBonus|Expert Eagle Eye" msgstr "Ojo de águila experto" #, fuzzy msgid "campaignBonus|Basic Estates" msgstr "Peto de Anduran" #, fuzzy msgid "campaignBonus|Advanced Estates" msgstr "Peto de Anduran" #, fuzzy msgid "campaignBonus|Expert Estates" msgstr "Peto de Anduran" #, fuzzy msgid "campaignBonus|Basic Leadership" msgstr "Perla Negra" msgid "campaignBonus|Advanced Leadership" msgstr "Liderazgo Avanzado" msgid "campaignBonus|Expert Leadership" msgstr "Liderazgo Experto" #, fuzzy msgid "campaignBonus|Basic Logistics" msgstr "Cadena de Rayos" msgid "campaignBonus|Advanced Logistics" msgstr "Logística Avanzada" msgid "campaignBonus|Expert Logistics" msgstr "Logística Experta" #, fuzzy msgid "campaignBonus|Basic Luck" msgstr "Perla Negra" #, fuzzy msgid "campaignBonus|Advanced Luck" msgstr "Animar Muertos" #, fuzzy msgid "campaignBonus|Expert Luck" msgstr "Maza del Trueno de Dominio" msgid "campaignBonus|Basic Mysticism" msgstr "Misticismo Básico" msgid "campaignBonus|Advanced Mysticism" msgstr "Misticismo Avanzado" msgid "campaignBonus|Expert Mysticism" msgstr "Misticismo Experto" #, fuzzy msgid "campaignBonus|Basic Navigation" msgstr "Cadena de Rayos" msgid "campaignBonus|Advanced Navigation" msgstr "Navegación Avanzada" msgid "campaignBonus|Expert Navigation" msgstr "Navegación Experta" #, fuzzy msgid "campaignBonus|Basic Necromancy" msgstr "Perla Negra" msgid "campaignBonus|Advanced Necromancy" msgstr "Necromancia Avanzada" msgid "campaignBonus|Expert Necromancy" msgstr "Necromancia Experta" #, fuzzy msgid "campaignBonus|Basic Pathfinding" msgstr "Cadena de Rayos" #, fuzzy msgid "campaignBonus|Advanced Pathfinding" msgstr "Cadena de Rayos" #, fuzzy msgid "campaignBonus|Expert Pathfinding" msgstr "Escudo Picudo" #, fuzzy msgid "campaignBonus|Basic Scouting" msgstr "Anillo del Mago del Poder" #, fuzzy msgid "campaignBonus|Advanced Scouting" msgstr "Anillo del Mago del Poder" #, fuzzy msgid "campaignBonus|Expert Scouting" msgstr "Anillo del Mago del Poder" #, fuzzy msgid "campaignBonus|Basic Wisdom" msgstr "Perla Negra" #, fuzzy msgid "campaignBonus|Advanced Wisdom" msgstr "Animar Muertos" #, fuzzy msgid "campaignBonus|Expert Wisdom" msgstr "Escudo Picudo" msgid "" "The main hero will have \"%{artifact}\" artifact at the start of the " "scenario." msgstr "" msgid "" "The kingdom will receive %{amount} additional %{resource} at the start of " "the scenario." msgstr "" msgid "" "The kingdom will have %{amount} less %{resource} at the start of the " "scenario." msgstr "" msgid "" "The main hero will have %{count} %{monster} at the start of the scenario." msgstr "" msgid "" "The main hero will have \"%{spell}\" spell at the start of the scenario." msgstr "" msgid "The starting race of the scenario will be %{race}." msgstr "" msgid "" "The main hero will have additional %{count} %{skill} at the start of the " "scenario." msgstr "" msgid "The main hero will have %{skill} at the start of the scenario." msgstr "" msgid "Roland" msgstr "Roland" msgid "Archibald" msgstr "Archibald" #, fuzzy msgid "Price of Loyalty" msgstr "El Precio de la Lealtad" msgid "Voyage Home" msgstr "Viaje a Casa" #, fuzzy msgid "Wizard's Isle" msgstr "Sombrero de Mago" #, fuzzy msgid "Descendants" msgstr "Campesinos" msgid "The %{building} produces %{monster}." msgstr "Produce: %{monster}." msgid "Requires:" msgstr "Requiere:" msgid "Exit this menu without doing anything." msgstr "Cerrar este menú sin hacer cambios." msgid "Cannot build. You have already built here today." msgstr "No se puede construir. Ya se construyo en este turno." msgid "For this action it is necessary to build a castle first." msgstr "Para realizar esa acción primero es necesario construir un castillo." msgid "Cannot build %{name} because the castle is too far away from an ocean." msgstr "" "No se puede construir %{name} porque el castillo está demasiado lejos del " "agua." msgid "disable build." msgstr "" #, fuzzy msgid "Cannot afford %{name}." msgstr "No se puede pagar el %{name}" msgid "%{name} is already built." msgstr "El %{name} ya está construido." msgid "Cannot build %{name}." msgstr "No se puede construir %{name}." msgid "Build %{name}." msgstr "Construir %{name}." #, fuzzy msgid "Blackridge" msgstr "Puente" msgid "Hillstone" msgstr "" msgid "Pinehurst" msgstr "" #, fuzzy msgid "Whiteshield" msgstr "Perla Blanca" #, fuzzy msgid "Woodhaven" msgstr "Madera" #, fuzzy msgid "Blackwind" msgstr "Herrero" #, fuzzy msgid "Bloodreign" msgstr "Buena Moral" #, fuzzy msgid "Dragontooth" msgstr "Dragón rojo" msgid "Greywind" msgstr "" msgid "Portsmith" msgstr "" msgid "Atlantium" msgstr "" msgid "Middle Gate" msgstr "" msgid "Sansobar" msgstr "" #, fuzzy msgid "Tundara" msgstr "Thundax" #, fuzzy msgid "Vulcania" msgstr "Volcán" msgid "Baywatch" msgstr "" #, fuzzy msgid "Fountainhead" msgstr "Fuente" msgid "Vertigo" msgstr "" msgid "Wildabar" msgstr "" msgid "Winterkill" msgstr "" msgid "Brindamoor" msgstr "" msgid "Lakeside" msgstr "" msgid "Nightshadow" msgstr "" msgid "Olympus" msgstr "" #, fuzzy msgid "Sandcaster" msgstr "Sandro" msgid "Alamar" msgstr "Alamar" #, fuzzy msgid "Burlock" msgstr "Brujo" #, fuzzy msgid "Dragadune" msgstr "Dragón rojo" msgid "Kalindra" msgstr "Kalindra" msgid "Xabran" msgstr "" msgid "Algary" msgstr "" msgid "Basenji" msgstr "" #, fuzzy msgid "Blackfang" msgstr "Dragón Negro" #, fuzzy msgid "New Dawn" msgstr "Dawn" msgid "Sorpigal" msgstr "" msgid "Avone" msgstr "" msgid "Big Oak" msgstr "" msgid "Chandler" msgstr "" msgid "Erliquin" msgstr "" msgid "Hampshire" msgstr "" #, fuzzy msgid "Antioch" msgstr "\"Anexión\"" #, fuzzy msgid "Avalon" msgstr "Halon" msgid "Roc Haven" msgstr "" msgid "South Mill" msgstr "" #, fuzzy msgid "Weed Patch" msgstr "Reloj de Oro" #, fuzzy msgid "Brownston" msgstr "Marrón" #, fuzzy msgid "Hilltop" msgstr "Fuerte de la Colina" msgid "Weddington" msgstr "" msgid "Westfork" msgstr "" #, fuzzy msgid "Whittingham" msgstr "Nivel" msgid "Cathcart" msgstr "" msgid "Elk's Head" msgstr "" msgid "Roscomon" msgstr "" #, fuzzy msgid "Sherman" msgstr "Sherman" msgid "Yorksford" msgstr "" msgid "Blackburn" msgstr "" msgid "Blacksford" msgstr "" msgid "Burton" msgstr "" msgid "Pig's Eye" msgstr "" msgid "Viper's Nest" msgstr "" msgid "Fenton" msgstr "" msgid "Lankershire" msgstr "" msgid "Lombard" msgstr "" msgid "Timberhill" msgstr "" #, fuzzy msgid "Troy" msgstr "Troyan" #, fuzzy msgid "Forder Oaks" msgstr "Tierras fronterizas" msgid "Meramec" msgstr "" msgid "Quick Silver" msgstr "" msgid "Westmoor" msgstr "" msgid "Willow" msgstr "" msgid "Corackston" msgstr "" msgid "Sheltemburg" msgstr "" msgid "Cannot recruit - you already have a Hero in this town." msgstr "No puedes reclutar, ya tienes un Héroe en esta ciudad." msgid "Cannot recruit - you have too many Heroes." msgstr "No puedes reclutar, tienes demasiados Héroes." msgid "Cannot afford a Hero" msgstr "No te puedes permitir un Héroe" msgid "There is no room in the garrison for this army." msgstr "" msgid "Fortifications" msgstr "Fortificación" msgid "Farm" msgstr "Granja" msgid "Thatched Hut" msgstr "Cobertizo" msgid "Archery Range" msgstr "Campo de Tiro" msgid "Upg. Archery Range" msgstr "Mej. Campo de Tiro" msgid "Blacksmith" msgstr "Herrero" msgid "Upg. Blacksmith" msgstr "Herrería Mejorada" msgid "Armory" msgstr "Armería" msgid "Upg. Armory" msgstr "Armería Mejorada" msgid "Jousting Arena" msgstr "Campo de Justa" msgid "Upg. Jousting Arena" msgstr "Mej. Campo de Justa" msgid "Cathedral" msgstr "Catedral" msgid "Upg. Cathedral" msgstr "Catedral Mejorada" msgid "Coliseum" msgstr "Coliseo" msgid "Garbage Heap" msgstr "Vertedero" msgid "Hut" msgstr "Choza" msgid "Stick Hut" msgstr "Chamizo" msgid "Upg. Stick Hut" msgstr "Mej. Chamizo" msgid "Den" msgstr "Lobera" msgid "Adobe" msgstr "Adobe" msgid "Upg. Adobe" msgstr "Adobe Mejorado" msgid "Bridge" msgstr "Puente" msgid "Upg. Bridge" msgstr "Puente Mejorado" msgid "Pyramid" msgstr "Pirámide" msgid "Rainbow" msgstr "Arcoíris" msgid "Crystal Garden" msgstr "Jardín de Cristales" msgid "Treehouse" msgstr "Casa en los Ãrboles" msgid "Cottage" msgstr "Casa de Campo" msgid "Upg. Cottage" msgstr "Mej. Casa de Campo" msgid "Stonehenge" msgstr "Stonehenge" msgid "Upg. Stonehenge" msgstr "Mej. Mejorado" msgid "Fenced Meadow" msgstr "Prado Vallado" msgid "sorceress|Red Tower" msgstr "Torre Roja" msgid "Dungeon" msgstr "Mazmorra" msgid "Waterfall" msgstr "Cascada" msgid "Cave" msgstr "Cueva" msgid "Crypt" msgstr "Cripta" msgid "Nest" msgstr "Nido" msgid "Maze" msgstr "Laberinto" msgid "Upg. Maze" msgstr "Laberinto Mejorado" msgid "Swamp" msgstr "Ciénaga" msgid "Green Tower" msgstr "Torre Verde" msgid "warlock|Red Tower" msgstr "Torre Roja" msgid "Black Tower" msgstr "Torre Negra" msgid "Library" msgstr "Biblioteca" msgid "Orchard" msgstr "Huerta" msgid "Habitat" msgstr "Hábitat" msgid "Pen" msgstr "Corral" msgid "Foundry" msgstr "Fundición" msgid "Upg. Foundry" msgstr "Fundición Mejorada" msgid "Cliff Nest" msgstr "Nido del acantilado" msgid "Ivory Tower" msgstr "Torre de Marfil" msgid "Upg. Ivory Tower" msgstr "Mej. Torre de Marfil" msgid "Cloud Castle" msgstr "Castillo de las Nubes" msgid "Upg. Cloud Castle" msgstr "Mej. Castillo Nubes" msgid "Storm" msgstr "Tormenta" msgid "Skull Pile" msgstr "Pila de Calaveras" msgid "Excavation" msgstr "Excavación" msgid "Graveyard" msgstr "Cementerio" msgid "Upg. Graveyard" msgstr "Cementerio Mejorado" msgid "Upg. Pyramid" msgstr "Pirámide Mejorada" msgid "Mansion" msgstr "Mansión" msgid "Upg. Mansion" msgstr "Mansión Mejorada" msgid "Mausoleum" msgstr "Mausoleo" msgid "Upg. Mausoleum" msgstr "Mausoleo Mejorado" msgid "Laboratory" msgstr "Laboratorio" msgid "Shrine" msgstr "Santuario" msgid "" "The Fortifications increase the toughness of the walls, increasing the " "number of turns it takes to knock them down." msgstr "" "La Fortificación incrementa la dureza de los muros, incrementa el número de " "turnos que se tarda en derribar el muro." msgid "The Farm increases production of Peasants by %{count} per week." msgstr "" "La Granja incrementa la producción de Campesinos en %{count} por semana." msgid "" "The Coliseum provides inspiring spectacles to defending troops, raising " "their morale by two during combat." msgstr "" "El Coliseo provee de inspiradores espectaculos a las tropas defensoras, " "aumentando la moral en 2 durante el combate." msgid "The Garbage Heap increases production of Goblins by %{count} per week." msgstr "" "El Montón de Basura incrementa la producción de Globlins en %{count} por " "semana." msgid "The Rainbow increases the luck of the defending units by two." msgstr "El Arcoíris incrementa la suerte de las unidades defensoras en 2." msgid "" "The Crystal Garden increases production of Sprites by %{count} per week." msgstr "" "El Jardín de Cristales incrementa la producción de Hadas en %{count} por " "semana." msgid "The Dungeon increases the income of the town by %{count} gold per day." msgstr "La Mazmorra incrementa los ingresos de la ciudad en %{count} por día." msgid "The Waterfall increases production of Centaurs by %{count} per week." msgstr "" "La Cascada incrementa la producción de Centauros en %{count} por semana." msgid "" "The Library increases the number of spells in the Guild by one for each " "level of the guild." msgstr "" "La Biblioteca proporciona un hechizo extra en cada nivel de la Torre Mágica." msgid "The Orchard increases production of Halflings by %{count} per week." msgstr "El Huerto incrementa la producción de Medianos en %{count} por semana." msgid "The Storm adds +2 to the power of spells of a defending spell caster." msgstr "" "La Tormenta añade +2 al poder de hechizo del lanzador de hechizos defensor." msgid "The Skull Pile increases production of Skeletons by %{count} per week." msgstr "" "La Pila de Calaberas incrementa la producción de Esqueletos en %{count} por " "semana." msgid "Thieves' Guild" msgstr "Gremio de Ladrones" msgid "Tavern" msgstr "Taberna" msgid "Shipyard" msgstr "Astillero" msgid "Well" msgstr "Pozo" msgid "Statue" msgstr "Estatua" msgid "Marketplace" msgstr "Mercado" msgid "Moat" msgstr "Foso" msgid "Castle" msgstr "Castillo" msgid "Tent" msgstr "Tienda" msgid "Captain's Quarters" msgstr "Alojamiento del Capitán" msgid "Mage Guild, Level 1" msgstr "Torre Mágica, Nivel 1" msgid "Mage Guild, Level 2" msgstr "Torre Mágica, Nivel 2" msgid "Mage Guild, Level 3" msgstr "Torre Mágica, Nivel 3" msgid "Mage Guild, Level 4" msgstr "Torre Mágica, Nivel 4" msgid "Mage Guild, Level 5" msgstr "Torre Mágica, Nivel 5" msgid "" "The Shrine increases the necromancy skill of all your necromancers by 10 " "percent." msgstr "El Santuario incrementa la habilidad de nigromancia." msgid "" "The Thieves' Guild provides information on enemy players. Thieves' Guilds " "can also provide scouting information on enemy towns. Additional Guilds " "provide more information." msgstr "" "El Gremio de los Ladrones ofrece información sobre los héroes enemigos y sus " "cuidades. Los Gremios de los Ladrones también pueden dar información sobre " "las ciudades enemigas. Los gremios adicionales proporcionan más información." msgid "The Tavern increases morale for troops defending the castle." msgstr "" "La Taberna incrementa la moral de las tropas miestras defiendes el castillo." msgid "The Shipyard allows ships to be built." msgstr "El Astillero permite construir barcos." msgid "" "The Well increases the growth rate of all dwellings by %{count} creatures " "per week." msgstr "" "El Pozo incrementa el ritmo de crecimiento de todas las moradas en %{count} " "criaturas por semana." msgid "The Statue increases the town's income by %{count} gold per day." msgstr "" "La Estatua incrementa los ingresos de tus ciudades en %{count} piezas de oro " "por día." msgid "The Left Turret provides extra firepower during castle combat." msgstr "" "El Torreón Izquierdo proporciona un disparo extra durante el asalto al " "castillo." msgid "The Right Turret provides extra firepower during castle combat." msgstr "" "El Torreón Derecho proporciona un disparo extra durante el asalto al " "castillo." msgid "" "The Marketplace can be used to convert one type of resource into another. " "The more marketplaces you control, the better the exchange rate." msgstr "" "El Mercado puede ser usado para convertir uno de los tipos de recursos en " "otro. Cuantos más mercados controles, mejor será el precio de intercambio." msgid "" "The Moat slows attacking units. Any unit entering the moat must end its turn " "there and becomes more vulnerable to attack." msgstr "" "El Foso ralentiza a las unidades atacantes. Cualquier unidad que entre en el " "foso terminará su turno y será más vulnerable a los ataques." msgid "" "The Castle improves the town's defense and increases its income to %{count} " "gold per day." msgstr "" "El Castillo mejora las defensas de la ciudad e incrementa los ingresos en " "%{count} piezas de oro al día." msgid "" "The Tent provides workers to build a castle, provided the materials and the " "gold are available." msgstr "" "La Tienda provee de trabajadores para construir un castillo, con la " "condición de que estén disponibles los materiales y el oro." msgid "" "The Captain's Quarters provides a captain to assist in the castle's defense " "when no hero is present." msgstr "" "El Alojamiento del Capitán provee un capitán para que asista en la defensa " "del castillo cuando no hay un héroe presente." msgid "" "The Mage Guild allows heroes to learn spells and replenish their spell " "points." msgstr "" "La Torre Mágica permite aprender hechizos y restablecer los puntos de Maná a " "los héroes visitantes." msgid "Recruit %{name}" msgstr "Recluta %{name}" msgid "Month: %{month}, Week: %{week}, Day: %{day}" msgstr "Mes: %{month}, Semana: %{week}, Día: %{day}" msgid "" "You must purchase a spell book to use the mage guild, but you currently have " "no room for a spell book. Try giving one of your artifacts to another hero." msgstr "" "Debes compra un libro de hechizos para usar el gremio de magos, pero " "actualmente no tienes espacio para un libro de hechizos. Prueba dando uno de " "tus artefactos a otro héroe." msgid "Click to show next town." msgstr "Haga clic para ver la siguiente ciudad." msgid "Show next town" msgstr "Ver ciudad siguiente" msgid "Click to show previous town." msgstr "Haga clic para ver la ciudad anterior." msgid "Show previous town" msgstr "Ver ciudad anterior" msgid "This town may not be upgraded to a castle." msgstr "Este pueblo no se puede actualizar a castillo." msgid "Town" msgstr "Pueblo" msgid "Exit Castle" msgstr "Salir del castillo" msgid "Exit Town" msgstr "Salir de la ciudad" msgid "Show Income" msgstr "Mostrar Ingresos" msgid "View Hero" msgstr "Ver Heroe" msgid "The above spells are available here." msgstr "Los hechizos anteriores están disponibles aquí." msgid "The spells the hero can learn have been added to their book." msgstr "Los hechizos que el héroe puede aprender se han añadido a su libro." msgid "A generous tip for the barkeep yields the following rumor:" msgstr "Una generosa propina para el camarero te cede el siguiente rumor:" msgid "Recruit Hero" msgstr "Reclutar Héroe" msgid "%{name} is a level %{value} %{race} " msgstr "%{name} es un nivel %{value} %{race} " msgid "with %{count} artifacts." msgstr "con %{count} artefactos." msgid "with 1 artifact." msgstr "con un artefacto." msgid "without artifacts." msgstr "sin artefactos." msgid "Recruit %{name} the %{race}" msgstr "Reclutar a %{name} el %{race}" msgid "" "'Spread' combat formation spreads the castle's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "La formación 'Desplegada' de combate disemina las tropas del castillo desde " "la parte superior a la inferior del campo de batalla con al menos un espacio " "vacío entre las tropas." msgid "" "'Grouped' combat formation bunches the castle's units together in the center " "of the castle's side of the battlefield." msgstr "" "La formación 'Agrupada' de combate aglutina las trupas del castillo en el " "centro del su lado del campo de batalla." msgid "Spread Formation" msgstr "Formación Desplegada" msgid "Grouped Formation" msgstr "Formación Agrupada" msgid "Set garrison combat formation to 'Spread'" msgstr "Ordena a la guarnición formación Desplegada de combate." msgid "Set garrison combat formation to 'Grouped'" msgstr "Ordena a la guarnición formación Agrupada de combate." msgid "Exit Castle Options" msgstr "Salir de las Opciones del Castillo" msgid "Castle Options" msgstr "Opciones del Castillo" msgid "Not enough resources to recruit creatures." msgstr "" msgid "You are unable to recruit at this time, your ranks are full." msgstr "Eres incapaz de reclutar ahora mismo, tus filas están completas." msgid "No creatures available for purchase." msgstr "" msgid "Recruit Creatures" msgstr "Reclutar Criaturas" msgid "Max" msgstr "Máx" msgid "Hire all creatures in the town." msgstr "Recluta a todas las criaturas de la ciudad." msgid "Available" msgstr "Disponible" msgid "Town Population Information and Statistics" msgstr "Población y estadísticas de la ciudad" msgid "Damage" msgstr "Daño" msgid "HP" msgstr "PV" msgid "Growth" msgstr "Crecimiento" msgid "week" msgstr "semana" msgid "View World" msgstr "Ver Todo" msgid "View the entire world." msgstr "Ver el mundo entero." msgid "Puzzle" msgstr "Rompecabezas" msgid "View the obelisk puzzle." msgstr "Ver el puzzle del Obelisco." msgid "Scenario Information" msgstr "Información del escenario" msgid "View information on the scenario you are currently playing." msgstr "Ver información del escenario que estás jugando actualmente." msgid "Dig for the Ultimate Artifact." msgstr "Cavar buscando el Artefacto Definitivo." msgid "Digging" msgstr "Excavando" msgid "Arena" msgstr "Arena" msgid "" "You enter the arena and face a pack of vicious lions. You handily defeat " "them, to the wild cheers of the crowd. Impressed by your skill, the aged " "trainer of gladiators agrees to train you in a skill of your choice." msgstr "" "Entras a la arena y te enfrentas a un grupo de fieros leones. Los derrotas " "sin problemas mientras la muchedumbre te vitorea. Impresionado, el veterano " "entrenador de gladiadores acepta adiestrarte en la habilidad que prefieras." msgid "Attack Skill" msgstr "Ataque" msgid "Defense Skill" msgstr "Defensa" msgid "Shots" msgstr "Disparos" msgid "Shots Left" msgstr "Disparos restantes" msgid "Hit Points" msgstr "Pts. de Vida" msgid "Hit Points Left" msgstr "Vida Restante" msgid "You can't afford to upgrade your troops!" msgstr "¡No puedes permitirte mejorar tus tropas!" msgid "" "Your troops can be upgraded, but it will cost you dearly. Do you wish to " "upgrade them?" msgstr "" "Tus tropas pueden ser mejoradas, pero te saldrá caro. ¿Quieres actualizarlas?" msgid "Are you sure you want to dismiss this army?" msgstr "¿Estás seguro de que quieres despedir a este ejército?" msgid "Upgrade" msgstr "Mejorar" msgid "Upgrade your troops." msgstr "Mejorar tus tropas." msgid "Dismiss" msgstr "Despedir" msgid "Dismiss this army." msgstr "Despedir a este ejército." msgid "" "A group of %{monster} with a desire for greater glory wish to join you.\n" "Do you accept?" msgstr "" "Un grupo de %{monster} con ansia de gloria desea unirse a tus tropas.\n" "¿Aceptas?" msgid "Followers" msgstr "Seguidores" msgid "" "The %{monster} is swayed by your diplomatic tongue, and offers to join your " "army for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "El %{monster} es dominado por tu diplomático lenguaje, y te ofrece unirse a " "tu ejercito por la suma de %{gold} piezas de oro.\n" "¿Aceptas?" msgid "" "The creatures are swayed by your diplomatic\n" "tongue, and make you an offer:\n" "\n" msgstr "" "Las criaturas, influidas por tu diplomacia,\n" "te hacen una oferta:\n" "\n" msgid "" "%{offer} of the %{total} %{monster} will join your army, and the rest will " "leave you alone, for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "%{offer} de los %{total} %{monster} se unirán a tu ejercito y el resto te " "dejará continuar, por la módica cantidad de %{gold} piezas de oro.\n" "¿Aceptas?" msgid "" "All %{offer} of the %{monster} will join your army for the sum of %{gold} " "gold.\n" "Do you accept?" msgstr "" "Todos los %{monster} (%{offer}) se unirán a tu ejercito por la cantidad de " "%{gold} piezas de oro.\n" "¿Acepta?" msgid "(Rate: %{percent})" msgstr "" msgid "off" msgstr "apagado" msgid "Music" msgstr "Música" msgid "Effects" msgstr "Efectos" msgid "MIDI" msgstr "MIDIs" msgid "MIDI Expansion" msgstr "MIDI Expansión" msgid "External" msgstr "Externa" msgid "Music Type" msgstr "Tipo de Música" msgid "3D Audio" msgstr "Sonido 3D" msgid "Toggle ambient music level." msgstr "Cambia el volumen de la música ambiental." msgid "Toggle foreground sounds level." msgstr "Cambia el volumen de los sonidos de primer plano." msgid "Change the type of music." msgstr "Cambiar el tipo de música." msgid "Toggle 3D effects of foreground sounds." msgstr "Alternar efectos 3D de sonidos en primer plano." msgid "Build a new ship:" msgstr "Construir un nuevo barco:" msgid "Resource cost:" msgstr "Coste del recurso:" msgid "Total: " msgstr "Total: " msgid "Need: " msgstr "Necesita: " msgid "Load Game" msgstr "Cargar Partida" msgid "No save files to load." msgstr "No hay partidas para cargar." msgid "New Game" msgstr "Nueva Partida" msgid "Start a single or multi-player game." msgstr "Comenzar una partida simple o multijugador." msgid "Load a previously saved game." msgstr "Cargar una partida previamente guardada." msgid "Save Game" msgstr "Guardar Partida" msgid "Save the current game." msgstr "Guardar la partida en curso." msgid "Quit" msgstr "Salir" msgid "Quit out of Heroes of Might and Magic II." msgstr "Salir de Heroes of Might and Magic II." msgid "Language" msgstr "Idioma" msgid "Graphics" msgstr "Gráficos" msgid "Black & White" msgstr "Blanco y Negro" msgid "Mouse Cursor" msgstr "Cursor Ratón" msgid "Color" msgstr "Color" msgid "Text Support" msgstr "Soporte de Texto" msgid "Change the language of the game." msgstr "Cambiar el idioma del juego." msgid "Select Game Language" msgstr "Seleccionar Idioma del Juego" msgid "Change the graphics settings of the game." msgstr "Cambiar los ajustes gráficos del juego." #, fuzzy msgid "Toggle colored cursor on or off. This is only an aesthetic choice." msgstr "Activa o desactiva el cursor de color. Elección estética." msgid "" "Toggle text support mode to output extra information about windows and " "events in the game." msgstr "" "Alternar el modo de soporte de texto para generar información adicional " "sobre ventanas y eventos en el juego." msgid "" "Map\n" "Difficulty" msgstr "" "Dificultad\n" "del Mapa" msgid "" "Game\n" "Difficulty" msgstr "" "Dificultad\n" "del Juego" msgid "Rating" msgstr "Nivel" msgid "Map Size" msgstr "" "Tamaño\n" "del Mapa" msgid "Opponents" msgstr "Contrincantes" msgid "Class" msgstr "Raza" msgid "" "Victory\n" "Conditions" msgstr "" "Condiciones\n" "de Victoria" msgid "" "Loss\n" "Conditions" msgstr "" "Condiciones\n" "de Derrota" msgid "First select recipients!" msgstr "¡Selecciona primero los destinatarios!" msgid "You cannot select %{resource}!" msgstr "¡No puedes seleccionar %{resource}!" msgid "Select count %{resource}:" msgstr "Seleccionar cantidad de %{resource}:" msgid "Select Recipients" msgstr "Selecciona Destinatarios" msgid "Your Funds" msgstr "Tus Fondos" msgid "Planned Gift" msgstr "Tributo Planeado" msgid "Gift from %{name}" msgstr "Regalo de %{name}" msgid "Resolution" msgstr "Resolución" msgid "Fullscreen" msgstr "Pantalla Completa" msgid "window|Mode" msgstr "Modo" msgid "Windowed" msgstr "Ventana" msgid "V-Sync" msgstr "V-Sync" msgid "on" msgstr "activado" msgid "FPS" msgstr "FPS" msgid "System Info" msgstr "Información del Sistema" msgid "Change the resolution of the game." msgstr "Cambiar la resolución del juego." msgid "Select Game Resolution" msgstr "Seleccionar Resolución del Juego" msgid "Toggle between fullscreen and windowed modes." msgstr "Alternar entre pantalla completa y modos de ventana." msgid "" "The V-Sync option can be enabled to resolve flickering issues on some " "monitors." msgstr "" msgid "Show extra information such as FPS and current time." msgstr "Muestra información adicional como FPS y la hora actual." msgid "Hotkey: " msgstr "" msgid "Category: " msgstr "" msgid "Event: " msgstr "" msgid "Hot Keys:" msgstr "Atajos de Teclado:" msgid "Evil" msgstr "Mal" msgid "Good" msgstr "Bien" msgid "Interface Type" msgstr "Tipo de interfaz" msgid "Hide" msgstr "" msgid "Show" msgstr "" msgid "Interface" msgstr "Interfaz" msgid "Slow" msgstr "Lento" msgid "Normal" msgstr "Normal" msgid "Fast" msgstr "" msgid "Very Fast" msgstr "Muy rápido" msgid "Scroll Speed" msgstr "Velocidad de desplazamiento" msgid "Toggle the type of interface you want to use." msgstr "" msgid "Toggle interface visibility." msgstr "" msgid "Toggle colored cursor on or off. This is only an esthetic choice." msgstr "Activa o desactiva el cursor de color. Elección estética." msgid "Sets the speed at which you scroll the window." msgstr "" msgid "Select Game Language:" msgstr "Seleccionar Idioma del Juego:" msgid "Click to choose the selected language." msgstr "Haz clic para seleccionar el idioma." msgid "%{name} has gained a level." msgstr "%{name} ha ganado un nivel." msgid "%{skill} +1" msgstr "%{skill} +1" msgid "You have learned %{skill}." msgstr "Has aprendido %{skill}." msgid "" "%{name} has gained a level.\n" "\n" "%{skill} +1" msgstr "" "%{name} ha ganado un nivel.\n" "\n" "%{skill} +1" msgid "" "You may learn either:\n" "%{skill1}\n" "or\n" "%{skill2}" msgstr "" "Puedes aprender:\n" "%{skill1}\n" "o\n" "%{skill2}" msgid "" "Please inspect our fine wares. If you feel like offering a trade, click on " "the items you wish to trade with and for." msgstr "" "Por favor ojea nuestros magníficos puestos. Si te apetece comerciar, haz " "click en los artículos que deseas cambiar y obtener." msgid "" "You have received quite a bargain. I expect to make no profit on the deal. " "Can I interest you in any of my other wares?" msgstr "" "Te llevas una ganga, creo que no ganaré nada con el trato. ¿Hay algo que te " "pueda ofrecer de cualquiera de mis otros puestos ?" msgid "I can offer you %{count} for 1 unit of %{resfrom}." msgstr "Te puedo ofrecer %{count} por 1 unidad de %{resfrom}." msgid "I can offer you 1 unit of %{resto} for %{count} units of %{resfrom}." msgstr "" "Te puedo ofrecer 1 unidad de %{resto} por %{count} unidades de %{resfrom}." msgid "Min" msgstr "Mín" msgid "Qty to trade" msgstr "Cantitades" msgid "Trading Post" msgstr "Mercadillo" msgid "Your Resources" msgstr "Tus recursos" msgid "Available Trades" msgstr "Tratos Disponibles" msgid "n/a" msgstr "n/d" msgid "guarded by %{count} %{monster}" msgstr "protegido por %{count} %{monster}" msgid "guarded by " msgstr "protegido por " msgid "(available: %{count})" msgstr "(disponibles: %{count})" msgid "(empty)" msgstr "(vacío)" msgid "already learned" msgstr "ya se ha aprendido" msgid "treeOfKnowledge|free" msgstr "gratis" msgid "already claimed" msgstr "EXP ya adquirida" msgid "not claimed" msgstr "EXP disponible" msgid "already knows this skill" msgstr "ya conoce esta habilidad" msgid "already has max skills" msgstr "ya tiene las habilidades al máximo" msgid "(already visited)" msgstr "(ya visitado)" msgid "(not visited)" msgstr "(no visitado)" msgid "%{color} Barrier" msgstr "Barrera %{color}" msgid "(tent visited)" msgstr "(tienda visitada)" msgid "%{color} Tent" msgstr "Tienda %{color}" msgid "Road" msgstr "Carretera" msgid "(digging ok)" msgstr "(excavación posible)" msgid "(no digging)" msgstr "(excavación imposible)" msgid "penalty: %{cost}" msgstr "Penalización: %{cost}" msgid "Defenders:" msgstr "Defensores:" msgid "Unknown" msgstr "" msgid "%{name} (Level %{level})" msgstr "%{name} ( Nivel %{level} )" msgid "Attack:" msgstr "Ataque:" msgid "Defense:" msgstr "Defensa:" msgid "Spell Power:" msgstr "Pot. de Hech:" msgid "Knowledge:" msgstr "Conocimiento:" msgid "Spell Points:" msgstr "Maná:" msgid "Move Points:" msgstr "Movimiento:" msgid "Uncharted Territory" msgstr "Territorio inexplorado" msgid "Cost per troop:" msgstr "Coste por tropa:" msgid "Available: %{count}" msgstr "Disponible: %{count}" msgid "Number to buy:" msgstr "Reclutar:" msgid "Recruit selected monsters." msgstr "" msgid "Select maximum monsters to be recruited." msgstr "" msgid "Select only 1 monster to be recruited." msgstr "" msgid "Select Game Resolution:" msgstr "Seleccione la resolución del juego:" msgid "Click to apply the selected resolution." msgstr "" #, fuzzy msgid "Click to apply the entered text." msgstr "Haz clic para volver al menú principal" msgid "Click to open the Virtual Keyboard dialog." msgstr "" msgid "Open Virtual Keyboard" msgstr "" msgid "How many troops to move?" msgstr "¿Cuántas tropas deseas mover?" msgid "Fast separation into slots:" msgstr "Distribución rápida del ejército en espacios vacíos:" msgid "Map: " msgstr "" msgid "" "\n" "\n" "Month: " msgstr "" msgid ", Week: " msgstr "" msgid ", Day: " msgstr "" msgid "" "\n" "\n" "Location: " msgstr "" #, fuzzy msgid "Click to save the current game." msgstr "Haz clic para volver al menú principal" #, fuzzy msgid "Click to load a previously saved game." msgstr "Cargar una partida previamente grabada." msgid "Are you sure you want to delete file:" msgstr "Estas seguro de que quieres eliminar el archivo:" msgid "Warning!" msgstr "¡Advertencia!" msgid "File to Save:" msgstr "Archivo a Guardar:" msgid "File to Load:" msgstr "Archivo a Cargar:" msgid "Accept the choice made." msgstr "Aceptar la elección realizada." msgid "%{color} %{race} hero" msgstr "Héroe %{race} %{color}" msgid "Terrain object" msgstr "" #, fuzzy msgid "Select Skill:" msgstr "Defensa" #, fuzzy msgid "Select Spell:" msgstr "Seleccionar objetivo del hechizo" #, fuzzy msgid "Select Artifact:" msgstr "Artefactos" #, fuzzy msgid "Select Monster:" msgstr "Mejor monstruo:" #, fuzzy msgid "Select Hero:" msgstr "Mejor héroe:" #, fuzzy msgid "Select Treasure:" msgstr "Mejor héroe:" #, fuzzy msgid "Select Ocean Object:" msgstr "Artefactos" #, fuzzy msgid "Castle/town placing" msgstr "Opciones del Castillo" msgid "doubleLinedRace|Neutral" msgstr "Neutral" msgid "race|Random" msgstr "Aleatorio" #, fuzzy msgid "Click to start placing the selected castle/town." msgstr "Haz clic para volver al menú principal" #, fuzzy msgid "Click to select town placing." msgstr "Ver ciudad siguiente" #, fuzzy msgid "Click to select castle placing." msgstr "Haga clic aquí para seleccionar el escenario donde jugar." #, fuzzy msgid "%{color} %{race} %{townOrCastle}" msgstr "Héroe %{race} %{color}" msgid "race|Neutral" msgstr "Neutral" msgid "You will place" msgstr "" #, fuzzy msgid "Click to select this class." msgstr "Haga clic aquí para seleccionar el escenario donde jugar." #, fuzzy msgid "Click to select this color." msgstr "Haga clic aquí para seleccionar el escenario donde jugar." #, fuzzy msgid "Select Dwelling:" msgstr "Seleccionar objetivo del hechizo" #, fuzzy msgid "Select Landscape Object:" msgstr "Artefactos" msgid "Mine placing" msgstr "" #, fuzzy msgid "" "Resource\n" "type:" msgstr "Recursos" #, fuzzy msgid "%{mineName} appearance:" msgstr "%{name} el %{race}" #, fuzzy msgid "Click to select %{object} as the resource generator to be placed." msgstr "Haga clic aquí para seleccionar el escenario donde jugar." #, fuzzy msgid "Select Mountain Object:" msgstr "Artefactos" #, fuzzy msgid "Select Rock Object:" msgstr "Artefactos" #, fuzzy msgid "Select Tree Object:" msgstr "Artefactos" #, fuzzy msgid "Select Power Up Object:" msgstr "Artefactos" #, fuzzy msgid "Select Adventure Object:" msgstr "Artefactos" #, fuzzy msgid "Map Type:\n" msgstr "Tamaño del Mapa" #, fuzzy msgid "The Succession Wars" msgstr "El Otro Lado" msgid "The Price of Loyalty" msgstr "El Precio de la Lealtad" #, fuzzy msgid "Resurrection" msgstr "Resurrección" msgid "Lose all your heroes and towns." msgstr "Perder todos tus héroes y ciudades." msgid "Lose a specific town." msgstr "Perder una ciudad específica." msgid "Lose a specific hero." msgstr "Perder un héroe específico." msgid "Run out of time. Fail to win by a certain point." msgstr "Se acaba el tiempo. No conseguir ganar llegado un punto determinado." msgid "Loss Condition" msgstr "Condiciones de derrota" msgid "Defeat all enemy heroes and towns." msgstr "Derrota a todos los heroes enemígos y sus ciudades." msgid "Capture a specific town." msgstr "Capturar una ciudad específica." msgid "Defeat a specific hero." msgstr "Derrotar un héroe específico." msgid "Find a specific artifact." msgstr "Encontrar un artefacto específico." msgid "Your side defeats the opposing side." msgstr "Tu bando derrota al bando contrario." msgid "Accumulate a large amount of gold." msgstr "Acumular una gran cantidad de dinero." msgid "Victory Condition" msgstr "Condiciones de victoria" msgid "Map difficulty:" msgstr "Nivel del mapa:" msgid "N" msgstr "P" msgid "No maps exist at that size" msgstr "" msgid "Small Maps" msgstr "Mapas pequeños" msgid "View only maps of size small (36 x 36)." msgstr "Ver sólo mapas pequeños (36x36)." msgid "Medium Maps" msgstr "Mapas medianos" msgid "View only maps of size medium (72 x 72)." msgstr "Ver sólo mapas medianos (72x72)" msgid "Large Maps" msgstr "Mapas grandes" msgid "View only maps of size large (108 x 108)." msgstr "Ver sólo mapas grandes (108x108)" msgid "Extra Large Maps" msgstr "Mapas extra grandes" msgid "View only maps of size extra large (144 x 144)." msgstr "Ver sólo mapas extra grandes (144x144)." msgid "All Maps" msgstr "Todos los mapas" msgid "View all maps, regardless of size." msgstr "Ver todos los mapas, independientemente de su tamaño." msgid "Players Icon" msgstr "Icono de los jugadores" msgid "" "Indicates how many players total are in the scenario. Any positions not " "occupied by humans will be occupied by computer players." msgstr "" "Indica cuantos jugadores hay en total en el escenario. Cualquier posición no " "ocupada por humanos, será ocupada por jugadores virtuales." msgid "" "Indicates whether the map\n" "is small (36 x 36), medium\n" "(72 x 72), large (108 x 108),\n" "or extra large (144 x 144)." msgstr "" "Indica si los mapas son pequeños (36x36), medianos (72x72), grandes " "(108x108) o extra grandes (144x144)." msgid "Size Icon" msgstr "Icono del tamaño" msgid "" "Indicates whether the map is made for \"The Succession Wars\" or \"The Price " "of Loyalty\" version of the game." msgstr "" #, fuzzy msgid "Map Type" msgstr "Tamaño del Mapa" msgid "Selected Name" msgstr "Nombre elegido" msgid "The name of the currently selected map." msgstr "El nombre del mapa actualmente seleccionado." msgid "Selected Map Difficulty" msgstr "Dificultad del mapa elegido" #, fuzzy msgid "" "The map difficulty of the currently selected map. The map difficulty is " "determined by the scenario designer. More difficult maps might include more " "or stronger enemies, fewer resources, or other special conditions making " "things tougher for the human player." msgstr "" "La dificultad de mapa del mapa seleccionado actualmente. La dificultad de " "mapa se determina por el diseñador del escenario. Los mapas más difíciles " "pueden incluir enemigos más abundantes o poderosos, menos recursos, o " "algunas otras condiciones que vuelvan las cosas más duras para el jugador " "humano." msgid "Selected Description" msgstr "Descripción seleccionada" msgid "The description of the currently selected map." msgstr "La descripción del mapa actualmente elegido." msgid "Jump" msgstr "Salta" msgid "Hero Speed" msgstr "Velocidad del héroe" msgid "Don't Show" msgstr "No mostrar" msgid "Enemy Speed" msgstr "Velocidad del enemigo" msgid "Auto Resolve" msgstr "Automáticas" msgid "Auto, No Spells" msgstr "Automáticas, sin hechizos" msgid "Battles" msgstr "Batallas" msgid "autoBattle|Manual" msgstr "Manuales" msgid "Change the speed at which your heroes move on the main screen." msgstr "" msgid "" "Sets the speed that A.I. heroes move at. You can also elect not to view A.I. " "movement at all." msgstr "" #, fuzzy msgid "Change the interface settings of the game." msgstr "Cambiar los ajustes gráficos del juego." msgid "Interface Settings" msgstr "Ajustes de interfaz" msgid "Toggle instant battle mode." msgstr "" msgid "Att." msgstr "Ataq." msgid "Def." msgstr "Def." msgid "Power" msgstr "Poder" msgid "Knowl" msgstr "Conoc." msgid "1st" msgstr "1er" msgid "2nd" msgstr "2do" msgid "3rd" msgstr "3er" msgid "4th" msgstr "4o" msgid "5th" msgstr "5o" msgid "6th" msgstr "6o" msgid "Oracle: Player Rankings" msgstr "Oracle: Clasificación de jugadores" msgid "Thieves' Guild: Player Rankings" msgstr "Gremio de ladrones: Clasificación de jugadores" msgid "Number of Towns:" msgstr "Número de ciudades:" msgid "Number of Castles:" msgstr "Número de castillos:" msgid "Number of Heroes:" msgstr "Número de héroes:" msgid "Gold in Treasury:" msgstr "Fondos de oro:" msgid "Wood & Ore:" msgstr "Madera y Hierro:" msgid "Gems, Cr, Slf & Mer:" msgstr "Gem, Cri, Azu y Mer:" msgid "Obelisks Found:" msgstr "Obeliscos encontrados:" msgid "Artifacts:" msgstr "Artefactos:" msgid "Total Army Strength:" msgstr "Fuerza total del ejercito:" msgid "Income:" msgstr "Ingresos:" msgid "Best Hero:" msgstr "Mejor héroe:" msgid "Best Hero Stats:" msgstr "Estads. mejor héroe:" msgid "Personality:" msgstr "Personalidad:" msgid "Best Monster:" msgstr "Mejor monstruo:" #, fuzzy msgid "Map filename" msgstr "" "Tamaño\n" "del Mapa" #, fuzzy msgid "" "Are you sure you want to load a new map? (Any unsaved changes to the current " "map will be lost.)" msgstr "" "¿Estás seguro de que quieres cargar una nueva partida? (Tu partida actual se " "perderá)" #, fuzzy msgid "" "Are you sure you want to create a new map? (Any unsaved changes to the " "current map will be lost.)" msgstr "" "¿Estás seguro de que quieres cargar una nueva partida? (Tu partida actual se " "perderá)" msgid "" "Create a new map, either from scratch or using the random map generator." msgstr "" #, fuzzy msgid "New Map" msgstr "Dawn" #, fuzzy msgid "Load Map" msgstr "Cargar juego" msgid "Load an existing map." msgstr "" #, fuzzy msgid "Save Map" msgstr "Guardar Partida" #, fuzzy msgid "Save the current map." msgstr "Guardar la partida en curso." msgid "Quit out of the map editor." msgstr "" #, fuzzy msgid "Objects cannot be placed outside the map." msgstr "Este pueblo no se puede actualizar a castillo." msgid "Monsters cannot be placed on water." msgstr "" msgid "Choose a tile which does not contain any objects." msgstr "" #, fuzzy msgid "Treasures cannot be placed on water." msgstr "Este pueblo no se puede actualizar a castillo." msgid "Heroes cannot be placed on water." msgstr "" msgid "" "A maximum of %{count} heroes of the same color can be placed on the map." msgstr "" #, fuzzy msgid "Artifacts cannot be placed on water." msgstr "Este pueblo no se puede actualizar a castillo." #, fuzzy msgid "Mountains cannot be placed on water." msgstr "Este pueblo no se puede actualizar a castillo." #, fuzzy msgid "Rocks cannot be placed on water." msgstr "Este pueblo no se puede actualizar a castillo." #, fuzzy msgid "Trees cannot be placed on water." msgstr "Este pueblo no se puede actualizar a castillo." #, fuzzy msgid "Ocean object must be placed on water." msgstr "Este pueblo no se puede actualizar a castillo." #, fuzzy msgid "Landscape objects cannot be placed on water." msgstr "Este pueblo no se puede actualizar a castillo." #, fuzzy msgid "Towns cannot be placed on water." msgstr "Este pueblo no se puede actualizar a castillo." #, fuzzy msgid "Mines cannot be placed on water." msgstr "Este pueblo no se puede actualizar a castillo." #, fuzzy msgid "Dwellings cannot be placed on water." msgstr "Este pueblo no se puede actualizar a castillo." #, fuzzy msgid "Power-ups cannot be placed on water." msgstr "Este pueblo no se puede actualizar a castillo." #, fuzzy msgid "Adventure objects cannot be placed on water." msgstr "Este pueblo no se puede actualizar a castillo." #, fuzzy msgid "Used to place %{object}." msgstr "Encuentra el artefacto definitivo" #, fuzzy msgid "Select object type" msgstr "Artefactos" #, fuzzy msgid "" "Click here to\n" "select a monster." msgstr "Haga clic aquí para seleccionar el escenario donde jugar." #, fuzzy msgid "" "Click here to\n" "select another monster." msgstr "Por favor, selecciona otro héroe." msgid "" "Cell\n" "Details" msgstr "" #, fuzzy msgid "Roads" msgstr "Fin del camino" msgid "Streams" msgstr "" msgid "Erase" msgstr "" msgid "Mountains" msgstr "Montañas" #, fuzzy msgid "Rocks" msgstr "Roca" msgid "Trees" msgstr "Ãrboles" #, fuzzy msgid "Water Objects" msgstr "Paraje baldio" #, fuzzy msgid "Miscellaneous" msgstr "Fineous" msgid "Artifacts" msgstr "Artefactos" #, fuzzy msgid "Dwellings" msgstr "Medianos" msgid "Mines" msgstr "Minas" #, fuzzy msgid "Power-ups" msgstr "Poder" #, fuzzy msgid "Treasures" msgstr "Tesoro" #, fuzzy msgid "Towns" msgstr "Pueblo" msgid "Heroes" msgstr "Héroes" msgid "editorErasure|Terrain" msgstr "" msgid "editorErasure|Castles" msgstr "" msgid "editorErasure|Monsters" msgstr "" msgid "editorErasure|Heroes" msgstr "" msgid "editorErasure|Artifacts" msgstr "" msgid "editorErasure|Roads" msgstr "" msgid "editorErasure|Streams" msgstr "" msgid "editorErasure|Treasures" msgstr "" msgid "" "Draws terrain in\n" "%{size} by %{size} square increments." msgstr "" msgid "" "Erases objects in\n" "%{size} by %{size} square increments." msgstr "" #, fuzzy msgid "Small Brush" msgstr "Mapas pequeños" #, fuzzy msgid "Medium Brush" msgstr "Mapas medianos" #, fuzzy msgid "Large Brush" msgstr "Mapas grandes" msgid "Area Fill" msgstr "" msgid "Used to click and drag for filling in large areas." msgstr "" msgid "Clear Area" msgstr "" msgid "Used to click and drag for clearing large areas." msgstr "" msgid "" "Costs %{rate} times normal movement for all heroes. (Pathfinding reduces or " "eliminates the penalty.)" msgstr "" msgid "Traversable only by boat." msgstr "" msgid "No special modifiers." msgstr "" msgid "Toggle the erasure of %{type} objects." msgstr "" msgid "" "Objects of this type will be deleted with the Erase tool. Left-click here to " "deselect this type. Press and hold this button to deselect all other object " "types." msgstr "" msgid "" "Objects of this type will NOT be deleted with the Erase tool. Left-click " "here to select this type. Press and hold this button to select all other " "object types." msgstr "" msgid "Terrain Mode" msgstr "" msgid "Used to draw the underlying grass, dirt, water, etc. on the map." msgstr "" msgid "Landscape Objects Mode" msgstr "" msgid "" "Used to place landscape objects (mountains, rocks, trees, etc.) on the map." msgstr "" msgid "Detail Mode" msgstr "" #, fuzzy msgid "Used for special editing of monsters, heroes and towns." msgstr "Derrota a todos los heroes enemígos y sus ciudades." #, fuzzy msgid "Adventure Objects Mode" msgstr "Opciones de la Aventura" msgid "" "Used to place adventure objects (artifacts, dwellings, mines, treasures, " "etc.) on the map." msgstr "" #, fuzzy msgid "Kingdom Objects Mode" msgstr "Ingresos del reino" msgid "Used to place kingdom objects (towns, castles and heroes) on the map." msgstr "" #, fuzzy msgid "Monsters Mode" msgstr "Monstruo" #, fuzzy msgid "Used to place monsters on the map." msgstr "Encuentra el artefacto definitivo" msgid "Allows you to draw streams by clicking and dragging." msgstr "" msgid "Stream Mode" msgstr "" msgid "Allows you to draw roads by clicking and dragging." msgstr "" #, fuzzy msgid "Road Mode" msgstr "Fin del camino" msgid "Erase Mode" msgstr "" msgid "Used to erase objects from the map." msgstr "" msgid "Change between zoom and normal view." msgstr "" msgid "Magnify" msgstr "" msgid "Undo" msgstr "" msgid "Undo your last action." msgstr "" msgid "Create a new map either from scratch or using the random map generator." msgstr "" msgid "Edit map title, description, and other general information." msgstr "" #, fuzzy msgid "Specifications" msgstr "Fortificación" msgid "File Options" msgstr "Opciones de archivo" #, fuzzy msgid "" "Open the file options menu, where you can save or load maps, or quit out of " "the editor." msgstr "" "Desplegar el menú de opciones de archivo, permiéndote cargar, grabar etc." #, fuzzy msgid "View the editor system options, which let you customize the editor." msgstr "" "Desplegar el menú de opciones de sistema, permitiéndote personalizar tu " "partida." msgid "Create a map that is %{size} squares wide and %{size} squares high." msgstr "" #, fuzzy msgid "Cancel back to the New Map menu." msgstr "Cancelar y volver al menú principal." msgid "Cancel back to the main menu." msgstr "Cancelar y volver al menú principal." msgid "From Scratch" msgstr "" msgid "Start from scratch with a blank map." msgstr "" msgid "Create a randomly generated map." msgstr "" #, fuzzy msgid "Random" msgstr "Cualquier torre" #, fuzzy msgid "Cancel back to the Map Editor main menu." msgstr "Cancelar y volver al menú principal." msgid "No maps available!" msgstr "¡Ningún mapa disponible!" msgid "Warning" msgstr "Advertencia" msgid "difficulty|Easy" msgstr "Fácil" msgid "difficulty|Normal" msgstr "Normal" msgid "difficulty|Hard" msgstr "Difícil" msgid "difficulty|Expert" msgstr "Experto" msgid "difficulty|Impossible" msgstr "Imposible" msgid "and more..." msgstr "" msgid "Easy" msgstr "Fácil" msgid "Hard" msgstr "Difícil" msgid "Campaign Difficulty" msgstr "Dificultad de la campaña" msgid "" "Choose this difficulty if you want to prefer game story over challenge. AI " "is weaker in comparison with normal difficulty." msgstr "" msgid "" "Choose this difficulty to enjoy the campaign as per the original design." msgstr "" msgid "" "Choose this difficulty if you want challenge. AI is stronger in comparison " "with normal difficulty." msgstr "" msgid "" "Congratulations!\n" "\n" "Days: %{days}\n" msgstr "" "¡Enhorabuena!\n" "\n" "Días: %{days}\n" msgid "" "\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" "\n" "Dificultad: %{difficulty}\n" "\n" msgid "" "Score: %{score}\n" "\n" "Rating:\n" "%{rating}" msgstr "" "Puntuación: %{score}\n" "\n" "Clasificación:\n" "%{rating}" msgid "Start the selected scenario." msgstr "" msgid "View Intro" msgstr "Ver Intro" msgid "View Intro videos for the current state of the campaign." msgstr "Vea el vídeo de introducción para el escenario actual de la campaña." msgid "" "Select the campaign difficulty. This can be lowered at any point during the " "campaign." msgstr "" #, fuzzy msgid "Restart the current scenario." msgstr "Ver la pantalla de créditos" msgid "Difficulty" msgstr "Dificultad" msgid "" "You have changed to a lower difficulty for the campaign. You will not be " "able to revert this after this point. The high score will be calculated " "based solely on the new difficulty. Do you want to proceed?" msgstr "" msgid "Are you sure you want to restart this scenario?" msgstr "¿Estás seguro de que quieres reiniciar este escenario?" msgid "Campaign Scenario loading failure" msgstr "" msgid "Please make sure that campaign files are correct and present." msgstr "" msgid "Days spent" msgstr "" msgid "The number of days spent on this campaign." msgstr "" msgid "Project Coordination and Core Development" msgstr "" msgid "Development" msgstr "" msgid "Visit us at " msgstr "" msgid "QA and Support" msgstr "" #, fuzzy msgid "Dev and Support" msgstr "Teletransporte" msgid "Special Thanks to" msgstr "" msgid "and many other contributors!" msgstr "" msgid "and many-many other supporters!" msgstr "" msgid "Support us at" msgstr "" msgid "local-donation-platform|https://www.patreon.com/fheroes2" msgstr "" msgid "Connect with us at" msgstr "" msgid "local-social-network|https://www.facebook.com/groups/fheroes2" msgstr "" msgid "Need help with the game?" msgstr "" msgid "Original project before 0.7" msgstr "" #, fuzzy msgid "Heroes of Might and Magic II: The Succession Wars team" msgstr "Salir del juego y volver al sistema operativo." msgid "Designed and Directed" msgstr "" msgid "Programming and Design" msgstr "" msgid "Executive Producer" msgstr "" msgid "Producer" msgstr "" msgid "Additional Design" msgstr "" msgid "Additional Programming" msgstr "" msgid "Musical Production" msgstr "" msgid "Music and Sound Design" msgstr "" #, fuzzy msgid "Vocalists" msgstr "Balística" msgid "Art Director" msgstr "" msgid "Assistant Art Director" msgstr "" #, fuzzy msgid "Artists" msgstr "Artefactos" msgid "QA Manager" msgstr "" msgid "QA" msgstr "" #, fuzzy msgid "Writing" msgstr "Nivel" msgid "Manual and Helpfile" msgstr "" msgid "Scenarios" msgstr "Escenarios" #, fuzzy msgid "Heroes of Might and Magic II: The Price of Loyalty team" msgstr "Salir del juego y volver al sistema operativo." msgid "Design Lead" msgstr "" msgid "Designers" msgstr "" msgid "Programming Lead" msgstr "" msgid "Art Lead" msgstr "" #, fuzzy msgid "Playtesters" msgstr "2 jugadores" msgid "Designer" msgstr "" msgid "Producers" msgstr "" #, fuzzy msgid "QA Managers" msgstr "Cazadores" msgid "Sound Design" msgstr "" #, fuzzy msgid "Town Themes" msgstr "Puerta de la Ciudad" msgid "Alto Sax" msgstr "" msgid "Harpsichord and Piano" msgstr "" msgid "Basso Vocal" msgstr "" msgid "Soprano Vocal" msgstr "" msgid "Recorded at %{recordingStudio}" msgstr "" msgid "credits|Manual" msgstr "" msgid "German Consultant" msgstr "" msgid "Map Designers" msgstr "" msgid "Package Design" msgstr "" msgid "Your Name" msgstr "Tu nombre" msgid "Unknown Hero" msgstr "Héroe desconocido" #, fuzzy msgid "Standard" msgstr "Juego estandar" #, fuzzy msgid "View High Scores for Standard Maps." msgstr "Ver la pantalla de puntuaciones más altas" msgid "Campaign" msgstr "Partida de Campaña" #, fuzzy msgid "View High Scores for Campaigns." msgstr "Ver la pantalla de puntuaciones más altas" msgid "hotkey|default okay event" msgstr "" msgid "hotkey|default cancel event" msgstr "" msgid "hotkey|default left" msgstr "" msgid "hotkey|default right" msgstr "" msgid "hotkey|default up" msgstr "" msgid "hotkey|default down" msgstr "" msgid "hotkey|toggle fullscreen" msgstr "" msgid "hotkey|toggle text support mode" msgstr "" msgid "hotkey|new game" msgstr "" msgid "hotkey|load game" msgstr "" msgid "hotkey|highscores" msgstr "" msgid "hotkey|credits" msgstr "" #, fuzzy msgid "hotkey|standard game" msgstr "Juego estandar" #, fuzzy msgid "hotkey|campaign game" msgstr "Partida de Campaña." #, fuzzy msgid "hotkey|multi-player game" msgstr "Juego multi-jugador" #, fuzzy msgid "hotkey|settings" msgstr "Ajustes" msgid "hotkey|quit" msgstr "" msgid "hotkey|select map" msgstr "" msgid "hotkey|select small map size" msgstr "" msgid "hotkey|select medium map size" msgstr "" msgid "hotkey|select large map size" msgstr "" msgid "hotkey|select extra large map size" msgstr "" msgid "hotkey|select all map sizes" msgstr "" msgid "hotkey|hotseat game" msgstr "" msgid "hotkey|battle only game" msgstr "" msgid "hotkey|choose the original campaign" msgstr "" msgid "hotkey|choose the expansion campaign" msgstr "" #, fuzzy msgid "hotkey|map editor main menu" msgstr "Partida de Campaña." #, fuzzy msgid "hotkey|new map menu" msgstr "Mostrar el siguiente héroe" #, fuzzy msgid "hotkey|load map menu" msgstr "Juego estandar" msgid "hotkey|new map from scratch" msgstr "" #, fuzzy msgid "hotkey|new random map" msgstr "Resumen del reino" #, fuzzy msgid "hotkey|undo last action" msgstr "Ver ciudad siguiente" #, fuzzy msgid "hotkey|redo last action" msgstr "Ver ciudad siguiente" msgid "hotkey|roland campaign" msgstr "" msgid "hotkey|archibald campaign" msgstr "" msgid "hotkey|the price of loyalty campaign" msgstr "" msgid "hotkey|voyage home campaign" msgstr "" msgid "hotkey|wizard's isle campaign" msgstr "" msgid "hotkey|descendants campaign" msgstr "" msgid "hotkey|select first campaign bonus" msgstr "" msgid "hotkey|select second campaign bonus" msgstr "" msgid "hotkey|select third campaign bonus" msgstr "" msgid "hotkey|view campaign intro" msgstr "" #, fuzzy msgid "hotkey|select campaign difficulty" msgstr "Dificultad del juego" msgid "hotkey|restart campaign scenario" msgstr "" msgid "hotkey|world map left" msgstr "" msgid "hotkey|world map right" msgstr "" msgid "hotkey|world map up" msgstr "" msgid "hotkey|world map down" msgstr "" msgid "hotkey|world map up left" msgstr "" msgid "hotkey|world map up right" msgstr "" msgid "hotkey|world map down left" msgstr "" msgid "hotkey|world map down right" msgstr "" msgid "hotkey|save game" msgstr "" #, fuzzy msgid "hotkey|next hero" msgstr "Mostrar el siguiente héroe" #, fuzzy msgid "hotkey|start hero movement" msgstr "Continuar movimento" #, fuzzy msgid "hotkey|cast adventure spell" msgstr "Lanzar un hechizo tipo aventura." msgid "hotkey|put hero to sleep" msgstr "" #, fuzzy msgid "hotkey|next town" msgstr "Ver ciudad siguiente" msgid "hotkey|end turn" msgstr "" #, fuzzy msgid "hotkey|file options" msgstr "Opciones de archivo" #, fuzzy msgid "hotkey|adventure options" msgstr "Opciones de la Aventura" msgid "hotkey|puzzle map" msgstr "" #, fuzzy msgid "hotkey|scenario information" msgstr "Formación Desplegada" msgid "hotkey|dig for artifact" msgstr "" msgid "hotkey|view world" msgstr "" #, fuzzy msgid "hotkey|kingdom summary" msgstr "Resumen del reino" msgid "hotkey|default action" msgstr "" msgid "hotkey|open focus" msgstr "" #, fuzzy msgid "hotkey|system options" msgstr "Personalizar opciones del sistema" msgid "hotkey|scroll left" msgstr "" msgid "hotkey|scroll right" msgstr "" msgid "hotkey|scroll up" msgstr "" msgid "hotkey|scroll down" msgstr "" msgid "hotkey|toggle control panel" msgstr "" msgid "hotkey|toggle radar" msgstr "" msgid "hotkey|toggle buttons" msgstr "" msgid "hotkey|toggle status" msgstr "" msgid "hotkey|toggle icons" msgstr "" msgid "hotkey|transfer control to ai" msgstr "" msgid "hotkey|retreat from battle" msgstr "" msgid "hotkey|surrender during battle" msgstr "" msgid "hotkey|toggle battle auto mode" msgstr "" #, fuzzy msgid "hotkey|finish the battle in auto mode" msgstr "¿Estás seguro de que desea descartar este ejército?" #, fuzzy msgid "hotkey|battle options" msgstr "Opciones del castillo" msgid "hotkey|skip turn in battle" msgstr "" msgid "hotkey|cast battle spell" msgstr "" msgid "hotkey|dwelling level 1" msgstr "" msgid "hotkey|dwelling level 2" msgstr "" msgid "hotkey|dwelling level 3" msgstr "" msgid "hotkey|dwelling level 4" msgstr "" msgid "hotkey|dwelling level 5" msgstr "" msgid "hotkey|dwelling level 6" msgstr "" msgid "hotkey|well" msgstr "" #, fuzzy msgid "hotkey|marketplace" msgstr "Mercado" msgid "hotkey|mage guild" msgstr "" #, fuzzy msgid "hotkey|shipyard" msgstr "Astillero" #, fuzzy msgid "hotkey|thieves guild" msgstr "Gremio de Ladrones" msgid "hotkey|tavern" msgstr "" msgid "hotkey|construction screen" msgstr "" msgid "hotkey|buy all monsters in well" msgstr "" msgid "hotkey|split stack by half" msgstr "" msgid "hotkey|split stack by one" msgstr "" msgid "hotkey|join stacks" msgstr "" msgid "hotkey|upgrade troop" msgstr "" msgid "hotkey|dismiss hero or troop" msgstr "" msgid "" "Do you want to regain control from AI? The effect will take place only on " "the next turn." msgstr "" "¿Quieres recuperar el control de la IA? El efecto sólo tendrá efecto en\n" "el siguiente turno." msgid "" "Do you want to transfer control from you to the AI? The effect will take " "place only on the next turn." msgstr "" msgid "Default Actions" msgstr "" msgid "Global Actions" msgstr "" msgid "Main Menu" msgstr "" msgid "World Map" msgstr "Mapa del mundo" #, fuzzy msgid "Battle Screen" msgstr "Pantalla de Héroe" #, fuzzy msgid "Town Screen" msgstr "Pantalla de Héroe" msgid "Army Actions" msgstr "" msgid "Editor" msgstr "" #, fuzzy msgid "The save file is corrupted." msgstr "Reflejo creado." msgid "Unsupported save format: " msgstr "" msgid "Current game version: " msgstr "Versión actual del juego: " msgid "Last supported version: " msgstr "" msgid "This file contains a save with an invalid game type." msgstr "" msgid "" "This save file requires \"The Price of Loyalty\" game assets, but they have " "not been provided to the engine." msgstr "" msgid "This saved game is localized to '" msgstr "" msgid "' language, but the current language of the game is '" msgstr "" msgid "Hot Seat" msgstr "Asiento caliente." msgid "" "Play a Hot Seat game, where 2 to 4 players play around the same computer, " "switching into the 'Hot Seat' when it is their turn." msgstr "" "Juega una partida de \"Asiento caliente\" donde de 2 a 4 jugadores juegan en " "el mismo PC por turnos." msgid "A single player game playing out a single map." msgstr "Un solo jugador jugando un único mapa." msgid "Standard Game" msgstr "Juego estandar" #, fuzzy msgid "A single player game playing through a series of maps." msgstr "Un solo jugador jugando un único mapa." msgid "Campaign Game" msgstr "Partida de Campaña" msgid "Multi-Player Game" msgstr "Juego multi-jugador" msgid "" "A multi-player game, with several human players completing against each " "other on a single map." msgstr "" "Una partida multijugador con varios jugadores humanos compitiendo entre " "ellos en un mapa." msgid "fheroes2 Resurrection Team presents" msgstr "El equipo fheroes2 Resurrection presenta" msgid "Greetings!" msgstr "¡Saludos!" msgid "Welcome to Heroes of Might and Magic II powered by fheroes2 engine!" msgstr "Bienvenido a Heroes of Might and Magic II con el motor fheroes2." msgid "" "Welcome to Heroes of Might and Magic II powered by the fheroes2 engine!\n" "Before starting the game please select a game resolution." msgstr "" "Bienvenido a Heroes of Might and Magic II con el motor fheroes2.\n" "Antes de empezar el juego, selecciona una resolución de juego." msgid "Please Remember" msgstr "Recuerde" msgid "" "You can always change the language, resolution and settings of the game by " "clicking on the " msgstr "" "Siempre puedes cambiar el idioma, la resolución y la configuración del juego " "haciendo clic en la " msgid "door" msgstr "puerta" msgid " on the left side of the Main Menu, or with the " msgstr "de la izquierda del menú principal, o con el botón " msgid " button from the " msgstr " del menú " msgid "NEW GAME" msgstr "NUEVO JUEGO" msgid "" " menu. \n" "\n" "Enjoy the game!" msgstr "" ".\n" "\n" "¡Disfruta del juego!" msgid "The Editor requires \"The Price of Loyalty\" expansion files to work." msgstr "" msgid "Quit Heroes of Might and Magic II and return to the operating system." msgstr "Sal de Heroes of Might and Magic II y vuelve al sistema operativo." msgid "Credits" msgstr "Creditos" msgid "View the credits screen." msgstr "Ver la pantalla de créditos." msgid "High Scores" msgstr "Puntuaciones más altas" msgid "View the high scores screen." msgstr "Ver la pantalla de puntuaciones más altas." msgid "Change language, resolution and settings of the game." msgstr "Cambia el idioma, la resolución y la configuración del juego." msgid "Game Settings" msgstr "Ajustes del juego" msgid "" "Required video files for campaign selection window are missing. Please make " "sure that all necessary files are present in the system." msgstr "" "Faltan los archivos de vídeo necesarios para la ventana de selección de " "campaña. Por favor, asegúrese de que todos los archivos necesarios están " "presentes en el sistema." msgid "" "Either Roland's or Archibald's campaign from the original Heroes of Might " "and Magic II." msgstr "" msgid "Original Campaign" msgstr "" msgid "Expansion Campaign" msgstr "" msgid "One of the four new campaigns from the Price of Loyalty expansion set." msgstr "" msgid "Loading video. Please wait..." msgstr "Cargando vídeo. Por favor espere..." msgid "Host" msgstr "Anfitrión" msgid "" "The host sets up the game options. There can only be one host per network " "game." msgstr "" "El anfitrión establece las opciones de la partida. Solo puede haber un " "anfitrión por partida en red." msgid "Guest" msgstr "Invitado" msgid "" "The guest waits for the host to set up the game, then is automatically added " "in. There can be multiple guests for TCP/IP games." msgstr "" "El invitado espera a que el anfitrión monte una partida, entonces es " "añadido automáticamente en ella. Pueden haber varios invitados en las " "partidas TCP/IP." #, fuzzy msgid "Battle Only" msgstr "Enano de Combate" msgid "Setup and play a battle without loading any map." msgstr "" msgid "2 Players" msgstr "2 jugadores" msgid "" "Play with 2 human players, and optionally, up to 4 additional computer " "players." msgstr "" "Jugar con 2 jugadores humanos y opcionalmente, hasta 4 jugadores virtuales." msgid "3 Players" msgstr "3 Jugadores" msgid "" "Play with 3 human players, and optionally, up to 3 additional computer " "players." msgstr "" "Jugar con 3 jugadores humanos y, opcionalmente, hasta con 3 jugadores " "virtuales." msgid "4 Players" msgstr "4 Jugadores" msgid "" "Play with 4 human players, and optionally, up to 2 additional computer " "players." msgstr "" "Jugar con 4 jugadores humanos y opcionalmente, hasta con 2 jugadores " "virtuales." msgid "5 Players" msgstr "5 Jugadores" #, fuzzy msgid "" "Play with 5 human players, and optionally, up to 1 additional computer " "player." msgstr "" "Jugar con 5 jugadores humanos y, opcionalmente, con un jugador virtual " "adicional." msgid "6 Players" msgstr "6 Jugadores" msgid "Play with 6 human players." msgstr "Jugar con 6 jugadores humanos." msgid "Dragon city has fallen! You are now the Master of the Dragons." msgstr "" msgid "" "You captured %{name}!\n" "You are victorious." msgstr "" "¡Capturas %{name}!\n" "Eres el vencedor." msgid "" "You have captured the enemy hero %{name}!\n" "Your quest is complete." msgstr "" "¡Has capturado al héroe enemigo (%{name})!\n" "Tu búsqueda está completada." msgid "" "You have found the %{name}.\n" "Your quest is complete." msgstr "" "Has encontrado %{name}.\n" "Tu búsqueda ha concluido." msgid "Ultimate Artifact" msgstr "El artefacto definitivo" msgid "" "The enemy is beaten.\n" "Your side has triumphed!" msgstr "" "Tu enemigo esta derrotado.\n" "¡Tu bando ha triunfado!" msgid "" "You have built up over %{count} gold in your treasury.\n" "All enemies bow before your wealth and power." msgstr "" "Has recaudado por encima de %{count} piezas de oro en tu tesoro.\n" "Todos los enemigos te reverencia ante tu riqueza y poder." msgid "Victory!" msgstr "¡Victoria!" msgid "" "The enemy has captured %{name}!\n" "They are triumphant." msgstr "" "¡El enemigo ha capturado %{name}!\n" "Son los triunfadores." msgid "" "The enemy has built up over %{count} gold in his treasury.\n" "You must bow done in defeat before his wealth and power." msgstr "" "El enemigo a recaudado más de %{count} de oro en su tesoro.\n" "Debes inclinarte derrotado ante su riqueza y poder." msgid "You have been eliminated from the game!!!" msgstr "¡¡¡Has sido eliminado del juego!!!" msgid "" "You have lost the hero %{name}.\n" "Your quest is over." msgstr "" "Has perdido al héroe %{name}.\n" "Tu búsqueda ha terminado." msgid "" "You have failed to complete your quest in time.\n" "All is lost." msgstr "" "Has fallado en completar tu búsqueda a tiempo.\n" "Todo está perdido." msgid "Defeat!" msgstr "" msgid "" "Base score: %{score}\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" "Puntuación base: %{score}\n" "Dificultad: %{difficulty}\n" "\n" msgid "Defeat all enemy heroes and capture all enemy towns and castles." msgstr "" "Derrota a todos los héroes enemigos y captura todas las ciudades y castillos " "enemigos." msgid "Run out of time. (Fail to win by a certain point.)" msgstr "Agotarse el tiempo. (Fracasando sin ganar por alguna condición.)" msgid "You must defeat the enemy %{enemies}." msgid_plural "You must defeat the enemy alliance of %{enemies}." msgstr[0] "Debes derrotar al enemigo %{enemies}." msgstr[1] "Debes derrotar a la alianza enemiga de %{enemies}." msgid "" "The alliance consisting of %{allies} and you must defeat the enemy " "%{enemies}." msgid_plural "" "The alliance consisting of %{allies} and you must defeat the enemy alliance " "of %{enemies}." msgstr[0] "" msgstr[1] "" msgid "Capture the castle '%{name}'." msgstr "Captura el castillo de '%{name}'." msgid "Capture the town '%{name}'." msgstr "Captura la ciudad de '%{name}'." msgid "Defeat the hero '%{name}'." msgstr "Derrota el héroe '%{name}'." msgid "Find the ultimate artifact." msgstr "Encuentra el artefacto definitivo." msgid "Find the '%{name}' artifact." msgstr "Encuentra el artefacto '%{name}'." msgid "Accumulate %{count} gold." msgstr "Acumula %{count} oro." msgid "" ", or you may win by defeating all enemy heroes and capturing all enemy towns " "and castles." msgstr "" ", o puedes ganar derrotando a todos los héroes enemigos y capturando todas " "las ciudades y castillos enemigos." msgid "Lose the castle '%{name}'." msgstr "Pierde el castillo '%{name}'." msgid "Lose the town '%{name}'." msgstr "Pierde la ciudad de '%{name}'." msgid "Lose the hero: %{name}." msgstr "Pierdes el héroe: %{name}." msgid "Fail to win by the end of month %{month}, week %{week}, day %{day}." msgstr "" "No consigas ganar para el final del mes %{month}, semana %{week}, día %{day}." msgid "%{color} player has been vanquished!" msgstr "¡El jugador %{color} ha sido derrotado!" msgid "Major Event!" msgstr "¡Evento importante!" msgid "Scenario:" msgstr "Escenario:" msgid "Game Difficulty:" msgstr "Dificultad del juego:" msgid "Opponents:" msgstr "Contrincantes:" msgid "Class:" msgstr "Raza:" msgid "Rating %{rating}%" msgstr "Nivel %{rating}%" msgid "Click here to select which scenario to play." msgstr "Haga clic aquí para seleccionar el escenario donde jugar." msgid "Scenario" msgstr "Escenario" msgid "Game Difficulty" msgstr "Dificultad del juego" msgid "" "This lets you change the starting difficulty at which you will play. Higher " "difficulty levels start you of with fewer resources, and at the higher " "settings, give extra resources to the computer." msgstr "" "Te permite cambiar la dificultad de la partida en la que jugarás. A mayor " "nivel de dificultad comienzas con menos recursos, cuanto más alta la " "configuración más recursos extra recibe el jugador virtual." msgid "Difficulty Rating" msgstr "Nivel de Dificultad" msgid "" "The difficulty rating reflects a combination of various settings for your " "game. This number will be applied to your final score." msgstr "" "El Nivel de Dificultad refleja la combinación de varias configuraciones del " "juego. Este número se aplicará a tu puntuación final." msgid "Click to accept these settings and start a new game." msgstr "" "Haz clic para aceptar estas configuraciones y empezar una nueva partida." msgid "Click to return to the main menu." msgstr "Haz clic para volver al menú principal." msgid "Astrologers proclaim the Month of the %{name}." msgstr "Los Astrólogos proclaman el Mes de %{name}." msgid "Astrologers proclaim the Week of the %{name}." msgstr "Los Astrólogos proclaman la Semana de %{name}." msgid "After regular growth, the population of %{monster} is doubled!" msgstr "" "Tras un crecimiento normal, ¡la población del %{monster} se ha doblado!" msgid "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgid_plural "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgstr[0] "" "Tras un crecimiento normal, ¡la población de %{monster} aumenta un %{count} " "por ciento!" msgstr[1] "" "Tras un crecimiento normal, ¡la población de %{monster} aumenta un %{count} " "por ciento!" msgid "%{monster} growth +%{count}." msgstr "Crecimiento de %{monster} +%{count}." msgid " All populations are halved." msgstr " El crecimiento de las poblaciones se reduce a la mitad." msgid " All dwellings increase population." msgstr " Todas las moradas aumentan su población." msgid "New Month!" msgstr "¡Nuevo mes!" msgid "New Week!" msgstr "¡Nueva semana!" msgid "Beware!" msgstr "¡Atención!" msgid "" "%{color} player, this is your last day to capture a town, or you will be " "banished from this land." msgstr "" "Jugador %{color}, éste es tu último día para capturar una ciudad o serás " "desterrado de estas tierras." msgid "" "%{color} player, you only have %{day} days left to capture a town, or you " "will be banished from this land." msgstr "" "Jugador %{color}, sólo te quedan %{day} días para capturar una ciudad o " "serás desterrado de estas tierras." msgid "%{color} player's turn." msgstr "Turno del jugador %{color}." msgid "" "%{color} player, you have lost your last town. If you do not conquer another " "town in the next week, you will be eliminated." msgstr "" "Jugador %{color}, has perdido tu última ciudad. Si no conquistas ótra la " "próxima semana serás eliminado." msgid "" "%{color} player, your heroes abandon you, and you are banished from this " "land." msgstr "" "Jugador %{color}, tus héroes te abandonan y eres desterrado de estas tierras." msgid "Lord Kilburn" msgstr "Lord Kilburn" msgid "Tsabu" msgstr "Tsabu" msgid "Sir Galant" msgstr "Sir Galant" msgid "Thundax" msgstr "Thundax" msgid "Lord Haart" msgstr "Lord Haart" msgid "Ariel" msgstr "Ariel" msgid "Rebecca" msgstr "Rebecca" msgid "Sandro" msgstr "Sandro" msgid "Crodo" msgstr "Crodo" msgid "Barock" msgstr "Barock" msgid "Antoine" msgstr "" msgid "Astra" msgstr "Astra" msgid "Agar" msgstr "Agar" msgid "Vatawna" msgstr "Vatawna" msgid "Vesper" msgstr "Vesper" msgid "Ambrose" msgstr "Ambrose" msgid "Troyan" msgstr "Troyan" msgid "Jojosh" msgstr "Jojosh" msgid "Wrathmont" msgstr "Wrathmont" msgid "Maximus" msgstr "Maximus" msgid "Next Hero" msgstr "Siguiente Héroe" msgid "Select the next Hero." msgstr "Seleccione el siguiente Héroe." #, fuzzy msgid "Hero Movement" msgstr "Continuar movimento" msgid "" "Start the Hero's movement along the current path or re-visit the object " "occupied by the Hero. Press and hold this button to reset the Hero's path." msgstr "" msgid "Kingdom Summary" msgstr "Resumen del reino" msgid "View a Summary of your Kingdom." msgstr "Ver un resumen de tu reino." msgid "Cast an adventure spell." msgstr "Lanzar un hechizo tipo aventura." msgid "End Turn" msgstr "Finalizar el turno" msgid "End your turn and left the computer take its turn." msgstr "Finalizar tu turno y pasárselo al jugador virtual." msgid "Adventure Options" msgstr "Opciones de la Aventura" msgid "Bring up the adventure options menu." msgstr "Desplegar el menú de opciones de aventura." #, fuzzy msgid "" "Bring up the file options menu, allowing you to load, save, start a new game " "or quit." msgstr "" "Desplegar el menú de opciones de archivo, permiéndote cargar, grabar etc." msgid "Bring up the system options menu, allowing you to customize your game." msgstr "" "Desplegar el menú de opciones de sistema, permitiéndote personalizar tu " "partida." msgid "" "One or more heroes may still move, are you sure you want to end your turn?" msgstr "" "Uno o más de tus héroes todavía tiene posibilidad de movimiento. ¿Seguro que " "quieres terminar tu turno?" msgid "Are you sure you want to quit?" msgstr "¿Estás seguro que deseas salir?" msgid "Are you sure you want to restart? (Your current game will be lost.)" msgstr "¿Estás seguro que quieres reiniciar? (Tu partida actual se perderá.)" msgid "Are you sure you want to overwrite the save with this name?" msgstr "" "¿Estás seguro de que quieres sobreescribir el guardado con este nombre?" msgid "Game saved successfully." msgstr "Partida grabada con éxito." msgid "There was an issue during saving." msgstr "" msgid "" "Are you sure you want to load a new game? (Your current game will be lost.)" msgstr "" "¿Estás seguro de que quieres cargar una nueva partida? (Tu partida actual se " "perderá.)" msgid "Try looking on land!!!" msgstr "" msgid "" "Searching for the Ultimate Artifact is fruitless. Your hero could not carry " "it even if he found it - all his artifact slots are full." msgstr "" msgid "Digging for artifacts requires a whole day, try again tomorrow." msgstr "Excavar artefactos requiere un día entero, inténtalo mañana." msgid "" "After spending many hours digging here, you have uncovered the %{artifact}." msgstr "" "Desoyñes de muchas horas cabando aquí, al fin has desenterrado %{artifact}." msgid "Congratulations!" msgstr "¡Enhorabuena!" msgid "Nothing here. Where could it be?" msgstr "No hay nada aquí. ¿Dónde puede estar?" msgid "Try searching on clear ground." msgstr "Prueba buscando en terreno despejado." msgid "A miniature view of the known world. Left click to move viewing area." msgstr "" "Una vista en minatura del mundo explorado. Cliquea con el botón izquierdo " "para ver dicha área." msgid "Month: %{month} Week: %{week}" msgstr "Mes: %{month} Semana: %{week}" msgid "Day: %{day}" msgstr "Día: %{day}" msgid "" "You find a small\n" "quantity of %{resource}." msgstr "Encuentras una pequeña cantidad de %{resource}." msgid "Status Window" msgstr "Ventana de estado" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date." msgstr "" "Esta ventana muestra información de la situación de tu héroe o reino y " "muestra la fecha." msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date. Left click here to cycle through these windows." msgstr "" "Esta ventana muestra información de la situación de tu héroe o reino y " "muestra la fecha. Cliquea en el botón izquierdo para alternar entre las " "ventanas." msgid "" "This lets you change player starting positions and colors. A particular " "color will always start in a particular location. Some positions may only be " "played by a computer player or only by a human player." msgstr "" "Te permite cambiar las posición inicial y los colores. Cada color siempre " "comenzará en una situación precisa. Algunas posiciones sólo puede ser " "jugadas por un jugador humano o uno virtual." msgid "" "This lets you change the class of a player. Classes are not always " "changeable. Depending on the scenario, a player may receive additional towns " "and/or heroes not of their primary alignment." msgstr "" "Te permite cambiar la raza de un jugador. No siempre se pueden cambiar las " "razas. Dependiendo del escenario, un jugador puede recibir ciudades y/o " "héroes adicionales no pertenecientes a su primer alineamiento." msgid "Handicap" msgstr "Handicap" # msgid "" "This lets you change the handicap of a particular player. Only humans may be " "handicapped. Handicapped players start with fewer resources and earn 15 or " "30% fewer resources per turn for mild and severe handicaps, respectively." msgstr "" msgid "%{color} player" msgstr "%{color} jugador" msgid "No Handicap" msgstr "" msgid "No special restrictions on start resources and earning them per turn." msgstr "" msgid "Mild Handicap" msgstr "" # msgid "" "Mild handicapped players start with fewer resources and earn 15% fewer " "resources per turn." msgstr "" msgid "Severe Handicap" msgstr "" # msgid "" "Severe handicapped players start with fewer resources and earn 30% fewer " "resources per turn." msgstr "" msgid "View %{skill} Info" msgstr "Ver información de %{skill}" msgid "Keyboard|123" msgstr "123" msgid "Keyboard|SPACE" msgstr "ESPACIO" msgid "Keyboard|ABC" msgstr "ABC" msgid "Kingdom Income" msgstr "Ingresos del reino" msgid "Kingdom Income per day." msgstr "Ingresos del Reino por día." #, fuzzy msgid "For every lighthouse controlled, your ships will move further each day." msgstr "" "El faro está ahora bajo tu control, todos tus barcos podrán recorrer mayor " "distancia por turno." msgid "English" msgstr "" msgid "French" msgstr "" msgid "Polish" msgstr "" msgid "German" msgstr "" msgid "Russian" msgstr "" msgid "Italian" msgstr "" msgid "Czech" msgstr "" msgid "Norwegian" msgstr "" msgid "Belarusian" msgstr "" msgid "Bulgarian" msgstr "" msgid "Ukrainian" msgstr "" msgid "Romanian" msgstr "" msgid "Spanish" msgstr "Español" msgid "Swedish" msgstr "" msgid "Portuguese" msgstr "" msgid "Turkish" msgstr "" msgid "Dutch" msgstr "" msgid "Hungarian" msgstr "" msgid "Danish" msgstr "" msgid "Slovak" msgstr "" msgid "Vietnamese" msgstr "" msgid ", FPS: " msgstr "" msgid "%{object} robber" msgstr "Ladrón de %{object}" msgid "%{object} raided" msgstr "%{object} asaltada" msgid "Ector" msgstr "Ector" msgid "Gwenneth" msgstr "Gwenneth" msgid "Sir Gallant" msgstr "Sir Gallant" msgid "Tyro" msgstr "Tyro" msgid "Dimitry" msgstr "Dimitry" msgid "Ruby" msgstr "Ruby" msgid "Crag Hack" msgstr "Crag Hack" msgid "Fineous" msgstr "Fineous" msgid "Jezebel" msgstr "Jezebel" msgid "Atlas" msgstr "Atlas" msgid "Ergon" msgstr "Ergon" msgid "Jaclyn" msgstr "Jaclyn" msgid "Gem" msgstr "Gem" msgid "Natasha" msgstr "Natasha" msgid "Carlawn" msgstr "Carlawn" msgid "Luna" msgstr "Luna" msgid "Arie" msgstr "Arie" msgid "Barok" msgstr "Barok" msgid "Kastore" msgstr "Katore" msgid "Falagar" msgstr "Falagar" msgid "Dawn" msgstr "Dawn" msgid "Flint" msgstr "Flint" msgid "Halon" msgstr "Halon" msgid "Myra" msgstr "Myra" msgid "Myrini" msgstr "Myrini" msgid "Wilfrey" msgstr "Wilfrey" msgid "Mandigal" msgstr "Mandigal" msgid "Sarakin" msgstr "Sarakin" msgid "Charity" msgstr "Charity" msgid "Darlana" msgstr "Darlana" msgid "Ranloo" msgstr "Ranloo" msgid "Rialdo" msgstr "Rialdo" msgid "Zam" msgstr "Zam" msgid "Zom" msgstr "Zom" msgid "Celia" msgstr "Celia" msgid "Roxana" msgstr "Roxana" msgid "Lord Corlagon" msgstr "Lord Corlagon" msgid "Lord Halton" msgstr "Lord Halton" msgid "Sister Eliza" msgstr "Hermana Eliza" msgid "Brother Brax" msgstr "Hermano Brax" msgid "Dainwin" msgstr "Dainwin" msgid "Joseph" msgstr "Joseph" msgid "Mog" msgstr "Mog" msgid "Solmyr" msgstr "Solmyr" msgid "Ceallach" msgstr "Ceallach" msgid "Drakonia" msgstr "Drakonia" msgid "Elderian" msgstr "Elderian" msgid "Gallavant" msgstr "Gallavant" msgid "Jarkonas" msgstr "Jarkonas" msgid "Martine" msgstr "Martine" msgid " gives you maximum morale" msgstr "" msgid " gives you maximum luck" msgstr "" msgid "You cannot pick up this artifact, you already have a full load!" msgstr "No puedes recoger este artefacto, ¡tu inventario está lleno!" msgid "To cast spells, you must first buy a spell book for %{gold} gold." msgstr "" "Para lanzar hechizos primero has de comprar un libro de hechizos por %{gold} " "monedas de oro." msgid "Unfortunately, you seem to be a little short of cash at the moment." msgstr "" "Desafortunadamente, parece que andas un poco justo de dinero en estos " "momentos." msgid "Do you wish to buy one?" msgstr "¿Quiéres comprar uno?" msgid "%{count} / day" msgstr "%{count} / día" msgid "one" msgstr "uno" msgid "two" msgstr "dos" msgid "A whirlpool engulfs your ship. Some of your army has fallen overboard." msgstr "" "Un remolino engulle tu nave. Parte de tu ejército ha caído por la borda." msgid "Insulted by your refusal of their offer, the monsters attack!" msgstr "¡Insultados por la negativa a su oferta, los monstruos te atacan!" msgid "" "The %{monster}, awed by the power of your forces, begin to scatter.\n" "Do you wish to pursue and engage them?" msgstr "" "Ante la superioridad de tus fuerzas, el ejército de %{monster} comienza a " "dispersarse.\n" "¿Deseas proceder a su persecución y entablar combate?" msgid "Ransacking an enemy camp, you discover a hidden cache of treasures." msgstr "" "Saqueando un campamento enemigo descubres un depósito oculto con tesoros." msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with these resources, " "come back next week for more.\"" msgstr "" "El guarda del molino anuncia:\n" "\"Mi Señor, he estado trabajando muy duro para proporcionarte estos " "recursos, vuelve la próxima semana a por más.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there are no resources currently available. Please try " "again next week.\"" msgstr "" "El guarda del molino anuncia:\n" "\"Mi Señor, lo siento, pero no hay recursos disponibles en este momento. Por " "favor, vuelve a intentarlo la próxima semana.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with this gold, come " "back next week for more.\"" msgstr "" "El guarda del molino anuncia:\n" "\"Mi Señor, he estado trabajando muy duro para proporcionarte este oro, " "vuelve la próxima semana a por más.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there is no gold currently available. Please try again " "next week.\"" msgstr "" "El guarda del molino anuncia:\n" "\"Mi Señor, lo siento, no hay oro disponible en este momento. Por favor, " "vuelve a intentarlo la próxima semana.\"" msgid "" "You've found an abandoned lean-to.\n" "Poking about, you discover some resources hidden nearby." msgstr "" "Has encontrado un alpendre abandonado.\n" "Dando una vuelta a su alrededor, descubres cerca algunos recursos ocultos." msgid "The lean-to is long abandoned. There is nothing of value here." msgstr "El alpendre hace tiempo que fue abandonado. No hay nada de valor aquí." msgid "" "You catch a leprechaun foolishly sleeping amidst a cluster of magic " "mushrooms.\n" "In exchange for his freedom, he guides you to a small pot filled with " "precious things." msgstr "" "Atrapas un leprechaun que dormía descuidado entre unas setas mágicas.\n" "A cambio de su libertad, te guía a una pequeña olla llena de objetos " "preciosos." msgid "" "You've found a magic garden, the kind of place that leprechauns and faeries " "like to cavort in, but there is no one here today.\n" "Perhaps you should try again next week." msgstr "" "Has encontrado un jardín mágico, el tipo de sitio en el que suelen estar " "leprechauns y hadas, pero no hay ninguno hoy aquí.\n" "A lo mejor debieras de intentarlo de nuevo la próxima semana." msgid "You come upon the remains of an unfortunate adventurer." msgstr "Te topas con los restos de un desafortunado aventurero." msgid "Treasure" msgstr "Tesoro" msgid "Searching through the tattered clothing, you find the %{artifact}." msgstr "Mientras buscabas entre las ropas sucias, encontraste %{artifact}." msgid "Searching through the tattered clothing, you find nothing." msgstr "Mientras buscabas entre las ropas sucias, encontraste %{artifact}." msgid "" "You come across an old wagon left by a trader who didn't quite make it to " "safe terrain." msgstr "" msgid "Unfortunately, others have found it first, and the wagon is empty." msgstr "" "Desafortunadamente, otros han estado aquí antes, y el vagón está vacío." msgid "Searching inside, you find the %{artifact}." msgstr "" msgid "Inside, you find some of the wagon's cargo still intact." msgstr "" msgid "You search through the flotsam, and find some wood and some gold." msgstr "" "Buscas entre los restos del naufragio y encuentras algo de de madera y oro." msgid "You search through the flotsam, and find some wood." msgstr "Buscas entre los restos del naufragio y encuentras algo de madera." msgid "You search through the flotsam, but find nothing." msgstr "Buscas entre los restos del naufragio pero no encuentras nada." msgid "Shrine of the 1st Circle" msgstr "Capilla del Primer Círculo" msgid "" "You come across a small shrine attended by a group of novice acolytes.\n" "In exchange for your protection, they agree to teach you a simple spell - " "'%{spell}'." msgstr "" "Te encuentras con un pequeño santuario atendido por un grupo de acólitos " "novicios. A cambio de tu protección aceptan enseñarte un hechizo sencillo: " "'%{spell}'." msgid "Shrine of the 2nd Circle" msgstr "Altar del Segundo Círculo" msgid "" "You come across an ornate shrine attended by a group of rotund friars.\n" "In exchange for your protection, they agree to teach you a spell - " "'%{spell}'." msgstr "" "Te encuentras con un santuario ornamentado atendido por un grupo de " "voluminosos frailes. A cambio de tu protección aceptan enseñarte un hechizo: " "'%{spell}'." msgid "Shrine of the 3rd Circle" msgstr "Altar del Tercer Círculo" msgid "" "You come across a lavish shrine attended by a group of high priests.\n" "In exchange for your protection, they agree to teach you a sophisticated " "spell - '%{spell}'." msgstr "" "Te encuentras con un esplendido santuario atendido por un grupo de sumo " "sacerdotes. A cambio de tu protección aceptan enseñarte un sofisticado " "hechizo: '%{spell}'." msgid "" "\n" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "\n" "Desafortunadamente, no tienes la sabiduría necesaria para comprender el " "hechizo, y eres incapaz de aprenderlo." msgid "" "\n" "Unfortunately, you already have knowledge of this spell, so there is nothing " "more for them to teach you." msgstr "" "\n" "Desafortunadamente, ya conocías ese hechizo, así que no hay nada más que te " "pueda enseñar." msgid "" "\n" "Unfortunately, you have no Magic Book to record the spell with." msgstr "" "\n" "Desafortunadamente, no tienes un Libro de Hechizos donde registrar este " "hechizo." msgid "" "You approach the hut and observe a witch inside studying an ancient tome on " "%{skill}.\n" "\n" msgstr "" "Te aproximas a la cabaña y observas dentro a una bruja estudiando un antiguo " "tomo de %{skill}.\n" "\n" msgid "" "As you approach, she turns and focuses her one glass eye on you.\n" "\"You already know everything you deserve to learn!\" the witch screeches. " "\"NOW GET OUT OF MY HOUSE!\"" msgstr "" "Al aproximarte, ella se gira y enfoca su único ojo de cristal en hacia ti.\n" "\"¡Ya sabes todo lo que merecías aprender!\" chilla la bruja. \"¡AHORA, " "FUERA DE MI CASA!\"" msgid "" "As you approach, she turns and speaks.\n" "\"You already know that which I would teach you. I can help you no further.\"" msgstr "" "Al aproximarte, ella se gira y habla.\n" "\"Ya sabes lo que yo te enseñaría. Ya no puedo ayudarte más.\"" msgid "" "An ancient and immortal witch living in a hut with bird's legs for stilts " "teaches you %{skill} for her own inscrutable purposes." msgstr "" "Una antigua e inmortal bruja que vive en una cabaña rodeada de zancos de " "aves te enseña %{skill}, para sus inescrutables propósitos." msgid "As you drink the sweet water, you gain luck for your next battle." msgstr "" "Mientras bebes la dulce agua adquieres suerte para tu siguiente batalla." msgid "You drink from the enchanted fountain, but nothing happens." msgstr "Bebes de una fuente encantada pero nada sucede." msgid "You enter the faerie ring, but nothing happens." msgstr "Entras en el anillo místico de hadas pero nada sucede." msgid "" "Upon entering the mystical faerie ring, your army gains luck for its next " "battle." msgstr "" "Justo al entrar el anillo místico de las hadas tu ejército adquiere suerte " "para su próxima batalla." msgid "" "You've found an ancient and weathered stone idol.\n" "It is supposed to grant luck to visitors, but since the stars are already " "smiling upon you, it does nothing." msgstr "" "Has encontrado un antiguo y erosionado ídolo de piedra.\n" "Se supone que otorga suerte a los visitantes, pero dado que las estrellas ya " "te sonríen, no ocurre nada." msgid "" "You've found an ancient and weathered stone idol.\n" "Kissing it is supposed to be lucky, so you do. The stone is very cold to the " "touch." msgstr "" "Has encontrado un antiguo y erosionado ídolo de piedra.\n" "Besarlo se supone que da suerte, así que lo haces. La piedra esta muy fría " "al tacto." msgid "The mermaids silently entice you to return later and be blessed again." msgstr "" "Las sirenas en silencio te engatusan para volver más tarde y ser bendecido " "de nuevo." msgid "" "The magical, soothing beauty of the Mermaids reaches you and your crew.\n" "Just for a moment, you forget your worries and bask in the beauty of the " "moment.\n" "The mermaids charms bless you with increased luck for your next combat." msgstr "" "La mágica y calmante belleza de las Sirenas te alcanza a ti y a tu " "tripulación.\n" "Por un momento olvidas tus preocupaciones y te deleitas con su belleza.\n" "Los encantos de las Sirenas te bendicen e incrementan tu suerte para el " "próximo combate." msgid "" "You come upon the pyramid of a great and ancient king.\n" "You are tempted to search it for treasure, but all the old stories warn of " "fearful curses and undead guardians.\n" "Will you search?" msgstr "" "Te aproximas a la pirámide de un antiguo y gran rey.\n" "Te tienta buscar en ella su tesoro, pero todas las historias te advierten de " "las espantosas maldiciones y los guardianes nomuertos.\n" "¿Vas a buscar?" msgid "" "Upon defeating the monsters, you decipher an ancient glyph on the wall, " "telling the secret of the spell - '" msgstr "" "Al derrotar a los monstruos, descrifras un antiguo jeroglífico de la pared " "que explica el secreto del hechizo - '" msgid "Unfortunately, you have no Magic Book to record the spell with." msgstr "" "Desafortunamente, no posees Libro de Hechizos donde registrar este hechizo." msgid "" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "Desafortunadamente, no tienes el conocimiento necesario para comprender el " "hechizo y no eres capaz de aprenderlo." msgid "" "You come upon the pyramid of a great and ancient king.\n" "Routine exploration reveals that the pyramid is completely empty." msgstr "" "Te aproximas a la pirámide de un antiguo y gran rey.\n" "Exploraciones rutinarias te indican que la pirámide está completamente vacía." msgid "" "A drink at the well is supposed to restore your spell points, but you are " "already at maximum." msgstr "Al beber pozo se restablece tu Maná, pero ya estás al máximo." msgid "A second drink at the well in one day will not help you." msgstr "Beber dos veces del pozo en el mismo día no te ayudará." msgid "A drink from the well has restored your spell points to maximum." msgstr "Al beber del pozo se han restablecido tus puntos de Maná al máximo." msgid "" "\"I'm sorry sir,\" The leader of the soldiers says, \"but you already know " "everything we have to teach.\"" msgstr "" "\"Lo siento\" dice el lider de los solados, \"pero ya has sabeis todo lo que " "nosotros podemos enserñarte\"." msgid "The soldiers living in the fort teach you a few new defensive tricks." msgstr "" "Los soldados que viven en el fuerte te enseñan algunos trucos defensivos " "nuevos." msgid "" "You've come upon a mercenary camp practicing their tactics. \"You're too " "advanced for us,\" the mercenary captain says. \"We can teach nothing more.\"" msgstr "" "Te has topado con un campamento de mercenarios practicando sus estrategias. " "\"Eres demasiado avanzado para nosotros\", dice el capitán de los " "mercenarios. \"No podemos enseñarte nada más\"." msgid "" "You've come upon a mercenary camp practicing their tactics. The mercenaries " "welcome you and your troops and invite you to train with them." msgstr "" "Te has topado con un campamento de mercenarios practicando sus estrategias. " "Los mercenarios te dan la bienvenida a ti y a tus tropas y os invitan a " "entrenar con ellos." msgid "\"Go 'way!\", the witch doctor barks, \"you know all I know.\"" msgstr "¡Vete! grita el curandero, \"sabes todo lo que yo sé\"" msgid "" "An Orcish witch doctor living in the hut deepens your knowledge of magic by " "showing you how to cast stones, read portents, and decipher the intricacies " "of chicken entrails." msgstr "" "Una Curandera Orca que vive en la choza profundiza tu conocimiento de la " "magia enseñándote a: invocar piedras, leer augurios y descifrar las " "complejidades en las entrañas de los pollos." msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, the Druids turn you away, indicating they have nothing " "new to teach you." msgstr "" "Encuentras un grupo de Druidas rindiendo culto a uno de sus extraños " "edificios de piedra. Los Druidas te apartan silenciosamente, indicando que " "no tienen nada nuevo que enseñarte." msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, they teach you new ways to cast spells." msgstr "" "Encuentras un grupo de Druidas rindiendo culto a uno de sus extraños " "edificios de piedra. Silenciosamente te enseñan nuevas formas de lanzar " "hechizos." msgid "" "You tentatively approach the burial ground of ancient warriors. Do you want " "to search the graves?" msgstr "" "Tímidamente te aproximas al cementerio de antiguos guerreros. ¿Deseas buscar " "en las tumbas?" msgid "" "You spend several hours searching the graves and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" "Después derrotar a los zombis y pasar varias horas buscando en las tumbas " "sin premio, la moral de tu ejercito se reduce por un hecho tan despreciable." msgid "Upon defeating the Zombies you search the graves and find something!" msgstr "" "¡Después de derrotar a los zombis entras en las tumbas y encuentras algo!" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the shipwreck?" msgstr "" "El casco putrefacto de una gran nave pirata chirría de manera inquietante " "contra las rocas.¿Deseas buscar entre los restos del naufragio?" msgid "" "You spend several hours sifting through the debris and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" "Después de derrotar a los fantamas y pasar varias horas tamizando sus restos " "sin premio la moral de tu ejercito se reduce por un acto tan despreciable." msgid "" "Upon defeating the Ghosts you sift through the debris and find something!" msgstr "¡Despúes de derrotar a los fantasmas tamizas sus restos sin premio!" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the ship?" msgstr "" "El casco putrefacto de una gran nave pirata chirría inquietantemente contra " "las rocas. ¿Deseas buscar en la nave?" msgid "" "Upon defeating the Skeletons you sift through the debris and find something!" msgstr "" "!Después de derrotar a los esqueletos tamizas sus restos y encuentras algo!" msgid "Your men spot a navigational buoy, confirming that you are on course." msgstr "" "Tus hombres descubren una boya de navegación, confirmando que estás en la " "ruta correcta." msgid "" "Your men spot a navigational buoy, confirming that you are on course and " "increasing their morale." msgstr "" "Tus hombres descubren una boya de navegación, confirmando que estás en la " "ruta correcta e incrementa su moral." msgid "" "The drink at the oasis is refreshing, but offers no further benefit. The " "oasis might help again if you fought a battle first." msgstr "" "Beber en el oasis es refrescante, pero no ofrece ningún beneficio adicional. " "El oasis te podrá ayudar otra vez si primero has luchado en una batalla." msgid "" "A drink at the oasis fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" "Al beber en el oasis se restablecen las fuerzas de tus tropas y se elevan " "sus espíritus. Puedes viajar un poco más hoy." msgid "" "The drink at the watering hole is refreshing, but offers no further benefit. " "The watering hole might help again if you fought a battle first." msgstr "" "Beber por esta grieta de la que fluye agua es refrescante pero no ofrece " "otros beneficios. La grieta te podría ayudar de nuevo si antes luchas en " "batalla." msgid "" "A drink at the watering hole fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" "Beber por esta grieta de la que fluye agua restablece las fuerzas de tus " "tropas y eleva sus espíritus. Puedes viajar un poco más hoy." msgid "" "It doesn't help to pray twice before a battle. Come back after you've fought." msgstr "" "No ayuda rezar dos veces antes de una batalla. Regresa después de que hayas " "luchado." msgid "A visit and a prayer at the temple raises the morale of your troops." msgstr "Una visita y una oración en el templo aumenta la moral de tus tropas." msgid "" "An old Knight appears on the steps of the gazebo. \"I am sorry, my liege, I " "have taught you all I can.\"" msgstr "" "Un anciano caballero aparece en los peldaños del cenador. \"Lo siento os he " "enseñado todo lo que puedo\"." msgid "" "An old Knight appears on the steps of the gazebo. \"My liege, I will teach " "you all that I know to aid you in your travels.\"" msgstr "" "Un anciano caballero aparece en los peldaños del cenador. \"Os enseñaré todo " "lo que sé para ayudaros en vuestros viajes\"." msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he says, \"I would give you an artifact as a reward, but " "you're all full.\"" msgstr "" "Has rescatado a un superviviente de un naufragio de una muerte segura en un " "océano olvidado. Agradecido, él dice, \"Yo te daría un artefacto como " "recompensa, pero no tienes espacio\"." msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he rewards you for your act of kindness by giving you the " "%{art}." msgstr "" "Arrastras a un superviviente de un naufragio, salvándolo de una muerte " "segura en un océano sin compasión. Agradecido, te recompensa por tu acto de " "bondad con %{art}" msgid "A leprechaun offers you the %{art} for the small price of %{gold} Gold." msgstr "" "Un Leprechaun te ofrece %{art} por el pequeño precio de %{gold} piezas de " "Oro." msgid "" "A leprechaun offers you the %{art} for the small price of %{gold} Gold and " "%{count} %{res}." msgstr "" "Un Leprechaun te ofrece %{art} por el precio de %{gold} piezas de Oro y " "%{count} %{res}." msgid "Do you wish to buy this artifact?" msgstr "¿Deseas comprar este artefacto?" msgid "" "You try to pay the leprechaun, but realize that you can't afford it. The " "leprechaun stamps his foot and ignores you." msgstr "" "Intentas pagar al leprechaun, pero te das cuenta de que no te lo puedes " "permitir. El leprechaun patea el suelo y te ignora." msgid "" "Insulted by your refusal of his generous offer, the leprechaun stamps his " "foot and ignores you." msgstr "" "Insultado por el rechazo de su generosa oferta, el Leprechaun te da una " "patada y te ignora." msgid "You've found the artifact: " msgstr "Has encontrado el artefacto: " msgid "" "You've found the humble dwelling of a withered hermit. The hermit tells you " "that he is willing to give the %{art} to the first wise person he meets." msgstr "" "Has encontrado la humilde morada de un anciano eremita. El eremita te dice " "que está dispuesto a darle el %{art} a la primera persona sabia que se " "encuentre." msgid "" "You've come across the spartan quarters of a retired soldier. The soldier " "tells you that he is willing to pass on the %{art} to the first true leader " "he meets." msgstr "" "Llegas a la espartana morada de un soldado retirado. El soldado te dice que " "está dispuesto a darle el %{art} al primer líder auténtico que se encuentre." msgid "" "You've encountered a strange person with a hat and an owl on it. He tells " "you that he is willing to give %{art} if you have %{skill}." msgstr "" msgid "" "You come upon an ancient artifact. As you reach for it, a pack of Rogues " "leap out of the brush to guard their stolen loot." msgstr "" "Te encuentras un artefacto antiguo. Al ir a recogerlo, un grupo de Pícaros " "saltan de entre la maleza para proteger su botín." msgid "" "Through a clearing you observe an ancient artifact. Unfortunately, it's " "guarded by a nearby %{monster}. Do you want to fight the %{monster} for the " "artifact?" msgstr "" "A través de un claro, observas un artefacto antiguo. Desafortunadamente, " "está guardado por un %{monster} cercano. ¿Quiers combatir el %{monster} por " "el artefacto?" msgid "Victorious, you take your prize, the %{art}." msgstr "Victorioso, tomas tu premio: %{art}" msgid "" "Discretion is the better part of valor, and you decide to avoid this fight " "for today." msgstr "" "La madurez es la mejor parte del valor, así pues decides evitar esta lucha " "hoy." msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold pieces." msgstr "" "Después de varias horas intentando sacar el cofre del mar, lo abres y " "encentras %{gold} piezas de oro." msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold and the %{art}." msgstr "" "Después de pasar horas intentando sacar el cofre del mar, lo abres y " "encuentras %{gold} piezas de oro y %{art}" msgid "" "After spending hours trying to fish the chest out of the sea, you open it, " "only to find it empty." msgstr "" "Después de pasar horas intentando sacar el cofre del mar, lo abres y resulta " "estar vacío." msgid "" "After scouring the area, you fall upon a hidden treasure cache. You may take " "the gold or distribute the gold to the peasants for experience. Do you wish " "to keep the gold?" msgstr "" "Después de dar una batida por el área, das con un tesoro escondido. Puedes " "quedarte con el oro o distribuirlo entre los campesinos a cambio de " "experiencia. ¿Deseas quedarte con el oro?" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "%{gold} gold pieces." msgstr "" "Después de dar una batida por el área, das con un cofre escondido que " "contiene %{gold} piezas de oro." msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "ancient artifact %{art}." msgstr "" "Después de dar una batida por el área, das con un cofre escondido que " "contiene un antiguo artefacto: %{art}" msgid "" "You stumble upon a dented and tarnished lamp lodged deep in the earth. Do " "you wish to rub the lamp?" msgstr "" "Te tropiezas con una lámpara abollada que está depositada en lo profundo de " "la tierra. ¿Deseas frotar la lámpara?" msgid "" "You have taken control of the local Alchemist shop. It will provide you with " "%{count} unit of Mercury per day." msgstr "" "Has tomado el control de la casa del Alquimista local. Te proveerá con " "%{count} unidades de mercurio al día." msgid "" "You gain control of a sawmill. It will provide you with %{count} units of " "wood per day." msgstr "" "Has tomado el control de un aserradero. Te proveerá con %{count} unidades de " "madera al día." msgid "" "You gain control of an ore mine. It will provide you with %{count} units of " "ore per day." msgstr "" "Has tomado el control de una mina de hierro. Te proveerá con %{count} " "unidades de mineral al día." msgid "" "You gain control of a sulfur mine. It will provide you with %{count} unit of " "sulfur per day." msgstr "" "Has tomado el control de una mina de azufre. Te proveerá con %{count} " "unidades de azufre al día." msgid "" "You gain control of a crystal mine. It will provide you with %{count} unit " "of crystal per day." msgstr "" "Has tomado el control de una mina de cristal. Te proveerá con %{count} " "unidades de cristal al día." msgid "" "You gain control of a gem mine. It will provide you with %{count} unit of " "gems per day." msgstr "" "Has tomado el control de una mina de gemas. Te proveerá con %{count} " "unidades de gemas al día." msgid "" "You gain control of a gold mine. It will provide you with %{count} gold per " "day." msgstr "" "Has tomado el control de una mina de oro. Te proveerá con %{count} piezas de " "oro al día." msgid "" "The lighthouse is now under your control, and all of your ships will now " "move further each day." msgstr "" "El faro está ahora bajo tu control, todos tus barcos podrán recorrer mayor " "distancia por turno." msgid "You gain control of a %{name}." msgstr "Tomas control de %{name}." msgid "" "You come upon an abandoned gold mine. The mine appears to be haunted. Do you " "wish to enter?" msgstr "" "Llegas a una mina de oro abandonada. La mina parece estar embrujada. " "¿Quieres entrar en ella?" msgid "You beat the Ghosts and are able to restore the mine to production." msgstr "" "Derrotas a los Fantasmas y eres capaz de restablecer la producción de la " "mina." msgid "" "A group of %{monster} with a desire for greater glory wish to join you. Do " "you accept?" msgstr "" "Un grupo de %{monster} con ansias de gloria desea unirse a tus tropas. " "¿Aceptas?" msgid "As you approach the dwelling, you notice that there is no one here." msgstr "Al aproximarte a la morada te percatas de que ya no hay nadie allí." msgid "" "You search the ruins, but the Medusas that used to live here are gone. " "Perhaps there will be more next week." msgstr "" "Buscas en las ruinas, pero las Medusas que solían vivir aquí se han ido. " "Quizás habrá más la próxima semana." msgid "" "You've found some Medusas living in the ruins. They are willing to join your " "army for a price. Do you want to recruit Medusas?" msgstr "" "Has encontrado algunas Medusas viviendo en las Ruinas. Están deseosas de " "servir como mercenarias por un buen precio. ¿Deseas reclutar Medusas?" msgid "" "You've found a Sprite Tree City. Unfortunately, none of the Sprites living " "there wish to join an army. Maybe next week." msgstr "" "Has encontrado la Ciudad de los Ãrboles de las Hadas. Desafortunadamente " "ninguna desea unirse a tu ejército. Quizás la próxima semana." msgid "" "Some of the Sprites living in the tree city are willing to join your army " "for a price. Do you want to recruit Sprites?" msgstr "" "Alguna de las Hadas que viven en la ciudad están deseosas de unirse como " "mercenarias a tu ejército por un precio. ¿Quieres reclutar Hadas?" msgid "" "A colorful Rogues' wagon stands empty here. Perhaps more Rogues will be here " "later." msgstr "" "Un colorido vagón de Bandidos se encuentra vacio ahora mismo. Quizás estarán " "aquí más tarde." msgid "" "Distant sounds of music and laughter draw you to a colorful wagon housing " "Rogues. Do you wish to have any Rogues join your army?" msgstr "" "Sonidos de música y risas distantes te guían a una colorida caravana de " "Bandidos. ¿Quieres que se una algún Bandido a tu ejército?" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. The " "tents are unoccupied. Perhaps more Nomads will be here later." msgstr "" "Te intriga un grupo de destartaladas tiendas, que se mueven con el viento. " "Las tiendas están vacías. Quizás haya mas Nómadas mas adelante." msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. Do you " "wish to have any Nomads join you during your travels?" msgstr "" "Te intriga un grupo de destartaladas tiendas, que se mueven con el viento. " "¿Quieres que algún Nómada te acompañe durante tus viajes?" msgid "The pit of mud bubbles for a minute and then lies still." msgstr "La fosa de barro burbujea por un minuto y entonces permanece en calma." msgid "" "As you approach the bubbling pit of mud, creatures begin to climb out and " "position themselves around it. In unison they say: \"Mother Earth would like " "to offer you a few of her troops. Do you want to recruit Earth Elementals?\"" msgstr "" "Cuando le aproximas a la fosa de barro que burbujea, unas critaturas surgen " "y te rodean. Dicen al unísono: \"La Madre Tierra le gustaría ofrecerte " "algunas de sus tropas. ¿Quieres reclutar Elementales de la Tierra?" msgid "You enter the structure of white stone pillars, and find nothing." msgstr "" "Entras en la estructura de blancos pilares de piedra y no encuentras nada." msgid "" "White stone pillars support a roof that rises up to the sky. As you enter " "the structure, the dead air of the outside gives way to a whirling gust that " "almost pushes you back out. The air current materializes into a barely " "visible form. The creature asks, in what can only be described as a loud " "whisper: \"Why have you come? Are you here to call upon the forces of the " "air?\"" msgstr "" "Los pilares blancos de piedra soportan un tejado que asciende hasta el " "cielo. Cuando entras en la estructura, el aire putrefacto del exterior envía " "una rafaga que casi te hace retroceder. La corriente de aire se materializa " "en una forma apenas visible. La criatura pregunta, en lo que sólo puede " "considerarse un susurro. ¿Por qué has venido?, ¿Estás aquí para invocar a " "las fuerzas del Aire?" msgid "No Fire Elementals approach you from the lava pool." msgstr "Ningun Elemental del Fuego se aproxima a ti desde el charco de lava." msgid "" "Beneath a structure that serves to hold in heat, Fire Elementals move about " "in a fiery pool of molten lava. A group of them approach you and offer their " "services. Would you like to recruit Fire Elementals?" msgstr "" "Bajo la estructura incandescente que sirve de sujección, Elementales del " "Fuego se mueven por una ardiente charca de lava fundida. Un grupo de ellos " "se aproxima a ti y te ofrece sus servicios. ¿Te gustaría reclutar " "Elementales de Fuego?" msgid "A face forms in the water for a moment, and then is gone." msgstr "Una cara se forma en el agua por un momento y entonces se va." msgid "" "Crystalline structures cast shadows over a small reflective pool of water. " "You peer into the pool, and a face that is not your own peers back. It asks: " "\"Would you like to call upon the powers of water?\"" msgstr "" "Formas cristalinas proyectan sombras que se reflejan en una pequeña charca " "de agua. Miras con detenimiento la charca y una cara que no es la tuya te " "devuelve la mirada. Dice: \"¿Te gustaría invocar los poderes del agua?" msgid "This burial site is deathly still." msgstr "En este lugar de enterramiento se respira una quietud de muerte." msgid "" "Restless spirits of long dead warriors seeking their final resting place " "offer to join you in hopes of finding peace. Do you wish to recruit ghosts?" msgstr "" "Los espíritus sin descanso de unos guerreros muertos en el pasado buscan su " "lugar de definitivo descanso, ofrecen incorporase a tus fuezas con la " "esperanza de encontrar la paz. ¿Deseas reclutar Fantasmas?" msgid "" "The City of the Dead is empty of life, and empty of unlife as well. Perhaps " "some undead will move in next week." msgstr "" "La Ciudad de los Muertos está vacía de vida y de muertos vivientes también. " "Quizás algunos nomuertos vendrán la próxima semana." msgid "" "Some Liches living here are willing to join your army for a price. Do you " "want to recruit Liches?" msgstr "" "Algunos Liches que moran aquí están deseando unirse a tu ejército por una " "buena suma. ¿Quieres reclutar Liches?" msgid "" "You've found the ruins of an ancient city, now inhabited solely by the " "undead. Will you search?" msgstr "" "Has encontrado las ruinas de una antigua ciudad, ahora habitada sólamente " "por nomuertos. ¿Quieres investigar?" msgid "" "Some of the surviving Liches are impressed by your victory over their " "fellows, and offer to join you for a price. Do you want to recruit Liches?" msgstr "" "Algunas Liches, impresionadas por tu victoria sobre sus camaradas, se " "ofrecen como mercenarias. ¿Quieres reclutar liches?" msgid "" "You've found one of those bridges that Trolls are so fond of living under, " "but there are none here. Perhaps there will be some next week." msgstr "" "Has encontrado uno de esos puentes debajo de los cuales los Trolls están tan " "a gusto viviendo. Quizás la próxima semana habrá algunos." msgid "" "Some Trolls living under a bridge are willing to join your army, but for a " "price. Do you want to recruit Trolls?" msgstr "" "Algunos Trolls que viven bajo un puente están deseosos de unirse a tu " "ejercitgo por un precio. ¿Quieres reclutar Trolls?" msgid "Trolls living under the bridge challenge you. Will you fight them?" msgstr "" "Los Trolls que viven bajo el puente te desafían. ¿Lucharás contra ellos?" msgid "" "A few Trolls remain, cowering under the bridge. They approach you and offer " "to join your forces as mercenaries. Do you want to buy any Trolls?" msgstr "" "Unos cuantos Trolls permanecen asustados bajo el Puente. Se aproximan y te " "ofrecen unirse a tus fuerzas como mercenarios. ¿Quieres reclutar algunos " "Trolls?" msgid "" "The Dragon city has no Dragons willing to join you this week. Perhaps a " "Dragon will become available next week." msgstr "" "En la Ciudad del Dragón no hay ningún Dragón que desee unirse a tus filas " "esta semana. Quizás un dragón estará disponible la próxima semana." msgid "" "The Dragon city is willing to offer some Dragons for your army for a price. " "Do you wish to recruit Dragons?" msgstr "" "La Ciudad del Dragón está deseando ofrecer algunos Dragones a tu ejercito " "por un precio. ¿Deseas reclutar Dragones?" msgid "" "You stand before the Dragon City, a place off-limits to mere humans. Do you " "wish to violate this rule and challenge the Dragons to a fight?" msgstr "" "Estás ante la Ciudad del Dragón, un lugar fuera de los límites de los " "humanos. ¿Deseas romper esta norma y desafiar a los Dragones en combate?" msgid "" "Having defeated the Dragon champions, the city's leaders agree to supply " "some Dragons to your army for a price. Do you wish to recruit Dragons?" msgstr "" "Habiendo desafíado a los campeones de los Dragones, el Lider de la ciudad " "accede a sumistrar algunos Dragones a tu ejército por un precio. ¿Deseas " "reclutar Dragones?" msgid "From the observation tower, you are able to see distant lands." msgstr "Desde la torre de observación, puedes ver tierras lejanas." msgid "" "The spring only refills once a week, and someone's already been here this " "week." msgstr "" "El manaltial sólo se rellena una vez a la semana y alguien ya ha estado aquí " "esta semana." msgid "" "A drink at the spring is supposed to give you twice your normal spell " "points, but you are already at that level." msgstr "" "Al beber del manantial se duplican tus puntos normales de Maná, pero ya " "estás a ese nivel." msgid "" "A drink from the spring fills your blood with magic! You have twice your " "normal spell points in reserve." msgstr "" "¡Beber del manantial colma tu sangre con magia! Duplicas la reserva de tus " "puntos normales de Maná." msgid "" "Recognizing you, the butler refuses to admit you. \"The master,\" he says, " "\"will not see the same student twice.\"" msgstr "" "El mayordomo te reconoce y rechaza admitirte. \"El Maestro\" dice, \"no " "atenderá al mismo estudiante dos veces\"." msgid "" "The butler admits you to see the master of the house. He trains you in the " "four skills a hero should know." msgstr "" "El Mayordomo permite que veas al Maestro de la Casa. Él te adiestra en las " "cuatro habilidades que todo héroe debería conocer." msgid "" "The butler opens the door and looks you up and down. \"You are neither " "famous nor diplomatic enough to be admitted to see my master,\" he sniffs. " "\"Come back when you think yourself worthy.\"" msgstr "" "El Mayordomo abre la puerte y te mira de arriba abajo. \"Tú no eres ni " "famoso ni suficientemente letrado para llegar a ver a mi maestro\" dice con " "desdén. \"Vuelve cuando te consideres suficientemente válido\"." msgid " and " msgstr " y " msgid "" "All of the %{monsters} you have in your army have been trained by the battle " "masters of the fort. Your army now contains %{monsters2}." msgstr "" "Tus %{monsters} han sido entrenados por los Maestros en Batalla del fuerte. " "Tus ejército ahora cuenta con %{monsters2}" msgid "" "An unusual alliance of Ogres, Orcs, and Dwarves offer to train (upgrade) any " "such troops brought to them. Unfortunately, you have none with you." msgstr "" "Una inusual alianza entre Orcos, Ogros y Enanos te ofrece mejorar ese clase " "de tropas si se las traes. Desafortunadamente no tienes ninguno de ellos " "contigo." msgid "All of your %{monsters} have been upgraded into %{monsters2}." msgstr "Tus %{monsters} han mejorado a %{monsters2}." msgid "" "A blacksmith working at the foundry offers to convert all Pikemen and " "Swordsmen's weapons brought to him from iron to steel. He also says that he " "knows a process that will convert Iron Golems into Steel Golems. " "Unfortunately, you have none of these troops in your army, so he can't help " "you." msgstr "" "El herrero que trabaja en la Fundición te ofrece convertir todas las armas " "de Piqueros y Espadachines a acero . También dice que conoce un proceso que " "convertirá Golems en Golems de Acero. Desafortunadamente no tienes esas " "tropas en tu ejército, por lo tanto no puede ayudarte." msgid "" "The captain looks at you with surprise and says:\n" "\"You already have all the maps I know about. Let me fish in peace now.\"" msgstr "" msgid "" "A retired captain living on this refurbished fishing platform offers to sell " "you maps of the sea he made in his younger days for 1,000 gold. Do you wish " "to buy the maps?" msgstr "" "Un Capitán retirado que vive de esta plataforma de pesca ofrece venderte " "mapas marítimos que hizo en sus días de juventud por 1.000 piezas de Oro. " "¿Deseas comprar los mapas?" msgid "" "The captain sighs. \"You don't have enough money, eh? You can't expect me " "to give my maps away for free!\"" msgstr "" "El Capitán suspira: \"No tienes suficiente dinero, ¿eh? No esperarás que te " "ofrezca mis mapas gratis." msgid "" "You come upon an obelisk made from a type of stone you have never seen " "before. Staring at it intensely, the smooth surface suddenly changes to an " "inscription. The inscription is a piece of a lost ancient map. Quickly you " "copy down the piece and the inscription vanishes as abruptly as it appeared." msgstr "" "Te topas con un obelisco hecho de un tipo de piedra que nunca has visto " "antes. Fijándote con atención, la lisa superficie de repente cambia su " "inscripción. La inscripción es un trozo de un antiguo mapa perdido. " "Rápidamente lo copias y la inscripción se esfuma tan abruptamente como " "apareció." msgid "You have already been to this obelisk." msgstr "Ya has habias estado en este obelisco." msgid "" "Upon your approach, the tree opens its eyes in delight. \"It is good to see " "you, my student. I hope my teachings have helped you.\"" msgstr "" "Al aproximarte, el Ãrbol abre sus ojos con deleite. \"Es bueno verte de " "nuevo, alumno mío. Espero que mis enseñanzas te hayan ayudado.\"" msgid "" "Upon your approach, the tree opens its eyes in delight. \"Ahh, an " "adventurer! Allow me to teach you a little of what I have learned over the " "ages.\"" msgstr "" "Al aproximarte, el Ãrbol abre sus ojos con deleite. \"Ahh, ¡un aventurero! " "Permíteme enseñarte un poco de lo que aprendido con los años.\"" msgid "Upon your approach, the tree opens its eyes in delight." msgstr "Al aproximarte, el Ãrbol abre sus ojos con deleite." msgid "" "\"Ahh, an adventurer! I will be happy to teach you a little of what I have " "learned over the ages for a mere %{count} %{res}.\"" msgstr "" msgid "(Just bury it around my roots.)" msgstr "(Simplemente entierra lo que te pido alredor de mis raices.)" msgid "Tears brim in the eyes of the tree." msgstr "Las lágrimas colman los ojos del Ãrbol." msgid "\"I need %{count} %{res}.\"" msgstr "" msgid "it whispers. (sniff) \"Well, come back when you can pay me.\"" msgstr "suspira .. sniff, \"Bien, regresa cuando puedas pagarme.\"" msgid "" "Nestled among the trees sits a blind seer. After you explain the intent of " "your journey, the seer activates his crystal ball, allowing you to see the " "strengths and weaknesses of your opponents." msgstr "" msgid "" "The entrance to the cave is dark, and a foul, sulfurous smell issues from " "the cave mouth. Will you enter?" msgstr "" "La entrada a la Cueva es oscura, un hediondo y sufuroso olor sale de la boca " "de la cueva. ¿Entrarás?" msgid "Except for evidence of a terrible battle, the cave is empty." msgstr "Excepto por la evidencia de una terrible batalla, la Cueva está vacía." msgid "" "You find a powerful and grotesque Demon in the cave. \"Today,\" it rasps, " "\"you will fight and surely die. But I will give you a choice of deaths. You " "may fight me, or you may fight my servants. Do you prefer to fight my " "servants?\"" msgstr "" "Encuentras un poderoso y grotesco Demonio en la cueva. \"Hoy\" brama " "\"lucharás y seguramente morirás\". Pero podrás elegir la muerte que gustes: " "puedes luchar contra mi o contra mis sirvientes. ¿Prefieres luchar contra " "mis sirvientes?" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points." msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points and " "%{count} gold." msgstr "" "¡El Demonio grita su desafío y ataca! Después de una pequeña y desesperada " "batalla, matas al monstruo y recibes %{exp} puntos de experiencia y %{count} " "piezas de oro." msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and find the %{art} in the back of the cave." msgstr "" "¡El Demonio grita su desafío y ataca! Después de una pequeña y desesperada " "batalla, das muerte al monstruo y encuentras %{art} in la parte posterior de " "la cueva." msgid "" "Seeing that you do not have %{count} gold, the demon slashes you with its " "claws, and the last thing you see is a red haze." msgstr "" msgid "" "The Demon leaps upon you and has its claws at your throat before you can " "even draw your sword. \"Your life is mine,\" it says. \"I will sell it back " "to you for %{count} gold.\"" msgstr "" msgid "" "Upon defeating the daemon's servants, you find a hidden cache with %{count} " "gold." msgstr "" msgid "" "As you enter the Alchemist's Tower, a hobbled, graying man in a brown cloak " "makes his way towards you." msgstr "" msgid "He checks your pack, and sees that you have 1 cursed item." msgid_plural "" "He checks your pack, and sees that you have %{count} cursed items." msgstr[0] "" msgstr[1] "" msgid "For %{gold} gold, the alchemist will remove it for you. Do you pay?" msgid_plural "" "For %{gold} gold, the alchemist will remove them for you. Do you pay?" msgstr[0] "" msgstr[1] "" msgid "" "After you consent to pay the requested amount of gold, the alchemist grabs " "the cursed artifact and throws it into his magical cauldron." msgid_plural "" "After you consent to pay the requested amount of gold, the alchemist grabs " "all cursed artifacts and throws them into his magical cauldron." msgstr[0] "" msgstr[1] "" msgid "" "You hear a voice from behind the locked door, \"You don't have enough gold " "to pay for my services.\"" msgstr "" msgid "" "You hear a voice from high above in the tower, \"Go away! I can't help you!\"" msgstr "" "Oyes una voz que proviene de lo alto de la torre, \"¡Vete, no puedo ayudarte!" "\"" msgid "" "The head groom speaks to you, \"That is a fine looking horse you have. I am " "afraid we can give you no better, but the horses your cavalry are riding " "look to be of poor breeding stock. We have many trained war horses which " "would aid your riders greatly. I insist you take them.\"" msgstr "" "El mozo de establo dice: \"Parece buen caballo el que montas. Me temo que no " "puedo ofrecer uno mejor, pero los caballos que lleva tu caballería parecen " "no ser de raza. Hemos entrenado muchos caballos de batalla lo cual ayudará a " "tus jinetes mucho. Insisto en que te los lleves." msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, he will " "grow tired in a week. You must also let me give better horses to your " "mounted soldiers, their horses look shoddy and weak.\"" msgstr "" "Al aproximarte a los establos, el mozo de cuadra aparece guiando un buen " "caballo de batalla. \"Este corcel te ayudará a acelerar tus viajes. Oh, se " "cansará en una semana. Deberíais permitirme daros mejores caballos para tus " "soldados montados, puesto que sus caballos parecen debiluchos." msgid "" "The head groom approaches you and speaks, \"You already have a fine horse, " "and have no inexperienced cavalry which might make use of our trained war " "horses.\"" msgstr "" "El mozo de cuadra se aproxima y dice: \"Ya tienes un buen caballo y " "caballería con experiencia lo que evitará hacer uso de nuestros caballos de " "batalla entrenados." msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, his " "endurance will wane with a lot of heavy riding, and you must return for a " "fresh mount in a week. We also have many fine war horses which could benefit " "mounted soldiers, but you have none we can help.\"" msgstr "" "Al aproximarte a los establos el mozo de cuadra aparece guiando un buen " "caballo de batalla. \"Este corcel te ayudará a acelerar tus viajes. Oh, su " "resistencia disminuira mucho con el tiempo, debes volver por una nueva monta " "en una semana. También tenemos buenos caballos de batalla que podrían " "beneficiar a tus solados montados, pero no tienes ninguno así que no te " "podemos ayudar\"." msgid "The Arena guards turn you away." msgstr "Los Guardas de la Arena te apartan." msgid "" "You have your crew stop up their ears with wax before the sirens' eerie song " "has any chance of luring them to a watery grave." msgstr "" "Obligas a tu tripulación a ponerse tapones en los oídos antes de que la " "inquietante canción de las sirenas pueda atraerlos a una muerte segura." msgid "" "As the sirens sing their eerie song, your small, determined army manages to " "overcome the urge to dive headlong into the sea." msgstr "" "Ta pronto como las sirenas cantan su escalofriante canción, tu pequeño " "ejercito empieza a pensar en dejarse llevar por el impulso de tirarse de " "cabeza al mar." msgid "" "An eerie wailing song emanates from the sirens perched upon the rocks. Many " "of your crew fall under its spell, and dive into the water where they drown. " "You are now wiser for the visit, and gain %{exp} experience." msgstr "" "Una espeluznante canción de gemidos emana de las sirenas subidas a las " "rocas. Algunos tripulantes caen bajo su hechizo y se lanzan al agua donde se " "ahogan. Ahora estas prevenido de su localización, y ganas %{exp} de " "experiencia." msgid "" "In a dazzling display of daring, you break into the local jail and free the " "hero imprisoned there, who, in return, pledges loyalty to your cause." msgstr "" "En una deslumbrante momento de atrevimiento, irrumpes en la cárcel local y " "liberas el héroe que está prisionero allí, quien, a cambio, jura lealtad a " "tu causa." msgid "" "You already have %{count} heroes, and regretfully must leave the prisoner in " "this jail to languish in agony for untold days." msgstr "" "Ya tienes %{count} héroes, y lamentablemente debes dejar al prisionero en su " "cárcel languideciendo de agonía por incontables días." msgid "" "You enter a rickety hut and talk to the magician who lives there. He tells " "you of places near and far which may aid you in your journeys." msgstr "" "Entras en una desvencijada cabaña y hablas con el mago que vive allí. El te " "habla de lugares cercanos y lejanos que te pueden ayudar en tus viajes." msgid "This eye seems to be intently studying its surroundings." msgstr "Este ojo parece estar atentamente estudiando los alrededores." msgid "You come across a giant Sphinx. The Sphinx remains strangely quiet." msgstr "" "Te topas con una Esfinge Gigante. La Esfinge permanece extrañamente " "tranquila." msgid "" "\"I have a riddle for you,\" the Sphinx says. \"Answer correctly, and you " "shall be rewarded. Answer incorrectly, and you shall be eaten. Do you accept " "the challenge?\"" msgstr "" "\"Tengo un acertijo para ti\" dice la Esfinge. \"Contesta correctamente y " "serás recompensado. Falla y serás engullido.\" ¿Aceptas el Desafío?" msgid "" "The Sphinx asks you the following riddle:\n" "\n" "'%{riddle}'\n" "\n" "Your answer?" msgstr "" "La Esfinge te pregunta el siguiente acertijo:\n" "\n" "%{riddle}\n" "\n" "¿Tú respuesta?" msgid "" "\"You guessed incorrectly,\" the Sphinx says, smiling. The Sphinx swipes at " "you with a paw, knocking you to the ground. Another blow makes the world go " "black, and you know no more." msgstr "" "\"No has adivinado\" dice la Esfinge. La Esfinge te golpea con una pata, " "tirándote al suelo. Otro soplido hace retrodecer al mundo y no sabes nada " "más." msgid "" "Looking somewhat disappointed, the Sphinx sighs. \"You've answered my riddle " "so here's your reward. Now begone.\"" msgstr "" "Pareciendo en cierto modo decepcionada, la Esfinge suspira. Has acertado mi " "acertijo, por lo tanto aquí está tu recompensa. Ahora vete." msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "As you speak the magic word, the glowing barrier dissolves into nothingness." msgstr "" "Una barrera mágica se alza frente a ti, bloqueándote el paso. La runas en el " "arco dicen, \"Di la clave y podrás pasar\".\n" "En cuanto dices la palabra mágica, la brillante barrera se disuelve en la " "nada." msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "You speak, and nothing happens." msgstr "" "Una barrera mágica se alza frente a ti, bloqueándote el paso. Las runas en " "el arco dicen, \"Di la clave y podrás pasar\".\n" "Hablas, pero no ocurre nada." msgid "" "You enter the tent and see an old woman gazing into a magic gem. She looks " "up and says,\n" "\"In my travels, I have learned much in the way of arcane magic. A great " "oracle taught me his skill. I have the answer you seek.\"" msgstr "" "Entras en la tienda y ves una mujer anciana contemplando una gema mágica. " "Ella mira hacia arriba y dice,\n" "\"En mis viajes, he aprendido mucho del camino de la magia arcana. Un gran " "oráculo me paso sus habilidades. Tengo la respuesta que buscas.\"" msgid "No spell book is present." msgstr "No hay libro de hechizos." #, fuzzy msgid "" "This spell costs %{mana} spell points. You have no spell points, so you " "cannot cast it." msgstr "" "No puedes usar ese hechizo. Cuesta %{mana} puntos de Maná y sólo tienes " "&{point}." #, fuzzy msgid "" "This spell costs %{mana} spell points. You only have %{point} spell points, " "so you cannot cast it." msgstr "" "No puedes usar ese hechizo. Cuesta %{mana} puntos de Maná y sólo tienes " "&{point}." #, fuzzy msgid "The spell was not found." msgstr "¡El hechizo no afectará a nadie!" msgid "Only heroes can cast this spell." msgstr "" #, fuzzy msgid "This hero is not able to cast adventure spells." msgstr "Lanzar un hechizo tipo aventura." msgid "Your hero is too tired to cast this spell today. Try again tomorrow." msgstr "" "Tu héroe está demasiado cansado como para conjurar este hechizo hoy. Prueba " "de nuevo mañana." #, fuzzy msgid "This spell cannot be cast on a boat." msgstr "Este pueblo no se puede actualizar a castillo." #, fuzzy msgid "This spell can only be cast near an ocean." msgstr "Este pueblo no se puede actualizar a castillo." msgid "" "There are no boats available and no ocean adjacent to the hero where this " "spell will work." msgstr "" #, fuzzy msgid "There are no boats available for this spell." msgstr "Lanzar un hechizo tipo aventura." #, fuzzy msgid "There is no ocean adjacent to the hero where this spell will work." msgstr "" "No hay un espacio libre adyacente a tu héroe para convocar un Elemental." msgid "" "You do not own any town or castle that is not currently occupied by a hero. " "This spell will have no effect." msgstr "" #, fuzzy msgid "This hero is already in a town, so this spell will have no effect." msgstr "" "No puedes usar ese hechizo. Cuesta %{mana} puntos de Maná y sólo tienes " "&{point}." msgid "" "The nearest town is %{town}.\n" "\n" "This town is occupied by your hero %{hero}." msgstr "" "La ciudad más cercana es %{town}.\n" "\n" "Esta ciudad está ocupada por tu héroe %{hero}." msgid "This spell is already in effect." msgstr "" msgid "" "No opponent neither has nor can have any hero under their command anymore. " "Casting this spell will have no effect." msgstr "" msgid "" "No opponent has a hero under their command at this time. Casting this spell " "will have no effect." msgstr "" msgid "" "You must be within %{count} spaces of a monster for the Visions spell to " "work." msgstr "" "Debes estar a %{count} casillas de un monstruo para que el hechizo Visiones " "funcione." #, fuzzy msgid "" "You must be standing on the entrance to a mine (sawmills and alchemist labs " "do not count) to cast this spell." msgstr "" "Has de estar en la entrada de una mina para lanzar este hechizo (las " "serrerías y las casas de alquimia no cuentan)." msgid "You must first defeat the ghosts guarding the mine to cast this spell." msgstr "" msgid "" "There are already at least as many elementals guarding the mine as this hero " "can generate. Casting this spell will have no effect." msgstr "" msgid "%{name} the %{race} (Level %{level})" msgstr "%{name}, %{race} ( Nivel %{level} )" msgid "" "'Spread' combat formation spreads the hero's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "La formación 'Desplegada' de combate disemina las tropas del héroe desde la " "parte superior a la inferior del campo de batalla, con al menos un espacio " "vacío entre las tropas." msgid "" "'Grouped' combat formation bunches the hero's army together in the center of " "their side of the battlefield." msgstr "" "La formación 'Agrupada' de combate aglutina las tropas del heroe en el " "centro de su lado del campo de batalla." msgid "Are you sure you want to dismiss this Hero?" msgstr "¿Estas seguro que quieres despedir este Héroe?" msgid "View Experience Info" msgstr "Ver información sobre Experiencia" msgid "View Spell Points Info" msgstr "Ver información sobre Maná" msgid "Set army combat formation to 'Spread'" msgstr "Ordenar al ejercito formación Desplegada." msgid "Set army combat formation to 'Grouped'" msgstr "Ordenar al ejercito formación Agrupada" #, fuzzy msgid "Exit Hero Screen" msgstr "Pantalla de Héroe" msgid "You cannot dismiss a hero in a castle" msgstr "" msgid "Dismissal of %{name} the %{race} is prohibited by scenario" msgstr "" msgid "Dismiss %{name} the %{race}" msgstr "Despedir a %{name} (%{race})" #, fuzzy msgid "Show previous hero" msgstr "Ver ciudad anterior" msgid "Show next hero" msgstr "Mostrar el siguiente héroe" #, fuzzy msgid "Blood Morale" msgstr "Buena Moral" msgid "%{morale} Morale" msgstr "%{morale} Moral" msgid "%{luck} Luck" msgstr "" msgid "Current Luck Modifiers:" msgstr "Modificadores de suerte actuales:" msgid "Current Morale Modifiers:" msgstr "Modificadores de moral actuales:" msgid "Entire army is undead, so morale does not apply." msgstr "Todo el ejército es nomuerto, por lo tanto la moral no se aplica." msgid "" "Current experience %{exp1}.\n" " Next level %{exp2}." msgstr "" "Experiencia actual: %{exp1}.\n" "Siguiente nivel a: %{exp2}." msgid "Level %{level}" msgstr "Nivel %{level}" msgid "" "%{name} currently has %{point} spell points out of a maximum of %{max}. The " "maximum number of spell points is 10 times the hero's knowledge. It is " "occasionally possible for the hero to have more than their maximum spell " "points via special events." msgstr "" "%{name} actualmente tiene %{point} puntos de Maná de un total de %{max}. El " "máximo número de puntos de Maná es 10 veces el conocimiento del héroe. " "Ocasionalmente es posible sobrepasar el máximo de puntos de Maná mediante " "eventos especiales." msgid "%{name1} meets %{name2}" msgstr "%{name1} se reune con %{name2}" msgid "Enemy heroes are now fully identifiable." msgstr "Ahora se identifica totalmente a los héroes enemigos." msgid "Identify Hero" msgstr "Identificar Héroe" msgid "Select town to port to." msgstr "Seleccione la ciudad a la que transportarse." msgid "Town Portal" msgstr "Portal de la Ciudad" msgid "The creatures are willing to join us!" msgstr "¡Las criaturas quieren unirse a nosotros!" msgid "All the creatures will join us..." msgstr "Todas las criaturas se nos unirán..." msgid "The creature will join us..." msgid_plural "%{count} of the creatures will join us..." msgstr[0] "La criatura se unirá a nosotros..." msgstr[1] "%{count} de las criaturas se unirán a nosotros..." msgid "" "\n" " for a fee of %{gold} gold." msgstr "" msgid "These weak creatures will surely flee before us." msgstr "Esas débiles criaturas seguramente huirán nada más vernos." msgid "I fear these creatures are in the mood for a fight." msgstr "Me temo que esas criaturas tienen ganas de luchar." msgid "The hero's attack skill is a bonus added to each unit's attack skill." msgstr "" "El ataque del héroe es una bonificación que se añade al ataque de cada " "unidad." msgid "The hero's defense skill is a bonus added to each unit's defense skill." msgstr "" "La defensa del héroe es una bonificación añadida a la defensa de cada unidad." msgid "The hero's spell power determines the duration or power of a spell." msgstr "" "La potencia de hechizo del héroe determina la longitud o el poder de los " "hechizos." msgid "" " The hero's knowledge determines how many spell points the hero may have. " "Under normal circumstances, a hero is limited to 10 spell points per level " "of knowledge." msgstr "" " El conocimiento del héroe determina el número máximo de puntos de Maná que " "el héroe puede alcanzar. Bajo circunstancias normales, un héroe está " "limitado a 10 puntos de Maná por nivel de conocimiento." msgid "Current Modifiers:" msgstr "Modificadores actuales:" msgid "skill|Basic" msgstr "Básica" msgid "skill|Advanced" msgstr "Avanzada" msgid "skill|Expert" msgstr "Experta" msgid "Pathfinding" msgstr "Orientación" msgid "Archery" msgstr "Tiro con Arco" msgid "Logistics" msgstr "Logística" msgid "Scouting" msgstr "Exploración" msgid "Diplomacy" msgstr "Diplomacia" msgid "Navigation" msgstr "Navegación" msgid "Leadership" msgstr "Liderazgo" msgid "Wisdom" msgstr "Sabiduría" msgid "Mysticism" msgstr "Misticismo" msgid "Ballistics" msgstr "Balística" msgid "Eagle Eye" msgstr "Ojo de Ãguila" msgid "Necromancy" msgstr "Nigromancia" msgid "Estates" msgstr "Patrimonio" msgid "Advanced Archery" msgstr "Tiro con Arco Avanzado" msgid "Advanced Pathfinding" msgstr "Orientación Avanzada" msgid "Basic Archery" msgstr "Tiro con Arco Básico" msgid "Basic Pathfinding" msgstr "Orientación Básica" msgid "Expert Pathfinding" msgstr "Orientación Experta" msgid "Advanced Logistics" msgstr "Logística Avanzada" msgid "Basic Logistics" msgstr "Logística Básica" msgid "Basic Scouting" msgstr "Exploración Básica" msgid "Expert Archery" msgstr "Tiro con Arco Experto" msgid "Expert Logistics" msgstr "Logística Experta" msgid "Advanced Diplomacy" msgstr "Diplomacia Avanzada" msgid "Advanced Scouting" msgstr "Exploración Avanzada" msgid "Basic Diplomacy" msgstr "Diplomacia Básica" msgid "Expert Diplomacy" msgstr "Diplomacia Experta" msgid "Expert Scouting" msgstr "Exploración Experta" msgid "Advanced Leadership" msgstr "Liderazgo Avanzado" msgid "Advanced Navigation" msgstr "Navegación Avanzada" msgid "Basic Leadership" msgstr "Liderazgo Básico" msgid "Basic Navigation" msgstr "Navegación Básica" msgid "Expert Navigation" msgstr "Navegación Experta" msgid "Advanced Wisdom" msgstr "Sabiduría Avanzada" msgid "Basic Mysticism" msgstr "Misticismo Básico" msgid "Basic Wisdom" msgstr "Sabiduría Básica" msgid "Expert Leadership" msgstr "Liderazgo Experto" msgid "Expert Wisdom" msgstr "Sabiduría Experta" msgid "Advanced Luck" msgstr "Suerte Avanzada" msgid "Advanced Mysticism" msgstr "Misticismo Avanzado" msgid "Basic Luck" msgstr "Suerte Básica" msgid "Expert Luck" msgstr "Suerte Experta" msgid "Expert Mysticism" msgstr "Misticismo Experto" msgid "Advanced Ballistics" msgstr "Balística Avanzada" msgid "Advanced Eagle Eye" msgstr "Ojo de águila avanzado" msgid "Basic Ballistics" msgstr "Balística Básica" msgid "Basic Eagle Eye" msgstr "Ojo de águila básico" msgid "Expert Ballistics" msgstr "Balística Experta" msgid "Advanced Necromancy" msgstr "Necromancia Avanzada" msgid "Basic Estates" msgstr "Partimonio Básico" msgid "Basic Necromancy" msgstr "Necromancia Básica" msgid "Expert Eagle Eye" msgstr "Ojo de águila experto" msgid "Expert Necromancy" msgstr "Necromancia Experta" msgid "Advanced Estates" msgstr "Partimonio Avanzado" msgid "Expert Estates" msgstr "Partimonio Experto" msgid "" "%{skill} reduces the movement penalty for rough terrain by %{count} percent." msgstr "" "La %{skill} reduce la penalización al movimiento debida a terreno agreste en " "un %{count} por ciento." msgid "%{skill} eliminates the movement penalty for rough terrain." msgstr "La %{skill} elimina la penalización de movimiento por terreno agreste." msgid "" "%{skill} increases the damage done by the hero's range attacking creatures " "by %{count} percent." msgstr "" "El %{skill} aumenta un %{count}% el daño infligido por las criaturas " "atacantes a distancia del héroe." msgid "%{skill} increases the hero's movement points by %{count} percent." msgstr "" "La %{skill} aumenta los puntos de movimiento del héroe en un %{count}%." msgid "%{skill} increases the hero's viewable area by one square." msgid_plural "%{skill} increases the hero's viewable area by %{count} squares." msgstr[0] "La %{skill} aumenta el área visible del héroe en un cuadrado." msgstr[1] "" "La %{skill} aumenta el área visible del héroe en %{count} cuadrados." msgid "" "%{skill} allows the hero to negotiate with monsters who are weaker than " "their army, and reduces the cost of surrender." msgstr "" "La %{skill} permite al héroe negociar con monstruos más débiles que su " "ejército, y reduce el coste de la rendición." msgid "" "Approximately %{count} percent of the creatures may offer to join the hero." msgstr "" "Aproximadamente el %{count} de las criaturas pueden ofrecer unirse al héroe." msgid "All of the creatures may offer to join the hero." msgstr "Todas las criaturas pueden ofrecer unirse al héroe." msgid "" "The cost of surrender is reduced to %{percent} percent of the total cost of " "troops in the army." msgstr "" "El coste de la rendición se reduce al %{percent}% del coste total de las " "tropas del ejército." msgid "" "%{skill} increases the hero's movement points over water by %{count} percent." msgstr "" "La %{skill} aumenta los puntos de movimiento del héroe sobre el agua en un " "%{count}%." msgid "%{skill} increases the hero's troops morale by %{count}." msgstr "El %{skill} aumenta la moral de las tropas del héroe en %{count}." msgid "%{skill} allows the hero to learn third level spells." msgstr "La %{skill} permite al héroe aprender hechizos de tercer nivel." msgid "%{skill} allows the hero to learn fourth level spells." msgstr "La %{skill} permite al héroe aprender hechizos de cuarto nivel." msgid "%{skill} allows the hero to learn fifth level spells." msgstr "La %{skill} permite al héroe aprender hechizos de quinto nivel." msgid "%{skill} regenerates one additional spell point per day to the hero." msgid_plural "" "%{skill} regenerates %{count} additional spell points per day to the hero." msgstr[0] "" "El %{skill} regenera un punto de hechizo adicional por día al héroe." msgstr[1] "" "El %{skill} regenera %{count} puntos de hechizo adicionales al día al héroe." msgid "%{skill} increases the hero's luck by %{count}." msgstr "La %{skill} aumenta la suerte del héroe en %{count}." msgid "" "%{skill} gives the hero's catapult shots a greater chance to hit and do " "damage to castle walls." msgstr "" "La %{skill} da a los disparos de catapulta del héroe una mayor probabilidad " "de impactar y hacer daño a los muros del castillo." msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot has a " "greater chance to hit and do damage to castle walls." msgstr "" "La %{skill} da a la catapulta del héroe un disparo extra, y cada disparo " "tiene más posibilidades de acertar y hacer daño a los muros del castillo." msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot " "automatically destroys any wall, except a fortified wall in a Knight castle." msgstr "" "La %{skill} da a la catapulta del héroe un disparo extra, y cada disparo " "destruye automáticamente cualquier muro, excepto un muro fortificado en un " "castillo de caballero." msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 1st or " "2nd level spell that was cast by an enemy during combat." msgstr "" "El %{skill} otorga al héroe un %{count}% de posibilidades de aprender " "cualquier hechizo de 1o o 2o nivel lanzado por un enemigo durante el combate." msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 3rd " "level spell (or below) that was cast by an enemy during combat." msgstr "" "El %{skill} otorga al héroe un %{count}% de posibilidades de aprender " "cualquier hechizo de 4o nivel (o inferior) lanzado por un enemigo durante el " "combate." msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 4th " "level spell (or below) that was cast by an enemy during combat." msgstr "" "El %{skill} otorga al héroe un %{count}% de posibilidades de aprender " "cualquier hechizo de 3er nivel (o inferior) lanzado por un enemigo durante " "el combate." msgid "" "%{skill} allows %{count} percent of the creatures killed in combat to be " "brought back from the dead as Skeletons." msgstr "" "La %{skill} permite resucitar como esqueletos al %{count}% de las criaturas " "muertas en combate." msgid "" "The hero produces %{count} gold pieces per day as tax revenue from estates." msgstr "" "El héroe produce %{count} piezas de oro al día como ingresos fiscales de las " "propiedades." msgid "Blue" msgstr "Azul" msgid "Green" msgstr "Verde" msgid "Red" msgstr "Rojo" msgid "Yellow" msgstr "Amarillo" msgid "Orange" msgstr "Naranja" msgid "Purple" msgstr "Morado" msgid "barrier|Aqua" msgstr "turquesa" msgid "barrier|Blue" msgstr "azul" msgid "barrier|Brown" msgstr "marrón" msgid "barrier|Gold" msgstr "dorada" msgid "barrier|Green" msgstr "verde" msgid "barrier|Orange" msgstr "naranja" msgid "barrier|Purple" msgstr "morada" msgid "barrier|Red" msgstr "roja" msgid "tent|Aqua" msgstr "turquesa" msgid "tent|Blue" msgstr "azul" msgid "tent|Brown" msgstr "marrón" msgid "tent|Gold" msgstr "dorada" msgid "tent|Green" msgstr "verde" msgid "tent|Orange" msgstr "naranja" msgid "tent|Purple" msgstr "morada" msgid "tent|Red" msgstr "roja" msgid "Experience" msgstr "Experiencia" msgid "" "Experience allows your heroes to go up levels, increasing their primary and " "secondary skills." msgstr "" msgid "Hero/Stats" msgstr "Héroe / Estadísticas" msgid "Skills" msgstr "Experiencia" msgid "Town/Castle" msgstr "Ciudad/Castillo" msgid "Garrison" msgstr "Guarnición" msgid "Gold Per Day:" msgstr "Oro por día:" msgid "View Heroes." msgstr "Mostrar Héroes." msgid "Towns/Castles" msgstr "Ciudades/Castillos" msgid "View Towns and Castles." msgstr "Ver Ciudades y Castillos." msgid "luck|Cursed" msgstr "Maldito" msgid "luck|Awful" msgstr "Horrible" msgid "luck|Bad" msgstr "Mala" msgid "luck|Normal" msgstr "Normal" msgid "luck|Good" msgstr "Buena" msgid "luck|Great" msgstr "Grande" msgid "luck|Irish" msgstr "Irlandés" #, fuzzy msgid "" "Bad luck sometimes falls on the hero's units in combat, causing their " "attacks to only do half damage." msgstr "" "La Mala suerte cae a veces en combate sobre tus tropas dividiendo por 2 el " "daño de sus ataques." #, fuzzy msgid "" "Neutral luck means the hero's units will never get lucky or unlucky attacks " "on the enemy." msgstr "" "Con Suerte Neutral tus ejércitos nunca lograrán ataques afortunados o " "desafortunados." #, fuzzy msgid "" "Good luck sometimes lets the hero's units get lucky attacks (double " "strength) in combat." msgstr "" "Con Buena Suerte a veces tus tropas consegirán ataques afortunados " "duplicando su daño." msgid "morale|Treason" msgstr "Traición" msgid "morale|Awful" msgstr "Horrible" msgid "morale|Poor" msgstr "Pobre" msgid "morale|Normal" msgstr "Normal" msgid "morale|Good" msgstr "Bueno" msgid "morale|Great" msgstr "Grande" msgid "morale|Blood!" msgstr "¡Sangre!" #, fuzzy msgid "Bad morale may cause the hero's units to freeze in combat." msgstr "Con Baja Moral tus tropas puede paralizarse en combate." #, fuzzy msgid "" "Neutral morale means the hero's units will never be blessed with extra " "attacks or freeze in combat." msgstr "" "Con Moral Neutral tus tropas no serán bendecidas con ataques adicionales ni " "se petrificarán en combate." #, fuzzy msgid "Good morale may give the hero's units extra attacks in combat." msgstr "Con Moral Alta tus tropas podrán lograr ataques extra en combate." msgid "Knight" msgstr "Caballero" msgid "Barbarian" msgstr "Bárbaro" msgid "Sorceress" msgstr "Hechicera" msgid "Warlock" msgstr "Brujo" msgid "Wizard" msgstr "Mago" msgid "Necromancer" msgstr "Nigromante" msgid "Multi" msgstr "Multi" msgid "doubleLined|Knight" msgstr "Caballero" msgid "doubleLined|Barbarian" msgstr "Bárbaro" msgid "doubleLined|Sorceress" msgstr "Hechicera" msgid "doubleLined|Warlock" msgstr "Brujo" msgid "doubleLined|Wizard" msgstr "Mago" msgid "" "doubleLined|Necro-\n" "mancer" msgstr "" "Nigro-\n" "mante" msgid "doubleLinedRace|Multi" msgstr "Multi" msgid "doubleLinedRace|Random" msgstr "Aleatorio" msgid "speed|Standing" msgstr "Fijo" msgid "speed|Crawling" msgstr "Arrastrando" msgid "speed|Very Slow" msgstr "Muy lenta" msgid "speed|Slow" msgstr "Lenta" msgid "speed|Average" msgstr "Media" msgid "speed|Fast" msgstr "Rápida" msgid "speed|Very Fast" msgstr "Muy rápida" msgid "speed|Ultra Fast" msgstr "Ultra rápida" msgid "speed|Blazing" msgstr "Fugaz" msgid "speed|Instant" msgstr "Instantáneo" msgid "week|Squirrel" msgstr "Ardilla" msgid "week|Rabbit" msgstr "Conejo" msgid "week|Gopher" msgstr "Tuza" msgid "week|Badger" msgstr "Tejón" msgid "week|Rat" msgstr "Rata" msgid "week|Eagle" msgstr "Ãguila" msgid "week|Weasel" msgstr "Comadreja" msgid "week|Raven" msgstr "Cuervo" msgid "week|Mongoose" msgstr "Mangosta" msgid "week|Dog" msgstr "Perro" msgid "week|Aardvark" msgstr "Oso hormiguero" msgid "week|Lizard" msgstr "Lagarto" msgid "week|Tortoise" msgstr "Tortuga" msgid "week|Hedgehog" msgstr "Puercoespín" msgid "week|Condor" msgstr "Cóndor" msgid "week|Ant" msgstr "Hormiga" msgid "week|Grasshopper" msgstr "Saltamontes" msgid "week|Dragonfly" msgstr "Libélula" msgid "week|Spider" msgstr "Araña" msgid "week|Butterfly" msgstr "Mariposa" msgid "week|Bumblebee" msgstr "Abejorro" msgid "week|Locust" msgstr "Langosta" msgid "week|Earthworm" msgstr "Gusano" msgid "week|Hornet" msgstr "Avispa" msgid "week|Beetle" msgstr "Escarabajo" msgid "week|PLAGUE" msgstr "PLAGA" msgid "week|Unnamed" msgstr "Sin nombre" msgid "Desert" msgstr "Desierto" msgid "Snow" msgstr "Nieve" msgid "Wasteland" msgstr "Paraje baldio" msgid "Beach" msgstr "Playa" msgid "Lava" msgstr "Zona Volcánica" msgid "Dirt" msgstr "Terreno arcilloso" msgid "Grass" msgstr "Pastos" msgid "Ocean" msgstr "Océano" msgid "maps|Small" msgstr "Pequeño" msgid "maps|Medium" msgstr "Mediano" msgid "maps|Large" msgstr "Grande" msgid "maps|Extra Large" msgstr "Extra grande" msgid "maps|Custom Size" msgstr "" msgid "Ore Mine" msgstr "Mina de Hierro" msgid "Sulfur Mine" msgstr "Mina de Azufre" msgid "Crystal Mine" msgstr "Mina de Cristal" msgid "Gems Mine" msgstr "Mina de Gemas" msgid "Gold Mine" msgstr "Mina de Oro" msgid "Mine" msgstr "Mina" msgid "Burma shave." msgstr "" msgid "Next sign 50 miles." msgstr "" #, fuzzy msgid "See Rock City." msgstr "Ciudad del Ãrbol" msgid "This space for rent." msgstr "" msgid "No object" msgstr "" msgid "Alchemist Lab" msgstr "Alquimista" msgid "Sign" msgstr "Señal" msgid "Buoy" msgstr "Boya" msgid "Skeleton" msgstr "Esqueleto" msgid "Daemon Cave" msgstr "Cueva del Demonio" msgid "Treasure Chest" msgstr "Cofre del Tesoro" msgid "Faerie Ring" msgstr "Anillo del Hada" msgid "Campfire" msgstr "Hoguera" msgid "Fountain" msgstr "Fuente" msgid "Gazebo" msgstr "Cenador" msgid "Genie Lamp" msgstr "Lampara del Genio" msgid "Archer's House" msgstr "Casa del Arquero" msgid "Goblin Hut" msgstr "Palloza" msgid "Dwarf Cottage" msgstr "Casita de Enanos" msgid "Peasant Hut" msgstr "Cabaña de Campesinos" msgid "Stables" msgstr "Establos" msgid "Alchemist's Tower" msgstr "Torre del Alquimista" msgid "Event" msgstr "" msgid "Dragon City" msgstr "Ciudad Dragón" msgid "Lighthouse" msgid_plural "Lighthouses" msgstr[0] "Faro" msgstr[1] "Faros" msgid "Water Wheel" msgid_plural "Water Wheels" msgstr[0] "Molino de Agua" msgstr[1] "Molinos de Agua" msgid "Monster" msgstr "Monstruo" msgid "Obelisk" msgstr "Obelisco" msgid "Oasis" msgstr "Oasis" msgid "Resource" msgstr "Recursos" msgid "Sawmill" msgstr "Aserradero" msgid "Oracle" msgstr "Oráculo" msgid "Shrine of the First Circle" msgstr "Capilla del Primer Círculo" msgid "Shipwreck" msgstr "Naufragio" msgid "Sea Chest" msgstr "Cofre Marino" msgid "Desert Tent" msgstr "Tienda del Desierto" msgid "Stone Liths" msgstr "" msgid "Wagon Camp" msgstr "Carromato" msgid "Hut of the Magi" msgstr "Cabaña del los Magos" msgid "Whirlpool" msgstr "Remolino" msgid "Windmill" msgid_plural "Windmills" msgstr[0] "Molino de Viento" msgstr[1] "Molinos de Viento" msgid "Artifact" msgstr "Artefacto" msgid "Mermaid" msgstr "Sirena" msgid "Boat" msgstr "Bote" #, fuzzy msgid "Random Ultimate Artifact" msgstr "Encuentra el artefacto definitivo" #, fuzzy msgid "Random Artifact" msgstr "Artefacto" #, fuzzy msgid "Random Resource" msgstr "Recursos" #, fuzzy msgid "Random Monster" msgstr "Cualquier castillo" msgid "Random Town" msgstr "Cualquier torre" msgid "Random Castle" msgstr "Cualquier castillo" msgid "Eye of the Magi" msgstr "Ojo de los Magos" #, fuzzy msgid "Random Monster - weak" msgstr "Cualquier castillo" #, fuzzy msgid "Random Monster - medium" msgstr "Cualquier castillo" #, fuzzy msgid "Random Monster - strong" msgstr "Cualquier castillo" #, fuzzy msgid "Random Monster - very strong" msgstr "Cualquier castillo" msgid "Hero" msgstr "Héroe" msgid "Nothing Special" msgstr "Nada especial" msgid "Mossy Rock" msgstr "" msgid "Watch Tower" msgstr "Torre de Vigilancia" msgid "Tree House" msgstr "Casa de Ãrbol" msgid "Tree City" msgstr "Ciudad del Ãrbol" msgid "Ruins" msgstr "Ruinas" msgid "Fort" msgstr "Fortaleza" msgid "Abandoned Mine" msgstr "Mina Abandonada" msgid "Sirens" msgstr "Sirenas" msgid "Standing Stones" msgstr "Círculo Lítico" msgid "Idol" msgstr "Ãdolo" msgid "Tree of Knowledge" msgstr "Ãrbol del conocimiento" msgid "Witch Doctor's Hut" msgstr "Cabaña de la Bruja" msgid "Temple" msgstr "Templo" msgid "Hill Fort" msgstr "Fuerte de la Colina" msgid "Halfling Hole" msgstr "Hoyo de los Medianos" msgid "Mercenary Camp" msgstr "Campamento Mercenario" msgid "Shrine of the Second Circle" msgstr "Capilla del Segundo Círculo" msgid "Shrine of the Third Circle" msgstr "Capilla del Tercer Círculo" msgid "City of the Dead" msgstr "Ciudad de la muerte" msgid "Sphinx" msgstr "Esfinge" msgid "Wagon" msgstr "Vagón" msgid "Tar Pit" msgstr "Fosa de Alquitrán" msgid "Artesian Spring" msgstr "Pozo Artesiano" msgid "Troll Bridge" msgstr "Puente de los Trolls" msgid "Watering Hole" msgstr "Gotera" msgid "Witch's Hut" msgstr "Choza de brujas" msgid "Xanadu" msgstr "Xanadu" msgid "Lean-To" msgstr "Apoyar" msgid "Magellan's Maps" msgstr "Mapas de Magallanes" msgid "Flotsam" msgstr "Restos de un naufragio" msgid "Derelict Ship" msgstr "Barco Abandonado" msgid "Shipwreck Survivor" msgstr "" msgid "Bottle" msgstr "Botella" msgid "Magic Well" msgstr "Pozo Mágico" msgid "Magic Garden" msgid_plural "Magic Gardens" msgstr[0] "Jardín mágico" msgstr[1] "Jardines mágicos" msgid "Observation Tower" msgstr "Torre de observación" #, fuzzy msgid "Freeman's Foundry" msgstr "Fundición" msgid "Reefs" msgstr "Arrecifes" msgid "Volcano" msgstr "Volcán" msgid "Flowers" msgstr "Flores" msgid "Rock" msgstr "Roca" msgid "Water Lake" msgstr "Lago" msgid "Mandrake" msgstr "Mandrágora" msgid "Dead Tree" msgstr "Ãrbol muerto" msgid "Stump" msgstr "Cepa" msgid "Crater" msgstr "Cráter" msgid "Cactus" msgstr "Cactus" msgid "Mound" msgstr "Montón" msgid "Dune" msgstr "Duna" msgid "Lava Pool" msgstr "Charca de Lava" msgid "Shrub" msgstr "Arbusto" msgid "Barrow Mounds" msgstr "Montículo de la Carretilla" msgid "Random Artifact - Treasure" msgstr "" msgid "Random Artifact - Minor" msgstr "" msgid "Random Artifact - Major" msgstr "" msgid "Barrier" msgstr "Barrera" msgid "Traveller's Tent" msgstr "Tienda del Viajero" msgid "Jail" msgstr "Cárcel" msgid "Fire Summoning Altar" msgstr "Altar del Fuego" msgid "Air Summoning Altar" msgstr "Altar del Aire" msgid "Earth Summoning Altar" msgstr "Altar de la Tierra" msgid "Water Summoning Altar" msgstr "Altar del Agua" #, fuzzy msgid "Unknown Monster" msgstr "Monstruo" #, fuzzy msgid "Unknown Monsters" msgstr "Monstruos" msgid "Peasant" msgstr "Campesino" msgid "Peasants" msgstr "Campesinos" msgid "Archer" msgstr "Arquero" msgid "Archers" msgstr "Arqueros" msgid "Ranger" msgstr "Cazador" msgid "Rangers" msgstr "Cazadores" msgid "Pikeman" msgstr "Piquero" msgid "Pikemen" msgstr "Piqueros" msgid "Veteran Pikeman" msgstr "Piquero veterano" msgid "Veteran Pikemen" msgstr "Piqueros veteranos" msgid "Swordsman" msgstr "Espadachín" msgid "Swordsmen" msgstr "Espadachín" msgid "Master Swordsman" msgstr "Maestro Espadachín" msgid "Master Swordsmen" msgstr "Maestros Espadachines" msgid "Cavalries" msgstr "Caballerías" msgid "Cavalry" msgstr "Caballería" msgid "Champion" msgstr "Campeón" msgid "Champions" msgstr "Campeones" msgid "Paladin" msgstr "Paladín" msgid "Paladins" msgstr "Paladines" msgid "Crusader" msgstr "Cruzado" msgid "Crusaders" msgstr "Cruzados" msgid "Goblin" msgstr "Trasgo" msgid "Goblins" msgstr "Trasgos" msgid "Orc" msgstr "Orco" msgid "Orcs" msgstr "Orcos" msgid "Orc Chief" msgstr "Jefe Orco" msgid "Orc Chiefs" msgstr "Jefes Orcos" msgid "Wolf" msgstr "Lobo" msgid "Wolves" msgstr "Lobos" msgid "Ogre" msgstr "Ogro" msgid "Ogres" msgstr "Ogros" msgid "Ogre Lord" msgstr "Ogro de Combate" msgid "Ogre Lords" msgstr "Ogros de Combate" msgid "Troll" msgstr "Troll" msgid "Trolls" msgstr "Trolls" msgid "War Troll" msgstr "Troll de Guerra" msgid "War Trolls" msgstr "Trolls de Guerra" msgid "Cyclopes" msgstr "Cíclopes" msgid "Cyclops" msgstr "Cíclope" msgid "Sprite" msgstr "Hada" msgid "Sprites" msgstr "Hadas" msgid "Dwarf" msgstr "Enano" msgid "Dwarves" msgstr "Enanos" msgid "Battle Dwarf" msgstr "Enano de Combate" msgid "Battle Dwarves" msgstr "Enanos de Combate" msgid "Elf" msgstr "Elfo" msgid "Elves" msgstr "Elfos" msgid "Grand Elf" msgstr "Gran Elfo" msgid "Grand Elves" msgstr "Grandes Elfos" msgid "Druid" msgstr "Druida" msgid "Druids" msgstr "Druidas" msgid "Greater Druid" msgstr "Gran Druida" msgid "Greater Druids" msgstr "Grandes Druidas" msgid "Unicorn" msgstr "Unicornio" msgid "Unicorns" msgstr "Unicornios" msgid "Phoenix" msgstr "Fénix" msgid "Phoenixes" msgstr "Fénix" msgid "Centaur" msgstr "Centauro" msgid "Centaurs" msgstr "Centauros" msgid "Gargoyle" msgstr "Gárgola" msgid "Gargoyles" msgstr "Gárgolas" msgid "Griffin" msgstr "Grifo" msgid "Griffins" msgstr "Grifos" msgid "Minotaur" msgstr "Minotauro" msgid "Minotaurs" msgstr "Minotauros" msgid "Minotaur King" msgstr "Minotauro Rey" msgid "Minotaur Kings" msgstr "Minotauros Reyes" msgid "Hydra" msgstr "Hidra" msgid "Hydras" msgstr "Hidras" msgid "Green Dragon" msgstr "Dragón Verde" msgid "Green Dragons" msgstr "Dragones Verdes" msgid "Red Dragon" msgstr "Dragón Rojo" msgid "Red Dragons" msgstr "Dragones Rojos" msgid "Black Dragon" msgstr "Dragón Negro" msgid "Black Dragons" msgstr "Dragones Negros" msgid "Halfling" msgstr "Mediano" msgid "Halflings" msgstr "Medianos" msgid "Boar" msgstr "Jabalí" msgid "Boars" msgstr "Jabalíes" msgid "Iron Golem" msgstr "Golem de Hierro" msgid "Iron Golems" msgstr "Golems de Hierro" msgid "Steel Golem" msgstr "Golem de Acero" msgid "Steel Golems" msgstr "Golems de Acero" msgid "Roc" msgstr "Roc" msgid "Rocs" msgstr "Rocs" msgid "Mage" msgstr "Mago" msgid "Magi" msgstr "Magos" msgid "Archmage" msgstr "Archimago" msgid "Archmagi" msgstr "Archimagos" msgid "Giant" msgstr "Gigante" msgid "Giants" msgstr "Gigantes" msgid "Titan" msgstr "Titán" msgid "Titans" msgstr "Titanes" msgid "Skeletons" msgstr "Esqueletos" msgid "Zombie" msgstr "Zombi" msgid "Zombies" msgstr "Zombis" msgid "Mutant Zombie" msgstr "Zombi Mutante" msgid "Mutant Zombies" msgstr "Zombis Mutantes" msgid "Mummies" msgstr "Momias" msgid "Mummy" msgstr "Momia" msgid "Royal Mummies" msgstr "Momias Reales" msgid "Royal Mummy" msgstr "Momia Real" msgid "Vampire" msgstr "Vampiro" msgid "Vampires" msgstr "Vampiros" msgid "Vampire Lord" msgstr "Maestro Vampiro" msgid "Vampire Lords" msgstr "Maestros Vampiros" msgid "Lich" msgstr "Liche" msgid "Liches" msgstr "Liches" msgid "Power Lich" msgstr "Gran Liche" msgid "Power Liches" msgstr "Grandes Liches" msgid "Bone Dragon" msgstr "Dragón de Hueso" msgid "Bone Dragons" msgstr "Dragones de Hueso" msgid "Rogue" msgstr "Bandido" msgid "Rogues" msgstr "Bandidos" msgid "Nomad" msgstr "Nómada" msgid "Nomads" msgstr "Nómadas" msgid "Ghost" msgstr "Fantasma" msgid "Ghosts" msgstr "Fantasmas" msgid "Genie" msgstr "Genio" msgid "Genies" msgstr "Genios" msgid "Medusa" msgstr "Medusa" msgid "Medusas" msgstr "Medusas" msgid "Earth Elemental" msgstr "Elemental de la Tierra" msgid "Earth Elementals" msgstr "Elementales de la Tierra" msgid "Air Elemental" msgstr "Elemental del Aire" msgid "Air Elementals" msgstr "Elementales del Aire" msgid "Fire Elemental" msgstr "Elemental del Fuego" msgid "Fire Elementals" msgstr "Elementales del Fuego" msgid "Water Elemental" msgstr "Elemental del Agua" msgid "Water Elementals" msgstr "Elementales del Agua" #, fuzzy msgid "Random Monsters" msgstr "Cualquier castillo" #, fuzzy msgid "Random Monster 1" msgstr "Cualquier castillo" #, fuzzy msgid "Random Monsters 1" msgstr "Cualquier castillo" #, fuzzy msgid "Random Monster 2" msgstr "Cualquier castillo" #, fuzzy msgid "Random Monsters 2" msgstr "Cualquier castillo" #, fuzzy msgid "Random Monster 3" msgstr "Cualquier castillo" #, fuzzy msgid "Random Monsters 3" msgstr "Cualquier castillo" #, fuzzy msgid "Random Monster 4" msgstr "Cualquier castillo" #, fuzzy msgid "Random Monsters 4" msgstr "Cualquier castillo" msgid "Double shot" msgstr "Doble tiro" msgid "2-hex monster" msgstr "Ocupa dos casillas" msgid "Double strike" msgstr "Doble golpe" msgid "Double damage to Undead" msgstr "Doble daño a nomuertos" msgid "% magic resistance" msgstr "% de resistencia mágica" msgid "Immune to Mind spells" msgstr "Inmune a los hechizos mentales" msgid "Immune to Elemental spells" msgstr "Inmune a hechizos elementales" msgid "Immune to Fire spells" msgstr "Inmune a los hechizos de fuego" msgid "Immune to Cold spells" msgstr "Inmune a los hechizos de frío" msgid "Immune to " msgstr "Inmune a " msgid "% immunity to %{spell} spell" msgstr "% de inmunidad al hechizo %{spell}" msgid "% damage from Elemental spells" msgstr "% de daño de los hechizos elementales" msgid "% chance to Dispel beneficial spells" msgstr "% de probabilidad de disipar hechizos beneficiosos" msgid "% chance to Paralyze" msgstr "% de probabilidad de paralizar" msgid "% chance to Petrify" msgstr "% de probabilidad de petrificar" msgid "% chance to Blind" msgstr "% de probabilidad de cegar" msgid "% chance to Curse" msgstr "% de probabilidad de maldecir" msgid "% chance to cast %{spell} spell" msgstr "% de probabilidad de lanzar hechizo de %{spell}" msgid "HP regeneration" msgstr "Regeneración de PV" msgid "Two hexes attack" msgstr "Ataque a dos casillas" msgid "Flyer" msgstr "Volador" msgid "Always retaliates" msgstr "Siempre contraataca" msgid "Attacks all adjacent enemies" msgstr "Ataca a todos los enemigos adyacentes" msgid "No melee penalty" msgstr "Ninguna penalización cuerpo a cuerpo" msgid "Dragon" msgstr "Dragón" msgid "Undead" msgstr "Nomuerto" msgid "No enemy retaliation" msgstr "Ningún contraataque enemigo" msgid "HP drain" msgstr "Drenaje PV" msgid "Cloud attack" msgstr "Ataque de nube" msgid "Decreases enemy's morale by " msgstr "Disminuye la moral del enemigo en " msgid "% chance to halve enemy" msgstr "% de probabilidad de reducir a la mitad a un enemigo" msgid "Soul Eater" msgstr "Devorador de almas" msgid "Elemental" msgstr "Elemental" msgid "No Morale" msgstr "Sin moral" msgid "200% damage from Fire spells" msgstr "200% de daño de los hechizos de fuego" msgid "200% damage from Cold spells" msgstr "200% de daño de los hechizos de frío" msgid "% damage from %{spell} spell" msgstr "% de daño del hechizo %{spell}" msgid "% immunity to " msgstr "% de inmunidad al " msgid "Lightning" msgstr "Rayo" msgid "% damage from " msgstr "% de daño de " msgid "The three Anduran artifacts magically combine into one." msgstr "Los tres artefactos mágicos de Anduran combinados en uno." #, fuzzy msgid "View Spells" msgstr "Ver Todo" #, fuzzy msgid "View %{name} Info" msgstr "Ver %{name}" msgid "Move %{name}" msgstr "" msgid "Cannot move the Spellbook" msgstr "No se puede transferir el Libro de Hechizos" msgid "This item can't be traded." msgstr "Este artefacto no puede transferirse." #, fuzzy msgid "Invalid Artifact" msgstr "Artefacto" msgid "The %{name} increases the hero's knowledge by %{count}." msgstr "%{name} aumenta lo conocimiento del héroe en %{count}." msgid "Ultimate Book of Knowledge" msgstr "El Libro Definitivo del Conocimiento" msgid "The %{name} increases the hero's attack skill by %{count}." msgstr "%{name} aumenta el ataque del héroe en %{count}." msgid "Ultimate Sword of Dominion" msgstr "La Espada Final de la Dominación" msgid "The %{name} increases the hero's defense skill by %{count}." msgstr "%{name} aumenta la defensa del héroe en %{count}." msgid "Ultimate Cloak of Protection" msgstr "La Capa Final de la Protección" msgid "The %{name} increases the hero's spell power by %{count}." msgstr "%{name} aumenta la potencia del héroe en %{count}." msgid "Ultimate Wand of Magic" msgstr "La Varíta Final de la Magia" msgid "" "The %{name} increases the hero's attack and defense skills by %{count} each." msgstr "%{name} aumenta el ataque y la defensa del héroe en %{count} cada uno." msgid "Ultimate Shield" msgstr "El Escudo Supremo" msgid "" "The %{name} increases the hero's spell power and knowledge by %{count} each." msgstr "" "%{name} aumenta la potencia y el conocimiento del héroe en %{count} cada uno." msgid "Ultimate Staff" msgstr "El Cayado Supremo" msgid "" "The %{name} increases each of the hero's basic skills by %{count} points." msgstr "" "%{name} aumenta cada una de las habilidades básicas del héroe en %{count} " "puntos." msgid "Ultimate Crown" msgstr "La Corona Suprema" msgid "Golden Goose" msgstr "El Ganso Dorado" msgid "The %{name} brings in an income of %{count} gold per day." msgstr "%{name} aporta unos ingresos de %{count} piezas de oro al día." msgid "Arcane Necklace of Magic" msgstr "El Collar Arcano de la Magia" msgid "" "After rescuing a Sorceress from a cursed tomb, she rewards your heroism with " "an exquisite jeweled necklace." msgstr "" msgid "Caster's Bracelet of Magic" msgstr "El Brazalete del Lanzador de la Magia" msgid "" "While searching through the rubble of a caved-in mine, you free a group of " "trapped Dwarves. Grateful, the leader gives you a golden bracelet." msgstr "" msgid "Mage's Ring of Power" msgstr "El Anillo del Mago del Poder" msgid "" "A cry of pain leads you to a Centaur, caught in a trap. Upon setting the " "creature free, he hands you a small pouch. Emptying the contents, you find a " "dazzling jeweled ring." msgstr "" msgid "Witch's Broach of Magic" msgstr "El Broche de Bruja de la Magia" msgid "" "Alongside the remains of a burnt witch lies a beautiful broach, intricately " "designed. Approaching the corpse with caution, you add the broach to your " "inventory." msgstr "" msgid "Medal of Valor" msgstr "La Medalla de Valor" msgid "The %{name} increases the morale of the hero's army by %{count}." msgstr "%{name} aumenta la moral del ejército del héroe en %{count}." msgid "" "Freeing a virtuous maiden from the clutches of an evil overlord, you are " "granted a Medal of Valor by the King's herald." msgstr "" msgid "Medal of Courage" msgstr "La Medalla de Coraje" msgid "" "After saving a young boy from a vicious pack of Wolves, you return him to " "his father's manor. The grateful nobleman awards you with a Medal of Courage." msgstr "" msgid "Medal of Honor" msgstr "La Medalla de Honor" msgid "" "After freeing a princess of a neighboring kingdom from the evil clutches of " "despicable slavers, she awards you with a Medal of Honor." msgstr "" msgid "Medal of Distinction" msgstr "La Medalla de Distinción" msgid "" "Ridding the countryside of the hideous Minotaur who made a sport of eating " "noblemen's Knights, you are honored with the Medal of Distinction." msgstr "" msgid "Fizbin of Misfortune" msgstr "El Fizbin de la Mala Suerte" msgid "" "The %{name} greatly decreases the morale of the hero's army by %{count}." msgstr "" "%{name} disminuye enormemente la moral del ejército del héroe en %{count}." msgid "" "You stumble upon a medal lying alongside the empty road. Adding the medal to " "your inventory, you become aware that you have acquired the undesirable " "Fizbin of Misfortune, greatly decreasing your army's morale." msgstr "" msgid "Thunder Mace of Dominion" msgstr "La Maza del Trueno de Dominio" msgid "" "During a sudden storm, a bolt of lightning strikes a tree, splitting it. " "Inside the tree you find a mysterious mace." msgstr "" msgid "Armored Gauntlets of Protection" msgstr "Los Guanteletes Blindados de Protección" msgid "The %{name} increase the hero's defense skill by %{count}." msgstr "%{name} aumenta la defensa del héroe en %{count}." msgid "" "You encounter the infamous Black Knight! After a grueling duel ending in a " "draw, the Knight, out of respect, offers you a pair of armored gauntlets." msgstr "" msgid "Defender Helm of Protection" msgstr "El Yelmo Defensor de la Protección" msgid "" "A glint of golden light catches your eye. Upon further investigation, you " "find a golden helm hidden under a bush." msgstr "" msgid "Giant Flail of Dominion" msgstr "El Mangual Gigante de la Dominación" msgid "" "A clumsy Giant has killed himself with his own flail. Knowing your superior " "skill with this weapon, you confidently remove the spectacular flail from " "the fallen Giant." msgstr "" msgid "Ballista of Quickness" msgstr "La Ballesta de Rapidez" msgid "The %{name} gives the hero's catapult one extra shot per combat round." msgstr "" "%{name} otorga a la catapulta del héroe un disparo extra por ronda de " "combate." msgid "" "Walking through the ruins of an ancient walled city, you find the instrument " "of the city's destruction, an elaborately crafted ballista." msgstr "" msgid "Stealth Shield of Protection" msgstr "El Escudo Sigiloso de la Protección" msgid "" "A stone statue of a warrior holds a silver shield. As you remove the shield, " "the statue crumbles into dust." msgstr "" msgid "Dragon Sword of Dominion" msgstr "La Espada Dragón de la Dominación" msgid "" "As you are walking along a narrow path, a nearby bush suddenly bursts into " "flames. Before your eyes the flames become the image of a beautiful woman. " "She holds out a magnificent sword to you." msgstr "" msgid "Power Axe of Dominion" msgstr "La Hacha Poderosa de la Dominación" msgid "" "You see a silver axe embedded deeply in the ground. After several " "unsuccessful attempts by your army to remove the axe, you tightly grip the " "handle of the axe and effortlessly pull it free." msgstr "" msgid "Divine Breastplate of Protection" msgstr "La Coraza Divina de Protección" msgid "" "A gang of Rogues is sifting through the possessions of dead warriors. " "Scaring off the scavengers, you note the Rogues had overlooked a beautiful " "breastplate." msgstr "" msgid "Minor Scroll of Knowledge" msgstr "El Pergamino Menor de Conocimiento" msgid "" "Before you appears a levitating glass case with a scroll, perched upon a bed " "of crimson velvet. At your touch, the lid opens and the scroll floats into " "your awaiting hands." msgstr "" msgid "Major Scroll of Knowledge" msgstr "El Rollo Mayor del Conocimiento" msgid "" "Visiting a local wiseman, you explain the intent of your journey. He reaches " "into a sack and withdraws a yellowed scroll and hands it to you." msgstr "" msgid "Superior Scroll of Knowledge" msgstr "El Rollo Superior del Conocimiento" msgid "" "You come across the remains of an ancient Druid. Bones, yellowed with age, " "peer from the ragged folds of her robe. Searching the robe, you discover a " "scroll hidden in the folds." msgstr "" msgid "Foremost Scroll of Knowledge" msgstr "El Principal Pergamino de Conocimiento" msgid "" "Mangled bones, yellowed with age, peer from the ragged folds of a dead " "Druid's robe. Searching the robe, you discover a scroll hidden within." msgstr "" msgid "Endless Sack of Gold" msgstr "La Saca inagotable de Oro" msgid "The %{name} provides the hero with %{count} gold per day." msgstr "%{name} proporciona al héroe %{count} piezas de oro al día." msgid "" "A little leprechaun dances gleefully around a magic sack. Seeing you " "approach, he stops in mid-stride. The little man screams and stamps his foot " "ferociously, vanishing into thin air. Remembering the old leprechaun saying " "'Finders Keepers', you grab the sack and leave." msgstr "" msgid "Endless Bag of Gold" msgstr "La Bolsa inagotable de Oro" msgid "" "A noblewoman, separated from her traveling companions, asks for your help. " "After escorting her home, she rewards you with a bag filled with gold." msgstr "" msgid "Endless Purse of Gold" msgstr "La Cartera inagotable de Oro" msgid "" "In your travels, you find a leather purse filled with gold that once " "belonged to a great warrior king who had the ability to transform any " "inanimate object into gold." msgstr "" msgid "Nomad Boots of Mobility" msgstr "Las Botas Nómadas de la Movilidad" msgid "The %{name} increase the hero's movement on land." msgstr "%{name} aumenta el movimiento del héroe en tierra." msgid "" "A Nomad trader seeks protection from a tribe of Goblins. For your " "assistance, he gives you a finely crafted pair of boots made from the " "softest leather. Looking closely, you see fascinating ancient carvings " "engraved on the leather." msgstr "" msgid "Traveler's Boots of Mobility" msgstr "Las Botas de Viajero de la Movilidad" msgid "" "Discovering a pair of beautifully beaded boots made from the finest and " "softest leather, you thank the anonymous donor and add the boots to your " "inventory." msgstr "" msgid "Lucky Rabbit's Foot" msgstr "La Pata de Conejo de la Suerte" msgid "The %{name} increases the luck of the hero's army by %{count}." msgstr "%{name} aumenta la suerte del ejército del héroe en %{count}." msgid "" "A traveling merchant offers you a rabbit's foot, made of gleaming silver " "fur, for safe passage. The merchant explains the charm will increase your " "luck in combat." msgstr "" msgid "Golden Horseshoe" msgstr "La Herradura Dorada" msgid "" "An ensnared Unicorn whinnies in fright. Murmuring soothing words, you set " "her free. Snorting and stamping her front hoof once, she gallops off. " "Looking down you see a golden horseshoe." msgstr "" msgid "Gambler's Lucky Coin" msgstr "La Moneda de la Suerte del Jugador" msgid "" "You have captured a mischievous imp who has been terrorizing the region. In " "exchange for his release, he rewards you with a magical coin." msgstr "" msgid "Four-Leaf Clover" msgstr "El Trébol de 4 Hojas" msgid "" "In the middle of a patch of dead and dry vegetation, to your surprise you " "find a healthy green four-leaf clover." msgstr "" msgid "True Compass of Mobility" msgstr "La Brújula de la Verdera Movilidad" msgid "The %{name} increases the hero's movement on land and sea." msgstr "%{name} aumenta el movimiento del héroe en tierra y mar." msgid "" "An old man claiming to be an inventor asks you to try his latest invention. " "He then hands you a compass." msgstr "" msgid "Sailor's Astrolabe of Mobility" msgstr "El Astrolabio del Marinero de la Movilidad" msgid "The %{name} increases the hero's movement on sea." msgstr "%{name} aumenta el movimiento del héroe en el mar." msgid "" "An old sea captain is being tortured by Ogres. You save him, and in return " "he rewards you with a wondrous instrument to measure the distance of a star." msgstr "" msgid "Evil Eye" msgstr "El Ojo Maligno" msgid "The %{name} reduces the casting cost of curse spells by half." msgstr "" "%{name} reduce a la mitad el coste de lanzamiento de los hechizos de " "maldición." msgid "" "While venturing into a decrepit hut you find the Skeleton of a long dead " "witch. Investigation of the remains reveals a glass eye rolling around " "inside an empty skull." msgstr "" msgid "Enchanted Hourglass" msgstr "El Reloj de Arena Encantado" msgid "" "The %{name} extends the duration of all the hero's spells by %{count} turns." msgstr "" "%{name} prolonga la duración de todos los hechizos del héroe en %{count} " "turnos." msgid "" "A surprise turn in the landscape finds you in the midst of a grisly scene: " "Vultures picking at the aftermath of a terrible battle. Your cursory search " "of the remains turns up an enchanted hourglass." msgstr "" msgid "Gold Watch" msgstr "El Reloj de Oro" msgid "The %{name} doubles the effectiveness of the hero's hypnotize spells." msgstr "%{name} dobla la eficacia de los hechizos de hipnosis del héroe." msgid "" "In reward for helping his cart out of a ditch, a traveling potion salesman " "gives you a \"magic\" gold watch. Unbeknownst to him, the watch really is " "magical." msgstr "" msgid "Skullcap" msgstr "El Casco de Cráneo" msgid "The %{name} halves the casting cost of all mind influencing spells." msgstr "" "%{name} reduce a la mitad el coste de lanzamiento de todos los hechizos que " "influencian la mente." msgid "" "A brief stop at an improbable rural inn yields an exchange of money, tales, " "and accidentally, luggage. You find a magical skullcap in your new backpack." msgstr "" msgid "Ice Cloak" msgstr "La Capa de Hielo" msgid "" "The %{name} halves all damage the hero's troops receive from cold spells." msgstr "" "%{name} reduce a la mitad todo el daño que las tropas del héroe reciben de " "hechizos de frío." msgid "" "Responding to the panicked cries of a damsel in distress, you discover a " "young woman fleeing from a hungry bear. You slay the beast in the nick of " "time, and the grateful Sorceress weaves a magic cloak from the bear's hide." msgstr "" msgid "Fire Cloak" msgstr "La Capa de Fuego" msgid "" "The %{name} halves all damage the hero's troops receive from fire spells." msgstr "" "%{name} reduce a la mitad todo el daño que las tropas del héroe reciben de " "hechizos de fuego." msgid "" "You've come upon a fight between a Necromancer and a Paladin. The " "Necromancer blasts the Paladin with a fire bolt, bringing him to his knees. " "Acting quickly, you slay the evil one before the final blow. The grateful " "Paladin gives you the fire cloak that saved him." msgstr "" msgid "Lightning Helm" msgstr "El Casco Relampagueante" msgid "" "The %{name} halves all damage the hero's troops receive from lightning " "spells." msgstr "" msgid "" "A traveling tinker in need of supplies offers you a helm with a thunderbolt " "design on its top in exchange for food and water. Curious, you accept, and " "later find out that the helm is magical." msgstr "" msgid "Evercold Icicle" msgstr "El Carámbano del Frío Eterno" msgid "" "The %{name} causes the hero's cold spells to do %{count} percent more damage " "to enemy troops." msgstr "" "%{name} hace que los hechizos de frío del héroe inflijan un %{count}% más de " "daño a las tropas enemigas." msgid "" "An icicle withstanding the full heat of the noonday sun attracts your " "attention. Intrigued, you break it off, and find that it does not melt in " "your hand." msgstr "" msgid "Everhot Lava Rock" msgstr "La Roca de Lava del Calor Eterno" msgid "" "The %{name} causes the hero's fire spells to do %{count} percent more damage " "to enemy troops." msgstr "" msgid "" "Your wanderings bring you into contact with a tribe of ape-like beings using " "a magical lava rock that never cools to light their fires. You take pity on " "them and teach them to make fire with sticks. Believing you to be a god, the " "apes give you their rock." msgstr "" msgid "Lightning Rod" msgstr "La Vara Relampagueante" msgid "" "The %{name} causes the hero's lightning spells to do %{count} percent more " "damage to enemy troops." msgstr "" msgid "" "While waiting out a storm, a lighting bolt strikes a nearby cottage's " "lightning rod, which melts and falls to the ground. The tip of the rod, " "however, survives intact and makes your hair stand on end when you touch it. " "Hmm..." msgstr "" msgid "Snake-Ring" msgstr "El Anillo-Serpiente" msgid "The %{name} halves the casting cost of all of the hero's bless spells." msgstr "" msgid "" "You've found an oddly shaped ring on the finger of a long dead traveler. The " "ring looks like a snake biting its own tail." msgstr "" msgid "Ankh" msgstr "Llave de la Vida" msgid "" "The %{name} doubles the effectiveness of all of the hero's resurrect and " "animate spells." msgstr "" msgid "" "A fierce windstorm reveals the entrance to a buried tomb. Your investigation " "reveals that the tomb has already been looted, but the thieves overlooked an " "ankh on a silver chain in the dark." msgstr "" msgid "Book of Elements" msgstr "El Libro de los Elementos" msgid "" "The %{name} doubles the effectiveness of all of the hero's summoning spells." msgstr "" msgid "" "You come across a conjurer who begs to accompany you and your army awhile " "for safety. You agree, and he offers as payment a copy of the book of the " "elements." msgstr "" msgid "Elemental Ring" msgstr "El Anillo Elemental" msgid "The %{name} halves the casting cost of all summoning spells." msgstr "" msgid "" "While pausing to rest, you notice a bobcat climbing a short tree to get at a " "crow's nest. On impulse, you climb the tree yourself and scare off the cat. " "When you look in the nest, you find a collection of shiny stones and a ring." msgstr "" msgid "Holy Pendant" msgstr "El Colgante Sagrado" msgid "The %{name} makes all of the hero's troops immune to curse spells." msgstr "" msgid "" "In your wanderings you come across a hermit living in a small, tidy hut. " "Impressed with your mission, he takes time out from his meditations to bless " "and give you a charm against curses." msgstr "" msgid "Pendant of Free Will" msgstr "El Colgante de la Voluntad Libre" msgid "The %{name} makes all of the hero's troops immune to hypnotize spells." msgstr "" msgid "" "Responding to cries for help, you find river Sprites making a sport of " "dunking an old man. Feeling vengeful, you rescue the man and drag a Sprite " "onto dry land for awhile. The Sprite, uncomfortable in the air, gives you a " "magic pendant to let him go." msgstr "" msgid "Pendant of Life" msgstr "El Colgante de la Vida" msgid "The %{name} makes all of the hero's troops immune to death spells." msgstr "" msgid "" "A brief roadside encounter with a small caravan and a game of knucklebones " "wins a magic pendant. Its former owner says that it protects from " "Necromancers' death spells." msgstr "" msgid "Serenity Pendant" msgstr "El Colgante de la Serenidad" msgid "The %{name} makes all of the hero's troops immune to berserk spells." msgstr "" msgid "" "The sounds of combat draw you to the scene of a fight between an old " "Barbarian and an eight-headed Hydra. Your timely intervention swings the " "battle in favor of the man, and he rewards you with a pendant he used to use " "to calm his mind for battle." msgstr "" msgid "Seeing-eye Pendant" msgstr "El Colgante Visionario" msgid "The %{name} makes all of the hero's troops immune to blindness spells." msgstr "" msgid "" "You come upon a very old woman, long blind from cataracts and dying alone. " "You tend to her final needs and promise a proper burial. Grateful, she gives " "you a magic pendant emblazoned with a stylized eye. It lets you see with " "your eyes closed." msgstr "" msgid "Kinetic Pendant" msgstr "El Colgante Cinético" msgid "The %{name} makes all of the hero's troops immune to paralyze spells." msgstr "" msgid "" "You come across a golem wearing a glowing pendant and blocking your way. " "Acting on a hunch, you cut the pendant from its neck. Deprived of its power " "source, the golem breaks down, leaving you with the magical pendant." msgstr "" msgid "Pendant of Death" msgstr "El Colgante de la Muerte" msgid "The %{name} makes all of the hero's troops immune to holy spells." msgstr "" msgid "" "A quick and deadly battle with a Necromancer wins you his magical pendant. " "Later, a Wizard tells you that the pendant protects undead under your " "control from holy word spells." msgstr "" msgid "Wand of Negation" msgstr "La Varita de la Negación" msgid "" "The %{name} makes all of the hero's troops immune to dispel magic spells." msgstr "" msgid "" "You meet an old Wizard friend of yours traveling in the opposite direction. " "He presents you with a gift: A wand that prevents the use of the dispel " "magic spell on your allies." msgstr "" msgid "Golden Bow" msgstr "El Arco Dorado" msgid "" "The %{name} eliminates the %{count} percent penalty for the hero's troops " "shooting past obstacles (e.g. castle walls)." msgstr "" msgid "" "A chance meeting with a famous Archer finds you in a game of knucklebones " "pitting his bow against your horse. You win." msgstr "" msgid "Telescope" msgstr "El Telescopio" msgid "" "The %{name} increases the amount of terrain the hero reveals when " "adventuring by %{count} extra square." msgstr "" "%{name} aumentan la cantidad de terreno que el héroe revela al aventurarse " "en %{count} casilla extra." msgid "" "A merchant from far away lands trades you a new invention of his people for " "traveling supplies. It makes distant objects appear closer, and he calls " "it...\n" "\n" "a telescope." msgstr "" msgid "Statesman's Quill" msgstr "La Pluma de Estadista" msgid "" "The %{name} reduces the cost of surrender to %{count} percent of the total " "cost of troops the hero has in their army." msgstr "" msgid "" "You pause to help a diplomat with a broken axle fix his problem. In " "gratitude, he gives you a writing quill with magical properties which he " "says will \"help people see things your way\"." msgstr "" msgid "Wizard's Hat" msgstr "El Sombrero de Mago" msgid "" "The %{name} increases the duration of the hero's spells by %{count} turns." msgstr "" "%{name} aumenta la duración de los hechizos del héroe en %{count} turnos." msgid "" "You see a Wizard fleeing from a Griffin and riding like the wind. The Wizard " "opens a portal and rides through, getting his hat knocked off by the edge of " "the gate. The Griffin follows; the gate closes. You pick the hat up, dust it " "off, and put it on." msgstr "" msgid "Power Ring" msgstr "El Anillo de Poder" msgid "The %{name} returns %{count} extra spell points per day to the hero." msgstr "%{name} devuelve %{count} puntos de hechizo extra por día al héroe." msgid "" "You find a small tree that closely resembles the great Warlock Carnauth with " "a ring around one of its twigs. Scraps of clothing and rotting leather lead " "you to suspect that it IS Carnauth, transformed. Since you can't help him, " "you take the magic ring." msgstr "" msgid "Ammo Cart" msgstr "El Carro de Munición" msgid "" "The %{name} provides endless ammunition for all of the hero's troops that " "shoot." msgstr "" msgid "" "An ammunition cart in the middle of an old battlefield catches your eye. " "Inspection shows it to be in good working order, so you take it along." msgstr "" msgid "Tax Lien" msgstr "La Tasa de Embargo" msgid "The %{name} costs the hero %{count} gold pieces per day." msgstr "%{name} le cuesta al héroe %{count} piezas de oro al día." msgid "" "Your big spending habits have earned you a massive tax bill that you can't " "hope to pay. The tax man takes pity and agrees to only take 250 gold a day " "from your account for life. Check here if you want one dollar to go to the " "presidential campaign election fund." msgstr "" msgid "Hideous Mask" msgstr "La Máscara Horrenda" msgid "The %{name} prevents all 'wandering' armies from joining the hero." msgstr "" msgid "" "Your looting of the grave of Sinfilas Gardolad, the famous shapeshifting " "Warlock, unearths his fabled mask. Trembling, you put it on and it twists " "your visage into an awful grimace! Oh no! It's actually the hideous mask of " "Gromluck Greene, and you are stuck with it." msgstr "" msgid "Endless Pouch of Sulfur" msgstr "La Bolsa Inagotable de Sulfuro" msgid "The %{name} provides %{count} unit of sulfur per day." msgstr "" msgid "" "You visit an alchemist who, upon seeing your army, is swayed by the " "righteousness of your cause. The newly loyal subject gives you his endless " "pouch of sulfur to help with the war effort." msgstr "" msgid "Endless Vial of Mercury" msgstr "El Frasco Inagotable de Mercurio" msgid "The %{name} provides %{count} unit of mercury per day." msgstr "" msgid "" "A brief stop at a hastily abandoned Wizard's tower turns up a magical vial " "of mercury that always has a little left on the bottom. Recognizing a " "treasure when you see one, you cap it and slip it in your pocket." msgstr "" msgid "Endless Pouch of Gems" msgstr "La Bolsa Inagotable de Gemas" msgid "The %{name} provides %{count} unit of gems per day." msgstr "" msgid "" "A short rainstorm brings forth a rainbow...and you can see the end of it. " "Riding quickly, you seize the pot of gold you find there. The leprechaun who " "owns it, unable to stop you from taking it, offers an endless pouch of gems " "for the return of his gold. You accept." msgstr "" msgid "Endless Cord of Wood" msgstr "La Cuerda Interminable de Madera" msgid "The %{name} provides %{count} unit of wood per day." msgstr "" msgid "" "Pausing to rest and light a cook fire, you pull wood out of a nearby pile of " "dead wood. As you keep pulling wood from the pile, you notice that it " "doesn't shrink. You realize to your delight that the wood is enchanted, so " "you take it along." msgstr "" msgid "Endless Cart of Ore" msgstr "El Carro de Piedra Inagotable" msgid "The %{name} provides %{count} unit of ore per day." msgstr "" msgid "" "You've found a Goblin weapon smithy making weapons for use against humans. " "With a tremendous yell you and your army descend upon their camp and drive " "them away. A search finds a magic ore cart that never runs out of iron." msgstr "" msgid "Endless Pouch of Crystal" msgstr "La Bolsa Inagotable de Cristal" msgid "The %{name} provides %{count} unit of crystal per day." msgstr "%{name} proporciona %{count} unidades de cristal al día." msgid "" "Taking shelter from a storm in a small cave, you notice a small patch of " "crystal in one corner. Curious, you break a piece off and notice that the " "original crystal grows the lost piece back. You decide to stuff the entire " "patch into a pouch and take it with you." msgstr "" msgid "Spiked Helm" msgstr "El Casco Picudo" msgid "" "Your army is ambushed by a small tribe of wild (and none too bright) Orcs. " "You fend them off easily and the survivors flee in all directions. One of " "the Orcs was wearing a polished spiked helm. Figuring it will make a good " "souvenir, you take it." msgstr "" msgid "Spiked Shield" msgstr "El Escudo Picudo" msgid "" "You come upon a bridge spanning a dry gully. Before you can cross, a Troll " "steps out from under the bridge and demands payment before it will permit " "you to pass. You refuse, and the Troll charges, forcing you to slay it. You " "take its spiked shield as a trophy." msgstr "" msgid "White Pearl" msgstr "La Perla Blanca" msgid "" "A walk across a dry saltwater lake bed yields an unlikely prize: A white " "pearl amidst shattered shells and debris." msgstr "" msgid "Black Pearl" msgstr "La Perla Negra" msgid "" "Rumors of a Griffin of unusual size preying upon the countryside lead you to " "its cave lair. A quick, brutal fight dispatches the beast, and a search of " "its foul nest turns up a huge black pearl." msgstr "" msgid "Magic Book" msgstr "El Libro de Hechizos" msgid "The %{name} enables the hero to cast spells." msgstr "%{name} permite al héroe lanzar hechizos." #, fuzzy msgid "Dummy 1" msgstr "Momia" #, fuzzy msgid "The reserved artifact." msgstr " con un artefacto" #, fuzzy msgid "Dummy 2" msgstr "Momia" #, fuzzy msgid "Dummy 3" msgstr "Momia" #, fuzzy msgid "Dummy 4" msgstr "Momia" msgid "Spell Scroll" msgstr "Pegamino del Hechizo" msgid "" "This %{name} gives the hero the ability to cast the %{spell} spell if the " "hero has a Magic Book." msgstr "" "Este %{name} le da a tu héroe la habilidad de lanzar el hechizo %{spell} si " "el héroe tiene un Libro de Hechizos." msgid "" "You find an elaborate container which houses an old vellum scroll. The runes " "on the container are very old, and the artistry with which it was put " "together is stunning. As you pull the scroll out, you feel imbued with " "magical power." msgstr "" "Encuentras una caja labrada que contiene un antiguo rollo de pergamino. Las " "runas de la caja son muy antiguas, y la destreza con la que se elaboró es " "impresionante. Al sacar el pergamino de la caja sientes cómo el poder mágico " "te envuelve." msgid "Arm of the Martyr" msgstr "El Brazo del Martir" msgid "" "The %{name} increases the hero's spell power by %{count} but adds the undead " "morale penalty." msgstr "" "%{name} aumenta el poder de hechizo del héroe en %{count}, pero añade la " "penalización de moral de nomuerto al ejército." msgid "" "One of the less intelligent members of your party picks up an arm off of the " "ground. Despite its missing a body, it is still moving. Your troops find the " "dismembered arm repulsive, but you cannot bring yourself to drop it: it " "seems to hold some sort of magical power that influences your decision " "making." msgstr "" "Uno de los miembros menos inteligentes de tu grupo recoge un brazo del " "suelo. A pesar de estar amputado del cuerpo, aún se mueve. Tus tropas " "encuentran el brazo repulsivo, pero no puedes dejarlo: parece que posee " "algún tipo de poder mágico que influye en tu capacidad de decisión." msgid "Breastplate of Anduran" msgstr "El Peto de Anduran" msgid "The %{name} increases the hero's defense by %{count}." msgstr "%{name} aumenta la defensa del héroe en %{count}." msgid "" "You come upon a sign. It reads: \"Here lies the body of Anduran. Bow and " "swear fealty, and you shall be rewarded.\" You decide to do as it says. As " "you stand up, you feel a coldness against your skin. Looking down, you find " "that you are suddenly wearing a gleaming, ornate breastplate." msgstr "" "Encuentras una señal. En ella se lee: \"Aquí yace el cuerpo de Anduran. " "Inclínate y jura fidelidad, y serás recompensad.\" Decides hacer tal y como " "dice. Al levantarte, sientes frío contra la piel. Al mirar hacia abajo, ves " "que de repente estás llevando una brillante y ornada coraza." msgid "Broach of Shielding" msgstr "El Broche de Protección" msgid "" "The %{name} provides %{count} percent protection from Armageddon and " "Elemental Storm, but decreases spell power by 2." msgstr "" msgid "" "A kindly Sorceress thinks that your army's defenses could use a magical " "boost. She offers to enchant the Broach that you wear on your cloak, and you " "accept." msgstr "" "Una amable Hechicera piensa que a las defensas de tu ejército le vendría " "bien un plus mágico. Se ofrece para encantar el Broche de tu capa, y tú " "aceptas." msgid "Battle Garb of Anduran" msgstr "El Traje de Batalla de Anduran" msgid "" "The %{name} combines the powers of the three Anduran artifacts. It provides " "maximum luck and morale for the hero's troops and gives the hero the Town " "Portal spell." msgstr "" msgid "" "Out of pity for a poor peasant, you purchase a chest of old junk they are " "hawking for too much gold. Later, as you search through it, you find it " "contains the 3 pieces of the legendary battle garb of Anduran!" msgstr "" "Al darte pena un pobre campesino, le compras un baúl lleno de viejos " "cachivaches, por el que pedía demasiado dinero. Más tarde, cuando estás " "revisando su contenido, ¡encuentras en su interior las tres piezas del " "legendario traje de combate de Anduran!" msgid "Crystal Ball" msgstr "La Bola de Cristal" msgid "" "The %{name} lets the hero get more specific information about monsters, " "enemy heroes, and castles nearby the hero." msgstr "" msgid "" "You come upon a caravan of gypsies who are feasting and fortifying their " "bodies with mead. They call you forward and say \"If you prove that you can " "dance the Rama-Buta, we will reward you.\" You don't know it, but try " "anyway. They laugh hysterically, but admire your bravery, giving you a " "Crystal Ball." msgstr "" "Te encuentras con una caravana de gitanos que están de fiesta, fortaleciendo " "sus cuerpos con hidromiel. Te invitan a unirte a ellos, diciéndote: \"Si " "demuestras que eres capaz de bailar el Rama-Buta, te recompensaremos.\" No " "tienes ni idea, pero aún así lo intentas. Se ríen a carcajadas, pero admiran " "tu coraje, y te regalan una Bola de Cristal." msgid "Heart of Fire" msgstr "El Corazón de Fuego" msgid "" "The %{name} provides %{count} percent protection from fire, but doubles the " "damage taken from cold." msgstr "" msgid "" "You enter a recently burned glade and come upon a Fire Elemental sitting " "atop a rock. It looks up, its flaming face contorted in a look of severe " "pain. It then tosses a glowing object at you. You put up your hands to block " "it, but it passes right through them and sears itself into your chest." msgstr "" "Entras en un claro quemado recientemente y te encuentras con un Elemental de " "Fuego, sentado en lo alto de una roca. Éste alza la vista, con la cara " "desfigurada en una mueca de dolor intenso. Entonces te lanza un objeto " "brillante. Te cubres con las manos, pero las atraviesa, y te quema, " "incrustándose en tu pecho." msgid "Heart of Ice" msgstr "El Corazón de Hielo" msgid "" "The %{name} provides %{count} percent protection from cold, but doubles the " "damage taken from fire." msgstr "" #, fuzzy msgid "" "Suddenly, a biting coldness engulfs your body. You seize up, falling from " "your horse. The pain subsides, but you still feel as if your chest is " "frozen. As you pick yourself up off of the ground, you hear hearty laughter. " "You turn around just in time to see a Frost Giant run off into the woods and " "disappear." msgstr "" "De repente, un frío aterrador envuelve tu cuerpo. Te agarrotas de tal modo " "que te caes del caballo. El dolor se suaviza lentamente, pero aún tienes la " "sensación de tener el pecho congelado. Al levantarte del suelo, oyes una " "sonora carcajada. Te das la vuelta justo a tiempo para ver a un Gigante de " "Hielo huyendo hacia los bosques hasta desaparecer." msgid "Helmet of Anduran" msgstr "El Casco de Anduran" msgid "" "You spy a gleaming object poking up out of the ground. You send a member of " "your party over to investigate. He comes back with a golden helmet in his " "hands. You realize that it must be the helmet of the legendary Anduran, the " "only man who was known to wear solid gold armor." msgstr "" "Ves un objeto brillante que sobresale del suelo. Envías a un miembro de tu " "grupo a investigar. Regresa con un casco dorado en las manos. Te das cuenta " "de que debe de ser el casco del legendario Anduran, el único hombre que haya " "llevado una armadura de oro macizo." msgid "Holy Hammer" msgstr "El Martillo Sagrado" #, fuzzy msgid "" "You come upon a battle where a Paladin has been mortally wounded by a group " "of Zombies. He asks you to take his hammer and finish what he started. As " "you pick it up, it begins to hum, and then everything becomes a blur. The " "Zombies lie dead, the hammer dripping with blood. You strap it to your belt." msgstr "" "Te encuentras con una batalla en la que un Paladín ha sido mortalmente " "herido por un grupo de Zombies. Te pide que tomes su martillo y termines lo " "que él empezó. Al tomarlo en tu mano, comienza a zumbar, y todo se convierte " "en un borrón. Los zombies yacen muertos, y el martillo gotea su sangre. Lo " "enganchas a tu cinturón." msgid "Legendary Scepter" msgstr "El Cetro Legendario" msgid "The %{name} adds %{count} points to all attributes." msgstr "" msgid "" "Upon cresting a small hill, you come upon a ridiculous looking sight. A " "Sprite is attempting to carry a Scepter that is almost as big as it is. " "Trying not to laugh, you ask, \"Need help?\" The Sprite glares at you and " "answers: \"You think this is funny? Fine. You can carry it. I much prefer " "flying anyway.\"" msgstr "" "Al llegar a lo alto de una pequeña colina, te encuentras con una vista " "ridícula. Un hada está intentando llevar un Cetro que es casi tan grande " "como ella. Intentando no reir, le preguntas \"¿Necesitas ayuda?\". El hada " "te mira y te responde: \"¿Crees que es divertido? Vale. Puedes llevártelo. " "De cualquier modo, prefiero volar.\"" msgid "" "An old seaman tells you a tale of an enchanted masthead that he used in his " "youth to rally his crew during times of trouble. He then hands you a faded " "map that shows where he hid it. After much exploring, you find it stashed " "underneath a nearby dock." msgstr "" "Un viejo marinero te cuenta una historia acerca de un \"masthead\" que solía " "usar para arengar a su tripulación en momentos difíciles. Te da entonces un " "mapa borroso que muestra dónde lo escondió. Después de un buen rato " "explorando, lo encuentras escondido debajo de un embarcadero cercano." msgid "Masthead" msgstr "El Mástil" msgid "" "The %{name} boosts the hero's troops' luck and morale by %{count} each in " "sea combat." msgstr "" "%{name} aumenta la suerte y la moral de las tropas del héroe en %{count} " "cada una en combate marítimo." msgid "Sphere of Negation" msgstr "La Esfera de la Negación" msgid "The %{name} disables all spell casting, for both sides, in combat." msgstr "" msgid "" "You stop to help a Peasant catch a runaway mare. To show his gratitude, he " "hands you a tiny sphere. As soon as you grasp it, you feel the magical " "energy drain from your limbs..." msgstr "" "Te detienes a ayudar a un campesino, que trata de capturar a una yegua " "escapada. En muestra de gratitud, te entrega una esfera diminuta. Tan pronto " "como la coges, sientes la energía mágica fluir de tus miembros..." msgid "Staff of Wizardry" msgstr "El Cayado de Brujería" msgid "The %{name} boosts the hero's spell power by %{count}." msgstr "%{name} aumenta la potencia de hechizo del héroe en %{count}." msgid "" "While out scaring up game, your troops find a mysterious staff levitating " "about three feet off of the ground. They hand it to you, and you notice an " "inscription. It reads: \"Brains best brawn and magic beats might. Heed my " "words, and you'll win every fight.\"" msgstr "" "Al dejar la caza, asustados, tus tropas encuentran un misterioso bastón " "levitando unos tres pies por encima del suelo. Ellos te lo traen, y notas " "una inscripción en él. Se lee: \"Cerebro mejor que musculo y la magia vence " "a la fuerza. Considera mis palabras, y ganarás todas las batallas\"." msgid "Sword Breaker" msgstr "El Quebranta Espadas" msgid "The %{name} increases the hero's defense by %{count} and attack by 1." msgstr "%{name} aumenta la defensa del héroe en %{count} y el ataque en 1." msgid "" "A former Captain of the Guard admires your quest and gives you the enchanted " "Sword Breaker that he relied on during his tour of duty." msgstr "" "Un Capitán de la Guardia retirado admira tu búsqueda, y te entrega la " "Rompedora de Espadas encantada en la que confiaba durante sus años de " "servicio." msgid "Sword of Anduran" msgstr "La Espada de Anduran" msgid "" "A Troll stops you and says: \"Pay me 5,000 gold, or the Sword of Anduran " "will slay you where you stand.\" You refuse. The troll grabs the sword " "hanging from its belt, screams in pain, and runs away. Picking up the fabled " "sword, you give thanks that half-witted Trolls tend to grab the wrong end of " "sharp objects." msgstr "" "Un Troll te detiene, diciendo: \"Págame 5,000 de oro, o la Espada de Anduran " "te abatirá donde estás.\" Te niegas. El Troll coge la espada que cuelga de " "su cinturón, chilla de dolor, y huye corriendo. Al recoger la fabulosa " "espada, das gracias por los trolls medio idiotas que tienden a agarrar el " "extremo incorrecto de los objetos afilados." msgid "Spade of Necromancy" msgstr "La Pica de Nigromancia" msgid "The %{name} gives the hero increased necromancy skill." msgstr "" msgid "" "A dirty shovel has been thrust into a dirt mound nearby. Upon investigation, " "you discover it to be the enchanted shovel of the Gravediggers, long thought " "lost by mortals." msgstr "" "Una sucia pala ha sido clavada en un montón de tierra cercano. Tras " "investigarla, descubres que es la pala encantada de los Enterradores, que se " "creía perdida hacía tiempo por los mortales." msgid "spellBonus|selected by user" msgstr "" msgid "Wood" msgstr "Madera" msgid "Mercury" msgstr "Mercurio" msgid "Ore" msgstr "Hierro" msgid "Sulfur" msgstr "Azufre" msgid "Crystal" msgstr "Cristal" msgid "Gems" msgstr "Gemas" msgid "Gold" msgstr "Oro" msgid "" "There are seven resources in Heroes 2, used to build and improves castles, " "purchase troops and recruit heroes. Gold is the most common, required for " "virtually everything. Wood and ore are used for most buildings. Gems, " "Mercury, Sulfur and Crystal are rare magical resources used for the most " "powerful creatures and buildings." msgstr "" msgid "" "Causes a giant fireball to strike the selected area, damaging all nearby " "creatures." msgstr "" "Origina una Bola de Fuego gigante dañándo las criaturas del area " "seleccionada." msgid "Fireball" msgstr "Bola de Fuego" msgid "Fireblast" msgstr "Explosión" msgid "" "An improved version of fireball, fireblast affects two hexes around the " "center point of the spell, rather than one." msgstr "" "Una versión mejorada de Bola de Fuego, Explosión afecta a dos cuadrados " "alrededor del punto central del Hechizo, en vez de uno." msgid "Causes a bolt of electrical energy to strike the selected creature." msgstr "Hace que un rayo de energía alcance a la criatura seleccionada." msgid "Lightning Bolt" msgstr "Rayo" msgid "Chain Lightning" msgstr "Cadena de Rayos" msgid "" "Causes a bolt of electrical energy to strike a selected creature, then " "strike the nearest creature with half damage, then strike the NEXT nearest " "creature with half again damage, and so on, until it becomes too weak to be " "harmful. Warning: This spell can hit your own creatures!" msgstr "" "Hace que un rayo alcance a la criatura seleccionada, seguidamente alcanza a " "la criatura más cercana causándo la midad de daño, luego alcanza a una " "tercera causando de nuevo la mitad de daño y así sucesivamente hasta que es " "demasiado debil para ser dañino. ADVERTENCIA: Este hechizo puede alcanzar a " "tus propias criaturas!" msgid "Teleport" msgstr "Teletransporte" msgid "" "Teleports the creature you select to any open position on the battlefield." msgstr "" "Teletransporta la critatura seleccionada a cualquier punto desocupado del " "campo de batalla." msgid "Cure" msgstr "Cura" msgid "" "Removes all negative spells cast upon one of your units, and restores up to " "%{count} HP per level of spell power." msgstr "" "Quita todos los hechizos negativos que afecten a tus unidades, y restaura " "hasta %{count} puntos de vida por nivel del hechizo." msgid "Mass Cure" msgstr "Curar en Masas" msgid "" "Removes all negative spells cast upon your forces, and restores up to " "%{count} HP per level of spell power, per creature." msgstr "" msgid "Resurrect" msgstr "Resurrección" msgid "Resurrects creatures from a damaged or dead unit until end of combat." msgstr "" "Devuelve a la vida las criaturas de una unidad dañada o muerta hasta el " "final de combate." msgid "Resurrect True" msgstr "Resurrección Verdadera" msgid "Resurrects creatures from a damaged or dead unit permanently." msgstr "" "Devuelve permanentemente a la vida las unidades de una criatura muerta o " "dañana." msgid "Haste" msgstr "Prisa" msgid "Increases the speed of any creature by %{count}." msgstr "Aumenta la velocidad de cualquier criatura en %{count}." msgid "Increases the speed of all of your creatures by %{count}." msgstr "Aumenta la velocidad de todas tus criaturas en %{count}." msgid "Mass Haste" msgstr "Prisa en Masas" msgid "Slows target to half movement rate." msgstr "Ralentiza el objetivo a la mitad del movimiento." msgid "spell|Slow" msgstr "Ralentizar" msgid "Mass Slow" msgstr "Ralentizar en Masas" msgid "Slows all enemies to half movement rate." msgstr "Ralentiza a todos los enemigas a la mitad del movimiento." msgid "Clouds the affected creatures' eyes, preventing them from moving." msgstr "Cubre los ojos de la criatura afectada, previniendo que se mueva." msgid "spell|Blind" msgstr "Cegar" msgid "Bless" msgstr "Bendecir" msgid "Causes the selected creatures to inflict maximum damage." msgstr "Hace que las criaturas seleccionadas inflijan daños máximos." msgid "Causes all of your units to inflict maximum damage." msgstr "Hace que todas tus unidades inflijan daños máximos." msgid "Mass Bless" msgstr "Bendición en Masa" msgid "Magically increases the defense skill of the selected creatures." msgstr "" "Magicamente incrementa la habilidad de Defensa de las criaturas " "seleccionadas." msgid "Stoneskin" msgstr "Piel de Roca" msgid "" "Increases the defense skill of the targeted creatures. This is an improved " "version of Stoneskin." msgstr "" "Incrementa la habilidad de Defensa de las criaturas seleccionadas. Ésta es " "una versión mejorada de Piel de Roca." msgid "Steelskin" msgstr "Piel de Acero" msgid "Causes the selected creatures to inflict minimum damage." msgstr "Hace que las criaturas seleccionadas inflijan daños mínimos." msgid "Curse" msgstr "Maldición" msgid "Causes all enemy troops to inflict minimum damage." msgstr "Hace que todas las tropas enemigas inflijan daños mínimos." msgid "Mass Curse" msgstr "Maldición en Masa" msgid "Damages all undead in the battle." msgstr "Daña a todos los nomuertos en combate." msgid "Holy Word" msgstr "Palabra Sagrada" msgid "" "Damages all undead in the battle. This is an improved version of Holy Word." msgstr "" "Daña a todos los nomuertos en la batalla. Es una versión mejorada de la " "Palabra Sagrada." msgid "Holy Shout" msgstr "Grito Sagrado" msgid "Anti-Magic" msgstr "Anti-Magia" msgid "Prevents harmful magic against the selected creatures." msgstr "Previene de magia perjudicial contra la criatura seleccionada." msgid "Dispel Magic" msgstr "Disipar" msgid "Removes all magic spells from a single target." msgstr "" "Elimina todos los hechizos mágicos que afectan a la criatura que se " "selecciona." msgid "Mass Dispel" msgstr "Disipar en Masa" msgid "Removes all magic spells from all creatures." msgstr "Elimina todos los hechizos mágicos que afectan a todas las criaturas." msgid "Causes a magic arrow to strike the selected target." msgstr "Hace que una flecha mágica alcance al objetivo seleccionado." msgid "Magic Arrow" msgstr "Flecha Mágica" msgid "Berserker" msgstr "Berserker" msgid "Causes a creature to attack its nearest neighbor." msgstr "Hace que una criatura ataque a su vecino más cercano." msgid "Armageddon" msgstr "Armagedón" msgid "" "Holy terror strikes the battlefield, causing severe damage to all creatures." msgstr "" "El terror sagrado ataca el campo de batalla, causando daños severos a todas " "las criaturas." msgid "Elemental Storm" msgstr "LLuvia de Meteoritos" msgid "Magical elements pour down on the battlefield, damaging all creatures." msgstr "" "Elementos mágicos caen sobre el campo de batalla, dañando a todas las " "criaturas." msgid "" "A rain of rocks strikes an area of the battlefield, damaging all nearby " "creatures." msgstr "" "Una lluvia de piedras se abate sobre el campo de batalla, dañando a todas " "criaturas cercanas." msgid "Meteor Shower" msgstr "Lluvia de Meteoritos" msgid "Paralyze" msgstr "Paralizar" msgid "The targeted creatures are paralyzed, unable to move or retaliate." msgstr "" "Las criaturas seleccionadas quedan paralizadas y no se pueden mover ni " "contratacar." msgid "Hypnotize" msgstr "Hipnotizar" msgid "" "Brings a single enemy unit under your control if its hits are less than " "%{count} times the caster's spell power." msgstr "" msgid "Cold Ray" msgstr "Rayo Frío" msgid "Drains body heat from a single enemy unit." msgstr "Absorbe el calor del cuerpo de una sola unidad enemiga." msgid "Cold Ring" msgstr "Anillo Frío" msgid "" "Drains body heat from all units surrounding the center point, but not " "including the center point." msgstr "" "Absorbe el calor del cuerdo de todas las unidades alrededor del punto " "central, pero sin incluir dicho punto." msgid "Disrupting Ray" msgstr "Rayo Pertubador" msgid "Reduces the defense rating of an enemy unit by three." msgstr "Reduce la defensa de una unidad enemiga en 3." msgid "Damages all living (non-undead) units in the battle." msgstr "Daña todas las unidades Vivientes (no Nomuertas) en la batalla." msgid "Death Ripple" msgstr "Onda de la Muerte" msgid "Death Wave" msgstr "Ola de la Muerte" msgid "" "Damages all living (non-undead) units in the battle. This spell is an " "improved version of Death Ripple." msgstr "" "Daña todas las unidades Vivientes (no Nomuertas) en la batalla. Es una " "versión mejorada de Onda de la Muerte." msgid "Dragon Slayer" msgstr "Matadragones" msgid "Greatly increases a unit's attack skill vs. Dragons." msgstr "Incrementa enormente tu habilidad de ataque contra Dragones." msgid "Blood Lust" msgstr "Ansia de Sangre" msgid "Increases a unit's attack skill." msgstr "Incrementa la habilidad de ataque de una unidad." msgid "Animate Dead" msgstr "Animar Muertos" msgid "Resurrects creatures from a damaged or dead undead unit permanently." msgstr "" "Devuelve permanentemente a la vida las unidades muertas o dañadas de una " "criatura Nomuerta." msgid "Mirror Image" msgstr "Doble Imagen" msgid "" "Creates an illusionary unit that duplicates one of your existing units. This " "illusionary unit does the same damages as the original, but will vanish if " "it takes any damage." msgstr "" "Crea una unidad ficticia que duplica a una de tus unidades existentes. Ésta " "causará los mismos daños que la original, pero se esfumará si recibe algún " "daño." msgid "Shield" msgstr "Escudo" msgid "" "Halves damage received from ranged attacks for a single unit. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Reduce a la midad los daños recibidos en ataques a distancia por una unidad. " "No afecta al daño recibido de torretas o ballestas." msgid "Mass Shield" msgstr "Escudo en Masa" msgid "" "Halves damage received from ranged attacks for all of your units. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Reduce a la midad los daños recibidos en ataques a distancia por todas tus " "unidades. No afecta al daño recibido de torretas o ballestas." msgid "Summon Earth Elemental" msgstr "LLamar al Elemental de la Tierra" msgid "Summons Earth Elementals to fight for your army." msgstr "Llama a los Elementales de la Tierra para luchar en tu ejercito." msgid "Summon Air Elemental" msgstr "LLamar al Elemental del Aire" msgid "Summons Air Elementals to fight for your army." msgstr "LLama a los Elementales del Aire para combatir en tu ejercito." msgid "Summon Fire Elemental" msgstr "LLamar al Elemental del Fuego" msgid "Summons Fire Elementals to fight for your army." msgstr "LLama a los Elementales del Fuego para luchar en tu ejercito." msgid "Summon Water Elemental" msgstr "LLamar Elemental del Agua" msgid "Summons Water Elementals to fight for your army." msgstr "LLama a los Elementales del Agua para luchar en tu ejercito." msgid "Damages castle walls." msgstr "Daña los muros del castillo." msgid "Earthquake" msgstr "Terremoto" msgid "Causes all mines across the land to become visible." msgstr "Hace que todas las minas del territorio sean visibles." msgid "View Mines" msgstr "Ver minas" msgid "Causes all resources across the land to become visible." msgstr "Hace que todos los recursos del territorio sean visibles." msgid "View Resources" msgstr "Ver Recursos" msgid "Causes all artifacts across the land to become visible." msgstr "Hace que todos los Artefactos del territorio sean visibles." msgid "View Artifacts" msgstr "Ver Artefactos" msgid "Causes all towns and castles across the land to become visible." msgstr "Hace que todas las ciudades y castillos del territorio sean visibles." msgid "View Towns" msgstr "Ver Ciudades" msgid "Causes all Heroes across the land to become visible." msgstr "Hace que todos los Héroes del territorio sean visibles." msgid "View Heroes" msgstr "Ver Heroes" msgid "Causes the entire land to become visible." msgstr "Hace que todo el territorio sea visible." msgid "View All" msgstr "Ver Todo" msgid "Allows the caster to view detailed information on enemy Heroes." msgstr "Permite al lanzador ver información detallada de los héroes enemigos." msgid "Summon Boat" msgstr "LLamar Barco" #, fuzzy msgid "" "Summons the nearest unoccupied, friendly boat to an adjacent shore location. " "A friendly boat is one which you just built or were the most recent player " "to occupy." msgstr "" "LLama a un barco amigo desocupado al punto de costa más cercano. Un barco " "amigo es aquel que has contruido o has sido el último jugador en ocupar." msgid "Allows the caster to magically transport to a nearby location." msgstr "" "Permite al lanzador del hechizo ser magicamente transportado a un punto " "cercano." msgid "Dimension Door" msgstr "Puerta Dimensional" msgid "Returns the caster to any town or castle currently owned." msgstr "LLeva al Héroe a cualquier ciudad o castillo que posea." msgid "Town Gate" msgstr "Puerta de la Ciudad" msgid "" "Returns the hero to the town or castle of choice, provided it is controlled " "by you." msgstr "" "Devuelve al héroe que lanza este hechizo a cualquier ciudad o castillo, " "siempre que sea controlado por él." msgid "Visions" msgstr "Visiones" msgid "" "Visions predicts the likely outcome of an encounter with a neutral army camp." msgstr "" "Visiones predice el resultado problable de un encuentro con un ejército " "neutral." msgid "Haunt" msgstr "Encantar" #, fuzzy msgid "" "Haunts a mine you control with Ghosts. This mine stops producing resources. " "(If I can't keep it, nobody will!)" msgstr "" "Encanta una mina que controlas con Fantasmas. La mina no podrá producir " "recursos para nadie." msgid "Set Earth Guardian" msgstr "Establecer guardian de la Tierra" msgid "Sets Earth Elementals to guard a mine against enemy armies." msgstr "Ordena a los Elementales de la Tierra que protejan una mina." msgid "Set Air Guardian" msgstr "Establecer guardian del aire" msgid "Sets Air Elementals to guard a mine against enemy armies." msgstr "Ordena a los Elementales del Aire que protejan una mina." msgid "Set Fire Guardian" msgstr "Establecer guardia del fuego" msgid "Sets Fire Elementals to guard a mine against enemy armies." msgstr "" "Ordenar a los Elementales del Fuego proteger una mina frente a los ejercitos " "enemigos." msgid "Set Water Guardian" msgstr "Establecer guardia del agua" msgid "Sets Water Elementals to guard a mine against enemy armies." msgstr "Ordenar que los Elementales del Agua protejan una mina." #, fuzzy msgid "Random Spell" msgstr "Cualquier torre" msgid "Randomly selected spell of any level." msgstr "" #, fuzzy msgid "Random 1st Level Spell" msgstr "Cualquier castillo" msgid "Randomly selected 1st level spell." msgstr "" msgid "Random 2nd Level Spell" msgstr "" msgid "Randomly selected 2nd level spell." msgstr "" msgid "Random 3rd Level Spell" msgstr "" msgid "Randomly selected 3rd level spell." msgstr "" msgid "Random 4th Level Spell" msgstr "" msgid "Randomly selected 4th level spell." msgstr "" msgid "Random 5th Level Spell" msgstr "" msgid "Randomly selected 5th level spell." msgstr "" msgid "Petrification" msgstr "Petrificación" msgid "" "Turns the affected creature into stone. A petrified creature receives half " "damage from a direct attack." msgstr "" msgid "You have no Magic Book, so you cannot cast a spell." msgstr "" "No tienes un Libro de Hechizos, por lo que no puedes lanzar un hechizo." msgid "No spell to cast." msgstr "No hay hechizo que lanzar." msgid "Your hero has %{point} spell points remaining." msgstr "Tú héroe tiene todavía %{point} puntos de Maná." msgid "View Adventure Spells" msgstr "Ver Hechizos de Aventura" msgid "View Combat Spells" msgstr "Ver Hechizos de Combate" #, fuzzy msgid "View previous page" msgstr "Ver ciudad anterior" #, fuzzy msgid "View next page" msgstr "Ver %{name}" #, fuzzy msgid "Close Spellbook" msgstr "Lanzar hechizo" msgid "View %{spell}" msgstr "Ver %{spell}" msgid "This spell does %{damage} points of damage." msgstr "Este hechizo hace %{damage} de daño." #, fuzzy msgid "" "This spell summons\n" "%{count} %{monster}." msgstr "Lanzar %{spell} sobre %{monster}" msgid "This spell restores %{hp} HP." msgstr "" msgid "This spell summons %{count} %{monster} to guard the mine." msgstr "" msgid "The nearest town is %{town}." msgstr "" msgid "This town is occupied by your hero %{hero}." msgstr "" msgid "" "This spell controls up to\n" "%{hp} HP." msgstr "" #, fuzzy msgid "The ultimate artifact is really the %{name}." msgstr "¡La maldición de la Momia cae sobre %{name}!" msgid "The ultimate artifact may be found in the %{name} regions of the world." msgstr "" msgid "north-west" msgstr "" #, fuzzy msgid "north" msgstr "Enroth" msgid "north-east" msgstr "" #, fuzzy msgid "west" msgstr "Nido" #, fuzzy msgid "center" msgstr "Monstruo" #, fuzzy msgid "east" msgstr "Nido" msgid "south-west" msgstr "" #, fuzzy msgid "south" msgstr "Ostoroth" msgid "south-east" msgstr "" msgid "The truth is out there." msgstr "" msgid "The dark side is stronger." msgstr "" msgid "The end of the world is near." msgstr "El fin del mundo está cerca." msgid "The bones of Lord Slayer are buried in the foundation of the arena." msgstr "" msgid "A Black Dragon will take out a Titan any day of the week." msgstr "" msgid "He told her: Yada yada yada... and then she said: Blah, blah, blah..." msgstr "" msgid "An unknown force is being resurrected..." msgstr "" msgid "" "Check the newest version of the game at\n" "https://github.com/ihhub/\n" "fheroes2/releases" msgstr "" "Puedes descargar la última versión del juego desde el sítio:\n" "https://github.com/ihhub/\n" "fheroes2/releases" #, fuzzy #~ msgid "Castles" #~ msgstr "Castillo" #, fuzzy #~ msgid "Ocean object" #~ msgstr "Paraje baldio" #, fuzzy #~ msgid "Desert Objects" #~ msgstr "Tienda del Desierto" #, fuzzy #~ msgid "%{race1} %{name1}" #~ msgstr "%{name1} se reune con %{name2}" #, fuzzy #~ msgid "%{race2} %{name2}" #~ msgstr "%{name1} se reune con %{name2}" fheroes2-1.0.12+dfsg/files/lang/fr.po000066400000000000000000011501521456075706000172400ustar00rootroot00000000000000# French translation for fheroes2 # Copyright (C) 2021 - 2024 fheroes2 team # This file is distributed under the same license as the fheroes2 package. # msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-03 03:27+0000\n" "PO-Revision-Date: 2024-02-06 13:52+0100\n" "Last-Translator: fheroes2 team \n" "Language-Team: French \n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" "X-Launchpad-Export-Date: 2010-05-27 09:12+0000\n" "X-Generator: Poedit 3.2.2\n" msgid "" "BATTLE\n" "ONLY" msgstr "BATAILLE" msgid "" "NEW\n" "GAME" msgstr "" "NOU-\n" "VEAU" msgid "" "SAVE\n" "GAME" msgstr "" "SAUVE-\n" "GARDER" msgid "" "LOAD\n" "GAME" msgstr "" "CHAR-\n" "GER" msgid "INFO" msgstr "INFOS" msgid "QUIT" msgstr "" "QUIT-\n" "TER" msgid "CANCEL" msgstr "ANNULER" msgid "OKAY" msgstr "OK" msgid "smallerButton|OKAY" msgstr "OK" msgid "ACCEPT" msgstr "ACCEPTER" msgid "DECLINE" msgstr "REFUSER" msgid "LEARN" msgstr "OK" msgid "TRADE" msgstr "TROQUER" msgid "YES" msgstr "OUI" msgid "NO" msgstr "NON" msgid "EXIT" msgstr "FIN" msgid "smallerButton|EXIT" msgstr "FIN" msgid "DISMISS" msgstr "RENVOYER" msgid "UPGRADE" msgstr "AMÉLIORER" msgid "RESTART" msgstr "REJOUER" msgid "HEROES" msgstr "HÉROS" msgid "" "TOWNS/\n" "CASTLES" msgstr "" "VIL-\n" "LAGES" msgid "S" msgstr "S" msgid "M" msgstr "M" msgid "L" msgstr "L" msgid "X-L" msgstr "XL" msgid "ALL" msgstr "TOUT" msgid "SELECT" msgstr "AUTRE" msgid "" "STANDARD\n" "GAME" msgstr "SCÉNARIO" msgid "" "CAMPAIGN\n" "GAME" msgstr "CAMPAGNE" msgid "" "MULTI-\n" "PLAYER\n" "GAME" msgstr "" "MODES\n" "MULTI-\n" "JOUEUR" msgid "CONFIG" msgstr "" "PARA-\n" "MÈTRES" msgid "" "ORIGINAL\n" "CAMPAIGN" msgstr "" "CAMPAGNE\n" "ORIGINALE" msgid "" "EXPANSION\n" "CAMPAIGN" msgstr "" "SCÉNARIO\n" "DISC" msgid "HOT SEAT" msgstr "" "JOUER À\n" "PLUSIEURS" msgid "2 PLAYERS" msgstr "2 JOUEURS" msgid "3 PLAYERS" msgstr "3 JOUEURS" msgid "4 PLAYERS" msgstr "4 JOUEURS" msgid "5 PLAYERS" msgstr "5 JOUEURS" msgid "6 PLAYERS" msgstr "6 JOUEURS" msgid "GIFT" msgstr "DONNER" msgid "MAX" msgstr "MAX" msgid "DIFFICULTY" msgstr "DIFFICULTÉ" msgid "VIEW INTRO" msgstr "VOIR INTRO" msgid "MIN" msgstr "MIN" msgid "RESET" msgstr "R.A.Z." msgid "START" msgstr "COMMENCER" msgid "CASTLE" msgstr "CHÂTEAU" msgid "TOWN" msgstr "VILLAGE" msgid "" "D\n" "I\n" "S\n" "M\n" "I\n" "S\n" "S" msgstr "" "R\n" "E\n" "N\n" "V\n" "O\n" "I" msgid "" "E\n" "X\n" "I\n" "T" msgstr "" "Q\n" "U\n" "I\n" "T\n" "T\n" "E\n" "R" msgid "" "C\n" "A\n" "M\n" "P\n" "A\n" "I\n" "G\n" "N" msgstr "" "C\n" "A\n" "M\n" "P\n" "A\n" "G\n" "N\n" "E" msgid "" "S\n" "T\n" "A\n" "N\n" "D\n" "A\n" "R\n" "D" msgstr "" "S\n" "C\n" "É\n" "N\n" "A\n" "R\n" "I\n" "O" msgid "Warrior" msgstr "Guerrier" msgid "Builder" msgstr "Bâtisseur" msgid "Explorer" msgstr "Voyageur" msgid "None" msgstr "Aucune" msgid "" "A few\n" "%{monster}" msgstr "" "Quelques\n" "%{monster}" msgid "" "Several\n" "%{monster}" msgstr "" "Plusieurs\n" "%{monster}" msgid "" "A pack of\n" "%{monster}" msgstr "" "Un groupe de\n" "%{monster}" msgid "" "Lots of\n" "%{monster}" msgstr "" "Une troupe de\n" "%{monster}" msgid "" "A horde of\n" "%{monster}" msgstr "" "Une horde de \n" "%{monster}" msgid "" "A throng of\n" "%{monster}" msgstr "" "Une foule de \n" "%{monster}" msgid "" "A swarm of\n" "%{monster}" msgstr "" "Une colonie de \n" "%{monster}" msgid "" "Zounds...\n" "%{monster}" msgstr "" "Une armée de \n" "%{monster}" msgid "" "A legion of\n" "%{monster}" msgstr "" "Une légion de\n" "%{monster}" msgid "army|Few" msgstr "1-4" msgid "army|Several" msgstr "5-9" msgid "army|Pack" msgstr "10-19" msgid "army|Lots" msgstr "20-49" msgid "army|Horde" msgstr "50-99" msgid "army|Throng" msgstr "100-249" msgid "army|Swarm" msgstr "250-499" msgid "army|Zounds" msgstr "500-999" msgid "army|Legion" msgstr "1000+" msgid "All %{race} troops +1" msgstr "+1 pour toutes les troupes %{race}" msgid "Multiple" msgstr "Multiple" msgid "Troops of %{count} alignments -%{penalty}" msgstr "Troupes de %{count} alignements -%{penalty}" msgid "Some undead in army -1" msgstr "Morts-vivants parmi les troupes -1" msgid "View %{name}" msgstr "Voir %{name}" msgid "Move the %{name} " msgstr "Déplacer la troupe de %{name} " msgid "Move or right click to redistribute %{name}" msgstr "Déplacer ou faire un clic droit pour répartir les %{name}" msgid "Combine %{name} armies" msgstr "Fusionner les armées de %{name}" msgid "Exchange %{name2} with %{name}" msgstr "Échanger %{name2} contre %{name}" msgid "Select %{name}" msgstr "Sélectionner %{name}" msgid "Cannot move last troop" msgstr "Impossible de déplacer la dernière armée dans la garnison" msgid "Move the %{name}" msgstr "Déplacer la troupe de %{name}" msgid "Set Count" msgstr "Régler le nombre" msgid "%{name} destroys half the enemy troops!" msgid_plural "%{name} destroy half the enemy troops!" msgstr[0] "La troupe de %{name} décime la troupe ennemie !" msgstr[1] "Les %{name} déciment la troupe ennemie !" msgid "%{name} has turned off the auto battle" msgstr "%{name} a désactivé le combat auto" msgid "%{name} has turned on the auto battle" msgstr "%{name} a activé le combat auto" msgid "Spell failed!" msgstr "Sort échoué !" msgid "" "The Sphere of Negation artifact is in effect for this battle, disabling all " "combat spells." msgstr "" "L'artéfact Sphère de négation est en vigueur pour cette bataille, " "désactivant tous les sorts de combat." msgid "You have already cast a spell this round." msgstr "Vous avez déjà jeté un sort pendant ce tour." msgid "That spell will have no effect!" msgstr "Ce sort n'aura aucun effet !" msgid "You may only summon one type of elemental per combat." msgstr "Vous ne pouvez invoquer qu'un seul type d'élémentaire par combat." msgid "" "There is no open space adjacent to your hero where you can summon an " "Elemental to." msgstr "" "Il n'y a pas d'espace libre adjacent à votre héros pour y invoquer un " "élémentaire." msgid "" "The Moat reduces by -%{count} the defense skill of any unit and slows to " "half movement rate." msgstr "" "Le Fossé réduit de -%{count} la caractéristique de défense de n'importe " "quelle créature et ralentit sa capacité de mouvement de moitié." msgid "Speed: %{speed}" msgstr "Vitesse : %{speed}" msgid "Speed" msgstr "Vitesse" msgid "Off" msgstr "Désactivé" msgid "On" msgstr "Activé" msgid "Turn Order" msgstr "Ordre de jeu" msgid "Auto Spell Casting" msgstr "Sorts auto" msgid "Grid" msgstr "Grille" msgid "Shadow Movement" msgstr "Ombre de portée" msgid "Shadow Cursor" msgstr "Ombre du curseur" msgid "Audio" msgstr "Audio" msgid "Settings" msgstr "Paramètres" msgid "Configure" msgstr "Configurer" msgid "Hot Keys" msgstr "" "Raccourcis\n" "clavier" msgid "Damage Info" msgstr "Infos sur dégâts" msgid "Set the speed of combat actions and animations." msgstr "Définir la vitesse des actions et animations de combat." msgid "Toggle to display the turn order during the battle." msgstr "" "Activer ou désactiver l'affichage de l'ordre de jeu des créatures pendant le " "combat." msgid "" "Toggle whether or not the computer will cast spells for you when auto combat " "is on. (Note: This does not affect spell casting for computer players in any " "way, nor does it affect quick combat.)" msgstr "" "Activer ou désactiver le lancement de sorts pendant le combat automatique. " "(Remarque : cela n'affecte en aucun cas le lancement de sorts pour les héros " "de l'ordinateur, ni le combat rapide.)" msgid "" "Toggle the hex grid on or off. The hex grid always underlies movement, even " "if turned off. This switch only determines if the grid is visible." msgstr "" "Activer ou désactiver l'affichage de la grille d'hexagones. Même désactivée, " "le jeu utilise cette grille pour les déplacements et certains sortilèges." msgid "" "Toggle on or off shadows showing where your creatures can move and attack." msgstr "" "Activer ou désactiver la zone ombragée indiquant où vos créatures peuvent se " "déplacer et attaquer." msgid "" "Toggle on or off a shadow showing the current hex location of the mouse " "cursor." msgstr "" "Activer ou désactiver l'ombre montrant l'hexagone où est situé le curseur de " "la souris." msgid "Change the audio settings of the game." msgstr "Changer les paramètres du son du jeu." msgid "Check and configure all the hot keys present in the game." msgstr "Vérifier et configurer les raccourcis clavier du jeu." msgid "Toggle to display damage information during the battle." msgstr "" "Basculez pour afficher les informations sur les dégâts pendant la bataille." msgid "Exit this menu." msgstr "Quitter ce menu." msgid "Okay" msgstr "OK" msgid "The enemy has surrendered!" msgstr "L'ennemi s'est rendu !" msgid "Their cowardice costs them %{gold} gold." msgstr "Sa couardise lui coûte %{gold} pièces d'or." msgid "The enemy has fled!" msgstr "L'ennemi a pris la fuite !" msgid "A glorious victory!" msgstr "Une glorieuse victoire !" msgid "For valor in combat, %{name} receives %{exp} experience." msgstr "Pour sa bravoure au combat, %{name} reçoit %{exp} points d'expérience." msgid "The cowardly %{name} flees from battle." msgstr "%{name} fuit la bataille avec lâcheté." msgid "%{name} surrenders to the enemy, and departs in shame." msgstr "%{name} se rend à l'ennemi et part, son nom couvert de honte." msgid "Your forces suffer a bitter defeat, and %{name} abandons your cause." msgstr "Votre armée subit une défaite amère, et %{name} abandonne votre cause." msgid "Your forces suffer a bitter defeat." msgstr "Votre armée subit une défaite amère." msgid "Battlefield Casualties" msgstr "Pertes sur le champ de bataille" msgid "Attacker" msgstr "Attaquant" msgid "Defender" msgstr "Défenseur" msgid "Click to leave the battle results." msgstr "Cliquez pour quitter les résultats de la bataille." msgid "Click to restart the battle in manual mode." msgstr "Cliquez pour relancer la bataille en mode manual." msgid "Restart" msgstr "Recommencer" msgid "You have captured an enemy artifact!" msgstr "Vous avez capturé un artéfact ennemi !" msgid "As you reach for the %{name}, it mysteriously disappears." msgstr "Alors que vous vous approchez %{name}, il disparaît mystérieusement." msgid "As your enemy reaches for the %{name}, it mysteriously disappears." msgstr "Tandis que votre ennemi atteint %{name}, il disparaît mystérieusement." msgid "Necromancy!" msgstr "Nécromancie !" msgid "" "Practicing the dark arts of necromancy, you are able to raise %{count} of " "the enemy's dead to return under your service as %{monster}." msgstr "" "Pratiquant les arts sombres de la nécromancie, vous êtes capable de réanimer " "%{count} des morts de l'ennemi en tant que %{monster}." msgid "%{name} the %{race}" msgstr "%{name} le %{race}" msgid "Captain of %{name}" msgstr "Capitaine de %{name}" msgid "Attack" msgstr "Attaque" msgid "Defense" msgstr "Défense" msgid "Spell Power" msgstr "Puissance" msgid "Knowledge" msgstr "Connaissance" msgid "Morale" msgstr "Moral" msgid "Luck" msgstr "Chance" msgid "Spell Points" msgstr "Points de magie" msgid "Hero's Options" msgstr "Les options du héros" msgid "Cast Spell" msgstr "Lancer un sort" msgid "Retreat" msgstr "Sonner la retraite" msgid "Surrender" msgstr "Se rendre" msgid "Cancel" msgstr "Annuler" msgid "Hero Screen" msgstr "Fenêtre d'informations du héros" msgid "Captain's Options" msgstr "Options du capitaine" msgid "" "Cast a magical spell. You may only cast one spell per combat round. The " "round is reset when every creature has had a turn." msgstr "" "Lance un sortilège. Vous pouvez seulement en lancer un par tour pendant le " "combat. Le tour se termine lorsque chaque créature a effectué une action." msgid "" "Retreat your hero, abandoning your creatures. Your hero will be available " "for you to recruit again, however, the hero will have only a novice hero's " "forces." msgstr "" "Retirer votre héros de la bataille en abandonnant vos créatures. Votre héros " "pourra ensuite être recruté de nouveau, mais ne disposera que des forces " "d'un héros novice." msgid "" "Surrendering costs gold. However if you pay the ransom, the hero and all of " "his or her surviving creatures will be available to recruit again. The cost " "of surrender is half of the total cost of the non-temporary troops remaining " "in the army." msgstr "" "Se rendre prisonnier coûte de l'or. Toutefois, si vous payez la rançon, le " "héros et toutes les créatures survivantes pourront être recrutés de nouveau. " "Le coût de la reddition est égal à la moitié du coût total des troupes non " "temporaires restantes dans l'armée." msgid "Open Hero Screen to view full information about the hero." msgstr "" "Ouvrir la fenêtre d'informations du Héros pour afficher toutes les " "informations sur le héros." msgid "Return to the battle." msgstr "Retourner à la bataille." msgid "Not enough gold (%{gold})" msgstr "Pas assez d'or (%{gold})" msgid "%{name} states:" msgstr "%{name} déclare :" msgid "Captain of %{name} states:" msgstr "Capitaine de %{name} :" msgid "" "\"I will accept your surrender and grant you and your troops safe passage " "for the price of %{price} gold.\"" msgstr "" "\" J'accepterai votre reddition et vous accorderai, à vous et à vos troupes, " "un passage sûr pour le prix de %{price} pièces d'or. \"" msgid "View %{monster} info" msgstr "Voir les infos de %{monster}" msgid "Fly %{monster} here" msgstr "Faire voler %{monster} ici" msgid "Move %{monster} here" msgstr "Déplacer %{monster} ici" msgid "Shoot %{monster}" msgstr "Tirer sur %{monster}" msgid "(1 shot left)" msgid_plural "(%{count} shots left)" msgstr[0] "(1 tir restant)" msgstr[1] "(%{count} tirs restants)" msgid "Attack %{monster}" msgstr "Attaquer %{monster}" msgid "Turn %{turn}" msgstr "Tour %{turn}" msgid "Teleport here" msgstr "Téléporter ici" msgid "Invalid teleport destination" msgstr "Destination de téléportation invalide" msgid "Cast %{spell} on %{monster}" msgstr "Lancer %{spell} sur %{monster}" msgid "Cast %{spell}" msgstr "Lancer %{spell}" msgid "Select spell target" msgstr "Sélectionner la cible du sort" msgid "View Ballista info" msgstr "Voir les infos de la baliste" msgid "Ballista" msgstr "Baliste" msgid "Enable auto combat" msgstr "Activer le combat automatique" msgid "Allows the computer to fight out the battle for you." msgstr "L'ordinateur joue le combat à votre place." msgid "Auto Combat" msgstr "Combat Auto" msgid "Customize system options" msgstr "Modifier les options système" msgid "Allows you to customize the combat screen." msgstr "Modifier les options de l'écran de combat." msgid "System Options" msgstr "Options du système" msgid "Skip this unit" msgstr "Passer ce peloton" msgid "Skip" msgstr "Passer" msgid "" "Skips the current creature. The current creature ends its turn and does not " "get to go again until the next round." msgstr "" "Le peloton actif passe son tour et ne peut pas jouer avant le prochain tour." msgid "View Captain's options" msgstr "Voir les options du capitaine" msgid "View Hero's options" msgstr "Voir les options du héros" msgid "View opposing Captain" msgstr "Voir le capitaine adverse" msgid "View opposing Hero" msgstr "Voir le héros adverse" msgid "Hide logs" msgstr "Cacher le journal" msgid "Show logs" msgstr "Afficher le journal" msgid "Message Bar" msgstr "Barre d'informations" msgid "Shows the results of individual monster's actions." msgstr "" "Affiche le déroulement du combat et indique toutes les actions individuelles." msgid "Are you sure you want to finish the battle in auto mode?" msgstr "Voulez-vous vraiment finir la bataille en mode combat auto ?" msgid "Are you sure you want to enable auto combat?" msgstr "Voulez-vous vraiment activer le combat automatique ?" msgid "%{name} skip their turn." msgstr "%{name} passent leur tour." msgid "%{attacker} does %{damage} damage." msgid_plural "%{attacker} do %{damage} damage." msgstr[0] "%{attacker} inflige %{damage} dégâts." msgstr[1] "Les %{attacker} infligent %{damage} dégâts." # if only 1 monster: "... inflige ..." msgid "1 creature perishes." msgid_plural "%{count} creatures perish." msgstr[0] "1 créature périt." msgstr[1] "%{count} créatures périssent." msgid "1 %{defender} perishes." msgid_plural "%{count} %{defender} perish." msgstr[0] "1 %{defender} périt." msgstr[1] "%{count} %{defender} périssent." msgid "1 soul is incorporated." msgid_plural "%{count} souls are incorporated." msgstr[0] "1 âme est incorporée." msgstr[1] "%{count} âmes sont incorporées." msgid "1 %{unit} is revived." msgid_plural "%{count} %{unit} are revived." msgstr[0] "1 %{unit} a été réanimé." msgstr[1] "%{count} %{unit} ont été réanimés." msgid "Moved %{monster}: from [%{src}] to [%{dst}]." msgstr "Déplacement %{monster} : de [%{src}] à [%{dst}]." msgid "The %{name} resist the spell!" msgstr "%{name} résiste au sort !" msgid "%{name} casts %{spell} on the %{troop}." msgstr "%{name} lance %{spell} sur les %{troop}." msgid "%{name} casts %{spell}." msgstr "%{name} lance %{spell}." msgid "The %{spell} does %{damage} damage to one undead creature." msgstr "%{spell} inflige %{damage} dégâts à un mort-vivant." msgid "The %{spell} does %{damage} damage to all undead creatures." msgstr "%{spell} inflige %{damage} dégâts à tous les morts-vivants." msgid "The %{spell} does %{damage} damage, %{count} creatures perish." msgstr "%{spell} inflige %{damage} dégâts, %{count} créatures meurent." msgid "The %{spell} does %{damage} damage." msgstr "%{spell} inflige %{damage} dégâts." msgid "The %{spell} does %{damage} damage to one living creature." msgstr "%{spell} inflige %{damage} dégâts à une créature vivante." msgid "The %{spell} does %{damage} damage to all living creatures." msgstr "%{spell} inflige %{damage} dégâts à toutes les créatures vivantes." msgid "The %{attacker}' attack blinds the %{target}!" msgstr "L'attaque de %{attacker} aveugle %{target} !" msgid "The %{attacker}' gaze turns the %{target} to stone!" msgstr "Le regard de %{attacker} transforme %{target} en pierre !" msgid "The %{attacker}' curse falls upon the %{target}!" msgstr "La malédiction de %{attacker} s'abat sur %{target} !" msgid "The %{target} are paralyzed by the %{attacker}!" msgstr "La troupe de %{target} est paralysée par %{attacker} !" msgid "The %{attacker} dispel all good spells on your %{target}!" msgstr "%{attacker} dissipe tous les sorts bénéfiques sur %{target} !" msgid "The %{attacker} cast %{spell} on %{target}!" msgstr "%{attacker} lance %{spell} sur %{target} !" msgid "Bad luck descends on the %{attacker}." msgstr "La malchance s'abat sur les %{attacker}." msgid "Good luck shines on the %{attacker}." msgstr "La chance sourit aux %{attacker}." msgid "High morale enables the %{monster} to attack again." msgstr "Un moral élevé permet aux %{monster} d'attaquer à nouveau." msgid "Low morale causes the %{monster} to freeze in panic." msgstr "Un faible moral paralyse les %{monster}." msgid "%{tower} does %{damage} damage." msgstr "%{tower} fait %{damage} dégât(s)." msgid "The mirror image is created." msgstr "L'image miroir est créée." msgid "The mirror image is destroyed!" msgstr "L'image miroir est détruite !" msgid "Are you sure you want to interrupt the auto battle?" msgstr "Voulez-vous vraiment interrompre le combat auto ?" msgid "Error" msgstr "Erreur" msgid "No spells to cast." msgstr "Aucun sort disponible." msgid "Are you sure you want to retreat?" msgstr "Voulez-vous vraiment sonner la retraite ?" msgid "Retreat disabled" msgstr "Retraite impossible" msgid "Surrender disabled" msgstr "Reddition impossible" msgid "Damage: %{max}" msgstr "Dégâts : %{max}" msgid "Damage: %{min} - %{max}" msgstr "Dégâts : %{min} - %{max}" msgid "Perish: %{max}" msgstr "Perte : %{max}" msgid "Perish: %{min} - %{max}" msgstr "Perte : %{min} - %{max}" msgid "" "Through eagle-eyed observation, %{name} is able to learn the magic spell " "%{spell}." msgstr "" "En observant de son oeil d'aigle, %{name} a pu apprendre le sortilège " "%{spell}." msgid "Human" msgstr "Humain" msgid "AI" msgstr "IA" msgid "Start" msgstr "Commencer" msgid "Start the battle." msgstr "Commencer la bataille." msgid "Exit" msgstr "Quitter" msgid "Reset" msgstr "Remise à zéro" msgid "Reset to default settings." msgstr "Réinitialiser les paramétres par défaut." msgid "Please select another hero." msgstr "Veuillez choisir un autre héros." msgid "Set Attack Skill" msgstr "Régler Attaque" msgid "Set Defense Skill" msgstr "Régler Défense" msgid "Set Power Skill" msgstr "Régler Puissance" msgid "Set Knowledge Skill" msgstr "Régler Connaissance" msgid "%{race1} %{name1} vs %{race2} %{name2}" msgstr "%{race1} %{name1} vs. %{race2} %{name2}" msgid "Monsters" msgstr "Monstres" msgid "N/A" msgstr "N/A" msgid "Left Turret" msgstr "Tourelle gauche" msgid "Right Turret" msgstr "Tourelle droite" msgid "The %{name} fires with the strength of %{count} Archers" msgstr "La %{name} tire avec la force de %{count} archers" msgid "each with a +%{attack} bonus to their attack skill." msgstr "chacun avec un bonus de +%{attack} à leur caractéristique d'attaque." msgid "The %{name} is destroyed." msgstr "La %{name} est détruite." msgid "%{count} %{name} rises from the dead!" msgid_plural "%{count} %{name} rise from the dead!" msgstr[0] "%{count} %{name} revient à la vie !" msgstr[1] "%{count} %{name} reviennent à la vie !" msgid "Dwarven Alliance" msgstr "Alliance des nains" msgid "Sorceress Guild" msgstr "Guilde des sorcières" msgid "Necromancer Guild" msgstr "Guilde des nécromanciens" msgid "Ogre Alliance" msgstr "Alliance des ogres" msgid "Dwarfbane" msgstr "Fléau des nains" msgid "Dragon Alliance" msgstr "Alliance des dragons" msgid "Elven Alliance" msgstr "Alliance des elfes" msgid "Kraeger defeated" msgstr "Kraeger vaincu" msgid "Wayward Son" msgstr "Fils dévoyé" msgid "Uncle Ivan" msgstr "Oncle Ivan" msgid "Annexation" msgstr "Annexion" msgid "Force of Arms" msgstr "Force des armes" msgid "Save the Dwarves" msgstr "Sauve les nains" msgid "Carator Mines" msgstr "Mines de Carator" msgid "Turning Point" msgstr "Moment décisif" msgid "scenarioName|Defender" msgstr "Défenseur" msgid "Corlagon's Defense" msgstr "Défense de Corlagon" msgid "The Crown" msgstr "La couronne" msgid "The Gauntlet" msgstr "Le gantelet" msgid "Betrayal" msgstr "Trahison" msgid "Final Justice" msgstr "Justice définitive" msgid "" "Roland needs you to defeat the lords near his castle to begin his war of " "rebellion against his brother. They are not allied with each other, so they " "will spend most of their time fighting with one another. Victory is yours " "when you have defeated all of their castles and heroes." msgstr "" "Roland a besoin de vous pour vaincre les seigneurs près de son château afin " "de commencer sa guerre de rébellion contre son frère. Ces seigneurs ne sont " "pas alliés, et ils s'affronteront donc entre eux. La victoire vous reviendra " "lorsque vous aurez vaincu tous les châteaux et héros ennemis." msgid "" "The local lords refuse to swear allegiance to Roland, and must be subdued. " "They are wealthy and powerful, so be prepared for a tough fight. Capture all " "enemy castles to win." msgstr "" "Les seigneurs locaux refusent de prêter allégeance à Roland et doivent être\n" "matés. Ils sont riches et puissants, alors préparez-vous à un combat " "difficile. Capturez tous les châteaux ennemis pour gagner." msgid "" "Your task is to defend the Dwarves against Archibald's forces. Capture all " "of the enemy towns and castles to win, and be sure not to lose all of the " "dwarf towns at once, or the enemy will have won." msgstr "" "Votre tâche est de défendre les Nains contre les forces d'Archibald. " "Capturez tous les villages et tous les châteaux ennemis pour gagner, et " "veillez à ne pas perdre tous les villages nains d'un coup, ou l'ennemi aura " "gagné." msgid "" "You will face four allied enemies in a straightforward fight for resource " "and treasure. Capture all of the enemy castles for victory." msgstr "" "Vous affronterez quatre ennemis alliés dans une lutte directe pour les " "ressources et les trésors. Capturez tous les châteaux ennemis pour remporter " "la victoire." msgid "" "Your enemies are allied against you and start close by, so be ready to come " "out fighting. You will need to own all four castles in this small valley to " "win." msgstr "" "Vos ennemis sont alliés contre vous et commencent tout près, alors soyez " "prêt à vous battre. Vous devrez posséder les quatre châteaux de cette petite " "vallée pour gagner." msgid "" "The Sorceress' guild of Noraston has requested Roland's aid against an " "attack from Archibald's allies. Capture all of the enemy castles to win, and " "don't lose Noraston, or you'll lose the scenario. (Hint: There is an enemy " "castle on an island in the ocean.)" msgstr "" "La guilde des sorcières de Noraston a demandé l'aide de Roland contre une " "attaque des alliés d'Archibald. Capturez tous les châteaux ennemis pour " "gagner, et ne perdez pas Noraston, ou vous perdrez le scénario. (Indice : il " "y a un château ennemi sur une île dans l'océan)." msgid "" "Gather as large an army as possible and capture the enemy castle within 8 " "weeks. You are opposed by only one enemy, but must travel a long way to get " "to the enemy castle. Any troops you have in your army at the end of this " "scenario will be with you in the final battle." msgstr "" "Rassemblez une armée aussi grande que possible et capturez le château ennemi " "en 8 semaines. Vous n'affronterez qu'un seul ennemi, mais vous devez " "parcourir un long chemin pour atteindre le château ennemi. Toutes les " "troupes que vous avez dans votre armée à la fin de ce scénario seront avec " "vous lors de la bataille finale." msgid "" "Find the Crown before Archibald's heroes find it. Roland will need the Crown " "for the final battle against Archibald." msgstr "" "Trouvez la couronne avant que les héros d'Archibald ne la trouvent. Roland " "aura besoin de la couronne pour la bataille finale contre Archibald." msgid "" "Three allied enemies stand before you and victory, including Lord Corlagon. " "Roland is in a castle to the northwest, and you will lose if he falls to the " "enemy. Remember that capturing Lord Corlagon will ensure that he will not " "fight against you in the final scenario." msgstr "" "Trois ennemis alliés se dressent devant vous et la victoire, y compris Lord " "Corlagon. Roland se trouve dans un château au nord-ouest, et vous perdrez " "s'il tombe aux mains de l'ennemi. N'oubliez pas que si vous capturez Lord " "Corlagon, il ne se battra pas contre vous dans le scénario final." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Archibald to end the war!" msgstr "" "C'est la bataille finale. Vous et votre ennemi êtes armés jusqu'aux dents, " "et tous sont alliés contre vous. Capturez Archibald pour mettre fin à la " "guerre !" msgid "" "Switching sides leaves you with three castles against the enemy's one. This " "battle will be the easiest one you will face for the rest of the war..." "traitor." msgstr "" "Changer de camp vous laisse avec trois châteaux contre un seul pour " "l'ennemi. Cette bataille sera la plus facile que vous aurez à mener jusqu'à " "la fin de la guerre... traître." msgid "Barbarian Wars" msgstr "Guerres barbares" msgid "First Blood" msgstr "Premier sang" msgid "Necromancers" msgstr "Nécromanciens" msgid "Slay the Dwarves" msgstr "Massacre des nains" msgid "Country Lords" msgstr "Seigneurs du pays" msgid "Dragon Master" msgstr "Maître des dragons" msgid "Rebellion" msgstr "Rébellion" msgid "Apocalypse" msgstr "Apocalypse" msgid "Greater Glory" msgstr "Grande gloire" msgid "" "King Archibald requires you to defeat the three enemies in this region. They " "are not allied with one another, so they will spend most of their energy " "fighting amongst themselves. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Le roi Archibald vous demande de vaincre les trois ennemis de cette région. " "Ils ne sont pas alliés les uns aux autres, ils dépenseront donc la majeure " "partie de leur énergie à se battre entre eux. Vous gagnerez lorsque vous " "posséderez tous les châteaux ennemis et qu'il ne restera plus de héros à " "combattre." msgid "" "You must unify the barbarian tribes of the north by conquering them. As in " "the previous mission, the enemy is not allied against you, but they have " "more resources at their disposal. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Vous devez unifier les tribus barbares du nord en les conquérant. Comme dans " "la mission précédente, les ennemis ne sont pas alliés contre vous, mais ils " "ont plus de ressources à leur disposition. Vous gagnerez lorsque vous " "posséderez tous les châteaux ennemis et qu'il n'y aura plus de héros à " "combattre." msgid "" "Do-gooder wizards have taken the Necromancers' castle. You must retake it to " "achieve victory. Remember that while you start with a powerful army, you " "have no castle and must take one within 7 days, or lose this battle. (Hint: " "The nearest castle is to the southeast.)" msgstr "" "Des sorciers bienfaisants ont pris le château des nécromanciens. Reprenez-le " "pour remporter la victoire. Rappelez-vous que si vous commencez avec une " "puissante armée, vous n'avez pas de château et devez en prendre un dans les " "7 jours, ou vous perdrez cette bataille. (Indice : le château le plus proche " "est au sud-est)." msgid "" "The dwarves need conquering before they can interfere in King Archibald's " "plans. Roland's forces have more than one hero and many towns to start with, " "so be ready for attack from multiple directions. You must capture all of the " "enemy towns and castles to claim victory." msgstr "" "Les nains doivent être conquis avant de pouvoir interférer dans les plans du " "roi Archibald. Les forces de Roland ont plus d'un héros et de nombreux " "villages au départ, alors soyez prêt à être attaqué de plusieurs directions. " "Vous devez capturer tous les villages et tous les châteaux ennemis pour " "remporter la victoire." msgid "" "You must put down a peasant revolt led by Roland's forces. All are allied " "against you, but you have Lord Corlagon, an experienced hero, to help you. " "Capture all enemy castles to win." msgstr "" "Vous devez réprimer une révolte paysanne menée par les forces de Roland. " "Tous sont alliés contre vous, mais vous avez Lord Corlagon, un héros " "expérimenté, pour vous aider. Capturez tous les châteaux ennemis pour gagner." msgid "" "There are two enemies allied against you in this mission. Both are well " "armed and seek to evict you from their island. Avoid them and capture Dragon " "City to win." msgstr "" "Il y a deux ennemis alliés contre vous dans cette mission. Tous deux sont " "bien armés et cherchent à vous expulser de leur île. Évitez-les et capturez " "la cité des dragons pour gagner." msgid "" "Your orders are to conquer the country lords that have sworn to serve " "Roland. All of the enemy castles are unified against you. Since you start " "without a castle, you must hurry to capture one before the end of the week. " "Capture all enemy castles for victory." msgstr "" "Vos ordres sont de conquérir les seigneurs du pays qui ont juré de servir " "Roland. Tous les châteaux ennemis sont unis contre vous. Comme vous " "commencez sans château, vous devez vous dépêcher d'en capturer un avant la " "fin de la semaine. Capturez tous les châteaux ennemis pour remporter la " "victoire." msgid "" "Find the Crown before Roland's heroes find it. Archibald will need the Crown " "for the final battle against Roland." msgstr "" "Trouvez la couronne avant que les héros de Roland ne la trouvent. Archibald " "aura besoin de la couronne pour la bataille finale contre Roland." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Roland to win the war, and be sure " "not to lose Archibald in the fight!" msgstr "" "C'est la bataille finale. Vous et votre ennemi êtes tous deux armés " "jusqu'aux dents, et tous sont alliés contre vous. Capturez Roland pour " "gagner la guerre, et assurez-vous de ne pas perdre Archibald dans le combat !" msgid "Arrow's Flight" msgstr "Vol de la flèche" msgid "Island of Chaos" msgstr "L'île du chaos" msgid "The Abyss" msgstr "L'abîme" msgid "Uprising" msgstr "Révolte" msgid "Aurora Borealis" msgstr "Aurore boréale" msgid "Betrayal's End" msgstr "Fin de la trahison" msgid "Corruption's Heart" msgstr "Coeur de la corruption" msgid "The Giant's Pass" msgstr "Col des géants" msgid "" "Subdue the unruly local lords in order to provide the Empire with facilities " "to operate in this region." msgstr "" "Soumettez les seigneurs locaux indisciplinés afin de fournir à l'empire des " "installations pour opérer dans cette région." msgid "" "Eliminate all opposition in this area. Then the first piece of the artifact " "will be yours." msgstr "" "Éliminez tous les opposants dans cette zone. Ensuite, la première pièce de " "l'artéfact sera à vous." msgid "" "The sorceresses to the northeast are rebelling! For the good of the empire " "you must quash their feeble uprising on your way to the mountains." msgstr "" "Les sorcières du nord-est se rebellent ! Pour le bien de l'empire, vous " "devez réprimer leur faible soulèvement en vous rendant dans les montagnes." msgid "" "Having prepared for your arrival, Kraeger has arranged for a force of " "necromancers to thwart your quest. You must capture the castle of Scabsdale " "before the first day of the third week, or the Necromancers will be too " "strong for you." msgstr "" "Ayant préparé votre arrivée, Kraeger a mis en place une force de " "nécromanciens pour contrecarrer votre quête. Vous devez capturer le château " "de Scabsdale avant le premier jour de la troisième semaine, ou les " "nécromanciens seront trop forts pour vous." msgid "" "The barbarian despot in this area is, as yet, ignorant of your presence. " "Quickly, build up your forces before you are discovered and attacked! Secure " "the region by subduing all enemy forces." msgstr "" "Le despote barbare de cette région ignore pour l'instant votre présence. " "Vite, renforcez vos forces avant d'être découvert et attaqué ! Sécurisez la " "région en soumettant toutes les forces ennemies." msgid "" "The Empire is weak in this region. You will be unable to completely subdue " "all forces in this area, so take what you can before reprisal strikes. " "Remember, your true goal is to claim the Helmet of Anduran." msgstr "" "L'Empire est faible dans cette région. Vous ne pourrez pas soumettre " "complètement toutes les forces dans cette zone, alors prenez ce que vous " "pouvez avant que les représailles ne frappent. Rappelez-vous, votre " "véritable objectif est de revendiquer le casque d'Anduran." msgid "For the good of the Empire, eliminate Kraeger." msgstr "Pour le bien de l'Empire, éliminez Kraeger." msgid "" "At last, you have the opportunity and the facilities to rid the Empire of " "the necromancer's evil. Eradicate them completely, and you will be sung as a " "hero for all time." msgstr "" "Vous avez enfin l'occasion et les moyens de débarrasser l'empire du mal des " "nécromanciens. Éradiquez-les complètement, et on chantera vos louanges pour " "l'éternité." msgid "Border Towns" msgstr "Villes frontalières" msgid "Conquer and Unify" msgstr "Conquérir et unifier" msgid "Crazy Uncle Ivan" msgstr "Oncle Fou Ivan" msgid "The Wayward Son" msgstr "Le Fils dévoyé" msgid "Ivory Gates" msgstr "Portes d'ivoire" msgid "The Elven Lands" msgstr "Les Terres elfiques" msgid "The Epic Battle" msgstr "Le combat épique" msgid "The Southern War" msgstr "La guerre du sud" msgid "" "Conquer and unite all the enemy tribes. Don't lose the hero Jarkonas, the " "forefather of all descendants." msgstr "" "Conquérez et unissez toutes les tribus ennemies. Ne perdez pas le héros " "Jarkonas, l'ancêtre de tous les descendants." msgid "" "Your rival, the Kingdom of Harondale, is attacking weak towns on your " "border! Recover from their first strike and crush them completely!" msgstr "" "Votre rival, le Royaume d'Harondale, attaque des villages faibles à votre " "frontière ! Remettez-vous de leur première frappe et écrasez-les " "complètement !" msgid "" "Find your wayward son Joseph who is rumored to be living in the desolate " "lands. Do it before the first day of the third month or it will be of no " "help to your family." msgstr "" "Trouvez votre fils Joseph qui, selon la rumeur, vit dans les terres " "désolées. Faites-le avant le premier jour du troisième mois ou il ne sera " "d'aucune aide pour votre famille." msgid "" "Rescue your crazy uncle Ivan. Find him before the first day of the fourth " "month or it will be no help to your kingdom." msgstr "" "Sauvez votre oncle fou Ivan. Trouvez-le avant le premier jour du quatrième " "mois ou il ne sera d'aucune aide pour votre royaume." msgid "" "Destroy the barbarians who are attacking the southern border of your " "kingdom! Recover your fallen towns, and then invade the jungle kingdom. " "Leave no enemy standing." msgstr "" "Détruisez les barbares qui attaquent la frontière sud de votre royaume ! " "Récupérez vos villages perdus, puis envahissez le royaume de la jungle. Ne " "laissez aucun ennemi debout." msgid "Retake the castle of Ivory Gates, which has fallen due to treachery." msgstr "" "Reprenez le château des portes d'ivoire, qui est tombé à la suite d'une " "trahison." msgid "" "Gain the favor of the elves. They will not allow trees to be chopped down, " "so they will send you wood every 2 weeks. You must complete your mission " "before the first day of the seventh month, or the kingdom will surely fall." msgstr "" "Gagnez la faveur des elfes. Ils ne permettront pas aux arbres d'être coupés, " "ils vous enverront donc du bois toutes les 2 semaines. Vous devez terminer " "votre mission avant le premier jour du septième mois, sinon le royaume va " "sûrement s'effondrer." msgid "" "This is the final battle against your rival kingdom of Harondale. Eliminate " "everyone, and don't lose the hero Jarkonas VI." msgstr "" "C'est la bataille finale contre votre rival, le royaume de Harondale. " "Éliminez tout le monde et ne perdez pas le héros Jarkonas VI." msgid "Fount of Wizardry" msgstr "Source de magie" msgid "Power's End" msgstr "Fin du pouvoir" msgid "The Eternal Scrolls" msgstr "Les parchemins éternels" msgid "The Shrouded Isles" msgstr "Les îles du linceul" msgid "" "Your mission is to vanquish the warring mages in the magical Shrouded Isles. " "The completion of this task will give you a fighting chance against your " "rivals." msgstr "" "Votre mission est de vaincre les mages en guerre dans les îles magiques du " "linceul. L'achèvement de cette tâche vous donnera une chance de vous battre " "contre vos rivaux." msgid "" "The location of the great library has been discovered! You must make your " "way to it, and reclaim the city of Chronos in which it lies." msgstr "" "L'emplacement de la grande bibliothèque a été découverte ! Vous devez vous y " "rendre, et reconquérir le village de Chronos dans lequel elle se trouve." msgid "" "Find the Orb of negation, which is said to be buried in this land. There are " "clues inscribed on stone obelisks which will help lead you to your prize. " "Find the Orb before the first day of the sixth month, or your rivals will " "surely have gotten to the fount before you." msgstr "" "Trouvez l'orbe de la négation, qui serait enterré dans ce pays. Il y a des " "indices inscrits sur les obélisques en pierre qui vous aideront à vous " "conduire à votre prix. Trouvez l'orbe avant le premier jour du sixième mois, " "ou vos rivaux seront sûrement arrivés à la source avant vous." msgid "" "You must take control of the castle of Magic, where the fount of wizardry " "lies. Do this and your victory will be supreme." msgstr "" "Vous devez prendre le contrôle du château de la magie, où se trouve la " "source de magie. Faites-le et votre victoire sera suprême." msgid "Blood is Thicker" msgstr "Sang épais" msgid "King and Country" msgstr "Roi et pays" msgid "Pirate Isles" msgstr "Iles pirates" msgid "Stranded" msgstr "Échoué" msgid "" "Capture the town on the island off the southeast shore in order to construct " "a boat and travel back towards the mainland. Do not lose the hero Gallavant." msgstr "" "Capturez le village sur l'île au large de la côte sud-est afin de construire " "un bateau et de retourner vers le continent. Ne perdez pas le héros " "Gallavant." msgid "" "Find and defeat Martine, the pirate leader, who resides in Pirates Cove. Do " "not lose Gallavant or your quest will be over." msgstr "" "Trouvez et battez Martine, la chef des pirates, qui réside à Pirates Cove. " "Ne perdez pas Gallavant ou votre quête sera terminée." msgid "" "Eliminate all the other forces who oppose the rule of Lord Alberon. " "Gallavant must not die." msgstr "" "Éliminez toutes les autres forces qui résistent au joug de lord Alberon. " "Gallavant ne doit pas mourir." msgid "" "Overthrow the entrenched monarchy of Lord Alberon, and claim all the land in " "your name. Gallavant must not die." msgstr "" "Renversez la monarchie enracinée de lord Alberon et revendiquez toutes les " "terres en votre nom. Gallavant ne doit pas mourir." msgid " bane" msgstr " fléau" msgid " alliance" msgstr " alliance" msgid "Carry-over forces" msgstr "Renforts" msgid " bonus" msgstr " bonus" msgid " defeated" msgstr " vaincu" msgid " will always run away from your army." msgstr " fuiront toujours face à votre armée." msgid " will be willing to join your army." msgstr " voudront toujours se joindre à votre armée." msgid "\"%{artifact}\" artifact will be carried over the scenario." msgstr "L'artéfact \"%{artifact}\" sera conservé dans le scénario suivant." msgid "The army will be carried over the scenario." msgstr "Les renforts rejoindront vos rangs dans le scénario final." msgid "The kingdom will have +%{count} %{resource} each day." msgstr "" "Le royaume obtiendra +%{count} ressources supplémentaires en %{resource} " "chaque jour." msgid "\"%{spell}\" spell will be carried over the scenario." msgstr "Le sort \"%{spell}\" sera conservé dans le scénario suivant." msgid "%{hero} can be hired in the scenario." msgstr "Le héros %{hero} peut être recruté dans ce scénario." msgid "" "%{hero} has been defeated and will not appear in the subsequent scenarios." msgstr "Le héros vaincu %{hero} n'apparaîtra pas dans les scénarios suivants." msgid "The dwarves recognize their allies and gladly join your forces." msgstr "" "Les nains reconnaissent leurs alliés et se joignent volontiers à vos forces." msgid "The ogres recognize you as the Dwarfbane and lumber over to join you." msgstr "" "Les ogres vous reconnaissent comme le fléau des nains et vous rallient." msgid "" "The dragons, snarling and growling, agree to join forces with you, their " "'Ally'." msgstr "" "Les dragons, en grognant, acceptent d'unir leurs forces avec vous, leur " "\"allié\"." msgid "" "As you approach the group of elves, their leader calls them all to " "attention. He shouts to them, \"Who of you is brave enough to join this " "fearless ally of ours?\" The group explodes with cheers as they run to join " "your ranks." msgstr "" "Alors que vous vous approchez du groupe d'elfes, leur chef les appelle tous " "au garde-à-vous. Il leur crie : \"Qui d'entre vous est assez courageux pour " "joindre notre allié intrépide ?\" Le groupe explose d'acclamations en " "courant pour rejoindre vos rangs." msgid "" "The dwarves hail you, \"Any friend of Roland is a friend of ours. You may " "pass.\"" msgstr "" "Les nains vous saluent, \"Tous les amis de Roland sont nos amis. Vous pouvez " "passer.\"" msgid "" "The ogres give you a grunt of recognition, \"Archibald's allies may pass.\"" msgstr "" "Les ogres poussent un grognement de reconnaissance à votre encontre, \"Les " "alliés d'Archibald peuvent passer\"." msgid "" "The dragons see you and call out. \"Our alliance with Archibald compels us " "to join you. Unfortunately you have no room. A pity!\" They quickly scatter." msgstr "" "Les dragons vous voient et crient. \"Notre alliance avec Archibald nous " "oblige à nous joindre à vous. Malheureusement, vous n'avez pas de place. " "C'est dommage !\" Ils se dispersent rapidement." msgid "" "The elves stand at attention as you approach. Their leader calls to you and " "says, \"Let us not impede your progress, ally! Move on, and may victory be " "yours.\"" msgstr "" "Les elfes se tiennent au garde-à-vous à votre approche. Leur chef vous " "appelle et dit : \"N'entravons pas votre progression, allié ! Avancez, et " "que la victoire soit vôtre.\"" msgid "\"The Dwarfbane!!!!, run for your lives.\"" msgstr "\"Le fléau des nains !!!!, sauve qui peut.\"" msgid "campaignBonus|Animate Dead" msgstr "Mort-vivant" msgid "campaignBonus|Chain Lightning" msgstr "Foudre" msgid "campaignBonus|Fireblast" msgstr "Souffle de feu" msgid "campaignBonus|Mass Curse" msgstr "Malédiction massive" msgid "campaignBonus|Mass Haste" msgstr "Célérité de masse" msgid "campaignBonus|Mirror Image" msgstr "Image miroir" msgid "campaignBonus|Resurrect" msgstr "Résurrection" msgid "campaignBonus|Steelskin" msgstr "Chair d'acier" msgid "campaignBonus|Summon Earth" msgstr "Invoquer la terre" msgid "campaignBonus|View Heroes" msgstr "Voir héros" msgid "campaignBonus|Ballista" msgstr "Baliste" msgid "campaignBonus|Black Pearl" msgstr "Perle noire" msgid "campaignBonus|Caster's Bracelet" msgstr "Bracelet magique" msgid "campaignBonus|Defender Helm" msgstr "Casque défenseur" msgid "campaignBonus|Breastplate" msgstr "Plastron divin" msgid "campaignBonus|Dragon Sword" msgstr "Épée du dragon" msgid "campaignBonus|Fizbin Medal" msgstr "Médaille Fizbin" msgid "campaignBonus|Foremost Scroll" msgstr "Parchemin ultime" msgid "campaignBonus|Gauntlets" msgstr "Gantelets" msgid "campaignBonus|Hideous Mask" msgstr "Masque sinistre" msgid "campaignBonus|Mage's Ring" msgstr "Anneau du magicien" msgid "campaignBonus|Major Scroll" msgstr "Parchemin majeur" msgid "campaignBonus|Medal of Honor" msgstr "Médaille d'honneur" msgid "campaignBonus|Medal of Valor" msgstr "Médaille de bravoure" msgid "campaignBonus|Minor Scroll" msgstr "Parchemin mineur" msgid "campaignBonus|Nomad Boots" msgstr "Bottes de nomade" msgid "campaignBonus|Power Axe" msgstr "Puissante hache" msgid "campaignBonus|Spiked Shield" msgstr "Bouclier à pointes" msgid "campaignBonus|Stealth Shield" msgstr "Bouclier furtif" msgid "campaignBonus|Tax Lien" msgstr "Privilège fiscal" msgid "campaignBonus|Thunder Mace" msgstr "Marteau de foudre" msgid "campaignBonus|Traveler's Boots" msgstr "Bottes de sept lieues" msgid "campaignBonus|White Pearl" msgstr "Perle blanche" msgid "campaignBonus|Basic Archery" msgstr "Principes d'archerie" msgid "campaignBonus|Advanced Archery" msgstr "Archerie avancée" msgid "campaignBonus|Expert Archery" msgstr "Expert en archerie" msgid "campaignBonus|Basic Ballistics" msgstr "Principes de balistique" msgid "campaignBonus|Advanced Ballistics" msgstr "Balistique avancée" msgid "campaignBonus|Expert Ballistics" msgstr "Expert en balistique" msgid "campaignBonus|Basic Diplomacy" msgstr "Principes de diplomatie" msgid "campaignBonus|Advanced Diplomacy" msgstr "Diplomatie avancée" msgid "campaignBonus|Expert Diplomacy" msgstr "Expert en diplomatie" msgid "campaignBonus|Basic Eagle Eye" msgstr "Principes d'oeil d'aigle" msgid "campaignBonus|Advanced Eagle Eye" msgstr "Oeil d'aigle avancé" msgid "campaignBonus|Expert Eagle Eye" msgstr "Expert en oeil d'aigle" msgid "campaignBonus|Basic Estates" msgstr "Principes de finances" msgid "campaignBonus|Advanced Estates" msgstr "Finances avancées" msgid "campaignBonus|Expert Estates" msgstr "Expert en finances" msgid "campaignBonus|Basic Leadership" msgstr "Principes de charisme" msgid "campaignBonus|Advanced Leadership" msgstr "Charisme avancé" msgid "campaignBonus|Expert Leadership" msgstr "Expert en charisme" msgid "campaignBonus|Basic Logistics" msgstr "Principes de logistique" msgid "campaignBonus|Advanced Logistics" msgstr "Logistique avancée" msgid "campaignBonus|Expert Logistics" msgstr "Expert en logistique" msgid "campaignBonus|Basic Luck" msgstr "Principes de chance" msgid "campaignBonus|Advanced Luck" msgstr "Chance avancée" msgid "campaignBonus|Expert Luck" msgstr "Expert en chance" msgid "campaignBonus|Basic Mysticism" msgstr "Principes de mysticisme" msgid "campaignBonus|Advanced Mysticism" msgstr "Mysticisme avancé" msgid "campaignBonus|Expert Mysticism" msgstr "Expert en mysticisme" msgid "campaignBonus|Basic Navigation" msgstr "Principes de navigation" msgid "campaignBonus|Advanced Navigation" msgstr "Navigation avancée" msgid "campaignBonus|Expert Navigation" msgstr "Expert en navigation" msgid "campaignBonus|Basic Necromancy" msgstr "Principes de nécromancie" msgid "campaignBonus|Advanced Necromancy" msgstr "Nécromancie avancée" msgid "campaignBonus|Expert Necromancy" msgstr "Expert en nécromancie" msgid "campaignBonus|Basic Pathfinding" msgstr "Principes d'orientation" msgid "campaignBonus|Advanced Pathfinding" msgstr "Orientation avancée" msgid "campaignBonus|Expert Pathfinding" msgstr "Expert en orientation" msgid "campaignBonus|Basic Scouting" msgstr "Principes de reconnaissance" msgid "campaignBonus|Advanced Scouting" msgstr "Reconnaissance avancée" msgid "campaignBonus|Expert Scouting" msgstr "Expert en reconnaissance" msgid "campaignBonus|Basic Wisdom" msgstr "Principes de sagesse" msgid "campaignBonus|Advanced Wisdom" msgstr "Sagesse avancée" msgid "campaignBonus|Expert Wisdom" msgstr "Expert en sagesse" msgid "" "The main hero will have \"%{artifact}\" artifact at the start of the " "scenario." msgstr "" "Le héros principal recevra l'artéfact \"%{artifact}\" au début du scénario." msgid "" "The kingdom will receive %{amount} additional %{resource} at the start of " "the scenario." msgstr "" "Le royaume bénéficiera de %{amount} %{resource} supplémentaire(s) au début " "du scénario." msgid "" "The kingdom will have %{amount} less %{resource} at the start of the " "scenario." msgstr "" "Le royaume aura %{amount} ressource(s) en %{resource} en moins au début du " "scénario." msgid "" "The main hero will have %{count} %{monster} at the start of the scenario." msgstr "Le héros principal recevra %{count} %{monster} au début du scénario." msgid "" "The main hero will have \"%{spell}\" spell at the start of the scenario." msgstr "Le héros principal recevra le sort \"%{spell}\" au début du scénario." msgid "The starting race of the scenario will be %{race}." msgstr "La faction de départ sur ce scénario sera %{race}." msgid "" "The main hero will have additional %{count} %{skill} at the start of the " "scenario." msgstr "" "Le héros principal recevra %{count} %{skill} supplémentaire(s) au début du " "scénario." msgid "The main hero will have %{skill} at the start of the scenario." msgstr "Le héros principal recevra %{skill} au début du scénario." msgid "Roland" msgstr "Roland" msgid "Archibald" msgstr "Archibald" msgid "Price of Loyalty" msgstr "Price of Loyalty" msgid "Voyage Home" msgstr "Le retour" msgid "Wizard's Isle" msgstr "L'archipel" msgid "Descendants" msgstr "Les héritiers" msgid "The %{building} produces %{monster}." msgstr "Le %{building} produit des %{monster}." msgid "Requires:" msgstr "Nécessite :" msgid "Exit this menu without doing anything." msgstr "Sortir de ce menu sans modification." msgid "Cannot build. You have already built here today." msgstr "Construction impossible. Une construction a déjà eu lieu aujourd'hui." msgid "For this action it is necessary to build a castle first." msgstr "Pour effectuer cette action il faut d'abord construire un château." msgid "Cannot build %{name} because the castle is too far away from an ocean." msgstr "Impossible de construire %{name} - le château est trop loin de l'eau." msgid "disable build." msgstr "désactiver la construction." msgid "Cannot afford %{name}." msgstr "Impossible d'acheter %{name}." msgid "%{name} is already built." msgstr "%{name} est déjà construit(e)." msgid "Cannot build %{name}." msgstr "Impossible de construire %{name}." msgid "Build %{name}." msgstr "Construire %{name}." msgid "Blackridge" msgstr "Blackridge" msgid "Hillstone" msgstr "Hillstone" msgid "Pinehurst" msgstr "Pinehurst" msgid "Whiteshield" msgstr "Whiteshield" msgid "Woodhaven" msgstr "Woodhaven" msgid "Blackwind" msgstr "Blackwind" msgid "Bloodreign" msgstr "Bloodreign" msgid "Dragontooth" msgstr "Dragontooth" msgid "Greywind" msgstr "Greywind" msgid "Portsmith" msgstr "Portsmith" msgid "Atlantium" msgstr "Atlantium" msgid "Middle Gate" msgstr "Middle Gate" msgid "Sansobar" msgstr "Sansobar" msgid "Tundara" msgstr "Tundara" msgid "Vulcania" msgstr "Vulcania" msgid "Baywatch" msgstr "Baywatch" msgid "Fountainhead" msgstr "Fountainhead" msgid "Vertigo" msgstr "Vertigo" msgid "Wildabar" msgstr "Wildabar" msgid "Winterkill" msgstr "Winterkill" msgid "Brindamoor" msgstr "Brindamoor" msgid "Lakeside" msgstr "Lakeside" msgid "Nightshadow" msgstr "Nightshadow" msgid "Olympus" msgstr "Olympus" msgid "Sandcaster" msgstr "Sandcaster" msgid "Alamar" msgstr "Alamar" msgid "Burlock" msgstr "Burlock" msgid "Dragadune" msgstr "Dragadune" msgid "Kalindra" msgstr "Kalindra" msgid "Xabran" msgstr "Xabran" msgid "Algary" msgstr "Algary" msgid "Basenji" msgstr "Basenji" msgid "Blackfang" msgstr "Blackfang" msgid "New Dawn" msgstr "New Dawn" msgid "Sorpigal" msgstr "Sorpigal" msgid "Avone" msgstr "Avone" msgid "Big Oak" msgstr "Big Oak" msgid "Chandler" msgstr "Chandler" msgid "Erliquin" msgstr "Erliquin" msgid "Hampshire" msgstr "Hampshire" msgid "Antioch" msgstr "Antioch" msgid "Avalon" msgstr "Avalon" msgid "Roc Haven" msgstr "Roc Haven" msgid "South Mill" msgstr "South Mill" msgid "Weed Patch" msgstr "Weed Patch" msgid "Brownston" msgstr "Brownston" msgid "Hilltop" msgstr "Hilltop" msgid "Weddington" msgstr "Weddington" msgid "Westfork" msgstr "Westfork" msgid "Whittingham" msgstr "Whittingham" msgid "Cathcart" msgstr "Cathcart" msgid "Elk's Head" msgstr "Elk's Head" msgid "Roscomon" msgstr "Roscomon" msgid "Sherman" msgstr "Sherman" msgid "Yorksford" msgstr "Yorksford" msgid "Blackburn" msgstr "Blackburn" msgid "Blacksford" msgstr "Blacksford" msgid "Burton" msgstr "Burton" msgid "Pig's Eye" msgstr "Pig's Eye" msgid "Viper's Nest" msgstr "Viper's Nest" msgid "Fenton" msgstr "Fenton" msgid "Lankershire" msgstr "Lankershire" msgid "Lombard" msgstr "Lombard" msgid "Timberhill" msgstr "Timberhill" msgid "Troy" msgstr "Troy" msgid "Forder Oaks" msgstr "Forder Oaks" msgid "Meramec" msgstr "Meramec" msgid "Quick Silver" msgstr "Quick Silver" msgid "Westmoor" msgstr "Westmoor" msgid "Willow" msgstr "Willow" msgid "Corackston" msgstr "Corackston" msgid "Sheltemburg" msgstr "Sheltemburg" msgid "Cannot recruit - you already have a Hero in this town." msgstr "Impossible de recruter - vous avez déjà un héros dans ce village." msgid "Cannot recruit - you have too many Heroes." msgstr "Impossible de recruter - vous avez trop de héros." msgid "Cannot afford a Hero" msgstr "Pas assez d'or pour un héros" msgid "There is no room in the garrison for this army." msgstr "Il n'y a pas de place dans la garnison pour cette armée." msgid "Fortifications" msgstr "Fortifications" msgid "Farm" msgstr "Ferme" msgid "Thatched Hut" msgstr "Grange" msgid "Archery Range" msgstr "Archerie" msgid "Upg. Archery Range" msgstr "Archerie renforcée" msgid "Blacksmith" msgstr "Forge" msgid "Upg. Blacksmith" msgstr "Forge renforcée" msgid "Armory" msgstr "Armurerie" msgid "Upg. Armory" msgstr "Armurerie renforcée" msgid "Jousting Arena" msgstr "Arène" msgid "Upg. Jousting Arena" msgstr "Arène améliorée" msgid "Cathedral" msgstr "Cathédrale" msgid "Upg. Cathedral" msgstr "Basilique" msgid "Coliseum" msgstr "Colisée" msgid "Garbage Heap" msgstr "Tas d'ordures" msgid "Hut" msgstr "Paillote" msgid "Stick Hut" msgstr "Hutte" msgid "Upg. Stick Hut" msgstr "Hutte renforcée" msgid "Den" msgstr "Tanière" msgid "Adobe" msgstr "Cahute" msgid "Upg. Adobe" msgstr "Cahute renforcée" msgid "Bridge" msgstr "Pont" msgid "Upg. Bridge" msgstr "Pont renforcé" msgid "Pyramid" msgstr "Pyramide" msgid "Rainbow" msgstr "Arc-en-ciel" msgid "Crystal Garden" msgstr "Jardin de cristaux" msgid "Treehouse" msgstr "Cabane" msgid "Cottage" msgstr "Cottage" msgid "Upg. Cottage" msgstr "Cottage renforcé" msgid "Stonehenge" msgstr "Cromlech" msgid "Upg. Stonehenge" msgstr "Grand cromlech" msgid "Fenced Meadow" msgstr "Pré" msgid "sorceress|Red Tower" msgstr "Tour rouge" msgid "Dungeon" msgstr "Donjon" msgid "Waterfall" msgstr "Cascades" msgid "Cave" msgstr "Grotte" msgid "Crypt" msgstr "Crypte" msgid "Nest" msgstr "Nid" msgid "Maze" msgstr "Labyrinthe" msgid "Upg. Maze" msgstr "Grand labyrinthe" msgid "Swamp" msgstr "Marais" msgid "Green Tower" msgstr "Tour verte" msgid "warlock|Red Tower" msgstr "Tour rouge" msgid "Black Tower" msgstr "Tour noire" msgid "Library" msgstr "Bibliothèque" msgid "Orchard" msgstr "Verger" msgid "Habitat" msgstr "Repaire" msgid "Pen" msgstr "Enclos" msgid "Foundry" msgstr "Fonderie" msgid "Upg. Foundry" msgstr "Fonderie renforcée" msgid "Cliff Nest" msgstr "Nid perché" msgid "Ivory Tower" msgstr "Tour d'ivoire" msgid "Upg. Ivory Tower" msgstr "Haute tour d'ivoire" msgid "Cloud Castle" msgstr "Château céleste" msgid "Upg. Cloud Castle" msgstr "Grand château céleste" msgid "Storm" msgstr "Tempête" msgid "Skull Pile" msgstr "Crânes empilés" msgid "Excavation" msgstr "Fouille" msgid "Graveyard" msgstr "Cimetière" msgid "Upg. Graveyard" msgstr "Grand cimetière" msgid "Upg. Pyramid" msgstr "Grande pyramide" msgid "Mansion" msgstr "Manoir" msgid "Upg. Mansion" msgstr "Grand manoir" msgid "Mausoleum" msgstr "Mausolée" msgid "Upg. Mausoleum" msgstr "Grand mausolée" msgid "Laboratory" msgstr "Laboratoire" msgid "Shrine" msgstr "Autel des ténèbres" msgid "" "The Fortifications increase the toughness of the walls, increasing the " "number of turns it takes to knock them down." msgstr "" "Les fortifications augmentent la résistance des murs, augmentant le nombre " "de tours nécessaires pour les détruire." msgid "The Farm increases production of Peasants by %{count} per week." msgstr "La ferme augmente la production de paysans de %{count} par semaine." msgid "" "The Coliseum provides inspiring spectacles to defending troops, raising " "their morale by two during combat." msgstr "" "Le Colisée fournit des spectacles motivants aux troupes en défense, " "augmentant leur moral de deux pendant le combat." msgid "The Garbage Heap increases production of Goblins by %{count} per week." msgstr "" "Le Tas d'ordures augmente la production de gobelins de %{count} par semaine." msgid "The Rainbow increases the luck of the defending units by two." msgstr "L'arc-en-ciel augmente de deux la chance des créatures en défense." msgid "" "The Crystal Garden increases production of Sprites by %{count} per week." msgstr "" "Le jardin de cristaux augmente la production de fées de %{count} par semaine." msgid "The Dungeon increases the income of the town by %{count} gold per day." msgstr "" "Le donjon augmente les revenus du village de %{count} pièces d'or par jour." msgid "The Waterfall increases production of Centaurs by %{count} per week." msgstr "" "Les cascades augmentent la production de centaures de %{count} par semaine." msgid "" "The Library increases the number of spells in the Guild by one for each " "level of the guild." msgstr "" "La bibliothèque rajoute 1 sort à chaque niveau de la guilde des magiciens." msgid "The Orchard increases production of Halflings by %{count} per week." msgstr "Le verger augmente la production de galopins de %{count} par semaine." msgid "The Storm adds +2 to the power of spells of a defending spell caster." msgstr "" "La tempête ajoute +2 à la Puissance des sorts du héros qui défend lors d'un " "siège." msgid "The Skull Pile increases production of Skeletons by %{count} per week." msgstr "" "Les crânes empilés augmentent la production de squelettes de %{count} par " "semaine." msgid "Thieves' Guild" msgstr "Guilde des voleurs" msgid "Tavern" msgstr "Taverne" msgid "Shipyard" msgstr "Ponton" msgid "Well" msgstr "Puits" msgid "Statue" msgstr "Statue" msgid "Marketplace" msgstr "Place du marché" msgid "Moat" msgstr "Fossé" msgid "Castle" msgstr "Château" msgid "Tent" msgstr "Tente" msgid "Captain's Quarters" msgstr "Quartiers du capitaine" msgid "Mage Guild, Level 1" msgstr "Guilde des M. niv. 1" msgid "Mage Guild, Level 2" msgstr "Guilde des M. niv. 2" msgid "Mage Guild, Level 3" msgstr "Guilde des M. niv. 3" msgid "Mage Guild, Level 4" msgstr "Guilde des M. niv. 4" msgid "Mage Guild, Level 5" msgstr "Guilde des M. niv. 5" msgid "" "The Shrine increases the necromancy skill of all your necromancers by 10 " "percent." msgstr "" "L'Autel des ténèbres augmente la puissance de nécromancie de tous les " "nécromanciens de 10 pour cent." msgid "" "The Thieves' Guild provides information on enemy players. Thieves' Guilds " "can also provide scouting information on enemy towns. Additional Guilds " "provide more information." msgstr "" "La Guilde des voleurs fournit des informations sur les joueurs ennemis. Les " "guildes de voleurs vous informent également sur les villages ennemis en vue. " "Plus de guildes donnent des informations plus précises." msgid "The Tavern increases morale for troops defending the castle." msgstr "La taverne augmente le moral des défenseurs du château." msgid "The Shipyard allows ships to be built." msgstr "Le ponton permet la construction de navires." msgid "" "The Well increases the growth rate of all dwellings by %{count} creatures " "per week." msgstr "" "Le puits augmente le taux de croissance de population de %{count} créatures " "par semaine dans chaque demeure." msgid "The Statue increases the town's income by %{count} gold per day." msgstr "" "La statue augmente les revenus du village de %{count} pièces d'or par jour." msgid "The Left Turret provides extra firepower during castle combat." msgstr "" "La tourelle gauche fournit une puissance de feu supplémentaire lorsque le " "château est assiégé." msgid "The Right Turret provides extra firepower during castle combat." msgstr "" "La tourelle droite fournit une puissance de feu supplémentaire lorsque le " "château est assiégé." msgid "" "The Marketplace can be used to convert one type of resource into another. " "The more marketplaces you control, the better the exchange rate." msgstr "" "La place du marché peut être utilisée pour convertir un type de ressource en " "un autre. Plus vous contrôlez de places de marchés, plus le taux de change " "est favorable." msgid "" "The Moat slows attacking units. Any unit entering the moat must end its turn " "there and becomes more vulnerable to attack." msgstr "" "Le fossé ralentit les attaques des ennemis. Chaque créature qui tente de " "franchir le fossé perd son tour et devient plus vulnérable aux attaques." msgid "" "The Castle improves the town's defense and increases its income to %{count} " "gold per day." msgstr "" "Le château améliore la défense du village et augmente ses revenus totaux à " "%{count} pièces d'or par jour." msgid "" "The Tent provides workers to build a castle, provided the materials and the " "gold are available." msgstr "" "La tente permet aux travailleurs de construire un château, si les matériaux " "et l'or sont disponibles." msgid "" "The Captain's Quarters provides a captain to assist in the castle's defense " "when no hero is present." msgstr "" "Les quartiers du capitaine permettent au capitaine d'assurer une aide à la " "défense du château lorsque aucun héros n'est présent." msgid "" "The Mage Guild allows heroes to learn spells and replenish their spell " "points." msgstr "" "La guilde des magiciens permet aux héros d'apprendre des sortilèges et de " "restaurer leurs points de magie." msgid "Recruit %{name}" msgstr "Recruter %{name}" msgid "Month: %{month}, Week: %{week}, Day: %{day}" msgstr "Mois : %{month}, Semaine : %{week}, Jour : %{day}" msgid "" "You must purchase a spell book to use the mage guild, but you currently have " "no room for a spell book. Try giving one of your artifacts to another hero." msgstr "" "Vous devez acheter un grimoire pour utiliser la guilde des magiciens, mais " "vous n'avez actuellement pas de place pour un grimoire. Essayez de donner un " "de vos artéfacts à un autre héros." msgid "Click to show next town." msgstr "Cliquez pour afficher le village suivant." msgid "Show next town" msgstr "Montrer le village suivant" msgid "Click to show previous town." msgstr "Cliquez pour afficher le village précédent." msgid "Show previous town" msgstr "Montrer le village précédent" msgid "This town may not be upgraded to a castle." msgstr "Ce village ne peut pas être amélioré en château." msgid "Town" msgstr "Village" msgid "Exit Castle" msgstr "Quitter le château" msgid "Exit Town" msgstr "Quitter le village" msgid "Show Income" msgstr "Afficher le revenu" msgid "View Hero" msgstr "Voir le héros" msgid "The above spells are available here." msgstr "Les sorts ci-dessus sont disponibles." msgid "The spells the hero can learn have been added to their book." msgstr "Les sorts que le héros peut apprendre ont été ajoutés au grimoire." msgid "A generous tip for the barkeep yields the following rumor:" msgstr "Le tavernier fait courir la rumeur suivante :" msgid "Recruit Hero" msgstr "Recruter un héros" msgid "%{name} is a level %{value} %{race} " msgstr "%{name} est un %{race} de niveau %{value} " msgid "with %{count} artifacts." msgstr "avec %{count} artéfacts." msgid "with 1 artifact." msgstr "avec un artéfact." msgid "without artifacts." msgstr "sans artéfact." msgid "Recruit %{name} the %{race}" msgstr "Recrutement de %{name} le %{race}" msgid "" "'Spread' combat formation spreads the castle's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "La formation de combat 'dispersée' propage les troupes du château du haut " "vers le bas du champ de bataille, avec au moins un espace vide entre chaque " "troupe." msgid "" "'Grouped' combat formation bunches the castle's units together in the center " "of the castle's side of the battlefield." msgstr "" "La formation de combat 'groupée' regroupe les troupes du château au centre " "du champ de bataille, à l'intérieur du château." msgid "Spread Formation" msgstr "Formation dispersée" msgid "Grouped Formation" msgstr "Formation groupée" msgid "Set garrison combat formation to 'Spread'" msgstr "Utiliser la formation de combat 'dispersée' pour la garnison" msgid "Set garrison combat formation to 'Grouped'" msgstr "Utiliser la formation de combat 'groupée' pour la garnison" msgid "Exit Castle Options" msgstr "Quitter les options du château" msgid "Castle Options" msgstr "Options du château" msgid "Not enough resources to recruit creatures." msgstr "Pas assez de ressources pour recruter des créatures." msgid "You are unable to recruit at this time, your ranks are full." msgstr "" "Vous n'êtes pas en mesure de recruter pour le moment. Vos rangs sont pleins." msgid "No creatures available for purchase." msgstr "Pas de créatures disponibles à l'achat." msgid "Recruit Creatures" msgstr "Recruter des créatures" msgid "Max" msgstr "Max" msgid "Hire all creatures in the town." msgstr "Recruter toutes les créatures disponibles dans le village." msgid "Available" msgstr "Disponible" msgid "Town Population Information and Statistics" msgstr "Informations et statistiques sur la population du village" msgid "Damage" msgstr "Dégâts" msgid "HP" msgstr "PV" msgid "Growth" msgstr "Croissance" msgid "week" msgstr "semaine" msgid "View World" msgstr "Tout voir" msgid "View the entire world." msgstr "Voir le monde entier." msgid "Puzzle" msgstr "Puzzle" msgid "View the obelisk puzzle." msgstr "Voir le puzzle de l'obélisque." msgid "Scenario Information" msgstr "Informations sur le scénario" msgid "View information on the scenario you are currently playing." msgstr "Voir les informations du scénario de la partie actuelle." msgid "Dig for the Ultimate Artifact." msgstr "Creuser pour l'ultime artéfact." msgid "Digging" msgstr "Fouille" msgid "Arena" msgstr "Arène" msgid "" "You enter the arena and face a pack of vicious lions. You handily defeat " "them, to the wild cheers of the crowd. Impressed by your skill, the aged " "trainer of gladiators agrees to train you in a skill of your choice." msgstr "" "Vous entrez dans l'arène et vous vous retrouvez face à un groupe de lions " "féroces. Vous vous débarrassez facilement d'eux, sous les applaudissements " "déchaînés de la foule. Impressionné par votre talent, l'entraîneur des " "gladiateurs vous propose d'entraîner le talent de votre choix." msgid "Attack Skill" msgstr "Attaque" msgid "Defense Skill" msgstr "Défense" msgid "Shots" msgstr "Tirs" msgid "Shots Left" msgstr "Tirs restants" msgid "Hit Points" msgstr "Points de vie" msgid "Hit Points Left" msgstr "PV restants" msgid "You can't afford to upgrade your troops!" msgstr "Vous n'avez pas les moyens d'améliorer vos troupes !" msgid "" "Your troops can be upgraded, but it will cost you dearly. Do you wish to " "upgrade them?" msgstr "" "Vos troupes peuvent être améliorées, mais cela vous coûtera cher. Souhaitez-" "vous les améliorer ?" msgid "Are you sure you want to dismiss this army?" msgstr "Voulez-vous vraiment renvoyer cette troupe ?" msgid "Upgrade" msgstr "Améliorer" msgid "Upgrade your troops." msgstr "Améliorer vos troupes." msgid "Dismiss" msgstr "Renvoyer" msgid "Dismiss this army." msgstr "Ronvoyer cette armée." msgid "" "A group of %{monster} with a desire for greater glory wish to join you.\n" "Do you accept?" msgstr "" "Un groupe de %{monster} désirant de plus grandes gloires souhaite vous " "rallier.\n" "Acceptez-vous ?" msgid "Followers" msgstr "Alliés" msgid "" "The %{monster} is swayed by your diplomatic tongue, and offers to join your " "army for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "Le %{monster} est influencé par votre discours diplomatique, et propose de " "rallier votre armée pour la somme de %{gold} pièces d'or.\n" "Acceptez-vous ?" msgid "" "The creatures are swayed by your diplomatic\n" "tongue, and make you an offer:\n" "\n" msgstr "" "Les créatures sont charmées par votre habileté\n" "diplomatique, et vous propose ceci :\n" "\n" msgid "" "%{offer} of the %{total} %{monster} will join your army, and the rest will " "leave you alone, for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "%{offer} des %{total} %{monster} rallieront votre armée, et le reste vous " "laissera tranquille, pour la somme de %{gold} pièces d'or.\n" "Acceptez-vous ?" msgid "" "All %{offer} of the %{monster} will join your army for the sum of %{gold} " "gold.\n" "Do you accept?" msgstr "" "Tous les %{offer} de la %{monster} rallieront votre armée pour la somme de " "%{gold} pièces d'or.\n" "Acceptez-vous ?" msgid "(Rate: %{percent})" msgstr "(Taux : %{percent})" msgid "off" msgstr "coupé" msgid "Music" msgstr "Musique" msgid "Effects" msgstr "Effets" msgid "MIDI" msgstr "MIDI" msgid "MIDI Expansion" msgstr "MIDI Expansion" msgid "External" msgstr "Externe" msgid "Music Type" msgstr "Musique" msgid "3D Audio" msgstr "Audio 3D" msgid "Toggle ambient music level." msgstr "Change le volume de la musique." msgid "Toggle foreground sounds level." msgstr "Change le volume des effets sonores." msgid "Change the type of music." msgstr "Change de type de musique." msgid "Toggle 3D effects of foreground sounds." msgstr "Basculer les effets 3D des sons au premier plan." msgid "Build a new ship:" msgstr "Construire un nouveau navire :" msgid "Resource cost:" msgstr "Coût en ressources :" msgid "Total: " msgstr "Total : " msgid "Need: " msgstr "Besoin : " msgid "Load Game" msgstr "Charger une partie" msgid "No save files to load." msgstr "Pas de sauvegarde disponible." msgid "New Game" msgstr "Nouvelle partie" msgid "Start a single or multi-player game." msgstr "Démarrer un jeu solo ou multijoueur." msgid "Load a previously saved game." msgstr "Charger une partie sauvegardée." msgid "Save Game" msgstr "Enregistrer" msgid "Save the current game." msgstr "Enregistrer le jeu en cours." msgid "Quit" msgstr "Quitter" msgid "Quit out of Heroes of Might and Magic II." msgstr "Quittez Heroes of Might and Magic II." msgid "Language" msgstr "Langue" msgid "Graphics" msgstr "" "Paramètres\n" "d'affichage" msgid "Black & White" msgstr "Noir et blanc" msgid "Mouse Cursor" msgstr "Curseur" msgid "Color" msgstr "Couleur" msgid "Text Support" msgstr "Mode texte" msgid "Change the language of the game." msgstr "Changer la langue du jeu." msgid "Select Game Language" msgstr "Choix de la langue" msgid "Change the graphics settings of the game." msgstr "Changer les paramètres d'affichage." msgid "Toggle colored cursor on or off. This is only an aesthetic choice." msgstr "" "Basculer le curseur en couleur. Il ne s'agit que d'un choix esthétique." msgid "" "Toggle text support mode to output extra information about windows and " "events in the game." msgstr "" "Activer le mode texte pour afficher des informations supplémentaires sur les " "différentes fenêtres et les événements du jeu." msgid "" "Map\n" "Difficulty" msgstr "" "Difficulté\n" "de la carte" msgid "" "Game\n" "Difficulty" msgstr "" "Difficulté\n" "du jeu" msgid "Rating" msgstr "Taux" msgid "Map Size" msgstr "" "Taille de\n" "la carte" msgid "Opponents" msgstr "Adversaires" msgid "Class" msgstr "Alignement" msgid "" "Victory\n" "Conditions" msgstr "" "Conditions\n" "de victoire" msgid "" "Loss\n" "Conditions" msgstr "" "Conditions\n" "de défaite" msgid "First select recipients!" msgstr "Sélectionnez d'abord les destinataires !" msgid "You cannot select %{resource}!" msgstr "Vous ne pouvez pas sélectionner %{resource} !" msgid "Select count %{resource}:" msgstr "Sélectionner %{resource} :" msgid "Select Recipients" msgstr "Sélectionner les destinataires" msgid "Your Funds" msgstr "Vos fonds" msgid "Planned Gift" msgstr "Don planifié" msgid "Gift from %{name}" msgstr "Don de la part de %{name}" msgid "Resolution" msgstr "Résolution" msgid "Fullscreen" msgstr "Plein écran" msgid "window|Mode" msgstr "Mode" msgid "Windowed" msgstr "Fenêtré" msgid "V-Sync" msgstr "V-sync" msgid "on" msgstr "activé" msgid "FPS" msgstr "FPS" msgid "System Info" msgstr "" "Information\n" "système" msgid "Change the resolution of the game." msgstr "Changer la résolution du jeu." msgid "Select Game Resolution" msgstr "Choisir la résolution d'écran" msgid "Toggle between fullscreen and windowed modes." msgstr "Basculez entre les modes plein écran et fenêtré." msgid "" "The V-Sync option can be enabled to resolve flickering issues on some " "monitors." msgstr "" "L'option V-Sync peut être activée pour résoudre les problèmes de " "scintillement sur certains moniteurs." msgid "Show extra information such as FPS and current time." msgstr "" "Afficher des informations supplémentaires telles que le FPS et l'heure " "actuelle." msgid "Hotkey: " msgstr "Raccourci clavier : " msgid "Category: " msgstr "Catégorie : " msgid "Event: " msgstr "Événement : " msgid "Hot Keys:" msgstr "Raccourcis clavier :" msgid "Evil" msgstr "Mal" msgid "Good" msgstr "Bien" msgid "Interface Type" msgstr "Interface" msgid "Hide" msgstr "Libre" msgid "Show" msgstr "Attachée" msgid "Interface" msgstr "Interface" msgid "Slow" msgstr "Lente" msgid "Normal" msgstr "Normal" msgid "Fast" msgstr "Rapide" msgid "Very Fast" msgstr "Très rapide" msgid "Scroll Speed" msgstr "Défilement" msgid "Toggle the type of interface you want to use." msgstr "Bascule le type d'interface que vous voulez utiliser." msgid "Toggle interface visibility." msgstr "Visibilité d'interface." msgid "Toggle colored cursor on or off. This is only an esthetic choice." msgstr "" "Basculer le curseur en couleur. Il ne s'agit que d'un choix esthétique." msgid "Sets the speed at which you scroll the window." msgstr "Définit la vitesse à laquelle vous faites défiler la fenêtre." msgid "Select Game Language:" msgstr "Choix de la langue :" msgid "Click to choose the selected language." msgstr "Cliquez pour valider la langue choisie." msgid "%{name} has gained a level." msgstr "%{name} a gagné un niveau." msgid "%{skill} +1" msgstr "%{skill} +1" msgid "You have learned %{skill}." msgstr "Vous avez appris %{skill}." msgid "" "%{name} has gained a level.\n" "\n" "%{skill} +1" msgstr "" "%{name} a gagné un niveau.\n" "\n" "%{skill} +1" msgid "" "You may learn either:\n" "%{skill1}\n" "or\n" "%{skill2}" msgstr "" "Vous pouvez apprendre :\n" "%{skill1}\n" "ou\n" "%{skill2}" msgid "" "Please inspect our fine wares. If you feel like offering a trade, click on " "the items you wish to trade with and for." msgstr "" "Regardez nos belles marchandises. Si vous souhaitez faire affaire, cliquez " "sur les objets que vous souhaitez échanger." msgid "" "You have received quite a bargain. I expect to make no profit on the deal. " "Can I interest you in any of my other wares?" msgstr "" "Vous avez fait une bonne affaire. Je ne compte pas faire de profit sur cette " "affaire. Puis-je vous proposer d'autres de mes produits ?" msgid "I can offer you %{count} for 1 unit of %{resfrom}." msgstr "Je peux vous offrir %{count} pour une unité de %{resfrom}." msgid "I can offer you 1 unit of %{resto} for %{count} units of %{resfrom}." msgstr "" "Je peux vous offrir 1 unité de %{resto} pour %{count} unités de %{resfrom}." msgid "Min" msgstr "Min" msgid "Qty to trade" msgstr "Qté à échanger" msgid "Trading Post" msgstr "Comptoir" msgid "Your Resources" msgstr "Vos ressources" msgid "Available Trades" msgstr "Échanges possibles" msgid "n/a" msgstr "n/a" # artifact is guarded by? (=gardé). A mine is guarded by? (=gardée). Ruins are guarded by? (=gardées). msgid "guarded by %{count} %{monster}" msgstr "gardé par %{count} %{monster}" msgid "guarded by " msgstr "gardée par " # same "gardé/e/s problem as above. msgid "(available: %{count})" msgstr "(disponible : %{count})" msgid "(empty)" msgstr "(vide)" msgid "already learned" msgstr "déjà appris" msgid "treeOfKnowledge|free" msgstr "gratuit" msgid "already claimed" msgstr "XP déjà acquise" msgid "not claimed" msgstr "XP disponible" msgid "already knows this skill" msgstr "connaît déjà cette compétence" msgid "already has max skills" msgstr "a déjà le nombre maximum de compétences" msgid "(already visited)" msgstr "(déjà visité)" msgid "(not visited)" msgstr "(non visité)" msgid "%{color} Barrier" msgstr "Barrière magique %{color}" msgid "(tent visited)" msgstr "(tente visitée)" msgid "%{color} Tent" msgstr "Tente %{color}" msgid "Road" msgstr "Route" msgid "(digging ok)" msgstr "(fouille possible)" msgid "(no digging)" msgstr "(fouille impossible)" msgid "penalty: %{cost}" msgstr "pénalité : %{cost}" msgid "Defenders:" msgstr "Défenseurs :" msgid "Unknown" msgstr "Inconnu" msgid "%{name} (Level %{level})" msgstr "%{name} (niveau %{level})" msgid "Attack:" msgstr "Attaque :" msgid "Defense:" msgstr "Défense :" msgid "Spell Power:" msgstr "Puissance :" msgid "Knowledge:" msgstr "Connaissance :" msgid "Spell Points:" msgstr "Pts. de magie :" msgid "Move Points:" msgstr "Mouvement :" msgid "Uncharted Territory" msgstr "Terra Incognita" msgid "Cost per troop:" msgstr "Coût par troupe :" msgid "Available: %{count}" msgstr "Disponible : %{count}" msgid "Number to buy:" msgstr "Recruter :" msgid "Recruit selected monsters." msgstr "Recruter les forces sélectionnées." msgid "Select maximum monsters to be recruited." msgstr "Sélectionnez les forces maximales à recruter." msgid "Select only 1 monster to be recruited." msgstr "Ne sélectionnez qu'une seule unité à recruter." msgid "Select Game Resolution:" msgstr "Choisir la résolution d'écran:" msgid "Click to apply the selected resolution." msgstr "Cliquez pour valider la résolution choisie." msgid "Click to apply the entered text." msgstr "Cliquez pour valider le texte." msgid "Click to open the Virtual Keyboard dialog." msgstr "Cliquez pour ouvrir la fenêtre du clavier virtuel." msgid "Open Virtual Keyboard" msgstr "Open Virtual Keyboard" msgid "How many troops to move?" msgstr "Troupes à déplacer :" msgid "Fast separation into slots:" msgstr "Répartition rapide :" msgid "Map: " msgstr "Carte : " msgid "" "\n" "\n" "Month: " msgstr "" "\n" "\n" "Mois : " msgid ", Week: " msgstr ", Semaine : " msgid ", Day: " msgstr ", Jour : " msgid "" "\n" "\n" "Location: " msgstr "" "\n" "\n" "Emplacement : " msgid "Click to save the current game." msgstr "Enregistrer le jeu en cours." msgid "Click to load a previously saved game." msgstr "Charger une partie sauvegardée." msgid "Are you sure you want to delete file:" msgstr "Voulez-vous vraiment effacer le fichier :" msgid "Warning!" msgstr "Attention !" msgid "File to Save:" msgstr "Fichier à sauvegarder :" msgid "File to Load:" msgstr "Fichier à charger :" msgid "Accept the choice made." msgstr "Confirmez le choix." msgid "%{color} %{race} hero" msgstr "Héros %{race} %{color}" msgid "Terrain object" msgstr "Objets généraux" msgid "Select Skill:" msgstr "Choisir compétence :" msgid "Select Spell:" msgstr "Choisir sort :" msgid "Select Artifact:" msgstr "Choisir artéfact :" msgid "Select Monster:" msgstr "Choisir créature :" msgid "Select Hero:" msgstr "Choisir héros :" msgid "Select Treasure:" msgstr "Choix du trésor :" msgid "Select Ocean Object:" msgstr "Choix d'élément (océan) :" msgid "Castle/town placing" msgstr "Choix du village à placer" msgid "doubleLinedRace|Neutral" msgstr "Neutre" msgid "race|Random" msgstr "Aléatoire" msgid "Click to start placing the selected castle/town." msgstr "Cliquez pour commencer le placement du village/château sélectionné." msgid "Click to select town placing." msgstr "Cliquez pour sélectionner le village." msgid "Click to select castle placing." msgstr "Cliquez pour sélectionner le château." msgid "%{color} %{race} %{townOrCastle}" msgstr "%{townOrCastle} %{race} %{color}" msgid "race|Neutral" msgstr "Neutre" msgid "You will place" msgstr "Vous avez choisi de placer" msgid "Click to select this class." msgstr "Cliquez ici pour sélectionner cet alignement." msgid "Click to select this color." msgstr "Cliquez ici pour sélectionner cette couleur." msgid "Select Dwelling:" msgstr "Séléctionner demeure :" msgid "Select Landscape Object:" msgstr "Choix d'élément (paysage) :" msgid "Mine placing" msgstr "Choix de la mine à placer" msgid "" "Resource\n" "type:" msgstr "" "Type de\n" "ressource :" msgid "%{mineName} appearance:" msgstr "Aspect de %{mineName} :" msgid "Click to select %{object} as the resource generator to be placed." msgstr "" "Cliquez ici pour sélectionner %{objet} comme générateur de ressources à " "placer." msgid "Select Mountain Object:" msgstr "Choix d'élément (montagnes) :" msgid "Select Rock Object:" msgstr "Choix d'élément (rochers) :" msgid "Select Tree Object:" msgstr "Choix d'élément (arbres) :" msgid "Select Power Up Object:" msgstr "Choix d'élément (bonus) :" msgid "Select Adventure Object:" msgstr "Choix d'élément (aventure) :" msgid "Map Type:\n" msgstr "Type de carte :\n" msgid "The Succession Wars" msgstr "The Succession Wars" msgid "The Price of Loyalty" msgstr "Le prix de la loyauté" msgid "Resurrection" msgstr "Résurrection" msgid "Lose all your heroes and towns." msgstr "Perdre tous les héros et tous les villages." msgid "Lose a specific town." msgstr "Perdre un village spécifique." msgid "Lose a specific hero." msgstr "Perdre un héros spécifique." msgid "Run out of time. Fail to win by a certain point." msgstr "Manquer de temps. Ne pas gagner avant un certain point." msgid "Loss Condition" msgstr "Conditions de défaite" msgid "Defeat all enemy heroes and towns." msgstr "Vaincre tous les héros et villages ennemis." msgid "Capture a specific town." msgstr "Capturer un village spécifique." msgid "Defeat a specific hero." msgstr "Vaincre un héros spécifique." msgid "Find a specific artifact." msgstr "Trouver un artéfact spécifique." msgid "Your side defeats the opposing side." msgstr "Votre camp l'emporte sur le camp adverse." msgid "Accumulate a large amount of gold." msgstr "Accumuler une grande quantité d'or." msgid "Victory Condition" msgstr "Conditions de victoire" msgid "Map difficulty:" msgstr "Difficulté de la carte :" msgid "N" msgstr "P" msgid "No maps exist at that size" msgstr "Aucune carte de cette taille n'existe" msgid "Small Maps" msgstr "Petites cartes" msgid "View only maps of size small (36 x 36)." msgstr "Voir seulement les petites cartes (36x36)." msgid "Medium Maps" msgstr "Cartes moyennes" msgid "View only maps of size medium (72 x 72)." msgstr "Voir seulement les cartes de taille moyenne (72x72)." msgid "Large Maps" msgstr "Grandes cartes" msgid "View only maps of size large (108 x 108)." msgstr "Voir seulement les grandes cartes (108x108)." msgid "Extra Large Maps" msgstr "Très grandes cartes" msgid "View only maps of size extra large (144 x 144)." msgstr "Voir seulement les très grandes cartes (144x144)." msgid "All Maps" msgstr "Toutes les cartes" msgid "View all maps, regardless of size." msgstr "Voir toutes les cartes, sans tenir compte de leur taille." msgid "Players Icon" msgstr "Icônes des joueurs" msgid "" "Indicates how many players total are in the scenario. Any positions not " "occupied by humans will be occupied by computer players." msgstr "" "Indique combien de joueurs au total sont dans le scenario. Toute position " "non occupée par un humain sera occupée par l'ordinateur." msgid "" "Indicates whether the map\n" "is small (36 x 36), medium\n" "(72 x 72), large (108 x 108),\n" "or extra large (144 x 144)." msgstr "" "Indique si la carte est\n" "petite (36x36), moyenne\n" "(72x72), grande (108x108)\n" "ou très grande (144x144)." msgid "Size Icon" msgstr "Taille de l'icône" msgid "" "Indicates whether the map is made for \"The Succession Wars\" or \"The Price " "of Loyalty\" version of the game." msgstr "" "Indique si la carte est faite pour la version du jeu \"The Succession Wars\" " "ou \"The Price of Loyalty\"." msgid "Map Type" msgstr "Type de carte" msgid "Selected Name" msgstr "Carte sélectionnée" msgid "The name of the currently selected map." msgstr "Le nom de la carte actuellement sélectionnée." msgid "Selected Map Difficulty" msgstr "Difficulté de la carte choisie" msgid "" "The map difficulty of the currently selected map. The map difficulty is " "determined by the scenario designer. More difficult maps might include more " "or stronger enemies, fewer resources, or other special conditions making " "things tougher for the human player." msgstr "" "Difficulté de la carte actuellement sélectionnée. La difficulté de la carte " "est déterminée par le concepteur du scénario. Les cartes plus difficiles " "peuvent inclure des ennemis plus nombreux ou plus forts, moins de ressources " "ou d'autres conditions spéciales rendant les choses plus difficiles pour le " "joueur humain." msgid "Selected Description" msgstr "Description sélectionnée" msgid "The description of the currently selected map." msgstr "La description de la carte actuellement sélectionnée." msgid "Jump" msgstr "Immédiat" msgid "Hero Speed" msgstr "Vitesse" msgid "Don't Show" msgstr "Masquer" msgid "Enemy Speed" msgstr "Vitesse IA" msgid "Auto Resolve" msgstr "Combat auto" msgid "Auto, No Spells" msgstr "Auto sans magie" msgid "Battles" msgstr "Batailles" msgid "autoBattle|Manual" msgstr "Manuelles" msgid "Change the speed at which your heroes move on the main screen." msgstr "" "Change la vitesse à laquelle vos héros se déplacent sur l'écran principal." msgid "" "Sets the speed that A.I. heroes move at. You can also elect not to view A.I. " "movement at all." msgstr "" "Définit la vitesse à laquelle les héros CPU se déplacent. Vous pouvez " "également choisir de ne pas voir du tout le mouvement des CPU." msgid "Change the interface settings of the game." msgstr "Modifier les paramètres de l'interface du jeu." msgid "Interface Settings" msgstr "Paramètres de l'interface" msgid "Toggle instant battle mode." msgstr "Change les modes de combat instantané." msgid "Att." msgstr "Att." msgid "Def." msgstr "Déf." msgid "Power" msgstr "Puiss." msgid "Knowl" msgstr "Conn." msgid "1st" msgstr "1er" msgid "2nd" msgstr "2ème" msgid "3rd" msgstr "3ème" msgid "4th" msgstr "4ème" msgid "5th" msgstr "5ème" msgid "6th" msgstr "6ème" msgid "Oracle: Player Rankings" msgstr "Oracle : Classement des joueurs" msgid "Thieves' Guild: Player Rankings" msgstr "Guilde des voleurs : Classement des joueurs" msgid "Number of Towns:" msgstr "Nombre de villages :" msgid "Number of Castles:" msgstr "Nombre de châteaux :" msgid "Number of Heroes:" msgstr "Nombre de héros :" msgid "Gold in Treasury:" msgstr "Or dans la trésorerie :" msgid "Wood & Ore:" msgstr "Bois et fer :" msgid "Gems, Cr, Slf & Mer:" msgstr "Gemmes, Cr, Sfr & Mer :" msgid "Obelisks Found:" msgstr "Obélisques trouvés :" msgid "Artifacts:" msgstr "Artéfacts :" msgid "Total Army Strength:" msgstr "Force totale de l'armée :" msgid "Income:" msgstr "Rentrées d'argent :" msgid "Best Hero:" msgstr "Meilleur héros :" msgid "Best Hero Stats:" msgstr "Stats du meilleur héros :" msgid "Personality:" msgstr "Personnalité :" msgid "Best Monster:" msgstr "Meilleure créature :" msgid "Map filename" msgstr "Nom du fichier pour la carte" # Needs line break to have insectile space but keep partie and ? together. msgid "" "Are you sure you want to load a new map? (Any unsaved changes to the current " "map will be lost.)" msgstr "" "Voulez-vous vraiment charger une carte ? (Toutes les modifications non " "enregistrées seront perdues.)" # Needs line break to have insectile space but keep partie and ? together. msgid "" "Are you sure you want to create a new map? (Any unsaved changes to the " "current map will be lost.)" msgstr "" "Voulez-vous vraiment créer une nouvelle carte ? (Toutes les modifications " "non enregistrées seront perdues.)" msgid "" "Create a new map, either from scratch or using the random map generator." msgstr "" "Créer une nouvelle carte à la main ou à l'aide du générateur aléatoire." msgid "New Map" msgstr "Nouvelle carte" msgid "Load Map" msgstr "Charger une carte" msgid "Load an existing map." msgstr "Charger une carte existante." msgid "Save Map" msgstr "Sauvegarder la carte" msgid "Save the current map." msgstr "Sauvegarder la carte actuelle." msgid "Quit out of the map editor." msgstr "Quitter l'éditeur de carte." msgid "Objects cannot be placed outside the map." msgstr "Les élements ne peuvent pas être placés en dehors de la carte." msgid "Monsters cannot be placed on water." msgstr "Les créatures ne peuvent pas être placées sur l'eau." msgid "Choose a tile which does not contain any objects." msgstr "Sélectionnez une case qui n'est pas déjà occupée." msgid "Treasures cannot be placed on water." msgstr "Les trésors ne peuvent pas être placés sur l'eau." msgid "Heroes cannot be placed on water." msgstr "Les héros ne peuvent pas être placés sur l'eau." msgid "" "A maximum of %{count} heroes of the same color can be placed on the map." msgstr "" msgid "Artifacts cannot be placed on water." msgstr "Les artéfacts ne peuvent pas être placés sur l'eau." msgid "Mountains cannot be placed on water." msgstr "Les montagnes ne peuvent pas être placées sur l'eau." msgid "Rocks cannot be placed on water." msgstr "Les rochers ne peuvent pas être placés sur l'eau." msgid "Trees cannot be placed on water." msgstr "Les arbres ne peuvent pas être placés sur l'eau." msgid "Ocean object must be placed on water." msgstr "Les éléments liés à la mer doivent être placés sur l'eau." msgid "Landscape objects cannot be placed on water." msgstr "Les éléments de paysage ne peuvent pas être placés sur l'eau." msgid "Towns cannot be placed on water." msgstr "Les villages ne peuvent pas être placés sur l'eau." msgid "Mines cannot be placed on water." msgstr "Les mines ne peuvent pas être placées sur l'eau." msgid "Dwellings cannot be placed on water." msgstr "Les demeures ne peuvent pas être placées sur l'eau." msgid "Power-ups cannot be placed on water." msgstr "Les bonus ne peuvent pas être placés sur l'eau." msgid "Adventure objects cannot be placed on water." msgstr "Les éléments d'aventure ne peuvent pas être placés sur l'eau." msgid "Used to place %{object}." msgstr "Permet de placer %{object}." msgid "Select object type" msgstr "Choix d'élément" msgid "" "Click here to\n" "select a monster." msgstr "" "Cliquez ici pour\n" "sélectionner une créature." msgid "" "Click here to\n" "select another monster." msgstr "" "Cliquez ici pour\n" "sélectionner un autre créature." msgid "" "Cell\n" "Details" msgstr "" "Details\n" "cellule" msgid "Roads" msgstr "Routes" msgid "Streams" msgstr "Rivières" msgid "Erase" msgstr "Effacer" msgid "Mountains" msgstr "Montagnes" msgid "Rocks" msgstr "Rochers" msgid "Trees" msgstr "Arbres" msgid "Water Objects" msgstr "Éléments d'eau" msgid "Miscellaneous" msgstr "Divers" msgid "Artifacts" msgstr "Artéfacts" msgid "Dwellings" msgstr "Demeures" msgid "Mines" msgstr "Mines" msgid "Power-ups" msgstr "Bonus" msgid "Treasures" msgstr "Trésors" msgid "Towns" msgstr "Villages" msgid "Heroes" msgstr "Héros" msgid "editorErasure|Terrain" msgstr "de terrains" msgid "editorErasure|Castles" msgstr "de châteaux" msgid "editorErasure|Monsters" msgstr "de monstres" msgid "editorErasure|Heroes" msgstr "de héros" msgid "editorErasure|Artifacts" msgstr "d'artéfacts" msgid "editorErasure|Roads" msgstr "de routes" msgid "editorErasure|Streams" msgstr "de rivières" msgid "editorErasure|Treasures" msgstr "de trésors" msgid "" "Draws terrain in\n" "%{size} by %{size} square increments." msgstr "" "Place le terrain sélectionné sur la carte\n" "(précision: %{size} cases sur %{size})." msgid "" "Erases objects in\n" "%{size} by %{size} square increments." msgstr "" "Efface des éléments sélectionnés sur la carte\n" "(précision: %{size} cases sur %{size})." msgid "Small Brush" msgstr "Petit pinceau" msgid "Medium Brush" msgstr "Pinceau moyen" msgid "Large Brush" msgstr "Grand pinceau" msgid "Area Fill" msgstr "" msgid "Used to click and drag for filling in large areas." msgstr "" msgid "Clear Area" msgstr "" msgid "Used to click and drag for clearing large areas." msgstr "" msgid "" "Costs %{rate} times normal movement for all heroes. (Pathfinding reduces or " "eliminates the penalty.)" msgstr "" msgid "Traversable only by boat." msgstr "" msgid "No special modifiers." msgstr "" msgid "Toggle the erasure of %{type} objects." msgstr "Effacement %{type}." msgid "" "Objects of this type will be deleted with the Erase tool. Left-click here to " "deselect this type. Press and hold this button to deselect all other object " "types." msgstr "" msgid "" "Objects of this type will NOT be deleted with the Erase tool. Left-click " "here to select this type. Press and hold this button to select all other " "object types." msgstr "" msgid "Terrain Mode" msgstr "Terrains" msgid "Used to draw the underlying grass, dirt, water, etc. on the map." msgstr "" msgid "Landscape Objects Mode" msgstr "Éléments de paysage" msgid "" "Used to place landscape objects (mountains, rocks, trees, etc.) on the map." msgstr "" msgid "Detail Mode" msgstr "Details" msgid "Used for special editing of monsters, heroes and towns." msgstr "Permet d'éditer les monstres, les héros et les villes." msgid "Adventure Objects Mode" msgstr "Éléments d'aventure" msgid "" "Used to place adventure objects (artifacts, dwellings, mines, treasures, " "etc.) on the map." msgstr "" msgid "Kingdom Objects Mode" msgstr "Royaumes" msgid "Used to place kingdom objects (towns, castles and heroes) on the map." msgstr "" msgid "Monsters Mode" msgstr "Créatures" msgid "Used to place monsters on the map." msgstr "Permet de placer des créatures sur la carte." msgid "Allows you to draw streams by clicking and dragging." msgstr "" msgid "Stream Mode" msgstr "Rivières" msgid "Allows you to draw roads by clicking and dragging." msgstr "" msgid "Road Mode" msgstr "Routes" msgid "Erase Mode" msgstr "Effacement" msgid "Used to erase objects from the map." msgstr "" msgid "Change between zoom and normal view." msgstr "" msgid "Magnify" msgstr "" msgid "Undo" msgstr "" msgid "Undo your last action." msgstr "" msgid "Create a new map either from scratch or using the random map generator." msgstr "" msgid "Edit map title, description, and other general information." msgstr "" msgid "Specifications" msgstr "Spécificités" msgid "File Options" msgstr "Options fichier" msgid "" "Open the file options menu, where you can save or load maps, or quit out of " "the editor." msgstr "" msgid "View the editor system options, which let you customize the editor." msgstr "" msgid "Create a map that is %{size} squares wide and %{size} squares high." msgstr "" msgid "Cancel back to the New Map menu." msgstr "Annuler et revenir au menu de création de carte." msgid "Cancel back to the main menu." msgstr "Annuler et revenir au menu principal." msgid "From Scratch" msgstr "" msgid "Start from scratch with a blank map." msgstr "" msgid "Create a randomly generated map." msgstr "" msgid "Random" msgstr "Aléatoire" msgid "Cancel back to the Map Editor main menu." msgstr "Annuler et revenir au menu principal de l'éditeur." msgid "No maps available!" msgstr "Aucune carte disponible !" msgid "Warning" msgstr "Attention" msgid "difficulty|Easy" msgstr "Facile" msgid "difficulty|Normal" msgstr "Normal" msgid "difficulty|Hard" msgstr "Difficile" msgid "difficulty|Expert" msgstr "Expert" msgid "difficulty|Impossible" msgstr "Impossible" msgid "and more..." msgstr "et plus encore..." msgid "Easy" msgstr "Facile" msgid "Hard" msgstr "Difficile" msgid "Campaign Difficulty" msgstr "Difficulté de la campagne" msgid "" "Choose this difficulty if you want to prefer game story over challenge. AI " "is weaker in comparison with normal difficulty." msgstr "" "Choisissez cette difficulté si vous préférez profiter de l'histoire du jeu " "sans challenge. L'IA est plus faible dans ce mode de difficulté." msgid "" "Choose this difficulty to enjoy the campaign as per the original design." msgstr "" "Choisissez cette difficulté si vous voulez jouer à la campagne telle qu'elle " "a été conçue originellement." msgid "" "Choose this difficulty if you want challenge. AI is stronger in comparison " "with normal difficulty." msgstr "" "Choisissez cette difficulté si vous voulez un vrai challenge. L'IA sera plus " "forte dans ce mode de difficulté." msgid "" "Congratulations!\n" "\n" "Days: %{days}\n" msgstr "" "Félicitations !\n" "\n" "Jours : %{days}\n" msgid "" "\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" "\n" "Difficulté : %{difficulty}\n" "\n" msgid "" "Score: %{score}\n" "\n" "Rating:\n" "%{rating}" msgstr "" "Score : %{score}\n" "\n" "Taux :\n" "%{rating}" msgid "Start the selected scenario." msgstr "Démarrer le scénario sélectionné." msgid "View Intro" msgstr "Voir l'intro" msgid "View Intro videos for the current state of the campaign." msgstr "Voir la vidéo d'introduction du scénario actuel." msgid "" "Select the campaign difficulty. This can be lowered at any point during the " "campaign." msgstr "" "Choisissez la difficulté de la campagne. Elle pourra être abaissée à tout " "moment au cours de la campagne." msgid "Restart the current scenario." msgstr "Recommencer le scénario actuel." msgid "Difficulty" msgstr "Difficulté" msgid "" "You have changed to a lower difficulty for the campaign. You will not be " "able to revert this after this point. The high score will be calculated " "based solely on the new difficulty. Do you want to proceed?" msgstr "" "Vous avez diminué la difficulté de la campagne. Vous ne pourrez plus annuler " "ce changement après validation. Le score final sera calculé en se basant " "seulement sur le nouveau niveau de difficulté. Voulez-vous confirmer ?" msgid "Are you sure you want to restart this scenario?" msgstr "Voulez-vous vraiment recommencer ce scénario ?" msgid "Campaign Scenario loading failure" msgstr "Échec du chargement du scénario de campagne" msgid "Please make sure that campaign files are correct and present." msgstr "Assurez-vous que les fichiers de campagne sont corrects et présents." msgid "Days spent" msgstr "Jours passés" msgid "The number of days spent on this campaign." msgstr "Le nombre de jours passés dans cette campagne." msgid "Project Coordination and Core Development" msgstr "Coordination du projet et développement" msgid "Development" msgstr "Développement" msgid "Visit us at " msgstr "Rendez-vous sur " msgid "QA and Support" msgstr "AQ et support" msgid "Dev and Support" msgstr "Dév et support" msgid "Special Thanks to" msgstr "Remerciements spéciaux à" msgid "and many other contributors!" msgstr "et aux nombreux autres contributeurs !" msgid "and many-many other supporters!" msgstr "et beaucoup, beaucoup d'autres supporteurs !" msgid "Support us at" msgstr "Soutenez-nous sur" msgid "local-donation-platform|https://www.patreon.com/fheroes2" msgstr "https://www.patreon.com/fheroes2" msgid "Connect with us at" msgstr "Prenez contact avec nous sur" msgid "local-social-network|https://www.facebook.com/groups/fheroes2" msgstr "https://www.facebook.com/groups/fheroes2" msgid "Need help with the game?" msgstr "Besoin d'aide avec le jeu ?" msgid "Original project before 0.7" msgstr "Projet original avant 0.7" msgid "Heroes of Might and Magic II: The Succession Wars team" msgstr "L'équipe de Heroes of Might and Magic II: The Succession Wars" msgid "Designed and Directed" msgstr "Conçu et réalisé" msgid "Programming and Design" msgstr "Programmation et conception" msgid "Executive Producer" msgstr "Producteur exécutif" msgid "Producer" msgstr "Producteur" msgid "Additional Design" msgstr "Conception supplémentaire" msgid "Additional Programming" msgstr "Programmation supplémentaire" msgid "Musical Production" msgstr "Production musicale" msgid "Music and Sound Design" msgstr "Musique et conception sonore" msgid "Vocalists" msgstr "Vocalistes" msgid "Art Director" msgstr "Directeur artistique" msgid "Assistant Art Director" msgstr "Directeur artistique adjoint" msgid "Artists" msgstr "Artistes" msgid "QA Manager" msgstr "Responsable AQ" msgid "QA" msgstr "AQ" msgid "Writing" msgstr "Écriture" msgid "Manual and Helpfile" msgstr "Manuel et fichier d'aide" msgid "Scenarios" msgstr "Scénarios" msgid "Heroes of Might and Magic II: The Price of Loyalty team" msgstr "L'équipe de Heroes of Might and Magic II: The Price of Loyalty" msgid "Design Lead" msgstr "Chef du design" msgid "Designers" msgstr "Concepteurs" msgid "Programming Lead" msgstr "Responsable de programmation" msgid "Art Lead" msgstr "Responsable artistique" msgid "Playtesters" msgstr "Testeurs" msgid "Designer" msgstr "Concepteur" msgid "Producers" msgstr "Producteurs" msgid "QA Managers" msgstr "Responsables AQ" msgid "Sound Design" msgstr "Conception sonore" msgid "Town Themes" msgstr "Thèmes des villages" msgid "Alto Sax" msgstr "Saxo alto" msgid "Harpsichord and Piano" msgstr "Clavecin et piano" msgid "Basso Vocal" msgstr "Basse (Voix)" msgid "Soprano Vocal" msgstr "Soprano (Voix)" msgid "Recorded at %{recordingStudio}" msgstr "Enregistrée aux %{recordingStudio}" msgid "credits|Manual" msgstr "Manuel" msgid "German Consultant" msgstr "Consultant allemand" msgid "Map Designers" msgstr "Concepteurs de cartes" msgid "Package Design" msgstr "Conception du conditionnement" msgid "Your Name" msgstr "Votre nom" msgid "Unknown Hero" msgstr "Héros inconnu" msgid "Standard" msgstr "Jeu standard" msgid "View High Scores for Standard Maps." msgstr "Voir l'écran des meilleurs scores des cartes standards." msgid "Campaign" msgstr "Campagne" msgid "View High Scores for Campaigns." msgstr "Voir l'écran des meilleurs scores." msgid "hotkey|default okay event" msgstr "Confirmer l'action prédéfinie" msgid "hotkey|default cancel event" msgstr "Annuler l'action en cours" msgid "hotkey|default left" msgstr "Direction gauche" msgid "hotkey|default right" msgstr "Direction droite" msgid "hotkey|default up" msgstr "Direction haut" msgid "hotkey|default down" msgstr "Direction bas" msgid "hotkey|toggle fullscreen" msgstr "Basculer en plein écran" msgid "hotkey|toggle text support mode" msgstr "Basculer en mode texte" msgid "hotkey|new game" msgstr "Nouveau partie" msgid "hotkey|load game" msgstr "Charger une partie" msgid "hotkey|highscores" msgstr "Écran des meilleurs scores" msgid "hotkey|credits" msgstr "Crédits" msgid "hotkey|standard game" msgstr "Partie standard" msgid "hotkey|campaign game" msgstr "Partie campagne" msgid "hotkey|multi-player game" msgstr "Partie multijoueur" msgid "hotkey|settings" msgstr "Paramètres" msgid "hotkey|quit" msgstr "Quitter" msgid "hotkey|select map" msgstr "Sélectionner carte" msgid "hotkey|select small map size" msgstr "Voir les petites cartes" msgid "hotkey|select medium map size" msgstr "Voir les cartes moyennes" msgid "hotkey|select large map size" msgstr "Voir les grandes cartes" msgid "hotkey|select extra large map size" msgstr "Voir les très grandes cartes" msgid "hotkey|select all map sizes" msgstr "Voir toutes les cartes" msgid "hotkey|hotseat game" msgstr "Partie à tour de rôle" msgid "hotkey|battle only game" msgstr "Mode bataille" msgid "hotkey|choose the original campaign" msgstr "Sélectionner la campagne originale" msgid "hotkey|choose the expansion campaign" msgstr "Choisir les campagnes de l'extension \"The Price of Loyalty\"" msgid "hotkey|map editor main menu" msgstr "Menu principal de l'éditeur" msgid "hotkey|new map menu" msgstr "Nouvelle carte" msgid "hotkey|load map menu" msgstr "Menu de chargement de carte" msgid "hotkey|new map from scratch" msgstr "Carte vierge" msgid "hotkey|new random map" msgstr "Nouvelle carte aléatoire" msgid "hotkey|undo last action" msgstr "Annuler la dernière action" msgid "hotkey|redo last action" msgstr "Refaire la dernière action" msgid "hotkey|roland campaign" msgstr "Campagne de Roland" msgid "hotkey|archibald campaign" msgstr "Campagne d'Archibald" msgid "hotkey|the price of loyalty campaign" msgstr "La campagne \"Price of Loyalty\"" msgid "hotkey|voyage home campaign" msgstr "La campagne \"Le retour\"" msgid "hotkey|wizard's isle campaign" msgstr "La campagne \"L'archipel\"" msgid "hotkey|descendants campaign" msgstr "La campagne \"Les héritiers\"" msgid "hotkey|select first campaign bonus" msgstr "Choisir le premier bonus de campagne" msgid "hotkey|select second campaign bonus" msgstr "Choisir le second bonus de campagne" msgid "hotkey|select third campaign bonus" msgstr "Choisir le troisième bonus de campagne" msgid "hotkey|view campaign intro" msgstr "Voir l'intro de la campagne" msgid "hotkey|select campaign difficulty" msgstr "Choisir la difficulté de la campagne" msgid "hotkey|restart campaign scenario" msgstr "Recommencer le scénario de campagne" msgid "hotkey|world map left" msgstr "Déplacement vers la gauche" msgid "hotkey|world map right" msgstr "Déplacement vers la droite" msgid "hotkey|world map up" msgstr "Déplacement vers le haut" msgid "hotkey|world map down" msgstr "Déplacement vers le bas" msgid "hotkey|world map up left" msgstr "Déplacement vers le haut et la gauche" msgid "hotkey|world map up right" msgstr "Déplacement vers le haut et la droite" msgid "hotkey|world map down left" msgstr "Déplacement vers le bas et la gauche" msgid "hotkey|world map down right" msgstr "Déplacement vers le bas et la droite" msgid "hotkey|save game" msgstr "Sauvegarder la partie" msgid "hotkey|next hero" msgstr "Héros suivant" msgid "hotkey|start hero movement" msgstr "Commencer le déplacement du héros" msgid "hotkey|cast adventure spell" msgstr "Lancement de sorts d'aventure." msgid "hotkey|put hero to sleep" msgstr "Mettre le héros en mode \"endormi\"" msgid "hotkey|next town" msgstr "Village suivant" msgid "hotkey|end turn" msgstr "Fin du tour" msgid "hotkey|file options" msgstr "Options fichier" msgid "hotkey|adventure options" msgstr "Options d'aventure" msgid "hotkey|puzzle map" msgstr "Visualiser le puzzle" msgid "hotkey|scenario information" msgstr "Informations du scénario" msgid "hotkey|dig for artifact" msgstr "Creuser pour l'artéfact" msgid "hotkey|view world" msgstr "Voir le monde entier" msgid "hotkey|kingdom summary" msgstr "Informations du royaume" msgid "hotkey|default action" msgstr "Action par défaut" msgid "hotkey|open focus" msgstr "Ouvrir l'objet actuel" msgid "hotkey|system options" msgstr "Options système" msgid "hotkey|scroll left" msgstr "Défilement gauche" msgid "hotkey|scroll right" msgstr "Défilement droite" msgid "hotkey|scroll up" msgstr "Défilement en haut" msgid "hotkey|scroll down" msgstr "Défilement en bas" msgid "hotkey|toggle control panel" msgstr "Afficher/masquer le panneau de commande" msgid "hotkey|toggle radar" msgstr "Afficher/masquer le radar" msgid "hotkey|toggle buttons" msgstr "Afficher/masquer les boutons" msgid "hotkey|toggle status" msgstr "Afficher/masquer la fenêtre d'état" msgid "hotkey|toggle icons" msgstr "Afficher/masquer les icônes" msgid "hotkey|transfer control to ai" msgstr "Transférer le contrôle à l'IA" msgid "hotkey|retreat from battle" msgstr "Retraite" msgid "hotkey|surrender during battle" msgstr "Reddition" msgid "hotkey|toggle battle auto mode" msgstr "Activer/désactiver le mode combat auto" msgid "hotkey|finish the battle in auto mode" msgstr "Finir la bataille en mode auto" msgid "hotkey|battle options" msgstr "Options de bataille" msgid "hotkey|skip turn in battle" msgstr "Passer le tour dans le combat" msgid "hotkey|cast battle spell" msgstr "Lancer sortilège de bataille" msgid "hotkey|dwelling level 1" msgstr "Demeure de niveau 1" msgid "hotkey|dwelling level 2" msgstr "Demeure de niveau 2" msgid "hotkey|dwelling level 3" msgstr "Demeure de niveau 3" msgid "hotkey|dwelling level 4" msgstr "Demeure de niveau 4" msgid "hotkey|dwelling level 5" msgstr "Demeure de niveau 5" msgid "hotkey|dwelling level 6" msgstr "Demeure de niveau 6" msgid "hotkey|well" msgstr "Puits" msgid "hotkey|marketplace" msgstr "Place du marché" msgid "hotkey|mage guild" msgstr "Guilde des magiciens" msgid "hotkey|shipyard" msgstr "Ponton" msgid "hotkey|thieves guild" msgstr "Guilde des voleurs" msgid "hotkey|tavern" msgstr "Taverne" msgid "hotkey|construction screen" msgstr "Écran de construction" msgid "hotkey|buy all monsters in well" msgstr "Enrôler toutes les créatures dans le puits" msgid "hotkey|split stack by half" msgstr "Diviser la troupe en deux" msgid "hotkey|split stack by one" msgstr "Détacher une créature de la troupe" msgid "hotkey|join stacks" msgstr "Fusionner les troupes" msgid "hotkey|upgrade troop" msgstr "Améliorer une troupe" msgid "hotkey|dismiss hero or troop" msgstr "Renvoyer un héros ou une troupe" msgid "" "Do you want to regain control from AI? The effect will take place only on " "the next turn." msgstr "" "Voulez-vous reprendre à l'IA le contrôle du jeu ? L'effet aura lieu " "uniquement au prochain tour." msgid "" "Do you want to transfer control from you to the AI? The effect will take " "place only on the next turn." msgstr "" "Voulez-vous laisser l'IA prendre les commandes ? L'effet aura lieu " "uniquement au prochain tour." msgid "Default Actions" msgstr "Actions par défaut" msgid "Global Actions" msgstr "Actions globales" msgid "Main Menu" msgstr "Menu principal" msgid "World Map" msgstr "Carte du monde" msgid "Battle Screen" msgstr "Écran de bataille" msgid "Town Screen" msgstr "Écran de ville" msgid "Army Actions" msgstr "Actions de l'armée" msgid "Editor" msgstr "Éditeur" msgid "The save file is corrupted." msgstr "Le fichier de sauvegarde est corrompu." msgid "Unsupported save format: " msgstr "Format de sauvegarde non supporté : " msgid "Current game version: " msgstr "Version actuelle du jeu : " msgid "Last supported version: " msgstr "Dernière version supportée : " msgid "This file contains a save with an invalid game type." msgstr "Ce fichier contient une sauvegarde avec un type de jeu invalide." msgid "" "This save file requires \"The Price of Loyalty\" game assets, but they have " "not been provided to the engine." msgstr "" "Cette sauvegarde a besoin des ressources de l'extension \"The Price of " "Loyalty\". Mais ces ressources n'ont pas été fournis au moteur." msgid "This saved game is localized to '" msgstr "Cette partie a été sauvegardée en langue '" msgid "' language, but the current language of the game is '" msgstr "', mais la langue actuelle du jeu est '" msgid "Hot Seat" msgstr "Tour de rôle" msgid "" "Play a Hot Seat game, where 2 to 4 players play around the same computer, " "switching into the 'Hot Seat' when it is their turn." msgstr "" "Jouez à tour de rôle, où 2 à 4 joueurs jouent autour du même ordinateur, se " "passant la souris lorsque c'est leur tour." msgid "A single player game playing out a single map." msgstr "Une partie joueur solo sur une seule carte." msgid "Standard Game" msgstr "Jeu standard" msgid "A single player game playing through a series of maps." msgstr "Une partie joueur solo à travers une série de cartes." msgid "Campaign Game" msgstr "Mode campagne" msgid "Multi-Player Game" msgstr "Jeu multijoueur" msgid "" "A multi-player game, with several human players completing against each " "other on a single map." msgstr "" "Une partie multijoueurs, avec plusieurs joueurs humains en compétition les " "uns contre les autres sur une seule carte." msgid "fheroes2 Resurrection Team presents" msgstr "L'équipe fheroes2 Resurrection présente" msgid "Greetings!" msgstr "Salutations !" msgid "Welcome to Heroes of Might and Magic II powered by fheroes2 engine!" msgstr "" "Bienvenue sur Heroes of Might and Magic II (amélioré par le moteur " "fheroes2) !" msgid "" "Welcome to Heroes of Might and Magic II powered by the fheroes2 engine!\n" "Before starting the game please select a game resolution." msgstr "" "Bienvenue sur Heroes of Might and Magic II (amélioré par le moteur " "fheroes2) !\n" "Avant de commencer le jeu choisissez la résolution du jeu." msgid "Please Remember" msgstr "N'oubliez pas" # Needs extra whitespace to avoid yellow word splitting msgid "" "You can always change the language, resolution and settings of the game by " "clicking on the " msgstr "" "Vous pouvez toujours changer la langue, la résolution et les paramètres en " "cliquant sur la " msgid "door" msgstr "porte" msgid " on the left side of the Main Menu, or with the " msgstr " située à gauche du menu principal, ou avec le bouton " msgid " button from the " msgstr " à partir du menu " msgid "NEW GAME" msgstr "NOUVELLE PARTIE" msgid "" " menu. \n" "\n" "Enjoy the game!" msgstr "" ".\n" "\n" "Bon jeu !" msgid "The Editor requires \"The Price of Loyalty\" expansion files to work." msgstr "" "L'éditeur a besoin que l'extension \"The Price of Loyalty\" soit installée " "pour fonctionner." msgid "Quit Heroes of Might and Magic II and return to the operating system." msgstr "" "Quitter Heroes of Might and Magic II et retourner au système d'exploitation." msgid "Credits" msgstr "Crédits" msgid "View the credits screen." msgstr "Voir l'écran des crédits." msgid "High Scores" msgstr "Meilleurs scores" msgid "View the high scores screen." msgstr "Voir l'écran des meilleurs scores." msgid "Change language, resolution and settings of the game." msgstr "Changer la langue, la résolution et les paramètres du jeu." msgid "Game Settings" msgstr "Paramètres du jeu" msgid "" "Required video files for campaign selection window are missing. Please make " "sure that all necessary files are present in the system." msgstr "" "Les fichiers vidéo requis pour la fenêtre de sélection de la campagne sont " "manquants. Assurez-vous que tous les fichiers nécessaires sont présents dans " "le système." msgid "" "Either Roland's or Archibald's campaign from the original Heroes of Might " "and Magic II." msgstr "" "La campagne de Roland ou d'Archibald du jeu original Heroes of Might and " "Magic II." msgid "Original Campaign" msgstr "Campagne originale" msgid "Expansion Campaign" msgstr "Campagne expansion" msgid "One of the four new campaigns from the Price of Loyalty expansion set." msgstr "" "L'une des quatre nouvelles campagnes de l'extension The Price of Loyalty." msgid "Loading video. Please wait..." msgstr "Chargement de la vidéo. Veuillez patienter..." msgid "Host" msgstr "Hôte" msgid "" "The host sets up the game options. There can only be one host per network " "game." msgstr "" "L'hôte configure les options de jeu. Il ne peut y avoir qu'un seul hôte par " "jeu en réseau." msgid "Guest" msgstr "Invité" msgid "" "The guest waits for the host to set up the game, then is automatically added " "in. There can be multiple guests for TCP/IP games." msgstr "" "L'invité attend que l'hôte configure le jeu, puis est automatiquement " "ajouté. Il peut y avoir plusieurs invités pour les jeux TCP/IP." msgid "Battle Only" msgstr "Champ de bataille" msgid "Setup and play a battle without loading any map." msgstr "Configurez et jouez une bataille sans charger de carte." msgid "2 Players" msgstr "2 Joueurs" msgid "" "Play with 2 human players, and optionally, up to 4 additional computer " "players." msgstr "" "Jouez avec 2 joueurs humains, et éventuellement, jusqu'à 4 joueurs " "ordinateur supplémentaires." msgid "3 Players" msgstr "3 Joueurs" msgid "" "Play with 3 human players, and optionally, up to 3 additional computer " "players." msgstr "" "Jouez avec 3 joueurs humains, et éventuellement, jusqu'à 3 joueurs " "ordinateur supplémentaires." msgid "4 Players" msgstr "4 Joueurs" msgid "" "Play with 4 human players, and optionally, up to 2 additional computer " "players." msgstr "" "Jouez avec 4 joueurs humains, et éventuellement, jusqu'à 2 joueurs " "ordinateur supplémentaires." msgid "5 Players" msgstr "5 Joueurs" msgid "" "Play with 5 human players, and optionally, up to 1 additional computer " "player." msgstr "" "Jouez avec 5 joueurs humains, et éventuellement, jusqu'à 1 joueur ordinateur " "supplémentaire." msgid "6 Players" msgstr "6 Joueurs" msgid "Play with 6 human players." msgstr "Jouer avec 6 joueurs humains." msgid "Dragon city has fallen! You are now the Master of the Dragons." msgstr "" "La cité des dragons est tombée ! Vous êtes maintenant le maître des dragons." msgid "" "You captured %{name}!\n" "You are victorious." msgstr "" "Vous avez capturé %{name} !\n" "Vous êtes victorieux." msgid "" "You have captured the enemy hero %{name}!\n" "Your quest is complete." msgstr "" "Vous avez capturé le héros ennemi %{name} !\n" "Votre quête est terminée." msgid "" "You have found the %{name}.\n" "Your quest is complete." msgstr "" "Vous avez trouvé le %{name}.\n" "Votre quête est terminée." msgid "Ultimate Artifact" msgstr "Ultime artéfact" msgid "" "The enemy is beaten.\n" "Your side has triumphed!" msgstr "" "L'ennemi est battu.\n" "Votre camp a triomphé !" msgid "" "You have built up over %{count} gold in your treasury.\n" "All enemies bow before your wealth and power." msgstr "" "Vous avez accumulé plus de %{count} pièces d'or dans votre trésorerie.\n" "Tous les ennemis s'inclinent devant votre richesse et votre puissance." msgid "Victory!" msgstr "Victoire !" msgid "" "The enemy has captured %{name}!\n" "They are triumphant." msgstr "" "L'ennemi a capturé %{name} !\n" "Ce camp adverse a triomphé." msgid "" "The enemy has built up over %{count} gold in his treasury.\n" "You must bow done in defeat before his wealth and power." msgstr "" "L'ennemi a accumulé plus de %{count} pièces d'or dans sa trésorerie.\n" "Vous devez vous incliner dans la défaite devant sa richesse et sa puissance." msgid "You have been eliminated from the game!!!" msgstr "Vous avez été éliminé du jeu !!!" msgid "" "You have lost the hero %{name}.\n" "Your quest is over." msgstr "" "Vous avez perdu le héros %{name} .\n" "Votre quête est terminée." msgid "" "You have failed to complete your quest in time.\n" "All is lost." msgstr "" "Vous n'avez pas réussi à terminer votre quête à temps.\n" "Tout est perdu." msgid "Defeat!" msgstr "Défaite !" msgid "" "Base score: %{score}\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" "Score de base : %{score}\n" "Difficulté : %{difficulty}\n" "\n" msgid "Defeat all enemy heroes and capture all enemy towns and castles." msgstr "Défaire tous les héros et capturer tous les villages ennemis." msgid "Run out of time. (Fail to win by a certain point.)" msgstr "Manquer de temps. (Ne pas gagner avant un certain point.)" msgid "You must defeat the enemy %{enemies}." msgid_plural "You must defeat the enemy alliance of %{enemies}." msgstr[0] "Vaincre l'ennemi %{enemies}." msgstr[1] "Vaincre l'alliance ennemie de %{enemies}." msgid "" "The alliance consisting of %{allies} and you must defeat the enemy " "%{enemies}." msgid_plural "" "The alliance consisting of %{allies} and you must defeat the enemy alliance " "of %{enemies}." msgstr[0] "" "L'alliance est composée de %{allies} et vous devez vaincre %{enemies}." msgstr[1] "" "L'alliance est composée de %{allies} et vous devez vaincre l'alliance " "ennemie composée de %{enemies}." msgid "Capture the castle '%{name}'." msgstr "Capturer le château '%{name}'." msgid "Capture the town '%{name}'." msgstr "Capturer le village '%{name}'." msgid "Defeat the hero '%{name}'." msgstr "Vaincre le héros '%{name}'." msgid "Find the ultimate artifact." msgstr "Trouver l'ultime artéfact." msgid "Find the '%{name}' artifact." msgstr "Trouver l'artéfact '%{name}'." msgid "Accumulate %{count} gold." msgstr "Amasser %{count} pièces d'or." msgid "" ", or you may win by defeating all enemy heroes and capturing all enemy towns " "and castles." msgstr ", ou éliminer tous les adversaires." msgid "Lose the castle '%{name}'." msgstr "Perdre le château '%{name}'." msgid "Lose the town '%{name}'." msgstr "Perdre le village de '%{name}'." msgid "Lose the hero: %{name}." msgstr "Perdre le héros : %{name}." msgid "Fail to win by the end of month %{month}, week %{week}, day %{day}." msgstr "Limite de temps : mois %{month}, semaine %{week}, jour %{day}." msgid "%{color} player has been vanquished!" msgstr "Le joueur %{color} a été vaincu !" msgid "Major Event!" msgstr "Événement majeur !" msgid "Scenario:" msgstr "Scénario :" msgid "Game Difficulty:" msgstr "Difficulté du jeu :" msgid "Opponents:" msgstr "Adversaires :" msgid "Class:" msgstr "Alignement :" msgid "Rating %{rating}%" msgstr "Taux %{rating}%" msgid "Click here to select which scenario to play." msgstr "Cliquez ici pour sélectionner le scénario à jouer." msgid "Scenario" msgstr "Scénario" msgid "Game Difficulty" msgstr "Difficulté du jeu" msgid "" "This lets you change the starting difficulty at which you will play. Higher " "difficulty levels start you of with fewer resources, and at the higher " "settings, give extra resources to the computer." msgstr "" "Vous permet de modifier la difficulté de départ à laquelle vous allez jouer. " "Les niveaux de difficulté plus élevés vous font commencer avec moins de " "ressources et, aux paramètres les plus élevés, donne des ressources " "supplémentaires à l'ordinateur." msgid "Difficulty Rating" msgstr "Taux de difficulté" msgid "" "The difficulty rating reflects a combination of various settings for your " "game. This number will be applied to your final score." msgstr "" "Le taux de difficulté reflète une combinaison de différents paramètres pour " "votre jeu. Ce taux sera appliqué à votre score final." msgid "Click to accept these settings and start a new game." msgstr "Cliquez pour accepter ces paramètres et démarrer une nouvelle partie." msgid "Click to return to the main menu." msgstr "Cliquez pour revenir au menu principal." msgid "Astrologers proclaim the Month of the %{name}." msgstr "Les astrologues proclament le mois %{name}." msgid "Astrologers proclaim the Week of the %{name}." msgstr "Les astrologues proclament la semaine %{name}." msgid "After regular growth, the population of %{monster} is doubled!" msgstr "" "Après une croissance régulière, la population de %{monster} est doublée !" msgid "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgid_plural "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgstr[0] "" "Après une croissance régulière, la population de %{monster} augmente de " "%{count} pour cent !" msgstr[1] "" "Après une croissance régulière, la population de %{monster} augmente de " "%{count} pour cent !" msgid "%{monster} growth +%{count}." msgstr "La population de %{monster} augmente de +%{count}." msgid " All populations are halved." msgstr " Toutes les populations sont réduites de moitié." msgid " All dwellings increase population." msgstr " La population de toutes les demeures augmente." msgid "New Month!" msgstr "Nouveau mois !" msgid "New Week!" msgstr "Nouvelle semaine !" msgid "Beware!" msgstr "Attention !" msgid "" "%{color} player, this is your last day to capture a town, or you will be " "banished from this land." msgstr "" "%{color}, c'est votre dernier jour pour capturer un village, ou vous serez " "banni de cette terre." msgid "" "%{color} player, you only have %{day} days left to capture a town, or you " "will be banished from this land." msgstr "" "Joueur %{color}, il ne vous reste que %{day} jours pour capturer un village, " "sinon vous serez banni de cette terre." msgid "%{color} player's turn." msgstr "Tour du joueur %{color}." msgid "" "%{color} player, you have lost your last town. If you do not conquer another " "town in the next week, you will be eliminated." msgstr "" "Joueur %{color}, vous venez de perdre votre dernier village. Si vous n'avez " "pas de village dans une semaine, vous serez éliminé." msgid "" "%{color} player, your heroes abandon you, and you are banished from this " "land." msgstr "" "Joueur %{color}, vos héros vous abandonnent et vous êtes banni de cette " "terre." msgid "Lord Kilburn" msgstr "Lord Kilburn" msgid "Tsabu" msgstr "Tsabu" msgid "Sir Galant" msgstr "Sir Galant" msgid "Thundax" msgstr "Thundax" msgid "Lord Haart" msgstr "Lord Haart" msgid "Ariel" msgstr "Ariel" msgid "Rebecca" msgstr "Rebecca" msgid "Sandro" msgstr "Sandro" msgid "Crodo" msgstr "Crodo" msgid "Barock" msgstr "Barock" msgid "Antoine" msgstr "Antoine" msgid "Astra" msgstr "Astra" msgid "Agar" msgstr "Agar" msgid "Vatawna" msgstr "Vatawna" msgid "Vesper" msgstr "Vesper" msgid "Ambrose" msgstr "Ambrose" msgid "Troyan" msgstr "Troyan" msgid "Jojosh" msgstr "Jojosh" msgid "Wrathmont" msgstr "Wrathmont" msgid "Maximus" msgstr "Maximus" msgid "Next Hero" msgstr "Héros suivant" msgid "Select the next Hero." msgstr "Passer au héros suivant." msgid "Hero Movement" msgstr "Déplacement du héros" msgid "" "Start the Hero's movement along the current path or re-visit the object " "occupied by the Hero. Press and hold this button to reset the Hero's path." msgstr "" "Commence le déplacement du héros selon le chemin choisi ou revisite le lieu " "occupé par le héros. Appuyez sur ce bouton et maintenez-le enfoncé pour " "réinitialiser le chemin du héros." msgid "Kingdom Summary" msgstr "Informations sur votre royaume" msgid "View a Summary of your Kingdom." msgstr "Voir un récapitulatif de votre royaume." msgid "Cast an adventure spell." msgstr "Lancer un sort d'aventure." msgid "End Turn" msgstr "Fin du tour" msgid "End your turn and left the computer take its turn." msgstr "Terminer votre tour et laisser l'ordinateur faire son tour." msgid "Adventure Options" msgstr "Options d'aventure" msgid "Bring up the adventure options menu." msgstr "Fait apparaître le menu des options d'aventure." msgid "" "Bring up the file options menu, allowing you to load, save, start a new game " "or quit." msgstr "" "Affiche le menu des options fichier, qui vous permet de charger, " "sauvegarder, recommencer ou quitter." msgid "Bring up the system options menu, allowing you to customize your game." msgstr "" "Affiche le menu des options du système, qui vous permet de personnaliser " "votre jeu." msgid "" "One or more heroes may still move, are you sure you want to end your turn?" msgstr "" "Un ou plusieurs héros peuvent encore se déplacer. Voulez-vous vraiment " "terminer votre tour ?" msgid "Are you sure you want to quit?" msgstr "Voulez-vous vraiment quitter ?" msgid "Are you sure you want to restart? (Your current game will be lost.)" msgstr "" "Voulez-vous vraiment recommencer ? (Votre partie en cours sera perdue.)" msgid "Are you sure you want to overwrite the save with this name?" msgstr "Voulez-vous vraiment remplacer la sauvegarde avec ce nom ?" msgid "Game saved successfully." msgstr "Sauvegarde réussie." msgid "There was an issue during saving." msgstr "Il y a eu un problème lors de la sauvegarde." # Needs line break to have insectile space but keep partie and ? together. msgid "" "Are you sure you want to load a new game? (Your current game will be lost.)" msgstr "" "Voulez-vous vraiment charger une nouvelle\n" "partie ? (Votre partie en cours sera perdue.)" msgid "Try looking on land!!!" msgstr "Essayez de chercher sur la terre ferme !!!" msgid "" "Searching for the Ultimate Artifact is fruitless. Your hero could not carry " "it even if he found it - all his artifact slots are full." msgstr "" "Rechercher l'artéfact ultime est vain. Même s'il le trouvait, votre héros ne " "pourrait pas le porter - tous ses emplacements d'artéfacts sont occupés." msgid "Digging for artifacts requires a whole day, try again tomorrow." msgstr "" "La fouille pour les artéfacts nécessite une journée entière, réessayez " "demain." msgid "" "After spending many hours digging here, you have uncovered the %{artifact}." msgstr "" "Après avoir passé de nombreuses heures à creuser ici, vous avez découvert " "l'artéfact '%{artifact}'." msgid "Congratulations!" msgstr "Félicitations !" msgid "Nothing here. Where could it be?" msgstr "Rien ici. Où cela pourrait-il être ?" msgid "Try searching on clear ground." msgstr "Essayez de chercher sur un terrain dégagé." msgid "A miniature view of the known world. Left click to move viewing area." msgstr "" "Une vue miniature du monde connu. Cliquez avec le bouton gauche de la souris " "pour déplacer la zone d'affichage." msgid "Month: %{month} Week: %{week}" msgstr "Mois : %{month}, Semaine : %{week}" msgid "Day: %{day}" msgstr "Jour : %{day}" msgid "" "You find a small\n" "quantity of %{resource}." msgstr "" "Vous trouvez une petite\n" "quantité de %{resource}." msgid "Status Window" msgstr "Fenêtre d'état" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date." msgstr "" "Cette fenêtre fournit des informations sur le statut de votre héros et de " "votre royaume, et affiche la date." msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date. Left click here to cycle through these windows." msgstr "" "Cette fenêtre fournit des informations sur le statut de votre héros ou " "royaume, et affiche la date. Faites un clic gauche ici pour passer d'une " "fenêtre à l'autre." msgid "" "This lets you change player starting positions and colors. A particular " "color will always start in a particular location. Some positions may only be " "played by a computer player or only by a human player." msgstr "" "Cela vous permet de changer les positions de départ et les couleurs du " "joueur. Une couleur particulière commencera toujours dans un emplacement " "particulier. Certaines positions ne peuvent être jouées que par un joueur " "d'ordinateur ou uniquement par un joueur humain." msgid "" "This lets you change the class of a player. Classes are not always " "changeable. Depending on the scenario, a player may receive additional towns " "and/or heroes not of their primary alignment." msgstr "" "Ceci vous permet de changer l'alignement d'un joueur. Les alignements ne " "sont pas toujours modifiables. Selon le scénario, un joueur peut recevoir " "des villages et/ou des héros supplémentaires qui ne sont pas de son " "alignement principal." msgid "Handicap" msgstr "Pénalité" # msgid "" "This lets you change the handicap of a particular player. Only humans may be " "handicapped. Handicapped players start with fewer resources and earn 15 or " "30% fewer resources per turn for mild and severe handicaps, respectively." msgstr "" "Cela vous permet de modifier la pénalité d'un joueur particulier. Seuls les " "joueurs humains peuvent avoir une pénalité. Les joueurs avec une pénalité " "commencent avec moins de ressources et reçoivent 15 ou 30 % de ressources en " "moins à chaque tour pour la pénalité légère et la pénalité sévère." msgid "%{color} player" msgstr "Joueur %{color}" msgid "No Handicap" msgstr "Pas de pénalité" msgid "No special restrictions on start resources and earning them per turn." msgstr "" "Pas de diminution des ressources de départ ni de celles reçues à chaque tour." msgid "Mild Handicap" msgstr "Pénalité légère" # msgid "" "Mild handicapped players start with fewer resources and earn 15% fewer " "resources per turn." msgstr "" "Les joueurs avec la pénalité légère commenceront avec moins de ressources et " "recevront 15 % de ressources en moins à chaque tour." msgid "Severe Handicap" msgstr "Pénalité sévère" # msgid "" "Severe handicapped players start with fewer resources and earn 30% fewer " "resources per turn." msgstr "" "Les joueurs avec la pénalité sévère commenceront avec moins de ressources et " "recevront 30 % de ressources en moins à chaque tour." msgid "View %{skill} Info" msgstr "Voir les infos de %{skill}" msgid "Keyboard|123" msgstr "123" msgid "Keyboard|SPACE" msgstr "ESPACE" msgid "Keyboard|ABC" msgstr "ABC" msgid "Kingdom Income" msgstr "Revenu du royaume" msgid "Kingdom Income per day." msgstr "Revenu du royaume par jour." msgid "For every lighthouse controlled, your ships will move further each day." msgstr "" "Chaque phare sous votre contrôle donnera un bonus de déplacement journalier " "à tous vos navires." msgid "English" msgstr "Anglais" msgid "French" msgstr "Français" msgid "Polish" msgstr "Polonais" msgid "German" msgstr "Allemand" msgid "Russian" msgstr "Russe" msgid "Italian" msgstr "Italien" msgid "Czech" msgstr "Tchèque" msgid "Norwegian" msgstr "Norvégien" msgid "Belarusian" msgstr "Biélorusse" msgid "Bulgarian" msgstr "Bulgare" msgid "Ukrainian" msgstr "Ukrainien" msgid "Romanian" msgstr "Roumain" msgid "Spanish" msgstr "Espagnol" msgid "Swedish" msgstr "Suédois" msgid "Portuguese" msgstr "Portugais" msgid "Turkish" msgstr "Turc" msgid "Dutch" msgstr "Néerlandais" msgid "Hungarian" msgstr "Hongrois" msgid "Danish" msgstr "Danois" msgid "Slovak" msgstr "Slovaque" msgid "Vietnamese" msgstr "Vietnamien" msgid ", FPS: " msgstr ", FPS : " msgid "%{object} robber" msgstr "%{object} cambriolé" msgid "%{object} raided" msgstr "%{object} pillé" msgid "Ector" msgstr "Ector" msgid "Gwenneth" msgstr "Gwenneth" msgid "Sir Gallant" msgstr "Sir Gallant" msgid "Tyro" msgstr "Tyro" msgid "Dimitry" msgstr "Dimitry" msgid "Ruby" msgstr "Ruby" msgid "Crag Hack" msgstr "Crag Hack" msgid "Fineous" msgstr "Fineous" msgid "Jezebel" msgstr "Jezebel" msgid "Atlas" msgstr "Atlas" msgid "Ergon" msgstr "Ergon" msgid "Jaclyn" msgstr "Jaclyn" msgid "Gem" msgstr "Gem" msgid "Natasha" msgstr "Natasha" msgid "Carlawn" msgstr "Carlawn" msgid "Luna" msgstr "Luna" msgid "Arie" msgstr "Arie" msgid "Barok" msgstr "Barok" msgid "Kastore" msgstr "Kastore" msgid "Falagar" msgstr "Falagar" msgid "Dawn" msgstr "Dawn" msgid "Flint" msgstr "Flint" msgid "Halon" msgstr "Halon" msgid "Myra" msgstr "Myra" msgid "Myrini" msgstr "Myrini" msgid "Wilfrey" msgstr "Wilfrey" msgid "Mandigal" msgstr "Mandigal" msgid "Sarakin" msgstr "Sarakin" msgid "Charity" msgstr "Charity" msgid "Darlana" msgstr "Darlana" msgid "Ranloo" msgstr "Ranloo" msgid "Rialdo" msgstr "Rialdo" msgid "Zam" msgstr "Zam" msgid "Zom" msgstr "Zom" msgid "Celia" msgstr "Celia" msgid "Roxana" msgstr "Roxana" msgid "Lord Corlagon" msgstr "Lord Corlagon" msgid "Lord Halton" msgstr "Lord Halton" msgid "Sister Eliza" msgstr "Soeur Eliza" msgid "Brother Brax" msgstr "Frère Brax" msgid "Dainwin" msgstr "Dainwin" msgid "Joseph" msgstr "Joseph" msgid "Mog" msgstr "Mog" msgid "Solmyr" msgstr "Solmyr" msgid "Ceallach" msgstr "Ceallach" msgid "Drakonia" msgstr "Drakonia" msgid "Elderian" msgstr "Elderian" msgid "Gallavant" msgstr "Gallavant" msgid "Jarkonas" msgstr "Jarkonas" msgid "Martine" msgstr "Martine" msgid " gives you maximum morale" msgstr " augmente votre moral à son maximum" msgid " gives you maximum luck" msgstr " augmente votre chance à son maximum" msgid "You cannot pick up this artifact, you already have a full load!" msgstr "Vous ne pouvez pas ramasser cet artéfact, vous êtes déjà surchargé !" msgid "To cast spells, you must first buy a spell book for %{gold} gold." msgstr "" "Pour lancer des sorts, vous devez d'abord acheter un grimoire pour %{gold} " "pièces d'or." msgid "Unfortunately, you seem to be a little short of cash at the moment." msgstr "" "Malheureusement, vous semblez être un peu à court d'argent pour le moment." msgid "Do you wish to buy one?" msgstr "Voulez-vous en acheter un ?" msgid "%{count} / day" msgstr "%{count} / jour" msgid "one" msgstr "1" msgid "two" msgstr "2" msgid "A whirlpool engulfs your ship. Some of your army has fallen overboard." msgstr "" "Un tourbillon engloutit votre vaisseau. Une partie de votre armée est tombée " "par-dessus du bord." msgid "Insulted by your refusal of their offer, the monsters attack!" msgstr "Insultés par votre refus de leur offre, les monstres attaquent !" msgid "" "The %{monster}, awed by the power of your forces, begin to scatter.\n" "Do you wish to pursue and engage them?" msgstr "" "Intimidés par votre puissance, les %{monster} commencent à se disperser.\n" "Voulez-vous les rattraper et les affronter ?" msgid "Ransacking an enemy camp, you discover a hidden cache of treasures." msgstr "En fouillant le camp, vous découvrez des trésors cachés." msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with these resources, " "come back next week for more.\"" msgstr "" "Le gardien du moulin annonce :\n" "\"Milord, j'ai travaillé très dur pour vous fournir ces ressources, revenez " "la semaine prochaine pour en avoir plus.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there are no resources currently available. Please try " "again next week.\"" msgstr "" "Le gardien du moulin annonce :\n" "\"Milord, je suis désolé, il n'y a pas de ressources disponibles " "actuellement. Veuillez réessayer la semaine prochaine.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with this gold, come " "back next week for more.\"" msgstr "" "Le gardien du moulin annonce :\n" "\"Milord, j'ai travaillé très dur pour vous fournir cet or, revenez la " "semaine prochaine pour en avoir plus.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there is no gold currently available. Please try again " "next week.\"" msgstr "" "Le gardien du moulin annonce :\n" "\"Milord, je suis désolé, il n'y a pas d'or disponible actuellement. " "Veuillez réessayer la semaine prochaine.\"" msgid "" "You've found an abandoned lean-to.\n" "Poking about, you discover some resources hidden nearby." msgstr "" "Vous avez trouvé un appentis abandonné.\n" "En fouillant, vous découvrez des ressources cachées à proximité." msgid "The lean-to is long abandoned. There is nothing of value here." msgstr "" "L'appentis est abandonné depuis longtemps. Il n'y a rien de valeur ici." msgid "" "You catch a leprechaun foolishly sleeping amidst a cluster of magic " "mushrooms.\n" "In exchange for his freedom, he guides you to a small pot filled with " "precious things." msgstr "" "Vous attrapez un lutin qui dort bêtement au milieu d'un amas de champignons " "magiques.\n" "En échange de sa liberté, il vous guide vers un petit pot rempli de choses " "précieuses." msgid "" "You've found a magic garden, the kind of place that leprechauns and faeries " "like to cavort in, but there is no one here today.\n" "Perhaps you should try again next week." msgstr "" "Vous avez trouvé un jardin magique, le genre d'endroit où les lutins et les " "fées aiment s'ébattre, mais il n'y a personne aujourd'hui.\n" "Vous devriez peut-être réessayer la semaine prochaine." msgid "You come upon the remains of an unfortunate adventurer." msgstr "Vous tombez sur les restes d'un malheureux aventurier." msgid "Treasure" msgstr "Trésor" msgid "Searching through the tattered clothing, you find the %{artifact}." msgstr "" "En cherchant dans les vêtements en lambeaux, vous trouvez l'artéfact " "'%{artifact}'." msgid "Searching through the tattered clothing, you find nothing." msgstr "En cherchant dans les vêtements en lambeaux, vous ne trouvez rien." msgid "" "You come across an old wagon left by a trader who didn't quite make it to " "safe terrain." msgstr "" "Vous tombez sur un vieux chariot laissé par un commerçant qui n'a pas réussi " "à atteindre un terrain sûr." msgid "Unfortunately, others have found it first, and the wagon is empty." msgstr "" "Malheureusement, d'autres l'ont trouvé en premier, et le wagon est vide." msgid "Searching inside, you find the %{artifact}." msgstr "En cherchant à l'intérieur, vous trouvez l'artéfact '%{artifact}'." msgid "Inside, you find some of the wagon's cargo still intact." msgstr "" "A l'intérieur, vous trouvez une partie de la cargaison du wagon encore " "intacte." msgid "You search through the flotsam, and find some wood and some gold." msgstr "Vous cherchez dans les débris, et vous trouvez du bois et de l'or." msgid "You search through the flotsam, and find some wood." msgstr "Vous cherchez dans les débris et trouvez du bois." msgid "You search through the flotsam, but find nothing." msgstr "Vous cherchez parmi les débris, mais ne trouvez rien." msgid "Shrine of the 1st Circle" msgstr "Sanctuaire du premier cercle" msgid "" "You come across a small shrine attended by a group of novice acolytes.\n" "In exchange for your protection, they agree to teach you a simple spell - " "'%{spell}'." msgstr "" "Vous tombez sur un petit sanctuaire fréquenté par un groupe d'acolytes " "novices.\n" "En échange de votre protection, ils acceptent de vous enseigner un sort " "simple - '%{spell}'." msgid "Shrine of the 2nd Circle" msgstr "Sanctuaire du second cercle" msgid "" "You come across an ornate shrine attended by a group of rotund friars.\n" "In exchange for your protection, they agree to teach you a spell - " "'%{spell}'." msgstr "" "Vous tombez sur un sanctuaire orné, fréquenté par un groupe de frères " "corpulents.\n" "En échange de votre protection, ils acceptent de vous enseigner un sort - " "'%{spell}'." msgid "Shrine of the 3rd Circle" msgstr "Sanctuaire du troisième cercle" msgid "" "You come across a lavish shrine attended by a group of high priests.\n" "In exchange for your protection, they agree to teach you a sophisticated " "spell - '%{spell}'." msgstr "" "Vous tombez sur un sanctuaire somptueux fréquenté par un groupe de grands " "prêtres.\n" "En échange de votre protection, ils acceptent de vous enseigner un sort " "sophistiqué : '%{spell}'." msgid "" "\n" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "\n" "Malheureusement, vous n'avez pas la sagesse nécessaire pour comprendre ce " "sort et vous ne pouvez pas l'apprendre." msgid "" "\n" "Unfortunately, you already have knowledge of this spell, so there is nothing " "more for them to teach you." msgstr "" "\n" "Malheureusement, vous connaissez déjà ce sort, il n'y a donc rien de plus à " "vous apprendre." msgid "" "\n" "Unfortunately, you have no Magic Book to record the spell with." msgstr "" "\n" "Malheureusement, vous n'avez pas de grimoire pour enregistrer le sort." msgid "" "You approach the hut and observe a witch inside studying an ancient tome on " "%{skill}.\n" "\n" msgstr "" "Vous vous approchez de la hutte et observez une sorcière à l'intérieur qui " "étudie un tome ancien sur %{skill}.\n" msgid "" "As you approach, she turns and focuses her one glass eye on you.\n" "\"You already know everything you deserve to learn!\" the witch screeches. " "\"NOW GET OUT OF MY HOUSE!\"" msgstr "" "Quand vous vous approchez, elle se retourne et concentre son unique oeil de " "verre sur vous.\n" "\"Tu sais déjà tout ce que tu mérites d'apprendre !\" hurle la sorcière. " "\"MAINTENANT, SORS DE CHEZ MOI !\"" msgid "" "As you approach, she turns and speaks.\n" "\"You already know that which I would teach you. I can help you no further.\"" msgstr "" "Lorsque vous vous approchez, elle se tourne et parle.\n" "\"Tu sais déjà ce que je voudrais t'enseigner. Je ne peux pas t'aider " "davantage.\"" msgid "" "An ancient and immortal witch living in a hut with bird's legs for stilts " "teaches you %{skill} for her own inscrutable purposes." msgstr "" "Une sorcière ancienne et immortelle vivant dans une hutte avec des pattes " "d'oiseau en guise d'échasses vous enseigne %{skill} pour ses propres " "objectifs impénétrables." msgid "As you drink the sweet water, you gain luck for your next battle." msgstr "" "En buvant l'eau douce, vous gagnez de la chance pour votre prochaine " "bataille." msgid "You drink from the enchanted fountain, but nothing happens." msgstr "Vous buvez à la fontaine enchantée, mais rien ne se passe." msgid "You enter the faerie ring, but nothing happens." msgstr "Vous entrez dans l'anneau des fées, mais rien ne se passe." msgid "" "Upon entering the mystical faerie ring, your army gains luck for its next " "battle." msgstr "" "En entrant dans l'anneau mystique des fées, votre armée gagne de la chance " "pour sa prochaine bataille." msgid "" "You've found an ancient and weathered stone idol.\n" "It is supposed to grant luck to visitors, but since the stars are already " "smiling upon you, it does nothing." msgstr "" "Vous avez trouvé une ancienne idole de pierre usée par le temps.\n" "Elle est censée porter chance aux visiteurs, mais comme les étoiles vous " "sourient déjà, elle ne fait rien." msgid "" "You've found an ancient and weathered stone idol.\n" "Kissing it is supposed to be lucky, so you do. The stone is very cold to the " "touch." msgstr "" "Vous avez trouvé une idole en pierre ancienne et usée par le temps.\n" "L'embrasser est censé porter chance, alors vous le faites. La pierre est " "très froide au toucher." msgid "The mermaids silently entice you to return later and be blessed again." msgstr "" "Sans aucune parole, les sirènes vous intiment de revenir plus tard pour une " "nouvelle bénédiction." msgid "" "The magical, soothing beauty of the Mermaids reaches you and your crew.\n" "Just for a moment, you forget your worries and bask in the beauty of the " "moment.\n" "The mermaids charms bless you with increased luck for your next combat." msgstr "" "La beauté magique et apaisante des sirènes vous atteint, vous et votre " "équipage.\n" "Pendant un court instant, vous oubliez vos soucis et savourez la beauté du " "moment.\n" "Les charmes des sirènes vous bénissent avec une chance accrue pour votre " "prochain combat." msgid "" "You come upon the pyramid of a great and ancient king.\n" "You are tempted to search it for treasure, but all the old stories warn of " "fearful curses and undead guardians.\n" "Will you search?" msgstr "" "Vous tombez sur la pyramide d'un grand et ancien roi.\n" "Vous êtes tenté d'entrer à la recherche d'un trésor, mais toutes les " "vieilles histoires mettent en garde contre les malédictions effrayantes et " "les gardiens morts-vivants.\n" "Allez-vous entrer ?" msgid "" "Upon defeating the monsters, you decipher an ancient glyph on the wall, " "telling the secret of the spell - '" msgstr "" "Après avoir vaincu les monstres, vous déchiffrez un ancien glyphe sur le " "mur, qui révèle le secret du sort - '" msgid "Unfortunately, you have no Magic Book to record the spell with." msgstr "Malheureusement, vous n'avez pas de grimoire pour enregistrer le sort." msgid "" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "Malheureusement, vous n'avez pas la sagesse nécessaire pour comprendre ce " "sort et vous ne pouvez pas l'apprendre." msgid "" "You come upon the pyramid of a great and ancient king.\n" "Routine exploration reveals that the pyramid is completely empty." msgstr "" "Vous tombez sur la pyramide d'un grand et ancien roi.\n" "Une exploration de routine révèle que la pyramide est complètement vide." msgid "" "A drink at the well is supposed to restore your spell points, but you are " "already at maximum." msgstr "" "Boire au puits est censé restaurer tous vos points de magie, mais vous êtes " "déjà au maximum." msgid "A second drink at the well in one day will not help you." msgstr "Un deuxième passage au puits en une journée ne vous aidera pas." msgid "A drink from the well has restored your spell points to maximum." msgstr "Une gorgée d'eau du puits a restauré vos points de magie au maximum." msgid "" "\"I'm sorry sir,\" The leader of the soldiers says, \"but you already know " "everything we have to teach.\"" msgstr "" "\"Je suis désolé, monsieur\", dit le chef des soldats, \"mais vous savez " "déjà tout ce que nous avons à enseigner.\"" msgid "The soldiers living in the fort teach you a few new defensive tricks." msgstr "" "Les soldats vivant dans le fort vous enseignent quelques nouvelles astuces " "défensives." msgid "" "You've come upon a mercenary camp practicing their tactics. \"You're too " "advanced for us,\" the mercenary captain says. \"We can teach nothing more.\"" msgstr "" "Vous êtes tombé sur un camp de mercenaires qui pratiquent leurs tactiques. " "\"Vous êtes trop avancé pour nous\", dit le capitaine mercenaire. \"Nous ne " "pouvons rien enseigner de plus.\"" msgid "" "You've come upon a mercenary camp practicing their tactics. The mercenaries " "welcome you and your troops and invite you to train with them." msgstr "" "Vous êtes tombé sur un camp de mercenaires en plein entraînement tactique. " "Les mercenaires vous accueillent et vous invitent à vous entraîner avec eux." msgid "\"Go 'way!\", the witch doctor barks, \"you know all I know.\"" msgstr "\"Pars !\", aboie le sorcier, \"tu sais tout ce que je sais.\"" msgid "" "An Orcish witch doctor living in the hut deepens your knowledge of magic by " "showing you how to cast stones, read portents, and decipher the intricacies " "of chicken entrails." msgstr "" "Un sorcier orc vivant dans la cabane approfondit votre connaissance de la " "magie en vous montrant comment jeter des pierres, lire des présages, et " "déchiffrer les subtilités des entrailles de poulet." msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, the Druids turn you away, indicating they have nothing " "new to teach you." msgstr "" "Vous avez trouvé un groupe de druides en train de prier dans l'un de leurs " "étranges édifices de pierre. En silence, les druides vous repoussent, " "indiquant qu'ils n'ont rien de nouveau à vous apprendre." msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, they teach you new ways to cast spells." msgstr "" "Vous avez trouvé un groupe de druides en train de prier dans l'un de leurs " "étranges édifices de pierre. En silence, ils vous enseignent de nouvelles " "façons de lancer des sorts." msgid "" "You tentatively approach the burial ground of ancient warriors. Do you want " "to search the graves?" msgstr "" "Vous vous approchez disctrètement du cimetière d'anciens guerriers. Voulez-" "vous fouiller les tombes ?" msgid "" "You spend several hours searching the graves and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" "Après avoir vaincu les zombies, vous passez plusieurs heures à fouiller les " "tombes sans rien trouver. Un tel acte méprisable réduit le moral de votre " "armée." msgid "Upon defeating the Zombies you search the graves and find something!" msgstr "" "Après avoir vaincu les zombies, vous fouillez les tombes et trouvez quelque " "chose !" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the shipwreck?" msgstr "" "La coque en décomposition d'un grand bateau pirate grince sinistrement " "lorsqu'elle est poussée contre les rochers. Voulez-vous fouiller l'épave ?" msgid "" "You spend several hours sifting through the debris and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" "Vous passez plusieurs heures à fouiller les débris et ne trouvez rien. Un " "tel acte méprisable réduit le moral de votre armée." msgid "" "Upon defeating the Ghosts you sift through the debris and find something!" msgstr "" "Après avoir vaincu les fantômes, vous passez en revue les débris et trouvez " "quelque chose !" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the ship?" msgstr "" "La carcasse pourrie d'un grand bateau pirate grince sinistrement lorsqu'elle " "est poussée contre les rochers. Voulez-vous fouiller le navire ?" msgid "" "Upon defeating the Skeletons you sift through the debris and find something!" msgstr "" "Après avoir vaincu les squelettes, vous passez en revue les débris et " "trouvez quelque chose !" msgid "Your men spot a navigational buoy, confirming that you are on course." msgstr "" "Vos hommes repèrent une bouée de navigation, confirmant que vous êtes sur la " "bonne voie." msgid "" "Your men spot a navigational buoy, confirming that you are on course and " "increasing their morale." msgstr "" "Vos hommes repèrent une bouée de navigation, confirmant que vous êtes sur la " "bonne voie et augmentant leur moral." msgid "" "The drink at the oasis is refreshing, but offers no further benefit. The " "oasis might help again if you fought a battle first." msgstr "" "L'eau de l'oasis est rafraîchissante, mais n'offre aucun autre avantage. " "L'oasis pourrait vous aider à nouveau si vous livriez d'abord une bataille." msgid "" "A drink at the oasis fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" "Une gorgée d'eau de l'oasis remplit vos troupes de force et leur remonte le " "moral. Vous pouvez voyager un peu plus loin aujourd'hui." msgid "" "The drink at the watering hole is refreshing, but offers no further benefit. " "The watering hole might help again if you fought a battle first." msgstr "" "L'eau de l'abreuvoir est rafraîchissante, mais n'offre aucun autre avantage. " "L'abreuvoir pourrait vous aider à nouveau si vous vous battiez d'abord." msgid "" "A drink at the watering hole fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" "Boire à l'abreuvoir donne de la force à vos troupes et leur remonte le " "moral. Vous pouvez voyager un peu plus loin aujourd'hui." msgid "" "It doesn't help to pray twice before a battle. Come back after you've fought." msgstr "" "Ça ne sert à rien de prier deux fois avant une bataille. Revenez après avoir " "combattu." msgid "A visit and a prayer at the temple raises the morale of your troops." msgstr "Une visite et une prière au temple remontent le moral de vos troupes." msgid "" "An old Knight appears on the steps of the gazebo. \"I am sorry, my liege, I " "have taught you all I can.\"" msgstr "" "Un vieux chevalier apparaît sur les marches du belvédère. \"Je suis désolé, " "mon seigneur, je vous ai appris tout ce que je pouvais.\"" msgid "" "An old Knight appears on the steps of the gazebo. \"My liege, I will teach " "you all that I know to aid you in your travels.\"" msgstr "" "Un vieux chevalier apparaît sur les marches du belvédère. \"Mon seigneur, je " "vais vous apprendre tout ce que je sais pour vous aider dans vos voyages.\"" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he says, \"I would give you an artifact as a reward, but " "you're all full.\"" msgstr "" "Vous avez tiré un survivant du naufrage d'une mort certaine dans un océan " "impitoyable. Reconnaissant, dit-il, \"Je vous donnerais un artéfact en " "récompense, mais vous êtes tout plein.\"" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he rewards you for your act of kindness by giving you the " "%{art}." msgstr "" "Vous avez tiré un survivant du naufrage d'une mort certaine dans un océan " "impitoyable. Reconnaissant, il vous récompense pour votre acte de " "gentillesse en vous donnant l'artéfact '%{art}'." msgid "A leprechaun offers you the %{art} for the small price of %{gold} Gold." msgstr "" "Un lutin vous offre l'artéfact '%{art}' pour le petit prix de %{gold} pièces " "d'or." msgid "" "A leprechaun offers you the %{art} for the small price of %{gold} Gold and " "%{count} %{res}." msgstr "" "Un lutin vous offre l'artéfact '%{art}' pour le petit prix de %{gold} pièces " "d'or et %{count} de %{res}." msgid "Do you wish to buy this artifact?" msgstr "Souhaitez-vous acheter cet artéfact ?" msgid "" "You try to pay the leprechaun, but realize that you can't afford it. The " "leprechaun stamps his foot and ignores you." msgstr "" "Vous essayez de payer le lutin, mais réalisez que vous ne pouvez pas vous le " "permettre. Le lutin frappe le sol avec son pied et vous ignore." msgid "" "Insulted by your refusal of his generous offer, the leprechaun stamps his " "foot and ignores you." msgstr "" "Insulté par votre refus de son offre généreuse, le lutin tape du pied sur le " "sol et vous ignore." msgid "You've found the artifact: " msgstr "Vous avez trouvé un artéfact : " msgid "" "You've found the humble dwelling of a withered hermit. The hermit tells you " "that he is willing to give the %{art} to the first wise person he meets." msgstr "" "Vous avez trouvé l'humble demeure d'un ermite desséché. L'ermite vous dit " "qu'il est prêt à donner l'artéfact '%{art}' à la première personne sage " "qu'il rencontre." msgid "" "You've come across the spartan quarters of a retired soldier. The soldier " "tells you that he is willing to pass on the %{art} to the first true leader " "he meets." msgstr "" "Vous avez rencontré les quartiers spartiates d'un soldat à la retraite. Le " "soldat vous dit qu'il est prêt à transmettre l'artéfact '%{art}' au premier " "vrai chef qu'il rencontre." msgid "" "You've encountered a strange person with a hat and an owl on it. He tells " "you that he is willing to give %{art} if you have %{skill}." msgstr "" "Vous avez rencontré une personne étrange avec un chapeau et un hibou dessus. " "Il vous dit qu'il est prêt à vous donner %{art} si vous avez %{skill}." msgid "" "You come upon an ancient artifact. As you reach for it, a pack of Rogues " "leap out of the brush to guard their stolen loot." msgstr "" "Vous tombez sur un ancien artéfact. Alors que vous l'atteignez, une bande de " "pillards bondit des broussailles pour garder le butin volé." msgid "" "Through a clearing you observe an ancient artifact. Unfortunately, it's " "guarded by a nearby %{monster}. Do you want to fight the %{monster} for the " "artifact?" msgstr "" "À travers une clairière, vous observez un ancien artéfact. Malheureusement, " "il est gardé par un %{monster} à proximité. Voulez-vous combattre le " "%{monster} pour l'artéfact ?" msgid "Victorious, you take your prize, the %{art}." msgstr "Victorieux, vous prenez votre prix, l'artéfact '%{art}'." msgid "" "Discretion is the better part of valor, and you decide to avoid this fight " "for today." msgstr "" "Le discernement est le meilleur aspect de la vaillance, et vous décidez " "d'éviter ce combat pour aujourd'hui." msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold pieces." msgstr "" "Après avoir passé des heures à essayer de repêcher le coffre de la mer, vous " "l'ouvrez et trouvez %{gold} pièces d'or." msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold and the %{art}." msgstr "" "Après avoir passé des heures à essayer de repêcher le coffre de la mer, vous " "l'ouvrez et trouvez %{gold} pièces d'or et l'artéfact '%{art}'." msgid "" "After spending hours trying to fish the chest out of the sea, you open it, " "only to find it empty." msgstr "" "Après avoir passé des heures à essayer de repêcher le coffre de la mer, vous " "l'ouvrez, et découvrez qu'il est vide." msgid "" "After scouring the area, you fall upon a hidden treasure cache. You may take " "the gold or distribute the gold to the peasants for experience. Do you wish " "to keep the gold?" msgstr "" "Après avoir parcouru la zone, vous tombez sur une cache au trésor. Vous " "pouvez prendre l'or ou distribuer l'or aux paysans pour l'expérience. " "Souhaitez-vous garder l'or ?" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "%{gold} gold pieces." msgstr "" "Après avoir parcouru la zone, vous tombez sur un coffre caché, contenant " "%{gold} pièces d'or." msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "ancient artifact %{art}." msgstr "" "Après avoir parcouru la zone, vous tombez sur un coffre caché, contenant " "l'ancien artéfact '%{art}'." msgid "" "You stumble upon a dented and tarnished lamp lodged deep in the earth. Do " "you wish to rub the lamp?" msgstr "" "Vous tombez sur une lampe cabossée et ternie, enfouie dans la terre. Voulez-" "vous frotter la lampe ?" msgid "" "You have taken control of the local Alchemist shop. It will provide you with " "%{count} unit of Mercury per day." msgstr "" "Vous avez pris le contrôle du laboratoire de l'alchimiste local. Il vous " "fournira %{count} fiole de mercure par jour." msgid "" "You gain control of a sawmill. It will provide you with %{count} units of " "wood per day." msgstr "" "Vous prenez le contrôle d'une scierie. Elle vous fournira %{count} bûches de " "bois par jour." msgid "" "You gain control of an ore mine. It will provide you with %{count} units of " "ore per day." msgstr "" "Vous prenez le contrôle d'une mine de fer. Elle vous fournira %{count} " "morceaux de fer par jour." msgid "" "You gain control of a sulfur mine. It will provide you with %{count} unit of " "sulfur per day." msgstr "" "Vous prenez le contrôle d'une mine de soufre. Elle vous fournira %{count} " "morceau de soufre par jour." msgid "" "You gain control of a crystal mine. It will provide you with %{count} unit " "of crystal per day." msgstr "" "Vous prenez le contrôle d'une mine de cristal. Elle vous fournira %{count} " "cristal par jour." msgid "" "You gain control of a gem mine. It will provide you with %{count} unit of " "gems per day." msgstr "" "Vous prenez le contrôle d'une mine de gemmes. Elle vous fournira %{count} " "gemmes par jour." msgid "" "You gain control of a gold mine. It will provide you with %{count} gold per " "day." msgstr "" "Vous prenez le contrôle d'une mine d'or. Elle vous fournira %{count} pièces " "d'or par jour." msgid "" "The lighthouse is now under your control, and all of your ships will now " "move further each day." msgstr "" "Le phare est à présent sous votre contrôle, et tous vos navires se " "déplaceront maintenant plus loin chaque jour." msgid "You gain control of a %{name}." msgstr "Vous avez capturé %{name}." msgid "" "You come upon an abandoned gold mine. The mine appears to be haunted. Do you " "wish to enter?" msgstr "" "Vous tombez sur une mine d'or abandonnée. La mine semble hantée. Souhaitez-" "vous entrer ?" msgid "You beat the Ghosts and are able to restore the mine to production." msgstr "" "Vous battez les fantômes et êtes capable de remettre la mine en production." msgid "" "A group of %{monster} with a desire for greater glory wish to join you. Do " "you accept?" msgstr "" "Un groupe de %{monster} désirant de plus grandes gloires souhaite vous " "rallier. Acceptez-vous ?" msgid "As you approach the dwelling, you notice that there is no one here." msgstr "" "En vous approchant du logement, vous remarquez qu'il n'y a personne ici." msgid "" "You search the ruins, but the Medusas that used to live here are gone. " "Perhaps there will be more next week." msgstr "" "Vous fouillez les ruines, mais les méduses qui vivaient ici ont disparu. " "Peut-être y en aura-t-il d'autres la semaine prochaine." msgid "" "You've found some Medusas living in the ruins. They are willing to join your " "army for a price. Do you want to recruit Medusas?" msgstr "" "Vous avez trouvé des méduses vivant dans les ruines. Elles sont prêtes à " "rallier votre armée pour un prix. Voulez-vous recruter des méduses ?" msgid "" "You've found a Sprite Tree City. Unfortunately, none of the Sprites living " "there wish to join an army. Maybe next week." msgstr "" "Vous avez trouvé une cité des arbres de fées. Malheureusement, aucune des " "fées qui y vivent ne souhaite rallier une armée. Peut-être la semaine " "prochaine." msgid "" "Some of the Sprites living in the tree city are willing to join your army " "for a price. Do you want to recruit Sprites?" msgstr "" "Certaines des fées vivant dans la cité des arbres sont prêtes à rallier " "votre armée pour un prix. Voulez-vous recruter des fées ?" msgid "" "A colorful Rogues' wagon stands empty here. Perhaps more Rogues will be here " "later." msgstr "" "Un chariot coloré appartenant aux pillards se tient là, vide. Peut-être que " "d'autres pillards seront là plus tard." msgid "" "Distant sounds of music and laughter draw you to a colorful wagon housing " "Rogues. Do you wish to have any Rogues join your army?" msgstr "" "Des sons lointains de musique et de rires vous attirent vers un wagon coloré " "abritant des pillards. Souhaitez-vous que des pillards rallient votre armée ?" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. The " "tents are unoccupied. Perhaps more Nomads will be here later." msgstr "" "Un groupe de tentes en lambeaux, gonflées par le vent de sable, vous attire." "Les tentes sont inoccupées. Peut-être que d'autres nomades seront là plus " "tard." msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. Do you " "wish to have any Nomads join you during your travels?" msgstr "" "Un groupe de tentes en lambeaux, flottant dans le vent ensablé, vous " "appelle. Souhaitez-vous que des nomades vous rallient durant votre aventure ?" msgid "The pit of mud bubbles for a minute and then lies still." msgstr "La fosse de boue bouillonne pendant une minute, puis reste immobile." msgid "" "As you approach the bubbling pit of mud, creatures begin to climb out and " "position themselves around it. In unison they say: \"Mother Earth would like " "to offer you a few of her troops. Do you want to recruit Earth Elementals?\"" msgstr "" "Lorsque vous vous approchez de la fosse de boue bouillonnante, des créatures " "commencent à en sortir et à se positionner autour d'elle. À l'unisson, elles " "disent : \"Mère terre aimerait vous offrir quelques-unes de ses troupes. " "Voulez-vous recruter des élémentaires de la terre ?\"" msgid "You enter the structure of white stone pillars, and find nothing." msgstr "" "Vous entrez dans la structure de piliers de pierre blanche, et ne trouvez " "rien." msgid "" "White stone pillars support a roof that rises up to the sky. As you enter " "the structure, the dead air of the outside gives way to a whirling gust that " "almost pushes you back out. The air current materializes into a barely " "visible form. The creature asks, in what can only be described as a loud " "whisper: \"Why have you come? Are you here to call upon the forces of the " "air?\"" msgstr "" "Des piliers de pierre blanche soutiennent un toit qui s'élève vers le ciel. " "En entrant dans la structure, l'air mort de l'extérieur fait place à une " "bourrasque tourbillonnante qui vous repousse presque à l'extérieur. Le " "courant d'air se matérialise en une forme à peine visible. La créature " "demande, dans ce qui ne peut être décrit que comme un fort chuchotement : " "\"Pourquoi es-tu venu ? Es-tu ici pour faire appel aux forces de l'air ?\"" msgid "No Fire Elementals approach you from the lava pool." msgstr "" "Aucun élémentaire de feu ne s'approche de vous depuis le bassin de lave." msgid "" "Beneath a structure that serves to hold in heat, Fire Elementals move about " "in a fiery pool of molten lava. A group of them approach you and offer their " "services. Would you like to recruit Fire Elementals?" msgstr "" "Dans une mare ardente de lave en fusion, des élémentaires de feu se " "déplacent. Certains d'entre eux s'approchent de vous et vous proposent leurs " "services. Voulez-vous recruter des élémentaires de feu ?" msgid "A face forms in the water for a moment, and then is gone." msgstr "Un visage se forme dans l'eau pendant un instant, puis disparaît." msgid "" "Crystalline structures cast shadows over a small reflective pool of water. " "You peer into the pool, and a face that is not your own peers back. It asks: " "\"Would you like to call upon the powers of water?\"" msgstr "" "Des structures cristallines projettent des ombres sur un petit bassin d'eau " "réfléchissant. Vous regardez dans la piscine, et un visage qui n'est pas le " "vôtre vous regarde en retour. Il demande : \"Voulez-vous faire appel aux " "pouvoirs de l'eau ?\"" msgid "This burial site is deathly still." msgstr "Ce site funéraire est mortellement calme." msgid "" "Restless spirits of long dead warriors seeking their final resting place " "offer to join you in hopes of finding peace. Do you wish to recruit ghosts?" msgstr "" "Les esprits agités de guerriers morts depuis longtemps et cherchant leur " "dernière demeure vous proposent de vous rallier dans l'espoir de trouver la " "paix. Voulez-vous recruter des fantômes ?" msgid "" "The City of the Dead is empty of life, and empty of unlife as well. Perhaps " "some undead will move in next week." msgstr "" "La Cité des morts est vide de vie, et vide de non-vie aussi. Peut-être que " "des morts-vivants vont emménager la semaine prochaine." msgid "" "Some Liches living here are willing to join your army for a price. Do you " "want to recruit Liches?" msgstr "" "Certains macchabées vivant ici sont prêts à rallier votre armée pour un " "prix. Voulez-vous recruter des macchabées ?" msgid "" "You've found the ruins of an ancient city, now inhabited solely by the " "undead. Will you search?" msgstr "" "Vous avez trouvé les ruines d'une ancienne cité, désormais habitée " "uniquement par des morts-vivants. Allez-vous la fouiller ?" msgid "" "Some of the surviving Liches are impressed by your victory over their " "fellows, and offer to join you for a price. Do you want to recruit Liches?" msgstr "" "Certains des macchabées survivants sont impressionnés par votre victoire sur " "leurs congénères et proposent de vous rallier moyennant un certain prix. " "Voulez-vous recruter des macchabées ?" msgid "" "You've found one of those bridges that Trolls are so fond of living under, " "but there are none here. Perhaps there will be some next week." msgstr "" "Vous avez trouvé un de ces ponts sous lesquels les trolls aiment tant vivre, " "mais il n'y en a pas ici. Peut-être qu'il y en aura la semaine prochaine." msgid "" "Some Trolls living under a bridge are willing to join your army, but for a " "price. Do you want to recruit Trolls?" msgstr "" "Des trolls vivant sous un pont sont prêts à rallier votre armée, mais à un " "prix. Voulez-vous recruter des trolls ?" msgid "Trolls living under the bridge challenge you. Will you fight them?" msgstr "" "Les trolls qui vivent sous le pont vous défient. Allez-vous les combattre ?" msgid "" "A few Trolls remain, cowering under the bridge. They approach you and offer " "to join your forces as mercenaries. Do you want to buy any Trolls?" msgstr "" "Il reste quelques trolls, qui se cachent sous le pont. Ils s'approchent de " "vous et proposent de rallier vos forces en tant que mercenaires. Voulez-vous " "acheter des trolls ?" msgid "" "The Dragon city has no Dragons willing to join you this week. Perhaps a " "Dragon will become available next week." msgstr "" "La cité des dragons n'a pas de dragons prêts à vous rallier cette semaine. " "Peut-être un dragon sera-t-il disponible la semaine prochaine." msgid "" "The Dragon city is willing to offer some Dragons for your army for a price. " "Do you wish to recruit Dragons?" msgstr "" "La cité des dragons est prête à offrir quelques dragons pour votre armée " "moyennant un certain prix. Souhaitez-vous recruter des dragons ?" msgid "" "You stand before the Dragon City, a place off-limits to mere humans. Do you " "wish to violate this rule and challenge the Dragons to a fight?" msgstr "" "Vous vous trouvez devant la cité des dragons, un lieu interdit aux simples " "humains. Voulez-vous enfreindre cette règle et défier les dragons dans un " "combat ?" msgid "" "Having defeated the Dragon champions, the city's leaders agree to supply " "some Dragons to your army for a price. Do you wish to recruit Dragons?" msgstr "" "Après avoir vaincu les champions dragons, les dirigeants du village " "acceptent de fournir quelques dragons à votre armée contre un certain prix. " "Souhaitez-vous recruter des dragons ?" msgid "From the observation tower, you are able to see distant lands." msgstr "" "Depuis la tour d'observation, vous êtes en mesure de voir des terres " "lointaines." msgid "" "The spring only refills once a week, and someone's already been here this " "week." msgstr "" "La source ne se remplit qu'une fois par semaine, et quelqu'un est déjà venu " "cette semaine." msgid "" "A drink at the spring is supposed to give you twice your normal spell " "points, but you are already at that level." msgstr "" "Boire à la source est censé vous donner deux fois vos points de magie " "normaux, mais vous avez déjà atteint ce niveau." msgid "" "A drink from the spring fills your blood with magic! You have twice your " "normal spell points in reserve." msgstr "" "Une gorgée de la source remplit votre sang de magie ! Vous avez deux fois " "vos points de magie normaux en réserve." msgid "" "Recognizing you, the butler refuses to admit you. \"The master,\" he says, " "\"will not see the same student twice.\"" msgstr "" "En vous reconnaissant, le majordome refuse de vous admettre. \"Le maître,\" " "dit-il, \"ne verra pas deux fois le même élève.\"" msgid "" "The butler admits you to see the master of the house. He trains you in the " "four skills a hero should know." msgstr "" "Le majordome vous admet à voir le maître de maison. Il vous forme aux quatre " "compétences qu'un héros doit connaître." msgid "" "The butler opens the door and looks you up and down. \"You are neither " "famous nor diplomatic enough to be admitted to see my master,\" he sniffs. " "\"Come back when you think yourself worthy.\"" msgstr "" "Le majordome ouvre la porte et vous regarde de haut en bas. \"Vous n'êtes ni " "assez célèbre ni assez diplomate pour être admis à voir mon maître\", " "renifle-t-il. \"Revenez quand vous vous en croirez digne.\"" msgid " and " msgstr " et " msgid "" "All of the %{monsters} you have in your army have been trained by the battle " "masters of the fort. Your army now contains %{monsters2}." msgstr "" "Tous les %{monsters} que vous avez dans votre armée ont été entraînés par " "les maîtres de guerre du fort. Votre armée contient maintenant %{monsters2}." msgid "" "An unusual alliance of Ogres, Orcs, and Dwarves offer to train (upgrade) any " "such troops brought to them. Unfortunately, you have none with you." msgstr "" "Une alliance inhabituelle d'ogres, d'orques et de nains propose de former " "(améliorer) toutes les troupes de ce type qui leur sont apportées. " "Malheureusement, vous n'en avez aucune avec vous." msgid "All of your %{monsters} have been upgraded into %{monsters2}." msgstr "Tous vos %{monsters} ont été transformés en %{monsters2}." msgid "" "A blacksmith working at the foundry offers to convert all Pikemen and " "Swordsmen's weapons brought to him from iron to steel. He also says that he " "knows a process that will convert Iron Golems into Steel Golems. " "Unfortunately, you have none of these troops in your army, so he can't help " "you." msgstr "" "Un forgeron travaillant à la fonderie propose de convertir en acier toutes " "les armes de piquiers et d'épéistes qui lui sont apportées. Il dit également " "qu'il connaît un procédé permettant de convertir les golems de fer en golems " "d'acier. Malheureusement, vous n'avez aucune de ces troupes dans votre " "armée, il ne peut donc pas vous aider." msgid "" "The captain looks at you with surprise and says:\n" "\"You already have all the maps I know about. Let me fish in peace now.\"" msgstr "" "Le capitaine vous regarde avec surprise et dit :\n" "\"Vous avez déjà toutes les cartes que je connais. Laissez-moi pêcher en " "paix maintenant.\"" msgid "" "A retired captain living on this refurbished fishing platform offers to sell " "you maps of the sea he made in his younger days for 1,000 gold. Do you wish " "to buy the maps?" msgstr "" "Un capitaine à la retraite vivant sur cette plate-forme de pêche rénovée " "propose de vous vendre des cartes de la mer qu'il a faites dans sa jeunesse " "pour 1000 pièces d'or. Souhaitez-vous acheter ces cartes ?" msgid "" "The captain sighs. \"You don't have enough money, eh? You can't expect me " "to give my maps away for free!\"" msgstr "" "Le capitaine soupire. \"Tu n'as pas assez d'argent, hein ? Tu ne peux pas " "attendre de moi que je donne mes cartes gratuitement !\"" msgid "" "You come upon an obelisk made from a type of stone you have never seen " "before. Staring at it intensely, the smooth surface suddenly changes to an " "inscription. The inscription is a piece of a lost ancient map. Quickly you " "copy down the piece and the inscription vanishes as abruptly as it appeared." msgstr "" "Vous tombez sur un obélisque fait d'un type de pierre que vous n'avez jamais " "vu auparavant. Lorsque vous l'étudiez, la surface lisse se transforme " "soudainement en une inscription. L'inscription est un morceau d'une ancienne " "carte perdue. Vous copiez rapidement le morceau et l'inscription disparaît " "aussi brusquement qu'elle est apparue." msgid "You have already been to this obelisk." msgstr "Vous avez déjà visité cet obélisque." msgid "" "Upon your approach, the tree opens its eyes in delight. \"It is good to see " "you, my student. I hope my teachings have helped you.\"" msgstr "" "À votre approche, l'arbre ouvre ses yeux avec joie. \"C'est bon de te voir, " "mon élève. J'espère que mes enseignements t'ont aidé.\"" msgid "" "Upon your approach, the tree opens its eyes in delight. \"Ahh, an " "adventurer! Allow me to teach you a little of what I have learned over the " "ages.\"" msgstr "" "À votre approche, l'arbre ouvre ses yeux avec joie. \"Ahh, un aventurier ! " "Permettez-moi de vous enseigner un peu de ce que j'ai appris au cours des " "âges.\"" msgid "Upon your approach, the tree opens its eyes in delight." msgstr "À votre approche, l'arbre ouvre ses yeux avec joie." msgid "" "\"Ahh, an adventurer! I will be happy to teach you a little of what I have " "learned over the ages for a mere %{count} %{res}.\"" msgstr "" "\"Ahh, un aventurier ! Je serai heureux de vous enseigner un peu de ce que " "j'ai appris au cours des âges pour un prix de %{count} %{res}.\"" msgid "(Just bury it around my roots.)" msgstr "(Enterrez-les juste autour de mes racines.)" msgid "Tears brim in the eyes of the tree." msgstr "Des larmes coulent dans les yeux de l'arbre." msgid "\"I need %{count} %{res}.\"" msgstr "\"J'ai besoin de %{count} %{res}.\"" msgid "it whispers. (sniff) \"Well, come back when you can pay me.\"" msgstr "il chuchote. (sniff) \"Eh bien, reviens quand tu pourras me payer.\"" msgid "" "Nestled among the trees sits a blind seer. After you explain the intent of " "your journey, the seer activates his crystal ball, allowing you to see the " "strengths and weaknesses of your opponents." msgstr "" "Niché parmi les arbres se trouve un devin aveugle. Lorsque vous lui " "expliquez l'intention de votre voyage, le devin active sa boule de cristal, " "vous permettant de voir les forces et les faiblesses de vos adversaires." msgid "" "The entrance to the cave is dark, and a foul, sulfurous smell issues from " "the cave mouth. Will you enter?" msgstr "" "L'entrée de la grotte est sombre, et une odeur nauséabonde et sulfureuse " "provient de la bouche de la grotte. Allez-vous entrer ?" msgid "Except for evidence of a terrible battle, the cave is empty." msgstr "À part les preuves d'une terrible bataille, la grotte est vide." msgid "" "You find a powerful and grotesque Demon in the cave. \"Today,\" it rasps, " "\"you will fight and surely die. But I will give you a choice of deaths. You " "may fight me, or you may fight my servants. Do you prefer to fight my " "servants?\"" msgstr "" "Vous trouvez un démon puissant et grotesque dans la grotte. \"Aujourd'hui, " "râpe-t-il, vous allez vous battre et sûrement mourir. Mais je vais vous " "donner le choix des décès. Vous pouvez me combattre, ou vous pouvez " "combattre mes serviteurs. Préférez-vous combattre mes serviteurs ?\"" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points." msgstr "" "Le Démon pousse son cri de défi et attaque ! Après une courte bataille " "désespérée, vous tuez le monstre et recevez %{exp} points d'expérience." msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points and " "%{count} gold." msgstr "" "Le Démon pousse son cri de défi et attaque ! Après une courte bataille " "désespérée, vous tuez le monstre et recevez %{exp} points d'expérience et " "%{count} pièces d'or." msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and find the %{art} in the back of the cave." msgstr "" "Le Démon pousse son cri de défi et attaque ! Après une courte bataille " "désespérée, vous tuez le monstre et trouvez l'artéfact '%{art}' à l'arrière " "de la grotte." msgid "" "Seeing that you do not have %{count} gold, the demon slashes you with its " "claws, and the last thing you see is a red haze." msgstr "" "Voyant que vous n'avez pas %{count} pièces d'or, le démon vous coupe avec " "ses griffes, et la dernière chose que vous voyez est une brume rouge." msgid "" "The Demon leaps upon you and has its claws at your throat before you can " "even draw your sword. \"Your life is mine,\" it says. \"I will sell it back " "to you for %{count} gold.\"" msgstr "" "Le démon saute sur vous et a ses griffes à votre gorge avant même que vous " "puissiez tirer votre épée. \"Votre vie est à moi\", dit-il. \"Je vous la " "céderai pour %{count} pièces d'or.\"" msgid "" "Upon defeating the daemon's servants, you find a hidden cache with %{count} " "gold." msgstr "" "En battant les serviteurs du démon, vous trouvez une cache dissimulée avec " "%{count} pièces d'or." msgid "" "As you enter the Alchemist's Tower, a hobbled, graying man in a brown cloak " "makes his way towards you." msgstr "" "Lorsque vous entrez dans la tour de l'alchimiste, un homme boiteux et " "grisonnant portant un manteau brun se dirige vers vous." msgid "He checks your pack, and sees that you have 1 cursed item." msgid_plural "" "He checks your pack, and sees that you have %{count} cursed items." msgstr[0] "Il vérifie votre sac, et voit que vous avez un objet maudit." msgstr[1] "" "Il vérifie votre sac, et voit que vous avez %{count} objets maudits." msgid "For %{gold} gold, the alchemist will remove it for you. Do you pay?" msgid_plural "" "For %{gold} gold, the alchemist will remove them for you. Do you pay?" msgstr[0] "" "Pour %{gold} pièces d'or, l'alchimiste l'enlèvera pour vous. Payez-vous ?" msgstr[1] "" "Pour %{gold} pièces d'or, l'alchimiste les enlèvera pour vous. Payez-vous ?" msgid "" "After you consent to pay the requested amount of gold, the alchemist grabs " "the cursed artifact and throws it into his magical cauldron." msgid_plural "" "After you consent to pay the requested amount of gold, the alchemist grabs " "all cursed artifacts and throws them into his magical cauldron." msgstr[0] "" "Après avoir consenti à payer la quantité d'or demandée, l'alchimiste prend " "l'artéfact maudit et le jette dans son chaudron magique." msgstr[1] "" "Après avoir consenti à payer la quantité d'or demandée, l'alchimiste prend " "les artéfacts maudits et les jette dans son chaudron magique." msgid "" "You hear a voice from behind the locked door, \"You don't have enough gold " "to pay for my services.\"" msgstr "" "Vous entendez une voix derrière la porte fermée : \"Vous n'avez pas assez " "d'or pour payer mes services.\"" msgid "" "You hear a voice from high above in the tower, \"Go away! I can't help you!\"" msgstr "" "Vous entendez une voix venant des hauteurs de la tour : \"Allez-vous-en ! Je " "ne peux pas vous aider !\"" msgid "" "The head groom speaks to you, \"That is a fine looking horse you have. I am " "afraid we can give you no better, but the horses your cavalry are riding " "look to be of poor breeding stock. We have many trained war horses which " "would aid your riders greatly. I insist you take them.\"" msgstr "" "Le palefrenier en chef vous parle : \"C'est un beau cheval que vous avez. Je " "crains que nous ne puissions pas vous donner mieux, mais les chevaux que " "votre cavalerie monte semblent être de mauvaise race. Nous avons beaucoup de " "chevaux de guerre entraînés qui aideraient grandement vos cavaliers. " "J'insiste pour que vous les preniez.\"" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, he will " "grow tired in a week. You must also let me give better horses to your " "mounted soldiers, their horses look shoddy and weak.\"" msgstr "" "Alors que vous approchez des écuries, le palefrenier en chef apparaît, " "conduisant un beau cheval de guerre. \"Ce coursier vous aidera à accélérer " "vos déplacements. Hélas, il sera fatigué dans une semaine. Vous devez " "également me permettre de donner de meilleurs chevaux à vos soldats montés, " "leurs chevaux sont en mauvais état et faibles.\"" msgid "" "The head groom approaches you and speaks, \"You already have a fine horse, " "and have no inexperienced cavalry which might make use of our trained war " "horses.\"" msgstr "" "Le palefrenier en chef s'approche de vous et vous dit : \"Vous avez déjà un " "bon cheval, et vous n'avez pas de cavalerie inexpérimentée qui pourrait se " "servir de nos chevaux de guerre entraînés.\"" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, his " "endurance will wane with a lot of heavy riding, and you must return for a " "fresh mount in a week. We also have many fine war horses which could benefit " "mounted soldiers, but you have none we can help.\"" msgstr "" "Alors que vous approchez des écuries, le palefrenier en chef apparaît, " "conduisant un beau cheval de guerre. \"Ce destrier vous aidera à accélérer " "vos déplacements. Hélas, son endurance diminuera avec beaucoup de " "chevauchées, et vous devrez revenir pour une nouvelle monture dans une " "semaine. Nous avons aussi beaucoup de beaux chevaux de guerre qui pourraient " "profiter aux soldats montés, mais vous n'en avez aucun que nous puissions " "aider.\"" msgid "The Arena guards turn you away." msgstr "Les gardes de l'arène vous repoussent." msgid "" "You have your crew stop up their ears with wax before the sirens' eerie song " "has any chance of luring them to a watery grave." msgstr "" "Vous demandez à votre équipage de se boucher les oreilles avec de la cire " "avant que le chant sinistre des sirènes n'ait la moindre chance de les " "attirer vers une mort certaine." msgid "" "As the sirens sing their eerie song, your small, determined army manages to " "overcome the urge to dive headlong into the sea." msgstr "" "Alors que les sirènes chantent leur sinistre chanson, votre petite armée " "déterminée parvient à surmonter l'envie de plonger tête baissée dans la mer." msgid "" "An eerie wailing song emanates from the sirens perched upon the rocks. Many " "of your crew fall under its spell, and dive into the water where they drown. " "You are now wiser for the visit, and gain %{exp} experience." msgstr "" "Un chant sinistre et plaintif émane des sirènes perchées sur les rochers. " "Plusieurs membres de votre équipage tombent sous son charme et plongent dans " "l'eau où ils se noient. Vous êtes maintenant plus sage pour cette visite, et " "gagnez %{exp} d'expérience." msgid "" "In a dazzling display of daring, you break into the local jail and free the " "hero imprisoned there, who, in return, pledges loyalty to your cause." msgstr "" "Dans une démonstration d'audace éblouissante, vous vous introduisez dans la " "prison locale et libérez le héros qui y est emprisonné, qui, en retour, " "promet d'être loyal à votre cause." msgid "" "You already have %{count} heroes, and regretfully must leave the prisoner in " "this jail to languish in agony for untold days." msgstr "" "Vous avez déjà %{count} héros, et vous devez malheureusement laisser le " "prisonnier dans cette prison languir dans l'agonie pendant des jours " "indicibles." msgid "" "You enter a rickety hut and talk to the magician who lives there. He tells " "you of places near and far which may aid you in your journeys." msgstr "" "Vous entrez dans une cabane branlante et parlez au magicien qui y vit. Il " "vous parle de lieux proches et lointains qui peuvent vous aider dans vos " "voyages." msgid "This eye seems to be intently studying its surroundings." msgstr "Cet oeil semble étudier attentivement son environnement." msgid "You come across a giant Sphinx. The Sphinx remains strangely quiet." msgstr "" "Vous tombez sur un sphinx géant. Le sphinx reste étrangement silencieux." msgid "" "\"I have a riddle for you,\" the Sphinx says. \"Answer correctly, and you " "shall be rewarded. Answer incorrectly, and you shall be eaten. Do you accept " "the challenge?\"" msgstr "" "\"J'ai une énigme pour vous\", dit le sphinx. \"Répondez correctement, et " "vous serez récompensé. Répondez incorrectement, et vous serez mangé. " "Acceptez-vous de relever le défi ?\"" msgid "" "The Sphinx asks you the following riddle:\n" "\n" "'%{riddle}'\n" "\n" "Your answer?" msgstr "" "Le Sphinx vous pose l'énigme suivante :\n" "\n" "\"%{riddle}\n" "\n" "Votre réponse ?" msgid "" "\"You guessed incorrectly,\" the Sphinx says, smiling. The Sphinx swipes at " "you with a paw, knocking you to the ground. Another blow makes the world go " "black, and you know no more." msgstr "" "\"Vous avez mal deviné\", dit le sphinx en souriant. Le sphinx vous frappe " "d'un coup de patte, vous faisant tomber au sol. Un autre coup rend le monde " "noir, et vous ne savez plus rien." msgid "" "Looking somewhat disappointed, the Sphinx sighs. \"You've answered my riddle " "so here's your reward. Now begone.\"" msgstr "" "Le sphinx soupire, l'air un peu déçu. \"Vous avez répondu à mon énigme, " "voici votre récompense. Maintenant, partez.\"" msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "As you speak the magic word, the glowing barrier dissolves into nothingness." msgstr "" "Une barrière magique se dresse devant vous, bloquant votre chemin. Les runes " "sur l'arche disent,\n" "\"Prononcez la clé et vous pourrez passer.\"\n" "Lorsque vous prononcez le mot magique, la barrière lumineuse se dissout dans " "le néant." msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "You speak, and nothing happens." msgstr "" "Une barrière magique se dresse devant vous, bloquant votre chemin. Les runes " "sur l'arche disent :\n" "\"Prononcez le mot clé et vous pouvez passer.\"\n" "Vous parlez, mais rien ne se passe." msgid "" "You enter the tent and see an old woman gazing into a magic gem. She looks " "up and says,\n" "\"In my travels, I have learned much in the way of arcane magic. A great " "oracle taught me his skill. I have the answer you seek.\"" msgstr "" "Vous entrez dans la tente et voyez une vieille femme regarder fixement un " "joyau magique. Elle relève son regard et dit :\n" "\"Au cours de mes voyages, j'ai beaucoup appris sur le sujet de la magie " "arcane. Un grand oracle m'a appris son talent. J'ai la réponse que vous " "cherchez.\"" msgid "No spell book is present." msgstr "Le grimoire n'est pas présent." msgid "" "This spell costs %{mana} spell points. You have no spell points, so you " "cannot cast it." msgstr "" "Ce sort coûte %{mana} points de magie. Vous n'avez pas assez de points de " "magie, vous ne pouvez donc pas lancer le sort." msgid "" "This spell costs %{mana} spell points. You only have %{point} spell points, " "so you cannot cast it." msgstr "" "Ce sort coûte %{mana} points de magie. Vous n'avez que %{point} points de " "magie, vous ne pouvez donc pas lancer le sort." msgid "The spell was not found." msgstr "Sort introuvable." msgid "Only heroes can cast this spell." msgstr "Seuls les héros peuvent lancer ce sort." msgid "This hero is not able to cast adventure spells." msgstr "Ce héros ne peut pas lancer de sort d'aventure." msgid "Your hero is too tired to cast this spell today. Try again tomorrow." msgstr "" "Votre héros est trop fatigué pour lancer ce sort aujourd'hui. Essayez à " "nouveau demain." msgid "This spell cannot be cast on a boat." msgstr "Ce sort ne peut pas être lancé en mer." msgid "This spell can only be cast near an ocean." msgstr "Ce sort ne peut être lancé qu'à proximité d'une mer." msgid "" "There are no boats available and no ocean adjacent to the hero where this " "spell will work." msgstr "" "Il n'y a pas de bâteau disponible ni de mer adjacente au héros où ce " "sortilège peut fonctionner." msgid "There are no boats available for this spell." msgstr "Il n'y a pas de bateau disponible pour lancer ce sort." msgid "There is no ocean adjacent to the hero where this spell will work." msgstr "" "Il n'y a pas de mer à proximité du héros, vous ne pouvez donc pas lancer ce " "sort." msgid "" "You do not own any town or castle that is not currently occupied by a hero. " "This spell will have no effect." msgstr "" "Vous ne possédez aucun village ou château qui ne soit pas occupé par un " "héros, vous ne pouvez donc pas lancer ce sort." msgid "This hero is already in a town, so this spell will have no effect." msgstr "" "Ce héros est déjà dans un village ou un château, le sort n'a donc aucun " "effet." msgid "" "The nearest town is %{town}.\n" "\n" "This town is occupied by your hero %{hero}." msgstr "" "Le village le plus proche est %{town}.\n" "\n" "Ce village est occupé par votre héros %{hero}." msgid "This spell is already in effect." msgstr "Ce sort est déjà actif." msgid "" "No opponent neither has nor can have any hero under their command anymore. " "Casting this spell will have no effect." msgstr "" "Aucun adversaire n'a ni ne peut plus avoir de héros sous son commandement. " "Lancer ce sort n'aura aucun effet." msgid "" "No opponent has a hero under their command at this time. Casting this spell " "will have no effect." msgstr "" "Aucun adversaire n'a de héros sous son commandement à ce moment. Lancer ce " "sort n'aura aucun effet." msgid "" "You must be within %{count} spaces of a monster for the Visions spell to " "work." msgstr "" "Vous devez être à maximum %{count} de distance d'un monstre pour que le sort " "visions fonctionne." msgid "" "You must be standing on the entrance to a mine (sawmills and alchemist labs " "do not count) to cast this spell." msgstr "" "Vous devez être à l'entrée d'une mine (les scieries et les alchimistes ne " "comptent pas) pour lancer ce sort." msgid "You must first defeat the ghosts guarding the mine to cast this spell." msgstr "" "Vous devez d'abord vaincre les fantômes qui gardent la mine pour lancer ce " "sort." msgid "" "There are already at least as many elementals guarding the mine as this hero " "can generate. Casting this spell will have no effect." msgstr "" "Il y a déjà au moins autant d'élémentaires qui gardent la mine que ce héros " "peut invoquer. Lancer ce sort n'aura aucun effet." msgid "%{name} the %{race} (Level %{level})" msgstr "%{name} le %{race} (niveau %{level})" msgid "" "'Spread' combat formation spreads the hero's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "La formation de combat 'dispersée' propage les troupes du héros du haut vers " "le bas du champ de bataille, avec au moins un espace vide entre chaque " "troupe." msgid "" "'Grouped' combat formation bunches the hero's army together in the center of " "their side of the battlefield." msgstr "" "La formation de combat 'groupée' regroupe les troupes du héros au centre du " "champ de bataille, du côté du héros." msgid "Are you sure you want to dismiss this Hero?" msgstr "Voulez-vous vraiment renvoyer ce héros ?" msgid "View Experience Info" msgstr "Voir les infos de l'expérience" msgid "View Spell Points Info" msgstr "Voir les infos sur les points de magie" msgid "Set army combat formation to 'Spread'" msgstr "Utiliser la formation de combat 'dispersée'" msgid "Set army combat formation to 'Grouped'" msgstr "Utiliser la formation de combat 'groupée'" msgid "Exit Hero Screen" msgstr "Quitter la fenêtre d'informations du héros" msgid "You cannot dismiss a hero in a castle" msgstr "Vous ne pouvez pas renvoyer un héros dans un château" msgid "Dismissal of %{name} the %{race} is prohibited by scenario" msgstr "Le renvoi de %{name} le %{race} est interdit par le scénario" msgid "Dismiss %{name} the %{race}" msgstr "Renvoyer %{name} le %{race}" msgid "Show previous hero" msgstr "Afficher le héros précédent" msgid "Show next hero" msgstr "Afficher le héros suivant" msgid "Blood Morale" msgstr "Moral de sang" msgid "%{morale} Morale" msgstr "Moral %{morale}" msgid "%{luck} Luck" msgstr "Chance %{luck}" msgid "Current Luck Modifiers:" msgstr "Modificateurs de chance actuels :" msgid "Current Morale Modifiers:" msgstr "Modificateurs de moral actuels :" msgid "Entire army is undead, so morale does not apply." msgstr "" "L'armée entière est composée de morts-vivants, donc le moral ne s'applique " "pas." msgid "" "Current experience %{exp1}.\n" " Next level %{exp2}." msgstr "" "Expérience actuelle %{exp1}.\n" " Niveau suivant %{exp2}." msgid "Level %{level}" msgstr "Niveau %{level}" msgid "" "%{name} currently has %{point} spell points out of a maximum of %{max}. The " "maximum number of spell points is 10 times the hero's knowledge. It is " "occasionally possible for the hero to have more than their maximum spell " "points via special events." msgstr "" "%{name} dispose actuellement de %{point} points de magie sur un maximum de " "%{max}. Le nombre maximum de points de magie est égal à 10 fois le niveau de " "Connaissance. Il est toutefois possible de dépasser ce maximum dans des cas " "particuliers grâce à des événements spéciaux." msgid "%{name1} meets %{name2}" msgstr "Rencontre entre %{name1} et %{name2}" msgid "Enemy heroes are now fully identifiable." msgstr "Les héros ennemis sont désormais parfaitement identifiables." msgid "Identify Hero" msgstr "Identifier héros" msgid "Select town to port to." msgstr "Sélectionnez la destination." msgid "Town Portal" msgstr "Portes du village" msgid "The creatures are willing to join us!" msgstr "Les créatures sont prêtes à nous rallier !" msgid "All the creatures will join us..." msgstr "Toutes les créatures vont nous rallier..." msgid "The creature will join us..." msgid_plural "%{count} of the creatures will join us..." msgstr[0] "La créature va nous rallier..." msgstr[1] "%{count} créatures vont nous rallier..." msgid "" "\n" " for a fee of %{gold} gold." msgstr "" "\n" " pour une somme de %{gold} or." msgid "These weak creatures will surely flee before us." msgstr "Ces faibles créatures vont sûrement fuir devant nous." msgid "I fear these creatures are in the mood for a fight." msgstr "Je crains que ces créatures ne soient d'humeur à se battre." msgid "The hero's attack skill is a bonus added to each unit's attack skill." msgstr "" "L'Attaque du héros est ajoutée á la statistique d'attaque de chacune de ses " "créatures." msgid "The hero's defense skill is a bonus added to each unit's defense skill." msgstr "" "La Défense du héros est ajoutée á la statistique de défense de chacune de " "ses créatures." msgid "The hero's spell power determines the duration or power of a spell." msgstr "La Puissance du héros détermine la durée ou la puissance de ses sorts." msgid "" " The hero's knowledge determines how many spell points the hero may have. " "Under normal circumstances, a hero is limited to 10 spell points per level " "of knowledge." msgstr "" "La Connaissance du héros détermine le nombre de points de magie dont dispose " "ce héros. Dans des circonstances normales, un héros est limité à 10 points " "de magie par niveau de Connaissance." msgid "Current Modifiers:" msgstr "Modificateurs actuels :" msgid "skill|Basic" msgstr "Principes" msgid "skill|Advanced" msgstr "Avancé" msgid "skill|Expert" msgstr "Expert" msgid "Pathfinding" msgstr "Orientation" msgid "Archery" msgstr "Archerie" msgid "Logistics" msgstr "Logistique" msgid "Scouting" msgstr "Reconnaiss." msgid "Diplomacy" msgstr "Diplomatie" msgid "Navigation" msgstr "Navigation" msgid "Leadership" msgstr "Charisme" msgid "Wisdom" msgstr "Sagesse" msgid "Mysticism" msgstr "Mysticisme" msgid "Ballistics" msgstr "Balistique" msgid "Eagle Eye" msgstr "Oeil d'aigle" msgid "Necromancy" msgstr "Nécromancie" msgid "Estates" msgstr "Finances" msgid "Advanced Archery" msgstr "Archerie avancée" msgid "Advanced Pathfinding" msgstr "Orientation avancée" msgid "Basic Archery" msgstr "Principes d'archerie" msgid "Basic Pathfinding" msgstr "Principes d'orientation" msgid "Expert Pathfinding" msgstr "Expert en orientation" msgid "Advanced Logistics" msgstr "Logistique avancée" msgid "Basic Logistics" msgstr "Principes de logistique" msgid "Basic Scouting" msgstr "Principes de reconnaissance" msgid "Expert Archery" msgstr "Expert en archerie" msgid "Expert Logistics" msgstr "Expert en logistique" msgid "Advanced Diplomacy" msgstr "Diplomatie avancée" msgid "Advanced Scouting" msgstr "Reconnaissance avancée" msgid "Basic Diplomacy" msgstr "Principes de diplomatie" msgid "Expert Diplomacy" msgstr "Expert en diplomatie" msgid "Expert Scouting" msgstr "Expert en reconnaissance" msgid "Advanced Leadership" msgstr "Charisme avancée" msgid "Advanced Navigation" msgstr "Navigation avancée" msgid "Basic Leadership" msgstr "Principes de charisme" msgid "Basic Navigation" msgstr "Principes de navigation" msgid "Expert Navigation" msgstr "Expert en navigation" msgid "Advanced Wisdom" msgstr "Sagesse avancée" msgid "Basic Mysticism" msgstr "Principes de mysticisme" msgid "Basic Wisdom" msgstr "Principes de sagesse" msgid "Expert Leadership" msgstr "Expert en charisme" msgid "Expert Wisdom" msgstr "Expert en sagesse" msgid "Advanced Luck" msgstr "Chance avancée" msgid "Advanced Mysticism" msgstr "Mysticisme avancé" msgid "Basic Luck" msgstr "Principes de chance" msgid "Expert Luck" msgstr "Expert en chance" msgid "Expert Mysticism" msgstr "Expert en mysticisme" msgid "Advanced Ballistics" msgstr "Balistique avancée" msgid "Advanced Eagle Eye" msgstr "Oeil d'aigle avancé" msgid "Basic Ballistics" msgstr "Principes de balistique" msgid "Basic Eagle Eye" msgstr "Principes d'oeil d'aigle" msgid "Expert Ballistics" msgstr "Expert en balistique" msgid "Advanced Necromancy" msgstr "Nécromancie avancée" msgid "Basic Estates" msgstr "Principes de finances" msgid "Basic Necromancy" msgstr "Principes de nécromancie" msgid "Expert Eagle Eye" msgstr "Expert en oeil d'aigle" msgid "Expert Necromancy" msgstr "Expert en nécromancie" msgid "Advanced Estates" msgstr "Finances avancées" msgid "Expert Estates" msgstr "Expert en finances" msgid "" "%{skill} reduces the movement penalty for rough terrain by %{count} percent." msgstr "" "%{skill} réduit la pénalité de mouvement pour les terrains accidentés de " "%{count} pour cent." msgid "%{skill} eliminates the movement penalty for rough terrain." msgstr "%{skill} élimine la pénalité de mouvement pour terrain accidenté." msgid "" "%{skill} increases the damage done by the hero's range attacking creatures " "by %{count} percent." msgstr "" "%{skill} augmente de %{count} pour cent les dégâts infligés par les " "créatures qui attaquent à distance dans l'armée du héros." msgid "%{skill} increases the hero's movement points by %{count} percent." msgstr "" "%{skill} augmente les points de mouvement du héros de %{count} pour cent." msgid "%{skill} increases the hero's viewable area by one square." msgid_plural "%{skill} increases the hero's viewable area by %{count} squares." msgstr[0] "%{skill} augmente le champ de vision du héros d'une case." msgstr[1] "%{skill} augmente le champ de vision du héros de %{count} cases." msgid "" "%{skill} allows the hero to negotiate with monsters who are weaker than " "their army, and reduces the cost of surrender." msgstr "" "%{skill} permet au héros de négocier avec des monstres plus faibles que son " "armée." msgid "" "Approximately %{count} percent of the creatures may offer to join the hero." msgstr "" "Environ %{count} pour cent des créatures peuvent vous proposer de rallier le " "héros." msgid "All of the creatures may offer to join the hero." msgstr "Toutes les créatures peuvent proposer de rallier le héros." msgid "" "The cost of surrender is reduced to %{percent} percent of the total cost of " "troops in the army." msgstr "" "Le coût de la reddition est reduite à %{percent} pour cent du coût total des " "troupes dans l'armée du héros." msgid "" "%{skill} increases the hero's movement points over water by %{count} percent." msgstr "" "%{skill} augmente les points de mouvement du héros sur l'eau de %{count} " "pour cent." msgid "%{skill} increases the hero's troops morale by %{count}." msgstr "%{skill} augmente le moral des troupes du héros de %{count}." msgid "%{skill} allows the hero to learn third level spells." msgstr "%{skill} permet au héros d'apprendre des sorts de troisième niveau." msgid "%{skill} allows the hero to learn fourth level spells." msgstr "%{skill} permet au héros d'apprendre des sorts de quatrième niveau." msgid "%{skill} allows the hero to learn fifth level spells." msgstr "%{skill} permet au héros d'apprendre des sorts de cinquième niveau." msgid "%{skill} regenerates one additional spell point per day to the hero." msgid_plural "" "%{skill} regenerates %{count} additional spell points per day to the hero." msgstr[0] "%{skill} octroie un point de magie par jour au héros." msgstr[1] "%{skill} octroie %{count} points de magie par jour au héros." msgid "%{skill} increases the hero's luck by %{count}." msgstr "%{skill} augmente la chance du héros de %{count}." msgid "" "%{skill} gives the hero's catapult shots a greater chance to hit and do " "damage to castle walls." msgstr "" "%{skill} donne aux tirs de catapulte du héros plus de chance de toucher et " "d'infliger des dégâts aux murs du château." msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot has a " "greater chance to hit and do damage to castle walls." msgstr "" "%{skill} donne à la catapulte du héros un tir supplémentaire, et chaque tir " "a plus de chance de toucher et d'endommager les murs du château." msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot " "automatically destroys any wall, except a fortified wall in a Knight castle." msgstr "" "%{skill} donne à la catapulte du héros un tir supplémentaire, et chaque tir " "détruit automatiquement n'importe quel mur, sauf le mur fortifié d'un " "château de chevalier." msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 1st or " "2nd level spell that was cast by an enemy during combat." msgstr "" "%{skill} donne au héros %{count} de chance d'apprendre un sort ennemi de 1er " "ou 2eme niveau utilisé lors d'un combat." msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 3rd " "level spell (or below) that was cast by an enemy during combat." msgstr "" "%{skill} donne au héros %{count} de chance d'apprendre un sort de 3eme " "niveau (ou inférieur) utilisé lors d'un combat." msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 4th " "level spell (or below) that was cast by an enemy during combat." msgstr "" "%{skill} donne au héros %{count} de chance d'apprendre un sort de 4eme " "niveau (ou inférieur) utilisé lors d'un combat." msgid "" "%{skill} allows %{count} percent of the creatures killed in combat to be " "brought back from the dead as Skeletons." msgstr "" "%{skill} permet à %{count} pour cent des créatures tuées au combat d'être " "réanimées en tant que squelettes." msgid "" "The hero produces %{count} gold pieces per day as tax revenue from estates." msgstr "Le héros produit %{count} pièces d'or par jour en recettes fiscales." msgid "Blue" msgstr "Bleu" msgid "Green" msgstr "Vert" msgid "Red" msgstr "Rouge" msgid "Yellow" msgstr "Jaune" msgid "Orange" msgstr "Orange" msgid "Purple" msgstr "Violet" msgid "barrier|Aqua" msgstr "turquoise" msgid "barrier|Blue" msgstr "bleue" msgid "barrier|Brown" msgstr "marron" msgid "barrier|Gold" msgstr "dorée" msgid "barrier|Green" msgstr "verte" msgid "barrier|Orange" msgstr "orange" msgid "barrier|Purple" msgstr "violette" msgid "barrier|Red" msgstr "rouge" msgid "tent|Aqua" msgstr "turquoise" msgid "tent|Blue" msgstr "bleue" msgid "tent|Brown" msgstr "marron" msgid "tent|Gold" msgstr "dorée" msgid "tent|Green" msgstr "verte" msgid "tent|Orange" msgstr "orange" msgid "tent|Purple" msgstr "violette" msgid "tent|Red" msgstr "rouge" msgid "Experience" msgstr "Expérience" msgid "" "Experience allows your heroes to go up levels, increasing their primary and " "secondary skills." msgstr "" "L'expérience permet à votre héros de monter de niveau, et ainsi d'améliorer " "ses statistiques et ses compétences." msgid "Hero/Stats" msgstr "Héros/Stats" msgid "Skills" msgstr "Compétences" msgid "Town/Castle" msgstr "Village/Château" msgid "Garrison" msgstr "Garnison" msgid "Gold Per Day:" msgstr "Or par jour :" msgid "View Heroes." msgstr "Voir héros." msgid "Towns/Castles" msgstr "Villages/Châteaux" msgid "View Towns and Castles." msgstr "Voir villages et châteaux." msgid "luck|Cursed" msgstr "Maudite" msgid "luck|Awful" msgstr "Affreuse" msgid "luck|Bad" msgstr "Défavorable" msgid "luck|Normal" msgstr "Normale" msgid "luck|Good" msgstr "Favorable" msgid "luck|Great" msgstr "Superbe" msgid "luck|Irish" msgstr "Maximale" msgid "" "Bad luck sometimes falls on the hero's units in combat, causing their " "attacks to only do half damage." msgstr "" "La malchance s'abat parfois sur les troupes du héros au combat, leurs " "attaques ne faisant que la moitié des dégâts." msgid "" "Neutral luck means the hero's units will never get lucky or unlucky attacks " "on the enemy." msgstr "" "La chance neutre signifie que les troupes du héros n'auront jamais de chance " "ou de malchance en attaquant l'ennemi." msgid "" "Good luck sometimes lets the hero's units get lucky attacks (double " "strength) in combat." msgstr "" "La bonne chance permet parfois aux troupes du héros d'obtenir des attaques " "chanceuses (dégâts doublés) au combat." msgid "morale|Treason" msgstr "Traître" msgid "morale|Awful" msgstr "Désastreux" msgid "morale|Poor" msgstr "Médiocre" msgid "morale|Normal" msgstr "Normal" msgid "morale|Good" msgstr "Bon" msgid "morale|Great" msgstr "Superbe" msgid "morale|Blood!" msgstr "Sanglant !" msgid "Bad morale may cause the hero's units to freeze in combat." msgstr "" "Un mauvais moral peut entraîner la paralysie des troupes du héros au combat." msgid "" "Neutral morale means the hero's units will never be blessed with extra " "attacks or freeze in combat." msgstr "" "Un moral neutre signifie que les troupes du héros ne seront jamais bénies " "avec des attaques supplémentaires ni ne se figeront au combat." msgid "Good morale may give the hero's units extra attacks in combat." msgstr "" "Un bon moral peut donner àux troupes du héros des attaques supplémentaires " "en combat." msgid "Knight" msgstr "Chevalier" msgid "Barbarian" msgstr "Barbare" msgid "Sorceress" msgstr "Sorcière" msgid "Warlock" msgstr "Mage" msgid "Wizard" msgstr "Magicien" msgid "Necromancer" msgstr "Nécromant" msgid "Multi" msgstr "Multi" msgid "doubleLined|Knight" msgstr "Chevalier" msgid "doubleLined|Barbarian" msgstr "Barbare" msgid "doubleLined|Sorceress" msgstr "Sorcière" msgid "doubleLined|Warlock" msgstr "Mage" msgid "doubleLined|Wizard" msgstr "Magicien" msgid "" "doubleLined|Necro-\n" "mancer" msgstr "" "Nécro-\n" "mant" msgid "doubleLinedRace|Multi" msgstr "Multi" msgid "doubleLinedRace|Random" msgstr "Aléatoire" msgid "speed|Standing" msgstr "Immobile" msgid "speed|Crawling" msgstr "Rampante" msgid "speed|Very Slow" msgstr "Très lente" msgid "speed|Slow" msgstr "Lente" msgid "speed|Average" msgstr "Moyenne" msgid "speed|Fast" msgstr "Rapide" msgid "speed|Very Fast" msgstr "Très rapide" msgid "speed|Ultra Fast" msgstr "Ultra rapide" msgid "speed|Blazing" msgstr "Foudroyante" msgid "speed|Instant" msgstr "Téléportée" msgid "week|Squirrel" msgstr "de l'écureuil" msgid "week|Rabbit" msgstr "du lapin" msgid "week|Gopher" msgstr "de la marmotte" msgid "week|Badger" msgstr "du blaireau" msgid "week|Rat" msgstr "du rat" msgid "week|Eagle" msgstr "de l'aigle" msgid "week|Weasel" msgstr "de la belette" msgid "week|Raven" msgstr "du corbeau" msgid "week|Mongoose" msgstr "de la mangouste" msgid "week|Dog" msgstr "du chien" msgid "week|Aardvark" msgstr "de l'oryctérope" msgid "week|Lizard" msgstr "du lézard" msgid "week|Tortoise" msgstr "de la tortue" msgid "week|Hedgehog" msgstr "du hérisson" msgid "week|Condor" msgstr "du condor" msgid "week|Ant" msgstr "de la fourmi" msgid "week|Grasshopper" msgstr "de la sauterelle" msgid "week|Dragonfly" msgstr "de la libellule" msgid "week|Spider" msgstr "de l'araignée" msgid "week|Butterfly" msgstr "du papillon" msgid "week|Bumblebee" msgstr "du bourdon" msgid "week|Locust" msgstr "du criquet" msgid "week|Earthworm" msgstr "du ver" msgid "week|Hornet" msgstr "du frelon" msgid "week|Beetle" msgstr "de la coccinelle" msgid "week|PLAGUE" msgstr "de la PESTE" msgid "week|Unnamed" msgstr "sans nom" msgid "Desert" msgstr "Désert" msgid "Snow" msgstr "Neige" msgid "Wasteland" msgstr "Terrain vague" msgid "Beach" msgstr "Plage" msgid "Lava" msgstr "Lave" msgid "Dirt" msgstr "Boue" msgid "Grass" msgstr "Herbe" msgid "Ocean" msgstr "Océan" msgid "maps|Small" msgstr "Petite" msgid "maps|Medium" msgstr "Moyenne" msgid "maps|Large" msgstr "Grande" msgid "maps|Extra Large" msgstr "Très grande" msgid "maps|Custom Size" msgstr "Taille personnalisée" msgid "Ore Mine" msgstr "Mine de fer" msgid "Sulfur Mine" msgstr "Mine de soufre" msgid "Crystal Mine" msgstr "Mine de cristal" msgid "Gems Mine" msgstr "Mine de gemmes" msgid "Gold Mine" msgstr "Mine d'or" msgid "Mine" msgstr "Mine" msgid "Burma shave." msgstr "Burma shave." msgid "Next sign 50 miles." msgstr "Panneau suivant : 50 miles." msgid "See Rock City." msgstr "Visitez Rock City." msgid "This space for rent." msgstr "Espace à louer." msgid "No object" msgstr "Aucun objet" msgid "Alchemist Lab" msgstr "Laboratoire d'alchimiste" msgid "Sign" msgstr "Panneau" msgid "Buoy" msgstr "Bouée" msgid "Skeleton" msgstr "Squelette" msgid "Daemon Cave" msgstr "Grotte de démon" msgid "Treasure Chest" msgstr "Coffre au trésor" msgid "Faerie Ring" msgstr "Anneau des fées" msgid "Campfire" msgstr "Feu de camp" msgid "Fountain" msgstr "Fontaine" msgid "Gazebo" msgstr "Belvédère" msgid "Genie Lamp" msgstr "Lampe ancienne" msgid "Archer's House" msgstr "Demeure des archers" msgid "Goblin Hut" msgstr "Paillote" msgid "Dwarf Cottage" msgstr "Cottage des nains" msgid "Peasant Hut" msgstr "Grange" msgid "Stables" msgstr "Écurie" msgid "Alchemist's Tower" msgstr "Tour d'alchimiste" msgid "Event" msgstr "Événement" msgid "Dragon City" msgstr "Cité des dragons" msgid "Lighthouse" msgid_plural "Lighthouses" msgstr[0] "Phare" msgstr[1] "Phares" msgid "Water Wheel" msgid_plural "Water Wheels" msgstr[0] "Roue hydraulique" msgstr[1] "Roue hydraulique" msgid "Monster" msgstr "Monstre" msgid "Obelisk" msgstr "Obélisque" msgid "Oasis" msgstr "Oasis" msgid "Resource" msgstr "Ressource" msgid "Sawmill" msgstr "Scierie" msgid "Oracle" msgstr "Oracle" msgid "Shrine of the First Circle" msgstr "Sanctuaire du premier cercle" msgid "Shipwreck" msgstr "Épave" msgid "Sea Chest" msgstr "Coffre au trésor englouti" msgid "Desert Tent" msgstr "Tente de désert" msgid "Stone Liths" msgstr "Portail magique" msgid "Wagon Camp" msgstr "Roulotte" msgid "Hut of the Magi" msgstr "Hutte des mages" msgid "Whirlpool" msgstr "Tourbillon" msgid "Windmill" msgid_plural "Windmills" msgstr[0] "Moulin à vent" msgstr[1] "Moulins à vent" msgid "Artifact" msgstr "Artéfact" msgid "Mermaid" msgstr "Nymphe" msgid "Boat" msgstr "Navire" msgid "Random Ultimate Artifact" msgstr "Ultime artéfact aléatoire" msgid "Random Artifact" msgstr "Artéfact aléatoire" msgid "Random Resource" msgstr "Ressource aléatoire" msgid "Random Monster" msgstr "Monstre aléatoire" msgid "Random Town" msgstr "Village aléatoire" msgid "Random Castle" msgstr "Château aléatoire" msgid "Eye of the Magi" msgstr "Oeil des mages" msgid "Random Monster - weak" msgstr "Monstre aléatoire - faible" msgid "Random Monster - medium" msgstr "Monstre aléatoire - moyen" msgid "Random Monster - strong" msgstr "Monstre aléatoire - fort" msgid "Random Monster - very strong" msgstr "Monstre aléatoire - très fort" msgid "Hero" msgstr "Héros" msgid "Nothing Special" msgstr "Rien de spécial" msgid "Mossy Rock" msgstr "Mousse et rochers" msgid "Watch Tower" msgstr "Tour de guet" msgid "Tree House" msgstr "Cabane" msgid "Tree City" msgstr "Cité des arbres" msgid "Ruins" msgstr "Ruines" msgid "Fort" msgstr "Fort" msgid "Abandoned Mine" msgstr "Mine abandonnée" msgid "Sirens" msgstr "Sirènes" msgid "Standing Stones" msgstr "Pierres dressées" msgid "Idol" msgstr "Idole" msgid "Tree of Knowledge" msgstr "Arbre de connaissance" msgid "Witch Doctor's Hut" msgstr "Chaumière de sorcier" msgid "Temple" msgstr "Temple" msgid "Hill Fort" msgstr "Fort sur la colline" msgid "Halfling Hole" msgstr "Repaire des galopins" msgid "Mercenary Camp" msgstr "Camp de mercenaires" msgid "Shrine of the Second Circle" msgstr "Sanctuaire du second cercle" msgid "Shrine of the Third Circle" msgstr "Sanctuaire du troisième cercle" msgid "City of the Dead" msgstr "Cité des morts" msgid "Sphinx" msgstr "Sphinx" msgid "Wagon" msgstr "Charrette" msgid "Tar Pit" msgstr "Fosse de goudron" msgid "Artesian Spring" msgstr "Puits artésien" msgid "Troll Bridge" msgstr "Pont des trolls" msgid "Watering Hole" msgstr "Point d'eau" msgid "Witch's Hut" msgstr "Cabane de la sorcière" msgid "Xanadu" msgstr "Xanadu" msgid "Lean-To" msgstr "Appentis" msgid "Magellan's Maps" msgstr "Cartes de Magellan" msgid "Flotsam" msgstr "Débris" msgid "Derelict Ship" msgstr "Navire abandonné" msgid "Shipwreck Survivor" msgstr "Survivant du naufrage" msgid "Bottle" msgstr "Bouteille" msgid "Magic Well" msgstr "Puits magique" msgid "Magic Garden" msgid_plural "Magic Gardens" msgstr[0] "Jardin magique" msgstr[1] "Jardins magiques" msgid "Observation Tower" msgstr "Tour d'observation" msgid "Freeman's Foundry" msgstr "Fonderie du citoyen" msgid "Reefs" msgstr "Récifs" msgid "Volcano" msgstr "Volcan" msgid "Flowers" msgstr "Fleurs" msgid "Rock" msgstr "Caillou" msgid "Water Lake" msgstr "Lac" msgid "Mandrake" msgstr "Mandragore" msgid "Dead Tree" msgstr "Arbre mort" msgid "Stump" msgstr "Souche" msgid "Crater" msgstr "Cratère" msgid "Cactus" msgstr "Cactus" msgid "Mound" msgstr "Monticule" msgid "Dune" msgstr "Dune" msgid "Lava Pool" msgstr "Lac de lave" msgid "Shrub" msgstr "Arbuste" msgid "Barrow Mounds" msgstr "Tumulus" msgid "Random Artifact - Treasure" msgstr "Artéfact aléatoire - trésor" msgid "Random Artifact - Minor" msgstr "Artéfact aléatoire - mineur" msgid "Random Artifact - Major" msgstr "Artéfact aléatoire - majeur" msgid "Barrier" msgstr "Barrière magique" msgid "Traveller's Tent" msgstr "Tente de voyageur" msgid "Jail" msgstr "Prison" msgid "Fire Summoning Altar" msgstr "Autel d'invocation du feu" msgid "Air Summoning Altar" msgstr "Autel d'invocation de l'air" msgid "Earth Summoning Altar" msgstr "Autel d'invocation de la terre" msgid "Water Summoning Altar" msgstr "Autel d'invocation de l'eau" msgid "Unknown Monster" msgstr "Monstre inconnu" msgid "Unknown Monsters" msgstr "Monstres inconnus" msgid "Peasant" msgstr "Paysan" msgid "Peasants" msgstr "Paysans" msgid "Archer" msgstr "Archer" msgid "Archers" msgstr "Archers" msgid "Ranger" msgstr "Ranger" msgid "Rangers" msgstr "Rangers" msgid "Pikeman" msgstr "Fantassin" msgid "Pikemen" msgstr "Fantassins" msgid "Veteran Pikeman" msgstr "Fantassin vétéran" msgid "Veteran Pikemen" msgstr "Fantassins vétérans" msgid "Swordsman" msgstr "Spadassin" msgid "Swordsmen" msgstr "Spadassins" msgid "Master Swordsman" msgstr "Maître spadassin" msgid "Master Swordsmen" msgstr "Maîtres spadassins" msgid "Cavalries" msgstr "Cavaliers" msgid "Cavalry" msgstr "Cavalier" msgid "Champion" msgstr "Champion" msgid "Champions" msgstr "Champions" msgid "Paladin" msgstr "Paladin" msgid "Paladins" msgstr "Paladins" msgid "Crusader" msgstr "Grand paladin" msgid "Crusaders" msgstr "Grands paladins" msgid "Goblin" msgstr "Gobelin" msgid "Goblins" msgstr "Gobelins" msgid "Orc" msgstr "Orc" msgid "Orcs" msgstr "Orcs" msgid "Orc Chief" msgstr "Chef orc" msgid "Orc Chiefs" msgstr "Chefs orcs" msgid "Wolf" msgstr "Loup" msgid "Wolves" msgstr "Loups" msgid "Ogre" msgstr "Ogre" msgid "Ogres" msgstr "Ogres" msgid "Ogre Lord" msgstr "Grand ogre" msgid "Ogre Lords" msgstr "Grands ogres" msgid "Troll" msgstr "Troll" msgid "Trolls" msgstr "Trolls" msgid "War Troll" msgstr "Troll guerrier" msgid "War Trolls" msgstr "Trolls guerriers" msgid "Cyclopes" msgstr "Cyclopes" msgid "Cyclops" msgstr "Cyclope" msgid "Sprite" msgstr "Fée" msgid "Sprites" msgstr "Fées" msgid "Dwarf" msgstr "Nain" msgid "Dwarves" msgstr "Nains" msgid "Battle Dwarf" msgstr "Nain guerrier" msgid "Battle Dwarves" msgstr "Nains guerriers" msgid "Elf" msgstr "Elfe" msgid "Elves" msgstr "Elfes" msgid "Grand Elf" msgstr "Grand elfe" msgid "Grand Elves" msgstr "Grands elfes" msgid "Druid" msgstr "Druide" msgid "Druids" msgstr "Druides" msgid "Greater Druid" msgstr "Grand druide" msgid "Greater Druids" msgstr "Grands druides" msgid "Unicorn" msgstr "Licorne" msgid "Unicorns" msgstr "Licornes" msgid "Phoenix" msgstr "Phénix" msgid "Phoenixes" msgstr "Phénix" msgid "Centaur" msgstr "Centaure" msgid "Centaurs" msgstr "Centaures" msgid "Gargoyle" msgstr "Gargouille" msgid "Gargoyles" msgstr "Gargouilles" msgid "Griffin" msgstr "Griffon" msgid "Griffins" msgstr "Griffons" msgid "Minotaur" msgstr "Minotaure" msgid "Minotaurs" msgstr "Minotaures" msgid "Minotaur King" msgstr "Roi minotaure" msgid "Minotaur Kings" msgstr "Rois minotaures" msgid "Hydra" msgstr "Hydre" msgid "Hydras" msgstr "Hydres" msgid "Green Dragon" msgstr "Dragon vert" msgid "Green Dragons" msgstr "Dragons verts" msgid "Red Dragon" msgstr "Dragon rouge" msgid "Red Dragons" msgstr "Dragons rouges" msgid "Black Dragon" msgstr "Dragon noir" msgid "Black Dragons" msgstr "Dragons noirs" msgid "Halfling" msgstr "Galopin" msgid "Halflings" msgstr "Galopins" msgid "Boar" msgstr "Sanglier" msgid "Boars" msgstr "Sangliers" msgid "Iron Golem" msgstr "Golem de fer" msgid "Iron Golems" msgstr "Golems de fer" msgid "Steel Golem" msgstr "Golem d'acier" msgid "Steel Golems" msgstr "Golems d'acier" msgid "Roc" msgstr "Rock" msgid "Rocs" msgstr "Rocks" msgid "Mage" msgstr "Mage" msgid "Magi" msgstr "Mages" msgid "Archmage" msgstr "Grand mage" msgid "Archmagi" msgstr "Grands mages" msgid "Giant" msgstr "Géant" msgid "Giants" msgstr "Géants" msgid "Titan" msgstr "Titan" msgid "Titans" msgstr "Titans" msgid "Skeletons" msgstr "Squelettes" msgid "Zombie" msgstr "Zombie" msgid "Zombies" msgstr "Zombies" msgid "Mutant Zombie" msgstr "Zombie mutant" msgid "Mutant Zombies" msgstr "Zombies mutants" msgid "Mummies" msgstr "Momies" msgid "Mummy" msgstr "Momie" msgid "Royal Mummies" msgstr "Momies royales" msgid "Royal Mummy" msgstr "Momie royale" msgid "Vampire" msgstr "Vampire" msgid "Vampires" msgstr "Vampires" msgid "Vampire Lord" msgstr "Grand vampire" msgid "Vampire Lords" msgstr "Grands vampires" msgid "Lich" msgstr "Macchabée" msgid "Liches" msgstr "Macchabées" msgid "Power Lich" msgstr "Grand macchabée" msgid "Power Liches" msgstr "Grands macchabées" msgid "Bone Dragon" msgstr "Dragon étique" msgid "Bone Dragons" msgstr "Dragons étiques" msgid "Rogue" msgstr "Pillard" msgid "Rogues" msgstr "Pillards" msgid "Nomad" msgstr "Nomade" msgid "Nomads" msgstr "Nomades" msgid "Ghost" msgstr "Fantôme" msgid "Ghosts" msgstr "Fantômes" msgid "Genie" msgstr "Génie" msgid "Genies" msgstr "Génies" msgid "Medusa" msgstr "Méduse" msgid "Medusas" msgstr "Méduses" msgid "Earth Elemental" msgstr "Elémentaire de la terre" msgid "Earth Elementals" msgstr "Elémentaires de la terre" msgid "Air Elemental" msgstr "Elémentaire de l'air" msgid "Air Elementals" msgstr "Elémentaires de l'air" msgid "Fire Elemental" msgstr "Elémentaire de feu" msgid "Fire Elementals" msgstr "Elémentaires de feu" msgid "Water Elemental" msgstr "Elémentaire de l'eau" msgid "Water Elementals" msgstr "Elémentaires de l'eau" msgid "Random Monsters" msgstr "Monstres aléatoires" msgid "Random Monster 1" msgstr "Monstre aléatoire 1" msgid "Random Monsters 1" msgstr "Monstres aléatoires 1" msgid "Random Monster 2" msgstr "Monstre aléatoire 2" msgid "Random Monsters 2" msgstr "Monstres aléatoires 2" msgid "Random Monster 3" msgstr "Monstre aléatoire 3" msgid "Random Monsters 3" msgstr "Monstres aléatoires 3" msgid "Random Monster 4" msgstr "Monstre aléatoire 4" msgid "Random Monsters 4" msgstr "Monstres aléatoires 4" msgid "Double shot" msgstr "Double tir" msgid "2-hex monster" msgstr "Occupe deux cases" msgid "Double strike" msgstr "Double attaque" msgid "Double damage to Undead" msgstr "Double dégâts aux morts-vivants" msgid "% magic resistance" msgstr "% de résistance magique" msgid "Immune to Mind spells" msgstr "Immunité contre les sorts de l'esprit" msgid "Immune to Elemental spells" msgstr "Immunité contre sorts élémentaires" msgid "Immune to Fire spells" msgstr "Immunité contre les sorts de feu" msgid "Immune to Cold spells" msgstr "Immunité contre sorts de froid" msgid "Immune to " msgstr "Immunité contre " msgid "% immunity to %{spell} spell" msgstr "% d'immunité contre le sort %{spell}" msgid "% damage from Elemental spells" msgstr "% de dégâts des sorts élémentaires" msgid "% chance to Dispel beneficial spells" msgstr "% de chances de dissiper les sorts bénéfiques" msgid "% chance to Paralyze" msgstr "% de chances de paralyser" msgid "% chance to Petrify" msgstr "% de chances de pétrifier" msgid "% chance to Blind" msgstr "% de chances d'aveugler" msgid "% chance to Curse" msgstr "% de chances de maudire" msgid "% chance to cast %{spell} spell" msgstr "% de chances de lancer le sort %{spell}" msgid "HP regeneration" msgstr "Régénération PV" msgid "Two hexes attack" msgstr "Attaque sur deux hexagones" msgid "Flyer" msgstr "Volant" msgid "Always retaliates" msgstr "Riposte toujours" msgid "Attacks all adjacent enemies" msgstr "Attaque tous les ennemis adjacents" msgid "No melee penalty" msgstr "Pas de pénalité de mêlée" msgid "Dragon" msgstr "Dragon" msgid "Undead" msgstr "Mort-vivant" msgid "No enemy retaliation" msgstr "Aucune riposte de l'ennemi" msgid "HP drain" msgstr "Drainage PV" msgid "Cloud attack" msgstr "Attaque nuage" msgid "Decreases enemy's morale by " msgstr "Diminue le moral de l'ennemi de " msgid "% chance to halve enemy" msgstr "% de chances de tuer la moitié du peloton ennemi" msgid "Soul Eater" msgstr "Dévoreur d'âmes" msgid "Elemental" msgstr "Élémentaire" msgid "No Morale" msgstr "Pas de moral" msgid "200% damage from Fire spells" msgstr "200% des dégâts des sorts du feu" msgid "200% damage from Cold spells" msgstr "200% des dégâts des sorts du froid" msgid "% damage from %{spell} spell" msgstr "% de dégâts du sort %{spell}" msgid "% immunity to " msgstr "% d'immunité contre " msgid "Lightning" msgstr "Éclair foudroyant" msgid "% damage from " msgstr "% de dégâts de " msgid "The three Anduran artifacts magically combine into one." msgstr "Les trois artéfacts d'Anduran se combinent magiquement en un seul." msgid "View Spells" msgstr "Voir les sorts" msgid "View %{name} Info" msgstr "Voir les infos de %{name}" msgid "Move %{name}" msgstr "Déplacer %{name}" msgid "Cannot move the Spellbook" msgstr "Impossible de déplacer le grimoire" msgid "This item can't be traded." msgstr "Cet objet ne peut pas être échangé." msgid "Invalid Artifact" msgstr "Artéfact non valide" msgid "The %{name} increases the hero's knowledge by %{count}." msgstr "%{name} augmente la Connaissance du héros de %{count}." msgid "Ultimate Book of Knowledge" msgstr "L'ultime livre de la Connaissance" msgid "The %{name} increases the hero's attack skill by %{count}." msgstr "%{name} augmente l'Attaque du héros de %{count}." msgid "Ultimate Sword of Dominion" msgstr "L'ultime épée de domination" msgid "The %{name} increases the hero's defense skill by %{count}." msgstr "%{name} augmente la Défense du héros de %{count}." msgid "Ultimate Cloak of Protection" msgstr "L'ultime voile de protection" msgid "The %{name} increases the hero's spell power by %{count}." msgstr "%{name} augmente la Puissance des sorts du héros de %{count}." msgid "Ultimate Wand of Magic" msgstr "L'ultime baguette magique" msgid "" "The %{name} increases the hero's attack and defense skills by %{count} each." msgstr "" "%{name} augmente les compétences d'attaque et de défense du héros de " "%{count} chacune." msgid "Ultimate Shield" msgstr "L'ultime bouclier" msgid "" "The %{name} increases the hero's spell power and knowledge by %{count} each." msgstr "" "%{name} augmente la Puissance et la Connaissance du héros de %{count} chacun." msgid "Ultimate Staff" msgstr "L'ultime bâton" msgid "" "The %{name} increases each of the hero's basic skills by %{count} points." msgstr "" "%{name} augmente chacune des statistiques primaires du héros de %{count} " "points." msgid "Ultimate Crown" msgstr "L'ultime couronne" msgid "Golden Goose" msgstr "La poule aux oeufs d'or" msgid "The %{name} brings in an income of %{count} gold per day." msgstr "%{name} apporte un revenu de %{count} pièces d'or par jour." msgid "Arcane Necklace of Magic" msgstr "L'obscur collier magique" msgid "" "After rescuing a Sorceress from a cursed tomb, she rewards your heroism with " "an exquisite jeweled necklace." msgstr "" "Après avoir sauvé une sorcière d'une tombe maudite, elle récompense votre " "héroïsme avec un superbe collier de joyaux." msgid "Caster's Bracelet of Magic" msgstr "Le bracelet magique du jeteur de sorts" msgid "" "While searching through the rubble of a caved-in mine, you free a group of " "trapped Dwarves. Grateful, the leader gives you a golden bracelet." msgstr "" "En fouillant dans les décombres d'une mine effondrée, vous libérez un groupe " "de nains piégés. Reconnaissant, le chef vous donne un bracelet en or." msgid "Mage's Ring of Power" msgstr "L'anneau de Puissance du magicien" msgid "" "A cry of pain leads you to a Centaur, caught in a trap. Upon setting the " "creature free, he hands you a small pouch. Emptying the contents, you find a " "dazzling jeweled ring." msgstr "" "Un cri de douleur vous conduit à un Centaure, pris dans un piège. Après " "avoir libéré la créature, elle vous tend une petite pochette. En vidant son " "contenu, vous trouvez un anneau éblouissant orné de pierres précieuses." msgid "Witch's Broach of Magic" msgstr "La broche magique de la sorcière" msgid "" "Alongside the remains of a burnt witch lies a beautiful broach, intricately " "designed. Approaching the corpse with caution, you add the broach to your " "inventory." msgstr "" "À côté des restes d'une sorcière brûlée se trouve une belle broche, finement " "conçue. Approchant le cadavre avec précaution, vous ajoutez la broche à " "votre inventaire." msgid "Medal of Valor" msgstr "La médaille de la bravoure" msgid "The %{name} increases the morale of the hero's army by %{count}." msgstr "%{name} augmente le moral de l'armée du héros de %{count}." msgid "" "Freeing a virtuous maiden from the clutches of an evil overlord, you are " "granted a Medal of Valor by the King's herald." msgstr "" "En libérant une jeune femme vertueuse des griffes d'un seigneur maléfique, " "vous êtes récompensé de la médaille de bravoure par le hérault du roi." msgid "Medal of Courage" msgstr "La médaille du courage" msgid "" "After saving a young boy from a vicious pack of Wolves, you return him to " "his father's manor. The grateful nobleman awards you with a Medal of Courage." msgstr "" "Après avoir sauvé un jeune garçon d'une dangereuse meute de loups, vous le " "ramenez au manoir de son père. Reconnaissant, le noble vous remet la " "médaille du courage." msgid "Medal of Honor" msgstr "La médaille d'honneur" msgid "" "After freeing a princess of a neighboring kingdom from the evil clutches of " "despicable slavers, she awards you with a Medal of Honor." msgstr "" "Après avoir libéré la princesse d'un royaume voisin des griffes d'ignobles " "esclavagistes, elle vous décerne la médaille d'honneur." msgid "Medal of Distinction" msgstr "La médaille de distinction" msgid "" "Ridding the countryside of the hideous Minotaur who made a sport of eating " "noblemen's Knights, you are honored with the Medal of Distinction." msgstr "" "Après avoir débarrassé ces terres de l'affreux minotaure qui adorait dévorer " "les nobles chevaliers, on vous décerne la médaille de la distinction." msgid "Fizbin of Misfortune" msgstr "Le Fizbin de l'infortune" msgid "" "The %{name} greatly decreases the morale of the hero's army by %{count}." msgstr "%{name} diminue fortement le moral de l'armée du héros de %{count}." msgid "" "You stumble upon a medal lying alongside the empty road. Adding the medal to " "your inventory, you become aware that you have acquired the undesirable " "Fizbin of Misfortune, greatly decreasing your army's morale." msgstr "" "Vous tombez sur une médaille gisant sur le bord d'une route déserte. En " "ajoutant la médaille à votre inventaire, vous vous rendez compte que vous " "avez acquis l'indésirable Fizbin de l'infortune, diminuant considérablement " "le moral de votre armée." msgid "Thunder Mace of Dominion" msgstr "Le marteau de foudre de domination" msgid "" "During a sudden storm, a bolt of lightning strikes a tree, splitting it. " "Inside the tree you find a mysterious mace." msgstr "" "En pleine tempête, un éclair frappe un arbre, le fendant. A l'intérieur de " "l'arbre, vous trouvez une mystérieuse masse d'armes." msgid "Armored Gauntlets of Protection" msgstr "Les gantelets de protection" msgid "The %{name} increase the hero's defense skill by %{count}." msgstr "%{name} augmentent la Défense du héros de %{count}." msgid "" "You encounter the infamous Black Knight! After a grueling duel ending in a " "draw, the Knight, out of respect, offers you a pair of armored gauntlets." msgstr "" "Vous rencontrez le tristement célèbre chevalier noir ! Après un duel " "éreintant suivi d'un match nul, le chevalier, par respect, vous offre une " "paire de gantelets renforcés." msgid "Defender Helm of Protection" msgstr "Le casque de protection du défenseur" msgid "" "A glint of golden light catches your eye. Upon further investigation, you " "find a golden helm hidden under a bush." msgstr "" "Un éclat de lumière attire votre attention. En y regardant de plus près, " "vous trouvez un casque en or caché dans un buisson." msgid "Giant Flail of Dominion" msgstr "Le fléau géant de domination" msgid "" "A clumsy Giant has killed himself with his own flail. Knowing your superior " "skill with this weapon, you confidently remove the spectacular flail from " "the fallen Giant." msgstr "" "Un géant maladroit s'est tué avec son propre fléau. Vous sachant plus habile " "avec cette arme, vous retirez avec confiance le spectaculaire fléau de la " "dépouille du géant." msgid "Ballista of Quickness" msgstr "La baliste de rapidité" msgid "The %{name} gives the hero's catapult one extra shot per combat round." msgstr "" "%{name} permet à la catapulte du héros de tirer un coup supplémentaire par " "tour en combat." msgid "" "Walking through the ruins of an ancient walled city, you find the instrument " "of the city's destruction, an elaborately crafted ballista." msgstr "" "En marchant dans les ruines d'une ancienne ville fortifiée, vous trouvez " "l'instrument de sa destruction, une baliste artisanale minutieusement " "confectionnée." msgid "Stealth Shield of Protection" msgstr "Le bouclier furtif de protection" msgid "" "A stone statue of a warrior holds a silver shield. As you remove the shield, " "the statue crumbles into dust." msgstr "" "Une statue de pierre représente un guerrier tenant un bouclier en argent. Au " "moment où vous retirez le bouclier, la statue tombe en poussière." msgid "Dragon Sword of Dominion" msgstr "L'épée de domination du dragon" msgid "" "As you are walking along a narrow path, a nearby bush suddenly bursts into " "flames. Before your eyes the flames become the image of a beautiful woman. " "She holds out a magnificent sword to you." msgstr "" "Alors que vous marchez sur un chemin étroit, un buisson s'enflamme " "soudainement. Devant vos yeux, les flammes se transforment et dessinent la " "silhouette d'une belle femme. Elle vous tend une magnifique épée." msgid "Power Axe of Dominion" msgstr "La puissante hache de domination" msgid "" "You see a silver axe embedded deeply in the ground. After several " "unsuccessful attempts by your army to remove the axe, you tightly grip the " "handle of the axe and effortlessly pull it free." msgstr "" "Vous voyez une hache en argent profondément enfoncée dans le sol. Après " "plusieurs tentatives infructueuses de vos troupes pour retirer la hache, " "vous en saisissez fermement le manche et la libérez sans effort." msgid "Divine Breastplate of Protection" msgstr "Le plastron divin de protection" msgid "" "A gang of Rogues is sifting through the possessions of dead warriors. " "Scaring off the scavengers, you note the Rogues had overlooked a beautiful " "breastplate." msgstr "" "Une bande de voleurs pille les dépouilles de guerriers. En effrayant les " "charognards, vous remarquez qu'ils ont oublié un beau plastron." msgid "Minor Scroll of Knowledge" msgstr "Le parchemin mineur de la Connaissance" msgid "" "Before you appears a levitating glass case with a scroll, perched upon a bed " "of crimson velvet. At your touch, the lid opens and the scroll floats into " "your awaiting hands." msgstr "" "Devant vous apparaît un coffre en verre contenant un parchemin, juché sur un " "lit de velours cramoisi. À votre contact, le couvercle s'ouvre et le " "parchemin flotte jusqu'à vos mains impatientes." msgid "Major Scroll of Knowledge" msgstr "Le parchemin majeur de la Connaissance" msgid "" "Visiting a local wiseman, you explain the intent of your journey. He reaches " "into a sack and withdraws a yellowed scroll and hands it to you." msgstr "" "Rendant visite à un sage des alentours, vous expliquez l'intention de votre " "voyage. Il fouille dans un sac et en retire un parchemin jauni qu'il vous " "remet." msgid "Superior Scroll of Knowledge" msgstr "Le parchemin supérieur de la Connaissance" msgid "" "You come across the remains of an ancient Druid. Bones, yellowed with age, " "peer from the ragged folds of her robe. Searching the robe, you discover a " "scroll hidden in the folds." msgstr "" "Vous tombez sur les restes d'un ancien druide. Des os, jaunis par l'âge, " "émergent des plis de sa robe. En la fouillant, vous découvrez un parchemin " "caché." msgid "Foremost Scroll of Knowledge" msgstr "Le parchemin de Connaissance ultime" msgid "" "Mangled bones, yellowed with age, peer from the ragged folds of a dead " "Druid's robe. Searching the robe, you discover a scroll hidden within." msgstr "" "Des ossements abîmés, jaunis par l'âge, émergent des plis de la robe en " "lambeaux d'un druide mort. En fouillant la robe, vous découvrez un parchemin " "caché." msgid "Endless Sack of Gold" msgstr "La besace d'or sans fin" msgid "The %{name} provides the hero with %{count} gold per day." msgstr "%{name} fournit au héros %{count} pièces d'or par jour." msgid "" "A little leprechaun dances gleefully around a magic sack. Seeing you " "approach, he stops in mid-stride. The little man screams and stamps his foot " "ferociously, vanishing into thin air. Remembering the old leprechaun saying " "'Finders Keepers', you grab the sack and leave." msgstr "" "Un petit lutin danse gaiement autour d'un sac magique. En vous voyant " "approcher, il s'arrête soudainement. Le petit homme crie et tape du pied " "férocement, puis disparaît magiquement. Vous vous souvenez alors du vieux " "dicton des lutins 'Qui trouve, garde !'. Vous prenez le sac et vous partez." msgid "Endless Bag of Gold" msgstr "Le sac d'or sans fin" msgid "" "A noblewoman, separated from her traveling companions, asks for your help. " "After escorting her home, she rewards you with a bag filled with gold." msgstr "" "Une noble séparée de ses compagnons de voyage, demande votre aide. Après " "l'avoir raccompagnée chez elle, elle vous récompense en vous donnant un sac " "rempli d'or." msgid "Endless Purse of Gold" msgstr "La bourse d'or sans fin" msgid "" "In your travels, you find a leather purse filled with gold that once " "belonged to a great warrior king who had the ability to transform any " "inanimate object into gold." msgstr "" "Au cours de votre voyage, vous trouvez une bourse en cuir remplie d'or qui " "appartenait autrefois à un grand roi guerrier qui avait la capacité de " "transformer tout objet inanimé en or." msgid "Nomad Boots of Mobility" msgstr "Les bottes de nomade" msgid "The %{name} increase the hero's movement on land." msgstr "%{name} augmentent le potentiel de déplacement d'un héros sur terre." msgid "" "A Nomad trader seeks protection from a tribe of Goblins. For your " "assistance, he gives you a finely crafted pair of boots made from the " "softest leather. Looking closely, you see fascinating ancient carvings " "engraved on the leather." msgstr "" "Un commerçant nomade cherche à se protéger d'une tribu de gobelins. Pour " "avoir offert votre aide, il vous donne une paire de bottes finement " "confectionnées en cuir très souple. En regardant de plus près, vous " "remarquez de fascinantes gravures creusées à même le cuir." msgid "Traveler's Boots of Mobility" msgstr "Les bottes de sept lieues" msgid "" "Discovering a pair of beautifully beaded boots made from the finest and " "softest leather, you thank the anonymous donor and add the boots to your " "inventory." msgstr "" "En découvrant une paire de bottes ornées de perles et fabriquées dans un " "cuir de grande qualité, vous remerciez le donateur anonyme et ajoutez les " "bottes à votre inventaire." msgid "Lucky Rabbit's Foot" msgstr "La patte de lapin porte-bonheur" msgid "The %{name} increases the luck of the hero's army by %{count}." msgstr "%{name} augmente la chance de l'armée du héros en combat de %{count}." msgid "" "A traveling merchant offers you a rabbit's foot, made of gleaming silver " "fur, for safe passage. The merchant explains the charm will increase your " "luck in combat." msgstr "" "Un marchand ambulant vous offre une patte de lapin à la fourrure argentée " "étincelante en échange d'un passage sûr. Le marchand vous explique que ce " "charme augmentera votre chance au combat." msgid "Golden Horseshoe" msgstr "Le fer à cheval en or" msgid "" "An ensnared Unicorn whinnies in fright. Murmuring soothing words, you set " "her free. Snorting and stamping her front hoof once, she gallops off. " "Looking down you see a golden horseshoe." msgstr "" "Une licorne effrayée est prisonnière. Vous lui murmurez des mots apaisants " "tout en la libérant. Elle hennit soudainement, frappe du sabot devant elle, " "puis part au galop. En regardant au sol, vous trouvez un fer à cheval doré." msgid "Gambler's Lucky Coin" msgstr "La pièce porte-bonheur" msgid "" "You have captured a mischievous imp who has been terrorizing the region. In " "exchange for his release, he rewards you with a magical coin." msgstr "" "Vous avez capturé un diablotin espiègle qui terrorisait la région. En " "échange de sa libération, il vous récompense avec une pièce magique." msgid "Four-Leaf Clover" msgstr "Le trèfle à quatre feuilles" msgid "" "In the middle of a patch of dead and dry vegetation, to your surprise you " "find a healthy green four-leaf clover." msgstr "" "Au milieu d'une parcelle de végétation morte et sèche, vous êtes surpris de " "trouver un trèfle à quatre feuilles vert en très bon état." msgid "True Compass of Mobility" msgstr "La boussole de mobilité" msgid "The %{name} increases the hero's movement on land and sea." msgstr "" "%{name} augmente le potentiel de déplacement d'un héros sur terre et en mer." msgid "" "An old man claiming to be an inventor asks you to try his latest invention. " "He then hands you a compass." msgstr "" "Un vieil homme prétendant être un inventeur vous demande d'essayer sa " "dernière invention. Il vous tend alors une boussole." msgid "Sailor's Astrolabe of Mobility" msgstr "Le sextant du marin" msgid "The %{name} increases the hero's movement on sea." msgstr "%{name} augmente le potentiel de déplacement d'un héros en mer." msgid "" "An old sea captain is being tortured by Ogres. You save him, and in return " "he rewards you with a wondrous instrument to measure the distance of a star." msgstr "" "Un vieux capitaine marin est torturé par des ogres. Vous le sauvez, et en " "retour il vous récompense avec un merveilleux instrument pour mesurer la " "distance d'une étoile." msgid "Evil Eye" msgstr "L'oeil du diable" msgid "The %{name} reduces the casting cost of curse spells by half." msgstr "" "%{name} réduit de moitié le coût d'incantation des sorts de malédiction." msgid "" "While venturing into a decrepit hut you find the Skeleton of a long dead " "witch. Investigation of the remains reveals a glass eye rolling around " "inside an empty skull." msgstr "" "En vous aventurant dans une cabane décrépite, vous trouvez le squelette " "d'une sorcière morte depuis longtemps. Une recherche sur la dépouille révèle " "un Å“il de verre roulant à l'intérieur d'un crâne vide." msgid "Enchanted Hourglass" msgstr "Le sablier magique" msgid "" "The %{name} extends the duration of all the hero's spells by %{count} turns." msgstr "" "%{name} prolonge la durée de tous les sorts du héros de %{count} tours." msgid "" "A surprise turn in the landscape finds you in the midst of a grisly scene: " "Vultures picking at the aftermath of a terrible battle. Your cursory search " "of the remains turns up an enchanted hourglass." msgstr "" "Vous vous retrouvez au milieu d'une scène macabre : des vautours picorant " "les restes d'une terrible bataille. Votre fouille rapide des corps révèle un " "sablier enchanté." msgid "Gold Watch" msgstr "La montre en or" msgid "The %{name} doubles the effectiveness of the hero's hypnotize spells." msgstr "%{name} double l'efficacité des sorts d'hypnose du héros." msgid "" "In reward for helping his cart out of a ditch, a traveling potion salesman " "gives you a \"magic\" gold watch. Unbeknownst to him, the watch really is " "magical." msgstr "" "En récompense de l'avoir aidé à sortir son chariot du fossé, un vendeur de " "potions ambulant vous offre une montre en or 'magique'. Sans qu'il le sache, " "il se trouve que la montre est vraiment magique." msgid "Skullcap" msgstr "La calotte" msgid "The %{name} halves the casting cost of all mind influencing spells." msgstr "" "%{name} réduit de moitié le coût de tous les sorts influençant l'esprit." msgid "" "A brief stop at an improbable rural inn yields an exchange of money, tales, " "and accidentally, luggage. You find a magical skullcap in your new backpack." msgstr "" "Un bref arrêt dans une improbable auberge donne lieu à un échange d'argent, " "de contes et accidentellement de bagages. Vous trouvez une calotte magique " "dans votre nouveau sac de voyage." msgid "Ice Cloak" msgstr "Le manteau de glace" msgid "" "The %{name} halves all damage the hero's troops receive from cold spells." msgstr "" "%{name} réduit de moitié tous les dégâts de froid subis par les troupes du " "héros." msgid "" "Responding to the panicked cries of a damsel in distress, you discover a " "young woman fleeing from a hungry bear. You slay the beast in the nick of " "time, and the grateful Sorceress weaves a magic cloak from the bear's hide." msgstr "" "Répondant aux cris paniqués d'une demoiselle en détresse, vous découvrez une " "jeune femme fuyant un ours affamé. Vous tuez la bête à temps, et la sorcière " "reconnaissante tisse un manteau magique à partir de la peau de l'ours." msgid "Fire Cloak" msgstr "Le manteau de feu" msgid "" "The %{name} halves all damage the hero's troops receive from fire spells." msgstr "" "%{name} réduit de moitié tous les dégâts de feu subis par les troupes du " "héros." msgid "" "You've come upon a fight between a Necromancer and a Paladin. The " "Necromancer blasts the Paladin with a fire bolt, bringing him to his knees. " "Acting quickly, you slay the evil one before the final blow. The grateful " "Paladin gives you the fire cloak that saved him." msgstr "" "Vous tombez sur un combat entre un nécromancien et un paladin. Le " "nécromancien frappe le paladin avec un éclair de feu, le mettant à genoux. " "Agissant rapidement, vous tuez le nécromancien juste avant le coup fatal. " "Reconnaissant, le paladin vous donne le manteau de feu qui l'a sauvé." msgid "Lightning Helm" msgstr "Le casque paratonnerre" msgid "" "The %{name} halves all damage the hero's troops receive from lightning " "spells." msgstr "" "%{name} réduit de moitié tous les dégâts de foudre subis par les troupes du " "héros." msgid "" "A traveling tinker in need of supplies offers you a helm with a thunderbolt " "design on its top in exchange for food and water. Curious, you accept, and " "later find out that the helm is magical." msgstr "" "Un rétameur itinérant à la recherche de provisions vous offre un casque qui " "porte le motif d'un éclair sur le dessus, en échange de nourriture et d'eau. " "Curieux, vous acceptez, et découvrez plus tard que le casque est magique." msgid "Evercold Icicle" msgstr "Le morceau de glace éternel" msgid "" "The %{name} causes the hero's cold spells to do %{count} percent more damage " "to enemy troops." msgstr "" "%{name} permet aux sorts de froid du héros d'infliger %{count} pour cent de " "dégâts supplémentaires aux troupes ennemies." msgid "" "An icicle withstanding the full heat of the noonday sun attracts your " "attention. Intrigued, you break it off, and find that it does not melt in " "your hand." msgstr "" "Un morceau de glace qui résiste à la forte chaleur du soleil de midi attire " "votre attention. Intrigué, vous le détachez, et vous vous rendez compte " "qu'il ne fond pas dans votre main." msgid "Everhot Lava Rock" msgstr "La pierre de lave brûlante" msgid "" "The %{name} causes the hero's fire spells to do %{count} percent more damage " "to enemy troops." msgstr "" "%{name} permet aux sorts de feu du héros d'infliger %{count} pour cent de " "dégâts supplémentaires aux troupes ennemies." msgid "" "Your wanderings bring you into contact with a tribe of ape-like beings using " "a magical lava rock that never cools to light their fires. You take pity on " "them and teach them to make fire with sticks. Believing you to be a god, the " "apes give you their rock." msgstr "" "Vos pérégrinations vous mettent en contact avec une tribu d'êtres " "ressemblant à des singes, qui utilisent un rocher de lave magique qui ne " "refroidit jamais pour allumer leurs feux. Vous les prenez en pitié et leur " "apprenez à faire du feu avec des bouts de bois. Vous prenant pour un dieu, " "les singes vous offrent leur rocher." msgid "Lightning Rod" msgstr "La baguette éclair" msgid "" "The %{name} causes the hero's lightning spells to do %{count} percent more " "damage to enemy troops." msgstr "" "%{name} permet aux sorts de foudre du héros d'infliger %{count} pour cent de " "dégâts supplémentaires aux troupes ennemies." msgid "" "While waiting out a storm, a lighting bolt strikes a nearby cottage's " "lightning rod, which melts and falls to the ground. The tip of the rod, " "however, survives intact and makes your hair stand on end when you touch it. " "Hmm..." msgstr "" "En attendant la fin d'un orage, un éclair frappe le paratonnerre d'une " "chaumière voisine, qui fond alors et tombe au sol. L'extrémité du " "paratonnerre est intact mais vos cheveux se hérissent lorsque vous le " "touchez. Intéressant..." msgid "Snake-Ring" msgstr "L'anneau-serpent" msgid "The %{name} halves the casting cost of all of the hero's bless spells." msgstr "" "%{name} réduit de moitié le coût de tous les sorts de bénédiction du héros." msgid "" "You've found an oddly shaped ring on the finger of a long dead traveler. The " "ring looks like a snake biting its own tail." msgstr "" "Vous avez trouvé une bague étrange sur le doigt d'un voyageur mort depuis " "bien longtemps. L'anneau ressemble à un serpent qui se mord la queue." msgid "Ankh" msgstr "L'ânkh" msgid "" "The %{name} doubles the effectiveness of all of the hero's resurrect and " "animate spells." msgstr "" "%{name} double l'efficacité de tous les sorts de résurrection et de " "réanimation du héros." msgid "" "A fierce windstorm reveals the entrance to a buried tomb. Your investigation " "reveals that the tomb has already been looted, but the thieves overlooked an " "ankh on a silver chain in the dark." msgstr "" "Une violente tempête vous révèle l'entrée d'un tombeau enseveli. En faisant " "des recherches vous découvrez que le tombeau a déjà été pillé, mais les " "voleurs n'ont pas remarqué une ânkh accrochée à une chaîne d'argent cachée " "dans l'obscurité." msgid "Book of Elements" msgstr "Le livre des éléments" msgid "" "The %{name} doubles the effectiveness of all of the hero's summoning spells." msgstr "%{name} double l'efficacité des sorts d'invocation du héros." msgid "" "You come across a conjurer who begs to accompany you and your army awhile " "for safety. You agree, and he offers as payment a copy of the book of the " "elements." msgstr "" "Vous rencontrez un invocateur qui vous supplie de l'accompagner vous et " "votre armée pour le protéger du danger. Vous acceptez, et en récompense il " "vous offre une copie du livre des éléments." msgid "Elemental Ring" msgstr "L'anneau des éléments" msgid "The %{name} halves the casting cost of all summoning spells." msgstr "%{name} réduit de moitié le coût de vos sorts d'invocation." msgid "" "While pausing to rest, you notice a bobcat climbing a short tree to get at a " "crow's nest. On impulse, you climb the tree yourself and scare off the cat. " "When you look in the nest, you find a collection of shiny stones and a ring." msgstr "" "Alors que vous vous reposez, vous remarquez un lynx qui grimpe à un arbuste " "pour atteindre un nid de corbeaux. Sur un coup de tête, vous grimpez à votre " "tour à l'arbre et effrayez le félin. Lorsque vous regardez le nid, vous " "découvrez une collection de pierres brillantes et un anneau." msgid "Holy Pendant" msgstr "Le pendentif sacré" msgid "The %{name} makes all of the hero's troops immune to curse spells." msgstr "%{name} immunise les troupes du héros contre les sorts de malédiction." msgid "" "In your wanderings you come across a hermit living in a small, tidy hut. " "Impressed with your mission, he takes time out from his meditations to bless " "and give you a charm against curses." msgstr "" "Au cours de vos pérégrinations, vous croisez un ermite qui vit dans une " "petite cabane bien rangée. Impressionné par votre mission, il sort quelque " "temps de sa méditation pour bénir un charme contre les malédictions, dont il " "vous fait cadeau." msgid "Pendant of Free Will" msgstr "Le pendentif du libre arbitre" msgid "The %{name} makes all of the hero's troops immune to hypnotize spells." msgstr "%{name} immunise les troupes du héros contre les sorts d'hypnose." msgid "" "Responding to cries for help, you find river Sprites making a sport of " "dunking an old man. Feeling vengeful, you rescue the man and drag a Sprite " "onto dry land for awhile. The Sprite, uncomfortable in the air, gives you a " "magic pendant to let him go." msgstr "" "Guidé par des appels à l'aide, vous trouvez des fées de rivière qui " "s'amusent à plonger un vieil homme dans l'eau. Épris de revanche, vous " "sauvez l'homme et placez une fée sur la terre ferme pendant quelque temps. " "La fée, se sentant mal hors de l'eau, vous donne un pendentif magique afin " "de la laisser partir." msgid "Pendant of Life" msgstr "Le pendentif de la vie" msgid "The %{name} makes all of the hero's troops immune to death spells." msgstr "%{name} immunise les troupes du héros contre les sorts de mort." msgid "" "A brief roadside encounter with a small caravan and a game of knucklebones " "wins a magic pendant. Its former owner says that it protects from " "Necromancers' death spells." msgstr "" "Vous gagnez un pendentif magique grâce à une brève rencontre avec une petite " "caravane sur le bord de la route et une victoire au jeu des osselets. Son " "ancien propriétaire vous affirme qu'il protège contre les sorts mortels des " "nécromanciens." msgid "Serenity Pendant" msgstr "Le pendentif de la sérénité" msgid "The %{name} makes all of the hero's troops immune to berserk spells." msgstr "%{name} immunise les troupes du héros contre le sort furie." msgid "" "The sounds of combat draw you to the scene of a fight between an old " "Barbarian and an eight-headed Hydra. Your timely intervention swings the " "battle in favor of the man, and he rewards you with a pendant he used to use " "to calm his mind for battle." msgstr "" "Les échos d'une bataille vous attirent vers les lieux d'un combat entre un " "barbare âgé et une hydre à huit têtes. Votre intervention à point nommé fait " "basculer la bataille à la faveur du vieil homme, et il vous récompense avec " "un pendentif qu'il utilisait afin d'apaiser son esprit lors des combats." msgid "Seeing-eye Pendant" msgstr "Le pendentif de l'oeil qui voit" msgid "The %{name} makes all of the hero's troops immune to blindness spells." msgstr "%{name} immunise les troupes du héros contre les sorts de cécité." msgid "" "You come upon a very old woman, long blind from cataracts and dying alone. " "You tend to her final needs and promise a proper burial. Grateful, she gives " "you a magic pendant emblazoned with a stylized eye. It lets you see with " "your eyes closed." msgstr "" "Vous tombez sur une très vieille dame, aveugle, seule et mourante. Vous vous " "occupez de ses dernières volontés et vous lui promettez un enterrement " "digne. Reconnaissante, elle vous offre un pendentif magique orné du symbole " "d'un oeil. Il vous permet de voir les yeux fermés." msgid "Kinetic Pendant" msgstr "Le pendentif cinétique" msgid "The %{name} makes all of the hero's troops immune to paralyze spells." msgstr "%{name} immunise les troupes du héros contre les sorts de paralysie." msgid "" "You come across a golem wearing a glowing pendant and blocking your way. " "Acting on a hunch, you cut the pendant from its neck. Deprived of its power " "source, the golem breaks down, leaving you with the magical pendant." msgstr "" "Vous tombez sur un golem portant un pendentif brillant qui bloque votre " "route. Sur un pressentiment, vous coupez le pendentif de son cou. Privé de " "sa source de pouvoir, le golem tombe en morceaux, vous laissant son " "pendentif magique." msgid "Pendant of Death" msgstr "Le pendentif de la mort" msgid "The %{name} makes all of the hero's troops immune to holy spells." msgstr "%{name} immunise les troupes du héros contre les sorts divins." msgid "" "A quick and deadly battle with a Necromancer wins you his magical pendant. " "Later, a Wizard tells you that the pendant protects undead under your " "control from holy word spells." msgstr "" "Un combat rapide et mortel contre un nécromancien vous fait gagner son " "pendentif magique. Plus tard, un magicien vous raconte que ce pendentif " "protège les morts-vivants sous votre contrôle contre les sorts divins." msgid "Wand of Negation" msgstr "La baguette de la négation" msgid "" "The %{name} makes all of the hero's troops immune to dispel magic spells." msgstr "" "%{name} immunise les troupes du héros contre les sortilèges de contre-sort." msgid "" "You meet an old Wizard friend of yours traveling in the opposite direction. " "He presents you with a gift: A wand that prevents the use of the dispel " "magic spell on your allies." msgstr "" "Vous rencontrez un vieil ami sorcier qui voyage dans une direction opposée. " "Il vous offre un cadeau : une baguette qui empêche le contre-sort d'affecter " "vos troupes." msgid "Golden Bow" msgstr "L'arc doré" msgid "" "The %{name} eliminates the %{count} percent penalty for the hero's troops " "shooting past obstacles (e.g. castle walls)." msgstr "" "%{name} élimine la pénalité de %{count} pour cent pour les troupes du héros " "qui tirent au-delà des obstacles (par exemple, les murs d'un château)." msgid "" "A chance meeting with a famous Archer finds you in a game of knucklebones " "pitting his bow against your horse. You win." msgstr "" "Une rencontre fortuite avec un célèbre archer vous entraîne dans un jeu " "d'osselets. Il vous propose de parier son arc contre votre cheval. Vous " "gagnez." msgid "Telescope" msgstr "Le télescope" msgid "" "The %{name} increases the amount of terrain the hero reveals when " "adventuring by %{count} extra square." msgstr "" "%{name} augmente la quantité de terrain que le héros révèle lorsqu'il part à " "l'aventure de %{count} cases supplémentaires." msgid "" "A merchant from far away lands trades you a new invention of his people for " "traveling supplies. It makes distant objects appear closer, and he calls " "it...\n" "\n" "a telescope." msgstr "" "Un marchand venu d'un pays lointain vous échange une nouvelle invention de " "son peuple contre des fournitures de voyage. Étrangement, les objets " "éloignés vous semblent plus proches, il appelle ça...\n" "\n" "un télescope." msgid "Statesman's Quill" msgstr "La plume de l'homme d'état" msgid "" "The %{name} reduces the cost of surrender to %{count} percent of the total " "cost of troops the hero has in their army." msgstr "" "%{name} réduit le coût de la reddition à %{count} pour cent du coût total " "des troupes dans l'armée du héros." msgid "" "You pause to help a diplomat with a broken axle fix his problem. In " "gratitude, he gives you a writing quill with magical properties which he " "says will \"help people see things your way\"." msgstr "" "Vous faites une pause pour aider un diplomate dont l'essieu est cassé. Pour " "montrer sa gratitude, il vous donne une plume qui possède des propriétés " "magiques qui, selon lui, \"Aideront les gens à voir les choses à votre " "façon\"." msgid "Wizard's Hat" msgstr "Le chapeau du sorcier" msgid "" "The %{name} increases the duration of the hero's spells by %{count} turns." msgstr "%{name} augmente la durée des sorts du héros de %{count} tours." msgid "" "You see a Wizard fleeing from a Griffin and riding like the wind. The Wizard " "opens a portal and rides through, getting his hat knocked off by the edge of " "the gate. The Griffin follows; the gate closes. You pick the hat up, dust it " "off, and put it on." msgstr "" "Vous apercevez un sorcier qui se sauve d'un griffon, courant à toute allure. " "Le sorcier ouvre un portail qu'il emprunte, alors que son chapeau tombe à " "terre en heurtant le portail. Le griffon le suit ; enfin le portail se " "referme. Vous ramassez le chapeau, enlevez la poussière, et le déposez sur " "votre tête." msgid "Power Ring" msgstr "L'anneau de Puissance" msgid "The %{name} returns %{count} extra spell points per day to the hero." msgstr "" "%{name} restaure %{count} points de magie supplémentaires par jour au héros." msgid "" "You find a small tree that closely resembles the great Warlock Carnauth with " "a ring around one of its twigs. Scraps of clothing and rotting leather lead " "you to suspect that it IS Carnauth, transformed. Since you can't help him, " "you take the magic ring." msgstr "" "Vous découvrez un arbuste qui ressemble fort au grand sorcier Carnauth, avec " "un anneau autour d'une petite branche. Des bouts de vêtements et de cuir " "pourrissant vous amènent à penser qu'il s'AGIT de Carnauth, transformé. " "Puisque vous ne pouvez pas l'aider, vous vous emparez de l'anneau magique." msgid "Ammo Cart" msgstr "La charrette de munitions" msgid "" "The %{name} provides endless ammunition for all of the hero's troops that " "shoot." msgstr "" "%{name} fournit des munitions illimités pour toutes les troupes qui tirent " "dans l'armée du héros." msgid "" "An ammunition cart in the middle of an old battlefield catches your eye. " "Inspection shows it to be in good working order, so you take it along." msgstr "" "Une charrette de munitions au milieu d'un vieux champ de bataille attire " "votre regard. En l'examinant, les munitions ont l'air opérationnelles, vous " "l'emportez donc avec vous." msgid "Tax Lien" msgstr "Le privilège fiscal" msgid "The %{name} costs the hero %{count} gold pieces per day." msgstr "%{name} coûte au héros %{count} pièces d'or par jour." msgid "" "Your big spending habits have earned you a massive tax bill that you can't " "hope to pay. The tax man takes pity and agrees to only take 250 gold a day " "from your account for life. Check here if you want one dollar to go to the " "presidential campaign election fund." msgstr "" "Vos habitudes particulièrement dépensières ont engendré une taxe importante " "que vous ne pouvez pas espérer régler. Le collecteur de taxes vous prend en " "pitié et consent à vous prélever de seulement 250 pièces d'or par jour " "jusqu'au restant de votre vie. Cochez cette case si vous voulez qu'un euro " "aille au financement de la campagne pour l'élection présidentielle." msgid "Hideous Mask" msgstr "Le masque hideux" msgid "The %{name} prevents all 'wandering' armies from joining the hero." msgstr "%{name} empêche toutes les armées 'errantes' de rallier le héros." msgid "" "Your looting of the grave of Sinfilas Gardolad, the famous shapeshifting " "Warlock, unearths his fabled mask. Trembling, you put it on and it twists " "your visage into an awful grimace! Oh no! It's actually the hideous mask of " "Gromluck Greene, and you are stuck with it." msgstr "" "En pillant la tombe de Sinfilas Gardolad, le fameux sorcier changeforme, " "vous exhumez son masque légendaire. En tremblant, vous mettez son masque et " "votre visage se change en une affreuse grimace ! Oh, non ! Il s'agit en fait " "du masque hideux de Gromluck Greene, et il reste collé à votre visage." msgid "Endless Pouch of Sulfur" msgstr "Le sac de soufre sans fin" msgid "The %{name} provides %{count} unit of sulfur per day." msgstr "%{name} fournit %{count} morceau de soufre par jour." msgid "" "You visit an alchemist who, upon seeing your army, is swayed by the " "righteousness of your cause. The newly loyal subject gives you his endless " "pouch of sulfur to help with the war effort." msgstr "" "Vous rendez visite à un alchimiste qui, en voyant votre armée, est " "impressionné par la justesse de votre cause. Votre nouveau sujet loyal vous " "donne son sac de source infinie de soufre pour aider en tant qu'effort de " "guerre." msgid "Endless Vial of Mercury" msgstr "La fiole de mercure sans fin" msgid "The %{name} provides %{count} unit of mercury per day." msgstr "%{name} fournit %{count} fiole de mercure par jour." msgid "" "A brief stop at a hastily abandoned Wizard's tower turns up a magical vial " "of mercury that always has a little left on the bottom. Recognizing a " "treasure when you see one, you cap it and slip it in your pocket." msgstr "" "Un arrêt bref dans une tour de sorcier abandonnée à la hâte fait refaire " "surface une fiole magique de mercure qui n'est jamais totalement vide. " "Sachant reconnaître d'un coup d'oeil un trésor, vous recouvrez la fiole d'un " "bouchon et la fourrez dans votre poche." msgid "Endless Pouch of Gems" msgstr "La bourse de gemmes sans fin" msgid "The %{name} provides %{count} unit of gems per day." msgstr "%{name} fournit %{count} gemmes par jour." msgid "" "A short rainstorm brings forth a rainbow...and you can see the end of it. " "Riding quickly, you seize the pot of gold you find there. The leprechaun who " "owns it, unable to stop you from taking it, offers an endless pouch of gems " "for the return of his gold. You accept." msgstr "" "Une courte averse orageuse laisse place à un arc-en-ciel... et vous pouvez " "voir son extrémité. Allant à vive allure, vous vous emparez de la marmite " "d'or qui s'y trouve. Le leprechaun à qui elle appartient, incapable de vous " "empêcher de la prendre, vous offre une bourse infinie de gemmes en retour de " "son or. Vous acceptez." msgid "Endless Cord of Wood" msgstr "Le tas de bois sans fin" msgid "The %{name} provides %{count} unit of wood per day." msgstr "%{name} fournit %{count} bûche de bois par jour." msgid "" "Pausing to rest and light a cook fire, you pull wood out of a nearby pile of " "dead wood. As you keep pulling wood from the pile, you notice that it " "doesn't shrink. You realize to your delight that the wood is enchanted, so " "you take it along." msgstr "" "En vous arrêtant pour vous reposer et allumer un feu de cuisine, vous prenez " "des branches à partir d'un tas de bois mort à proximité. Alors que vous " "continuez à retirer du bois du tas, vous vous rendez compte qu'il ne diminue " "pas de volume. Vous réalisez à votre grande joie que le bois est enchanté, " "vous l'emportez donc avec vous." msgid "Endless Cart of Ore" msgstr "Le chariot de fer sans fin" msgid "The %{name} provides %{count} unit of ore per day." msgstr "%{name} fournit %{count} morceau de fer par jour." msgid "" "You've found a Goblin weapon smithy making weapons for use against humans. " "With a tremendous yell you and your army descend upon their camp and drive " "them away. A search finds a magic ore cart that never runs out of iron." msgstr "" "Vous avez trouvé une forge de gobelins qui façonne des armes contre les " "humains. Avec d'immenses cris, vous et votre armée effectuent un assaut " "contre leur base, les délogeant. En explorant, vous trouvez un chariot " "magique qui ne se vide jamais, rempli de fer." msgid "Endless Pouch of Crystal" msgstr "La bourse de cristal sans fin" msgid "The %{name} provides %{count} unit of crystal per day." msgstr "%{name} fournit %{count} cristal par jour." msgid "" "Taking shelter from a storm in a small cave, you notice a small patch of " "crystal in one corner. Curious, you break a piece off and notice that the " "original crystal grows the lost piece back. You decide to stuff the entire " "patch into a pouch and take it with you." msgstr "" "En vous abritant d'un orage dans une petite caverne, vous remarquez dans un " "coin un petit amas de cristal. Curieux, vous en détachez un morceau et vous " "vous rendez compte que le morceau a repoussé chez le cristal d'origine. Vous " "décidez de fourrer l'amas entier dans une bourse et l'emportez avec vous." msgid "Spiked Helm" msgstr "Le casque à pointes" msgid "" "Your army is ambushed by a small tribe of wild (and none too bright) Orcs. " "You fend them off easily and the survivors flee in all directions. One of " "the Orcs was wearing a polished spiked helm. Figuring it will make a good " "souvenir, you take it." msgstr "" "Votre armée est prise en embuscade par une petite tribu d'orcs sauvages (et " "pas très fûtés). Vous les repoussez facilement et les survivants s'enfuient " "dans toutes les directions. Un des orcs portait un casque à pointes poli. En " "pensant qu'il fera un bon souvenir, vous l'emportez." msgid "Spiked Shield" msgstr "Le bouclier à pointes" msgid "" "You come upon a bridge spanning a dry gully. Before you can cross, a Troll " "steps out from under the bridge and demands payment before it will permit " "you to pass. You refuse, and the Troll charges, forcing you to slay it. You " "take its spiked shield as a trophy." msgstr "" "Vous tombez sur un pont qui passe au-dessus d'une ravine asséchée. Avant de " "poser un pied dessus, un troll sort d'en-dessous le pont et exige un " "paiement afin de vous laisser passer. Vous refusez et le troll charge alors, " "vous forçant à l'abbatre. Vous emportez son bouclier à pointes en guise de " "trophée." msgid "White Pearl" msgstr "La perle blanche" msgid "" "A walk across a dry saltwater lake bed yields an unlikely prize: A white " "pearl amidst shattered shells and debris." msgstr "" "Une promenade dans le bassin d'un lac salé asséché vous a permis de trouver " "un objet improbable : une perle blanche parmi des coquillages brisés et des " "débris." msgid "Black Pearl" msgstr "La perle noire" msgid "" "Rumors of a Griffin of unusual size preying upon the countryside lead you to " "its cave lair. A quick, brutal fight dispatches the beast, and a search of " "its foul nest turns up a huge black pearl." msgstr "" "Les rumeurs à propos d'un griffon de taille inhabituelle s'attaquant à ces " "terres vous conduisent à son repaire. Un combat rapide et brutal permet de " "vous débarrasser de la bête, et une fouille de son nid fétide vous permet de " "trouver une énorme perle noire." msgid "Magic Book" msgstr "Le grimoire" msgid "The %{name} enables the hero to cast spells." msgstr "%{name} permet au héros de lancer des sorts." msgid "Dummy 1" msgstr "Emplacement 1" msgid "The reserved artifact." msgstr "L'artéfact réservé." msgid "Dummy 2" msgstr "Emplacement 2" msgid "Dummy 3" msgstr "Emplacement 3" msgid "Dummy 4" msgstr "Emplacement 4" msgid "Spell Scroll" msgstr "Parchemin magique" msgid "" "This %{name} gives the hero the ability to cast the %{spell} spell if the " "hero has a Magic Book." msgstr "" "Ce %{name} donne au héros la capacité de lancer le sort %{spell} si celui-ci " "possède un grimoire." msgid "" "You find an elaborate container which houses an old vellum scroll. The runes " "on the container are very old, and the artistry with which it was put " "together is stunning. As you pull the scroll out, you feel imbued with " "magical power." msgstr "" "Vous trouvez un récipient élaboré qui contient un vieux parchemin en vélin. " "Les runes sur le récipient sont très anciennes, et l'art avec lequel il a " "été assemblé est stupéfiant. Lorsque vous sortez le parchemin, vous vous " "sentez imprégné d'un pouvoir magique." msgid "Arm of the Martyr" msgstr "Le bras du martyre" msgid "" "The %{name} increases the hero's spell power by %{count} but adds the undead " "morale penalty." msgstr "" "%{name} augmente la Puissance des sorts du héros de %{count} mais ajoute la " "pénalité de moral des morts-vivants." msgid "" "One of the less intelligent members of your party picks up an arm off of the " "ground. Despite its missing a body, it is still moving. Your troops find the " "dismembered arm repulsive, but you cannot bring yourself to drop it: it " "seems to hold some sort of magical power that influences your decision " "making." msgstr "" "L'un des membres les moins intelligents de votre troupe ramasse un bras par " "terre. Malgré l'absence d'un corps, il est toujours en mouvement. Vos " "troupes trouvent le bras démembré répugnant, mais vous ne pouvez pas vous " "résoudre à le laisser tomber : il semble détenir une sorte de pouvoir " "magique qui influence votre prise de décision." msgid "Breastplate of Anduran" msgstr "Le plastron d'Anduran" msgid "The %{name} increases the hero's defense by %{count}." msgstr "%{name} augmente la Défense du héros de %{count}." msgid "" "You come upon a sign. It reads: \"Here lies the body of Anduran. Bow and " "swear fealty, and you shall be rewarded.\" You decide to do as it says. As " "you stand up, you feel a coldness against your skin. Looking down, you find " "that you are suddenly wearing a gleaming, ornate breastplate." msgstr "" "Vous tombez sur un panneau. On peut y lire : \"Ici se trouve le corps " "d'Anduran. Inclinez-vous et jurez fidélité, et vous serez récompensés.\" " "Vous décidez de faire ce qu'il dit. En vous relevant, vous ressentez une " "froideur contre votre peau. En regardant vers le bas, vous constatez que " "vous portez soudainement une cuirasse étincelante et ornée." msgid "Broach of Shielding" msgstr "La broche-bouclier" msgid "" "The %{name} provides %{count} percent protection from Armageddon and " "Elemental Storm, but decreases spell power by 2." msgstr "" "%{name} fournit %{count} pour cent de protection contre Armageddon et Orage, " "mais diminue la Puissance de 2." msgid "" "A kindly Sorceress thinks that your army's defenses could use a magical " "boost. She offers to enchant the Broach that you wear on your cloak, and you " "accept." msgstr "" "Une sorcière bienveillante pense que les défenses de votre armée pourraient " "utiliser un coup de pouce magique. Elle propose d'enchanter la broche que " "vous portez sur votre manteau, et vous acceptez." msgid "Battle Garb of Anduran" msgstr "La cuirasse d'Anduran" msgid "" "The %{name} combines the powers of the three Anduran artifacts. It provides " "maximum luck and morale for the hero's troops and gives the hero the Town " "Portal spell." msgstr "" "%{name} combine les pouvoirs des trois artéfacts d'Anduran. Il offre un " "maximum de chance et de moral aux troupes du héros et lui donne le sortilège " "Portes du village." msgid "" "Out of pity for a poor peasant, you purchase a chest of old junk they are " "hawking for too much gold. Later, as you search through it, you find it " "contains the 3 pieces of the legendary battle garb of Anduran!" msgstr "" "Par pitié pour un pauvre paysan, vous achetez hors de prix un coffre de " "vieilles ordures qu'il colporte. Plus tard, en fouillant dedans, vous " "constatez qu'il contient les 3 pièces du légendaire costume de bataille " "d'Anduran !" msgid "Crystal Ball" msgstr "La boule de cristal" msgid "" "The %{name} lets the hero get more specific information about monsters, " "enemy heroes, and castles nearby the hero." msgstr "" "%{name} permet au héros d'obtenir des informations plus spécifiques sur les " "monstres, les héros ennemis et les châteaux à proximité du héros." msgid "" "You come upon a caravan of gypsies who are feasting and fortifying their " "bodies with mead. They call you forward and say \"If you prove that you can " "dance the Rama-Buta, we will reward you.\" You don't know it, but try " "anyway. They laugh hysterically, but admire your bravery, giving you a " "Crystal Ball." msgstr "" "Vous tombez sur une caravane de gitans qui se régalent et fortifient leurs " "corps avec de l'hydromel. Ils vous appellent vers l'avant et disent : \"Si " "vous prouvez que vous pouvez danser le Rama-Buta, nous vous récompenserons." "\" Vous ne le savez pas, mais essayez quand même. Ils rient hystériquement, " "mais admirent votre bravoure, vous donnant une boule de cristal." msgid "Heart of Fire" msgstr "Le coeur de feu" msgid "" "The %{name} provides %{count} percent protection from fire, but doubles the " "damage taken from cold." msgstr "" "%{name} offre une protection de %{count} pour cent contre le feu, mais " "double les dégâts de froid infligés à vos troupes." msgid "" "You enter a recently burned glade and come upon a Fire Elemental sitting " "atop a rock. It looks up, its flaming face contorted in a look of severe " "pain. It then tosses a glowing object at you. You put up your hands to block " "it, but it passes right through them and sears itself into your chest." msgstr "" "Vous entrez dans une clairière récemment brûlée et tombez sur un élémentaire " "de feu assis au sommet d'un rocher. Il lève les yeux au ciel, son visage " "flamboyant tordu montre un regard de douleur intense. Il vous lance alors un " "objet lumineux. Vous levez les mains pour le bloquer, mais il passe à " "travers elles et se loge dans votre poitrine." msgid "Heart of Ice" msgstr "Le coeur de glace" msgid "" "The %{name} provides %{count} percent protection from cold, but doubles the " "damage taken from fire." msgstr "" "%{name} offre une protection de %{count} pour cent contre le froid, mais " "double les dégâts de feu infligés à vos troupes." msgid "" "Suddenly, a biting coldness engulfs your body. You seize up, falling from " "your horse. The pain subsides, but you still feel as if your chest is " "frozen. As you pick yourself up off of the ground, you hear hearty laughter. " "You turn around just in time to see a Frost Giant run off into the woods and " "disappear." msgstr "" "Soudain, un froid mordant envahit votre corps. Sous la surprise glaciale, " "vous tombez de votre cheval. La douleur s'atténue, mais vous avez toujours " "l'impression que votre poitrine est gelée. Lorsque vous vous relevez, vous " "entendez rire de bon coeur. Vous vous retournez juste à temps pour voir un " "géant de glace s'enfuir et disparaître dans les bois." msgid "Helmet of Anduran" msgstr "Le casque d'Anduran" msgid "" "You spy a gleaming object poking up out of the ground. You send a member of " "your party over to investigate. He comes back with a golden helmet in his " "hands. You realize that it must be the helmet of the legendary Anduran, the " "only man who was known to wear solid gold armor." msgstr "" "Vous scrutez un objet étincelant qui sort du sol. Vous envoyez un membre de " "votre groupe pour enquêter. Il revient avec un casque d'or à la main. Vous " "vous rendez compte que ce doit être le casque du légendaire d'Anduran, le " "seul homme qui était connu pour porter une armure en or massif." msgid "Holy Hammer" msgstr "Le marteau saint" msgid "" "You come upon a battle where a Paladin has been mortally wounded by a group " "of Zombies. He asks you to take his hammer and finish what he started. As " "you pick it up, it begins to hum, and then everything becomes a blur. The " "Zombies lie dead, the hammer dripping with blood. You strap it to your belt." msgstr "" "Vous tombez sur une bataille où un paladin a été mortellement blessé par un " "groupe de zombies. Il vous demande de prendre son marteau et de terminer ce " "qu'il a commencé. Lorsque vous le ramassez, un bourdonnement s'entend, puis " "tout devient flou. Les zombies gisent sur le sol, le marteau dégouline de " "sang. Vous l'attachez à votre ceinture." msgid "Legendary Scepter" msgstr "Le sceptre légendaire" msgid "The %{name} adds %{count} points to all attributes." msgstr "%{name} ajoute %{count} points à tous les attributs." msgid "" "Upon cresting a small hill, you come upon a ridiculous looking sight. A " "Sprite is attempting to carry a Scepter that is almost as big as it is. " "Trying not to laugh, you ask, \"Need help?\" The Sprite glares at you and " "answers: \"You think this is funny? Fine. You can carry it. I much prefer " "flying anyway.\"" msgstr "" "En culminant une petite colline, vous tombez sur un spectacle ridicule. Une " "fée tente de transporter un sceptre presque aussi grand qu'elle. En essayant " "de ne pas rire, vous demandez : \"Besoin d'aide ?\" La fée vous regarde et " "répond : \"Vous trouvez ça drôle ? Bien. Vous pouvez le prendre. Je préfère " "voler de toute façon.\"" msgid "" "An old seaman tells you a tale of an enchanted masthead that he used in his " "youth to rally his crew during times of trouble. He then hands you a faded " "map that shows where he hid it. After much exploring, you find it stashed " "underneath a nearby dock." msgstr "" "Un vieux marin vous raconte l'histoire d'une tête de mât enchantée qu'il a " "utilisée dans sa jeunesse pour rallier son équipage pendant les moments " "difficiles. Il vous remet ensuite une carte fanée qui montre où il l'a " "cachée. Après beaucoup d'exploration, vous le trouvez planqué sous un quai à " "proximité." msgid "Masthead" msgstr "La tête de mât" msgid "" "The %{name} boosts the hero's troops' luck and morale by %{count} each in " "sea combat." msgstr "" "%{name} augmente la chance et le moral de l'armée du héros de %{count} " "points chacun lors des batailles en mer." msgid "Sphere of Negation" msgstr "La sphère de négation" msgid "The %{name} disables all spell casting, for both sides, in combat." msgstr "%{name} désactive tout lancer de sorts, pour chaque camp, au combat." msgid "" "You stop to help a Peasant catch a runaway mare. To show his gratitude, he " "hands you a tiny sphere. As soon as you grasp it, you feel the magical " "energy drain from your limbs..." msgstr "" "Vous vous arrêtez pour aider un paysan à attraper une jument fugueuse. Pour " "montrer sa gratitude, il vous tend une minuscule sphère. Dès que vous la " "saisissez, vous sentez l'énergie magique s'écouler de vos membres..." msgid "Staff of Wizardry" msgstr "Le bâton de la sorcellerie" msgid "The %{name} boosts the hero's spell power by %{count}." msgstr "%{name} augmente la Puissance des sorts du héros de %{count}." msgid "" "While out scaring up game, your troops find a mysterious staff levitating " "about three feet off of the ground. They hand it to you, and you notice an " "inscription. It reads: \"Brains best brawn and magic beats might. Heed my " "words, and you'll win every fight.\"" msgstr "" "Vos troupes trouvent un mystérieux bâton en lévitation à un mètre au dessus " "du sol. Ils vous le remettent et vous remarquez une inscription. On peut y " "lire : \"Cerveau plutôt que muscle, magie plutôt que force. Faites vôtres " "ces paroles, et vous gagnerez tous les combats.\"" msgid "Sword Breaker" msgstr "Le briseur d'épée" msgid "The %{name} increases the hero's defense by %{count} and attack by 1." msgstr "%{name} augmente la défense du héros de %{count} et l'attaque de 1." msgid "" "A former Captain of the Guard admires your quest and gives you the enchanted " "Sword Breaker that he relied on during his tour of duty." msgstr "" "Un ancien capitaine de la garde admire votre quête et vous donne le briseur " "d'épée enchanté sur lequel il s'appuyait pendant ses tours de service." msgid "Sword of Anduran" msgstr "L'épée d'Anduran" msgid "" "A Troll stops you and says: \"Pay me 5,000 gold, or the Sword of Anduran " "will slay you where you stand.\" You refuse. The troll grabs the sword " "hanging from its belt, screams in pain, and runs away. Picking up the fabled " "sword, you give thanks that half-witted Trolls tend to grab the wrong end of " "sharp objects." msgstr "" "Un troll vous arrête et dit : \"Payez-moi 5000 or, ou l'épée d'Anduran vous " "tuera où vous vous tenez.\" Vous refusez. Le troll attrape l'épée suspendue " "à sa ceinture, crie de douleur et s'enfuit. Vous ramassez l'épée " "légendaire ; une chance que ces crétins de Trolls saisissent parfois les " "objets tranchants par le mauvais bout." msgid "Spade of Necromancy" msgstr "La pelle du fossoyeur" msgid "The %{name} gives the hero increased necromancy skill." msgstr "%{name} améliore la compétence de nécromancie du héros." msgid "" "A dirty shovel has been thrust into a dirt mound nearby. Upon investigation, " "you discover it to be the enchanted shovel of the Gravediggers, long thought " "lost by mortals." msgstr "" "Une pelle sale a été enfoncée dans un monticule de terre à proximité. Après " "enquête, vous découvrez qu'il s'agit de la pelle enchantée du fossoyeur, " "longtemps pensée perdue par les mortels." msgid "spellBonus|selected by user" msgstr "choisi par le joueur" msgid "Wood" msgstr "Bois" msgid "Mercury" msgstr "Mercure" msgid "Ore" msgstr "Fer" msgid "Sulfur" msgstr "Soufre" msgid "Crystal" msgstr "Cristal" msgid "Gems" msgstr "Gemmes" msgid "Gold" msgstr "Or" msgid "" "There are seven resources in Heroes 2, used to build and improves castles, " "purchase troops and recruit heroes. Gold is the most common, required for " "virtually everything. Wood and ore are used for most buildings. Gems, " "Mercury, Sulfur and Crystal are rare magical resources used for the most " "powerful creatures and buildings." msgstr "" "Il existe sept ressources différentes dans Heroes 2, utiles pour construire " "et améliorer les châteaux, acheter des troupes et recruter des héros. L'or " "est la ressource la plus courante, nécessaire pour pratiquement tout. Le " "bois et le fer sont utilisés pour la plupart des bâtiments. Les gemmes, le " "mercure, le soufre et le cristal sont des ressources magiques rares " "utilisées pour les créatures et les bâtiments les plus puissants." msgid "" "Causes a giant fireball to strike the selected area, damaging all nearby " "creatures." msgstr "" "Provoque une boule de feu géante qui frappe la zone sélectionnée, " "endommageant toutes les créatures proches." msgid "Fireball" msgstr "Boule de feu" msgid "Fireblast" msgstr "Souffle de feu" msgid "" "An improved version of fireball, fireblast affects two hexes around the " "center point of the spell, rather than one." msgstr "" "Version améliorée de boule de feu, le souffle de feu affecte deux hexagones " "autour du point central du sort, au lieu d'un seul." msgid "Causes a bolt of electrical energy to strike the selected creature." msgstr "Provoque un éclair qui frappe la créature sélectionnée." msgid "Lightning Bolt" msgstr "Éclair foudroyant" msgid "Chain Lightning" msgstr "Foudre" msgid "" "Causes a bolt of electrical energy to strike a selected creature, then " "strike the nearest creature with half damage, then strike the NEXT nearest " "creature with half again damage, and so on, until it becomes too weak to be " "harmful. Warning: This spell can hit your own creatures!" msgstr "" "Provoque un éclair qui frappe une créature sélectionnée, puis la créature la " "plus proche en infligeant la moitié des dégâts, puis la créature suivante la " "plus proche infligeant encore la moitié des dégâts, et ainsi de suite, " "jusqu'à ce que l'éclair devienne trop faible pour être nuisible. " "Avertissement : Ce sort peut toucher vos propres créatures !" msgid "Teleport" msgstr "Téléportation" msgid "" "Teleports the creature you select to any open position on the battlefield." msgstr "" "Téléporte la créature que vous sélectionnez vers n'importe quelle place " "libre sur le champ de bataille." msgid "Cure" msgstr "Guérison" msgid "" "Removes all negative spells cast upon one of your units, and restores up to " "%{count} HP per level of spell power." msgstr "" "Annule tous les sorts négatifs lancés sur l'une de vos créatures, et " "restaure jusqu'à %{count} PV par niveau de Puissance." msgid "Mass Cure" msgstr "Grande guérison" msgid "" "Removes all negative spells cast upon your forces, and restores up to " "%{count} HP per level of spell power, per creature." msgstr "" "Supprime tous les sorts négatifs lancés sur vos forces, et restaure jusqu'à " "%{count} de PV par niveau de Puissance, par créature." msgid "Resurrect" msgstr "Résurrection" msgid "Resurrects creatures from a damaged or dead unit until end of combat." msgstr "" "Ressuscite les créatures d'un peloton endommagé ou mort jusqu'à la fin du " "combat." msgid "Resurrect True" msgstr "Ultime résurrection" msgid "Resurrects creatures from a damaged or dead unit permanently." msgstr "" "Ressuscite définitivement les créatures d'un peloton endommagé ou mort." msgid "Haste" msgstr "Célérité" msgid "Increases the speed of any creature by %{count}." msgstr "Augmente la vitesse d'une créature de %{count}." msgid "Increases the speed of all of your creatures by %{count}." msgstr "Augmente la vitesse de toutes vos créatures de %{count}." msgid "Mass Haste" msgstr "Célérité collective" msgid "Slows target to half movement rate." msgstr "Ralentit la cible à la moitié de sa vitesse de déplacement." msgid "spell|Slow" msgstr "Lourdeur" msgid "Mass Slow" msgstr "Lourdeur collective" msgid "Slows all enemies to half movement rate." msgstr "Ralentit tous les ennemis à la moitié de leur vitesse de déplacement." msgid "Clouds the affected creatures' eyes, preventing them from moving." msgstr "Obscurcit les yeux des créatures affectées, les empêchant de bouger." msgid "spell|Blind" msgstr "Aveugler" msgid "Bless" msgstr "Bénédiction" msgid "Causes the selected creatures to inflict maximum damage." msgstr "Permet à la créature sélectionnée d'infliger un maximum de dégâts." msgid "Causes all of your units to inflict maximum damage." msgstr "Permet à toutes vos créatures d'infliger un maximum de dégâts." msgid "Mass Bless" msgstr "Grande bénédiction" msgid "Magically increases the defense skill of the selected creatures." msgstr "Augmente la statistique de défense des créatures sélectionnées." msgid "Stoneskin" msgstr "Chair de pierre" msgid "" "Increases the defense skill of the targeted creatures. This is an improved " "version of Stoneskin." msgstr "" "Augmente la caractéristique de défense des créatures ciblées. Il s'agit " "d'une version améliorée de chair de pierre." msgid "Steelskin" msgstr "Chair d'acier" msgid "Causes the selected creatures to inflict minimum damage." msgstr "La créature choisie inflige des dégâts minimums." msgid "Curse" msgstr "Malédiction" msgid "Causes all enemy troops to inflict minimum damage." msgstr "Toutes les troupes ennemies infligent des dégâts minimums." msgid "Mass Curse" msgstr "Grande malédiction" msgid "Damages all undead in the battle." msgstr "Inflige des dégâts à tous les morts-vivants du champ de bataille." msgid "Holy Word" msgstr "Sainte parole" msgid "" "Damages all undead in the battle. This is an improved version of Holy Word." msgstr "" "Inflige des dégâts à tous les morts-vivants du champ de bataille. Il s'agit " "d'une version améliorée de sainte parole." msgid "Holy Shout" msgstr "Saint cri" msgid "Anti-Magic" msgstr "Anti-magie" msgid "Prevents harmful magic against the selected creatures." msgstr "Protège la créature choisie de la magie néfaste." msgid "Dispel Magic" msgstr "Contre-sorts" msgid "Removes all magic spells from a single target." msgstr "Lève tous les sortilèges de la créature choisie." msgid "Mass Dispel" msgstr "Ultime contre-sorts" msgid "Removes all magic spells from all creatures." msgstr "Lève tous les sortilèges de toutes les créatures." msgid "Causes a magic arrow to strike the selected target." msgstr "Provoque une flèche magique qui frappe la cible sélectionnée." msgid "Magic Arrow" msgstr "Flèche magique" msgid "Berserker" msgstr "Furie" msgid "Causes a creature to attack its nearest neighbor." msgstr "Fait en sorte qu'une créature attaque son plus proche voisin." msgid "Armageddon" msgstr "Apocalypse" msgid "" "Holy terror strikes the battlefield, causing severe damage to all creatures." msgstr "" "La terreur sacrée frappe le champ de bataille, causant de graves dégâts à " "toutes les créatures." msgid "Elemental Storm" msgstr "Orage" msgid "Magical elements pour down on the battlefield, damaging all creatures." msgstr "" "Les éléments magiques se déversent sur le champ de bataille, frappant toutes " "les créatures." msgid "" "A rain of rocks strikes an area of the battlefield, damaging all nearby " "creatures." msgstr "" "Une pluie de pierres frappe une zone du champ de bataille, frappant toutes " "les créatures proches." msgid "Meteor Shower" msgstr "Pluie de météores" msgid "Paralyze" msgstr "Paralysie" msgid "The targeted creatures are paralyzed, unable to move or retaliate." msgstr "Le créature ciblée est paralysée, incapable de bouger ni de riposter." msgid "Hypnotize" msgstr "Hypnose" msgid "" "Brings a single enemy unit under your control if its hits are less than " "%{count} times the caster's spell power." msgstr "" "Ramène un peloton ennemi sous votre contrôle si ses points de vie sont " "inférieurs à %{count} fois le niveau de Puissance du lanceur." msgid "Cold Ray" msgstr "Rayon froid" msgid "Drains body heat from a single enemy unit." msgstr "Draine la chaleur corporelle d'une créature ennemie." msgid "Cold Ring" msgstr "Anneau froid" msgid "" "Drains body heat from all units surrounding the center point, but not " "including the center point." msgstr "" "Draine la chaleur corporelle de toutes les créatures entourant le point " "central, mais sans le point central." msgid "Disrupting Ray" msgstr "Rayon perturbateur" msgid "Reduces the defense rating of an enemy unit by three." msgstr "Réduit de trois points la défense d'une créature ennemie." msgid "Damages all living (non-undead) units in the battle." msgstr "" "Blesse toutes les créatures vivantes (non mortes-vivantes) de la bataille." msgid "Death Ripple" msgstr "Onde mortelle" msgid "Death Wave" msgstr "Vague mortelle" msgid "" "Damages all living (non-undead) units in the battle. This spell is an " "improved version of Death Ripple." msgstr "" "Blesse toutes les créatures vivantes (non mortes-vivantes) dans la bataille. " "Ce sort est une version améliorée d'onde mortelle." msgid "Dragon Slayer" msgstr "Anti-dragon" msgid "Greatly increases a unit's attack skill vs. Dragons." msgstr "" "Augmente considérablement la statistique d'attaque d'une créature contre les " "dragons." msgid "Blood Lust" msgstr "Soif de sang" msgid "Increases a unit's attack skill." msgstr "Augmente la statistique d'attaque d'une créature." msgid "Animate Dead" msgstr "Mort-vivant" msgid "Resurrects creatures from a damaged or dead undead unit permanently." msgstr "" "Ressuscite les créatures décimées d'un peloton de mort-vivants, de façon " "permanente." msgid "Mirror Image" msgstr "Image miroir" msgid "" "Creates an illusionary unit that duplicates one of your existing units. This " "illusionary unit does the same damages as the original, but will vanish if " "it takes any damage." msgstr "" "Crée un peloton illusoire qui réplique un de vos pelotons existant. Cette " "illusion inflige les mêmes dégâts que l'original, mais disparaît si elle " "subit des dégâts." msgid "Shield" msgstr "Bouclier" msgid "" "Halves damage received from ranged attacks for a single unit. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Réduit de moitié les dégâts reçus par les attaques à distance sur la troupe " "choisie. Cela n'affecte pas les dégâts infligés par les balistes et les " "tourelles." msgid "Mass Shield" msgstr "Grand bouclier" msgid "" "Halves damage received from ranged attacks for all of your units. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Réduit de moitié les dégâts reçus par les attaques à distance pour toutes " "vos troupes. Cela n'affecte pas les dégâts infligés par les balistes et les " "tourelles." msgid "Summon Earth Elemental" msgstr "Invoquer élémentaire de la terre" msgid "Summons Earth Elementals to fight for your army." msgstr "" "Invoque des élémentaires de la terre pour qu'ils se battent à vos côtés." msgid "Summon Air Elemental" msgstr "Invoquer élémentaire de l'air" msgid "Summons Air Elementals to fight for your army." msgstr "Invoque des élémentaires de l'air pour qu'ils se battent à vos côtés." msgid "Summon Fire Elemental" msgstr "Invoquer élémentaire de feu" msgid "Summons Fire Elementals to fight for your army." msgstr "Invoque des élémentaires de feu pour qu'ils se battent à vos côtés." msgid "Summon Water Elemental" msgstr "Invoquer élémentaire de l'eau" msgid "Summons Water Elementals to fight for your army." msgstr "Invoque des élémentaires de l'eau pour qu'ils se battent à vos côtés." msgid "Damages castle walls." msgstr "Endommage les murs des châteaux." msgid "Earthquake" msgstr "Séisme" msgid "Causes all mines across the land to become visible." msgstr "Rend visibles toutes les mines du pays." msgid "View Mines" msgstr "Voir mines" msgid "Causes all resources across the land to become visible." msgstr "Rend visibles toutes les ressources du pays." msgid "View Resources" msgstr "Voir ressources" msgid "Causes all artifacts across the land to become visible." msgstr "Rend visibles tous les artéfacts du pays." msgid "View Artifacts" msgstr "Voir artéfacts" msgid "Causes all towns and castles across the land to become visible." msgstr "Rend visibles toutes les villages et tous les châteaux du pays." msgid "View Towns" msgstr "Voir villages" msgid "Causes all Heroes across the land to become visible." msgstr "Rend visibles tous les héros." msgid "View Heroes" msgstr "Voir héros" msgid "Causes the entire land to become visible." msgstr "Rend visible tout le pays." msgid "View All" msgstr "Tout voir" msgid "Allows the caster to view detailed information on enemy Heroes." msgstr "" "Permet au lanceur du sort de visualiser des informations détaillées sur les " "héros ennemis." msgid "Summon Boat" msgstr "Embarquement" msgid "" "Summons the nearest unoccupied, friendly boat to an adjacent shore location. " "A friendly boat is one which you just built or were the most recent player " "to occupy." msgstr "" "Invoque le bateau ami inoccupé le plus proche sur un emplacement côtier " "adjacent. Un bateau ami est un bateau que vous venez de construire ou que " "vous êtes le dernier joueur à avoir occupé." msgid "Allows the caster to magically transport to a nearby location." msgstr "" "Permet au lanceur du sort de se transporter magiquement dans un lieu proche." msgid "Dimension Door" msgstr "Autre dimension" msgid "Returns the caster to any town or castle currently owned." msgstr "" "Ramène le lanceur du sort dans n'importe quelle village ou château qu'il " "possède actuellement." msgid "Town Gate" msgstr "Retour village" msgid "" "Returns the hero to the town or castle of choice, provided it is controlled " "by you." msgstr "" "Ramène le héros dans le village ou le château de son choix, à condition " "qu'il soit contrôlé par vous." msgid "Visions" msgstr "Visions" msgid "" "Visions predicts the likely outcome of an encounter with a neutral army camp." msgstr "" "Visions prédit l'issue probable d'une rencontre avec un camp d'armée neutre." msgid "Haunt" msgstr "Hanter" msgid "" "Haunts a mine you control with Ghosts. This mine stops producing resources. " "(If I can't keep it, nobody will!)" msgstr "" "Hante une mine que vous contrôlez avec des fantômes. Cette mine cesse de " "produire des ressources. (Si je ne peux pas la garder, personne ne l'aura !)" msgid "Set Earth Guardian" msgstr "Gardien de terre" msgid "Sets Earth Elementals to guard a mine against enemy armies." msgstr "" "Invoque les élémentaires de la terre pour garder une mine contre les armées " "ennemies." msgid "Set Air Guardian" msgstr "Gardien d'air" msgid "Sets Air Elementals to guard a mine against enemy armies." msgstr "" "Invoque les élémentaires de l'air pour garder une mine contre les armées " "ennemies." msgid "Set Fire Guardian" msgstr "Gardien de feu" msgid "Sets Fire Elementals to guard a mine against enemy armies." msgstr "" "Invoque les élémentaires du feu pour garder une mine contre les armées " "ennemies." msgid "Set Water Guardian" msgstr "Gardien d'eau" msgid "Sets Water Elementals to guard a mine against enemy armies." msgstr "" "Invoque les élémentaires de l'eau pour garder une mine contre les armées " "ennemies." msgid "Random Spell" msgstr "Sort aléatoire" msgid "Randomly selected spell of any level." msgstr "" msgid "Random 1st Level Spell" msgstr "Sort aléatoire de 1er niveau" msgid "Randomly selected 1st level spell." msgstr "" msgid "Random 2nd Level Spell" msgstr "" msgid "Randomly selected 2nd level spell." msgstr "" msgid "Random 3rd Level Spell" msgstr "" msgid "Randomly selected 3rd level spell." msgstr "" msgid "Random 4th Level Spell" msgstr "" msgid "Randomly selected 4th level spell." msgstr "" msgid "Random 5th Level Spell" msgstr "" msgid "Randomly selected 5th level spell." msgstr "" msgid "Petrification" msgstr "Pétrification" msgid "" "Turns the affected creature into stone. A petrified creature receives half " "damage from a direct attack." msgstr "" "Transforme en pierre la créature affectée. Une créature pétrifiée reçoit la " "moitié des dégâts d'une attaque directe." msgid "You have no Magic Book, so you cannot cast a spell." msgstr "Vous n'avez pas de grimoire, vous ne pouvez donc pas lancer un sort." msgid "No spell to cast." msgstr "Aucun sort à lancer." msgid "Your hero has %{point} spell points remaining." msgstr "Le héros a %{point} points de magie restants." msgid "View Adventure Spells" msgstr "Voir les sorts d'aventure" msgid "View Combat Spells" msgstr "Voir les sorts de combat" msgid "View previous page" msgstr "Voir la page précédente" msgid "View next page" msgstr "Voir la page suivante" msgid "Close Spellbook" msgstr "Fermer le grimoire" msgid "View %{spell}" msgstr "Voir %{spell}" msgid "This spell does %{damage} points of damage." msgstr "Ce sort fait %{damage} points de dégâts." msgid "" "This spell summons\n" "%{count} %{monster}." msgstr "" "Ce sort invoque\n" "%{count} %{monster}." msgid "This spell restores %{hp} HP." msgstr "Ce sort restaure %{hp} PV." msgid "This spell summons %{count} %{monster} to guard the mine." msgstr "Ce sort invoque %{count} %{monster} pour garder la mine." msgid "The nearest town is %{town}." msgstr "Le village le plus proche est %{town}." msgid "This town is occupied by your hero %{hero}." msgstr "Ce village est occupé par votre héros %{hero}." msgid "" "This spell controls up to\n" "%{hp} HP." msgstr "" "Ce sort contrôle jusqu'à\n" "%{hp} PV." msgid "The ultimate artifact is really the %{name}." msgstr "L'ultime artéfact est vraiment le %{name}." msgid "The ultimate artifact may be found in the %{name} regions of the world." msgstr "L'ultime artéfact peut être trouvé dans les régions %{name} du monde." msgid "north-west" msgstr "nord-ouest" msgid "north" msgstr "nord" msgid "north-east" msgstr "nord-est" msgid "west" msgstr "ouest" msgid "center" msgstr "centre" msgid "east" msgstr "est" msgid "south-west" msgstr "sud-ouest" msgid "south" msgstr "sud" msgid "south-east" msgstr "sud-est" msgid "The truth is out there." msgstr "La vérité est là." msgid "The dark side is stronger." msgstr "Le côté obscur est plus fort." msgid "The end of the world is near." msgstr "La fin du monde est proche." msgid "The bones of Lord Slayer are buried in the foundation of the arena." msgstr "Les os de Lord Slayer sont ensevelis dans les fondations de l'arène." msgid "A Black Dragon will take out a Titan any day of the week." msgstr "Un dragon noir éliminera un titan n'importe quel jour de la semaine." # Orignal H2-fr: Un Dragon noir peut vaincre un Titan à tout moment. msgid "He told her: Yada yada yada... and then she said: Blah, blah, blah..." msgstr "Il lui a dit : Yada yada yada... et elle a répondu : Bla, bla, bla..." msgid "An unknown force is being resurrected..." msgstr "Une force inconnue est ressuscitée..." msgid "" "Check the newest version of the game at\n" "https://github.com/ihhub/\n" "fheroes2/releases" msgstr "" "Téléchargez la dernière version du jeu sur\n" "https://github.com/ihhub/\n" "fheroes2/releases" #~ msgid "Ocean object" #~ msgstr "Océan" #~ msgid "Ocean Objects" #~ msgstr "Océan" #~ msgid "Grass Objects" #~ msgstr "Herbe" #~ msgid "Snow Objects" #~ msgstr "Neige" #~ msgid "Swamp Objects" #~ msgstr "Marais" #~ msgid "Desert Objects" #~ msgstr "Désert" #~ msgid "Dirt Objects" #~ msgstr "Boue" #~ msgid "Beach Objects" #~ msgstr "Plage" #~ msgid "%{race1} %{name1}" #~ msgstr "%{race1} le %{name1}" #~ msgid "vs" #~ msgstr "vs" #~ msgid "%{race2} %{name2}" #~ msgstr "%{race2} le %{name2}" fheroes2-1.0.12+dfsg/files/lang/hu.po000066400000000000000000010704101456075706000172430ustar00rootroot00000000000000# Hungarian translation for fheroes2 # Copyright (C) 2022 fheroes2 team # This file is distributed under the same license as the fheroes2 package. # msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-03 03:27+0000\n" "PO-Revision-Date: 2023-09-04 21:55+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: Hungarian \n" "Language: hu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Launchpad-Export-Date: 2011-06-02 03:12+0000\n" "X-Generator: Poedit 3.3.2\n" msgid "" "BATTLE\n" "ONLY" msgstr "" "CSAK\n" "CSATA" msgid "" "NEW\n" "GAME" msgstr "" "ÚJ\n" "JÃTÉK" msgid "" "SAVE\n" "GAME" msgstr "" "JÃTÉK\n" "MENTÉS" msgid "" "LOAD\n" "GAME" msgstr "" "JÃTÉK\n" "BETÖLTÉS" msgid "INFO" msgstr "INFÓ" msgid "QUIT" msgstr "KILÉPÉS" msgid "CANCEL" msgstr "MÉGSEM" msgid "OKAY" msgstr "OK" msgid "smallerButton|OKAY" msgstr "OK" msgid "ACCEPT" msgstr "ELFOGAD" msgid "DECLINE" msgstr "ELUTASÃT" msgid "LEARN" msgstr "TANUL" msgid "TRADE" msgstr "ÜZLET" msgid "YES" msgstr "IGEN" msgid "NO" msgstr "NEM" msgid "EXIT" msgstr "KILÉP" msgid "smallerButton|EXIT" msgstr "EXIT" msgid "DISMISS" msgstr "ELBOCSÃT" msgid "UPGRADE" msgstr "FEJLESZT" msgid "RESTART" msgstr "ÚJRA" msgid "HEROES" msgstr "HÅSÖK" msgid "" "TOWNS/\n" "CASTLES" msgstr "" "VÃROS\n" "/VÃR" msgid "S" msgstr "S" msgid "M" msgstr "M" msgid "L" msgstr "L" msgid "X-L" msgstr "XL" msgid "ALL" msgstr "MIND" msgid "SELECT" msgstr "KIVÃLASZT" msgid "" "STANDARD\n" "GAME" msgstr "" "NORMÃL\n" "JÃTÉK" msgid "" "CAMPAIGN\n" "GAME" msgstr "" "HADJÃRAT\n" "JÃTÉK" msgid "" "MULTI-\n" "PLAYER\n" "GAME" msgstr "" "TÖBB-\n" "JÃTÉKOS\n" "MÓD" msgid "CONFIG" msgstr "OPCIÓK" msgid "" "ORIGINAL\n" "CAMPAIGN" msgstr "" "EREDETI\n" "HADJÃRAT" msgid "" "EXPANSION\n" "CAMPAIGN" msgstr "" "KIEGÉSZÃTÅ\n" "HADJÃRAT" msgid "HOT SEAT" msgstr "HELYCSERE" msgid "2 PLAYERS" msgstr "2 JÃTÉKOS" msgid "3 PLAYERS" msgstr "3 JÃTÉKOS" msgid "4 PLAYERS" msgstr "4 JÃTÉKOS" msgid "5 PLAYERS" msgstr "5 JÃTÉKOS" msgid "6 PLAYERS" msgstr "6 JÃTÉKOS" msgid "GIFT" msgstr "AJÃND." msgid "MAX" msgstr "MAX" msgid "DIFFICULTY" msgstr "NEHÉZSÉG" msgid "VIEW INTRO" msgstr "BEVEZETÅ" msgid "MIN" msgstr "MIN" msgid "RESET" msgstr "ÚJRA" msgid "START" msgstr "START" msgid "CASTLE" msgstr "VÃR" msgid "TOWN" msgstr "VÃROS" msgid "" "D\n" "I\n" "S\n" "M\n" "I\n" "S\n" "S" msgstr "" "E\n" "L\n" "B\n" "O\n" "C\n" "S\n" "Ã\n" "T" msgid "" "E\n" "X\n" "I\n" "T" msgstr "" "K\n" "I\n" "L\n" "É\n" "P" msgid "" "C\n" "A\n" "M\n" "P\n" "A\n" "I\n" "G\n" "N" msgstr "" "H\n" "A\n" "D\n" "J\n" "Ã\n" "R\n" "A\n" "T" msgid "" "S\n" "T\n" "A\n" "N\n" "D\n" "A\n" "R\n" "D" msgstr "" "N\n" "O\n" "R\n" "M\n" "Ã\n" "L" msgid "Warrior" msgstr "Harcos" msgid "Builder" msgstr "ÉpítÅ‘" msgid "Explorer" msgstr "FelfedezÅ‘" msgid "None" msgstr "Nincs" msgid "" "A few\n" "%{monster}" msgstr "" "Néhány\n" "%{monster}" msgid "" "Several\n" "%{monster}" msgstr "" "Több\n" "%{monster}" msgid "" "A pack of\n" "%{monster}" msgstr "" "Tucatnyi\n" "%{monster}" msgid "" "Lots of\n" "%{monster}" msgstr "" "Sok\n" "%{monster}" msgid "" "A horde of\n" "%{monster}" msgstr "" "Hordányi\n" "%{monster}" msgid "" "A throng of\n" "%{monster}" msgstr "" "Tömegnyi\n" "%{monster}" msgid "" "A swarm of\n" "%{monster}" msgstr "" "Többszáz\n" "%{monster}" msgid "" "Zounds...\n" "%{monster}" msgstr "" "Seregnyi\n" "%{monster}" msgid "" "A legion of\n" "%{monster}" msgstr "" "Légiónyi\n" "%{monster}" msgid "army|Few" msgstr "Néhány" msgid "army|Several" msgstr "Több" msgid "army|Pack" msgstr "Tucat" msgid "army|Lots" msgstr "Sok" msgid "army|Horde" msgstr "Horda" msgid "army|Throng" msgstr "Tömeg" msgid "army|Swarm" msgstr "Többszáz" msgid "army|Zounds" msgstr "Sereg" msgid "army|Legion" msgstr "Légió" msgid "All %{race} troops +1" msgstr "Minden %{race} egység +1" msgid "Multiple" msgstr "Több" msgid "Troops of %{count} alignments -%{penalty}" msgstr "%{count} féle csapatod van: -%{penalty}" msgid "Some undead in army -1" msgstr "ÉlÅ‘halottak a seregben -1" msgid "View %{name}" msgstr "%{name} mutatása" msgid "Move the %{name} " msgstr "%{name} ide lép " msgid "Move or right click to redistribute %{name}" msgstr "Mozgasd vagy jobb gombot nyomj %{name} szétosztásához" msgid "Combine %{name} armies" msgstr "%{name} seregek egyesítése" msgid "Exchange %{name2} with %{name}" msgstr "%{name2} és %{name} cseréje" msgid "Select %{name}" msgstr "%{name} kiválasztása" msgid "Cannot move last troop" msgstr "A sereg utolsó egységét nem lehet áthelyezni" msgid "Move the %{name}" msgstr "%{name} ide lép" msgid "Set Count" msgstr "Mennyiség" msgid "%{name} destroys half the enemy troops!" msgid_plural "%{name} destroy half the enemy troops!" msgstr[0] "%{name} elpusztítja az ellenséges csapatok felét!" msgstr[1] "%{name} elpusztítják az ellenséges csapatok felét!" msgid "%{name} has turned off the auto battle" msgstr "%{name} kikapcsolta az automata csata funkciót" msgid "%{name} has turned on the auto battle" msgstr "%{name} bekapcsolta az automata csata funkciót" msgid "Spell failed!" msgstr "A varázslat meghiúsult!" msgid "" "The Sphere of Negation artifact is in effect for this battle, disabling all " "combat spells." msgstr "A Tagadás Gömbje varázstárgy hatására a csatában nem lehet varázsolni." msgid "You have already cast a spell this round." msgstr "Már varázsoltál ebben a körben." msgid "That spell will have no effect!" msgstr "Ez a varázslat nem hatásos senkire!" msgid "You may only summon one type of elemental per combat." msgstr "Csak egyféle elementált idézhetsz meg egy csatában." msgid "" "There is no open space adjacent to your hero where you can summon an " "Elemental to." msgstr "Nincs szabad hely a hÅ‘söd körül egy újabb Elementál idézéséhez." msgid "" "The Moat reduces by -%{count} the defense skill of any unit and slows to " "half movement rate." msgstr "" "A várárok csökkenti az egység védelmi képességét (-%{count}) és felezi a " "lépésszámát." msgid "Speed: %{speed}" msgstr "Sebesség: %{speed}" msgid "Speed" msgstr "Sebesség" msgid "Off" msgstr "Ki" msgid "On" msgstr "Be" msgid "Turn Order" msgstr "Körsorrend" msgid "Auto Spell Casting" msgstr "Automata varázslás" msgid "Grid" msgstr "Háló" msgid "Shadow Movement" msgstr "Mozgás árnyék" msgid "Shadow Cursor" msgstr "Kurzor árnyék" msgid "Audio" msgstr "Hang" msgid "Settings" msgstr "Beállítások" msgid "Configure" msgstr "Beállítás" msgid "Hot Keys" msgstr "" "Funkció-\n" "billentyűk" msgid "Damage Info" msgstr "Sebzés infó" msgid "Set the speed of combat actions and animations." msgstr "A harci cselekmények és animációk gyorsaságának beállítása." msgid "Toggle to display the turn order during the battle." msgstr "Csatában a támadási sorrend kijelzésének váltása." msgid "" "Toggle whether or not the computer will cast spells for you when auto combat " "is on. (Note: This does not affect spell casting for computer players in any " "way, nor does it affect quick combat.)" msgstr "" "Be- és kikapcsolja, hagy a gép automata csata idején varázsolhat-e vagy nem. " "(Figyelem! Ez nincs hatással arra, hogy az ellenfelek varázsolhatnak-e.)" msgid "" "Toggle the hex grid on or off. The hex grid always underlies movement, even " "if turned off. This switch only determines if the grid is visible." msgstr "" "Be- és kikapcsolja a hatszögű rácsozat láthatóságát. A hatszögek mindenképp " "a mozgás alapját képezik, így a láthatóságuk kikapcsolásakor is." msgid "" "Toggle on or off shadows showing where your creatures can move and attack." msgstr "Be- és kikapcsolja az egységek mozgástartományát jelzÅ‘ árnyékot." msgid "" "Toggle on or off a shadow showing the current hex location of the mouse " "cursor." msgstr "Be- és kikapcsolja az egérkurzor alatti árnyékot." msgid "Change the audio settings of the game." msgstr "Itt lehet megváltoztatni a játék hangbeállításait." msgid "Check and configure all the hot keys present in the game." msgstr "A játékban használható billentyűkombinációk áttekintése és beállítása." msgid "Toggle to display damage information during the battle." msgstr "Be- és kikapcsolja a sebzés információkat a csatában." msgid "Exit this menu." msgstr "Kilépés a menübÅ‘l." msgid "Okay" msgstr "OK" msgid "The enemy has surrendered!" msgstr "Az ellenség megadta magát!" msgid "Their cowardice costs them %{gold} gold." msgstr "" msgid "The enemy has fled!" msgstr "Az ellenség elmenekült!" msgid "A glorious victory!" msgstr "DicsÅ‘séges gyÅ‘zelem!" msgid "For valor in combat, %{name} receives %{exp} experience." msgstr "Vitézségéért %{name} %{exp} tapasztalati pontot kap." msgid "The cowardly %{name} flees from battle." msgstr "A gyáva %{name} elmenekül a csatából." msgid "%{name} surrenders to the enemy, and departs in shame." msgstr "%{name} megadja magát az ellenségnek és szégyenteljesen elvonul." #, fuzzy msgid "Your forces suffer a bitter defeat, and %{name} abandons your cause." msgstr "" "A sereged vereséget szenvedett, ezért %{name} - elpártolva ügyedtÅ‘l - " "otthagy téged." #, fuzzy msgid "Your forces suffer a bitter defeat." msgstr "A sereged vereséget szenvedett." msgid "Battlefield Casualties" msgstr "Harctéri veszteségek" msgid "Attacker" msgstr "Támadó" msgid "Defender" msgstr "VédekezÅ‘" #, fuzzy msgid "Click to leave the battle results." msgstr "Kattints a kiválasztott képernyÅ‘felbontás alkalmazásához." #, fuzzy msgid "Click to restart the battle in manual mode." msgstr "Kattints a fÅ‘menübe való visszatéréshez." msgid "Restart" msgstr "Újra" msgid "You have captured an enemy artifact!" msgstr "Megszereztél egy tárgyat az ellenségtÅ‘l!" msgid "As you reach for the %{name}, it mysteriously disappears." msgstr "Amint %{name} felé nyúlsz, az hirtelen eltűnik." msgid "As your enemy reaches for the %{name}, it mysteriously disappears." msgstr "Amint %{name} felé nyúl az ellenség, az hirtelen eltűnik." msgid "Necromancy!" msgstr "Nekromancia!" msgid "" "Practicing the dark arts of necromancy, you are able to raise %{count} of " "the enemy's dead to return under your service as %{monster}." msgstr "" "A nekromancia tudományának köszönhetÅ‘en képes vagy az ellenség seregében " "elhullott lényekbÅ‘l %{count} %{monster} lényt feltámasztani." msgid "%{name} the %{race}" msgstr "%{name} a %{race}" msgid "Captain of %{name}" msgstr "%{name} kapitánya" msgid "Attack" msgstr "Támadás" msgid "Defense" msgstr "Védekezés" msgid "Spell Power" msgstr "VarázserÅ‘" msgid "Knowledge" msgstr "Tudás" msgid "Morale" msgstr "Morál" msgid "Luck" msgstr "Szerencse" msgid "Spell Points" msgstr "Varázspontok" msgid "Hero's Options" msgstr "HÅ‘s beállításai" msgid "Cast Spell" msgstr "Varázslás" msgid "Retreat" msgstr "Visszavonulás" msgid "Surrender" msgstr "Megadás" msgid "Cancel" msgstr "Mégsem" msgid "Hero Screen" msgstr "HÅ‘s képernyÅ‘" msgid "Captain's Options" msgstr "Kapitány beállítások" msgid "" "Cast a magical spell. You may only cast one spell per combat round. The " "round is reset when every creature has had a turn." msgstr "" "Varázslat idézés. A harc során körönként csak egyszer varázsolhatsz. A kör " "az összes egység lépése után indul újra." msgid "" "Retreat your hero, abandoning your creatures. Your hero will be available " "for you to recruit again, however, the hero will have only a novice hero's " "forces." msgstr "" "A visszavonuló hÅ‘söd, elhagyja a lényeidet. A hÅ‘st újra toborozhatod, habár " "a hÅ‘snek csak újonc serege lesz." #, fuzzy msgid "" "Surrendering costs gold. However if you pay the ransom, the hero and all of " "his or her surviving creatures will be available to recruit again. The cost " "of surrender is half of the total cost of the non-temporary troops remaining " "in the army." msgstr "" "A megadás aranyba kerül. Ha megfizeted a váltságdíjat, akkor a hÅ‘st és az " "összes túlélÅ‘ lényét újra toborozhatod." msgid "Open Hero Screen to view full information about the hero." msgstr "" "Nyisd meg a HÅ‘s képernyÅ‘t, hogy megnézhesd az összes információt a hÅ‘srÅ‘l." msgid "Return to the battle." msgstr "Visszatérés a csatába." msgid "Not enough gold (%{gold})" msgstr "Nincs elég aranyad (%{gold})" msgid "%{name} states:" msgstr "%{name} kinyilvánítja:" msgid "Captain of %{name} states:" msgstr "%{name} kapitánya kinyilvánítja:" msgid "" "\"I will accept your surrender and grant you and your troops safe passage " "for the price of %{price} gold.\"" msgstr "" "\"Elfogadom a megadásodat és garantálom, hogy %{price} aranyért cserébe a " "csapatod szabadon távozhat.\"" msgid "View %{monster} info" msgstr "Megmutatja %{monster} információit" msgid "Fly %{monster} here" msgstr "%{monster} ide repül" msgid "Move %{monster} here" msgstr "%{monster} ide lép" msgid "Shoot %{monster}" msgstr "%{monster} lövése" msgid "(1 shot left)" msgid_plural "(%{count} shots left)" msgstr[0] "(1 lövés maradt)" msgstr[1] "(%{count} lövés maradt)" msgid "Attack %{monster}" msgstr "%{monster} támadása" msgid "Turn %{turn}" msgstr "Kör %{turn}" msgid "Teleport here" msgstr "Teleportálás ide" msgid "Invalid teleport destination" msgstr "Nem megfelelÅ‘ teleport cél" msgid "Cast %{spell} on %{monster}" msgstr "%{spell} varázslat idézése a(z) %{monster} lényre" msgid "Cast %{spell}" msgstr "%{spell} varázslat idézése" msgid "Select spell target" msgstr "Varázslat célpontjának kiválasztása" msgid "View Ballista info" msgstr "Hajítógép információk" msgid "Ballista" msgstr "Hajítógép" msgid "Enable auto combat" msgstr "Automatikus harc bekapcsolása" msgid "Allows the computer to fight out the battle for you." msgstr "A számítógép lejátssza helyetted a csatát." msgid "Auto Combat" msgstr "Automatikus csata" msgid "Customize system options" msgstr "Rendszerbeállítások testreszabása" msgid "Allows you to customize the combat screen." msgstr "Harci képernyÅ‘ testreszabásának engedélyezése." msgid "System Options" msgstr "Rendszerbeállítások" msgid "Skip this unit" msgstr "Egység kimarad" msgid "Skip" msgstr "Kimarad" msgid "" "Skips the current creature. The current creature ends its turn and does not " "get to go again until the next round." msgstr "" "Az aktuális egység kimarad egy lépésbÅ‘l, azaz köre véget ér és csak a " "következÅ‘ körben kerül rá sor újra." msgid "View Captain's options" msgstr "Kapitány beállításai" msgid "View Hero's options" msgstr "HÅ‘s beállításai" msgid "View opposing Captain" msgstr "Ellenséges kapitány megmutatása" msgid "View opposing Hero" msgstr "Ellenséges hÅ‘s megmutatása" msgid "Hide logs" msgstr "Napló elrejtése" msgid "Show logs" msgstr "Napló mutatása" msgid "Message Bar" msgstr "Üzenetsáv" msgid "Shows the results of individual monster's actions." msgstr "Mutatja egy lény tevékenységének eredményét." msgid "Are you sure you want to finish the battle in auto mode?" msgstr "Biztos vagy benne hogy automatikus módban kívánod befejezni a csatát?" msgid "Are you sure you want to enable auto combat?" msgstr "Biztos, hogy be akarod kapcsolni az automata csatát?" msgid "%{name} skip their turn." msgstr "%{name} kihagynak egy kört." msgid "%{attacker} does %{damage} damage." msgid_plural "%{attacker} do %{damage} damage." msgstr[0] "A(z) %{attacker} %{damage} sebzést okoz." msgstr[1] "A(z) %{attacker} %{damage} sebzést okoz." msgid "1 creature perishes." msgid_plural "%{count} creatures perish." msgstr[0] "egy lény elpusztult." msgstr[1] "%{count} lény elpusztult." msgid "1 %{defender} perishes." msgid_plural "%{count} %{defender} perish." msgstr[0] "egy %{defender} elpusztult." msgstr[1] "%{count} %{defender} elpusztult." msgid "1 soul is incorporated." msgid_plural "%{count} souls are incorporated." msgstr[0] "1 lélek visszatért." msgstr[1] "%{count} lélek visszatért." msgid "1 %{unit} is revived." msgid_plural "%{count} %{unit} are revived." msgstr[0] "1 %{unit} újjáéledt." msgstr[1] "%{count} %{unit} újjáéledt." msgid "Moved %{monster}: from [%{src}] to [%{dst}]." msgstr "%{monster} mozgása: [%{src}] -> [%{dst}]." msgid "The %{name} resist the spell!" msgstr "%{name} ellenáll a varázslatnak!" msgid "%{name} casts %{spell} on the %{troop}." msgstr "%{name} %{spell} -t varázsol a %{troop} egységre." msgid "%{name} casts %{spell}." msgstr "%{name} megidézte ezt a varázslatot: %{spell}." msgid "The %{spell} does %{damage} damage to one undead creature." msgstr "A %{spell} varázslat %{damage} sebzést okoz egy élÅ‘halottnak." msgid "The %{spell} does %{damage} damage to all undead creatures." msgstr "" "A %{spell} varázslat %{damage} sebzést okoz valamennyi élÅ‘halott lénynek." msgid "The %{spell} does %{damage} damage, %{count} creatures perish." msgstr "" "A %{spell} varázslat %{damage} sebzést okoz, %{count} teremtmény elpusztul." msgid "The %{spell} does %{damage} damage." msgstr "The %{spell} varázslat %{damage} sebzést okoz." msgid "The %{spell} does %{damage} damage to one living creature." msgstr "A %{spell} varázslat %{damage} sebzést okoz egy élÅ‘ lénynek." msgid "The %{spell} does %{damage} damage to all living creatures." msgstr "A %{spell} varázslat %{damage} sebzést okoz valamennyi élÅ‘ lénynek." msgid "The %{attacker}' attack blinds the %{target}!" msgstr "A(z) %{attacker} megvakítja a(z) %{target} lényt!" msgid "The %{attacker}' gaze turns the %{target} to stone!" msgstr "A(z) %{attacker} tekintete kÅ‘vé dermeszti a(z) %{target} lényt!" msgid "The %{attacker}' curse falls upon the %{target}!" msgstr "A(z) %{attacker} átka lesújt a(z) %{target} lényre!" msgid "The %{target} are paralyzed by the %{attacker}!" msgstr "A(z) %{attacker} lebénítja a(z) %{target} lényt!" msgid "The %{attacker} dispel all good spells on your %{target}!" msgstr "" "A(z) %{attacker} eloszlat minden pozitív varázslatot a(z) %{target} lényrÅ‘l!" msgid "The %{attacker} cast %{spell} on %{target}!" msgstr "A(z) %{attacker} %{spell} varázslatot szór a %{target} csapatra!" msgid "Bad luck descends on the %{attacker}." msgstr "A támadó %{attacker} lénynek balszerencséje van." msgid "Good luck shines on the %{attacker}." msgstr "A támadó %{attacker} lénynek jó szerencséje van." msgid "High morale enables the %{monster} to attack again." msgstr "A jó morál következtében a(z) %{monster} újra támadhat." msgid "Low morale causes the %{monster} to freeze in panic." msgstr "Az alacsony morál miatt a(z) %{monster} pánikba esett és kimarad." msgid "%{tower} does %{damage} damage." msgstr "%{tower} sebzése: %{damage}." msgid "The mirror image is created." msgstr "Tükör képmás készült." msgid "The mirror image is destroyed!" msgstr "Tükör képmás megsemmisült!" msgid "Are you sure you want to interrupt the auto battle?" msgstr "Biztos, hogy meg akarod szakítani a csata automatikus menetét?" msgid "Error" msgstr "Hiba" msgid "No spells to cast." msgstr "Nincsenek varázslataid." msgid "Are you sure you want to retreat?" msgstr "Biztos, hogy vissza akarsz vonulni?" msgid "Retreat disabled" msgstr "Visszavonulás nem lehetséges" msgid "Surrender disabled" msgstr "Megadás nem lehetséges" msgid "Damage: %{max}" msgstr "Sebzés: %{max}" msgid "Damage: %{min} - %{max}" msgstr "Sebzés: %{min} - %{max}" msgid "Perish: %{max}" msgstr "Megöl: %{max}" msgid "Perish: %{min} - %{max}" msgstr "Megöl: %{min} - %{max}" msgid "" "Through eagle-eyed observation, %{name} is able to learn the magic spell " "%{spell}." msgstr "" "%{name} sasszemével a következÅ‘ varázslatot lesi el és tanulja meg: %{spell}." msgid "Human" msgstr "Ember" msgid "AI" msgstr "Gép" msgid "Start" msgstr "Start" msgid "Start the battle." msgstr "Csata indítása." msgid "Exit" msgstr "Kilép" msgid "Reset" msgstr "Reset" msgid "Reset to default settings." msgstr "Alapbeállítások visszaállítása" msgid "Please select another hero." msgstr "Kérlek válassz másik hÅ‘st." msgid "Set Attack Skill" msgstr "TámadóerÅ‘ beállítása" msgid "Set Defense Skill" msgstr "VédÅ‘erÅ‘ beállítása" msgid "Set Power Skill" msgstr "VarázserÅ‘ beállítása" msgid "Set Knowledge Skill" msgstr "Tudás beállítása" msgid "%{race1} %{name1} vs %{race2} %{name2}" msgstr "%{race1} %{name1} és %{race2} %{name2} találkozik" msgid "Monsters" msgstr "Lények" msgid "N/A" msgstr "N/A" msgid "Left Turret" msgstr "Bal torony" msgid "Right Turret" msgstr "Jobb torony" msgid "The %{name} fires with the strength of %{count} Archers" msgstr "%{name} %{count} íjász erejével lÅ‘" msgid "each with a +%{attack} bonus to their attack skill." msgstr "mindenki +%{attack} támadóerÅ‘vel rendelkezik." msgid "The %{name} is destroyed." msgstr "A %{name} megsemmisült." msgid "%{count} %{name} rises from the dead!" msgid_plural "%{count} %{name} rise from the dead!" msgstr[0] "%{count} %{name} lényt feltámasztottak a halálból!" msgstr[1] "%{count} %{name} lényt feltámasztottak a halálból!" msgid "Dwarven Alliance" msgstr "Törp Szövetség" msgid "Sorceress Guild" msgstr "VarázslónÅ‘ Testület" msgid "Necromancer Guild" msgstr "Nekromanta Testület" msgid "Ogre Alliance" msgstr "Ogre Szövetség" msgid "Dwarfbane" msgstr "Törpveszedelem" msgid "Dragon Alliance" msgstr "Sárkány Szövetség" msgid "Elven Alliance" msgstr "Tünde Szövetség" msgid "Kraeger defeated" msgstr "Kraeger legyÅ‘zetett" msgid "Wayward Son" msgstr "Önfejű fiú" msgid "Uncle Ivan" msgstr "Iván nagybácsi" msgid "Annexation" msgstr "Birtokbavétel" msgid "Force of Arms" msgstr "Fegyverek ereje" msgid "Save the Dwarves" msgstr "Mentsd meg a törpöket" msgid "Carator Mines" msgstr "Carator bányái" msgid "Turning Point" msgstr "Fordulópont" msgid "scenarioName|Defender" msgstr "VédÅ‘" msgid "Corlagon's Defense" msgstr "Corlagon oltalma" msgid "The Crown" msgstr "Korona" msgid "The Gauntlet" msgstr "A vesszÅ‘futás" msgid "Betrayal" msgstr "Ãrulás" msgid "Final Justice" msgstr "Utolsó ítélet" msgid "" "Roland needs you to defeat the lords near his castle to begin his war of " "rebellion against his brother. They are not allied with each other, so they " "will spend most of their time fighting with one another. Victory is yours " "when you have defeated all of their castles and heroes." msgstr "" "Rolandnak szüksége van rád, hogy legyÅ‘zze a vára körüli uraságokat és " "megkezdje a bátyja elleni felkelést. Az uraságok nem szövetségesei " "egymásnak, idejüket az egymással való csatározásokkal töltik. Csak akkor " "tiéd a gyÅ‘zelem, ha elesett minden váruk és hÅ‘sük." msgid "" "The local lords refuse to swear allegiance to Roland, and must be subdued. " "They are wealthy and powerful, so be prepared for a tough fight. Capture all " "enemy castles to win." msgstr "" "Helyi nagyurak megtagadták a hűségesküt Rolandnak, így erÅ‘vel kell Å‘ket " "igába hajtani. Gazdagok és erÅ‘sek, így kemény harcra kell felkészüljél. A " "gyÅ‘zelemhez minden ellenséges várat el kell foglalnod." msgid "" "Your task is to defend the Dwarves against Archibald's forces. Capture all " "of the enemy towns and castles to win, and be sure not to lose all of the " "dwarf towns at once, or the enemy will have won." msgstr "" "A feladatod megvédeni a törpöket Archibald csapataitól. Minden ellenséges " "várat el kell foglalnod, de közben nem szabad elvesztened minden törp várat, " "különben veszítesz." msgid "" "You will face four allied enemies in a straightforward fight for resource " "and treasure. Capture all of the enemy castles for victory." msgstr "" "Négy, egymással szövetséges ellenféllel kell szembenézned egy nyílt " "csatában. A tét: erÅ‘források és kincsek. Foglalj el minden ellenséges várat " "a gyÅ‘zelemhez." msgid "" "Your enemies are allied against you and start close by, so be ready to come " "out fighting. You will need to own all four castles in this small valley to " "win." msgstr "" "Ellenségeid szövetkeztek ellened és közelednek, így készen kell állnod a " "harcra. A kis völgy mind a négy várára szükséged lesz a gyÅ‘zelemhez." msgid "" "The Sorceress' guild of Noraston has requested Roland's aid against an " "attack from Archibald's allies. Capture all of the enemy castles to win, and " "don't lose Noraston, or you'll lose the scenario. (Hint: There is an enemy " "castle on an island in the ocean.)" msgstr "" "Noraston boszorkányszövetsége a Roland segítségét kérte Archibald támadásai " "ellen. Foglalj el minden ellenséges várat és közben ne veszítsd el " "Norastont, különben elbukod a küldetést. (Tipp: van egy vár egy óceáni " "szigeten is)" msgid "" "Gather as large an army as possible and capture the enemy castle within 8 " "weeks. You are opposed by only one enemy, but must travel a long way to get " "to the enemy castle. Any troops you have in your army at the end of this " "scenario will be with you in the final battle." msgstr "" "Gyűjts akkora sereget, amennyit csak lehet és foglald el az ellenséges várat " "8 héten belül. Csak egy ellenfeled lesz, de hosszú út vezet a váráig. A " "küldetés végén megmaradt lényeidet tovább viheted a végsÅ‘ ütközetbe." msgid "" "Find the Crown before Archibald's heroes find it. Roland will need the Crown " "for the final battle against Archibald." msgstr "" "Találd meg a Koronát, mielÅ‘tt Archibald hÅ‘seik teszik meg ugyanezt. " "Rolandnak szüksége lesz a Koronára a végsÅ‘ összecsapásban Archibald ellen." msgid "" "Three allied enemies stand before you and victory, including Lord Corlagon. " "Roland is in a castle to the northwest, and you will lose if he falls to the " "enemy. Remember that capturing Lord Corlagon will ensure that he will not " "fight against you in the final scenario." msgstr "" "Három, egymással szövetséges ellenfél - köztük Lord Corlagon - áll közted és " "a gyÅ‘zelem között. Roland vára észak-nyugaton van, melyet nem veszíthetsz " "el. Ne feledd, Lord Corlagon elfogása biztosítja, hogy Å‘ ne harcoljon " "ellened a végsÅ‘ ütközetben." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Archibald to end the war!" msgstr "" "Ez a végsÅ‘ ütközet. Te és ellenfeleid is állig felfegyverkezve várják a " "csatát. Fogd el Archibaldot és megnyered a háborút!" msgid "" "Switching sides leaves you with three castles against the enemy's one. This " "battle will be the easiest one you will face for the rest of the war..." "traitor." msgstr "" "Miután átálltál a másik oldalra, három várad maradt az ellenség egyetlenével " "szemben. Ez a csata lesz a legkönnyebb, mellyel a háború további részében " "szembesülsz, te hitszegÅ‘." msgid "Barbarian Wars" msgstr "Barbár háborúk" msgid "First Blood" msgstr "ElsÅ‘ vérig" msgid "Necromancers" msgstr "Nekromanták" msgid "Slay the Dwarves" msgstr "Öld meg a törpöket" msgid "Country Lords" msgstr "Vidéki nagyurak" msgid "Dragon Master" msgstr "Sárkánymester" msgid "Rebellion" msgstr "Lázadás" msgid "Apocalypse" msgstr "Apokalipszis" msgid "Greater Glory" msgstr "Nagyobb dicsÅ‘ség" msgid "" "King Archibald requires you to defeat the three enemies in this region. They " "are not allied with one another, so they will spend most of their energy " "fighting amongst themselves. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Archibald királynak szüksége van rád, hogy legyÅ‘zze három ellenségét ezen a " "területen. Åk nem szövetségesei egymásnak, így energiáik nagy része az " "egymással való csatározásokra megy el. Te gyÅ‘zöl, ha a kezedre kerül az " "összes ellenséges vár és nincs több ellenséges hÅ‘s, aki harcolhatna ellened." msgid "" "You must unify the barbarian tribes of the north by conquering them. As in " "the previous mission, the enemy is not allied against you, but they have " "more resources at their disposal. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Egyesítened kell az északi barbár törzseket, miután leigáztad Å‘ket. Ahogy az " "elÅ‘zÅ‘ küldetésben, úgy az ellenségek most sem állnak szövetségben ellened, " "de több erÅ‘forrással rendelkeznek nálad. Te gyÅ‘zöl, ha a kezedre kerül az " "összes ellenséges vár és nincs több ellenséges hÅ‘s, aki harcolhatna ellened." msgid "" "Do-gooder wizards have taken the Necromancers' castle. You must retake it to " "achieve victory. Remember that while you start with a powerful army, you " "have no castle and must take one within 7 days, or lose this battle. (Hint: " "The nearest castle is to the southeast.)" msgstr "" "Jó varázslók elfoglalták a nekromanták várát. Vedd vissza a gyÅ‘zelemért. Ne " "feledd! Egy viszonylag erÅ‘s csapattal kezdesz, de nincs várad, és hogy ne " "veszítsd el a küldetést rögtön az elején, 7-non belül birtokba kell venned " "egyet. (Jótanács: A legközelebbi vár délkeletre van.)" msgid "" "The dwarves need conquering before they can interfere in King Archibald's " "plans. Roland's forces have more than one hero and many towns to start with, " "so be ready for attack from multiple directions. You must capture all of the " "enemy towns and castles to claim victory." msgstr "" "Le kell gyÅ‘zni a törpöket, mielÅ‘tt keresztülhúzzák Archibald király terveit. " "Roland erÅ‘inek egynél több hÅ‘se van és több vára kezdéskor, így légy résen, " "mert egyszerre több oldalról támadhatnak. Minden ellenséges várat és várost " "el kell foglalj a gyÅ‘zelemért." msgid "" "You must put down a peasant revolt led by Roland's forces. All are allied " "against you, but you have Lord Corlagon, an experienced hero, to help you. " "Capture all enemy castles to win." msgstr "" "Verd le a jobbágyfelkelést, melyet Roland követÅ‘i vezetnek. Mindegyik " "összeszövetkezett ellened, viszont téged erÅ‘sít Lord Corlagon, aki egy " "tapasztalt hÅ‘s. Foglalj el minden ellenséges várat a gyÅ‘zelemért." msgid "" "There are two enemies allied against you in this mission. Both are well " "armed and seek to evict you from their island. Avoid them and capture Dragon " "City to win." msgstr "" "Ebben a küldetésben két, egymással szövetséges ellenfeled lesz. MindkettÅ‘ " "erÅ‘s és azon vannak, hogy kiűzzenek téged a szigetükrÅ‘l. Kerüld Å‘ket és " "foglald el Sárkányvárost a gyÅ‘zelemért." msgid "" "Your orders are to conquer the country lords that have sworn to serve " "Roland. All of the enemy castles are unified against you. Since you start " "without a castle, you must hurry to capture one before the end of the week. " "Capture all enemy castles for victory." msgstr "" "A parancs szerint le kell gyÅ‘zzed a Rolandhoz hű nagyurakat a területen. " "Minden ellenséges vár egyesült ellened. Vár nélkül kezdesz és egy héten " "belül el kell foglalnod egyet. Vegyél be minden ellenséges várat a " "gyÅ‘zelemért." msgid "" "Find the Crown before Roland's heroes find it. Archibald will need the Crown " "for the final battle against Roland." msgstr "" "Találd meg a Koronát Roland hÅ‘sei elÅ‘tt. Archibaldnak nagy szüksége lesz rá " "a Roland elleni végsÅ‘ csatában." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Roland to win the war, and be sure " "not to lose Archibald in the fight!" msgstr "" "Ez a végsÅ‘ csata. Te és ellenségeid is állig fel vannak fegyverkezve és " "mindegyikük összeesküdött ellened. Fogd el Rolandot és tegyél róla, hogy ne " "veszítsd el Archibaldot a csatában!" msgid "Arrow's Flight" msgstr "A nyíl röpte" msgid "Island of Chaos" msgstr "A káosz szigete" msgid "The Abyss" msgstr "A szakadék" msgid "Uprising" msgstr "Felkelés" msgid "Aurora Borealis" msgstr "Sarki fény" msgid "Betrayal's End" msgstr "Az árulás vége" msgid "Corruption's Heart" msgstr "A romlás szíve" msgid "The Giant's Pass" msgstr "Óriás hágó" msgid "" "Subdue the unruly local lords in order to provide the Empire with facilities " "to operate in this region." msgstr "" "Fékezd meg az engedetlen helyi nagyurakat, hogy elláthasd javakkal a " "Birodalmat és megszilárdítsd a hatalmát a régióban." msgid "" "Eliminate all opposition in this area. Then the first piece of the artifact " "will be yours." msgstr "" "Pusztíts el minden ellenséget a területen. Eztán lesz tied a varázstárgy " "elsÅ‘ darabja." msgid "" "The sorceresses to the northeast are rebelling! For the good of the empire " "you must quash their feeble uprising on your way to the mountains." msgstr "" "Az északkeleti varázslónÅ‘k fellázadtak! A Birodalom érdekében fojtsd el " "csírájában erÅ‘tlen próbálkozásukat, útban a hegyek felé." msgid "" "Having prepared for your arrival, Kraeger has arranged for a force of " "necromancers to thwart your quest. You must capture the castle of Scabsdale " "before the first day of the third week, or the Necromancers will be too " "strong for you." msgstr "" "Jöttödre készülvén Kraeger intézkedéseket tett, hogy a nekromanták ereje " "meghiúsítsa a küldetésedet. Vedd be Scabsdale várát a harmadik hét elejéig " "különben a nektromanták felülkerekednek rajtad." msgid "" "The barbarian despot in this area is, as yet, ignorant of your presence. " "Quickly, build up your forces before you are discovered and attacked! Secure " "the region by subduing all enemy forces." msgstr "" "A despota barbárok - eddig még - nincsenek tudatában a jelenlétednek a " "térségben. SürgÅ‘sen erÅ‘sítsd meg a csapataidat, míg fel nem fedeznek és rád " "nem támadnak! Biztosítsd a területet az ellenséges erÅ‘k kiiktatásával." msgid "" "The Empire is weak in this region. You will be unable to completely subdue " "all forces in this area, so take what you can before reprisal strikes. " "Remember, your true goal is to claim the Helmet of Anduran." msgstr "" "A Birodalom hatalma gyenge ezen a területen. Nem fogod tudni teljesen " "legyÅ‘zni az ellenséges erÅ‘ket, úgyhogy vidd, amit csak lehet, még a várható " "megtorlás elÅ‘tt. Ne feledd! A cél Anduran sisakjának megszerzése." msgid "For the good of the Empire, eliminate Kraeger." msgstr "A Birodalom érdekében iktasd ki Kraegert." msgid "" "At last, you have the opportunity and the facilities to rid the Empire of " "the necromancer's evil. Eradicate them completely, and you will be sung as a " "hero for all time." msgstr "" "Végre meg vannak a lehetÅ‘ségeid és eszközeid arra, hogy leigázd a " "nekromanták gonosz birodalmát. Pusztítsd el mindegyiküket és nevedet hÅ‘sként " "zengik majd örök idÅ‘kre." msgid "Border Towns" msgstr "Határvárosok" msgid "Conquer and Unify" msgstr "Hódíts és egyesíts" msgid "Crazy Uncle Ivan" msgstr "Årült Iván nagybácsi" msgid "The Wayward Son" msgstr "Önfejű fiú" msgid "Ivory Gates" msgstr "Elefántcsont kapuk" msgid "The Elven Lands" msgstr "Tünde vidék" msgid "The Epic Battle" msgstr "A hÅ‘sies csata" msgid "The Southern War" msgstr "A déli háború" msgid "" "Conquer and unite all the enemy tribes. Don't lose the hero Jarkonas, the " "forefather of all descendants." msgstr "" "Hódítsd meg és egyesítsd az ellenséges törzseket. Ne hagyd veszni Jarkonast, " "minden leszármazott Å‘satyját." msgid "" "Your rival, the Kingdom of Harondale, is attacking weak towns on your " "border! Recover from their first strike and crush them completely!" msgstr "" "A rivális Harondale királyság megtámadta a határmenti gyengébb városaidat! " "Miután felocsúdtál az elsÅ‘ meglepetésbÅ‘l, morzsold fel Å‘ket mind egy szálig!" msgid "" "Find your wayward son Joseph who is rumored to be living in the desolate " "lands. Do it before the first day of the third month or it will be of no " "help to your family." msgstr "" "Találd meg önfejű fiadat, Josephet, aki a szóbeszéd szerint egy elhagyatott " "vidéken él. Meg kell találnod a harmadik hónap elejéig, különben nem lesz " "segítségére a családodnak." msgid "" "Rescue your crazy uncle Ivan. Find him before the first day of the fourth " "month or it will be no help to your kingdom." msgstr "" "Mentsd meg az Å‘rült Iván bácsikádat. Találd meg a negyedik hónap legelsÅ‘ " "napjáig, különben a királyságodon nem lehet segíteni." msgid "" "Destroy the barbarians who are attacking the southern border of your " "kingdom! Recover your fallen towns, and then invade the jungle kingdom. " "Leave no enemy standing." msgstr "" "Pusztítsd el a barbárokat, akik megtámadták királyságod déli határvidékét! " "Vedd vissza váraidat, majd hatolj be a dzsungel királyságba." msgid "Retake the castle of Ivory Gates, which has fallen due to treachery." msgstr "Vedd vissza Elefántcsont kapuk várát, mely árulás miatt veszett el." msgid "" "Gain the favor of the elves. They will not allow trees to be chopped down, " "so they will send you wood every 2 weeks. You must complete your mission " "before the first day of the seventh month, or the kingdom will surely fall." msgstr "" "Nyerd el a tündék kegyét. Åk nem engedik a fák kivágását, ezért kéthetente " "küldenek faanyagot inkább maguk. Be kell tudd fejezni a küldetést a hetedik " "hónap elejéig, különben a királyság menthetetlenül elesik." msgid "" "This is the final battle against your rival kingdom of Harondale. Eliminate " "everyone, and don't lose the hero Jarkonas VI." msgstr "" "Ez az utolsó csata a rivális Harondale királysággal. Pusztíts el mindenkit " "és ne veszítsd el VI. Jarkonas hÅ‘södet." msgid "Fount of Wizardry" msgstr "Varázslás forrása" msgid "Power's End" msgstr "A hatalom vége" msgid "The Eternal Scrolls" msgstr "Örök pergamenek" msgid "The Shrouded Isles" msgstr "Ködbe borult szigetek" msgid "" "Your mission is to vanquish the warring mages in the magical Shrouded Isles. " "The completion of this task will give you a fighting chance against your " "rivals." msgstr "" "Az a küldetésed, hogy legyÅ‘zd a varázslatos ködbe borult szigetek háborúzó " "mágusait. A feladat teljesítése ad lehetÅ‘séget számodra a riválisaid ellen." msgid "" "The location of the great library has been discovered! You must make your " "way to it, and reclaim the city of Chronos in which it lies." msgstr "" "Megtalálták a nagy könyvtár pontos helyét! Utat kell találj oda és módot, " "hogy birtokba vegyed Chronost a várost, mely magában rejti." msgid "" "Find the Orb of negation, which is said to be buried in this land. There are " "clues inscribed on stone obelisks which will help lead you to your prize. " "Find the Orb before the first day of the sixth month, or your rivals will " "surely have gotten to the fount before you." msgstr "" "Találd meg a Tagadás Gömbjét, melyrÅ‘l úgy tartják, hogy ezen a vidéken lett " "eltemetve. A hozzá vezetÅ‘ nyomok kÅ‘obeliszkekre vannak felvésve. Találd meg " "a Gömböt a hatodik hónap elejéig, különben ellenfeleid bízvást elorozzák " "elÅ‘led és elÅ‘bb érnek a varázslat forrásához." msgid "" "You must take control of the castle of Magic, where the fount of wizardry " "lies. Do this and your victory will be supreme." msgstr "" "Be kell vegyed a varázslatos Magic várat, ahol a varázslás forrása " "található. Ha sikerrel jársz, gyÅ‘zelmed döntÅ‘ jelentÅ‘ségű lesz." msgid "Blood is Thicker" msgstr "A vér nem válik vízzé" msgid "King and Country" msgstr "Király és ország" msgid "Pirate Isles" msgstr "Kalóz szigetek" msgid "Stranded" msgstr "Hajótörés" msgid "" "Capture the town on the island off the southeast shore in order to construct " "a boat and travel back towards the mainland. Do not lose the hero Gallavant." msgstr "" "Vedd be a várost a délkeleti partokon túli szigeten, hogy hajót ácsolva " "visszatérj a szárazföldre. Ne veszítsd el Gallavant hÅ‘södet." msgid "" "Find and defeat Martine, the pirate leader, who resides in Pirates Cove. Do " "not lose Gallavant or your quest will be over." msgstr "" "Keresd meg és gyÅ‘zd le a kalózvezért, Martine-t, aki a Kalóz öbölben " "tanyázik. Ne veszítsd el Gallavant hÅ‘södet, különben a küldetésednek " "befellegzett." msgid "" "Eliminate all the other forces who oppose the rule of Lord Alberon. " "Gallavant must not die." msgstr "" "Iktass ki minden más ellenséges erÅ‘t, aki szembe mer szállni Lord Alberon " "uralmával. Gallavant nem halhat meg." msgid "" "Overthrow the entrenched monarchy of Lord Alberon, and claim all the land in " "your name. Gallavant must not die." msgstr "" "Döntsd meg a védművekkel körbesáncolt Alberon monarchiát és követeld " "magadnak az egész vidéket. Gallavant nem halhat meg." msgid " bane" msgstr " veszedelem" msgid " alliance" msgstr " szövetség" msgid "Carry-over forces" msgstr "Ãtvitt csapatok" msgid " bonus" msgstr " bónusz" msgid " defeated" msgstr " legyÅ‘zetett" msgid " will always run away from your army." msgstr " mindig elmenekülnek majd a sereged elÅ‘l." msgid " will be willing to join your army." msgstr " csatlakozni szándékoznak majd a seregedhez." msgid "\"%{artifact}\" artifact will be carried over the scenario." msgstr "\"%{artifact}\" varázstárgy továbbvihetÅ‘ a további küldetésekre." msgid "The army will be carried over the scenario." msgstr "A hadsereg továbbvihetÅ‘ a további küldetésekre." msgid "The kingdom will have +%{count} %{resource} each day." msgstr "Királyságod napi +%{count} %{resource} erÅ‘forrással gyarapszik." msgid "\"%{spell}\" spell will be carried over the scenario." msgstr "\"%{spell}\" varázslat továbbvihetÅ‘ a további küldetésekre." msgid "%{hero} can be hired in the scenario." msgstr "%{hero} hÅ‘s felvehetÅ‘ a küldetésben." msgid "" "%{hero} has been defeated and will not appear in the subsequent scenarios." msgstr "%{hero} legyÅ‘zetett, így nem szerepel a további küldetésekben." msgid "The dwarves recognize their allies and gladly join your forces." msgstr "" "A törpök felismerik a szövetségeseiket és szívesen csatlakoznak csapataidhoz." msgid "The ogres recognize you as the Dwarfbane and lumber over to join you." msgstr "" "Az ogrék felismerik benned a Törpveszedelmet és komótosan csatlakoznak " "hozzád." msgid "" "The dragons, snarling and growling, agree to join forces with you, their " "'Ally'." msgstr "" "A sárkányok vicsorognak és morognak, majd beleegyeznek, hogy egyesítsék " "erÅ‘iket veled, a 'szövetségesükkel'." msgid "" "As you approach the group of elves, their leader calls them all to " "attention. He shouts to them, \"Who of you is brave enough to join this " "fearless ally of ours?\" The group explodes with cheers as they run to join " "your ranks." msgstr "" "Amint közeledsz a tündék csoportjához, hallod, hogy a vezetÅ‘jük a " "figyelmüket kéri és így kiállt: \"Ki elég bátor közületek, hogy csatlakozzon " "rettenthetetlen szövetségesünkhöz?\" A csoport éljenzésben tör ki és futnak, " "hogy beálljanak a soraid közé." msgid "" "The dwarves hail you, \"Any friend of Roland is a friend of ours. You may " "pass.\"" msgstr "" "A törpök fennhangon üdvözölnek téged: \"Bárki, aki Roland barátja, az a mi " "barátunk is egyben. Továbbhaladhatsz.\"" msgid "" "The ogres give you a grunt of recognition, \"Archibald's allies may pass.\"" msgstr "Az ogrék elismerÅ‘en röfögnek: \"Archibald szövetségesei átkelhetnek.\"" msgid "" "The dragons see you and call out. \"Our alliance with Archibald compels us " "to join you. Unfortunately you have no room. A pity!\" They quickly scatter." msgstr "" "A sárkányok meglátnak és így kiáltanak: \"Archibalddal kötött szövetségünk " "arra kötelez, hogy csatlakozzunk hozzád. Azonban sajnos nincs hely a " "csapatodban. Nagy kár!\" Ezután gyorsan szétrebbennek." msgid "" "The elves stand at attention as you approach. Their leader calls to you and " "says, \"Let us not impede your progress, ally! Move on, and may victory be " "yours.\"" msgstr "" "A tündék vigyázzba vágják magukat a közeledtedre. VezetÅ‘jük megszólít és azt " "mondja: \"Nem akadályozzuk az elÅ‘rehaladásodat, szövetségesünk! Haladj " "tovább és legyen a gyÅ‘zelem a tiéd.\"" msgid "\"The Dwarfbane!!!!, run for your lives.\"" msgstr "\"Törp veszedelem! Fuss az életedért.\"" msgid "campaignBonus|Animate Dead" msgstr "Halott feltámasztás" msgid "campaignBonus|Chain Lightning" msgstr "Láncvillám" msgid "campaignBonus|Fireblast" msgstr "Tűzrobbanás" msgid "campaignBonus|Mass Curse" msgstr "Tömeges átok" msgid "campaignBonus|Mass Haste" msgstr "Tömeges gyorsítás" msgid "campaignBonus|Mirror Image" msgstr "Tükör képmás" msgid "campaignBonus|Resurrect" msgstr "Újjáélesztés" msgid "campaignBonus|Steelskin" msgstr "AcélbÅ‘r" msgid "campaignBonus|Summon Earth" msgstr "Föld idézés" msgid "campaignBonus|View Heroes" msgstr "HÅ‘sök mutatása" msgid "campaignBonus|Ballista" msgstr "Hajítógép" msgid "campaignBonus|Black Pearl" msgstr "Fekete gyöngy" msgid "campaignBonus|Caster's Bracelet" msgstr "Varázs Karperec" msgid "campaignBonus|Defender Helm" msgstr "VédÅ‘sisak" msgid "campaignBonus|Breastplate" msgstr "Mellvért" msgid "campaignBonus|Dragon Sword" msgstr "Sárkánykard" msgid "campaignBonus|Fizbin Medal" msgstr "Fizbin medál" msgid "campaignBonus|Foremost Scroll" msgstr "LegfÅ‘bb pergamentekercs" msgid "campaignBonus|Gauntlets" msgstr "Kesztyűk" msgid "campaignBonus|Hideous Mask" msgstr "Szörnyű Maszk" msgid "campaignBonus|Mage's Ring" msgstr "Varázsló gyűrűje" msgid "campaignBonus|Major Scroll" msgstr "Nagy pergamentekercs" msgid "campaignBonus|Medal of Honor" msgstr "Becsületrend" msgid "campaignBonus|Medal of Valor" msgstr "Vitézség medál" msgid "campaignBonus|Minor Scroll" msgstr "Kis pergamentekercs" msgid "campaignBonus|Nomad Boots" msgstr "Nomád csizma" msgid "campaignBonus|Power Axe" msgstr "Hatalom fejszéje" msgid "campaignBonus|Spiked Shield" msgstr "Tüskés pajzs" msgid "campaignBonus|Stealth Shield" msgstr "Tolvaj pajzs" msgid "campaignBonus|Tax Lien" msgstr "Adósságlevél" msgid "campaignBonus|Thunder Mace" msgstr "Mennydörgés pálcája" msgid "campaignBonus|Traveler's Boots" msgstr "Utazócsizma" msgid "campaignBonus|White Pearl" msgstr "Fehér gyöngy" msgid "campaignBonus|Basic Archery" msgstr "Alap íjászat" msgid "campaignBonus|Advanced Archery" msgstr "Haladó íjászat" msgid "campaignBonus|Expert Archery" msgstr "SzakértÅ‘ íjászat" msgid "campaignBonus|Basic Ballistics" msgstr "Alap ballisztika" msgid "campaignBonus|Advanced Ballistics" msgstr "Haladó ballisztika" msgid "campaignBonus|Expert Ballistics" msgstr "SzakértÅ‘ ballisztika" msgid "campaignBonus|Basic Diplomacy" msgstr "Alap diplomácia" msgid "campaignBonus|Advanced Diplomacy" msgstr "Haladó diplomácia" msgid "campaignBonus|Expert Diplomacy" msgstr "SzakértÅ‘ diplomácia" msgid "campaignBonus|Basic Eagle Eye" msgstr "Alap sasszem" msgid "campaignBonus|Advanced Eagle Eye" msgstr "Haladó sasszem" msgid "campaignBonus|Expert Eagle Eye" msgstr "SzakértÅ‘ sasszem" msgid "campaignBonus|Basic Estates" msgstr "Alap vagyon" msgid "campaignBonus|Advanced Estates" msgstr "Haladó vagyon" msgid "campaignBonus|Expert Estates" msgstr "SzakértÅ‘ vagyon" msgid "campaignBonus|Basic Leadership" msgstr "Alap vezetés" msgid "campaignBonus|Advanced Leadership" msgstr "Haladó vezetés" msgid "campaignBonus|Expert Leadership" msgstr "SzakértÅ‘ vezetés" msgid "campaignBonus|Basic Logistics" msgstr "Alap logisztika" msgid "campaignBonus|Advanced Logistics" msgstr "Haladó logisztika" msgid "campaignBonus|Expert Logistics" msgstr "SzakértÅ‘ logisztika" msgid "campaignBonus|Basic Luck" msgstr "Alap szerencse" msgid "campaignBonus|Advanced Luck" msgstr "Haladó szerencse" msgid "campaignBonus|Expert Luck" msgstr "SzakértÅ‘ szerencse" msgid "campaignBonus|Basic Mysticism" msgstr "Alap misztika" msgid "campaignBonus|Advanced Mysticism" msgstr "Haladó misztika" msgid "campaignBonus|Expert Mysticism" msgstr "SzakértÅ‘ misztika" msgid "campaignBonus|Basic Navigation" msgstr "Alap navigáció" msgid "campaignBonus|Advanced Navigation" msgstr "Fejlett navigáció" msgid "campaignBonus|Expert Navigation" msgstr "SzakértÅ‘ navigáció" msgid "campaignBonus|Basic Necromancy" msgstr "Alap nekromancia" msgid "campaignBonus|Advanced Necromancy" msgstr "Haladó nekromancia" msgid "campaignBonus|Expert Necromancy" msgstr "SzakértÅ‘ nekromancia" msgid "campaignBonus|Basic Pathfinding" msgstr "Alap nyomkövetés" msgid "campaignBonus|Advanced Pathfinding" msgstr "Haladó nyomkövetés" msgid "campaignBonus|Expert Pathfinding" msgstr "SzakértÅ‘ nyomkövetés" msgid "campaignBonus|Basic Scouting" msgstr "Alap felderítés" msgid "campaignBonus|Advanced Scouting" msgstr "Haladó felderítés" msgid "campaignBonus|Expert Scouting" msgstr "SzakértÅ‘ felderítés" msgid "campaignBonus|Basic Wisdom" msgstr "Alap bölcsesség" msgid "campaignBonus|Advanced Wisdom" msgstr "Haladó bölcsesség" msgid "campaignBonus|Expert Wisdom" msgstr "SzakértÅ‘ bölcsesség" msgid "" "The main hero will have \"%{artifact}\" artifact at the start of the " "scenario." msgstr "A fÅ‘hÅ‘s \"%{artifact}\" varázstárggyal kezdi a küldetést." msgid "" "The kingdom will receive %{amount} additional %{resource} at the start of " "the scenario." msgstr "" "A királyság %{amount} %{resource} plusz erÅ‘forrással indul a küldetésben." msgid "" "The kingdom will have %{amount} less %{resource} at the start of the " "scenario." msgstr "" "A királyság %{amount} %{resource} erÅ‘forrással kevesebbel indul a " "küldetésben." msgid "" "The main hero will have %{count} %{monster} at the start of the scenario." msgstr "A fÅ‘hÅ‘s %{count} %{monster} lénnyel kezdi meg a küldetést." msgid "" "The main hero will have \"%{spell}\" spell at the start of the scenario." msgstr "A fÅ‘hÅ‘s \"%{spell}\" varázslat birtokában kezdi meg a küldetést." msgid "The starting race of the scenario will be %{race}." msgstr "A(z) %{race} kezd a küldetésben." msgid "" "The main hero will have additional %{count} %{skill} at the start of the " "scenario." msgstr "" "A fÅ‘hÅ‘s további %{count} %{skill} képesség birtokában kezdi a küldetést." msgid "The main hero will have %{skill} at the start of the scenario." msgstr "A fÅ‘hÅ‘s %{skill} képesség birtokában kezdi a küldetést." msgid "Roland" msgstr "Roland" msgid "Archibald" msgstr "Archibald" #, fuzzy msgid "Price of Loyalty" msgstr "A hűség ára" msgid "Voyage Home" msgstr "Hazaút" msgid "Wizard's Isle" msgstr "Varázsló-sziget" msgid "Descendants" msgstr "Utódok" msgid "The %{building} produces %{monster}." msgstr "A(z) %{building} %{monster}-t hoz létre." msgid "Requires:" msgstr "ElÅ‘feltétel:" msgid "Exit this menu without doing anything." msgstr "Kilépés ebbÅ‘l a menübÅ‘l, bármilyen cselekvés nélkül." msgid "Cannot build. You have already built here today." msgstr "Nem építhetÅ‘. Már építettél ide ebben körben." msgid "For this action it is necessary to build a castle first." msgstr "Ehhez a művelethez elÅ‘ször a várat kell felépíteni." msgid "Cannot build %{name} because the castle is too far away from an ocean." msgstr "%{name}: nem lehet megépíteni, mert a vár túl messze van a víztÅ‘l." msgid "disable build." msgstr "építés letiltása." msgid "Cannot afford %{name}." msgstr "Nem engedheted meg magadnak %{name}-t." msgid "%{name} is already built." msgstr "%{name} már fel van építve." msgid "Cannot build %{name}." msgstr "Nem tudod felépíteni %{name}-t." msgid "Build %{name}." msgstr "%{name} építése." msgid "Blackridge" msgstr "Blackridge" msgid "Hillstone" msgstr "Hillstone" msgid "Pinehurst" msgstr "Pinehurst" msgid "Whiteshield" msgstr "Whiteshield" msgid "Woodhaven" msgstr "Woodhaven" msgid "Blackwind" msgstr "Blackwind" msgid "Bloodreign" msgstr "Bloodreign" msgid "Dragontooth" msgstr "Dragontooth" msgid "Greywind" msgstr "Greywind" msgid "Portsmith" msgstr "Portsmith" msgid "Atlantium" msgstr "Atlantium" msgid "Middle Gate" msgstr "Middle Gate" msgid "Sansobar" msgstr "Sansobar" msgid "Tundara" msgstr "Tundara" msgid "Vulcania" msgstr "Vulcania" msgid "Baywatch" msgstr "Baywatch" msgid "Fountainhead" msgstr "Fountainhead" msgid "Vertigo" msgstr "Vertigo" msgid "Wildabar" msgstr "Wildabar" msgid "Winterkill" msgstr "Winterkill" msgid "Brindamoor" msgstr "Brindamoor" msgid "Lakeside" msgstr "Lakeside" msgid "Nightshadow" msgstr "Nightshadow" msgid "Olympus" msgstr "Olümposz" msgid "Sandcaster" msgstr "Sandcaster" msgid "Alamar" msgstr "Alamar" msgid "Burlock" msgstr "Burlock" msgid "Dragadune" msgstr "Dragadune" msgid "Kalindra" msgstr "Kalindra" msgid "Xabran" msgstr "Xabran" msgid "Algary" msgstr "Algary" msgid "Basenji" msgstr "Basenji" msgid "Blackfang" msgstr "Blackfang" msgid "New Dawn" msgstr "New Dawn" msgid "Sorpigal" msgstr "Sorpigal" msgid "Avone" msgstr "Avone" msgid "Big Oak" msgstr "Big Oak" msgid "Chandler" msgstr "Chandler" msgid "Erliquin" msgstr "Erliquin" msgid "Hampshire" msgstr "Hampshire" msgid "Antioch" msgstr "Antiókhia" msgid "Avalon" msgstr "Avalon" msgid "Roc Haven" msgstr "Rukh menedék" msgid "South Mill" msgstr "South Mill" msgid "Weed Patch" msgstr "Weed Patch" msgid "Brownston" msgstr "Brownston" msgid "Hilltop" msgstr "Hilltop" msgid "Weddington" msgstr "Weddington" msgid "Westfork" msgstr "Westfork" msgid "Whittingham" msgstr "Whittingham" msgid "Cathcart" msgstr "Cathcart" msgid "Elk's Head" msgstr "ElkfÅ‘" msgid "Roscomon" msgstr "Roscomon" msgid "Sherman" msgstr "Sherman" msgid "Yorksford" msgstr "Yorksford" msgid "Blackburn" msgstr "Blackburn" msgid "Blacksford" msgstr "Blacksford" msgid "Burton" msgstr "Burton" msgid "Pig's Eye" msgstr "Pig's Eye" msgid "Viper's Nest" msgstr "Viperafészek" msgid "Fenton" msgstr "Fenton" msgid "Lankershire" msgstr "Lankershire" msgid "Lombard" msgstr "Lombard" msgid "Timberhill" msgstr "Timberhill" msgid "Troy" msgstr "Trója" msgid "Forder Oaks" msgstr "Forder Oaks" msgid "Meramec" msgstr "Meramec" msgid "Quick Silver" msgstr "Quick Silver" msgid "Westmoor" msgstr "Westmoor" msgid "Willow" msgstr "Willow" msgid "Corackston" msgstr "Corackston" msgid "Sheltemburg" msgstr "Sheltemburg" msgid "Cannot recruit - you already have a Hero in this town." msgstr "Nem lehet tobrozni - van már hÅ‘söd ebben a városban." msgid "Cannot recruit - you have too many Heroes." msgstr "Nem lehet tobrozni - túl sok hÅ‘söd van." msgid "Cannot afford a Hero" msgstr "Nem vásárolható meg a hÅ‘s" msgid "There is no room in the garrison for this army." msgstr "Nincs elég hely a helyÅ‘rségben a csapat számára." msgid "Fortifications" msgstr "ErÅ‘dítmények" msgid "Farm" msgstr "Szántóföldek" msgid "Thatched Hut" msgstr "Nádkunyhó" msgid "Archery Range" msgstr "Ãjászpálya" msgid "Upg. Archery Range" msgstr "Fejl. íjászpálya" msgid "Blacksmith" msgstr "Kovácsműhely" msgid "Upg. Blacksmith" msgstr "Fejl. kovácsműhely" msgid "Armory" msgstr "Fegyverkovács" msgid "Upg. Armory" msgstr "Fejl. fegyverkovács" msgid "Jousting Arena" msgstr "Lovarda" msgid "Upg. Jousting Arena" msgstr "Fejl. lovarda" msgid "Cathedral" msgstr "Katedrális" msgid "Upg. Cathedral" msgstr "Fejl. katedrális" msgid "Coliseum" msgstr "Kolosszeum" msgid "Garbage Heap" msgstr "Szemétdomb" msgid "Hut" msgstr "Kunyhó" msgid "Stick Hut" msgstr "Fakunyhó" msgid "Upg. Stick Hut" msgstr "Fejl. fakunyhó" msgid "Den" msgstr "Barlang" msgid "Adobe" msgstr "Vályogház" msgid "Upg. Adobe" msgstr "Fejl. vályogház" msgid "Bridge" msgstr "Híd" msgid "Upg. Bridge" msgstr "Fejl. híd" msgid "Pyramid" msgstr "Piramis" msgid "Rainbow" msgstr "Szivárvány" msgid "Crystal Garden" msgstr "Kristálykert" msgid "Treehouse" msgstr "Fa ház" msgid "Cottage" msgstr "Törp kunyhó" msgid "Upg. Cottage" msgstr "Fejl. törp kunyhó" msgid "Stonehenge" msgstr "KÅ‘kör" msgid "Upg. Stonehenge" msgstr "Fejl. kÅ‘kör" msgid "Fenced Meadow" msgstr "LegelÅ‘" msgid "sorceress|Red Tower" msgstr "Vörös torony" msgid "Dungeon" msgstr "Kazamata" msgid "Waterfall" msgstr "Vízesés" msgid "Cave" msgstr "Barlang" msgid "Crypt" msgstr "Kripta" msgid "Nest" msgstr "Fészek" msgid "Maze" msgstr "ÚtvesztÅ‘" msgid "Upg. Maze" msgstr "Fejl. útvesztÅ‘" msgid "Swamp" msgstr "Mocsár" msgid "Green Tower" msgstr "Zöld torony" msgid "warlock|Red Tower" msgstr "Vörös torony" msgid "Black Tower" msgstr "Fekete torony" msgid "Library" msgstr "Könyvtár" msgid "Orchard" msgstr "Gyümölcsöskert" msgid "Habitat" msgstr "Félszerzet-lak" msgid "Pen" msgstr "Akol" msgid "Foundry" msgstr "Öntöde" msgid "Upg. Foundry" msgstr "Fejl. Öntöde" msgid "Cliff Nest" msgstr "Sziklafészek" msgid "Ivory Tower" msgstr "Elefántcsont torony" msgid "Upg. Ivory Tower" msgstr "Fejl. elefántcsont torony" msgid "Cloud Castle" msgstr "Fellegvár" msgid "Upg. Cloud Castle" msgstr "Fejl. fellegvár" msgid "Storm" msgstr "Vihar" msgid "Skull Pile" msgstr "Csonthalom" msgid "Excavation" msgstr "Ãsatás" msgid "Graveyard" msgstr "Sírkert" msgid "Upg. Graveyard" msgstr "Fejl. sírkert" msgid "Upg. Pyramid" msgstr "Fejl. piramis" msgid "Mansion" msgstr "Kúria" msgid "Upg. Mansion" msgstr "Fejl. kúria" msgid "Mausoleum" msgstr "Mauzóleum" msgid "Upg. Mausoleum" msgstr "Fejl. mauzóleum" msgid "Laboratory" msgstr "Laboratórium" msgid "Shrine" msgstr "Szentély" msgid "" "The Fortifications increase the toughness of the walls, increasing the " "number of turns it takes to knock them down." msgstr "" "Az ErÅ‘dítmények növelik a várfalak védelmét, melynek hatására több lövéssel " "lehet lerombolni Å‘ket." msgid "The Farm increases production of Peasants by %{count} per week." msgstr "" "A szántóföldek a jobbágyok szaporulatát hetente ennyivel növelik: %{count}." msgid "" "The Coliseum provides inspiring spectacles to defending troops, raising " "their morale by two during combat." msgstr "" "A Kolosszeum látványa lelkesíti a védÅ‘ket, ezért a moráljuk kettÅ‘vel nÅ‘ a " "csata idejére." msgid "The Garbage Heap increases production of Goblins by %{count} per week." msgstr "" "A Szemétdomb a Goblinok szaporulatát hetente ennyivel növeli: %{count}." msgid "The Rainbow increases the luck of the defending units by two." msgstr "A Szivárvány növeli a védÅ‘egységek szerencséjét kettÅ‘vel." msgid "" "The Crystal Garden increases production of Sprites by %{count} per week." msgstr "" "A Kristálykert a Tündérek szaporulatát hetente ennyivel növeli: %{count}." msgid "The Dungeon increases the income of the town by %{count} gold per day." msgstr "A Várbörtön naponta %{count} arannyal növeli a város bevételét." msgid "The Waterfall increases production of Centaurs by %{count} per week." msgstr "A Vízesés a Kentaurok szaporulatát hetente ennyivel növeli: %{count}." msgid "" "The Library increases the number of spells in the Guild by one for each " "level of the guild." msgstr "" "A könyvtár növeli a varázstorony varázslatainak számát eggyel mindegyik " "szinten." msgid "The Orchard increases production of Halflings by %{count} per week." msgstr "" "A Gyümölcsöskert a Félszeretek szaporulatát hetente ennyivel növeli: " "%{count}." msgid "The Storm adds +2 to the power of spells of a defending spell caster." msgstr "A Vihar növeli a védÅ‘ varázserejét kettÅ‘vel." msgid "The Skull Pile increases production of Skeletons by %{count} per week." msgstr "" "A Koponyahalom a Csontvázak termelÅ‘dését hetente ennyivel növeli: %{count}." msgid "Thieves' Guild" msgstr "Tolvajcéh" msgid "Tavern" msgstr "Fogadó" msgid "Shipyard" msgstr "Hajógyár" msgid "Well" msgstr "Kút" msgid "Statue" msgstr "Szobor" msgid "Marketplace" msgstr "Piactér" msgid "Moat" msgstr "Várárok" msgid "Castle" msgstr "Vár" msgid "Tent" msgstr "Sátor" msgid "Captain's Quarters" msgstr "Kapitány Å‘rház" msgid "Mage Guild, Level 1" msgstr "Varázstorony, 1. szint" msgid "Mage Guild, Level 2" msgstr "Varázstorony, 2. szint" msgid "Mage Guild, Level 3" msgstr "Varázstorony, 3. szint" msgid "Mage Guild, Level 4" msgstr "Varázstorony, 4. szint" msgid "Mage Guild, Level 5" msgstr "Varázstorony, 5. szint" msgid "" "The Shrine increases the necromancy skill of all your necromancers by 10 " "percent." msgstr "" "A Szentély növeli az összes nekromantád nekromancia képességét 10%-kal." msgid "" "The Thieves' Guild provides information on enemy players. Thieves' Guilds " "can also provide scouting information on enemy towns. Additional Guilds " "provide more information." msgstr "" "A Tolvajcéh tagjai hasznos információkkal tudnak szolgálni az ellenfelekrÅ‘l, " "illetve kémkedhetnek az ellenséges városokban." msgid "The Tavern increases morale for troops defending the castle." msgstr "A Fogadó növeli a várvédÅ‘ katonák morálját." msgid "The Shipyard allows ships to be built." msgstr "A Hajógyárban építhetsz hajókat." msgid "" "The Well increases the growth rate of all dwellings by %{count} creatures " "per week." msgstr "A Kút hetente ennyivel növeli az összes lény szaporulatát: %{count}." msgid "The Statue increases the town's income by %{count} gold per day." msgstr "A Szobor naponta ennyivel növeli a város bevételét: %{count}." msgid "The Left Turret provides extra firepower during castle combat." msgstr "A Bal Torony megnöveli a várvédÅ‘id tűzerejét ostrom idején." msgid "The Right Turret provides extra firepower during castle combat." msgstr "A Jobb Torony megnöveli a várvédÅ‘id tűzerejét ostrom idején." msgid "" "The Marketplace can be used to convert one type of resource into another. " "The more marketplaces you control, the better the exchange rate." msgstr "" "A Piactéren vásárolhatsz és eladhatsz nyersanyagokat. Több Piactér " "birtoklása javítja az átváltási arányt." msgid "" "The Moat slows attacking units. Any unit entering the moat must end its turn " "there and becomes more vulnerable to attack." msgstr "" "A Vizesárok lassítja a támadó egységeket. Minden egység, aki belelép az " "árokba, ott be kell fejezze a körét és sebezhetÅ‘bbé válik." msgid "" "The Castle improves the town's defense and increases its income to %{count} " "gold per day." msgstr "" "A Vár javítja a város védelmét és naponta %{count} arannyal növeli a " "bevételét." msgid "" "The Tent provides workers to build a castle, provided the materials and the " "gold are available." msgstr "" "A Sátor biztosítja a munkásokat a vár felépítéséhez a megfelelÅ‘ mennyiségű " "nyersanyag és arany rendelkezésre állása esetén." msgid "" "The Captain's Quarters provides a captain to assist in the castle's defense " "when no hero is present." msgstr "" "A Kapitány Å‘rház egy várkapitányt biztosít, aki segít a város védelmében, " "amikor nincs hÅ‘s a várban." msgid "" "The Mage Guild allows heroes to learn spells and replenish their spell " "points." msgstr "" "A Varázstorony lehetÅ‘vé teszi varázslatok megtanulását és a varázspontok " "feltöltését." msgid "Recruit %{name}" msgstr "%{name} toborzása" msgid "Month: %{month}, Week: %{week}, Day: %{day}" msgstr "Hónap: %{month}, hét: %{week}, nap: %{day}" msgid "" "You must purchase a spell book to use the mage guild, but you currently have " "no room for a spell book. Try giving one of your artifacts to another hero." msgstr "" "Ahhoz, hogy használhasd a varázstoronyban lévÅ‘ varázslatokat, szükséged van " "varázskönyvre, de nincs szabad hely a könyv számára. Próbálj meg egy " "varázstárgyat átadni egy másik hÅ‘snek." msgid "Click to show next town." msgstr "Kattints a következÅ‘ város mutatásához." msgid "Show next town" msgstr "KövetkezÅ‘ város mutatása" msgid "Click to show previous town." msgstr "Kattints az elÅ‘zÅ‘ város mutatásához." msgid "Show previous town" msgstr "ElÅ‘zÅ‘ város mutatása" msgid "This town may not be upgraded to a castle." msgstr "Ezt a várost nem lehet várrá fejleszteni." msgid "Town" msgstr "Város" msgid "Exit Castle" msgstr "Kilépés a várból" msgid "Exit Town" msgstr "Kilépés a városból" msgid "Show Income" msgstr "Jövedelem mutatása" msgid "View Hero" msgstr "HÅ‘s mutatása" msgid "The above spells are available here." msgstr "A fenti varázslatok érhetÅ‘k el itt." msgid "The spells the hero can learn have been added to their book." msgstr "A hÅ‘s által megtanulható varázslatok beíródtak a varázskönyvébe." msgid "A generous tip for the barkeep yields the following rumor:" msgstr "" "A nagylelkű borravalód hatására a csapos a következÅ‘ szóbeszédet osztja meg " "veled:" msgid "Recruit Hero" msgstr "HÅ‘s toborzása" msgid "%{name} is a level %{value} %{race} " msgstr "%{name} %{value}. szintű %{race}" msgid "with %{count} artifacts." msgstr "%{count} varázstárggyal" msgid "with 1 artifact." msgstr "egy varázstárggyal" msgid "without artifacts." msgstr "varázstárgyak nélkül." msgid "Recruit %{name} the %{race}" msgstr "%{name} a %{race} toborzása" #, fuzzy msgid "" "'Spread' combat formation spreads the castle's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "'Laza' csataalakzatban az egységeid szétszóródva helyezkednek el a harcmezÅ‘ " "egyik oldalán (legalább 1 üres hely van közöttük)." #, fuzzy msgid "" "'Grouped' combat formation bunches the castle's units together in the center " "of the castle's side of the battlefield." msgstr "" "'Szoros' csataalakzatban az egységeid szorosan egymás mellett helyezkednek " "el a harcmezÅ‘ oldalának közepén." msgid "Spread Formation" msgstr "Laza felállás" msgid "Grouped Formation" msgstr "Szoros felállás" msgid "Set garrison combat formation to 'Spread'" msgstr "A helyÅ‘rség csataalakzata legyen 'Laza'." msgid "Set garrison combat formation to 'Grouped'" msgstr "A helyÅ‘rség csataalakzata legyen 'Szoros'." msgid "Exit Castle Options" msgstr "Kilépés a vár beállításokból" msgid "Castle Options" msgstr "Vár beállítások" msgid "Not enough resources to recruit creatures." msgstr "Nincs elég erÅ‘forrás a lények toborzásához." msgid "You are unable to recruit at this time, your ranks are full." msgstr "Most nem tudsz toborozni, a csapatod tele van." msgid "No creatures available for purchase." msgstr "Nincsenek megvásárolható lények." msgid "Recruit Creatures" msgstr "Lények toborzása" msgid "Max" msgstr "Max" msgid "Hire all creatures in the town." msgstr "Összes lény toborzása." msgid "Available" msgstr "ElérhetÅ‘" msgid "Town Population Information and Statistics" msgstr "A város lakosságának adatai és statisztikái" msgid "Damage" msgstr "Sebzés" msgid "HP" msgstr "ÉleterÅ‘" msgid "Growth" msgstr "Szaporulat" msgid "week" msgstr "hét" msgid "View World" msgstr "Világ mutatása" msgid "View the entire world." msgstr "Az egész világ mutatása." msgid "Puzzle" msgstr "Rejtvény" msgid "View the obelisk puzzle." msgstr "Az obeliszk rejtvény mutatása" msgid "Scenario Information" msgstr "Küldetés információ" msgid "View information on the scenario you are currently playing." msgstr "Nézd meg az éppen játszott küldetés információit." msgid "Dig for the Ultimate Artifact." msgstr "A Szent Varázstárgy kiásása." msgid "Digging" msgstr "Ãsás" msgid "Arena" msgstr "Aréna" msgid "" "You enter the arena and face a pack of vicious lions. You handily defeat " "them, to the wild cheers of the crowd. Impressed by your skill, the aged " "trainer of gladiators agrees to train you in a skill of your choice." msgstr "" "Belépve a küzdÅ‘térre egy csomó megvadult oroszlánnal találod szembe magad, " "melyeket könnyedén leterítesz a tömeg éljenzése közepette. Látva a " "tehetségedet az idÅ‘s gladiátormester felajánlja, hogy kiképzést ad neked egy " "választott képességbÅ‘l." msgid "Attack Skill" msgstr "TámadóerÅ‘" msgid "Defense Skill" msgstr "VédÅ‘erÅ‘" msgid "Shots" msgstr "Lövések" msgid "Shots Left" msgstr "Maradék lövés" msgid "Hit Points" msgstr "ÉleterÅ‘" msgid "Hit Points Left" msgstr "Maradék élete." msgid "You can't afford to upgrade your troops!" msgstr "Nem engedheted meg magadnak a csapatok fejlesztését!" msgid "" "Your troops can be upgraded, but it will cost you dearly. Do you wish to " "upgrade them?" msgstr "" "Felfejlesztheted a csoportot, de ez többe kerül. Fel szeretnéd fejleszteni " "Å‘ket?" msgid "Are you sure you want to dismiss this army?" msgstr "Biztos vagy benne hogy feloszlatod ezt a sereget?" msgid "Upgrade" msgstr "Fejlesztés" msgid "Upgrade your troops." msgstr "Fejleszti a csapataidat." msgid "Dismiss" msgstr "Elbocsátás" msgid "Dismiss this army." msgstr "Sereg feloszlatása." msgid "" "A group of %{monster} with a desire for greater glory wish to join you.\n" "Do you accept?" msgstr "" "%{monster} csoportja a nagyobb dicsÅ‘ség reményében csatlakozna hozzád.\n" "Elfogadod Å‘ket?" msgid "Followers" msgstr "Csatlakozók" msgid "" "The %{monster} is swayed by your diplomatic tongue, and offers to join your " "army for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "%{monster} a diplomatikus modorod hatására hajlanak rá, hogy csatlakozzanak " "hozzád %{gold} arany ellenében.\n" "Elfogadod?" msgid "" "The creatures are swayed by your diplomatic\n" "tongue, and make you an offer:\n" "\n" msgstr "" "A diplomáciai képességed hatására a lények \n" "egy ajánlattal állnak elÅ‘:\n" "\n" msgid "" "%{offer} of the %{total} %{monster} will join your army, and the rest will " "leave you alone, for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "%{total} lénybÅ‘l %{offer} %{monster} csatlakozna hozzád összesen %{gold} " "aranyért. A többiek pedig szabadon távoznának.\n" "Elfogadod az ajánlatot?" msgid "" "All %{offer} of the %{monster} will join your army for the sum of %{gold} " "gold.\n" "Do you accept?" msgstr "" "%{offer} %{monster} mindegyike csatlakozna hozzád összesen %{gold} " "aranyért.\n" "Elfogadod az ajánlatukat?" msgid "(Rate: %{percent})" msgstr "(arány: %{count})" msgid "off" msgstr "ki" msgid "Music" msgstr "Zene" msgid "Effects" msgstr "Effektusok" msgid "MIDI" msgstr "MIDI" msgid "MIDI Expansion" msgstr "MIDI kiegészítÅ‘" msgid "External" msgstr "KülsÅ‘" msgid "Music Type" msgstr "Zene típusa" msgid "3D Audio" msgstr "3D hangzás" msgid "Toggle ambient music level." msgstr "Háttérzene jelszint kapcsolója." msgid "Toggle foreground sounds level." msgstr "Hanghatások jelszint kapcsolója." msgid "Change the type of music." msgstr "Zene típusának megváltoztatása." msgid "Toggle 3D effects of foreground sounds." msgstr "Hanghatások 3D kapcsolója." msgid "Build a new ship:" msgstr "Új hajó építése:" msgid "Resource cost:" msgstr "ErÅ‘forrás költség:" msgid "Total: " msgstr "Összesen: " msgid "Need: " msgstr "Szükséges: " msgid "Load Game" msgstr "Játék betöltése" msgid "No save files to load." msgstr "Nincsenek mentések a betöltéshez." msgid "New Game" msgstr "Új játék" msgid "Start a single or multi-player game." msgstr "Egy- vagy többszemélyes játék indítása." msgid "Load a previously saved game." msgstr "Mentett játék betöltése." msgid "Save Game" msgstr "Játék mentése" msgid "Save the current game." msgstr "Aktuális játék mentése." msgid "Quit" msgstr "Kilépés" msgid "Quit out of Heroes of Might and Magic II." msgstr "Kilépés a Heroes of Might and Magic II-bÅ‘l." msgid "Language" msgstr "Nyelv" msgid "Graphics" msgstr "Grafika" msgid "Black & White" msgstr "Fekete-fehér" msgid "Mouse Cursor" msgstr "Egér kurzor" msgid "Color" msgstr "Szín" msgid "Text Support" msgstr "Szöveg támogatás" msgid "Change the language of the game." msgstr "A játék nyelvének megváltoztatása." msgid "Select Game Language" msgstr "Játék nyelvének kiválasztása" msgid "Change the graphics settings of the game." msgstr "Játék grafikai beállításainak módosítása." msgid "Toggle colored cursor on or off. This is only an aesthetic choice." msgstr "" "Színes kurzor be-/kikapcsolása. Ez csak egy esztétikai jellegű választás." msgid "" "Toggle text support mode to output extra information about windows and " "events in the game." msgstr "" "A szöveges támogatás kapcsolója, mellyel extra információkhoz juthatunk a " "játék párbeszédablakairól, illetve eseményeirÅ‘l." msgid "" "Map\n" "Difficulty" msgstr "" "Térkép\n" "Nehézség" msgid "" "Game\n" "Difficulty" msgstr "" "Játék\n" "Nehézség" msgid "Rating" msgstr "Besorolás" msgid "Map Size" msgstr "" "Térkép\n" "mérete" msgid "Opponents" msgstr "Ellenfelek" msgid "Class" msgstr "Osztály" msgid "" "Victory\n" "Conditions" msgstr "" "GyÅ‘zelmi\n" "Feltételek" msgid "" "Loss\n" "Conditions" msgstr "" "Vesztési\n" "Feltételek" msgid "First select recipients!" msgstr "ElÅ‘ször válaszd ki a kedvezményezetteket!" msgid "You cannot select %{resource}!" msgstr "Nem választhatod a(z) %{resource} erÅ‘forrást!" msgid "Select count %{resource}:" msgstr "A számításba veendÅ‘ %{resource} kiválasztása:" msgid "Select Recipients" msgstr "Kedvezményezettek kiválasztása" msgid "Your Funds" msgstr "Forrásaid" msgid "Planned Gift" msgstr "Tervezett ajándék" msgid "Gift from %{name}" msgstr "Ajándék %{name} játékostól" msgid "Resolution" msgstr "Felbontás" msgid "Fullscreen" msgstr "Teljes képernyÅ‘" msgid "window|Mode" msgstr "Mód" msgid "Windowed" msgstr "Ablakos" msgid "V-Sync" msgstr "V-Sync" msgid "on" msgstr "be" msgid "FPS" msgstr "FPS" msgid "System Info" msgstr "" "Rendszer-\n" "információk" msgid "Change the resolution of the game." msgstr "A játék felbontásának megváltoztatása." msgid "Select Game Resolution" msgstr "Játék felbontás kiválasztása" msgid "Toggle between fullscreen and windowed modes." msgstr "Váltás teljes képernyÅ‘s és ablakos mód között." msgid "" "The V-Sync option can be enabled to resolve flickering issues on some " "monitors." msgstr "" "A V-Sync opciót akkor érdemes használni, amikor egyes monitorokon vibrálást " "tapasztalunk." msgid "Show extra information such as FPS and current time." msgstr "Extra információkat jelenít meg, mint például FPS, illetve pontos idÅ‘." msgid "Hotkey: " msgstr "Gyorsbillentyű: " msgid "Category: " msgstr "Kategória: " msgid "Event: " msgstr "Esemény: " msgid "Hot Keys:" msgstr "Billentyűfunkciók:" msgid "Evil" msgstr "Gonosz" msgid "Good" msgstr "Jó" msgid "Interface Type" msgstr "KezelÅ‘felület típusa" msgid "Hide" msgstr "Elrejt" msgid "Show" msgstr "Mutat" msgid "Interface" msgstr "KezelÅ‘felület" msgid "Slow" msgstr "Lassú" msgid "Normal" msgstr "Normál" msgid "Fast" msgstr "Gyors" msgid "Very Fast" msgstr "Nagyon gyors" msgid "Scroll Speed" msgstr "KépernyÅ‘görgetés sebessége" msgid "Toggle the type of interface you want to use." msgstr "KezelÅ‘felület kívánt típusának kapcsolója." msgid "Toggle interface visibility." msgstr "KezelÅ‘felület láthatóságának kapcsolója." msgid "Toggle colored cursor on or off. This is only an esthetic choice." msgstr "" "Színes kurzor be-/kikapcsolása. Ez csak egy esztétikai jellegű választás." msgid "Sets the speed at which you scroll the window." msgstr "Beállítja az ablakok görgetésének sebességét." msgid "Select Game Language:" msgstr "Játék nyelvének kiválasztása:" msgid "Click to choose the selected language." msgstr "Kattints a kijelölt nyelv kiválasztásához." msgid "%{name} has gained a level." msgstr "%{name} a következÅ‘ szintre lépett." msgid "%{skill} +1" msgstr "%{skill} +1" msgid "You have learned %{skill}." msgstr "%{skill} képességre tettél szert." msgid "" "%{name} has gained a level.\n" "\n" "%{skill} +1" msgstr "" "%{name} a következÅ‘ szintre lépett.\n" "\n" "%{skill} +1" msgid "" "You may learn either:\n" "%{skill1}\n" "or\n" "%{skill2}" msgstr "" "Tanulható:\n" "%{skill1}\n" "vagy\n" "%{skill2}" msgid "" "Please inspect our fine wares. If you feel like offering a trade, click on " "the items you wish to trade with and for." msgstr "" "Kérem tekintsd meg nagyszerű portékáinkat. Amennyiben kereskedni kívánsz, " "kattints arra az elemre, amit és amire cserélni akarsz." msgid "" "You have received quite a bargain. I expect to make no profit on the deal. " "Can I interest you in any of my other wares?" msgstr "" "Jó alkut kötöttél. Nem várok el nyereséget az üzleten. Érdekel esetleg más " "áru is?" msgid "I can offer you %{count} for 1 unit of %{resfrom}." msgstr "%{count}-t ajánlok 1 darab %{resfrom}-ért." msgid "I can offer you 1 unit of %{resto} for %{count} units of %{resfrom}." msgstr "1 darab %{resto}-t ajánlok %{count} darab %{resfrom}-ért." msgid "Min" msgstr "Min" msgid "Qty to trade" msgstr "darab eladása" msgid "Trading Post" msgstr "Kereskedelmi Ãllomás" msgid "Your Resources" msgstr "ErÅ‘forrásaid" msgid "Available Trades" msgstr "Üzleti lehetÅ‘ségek" msgid "n/a" msgstr "n/a" msgid "guarded by %{count} %{monster}" msgstr "%{count} %{monster} Å‘rzi." msgid "guarded by " msgstr "Å‘rzÅ‘k: " msgid "(available: %{count})" msgstr "(elérhetÅ‘: %{count})" msgid "(empty)" msgstr "(üres)" msgid "already learned" msgstr "már ismered" msgid "treeOfKnowledge|free" msgstr "ingyenes" msgid "already claimed" msgstr "már megszerzett" msgid "not claimed" msgstr "még nem megszerzett" msgid "already knows this skill" msgstr "már bírsz ezzel a képességgel" msgid "already has max skills" msgstr "már elérted a max. képességet" msgid "(already visited)" msgstr "(már meglátogatott)" msgid "(not visited)" msgstr "(nem látogatott)" msgid "%{color} Barrier" msgstr "%{color} útakadály" msgid "(tent visited)" msgstr "(nem látogatott)" msgid "%{color} Tent" msgstr "%{color} sátor" msgid "Road" msgstr "Út" msgid "(digging ok)" msgstr "(ásható)" msgid "(no digging)" msgstr "(nem ásható)" msgid "penalty: %{cost}" msgstr "büntetés: %{cost}" msgid "Defenders:" msgstr "VédÅ‘k:" msgid "Unknown" msgstr "Ismeretlen" msgid "%{name} (Level %{level})" msgstr "%{name} (%{level}. szint)" msgid "Attack:" msgstr "Támadás:" msgid "Defense:" msgstr "Védekezés:" msgid "Spell Power:" msgstr "VarázserÅ‘:" msgid "Knowledge:" msgstr "Tudás:" msgid "Spell Points:" msgstr "Varázspontok:" msgid "Move Points:" msgstr "Mozgáspontok:" msgid "Uncharted Territory" msgstr "Felderítetlen terület" msgid "Cost per troop:" msgstr "Költség egységenként:" msgid "Available: %{count}" msgstr "Toborozható: %{count}" msgid "Number to buy:" msgstr "Vétel (db):" msgid "Recruit selected monsters." msgstr "A kiválasztott lények toborzása." msgid "Select maximum monsters to be recruited." msgstr "A lények maximális számának kiválasztása toborzáshoz." msgid "Select only 1 monster to be recruited." msgstr "Csak egyetlen lény kiválasztása toborzáshoz." msgid "Select Game Resolution:" msgstr "Játék felbontás kiválasztása:" msgid "Click to apply the selected resolution." msgstr "Kattints a kiválasztott képernyÅ‘felbontás alkalmazásához." msgid "Click to apply the entered text." msgstr "Kattints a begépelt szöveg elküldéséhez." msgid "Click to open the Virtual Keyboard dialog." msgstr "Kattints a virtuális billentyűzet dialógusablak megnyitásához." msgid "Open Virtual Keyboard" msgstr "Virtuális billentyűzet megnyitása" msgid "How many troops to move?" msgstr "Hány egységet mozgassunk?" msgid "Fast separation into slots:" msgstr "Gyors szétosztás:" msgid "Map: " msgstr "Térkép: " msgid "" "\n" "\n" "Month: " msgstr "" "\n" "\n" "Hónap: " msgid ", Week: " msgstr ", Hét: " msgid ", Day: " msgstr ", Nap: " msgid "" "\n" "\n" "Location: " msgstr "" "\n" "\n" "Hely: " msgid "Click to save the current game." msgstr "Kattints az aktuális játékállás mentéséhez." msgid "Click to load a previously saved game." msgstr "Kattints a mentett játékállás betöltéséhez." msgid "Are you sure you want to delete file:" msgstr "Biztosan törlöd ezt a fájlt?:" msgid "Warning!" msgstr "Figyelem!" msgid "File to Save:" msgstr "Fájl mentése:" msgid "File to Load:" msgstr "Fájl betöltése:" msgid "Accept the choice made." msgstr "A választás elfogadása." msgid "%{color} %{race} hero" msgstr "%{color} %{race} hÅ‘s" msgid "Terrain object" msgstr "Szárazföldi objektum" msgid "Select Skill:" msgstr "Képesség kiválasztása:" msgid "Select Spell:" msgstr "Varázslat kiválasztása:" msgid "Select Artifact:" msgstr "Varázstárgy kiválasztása:" msgid "Select Monster:" msgstr "Lény kiválasztása:" msgid "Select Hero:" msgstr "HÅ‘s kiválasztása:" msgid "Select Treasure:" msgstr "Kincs kiválasztása:" msgid "Select Ocean Object:" msgstr "Tengeri objektum kiválasztása:" msgid "Castle/town placing" msgstr "Vár/város elhelyezés" msgid "doubleLinedRace|Neutral" msgstr "Semleges" msgid "race|Random" msgstr "Véletlen" msgid "Click to start placing the selected castle/town." msgstr "Kattints a kiválasztott vár/város elhelyezéséhez." msgid "Click to select town placing." msgstr "Kattints a következÅ‘ város elhelyezéséhez." msgid "Click to select castle placing." msgstr "Kattints a következÅ‘ vár elhelyezéséhez." msgid "%{color} %{race} %{townOrCastle}" msgstr "%{color} %{race} %{townOrCastle}" msgid "race|Neutral" msgstr "Semleges" msgid "You will place" msgstr "El fogod helyezni" msgid "Click to select this class." msgstr "Kattints az osztály kiválasztásához." msgid "Click to select this color." msgstr "Kattints a szín kiválasztásához." #, fuzzy msgid "Select Dwelling:" msgstr "Varázslat kiválasztása:" #, fuzzy msgid "Select Landscape Object:" msgstr "Tengeri objektum kiválasztása:" msgid "Mine placing" msgstr "" #, fuzzy msgid "" "Resource\n" "type:" msgstr "ErÅ‘forrás" #, fuzzy msgid "%{mineName} appearance:" msgstr "%{name} a %{race}" #, fuzzy msgid "Click to select %{object} as the resource generator to be placed." msgstr "Kattints ide, hogy kiválaszd melyik Küldetéssel játszol." #, fuzzy msgid "Select Mountain Object:" msgstr "Tengeri objektum kiválasztása:" #, fuzzy msgid "Select Rock Object:" msgstr "Tengeri objektum kiválasztása:" #, fuzzy msgid "Select Tree Object:" msgstr "Tengeri objektum kiválasztása:" #, fuzzy msgid "Select Power Up Object:" msgstr "Tengeri objektum kiválasztása:" #, fuzzy msgid "Select Adventure Object:" msgstr "Tengeri objektum kiválasztása:" msgid "Map Type:\n" msgstr "Térképtípus:\n" msgid "The Succession Wars" msgstr "Örökösödési háborúk" msgid "The Price of Loyalty" msgstr "A hűség ára" msgid "Resurrection" msgstr "Feltámasztás" msgid "Lose all your heroes and towns." msgstr "Összes hÅ‘s és város elvesztése." msgid "Lose a specific town." msgstr "Meghatározott város elvesztése." msgid "Lose a specific hero." msgstr "Meghatározott hÅ‘s elvesztése." msgid "Run out of time. Fail to win by a certain point." msgstr "Elfogy a szabott idÅ‘. Nem sikerül gyÅ‘zni egy bizonyos idÅ‘pontig." msgid "Loss Condition" msgstr "Vesztési feltétel" msgid "Defeat all enemy heroes and towns." msgstr "Az ellenfelek összes hÅ‘sének és városának elpusztítása." msgid "Capture a specific town." msgstr "Meghatározott város elfoglalása." msgid "Defeat a specific hero." msgstr "Megadott hÅ‘s legyÅ‘zése." msgid "Find a specific artifact." msgstr "Egy különleges varázstárgy megtalálása." msgid "Your side defeats the opposing side." msgstr "Te vagy szövetségeseid legyÅ‘zik az ellenfeleket." msgid "Accumulate a large amount of gold." msgstr "Nagy mennyiségű arany összegyűjtése." msgid "Victory Condition" msgstr "GyÅ‘zelmi feltétel" msgid "Map difficulty:" msgstr "Térkép nehézsége:" msgid "N" msgstr "N" msgid "No maps exist at that size" msgstr "Nincs ilyen méretű térkép" msgid "Small Maps" msgstr "Kicsi térképek" msgid "View only maps of size small (36 x 36)." msgstr "Csak a kicsi térképek (36x36) mutatása." msgid "Medium Maps" msgstr "Közepes térképek" msgid "View only maps of size medium (72 x 72)." msgstr "Csak a közepes térképek (72x72) mutatása." msgid "Large Maps" msgstr "Nagy térképek" msgid "View only maps of size large (108 x 108)." msgstr "Csak a nagy térképek (108x108) mutatása." msgid "Extra Large Maps" msgstr "Hatalmas térképek" msgid "View only maps of size extra large (144 x 144)." msgstr "Csak a hatalmas térképek (144x144) mutatása." msgid "All Maps" msgstr "Összes térkép" msgid "View all maps, regardless of size." msgstr "Összes térkép mutatása, mérettÅ‘l függetlenül." msgid "Players Icon" msgstr "Játékos ikon" msgid "" "Indicates how many players total are in the scenario. Any positions not " "occupied by humans will be occupied by computer players." msgstr "" "Jelzi, hogy összesen hány játékos van a pályán. Minden ember által le nem " "foglalt pozíciót a számítógép fog elfoglalni." msgid "" "Indicates whether the map\n" "is small (36 x 36), medium\n" "(72 x 72), large (108 x 108),\n" "or extra large (144 x 144)." msgstr "" "Jelzi a térkép méretét,\n" "azaz: kicsi (36 x 36), közepes\n" "(72 x 72), nagy (108 x 108),vagy hatalmas (144 x 144)." msgid "Size Icon" msgstr "Ikon méret" msgid "" "Indicates whether the map is made for \"The Succession Wars\" or \"The Price " "of Loyalty\" version of the game." msgstr "" "Azt jelzi, hogy a térkép az \"Örökösödési háborúk\" vagy a \"Hűség " "ára\"verzióhoz készült-e." msgid "Map Type" msgstr "Térkép típusa" msgid "Selected Name" msgstr "Kiválasztott név" msgid "The name of the currently selected map." msgstr "A kiválasztott térkép neve." msgid "Selected Map Difficulty" msgstr "Kiválasztott nehézség" msgid "" "The map difficulty of the currently selected map. The map difficulty is " "determined by the scenario designer. More difficult maps might include more " "or stronger enemies, fewer resources, or other special conditions making " "things tougher for the human player." msgstr "" "Az aktuálisan kiválasztott térkép nehézségi fokozata. A térkép nehézségét a " "pálya szerkesztÅ‘je határozta meg. A nehezebb pályák több és erÅ‘sebb " "ellenfeleket, kevesebb erÅ‘forrást, vagy más különleges nehezítÅ‘ feltételeket " "foglalhatnak magukban." msgid "Selected Description" msgstr "Kiválasztott leírás" msgid "The description of the currently selected map." msgstr "A kiválasztott térkép leírása." msgid "Jump" msgstr "Ugrás" msgid "Hero Speed" msgstr "HÅ‘s sebessége" msgid "Don't Show" msgstr "Ne mutasd" msgid "Enemy Speed" msgstr "Ellenfél sebessége" msgid "Auto Resolve" msgstr "Auto. döntés" msgid "Auto, No Spells" msgstr "Auto, nincs varázslás" msgid "Battles" msgstr "Csaták" msgid "autoBattle|Manual" msgstr "Kézi vezérlés" msgid "Change the speed at which your heroes move on the main screen." msgstr "A hÅ‘s sebességének megválasztása a fÅ‘ képernyÅ‘n." msgid "" "Sets the speed that A.I. heroes move at. You can also elect not to view A.I. " "movement at all." msgstr "" "A gépi játékosok (A.I.) sebességének megválasztása a fÅ‘ képernyÅ‘n. Opció az " "is, hogy egyáltalán nem látszik az ellenség mozgása." msgid "Change the interface settings of the game." msgstr "A játék kezelÅ‘felülete beállításainak megváltoztatása." msgid "Interface Settings" msgstr "KezelÅ‘felület beállítások" msgid "Toggle instant battle mode." msgstr "Gyors csata mód váltása." msgid "Att." msgstr "Tám." msgid "Def." msgstr "Véd." msgid "Power" msgstr "ErÅ‘" msgid "Knowl" msgstr "Tudás" msgid "1st" msgstr "1." msgid "2nd" msgstr "2." msgid "3rd" msgstr "3." msgid "4th" msgstr "4." msgid "5th" msgstr "5." msgid "6th" msgstr "6." msgid "Oracle: Player Rankings" msgstr "Orákulum: Játékos ranglista" msgid "Thieves' Guild: Player Rankings" msgstr "Tolvajcéh: Játékos ranglista" msgid "Number of Towns:" msgstr "Városok száma:" msgid "Number of Castles:" msgstr "Várak száma:" msgid "Number of Heroes:" msgstr "HÅ‘sök száma:" msgid "Gold in Treasury:" msgstr "Arany a kincstárban:" msgid "Wood & Ore:" msgstr "Fa és érc:" msgid "Gems, Cr, Slf & Mer:" msgstr "Dr.kÅ‘, Krist., Kén és Hg:" msgid "Obelisks Found:" msgstr "Megtalált obeliszkek:" msgid "Artifacts:" msgstr "Varázstárgyak:" msgid "Total Army Strength:" msgstr "Sereg összereje:" msgid "Income:" msgstr "Jövedelem:" msgid "Best Hero:" msgstr "Legjobb hÅ‘s:" msgid "Best Hero Stats:" msgstr "Legjobb hÅ‘s statisztikái:" msgid "Personality:" msgstr "Személyiség:" msgid "Best Monster:" msgstr "LegerÅ‘sebb lény:" msgid "Map filename" msgstr "Térképfájl neve" msgid "" "Are you sure you want to load a new map? (Any unsaved changes to the current " "map will be lost.)" msgstr "" "Biztos hogy benne hogy új térképet töltesz be? (A jelenlegi térkép " "változtatásai el fognak veszni.)" msgid "" "Are you sure you want to create a new map? (Any unsaved changes to the " "current map will be lost.)" msgstr "" "Biztos hogy benne hogy új térképet hozol létre? (A jelenlegi térkép " "változtatásai el fognak veszni.)" msgid "" "Create a new map, either from scratch or using the random map generator." msgstr "" "Készíts új térképet a saját elképzelésed szerint vagy használd a " "véletlenszerű térkép generálót." msgid "New Map" msgstr "Új térkép" msgid "Load Map" msgstr "Térkép betöltése" msgid "Load an existing map." msgstr "LétezÅ‘ térkép betöltése" msgid "Save Map" msgstr "Térkép mentése" msgid "Save the current map." msgstr "Az aktuális térkép mentése." msgid "Quit out of the map editor." msgstr "Kilépés a térképszerkesztÅ‘bÅ‘l." msgid "Objects cannot be placed outside the map." msgstr "Tárgyakat nem lehet a térképen kívülre helyezni." msgid "Monsters cannot be placed on water." msgstr "Vízen nem helyezhetÅ‘k el lények." msgid "Choose a tile which does not contain any objects." msgstr "Olyan térképpontot válassz, ahol nincsenek még tárgyak." msgid "Treasures cannot be placed on water." msgstr "Vízen nem helyezhetÅ‘k el kincsek." msgid "Heroes cannot be placed on water." msgstr "Vízen nem helyezhetÅ‘k el hÅ‘sök." msgid "" "A maximum of %{count} heroes of the same color can be placed on the map." msgstr "" msgid "Artifacts cannot be placed on water." msgstr "Vízen nem helyezhetÅ‘k el varázstárgyak." msgid "Mountains cannot be placed on water." msgstr "Vízen nem helyezhetÅ‘k el hegyek." msgid "Rocks cannot be placed on water." msgstr "Vízen nem helyezhetÅ‘k el kövek." msgid "Trees cannot be placed on water." msgstr "Vízen nem helyezhetÅ‘k el fák." msgid "Ocean object must be placed on water." msgstr "Tengeri objektumot vízre kell elhelyezni." msgid "Landscape objects cannot be placed on water." msgstr "Vízen nem helyezhetÅ‘k el szárazföldi objektumok." msgid "Towns cannot be placed on water." msgstr "Vízen nem helyezhetÅ‘k el városok." msgid "Mines cannot be placed on water." msgstr "Vízen nem helyezhetÅ‘k el bányák." msgid "Dwellings cannot be placed on water." msgstr "Vízen nem helyezhetÅ‘k el toborzóhelyek." msgid "Power-ups cannot be placed on water." msgstr "Vízen nem helyezhetÅ‘k el képességnövelÅ‘ tárgyak." msgid "Adventure objects cannot be placed on water." msgstr "Kaland objektumok nem helyezhetÅ‘k el vízen." msgid "Used to place %{object}." msgstr "%{object} elhelyezésére való." msgid "Select object type" msgstr "Objektum típus kiválasztása:" msgid "" "Click here to\n" "select a monster." msgstr "" "Kattints ide, a\n" "lény kiválasztásához." msgid "" "Click here to\n" "select another monster." msgstr "" "Kattints ide, egy másik\n" "lény kiválasztásához." msgid "" "Cell\n" "Details" msgstr "" "Cella\n" "részletek" msgid "Roads" msgstr "Utak" msgid "Streams" msgstr "Folyóvizek" msgid "Erase" msgstr "Törlés" msgid "Mountains" msgstr "Hegyek" msgid "Rocks" msgstr "Kövek" msgid "Trees" msgstr "Fák" msgid "Water Objects" msgstr "Vízi tárgyak" msgid "Miscellaneous" msgstr "Vegyes" msgid "Artifacts" msgstr "Varázstárgyak" msgid "Dwellings" msgstr "Toborzóhelyek" msgid "Mines" msgstr "Bányák" msgid "Power-ups" msgstr "ErÅ‘-feltöltÅ‘k" msgid "Treasures" msgstr "Kincsek" msgid "Towns" msgstr "Városok" msgid "Heroes" msgstr "HÅ‘sök" msgid "editorErasure|Terrain" msgstr "" msgid "editorErasure|Castles" msgstr "" msgid "editorErasure|Monsters" msgstr "" msgid "editorErasure|Heroes" msgstr "" msgid "editorErasure|Artifacts" msgstr "" msgid "editorErasure|Roads" msgstr "" msgid "editorErasure|Streams" msgstr "" msgid "editorErasure|Treasures" msgstr "" msgid "" "Draws terrain in\n" "%{size} by %{size} square increments." msgstr "" "Talaj rajzolása\n" "%{size} x %{size} négyzetes egységekben." msgid "" "Erases objects in\n" "%{size} by %{size} square increments." msgstr "" "Tárgyak törlése\n" "%{size} x %{size} négyzetes egységekben." msgid "Small Brush" msgstr "Kicsi ecset" msgid "Medium Brush" msgstr "Közepes ecset" msgid "Large Brush" msgstr "Nagy ecset" msgid "Area Fill" msgstr "Terület kitöltése" msgid "Used to click and drag for filling in large areas." msgstr "Nagy térképterületek kitöltésére használatos." msgid "Clear Area" msgstr "Terület törlése" msgid "Used to click and drag for clearing large areas." msgstr "Nagy térképterületek törlésére használatos." msgid "" "Costs %{rate} times normal movement for all heroes. (Pathfinding reduces or " "eliminates the penalty.)" msgstr "" "Költség: a hÅ‘sök normál lépésszáma x %{rate}. (A nyomkövetés képesség " "csökkenti vagy teljesen lenullázza ezt a hátrányt.)" msgid "Traversable only by boat." msgstr "Csak hajóval járható." msgid "No special modifiers." msgstr "Nincs speciális módosító." msgid "Toggle the erasure of %{type} objects." msgstr "%{type} tárgyak törlésének kapcsolója." msgid "" "Objects of this type will be deleted with the Erase tool. Left-click here to " "deselect this type. Press and hold this button to deselect all other object " "types." msgstr "" "Ezek a típusú tárgyak fognak törlÅ‘dni a Törlés eszközzel. Bal kattintásra a " "típuskiválasztás törlÅ‘dik, a gomb nyomva tartására pedig az összes többi " "típuskiválasztás is törlÅ‘dik." msgid "" "Objects of this type will NOT be deleted with the Erase tool. Left-click " "here to select this type. Press and hold this button to select all other " "object types." msgstr "" "Ezek a típusú tárgyak NEM fognak törlÅ‘dni a Törlés eszközzel. Bal " "kattintásra a típuskiválasztás törlÅ‘dik, a gomb nyomva tartására pedig az " "összes többi típuskiválasztás is törlÅ‘dik." msgid "Terrain Mode" msgstr "Talaj mód" msgid "Used to draw the underlying grass, dirt, water, etc. on the map." msgstr "Különféle talajokat (fű, föld, víz stb.) rajzol a térképen." msgid "Landscape Objects Mode" msgstr "Szárazföldi objektum mód" msgid "" "Used to place landscape objects (mountains, rocks, trees, etc.) on the map." msgstr "" "Elhelyezi a szárazföldi tereptárgyakat (hegyek, kövek, fák stb.) a térképen." msgid "Detail Mode" msgstr "Részletes mód" msgid "Used for special editing of monsters, heroes and towns." msgstr "Lények, hÅ‘sök, városok speciális szerkesztéséhez használatos." msgid "Adventure Objects Mode" msgstr "Kaland objektum mód" msgid "" "Used to place adventure objects (artifacts, dwellings, mines, treasures, " "etc.) on the map." msgstr "" "Elhelyezi a kaland térkép tárgyakat (varázstárgyak, toborzóhelyek, bányák, " "kincsek stb.) a térképen." msgid "Kingdom Objects Mode" msgstr "Királyság objektum mód" msgid "Used to place kingdom objects (towns, castles and heroes) on the map." msgstr "Elhelyezi a királyság tárgyait (városok, várak, hÅ‘sök) a térképen." msgid "Monsters Mode" msgstr "Lények mód" msgid "Used to place monsters on the map." msgstr "Elhelyezi a lényeket a térképen." msgid "Allows you to draw streams by clicking and dragging." msgstr "Kattintás és húzás révén lehetÅ‘vé teszi vízfolyások rajzolását." msgid "Stream Mode" msgstr "Vízfolyás mód" msgid "Allows you to draw roads by clicking and dragging." msgstr "Kattintás és húzás révén lehetÅ‘vé teszi utak rajzolását." msgid "Road Mode" msgstr "Út mód" msgid "Erase Mode" msgstr "Törlés mód" msgid "Used to erase objects from the map." msgstr "Ezzel törölheted a tárgyakat a térképrÅ‘l." msgid "Change between zoom and normal view." msgstr "Váltás nagyított és normál nézet között." msgid "Magnify" msgstr "Nagyítás" msgid "Undo" msgstr "Visszavonás" msgid "Undo your last action." msgstr "Utolsó cselekedet visszavonása." msgid "Create a new map either from scratch or using the random map generator." msgstr "" "Készíts új térképet a saját elképzelésed szerint vagy használd a " "véletlenszerű térkép generálót." msgid "Edit map title, description, and other general information." msgstr "A pályanevek, leírások és egyéb általános információk szerkesztése." msgid "Specifications" msgstr "JellemzÅ‘k" msgid "File Options" msgstr "Fájlbeállítások" msgid "" "Open the file options menu, where you can save or load maps, or quit out of " "the editor." msgstr "" "A beállítások menü megnyitása, ahol elmenteni vagy betölteni tudsz pályákat, " "vagy kilépni a szerkesztÅ‘bÅ‘l." msgid "View the editor system options, which let you customize the editor." msgstr "" "Tekintsd meg a szerkesztÅ‘ rendszer beállításait, amik lehetÅ‘vé teszik annak " "testreszabását." msgid "Create a map that is %{size} squares wide and %{size} squares high." msgstr "Egy %{size} széles és %{size} magas térképet hoz létre." msgid "Cancel back to the New Map menu." msgstr "Vissza az új térkép menübe." msgid "Cancel back to the main menu." msgstr "Vissza a fÅ‘menübe." msgid "From Scratch" msgstr "ElölrÅ‘l kezd" msgid "Start from scratch with a blank map." msgstr "Egy teljesen üres térképpel a legelejérÅ‘l kezdeni." msgid "Create a randomly generated map." msgstr "Egy véletlenszerű térkép generálása." msgid "Random" msgstr "Véletlenszerű" msgid "Cancel back to the Map Editor main menu." msgstr "Vissza a térképszerkesztÅ‘ fÅ‘menüjébe." msgid "No maps available!" msgstr "Nincs használható térkép!" msgid "Warning" msgstr "Figyelmeztetés" msgid "difficulty|Easy" msgstr "Könnyű" msgid "difficulty|Normal" msgstr "Normál" msgid "difficulty|Hard" msgstr "Nehéz" msgid "difficulty|Expert" msgstr "SzakértÅ‘" msgid "difficulty|Impossible" msgstr "Lehetetlen" msgid "and more..." msgstr "és egyebek..." msgid "Easy" msgstr "Könnyű" msgid "Hard" msgstr "Nehéz" msgid "Campaign Difficulty" msgstr "Hadjárat nehézsége" msgid "" "Choose this difficulty if you want to prefer game story over challenge. AI " "is weaker in comparison with normal difficulty." msgstr "" "Válaszd ezt a nehézséget, ha elÅ‘nyben részesíted a játék történetiségét a " "kihíváshoz képest. A gép gyengébb lesz a normálishoz képest." msgid "" "Choose this difficulty to enjoy the campaign as per the original design." msgstr "" "Válaszd ezt a nehézséget, ha a hadjáratot az eredeti elgondolásnak " "megfelelÅ‘en szeretnéd játszani." msgid "" "Choose this difficulty if you want challenge. AI is stronger in comparison " "with normal difficulty." msgstr "" "Válaszd ezt a nehézséget, ha kihívást akarsz. A gép erÅ‘sebb, mint a normális " "nehézségi fokozaton." msgid "" "Congratulations!\n" "\n" "Days: %{days}\n" msgstr "" "Gratulálunk!\n" "\n" "Nap: %{days}\n" msgid "" "\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" "\n" "Nehézség: %{difficulty}\n" "\n" msgid "" "Score: %{score}\n" "\n" "Rating:\n" "%{rating}" msgstr "" "Pontszám: %{score}\n" "\n" "Besorolás:\n" "%{rating}" msgid "Start the selected scenario." msgstr "A kiválasztott küldetés indítása." msgid "View Intro" msgstr "Intró mutatása" msgid "View Intro videos for the current state of the campaign." msgstr "A hadjárat jelenlegi állásához tartozó bemutató videó megnézése." msgid "" "Select the campaign difficulty. This can be lowered at any point during the " "campaign." msgstr "" "A hadjárat nehézségének kiválasztása. Ez bármikor csökkenthetÅ‘ a hadjárat " "során." msgid "Restart the current scenario." msgstr "Aktuális küldetés újraindítása." msgid "Difficulty" msgstr "Nehézség" msgid "" "You have changed to a lower difficulty for the campaign. You will not be " "able to revert this after this point. The high score will be calculated " "based solely on the new difficulty. Do you want to proceed?" msgstr "" "Alacsonyabb nehézségi szintre szeretnéd állítani a hadjáratot. InnentÅ‘l nem " "állíthatod újra vissza és a pontszámaid is az újonnan választott szintnek " "megfelelÅ‘en számolódnak majd. Ãgy is végre akarod hajtani?" msgid "Are you sure you want to restart this scenario?" msgstr "Biztosan újra akarod indítani a küldetést?" msgid "Campaign Scenario loading failure" msgstr "A hadjárat küldetésének betöltése sikertelen" msgid "Please make sure that campaign files are correct and present." msgstr "Bizonyosodj meg róla, hogy a hadjárat fájljai épek és mind megvannak." msgid "Days spent" msgstr "Nap eltelt" msgid "The number of days spent on this campaign." msgstr "Az eltöltött napok száma ebben a hadjáratban." msgid "Project Coordination and Core Development" msgstr "Projekt-koordináció és fÅ‘ fejlesztés" msgid "Development" msgstr "Fejlesztés" msgid "Visit us at " msgstr "Látogass meg minket itt: " msgid "QA and Support" msgstr "MinÅ‘ségbiztosítás és támogatás" msgid "Dev and Support" msgstr "Fejl. és támogatás" msgid "Special Thanks to" msgstr "Külön köszönet" msgid "and many other contributors!" msgstr "és sok egyéb további közreműködÅ‘nek!" msgid "and many-many other supporters!" msgstr "és sok-sok egyéb támogatónak!" msgid "Support us at" msgstr "Támogass minket itt" msgid "local-donation-platform|https://www.patreon.com/fheroes2" msgstr "https://www.patreon.com/fheroes2" msgid "Connect with us at" msgstr "Kapcsolat" msgid "local-social-network|https://www.facebook.com/groups/fheroes2" msgstr "https://www.facebook.com/groups/fheroes2" msgid "Need help with the game?" msgstr "Segítség kell a játékhoz?" msgid "Original project before 0.7" msgstr "Eredeti projekt, v0.7 elÅ‘tti" msgid "Heroes of Might and Magic II: The Succession Wars team" msgstr "Heroes of Might and Magic II: Az örökösödési háborúk csapata" msgid "Designed and Directed" msgstr "Tervezte és rendezte" msgid "Programming and Design" msgstr "Programozta és tervezte" msgid "Executive Producer" msgstr "VezetÅ‘ gyártásvezetÅ‘" msgid "Producer" msgstr "GyártásvezetÅ‘" msgid "Additional Design" msgstr "További tervezés" msgid "Additional Programming" msgstr "További programozás" msgid "Musical Production" msgstr "Zenei produkció" msgid "Music and Sound Design" msgstr "Zene és hang tervezés" msgid "Vocalists" msgstr "Énekesek" msgid "Art Director" msgstr "Művészeti igazgató" msgid "Assistant Art Director" msgstr "Művészeti igazgató asszisztens" msgid "Artists" msgstr "Művészek" msgid "QA Manager" msgstr "MinÅ‘ségbiztosítási menedzser" msgid "QA" msgstr "MinÅ‘ségbiztosítás" msgid "Writing" msgstr "Alaptörténet" msgid "Manual and Helpfile" msgstr "Kézikönyv és segédlet" msgid "Scenarios" msgstr "Küldetések" msgid "Heroes of Might and Magic II: The Price of Loyalty team" msgstr "Heroes of Might and Magic II: A hűség ára csapata" msgid "Design Lead" msgstr "VezetÅ‘ tervezÅ‘" msgid "Designers" msgstr "TervezÅ‘k" msgid "Programming Lead" msgstr "VezetÅ‘ programozó" msgid "Art Lead" msgstr "Művészeti vezetÅ‘" msgid "Playtesters" msgstr "TesztelÅ‘k" msgid "Designer" msgstr "TervezÅ‘" msgid "Producers" msgstr "GyártásvezetÅ‘k" msgid "QA Managers" msgstr "MinÅ‘ségbiztosítási menedzserek" msgid "Sound Design" msgstr "Hang tervezÅ‘" msgid "Town Themes" msgstr "Város témák" msgid "Alto Sax" msgstr "Altszaxofon" msgid "Harpsichord and Piano" msgstr "Csembaló és zongora" msgid "Basso Vocal" msgstr "Basszus ének" msgid "Soprano Vocal" msgstr "Szoprán ének" msgid "Recorded at %{recordingStudio}" msgstr "Felvétel: %{recordingStudio}" msgid "credits|Manual" msgstr "Kézikönyv" msgid "German Consultant" msgstr "Német tanácsadó" msgid "Map Designers" msgstr "Térkép tervezÅ‘k" msgid "Package Design" msgstr "Csomagterv" msgid "Your Name" msgstr "A te neved" msgid "Unknown Hero" msgstr "Ismeretlen hÅ‘s" msgid "Standard" msgstr "Normál" msgid "View High Scores for Standard Maps." msgstr "A normál térképek dicsÅ‘ségtáblájának megmutatása." msgid "Campaign" msgstr "Hadjárat" msgid "View High Scores for Campaigns." msgstr "A hadjárat térképek dicsÅ‘ségtáblájának megmutatása." msgid "hotkey|default okay event" msgstr "nyugtázás (OK)" msgid "hotkey|default cancel event" msgstr "visszavonás (MÉGSE)" msgid "hotkey|default left" msgstr "balra" msgid "hotkey|default right" msgstr "jobbra" msgid "hotkey|default up" msgstr "fel" msgid "hotkey|default down" msgstr "le" msgid "hotkey|toggle fullscreen" msgstr "teljes képernyÅ‘ váltás" msgid "hotkey|toggle text support mode" msgstr "szöveges támogatás váltás" msgid "hotkey|new game" msgstr "új játék" msgid "hotkey|load game" msgstr "játék betöltés" msgid "hotkey|highscores" msgstr "dicsÅ‘ségtábla" msgid "hotkey|credits" msgstr "stáblista" msgid "hotkey|standard game" msgstr "normál játék" msgid "hotkey|campaign game" msgstr "hadjárat" msgid "hotkey|multi-player game" msgstr "többjátékos mód" msgid "hotkey|settings" msgstr "beállítások" msgid "hotkey|quit" msgstr "kilépés" msgid "hotkey|select map" msgstr "térkép kiválasztás" msgid "hotkey|select small map size" msgstr "kis térkép kiválasztása" msgid "hotkey|select medium map size" msgstr "közepes térkép kiválasztása" msgid "hotkey|select large map size" msgstr "nagy térkép kiválasztása" msgid "hotkey|select extra large map size" msgstr "nagyon nagy térkép kiválasztása" msgid "hotkey|select all map sizes" msgstr "minden térképméret kiválasztása" msgid "hotkey|hotseat game" msgstr "helycserés játék" msgid "hotkey|battle only game" msgstr "csak csata mód" msgid "hotkey|choose the original campaign" msgstr "eredeti hadjárat kiválasztása" msgid "hotkey|choose the expansion campaign" msgstr "kiegészítÅ‘ hadjárat kiválasztása" msgid "hotkey|map editor main menu" msgstr "térképszerkesztÅ‘ fÅ‘menü" msgid "hotkey|new map menu" msgstr "új térkép menü" msgid "hotkey|load map menu" msgstr "térkép betöltés menü" msgid "hotkey|new map from scratch" msgstr "teljesen új térkép" msgid "hotkey|new random map" msgstr "véletlenszerű térkép" msgid "hotkey|undo last action" msgstr "Legutolsó cselekvés visszavonása (UNDO)" msgid "hotkey|redo last action" msgstr "Legutolsó cselekvés visszaállítása (REDO)" msgid "hotkey|roland campaign" msgstr "roland hadjárata" msgid "hotkey|archibald campaign" msgstr "archibald hadjárata" msgid "hotkey|the price of loyalty campaign" msgstr "a hűség ára hadjárat" msgid "hotkey|voyage home campaign" msgstr "hazatérés hadjárat" msgid "hotkey|wizard's isle campaign" msgstr "varázslósziget hadjárat" msgid "hotkey|descendants campaign" msgstr "leszármazottak hadjárata" msgid "hotkey|select first campaign bonus" msgstr "elsÅ‘ hadjárat bónusz kiválasztása" msgid "hotkey|select second campaign bonus" msgstr "második hadjárat bónusz kiválasztása" msgid "hotkey|select third campaign bonus" msgstr "harmadik hadjárat bónusz kiválasztása" msgid "hotkey|view campaign intro" msgstr "hadjárat bevezetÅ‘jének megtekintése" msgid "hotkey|select campaign difficulty" msgstr "térkép nehézségének kiválasztása" msgid "hotkey|restart campaign scenario" msgstr "hadjárat küldetés újrakezdése" msgid "hotkey|world map left" msgstr "világtérkép: balra" msgid "hotkey|world map right" msgstr "világtérkép: jobbra" msgid "hotkey|world map up" msgstr "világtérkép: fel" msgid "hotkey|world map down" msgstr "világtérkép: le" msgid "hotkey|world map up left" msgstr "világtérkép: fel-balra" msgid "hotkey|world map up right" msgstr "világtérkép: fel-jobbra" msgid "hotkey|world map down left" msgstr "világtérkép: le-balra" msgid "hotkey|world map down right" msgstr "világtérkép: le-jobbra" msgid "hotkey|save game" msgstr "játék mentése" msgid "hotkey|next hero" msgstr "következÅ‘ hÅ‘s" msgid "hotkey|start hero movement" msgstr "hÅ‘s mozgásának megkezdése" msgid "hotkey|cast adventure spell" msgstr "kalandozó varázslat idézése" msgid "hotkey|put hero to sleep" msgstr "hÅ‘s altatása" msgid "hotkey|next town" msgstr "következÅ‘ város" msgid "hotkey|end turn" msgstr "kör befejezése" msgid "hotkey|file options" msgstr "fájlbeállítások" msgid "hotkey|adventure options" msgstr "kaland opciók" msgid "hotkey|puzzle map" msgstr "rejtvény térkép" msgid "hotkey|scenario information" msgstr "küldetés infromáció" msgid "hotkey|dig for artifact" msgstr "varázstárgy kiásása" msgid "hotkey|view world" msgstr "világ mutatása" msgid "hotkey|kingdom summary" msgstr "királyság összesítÅ‘" msgid "hotkey|default action" msgstr "alapértelmezett cselekvés" msgid "hotkey|open focus" msgstr "fókuszálás kezdése" msgid "hotkey|system options" msgstr "rendszerbeállítások" msgid "hotkey|scroll left" msgstr "görgetés balra" msgid "hotkey|scroll right" msgstr "görgetés jobbra" msgid "hotkey|scroll up" msgstr "görgetés fel" msgid "hotkey|scroll down" msgstr "görgetés le" msgid "hotkey|toggle control panel" msgstr "vezérlÅ‘pult kapcsoló" msgid "hotkey|toggle radar" msgstr "radar kapcsoló" msgid "hotkey|toggle buttons" msgstr "gomb átkapcsoló" msgid "hotkey|toggle status" msgstr "státusz kapcsoló" msgid "hotkey|toggle icons" msgstr "ikon kapcsoló" msgid "hotkey|transfer control to ai" msgstr "irányítás átadása a gépnek" msgid "hotkey|retreat from battle" msgstr "visszavonulás a csatából" msgid "hotkey|surrender during battle" msgstr "megadás a csatában" msgid "hotkey|toggle battle auto mode" msgstr "automatikus csata mód kapcsoló" msgid "hotkey|finish the battle in auto mode" msgstr "csata befejezése automatikus módban" msgid "hotkey|battle options" msgstr "csata beállítások" msgid "hotkey|skip turn in battle" msgstr "kör kihagyása a csatában" msgid "hotkey|cast battle spell" msgstr "varázslat idézése csatában" msgid "hotkey|dwelling level 1" msgstr "toborzóhely 1. szint" msgid "hotkey|dwelling level 2" msgstr "toborzóhely 2. szint" msgid "hotkey|dwelling level 3" msgstr "toborzóhely 3. szint" msgid "hotkey|dwelling level 4" msgstr "toborzóhely 4. szint" msgid "hotkey|dwelling level 5" msgstr "toborzóhely 5. szint" msgid "hotkey|dwelling level 6" msgstr "toborzóhely 6. szint" msgid "hotkey|well" msgstr "kút" msgid "hotkey|marketplace" msgstr "piactér" msgid "hotkey|mage guild" msgstr "varázstorony" msgid "hotkey|shipyard" msgstr "hajógyár" msgid "hotkey|thieves guild" msgstr "tolvajcéh" msgid "hotkey|tavern" msgstr "fogadó" msgid "hotkey|construction screen" msgstr "tervezési képernyÅ‘" msgid "hotkey|buy all monsters in well" msgstr "minden lény megvásárlása a kútban" msgid "hotkey|split stack by half" msgstr "egység kétfelé osztása" msgid "hotkey|split stack by one" msgstr "egység szétosztása" msgid "hotkey|join stacks" msgstr "egységek összevonása" msgid "hotkey|upgrade troop" msgstr "csapat fejlesztése" msgid "hotkey|dismiss hero or troop" msgstr "hÅ‘s/egység elbocsátása" msgid "" "Do you want to regain control from AI? The effect will take place only on " "the next turn." msgstr "" "Szeretnéd visszavenni az irányítást az AI-tól? Ez csak a következÅ‘ körben " "lesz érvényes." msgid "" "Do you want to transfer control from you to the AI? The effect will take " "place only on the next turn." msgstr "" "Ãt akarod adni az irányítást a gépnek? Ez csak a következÅ‘ körtÅ‘l lesz " "hatásos." msgid "Default Actions" msgstr "Alapértelmezett cselekvés" msgid "Global Actions" msgstr "Globális cselekvések" msgid "Main Menu" msgstr "FÅ‘menü" msgid "World Map" msgstr "Világtérkép" msgid "Battle Screen" msgstr "Csata képernyÅ‘" msgid "Town Screen" msgstr "Város képernyÅ‘" msgid "Army Actions" msgstr "Sereg cselekvései" msgid "Editor" msgstr "SzerkesztÅ‘" msgid "The save file is corrupted." msgstr "A mentés fájl sérült." msgid "Unsupported save format: " msgstr "Nem támogatott mentés formátum: " msgid "Current game version: " msgstr "Jelenlegi játék verziója: " msgid "Last supported version: " msgstr "Utolsó támogatott verzió: " msgid "This file contains a save with an invalid game type." msgstr "Ez a fájl nem megfelelÅ‘ játékverzióval lett mentve." msgid "" "This save file requires \"The Price of Loyalty\" game assets, but they have " "not been provided to the engine." msgstr "" "Ehhez a mentés fájlhoz szükség van a \"Hűség ára\" kiegészítÅ‘höz, amely " "azonban nem található." msgid "This saved game is localized to '" msgstr "Ez a mentett játék lokalizált '" msgid "' language, but the current language of the game is '" msgstr "' nyelvre, azonban a játék jelenlegi nyelve '" msgid "Hot Seat" msgstr "Helycserés" msgid "" "Play a Hot Seat game, where 2 to 4 players play around the same computer, " "switching into the 'Hot Seat' when it is their turn." msgstr "" "A helycserés játék során 2-4 játékos játszik ugyanazon a számítógépen és " "körönként cserélÅ‘dnek (Hot Seat)." msgid "A single player game playing out a single map." msgstr "Egyjátékos mód egy pályán." msgid "Standard Game" msgstr "Szimpla játék" msgid "A single player game playing through a series of maps." msgstr "Egyjátékos mód egy csomó pályán át." msgid "Campaign Game" msgstr "Hadjárat" msgid "Multi-Player Game" msgstr "Többjátékos mód" msgid "" "A multi-player game, with several human players completing against each " "other on a single map." msgstr "" "A többjátékos módban több emberi játékos vesz részt, hogy legyÅ‘zze a másikat " "egy pályán." msgid "fheroes2 Resurrection Team presents" msgstr "fheroes2 Resurrection Team bemutatja" msgid "Greetings!" msgstr "Üdvözlet!" msgid "Welcome to Heroes of Might and Magic II powered by fheroes2 engine!" msgstr "" "Üdvözlünk az fheroes2 motorral működÅ‘ Heroes of Might and Magic II-ben!" msgid "" "Welcome to Heroes of Might and Magic II powered by the fheroes2 engine!\n" "Before starting the game please select a game resolution." msgstr "" "Üdvözlünk az fheroes2 motorral megújított Heroes of Might and Magic II-ben! " "MielÅ‘tt megkezded a játékot, válaszd ki a képernyÅ‘felbontást." msgid "Please Remember" msgstr "Kérlek, ne felejtsd el" msgid "" "You can always change the language, resolution and settings of the game by " "clicking on the " msgstr "Mindig megváltoztathatod a játék felbontását, ha megnyomod a(z) " msgid "door" msgstr "ajtó" msgid " on the left side of the Main Menu, or with the " msgstr " gombot a fÅ‘menü baloldalán, illetve a(z) " msgid " button from the " msgstr " gomb megnyomásával a " msgid "NEW GAME" msgstr "ÚJ JÃTÉK" msgid "" " menu. \n" "\n" "Enjoy the game!" msgstr "" " menübÅ‘l. \n" "\n" "Élvezetes játékot!" msgid "The Editor requires \"The Price of Loyalty\" expansion files to work." msgstr "" "A szerkesztÅ‘ a \"Hűség ára\" kiegészítÅ‘ fájljait igényli a működéséhez." msgid "Quit Heroes of Might and Magic II and return to the operating system." msgstr "" "Kilépés a Heroes of Might and Magic II-bÅ‘l és visszatérés az operációs " "rendszerhez." msgid "Credits" msgstr "KészítÅ‘k" msgid "View the credits screen." msgstr "KészítÅ‘k listájának megjelenítése." msgid "High Scores" msgstr "DicsÅ‘ségtábla" msgid "View the high scores screen." msgstr "DicsÅ‘ségtábla megjelenítése." msgid "Change language, resolution and settings of the game." msgstr "" "Játék nyelvének, képernyÅ‘felbontásának kiválasztása, illetve egyéb " "beállítások." msgid "Game Settings" msgstr "Játékbeállítások" msgid "" "Required video files for campaign selection window are missing. Please make " "sure that all necessary files are present in the system." msgstr "" "A hadjárat kiválasztási ablak megnyitásához egy videó hiányzik. Kérlek nézd " "át, hogy a játékhoz minden szükséges fájl jelen van-e a rendszerben." msgid "" "Either Roland's or Archibald's campaign from the original Heroes of Might " "and Magic II." msgstr "" "Roland, illetve Archibald hadjárata az eredeti Heroes of Might and Magic II-" "bÅ‘l." msgid "Original Campaign" msgstr "Eredeti hadjárat" msgid "Expansion Campaign" msgstr "KiegészítÅ‘ hadjárat" msgid "One of the four new campaigns from the Price of Loyalty expansion set." msgstr "A Hűség ára kiegészítÅ‘ négy új hadjáratának egyike." msgid "Loading video. Please wait..." msgstr "Videó betöltése. Kérlek várj..." msgid "Host" msgstr "Kiszolgáló" msgid "" "The host sets up the game options. There can only be one host per network " "game." msgstr "" "A kiszolgáló határozza meg a játék beállításait. Csak egy kiszolgáló lehet " "egy hálózati játékban." msgid "Guest" msgstr "Vendég" msgid "" "The guest waits for the host to set up the game, then is automatically added " "in. There can be multiple guests for TCP/IP games." msgstr "" "A vendég várja a kiszolgálót, hogy állítson be egy játékot, ami aztán " "automatikusan látható lesz. Több vendég is lehet egyszerre egy hálózati " "játszmában." msgid "Battle Only" msgstr "Csak csata" msgid "Setup and play a battle without loading any map." msgstr "Egyetlen csata beállítása és lejátszása térkép betöltése nélkül." msgid "2 Players" msgstr "2 játékos" msgid "" "Play with 2 human players, and optionally, up to 4 additional computer " "players." msgstr "Kétszemélyes játék, opcionálisan max. 4 további gépi ellenféllel." msgid "3 Players" msgstr "3 játékos" msgid "" "Play with 3 human players, and optionally, up to 3 additional computer " "players." msgstr "Háromszemélyes játék, opcionálisan max. 3 további gépi ellenféllel." msgid "4 Players" msgstr "4 játékos" msgid "" "Play with 4 human players, and optionally, up to 2 additional computer " "players." msgstr "Négyszemélyes játék, opcionálisan max. 2 további gépi ellenféllel." msgid "5 Players" msgstr "5 játékos" msgid "" "Play with 5 human players, and optionally, up to 1 additional computer " "player." msgstr "Ötszemélyes játék, opcionálisan max. 1 további gépi ellenféllel." msgid "6 Players" msgstr "6 játékos" msgid "Play with 6 human players." msgstr "Hatszemélyes játék." msgid "Dragon city has fallen! You are now the Master of the Dragons." msgstr "Sárkányváros elesett! Mától te vagy a Sárkánymester." msgid "" "You captured %{name}!\n" "You are victorious." msgstr "" "Elfoglaltad %{name} várat!\n" "GyÅ‘ztél." msgid "" "You have captured the enemy hero %{name}!\n" "Your quest is complete." msgstr "" "Elkaptad az ellenséges %{name} hÅ‘st!\n" "Küldetés teljesítve." msgid "" "You have found the %{name}.\n" "Your quest is complete." msgstr "" "Megtaláltad %{name} varázstárgyat.\n" "Küldetés teljesítve." msgid "Ultimate Artifact" msgstr "Szent Varázstárgy" msgid "" "The enemy is beaten.\n" "Your side has triumphed!" msgstr "" "Az ellenség elbukott.\n" "GyÅ‘ztél!" msgid "" "You have built up over %{count} gold in your treasury.\n" "All enemies bow before your wealth and power." msgstr "" "Sikerült %{count} aranyat felhalmoznod a kincstáradban.\n" "Az összes ellenfeled fejet hajt elÅ‘tted." msgid "Victory!" msgstr "GyÅ‘zelem!" msgid "" "The enemy has captured %{name}!\n" "They are triumphant." msgstr "" "Az ellenfél elfoglalta %{name} várat!\n" "Åk gyÅ‘ztek." msgid "" "The enemy has built up over %{count} gold in his treasury.\n" "You must bow done in defeat before his wealth and power." msgstr "" "Az ellenfél már %{count} aranyat felhalmozott a kincstárában.\n" "Fejet kell hajtanod az ereje és hatalma elÅ‘tt." msgid "You have been eliminated from the game!!!" msgstr "Kiestél a játékból!" msgid "" "You have lost the hero %{name}.\n" "Your quest is over." msgstr "" "Elveszítetted %{name} hÅ‘st.\n" "A küldetésed elbukott." msgid "" "You have failed to complete your quest in time.\n" "All is lost." msgstr "" "Nem tudtad idÅ‘ben teljesíteni a küldetésed.\n" "Minden elveszett." msgid "Defeat!" msgstr "Vereség!" msgid "" "Base score: %{score}\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" "Alap pontszám: %{score}\n" "Nehézség: %{difficulty}\n" "\n" msgid "Defeat all enemy heroes and capture all enemy towns and castles." msgstr "" "Az ellenfelek összes hÅ‘sének legyÅ‘zése és várainak, városainak elpusztítása." msgid "Run out of time. (Fail to win by a certain point.)" msgstr "Az idÅ‘ lejárt. (Nem sikerült nyernie adott idÅ‘ alatt.)" msgid "You must defeat the enemy %{enemies}." msgid_plural "You must defeat the enemy alliance of %{enemies}." msgstr[0] "Le kell gyÅ‘znöd %{enemies} ellenfelet." msgstr[1] "Le kell gyÅ‘znöd %{enemies} ellenfeleket." msgid "" "The alliance consisting of %{allies} and you must defeat the enemy " "%{enemies}." msgid_plural "" "The alliance consisting of %{allies} and you must defeat the enemy alliance " "of %{enemies}." msgstr[0] "" "Szövetséged tagja %{allies} és te. LegyÅ‘zendÅ‘ ellenségeitek: %{enemies}." msgstr[1] "" "Szövetséged tagjai %{allies} és te. LegyÅ‘zendÅ‘ ellenséges szövetség: " "%{enemies}." msgid "Capture the castle '%{name}'." msgstr "'%{name}' vár elfoglalása." msgid "Capture the town '%{name}'." msgstr "'%{name}' város elfoglalása." msgid "Defeat the hero '%{name}'." msgstr "'%{name}' hÅ‘s legyÅ‘zése." msgid "Find the ultimate artifact." msgstr "A Szent Varázstárgy megtalálása." msgid "Find the '%{name}' artifact." msgstr "'%{name}' varázstárgy megtalálása." msgid "Accumulate %{count} gold." msgstr "%{count} arany összegyűjtése." msgid "" ", or you may win by defeating all enemy heroes and capturing all enemy towns " "and castles." msgstr "" ", vagy legyÅ‘zöd az ellenfelek összes hÅ‘sét és elfoglalsz minden ellenséges " "várat és várost." msgid "Lose the castle '%{name}'." msgstr "'%{name}' vár elvesztése." msgid "Lose the town '%{name}'." msgstr "'%{name}' város elvesztése." msgid "Lose the hero: %{name}." msgstr "'%{name}' hÅ‘s elvesztése." msgid "Fail to win by the end of month %{month}, week %{week}, day %{day}." msgstr "Nem sikerül gyÅ‘zni %{month} hó %{week} hét %{day} napjáig." msgid "%{color} player has been vanquished!" msgstr "%{color} játékos legyÅ‘zetett!" msgid "Major Event!" msgstr "Fontos esemény!" msgid "Scenario:" msgstr "Küldetés:" msgid "Game Difficulty:" msgstr "Játék nehézsége:" msgid "Opponents:" msgstr "Ellenfelek:" msgid "Class:" msgstr "Osztály:" msgid "Rating %{rating}%" msgstr "Besorolás: %{rating}%" msgid "Click here to select which scenario to play." msgstr "Kattints ide, hogy kiválaszd melyik Küldetéssel játszol." msgid "Scenario" msgstr "Küldetés" msgid "Game Difficulty" msgstr "Játék nehézsége" msgid "" "This lets you change the starting difficulty at which you will play. Higher " "difficulty levels start you of with fewer resources, and at the higher " "settings, give extra resources to the computer." msgstr "" "LehetÅ‘vé teszi a játék kezdÅ‘ nehézségének beállítását. Nehezebb fokozatokon " "egyre kevesebb erÅ‘forrással kezdesz, illetve a számítógép extra " "erÅ‘forrásokat kap." msgid "Difficulty Rating" msgstr "Nehézség besorolás" msgid "" "The difficulty rating reflects a combination of various settings for your " "game. This number will be applied to your final score." msgstr "" "A nehézségi besorolás visszatükrözi a játék beállításait. Ez a besorolás meg " "fog jelenni majd az összpontszámnál is." msgid "Click to accept these settings and start a new game." msgstr "Kattints a beállítások elfogadásához és új játék indításához." msgid "Click to return to the main menu." msgstr "Kattints a fÅ‘menübe való visszatéréshez." msgid "Astrologers proclaim the Month of the %{name}." msgstr "Az asztrológusok kihirdették a(z) %{name} havát." msgid "Astrologers proclaim the Week of the %{name}." msgstr "Az asztrológusok kihirdették a(z) %{name} hetét." msgid "After regular growth, the population of %{monster} is doubled!" msgstr "A(z) %{monster} lények népessége megduplázódott!" msgid "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgid_plural "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgstr[0] "A(z) %{monster} népessége %{count} százalékkal megnövekedett!" msgstr[1] "A(z) %{monster} népessége %{count} százalékkal megnövekedett!" msgid "%{monster} growth +%{count}." msgstr "%{monster} szaporulat: +%{count}." msgid " All populations are halved." msgstr " Minden populáció megfelezÅ‘dött." msgid " All dwellings increase population." msgstr " Mindegyik toborzóhelynek nÅ‘tt a szaporulata." msgid "New Month!" msgstr "Új hónap!" msgid "New Week!" msgstr "Új hét!" msgid "Beware!" msgstr "Vigyázat!" msgid "" "%{color} player, this is your last day to capture a town, or you will be " "banished from this land." msgstr "" "%{color} játékos, ez az utolsó napod, hogy elfoglalj egy várost vagy " "száműznek errÅ‘l a földrÅ‘l." msgid "" "%{color} player, you only have %{day} days left to capture a town, or you " "will be banished from this land." msgstr "" "%{color} játékos, már csak %{day} napod maradt, hogy elfoglalj egy város, " "vagy száműznek errÅ‘l a földrÅ‘l." msgid "%{color} player's turn." msgstr "%{color} játékos köre." #, fuzzy msgid "" "%{color} player, you have lost your last town. If you do not conquer another " "town in the next week, you will be eliminated." msgstr "" "%{color} játékos, elvesztetted az utolsó városod is, ha nem szerzel egy " "másik várost a következÅ‘ héten, akkor elbuktad a játékot." msgid "" "%{color} player, your heroes abandon you, and you are banished from this " "land." msgstr "" "%{color} játékos, a hÅ‘seid elhagytak, téged pedig száműztek errÅ‘l a földrÅ‘l." msgid "Lord Kilburn" msgstr "Lord Kilburn" msgid "Tsabu" msgstr "Tsabu" msgid "Sir Galant" msgstr "Sir Galant" msgid "Thundax" msgstr "Thundax" msgid "Lord Haart" msgstr "Lord Haart" msgid "Ariel" msgstr "Ariel" msgid "Rebecca" msgstr "Rebeka" msgid "Sandro" msgstr "Sandro" msgid "Crodo" msgstr "Crodo" msgid "Barock" msgstr "Barock" msgid "Antoine" msgstr "Antoine" msgid "Astra" msgstr "Astra" msgid "Agar" msgstr "Agar" msgid "Vatawna" msgstr "Vatawna" msgid "Vesper" msgstr "Vesper" msgid "Ambrose" msgstr "Ambrose" msgid "Troyan" msgstr "Troyan" msgid "Jojosh" msgstr "Jojosh" msgid "Wrathmont" msgstr "Wrathmont" msgid "Maximus" msgstr "Maximus" msgid "Next Hero" msgstr "KövetkezÅ‘ hÅ‘s" msgid "Select the next Hero." msgstr "KövetkezÅ‘ hÅ‘s kiválasztása." msgid "Hero Movement" msgstr "HÅ‘s mozgása" msgid "" "Start the Hero's movement along the current path or re-visit the object " "occupied by the Hero. Press and hold this button to reset the Hero's path." msgstr "" "A hÅ‘s mozgásának megkezdése a kijelölt útvonal mentén vagy pedig a hÅ‘s által " "elfoglalt objektum újra-látogatása. A gombot hosszan nyomva tartva, törlÅ‘dik " "a hÅ‘s útvonala." msgid "Kingdom Summary" msgstr "Királyság összesítése" msgid "View a Summary of your Kingdom." msgstr "Királyságod összesítésének megjelenítése." msgid "Cast an adventure spell." msgstr "Kalandozó varázslat elsütése." msgid "End Turn" msgstr "A kör befejezése" msgid "End your turn and left the computer take its turn." msgstr "Befejezi a kört és a számítógép lép." msgid "Adventure Options" msgstr "Kaland Opciók" msgid "Bring up the adventure options menu." msgstr "Megjeleníti a kaland opciók menüt." msgid "" "Bring up the file options menu, allowing you to load, save, start a new game " "or quit." msgstr "" "Megjeleníti a fájlbeállítások menüt, ahol pl. létrehozhatsz új játékot, " "illetve menthetsz és betölthetsz játékállásokat." msgid "Bring up the system options menu, allowing you to customize your game." msgstr "" "Megjeleníti a rendszerbeállítások menüt, melyben testre szabhatod a " "játékprogram működését." msgid "" "One or more heroes may still move, are you sure you want to end your turn?" msgstr "" "Egy vagy több hÅ‘s még mozoghat, biztos vagy benne hogy be akarod fejezni a " "kört?" msgid "Are you sure you want to quit?" msgstr "Biztosan ki szeretne lépni?" msgid "Are you sure you want to restart? (Your current game will be lost.)" msgstr "" "Biztos vagy benne hogy újrakezded? (A jelenlegi játékod el fog veszni.)" msgid "Are you sure you want to overwrite the save with this name?" msgstr "Biztosan felülírod a mentést ezzel a névvel?" msgid "Game saved successfully." msgstr "A játék sikeresen mentve." msgid "There was an issue during saving." msgstr "Valami szösz került a gépezetbe mentés közben." msgid "" "Are you sure you want to load a new game? (Your current game will be lost.)" msgstr "" "Biztos hogy új játékot töltesz be? (A jelenlegi játékod el fog veszni.)" msgid "Try looking on land!!!" msgstr "Próbálkozz a szárazföldön!!!" msgid "" "Searching for the Ultimate Artifact is fruitless. Your hero could not carry " "it even if he found it - all his artifact slots are full." msgstr "" "Megtaláltad a Szent varázstárgyat, de nem tudod magadhoz venni, mert tele " "vagy egyéb felszereléssel." msgid "Digging for artifacts requires a whole day, try again tomorrow." msgstr "" "Egy varázstárgy kiásása egy teljes napot vesz igénybe, holnap próbáld újra." msgid "" "After spending many hours digging here, you have uncovered the %{artifact}." msgstr "Több órányi ásás után megtaláltad ezt: %{artifact}." msgid "Congratulations!" msgstr "Gratulálunk!" msgid "Nothing here. Where could it be?" msgstr "Itt nincs semmi, hol lehet?" msgid "Try searching on clear ground." msgstr "Keress tiszta területet." msgid "A miniature view of the known world. Left click to move viewing area." msgstr "" "Az ismert világ kicsinyített mása. Bal kattintással mozgathatod a látható " "területet." msgid "Month: %{month} Week: %{week}" msgstr "Hónap: %{month} Hét: %{week}" msgid "Day: %{day}" msgstr "Nap: %{day}" msgid "" "You find a small\n" "quantity of %{resource}." msgstr "" "Találtál\n" "egy kis %{resource}-t." msgid "Status Window" msgstr "ÃllapotjelzÅ‘ ablak" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date." msgstr "" "Ez az ablak információkat ad a hÅ‘södrÅ‘l, a királyságodról és mutatja a " "dátumot." msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date. Left click here to cycle through these windows." msgstr "" "Ez az ablak információkat ad a hÅ‘södrÅ‘l, a királyságodról és mutatja a " "dátumot. Bal kattintással lehet lépkedni az ablakok között." msgid "" "This lets you change player starting positions and colors. A particular " "color will always start in a particular location. Some positions may only be " "played by a computer player or only by a human player." msgstr "" "LehetÅ‘vé teszi a játékos színének és kezdÅ‘pontjának megválasztását. Egy " "bizonyos szín mindig egy bizonyos helyrÅ‘l indul a játékban. Néhány pozíciót " "csak gépi vagy emberi játékos foglalhat el." msgid "" "This lets you change the class of a player. Classes are not always " "changeable. Depending on the scenario, a player may receive additional towns " "and/or heroes not of their primary alignment." msgstr "" "LehetÅ‘vé teszi a játékos osztályának megválasztását. Az osztály nem mindig " "módosítható. KüldetéstÅ‘l függÅ‘en a játékos további várakat és hÅ‘söket kaphat." msgid "Handicap" msgstr "Hátrány" msgid "" "This lets you change the handicap of a particular player. Only humans may be " "handicapped. Handicapped players start with fewer resources and earn 15 or " "30% fewer resources per turn for mild and severe handicaps, respectively." msgstr "" "LehetÅ‘vé teszi az egyes játékosok hátrányának beállítását. Csak emberi " "játékos sújtható hátránnyal. A hátrányban lévÅ‘ játékosok kevesebb " "erÅ‘forrással kezdenek és azok 15-30%-kal kevesebbel gyarapodnak körönként a " "hátrány mértékétÅ‘l függÅ‘en, mely lehet mérsékelt vagy súlyos." msgid "%{color} player" msgstr "%{color} játékos" msgid "No Handicap" msgstr "Nincs hátrány" msgid "No special restrictions on start resources and earning them per turn." msgstr "" "Nincs semmilyen megkötés az induló erÅ‘forrásokkal és gyarapodásukkal " "kapcsolatban." msgid "Mild Handicap" msgstr "Mérsékelt hátrány" msgid "" "Mild handicapped players start with fewer resources and earn 15% fewer " "resources per turn." msgstr "" "Mérsékelt hátrányban lévÅ‘ játékosok kevesebb erÅ‘forrással kezdenek és azok " "15%-kal kevesebbel gyarapodnak körönként." msgid "Severe Handicap" msgstr "Súlyos hátrány" msgid "" "Severe handicapped players start with fewer resources and earn 30% fewer " "resources per turn." msgstr "" "Súlyosan hátrányban lévÅ‘ játékosok kevesebb erÅ‘forrással kezdenek és azok " "30%-kal kevesebbel gyarapodnak körönként." msgid "View %{skill} Info" msgstr "%{skill} információ megtekintése" msgid "Keyboard|123" msgstr "123" msgid "Keyboard|SPACE" msgstr "SPACE" msgid "Keyboard|ABC" msgstr "ABC" msgid "Kingdom Income" msgstr "Királyság jövedelme:" msgid "Kingdom Income per day." msgstr "Királyság napi jövedelme:" msgid "For every lighthouse controlled, your ships will move further each day." msgstr "" "Minden fennhatóságod alatt lévÅ‘ világítótorony miatt a hajóid többet " "mozoghatnak minden nap." msgid "English" msgstr "angol" msgid "French" msgstr "francia" msgid "Polish" msgstr "lengyel" msgid "German" msgstr "német" msgid "Russian" msgstr "orosz" msgid "Italian" msgstr "olasz" msgid "Czech" msgstr "cseh" msgid "Norwegian" msgstr "norvég" msgid "Belarusian" msgstr "fehérorosz" msgid "Bulgarian" msgstr "bolgár" msgid "Ukrainian" msgstr "ukrán" msgid "Romanian" msgstr "román" msgid "Spanish" msgstr "spanyol" msgid "Swedish" msgstr "svéd" msgid "Portuguese" msgstr "portugál" msgid "Turkish" msgstr "török" msgid "Dutch" msgstr "holland" msgid "Hungarian" msgstr "magyar" msgid "Danish" msgstr "dán" msgid "Slovak" msgstr "szlovák" msgid "Vietnamese" msgstr "vietnámi" msgid ", FPS: " msgstr ", FPS: " msgid "%{object} robber" msgstr "%{object} rabló" msgid "%{object} raided" msgstr "%{object} kifosztva" msgid "Ector" msgstr "Ector" msgid "Gwenneth" msgstr "Gwenneth" msgid "Sir Gallant" msgstr "Sir Gallant" msgid "Tyro" msgstr "Tyro" msgid "Dimitry" msgstr "Dimitry" msgid "Ruby" msgstr "Ruby" msgid "Crag Hack" msgstr "Crag Hack" msgid "Fineous" msgstr "Fineous" msgid "Jezebel" msgstr "Jezebel" msgid "Atlas" msgstr "Atlasz" msgid "Ergon" msgstr "Ergon" msgid "Jaclyn" msgstr "Jaclyn" msgid "Gem" msgstr "Gem" msgid "Natasha" msgstr "Natasa" msgid "Carlawn" msgstr "Carlawn" msgid "Luna" msgstr "Luna" msgid "Arie" msgstr "Arie" msgid "Barok" msgstr "Barok" msgid "Kastore" msgstr "Kastore" msgid "Falagar" msgstr "Falagar" msgid "Dawn" msgstr "Dawn" msgid "Flint" msgstr "Flint" msgid "Halon" msgstr "Halon" msgid "Myra" msgstr "Myra" msgid "Myrini" msgstr "Myrini" msgid "Wilfrey" msgstr "Wilfrey" msgid "Mandigal" msgstr "Mandigal" msgid "Sarakin" msgstr "Sarakin" msgid "Charity" msgstr "Charity" msgid "Darlana" msgstr "Darlana" msgid "Ranloo" msgstr "Ranloo" msgid "Rialdo" msgstr "Rialdo" msgid "Zam" msgstr "Zam" msgid "Zom" msgstr "Zom" msgid "Celia" msgstr "Celia" msgid "Roxana" msgstr "Roxána" msgid "Lord Corlagon" msgstr "Lord Corlagon" msgid "Lord Halton" msgstr "Lord Halton" msgid "Sister Eliza" msgstr "Eliza nÅ‘vér" msgid "Brother Brax" msgstr "Brax atya" msgid "Dainwin" msgstr "Dainwin" msgid "Joseph" msgstr "Joseph" msgid "Mog" msgstr "Mog" msgid "Solmyr" msgstr "Solmyr" msgid "Ceallach" msgstr "Ceallach" msgid "Drakonia" msgstr "Drakonia" msgid "Elderian" msgstr "Elderian" msgid "Gallavant" msgstr "Gallavant" msgid "Jarkonas" msgstr "Jarkonas" msgid "Martine" msgstr "Martine" msgid " gives you maximum morale" msgstr " maximális morált ad" msgid " gives you maximum luck" msgstr " maximális szerencsét ad" msgid "You cannot pick up this artifact, you already have a full load!" msgstr "" "Nem tudod felvenni ezt a varázstárgyat, mert már így is túl vagy terhelve!" msgid "To cast spells, you must first buy a spell book for %{gold} gold." msgstr "" "Hogy varázsolhass, elÅ‘ször vásárolnod kell %{gold} aranyért varázskönyvet." msgid "Unfortunately, you seem to be a little short of cash at the moment." msgstr "Sajnos, nincs elég aranyad." msgid "Do you wish to buy one?" msgstr "Szeretnél venni egyet?" msgid "%{count} / day" msgstr "%{count} / nap" msgid "one" msgstr "egy" msgid "two" msgstr "kettÅ‘" msgid "A whirlpool engulfs your ship. Some of your army has fallen overboard." msgstr "Egy örvény elnyeli a hajód. A csapatod néhány tagja a tengerbe esett." msgid "Insulted by your refusal of their offer, the monsters attack!" msgstr "Visszautasítottad az ajánlatukat, ezért megtámadnak!" msgid "" "The %{monster}, awed by the power of your forces, begin to scatter.\n" "Do you wish to pursue and engage them?" msgstr "" "A(z) %{monster} félnek a csapatod erejétÅ‘l és megpróbálnak elmenekülni.\n" "Akarod üldözni és megtámadni Å‘ket?" msgid "Ransacking an enemy camp, you discover a hidden cache of treasures." msgstr "Az ellenséges tábor kifosztása közben rálelsz egy elrejtett kicsre." msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with these resources, " "come back next week for more.\"" msgstr "" "A malom gazdája azt mondja:\n" "\"Uram, keményen dolgoztam, hogy ezeket a nyersanyagokat neked adjam, Gyere " "vissza a következÅ‘ héten továbbiakért." msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there are no resources currently available. Please try " "again next week.\"" msgstr "" "A malom gazdája azt mondja:\n" "\"Uram, elnézést, de jelenleg nincsen nyersanyagom. Próbálja meg jövÅ‘ héten." "\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with this gold, come " "back next week for more.\"" msgstr "" "A malom gazdája azt mondja:\n" "\"Uram, keményen dolgoztam, hogy ennyi aranyat tudjak neked adni, Gyere " "vissza a következÅ‘ héten továbbiakért.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there is no gold currently available. Please try again " "next week.\"" msgstr "" "A malom gazdája azt mondja:\n" "\"Uram, elnézést, de jelenleg nincsen aranyam. Próbálja meg jövÅ‘ héten.\"" msgid "" "You've found an abandoned lean-to.\n" "Poking about, you discover some resources hidden nearby." msgstr "" "Rátaláltál egy elhagyatott fészerre.\n" "Körbekutatva rálelsz a közelben némi elrejtett erÅ‘forrásra." msgid "The lean-to is long abandoned. There is nothing of value here." msgstr "A fészer már régóta elhagyatott. Semmi értékes nincs már itt." msgid "" "You catch a leprechaun foolishly sleeping amidst a cluster of magic " "mushrooms.\n" "In exchange for his freedom, he guides you to a small pot filled with " "precious things." msgstr "" "Fülön csípsz egy koboldot, amint egy csomó varázsgomba között szunyókál.\n" "Szabadságáért cserébe mutat neked egy edényt tele különleges dolgokkal." msgid "" "You've found a magic garden, the kind of place that leprechauns and faeries " "like to cavort in, but there is no one here today.\n" "Perhaps you should try again next week." msgstr "" "Egy varázskertre bukkantál, ahol koboldok és tündérek szoktak játszadozni. " "Ma azonban senki sincs itt.\n" "Próbáld meg esetleg a jövÅ‘ héten." msgid "You come upon the remains of an unfortunate adventurer." msgstr "Egy szerencsétlen kalandor maradványaira lelsz rá." msgid "Treasure" msgstr "Kincs" msgid "Searching through the tattered clothing, you find the %{artifact}." msgstr "" "Ãtkutatod rongyos ruházatát és rábukkansz erre a varázstárgyra: %{artifact}." msgid "Searching through the tattered clothing, you find nothing." msgstr "Ãtkutatod rongyos ruházatát, de semmit sem találsz." msgid "" "You come across an old wagon left by a trader who didn't quite make it to " "safe terrain." msgstr "" "Rábukkansz egy öreg szekérre, amit egy kereskedÅ‘ hagyott hátra, mivel nem " "tudta megvédeni." msgid "Unfortunately, others have found it first, and the wagon is empty." msgstr "Sajnos, valaki hamarabb rátalált a szekérre, így az üres." msgid "Searching inside, you find the %{artifact}." msgstr "Ãtkutatva megtaláltad a(z) %{artifact} varázstárgyat." msgid "Inside, you find some of the wagon's cargo still intact." msgstr "Ãtkutatva találsz némi érintetlen rakományt." msgid "You search through the flotsam, and find some wood and some gold." msgstr "Ãtkutatod az uszadékot, és találsz egy kevés fát és aranyat." msgid "You search through the flotsam, and find some wood." msgstr "Ãtkutatod az uszadékot, és találsz egy kevés fát." msgid "You search through the flotsam, but find nothing." msgstr "Ãtkutatod az uszadékot, de semmit sem találsz." msgid "Shrine of the 1st Circle" msgstr "ElsÅ‘ Kör szentélye" msgid "" "You come across a small shrine attended by a group of novice acolytes.\n" "In exchange for your protection, they agree to teach you a simple spell - " "'%{spell}'." msgstr "" "Egy kis szentélyhez értél, melyet novíciusok csoportja látogat.\n" "Védelmedért cserébe megtanítanak egy varázslatra: '%{spell}'." msgid "Shrine of the 2nd Circle" msgstr "Második Kör szentélye" msgid "" "You come across an ornate shrine attended by a group of rotund friars.\n" "In exchange for your protection, they agree to teach you a spell - " "'%{spell}'." msgstr "" "Egy díszes szentélyhez értél, melyet joviális szerzetesek látogatnak.\n" "Védelmedért cserébe megtanítanak egy varázslatra: '%{spell}'." msgid "Shrine of the 3rd Circle" msgstr "Harmadik Kör szentélye" msgid "" "You come across a lavish shrine attended by a group of high priests.\n" "In exchange for your protection, they agree to teach you a sophisticated " "spell - '%{spell}'." msgstr "" "Egy pazar szentélyhez értél, melyet fÅ‘papok csoportja látogat.\n" "Védelmedért cserébe megtanítanak egy bonyolult varázslatra: '%{spell}'." msgid "" "\n" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "\n" "Sajnos nem elegendÅ‘ a bölcsességed, hogy megértsd a varázslatot, így nem " "vagy képes azt megtanulni." msgid "" "\n" "Unfortunately, you already have knowledge of this spell, so there is nothing " "more for them to teach you." msgstr "" "\n" "Már ismered a varázslatot, másuk pedig nincs, amit taníthatnának neked." msgid "" "\n" "Unfortunately, you have no Magic Book to record the spell with." msgstr "" "\n" "Sajnos nincs varázskönyved, amibe a varázslatot beírhatnád." msgid "" "You approach the hut and observe a witch inside studying an ancient tome on " "%{skill}.\n" "\n" msgstr "" "A kunyhóhoz érve meglátsz egy öreg banyát, aki egy %{skill}-könyvet " "olvasgat.\n" "\n" msgid "" "As you approach, she turns and focuses her one glass eye on you.\n" "\"You already know everything you deserve to learn!\" the witch screeches. " "\"NOW GET OUT OF MY HOUSE!\"" msgstr "" "Amint észrevesz, elkezd téged bámulni az egy szem üvegszemével.\n" "Rádkiabál: \"Te már mindent tudsz, amit kiérdemeltél! KIFELÉ A HÃZAMBÓL!\"" msgid "" "As you approach, she turns and speaks.\n" "\"You already know that which I would teach you. I can help you no further.\"" msgstr "" "Ahogy közeledsz megfordul és ezt mondja:\n" "\"Te már tudod azt amit én tanítani tudnék neked, Másban nem segíthetek.\"" msgid "" "An ancient and immortal witch living in a hut with bird's legs for stilts " "teaches you %{skill} for her own inscrutable purposes." msgstr "" "Egy vénséges, halhatatlan boszorkány lakik a cölöpkunyhóban, aki " "kifürkészhetetlen okok miatt megtanít téged a(z) %{skill} képességre." msgid "As you drink the sweet water, you gain luck for your next battle." msgstr "" "Ha iszol az édesvízbÅ‘l, akkor a következÅ‘ csatádhoz szerencsét szerzel." msgid "You drink from the enchanted fountain, but nothing happens." msgstr "Iszol az elvarázsolt kútból, de dem történik semmi." msgid "You enter the faerie ring, but nothing happens." msgstr "Belépsz a körbe, de semmi nem történik." msgid "" "Upon entering the mystical faerie ring, your army gains luck for its next " "battle." msgstr "" "Ahogy belépsz a misztikus körbe, a sereged szerencsét nyer a következÅ‘ " "csatáig." msgid "" "You've found an ancient and weathered stone idol.\n" "It is supposed to grant luck to visitors, but since the stars are already " "smiling upon you, it does nothing." msgstr "" "Rábukkantál egy Å‘srégi, szélkoptatta kÅ‘bálványra.\n" "Azt mondják, szerencsét hoz a látogatónak, azonban a szerencse már " "rádmosolygott, ezért nem történik semmi." msgid "" "You've found an ancient and weathered stone idol.\n" "Kissing it is supposed to be lucky, so you do. The stone is very cold to the " "touch." msgstr "" "Rábukkantál egy Å‘srégi, szélkoptatta kÅ‘bálványra.\n" "Azt mondják, ha megcsókolják, szerencsét hoz - így is teszel. A kÅ‘ nagyon " "hideg." msgid "The mermaids silently entice you to return later and be blessed again." msgstr "A sellÅ‘k csábítanak, hogy késÅ‘bb jöjj vissza és újra megáldanak." msgid "" "The magical, soothing beauty of the Mermaids reaches you and your crew.\n" "Just for a moment, you forget your worries and bask in the beauty of the " "moment.\n" "The mermaids charms bless you with increased luck for your next combat." msgstr "" "A sellÅ‘k varászlatos és andalító szépsége magával ragad téged és " "embereidet.\n" "Egy pillanatra elfelejted minden bánatod és csak fürdesz a pillanat " "szépségében.\n" "A sellÅ‘k bűvöletétÅ‘l szerencsésebb leszel a következÅ‘ csatában." msgid "" "You come upon the pyramid of a great and ancient king.\n" "You are tempted to search it for treasure, but all the old stories warn of " "fearful curses and undead guardians.\n" "Will you search?" msgstr "" "Egy Å‘si és nagy király piramisához érkezel.\n" "Izgat a kíváncsiság és átkutatnád kincsek után, de visszatartanak a " "félelmetes átkokról és élÅ‘halott Å‘rzÅ‘krÅ‘l szóló mendemondák.\n" "Ãtkutatod ennek ellenére?" msgid "" "Upon defeating the monsters, you decipher an ancient glyph on the wall, " "telling the secret of the spell - '" msgstr "" "A lények legyÅ‘zése után egy Å‘si írásjelet betűzöl ki a falon, mely e " "varázslat titkáról mesél: '" msgid "Unfortunately, you have no Magic Book to record the spell with." msgstr "" "Szerencsétlenségedre, nincs varázskönyved, amibe a varázslatot beírhatnád." msgid "" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "Szerencsétlenségedre, nem vagy elég bölcs, hogy megértsd a varázslatot, nem " "hogy megtanuld..." msgid "" "You come upon the pyramid of a great and ancient king.\n" "Routine exploration reveals that the pyramid is completely empty." msgstr "" "Egy Å‘si és nagy király piramisához érkezel.\n" "Rutinszerűen átkutatod, de a piramis teljesen üres." msgid "" "A drink at the well is supposed to restore your spell points, but you are " "already at maximum." msgstr "" "A kútból történÅ‘ ivás feltöltené a varázspontodat, de már maximumon van." msgid "A second drink at the well in one day will not help you." msgstr "Egy napon belüli második ivás nem hat rád." msgid "A drink from the well has restored your spell points to maximum." msgstr "A kútból történÅ‘ ivás feltölti a varázspontodat." msgid "" "\"I'm sorry sir,\" The leader of the soldiers says, \"but you already know " "everything we have to teach.\"" msgstr "" "\"Sajnálom Uram,\" - mondja a katonák vezetÅ‘je - \"de Ön már mindent tud, " "amit mi megtaníthatnánk...\"" msgid "The soldiers living in the fort teach you a few new defensive tricks." msgstr "" "Az erÅ‘dítményben állomásozó katonák tanítanak neked néhány új védelmi " "trükköt." msgid "" "You've come upon a mercenary camp practicing their tactics. \"You're too " "advanced for us,\" the mercenary captain says. \"We can teach nothing more.\"" msgstr "" "Zsoldosok táborához érkezel, akik éppen harci taktikáikat gyakorolják. \"Túl " "képzett vagy hozzánk\" - mondja a zsoldosvezér. \"Nem tudunk többet tanítani." "\"" msgid "" "You've come upon a mercenary camp practicing their tactics. The mercenaries " "welcome you and your troops and invite you to train with them." msgstr "" "Zsoldosok táborához érkezel, akik éppen harci taktikáikat gyakorolják. A " "zsoldosok köszöntenek téged és embereidet, majd meghívnak egy kis " "gyakorlatozásra velük." msgid "\"Go 'way!\", the witch doctor barks, \"you know all I know.\"" msgstr "" "\"Takarodj innen!\" - rivall rád az vajákos - \"Nem tudok neked újat mondani!" "\"" msgid "" "An Orcish witch doctor living in the hut deepens your knowledge of magic by " "showing you how to cast stones, read portents, and decipher the intricacies " "of chicken entrails." msgstr "" "A kunyhóban élÅ‘ ork vajákos boszorkány szélesíti a mágikus tudásodat kövek " "elmozdítása, csodás jelek értelmezése, illetve csirke zsigerekbÅ‘l jóslás " "terén." msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, the Druids turn you away, indicating they have nothing " "new to teach you." msgstr "" "Druidák egy csoportjára bukkansz, akik furcsa kőépítményükben imádkoznak. A " "Druidák elküldenek azzal, hogy nem tudnak neked semmi újat tanítani." msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, they teach you new ways to cast spells." msgstr "" "Druidák egy csoportjára bukkansz, akik furcsa kőépítményükben imádkoznak. Új " "dolgokat tanítanak meg neked a varázslás terén." msgid "" "You tentatively approach the burial ground of ancient warriors. Do you want " "to search the graves?" msgstr "" "Óvatosan megközelíted a letűnt harcosok temetkezési helyét. Ãt akarod " "kutatni a sírokat?" msgid "" "You spend several hours searching the graves and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" "Miután legyÅ‘zted a Zombikat, órákat töltöttél el a sírok átkutatásával, de " "nem találtál semmit. Csökkent a csapatod morálja." msgid "Upon defeating the Zombies you search the graves and find something!" msgstr "Miután legyÅ‘zted a zombikat, átkutattad a sírokat és találtál valamit!" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the shipwreck?" msgstr "" "Egy nagy kalózhajó korhadó fateste nyikorog hátborzongatóan a sziklákon. Ãt " "akarod kutatni a hajóroncsot?" msgid "" "You spend several hours sifting through the debris and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" "Órákat töltesz el a törmelék átkutatásával, de nem találsz semmit. Csapatod " "tagjai alantasnak találják ezt a munkát és csökken a moráljuk." msgid "" "Upon defeating the Ghosts you sift through the debris and find something!" msgstr "A szellemek legyÅ‘zése után átkutatod a törmeléket és találsz valamit!" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the ship?" msgstr "" "Egy nagy kalózhajó korhadó fateste nyikorog hátborzongatóan a sziklákon. Ãt " "akarod kutatni a hajóroncsot?" msgid "" "Upon defeating the Skeletons you sift through the debris and find something!" msgstr "A csontvázak legyÅ‘zése után átkutatod a törmeléket és találsz valamit!" msgid "Your men spot a navigational buoy, confirming that you are on course." msgstr "A matrózaid meglátnak egy bóját a vízen. Jó irányba haladsz." msgid "" "Your men spot a navigational buoy, confirming that you are on course and " "increasing their morale." msgstr "" "A matrózaid meglátnak egy bóját a vízen. Jó irányba haladsz és növekszik " "sereged morálja." msgid "" "The drink at the oasis is refreshing, but offers no further benefit. The " "oasis might help again if you fought a battle first." msgstr "" "Ha iszol az oázisban, akkor felfrissülsz, de más nem történik. Csak az elsÅ‘ " "csatádban segít a hatása." msgid "" "A drink at the oasis fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" "Ha iszol az oázisban, akkor az egységeid ereje és morálja feltöltÅ‘dik. Ezért " "ma egy kicsit tovább tudsz haladni." msgid "" "The drink at the watering hole is refreshing, but offers no further benefit. " "The watering hole might help again if you fought a battle first." msgstr "" "Ha iszol az itatónál, akkor felfrissülsz, de más nem történik. Csak az elsÅ‘ " "csatádban segít a hatása." msgid "" "A drink at the watering hole fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" "Ha iszol az itatónál, akkor az egységeid ereje és hangulata feltöltÅ‘dik. " "Ezért ma egy kicsit tovább tudsz haladni." msgid "" "It doesn't help to pray twice before a battle. Come back after you've fought." msgstr "" "Az nem segít, ha többször imádkozol csata elÅ‘tt, gyere vissza harc után." msgid "A visit and a prayer at the temple raises the morale of your troops." msgstr "Egy látogatás és egy imádság a templomban niveli a csapataid morálját." msgid "" "An old Knight appears on the steps of the gazebo. \"I am sorry, my liege, I " "have taught you all I can.\"" msgstr "" "Egy öreg lovag jelenik meg a kilátó lépcsÅ‘in. \"Bocsánat, Uram, mindenre " "megtanítottam Önt, amit én tudok.\"" msgid "" "An old Knight appears on the steps of the gazebo. \"My liege, I will teach " "you all that I know to aid you in your travels.\"" msgstr "" "Egy öreg lovag jelenik meg a kilátó lépcsÅ‘in. \"Uram, mindenre meg akarom " "Önt tanítani, amit én tudok, hogy segítsem az utazását.\"" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he says, \"I would give you an artifact as a reward, but " "you're all full.\"" msgstr "" "Megmentesz egy hajótöröttet az óceán halálosan ölelÅ‘ karjából. Jótettedért " "cserébe azt mondja: \"Jutalmul adnék neked egy varázstárgyat, de tele van a " "hátizsákod.\"" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he rewards you for your act of kindness by giving you the " "%{art}." msgstr "" "Megmentesz egy hajótöröttet az óceán halálosan ölelÅ‘ karjából. Jótettedért " "cserébe megjutalmaz egy %{art} varázstárggyal." msgid "A leprechaun offers you the %{art} for the small price of %{gold} Gold." msgstr "Egy manó %{art}-t kínál neked %{gold} aranyért cserébe." msgid "" "A leprechaun offers you the %{art} for the small price of %{gold} Gold and " "%{count} %{res}." msgstr "" "Egy manó %{art}-t kínál neked %{gold} aranyért és %{count} %{res}-ért " "cserébe." msgid "Do you wish to buy this artifact?" msgstr "Meg akarod venni ezt a varázstárgyat?" msgid "" "You try to pay the leprechaun, but realize that you can't afford it. The " "leprechaun stamps his foot and ignores you." msgstr "" "Épp fizetnél a koboldnak, de rájössz, hogy nincs elég pénzed. A kobold " "dobbant egyet a lábával és nem vesz rólad tudomást." msgid "" "Insulted by your refusal of his generous offer, the leprechaun stamps his " "foot and ignores you." msgstr "" "A kobold vérig sértÅ‘dik, mert visszautasítottad nagylelkű ajánlatát. Dobbant " "egyet a lábával és nem vesz rólad tudomást." msgid "You've found the artifact: " msgstr "Megtaláltad a varázstárgyat. " msgid "" "You've found the humble dwelling of a withered hermit. The hermit tells you " "that he is willing to give the %{art} to the first wise person he meets." msgstr "" "Rátalálsz egy ráncos vén remete szerény lakhelyére. A remete elmondja neked, " "hogy a legelsÅ‘ bölcs embernek akarja odadni ezt: %{art}." msgid "" "You've come across the spartan quarters of a retired soldier. The soldier " "tells you that he is willing to pass on the %{art} to the first true leader " "he meets." msgstr "" "Rátalálsz egy obsitos katona szerény lakhelyére. A volt katona elmondja " "neked, hogy a legelsÅ‘ igazi vezéregyéniségnek akarja odadni ezt: %{art}." msgid "" "You've encountered a strange person with a hat and an owl on it. He tells " "you that he is willing to give %{art} if you have %{skill}." msgstr "" "Összetalálkozol egy furcsa emberrel, akinek a kalapján egy bagoly ül. Azt " "mondja, hogy ha bírsz a(z) %{skill} képességgel, akkor odaadja neked ezt: " "%{art}." msgid "" "You come upon an ancient artifact. As you reach for it, a pack of Rogues " "leap out of the brush to guard their stolen loot." msgstr "" "Ráakadsz egy Å‘si varázstárgyra, de amint közeledsz hozzá, egy rablócsapat " "lép elÅ‘ a környezÅ‘ bokrokból, hogy megvédjék lopott zsákmányukat." msgid "" "Through a clearing you observe an ancient artifact. Unfortunately, it's " "guarded by a nearby %{monster}. Do you want to fight the %{monster} for the " "artifact?" msgstr "" "Kiürítés közben felfedezel egy varázstárgyat. Sajnos, %{monster} Å‘rzik. Meg " "akarsz küzdeni velük a varázstárgyért?" msgid "Victorious, you take your prize, the %{art}." msgstr "GyÅ‘zelem! Vedd át megérdemelt jutalmadat, mely egy %{art}." msgid "" "Discretion is the better part of valor, and you decide to avoid this fight " "for today." msgstr "" "A megfontoltság a vitézség tiszteletre méltó oldala. Ma úgy döntesz, hogy " "elállsz a harctól." msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold pieces." msgstr "" "Több órás munkával sikerül kihalásznod egy ládát a tengerbÅ‘l. Kinyitod, és " "%{gold} aranyat találsz benne." msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold and the %{art}." msgstr "" "Több órás munkával sikerül kihalásznod egy ládát a tengerbÅ‘l. Kinyitod, és " "%{gold} aranyat valamint egy %{art} varázstárgyat találsz benne." msgid "" "After spending hours trying to fish the chest out of the sea, you open it, " "only to find it empty." msgstr "" "Több órás munkával sikerül kihalásznod egy ládát a tengerbÅ‘l. Kinyitod, de " "nem találsz benne semmit sem." msgid "" "After scouring the area, you fall upon a hidden treasure cache. You may take " "the gold or distribute the gold to the peasants for experience. Do you wish " "to keep the gold?" msgstr "" "A terület átkutatása után rábukkansz egy elrejtett kincsesraktárra. " "Elteheted magadnak az aranyat, vagy szétoszthatod tapasztalati ponttért " "cserébe a parasztok között. Megtartod az aranyat?" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "%{gold} gold pieces." msgstr "" "A terület átkutatása után rábukkansz egy elrejtett ládára, amiben %{gold} " "arany van." msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "ancient artifact %{art}." msgstr "" "A terület átkutatása után rábukkansz egy elrejtett ládára, amiben egy régi " "%{art} varázstárgy van." msgid "" "You stumble upon a dented and tarnished lamp lodged deep in the earth. Do " "you wish to rub the lamp?" msgstr "" "Egy horpadt, patinás lámpásra bukkansz mélyen a földbe fúródva. Ki akarod " "fényesíteni a lámpást?" msgid "" "You have taken control of the local Alchemist shop. It will provide you with " "%{count} unit of Mercury per day." msgstr "" "Megszerezted a helyi alkímista labort. Ez naponta %{count} egységnyi higanyt " "biztosít számodra." msgid "" "You gain control of a sawmill. It will provide you with %{count} units of " "wood per day." msgstr "" "Megszereztél egy fűrészmalmot. Ez naponta %{count} egységnyi fát biztosít " "számodra." msgid "" "You gain control of an ore mine. It will provide you with %{count} units of " "ore per day." msgstr "" "Megszereztél egy vasércbányát. Ez naponta %{count} egységnyi vasércet ad " "neked." msgid "" "You gain control of a sulfur mine. It will provide you with %{count} unit of " "sulfur per day." msgstr "" "Megszereztél egy kénbányát. Ez naponta %{count} egységnyi ként ad neked." msgid "" "You gain control of a crystal mine. It will provide you with %{count} unit " "of crystal per day." msgstr "" "Megszereztél egy kristálybányát. Ez naponta %{count} egységnyi kristályt ad " "neked." msgid "" "You gain control of a gem mine. It will provide you with %{count} unit of " "gems per day." msgstr "" "Megszereztél egy gyöngybányát. Ez naponta %{count} egységnyi gyöngyöt ad " "neked." msgid "" "You gain control of a gold mine. It will provide you with %{count} gold per " "day." msgstr "Megszereztél egy aranybányát. Ez naponta %{count} aranyat ad neked." msgid "" "The lighthouse is now under your control, and all of your ships will now " "move further each day." msgstr "" "A kilátótorony a fennhatóságod alá került, így minden hajód többet mozoghat " "körönként." msgid "You gain control of a %{name}." msgstr "Irányítást szereztél %{name} felett." msgid "" "You come upon an abandoned gold mine. The mine appears to be haunted. Do you " "wish to enter?" msgstr "" "Egy elhagyatott aranybányához érsz, mely kísértetjártának tűnik. Be akarsz " "lépni?" msgid "You beat the Ghosts and are able to restore the mine to production." msgstr "Elűzted a szellemeket, a bánya újra használható." msgid "" "A group of %{monster} with a desire for greater glory wish to join you. Do " "you accept?" msgstr "" "%{monster} egy csoportja a dicsÅ‘ség reményében csatlakozna hozzád. Elfogadod " "Å‘ket?" msgid "As you approach the dwelling, you notice that there is no one here." msgstr "" "Ahogy megközelítetted a lakóhelyet, észreveszed, hogy nincs senki bent." msgid "" "You search the ruins, but the Medusas that used to live here are gone. " "Perhaps there will be more next week." msgstr "" "Ãtkutattad a romokat, ahol Medúzák laknak, de most nem találsz itt senkit. " "Próbáld meg a jövÅ‘ héten." msgid "" "You've found some Medusas living in the ruins. They are willing to join your " "army for a price. Do you want to recruit Medusas?" msgstr "" "Megtaláltad a romok közt élÅ‘ Medúzákat. Csatlakoznak hozzád aranyért " "cserébe. Akarsz Medúzát toborozni?" msgid "" "You've found a Sprite Tree City. Unfortunately, none of the Sprites living " "there wish to join an army. Maybe next week." msgstr "" "Találtál egy Tündér Fa Várost. Sajnos egyetlen Tündér sem tartózkodik itt, " "hogy csatlakozzanak hozzád. Talán a jövÅ‘ héten." msgid "" "Some of the Sprites living in the tree city are willing to join your army " "for a price. Do you want to recruit Sprites?" msgstr "" "Tündérek laknak a Fa Városukban és csatlakoznának hozzád aranyért cserébe. " "Szeretnél Tündért toborozni?" msgid "" "A colorful Rogues' wagon stands empty here. Perhaps more Rogues will be here " "later." msgstr "" "Egy tarkabarka szekér áll itt, láthatóan rablóké. Lehet, hogy késÅ‘bb még " "felbukkannak itt." msgid "" "Distant sounds of music and laughter draw you to a colorful wagon housing " "Rogues. Do you wish to have any Rogues join your army?" msgstr "" "Távoli zeneszó és nevetés hallatszik, mely odavonz a rablóknak otthont adó " "tarkabarka szekérhez. Akarod, hogy csatlakozzon hozzád bármelyikük?" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. The " "tents are unoccupied. Perhaps more Nomads will be here later." msgstr "" "Ütött-kopott sátrak hullámoznak a szélben hívogatóan. A sátrak " "elhagyatottak, de késÅ‘bb talán lesznek még nomádok itt." msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. Do you " "wish to have any Nomads join you during your travels?" msgstr "" "Ütött-kopott sátrak hullámoznak a szélben hívogatóan. Akarod, hogy nomádok " "csatlakozzanak hozzád utazásaid során?" msgid "The pit of mud bubbles for a minute and then lies still." msgstr "A sárgödör bugyog egy percig, majd elcsitul." msgid "" "As you approach the bubbling pit of mud, creatures begin to climb out and " "position themselves around it. In unison they say: \"Mother Earth would like " "to offer you a few of her troops. Do you want to recruit Earth Elementals?\"" msgstr "" "Ahogy közeledsz a bugyogó sárgödörhöz, fura lények kezdenek elÅ‘bújni belÅ‘le " "és körbeállni azt. Együtt, egyszólamban mondják neked: \"Föld Anya szeretne " "felajánlani számodra néhányat a seregeibÅ‘l. Akarsz Földelementálokat " "toborozni?\"" msgid "You enter the structure of white stone pillars, and find nothing." msgstr "Belépsz a fehér kÅ‘oszlopos építménybe, de nem találsz semmit." msgid "" "White stone pillars support a roof that rises up to the sky. As you enter " "the structure, the dead air of the outside gives way to a whirling gust that " "almost pushes you back out. The air current materializes into a barely " "visible form. The creature asks, in what can only be described as a loud " "whisper: \"Why have you come? Are you here to call upon the forces of the " "air?\"" msgstr "" "A fehér kÅ‘oszlopok az egekig nyúló tetÅ‘t tartanak. Ahogy belépsz közéjük, " "áporodott levegÅ‘ kerekdik fel és kavarog hatalmas erÅ‘vel, mely majdnem téged " "is kilök. A légáramlat lassan alig látható formát kezd ölteni, majd a lény " "susogva így szól: \"Miért jöttél? Meg akarod idézni a levegÅ‘ erejét?\"" msgid "No Fire Elementals approach you from the lava pool." msgstr "A lávatóból nem közeledik feléd egyetlen Tűzelementál sem." msgid "" "Beneath a structure that serves to hold in heat, Fire Elementals move about " "in a fiery pool of molten lava. A group of them approach you and offer their " "services. Would you like to recruit Fire Elementals?" msgstr "" "A meleget magában tartó struktúrák alatt Tűzelementálok mozgolódnak egy " "tüzes lávatóban. Egy kisebb csoportjuk közeledik hozzád és felajánlják a " "szolgálataikat neked. Akarsz Tűzelementálokat toborozni?" msgid "A face forms in the water for a moment, and then is gone." msgstr "" "Egy arckép válik láthatóvá a vízben egy piilanatra és aztán el is tűnik." msgid "" "Crystalline structures cast shadows over a small reflective pool of water. " "You peer into the pool, and a face that is not your own peers back. It asks: " "\"Would you like to call upon the powers of water?\"" msgstr "" "A kristály struktúrák árnyékot vetnek egy kicsi, tükrözÅ‘dÅ‘ vízű tócsára. " "Belepillantasz a vízbe, de az arc, ami visszabámul rád, nem a tied. Emígyen " "szólít meg: \"Valóban e víz hatalmához szeretnél fordulni?\"" msgid "This burial site is deathly still." msgstr "A temetkezési hely még mindig halálos." msgid "" "Restless spirits of long dead warriors seeking their final resting place " "offer to join you in hopes of finding peace. Do you wish to recruit ghosts?" msgstr "" "Sok halott harcos nyughatatlan lelke a végsÅ‘ nyughely megtalálásának " "reményében csatlakoznának hozzád. Szeretnél szellemeket toborozni?" msgid "" "The City of the Dead is empty of life, and empty of unlife as well. Perhaps " "some undead will move in next week." msgstr "" "A Halott Városban jelenleg egy lélek sem található. Talán a jövÅ‘ héten lesz " "néhány élÅ‘halott." msgid "" "Some Liches living here are willing to join your army for a price. Do you " "want to recruit Liches?" msgstr "" "Néhány Lich él itt és fizetség fejében csatlakoznak hozzád. Akarsz Lich-eket " "toborozni?" msgid "" "You've found the ruins of an ancient city, now inhabited solely by the " "undead. Will you search?" msgstr "" "Egy Å‘si város romjaira bukkantál, melyet kizárólag élÅ‘holtak laknak. " "Szeretnél körülnézni?" msgid "" "Some of the surviving Liches are impressed by your victory over their " "fellows, and offer to join you for a price. Do you want to recruit Liches?" msgstr "" "A túlélÅ‘ Lich-ek elismerik a társaik felett aratott gyÅ‘zelmedet és " "felajánlják, hogy fizettségért cserébe csatlakoznak hozzád. Akarsz Lich-eket " "toborozni?" msgid "" "You've found one of those bridges that Trolls are so fond of living under, " "but there are none here. Perhaps there will be some next week." msgstr "" "Találtál egy olyan hidat, amely alatt Trollok laknak, de most nincs itt egy " "sem. Talán a jövÅ‘ héten itt lesznek néhánzan." msgid "" "Some Trolls living under a bridge are willing to join your army, but for a " "price. Do you want to recruit Trolls?" msgstr "" "Néhány Troll él a híd alatt, akik némi pénzért cserébe csatlakoznának " "hozzád. Felfogadod Å‘ket?" msgid "Trolls living under the bridge challenge you. Will you fight them?" msgstr "A híd alatt élÅ‘ Trollok belédkötnek. Megküzdesz velük?" msgid "" "A few Trolls remain, cowering under the bridge. They approach you and offer " "to join your forces as mercenaries. Do you want to buy any Trolls?" msgstr "" "Egy kevés Troll a híd alatt rejtÅ‘zködik. Felajánlják, hogy beállnak hozzád " "zsoldosnak. Akarsz vásárolni néhány Trollt?" msgid "" "The Dragon city has no Dragons willing to join you this week. Perhaps a " "Dragon will become available next week." msgstr "" "A Sárkány városban nincsenek Sárkányok ezen a héten. Talán a következÅ‘ héten " "újra lesznek." msgid "" "The Dragon city is willing to offer some Dragons for your army for a price. " "Do you wish to recruit Dragons?" msgstr "" "A Sárkányvárosban lévÅ‘ Sárkányok fizetség fejében csatlakoznak hozzád. " "Akarsz Sárkányokat toborozni?" msgid "" "You stand before the Dragon City, a place off-limits to mere humans. Do you " "wish to violate this rule and challenge the Dragons to a fight?" msgstr "" "Sárkányváros kapui elÅ‘tt állsz, mely tiltott terület egyszerű halandók " "számára. Meg akarod szegni a szabályt és kihívni a sárkányokat egy " "küzdelemre?" msgid "" "Having defeated the Dragon champions, the city's leaders agree to supply " "some Dragons to your army for a price. Do you wish to recruit Dragons?" msgstr "" "Miután legyÅ‘zted a bajnok Sárkányokat a város vezetÅ‘i beleegyeznek, hogy " "fizetségért cserébe Sárkányokat vehetsz a csapatodba. Szeretnél Sárkányokat " "toborozni?" msgid "From the observation tower, you are able to see distant lands." msgstr "A Kilátótoronyból láthatod a távoli vidéket is." msgid "" "The spring only refills once a week, and someone's already been here this " "week." msgstr "" "A forrás csak heti egyszer telik meg, és valaki már járt itt mostanában." msgid "" "A drink at the spring is supposed to give you twice your normal spell " "points, but you are already at that level." msgstr "" "Ãllítólag, ha iszol a forrásból, akkor megkétszerezÅ‘dik a normál " "varázspontjaid száma, de te ezt a szintet már elérted." msgid "" "A drink from the spring fills your blood with magic! You have twice your " "normal spell points in reserve." msgstr "" "Ha iszol a forrásból, akkor a véred megtelik varázserÅ‘vel! MegkétszerezÅ‘dik " "a normál varázspontjaid száma." msgid "" "Recognizing you, the butler refuses to admit you. \"The master,\" he says, " "\"will not see the same student twice.\"" msgstr "" "A komornyik felismer téged és nem engedi, hogy belépj. \"A Mester\" mondja " "\"egy tanítványt sem fogad kétszer.\"" msgid "" "The butler admits you to see the master of the house. He trains you in the " "four skills a hero should know." msgstr "Az inas beenged a mesterhez, aki 4 tulajdonságodban is kiképez." msgid "" "The butler opens the door and looks you up and down. \"You are neither " "famous nor diplomatic enough to be admitted to see my master,\" he sniffs. " "\"Come back when you think yourself worthy.\"" msgstr "" "Az inas ajtót nyit, és alaposan megnéz. \"Nem vagy elég ismert és " "diplomatikus ahhoz, hogy beengedjelek a mesteremhez. Gyere vissza, ha úgy " "érzed, hogy fejlÅ‘dtél valamennyit.\"" msgid " and " msgstr " és " msgid "" "All of the %{monsters} you have in your army have been trained by the battle " "masters of the fort. Your army now contains %{monsters2}." msgstr "" "Az összes csapatodban lévÅ‘ %{monsters} lényed az erÅ‘dítmény harcos " "mestereitÅ‘l vettek leckét. A csapatodban már %{monsters2} lények vannak." msgid "" "An unusual alliance of Ogres, Orcs, and Dwarves offer to train (upgrade) any " "such troops brought to them. Unfortunately, you have none with you." msgstr "" "Az ogrék, orkok és törpök közötti szokatlan szövetkezés eredményeképpen " "bármely közülük való egységet ki lehet képezni (fejleszteni) ezen a helyen. " "Sajnos neked egyikbÅ‘l sincs a csapatodban." msgid "All of your %{monsters} have been upgraded into %{monsters2}." msgstr "Minden %{monsters} fel lett fejlesztve erre: %{monsters2}." msgid "" "A blacksmith working at the foundry offers to convert all Pikemen and " "Swordsmen's weapons brought to him from iron to steel. He also says that he " "knows a process that will convert Iron Golems into Steel Golems. " "Unfortunately, you have none of these troops in your army, so he can't help " "you." msgstr "" "Az Öntödében dolgozó kovácsmester felajánlja, hogy az összes Lándzsásod és " "Kardforgatód fegyverét vasból acéllá edzi. Azt mondja, ismeri a Vasgólem - " "Acélgólem átalakításának módszerét is. Sajnos, egyik egység sincs a " "seregedben, ezért nem tud neked segíteni." msgid "" "The captain looks at you with surprise and says:\n" "\"You already have all the maps I know about. Let me fish in peace now.\"" msgstr "" "A kapitány meglepetten néz rád és azt mondja:\n" "\"Már rendelkezel minden térképpel, ami nekem csak van. Hagy nyugodtan " "pecázni.\"" msgid "" "A retired captain living on this refurbished fishing platform offers to sell " "you maps of the sea he made in his younger days for 1,000 gold. Do you wish " "to buy the maps?" msgstr "" "Egy vén kapitány, aki ezen a mólón lakik, felajánlja neked megvételre az Å‘ " "általa, fiatalkorában készített tengeri térképet, 1000 aranyért cserébe. " "Megvásárolod a térképet?" msgid "" "The captain sighs. \"You don't have enough money, eh? You can't expect me " "to give my maps away for free!\"" msgstr "" "A kapitány elmosolyodik. \"Nincs elég pénzed, ugye? Csak nem képzeled, hogy " "ingyen odaadom a térképemet?\"" msgid "" "You come upon an obelisk made from a type of stone you have never seen " "before. Staring at it intensely, the smooth surface suddenly changes to an " "inscription. The inscription is a piece of a lost ancient map. Quickly you " "copy down the piece and the inscription vanishes as abruptly as it appeared." msgstr "" "Rátalálsz egy obeliszkre, mely olyan kÅ‘bÅ‘l készült, amilyent még sohasem " "láttál. Bámulod a sima felületét, melybÅ‘l egyszercsak rajzolat tűnik elÅ‘, " "egy régi térkép darabja. Gyorsan lemásolod a térképrészletet, majd a " "rajzolat ugyanolyan hirtelen tűnik el, ahogy megjelent." msgid "You have already been to this obelisk." msgstr "Már megnézted ezt az obeliszket." msgid "" "Upon your approach, the tree opens its eyes in delight. \"It is good to see " "you, my student. I hope my teachings have helped you.\"" msgstr "" "Ahogy megközelíted, a fa kinyitja a szemeit. \"Öröm látni téged, " "tanítványom. Remélem a tanításaim segítségedre lesznek.\"" msgid "" "Upon your approach, the tree opens its eyes in delight. \"Ahh, an " "adventurer! Allow me to teach you a little of what I have learned over the " "ages.\"" msgstr "" "Ahogy megközelíted, a fa kinyitja a szemeit . \"Ahh, egy kalandor! Engedd " "meg, hogy megtanítsalak valamire, amit évek során megtanultam.\"" msgid "Upon your approach, the tree opens its eyes in delight." msgstr "Ahogy megközelíted, a fa kinyitja a szemeit." msgid "" "\"Ahh, an adventurer! I will be happy to teach you a little of what I have " "learned over the ages for a mere %{count} %{res}.\"" msgstr "" "\"Ahh, egy kalandor! Szívesen megtanítalak cserébe valamire, amit az évek " "során megtanultam. Ennek ára: %{count} %{res}.\"" msgid "(Just bury it around my roots.)" msgstr "(Csak ásd el a gyökereim közé.)" msgid "Tears brim in the eyes of the tree." msgstr "A fa szemei megtelnek könnyel." msgid "\"I need %{count} %{res}.\"" msgstr "Szükségem van a következÅ‘kre: %{count} %{res}." msgid "it whispers. (sniff) \"Well, come back when you can pay me.\"" msgstr "azt suttogja. (hüpp) \"Nos, gyere vissza amikor már tudsz fizetni.\"" msgid "" "Nestled among the trees sits a blind seer. After you explain the intent of " "your journey, the seer activates his crystal ball, allowing you to see the " "strengths and weaknesses of your opponents." msgstr "" "Fák között megbújva üldögél egy vak látnok. Miután elmagyaráztad neki a " "küldetésed célját, a látnok működésbe hozza kristálygömbjét, melyben " "lehetÅ‘séged van meglátni ellenségeid gyengéit és erÅ‘sségeit." msgid "" "The entrance to the cave is dark, and a foul, sulfurous smell issues from " "the cave mouth. Will you enter?" msgstr "" "A barlang bejárata sötét, és undorító, kénes szag áramlik ki a barlang " "szájából. Belépsz?" msgid "Except for evidence of a terrible battle, the cave is empty." msgstr "Egy iszonyú csata tanújelein kívül a barlang üres." msgid "" "You find a powerful and grotesque Demon in the cave. \"Today,\" it rasps, " "\"you will fight and surely die. But I will give you a choice of deaths. You " "may fight me, or you may fight my servants. Do you prefer to fight my " "servants?\"" msgstr "" "A barlangban egy hatalmas és groteszk démont találsz, aki fület tépÅ‘ hangon " "azt mondja: \"Harcolni fogsz és halál fia leszel. De megadom neked a " "lehetÅ‘séget, hogy megválaszd a halálod módját. Harcolhatsz velem vagy a " "szolgáimmal. Szívesebben harcolnál a szolgáimmal?\"" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points." msgstr "" "A démon felüvölt a kihívás hallatán és támad! Rövid, de ádáz küzdelem után " "elpusztítod a szörnyet és ennyi tapasztalati ponttal gazdagodsz: %{exp}." msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points and " "%{count} gold." msgstr "" "A démon felüvölt a kihívás hallatán és támad! Rövid, de ádáz küzdelem után " "elpusztítod a szörnyet és ennyi tapasztalati ponttal gazdagodsz: %{exp}, " "valamint %{count} aranyat zsebelsz be." msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and find the %{art} in the back of the cave." msgstr "" "A démon felüvölt a kihívás hallatán és támad! Rövid, de ádáz küzdelem után " "elpusztítod a szörnyet és a barlang hátuljában rátalálsz erre: %{art}." msgid "" "Seeing that you do not have %{count} gold, the demon slashes you with its " "claws, and the last thing you see is a red haze." msgstr "" "Látva, hogy nincs %{count} aranyad, a démon lesújt rád hatalmas karmaival és " "az utolsó emléked vörös ködbe vész." msgid "" "The Demon leaps upon you and has its claws at your throat before you can " "even draw your sword. \"Your life is mine,\" it says. \"I will sell it back " "to you for %{count} gold.\"" msgstr "" "A démon rádugrik és karmaival megragadja a nyakadat, mielÅ‘tt akárcsak " "elÅ‘húzhatnád a kardodat. \"Az életed a kezemben van\" - ordítja - és csak " "%{count} aranyért adom vissza." msgid "" "Upon defeating the daemon's servants, you find a hidden cache with %{count} " "gold." msgstr "" "Miután legyÅ‘zted a démon alattvalóit, egy titkos rejtekhelyen találsz " "%{count} aranyat." msgid "" "As you enter the Alchemist's Tower, a hobbled, graying man in a brown cloak " "makes his way towards you." msgstr "" "Ahogy belépsz az alkimista toronyba egy barna-köpenyes sánta, öregedÅ‘ ember " "botorkál feléd." msgid "He checks your pack, and sees that you have 1 cursed item." msgid_plural "" "He checks your pack, and sees that you have %{count} cursed items." msgstr[0] "Ãtnézi a batyudat és egy elátkozott tárgyat talál." msgstr[1] "Ãtnézi a batyudat és %{count} elátkozott tárgyat talál." msgid "For %{gold} gold, the alchemist will remove it for you. Do you pay?" msgid_plural "" "For %{gold} gold, the alchemist will remove them for you. Do you pay?" msgstr[0] "Az alkimista %{gold} arany ellenében megszabadít tÅ‘le. Kifizeted?" msgstr[1] "Az alkimista %{gold} arany ellenében megszabadít tÅ‘lük. Kifizeted?" msgid "" "After you consent to pay the requested amount of gold, the alchemist grabs " "the cursed artifact and throws it into his magical cauldron." msgid_plural "" "After you consent to pay the requested amount of gold, the alchemist grabs " "all cursed artifacts and throws them into his magical cauldron." msgstr[0] "" "Miután belementél, hogy fizess, az alkimista megragadja az átokkal sújtott " "varázstárgyat és a varázsüstjébe veti." msgstr[1] "" "Miután belementél, hogy fizess, az alkimista megragadja az átokkal sújtott " "varázstárgyakat és a varázsüstjébe veti azokat." msgid "" "You hear a voice from behind the locked door, \"You don't have enough gold " "to pay for my services.\"" msgstr "" "Egy hangot hallasz a bezárt ajtó mögül: \"Nincs elég aranyad, hogy megfizesd " "a szolgálataimat.\"" msgid "" "You hear a voice from high above in the tower, \"Go away! I can't help you!\"" msgstr "" "Egy hangot hallasz a torony tetejérÅ‘l: \"Menj el! Nem tudok neked segíteni!\"" msgid "" "The head groom speaks to you, \"That is a fine looking horse you have. I am " "afraid we can give you no better, but the horses your cavalry are riding " "look to be of poor breeding stock. We have many trained war horses which " "would aid your riders greatly. I insist you take them.\"" msgstr "" "A lovászmester megszólít: \"Szép lovad van. Attól tartok nem tudunk neked " "jobbat adni, viszont a lovasaid nem ilyen jó ménesbÅ‘l való lovakat ülnek meg." "Számos gyakorlott csatalovunk van, mely jó szolgálatot tehet nekik. Jó " "szívvelajánlom, hogy vigyél belÅ‘lük.\"" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, he will " "grow tired in a week. You must also let me give better horses to your " "mounted soldiers, their horses look shoddy and weak.\"" msgstr "" "Ahogy közeledsz az istállóhoz, megjelenik a lovászmester, aki egy jó " "kiállású lovat vezet maga mellett. \"Ez a paripa segíteni fog, hogy gyorsabb " "legyen az utad. Sajnos egy hét után ez is elfárad. Engedd meg, hogy a " "lovasaidnak is jobb lovakat adjak, mert a lovaik hitványnak és gyengének " "tűnnek.\"" msgid "" "The head groom approaches you and speaks, \"You already have a fine horse, " "and have no inexperienced cavalry which might make use of our trained war " "horses.\"" msgstr "" "A lovászmester feléd közeledik és azt mondja: \"Jó lovad van és a többi ló " "is elég harcedzettnek tűnik, csakúgy mint a mi csatalovaink.\"" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, his " "endurance will wane with a lot of heavy riding, and you must return for a " "fresh mount in a week. We also have many fine war horses which could benefit " "mounted soldiers, but you have none we can help.\"" msgstr "" "Ahogy közeledsz az istállóhoz, megjelenik a lovászmester, aki egy jó " "kiállású lovat vezet maga mellett. \"Ez a paripa segíteni fog, hogy gyorsabb " "legyen az utad. Sajnos az ereje majd ennek is alábbhagy a nehéz vágtáktól és " "egy hét után vissza kell térj egy friss hátaslóért. Sok jó csatalovunk van, " "melyek jó szolgálatot tesznek felvértezett katonáknak, de úgy látom " "csapatodban nincs ilyen.\"" msgid "The Arena guards turn you away." msgstr "Az aréna Å‘rei elküldenek téged." msgid "" "You have your crew stop up their ears with wax before the sirens' eerie song " "has any chance of luring them to a watery grave." msgstr "" "Megparancsolod a legénységednek, hogy viasszal tömjék be a fülüket, hogy ne " "hallják a szirének félelmetes énekét, ami hullámsírba csalhatná Å‘ket." msgid "" "As the sirens sing their eerie song, your small, determined army manages to " "overcome the urge to dive headlong into the sea." msgstr "" "Amikor a szirének rákezdenek énekükre, kicsi és eltökélt seregednek sikerül " "leküzdeni a csábítást a halált hozó mélységek felé." msgid "" "An eerie wailing song emanates from the sirens perched upon the rocks. Many " "of your crew fall under its spell, and dive into the water where they drown. " "You are now wiser for the visit, and gain %{exp} experience." msgstr "" "Félelmetes jajveszékelÅ‘ hang tör elÅ‘ a sziklákon tanyázó szirénekbÅ‘l. Több " "embered is elcsábul a bűbájuktól és a vízbe ugorva, belefulladnak. Bölcsebb " "lettél és tanultál az esetbÅ‘l, így ennyi tapasztalatra teszel szert: %{exp}." msgid "" "In a dazzling display of daring, you break into the local jail and free the " "hero imprisoned there, who, in return, pledges loyalty to your cause." msgstr "" "Egy ragyogó és merész húzással betörsz a fogdába és kiszabadítod az ott " "bebörtönzött hÅ‘st, aki hálából hűséget esküszik neked és ügyednek." msgid "" "You already have %{count} heroes, and regretfully must leave the prisoner in " "this jail to languish in agony for untold days." msgstr "" "Már %{count} hÅ‘söd van, így sajnos itt kell hagyd a foglyot a börtönében, " "aki még beláthatatlan ideig tovább fog sínylÅ‘dni." msgid "" "You enter a rickety hut and talk to the magician who lives there. He tells " "you of places near and far which may aid you in your journeys." msgstr "" "Belépsz egy rozoga kunyhóba és beszédbe elegyedsz az itt lakó varázslóval. " "Elárul neked helyeket közel s távol, melyek segítségedre lesznek utadon." msgid "This eye seems to be intently studying its surroundings." msgstr "Úgy tűnik, ez a szem erÅ‘sen fürkészi a környéket." msgid "You come across a giant Sphinx. The Sphinx remains strangely quiet." msgstr "Egy hatalmas Szfinxhez érkezel. A Szfinx furcsán suttogva megszólít." msgid "" "\"I have a riddle for you,\" the Sphinx says. \"Answer correctly, and you " "shall be rewarded. Answer incorrectly, and you shall be eaten. Do you accept " "the challenge?\"" msgstr "" "\"Van számodra egy rejtvényem.\" - mondja. \"Ha jó a válaszod, " "megjutalmazlak. Ha rossz a válaszod, felfallak. Elfogadod a kihívást?\"" msgid "" "The Sphinx asks you the following riddle:\n" "\n" "'%{riddle}'\n" "\n" "Your answer?" msgstr "" "A Szfinx a következÅ‘ találós kérdést adja fel:\n" "\n" "'%{riddle}'\n" "\n" "Mi a válaszod?" msgid "" "\"You guessed incorrectly,\" the Sphinx says, smiling. The Sphinx swipes at " "you with a paw, knocking you to the ground. Another blow makes the world go " "black, and you know no more." msgstr "" "\"Rossz a válaszod.\" - mondja gúnyosan a Szfinx. Rádcsap egy hatalmasat a " "mancsával, és ledönt a földre. A következÅ‘ ütésre elsötétül elÅ‘tted a világ, " "többre nem emlékszel." msgid "" "Looking somewhat disappointed, the Sphinx sighs. \"You've answered my riddle " "so here's your reward. Now begone.\"" msgstr "" "A Szfinx kissé csalódottan felsóhajt. \"Jó a válaszod, itt van a jutalmad. " "Most menj!\"" msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "As you speak the magic word, the glowing barrier dissolves into nothingness." msgstr "" "Egy mágikus útakadály állja utadat. A kapuív rúnái ezt üzenik:\n" "\"Mondd ki a varázsigét, hogy átmehess.\"\n" "Ahogy kimondtad a varázsigét, a sugárzó útakadály semmivé foszlik elÅ‘tted." msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "You speak, and nothing happens." msgstr "" "Egy mágikus útakadály állja utadat. A kapuív rúnái ezt üzenik:\n" "\"Mondd ki a varázsigét, hogy átmehess.\"\n" "A próbálkozásod hatástalan, nem történik semmi." msgid "" "You enter the tent and see an old woman gazing into a magic gem. She looks " "up and says,\n" "\"In my travels, I have learned much in the way of arcane magic. A great " "oracle taught me his skill. I have the answer you seek.\"" msgstr "" "Belépsz a sátorba és meglátsz egy öregasszonyt, amint egy mágikus drágakövet " "bámul. Felnéz és azt mondja: \"Utazásaim során sokat megtanultam a titkos " "mágia módozatairól. Egy nagy orákulum tanított engem. Tudom a választ arra, " "amit keresel.\"" msgid "No spell book is present." msgstr "Nincs varázskönyved." msgid "" "This spell costs %{mana} spell points. You have no spell points, so you " "cannot cast it." msgstr "" "A varázslat %{mana} varázslatpontot emészt fel, de neked nincs már " "varázslatpontod, ezért nem használhatod most." msgid "" "This spell costs %{mana} spell points. You only have %{point} spell points, " "so you cannot cast it." msgstr "" "A varázslat %{mana} varázslatpontot emészt fel, de neked csak %{point} " "varázslatpontod van, ezért nem használhatod most." msgid "The spell was not found." msgstr "A varázslat nem található." msgid "Only heroes can cast this spell." msgstr "Csak hÅ‘sök idézhetik meg ezt a varázslatot." msgid "This hero is not able to cast adventure spells." msgstr "A hÅ‘s nem képes kaland varázslatok megidézésére." msgid "Your hero is too tired to cast this spell today. Try again tomorrow." msgstr "HÅ‘söd ma már túl fáradt ehhez a varázslathoz. Próbáld újra holnap." msgid "This spell cannot be cast on a boat." msgstr "A varázslat nem használható hajón." msgid "This spell can only be cast near an ocean." msgstr "A varázslat csak tengerparton használható." msgid "" "There are no boats available and no ocean adjacent to the hero where this " "spell will work." msgstr "" "Nincs elérhetÅ‘ hajó, illetve nincs szabad tengerpart a hÅ‘s közelében. Ãgy " "nem használható a varázslat." msgid "There are no boats available for this spell." msgstr "Nincs elérhetÅ‘ hajó a varázslathoz." msgid "There is no ocean adjacent to the hero where this spell will work." msgstr "" "Nincs szabad tengerpart a hÅ‘s közelében, így nem használható a varázslat." msgid "" "You do not own any town or castle that is not currently occupied by a hero. " "This spell will have no effect." msgstr "" "Jelenleg nincs olyan saját várad vagy városod, melyet ne foglalna el egy " "hÅ‘söd. Ãgy ez a varázslat hatástalan." msgid "This hero is already in a town, so this spell will have no effect." msgstr "A hÅ‘s már városban áll, így a varázslat most nem használható." msgid "" "The nearest town is %{town}.\n" "\n" "This town is occupied by your hero %{hero}." msgstr "" "A legközelebbi város %{town}.\n" "\n" "A várost %{hero} foglalja el." msgid "This spell is already in effect." msgstr "A varázslat már hat." msgid "" "No opponent neither has nor can have any hero under their command anymore. " "Casting this spell will have no effect." msgstr "" "Egyik ellenfeled parancsnoksága alá sem tartozik/tartozhat hÅ‘s többé. A " "varázslat így hatástalan." msgid "" "No opponent has a hero under their command at this time. Casting this spell " "will have no effect." msgstr "" "Jelenleg egyik ellenfeled parancsnoksága alá sem tartozik hÅ‘s. A varázslat " "így hatástalan." msgid "" "You must be within %{count} spaces of a monster for the Visions spell to " "work." msgstr "" "A vízió varázslat működéséhez %{count} lépésen belül kell legyél a lénytÅ‘l." msgid "" "You must be standing on the entrance to a mine (sawmills and alchemist labs " "do not count) to cast this spell." msgstr "" "A bánya bejáratánál kell állnod (a fűrészmalmot és az alkimista labort " "leszámítva), hogy használd ezt a varázslatot." msgid "You must first defeat the ghosts guarding the mine to cast this spell." msgstr "" "ElÅ‘bb le kell gyÅ‘znöd a bányát Å‘rzÅ‘ szellemeket, mielÅ‘tt ezt a varázslatot " "használnád." msgid "" "There are already at least as many elementals guarding the mine as this hero " "can generate. Casting this spell will have no effect." msgstr "" "Már jelenleg is annyi elementál Å‘rzi a bányát, amennyit ez a hÅ‘s meg tud " "idézni. A varázslat megidézése hatástalan." msgid "%{name} the %{race} (Level %{level})" msgstr "%{name} a %{race} (%{level}. szint)" #, fuzzy msgid "" "'Spread' combat formation spreads the hero's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "'Laza' csataalakzatban az egységeid szétszóródva helyezkednek el a harcmezÅ‘ " "egyik oldalán (legalább 1 üres hely van közöttük)." #, fuzzy msgid "" "'Grouped' combat formation bunches the hero's army together in the center of " "their side of the battlefield." msgstr "" "'Szoros' csataalakzatban az egységeid szorosan egymás mellett helyezkednek " "el a harcmezÅ‘ oldalának közepén." msgid "Are you sure you want to dismiss this Hero?" msgstr "Biztos, hogy elbocsátod ezt a hÅ‘st?" msgid "View Experience Info" msgstr "Tapasztalat információ" msgid "View Spell Points Info" msgstr "Varázspont információ" msgid "Set army combat formation to 'Spread'" msgstr "A csataalakzat legyen 'Laza'." msgid "Set army combat formation to 'Grouped'" msgstr "A csataalakzat legyen 'Szoros'." msgid "Exit Hero Screen" msgstr "Kilépés a hÅ‘s képernyÅ‘bÅ‘l" msgid "You cannot dismiss a hero in a castle" msgstr "Várban nem lehet hÅ‘st elbocsátani" msgid "Dismissal of %{name} the %{race} is prohibited by scenario" msgstr "%{name} a %{race} elbocsátása tiltott ebben a küldetésben" msgid "Dismiss %{name} the %{race}" msgstr "%{name} a %{race} elbocsátása" msgid "Show previous hero" msgstr "ElÅ‘zÅ‘ hÅ‘s mutatása" msgid "Show next hero" msgstr "KövetkezÅ‘ hÅ‘s mutatása" msgid "Blood Morale" msgstr "Szenvedélyes morál" msgid "%{morale} Morale" msgstr "%{morale} morál" msgid "%{luck} Luck" msgstr "%{luck} szerencse" msgid "Current Luck Modifiers:" msgstr "Ható szerencse módosítók:" msgid "Current Morale Modifiers:" msgstr "Ható morál módosítók:" msgid "Entire army is undead, so morale does not apply." msgstr "A csapat minden tagja élÅ‘halott, így a morál nem hat rájuk." msgid "" "Current experience %{exp1}.\n" " Next level %{exp2}." msgstr "" "Jelenlegi tapasztalati pont: %{exp1}\n" " KövetkezÅ‘ szint: %{exp2}." msgid "Level %{level}" msgstr "%{level} szint" #, fuzzy msgid "" "%{name} currently has %{point} spell points out of a maximum of %{max}. The " "maximum number of spell points is 10 times the hero's knowledge. It is " "occasionally possible for the hero to have more than their maximum spell " "points via special events." msgstr "" "%{name} tulajdonában %{point} varázspont van, maximum %{max} lehet. A " "varázspontok maximális száma a tudásod értékének 10-szerese. Ez speciális " "esetekben lehet több is." msgid "%{name1} meets %{name2}" msgstr "%{name1} és %{name2} találkozik" msgid "Enemy heroes are now fully identifiable." msgstr "Az ellenséges hÅ‘sök teljes mértékben azonosíthatóak." msgid "Identify Hero" msgstr "HÅ‘sazonosítás" msgid "Select town to port to." msgstr "Válaszd ki, melyik városba szeretnél menni." msgid "Town Portal" msgstr "Városba lépés" msgid "The creatures are willing to join us!" msgstr "A lények csatlakozni akarnak hozzád!" msgid "All the creatures will join us..." msgstr "Minden lény csatlakozni fog hozzád..." msgid "The creature will join us..." msgid_plural "%{count} of the creatures will join us..." msgstr[0] "A lény csatlakozni fog hozzád..." msgstr[1] "%{count} lény csatlakozni fog hozzád..." msgid "" "\n" " for a fee of %{gold} gold." msgstr "" "\n" "%{gold} arany fizetségért." msgid "These weak creatures will surely flee before us." msgstr "Ezek a gyenge lények valószínű elmenekül elÅ‘led." msgid "I fear these creatures are in the mood for a fight." msgstr "FélÅ‘, hogy ezek a lények készek harcolni." msgid "The hero's attack skill is a bonus added to each unit's attack skill." msgstr "" "A hÅ‘s támadóerejének értéke bónuszként hozzáadódik mindegyik egység " "támadóerejéhez." msgid "The hero's defense skill is a bonus added to each unit's defense skill." msgstr "" "A hÅ‘s védÅ‘erejének értéke bónuszként hozzáadódik mindegyik egység " "védÅ‘erejéhez." msgid "The hero's spell power determines the duration or power of a spell." msgstr "A hÅ‘s varázsereje meghatározza a varázslat hatóidejét." msgid "" " The hero's knowledge determines how many spell points the hero may have. " "Under normal circumstances, a hero is limited to 10 spell points per level " "of knowledge." msgstr "" "A hÅ‘s tudása határozza meg, hogy hány varázspontja lehet. Ãltalában a hÅ‘s " "szintenként 10 varázspontot kaphat." msgid "Current Modifiers:" msgstr "Ható módosítók:" msgid "skill|Basic" msgstr "Alap" msgid "skill|Advanced" msgstr "Haladó" msgid "skill|Expert" msgstr "SzakértÅ‘" msgid "Pathfinding" msgstr "Nyomkövetés" msgid "Archery" msgstr "Ãjászat" msgid "Logistics" msgstr "Logisztika" msgid "Scouting" msgstr "Cserkészet" msgid "Diplomacy" msgstr "Diplomácia" msgid "Navigation" msgstr "Navigáció" msgid "Leadership" msgstr "Vezetés" msgid "Wisdom" msgstr "Bölcsesség" msgid "Mysticism" msgstr "Misztika" msgid "Ballistics" msgstr "Ballisztika" msgid "Eagle Eye" msgstr "Sasszem" msgid "Necromancy" msgstr "Nekromancia" msgid "Estates" msgstr "Vagyon" msgid "Advanced Archery" msgstr "Haladó íjászat" msgid "Advanced Pathfinding" msgstr "Haladó nyomkövetés" msgid "Basic Archery" msgstr "Alap íjászat" msgid "Basic Pathfinding" msgstr "Alap nyomkövetés" msgid "Expert Pathfinding" msgstr "SzakértÅ‘ nyomkövetés" msgid "Advanced Logistics" msgstr "Haladó logisztika" msgid "Basic Logistics" msgstr "Alap logisztika" msgid "Basic Scouting" msgstr "Alap cserkészet" msgid "Expert Archery" msgstr "SzakértÅ‘ íjászat" msgid "Expert Logistics" msgstr "SzakértÅ‘ logisztika" msgid "Advanced Diplomacy" msgstr "Haladó diplomácia" msgid "Advanced Scouting" msgstr "Haladó cserkészet" msgid "Basic Diplomacy" msgstr "Alap diplomácia" msgid "Expert Diplomacy" msgstr "SzakértÅ‘ diplomácia" msgid "Expert Scouting" msgstr "SzakértÅ‘ cserkészet" msgid "Advanced Leadership" msgstr "Haladó vezetés" msgid "Advanced Navigation" msgstr "Haladó navigáció" msgid "Basic Leadership" msgstr "Alap vezetés" msgid "Basic Navigation" msgstr "Alap navigáció" msgid "Expert Navigation" msgstr "SzakértÅ‘ navigáció" msgid "Advanced Wisdom" msgstr "Haladó bölcsesség" msgid "Basic Mysticism" msgstr "Alap misztika" msgid "Basic Wisdom" msgstr "Alap bölcsesség" msgid "Expert Leadership" msgstr "SzakértÅ‘ vezetés" msgid "Expert Wisdom" msgstr "SzakértÅ‘ bölcsesség" msgid "Advanced Luck" msgstr "Haladó szerencse" msgid "Advanced Mysticism" msgstr "Haladó misztika" msgid "Basic Luck" msgstr "Alap szerencse" msgid "Expert Luck" msgstr "SzakértÅ‘ szerencse" msgid "Expert Mysticism" msgstr "SzakértÅ‘ misztika" msgid "Advanced Ballistics" msgstr "Haladó ballisztika" msgid "Advanced Eagle Eye" msgstr "Haladó sasszem" msgid "Basic Ballistics" msgstr "Alap ballisztika" msgid "Basic Eagle Eye" msgstr "Alap sasszem" msgid "Expert Ballistics" msgstr "SzakértÅ‘ ballisztika" msgid "Advanced Necromancy" msgstr "Haladó nekromancia" msgid "Basic Estates" msgstr "Alap vagyon" msgid "Basic Necromancy" msgstr "Alap nekromancia" msgid "Expert Eagle Eye" msgstr "SzakértÅ‘ sasszem" msgid "Expert Necromancy" msgstr "SzakértÅ‘ nekromancia" msgid "Advanced Estates" msgstr "Haladó vagyon" msgid "Expert Estates" msgstr "SzakértÅ‘ vagyon" msgid "" "%{skill} reduces the movement penalty for rough terrain by %{count} percent." msgstr "" "%{skill} csökkenti a nehéz terepen történÅ‘ haladásból fakadó hátrányt " "%{count} százalékkal." msgid "%{skill} eliminates the movement penalty for rough terrain." msgstr "%{skill} kiiktatja a nehéz terepen történÅ‘ haladásból fakadó hátrányt." msgid "" "%{skill} increases the damage done by the hero's range attacking creatures " "by %{count} percent." msgstr "" "%{skill} növeli a hÅ‘s távolsági támadással rendelkezÅ‘ lényeinek sebzését " "%{count} százalékkal." msgid "%{skill} increases the hero's movement points by %{count} percent." msgstr "%{skill} növeli a hÅ‘söd mozgáspontjait %{count} százalékkal." msgid "%{skill} increases the hero's viewable area by one square." msgid_plural "%{skill} increases the hero's viewable area by %{count} squares." msgstr[0] "%{skill} növeli a hÅ‘s látóhatárát egy egységgel." msgstr[1] "%{skill} növeli a hÅ‘s látóhatárát %{count} egységgel." msgid "" "%{skill} allows the hero to negotiate with monsters who are weaker than " "their army, and reduces the cost of surrender." msgstr "" "A(z) %{skill} lehetÅ‘vé teszi a hÅ‘s seregénél gyengébb lényekkel való " "tárgyalást. " msgid "" "Approximately %{count} percent of the creatures may offer to join the hero." msgstr "A lények kb. %{count} százaléka csatlakozhat a hÅ‘s seregéhez." msgid "All of the creatures may offer to join the hero." msgstr "Az összes lény csatlakozhat a hÅ‘s seregéhez." msgid "" "The cost of surrender is reduced to %{percent} percent of the total cost of " "troops in the army." msgstr "A(z) %{name} %{count} százalékkal csökkenti a megadás költségét." #, fuzzy msgid "" "%{skill} increases the hero's movement points over water by %{count} percent." msgstr "" "%{skill} növeli a hÅ‘s vízen történÅ‘ mozgáspontjait %{count} százalékkal." msgid "%{skill} increases the hero's troops morale by %{count}." msgstr "%{skill} ennyivel növeli a hÅ‘s seregének morálját: %{count}." msgid "%{skill} allows the hero to learn third level spells." msgstr "%{skill} képessé teszi a hÅ‘st 3. szintű varázslatok megtanulására." msgid "%{skill} allows the hero to learn fourth level spells." msgstr "%{skill} képessé teszi a hÅ‘st 4. szintű varázslatok megtanulására." msgid "%{skill} allows the hero to learn fifth level spells." msgstr "%{skill} képessé teszi a hÅ‘st 5. szintű varázslatok megtanulására." msgid "%{skill} regenerates one additional spell point per day to the hero." msgid_plural "" "%{skill} regenerates %{count} additional spell points per day to the hero." msgstr[0] "" "%{skill} naponta eggyel növeli a hÅ‘s varázspontjainak újratermelÅ‘dését." msgstr[1] "" "%{skill} naponta %{count}-vel növeli a hÅ‘s varázspontjainak újratermelÅ‘dését." msgid "%{skill} increases the hero's luck by %{count}." msgstr "%{skill} ennyivel növeli a hÅ‘s szerencséjét: %{count}." msgid "" "%{skill} gives the hero's catapult shots a greater chance to hit and do " "damage to castle walls." msgstr "" "%{skill} nagyobb esélyt ad a hÅ‘s katapultjának a találatra és a várfal " "rombolására." msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot has a " "greater chance to hit and do damage to castle walls." msgstr "" "%{skill} egy plusz lövést és nagyobb esélyt ad a hÅ‘s katapultjának a " "találatra és a várfal rombolására." msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot " "automatically destroys any wall, except a fortified wall in a Knight castle." msgstr "" "%{skill} egy plusz lövést ad a hÅ‘s katapultjának. Minden lövés automatikusan " "rombolja a falat, kivéve a Lovagvár sánccal megerÅ‘sített várát." msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 1st or " "2nd level spell that was cast by an enemy during combat." msgstr "" "%{skill} %{count} százaléknyi esélyt ad a hÅ‘snek, hogy az ellenfél által a " "csatában használt 1-es vagy 2-es szintű varázslatot tanuljon el." msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 3rd " "level spell (or below) that was cast by an enemy during combat." msgstr "" "%{skill} %{count} százaléknyi esélyt ad a hÅ‘snek, hogy az ellenfél által a " "csatában használt 3-as (vagy alacsonyabb) szintű varázslatot tanuljon el." msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 4th " "level spell (or below) that was cast by an enemy during combat." msgstr "" "%{skill} %{count} százaléknyi esélyt ad a hÅ‘snek, hogy az ellenfél által a " "csatában használt 4-es (vagy alacsonyabb) szintű varázslatot tanuljon el." msgid "" "%{skill} allows %{count} percent of the creatures killed in combat to be " "brought back from the dead as Skeletons." msgstr "" "%{skill} segítségével a csatában megölt lények %{count} százalékából " "Csontvázakat idézhetünk meg." msgid "" "The hero produces %{count} gold pieces per day as tax revenue from estates." msgstr "A hÅ‘s napi %{count} arany adóbevételt kap birtokai után." msgid "Blue" msgstr "Kék" msgid "Green" msgstr "Zöld" msgid "Red" msgstr "Vörös" msgid "Yellow" msgstr "Sárga" msgid "Orange" msgstr "Narancs" msgid "Purple" msgstr "Lila" msgid "barrier|Aqua" msgstr "Tengerkék" msgid "barrier|Blue" msgstr "Kék" msgid "barrier|Brown" msgstr "Barna" msgid "barrier|Gold" msgstr "Arany" msgid "barrier|Green" msgstr "Zöld" msgid "barrier|Orange" msgstr "Narancs" msgid "barrier|Purple" msgstr "Lila" msgid "barrier|Red" msgstr "Vörös" msgid "tent|Aqua" msgstr "Tengerkék" msgid "tent|Blue" msgstr "Kék" msgid "tent|Brown" msgstr "Barna" msgid "tent|Gold" msgstr "Arany" msgid "tent|Green" msgstr "Zöld" msgid "tent|Orange" msgstr "Narancs" msgid "tent|Purple" msgstr "Lila" msgid "tent|Red" msgstr "Vörös" msgid "Experience" msgstr "Tapasztalat" msgid "" "Experience allows your heroes to go up levels, increasing their primary and " "secondary skills." msgstr "" "A tapasztalat lehetÅ‘vé teszi, hogy a hÅ‘sök elsÅ‘dleges és másodlagos " "képességei fejlÅ‘djenek és szintet lépjenek." msgid "Hero/Stats" msgstr "HÅ‘s statisztikái" msgid "Skills" msgstr "Képességek" msgid "Town/Castle" msgstr "Város/Vár" msgid "Garrison" msgstr "HelyÅ‘rség" msgid "Gold Per Day:" msgstr "Arany naponta:" msgid "View Heroes." msgstr "HÅ‘sök mutatása." msgid "Towns/Castles" msgstr "Városok/Várak" msgid "View Towns and Castles." msgstr "Városok/Várak mutatása." msgid "luck|Cursed" msgstr "Ãtkozott" msgid "luck|Awful" msgstr "Szörnyű" msgid "luck|Bad" msgstr "Rossz" msgid "luck|Normal" msgstr "Normál" msgid "luck|Good" msgstr "Jó" msgid "luck|Great" msgstr "Nagyszerű" msgid "luck|Irish" msgstr "Páratlan" msgid "" "Bad luck sometimes falls on the hero's units in combat, causing their " "attacks to only do half damage." msgstr "" "A Rossz szerencse következtében a csatában alkalmanként egyes egységek " "sebzése felére csökken." msgid "" "Neutral luck means the hero's units will never get lucky or unlucky attacks " "on the enemy." msgstr "" "A Semleges szerencse azt jelenti, hogy a csata során se szerencse se " "balszerencse nem éri az egységeid támadását." msgid "" "Good luck sometimes lets the hero's units get lucky attacks (double " "strength) in combat." msgstr "" "Jó szerencse esetén a hÅ‘s egységeinek alkalmanként esélye van szerencsés " "(dupla erÅ‘sségű) támadásra a csata során." msgid "morale|Treason" msgstr "Felségárulás" msgid "morale|Awful" msgstr "Szörnyű" msgid "morale|Poor" msgstr "Rossz" msgid "morale|Normal" msgstr "Normál" msgid "morale|Good" msgstr "Jó" msgid "morale|Great" msgstr "Nagyszerű" msgid "morale|Blood!" msgstr "ÖrjöngÅ‘!" msgid "Bad morale may cause the hero's units to freeze in combat." msgstr "Rossz morál következtében a hÅ‘s egységei kimaradhatnak a körbÅ‘l." msgid "" "Neutral morale means the hero's units will never be blessed with extra " "attacks or freeze in combat." msgstr "" "Semleges morál azt jelenti, hogy a hÅ‘s egységei nem kapnak lehetÅ‘séget extra " "támadásra, de nem is maradnak ki." msgid "Good morale may give the hero's units extra attacks in combat." msgstr "Jó morál esetén a hÅ‘s egységei extra támadási lehetÅ‘séget kaphatnak." msgid "Knight" msgstr "Lovag" msgid "Barbarian" msgstr "Barbár" msgid "Sorceress" msgstr "VarázslónÅ‘" msgid "Warlock" msgstr "Boszorkánymester" msgid "Wizard" msgstr "Varázsló" msgid "Necromancer" msgstr "Nekromanta" msgid "Multi" msgstr "Többszörös" msgid "doubleLined|Knight" msgstr "Lovag" msgid "doubleLined|Barbarian" msgstr "Barbár" msgid "doubleLined|Sorceress" msgstr "" "Varázsló-\n" "nÅ‘" msgid "doubleLined|Warlock" msgstr "" "Boszor-\n" "kánymester" msgid "doubleLined|Wizard" msgstr "Varázsló" msgid "" "doubleLined|Necro-\n" "mancer" msgstr "" "Nekro-\n" "manta" msgid "doubleLinedRace|Multi" msgstr "" "Több-\n" "szörös" msgid "doubleLinedRace|Random" msgstr "Véletlen" msgid "speed|Standing" msgstr "Ãlló" msgid "speed|Crawling" msgstr "Vánszorgó" msgid "speed|Very Slow" msgstr "Csigalassú" msgid "speed|Slow" msgstr "Lassú" msgid "speed|Average" msgstr "Ãtlagos" msgid "speed|Fast" msgstr "Sebes" msgid "speed|Very Fast" msgstr "Szélsebes" msgid "speed|Ultra Fast" msgstr "Nyílsebes" msgid "speed|Blazing" msgstr "Villámgyors" msgid "speed|Instant" msgstr "Itt terem" msgid "week|Squirrel" msgstr "Mókus" msgid "week|Rabbit" msgstr "Nyúl" msgid "week|Gopher" msgstr "Hörcsög" msgid "week|Badger" msgstr "Borz" msgid "week|Rat" msgstr "Patkány" msgid "week|Eagle" msgstr "Sas" msgid "week|Weasel" msgstr "Menyét" msgid "week|Raven" msgstr "Holló" msgid "week|Mongoose" msgstr "Mongúz" msgid "week|Dog" msgstr "Kutya" msgid "week|Aardvark" msgstr "Földimalac" msgid "week|Lizard" msgstr "Gyík" msgid "week|Tortoise" msgstr "TeknÅ‘sbéka" msgid "week|Hedgehog" msgstr "Sün" msgid "week|Condor" msgstr "Kondorkeselyű" msgid "week|Ant" msgstr "Hangya" msgid "week|Grasshopper" msgstr "Szöcske" msgid "week|Dragonfly" msgstr "SzitakötÅ‘" msgid "week|Spider" msgstr "Pók" msgid "week|Butterfly" msgstr "Pillangó" msgid "week|Bumblebee" msgstr "Darázs" msgid "week|Locust" msgstr "Sáska" msgid "week|Earthworm" msgstr "Földigiliszta" msgid "week|Hornet" msgstr "Lódarázs" msgid "week|Beetle" msgstr "Bogár" msgid "week|PLAGUE" msgstr "PESTIS" msgid "week|Unnamed" msgstr "Névtelen" msgid "Desert" msgstr "Sivatag" msgid "Snow" msgstr "Hó" msgid "Wasteland" msgstr "Pusztaság" msgid "Beach" msgstr "Part" msgid "Lava" msgstr "Láva" msgid "Dirt" msgstr "Sár" msgid "Grass" msgstr "Fű" msgid "Ocean" msgstr "Óceán" msgid "maps|Small" msgstr "Kicsi" msgid "maps|Medium" msgstr "Közepes" msgid "maps|Large" msgstr "Nagy" msgid "maps|Extra Large" msgstr "Nagyon nagy" msgid "maps|Custom Size" msgstr "Egyedi" msgid "Ore Mine" msgstr "Ércbánya" msgid "Sulfur Mine" msgstr "Kénbánya" msgid "Crystal Mine" msgstr "Kristálybánya" msgid "Gems Mine" msgstr "DrágakÅ‘bánya" msgid "Gold Mine" msgstr "Aranybánya" msgid "Mine" msgstr "Bánya" msgid "Burma shave." msgstr "HirdetÅ‘tábla." msgid "Next sign 50 miles." msgstr "KövetkezÅ‘ jelzés 50 mérföldre." msgid "See Rock City." msgstr "KÅ‘város" msgid "This space for rent." msgstr "Ez a hely kiadó." msgid "No object" msgstr "Nincs tárgy" msgid "Alchemist Lab" msgstr "Alkímista labor" msgid "Sign" msgstr "Jel" msgid "Buoy" msgstr "Bója" msgid "Skeleton" msgstr "Csontváz" msgid "Daemon Cave" msgstr "Démonbarlang" msgid "Treasure Chest" msgstr "Kincsesláda" msgid "Faerie Ring" msgstr "Tündérgyűrű" msgid "Campfire" msgstr "Tábortűz" msgid "Fountain" msgstr "SzökÅ‘kút" msgid "Gazebo" msgstr "Kilátó" msgid "Genie Lamp" msgstr "Lámpás" msgid "Archer's House" msgstr "Ãjász Ház" msgid "Goblin Hut" msgstr "Goblinkunyhó" msgid "Dwarf Cottage" msgstr "Törp tanya" msgid "Peasant Hut" msgstr "Tanya" msgid "Stables" msgstr "Istálló" msgid "Alchemist's Tower" msgstr "Alkímista torony" msgid "Event" msgstr "Esemény" msgid "Dragon City" msgstr "Sárkányváros" msgid "Lighthouse" msgid_plural "Lighthouses" msgstr[0] "Világítótorony" msgstr[1] "Világítótornyok" msgid "Water Wheel" msgid_plural "Water Wheels" msgstr[0] "Vizikerék" msgstr[1] "Vizikerekek" msgid "Monster" msgstr "Lény" msgid "Obelisk" msgstr "Obeliszk" msgid "Oasis" msgstr "Oázis" msgid "Resource" msgstr "ErÅ‘forrás" msgid "Sawmill" msgstr "Fűrészmalom" msgid "Oracle" msgstr "Orákulum" msgid "Shrine of the First Circle" msgstr "Az ElsÅ‘ Kör szentélye" msgid "Shipwreck" msgstr "Hajóroncs" msgid "Sea Chest" msgstr "Tengeri kincs" msgid "Desert Tent" msgstr "Sivatagi sátor" msgid "Stone Liths" msgstr "Ãtjáró" msgid "Wagon Camp" msgstr "Szekértábor" msgid "Hut of the Magi" msgstr "Máguskunyhó" msgid "Whirlpool" msgstr "Örvény" msgid "Windmill" msgid_plural "Windmills" msgstr[0] "Szélmalom" msgstr[1] "Szélmalmok" msgid "Artifact" msgstr "Varázstárgy" msgid "Mermaid" msgstr "SellÅ‘" msgid "Boat" msgstr "Hajó" msgid "Random Ultimate Artifact" msgstr "Véletlenszerű Szent Varázstárgy" msgid "Random Artifact" msgstr "Véletlenszerű varázstárgy" msgid "Random Resource" msgstr "Véletlenszerű erÅ‘forrás" msgid "Random Monster" msgstr "Véletlenszerű lény" msgid "Random Town" msgstr "Véletlenszerű város" msgid "Random Castle" msgstr "Véletlenszerű vár" msgid "Eye of the Magi" msgstr "Mágus szeme" msgid "Random Monster - weak" msgstr "Véletlenszerű lény - gyenge" msgid "Random Monster - medium" msgstr "Véletlenszerű lény - közepes" msgid "Random Monster - strong" msgstr "Véletlenszerű lény - erÅ‘s" msgid "Random Monster - very strong" msgstr "Véletlenszerű lény - nagyon erÅ‘s" msgid "Hero" msgstr "HÅ‘s" msgid "Nothing Special" msgstr "Semmi érdekes" msgid "Mossy Rock" msgstr "Mohás kÅ‘" msgid "Watch Tower" msgstr "Toronyóra" msgid "Tree House" msgstr "Faház" msgid "Tree City" msgstr "Fa város" msgid "Ruins" msgstr "Romok" msgid "Fort" msgstr "ErÅ‘d" msgid "Abandoned Mine" msgstr "Elhagyott bánya" msgid "Sirens" msgstr "Szirének" msgid "Standing Stones" msgstr "Ãllókövek" msgid "Idol" msgstr "Bálvány" msgid "Tree of Knowledge" msgstr "A Tudás fája" msgid "Witch Doctor's Hut" msgstr "Vajákoskunyhó" msgid "Temple" msgstr "Templom" msgid "Hill Fort" msgstr "ErÅ‘d" msgid "Halfling Hole" msgstr "Félszerzet odú" msgid "Mercenary Camp" msgstr "Zsoldostábor" msgid "Shrine of the Second Circle" msgstr "A Második Kör szentélye" msgid "Shrine of the Third Circle" msgstr "A Harmadik Kör szentélye" msgid "City of the Dead" msgstr "Holtak városa" msgid "Sphinx" msgstr "Szfinx" msgid "Wagon" msgstr "Szekér" msgid "Tar Pit" msgstr "Kátrányosgödör" msgid "Artesian Spring" msgstr "Artéziforrás" msgid "Troll Bridge" msgstr "Trollhíd" msgid "Watering Hole" msgstr "Itató" msgid "Witch's Hut" msgstr "Boszorkánykunyhó" msgid "Xanadu" msgstr "Xanadu" msgid "Lean-To" msgstr "Fészer" msgid "Magellan's Maps" msgstr "Magellán térképe" msgid "Flotsam" msgstr "Uszadék" msgid "Derelict Ship" msgstr "Gazdátlan hajó" msgid "Shipwreck Survivor" msgstr "Hajótörött" msgid "Bottle" msgstr "Palack" msgid "Magic Well" msgstr "Varázskút" msgid "Magic Garden" msgid_plural "Magic Gardens" msgstr[0] "Varázskert" msgstr[1] "Varázskertek" msgid "Observation Tower" msgstr "Kilátótorony" msgid "Freeman's Foundry" msgstr "ÖntÅ‘de" msgid "Reefs" msgstr "Zátonyok" msgid "Volcano" msgstr "Vulkán" msgid "Flowers" msgstr "Virágok" msgid "Rock" msgstr "KÅ‘" msgid "Water Lake" msgstr "Tó" msgid "Mandrake" msgstr "Mandragóra" msgid "Dead Tree" msgstr "Kiszáradt fa" msgid "Stump" msgstr "Tuskó" msgid "Crater" msgstr "Kráter" msgid "Cactus" msgstr "Kaktusz" msgid "Mound" msgstr "Bucka" msgid "Dune" msgstr "Dűne" msgid "Lava Pool" msgstr "Lávató" msgid "Shrub" msgstr "Bozót" msgid "Barrow Mounds" msgstr "Halomsírok" msgid "Random Artifact - Treasure" msgstr "Véletlen tárgy - kincs" msgid "Random Artifact - Minor" msgstr "Véletlen tárgy - kisebb" msgid "Random Artifact - Major" msgstr "Véletlen tárgy - nagyobb" msgid "Barrier" msgstr "Mágikus útakadály" msgid "Traveller's Tent" msgstr "Utazók sátra" msgid "Jail" msgstr "Börtön" msgid "Fire Summoning Altar" msgstr "TűzidézÅ‘ oltár" msgid "Air Summoning Altar" msgstr "LégidézÅ‘ oltár" msgid "Earth Summoning Altar" msgstr "FöldidézÅ‘ oltár" msgid "Water Summoning Altar" msgstr "VízidézÅ‘ oltár" msgid "Unknown Monster" msgstr "Ismeretlen lény" msgid "Unknown Monsters" msgstr "Ismeretlen lények" msgid "Peasant" msgstr "Jobbágy" msgid "Peasants" msgstr "Jobbágyok" msgid "Archer" msgstr "Ãjász" msgid "Archers" msgstr "Ãjászok" msgid "Ranger" msgstr "ErdÅ‘kerülÅ‘" msgid "Rangers" msgstr "ErdÅ‘kerülÅ‘k" msgid "Pikeman" msgstr "Lándzsás" msgid "Pikemen" msgstr "Lándzsások" msgid "Veteran Pikeman" msgstr "Veterán lándzsás" msgid "Veteran Pikemen" msgstr "Veterán lándzsások" msgid "Swordsman" msgstr "Kardforgató" msgid "Swordsmen" msgstr "Kardforgatók" msgid "Master Swordsman" msgstr "Kardforgató mester" msgid "Master Swordsmen" msgstr "Kardforgató mesterek" msgid "Cavalries" msgstr "Lovasok" msgid "Cavalry" msgstr "Lovas" msgid "Champion" msgstr "Bajnok" msgid "Champions" msgstr "Bajnokok" msgid "Paladin" msgstr "Lovag" msgid "Paladins" msgstr "Lovagok" msgid "Crusader" msgstr "Kereszteslovag" msgid "Crusaders" msgstr "Kereszteslovagok" msgid "Goblin" msgstr "Goblin" msgid "Goblins" msgstr "Goblinok" msgid "Orc" msgstr "Ork" msgid "Orcs" msgstr "Orkok" msgid "Orc Chief" msgstr "Ork vezér" msgid "Orc Chiefs" msgstr "Ork vezérek" msgid "Wolf" msgstr "Farkas" msgid "Wolves" msgstr "Farkas" msgid "Ogre" msgstr "Ogre" msgid "Ogres" msgstr "Ogrék" msgid "Ogre Lord" msgstr "Ogre nagyúr" msgid "Ogre Lords" msgstr "Ogre nagyurak" msgid "Troll" msgstr "Troll" msgid "Trolls" msgstr "Trollok" msgid "War Troll" msgstr "Harci troll" msgid "War Trolls" msgstr "Harci trollok" msgid "Cyclopes" msgstr "Küklopszok" msgid "Cyclops" msgstr "Küklopsz" msgid "Sprite" msgstr "Tündér" msgid "Sprites" msgstr "Tündérek" msgid "Dwarf" msgstr "Törp" msgid "Dwarves" msgstr "Törpök" msgid "Battle Dwarf" msgstr "Törp harcos" msgid "Battle Dwarves" msgstr "Törp harcosok" msgid "Elf" msgstr "Tünde" msgid "Elves" msgstr "Tündék" msgid "Grand Elf" msgstr "Tünde úr" msgid "Grand Elves" msgstr "Tünde urak" msgid "Druid" msgstr "Druida" msgid "Druids" msgstr "Druidák" msgid "Greater Druid" msgstr "Nemes druida" msgid "Greater Druids" msgstr "Nemes druidák" msgid "Unicorn" msgstr "Egyszarvú" msgid "Unicorns" msgstr "Egyszarvúak" msgid "Phoenix" msgstr "FÅ‘nix" msgid "Phoenixes" msgstr "FÅ‘nixek" msgid "Centaur" msgstr "Kentaur" msgid "Centaurs" msgstr "Kentaurok" msgid "Gargoyle" msgstr "VízköpÅ‘" msgid "Gargoyles" msgstr "VízköpÅ‘k" msgid "Griffin" msgstr "Griff" msgid "Griffins" msgstr "Griffek" msgid "Minotaur" msgstr "Minotaurusz" msgid "Minotaurs" msgstr "Minotauruszok" msgid "Minotaur King" msgstr "Királyi minotaurusz" msgid "Minotaur Kings" msgstr "Királyi minotauruszok" msgid "Hydra" msgstr "Hidra" msgid "Hydras" msgstr "Hidrák" msgid "Green Dragon" msgstr "Zöld sárkány" msgid "Green Dragons" msgstr "Zöld sárkányok" msgid "Red Dragon" msgstr "Vörös sárkány" msgid "Red Dragons" msgstr "Vörös sárkányok" msgid "Black Dragon" msgstr "Fekete sárkány" msgid "Black Dragons" msgstr "Fekete sárkányok" msgid "Halfling" msgstr "Félszerzet" msgid "Halflings" msgstr "Félszerzetek" msgid "Boar" msgstr "Vaddisznó" msgid "Boars" msgstr "Vaddisznók" msgid "Iron Golem" msgstr "Vasgólem" msgid "Iron Golems" msgstr "Vasgólemek" msgid "Steel Golem" msgstr "Acélgólem" msgid "Steel Golems" msgstr "Acélgólemek" msgid "Roc" msgstr "Rukh-madár" msgid "Rocs" msgstr "Rukh-madarak" msgid "Mage" msgstr "Mágus" msgid "Magi" msgstr "Mágusok" msgid "Archmage" msgstr "FÅ‘mágus" msgid "Archmagi" msgstr "FÅ‘mágusok" msgid "Giant" msgstr "Óriás" msgid "Giants" msgstr "Óriások" msgid "Titan" msgstr "Titán" msgid "Titans" msgstr "Titánok" msgid "Skeletons" msgstr "Csontvázak" msgid "Zombie" msgstr "Zombi" msgid "Zombies" msgstr "Zombik" msgid "Mutant Zombie" msgstr "Mutáns zombi" msgid "Mutant Zombies" msgstr "Mutáns zombik" msgid "Mummies" msgstr "Múmiák" msgid "Mummy" msgstr "Múmia" msgid "Royal Mummies" msgstr "Királyi múmiák" msgid "Royal Mummy" msgstr "Királyi múmia" msgid "Vampire" msgstr "Vámpír" msgid "Vampires" msgstr "Vámpírok" msgid "Vampire Lord" msgstr "Vámpír nagyúr" msgid "Vampire Lords" msgstr "Vámpír nagyurak" msgid "Lich" msgstr "Lich" msgid "Liches" msgstr "Lichek" msgid "Power Lich" msgstr "Åslich" msgid "Power Liches" msgstr "Åslichek" msgid "Bone Dragon" msgstr "Csontsárkány" msgid "Bone Dragons" msgstr "Csontsárkányok" msgid "Rogue" msgstr "Rabló" msgid "Rogues" msgstr "Rablók" msgid "Nomad" msgstr "Nomád" msgid "Nomads" msgstr "Nomádok" msgid "Ghost" msgstr "Szellem" msgid "Ghosts" msgstr "Szellemek" msgid "Genie" msgstr "Dzsinn" msgid "Genies" msgstr "Dzsinnek" msgid "Medusa" msgstr "Medúza" msgid "Medusas" msgstr "Medúzák" msgid "Earth Elemental" msgstr "Földelementál" msgid "Earth Elementals" msgstr "Földelementálok" msgid "Air Elemental" msgstr "Légelementál" msgid "Air Elementals" msgstr "Légelementálok" msgid "Fire Elemental" msgstr "Tűzelementál" msgid "Fire Elementals" msgstr "Tűzelementálok" msgid "Water Elemental" msgstr "Vízelementál" msgid "Water Elementals" msgstr "Vízelementálok" msgid "Random Monsters" msgstr "Véletlenszerű lények" msgid "Random Monster 1" msgstr "Véletlenszerű lény 1" msgid "Random Monsters 1" msgstr "Véletlenszerű lények 1" msgid "Random Monster 2" msgstr "Véletlenszerű lény 2" msgid "Random Monsters 2" msgstr "Véletlenszerű lények 2" msgid "Random Monster 3" msgstr "Véletlenszerű lény 3" msgid "Random Monsters 3" msgstr "Véletlenszerű lények 3" msgid "Random Monster 4" msgstr "Véletlenszerű lény 4" msgid "Random Monsters 4" msgstr "Véletlenszerű lények 4" msgid "Double shot" msgstr "Dupla lövés" msgid "2-hex monster" msgstr "Két hexás lény" msgid "Double strike" msgstr "Dupla csapás" msgid "Double damage to Undead" msgstr "Dupla sebzés élÅ‘halottakon" msgid "% magic resistance" msgstr "% varázslat ellenállóképesség" msgid "Immune to Mind spells" msgstr "Immunis az elmére ható varázslatokra" msgid "Immune to Elemental spells" msgstr "Immunis az elemi varázslatokra" msgid "Immune to Fire spells" msgstr "Immunis a tűz varázslatokra" msgid "Immune to Cold spells" msgstr "Immunis a fagy varázslatokra" msgid "Immune to " msgstr "Immunis erre: " msgid "% immunity to %{spell} spell" msgstr "% immunitás a(z) %{spell} varázslattól" msgid "% damage from Elemental spells" msgstr "% sebzés elemi varázslatoktól" msgid "% chance to Dispel beneficial spells" msgstr "% esély jótékony varázslatok eloszlatására" msgid "% chance to Paralyze" msgstr "% esély bénításra" msgid "% chance to Petrify" msgstr "% esély kÅ‘vé dermesztésre" msgid "% chance to Blind" msgstr "% esély vakításra" msgid "% chance to Curse" msgstr "% esély átokra" msgid "% chance to cast %{spell} spell" msgstr "% eséllyel varázsolja a(z) %{spell} varázslatot" msgid "HP regeneration" msgstr "ÉleterÅ‘ visszatöltÅ‘dés" msgid "Two hexes attack" msgstr "Két hexára támad" msgid "Flyer" msgstr "Repül" msgid "Always retaliates" msgstr "Mindig visszatámad" msgid "Attacks all adjacent enemies" msgstr "Minden mellette álló ellenséget támad" msgid "No melee penalty" msgstr "Nincs közelharc hátrány" msgid "Dragon" msgstr "Sárkány" msgid "Undead" msgstr "ÉlÅ‘halott" msgid "No enemy retaliation" msgstr "Nem támad vissza rá az ellenség" msgid "HP drain" msgstr "ÉleterÅ‘t szív" msgid "Cloud attack" msgstr "Gyilkos köd támadás" msgid "Decreases enemy's morale by " msgstr "Csökkenti az ellenfél morálját ennyivel: " msgid "% chance to halve enemy" msgstr "% esély az ellenség megfelezésére" msgid "Soul Eater" msgstr "Lélekfaló" msgid "Elemental" msgstr "Elemi lény" msgid "No Morale" msgstr "Nincs morál" msgid "200% damage from Fire spells" msgstr "200% sebzés tűzvarázslatoktól" msgid "200% damage from Cold spells" msgstr "200% sebzés fagyvarázslatoktól" msgid "% damage from %{spell} spell" msgstr "% sebzés %{spell} varázslattól" msgid "% immunity to " msgstr "% immunitás ettÅ‘l: " msgid "Lightning" msgstr "Villámlás" msgid "% damage from " msgstr "% sebzés ettÅ‘l: " msgid "The three Anduran artifacts magically combine into one." msgstr "A három Anduran varázstárgy mágikusan eggyéolvadt." msgid "View Spells" msgstr "Varázslatok mutatása" msgid "View %{name} Info" msgstr "%{name} adatai" msgid "Move %{name}" msgstr "%{name} lép" msgid "Cannot move the Spellbook" msgstr "A varázskönyv nem mozgatható" msgid "This item can't be traded." msgstr "Ezt a tárgyat nem lehet elcserélni." msgid "Invalid Artifact" msgstr "Érvénytelen varázstárgy" #, fuzzy msgid "The %{name} increases the hero's knowledge by %{count}." msgstr "A %{name} ennyivel növeli a tudásodat: %{count}." msgid "Ultimate Book of Knowledge" msgstr "Tudás szent könyve" #, fuzzy msgid "The %{name} increases the hero's attack skill by %{count}." msgstr "A %{name} ennyivel növeli a támadóerÅ‘det: %{count}." msgid "Ultimate Sword of Dominion" msgstr "Hatalom szent kardja" #, fuzzy msgid "The %{name} increases the hero's defense skill by %{count}." msgstr "A %{name} ennyivel növeli a védÅ‘erÅ‘det: %{count}." msgid "Ultimate Cloak of Protection" msgstr "Védelem szent köpenye" #, fuzzy msgid "The %{name} increases the hero's spell power by %{count}." msgstr "A %{name} megnöveli a varázserÅ‘det ennyivel: %{count}." msgid "Ultimate Wand of Magic" msgstr "Varázslás szent pálcája" #, fuzzy msgid "" "The %{name} increases the hero's attack and defense skills by %{count} each." msgstr "" "A %{name} mind a támadóerÅ‘det, mind a védÅ‘erÅ‘det növeli ennyivel: %{count}." msgid "Ultimate Shield" msgstr "Szent pajzs" #, fuzzy msgid "" "The %{name} increases the hero's spell power and knowledge by %{count} each." msgstr "" "A %{name} mind a tudásodat, mind a varázserÅ‘det növeli ennyivel: %{count}." msgid "Ultimate Staff" msgstr "Szent jogar" #, fuzzy msgid "" "The %{name} increases each of the hero's basic skills by %{count} points." msgstr "A %{name} minden Alap képességedet növeli ennyivel: %{count}." msgid "Ultimate Crown" msgstr "Szent Korona" msgid "Golden Goose" msgstr "Arany lúd" msgid "The %{name} brings in an income of %{count} gold per day." msgstr "Az %{name} naponta %{count} aranyat hoz a konyhára." msgid "Arcane Necklace of Magic" msgstr "Misztikus varázslás nyaklánc" msgid "" "After rescuing a Sorceress from a cursed tomb, she rewards your heroism with " "an exquisite jeweled necklace." msgstr "" "Miután megmentetted a varázslónÅ‘t az elátkozott sírboltból, megjutalmaz " "hÅ‘siességedért egy gazdagon ékesített nyaklánccal." msgid "Caster's Bracelet of Magic" msgstr "IdézÅ‘ varázslat karkötÅ‘" msgid "" "While searching through the rubble of a caved-in mine, you free a group of " "trapped Dwarves. Grateful, the leader gives you a golden bracelet." msgstr "" "Amint átkutatod a beomlott bánya törmelékét, kiszabadítasz egy csapat " "csapdába esett törpöt. Hálából a vezetÅ‘jük megajándékoz egy arany " "karpereccel." msgid "Mage's Ring of Power" msgstr "MáguserÅ‘ gyűrű" msgid "" "A cry of pain leads you to a Centaur, caught in a trap. Upon setting the " "creature free, he hands you a small pouch. Emptying the contents, you find a " "dazzling jeweled ring." msgstr "" "Fájdalmas jajgatása vezet el egy kentaurhoz, aki csapdába esett. " "Kiszabadítod és hálából átad neked egy kis erszényt. Amikor kiüríted a " "tartalmát, egy gazdagon díszített gyűrűt találsz." msgid "Witch's Broach of Magic" msgstr "Boszorkány mágikus mellcsatja" msgid "" "Alongside the remains of a burnt witch lies a beautiful broach, intricately " "designed. Approaching the corpse with caution, you add the broach to your " "inventory." msgstr "" "Egy megégetett boszorkány maradványai mellett rátalálsz egy gyönyörű melltűt " "találsz, melyet elteszel magadnak." msgid "Medal of Valor" msgstr "Vitézség medál" #, fuzzy msgid "The %{name} increases the morale of the hero's army by %{count}." msgstr "A %{name} ennyivel növeli a csapatod morálját: %{count}." msgid "" "Freeing a virtuous maiden from the clutches of an evil overlord, you are " "granted a Medal of Valor by the King's herald." msgstr "" "Megmentesz egy erényes szűzlányt egy gonosz kegyúr karmaiból, melynek " "elismeréseként a király hírnöke átadja neked a Vitézség medált." msgid "Medal of Courage" msgstr "Bátorság medál" msgid "" "After saving a young boy from a vicious pack of Wolves, you return him to " "his father's manor. The grateful nobleman awards you with a Medal of Courage." msgstr "" "Miután megmentesz egy fiút az ádáz farkasok szorításából és visszakíséred " "apja birtokára, a nemesember hálából megjutalmaz a Bátorság medállal." msgid "Medal of Honor" msgstr "Becsület medál" msgid "" "After freeing a princess of a neighboring kingdom from the evil clutches of " "despicable slavers, she awards you with a Medal of Honor." msgstr "" "Miután megmentesz egy hercegnÅ‘t a szomszédos királyságból egy aljas " "rabszolgakereskedÅ‘ gonosz karmaiból, megajándékoz a Becsület medállal." msgid "Medal of Distinction" msgstr "Kiválóság medál" msgid "" "Ridding the countryside of the hideous Minotaur who made a sport of eating " "noblemen's Knights, you are honored with the Medal of Distinction." msgstr "" msgid "Fizbin of Misfortune" msgstr "Balszerencse medál" #, fuzzy msgid "" "The %{name} greatly decreases the morale of the hero's army by %{count}." msgstr "A %{name} %{count} egységgel lecsökkenti a morálodat." msgid "" "You stumble upon a medal lying alongside the empty road. Adding the medal to " "your inventory, you become aware that you have acquired the undesirable " "Fizbin of Misfortune, greatly decreasing your army's morale." msgstr "" msgid "Thunder Mace of Dominion" msgstr "Hatalom mennydörgÅ‘ buzogánya" msgid "" "During a sudden storm, a bolt of lightning strikes a tree, splitting it. " "Inside the tree you find a mysterious mace." msgstr "" msgid "Armored Gauntlets of Protection" msgstr "Védelem páncélkesztyűi" #, fuzzy msgid "The %{name} increase the hero's defense skill by %{count}." msgstr "A %{name} ennyivel növelik a védÅ‘erÅ‘det: %{count}." msgid "" "You encounter the infamous Black Knight! After a grueling duel ending in a " "draw, the Knight, out of respect, offers you a pair of armored gauntlets." msgstr "" msgid "Defender Helm of Protection" msgstr "Védelem sisak" msgid "" "A glint of golden light catches your eye. Upon further investigation, you " "find a golden helm hidden under a bush." msgstr "" msgid "Giant Flail of Dominion" msgstr "Óriás hatalom cséphadaró" msgid "" "A clumsy Giant has killed himself with his own flail. Knowing your superior " "skill with this weapon, you confidently remove the spectacular flail from " "the fallen Giant." msgstr "" msgid "Ballista of Quickness" msgstr "GyorstüzelÅ‘ hajítógép" #, fuzzy msgid "The %{name} gives the hero's catapult one extra shot per combat round." msgstr "A %{name} plusz lövést ad körönként a hajítógépednek." msgid "" "Walking through the ruins of an ancient walled city, you find the instrument " "of the city's destruction, an elaborately crafted ballista." msgstr "" msgid "Stealth Shield of Protection" msgstr "Védelem titkos pajzsa" msgid "" "A stone statue of a warrior holds a silver shield. As you remove the shield, " "the statue crumbles into dust." msgstr "" msgid "Dragon Sword of Dominion" msgstr "Sárkány hatalom kard" msgid "" "As you are walking along a narrow path, a nearby bush suddenly bursts into " "flames. Before your eyes the flames become the image of a beautiful woman. " "She holds out a magnificent sword to you." msgstr "" msgid "Power Axe of Dominion" msgstr "ErÅ‘ hatalom fejsze" msgid "" "You see a silver axe embedded deeply in the ground. After several " "unsuccessful attempts by your army to remove the axe, you tightly grip the " "handle of the axe and effortlessly pull it free." msgstr "" msgid "Divine Breastplate of Protection" msgstr "Védelem isteni mellvértje" msgid "" "A gang of Rogues is sifting through the possessions of dead warriors. " "Scaring off the scavengers, you note the Rogues had overlooked a beautiful " "breastplate." msgstr "" msgid "Minor Scroll of Knowledge" msgstr "Tudás kis tekercse" msgid "" "Before you appears a levitating glass case with a scroll, perched upon a bed " "of crimson velvet. At your touch, the lid opens and the scroll floats into " "your awaiting hands." msgstr "" msgid "Major Scroll of Knowledge" msgstr "Tudás nagy tekercse" msgid "" "Visiting a local wiseman, you explain the intent of your journey. He reaches " "into a sack and withdraws a yellowed scroll and hands it to you." msgstr "" msgid "Superior Scroll of Knowledge" msgstr "FelsÅ‘bb tudás tekercse" msgid "" "You come across the remains of an ancient Druid. Bones, yellowed with age, " "peer from the ragged folds of her robe. Searching the robe, you discover a " "scroll hidden in the folds." msgstr "" msgid "Foremost Scroll of Knowledge" msgstr "FelsÅ‘bb tudás nagy tekercse" msgid "" "Mangled bones, yellowed with age, peer from the ragged folds of a dead " "Druid's robe. Searching the robe, you discover a scroll hidden within." msgstr "" msgid "Endless Sack of Gold" msgstr "Végtelen aranyzsák" #, fuzzy msgid "The %{name} provides the hero with %{count} gold per day." msgstr "A %{name} naponta %{count} aranyat ad." msgid "" "A little leprechaun dances gleefully around a magic sack. Seeing you " "approach, he stops in mid-stride. The little man screams and stamps his foot " "ferociously, vanishing into thin air. Remembering the old leprechaun saying " "'Finders Keepers', you grab the sack and leave." msgstr "" msgid "Endless Bag of Gold" msgstr "Végtelen aranytarisznya" msgid "" "A noblewoman, separated from her traveling companions, asks for your help. " "After escorting her home, she rewards you with a bag filled with gold." msgstr "" msgid "Endless Purse of Gold" msgstr "Végtelen aranyerszény" msgid "" "In your travels, you find a leather purse filled with gold that once " "belonged to a great warrior king who had the ability to transform any " "inanimate object into gold." msgstr "" msgid "Nomad Boots of Mobility" msgstr "Nomádcsizma" #, fuzzy msgid "The %{name} increase the hero's movement on land." msgstr "A %{name} megnöveli a mozgáspontjaidat szárazföldön." msgid "" "A Nomad trader seeks protection from a tribe of Goblins. For your " "assistance, he gives you a finely crafted pair of boots made from the " "softest leather. Looking closely, you see fascinating ancient carvings " "engraved on the leather." msgstr "" msgid "Traveler's Boots of Mobility" msgstr "Utazócsizma" msgid "" "Discovering a pair of beautifully beaded boots made from the finest and " "softest leather, you thank the anonymous donor and add the boots to your " "inventory." msgstr "" msgid "Lucky Rabbit's Foot" msgstr "Szerencsehozó nyúlláb" #, fuzzy msgid "The %{name} increases the luck of the hero's army by %{count}." msgstr "" "A %{name} ennyivel növeli a csapatod szerencséjét a csatában: %{count}." msgid "" "A traveling merchant offers you a rabbit's foot, made of gleaming silver " "fur, for safe passage. The merchant explains the charm will increase your " "luck in combat." msgstr "" msgid "Golden Horseshoe" msgstr "Arany lópatkó" msgid "" "An ensnared Unicorn whinnies in fright. Murmuring soothing words, you set " "her free. Snorting and stamping her front hoof once, she gallops off. " "Looking down you see a golden horseshoe." msgstr "" msgid "Gambler's Lucky Coin" msgstr "Szerencsepénz" msgid "" "You have captured a mischievous imp who has been terrorizing the region. In " "exchange for his release, he rewards you with a magical coin." msgstr "" msgid "Four-Leaf Clover" msgstr "Négylevelű lóhere" msgid "" "In the middle of a patch of dead and dry vegetation, to your surprise you " "find a healthy green four-leaf clover." msgstr "" msgid "True Compass of Mobility" msgstr "Iránytű" #, fuzzy msgid "The %{name} increases the hero's movement on land and sea." msgstr "" "Az %{name} megnöveli a mozgáspontjaidat szárazföldön és vízen egyaránt." msgid "" "An old man claiming to be an inventor asks you to try his latest invention. " "He then hands you a compass." msgstr "" msgid "Sailor's Astrolabe of Mobility" msgstr "Tengerész asztrolábium" #, fuzzy msgid "The %{name} increases the hero's movement on sea." msgstr "A %{name} megnöveli a mozgáspontjaidat vízen." msgid "" "An old sea captain is being tortured by Ogres. You save him, and in return " "he rewards you with a wondrous instrument to measure the distance of a star." msgstr "" msgid "Evil Eye" msgstr "Gonosz szem" msgid "The %{name} reduces the casting cost of curse spells by half." msgstr "A %{name} felére csökkenti az átok varázslat varázslatpont-igényét." msgid "" "While venturing into a decrepit hut you find the Skeleton of a long dead " "witch. Investigation of the remains reveals a glass eye rolling around " "inside an empty skull." msgstr "" msgid "Enchanted Hourglass" msgstr "Bűvös homokóra" #, fuzzy msgid "" "The %{name} extends the duration of all the hero's spells by %{count} turns." msgstr "A %{name} megnöveli a varázslataid hatóidÅ‘it %{count} körrel." msgid "" "A surprise turn in the landscape finds you in the midst of a grisly scene: " "Vultures picking at the aftermath of a terrible battle. Your cursory search " "of the remains turns up an enchanted hourglass." msgstr "" msgid "Gold Watch" msgstr "Aranyóra" #, fuzzy msgid "The %{name} doubles the effectiveness of the hero's hypnotize spells." msgstr "Az %{name} megduplázza a hipnózis varázslat hatásosságát." msgid "" "In reward for helping his cart out of a ditch, a traveling potion salesman " "gives you a \"magic\" gold watch. Unbeknownst to him, the watch really is " "magical." msgstr "" msgid "Skullcap" msgstr "FejfedÅ‘" msgid "The %{name} halves the casting cost of all mind influencing spells." msgstr "" "A %{name} felére csökkenti minden elmére ható varázslat varázslatpont-" "igényét." msgid "" "A brief stop at an improbable rural inn yields an exchange of money, tales, " "and accidentally, luggage. You find a magical skullcap in your new backpack." msgstr "" "Egy rövid pihenÅ‘ egy vidéki fogadóban lehetÅ‘séget ad pénz, történetek, vagy " "- akaratlanul - csomagok cseréjére. Az elcserélt új hátizsákodban egy " "fejfedÅ‘t találsz." msgid "Ice Cloak" msgstr "Jégköpeny" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from cold spells." msgstr "" "A %{name} felére csökkenti a fagy-varázslatok által okozott sérüléseket." msgid "" "Responding to the panicked cries of a damsel in distress, you discover a " "young woman fleeing from a hungry bear. You slay the beast in the nick of " "time, and the grateful Sorceress weaves a magic cloak from the bear's hide." msgstr "" msgid "Fire Cloak" msgstr "Tűzköpeny" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from fire spells." msgstr "" "A %{name} felére csökkenti a tűz-varázslatok által okozott sérüléseket." msgid "" "You've come upon a fight between a Necromancer and a Paladin. The " "Necromancer blasts the Paladin with a fire bolt, bringing him to his knees. " "Acting quickly, you slay the evil one before the final blow. The grateful " "Paladin gives you the fire cloak that saved him." msgstr "" msgid "Lightning Helm" msgstr "Villámló sisak" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from lightning " "spells." msgstr "" "A %{name} felére csökkenti a villám-varázslatok által okozott sérüléseket." msgid "" "A traveling tinker in need of supplies offers you a helm with a thunderbolt " "design on its top in exchange for food and water. Curious, you accept, and " "later find out that the helm is magical." msgstr "" msgid "Evercold Icicle" msgstr "Örök jégcsap" #, fuzzy msgid "" "The %{name} causes the hero's cold spells to do %{count} percent more damage " "to enemy troops." msgstr "" "Az %{name} %{count} százalékkal megnöveli a fagy-varázslataid hatásosságát " "az ellenséges csapatokon." msgid "" "An icicle withstanding the full heat of the noonday sun attracts your " "attention. Intrigued, you break it off, and find that it does not melt in " "your hand." msgstr "" msgid "Everhot Lava Rock" msgstr "Örökké izzó lávakÅ‘" #, fuzzy msgid "" "The %{name} causes the hero's fire spells to do %{count} percent more damage " "to enemy troops." msgstr "" "Az %{name} %{count} százalékkal megnöveli a tűz-varázslataid hatásosságát az " "ellenséges csapatokon." msgid "" "Your wanderings bring you into contact with a tribe of ape-like beings using " "a magical lava rock that never cools to light their fires. You take pity on " "them and teach them to make fire with sticks. Believing you to be a god, the " "apes give you their rock." msgstr "" msgid "Lightning Rod" msgstr "Villámló bot" #, fuzzy msgid "" "The %{name} causes the hero's lightning spells to do %{count} percent more " "damage to enemy troops." msgstr "" "A %{name} %{count} százalékkal megnöveli a villám-varázslataid hatásosságát " "az ellenséges csapatokon." msgid "" "While waiting out a storm, a lighting bolt strikes a nearby cottage's " "lightning rod, which melts and falls to the ground. The tip of the rod, " "however, survives intact and makes your hair stand on end when you touch it. " "Hmm..." msgstr "" msgid "Snake-Ring" msgstr "Kígyógyűrű" #, fuzzy msgid "The %{name} halves the casting cost of all of the hero's bless spells." msgstr "A %{name} felére csökkenti az áldás varázslat varázslatpont-igényét." msgid "" "You've found an oddly shaped ring on the finger of a long dead traveler. The " "ring looks like a snake biting its own tail." msgstr "" msgid "Ankh" msgstr "Ankh" #, fuzzy msgid "" "The %{name} doubles the effectiveness of all of the hero's resurrect and " "animate spells." msgstr "Az %{name} megduplázza a feltámasztó varázslatok hatásosságát." msgid "" "A fierce windstorm reveals the entrance to a buried tomb. Your investigation " "reveals that the tomb has already been looted, but the thieves overlooked an " "ankh on a silver chain in the dark." msgstr "" msgid "Book of Elements" msgstr "Elemek könyve" #, fuzzy msgid "" "The %{name} doubles the effectiveness of all of the hero's summoning spells." msgstr "Az %{name} megduplázza az idézÅ‘ varázslatok hatásosságát." msgid "" "You come across a conjurer who begs to accompany you and your army awhile " "for safety. You agree, and he offers as payment a copy of the book of the " "elements." msgstr "" msgid "Elemental Ring" msgstr "Elemi gyűrű" msgid "The %{name} halves the casting cost of all summoning spells." msgstr "" "Az %{name} felére csökkenti az idézÅ‘ varázslatok varázslatpont-igényét." msgid "" "While pausing to rest, you notice a bobcat climbing a short tree to get at a " "crow's nest. On impulse, you climb the tree yourself and scare off the cat. " "When you look in the nest, you find a collection of shiny stones and a ring." msgstr "" msgid "Holy Pendant" msgstr "Szent nyakék" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to curse spells." msgstr "A %{name} immunissá teszi a csapataidat az átok varázslatra." msgid "" "In your wanderings you come across a hermit living in a small, tidy hut. " "Impressed with your mission, he takes time out from his meditations to bless " "and give you a charm against curses." msgstr "" msgid "Pendant of Free Will" msgstr "Szabad akarat nyakék" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to hypnotize spells." msgstr "A %{name} immunissá teszi a csapataidat a hipnózis varázslatra." msgid "" "Responding to cries for help, you find river Sprites making a sport of " "dunking an old man. Feeling vengeful, you rescue the man and drag a Sprite " "onto dry land for awhile. The Sprite, uncomfortable in the air, gives you a " "magic pendant to let him go." msgstr "" msgid "Pendant of Life" msgstr "Élet nyakék" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to death spells." msgstr "Az %{name} immunissá teszi a csapataidat a halál varázslatokra." msgid "" "A brief roadside encounter with a small caravan and a game of knucklebones " "wins a magic pendant. Its former owner says that it protects from " "Necromancers' death spells." msgstr "" msgid "Serenity Pendant" msgstr "Higgadtság nyakék" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to berserk spells." msgstr "A %{name} immunissá teszi a csapataidat az Å‘rjöngés varázslatra." msgid "" "The sounds of combat draw you to the scene of a fight between an old " "Barbarian and an eight-headed Hydra. Your timely intervention swings the " "battle in favor of the man, and he rewards you with a pendant he used to use " "to calm his mind for battle." msgstr "" msgid "Seeing-eye Pendant" msgstr "Látás nyakék" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to blindness spells." msgstr "A %{name} immunissá teszi a csapataidat a vakítás varázslatra." msgid "" "You come upon a very old woman, long blind from cataracts and dying alone. " "You tend to her final needs and promise a proper burial. Grateful, she gives " "you a magic pendant emblazoned with a stylized eye. It lets you see with " "your eyes closed." msgstr "" msgid "Kinetic Pendant" msgstr "Eleven nyakék" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to paralyze spells." msgstr "A %{name} immunissá teszi a csapataidat a paralízis varázslatra." msgid "" "You come across a golem wearing a glowing pendant and blocking your way. " "Acting on a hunch, you cut the pendant from its neck. Deprived of its power " "source, the golem breaks down, leaving you with the magical pendant." msgstr "" msgid "Pendant of Death" msgstr "Halál nyakék" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to holy spells." msgstr "A %{name} immunissá teszi a csapataidat a szent varázslatokra." msgid "" "A quick and deadly battle with a Necromancer wins you his magical pendant. " "Later, a Wizard tells you that the pendant protects undead under your " "control from holy word spells." msgstr "" msgid "Wand of Negation" msgstr "Hatástalanító pálca" #, fuzzy msgid "" "The %{name} makes all of the hero's troops immune to dispel magic spells." msgstr "A %{name} megvédi a csapataidat a varázslatoszlató varázslattól." msgid "" "You meet an old Wizard friend of yours traveling in the opposite direction. " "He presents you with a gift: A wand that prevents the use of the dispel " "magic spell on your allies." msgstr "" msgid "Golden Bow" msgstr "Arany íj" #, fuzzy msgid "" "The %{name} eliminates the %{count} percent penalty for the hero's troops " "shooting past obstacles (e.g. castle walls)." msgstr "" "Az %{name} eltörli a(z) %{count} százalékos távolsági lövés büntetést " "tereptárgyakon, várfalakon keresztül." msgid "" "A chance meeting with a famous Archer finds you in a game of knucklebones " "pitting his bow against your horse. You win." msgstr "" msgid "Telescope" msgstr "Teleszkóp" #, fuzzy msgid "" "The %{name} increases the amount of terrain the hero reveals when " "adventuring by %{count} extra square." msgstr "" "A %{name} %{count} egységgel megnöveli a hÅ‘s által felfedett terület " "nagyságát." msgid "" "A merchant from far away lands trades you a new invention of his people for " "traveling supplies. It makes distant objects appear closer, and he calls " "it...\n" "\n" "a telescope." msgstr "" msgid "Statesman's Quill" msgstr "Ãllamférfi pennája" #, fuzzy msgid "" "The %{name} reduces the cost of surrender to %{count} percent of the total " "cost of troops the hero has in their army." msgstr "A(z) %{name} %{count} százalékkal csökkenti a megadás költségét." msgid "" "You pause to help a diplomat with a broken axle fix his problem. In " "gratitude, he gives you a writing quill with magical properties which he " "says will \"help people see things your way\"." msgstr "" msgid "Wizard's Hat" msgstr "Varázslósüveg" #, fuzzy msgid "" "The %{name} increases the duration of the hero's spells by %{count} turns." msgstr "A %{name} megnöveli a varázslataid hatóidejét %{count} körrel." msgid "" "You see a Wizard fleeing from a Griffin and riding like the wind. The Wizard " "opens a portal and rides through, getting his hat knocked off by the edge of " "the gate. The Griffin follows; the gate closes. You pick the hat up, dust it " "off, and put it on." msgstr "" msgid "Power Ring" msgstr "Hatalom gyűrűje" #, fuzzy msgid "The %{name} returns %{count} extra spell points per day to the hero." msgstr "" "A %{name} naponta %{count} ponttal tölti vissza a hÅ‘söd lecsökkent " "varázserejét." msgid "" "You find a small tree that closely resembles the great Warlock Carnauth with " "a ring around one of its twigs. Scraps of clothing and rotting leather lead " "you to suspect that it IS Carnauth, transformed. Since you can't help him, " "you take the magic ring." msgstr "" msgid "Ammo Cart" msgstr "LÅ‘szeres kordé" #, fuzzy msgid "" "The %{name} provides endless ammunition for all of the hero's troops that " "shoot." msgstr "A %{name} végtelen lÅ‘szert ad a távolsági lövÅ‘ egységeidnek." msgid "" "An ammunition cart in the middle of an old battlefield catches your eye. " "Inspection shows it to be in good working order, so you take it along." msgstr "" msgid "Tax Lien" msgstr "Adósságlevél" #, fuzzy msgid "The %{name} costs the hero %{count} gold pieces per day." msgstr "Az %{name} napi %{count} aranyba kerül neked." msgid "" "Your big spending habits have earned you a massive tax bill that you can't " "hope to pay. The tax man takes pity and agrees to only take 250 gold a day " "from your account for life. Check here if you want one dollar to go to the " "presidential campaign election fund." msgstr "" msgid "Hideous Mask" msgstr "Förtelmes maszk" #, fuzzy msgid "The %{name} prevents all 'wandering' armies from joining the hero." msgstr "A %{name} megakadályozza a kósza lények csatlakozását a csapatodhoz." msgid "" "Your looting of the grave of Sinfilas Gardolad, the famous shapeshifting " "Warlock, unearths his fabled mask. Trembling, you put it on and it twists " "your visage into an awful grimace! Oh no! It's actually the hideous mask of " "Gromluck Greene, and you are stuck with it." msgstr "" msgid "Endless Pouch of Sulfur" msgstr "Végtelen kénzacskó" msgid "The %{name} provides %{count} unit of sulfur per day." msgstr "A %{name} napi %{count} ként biztosít neked." msgid "" "You visit an alchemist who, upon seeing your army, is swayed by the " "righteousness of your cause. The newly loyal subject gives you his endless " "pouch of sulfur to help with the war effort." msgstr "" msgid "Endless Vial of Mercury" msgstr "Végtelen higanyfiola" msgid "The %{name} provides %{count} unit of mercury per day." msgstr "A %{name} napi %{count} higanyt biztosít neked." msgid "" "A brief stop at a hastily abandoned Wizard's tower turns up a magical vial " "of mercury that always has a little left on the bottom. Recognizing a " "treasure when you see one, you cap it and slip it in your pocket." msgstr "" msgid "Endless Pouch of Gems" msgstr "Végtelen drágakÅ‘erszény" msgid "The %{name} provides %{count} unit of gems per day." msgstr "A %{name} napi %{count} drágakövet biztosít neked." msgid "" "A short rainstorm brings forth a rainbow...and you can see the end of it. " "Riding quickly, you seize the pot of gold you find there. The leprechaun who " "owns it, unable to stop you from taking it, offers an endless pouch of gems " "for the return of his gold. You accept." msgstr "" msgid "Endless Cord of Wood" msgstr "Végtelen fahalom" msgid "The %{name} provides %{count} unit of wood per day." msgstr "A %{name} napi %{count} fát biztosít neked." msgid "" "Pausing to rest and light a cook fire, you pull wood out of a nearby pile of " "dead wood. As you keep pulling wood from the pile, you notice that it " "doesn't shrink. You realize to your delight that the wood is enchanted, so " "you take it along." msgstr "" msgid "Endless Cart of Ore" msgstr "Végtelen érccsille" msgid "The %{name} provides %{count} unit of ore per day." msgstr "A %{name} napi %{count} ércet biztosít neked." msgid "" "You've found a Goblin weapon smithy making weapons for use against humans. " "With a tremendous yell you and your army descend upon their camp and drive " "them away. A search finds a magic ore cart that never runs out of iron." msgstr "" msgid "Endless Pouch of Crystal" msgstr "Végtelen kristályzacskó" msgid "The %{name} provides %{count} unit of crystal per day." msgstr "A %{name} napi %{count} kristályt biztosít neked." msgid "" "Taking shelter from a storm in a small cave, you notice a small patch of " "crystal in one corner. Curious, you break a piece off and notice that the " "original crystal grows the lost piece back. You decide to stuff the entire " "patch into a pouch and take it with you." msgstr "" msgid "Spiked Helm" msgstr "Tüskés sisak" msgid "" "Your army is ambushed by a small tribe of wild (and none too bright) Orcs. " "You fend them off easily and the survivors flee in all directions. One of " "the Orcs was wearing a polished spiked helm. Figuring it will make a good " "souvenir, you take it." msgstr "" msgid "Spiked Shield" msgstr "Tüskés pajzs" msgid "" "You come upon a bridge spanning a dry gully. Before you can cross, a Troll " "steps out from under the bridge and demands payment before it will permit " "you to pass. You refuse, and the Troll charges, forcing you to slay it. You " "take its spiked shield as a trophy." msgstr "" msgid "White Pearl" msgstr "Fehér gyöngy" msgid "" "A walk across a dry saltwater lake bed yields an unlikely prize: A white " "pearl amidst shattered shells and debris." msgstr "" msgid "Black Pearl" msgstr "Fekete gyöngy" msgid "" "Rumors of a Griffin of unusual size preying upon the countryside lead you to " "its cave lair. A quick, brutal fight dispatches the beast, and a search of " "its foul nest turns up a huge black pearl." msgstr "" msgid "Magic Book" msgstr "Varázskönyv" #, fuzzy msgid "The %{name} enables the hero to cast spells." msgstr "A(z) %{name} lehetÅ‘vé teszi varázslatok megidézését." msgid "Dummy 1" msgstr "Próba 1" msgid "The reserved artifact." msgstr "A félretett varázstárgy." msgid "Dummy 2" msgstr "Próba 2" msgid "Dummy 3" msgstr "Próba 3" msgid "Dummy 4" msgstr "Próba 4" msgid "Spell Scroll" msgstr "Varázsige tekercs" #, fuzzy msgid "" "This %{name} gives the hero the ability to cast the %{spell} spell if the " "hero has a Magic Book." msgstr "" "A %{name} lehetÅ‘vé teszi a(z) %{spell} varázslat használatát, ha van " "varázskönyved." msgid "" "You find an elaborate container which houses an old vellum scroll. The runes " "on the container are very old, and the artistry with which it was put " "together is stunning. As you pull the scroll out, you feel imbued with " "magical power." msgstr "" msgid "Arm of the Martyr" msgstr "Mártír keze" #, fuzzy msgid "" "The %{name} increases the hero's spell power by %{count} but adds the undead " "morale penalty." msgstr "" "A %{name} %{count} ponttal növeli a varázserÅ‘det, de büntetésként élÅ‘halott " "morált ad." msgid "" "One of the less intelligent members of your party picks up an arm off of the " "ground. Despite its missing a body, it is still moving. Your troops find the " "dismembered arm repulsive, but you cannot bring yourself to drop it: it " "seems to hold some sort of magical power that influences your decision " "making." msgstr "" msgid "Breastplate of Anduran" msgstr "Anduran mellvértje" #, fuzzy msgid "The %{name} increases the hero's defense by %{count}." msgstr "A %{name} %{count} ponttal növeli a védekezÅ‘képességedet." msgid "" "You come upon a sign. It reads: \"Here lies the body of Anduran. Bow and " "swear fealty, and you shall be rewarded.\" You decide to do as it says. As " "you stand up, you feel a coldness against your skin. Looking down, you find " "that you are suddenly wearing a gleaming, ornate breastplate." msgstr "" msgid "Broach of Shielding" msgstr "Védelem mellcsat" msgid "" "The %{name} provides %{count} percent protection from Armageddon and " "Elemental Storm, but decreases spell power by 2." msgstr "" "A %{name} %{count} százalék védelmet nyújt az Armageddon és az Elemi vihar " "varázslatok ellen, ugyanakkor kettÅ‘vel csökkenti a varázserÅ‘t." msgid "" "A kindly Sorceress thinks that your army's defenses could use a magical " "boost. She offers to enchant the Broach that you wear on your cloak, and you " "accept." msgstr "" "Egy barátságos varázslónÅ‘ úgy véli, hogy seregednek hasznára lehet a mágia " "elleni védelmének a megerÅ‘sítése. Felajánlja, hogy a köpenyed mellcsatját " "védelmezÅ‘ varázstárggyá változtatja. Elfogadod az ajánlatot." msgid "Battle Garb of Anduran" msgstr "Anduran Harci Pajzsa" #, fuzzy msgid "" "The %{name} combines the powers of the three Anduran artifacts. It provides " "maximum luck and morale for the hero's troops and gives the hero the Town " "Portal spell." msgstr "" "A %{name} összekapcsolja a három Anduran varázstárgy erejét. Maximum " "szerencsét és morált, valamint Városkapu varázslatot ad." msgid "" "Out of pity for a poor peasant, you purchase a chest of old junk they are " "hawking for too much gold. Later, as you search through it, you find it " "contains the 3 pieces of the legendary battle garb of Anduran!" msgstr "" msgid "Crystal Ball" msgstr "Kristálygömb" #, fuzzy msgid "" "The %{name} lets the hero get more specific information about monsters, " "enemy heroes, and castles nearby the hero." msgstr "" "A %{name} sokkal pontosabb információkat nyújt az azt viselÅ‘ hÅ‘s " "környezetében lévÅ‘ lényekrÅ‘l, kastélyokról, hÅ‘sökrÅ‘l." msgid "" "You come upon a caravan of gypsies who are feasting and fortifying their " "bodies with mead. They call you forward and say \"If you prove that you can " "dance the Rama-Buta, we will reward you.\" You don't know it, but try " "anyway. They laugh hysterically, but admire your bravery, giving you a " "Crystal Ball." msgstr "" msgid "Heart of Fire" msgstr "Tűzszív" msgid "" "The %{name} provides %{count} percent protection from fire, but doubles the " "damage taken from cold." msgstr "" "A %{name} %{count} százalékos védelmet nyújt a tűzvarázslatok ellen, de " "duplázza a sebzést a fagyvarázslatoktól." msgid "" "You enter a recently burned glade and come upon a Fire Elemental sitting " "atop a rock. It looks up, its flaming face contorted in a look of severe " "pain. It then tosses a glowing object at you. You put up your hands to block " "it, but it passes right through them and sears itself into your chest." msgstr "" msgid "Heart of Ice" msgstr "Jégszív" msgid "" "The %{name} provides %{count} percent protection from cold, but doubles the " "damage taken from fire." msgstr "" "A %{name} %{count} százalékos védelmet nyújt a fagyvarázslatok ellen, de " "duplázza a sebzést a tűzvarázslatoktól." msgid "" "Suddenly, a biting coldness engulfs your body. You seize up, falling from " "your horse. The pain subsides, but you still feel as if your chest is " "frozen. As you pick yourself up off of the ground, you hear hearty laughter. " "You turn around just in time to see a Frost Giant run off into the woods and " "disappear." msgstr "" msgid "Helmet of Anduran" msgstr "Anduran sisakja" msgid "" "You spy a gleaming object poking up out of the ground. You send a member of " "your party over to investigate. He comes back with a golden helmet in his " "hands. You realize that it must be the helmet of the legendary Anduran, the " "only man who was known to wear solid gold armor." msgstr "" msgid "Holy Hammer" msgstr "Szent kalapács" msgid "" "You come upon a battle where a Paladin has been mortally wounded by a group " "of Zombies. He asks you to take his hammer and finish what he started. As " "you pick it up, it begins to hum, and then everything becomes a blur. The " "Zombies lie dead, the hammer dripping with blood. You strap it to your belt." msgstr "" msgid "Legendary Scepter" msgstr "Legendás jogar" msgid "The %{name} adds %{count} points to all attributes." msgstr "A %{name} %{count} ponttal növeli az összes képességedet." msgid "" "Upon cresting a small hill, you come upon a ridiculous looking sight. A " "Sprite is attempting to carry a Scepter that is almost as big as it is. " "Trying not to laugh, you ask, \"Need help?\" The Sprite glares at you and " "answers: \"You think this is funny? Fine. You can carry it. I much prefer " "flying anyway.\"" msgstr "" msgid "" "An old seaman tells you a tale of an enchanted masthead that he used in his " "youth to rally his crew during times of trouble. He then hands you a faded " "map that shows where he hid it. After much exploring, you find it stashed " "underneath a nearby dock." msgstr "" msgid "Masthead" msgstr "Ãrbóc" #, fuzzy msgid "" "The %{name} boosts the hero's troops' luck and morale by %{count} each in " "sea combat." msgstr "" "A %{name} %{count} ponttal növeli mind a szerencsédet, mind pedig a " "morálodat tengeri ütközetben." msgid "Sphere of Negation" msgstr "Tagadás gömbje" msgid "The %{name} disables all spell casting, for both sides, in combat." msgstr "A %{name} megakadályozza, hogy a háborúzó felek varázsolhassanak." msgid "" "You stop to help a Peasant catch a runaway mare. To show his gratitude, he " "hands you a tiny sphere. As soon as you grasp it, you feel the magical " "energy drain from your limbs..." msgstr "" msgid "Staff of Wizardry" msgstr "Varázsjogar" #, fuzzy msgid "The %{name} boosts the hero's spell power by %{count}." msgstr "A %{name} %{count} ponttal növeli a varázserÅ‘det." msgid "" "While out scaring up game, your troops find a mysterious staff levitating " "about three feet off of the ground. They hand it to you, and you notice an " "inscription. It reads: \"Brains best brawn and magic beats might. Heed my " "words, and you'll win every fight.\"" msgstr "" msgid "Sword Breaker" msgstr "KardtörÅ‘" #, fuzzy msgid "The %{name} increases the hero's defense by %{count} and attack by 1." msgstr "A(z) %{name} eggyel növeli a támadó- és %{count} ponttal a védÅ‘erÅ‘det." msgid "" "A former Captain of the Guard admires your quest and gives you the enchanted " "Sword Breaker that he relied on during his tour of duty." msgstr "" "A korábbi Årkapitány küldetésed nagyrabecsülése jeléül átadja neked ezt a " "nagyszerű KardtörÅ‘t, melyet szolgálati ideje alatt viselt." msgid "Sword of Anduran" msgstr "Anduran Kardja" msgid "" "A Troll stops you and says: \"Pay me 5,000 gold, or the Sword of Anduran " "will slay you where you stand.\" You refuse. The troll grabs the sword " "hanging from its belt, screams in pain, and runs away. Picking up the fabled " "sword, you give thanks that half-witted Trolls tend to grab the wrong end of " "sharp objects." msgstr "" "Egy troll megállít és ezt mondja: \"Fizess 5000 aranyat, különben Anduran " "Kardja menten kettéhasít.\" Elutasítod. Erre a troll megragadja az övén lógó " "kardot és rögtön felordít a fájdalomtól, majd elszalad. Felveszed a mesés " "kardot és hálát adsz, amiért ezek a féleszű trollok az élénél fogják meg az " "éles tárgyakat." msgid "Spade of Necromancy" msgstr "Nekromancia sírásó lapát" #, fuzzy msgid "The %{name} gives the hero increased necromancy skill." msgstr "A %{name} fokozza a Nekromancia képességedet." msgid "" "A dirty shovel has been thrust into a dirt mound nearby. Upon investigation, " "you discover it to be the enchanted shovel of the Gravediggers, long thought " "lost by mortals." msgstr "" "Egy koszos ásólapátot szúrtak egy közeli sárhalomba. Tüzetesen megvizsgálod " "és felismered, hogy ez a Sírásók ásólapátja, melyrÅ‘l régóta azt hitték, hogy " "elvesztették a halandók." msgid "spellBonus|selected by user" msgstr "kiválasztva" msgid "Wood" msgstr "Fa" msgid "Mercury" msgstr "Higany" msgid "Ore" msgstr "Érc" msgid "Sulfur" msgstr "Kén" msgid "Crystal" msgstr "Kristály" msgid "Gems" msgstr "DrágakÅ‘" msgid "Gold" msgstr "Arany" msgid "" "There are seven resources in Heroes 2, used to build and improves castles, " "purchase troops and recruit heroes. Gold is the most common, required for " "virtually everything. Wood and ore are used for most buildings. Gems, " "Mercury, Sulfur and Crystal are rare magical resources used for the most " "powerful creatures and buildings." msgstr "" "Hét erÅ‘forrás van a Heroes 2-ben, melyeket építésre, csapatok vásárlására, " "hÅ‘sök toborzására lehet használni. Az Arany a legáltalánosabb, mely szinte " "mindenhez szükséges. A Fa és Érc használatos a legtöbb épülethez. A DrágakÅ‘, " "a Higany és a Kristály a legerÅ‘sebb teremtményekhez, vagy építményekhez kell." msgid "" "Causes a giant fireball to strike the selected area, damaging all nearby " "creatures." msgstr "" "Egy hatalmas tűzgolyó robban a területen, megsebesítvén a közeli lényeket." msgid "Fireball" msgstr "Tűzgolyó" msgid "Fireblast" msgstr "Tűzlabda" msgid "" "An improved version of fireball, fireblast affects two hexes around the " "center point of the spell, rather than one." msgstr "" "A tűzgolyó fejlettebb változata, a varázslat középpontjától 2 hexányi " "területen hat." msgid "Causes a bolt of electrical energy to strike the selected creature." msgstr "Villámcsapást okoz a kiválasztott lényen." msgid "Lightning Bolt" msgstr "Villám" msgid "Chain Lightning" msgstr "Láncvillám" msgid "" "Causes a bolt of electrical energy to strike a selected creature, then " "strike the nearest creature with half damage, then strike the NEXT nearest " "creature with half again damage, and so on, until it becomes too weak to be " "harmful. Warning: This spell can hit your own creatures!" msgstr "" "Villámcsapások láncolatát okozza a kiválasztott lénytÅ‘l kezdÅ‘dÅ‘en, aztán a " "hozzá LEGKÖZELEBBIN feleakkora sebzéssel, és így tovább mindaddig, amíg el " "nem fogy az energiája. Vigyázat: ez a varázslat saját egységet is sebezhet!" msgid "Teleport" msgstr "Teleportálás" msgid "" "Teleports the creature you select to any open position on the battlefield." msgstr "Ãthelyezi a kiválasztott egységet a csatatér bármely szabad pontjára." msgid "Cure" msgstr "Gyógyítás" msgid "" "Removes all negative spells cast upon one of your units, and restores up to " "%{count} HP per level of spell power." msgstr "" "Elűz minden negatív varázslatot egy egységedrÅ‘l és a varázserÅ‘d mértéke " "függvényében visszaállít %{count} életerÅ‘t." msgid "Mass Cure" msgstr "Tömeges gyógyítás" msgid "" "Removes all negative spells cast upon your forces, and restores up to " "%{count} HP per level of spell power, per creature." msgstr "" "Elűz minden negatív varázslatot az egységeidrÅ‘l és a varázserÅ‘d mértéke " "függvényében visszaállít %{count} életerÅ‘t." msgid "Resurrect" msgstr "Feltámasztás" msgid "Resurrects creatures from a damaged or dead unit until end of combat." msgstr "" "A csata végéig feltámaszt teremtményeket halott vagy sérült egységekbÅ‘l." msgid "Resurrect True" msgstr "Valódi feltámasztás" msgid "Resurrects creatures from a damaged or dead unit permanently." msgstr "Maradandóan feltámaszt teremtményeket halott vagy sérült egységekbÅ‘l." msgid "Haste" msgstr "Gyorsítás" msgid "Increases the speed of any creature by %{count}." msgstr "%{count} ponttal növeli bármely lény sebességét." msgid "Increases the speed of all of your creatures by %{count}." msgstr "%{count} ponttal növeli az összes saját lény sebességét." msgid "Mass Haste" msgstr "Tömeges gyorsítás" msgid "Slows target to half movement rate." msgstr "Felére lassítja a kiválasztott lény sebességét." msgid "spell|Slow" msgstr "Lassítás" msgid "Mass Slow" msgstr "Tömeges lassítás" msgid "Slows all enemies to half movement rate." msgstr "Felezi az összes ellenség sebességét." msgid "Clouds the affected creatures' eyes, preventing them from moving." msgstr "Elhomályosítja az érintett lény látását és megakadályozza a mozgásban." msgid "spell|Blind" msgstr "Vakítás" msgid "Bless" msgstr "Ãldás" msgid "Causes the selected creatures to inflict maximum damage." msgstr "Maximális sebzést ad a kiválasztott lényeknek." msgid "Causes all of your units to inflict maximum damage." msgstr "Maximális sebzést ad az összes saját egységednek." msgid "Mass Bless" msgstr "Tömeges áldás" msgid "Magically increases the defense skill of the selected creatures." msgstr "A kiválasztott lény védÅ‘ereje varázslatosan növekszik." msgid "Stoneskin" msgstr "KÅ‘bÅ‘r" msgid "" "Increases the defense skill of the targeted creatures. This is an improved " "version of Stoneskin." msgstr "" "A kiválasztott lény védÅ‘ereje növekszik. Ez a KÅ‘bÅ‘r egy fejlettebb változata." msgid "Steelskin" msgstr "AcélbÅ‘r" msgid "Causes the selected creatures to inflict minimum damage." msgstr "Minimálisra csökkenti a kiválasztott lények sebzését." msgid "Curse" msgstr "Ãtok" msgid "Causes all enemy troops to inflict minimum damage." msgstr "Minimálisra csökkenti az összes ellenséges lény sebzését." msgid "Mass Curse" msgstr "Tömeges átok" msgid "Damages all undead in the battle." msgstr "Megsebzi az összes élÅ‘halott egységet a csatában." msgid "Holy Word" msgstr "Szent Ige" msgid "" "Damages all undead in the battle. This is an improved version of Holy Word." msgstr "" "Megsebesít minden élÅ‘holtat a csatában. A Szent Ige varázslat egy fejlettebb " "formája." msgid "Holy Shout" msgstr "Szent kiáltás" msgid "Anti-Magic" msgstr "Antimágia" msgid "Prevents harmful magic against the selected creatures." msgstr "" "Megakadályozza a kártékony varázslatok idézését a kiválasztott egységeken." msgid "Dispel Magic" msgstr "Varázslatűzés" msgid "Removes all magic spells from a single target." msgstr "Eloszlat minden varászlatot a kiválasztott egységrÅ‘l." msgid "Mass Dispel" msgstr "Tömeges varázslatűzés" msgid "Removes all magic spells from all creatures." msgstr "Eloszlat minden varászlatot minden lényrÅ‘l." msgid "Causes a magic arrow to strike the selected target." msgstr "Varázsnyíllal mér csapást a kiválasztott célpontra." msgid "Magic Arrow" msgstr "Varázsnyíl" msgid "Berserker" msgstr "Årjöngés" msgid "Causes a creature to attack its nearest neighbor." msgstr "A lény megtámadja a legközelebbi szomszédját." msgid "Armageddon" msgstr "Végítélet" msgid "" "Holy terror strikes the battlefield, causing severe damage to all creatures." msgstr "Isteni csapást mér a harcmezÅ‘ összes egységére." msgid "Elemental Storm" msgstr "Elemi vihar" msgid "Magical elements pour down on the battlefield, damaging all creatures." msgstr "Varázslat zúdul a csatatérre és minden lény sebzÅ‘dik." msgid "" "A rain of rocks strikes an area of the battlefield, damaging all nearby " "creatures." msgstr "KÅ‘esÅ‘ zúdul a csatatérre és minden lényt megsebez a közelben." msgid "Meteor Shower" msgstr "MeteoresÅ‘" msgid "Paralyze" msgstr "Bénítás" msgid "The targeted creatures are paralyzed, unable to move or retaliate." msgstr "A kiválasztott egység megbénul, nem képes mozogni vagy visszaütni." msgid "Hypnotize" msgstr "Hipnózis" msgid "" "Brings a single enemy unit under your control if its hits are less than " "%{count} times the caster's spell power." msgstr "" "Ãtadja egy ellenséges egység irányítását, ha ütÅ‘ereje kisebb, mint a " "varázserÅ‘d %{count}-szorosa." msgid "Cold Ray" msgstr "Jégsugár" msgid "Drains body heat from a single enemy unit." msgstr "TesthÅ‘t von el a kiválasztott ellenséges egységbÅ‘l." msgid "Cold Ring" msgstr "Jéggyűrű" msgid "" "Drains body heat from all units surrounding the center point, but not " "including the center point." msgstr "" "TesthÅ‘t von el minden szomszédos egységbÅ‘l, kivéve a középpontban állót." msgid "Disrupting Ray" msgstr "GyengítÅ‘ sugár" msgid "Reduces the defense rating of an enemy unit by three." msgstr "Csökkenti egy ellenséges egység védelmi osztályát hárommal." msgid "Damages all living (non-undead) units in the battle." msgstr "Az összes élÅ‘ (nem élÅ‘halott) egység a csatatéren sebzÅ‘dik." msgid "Death Ripple" msgstr "Halállökés" msgid "Death Wave" msgstr "Halálhullám" msgid "" "Damages all living (non-undead) units in the battle. This spell is an " "improved version of Death Ripple." msgstr "" "Megsebesít minden élÅ‘t (nem élÅ‘halottat) a csatában. Ez a varázslat a " "Halállökés egy fejlettebb változata." msgid "Dragon Slayer" msgstr "SárkányölÅ‘" msgid "Greatly increases a unit's attack skill vs. Dragons." msgstr "Az egység támadóereje jelentÅ‘sen növekszik sárkányok ellen." msgid "Blood Lust" msgstr "Vérszomj" msgid "Increases a unit's attack skill." msgstr "Az egység támadóereje növekszik." msgid "Animate Dead" msgstr "Feltámasztás" msgid "Resurrects creatures from a damaged or dead undead unit permanently." msgstr "Maradandóan feltámaszt teremtményeket halott vagy sérült egységekbÅ‘l." msgid "Mirror Image" msgstr "Tükörkép" msgid "" "Creates an illusionary unit that duplicates one of your existing units. This " "illusionary unit does the same damages as the original, but will vanish if " "it takes any damage." msgstr "" "Egy kiválasztott létezÅ‘ egységedrÅ‘l készít egy hamis másolatot. Az illúzió " "ugyanakkora erÅ‘vel támad, mint az eredeti, de bármekkora sebzés " "következtében eltűnik." msgid "Shield" msgstr "Pajzs" msgid "" "Halves damage received from ranged attacks for a single unit. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Felére csökkenti a kiválasztott egység sérülékenységét a távolsági " "lövésektÅ‘l. Ez nem vonatkozik a vártornyok, illetve a hajítógép által " "okozott sérülésekre." msgid "Mass Shield" msgstr "Tömeges pajzs" msgid "" "Halves damage received from ranged attacks for all of your units. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Felére csökkenti minden egység sérülékenységét a távolsági lövésektÅ‘l. Ez " "nem vonatkozik a vártornyok, illetve a hajítógép által okozott sérülésekre." msgid "Summon Earth Elemental" msgstr "Földelementál idézése" msgid "Summons Earth Elementals to fight for your army." msgstr "Megidézett Földelementálok segítenek a harcban." msgid "Summon Air Elemental" msgstr "Légelementál idézése" msgid "Summons Air Elementals to fight for your army." msgstr "Megidézett Légelementálok segítenek a harcban." msgid "Summon Fire Elemental" msgstr "Tűzelementál idézése" msgid "Summons Fire Elementals to fight for your army." msgstr "Megidézett Tűzelementálok segítenek a harcban." msgid "Summon Water Elemental" msgstr "Vízelementál idézése" msgid "Summons Water Elementals to fight for your army." msgstr "Megidézett Vízelementálok segítenek a harcban." msgid "Damages castle walls." msgstr "Rombolja a vár falait." msgid "Earthquake" msgstr "Földrengés" msgid "Causes all mines across the land to become visible." msgstr "Láthatóvá teszi a környék összes bányáját." msgid "View Mines" msgstr "Bányák mutatása" msgid "Causes all resources across the land to become visible." msgstr "Láthatóvá teszi a környék összes nyersanyagát." msgid "View Resources" msgstr "Nyersanyagok mutatása" msgid "Causes all artifacts across the land to become visible." msgstr "Minden varázstárgyat megmutat a vidéken." msgid "View Artifacts" msgstr "Varázstárgyak mutatása" msgid "Causes all towns and castles across the land to become visible." msgstr "A környék összes városa és vára láthatóvá válik." msgid "View Towns" msgstr "Városok mutatása" msgid "Causes all Heroes across the land to become visible." msgstr "A környék összes hÅ‘se láthatóvá válik." msgid "View Heroes" msgstr "HÅ‘sök mutatása" msgid "Causes the entire land to become visible." msgstr "Az egész vidék láthatóvá válik." msgid "View All" msgstr "Minden mutatása" msgid "Allows the caster to view detailed information on enemy Heroes." msgstr "Részletes információkat ad az ellenséges hÅ‘sökrÅ‘l." msgid "Summon Boat" msgstr "Hajó idézése" msgid "" "Summons the nearest unoccupied, friendly boat to an adjacent shore location. " "A friendly boat is one which you just built or were the most recent player " "to occupy." msgstr "" "Megidézi egy közeli partra a legközelebbi használaton kívüli baráti " "tulajdonban lévÅ‘ hajót. Baráti hajó az, amit te építettél, vagy hÅ‘söd " "használt legutóbb." msgid "Allows the caster to magically transport to a nearby location." msgstr "" "LehetÅ‘vé teszi a megidézÅ‘ számára, hogy egy közeli helyre teleportáljon." msgid "Dimension Door" msgstr "Dimenziókapu" msgid "Returns the caster to any town or castle currently owned." msgstr "" "A varázslat megidézÅ‘jét visszarepíti bármelyik saját városba vagy várba." msgid "Town Gate" msgstr "Városkapu" msgid "" "Returns the hero to the town or castle of choice, provided it is controlled " "by you." msgstr "A hÅ‘s visszaugorhat egy kiválasztott saját városba vagy várba." msgid "Visions" msgstr "Látomás" msgid "" "Visions predicts the likely outcome of an encounter with a neutral army camp." msgstr "Megjósolja az összecsapás kimenetelét egy semleges csapattal szemben." msgid "Haunt" msgstr "Bánya elátkozás" msgid "" "Haunts a mine you control with Ghosts. This mine stops producing resources. " "(If I can't keep it, nobody will!)" msgstr "" "A bánya kísértjárta hellyé válik és nem termel többé. (Ha az enyém nem " "lehet, ne legyen másé sem!)" msgid "Set Earth Guardian" msgstr "Földelementál Å‘r" msgid "Sets Earth Elementals to guard a mine against enemy armies." msgstr "Földelementálok beállítása bányaÅ‘rzÅ‘nek." msgid "Set Air Guardian" msgstr "Légelementál Å‘r" msgid "Sets Air Elementals to guard a mine against enemy armies." msgstr "Légelementálok beállítása bányaÅ‘rzÅ‘nek." msgid "Set Fire Guardian" msgstr "Tűzelementál Å‘r" msgid "Sets Fire Elementals to guard a mine against enemy armies." msgstr "Tűzelementálok beállítása bányaÅ‘rzÅ‘nek." msgid "Set Water Guardian" msgstr "Vízelementál Å‘r" msgid "Sets Water Elementals to guard a mine against enemy armies." msgstr "Vízelementálok beállítása bányaÅ‘rzÅ‘nek." msgid "Random Spell" msgstr "Véletlenszerű varázslat" msgid "Randomly selected spell of any level." msgstr "Véletlenszerűen kiválasztott bármilyen szintű varázslat." msgid "Random 1st Level Spell" msgstr "Véletlenszerű 1. szintű varázslat" msgid "Randomly selected 1st level spell." msgstr "Véletlenszerűen kiválasztott 1. szintű varázslat." msgid "Random 2nd Level Spell" msgstr "Véletlenszerű 2. szintű varázslat" msgid "Randomly selected 2nd level spell." msgstr "Véletlenszerűen kiválasztott 2. szintű varázslat." msgid "Random 3rd Level Spell" msgstr "Véletlenszerű 3. szintű varázslat" msgid "Randomly selected 3rd level spell." msgstr "Véletlenszerűen kiválasztott 3. szintű varázslat." msgid "Random 4th Level Spell" msgstr "Véletlenszerű 4. szintű varázslat" msgid "Randomly selected 4th level spell." msgstr "Véletlenszerűen kiválasztott 4. szintű varázslat." msgid "Random 5th Level Spell" msgstr "Véletlenszerű 5. szintű varázslat" msgid "Randomly selected 5th level spell." msgstr "Véletlenszerűen kiválasztott 5. szintű varázslat." msgid "Petrification" msgstr "KÅ‘vé dermesztés" msgid "" "Turns the affected creature into stone. A petrified creature receives half " "damage from a direct attack." msgstr "" "KÅ‘vé dermeszti az érintett lényt. A kÅ‘vé vált egység fele sebzést szenved " "el, ha közvetlenül támadják." msgid "You have no Magic Book, so you cannot cast a spell." msgstr "Nincs varázskönyved, így nem tudsz varázsolni." msgid "No spell to cast." msgstr "Nincs idézhetÅ‘ varázslat." msgid "Your hero has %{point} spell points remaining." msgstr "A hÅ‘södnek van még %{point} varázspontja." msgid "View Adventure Spells" msgstr "Kaland varázslatok mutatása" msgid "View Combat Spells" msgstr "Csata varázslatok mutatása" msgid "View previous page" msgstr "ElÅ‘zÅ‘ oldal mutatása" msgid "View next page" msgstr "KövetkezÅ‘ oldal mutatása" msgid "Close Spellbook" msgstr "Varázskönyv becsukása" msgid "View %{spell}" msgstr "%{spell} mutatása" msgid "This spell does %{damage} points of damage." msgstr "Ez a varázslat %{damage} sebzést okoz." msgid "" "This spell summons\n" "%{count} %{monster}." msgstr "" "Megidézett lények:\n" "%{count} %{monster}." msgid "This spell restores %{hp} HP." msgstr "A varázslat visszaállít %{hp} életerÅ‘t." msgid "This spell summons %{count} %{monster} to guard the mine." msgstr "Ez a varázslat %{count} %{monster} Å‘rzÅ‘t idéz meg a bánya védelmére." msgid "The nearest town is %{town}." msgstr "A legközelebbi város: %{town}" msgid "This town is occupied by your hero %{hero}." msgstr "Ezt a várost %{hero} hÅ‘söd foglalja el." msgid "" "This spell controls up to\n" "%{hp} HP." msgstr "" "A varázslat %{hp} vagy ez alatti életerÅ‘vel\n" "rendelkezÅ‘ csapatokra van hatással." msgid "The ultimate artifact is really the %{name}." msgstr "A legszentebb varázstárgy kétség kívül a(z) %{name}." msgid "The ultimate artifact may be found in the %{name} regions of the world." msgstr "A legszentebb varázstárgyat a %{name} régióban találhatod." msgid "north-west" msgstr "észak-nyugati" msgid "north" msgstr "északi" msgid "north-east" msgstr "észak-keleti" msgid "west" msgstr "nyugati" msgid "center" msgstr "középsÅ‘" msgid "east" msgstr "keleti" msgid "south-west" msgstr "dél-nyugati" msgid "south" msgstr "déli" msgid "south-east" msgstr "dél-keleti" msgid "The truth is out there." msgstr "Az igazság odaát van." msgid "The dark side is stronger." msgstr "A sötét oldal erÅ‘sebb." msgid "The end of the world is near." msgstr "Közel a világvége." msgid "The bones of Lord Slayer are buried in the foundation of the arena." msgstr "Lord Slayer csontjai az aréna talapzatába vannak eltemetve." msgid "A Black Dragon will take out a Titan any day of the week." msgstr "A fekete sárkány titánt eszik reggelire." msgid "He told her: Yada yada yada... and then she said: Blah, blah, blah..." msgstr "A férfi üres fecsegésére a nÅ‘ így válaszolt: bla-bla-bla..." msgid "An unknown force is being resurrected..." msgstr "Egy ismeretlen erÅ‘ kelt új életre ..." msgid "" "Check the newest version of the game at\n" "https://github.com/ihhub/\n" "fheroes2/releases" msgstr "" "Letöltheted a legújabb verziót innen:\n" "https://github.com/ihhub/\n" "fheroes2/releases" #~ msgid "Terrain" #~ msgstr "Talaj" #~ msgid "Castles" #~ msgstr "Várak" #~ msgid "Ocean object" #~ msgstr "Tengeri objektum" #~ msgid "Ocean Objects" #~ msgstr "Tengeri tárgyak" #~ msgid "Grass Objects" #~ msgstr "Füves tárgyak" #~ msgid "Snow Objects" #~ msgstr "Havas tárgyak" #~ msgid "Swamp Objects" #~ msgstr "Mocsári tárgyak" #~ msgid "Lava Objects" #~ msgstr "Láva tárgyak" #~ msgid "Desert Objects" #~ msgstr "Sivatagi tárgyak" #~ msgid "Dirt Objects" #~ msgstr "Talaj tárgyak" #~ msgid "Beach Objects" #~ msgstr "Tengerparti tárgyak" #~ msgid "Used to place objects most appropriate for use on %{terrain}." #~ msgstr "Tárgyak elhelyezésére használatos, leginkább %{terrain} esetén." #~ msgid "" #~ "Used to place\n" #~ "a town or castle." #~ msgstr "" #~ "Város vagy vár\n" #~ "elhelyezésére való." #~ msgid "Used to place an artifact." #~ msgstr "Varázstárgy elhelyezésére való." #~ msgid "" #~ "Used to place\n" #~ "a resource or treasure." #~ msgstr "" #~ "ErÅ‘forrás vagy kincs\n" #~ "elhelyezésére való." #~ msgid "%{race1} %{name1}" #~ msgstr "%{race1} %{name1}" #~ msgid "vs" #~ msgstr "vs" #~ msgid "%{race2} %{name2}" #~ msgstr "%{race2} %{name2}" fheroes2-1.0.12+dfsg/files/lang/it.po000066400000000000000000006566011456075706000172560ustar00rootroot00000000000000# Italian translation of fheroes2 # Copyright (C) 2021 - 2022 fheroes2 team # This file is distributed under the same license as the fheroes2 package. # msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-03 03:27+0000\n" "PO-Revision-Date: 2022-12-03 04:36+0100\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Launchpad-Export-Date: 2021-10-08 00:00+0800\n" "X-Generator: Poedit 3.2\n" msgid "" "BATTLE\n" "ONLY" msgstr "" msgid "" "NEW\n" "GAME" msgstr "" msgid "" "SAVE\n" "GAME" msgstr "" msgid "" "LOAD\n" "GAME" msgstr "" msgid "INFO" msgstr "" msgid "QUIT" msgstr "" msgid "CANCEL" msgstr "" msgid "OKAY" msgstr "" msgid "smallerButton|OKAY" msgstr "" msgid "ACCEPT" msgstr "" msgid "DECLINE" msgstr "" msgid "LEARN" msgstr "" msgid "TRADE" msgstr "" msgid "YES" msgstr "" msgid "NO" msgstr "" msgid "EXIT" msgstr "" msgid "smallerButton|EXIT" msgstr "" msgid "DISMISS" msgstr "" msgid "UPGRADE" msgstr "" msgid "RESTART" msgstr "" msgid "HEROES" msgstr "" msgid "" "TOWNS/\n" "CASTLES" msgstr "" msgid "S" msgstr "" msgid "M" msgstr "" msgid "L" msgstr "" msgid "X-L" msgstr "" msgid "ALL" msgstr "" msgid "SELECT" msgstr "" msgid "" "STANDARD\n" "GAME" msgstr "" msgid "" "CAMPAIGN\n" "GAME" msgstr "" msgid "" "MULTI-\n" "PLAYER\n" "GAME" msgstr "" msgid "CONFIG" msgstr "" msgid "" "ORIGINAL\n" "CAMPAIGN" msgstr "" msgid "" "EXPANSION\n" "CAMPAIGN" msgstr "" msgid "HOT SEAT" msgstr "" msgid "2 PLAYERS" msgstr "" msgid "3 PLAYERS" msgstr "" msgid "4 PLAYERS" msgstr "" msgid "5 PLAYERS" msgstr "" msgid "6 PLAYERS" msgstr "" msgid "GIFT" msgstr "" msgid "MAX" msgstr "MAX" msgid "DIFFICULTY" msgstr "" msgid "VIEW INTRO" msgstr "" msgid "MIN" msgstr "" msgid "RESET" msgstr "" msgid "START" msgstr "" msgid "CASTLE" msgstr "" msgid "TOWN" msgstr "" msgid "" "D\n" "I\n" "S\n" "M\n" "I\n" "S\n" "S" msgstr "" msgid "" "E\n" "X\n" "I\n" "T" msgstr "" msgid "" "C\n" "A\n" "M\n" "P\n" "A\n" "I\n" "G\n" "N" msgstr "" msgid "" "S\n" "T\n" "A\n" "N\n" "D\n" "A\n" "R\n" "D" msgstr "" msgid "Warrior" msgstr "Lottare" msgid "Builder" msgstr "Costruire" msgid "Explorer" msgstr "Esplorare" msgid "None" msgstr "Nessun/a" msgid "" "A few\n" "%{monster}" msgstr "Pochi/e" msgid "" "Several\n" "%{monster}" msgstr "" "Alcuni/e\n" "%{monster}" msgid "" "A pack of\n" "%{monster}" msgstr "" "Banda di\n" "%{monster}" msgid "" "Lots of\n" "%{monster}" msgstr "" "Molti/e\n" "%{monster}" msgid "" "A horde of\n" "%{monster}" msgstr "" "Orda di\n" "%{monster}" msgid "" "A throng of\n" "%{monster}" msgstr "" "Una folla di\n" "%{monster}" msgid "" "A swarm of\n" "%{monster}" msgstr "" "Un plotone di\n" "%{monster}" msgid "" "Zounds...\n" "%{monster}" msgstr "" "Un esercito di\n" "%{monster}" msgid "" "A legion of\n" "%{monster}" msgstr "" "Una legione di\n" "%{monster}" msgid "army|Few" msgstr "Pochi/e" msgid "army|Several" msgstr "Alcuni/e" msgid "army|Pack" msgstr "Banda" msgid "army|Lots" msgstr "Molti/e" msgid "army|Horde" msgstr "Orda" msgid "army|Throng" msgstr "Folla" msgid "army|Swarm" msgstr "Plotone" msgid "army|Zounds" msgstr "Esercito" msgid "army|Legion" msgstr "Legione" msgid "All %{race} troops +1" msgstr "Tutte le forze da %{race}: +1" msgid "Multiple" msgstr "Multipli" msgid "Troops of %{count} alignments -%{penalty}" msgstr "Truppe da 3 fazioni -1" #, fuzzy msgid "Some undead in army -1" msgstr "Dei non-morti nell'esercito -1" msgid "View %{name}" msgstr "Mostra %{name}" #, fuzzy msgid "Move the %{name} " msgstr "Sposta %{name}" msgid "Move or right click to redistribute %{name}" msgstr "Muovi o schiaccia il tasto destro per riposizionare %{name}." msgid "Combine %{name} armies" msgstr "Unisci le truppe di %{name}" msgid "Exchange %{name2} with %{name}" msgstr "Scambia %{name2} con %{name}" msgid "Select %{name}" msgstr "Scegli %{name}" msgid "Cannot move last troop" msgstr "Impossibile spostare l'ultima unità" #, fuzzy msgid "Move the %{name}" msgstr "Sposta %{name}" msgid "Set Count" msgstr "Seleziona quanti" #, fuzzy msgid "%{name} destroys half the enemy troops!" msgid_plural "%{name} destroy half the enemy troops!" msgstr[0] "Le unità nemiche sono state dimezzate da %{name}!" msgstr[1] "Le unità nemiche sono state dimezzate da %{name}!" #, fuzzy msgid "%{name} has turned off the auto battle" msgstr "Colpisce tutti i non-morti nella battaglia." #, fuzzy msgid "%{name} has turned on the auto battle" msgstr "Colpisce tutti i non-morti nella battaglia." #, fuzzy msgid "Spell failed!" msgstr "magia fallita!" msgid "" "The Sphere of Negation artifact is in effect for this battle, disabling all " "combat spells." msgstr "" msgid "You have already cast a spell this round." msgstr "Hai già gettato una magia questo round." #, fuzzy msgid "That spell will have no effect!" msgstr "Questa magia non influirà nessuno!" msgid "You may only summon one type of elemental per combat." msgstr "" msgid "" "There is no open space adjacent to your hero where you can summon an " "Elemental to." msgstr "" msgid "" "The Moat reduces by -%{count} the defense skill of any unit and slows to " "half movement rate." msgstr "" msgid "Speed: %{speed}" msgstr "Velocità: %{speed}" msgid "Speed" msgstr "Velocità" msgid "Off" msgstr "No" msgid "On" msgstr "Sì" msgid "Turn Order" msgstr "" msgid "Auto Spell Casting" msgstr "" "Lancio Magie\n" "Automatico" msgid "Grid" msgstr "Griglia" msgid "Shadow Movement" msgstr "Movimento Ombra" msgid "Shadow Cursor" msgstr "Cursore Ombra" msgid "Audio" msgstr "" msgid "Settings" msgstr "Impostazioni" msgid "Configure" msgstr "" msgid "Hot Keys" msgstr "" #, fuzzy msgid "Damage Info" msgstr "Danni" msgid "Set the speed of combat actions and animations." msgstr "" msgid "Toggle to display the turn order during the battle." msgstr "" msgid "" "Toggle whether or not the computer will cast spells for you when auto combat " "is on. (Note: This does not affect spell casting for computer players in any " "way, nor does it affect quick combat.)" msgstr "" msgid "" "Toggle the hex grid on or off. The hex grid always underlies movement, even " "if turned off. This switch only determines if the grid is visible." msgstr "" msgid "" "Toggle on or off shadows showing where your creatures can move and attack." msgstr "" msgid "" "Toggle on or off a shadow showing the current hex location of the mouse " "cursor." msgstr "" msgid "Change the audio settings of the game." msgstr "Cambia le impostazioni dell'audio del gioco." msgid "Check and configure all the hot keys present in the game." msgstr "" msgid "Toggle to display damage information during the battle." msgstr "" msgid "Exit this menu." msgstr "Lascia questo menù." msgid "Okay" msgstr "Okay" msgid "The enemy has surrendered!" msgstr "Il nemico si è ceduto!" msgid "Their cowardice costs them %{gold} gold." msgstr "" msgid "The enemy has fled!" msgstr "L'avversario è scappato!" msgid "A glorious victory!" msgstr "Una gloriosa vittoria!" msgid "For valor in combat, %{name} receives %{exp} experience." msgstr "Per valore in combattimento %{name} riceve %{exp} punti d'esperienza." msgid "The cowardly %{name} flees from battle." msgstr "Il vigliacco %{name} si scappa dalla battaglia." msgid "%{name} surrenders to the enemy, and departs in shame." msgstr "" #, fuzzy msgid "Your forces suffer a bitter defeat, and %{name} abandons your cause." msgstr "" "Il tuo esercito subisce una sconfitta amara, e %{name} abbandona la tua " "causa." #, fuzzy msgid "Your forces suffer a bitter defeat." msgstr "Il tuo esercito subisce una sconfitta amara." msgid "Battlefield Casualties" msgstr "Vittime in Battaglia" msgid "Attacker" msgstr "Attaccante" msgid "Defender" msgstr "Difensore" #, fuzzy msgid "Click to leave the battle results." msgstr "Clicca per salvare la partita in corso." #, fuzzy msgid "Click to restart the battle in manual mode." msgstr "Sei sicuro di voler congedare queste forze?" msgid "Restart" msgstr "" msgid "You have captured an enemy artifact!" msgstr "Hai ottenuto un artefatto nemico!" msgid "As you reach for the %{name}, it mysteriously disappears." msgstr "" msgid "As your enemy reaches for the %{name}, it mysteriously disappears." msgstr "" msgid "Necromancy!" msgstr "Negromanzia!" msgid "" "Practicing the dark arts of necromancy, you are able to raise %{count} of " "the enemy's dead to return under your service as %{monster}." msgstr "" msgid "%{name} the %{race}" msgstr "%{name}: %{race}" #, fuzzy msgid "Captain of %{name}" msgstr "Mancano delle risorse per %{name}." msgid "Attack" msgstr "Attacco" msgid "Defense" msgstr "Difesa" msgid "Spell Power" msgstr "Potere" msgid "Knowledge" msgstr "Sapere" msgid "Morale" msgstr "Morale" msgid "Luck" msgstr "Fortuna" msgid "Spell Points" msgstr "Pt. Magia" msgid "Hero's Options" msgstr "Opzioni Eroe" msgid "Cast Spell" msgstr "Lancia magia" msgid "Retreat" msgstr "Ritirata" msgid "Surrender" msgstr "Resa" msgid "Cancel" msgstr "Annulla" msgid "Hero Screen" msgstr "Schermata Eroe" #, fuzzy msgid "Captain's Options" msgstr "Opzioni Castello" msgid "" "Cast a magical spell. You may only cast one spell per combat round. The " "round is reset when every creature has had a turn." msgstr "" msgid "" "Retreat your hero, abandoning your creatures. Your hero will be available " "for you to recruit again, however, the hero will have only a novice hero's " "forces." msgstr "" msgid "" "Surrendering costs gold. However if you pay the ransom, the hero and all of " "his or her surviving creatures will be available to recruit again. The cost " "of surrender is half of the total cost of the non-temporary troops remaining " "in the army." msgstr "" msgid "Open Hero Screen to view full information about the hero." msgstr "" msgid "Return to the battle." msgstr "Torna al combattimento." msgid "Not enough gold (%{gold})" msgstr "Manca dell'oro (%{gold})" msgid "%{name} states:" msgstr "%{name} dichiara:" #, fuzzy msgid "Captain of %{name} states:" msgstr "Mancano delle risorse per %{name}." msgid "" "\"I will accept your surrender and grant you and your troops safe passage " "for the price of %{price} gold.\"" msgstr "" "\"Ti permetterò di arrenderti e ti lascierò passare con le tue truppe per " "%{price} Oro.\"" msgid "View %{monster} info" msgstr "Mostra dettagli su %{monster}" msgid "Fly %{monster} here" msgstr "Fai volare %{monster} qua" msgid "Move %{monster} here" msgstr "Sposta %{monster} qua" msgid "Shoot %{monster}" msgstr "Tira a %{monster}" msgid "(1 shot left)" msgid_plural "(%{count} shots left)" msgstr[0] "(rimasto 1 colpo)" msgstr[1] "(rimasti %{count} colpi)" msgid "Attack %{monster}" msgstr "Attacca %{monster}" msgid "Turn %{turn}" msgstr "Round %{turn}" msgid "Teleport here" msgstr "Teletrasporta qui" msgid "Invalid teleport destination" msgstr "" msgid "Cast %{spell} on %{monster}" msgstr "Getta %{spell} su %{monster}" msgid "Cast %{spell}" msgstr "Getta %{spell}" msgid "Select spell target" msgstr "Seleziona il bersaglio della magia" msgid "View Ballista info" msgstr "Mostra dettagli sulla Ballista" msgid "Ballista" msgstr "Ballista" msgid "Enable auto combat" msgstr "" msgid "Allows the computer to fight out the battle for you." msgstr "Il computer fa le mossa al posto tuo." msgid "Auto Combat" msgstr "Combattimento Automatico" msgid "Customize system options" msgstr "Modifica le opzioni di sistema" msgid "Allows you to customize the combat screen." msgstr "Permette di modificare la schermata del combattimento." msgid "System Options" msgstr "Opzioni Sistema" msgid "Skip this unit" msgstr "Salta questa unità" msgid "Skip" msgstr "Salta" msgid "" "Skips the current creature. The current creature ends its turn and does not " "get to go again until the next round." msgstr "" "Salta l'unità attuale. L'unità salta il suo turno senza fare nulla e deve " "aspettare per il prossimo round." #, fuzzy msgid "View Captain's options" msgstr "Mostra opzioni eroe" msgid "View Hero's options" msgstr "Mostra opzioni eroe" #, fuzzy msgid "View opposing Captain" msgstr "Mostra eroe avversario" msgid "View opposing Hero" msgstr "Mostra eroe avversario" msgid "Hide logs" msgstr "Nascondi registro" msgid "Show logs" msgstr "Mostra registro" msgid "Message Bar" msgstr "" msgid "Shows the results of individual monster's actions." msgstr "" #, fuzzy msgid "Are you sure you want to finish the battle in auto mode?" msgstr "Sei sicuro di voler congedare queste forze?" #, fuzzy msgid "Are you sure you want to enable auto combat?" msgstr "Sei sicuro di voler fuggire?" msgid "%{name} skip their turn." msgstr "%{name} salta il suo turno." #, fuzzy msgid "%{attacker} does %{damage} damage." msgid_plural "%{attacker} do %{damage} damage." msgstr[0] "%{attacker} fanno %{damage} danni." msgstr[1] "%{attacker} fanno %{damage} danni." msgid "1 creature perishes." msgid_plural "%{count} creatures perish." msgstr[0] "1 creatura perisce." msgstr[1] "%{count} creature periscono." msgid "1 %{defender} perishes." msgid_plural "%{count} %{defender} perish." msgstr[0] "1 %{defender} perisce." msgstr[1] "%{count} %{defender} periscono." #, fuzzy msgid "1 soul is incorporated." msgid_plural "%{count} souls are incorporated." msgstr[0] "Non esistono punti di salvataggio da caricare." msgstr[1] "Non esistono punti di salvataggio da caricare." msgid "1 %{unit} is revived." msgid_plural "%{count} %{unit} are revived." msgstr[0] "" msgstr[1] "" #, fuzzy msgid "Moved %{monster}: from [%{src}] to [%{dst}]." msgstr "Spostamento %{monster}: %{src}, %{dst}" msgid "The %{name} resist the spell!" msgstr "La magia non ha funzianto a %{name}!" msgid "%{name} casts %{spell} on the %{troop}." msgstr "%{name} getta %{spell} al gruppo di %{troop}." msgid "%{name} casts %{spell}." msgstr "%{name} getta %{spell}." msgid "The %{spell} does %{damage} damage to one undead creature." msgstr "" msgid "The %{spell} does %{damage} damage to all undead creatures." msgstr "" msgid "The %{spell} does %{damage} damage, %{count} creatures perish." msgstr "" msgid "The %{spell} does %{damage} damage." msgstr "" msgid "The %{spell} does %{damage} damage to one living creature." msgstr "" msgid "The %{spell} does %{damage} damage to all living creatures." msgstr "" msgid "The %{attacker}' attack blinds the %{target}!" msgstr "" msgid "The %{attacker}' gaze turns the %{target} to stone!" msgstr "" msgid "The %{attacker}' curse falls upon the %{target}!" msgstr "" msgid "The %{target} are paralyzed by the %{attacker}!" msgstr "" msgid "The %{attacker} dispel all good spells on your %{target}!" msgstr "" #, fuzzy msgid "The %{attacker} cast %{spell} on %{target}!" msgstr "%{name} getta %{spell} al gruppo di %{troop}." msgid "Bad luck descends on the %{attacker}." msgstr "" msgid "Good luck shines on the %{attacker}." msgstr "La fortuna sorride su %{attacker}." msgid "High morale enables the %{monster} to attack again." msgstr "Il morale alto permette a %{monster} di attaccare ancora." msgid "Low morale causes the %{monster} to freeze in panic." msgstr "Per via di morale basso viene saltato il turno di %{monster}." msgid "%{tower} does %{damage} damage." msgstr "%{tower} fa %{damage} punti di danno." msgid "The mirror image is created." msgstr "" msgid "The mirror image is destroyed!" msgstr "" #, fuzzy msgid "Are you sure you want to interrupt the auto battle?" msgstr "Sei sicuro di voler fuggire?" msgid "Error" msgstr "" msgid "No spells to cast." msgstr "Nessuna magia da lanciare." msgid "Are you sure you want to retreat?" msgstr "Sei sicuro di voler fuggire?" msgid "Retreat disabled" msgstr "" msgid "Surrender disabled" msgstr "" msgid "Damage: %{max}" msgstr "Danni: %{max}" msgid "Damage: %{min} - %{max}" msgstr "Danni: %{min} - %{max}" msgid "Perish: %{max}" msgstr "Periti: %{max}" msgid "Perish: %{min} - %{max}" msgstr "Periti: %{min} - %{max}" msgid "" "Through eagle-eyed observation, %{name} is able to learn the magic spell " "%{spell}." msgstr "" msgid "Human" msgstr "Umano" msgid "AI" msgstr "Computer" msgid "Start" msgstr "" #, fuzzy msgid "Start the battle." msgstr "Torna al combattimento." msgid "Exit" msgstr "Esci" msgid "Reset" msgstr "" msgid "Reset to default settings." msgstr "" msgid "Please select another hero." msgstr "Per favore, scegli un altro eroe." msgid "Set Attack Skill" msgstr "Scegli Attacco" msgid "Set Defense Skill" msgstr "Scegli Difesa" msgid "Set Power Skill" msgstr "Scegli Potere" msgid "Set Knowledge Skill" msgstr "Scegli Sapere" #, fuzzy msgid "%{race1} %{name1} vs %{race2} %{name2}" msgstr "%{name1} incontra %{name2}" msgid "Monsters" msgstr "Mostri" msgid "N/A" msgstr "N/D" msgid "Left Turret" msgstr "Torrione Sinistro" msgid "Right Turret" msgstr "Torrione Destro" msgid "The %{name} fires with the strength of %{count} Archers" msgstr "" msgid "each with a +%{attack} bonus to their attack skill." msgstr "" #, fuzzy msgid "The %{name} is destroyed." msgstr "%{name} incrementa il morale delle tue truppe." msgid "%{count} %{name} rises from the dead!" msgid_plural "%{count} %{name} rise from the dead!" msgstr[0] "" msgstr[1] "" msgid "Dwarven Alliance" msgstr "" msgid "Sorceress Guild" msgstr "" msgid "Necromancer Guild" msgstr "" msgid "Ogre Alliance" msgstr "" #, fuzzy msgid "Dwarfbane" msgstr "Nano" msgid "Dragon Alliance" msgstr "" msgid "Elven Alliance" msgstr "" msgid "Kraeger defeated" msgstr "" msgid "Wayward Son" msgstr "" msgid "Uncle Ivan" msgstr "" msgid "Annexation" msgstr "" msgid "Force of Arms" msgstr "" msgid "Save the Dwarves" msgstr "" msgid "Carator Mines" msgstr "" msgid "Turning Point" msgstr "" msgid "scenarioName|Defender" msgstr "" msgid "Corlagon's Defense" msgstr "" msgid "The Crown" msgstr "" msgid "The Gauntlet" msgstr "" msgid "Betrayal" msgstr "" msgid "Final Justice" msgstr "" msgid "" "Roland needs you to defeat the lords near his castle to begin his war of " "rebellion against his brother. They are not allied with each other, so they " "will spend most of their time fighting with one another. Victory is yours " "when you have defeated all of their castles and heroes." msgstr "" msgid "" "The local lords refuse to swear allegiance to Roland, and must be subdued. " "They are wealthy and powerful, so be prepared for a tough fight. Capture all " "enemy castles to win." msgstr "" msgid "" "Your task is to defend the Dwarves against Archibald's forces. Capture all " "of the enemy towns and castles to win, and be sure not to lose all of the " "dwarf towns at once, or the enemy will have won." msgstr "" msgid "" "You will face four allied enemies in a straightforward fight for resource " "and treasure. Capture all of the enemy castles for victory." msgstr "" msgid "" "Your enemies are allied against you and start close by, so be ready to come " "out fighting. You will need to own all four castles in this small valley to " "win." msgstr "" msgid "" "The Sorceress' guild of Noraston has requested Roland's aid against an " "attack from Archibald's allies. Capture all of the enemy castles to win, and " "don't lose Noraston, or you'll lose the scenario. (Hint: There is an enemy " "castle on an island in the ocean.)" msgstr "" msgid "" "Gather as large an army as possible and capture the enemy castle within 8 " "weeks. You are opposed by only one enemy, but must travel a long way to get " "to the enemy castle. Any troops you have in your army at the end of this " "scenario will be with you in the final battle." msgstr "" msgid "" "Find the Crown before Archibald's heroes find it. Roland will need the Crown " "for the final battle against Archibald." msgstr "" msgid "" "Three allied enemies stand before you and victory, including Lord Corlagon. " "Roland is in a castle to the northwest, and you will lose if he falls to the " "enemy. Remember that capturing Lord Corlagon will ensure that he will not " "fight against you in the final scenario." msgstr "" msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Archibald to end the war!" msgstr "" msgid "" "Switching sides leaves you with three castles against the enemy's one. This " "battle will be the easiest one you will face for the rest of the war..." "traitor." msgstr "" msgid "Barbarian Wars" msgstr "" msgid "First Blood" msgstr "" msgid "Necromancers" msgstr "" msgid "Slay the Dwarves" msgstr "" msgid "Country Lords" msgstr "" msgid "Dragon Master" msgstr "" msgid "Rebellion" msgstr "" msgid "Apocalypse" msgstr "" msgid "Greater Glory" msgstr "" msgid "" "King Archibald requires you to defeat the three enemies in this region. They " "are not allied with one another, so they will spend most of their energy " "fighting amongst themselves. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" msgid "" "You must unify the barbarian tribes of the north by conquering them. As in " "the previous mission, the enemy is not allied against you, but they have " "more resources at their disposal. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" msgid "" "Do-gooder wizards have taken the Necromancers' castle. You must retake it to " "achieve victory. Remember that while you start with a powerful army, you " "have no castle and must take one within 7 days, or lose this battle. (Hint: " "The nearest castle is to the southeast.)" msgstr "" msgid "" "The dwarves need conquering before they can interfere in King Archibald's " "plans. Roland's forces have more than one hero and many towns to start with, " "so be ready for attack from multiple directions. You must capture all of the " "enemy towns and castles to claim victory." msgstr "" msgid "" "You must put down a peasant revolt led by Roland's forces. All are allied " "against you, but you have Lord Corlagon, an experienced hero, to help you. " "Capture all enemy castles to win." msgstr "" msgid "" "There are two enemies allied against you in this mission. Both are well " "armed and seek to evict you from their island. Avoid them and capture Dragon " "City to win." msgstr "" msgid "" "Your orders are to conquer the country lords that have sworn to serve " "Roland. All of the enemy castles are unified against you. Since you start " "without a castle, you must hurry to capture one before the end of the week. " "Capture all enemy castles for victory." msgstr "" msgid "" "Find the Crown before Roland's heroes find it. Archibald will need the Crown " "for the final battle against Roland." msgstr "" msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Roland to win the war, and be sure " "not to lose Archibald in the fight!" msgstr "" msgid "Arrow's Flight" msgstr "" msgid "Island of Chaos" msgstr "" msgid "The Abyss" msgstr "" msgid "Uprising" msgstr "" msgid "Aurora Borealis" msgstr "" msgid "Betrayal's End" msgstr "" msgid "Corruption's Heart" msgstr "" msgid "The Giant's Pass" msgstr "" msgid "" "Subdue the unruly local lords in order to provide the Empire with facilities " "to operate in this region." msgstr "" msgid "" "Eliminate all opposition in this area. Then the first piece of the artifact " "will be yours." msgstr "" msgid "" "The sorceresses to the northeast are rebelling! For the good of the empire " "you must quash their feeble uprising on your way to the mountains." msgstr "" msgid "" "Having prepared for your arrival, Kraeger has arranged for a force of " "necromancers to thwart your quest. You must capture the castle of Scabsdale " "before the first day of the third week, or the Necromancers will be too " "strong for you." msgstr "" msgid "" "The barbarian despot in this area is, as yet, ignorant of your presence. " "Quickly, build up your forces before you are discovered and attacked! Secure " "the region by subduing all enemy forces." msgstr "" msgid "" "The Empire is weak in this region. You will be unable to completely subdue " "all forces in this area, so take what you can before reprisal strikes. " "Remember, your true goal is to claim the Helmet of Anduran." msgstr "" msgid "For the good of the Empire, eliminate Kraeger." msgstr "" msgid "" "At last, you have the opportunity and the facilities to rid the Empire of " "the necromancer's evil. Eradicate them completely, and you will be sung as a " "hero for all time." msgstr "" msgid "Border Towns" msgstr "" msgid "Conquer and Unify" msgstr "" msgid "Crazy Uncle Ivan" msgstr "" msgid "The Wayward Son" msgstr "" msgid "Ivory Gates" msgstr "" msgid "The Elven Lands" msgstr "" msgid "The Epic Battle" msgstr "" msgid "The Southern War" msgstr "" msgid "" "Conquer and unite all the enemy tribes. Don't lose the hero Jarkonas, the " "forefather of all descendants." msgstr "" msgid "" "Your rival, the Kingdom of Harondale, is attacking weak towns on your " "border! Recover from their first strike and crush them completely!" msgstr "" msgid "" "Find your wayward son Joseph who is rumored to be living in the desolate " "lands. Do it before the first day of the third month or it will be of no " "help to your family." msgstr "" msgid "" "Rescue your crazy uncle Ivan. Find him before the first day of the fourth " "month or it will be no help to your kingdom." msgstr "" msgid "" "Destroy the barbarians who are attacking the southern border of your " "kingdom! Recover your fallen towns, and then invade the jungle kingdom. " "Leave no enemy standing." msgstr "" msgid "Retake the castle of Ivory Gates, which has fallen due to treachery." msgstr "" msgid "" "Gain the favor of the elves. They will not allow trees to be chopped down, " "so they will send you wood every 2 weeks. You must complete your mission " "before the first day of the seventh month, or the kingdom will surely fall." msgstr "" msgid "" "This is the final battle against your rival kingdom of Harondale. Eliminate " "everyone, and don't lose the hero Jarkonas VI." msgstr "" msgid "Fount of Wizardry" msgstr "" msgid "Power's End" msgstr "" msgid "The Eternal Scrolls" msgstr "" msgid "The Shrouded Isles" msgstr "" msgid "" "Your mission is to vanquish the warring mages in the magical Shrouded Isles. " "The completion of this task will give you a fighting chance against your " "rivals." msgstr "" msgid "" "The location of the great library has been discovered! You must make your " "way to it, and reclaim the city of Chronos in which it lies." msgstr "" msgid "" "Find the Orb of negation, which is said to be buried in this land. There are " "clues inscribed on stone obelisks which will help lead you to your prize. " "Find the Orb before the first day of the sixth month, or your rivals will " "surely have gotten to the fount before you." msgstr "" msgid "" "You must take control of the castle of Magic, where the fount of wizardry " "lies. Do this and your victory will be supreme." msgstr "" msgid "Blood is Thicker" msgstr "" msgid "King and Country" msgstr "" msgid "Pirate Isles" msgstr "" msgid "Stranded" msgstr "" msgid "" "Capture the town on the island off the southeast shore in order to construct " "a boat and travel back towards the mainland. Do not lose the hero Gallavant." msgstr "" msgid "" "Find and defeat Martine, the pirate leader, who resides in Pirates Cove. Do " "not lose Gallavant or your quest will be over." msgstr "" msgid "" "Eliminate all the other forces who oppose the rule of Lord Alberon. " "Gallavant must not die." msgstr "" msgid "" "Overthrow the entrenched monarchy of Lord Alberon, and claim all the land in " "your name. Gallavant must not die." msgstr "" msgid " bane" msgstr "" msgid " alliance" msgstr "" msgid "Carry-over forces" msgstr "" msgid " bonus" msgstr "" msgid " defeated" msgstr "" msgid " will always run away from your army." msgstr "" msgid " will be willing to join your army." msgstr "" msgid "\"%{artifact}\" artifact will be carried over the scenario." msgstr "" msgid "The army will be carried over the scenario." msgstr "" msgid "The kingdom will have +%{count} %{resource} each day." msgstr "" msgid "\"%{spell}\" spell will be carried over the scenario." msgstr "" msgid "%{hero} can be hired in the scenario." msgstr "" msgid "" "%{hero} has been defeated and will not appear in the subsequent scenarios." msgstr "" msgid "The dwarves recognize their allies and gladly join your forces." msgstr "" msgid "The ogres recognize you as the Dwarfbane and lumber over to join you." msgstr "" msgid "" "The dragons, snarling and growling, agree to join forces with you, their " "'Ally'." msgstr "" msgid "" "As you approach the group of elves, their leader calls them all to " "attention. He shouts to them, \"Who of you is brave enough to join this " "fearless ally of ours?\" The group explodes with cheers as they run to join " "your ranks." msgstr "" msgid "" "The dwarves hail you, \"Any friend of Roland is a friend of ours. You may " "pass.\"" msgstr "" msgid "" "The ogres give you a grunt of recognition, \"Archibald's allies may pass.\"" msgstr "" msgid "" "The dragons see you and call out. \"Our alliance with Archibald compels us " "to join you. Unfortunately you have no room. A pity!\" They quickly scatter." msgstr "" msgid "" "The elves stand at attention as you approach. Their leader calls to you and " "says, \"Let us not impede your progress, ally! Move on, and may victory be " "yours.\"" msgstr "" msgid "\"The Dwarfbane!!!!, run for your lives.\"" msgstr "" #, fuzzy msgid "campaignBonus|Animate Dead" msgstr "Anima i Morti" #, fuzzy msgid "campaignBonus|Chain Lightning" msgstr "Fulmini in Serie" msgid "campaignBonus|Fireblast" msgstr "" msgid "campaignBonus|Mass Curse" msgstr "" msgid "campaignBonus|Mass Haste" msgstr "" #, fuzzy msgid "campaignBonus|Mirror Image" msgstr "Rispecchia" msgid "campaignBonus|Resurrect" msgstr "" #, fuzzy msgid "campaignBonus|Steelskin" msgstr "La Perla Bianca" msgid "campaignBonus|Summon Earth" msgstr "" #, fuzzy msgid "campaignBonus|View Heroes" msgstr "Mostra Eroi" msgid "campaignBonus|Ballista" msgstr "" #, fuzzy msgid "campaignBonus|Black Pearl" msgstr "La Perla Nera" msgid "campaignBonus|Caster's Bracelet" msgstr "" msgid "campaignBonus|Defender Helm" msgstr "" msgid "campaignBonus|Breastplate" msgstr "" msgid "campaignBonus|Dragon Sword" msgstr "" msgid "campaignBonus|Fizbin Medal" msgstr "" msgid "campaignBonus|Foremost Scroll" msgstr "" #, fuzzy msgid "campaignBonus|Gauntlets" msgstr "La Perla Bianca" msgid "campaignBonus|Hideous Mask" msgstr "" msgid "campaignBonus|Mage's Ring" msgstr "" msgid "campaignBonus|Major Scroll" msgstr "" msgid "campaignBonus|Medal of Honor" msgstr "" #, fuzzy msgid "campaignBonus|Medal of Valor" msgstr "La Medaglia al Valore" msgid "campaignBonus|Minor Scroll" msgstr "" msgid "campaignBonus|Nomad Boots" msgstr "" msgid "campaignBonus|Power Axe" msgstr "" msgid "campaignBonus|Spiked Shield" msgstr "" #, fuzzy msgid "campaignBonus|Stealth Shield" msgstr "Scudo Segreto" msgid "campaignBonus|Tax Lien" msgstr "" msgid "campaignBonus|Thunder Mace" msgstr "" #, fuzzy msgid "campaignBonus|Traveler's Boots" msgstr "Scarpe del viaggiante" #, fuzzy msgid "campaignBonus|White Pearl" msgstr "La Perla Bianca" #, fuzzy msgid "campaignBonus|Basic Archery" msgstr "La Perla Nera" #, fuzzy msgid "campaignBonus|Advanced Archery" msgstr "Tiro con L'Arco Avanzato" #, fuzzy msgid "campaignBonus|Expert Archery" msgstr "La Perla Bianca" #, fuzzy msgid "campaignBonus|Basic Ballistics" msgstr "La Perla Nera" #, fuzzy msgid "campaignBonus|Advanced Ballistics" msgstr "Balistica Avanzata" #, fuzzy msgid "campaignBonus|Expert Ballistics" msgstr "Balistica Esperta" #, fuzzy msgid "campaignBonus|Basic Diplomacy" msgstr "La Perla Nera" #, fuzzy msgid "campaignBonus|Advanced Diplomacy" msgstr "Diplomazia Avanzata" #, fuzzy msgid "campaignBonus|Expert Diplomacy" msgstr "Diplomazia Esperta" #, fuzzy msgid "campaignBonus|Basic Eagle Eye" msgstr "La Perla Nera" #, fuzzy msgid "campaignBonus|Advanced Eagle Eye" msgstr "Vista Avanzata" #, fuzzy msgid "campaignBonus|Expert Eagle Eye" msgstr "Vista Esperta" #, fuzzy msgid "campaignBonus|Basic Estates" msgstr "La Perla Nera" #, fuzzy msgid "campaignBonus|Advanced Estates" msgstr "Investimenti Avanzati" #, fuzzy msgid "campaignBonus|Expert Estates" msgstr "Mostra Eroi" #, fuzzy msgid "campaignBonus|Basic Leadership" msgstr "La Perla Nera" #, fuzzy msgid "campaignBonus|Advanced Leadership" msgstr "Carisma Avanzato" #, fuzzy msgid "campaignBonus|Expert Leadership" msgstr "Carisma Esperto" #, fuzzy msgid "campaignBonus|Basic Logistics" msgstr "Fulmini in Serie" #, fuzzy msgid "campaignBonus|Advanced Logistics" msgstr "Logistica Avanzata" #, fuzzy msgid "campaignBonus|Expert Logistics" msgstr "Logistica Esperta" #, fuzzy msgid "campaignBonus|Basic Luck" msgstr "La Perla Nera" #, fuzzy msgid "campaignBonus|Advanced Luck" msgstr "Anima i Morti" #, fuzzy msgid "campaignBonus|Expert Luck" msgstr "La Perla Bianca" #, fuzzy msgid "campaignBonus|Basic Mysticism" msgstr "Misticismo Normale" #, fuzzy msgid "campaignBonus|Advanced Mysticism" msgstr "Misticismo Avanzato" #, fuzzy msgid "campaignBonus|Expert Mysticism" msgstr "Misticismo Esperto" #, fuzzy msgid "campaignBonus|Basic Navigation" msgstr "Fulmini in Serie" #, fuzzy msgid "campaignBonus|Advanced Navigation" msgstr "Navigazione Avanzata" #, fuzzy msgid "campaignBonus|Expert Navigation" msgstr "Navigazione Esperta" #, fuzzy msgid "campaignBonus|Basic Necromancy" msgstr "La Perla Nera" #, fuzzy msgid "campaignBonus|Advanced Necromancy" msgstr "Negromanzia Avanzata" #, fuzzy msgid "campaignBonus|Expert Necromancy" msgstr "Negromanzia Esperta" #, fuzzy msgid "campaignBonus|Basic Pathfinding" msgstr "Fulmini in Serie" #, fuzzy msgid "campaignBonus|Advanced Pathfinding" msgstr "Orientamento Avanzato" #, fuzzy msgid "campaignBonus|Expert Pathfinding" msgstr "Orientamento Esperto" #, fuzzy msgid "campaignBonus|Basic Scouting" msgstr "Fulmini in Serie" #, fuzzy msgid "campaignBonus|Advanced Scouting" msgstr "Esplorazione Avanzata" #, fuzzy msgid "campaignBonus|Expert Scouting" msgstr "La Perla Bianca" #, fuzzy msgid "campaignBonus|Basic Wisdom" msgstr "La Perla Nera" #, fuzzy msgid "campaignBonus|Advanced Wisdom" msgstr "Anima i Morti" #, fuzzy msgid "campaignBonus|Expert Wisdom" msgstr "Scudo Segreto" msgid "" "The main hero will have \"%{artifact}\" artifact at the start of the " "scenario." msgstr "" msgid "" "The kingdom will receive %{amount} additional %{resource} at the start of " "the scenario." msgstr "" msgid "" "The kingdom will have %{amount} less %{resource} at the start of the " "scenario." msgstr "" msgid "" "The main hero will have %{count} %{monster} at the start of the scenario." msgstr "" msgid "" "The main hero will have \"%{spell}\" spell at the start of the scenario." msgstr "" msgid "The starting race of the scenario will be %{race}." msgstr "" msgid "" "The main hero will have additional %{count} %{skill} at the start of the " "scenario." msgstr "" msgid "The main hero will have %{skill} at the start of the scenario." msgstr "" msgid "Roland" msgstr "Rolando" msgid "Archibald" msgstr "" msgid "Price of Loyalty" msgstr "" msgid "Voyage Home" msgstr "" #, fuzzy msgid "Wizard's Isle" msgstr "Mago" #, fuzzy msgid "Descendants" msgstr "Burini" msgid "The %{building} produces %{monster}." msgstr "Il/la %{building} produce %{monster}." msgid "Requires:" msgstr "Richiede:" msgid "Exit this menu without doing anything." msgstr "Esci dal menù senza far nulla." #, fuzzy msgid "Cannot build. You have already built here today." msgstr "Costruzione impossibile. Già costruito qua questo turno." msgid "For this action it is necessary to build a castle first." msgstr "Per fare questo bisogna prima costruire il castello." #, fuzzy msgid "Cannot build %{name} because the castle is too far away from an ocean." msgstr "" "Non si può costruire il %{name}. Troppo lontano dall'acqua per costruire il " "%{name}." msgid "disable build." msgstr "costruzione disabilitata." msgid "Cannot afford %{name}." msgstr "Mancano delle risorse per %{name}." msgid "%{name} is already built." msgstr "Questo castello ha già %{name}." msgid "Cannot build %{name}." msgstr "Impossibile costruire %{name}." msgid "Build %{name}." msgstr "Costruisci %{name}." #, fuzzy msgid "Blackridge" msgstr "Ponte" msgid "Hillstone" msgstr "" msgid "Pinehurst" msgstr "" #, fuzzy msgid "Whiteshield" msgstr "La Perla Bianca" #, fuzzy msgid "Woodhaven" msgstr "Legno" #, fuzzy msgid "Blackwind" msgstr "Fabbro" #, fuzzy msgid "Bloodreign" msgstr "Morale Sangue" #, fuzzy msgid "Dragontooth" msgstr "Drago" msgid "Greywind" msgstr "" msgid "Portsmith" msgstr "" msgid "Atlantium" msgstr "" msgid "Middle Gate" msgstr "" msgid "Sansobar" msgstr "" msgid "Tundara" msgstr "" #, fuzzy msgid "Vulcania" msgstr "Vulcano" msgid "Baywatch" msgstr "" #, fuzzy msgid "Fountainhead" msgstr "Fontana" msgid "Vertigo" msgstr "" msgid "Wildabar" msgstr "" msgid "Winterkill" msgstr "" msgid "Brindamoor" msgstr "" msgid "Lakeside" msgstr "" msgid "Nightshadow" msgstr "" msgid "Olympus" msgstr "" msgid "Sandcaster" msgstr "" msgid "Alamar" msgstr "" #, fuzzy msgid "Burlock" msgstr "Guerriero" #, fuzzy msgid "Dragadune" msgstr "Drago" msgid "Kalindra" msgstr "" msgid "Xabran" msgstr "" msgid "Algary" msgstr "" msgid "Basenji" msgstr "" #, fuzzy msgid "Blackfang" msgstr "Drago Nero" #, fuzzy msgid "New Dawn" msgstr "Nuova Partita" msgid "Sorpigal" msgstr "" msgid "Avone" msgstr "" msgid "Big Oak" msgstr "" msgid "Chandler" msgstr "" msgid "Erliquin" msgstr "" msgid "Hampshire" msgstr "" #, fuzzy msgid "Antioch" msgstr "Attenzione" msgid "Avalon" msgstr "" msgid "Roc Haven" msgstr "" msgid "South Mill" msgstr "" #, fuzzy msgid "Weed Patch" msgstr "L'Orologio d'Oro" #, fuzzy msgid "Brownston" msgstr "Marrone" #, fuzzy msgid "Hilltop" msgstr "Forte in Collina" msgid "Weddington" msgstr "" msgid "Westfork" msgstr "" msgid "Whittingham" msgstr "" msgid "Cathcart" msgstr "" msgid "Elk's Head" msgstr "" msgid "Roscomon" msgstr "" msgid "Sherman" msgstr "" msgid "Yorksford" msgstr "" #, fuzzy msgid "Blackburn" msgstr "Drago Nero" #, fuzzy msgid "Blacksford" msgstr "Torre Nera" msgid "Burton" msgstr "" msgid "Pig's Eye" msgstr "" msgid "Viper's Nest" msgstr "" msgid "Fenton" msgstr "" #, fuzzy msgid "Lankershire" msgstr "Carisma" msgid "Lombard" msgstr "" msgid "Timberhill" msgstr "" msgid "Troy" msgstr "" msgid "Forder Oaks" msgstr "" msgid "Meramec" msgstr "" msgid "Quick Silver" msgstr "" msgid "Westmoor" msgstr "" #, fuzzy msgid "Willow" msgstr "Giallo" msgid "Corackston" msgstr "" msgid "Sheltemburg" msgstr "" msgid "Cannot recruit - you already have a Hero in this town." msgstr "Reclutamento impossibile. Eroe già presente in questa città." msgid "Cannot recruit - you have too many Heroes." msgstr "Impossibile reclutare - sei al numero massimo di eroi contemporanei." msgid "Cannot afford a Hero" msgstr "Manca dell'oro per reclutare l'eroe" msgid "There is no room in the garrison for this army." msgstr "La guarnigione non ha posto libero per questa unità." msgid "Fortifications" msgstr "Fortificazioni" msgid "Farm" msgstr "Fattoria" msgid "Thatched Hut" msgstr "Capanna di Paglia" msgid "Archery Range" msgstr "Campo di Tiro" msgid "Upg. Archery Range" msgstr "Migl. Campo di Tiro" msgid "Blacksmith" msgstr "Fabbro" msgid "Upg. Blacksmith" msgstr "Migl. Fabbro" msgid "Armory" msgstr "Armeria" msgid "Upg. Armory" msgstr "Migl. Armeria" msgid "Jousting Arena" msgstr "Arena del Torneo" msgid "Upg. Jousting Arena" msgstr "Migl. Arena Torneo" msgid "Cathedral" msgstr "Cattedrale" msgid "Upg. Cathedral" msgstr "Migl. Cattedrale" msgid "Coliseum" msgstr "Colosseo" msgid "Garbage Heap" msgstr "Mucchio di Rifiuti" msgid "Hut" msgstr "Capanna" msgid "Stick Hut" msgstr "Capanna di Frasche" msgid "Upg. Stick Hut" msgstr "Migl. Cap. Frasche" msgid "Den" msgstr "Antro" msgid "Adobe" msgstr "Casa d'Adobe" msgid "Upg. Adobe" msgstr "Migl. Casa d'Adobe" msgid "Bridge" msgstr "Ponte" msgid "Upg. Bridge" msgstr "Migl. Ponte" msgid "Pyramid" msgstr "Piramide" msgid "Rainbow" msgstr "Arcobaleno" msgid "Crystal Garden" msgstr "Giardino di Cristallo" msgid "Treehouse" msgstr "Casa sugli Alberi" msgid "Cottage" msgstr "Casetta" msgid "Upg. Cottage" msgstr "Migl. Casetta" msgid "Stonehenge" msgstr "Dolmen" msgid "Upg. Stonehenge" msgstr "Migl. Dolmen" msgid "Fenced Meadow" msgstr "Prato Recintato" #, fuzzy msgid "sorceress|Red Tower" msgstr "Torre Rossa" msgid "Dungeon" msgstr "Sotterraneo" msgid "Waterfall" msgstr "Cascata" msgid "Cave" msgstr "Caverna" msgid "Crypt" msgstr "Cripta" msgid "Nest" msgstr "Nido" msgid "Maze" msgstr "Labirinto" msgid "Upg. Maze" msgstr "Migl. Labirinto" msgid "Swamp" msgstr "Palude" msgid "Green Tower" msgstr "Torre Verde" #, fuzzy msgid "warlock|Red Tower" msgstr "Torre Rossa" msgid "Black Tower" msgstr "Torre Nera" msgid "Library" msgstr "Biblioteca" msgid "Orchard" msgstr "Frutteto" msgid "Habitat" msgstr "Habitat" msgid "Pen" msgstr "Recinto" msgid "Foundry" msgstr "Fucina" msgid "Upg. Foundry" msgstr "Migl. Fucina" msgid "Cliff Nest" msgstr "Nido su Scogliera" msgid "Ivory Tower" msgstr "Torre d'Avorio" msgid "Upg. Ivory Tower" msgstr "Migl. Torre Avorio" msgid "Cloud Castle" msgstr "Castello Celeste" msgid "Upg. Cloud Castle" msgstr "Migl. Cast. Cielo" msgid "Storm" msgstr "Tempesta Perpetua" msgid "Skull Pile" msgstr "Pila di Teschi" msgid "Excavation" msgstr "Scavo" msgid "Graveyard" msgstr "Cimitero" msgid "Upg. Graveyard" msgstr "Migl. Cimitero" msgid "Upg. Pyramid" msgstr "Migl. Piramide" msgid "Mansion" msgstr "Magione" msgid "Upg. Mansion" msgstr "Migl. Magione" msgid "Mausoleum" msgstr "Mausoleo" msgid "Upg. Mausoleum" msgstr "Migl. Mausoleo" msgid "Laboratory" msgstr "Laboratorio" msgid "Shrine" msgstr "Altare Oscuro" msgid "" "The Fortifications increase the toughness of the walls, increasing the " "number of turns it takes to knock them down." msgstr "" msgid "The Farm increases production of Peasants by %{count} per week." msgstr "" msgid "" "The Coliseum provides inspiring spectacles to defending troops, raising " "their morale by two during combat." msgstr "" msgid "The Garbage Heap increases production of Goblins by %{count} per week." msgstr "" msgid "The Rainbow increases the luck of the defending units by two." msgstr "" msgid "" "The Crystal Garden increases production of Sprites by %{count} per week." msgstr "" #, fuzzy msgid "The Dungeon increases the income of the town by %{count} gold per day." msgstr "%{name} incrementa di %{count} punti il sapere del tuo eroe." msgid "The Waterfall increases production of Centaurs by %{count} per week." msgstr "" msgid "" "The Library increases the number of spells in the Guild by one for each " "level of the guild." msgstr "" msgid "The Orchard increases production of Halflings by %{count} per week." msgstr "" msgid "The Storm adds +2 to the power of spells of a defending spell caster." msgstr "" msgid "The Skull Pile increases production of Skeletons by %{count} per week." msgstr "" msgid "Thieves' Guild" msgstr "Lega dei Ladri" msgid "Tavern" msgstr "Taverna" msgid "Shipyard" msgstr "Cantiere Navale" msgid "Well" msgstr "Pozzo" msgid "Statue" msgstr "Statua" msgid "Marketplace" msgstr "Piazza del Mercato" msgid "Moat" msgstr "Fossato" msgid "Castle" msgstr "Castello" msgid "Tent" msgstr "Tenda" msgid "Captain's Quarters" msgstr "Alloggio del Capitano" msgid "Mage Guild, Level 1" msgstr "Lega dei Maghi" msgid "Mage Guild, Level 2" msgstr "Lega dei Maghi, liv. 2" msgid "Mage Guild, Level 3" msgstr "Lega dei Maghi, liv. 3" msgid "Mage Guild, Level 4" msgstr "Lega dei Maghi, liv. 4" msgid "Mage Guild, Level 5" msgstr "Lega dei Maghi, liv. 5" msgid "" "The Shrine increases the necromancy skill of all your necromancers by 10 " "percent." msgstr "" "L'Altare Oscuro aumenta la capacità negromantica dei tuoi negromanti di 10 " "percento." msgid "" "The Thieves' Guild provides information on enemy players. Thieves' Guilds " "can also provide scouting information on enemy towns. Additional Guilds " "provide more information." msgstr "" msgid "The Tavern increases morale for troops defending the castle." msgstr "" msgid "The Shipyard allows ships to be built." msgstr "" msgid "" "The Well increases the growth rate of all dwellings by %{count} creatures " "per week." msgstr "" #, fuzzy msgid "The Statue increases the town's income by %{count} gold per day." msgstr "%{name} incrementa di %{count} punti il sapere del tuo eroe." msgid "The Left Turret provides extra firepower during castle combat." msgstr "" msgid "The Right Turret provides extra firepower during castle combat." msgstr "" msgid "" "The Marketplace can be used to convert one type of resource into another. " "The more marketplaces you control, the better the exchange rate." msgstr "" msgid "" "The Moat slows attacking units. Any unit entering the moat must end its turn " "there and becomes more vulnerable to attack." msgstr "" #, fuzzy msgid "" "The Castle improves the town's defense and increases its income to %{count} " "gold per day." msgstr "%{name} incrementa di %{count} punti il sapere del tuo eroe." msgid "" "The Tent provides workers to build a castle, provided the materials and the " "gold are available." msgstr "" msgid "" "The Captain's Quarters provides a captain to assist in the castle's defense " "when no hero is present." msgstr "" msgid "" "The Mage Guild allows heroes to learn spells and replenish their spell " "points." msgstr "" msgid "Recruit %{name}" msgstr "Recluta %{name}" msgid "Month: %{month}, Week: %{week}, Day: %{day}" msgstr "Mese: %{month}, Sett.: %{week}, Giorno: %{day}" msgid "" "You must purchase a spell book to use the mage guild, but you currently have " "no room for a spell book. Try giving one of your artifacts to another hero." msgstr "" msgid "Click to show next town." msgstr "Clicca per andare alla città succ." msgid "Show next town" msgstr "Va alla città pross." msgid "Click to show previous town." msgstr "Clicca per andare alla città prec." msgid "Show previous town" msgstr "Vai alla città prec." msgid "This town may not be upgraded to a castle." msgstr "" msgid "Town" msgstr "Castello" msgid "Exit Castle" msgstr "Esci dal Castello" msgid "Exit Town" msgstr "Esci dalla Città" msgid "Show Income" msgstr "Mostra Rendita" msgid "View Hero" msgstr "Mostra Eroe" msgid "The above spells are available here." msgstr "Qua sono disponibili queste magie." #, fuzzy msgid "The spells the hero can learn have been added to their book." msgstr "" "Le magie alle quali sodisfi i requisiti sono state aggiunte nel tuo libro." msgid "A generous tip for the barkeep yields the following rumor:" msgstr "Una mancia generosa al gestore del bar risulta in questa informazione:" msgid "Recruit Hero" msgstr "Recluta Eroe" msgid "%{name} is a level %{value} %{race} " msgstr "%{name} è un/a %{race} di livello %{value} " msgid "with %{count} artifacts." msgstr "con %{count} artefatti." msgid "with 1 artifact." msgstr "con 1 artefatto." msgid "without artifacts." msgstr "senza artefatti." msgid "Recruit %{name} the %{race}" msgstr "Recluta %{name}: %{race}" msgid "" "'Spread' combat formation spreads the castle's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" msgid "" "'Grouped' combat formation bunches the castle's units together in the center " "of the castle's side of the battlefield." msgstr "" msgid "Spread Formation" msgstr "Formazione sparsa" msgid "Grouped Formation" msgstr "Formazione chiusa" msgid "Set garrison combat formation to 'Spread'" msgstr "" msgid "Set garrison combat formation to 'Grouped'" msgstr "" msgid "Exit Castle Options" msgstr "Esci dal castello" msgid "Castle Options" msgstr "Opzioni Castello" #, fuzzy msgid "Not enough resources to recruit creatures." msgstr "Mancano risorse per comprare truppe." msgid "You are unable to recruit at this time, your ranks are full." msgstr "" #, fuzzy msgid "No creatures available for purchase." msgstr "Niente truppe disponibili da comprare." #, fuzzy msgid "Recruit Creatures" msgstr "Recluta Eroe" msgid "Max" msgstr "Max" #, fuzzy msgid "Hire all creatures in the town." msgstr "Perdi tutti i tuoi eroi e le città." msgid "Available" msgstr "Disponibile" msgid "Town Population Information and Statistics" msgstr "Populazione e Dati della Città" msgid "Damage" msgstr "Danni" msgid "HP" msgstr "PF" msgid "Growth" msgstr "Crescita" msgid "week" msgstr "sett." msgid "View World" msgstr "Mostra Mondo" msgid "View the entire world." msgstr "Fa vedere il mondo intero." msgid "Puzzle" msgstr "Puzzle" msgid "View the obelisk puzzle." msgstr "Mostra il puzzle obelisco." msgid "Scenario Information" msgstr "Informazioni sullo scenario" msgid "View information on the scenario you are currently playing." msgstr "Mostra lo scenario sul quale stai giocando." msgid "Dig for the Ultimate Artifact." msgstr "Scava alla ricerca dell'Estremo Artefatto." msgid "Digging" msgstr "Scavo" msgid "Arena" msgstr "Arena" #, fuzzy msgid "" "You enter the arena and face a pack of vicious lions. You handily defeat " "them, to the wild cheers of the crowd. Impressed by your skill, the aged " "trainer of gladiators agrees to train you in a skill of your choice." msgstr "" "Entri nell'arena e affronti un branco di leoni feroci. Li sconfiggi, senza " "sforzo, e il pubblico ti applaude. Colpito dalle tue capacità, un allenatore " "di gladiatori attempato acconsente ad allenarti in una dote a scelta tua." msgid "Attack Skill" msgstr "Attacco" msgid "Defense Skill" msgstr "Difesa" msgid "Shots" msgstr "Colpi" msgid "Shots Left" msgstr "Colpi Residui" msgid "Hit Points" msgstr "Pt. Ferita" msgid "Hit Points Left" msgstr "PF Rimasti" msgid "You can't afford to upgrade your troops!" msgstr "" msgid "" "Your troops can be upgraded, but it will cost you dearly. Do you wish to " "upgrade them?" msgstr "" msgid "Are you sure you want to dismiss this army?" msgstr "Sei sicuro di voler congedare queste forze?" msgid "Upgrade" msgstr "" msgid "Upgrade your troops." msgstr "" msgid "Dismiss" msgstr "" msgid "Dismiss this army." msgstr "" msgid "" "A group of %{monster} with a desire for greater glory wish to join you.\n" "Do you accept?" msgstr "" "Un gruppo di %{monster} con un desiderio di gloria vuole unirsi alla tua " "armata.\n" "Accetti?" msgid "Followers" msgstr "Accoliti" msgid "" "The %{monster} is swayed by your diplomatic tongue, and offers to join your " "army for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "Il gruppo di %{monster} è persuaso dalle tue abilità diplomatiche ed offre " "di aggiungersi al tuo esercito per %{gold} pezzi d'oro.\n" "Accetti?" msgid "" "The creatures are swayed by your diplomatic\n" "tongue, and make you an offer:\n" "\n" msgstr "" "Il gruppo di mostri è persuaso dalla tua capacità diplomatica \n" "e ti offrono questo:\n" "\n" msgid "" "%{offer} of the %{total} %{monster} will join your army, and the rest will " "leave you alone, for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "%{offer} dei/delle %{total} %{monster} si aggiungeranno al tuo esercito, e " "gli altri ti lasceranno andare per %{gold} pezzi d'oro.\n" "Accetti?" msgid "" "All %{offer} of the %{monster} will join your army for the sum of %{gold} " "gold.\n" "Do you accept?" msgstr "" "Tutti/e %{offer} %{monster} si aggiungono al tuo esercito per %{gold} pezzi " "d'oro.\n" "Accetti?" msgid "(Rate: %{percent})" msgstr "(Grado: %{percent})" msgid "off" msgstr "Muto" msgid "Music" msgstr "Musica" msgid "Effects" msgstr "Effetti" msgid "MIDI" msgstr "MIDI" msgid "MIDI Expansion" msgstr "Espansione MIDI" msgid "External" msgstr "Esterna" msgid "Music Type" msgstr "Stile di Musica" msgid "3D Audio" msgstr "" msgid "Toggle ambient music level." msgstr "" msgid "Toggle foreground sounds level." msgstr "" msgid "Change the type of music." msgstr "" msgid "Toggle 3D effects of foreground sounds." msgstr "" msgid "Build a new ship:" msgstr "Costruisci una nave:" msgid "Resource cost:" msgstr "Costo Risorse:" msgid "Total: " msgstr "Totale: " msgid "Need: " msgstr "Servono: " msgid "Load Game" msgstr "Carica Partita" msgid "No save files to load." msgstr "Non esistono punti di salvataggio da caricare." msgid "New Game" msgstr "Nuova Partita" msgid "Start a single or multi-player game." msgstr "Inizia una partita singola o multigiocatore." msgid "Load a previously saved game." msgstr "Carica una partita salvata." msgid "Save Game" msgstr "Salva Partita" msgid "Save the current game." msgstr "Salva la partita in corso." msgid "Quit" msgstr "Esci" #, fuzzy msgid "Quit out of Heroes of Might and Magic II." msgstr "Esci da fheroes2." msgid "Language" msgstr "Lingua" msgid "Graphics" msgstr "" #, fuzzy msgid "Black & White" msgstr "Ponte" #, fuzzy msgid "Mouse Cursor" msgstr "Cursore Ombra" msgid "Color" msgstr "" #, fuzzy msgid "Text Support" msgstr "" "Tele-\n" "trasporto" msgid "Change the language of the game." msgstr "Cambia la lingua del gioco." msgid "Select Game Language" msgstr "Scegli lingua del gioco" #, fuzzy msgid "Change the graphics settings of the game." msgstr "Cambia le impostazioni dell'audio del gioco." msgid "Toggle colored cursor on or off. This is only an aesthetic choice." msgstr "" msgid "" "Toggle text support mode to output extra information about windows and " "events in the game." msgstr "" msgid "" "Map\n" "Difficulty" msgstr "" "Difficoltà\n" "Mappa" msgid "" "Game\n" "Difficulty" msgstr "" "Difficoltà\n" "Gioco" msgid "Rating" msgstr "Classifica" msgid "Map Size" msgstr "" "Dimensioni\n" "Mappa" msgid "Opponents" msgstr "Avversari" msgid "Class" msgstr "Classe" msgid "" "Victory\n" "Conditions" msgstr "" "Condizioni\n" "Vittoria" msgid "" "Loss\n" "Conditions" msgstr "" "Condizioni\n" "Sconfitta" msgid "First select recipients!" msgstr "Prima scegli destinatari!" msgid "You cannot select %{resource}!" msgstr "Non puoi scegliere %{resource}!" msgid "Select count %{resource}:" msgstr "Scegli quantità di %{resource}:" msgid "Select Recipients" msgstr "Scegli Destinatari" msgid "Your Funds" msgstr "Le Tue Risorse" msgid "Planned Gift" msgstr "Dono Scelto" msgid "Gift from %{name}" msgstr "Dono da %{name}" msgid "Resolution" msgstr "Risoluzione" msgid "Fullscreen" msgstr "" msgid "window|Mode" msgstr "" msgid "Windowed" msgstr "" msgid "V-Sync" msgstr "" #, fuzzy msgid "on" msgstr "1" msgid "FPS" msgstr "" #, fuzzy msgid "System Info" msgstr "Opzioni Sistema" msgid "Change the resolution of the game." msgstr "Cambia la risoluzione del gioco." msgid "Select Game Resolution" msgstr "Scegli la risoluzione del gioco" msgid "Toggle between fullscreen and windowed modes." msgstr "" msgid "" "The V-Sync option can be enabled to resolve flickering issues on some " "monitors." msgstr "" msgid "Show extra information such as FPS and current time." msgstr "" msgid "Hotkey: " msgstr "" msgid "Category: " msgstr "" #, fuzzy msgid "Event: " msgstr "Evento" msgid "Hot Keys:" msgstr "" msgid "Evil" msgstr "Cattivo" msgid "Good" msgstr "Buono" msgid "Interface Type" msgstr "Stile d'Interfaccia" msgid "Hide" msgstr "Nascondi" msgid "Show" msgstr "Mostra" msgid "Interface" msgstr "Interfaccia" #, fuzzy msgid "Slow" msgstr "Pianissimo" #, fuzzy msgid "Normal" msgstr "Normale" msgid "Fast" msgstr "" #, fuzzy msgid "Very Fast" msgstr "Velocissima" msgid "Scroll Speed" msgstr "Velocità dello Scorrimento" msgid "Toggle the type of interface you want to use." msgstr "" msgid "Toggle interface visibility." msgstr "" msgid "Toggle colored cursor on or off. This is only an esthetic choice." msgstr "" msgid "Sets the speed at which you scroll the window." msgstr "" msgid "Select Game Language:" msgstr "Scegli la lingua del gioco:" #, fuzzy msgid "Click to choose the selected language." msgstr "Clicca per salvare la partita in corso." msgid "%{name} has gained a level." msgstr "%{name} sale di livello." msgid "%{skill} +1" msgstr "%{skill} +1" msgid "You have learned %{skill}." msgstr "Hai appreso %{skill}." #, fuzzy msgid "" "%{name} has gained a level.\n" "\n" "%{skill} +1" msgstr "%{name} sale di livello." #, fuzzy msgid "" "You may learn either:\n" "%{skill1}\n" "or\n" "%{skill2}" msgstr "Può imparare:" msgid "" "Please inspect our fine wares. If you feel like offering a trade, click on " "the items you wish to trade with and for." msgstr "" msgid "" "You have received quite a bargain. I expect to make no profit on the deal. " "Can I interest you in any of my other wares?" msgstr "" "Hai davvero fatto un affare. Non ci guadagno nulla su questo. Ti possono " "interessare degli altri articoli?" msgid "I can offer you %{count} for 1 unit of %{resfrom}." msgstr "Ti offro %{count} unità d'oro per 1 unità di %{resfrom}." msgid "I can offer you 1 unit of %{resto} for %{count} units of %{resfrom}." msgstr "Ti offro 1 unità di %{resto} per %{count} unità di %{resfrom}." msgid "Min" msgstr "Min" msgid "Qty to trade" msgstr "Qtà di scambio" msgid "Trading Post" msgstr "Punto di Scambio" msgid "Your Resources" msgstr "Le Tue Risorse" msgid "Available Trades" msgstr "Scambi disponibili" msgid "n/a" msgstr "n/d" msgid "guarded by %{count} %{monster}" msgstr "Protetto/a da %{count} %{monster}" msgid "guarded by " msgstr "Protetto/a da " msgid "(available: %{count})" msgstr "(disponibiltà: %{count})" msgid "(empty)" msgstr "(vuoto/a)" msgid "already learned" msgstr "già appreso/a" #, fuzzy msgid "treeOfKnowledge|free" msgstr "Albero del Sapere" #, fuzzy msgid "already claimed" msgstr "già appreso/a" msgid "not claimed" msgstr "" msgid "already knows this skill" msgstr "già imparato/a" msgid "already has max skills" msgstr "già al massimo dei doti" msgid "(already visited)" msgstr "(già visitato/a)" msgid "(not visited)" msgstr "(non visitato/a)" msgid "%{color} Barrier" msgstr "Barriera %{color}" #, fuzzy msgid "(tent visited)" msgstr "(non visitato/a)" msgid "%{color} Tent" msgstr "Tenda %{color}" msgid "Road" msgstr "Strada" msgid "(digging ok)" msgstr "(scavo possibile)" msgid "(no digging)" msgstr "(scavo impossibile)" msgid "penalty: %{cost}" msgstr "penalità: %{cost}" msgid "Defenders:" msgstr "Difensori:" msgid "Unknown" msgstr "Sconosciuta" msgid "%{name} (Level %{level})" msgstr "%{name} (Livello %{level})" #, fuzzy msgid "Attack:" msgstr "Attacco" #, fuzzy msgid "Defense:" msgstr "Difesa" #, fuzzy msgid "Spell Power:" msgstr "Potere" #, fuzzy msgid "Knowledge:" msgstr "Sapere" #, fuzzy msgid "Spell Points:" msgstr "Pt. Magia" #, fuzzy msgid "Move Points:" msgstr "Pt. Movim." msgid "Uncharted Territory" msgstr "Terra Ignota" msgid "Cost per troop:" msgstr "" msgid "Available: %{count}" msgstr "Disponibile/i: %{count}" msgid "Number to buy:" msgstr "Quantità:" msgid "Recruit selected monsters." msgstr "" msgid "Select maximum monsters to be recruited." msgstr "" msgid "Select only 1 monster to be recruited." msgstr "" msgid "Select Game Resolution:" msgstr "Scegli la risoluzione del gioco:" msgid "Click to apply the selected resolution." msgstr "" #, fuzzy msgid "Click to apply the entered text." msgstr "Clicca per salvare la partita in corso." msgid "Click to open the Virtual Keyboard dialog." msgstr "" msgid "Open Virtual Keyboard" msgstr "" msgid "How many troops to move?" msgstr "Quante forze spostare?" msgid "Fast separation into slots:" msgstr "Distribuzione rapida ai riquadri liberi:" msgid "Map: " msgstr "Mappa: " msgid "" "\n" "\n" "Month: " msgstr "" msgid ", Week: " msgstr "" msgid ", Day: " msgstr "" msgid "" "\n" "\n" "Location: " msgstr "" "\n" "\n" "Percorso: " msgid "Click to save the current game." msgstr "Clicca per salvare la partita in corso." msgid "Click to load a previously saved game." msgstr "Clicca per caricare una partita precedente." msgid "Are you sure you want to delete file:" msgstr "Sei sicuro di voler cancellare il file:" msgid "Warning!" msgstr "Attenzione!" msgid "File to Save:" msgstr "File da Salvare:" msgid "File to Load:" msgstr "File da Caricare:" msgid "Accept the choice made." msgstr "Accetta la scelta." #, fuzzy msgid "%{color} %{race} hero" msgstr "Giocatore %{color}" #, fuzzy msgid "Terrain object" msgstr "Nessun oggetto" msgid "Select Skill:" msgstr "Scegli dote:" msgid "Select Spell:" msgstr "Scegli magia:" msgid "Select Artifact:" msgstr "Scegli artefatto:" msgid "Select Monster:" msgstr "Scegli mostro:" msgid "Select Hero:" msgstr "Scegli eroe:" #, fuzzy msgid "Select Treasure:" msgstr "Scegli eroe:" #, fuzzy msgid "Select Ocean Object:" msgstr "Scegli artefatto:" #, fuzzy msgid "Castle/town placing" msgstr "Opzioni Castello" msgid "doubleLinedRace|Neutral" msgstr "Neutro" msgid "race|Random" msgstr "Casuale" #, fuzzy msgid "Click to start placing the selected castle/town." msgstr "Clicca per salvare la partita in corso." #, fuzzy msgid "Click to select town placing." msgstr "Clicca per andare alla città succ." #, fuzzy msgid "Click to select castle placing." msgstr "Clicca per salvare la partita in corso." #, fuzzy msgid "%{color} %{race} %{townOrCastle}" msgstr "Giocatore %{color}" msgid "race|Neutral" msgstr "Neutro" msgid "You will place" msgstr "" #, fuzzy msgid "Click to select this class." msgstr "Clicca per salvare la partita in corso." #, fuzzy msgid "Click to select this color." msgstr "Clicca per andare alla città succ." #, fuzzy msgid "Select Dwelling:" msgstr "Scegli magia:" #, fuzzy msgid "Select Landscape Object:" msgstr "Scegli artefatto:" msgid "Mine placing" msgstr "" #, fuzzy msgid "" "Resource\n" "type:" msgstr "Risorso" #, fuzzy msgid "%{mineName} appearance:" msgstr "%{name}: %{race}" msgid "Click to select %{object} as the resource generator to be placed." msgstr "" #, fuzzy msgid "Select Mountain Object:" msgstr "Scegli artefatto:" #, fuzzy msgid "Select Rock Object:" msgstr "Scegli artefatto:" #, fuzzy msgid "Select Tree Object:" msgstr "Scegli artefatto:" #, fuzzy msgid "Select Power Up Object:" msgstr "Scegli artefatto:" #, fuzzy msgid "Select Adventure Object:" msgstr "Scegli artefatto:" #, fuzzy msgid "Map Type:\n" msgstr "Tipo di mappa" msgid "The Succession Wars" msgstr "" msgid "The Price of Loyalty" msgstr "" #, fuzzy msgid "Resurrection" msgstr "Risorgi" msgid "Lose all your heroes and towns." msgstr "Perdi tutti i tuoi eroi e le città." msgid "Lose a specific town." msgstr "Perdi una città specifica." msgid "Lose a specific hero." msgstr "Perdi un eroe specifico." msgid "Run out of time. Fail to win by a certain point." msgstr "Vai fuori tempo. Non riuscire a vincere entro un certo tempo." msgid "Loss Condition" msgstr "Condizione Sconfitta" msgid "Defeat all enemy heroes and towns." msgstr "Sconfiggi tutti gli eroi e città nemiche." msgid "Capture a specific town." msgstr "Cattura una città specifica." msgid "Defeat a specific hero." msgstr "Sconfiggi un eroe specifico." msgid "Find a specific artifact." msgstr "Trova un dato artefatto." msgid "Your side defeats the opposing side." msgstr "La tua alleanza sconfigge quella avversaria." msgid "Accumulate a large amount of gold." msgstr "Accumula una grande quantità d'oro." msgid "Victory Condition" msgstr "Condizione Vittoria" msgid "Map difficulty:" msgstr "Difficoltà mappa:" msgid "N" msgstr "" msgid "No maps exist at that size" msgstr "Non esistono mappe di quella dimensione" msgid "Small Maps" msgstr "Mappe Piccole" msgid "View only maps of size small (36 x 36)." msgstr "Mostra solo le mappe piccole (36 x 36)." msgid "Medium Maps" msgstr "Mappe Medie" msgid "View only maps of size medium (72 x 72)." msgstr "Mostra solo le mappe medie (72 x 72)." msgid "Large Maps" msgstr "Mappe Grandi" msgid "View only maps of size large (108 x 108)." msgstr "Mostra solo le mappe grandi (108 x 108)." msgid "Extra Large Maps" msgstr "Mappe XL" msgid "View only maps of size extra large (144 x 144)." msgstr "Mostra solo mappe XL (144 x 144)." msgid "All Maps" msgstr "Tutte le Mappe" msgid "View all maps, regardless of size." msgstr "Mostra tutte le mappe, indipendentemente dalla dimensione." msgid "Players Icon" msgstr "Icona dei Giocatori" msgid "" "Indicates how many players total are in the scenario. Any positions not " "occupied by humans will be occupied by computer players." msgstr "" "Indica quanti giocatori vi sono in totale nello scenario. Ogni posizione non " "occupata da umani sarà occupata da giocatori computer." msgid "" "Indicates whether the map\n" "is small (36 x 36), medium\n" "(72 x 72), large (108 x 108),\n" "or extra large (144 x 144)." msgstr "" "Indica se la mappa è\n" "piccola (36 x 36), media\n" "(72 x 72), grande (108 x 108), o\n" "XL (114 x 114)." msgid "Size Icon" msgstr "Icona del Dimensione" #, fuzzy msgid "" "Indicates whether the map is made for \"The Succession Wars\" or \"The Price " "of Loyalty\" version of the game." msgstr "" "Indica se la mappa è stata creata per la versione del gioco Guerre di " "Successione o The Price of Loyalty." msgid "Map Type" msgstr "Tipo di mappa" msgid "Selected Name" msgstr "Nome della Mappa" msgid "The name of the currently selected map." msgstr "Il nome della mappa scelta attualmente." msgid "Selected Map Difficulty" msgstr "Difficoltà della Mappa Scelta" #, fuzzy msgid "" "The map difficulty of the currently selected map. The map difficulty is " "determined by the scenario designer. More difficult maps might include more " "or stronger enemies, fewer resources, or other special conditions making " "things tougher for the human player." msgstr "" "La difficoltà della mappa scelta attualmente. La difficoltà della mappa è " "data dallo scenario. Mappe più difficili includono più nemici (o più forti), " "meno risorse oppure altre condizioni speciali che rendono la partita più " "dura all'umano." msgid "Selected Description" msgstr "Descrizione scelta" msgid "The description of the currently selected map." msgstr "La descrizione della mappa scelta attualmente." msgid "Jump" msgstr "Salta" msgid "Hero Speed" msgstr "Velocità dell'Eroe" msgid "Don't Show" msgstr "Nascondi" msgid "Enemy Speed" msgstr "Velocità Nemica" msgid "Auto Resolve" msgstr "Auto-Risolva" msgid "Auto, No Spells" msgstr "Auto, Senza Magie" msgid "Battles" msgstr "Battaglie" msgid "autoBattle|Manual" msgstr "Manuali" msgid "Change the speed at which your heroes move on the main screen." msgstr "" msgid "" "Sets the speed that A.I. heroes move at. You can also elect not to view A.I. " "movement at all." msgstr "" #, fuzzy msgid "Change the interface settings of the game." msgstr "Cambia le impostazioni dell'audio del gioco." #, fuzzy msgid "Interface Settings" msgstr "Impostazioni del gioco." msgid "Toggle instant battle mode." msgstr "" msgid "Att." msgstr "Att." msgid "Def." msgstr "Dif." msgid "Power" msgstr "Potere" msgid "Knowl" msgstr "Sapere" msgid "1st" msgstr "Primo" msgid "2nd" msgstr "Secondo" msgid "3rd" msgstr "Terzo" msgid "4th" msgstr "Quarto" msgid "5th" msgstr "Quinto" msgid "6th" msgstr "Sesto" msgid "Oracle: Player Rankings" msgstr "Tempio: Graduatoria Giocatori" msgid "Thieves' Guild: Player Rankings" msgstr "Lega dei Ladri: Graduatoria" msgid "Number of Towns:" msgstr "Numero di Città:" msgid "Number of Castles:" msgstr "Numero di Castelli:" msgid "Number of Heroes:" msgstr "Numero di Eroi:" msgid "Gold in Treasury:" msgstr "Oro nei Forzieri:" msgid "Wood & Ore:" msgstr "Legname e Ferro:" msgid "Gems, Cr, Slf & Mer:" msgstr "Gem, Cr, Zlf e Merc:" msgid "Obelisks Found:" msgstr "Obelischi Trovati:" msgid "Artifacts:" msgstr "Artefatti:" msgid "Total Army Strength:" msgstr "Forza Totale Esercito:" msgid "Income:" msgstr "Rendita:" msgid "Best Hero:" msgstr "Miglior Eroe:" msgid "Best Hero Stats:" msgstr "Dati Miglior Eroe:" msgid "Personality:" msgstr "Ruolo:" msgid "Best Monster:" msgstr "Miglior Mostro:" #, fuzzy msgid "Map filename" msgstr "" "Dimensioni\n" "Mappa" #, fuzzy msgid "" "Are you sure you want to load a new map? (Any unsaved changes to the current " "map will be lost.)" msgstr "" "Sei sicuro di voler cominciare una nuova partita? (La partita attuale andrà " "perduta.)" #, fuzzy msgid "" "Are you sure you want to create a new map? (Any unsaved changes to the " "current map will be lost.)" msgstr "" "Sei sicuro di voler cominciare una nuova partita? (La partita attuale andrà " "perduta.)" msgid "" "Create a new map, either from scratch or using the random map generator." msgstr "" #, fuzzy msgid "New Map" msgstr "Nuova Partita" #, fuzzy msgid "Load Map" msgstr "Carica Partita" msgid "Load an existing map." msgstr "" #, fuzzy msgid "Save Map" msgstr "Salva Partita" #, fuzzy msgid "Save the current map." msgstr "Salva la partita in corso." msgid "Quit out of the map editor." msgstr "" msgid "Objects cannot be placed outside the map." msgstr "" msgid "Monsters cannot be placed on water." msgstr "" msgid "Choose a tile which does not contain any objects." msgstr "" msgid "Treasures cannot be placed on water." msgstr "" msgid "Heroes cannot be placed on water." msgstr "" msgid "" "A maximum of %{count} heroes of the same color can be placed on the map." msgstr "" msgid "Artifacts cannot be placed on water." msgstr "" msgid "Mountains cannot be placed on water." msgstr "" msgid "Rocks cannot be placed on water." msgstr "" msgid "Trees cannot be placed on water." msgstr "" msgid "Ocean object must be placed on water." msgstr "" #, fuzzy msgid "Landscape objects cannot be placed on water." msgstr "Opzioni Avventura" msgid "Towns cannot be placed on water." msgstr "" msgid "Mines cannot be placed on water." msgstr "" msgid "Dwellings cannot be placed on water." msgstr "" msgid "Power-ups cannot be placed on water." msgstr "" #, fuzzy msgid "Adventure objects cannot be placed on water." msgstr "Opzioni Avventura" msgid "Used to place %{object}." msgstr "" #, fuzzy msgid "Select object type" msgstr "Scegli artefatto:" #, fuzzy msgid "" "Click here to\n" "select a monster." msgstr "Clicca per andare alla città succ." #, fuzzy msgid "" "Click here to\n" "select another monster." msgstr "Per favore, scegli un altro eroe." msgid "" "Cell\n" "Details" msgstr "" #, fuzzy msgid "Roads" msgstr "Strada" msgid "Streams" msgstr "" msgid "Erase" msgstr "" msgid "Mountains" msgstr "Montagne" #, fuzzy msgid "Rocks" msgstr "Roccia" msgid "Trees" msgstr "Alberi" #, fuzzy msgid "Water Objects" msgstr "Terra sterile" #, fuzzy msgid "Miscellaneous" msgstr "Fineo" msgid "Artifacts" msgstr "Artefatti" #, fuzzy msgid "Dwellings" msgstr "Frombolieri" msgid "Mines" msgstr "Miniere" #, fuzzy msgid "Power-ups" msgstr "Potere" #, fuzzy msgid "Treasures" msgstr "Tesoro" #, fuzzy msgid "Towns" msgstr "Castello" msgid "Heroes" msgstr "Eroi" msgid "editorErasure|Terrain" msgstr "" msgid "editorErasure|Castles" msgstr "" msgid "editorErasure|Monsters" msgstr "" msgid "editorErasure|Heroes" msgstr "" msgid "editorErasure|Artifacts" msgstr "" msgid "editorErasure|Roads" msgstr "" msgid "editorErasure|Streams" msgstr "" msgid "editorErasure|Treasures" msgstr "" msgid "" "Draws terrain in\n" "%{size} by %{size} square increments." msgstr "" msgid "" "Erases objects in\n" "%{size} by %{size} square increments." msgstr "" #, fuzzy msgid "Small Brush" msgstr "Mappe Piccole" #, fuzzy msgid "Medium Brush" msgstr "Mappe Medie" #, fuzzy msgid "Large Brush" msgstr "Mappe Grandi" msgid "Area Fill" msgstr "" msgid "Used to click and drag for filling in large areas." msgstr "" msgid "Clear Area" msgstr "" msgid "Used to click and drag for clearing large areas." msgstr "" msgid "" "Costs %{rate} times normal movement for all heroes. (Pathfinding reduces or " "eliminates the penalty.)" msgstr "" msgid "Traversable only by boat." msgstr "" msgid "No special modifiers." msgstr "" msgid "Toggle the erasure of %{type} objects." msgstr "" msgid "" "Objects of this type will be deleted with the Erase tool. Left-click here to " "deselect this type. Press and hold this button to deselect all other object " "types." msgstr "" msgid "" "Objects of this type will NOT be deleted with the Erase tool. Left-click " "here to select this type. Press and hold this button to select all other " "object types." msgstr "" msgid "Terrain Mode" msgstr "" msgid "Used to draw the underlying grass, dirt, water, etc. on the map." msgstr "" msgid "Landscape Objects Mode" msgstr "" msgid "" "Used to place landscape objects (mountains, rocks, trees, etc.) on the map." msgstr "" msgid "Detail Mode" msgstr "" #, fuzzy msgid "Used for special editing of monsters, heroes and towns." msgstr "Sconfiggi tutti gli eroi e città nemiche." #, fuzzy msgid "Adventure Objects Mode" msgstr "Opzioni Avventura" msgid "" "Used to place adventure objects (artifacts, dwellings, mines, treasures, " "etc.) on the map." msgstr "" #, fuzzy msgid "Kingdom Objects Mode" msgstr "Rendita" msgid "Used to place kingdom objects (towns, castles and heroes) on the map." msgstr "" #, fuzzy msgid "Monsters Mode" msgstr "Mostri" msgid "Used to place monsters on the map." msgstr "" msgid "Allows you to draw streams by clicking and dragging." msgstr "" msgid "Stream Mode" msgstr "" msgid "Allows you to draw roads by clicking and dragging." msgstr "" #, fuzzy msgid "Road Mode" msgstr "Strada" msgid "Erase Mode" msgstr "" msgid "Used to erase objects from the map." msgstr "" msgid "Change between zoom and normal view." msgstr "" msgid "Magnify" msgstr "" msgid "Undo" msgstr "" msgid "Undo your last action." msgstr "" msgid "Create a new map either from scratch or using the random map generator." msgstr "" msgid "Edit map title, description, and other general information." msgstr "" #, fuzzy msgid "Specifications" msgstr "Pietrificazione" msgid "File Options" msgstr "Opzioni File" #, fuzzy msgid "" "Open the file options menu, where you can save or load maps, or quit out of " "the editor." msgstr "Mostra il menù file che ti permette di caricare, salvare ecc.." #, fuzzy msgid "View the editor system options, which let you customize the editor." msgstr "" "Mostra il menù di opzioni per il sistema per modificare l'esperienza del " "gioco." msgid "Create a map that is %{size} squares wide and %{size} squares high." msgstr "" msgid "Cancel back to the New Map menu." msgstr "" msgid "Cancel back to the main menu." msgstr "" msgid "From Scratch" msgstr "" msgid "Start from scratch with a blank map." msgstr "" msgid "Create a randomly generated map." msgstr "" #, fuzzy msgid "Random" msgstr "Casuale" msgid "Cancel back to the Map Editor main menu." msgstr "" msgid "No maps available!" msgstr "" msgid "Warning" msgstr "" msgid "difficulty|Easy" msgstr "Facile" msgid "difficulty|Normal" msgstr "Normale" msgid "difficulty|Hard" msgstr "Duro" msgid "difficulty|Expert" msgstr "Esperto" msgid "difficulty|Impossible" msgstr "Impossibile" msgid "and more..." msgstr "" msgid "Easy" msgstr "" msgid "Hard" msgstr "" #, fuzzy msgid "Campaign Difficulty" msgstr "Difficoltà Gioco" msgid "" "Choose this difficulty if you want to prefer game story over challenge. AI " "is weaker in comparison with normal difficulty." msgstr "" msgid "" "Choose this difficulty to enjoy the campaign as per the original design." msgstr "" msgid "" "Choose this difficulty if you want challenge. AI is stronger in comparison " "with normal difficulty." msgstr "" #, fuzzy msgid "" "Congratulations!\n" "\n" "Days: %{days}\n" msgstr "Complimenti!" msgid "" "\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" msgid "" "Score: %{score}\n" "\n" "Rating:\n" "%{rating}" msgstr "" msgid "Start the selected scenario." msgstr "" #, fuzzy msgid "View Intro" msgstr "Mostra Eroe" msgid "View Intro videos for the current state of the campaign." msgstr "" msgid "" "Select the campaign difficulty. This can be lowered at any point during the " "campaign." msgstr "" #, fuzzy msgid "Restart the current scenario." msgstr "Salva la partita in corso." #, fuzzy msgid "Difficulty" msgstr "" "Difficoltà\n" "Mappa" msgid "" "You have changed to a lower difficulty for the campaign. You will not be " "able to revert this after this point. The high score will be calculated " "based solely on the new difficulty. Do you want to proceed?" msgstr "" msgid "Are you sure you want to restart this scenario?" msgstr "Sei sicuro di voler ricominciare questo scenario?" msgid "Campaign Scenario loading failure" msgstr "" msgid "Please make sure that campaign files are correct and present." msgstr "" msgid "Days spent" msgstr "" msgid "The number of days spent on this campaign." msgstr "" msgid "Project Coordination and Core Development" msgstr "" msgid "Development" msgstr "" msgid "Visit us at " msgstr "" msgid "QA and Support" msgstr "" #, fuzzy msgid "Dev and Support" msgstr "" "Tele-\n" "trasporto" msgid "Special Thanks to" msgstr "" msgid "and many other contributors!" msgstr "" msgid "and many-many other supporters!" msgstr "" msgid "Support us at" msgstr "" msgid "local-donation-platform|https://www.patreon.com/fheroes2" msgstr "" msgid "Connect with us at" msgstr "" msgid "local-social-network|https://www.facebook.com/groups/fheroes2" msgstr "" msgid "Need help with the game?" msgstr "" msgid "Original project before 0.7" msgstr "" #, fuzzy msgid "Heroes of Might and Magic II: The Succession Wars team" msgstr "Esci da fheroes2." msgid "Designed and Directed" msgstr "" msgid "Programming and Design" msgstr "" msgid "Executive Producer" msgstr "" msgid "Producer" msgstr "" msgid "Additional Design" msgstr "" msgid "Additional Programming" msgstr "" msgid "Musical Production" msgstr "" msgid "Music and Sound Design" msgstr "" msgid "Vocalists" msgstr "" msgid "Art Director" msgstr "" msgid "Assistant Art Director" msgstr "" msgid "Artists" msgstr "" msgid "QA Manager" msgstr "" msgid "QA" msgstr "" msgid "Writing" msgstr "" msgid "Manual and Helpfile" msgstr "" msgid "Scenarios" msgstr "" #, fuzzy msgid "Heroes of Might and Magic II: The Price of Loyalty team" msgstr "Esci da fheroes2." msgid "Design Lead" msgstr "" msgid "Designers" msgstr "" msgid "Programming Lead" msgstr "" msgid "Art Lead" msgstr "" #, fuzzy msgid "Playtesters" msgstr "Icona dei Giocatori" msgid "Designer" msgstr "" msgid "Producers" msgstr "" #, fuzzy msgid "QA Managers" msgstr "Cacciatori" msgid "Sound Design" msgstr "" #, fuzzy msgid "Town Themes" msgstr "Alle Porte" msgid "Alto Sax" msgstr "" msgid "Harpsichord and Piano" msgstr "" msgid "Basso Vocal" msgstr "" msgid "Soprano Vocal" msgstr "" msgid "Recorded at %{recordingStudio}" msgstr "" msgid "credits|Manual" msgstr "Manuale" msgid "German Consultant" msgstr "" msgid "Map Designers" msgstr "" msgid "Package Design" msgstr "" msgid "Your Name" msgstr "" msgid "Unknown Hero" msgstr "" msgid "Standard" msgstr "" msgid "View High Scores for Standard Maps." msgstr "" msgid "Campaign" msgstr "" msgid "View High Scores for Campaigns." msgstr "" msgid "hotkey|default okay event" msgstr "" msgid "hotkey|default cancel event" msgstr "" msgid "hotkey|default left" msgstr "" msgid "hotkey|default right" msgstr "" msgid "hotkey|default up" msgstr "" msgid "hotkey|default down" msgstr "" msgid "hotkey|toggle fullscreen" msgstr "" msgid "hotkey|toggle text support mode" msgstr "" msgid "hotkey|new game" msgstr "" msgid "hotkey|load game" msgstr "" msgid "hotkey|highscores" msgstr "" msgid "hotkey|credits" msgstr "" msgid "hotkey|standard game" msgstr "" msgid "hotkey|campaign game" msgstr "" #, fuzzy msgid "hotkey|multi-player game" msgstr "Inizia una partita singola o multigiocatore." #, fuzzy msgid "hotkey|settings" msgstr "Impostazioni" msgid "hotkey|quit" msgstr "" msgid "hotkey|select map" msgstr "" msgid "hotkey|select small map size" msgstr "" msgid "hotkey|select medium map size" msgstr "" msgid "hotkey|select large map size" msgstr "" msgid "hotkey|select extra large map size" msgstr "" msgid "hotkey|select all map sizes" msgstr "" msgid "hotkey|hotseat game" msgstr "" msgid "hotkey|battle only game" msgstr "" msgid "hotkey|choose the original campaign" msgstr "" msgid "hotkey|choose the expansion campaign" msgstr "" msgid "hotkey|map editor main menu" msgstr "" #, fuzzy msgid "hotkey|new map menu" msgstr "Mostra eroe successivo" msgid "hotkey|load map menu" msgstr "" msgid "hotkey|new map from scratch" msgstr "" #, fuzzy msgid "hotkey|new random map" msgstr "Sommario Regno" #, fuzzy msgid "hotkey|undo last action" msgstr "Va alla città pross." #, fuzzy msgid "hotkey|redo last action" msgstr "Va alla città pross." msgid "hotkey|roland campaign" msgstr "" msgid "hotkey|archibald campaign" msgstr "" msgid "hotkey|the price of loyalty campaign" msgstr "" msgid "hotkey|voyage home campaign" msgstr "" msgid "hotkey|wizard's isle campaign" msgstr "" msgid "hotkey|descendants campaign" msgstr "" msgid "hotkey|select first campaign bonus" msgstr "" msgid "hotkey|select second campaign bonus" msgstr "" msgid "hotkey|select third campaign bonus" msgstr "" msgid "hotkey|view campaign intro" msgstr "" #, fuzzy msgid "hotkey|select campaign difficulty" msgstr "Difficoltà Gioco" msgid "hotkey|restart campaign scenario" msgstr "" msgid "hotkey|world map left" msgstr "" msgid "hotkey|world map right" msgstr "" msgid "hotkey|world map up" msgstr "" msgid "hotkey|world map down" msgstr "" msgid "hotkey|world map up left" msgstr "" msgid "hotkey|world map up right" msgstr "" msgid "hotkey|world map down left" msgstr "" msgid "hotkey|world map down right" msgstr "" msgid "hotkey|save game" msgstr "" #, fuzzy msgid "hotkey|next hero" msgstr "Mostra eroe successivo" #, fuzzy msgid "hotkey|start hero movement" msgstr "Continua spostamento" #, fuzzy msgid "hotkey|cast adventure spell" msgstr "Lancia una magia avventura." msgid "hotkey|put hero to sleep" msgstr "" #, fuzzy msgid "hotkey|next town" msgstr "Va alla città pross." msgid "hotkey|end turn" msgstr "" #, fuzzy msgid "hotkey|file options" msgstr "Opzioni File" #, fuzzy msgid "hotkey|adventure options" msgstr "Opzioni Avventura" msgid "hotkey|puzzle map" msgstr "" #, fuzzy msgid "hotkey|scenario information" msgstr "Informazioni sullo scenario" msgid "hotkey|dig for artifact" msgstr "" msgid "hotkey|view world" msgstr "" #, fuzzy msgid "hotkey|kingdom summary" msgstr "Sommario Regno" msgid "hotkey|default action" msgstr "" msgid "hotkey|open focus" msgstr "" #, fuzzy msgid "hotkey|system options" msgstr "Modifica le opzioni di sistema" msgid "hotkey|scroll left" msgstr "" msgid "hotkey|scroll right" msgstr "" msgid "hotkey|scroll up" msgstr "" msgid "hotkey|scroll down" msgstr "" msgid "hotkey|toggle control panel" msgstr "" msgid "hotkey|toggle radar" msgstr "" msgid "hotkey|toggle buttons" msgstr "" msgid "hotkey|toggle status" msgstr "" msgid "hotkey|toggle icons" msgstr "" msgid "hotkey|transfer control to ai" msgstr "" msgid "hotkey|retreat from battle" msgstr "" msgid "hotkey|surrender during battle" msgstr "" msgid "hotkey|toggle battle auto mode" msgstr "" #, fuzzy msgid "hotkey|finish the battle in auto mode" msgstr "Sei sicuro di voler congedare queste forze?" #, fuzzy msgid "hotkey|battle options" msgstr "Opzioni Castello" msgid "hotkey|skip turn in battle" msgstr "" msgid "hotkey|cast battle spell" msgstr "" msgid "hotkey|dwelling level 1" msgstr "" msgid "hotkey|dwelling level 2" msgstr "" msgid "hotkey|dwelling level 3" msgstr "" msgid "hotkey|dwelling level 4" msgstr "" msgid "hotkey|dwelling level 5" msgstr "" msgid "hotkey|dwelling level 6" msgstr "" msgid "hotkey|well" msgstr "" #, fuzzy msgid "hotkey|marketplace" msgstr "Piazza del Mercato" msgid "hotkey|mage guild" msgstr "" #, fuzzy msgid "hotkey|shipyard" msgstr "Cantiere Navale" #, fuzzy msgid "hotkey|thieves guild" msgstr "Lega dei Ladri" msgid "hotkey|tavern" msgstr "" msgid "hotkey|construction screen" msgstr "" msgid "hotkey|buy all monsters in well" msgstr "" msgid "hotkey|split stack by half" msgstr "" msgid "hotkey|split stack by one" msgstr "" msgid "hotkey|join stacks" msgstr "" msgid "hotkey|upgrade troop" msgstr "" msgid "hotkey|dismiss hero or troop" msgstr "" msgid "" "Do you want to regain control from AI? The effect will take place only on " "the next turn." msgstr "" msgid "" "Do you want to transfer control from you to the AI? The effect will take " "place only on the next turn." msgstr "" msgid "Default Actions" msgstr "" msgid "Global Actions" msgstr "" msgid "Main Menu" msgstr "" msgid "World Map" msgstr "Mappa mondiale" #, fuzzy msgid "Battle Screen" msgstr "Schermata Eroe" #, fuzzy msgid "Town Screen" msgstr "Schermata Eroe" msgid "Army Actions" msgstr "" msgid "Editor" msgstr "" #, fuzzy msgid "The save file is corrupted." msgstr "Non esistono punti di salvataggio da caricare." msgid "Unsupported save format: " msgstr "" msgid "Current game version: " msgstr "" msgid "Last supported version: " msgstr "" msgid "This file contains a save with an invalid game type." msgstr "" msgid "" "This save file requires \"The Price of Loyalty\" game assets, but they have " "not been provided to the engine." msgstr "" msgid "This saved game is localized to '" msgstr "" msgid "' language, but the current language of the game is '" msgstr "" msgid "Hot Seat" msgstr "" msgid "" "Play a Hot Seat game, where 2 to 4 players play around the same computer, " "switching into the 'Hot Seat' when it is their turn." msgstr "" msgid "A single player game playing out a single map." msgstr "" msgid "Standard Game" msgstr "" msgid "A single player game playing through a series of maps." msgstr "" msgid "Campaign Game" msgstr "" msgid "Multi-Player Game" msgstr "" msgid "" "A multi-player game, with several human players completing against each " "other on a single map." msgstr "" msgid "fheroes2 Resurrection Team presents" msgstr "Il Team di fheroes2 Resurrection presenta" msgid "Greetings!" msgstr "Saluti!" #, fuzzy msgid "Welcome to Heroes of Might and Magic II powered by fheroes2 engine!" msgstr "" "Benvenuti a fheroes2! Gentilmente, prima di cominciare il gioco scegli una " "risoluzione per il gioco." #, fuzzy msgid "" "Welcome to Heroes of Might and Magic II powered by the fheroes2 engine!\n" "Before starting the game please select a game resolution." msgstr "" "Benvenuti a fheroes2! Gentilmente, prima di cominciare il gioco scegli una " "risoluzione per il gioco." msgid "Please Remember" msgstr "Non Dimenticare" # Needs extra whitespace to avoid yellow word splitting #, fuzzy msgid "" "You can always change the language, resolution and settings of the game by " "clicking on the " msgstr "Puoi sempre modificare la risoluzione cliccando sulla " msgid "door" msgstr "porta" msgid " on the left side of the Main Menu, or with the " msgstr "" msgid " button from the " msgstr "" msgid "NEW GAME" msgstr "" msgid "" " menu. \n" "\n" "Enjoy the game!" msgstr "" #, fuzzy msgid "The Editor requires \"The Price of Loyalty\" expansion files to work." msgstr "" "Una delle quattro nuove campagne dalla espansione 'The Price of Loyalty\"." #, fuzzy msgid "Quit Heroes of Might and Magic II and return to the operating system." msgstr "Lascia fheroes2." msgid "Credits" msgstr "Titoli di coda" msgid "View the credits screen." msgstr "Mostra titoli di coda." msgid "High Scores" msgstr "Classifiche" msgid "View the high scores screen." msgstr "Vedi i migliori punteggi." #, fuzzy msgid "Change language, resolution and settings of the game." msgstr "Cambia la risoluzione del gioco." #, fuzzy msgid "Game Settings" msgstr "Impostazioni del gioco." msgid "" "Required video files for campaign selection window are missing. Please make " "sure that all necessary files are present in the system." msgstr "" #, fuzzy msgid "" "Either Roland's or Archibald's campaign from the original Heroes of Might " "and Magic II." msgstr "" "O la campagna di Rolando or quella di Arcibaldo dal gioco originale di " "Heroes of Might and Magic II." msgid "Original Campaign" msgstr "Campagna Originale" msgid "Expansion Campaign" msgstr "Campagna Espansione" msgid "One of the four new campaigns from the Price of Loyalty expansion set." msgstr "" "Una delle quattro nuove campagne dalla espansione 'The Price of Loyalty\"." msgid "Loading video. Please wait..." msgstr "" msgid "Host" msgstr "" msgid "" "The host sets up the game options. There can only be one host per network " "game." msgstr "" msgid "Guest" msgstr "" msgid "" "The guest waits for the host to set up the game, then is automatically added " "in. There can be multiple guests for TCP/IP games." msgstr "" msgid "Battle Only" msgstr "" msgid "Setup and play a battle without loading any map." msgstr "" msgid "2 Players" msgstr "" msgid "" "Play with 2 human players, and optionally, up to 4 additional computer " "players." msgstr "" msgid "3 Players" msgstr "" msgid "" "Play with 3 human players, and optionally, up to 3 additional computer " "players." msgstr "" msgid "4 Players" msgstr "" msgid "" "Play with 4 human players, and optionally, up to 2 additional computer " "players." msgstr "" msgid "5 Players" msgstr "" msgid "" "Play with 5 human players, and optionally, up to 1 additional computer " "player." msgstr "" msgid "6 Players" msgstr "" msgid "Play with 6 human players." msgstr "" msgid "Dragon city has fallen! You are now the Master of the Dragons." msgstr "" msgid "" "You captured %{name}!\n" "You are victorious." msgstr "" msgid "" "You have captured the enemy hero %{name}!\n" "Your quest is complete." msgstr "" msgid "" "You have found the %{name}.\n" "Your quest is complete." msgstr "" msgid "Ultimate Artifact" msgstr "" msgid "" "The enemy is beaten.\n" "Your side has triumphed!" msgstr "" msgid "" "You have built up over %{count} gold in your treasury.\n" "All enemies bow before your wealth and power." msgstr "" msgid "Victory!" msgstr "" msgid "" "The enemy has captured %{name}!\n" "They are triumphant." msgstr "" msgid "" "The enemy has built up over %{count} gold in his treasury.\n" "You must bow done in defeat before his wealth and power." msgstr "" msgid "You have been eliminated from the game!!!" msgstr "Sei stato eliminato dal gioco!!!" msgid "" "You have lost the hero %{name}.\n" "Your quest is over." msgstr "" msgid "" "You have failed to complete your quest in time.\n" "All is lost." msgstr "" msgid "Defeat!" msgstr "" msgid "" "Base score: %{score}\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" msgid "Defeat all enemy heroes and capture all enemy towns and castles." msgstr "" "Sconfiggi tutti gli eroi nemici e cattura tutte le città e i castelli nemici." msgid "Run out of time. (Fail to win by a certain point.)" msgstr "" #, fuzzy msgid "You must defeat the enemy %{enemies}." msgid_plural "You must defeat the enemy alliance of %{enemies}." msgstr[0] "La tua alleanza sconfigge quella avversaria." msgstr[1] "La tua alleanza sconfigge quella avversaria." msgid "" "The alliance consisting of %{allies} and you must defeat the enemy " "%{enemies}." msgid_plural "" "The alliance consisting of %{allies} and you must defeat the enemy alliance " "of %{enemies}." msgstr[0] "" msgstr[1] "" msgid "Capture the castle '%{name}'." msgstr "" msgid "Capture the town '%{name}'." msgstr "" msgid "Defeat the hero '%{name}'." msgstr "" msgid "Find the ultimate artifact." msgstr "" msgid "Find the '%{name}' artifact." msgstr "" msgid "Accumulate %{count} gold." msgstr "" msgid "" ", or you may win by defeating all enemy heroes and capturing all enemy towns " "and castles." msgstr "" msgid "Lose the castle '%{name}'." msgstr "" msgid "Lose the town '%{name}'." msgstr "" msgid "Lose the hero: %{name}." msgstr "" msgid "Fail to win by the end of month %{month}, week %{week}, day %{day}." msgstr "" msgid "%{color} player has been vanquished!" msgstr "Giocatore %{color} è stato sconfitto!" msgid "Major Event!" msgstr "" msgid "Scenario:" msgstr "Scenario:" msgid "Game Difficulty:" msgstr "Difficoltà Partita:" msgid "Opponents:" msgstr "Avversari:" msgid "Class:" msgstr "Classe:" msgid "Rating %{rating}%" msgstr "Punteggio %{rating}%" msgid "Click here to select which scenario to play." msgstr "" msgid "Scenario" msgstr "Scenario" msgid "Game Difficulty" msgstr "Difficoltà Gioco" msgid "" "This lets you change the starting difficulty at which you will play. Higher " "difficulty levels start you of with fewer resources, and at the higher " "settings, give extra resources to the computer." msgstr "" msgid "Difficulty Rating" msgstr "Grado di Difficoltà" msgid "" "The difficulty rating reflects a combination of various settings for your " "game. This number will be applied to your final score." msgstr "" msgid "Click to accept these settings and start a new game." msgstr "" msgid "Click to return to the main menu." msgstr "" msgid "Astrologers proclaim the Month of the %{name}." msgstr "Gli Astrologi prevedono il mese di %{name}." msgid "Astrologers proclaim the Week of the %{name}." msgstr "Gli Astrologi prevedono la settimana di %{name}." msgid "After regular growth, the population of %{monster} is doubled!" msgstr "" "Dopo un periodo di crescita normale, la populazione di %{monster} si " "raddoppia!" msgid "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgid_plural "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgstr[0] "" "Dopo un periodo di crescita normale, la populazione di %{monster} incrementa " "di %{count} percento!" msgstr[1] "" "Dopo un periodo di crescita normale, la populazione di %{monster} incrementa " "di %{count} percento!" msgid "%{monster} growth +%{count}." msgstr "Crescita %{monster} +%{count}." msgid " All populations are halved." msgstr "Tutte le populazioni dimezzate." msgid " All dwellings increase population." msgstr "Cresce il numero di truppe disponibili di ogni dimora." msgid "New Month!" msgstr "" msgid "New Week!" msgstr "" msgid "Beware!" msgstr "" msgid "" "%{color} player, this is your last day to capture a town, or you will be " "banished from this land." msgstr "" msgid "" "%{color} player, you only have %{day} days left to capture a town, or you " "will be banished from this land." msgstr "" msgid "%{color} player's turn." msgstr "Turno di giocatore %{color}" msgid "" "%{color} player, you have lost your last town. If you do not conquer another " "town in the next week, you will be eliminated." msgstr "" msgid "" "%{color} player, your heroes abandon you, and you are banished from this " "land." msgstr "" msgid "Lord Kilburn" msgstr "" msgid "Tsabu" msgstr "" #, fuzzy msgid "Sir Galant" msgstr "Guardiano d'Aria" msgid "Thundax" msgstr "" msgid "Lord Haart" msgstr "" msgid "Ariel" msgstr "" msgid "Rebecca" msgstr "" msgid "Sandro" msgstr "" msgid "Crodo" msgstr "" msgid "Barock" msgstr "" msgid "Antoine" msgstr "" msgid "Astra" msgstr "" msgid "Agar" msgstr "" msgid "Vatawna" msgstr "" msgid "Vesper" msgstr "" msgid "Ambrose" msgstr "Ambrogio" msgid "Troyan" msgstr "" msgid "Jojosh" msgstr "" msgid "Wrathmont" msgstr "Flagello" msgid "Maximus" msgstr "" msgid "Next Hero" msgstr "Pross. Eroe" msgid "Select the next Hero." msgstr "Scegli il prossimo eroe." #, fuzzy msgid "Hero Movement" msgstr "Movimento Ombra" msgid "" "Start the Hero's movement along the current path or re-visit the object " "occupied by the Hero. Press and hold this button to reset the Hero's path." msgstr "" msgid "Kingdom Summary" msgstr "Sommario Regno" msgid "View a Summary of your Kingdom." msgstr "Mostra un sommario del tuo regno." msgid "Cast an adventure spell." msgstr "Lancia una magia avventura." msgid "End Turn" msgstr "Fine Turno" msgid "End your turn and left the computer take its turn." msgstr "Conclude il tuo turno e passa al/ai computer." msgid "Adventure Options" msgstr "Opzioni Avventura" msgid "Bring up the adventure options menu." msgstr "Mostra il menù con le opzioni dell'avventura." #, fuzzy msgid "" "Bring up the file options menu, allowing you to load, save, start a new game " "or quit." msgstr "Mostra il menù file che ti permette di caricare, salvare ecc.." msgid "Bring up the system options menu, allowing you to customize your game." msgstr "" "Mostra il menù di opzioni per il sistema per modificare l'esperienza del " "gioco." msgid "" "One or more heroes may still move, are you sure you want to end your turn?" msgstr "" "Sei sicuro di voler concludere il tuo turno? Alcuni eroi si possono ancora " "spostare." msgid "Are you sure you want to quit?" msgstr "Sei sicuro di voler chiudere il gioco?" msgid "Are you sure you want to restart? (Your current game will be lost.)" msgstr "" "Sei sicuro di voler cominciare da capo? (Il gioco in corso sarà perduto)" msgid "Are you sure you want to overwrite the save with this name?" msgstr "Sei sicuro di voler sovrascrivere il salvataggio di questo nome?" msgid "Game saved successfully." msgstr "Il salvataggio è andato a buon fine." msgid "There was an issue during saving." msgstr "Si è verificato un errore durante il salvataggio." msgid "" "Are you sure you want to load a new game? (Your current game will be lost.)" msgstr "" "Sei sicuro di voler cominciare una nuova partita? (La partita attuale andrà " "perduta.)" msgid "Try looking on land!!!" msgstr "Cerca sulla terraferma!" msgid "" "Searching for the Ultimate Artifact is fruitless. Your hero could not carry " "it even if he found it - all his artifact slots are full." msgstr "" msgid "Digging for artifacts requires a whole day, try again tomorrow." msgstr "" msgid "" "After spending many hours digging here, you have uncovered the %{artifact}." msgstr "" msgid "Congratulations!" msgstr "Complimenti!" msgid "Nothing here. Where could it be?" msgstr "" "Nulla qua.\n" "Dove potrebbe stare?" msgid "Try searching on clear ground." msgstr "Riprova su terreno sgombro." msgid "A miniature view of the known world. Left click to move viewing area." msgstr "" msgid "Month: %{month} Week: %{week}" msgstr "Mese: %{month} Settimana: %{week}" msgid "Day: %{day}" msgstr "Giorno: %{day}" msgid "" "You find a small\n" "quantity of %{resource}." msgstr "" "Trovi una piccola\n" "quantità di %{resource}." msgid "Status Window" msgstr "Finestra di Stato" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date." msgstr "" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date. Left click here to cycle through these windows." msgstr "" msgid "" "This lets you change player starting positions and colors. A particular " "color will always start in a particular location. Some positions may only be " "played by a computer player or only by a human player." msgstr "" msgid "" "This lets you change the class of a player. Classes are not always " "changeable. Depending on the scenario, a player may receive additional towns " "and/or heroes not of their primary alignment." msgstr "" msgid "Handicap" msgstr "" # msgid "" "This lets you change the handicap of a particular player. Only humans may be " "handicapped. Handicapped players start with fewer resources and earn 15 or " "30% fewer resources per turn for mild and severe handicaps, respectively." msgstr "" msgid "%{color} player" msgstr "Giocatore %{color}" msgid "No Handicap" msgstr "" msgid "No special restrictions on start resources and earning them per turn." msgstr "" msgid "Mild Handicap" msgstr "" # msgid "" "Mild handicapped players start with fewer resources and earn 15% fewer " "resources per turn." msgstr "" msgid "Severe Handicap" msgstr "" # msgid "" "Severe handicapped players start with fewer resources and earn 30% fewer " "resources per turn." msgstr "" msgid "View %{skill} Info" msgstr "Mostra dettagli su %{skill}" msgid "Keyboard|123" msgstr "" msgid "Keyboard|SPACE" msgstr "" msgid "Keyboard|ABC" msgstr "" msgid "Kingdom Income" msgstr "Rendita" #, fuzzy msgid "Kingdom Income per day." msgstr "Rendita" #, fuzzy msgid "For every lighthouse controlled, your ships will move further each day." msgstr "" "Hai catturato un faro, e ora i punti di movimento in nave si aumentano per " "tutti i tuoi eroi." msgid "English" msgstr "" msgid "French" msgstr "" msgid "Polish" msgstr "" msgid "German" msgstr "" msgid "Russian" msgstr "" msgid "Italian" msgstr "Italiano" msgid "Czech" msgstr "" msgid "Norwegian" msgstr "" msgid "Belarusian" msgstr "" #, fuzzy msgid "Bulgarian" msgstr "Barbaro" msgid "Ukrainian" msgstr "" msgid "Romanian" msgstr "" msgid "Spanish" msgstr "" msgid "Swedish" msgstr "" #, fuzzy msgid "Portuguese" msgstr "Furfanti" msgid "Turkish" msgstr "" msgid "Dutch" msgstr "" #, fuzzy msgid "Hungarian" msgstr "Barbaro" msgid "Danish" msgstr "" msgid "Slovak" msgstr "" #, fuzzy msgid "Vietnamese" msgstr "Mostra %{name}" msgid ", FPS: " msgstr "" msgid "%{object} robber" msgstr "" msgid "%{object} raided" msgstr "" msgid "Ector" msgstr "" msgid "Gwenneth" msgstr "Guendal" msgid "Sir Gallant" msgstr "" msgid "Tyro" msgstr "" msgid "Dimitry" msgstr "" msgid "Ruby" msgstr "" msgid "Crag Hack" msgstr "" msgid "Fineous" msgstr "Fineo" msgid "Jezebel" msgstr "Gezebel" msgid "Atlas" msgstr "" msgid "Ergon" msgstr "" msgid "Jaclyn" msgstr "" msgid "Gem" msgstr "" msgid "Natasha" msgstr "" msgid "Carlawn" msgstr "Teodolinda" msgid "Luna" msgstr "" msgid "Arie" msgstr "" msgid "Barok" msgstr "" msgid "Kastore" msgstr "" msgid "Falagar" msgstr "" msgid "Dawn" msgstr "" msgid "Flint" msgstr "" msgid "Halon" msgstr "" msgid "Myra" msgstr "" msgid "Myrini" msgstr "" msgid "Wilfrey" msgstr "" msgid "Mandigal" msgstr "" msgid "Sarakin" msgstr "" msgid "Charity" msgstr "" msgid "Darlana" msgstr "" msgid "Ranloo" msgstr "" msgid "Rialdo" msgstr "" msgid "Zam" msgstr "" msgid "Zom" msgstr "" msgid "Celia" msgstr "" msgid "Roxana" msgstr "" msgid "Lord Corlagon" msgstr "" msgid "Lord Halton" msgstr "" msgid "Sister Eliza" msgstr "Sorella Eliza" msgid "Brother Brax" msgstr "Frate Brax" msgid "Dainwin" msgstr "" msgid "Joseph" msgstr "" msgid "Mog" msgstr "" msgid "Solmyr" msgstr "" msgid "Ceallach" msgstr "" msgid "Drakonia" msgstr "" msgid "Elderian" msgstr "" msgid "Gallavant" msgstr "" msgid "Jarkonas" msgstr "" msgid "Martine" msgstr "" msgid " gives you maximum morale" msgstr "" msgid " gives you maximum luck" msgstr "" msgid "You cannot pick up this artifact, you already have a full load!" msgstr "" msgid "To cast spells, you must first buy a spell book for %{gold} gold." msgstr "" "Bisogna prima acquistare un libro magie per %{gold} pezzi d'oro per poter " "gettare magie." msgid "Unfortunately, you seem to be a little short of cash at the moment." msgstr "" msgid "Do you wish to buy one?" msgstr "Ne desideri uno?" msgid "%{count} / day" msgstr "%{count} al giorno" msgid "one" msgstr "1" msgid "two" msgstr "2" msgid "A whirlpool engulfs your ship. Some of your army has fallen overboard." msgstr "Un gorgo travolge la tua nave." msgid "Insulted by your refusal of their offer, the monsters attack!" msgstr "Rifiutandoli si sono offesi i mostri e ti attaccano!" msgid "" "The %{monster}, awed by the power of your forces, begin to scatter.\n" "Do you wish to pursue and engage them?" msgstr "" msgid "Ransacking an enemy camp, you discover a hidden cache of treasures." msgstr "" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with these resources, " "come back next week for more.\"" msgstr "" "Il custode del mulino dice: \"Mio signore, mi sono sforzato molto per " "procurarti queste risorse. Se torni la prossima settimana ne avrò ancora.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there are no resources currently available. Please try " "again next week.\"" msgstr "" "Il custode del mulino dice: \"Mio signore, perdonami, al momento non ho " "risorse. Ti chiedo di ritornare la settimana prossima.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with this gold, come " "back next week for more.\"" msgstr "" "Il custode del mulino dice: \"Mio signore, mi sono sforzato molto per " "procurarti quest'oro. Se ripassi la settimana prossima ne avrò ancora.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there is no gold currently available. Please try again " "next week.\"" msgstr "" "Il custode del mulino dice: \"Mio signore, perdonami, al momento sono senza " "oro. Ti chiedo di riprovare la settimana prossima.\"" msgid "" "You've found an abandoned lean-to.\n" "Poking about, you discover some resources hidden nearby." msgstr "" msgid "The lean-to is long abandoned. There is nothing of value here." msgstr "" msgid "" "You catch a leprechaun foolishly sleeping amidst a cluster of magic " "mushrooms.\n" "In exchange for his freedom, he guides you to a small pot filled with " "precious things." msgstr "" msgid "" "You've found a magic garden, the kind of place that leprechauns and faeries " "like to cavort in, but there is no one here today.\n" "Perhaps you should try again next week." msgstr "" msgid "You come upon the remains of an unfortunate adventurer." msgstr "Trovi le spoglie di un avventuriero sfortunato." msgid "Treasure" msgstr "Tesoro" msgid "Searching through the tattered clothing, you find the %{artifact}." msgstr "" msgid "Searching through the tattered clothing, you find nothing." msgstr "" msgid "" "You come across an old wagon left by a trader who didn't quite make it to " "safe terrain." msgstr "" msgid "Unfortunately, others have found it first, and the wagon is empty." msgstr "" msgid "Searching inside, you find the %{artifact}." msgstr "" msgid "Inside, you find some of the wagon's cargo still intact." msgstr "" msgid "You search through the flotsam, and find some wood and some gold." msgstr "Spulci i detriti galleggianti e scopri del legname e dell'oro." msgid "You search through the flotsam, and find some wood." msgstr "Spulci i detriti galleggianti e scopri del legname." msgid "You search through the flotsam, but find nothing." msgstr "Spulci i detriti galleggianti, però non scopri niente." msgid "Shrine of the 1st Circle" msgstr "" msgid "" "You come across a small shrine attended by a group of novice acolytes.\n" "In exchange for your protection, they agree to teach you a simple spell - " "'%{spell}'." msgstr "" msgid "Shrine of the 2nd Circle" msgstr "" msgid "" "You come across an ornate shrine attended by a group of rotund friars.\n" "In exchange for your protection, they agree to teach you a spell - " "'%{spell}'." msgstr "" msgid "Shrine of the 3rd Circle" msgstr "" msgid "" "You come across a lavish shrine attended by a group of high priests.\n" "In exchange for your protection, they agree to teach you a sophisticated " "spell - '%{spell}'." msgstr "" msgid "" "\n" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" msgid "" "\n" "Unfortunately, you already have knowledge of this spell, so there is nothing " "more for them to teach you." msgstr "" msgid "" "\n" "Unfortunately, you have no Magic Book to record the spell with." msgstr "" msgid "" "You approach the hut and observe a witch inside studying an ancient tome on " "%{skill}.\n" "\n" msgstr "" msgid "" "As you approach, she turns and focuses her one glass eye on you.\n" "\"You already know everything you deserve to learn!\" the witch screeches. " "\"NOW GET OUT OF MY HOUSE!\"" msgstr "" msgid "" "As you approach, she turns and speaks.\n" "\"You already know that which I would teach you. I can help you no further.\"" msgstr "" msgid "" "An ancient and immortal witch living in a hut with bird's legs for stilts " "teaches you %{skill} for her own inscrutable purposes." msgstr "" msgid "As you drink the sweet water, you gain luck for your next battle." msgstr "" msgid "You drink from the enchanted fountain, but nothing happens." msgstr "Prendi un sorso dalla fontana incantata, ma non accade niente." msgid "You enter the faerie ring, but nothing happens." msgstr "Visiti il cerchio fatato, ma non accade nulla." msgid "" "Upon entering the mystical faerie ring, your army gains luck for its next " "battle." msgstr "" "Visitando il mistico cerchio fatato, l'esercito riceve la fortuna buona per " "il combattimento prossimo." msgid "" "You've found an ancient and weathered stone idol.\n" "It is supposed to grant luck to visitors, but since the stars are already " "smiling upon you, it does nothing." msgstr "" msgid "" "You've found an ancient and weathered stone idol.\n" "Kissing it is supposed to be lucky, so you do. The stone is very cold to the " "touch." msgstr "" msgid "The mermaids silently entice you to return later and be blessed again." msgstr "" msgid "" "The magical, soothing beauty of the Mermaids reaches you and your crew.\n" "Just for a moment, you forget your worries and bask in the beauty of the " "moment.\n" "The mermaids charms bless you with increased luck for your next combat." msgstr "" msgid "" "You come upon the pyramid of a great and ancient king.\n" "You are tempted to search it for treasure, but all the old stories warn of " "fearful curses and undead guardians.\n" "Will you search?" msgstr "" msgid "" "Upon defeating the monsters, you decipher an ancient glyph on the wall, " "telling the secret of the spell - '" msgstr "" msgid "Unfortunately, you have no Magic Book to record the spell with." msgstr "" msgid "" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" msgid "" "You come upon the pyramid of a great and ancient king.\n" "Routine exploration reveals that the pyramid is completely empty." msgstr "" msgid "" "A drink at the well is supposed to restore your spell points, but you are " "already at maximum." msgstr "" msgid "A second drink at the well in one day will not help you." msgstr "" msgid "A drink from the well has restored your spell points to maximum." msgstr "" msgid "" "\"I'm sorry sir,\" The leader of the soldiers says, \"but you already know " "everything we have to teach.\"" msgstr "" msgid "The soldiers living in the fort teach you a few new defensive tricks." msgstr "" msgid "" "You've come upon a mercenary camp practicing their tactics. \"You're too " "advanced for us,\" the mercenary captain says. \"We can teach nothing more.\"" msgstr "" msgid "" "You've come upon a mercenary camp practicing their tactics. The mercenaries " "welcome you and your troops and invite you to train with them." msgstr "" msgid "\"Go 'way!\", the witch doctor barks, \"you know all I know.\"" msgstr "" msgid "" "An Orcish witch doctor living in the hut deepens your knowledge of magic by " "showing you how to cast stones, read portents, and decipher the intricacies " "of chicken entrails." msgstr "" msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, the Druids turn you away, indicating they have nothing " "new to teach you." msgstr "" msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, they teach you new ways to cast spells." msgstr "" msgid "" "You tentatively approach the burial ground of ancient warriors. Do you want " "to search the graves?" msgstr "" msgid "" "You spend several hours searching the graves and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" msgid "Upon defeating the Zombies you search the graves and find something!" msgstr "" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the shipwreck?" msgstr "" msgid "" "You spend several hours sifting through the debris and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" msgid "" "Upon defeating the Ghosts you sift through the debris and find something!" msgstr "" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the ship?" msgstr "" msgid "" "Upon defeating the Skeletons you sift through the debris and find something!" msgstr "" msgid "Your men spot a navigational buoy, confirming that you are on course." msgstr "" msgid "" "Your men spot a navigational buoy, confirming that you are on course and " "increasing their morale." msgstr "" msgid "" "The drink at the oasis is refreshing, but offers no further benefit. The " "oasis might help again if you fought a battle first." msgstr "" msgid "" "A drink at the oasis fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" msgid "" "The drink at the watering hole is refreshing, but offers no further benefit. " "The watering hole might help again if you fought a battle first." msgstr "" msgid "" "A drink at the watering hole fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" msgid "" "It doesn't help to pray twice before a battle. Come back after you've fought." msgstr "" msgid "A visit and a prayer at the temple raises the morale of your troops." msgstr "" msgid "" "An old Knight appears on the steps of the gazebo. \"I am sorry, my liege, I " "have taught you all I can.\"" msgstr "" msgid "" "An old Knight appears on the steps of the gazebo. \"My liege, I will teach " "you all that I know to aid you in your travels.\"" msgstr "" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he says, \"I would give you an artifact as a reward, but " "you're all full.\"" msgstr "" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he rewards you for your act of kindness by giving you the " "%{art}." msgstr "" msgid "A leprechaun offers you the %{art} for the small price of %{gold} Gold." msgstr "" msgid "" "A leprechaun offers you the %{art} for the small price of %{gold} Gold and " "%{count} %{res}." msgstr "" msgid "Do you wish to buy this artifact?" msgstr "Vuoi acquistare questo artefatto?" msgid "" "You try to pay the leprechaun, but realize that you can't afford it. The " "leprechaun stamps his foot and ignores you." msgstr "" msgid "" "Insulted by your refusal of his generous offer, the leprechaun stamps his " "foot and ignores you." msgstr "" msgid "You've found the artifact: " msgstr "Hai scoperto l'artefatto: " msgid "" "You've found the humble dwelling of a withered hermit. The hermit tells you " "that he is willing to give the %{art} to the first wise person he meets." msgstr "" msgid "" "You've come across the spartan quarters of a retired soldier. The soldier " "tells you that he is willing to pass on the %{art} to the first true leader " "he meets." msgstr "" msgid "" "You've encountered a strange person with a hat and an owl on it. He tells " "you that he is willing to give %{art} if you have %{skill}." msgstr "" msgid "" "You come upon an ancient artifact. As you reach for it, a pack of Rogues " "leap out of the brush to guard their stolen loot." msgstr "" msgid "" "Through a clearing you observe an ancient artifact. Unfortunately, it's " "guarded by a nearby %{monster}. Do you want to fight the %{monster} for the " "artifact?" msgstr "" msgid "Victorious, you take your prize, the %{art}." msgstr "Trionfante, cogli la ricompensa, il/la %{art}." msgid "" "Discretion is the better part of valor, and you decide to avoid this fight " "for today." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold pieces." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold and the %{art}." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it, " "only to find it empty." msgstr "" msgid "" "After scouring the area, you fall upon a hidden treasure cache. You may take " "the gold or distribute the gold to the peasants for experience. Do you wish " "to keep the gold?" msgstr "" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "%{gold} gold pieces." msgstr "" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "ancient artifact %{art}." msgstr "" msgid "" "You stumble upon a dented and tarnished lamp lodged deep in the earth. Do " "you wish to rub the lamp?" msgstr "" msgid "" "You have taken control of the local Alchemist shop. It will provide you with " "%{count} unit of Mercury per day." msgstr "" "Hai catturato la locale Bottega dell'Alchimista. Ti fornirà %{count} " "mercurio al giorno." msgid "" "You gain control of a sawmill. It will provide you with %{count} units of " "wood per day." msgstr "Hai catturato una segheria, e ti fornirà %{count} legname al giorno." msgid "" "You gain control of an ore mine. It will provide you with %{count} units of " "ore per day." msgstr "" "Hai catturato una miniera di ferro. Ti fornirà %{count} ferro al giorno." msgid "" "You gain control of a sulfur mine. It will provide you with %{count} unit of " "sulfur per day." msgstr "" "Hai catturato una miniera di zolfo. Ti fornirà %{count} zolfo al giorno." msgid "" "You gain control of a crystal mine. It will provide you with %{count} unit " "of crystal per day." msgstr "" "Hai catturato una miniera di cristallo. Ti fornirà %{count} cristallo al " "giorno." msgid "" "You gain control of a gem mine. It will provide you with %{count} unit of " "gems per day." msgstr "" "Hai catturato una miniera di gemme. Ti fornirà %{count} gemma al giorno." msgid "" "You gain control of a gold mine. It will provide you with %{count} gold per " "day." msgstr "Hai catturato una miniera d'oro. Ti fornirà %{count} oro al giorno." #, fuzzy msgid "" "The lighthouse is now under your control, and all of your ships will now " "move further each day." msgstr "" "Hai catturato un faro, e ora i punti di movimento in nave si aumentano per " "tutti i tuoi eroi." msgid "You gain control of a %{name}." msgstr "Catturi un %{name}." msgid "" "You come upon an abandoned gold mine. The mine appears to be haunted. Do you " "wish to enter?" msgstr "" msgid "You beat the Ghosts and are able to restore the mine to production." msgstr "Sconfiggi i Fantasmi e puoi rimettere in produzione la miniera." msgid "" "A group of %{monster} with a desire for greater glory wish to join you. Do " "you accept?" msgstr "" "Un gruppo di %{monster} desideroso di gloria vuole unirsi a te.\n" "Accetti?" msgid "As you approach the dwelling, you notice that there is no one here." msgstr "" msgid "" "You search the ruins, but the Medusas that used to live here are gone. " "Perhaps there will be more next week." msgstr "" msgid "" "You've found some Medusas living in the ruins. They are willing to join your " "army for a price. Do you want to recruit Medusas?" msgstr "" msgid "" "You've found a Sprite Tree City. Unfortunately, none of the Sprites living " "there wish to join an army. Maybe next week." msgstr "" msgid "" "Some of the Sprites living in the tree city are willing to join your army " "for a price. Do you want to recruit Sprites?" msgstr "" msgid "" "A colorful Rogues' wagon stands empty here. Perhaps more Rogues will be here " "later." msgstr "" msgid "" "Distant sounds of music and laughter draw you to a colorful wagon housing " "Rogues. Do you wish to have any Rogues join your army?" msgstr "" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. The " "tents are unoccupied. Perhaps more Nomads will be here later." msgstr "" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. Do you " "wish to have any Nomads join you during your travels?" msgstr "" msgid "The pit of mud bubbles for a minute and then lies still." msgstr "" msgid "" "As you approach the bubbling pit of mud, creatures begin to climb out and " "position themselves around it. In unison they say: \"Mother Earth would like " "to offer you a few of her troops. Do you want to recruit Earth Elementals?\"" msgstr "" msgid "You enter the structure of white stone pillars, and find nothing." msgstr "" msgid "" "White stone pillars support a roof that rises up to the sky. As you enter " "the structure, the dead air of the outside gives way to a whirling gust that " "almost pushes you back out. The air current materializes into a barely " "visible form. The creature asks, in what can only be described as a loud " "whisper: \"Why have you come? Are you here to call upon the forces of the " "air?\"" msgstr "" msgid "No Fire Elementals approach you from the lava pool." msgstr "" msgid "" "Beneath a structure that serves to hold in heat, Fire Elementals move about " "in a fiery pool of molten lava. A group of them approach you and offer their " "services. Would you like to recruit Fire Elementals?" msgstr "" msgid "A face forms in the water for a moment, and then is gone." msgstr "" msgid "" "Crystalline structures cast shadows over a small reflective pool of water. " "You peer into the pool, and a face that is not your own peers back. It asks: " "\"Would you like to call upon the powers of water?\"" msgstr "" msgid "This burial site is deathly still." msgstr "Il luogo di sepoltura è sinistramente calmo." msgid "" "Restless spirits of long dead warriors seeking their final resting place " "offer to join you in hopes of finding peace. Do you wish to recruit ghosts?" msgstr "" msgid "" "The City of the Dead is empty of life, and empty of unlife as well. Perhaps " "some undead will move in next week." msgstr "" msgid "" "Some Liches living here are willing to join your army for a price. Do you " "want to recruit Liches?" msgstr "" msgid "" "You've found the ruins of an ancient city, now inhabited solely by the " "undead. Will you search?" msgstr "" msgid "" "Some of the surviving Liches are impressed by your victory over their " "fellows, and offer to join you for a price. Do you want to recruit Liches?" msgstr "" msgid "" "You've found one of those bridges that Trolls are so fond of living under, " "but there are none here. Perhaps there will be some next week." msgstr "" msgid "" "Some Trolls living under a bridge are willing to join your army, but for a " "price. Do you want to recruit Trolls?" msgstr "" msgid "Trolls living under the bridge challenge you. Will you fight them?" msgstr "" msgid "" "A few Trolls remain, cowering under the bridge. They approach you and offer " "to join your forces as mercenaries. Do you want to buy any Trolls?" msgstr "" msgid "" "The Dragon city has no Dragons willing to join you this week. Perhaps a " "Dragon will become available next week." msgstr "" msgid "" "The Dragon city is willing to offer some Dragons for your army for a price. " "Do you wish to recruit Dragons?" msgstr "" msgid "" "You stand before the Dragon City, a place off-limits to mere humans. Do you " "wish to violate this rule and challenge the Dragons to a fight?" msgstr "" msgid "" "Having defeated the Dragon champions, the city's leaders agree to supply " "some Dragons to your army for a price. Do you wish to recruit Dragons?" msgstr "" msgid "From the observation tower, you are able to see distant lands." msgstr "" msgid "" "The spring only refills once a week, and someone's already been here this " "week." msgstr "" msgid "" "A drink at the spring is supposed to give you twice your normal spell " "points, but you are already at that level." msgstr "" msgid "" "A drink from the spring fills your blood with magic! You have twice your " "normal spell points in reserve." msgstr "" msgid "" "Recognizing you, the butler refuses to admit you. \"The master,\" he says, " "\"will not see the same student twice.\"" msgstr "" msgid "" "The butler admits you to see the master of the house. He trains you in the " "four skills a hero should know." msgstr "" msgid "" "The butler opens the door and looks you up and down. \"You are neither " "famous nor diplomatic enough to be admitted to see my master,\" he sniffs. " "\"Come back when you think yourself worthy.\"" msgstr "" msgid " and " msgstr " e i " msgid "" "All of the %{monsters} you have in your army have been trained by the battle " "masters of the fort. Your army now contains %{monsters2}." msgstr "" msgid "" "An unusual alliance of Ogres, Orcs, and Dwarves offer to train (upgrade) any " "such troops brought to them. Unfortunately, you have none with you." msgstr "" msgid "All of your %{monsters} have been upgraded into %{monsters2}." msgstr "" msgid "" "A blacksmith working at the foundry offers to convert all Pikemen and " "Swordsmen's weapons brought to him from iron to steel. He also says that he " "knows a process that will convert Iron Golems into Steel Golems. " "Unfortunately, you have none of these troops in your army, so he can't help " "you." msgstr "" msgid "" "The captain looks at you with surprise and says:\n" "\"You already have all the maps I know about. Let me fish in peace now.\"" msgstr "" "Il comandante ti guarda stupito e dice:\n" "\"Hai già tutte le mappe che conosco. Lasciami pescare in pace adesso.\"" msgid "" "A retired captain living on this refurbished fishing platform offers to sell " "you maps of the sea he made in his younger days for 1,000 gold. Do you wish " "to buy the maps?" msgstr "" msgid "" "The captain sighs. \"You don't have enough money, eh? You can't expect me " "to give my maps away for free!\"" msgstr "" msgid "" "You come upon an obelisk made from a type of stone you have never seen " "before. Staring at it intensely, the smooth surface suddenly changes to an " "inscription. The inscription is a piece of a lost ancient map. Quickly you " "copy down the piece and the inscription vanishes as abruptly as it appeared." msgstr "" msgid "You have already been to this obelisk." msgstr "Hai già visitato questo obelisco." msgid "" "Upon your approach, the tree opens its eyes in delight. \"It is good to see " "you, my student. I hope my teachings have helped you.\"" msgstr "" msgid "" "Upon your approach, the tree opens its eyes in delight. \"Ahh, an " "adventurer! Allow me to teach you a little of what I have learned over the " "ages.\"" msgstr "" msgid "Upon your approach, the tree opens its eyes in delight." msgstr "L'albero si apre gli occhi in delizia mentre ti avvicini." msgid "" "\"Ahh, an adventurer! I will be happy to teach you a little of what I have " "learned over the ages for a mere %{count} %{res}.\"" msgstr "" msgid "(Just bury it around my roots.)" msgstr "(Basta seppellirli intorno alle mie radici.)" msgid "Tears brim in the eyes of the tree." msgstr "Le lacrime traboccano dagli occhi dell'albero." msgid "\"I need %{count} %{res}.\"" msgstr "\"Ho bisgno di %{count} %{res},\"" msgid "it whispers. (sniff) \"Well, come back when you can pay me.\"" msgstr "" "sussurra mentre fiuta. \"D'accordo, vieni di nuovo qua quando potrai pagarmi." "\"" msgid "" "Nestled among the trees sits a blind seer. After you explain the intent of " "your journey, the seer activates his crystal ball, allowing you to see the " "strengths and weaknesses of your opponents." msgstr "" msgid "" "The entrance to the cave is dark, and a foul, sulfurous smell issues from " "the cave mouth. Will you enter?" msgstr "" msgid "Except for evidence of a terrible battle, the cave is empty." msgstr "" "Tranne che per le tracce di una terribile battaglia, la caverna è " "abbandonata." msgid "" "You find a powerful and grotesque Demon in the cave. \"Today,\" it rasps, " "\"you will fight and surely die. But I will give you a choice of deaths. You " "may fight me, or you may fight my servants. Do you prefer to fight my " "servants?\"" msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points." msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points and " "%{count} gold." msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and find the %{art} in the back of the cave." msgstr "" msgid "" "Seeing that you do not have %{count} gold, the demon slashes you with its " "claws, and the last thing you see is a red haze." msgstr "" msgid "" "The Demon leaps upon you and has its claws at your throat before you can " "even draw your sword. \"Your life is mine,\" it says. \"I will sell it back " "to you for %{count} gold.\"" msgstr "" msgid "" "Upon defeating the daemon's servants, you find a hidden cache with %{count} " "gold." msgstr "" msgid "" "As you enter the Alchemist's Tower, a hobbled, graying man in a brown cloak " "makes his way towards you." msgstr "" msgid "He checks your pack, and sees that you have 1 cursed item." msgid_plural "" "He checks your pack, and sees that you have %{count} cursed items." msgstr[0] "" msgstr[1] "" msgid "For %{gold} gold, the alchemist will remove it for you. Do you pay?" msgid_plural "" "For %{gold} gold, the alchemist will remove them for you. Do you pay?" msgstr[0] "" msgstr[1] "" msgid "" "After you consent to pay the requested amount of gold, the alchemist grabs " "the cursed artifact and throws it into his magical cauldron." msgid_plural "" "After you consent to pay the requested amount of gold, the alchemist grabs " "all cursed artifacts and throws them into his magical cauldron." msgstr[0] "" msgstr[1] "" msgid "" "You hear a voice from behind the locked door, \"You don't have enough gold " "to pay for my services.\"" msgstr "" msgid "" "You hear a voice from high above in the tower, \"Go away! I can't help you!\"" msgstr "" msgid "" "The head groom speaks to you, \"That is a fine looking horse you have. I am " "afraid we can give you no better, but the horses your cavalry are riding " "look to be of poor breeding stock. We have many trained war horses which " "would aid your riders greatly. I insist you take them.\"" msgstr "" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, he will " "grow tired in a week. You must also let me give better horses to your " "mounted soldiers, their horses look shoddy and weak.\"" msgstr "" msgid "" "The head groom approaches you and speaks, \"You already have a fine horse, " "and have no inexperienced cavalry which might make use of our trained war " "horses.\"" msgstr "" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, his " "endurance will wane with a lot of heavy riding, and you must return for a " "fresh mount in a week. We also have many fine war horses which could benefit " "mounted soldiers, but you have none we can help.\"" msgstr "" msgid "The Arena guards turn you away." msgstr "" msgid "" "You have your crew stop up their ears with wax before the sirens' eerie song " "has any chance of luring them to a watery grave." msgstr "" msgid "" "As the sirens sing their eerie song, your small, determined army manages to " "overcome the urge to dive headlong into the sea." msgstr "" msgid "" "An eerie wailing song emanates from the sirens perched upon the rocks. Many " "of your crew fall under its spell, and dive into the water where they drown. " "You are now wiser for the visit, and gain %{exp} experience." msgstr "" msgid "" "In a dazzling display of daring, you break into the local jail and free the " "hero imprisoned there, who, in return, pledges loyalty to your cause." msgstr "" msgid "" "You already have %{count} heroes, and regretfully must leave the prisoner in " "this jail to languish in agony for untold days." msgstr "" msgid "" "You enter a rickety hut and talk to the magician who lives there. He tells " "you of places near and far which may aid you in your journeys." msgstr "" msgid "This eye seems to be intently studying its surroundings." msgstr "" msgid "You come across a giant Sphinx. The Sphinx remains strangely quiet." msgstr "Trovi una Sfinge enorme, però stranamente resta in silenzio." msgid "" "\"I have a riddle for you,\" the Sphinx says. \"Answer correctly, and you " "shall be rewarded. Answer incorrectly, and you shall be eaten. Do you accept " "the challenge?\"" msgstr "" "\"Per te ho un enigma,\" dichiara la Sfinge. \"Rispondi corretto, e ti " "ricompenserò. Rispondi invece scorretto, e ti divorerò. Acconsenti le " "condizioni?\"" msgid "" "The Sphinx asks you the following riddle:\n" "\n" "'%{riddle}'\n" "\n" "Your answer?" msgstr "" "La Sfinge ti presenta questo enigma:\n" "\n" "'%{riddle}'\n" "\n" "La tua risposta?" msgid "" "\"You guessed incorrectly,\" the Sphinx says, smiling. The Sphinx swipes at " "you with a paw, knocking you to the ground. Another blow makes the world go " "black, and you know no more." msgstr "" "\"La tua risposta è scorretta,\" spiega la Sfinge, sorridendo. E così la " "Sfinge ti colpisce con la sua zampa, lanciando ti per terra. Un'altra " "zampata ancora ti fa svenire, e dopodiché non sai più cosa succede." #, fuzzy msgid "" "Looking somewhat disappointed, the Sphinx sighs. \"You've answered my riddle " "so here's your reward. Now begone.\"" msgstr "" "La Sfinge fa un soffio di delusione. Hai dato la risposta giusta e come ti " "ho premesso qua è il tuo premio. Ora sparisci da qui." msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "As you speak the magic word, the glowing barrier dissolves into nothingness." msgstr "" msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "You speak, and nothing happens." msgstr "" msgid "" "You enter the tent and see an old woman gazing into a magic gem. She looks " "up and says,\n" "\"In my travels, I have learned much in the way of arcane magic. A great " "oracle taught me his skill. I have the answer you seek.\"" msgstr "" #, fuzzy msgid "No spell book is present." msgstr "Nessuna magia disponibile." msgid "" "This spell costs %{mana} spell points. You have no spell points, so you " "cannot cast it." msgstr "" msgid "" "This spell costs %{mana} spell points. You only have %{point} spell points, " "so you cannot cast it." msgstr "" #, fuzzy msgid "The spell was not found." msgstr "Questa magia non influirà nessuno!" msgid "Only heroes can cast this spell." msgstr "" #, fuzzy msgid "This hero is not able to cast adventure spells." msgstr "Lancia una magia avventura." msgid "Your hero is too tired to cast this spell today. Try again tomorrow." msgstr "" msgid "This spell cannot be cast on a boat." msgstr "" msgid "This spell can only be cast near an ocean." msgstr "" msgid "" "There are no boats available and no ocean adjacent to the hero where this " "spell will work." msgstr "" #, fuzzy msgid "There are no boats available for this spell." msgstr "Lancia una magia avventura." #, fuzzy msgid "There is no ocean adjacent to the hero where this spell will work." msgstr "Lancia una magia avventura." msgid "" "You do not own any town or castle that is not currently occupied by a hero. " "This spell will have no effect." msgstr "" #, fuzzy msgid "This hero is already in a town, so this spell will have no effect." msgstr "Lancia una magia avventura." msgid "" "The nearest town is %{town}.\n" "\n" "This town is occupied by your hero %{hero}." msgstr "" msgid "This spell is already in effect." msgstr "" msgid "" "No opponent neither has nor can have any hero under their command anymore. " "Casting this spell will have no effect." msgstr "" msgid "" "No opponent has a hero under their command at this time. Casting this spell " "will have no effect." msgstr "" msgid "" "You must be within %{count} spaces of a monster for the Visions spell to " "work." msgstr "" msgid "" "You must be standing on the entrance to a mine (sawmills and alchemist labs " "do not count) to cast this spell." msgstr "" msgid "You must first defeat the ghosts guarding the mine to cast this spell." msgstr "" msgid "" "There are already at least as many elementals guarding the mine as this hero " "can generate. Casting this spell will have no effect." msgstr "" msgid "%{name} the %{race} (Level %{level})" msgstr "%{name}: %{race} (Livello %{level})" msgid "" "'Spread' combat formation spreads the hero's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" msgid "" "'Grouped' combat formation bunches the hero's army together in the center of " "their side of the battlefield." msgstr "" msgid "Are you sure you want to dismiss this Hero?" msgstr "Sei sicuro di voler congedare questo eroe?" msgid "View Experience Info" msgstr "Mostra dettagli sull'esperienza" msgid "View Spell Points Info" msgstr "Mostra dettagli sui Punti Magia" msgid "Set army combat formation to 'Spread'" msgstr "Imposta formazione sparsa all'esercito" msgid "Set army combat formation to 'Grouped'" msgstr "Imposta formazione chiusa all'esercito" msgid "Exit Hero Screen" msgstr "Lascia la schermata eroe" msgid "You cannot dismiss a hero in a castle" msgstr "" msgid "Dismissal of %{name} the %{race} is prohibited by scenario" msgstr "" msgid "Dismiss %{name} the %{race}" msgstr "Congeda %{name}: %{race}" msgid "Show previous hero" msgstr "Mostra eroe precedente" msgid "Show next hero" msgstr "Mostra eroe successivo" msgid "Blood Morale" msgstr "Morale Sangue" msgid "%{morale} Morale" msgstr "Morale %{morale}" msgid "%{luck} Luck" msgstr "Fortuna %{luck}" msgid "Current Luck Modifiers:" msgstr "Influenze sulla Fortuna:" msgid "Current Morale Modifiers:" msgstr "Influenze sul Morale:" msgid "Entire army is undead, so morale does not apply." msgstr "Tutto l'esercito è non-morto, quindi il morale non vale." msgid "" "Current experience %{exp1}.\n" " Next level %{exp2}." msgstr "" "Esperienza attuale: %{exp1}\n" "Livello prossimo: %{exp2}" msgid "Level %{level}" msgstr "Livello %{level}" #, fuzzy msgid "" "%{name} currently has %{point} spell points out of a maximum of %{max}. The " "maximum number of spell points is 10 times the hero's knowledge. It is " "occasionally possible for the hero to have more than their maximum spell " "points via special events." msgstr "" "Attualmente %{name} ha %{point} sul totale di %{max} punti di magia. I punti " "totali di magia sono dieci volte il sapere dell'eroe. Vari avvenimenti " "possono influire al numero attuale, aumentandolo anche sopra il numero " "previsto dal sapere." msgid "%{name1} meets %{name2}" msgstr "%{name1} incontra %{name2}" msgid "Enemy heroes are now fully identifiable." msgstr "" msgid "Identify Hero" msgstr "Riconosci Eroi" msgid "Select town to port to." msgstr "" msgid "Town Portal" msgstr "Alla Porta" msgid "The creatures are willing to join us!" msgstr "" msgid "All the creatures will join us..." msgstr "" msgid "The creature will join us..." msgid_plural "%{count} of the creatures will join us..." msgstr[0] "" msgstr[1] "" msgid "" "\n" " for a fee of %{gold} gold." msgstr "" msgid "These weak creatures will surely flee before us." msgstr "" msgid "I fear these creatures are in the mood for a fight." msgstr "" msgid "The hero's attack skill is a bonus added to each unit's attack skill." msgstr "" msgid "The hero's defense skill is a bonus added to each unit's defense skill." msgstr "" msgid "The hero's spell power determines the duration or power of a spell." msgstr "" msgid "" " The hero's knowledge determines how many spell points the hero may have. " "Under normal circumstances, a hero is limited to 10 spell points per level " "of knowledge." msgstr "" msgid "Current Modifiers:" msgstr "Modifiche Attuali:" msgid "skill|Basic" msgstr "Normale" msgid "skill|Advanced" msgstr "Avanzato/a" msgid "skill|Expert" msgstr "Esperto/a" msgid "Pathfinding" msgstr "Orientam." msgid "Archery" msgstr "Tiro col arco" msgid "Logistics" msgstr "Logistica" msgid "Scouting" msgstr "Esplorazione" msgid "Diplomacy" msgstr "Diplomazia" msgid "Navigation" msgstr "Navigazione" msgid "Leadership" msgstr "Carisma" msgid "Wisdom" msgstr "Saggezza" msgid "Mysticism" msgstr "Misticismo" msgid "Ballistics" msgstr "Balistica" msgid "Eagle Eye" msgstr "Vista d'aquila" msgid "Necromancy" msgstr "Negromanz" msgid "Estates" msgstr "Investimenti" msgid "Advanced Archery" msgstr "Tiro con L'Arco Avanzato" msgid "Advanced Pathfinding" msgstr "Orientamento Avanzato" msgid "Basic Archery" msgstr "Tiro con l'Arco Normale" msgid "Basic Pathfinding" msgstr "Orientamento Normale" msgid "Expert Pathfinding" msgstr "Orientamento Esperto" msgid "Advanced Logistics" msgstr "Logistica Avanzata" msgid "Basic Logistics" msgstr "Logistica Normale" msgid "Basic Scouting" msgstr "Esplorazione Normale" msgid "Expert Archery" msgstr "Tiro con l'Arco Esperto" msgid "Expert Logistics" msgstr "Logistica Esperta" msgid "Advanced Diplomacy" msgstr "Diplomazia Avanzata" msgid "Advanced Scouting" msgstr "Esplorazione Avanzata" msgid "Basic Diplomacy" msgstr "Diplomazia Normale" msgid "Expert Diplomacy" msgstr "Diplomazia Esperta" msgid "Expert Scouting" msgstr "Esplorazione Esperta" msgid "Advanced Leadership" msgstr "Carisma Avanzato" msgid "Advanced Navigation" msgstr "Navigazione Avanzata" msgid "Basic Leadership" msgstr "Carisma Normale" msgid "Basic Navigation" msgstr "Navigazione Normale" msgid "Expert Navigation" msgstr "Navigazione Esperta" msgid "Advanced Wisdom" msgstr "Sagezza Avanzata" msgid "Basic Mysticism" msgstr "Misticismo Normale" msgid "Basic Wisdom" msgstr "Sagezza Normale" msgid "Expert Leadership" msgstr "Carisma Esperto" msgid "Expert Wisdom" msgstr "Sagezza Esperta" msgid "Advanced Luck" msgstr "Fortuna Avanzata" msgid "Advanced Mysticism" msgstr "Misticismo Avanzato" msgid "Basic Luck" msgstr "Fortuna Normale" msgid "Expert Luck" msgstr "Fortuna Esperta" msgid "Expert Mysticism" msgstr "Misticismo Esperto" msgid "Advanced Ballistics" msgstr "Balistica Avanzata" msgid "Advanced Eagle Eye" msgstr "Vista Avanzata" msgid "Basic Ballistics" msgstr "Balistica Normale" msgid "Basic Eagle Eye" msgstr "Vista Normale" msgid "Expert Ballistics" msgstr "Balistica Esperta" msgid "Advanced Necromancy" msgstr "Negromanzia Avanzata" msgid "Basic Estates" msgstr "Investimenti Normali" msgid "Basic Necromancy" msgstr "Negromanzia Normale" msgid "Expert Eagle Eye" msgstr "Vista Esperta" msgid "Expert Necromancy" msgstr "Negromanzia Esperta" msgid "Advanced Estates" msgstr "Investimenti Avanzati" msgid "Expert Estates" msgstr "Investimenti Esperti" msgid "" "%{skill} reduces the movement penalty for rough terrain by %{count} percent." msgstr "" msgid "%{skill} eliminates the movement penalty for rough terrain." msgstr "" msgid "" "%{skill} increases the damage done by the hero's range attacking creatures " "by %{count} percent." msgstr "" msgid "%{skill} increases the hero's movement points by %{count} percent." msgstr "" msgid "%{skill} increases the hero's viewable area by one square." msgid_plural "%{skill} increases the hero's viewable area by %{count} squares." msgstr[0] "" msgstr[1] "" msgid "" "%{skill} allows the hero to negotiate with monsters who are weaker than " "their army, and reduces the cost of surrender." msgstr "" msgid "" "Approximately %{count} percent of the creatures may offer to join the hero." msgstr "" msgid "All of the creatures may offer to join the hero." msgstr "" #, fuzzy msgid "" "The cost of surrender is reduced to %{percent} percent of the total cost of " "troops in the army." msgstr "%{name} diminuisce di %{count} percento il costo della resa." msgid "" "%{skill} increases the hero's movement points over water by %{count} percent." msgstr "" #, fuzzy msgid "%{skill} increases the hero's troops morale by %{count}." msgstr "%{name} incrementa di %{count} punti il sapere del tuo eroe." msgid "%{skill} allows the hero to learn third level spells." msgstr "" msgid "%{skill} allows the hero to learn fourth level spells." msgstr "" msgid "%{skill} allows the hero to learn fifth level spells." msgstr "" msgid "%{skill} regenerates one additional spell point per day to the hero." msgid_plural "" "%{skill} regenerates %{count} additional spell points per day to the hero." msgstr[0] "" msgstr[1] "" #, fuzzy msgid "%{skill} increases the hero's luck by %{count}." msgstr "%{name} ti dà più fortuna in combattimento." msgid "" "%{skill} gives the hero's catapult shots a greater chance to hit and do " "damage to castle walls." msgstr "" msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot has a " "greater chance to hit and do damage to castle walls." msgstr "" msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot " "automatically destroys any wall, except a fortified wall in a Knight castle." msgstr "" msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 1st or " "2nd level spell that was cast by an enemy during combat." msgstr "" msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 3rd " "level spell (or below) that was cast by an enemy during combat." msgstr "" msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 4th " "level spell (or below) that was cast by an enemy during combat." msgstr "" msgid "" "%{skill} allows %{count} percent of the creatures killed in combat to be " "brought back from the dead as Skeletons." msgstr "" #, fuzzy msgid "" "The hero produces %{count} gold pieces per day as tax revenue from estates." msgstr "%{name} contiene le tue magie." msgid "Blue" msgstr "Blu" msgid "Green" msgstr "Verde" msgid "Red" msgstr "Rosso" msgid "Yellow" msgstr "Giallo" msgid "Orange" msgstr "Arancio" msgid "Purple" msgstr "Viola" msgid "barrier|Aqua" msgstr "Acqua" msgid "barrier|Blue" msgstr "Blu" msgid "barrier|Brown" msgstr "Marrone" msgid "barrier|Gold" msgstr "Dorata" msgid "barrier|Green" msgstr "Verde" msgid "barrier|Orange" msgstr "Arancia" msgid "barrier|Purple" msgstr "Viola" msgid "barrier|Red" msgstr "Rossa" msgid "tent|Aqua" msgstr "Acqua" msgid "tent|Blue" msgstr "Blu" msgid "tent|Brown" msgstr "Marrone" msgid "tent|Gold" msgstr "Dorata" msgid "tent|Green" msgstr "Verde" msgid "tent|Orange" msgstr "Arancia" msgid "tent|Purple" msgstr "Viola" msgid "tent|Red" msgstr "Rossa" #, fuzzy msgid "Experience" msgstr "Sperimentale" msgid "" "Experience allows your heroes to go up levels, increasing their primary and " "secondary skills." msgstr "" msgid "Hero/Stats" msgstr "Dati Eroe" msgid "Skills" msgstr "Doti" msgid "Town/Castle" msgstr "Città/Castello" msgid "Garrison" msgstr "Guarnigione" msgid "Gold Per Day:" msgstr "Oro al giorno:" msgid "View Heroes." msgstr "Mostra Eroi." msgid "Towns/Castles" msgstr "Città/Castelli" msgid "View Towns and Castles." msgstr "Mostra Città e Castelli." msgid "luck|Cursed" msgstr "Maledetta" msgid "luck|Awful" msgstr "Bassissima" msgid "luck|Bad" msgstr "Scarsa" msgid "luck|Normal" msgstr "Normale" msgid "luck|Good" msgstr "Buona" msgid "luck|Great" msgstr "Alta" msgid "luck|Irish" msgstr "Irlandese" #, fuzzy msgid "" "Bad luck sometimes falls on the hero's units in combat, causing their " "attacks to only do half damage." msgstr "" "La fortuna cattiva potrebbe accadere al tuo esercito in combattimento, " "facendo sì che i danni che fanno si dimezzano." #, fuzzy msgid "" "Neutral luck means the hero's units will never get lucky or unlucky attacks " "on the enemy." msgstr "" "La fortuna normale non dà la possibiltà di fare ne colpi fortunati ne " "sfortunati al tuo esercito." #, fuzzy msgid "" "Good luck sometimes lets the hero's units get lucky attacks (double " "strength) in combat." msgstr "La buona fortuna potrebbe radoppiare i danni che fanno le tue forze." msgid "morale|Treason" msgstr "Tradimento" msgid "morale|Awful" msgstr "Bassissimo" msgid "morale|Poor" msgstr "Basso" msgid "morale|Normal" msgstr "Normale" msgid "morale|Good" msgstr "Buono" msgid "morale|Great" msgstr "Alto" msgid "morale|Blood!" msgstr "Sangue!" #, fuzzy msgid "Bad morale may cause the hero's units to freeze in combat." msgstr "" "Il morale negativo potrebbe far perdere il turno alle tue unità in battagla." #, fuzzy msgid "" "Neutral morale means the hero's units will never be blessed with extra " "attacks or freeze in combat." msgstr "" "Il morale normale vuol dire che il tuo esercito non avrà mai la fortuna di " "avere turni in più mai il vantaggio di attacchi o paralisi supplementari in " "battaglia." #, fuzzy msgid "Good morale may give the hero's units extra attacks in combat." msgstr "" "Il morale positivo potrebbe dare turni in più alle tue unità in battaglia." msgid "Knight" msgstr "Cavaliere" msgid "Barbarian" msgstr "Barbaro" msgid "Sorceress" msgstr "Strega" msgid "Warlock" msgstr "Guerriero" msgid "Wizard" msgstr "Mago" msgid "Necromancer" msgstr "Negromante" msgid "Multi" msgstr "Multiplo" msgid "doubleLined|Knight" msgstr "Cavaliere" msgid "doubleLined|Barbarian" msgstr "Barbaro" msgid "doubleLined|Sorceress" msgstr "Strega" msgid "doubleLined|Warlock" msgstr "Guerriero" msgid "doubleLined|Wizard" msgstr "Mago" msgid "" "doubleLined|Necro-\n" "mancer" msgstr "" "Negro-\n" "mante" msgid "doubleLinedRace|Multi" msgstr "Multiplo" msgid "doubleLinedRace|Random" msgstr "Casuale" msgid "speed|Standing" msgstr "Fermo" msgid "speed|Crawling" msgstr "Lumaca" msgid "speed|Very Slow" msgstr "Lentissima" msgid "speed|Slow" msgstr "Lenta" msgid "speed|Average" msgstr "Media" msgid "speed|Fast" msgstr "Veloce" msgid "speed|Very Fast" msgstr "Velocissima" msgid "speed|Ultra Fast" msgstr "Ultraveloce" msgid "speed|Blazing" msgstr "Folgore" msgid "speed|Instant" msgstr "Istantanea" msgid "week|Squirrel" msgstr "Scoiattolo" msgid "week|Rabbit" msgstr "Coniglio" msgid "week|Gopher" msgstr "Topo" msgid "week|Badger" msgstr "Tasso" #, fuzzy msgid "week|Rat" msgstr "Coniglio" msgid "week|Eagle" msgstr "Aquila" msgid "week|Weasel" msgstr "Donnola" msgid "week|Raven" msgstr "Corvo" msgid "week|Mongoose" msgstr "Mangusta" #, fuzzy msgid "week|Dog" msgstr "Aquila" msgid "week|Aardvark" msgstr "Formichiere" msgid "week|Lizard" msgstr "Lucertola" msgid "week|Tortoise" msgstr "Testuggine" msgid "week|Hedgehog" msgstr "Riccio" msgid "week|Condor" msgstr "Condor" msgid "week|Ant" msgstr "Formica" msgid "week|Grasshopper" msgstr "Cavalletta" msgid "week|Dragonfly" msgstr "Libellula" msgid "week|Spider" msgstr "Ragno" msgid "week|Butterfly" msgstr "Farfalla" msgid "week|Bumblebee" msgstr "Bombo" msgid "week|Locust" msgstr "Locusta" msgid "week|Earthworm" msgstr "Verme" msgid "week|Hornet" msgstr "Calabrone" msgid "week|Beetle" msgstr "Scarabeo" msgid "week|PLAGUE" msgstr "PESTE" #, fuzzy msgid "week|Unnamed" msgstr "Aquila" msgid "Desert" msgstr "Deserto" msgid "Snow" msgstr "Neve" msgid "Wasteland" msgstr "Terra sterile" msgid "Beach" msgstr "Spiaggia" msgid "Lava" msgstr "Lava" msgid "Dirt" msgstr "Terra" msgid "Grass" msgstr "Prato" msgid "Ocean" msgstr "Oceano" msgid "maps|Small" msgstr "Piccola" msgid "maps|Medium" msgstr "Media" msgid "maps|Large" msgstr "Grande" msgid "maps|Extra Large" msgstr "XL" msgid "maps|Custom Size" msgstr "Dimensione personalizzata" msgid "Ore Mine" msgstr "Miniera di Ferro" msgid "Sulfur Mine" msgstr "Miniera di Zolfo" msgid "Crystal Mine" msgstr "Miniera di Cristallo" msgid "Gems Mine" msgstr "Miniera di Gemme" msgid "Gold Mine" msgstr "Miniera d'Oro" msgid "Mine" msgstr "Miniera" msgid "Burma shave." msgstr "Schiuma da Burba." msgid "Next sign 50 miles." msgstr "Prossimo cartello tra 50 miglia." msgid "See Rock City." msgstr "Vai a Hard Rock." msgid "This space for rent." msgstr "Affittasi." msgid "No object" msgstr "Nessun oggetto" msgid "Alchemist Lab" msgstr "Laboratorio dell'Alchimista" msgid "Sign" msgstr "Cartello" msgid "Buoy" msgstr "Boa" msgid "Skeleton" msgstr "Scheletro" msgid "Daemon Cave" msgstr "Caverna del Demone" msgid "Treasure Chest" msgstr "Forziere del Tesoro" msgid "Faerie Ring" msgstr "Cerchio Fatato" msgid "Campfire" msgstr "Fuoco da Campo" msgid "Fountain" msgstr "Fontana" msgid "Gazebo" msgstr "Gazebo" msgid "Genie Lamp" msgstr "Lampa Antica" msgid "Archer's House" msgstr "Casa degli Arcieri" msgid "Goblin Hut" msgstr "Capanna dei Goblin" msgid "Dwarf Cottage" msgstr "Casetta dei Nani" msgid "Peasant Hut" msgstr "Capanna dei Burini" msgid "Stables" msgstr "Stalla" msgid "Alchemist's Tower" msgstr "Torre dell'Alchimista" msgid "Event" msgstr "Evento" msgid "Dragon City" msgstr "Dragon City" #, fuzzy msgid "Lighthouse" msgid_plural "Lighthouses" msgstr[0] "Faro" msgstr[1] "Faro" msgid "Water Wheel" msgid_plural "Water Wheels" msgstr[0] "Mulino ad Acqua" msgstr[1] "Mulini ad Acqua" msgid "Monster" msgstr "Mostro" msgid "Obelisk" msgstr "Obelisco" msgid "Oasis" msgstr "Oasi" msgid "Resource" msgstr "Risorso" msgid "Sawmill" msgstr "Segheria" msgid "Oracle" msgstr "Oracolo" msgid "Shrine of the First Circle" msgstr "Tempio del Primo Cerchio" msgid "Shipwreck" msgstr "Naufragio" msgid "Sea Chest" msgstr "Forziere Marino" msgid "Desert Tent" msgstr "Tenda da Deserto" msgid "Stone Liths" msgstr "Monoliti" msgid "Wagon Camp" msgstr "Campo Carri" msgid "Hut of the Magi" msgstr "Capanna del Mago" msgid "Whirlpool" msgstr "Gorgo" msgid "Windmill" msgid_plural "Windmills" msgstr[0] "Mulino a Vento" msgstr[1] "Mulini a Vento" msgid "Artifact" msgstr "Artefatto" msgid "Mermaid" msgstr "Sirenetta" msgid "Boat" msgstr "Nave" msgid "Random Ultimate Artifact" msgstr "Artefatto estremo casuale" msgid "Random Artifact" msgstr "Artefatto casuale" msgid "Random Resource" msgstr "Risorso casuale" msgid "Random Monster" msgstr "Mostro casuale" msgid "Random Town" msgstr "Città casuale" msgid "Random Castle" msgstr "Castello casuale" msgid "Eye of the Magi" msgstr "Occhio del Mago" msgid "Random Monster - weak" msgstr "Mostro casuale - debole" msgid "Random Monster - medium" msgstr "Mostro casuale - medio" msgid "Random Monster - strong" msgstr "Mostro casuale - forte" msgid "Random Monster - very strong" msgstr "Mostra casuale - molto forte" #, fuzzy msgid "Hero" msgstr "Eroi" msgid "Nothing Special" msgstr "Niente particolare" msgid "Mossy Rock" msgstr "" msgid "Watch Tower" msgstr "Torre di Guardia" msgid "Tree House" msgstr "Casa sugli Alberi" msgid "Tree City" msgstr "Città degli Alberi" msgid "Ruins" msgstr "Rovine" msgid "Fort" msgstr "Forte" msgid "Abandoned Mine" msgstr "Miniera Abbandonata" msgid "Sirens" msgstr "Sirene" msgid "Standing Stones" msgstr "Massi Eretti" msgid "Idol" msgstr "Idolo" msgid "Tree of Knowledge" msgstr "Albero del Sapere" msgid "Witch Doctor's Hut" msgstr "Capanna dello Sciamano" msgid "Temple" msgstr "Tempio" msgid "Hill Fort" msgstr "Forte in Collina" msgid "Halfling Hole" msgstr "Tana dei Frombolieri" msgid "Mercenary Camp" msgstr "Campo Mercenari" msgid "Shrine of the Second Circle" msgstr "Tempio del Secondo Cerchio" msgid "Shrine of the Third Circle" msgstr "Tempio del Terzo Cerchio" msgid "City of the Dead" msgstr "Città dei Morti" msgid "Sphinx" msgstr "Sfinge" msgid "Wagon" msgstr "Carro" msgid "Tar Pit" msgstr "Pozzo di Catrame" msgid "Artesian Spring" msgstr "PozzoAartesiano" msgid "Troll Bridge" msgstr "Ponte dei Troll" msgid "Watering Hole" msgstr "Abbeveratoio" msgid "Witch's Hut" msgstr "Capanna della Strega" msgid "Xanadu" msgstr "Xanadu" msgid "Lean-To" msgstr "Casotto" msgid "Magellan's Maps" msgstr "Mappe di Magellano" msgid "Flotsam" msgstr "Rottami" msgid "Derelict Ship" msgstr "Relitto Navale" msgid "Shipwreck Survivor" msgstr "Superstite Naufragio" msgid "Bottle" msgstr "Bottiglia" msgid "Magic Well" msgstr "Pozzo Magico" msgid "Magic Garden" msgid_plural "Magic Gardens" msgstr[0] "Giardino Magico" msgstr[1] "Giardini Magici" msgid "Observation Tower" msgstr "Osservatorio" msgid "Freeman's Foundry" msgstr "Fucina dei Liberti" msgid "Reefs" msgstr "Barriera Corallina" msgid "Volcano" msgstr "Vulcano" msgid "Flowers" msgstr "Fiori" msgid "Rock" msgstr "Roccia" msgid "Water Lake" msgstr "Lago" msgid "Mandrake" msgstr "Mandragola" msgid "Dead Tree" msgstr "Albero Morto" msgid "Stump" msgstr "Troncone" msgid "Crater" msgstr "Cratere" msgid "Cactus" msgstr "Cactus" msgid "Mound" msgstr "Terrapieno" msgid "Dune" msgstr "Duna" msgid "Lava Pool" msgstr "Pozza di Lava" msgid "Shrub" msgstr "Arbusto" msgid "Barrow Mounds" msgstr "Tumulo" msgid "Random Artifact - Treasure" msgstr "Artefatto casuale - tesoro" msgid "Random Artifact - Minor" msgstr "Artefatto casuale - minore" msgid "Random Artifact - Major" msgstr "Artefatto casuale - maggiore" msgid "Barrier" msgstr "Barriera" msgid "Traveller's Tent" msgstr "Tenda del Viaggiatore" msgid "Jail" msgstr "Galera" msgid "Fire Summoning Altar" msgstr "Altare Convocante del Fuoco" msgid "Air Summoning Altar" msgstr "Altare Convocante d'Aria" msgid "Earth Summoning Altar" msgstr "Altare Convocante di Terra" msgid "Water Summoning Altar" msgstr "Altare Convocante dell'Acqua" msgid "Unknown Monster" msgstr "Mostro sconosciuto" msgid "Unknown Monsters" msgstr "Mostri sconosciuti" msgid "Peasant" msgstr "Burino" msgid "Peasants" msgstr "Burini" msgid "Archer" msgstr "Arciere" msgid "Archers" msgstr "Arcieri" msgid "Ranger" msgstr "Cacciatore" msgid "Rangers" msgstr "Cacciatori" msgid "Pikeman" msgstr "Alabardiere" msgid "Pikemen" msgstr "Alabardieri" msgid "Veteran Pikeman" msgstr "Alabardiere Esperto" msgid "Veteran Pikemen" msgstr "Alabardieri Esperti" msgid "Swordsman" msgstr "Spadaccino" msgid "Swordsmen" msgstr "Spadaccini" msgid "Master Swordsman" msgstr "Maestro Spadaccino" msgid "Master Swordsmen" msgstr "Maestri Spadaccini" msgid "Cavalries" msgstr "Cavalleria" msgid "Cavalry" msgstr "Cavallerie" msgid "Champion" msgstr "Campione" msgid "Champions" msgstr "Campioni" msgid "Paladin" msgstr "Paladino" msgid "Paladins" msgstr "Paladini" msgid "Crusader" msgstr "Crociato" msgid "Crusaders" msgstr "Crociati" msgid "Goblin" msgstr "Goblin" msgid "Goblins" msgstr "Goblins" msgid "Orc" msgstr "Orchino" msgid "Orcs" msgstr "Orchini" msgid "Orc Chief" msgstr "Capo Orchino" msgid "Orc Chiefs" msgstr "Capo Orchini" msgid "Wolf" msgstr "Lupo" msgid "Wolves" msgstr "Lupi" msgid "Ogre" msgstr "Orco" msgid "Ogres" msgstr "Orchi" msgid "Ogre Lord" msgstr "Lord Orco" msgid "Ogre Lords" msgstr "Lord Orchi" msgid "Troll" msgstr "Troll" msgid "Trolls" msgstr "Troll" msgid "War Troll" msgstr "Troll da Guerra" msgid "War Trolls" msgstr "Troll da Guerra" msgid "Cyclopes" msgstr "Ciclope" msgid "Cyclops" msgstr "Ciclopi" msgid "Sprite" msgstr "Spiritello" msgid "Sprites" msgstr "Spiritelli" msgid "Dwarf" msgstr "Nano" msgid "Dwarves" msgstr "Nani" msgid "Battle Dwarf" msgstr "Nano da Battaglia" msgid "Battle Dwarves" msgstr "Nani da Battaglia" msgid "Elf" msgstr "Elfo" msgid "Elves" msgstr "Elfi" msgid "Grand Elf" msgstr "Grande Elfo" msgid "Grand Elves" msgstr "Grandi Elfi" msgid "Druid" msgstr "Druido" msgid "Druids" msgstr "Druidi" msgid "Greater Druid" msgstr "Druido Maggiore" msgid "Greater Druids" msgstr "Druidi Maggiori" msgid "Unicorn" msgstr "Unicorno" msgid "Unicorns" msgstr "Unicorni" msgid "Phoenix" msgstr "Fenice" msgid "Phoenixes" msgstr "Fenici" msgid "Centaur" msgstr "Centauro" msgid "Centaurs" msgstr "Centauri" msgid "Gargoyle" msgstr "Gargolla" msgid "Gargoyles" msgstr "Gargolle" msgid "Griffin" msgstr "Grifone" msgid "Griffins" msgstr "Grifoni" msgid "Minotaur" msgstr "Minotauro" msgid "Minotaurs" msgstr "Minotauri" msgid "Minotaur King" msgstr "Minotauro Re" msgid "Minotaur Kings" msgstr "Minotauri Re" msgid "Hydra" msgstr "Idra" msgid "Hydras" msgstr "Idre" msgid "Green Dragon" msgstr "Drago Verde" msgid "Green Dragons" msgstr "Draghi Verdi" msgid "Red Dragon" msgstr "Drago Rosso" msgid "Red Dragons" msgstr "Draghi Rossi" msgid "Black Dragon" msgstr "Drago Nero" msgid "Black Dragons" msgstr "Draghi Neri" msgid "Halfling" msgstr "Fromboliere" msgid "Halflings" msgstr "Frombolieri" msgid "Boar" msgstr "Cinghiale" msgid "Boars" msgstr "Cinghiali" msgid "Iron Golem" msgstr "Golem del Ferro" msgid "Iron Golems" msgstr "Golem del Ferro" msgid "Steel Golem" msgstr "Golem dell'Acciaio" msgid "Steel Golems" msgstr "Golem dell'Acciaio" # Roc? msgid "Roc" msgstr "Aquila" msgid "Rocs" msgstr "Aquile" msgid "Mage" msgstr "Mago" msgid "Magi" msgstr "Maghi" msgid "Archmage" msgstr "Arcimago" msgid "Archmagi" msgstr "Arcimaghi" msgid "Giant" msgstr "Gigante" msgid "Giants" msgstr "Giganti" msgid "Titan" msgstr "Titano" msgid "Titans" msgstr "Titani" msgid "Skeletons" msgstr "Scheletri" msgid "Zombie" msgstr "Zombi" msgid "Zombies" msgstr "Zombi" msgid "Mutant Zombie" msgstr "Zombi Mutante" msgid "Mutant Zombies" msgstr "Zombi Mutanti" msgid "Mummies" msgstr "Mummie" msgid "Mummy" msgstr "Mummia" msgid "Royal Mummies" msgstr "Mummie Reali" msgid "Royal Mummy" msgstr "Mummia Reale" msgid "Vampire" msgstr "Vampiro" msgid "Vampires" msgstr "Vampiri" msgid "Vampire Lord" msgstr "Lord Vampiro" msgid "Vampire Lords" msgstr "Lord Vampiri" msgid "Lich" msgstr "Salma" msgid "Liches" msgstr "Salme" msgid "Power Lich" msgstr "Salma Potente" msgid "Power Liches" msgstr "Salme Potenti" msgid "Bone Dragon" msgstr "Drago d'Ossa" msgid "Bone Dragons" msgstr "Draghi d'Ossa" msgid "Rogue" msgstr "Furfante" msgid "Rogues" msgstr "Furfanti" msgid "Nomad" msgstr "Nomade" msgid "Nomads" msgstr "Nomadi" msgid "Ghost" msgstr "Fantasma" msgid "Ghosts" msgstr "Fantasmi" msgid "Genie" msgstr "Genio" msgid "Genies" msgstr "Geni" msgid "Medusa" msgstr "Medusa" msgid "Medusas" msgstr "Meduse" msgid "Earth Elemental" msgstr "Creatura di Terra" msgid "Earth Elementals" msgstr "Creature di Terra" msgid "Air Elemental" msgstr "Creatura d'Aria" msgid "Air Elementals" msgstr "Creature d'Aria" msgid "Fire Elemental" msgstr "Creatura del Fuoco" msgid "Fire Elementals" msgstr "Creature del Fuoco" msgid "Water Elemental" msgstr "Creatura dell'Acqua" msgid "Water Elementals" msgstr "Creature dell'Acqua" msgid "Random Monsters" msgstr "Mostri casuali" msgid "Random Monster 1" msgstr "" msgid "Random Monsters 1" msgstr "" msgid "Random Monster 2" msgstr "" msgid "Random Monsters 2" msgstr "" msgid "Random Monster 3" msgstr "" msgid "Random Monsters 3" msgstr "" msgid "Random Monster 4" msgstr "" msgid "Random Monsters 4" msgstr "" msgid "Double shot" msgstr "Doppio Tiro" msgid "2-hex monster" msgstr "Occupa due riquadri" msgid "Double strike" msgstr "Doppio Attacco" msgid "Double damage to Undead" msgstr "Doppio danno contro non-morti" msgid "% magic resistance" msgstr "% di resistenza magica" msgid "Immune to Mind spells" msgstr "Immune alle magie mentali" msgid "Immune to Elemental spells" msgstr "Immune alle magie elementali" msgid "Immune to Fire spells" msgstr "Immune alle magie del fuoco" msgid "Immune to Cold spells" msgstr "Immune alle magie del gelo" msgid "Immune to " msgstr "Immune a " msgid "% immunity to %{spell} spell" msgstr "% di immunità alla magia %{spell}" msgid "% damage from Elemental spells" msgstr "% di danni dalle magie elementali" msgid "% chance to Dispel beneficial spells" msgstr "% di probabiltà di annullare magie positive" msgid "% chance to Paralyze" msgstr "% di probabiltà di paralizzare" msgid "% chance to Petrify" msgstr "% di probabiltà di pietrificare" msgid "% chance to Blind" msgstr "% di probabiltà di accecare" msgid "% chance to Curse" msgstr "% di probabiltà di maledire" msgid "% chance to cast %{spell} spell" msgstr "% di probabiltà di gettare la magia %{spell}" msgid "HP regeneration" msgstr "Rigenerazione PF" msgid "Two hexes attack" msgstr "Colpo di due riquardri" msgid "Flyer" msgstr "Creatura Volante" msgid "Always retaliates" msgstr "Contrattacca sempre" msgid "Attacks all adjacent enemies" msgstr "Attacca tutti i nemici adiacenti" msgid "No melee penalty" msgstr "Nessuna pena per il corpo a corpo" msgid "Dragon" msgstr "Drago" msgid "Undead" msgstr "Non-Morto" msgid "No enemy retaliation" msgstr "Nessun contrattacco nemico" msgid "HP drain" msgstr "Prosciuga Pt. Ferita" msgid "Cloud attack" msgstr "Attacco di Nuvola" msgid "Decreases enemy's morale by " msgstr "Diminuisce il morale nemico di " msgid "% chance to halve enemy" msgstr "% possibiltà di dimezzare il nemico" msgid "Soul Eater" msgstr "Mietitrice d'Anime" msgid "Elemental" msgstr "Elementale" msgid "No Morale" msgstr "Nessun morale" msgid "200% damage from Fire spells" msgstr "200% di danni dalle magie del fuoco" msgid "200% damage from Cold spells" msgstr "200% di danni dalle magie del gelo" msgid "% damage from %{spell} spell" msgstr "% di danni dalla magia %{spell}" msgid "% immunity to " msgstr "% di immunità a " msgid "Lightning" msgstr "Fulmine" msgid "% damage from " msgstr "% di danni da " msgid "The three Anduran artifacts magically combine into one." msgstr "" msgid "View Spells" msgstr "Mostra Magie" msgid "View %{name} Info" msgstr "Mostra dettagli su %{name}" msgid "Move %{name}" msgstr "Sposta %{name}" #, fuzzy msgid "Cannot move the Spellbook" msgstr "Impossibile spostare l'ultima unità" msgid "This item can't be traded." msgstr "Questo oggetto non si può spostare." #, fuzzy msgid "Invalid Artifact" msgstr "Artefatto casuale" #, fuzzy msgid "The %{name} increases the hero's knowledge by %{count}." msgstr "%{name} incrementa di %{count} punti il sapere del tuo eroe." msgid "Ultimate Book of Knowledge" msgstr "Il Massimo Libro del Sapere" #, fuzzy msgid "The %{name} increases the hero's attack skill by %{count}." msgstr "%{name} incrementa di %{count} la capacità d'attacco del tuo eroe." msgid "Ultimate Sword of Dominion" msgstr "" #, fuzzy msgid "The %{name} increases the hero's defense skill by %{count}." msgstr "%{name} incrementa di %{count} la capacità d'attacco del tuo eroe." msgid "Ultimate Cloak of Protection" msgstr "" #, fuzzy msgid "The %{name} increases the hero's spell power by %{count}." msgstr "%{name} incrementa di %{count} punti il sapere del tuo eroe." msgid "Ultimate Wand of Magic" msgstr "" #, fuzzy msgid "" "The %{name} increases the hero's attack and defense skills by %{count} each." msgstr "%{name} incrementa di %{count} la capacità d'attacco del tuo eroe." msgid "Ultimate Shield" msgstr "" #, fuzzy msgid "" "The %{name} increases the hero's spell power and knowledge by %{count} each." msgstr "%{name} incrementa di %{count} punti il sapere del tuo eroe." msgid "Ultimate Staff" msgstr "" #, fuzzy msgid "" "The %{name} increases each of the hero's basic skills by %{count} points." msgstr "%{name} incrementa di %{count} punti tutte le tue capacità base." msgid "Ultimate Crown" msgstr "La Massima Corona" msgid "Golden Goose" msgstr "" msgid "The %{name} brings in an income of %{count} gold per day." msgstr "" msgid "Arcane Necklace of Magic" msgstr "" msgid "" "After rescuing a Sorceress from a cursed tomb, she rewards your heroism with " "an exquisite jeweled necklace." msgstr "" msgid "Caster's Bracelet of Magic" msgstr "" msgid "" "While searching through the rubble of a caved-in mine, you free a group of " "trapped Dwarves. Grateful, the leader gives you a golden bracelet." msgstr "" msgid "Mage's Ring of Power" msgstr "" msgid "" "A cry of pain leads you to a Centaur, caught in a trap. Upon setting the " "creature free, he hands you a small pouch. Emptying the contents, you find a " "dazzling jeweled ring." msgstr "" msgid "Witch's Broach of Magic" msgstr "" msgid "" "Alongside the remains of a burnt witch lies a beautiful broach, intricately " "designed. Approaching the corpse with caution, you add the broach to your " "inventory." msgstr "" msgid "Medal of Valor" msgstr "La Medaglia al Valore" #, fuzzy msgid "The %{name} increases the morale of the hero's army by %{count}." msgstr "%{name} incrementa di %{count} punti il sapere del tuo eroe." msgid "" "Freeing a virtuous maiden from the clutches of an evil overlord, you are " "granted a Medal of Valor by the King's herald." msgstr "" msgid "Medal of Courage" msgstr "" msgid "" "After saving a young boy from a vicious pack of Wolves, you return him to " "his father's manor. The grateful nobleman awards you with a Medal of Courage." msgstr "" msgid "Medal of Honor" msgstr "" msgid "" "After freeing a princess of a neighboring kingdom from the evil clutches of " "despicable slavers, she awards you with a Medal of Honor." msgstr "" msgid "Medal of Distinction" msgstr "" msgid "" "Ridding the countryside of the hideous Minotaur who made a sport of eating " "noblemen's Knights, you are honored with the Medal of Distinction." msgstr "" msgid "Fizbin of Misfortune" msgstr "Il Fizbin della Malasorte" #, fuzzy msgid "" "The %{name} greatly decreases the morale of the hero's army by %{count}." msgstr "%{name} diminuisce di molto il morale delle tue truppe di %{count}." msgid "" "You stumble upon a medal lying alongside the empty road. Adding the medal to " "your inventory, you become aware that you have acquired the undesirable " "Fizbin of Misfortune, greatly decreasing your army's morale." msgstr "" msgid "Thunder Mace of Dominion" msgstr "La Mazza Tonante del Dominio" msgid "" "During a sudden storm, a bolt of lightning strikes a tree, splitting it. " "Inside the tree you find a mysterious mace." msgstr "" msgid "Armored Gauntlets of Protection" msgstr "" #, fuzzy msgid "The %{name} increase the hero's defense skill by %{count}." msgstr "%{name} incrementa di %{count} la capacità d'attacco del tuo eroe." msgid "" "You encounter the infamous Black Knight! After a grueling duel ending in a " "draw, the Knight, out of respect, offers you a pair of armored gauntlets." msgstr "" msgid "Defender Helm of Protection" msgstr "L'Elmo di Protezione del Difensore" msgid "" "A glint of golden light catches your eye. Upon further investigation, you " "find a golden helm hidden under a bush." msgstr "" msgid "Giant Flail of Dominion" msgstr "" msgid "" "A clumsy Giant has killed himself with his own flail. Knowing your superior " "skill with this weapon, you confidently remove the spectacular flail from " "the fallen Giant." msgstr "" msgid "Ballista of Quickness" msgstr "La Ballista della Rapidità" msgid "The %{name} gives the hero's catapult one extra shot per combat round." msgstr "" msgid "" "Walking through the ruins of an ancient walled city, you find the instrument " "of the city's destruction, an elaborately crafted ballista." msgstr "" msgid "Stealth Shield of Protection" msgstr "Lo Scudo Segreto di Protezione" msgid "" "A stone statue of a warrior holds a silver shield. As you remove the shield, " "the statue crumbles into dust." msgstr "" msgid "Dragon Sword of Dominion" msgstr "La Spada Dominio del Drago" msgid "" "As you are walking along a narrow path, a nearby bush suddenly bursts into " "flames. Before your eyes the flames become the image of a beautiful woman. " "She holds out a magnificent sword to you." msgstr "" msgid "Power Axe of Dominion" msgstr "" msgid "" "You see a silver axe embedded deeply in the ground. After several " "unsuccessful attempts by your army to remove the axe, you tightly grip the " "handle of the axe and effortlessly pull it free." msgstr "" msgid "Divine Breastplate of Protection" msgstr "" msgid "" "A gang of Rogues is sifting through the possessions of dead warriors. " "Scaring off the scavengers, you note the Rogues had overlooked a beautiful " "breastplate." msgstr "" msgid "Minor Scroll of Knowledge" msgstr "" msgid "" "Before you appears a levitating glass case with a scroll, perched upon a bed " "of crimson velvet. At your touch, the lid opens and the scroll floats into " "your awaiting hands." msgstr "" msgid "Major Scroll of Knowledge" msgstr "" msgid "" "Visiting a local wiseman, you explain the intent of your journey. He reaches " "into a sack and withdraws a yellowed scroll and hands it to you." msgstr "" msgid "Superior Scroll of Knowledge" msgstr "" msgid "" "You come across the remains of an ancient Druid. Bones, yellowed with age, " "peer from the ragged folds of her robe. Searching the robe, you discover a " "scroll hidden in the folds." msgstr "" msgid "Foremost Scroll of Knowledge" msgstr "" msgid "" "Mangled bones, yellowed with age, peer from the ragged folds of a dead " "Druid's robe. Searching the robe, you discover a scroll hidden within." msgstr "" msgid "Endless Sack of Gold" msgstr "Borsa d'Oro Infinita" #, fuzzy msgid "The %{name} provides the hero with %{count} gold per day." msgstr "%{name} contiene le tue magie." msgid "" "A little leprechaun dances gleefully around a magic sack. Seeing you " "approach, he stops in mid-stride. The little man screams and stamps his foot " "ferociously, vanishing into thin air. Remembering the old leprechaun saying " "'Finders Keepers', you grab the sack and leave." msgstr "" msgid "Endless Bag of Gold" msgstr "La Sacca d'Oro Infinita" msgid "" "A noblewoman, separated from her traveling companions, asks for your help. " "After escorting her home, she rewards you with a bag filled with gold." msgstr "" msgid "Endless Purse of Gold" msgstr "" msgid "" "In your travels, you find a leather purse filled with gold that once " "belonged to a great warrior king who had the ability to transform any " "inanimate object into gold." msgstr "" msgid "Nomad Boots of Mobility" msgstr "Gli Stivali di Mobilità dei Nomadi" #, fuzzy msgid "The %{name} increase the hero's movement on land." msgstr "%{name} incrementa di %{count} punti il sapere del tuo eroe." msgid "" "A Nomad trader seeks protection from a tribe of Goblins. For your " "assistance, he gives you a finely crafted pair of boots made from the " "softest leather. Looking closely, you see fascinating ancient carvings " "engraved on the leather." msgstr "" msgid "Traveler's Boots of Mobility" msgstr "" msgid "" "Discovering a pair of beautifully beaded boots made from the finest and " "softest leather, you thank the anonymous donor and add the boots to your " "inventory." msgstr "" msgid "Lucky Rabbit's Foot" msgstr "La Zampa del Coniglio Fortunato" #, fuzzy msgid "The %{name} increases the luck of the hero's army by %{count}." msgstr "%{name} ti dà più fortuna in combattimento." msgid "" "A traveling merchant offers you a rabbit's foot, made of gleaming silver " "fur, for safe passage. The merchant explains the charm will increase your " "luck in combat." msgstr "" msgid "Golden Horseshoe" msgstr "Il Ferro di Cavallo d'Oro" msgid "" "An ensnared Unicorn whinnies in fright. Murmuring soothing words, you set " "her free. Snorting and stamping her front hoof once, she gallops off. " "Looking down you see a golden horseshoe." msgstr "" msgid "Gambler's Lucky Coin" msgstr "" msgid "" "You have captured a mischievous imp who has been terrorizing the region. In " "exchange for his release, he rewards you with a magical coin." msgstr "" msgid "Four-Leaf Clover" msgstr "Il Quadrifoglio" msgid "" "In the middle of a patch of dead and dry vegetation, to your surprise you " "find a healthy green four-leaf clover." msgstr "" msgid "True Compass of Mobility" msgstr "" #, fuzzy msgid "The %{name} increases the hero's movement on land and sea." msgstr "%{name} incrementa di %{count} punti il sapere del tuo eroe." msgid "" "An old man claiming to be an inventor asks you to try his latest invention. " "He then hands you a compass." msgstr "" msgid "Sailor's Astrolabe of Mobility" msgstr "L'Astrolabio del Marinaio" #, fuzzy msgid "The %{name} increases the hero's movement on sea." msgstr "%{name} incrementa di %{count} punti il sapere del tuo eroe." msgid "" "An old sea captain is being tortured by Ogres. You save him, and in return " "he rewards you with a wondrous instrument to measure the distance of a star." msgstr "" msgid "Evil Eye" msgstr "" msgid "The %{name} reduces the casting cost of curse spells by half." msgstr "" msgid "" "While venturing into a decrepit hut you find the Skeleton of a long dead " "witch. Investigation of the remains reveals a glass eye rolling around " "inside an empty skull." msgstr "" msgid "Enchanted Hourglass" msgstr "" #, fuzzy msgid "" "The %{name} extends the duration of all the hero's spells by %{count} turns." msgstr "%{name} incrementa di %{count} punti tutte le tue capacità base." msgid "" "A surprise turn in the landscape finds you in the midst of a grisly scene: " "Vultures picking at the aftermath of a terrible battle. Your cursory search " "of the remains turns up an enchanted hourglass." msgstr "" msgid "Gold Watch" msgstr "L'Orologio d'Oro" #, fuzzy msgid "The %{name} doubles the effectiveness of the hero's hypnotize spells." msgstr "%{name} aumenta di due volte il potere delle tua magia Ipnotizza." msgid "" "In reward for helping his cart out of a ditch, a traveling potion salesman " "gives you a \"magic\" gold watch. Unbeknownst to him, the watch really is " "magical." msgstr "" msgid "Skullcap" msgstr "" msgid "The %{name} halves the casting cost of all mind influencing spells." msgstr "" msgid "" "A brief stop at an improbable rural inn yields an exchange of money, tales, " "and accidentally, luggage. You find a magical skullcap in your new backpack." msgstr "" msgid "Ice Cloak" msgstr "Il Mantello di Ghiaccio" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from cold spells." msgstr "" "%{name} riduce a metà i danni le tue forze predono da magie di fulmini." msgid "" "Responding to the panicked cries of a damsel in distress, you discover a " "young woman fleeing from a hungry bear. You slay the beast in the nick of " "time, and the grateful Sorceress weaves a magic cloak from the bear's hide." msgstr "" msgid "Fire Cloak" msgstr "Il Mantello di Fuoco" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from fire spells." msgstr "" "%{name} riduce a metà i danni le tue forze predono da magie di fulmini." msgid "" "You've come upon a fight between a Necromancer and a Paladin. The " "Necromancer blasts the Paladin with a fire bolt, bringing him to his knees. " "Acting quickly, you slay the evil one before the final blow. The grateful " "Paladin gives you the fire cloak that saved him." msgstr "" msgid "Lightning Helm" msgstr "L'Elmo Parafulmini" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from lightning " "spells." msgstr "" "%{name} riduce a metà i danni le tue forze predono da magie di fulmini." msgid "" "A traveling tinker in need of supplies offers you a helm with a thunderbolt " "design on its top in exchange for food and water. Curious, you accept, and " "later find out that the helm is magical." msgstr "" msgid "Evercold Icicle" msgstr "Il Ghiacciolo Semprefreddo" #, fuzzy msgid "" "The %{name} causes the hero's cold spells to do %{count} percent more damage " "to enemy troops." msgstr "" "%{name} fa sì che le tue magie di gelo fanno %{count} % più danni contro le " "unità nemiche." msgid "" "An icicle withstanding the full heat of the noonday sun attracts your " "attention. Intrigued, you break it off, and find that it does not melt in " "your hand." msgstr "" msgid "Everhot Lava Rock" msgstr "Lava Semprecalda" #, fuzzy msgid "" "The %{name} causes the hero's fire spells to do %{count} percent more damage " "to enemy troops." msgstr "" "%{name} fa sì che le tue magie di gelo fanno %{count} % più danni contro le " "unità nemiche." msgid "" "Your wanderings bring you into contact with a tribe of ape-like beings using " "a magical lava rock that never cools to light their fires. You take pity on " "them and teach them to make fire with sticks. Believing you to be a god, the " "apes give you their rock." msgstr "" msgid "Lightning Rod" msgstr "" #, fuzzy msgid "" "The %{name} causes the hero's lightning spells to do %{count} percent more " "damage to enemy troops." msgstr "" "%{name} fa sì che le tue magie di gelo fanno %{count} % più danni contro le " "unità nemiche." msgid "" "While waiting out a storm, a lighting bolt strikes a nearby cottage's " "lightning rod, which melts and falls to the ground. The tip of the rod, " "however, survives intact and makes your hair stand on end when you touch it. " "Hmm..." msgstr "" msgid "Snake-Ring" msgstr "" #, fuzzy msgid "The %{name} halves the casting cost of all of the hero's bless spells." msgstr "%{name} aumenta di due volte il potere delle tua magia Ipnotizza." msgid "" "You've found an oddly shaped ring on the finger of a long dead traveler. The " "ring looks like a snake biting its own tail." msgstr "" msgid "Ankh" msgstr "" #, fuzzy msgid "" "The %{name} doubles the effectiveness of all of the hero's resurrect and " "animate spells." msgstr "%{name} aumenta di due volte il potere delle tua magia Ipnotizza." msgid "" "A fierce windstorm reveals the entrance to a buried tomb. Your investigation " "reveals that the tomb has already been looted, but the thieves overlooked an " "ankh on a silver chain in the dark." msgstr "" msgid "Book of Elements" msgstr "" #, fuzzy msgid "" "The %{name} doubles the effectiveness of all of the hero's summoning spells." msgstr "%{name} aumenta di due volte il potere delle tua magia Ipnotizza." msgid "" "You come across a conjurer who begs to accompany you and your army awhile " "for safety. You agree, and he offers as payment a copy of the book of the " "elements." msgstr "" msgid "Elemental Ring" msgstr "" msgid "The %{name} halves the casting cost of all summoning spells." msgstr "" msgid "" "While pausing to rest, you notice a bobcat climbing a short tree to get at a " "crow's nest. On impulse, you climb the tree yourself and scare off the cat. " "When you look in the nest, you find a collection of shiny stones and a ring." msgstr "" msgid "Holy Pendant" msgstr "" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to curse spells." msgstr "%{name} protegge le tue forze da Ipnotizza." msgid "" "In your wanderings you come across a hermit living in a small, tidy hut. " "Impressed with your mission, he takes time out from his meditations to bless " "and give you a charm against curses." msgstr "" msgid "Pendant of Free Will" msgstr "Il Pendente Arbitrio" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to hypnotize spells." msgstr "%{name} protegge le tue forze da Ipnotizza." msgid "" "Responding to cries for help, you find river Sprites making a sport of " "dunking an old man. Feeling vengeful, you rescue the man and drag a Sprite " "onto dry land for awhile. The Sprite, uncomfortable in the air, gives you a " "magic pendant to let him go." msgstr "" msgid "Pendant of Life" msgstr "" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to death spells." msgstr "%{name} protegge le tue forze da Ipnotizza." msgid "" "A brief roadside encounter with a small caravan and a game of knucklebones " "wins a magic pendant. Its former owner says that it protects from " "Necromancers' death spells." msgstr "" msgid "Serenity Pendant" msgstr "" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to berserk spells." msgstr "%{name} protegge le tue forze da Ipnotizza." msgid "" "The sounds of combat draw you to the scene of a fight between an old " "Barbarian and an eight-headed Hydra. Your timely intervention swings the " "battle in favor of the man, and he rewards you with a pendant he used to use " "to calm his mind for battle." msgstr "" msgid "Seeing-eye Pendant" msgstr "" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to blindness spells." msgstr "%{name} protegge le tue forze da Ipnotizza." msgid "" "You come upon a very old woman, long blind from cataracts and dying alone. " "You tend to her final needs and promise a proper burial. Grateful, she gives " "you a magic pendant emblazoned with a stylized eye. It lets you see with " "your eyes closed." msgstr "" msgid "Kinetic Pendant" msgstr "" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to paralyze spells." msgstr "%{name} protegge le tue forze da Ipnotizza." msgid "" "You come across a golem wearing a glowing pendant and blocking your way. " "Acting on a hunch, you cut the pendant from its neck. Deprived of its power " "source, the golem breaks down, leaving you with the magical pendant." msgstr "" msgid "Pendant of Death" msgstr "Il Pendente Mortale" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to holy spells." msgstr "%{name} protegge le tue forze da Ipnotizza." msgid "" "A quick and deadly battle with a Necromancer wins you his magical pendant. " "Later, a Wizard tells you that the pendant protects undead under your " "control from holy word spells." msgstr "" msgid "Wand of Negation" msgstr "" #, fuzzy msgid "" "The %{name} makes all of the hero's troops immune to dispel magic spells." msgstr "%{name} protegge le tue forze da Ipnotizza." msgid "" "You meet an old Wizard friend of yours traveling in the opposite direction. " "He presents you with a gift: A wand that prevents the use of the dispel " "magic spell on your allies." msgstr "" msgid "Golden Bow" msgstr "" msgid "" "The %{name} eliminates the %{count} percent penalty for the hero's troops " "shooting past obstacles (e.g. castle walls)." msgstr "" msgid "" "A chance meeting with a famous Archer finds you in a game of knucklebones " "pitting his bow against your horse. You win." msgstr "" msgid "Telescope" msgstr "" msgid "" "The %{name} increases the amount of terrain the hero reveals when " "adventuring by %{count} extra square." msgstr "" msgid "" "A merchant from far away lands trades you a new invention of his people for " "traveling supplies. It makes distant objects appear closer, and he calls " "it...\n" "\n" "a telescope." msgstr "" msgid "Statesman's Quill" msgstr "Il Calamo dello Statista" #, fuzzy msgid "" "The %{name} reduces the cost of surrender to %{count} percent of the total " "cost of troops the hero has in their army." msgstr "%{name} diminuisce di %{count} percento il costo della resa." msgid "" "You pause to help a diplomat with a broken axle fix his problem. In " "gratitude, he gives you a writing quill with magical properties which he " "says will \"help people see things your way\"." msgstr "" msgid "Wizard's Hat" msgstr "" #, fuzzy msgid "" "The %{name} increases the duration of the hero's spells by %{count} turns." msgstr "%{name} incrementa di %{count} punti tutte le tue capacità base." msgid "" "You see a Wizard fleeing from a Griffin and riding like the wind. The Wizard " "opens a portal and rides through, getting his hat knocked off by the edge of " "the gate. The Griffin follows; the gate closes. You pick the hat up, dust it " "off, and put it on." msgstr "" msgid "Power Ring" msgstr "" #, fuzzy msgid "The %{name} returns %{count} extra spell points per day to the hero." msgstr "%{name} contiene le tue magie." msgid "" "You find a small tree that closely resembles the great Warlock Carnauth with " "a ring around one of its twigs. Scraps of clothing and rotting leather lead " "you to suspect that it IS Carnauth, transformed. Since you can't help him, " "you take the magic ring." msgstr "" msgid "Ammo Cart" msgstr "" msgid "" "The %{name} provides endless ammunition for all of the hero's troops that " "shoot." msgstr "" msgid "" "An ammunition cart in the middle of an old battlefield catches your eye. " "Inspection shows it to be in good working order, so you take it along." msgstr "" msgid "Tax Lien" msgstr "" #, fuzzy msgid "The %{name} costs the hero %{count} gold pieces per day." msgstr "%{name} contiene le tue magie." msgid "" "Your big spending habits have earned you a massive tax bill that you can't " "hope to pay. The tax man takes pity and agrees to only take 250 gold a day " "from your account for life. Check here if you want one dollar to go to the " "presidential campaign election fund." msgstr "" msgid "Hideous Mask" msgstr "" msgid "The %{name} prevents all 'wandering' armies from joining the hero." msgstr "" msgid "" "Your looting of the grave of Sinfilas Gardolad, the famous shapeshifting " "Warlock, unearths his fabled mask. Trembling, you put it on and it twists " "your visage into an awful grimace! Oh no! It's actually the hideous mask of " "Gromluck Greene, and you are stuck with it." msgstr "" msgid "Endless Pouch of Sulfur" msgstr "" msgid "The %{name} provides %{count} unit of sulfur per day." msgstr "" msgid "" "You visit an alchemist who, upon seeing your army, is swayed by the " "righteousness of your cause. The newly loyal subject gives you his endless " "pouch of sulfur to help with the war effort." msgstr "" msgid "Endless Vial of Mercury" msgstr "" msgid "The %{name} provides %{count} unit of mercury per day." msgstr "" msgid "" "A brief stop at a hastily abandoned Wizard's tower turns up a magical vial " "of mercury that always has a little left on the bottom. Recognizing a " "treasure when you see one, you cap it and slip it in your pocket." msgstr "" msgid "Endless Pouch of Gems" msgstr "" msgid "The %{name} provides %{count} unit of gems per day." msgstr "" msgid "" "A short rainstorm brings forth a rainbow...and you can see the end of it. " "Riding quickly, you seize the pot of gold you find there. The leprechaun who " "owns it, unable to stop you from taking it, offers an endless pouch of gems " "for the return of his gold. You accept." msgstr "" msgid "Endless Cord of Wood" msgstr "" msgid "The %{name} provides %{count} unit of wood per day." msgstr "" msgid "" "Pausing to rest and light a cook fire, you pull wood out of a nearby pile of " "dead wood. As you keep pulling wood from the pile, you notice that it " "doesn't shrink. You realize to your delight that the wood is enchanted, so " "you take it along." msgstr "" msgid "Endless Cart of Ore" msgstr "" msgid "The %{name} provides %{count} unit of ore per day." msgstr "" msgid "" "You've found a Goblin weapon smithy making weapons for use against humans. " "With a tremendous yell you and your army descend upon their camp and drive " "them away. A search finds a magic ore cart that never runs out of iron." msgstr "" msgid "Endless Pouch of Crystal" msgstr "" msgid "The %{name} provides %{count} unit of crystal per day." msgstr "" msgid "" "Taking shelter from a storm in a small cave, you notice a small patch of " "crystal in one corner. Curious, you break a piece off and notice that the " "original crystal grows the lost piece back. You decide to stuff the entire " "patch into a pouch and take it with you." msgstr "" msgid "Spiked Helm" msgstr "" msgid "" "Your army is ambushed by a small tribe of wild (and none too bright) Orcs. " "You fend them off easily and the survivors flee in all directions. One of " "the Orcs was wearing a polished spiked helm. Figuring it will make a good " "souvenir, you take it." msgstr "" msgid "Spiked Shield" msgstr "" msgid "" "You come upon a bridge spanning a dry gully. Before you can cross, a Troll " "steps out from under the bridge and demands payment before it will permit " "you to pass. You refuse, and the Troll charges, forcing you to slay it. You " "take its spiked shield as a trophy." msgstr "" msgid "White Pearl" msgstr "La Perla Bianca" msgid "" "A walk across a dry saltwater lake bed yields an unlikely prize: A white " "pearl amidst shattered shells and debris." msgstr "" msgid "Black Pearl" msgstr "La Perla Nera" msgid "" "Rumors of a Griffin of unusual size preying upon the countryside lead you to " "its cave lair. A quick, brutal fight dispatches the beast, and a search of " "its foul nest turns up a huge black pearl." msgstr "" msgid "Magic Book" msgstr "Il Libro Magico" #, fuzzy msgid "The %{name} enables the hero to cast spells." msgstr "%{name} contiene le tue magie." #, fuzzy msgid "Dummy 1" msgstr "Mummia" #, fuzzy msgid "The reserved artifact." msgstr "con 1 artefatto." #, fuzzy msgid "Dummy 2" msgstr "Mummia" #, fuzzy msgid "Dummy 3" msgstr "Mummia" #, fuzzy msgid "Dummy 4" msgstr "Mummia" msgid "Spell Scroll" msgstr "" msgid "" "This %{name} gives the hero the ability to cast the %{spell} spell if the " "hero has a Magic Book." msgstr "" msgid "" "You find an elaborate container which houses an old vellum scroll. The runes " "on the container are very old, and the artistry with which it was put " "together is stunning. As you pull the scroll out, you feel imbued with " "magical power." msgstr "" msgid "Arm of the Martyr" msgstr "" #, fuzzy msgid "" "The %{name} increases the hero's spell power by %{count} but adds the undead " "morale penalty." msgstr "%{name} incrementa di %{count} punti il sapere del tuo eroe." msgid "" "One of the less intelligent members of your party picks up an arm off of the " "ground. Despite its missing a body, it is still moving. Your troops find the " "dismembered arm repulsive, but you cannot bring yourself to drop it: it " "seems to hold some sort of magical power that influences your decision " "making." msgstr "" msgid "Breastplate of Anduran" msgstr "" #, fuzzy msgid "The %{name} increases the hero's defense by %{count}." msgstr "%{name} incrementa di %{count} punti il sapere del tuo eroe." msgid "" "You come upon a sign. It reads: \"Here lies the body of Anduran. Bow and " "swear fealty, and you shall be rewarded.\" You decide to do as it says. As " "you stand up, you feel a coldness against your skin. Looking down, you find " "that you are suddenly wearing a gleaming, ornate breastplate." msgstr "" msgid "Broach of Shielding" msgstr "" msgid "" "The %{name} provides %{count} percent protection from Armageddon and " "Elemental Storm, but decreases spell power by 2." msgstr "" msgid "" "A kindly Sorceress thinks that your army's defenses could use a magical " "boost. She offers to enchant the Broach that you wear on your cloak, and you " "accept." msgstr "" msgid "Battle Garb of Anduran" msgstr "" msgid "" "The %{name} combines the powers of the three Anduran artifacts. It provides " "maximum luck and morale for the hero's troops and gives the hero the Town " "Portal spell." msgstr "" msgid "" "Out of pity for a poor peasant, you purchase a chest of old junk they are " "hawking for too much gold. Later, as you search through it, you find it " "contains the 3 pieces of the legendary battle garb of Anduran!" msgstr "" msgid "Crystal Ball" msgstr "" msgid "" "The %{name} lets the hero get more specific information about monsters, " "enemy heroes, and castles nearby the hero." msgstr "" msgid "" "You come upon a caravan of gypsies who are feasting and fortifying their " "bodies with mead. They call you forward and say \"If you prove that you can " "dance the Rama-Buta, we will reward you.\" You don't know it, but try " "anyway. They laugh hysterically, but admire your bravery, giving you a " "Crystal Ball." msgstr "" msgid "Heart of Fire" msgstr "" msgid "" "The %{name} provides %{count} percent protection from fire, but doubles the " "damage taken from cold." msgstr "" msgid "" "You enter a recently burned glade and come upon a Fire Elemental sitting " "atop a rock. It looks up, its flaming face contorted in a look of severe " "pain. It then tosses a glowing object at you. You put up your hands to block " "it, but it passes right through them and sears itself into your chest." msgstr "" msgid "Heart of Ice" msgstr "" msgid "" "The %{name} provides %{count} percent protection from cold, but doubles the " "damage taken from fire." msgstr "" msgid "" "Suddenly, a biting coldness engulfs your body. You seize up, falling from " "your horse. The pain subsides, but you still feel as if your chest is " "frozen. As you pick yourself up off of the ground, you hear hearty laughter. " "You turn around just in time to see a Frost Giant run off into the woods and " "disappear." msgstr "" msgid "Helmet of Anduran" msgstr "" msgid "" "You spy a gleaming object poking up out of the ground. You send a member of " "your party over to investigate. He comes back with a golden helmet in his " "hands. You realize that it must be the helmet of the legendary Anduran, the " "only man who was known to wear solid gold armor." msgstr "" msgid "Holy Hammer" msgstr "" msgid "" "You come upon a battle where a Paladin has been mortally wounded by a group " "of Zombies. He asks you to take his hammer and finish what he started. As " "you pick it up, it begins to hum, and then everything becomes a blur. The " "Zombies lie dead, the hammer dripping with blood. You strap it to your belt." msgstr "" msgid "Legendary Scepter" msgstr "" msgid "The %{name} adds %{count} points to all attributes." msgstr "" msgid "" "Upon cresting a small hill, you come upon a ridiculous looking sight. A " "Sprite is attempting to carry a Scepter that is almost as big as it is. " "Trying not to laugh, you ask, \"Need help?\" The Sprite glares at you and " "answers: \"You think this is funny? Fine. You can carry it. I much prefer " "flying anyway.\"" msgstr "" msgid "" "An old seaman tells you a tale of an enchanted masthead that he used in his " "youth to rally his crew during times of trouble. He then hands you a faded " "map that shows where he hid it. After much exploring, you find it stashed " "underneath a nearby dock." msgstr "" msgid "Masthead" msgstr "" #, fuzzy msgid "" "The %{name} boosts the hero's troops' luck and morale by %{count} each in " "sea combat." msgstr "%{name} incrementa di %{count} punti il sapere del tuo eroe." msgid "Sphere of Negation" msgstr "" msgid "The %{name} disables all spell casting, for both sides, in combat." msgstr "" msgid "" "You stop to help a Peasant catch a runaway mare. To show his gratitude, he " "hands you a tiny sphere. As soon as you grasp it, you feel the magical " "energy drain from your limbs..." msgstr "" msgid "Staff of Wizardry" msgstr "" #, fuzzy msgid "The %{name} boosts the hero's spell power by %{count}." msgstr "%{name} incrementa di %{count} punti il sapere del tuo eroe." msgid "" "While out scaring up game, your troops find a mysterious staff levitating " "about three feet off of the ground. They hand it to you, and you notice an " "inscription. It reads: \"Brains best brawn and magic beats might. Heed my " "words, and you'll win every fight.\"" msgstr "" msgid "Sword Breaker" msgstr "" #, fuzzy msgid "The %{name} increases the hero's defense by %{count} and attack by 1." msgstr "%{name} incrementa di %{count} punti il sapere del tuo eroe." msgid "" "A former Captain of the Guard admires your quest and gives you the enchanted " "Sword Breaker that he relied on during his tour of duty." msgstr "" msgid "Sword of Anduran" msgstr "" msgid "" "A Troll stops you and says: \"Pay me 5,000 gold, or the Sword of Anduran " "will slay you where you stand.\" You refuse. The troll grabs the sword " "hanging from its belt, screams in pain, and runs away. Picking up the fabled " "sword, you give thanks that half-witted Trolls tend to grab the wrong end of " "sharp objects." msgstr "" msgid "Spade of Necromancy" msgstr "" msgid "The %{name} gives the hero increased necromancy skill." msgstr "" msgid "" "A dirty shovel has been thrust into a dirt mound nearby. Upon investigation, " "you discover it to be the enchanted shovel of the Gravediggers, long thought " "lost by mortals." msgstr "" msgid "spellBonus|selected by user" msgstr "" msgid "Wood" msgstr "Legno" msgid "Mercury" msgstr "Mercurio" msgid "Ore" msgstr "Ferro" msgid "Sulfur" msgstr "Zolfo" msgid "Crystal" msgstr "Crystallo" msgid "Gems" msgstr "Gemme" msgid "Gold" msgstr "Oro" msgid "" "There are seven resources in Heroes 2, used to build and improves castles, " "purchase troops and recruit heroes. Gold is the most common, required for " "virtually everything. Wood and ore are used for most buildings. Gems, " "Mercury, Sulfur and Crystal are rare magical resources used for the most " "powerful creatures and buildings." msgstr "" msgid "" "Causes a giant fireball to strike the selected area, damaging all nearby " "creatures." msgstr "" "Un'enorme massa di fiamme si schianta contro i riquadri selezionati " "colpendovi tutte le unità." msgid "Fireball" msgstr "Palla di Fuoco" msgid "Fireblast" msgstr "Vampata" msgid "" "An improved version of fireball, fireblast affects two hexes around the " "center point of the spell, rather than one." msgstr "" "La forma potenziata di palla di fuoco. La vampata danneggia due riquadri " "circostanti all'esagono specificato, piuttosto che solo uno." msgid "Causes a bolt of electrical energy to strike the selected creature." msgstr "Un colpo d'energia elettrica daneggia l'unità specificata." msgid "Lightning Bolt" msgstr "Colpo di Fulmine" msgid "Chain Lightning" msgstr "Fulmini in Serie" #, fuzzy msgid "" "Causes a bolt of electrical energy to strike a selected creature, then " "strike the nearest creature with half damage, then strike the NEXT nearest " "creature with half again damage, and so on, until it becomes too weak to be " "harmful. Warning: This spell can hit your own creatures!" msgstr "" "Un colpo di fulmine colpisce un'unità specificata, poi colpisce quella gli " "sta più vicina a metà del danno, poi quell'altra più vicina all'ultima " "sempre a metà del danno, finché non riesca a fare più danni. Attenti: Questa " "magia può anche colpire le tue forze!" msgid "Teleport" msgstr "" "Tele-\n" "trasporto" msgid "" "Teleports the creature you select to any open position on the battlefield." msgstr "" "Sposta un'unità a scelta tua in un posto non occupato del campo di battaglia." msgid "Cure" msgstr "Cura" msgid "" "Removes all negative spells cast upon one of your units, and restores up to " "%{count} HP per level of spell power." msgstr "" "Rimuove le magie negative su una creatura prescelta e cura %{count} PF per " "ogni livello di potere magia dell'eroe." msgid "Mass Cure" msgstr "Max Cura" msgid "" "Removes all negative spells cast upon your forces, and restores up to " "%{count} HP per level of spell power, per creature." msgstr "" "Rimuove le magie negative su tutte le tue creature e cura %{count} PF per " "ogni livello di potere magia dell'eroe." msgid "Resurrect" msgstr "Risorgi" msgid "Resurrects creatures from a damaged or dead unit until end of combat." msgstr "Fa risuscitare delle unità per la durata della battaglia." msgid "Resurrect True" msgstr "Risorgi Sempre" msgid "Resurrects creatures from a damaged or dead unit permanently." msgstr "Fa risuscitare definitivamente delle unità morte." msgid "Haste" msgstr "Furia" msgid "Increases the speed of any creature by %{count}." msgstr "Aumenta la velocità di un'unità di %{count}." msgid "Increases the speed of all of your creatures by %{count}." msgstr "Aumenta la velocità di tutte le tue unità di %{count}." msgid "Mass Haste" msgstr "Max Furia" msgid "Slows target to half movement rate." msgstr "Dimezza la distanza di spostamento e la velocità del bersaglio." msgid "spell|Slow" msgstr "Adagio" msgid "Mass Slow" msgstr "Pianissimo" msgid "Slows all enemies to half movement rate." msgstr "" "Dimezza la distanza di spostamento e la velocità di tutte le unità nemiche." msgid "Clouds the affected creatures' eyes, preventing them from moving." msgstr "Acceca l'unità prescelta, inibendola di fare il suo turno." msgid "spell|Blind" msgstr "Blind" msgid "Bless" msgstr "Bless" msgid "Causes the selected creatures to inflict maximum damage." msgstr "" "Fa sì che l'unità prescelta infligga il massimo del suo danno potenziale." msgid "Causes all of your units to inflict maximum damage." msgstr "" "Fa sì che tutte le tue creature infliggano il massimo del loro danno " "potenziale." msgid "Mass Bless" msgstr "Max Bless" msgid "Magically increases the defense skill of the selected creatures." msgstr "Incrementa la difesa dell'unità prescelta." msgid "Stoneskin" msgstr "Schermo di Pietra" #, fuzzy msgid "" "Increases the defense skill of the targeted creatures. This is an improved " "version of Stoneskin." msgstr "" "Incrementa la capacità di difesa dell'unità prescelta. È la forma sviluppata " "di Schermo di pietra." msgid "Steelskin" msgstr "Corazza" msgid "Causes the selected creatures to inflict minimum damage." msgstr "Costringe l'unità nemiche prescelta di fare il danno minimo." msgid "Curse" msgstr "Maledizione" msgid "Causes all enemy troops to inflict minimum damage." msgstr "Costringe tutte le truppe nemiche di fare il danno minimo." msgid "Mass Curse" msgstr "Malefizio" msgid "Damages all undead in the battle." msgstr "Colpisce tutti i non-morti nella battaglia." msgid "Holy Word" msgstr "Sacro Motto" #, fuzzy msgid "" "Damages all undead in the battle. This is an improved version of Holy Word." msgstr "" "Colpisce tutti i non-morti nella battaglia. È la forma sviluppata di Sacro " "Motto." msgid "Holy Shout" msgstr "Sacro Grido" msgid "Anti-Magic" msgstr "Anti-Magia" msgid "Prevents harmful magic against the selected creatures." msgstr "Previene tutte le magie, sia utili che dannose, all'unità prescelta." msgid "Dispel Magic" msgstr "" "Annulla-\n" "Magie" msgid "Removes all magic spells from a single target." msgstr "Rimuove tutte le magie da un unico bersaglio." msgid "Mass Dispel" msgstr "" "Max Annulla-\n" "Magie" msgid "Removes all magic spells from all creatures." msgstr "Rimuove tutte le magie da tutte le creature nel campo di battaglia." msgid "Causes a magic arrow to strike the selected target." msgstr "Fa colpire il bersaglio prescelto da una freccia magica." msgid "Magic Arrow" msgstr "Freccia Magica" msgid "Berserker" msgstr "Berzerker" msgid "Causes a creature to attack its nearest neighbor." msgstr "" "Provoca una creatura di partire all'attacco al suo vicino più prossimo." msgid "Armageddon" msgstr "Armageddon" msgid "" "Holy terror strikes the battlefield, causing severe damage to all creatures." msgstr "" "Un sacro terrore beraglia tutti sul campo di battaglia, causando danni " "severi." msgid "Elemental Storm" msgstr "Tempesta di Elementi" msgid "Magical elements pour down on the battlefield, damaging all creatures." msgstr "Elementi magici bersagliano tutte le creature nella battaglia." msgid "" "A rain of rocks strikes an area of the battlefield, damaging all nearby " "creatures." msgstr "" "Una pioggia di rocce cade su una parte del campo, colpendo tutte le creature " "vicine." msgid "Meteor Shower" msgstr "Pioggia di Meteore" msgid "Paralyze" msgstr "Paralizza" msgid "The targeted creatures are paralyzed, unable to move or retaliate." msgstr "" "Manda in paralizzo l'unità prescelta, rendendola non più in grado di " "spostarsi o di contrattaccare." msgid "Hypnotize" msgstr "Ipnotizza" msgid "" "Brings a single enemy unit under your control if its hits are less than " "%{count} times the caster's spell power." msgstr "" "Assuma il controllo di un'unità nemica se la sua capacità di danni è minore " "di %{count} volte al confronto del potere magia dell'incantatore." msgid "Cold Ray" msgstr "Raggio Freddo" msgid "Drains body heat from a single enemy unit." msgstr "Prosciuga il calore corporeo da una singola unità nemica." msgid "Cold Ring" msgstr "Anello Freddo" msgid "" "Drains body heat from all units surrounding the center point, but not " "including the center point." msgstr "" "Prosciuga il calore corporeo da tutte le unità che stanno appena attorno al " "riquadro centrale della magia, tranne l'unità che sta in quel esagono." msgid "Disrupting Ray" msgstr "Raggio Dispersore" msgid "Reduces the defense rating of an enemy unit by three." msgstr "Riduce la capacità di difesa di un'unità nemica di tre." msgid "Damages all living (non-undead) units in the battle." msgstr "Colpisce tutte le unità viventi (non quelle non-morti) in battaglia." msgid "Death Ripple" msgstr "Ondina Mortale" msgid "Death Wave" msgstr "Onda Mortale" #, fuzzy msgid "" "Damages all living (non-undead) units in the battle. This spell is an " "improved version of Death Ripple." msgstr "" "Colpisce tutte le unità viventi (cioè non quelle non-morti) nella battaglia. " "È la miglioria di ondina mortale." msgid "Dragon Slayer" msgstr "Trucida Drago" msgid "Greatly increases a unit's attack skill vs. Dragons." msgstr "Incrementa di tanto i danni contro i draghi." msgid "Blood Lust" msgstr "Ferocia" msgid "Increases a unit's attack skill." msgstr "Incrementa la capacità d'attacco di un'unità." msgid "Animate Dead" msgstr "Anima i Morti" msgid "Resurrects creatures from a damaged or dead undead unit permanently." msgstr "Fa risorgere permanentamente dei non-morti distrutti di un'unità." msgid "Mirror Image" msgstr "Rispecchia" #, fuzzy msgid "" "Creates an illusionary unit that duplicates one of your existing units. This " "illusionary unit does the same damages as the original, but will vanish if " "it takes any damage." msgstr "" "Fa comparire un'unità illusionale che copia una delle tue già esistenti " "unità. Questa illusione è capace di fare gli stessi di quello di cui sono la " "copia, però sparisce se subisce qualsiasi danno." msgid "Shield" msgstr "Scudo" #, fuzzy msgid "" "Halves damage received from ranged attacks for a single unit. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Riduce a metà i danni subiti da colpi a distanza per una singola unità." msgid "Mass Shield" msgstr "Max Scudo" #, fuzzy msgid "" "Halves damage received from ranged attacks for all of your units. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Riduce a metà i danni subiti da colpi a distanza per tutte le tue unità." msgid "Summon Earth Elemental" msgstr "Richiama Creature di Terra" msgid "Summons Earth Elementals to fight for your army." msgstr "Convoca creature di terra a combattere per il tuo esercito." msgid "Summon Air Elemental" msgstr "Richiama Creature d'Aria" msgid "Summons Air Elementals to fight for your army." msgstr "Convoca creature d'aria a combattere per il tuo esercito." msgid "Summon Fire Elemental" msgstr "Richiama Creature del Fuoco" msgid "Summons Fire Elementals to fight for your army." msgstr "Convoca Creature del Fuoco a combattere per il tuo esercito." msgid "Summon Water Elemental" msgstr "Richiama Creature dell'Acqua" msgid "Summons Water Elementals to fight for your army." msgstr "Convoca Creature dell'Acqua a combattere per il tuo esercito." msgid "Damages castle walls." msgstr "Colpisce le mura del castello." msgid "Earthquake" msgstr "Terremoto" msgid "Causes all mines across the land to become visible." msgstr "Mostra dove stanno tutte le miniere della mappa." msgid "View Mines" msgstr "Mostra Miniere" msgid "Causes all resources across the land to become visible." msgstr "Mostra dove stanno tutte le risorse della mappa." msgid "View Resources" msgstr "Mostra Risorse" msgid "Causes all artifacts across the land to become visible." msgstr "Mostra dove stanno tutti gli artefatti della mappa." msgid "View Artifacts" msgstr "Mostra Artefatti" msgid "Causes all towns and castles across the land to become visible." msgstr "Mostra dove stanno tutte le città e i castelli della mappa." msgid "View Towns" msgstr "Mostra Città" msgid "Causes all Heroes across the land to become visible." msgstr "Mostra la posizione ed il colore di tutti gli eroi sulla mappa." msgid "View Heroes" msgstr "Mostra Eroi" msgid "Causes the entire land to become visible." msgstr "Rivela tutta la mappa." msgid "View All" msgstr "Mostra Tutto" msgid "Allows the caster to view detailed information on enemy Heroes." msgstr "Permette di ottenere informazioni in detaglio sugli eroi del nemico." msgid "Summon Boat" msgstr "Richiama Nave" #, fuzzy msgid "" "Summons the nearest unoccupied, friendly boat to an adjacent shore location. " "A friendly boat is one which you just built or were the most recent player " "to occupy." msgstr "" "Convoca una nave attualmente libera, amica e più vicina ad una costa " "adiacente. Una nave amica vuol dire o una che hai appena costruita, o una " "che sei stato l'ultimo ad usare." msgid "Allows the caster to magically transport to a nearby location." msgstr "" "Consente a chi invoca la magia di trasportarsi magicamente ad una parte " "della mappa vicina." msgid "Dimension Door" msgstr "Porta Magica" msgid "Returns the caster to any town or castle currently owned." msgstr "" "Trasporta subito chi la lancia ad un qualsiasi castello o città al momento " "posseduto/a." msgid "Town Gate" msgstr "Alle Porte" msgid "" "Returns the hero to the town or castle of choice, provided it is controlled " "by you." msgstr "" "Permette all'eroe di scegliere un castello o città a cui tornare, finché " "appartenga all'eroe." msgid "Visions" msgstr "Visioni" msgid "" "Visions predicts the likely outcome of an encounter with a neutral army camp." msgstr "" "Mostra all'eroe che la usa un risultato probabile di uno scontro con un " "esercito neutrale." msgid "Haunt" msgstr "Stregata" #, fuzzy msgid "" "Haunts a mine you control with Ghosts. This mine stops producing resources. " "(If I can't keep it, nobody will!)" msgstr "" "Infesta una miniera vicina al tuo eroe con i fantasmi. La miniera smette di " "produrre risorse (se non lo posso avere io, non l'avrà nessuno!)" msgid "Set Earth Guardian" msgstr "Guardiano di Terra" msgid "Sets Earth Elementals to guard a mine against enemy armies." msgstr "Convoca una Creatura di Terra per proteggere una miniera." msgid "Set Air Guardian" msgstr "Guardiano d'Aria" msgid "Sets Air Elementals to guard a mine against enemy armies." msgstr "Convoca una Creatura d'Aria per proteggere una miniera." msgid "Set Fire Guardian" msgstr "Guardiano del Fuoco" msgid "Sets Fire Elementals to guard a mine against enemy armies." msgstr "Convoca una Creatura del Fuoco per proteggere una miniera." msgid "Set Water Guardian" msgstr "Guardiano dell'Acqua" msgid "Sets Water Elementals to guard a mine against enemy armies." msgstr "Convoca una Creatura dell'Acqua per proteggere una miniera." #, fuzzy msgid "Random Spell" msgstr "Casuale" msgid "Randomly selected spell of any level." msgstr "" #, fuzzy msgid "Random 1st Level Spell" msgstr "Castello casuale" msgid "Randomly selected 1st level spell." msgstr "" msgid "Random 2nd Level Spell" msgstr "" msgid "Randomly selected 2nd level spell." msgstr "" msgid "Random 3rd Level Spell" msgstr "" msgid "Randomly selected 3rd level spell." msgstr "" msgid "Random 4th Level Spell" msgstr "" msgid "Randomly selected 4th level spell." msgstr "" msgid "Random 5th Level Spell" msgstr "" msgid "Randomly selected 5th level spell." msgstr "" msgid "Petrification" msgstr "Pietrificazione" #, fuzzy msgid "" "Turns the affected creature into stone. A petrified creature receives half " "damage from a direct attack." msgstr "" "Trasforma la creatura colpita in pietra. Una creatura impietrita subisce " "metà dei danni da un attacco diretto." msgid "You have no Magic Book, so you cannot cast a spell." msgstr "" msgid "No spell to cast." msgstr "Nessuna magia disponibile." msgid "Your hero has %{point} spell points remaining." msgstr "Sono rimasti %{point} punti magia al tuo eroe." msgid "View Adventure Spells" msgstr "Mostra Magie d'Avventura" msgid "View Combat Spells" msgstr "Mostra Magie di Battaglia" msgid "View previous page" msgstr "Mostra pag. precedente" msgid "View next page" msgstr "Mostra pag. successiva" msgid "Close Spellbook" msgstr "Chiudi Libro Magie" msgid "View %{spell}" msgstr "Mostra %{spell}" #, fuzzy msgid "This spell does %{damage} points of damage." msgstr "%{tower} fa %{damage} punti di danno." #, fuzzy msgid "" "This spell summons\n" "%{count} %{monster}." msgstr "Getta %{spell} su %{monster}" msgid "This spell restores %{hp} HP." msgstr "" msgid "This spell summons %{count} %{monster} to guard the mine." msgstr "" msgid "The nearest town is %{town}." msgstr "" msgid "This town is occupied by your hero %{hero}." msgstr "" msgid "" "This spell controls up to\n" "%{hp} HP." msgstr "" msgid "The ultimate artifact is really the %{name}." msgstr "Ho sentito che l'Estremo Artefatto è in verità il/la %{name}." msgid "The ultimate artifact may be found in the %{name} regions of the world." msgstr "" "L'Estremo Artefatto può essere trovato in un parte del mondo con il/la " "%{name}." msgid "north-west" msgstr "nord-ovest" msgid "north" msgstr "nord" msgid "north-east" msgstr "nord-est" msgid "west" msgstr "ovest" msgid "center" msgstr "centro" msgid "east" msgstr "est" msgid "south-west" msgstr "sud-ovest" msgid "south" msgstr "sud" msgid "south-east" msgstr "sud-est" msgid "The truth is out there." msgstr "Lì fuori da qualche parte sta la verità." msgid "The dark side is stronger." msgstr "Il Lato Oscuro è più potente." msgid "The end of the world is near." msgstr "Sta arrivando La Fine del Mondo." msgid "The bones of Lord Slayer are buried in the foundation of the arena." msgstr "" "Le ossa di Lord Slayer sono state sepolte sotto il fondamento dell'arena." msgid "A Black Dragon will take out a Titan any day of the week." msgstr "Un Drago Nero farà fuori un Titano qualsiasi giorno della settimana." #, fuzzy msgid "He told her: Yada yada yada... and then she said: Blah, blah, blah..." msgstr "" "Egli ha detto a lei: Bla bla bla... e poi lei ha detto: Bla, bla, bla..." msgid "An unknown force is being resurrected..." msgstr "Una forza sconosciuta sta risorgendo..." #, fuzzy msgid "" "Check the newest version of the game at\n" "https://github.com/ihhub/\n" "fheroes2/releases" msgstr "" "Scopri l'ultima versione del gioco su\n" "https://github.com/ihhub/\n" "fheroes2/releases" #, fuzzy #~ msgid "Castles" #~ msgstr "Castello" #, fuzzy #~ msgid "Ocean object" #~ msgstr "Nessun oggetto" #, fuzzy #~ msgid "Snow Objects" #~ msgstr "Nessun oggetto" #, fuzzy #~ msgid "Desert Objects" #~ msgstr "Tenda da Deserto" #~ msgid "vs" #~ msgstr "contro" fheroes2-1.0.12+dfsg/files/lang/lt.po000066400000000000000000005720271456075706000172600ustar00rootroot00000000000000# Lithuanian translation for fheroes2 # Copyright (C) 2022 fheroes2 team # Copyright (C) 2012-2014 Mantas KriauÄiÅ«nas # This file is distributed under the same license as the fheroes2 package. # msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-03 03:27+0000\n" "PO-Revision-Date: 2022-09-23 16:14+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: Lithuanian \n" "Language: lt\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "(n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Launchpad-Export-Date: 2014-12-05 05:56+0000\n" "X-Generator: Poedit 3.1.1\n" msgid "" "BATTLE\n" "ONLY" msgstr "" msgid "" "NEW\n" "GAME" msgstr "" msgid "" "SAVE\n" "GAME" msgstr "" msgid "" "LOAD\n" "GAME" msgstr "" msgid "INFO" msgstr "" msgid "QUIT" msgstr "" msgid "CANCEL" msgstr "" msgid "OKAY" msgstr "" msgid "smallerButton|OKAY" msgstr "" msgid "ACCEPT" msgstr "" msgid "DECLINE" msgstr "" msgid "LEARN" msgstr "" msgid "TRADE" msgstr "" msgid "YES" msgstr "" msgid "NO" msgstr "" msgid "EXIT" msgstr "" msgid "smallerButton|EXIT" msgstr "" msgid "DISMISS" msgstr "" msgid "UPGRADE" msgstr "" msgid "RESTART" msgstr "" msgid "HEROES" msgstr "" msgid "" "TOWNS/\n" "CASTLES" msgstr "" msgid "S" msgstr "" msgid "M" msgstr "" msgid "L" msgstr "" msgid "X-L" msgstr "" msgid "ALL" msgstr "" msgid "SELECT" msgstr "" msgid "" "STANDARD\n" "GAME" msgstr "" msgid "" "CAMPAIGN\n" "GAME" msgstr "" msgid "" "MULTI-\n" "PLAYER\n" "GAME" msgstr "" msgid "CONFIG" msgstr "" msgid "" "ORIGINAL\n" "CAMPAIGN" msgstr "" msgid "" "EXPANSION\n" "CAMPAIGN" msgstr "" msgid "HOT SEAT" msgstr "" msgid "2 PLAYERS" msgstr "" msgid "3 PLAYERS" msgstr "" msgid "4 PLAYERS" msgstr "" msgid "5 PLAYERS" msgstr "" msgid "6 PLAYERS" msgstr "" msgid "GIFT" msgstr "" msgid "MAX" msgstr "" msgid "DIFFICULTY" msgstr "" msgid "VIEW INTRO" msgstr "" msgid "MIN" msgstr "" msgid "RESET" msgstr "" msgid "START" msgstr "" msgid "CASTLE" msgstr "" msgid "TOWN" msgstr "" msgid "" "D\n" "I\n" "S\n" "M\n" "I\n" "S\n" "S" msgstr "" msgid "" "E\n" "X\n" "I\n" "T" msgstr "" msgid "" "C\n" "A\n" "M\n" "P\n" "A\n" "I\n" "G\n" "N" msgstr "" msgid "" "S\n" "T\n" "A\n" "N\n" "D\n" "A\n" "R\n" "D" msgstr "" msgid "Warrior" msgstr "" msgid "Builder" msgstr "" msgid "Explorer" msgstr "" msgid "None" msgstr "" msgid "" "A few\n" "%{monster}" msgstr "" "Mažai\n" "%{monster}" msgid "" "Several\n" "%{monster}" msgstr "" "Keletas\n" "%{monster}" msgid "" "A pack of\n" "%{monster}" msgstr "" "Keliolika\n" "%{monster}" msgid "" "Lots of\n" "%{monster}" msgstr "" msgid "" "A horde of\n" "%{monster}" msgstr "" msgid "" "A throng of\n" "%{monster}" msgstr "" msgid "" "A swarm of\n" "%{monster}" msgstr "" msgid "" "Zounds...\n" "%{monster}" msgstr "" msgid "" "A legion of\n" "%{monster}" msgstr "" msgid "army|Few" msgstr "" msgid "army|Several" msgstr "" msgid "army|Pack" msgstr "" msgid "army|Lots" msgstr "" msgid "army|Horde" msgstr "" msgid "army|Throng" msgstr "" msgid "army|Swarm" msgstr "" msgid "army|Zounds" msgstr "" msgid "army|Legion" msgstr "" msgid "All %{race} troops +1" msgstr "" msgid "Multiple" msgstr "" msgid "Troops of %{count} alignments -%{penalty}" msgstr "" msgid "Some undead in army -1" msgstr "" msgid "View %{name}" msgstr "ŽiÅ«rÄ—ti %{name}" #, fuzzy msgid "Move the %{name} " msgstr "Prarasti miestÄ… '%{name}'." msgid "Move or right click to redistribute %{name}" msgstr "" msgid "Combine %{name} armies" msgstr "" msgid "Exchange %{name2} with %{name}" msgstr "Sukeisti %{name2} su %{name}" msgid "Select %{name}" msgstr "Pasirinkti %{name}" msgid "Cannot move last troop" msgstr "" #, fuzzy msgid "Move the %{name}" msgstr "Prarasti miestÄ… '%{name}'." msgid "Set Count" msgstr "" msgid "%{name} destroys half the enemy troops!" msgid_plural "%{name} destroy half the enemy troops!" msgstr[0] "" msgstr[1] "" msgstr[2] "" #, fuzzy msgid "%{name} has turned off the auto battle" msgstr "Grįžti į mūšį." #, fuzzy msgid "%{name} has turned on the auto battle" msgstr "Grįžti į mūšį." msgid "Spell failed!" msgstr "" msgid "" "The Sphere of Negation artifact is in effect for this battle, disabling all " "combat spells." msgstr "" msgid "You have already cast a spell this round." msgstr "" msgid "That spell will have no effect!" msgstr "" msgid "You may only summon one type of elemental per combat." msgstr "" msgid "" "There is no open space adjacent to your hero where you can summon an " "Elemental to." msgstr "" msgid "" "The Moat reduces by -%{count} the defense skill of any unit and slows to " "half movement rate." msgstr "" msgid "Speed: %{speed}" msgstr "" msgid "Speed" msgstr "Greitis" msgid "Off" msgstr "" msgid "On" msgstr "" msgid "Turn Order" msgstr "" msgid "Auto Spell Casting" msgstr "" msgid "Grid" msgstr "" msgid "Shadow Movement" msgstr "" msgid "Shadow Cursor" msgstr "" msgid "Audio" msgstr "" msgid "Settings" msgstr "" msgid "Configure" msgstr "" msgid "Hot Keys" msgstr "" msgid "Damage Info" msgstr "" msgid "Set the speed of combat actions and animations." msgstr "" msgid "Toggle to display the turn order during the battle." msgstr "" msgid "" "Toggle whether or not the computer will cast spells for you when auto combat " "is on. (Note: This does not affect spell casting for computer players in any " "way, nor does it affect quick combat.)" msgstr "" msgid "" "Toggle the hex grid on or off. The hex grid always underlies movement, even " "if turned off. This switch only determines if the grid is visible." msgstr "" msgid "" "Toggle on or off shadows showing where your creatures can move and attack." msgstr "" msgid "" "Toggle on or off a shadow showing the current hex location of the mouse " "cursor." msgstr "" msgid "Change the audio settings of the game." msgstr "" msgid "Check and configure all the hot keys present in the game." msgstr "" msgid "Toggle to display damage information during the battle." msgstr "" #, fuzzy msgid "Exit this menu." msgstr "Å is karys palauks" msgid "Okay" msgstr "Gerai" msgid "The enemy has surrendered!" msgstr "PrieÅ¡as pasidavÄ—!" msgid "Their cowardice costs them %{gold} gold." msgstr "" msgid "The enemy has fled!" msgstr "PrieÅ¡as pabÄ—go!" msgid "A glorious victory!" msgstr "Å lovinga pergalÄ—!" msgid "For valor in combat, %{name} receives %{exp} experience." msgstr "" msgid "The cowardly %{name} flees from battle." msgstr "" msgid "%{name} surrenders to the enemy, and departs in shame." msgstr "" msgid "Your forces suffer a bitter defeat, and %{name} abandons your cause." msgstr "" msgid "Your forces suffer a bitter defeat." msgstr "" msgid "Battlefield Casualties" msgstr "" msgid "Attacker" msgstr "" msgid "Defender" msgstr "" msgid "Click to leave the battle results." msgstr "" #, fuzzy msgid "Click to restart the battle in manual mode." msgstr "" "Ar tikrai norite perraÅ¡yti anksÄiau tuo paÄiu pavadinimu įraÅ¡ytÄ… failÄ…?" msgid "Restart" msgstr "" msgid "You have captured an enemy artifact!" msgstr "JÅ«s gavote magiÅ¡kÄ… artefaktÄ… iÅ¡ prieÅ¡o!" msgid "As you reach for the %{name}, it mysteriously disappears." msgstr "" msgid "As your enemy reaches for the %{name}, it mysteriously disappears." msgstr "" msgid "Necromancy!" msgstr "" msgid "" "Practicing the dark arts of necromancy, you are able to raise %{count} of " "the enemy's dead to return under your service as %{monster}." msgstr "" msgid "%{name} the %{race}" msgstr "" #, fuzzy msgid "Captain of %{name}" msgstr "Pasamdyti %{name}" msgid "Attack" msgstr "Puolimas" msgid "Defense" msgstr "Gynyba" msgid "Spell Power" msgstr "Burtų galia" msgid "Knowledge" msgstr "IÅ¡manymas" msgid "Morale" msgstr "" msgid "Luck" msgstr "" msgid "Spell Points" msgstr "" msgid "Hero's Options" msgstr "" msgid "Cast Spell" msgstr "" msgid "Retreat" msgstr "" msgid "Surrender" msgstr "Pasiduoti" msgid "Cancel" msgstr "Atsisakyti" msgid "Hero Screen" msgstr "" #, fuzzy msgid "Captain's Options" msgstr "IÅ¡eiti iÅ¡ pilies" msgid "" "Cast a magical spell. You may only cast one spell per combat round. The " "round is reset when every creature has had a turn." msgstr "" msgid "" "Retreat your hero, abandoning your creatures. Your hero will be available " "for you to recruit again, however, the hero will have only a novice hero's " "forces." msgstr "" msgid "" "Surrendering costs gold. However if you pay the ransom, the hero and all of " "his or her surviving creatures will be available to recruit again. The cost " "of surrender is half of the total cost of the non-temporary troops remaining " "in the army." msgstr "" msgid "Open Hero Screen to view full information about the hero." msgstr "" msgid "Return to the battle." msgstr "Grįžti į mūšį." msgid "Not enough gold (%{gold})" msgstr "Neužtenka aukso (%{gold})" msgid "%{name} states:" msgstr "" #, fuzzy msgid "Captain of %{name} states:" msgstr "Pasamdyti %{name}" msgid "" "\"I will accept your surrender and grant you and your troops safe passage " "for the price of %{price} gold.\"" msgstr "" msgid "View %{monster} info" msgstr "ŽiÅ«rÄ—ti %{monster} informacijÄ…" msgid "Fly %{monster} here" msgstr "" msgid "Move %{monster} here" msgstr "" #, fuzzy msgid "Shoot %{monster}" msgstr "Pulti %{monster}" msgid "(1 shot left)" msgid_plural "(%{count} shots left)" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "Attack %{monster}" msgstr "Pulti %{monster}" msgid "Turn %{turn}" msgstr "" msgid "Teleport here" msgstr "" msgid "Invalid teleport destination" msgstr "" msgid "Cast %{spell} on %{monster}" msgstr "" msgid "Cast %{spell}" msgstr "" msgid "Select spell target" msgstr "" msgid "View Ballista info" msgstr "" msgid "Ballista" msgstr "" msgid "Enable auto combat" msgstr "" msgid "Allows the computer to fight out the battle for you." msgstr "" msgid "Auto Combat" msgstr "" msgid "Customize system options" msgstr "" msgid "Allows you to customize the combat screen." msgstr "" msgid "System Options" msgstr "" msgid "Skip this unit" msgstr "Å is karys praleis Ä—jimÄ…" msgid "Skip" msgstr "" msgid "" "Skips the current creature. The current creature ends its turn and does not " "get to go again until the next round." msgstr "" msgid "View Captain's options" msgstr "" msgid "View Hero's options" msgstr "" msgid "View opposing Captain" msgstr "" msgid "View opposing Hero" msgstr "" msgid "Hide logs" msgstr "" msgid "Show logs" msgstr "" msgid "Message Bar" msgstr "" msgid "Shows the results of individual monster's actions." msgstr "" #, fuzzy msgid "Are you sure you want to finish the battle in auto mode?" msgstr "" "Ar tikrai norite perraÅ¡yti anksÄiau tuo paÄiu pavadinimu įraÅ¡ytÄ… failÄ…?" #, fuzzy msgid "Are you sure you want to enable auto combat?" msgstr "" "Ar tikrai norite perraÅ¡yti anksÄiau tuo paÄiu pavadinimu įraÅ¡ytÄ… failÄ…?" msgid "%{name} skip their turn." msgstr "" msgid "%{attacker} does %{damage} damage." msgid_plural "%{attacker} do %{damage} damage." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "1 creature perishes." msgid_plural "%{count} creatures perish." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "1 %{defender} perishes." msgid_plural "%{count} %{defender} perish." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "1 soul is incorporated." msgid_plural "%{count} souls are incorporated." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "1 %{unit} is revived." msgid_plural "%{count} %{unit} are revived." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "Moved %{monster}: from [%{src}] to [%{dst}]." msgstr "" msgid "The %{name} resist the spell!" msgstr "" msgid "%{name} casts %{spell} on the %{troop}." msgstr "" msgid "%{name} casts %{spell}." msgstr "" msgid "The %{spell} does %{damage} damage to one undead creature." msgstr "" msgid "The %{spell} does %{damage} damage to all undead creatures." msgstr "" msgid "The %{spell} does %{damage} damage, %{count} creatures perish." msgstr "" msgid "The %{spell} does %{damage} damage." msgstr "" msgid "The %{spell} does %{damage} damage to one living creature." msgstr "" msgid "The %{spell} does %{damage} damage to all living creatures." msgstr "" msgid "The %{attacker}' attack blinds the %{target}!" msgstr "" msgid "The %{attacker}' gaze turns the %{target} to stone!" msgstr "" msgid "The %{attacker}' curse falls upon the %{target}!" msgstr "" msgid "The %{target} are paralyzed by the %{attacker}!" msgstr "" msgid "The %{attacker} dispel all good spells on your %{target}!" msgstr "" msgid "The %{attacker} cast %{spell} on %{target}!" msgstr "" msgid "Bad luck descends on the %{attacker}." msgstr "" msgid "Good luck shines on the %{attacker}." msgstr "" msgid "High morale enables the %{monster} to attack again." msgstr "" msgid "Low morale causes the %{monster} to freeze in panic." msgstr "" msgid "%{tower} does %{damage} damage." msgstr "" msgid "The mirror image is created." msgstr "" msgid "The mirror image is destroyed!" msgstr "" #, fuzzy msgid "Are you sure you want to interrupt the auto battle?" msgstr "" "Ar tikrai norite perraÅ¡yti anksÄiau tuo paÄiu pavadinimu įraÅ¡ytÄ… failÄ…?" msgid "Error" msgstr "" msgid "No spells to cast." msgstr "" msgid "Are you sure you want to retreat?" msgstr "" msgid "Retreat disabled" msgstr "" #, fuzzy msgid "Surrender disabled" msgstr "Pasiduoti" msgid "Damage: %{max}" msgstr "Žala: %{max}" msgid "Damage: %{min} - %{max}" msgstr "" msgid "Perish: %{max}" msgstr "" msgid "Perish: %{min} - %{max}" msgstr "" msgid "" "Through eagle-eyed observation, %{name} is able to learn the magic spell " "%{spell}." msgstr "" msgid "Human" msgstr "" msgid "AI" msgstr "" msgid "Start" msgstr "" #, fuzzy msgid "Start the battle." msgstr "Grįžti į mūšį." #, fuzzy msgid "Exit" msgstr "IÅ¡eiti iÅ¡ miesto" msgid "Reset" msgstr "" msgid "Reset to default settings." msgstr "" msgid "Please select another hero." msgstr "" #, fuzzy msgid "Set Attack Skill" msgstr "Puolimo įgÅ«dis" #, fuzzy msgid "Set Defense Skill" msgstr "Gynybos įgÅ«dis" #, fuzzy msgid "Set Power Skill" msgstr "Burtų galia" #, fuzzy msgid "Set Knowledge Skill" msgstr "IÅ¡manymas" #, fuzzy msgid "%{race1} %{name1} vs %{race2} %{name2}" msgstr "ŽiÅ«rÄ—ti %{name}" #, fuzzy msgid "Monsters" msgstr "AtsitiktinÄ— pilis" msgid "N/A" msgstr "" msgid "Left Turret" msgstr "Kairysis bokÅ¡telis" msgid "Right Turret" msgstr "DeÅ¡inysis bokÅ¡telis" msgid "The %{name} fires with the strength of %{count} Archers" msgstr "" msgid "each with a +%{attack} bonus to their attack skill." msgstr "" msgid "The %{name} is destroyed." msgstr "" msgid "%{count} %{name} rises from the dead!" msgid_plural "%{count} %{name} rise from the dead!" msgstr[0] "" msgstr[1] "" msgstr[2] "" #, fuzzy msgid "Dwarven Alliance" msgstr "Drakonų miestas" #, fuzzy msgid "Sorceress Guild" msgstr "Vagių gildija" msgid "Necromancer Guild" msgstr "" #, fuzzy msgid "Ogre Alliance" msgstr "Drakonų miestas" msgid "Dwarfbane" msgstr "" #, fuzzy msgid "Dragon Alliance" msgstr "Drakonų miestas" msgid "Elven Alliance" msgstr "" msgid "Kraeger defeated" msgstr "" msgid "Wayward Son" msgstr "" msgid "Uncle Ivan" msgstr "" msgid "Annexation" msgstr "" msgid "Force of Arms" msgstr "" msgid "Save the Dwarves" msgstr "" #, fuzzy msgid "Carator Mines" msgstr "Kristalų kasykla" msgid "Turning Point" msgstr "" msgid "scenarioName|Defender" msgstr "" msgid "Corlagon's Defense" msgstr "" #, fuzzy msgid "The Crown" msgstr "Rytų miestas." msgid "The Gauntlet" msgstr "" msgid "Betrayal" msgstr "" msgid "Final Justice" msgstr "" msgid "" "Roland needs you to defeat the lords near his castle to begin his war of " "rebellion against his brother. They are not allied with each other, so they " "will spend most of their time fighting with one another. Victory is yours " "when you have defeated all of their castles and heroes." msgstr "" msgid "" "The local lords refuse to swear allegiance to Roland, and must be subdued. " "They are wealthy and powerful, so be prepared for a tough fight. Capture all " "enemy castles to win." msgstr "" msgid "" "Your task is to defend the Dwarves against Archibald's forces. Capture all " "of the enemy towns and castles to win, and be sure not to lose all of the " "dwarf towns at once, or the enemy will have won." msgstr "" msgid "" "You will face four allied enemies in a straightforward fight for resource " "and treasure. Capture all of the enemy castles for victory." msgstr "" msgid "" "Your enemies are allied against you and start close by, so be ready to come " "out fighting. You will need to own all four castles in this small valley to " "win." msgstr "" msgid "" "The Sorceress' guild of Noraston has requested Roland's aid against an " "attack from Archibald's allies. Capture all of the enemy castles to win, and " "don't lose Noraston, or you'll lose the scenario. (Hint: There is an enemy " "castle on an island in the ocean.)" msgstr "" msgid "" "Gather as large an army as possible and capture the enemy castle within 8 " "weeks. You are opposed by only one enemy, but must travel a long way to get " "to the enemy castle. Any troops you have in your army at the end of this " "scenario will be with you in the final battle." msgstr "" msgid "" "Find the Crown before Archibald's heroes find it. Roland will need the Crown " "for the final battle against Archibald." msgstr "" msgid "" "Three allied enemies stand before you and victory, including Lord Corlagon. " "Roland is in a castle to the northwest, and you will lose if he falls to the " "enemy. Remember that capturing Lord Corlagon will ensure that he will not " "fight against you in the final scenario." msgstr "" msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Archibald to end the war!" msgstr "" msgid "" "Switching sides leaves you with three castles against the enemy's one. This " "battle will be the easiest one you will face for the rest of the war..." "traitor." msgstr "" msgid "Barbarian Wars" msgstr "" msgid "First Blood" msgstr "" msgid "Necromancers" msgstr "" msgid "Slay the Dwarves" msgstr "" msgid "Country Lords" msgstr "" msgid "Dragon Master" msgstr "" msgid "Rebellion" msgstr "" msgid "Apocalypse" msgstr "" msgid "Greater Glory" msgstr "" msgid "" "King Archibald requires you to defeat the three enemies in this region. They " "are not allied with one another, so they will spend most of their energy " "fighting amongst themselves. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" msgid "" "You must unify the barbarian tribes of the north by conquering them. As in " "the previous mission, the enemy is not allied against you, but they have " "more resources at their disposal. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" msgid "" "Do-gooder wizards have taken the Necromancers' castle. You must retake it to " "achieve victory. Remember that while you start with a powerful army, you " "have no castle and must take one within 7 days, or lose this battle. (Hint: " "The nearest castle is to the southeast.)" msgstr "" msgid "" "The dwarves need conquering before they can interfere in King Archibald's " "plans. Roland's forces have more than one hero and many towns to start with, " "so be ready for attack from multiple directions. You must capture all of the " "enemy towns and castles to claim victory." msgstr "" msgid "" "You must put down a peasant revolt led by Roland's forces. All are allied " "against you, but you have Lord Corlagon, an experienced hero, to help you. " "Capture all enemy castles to win." msgstr "" msgid "" "There are two enemies allied against you in this mission. Both are well " "armed and seek to evict you from their island. Avoid them and capture Dragon " "City to win." msgstr "" msgid "" "Your orders are to conquer the country lords that have sworn to serve " "Roland. All of the enemy castles are unified against you. Since you start " "without a castle, you must hurry to capture one before the end of the week. " "Capture all enemy castles for victory." msgstr "" msgid "" "Find the Crown before Roland's heroes find it. Archibald will need the Crown " "for the final battle against Roland." msgstr "" msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Roland to win the war, and be sure " "not to lose Archibald in the fight!" msgstr "" msgid "Arrow's Flight" msgstr "" msgid "Island of Chaos" msgstr "" msgid "The Abyss" msgstr "" msgid "Uprising" msgstr "" msgid "Aurora Borealis" msgstr "" msgid "Betrayal's End" msgstr "" msgid "Corruption's Heart" msgstr "" msgid "The Giant's Pass" msgstr "" msgid "" "Subdue the unruly local lords in order to provide the Empire with facilities " "to operate in this region." msgstr "" msgid "" "Eliminate all opposition in this area. Then the first piece of the artifact " "will be yours." msgstr "" msgid "" "The sorceresses to the northeast are rebelling! For the good of the empire " "you must quash their feeble uprising on your way to the mountains." msgstr "" msgid "" "Having prepared for your arrival, Kraeger has arranged for a force of " "necromancers to thwart your quest. You must capture the castle of Scabsdale " "before the first day of the third week, or the Necromancers will be too " "strong for you." msgstr "" msgid "" "The barbarian despot in this area is, as yet, ignorant of your presence. " "Quickly, build up your forces before you are discovered and attacked! Secure " "the region by subduing all enemy forces." msgstr "" msgid "" "The Empire is weak in this region. You will be unable to completely subdue " "all forces in this area, so take what you can before reprisal strikes. " "Remember, your true goal is to claim the Helmet of Anduran." msgstr "" msgid "For the good of the Empire, eliminate Kraeger." msgstr "" msgid "" "At last, you have the opportunity and the facilities to rid the Empire of " "the necromancer's evil. Eradicate them completely, and you will be sung as a " "hero for all time." msgstr "" #, fuzzy msgid "Border Towns" msgstr "Rodyti miestus" msgid "Conquer and Unify" msgstr "" msgid "Crazy Uncle Ivan" msgstr "" msgid "The Wayward Son" msgstr "" msgid "Ivory Gates" msgstr "" msgid "The Elven Lands" msgstr "" msgid "The Epic Battle" msgstr "" msgid "The Southern War" msgstr "" msgid "" "Conquer and unite all the enemy tribes. Don't lose the hero Jarkonas, the " "forefather of all descendants." msgstr "" msgid "" "Your rival, the Kingdom of Harondale, is attacking weak towns on your " "border! Recover from their first strike and crush them completely!" msgstr "" msgid "" "Find your wayward son Joseph who is rumored to be living in the desolate " "lands. Do it before the first day of the third month or it will be of no " "help to your family." msgstr "" msgid "" "Rescue your crazy uncle Ivan. Find him before the first day of the fourth " "month or it will be no help to your kingdom." msgstr "" msgid "" "Destroy the barbarians who are attacking the southern border of your " "kingdom! Recover your fallen towns, and then invade the jungle kingdom. " "Leave no enemy standing." msgstr "" msgid "Retake the castle of Ivory Gates, which has fallen due to treachery." msgstr "" msgid "" "Gain the favor of the elves. They will not allow trees to be chopped down, " "so they will send you wood every 2 weeks. You must complete your mission " "before the first day of the seventh month, or the kingdom will surely fall." msgstr "" msgid "" "This is the final battle against your rival kingdom of Harondale. Eliminate " "everyone, and don't lose the hero Jarkonas VI." msgstr "" msgid "Fount of Wizardry" msgstr "" msgid "Power's End" msgstr "" #, fuzzy msgid "The Eternal Scrolls" msgstr "Rytų miestas." msgid "The Shrouded Isles" msgstr "" msgid "" "Your mission is to vanquish the warring mages in the magical Shrouded Isles. " "The completion of this task will give you a fighting chance against your " "rivals." msgstr "" msgid "" "The location of the great library has been discovered! You must make your " "way to it, and reclaim the city of Chronos in which it lies." msgstr "" msgid "" "Find the Orb of negation, which is said to be buried in this land. There are " "clues inscribed on stone obelisks which will help lead you to your prize. " "Find the Orb before the first day of the sixth month, or your rivals will " "surely have gotten to the fount before you." msgstr "" msgid "" "You must take control of the castle of Magic, where the fount of wizardry " "lies. Do this and your victory will be supreme." msgstr "" msgid "Blood is Thicker" msgstr "" msgid "King and Country" msgstr "" msgid "Pirate Isles" msgstr "" msgid "Stranded" msgstr "" msgid "" "Capture the town on the island off the southeast shore in order to construct " "a boat and travel back towards the mainland. Do not lose the hero Gallavant." msgstr "" msgid "" "Find and defeat Martine, the pirate leader, who resides in Pirates Cove. Do " "not lose Gallavant or your quest will be over." msgstr "" msgid "" "Eliminate all the other forces who oppose the rule of Lord Alberon. " "Gallavant must not die." msgstr "" msgid "" "Overthrow the entrenched monarchy of Lord Alberon, and claim all the land in " "your name. Gallavant must not die." msgstr "" msgid " bane" msgstr "" msgid " alliance" msgstr "" msgid "Carry-over forces" msgstr "" msgid " bonus" msgstr "" msgid " defeated" msgstr "" msgid " will always run away from your army." msgstr "" msgid " will be willing to join your army." msgstr "" msgid "\"%{artifact}\" artifact will be carried over the scenario." msgstr "" msgid "The army will be carried over the scenario." msgstr "" msgid "The kingdom will have +%{count} %{resource} each day." msgstr "" msgid "\"%{spell}\" spell will be carried over the scenario." msgstr "" msgid "%{hero} can be hired in the scenario." msgstr "" msgid "" "%{hero} has been defeated and will not appear in the subsequent scenarios." msgstr "" msgid "The dwarves recognize their allies and gladly join your forces." msgstr "" msgid "The ogres recognize you as the Dwarfbane and lumber over to join you." msgstr "" msgid "" "The dragons, snarling and growling, agree to join forces with you, their " "'Ally'." msgstr "" msgid "" "As you approach the group of elves, their leader calls them all to " "attention. He shouts to them, \"Who of you is brave enough to join this " "fearless ally of ours?\" The group explodes with cheers as they run to join " "your ranks." msgstr "" msgid "" "The dwarves hail you, \"Any friend of Roland is a friend of ours. You may " "pass.\"" msgstr "" msgid "" "The ogres give you a grunt of recognition, \"Archibald's allies may pass.\"" msgstr "" msgid "" "The dragons see you and call out. \"Our alliance with Archibald compels us " "to join you. Unfortunately you have no room. A pity!\" They quickly scatter." msgstr "" msgid "" "The elves stand at attention as you approach. Their leader calls to you and " "says, \"Let us not impede your progress, ally! Move on, and may victory be " "yours.\"" msgstr "" msgid "\"The Dwarfbane!!!!, run for your lives.\"" msgstr "" msgid "campaignBonus|Animate Dead" msgstr "" msgid "campaignBonus|Chain Lightning" msgstr "" msgid "campaignBonus|Fireblast" msgstr "" msgid "campaignBonus|Mass Curse" msgstr "" msgid "campaignBonus|Mass Haste" msgstr "" msgid "campaignBonus|Mirror Image" msgstr "" msgid "campaignBonus|Resurrect" msgstr "" #, fuzzy msgid "campaignBonus|Steelskin" msgstr "Gynybos įgÅ«dis" msgid "campaignBonus|Summon Earth" msgstr "" msgid "campaignBonus|View Heroes" msgstr "" msgid "campaignBonus|Ballista" msgstr "" msgid "campaignBonus|Black Pearl" msgstr "" msgid "campaignBonus|Caster's Bracelet" msgstr "" #, fuzzy msgid "campaignBonus|Defender Helm" msgstr "Gynybos įgÅ«dis" #, fuzzy msgid "campaignBonus|Breastplate" msgstr "Turgus" #, fuzzy msgid "campaignBonus|Dragon Sword" msgstr "Drakonų miestas" msgid "campaignBonus|Fizbin Medal" msgstr "" msgid "campaignBonus|Foremost Scroll" msgstr "" #, fuzzy msgid "campaignBonus|Gauntlets" msgstr "Turgus" msgid "campaignBonus|Hideous Mask" msgstr "" msgid "campaignBonus|Mage's Ring" msgstr "" msgid "campaignBonus|Major Scroll" msgstr "" msgid "campaignBonus|Medal of Honor" msgstr "" msgid "campaignBonus|Medal of Valor" msgstr "" msgid "campaignBonus|Minor Scroll" msgstr "" msgid "campaignBonus|Nomad Boots" msgstr "" msgid "campaignBonus|Power Axe" msgstr "" msgid "campaignBonus|Spiked Shield" msgstr "" msgid "campaignBonus|Stealth Shield" msgstr "" msgid "campaignBonus|Tax Lien" msgstr "" msgid "campaignBonus|Thunder Mace" msgstr "" msgid "campaignBonus|Traveler's Boots" msgstr "" msgid "campaignBonus|White Pearl" msgstr "" #, fuzzy msgid "campaignBonus|Basic Archery" msgstr "Turgus" #, fuzzy msgid "campaignBonus|Advanced Archery" msgstr "Pažangus Å¡audymas" #, fuzzy msgid "campaignBonus|Expert Archery" msgstr "Eksperto Å¡audymas" #, fuzzy msgid "campaignBonus|Basic Ballistics" msgstr "Turgus" #, fuzzy msgid "campaignBonus|Advanced Ballistics" msgstr "Gynybos įgÅ«dis" #, fuzzy msgid "campaignBonus|Expert Ballistics" msgstr "Gynybos įgÅ«dis" #, fuzzy msgid "campaignBonus|Basic Diplomacy" msgstr "Turgus" #, fuzzy msgid "campaignBonus|Advanced Diplomacy" msgstr "Pažangi diplomatija" #, fuzzy msgid "campaignBonus|Expert Diplomacy" msgstr "Eksperto diplomatija" #, fuzzy msgid "campaignBonus|Basic Eagle Eye" msgstr "Turgus" #, fuzzy msgid "campaignBonus|Advanced Eagle Eye" msgstr "Gynybos įgÅ«dis" #, fuzzy msgid "campaignBonus|Expert Eagle Eye" msgstr "Turgus" #, fuzzy msgid "campaignBonus|Basic Estates" msgstr "Turgus" #, fuzzy msgid "campaignBonus|Advanced Estates" msgstr "Turgus" #, fuzzy msgid "campaignBonus|Expert Estates" msgstr "Turgus" #, fuzzy msgid "campaignBonus|Basic Leadership" msgstr "PradinÄ— lyderystÄ—" #, fuzzy msgid "campaignBonus|Advanced Leadership" msgstr "Pažangi lyderystÄ—" #, fuzzy msgid "campaignBonus|Expert Leadership" msgstr "Eksperto lyderystÄ—" #, fuzzy msgid "campaignBonus|Basic Logistics" msgstr "Turgus" #, fuzzy msgid "campaignBonus|Advanced Logistics" msgstr "Drakonų miestas" #, fuzzy msgid "campaignBonus|Expert Logistics" msgstr "Gynybos įgÅ«dis" #, fuzzy msgid "campaignBonus|Basic Luck" msgstr "Turgus" #, fuzzy msgid "campaignBonus|Advanced Luck" msgstr "Drakonų miestas" #, fuzzy msgid "campaignBonus|Expert Luck" msgstr "Gynybos įgÅ«dis" #, fuzzy msgid "campaignBonus|Basic Mysticism" msgstr "Turgus" #, fuzzy msgid "campaignBonus|Advanced Mysticism" msgstr "Gynybos įgÅ«dis" #, fuzzy msgid "campaignBonus|Expert Mysticism" msgstr "Gynybos įgÅ«dis" #, fuzzy msgid "campaignBonus|Basic Navigation" msgstr "PradinÄ— navigacija" #, fuzzy msgid "campaignBonus|Advanced Navigation" msgstr "Pažangi navigacija" #, fuzzy msgid "campaignBonus|Expert Navigation" msgstr "Eksperto navigacija" #, fuzzy msgid "campaignBonus|Basic Necromancy" msgstr "Turgus" #, fuzzy msgid "campaignBonus|Advanced Necromancy" msgstr "Gynybos įgÅ«dis" #, fuzzy msgid "campaignBonus|Expert Necromancy" msgstr "Gynybos įgÅ«dis" #, fuzzy msgid "campaignBonus|Basic Pathfinding" msgstr "Turgus" #, fuzzy msgid "campaignBonus|Advanced Pathfinding" msgstr "Drakonų miestas" #, fuzzy msgid "campaignBonus|Expert Pathfinding" msgstr "Turgus" #, fuzzy msgid "campaignBonus|Basic Scouting" msgstr "Gynybos įgÅ«dis" #, fuzzy msgid "campaignBonus|Advanced Scouting" msgstr "Drakonų miestas" #, fuzzy msgid "campaignBonus|Expert Scouting" msgstr "Gynybos įgÅ«dis" #, fuzzy msgid "campaignBonus|Basic Wisdom" msgstr "Turgus" #, fuzzy msgid "campaignBonus|Advanced Wisdom" msgstr "Pažangi iÅ¡mintis" #, fuzzy msgid "campaignBonus|Expert Wisdom" msgstr "Gynybos įgÅ«dis" msgid "" "The main hero will have \"%{artifact}\" artifact at the start of the " "scenario." msgstr "" msgid "" "The kingdom will receive %{amount} additional %{resource} at the start of " "the scenario." msgstr "" msgid "" "The kingdom will have %{amount} less %{resource} at the start of the " "scenario." msgstr "" msgid "" "The main hero will have %{count} %{monster} at the start of the scenario." msgstr "" msgid "" "The main hero will have \"%{spell}\" spell at the start of the scenario." msgstr "" msgid "The starting race of the scenario will be %{race}." msgstr "" msgid "" "The main hero will have additional %{count} %{skill} at the start of the " "scenario." msgstr "" msgid "The main hero will have %{skill} at the start of the scenario." msgstr "" msgid "Roland" msgstr "" msgid "Archibald" msgstr "" msgid "Price of Loyalty" msgstr "" msgid "Voyage Home" msgstr "" msgid "Wizard's Isle" msgstr "" msgid "Descendants" msgstr "" msgid "The %{building} produces %{monster}." msgstr "" msgid "Requires:" msgstr "Reikalauja:" msgid "Exit this menu without doing anything." msgstr "" msgid "Cannot build. You have already built here today." msgstr "" msgid "For this action it is necessary to build a castle first." msgstr "Pradžioje bÅ«tina pastatyti pilį." #, fuzzy msgid "Cannot build %{name} because the castle is too far away from an ocean." msgstr "Negalima statyti %{name} kadangi pilis yra per toli nuo vandens." msgid "disable build." msgstr "" msgid "Cannot afford %{name}." msgstr "" msgid "%{name} is already built." msgstr "" msgid "Cannot build %{name}." msgstr "" #, fuzzy msgid "Build %{name}." msgstr "ŽiÅ«rÄ—ti %{name}" #, fuzzy msgid "Blackridge" msgstr "Tiltas" msgid "Hillstone" msgstr "" msgid "Pinehurst" msgstr "" msgid "Whiteshield" msgstr "" #, fuzzy msgid "Woodhaven" msgstr "Mediena" #, fuzzy msgid "Blackwind" msgstr "KalvÄ—" msgid "Bloodreign" msgstr "" #, fuzzy msgid "Dragontooth" msgstr "Drakonų miestas" msgid "Greywind" msgstr "" msgid "Portsmith" msgstr "" msgid "Atlantium" msgstr "" msgid "Middle Gate" msgstr "" msgid "Sansobar" msgstr "" msgid "Tundara" msgstr "" msgid "Vulcania" msgstr "" msgid "Baywatch" msgstr "" msgid "Fountainhead" msgstr "" msgid "Vertigo" msgstr "" msgid "Wildabar" msgstr "" msgid "Winterkill" msgstr "" msgid "Brindamoor" msgstr "" msgid "Lakeside" msgstr "" msgid "Nightshadow" msgstr "" msgid "Olympus" msgstr "" msgid "Sandcaster" msgstr "" msgid "Alamar" msgstr "" msgid "Burlock" msgstr "" #, fuzzy msgid "Dragadune" msgstr "Drakonų miestas" msgid "Kalindra" msgstr "" msgid "Xabran" msgstr "" msgid "Algary" msgstr "" msgid "Basenji" msgstr "" #, fuzzy msgid "Blackfang" msgstr "Juodas bokÅ¡tas" msgid "New Dawn" msgstr "" msgid "Sorpigal" msgstr "" msgid "Avone" msgstr "" msgid "Big Oak" msgstr "" msgid "Chandler" msgstr "" msgid "Erliquin" msgstr "" msgid "Hampshire" msgstr "" msgid "Antioch" msgstr "" msgid "Avalon" msgstr "" msgid "Roc Haven" msgstr "" msgid "South Mill" msgstr "" msgid "Weed Patch" msgstr "" msgid "Brownston" msgstr "" msgid "Hilltop" msgstr "" msgid "Weddington" msgstr "" msgid "Westfork" msgstr "" msgid "Whittingham" msgstr "" msgid "Cathcart" msgstr "" msgid "Elk's Head" msgstr "" msgid "Roscomon" msgstr "" msgid "Sherman" msgstr "" msgid "Yorksford" msgstr "" #, fuzzy msgid "Blackburn" msgstr "Juodas bokÅ¡tas" #, fuzzy msgid "Blacksford" msgstr "Juodas bokÅ¡tas" msgid "Burton" msgstr "" msgid "Pig's Eye" msgstr "" msgid "Viper's Nest" msgstr "" msgid "Fenton" msgstr "" msgid "Lankershire" msgstr "" msgid "Lombard" msgstr "" msgid "Timberhill" msgstr "" msgid "Troy" msgstr "" #, fuzzy msgid "Forder Oaks" msgstr "Rodyti miestus" msgid "Meramec" msgstr "" msgid "Quick Silver" msgstr "" msgid "Westmoor" msgstr "" msgid "Willow" msgstr "" msgid "Corackston" msgstr "" msgid "Sheltemburg" msgstr "" msgid "Cannot recruit - you already have a Hero in this town." msgstr "" msgid "Cannot recruit - you have too many Heroes." msgstr "" msgid "Cannot afford a Hero" msgstr "" msgid "There is no room in the garrison for this army." msgstr "" msgid "Fortifications" msgstr "Ä®tvirtinimai" msgid "Farm" msgstr "Ferma" msgid "Thatched Hut" msgstr "" msgid "Archery Range" msgstr "" msgid "Upg. Archery Range" msgstr "" msgid "Blacksmith" msgstr "KalvÄ—" msgid "Upg. Blacksmith" msgstr "GeresnÄ— kalvÄ—" msgid "Armory" msgstr "" msgid "Upg. Armory" msgstr "" msgid "Jousting Arena" msgstr "Riterių arena" msgid "Upg. Jousting Arena" msgstr "GeresnÄ— riterių arena" msgid "Cathedral" msgstr "Katedra" msgid "Upg. Cathedral" msgstr "GeresnÄ— katedra" msgid "Coliseum" msgstr "Koliziejus" msgid "Garbage Heap" msgstr "" msgid "Hut" msgstr "" msgid "Stick Hut" msgstr "" msgid "Upg. Stick Hut" msgstr "" msgid "Den" msgstr "Ola" msgid "Adobe" msgstr "Moliniai nameliai" msgid "Upg. Adobe" msgstr "" msgid "Bridge" msgstr "Tiltas" msgid "Upg. Bridge" msgstr "Tobulesnis tiltas" msgid "Pyramid" msgstr "PiramidÄ—" msgid "Rainbow" msgstr "VaivorykÅ¡tÄ—" msgid "Crystal Garden" msgstr "" msgid "Treehouse" msgstr "Namelis medyje" msgid "Cottage" msgstr "" msgid "Upg. Cottage" msgstr "" msgid "Stonehenge" msgstr "" msgid "Upg. Stonehenge" msgstr "" msgid "Fenced Meadow" msgstr "Aptverta pieva" #, fuzzy msgid "sorceress|Red Tower" msgstr "Raudonas bokÅ¡tas" msgid "Dungeon" msgstr "Požemiai" msgid "Waterfall" msgstr "Krioklys" msgid "Cave" msgstr "Urvas" msgid "Crypt" msgstr "" msgid "Nest" msgstr "Lizdas" msgid "Maze" msgstr "Labirintas" msgid "Upg. Maze" msgstr "Tobulesnis labirintas" msgid "Swamp" msgstr "PelkÄ—" msgid "Green Tower" msgstr "Žalias bokÅ¡tas" #, fuzzy msgid "warlock|Red Tower" msgstr "Raudonas bokÅ¡tas" msgid "Black Tower" msgstr "Juodas bokÅ¡tas" msgid "Library" msgstr "Biblioteka" msgid "Orchard" msgstr "Vaisių sodas" msgid "Habitat" msgstr "" msgid "Pen" msgstr "" msgid "Foundry" msgstr "Liejykla" msgid "Upg. Foundry" msgstr "GeresnÄ— liejykla" msgid "Cliff Nest" msgstr "Lizdas ant uolos" msgid "Ivory Tower" msgstr "Burtininkų bokÅ¡tas" msgid "Upg. Ivory Tower" msgstr "Tobulesnis burtininkų bokÅ¡tas" msgid "Cloud Castle" msgstr "Dangaus pilis" msgid "Upg. Cloud Castle" msgstr "GeresnÄ— dangaus pilis" msgid "Storm" msgstr "Audra" msgid "Skull Pile" msgstr "" msgid "Excavation" msgstr "" msgid "Graveyard" msgstr "KapinÄ—s" msgid "Upg. Graveyard" msgstr "GeresnÄ—s kapinÄ—s" msgid "Upg. Pyramid" msgstr "Didžioji piramidÄ—" msgid "Mansion" msgstr "Apleisti rÅ«mai" msgid "Upg. Mansion" msgstr "" msgid "Mausoleum" msgstr "Mauzoliejus" msgid "Upg. Mausoleum" msgstr "" msgid "Laboratory" msgstr "Laboratorija" msgid "Shrine" msgstr "" msgid "" "The Fortifications increase the toughness of the walls, increasing the " "number of turns it takes to knock them down." msgstr "" msgid "The Farm increases production of Peasants by %{count} per week." msgstr "" msgid "" "The Coliseum provides inspiring spectacles to defending troops, raising " "their morale by two during combat." msgstr "" msgid "The Garbage Heap increases production of Goblins by %{count} per week." msgstr "" msgid "The Rainbow increases the luck of the defending units by two." msgstr "" msgid "" "The Crystal Garden increases production of Sprites by %{count} per week." msgstr "" #, fuzzy msgid "The Dungeon increases the income of the town by %{count} gold per day." msgstr "" "Požeminiai rÅ«siai padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgid "The Waterfall increases production of Centaurs by %{count} per week." msgstr "" msgid "" "The Library increases the number of spells in the Guild by one for each " "level of the guild." msgstr "" msgid "The Orchard increases production of Halflings by %{count} per week." msgstr "" msgid "The Storm adds +2 to the power of spells of a defending spell caster." msgstr "" msgid "The Skull Pile increases production of Skeletons by %{count} per week." msgstr "" msgid "Thieves' Guild" msgstr "Vagių gildija" msgid "Tavern" msgstr "Taverna" msgid "Shipyard" msgstr "Laivų statykla" msgid "Well" msgstr "Å ulinys" msgid "Statue" msgstr "Statula" msgid "Marketplace" msgstr "Turgus" msgid "Moat" msgstr "" msgid "Castle" msgstr "Pilis" msgid "Tent" msgstr "PalapinÄ—" msgid "Captain's Quarters" msgstr "" msgid "Mage Guild, Level 1" msgstr "" msgid "Mage Guild, Level 2" msgstr "" msgid "Mage Guild, Level 3" msgstr "" msgid "Mage Guild, Level 4" msgstr "" msgid "Mage Guild, Level 5" msgstr "" msgid "" "The Shrine increases the necromancy skill of all your necromancers by 10 " "percent." msgstr "" msgid "" "The Thieves' Guild provides information on enemy players. Thieves' Guilds " "can also provide scouting information on enemy towns. Additional Guilds " "provide more information." msgstr "" msgid "The Tavern increases morale for troops defending the castle." msgstr "" msgid "The Shipyard allows ships to be built." msgstr "Laivų statykla leidžia statyti laivus." msgid "" "The Well increases the growth rate of all dwellings by %{count} creatures " "per week." msgstr "" "Å ulinys padidina prieaugį visuose bÅ«stuose % {count} bÅ«tybÄ—m per savaitÄ™." #, fuzzy msgid "The Statue increases the town's income by %{count} gold per day." msgstr "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgid "The Left Turret provides extra firepower during castle combat." msgstr "" msgid "The Right Turret provides extra firepower during castle combat." msgstr "" msgid "" "The Marketplace can be used to convert one type of resource into another. " "The more marketplaces you control, the better the exchange rate." msgstr "" "Turguje galima iÅ¡keisti vienokius iÅ¡teklius į kitokius (pvz. rÅ«dÄ… į " "gyvsidabrį). Kuo daugiau turÄ—site turgų, tuo geresniu santykiu galÄ—site " "keisti." msgid "" "The Moat slows attacking units. Any unit entering the moat must end its turn " "there and becomes more vulnerable to attack." msgstr "" #, fuzzy msgid "" "The Castle improves the town's defense and increases its income to %{count} " "gold per day." msgstr "" "Pilis sustiprina miesto gynybÄ… bei padidina pajamas %{count} aukso monetų " "per dienÄ…." msgid "" "The Tent provides workers to build a castle, provided the materials and the " "gold are available." msgstr "" msgid "" "The Captain's Quarters provides a captain to assist in the castle's defense " "when no hero is present." msgstr "" msgid "" "The Mage Guild allows heroes to learn spells and replenish their spell " "points." msgstr "" msgid "Recruit %{name}" msgstr "Pasamdyti %{name}" msgid "Month: %{month}, Week: %{week}, Day: %{day}" msgstr "MÄ—nuo: %{month}, SavaitÄ—: %{week}, Diena: %{day}" msgid "" "You must purchase a spell book to use the mage guild, but you currently have " "no room for a spell book. Try giving one of your artifacts to another hero." msgstr "" #, fuzzy msgid "Click to show next town." msgstr "Rodyti kitÄ… miestÄ…" msgid "Show next town" msgstr "Rodyti kitÄ… miestÄ…" #, fuzzy msgid "Click to show previous town." msgstr "Rodyti ankstesnį miestÄ…" msgid "Show previous town" msgstr "Rodyti ankstesnį miestÄ…" msgid "This town may not be upgraded to a castle." msgstr "" msgid "Town" msgstr "Miestelis" #, fuzzy msgid "Exit Castle" msgstr "IÅ¡eiti iÅ¡ pilies" #, fuzzy msgid "Exit Town" msgstr "IÅ¡eiti iÅ¡ miesto" msgid "Show Income" msgstr "" msgid "View Hero" msgstr "" #, fuzzy msgid "The above spells are available here." msgstr "Å ie burtai buvo įdÄ—ti į jÅ«sų knygÄ…." #, fuzzy msgid "The spells the hero can learn have been added to their book." msgstr "Å ie burtai buvo įdÄ—ti į jÅ«sų knygÄ…." msgid "A generous tip for the barkeep yields the following rumor:" msgstr "" msgid "Recruit Hero" msgstr "" msgid "%{name} is a level %{value} %{race} " msgstr "" #, fuzzy msgid "with %{count} artifacts." msgstr " su vienu artefaktu" #, fuzzy msgid "with 1 artifact." msgstr " su vienu artefaktu" #, fuzzy msgid "without artifacts." msgstr " su vienu artefaktu" msgid "Recruit %{name} the %{race}" msgstr "" msgid "" "'Spread' combat formation spreads the castle's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" msgid "" "'Grouped' combat formation bunches the castle's units together in the center " "of the castle's side of the battlefield." msgstr "" msgid "Spread Formation" msgstr "" msgid "Grouped Formation" msgstr "" msgid "Set garrison combat formation to 'Spread'" msgstr "" msgid "Set garrison combat formation to 'Grouped'" msgstr "" #, fuzzy msgid "Exit Castle Options" msgstr "IÅ¡eiti iÅ¡ pilies" msgid "Castle Options" msgstr "" msgid "Not enough resources to recruit creatures." msgstr "" msgid "You are unable to recruit at this time, your ranks are full." msgstr "" msgid "No creatures available for purchase." msgstr "" #, fuzzy msgid "Recruit Creatures" msgstr "Pasamdyti %{name}" msgid "Max" msgstr "" #, fuzzy msgid "Hire all creatures in the town." msgstr "Prarasti visus savo herojus bei miestus." msgid "Available" msgstr "" msgid "Town Population Information and Statistics" msgstr "Miesto gyventojų informacija bei statistika" msgid "Damage" msgstr "" msgid "HP" msgstr "" msgid "Growth" msgstr "" msgid "week" msgstr "savait." #, fuzzy msgid "View World" msgstr "Rodyti miestus" msgid "View the entire world." msgstr "" msgid "Puzzle" msgstr "" msgid "View the obelisk puzzle." msgstr "" msgid "Scenario Information" msgstr "" msgid "View information on the scenario you are currently playing." msgstr "" msgid "Dig for the Ultimate Artifact." msgstr "" msgid "Digging" msgstr "" msgid "Arena" msgstr "" msgid "" "You enter the arena and face a pack of vicious lions. You handily defeat " "them, to the wild cheers of the crowd. Impressed by your skill, the aged " "trainer of gladiators agrees to train you in a skill of your choice." msgstr "" msgid "Attack Skill" msgstr "Puolimo įgÅ«dis" msgid "Defense Skill" msgstr "Gynybos įgÅ«dis" msgid "Shots" msgstr "Å Å«viai" msgid "Shots Left" msgstr "" msgid "Hit Points" msgstr "" msgid "Hit Points Left" msgstr "" msgid "You can't afford to upgrade your troops!" msgstr "" msgid "" "Your troops can be upgraded, but it will cost you dearly. Do you wish to " "upgrade them?" msgstr "" msgid "Are you sure you want to dismiss this army?" msgstr "" msgid "Upgrade" msgstr "" msgid "Upgrade your troops." msgstr "" msgid "Dismiss" msgstr "" msgid "Dismiss this army." msgstr "" msgid "" "A group of %{monster} with a desire for greater glory wish to join you.\n" "Do you accept?" msgstr "" #, fuzzy msgid "Followers" msgstr "Burtų galia" msgid "" "The %{monster} is swayed by your diplomatic tongue, and offers to join your " "army for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" msgid "" "The creatures are swayed by your diplomatic\n" "tongue, and make you an offer:\n" "\n" msgstr "" msgid "" "%{offer} of the %{total} %{monster} will join your army, and the rest will " "leave you alone, for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" msgid "" "All %{offer} of the %{monster} will join your army for the sum of %{gold} " "gold.\n" "Do you accept?" msgstr "" msgid "(Rate: %{percent})" msgstr "" msgid "off" msgstr "" msgid "Music" msgstr "" msgid "Effects" msgstr "" msgid "MIDI" msgstr "" #, fuzzy msgid "MIDI Expansion" msgstr "Apleisti rÅ«mai" msgid "External" msgstr "" msgid "Music Type" msgstr "" msgid "3D Audio" msgstr "" msgid "Toggle ambient music level." msgstr "" msgid "Toggle foreground sounds level." msgstr "" msgid "Change the type of music." msgstr "" msgid "Toggle 3D effects of foreground sounds." msgstr "" msgid "Build a new ship:" msgstr "Statyti naujÄ… laivÄ…:" msgid "Resource cost:" msgstr "IÅ¡teklio kaina:" msgid "Total: " msgstr "" msgid "Need: " msgstr "" msgid "Load Game" msgstr "Ä®kelti žaidimÄ…" msgid "No save files to load." msgstr "" msgid "New Game" msgstr "" msgid "Start a single or multi-player game." msgstr "" msgid "Load a previously saved game." msgstr "Atverti prieÅ¡ tai įraÅ¡ytÄ… žaidimÄ…" #, fuzzy msgid "Save Game" msgstr "Ä®kelti žaidimÄ…" msgid "Save the current game." msgstr "" msgid "Quit" msgstr "" msgid "Quit out of Heroes of Might and Magic II." msgstr "" msgid "Language" msgstr "" msgid "Graphics" msgstr "" #, fuzzy msgid "Black & White" msgstr "Tiltas" msgid "Mouse Cursor" msgstr "" msgid "Color" msgstr "" msgid "Text Support" msgstr "" msgid "Change the language of the game." msgstr "" msgid "Select Game Language" msgstr "" msgid "Change the graphics settings of the game." msgstr "" msgid "Toggle colored cursor on or off. This is only an aesthetic choice." msgstr "" msgid "" "Toggle text support mode to output extra information about windows and " "events in the game." msgstr "" msgid "" "Map\n" "Difficulty" msgstr "" "ŽemÄ—lapis\n" "SudÄ—tingumas" msgid "" "Game\n" "Difficulty" msgstr "" "Žaidimas\n" "SudÄ—tingumas" msgid "Rating" msgstr "" msgid "Map Size" msgstr "" "Pasaulio\n" "dydis" msgid "Opponents" msgstr "" msgid "Class" msgstr "" msgid "" "Victory\n" "Conditions" msgstr "" msgid "" "Loss\n" "Conditions" msgstr "" msgid "First select recipients!" msgstr "" msgid "You cannot select %{resource}!" msgstr "" msgid "Select count %{resource}:" msgstr "" msgid "Select Recipients" msgstr "" msgid "Your Funds" msgstr "" msgid "Planned Gift" msgstr "" #, fuzzy msgid "Gift from %{name}" msgstr "Pasamdyti %{name}" msgid "Resolution" msgstr "" msgid "Fullscreen" msgstr "" msgid "window|Mode" msgstr "" msgid "Windowed" msgstr "" msgid "V-Sync" msgstr "" #, fuzzy msgid "on" msgstr "Miestelis" msgid "FPS" msgstr "" msgid "System Info" msgstr "" msgid "Change the resolution of the game." msgstr "" msgid "Select Game Resolution" msgstr "" msgid "Toggle between fullscreen and windowed modes." msgstr "" msgid "" "The V-Sync option can be enabled to resolve flickering issues on some " "monitors." msgstr "" msgid "Show extra information such as FPS and current time." msgstr "" msgid "Hotkey: " msgstr "" msgid "Category: " msgstr "" msgid "Event: " msgstr "" msgid "Hot Keys:" msgstr "" msgid "Evil" msgstr "" msgid "Good" msgstr "" msgid "Interface Type" msgstr "" msgid "Hide" msgstr "" msgid "Show" msgstr "" msgid "Interface" msgstr "" msgid "Slow" msgstr "" msgid "Normal" msgstr "" msgid "Fast" msgstr "" msgid "Very Fast" msgstr "" msgid "Scroll Speed" msgstr "" msgid "Toggle the type of interface you want to use." msgstr "" msgid "Toggle interface visibility." msgstr "" msgid "Toggle colored cursor on or off. This is only an esthetic choice." msgstr "" msgid "Sets the speed at which you scroll the window." msgstr "" msgid "Select Game Language:" msgstr "" msgid "Click to choose the selected language." msgstr "" msgid "%{name} has gained a level." msgstr "" #, fuzzy msgid "%{skill} +1" msgstr "ŽiÅ«rÄ—ti %{skill} informacijÄ…" msgid "You have learned %{skill}." msgstr "JÅ«s iÅ¡mokote %{skill}." msgid "" "%{name} has gained a level.\n" "\n" "%{skill} +1" msgstr "" msgid "" "You may learn either:\n" "%{skill1}\n" "or\n" "%{skill2}" msgstr "" msgid "" "Please inspect our fine wares. If you feel like offering a trade, click on " "the items you wish to trade with and for." msgstr "" msgid "" "You have received quite a bargain. I expect to make no profit on the deal. " "Can I interest you in any of my other wares?" msgstr "" msgid "I can offer you %{count} for 1 unit of %{resfrom}." msgstr "" msgid "I can offer you 1 unit of %{resto} for %{count} units of %{resfrom}." msgstr "" #, fuzzy msgid "Min" msgstr "Apleisti rÅ«mai" msgid "Qty to trade" msgstr "" msgid "Trading Post" msgstr "" msgid "Your Resources" msgstr "Tavo iÅ¡tekliai" msgid "Available Trades" msgstr "" msgid "n/a" msgstr "" msgid "guarded by %{count} %{monster}" msgstr "" msgid "guarded by " msgstr "" msgid "(available: %{count})" msgstr "" msgid "(empty)" msgstr "" msgid "already learned" msgstr "" #, fuzzy msgid "treeOfKnowledge|free" msgstr "IÅ¡manymas" msgid "already claimed" msgstr "" msgid "not claimed" msgstr "" msgid "already knows this skill" msgstr "jau moka šį įgÅ«dį" msgid "already has max skills" msgstr "" msgid "(already visited)" msgstr "" msgid "(not visited)" msgstr "" msgid "%{color} Barrier" msgstr "" msgid "(tent visited)" msgstr "" msgid "%{color} Tent" msgstr "" msgid "Road" msgstr "" msgid "(digging ok)" msgstr "" msgid "(no digging)" msgstr "" msgid "penalty: %{cost}" msgstr "" msgid "Defenders:" msgstr "" msgid "Unknown" msgstr "" msgid "%{name} (Level %{level})" msgstr "" #, fuzzy msgid "Attack:" msgstr "Puolimas" #, fuzzy msgid "Defense:" msgstr "Gynyba" #, fuzzy msgid "Spell Power:" msgstr "Burtų galia" #, fuzzy msgid "Knowledge:" msgstr "IÅ¡manymas" #, fuzzy msgid "Spell Points:" msgstr "Burtų galia" msgid "Move Points:" msgstr "" msgid "Uncharted Territory" msgstr "" msgid "Cost per troop:" msgstr "" msgid "Available: %{count}" msgstr "Yra: %{count}" msgid "Number to buy:" msgstr "Norimas kiekis:" msgid "Recruit selected monsters." msgstr "" msgid "Select maximum monsters to be recruited." msgstr "" msgid "Select only 1 monster to be recruited." msgstr "" msgid "Select Game Resolution:" msgstr "" msgid "Click to apply the selected resolution." msgstr "" msgid "Click to apply the entered text." msgstr "" msgid "Click to open the Virtual Keyboard dialog." msgstr "" msgid "Open Virtual Keyboard" msgstr "" msgid "How many troops to move?" msgstr "" msgid "Fast separation into slots:" msgstr "" msgid "Map: " msgstr "" msgid "" "\n" "\n" "Month: " msgstr "" msgid ", Week: " msgstr "" msgid ", Day: " msgstr "" msgid "" "\n" "\n" "Location: " msgstr "" msgid "Click to save the current game." msgstr "" #, fuzzy msgid "Click to load a previously saved game." msgstr "Atverti prieÅ¡ tai įraÅ¡ytÄ… žaidimÄ…" msgid "Are you sure you want to delete file:" msgstr "Ar jÅ«s tikrai norite iÅ¡trinti failÄ…:" msgid "Warning!" msgstr "Ä®spÄ—jimas!" msgid "File to Save:" msgstr "Ä®raÅ¡yti į failÄ…:" msgid "File to Load:" msgstr "Failas įkÄ—limui:" msgid "Accept the choice made." msgstr "" msgid "%{color} %{race} hero" msgstr "" msgid "Terrain object" msgstr "" #, fuzzy msgid "Select Skill:" msgstr "Gynybos įgÅ«dis" msgid "Select Spell:" msgstr "" #, fuzzy msgid "Select Artifact:" msgstr "MagiÅ¡kas artefaktas" #, fuzzy msgid "Select Monster:" msgstr "Pasirinkti %{name}" #, fuzzy msgid "Select Hero:" msgstr "Pasirinkti %{name}" #, fuzzy msgid "Select Treasure:" msgstr "Pasirinkti %{name}" #, fuzzy msgid "Select Ocean Object:" msgstr "MagiÅ¡kas artefaktas" msgid "Castle/town placing" msgstr "" msgid "doubleLinedRace|Neutral" msgstr "" msgid "race|Random" msgstr "" msgid "Click to start placing the selected castle/town." msgstr "" #, fuzzy msgid "Click to select town placing." msgstr "Rodyti kitÄ… miestÄ…" msgid "Click to select castle placing." msgstr "" msgid "%{color} %{race} %{townOrCastle}" msgstr "" msgid "race|Neutral" msgstr "" msgid "You will place" msgstr "" msgid "Click to select this class." msgstr "" #, fuzzy msgid "Click to select this color." msgstr "Rodyti kitÄ… miestÄ…" #, fuzzy msgid "Select Dwelling:" msgstr "Gynybos įgÅ«dis" #, fuzzy msgid "Select Landscape Object:" msgstr "MagiÅ¡kas artefaktas" msgid "Mine placing" msgstr "" #, fuzzy msgid "" "Resource\n" "type:" msgstr "IÅ¡tekliai" msgid "%{mineName} appearance:" msgstr "" msgid "Click to select %{object} as the resource generator to be placed." msgstr "" #, fuzzy msgid "Select Mountain Object:" msgstr "MagiÅ¡kas artefaktas" #, fuzzy msgid "Select Rock Object:" msgstr "MagiÅ¡kas artefaktas" #, fuzzy msgid "Select Tree Object:" msgstr "MagiÅ¡kas artefaktas" #, fuzzy msgid "Select Power Up Object:" msgstr "MagiÅ¡kas artefaktas" #, fuzzy msgid "Select Adventure Object:" msgstr "MagiÅ¡kas artefaktas" #, fuzzy msgid "Map Type:\n" msgstr "Pasaulio dydis" msgid "The Succession Wars" msgstr "" msgid "The Price of Loyalty" msgstr "" msgid "Resurrection" msgstr "" msgid "Lose all your heroes and towns." msgstr "Prarasti visus savo herojus bei miestus." msgid "Lose a specific town." msgstr "Prarasti konkretų miestÄ…." msgid "Lose a specific hero." msgstr "" msgid "Run out of time. Fail to win by a certain point." msgstr "" msgid "Loss Condition" msgstr "PralaimÄ—jimo sÄ…lygos" msgid "Defeat all enemy heroes and towns." msgstr "NugalÄ—ti visus priešų herojus bei miestus." msgid "Capture a specific town." msgstr "Užimti konkretų miestÄ…." msgid "Defeat a specific hero." msgstr "" msgid "Find a specific artifact." msgstr "Rasti konkretų magiÅ¡kÄ… artefaktÄ…." msgid "Your side defeats the opposing side." msgstr "" msgid "Accumulate a large amount of gold." msgstr "Sukaupti labai daug aukso." msgid "Victory Condition" msgstr "PergalÄ—s salygos" #, fuzzy msgid "Map difficulty:" msgstr "" "ŽemÄ—lapis\n" "SudÄ—tingumas" msgid "N" msgstr "" msgid "No maps exist at that size" msgstr "" msgid "Small Maps" msgstr "Maži žemÄ—lapiai" msgid "View only maps of size small (36 x 36)." msgstr "" msgid "Medium Maps" msgstr "Vidutiniai žemÄ—lapiai" msgid "View only maps of size medium (72 x 72)." msgstr "" msgid "Large Maps" msgstr "Dideli žemÄ—lapiai" msgid "View only maps of size large (108 x 108)." msgstr "" msgid "Extra Large Maps" msgstr "YpaÄ dideli žemÄ—lapiai" msgid "View only maps of size extra large (144 x 144)." msgstr "" msgid "All Maps" msgstr "Visi žemÄ—lapiai" msgid "View all maps, regardless of size." msgstr "" msgid "Players Icon" msgstr "" msgid "" "Indicates how many players total are in the scenario. Any positions not " "occupied by humans will be occupied by computer players." msgstr "" msgid "" "Indicates whether the map\n" "is small (36 x 36), medium\n" "(72 x 72), large (108 x 108),\n" "or extra large (144 x 144)." msgstr "" msgid "Size Icon" msgstr "" msgid "" "Indicates whether the map is made for \"The Succession Wars\" or \"The Price " "of Loyalty\" version of the game." msgstr "" #, fuzzy msgid "Map Type" msgstr "Pasaulio dydis" msgid "Selected Name" msgstr "" msgid "The name of the currently selected map." msgstr "" msgid "Selected Map Difficulty" msgstr "" msgid "" "The map difficulty of the currently selected map. The map difficulty is " "determined by the scenario designer. More difficult maps might include more " "or stronger enemies, fewer resources, or other special conditions making " "things tougher for the human player." msgstr "" msgid "Selected Description" msgstr "" msgid "The description of the currently selected map." msgstr "" msgid "Jump" msgstr "" #, fuzzy msgid "Hero Speed" msgstr "Greitis" msgid "Don't Show" msgstr "" #, fuzzy msgid "Enemy Speed" msgstr "Greitis" msgid "Auto Resolve" msgstr "" msgid "Auto, No Spells" msgstr "" msgid "Battles" msgstr "" msgid "autoBattle|Manual" msgstr "" msgid "Change the speed at which your heroes move on the main screen." msgstr "" msgid "" "Sets the speed that A.I. heroes move at. You can also elect not to view A.I. " "movement at all." msgstr "" msgid "Change the interface settings of the game." msgstr "" msgid "Interface Settings" msgstr "" msgid "Toggle instant battle mode." msgstr "" msgid "Att." msgstr "" msgid "Def." msgstr "" msgid "Power" msgstr "" #, fuzzy msgid "Knowl" msgstr "IÅ¡manymas" msgid "1st" msgstr "" msgid "2nd" msgstr "" msgid "3rd" msgstr "" msgid "4th" msgstr "" msgid "5th" msgstr "" msgid "6th" msgstr "" msgid "Oracle: Player Rankings" msgstr "" #, fuzzy msgid "Thieves' Guild: Player Rankings" msgstr "Vagių gildija" msgid "Number of Towns:" msgstr "Miestų kiekis:" msgid "Number of Castles:" msgstr "Pilių kiekis:" msgid "Number of Heroes:" msgstr "Herojų kiekis:" msgid "Gold in Treasury:" msgstr "Aukso saugyklose:" msgid "Wood & Ore:" msgstr "Mediena bei rÅ«da:" msgid "Gems, Cr, Slf & Mer:" msgstr "" msgid "Obelisks Found:" msgstr "" #, fuzzy msgid "Artifacts:" msgstr "MagiÅ¡kas artefaktas" msgid "Total Army Strength:" msgstr "" msgid "Income:" msgstr "" msgid "Best Hero:" msgstr "" msgid "Best Hero Stats:" msgstr "" msgid "Personality:" msgstr "" msgid "Best Monster:" msgstr "" #, fuzzy msgid "Map filename" msgstr "" "Pasaulio\n" "dydis" #, fuzzy msgid "" "Are you sure you want to load a new map? (Any unsaved changes to the current " "map will be lost.)" msgstr "" "Ar tikrai norite įkelti anksÄiau įraÅ¡ytÄ… žaidimÄ…? (Dabartinis žaidimas bus " "prarastas, jei neįrašėte)" #, fuzzy msgid "" "Are you sure you want to create a new map? (Any unsaved changes to the " "current map will be lost.)" msgstr "" "Ar tikrai norite įkelti anksÄiau įraÅ¡ytÄ… žaidimÄ…? (Dabartinis žaidimas bus " "prarastas, jei neįrašėte)" msgid "" "Create a new map, either from scratch or using the random map generator." msgstr "" msgid "New Map" msgstr "" #, fuzzy msgid "Load Map" msgstr "Ä®kelti žaidimÄ…" msgid "Load an existing map." msgstr "" #, fuzzy msgid "Save Map" msgstr "Ä®kelti žaidimÄ…" msgid "Save the current map." msgstr "" msgid "Quit out of the map editor." msgstr "" msgid "Objects cannot be placed outside the map." msgstr "" msgid "Monsters cannot be placed on water." msgstr "" msgid "Choose a tile which does not contain any objects." msgstr "" msgid "Treasures cannot be placed on water." msgstr "" msgid "Heroes cannot be placed on water." msgstr "" msgid "" "A maximum of %{count} heroes of the same color can be placed on the map." msgstr "" msgid "Artifacts cannot be placed on water." msgstr "" msgid "Mountains cannot be placed on water." msgstr "" msgid "Rocks cannot be placed on water." msgstr "" msgid "Trees cannot be placed on water." msgstr "" msgid "Ocean object must be placed on water." msgstr "" msgid "Landscape objects cannot be placed on water." msgstr "" msgid "Towns cannot be placed on water." msgstr "" msgid "Mines cannot be placed on water." msgstr "" msgid "Dwellings cannot be placed on water." msgstr "" msgid "Power-ups cannot be placed on water." msgstr "" msgid "Adventure objects cannot be placed on water." msgstr "" #, fuzzy msgid "Used to place %{object}." msgstr "Rasti magiÅ¡kÄ… artefaktÄ… '%{name}'" #, fuzzy msgid "Select object type" msgstr "MagiÅ¡kas artefaktas" #, fuzzy msgid "" "Click here to\n" "select a monster." msgstr "Rodyti kitÄ… miestÄ…" #, fuzzy msgid "" "Click here to\n" "select another monster." msgstr "Rodyti kitÄ… miestÄ…" msgid "" "Cell\n" "Details" msgstr "" msgid "Roads" msgstr "" msgid "Streams" msgstr "" msgid "Erase" msgstr "" msgid "Mountains" msgstr "" msgid "Rocks" msgstr "" msgid "Trees" msgstr "" msgid "Water Objects" msgstr "" msgid "Miscellaneous" msgstr "" msgid "Artifacts" msgstr "" msgid "Dwellings" msgstr "" msgid "Mines" msgstr "" msgid "Power-ups" msgstr "" msgid "Treasures" msgstr "" #, fuzzy msgid "Towns" msgstr "Miestelis" msgid "Heroes" msgstr "" msgid "editorErasure|Terrain" msgstr "" msgid "editorErasure|Castles" msgstr "" msgid "editorErasure|Monsters" msgstr "" msgid "editorErasure|Heroes" msgstr "" msgid "editorErasure|Artifacts" msgstr "" msgid "editorErasure|Roads" msgstr "" msgid "editorErasure|Streams" msgstr "" msgid "editorErasure|Treasures" msgstr "" msgid "" "Draws terrain in\n" "%{size} by %{size} square increments." msgstr "" msgid "" "Erases objects in\n" "%{size} by %{size} square increments." msgstr "" #, fuzzy msgid "Small Brush" msgstr "Maži žemÄ—lapiai" #, fuzzy msgid "Medium Brush" msgstr "Vidutiniai žemÄ—lapiai" #, fuzzy msgid "Large Brush" msgstr "Dideli žemÄ—lapiai" msgid "Area Fill" msgstr "" msgid "Used to click and drag for filling in large areas." msgstr "" msgid "Clear Area" msgstr "" msgid "Used to click and drag for clearing large areas." msgstr "" msgid "" "Costs %{rate} times normal movement for all heroes. (Pathfinding reduces or " "eliminates the penalty.)" msgstr "" msgid "Traversable only by boat." msgstr "" msgid "No special modifiers." msgstr "" msgid "Toggle the erasure of %{type} objects." msgstr "" msgid "" "Objects of this type will be deleted with the Erase tool. Left-click here to " "deselect this type. Press and hold this button to deselect all other object " "types." msgstr "" msgid "" "Objects of this type will NOT be deleted with the Erase tool. Left-click " "here to select this type. Press and hold this button to select all other " "object types." msgstr "" msgid "Terrain Mode" msgstr "" msgid "Used to draw the underlying grass, dirt, water, etc. on the map." msgstr "" msgid "Landscape Objects Mode" msgstr "" msgid "" "Used to place landscape objects (mountains, rocks, trees, etc.) on the map." msgstr "" msgid "Detail Mode" msgstr "" #, fuzzy msgid "Used for special editing of monsters, heroes and towns." msgstr "NugalÄ—ti visus priešų herojus bei miestus." msgid "Adventure Objects Mode" msgstr "" msgid "" "Used to place adventure objects (artifacts, dwellings, mines, treasures, " "etc.) on the map." msgstr "" msgid "Kingdom Objects Mode" msgstr "" msgid "Used to place kingdom objects (towns, castles and heroes) on the map." msgstr "" #, fuzzy msgid "Monsters Mode" msgstr "AtsitiktinÄ— pilis" #, fuzzy msgid "Used to place monsters on the map." msgstr "Rasti magiÅ¡kÄ… artefaktÄ… '%{name}'" msgid "Allows you to draw streams by clicking and dragging." msgstr "" msgid "Stream Mode" msgstr "" msgid "Allows you to draw roads by clicking and dragging." msgstr "" msgid "Road Mode" msgstr "" msgid "Erase Mode" msgstr "" msgid "Used to erase objects from the map." msgstr "" msgid "Change between zoom and normal view." msgstr "" msgid "Magnify" msgstr "" msgid "Undo" msgstr "" msgid "Undo your last action." msgstr "" msgid "Create a new map either from scratch or using the random map generator." msgstr "" msgid "Edit map title, description, and other general information." msgstr "" #, fuzzy msgid "Specifications" msgstr "Ä®tvirtinimai" msgid "File Options" msgstr "" msgid "" "Open the file options menu, where you can save or load maps, or quit out of " "the editor." msgstr "" msgid "View the editor system options, which let you customize the editor." msgstr "" msgid "Create a map that is %{size} squares wide and %{size} squares high." msgstr "" msgid "Cancel back to the New Map menu." msgstr "" msgid "Cancel back to the main menu." msgstr "" msgid "From Scratch" msgstr "" msgid "Start from scratch with a blank map." msgstr "" msgid "Create a randomly generated map." msgstr "" #, fuzzy msgid "Random" msgstr "Atsitiktinis miestas" msgid "Cancel back to the Map Editor main menu." msgstr "" msgid "No maps available!" msgstr "" msgid "Warning" msgstr "" msgid "difficulty|Easy" msgstr "" msgid "difficulty|Normal" msgstr "" msgid "difficulty|Hard" msgstr "" msgid "difficulty|Expert" msgstr "" msgid "difficulty|Impossible" msgstr "" msgid "and more..." msgstr "" msgid "Easy" msgstr "" msgid "Hard" msgstr "" #, fuzzy msgid "Campaign Difficulty" msgstr "" "ŽemÄ—lapis\n" "SudÄ—tingumas" msgid "" "Choose this difficulty if you want to prefer game story over challenge. AI " "is weaker in comparison with normal difficulty." msgstr "" msgid "" "Choose this difficulty to enjoy the campaign as per the original design." msgstr "" msgid "" "Choose this difficulty if you want challenge. AI is stronger in comparison " "with normal difficulty." msgstr "" msgid "" "Congratulations!\n" "\n" "Days: %{days}\n" msgstr "" msgid "" "\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" msgid "" "Score: %{score}\n" "\n" "Rating:\n" "%{rating}" msgstr "" msgid "Start the selected scenario." msgstr "" #, fuzzy msgid "View Intro" msgstr "Rodyti iÅ¡teklius" msgid "View Intro videos for the current state of the campaign." msgstr "" msgid "" "Select the campaign difficulty. This can be lowered at any point during the " "campaign." msgstr "" msgid "Restart the current scenario." msgstr "" #, fuzzy msgid "Difficulty" msgstr "" "ŽemÄ—lapis\n" "SudÄ—tingumas" msgid "" "You have changed to a lower difficulty for the campaign. You will not be " "able to revert this after this point. The high score will be calculated " "based solely on the new difficulty. Do you want to proceed?" msgstr "" #, fuzzy msgid "Are you sure you want to restart this scenario?" msgstr "" "Ar tikrai norite perraÅ¡yti anksÄiau tuo paÄiu pavadinimu įraÅ¡ytÄ… failÄ…?" msgid "Campaign Scenario loading failure" msgstr "" msgid "Please make sure that campaign files are correct and present." msgstr "" msgid "Days spent" msgstr "" msgid "The number of days spent on this campaign." msgstr "" msgid "Project Coordination and Core Development" msgstr "" msgid "Development" msgstr "" msgid "Visit us at " msgstr "" msgid "QA and Support" msgstr "" msgid "Dev and Support" msgstr "" msgid "Special Thanks to" msgstr "" msgid "and many other contributors!" msgstr "" msgid "and many-many other supporters!" msgstr "" msgid "Support us at" msgstr "" msgid "local-donation-platform|https://www.patreon.com/fheroes2" msgstr "" msgid "Connect with us at" msgstr "" msgid "local-social-network|https://www.facebook.com/groups/fheroes2" msgstr "" msgid "Need help with the game?" msgstr "" msgid "Original project before 0.7" msgstr "" msgid "Heroes of Might and Magic II: The Succession Wars team" msgstr "" msgid "Designed and Directed" msgstr "" msgid "Programming and Design" msgstr "" msgid "Executive Producer" msgstr "" msgid "Producer" msgstr "" msgid "Additional Design" msgstr "" msgid "Additional Programming" msgstr "" msgid "Musical Production" msgstr "" msgid "Music and Sound Design" msgstr "" msgid "Vocalists" msgstr "" msgid "Art Director" msgstr "" msgid "Assistant Art Director" msgstr "" #, fuzzy msgid "Artists" msgstr "MagiÅ¡kas artefaktas" msgid "QA Manager" msgstr "" msgid "QA" msgstr "" msgid "Writing" msgstr "" msgid "Manual and Helpfile" msgstr "" msgid "Scenarios" msgstr "" msgid "Heroes of Might and Magic II: The Price of Loyalty team" msgstr "" msgid "Design Lead" msgstr "" msgid "Designers" msgstr "" msgid "Programming Lead" msgstr "" msgid "Art Lead" msgstr "" msgid "Playtesters" msgstr "" msgid "Designer" msgstr "" msgid "Producers" msgstr "" msgid "QA Managers" msgstr "" msgid "Sound Design" msgstr "" msgid "Town Themes" msgstr "" msgid "Alto Sax" msgstr "" msgid "Harpsichord and Piano" msgstr "" msgid "Basso Vocal" msgstr "" msgid "Soprano Vocal" msgstr "" msgid "Recorded at %{recordingStudio}" msgstr "" msgid "credits|Manual" msgstr "" msgid "German Consultant" msgstr "" msgid "Map Designers" msgstr "" msgid "Package Design" msgstr "" msgid "Your Name" msgstr "" msgid "Unknown Hero" msgstr "" msgid "Standard" msgstr "" msgid "View High Scores for Standard Maps." msgstr "" msgid "Campaign" msgstr "" msgid "View High Scores for Campaigns." msgstr "" msgid "hotkey|default okay event" msgstr "" msgid "hotkey|default cancel event" msgstr "" msgid "hotkey|default left" msgstr "" msgid "hotkey|default right" msgstr "" msgid "hotkey|default up" msgstr "" msgid "hotkey|default down" msgstr "" msgid "hotkey|toggle fullscreen" msgstr "" msgid "hotkey|toggle text support mode" msgstr "" msgid "hotkey|new game" msgstr "" msgid "hotkey|load game" msgstr "" msgid "hotkey|highscores" msgstr "" msgid "hotkey|credits" msgstr "" msgid "hotkey|standard game" msgstr "" msgid "hotkey|campaign game" msgstr "" msgid "hotkey|multi-player game" msgstr "" msgid "hotkey|settings" msgstr "" msgid "hotkey|quit" msgstr "" msgid "hotkey|select map" msgstr "" msgid "hotkey|select small map size" msgstr "" msgid "hotkey|select medium map size" msgstr "" msgid "hotkey|select large map size" msgstr "" msgid "hotkey|select extra large map size" msgstr "" msgid "hotkey|select all map sizes" msgstr "" msgid "hotkey|hotseat game" msgstr "" msgid "hotkey|battle only game" msgstr "" msgid "hotkey|choose the original campaign" msgstr "" msgid "hotkey|choose the expansion campaign" msgstr "" msgid "hotkey|map editor main menu" msgstr "" #, fuzzy msgid "hotkey|new map menu" msgstr "Rodyti kitÄ… miestÄ…" msgid "hotkey|load map menu" msgstr "" msgid "hotkey|new map from scratch" msgstr "" #, fuzzy msgid "hotkey|new random map" msgstr "Rodyti kitÄ… miestÄ…" #, fuzzy msgid "hotkey|undo last action" msgstr "Rodyti kitÄ… miestÄ…" #, fuzzy msgid "hotkey|redo last action" msgstr "Rodyti kitÄ… miestÄ…" msgid "hotkey|roland campaign" msgstr "" msgid "hotkey|archibald campaign" msgstr "" msgid "hotkey|the price of loyalty campaign" msgstr "" msgid "hotkey|voyage home campaign" msgstr "" msgid "hotkey|wizard's isle campaign" msgstr "" msgid "hotkey|descendants campaign" msgstr "" msgid "hotkey|select first campaign bonus" msgstr "" msgid "hotkey|select second campaign bonus" msgstr "" msgid "hotkey|select third campaign bonus" msgstr "" msgid "hotkey|view campaign intro" msgstr "" #, fuzzy msgid "hotkey|select campaign difficulty" msgstr "" "ŽemÄ—lapis\n" "SudÄ—tingumas" msgid "hotkey|restart campaign scenario" msgstr "" msgid "hotkey|world map left" msgstr "" msgid "hotkey|world map right" msgstr "" msgid "hotkey|world map up" msgstr "" msgid "hotkey|world map down" msgstr "" msgid "hotkey|world map up left" msgstr "" msgid "hotkey|world map up right" msgstr "" msgid "hotkey|world map down left" msgstr "" msgid "hotkey|world map down right" msgstr "" msgid "hotkey|save game" msgstr "" #, fuzzy msgid "hotkey|next hero" msgstr "Rodyti kitÄ… miestÄ…" #, fuzzy msgid "hotkey|start hero movement" msgstr "Rodyti kitÄ… miestÄ…" msgid "hotkey|cast adventure spell" msgstr "" msgid "hotkey|put hero to sleep" msgstr "" #, fuzzy msgid "hotkey|next town" msgstr "Rodyti kitÄ… miestÄ…" msgid "hotkey|end turn" msgstr "" msgid "hotkey|file options" msgstr "" msgid "hotkey|adventure options" msgstr "" msgid "hotkey|puzzle map" msgstr "" msgid "hotkey|scenario information" msgstr "" msgid "hotkey|dig for artifact" msgstr "" msgid "hotkey|view world" msgstr "" msgid "hotkey|kingdom summary" msgstr "" msgid "hotkey|default action" msgstr "" msgid "hotkey|open focus" msgstr "" msgid "hotkey|system options" msgstr "" msgid "hotkey|scroll left" msgstr "" msgid "hotkey|scroll right" msgstr "" msgid "hotkey|scroll up" msgstr "" msgid "hotkey|scroll down" msgstr "" msgid "hotkey|toggle control panel" msgstr "" msgid "hotkey|toggle radar" msgstr "" msgid "hotkey|toggle buttons" msgstr "" msgid "hotkey|toggle status" msgstr "" msgid "hotkey|toggle icons" msgstr "" msgid "hotkey|transfer control to ai" msgstr "" msgid "hotkey|retreat from battle" msgstr "" msgid "hotkey|surrender during battle" msgstr "" msgid "hotkey|toggle battle auto mode" msgstr "" #, fuzzy msgid "hotkey|finish the battle in auto mode" msgstr "" "Ar tikrai norite perraÅ¡yti anksÄiau tuo paÄiu pavadinimu įraÅ¡ytÄ… failÄ…?" #, fuzzy msgid "hotkey|battle options" msgstr "IÅ¡eiti iÅ¡ pilies" msgid "hotkey|skip turn in battle" msgstr "" msgid "hotkey|cast battle spell" msgstr "" msgid "hotkey|dwelling level 1" msgstr "" msgid "hotkey|dwelling level 2" msgstr "" msgid "hotkey|dwelling level 3" msgstr "" msgid "hotkey|dwelling level 4" msgstr "" msgid "hotkey|dwelling level 5" msgstr "" msgid "hotkey|dwelling level 6" msgstr "" msgid "hotkey|well" msgstr "" #, fuzzy msgid "hotkey|marketplace" msgstr "Turgus" msgid "hotkey|mage guild" msgstr "" #, fuzzy msgid "hotkey|shipyard" msgstr "Laivų statykla" #, fuzzy msgid "hotkey|thieves guild" msgstr "Vagių gildija" msgid "hotkey|tavern" msgstr "" msgid "hotkey|construction screen" msgstr "" msgid "hotkey|buy all monsters in well" msgstr "" msgid "hotkey|split stack by half" msgstr "" msgid "hotkey|split stack by one" msgstr "" msgid "hotkey|join stacks" msgstr "" msgid "hotkey|upgrade troop" msgstr "" msgid "hotkey|dismiss hero or troop" msgstr "" msgid "" "Do you want to regain control from AI? The effect will take place only on " "the next turn." msgstr "" msgid "" "Do you want to transfer control from you to the AI? The effect will take " "place only on the next turn." msgstr "" msgid "Default Actions" msgstr "" msgid "Global Actions" msgstr "" msgid "Main Menu" msgstr "" msgid "World Map" msgstr "" msgid "Battle Screen" msgstr "" msgid "Town Screen" msgstr "" msgid "Army Actions" msgstr "" msgid "Editor" msgstr "" msgid "The save file is corrupted." msgstr "" msgid "Unsupported save format: " msgstr "" msgid "Current game version: " msgstr "" msgid "Last supported version: " msgstr "" msgid "This file contains a save with an invalid game type." msgstr "" msgid "" "This save file requires \"The Price of Loyalty\" game assets, but they have " "not been provided to the engine." msgstr "" msgid "This saved game is localized to '" msgstr "" msgid "' language, but the current language of the game is '" msgstr "" msgid "Hot Seat" msgstr "" msgid "" "Play a Hot Seat game, where 2 to 4 players play around the same computer, " "switching into the 'Hot Seat' when it is their turn." msgstr "" msgid "A single player game playing out a single map." msgstr "" msgid "Standard Game" msgstr "" msgid "A single player game playing through a series of maps." msgstr "" msgid "Campaign Game" msgstr "" msgid "Multi-Player Game" msgstr "" msgid "" "A multi-player game, with several human players completing against each " "other on a single map." msgstr "" msgid "fheroes2 Resurrection Team presents" msgstr "" msgid "Greetings!" msgstr "" msgid "Welcome to Heroes of Might and Magic II powered by fheroes2 engine!" msgstr "" msgid "" "Welcome to Heroes of Might and Magic II powered by the fheroes2 engine!\n" "Before starting the game please select a game resolution." msgstr "" msgid "Please Remember" msgstr "" msgid "" "You can always change the language, resolution and settings of the game by " "clicking on the " msgstr "" msgid "door" msgstr "" msgid " on the left side of the Main Menu, or with the " msgstr "" msgid " button from the " msgstr "" msgid "NEW GAME" msgstr "" msgid "" " menu. \n" "\n" "Enjoy the game!" msgstr "" msgid "The Editor requires \"The Price of Loyalty\" expansion files to work." msgstr "" msgid "Quit Heroes of Might and Magic II and return to the operating system." msgstr "" msgid "Credits" msgstr "" msgid "View the credits screen." msgstr "" msgid "High Scores" msgstr "Geriausi rezultatai" msgid "View the high scores screen." msgstr "" msgid "Change language, resolution and settings of the game." msgstr "" msgid "Game Settings" msgstr "" msgid "" "Required video files for campaign selection window are missing. Please make " "sure that all necessary files are present in the system." msgstr "" msgid "" "Either Roland's or Archibald's campaign from the original Heroes of Might " "and Magic II." msgstr "" msgid "Original Campaign" msgstr "" msgid "Expansion Campaign" msgstr "" msgid "One of the four new campaigns from the Price of Loyalty expansion set." msgstr "" msgid "Loading video. Please wait..." msgstr "" msgid "Host" msgstr "" msgid "" "The host sets up the game options. There can only be one host per network " "game." msgstr "" msgid "Guest" msgstr "" msgid "" "The guest waits for the host to set up the game, then is automatically added " "in. There can be multiple guests for TCP/IP games." msgstr "" msgid "Battle Only" msgstr "" msgid "Setup and play a battle without loading any map." msgstr "" msgid "2 Players" msgstr "" msgid "" "Play with 2 human players, and optionally, up to 4 additional computer " "players." msgstr "" msgid "3 Players" msgstr "" msgid "" "Play with 3 human players, and optionally, up to 3 additional computer " "players." msgstr "" msgid "4 Players" msgstr "" msgid "" "Play with 4 human players, and optionally, up to 2 additional computer " "players." msgstr "" msgid "5 Players" msgstr "" msgid "" "Play with 5 human players, and optionally, up to 1 additional computer " "player." msgstr "" msgid "6 Players" msgstr "" msgid "Play with 6 human players." msgstr "" msgid "Dragon city has fallen! You are now the Master of the Dragons." msgstr "" msgid "" "You captured %{name}!\n" "You are victorious." msgstr "" msgid "" "You have captured the enemy hero %{name}!\n" "Your quest is complete." msgstr "" msgid "" "You have found the %{name}.\n" "Your quest is complete." msgstr "" #, fuzzy msgid "Ultimate Artifact" msgstr "Rasti magiÅ¡kÄ… artefaktÄ… '%{name}'" msgid "" "The enemy is beaten.\n" "Your side has triumphed!" msgstr "" msgid "" "You have built up over %{count} gold in your treasury.\n" "All enemies bow before your wealth and power." msgstr "" msgid "Victory!" msgstr "" msgid "" "The enemy has captured %{name}!\n" "They are triumphant." msgstr "" msgid "" "The enemy has built up over %{count} gold in his treasury.\n" "You must bow done in defeat before his wealth and power." msgstr "" msgid "You have been eliminated from the game!!!" msgstr "" msgid "" "You have lost the hero %{name}.\n" "Your quest is over." msgstr "" msgid "" "You have failed to complete your quest in time.\n" "All is lost." msgstr "" msgid "Defeat!" msgstr "" msgid "" "Base score: %{score}\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" msgid "Defeat all enemy heroes and capture all enemy towns and castles." msgstr "" "NugalÄ—ti visus prieÅ¡o herojus bei užimti visas prieÅ¡o pilis bei miestelius." msgid "Run out of time. (Fail to win by a certain point.)" msgstr "" #, fuzzy msgid "You must defeat the enemy %{enemies}." msgid_plural "You must defeat the enemy alliance of %{enemies}." msgstr[0] "Prarasti miestÄ… '%{name}'." msgstr[1] "Prarasti miestÄ… '%{name}'." msgstr[2] "Prarasti miestÄ… '%{name}'." msgid "" "The alliance consisting of %{allies} and you must defeat the enemy " "%{enemies}." msgid_plural "" "The alliance consisting of %{allies} and you must defeat the enemy alliance " "of %{enemies}." msgstr[0] "" msgstr[1] "" msgstr[2] "" #, fuzzy msgid "Capture the castle '%{name}'." msgstr "Užimti pilį '%{name}'" #, fuzzy msgid "Capture the town '%{name}'." msgstr "Užimti miestÄ… '%{name}'" #, fuzzy msgid "Defeat the hero '%{name}'." msgstr "Prarasti miestÄ… '%{name}'." #, fuzzy msgid "Find the ultimate artifact." msgstr "Rasti magiÅ¡kÄ… artefaktÄ… '%{name}'" #, fuzzy msgid "Find the '%{name}' artifact." msgstr "Rasti magiÅ¡kÄ… artefaktÄ… '%{name}'" #, fuzzy msgid "Accumulate %{count} gold." msgstr "Sukaupti %{count} aukso" msgid "" ", or you may win by defeating all enemy heroes and capturing all enemy towns " "and castles." msgstr "" msgid "Lose the castle '%{name}'." msgstr "Prarasti pilį '%{name}'." msgid "Lose the town '%{name}'." msgstr "Prarasti miestÄ… '%{name}'." msgid "Lose the hero: %{name}." msgstr "" msgid "Fail to win by the end of month %{month}, week %{week}, day %{day}." msgstr "" msgid "%{color} player has been vanquished!" msgstr "" msgid "Major Event!" msgstr "" msgid "Scenario:" msgstr "" msgid "Game Difficulty:" msgstr "" msgid "Opponents:" msgstr "" msgid "Class:" msgstr "" msgid "Rating %{rating}%" msgstr "" msgid "Click here to select which scenario to play." msgstr "" msgid "Scenario" msgstr "" msgid "Game Difficulty" msgstr "" msgid "" "This lets you change the starting difficulty at which you will play. Higher " "difficulty levels start you of with fewer resources, and at the higher " "settings, give extra resources to the computer." msgstr "" msgid "Difficulty Rating" msgstr "" msgid "" "The difficulty rating reflects a combination of various settings for your " "game. This number will be applied to your final score." msgstr "" msgid "Click to accept these settings and start a new game." msgstr "" msgid "Click to return to the main menu." msgstr "" msgid "Astrologers proclaim the Month of the %{name}." msgstr "" msgid "Astrologers proclaim the Week of the %{name}." msgstr "" msgid "After regular growth, the population of %{monster} is doubled!" msgstr "" msgid "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgid_plural "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "%{monster} growth +%{count}." msgstr "" msgid " All populations are halved." msgstr "" msgid " All dwellings increase population." msgstr "" msgid "New Month!" msgstr "" msgid "New Week!" msgstr "" msgid "Beware!" msgstr "" msgid "" "%{color} player, this is your last day to capture a town, or you will be " "banished from this land." msgstr "" "%{color} žaidÄ—jau, Å¡iandien bus paskutinÄ— tavo diena Å¡iame žaidime, jei " "neužimsi kokio nors miesto." msgid "" "%{color} player, you only have %{day} days left to capture a town, or you " "will be banished from this land." msgstr "" "%{color} žaidÄ—jau, tau liko tik %{day} dienos Å¡iame žaidime - jei neužimsi " "kokio nors miesto, bÅ«si paÅ¡alintas." msgid "%{color} player's turn." msgstr "" msgid "" "%{color} player, you have lost your last town. If you do not conquer another " "town in the next week, you will be eliminated." msgstr "" msgid "" "%{color} player, your heroes abandon you, and you are banished from this " "land." msgstr "" msgid "Lord Kilburn" msgstr "" msgid "Tsabu" msgstr "" msgid "Sir Galant" msgstr "" msgid "Thundax" msgstr "" msgid "Lord Haart" msgstr "" msgid "Ariel" msgstr "" msgid "Rebecca" msgstr "" msgid "Sandro" msgstr "" msgid "Crodo" msgstr "" msgid "Barock" msgstr "" msgid "Antoine" msgstr "" msgid "Astra" msgstr "" msgid "Agar" msgstr "" msgid "Vatawna" msgstr "" msgid "Vesper" msgstr "" msgid "Ambrose" msgstr "" msgid "Troyan" msgstr "" msgid "Jojosh" msgstr "" msgid "Wrathmont" msgstr "" msgid "Maximus" msgstr "" msgid "Next Hero" msgstr "" msgid "Select the next Hero." msgstr "" #, fuzzy msgid "Hero Movement" msgstr "Greitis" msgid "" "Start the Hero's movement along the current path or re-visit the object " "occupied by the Hero. Press and hold this button to reset the Hero's path." msgstr "" msgid "Kingdom Summary" msgstr "" msgid "View a Summary of your Kingdom." msgstr "" msgid "Cast an adventure spell." msgstr "" msgid "End Turn" msgstr "" msgid "End your turn and left the computer take its turn." msgstr "" msgid "Adventure Options" msgstr "" msgid "Bring up the adventure options menu." msgstr "" msgid "" "Bring up the file options menu, allowing you to load, save, start a new game " "or quit." msgstr "" msgid "Bring up the system options menu, allowing you to customize your game." msgstr "" msgid "" "One or more heroes may still move, are you sure you want to end your turn?" msgstr "" msgid "Are you sure you want to quit?" msgstr "" #, fuzzy msgid "Are you sure you want to restart? (Your current game will be lost.)" msgstr "" "Ar tikrai norite įkelti anksÄiau įraÅ¡ytÄ… žaidimÄ…? (Dabartinis žaidimas bus " "prarastas, jei neįrašėte)" msgid "Are you sure you want to overwrite the save with this name?" msgstr "" "Ar tikrai norite perraÅ¡yti anksÄiau tuo paÄiu pavadinimu įraÅ¡ytÄ… failÄ…?" msgid "Game saved successfully." msgstr "Žaidimas sÄ—kmingai įraÅ¡ytas." msgid "There was an issue during saving." msgstr "" #, fuzzy msgid "" "Are you sure you want to load a new game? (Your current game will be lost.)" msgstr "" "Ar tikrai norite įkelti anksÄiau įraÅ¡ytÄ… žaidimÄ…? (Dabartinis žaidimas bus " "prarastas, jei neįrašėte)" msgid "Try looking on land!!!" msgstr "" msgid "" "Searching for the Ultimate Artifact is fruitless. Your hero could not carry " "it even if he found it - all his artifact slots are full." msgstr "" msgid "Digging for artifacts requires a whole day, try again tomorrow." msgstr "" "MagiÅ¡ko artefakto kasinÄ—jimas užima visÄ… dienÄ… - bandykite dar kartÄ… rytoj." msgid "" "After spending many hours digging here, you have uncovered the %{artifact}." msgstr "" msgid "Congratulations!" msgstr "" msgid "Nothing here. Where could it be?" msgstr "" msgid "Try searching on clear ground." msgstr "" msgid "A miniature view of the known world. Left click to move viewing area." msgstr "" msgid "Month: %{month} Week: %{week}" msgstr "MÄ—nuo: %{month} SavaitÄ—: %{week}" msgid "Day: %{day}" msgstr "" msgid "" "You find a small\n" "quantity of %{resource}." msgstr "" "JÅ«s radote Å¡iek\n" "tiek %{resource}." msgid "Status Window" msgstr "" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date." msgstr "" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date. Left click here to cycle through these windows." msgstr "" msgid "" "This lets you change player starting positions and colors. A particular " "color will always start in a particular location. Some positions may only be " "played by a computer player or only by a human player." msgstr "" msgid "" "This lets you change the class of a player. Classes are not always " "changeable. Depending on the scenario, a player may receive additional towns " "and/or heroes not of their primary alignment." msgstr "" msgid "Handicap" msgstr "" # msgid "" "This lets you change the handicap of a particular player. Only humans may be " "handicapped. Handicapped players start with fewer resources and earn 15 or " "30% fewer resources per turn for mild and severe handicaps, respectively." msgstr "" msgid "%{color} player" msgstr "" msgid "No Handicap" msgstr "" msgid "No special restrictions on start resources and earning them per turn." msgstr "" msgid "Mild Handicap" msgstr "" # msgid "" "Mild handicapped players start with fewer resources and earn 15% fewer " "resources per turn." msgstr "" msgid "Severe Handicap" msgstr "" # msgid "" "Severe handicapped players start with fewer resources and earn 30% fewer " "resources per turn." msgstr "" msgid "View %{skill} Info" msgstr "ŽiÅ«rÄ—ti %{skill} informacijÄ…" msgid "Keyboard|123" msgstr "" msgid "Keyboard|SPACE" msgstr "" msgid "Keyboard|ABC" msgstr "" msgid "Kingdom Income" msgstr "" msgid "Kingdom Income per day." msgstr "" msgid "For every lighthouse controlled, your ships will move further each day." msgstr "" msgid "English" msgstr "" msgid "French" msgstr "" msgid "Polish" msgstr "" msgid "German" msgstr "" msgid "Russian" msgstr "" msgid "Italian" msgstr "" msgid "Czech" msgstr "" msgid "Norwegian" msgstr "" msgid "Belarusian" msgstr "" msgid "Bulgarian" msgstr "" msgid "Ukrainian" msgstr "" msgid "Romanian" msgstr "" msgid "Spanish" msgstr "" msgid "Swedish" msgstr "" msgid "Portuguese" msgstr "" msgid "Turkish" msgstr "" msgid "Dutch" msgstr "" msgid "Hungarian" msgstr "" msgid "Danish" msgstr "" msgid "Slovak" msgstr "" #, fuzzy msgid "Vietnamese" msgstr "ŽiÅ«rÄ—ti %{name}" msgid ", FPS: " msgstr "" msgid "%{object} robber" msgstr "" msgid "%{object} raided" msgstr "" msgid "Ector" msgstr "" msgid "Gwenneth" msgstr "" msgid "Sir Gallant" msgstr "" msgid "Tyro" msgstr "" msgid "Dimitry" msgstr "" msgid "Ruby" msgstr "" msgid "Crag Hack" msgstr "" msgid "Fineous" msgstr "" msgid "Jezebel" msgstr "" msgid "Atlas" msgstr "" msgid "Ergon" msgstr "" msgid "Jaclyn" msgstr "" msgid "Gem" msgstr "Brangakmenis" msgid "Natasha" msgstr "" msgid "Carlawn" msgstr "" msgid "Luna" msgstr "" msgid "Arie" msgstr "" msgid "Barok" msgstr "" msgid "Kastore" msgstr "" msgid "Falagar" msgstr "" msgid "Dawn" msgstr "" msgid "Flint" msgstr "" msgid "Halon" msgstr "" msgid "Myra" msgstr "" msgid "Myrini" msgstr "" msgid "Wilfrey" msgstr "" msgid "Mandigal" msgstr "" msgid "Sarakin" msgstr "" msgid "Charity" msgstr "" msgid "Darlana" msgstr "" msgid "Ranloo" msgstr "" msgid "Rialdo" msgstr "" msgid "Zam" msgstr "" msgid "Zom" msgstr "" msgid "Celia" msgstr "" msgid "Roxana" msgstr "" msgid "Lord Corlagon" msgstr "" msgid "Lord Halton" msgstr "" msgid "Sister Eliza" msgstr "" msgid "Brother Brax" msgstr "" msgid "Dainwin" msgstr "" msgid "Joseph" msgstr "" msgid "Mog" msgstr "" msgid "Solmyr" msgstr "" msgid "Ceallach" msgstr "" msgid "Drakonia" msgstr "" msgid "Elderian" msgstr "" msgid "Gallavant" msgstr "" msgid "Jarkonas" msgstr "" msgid "Martine" msgstr "" msgid " gives you maximum morale" msgstr "" msgid " gives you maximum luck" msgstr "" msgid "You cannot pick up this artifact, you already have a full load!" msgstr "" msgid "To cast spells, you must first buy a spell book for %{gold} gold." msgstr "" "Burtus galÄ—site naudoti tik nusipirkÄ™ burtų knygÄ… už %{gold} aukso monetų." msgid "Unfortunately, you seem to be a little short of cash at the moment." msgstr "" msgid "Do you wish to buy one?" msgstr "Ar norite nusipirkti vienÄ…?" msgid "%{count} / day" msgstr "" msgid "one" msgstr "" msgid "two" msgstr "" msgid "A whirlpool engulfs your ship. Some of your army has fallen overboard." msgstr "" msgid "Insulted by your refusal of their offer, the monsters attack!" msgstr "" msgid "" "The %{monster}, awed by the power of your forces, begin to scatter.\n" "Do you wish to pursue and engage them?" msgstr "" msgid "Ransacking an enemy camp, you discover a hidden cache of treasures." msgstr "" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with these resources, " "come back next week for more.\"" msgstr "" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there are no resources currently available. Please try " "again next week.\"" msgstr "" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with this gold, come " "back next week for more.\"" msgstr "" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there is no gold currently available. Please try again " "next week.\"" msgstr "" msgid "" "You've found an abandoned lean-to.\n" "Poking about, you discover some resources hidden nearby." msgstr "" msgid "The lean-to is long abandoned. There is nothing of value here." msgstr "" msgid "" "You catch a leprechaun foolishly sleeping amidst a cluster of magic " "mushrooms.\n" "In exchange for his freedom, he guides you to a small pot filled with " "precious things." msgstr "" msgid "" "You've found a magic garden, the kind of place that leprechauns and faeries " "like to cavort in, but there is no one here today.\n" "Perhaps you should try again next week." msgstr "" msgid "You come upon the remains of an unfortunate adventurer." msgstr "" msgid "Treasure" msgstr "" msgid "Searching through the tattered clothing, you find the %{artifact}." msgstr "" msgid "Searching through the tattered clothing, you find nothing." msgstr "" msgid "" "You come across an old wagon left by a trader who didn't quite make it to " "safe terrain." msgstr "" msgid "Unfortunately, others have found it first, and the wagon is empty." msgstr "" msgid "Searching inside, you find the %{artifact}." msgstr "" msgid "Inside, you find some of the wagon's cargo still intact." msgstr "" msgid "You search through the flotsam, and find some wood and some gold." msgstr "" msgid "You search through the flotsam, and find some wood." msgstr "" msgid "You search through the flotsam, but find nothing." msgstr "" msgid "Shrine of the 1st Circle" msgstr "" msgid "" "You come across a small shrine attended by a group of novice acolytes.\n" "In exchange for your protection, they agree to teach you a simple spell - " "'%{spell}'." msgstr "" msgid "Shrine of the 2nd Circle" msgstr "" msgid "" "You come across an ornate shrine attended by a group of rotund friars.\n" "In exchange for your protection, they agree to teach you a spell - " "'%{spell}'." msgstr "" msgid "Shrine of the 3rd Circle" msgstr "" msgid "" "You come across a lavish shrine attended by a group of high priests.\n" "In exchange for your protection, they agree to teach you a sophisticated " "spell - '%{spell}'." msgstr "" msgid "" "\n" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "\n" "Deja, jÅ«s neturite pakankamai iÅ¡minties kad suprasti šį burtÄ…, todÄ—l " "negalite jo iÅ¡mokti." msgid "" "\n" "Unfortunately, you already have knowledge of this spell, so there is nothing " "more for them to teach you." msgstr "" msgid "" "\n" "Unfortunately, you have no Magic Book to record the spell with." msgstr "" msgid "" "You approach the hut and observe a witch inside studying an ancient tome on " "%{skill}.\n" "\n" msgstr "" msgid "" "As you approach, she turns and focuses her one glass eye on you.\n" "\"You already know everything you deserve to learn!\" the witch screeches. " "\"NOW GET OUT OF MY HOUSE!\"" msgstr "" msgid "" "As you approach, she turns and speaks.\n" "\"You already know that which I would teach you. I can help you no further.\"" msgstr "" msgid "" "An ancient and immortal witch living in a hut with bird's legs for stilts " "teaches you %{skill} for her own inscrutable purposes." msgstr "" msgid "As you drink the sweet water, you gain luck for your next battle." msgstr "" msgid "You drink from the enchanted fountain, but nothing happens." msgstr "" msgid "You enter the faerie ring, but nothing happens." msgstr "" msgid "" "Upon entering the mystical faerie ring, your army gains luck for its next " "battle." msgstr "" msgid "" "You've found an ancient and weathered stone idol.\n" "It is supposed to grant luck to visitors, but since the stars are already " "smiling upon you, it does nothing." msgstr "" msgid "" "You've found an ancient and weathered stone idol.\n" "Kissing it is supposed to be lucky, so you do. The stone is very cold to the " "touch." msgstr "" msgid "The mermaids silently entice you to return later and be blessed again." msgstr "" msgid "" "The magical, soothing beauty of the Mermaids reaches you and your crew.\n" "Just for a moment, you forget your worries and bask in the beauty of the " "moment.\n" "The mermaids charms bless you with increased luck for your next combat." msgstr "" msgid "" "You come upon the pyramid of a great and ancient king.\n" "You are tempted to search it for treasure, but all the old stories warn of " "fearful curses and undead guardians.\n" "Will you search?" msgstr "" msgid "" "Upon defeating the monsters, you decipher an ancient glyph on the wall, " "telling the secret of the spell - '" msgstr "" msgid "Unfortunately, you have no Magic Book to record the spell with." msgstr "" msgid "" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "Deja, jÅ«s neturite pakankamai iÅ¡minties kad suprasti šį burtÄ…, todÄ—l " "negalite jo iÅ¡mokti." msgid "" "You come upon the pyramid of a great and ancient king.\n" "Routine exploration reveals that the pyramid is completely empty." msgstr "" msgid "" "A drink at the well is supposed to restore your spell points, but you are " "already at maximum." msgstr "" msgid "A second drink at the well in one day will not help you." msgstr "" msgid "A drink from the well has restored your spell points to maximum." msgstr "" msgid "" "\"I'm sorry sir,\" The leader of the soldiers says, \"but you already know " "everything we have to teach.\"" msgstr "" msgid "The soldiers living in the fort teach you a few new defensive tricks." msgstr "" msgid "" "You've come upon a mercenary camp practicing their tactics. \"You're too " "advanced for us,\" the mercenary captain says. \"We can teach nothing more.\"" msgstr "" msgid "" "You've come upon a mercenary camp practicing their tactics. The mercenaries " "welcome you and your troops and invite you to train with them." msgstr "" msgid "\"Go 'way!\", the witch doctor barks, \"you know all I know.\"" msgstr "" msgid "" "An Orcish witch doctor living in the hut deepens your knowledge of magic by " "showing you how to cast stones, read portents, and decipher the intricacies " "of chicken entrails." msgstr "" msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, the Druids turn you away, indicating they have nothing " "new to teach you." msgstr "" msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, they teach you new ways to cast spells." msgstr "" msgid "" "You tentatively approach the burial ground of ancient warriors. Do you want " "to search the graves?" msgstr "" msgid "" "You spend several hours searching the graves and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" msgid "Upon defeating the Zombies you search the graves and find something!" msgstr "" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the shipwreck?" msgstr "" msgid "" "You spend several hours sifting through the debris and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" msgid "" "Upon defeating the Ghosts you sift through the debris and find something!" msgstr "" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the ship?" msgstr "" msgid "" "Upon defeating the Skeletons you sift through the debris and find something!" msgstr "" msgid "Your men spot a navigational buoy, confirming that you are on course." msgstr "" msgid "" "Your men spot a navigational buoy, confirming that you are on course and " "increasing their morale." msgstr "" msgid "" "The drink at the oasis is refreshing, but offers no further benefit. The " "oasis might help again if you fought a battle first." msgstr "" msgid "" "A drink at the oasis fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" msgid "" "The drink at the watering hole is refreshing, but offers no further benefit. " "The watering hole might help again if you fought a battle first." msgstr "" msgid "" "A drink at the watering hole fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" msgid "" "It doesn't help to pray twice before a battle. Come back after you've fought." msgstr "" msgid "A visit and a prayer at the temple raises the morale of your troops." msgstr "" msgid "" "An old Knight appears on the steps of the gazebo. \"I am sorry, my liege, I " "have taught you all I can.\"" msgstr "" msgid "" "An old Knight appears on the steps of the gazebo. \"My liege, I will teach " "you all that I know to aid you in your travels.\"" msgstr "" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he says, \"I would give you an artifact as a reward, but " "you're all full.\"" msgstr "" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he rewards you for your act of kindness by giving you the " "%{art}." msgstr "" msgid "A leprechaun offers you the %{art} for the small price of %{gold} Gold." msgstr "" msgid "" "A leprechaun offers you the %{art} for the small price of %{gold} Gold and " "%{count} %{res}." msgstr "" msgid "Do you wish to buy this artifact?" msgstr "Ar jÅ«s norite pirkti šį magiÅ¡kÄ… artefaktÄ…?" msgid "" "You try to pay the leprechaun, but realize that you can't afford it. The " "leprechaun stamps his foot and ignores you." msgstr "" msgid "" "Insulted by your refusal of his generous offer, the leprechaun stamps his " "foot and ignores you." msgstr "" msgid "You've found the artifact: " msgstr "" msgid "" "You've found the humble dwelling of a withered hermit. The hermit tells you " "that he is willing to give the %{art} to the first wise person he meets." msgstr "" msgid "" "You've come across the spartan quarters of a retired soldier. The soldier " "tells you that he is willing to pass on the %{art} to the first true leader " "he meets." msgstr "" msgid "" "You've encountered a strange person with a hat and an owl on it. He tells " "you that he is willing to give %{art} if you have %{skill}." msgstr "" msgid "" "You come upon an ancient artifact. As you reach for it, a pack of Rogues " "leap out of the brush to guard their stolen loot." msgstr "" msgid "" "Through a clearing you observe an ancient artifact. Unfortunately, it's " "guarded by a nearby %{monster}. Do you want to fight the %{monster} for the " "artifact?" msgstr "" msgid "Victorious, you take your prize, the %{art}." msgstr "" msgid "" "Discretion is the better part of valor, and you decide to avoid this fight " "for today." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold pieces." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold and the %{art}." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it, " "only to find it empty." msgstr "" msgid "" "After scouring the area, you fall upon a hidden treasure cache. You may take " "the gold or distribute the gold to the peasants for experience. Do you wish " "to keep the gold?" msgstr "" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "%{gold} gold pieces." msgstr "" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "ancient artifact %{art}." msgstr "" msgid "" "You stumble upon a dented and tarnished lamp lodged deep in the earth. Do " "you wish to rub the lamp?" msgstr "" msgid "" "You have taken control of the local Alchemist shop. It will provide you with " "%{count} unit of Mercury per day." msgstr "" msgid "" "You gain control of a sawmill. It will provide you with %{count} units of " "wood per day." msgstr "" "JÅ«s užvaldÄ—te lentpjÅ«vÄ™. Å i lentpjÅ«vÄ— gamins jums %{count} medienos vnt. per " "dienÄ…." msgid "" "You gain control of an ore mine. It will provide you with %{count} units of " "ore per day." msgstr "" "JÅ«s užvaldÄ—te geležies rÅ«dos kasyklÄ…. Å i kasykla iÅ¡kas jums %{count} rÅ«dos " "vnt. per dienÄ…." msgid "" "You gain control of a sulfur mine. It will provide you with %{count} unit of " "sulfur per day." msgstr "" "JÅ«s užvaldÄ—te sieros kasyklÄ…. Å i kasykla iÅ¡kas jums %{count} sieros vnt. per " "dienÄ…." msgid "" "You gain control of a crystal mine. It will provide you with %{count} unit " "of crystal per day." msgstr "" "JÅ«s užvaldÄ—te kristalų kasyklÄ…. Å i kasykla iÅ¡kas jums %{count} kristalÄ…(us) " "per dienÄ…." msgid "" "You gain control of a gem mine. It will provide you with %{count} unit of " "gems per day." msgstr "" "JÅ«s užvaldÄ—te brangakmenių kasyklÄ…. Å i kasykla iÅ¡kas jums %{count} " "brangakmenį per dienÄ…." msgid "" "You gain control of a gold mine. It will provide you with %{count} gold per " "day." msgstr "" "JÅ«s užvaldÄ—te aukso kasyklÄ…. Å i kasykla pagamins jums %{count} aukso monetų " "per dienÄ…." msgid "" "The lighthouse is now under your control, and all of your ships will now " "move further each day." msgstr "" msgid "You gain control of a %{name}." msgstr "" msgid "" "You come upon an abandoned gold mine. The mine appears to be haunted. Do you " "wish to enter?" msgstr "" msgid "You beat the Ghosts and are able to restore the mine to production." msgstr "" msgid "" "A group of %{monster} with a desire for greater glory wish to join you. Do " "you accept?" msgstr "" msgid "As you approach the dwelling, you notice that there is no one here." msgstr "" msgid "" "You search the ruins, but the Medusas that used to live here are gone. " "Perhaps there will be more next week." msgstr "" msgid "" "You've found some Medusas living in the ruins. They are willing to join your " "army for a price. Do you want to recruit Medusas?" msgstr "" msgid "" "You've found a Sprite Tree City. Unfortunately, none of the Sprites living " "there wish to join an army. Maybe next week." msgstr "" msgid "" "Some of the Sprites living in the tree city are willing to join your army " "for a price. Do you want to recruit Sprites?" msgstr "" msgid "" "A colorful Rogues' wagon stands empty here. Perhaps more Rogues will be here " "later." msgstr "" msgid "" "Distant sounds of music and laughter draw you to a colorful wagon housing " "Rogues. Do you wish to have any Rogues join your army?" msgstr "" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. The " "tents are unoccupied. Perhaps more Nomads will be here later." msgstr "" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. Do you " "wish to have any Nomads join you during your travels?" msgstr "" msgid "The pit of mud bubbles for a minute and then lies still." msgstr "" msgid "" "As you approach the bubbling pit of mud, creatures begin to climb out and " "position themselves around it. In unison they say: \"Mother Earth would like " "to offer you a few of her troops. Do you want to recruit Earth Elementals?\"" msgstr "" msgid "You enter the structure of white stone pillars, and find nothing." msgstr "" msgid "" "White stone pillars support a roof that rises up to the sky. As you enter " "the structure, the dead air of the outside gives way to a whirling gust that " "almost pushes you back out. The air current materializes into a barely " "visible form. The creature asks, in what can only be described as a loud " "whisper: \"Why have you come? Are you here to call upon the forces of the " "air?\"" msgstr "" msgid "No Fire Elementals approach you from the lava pool." msgstr "" msgid "" "Beneath a structure that serves to hold in heat, Fire Elementals move about " "in a fiery pool of molten lava. A group of them approach you and offer their " "services. Would you like to recruit Fire Elementals?" msgstr "" msgid "A face forms in the water for a moment, and then is gone." msgstr "" msgid "" "Crystalline structures cast shadows over a small reflective pool of water. " "You peer into the pool, and a face that is not your own peers back. It asks: " "\"Would you like to call upon the powers of water?\"" msgstr "" msgid "This burial site is deathly still." msgstr "" msgid "" "Restless spirits of long dead warriors seeking their final resting place " "offer to join you in hopes of finding peace. Do you wish to recruit ghosts?" msgstr "" msgid "" "The City of the Dead is empty of life, and empty of unlife as well. Perhaps " "some undead will move in next week." msgstr "" msgid "" "Some Liches living here are willing to join your army for a price. Do you " "want to recruit Liches?" msgstr "" msgid "" "You've found the ruins of an ancient city, now inhabited solely by the " "undead. Will you search?" msgstr "" msgid "" "Some of the surviving Liches are impressed by your victory over their " "fellows, and offer to join you for a price. Do you want to recruit Liches?" msgstr "" msgid "" "You've found one of those bridges that Trolls are so fond of living under, " "but there are none here. Perhaps there will be some next week." msgstr "" msgid "" "Some Trolls living under a bridge are willing to join your army, but for a " "price. Do you want to recruit Trolls?" msgstr "" msgid "Trolls living under the bridge challenge you. Will you fight them?" msgstr "" msgid "" "A few Trolls remain, cowering under the bridge. They approach you and offer " "to join your forces as mercenaries. Do you want to buy any Trolls?" msgstr "" msgid "" "The Dragon city has no Dragons willing to join you this week. Perhaps a " "Dragon will become available next week." msgstr "" msgid "" "The Dragon city is willing to offer some Dragons for your army for a price. " "Do you wish to recruit Dragons?" msgstr "" msgid "" "You stand before the Dragon City, a place off-limits to mere humans. Do you " "wish to violate this rule and challenge the Dragons to a fight?" msgstr "" msgid "" "Having defeated the Dragon champions, the city's leaders agree to supply " "some Dragons to your army for a price. Do you wish to recruit Dragons?" msgstr "" msgid "From the observation tower, you are able to see distant lands." msgstr "" msgid "" "The spring only refills once a week, and someone's already been here this " "week." msgstr "" msgid "" "A drink at the spring is supposed to give you twice your normal spell " "points, but you are already at that level." msgstr "" msgid "" "A drink from the spring fills your blood with magic! You have twice your " "normal spell points in reserve." msgstr "" msgid "" "Recognizing you, the butler refuses to admit you. \"The master,\" he says, " "\"will not see the same student twice.\"" msgstr "" msgid "" "The butler admits you to see the master of the house. He trains you in the " "four skills a hero should know." msgstr "" msgid "" "The butler opens the door and looks you up and down. \"You are neither " "famous nor diplomatic enough to be admitted to see my master,\" he sniffs. " "\"Come back when you think yourself worthy.\"" msgstr "" msgid " and " msgstr "" msgid "" "All of the %{monsters} you have in your army have been trained by the battle " "masters of the fort. Your army now contains %{monsters2}." msgstr "" msgid "" "An unusual alliance of Ogres, Orcs, and Dwarves offer to train (upgrade) any " "such troops brought to them. Unfortunately, you have none with you." msgstr "" msgid "All of your %{monsters} have been upgraded into %{monsters2}." msgstr "" msgid "" "A blacksmith working at the foundry offers to convert all Pikemen and " "Swordsmen's weapons brought to him from iron to steel. He also says that he " "knows a process that will convert Iron Golems into Steel Golems. " "Unfortunately, you have none of these troops in your army, so he can't help " "you." msgstr "" msgid "" "The captain looks at you with surprise and says:\n" "\"You already have all the maps I know about. Let me fish in peace now.\"" msgstr "" msgid "" "A retired captain living on this refurbished fishing platform offers to sell " "you maps of the sea he made in his younger days for 1,000 gold. Do you wish " "to buy the maps?" msgstr "" msgid "" "The captain sighs. \"You don't have enough money, eh? You can't expect me " "to give my maps away for free!\"" msgstr "" msgid "" "You come upon an obelisk made from a type of stone you have never seen " "before. Staring at it intensely, the smooth surface suddenly changes to an " "inscription. The inscription is a piece of a lost ancient map. Quickly you " "copy down the piece and the inscription vanishes as abruptly as it appeared." msgstr "" msgid "You have already been to this obelisk." msgstr "" msgid "" "Upon your approach, the tree opens its eyes in delight. \"It is good to see " "you, my student. I hope my teachings have helped you.\"" msgstr "" msgid "" "Upon your approach, the tree opens its eyes in delight. \"Ahh, an " "adventurer! Allow me to teach you a little of what I have learned over the " "ages.\"" msgstr "" msgid "Upon your approach, the tree opens its eyes in delight." msgstr "" msgid "" "\"Ahh, an adventurer! I will be happy to teach you a little of what I have " "learned over the ages for a mere %{count} %{res}.\"" msgstr "" msgid "(Just bury it around my roots.)" msgstr "" msgid "Tears brim in the eyes of the tree." msgstr "" msgid "\"I need %{count} %{res}.\"" msgstr "" msgid "it whispers. (sniff) \"Well, come back when you can pay me.\"" msgstr "" msgid "" "Nestled among the trees sits a blind seer. After you explain the intent of " "your journey, the seer activates his crystal ball, allowing you to see the " "strengths and weaknesses of your opponents." msgstr "" msgid "" "The entrance to the cave is dark, and a foul, sulfurous smell issues from " "the cave mouth. Will you enter?" msgstr "" msgid "Except for evidence of a terrible battle, the cave is empty." msgstr "" msgid "" "You find a powerful and grotesque Demon in the cave. \"Today,\" it rasps, " "\"you will fight and surely die. But I will give you a choice of deaths. You " "may fight me, or you may fight my servants. Do you prefer to fight my " "servants?\"" msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points." msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points and " "%{count} gold." msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and find the %{art} in the back of the cave." msgstr "" msgid "" "Seeing that you do not have %{count} gold, the demon slashes you with its " "claws, and the last thing you see is a red haze." msgstr "" msgid "" "The Demon leaps upon you and has its claws at your throat before you can " "even draw your sword. \"Your life is mine,\" it says. \"I will sell it back " "to you for %{count} gold.\"" msgstr "" msgid "" "Upon defeating the daemon's servants, you find a hidden cache with %{count} " "gold." msgstr "" msgid "" "As you enter the Alchemist's Tower, a hobbled, graying man in a brown cloak " "makes his way towards you." msgstr "" msgid "He checks your pack, and sees that you have 1 cursed item." msgid_plural "" "He checks your pack, and sees that you have %{count} cursed items." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "For %{gold} gold, the alchemist will remove it for you. Do you pay?" msgid_plural "" "For %{gold} gold, the alchemist will remove them for you. Do you pay?" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "" "After you consent to pay the requested amount of gold, the alchemist grabs " "the cursed artifact and throws it into his magical cauldron." msgid_plural "" "After you consent to pay the requested amount of gold, the alchemist grabs " "all cursed artifacts and throws them into his magical cauldron." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "" "You hear a voice from behind the locked door, \"You don't have enough gold " "to pay for my services.\"" msgstr "" msgid "" "You hear a voice from high above in the tower, \"Go away! I can't help you!\"" msgstr "" msgid "" "The head groom speaks to you, \"That is a fine looking horse you have. I am " "afraid we can give you no better, but the horses your cavalry are riding " "look to be of poor breeding stock. We have many trained war horses which " "would aid your riders greatly. I insist you take them.\"" msgstr "" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, he will " "grow tired in a week. You must also let me give better horses to your " "mounted soldiers, their horses look shoddy and weak.\"" msgstr "" msgid "" "The head groom approaches you and speaks, \"You already have a fine horse, " "and have no inexperienced cavalry which might make use of our trained war " "horses.\"" msgstr "" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, his " "endurance will wane with a lot of heavy riding, and you must return for a " "fresh mount in a week. We also have many fine war horses which could benefit " "mounted soldiers, but you have none we can help.\"" msgstr "" msgid "The Arena guards turn you away." msgstr "" msgid "" "You have your crew stop up their ears with wax before the sirens' eerie song " "has any chance of luring them to a watery grave." msgstr "" msgid "" "As the sirens sing their eerie song, your small, determined army manages to " "overcome the urge to dive headlong into the sea." msgstr "" msgid "" "An eerie wailing song emanates from the sirens perched upon the rocks. Many " "of your crew fall under its spell, and dive into the water where they drown. " "You are now wiser for the visit, and gain %{exp} experience." msgstr "" msgid "" "In a dazzling display of daring, you break into the local jail and free the " "hero imprisoned there, who, in return, pledges loyalty to your cause." msgstr "" msgid "" "You already have %{count} heroes, and regretfully must leave the prisoner in " "this jail to languish in agony for untold days." msgstr "" msgid "" "You enter a rickety hut and talk to the magician who lives there. He tells " "you of places near and far which may aid you in your journeys." msgstr "" msgid "This eye seems to be intently studying its surroundings." msgstr "" msgid "You come across a giant Sphinx. The Sphinx remains strangely quiet." msgstr "" msgid "" "\"I have a riddle for you,\" the Sphinx says. \"Answer correctly, and you " "shall be rewarded. Answer incorrectly, and you shall be eaten. Do you accept " "the challenge?\"" msgstr "" msgid "" "The Sphinx asks you the following riddle:\n" "\n" "'%{riddle}'\n" "\n" "Your answer?" msgstr "" msgid "" "\"You guessed incorrectly,\" the Sphinx says, smiling. The Sphinx swipes at " "you with a paw, knocking you to the ground. Another blow makes the world go " "black, and you know no more." msgstr "" msgid "" "Looking somewhat disappointed, the Sphinx sighs. \"You've answered my riddle " "so here's your reward. Now begone.\"" msgstr "" msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "As you speak the magic word, the glowing barrier dissolves into nothingness." msgstr "" msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "You speak, and nothing happens." msgstr "" msgid "" "You enter the tent and see an old woman gazing into a magic gem. She looks " "up and says,\n" "\"In my travels, I have learned much in the way of arcane magic. A great " "oracle taught me his skill. I have the answer you seek.\"" msgstr "" msgid "No spell book is present." msgstr "" msgid "" "This spell costs %{mana} spell points. You have no spell points, so you " "cannot cast it." msgstr "" msgid "" "This spell costs %{mana} spell points. You only have %{point} spell points, " "so you cannot cast it." msgstr "" msgid "The spell was not found." msgstr "" msgid "Only heroes can cast this spell." msgstr "" msgid "This hero is not able to cast adventure spells." msgstr "" msgid "Your hero is too tired to cast this spell today. Try again tomorrow." msgstr "" msgid "This spell cannot be cast on a boat." msgstr "" msgid "This spell can only be cast near an ocean." msgstr "" msgid "" "There are no boats available and no ocean adjacent to the hero where this " "spell will work." msgstr "" msgid "There are no boats available for this spell." msgstr "" msgid "There is no ocean adjacent to the hero where this spell will work." msgstr "" msgid "" "You do not own any town or castle that is not currently occupied by a hero. " "This spell will have no effect." msgstr "" msgid "This hero is already in a town, so this spell will have no effect." msgstr "" msgid "" "The nearest town is %{town}.\n" "\n" "This town is occupied by your hero %{hero}." msgstr "" msgid "This spell is already in effect." msgstr "" msgid "" "No opponent neither has nor can have any hero under their command anymore. " "Casting this spell will have no effect." msgstr "" msgid "" "No opponent has a hero under their command at this time. Casting this spell " "will have no effect." msgstr "" msgid "" "You must be within %{count} spaces of a monster for the Visions spell to " "work." msgstr "" msgid "" "You must be standing on the entrance to a mine (sawmills and alchemist labs " "do not count) to cast this spell." msgstr "" msgid "You must first defeat the ghosts guarding the mine to cast this spell." msgstr "" msgid "" "There are already at least as many elementals guarding the mine as this hero " "can generate. Casting this spell will have no effect." msgstr "" msgid "%{name} the %{race} (Level %{level})" msgstr "" msgid "" "'Spread' combat formation spreads the hero's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" msgid "" "'Grouped' combat formation bunches the hero's army together in the center of " "their side of the battlefield." msgstr "" msgid "Are you sure you want to dismiss this Hero?" msgstr "" msgid "View Experience Info" msgstr "" msgid "View Spell Points Info" msgstr "" msgid "Set army combat formation to 'Spread'" msgstr "" msgid "Set army combat formation to 'Grouped'" msgstr "" msgid "Exit Hero Screen" msgstr "" msgid "You cannot dismiss a hero in a castle" msgstr "" msgid "Dismissal of %{name} the %{race} is prohibited by scenario" msgstr "" msgid "Dismiss %{name} the %{race}" msgstr "" #, fuzzy msgid "Show previous hero" msgstr "Rodyti ankstesnį miestÄ…" #, fuzzy msgid "Show next hero" msgstr "Rodyti kitÄ… miestÄ…" msgid "Blood Morale" msgstr "" msgid "%{morale} Morale" msgstr "" msgid "%{luck} Luck" msgstr "" msgid "Current Luck Modifiers:" msgstr "" msgid "Current Morale Modifiers:" msgstr "" msgid "Entire army is undead, so morale does not apply." msgstr "" msgid "" "Current experience %{exp1}.\n" " Next level %{exp2}." msgstr "" msgid "Level %{level}" msgstr "" msgid "" "%{name} currently has %{point} spell points out of a maximum of %{max}. The " "maximum number of spell points is 10 times the hero's knowledge. It is " "occasionally possible for the hero to have more than their maximum spell " "points via special events." msgstr "" msgid "%{name1} meets %{name2}" msgstr "" msgid "Enemy heroes are now fully identifiable." msgstr "" msgid "Identify Hero" msgstr "" msgid "Select town to port to." msgstr "" msgid "Town Portal" msgstr "" msgid "The creatures are willing to join us!" msgstr "" msgid "All the creatures will join us..." msgstr "" msgid "The creature will join us..." msgid_plural "%{count} of the creatures will join us..." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "" "\n" " for a fee of %{gold} gold." msgstr "" msgid "These weak creatures will surely flee before us." msgstr "" msgid "I fear these creatures are in the mood for a fight." msgstr "" msgid "The hero's attack skill is a bonus added to each unit's attack skill." msgstr "" msgid "The hero's defense skill is a bonus added to each unit's defense skill." msgstr "" #, fuzzy msgid "The hero's spell power determines the duration or power of a spell." msgstr "" "Nuo herojaus burtų galios priklauso burtų galiojimo laikas bei galingumas." msgid "" " The hero's knowledge determines how many spell points the hero may have. " "Under normal circumstances, a hero is limited to 10 spell points per level " "of knowledge." msgstr "" msgid "Current Modifiers:" msgstr "" msgid "skill|Basic" msgstr "" msgid "skill|Advanced" msgstr "" msgid "skill|Expert" msgstr "" msgid "Pathfinding" msgstr "" msgid "Archery" msgstr "" msgid "Logistics" msgstr "" msgid "Scouting" msgstr "" msgid "Diplomacy" msgstr "" msgid "Navigation" msgstr "" msgid "Leadership" msgstr "" msgid "Wisdom" msgstr "IÅ¡mintis" msgid "Mysticism" msgstr "" msgid "Ballistics" msgstr "" msgid "Eagle Eye" msgstr "" msgid "Necromancy" msgstr "" msgid "Estates" msgstr "" msgid "Advanced Archery" msgstr "Pažangus Å¡audymas" msgid "Advanced Pathfinding" msgstr "" msgid "Basic Archery" msgstr "Pradinis Å¡audymas" msgid "Basic Pathfinding" msgstr "" msgid "Expert Pathfinding" msgstr "" msgid "Advanced Logistics" msgstr "" msgid "Basic Logistics" msgstr "" msgid "Basic Scouting" msgstr "" msgid "Expert Archery" msgstr "Eksperto Å¡audymas" msgid "Expert Logistics" msgstr "" msgid "Advanced Diplomacy" msgstr "Pažangi diplomatija" msgid "Advanced Scouting" msgstr "" msgid "Basic Diplomacy" msgstr "PradinÄ— diplomatija" msgid "Expert Diplomacy" msgstr "Eksperto diplomatija" msgid "Expert Scouting" msgstr "" msgid "Advanced Leadership" msgstr "Pažangi lyderystÄ—" msgid "Advanced Navigation" msgstr "Pažangi navigacija" msgid "Basic Leadership" msgstr "PradinÄ— lyderystÄ—" msgid "Basic Navigation" msgstr "PradinÄ— navigacija" msgid "Expert Navigation" msgstr "Eksperto navigacija" msgid "Advanced Wisdom" msgstr "Pažangi iÅ¡mintis" msgid "Basic Mysticism" msgstr "" msgid "Basic Wisdom" msgstr "PradinÄ— iÅ¡mintis" msgid "Expert Leadership" msgstr "Eksperto lyderystÄ—" msgid "Expert Wisdom" msgstr "Eksperto iÅ¡mintis" msgid "Advanced Luck" msgstr "" msgid "Advanced Mysticism" msgstr "" msgid "Basic Luck" msgstr "" msgid "Expert Luck" msgstr "" msgid "Expert Mysticism" msgstr "" msgid "Advanced Ballistics" msgstr "" msgid "Advanced Eagle Eye" msgstr "" msgid "Basic Ballistics" msgstr "" msgid "Basic Eagle Eye" msgstr "" msgid "Expert Ballistics" msgstr "" msgid "Advanced Necromancy" msgstr "" msgid "Basic Estates" msgstr "" msgid "Basic Necromancy" msgstr "" msgid "Expert Eagle Eye" msgstr "" msgid "Expert Necromancy" msgstr "" msgid "Advanced Estates" msgstr "" msgid "Expert Estates" msgstr "" msgid "" "%{skill} reduces the movement penalty for rough terrain by %{count} percent." msgstr "" msgid "%{skill} eliminates the movement penalty for rough terrain." msgstr "" #, fuzzy msgid "" "%{skill} increases the damage done by the hero's range attacking creatures " "by %{count} percent." msgstr "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." #, fuzzy msgid "%{skill} increases the hero's movement points by %{count} percent." msgstr "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." #, fuzzy msgid "%{skill} increases the hero's viewable area by one square." msgid_plural "%{skill} increases the hero's viewable area by %{count} squares." msgstr[0] "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgstr[1] "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgstr[2] "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgid "" "%{skill} allows the hero to negotiate with monsters who are weaker than " "their army, and reduces the cost of surrender." msgstr "" msgid "" "Approximately %{count} percent of the creatures may offer to join the hero." msgstr "" msgid "All of the creatures may offer to join the hero." msgstr "" msgid "" "The cost of surrender is reduced to %{percent} percent of the total cost of " "troops in the army." msgstr "" #, fuzzy msgid "" "%{skill} increases the hero's movement points over water by %{count} percent." msgstr "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." #, fuzzy msgid "%{skill} increases the hero's troops morale by %{count}." msgstr "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgid "%{skill} allows the hero to learn third level spells." msgstr "" msgid "%{skill} allows the hero to learn fourth level spells." msgstr "" msgid "%{skill} allows the hero to learn fifth level spells." msgstr "" #, fuzzy msgid "%{skill} regenerates one additional spell point per day to the hero." msgid_plural "" "%{skill} regenerates %{count} additional spell points per day to the hero." msgstr[0] "%{name} teikia %{count} geležies rÅ«dos vnt. per dienÄ…." msgstr[1] "%{name} teikia %{count} geležies rÅ«dos vnt. per dienÄ…." msgstr[2] "%{name} teikia %{count} geležies rÅ«dos vnt. per dienÄ…." #, fuzzy msgid "%{skill} increases the hero's luck by %{count}." msgstr "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgid "" "%{skill} gives the hero's catapult shots a greater chance to hit and do " "damage to castle walls." msgstr "" msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot has a " "greater chance to hit and do damage to castle walls." msgstr "" msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot " "automatically destroys any wall, except a fortified wall in a Knight castle." msgstr "" msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 1st or " "2nd level spell that was cast by an enemy during combat." msgstr "" msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 3rd " "level spell (or below) that was cast by an enemy during combat." msgstr "" msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 4th " "level spell (or below) that was cast by an enemy during combat." msgstr "" msgid "" "%{skill} allows %{count} percent of the creatures killed in combat to be " "brought back from the dead as Skeletons." msgstr "" #, fuzzy msgid "" "The hero produces %{count} gold pieces per day as tax revenue from estates." msgstr "%{name} kainuos jums %{count} aukso monetų per dienÄ…." msgid "Blue" msgstr "" msgid "Green" msgstr "" msgid "Red" msgstr "" msgid "Yellow" msgstr "" msgid "Orange" msgstr "" msgid "Purple" msgstr "" msgid "barrier|Aqua" msgstr "" msgid "barrier|Blue" msgstr "" msgid "barrier|Brown" msgstr "" msgid "barrier|Gold" msgstr "" msgid "barrier|Green" msgstr "" msgid "barrier|Orange" msgstr "" msgid "barrier|Purple" msgstr "" msgid "barrier|Red" msgstr "" msgid "tent|Aqua" msgstr "" msgid "tent|Blue" msgstr "" msgid "tent|Brown" msgstr "" #, fuzzy msgid "tent|Gold" msgstr "Auksas" msgid "tent|Green" msgstr "" msgid "tent|Orange" msgstr "" msgid "tent|Purple" msgstr "" msgid "tent|Red" msgstr "" msgid "Experience" msgstr "" msgid "" "Experience allows your heroes to go up levels, increasing their primary and " "secondary skills." msgstr "" msgid "Hero/Stats" msgstr "" msgid "Skills" msgstr "Ä®gÅ«džiai" msgid "Town/Castle" msgstr "Miestelis/Pilis" msgid "Garrison" msgstr "" msgid "Gold Per Day:" msgstr "Aukso per dienÄ…:" #, fuzzy msgid "View Heroes." msgstr "Rodyti iÅ¡teklius" #, fuzzy msgid "Towns/Castles" msgstr "Miestelis/Pilis" #, fuzzy msgid "View Towns and Castles." msgstr "Rodyti miestus" msgid "luck|Cursed" msgstr "" msgid "luck|Awful" msgstr "" msgid "luck|Bad" msgstr "" msgid "luck|Normal" msgstr "" msgid "luck|Good" msgstr "" msgid "luck|Great" msgstr "" msgid "luck|Irish" msgstr "" msgid "" "Bad luck sometimes falls on the hero's units in combat, causing their " "attacks to only do half damage." msgstr "" msgid "" "Neutral luck means the hero's units will never get lucky or unlucky attacks " "on the enemy." msgstr "" msgid "" "Good luck sometimes lets the hero's units get lucky attacks (double " "strength) in combat." msgstr "" msgid "morale|Treason" msgstr "" msgid "morale|Awful" msgstr "" msgid "morale|Poor" msgstr "" msgid "morale|Normal" msgstr "" msgid "morale|Good" msgstr "" msgid "morale|Great" msgstr "" msgid "morale|Blood!" msgstr "" msgid "Bad morale may cause the hero's units to freeze in combat." msgstr "" msgid "" "Neutral morale means the hero's units will never be blessed with extra " "attacks or freeze in combat." msgstr "" msgid "Good morale may give the hero's units extra attacks in combat." msgstr "" msgid "Knight" msgstr "" msgid "Barbarian" msgstr "" msgid "Sorceress" msgstr "" msgid "Warlock" msgstr "" msgid "Wizard" msgstr "" msgid "Necromancer" msgstr "" msgid "Multi" msgstr "" msgid "doubleLined|Knight" msgstr "" msgid "doubleLined|Barbarian" msgstr "" msgid "doubleLined|Sorceress" msgstr "" msgid "doubleLined|Warlock" msgstr "" msgid "doubleLined|Wizard" msgstr "" msgid "" "doubleLined|Necro-\n" "mancer" msgstr "" msgid "doubleLinedRace|Multi" msgstr "" msgid "doubleLinedRace|Random" msgstr "" msgid "speed|Standing" msgstr "" msgid "speed|Crawling" msgstr "" msgid "speed|Very Slow" msgstr "" msgid "speed|Slow" msgstr "" msgid "speed|Average" msgstr "" msgid "speed|Fast" msgstr "" msgid "speed|Very Fast" msgstr "" msgid "speed|Ultra Fast" msgstr "" msgid "speed|Blazing" msgstr "" msgid "speed|Instant" msgstr "" msgid "week|Squirrel" msgstr "" msgid "week|Rabbit" msgstr "" msgid "week|Gopher" msgstr "" msgid "week|Badger" msgstr "" #, fuzzy msgid "week|Rat" msgstr "savait." msgid "week|Eagle" msgstr "" msgid "week|Weasel" msgstr "" msgid "week|Raven" msgstr "" msgid "week|Mongoose" msgstr "" #, fuzzy msgid "week|Dog" msgstr "savait." msgid "week|Aardvark" msgstr "" msgid "week|Lizard" msgstr "" msgid "week|Tortoise" msgstr "" msgid "week|Hedgehog" msgstr "" msgid "week|Condor" msgstr "" msgid "week|Ant" msgstr "" msgid "week|Grasshopper" msgstr "" msgid "week|Dragonfly" msgstr "" msgid "week|Spider" msgstr "" msgid "week|Butterfly" msgstr "" msgid "week|Bumblebee" msgstr "" msgid "week|Locust" msgstr "" msgid "week|Earthworm" msgstr "" msgid "week|Hornet" msgstr "" msgid "week|Beetle" msgstr "" msgid "week|PLAGUE" msgstr "" msgid "week|Unnamed" msgstr "" msgid "Desert" msgstr "" msgid "Snow" msgstr "" msgid "Wasteland" msgstr "" msgid "Beach" msgstr "" msgid "Lava" msgstr "" msgid "Dirt" msgstr "" msgid "Grass" msgstr "" msgid "Ocean" msgstr "" msgid "maps|Small" msgstr "" msgid "maps|Medium" msgstr "" msgid "maps|Large" msgstr "" msgid "maps|Extra Large" msgstr "" msgid "maps|Custom Size" msgstr "" msgid "Ore Mine" msgstr "RÅ«dos kasykla" msgid "Sulfur Mine" msgstr "Sieros kasykla" msgid "Crystal Mine" msgstr "Kristalų kasykla" msgid "Gems Mine" msgstr "Brangakmenių telkinys" msgid "Gold Mine" msgstr "Aukso kasykla" msgid "Mine" msgstr "" msgid "Burma shave." msgstr "" msgid "Next sign 50 miles." msgstr "" msgid "See Rock City." msgstr "" msgid "This space for rent." msgstr "" msgid "No object" msgstr "" msgid "Alchemist Lab" msgstr "" msgid "Sign" msgstr "" msgid "Buoy" msgstr "" msgid "Skeleton" msgstr "" msgid "Daemon Cave" msgstr "" msgid "Treasure Chest" msgstr "" msgid "Faerie Ring" msgstr "" msgid "Campfire" msgstr "" msgid "Fountain" msgstr "" msgid "Gazebo" msgstr "" msgid "Genie Lamp" msgstr "" msgid "Archer's House" msgstr "" msgid "Goblin Hut" msgstr "" msgid "Dwarf Cottage" msgstr "" msgid "Peasant Hut" msgstr "" msgid "Stables" msgstr "" msgid "Alchemist's Tower" msgstr "" msgid "Event" msgstr "" msgid "Dragon City" msgstr "" msgid "Lighthouse" msgid_plural "Lighthouses" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "Water Wheel" msgid_plural "Water Wheels" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "Monster" msgstr "" msgid "Obelisk" msgstr "" msgid "Oasis" msgstr "" msgid "Resource" msgstr "IÅ¡tekliai" msgid "Sawmill" msgstr "" msgid "Oracle" msgstr "" msgid "Shrine of the First Circle" msgstr "" msgid "Shipwreck" msgstr "" msgid "Sea Chest" msgstr "" msgid "Desert Tent" msgstr "" msgid "Stone Liths" msgstr "" msgid "Wagon Camp" msgstr "" msgid "Hut of the Magi" msgstr "" msgid "Whirlpool" msgstr "" msgid "Windmill" msgid_plural "Windmills" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "Artifact" msgstr "MagiÅ¡kas artefaktas" msgid "Mermaid" msgstr "" msgid "Boat" msgstr "" #, fuzzy msgid "Random Ultimate Artifact" msgstr "Rasti magiÅ¡kÄ… artefaktÄ… '%{name}'" #, fuzzy msgid "Random Artifact" msgstr "MagiÅ¡kas artefaktas" #, fuzzy msgid "Random Resource" msgstr "IÅ¡tekliai" #, fuzzy msgid "Random Monster" msgstr "AtsitiktinÄ— pilis" msgid "Random Town" msgstr "Atsitiktinis miestas" msgid "Random Castle" msgstr "AtsitiktinÄ— pilis" msgid "Eye of the Magi" msgstr "" #, fuzzy msgid "Random Monster - weak" msgstr "AtsitiktinÄ— pilis" #, fuzzy msgid "Random Monster - medium" msgstr "AtsitiktinÄ— pilis" #, fuzzy msgid "Random Monster - strong" msgstr "AtsitiktinÄ— pilis" #, fuzzy msgid "Random Monster - very strong" msgstr "AtsitiktinÄ— pilis" msgid "Hero" msgstr "" msgid "Nothing Special" msgstr "" msgid "Mossy Rock" msgstr "" msgid "Watch Tower" msgstr "" msgid "Tree House" msgstr "" msgid "Tree City" msgstr "" msgid "Ruins" msgstr "" msgid "Fort" msgstr "" msgid "Abandoned Mine" msgstr "" msgid "Sirens" msgstr "" msgid "Standing Stones" msgstr "" msgid "Idol" msgstr "" msgid "Tree of Knowledge" msgstr "" msgid "Witch Doctor's Hut" msgstr "" msgid "Temple" msgstr "" msgid "Hill Fort" msgstr "" msgid "Halfling Hole" msgstr "" msgid "Mercenary Camp" msgstr "" msgid "Shrine of the Second Circle" msgstr "" msgid "Shrine of the Third Circle" msgstr "" msgid "City of the Dead" msgstr "" msgid "Sphinx" msgstr "" msgid "Wagon" msgstr "" msgid "Tar Pit" msgstr "" msgid "Artesian Spring" msgstr "" msgid "Troll Bridge" msgstr "" msgid "Watering Hole" msgstr "" msgid "Witch's Hut" msgstr "" msgid "Xanadu" msgstr "" msgid "Lean-To" msgstr "" msgid "Magellan's Maps" msgstr "" msgid "Flotsam" msgstr "" msgid "Derelict Ship" msgstr "" msgid "Shipwreck Survivor" msgstr "" msgid "Bottle" msgstr "" msgid "Magic Well" msgstr "" msgid "Magic Garden" msgid_plural "Magic Gardens" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "Observation Tower" msgstr "" msgid "Freeman's Foundry" msgstr "" msgid "Reefs" msgstr "" msgid "Volcano" msgstr "" msgid "Flowers" msgstr "" msgid "Rock" msgstr "" msgid "Water Lake" msgstr "" msgid "Mandrake" msgstr "" msgid "Dead Tree" msgstr "" msgid "Stump" msgstr "" msgid "Crater" msgstr "" msgid "Cactus" msgstr "" msgid "Mound" msgstr "" msgid "Dune" msgstr "" msgid "Lava Pool" msgstr "" msgid "Shrub" msgstr "" msgid "Barrow Mounds" msgstr "" msgid "Random Artifact - Treasure" msgstr "" msgid "Random Artifact - Minor" msgstr "" msgid "Random Artifact - Major" msgstr "" msgid "Barrier" msgstr "" msgid "Traveller's Tent" msgstr "" msgid "Jail" msgstr "" msgid "Fire Summoning Altar" msgstr "" msgid "Air Summoning Altar" msgstr "" msgid "Earth Summoning Altar" msgstr "" msgid "Water Summoning Altar" msgstr "" msgid "Unknown Monster" msgstr "" msgid "Unknown Monsters" msgstr "" msgid "Peasant" msgstr "" msgid "Peasants" msgstr "" #, fuzzy msgid "Archer" msgstr "Pradinis Å¡audymas" #, fuzzy msgid "Archers" msgstr "Pradinis Å¡audymas" msgid "Ranger" msgstr "" msgid "Rangers" msgstr "" msgid "Pikeman" msgstr "" msgid "Pikemen" msgstr "" msgid "Veteran Pikeman" msgstr "" msgid "Veteran Pikemen" msgstr "" msgid "Swordsman" msgstr "" msgid "Swordsmen" msgstr "" msgid "Master Swordsman" msgstr "" msgid "Master Swordsmen" msgstr "" msgid "Cavalries" msgstr "" msgid "Cavalry" msgstr "" msgid "Champion" msgstr "" msgid "Champions" msgstr "" msgid "Paladin" msgstr "" msgid "Paladins" msgstr "" msgid "Crusader" msgstr "" msgid "Crusaders" msgstr "" msgid "Goblin" msgstr "" msgid "Goblins" msgstr "" #, fuzzy msgid "Orc" msgstr "RÅ«da" msgid "Orcs" msgstr "" msgid "Orc Chief" msgstr "" msgid "Orc Chiefs" msgstr "" msgid "Wolf" msgstr "" msgid "Wolves" msgstr "" msgid "Ogre" msgstr "" msgid "Ogres" msgstr "" msgid "Ogre Lord" msgstr "" msgid "Ogre Lords" msgstr "" msgid "Troll" msgstr "" msgid "Trolls" msgstr "" msgid "War Troll" msgstr "" msgid "War Trolls" msgstr "" msgid "Cyclopes" msgstr "" msgid "Cyclops" msgstr "" msgid "Sprite" msgstr "" msgid "Sprites" msgstr "" msgid "Dwarf" msgstr "" msgid "Dwarves" msgstr "" msgid "Battle Dwarf" msgstr "" msgid "Battle Dwarves" msgstr "" msgid "Elf" msgstr "" msgid "Elves" msgstr "" msgid "Grand Elf" msgstr "" msgid "Grand Elves" msgstr "" msgid "Druid" msgstr "" msgid "Druids" msgstr "" msgid "Greater Druid" msgstr "" msgid "Greater Druids" msgstr "" msgid "Unicorn" msgstr "" msgid "Unicorns" msgstr "" msgid "Phoenix" msgstr "" msgid "Phoenixes" msgstr "" msgid "Centaur" msgstr "" msgid "Centaurs" msgstr "" msgid "Gargoyle" msgstr "" msgid "Gargoyles" msgstr "" msgid "Griffin" msgstr "" msgid "Griffins" msgstr "" msgid "Minotaur" msgstr "" msgid "Minotaurs" msgstr "" msgid "Minotaur King" msgstr "" msgid "Minotaur Kings" msgstr "" msgid "Hydra" msgstr "" msgid "Hydras" msgstr "" #, fuzzy msgid "Green Dragon" msgstr "Žalias bokÅ¡tas" msgid "Green Dragons" msgstr "" msgid "Red Dragon" msgstr "" msgid "Red Dragons" msgstr "" #, fuzzy msgid "Black Dragon" msgstr "Juodas bokÅ¡tas" msgid "Black Dragons" msgstr "" msgid "Halfling" msgstr "" msgid "Halflings" msgstr "" msgid "Boar" msgstr "" msgid "Boars" msgstr "" msgid "Iron Golem" msgstr "" msgid "Iron Golems" msgstr "" msgid "Steel Golem" msgstr "" msgid "Steel Golems" msgstr "" msgid "Roc" msgstr "" msgid "Rocs" msgstr "" msgid "Mage" msgstr "" msgid "Magi" msgstr "" msgid "Archmage" msgstr "" msgid "Archmagi" msgstr "" msgid "Giant" msgstr "" msgid "Giants" msgstr "" msgid "Titan" msgstr "" msgid "Titans" msgstr "" msgid "Skeletons" msgstr "" msgid "Zombie" msgstr "" msgid "Zombies" msgstr "" msgid "Mutant Zombie" msgstr "" msgid "Mutant Zombies" msgstr "" msgid "Mummies" msgstr "" msgid "Mummy" msgstr "" msgid "Royal Mummies" msgstr "" msgid "Royal Mummy" msgstr "" msgid "Vampire" msgstr "" msgid "Vampires" msgstr "" msgid "Vampire Lord" msgstr "" msgid "Vampire Lords" msgstr "" msgid "Lich" msgstr "" msgid "Liches" msgstr "" msgid "Power Lich" msgstr "" msgid "Power Liches" msgstr "" msgid "Bone Dragon" msgstr "" msgid "Bone Dragons" msgstr "" msgid "Rogue" msgstr "" msgid "Rogues" msgstr "" msgid "Nomad" msgstr "" msgid "Nomads" msgstr "" msgid "Ghost" msgstr "" msgid "Ghosts" msgstr "" msgid "Genie" msgstr "" msgid "Genies" msgstr "" msgid "Medusa" msgstr "" msgid "Medusas" msgstr "" msgid "Earth Elemental" msgstr "" msgid "Earth Elementals" msgstr "" msgid "Air Elemental" msgstr "" msgid "Air Elementals" msgstr "" msgid "Fire Elemental" msgstr "" msgid "Fire Elementals" msgstr "" msgid "Water Elemental" msgstr "" msgid "Water Elementals" msgstr "" #, fuzzy msgid "Random Monsters" msgstr "AtsitiktinÄ— pilis" #, fuzzy msgid "Random Monster 1" msgstr "AtsitiktinÄ— pilis" #, fuzzy msgid "Random Monsters 1" msgstr "AtsitiktinÄ— pilis" #, fuzzy msgid "Random Monster 2" msgstr "AtsitiktinÄ— pilis" #, fuzzy msgid "Random Monsters 2" msgstr "AtsitiktinÄ— pilis" #, fuzzy msgid "Random Monster 3" msgstr "AtsitiktinÄ— pilis" #, fuzzy msgid "Random Monsters 3" msgstr "AtsitiktinÄ— pilis" #, fuzzy msgid "Random Monster 4" msgstr "AtsitiktinÄ— pilis" #, fuzzy msgid "Random Monsters 4" msgstr "AtsitiktinÄ— pilis" msgid "Double shot" msgstr "" #, fuzzy msgid "2-hex monster" msgstr "Pulti %{monster}" msgid "Double strike" msgstr "" msgid "Double damage to Undead" msgstr "" msgid "% magic resistance" msgstr "" msgid "Immune to Mind spells" msgstr "" msgid "Immune to Elemental spells" msgstr "" msgid "Immune to Fire spells" msgstr "" msgid "Immune to Cold spells" msgstr "" msgid "Immune to " msgstr "" msgid "% immunity to %{spell} spell" msgstr "" msgid "% damage from Elemental spells" msgstr "" msgid "% chance to Dispel beneficial spells" msgstr "" msgid "% chance to Paralyze" msgstr "" msgid "% chance to Petrify" msgstr "" msgid "% chance to Blind" msgstr "" msgid "% chance to Curse" msgstr "" msgid "% chance to cast %{spell} spell" msgstr "" msgid "HP regeneration" msgstr "" msgid "Two hexes attack" msgstr "" msgid "Flyer" msgstr "" msgid "Always retaliates" msgstr "" msgid "Attacks all adjacent enemies" msgstr "" msgid "No melee penalty" msgstr "" #, fuzzy msgid "Dragon" msgstr "Drakonų miestas" msgid "Undead" msgstr "" msgid "No enemy retaliation" msgstr "" msgid "HP drain" msgstr "" #, fuzzy msgid "Cloud attack" msgstr "Dangaus pilis" msgid "Decreases enemy's morale by " msgstr "" msgid "% chance to halve enemy" msgstr "" msgid "Soul Eater" msgstr "" msgid "Elemental" msgstr "" msgid "No Morale" msgstr "" msgid "200% damage from Fire spells" msgstr "" msgid "200% damage from Cold spells" msgstr "" msgid "% damage from %{spell} spell" msgstr "" msgid "% immunity to " msgstr "" msgid "Lightning" msgstr "" msgid "% damage from " msgstr "" msgid "The three Anduran artifacts magically combine into one." msgstr "" msgid "View Spells" msgstr "" #, fuzzy msgid "View %{name} Info" msgstr "ŽiÅ«rÄ—ti %{name}" msgid "Move %{name}" msgstr "" msgid "Cannot move the Spellbook" msgstr "" msgid "This item can't be traded." msgstr "" #, fuzzy msgid "Invalid Artifact" msgstr "MagiÅ¡kas artefaktas" #, fuzzy msgid "The %{name} increases the hero's knowledge by %{count}." msgstr "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgid "Ultimate Book of Knowledge" msgstr "" #, fuzzy msgid "The %{name} increases the hero's attack skill by %{count}." msgstr "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgid "Ultimate Sword of Dominion" msgstr "" #, fuzzy msgid "The %{name} increases the hero's defense skill by %{count}." msgstr "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgid "Ultimate Cloak of Protection" msgstr "" #, fuzzy msgid "The %{name} increases the hero's spell power by %{count}." msgstr "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgid "Ultimate Wand of Magic" msgstr "" #, fuzzy msgid "" "The %{name} increases the hero's attack and defense skills by %{count} each." msgstr "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgid "Ultimate Shield" msgstr "" #, fuzzy msgid "" "The %{name} increases the hero's spell power and knowledge by %{count} each." msgstr "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgid "Ultimate Staff" msgstr "" #, fuzzy msgid "" "The %{name} increases each of the hero's basic skills by %{count} points." msgstr "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgid "Ultimate Crown" msgstr "" msgid "Golden Goose" msgstr "" msgid "The %{name} brings in an income of %{count} gold per day." msgstr "%{name} teikia jums %{count} aukso monetų per dienÄ…." msgid "Arcane Necklace of Magic" msgstr "" msgid "" "After rescuing a Sorceress from a cursed tomb, she rewards your heroism with " "an exquisite jeweled necklace." msgstr "" msgid "Caster's Bracelet of Magic" msgstr "" msgid "" "While searching through the rubble of a caved-in mine, you free a group of " "trapped Dwarves. Grateful, the leader gives you a golden bracelet." msgstr "" msgid "Mage's Ring of Power" msgstr "" msgid "" "A cry of pain leads you to a Centaur, caught in a trap. Upon setting the " "creature free, he hands you a small pouch. Emptying the contents, you find a " "dazzling jeweled ring." msgstr "" msgid "Witch's Broach of Magic" msgstr "" msgid "" "Alongside the remains of a burnt witch lies a beautiful broach, intricately " "designed. Approaching the corpse with caution, you add the broach to your " "inventory." msgstr "" msgid "Medal of Valor" msgstr "" #, fuzzy msgid "The %{name} increases the morale of the hero's army by %{count}." msgstr "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgid "" "Freeing a virtuous maiden from the clutches of an evil overlord, you are " "granted a Medal of Valor by the King's herald." msgstr "" msgid "Medal of Courage" msgstr "" msgid "" "After saving a young boy from a vicious pack of Wolves, you return him to " "his father's manor. The grateful nobleman awards you with a Medal of Courage." msgstr "" msgid "Medal of Honor" msgstr "" msgid "" "After freeing a princess of a neighboring kingdom from the evil clutches of " "despicable slavers, she awards you with a Medal of Honor." msgstr "" msgid "Medal of Distinction" msgstr "" msgid "" "Ridding the countryside of the hideous Minotaur who made a sport of eating " "noblemen's Knights, you are honored with the Medal of Distinction." msgstr "" msgid "Fizbin of Misfortune" msgstr "" #, fuzzy msgid "" "The %{name} greatly decreases the morale of the hero's army by %{count}." msgstr "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgid "" "You stumble upon a medal lying alongside the empty road. Adding the medal to " "your inventory, you become aware that you have acquired the undesirable " "Fizbin of Misfortune, greatly decreasing your army's morale." msgstr "" msgid "Thunder Mace of Dominion" msgstr "" msgid "" "During a sudden storm, a bolt of lightning strikes a tree, splitting it. " "Inside the tree you find a mysterious mace." msgstr "" msgid "Armored Gauntlets of Protection" msgstr "" #, fuzzy msgid "The %{name} increase the hero's defense skill by %{count}." msgstr "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgid "" "You encounter the infamous Black Knight! After a grueling duel ending in a " "draw, the Knight, out of respect, offers you a pair of armored gauntlets." msgstr "" msgid "Defender Helm of Protection" msgstr "" msgid "" "A glint of golden light catches your eye. Upon further investigation, you " "find a golden helm hidden under a bush." msgstr "" msgid "Giant Flail of Dominion" msgstr "" msgid "" "A clumsy Giant has killed himself with his own flail. Knowing your superior " "skill with this weapon, you confidently remove the spectacular flail from " "the fallen Giant." msgstr "" msgid "Ballista of Quickness" msgstr "" msgid "The %{name} gives the hero's catapult one extra shot per combat round." msgstr "" msgid "" "Walking through the ruins of an ancient walled city, you find the instrument " "of the city's destruction, an elaborately crafted ballista." msgstr "" msgid "Stealth Shield of Protection" msgstr "" msgid "" "A stone statue of a warrior holds a silver shield. As you remove the shield, " "the statue crumbles into dust." msgstr "" msgid "Dragon Sword of Dominion" msgstr "" msgid "" "As you are walking along a narrow path, a nearby bush suddenly bursts into " "flames. Before your eyes the flames become the image of a beautiful woman. " "She holds out a magnificent sword to you." msgstr "" msgid "Power Axe of Dominion" msgstr "" msgid "" "You see a silver axe embedded deeply in the ground. After several " "unsuccessful attempts by your army to remove the axe, you tightly grip the " "handle of the axe and effortlessly pull it free." msgstr "" msgid "Divine Breastplate of Protection" msgstr "" msgid "" "A gang of Rogues is sifting through the possessions of dead warriors. " "Scaring off the scavengers, you note the Rogues had overlooked a beautiful " "breastplate." msgstr "" msgid "Minor Scroll of Knowledge" msgstr "" msgid "" "Before you appears a levitating glass case with a scroll, perched upon a bed " "of crimson velvet. At your touch, the lid opens and the scroll floats into " "your awaiting hands." msgstr "" msgid "Major Scroll of Knowledge" msgstr "" msgid "" "Visiting a local wiseman, you explain the intent of your journey. He reaches " "into a sack and withdraws a yellowed scroll and hands it to you." msgstr "" msgid "Superior Scroll of Knowledge" msgstr "" msgid "" "You come across the remains of an ancient Druid. Bones, yellowed with age, " "peer from the ragged folds of her robe. Searching the robe, you discover a " "scroll hidden in the folds." msgstr "" msgid "Foremost Scroll of Knowledge" msgstr "" msgid "" "Mangled bones, yellowed with age, peer from the ragged folds of a dead " "Druid's robe. Searching the robe, you discover a scroll hidden within." msgstr "" msgid "Endless Sack of Gold" msgstr "" #, fuzzy msgid "The %{name} provides the hero with %{count} gold per day." msgstr "%{name} teikia jums %{count} aukso monetų per dienÄ…." msgid "" "A little leprechaun dances gleefully around a magic sack. Seeing you " "approach, he stops in mid-stride. The little man screams and stamps his foot " "ferociously, vanishing into thin air. Remembering the old leprechaun saying " "'Finders Keepers', you grab the sack and leave." msgstr "" msgid "Endless Bag of Gold" msgstr "" msgid "" "A noblewoman, separated from her traveling companions, asks for your help. " "After escorting her home, she rewards you with a bag filled with gold." msgstr "" msgid "Endless Purse of Gold" msgstr "" msgid "" "In your travels, you find a leather purse filled with gold that once " "belonged to a great warrior king who had the ability to transform any " "inanimate object into gold." msgstr "" msgid "Nomad Boots of Mobility" msgstr "" #, fuzzy msgid "The %{name} increase the hero's movement on land." msgstr "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgid "" "A Nomad trader seeks protection from a tribe of Goblins. For your " "assistance, he gives you a finely crafted pair of boots made from the " "softest leather. Looking closely, you see fascinating ancient carvings " "engraved on the leather." msgstr "" msgid "Traveler's Boots of Mobility" msgstr "" msgid "" "Discovering a pair of beautifully beaded boots made from the finest and " "softest leather, you thank the anonymous donor and add the boots to your " "inventory." msgstr "" msgid "Lucky Rabbit's Foot" msgstr "" #, fuzzy msgid "The %{name} increases the luck of the hero's army by %{count}." msgstr "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgid "" "A traveling merchant offers you a rabbit's foot, made of gleaming silver " "fur, for safe passage. The merchant explains the charm will increase your " "luck in combat." msgstr "" msgid "Golden Horseshoe" msgstr "" msgid "" "An ensnared Unicorn whinnies in fright. Murmuring soothing words, you set " "her free. Snorting and stamping her front hoof once, she gallops off. " "Looking down you see a golden horseshoe." msgstr "" msgid "Gambler's Lucky Coin" msgstr "" msgid "" "You have captured a mischievous imp who has been terrorizing the region. In " "exchange for his release, he rewards you with a magical coin." msgstr "" msgid "Four-Leaf Clover" msgstr "" msgid "" "In the middle of a patch of dead and dry vegetation, to your surprise you " "find a healthy green four-leaf clover." msgstr "" msgid "True Compass of Mobility" msgstr "" #, fuzzy msgid "The %{name} increases the hero's movement on land and sea." msgstr "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgid "" "An old man claiming to be an inventor asks you to try his latest invention. " "He then hands you a compass." msgstr "" msgid "Sailor's Astrolabe of Mobility" msgstr "" #, fuzzy msgid "The %{name} increases the hero's movement on sea." msgstr "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgid "" "An old sea captain is being tortured by Ogres. You save him, and in return " "he rewards you with a wondrous instrument to measure the distance of a star." msgstr "" msgid "Evil Eye" msgstr "" msgid "The %{name} reduces the casting cost of curse spells by half." msgstr "" msgid "" "While venturing into a decrepit hut you find the Skeleton of a long dead " "witch. Investigation of the remains reveals a glass eye rolling around " "inside an empty skull." msgstr "" msgid "Enchanted Hourglass" msgstr "" msgid "" "The %{name} extends the duration of all the hero's spells by %{count} turns." msgstr "" msgid "" "A surprise turn in the landscape finds you in the midst of a grisly scene: " "Vultures picking at the aftermath of a terrible battle. Your cursory search " "of the remains turns up an enchanted hourglass." msgstr "" msgid "Gold Watch" msgstr "" msgid "The %{name} doubles the effectiveness of the hero's hypnotize spells." msgstr "" msgid "" "In reward for helping his cart out of a ditch, a traveling potion salesman " "gives you a \"magic\" gold watch. Unbeknownst to him, the watch really is " "magical." msgstr "" msgid "Skullcap" msgstr "" msgid "The %{name} halves the casting cost of all mind influencing spells." msgstr "" msgid "" "A brief stop at an improbable rural inn yields an exchange of money, tales, " "and accidentally, luggage. You find a magical skullcap in your new backpack." msgstr "" msgid "Ice Cloak" msgstr "" msgid "" "The %{name} halves all damage the hero's troops receive from cold spells." msgstr "" msgid "" "Responding to the panicked cries of a damsel in distress, you discover a " "young woman fleeing from a hungry bear. You slay the beast in the nick of " "time, and the grateful Sorceress weaves a magic cloak from the bear's hide." msgstr "" msgid "Fire Cloak" msgstr "" msgid "" "The %{name} halves all damage the hero's troops receive from fire spells." msgstr "" msgid "" "You've come upon a fight between a Necromancer and a Paladin. The " "Necromancer blasts the Paladin with a fire bolt, bringing him to his knees. " "Acting quickly, you slay the evil one before the final blow. The grateful " "Paladin gives you the fire cloak that saved him." msgstr "" msgid "Lightning Helm" msgstr "" msgid "" "The %{name} halves all damage the hero's troops receive from lightning " "spells." msgstr "" msgid "" "A traveling tinker in need of supplies offers you a helm with a thunderbolt " "design on its top in exchange for food and water. Curious, you accept, and " "later find out that the helm is magical." msgstr "" msgid "Evercold Icicle" msgstr "" msgid "" "The %{name} causes the hero's cold spells to do %{count} percent more damage " "to enemy troops." msgstr "" msgid "" "An icicle withstanding the full heat of the noonday sun attracts your " "attention. Intrigued, you break it off, and find that it does not melt in " "your hand." msgstr "" msgid "Everhot Lava Rock" msgstr "" msgid "" "The %{name} causes the hero's fire spells to do %{count} percent more damage " "to enemy troops." msgstr "" msgid "" "Your wanderings bring you into contact with a tribe of ape-like beings using " "a magical lava rock that never cools to light their fires. You take pity on " "them and teach them to make fire with sticks. Believing you to be a god, the " "apes give you their rock." msgstr "" msgid "Lightning Rod" msgstr "" msgid "" "The %{name} causes the hero's lightning spells to do %{count} percent more " "damage to enemy troops." msgstr "" msgid "" "While waiting out a storm, a lighting bolt strikes a nearby cottage's " "lightning rod, which melts and falls to the ground. The tip of the rod, " "however, survives intact and makes your hair stand on end when you touch it. " "Hmm..." msgstr "" msgid "Snake-Ring" msgstr "" msgid "The %{name} halves the casting cost of all of the hero's bless spells." msgstr "" msgid "" "You've found an oddly shaped ring on the finger of a long dead traveler. The " "ring looks like a snake biting its own tail." msgstr "" msgid "Ankh" msgstr "" msgid "" "The %{name} doubles the effectiveness of all of the hero's resurrect and " "animate spells." msgstr "" msgid "" "A fierce windstorm reveals the entrance to a buried tomb. Your investigation " "reveals that the tomb has already been looted, but the thieves overlooked an " "ankh on a silver chain in the dark." msgstr "" msgid "Book of Elements" msgstr "" msgid "" "The %{name} doubles the effectiveness of all of the hero's summoning spells." msgstr "" msgid "" "You come across a conjurer who begs to accompany you and your army awhile " "for safety. You agree, and he offers as payment a copy of the book of the " "elements." msgstr "" msgid "Elemental Ring" msgstr "" msgid "The %{name} halves the casting cost of all summoning spells." msgstr "" msgid "" "While pausing to rest, you notice a bobcat climbing a short tree to get at a " "crow's nest. On impulse, you climb the tree yourself and scare off the cat. " "When you look in the nest, you find a collection of shiny stones and a ring." msgstr "" msgid "Holy Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to curse spells." msgstr "" msgid "" "In your wanderings you come across a hermit living in a small, tidy hut. " "Impressed with your mission, he takes time out from his meditations to bless " "and give you a charm against curses." msgstr "" msgid "Pendant of Free Will" msgstr "" msgid "The %{name} makes all of the hero's troops immune to hypnotize spells." msgstr "" msgid "" "Responding to cries for help, you find river Sprites making a sport of " "dunking an old man. Feeling vengeful, you rescue the man and drag a Sprite " "onto dry land for awhile. The Sprite, uncomfortable in the air, gives you a " "magic pendant to let him go." msgstr "" msgid "Pendant of Life" msgstr "" msgid "The %{name} makes all of the hero's troops immune to death spells." msgstr "" msgid "" "A brief roadside encounter with a small caravan and a game of knucklebones " "wins a magic pendant. Its former owner says that it protects from " "Necromancers' death spells." msgstr "" msgid "Serenity Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to berserk spells." msgstr "" msgid "" "The sounds of combat draw you to the scene of a fight between an old " "Barbarian and an eight-headed Hydra. Your timely intervention swings the " "battle in favor of the man, and he rewards you with a pendant he used to use " "to calm his mind for battle." msgstr "" msgid "Seeing-eye Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to blindness spells." msgstr "" msgid "" "You come upon a very old woman, long blind from cataracts and dying alone. " "You tend to her final needs and promise a proper burial. Grateful, she gives " "you a magic pendant emblazoned with a stylized eye. It lets you see with " "your eyes closed." msgstr "" msgid "Kinetic Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to paralyze spells." msgstr "" msgid "" "You come across a golem wearing a glowing pendant and blocking your way. " "Acting on a hunch, you cut the pendant from its neck. Deprived of its power " "source, the golem breaks down, leaving you with the magical pendant." msgstr "" msgid "Pendant of Death" msgstr "" msgid "The %{name} makes all of the hero's troops immune to holy spells." msgstr "" msgid "" "A quick and deadly battle with a Necromancer wins you his magical pendant. " "Later, a Wizard tells you that the pendant protects undead under your " "control from holy word spells." msgstr "" msgid "Wand of Negation" msgstr "" msgid "" "The %{name} makes all of the hero's troops immune to dispel magic spells." msgstr "" msgid "" "You meet an old Wizard friend of yours traveling in the opposite direction. " "He presents you with a gift: A wand that prevents the use of the dispel " "magic spell on your allies." msgstr "" msgid "Golden Bow" msgstr "Auksinis lankas" msgid "" "The %{name} eliminates the %{count} percent penalty for the hero's troops " "shooting past obstacles (e.g. castle walls)." msgstr "" msgid "" "A chance meeting with a famous Archer finds you in a game of knucklebones " "pitting his bow against your horse. You win." msgstr "" msgid "Telescope" msgstr "" #, fuzzy msgid "" "The %{name} increases the amount of terrain the hero reveals when " "adventuring by %{count} extra square." msgstr "" "Å ulinys padidina prieaugį visuose bÅ«stuose % {count} bÅ«tybÄ—m per savaitÄ™." msgid "" "A merchant from far away lands trades you a new invention of his people for " "traveling supplies. It makes distant objects appear closer, and he calls " "it...\n" "\n" "a telescope." msgstr "" msgid "Statesman's Quill" msgstr "" msgid "" "The %{name} reduces the cost of surrender to %{count} percent of the total " "cost of troops the hero has in their army." msgstr "" msgid "" "You pause to help a diplomat with a broken axle fix his problem. In " "gratitude, he gives you a writing quill with magical properties which he " "says will \"help people see things your way\"." msgstr "" msgid "Wizard's Hat" msgstr "" #, fuzzy msgid "" "The %{name} increases the duration of the hero's spells by %{count} turns." msgstr "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgid "" "You see a Wizard fleeing from a Griffin and riding like the wind. The Wizard " "opens a portal and rides through, getting his hat knocked off by the edge of " "the gate. The Griffin follows; the gate closes. You pick the hat up, dust it " "off, and put it on." msgstr "" msgid "Power Ring" msgstr "" #, fuzzy msgid "The %{name} returns %{count} extra spell points per day to the hero." msgstr "%{name} teikia %{count} geležies rÅ«dos vnt. per dienÄ…." msgid "" "You find a small tree that closely resembles the great Warlock Carnauth with " "a ring around one of its twigs. Scraps of clothing and rotting leather lead " "you to suspect that it IS Carnauth, transformed. Since you can't help him, " "you take the magic ring." msgstr "" msgid "Ammo Cart" msgstr "" msgid "" "The %{name} provides endless ammunition for all of the hero's troops that " "shoot." msgstr "" msgid "" "An ammunition cart in the middle of an old battlefield catches your eye. " "Inspection shows it to be in good working order, so you take it along." msgstr "" msgid "Tax Lien" msgstr "" #, fuzzy msgid "The %{name} costs the hero %{count} gold pieces per day." msgstr "%{name} kainuos jums %{count} aukso monetų per dienÄ…." msgid "" "Your big spending habits have earned you a massive tax bill that you can't " "hope to pay. The tax man takes pity and agrees to only take 250 gold a day " "from your account for life. Check here if you want one dollar to go to the " "presidential campaign election fund." msgstr "" msgid "Hideous Mask" msgstr "" msgid "The %{name} prevents all 'wandering' armies from joining the hero." msgstr "" msgid "" "Your looting of the grave of Sinfilas Gardolad, the famous shapeshifting " "Warlock, unearths his fabled mask. Trembling, you put it on and it twists " "your visage into an awful grimace! Oh no! It's actually the hideous mask of " "Gromluck Greene, and you are stuck with it." msgstr "" msgid "Endless Pouch of Sulfur" msgstr "" #, fuzzy msgid "The %{name} provides %{count} unit of sulfur per day." msgstr "%{name} teikia %{count} sieros vnt. per dienÄ…." msgid "" "You visit an alchemist who, upon seeing your army, is swayed by the " "righteousness of your cause. The newly loyal subject gives you his endless " "pouch of sulfur to help with the war effort." msgstr "" msgid "Endless Vial of Mercury" msgstr "" #, fuzzy msgid "The %{name} provides %{count} unit of mercury per day." msgstr "%{name} teikia %{count} gyvsidabrio vnt. per dienÄ…." msgid "" "A brief stop at a hastily abandoned Wizard's tower turns up a magical vial " "of mercury that always has a little left on the bottom. Recognizing a " "treasure when you see one, you cap it and slip it in your pocket." msgstr "" msgid "Endless Pouch of Gems" msgstr "" #, fuzzy msgid "The %{name} provides %{count} unit of gems per day." msgstr "%{name} teikia %{count} brangakmenį per dienÄ…." msgid "" "A short rainstorm brings forth a rainbow...and you can see the end of it. " "Riding quickly, you seize the pot of gold you find there. The leprechaun who " "owns it, unable to stop you from taking it, offers an endless pouch of gems " "for the return of his gold. You accept." msgstr "" msgid "Endless Cord of Wood" msgstr "" #, fuzzy msgid "The %{name} provides %{count} unit of wood per day." msgstr "%{name} teikia %{count} medienos vnt. per dienÄ…." msgid "" "Pausing to rest and light a cook fire, you pull wood out of a nearby pile of " "dead wood. As you keep pulling wood from the pile, you notice that it " "doesn't shrink. You realize to your delight that the wood is enchanted, so " "you take it along." msgstr "" msgid "Endless Cart of Ore" msgstr "" #, fuzzy msgid "The %{name} provides %{count} unit of ore per day." msgstr "%{name} teikia %{count} geležies rÅ«dos vnt. per dienÄ…." msgid "" "You've found a Goblin weapon smithy making weapons for use against humans. " "With a tremendous yell you and your army descend upon their camp and drive " "them away. A search finds a magic ore cart that never runs out of iron." msgstr "" msgid "Endless Pouch of Crystal" msgstr "" msgid "The %{name} provides %{count} unit of crystal per day." msgstr "%{name} teikia %{count} kristalÄ… per dienÄ…." msgid "" "Taking shelter from a storm in a small cave, you notice a small patch of " "crystal in one corner. Curious, you break a piece off and notice that the " "original crystal grows the lost piece back. You decide to stuff the entire " "patch into a pouch and take it with you." msgstr "" msgid "Spiked Helm" msgstr "" msgid "" "Your army is ambushed by a small tribe of wild (and none too bright) Orcs. " "You fend them off easily and the survivors flee in all directions. One of " "the Orcs was wearing a polished spiked helm. Figuring it will make a good " "souvenir, you take it." msgstr "" msgid "Spiked Shield" msgstr "" msgid "" "You come upon a bridge spanning a dry gully. Before you can cross, a Troll " "steps out from under the bridge and demands payment before it will permit " "you to pass. You refuse, and the Troll charges, forcing you to slay it. You " "take its spiked shield as a trophy." msgstr "" msgid "White Pearl" msgstr "" msgid "" "A walk across a dry saltwater lake bed yields an unlikely prize: A white " "pearl amidst shattered shells and debris." msgstr "" msgid "Black Pearl" msgstr "" msgid "" "Rumors of a Griffin of unusual size preying upon the countryside lead you to " "its cave lair. A quick, brutal fight dispatches the beast, and a search of " "its foul nest turns up a huge black pearl." msgstr "" msgid "Magic Book" msgstr "MagiÅ¡ka knyga" msgid "The %{name} enables the hero to cast spells." msgstr "" msgid "Dummy 1" msgstr "" #, fuzzy msgid "The reserved artifact." msgstr " su vienu artefaktu" msgid "Dummy 2" msgstr "" msgid "Dummy 3" msgstr "" msgid "Dummy 4" msgstr "" msgid "Spell Scroll" msgstr "" msgid "" "This %{name} gives the hero the ability to cast the %{spell} spell if the " "hero has a Magic Book." msgstr "" msgid "" "You find an elaborate container which houses an old vellum scroll. The runes " "on the container are very old, and the artistry with which it was put " "together is stunning. As you pull the scroll out, you feel imbued with " "magical power." msgstr "" msgid "Arm of the Martyr" msgstr "" #, fuzzy msgid "" "The %{name} increases the hero's spell power by %{count} but adds the undead " "morale penalty." msgstr "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgid "" "One of the less intelligent members of your party picks up an arm off of the " "ground. Despite its missing a body, it is still moving. Your troops find the " "dismembered arm repulsive, but you cannot bring yourself to drop it: it " "seems to hold some sort of magical power that influences your decision " "making." msgstr "" msgid "Breastplate of Anduran" msgstr "" #, fuzzy msgid "The %{name} increases the hero's defense by %{count}." msgstr "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgid "" "You come upon a sign. It reads: \"Here lies the body of Anduran. Bow and " "swear fealty, and you shall be rewarded.\" You decide to do as it says. As " "you stand up, you feel a coldness against your skin. Looking down, you find " "that you are suddenly wearing a gleaming, ornate breastplate." msgstr "" msgid "Broach of Shielding" msgstr "" msgid "" "The %{name} provides %{count} percent protection from Armageddon and " "Elemental Storm, but decreases spell power by 2." msgstr "" msgid "" "A kindly Sorceress thinks that your army's defenses could use a magical " "boost. She offers to enchant the Broach that you wear on your cloak, and you " "accept." msgstr "" msgid "Battle Garb of Anduran" msgstr "" msgid "" "The %{name} combines the powers of the three Anduran artifacts. It provides " "maximum luck and morale for the hero's troops and gives the hero the Town " "Portal spell." msgstr "" msgid "" "Out of pity for a poor peasant, you purchase a chest of old junk they are " "hawking for too much gold. Later, as you search through it, you find it " "contains the 3 pieces of the legendary battle garb of Anduran!" msgstr "" msgid "Crystal Ball" msgstr "KriÅ¡tolinis kamuolys" msgid "" "The %{name} lets the hero get more specific information about monsters, " "enemy heroes, and castles nearby the hero." msgstr "" msgid "" "You come upon a caravan of gypsies who are feasting and fortifying their " "bodies with mead. They call you forward and say \"If you prove that you can " "dance the Rama-Buta, we will reward you.\" You don't know it, but try " "anyway. They laugh hysterically, but admire your bravery, giving you a " "Crystal Ball." msgstr "" msgid "Heart of Fire" msgstr "" msgid "" "The %{name} provides %{count} percent protection from fire, but doubles the " "damage taken from cold." msgstr "" msgid "" "You enter a recently burned glade and come upon a Fire Elemental sitting " "atop a rock. It looks up, its flaming face contorted in a look of severe " "pain. It then tosses a glowing object at you. You put up your hands to block " "it, but it passes right through them and sears itself into your chest." msgstr "" msgid "Heart of Ice" msgstr "" msgid "" "The %{name} provides %{count} percent protection from cold, but doubles the " "damage taken from fire." msgstr "" msgid "" "Suddenly, a biting coldness engulfs your body. You seize up, falling from " "your horse. The pain subsides, but you still feel as if your chest is " "frozen. As you pick yourself up off of the ground, you hear hearty laughter. " "You turn around just in time to see a Frost Giant run off into the woods and " "disappear." msgstr "" msgid "Helmet of Anduran" msgstr "" msgid "" "You spy a gleaming object poking up out of the ground. You send a member of " "your party over to investigate. He comes back with a golden helmet in his " "hands. You realize that it must be the helmet of the legendary Anduran, the " "only man who was known to wear solid gold armor." msgstr "" msgid "Holy Hammer" msgstr "" msgid "" "You come upon a battle where a Paladin has been mortally wounded by a group " "of Zombies. He asks you to take his hammer and finish what he started. As " "you pick it up, it begins to hum, and then everything becomes a blur. The " "Zombies lie dead, the hammer dripping with blood. You strap it to your belt." msgstr "" msgid "Legendary Scepter" msgstr "" msgid "The %{name} adds %{count} points to all attributes." msgstr "" msgid "" "Upon cresting a small hill, you come upon a ridiculous looking sight. A " "Sprite is attempting to carry a Scepter that is almost as big as it is. " "Trying not to laugh, you ask, \"Need help?\" The Sprite glares at you and " "answers: \"You think this is funny? Fine. You can carry it. I much prefer " "flying anyway.\"" msgstr "" msgid "" "An old seaman tells you a tale of an enchanted masthead that he used in his " "youth to rally his crew during times of trouble. He then hands you a faded " "map that shows where he hid it. After much exploring, you find it stashed " "underneath a nearby dock." msgstr "" msgid "Masthead" msgstr "" #, fuzzy msgid "" "The %{name} boosts the hero's troops' luck and morale by %{count} each in " "sea combat." msgstr "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgid "Sphere of Negation" msgstr "" msgid "The %{name} disables all spell casting, for both sides, in combat." msgstr "" msgid "" "You stop to help a Peasant catch a runaway mare. To show his gratitude, he " "hands you a tiny sphere. As soon as you grasp it, you feel the magical " "energy drain from your limbs..." msgstr "" msgid "Staff of Wizardry" msgstr "" #, fuzzy msgid "The %{name} boosts the hero's spell power by %{count}." msgstr "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgid "" "While out scaring up game, your troops find a mysterious staff levitating " "about three feet off of the ground. They hand it to you, and you notice an " "inscription. It reads: \"Brains best brawn and magic beats might. Heed my " "words, and you'll win every fight.\"" msgstr "" msgid "Sword Breaker" msgstr "" #, fuzzy msgid "The %{name} increases the hero's defense by %{count} and attack by 1." msgstr "Statula padidina jÅ«sų miesto pajamas %{count} auksinių per dienÄ…." msgid "" "A former Captain of the Guard admires your quest and gives you the enchanted " "Sword Breaker that he relied on during his tour of duty." msgstr "" msgid "Sword of Anduran" msgstr "" msgid "" "A Troll stops you and says: \"Pay me 5,000 gold, or the Sword of Anduran " "will slay you where you stand.\" You refuse. The troll grabs the sword " "hanging from its belt, screams in pain, and runs away. Picking up the fabled " "sword, you give thanks that half-witted Trolls tend to grab the wrong end of " "sharp objects." msgstr "" msgid "Spade of Necromancy" msgstr "" msgid "The %{name} gives the hero increased necromancy skill." msgstr "" msgid "" "A dirty shovel has been thrust into a dirt mound nearby. Upon investigation, " "you discover it to be the enchanted shovel of the Gravediggers, long thought " "lost by mortals." msgstr "" msgid "spellBonus|selected by user" msgstr "" msgid "Wood" msgstr "Mediena" msgid "Mercury" msgstr "Gyvsidabris" msgid "Ore" msgstr "RÅ«da" msgid "Sulfur" msgstr "Siera" msgid "Crystal" msgstr "Kristalai" msgid "Gems" msgstr "Brangakmeniai" msgid "Gold" msgstr "Auksas" msgid "" "There are seven resources in Heroes 2, used to build and improves castles, " "purchase troops and recruit heroes. Gold is the most common, required for " "virtually everything. Wood and ore are used for most buildings. Gems, " "Mercury, Sulfur and Crystal are rare magical resources used for the most " "powerful creatures and buildings." msgstr "" msgid "" "Causes a giant fireball to strike the selected area, damaging all nearby " "creatures." msgstr "" msgid "Fireball" msgstr "" msgid "Fireblast" msgstr "" msgid "" "An improved version of fireball, fireblast affects two hexes around the " "center point of the spell, rather than one." msgstr "" msgid "Causes a bolt of electrical energy to strike the selected creature." msgstr "" msgid "Lightning Bolt" msgstr "" msgid "Chain Lightning" msgstr "" msgid "" "Causes a bolt of electrical energy to strike a selected creature, then " "strike the nearest creature with half damage, then strike the NEXT nearest " "creature with half again damage, and so on, until it becomes too weak to be " "harmful. Warning: This spell can hit your own creatures!" msgstr "" msgid "Teleport" msgstr "" msgid "" "Teleports the creature you select to any open position on the battlefield." msgstr "" msgid "Cure" msgstr "" msgid "" "Removes all negative spells cast upon one of your units, and restores up to " "%{count} HP per level of spell power." msgstr "" msgid "Mass Cure" msgstr "" msgid "" "Removes all negative spells cast upon your forces, and restores up to " "%{count} HP per level of spell power, per creature." msgstr "" msgid "Resurrect" msgstr "" msgid "Resurrects creatures from a damaged or dead unit until end of combat." msgstr "" msgid "Resurrect True" msgstr "" msgid "Resurrects creatures from a damaged or dead unit permanently." msgstr "Atgaivina sužeistas ar numirusias bÅ«tybes." msgid "Haste" msgstr "" msgid "Increases the speed of any creature by %{count}." msgstr "" msgid "Increases the speed of all of your creatures by %{count}." msgstr "" msgid "Mass Haste" msgstr "" msgid "Slows target to half movement rate." msgstr "" msgid "spell|Slow" msgstr "" msgid "Mass Slow" msgstr "" msgid "Slows all enemies to half movement rate." msgstr "" msgid "Clouds the affected creatures' eyes, preventing them from moving." msgstr "" msgid "spell|Blind" msgstr "" msgid "Bless" msgstr "" msgid "Causes the selected creatures to inflict maximum damage." msgstr "" msgid "Causes all of your units to inflict maximum damage." msgstr "" msgid "Mass Bless" msgstr "" msgid "Magically increases the defense skill of the selected creatures." msgstr "MagiÅ¡kai padidina pasirinktų karių gynybos įgÅ«džius." msgid "Stoneskin" msgstr "" #, fuzzy msgid "" "Increases the defense skill of the targeted creatures. This is an improved " "version of Stoneskin." msgstr "MagiÅ¡kai padidina pasirinktų karių gynybos įgÅ«džius." msgid "Steelskin" msgstr "" msgid "Causes the selected creatures to inflict minimum damage." msgstr "" msgid "Curse" msgstr "" msgid "Causes all enemy troops to inflict minimum damage." msgstr "" msgid "Mass Curse" msgstr "" msgid "Damages all undead in the battle." msgstr "" msgid "Holy Word" msgstr "" msgid "" "Damages all undead in the battle. This is an improved version of Holy Word." msgstr "" msgid "Holy Shout" msgstr "" msgid "Anti-Magic" msgstr "" msgid "Prevents harmful magic against the selected creatures." msgstr "" msgid "Dispel Magic" msgstr "" msgid "Removes all magic spells from a single target." msgstr "" msgid "Mass Dispel" msgstr "" msgid "Removes all magic spells from all creatures." msgstr "" msgid "Causes a magic arrow to strike the selected target." msgstr "" msgid "Magic Arrow" msgstr "" msgid "Berserker" msgstr "" msgid "Causes a creature to attack its nearest neighbor." msgstr "" msgid "Armageddon" msgstr "" msgid "" "Holy terror strikes the battlefield, causing severe damage to all creatures." msgstr "" msgid "Elemental Storm" msgstr "" msgid "Magical elements pour down on the battlefield, damaging all creatures." msgstr "" msgid "" "A rain of rocks strikes an area of the battlefield, damaging all nearby " "creatures." msgstr "" msgid "Meteor Shower" msgstr "" msgid "Paralyze" msgstr "" msgid "The targeted creatures are paralyzed, unable to move or retaliate." msgstr "" msgid "Hypnotize" msgstr "" msgid "" "Brings a single enemy unit under your control if its hits are less than " "%{count} times the caster's spell power." msgstr "" msgid "Cold Ray" msgstr "" msgid "Drains body heat from a single enemy unit." msgstr "" msgid "Cold Ring" msgstr "" msgid "" "Drains body heat from all units surrounding the center point, but not " "including the center point." msgstr "" msgid "Disrupting Ray" msgstr "" msgid "Reduces the defense rating of an enemy unit by three." msgstr "" msgid "Damages all living (non-undead) units in the battle." msgstr "" msgid "Death Ripple" msgstr "" msgid "Death Wave" msgstr "" msgid "" "Damages all living (non-undead) units in the battle. This spell is an " "improved version of Death Ripple." msgstr "" msgid "Dragon Slayer" msgstr "" msgid "Greatly increases a unit's attack skill vs. Dragons." msgstr "Smarkiai padidina kario puolimo įgÅ«džius prieÅ¡ drakonus." msgid "Blood Lust" msgstr "" msgid "Increases a unit's attack skill." msgstr "Padidina kario puolimo įgÅ«džius." msgid "Animate Dead" msgstr "" msgid "Resurrects creatures from a damaged or dead undead unit permanently." msgstr "" msgid "Mirror Image" msgstr "" msgid "" "Creates an illusionary unit that duplicates one of your existing units. This " "illusionary unit does the same damages as the original, but will vanish if " "it takes any damage." msgstr "" msgid "Shield" msgstr "" msgid "" "Halves damage received from ranged attacks for a single unit. Does not " "affect damage received from Turrets or Ballistae." msgstr "" msgid "Mass Shield" msgstr "" msgid "" "Halves damage received from ranged attacks for all of your units. Does not " "affect damage received from Turrets or Ballistae." msgstr "" msgid "Summon Earth Elemental" msgstr "" msgid "Summons Earth Elementals to fight for your army." msgstr "" msgid "Summon Air Elemental" msgstr "" msgid "Summons Air Elementals to fight for your army." msgstr "" msgid "Summon Fire Elemental" msgstr "" msgid "Summons Fire Elementals to fight for your army." msgstr "" msgid "Summon Water Elemental" msgstr "" msgid "Summons Water Elementals to fight for your army." msgstr "" msgid "Damages castle walls." msgstr "" msgid "Earthquake" msgstr "" msgid "Causes all mines across the land to become visible." msgstr "" msgid "View Mines" msgstr "" msgid "Causes all resources across the land to become visible." msgstr "" msgid "View Resources" msgstr "Rodyti iÅ¡teklius" msgid "Causes all artifacts across the land to become visible." msgstr "" msgid "View Artifacts" msgstr "Rodyti magiÅ¡kus artefaktus" msgid "Causes all towns and castles across the land to become visible." msgstr "" msgid "View Towns" msgstr "Rodyti miestus" msgid "Causes all Heroes across the land to become visible." msgstr "" msgid "View Heroes" msgstr "" msgid "Causes the entire land to become visible." msgstr "" msgid "View All" msgstr "" msgid "Allows the caster to view detailed information on enemy Heroes." msgstr "" msgid "Summon Boat" msgstr "" msgid "" "Summons the nearest unoccupied, friendly boat to an adjacent shore location. " "A friendly boat is one which you just built or were the most recent player " "to occupy." msgstr "" msgid "Allows the caster to magically transport to a nearby location." msgstr "" msgid "Dimension Door" msgstr "" msgid "Returns the caster to any town or castle currently owned." msgstr "" msgid "Town Gate" msgstr "" msgid "" "Returns the hero to the town or castle of choice, provided it is controlled " "by you." msgstr "" msgid "Visions" msgstr "" msgid "" "Visions predicts the likely outcome of an encounter with a neutral army camp." msgstr "" msgid "Haunt" msgstr "" msgid "" "Haunts a mine you control with Ghosts. This mine stops producing resources. " "(If I can't keep it, nobody will!)" msgstr "" msgid "Set Earth Guardian" msgstr "" msgid "Sets Earth Elementals to guard a mine against enemy armies." msgstr "" msgid "Set Air Guardian" msgstr "" msgid "Sets Air Elementals to guard a mine against enemy armies." msgstr "" msgid "Set Fire Guardian" msgstr "" msgid "Sets Fire Elementals to guard a mine against enemy armies." msgstr "" msgid "Set Water Guardian" msgstr "" msgid "Sets Water Elementals to guard a mine against enemy armies." msgstr "" #, fuzzy msgid "Random Spell" msgstr "Atsitiktinis miestas" msgid "Randomly selected spell of any level." msgstr "" #, fuzzy msgid "Random 1st Level Spell" msgstr "AtsitiktinÄ— pilis" msgid "Randomly selected 1st level spell." msgstr "" msgid "Random 2nd Level Spell" msgstr "" msgid "Randomly selected 2nd level spell." msgstr "" msgid "Random 3rd Level Spell" msgstr "" msgid "Randomly selected 3rd level spell." msgstr "" msgid "Random 4th Level Spell" msgstr "" msgid "Randomly selected 4th level spell." msgstr "" msgid "Random 5th Level Spell" msgstr "" msgid "Randomly selected 5th level spell." msgstr "" #, fuzzy msgid "Petrification" msgstr "Ä®tvirtinimai" msgid "" "Turns the affected creature into stone. A petrified creature receives half " "damage from a direct attack." msgstr "" msgid "You have no Magic Book, so you cannot cast a spell." msgstr "" msgid "No spell to cast." msgstr "" msgid "Your hero has %{point} spell points remaining." msgstr "" msgid "View Adventure Spells" msgstr "" msgid "View Combat Spells" msgstr "" #, fuzzy msgid "View previous page" msgstr "Rodyti ankstesnį miestÄ…" #, fuzzy msgid "View next page" msgstr "ŽiÅ«rÄ—ti %{name}" msgid "Close Spellbook" msgstr "" #, fuzzy msgid "View %{spell}" msgstr "ŽiÅ«rÄ—ti %{name}" msgid "This spell does %{damage} points of damage." msgstr "" msgid "" "This spell summons\n" "%{count} %{monster}." msgstr "" msgid "This spell restores %{hp} HP." msgstr "" msgid "This spell summons %{count} %{monster} to guard the mine." msgstr "" msgid "The nearest town is %{town}." msgstr "" msgid "This town is occupied by your hero %{hero}." msgstr "" msgid "" "This spell controls up to\n" "%{hp} HP." msgstr "" msgid "The ultimate artifact is really the %{name}." msgstr "" msgid "The ultimate artifact may be found in the %{name} regions of the world." msgstr "" msgid "north-west" msgstr "" msgid "north" msgstr "" msgid "north-east" msgstr "" msgid "west" msgstr "" msgid "center" msgstr "" msgid "east" msgstr "" msgid "south-west" msgstr "" msgid "south" msgstr "" msgid "south-east" msgstr "" msgid "The truth is out there." msgstr "" msgid "The dark side is stronger." msgstr "" msgid "The end of the world is near." msgstr "" msgid "The bones of Lord Slayer are buried in the foundation of the arena." msgstr "" msgid "A Black Dragon will take out a Titan any day of the week." msgstr "" msgid "He told her: Yada yada yada... and then she said: Blah, blah, blah..." msgstr "" msgid "An unknown force is being resurrected..." msgstr "" msgid "" "Check the newest version of the game at\n" "https://github.com/ihhub/\n" "fheroes2/releases" msgstr "" #, fuzzy #~ msgid "Castles" #~ msgstr "Pilis" #, fuzzy #~ msgid "%{race2} %{name2}" #~ msgstr "ŽiÅ«rÄ—ti %{name}" fheroes2-1.0.12+dfsg/files/lang/nb.po000066400000000000000000011316511456075706000172330ustar00rootroot00000000000000# Norwegian translation for fheroes2 # Copyright (C) 2021 - 2022 fheroes2 team # This file is distributed under the same license as the fheroes2 package. # msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-03 03:27+0000\n" "PO-Revision-Date: 2023-09-03 18:42+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" "Language: nb_NO\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.2.2\n" msgid "" "BATTLE\n" "ONLY" msgstr "KAMP" msgid "" "NEW\n" "GAME" msgstr "" "NYTT\n" "PARTI" msgid "" "SAVE\n" "GAME" msgstr "" "LAGRE\n" "PARTI" msgid "" "LOAD\n" "GAME" msgstr "" "LAST\n" "INN\n" "PARTI" msgid "INFO" msgstr "INFO" msgid "QUIT" msgstr "" "AV-\n" "SLUTT" msgid "CANCEL" msgstr "AVBRYT" msgid "OKAY" msgstr "OK" msgid "smallerButton|OKAY" msgstr "OK" msgid "ACCEPT" msgstr "GODTA" msgid "DECLINE" msgstr "AVSLÃ…" msgid "LEARN" msgstr "LÆR" msgid "TRADE" msgstr "BYTT" msgid "YES" msgstr "JA" msgid "NO" msgstr "NEI" msgid "EXIT" msgstr "UT" msgid "smallerButton|EXIT" msgstr "UT" msgid "DISMISS" msgstr "BORTVIS" msgid "UPGRADE" msgstr "OPPGRAD." msgid "RESTART" msgstr "OMSTART" msgid "HEROES" msgstr "HELTER" msgid "" "TOWNS/\n" "CASTLES" msgstr "" "BYER/\n" "BORGER" msgid "S" msgstr "L" msgid "M" msgstr "M" msgid "L" msgstr "S" msgid "X-L" msgstr "XS" msgid "ALL" msgstr "ALT" msgid "SELECT" msgstr "ENDRE" msgid "" "STANDARD\n" "GAME" msgstr "" "ENKELT\n" "PARTI" msgid "" "CAMPAIGN\n" "GAME" msgstr "FELTTOG" msgid "" "MULTI-\n" "PLAYER\n" "GAME" msgstr "" "FLER-\n" "SPILLER" msgid "CONFIG" msgstr "VALG" msgid "" "ORIGINAL\n" "CAMPAIGN" msgstr "" "KRIGEN\n" "OM\n" "TRONEN" msgid "" "EXPANSION\n" "CAMPAIGN" msgstr "" "PRISEN FOR\n" "LOJALITET" msgid "HOT SEAT" msgstr "" "BYTTE-\n" "SPILLING" msgid "2 PLAYERS" msgstr "2 SPILLERE" msgid "3 PLAYERS" msgstr "3 SPILLERE" msgid "4 PLAYERS" msgstr "4 SPILLERE" msgid "5 PLAYERS" msgstr "5 SPILLERE" msgid "6 PLAYERS" msgstr "6 SPILLERE" msgid "GIFT" msgstr "SEND" msgid "MAX" msgstr "MAKS" msgid "DIFFICULTY" msgstr "FELTTOGSNIVÃ…" msgid "VIEW INTRO" msgstr "VIS VIDEO" msgid "MIN" msgstr "MIN" msgid "RESET" msgstr "" #, fuzzy msgid "START" msgstr "OMSTART" #, fuzzy msgid "CASTLE" msgstr "" "BYER/\n" "BORGER" msgid "TOWN" msgstr "" msgid "" "D\n" "I\n" "S\n" "M\n" "I\n" "S\n" "S" msgstr "" "B\n" "O\n" "R\n" "T\n" "V\n" "I\n" "S" msgid "" "E\n" "X\n" "I\n" "T" msgstr "" "F\n" "O\n" "R\n" "L\n" "A\n" "T" msgid "" "C\n" "A\n" "M\n" "P\n" "A\n" "I\n" "G\n" "N" msgstr "" msgid "" "S\n" "T\n" "A\n" "N\n" "D\n" "A\n" "R\n" "D" msgstr "" msgid "Warrior" msgstr "Kriger" msgid "Builder" msgstr "Bygger" msgid "Explorer" msgstr "Utforsker" msgid "None" msgstr "Ingen" msgid "" "A few\n" "%{monster}" msgstr "" "Noen fÃ¥\n" "%{monster}" msgid "" "Several\n" "%{monster}" msgstr "" "Flere\n" "%{monster}" msgid "" "A pack of\n" "%{monster}" msgstr "" "En flokk med\n" "%{monster}" msgid "" "Lots of\n" "%{monster}" msgstr "" "Mange\n" "%{monster}" msgid "" "A horde of\n" "%{monster}" msgstr "" "En horde med\n" "%{monster}" msgid "" "A throng of\n" "%{monster}" msgstr "" "En skare av\n" "%{monster}" msgid "" "A swarm of\n" "%{monster}" msgstr "" "En sverm av\n" "%{monster}" msgid "" "Zounds...\n" "%{monster}" msgstr "" "Helsikens mange\n" "%{monster}" msgid "" "A legion of\n" "%{monster}" msgstr "" "En legion av\n" "%{monster}" msgid "army|Few" msgstr "FÃ¥" msgid "army|Several" msgstr "Flere" msgid "army|Pack" msgstr "Flokk" msgid "army|Lots" msgstr "Mange" msgid "army|Horde" msgstr "Horde" msgid "army|Throng" msgstr "Skare" msgid "army|Swarm" msgstr "Sverm" msgid "army|Zounds" msgstr "Helsike" msgid "army|Legion" msgstr "Legion" msgid "All %{race} troops +1" msgstr "Kun styrker av %{race}tilhørighet: +1" msgid "Multiple" msgstr "Flere" msgid "Troops of %{count} alignments -%{penalty}" msgstr "Straff for Ã¥ ha styrker fra %{count} tilhørigheter -%{penalty}" #, fuzzy msgid "Some undead in army -1" msgstr "Vandøde blant styrkene -1" msgid "View %{name}" msgstr "Vis %{name}" msgid "Move the %{name} " msgstr "Flytt %{name} " msgid "Move or right click to redistribute %{name}" msgstr "Flytt eller høyreklikk for Ã¥ omfordele %{name}" msgid "Combine %{name} armies" msgstr "SlÃ¥ sammen %{name} styrker" msgid "Exchange %{name2} with %{name}" msgstr "Bytt %{name2} med %{name}" msgid "Select %{name}" msgstr "Velg %{name}" msgid "Cannot move last troop" msgstr "Kan ikke flytte siste styrke i garnisonen" msgid "Move the %{name}" msgstr "Flytt %{name}" msgid "Set Count" msgstr "Velg antall" msgid "%{name} destroys half the enemy troops!" msgid_plural "%{name} destroy half the enemy troops!" msgstr[0] "Troppen med 1 %{name} halverte fiendestyrken!" msgstr[1] "Troppen med %{name} halverte fiendestyrken!" msgid "%{name} has turned off the auto battle" msgstr "%{name} slo av autotrekk" msgid "%{name} has turned on the auto battle" msgstr "%{name} slo pÃ¥ autotrekk" msgid "Spell failed!" msgstr "Trolldommen mislyktes!" msgid "" "The Sphere of Negation artifact is in effect for this battle, disabling all " "combat spells." msgstr "" "Sfæren av opphevelse er til stede i denne kampen og dermed forhindres all " "kampmagi." msgid "You have already cast a spell this round." msgstr "Du har allerede brukt trolldom denne runden." msgid "That spell will have no effect!" msgstr "Denne trolldommen vil ikke ha noen virkning!" msgid "You may only summon one type of elemental per combat." msgstr "Du kan kun mane fram én type elementærÃ¥nd per kamp." msgid "" "There is no open space adjacent to your hero where you can summon an " "Elemental to." msgstr "Det er ikke plass nær helten din til Ã¥ framkalle en elementærÃ¥nd." msgid "" "The Moat reduces by -%{count} the defense skill of any unit and slows to " "half movement rate." msgstr "" "Vollgraven reduserer forsvarsevnen til krigere som befinner seg i den med " "%{count} og halverer skrittene deres." msgid "Speed: %{speed}" msgstr "Hurtighet : %{speed}" msgid "Speed" msgstr "Hurtighet" msgid "Off" msgstr "Av" msgid "On" msgstr "PÃ¥" #, fuzzy msgid "Turn Order" msgstr "" "Tur-\n" "rekkefølge" msgid "Auto Spell Casting" msgstr "" "Auto-\n" "trylling" msgid "Grid" msgstr "Rutenett" msgid "Shadow Movement" msgstr "" "Uthevd\n" "rekkevidde" msgid "Shadow Cursor" msgstr "" "Rute-\n" "markør" msgid "Audio" msgstr "Lyd" msgid "Settings" msgstr "Innstillinger" msgid "Configure" msgstr "Konfigurer" msgid "Hot Keys" msgstr "Hurtigtaster" msgid "Damage Info" msgstr "Skadeinfo" msgid "Set the speed of combat actions and animations." msgstr "Juster hastigheten til handlingene og animasjonene i kamp." #, fuzzy msgid "Toggle to display the turn order during the battle." msgstr "" "Skru pÃ¥ denne innstillingen for Ã¥ vise rekkefølgen av krigernes tur i kamp." msgid "" "Toggle whether or not the computer will cast spells for you when auto combat " "is on. (Note: This does not affect spell casting for computer players in any " "way, nor does it affect quick combat.)" msgstr "" "Velg om datamaskinen skal bruke trolldom nÃ¥r autokamp er skrudd pÃ¥. (Merk: " "Dette pÃ¥virker ikke magibruken til datamaskinspillere pÃ¥ noen som helst " "mÃ¥te, og det pÃ¥virker heller ikke hurtigkamp.)" msgid "" "Toggle the hex grid on or off. The hex grid always underlies movement, even " "if turned off. This switch only determines if the grid is visible." msgstr "" "Velg om rutenettet for slagmarken skal være synlig eller ikke. Det er de " "samme rutene man kan flytte seg til enten rutenettet vises eller ikke." msgid "" "Toggle on or off shadows showing where your creatures can move and attack." msgstr "" "Velg om rekkevidden for hvor krigerene dine kan flytte seg skal utheves ved " "Ã¥ gjøres mørkere." msgid "" "Toggle on or off a shadow showing the current hex location of the mouse " "cursor." msgstr "" "Velg om den ruten du peker pÃ¥ med markøren din skal utheves ved Ã¥ gjøres " "mørkere." msgid "Change the audio settings of the game." msgstr "Endre lydinnstillingene til spillet." msgid "Check and configure all the hot keys present in the game." msgstr "Kontroller og endre alle hurtigtastene i spillet." msgid "Toggle to display damage information during the battle." msgstr "" "Skru pÃ¥ dette for Ã¥ vise et vindu som forklarer hvor mye skade du kommer til " "Ã¥ gjøre." msgid "Exit this menu." msgstr "Forlat denne menyen." msgid "Okay" msgstr "Bekreft" msgid "The enemy has surrendered!" msgstr "Fienden overga seg!" msgid "Their cowardice costs them %{gold} gold." msgstr "" msgid "The enemy has fled!" msgstr "Fienden rømte!" msgid "A glorious victory!" msgstr "En storslagen seier!" msgid "For valor in combat, %{name} receives %{exp} experience." msgstr "" "For sin utmerkelse i kamp\n" " fÃ¥r %{name} %{exp} erfaringspoeng." msgid "The cowardly %{name} flees from battle." msgstr "Den feige %{name} rømmer fra kampen." msgid "%{name} surrenders to the enemy, and departs in shame." msgstr "%{name} gir opp kampen mot fienden og forlater slagmarken i skam." #, fuzzy msgid "Your forces suffer a bitter defeat, and %{name} abandons your cause." msgstr "Hæren din lider et bittert nederlag og %{name} forlater din sak." #, fuzzy msgid "Your forces suffer a bitter defeat." msgstr "Hæren din lider et bittert nederlag." msgid "Battlefield Casualties" msgstr "Slagmarkens offre" msgid "Attacker" msgstr "Angripende side" msgid "Defender" msgstr "Forsvarende side" #, fuzzy msgid "Click to leave the battle results." msgstr "Trykk for Ã¥ bruke den valgte oppløsningen." #, fuzzy msgid "Click to restart the battle in manual mode." msgstr "Klikk her for Ã¥ dra tilbake til hovedmenyen." msgid "Restart" msgstr "Start pÃ¥ nytt" msgid "You have captured an enemy artifact!" msgstr "Du har overtatt en av fiendens skatter!" msgid "As you reach for the %{name}, it mysteriously disappears." msgstr "Idet du skal til Ã¥ gripe %{name} forsvinner den pÃ¥ mystisk vis." msgid "As your enemy reaches for the %{name}, it mysteriously disappears." msgstr "Idet fienden din skal til Ã¥ ta %{name} forsvinner den pÃ¥ mystisk vis." msgid "Necromancy!" msgstr "Sortekunst!" msgid "" "Practicing the dark arts of necromancy, you are able to raise %{count} of " "the enemy's dead to return under your service as %{monster}." msgstr "" "Gjennom din utøvelse av sortekunst er du i stand til Ã¥ gjenreise %{count} av " "fiendens døde til Ã¥ tjene deg som %{monster}." msgid "%{name} the %{race}" msgstr "%{race}en %{name}" msgid "Captain of %{name}" msgstr "Kapteinen til %{name}" msgid "Attack" msgstr "Angrep" msgid "Defense" msgstr "Forsvar" msgid "Spell Power" msgstr "Kraft" msgid "Knowledge" msgstr "Kunnskap" msgid "Morale" msgstr "KampÃ¥nd" msgid "Luck" msgstr "Flaks" msgid "Spell Points" msgstr "Mana" msgid "Hero's Options" msgstr "Heltevalg" msgid "Cast Spell" msgstr "Bruk trolldom" msgid "Retreat" msgstr "Flykt" msgid "Surrender" msgstr "Overgi deg" msgid "Cancel" msgstr "Avbryt" msgid "Hero Screen" msgstr "Heltevindu" msgid "Captain's Options" msgstr "Innstillinger for kapteinen" msgid "" "Cast a magical spell. You may only cast one spell per combat round. The " "round is reset when every creature has had a turn." msgstr "" "Bruk en trolldom. Du kan kun bruke én per kamprunde. Runden er over nÃ¥r hver " "kriger har gjort sin tur." msgid "" "Retreat your hero, abandoning your creatures. Your hero will be available " "for you to recruit again, however, the hero will have only a novice hero's " "forces." msgstr "" "Flykt med helten din og forlat dine krigere. Helten vil bli tilgjengelig for " "verving med en gang, men kun med startstyrker." #, fuzzy msgid "" "Surrendering costs gold. However if you pay the ransom, the hero and all of " "his or her surviving creatures will be available to recruit again. The cost " "of surrender is half of the total cost of the non-temporary troops remaining " "in the army." msgstr "" "Det koster gull Ã¥ overgi seg. Dersom du betaler løsepengene vil helten din, " "samt alle de gjenværende krigerne, kunne verves igjen." msgid "Open Hero Screen to view full information about the hero." msgstr "Ã…pne heltevinduet for Ã¥ se all informasjon om en helt." msgid "Return to the battle." msgstr "GÃ¥ tilbake til kampen." msgid "Not enough gold (%{gold})" msgstr "Ikke nok gull (%{gold})" msgid "%{name} states:" msgstr "%{name} erklærer:" msgid "Captain of %{name} states:" msgstr "Kapteinen av %{name} erklærer:" msgid "" "\"I will accept your surrender and grant you and your troops safe passage " "for the price of %{price} gold.\"" msgstr "" "\"Jeg vil akseptere din overgivelse og gÃ¥ med pÃ¥ Ã¥ gi deg og dine styrker " "fritt leide imot %{price} gullstykker.\"" msgid "View %{monster} info" msgstr "Vis info om %{monster}" msgid "Fly %{monster} here" msgstr "Fly %{monster} hit" msgid "Move %{monster} here" msgstr "Flytt %{monster} hit" msgid "Shoot %{monster}" msgstr "Skyt pÃ¥ %{monster}" msgid "(1 shot left)" msgid_plural "(%{count} shots left)" msgstr[0] "(1 skudd igjen)" msgstr[1] "(%{count} skudd igjen)" msgid "Attack %{monster}" msgstr "Angrip %{monster}" msgid "Turn %{turn}" msgstr "Runde %{turn}" msgid "Teleport here" msgstr "Teleporter hit" msgid "Invalid teleport destination" msgstr "Ugyldig teleporteringsmÃ¥l" msgid "Cast %{spell} on %{monster}" msgstr "Bruk %{spell} pÃ¥ %{monster}" msgid "Cast %{spell}" msgstr "Bruk %{spell}" msgid "Select spell target" msgstr "Velg trolldomsmÃ¥l" msgid "View Ballista info" msgstr "Vis info om ballisten" msgid "Ballista" msgstr "Ballist" msgid "Enable auto combat" msgstr "Skru pÃ¥ autokamp" msgid "Allows the computer to fight out the battle for you." msgstr "Lar datamaskinen gjøre trekkene for deg." msgid "Auto Combat" msgstr "Autokamp" msgid "Customize system options" msgstr "Skreddersy systeminnstillinger" msgid "Allows you to customize the combat screen." msgstr "Lar deg skreddersy kampvinduet." msgid "System Options" msgstr "Systeminnstillinger" msgid "Skip this unit" msgstr "Hopp over denne styrken" msgid "Skip" msgstr "Hopp over" msgid "" "Skips the current creature. The current creature ends its turn and does not " "get to go again until the next round." msgstr "" "Hopper over den nÃ¥værende krigeren. Turen dens avsluttes og neste tur vil " "ikke komme før neste runde." msgid "View Captain's options" msgstr "Vis innstillinger for kapteinen" msgid "View Hero's options" msgstr "Vis helteinnstillinger" msgid "View opposing Captain" msgstr "Vis info om motstanderens kaptein" msgid "View opposing Hero" msgstr "Vis motstanderhelt" msgid "Hide logs" msgstr "Gjem logg" msgid "Show logs" msgstr "Vis logg" msgid "Message Bar" msgstr "Kamplogg" msgid "Shows the results of individual monster's actions." msgstr "Viser en logg over alle krigernes trekk." msgid "Are you sure you want to finish the battle in auto mode?" msgstr "Er du sikker pÃ¥ at du vil fullføre kampen med automatiske trekk?" msgid "Are you sure you want to enable auto combat?" msgstr "Er du sikker pÃ¥ at du ønsker Ã¥ skru pÃ¥ autokamp?" msgid "%{name} skip their turn." msgstr "Troppen med %{name} hoppet over turen sin." msgid "%{attacker} does %{damage} damage." msgid_plural "%{attacker} do %{damage} damage." msgstr[0] "Troppen med 1 %{attacker} gjorde %{damage} skadepoeng." msgstr[1] "Troppen med %{attacker} gjorde %{damage} skadepoeng." msgid "1 creature perishes." msgid_plural "%{count} creatures perish." msgstr[0] "1 kriger døde." msgstr[1] "%{count} krigere dør." msgid "1 %{defender} perishes." msgid_plural "%{count} %{defender} perish." msgstr[0] "1 %{defender} døde." msgstr[1] "%{count} %{defender} dør." msgid "1 soul is incorporated." msgid_plural "%{count} souls are incorporated." msgstr[0] "1 sjel ble sanket." msgstr[1] "%{count} sjeler ble sanket." msgid "1 %{unit} is revived." msgid_plural "%{count} %{unit} are revived." msgstr[0] "1 %{unit} ble gjenreist." msgstr[1] "%{count} %{unit} ble gjenreist." msgid "Moved %{monster}: from [%{src}] to [%{dst}]." msgstr "Flyttet troppen med %{monster} fra [%{src}] til [%{dst}]." msgid "The %{name} resist the spell!" msgstr "Troppen med %{name} motstod trolldommen!" msgid "%{name} casts %{spell} on the %{troop}." msgstr "%{name} brukte %{spell} pÃ¥ troppen med %{troop}." msgid "%{name} casts %{spell}." msgstr "%{name} brukte %{spell}." msgid "The %{spell} does %{damage} damage to one undead creature." msgstr "%{spell} gjorde %{damage} skadepoeng pÃ¥ én vandød kriger." msgid "The %{spell} does %{damage} damage to all undead creatures." msgstr "%{spell} gjorde %{damage} skadepoeng pÃ¥ alle vandøde." msgid "The %{spell} does %{damage} damage, %{count} creatures perish." msgstr "%{spell} gjorde %{damage} skadepoeng, %{count} krigere dør." msgid "The %{spell} does %{damage} damage." msgstr "%{spell} gjorde %{damage} skadepoeng." msgid "The %{spell} does %{damage} damage to one living creature." msgstr "%{spell} gjorde %{damage} skadepoeng pÃ¥ én levende kriger." msgid "The %{spell} does %{damage} damage to all living creatures." msgstr "%{spell} gjorde %{damage} skadepoeng pÃ¥ alle levende krigere." msgid "The %{attacker}' attack blinds the %{target}!" msgstr "" "Angrepet til troppen med %{attacker} gjorde troppen med %{target} blind!" msgid "The %{attacker}' gaze turns the %{target} to stone!" msgstr "Blikket til troppen med %{attacker} forsteinet troppen med %{target}!" msgid "The %{attacker}' curse falls upon the %{target}!" msgstr "" "Forbannelsen til troppen med %{attacker} la seg over troppen med %{target}!" msgid "The %{target} are paralyzed by the %{attacker}!" msgstr "Troppen med %{target} ble lammet av troppen med %{attacker}!" msgid "The %{attacker} dispel all good spells on your %{target}!" msgstr "" "Troppen med %{attacker} opphevde alle gode trolldommer pÃ¥ dine %{target}!" msgid "The %{attacker} cast %{spell} on %{target}!" msgstr "Gruppen med %{attacker} brukte %{spell} pÃ¥ troppen med %{target}!" # Bestemtform flertall feks. Dvergene fÃ¥r uflaks i stedet for "dverger fÃ¥r uflaks" msgid "Bad luck descends on the %{attacker}." msgstr "Troppen med %{attacker} fikk uflaks." msgid "Good luck shines on the %{attacker}." msgstr "Troppen med %{attacker} fikk flaks." msgid "High morale enables the %{monster} to attack again." msgstr "God kampÃ¥nd gjorde at troppen med %{monster} fikk en ny tur." msgid "Low morale causes the %{monster} to freeze in panic." msgstr "DÃ¥rlig kampÃ¥nd gjorde at troppen med %{monster} stivnet av panikk." # Kan være det venstre tÃ¥rnet eller Ballisten. Foreløpig best er Borgens ballist gjør... msgid "%{tower} does %{damage} damage." msgstr "Borgens %{tower} gjorde %{damage} skadepoeng." msgid "The mirror image is created." msgstr "Speilbildet ble skapt." msgid "The mirror image is destroyed!" msgstr "Speilbildet ble ødelagt!" msgid "Are you sure you want to interrupt the auto battle?" msgstr "Er du sikker pÃ¥ at du vil avbryte de automatiske trekkene?" msgid "Error" msgstr "Feil" msgid "No spells to cast." msgstr "Ingen trolldommer tilgjengelige." msgid "Are you sure you want to retreat?" msgstr "Er du sikker pÃ¥ at du vil flykte fra kampen?" msgid "Retreat disabled" msgstr "Tilbaketrekning forhindret" msgid "Surrender disabled" msgstr "Overgivelse forhindret" msgid "Damage: %{max}" msgstr "Skadepoeng : %{max}" msgid "Damage: %{min} - %{max}" msgstr "Skadepoeng : %{min} - %{max}" msgid "Perish: %{max}" msgstr "Dødstall : %{max}" msgid "Perish: %{min} - %{max}" msgstr "Dødstall : %{min} - %{max}" msgid "" "Through eagle-eyed observation, %{name} is able to learn the magic spell " "%{spell}." msgstr "" "Gjennom sitt skarpe ørneblikk fikk %{name} til Ã¥ plukke opp trolldommen:\n" " %{spell}." msgid "Human" msgstr "Menneske" msgid "AI" msgstr "Datamaskin" #, fuzzy msgid "Start" msgstr "Start pÃ¥ nytt" #, fuzzy msgid "Start the battle." msgstr "GÃ¥ tilbake til kampen." msgid "Exit" msgstr "GÃ¥ ut" msgid "Reset" msgstr "" msgid "Reset to default settings." msgstr "" msgid "Please select another hero." msgstr "Vennligst velg en annen helt." msgid "Set Attack Skill" msgstr "Spesifiser angrepsevne" msgid "Set Defense Skill" msgstr "Spesifiser forsvarsevne" msgid "Set Power Skill" msgstr "Spesifiser kraftsevne" msgid "Set Knowledge Skill" msgstr "Spesifiser kunnskapsevne" #, fuzzy msgid "%{race1} %{name1} vs %{race2} %{name2}" msgstr "%{name1} møter %{name2}" msgid "Monsters" msgstr "Monstre" msgid "N/A" msgstr "Ingen" # Brukes til borgbygging ogsÃ¥. Det venstre tÃ¥rnet eller Ballisten. msgid "Left Turret" msgstr "Venstre tÃ¥rn" msgid "Right Turret" msgstr "Høyre tÃ¥rn" # Samme som over. Det venstre tÃ¥rnet eller Ballisten. msgid "The %{name} fires with the strength of %{count} Archers" msgstr "Borgens %{name} skyter med styrken til %{count} bueskyttere" msgid "each with a +%{attack} bonus to their attack skill." msgstr "hver med en angrepsbonus pÃ¥ +%{attack}." msgid "The %{name} is destroyed." msgstr "Borgens %{name} ble ødelagt." msgid "%{count} %{name} rises from the dead!" msgid_plural "%{count} %{name} rise from the dead!" msgstr[0] "%{count} %{name} gjenoppstod fra de døde!" msgstr[1] "%{count} %{name} gjenoppstod fra de døde!" msgid "Dwarven Alliance" msgstr "Dvergealliansen" msgid "Sorceress Guild" msgstr "Trollkvinnelauget" msgid "Necromancer Guild" msgstr "Sortekunstnerlaug" msgid "Ogre Alliance" msgstr "Jettealliansen" msgid "Dwarfbane" msgstr "Dvergebane" msgid "Dragon Alliance" msgstr "Dragealliansen" msgid "Elven Alliance" msgstr "Alvealliansen" msgid "Kraeger defeated" msgstr "Kreiger fanget" msgid "Wayward Son" msgstr "Den villfarne sønnen" msgid "Uncle Ivan" msgstr "Onkel Ivan" msgid "Annexation" msgstr "Underleggelse" msgid "Force of Arms" msgstr "Til vÃ¥pnene!" msgid "Save the Dwarves" msgstr "Dvergenes unnsetning" msgid "Carator Mines" msgstr "Karatorgruvene" msgid "Turning Point" msgstr "Vendepunktet" msgid "scenarioName|Defender" msgstr "Beskytteren" msgid "Corlagon's Defense" msgstr "Korlagon sitt forsvar" msgid "The Crown" msgstr "Kronen" msgid "The Gauntlet" msgstr "Kappløpet" msgid "Betrayal" msgstr "Forræderi" msgid "Final Justice" msgstr "Rettferd, for godt" #, fuzzy msgid "" "Roland needs you to defeat the lords near his castle to begin his war of " "rebellion against his brother. They are not allied with each other, so they " "will spend most of their time fighting with one another. Victory is yours " "when you have defeated all of their castles and heroes." msgstr "" "Som første steg i opprøret mot sin bror, ber Roland deg om Ã¥ beseire " "krigsherrene i omrÃ¥det rundt hans borg. Krigsherrene er ikke allierte og vil " "dermed bruke mesteparten av sin tid pÃ¥ Ã¥ krige seg imellom. Seieren er din " "nÃ¥r du har erobret alle deres borger og overvunnet deres helter." msgid "" "The local lords refuse to swear allegiance to Roland, and must be subdued. " "They are wealthy and powerful, so be prepared for a tough fight. Capture all " "enemy castles to win." msgstr "" "De lokale fyrstene nekter Ã¥ underlegge seg Roland, og mÃ¥ tvinges til det. De " "er rike og mektige, sÃ¥ vær forberedt pÃ¥ en hard kamp. Erobre alle fiendtlige " "borger for Ã¥ vinne." msgid "" "Your task is to defend the Dwarves against Archibald's forces. Capture all " "of the enemy towns and castles to win, and be sure not to lose all of the " "dwarf towns at once, or the enemy will have won." msgstr "" "Din oppgave er Ã¥ beskytte dvergene fra Archibalds styrker. Erobre alle " "fiendens byer og borger for Ã¥ vinne, og pass pÃ¥ Ã¥ ikke miste alle " "dvergebyene til samme tid, ellers vinner fienden." # rett fram? msgid "" "You will face four allied enemies in a straightforward fight for resource " "and treasure. Capture all of the enemy castles for victory." msgstr "" "Du vil møte fire allierte fiender i en renspikket kamp om rÃ¥varer og " "skatter. Erobre alle fiendens borger for Ã¥ vinne." msgid "" "Your enemies are allied against you and start close by, so be ready to come " "out fighting. You will need to own all four castles in this small valley to " "win." msgstr "" "Dine fiender er allierte mot deg, og de starter i nærheten av deg, sÃ¥ du mÃ¥ " "være klar for kamp nÃ¥r som helst. Du mÃ¥ kontrollere alle de fire borgene i " "denne lille dalen for Ã¥ vinne." msgid "" "The Sorceress' guild of Noraston has requested Roland's aid against an " "attack from Archibald's allies. Capture all of the enemy castles to win, and " "don't lose Noraston, or you'll lose the scenario. (Hint: There is an enemy " "castle on an island in the ocean.)" msgstr "" "Trollkvinnenes laug i Noraston ber om Roland sin hjelp for Ã¥ klare Ã¥ stÃ¥ " "imot et angrep fra Archibald sine allierte. Overta alle fiendens borger for " "Ã¥ vinne og ikke mist Noraston, ellers taper du kartet.\n" "(Hint: Det ligger en fiendtlig borg pÃ¥ en øy i havet.)" msgid "" "Gather as large an army as possible and capture the enemy castle within 8 " "weeks. You are opposed by only one enemy, but must travel a long way to get " "to the enemy castle. Any troops you have in your army at the end of this " "scenario will be with you in the final battle." msgstr "" "Samle sammen en sÃ¥ stor hær som mulig og ta over borgen til fienden innen " "Ã¥tte uker. Du har kun én motstander, men du mÃ¥ reise langt for Ã¥ komme til " "ham. Alle styrkene du mÃ¥tte ha i hæren din ved enden av scenarioet vil følge " "med deg videre til det siste scenarioet." msgid "" "Find the Crown before Archibald's heroes find it. Roland will need the Crown " "for the final battle against Archibald." msgstr "" "Finn kronen før Archibald sine helter finner den. Roland vil trenge kronen " "for det siste slaget mot Archibald." msgid "" "Three allied enemies stand before you and victory, including Lord Corlagon. " "Roland is in a castle to the northwest, and you will lose if he falls to the " "enemy. Remember that capturing Lord Corlagon will ensure that he will not " "fight against you in the final scenario." msgstr "" "Tre allierte fiender stÃ¥r mellom deg og seier. Blant fiendene dine er baron " "Korlagon. Roland er i en borg i nordvest, og du taper dersom han faller i " "fiendens hender. Merk at hvis du fanger baron Korlagon vil det sørge for at " "du ikke møter ham i det siste scenarioet." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Archibald to end the war!" msgstr "" "Dette er det siste slaget. BÃ¥de du og fienden din er væpnet til tennene, og " "alle er allierte mot deg. Fang Archibald for Ã¥ avslutte krigen!" #, fuzzy msgid "" "Switching sides leaves you with three castles against the enemy's one. This " "battle will be the easiest one you will face for the rest of the war..." "traitor." msgstr "" "Hvis du bytter lag vil du begynne med tre borger, mens fienden kun med én. " "Dette slaget vil bli ditt enkleste under hele krigen...forræder." msgid "Barbarian Wars" msgstr "Barbarkrigene" msgid "First Blood" msgstr "Den første bloddrÃ¥pen" msgid "Necromancers" msgstr "Sortekunstnere" # Slakt brukes mer om dyr pÃ¥ norsk. msgid "Slay the Dwarves" msgstr "Myrd dvergene" msgid "Country Lords" msgstr "Landsherrene" msgid "Dragon Master" msgstr "Dragemesteren" msgid "Rebellion" msgstr "Opprør" msgid "Apocalypse" msgstr "Dommedag" msgid "Greater Glory" msgstr "Heder og ære" #, fuzzy msgid "" "King Archibald requires you to defeat the three enemies in this region. They " "are not allied with one another, so they will spend most of their energy " "fighting amongst themselves. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Kong Archibald forlanger at du beseirer hans tre fiender i dette omrÃ¥det. De " "er ikke allierte med hverandre og vil derfor bruke mesteparten av kreftene " "sine pÃ¥ Ã¥ krige seg imellom. Du vinner nÃ¥r du har underlagt deg alle de " "fiendtlige borgene og det ikke er flere helter igjen Ã¥ slÃ¥." msgid "" "You must unify the barbarian tribes of the north by conquering them. As in " "the previous mission, the enemy is not allied against you, but they have " "more resources at their disposal. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Du mÃ¥ forene de barbariske stammene i nord ved Ã¥ beseire dem. Som i det " "forrige oppdraget er fiendene ikke alliert mot deg, men de har mer rÃ¥varer " "tilgjengelig. Du vil vinne nÃ¥r du eier alle de fiendtlige borgene og det " "ikke er flere helter igjen Ã¥ slÃ¥." msgid "" "Do-gooder wizards have taken the Necromancers' castle. You must retake it to " "achieve victory. Remember that while you start with a powerful army, you " "have no castle and must take one within 7 days, or lose this battle. (Hint: " "The nearest castle is to the southeast.)" msgstr "" "Regelryttende magimestere har tatt over Sortekunstnernes borg. Du mÃ¥ " "gjenerobre den for Ã¥ oppnÃ¥ seier. Husk at selv om du starter med en sterk " "hær sÃ¥ har du ikke en borg. Derfor mÃ¥ du overta en innen 7 dager, hvis ikke " "taper du dette slaget. (Hint: Den nærmeste borgen ligger i sørøst.)" msgid "" "The dwarves need conquering before they can interfere in King Archibald's " "plans. Roland's forces have more than one hero and many towns to start with, " "so be ready for attack from multiple directions. You must capture all of the " "enemy towns and castles to claim victory." msgstr "" "Dvergene mÃ¥ slÃ¥s ut før de rekker Ã¥ blande seg i Kong Archibald sine planer. " "Roland sine styrker har mer enn én helt, for ikke Ã¥ nevne flere byer i " "begynnelsen, sÃ¥ vær forberedt pÃ¥ angrep fra alle kanter. Du mÃ¥ overta alle " "de fiendtlige byene og borgene for Ã¥ ta seieren." msgid "" "You must put down a peasant revolt led by Roland's forces. All are allied " "against you, but you have Lord Corlagon, an experienced hero, to help you. " "Capture all enemy castles to win." msgstr "" "Du mÃ¥ slÃ¥ ned pÃ¥ bondeopprøret ledet av Roland sine styrker. Alle er " "allierte mot deg, men du har den erfarne helten fyrst Korlagon pÃ¥ din side. " "Underlegg deg alle fiendens borger for Ã¥ vinne." msgid "" "There are two enemies allied against you in this mission. Both are well " "armed and seek to evict you from their island. Avoid them and capture Dragon " "City to win." msgstr "" "Det er to fiender som er allierte mot deg i dette oppdraget. Begge to er " "godt utrustede og vil forsøke Ã¥ drive deg ut av landet deres. UnngÃ¥ dem og " "overta Dragenes landsby for Ã¥ vinne." msgid "" "Your orders are to conquer the country lords that have sworn to serve " "Roland. All of the enemy castles are unified against you. Since you start " "without a castle, you must hurry to capture one before the end of the week. " "Capture all enemy castles for victory." msgstr "" "Dine ordre er Ã¥ slÃ¥ ut landsherrene som har sverget troskap til Roland. Alle " "de fiendtlige borgene er forente mot deg. Siden du starter uten en borg mÃ¥ " "du skynde deg med Ã¥ ta over én før slutten av uka. Overta alle fiendtlige " "borger for Ã¥ vinne." msgid "" "Find the Crown before Roland's heroes find it. Archibald will need the Crown " "for the final battle against Roland." msgstr "" "Finn kronen før Roland sine helter finner den. Archibald kommer til Ã¥ trenge " "kronen i det endelige slaget mot Roland." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Roland to win the war, and be sure " "not to lose Archibald in the fight!" msgstr "" "Dette er det endelige slaget. BÃ¥de du og din fiende er væpnet til tennene, " "og alle er allierte mot deg. Fang Roland for Ã¥ vinne krigen, og pass pÃ¥ Ã¥ " "ikke miste Archibald i kamp!" msgid "Arrow's Flight" msgstr "I pilens skuddlinje" msgid "Island of Chaos" msgstr "Kaosøya" msgid "The Abyss" msgstr "Avgrunnen" msgid "Uprising" msgstr "Opprør" msgid "Aurora Borealis" msgstr "Nordlys" msgid "Betrayal's End" msgstr "Forræderiets ende" msgid "Corruption's Heart" msgstr "Fordervelsens hjerte" msgid "The Giant's Pass" msgstr "Fjellovergangen" msgid "" "Subdue the unruly local lords in order to provide the Empire with facilities " "to operate in this region." msgstr "" "Underlegg deg de motvillige lokalhøvdingene slik at Keiserriket fÃ¥r et " "fotfeste i regionen." msgid "" "Eliminate all opposition in this area. Then the first piece of the artifact " "will be yours." msgstr "" "SlÃ¥ ned pÃ¥ all motstanden i dette omrÃ¥det. NÃ¥r du har klart det vil den " "første biten av skatten du søker bli din." msgid "" "The sorceresses to the northeast are rebelling! For the good of the empire " "you must quash their feeble uprising on your way to the mountains." msgstr "" "Trollkvinnene i nordøst gjør opprør!\n" "For Keiserrikets sak mÃ¥ du knuse deres stusslige opprør pÃ¥ ferden din mot " "fjellene." msgid "" "Having prepared for your arrival, Kraeger has arranged for a force of " "necromancers to thwart your quest. You must capture the castle of Scabsdale " "before the first day of the third week, or the Necromancers will be too " "strong for you." msgstr "" "For Ã¥ forberede seg pÃ¥ deg har Kreiger gjort klar en styrke med " "sortekunstnere hvis mÃ¥l er Ã¥ sette en stopper for oppdraget ditt Du mÃ¥ " "overta borgen kalt Scabsdale før den første dagen av den tredje uka, ellers " "vil sortekunstnerne bli for sterke for deg." msgid "" "The barbarian despot in this area is, as yet, ignorant of your presence. " "Quickly, build up your forces before you are discovered and attacked! Secure " "the region by subduing all enemy forces." msgstr "" "Det barbariske tyranniet i omrÃ¥det er, enn sÃ¥ lenge, uvitende om din " "tilstedeværelse. Bygg opp styrkene dine raskt før du blir oppdaget og " "angrepet! Sikre omrÃ¥det ved Ã¥ slÃ¥ ut alle fiendtlige styrker." msgid "" "The Empire is weak in this region. You will be unable to completely subdue " "all forces in this area, so take what you can before reprisal strikes. " "Remember, your true goal is to claim the Helmet of Anduran." msgstr "" "Keiserriket stÃ¥r svakt i denne regionen, og derfor vil det være umulig Ã¥ " "underlegge seg alle motstanderne. Du bør heller samle det du rekker før de " "gÃ¥r til angrep pÃ¥ deg. Husk at mÃ¥let ditt er Ã¥ fÃ¥ tak i Andurans hjelm." msgid "For the good of the Empire, eliminate Kraeger." msgstr "Du mÃ¥ utrydde Kreiger, for Keiserrikets beste." msgid "" "At last, you have the opportunity and the facilities to rid the Empire of " "the necromancer's evil. Eradicate them completely, and you will be sung as a " "hero for all time." msgstr "" "Endelig har du muligheten og evnene til Ã¥ befri Keiserriket fra " "sortekunstnerens ondskap. Tilintetgjør dem fullstendig, og du vil bli sunget " "om som en helt for evig tid." msgid "Border Towns" msgstr "Grensebyene" msgid "Conquer and Unify" msgstr "Erobre og foren" msgid "Crazy Uncle Ivan" msgstr "Den gale onkel Ivan" msgid "The Wayward Son" msgstr "Den villfarne sønnen" msgid "Ivory Gates" msgstr "Elfenbensportene" msgid "The Elven Lands" msgstr "Alvelandene" msgid "The Epic Battle" msgstr "Det store slaget" msgid "The Southern War" msgstr "Krigen i sør" msgid "" "Conquer and unite all the enemy tribes. Don't lose the hero Jarkonas, the " "forefather of all descendants." msgstr "" "Erobre og foren alle de fiendtlige stammene. Ikke mist helten ved navn " "Jarkonas, alle etterkommeres forfeder." msgid "" "Your rival, the Kingdom of Harondale, is attacking weak towns on your " "border! Recover from their first strike and crush them completely!" msgstr "" "Motstanderen din, kongedømmet av Harondale, driver og angriper svake byer pÃ¥ " "grensa di! Reis hæren din pÃ¥ nytt etter det første angrepet deres, og sÃ¥ " "knus dem fullstendig!" msgid "" "Find your wayward son Joseph who is rumored to be living in the desolate " "lands. Do it before the first day of the third month or it will be of no " "help to your family." msgstr "" "Finn din villfarne sønn, Josef, som ryktes Ã¥ bo pÃ¥ de øde øyene. Gjør det " "før den første dagen av den tredje mÃ¥neden, ellers vil alt være til ingen " "nytte for familien din." msgid "" "Rescue your crazy uncle Ivan. Find him before the first day of the fourth " "month or it will be no help to your kingdom." msgstr "" "Redd din gale onkel Ivan. Finn han før den første dagen av den fjerde " "mÃ¥neden, ellers vil ikke kongedømmet ditt ha noe Ã¥ tjene pÃ¥ det." msgid "" "Destroy the barbarians who are attacking the southern border of your " "kingdom! Recover your fallen towns, and then invade the jungle kingdom. " "Leave no enemy standing." msgstr "" "Utslett barbarene som angriper kongedømmet ditt sin sørlige grense! Ta " "tilbake dine tapte byer, og rykk inn i kongeriket som befinner seg i " "jungelen. Ikke etterlat deg noen overlevende." msgid "Retake the castle of Ivory Gates, which has fallen due to treachery." msgstr "Gjenerobre borgen kalt Ivory Gates, som har falt grunnet forræderi ." msgid "" "Gain the favor of the elves. They will not allow trees to be chopped down, " "so they will send you wood every 2 weeks. You must complete your mission " "before the first day of the seventh month, or the kingdom will surely fall." msgstr "" "Kom pÃ¥ godfot med alvene. De tillater ikke nedhogging av trær, sÃ¥ derfor " "sender de deg ved annenhver uke. Du mÃ¥ fullføre oppdraget ditt før den " "første dagen av den sjuende mÃ¥neden, ellers vil kongedømmet ditt falle." msgid "" "This is the final battle against your rival kingdom of Harondale. Eliminate " "everyone, and don't lose the hero Jarkonas VI." msgstr "" "Dette er det siste slaget mot motstanderen din, kongeriket av Harondale. " "Utrydd alle, og ikke mist helten kalt Jarkonas VI." msgid "Fount of Wizardry" msgstr "Trolldomskilden" msgid "Power's End" msgstr "Maktens ende" msgid "The Eternal Scrolls" msgstr "Evighetens skriftruller" msgid "The Shrouded Isles" msgstr "De skyggelagte øyene" msgid "" "Your mission is to vanquish the warring mages in the magical Shrouded Isles. " "The completion of this task will give you a fighting chance against your " "rivals." msgstr "" "Ditt oppdrag er Ã¥ utslette de krigende magikerne pÃ¥ De magiske skyggelagte " "øyene. Hvis du klarer denne oppgaven, vil du ha sjanse mot motstanderne dine." msgid "" "The location of the great library has been discovered! You must make your " "way to it, and reclaim the city of Chronos in which it lies." msgstr "" "Beliggenheten til det store biblioteket har blitt funnet! Du mÃ¥ bane deg vei " "til det og gjenvinn byen av Chronos der det ligger." #, fuzzy msgid "" "Find the Orb of negation, which is said to be buried in this land. There are " "clues inscribed on stone obelisks which will help lead you to your prize. " "Find the Orb before the first day of the sixth month, or your rivals will " "surely have gotten to the fount before you." msgstr "" "Finn Kulen av benektelse som ryktes Ã¥ være gravd ned et sted i denne " "regionen. Det fins ledetrÃ¥der til denne skatten i form av innriss pÃ¥ " "obelisker. Finn kulen før den første dagen av den sjette mÃ¥neden, ellers vil " "motstanderne dine fÃ¥ klørne i den før deg." msgid "" "You must take control of the castle of Magic, where the fount of wizardry " "lies. Do this and your victory will be supreme." msgstr "" "Du mÃ¥ ta kontrollen over Magislottet som er stedet hvor trolldomskilden " "ligger. Gjør dette, og seieren vil bli den største du noen sinne har oppnÃ¥dd." msgid "Blood is Thicker" msgstr "Blod er tykkere" msgid "King and Country" msgstr "Konge og velde" msgid "Pirate Isles" msgstr "Piratøyene" msgid "Stranded" msgstr "Strandet" msgid "" "Capture the town on the island off the southeast shore in order to construct " "a boat and travel back towards the mainland. Do not lose the hero Gallavant." msgstr "" "Overta byen pÃ¥ øya utenfor kysten i sørøst for Ã¥ kunne lage et skip og reise " "tilbake til fastlandet. Ikke mist helten kalt Gallavant." msgid "" "Find and defeat Martine, the pirate leader, who resides in Pirates Cove. Do " "not lose Gallavant or your quest will be over." msgstr "" "Finn og beseir Martine, lederen av piratene. Hun oppholder seg i Piratbukta. " "Ikke mist Gallavant, ellers vil oppdraget ditt mislykkes." msgid "" "Eliminate all the other forces who oppose the rule of Lord Alberon. " "Gallavant must not die." msgstr "" "Utslett alle styrker som motsier seg veldet til baron Alberon. Gallavant mÃ¥ " "under ingen omstendigheter dø." msgid "" "Overthrow the entrenched monarchy of Lord Alberon, and claim all the land in " "your name. Gallavant must not die." msgstr "" "Styrt det dypt rotfestede kongedømmet til baron Alberon, og legg krav pÃ¥ " "hele landet i ditt navn. Gallavant mÃ¥ ikke dø." msgid " bane" msgstr "morderen" msgid " alliance" msgstr "allianse" # Medbrakte? Videreførbare? msgid "Carry-over forces" msgstr "Medbrakte styrker" # Ville ha vært bedre med "Ekstra ved" msgid " bonus" msgstr "bonus" msgid " defeated" msgstr " fanget" msgid " will always run away from your army." msgstr " kommer alltid til Ã¥ rømme i frykt fra hæren din." msgid " will be willing to join your army." msgstr " kommer alltid til Ã¥ ønske Ã¥ bli med i hæren din." msgid "\"%{artifact}\" artifact will be carried over the scenario." msgstr "\"%{artifact}\" vil bli brakt med videre til det neste kartet." msgid "The army will be carried over the scenario." msgstr "Disse styrkene vil komme til deg ved et senere kart." msgid "The kingdom will have +%{count} %{resource} each day." msgstr "" "Kongedømmet ditt vil fÃ¥ %{count} ekstra %{resource} ved oppstarten av kartet." msgid "\"%{spell}\" spell will be carried over the scenario." msgstr "Trolldommen %{spell} vil bli brakt med videre til det neste kartet." msgid "%{hero} can be hired in the scenario." msgstr "Helten %{hero} kan bli vervet pÃ¥ kartet." msgid "" "%{hero} has been defeated and will not appear in the subsequent scenarios." msgstr "" "Helten %{hero} vil ikke komme i senere kart dersom han/hun har blitt fanget." msgid "The dwarves recognize their allies and gladly join your forces." msgstr "" "Dvergene kjenner igjen sine allierte og slÃ¥r seg med glede sammen med " "styrkene dine." msgid "The ogres recognize you as the Dwarfbane and lumber over to join you." msgstr "" "Jettene gjenkjenner deg som Dvergebanen og tumler seg tungt bort til deg for " "Ã¥ slÃ¥ seg til din armé." msgid "" "The dragons, snarling and growling, agree to join forces with you, their " "'Ally'." msgstr "" "Dragene, mens de ser pÃ¥ deg og knurrer og snerrer, godtar Ã¥ slÃ¥ seg sammen " "med deg, deres \"allierte\"." #, fuzzy msgid "" "As you approach the group of elves, their leader calls them all to " "attention. He shouts to them, \"Who of you is brave enough to join this " "fearless ally of ours?\" The group explodes with cheers as they run to join " "your ranks." msgstr "" "Idet du gÃ¥r mot en gruppe alver kommanderer lederen dem alle til Ã¥ stÃ¥ i " "givakt. Han roper til dem, \"Hvem av dere er tapre nok til Ã¥ slÃ¥ dere sammen " "med vÃ¥r fryktløse allierte?\". Gruppen bryter ut i jubel mens alle løper " "bort for Ã¥ slutte seg til styrken din." #, fuzzy msgid "" "The dwarves hail you, \"Any friend of Roland is a friend of ours. You may " "pass.\"" msgstr "" "Dvergene hilser deg, \"En venn av Roland er en venn av oss. Du kan fÃ¥ gÃ¥ " "forbi.\"." msgid "" "The ogres give you a grunt of recognition, \"Archibald's allies may pass.\"" msgstr "" "Jettene gir fra seg et grynt av annerkjennelse, \"Archibald sine allierte " "skal fÃ¥ slippe forbi.\"." #, fuzzy msgid "" "The dragons see you and call out. \"Our alliance with Archibald compels us " "to join you. Unfortunately you have no room. A pity!\" They quickly scatter." msgstr "" "Dragene ser deg og roper ut: \"VÃ¥r allianse med Archibald tvinger oss til Ã¥ " "bli med deg. Uheldigvis har du ikke plass. Det var synd!\" De drar raskt av " "gÃ¥rde." #, fuzzy msgid "" "The elves stand at attention as you approach. Their leader calls to you and " "says, \"Let us not impede your progress, ally! Move on, and may victory be " "yours.\"" msgstr "" "Alvene stÃ¥r i givakt idet du kommer nærmere. Lederen deres roper pÃ¥ deg og " "sier: \"La oss ikke komme i veien for deg, vÃ¥r allierte! Dra nÃ¥ videre, og " "mÃ¥tte seieren bli din.\"." msgid "\"The Dwarfbane!!!!, run for your lives.\"" msgstr "\"Dvergebanen!!!! Flykt for livene deres!\"" msgid "campaignBonus|Animate Dead" msgstr "Dødebesjelning" msgid "campaignBonus|Chain Lightning" msgstr "Kjedelyn" msgid "campaignBonus|Fireblast" msgstr "Ildblest" msgid "campaignBonus|Mass Curse" msgstr "Masseforbannelse" msgid "campaignBonus|Mass Haste" msgstr "Full hast" msgid "campaignBonus|Mirror Image" msgstr "Speilbilde" msgid "campaignBonus|Resurrect" msgstr "Gjenreising" msgid "campaignBonus|Steelskin" msgstr "StÃ¥lhud" msgid "campaignBonus|Summon Earth" msgstr "Tilkall jordÃ¥nd" msgid "campaignBonus|View Heroes" msgstr "Vis helter" msgid "campaignBonus|Ballista" msgstr "Hurtigballisten" msgid "campaignBonus|Black Pearl" msgstr "Den sorte perlen" msgid "campaignBonus|Caster's Bracelet" msgstr "FortryllerarmbÃ¥nd" msgid "campaignBonus|Defender Helm" msgstr "Forsvarerhjelmen" msgid "campaignBonus|Breastplate" msgstr "Brystplate" msgid "campaignBonus|Dragon Sword" msgstr "Dragesverdet" msgid "campaignBonus|Fizbin Medal" msgstr "Fizbinmedaljen" msgid "campaignBonus|Foremost Scroll" msgstr "Yprst. kunskpsrull" msgid "campaignBonus|Gauntlets" msgstr "Panserhansker" msgid "campaignBonus|Hideous Mask" msgstr "Avskyelig maske" msgid "campaignBonus|Mage's Ring" msgstr "Magikerens ring" msgid "campaignBonus|Major Scroll" msgstr "Str. kunskpsrull" msgid "campaignBonus|Medal of Honor" msgstr "Æresmedaljen" msgid "campaignBonus|Medal of Valor" msgstr "Heltemotmedaljen" msgid "campaignBonus|Minor Scroll" msgstr "Mindre skriftrull" msgid "campaignBonus|Nomad Boots" msgstr "Nomadens sko" msgid "campaignBonus|Power Axe" msgstr "Styrkeøksa" msgid "campaignBonus|Spiked Shield" msgstr "Piggskjoldet" msgid "campaignBonus|Stealth Shield" msgstr "Snikeskjoldet" msgid "campaignBonus|Tax Lien" msgstr "Skattepant" msgid "campaignBonus|Thunder Mace" msgstr "Tordenklubben" msgid "campaignBonus|Traveler's Boots" msgstr "Vandrerens sko" msgid "campaignBonus|White Pearl" msgstr "Den hvite perlen" msgid "campaignBonus|Basic Archery" msgstr "Enkel skyting" msgid "campaignBonus|Advanced Archery" msgstr "Forbedret skyting" msgid "campaignBonus|Expert Archery" msgstr "Ekspert skyting" msgid "campaignBonus|Basic Ballistics" msgstr "Enkel ballistikk" msgid "campaignBonus|Advanced Ballistics" msgstr "Forbedret ballistikk" msgid "campaignBonus|Expert Ballistics" msgstr "Ekspert ballistikk" msgid "campaignBonus|Basic Diplomacy" msgstr "Enkel megling" msgid "campaignBonus|Advanced Diplomacy" msgstr "Forbedret megling" msgid "campaignBonus|Expert Diplomacy" msgstr "Ekspert megling" msgid "campaignBonus|Basic Eagle Eye" msgstr "Enkelt ørneblikk" msgid "campaignBonus|Advanced Eagle Eye" msgstr "Forbedret ørneblikk" msgid "campaignBonus|Expert Eagle Eye" msgstr "Ekspert ørneblikk" msgid "campaignBonus|Basic Estates" msgstr "Enkel investering" msgid "campaignBonus|Advanced Estates" msgstr "Forbedret investering" msgid "campaignBonus|Expert Estates" msgstr "Ekspert investering" msgid "campaignBonus|Basic Leadership" msgstr "Enkelt lederskap" msgid "campaignBonus|Advanced Leadership" msgstr "Forbedret lederskap" msgid "campaignBonus|Expert Leadership" msgstr "Ekspert lederskap" msgid "campaignBonus|Basic Logistics" msgstr "Enkel logistikk" msgid "campaignBonus|Advanced Logistics" msgstr "Forbedret logistikk" msgid "campaignBonus|Expert Logistics" msgstr "Ekspert logistikk" msgid "campaignBonus|Basic Luck" msgstr "Enkel flaks" msgid "campaignBonus|Advanced Luck" msgstr "Forbedret flaks" msgid "campaignBonus|Expert Luck" msgstr "Ekspert flaks" msgid "campaignBonus|Basic Mysticism" msgstr "Enkel mystikk" msgid "campaignBonus|Advanced Mysticism" msgstr "Forbedret mystikk" msgid "campaignBonus|Expert Mysticism" msgstr "Ekspert mystikk" msgid "campaignBonus|Basic Navigation" msgstr "Enkel skipsfart" msgid "campaignBonus|Advanced Navigation" msgstr "Forbedret skipsfart" msgid "campaignBonus|Expert Navigation" msgstr "Ekspert skipsfart" msgid "campaignBonus|Basic Necromancy" msgstr "Enkel sortekunst" msgid "campaignBonus|Advanced Necromancy" msgstr "Forbedret sortekunst" msgid "campaignBonus|Expert Necromancy" msgstr "Ekspert sortekunst" msgid "campaignBonus|Basic Pathfinding" msgstr "Enkel kartlesing" msgid "campaignBonus|Advanced Pathfinding" msgstr "Forbedret kartlesing" msgid "campaignBonus|Expert Pathfinding" msgstr "Ekspert kartlesing" msgid "campaignBonus|Basic Scouting" msgstr "Enkel speiding" msgid "campaignBonus|Advanced Scouting" msgstr "Forbedret speiding" msgid "campaignBonus|Expert Scouting" msgstr "Ekspert speiding" msgid "campaignBonus|Basic Wisdom" msgstr "Enkel visdom" msgid "campaignBonus|Advanced Wisdom" msgstr "Forbedret visdom" msgid "campaignBonus|Expert Wisdom" msgstr "Ekspert visdom" msgid "" "The main hero will have \"%{artifact}\" artifact at the start of the " "scenario." msgstr "Hovedhelten din vil begynne kartet med %{artifact} i sin besittelse." msgid "" "The kingdom will receive %{amount} additional %{resource} at the start of " "the scenario." msgstr "Kongedømmet ditt vil begynne kartet med %{amount} ekstra %{resource}." msgid "" "The kingdom will have %{amount} less %{resource} at the start of the " "scenario." msgstr "" "Kongedømmet ditt vil fÃ¥ %{amount} mindre %{resource} ved oppstarten av " "kartet." msgid "" "The main hero will have %{count} %{monster} at the start of the scenario." msgstr "Hovedhelten din vil begynne kartet med %{count} %{monster}." msgid "" "The main hero will have \"%{spell}\" spell at the start of the scenario." msgstr "" "Hovedhelten din vil begynne kartet med trolldommen\n" "%{spell}." msgid "The starting race of the scenario will be %{race}." msgstr "Begynn kartet med helteslaget %{race}." msgid "" "The main hero will have additional %{count} %{skill} at the start of the " "scenario." msgstr "" "Hovedhelten din vil begynne kartet med %{count} ekstrapoeng i %{skill}." msgid "The main hero will have %{skill} at the start of the scenario." msgstr "Hovedhelten din vil begynne kartet med ferdigheten %{skill}." msgid "Roland" msgstr "Roland" msgid "Archibald" msgstr "Archibald" #, fuzzy msgid "Price of Loyalty" msgstr "Prisen for trofasthet" msgid "Voyage Home" msgstr "Ferden hjem" msgid "Wizard's Isle" msgstr "Magimestrenes øy" msgid "Descendants" msgstr "Etterkommerene" msgid "The %{building} produces %{monster}." msgstr "%{building} gjør det mulig Ã¥ verve %{monster}." msgid "Requires:" msgstr "Trenger:" msgid "Exit this menu without doing anything." msgstr "Forlat menyen uten Ã¥ gjøre endringer." msgid "Cannot build. You have already built here today." msgstr "Kan ikke bygges. Det har allerede blitt bygd her denne runden." msgid "For this action it is necessary to build a castle first." msgstr "For Ã¥ utføre denne handlingen mÃ¥ man bygge en borg først." #, fuzzy msgid "Cannot build %{name} because the castle is too far away from an ocean." msgstr "Borgen er for langt unna vann til Ã¥ bygge %{name}." # bygning? usikker funksjon msgid "disable build." msgstr "Bygning fjernet." msgid "Cannot afford %{name}." msgstr "Du har ikke rÃ¥d til %{name}." msgid "%{name} is already built." msgstr "%{name} har allerede blitt bygd." msgid "Cannot build %{name}." msgstr "Det kan ikke bygges %{name}." msgid "Build %{name}." msgstr "Bygg %{name}." msgid "Blackridge" msgstr "Svartfjellryggen" msgid "Hillstone" msgstr "Vollsten" msgid "Pinehurst" msgstr "Furuskogen" msgid "Whiteshield" msgstr "Hvitskjold" msgid "Woodhaven" msgstr "Skogheimen" msgid "Blackwind" msgstr "Sortevind" msgid "Bloodreign" msgstr "Bloddømme" msgid "Dragontooth" msgstr "Dragetann" msgid "Greywind" msgstr "" msgid "Portsmith" msgstr "" msgid "Atlantium" msgstr "" msgid "Middle Gate" msgstr "" msgid "Sansobar" msgstr "" msgid "Tundara" msgstr "Tundara" msgid "Vulcania" msgstr "Vulcania" msgid "Baywatch" msgstr "" #, fuzzy msgid "Fountainhead" msgstr "Fontene" msgid "Vertigo" msgstr "" msgid "Wildabar" msgstr "" msgid "Winterkill" msgstr "" msgid "Brindamoor" msgstr "" msgid "Lakeside" msgstr "" msgid "Nightshadow" msgstr "" msgid "Olympus" msgstr "" msgid "Sandcaster" msgstr "Sandeborg" msgid "Alamar" msgstr "Alamar" #, fuzzy msgid "Burlock" msgstr "Trollmann" msgid "Dragadune" msgstr "Drakendyn" msgid "Kalindra" msgstr "Kalindra" msgid "Xabran" msgstr "" msgid "Algary" msgstr "" msgid "Basenji" msgstr "" msgid "Blackfang" msgstr "Svarttann" msgid "New Dawn" msgstr "Nygry" msgid "Sorpigal" msgstr "" msgid "Avone" msgstr "" msgid "Big Oak" msgstr "" msgid "Chandler" msgstr "" msgid "Erliquin" msgstr "" msgid "Hampshire" msgstr "" #, fuzzy msgid "Antioch" msgstr "Advarsel" #, fuzzy msgid "Avalon" msgstr "Halon" msgid "Roc Haven" msgstr "" msgid "South Mill" msgstr "" #, fuzzy msgid "Weed Patch" msgstr "Gulluret" msgid "Brownston" msgstr "Brunnbygd" msgid "Hilltop" msgstr "Bakketopp" msgid "Weddington" msgstr "" msgid "Westfork" msgstr "" #, fuzzy msgid "Whittingham" msgstr "Skribent" msgid "Cathcart" msgstr "" msgid "Elk's Head" msgstr "" msgid "Roscomon" msgstr "" #, fuzzy msgid "Sherman" msgstr "Tysk" msgid "Yorksford" msgstr "" #, fuzzy msgid "Blackburn" msgstr "Svartdrage" #, fuzzy msgid "Blacksford" msgstr "Svart tÃ¥rn" msgid "Burton" msgstr "" msgid "Pig's Eye" msgstr "" msgid "Viper's Nest" msgstr "" msgid "Fenton" msgstr "" msgid "Lankershire" msgstr "Slankersyssel" msgid "Lombard" msgstr "" msgid "Timberhill" msgstr "Tømmertopp" #, fuzzy msgid "Troy" msgstr "Trojann" #, fuzzy msgid "Forder Oaks" msgstr "Grensebyene" msgid "Meramec" msgstr "" msgid "Quick Silver" msgstr "" msgid "Westmoor" msgstr "" msgid "Willow" msgstr "Vierlund" msgid "Corackston" msgstr "" msgid "Sheltemburg" msgstr "" msgid "Cannot recruit - you already have a Hero in this town." msgstr "Det kan ikke verves en helt. Det befinner seg alt én i byen." msgid "Cannot recruit - you have too many Heroes." msgstr "Kan ikke verves - du har for mange helter." msgid "Cannot afford a Hero" msgstr "Ikke rÃ¥d til helt" msgid "There is no room in the garrison for this army." msgstr "Det er ikke plass i garnisonen for denne styrken." # Forskansing eller festningsanlegg msgid "Fortifications" msgstr "Befestninger" msgid "Farm" msgstr "Ã…ker" msgid "Thatched Hut" msgstr "StrÃ¥tekt hytte" msgid "Archery Range" msgstr "Skytebane" msgid "Upg. Archery Range" msgstr "Oppg. skytebane" msgid "Blacksmith" msgstr "Smed" msgid "Upg. Blacksmith" msgstr "Oppg. smed" msgid "Armory" msgstr "Rustkammer" msgid "Upg. Armory" msgstr "Oppg. rustkammer" msgid "Jousting Arena" msgstr "Ridderbane" msgid "Upg. Jousting Arena" msgstr "Oppg. ridderbane" msgid "Cathedral" msgstr "Domkirke" msgid "Upg. Cathedral" msgstr "Oppg. domkirke" msgid "Coliseum" msgstr "Amfiteater" msgid "Garbage Heap" msgstr "Skrothaug" msgid "Hut" msgstr "Hytte" msgid "Stick Hut" msgstr "Kvisthytte" msgid "Upg. Stick Hut" msgstr "Oppg. kvisthytte" msgid "Den" msgstr "Hi" msgid "Adobe" msgstr "Leirhus" msgid "Upg. Adobe" msgstr "Oppg. leirhus" msgid "Bridge" msgstr "Bru" msgid "Upg. Bridge" msgstr "Oppg. bru" msgid "Pyramid" msgstr "Pyramide" msgid "Rainbow" msgstr "Regnbue" msgid "Crystal Garden" msgstr "Krystallhage" msgid "Treehouse" msgstr "Trehytte" msgid "Cottage" msgstr "Stue" msgid "Upg. Cottage" msgstr "Oppg. stue" msgid "Stonehenge" msgstr "Steinring" msgid "Upg. Stonehenge" msgstr "Oppg. steinring" # hesteslette heller enn hesteeng. Enkelte sprÃ¥k bruker plen. msgid "Fenced Meadow" msgstr "Inngjerdet slette" msgid "sorceress|Red Tower" msgstr "IldtÃ¥rn" msgid "Dungeon" msgstr "Skattehvelv" msgid "Waterfall" msgstr "Foss" msgid "Cave" msgstr "Hule" msgid "Crypt" msgstr "Gravkammer" msgid "Nest" msgstr "Rede" msgid "Maze" msgstr "Labyrint" msgid "Upg. Maze" msgstr "Oppg. labyrint" msgid "Swamp" msgstr "Myr" msgid "Green Tower" msgstr "Grønt tÃ¥rn" msgid "warlock|Red Tower" msgstr "Rødt tÃ¥rn" msgid "Black Tower" msgstr "Svart tÃ¥rn" msgid "Library" msgstr "Bibliotek" msgid "Orchard" msgstr "Frukthage" msgid "Habitat" msgstr "Hjemsted" msgid "Pen" msgstr "Binge" msgid "Foundry" msgstr "Støperi" msgid "Upg. Foundry" msgstr "Oppg. støperi" msgid "Cliff Nest" msgstr "Klipperede" msgid "Ivory Tower" msgstr "ElfenbenstÃ¥rn" msgid "Upg. Ivory Tower" msgstr "Oppg. elfenbenstÃ¥rn" msgid "Cloud Castle" msgstr "Himmelslott" msgid "Upg. Cloud Castle" msgstr "Oppg. himmelslott" msgid "Storm" msgstr "Storm" msgid "Skull Pile" msgstr "Beinhaug" msgid "Excavation" msgstr "Utgravninger" msgid "Graveyard" msgstr "Gravplass" msgid "Upg. Graveyard" msgstr "Oppg. gravplass" msgid "Upg. Pyramid" msgstr "Oppg. pyramide" # Evt herskapshus msgid "Mansion" msgstr "HerregÃ¥rd" msgid "Upg. Mansion" msgstr "Oppg. herregÃ¥rd" msgid "Mausoleum" msgstr "Kongegrav" msgid "Upg. Mausoleum" msgstr "Oppg. kongegrav" msgid "Laboratory" msgstr "Laboratorium" # Sortekunstnerens by. Offerplass? Offeralter? Tempel? Helligdom? msgid "Shrine" msgstr "Mørkets alter" msgid "" "The Fortifications increase the toughness of the walls, increasing the " "number of turns it takes to knock them down." msgstr "" "Befestningene styrker ringmuren til borgen slik at det mÃ¥ flere treff til " "for Ã¥ rive den ned." msgid "The Farm increases production of Peasants by %{count} per week." msgstr "Ã…keren øker veksten av bønder med %{count} i uka." msgid "" "The Coliseum provides inspiring spectacles to defending troops, raising " "their morale by two during combat." msgstr "" "Amfiteateret oppildner de forsvarende styrkene slik at kampÃ¥nden deres " "stiger med to." msgid "The Garbage Heap increases production of Goblins by %{count} per week." msgstr "Skrothaugen øker veksten av vetter med %{count} i uka." msgid "The Rainbow increases the luck of the defending units by two." msgstr "Regnbuen øker flaksen til de forsvarende styrkene med to." msgid "" "The Crystal Garden increases production of Sprites by %{count} per week." msgstr "Krystallhagen øker veksten av feer med %{count} i uka." msgid "The Dungeon increases the income of the town by %{count} gold per day." msgstr "" "Skattehvelvet øker den daglige inntekten til byen med %{count} gullstykker." msgid "The Waterfall increases production of Centaurs by %{count} per week." msgstr "Fossen øker veksten av kentaurer med %{count} i uka." msgid "" "The Library increases the number of spells in the Guild by one for each " "level of the guild." msgstr "" "Biblioteket øker antall trolldommer hos Magikerlauget med én for hver level." msgid "The Orchard increases production of Halflings by %{count} per week." msgstr "Frukthagen øker veksten av halvinger med %{count} i uka." msgid "The Storm adds +2 to the power of spells of a defending spell caster." msgstr "Stormen legger til +2 pÃ¥ kraften til en forsvarende trolldomsbruker." msgid "The Skull Pile increases production of Skeletons by %{count} per week." msgstr "Beinhaugen øker veksten av skjeletter med %{count} i uka." msgid "Thieves' Guild" msgstr "Tyvenes laug" msgid "Tavern" msgstr "Vertshus" msgid "Shipyard" msgstr "Skipsverft" msgid "Well" msgstr "Brønn" msgid "Statue" msgstr "Statue" msgid "Marketplace" msgstr "Markedsplass" msgid "Moat" msgstr "Vollgrav" msgid "Castle" msgstr "Borg" msgid "Tent" msgstr "Telt" msgid "Captain's Quarters" msgstr "Kapteinens kvarter" # Første nivÃ¥ uten forkortelse ser bÃ¥de bedre ut og gjør at man vet hva forkortelsen er for senere uten Ã¥ mÃ¥tte # høyre klikke osv. msgid "Mage Guild, Level 1" msgstr "Magikerlaug" # nivÃ¥ eller level? etasje? msgid "Mage Guild, Level 2" msgstr "Magikerlaug, level 2" msgid "Mage Guild, Level 3" msgstr "Magikerlaug, level 3" msgid "Mage Guild, Level 4" msgstr "Magikerlaug, level 4" msgid "Mage Guild, Level 5" msgstr "Magikerlaug, level 5" msgid "" "The Shrine increases the necromancy skill of all your necromancers by 10 " "percent." msgstr "" "Mørkets alter forbedrer sortekunstevnen til heltene dine med 10 prosent." msgid "" "The Thieves' Guild provides information on enemy players. Thieves' Guilds " "can also provide scouting information on enemy towns. Additional Guilds " "provide more information." msgstr "" "Tyvenes laug skaffer informasjon om motstanderspillerne og borgene deres. Jo " "flere tyvelaug du eier, desto mer informasjon fÃ¥r du om de andre spillerne." msgid "The Tavern increases morale for troops defending the castle." msgstr "Vertshuset styrker kampÃ¥nden til krigerne som forsvarer borgen." msgid "The Shipyard allows ships to be built." msgstr "Skipsverftet lar deg bygge skip." msgid "" "The Well increases the growth rate of all dwellings by %{count} creatures " "per week." msgstr "" "Brønnen øker den ukentlige veksten til alle borgens bosteder med %{count} " "krigere. Hvis du besøker den kan du fÃ¥ en oversikt over alle de forskjellige " "krigerne i borgen i tillegg til at du kan verve dem derfra." #, fuzzy msgid "The Statue increases the town's income by %{count} gold per day." msgstr "Statuen øker den daglige inntekten til byen med %{count} gullstykker." msgid "The Left Turret provides extra firepower during castle combat." msgstr "" "Det venstre tÃ¥rnet tilfører ekstra ildstyrke under beleiring av borgen." msgid "The Right Turret provides extra firepower during castle combat." msgstr "Det høyre tÃ¥rnet tilfører ekstra ildstyrke under beleiring av borgen." msgid "" "The Marketplace can be used to convert one type of resource into another. " "The more marketplaces you control, the better the exchange rate." msgstr "" "PÃ¥ markedsplassen kan man bytte én type rÃ¥vare mot en annen. Jo flere " "markedsplasser du eier, desto bedre vekslingskurs fÃ¥r du." msgid "" "The Moat slows attacking units. Any unit entering the moat must end its turn " "there and becomes more vulnerable to attack." msgstr "" "Vollgraven forsinker angripende styrker. Alle styrker som havner i " "vollgraven mÃ¥ avslutte turen sin der og i tillegg tar de mer skade fra " "angrep sÃ¥ lenge de befinner seg i den." #, fuzzy msgid "" "The Castle improves the town's defense and increases its income to %{count} " "gold per day." msgstr "" "Borgen forsterker byens beskyttelse og øker den daglige inntekten du fÃ¥r fra " "byen til %{count} gullstykker." msgid "" "The Tent provides workers to build a castle, provided the materials and the " "gold are available." msgstr "" "Teltet skaffer arbeidere til Ã¥ bygge en borg, gitt at rÃ¥varene og gullet er " "pÃ¥ plass." msgid "" "The Captain's Quarters provides a captain to assist in the castle's defense " "when no hero is present." msgstr "" "Kapteinens kvarter huser en kaptein som vil forsvare borgen dersom ingen " "helt er til stede." msgid "" "The Mage Guild allows heroes to learn spells and replenish their spell " "points." msgstr "" "Hos Magikerlauget kan heltene dine lære seg trolldommer og fÃ¥ fylt opp " "magipoengene sine." msgid "Recruit %{name}" msgstr "Verv %{name}" msgid "Month: %{month}, Week: %{week}, Day: %{day}" msgstr "MÃ¥ned: %{month}, Uke : %{week}, Dag : %{day}" msgid "" "You must purchase a spell book to use the mage guild, but you currently have " "no room for a spell book. Try giving one of your artifacts to another hero." msgstr "" "Du mÃ¥ kjøpe en tryllebok for Ã¥ bruke Magikerforbundet, men du har ikke plass " "til en tryllebok nÃ¥. Prøv Ã¥ gi en av dine skatter til en annen helt." msgid "Click to show next town." msgstr "Klikk for Ã¥ vise neste by." msgid "Show next town" msgstr "Vis neste by" msgid "Click to show previous town." msgstr "Klikk for Ã¥ vise forrige by." msgid "Show previous town" msgstr "Vis forrige by" msgid "This town may not be upgraded to a castle." msgstr "Denne byen kan ikke oppgraderes til borg." msgid "Town" msgstr "By" msgid "Exit Castle" msgstr "Forlat borgen" msgid "Exit Town" msgstr "Forlat byen" msgid "Show Income" msgstr "Vis inntekt" msgid "View Hero" msgstr "Vis helt" msgid "The above spells are available here." msgstr "Trolldommene ovenfor er tilgjengelige her." #, fuzzy msgid "The spells the hero can learn have been added to their book." msgstr "Med nok visdom kan du lære deg trolldommene over." msgid "A generous tip for the barkeep yields the following rumor:" msgstr "Et sjenerøst tips til bartenderen fÃ¥r han til Ã¥ dele dette ryktet:" msgid "Recruit Hero" msgstr "Verv en helt" msgid "%{name} is a level %{value} %{race} " msgstr "%{name} er en level %{value} %{race} " msgid "with %{count} artifacts." msgstr "med %{count} skatter." msgid "with 1 artifact." msgstr "med 1 skatt." msgid "without artifacts." msgstr "uten skatter." msgid "Recruit %{name} the %{race}" msgstr "Verv %{race}en %{name}" #, fuzzy msgid "" "'Spread' combat formation spreads the castle's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "\"Spredd\" kampoppstilling sprer styrkene dine fra topp til bunn av " "slagmarken med minst ett ledig felt mellom hver enkelt tropp." #, fuzzy msgid "" "'Grouped' combat formation bunches the castle's units together in the center " "of the castle's side of the battlefield." msgstr "" "\"Gruppert\" kampoppstilling setter styrkene dine tett sammen i midten av " "din side av slagmarken." msgid "Spread Formation" msgstr "Spredd oppstilling" msgid "Grouped Formation" msgstr "Gruppert oppstilling" msgid "Set garrison combat formation to 'Spread'" msgstr "Sett kampoppstillingen til garnisonen til \"Spredd\"." msgid "Set garrison combat formation to 'Grouped'" msgstr "Sett kampoppstillingen til garnisonen til \"Gruppert\"." msgid "Exit Castle Options" msgstr "Forlat borgvalg" msgid "Castle Options" msgstr "Borgvalg" msgid "Not enough resources to recruit creatures." msgstr "Ikke nok rÃ¥varer til Ã¥ verve krigere." msgid "You are unable to recruit at this time, your ranks are full." msgstr "Du kan ikke verve noen fordi hæren din har ikke plass." msgid "No creatures available for purchase." msgstr "Ingen krigere tilgjengelige for verving." msgid "Recruit Creatures" msgstr "Verv krigere" msgid "Max" msgstr "Maks" msgid "Hire all creatures in the town." msgstr "Verv alle krigerene som er tilgjengelige i byen." msgid "Available" msgstr "Tilgjengelig" msgid "Town Population Information and Statistics" msgstr "Info og tall om byens bosteder" msgid "Damage" msgstr "Skade" msgid "HP" msgstr "Liv" msgid "Growth" msgstr "Vekst" msgid "week" msgstr "uke" msgid "View World" msgstr "Vis verden" msgid "View the entire world." msgstr "Vis hele verdenen." msgid "Puzzle" msgstr "Puslekart" msgid "View the obelisk puzzle." msgstr "Vis obeliskenes puslekart." msgid "Scenario Information" msgstr "Kartbeskrivelse" msgid "View information on the scenario you are currently playing." msgstr "Vis beskrivelsen av kartet du spiller akkurat nÃ¥." msgid "Dig for the Ultimate Artifact." msgstr "Grav etter den sjeldneste skatten." msgid "Digging" msgstr "Graving" msgid "Arena" msgstr "Arena" #, fuzzy msgid "" "You enter the arena and face a pack of vicious lions. You handily defeat " "them, to the wild cheers of the crowd. Impressed by your skill, the aged " "trainer of gladiators agrees to train you in a skill of your choice." msgstr "" "Du gÃ¥r inn i arenaen og finner deg selv ansikt til ansikt med en flokk " "blodtørstige løver. Du tar lett hÃ¥nd om dem og publikum jubler. En gammel " "gladiatormester ble sÃ¥ imponert av deg at han gÃ¥r med pÃ¥ Ã¥ trene deg opp i " "én ferdighet etter ditt ønske." msgid "Attack Skill" msgstr "Angrep" msgid "Defense Skill" msgstr "Forsvar" msgid "Shots" msgstr "Skudd" msgid "Shots Left" msgstr "Skudd igjen" msgid "Hit Points" msgstr "Liv" msgid "Hit Points Left" msgstr "Liv igjen" msgid "You can't afford to upgrade your troops!" msgstr "Du har ikke rÃ¥d til Ã¥ oppgradere troppene dine!" msgid "" "Your troops can be upgraded, but it will cost you dearly. Do you wish to " "upgrade them?" msgstr "" "Krigerne dine kan oppgraderes, men det vil koste deg dyrt. Ønsker du Ã¥ " "oppgradere dem likevel?" # Avskjedige msgid "Are you sure you want to dismiss this army?" msgstr "Er du sikker pÃ¥ at du vil bortvise denne troppen?" msgid "Upgrade" msgstr "Oppgrader" msgid "Upgrade your troops." msgstr "Oppgrader troppene dine." msgid "Dismiss" msgstr "Bortvis" msgid "Dismiss this army." msgstr "Bortvis denne troppen." msgid "" "A group of %{monster} with a desire for greater glory wish to join you.\n" "Do you accept?" msgstr "" "En gruppe %{monster} med en drøm om heder og ære ønsker Ã¥ bli med deg.\n" "Godtar du?" msgid "Followers" msgstr "Følgere" msgid "" "The %{monster} is swayed by your diplomatic tongue, and offers to join your " "army for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "En enkelt %{monster} lar seg overtale av dine meglingsevner, og den tilbyr " "deg Ã¥ bli med i hæren din mot en pris pÃ¥ %{gold} gullstykker.\n" "Godtar du?" msgid "" "The creatures are swayed by your diplomatic\n" "tongue, and make you an offer:\n" "\n" msgstr "" "Krigerne lar seg overtale takket være dine formidable meglingsevner, og de " "gir deg følgende tilbud:\n" "\n" msgid "" "%{offer} of the %{total} %{monster} will join your army, and the rest will " "leave you alone, for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "%{offer} av %{total} av gruppen med %{monster} vil slutte seg til hæren din, " "mens resten vil la deg gÃ¥ uforstyrret videre i bytte mot %{gold} " "gullstykker.\n" "Godtar du?" msgid "" "All %{offer} of the %{monster} will join your army for the sum of %{gold} " "gold.\n" "Do you accept?" msgstr "" "Hele hærstyrken pÃ¥ %{offer} %{monster} vil slutte seg til hæren din i bytte " "mot %{gold} gullstykker.\n" "Godtar du?" msgid "(Rate: %{percent})" msgstr "(Grad: %{percent})" msgid "off" msgstr "av" msgid "Music" msgstr "Musikk" msgid "Effects" msgstr "Lydeffekter" msgid "MIDI" msgstr "MIDI" msgid "MIDI Expansion" msgstr "MIDI Expansion" msgid "External" msgstr "Ekstern" msgid "Music Type" msgstr "Musikktype" msgid "3D Audio" msgstr "3D-lyd" msgid "Toggle ambient music level." msgstr "Juster volumet til musikken." msgid "Toggle foreground sounds level." msgstr "Juster volumet til lydeffekter og videosnutter." msgid "Change the type of music." msgstr "Skift typen musikk." msgid "Toggle 3D effects of foreground sounds." msgstr "Skru 3D-effekten av bakgrunnslyder pÃ¥ eller av." msgid "Build a new ship:" msgstr "Bygg et nytt skip:" msgid "Resource cost:" msgstr "Pris i rÃ¥varer:" msgid "Total: " msgstr "Totalt: " msgid "Need: " msgstr "Trenger: " msgid "Load Game" msgstr "Last inn et parti" msgid "No save files to load." msgstr "Ingen lagrede spill Ã¥ laste inn." msgid "New Game" msgstr "Nytt parti" msgid "Start a single or multi-player game." msgstr "Start et enkelt- eller flerspillerparti." msgid "Load a previously saved game." msgstr "Last inn et tidligere lagret parti." msgid "Save Game" msgstr "Lagre spill" msgid "Save the current game." msgstr "Lagre det nÃ¥værende spillet." msgid "Quit" msgstr "Avslutt" msgid "Quit out of Heroes of Might and Magic II." msgstr "Forlat Heroes of Might and Magic II." msgid "Language" msgstr "SprÃ¥k" msgid "Graphics" msgstr "Bilde" msgid "Black & White" msgstr "Svart-hvit" msgid "Mouse Cursor" msgstr "Markør" msgid "Color" msgstr "Farget" msgid "Text Support" msgstr "Tekstmodus" msgid "Change the language of the game." msgstr "Endre sprÃ¥ket til spillet." msgid "Select Game Language" msgstr "Velg spillsprÃ¥k" msgid "Change the graphics settings of the game." msgstr "Endre bildeinnstillingene til spillet." msgid "Toggle colored cursor on or off. This is only an aesthetic choice." msgstr "Skru farget markør av eller pÃ¥. Dette er kun et estetisk valg." msgid "" "Toggle text support mode to output extra information about windows and " "events in the game." msgstr "" "Skru pÃ¥ tekstmodus for Ã¥ gi tekstbasert informasjon om vinduer og hendelser " "som skjer i spillet." msgid "" "Map\n" "Difficulty" msgstr "" "Kartets\n" "utfordring" msgid "" "Game\n" "Difficulty" msgstr "" "Spillets\n" "utfordring" msgid "Rating" msgstr "Utbytte" msgid "Map Size" msgstr "" "Kartets\n" "størrelse" # Evt Spillere # Motspillere og motstandere høres ut som dine egne motspillere og motstandere. Evt Konkurrenter men # det høres feil setting ut. Motspillere osv passer dÃ¥rlig nÃ¥r du har allierte blant dem. msgid "Opponents" msgstr "Deltakere" # Slag? Type? Klasse er feil. msgid "Class" msgstr "Helteslag" msgid "" "Victory\n" "Conditions" msgstr "" "Seiers-\n" "vilkÃ¥r" msgid "" "Loss\n" "Conditions" msgstr "" "Taps-\n" "vilkÃ¥r" msgid "First select recipients!" msgstr "Velg først mottakere!" msgid "You cannot select %{resource}!" msgstr "Du kan ikke velge %{resource}!" msgid "Select count %{resource}:" msgstr "Velg mengde %{resource}:" msgid "Select Recipients" msgstr "Velg mottakere" msgid "Your Funds" msgstr "Dine midler" msgid "Planned Gift" msgstr "Gis bort" msgid "Gift from %{name}" msgstr "Gave fra %{name}" msgid "Resolution" msgstr "Oppløsning" msgid "Fullscreen" msgstr "Fullskjerm" msgid "window|Mode" msgstr "Skjermmodus" msgid "Windowed" msgstr "Vindu" msgid "V-Sync" msgstr "V-synk" msgid "on" msgstr "pÃ¥" msgid "FPS" msgstr "FPS" msgid "System Info" msgstr "Systeminfo" msgid "Change the resolution of the game." msgstr "Endre oppløsningen til spillet." msgid "Select Game Resolution" msgstr "Velg spilloppløsning" msgid "Toggle between fullscreen and windowed modes." msgstr "Velg mellom fullskjerm og vindusskjerm." msgid "" "The V-Sync option can be enabled to resolve flickering issues on some " "monitors." msgstr "" "V-synk kan bli skrudd pÃ¥ for Ã¥ potensielt løse problemer med hakkete bilde " "eller ujevn bildeflyt." msgid "Show extra information such as FPS and current time." msgstr "Vis ekstra informasjon som bilder per sekund (FPS) og tid." msgid "Hotkey: " msgstr "" msgid "Category: " msgstr "" #, fuzzy msgid "Event: " msgstr "Hendelse" msgid "Hot Keys:" msgstr "Hurtigtaster:" msgid "Evil" msgstr "Ond" msgid "Good" msgstr "God" msgid "Interface Type" msgstr "Grensesnitt" msgid "Hide" msgstr "Løs" msgid "Show" msgstr "Bundet" msgid "Interface" msgstr "Grensesnitt" msgid "Slow" msgstr "Treg" msgid "Normal" msgstr "Middels" msgid "Fast" msgstr "Fort" msgid "Very Fast" msgstr "Rask" msgid "Scroll Speed" msgstr "" "Panorerings-\n" "fart" msgid "Toggle the type of interface you want to use." msgstr "Velg den typen grensesnitt du ønsker." msgid "Toggle interface visibility." msgstr "Skift synlighet for grensesnittet." msgid "Toggle colored cursor on or off. This is only an esthetic choice." msgstr "Skru farget markør av eller pÃ¥. Dette er kun smak og behag." msgid "Sets the speed at which you scroll the window." msgstr "Setter farten du panorerer rundt kartet pÃ¥." msgid "Select Game Language:" msgstr "Velg sprÃ¥k:" msgid "Click to choose the selected language." msgstr "Trykk for Ã¥ velge et sprÃ¥k." msgid "%{name} has gained a level." msgstr "%{name} gikk opp en level." msgid "%{skill} +1" msgstr "%{skill} +1" msgid "You have learned %{skill}." msgstr "Du har lært %{skill}." #, fuzzy msgid "" "%{name} has gained a level.\n" "\n" "%{skill} +1" msgstr "%{name} gikk opp en level." #, fuzzy msgid "" "You may learn either:\n" "%{skill1}\n" "or\n" "%{skill2}" msgstr "Du kan velge mellom Ã¥ lære:" msgid "" "Please inspect our fine wares. If you feel like offering a trade, click on " "the items you wish to trade with and for." msgstr "" "Ta en titt pÃ¥ vÃ¥re utmerkede varer. Hvis du ønsker Ã¥ gi et tilbud, klikker " "du pÃ¥ rÃ¥varene du ønsker Ã¥ bytte mellom." msgid "" "You have received quite a bargain. I expect to make no profit on the deal. " "Can I interest you in any of my other wares?" msgstr "" "Du fikk en veldig god pris! Jeg forventer ikke Ã¥ gÃ¥ i pluss pÃ¥ dette. Er du " "interessert i noen av de andre varene mine?" msgid "I can offer you %{count} for 1 unit of %{resfrom}." msgstr "Jeg kan tilby deg %{count} gullstykker for 1 enhet %{resfrom}." msgid "I can offer you 1 unit of %{resto} for %{count} units of %{resfrom}." msgstr "Jeg kan tilby deg 1 enhet %{resto} mot %{count} enheter %{resfrom}." msgid "Min" msgstr "Min" msgid "Qty to trade" msgstr "Ant. Ã¥ bytte" msgid "Trading Post" msgstr "Handelssted" msgid "Your Resources" msgstr "Dine rÃ¥varer" msgid "Available Trades" msgstr "Mulige bytter" msgid "n/a" msgstr "n/a" msgid "guarded by %{count} %{monster}" msgstr "bevoktet av %{count} %{monster}" msgid "guarded by " msgstr "Bevoktet av " msgid "(available: %{count})" msgstr "(tilgjengelig: %{count})" msgid "(empty)" msgstr "(tom)" msgid "already learned" msgstr "allerede lært" #, fuzzy msgid "treeOfKnowledge|free" msgstr "Kunnskapstre" #, fuzzy msgid "already claimed" msgstr "allerede lært" msgid "not claimed" msgstr "" msgid "already knows this skill" msgstr "kan allerede denne ferdigheten" msgid "already has max skills" msgstr "har allerede fullt med evner" msgid "(already visited)" msgstr "(allerede besøkt)" msgid "(not visited)" msgstr "(ikke besøkt)" msgid "%{color} Barrier" msgstr "%{color} barriere" #, fuzzy msgid "(tent visited)" msgstr "(ikke besøkt)" msgid "%{color} Tent" msgstr "%{color} telt" msgid "Road" msgstr "Vei" msgid "(digging ok)" msgstr "(Graving mulig)" msgid "(no digging)" msgstr "(Ingen graving)" msgid "penalty: %{cost}" msgstr "Tap av skritt: %{cost}" msgid "Defenders:" msgstr "Forsvarere:" msgid "Unknown" msgstr "Uvisst" msgid "%{name} (Level %{level})" msgstr "%{name} (Level %{level})" msgid "Attack:" msgstr "Angrep:" msgid "Defense:" msgstr "Forsvar:" msgid "Spell Power:" msgstr "Kraft:" msgid "Knowledge:" msgstr "Kunnskap:" msgid "Spell Points:" msgstr "Magipoeng:" msgid "Move Points:" msgstr "Skritt:" msgid "Uncharted Territory" msgstr "Ikke kartlagt omrÃ¥de" msgid "Cost per troop:" msgstr "Pris per kriger:" msgid "Available: %{count}" msgstr "Tilgjengelig: %{count}" msgid "Number to buy:" msgstr "Antall valgt:" msgid "Recruit selected monsters." msgstr "Verv det utvalgte antall krigere." msgid "Select maximum monsters to be recruited." msgstr "Velg maksimalt antall vervbare krigere." msgid "Select only 1 monster to be recruited." msgstr "Velg kun én kriger til Ã¥ verves." msgid "Select Game Resolution:" msgstr "Velg en oppløsning:" msgid "Click to apply the selected resolution." msgstr "Trykk for Ã¥ bruke den valgte oppløsningen." msgid "Click to apply the entered text." msgstr "Trykk for Ã¥ bekrefte den innskrevne teksten." msgid "Click to open the Virtual Keyboard dialog." msgstr "Trykk for Ã¥ Ã¥pne skjermtastaturet." msgid "Open Virtual Keyboard" msgstr "Ã…pne skjermtastatur" msgid "How many troops to move?" msgstr "Flytt hvor mange styrker?" msgid "Fast separation into slots:" msgstr "Hurtigfordeling:" msgid "Map: " msgstr "Kart: " msgid "" "\n" "\n" "Month: " msgstr "" "\n" "\n" "MÃ¥ned: " msgid ", Week: " msgstr ", uke: " msgid ", Day: " msgstr ", dag: " msgid "" "\n" "\n" "Location: " msgstr "" "\n" "\n" "Plassering: " msgid "Click to save the current game." msgstr "Klikk for Ã¥ lagre det nÃ¥værende spillet." msgid "Click to load a previously saved game." msgstr "Klikk for Ã¥ laste inn et tidligere lagret spill." msgid "Are you sure you want to delete file:" msgstr "Er du sikker pÃ¥ at du vil slette filen:" msgid "Warning!" msgstr "Advarsel!" msgid "File to Save:" msgstr "Lagringsfil:" msgid "File to Load:" msgstr "Lastingsfil:" msgid "Accept the choice made." msgstr "Bekreft valget og gÃ¥ videre." #, fuzzy msgid "%{color} %{race} hero" msgstr "%{color} spiller" #, fuzzy msgid "Terrain object" msgstr "Terrengmodus" msgid "Select Skill:" msgstr "Velg ferdighet:" msgid "Select Spell:" msgstr "Velg trolldom:" msgid "Select Artifact:" msgstr "Velg en skatt:" msgid "Select Monster:" msgstr "Velg monster:" msgid "Select Hero:" msgstr "Velg helt:" #, fuzzy msgid "Select Treasure:" msgstr "Velg helt:" #, fuzzy msgid "Select Ocean Object:" msgstr "Sjøting" #, fuzzy msgid "Castle/town placing" msgstr "Borgvalg" msgid "doubleLinedRace|Neutral" msgstr "Nøytral" msgid "race|Random" msgstr "Tilfeldig" #, fuzzy msgid "Click to start placing the selected castle/town." msgstr "Trykk for Ã¥ bruke den valgte oppløsningen." #, fuzzy msgid "Click to select town placing." msgstr "Klikk for Ã¥ vise neste by." #, fuzzy msgid "Click to select castle placing." msgstr "Klikk her for Ã¥ velge hvilket kart du vil spille." #, fuzzy msgid "%{color} %{race} %{townOrCastle}" msgstr "%{color} spiller" msgid "race|Neutral" msgstr "Nøytral" msgid "You will place" msgstr "" #, fuzzy msgid "Click to select this class." msgstr "Klikk her for Ã¥ velge hvilket kart du vil spille." #, fuzzy msgid "Click to select this color." msgstr "Klikk her for Ã¥ velge hvilket kart du vil spille." #, fuzzy msgid "Select Dwelling:" msgstr "Velg trolldom:" #, fuzzy msgid "Select Landscape Object:" msgstr "Sjøting" msgid "Mine placing" msgstr "" #, fuzzy msgid "" "Resource\n" "type:" msgstr "RÃ¥vare" #, fuzzy msgid "%{mineName} appearance:" msgstr "%{race}en %{name}" #, fuzzy msgid "Click to select %{object} as the resource generator to be placed." msgstr "Klikk her for Ã¥ velge hvilket kart du vil spille." #, fuzzy msgid "Select Mountain Object:" msgstr "Sjøting" #, fuzzy msgid "Select Rock Object:" msgstr "Sjøting" #, fuzzy msgid "Select Tree Object:" msgstr "Sjøting" #, fuzzy msgid "Select Power Up Object:" msgstr "Sjøting" #, fuzzy msgid "Select Adventure Object:" msgstr "Sjøting" msgid "Map Type:\n" msgstr "Kart-type:\n" msgid "The Succession Wars" msgstr "Krigen om tronen" msgid "The Price of Loyalty" msgstr "Prisen for trofasthet" #, fuzzy msgid "Resurrection" msgstr "" "Gjen-\n" "opplivning" msgid "Lose all your heroes and towns." msgstr "Mist alle helter og byer." msgid "Lose a specific town." msgstr "Mist en spesifikk by." msgid "Lose a specific hero." msgstr "Mist en spesifikk helt." msgid "Run out of time. Fail to win by a certain point." msgstr "GÃ¥ tom for tid. Mislykkes i Ã¥ vinne innen et spesifikt antall runder." msgid "Loss Condition" msgstr "TapsvilkÃ¥r" msgid "Defeat all enemy heroes and towns." msgstr "Beseire alle fiendtlige helter og byer." msgid "Capture a specific town." msgstr "Erobre en spesifikk by." msgid "Defeat a specific hero." msgstr "Beseire en spesifikk helt." msgid "Find a specific artifact." msgstr "Finn en spesifikk gjenstand." msgid "Your side defeats the opposing side." msgstr "Den siden du spiller pÃ¥ mÃ¥ slÃ¥ motstandersiden." msgid "Accumulate a large amount of gold." msgstr "Samle sammen mer enn en spesifisert mengde gull." msgid "Victory Condition" msgstr "SeiersvilkÃ¥r" msgid "Map difficulty:" msgstr "Kartets utfordring:" msgid "N" msgstr "" msgid "No maps exist at that size" msgstr "Det fins ingen kart i den størrelsen" msgid "Small Maps" msgstr "SmÃ¥ kart" msgid "View only maps of size small (36 x 36)." msgstr "Vis kun smÃ¥ kart (36 x 36)." msgid "Medium Maps" msgstr "Middels kart" msgid "View only maps of size medium (72 x 72)." msgstr "Vis kun middels store kart (72 x 72)." msgid "Large Maps" msgstr "Store kart" msgid "View only maps of size large (108 x 108)." msgstr "Vis kun store kart (108 x 108)." msgid "Extra Large Maps" msgstr "Ekstra store kart" msgid "View only maps of size extra large (144 x 144)." msgstr "Vis kun ekstra store kart (144 x 144)." msgid "All Maps" msgstr "Alle kart" msgid "View all maps, regardless of size." msgstr "Vis alle kart, uavhengig av størrelse." msgid "Players Icon" msgstr "Spillerikon" msgid "" "Indicates how many players total are in the scenario. Any positions not " "occupied by humans will be occupied by computer players." msgstr "" "Viser hvor mange spillere som totalt er med pÃ¥ kartet. Hver plass som ikke " "er tatt av et menneske vil bli tatt av en computerspiller." msgid "" "Indicates whether the map\n" "is small (36 x 36), medium\n" "(72 x 72), large (108 x 108),\n" "or extra large (144 x 144)." msgstr "" "Viser om kartet er\n" "smÃ¥tt (36 x 36), middels\n" "(72 x 72), stort (108 x 108)\n" "eller veldig stort (144 x 144)." msgid "Size Icon" msgstr "Størrelsesikon" msgid "" "Indicates whether the map is made for \"The Succession Wars\" or \"The Price " "of Loyalty\" version of the game." msgstr "" "Viser om kartet er laget for spillutgaven \"Krigen om tronen\" eller " "\"Prisen for trofasthet\"." msgid "Map Type" msgstr "Kart-type" msgid "Selected Name" msgstr "Navn" msgid "The name of the currently selected map." msgstr "Navnet til det utvalgte kartet." msgid "Selected Map Difficulty" msgstr "Kartets utfordring" #, fuzzy msgid "" "The map difficulty of the currently selected map. The map difficulty is " "determined by the scenario designer. More difficult maps might include more " "or stronger enemies, fewer resources, or other special conditions making " "things tougher for the human player." msgstr "" "Dette er utfordringen til det utvalgte kartet. Utfordringen til kartet er " "spesifisert av skaperen av kartet. De vanskeligste kartene kan inneholde " "sterkere fiender, færre rÃ¥varer, eller andre spesielle vilkÃ¥r som skal gjøre " "ting mer utfordrende for den menneskelige spilleren." msgid "Selected Description" msgstr "Kartbeskrivelse" msgid "The description of the currently selected map." msgstr "Beskrivelse av det utvalgte kartet." msgid "Jump" msgstr "Sprang" msgid "Hero Speed" msgstr "Heltefart" msgid "Don't Show" msgstr "Skjul" msgid "Enemy Speed" msgstr "Fiendefart" msgid "Auto Resolve" msgstr "Datastyrt" msgid "Auto, No Spells" msgstr "Data, ingen magi" msgid "Battles" msgstr "Kampstyring" msgid "autoBattle|Manual" msgstr "Manuell" msgid "Change the speed at which your heroes move on the main screen." msgstr "Juster hvor fort heltene dine flytter seg rundt pÃ¥ kartet." #, fuzzy msgid "" "Sets the speed that A.I. heroes move at. You can also elect not to view A.I. " "movement at all." msgstr "" "Justerer hvor fort datamaskinspillere flytter seg rundt pÃ¥ kartet. Du kan " "ogsÃ¥ velge Ã¥ ikke vise hva de gjør i det hele tatt." #, fuzzy msgid "Change the interface settings of the game." msgstr "Endre bildeinnstillingene til spillet." #, fuzzy msgid "Interface Settings" msgstr "Spillinnstillinger" msgid "Toggle instant battle mode." msgstr "Skift modus for hurtigkamp." msgid "Att." msgstr "Ang." msgid "Def." msgstr "Fors." msgid "Power" msgstr "Kraft" msgid "Knowl" msgstr "Kunns" msgid "1st" msgstr "Første" msgid "2nd" msgstr "Andre" msgid "3rd" msgstr "Tredje" msgid "4th" msgstr "Fjerde" msgid "5th" msgstr "Femte" msgid "6th" msgstr "Sjette" msgid "Oracle: Player Rankings" msgstr "Orakel: Spillerrangering" msgid "Thieves' Guild: Player Rankings" msgstr "Tyvenes forbund: SpillerkÃ¥ring" msgid "Number of Towns:" msgstr "Antall byer:" msgid "Number of Castles:" msgstr "Antall borger:" msgid "Number of Heroes:" msgstr "Antall helter:" msgid "Gold in Treasury:" msgstr "Gull i statskassa:" msgid "Wood & Ore:" msgstr "Ved og malm:" msgid "Gems, Cr, Slf & Mer:" msgstr "Edelsten, Kr, Svl & Kvik:" msgid "Obelisks Found:" msgstr "Obelisker funnet:" msgid "Artifacts:" msgstr "Skatter i besittelse:" msgid "Total Army Strength:" msgstr "Samlet hærstyrke:" msgid "Income:" msgstr "Inntekt:" msgid "Best Hero:" msgstr "Beste helt:" msgid "Best Hero Stats:" msgstr "Beste helteevner:" msgid "Personality:" msgstr "Rolle:" msgid "Best Monster:" msgstr "Beste krigerslag:" #, fuzzy msgid "Map filename" msgstr "" "Kartets\n" "størrelse" msgid "" "Are you sure you want to load a new map? (Any unsaved changes to the current " "map will be lost.)" msgstr "" "Er du sikker pÃ¥ at du ønsker Ã¥ laste inn et nytt kart?\n" "(Alt som er ulagret i dette kartet vil gÃ¥ tapt.)" msgid "" "Are you sure you want to create a new map? (Any unsaved changes to the " "current map will be lost.)" msgstr "" "Er du sikker pÃ¥ at du ønsker Ã¥ lage et nytt kart?(Alt som er ulagret i dette " "kartet vil gÃ¥ tapt.)" msgid "" "Create a new map, either from scratch or using the random map generator." msgstr "Lag et nytt kart, enten fra bunnen av eller automatisk." msgid "New Map" msgstr "Nytt kart" msgid "Load Map" msgstr "Last inn kart" msgid "Load an existing map." msgstr "Last inn et eksisterende kart." msgid "Save Map" msgstr "Lagre kart" msgid "Save the current map." msgstr "Lagre det nÃ¥værende kartet." msgid "Quit out of the map editor." msgstr "Forlat karttegneren" #, fuzzy msgid "Objects cannot be placed outside the map." msgstr "Denne trolldommen kan ikke brukes pÃ¥ et skip." msgid "Monsters cannot be placed on water." msgstr "" msgid "Choose a tile which does not contain any objects." msgstr "" #, fuzzy msgid "Treasures cannot be placed on water." msgstr "Denne trolldommen kan ikke brukes pÃ¥ et skip." msgid "Heroes cannot be placed on water." msgstr "" msgid "" "A maximum of %{count} heroes of the same color can be placed on the map." msgstr "" #, fuzzy msgid "Artifacts cannot be placed on water." msgstr "Denne trolldommen kan ikke brukes pÃ¥ et skip." #, fuzzy msgid "Mountains cannot be placed on water." msgstr "Denne trolldommen kan ikke brukes pÃ¥ et skip." #, fuzzy msgid "Rocks cannot be placed on water." msgstr "Denne trolldommen kan ikke brukes pÃ¥ et skip." #, fuzzy msgid "Trees cannot be placed on water." msgstr "Denne trolldommen kan ikke brukes pÃ¥ et skip." #, fuzzy msgid "Ocean object must be placed on water." msgstr "Denne trolldommen kan ikke brukes pÃ¥ et skip." #, fuzzy msgid "Landscape objects cannot be placed on water." msgstr "Denne trolldommen kan ikke brukes pÃ¥ et skip." #, fuzzy msgid "Towns cannot be placed on water." msgstr "Denne trolldommen kan ikke brukes pÃ¥ et skip." #, fuzzy msgid "Mines cannot be placed on water." msgstr "Denne trolldommen kan ikke brukes pÃ¥ et skip." #, fuzzy msgid "Dwellings cannot be placed on water." msgstr "Denne trolldommen kan ikke brukes pÃ¥ et skip." #, fuzzy msgid "Power-ups cannot be placed on water." msgstr "Denne trolldommen kan ikke brukes pÃ¥ et skip." #, fuzzy msgid "Adventure objects cannot be placed on water." msgstr "Denne trolldommen kan ikke brukes pÃ¥ et skip." #, fuzzy msgid "Used to place %{object}." msgstr "Brukes til Ã¥ plassere en helt." #, fuzzy msgid "Select object type" msgstr "Sjøting" #, fuzzy msgid "" "Click here to\n" "select a monster." msgstr "Klikk her for Ã¥ velge hvilket kart du vil spille." #, fuzzy msgid "" "Click here to\n" "select another monster." msgstr "Vennligst velg en annen helt." msgid "" "Cell\n" "Details" msgstr "" #, fuzzy msgid "Roads" msgstr "Vei" #, fuzzy msgid "Streams" msgstr "Bekkmodus" #, fuzzy msgid "Erase" msgstr "Viskemodus" msgid "Mountains" msgstr "Fjell" #, fuzzy msgid "Rocks" msgstr "Stein" msgid "Trees" msgstr "Trær" #, fuzzy msgid "Water Objects" msgstr "Ødemarksting" #, fuzzy msgid "Miscellaneous" msgstr "Finneas" msgid "Artifacts" msgstr "Gjenstander" #, fuzzy msgid "Dwellings" msgstr "Halvinger" msgid "Mines" msgstr "Gruver" #, fuzzy msgid "Power-ups" msgstr "Kraft" msgid "Treasures" msgstr "Verdifulle gjenstander" msgid "Towns" msgstr "Byer" msgid "Heroes" msgstr "Helter" msgid "editorErasure|Terrain" msgstr "" msgid "editorErasure|Castles" msgstr "" msgid "editorErasure|Monsters" msgstr "" msgid "editorErasure|Heroes" msgstr "" msgid "editorErasure|Artifacts" msgstr "" msgid "editorErasure|Roads" msgstr "" msgid "editorErasure|Streams" msgstr "" msgid "editorErasure|Treasures" msgstr "" msgid "" "Draws terrain in\n" "%{size} by %{size} square increments." msgstr "" "Tegner terreng innenfor en firkant med et størrelsesforhold pÃ¥\n" "%{size} ganger %{size}." msgid "" "Erases objects in\n" "%{size} by %{size} square increments." msgstr "" "Fjerner alle ting innenfor en firkant med et størrelsesforhold pÃ¥ \n" "%{size} ganger %{size}." msgid "Small Brush" msgstr "Liten pensel" msgid "Medium Brush" msgstr "Middels pensel" msgid "Large Brush" msgstr "Stor pensel" msgid "Area Fill" msgstr "OmrÃ¥defylling" msgid "Used to click and drag for filling in large areas." msgstr "Brukes til Ã¥ fylle omrÃ¥der ved Ã¥ klikke og dra over dem." msgid "Clear Area" msgstr "Tøm omrÃ¥de" msgid "Used to click and drag for clearing large areas." msgstr "Brukes til Ã¥ tømme omrÃ¥der ved Ã¥ klikke og dra over dem." msgid "" "Costs %{rate} times normal movement for all heroes. (Pathfinding reduces or " "eliminates the penalty.)" msgstr "" "Koster %{rate} ganger mer enn vanlig forflytning for alle helter.\n" "(Kartlesing minsker eller fjerner denne reduksjonen." msgid "Traversable only by boat." msgstr "Kan kun overfares med skip." msgid "No special modifiers." msgstr "Ingen spesielle pÃ¥virkninger." msgid "Toggle the erasure of %{type} objects." msgstr "" msgid "" "Objects of this type will be deleted with the Erase tool. Left-click here to " "deselect this type. Press and hold this button to deselect all other object " "types." msgstr "" msgid "" "Objects of this type will NOT be deleted with the Erase tool. Left-click " "here to select this type. Press and hold this button to select all other " "object types." msgstr "" msgid "Terrain Mode" msgstr "Terrengmodus" msgid "Used to draw the underlying grass, dirt, water, etc. on the map." msgstr "" "Brukes til Ã¥ tegne det underliggende gresset, gjørmen, vannet osv. pÃ¥ kartet." #, fuzzy msgid "Landscape Objects Mode" msgstr "Tingmodus" #, fuzzy msgid "" "Used to place landscape objects (mountains, rocks, trees, etc.) on the map." msgstr "" "Brukes til Ã¥ plassere ting (fjell, trær, verdifulle gjenstander osv.) pÃ¥ " "kartet." msgid "Detail Mode" msgstr "Detaljemodus" msgid "Used for special editing of monsters, heroes and towns." msgstr "Brukes for spesielle tilpasninger av monster, helter eller byer." #, fuzzy msgid "Adventure Objects Mode" msgstr "Kartinnstillinger" #, fuzzy msgid "" "Used to place adventure objects (artifacts, dwellings, mines, treasures, " "etc.) on the map." msgstr "" "Brukes til Ã¥ plassere ting (fjell, trær, verdifulle gjenstander osv.) pÃ¥ " "kartet." #, fuzzy msgid "Kingdom Objects Mode" msgstr "Tingmodus" #, fuzzy msgid "Used to place kingdom objects (towns, castles and heroes) on the map." msgstr "" "Brukes til Ã¥ plassere ting (fjell, trær, verdifulle gjenstander osv.) pÃ¥ " "kartet." #, fuzzy msgid "Monsters Mode" msgstr "Monstre" #, fuzzy msgid "Used to place monsters on the map." msgstr "" "Brukes til Ã¥ plassere\n" "en gruppe monstre." msgid "Allows you to draw streams by clicking and dragging." msgstr "Lar deg tegne bekker ved Ã¥ klikke og dra." msgid "Stream Mode" msgstr "Bekkmodus" msgid "Allows you to draw roads by clicking and dragging." msgstr "Lar deg tegne veier ved Ã¥ klikke og dra." msgid "Road Mode" msgstr "Veimodus" msgid "Erase Mode" msgstr "Viskemodus" #, fuzzy msgid "Used to erase objects from the map." msgstr "Brukes til Ã¥ fjerne ting fra kartet." msgid "Change between zoom and normal view." msgstr "Bytt mellom forstørret og normal visning." msgid "Magnify" msgstr "Forstørr" msgid "Undo" msgstr "Angre" msgid "Undo your last action." msgstr "" msgid "Create a new map either from scratch or using the random map generator." msgstr "" "Lag et nytt kart helt fra bunnen av eller ved Ã¥ bruke den automatiske " "kartlageren." msgid "Edit map title, description, and other general information." msgstr "Rediger kartets navn, beskrivelse eller andre detaljer." msgid "Specifications" msgstr "Spesifikasjoner" msgid "File Options" msgstr "Filvalg" msgid "" "Open the file options menu, where you can save or load maps, or quit out of " "the editor." msgstr "" "Ã…pne menyen for filvalg, hvor du kan lagre eller laste inn kart, eller " "forlate karttegneren." msgid "View the editor system options, which let you customize the editor." msgstr "Vis karttegnerens systeminnstillinger der den kan skreddersys." msgid "Create a map that is %{size} squares wide and %{size} squares high." msgstr "" "Lag et kart som har en høyde pÃ¥ %{size} kvadrater og en bredde pÃ¥ %{size} " "kvadrater." msgid "Cancel back to the New Map menu." msgstr "Avbryt og dra tilbake til menyen for nye kart." msgid "Cancel back to the main menu." msgstr "Avbryt og dra tilbake til hovedmenyen." msgid "From Scratch" msgstr "Fra bunnen av" msgid "Start from scratch with a blank map." msgstr "Begynn pÃ¥ et kart helt fra bunn av." msgid "Create a randomly generated map." msgstr "Lag et kart automatisk." msgid "Random" msgstr "Tilfeldig" msgid "Cancel back to the Map Editor main menu." msgstr "Avbryt og dra tilbake til karttegnerens hovedmeny." msgid "No maps available!" msgstr "Ingen kart er tilgjengelige!" msgid "Warning" msgstr "Advarsel" msgid "difficulty|Easy" msgstr "Lett" msgid "difficulty|Normal" msgstr "Middels" msgid "difficulty|Hard" msgstr "Vanskelig" msgid "difficulty|Expert" msgstr "Ekspert" msgid "difficulty|Impossible" msgstr "Umulig" msgid "and more..." msgstr "og mer..." msgid "Easy" msgstr "Enkel" msgid "Hard" msgstr "Vanskelig" msgid "Campaign Difficulty" msgstr "FelttogsnivÃ¥" msgid "" "Choose this difficulty if you want to prefer game story over challenge. AI " "is weaker in comparison with normal difficulty." msgstr "" "Velg dette nivÃ¥et hvis du kun er ute etter Ã¥ oppleve historien. Motstanderne " "dine vil være svakere enn ved middels nivÃ¥." msgid "" "Choose this difficulty to enjoy the campaign as per the original design." msgstr "" "Velg dette nivÃ¥et for Ã¥ oppleve felttoget slik som det opprinnelig var " "tiltenkt." msgid "" "Choose this difficulty if you want challenge. AI is stronger in comparison " "with normal difficulty." msgstr "" "Velg nivÃ¥et hvis du ønsker en utfordring. Motstanderne dine vil være " "sterkere enn ved middels nivÃ¥." msgid "" "Congratulations!\n" "\n" "Days: %{days}\n" msgstr "" "Gratulerer!\n" "\n" "Dager: %{days}\n" msgid "" "\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" "\n" "FelttogsnivÃ¥: %{difficulty}\n" "\n" msgid "" "Score: %{score}\n" "\n" "Rating:\n" "%{rating}" msgstr "" "Poeng: %{score}\n" "\n" "Grad:\n" "%{rating}" msgid "Start the selected scenario." msgstr "Begynn det kartet du har valgt." msgid "View Intro" msgstr "Vis video" msgid "View Intro videos for the current state of the campaign." msgstr "Vis filmen for det inneværende punktet av felttoget." msgid "" "Select the campaign difficulty. This can be lowered at any point during the " "campaign." msgstr "" "Velg vanskelighetsgrad for felttoget. Dette kan nedjusteres nÃ¥r som helst " "senere i felttoget." msgid "Restart the current scenario." msgstr "Start det nÃ¥værende kartet pÃ¥ nytt." msgid "Difficulty" msgstr "Vanskelighetsgrad" msgid "" "You have changed to a lower difficulty for the campaign. You will not be " "able to revert this after this point. The high score will be calculated " "based solely on the new difficulty. Do you want to proceed?" msgstr "" "Du har endret til en lavere vanskelighetsgrad for felttoget. Du vil ikke " "kunne omgjøre dette. Din endelige poengsum vil bli beregnet kun ut ifra den " "nye vanskelighetsgraden. Ønsker du Ã¥ gÃ¥ videre med valget?" msgid "Are you sure you want to restart this scenario?" msgstr "Er du sikker pÃ¥ at du vil begynne dette kartet pÃ¥ nytt?" msgid "Campaign Scenario loading failure" msgstr "Lasting av felttogskart mislyktes" msgid "Please make sure that campaign files are correct and present." msgstr "Vennligst forsikre deg om at kampanjefilene er riktige og til stede." msgid "Days spent" msgstr "Dager brukt" msgid "The number of days spent on this campaign." msgstr "Antall dager som har blitt brukt pÃ¥ felttoget sÃ¥ langt." msgid "Project Coordination and Core Development" msgstr "" "Prosjektkoordinering \n" "og hovedprogramvare-\n" "utvikling" msgid "Development" msgstr "Programvareutvikling" msgid "Visit us at " msgstr "Treff oss pÃ¥ " msgid "QA and Support" msgstr "Kvalitetssikring og støtte" msgid "Dev and Support" msgstr "Utvikling og støtte" msgid "Special Thanks to" msgstr "Spesielt takk til" msgid "and many other contributors!" msgstr "og mange andre bidragsytere!" msgid "and many-many other supporters!" msgstr "og mange flere bidragsytere!" msgid "Support us at" msgstr "Støtt oss pÃ¥" msgid "local-donation-platform|https://www.patreon.com/fheroes2" msgstr "https://www.patreon.com/fheroes2" msgid "Connect with us at" msgstr "Kontakt oss pÃ¥" msgid "local-social-network|https://www.facebook.com/groups/fheroes2" msgstr "https://www.facebook.com/groups/fheroes2" msgid "Need help with the game?" msgstr "Behov for hjelp med spillet?" msgid "Original project before 0.7" msgstr "Opprinnelig prosjekt før 0.7" msgid "Heroes of Might and Magic II: The Succession Wars team" msgstr "Teamet bak Heroes of Might and Magic II" msgid "Designed and Directed" msgstr "Utforming og ledelse" msgid "Programming and Design" msgstr "Programmering og utforming" msgid "Executive Producer" msgstr "Sjefsprodusent" msgid "Producer" msgstr "Produsent" msgid "Additional Design" msgstr "Øvrig utforming" msgid "Additional Programming" msgstr "Øvrig programmering" msgid "Musical Production" msgstr "Musikkproduksjon" msgid "Music and Sound Design" msgstr "Musikk- og lyddesign" msgid "Vocalists" msgstr "Vokalister" msgid "Art Director" msgstr "Kunstnerisk leder" msgid "Assistant Art Director" msgstr "Kunstnerisk leder-assistent" msgid "Artists" msgstr "Artister" msgid "QA Manager" msgstr "Koordinator for kvalitetssikring" msgid "QA" msgstr "Kvalitetssikring" msgid "Writing" msgstr "Skribent" msgid "Manual and Helpfile" msgstr "HÃ¥ndbok og hjelpefil" msgid "Scenarios" msgstr "Kart" msgid "Heroes of Might and Magic II: The Price of Loyalty team" msgstr "Teamet bak Heroes of Might and Magic II: The Price of Loyalty" msgid "Design Lead" msgstr "Hoveddesigner" msgid "Designers" msgstr "Designere" msgid "Programming Lead" msgstr "Hovedprogrammerer" msgid "Art Lead" msgstr "Artistisk leder" msgid "Playtesters" msgstr "Spilltestere" msgid "Designer" msgstr "Designer" msgid "Producers" msgstr "Produsenter" msgid "QA Managers" msgstr "Kvalitetssikringsledelse" msgid "Sound Design" msgstr "Lyddesign" msgid "Town Themes" msgstr "Musikktema i byer" msgid "Alto Sax" msgstr "Altsaksofonist" msgid "Harpsichord and Piano" msgstr "Cembalo og piano" msgid "Basso Vocal" msgstr "Bass" msgid "Soprano Vocal" msgstr "Sopran" msgid "Recorded at %{recordingStudio}" msgstr "Lydopptak gjort ved %{recordingStudio}" msgid "credits|Manual" msgstr "HÃ¥ndbok" msgid "German Consultant" msgstr "TyskrÃ¥dgiver" msgid "Map Designers" msgstr "Karttegnere" msgid "Package Design" msgstr "Innpakningsdesign" msgid "Your Name" msgstr "Ditt navn" msgid "Unknown Hero" msgstr "Ukjent helt" msgid "Standard" msgstr "Standard" msgid "View High Scores for Standard Maps." msgstr "Vis rekordtavle for enkle parti." msgid "Campaign" msgstr "Felttog" msgid "View High Scores for Campaigns." msgstr "Vis rekordtavle for felttog." msgid "hotkey|default okay event" msgstr "Bekreft" msgid "hotkey|default cancel event" msgstr "Avbryt" msgid "hotkey|default left" msgstr "Venstre" msgid "hotkey|default right" msgstr "Høyre" msgid "hotkey|default up" msgstr "Opp" msgid "hotkey|default down" msgstr "Ned" msgid "hotkey|toggle fullscreen" msgstr "Fullskjerm" msgid "hotkey|toggle text support mode" msgstr "Tekstmodus" msgid "hotkey|new game" msgstr "Nytt parti" msgid "hotkey|load game" msgstr "Last inn parti" msgid "hotkey|highscores" msgstr "Rekordtavle" msgid "hotkey|credits" msgstr "Rulletekst" msgid "hotkey|standard game" msgstr "Enkelt parti" msgid "hotkey|campaign game" msgstr "Felttog" msgid "hotkey|multi-player game" msgstr "Flerspillermodus" msgid "hotkey|settings" msgstr "Innstillinger" msgid "hotkey|quit" msgstr "Avslutt" msgid "hotkey|select map" msgstr "Velg kart" msgid "hotkey|select small map size" msgstr "Velg smÃ¥ kart" msgid "hotkey|select medium map size" msgstr "Velg middles kart" msgid "hotkey|select large map size" msgstr "Velg store kart" msgid "hotkey|select extra large map size" msgstr "Velg ekstra store kart" msgid "hotkey|select all map sizes" msgstr "Velg alle kart" msgid "hotkey|hotseat game" msgstr "Byttespillingsparti" msgid "hotkey|battle only game" msgstr "Kampparti" msgid "hotkey|choose the original campaign" msgstr "Velg Krigen om tronen" msgid "hotkey|choose the expansion campaign" msgstr "Velg Prisen for Lojalitet" msgid "hotkey|map editor main menu" msgstr "Karttegnerens hovedmeny" msgid "hotkey|new map menu" msgstr "Menyen for nye kart" msgid "hotkey|load map menu" msgstr "Menyen for kartlasting" msgid "hotkey|new map from scratch" msgstr "Nytt kart fra bunnen av" msgid "hotkey|new random map" msgstr "Nytt automatisk kart" #, fuzzy msgid "hotkey|undo last action" msgstr "Standard handling" #, fuzzy msgid "hotkey|redo last action" msgstr "Standard handling" msgid "hotkey|roland campaign" msgstr "Rolands felttog" msgid "hotkey|archibald campaign" msgstr "Archibalds felttog" msgid "hotkey|the price of loyalty campaign" msgstr "Felttoget Prisen for trofasthet" msgid "hotkey|voyage home campaign" msgstr "Felttoget Ferden hjem" msgid "hotkey|wizard's isle campaign" msgstr "Felttoget Magimestrenes øy" msgid "hotkey|descendants campaign" msgstr "Felttoget Etterkommerene" msgid "hotkey|select first campaign bonus" msgstr "Velg felttogsbonus nr.1" msgid "hotkey|select second campaign bonus" msgstr "Velg felttogsbonus nr.2" msgid "hotkey|select third campaign bonus" msgstr "Velg felttogsbonus nr.3" msgid "hotkey|view campaign intro" msgstr "Vis felttogsintro" msgid "hotkey|select campaign difficulty" msgstr "Velg felttogets vanskelighetsgrad" msgid "hotkey|restart campaign scenario" msgstr "Start felttogskart pÃ¥ nytt" msgid "hotkey|world map left" msgstr "Verdenskart venstre" msgid "hotkey|world map right" msgstr "Verdenskart høyre" msgid "hotkey|world map up" msgstr "Verdenskart opp" msgid "hotkey|world map down" msgstr "Verdenskart ned" msgid "hotkey|world map up left" msgstr "Verdenskart opp til venstre" msgid "hotkey|world map up right" msgstr "Verdenskart opp til høyre" msgid "hotkey|world map down left" msgstr "Verdenskart ned til venstre" msgid "hotkey|world map down right" msgstr "Verdenskart ned til høyre" msgid "hotkey|save game" msgstr "Lagre parti" msgid "hotkey|next hero" msgstr "Neste helt" #, fuzzy msgid "hotkey|start hero movement" msgstr "Fortsett heltens forflytning" msgid "hotkey|cast adventure spell" msgstr "Bruk kartmagi" msgid "hotkey|put hero to sleep" msgstr "Sett helt i dvale" msgid "hotkey|next town" msgstr "Neste by" msgid "hotkey|end turn" msgstr "Avslutt tur" msgid "hotkey|file options" msgstr "Filvalg" msgid "hotkey|adventure options" msgstr "Kartinnstillinger" msgid "hotkey|puzzle map" msgstr "Puslekart" msgid "hotkey|scenario information" msgstr "Kartbeskrivelse" msgid "hotkey|dig for artifact" msgstr "Grav etter gjenstand" msgid "hotkey|view world" msgstr "Vis verden" msgid "hotkey|kingdom summary" msgstr "Oversikt over kongedømmet" msgid "hotkey|default action" msgstr "Standard handling" msgid "hotkey|open focus" msgstr "Ã…pne nÃ¥værende plassering" msgid "hotkey|system options" msgstr "Systeminnstillinger" msgid "hotkey|scroll left" msgstr "Panorer til venstre" msgid "hotkey|scroll right" msgstr "Panorer til høyre" msgid "hotkey|scroll up" msgstr "Panorer opp" msgid "hotkey|scroll down" msgstr "Panorer ned" msgid "hotkey|toggle control panel" msgstr "SlÃ¥ av/pÃ¥ kontrollpanel" msgid "hotkey|toggle radar" msgstr "SlÃ¥ av/pÃ¥ minikart" msgid "hotkey|toggle buttons" msgstr "SlÃ¥ av/pÃ¥ knapper" msgid "hotkey|toggle status" msgstr "SlÃ¥ av/pÃ¥ status" msgid "hotkey|toggle icons" msgstr "SlÃ¥ av/pÃ¥ ikoner" msgid "hotkey|transfer control to ai" msgstr "Overfør styring til KI" msgid "hotkey|retreat from battle" msgstr "Trekk tilbake fra kamp" msgid "hotkey|surrender during battle" msgstr "Overgivelse i kamp" msgid "hotkey|toggle battle auto mode" msgstr "SlÃ¥ pÃ¥/av automatiske trekk i kamp" msgid "hotkey|finish the battle in auto mode" msgstr "Fullfør kamp med autotrekk" msgid "hotkey|battle options" msgstr "Kampvalg" msgid "hotkey|skip turn in battle" msgstr "Velg pass i kamp" msgid "hotkey|cast battle spell" msgstr "Bruk magi i kamp" msgid "hotkey|dwelling level 1" msgstr "Bosted nivÃ¥ 1" msgid "hotkey|dwelling level 2" msgstr "Bosted nivÃ¥ 2" msgid "hotkey|dwelling level 3" msgstr "Bosted nivÃ¥ 3" msgid "hotkey|dwelling level 4" msgstr "Bosted nivÃ¥ 4" msgid "hotkey|dwelling level 5" msgstr "Bosted nivÃ¥ 5" msgid "hotkey|dwelling level 6" msgstr "Bosted nivÃ¥ 6" msgid "hotkey|well" msgstr "Brønn" msgid "hotkey|marketplace" msgstr "Markedsplass" msgid "hotkey|mage guild" msgstr "Magikerforbund" msgid "hotkey|shipyard" msgstr "Skipsverft" msgid "hotkey|thieves guild" msgstr "Tyvenes laug" msgid "hotkey|tavern" msgstr "Vertshus" msgid "hotkey|construction screen" msgstr "Byggeskjerm" msgid "hotkey|buy all monsters in well" msgstr "Kjøp alle krigere fra brønnen" msgid "hotkey|split stack by half" msgstr "Del tropper i to" msgid "hotkey|split stack by one" msgstr "Del av én kriger fra tropp" msgid "hotkey|join stacks" msgstr "Foren tropper" msgid "hotkey|upgrade troop" msgstr "Oppgrader tropp" msgid "hotkey|dismiss hero or troop" msgstr "Bortvis helt" msgid "" "Do you want to regain control from AI? The effect will take place only on " "the next turn." msgstr "" "Vil du gi styringen til datamaskinen? Dette vil først tre i kraft den neste " "runden." msgid "" "Do you want to transfer control from you to the AI? The effect will take " "place only on the next turn." msgstr "" "Ønsker du Ã¥ ta styringen fra datamaskinen? Dette vil kun tre i kraft den " "neste runden." #, fuzzy msgid "Default Actions" msgstr "Standard handling" msgid "Global Actions" msgstr "" msgid "Main Menu" msgstr "" msgid "World Map" msgstr "Verdenskart" #, fuzzy msgid "Battle Screen" msgstr "Heltevindu" #, fuzzy msgid "Town Screen" msgstr "Heltevindu" msgid "Army Actions" msgstr "" msgid "Editor" msgstr "" msgid "The save file is corrupted." msgstr "Lagringsfilen er korrupt." msgid "Unsupported save format: " msgstr "Ustøttet lagringsformat: " msgid "Current game version: " msgstr "Spillutgave som brukes: " msgid "Last supported version: " msgstr "Sist støttede utgave: " msgid "This file contains a save with an invalid game type." msgstr "Denne filen inneholder et lagringspunkt med en ugyldig spillversjon." #, fuzzy msgid "" "This save file requires \"The Price of Loyalty\" game assets, but they have " "not been provided to the engine." msgstr "" "Denne filen ble lagret i spillutgaven \"Prisen for trofasthet\", men de " "tilhørende utvidelsesfilene mangler." msgid "This saved game is localized to '" msgstr "Dette lagrede spillet er satt opp for sprÃ¥ket '" msgid "' language, but the current language of the game is '" msgstr "', men det gjeldende sprÃ¥ket for spillet er '" msgid "Hot Seat" msgstr "Byttespilling" msgid "" "Play a Hot Seat game, where 2 to 4 players play around the same computer, " "switching into the 'Hot Seat' when it is their turn." msgstr "" "Spill en runde byttespilling hvor 2 til 4 spillere rullerer pÃ¥ Ã¥ spille pÃ¥ " "den samme enheten og bytter plass nÃ¥r det er nestemann sin tur." msgid "A single player game playing out a single map." msgstr "Et enkeltspillerparti som spilles ut pÃ¥ et enkelt kart." msgid "Standard Game" msgstr "Enkelt parti" msgid "A single player game playing through a series of maps." msgstr "Et enkeltspillerparti som utspilles over en rekke kart." msgid "Campaign Game" msgstr "Felttog" msgid "Multi-Player Game" msgstr "Flerspillermodus" msgid "" "A multi-player game, with several human players completing against each " "other on a single map." msgstr "" "Et flerspillerparti hvor flere menneskelige spillere konkurrerer mot " "hverandre pÃ¥ et enkelt kart." msgid "fheroes2 Resurrection Team presents" msgstr "Teamet bak fheroes2 Resurrection presenterer" msgid "Greetings!" msgstr "Heisann!" msgid "Welcome to Heroes of Might and Magic II powered by fheroes2 engine!" msgstr "" "Velkommen til Heroes of Might and Magic II drevet av fheroes2-spillmotoren!" #, fuzzy msgid "" "Welcome to Heroes of Might and Magic II powered by the fheroes2 engine!\n" "Before starting the game please select a game resolution." msgstr "" "Velkommen til Heroes of Might and Magic II drevet av fheroes2 spillmotoren! " "Vennligst velg skjermoppløsning før du begynner." msgid "Please Remember" msgstr "Merk" # Needs extra whitespace to avoid yellow word splitting #, fuzzy msgid "" "You can always change the language, resolution and settings of the game by " "clicking on the " msgstr "Du kan alltid bytte oppløsning ved Ã¥ klikke pÃ¥ " msgid "door" msgstr "døra" msgid " on the left side of the Main Menu, or with the " msgstr "" msgid " button from the " msgstr "" #, fuzzy msgid "NEW GAME" msgstr "" "NYTT\n" "PARTI" msgid "" " menu. \n" "\n" "Enjoy the game!" msgstr "" #, fuzzy msgid "The Editor requires \"The Price of Loyalty\" expansion files to work." msgstr "" "En av de fire nye utvidelsesfelttogene fra utvidelsen Prisen for trofasthet." msgid "Quit Heroes of Might and Magic II and return to the operating system." msgstr "" "Forlat Heroes of Might and Magic II og dra tilbake til operativsystemet." msgid "Credits" msgstr "Rulletekst" msgid "View the credits screen." msgstr "Vis rulleteksten." msgid "High Scores" msgstr "Rekordtavle" msgid "View the high scores screen." msgstr "Vis rekordtavlen." msgid "Change language, resolution and settings of the game." msgstr "Endre sprÃ¥ket, oppløsningen og andre spillinnstillinger." msgid "Game Settings" msgstr "Spillinnstillinger" msgid "" "Required video files for campaign selection window are missing. Please make " "sure that all necessary files are present in the system." msgstr "" "De nødvendige filene for felttogets valgvindu mangler. Vennligst forsikre " "deg om at alle nødvendige filer er tilstede i systemet." msgid "" "Either Roland's or Archibald's campaign from the original Heroes of Might " "and Magic II." msgstr "" "Velg mellom felttoget til Roland og Archibald fra det originale Heroes of " "Might and Magic II." msgid "Original Campaign" msgstr "Originalt felttog" msgid "Expansion Campaign" msgstr "Utvidelsesfelttog" msgid "One of the four new campaigns from the Price of Loyalty expansion set." msgstr "" "En av de fire nye utvidelsesfelttogene fra utvidelsen Prisen for trofasthet." msgid "Loading video. Please wait..." msgstr "Laster video. Vennligst vent..." msgid "Host" msgstr "Vert" msgid "" "The host sets up the game options. There can only be one host per network " "game." msgstr "" "Verten fyller ut spillvalgene. Det kan kun være én vert per nettverksspill." msgid "Guest" msgstr "Gjest" msgid "" "The guest waits for the host to set up the game, then is automatically added " "in. There can be multiple guests for TCP/IP games." msgstr "" "Gjesten venter pÃ¥ at verten setter opp partiet og blir deretter automatisk " "lagt til. Det kan være flere gjester for TCP/IP-parti." msgid "Battle Only" msgstr "Kampmodus" msgid "Setup and play a battle without loading any map." msgstr "Sett opp og spill en kamp uten Ã¥ laste inn et kart." msgid "2 Players" msgstr "2 Spillere" msgid "" "Play with 2 human players, and optionally, up to 4 additional computer " "players." msgstr "" "Spill med 2 menneskelige spillere, og eventuelt opp til 4 ekstra " "computerspillere." msgid "3 Players" msgstr "3 Spillere" msgid "" "Play with 3 human players, and optionally, up to 3 additional computer " "players." msgstr "" "Spill med 3 menneskelige spillere, og eventuelt opp til 3 ekstra " "computerspillere." msgid "4 Players" msgstr "4 Spillere" msgid "" "Play with 4 human players, and optionally, up to 2 additional computer " "players." msgstr "" "Spill med 4 menneskelige spillere, og eventuelt opp til 2 ekstra " "computerspillere." msgid "5 Players" msgstr "5 Spillere" msgid "" "Play with 5 human players, and optionally, up to 1 additional computer " "player." msgstr "" "Spill med 5 menneskelige spillere, og eventuelt opp til 1 ekstra " "computerspiller." msgid "6 Players" msgstr "6 Spillere" msgid "Play with 6 human players." msgstr "Spill med 6 menneskelige spillere." #, fuzzy msgid "Dragon city has fallen! You are now the Master of the Dragons." msgstr "Dragenes landsby har falt! Du har nÃ¥ blitt mesteren av dragene." msgid "" "You captured %{name}!\n" "You are victorious." msgstr "" "Du har overtatt %{name}!\n" "Seieren er din." msgid "" "You have captured the enemy hero %{name}!\n" "Your quest is complete." msgstr "" "Du har fanget fiendens helt %{name}!\n" "Oppdraget ditt er fullført." msgid "" "You have found the %{name}.\n" "Your quest is complete." msgstr "" "Du har funnet %{name}.\n" "Oppdraget ditt er fullført." msgid "Ultimate Artifact" msgstr "Den sjeldneste gjenstanden" msgid "" "The enemy is beaten.\n" "Your side has triumphed!" msgstr "" "Fienden har blitt slÃ¥tt ut.\n" "Din side har seiret!" msgid "" "You have built up over %{count} gold in your treasury.\n" "All enemies bow before your wealth and power." msgstr "" "Du har spart opp over %{count} gullstykker i statskassa di.\n" "Alle motstanderne bøyer seg foran din rikdom og makt." #, fuzzy msgid "Victory!" msgstr "Hektor" msgid "" "The enemy has captured %{name}!\n" "They are triumphant." msgstr "" "Fienden har fanget %{name}!\n" "De har seiret." msgid "" "The enemy has built up over %{count} gold in his treasury.\n" "You must bow done in defeat before his wealth and power." msgstr "" "Fienden har spart opp mer enn %{count} gull i statskassa si.\n" "Du mÃ¥ bøye deg i nederlag for fiendens rikdom og makt." msgid "You have been eliminated from the game!!!" msgstr "Du har blitt slÃ¥tt ut av spillet!!!" msgid "" "You have lost the hero %{name}.\n" "Your quest is over." msgstr "" "Du har mistet helten %{name}.\n" "Oppdraget ditt er over." msgid "" "You have failed to complete your quest in time.\n" "All is lost." msgstr "" "Du klarte ikke Ã¥ fullføre oppdraget innen tiden.\n" "Alt er tapt." msgid "Defeat!" msgstr "Nederlag!" msgid "" "Base score: %{score}\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" "Grunnpoeng: %{score}\n" "Vanskelighetsgrad: %{difficulty}\n" "\n" msgid "Defeat all enemy heroes and capture all enemy towns and castles." msgstr "" "SlÃ¥ ut alle fiendtlige helter og overta alle fiendtlige byer og borger." msgid "Run out of time. (Fail to win by a certain point.)" msgstr "GÃ¥ tom for tid. (Mislykkes i Ã¥ vinne innen en viss tid.)" msgid "You must defeat the enemy %{enemies}." msgid_plural "You must defeat the enemy alliance of %{enemies}." msgstr[0] "SlÃ¥ ut motstanderen din %{enemies}." msgstr[1] "SlÃ¥ ut den fiendtlige alliansen bestÃ¥ende av %{enemies}." msgid "" "The alliance consisting of %{allies} and you must defeat the enemy " "%{enemies}." msgid_plural "" "The alliance consisting of %{allies} and you must defeat the enemy alliance " "of %{enemies}." msgstr[0] "" "Alliansen som bestÃ¥r av %{allies} og deg mÃ¥ slÃ¥ motstanderen %{enemies}." msgstr[1] "" "Alliansen som bestÃ¥r av %{allies} og deg mÃ¥ slÃ¥ den fiendtlige alliansen som " "bestÃ¥r av %{enemies}." msgid "Capture the castle '%{name}'." msgstr "Overta borgen kalt %{name}." msgid "Capture the town '%{name}'." msgstr "Overta byen kalt %{name}." msgid "Defeat the hero '%{name}'." msgstr "SlÃ¥ ut helten ved navn %{name}." msgid "Find the ultimate artifact." msgstr "Finn den sjeldneste skatten." msgid "Find the '%{name}' artifact." msgstr "Finn skatten kalt %{name}." msgid "Accumulate %{count} gold." msgstr "Samle opp %{count} gullstykker." msgid "" ", or you may win by defeating all enemy heroes and capturing all enemy towns " "and castles." msgstr "" ", eller du kan vinne ved Ã¥ slÃ¥ ut alle fiendtlige helter og overta alle " "fiendtlige byer og borger." msgid "Lose the castle '%{name}'." msgstr "Mist borgen kalt %{name}." msgid "Lose the town '%{name}'." msgstr "Mist byen kalt %{name}." msgid "Lose the hero: %{name}." msgstr "Mist helten kalt %{name}." msgid "Fail to win by the end of month %{month}, week %{week}, day %{day}." msgstr "" "Mislykkes i Ã¥ vinne innen slutten av mÃ¥ned %{month}, uke %{week}, dag %{day}." msgid "%{color} player has been vanquished!" msgstr "%{color} spiller har blitt utslettet!" msgid "Major Event!" msgstr "Viktig hendelse!" msgid "Scenario:" msgstr "Kart:" msgid "Game Difficulty:" msgstr "Vanskelighetsgrad:" # Evt spillere. MÃ¥ funke for bÃ¥de motspillere og lagkamerater. msgid "Opponents:" msgstr "Deltakere:" msgid "Class:" msgstr "Helteslag:" msgid "Rating %{rating}%" msgstr "Utbytte %{rating}%" msgid "Click here to select which scenario to play." msgstr "Klikk her for Ã¥ velge hvilket kart du vil spille." msgid "Scenario" msgstr "Kart" msgid "Game Difficulty" msgstr "Vanskelighetsgrad" msgid "" "This lets you change the starting difficulty at which you will play. Higher " "difficulty levels start you of with fewer resources, and at the higher " "settings, give extra resources to the computer." msgstr "" "Dette lar deg forandre utfordringen du starter med. Jo høyere " "vanskelighetsgraden er desto større blir forskjellen mellom rÃ¥varene du " "starter med og den motspillerne starter med." msgid "Difficulty Rating" msgstr "Utbytte" msgid "" "The difficulty rating reflects a combination of various settings for your " "game. This number will be applied to your final score." msgstr "" "Utbytteprosenten regnes ut som summen av de forskjellige innstilligene du " "har valgt for partiet ditt. Denne prosenten pÃ¥virker den endelige " "poengsummen din dersom du vinner partiet." msgid "Click to accept these settings and start a new game." msgstr "Klikk her for Ã¥ bekrefte disse valgene og begynne pÃ¥ et nytt parti." msgid "Click to return to the main menu." msgstr "Klikk her for Ã¥ dra tilbake til hovedmenyen." msgid "Astrologers proclaim the Month of the %{name}." msgstr "Astrologene kunngjør at det er mÃ¥neden av %{name}." msgid "Astrologers proclaim the Week of the %{name}." msgstr "Astrologene kunngjør at det er uka av %{name}." msgid "After regular growth, the population of %{monster} is doubled!" msgstr "Etter en vanlig vekst dobles nÃ¥ alle befolkninger med %{monster}!" msgid "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgid_plural "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgstr[0] "" "Etter en vanlig vekst øker nÃ¥ befolkningen med %{monster} med %{count} " "prosent!" msgstr[1] "" "Etter en vanlig vekst øker nÃ¥ befolkningen av %{monster} med %{count} " "prosent!" msgid "%{monster} growth +%{count}." msgstr "Alle befolkninger med %{monster} vokser med +%{count}." msgid " All populations are halved." msgstr " Alle befolkninger halveres." msgid " All dwellings increase population." msgstr " Alle bostedenes befolkninger opplever vanlig vekst." msgid "New Month!" msgstr "" msgid "New Week!" msgstr "Ny uke!" msgid "Beware!" msgstr "Viktig beskjed!" msgid "" "%{color} player, this is your last day to capture a town, or you will be " "banished from this land." msgstr "" "%{color} spiller, dette er den siste dagen for Ã¥ ta over en by, ellers blir " "du bannlyst fra landet." msgid "" "%{color} player, you only have %{day} days left to capture a town, or you " "will be banished from this land." msgstr "" "%{color} spiller, du har kun %{day} dager igjen pÃ¥ Ã¥ ta over en by, ellers " "blir du bannlyst fra landet." msgid "%{color} player's turn." msgstr "%{color} spillers tur." #, fuzzy msgid "" "%{color} player, you have lost your last town. If you do not conquer another " "town in the next week, you will be eliminated." msgstr "" "%{color} spiller, du har mistet den siste byen din. Hvis du ikke tar over en " "ny by innen neste uke vil du bli slÃ¥tt ut." msgid "" "%{color} player, your heroes abandon you, and you are banished from this " "land." msgstr "" "%{color} spiller, dine helter forlater deg og du er bannlyst fra landet." msgid "Lord Kilburn" msgstr "Fyrst Kilburn" msgid "Tsabu" msgstr "Sabu" msgid "Sir Galant" msgstr "Herr Galant" msgid "Thundax" msgstr "Tordøks" msgid "Lord Haart" msgstr "Fyrst Hjart" msgid "Ariel" msgstr "Ariel" msgid "Rebecca" msgstr "Rebekka" msgid "Sandro" msgstr "Sandro" msgid "Crodo" msgstr "Krod" msgid "Barock" msgstr "Barokk" msgid "Antoine" msgstr "" msgid "Astra" msgstr "Astra" msgid "Agar" msgstr "Hagar" msgid "Vatawna" msgstr "Vatavna" msgid "Vesper" msgstr "Vesper" msgid "Ambrose" msgstr "Ambrose" msgid "Troyan" msgstr "Troyann" msgid "Jojosh" msgstr "Jojosva" msgid "Wrathmont" msgstr "Vredeberg" msgid "Maximus" msgstr "Maksimus" msgid "Next Hero" msgstr "Neste helt" msgid "Select the next Hero." msgstr "Velg neste helt." #, fuzzy msgid "Hero Movement" msgstr "" "Uthevd\n" "rekkevidde" msgid "" "Start the Hero's movement along the current path or re-visit the object " "occupied by the Hero. Press and hold this button to reset the Hero's path." msgstr "" msgid "Kingdom Summary" msgstr "Oppsummert kongedømme" msgid "View a Summary of your Kingdom." msgstr "Vis en oppsummering av kongedømmet ditt." msgid "Cast an adventure spell." msgstr "Bruk kartmagi." msgid "End Turn" msgstr "Avslutt tur" msgid "End your turn and left the computer take its turn." msgstr "Avslutt turen din og la motstanderne gjøre sine turer." msgid "Adventure Options" msgstr "Kartinnstillinger" msgid "Bring up the adventure options menu." msgstr "Hent fram menyen for kartinnstillinger." msgid "" "Bring up the file options menu, allowing you to load, save, start a new game " "or quit." msgstr "" "Ã…pne menyen for filvalg, som lar deg laste inn et parti, lagre et parti, " "begynne pÃ¥ et nytt parti eller avslutte." msgid "Bring up the system options menu, allowing you to customize your game." msgstr "Hent fram menyen for systeminnstillinger og skreddersy spillet." msgid "" "One or more heroes may still move, are you sure you want to end your turn?" msgstr "" "Én eller flere helter kan fortsatt forflytte seg. Vil du likevel avslutte " "turen din?" msgid "Are you sure you want to quit?" msgstr "Er du sikker pÃ¥ at du vil avslutte?" msgid "Are you sure you want to restart? (Your current game will be lost.)" msgstr "" "Er du sikker pÃ¥ at du vil begynne pÃ¥ nytt?\n" "(Alt som er ulagret i dette partiet vil gÃ¥ tapt.)" msgid "Are you sure you want to overwrite the save with this name?" msgstr "Er du sikker pÃ¥ at du vil overskrive lagringspunktet med dette navnet?" msgid "Game saved successfully." msgstr "Lagring vellykket." msgid "There was an issue during saving." msgstr "Det oppstod et problem under lagringen." msgid "" "Are you sure you want to load a new game? (Your current game will be lost.)" msgstr "" "Er du sikker pÃ¥ at du ønsker Ã¥ laste inn et tidligere parti?\n" "(Alt som er ulagret i dette partiet vil gÃ¥ tapt.)" msgid "Try looking on land!!!" msgstr "Prøv Ã¥ let pÃ¥ land!!!" msgid "" "Searching for the Ultimate Artifact is fruitless. Your hero could not carry " "it even if he found it - all his artifact slots are full." msgstr "" "Det vil være nytteløst Ã¥ lete etter den sjeldneste skatten fordi helten din " "vil ikke kunne ta den med seg - det er ikke plass til mer." msgid "Digging for artifacts requires a whole day, try again tomorrow." msgstr "Graving etter sjeldne skatter krever en hel dag. Prøv igjen i morgen." msgid "" "After spending many hours digging here, you have uncovered the %{artifact}." msgstr "Etter mange timer med graving her, oppdager du %{artifact}." msgid "Congratulations!" msgstr "Gratulerer!" msgid "Nothing here. Where could it be?" msgstr "Ingenting her. Hvor kan den være?" msgid "Try searching on clear ground." msgstr "Prøv Ã¥ let pÃ¥ bar bakke." msgid "A miniature view of the known world. Left click to move viewing area." msgstr "" "Et overblikk over den utforskede verdenen. Venstre-klikk for Ã¥ flytte " "visningsomrÃ¥det." msgid "Month: %{month} Week: %{week}" msgstr "MÃ¥ned: %{month}, Uke: %{week}" msgid "Day: %{day}" msgstr "Dag: %{day}" msgid "" "You find a small\n" "quantity of %{resource}." msgstr "" "Du fant en liten\n" "mengde %{resource}." msgid "Status Window" msgstr "Statusvindu" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date." msgstr "" "Dette vinduet gir informasjon om tilstanden til helten din og kongedømmet " "ditt, samt at det viser dagens dato." msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date. Left click here to cycle through these windows." msgstr "" "Dette vinduet viser informasjon om tilstanden til helten og kongedømmet " "ditt, i tillegg til dagens dato. Venstre-klikk her for Ã¥ bla gjennom de " "forskjellige informasjonsvinduene." msgid "" "This lets you change player starting positions and colors. A particular " "color will always start in a particular location. Some positions may only be " "played by a computer player or only by a human player." msgstr "" "Dette lar deg forandre startplassen og fargen til spillerne. Hver startplass " "er knyttet til en farge. Noen plasser kan kun tas av computerspillere, mens " "andre kun av menneskelige spillere." msgid "" "This lets you change the class of a player. Classes are not always " "changeable. Depending on the scenario, a player may receive additional towns " "and/or heroes not of their primary alignment." msgstr "" "Dette lar deg bytte helteslaget til en spiller. Helteslaget kan ikke alltid " "byttes. Avhengig av kartet, kan en spiller fÃ¥ flere byer og/eller helter som " "ikke er av spillerens hovedtilhørighet." msgid "Handicap" msgstr "Handikap" # msgid "" "This lets you change the handicap of a particular player. Only humans may be " "handicapped. Handicapped players start with fewer resources and earn 15 or " "30% fewer resources per turn for mild and severe handicaps, respectively." msgstr "" "Denne innstillingen lar deg justere handikapet til de forskjellige " "spillerne. Kun menneskelige spillere kan ha handikap. Spillere med handikap " "begynner kartet med færre rÃ¥varer og tjener 15 eller 30% færre rÃ¥varer hver " "runde for henholdsvis lett og sterkt handikap." msgid "%{color} player" msgstr "%{color} spiller" msgid "No Handicap" msgstr "Ingen handikap" msgid "No special restrictions on start resources and earning them per turn." msgstr "" "Ingen spesielle begrensninger pÃ¥ hvor mye rÃ¥varer man starter med pÃ¥ kartet " "og hvor mye man tjener hver runde." msgid "Mild Handicap" msgstr "Lett handikap" # msgid "" "Mild handicapped players start with fewer resources and earn 15% fewer " "resources per turn." msgstr "" "Spillere med et lett handikap vil begynne kartet med færre rÃ¥varer og tjene " "15% mindre rÃ¥varer hver runde." msgid "Severe Handicap" msgstr "Sterkt handikap" msgid "" "Severe handicapped players start with fewer resources and earn 30% fewer " "resources per turn." msgstr "" "Spillere med sterkt handikap vil begynne kartet med færre rÃ¥varer og tjene " "30% mindre rÃ¥varer hver runde." msgid "View %{skill} Info" msgstr "Vis info om %{skill}" msgid "Keyboard|123" msgstr "" msgid "Keyboard|SPACE" msgstr "MELLOMROM" msgid "Keyboard|ABC" msgstr "" msgid "Kingdom Income" msgstr "Kongedømmets inntekt" msgid "Kingdom Income per day." msgstr "Dette er den daglige inntekten til kongedømmet ditt." msgid "For every lighthouse controlled, your ships will move further each day." msgstr "" "For hvert fyrtÃ¥rn under din besittelse vil skipene dine kunne flytte seg " "lengre hver dag." msgid "English" msgstr "Engelsk" msgid "French" msgstr "Fransk" msgid "Polish" msgstr "Polsk" msgid "German" msgstr "Tysk" msgid "Russian" msgstr "Russisk" msgid "Italian" msgstr "Italiensk" msgid "Czech" msgstr "Tsjekkisk" msgid "Norwegian" msgstr "Norsk" msgid "Belarusian" msgstr "" msgid "Bulgarian" msgstr "" msgid "Ukrainian" msgstr "" msgid "Romanian" msgstr "" msgid "Spanish" msgstr "" msgid "Swedish" msgstr "" msgid "Portuguese" msgstr "" msgid "Turkish" msgstr "" msgid "Dutch" msgstr "" msgid "Hungarian" msgstr "" msgid "Danish" msgstr "" msgid "Slovak" msgstr "" msgid "Vietnamese" msgstr "" msgid ", FPS: " msgstr ", FPS: " msgid "%{object} robber" msgstr "%{object} ranet" msgid "%{object} raided" msgstr "%{object} plyndret" msgid "Ector" msgstr "Hektor" msgid "Gwenneth" msgstr "Lycke" msgid "Sir Gallant" msgstr "Herr Gallant" msgid "Tyro" msgstr "Tyros" msgid "Dimitry" msgstr "Dimitry" msgid "Ruby" msgstr "Rubin" msgid "Crag Hack" msgstr "Kragg Hakk" msgid "Fineous" msgstr "Finneas" msgid "Jezebel" msgstr "Izabell" msgid "Atlas" msgstr "Atlas" msgid "Ergon" msgstr "Herkon" msgid "Jaclyn" msgstr "Bina" msgid "Gem" msgstr "Edelsten" msgid "Natasha" msgstr "Natalie" msgid "Carlawn" msgstr "Karlana" msgid "Luna" msgstr "Luna" msgid "Arie" msgstr "Arie" msgid "Barok" msgstr "Barok" msgid "Kastore" msgstr "Kastor" msgid "Falagar" msgstr "Falagar" msgid "Dawn" msgstr "Morgengry" msgid "Flint" msgstr "Flint" msgid "Halon" msgstr "Halon" msgid "Myra" msgstr "Myrha" msgid "Myrini" msgstr "Myrini" msgid "Wilfrey" msgstr "Vilfrid" msgid "Mandigal" msgstr "Mandigal" msgid "Sarakin" msgstr "Sarakinn" msgid "Charity" msgstr "Karita" msgid "Darlana" msgstr "Darlana" msgid "Ranloo" msgstr "Rannlov" msgid "Rialdo" msgstr "Rialto" msgid "Zam" msgstr "Zam" msgid "Zom" msgstr "Zom" msgid "Celia" msgstr "Celia" msgid "Roxana" msgstr "Roksana" msgid "Lord Corlagon" msgstr "Fyrst Korlagon" msgid "Lord Halton" msgstr "Fyrst Halton" msgid "Sister Eliza" msgstr "Søster Eliza" msgid "Brother Brax" msgstr "Broder Brax" msgid "Dainwin" msgstr "Deinvinn" msgid "Joseph" msgstr "Josef" msgid "Mog" msgstr "Mog" msgid "Solmyr" msgstr "Solmyr" msgid "Ceallach" msgstr "Ketill" msgid "Drakonia" msgstr "Drakonia" msgid "Elderian" msgstr "Elderian" msgid "Gallavant" msgstr "Gallavant" msgid "Jarkonas" msgstr "Harkonas" msgid "Martine" msgstr "Martine" msgid " gives you maximum morale" msgstr " gir deg den beste kampÃ¥nden" msgid " gives you maximum luck" msgstr " gir deg den beste flaksen" msgid "You cannot pick up this artifact, you already have a full load!" msgstr "" "Du kan ikke plukke opp denne skatten - du er helt fullastet med skatter!" msgid "To cast spells, you must first buy a spell book for %{gold} gold." msgstr "" "For Ã¥ bruke trolldom mÃ¥ du først kjøpe ei tryllebok. Den koster %{gold} " "gullstykker." msgid "Unfortunately, you seem to be a little short of cash at the moment." msgstr "Dessverre ser det ut til at du ikke har rÃ¥d akkurat nÃ¥." msgid "Do you wish to buy one?" msgstr "Ønsker du Ã¥ kjøpe én?" msgid "%{count} / day" msgstr "%{count} / dag" msgid "one" msgstr "1" msgid "two" msgstr "2" msgid "A whirlpool engulfs your ship. Some of your army has fallen overboard." msgstr "" "Virvelstrømmen sluker skipet ditt. Noen av krigerne dine har falt om bord." msgid "Insulted by your refusal of their offer, the monsters attack!" msgstr "Fornærmet over at du avslo tilbudet deres angriper styrken deg!" msgid "" "The %{monster}, awed by the power of your forces, begin to scatter.\n" "Do you wish to pursue and engage them?" msgstr "" "Gruppen med %{monster} blir mÃ¥lløs nÃ¥r den ser hvor overlegen hæren din er, " "og den begynner Ã¥ rømme.\n" "Ønsker du Ã¥ følge etter og gÃ¥ til kamp?" msgid "Ransacking an enemy camp, you discover a hidden cache of treasures." msgstr "" "Du gjennomsøker den forlatte leirplassen og avdekker et skjult gjemmested " "for verdifulle ting." msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with these resources, " "come back next week for more.\"" msgstr "" "Vokteren av mølla sier:\n" "\"Min herre, jeg har jobbet veldig hardt for Ã¥ kunne gi deg disse rÃ¥varene. " "Kom tilbake neste uke for mer.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there are no resources currently available. Please try " "again next week.\"" msgstr "" "Vokteren av mølla sier:\n" "\"Min herre, jeg har ingen rÃ¥varer igjen. Vennligst prøv igjen neste uke.\"." msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with this gold, come " "back next week for more.\"" msgstr "" "Vokteren av mølla sier:\n" "\"Min herre, jeg har jobbet veldig hardt for Ã¥ kunne gi deg dette gullet. " "Kom tilbake neste uke for mer.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there is no gold currently available. Please try again " "next week.\"" msgstr "" "Vokteren av mølla sier:\n" "\"Min herre, det er ikke noe gull igjen. Vennligst prøv igjen neste uke.\"." msgid "" "You've found an abandoned lean-to.\n" "Poking about, you discover some resources hidden nearby." msgstr "" "Du kommer over en forlatt gapahuk.\n" "Etter Ã¥ ha sett litt rundt omkring den finner du noen bortgjemte rÃ¥varer." msgid "The lean-to is long abandoned. There is nothing of value here." msgstr "Gapahuken er for lengst forlatt. Det fins ingenting av verdi her." msgid "" "You catch a leprechaun foolishly sleeping amidst a cluster of magic " "mushrooms.\n" "In exchange for his freedom, he guides you to a small pot filled with " "precious things." msgstr "" "Du fÃ¥r fatt i en regnbuegnom som lÃ¥ og sov i dÃ¥rskap blant en klynge magiske " "sopper.\n" "I bytte mot sin frihet viser han deg veien til en liten gryte fylt med " "verdifulle ting." msgid "" "You've found a magic garden, the kind of place that leprechauns and faeries " "like to cavort in, but there is no one here today.\n" "Perhaps you should try again next week." msgstr "" "Du fant en magisk hage, den typen sted hvor regnbuegnomer og feer liker Ã¥ " "hoppe rundt, men det er ingen her i dag.\n" "Kanskje du skal prøve igjen neste uke." msgid "You come upon the remains of an unfortunate adventurer." msgstr "Du kommer over levningene til en uheldig eventyrer." msgid "Treasure" msgstr "Verdifull gjenstand" msgid "Searching through the tattered clothing, you find the %{artifact}." msgstr "Etter Ã¥ ha lett gjennom de fillete plaggene, finner du: %{artifact}." msgid "Searching through the tattered clothing, you find nothing." msgstr "Du leter gjennom de fillete plaggene, men finner ingenting." msgid "" "You come across an old wagon left by a trader who didn't quite make it to " "safe terrain." msgstr "" "Du kommer over en gammel varevogn etterlatt av en handelsmann, som etter alt " "Ã¥ bedømme ikke hadde hellet pÃ¥ sin side." msgid "Unfortunately, others have found it first, and the wagon is empty." msgstr "Dessverre har noen andre funnet den først og vognen er tom." msgid "Searching inside, you find the %{artifact}." msgstr "Etter Ã¥ ha lett inni den, finner du %{artifact}." msgid "Inside, you find some of the wagon's cargo still intact." msgstr "Inni vognen finner du noe av lasten dens i behold." msgid "You search through the flotsam, and find some wood and some gold." msgstr "Du gÃ¥r gjennom vrakgodset og finner litt ved og litt gull." msgid "You search through the flotsam, and find some wood." msgstr "Du gÃ¥r gjennom vrakgodset og finner litt ved." msgid "You search through the flotsam, but find nothing." msgstr "Du gÃ¥r gjennom vrakgodset, men finner ingenting." msgid "Shrine of the 1st Circle" msgstr "Tempel av første sirkel" msgid "" "You come across a small shrine attended by a group of novice acolytes.\n" "In exchange for your protection, they agree to teach you a simple spell - " "'%{spell}'." msgstr "" "Du kommer over et lite tempel passet pÃ¥ av en gruppe nyutdannete " "tempeltjenere.\n" "I bytte mot din beskyttelse gÃ¥r de med pÃ¥ Ã¥ lære deg den enkle trolldommen: " "'%{spell}'." msgid "Shrine of the 2nd Circle" msgstr "Tempel av andre sirkel" msgid "" "You come across an ornate shrine attended by a group of rotund friars.\n" "In exchange for your protection, they agree to teach you a spell - " "'%{spell}'." msgstr "" "Du kommer over et flott tempel passet pÃ¥ av en gruppe omfangsrike munker.\n" "I bytte mot din beskyttelse gÃ¥r de med pÃ¥ Ã¥ lære deg trolldommen: '%{spell}'." msgid "Shrine of the 3rd Circle" msgstr "Tempel av tredje sirkel" msgid "" "You come across a lavish shrine attended by a group of high priests.\n" "In exchange for your protection, they agree to teach you a sophisticated " "spell - '%{spell}'." msgstr "" "Du kommer over et overdÃ¥dig tempel passet pÃ¥ av en gruppe øversteprester.\n" "I bytte mot din beskyttelse gÃ¥r de med pÃ¥ Ã¥ lære deg den mektige " "trolldommen: '%{spell}'." msgid "" "\n" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "\n" "Dessverre har du ikke visdommen som skal til for Ã¥ forstÃ¥ trolldommen, og " "dermed klarer du ikke Ã¥ lære deg den." msgid "" "\n" "Unfortunately, you already have knowledge of this spell, so there is nothing " "more for them to teach you." msgstr "" "\n" "Dessverre kan du allerede denne trolldommen, sÃ¥ det er ingenting mer de kan " "lære deg." msgid "" "\n" "Unfortunately, you have no Magic Book to record the spell with." msgstr "" "\n" "Dessverre har du ingen tryllebok Ã¥ notere ned trolldommen i." msgid "" "You approach the hut and observe a witch inside studying an ancient tome on " "%{skill}.\n" "\n" msgstr "" "Du kommer nærme en hytte og fÃ¥r øye pÃ¥ en heks pÃ¥ innsiden. Hun leser fra en " "eldgammel bok om %{skill}.\n" "\n" msgid "" "As you approach, she turns and focuses her one glass eye on you.\n" "\"You already know everything you deserve to learn!\" the witch screeches. " "\"NOW GET OUT OF MY HOUSE!\"" msgstr "" "Idet du gÃ¥r nærmere snur hun seg og retter sitt ene glassøye mot deg.\n" "\"Du kan allerede alt du fortjener Ã¥ lære!\" skriker heksen. \"SE Ã… KOM DEG " "UT AV HUSET MITT!\"" msgid "" "As you approach, she turns and speaks.\n" "\"You already know that which I would teach you. I can help you no further.\"" msgstr "" "Imens du gÃ¥r nærmere snur hun seg og sier:\n" "\"Du kan allerede det jeg ville ha lært deg. Jeg kan ikke hjelpe deg med mer." "\"." msgid "" "An ancient and immortal witch living in a hut with bird's legs for stilts " "teaches you %{skill} for her own inscrutable purposes." msgstr "" "Den eldgamle og udødelige heksa som er bosatt i hytta pÃ¥ de gigantiske " "fuglebeinene velger av hennes egne merkelige grunner Ã¥ lære deg\n" "%{skill}." msgid "As you drink the sweet water, you gain luck for your next battle." msgstr "" "Etter Ã¥ ha drukket det forfriskende vannet øker flaksen din for det neste " "slaget ditt." msgid "You drink from the enchanted fountain, but nothing happens." msgstr "Du drikker fra den magiske fontenen, men det skjer ingenting." msgid "You enter the faerie ring, but nothing happens." msgstr "Du gÃ¥r inn i feenes ring, men ingenting skjer." msgid "" "Upon entering the mystical faerie ring, your army gains luck for its next " "battle." msgstr "PÃ¥ innsiden av feenes ring fÃ¥r hæren din flaks for sin neste kamp." msgid "" "You've found an ancient and weathered stone idol.\n" "It is supposed to grant luck to visitors, but since the stars are already " "smiling upon you, it does nothing." msgstr "" "Du fant en eldgammel og værbitt gudestatue av stein.\n" "Den skal visstnok bringe lykke til besøkende, men siden sola allerede " "skinner pÃ¥ deg gjør den ingenting." msgid "" "You've found an ancient and weathered stone idol.\n" "Kissing it is supposed to be lucky, so you do. The stone is very cold to the " "touch." msgstr "" "Du fant en eldgammel og værbitt gudestatue av stein.\n" "Ã… kysse den skal visstnok bringe lykke, sÃ¥ du gjør akkurat det. Steinen " "kjentes veldig kald ut." msgid "The mermaids silently entice you to return later and be blessed again." msgstr "" "I stillhet lokker havfruene deg til Ã¥ komme tilbake senere og bli velsignet " "igjen." msgid "" "The magical, soothing beauty of the Mermaids reaches you and your crew.\n" "Just for a moment, you forget your worries and bask in the beauty of the " "moment.\n" "The mermaids charms bless you with increased luck for your next combat." msgstr "" "Den magiske og beroligende skjønnheten til havfruene klarer Ã¥ lokke til seg " "deg og mannskapet ditt.\n" "For et øyeblikk glemmer du bekymringene dine og nyter stunden.\n" "Havfruenes fortryllelser velsigner deg med økt flaks til din neste kamp." msgid "" "You come upon the pyramid of a great and ancient king.\n" "You are tempted to search it for treasure, but all the old stories warn of " "fearful curses and undead guardians.\n" "Will you search?" msgstr "" "Du kommer over pyramiden til en mektig oldtidskonge.\n" "Du er fristet til Ã¥ lete etter skatter i den, men gamle fortellinger har " "advart deg om fryktelige forbannelser og vandøde voktere.\n" "Vil du likevel lete gjennom den?" msgid "" "Upon defeating the monsters, you decipher an ancient glyph on the wall, " "telling the secret of the spell - '" msgstr "" "Etter Ã¥ ha utryddet monstrene tyder du en eldgammel tekst pÃ¥ veggen, og den " "skjuler hemmeligheten bak trolldommen:\n" "'" msgid "Unfortunately, you have no Magic Book to record the spell with." msgstr "Dessverre har du ingen tryllebok Ã¥ notere ned trolldommen i." msgid "" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "Dessverre har du ikke visdommen som skal til for Ã¥ forstÃ¥ trolldommen, og du " "klarer ikke Ã¥ lære deg den." msgid "" "You come upon the pyramid of a great and ancient king.\n" "Routine exploration reveals that the pyramid is completely empty." msgstr "" "Du kommer over pyramiden til en stor konge av oldtiden.\n" "Etter en rask sjekk viser det seg at pyramiden er fullstendig tom." msgid "" "A drink at the well is supposed to restore your spell points, but you are " "already at maximum." msgstr "" "En slurk fra brønnen skal egentlig fylle opp manaen din, men du har allerede " "fullt." msgid "A second drink at the well in one day will not help you." msgstr "" "Flere slurker fra brønnen pÃ¥ én og samme dag vil ikke kunne hjelpe deg." msgid "A drink from the well has restored your spell points to maximum." msgstr "" "Du tar deg en slurk fra brønnen, og manaen din blir fullstendig fylt opp." msgid "" "\"I'm sorry sir,\" The leader of the soldiers says, \"but you already know " "everything we have to teach.\"" msgstr "" "\"Jeg beklager, min herre\", sier lederen av soldatene, \"men du kan " "allerede alt vi har Ã¥ lære bort.\"" msgid "The soldiers living in the fort teach you a few new defensive tricks." msgstr "" "Soldatene som holder til ved fortet lærer deg et par nye forsvarstriks." msgid "" "You've come upon a mercenary camp practicing their tactics. \"You're too " "advanced for us,\" the mercenary captain says. \"We can teach nothing more.\"" msgstr "" "Du kommer over leiren til noen leiesoldater som driver med kamptrening. \"Du " "har et mye høyere nivÃ¥ enn oss,\" sier leiesoldatenes kaptein. \"Det er " "ingenting vi kan lære deg.\"" msgid "" "You've come upon a mercenary camp practicing their tactics. The mercenaries " "welcome you and your troops and invite you to train with them." msgstr "" "Du kommer over leiren til noen leiesoldater som driver med kamptrening. " "Leiesoldatene ønsker deg og styrkene dine velkomne og inviterer dere til Ã¥ " "sparre med dem." msgid "\"Go 'way!\", the witch doctor barks, \"you know all I know.\"" msgstr "\"GÃ¥ vekk!\", bjeffer heksedoktoren, \"du kan alt jeg kan.\"" msgid "" "An Orcish witch doctor living in the hut deepens your knowledge of magic by " "showing you how to cast stones, read portents, and decipher the intricacies " "of chicken entrails." msgstr "" "En heksedoktor av en ork bor i hytta, og han beriker kunnskapen din om magi " "ved Ã¥ vise deg hvordan man kaster steiner, tolker jærtegn og finner skjulte " "sannheter i kyllinginnvoller." msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, the Druids turn you away, indicating they have nothing " "new to teach you." msgstr "" "Du har funnet en gruppe druider som tilber ved et av deres merkelige " "byggverk i stein. I stillhet bortviser druidene deg for Ã¥ vise at de ikke " "har noe nytt Ã¥ lære deg." msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, they teach you new ways to cast spells." msgstr "" "Du har funnet en gruppe druider som tilber ved et av deres merkelige " "byggverk i stein. I stillhet lærer de deg nye mÃ¥ter Ã¥ trylle pÃ¥." msgid "" "You tentatively approach the burial ground of ancient warriors. Do you want " "to search the graves?" msgstr "" "Du tilnærmer deg forsiktig et gravsted for krigere fra oldtiden. Ønsker du Ã¥ " "plyndre gravene deres?" msgid "" "You spend several hours searching the graves and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" "Du tilbringer flere timer pÃ¥ Ã¥ lete gjennom gravene, men finner ingenting. " "En slik fordømmelig handling svekker kampÃ¥nden til hæren din." msgid "Upon defeating the Zombies you search the graves and find something!" msgstr "" "Etter Ã¥ ha tatt hÃ¥nd om zombiene tilbringer du flere timer pÃ¥ Ã¥ lete gjennom " "gravene, og tilslutt finner du noe!" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the shipwreck?" msgstr "" "Det rÃ¥tne skroget til et storslagent sjørøverskip knaker nifst mens det " "skubber borti steinene.\n" "Vil du lete gjennom skipbruddet?" msgid "" "You spend several hours sifting through the debris and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" "Du tilbringer flere timer pÃ¥ Ã¥ gÃ¥ gjennom vraket, men finner ingenting. En " "slik forkastelig handling svekker kampÃ¥nden til hæren din." msgid "" "Upon defeating the Ghosts you sift through the debris and find something!" msgstr "" "Etter Ã¥ ha beseiret gjenferdene bruker du flere timer pÃ¥ Ã¥ gÃ¥ gjennom " "vraket, og tilslutt finner du noe!" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the ship?" msgstr "" "Det rÃ¥tne skroget til et mektig sjørøverskip knaker nifst mens det skubber " "borti steinene.\n" "Vil du lete gjennom skipet?" msgid "" "Upon defeating the Skeletons you sift through the debris and find something!" msgstr "" "Etter Ã¥ ha beseiret skjelettene tilbringer du flere timer pÃ¥ Ã¥ gÃ¥ gjennom " "godset, og tilslutt finner du noe!" msgid "Your men spot a navigational buoy, confirming that you are on course." msgstr "" "Mannskapet ditt fÃ¥r øye pÃ¥ et flytende sjømerke. Det bekrefter at dere er pÃ¥ " "rett kurs." msgid "" "Your men spot a navigational buoy, confirming that you are on course and " "increasing their morale." msgstr "" "Mannskapet ditt fÃ¥r øye pÃ¥ et flytende sjømerke. Det bekrefter at dere er pÃ¥ " "rett kurs og dette styrker kampÃ¥nden til hele mannskapet." msgid "" "The drink at the oasis is refreshing, but offers no further benefit. The " "oasis might help again if you fought a battle first." msgstr "" "Slurken fra oasen er forfriskende, men den gjør ingenting mer utover det. " "Oasen kan kanskje hjelpe deg igjen dersom du tar en kamp først." #, fuzzy msgid "" "A drink at the oasis fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" "En slurk fra oasen fyller troppene dine med styrke og løfter stemningen " "deres. Du kan reise litt lengre i dag." msgid "" "The drink at the watering hole is refreshing, but offers no further benefit. " "The watering hole might help again if you fought a battle first." msgstr "" "Slurken fra vannkilden er forfriskende, men utover det skjer det ingenting. " "Vannkilden kan kanskje hjelpe deg igjen hvis du tar en kamp først." msgid "" "A drink at the watering hole fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" "En slurk fra vannkilden fyller troppene dine med styrke og løfter stemningen " "deres. Du kan reise litt lengre i dag." msgid "" "It doesn't help to pray twice before a battle. Come back after you've fought." msgstr "" "Det hjelper ikke Ã¥ be to ganger før en kamp. Kom tilbake etter at du har " "kjempet." msgid "A visit and a prayer at the temple raises the morale of your troops." msgstr "Et besøk og en bønn ved tempelet styrker kampÃ¥nden til hæren din." msgid "" "An old Knight appears on the steps of the gazebo. \"I am sorry, my liege, I " "have taught you all I can.\"" msgstr "" "En gammel ridder dukker opp pÃ¥ trappa til lysthuset, \"Jeg beklager, min " "herre. Jeg har lært deg alt jeg kan.\"." msgid "" "An old Knight appears on the steps of the gazebo. \"My liege, I will teach " "you all that I know to aid you in your travels.\"" msgstr "" "En gammel ridder dukker opp pÃ¥ trappa til lysthuset, \"Min herre, jeg skal " "lære deg alt jeg kan for Ã¥ hjelpe deg pÃ¥ din ferd.\"" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he says, \"I would give you an artifact as a reward, but " "you're all full.\"" msgstr "" "Du berget en skipbrudden fra en sikker død i det nÃ¥deløse havet. Takknemlig " "sier han: \"Jeg skulle gjerne ha gitt deg en belønning som takk, men du har " "ikke plass.\"" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he rewards you for your act of kindness by giving you the " "%{art}." msgstr "" "Du berget en skipbrudden fra en sikker død i det nÃ¥deløse havet. Som takk " "for din godhet gir han deg skatten:\n" " %{art}." msgid "A leprechaun offers you the %{art} for the small price of %{gold} Gold." msgstr "" "En regnbuegnom tilbyr deg %{art} imot en beskjeden sum pÃ¥ %{gold} " "gullstykker." msgid "" "A leprechaun offers you the %{art} for the small price of %{gold} Gold and " "%{count} %{res}." msgstr "" "En regnbuegnom tilbyr deg %{art} imot en beskjeden sum pÃ¥ %{gold} " "gullstykker og %{count} enheter %{res}." msgid "Do you wish to buy this artifact?" msgstr "Ønsker du Ã¥ kjøpe denne verdifulle gjenstanden?" msgid "" "You try to pay the leprechaun, but realize that you can't afford it. The " "leprechaun stamps his foot and ignores you." msgstr "" "Du forsøker Ã¥ betale regnbuegnomen, men innser at du ikke har rÃ¥d. Gnomen " "tramper foten sin og snur seg vekk fra deg." msgid "" "Insulted by your refusal of his generous offer, the leprechaun stamps his " "foot and ignores you." msgstr "" "Fornærmet av avslaget ditt pÃ¥ det sjenerøse tilbudet hans tramper gnomen med " "foten sin og snur seg vekk fra deg." msgid "You've found the artifact: " msgstr "Du fant den verdifulle gjenstanden: " msgid "" "You've found the humble dwelling of a withered hermit. The hermit tells you " "that he is willing to give the %{art} to the first wise person he meets." msgstr "" "Du kommer over det beskjedne bostedet til en gammel og rynkete einstøing. " "Gamlingen forteller deg at han er villig til Ã¥ gi bort den verdifulle " "gjenstanden %{art} til det første vise mennesket han møter." msgid "" "You've come across the spartan quarters of a retired soldier. The soldier " "tells you that he is willing to pass on the %{art} to the first true leader " "he meets." msgstr "" "Du kommer over det nøysomme kvarteret til en avgÃ¥tt soldat. Soldaten " "forteller deg at han er villig til Ã¥ gi bort skatten %{art} til den første " "sanne lederen han møter." msgid "" "You've encountered a strange person with a hat and an owl on it. He tells " "you that he is willing to give %{art} if you have %{skill}." msgstr "" "Du har møtt pÃ¥ en snodig type med en hatt som har en ugle pÃ¥ seg. Han " "forteller deg at han er villig til Ã¥ gi deg skatten %{art} hvis du kan " "%{skill}" msgid "" "You come upon an ancient artifact. As you reach for it, a pack of Rogues " "leap out of the brush to guard their stolen loot." msgstr "" "Du kommer over en skatt fra oldtiden. Idet du strekker deg fram for Ã¥ ta " "den, hopper en gjeng banditter ut fra buskene for Ã¥ beskytte det som " "antakeligvis er tyvegodset deres." msgid "" "Through a clearing you observe an ancient artifact. Unfortunately, it's " "guarded by a nearby %{monster}. Do you want to fight the %{monster} for the " "artifact?" msgstr "" "Gjennom en klarning fÃ¥r du øye pÃ¥ en verdifull gjenstand fra oldtiden. " "Dessverre er den bevoktet av 1 %{monster} like ved.\n" "Ønsker du Ã¥ kjempe mot 1 %{monster} for den verdifulle gjenstanden?" msgid "Victorious, you take your prize, the %{art}." msgstr "" "Seirende griper du premien din, som er den verdifulle gjenstanden:\n" "%{art}." msgid "" "Discretion is the better part of valor, and you decide to avoid this fight " "for today." msgstr "" "Du velger varsomhet framfor dumdristighet og unngÃ¥r denne kampen for i dag." msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold pieces." msgstr "" "Etter Ã¥ ha strevd i timesvis med Ã¥ fiske opp kista fra havet, Ã¥pner du den " "og finner %{gold} gullstykker." msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold and the %{art}." msgstr "" "Etter Ã¥ ha strevd i timesvis med Ã¥ fiske opp kista fra havet, Ã¥pner du den " "og finner %{gold} gullstykker og skatten: %{art}." msgid "" "After spending hours trying to fish the chest out of the sea, you open it, " "only to find it empty." msgstr "" "Etter Ã¥ ha strevd i timesvis med Ã¥ fiske opp kista fra havet, Ã¥pner du den " "kun for Ã¥ finne ut at den er tom." msgid "" "After scouring the area, you fall upon a hidden treasure cache. You may take " "the gold or distribute the gold to the peasants for experience. Do you wish " "to keep the gold?" msgstr "" "Etter Ã¥ ha gjennomsøkt omrÃ¥det kommer du over ei bortgjemt skattekiste. Du " "kan enten ta gullet selv, eller dele det ut til fattige bønder imot " "erfaringspoeng.\n" "Vil du beholde gullet?" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "%{gold} gold pieces." msgstr "" "Etter Ã¥ ha gjennomsøkt omrÃ¥det kommer du over ei bortgjemt skattekiste som " "inneholder %{gold} gullstykker." msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "ancient artifact %{art}." msgstr "" "Etter Ã¥ ha gjennomsøkt omrÃ¥det kommer du over ei bortgjemt skattekiste. Den " "inneholder en verdifull gjenstand fra oldtiden:\n" "%{art}." msgid "" "You stumble upon a dented and tarnished lamp lodged deep in the earth. Do " "you wish to rub the lamp?" msgstr "" "Du snubler over en bulkete og tilskitnet lampe gravd dypt ned i bakken.\n" "Vil du gni pÃ¥ den?" msgid "" "You have taken control of the local Alchemist shop. It will provide you with " "%{count} unit of Mercury per day." msgstr "" "Du tar kontroll over den lokale alkymistforretningen. Den vil gi deg " "%{count} enhet kvikksølv hver dag." msgid "" "You gain control of a sawmill. It will provide you with %{count} units of " "wood per day." msgstr "" "Du tar kontroll over et sagbruk. Det vil gi deg %{count} enheter ved hver " "dag." msgid "" "You gain control of an ore mine. It will provide you with %{count} units of " "ore per day." msgstr "" "Du tar kontroll over ei malmgruve. Den vil gi deg %{count} enheter malm hver " "dag." msgid "" "You gain control of a sulfur mine. It will provide you with %{count} unit of " "sulfur per day." msgstr "" "Du tar kontroll over ei svovelgruve. Den vil gi deg %{count} enhet svovel " "hver dag." msgid "" "You gain control of a crystal mine. It will provide you with %{count} unit " "of crystal per day." msgstr "" "Du tar kontroll over ei krystallgruve. Den vil gi deg %{count} enhet " "krystall hver dag." msgid "" "You gain control of a gem mine. It will provide you with %{count} unit of " "gems per day." msgstr "" "Du tar kontroll over ei edelstensgruve. Den vil gi deg %{count} enhet " "edelsten hver dag." msgid "" "You gain control of a gold mine. It will provide you with %{count} gold per " "day." msgstr "" "Du tar kontroll over ei gullgruve. Den vil gi deg %{count} gullstykker hver " "dag." msgid "" "The lighthouse is now under your control, and all of your ships will now " "move further each day." msgstr "" "FyrtÃ¥rnet er herved under din kontroll og alle skipene dine vil fra nÃ¥ av " "kunne flytte seg lengre hver dag." msgid "You gain control of a %{name}." msgstr "Du tar kontroll over en %{name}." msgid "" "You come upon an abandoned gold mine. The mine appears to be haunted. Do you " "wish to enter?" msgstr "" "Du kommer over ei forlatt gullgruve. Gruva ser ut til Ã¥ være hjemsøkt.\n" "Vil du gÃ¥ inn?" msgid "You beat the Ghosts and are able to restore the mine to production." msgstr "Du fordriver gjenferdene og fÃ¥r satt gruva tilbake i drift." msgid "" "A group of %{monster} with a desire for greater glory wish to join you. Do " "you accept?" msgstr "" "En gruppe %{monster} med en drøm om heder og ære ønsker Ã¥ bli med deg.\n" "Godtar du?" msgid "As you approach the dwelling, you notice that there is no one here." msgstr "" "Idet du kommer nærmere bostedet, legger du merke til at det er forlatt." msgid "" "You search the ruins, but the Medusas that used to live here are gone. " "Perhaps there will be more next week." msgstr "" "Du leter gjennom ruinene, men medusaene som pleide Ã¥ leve her ikke er her " "lenger. Kanskje det kommer flere neste uke." msgid "" "You've found some Medusas living in the ruins. They are willing to join your " "army for a price. Do you want to recruit Medusas?" msgstr "" "Du møter pÃ¥ noen medusaer som bor i ruinene. De er villige til Ã¥ bli med i " "hæren din for en penge.\n" "Ønsker du Ã¥ verve medusaer?" msgid "" "You've found a Sprite Tree City. Unfortunately, none of the Sprites living " "there wish to join an army. Maybe next week." msgstr "" "Du fant en treby for feer. Dessverre vil ingen av feene som bor der bli med " "i hæren din. Kanskje neste uke." msgid "" "Some of the Sprites living in the tree city are willing to join your army " "for a price. Do you want to recruit Sprites?" msgstr "" "Noen av feene som bor i trebyen er villige til Ã¥ bli med i hæren din for en " "penge. Ønsker du Ã¥ verve feer?" msgid "" "A colorful Rogues' wagon stands empty here. Perhaps more Rogues will be here " "later." msgstr "" "Det stÃ¥r en fargerik men tom røverkjerre her. Kanskje det vil komme røvere " "hit senere." msgid "" "Distant sounds of music and laughter draw you to a colorful wagon housing " "Rogues. Do you wish to have any Rogues join your army?" msgstr "" "Musikk og latter i det fjerne lokker deg til en fargerik kjerre der det " "viser seg at røvere holder til.\n" "Vil du Ã¥ ha røvere i hæren din?" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. The " "tents are unoccupied. Perhaps more Nomads will be here later." msgstr "" "En samling utslitte telt som bølger i den sandete vinden kaller deg mot seg. " "Teltene er tomme. Kanskje flere nomader kommer hit senere." msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. Do you " "wish to have any Nomads join you during your travels?" msgstr "" "En samling utslitte telt bølger i ørkenvinden og lokker deg til seg.\n" "Ønsker du Ã¥ ta med deg noen nomader pÃ¥ ferden din?" msgid "The pit of mud bubbles for a minute and then lies still." msgstr "Pytten bobler litt i et minutt før den stilner til." msgid "" "As you approach the bubbling pit of mud, creatures begin to climb out and " "position themselves around it. In unison they say: \"Mother Earth would like " "to offer you a few of her troops. Do you want to recruit Earth Elementals?\"" msgstr "" "Idet du kommer nærmere lavapytten begynner skapninger Ã¥ krype ut og stille " "seg opp rundt den. I ett sier de: \"Moderjord ønsker Ã¥ tilby deg noen av " "sine krigere. Ønsker du Ã¥ verve jordÃ¥nder?\"" msgid "You enter the structure of white stone pillars, and find nothing." msgstr "" "Du gÃ¥r inn i bygningen med de hvite steinsøylene, men du finner ingenting." msgid "" "White stone pillars support a roof that rises up to the sky. As you enter " "the structure, the dead air of the outside gives way to a whirling gust that " "almost pushes you back out. The air current materializes into a barely " "visible form. The creature asks, in what can only be described as a loud " "whisper: \"Why have you come? Are you here to call upon the forces of the " "air?\"" msgstr "" "Hvite steinsøyler støtter opp under et tak som gÃ¥r høyt opp mot himmelen. " "Idet du gÃ¥r inn blir den stille luften pÃ¥ utsiden brÃ¥tt om til et virvlende " "vindkast som nesten slenger deg ut igjen. Luftstrømmen forvandler seg til en " "nesten gjennomsiktig skikkelse. Den spør, med en stemme som bare kan " "beskrives som høy visking: \"Hva bringer deg hit? Er du her for Ã¥ tilkalle " "luftens krefter?\"" msgid "No Fire Elementals approach you from the lava pool." msgstr "Ingen av ildÃ¥ndene kommer imot deg fra lavadammen." msgid "" "Beneath a structure that serves to hold in heat, Fire Elementals move about " "in a fiery pool of molten lava. A group of them approach you and offer their " "services. Would you like to recruit Fire Elementals?" msgstr "" "Under et varmefangende byggverk beveger ildÃ¥nder seg i en ildfull dam av " "lava. En gruppe kommer bort til deg og tilbyr deg Ã¥ bli med i hæren din. \n" "Ønsker du Ã¥ verve ildÃ¥nder?" msgid "A face forms in the water for a moment, and then is gone." msgstr "" "For et kort øyeblikk dukker et ansikt opp av vannet, før det sÃ¥ forsvinner." msgid "" "Crystalline structures cast shadows over a small reflective pool of water. " "You peer into the pool, and a face that is not your own peers back. It asks: " "\"Would you like to call upon the powers of water?\"" msgstr "" "Mektige krystaller kaster skygger over en liten blank vanndam. Du stirrer " "ned pÃ¥ vannspeilet og plutselig stirrer et annet ansikt tilbake pÃ¥ deg.\n" "Det spør deg: \"Ønsker du Ã¥ tilkalle vannets krefter?\"" msgid "This burial site is deathly still." msgstr "Gravstedet er dødsens rolig." msgid "" "Restless spirits of long dead warriors seeking their final resting place " "offer to join you in hopes of finding peace. Do you wish to recruit ghosts?" msgstr "" "De urolige sjelene til for lengst døde krigere søker deres endelige " "hvilested, og de tilbyr deg Ã¥ bli med i hæren din i hÃ¥p om Ã¥ endelig kunne " "finne fred.\n" "Vil du verve gjenferd?" msgid "" "The City of the Dead is empty of life, and empty of unlife as well. Perhaps " "some undead will move in next week." msgstr "" "Dødens by er helt uten liv, og helt uten uliv ogsÃ¥. Kanskje noen vandøde vil " "flytte inn neste uke." msgid "" "Some Liches living here are willing to join your army for a price. Do you " "want to recruit Liches?" msgstr "" "Noen heksekonger som holder til her er villige til Ã¥ bli med i hæren din " "imot en pris. Ønsker du Ã¥ verve heksekonger?" msgid "" "You've found the ruins of an ancient city, now inhabited solely by the " "undead. Will you search?" msgstr "" "Du har funnet ruinene til en by fra oldtiden. Den er nÃ¥ kun bebodd av " "vandøde.\n" "Vil du lete gjennom den?" msgid "" "Some of the surviving Liches are impressed by your victory over their " "fellows, and offer to join you for a price. Do you want to recruit Liches?" msgstr "" "Noen av de overlevende heksekongene ble imponert over seieren din mot brødre " "deres, og de tilbyr Ã¥ bli med deg mot et beløp. Ønsker du Ã¥ verve " "heksekonger?" msgid "" "You've found one of those bridges that Trolls are so fond of living under, " "but there are none here. Perhaps there will be some next week." msgstr "" "Du kommer over typisk bru som troll liker Ã¥ bosette seg under, men stedet er " "forlatt. Kanskje det kommer noen neste uke." msgid "" "Some Trolls living under a bridge are willing to join your army, but for a " "price. Do you want to recruit Trolls?" msgstr "" "Noen troll som bor under ei bru er villige til Ã¥ bli med i hæren din, men " "imot et beløp. Ønsker du Ã¥ verve troll?" msgid "Trolls living under the bridge challenge you. Will you fight them?" msgstr "" "Trollene som bor under brua utfordrer deg til kamp. Vil du kjempe mot dem?" msgid "" "A few Trolls remain, cowering under the bridge. They approach you and offer " "to join your forces as mercenaries. Do you want to buy any Trolls?" msgstr "" "Noen fÃ¥ livredde troll som er igjen gjemmer seg under brua. De kommer " "forsiktig ut og tilbyr deg Ã¥ bli med i styrken din som leiesoldater.\n" "Ønsker du Ã¥ kjøpe troll?" msgid "" "The Dragon city has no Dragons willing to join you this week. Perhaps a " "Dragon will become available next week." msgstr "" "Dragenes landsby har ingen drager som er villige til Ã¥ bli med deg denne " "uka. Kanskje det kommer flere drager neste uke." msgid "" "The Dragon city is willing to offer some Dragons for your army for a price. " "Do you wish to recruit Dragons?" msgstr "" "Dragenes landsby er villig til Ã¥ tilby noen drager for hæren din imot en " "pris. Ønsker du Ã¥ verve drager?" msgid "" "You stand before the Dragon City, a place off-limits to mere humans. Do you " "wish to violate this rule and challenge the Dragons to a fight?" msgstr "" "Du stÃ¥r overfor Dragenes landsby, et forbudt sted for vanlige mennesker.\n" "Tør du Ã¥ trosse denne regelen og utfordre dragene til kamp?" msgid "" "Having defeated the Dragon champions, the city's leaders agree to supply " "some Dragons to your army for a price. Do you wish to recruit Dragons?" msgstr "" "NÃ¥ som du har slÃ¥tt mestrene av dragene, gÃ¥r byens ledere med pÃ¥ Ã¥ tilby deg " "noen drager til hæren din imot en penge. Ønsker du Ã¥ verve drager?" msgid "From the observation tower, you are able to see distant lands." msgstr "Du kan se fjerntliggende land fra utkikkstÃ¥rnet." msgid "" "The spring only refills once a week, and someone's already been here this " "week." msgstr "" "Vannkilden fÃ¥r kun pÃ¥fyll én gang i uka, og noen har allerede vært her denne " "uka." msgid "" "A drink at the spring is supposed to give you twice your normal spell " "points, but you are already at that level." msgstr "" "En slurk fra kilden skal egentlig gi deg dobbelt sÃ¥ mye mana, men du har det " "allerede." msgid "" "A drink from the spring fills your blood with magic! You have twice your " "normal spell points in reserve." msgstr "" "En slurk fra kilden fÃ¥r blodet ditt til Ã¥ strømme av magi! Du har nÃ¥ dobbelt " "sÃ¥ mye mana som du har til vanlig." msgid "" "Recognizing you, the butler refuses to admit you. \"The master,\" he says, " "\"will not see the same student twice.\"" msgstr "" "Etter Ã¥ ha gjenkjent deg nekter hovmesteren Ã¥ slippe deg inn. \"Herren,\" " "sier han, \"ønsker ikke Ã¥ møte den samme eleven to ganger.\"" msgid "" "The butler admits you to see the master of the house. He trains you in the " "four skills a hero should know." msgstr "" "Hovmesteren slipper deg inn for Ã¥ møte herren av huset. Han trener deg opp i " "de fire egenskapene en hver helt burde ha." msgid "" "The butler opens the door and looks you up and down. \"You are neither " "famous nor diplomatic enough to be admitted to see my master,\" he sniffs. " "\"Come back when you think yourself worthy.\"" msgstr "" "Hovmesteren Ã¥pner døra og skuer deg opp og ned. \"Du er verken berømt eller " "veltalende nok til Ã¥ fÃ¥ tillatelse til Ã¥ møte herren min,\" fnyser han. " "\"Kom tilbake nÃ¥r du er verdig.\"" msgid " and " msgstr " og " msgid "" "All of the %{monsters} you have in your army have been trained by the battle " "masters of the fort. Your army now contains %{monsters2}." msgstr "" "Alt du hadde av %{monsters} i hæren din har blitt trent opp av fortets " "kampmestre. Disse har nÃ¥ blitt til %{monsters2}." msgid "" "An unusual alliance of Ogres, Orcs, and Dwarves offer to train (upgrade) any " "such troops brought to them. Unfortunately, you have none with you." msgstr "" "En usedvanlig allianse av orker, jetter og dverger tilbyr Ã¥ trene opp " "(oppgradere) det du har av samme krigere som dem. Dessverre har du ingen " "slike i hæren din." msgid "All of your %{monsters} have been upgraded into %{monsters2}." msgstr "Alle dine %{monsters} har blitt oppgradert til %{monsters2}." msgid "" "A blacksmith working at the foundry offers to convert all Pikemen and " "Swordsmen's weapons brought to him from iron to steel. He also says that he " "knows a process that will convert Iron Golems into Steel Golems. " "Unfortunately, you have none of these troops in your army, so he can't help " "you." msgstr "" "Smeden som driver støperiet tilbyr deg Ã¥ gjøre om alle jernvÃ¥pnene til " "pikenérene og sverdmennene dine til stÃ¥l. Han sier at han ogsÃ¥ vet hvordan " "man kan gjøre jerngolemer om til stÃ¥lgolemer. Dessverre har du ingen slike i " "hæren din, sÃ¥ det er ingenting han kan gjøre for deg." msgid "" "The captain looks at you with surprise and says:\n" "\"You already have all the maps I know about. Let me fish in peace now.\"" msgstr "" "Kapteinen ser overrasket pÃ¥ deg og sier:\n" "\"Du har allerede fÃ¥tt det eneste kartet jeg hadde. La meg fÃ¥ fiske i fred " "nÃ¥.\"" msgid "" "A retired captain living on this refurbished fishing platform offers to sell " "you maps of the sea he made in his younger days for 1,000 gold. Do you wish " "to buy the maps?" msgstr "" "Kapteinen som bor pÃ¥ denne velkonstruerte fiskeflÃ¥ta tilbyr deg et komplett " "kart over havet fra hans yngre dager. Han tilbyr deg det for 1000 " "gullstykker.\n" "Vil du kjøpe kartet av ham?" msgid "" "The captain sighs. \"You don't have enough money, eh? You can't expect me " "to give my maps away for free!\"" msgstr "" "Kapteinen sukker. \"Du har ikke nok penger, hva? Du kan ikke forvente at jeg " "skal gi fra meg kartet gratis!\"" msgid "" "You come upon an obelisk made from a type of stone you have never seen " "before. Staring at it intensely, the smooth surface suddenly changes to an " "inscription. The inscription is a piece of a lost ancient map. Quickly you " "copy down the piece and the inscription vanishes as abruptly as it appeared." msgstr "" "Du kommer over en obelisk laget av en bergart du aldri før har sett. Mens du " "skuer over den dannes det plutselig et innriss pÃ¥ dens tidligere glatte " "overflate. Innrisset viser et stykke av et eldgammelt, fortapt kart. Du " "skynder deg Ã¥ skissere ned kartet og rekker sÃ¥ vidt Ã¥ bli ferdig før det " "forsvinner like brÃ¥tt som det dukket opp." msgid "You have already been to this obelisk." msgstr "Du har allerede besøkt denne obelisken." msgid "" "Upon your approach, the tree opens its eyes in delight. \"It is good to see " "you, my student. I hope my teachings have helped you.\"" msgstr "" "Du kommer til et tre som Ã¥pner et par øyne i glede idet det ser deg komme " "nærmere. \"Det er godt Ã¥ se deg, min elev. Jeg hÃ¥per lærdommen min har " "hjulpet deg.\"" msgid "" "Upon your approach, the tree opens its eyes in delight. \"Ahh, an " "adventurer! Allow me to teach you a little of what I have learned over the " "ages.\"" msgstr "" "Du kommer til et tre som Ã¥pner et par øyne i glede idet det ser deg komme " "nærmere. \"Oi, en eventyrer! Tillat meg Ã¥ lære deg litt av hva jeg har lært " "gjennom tidene.\"" msgid "Upon your approach, the tree opens its eyes in delight." msgstr "" "Du kommer til et tre som Ã¥pner et par øyne i glede idet det ser deg komme " "nærmere." msgid "" "\"Ahh, an adventurer! I will be happy to teach you a little of what I have " "learned over the ages for a mere %{count} %{res}.\"" msgstr "" "\"Oi, en eventyrer! Jeg skal gledelig lære deg litt av hva jeg har lært opp " "gjennom tidene for bare %{count} %{res}.\"" msgid "(Just bury it around my roots.)" msgstr "(Bare grav det ned ved røttene mine)" msgid "Tears brim in the eyes of the tree." msgstr "TÃ¥rer ligger i øyekroken til treet." msgid "\"I need %{count} %{res}.\"" msgstr "\"Jeg trenger %{count} %{res}.\"" msgid "it whispers. (sniff) \"Well, come back when you can pay me.\"" msgstr "" "visker det mens det snufser, \"Vel, kom tilbake nÃ¥r du kan betale meg.\"" msgid "" "Nestled among the trees sits a blind seer. After you explain the intent of " "your journey, the seer activates his crystal ball, allowing you to see the " "strengths and weaknesses of your opponents." msgstr "" "En blind spÃ¥mann har slÃ¥tt seg til rede i et tre du oppdager ham i. Etter Ã¥ " "ha forklart ham hensikten med reisen din, tar spÃ¥mannen fram krystallkula " "si. Den lar deg se styrkene og svakhetene til motstanderne dine." msgid "" "The entrance to the cave is dark, and a foul, sulfurous smell issues from " "the cave mouth. Will you enter?" msgstr "" "Inngangen til hula er bekmørk, og en fæl eggete lukt oser ut av munningen " "til den.\n" "Vil du gÃ¥ inn?" msgid "Except for evidence of a terrible battle, the cave is empty." msgstr "Sett bort ifra sporene etter et fryktelig slag er hula tom." msgid "" "You find a powerful and grotesque Demon in the cave. \"Today,\" it rasps, " "\"you will fight and surely die. But I will give you a choice of deaths. You " "may fight me, or you may fight my servants. Do you prefer to fight my " "servants?\"" msgstr "" "Du finner en sterk og ufyselig djevel i hula. Med en skjærende stemme sier " "den: \"I dag,\" og den trekker pusten, \"skal du kjempe, og du vil uten tvil " "dø. Men, jeg skal la deg fÃ¥ velge hvordan du vil dø. Du kan enten kjempe mot " "meg, eller mot lakeiene mine.\n" "Blir det lakeiene?" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points." msgstr "" "Djevelen gir fra seg et vræl og angriper! Etter en kort og stusslig kamp, " "tar du livet av beistet. Du fÃ¥r %{exp} erfaringspoeng." msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points and " "%{count} gold." msgstr "" "Djevel skriker i været og angriper! Etter en kort og stusslig kamp dreper du " "monsteret. Du fÃ¥r %{exp} erfaringspoeng og %{count} gullstykker." msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and find the %{art} in the back of the cave." msgstr "" "Djevel skriker i været og angriper! Etter en kort og stusslig kamp dreper du " "monsteret. Du finner %{art} i enden av hula." msgid "" "Seeing that you do not have %{count} gold, the demon slashes you with its " "claws, and the last thing you see is a red haze." msgstr "" "Etter Ã¥ ha innsett at du ikke eier %{count} gullstykker hugger djevelen til " "deg med klørene sine, og det siste du ser er en rød tÃ¥ke." msgid "" "The Demon leaps upon you and has its claws at your throat before you can " "even draw your sword. \"Your life is mine,\" it says. \"I will sell it back " "to you for %{count} gold.\"" msgstr "" "Djevelen hopper pÃ¥ deg og har klørne sine pÃ¥ strupen din før du engang " "rekker Ã¥ dra sverdet ditt. \"Livet ditt ligger i mine klør nÃ¥,\" sier den. " "\"Du skal fÃ¥ beholde det imot %{count} gullstykker.\"" msgid "" "Upon defeating the daemon's servants, you find a hidden cache with %{count} " "gold." msgstr "" "Etter Ã¥ ha slÃ¥tt lakeiene til djevelen finner du et skjult lager med " "%{count} gullstykker." msgid "" "As you enter the Alchemist's Tower, a hobbled, graying man in a brown cloak " "makes his way towards you." msgstr "" "Idet du kommer inn i alkymistens tÃ¥rn kommer en haltende grÃ¥ mann med brun " "kappe mot deg." msgid "He checks your pack, and sees that you have 1 cursed item." msgid_plural "" "He checks your pack, and sees that you have %{count} cursed items." msgstr[0] "Han sjekker sekken din og ser at du har 1 forhekset gjenstand." msgstr[1] "" "Han sjekker sekken din og ser at du har %{count} forheksede gjenstander." msgid "For %{gold} gold, the alchemist will remove it for you. Do you pay?" msgid_plural "" "For %{gold} gold, the alchemist will remove them for you. Do you pay?" msgstr[0] "" "For %{gold} gullstykker tilbyr alkymisten Ã¥ fjerne den for deg. Vil du " "betale?" msgstr[1] "" "For %{gold} gullstykker tilbyr alkymisten Ã¥ fjerne dem for deg. Vil du " "betale?" msgid "" "After you consent to pay the requested amount of gold, the alchemist grabs " "the cursed artifact and throws it into his magical cauldron." msgid_plural "" "After you consent to pay the requested amount of gold, the alchemist grabs " "all cursed artifacts and throws them into his magical cauldron." msgstr[0] "" "SÃ¥ snart du gÃ¥r med pÃ¥ Ã¥ betale den forespurte mengden gull, tar alkymisten " "tak i den forbannede gjenstanden din og hiver den i den magiske gryta si." msgstr[1] "" "SÃ¥ snart du gÃ¥r med pÃ¥ Ã¥ betale den forespurte mengden gull tar alkymisten " "tak i alle de forbannede gjenstandene dine og kaster dem i den magiske gryta " "si." msgid "" "You hear a voice from behind the locked door, \"You don't have enough gold " "to pay for my services.\"" msgstr "" "Du hører en stemme fra de lukkede dørene, \"Du har ikke nok gull til Ã¥ " "betale for tjenestene mine.\"" msgid "" "You hear a voice from high above in the tower, \"Go away! I can't help you!\"" msgstr "" "Du hører en stemme fra høyt oppe i tÃ¥rnet: \"GÃ¥ vekk! Jeg kan ikke hjelpe " "deg!\"" msgid "" "The head groom speaks to you, \"That is a fine looking horse you have. I am " "afraid we can give you no better, but the horses your cavalry are riding " "look to be of poor breeding stock. We have many trained war horses which " "would aid your riders greatly. I insist you take them.\"" msgstr "" "Stallmesteren kommer mot deg og sier, \"Det er en flott hest du har. Jeg er " "redd for at jeg ikke kan gi deg noe bedre, men hestene rytterne dine rir pÃ¥ " "ser ut til Ã¥ være av en dÃ¥rlig avl. Vi har mange trente stridshester som vil " "hjelpe rytterne dine enormt. Jeg insisterer pÃ¥ at du tar dem.\"" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, he will " "grow tired in a week. You must also let me give better horses to your " "mounted soldiers, their horses look shoddy and weak.\"" msgstr "" "Idet du ankommer stallen dukker stallmesteren opp, og han har med seg en " "flott stridshest. \"Denne hingsten vil fÃ¥ fart pÃ¥ deg i reisene dine. " "Dessverre vil hesten bli utmattet om ei uke. Du mÃ¥ ogsÃ¥ la meg gi deg bedre " "hester til dine ridende soldater. Hestene deres ser slitne og svake ut." msgid "" "The head groom approaches you and speaks, \"You already have a fine horse, " "and have no inexperienced cavalry which might make use of our trained war " "horses.\"" msgstr "" "Stallmesteren kommer mot deg og sier, \"Du har allerede en fin hest og har " "ingen uerfarne ryttere som kan dra nytte av vÃ¥re trente stridshester.\"" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, his " "endurance will wane with a lot of heavy riding, and you must return for a " "fresh mount in a week. We also have many fine war horses which could benefit " "mounted soldiers, but you have none we can help.\"" msgstr "" "Idet du ankommer stallen dukker stallmesteren opp med en flott stridshest. " "\"Denne hingsten vil bringe deg fort fram pÃ¥ reisene dine. Dessverre vil den " "etter hvert bli utslitt. Om ei uke kan du komme tilbake og fÃ¥ en ny hest. Vi " "har ogsÃ¥ mange flotte stridshester for ridende krigere, men det har du ingen " "av i hæren din.\"" msgid "The Arena guards turn you away." msgstr "Vokterne av arenaen avviser deg." msgid "" "You have your crew stop up their ears with wax before the sirens' eerie song " "has any chance of luring them to a watery grave." msgstr "" "Du fÃ¥r mannskapet ditt til Ã¥ tette ørene sine med voks før sirenenes " "uhyggelige sang fÃ¥r muligheten til Ã¥ lokke dem ut til en vÃ¥t grav." msgid "" "As the sirens sing their eerie song, your small, determined army manages to " "overcome the urge to dive headlong into the sea." msgstr "" "Mens sirenene synger deres uhyggelige sang, klarer din lille men mÃ¥lrettede " "hær Ã¥ motstÃ¥ fristelsen til Ã¥ stupe med hodet først i sjøen." msgid "" "An eerie wailing song emanates from the sirens perched upon the rocks. Many " "of your crew fall under its spell, and dive into the water where they drown. " "You are now wiser for the visit, and gain %{exp} experience." msgstr "" "En ulende og nifs sang kommer fra sirenene pÃ¥ svaberget. Flere av mannskapet " "ditt blir offer for forhekselsen deres og stuper ut i sjøen hvor de drukner. " "Du kommer helberget ut av hendelsen med ny lærdom og %{exp} erfaringspoeng." msgid "" "In a dazzling display of daring, you break into the local jail and free the " "hero imprisoned there, who, in return, pledges loyalty to your cause." msgstr "" "I en imponerende framvisning av mot bryter du deg inn i det lokale fengselet " "og frigjør den innesperrede helten der. Til gjengjeld sverger helten troskap " "til din sak." msgid "" "You already have %{count} heroes, and regretfully must leave the prisoner in " "this jail to languish in agony for untold days." msgstr "" "Du har allerede %{count} helter, og du blir nødt til Ã¥ forlate fangen i " "fengselet for Ã¥ tæres av smertende lengsel i utallige dager." # fjernt og nært? # magician ? Betyr ofte tryllekunstner pÃ¥ engelsk. msgid "" "You enter a rickety hut and talk to the magician who lives there. He tells " "you of places near and far which may aid you in your journeys." msgstr "" "Du gÃ¥r inn i ei skrøpelig hytte og snakker med spÃ¥mannen som bor der. Han " "forteller deg om steder fjerne og nære. Dette vil kunne komme til nytte pÃ¥ " "reisene dine." msgid "This eye seems to be intently studying its surroundings." msgstr "Dette øyet ser ut til Ã¥ følge oppmerksomt med pÃ¥ omgivelsene sine." msgid "You come across a giant Sphinx. The Sphinx remains strangely quiet." msgstr "Du kommer over en stor sfinks. Sfinksen forblir merkverdig nok stille." msgid "" "\"I have a riddle for you,\" the Sphinx says. \"Answer correctly, and you " "shall be rewarded. Answer incorrectly, and you shall be eaten. Do you accept " "the challenge?\"" msgstr "" "\"Jeg har en gÃ¥te til deg,\" sier sfinksen. \"Svar riktig, og du vil bli " "belønnet. Svar galt, og du vil bli spist levende. Tar du imot utfordringen?\"" msgid "" "The Sphinx asks you the following riddle:\n" "\n" "'%{riddle}'\n" "\n" "Your answer?" msgstr "" "Sfinksen stiller deg den følgende gÃ¥ten :\n" "\n" "\"%{riddle}\n" "\n" "Ditt svar?" msgid "" "\"You guessed incorrectly,\" the Sphinx says, smiling. The Sphinx swipes at " "you with a paw, knocking you to the ground. Another blow makes the world go " "black, and you know no more." msgstr "" "\"Du gjettet feil\", sier sfinksen med et lurt smil før den denger til deg " "med en av potene sine slik at du gÃ¥r rett i bakken. Nok et slag treffer deg, " "og det svartner. Mer fÃ¥r du ikke med deg." msgid "" "Looking somewhat disappointed, the Sphinx sighs. \"You've answered my riddle " "so here's your reward. Now begone.\"" msgstr "" "Tilsynelatende litt skuffet, sukker Sfinksen: \"Du klarte Ã¥ løse gÃ¥ten min, " "sÃ¥ her er belønningen din. NÃ¥ forsvinn.\"." msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "As you speak the magic word, the glowing barrier dissolves into nothingness." msgstr "" "En magisk barriere rager foran deg og blokkerer veien. Runene pÃ¥ buen dens " "sier det følgende:\n" "\"Uttal nøkkelordet og du vil fÃ¥ komme forbi.\"\n" "Idet du roper ut det magiske ordet gÃ¥r den glødende barrieren i oppløsning." msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "You speak, and nothing happens." msgstr "" "En magisk barriere rager foran deg og blokkerer veien. Runene pÃ¥ buen dens " "sier,\n" "\"Uttal nøkkelordet og du vil fÃ¥ komme forbi.\"\n" "Du taler, men ingenting skjer." msgid "" "You enter the tent and see an old woman gazing into a magic gem. She looks " "up and says,\n" "\"In my travels, I have learned much in the way of arcane magic. A great " "oracle taught me his skill. I have the answer you seek.\"" msgstr "" "Du gÃ¥r inn i teltet og ser en gammel dame som stirrer inn i en magisk " "edelsten. Hun ser opp og sier:\n" "\"PÃ¥ vandringene mine har jeg lært mye om mørkets kunster. Jeg har gÃ¥tt i " "læren hos et dyktig orakel. Du skal fÃ¥ svaret du søker.\"" msgid "No spell book is present." msgstr "Det fins ingen tryllebok." msgid "" "This spell costs %{mana} spell points. You have no spell points, so you " "cannot cast it." msgstr "" "Denne trolldommen koster %{mana} magipoeng. Du har null magipoeng, sÃ¥ du kan " "ikke bruke denne trolldommen." msgid "" "This spell costs %{mana} spell points. You only have %{point} spell points, " "so you cannot cast it." msgstr "" "Denne trolldommen koster %{mana} magipoeng. Du har kun %{point} magipoeng, " "sÃ¥ du kan ikke bruke denne trolldommen." msgid "The spell was not found." msgstr "Trolldommen ble ikke funnet." msgid "Only heroes can cast this spell." msgstr "Kun helter kan bruke denne trolldommen." msgid "This hero is not able to cast adventure spells." msgstr "Denne helten er ikke i stand til Ã¥ bruke kartmagi." msgid "Your hero is too tired to cast this spell today. Try again tomorrow." msgstr "" "Din helt er for sliten til Ã¥ bruke denne trolldommen i dag. Prøv igjen i " "morgen." msgid "This spell cannot be cast on a boat." msgstr "Denne trolldommen kan ikke brukes pÃ¥ et skip." msgid "This spell can only be cast near an ocean." msgstr "Denne trolldommen kan kun bli brukt i nærheten av sjø." msgid "" "There are no boats available and no ocean adjacent to the hero where this " "spell will work." msgstr "" "Det er ingen skip tilgjengelige eller sjø i nærheten av helten hvor denne " "trolldommen vil virke." msgid "There are no boats available for this spell." msgstr "Ingen skip er tilgjengelige for denne trolldommen." msgid "There is no ocean adjacent to the hero where this spell will work." msgstr "" "Det er ingen sjø i umiddelbar nærhet av helten hvor denne trolldommen kan " "brukes." #, fuzzy msgid "" "You do not own any town or castle that is not currently occupied by a hero. " "This spell will have no effect." msgstr "" "Du eier ingen byer eller borger som ikke har besøk av andre helter for " "øyeblikket. Du kan ikke bruke denne trolldommen." #, fuzzy msgid "This hero is already in a town, so this spell will have no effect." msgstr "" "Denne helten befinner seg allerede i en by, og dermed kan ikke denne " "trolldommen brukes." msgid "" "The nearest town is %{town}.\n" "\n" "This town is occupied by your hero %{hero}." msgstr "" "Den nærmeste byen er %{town}.\n" "\n" "Denne byen er blokkert av helten din %{hero}." msgid "This spell is already in effect." msgstr "Denne trolldommen er allerede i virkning." msgid "" "No opponent neither has nor can have any hero under their command anymore. " "Casting this spell will have no effect." msgstr "" "Ingen av motstanderne dine har eller vil kunne ha helter under sitt befal " "lenger, og dermed vil ikke denne trolldommen ha noen virkning." msgid "" "No opponent has a hero under their command at this time. Casting this spell " "will have no effect." msgstr "" "Ingen av motstanderne dine har helter under sitt befal for øyeblikket, og " "dermed vil ikke denne trolldommen ha noen virkning." msgid "" "You must be within %{count} spaces of a monster for the Visions spell to " "work." msgstr "" "Du mÃ¥ være innenfor %{count} ruter av et monster for at trolldommen 'Syner' " "skal virke." msgid "" "You must be standing on the entrance to a mine (sawmills and alchemist labs " "do not count) to cast this spell." msgstr "" "Du mÃ¥ stÃ¥ ved inngangen til en gruve (sagbruk og alkymistlaber telles ikke) " "for Ã¥ bruke denne trolldommen." msgid "You must first defeat the ghosts guarding the mine to cast this spell." msgstr "" "Du mÃ¥ først fordrive spøkelsene som vokter over gruven for Ã¥ bruke denne " "trolldommen." msgid "" "There are already at least as many elementals guarding the mine as this hero " "can generate. Casting this spell will have no effect." msgstr "" "Det befinner seg alt minst like mange elementærÃ¥nder som denne helten kan " "tilkalle her. Ã… bruke denne trolldommen vil være uten nytte." msgid "%{name} the %{race} (Level %{level})" msgstr "%{race}en %{name} (level %{level})" #, fuzzy msgid "" "'Spread' combat formation spreads the hero's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "\"Spredd\" kampoppstilling sprer styrkene dine fra topp til bunn av " "slagmarken med minst ett ledig felt mellom hver enkelt tropp." #, fuzzy msgid "" "'Grouped' combat formation bunches the hero's army together in the center of " "their side of the battlefield." msgstr "" "\"Gruppert\" kampoppstilling setter styrkene dine tett sammen i midten av " "din side av slagmarken." msgid "Are you sure you want to dismiss this Hero?" msgstr "Er du sikker pÃ¥ at du ønsker Ã¥ bortvise denne helten?" msgid "View Experience Info" msgstr "Vis erfaringsinfo" msgid "View Spell Points Info" msgstr "Vis info om mana" msgid "Set army combat formation to 'Spread'" msgstr "Sett hærens kampoppstilling til \"Spredd\"" msgid "Set army combat formation to 'Grouped'" msgstr "Sett hærens kampoppstilling til \"Gruppert\"" msgid "Exit Hero Screen" msgstr "GÃ¥ ut av heltevinduet" msgid "You cannot dismiss a hero in a castle" msgstr "Du kan ikke bortvise en helt i en borg" msgid "Dismissal of %{name} the %{race} is prohibited by scenario" msgstr "Bortvisning av %{race}en %{name} er forbudt pÃ¥ dette kartet" msgid "Dismiss %{name} the %{race}" msgstr "Bortvis %{race}en %{name}" msgid "Show previous hero" msgstr "Vis forrige helt" msgid "Show next hero" msgstr "Vis neste helt" msgid "Blood Morale" msgstr "BlodskampÃ¥nd" msgid "%{morale} Morale" msgstr "%{morale} kampÃ¥nd" msgid "%{luck} Luck" msgstr "%{luck} flaks" msgid "Current Luck Modifiers:" msgstr "PÃ¥virkninger pÃ¥ flaksen:" msgid "Current Morale Modifiers:" msgstr "PÃ¥virkninger pÃ¥ kampÃ¥nden:" msgid "Entire army is undead, so morale does not apply." msgstr "Hele hæren er vandød sÃ¥ kampÃ¥nd gjelder ikke." msgid "" "Current experience %{exp1}.\n" " Next level %{exp2}." msgstr "" "NÃ¥værende erfaring: %{exp1}.\n" "Neste level: %{exp2}." msgid "Level %{level}" msgstr "Level %{level}" #, fuzzy msgid "" "%{name} currently has %{point} spell points out of a maximum of %{max}. The " "maximum number of spell points is 10 times the hero's knowledge. It is " "occasionally possible for the hero to have more than their maximum spell " "points via special events." msgstr "" "%{name} har for øyeblikket %{point} av maks %{max} mana. Den maksimal manaen " "til en helt er 10 ganger kunnskapen til helten. I visse tilfeller kan helten " "ha mer enn den maksimale manaen." msgid "%{name1} meets %{name2}" msgstr "%{name1} møter %{name2}" msgid "Enemy heroes are now fully identifiable." msgstr "De fiendtlige heltene er nÃ¥ helt kartlagte." msgid "Identify Hero" msgstr "Kartlegg helt" msgid "Select town to port to." msgstr "Velg by Ã¥ reise til." msgid "Town Portal" msgstr "Byportal" msgid "The creatures are willing to join us!" msgstr "Krigerne er villige til Ã¥ slÃ¥ seg til oss!" msgid "All the creatures will join us..." msgstr "Alle krigerne kommer til Ã¥ ville slÃ¥ seg til oss..." msgid "The creature will join us..." msgid_plural "%{count} of the creatures will join us..." msgstr[0] "Krigeren vil bli med oss..." msgstr[1] "%{count} krigere vil bli med oss..." msgid "" "\n" " for a fee of %{gold} gold." msgstr "" "\n" "imot en pris pÃ¥ %{gold} gullstykker." msgid "These weak creatures will surely flee before us." msgstr "Disse svake krigerne vil uten tvil flykte fra oss." msgid "I fear these creatures are in the mood for a fight." msgstr "Jeg er redd for at disse krigerne er ute etter en kamp." #, fuzzy msgid "The hero's attack skill is a bonus added to each unit's attack skill." msgstr "Angrepsevnen til helten din legges til angrepsevnen til krigerne dine." #, fuzzy msgid "The hero's defense skill is a bonus added to each unit's defense skill." msgstr "" "Forsvarsevnen til helten din legges til forsvarsevnen til krigerne dine." #, fuzzy msgid "The hero's spell power determines the duration or power of a spell." msgstr "Kraften din bestemmer varigheten og styrken til trolldommene dine." #, fuzzy msgid "" " The hero's knowledge determines how many spell points the hero may have. " "Under normal circumstances, a hero is limited to 10 spell points per level " "of knowledge." msgstr "" "Kunnskapen din bestemmer hvor mye mana helten din kan ha. Vanligvis er en " "helt begrenset til 10 mana per kunnskapsnivÃ¥." msgid "Current Modifiers:" msgstr "PÃ¥virkninger:" msgid "skill|Basic" msgstr "Enkel" msgid "skill|Advanced" msgstr "Forbedret" msgid "skill|Expert" msgstr "Ekspert" msgid "Pathfinding" msgstr "Kartlesing" msgid "Archery" msgstr "Skyting" # Transport? Varelevering? Forsyninger? msgid "Logistics" msgstr "Logistikk" msgid "Scouting" msgstr "Speiding" msgid "Diplomacy" msgstr "Megling" msgid "Navigation" msgstr "Skipsfart" msgid "Leadership" msgstr "Lederskap" msgid "Wisdom" msgstr "Visdom" msgid "Mysticism" msgstr "Mystikk" msgid "Ballistics" msgstr "Ballistikk" msgid "Eagle Eye" msgstr "Ørneblikk" msgid "Necromancy" msgstr "Sortekunst" # Eiendomsinvestering. msgid "Estates" msgstr "Investering" msgid "Advanced Archery" msgstr "Forbedret skyting" msgid "Advanced Pathfinding" msgstr "Forbedret kartlesing" msgid "Basic Archery" msgstr "Enkel skyting" msgid "Basic Pathfinding" msgstr "Enkel kartlesing" msgid "Expert Pathfinding" msgstr "Ekspert kartlesing" msgid "Advanced Logistics" msgstr "Forbedret logistikk" msgid "Basic Logistics" msgstr "Enkel logistikk" msgid "Basic Scouting" msgstr "Enkel speiding" msgid "Expert Archery" msgstr "Ekspert skyting" msgid "Expert Logistics" msgstr "Ekspert logistikk" msgid "Advanced Diplomacy" msgstr "Forbedret megling" msgid "Advanced Scouting" msgstr "Forbedret speiding" msgid "Basic Diplomacy" msgstr "Enkel megling" msgid "Expert Diplomacy" msgstr "Ekspert megling" msgid "Expert Scouting" msgstr "Ekspert speiding" msgid "Advanced Leadership" msgstr "Forbedret lederskap" msgid "Advanced Navigation" msgstr "Forbedret skipsfart" msgid "Basic Leadership" msgstr "Enkelt lederskap" msgid "Basic Navigation" msgstr "Enkel skipsfart" msgid "Expert Navigation" msgstr "Ekspert skipsfart" msgid "Advanced Wisdom" msgstr "Forbedret visdom" msgid "Basic Mysticism" msgstr "Enkel mystikk" msgid "Basic Wisdom" msgstr "Enkel visdom" msgid "Expert Leadership" msgstr "Ekspert lederskap" msgid "Expert Wisdom" msgstr "Ekspert visdom" msgid "Advanced Luck" msgstr "Forbedret flaks" msgid "Advanced Mysticism" msgstr "Forbedret mystikk" msgid "Basic Luck" msgstr "Enkel flaks" msgid "Expert Luck" msgstr "Ekspert flaks" msgid "Expert Mysticism" msgstr "Ekspert mystikk" msgid "Advanced Ballistics" msgstr "Forbedret ballistikk" msgid "Advanced Eagle Eye" msgstr "Forbedret ørneblikk" msgid "Basic Ballistics" msgstr "Enkel ballistikk" msgid "Basic Eagle Eye" msgstr "Enkelt ørneblikk" msgid "Expert Ballistics" msgstr "Ekspert ballistikk" msgid "Advanced Necromancy" msgstr "Forbedret sortekunst" msgid "Basic Estates" msgstr "Enkel investering" msgid "Basic Necromancy" msgstr "Enkel sortekunst" msgid "Expert Eagle Eye" msgstr "Ekspert ørneblikk" msgid "Expert Necromancy" msgstr "Ekspert sortekunst" msgid "Advanced Estates" msgstr "Forbedret investering" msgid "Expert Estates" msgstr "Ekspert investering" msgid "" "%{skill} reduces the movement penalty for rough terrain by %{count} percent." msgstr "%{skill} minsker tap av skritt pÃ¥ ulendt terreng med %{count} prosent." msgid "%{skill} eliminates the movement penalty for rough terrain." msgstr "%{skill} fjerner all tap av skritt pÃ¥ ulendt terreng." #, fuzzy msgid "" "%{skill} increases the damage done by the hero's range attacking creatures " "by %{count} percent." msgstr "" "%{skill} gjør at krigerne dine som angriper pÃ¥ avstand skader %{count} " "prosent mer." #, fuzzy msgid "%{skill} increases the hero's movement points by %{count} percent." msgstr "%{skill} øker skrittene til helten din med %{count} prosent." #, fuzzy msgid "%{skill} increases the hero's viewable area by one square." msgid_plural "%{skill} increases the hero's viewable area by %{count} squares." msgstr[0] "%{skill} utvider sikten til helten din med én rute." msgstr[1] "%{skill} utvider sikten til helten din med %{count} ruter." #, fuzzy msgid "" "%{skill} allows the hero to negotiate with monsters who are weaker than " "their army, and reduces the cost of surrender." msgstr "" "%{skill} gjør at du kan forhandle med hærstyrker som er svakere enn din " "egen. " #, fuzzy msgid "" "Approximately %{count} percent of the creatures may offer to join the hero." msgstr "" "Rundt %{count} prosent av en nøytral motstanderstyrke vil kunne tilby Ã¥ bli " "med deg." #, fuzzy msgid "All of the creatures may offer to join the hero." msgstr "En hel nøytral motstanderstyrke vil kunne tilby Ã¥ bli med deg." #, fuzzy msgid "" "The cost of surrender is reduced to %{percent} percent of the total cost of " "troops in the army." msgstr "" "%{name} reduserer prisen for Ã¥ overgi seg med %{count} prosent av den totale " "verdien av hærstyrken din." #, fuzzy msgid "" "%{skill} increases the hero's movement points over water by %{count} percent." msgstr "%{skill} øker helten din sine skritt til sjøs med %{count} prosent." #, fuzzy msgid "%{skill} increases the hero's troops morale by %{count}." msgstr "%{skill} bedrer kampÃ¥nden til krigerne dine med %{count}." #, fuzzy msgid "%{skill} allows the hero to learn third level spells." msgstr "%{skill} lar helten din lære seg trolldommer av tredje nivÃ¥." #, fuzzy msgid "%{skill} allows the hero to learn fourth level spells." msgstr "%{skill} lar helten din lære seg trolldommer av fjerde nivÃ¥." #, fuzzy msgid "%{skill} allows the hero to learn fifth level spells." msgstr "%{skill} lar helten din lære seg trolldommer av femte nivÃ¥." #, fuzzy msgid "%{skill} regenerates one additional spell point per day to the hero." msgid_plural "" "%{skill} regenerates %{count} additional spell points per day to the hero." msgstr[0] "%{skill} fyller opp ett manapoeng for helten din per dag." msgstr[1] "" "%{skill} fyller opp %{count} ekstra manapoeng for helten din per dag." #, fuzzy msgid "%{skill} increases the hero's luck by %{count}." msgstr "%{skill} øker hæren din sin flaks med %{count}." #, fuzzy msgid "" "%{skill} gives the hero's catapult shots a greater chance to hit and do " "damage to castle walls." msgstr "" "%{skill} gjør at skuddene til katapulten din har større sjanse for Ã¥ treffe " "og dermed skade ringmuren til borgene." #, fuzzy msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot has a " "greater chance to hit and do damage to castle walls." msgstr "" "%{skill} gir katapulten til helten din et ekstra skudd, og hvert skudd har " "en større sjanse til Ã¥ treffe og skade ringmuren til en borg." #, fuzzy msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot " "automatically destroys any wall, except a fortified wall in a Knight castle." msgstr "" "%{skill} gir katapulten til helten din et ekstra skudd, og hvert skudd " "ødelegger en del av ringmuren, bortsett fra pÃ¥ en ridderborg med " "befestninger." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 1st or " "2nd level spell that was cast by an enemy during combat." msgstr "" "%{skill} gir helten din %{count} prosent sjanse til Ã¥ lære første eller " "andre nivÃ¥s trolldommer som blir brukt mot krigerne dine i kamp." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 3rd " "level spell (or below) that was cast by an enemy during combat." msgstr "" "%{skill} gir helten din %{count} prosent sjanse til Ã¥ lære en av fiendens " "trolldommer av tredje nivÃ¥ (eller lavere) som blir brukt mot krigerne dine i " "kamp." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 4th " "level spell (or below) that was cast by an enemy during combat." msgstr "" "%{skill} gir helten din %{count} prosent sjanse til Ã¥ lære en av fiendens " "trolldommer av fjerde nivÃ¥ (eller lavere) som blir brukt mot krigerne dine i " "kamp." msgid "" "%{skill} allows %{count} percent of the creatures killed in combat to be " "brought back from the dead as Skeletons." msgstr "" "%{skill} gjør at %{count} prosent av krigerne som faller i kamp blir brakt " "tilbake fra de døde i form av skjeletter." #, fuzzy msgid "" "The hero produces %{count} gold pieces per day as tax revenue from estates." msgstr "" "Helten tjener %{count} gullstykker pÃ¥ daglige skatteinntekter fra faste " "eiendommer." msgid "Blue" msgstr "BlÃ¥" msgid "Green" msgstr "Grønn" msgid "Red" msgstr "Rød" msgid "Yellow" msgstr "Gul" msgid "Orange" msgstr "Oransje" msgid "Purple" msgstr "Lilla" msgid "barrier|Aqua" msgstr "LyseblÃ¥" msgid "barrier|Blue" msgstr "BlÃ¥" msgid "barrier|Brown" msgstr "Brun" msgid "barrier|Gold" msgstr "Gullfarget" msgid "barrier|Green" msgstr "Grønn" msgid "barrier|Orange" msgstr "Oransje" msgid "barrier|Purple" msgstr "Lilla" msgid "barrier|Red" msgstr "Rød" msgid "tent|Aqua" msgstr "LyseblÃ¥tt" msgid "tent|Blue" msgstr "BlÃ¥tt" msgid "tent|Brown" msgstr "Brunt" msgid "tent|Gold" msgstr "Gullfarget" msgid "tent|Green" msgstr "Grønt" msgid "tent|Orange" msgstr "Oransje" msgid "tent|Purple" msgstr "Lilla" msgid "tent|Red" msgstr "Rødt" msgid "Experience" msgstr "Erfaringspoeng" msgid "" "Experience allows your heroes to go up levels, increasing their primary and " "secondary skills." msgstr "" "Erfaringspoeng gjør at helten din kan gÃ¥ opp i level. Det vil forbedre " "heltens hovedferdigheter og sideferdigheter." msgid "Hero/Stats" msgstr "Helt/Egenskaper" msgid "Skills" msgstr "Evner" msgid "Town/Castle" msgstr "By/Borg" msgid "Garrison" msgstr "Garnison" msgid "Gold Per Day:" msgstr "Gull per dag:" msgid "View Heroes." msgstr "Vis helter." msgid "Towns/Castles" msgstr "Byer/Borger" msgid "View Towns and Castles." msgstr "Vis byer og borger." msgid "luck|Cursed" msgstr "Forbannet" msgid "luck|Awful" msgstr "Elendig" msgid "luck|Bad" msgstr "DÃ¥rlig" msgid "luck|Normal" msgstr "Normal" msgid "luck|Good" msgstr "God" msgid "luck|Great" msgstr "GullhÃ¥r" msgid "luck|Irish" msgstr "Heldiggris" #, fuzzy msgid "" "Bad luck sometimes falls on the hero's units in combat, causing their " "attacks to only do half damage." msgstr "" "Uflaks kan av og til føre til at styrkene dine kun skader halvparten av det " "vanlige." #, fuzzy msgid "" "Neutral luck means the hero's units will never get lucky or unlucky attacks " "on the enemy." msgstr "" "Nøytral flaks gjør at styrkene dine aldri vil fÃ¥ heldige eller uheldige slag " "nÃ¥r de angriper fienden." #, fuzzy msgid "" "Good luck sometimes lets the hero's units get lucky attacks (double " "strength) in combat." msgstr "" "Positiv flaks gjør at styrkene dine av og til gjør heldige slag (dobbel " "skade) i kamp." msgid "morale|Treason" msgstr "Forræderi" msgid "morale|Awful" msgstr "Elendig" msgid "morale|Poor" msgstr "DÃ¥rlig" msgid "morale|Normal" msgstr "Normal" msgid "morale|Good" msgstr "God" msgid "morale|Great" msgstr "Ypperlig" msgid "morale|Blood!" msgstr "Blod!" #, fuzzy msgid "Bad morale may cause the hero's units to freeze in combat." msgstr "" "Negativ kampÃ¥nd kan føre til at styrkene dine stivner til av panikk i kamp." #, fuzzy msgid "" "Neutral morale means the hero's units will never be blessed with extra " "attacks or freeze in combat." msgstr "" "Nøytral kampÃ¥nd betyr at styrkene dine aldri verken vil bli velsignet med en " "ekstra tur eller stivne til av panikk i kamp." #, fuzzy msgid "Good morale may give the hero's units extra attacks in combat." msgstr "Positiv kampÃ¥nd kan av og til gi styrkene dine en ekstra tur i kamp." msgid "Knight" msgstr "Ridder" msgid "Barbarian" msgstr "Barbar" # Heks brukes allerede som Witch i witch hut. Trollkvinne er best men passer ikke med formatet og bestemtformsbøyningen til # de andre rasene. Feks. Trollkvinne(en) "heltenavn". msgid "Sorceress" msgstr "Trollkjerring" msgid "Warlock" msgstr "Trollmann" # Magiker blir allerede brukt om mage: # Wizard kommer av wise og betegner en som har studert mye for Ã¥ bli flink. # Vurder vismann og visdame nÃ¥r det blir mulig med tittel for hvert kjønn. Slaget/tilhørigheten kan kalles visfolk. # Alternativer: Allviter (kjønnsnøytral), trolldomslærd fortryller, vismenneske, filosof, tryllemester, lærd, tryllelærd, # alkymist (siden de lager golemer), magilærd, trylleviter, Magiviter, trolldomsviter (litt mange slag med trolldom i navnet.) # Hvitkunstner siden de er pÃ¥ den gode siden og en slags motsetning til sortekunstnerne. Merk: Tryllekunstner er profesjonen # som gjør synsbedrageri osv. msgid "Wizard" msgstr "Magimester" msgid "Necromancer" msgstr "Sortekunstner" msgid "Multi" msgstr "Flere" msgid "doubleLined|Knight" msgstr "Ridder" msgid "doubleLined|Barbarian" msgstr "Barbar" # Heks brukes allerede som Witch i witch hut. Trollkvinne er best men passer ikke med formatet og bestemtformsbøyningen til # de andre rasene. Feks. Trollkvinne(en) "heltenavn". msgid "doubleLined|Sorceress" msgstr "" "Troll-\n" "kjerring" msgid "doubleLined|Warlock" msgstr "" "Troll-\n" "mann" msgid "doubleLined|Wizard" msgstr "" "Magi-\n" "mester" msgid "" "doubleLined|Necro-\n" "mancer" msgstr "" "Sorte-\n" "kunstner" msgid "doubleLinedRace|Multi" msgstr "Flere" msgid "doubleLinedRace|Random" msgstr "Tilfeldig" # StillestÃ¥ende (for lang), bomstille. msgid "speed|Standing" msgstr "Bomfast" # Sneglefart? Sneglende? Krypende, krÃ¥lende, smygende, langsom msgid "speed|Crawling" msgstr "Snegle" # Veldig sein blir for langt. maks 9 bokstaver. Særs sein? Veldig stivt. msgid "speed|Very Slow" msgstr "Treigost" msgid "speed|Slow" msgstr "Sein" msgid "speed|Average" msgstr "Middels" # God? Bra? Brukbar? msgid "speed|Fast" msgstr "Grei" # Veldig rask blir for langt msgid "speed|Very Fast" msgstr "Rask" msgid "speed|Ultra Fast" msgstr "Superrask" # Lynrask msgid "speed|Blazing" msgstr "Fykende" # Øyeblikkelig, tvert, umiddelbar. msgid "speed|Instant" msgstr "Lynrask" msgid "week|Squirrel" msgstr "ekorn" msgid "week|Rabbit" msgstr "hare" msgid "week|Gopher" msgstr "jordekorn" msgid "week|Badger" msgstr "grevling" msgid "week|Rat" msgstr "rotte" msgid "week|Eagle" msgstr "ørn" msgid "week|Weasel" msgstr "vesel" msgid "week|Raven" msgstr "ravn" msgid "week|Mongoose" msgstr "manguster" msgid "week|Dog" msgstr "hund" msgid "week|Aardvark" msgstr "jordsvin" msgid "week|Lizard" msgstr "øgle" msgid "week|Tortoise" msgstr "landskilpadde" msgid "week|Hedgehog" msgstr "piggsvin" msgid "week|Condor" msgstr "gribb" msgid "week|Ant" msgstr "maur" msgid "week|Grasshopper" msgstr "gresshopper" msgid "week|Dragonfly" msgstr "øyenstikker" msgid "week|Spider" msgstr "edderkopp" msgid "week|Butterfly" msgstr "sommerfugl" msgid "week|Bumblebee" msgstr "humle" msgid "week|Locust" msgstr "ørkengresshopper" msgid "week|Earthworm" msgstr "meitemark" msgid "week|Hornet" msgstr "veps" msgid "week|Beetle" msgstr "bille" msgid "week|PLAGUE" msgstr "pest" msgid "week|Unnamed" msgstr "navnløshet" msgid "Desert" msgstr "Ørken" msgid "Snow" msgstr "Snø" msgid "Wasteland" msgstr "Ødemark" msgid "Beach" msgstr "Strand" msgid "Lava" msgstr "Lava" msgid "Dirt" msgstr "Jord" msgid "Grass" msgstr "Gress" msgid "Ocean" msgstr "Sjø" msgid "maps|Small" msgstr "Liten" msgid "maps|Medium" msgstr "Middels" msgid "maps|Large" msgstr "Stor" msgid "maps|Extra Large" msgstr "Ekstra stor" msgid "maps|Custom Size" msgstr "Tilpasset størrelse" msgid "Ore Mine" msgstr "Malmgruve" msgid "Sulfur Mine" msgstr "Svovelgruve" msgid "Crystal Mine" msgstr "Krystallgruve" msgid "Gems Mine" msgstr "Edelstensgruve" msgid "Gold Mine" msgstr "Gullgruve" msgid "Mine" msgstr "Gruve" # Hensetting av hester forbudt. Bortsleping skjer pÃ¥ eiers regning. msgid "Burma shave." msgstr "Hensatte hester vil ende opp som pÃ¥legg." msgid "Next sign 50 miles." msgstr "Neste skilt om 50 mil." # Vennligst les pÃ¥ den andre siden av skiltet. msgid "See Rock City." msgstr "" "Gratulerer! \n" "Du er besøkende nr. 32167. \n" "Ta en tørr kjeks." msgid "This space for rent." msgstr "Skilt til utleie." msgid "No object" msgstr "Ingen objekt" msgid "Alchemist Lab" msgstr "Alkymistlab" msgid "Sign" msgstr "Skilt" msgid "Buoy" msgstr "Bøye" msgid "Skeleton" msgstr "Skjelett" msgid "Daemon Cave" msgstr "Djevelens hule" msgid "Treasure Chest" msgstr "Skattekiste" msgid "Faerie Ring" msgstr "Feenes ring" msgid "Campfire" msgstr "LeirbÃ¥l" msgid "Fountain" msgstr "Fontene" msgid "Gazebo" msgstr "Lysthus" msgid "Genie Lamp" msgstr "Magisk lampe" msgid "Archer's House" msgstr "Bueskytternes hus" msgid "Goblin Hut" msgstr "Vettehytte" msgid "Dwarf Cottage" msgstr "Dvergstue" msgid "Peasant Hut" msgstr "Bondehytte" msgid "Stables" msgstr "Stall" msgid "Alchemist's Tower" msgstr "Alkymistens tÃ¥rn" msgid "Event" msgstr "Hendelse" msgid "Dragon City" msgstr "Dragenes landsby" msgid "Lighthouse" msgid_plural "Lighthouses" msgstr[0] "FyrtÃ¥rn" msgstr[1] "FyrtÃ¥rn" msgid "Water Wheel" msgid_plural "Water Wheels" msgstr[0] "Vannhjul" msgstr[1] "Vannhjul" msgid "Monster" msgstr "Monster" msgid "Obelisk" msgstr "Obelisk" msgid "Oasis" msgstr "Oase" msgid "Resource" msgstr "RÃ¥vare" msgid "Sawmill" msgstr "Sagbruk" msgid "Oracle" msgstr "Orakel" # Sirkel? Ring? msgid "Shrine of the First Circle" msgstr "Tempel av den første sirkelen" msgid "Shipwreck" msgstr "Skipbrudd" msgid "Sea Chest" msgstr "Sjøkiste" msgid "Desert Tent" msgstr "Ørkentelt" msgid "Stone Liths" msgstr "Steinportal" msgid "Wagon Camp" msgstr "Vognleir" msgid "Hut of the Magi" msgstr "SpÃ¥mannens hytte" msgid "Whirlpool" msgstr "Virvelstrøm" msgid "Windmill" msgid_plural "Windmills" msgstr[0] "Vindmølle" msgstr[1] "Vindmøller" msgid "Artifact" msgstr "Gjenstand" msgid "Mermaid" msgstr "Havfrue" msgid "Boat" msgstr "Skip" msgid "Random Ultimate Artifact" msgstr "Tilfeldig sjeldneste skatt" msgid "Random Artifact" msgstr "Tilfeldig gjenstand" msgid "Random Resource" msgstr "Tilfeldig rÃ¥vare" msgid "Random Monster" msgstr "Tilfeldig monster" msgid "Random Town" msgstr "VilkÃ¥rlig by" msgid "Random Castle" msgstr "VilkÃ¥rlig borg" msgid "Eye of the Magi" msgstr "SpÃ¥mannens øye" msgid "Random Monster - weak" msgstr "Tilfeldig monster - svak" msgid "Random Monster - medium" msgstr "Tilfeldig monster - middels" msgid "Random Monster - strong" msgstr "Tilfeldig monster - sterk" msgid "Random Monster - very strong" msgstr "Tilfeldig monster - veldig sterk" #, fuzzy msgid "Hero" msgstr "Helter" msgid "Nothing Special" msgstr "Ingenting spesielt" msgid "Mossy Rock" msgstr "Mosesten" msgid "Watch Tower" msgstr "VakttÃ¥rn" msgid "Tree House" msgstr "Trehytte" msgid "Tree City" msgstr "Trærenes landsby" msgid "Ruins" msgstr "Ruiner" msgid "Fort" msgstr "Fort" msgid "Abandoned Mine" msgstr "Forlatt gruve" msgid "Sirens" msgstr "Sirener" msgid "Standing Stones" msgstr "Steinsøyler" # idol har ikke samme betydning pÃ¥ norsk. msgid "Idol" msgstr "Gudestatue" msgid "Tree of Knowledge" msgstr "Kunnskapstre" # Sjaman? Det er vel en ork. # Kvinnelig msgid "Witch Doctor's Hut" msgstr "Heksedoktorens hytte" msgid "Temple" msgstr "Tempel" msgid "Hill Fort" msgstr "Bergfestning" msgid "Halfling Hole" msgstr "Halvinghule" msgid "Mercenary Camp" msgstr "Leiesoldatleir" msgid "Shrine of the Second Circle" msgstr "Tempel av den andre sirkelen" msgid "Shrine of the Third Circle" msgstr "Tempel av den tredje sirkelen" msgid "City of the Dead" msgstr "De dødes by" msgid "Sphinx" msgstr "Sfinks" msgid "Wagon" msgstr "Vogn" msgid "Tar Pit" msgstr "Tjærepytt" msgid "Artesian Spring" msgstr "Artesisk brønn" msgid "Troll Bridge" msgstr "Trollbru" msgid "Watering Hole" msgstr "Vannkilde" msgid "Witch's Hut" msgstr "Heksehytte" msgid "Xanadu" msgstr "Xanadu" msgid "Lean-To" msgstr "Gapahuk" msgid "Magellan's Maps" msgstr "Magellanske kart" msgid "Flotsam" msgstr "Vrakgods" msgid "Derelict Ship" msgstr "Havarert skip" msgid "Shipwreck Survivor" msgstr "Skipbruddent menneske" msgid "Bottle" msgstr "Flaskepost" msgid "Magic Well" msgstr "Magisk brønn" msgid "Magic Garden" msgid_plural "Magic Gardens" msgstr[0] "Magisk hage" msgstr[1] "Magiske hager" msgid "Observation Tower" msgstr "UtkikkstÃ¥rn" msgid "Freeman's Foundry" msgstr "Borgerens støperi" msgid "Reefs" msgstr "Rev" msgid "Volcano" msgstr "Vulkan" msgid "Flowers" msgstr "Blomster" msgid "Rock" msgstr "Stein" msgid "Water Lake" msgstr "Innsjø" msgid "Mandrake" msgstr "Alrune" msgid "Dead Tree" msgstr "Trestubbe" msgid "Stump" msgstr "Stubbe" msgid "Crater" msgstr "Krater" msgid "Cactus" msgstr "Kaktus" msgid "Mound" msgstr "Haug" msgid "Dune" msgstr "Sanddyne" msgid "Lava Pool" msgstr "Lavadam" msgid "Shrub" msgstr "Busk" msgid "Barrow Mounds" msgstr "Gravhaug" msgid "Random Artifact - Treasure" msgstr "Tilfeldig skatt - Litt verdifull" msgid "Random Artifact - Minor" msgstr "Tilfeldig skatt - Mer verdifull" msgid "Random Artifact - Major" msgstr "Tilfeldig skatt - Ekstremt verdifull" msgid "Barrier" msgstr "Sperre" msgid "Traveller's Tent" msgstr "Vandrerens telt" msgid "Jail" msgstr "Fengsel" msgid "Fire Summoning Altar" msgstr "Ildmanende alter" msgid "Air Summoning Altar" msgstr "Luftmanende alter" msgid "Earth Summoning Altar" msgstr "Jordmanende alter" msgid "Water Summoning Altar" msgstr "Vannmanende alter" msgid "Unknown Monster" msgstr "Ukjent monster" msgid "Unknown Monsters" msgstr "Ukjente monstre" msgid "Peasant" msgstr "Bonde" msgid "Peasants" msgstr "Bønder" msgid "Archer" msgstr "Bueskytter" msgid "Archers" msgstr "Bueskyttere" # Denne oversettelsen brukes i norsk D&D msgid "Ranger" msgstr "Jeger" msgid "Rangers" msgstr "Jegere" msgid "Pikeman" msgstr "Spydmann" msgid "Pikemen" msgstr "Spydmenn" msgid "Veteran Pikeman" msgstr "Spydveteran" msgid "Veteran Pikemen" msgstr "Spydveteraner" msgid "Swordsman" msgstr "Sverdmann" msgid "Swordsmen" msgstr "Sverdmenn" msgid "Master Swordsman" msgstr "Sverdmester" msgid "Master Swordsmen" msgstr "Sverdmestre" msgid "Cavalries" msgstr "Ryttere" msgid "Cavalry" msgstr "Rytter" msgid "Champion" msgstr "Mesterrytter" msgid "Champions" msgstr "Mesterryttere" msgid "Paladin" msgstr "Ridderhelt" msgid "Paladins" msgstr "Ridderhelter" msgid "Crusader" msgstr "Korsfarer" msgid "Crusaders" msgstr "Korsfarere" # Tusse, smÃ¥troll? - For søtt. Landvette (litt likt stokkholm flyplass). Vette? For enkelt? Kampvette? msgid "Goblin" msgstr "Vette" msgid "Goblins" msgstr "Vetter" msgid "Orc" msgstr "Ork" msgid "Orcs" msgstr "Orker" msgid "Orc Chief" msgstr "Orkehøvding" msgid "Orc Chiefs" msgstr "Orkehøvdinger" msgid "Wolf" msgstr "Varg" msgid "Wolves" msgstr "Varger" msgid "Ogre" msgstr "Jette" msgid "Ogres" msgstr "Jetter" msgid "Ogre Lord" msgstr "Herskerjette" msgid "Ogre Lords" msgstr "Herskerjetter" msgid "Troll" msgstr "Troll" msgid "Trolls" msgstr "Troll" msgid "War Troll" msgstr "Stridstroll" msgid "War Trolls" msgstr "Stridstroll" msgid "Cyclopes" msgstr "Kykloper" msgid "Cyclops" msgstr "Kyklop" msgid "Sprite" msgstr "Fe" msgid "Sprites" msgstr "Feer" msgid "Dwarf" msgstr "Dverg" msgid "Dwarves" msgstr "Dverger" msgid "Battle Dwarf" msgstr "Stridsdverg" msgid "Battle Dwarves" msgstr "Stridsdverger" msgid "Elf" msgstr "Alv" msgid "Elves" msgstr "Alver" msgid "Grand Elf" msgstr "Storalv" msgid "Grand Elves" msgstr "Storalver" msgid "Druid" msgstr "Druide" msgid "Druids" msgstr "Druider" msgid "Greater Druid" msgstr "Mektig druide" msgid "Greater Druids" msgstr "Mektige druider" msgid "Unicorn" msgstr "Enhjørning" msgid "Unicorns" msgstr "Enhjørninger" msgid "Phoenix" msgstr "Føniks" msgid "Phoenixes" msgstr "Fønikser" msgid "Centaur" msgstr "Kentaur" msgid "Centaurs" msgstr "Kentaurer" msgid "Gargoyle" msgstr "Gravstatue" msgid "Gargoyles" msgstr "Gravstatuer" msgid "Griffin" msgstr "Griff" msgid "Griffins" msgstr "Griffer" msgid "Minotaur" msgstr "Minotaur" msgid "Minotaurs" msgstr "Minotaurer" msgid "Minotaur King" msgstr "Minotaurkonge" msgid "Minotaur Kings" msgstr "Minotaurkonger" msgid "Hydra" msgstr "Hydra" msgid "Hydras" msgstr "Hydraer" msgid "Green Dragon" msgstr "Grønndrage" msgid "Green Dragons" msgstr "Grønndrager" msgid "Red Dragon" msgstr "Røddrage" msgid "Red Dragons" msgstr "Røddrager" msgid "Black Dragon" msgstr "Svartdrage" msgid "Black Dragons" msgstr "Svartdrager" msgid "Halfling" msgstr "Halving" msgid "Halflings" msgstr "Halvinger" msgid "Boar" msgstr "Villsvin" msgid "Boars" msgstr "Villsvin" msgid "Iron Golem" msgstr "Jerngolem" msgid "Iron Golems" msgstr "Jerngolemer" msgid "Steel Golem" msgstr "StÃ¥lgolem" msgid "Steel Golems" msgstr "StÃ¥lgolemer" msgid "Roc" msgstr "Rokk" msgid "Rocs" msgstr "Rokker" msgid "Mage" msgstr "Magiker" msgid "Magi" msgstr "Magikere" msgid "Archmage" msgstr "Erkemagiker" msgid "Archmagi" msgstr "Erkemagikere" msgid "Giant" msgstr "Kjempe" msgid "Giants" msgstr "Kjemper" msgid "Titan" msgstr "Titan" msgid "Titans" msgstr "Titaner" msgid "Skeletons" msgstr "Skjeletter" msgid "Zombie" msgstr "Zombie" msgid "Zombies" msgstr "Zombier" msgid "Mutant Zombie" msgstr "Mutert zombie" msgid "Mutant Zombies" msgstr "Muterte zombier" msgid "Mummies" msgstr "Mumier" msgid "Mummy" msgstr "Mumie" msgid "Royal Mummies" msgstr "Kongelige mumier" msgid "Royal Mummy" msgstr "Kongelig mumie" msgid "Vampire" msgstr "Vampyr" msgid "Vampires" msgstr "Vampyrer" msgid "Vampire Lord" msgstr "Vampyrgreve" msgid "Vampire Lords" msgstr "Vampyrgrever" # Ser ut som konger og kommer fra mausoleum/kongegrav. Bedre enn hekselik som virker litt kvinnelig. # Konge beholder det som hankjønn. Lich betyr lik opprinnelig. msgid "Lich" msgstr "Heksekonge" msgid "Liches" msgstr "Heksekonger" msgid "Power Lich" msgstr "Mektig heksekonge" msgid "Power Liches" msgstr "Mektige heksekonger" msgid "Bone Dragon" msgstr "Beindrage" msgid "Bone Dragons" msgstr "Beindrager" msgid "Rogue" msgstr "Banditt" msgid "Rogues" msgstr "Banditter" msgid "Nomad" msgstr "Nomade" msgid "Nomads" msgstr "Nomader" msgid "Ghost" msgstr "Gjenferd" msgid "Ghosts" msgstr "Gjenferd" msgid "Genie" msgstr "LampeÃ¥nd" msgid "Genies" msgstr "LampeÃ¥nder" msgid "Medusa" msgstr "Medusa" msgid "Medusas" msgstr "Medusaer" msgid "Earth Elemental" msgstr "JordÃ¥nd" msgid "Earth Elementals" msgstr "JordÃ¥nder" msgid "Air Elemental" msgstr "LuftÃ¥nd" msgid "Air Elementals" msgstr "LuftÃ¥nder" msgid "Fire Elemental" msgstr "IldÃ¥nd" msgid "Fire Elementals" msgstr "IldÃ¥nder" msgid "Water Elemental" msgstr "VannÃ¥nd" msgid "Water Elementals" msgstr "VannÃ¥nder" msgid "Random Monsters" msgstr "Tilfeldige monstre" msgid "Random Monster 1" msgstr "Tilfeldig monster 1" msgid "Random Monsters 1" msgstr "Tilfeldige monstre 1" msgid "Random Monster 2" msgstr "Tilfeldig monster 2" msgid "Random Monsters 2" msgstr "Tilfeldige monstre 2" msgid "Random Monster 3" msgstr "Tilfeldig monster 3" msgid "Random Monsters 3" msgstr "Tilfeldige monstre 3" msgid "Random Monster 4" msgstr "Tilfeldig monster 4" msgid "Random Monsters 4" msgstr "Tilfeldige monstre 4" msgid "Double shot" msgstr "Dobbeltskudd" msgid "2-hex monster" msgstr "2-feltsmonster" msgid "Double strike" msgstr "Dobbeltslag" msgid "Double damage to Undead" msgstr "Dobbel skade mot vandøde" msgid "% magic resistance" msgstr "% magimotstand" msgid "Immune to Mind spells" msgstr "Immun mot sinnsmagi" msgid "Immune to Elemental spells" msgstr "Immun mot elementbasert magi" msgid "Immune to Fire spells" msgstr "Immun mot ildmagi" msgid "Immune to Cold spells" msgstr "Immun mot kuldemagi" msgid "Immune to " msgstr "Immun mot " msgid "% immunity to %{spell} spell" msgstr "% immunitet mot trolldommen %{spell}" msgid "% damage from Elemental spells" msgstr "% skade fra elementbasert magi" msgid "% chance to Dispel beneficial spells" msgstr "% sjanse til Ã¥ oppheve all fordelaktig trolldom" msgid "% chance to Paralyze" msgstr "% sjanse til Ã¥ lamme" msgid "% chance to Petrify" msgstr "% sjanse til Ã¥ forsteine" msgid "% chance to Blind" msgstr "% sjanse til Ã¥ forblinde" msgid "% chance to Curse" msgstr "% sjanse for forbannelse" msgid "% chance to cast %{spell} spell" msgstr "% sjanse til Ã¥ bruke trolldommen %{spell}" msgid "HP regeneration" msgstr "Selvhelbredning" msgid "Two hexes attack" msgstr "Tofeltsangrep" msgid "Flyer" msgstr "Flyger" msgid "Always retaliates" msgstr "Tar alltid igjen" msgid "Attacks all adjacent enemies" msgstr "Angriper alle fiender rundt seg" msgid "No melee penalty" msgstr "Ingen ulempe i nærkamp" msgid "Dragon" msgstr "Drage" msgid "Undead" msgstr "Vandød" msgid "No enemy retaliation" msgstr "Ingen fiendtlige hevnslag" msgid "HP drain" msgstr "Stjeling av liv" msgid "Cloud attack" msgstr "TÃ¥keskudd" msgid "Decreases enemy's morale by " msgstr "Svekker fiendens kampÃ¥nd med " msgid "% chance to halve enemy" msgstr "% sjanse til Ã¥ halvere antallet til den angrepne styrken" msgid "Soul Eater" msgstr "Sjelesanking" msgid "Elemental" msgstr "Elementær" msgid "No Morale" msgstr "Ingen kampÃ¥nd" msgid "200% damage from Fire spells" msgstr "200% skade fra ildmagi" msgid "200% damage from Cold spells" msgstr "200% skade fra kuldemagi" msgid "% damage from %{spell} spell" msgstr "% skade fra trolldommen %{spell}" msgid "% immunity to " msgstr "% immunitet mot trolldommen %{spell} " msgid "Lightning" msgstr "Lynnedslag" msgid "% damage from " msgstr "% skade fra " msgid "The three Anduran artifacts magically combine into one." msgstr "De tre skattene til Anduran blir pÃ¥ magisk vis om til én." msgid "View Spells" msgstr "Vis trolldommer" msgid "View %{name} Info" msgstr "Vis info om %{name}" msgid "Move %{name}" msgstr "Flytt %{name}" msgid "Cannot move the Spellbook" msgstr "Trylleboken kan ikke flyttes" msgid "This item can't be traded." msgstr "Denne tingen kan ikke overføres." msgid "Invalid Artifact" msgstr "Ugyldig gjenstand" #, fuzzy msgid "The %{name} increases the hero's knowledge by %{count}." msgstr "%{name} øker kunnskapen din med %{count}." # ypperste? beste? msgid "Ultimate Book of Knowledge" msgstr "Den ultimate kunnskapsboka" #, fuzzy msgid "The %{name} increases the hero's attack skill by %{count}." msgstr "%{name} øker angrepsevnen din med %{count}." msgid "Ultimate Sword of Dominion" msgstr "Det ultimate sverdet av Herreveldet" #, fuzzy msgid "The %{name} increases the hero's defense skill by %{count}." msgstr "%{name} øker forsvarsevnen din med %{count}." msgid "Ultimate Cloak of Protection" msgstr "Den ultimate beskyttelseskappen" #, fuzzy msgid "The %{name} increases the hero's spell power by %{count}." msgstr "%{name} øker kraften din med %{count}." msgid "Ultimate Wand of Magic" msgstr "Den ultimate tryllestaven" #, fuzzy msgid "" "The %{name} increases the hero's attack and defense skills by %{count} each." msgstr "%{name} øker angreps- og forsvarsevnen din med %{count} hver." msgid "Ultimate Shield" msgstr "Det ultimate skjoldet" #, fuzzy msgid "" "The %{name} increases the hero's spell power and knowledge by %{count} each." msgstr "%{name} øker kraften og kunnskapen din med %{count} hver." msgid "Ultimate Staff" msgstr "Den ultimate stokken" #, fuzzy msgid "" "The %{name} increases each of the hero's basic skills by %{count} points." msgstr "%{name} øker alle egenskapene dine med %{count} poeng." msgid "Ultimate Crown" msgstr "Den ultimate kronen" msgid "Golden Goose" msgstr "GullgÃ¥sa" msgid "The %{name} brings in an income of %{count} gold per day." msgstr "%{name} gir deg en ekstra inntekt pÃ¥ %{count} gullstykker hver dag." msgid "Arcane Necklace of Magic" msgstr "Det mørke magihalskjedet" msgid "" "After rescuing a Sorceress from a cursed tomb, she rewards your heroism with " "an exquisite jeweled necklace." msgstr "" "Etter Ã¥ ha reddet ei trollkvinne fra et forhekset gravkammer, belønner hun " "deg for heltemotet ditt med Ã¥ gi deg et storartet halskjede av juveler." msgid "Caster's Bracelet of Magic" msgstr "Fortryllerens magiarmbÃ¥nd" msgid "" "While searching through the rubble of a caved-in mine, you free a group of " "trapped Dwarves. Grateful, the leader gives you a golden bracelet." msgstr "" "Mens du leter gjennom restene av ei sammenfalt gruve, befrir du en gruppe " "med innesperrede dverger. Takknemlig gir lederen deg et armbÃ¥nd av gull." msgid "Mage's Ring of Power" msgstr "Magikerens kraftring" msgid "" "A cry of pain leads you to a Centaur, caught in a trap. Upon setting the " "creature free, he hands you a small pouch. Emptying the contents, you find a " "dazzling jeweled ring." msgstr "" "Et skrik av smerte fører deg til en kentaur fanget i en felle. Etter Ã¥ ha " "hjulpet det stakkars vesenet ut, gir han deg en liten taske. Blant innholdet " "i den finner du en fantastisk ring med en edelsten pÃ¥ seg." msgid "Witch's Broach of Magic" msgstr "Heksens magiske brystnÃ¥l" msgid "" "Alongside the remains of a burnt witch lies a beautiful broach, intricately " "designed. Approaching the corpse with caution, you add the broach to your " "inventory." msgstr "" "Ved levningene til ei ihjelbrent heks ligger ei vakker brystnÃ¥l av nøye " "hÃ¥ndverk. Du trÃ¥r varsomt mot liket og tar brystnÃ¥la." msgid "Medal of Valor" msgstr "Medaljen av heltemot" #, fuzzy msgid "The %{name} increases the morale of the hero's army by %{count}." msgstr "%{name} øker kampÃ¥nden din med %{count}." msgid "" "Freeing a virtuous maiden from the clutches of an evil overlord, you are " "granted a Medal of Valor by the King's herald." msgstr "" "Etter Ã¥ ha befridd en stakkars ungpike fra fangenskapet til en ond hersker, " "belønner det kongelige hoffet deg med en medalje av heltemot." msgid "Medal of Courage" msgstr "Medaljen av tapperhet" msgid "" "After saving a young boy from a vicious pack of Wolves, you return him to " "his father's manor. The grateful nobleman awards you with a Medal of Courage." msgstr "" "Etter Ã¥ ha kommet en unggutt til unnsetning fra en flokk med blodtørstige " "varger, tar du ham med tilbake til faren hans sin herregÃ¥rd. Den takknemlige " "adelsmannen belønner deg med en medalje av tapperhet." msgid "Medal of Honor" msgstr "Æresmedaljen" msgid "" "After freeing a princess of a neighboring kingdom from the evil clutches of " "despicable slavers, she awards you with a Medal of Honor." msgstr "" "Etter Ã¥ ha befridd en prinsesse fra et naborike fra det onde klørne til " "foraktelige slavehandlere, belønner hun deg med en medalje av ære." msgid "Medal of Distinction" msgstr "Utmerkelsesmedaljen" msgid "" "Ridding the countryside of the hideous Minotaur who made a sport of eating " "noblemen's Knights, you are honored with the Medal of Distinction." msgstr "" "Etter Ã¥ ha fjernet fra bygdelandet en grusom minotaur som hadde gÃ¥tt inn for " "Ã¥ jakte og spise adelsmennenes riddere, blir du hedret med en medalje for " "særskilt utmerkelse." msgid "Fizbin of Misfortune" msgstr "Fizbinen av uflaks" #, fuzzy msgid "" "The %{name} greatly decreases the morale of the hero's army by %{count}." msgstr "%{name} svekker kampÃ¥nden din skikkelig med %{count}." msgid "" "You stumble upon a medal lying alongside the empty road. Adding the medal to " "your inventory, you become aware that you have acquired the undesirable " "Fizbin of Misfortune, greatly decreasing your army's morale." msgstr "" "Du kommer over en medalje liggende langs den øde veien. Etter Ã¥ ha lagt " "medaljen sammen med resten av tingene dine, innser du at den er den lite " "ettertraktede Fizbinen av uflaks. Den svekker styrkene dine sin kampÃ¥nd " "betydelig." msgid "Thunder Mace of Dominion" msgstr "Tordenklubben av Herreveldet" msgid "" "During a sudden storm, a bolt of lightning strikes a tree, splitting it. " "Inside the tree you find a mysterious mace." msgstr "" "Midt i en uforventet storm, slÃ¥r et lyn ned i et tre, og det deles tvers " "gjennom. PÃ¥ innsiden av treet finner du en forunderlig klubbe." msgid "Armored Gauntlets of Protection" msgstr "Panserhanskene av beskyttelse" #, fuzzy msgid "The %{name} increase the hero's defense skill by %{count}." msgstr "%{name} øker forsvarsevnen din med %{count}." #, fuzzy msgid "" "You encounter the infamous Black Knight! After a grueling duel ending in a " "draw, the Knight, out of respect, offers you a pair of armored gauntlets." msgstr "" "Du møter pÃ¥ den berømte Sorte ridderen! Etter en intens men uavgjort duell " "tilbyr ridderen, av sin nylig ervervede respekt for deg, et par pansrede " "hansker." msgid "Defender Helm of Protection" msgstr "Forsvarerens hjelm av beskyttelse" msgid "" "A glint of golden light catches your eye. Upon further investigation, you " "find a golden helm hidden under a bush." msgstr "" "Et eller annet som glitrer av gull fanger oppmerksomheten din. Ved nærmere " "undersøkelse oppdager du en gullhjelm bortgjemt under en busk." msgid "Giant Flail of Dominion" msgstr "Kjempeklubben av Herreveldet" msgid "" "A clumsy Giant has killed himself with his own flail. Knowing your superior " "skill with this weapon, you confidently remove the spectacular flail from " "the fallen Giant." msgstr "" "Du oppdager en klønete kjempe som har prestert Ã¥ drepe seg selv med sin egen " "klubbe. Trygg pÃ¥ dine langt bedre ferdigheter med dette vÃ¥penet, fjerner du " "selvsikkert den imponerende klubben fra den falne kjempen." msgid "Ballista of Quickness" msgstr "Ballisten av hurtighet" #, fuzzy msgid "The %{name} gives the hero's catapult one extra shot per combat round." msgstr "%{name} lar katapulten din avfyre ett ekstra skudd per kamprunde." msgid "" "Walking through the ruins of an ancient walled city, you find the instrument " "of the city's destruction, an elaborately crafted ballista." msgstr "" "Mens du gÃ¥r gjennom ruinene til en eldgammel innmuret by, finner du Ã¥rsaken " "til byens ødeleggelse: en omhyggelig utarbeidet ballist." msgid "Stealth Shield of Protection" msgstr "Tyvenes skjold av beskyttelse" msgid "" "A stone statue of a warrior holds a silver shield. As you remove the shield, " "the statue crumbles into dust." msgstr "" "En krigers statue i stein bærer et sølvskjold. Idet du fjerner skjoldet " "smuldrer statuen opp." msgid "Dragon Sword of Dominion" msgstr "Dragesverdet av Herreveldet" msgid "" "As you are walking along a narrow path, a nearby bush suddenly bursts into " "flames. Before your eyes the flames become the image of a beautiful woman. " "She holds out a magnificent sword to you." msgstr "" "I mens du spaserer langs en smal sti, tar en busk i nærheten plutselig fyr. " "Flammene foran deg blir om til en skikkelse av en prektig kvinne. Hun rekker " "ut et storslÃ¥tt sverd til deg." msgid "Power Axe of Dominion" msgstr "Styrkeøksa av Herreveldet" msgid "" "You see a silver axe embedded deeply in the ground. After several " "unsuccessful attempts by your army to remove the axe, you tightly grip the " "handle of the axe and effortlessly pull it free." msgstr "" "Du fÃ¥r øynene pÃ¥ ei øks av sølv som er plantet dypt ned i bakken. Etter " "flere mislykkede forsøk fra følget ditt pÃ¥ Ã¥ fjerne øksa, tar du et godt tak " "i hÃ¥ndtaket og fÃ¥r den lett opp." msgid "Divine Breastplate of Protection" msgstr "Den gudommelige brystplata av beskyttelse" msgid "" "A gang of Rogues is sifting through the possessions of dead warriors. " "Scaring off the scavengers, you note the Rogues had overlooked a beautiful " "breastplate." msgstr "" "En gjeng røvere holder pÃ¥ Ã¥ gÃ¥ gjennom eiendelene til en avdød kriger. Etter " "Ã¥ ha jaget vekk plyndrerene, legger du merke til at de hadde oversett ei " "forseggjort brystplate." msgid "Minor Scroll of Knowledge" msgstr "Den mindre kunnskapsrullen" msgid "" "Before you appears a levitating glass case with a scroll, perched upon a bed " "of crimson velvet. At your touch, the lid opens and the scroll floats into " "your awaiting hands." msgstr "" "Rett foran deg dukker ei svevende glasskiste opp med en skriftrull lagt pÃ¥ " "en kongelig rød pute. Idet du berører den, Ã¥pner lokket seg og rullen svever " "magisk inn i dine utÃ¥lmodige hender." msgid "Major Scroll of Knowledge" msgstr "Den større kunnskapsrullen" msgid "" "Visiting a local wiseman, you explain the intent of your journey. He reaches " "into a sack and withdraws a yellowed scroll and hands it to you." msgstr "" "PÃ¥ besøk hos en lokal vismann forklarer du hensikten med ferden din. Han " "strekker hÃ¥nden ned i sekken sin og ut av den drar han fram en gulnet " "skriftrull, som han overrekker deg." msgid "Superior Scroll of Knowledge" msgstr "Den overlegne kunnskapsrullen" msgid "" "You come across the remains of an ancient Druid. Bones, yellowed with age, " "peer from the ragged folds of her robe. Searching the robe, you discover a " "scroll hidden in the folds." msgstr "" "Du kommer over levningene til en druide fra fortiden. Knokler, som har blitt " "gul med tiden, kan synes pÃ¥ innsiden av den fillete kjortelen hennes. Du " "leter gjennom den og oppdager en bortgjemt skriftrull i foldene." msgid "Foremost Scroll of Knowledge" msgstr "Den ypperste kunnskapsrullen" msgid "" "Mangled bones, yellowed with age, peer from the ragged folds of a dead " "Druid's robe. Searching the robe, you discover a scroll hidden within." msgstr "" "Oppsprukne knokler, som har gulnet med tiden, synes fra de fillete foldene " "til en omkommet druides kÃ¥pe. Du leter gjennom kÃ¥pen og oppdager en " "bortgjemt skriftrull." msgid "Endless Sack of Gold" msgstr "Den uendelige gullsekken" #, fuzzy msgid "The %{name} provides the hero with %{count} gold per day." msgstr "%{name} gir deg %{count} gullstykker hver dag." msgid "" "A little leprechaun dances gleefully around a magic sack. Seeing you " "approach, he stops in mid-stride. The little man screams and stamps his foot " "ferociously, vanishing into thin air. Remembering the old leprechaun saying " "'Finders Keepers', you grab the sack and leave." msgstr "" "En liten regnbuegnom danser lykkelig rundt en magisk sekk. Idet han ser deg " "komme, stanser han midt i steget. Den lille mannen hyler og tramper med " "foten sin febrilsk, før han forsvinner i løse luften. Du kommer pÃ¥ det gamle " "ordtaket til regnbuegnomene: \"Finneren er vinneren\", og du tar sekken og " "gÃ¥r din vei." msgid "Endless Bag of Gold" msgstr "Den uendelige gullbagen" msgid "" "A noblewoman, separated from her traveling companions, asks for your help. " "After escorting her home, she rewards you with a bag filled with gold." msgstr "" "En adelskvinne, som har mistet følge sitt, ber deg om hjelp. Etter Ã¥ ha " "eskortert henne hjem, belønner hun deg med en sekk fylt med gull." msgid "Endless Purse of Gold" msgstr "Den uendelige gullvesken" msgid "" "In your travels, you find a leather purse filled with gold that once " "belonged to a great warrior king who had the ability to transform any " "inanimate object into gold." msgstr "" "Midt under reisen din kommer du over ei skinntaske fylt med gull. Den skal " "en gang ha tilhørt en mektig krigerkonge som visstnok hadde evnen til Ã¥ " "forvandle alle ting til gull." msgid "Nomad Boots of Mobility" msgstr "Nomadens sko av bevegelighet" #, fuzzy msgid "The %{name} increase the hero's movement on land." msgstr "%{name} øker skrittene dine pÃ¥ land." msgid "" "A Nomad trader seeks protection from a tribe of Goblins. For your " "assistance, he gives you a finely crafted pair of boots made from the " "softest leather. Looking closely, you see fascinating ancient carvings " "engraved on the leather." msgstr "" "En forretningsnomade ber om beskyttelse fra en stamme med vetter. Som takk " "for hjelpen din, gir han deg et par forseggjorte sko av den mykeste typen " "skinn. Ved nærmere blikk oppdager du forunderlige eldgamle inngraveringer pÃ¥ " "skinnet." msgid "Traveler's Boots of Mobility" msgstr "Vandrerens bevegelighetssko" msgid "" "Discovering a pair of beautifully beaded boots made from the finest and " "softest leather, you thank the anonymous donor and add the boots to your " "inventory." msgstr "" "Du oppdager et par sko med utsmykninger i perler og lagd av den fineste og " "mykeste typen skinn. Du takker den navnløse giveren og tilføyer skoene til " "samlingen din av skatter." msgid "Lucky Rabbit's Foot" msgstr "Lykkeharens pote" #, fuzzy msgid "The %{name} increases the luck of the hero's army by %{count}." msgstr "%{name} øker flaksen din i kamp med %{count}." msgid "" "A traveling merchant offers you a rabbit's foot, made of gleaming silver " "fur, for safe passage. The merchant explains the charm will increase your " "luck in combat." msgstr "" "En reisende handelsmann tilbyr deg en kaninpote med en skinnende sølvpels " "imot at du beskytter ham pÃ¥ reisen hans. Handelsmannen forklarer deg at " "denne magiske tingen vil bedre flaksen din i kamp." msgid "Golden Horseshoe" msgstr "Den gylne hesteskoen" msgid "" "An ensnared Unicorn whinnies in fright. Murmuring soothing words, you set " "her free. Snorting and stamping her front hoof once, she gallops off. " "Looking down you see a golden horseshoe." msgstr "" "En fastbundet enhjørning vrinsker av redsel. Mens du sier beroliggende ord " "frigjør du henne. Den snøfter og tramper med den framre hoven sin en gang " "før den galopperer av gÃ¥rde. Du ser ned pÃ¥ bakken og finner en gyllen " "hestesko." msgid "Gambler's Lucky Coin" msgstr "Pengespillerens lykkemynt" msgid "" "You have captured a mischievous imp who has been terrorizing the region. In " "exchange for his release, he rewards you with a magical coin." msgstr "" "Du klarer Ã¥ fange en rampete smÃ¥djevel som har drevet ugagn i omrÃ¥det. I " "bytte mot sin frihet, gir han deg en magisk mynt." msgid "Four-Leaf Clover" msgstr "Firkløveren" msgid "" "In the middle of a patch of dead and dry vegetation, to your surprise you " "find a healthy green four-leaf clover." msgstr "" "Midt i et stykke uttørket og livløs eng, finner du til din overraskelse en " "frisk og grønn firkløver." msgid "True Compass of Mobility" msgstr "Det sanne kompasset av bevegelighet" #, fuzzy msgid "The %{name} increases the hero's movement on land and sea." msgstr "%{name} øker skrittene dine pÃ¥ land og til sjøs." msgid "" "An old man claiming to be an inventor asks you to try his latest invention. " "He then hands you a compass." msgstr "" "En gammel mann som hevder at han er en oppfinner ber deg om Ã¥ prøve hans " "nyeste oppfinnelse. Han tar deretter og gir deg et kompass." msgid "Sailor's Astrolabe of Mobility" msgstr "Sjøfarerens sekstant av bevegelighet" #, fuzzy msgid "The %{name} increases the hero's movement on sea." msgstr "%{name} øker skrittene dine til sjøs." msgid "" "An old sea captain is being tortured by Ogres. You save him, and in return " "he rewards you with a wondrous instrument to measure the distance of a star." msgstr "" "Du kommer over en gammel sjøulk som blir torturert av noen jetter. Du befrir " "ham, og til gjengjeld belønner han deg med et underlig verktøy som kan mÃ¥le " "avstanden til stjerner." msgid "Evil Eye" msgstr "Ondskapens øye" msgid "The %{name} reduces the casting cost of curse spells by half." msgstr "%{name} halverer tapet av mana ved bruk av forbannelsesmagi." msgid "" "While venturing into a decrepit hut you find the Skeleton of a long dead " "witch. Investigation of the remains reveals a glass eye rolling around " "inside an empty skull." msgstr "" "Du leter gjennom en forfallen hytte og fÃ¥r øyet pÃ¥ skjelettet til en for " "lengst død heks. Ved nærmere inspeksjon oppdager du et glassøye som ruller " "rundt i hennes tomme hodeskalle." msgid "Enchanted Hourglass" msgstr "Det fortryllede timeglasset" #, fuzzy msgid "" "The %{name} extends the duration of all the hero's spells by %{count} turns." msgstr "" "%{name} forlenger varigheten av alle dine trolldommer med %{count} runder." #, fuzzy msgid "" "A surprise turn in the landscape finds you in the midst of a grisly scene: " "Vultures picking at the aftermath of a terrible battle. Your cursory search " "of the remains turns up an enchanted hourglass." msgstr "" "En brÃ¥ endring i landskapet gir vei til et uhyggelig syn: gribber som Ã¥tsler " "pÃ¥ restene fra et blodig slag. Et raskt søk gjennom omrÃ¥det avdekker et " "fortryllet timeglass." msgid "Gold Watch" msgstr "Gulluret" #, fuzzy msgid "The %{name} doubles the effectiveness of the hero's hypnotize spells." msgstr "%{name} dobler virkningen av trollbinding." msgid "" "In reward for helping his cart out of a ditch, a traveling potion salesman " "gives you a \"magic\" gold watch. Unbeknownst to him, the watch really is " "magical." msgstr "" "Som belønning for Ã¥ ha hjulpet en vandrende eliksirselger med Ã¥ fÃ¥ kjerren " "sin ut av en grøft, gir han deg et \"magisk\" gullur. Uvitende for ham, " "viser det seg at uret faktisk er magisk." msgid "Skullcap" msgstr "Kalotten" msgid "The %{name} halves the casting cost of all mind influencing spells." msgstr "%{name} halverer tapet av mana ved bruk av all slags sinnsmagi." msgid "" "A brief stop at an improbable rural inn yields an exchange of money, tales, " "and accidentally, luggage. You find a magical skullcap in your new backpack." msgstr "" "En kort rast hos et uforventet vertshus langs landeveien fører til " "utveksling av penger, historier, og ved et uhell, reisegods. Du oppdager en " "magisk kalott i den nye ryggsekken din." msgid "Ice Cloak" msgstr "Iskappe" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from cold spells." msgstr "%{name}n halverer all skade styrkene dine tar av kuldemagi." msgid "" "Responding to the panicked cries of a damsel in distress, you discover a " "young woman fleeing from a hungry bear. You slay the beast in the nick of " "time, and the grateful Sorceress weaves a magic cloak from the bear's hide." msgstr "" "Du hører kvinnelige redselsskrik og oppdager ei ungkvinne pÃ¥ flukt fra en " "hungrig bjørn. Ikke et sekund for tidlig fÃ¥r du tatt livet av bjørnen, og " "damen, som viser seg Ã¥ være en trollkvinne, lager deg en magisk kappe av " "bjørneskinnet for Ã¥ vise sin takknemlighet." msgid "Fire Cloak" msgstr "Flammekappe" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from fire spells." msgstr "%{name}n halverer all skade styrkene dine tar fra ildmagi." msgid "" "You've come upon a fight between a Necromancer and a Paladin. The " "Necromancer blasts the Paladin with a fire bolt, bringing him to his knees. " "Acting quickly, you slay the evil one before the final blow. The grateful " "Paladin gives you the fire cloak that saved him." msgstr "" "Du blir plutselig vitne til en pÃ¥gÃ¥ende kamp mellom en sortekunstner og en " "ridderhelt. Sortekunstneren skyter en ildkule pÃ¥ ridderhelten som faller ned " "pÃ¥ kne. Raskt griper du inn og dreper den onde før han fÃ¥r satt nÃ¥destøtet. " "Den takknemlige ridderhelten gir deg en ildkappe for Ã¥ ha kommet ham til " "unnsetning." msgid "Lightning Helm" msgstr "Lynhjelmen" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from lightning " "spells." msgstr "%{name} halverer all skade styrkene dine tar av lynmagi." msgid "" "A traveling tinker in need of supplies offers you a helm with a thunderbolt " "design on its top in exchange for food and water. Curious, you accept, and " "later find out that the helm is magical." msgstr "" "En vandrende kjeleflikker med behov for forsyninger tilbyr deg en hjelm med " "et lynmerke pÃ¥ seg imot mat og vann. Nysgjerrig godtar du byttet, og senere " "oppdager du at hjelmen er magisk." msgid "Evercold Icicle" msgstr "Den evigkalde istappen" #, fuzzy msgid "" "The %{name} causes the hero's cold spells to do %{count} percent more damage " "to enemy troops." msgstr "" "%{name} gjør at kuldemagien din gjør %{count} prosent mer skade pÃ¥ " "fiendtlige styrker." msgid "" "An icicle withstanding the full heat of the noonday sun attracts your " "attention. Intrigued, you break it off, and find that it does not melt in " "your hand." msgstr "" "Oppmerksomheten din fanges av en istapp som unngÃ¥r Ã¥ smelte tross den " "heftige varmen til middagssolen. Fylt av nysgjerrighet brekker du den av og " "oppdager at den ikke smelter i hÃ¥nden din heller." msgid "Everhot Lava Rock" msgstr "Den evigvarme lavasteinen" #, fuzzy msgid "" "The %{name} causes the hero's fire spells to do %{count} percent more damage " "to enemy troops." msgstr "" "%{name} gjør at ildmagien din gjør %{count} prosent mer skade pÃ¥ fiendtlige " "styrker." msgid "" "Your wanderings bring you into contact with a tribe of ape-like beings using " "a magical lava rock that never cools to light their fires. You take pity on " "them and teach them to make fire with sticks. Believing you to be a god, the " "apes give you their rock." msgstr "" "Dine vandringer ender deg opp i et møte med en stamme apelignende " "skapninger. De bruker en magisk lavastein som aldri kjøler seg ned til Ã¥ fÃ¥ " "ild nÃ¥r de trenger det. Du syns synd pÃ¥ dem og lærer dem Ã¥ lage ild med " "pinner. De tror derfor at du er en gud og gir deg steinen sin." msgid "Lightning Rod" msgstr "Lynstav" #, fuzzy msgid "" "The %{name} causes the hero's lightning spells to do %{count} percent more " "damage to enemy troops." msgstr "" "%{name}en gjør at lynmagien din gjør %{count} prosent mer skade pÃ¥ " "fiendtlige styrker." msgid "" "While waiting out a storm, a lighting bolt strikes a nearby cottage's " "lightning rod, which melts and falls to the ground. The tip of the rod, " "however, survives intact and makes your hair stand on end when you touch it. " "Hmm..." msgstr "" "Mens du venter pÃ¥ at et uvær skal gi seg, slÃ¥r et lyn ned i lynavlederen til " "ei hytte like ved. Avlederen løsner og faller pÃ¥ bakken. Endestykket til den " "lange stangen overlever fallet og nÃ¥r du tar pÃ¥ den reiser hÃ¥ret ditt seg. " "Hmm..." msgid "Snake-Ring" msgstr "Slangering" #, fuzzy msgid "The %{name} halves the casting cost of all of the hero's bless spells." msgstr "%{name}en halverer tapet av mana ved bruk av velsignelsesmagi." msgid "" "You've found an oddly shaped ring on the finger of a long dead traveler. The " "ring looks like a snake biting its own tail." msgstr "" "Du fant en merkelig utformet ring pÃ¥ fingeren til en for lengst avdød " "reisende. Ringen ser ut som en slange som biter sin egen hale." msgid "Ankh" msgstr "Henkelkorset" #, fuzzy msgid "" "The %{name} doubles the effectiveness of all of the hero's resurrect and " "animate spells." msgstr "" "%{name} fordobler virkningen til all din gjenopplivnings- og " "gjenreisingsmagi." msgid "" "A fierce windstorm reveals the entrance to a buried tomb. Your investigation " "reveals that the tomb has already been looted, but the thieves overlooked an " "ankh on a silver chain in the dark." msgstr "" "En heftig storm avdekker inngangen til et underjordisk gravkammer. Ved " "nærmere undersøkelse oppdager du at graven allerede har blitt ransaket, men " "tyvene oversÃ¥ et henkelkors med et sølvkjede i mørket." msgid "Book of Elements" msgstr "Boken om elementene" #, fuzzy msgid "" "The %{name} doubles the effectiveness of all of the hero's summoning spells." msgstr "%{name} fordobler virkningsgraden til all din tilkallingsmagi." msgid "" "You come across a conjurer who begs to accompany you and your army awhile " "for safety. You agree, and he offers as payment a copy of the book of the " "elements." msgstr "" "Du kommer over en Ã¥ndemaner som trygler deg om Ã¥ fÃ¥ slÃ¥ følge med deg og " "hæren din for en stund for sin egen trygghet. Du gÃ¥r med pÃ¥ det, og som " "betaling tilbyr han en kopi av Boken om elementene." msgid "Elemental Ring" msgstr "Den elementære ringen" msgid "The %{name} halves the casting cost of all summoning spells." msgstr "%{name} halverer tapet av mana for all din tilkallingsmagi." msgid "" "While pausing to rest, you notice a bobcat climbing a short tree to get at a " "crow's nest. On impulse, you climb the tree yourself and scare off the cat. " "When you look in the nest, you find a collection of shiny stones and a ring." msgstr "" "I løpet av en av rastene dine, legger du merke til en gaupe som klatrer opp " "et lite tre for Ã¥ komme seg til et krÃ¥kerede. Av instinkt klatrer du ogsÃ¥ " "opp treet og jager vekk kattedyret. NÃ¥r du titter ned i reiret finner du en " "liten samling glinsende steiner og en ring." msgid "Holy Pendant" msgstr "Det hellige halskjedet" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to curse spells." msgstr "%{name} gjør alle krigerne dine immune mot forbannelsesmagi." msgid "" "In your wanderings you come across a hermit living in a small, tidy hut. " "Impressed with your mission, he takes time out from his meditations to bless " "and give you a charm against curses." msgstr "" "Ferden din blir avbrutt av et møte med en einstøing bosatt i ei ryddig, lita " "hytte. Han blir imponert nÃ¥r han fÃ¥r høre om oppdraget ditt, og han " "bestemmer seg for Ã¥ ta en pause fra mediteringen for Ã¥ velsigne deg og gi " "deg en magisk gjenstand som vil verne deg mot forbannelser." msgid "Pendant of Free Will" msgstr "Halskjedet av fri vilje" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to hypnotize spells." msgstr "%{name} gjør alle krigerne dine immune mot trollbinding." msgid "" "Responding to cries for help, you find river Sprites making a sport of " "dunking an old man. Feeling vengeful, you rescue the man and drag a Sprite " "onto dry land for awhile. The Sprite, uncomfortable in the air, gives you a " "magic pendant to let him go." msgstr "" "Du hører rop om hjelp og oppdager elvefeer som har det gøy med Ã¥ plage en " "gammel mann. Synet fyller deg av sinne og du bestemmer deg for Ã¥ komme " "mannen til unnsetning. Du tar tak i en av feene og drar den med deg opp pÃ¥ " "land. Feen pines utenfor vannet og imot sin frihet gir den deg et magisk " "halskjede." msgid "Pendant of Life" msgstr "Halskjedet av liv" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to death spells." msgstr "%{name} gjør alle krigerne dine immune mot dødsmagi." msgid "" "A brief roadside encounter with a small caravan and a game of knucklebones " "wins a magic pendant. Its former owner says that it protects from " "Necromancers' death spells." msgstr "" "Et kort møte med en liten karavane og en runde hÃ¥ndbak som du vinner, gjør " "deg til den nye eieren av et magisk halskjede. Dens forrige eier forklarer " "deg at halskjedet beskytter mot sortekunstneres dødsbesvergelser." msgid "Serenity Pendant" msgstr "Halskjedet av sinnsro" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to berserk spells." msgstr "%{name} gjør alle krigerne dine immune mot berserkergang." msgid "" "The sounds of combat draw you to the scene of a fight between an old " "Barbarian and an eight-headed Hydra. Your timely intervention swings the " "battle in favor of the man, and he rewards you with a pendant he used to use " "to calm his mind for battle." msgstr "" "Lyder av kamp trekker deg mot det pÃ¥gÃ¥ende slaget mellom en gammel barbar og " "en Ã¥ttehodet hydra. Du griper inn akkurat i tide til Ã¥ snu kampen mot " "barbarens favør. EtterpÃ¥ takker han deg med Ã¥ gi deg et halskjede han pleide " "Ã¥ bruke for Ã¥ berolige sinnet før kamp." msgid "Seeing-eye Pendant" msgstr "Halskjedet av det ublinde øyet" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to blindness spells." msgstr "%{name} gjør alle krigerne dine immune mot forblindelsesmagi." msgid "" "You come upon a very old woman, long blind from cataracts and dying alone. " "You tend to her final needs and promise a proper burial. Grateful, she gives " "you a magic pendant emblazoned with a stylized eye. It lets you see with " "your eyes closed." msgstr "" "Du kommer over en veldig gammel kvinne, for lengst blind av stær og i ferd " "med Ã¥ dø alene. Du tar deg av hennes siste behov og lover henne Ã¥ gi henne " "en ordentlig begravelse. Av takknemlighet gir hun deg et magisk halskjede " "prydet med et nøye utformet øye. Halskjedet lar deg kunne se med øynene " "lukket." msgid "Kinetic Pendant" msgstr "Halskjedet av bevegelse" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to paralyze spells." msgstr "%{name} gjør alle krigerne dine immune mot lammelsesmagi." msgid "" "You come across a golem wearing a glowing pendant and blocking your way. " "Acting on a hunch, you cut the pendant from its neck. Deprived of its power " "source, the golem breaks down, leaving you with the magical pendant." msgstr "" "Du kommer over en golem som bærer et skinnende halskjede og som sperrer " "veien for deg. Du handler ut ifra magefølelsen din og kutter halskjedet fra " "halsen pÃ¥ den. NÃ¥ uten en energikilde faller golemen sammen og etterlater " "deg det magiske halskjedet." msgid "Pendant of Death" msgstr "Dødens halskjede" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to holy spells." msgstr "%{name} gjør alle krigerne dine immune mot hellig magi." msgid "" "A quick and deadly battle with a Necromancer wins you his magical pendant. " "Later, a Wizard tells you that the pendant protects undead under your " "control from holy word spells." msgstr "" "En rask og dødelig kamp med en sortekunstner vinner deg det magiske " "halskjedet hans. Senere forteller en magimester deg at halskjedet beskytter " "vandøde under din makt fra trolldommer av hellige ord." msgid "Wand of Negation" msgstr "Tryllestaven av benektelse" #, fuzzy msgid "" "The %{name} makes all of the hero's troops immune to dispel magic spells." msgstr "%{name} gjør alle krigerne dine immune mot dødsmagi." #, fuzzy msgid "" "You meet an old Wizard friend of yours traveling in the opposite direction. " "He presents you with a gift: A wand that prevents the use of the dispel " "magic spell on your allies." msgstr "" "Du møter pÃ¥ en av dine gamle magimestervenner som reiser i motsatt retning " "av deg. Han overrekker deg en gave: En tryllestav som forhindrer bruken av " "trolldommen magiopphevelse mot krigerne dine." msgid "Golden Bow" msgstr "Gullbuen" #, fuzzy msgid "" "The %{name} eliminates the %{count} percent penalty for the hero's troops " "shooting past obstacles (e.g. castle walls)." msgstr "" "%{name} fjerner minskningen pÃ¥ %{count} prosent av skaden troppene dine gjør " "nÃ¥r de skyter pÃ¥ tvers av hindringer\n" "(f.eks. forbi ringmurer)." msgid "" "A chance meeting with a famous Archer finds you in a game of knucklebones " "pitting his bow against your horse. You win." msgstr "" "Helt tilfeldig møter du pÃ¥ en berømt bueskytter og dette fører til en runde " "hÃ¥ndbak der skytteren vedder bua si mot hesten din. Du vinner." msgid "Telescope" msgstr "Himmelkikkerten" #, fuzzy msgid "" "The %{name} increases the amount of terrain the hero reveals when " "adventuring by %{count} extra square." msgstr "" "%{name} utvider omrÃ¥det som helten din avdekker under forflytning med " "%{count} ekstra hakk." msgid "" "A merchant from far away lands trades you a new invention of his people for " "traveling supplies. It makes distant objects appear closer, and he calls " "it...\n" "\n" "a telescope." msgstr "" "En forretningsmann fra et fjerntliggende land gÃ¥r med pÃ¥ Ã¥ bytte sin nye " "oppfinnelse mot reiseforsyninger fra deg. Denne duppedingsen fÃ¥r fjerne ting " "til Ã¥ framstÃ¥ nærmere, og han har gitt den navnet...\n" "\n" "teleskop." msgid "Statesman's Quill" msgstr "Statsmannens fjærpenn" #, fuzzy msgid "" "The %{name} reduces the cost of surrender to %{count} percent of the total " "cost of troops the hero has in their army." msgstr "" "%{name} reduserer prisen for Ã¥ overgi seg med %{count} prosent av den totale " "verdien av hærstyrken din." msgid "" "You pause to help a diplomat with a broken axle fix his problem. In " "gratitude, he gives you a writing quill with magical properties which he " "says will \"help people see things your way\"." msgstr "" "Du tar et avbrekk fra reisen din for Ã¥ hjelpe en megler med Ã¥ fikse ei " "ødelagt hjulaksling. For Ã¥ vise sin takknemlighet gir han deg ei fjærpenn " "med magiske egenskaper. Han forklarer deg at den vil \"gjøre folk " "tilbøyelige til Ã¥ se ting pÃ¥ din mÃ¥te\"." msgid "Wizard's Hat" msgstr "Magimesterens hatt" #, fuzzy msgid "" "The %{name} increases the duration of the hero's spells by %{count} turns." msgstr "%{name} øker varigheten til trolldommene dine med %{count} runder." msgid "" "You see a Wizard fleeing from a Griffin and riding like the wind. The Wizard " "opens a portal and rides through, getting his hat knocked off by the edge of " "the gate. The Griffin follows; the gate closes. You pick the hat up, dust it " "off, and put it on." msgstr "" "Du fÃ¥r øye pÃ¥ en magimester ridende som vinden pÃ¥ flukt fra en griff. " "Magimesteren Ã¥pner en portal og fyker gjennom den, men i farten fÃ¥r han " "hatten sin slÃ¥tt av av munningen til portalen. Griffen følger etter og " "portalen lukkes. Du plukker opp hatten, børster av den, og setter den pent " "pÃ¥ hodet ditt." msgid "Power Ring" msgstr "Kraftsringen" #, fuzzy msgid "The %{name} returns %{count} extra spell points per day to the hero." msgstr "%{name} fyller opp %{count} mana til helten din hver dag." msgid "" "You find a small tree that closely resembles the great Warlock Carnauth with " "a ring around one of its twigs. Scraps of clothing and rotting leather lead " "you to suspect that it IS Carnauth, transformed. Since you can't help him, " "you take the magic ring." msgstr "" "Du finner et lite tre som ligner svært pÃ¥ den mektige trollmannen Karnauth. " "Treet har en ring rundt en av greinene sine. Rester av bekledning og rÃ¥tne " "lærstykker fÃ¥r deg til Ã¥ mistenke at det faktisk ER Karnauth, dog noe " "forvandlet. Siden du ikke ser noen vei du kan hjelpe ham, tar du den magiske " "ringen." msgid "Ammo Cart" msgstr "Ammunisjonsvogn" #, fuzzy msgid "" "The %{name} provides endless ammunition for all of the hero's troops that " "shoot." msgstr "%{name}en forsyner de skytende krigerne dine med uendelig ammunisjon." msgid "" "An ammunition cart in the middle of an old battlefield catches your eye. " "Inspection shows it to be in good working order, so you take it along." msgstr "" "En ammunisjonsvogn plassert midt i en forlatt slagmark fanger " "oppmerksomheten din. Nærmere inspeksjon viser at den er i god stand, sÃ¥ du " "tar den med deg." msgid "Tax Lien" msgstr "Skattepant" #, fuzzy msgid "The %{name} costs the hero %{count} gold pieces per day." msgstr "%{name}en koster deg %{count} gullstykker hver dag." msgid "" "Your big spending habits have earned you a massive tax bill that you can't " "hope to pay. The tax man takes pity and agrees to only take 250 gold a day " "from your account for life. Check here if you want one dollar to go to the " "presidential campaign election fund." msgstr "" "Din vane for Ã¥ bruke store penger har endt deg opp med en gedigen " "skattegjeld som du ikke har nubbe sjans til Ã¥ nedbetale. Kemneren syns synd " "pÃ¥ deg og gÃ¥r med pÃ¥ Ã¥ kun innkreve 250 gullstykker per dag fra deg resten " "av livet. Hak av herunder hvis du vil at én denar skal brukes til Ã¥ støtte " "presidentens valgfond." msgid "Hideous Mask" msgstr "Den avskyelige masken" #, fuzzy msgid "The %{name} prevents all 'wandering' armies from joining the hero." msgstr "" "%{name} hindrer alle \"omstreifende\" styrker fra Ã¥ bli med helten din." #, fuzzy msgid "" "Your looting of the grave of Sinfilas Gardolad, the famous shapeshifting " "Warlock, unearths his fabled mask. Trembling, you put it on and it twists " "your visage into an awful grimace! Oh no! It's actually the hideous mask of " "Gromluck Greene, and you are stuck with it." msgstr "" "Din ransaking av graven til Sinfilas Gardolad, den berømte hamskiftende " "trollmannen, avdekker hans legendariske maske. Skjelvende tar du den pÃ¥ deg " "og den forvrenger fjeset ditt til et uhyggelig uttrykk! Ã… nei! Dette er " "egentlig den avskyelige masken til Gromluck Greene, og nÃ¥ fÃ¥r du ikke " "kvittet deg med den." msgid "Endless Pouch of Sulfur" msgstr "Den uendelige posen av svovel" msgid "The %{name} provides %{count} unit of sulfur per day." msgstr "%{name} gir deg %{count} enhet svovel hver dag." msgid "" "You visit an alchemist who, upon seeing your army, is swayed by the " "righteousness of your cause. The newly loyal subject gives you his endless " "pouch of sulfur to help with the war effort." msgstr "" "Du besøker en alkymist som, ved synet av hæren din, blir grepet av " "rettskaffenheten til kallet ditt. Din nye trofaste støttespiller gir deg " "hans uendelige svovelpose for Ã¥ bistÃ¥ med innsatsen til krigen." msgid "Endless Vial of Mercury" msgstr "Den uendelige kvikksølvkolben" msgid "The %{name} provides %{count} unit of mercury per day." msgstr "%{name} gir deg %{count} enhet kvikksølv hver dag." msgid "" "A brief stop at a hastily abandoned Wizard's tower turns up a magical vial " "of mercury that always has a little left on the bottom. Recognizing a " "treasure when you see one, you cap it and slip it in your pocket." msgstr "" "Et kort stopp ved en magimesters tÃ¥rn som har blitt forlatt i hastverk, " "avdekker en kolbe med kvikksølv som ser ut til Ã¥ alltid ha igjen litt pÃ¥ " "bunnen. Du kjenner igjen en skatt nÃ¥r du ser en, og du setter en kork i den " "og slipper den i tasken din." msgid "Endless Pouch of Gems" msgstr "Den uendelige edesteinsposen" msgid "The %{name} provides %{count} unit of gems per day." msgstr "%{name} gir deg %{count} enhet edelsten hver dag." msgid "" "A short rainstorm brings forth a rainbow...and you can see the end of it. " "Riding quickly, you seize the pot of gold you find there. The leprechaun who " "owns it, unable to stop you from taking it, offers an endless pouch of gems " "for the return of his gold. You accept." msgstr "" "En kortvarig regnbyge fÃ¥r regnbuen til Ã¥ dukke opp... og sannelig klarer du " "Ã¥ se enden pÃ¥ den. Du rir i full fart bort for Ã¥ gripe gullgryta som " "befinner seg der. Eieren dens, en regnbuegnom, klarer ikke Ã¥ hindre deg fra " "Ã¥ ta den og tilbyr heller en uendelig pose med edelstener i bytte mot gullet " "sitt. Du gÃ¥r med pÃ¥ det." msgid "Endless Cord of Wood" msgstr "Den uendelige vedfavnen" msgid "The %{name} provides %{count} unit of wood per day." msgstr "%{name} gir deg %{count} enheter ved hver dag." msgid "" "Pausing to rest and light a cook fire, you pull wood out of a nearby pile of " "dead wood. As you keep pulling wood from the pile, you notice that it " "doesn't shrink. You realize to your delight that the wood is enchanted, so " "you take it along." msgstr "" "Du tar en rast for lage bÃ¥l til matlaging og henter ved fra en haug med " "morken ved i nærheten. I mens du henter ved fra haugen legger du merke til " "at den ikke blir mindre. Du innser til din glede at veden er fortryllet og " "du tar den derfor med deg." msgid "Endless Cart of Ore" msgstr "Den uendelige malmvogna" msgid "The %{name} provides %{count} unit of ore per day." msgstr "%{name} gir deg %{count} enheter malm hver dag." msgid "" "You've found a Goblin weapon smithy making weapons for use against humans. " "With a tremendous yell you and your army descend upon their camp and drive " "them away. A search finds a magic ore cart that never runs out of iron." msgstr "" "Du har kommet over en vettesmed som lager vÃ¥pen til bruk mot mennesker. Med " "et mektig brøl stormer du og hæren din ned pÃ¥ leiren og jager dem vekk. " "Ransakingen avdekker en magisk malmvogn som aldri gÃ¥r tom for jern." msgid "Endless Pouch of Crystal" msgstr "Den uendelige krystallposen" msgid "The %{name} provides %{count} unit of crystal per day." msgstr "%{name} gir deg %{count} enhet krystall hver dag." #, fuzzy msgid "" "Taking shelter from a storm in a small cave, you notice a small patch of " "crystal in one corner. Curious, you break a piece off and notice that the " "original crystal grows the lost piece back. You decide to stuff the entire " "patch into a pouch and take it with you." msgstr "" "Du søker ly i en liten hule fra en storm og oppdager en liten mengde " "krystaller i et hjørne. Av nysgjerrighet brekker du av en bit og legger " "merke til at den opprinnelige krystallen gror den tapte delen tilbake. Du " "bestemmer deg for Ã¥ stappe hele mengden i en pose og tar den med deg." msgid "Spiked Helm" msgstr "Den piggete hjelmen" msgid "" "Your army is ambushed by a small tribe of wild (and none too bright) Orcs. " "You fend them off easily and the survivors flee in all directions. One of " "the Orcs was wearing a polished spiked helm. Figuring it will make a good " "souvenir, you take it." msgstr "" "Hæren din blir overfalt av en liten stamme av ville (og ikke særlig kloke) " "orker. Du tar lett hÃ¥nd om dem og de fÃ¥ overlevende flykter i alle " "retninger. En av orkene hadde pÃ¥ seg en blankpolert hjelm med pigger. Du " "tenker at den vil gjøre seg bra som en suvenir og tar den med deg." msgid "Spiked Shield" msgstr "Det piggete skjoldet" msgid "" "You come upon a bridge spanning a dry gully. Before you can cross, a Troll " "steps out from under the bridge and demands payment before it will permit " "you to pass. You refuse, and the Troll charges, forcing you to slay it. You " "take its spiked shield as a trophy." msgstr "" "Du kommer over ei bru som strekker seg over en uttørket kløft. Før du rekker " "Ã¥ krysse den dukker et troll opp fra under brua og krever betaling før det " "lar deg slippe forbi. Du nekter Ã¥ betale og trollet stormer mot deg, og du " "blir nødt til Ã¥ drepe det. Du tar det taggete skjoldet til det som trofé." msgid "White Pearl" msgstr "Den hvite perlen" #, fuzzy msgid "" "A walk across a dry saltwater lake bed yields an unlikely prize: A white " "pearl amidst shattered shells and debris." msgstr "" "En spasertur langs et uttørket brakkvann fÃ¥r et øyeblikks opphold nÃ¥r du " "oppdager noe usedvanlig: ei hvit perle blant noen ødelagte skjell og andre " "rester." msgid "Black Pearl" msgstr "Den sorte perlen" msgid "" "Rumors of a Griffin of unusual size preying upon the countryside lead you to " "its cave lair. A quick, brutal fight dispatches the beast, and a search of " "its foul nest turns up a huge black pearl." msgstr "" "Ryktene om en usedvanlig stor griff som terroriserte folk ute pÃ¥ landsbygda " "førte deg til hulen dens. Et raskt men brutal slag blir til slutten pÃ¥ " "beistet. Du gÃ¥r gjennom de groteske restene i redet og oppdager en stor, " "svart perle." msgid "Magic Book" msgstr "Tryllebok" #, fuzzy msgid "The %{name} enables the hero to cast spells." msgstr "%{name}a gjør at du kan bruke trolldom." msgid "Dummy 1" msgstr "Plassholder 1" msgid "The reserved artifact." msgstr "Plassholderskatten." msgid "Dummy 2" msgstr "Plassholder 2" msgid "Dummy 3" msgstr "Plassholder 3" msgid "Dummy 4" msgstr "Plassholder 4" msgid "Spell Scroll" msgstr "Trolldomsrull" #, fuzzy msgid "" "This %{name} gives the hero the ability to cast the %{spell} spell if the " "hero has a Magic Book." msgstr "" "Denne %{name}en gir helten din evnen til Ã¥ bruke trolldommen %{spell}, gitt " "at helten din har ei tryllebok." msgid "" "You find an elaborate container which houses an old vellum scroll. The runes " "on the container are very old, and the artistry with which it was put " "together is stunning. As you pull the scroll out, you feel imbued with " "magical power." msgstr "" "Du finner en detaljert beholder som rommer et gammelt pergament i " "kalveskinn. Runene pÃ¥ beholderen er veldig gamle, og hÃ¥ndverket som ligger " "bak den er fabelaktig. Idet du tar ut rullen føler du deg fylt med magisk " "kraft." msgid "Arm of the Martyr" msgstr "Martyrens arm" #, fuzzy msgid "" "The %{name} increases the hero's spell power by %{count} but adds the undead " "morale penalty." msgstr "" "%{name} øker kraften din med %{count} men legger til vandød " "kampÃ¥ndspÃ¥virkning." msgid "" "One of the less intelligent members of your party picks up an arm off of the " "ground. Despite its missing a body, it is still moving. Your troops find the " "dismembered arm repulsive, but you cannot bring yourself to drop it: it " "seems to hold some sort of magical power that influences your decision " "making." msgstr "" "En av de mindre begavede medlemmene av følget ditt plukker opp en arm fra " "bakken. Til tross for at den mangler en kropp, beveger den seg. Styrkene " "dine syns den kroppsløse armen er motbydelig, men du klarer ikke Ã¥ fÃ¥ deg " "til Ã¥ kaste den: den ser ut til Ã¥ ha en slags magisk pÃ¥virkning over " "beslutningsevnen din." msgid "Breastplate of Anduran" msgstr "Andurans brystplate" #, fuzzy msgid "The %{name} increases the hero's defense by %{count}." msgstr "%{name} øker forsvarsevnen din med %{count}." msgid "" "You come upon a sign. It reads: \"Here lies the body of Anduran. Bow and " "swear fealty, and you shall be rewarded.\" You decide to do as it says. As " "you stand up, you feel a coldness against your skin. Looking down, you find " "that you are suddenly wearing a gleaming, ornate breastplate." msgstr "" "Du kommer over et skilt. Det stÃ¥r: \"Her ligger kroppen til Anduran. Bøy deg " "og sverg troskap, og du vil bli belønnet.\" Du bestemmer deg for Ã¥ gjøre det " "som stÃ¥r. Idet du reiser deg, føler du en kulde treffe huden din. NÃ¥r du ser " "ned oppdager du at du plutselig har pÃ¥ deg en skinnende og vakker brystplate." msgid "Broach of Shielding" msgstr "BrystnÃ¥len av beskyttelse" msgid "" "The %{name} provides %{count} percent protection from Armageddon and " "Elemental Storm, but decreases spell power by 2." msgstr "" "%{name} gir %{count} prosent beskyttelse mot ragnarok og elementær storm, " "men minsker kraften med 2." msgid "" "A kindly Sorceress thinks that your army's defenses could use a magical " "boost. She offers to enchant the Broach that you wear on your cloak, and you " "accept." msgstr "" "En snill trollkjerring syns at forsvarsevnen til hæren din kunne ha trengt " "en magisk forsterkning. Hun tilbyr Ã¥ fortrylle brystnÃ¥len du har pÃ¥ kappen " "din, og du godtar det." msgid "Battle Garb of Anduran" msgstr "Andurans kamputstyr" #, fuzzy msgid "" "The %{name} combines the powers of the three Anduran artifacts. It provides " "maximum luck and morale for the hero's troops and gives the hero the Town " "Portal spell." msgstr "" "%{name} slÃ¥r sammen kreftene til alle Andurans tre skatter. Den gir maksimal " "flaks og kampÃ¥nd til styrkene dine og gir deg mulighet til Ã¥ bruke " "trolldommen Byportal." msgid "" "Out of pity for a poor peasant, you purchase a chest of old junk they are " "hawking for too much gold. Later, as you search through it, you find it " "contains the 3 pieces of the legendary battle garb of Anduran!" msgstr "" "Av medlidenhet til en fattig bonde kjøper du ei kiste med gammelt skrap som " "han selger for alt for mye gull. Senere mens du gÃ¥r gjennom den oppdager du " "at den inneholder de 3 delene av det legendariske kamputstyret til Anduran!" msgid "Crystal Ball" msgstr "Krystallkule" #, fuzzy msgid "" "The %{name} lets the hero get more specific information about monsters, " "enemy heroes, and castles nearby the hero." msgstr "" "%{name}n lar deg fÃ¥ mer detaljert informasjon om monster, fiendtlige helter " "og borger i nærheten av helten som holder den." msgid "" "You come upon a caravan of gypsies who are feasting and fortifying their " "bodies with mead. They call you forward and say \"If you prove that you can " "dance the Rama-Buta, we will reward you.\" You don't know it, but try " "anyway. They laugh hysterically, but admire your bravery, giving you a " "Crystal Ball." msgstr "" "Du kommer over en karavane med vandringsfolk som storfester og fyller " "kroppene sine med mjød. De kaller deg fram og sier \"Hvis du kan bevise at " "du kan danse Rama-Butaen vil vi belønne deg.\" Du kan den ikke, men prøver " "likevel. De ler seg i hjel, men beundrer motet ditt og gir deg en " "krystallkule." msgid "Heart of Fire" msgstr "Ildhjerte" msgid "" "The %{name} provides %{count} percent protection from fire, but doubles the " "damage taken from cold." msgstr "" "%{name}t gir %{count} prosent beskyttelse mot ild, men dobler skaden fra " "kulde." msgid "" "You enter a recently burned glade and come upon a Fire Elemental sitting " "atop a rock. It looks up, its flaming face contorted in a look of severe " "pain. It then tosses a glowing object at you. You put up your hands to block " "it, but it passes right through them and sears itself into your chest." msgstr "" "Du kommer inn i en nylig nedbrent skogsklarning og møter en ildÃ¥nd som " "sitter pÃ¥ en stein. Den ser opp pÃ¥ deg med sitt brennende ansikt, og et " "uttrykk preget av sterk smerte. Plutselig kaster den et skinnende objekt pÃ¥ " "deg. Du tar fram hendene dine for Ã¥ skjerme deg, men det gÃ¥r rett igjennom " "dem og brenner seg inn i brystkassa di." msgid "Heart of Ice" msgstr "Ishjerte" msgid "" "The %{name} provides %{count} percent protection from cold, but doubles the " "damage taken from fire." msgstr "" "%{name}t gir %{count} prosent beskyttelse mot kulde, men dobler skaden fra " "ild." #, fuzzy msgid "" "Suddenly, a biting coldness engulfs your body. You seize up, falling from " "your horse. The pain subsides, but you still feel as if your chest is " "frozen. As you pick yourself up off of the ground, you hear hearty laughter. " "You turn around just in time to see a Frost Giant run off into the woods and " "disappear." msgstr "" "Ut av intet griper en sterk kulde fatt i kroppen din. Du fryser til og " "faller av hesten din. Smerten gÃ¥r over, men det føles fortsatt ut som at " "brystkassa di er av is. Du tar deg selv opp fra bakken og hører en rungende " "latter. Du snur deg rundt akkurat i tide til Ã¥ fÃ¥ øye pÃ¥ en frostkjempe som " "løper av gÃ¥rde inn i skogen og forsvinner." msgid "Helmet of Anduran" msgstr "Andurans hjelm" msgid "" "You spy a gleaming object poking up out of the ground. You send a member of " "your party over to investigate. He comes back with a golden helmet in his " "hands. You realize that it must be the helmet of the legendary Anduran, the " "only man who was known to wear solid gold armor." msgstr "" "Du fÃ¥r øynene pÃ¥ en glinsende gjenstand som stikker opp av bakken. Du sender " "en ufrivillig frivillig fra følget ditt bort for Ã¥ ta en nærmere titt. Han " "kommer tilbake med en gyllen hjelm i hendene. Du innser at det mÃ¥ være " "hjelmen til den legendariske Anduran, det eneste mennesket kjent for Ã¥ bruke " "en rustning laget av rent gull." msgid "Holy Hammer" msgstr "Den hellige hammeren" #, fuzzy msgid "" "You come upon a battle where a Paladin has been mortally wounded by a group " "of Zombies. He asks you to take his hammer and finish what he started. As " "you pick it up, it begins to hum, and then everything becomes a blur. The " "Zombies lie dead, the hammer dripping with blood. You strap it to your belt." msgstr "" "Du kommer over en slagmark hvor en ridderhelt har blitt dødelig sÃ¥ret av en " "gruppe zombier. Han ber deg om Ã¥ ta hammeren hans og avslutte det han " "begynte. Idet du plukker den opp, begynner den Ã¥ dure før alt blir tÃ¥kete " "for deg. NÃ¥r du kommer til igjen, ligger zombiene døde, og det drypper blod " "fra hammeren. Du fester hammeren til beltet ditt." msgid "Legendary Scepter" msgstr "Septeret av legender" msgid "The %{name} adds %{count} points to all attributes." msgstr "%{name} legger til %{count} poeng til alle egenskapene dine." msgid "" "Upon cresting a small hill, you come upon a ridiculous looking sight. A " "Sprite is attempting to carry a Scepter that is almost as big as it is. " "Trying not to laugh, you ask, \"Need help?\" The Sprite glares at you and " "answers: \"You think this is funny? Fine. You can carry it. I much prefer " "flying anyway.\"" msgstr "" "Idet du kommer til toppen av en kolle, kommer du over et latterlig syn: en " "fe som prøver Ã¥ bære et septer som er nesten like stort som seg selv. " "Samtidig som du prøver Ã¥ holde deg fra Ã¥ le, spør du \"Trenger du hjelp?\" " "Feen myser bort pÃ¥ deg og svarer: \"Syns du dette er morsomt? Greit. Da kan " "DU fÃ¥ bære den. Jeg foretrekker Ã¥ fly uansett.\"" msgid "" "An old seaman tells you a tale of an enchanted masthead that he used in his " "youth to rally his crew during times of trouble. He then hands you a faded " "map that shows where he hid it. After much exploring, you find it stashed " "underneath a nearby dock." msgstr "" "En gammel sjøulk forteller deg en historie om en fortryllet mastetopp som " "han brukte i sine yngre dager for Ã¥ samle mannskapet sitt i vanskelig " "farvann. Han gir deg deretter et falmet kart som viser hvor han gjemte den. " "Etter mye leting finner du den bortgjemt under en brygge i nærheten." msgid "Masthead" msgstr "Mastetoppen" #, fuzzy msgid "" "The %{name} boosts the hero's troops' luck and morale by %{count} each in " "sea combat." msgstr "%{name} øker flaksen og kampÃ¥nden din med %{count} under sjøslag." msgid "Sphere of Negation" msgstr "Kulen av benektelse" msgid "The %{name} disables all spell casting, for both sides, in combat." msgstr "%{name} stanser all bruk av trolldom for begge sider i kampen." msgid "" "You stop to help a Peasant catch a runaway mare. To show his gratitude, he " "hands you a tiny sphere. As soon as you grasp it, you feel the magical " "energy drain from your limbs..." msgstr "" "Du stanser for Ã¥ hjelpe en bonde med Ã¥ fange en løpsk hingst. For Ã¥ vise sin " "takknemmelighet gir han deg en liten kule. Idet du griper den føler du den " "magiske energien tappes fra lemmene dine." msgid "Staff of Wizardry" msgstr "Trolldomsstaven" #, fuzzy msgid "The %{name} boosts the hero's spell power by %{count}." msgstr "%{name} øker kraften din med %{count}." msgid "" "While out scaring up game, your troops find a mysterious staff levitating " "about three feet off of the ground. They hand it to you, and you notice an " "inscription. It reads: \"Brains best brawn and magic beats might. Heed my " "words, and you'll win every fight.\"" msgstr "" "Imens de var ute og fanget vilt fant styrkene dine en mystisk stav som " "svevde en meter over bakken. De overrekker deg den og du legger merke til en " "innrisset tekst. Det stÃ¥r: \"Vett slÃ¥r muskler og magi trumfer et slag. Lytt " "til mine ord og du vil vinne hver dag.\"" msgid "Sword Breaker" msgstr "Sverdknuser" #, fuzzy msgid "The %{name} increases the hero's defense by %{count} and attack by 1." msgstr "%{name}en øker forsvarsevnen din med %{count} og angrepsevnen med 1." msgid "" "A former Captain of the Guard admires your quest and gives you the enchanted " "Sword Breaker that he relied on during his tour of duty." msgstr "" "En tidligere kaptein av vaktstyrkene blir grepet av respekt for deg nÃ¥r han " "fÃ¥r høre om oppdraget ditt, og han gir deg den magiske sverdknuseren som han " "satte sin lit til da han var i tjeneste." msgid "Sword of Anduran" msgstr "Andurans sverd" msgid "" "A Troll stops you and says: \"Pay me 5,000 gold, or the Sword of Anduran " "will slay you where you stand.\" You refuse. The troll grabs the sword " "hanging from its belt, screams in pain, and runs away. Picking up the fabled " "sword, you give thanks that half-witted Trolls tend to grab the wrong end of " "sharp objects." msgstr "" "Et troll stopper deg og sier: \"Betal meg 5000 gullstykker, ellers vil " "Andurans sverd ende ditt liv her og nÃ¥.\" Du nekter. Trollet griper tak i " "sverdet pÃ¥ beltet sitt, gir fra seg et ul av smerte, og løper vekk. Mens du " "plukker opp det berømte sverdet, er du takknemlig for at tomsete troll ofte " "griper tak i feil ende av skarpe gjenstander." msgid "Spade of Necromancy" msgstr "Sortekunstnerens spade" #, fuzzy msgid "The %{name} gives the hero increased necromancy skill." msgstr "%{name} forbedrer sortekunstevnen din." msgid "" "A dirty shovel has been thrust into a dirt mound nearby. Upon investigation, " "you discover it to be the enchanted shovel of the Gravediggers, long thought " "lost by mortals." msgstr "" "En skitten spade har blitt kjørt inn i en jordhaug i nærheten. Ved en " "nærmere titt oppdager du at det er den fortryllede spaden til Gravleggerne, " "lenge trodd Ã¥ være forsvunnet av de dødelige." msgid "spellBonus|selected by user" msgstr "" msgid "Wood" msgstr "Ved" msgid "Mercury" msgstr "Kvikksølv" msgid "Ore" msgstr "Malm" msgid "Sulfur" msgstr "Svovel" msgid "Crystal" msgstr "Krystall" msgid "Gems" msgstr "Edelstener" msgid "Gold" msgstr "Gull" msgid "" "There are seven resources in Heroes 2, used to build and improves castles, " "purchase troops and recruit heroes. Gold is the most common, required for " "virtually everything. Wood and ore are used for most buildings. Gems, " "Mercury, Sulfur and Crystal are rare magical resources used for the most " "powerful creatures and buildings." msgstr "" "Det fins sju rÃ¥varer i Heroes II. Disse brukes til Ã¥ bygge pÃ¥ og forbedre " "byer, samt Ã¥ verve krigere og helter.\n" "Gull er den vanligste rÃ¥varen og trengs i bunn og grunn til alt.\n" "Ved og malm blir brukt til de fleste bygninger.\n" "Edelstener, kvikksølv, svovel og krystall er sjeldne, magiske rÃ¥varer som " "trengs for de sterkeste krigerne og mest avanserte bygningene." msgid "" "Causes a giant fireball to strike the selected area, damaging all nearby " "creatures." msgstr "" "FÃ¥r en stor ildkule til Ã¥ slÃ¥ ned pÃ¥ det utvalgte omrÃ¥det og skader alle " "nærstÃ¥ende krigere." msgid "Fireball" msgstr "Ildkule" msgid "Fireblast" msgstr "Ildblest" msgid "" "An improved version of fireball, fireblast affects two hexes around the " "center point of the spell, rather than one." msgstr "" "En forbedret utgave av ildkulen. Ildsmellet pÃ¥virker to felt rundt " "midtpunktet for trolldommen, i stedet for kun én." msgid "Causes a bolt of electrical energy to strike the selected creature." msgstr "Sender et kraftig elektrisk støt ned pÃ¥ en utvalgt kriger." msgid "Lightning Bolt" msgstr "Lynnedslag" msgid "Chain Lightning" msgstr "Kjedelyn" #, fuzzy msgid "" "Causes a bolt of electrical energy to strike a selected creature, then " "strike the nearest creature with half damage, then strike the NEXT nearest " "creature with half again damage, and so on, until it becomes too weak to be " "harmful. Warning: This spell can hit your own creatures!" msgstr "" "FÃ¥r et støt av elektrisk kraft til Ã¥ slÃ¥ ned pÃ¥ den utvalgte krigeren, " "deretter pÃ¥ den nærmeste krigeren med kun halvparten av styrken, og sÃ¥ pÃ¥ " "den som er nærmest den igjen med nok en gang halvparten, og sÃ¥ videre helt " "til lynet blir sÃ¥ svakt at det gjør skade. Advarsel: Denne trolldommen kan " "treffe dine egne krigere!" msgid "Teleport" msgstr "" "Tele-\n" "portering" msgid "" "Teleports the creature you select to any open position on the battlefield." msgstr "Teleporterer den utvalgte krigeren til en ledig rute pÃ¥ slagmarken." msgid "Cure" msgstr "Kur" msgid "" "Removes all negative spells cast upon one of your units, and restores up to " "%{count} HP per level of spell power." msgstr "" "Fjerner alle ugunstige trolldommer fra en av dine krigere og helbreder " "%{count} liv for hvert nivÃ¥ av brukerens kraft." msgid "Mass Cure" msgstr "Helbredelse" msgid "" "Removes all negative spells cast upon your forces, and restores up to " "%{count} HP per level of spell power, per creature." msgstr "" "Fjerner alle ugunstige trolldommer og helbreder %{count} livspoeng per " "kraftsnivÃ¥ pÃ¥ alle krigerne dine." msgid "Resurrect" msgstr "" "Gjen-\n" "opplivning" msgid "Resurrects creatures from a damaged or dead unit until end of combat." msgstr "" "Gjenoppliver krigere fra en skadet eller død tropp, men kun fram til kampens " "slutt." msgid "Resurrect True" msgstr "" "Sann gjen-\n" "opplivning" msgid "Resurrects creatures from a damaged or dead unit permanently." msgstr "Gjenoppliver krigere fra en skadet eller død tropp permanent." msgid "Haste" msgstr "Hast" msgid "Increases the speed of any creature by %{count}." msgstr "Øker hurtigheten til én kriger med %{count}." msgid "Increases the speed of all of your creatures by %{count}." msgstr "Øker hurtigheten til alle krigerne dine med %{count}." msgid "Mass Haste" msgstr "Full hast" msgid "Slows target to half movement rate." msgstr "Halverer skrittene og minsker hurtigheten til mÃ¥let." msgid "spell|Slow" msgstr "" "Forsink-\n" "else" msgid "Mass Slow" msgstr "" "Full for-\n" "sinkelse" msgid "Slows all enemies to half movement rate." msgstr "Reduserer alle fiender ned til halvparten av skrittene deres." msgid "Clouds the affected creatures' eyes, preventing them from moving." msgstr "" "TÃ¥kelegger øynene til den pÃ¥virkede krigeren slik at den ikke fÃ¥r til Ã¥ " "flytte seg." msgid "spell|Blind" msgstr "Forblinding" msgid "Bless" msgstr "Velsignelse" msgid "Causes the selected creatures to inflict maximum damage." msgstr "FÃ¥r den utvalgte krigeren til Ã¥ gjøre maksimal skade." msgid "Causes all of your units to inflict maximum damage." msgstr "FÃ¥r alle krigerne dine til Ã¥ gjøre maksimal skade." msgid "Mass Bless" msgstr "Forhelligelse" msgid "Magically increases the defense skill of the selected creatures." msgstr "Øker pÃ¥ magisk vis forsvarsevnen til den utvalgte krigeren." msgid "Stoneskin" msgstr "Stenhud" #, fuzzy msgid "" "Increases the defense skill of the targeted creatures. This is an improved " "version of Stoneskin." msgstr "" "Øker forsvarsevnen til de utvalgte krigerne. Dette er en forbedret utgave av " "stenhud." msgid "Steelskin" msgstr "StÃ¥lhud" msgid "Causes the selected creatures to inflict minimum damage." msgstr "FÃ¥r den utvalgte krigeren til Ã¥ gjøre minst mulig skade." msgid "Curse" msgstr "Forbannelse" msgid "Causes all enemy troops to inflict minimum damage." msgstr "FÃ¥r alle fiendtlige tropper til Ã¥ gjøre minst mulig skade." msgid "Mass Curse" msgstr "Besvergelse" msgid "Damages all undead in the battle." msgstr "Skader alle vandøde pÃ¥ slagmarken." msgid "Holy Word" msgstr "Hellig ord" #, fuzzy msgid "" "Damages all undead in the battle. This is an improved version of Holy Word." msgstr "" "Skader alle vandøde i kampen. Dette er en forbedret utgave av hellig ord." msgid "Holy Shout" msgstr "Hellig rop" msgid "Anti-Magic" msgstr "" "Trolldoms-\n" "vern" # en enkelt? flere? msgid "Prevents harmful magic against the selected creatures." msgstr "Skjermer den utvalgte krigeren mot skadelig magi." msgid "Dispel Magic" msgstr "" "Magi-\n" "opphevelse" msgid "Removes all magic spells from a single target." msgstr "Fjerner alle trolldommer fra mÃ¥let." msgid "Mass Dispel" msgstr "" "All-\n" "opphevelse" msgid "Removes all magic spells from all creatures." msgstr "Fjerner all trolldom fra alle krigere, bÃ¥de vennlige og fiendtlige." msgid "Causes a magic arrow to strike the selected target." msgstr "Skyter en magisk pil pÃ¥ det utvalgte mÃ¥let." msgid "Magic Arrow" msgstr "Tryllepil" msgid "Berserker" msgstr "Berserk" # nabo? msgid "Causes a creature to attack its nearest neighbor." msgstr "" "FÃ¥r en kriger til Ã¥ angripe dens nærmeste tropp, uavhengig om den er vennlig " "eller fiendtlig." msgid "Armageddon" msgstr "Dommedag" msgid "" "Holy terror strikes the battlefield, causing severe damage to all creatures." msgstr "" "Hellig forferdelse slÃ¥r ned pÃ¥ slagmarken og gjør alvorlig skade pÃ¥ alle " "krigere." msgid "Elemental Storm" msgstr "Elementær storm" msgid "Magical elements pour down on the battlefield, damaging all creatures." msgstr "Magiske elementer høljer ned pÃ¥ slagmarken og skader alle krigere." msgid "" "A rain of rocks strikes an area of the battlefield, damaging all nearby " "creatures." msgstr "" "Et regn av steiner hamrer ned pÃ¥ et omrÃ¥de av slagmarken, og skader alle " "krigere innenfor trolldommens rekkevidde." msgid "Meteor Shower" msgstr "Meteorregn" msgid "Paralyze" msgstr "Lammelse" msgid "The targeted creatures are paralyzed, unable to move or retaliate." msgstr "" "De utvalgte krigerne lammes og fÃ¥r ikke til Ã¥ flytte pÃ¥ seg eller Ã¥ slÃ¥ " "tilbake." msgid "Hypnotize" msgstr "Trollbinding" msgid "" "Brings a single enemy unit under your control if its hits are less than " "%{count} times the caster's spell power." msgstr "" "En enkelt fiendtlig kriger havner under din kontroll sÃ¥ lenge livene dens er " "mindre enn %{count} ganger kraften til helten din." msgid "Cold Ray" msgstr "KuldestrÃ¥le" msgid "Drains body heat from a single enemy unit." msgstr "PÃ¥fører ekstrem kulde pÃ¥ en enkelt fiendtlig kriger." msgid "Cold Ring" msgstr "Kuldering" msgid "" "Drains body heat from all units surrounding the center point, but not " "including the center point." msgstr "" "PÃ¥fører ekstrem kulde pÃ¥ alle krigerne omkring sentrum av mÃ¥lomrÃ¥det. " "Krigeren i midten unngÃ¥r skade." msgid "Disrupting Ray" msgstr "" "Forvitrings-\n" "strÃ¥le" msgid "Reduces the defense rating of an enemy unit by three." msgstr "Svekker forsvarsevnen til en fiendtlig tropp med 3." msgid "Damages all living (non-undead) units in the battle." msgstr "Skader alle levende (ikke vandøde) styrker pÃ¥ slagmarken." msgid "Death Ripple" msgstr "" "Døds-\n" "krusning" msgid "Death Wave" msgstr "Dødsbølge" #, fuzzy msgid "" "Damages all living (non-undead) units in the battle. This spell is an " "improved version of Death Ripple." msgstr "" "Skader alle levende (ikke-vandøde) tropper i kampen. Denne trolldommen er en " "forbedret utgave av dødskrusning." msgid "Dragon Slayer" msgstr "Dragebane" msgid "Greatly increases a unit's attack skill vs. Dragons." msgstr "En tropp fÃ¥r sitt angrep voldsomt forsterket mot drager." msgid "Blood Lust" msgstr "Blodlyst" msgid "Increases a unit's attack skill." msgstr "Øker en tropp sin angrepsevne." msgid "Animate Dead" msgstr "" "Døde-\n" "besjelning" msgid "Resurrects creatures from a damaged or dead undead unit permanently." msgstr "Gjenreiser krigere fra en skadet eller død vandød tropp permanent." msgid "Mirror Image" msgstr "Speilbilde" # tilsynelatende ekte , uvirkelig, # etterligner # kopierer #, fuzzy msgid "" "Creates an illusionary unit that duplicates one of your existing units. This " "illusionary unit does the same damages as the original, but will vanish if " "it takes any damage." msgstr "" "Tryller fram en illusjonstropp av samme slaget som en av dine eksisterende " "tropper. Denne falske troppen skader like mye som en ekte, men skulle den ta " "noe som helst skade sÃ¥ forsvinner den." msgid "Shield" msgstr "Skjold" msgid "" "Halves damage received from ranged attacks for a single unit. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Halverer skade en enkelt kriger tar fra skuddangrep. Dette pÃ¥virker ikke " "skudd fra tÃ¥rn og ballister." msgid "Mass Shield" msgstr "" "For-\n" "skansning" msgid "" "Halves damage received from ranged attacks for all of your units. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Halverer skade fra skuddangrep pÃ¥ alle dine styrker. Dette pÃ¥virker ikke " "skudd fra tÃ¥rn og ballister." msgid "Summon Earth Elemental" msgstr "Tilkall jordÃ¥nd" msgid "Summons Earth Elementals to fight for your army." msgstr "Maner fram en jordÃ¥nd for Ã¥ kjempe pÃ¥ din side." msgid "Summon Air Elemental" msgstr "Tilkall luftÃ¥nd" msgid "Summons Air Elementals to fight for your army." msgstr "Maner fram en luftÃ¥nd for Ã¥ kjempe pÃ¥ din side." msgid "Summon Fire Elemental" msgstr "Tilkall ildÃ¥nd" msgid "Summons Fire Elementals to fight for your army." msgstr "Maner fram en ildÃ¥nd for Ã¥ kjempe pÃ¥ din side." msgid "Summon Water Elemental" msgstr "Tilkall vannÃ¥nd" msgid "Summons Water Elementals to fight for your army." msgstr "Maner fram en vannÃ¥nd for Ã¥ kjempe pÃ¥ din side." msgid "Damages castle walls." msgstr "Skader ringmuren." msgid "Earthquake" msgstr "Jordskjelv" msgid "Causes all mines across the land to become visible." msgstr "Viser alle gruvene pÃ¥ kartet." msgid "View Mines" msgstr "Vis gruver" msgid "Causes all resources across the land to become visible." msgstr "Viser alle rÃ¥varene pÃ¥ kartet." msgid "View Resources" msgstr "Vis rÃ¥varer" msgid "Causes all artifacts across the land to become visible." msgstr "Viser alle kartets skatter." msgid "View Artifacts" msgstr "Vis skatter" msgid "Causes all towns and castles across the land to become visible." msgstr "Viser alle kartets borger og byer." msgid "View Towns" msgstr "Vis byer" msgid "Causes all Heroes across the land to become visible." msgstr "Viser alle kartets helter." msgid "View Heroes" msgstr "Vis helter" msgid "Causes the entire land to become visible." msgstr "Viser alt pÃ¥ hele kartet." msgid "View All" msgstr "Vis alt" msgid "Allows the caster to view detailed information on enemy Heroes." msgstr "Lar magibrukeren se detaljert informasjon om fiendtlige helter." msgid "Summon Boat" msgstr "Tilkall skip" #, fuzzy msgid "" "Summons the nearest unoccupied, friendly boat to an adjacent shore location. " "A friendly boat is one which you just built or were the most recent player " "to occupy." msgstr "" "Tilkaller det nærmeste ledige og vennlige skipet til det nærmeste " "kystpunktet. Med vennlig skip menes et som du har lagd eller som du var den " "siste til Ã¥ benytte." msgid "Allows the caster to magically transport to a nearby location." msgstr "Transporterer helten pÃ¥ magisk vis til et utvalgt sted i nærheten." # Dimensjon brukes egentlig ikke slik pÃ¥ norsk: verdensportal, verdensdør? msgid "Dimension Door" msgstr "" "Verdens-\n" "portal" msgid "Returns the caster to any town or castle currently owned." msgstr "" "Sender helten til en hvilken som helst by eller borg i heltens nÃ¥værende " "besittelse." msgid "Town Gate" msgstr "Byflukt" msgid "" "Returns the hero to the town or castle of choice, provided it is controlled " "by you." msgstr "" "Sender helten tilbake til en utvalgt by eller en borg, gitt at den tilhører " "spilleren." msgid "Visions" msgstr "Syner" msgid "" "Visions predicts the likely outcome of an encounter with a neutral army camp." msgstr "" "Forutser det mest sannsynlige utfallet av et sammenstøt med en nøytral " "styrke." msgid "Haunt" msgstr "Hjemsøking" #, fuzzy msgid "" "Haunts a mine you control with Ghosts. This mine stops producing resources. " "(If I can't keep it, nobody will!)" msgstr "" "FÃ¥r gjenferd til Ã¥ hjemsøke ei gruve du eier. Denne gruva slutter da Ã¥ " "produsere rÃ¥varer. (Hvis jeg ikke fÃ¥r beholde den skal ingen fÃ¥ det!)" msgid "Set Earth Guardian" msgstr "Plasser jordvokter" # mot? msgid "Sets Earth Elementals to guard a mine against enemy armies." msgstr "Tilkaller en jordÃ¥nd for Ã¥ vokte over ei gruve mot fiendtlige styrker." msgid "Set Air Guardian" msgstr "Plasser luftvokter" msgid "Sets Air Elementals to guard a mine against enemy armies." msgstr "Tilkaller en luftÃ¥nd for Ã¥ vokte over ei gruve mot fiendtlige styrker." msgid "Set Fire Guardian" msgstr "Plasser ildvokter" msgid "Sets Fire Elementals to guard a mine against enemy armies." msgstr "Tilkaller en ildÃ¥nd for Ã¥ vokte over ei gruve mot fiendtlige styrker." msgid "Set Water Guardian" msgstr "Plasser vannvokter" msgid "Sets Water Elementals to guard a mine against enemy armies." msgstr "Tilkaller en vannÃ¥nd for Ã¥ vokte over ei gruve mot fiendtlige styrker." #, fuzzy msgid "Random Spell" msgstr "Tilfeldig" msgid "Randomly selected spell of any level." msgstr "" #, fuzzy msgid "Random 1st Level Spell" msgstr "VilkÃ¥rlig borg" msgid "Randomly selected 1st level spell." msgstr "" msgid "Random 2nd Level Spell" msgstr "" msgid "Randomly selected 2nd level spell." msgstr "" msgid "Random 3rd Level Spell" msgstr "" msgid "Randomly selected 3rd level spell." msgstr "" msgid "Random 4th Level Spell" msgstr "" msgid "Randomly selected 4th level spell." msgstr "" msgid "Random 5th Level Spell" msgstr "" msgid "Randomly selected 5th level spell." msgstr "" msgid "Petrification" msgstr "Forstening" #, fuzzy msgid "" "Turns the affected creature into stone. A petrified creature receives half " "damage from a direct attack." msgstr "" "Forvandler den pÃ¥virkede krigeren til sten. En forstenet kriger tar halv " "skade fra fysiske angrep." msgid "You have no Magic Book, so you cannot cast a spell." msgstr "Du har ingen tryllebok og kan derfor ikke bruke magi." msgid "No spell to cast." msgstr "Ingen trolldommer tilgjengelige." msgid "Your hero has %{point} spell points remaining." msgstr "Helten din har %{point} mana igjen." msgid "View Adventure Spells" msgstr "Vis kartmagi" msgid "View Combat Spells" msgstr "Vis kampmagi" msgid "View previous page" msgstr "Vis forrige side" msgid "View next page" msgstr "Vis neste side" msgid "Close Spellbook" msgstr "Lukk trylleboka" msgid "View %{spell}" msgstr "Vis %{spell}" msgid "This spell does %{damage} points of damage." msgstr "Denne trolldommen gjør %{damage} skadepoeng." msgid "" "This spell summons\n" "%{count} %{monster}." msgstr "" "Denne trolldommen tilkaller\n" "%{count} %{monster}." msgid "This spell restores %{hp} HP." msgstr "Denne trolldommen fyller opp %{hp} liv." msgid "This spell summons %{count} %{monster} to guard the mine." msgstr "Denne trolldommen tilkaller %{count} %{monster} for Ã¥ beskytte gruva." msgid "The nearest town is %{town}." msgstr "Den nærmeste byen er %{town}." msgid "This town is occupied by your hero %{hero}." msgstr "Denne byen er blokkert av helten din %{hero}." msgid "" "This spell controls up to\n" "%{hp} HP." msgstr "" "Denne trolldommen gir deg kontroll over\n" "opptil %{hp} liv." msgid "The ultimate artifact is really the %{name}." msgstr "Den sjeldneste skatten er faktisk %{name}." msgid "The ultimate artifact may be found in the %{name} regions of the world." msgstr "Den sjeldneste gjenstanden befinner seg et sted %{name} verden." msgid "north-west" msgstr "nordvest i" msgid "north" msgstr "nord i" msgid "north-east" msgstr "nordøst i" msgid "west" msgstr "vest i" msgid "center" msgstr "i midten av" msgid "east" msgstr "øst i" msgid "south-west" msgstr "sørvest i" msgid "south" msgstr "sør i" msgid "south-east" msgstr "sørøst i" msgid "The truth is out there." msgstr "Sannheten er der ute." msgid "The dark side is stronger." msgstr "Den mørke siden er sterkere." msgid "The end of the world is near." msgstr "Verdens ende er nær." msgid "The bones of Lord Slayer are buried in the foundation of the arena." msgstr "" "Skjelettet til baron Myrder ligger begravd under grunnmuren til arenaen." msgid "A Black Dragon will take out a Titan any day of the week." msgstr "En svartdrage klarer Ã¥ slÃ¥ ut en titan en hvilken som helst dag i uka." #, fuzzy msgid "He told her: Yada yada yada... and then she said: Blah, blah, blah..." msgstr "" "Han sa til henne: \"Bla, bla, bla...\", og da svarte hun: \"Bla, bla, bla..." "\"" msgid "An unknown force is being resurrected..." msgstr "En ukjent styrke holder pÃ¥ Ã¥ komme tilbake..." msgid "" "Check the newest version of the game at\n" "https://github.com/ihhub/\n" "fheroes2/releases" msgstr "" "Finn den siste oppdateringen av spillet pÃ¥\n" "https://github.com/ihhub/\n" "fheroes2/releases" #, fuzzy #~ msgid "Terrain" #~ msgstr "Terrengmodus" #, fuzzy #~ msgid "Castles" #~ msgstr "Borg" #, fuzzy #~ msgid "Ocean object" #~ msgstr "Sjøting" #~ msgid "Ocean Objects" #~ msgstr "Sjøting" #~ msgid "Grass Objects" #~ msgstr "Gressting" #~ msgid "Snow Objects" #~ msgstr "Snøting" #~ msgid "Swamp Objects" #~ msgstr "Myrting" #~ msgid "Lava Objects" #~ msgstr "Lavating" #~ msgid "Desert Objects" #~ msgstr "Ørkenting" #~ msgid "Dirt Objects" #~ msgstr "Grusting" #~ msgid "Beach Objects" #~ msgstr "Strandting" #~ msgid "Used to place objects most appropriate for use on %{terrain}." #~ msgstr "" #~ "Brukes til Ã¥ plassere ting som passer best pÃ¥ terreng av typen %{terrain}." #~ msgid "" #~ "Used to place\n" #~ "a town or castle." #~ msgstr "" #~ "Brukes til Ã¥ plassere\n" #~ "en by eller borg." #~ msgid "Used to place an artifact." #~ msgstr "Brukes til Ã¥ plassere en gjenstand." #~ msgid "" #~ "Used to place\n" #~ "a resource or treasure." #~ msgstr "Brukes til Ã¥ plassere en rÃ¥vare eller en verdifull gjenstand." #~ msgid "%{race1} %{name1}" #~ msgstr "%{race1}en %{name1}" #~ msgid "vs" #~ msgstr "mot" #~ msgid "%{race2} %{name2}" #~ msgstr "%{race2}en %{name2}" fheroes2-1.0.12+dfsg/files/lang/nl.po000066400000000000000000006076161456075706000172550ustar00rootroot00000000000000# Dutch translation for fheroes2 # Copyright (C) 2022 fheroes2 team # This file is distributed under the same license as the fheroes2 package. # msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-03 03:27+0000\n" "PO-Revision-Date: 2022-10-25 08:52+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: Dutch \n" "Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Launchpad-Export-Date: 2010-05-27 09:12+0000\n" "X-Generator: Poedit 3.1.1\n" msgid "" "BATTLE\n" "ONLY" msgstr "" "ALLEEN\n" "STRIJD" msgid "" "NEW\n" "GAME" msgstr "" msgid "" "SAVE\n" "GAME" msgstr "" msgid "" "LOAD\n" "GAME" msgstr "" msgid "INFO" msgstr "" msgid "QUIT" msgstr "" msgid "CANCEL" msgstr "" msgid "OKAY" msgstr "" msgid "smallerButton|OKAY" msgstr "" msgid "ACCEPT" msgstr "" msgid "DECLINE" msgstr "" msgid "LEARN" msgstr "" msgid "TRADE" msgstr "" msgid "YES" msgstr "" msgid "NO" msgstr "" msgid "EXIT" msgstr "" msgid "smallerButton|EXIT" msgstr "" msgid "DISMISS" msgstr "" msgid "UPGRADE" msgstr "" msgid "RESTART" msgstr "" msgid "HEROES" msgstr "" msgid "" "TOWNS/\n" "CASTLES" msgstr "" msgid "S" msgstr "" msgid "M" msgstr "" msgid "L" msgstr "" msgid "X-L" msgstr "" msgid "ALL" msgstr "" msgid "SELECT" msgstr "" msgid "" "STANDARD\n" "GAME" msgstr "" msgid "" "CAMPAIGN\n" "GAME" msgstr "" msgid "" "MULTI-\n" "PLAYER\n" "GAME" msgstr "" msgid "CONFIG" msgstr "" msgid "" "ORIGINAL\n" "CAMPAIGN" msgstr "" msgid "" "EXPANSION\n" "CAMPAIGN" msgstr "" msgid "HOT SEAT" msgstr "" msgid "2 PLAYERS" msgstr "" msgid "3 PLAYERS" msgstr "" msgid "4 PLAYERS" msgstr "" msgid "5 PLAYERS" msgstr "" msgid "6 PLAYERS" msgstr "" msgid "GIFT" msgstr "DONEREN" msgid "MAX" msgstr "" msgid "DIFFICULTY" msgstr "NIVEAU" msgid "VIEW INTRO" msgstr "" msgid "MIN" msgstr "MIN" msgid "RESET" msgstr "" msgid "START" msgstr "" msgid "CASTLE" msgstr "" msgid "TOWN" msgstr "" msgid "" "D\n" "I\n" "S\n" "M\n" "I\n" "S\n" "S" msgstr "" msgid "" "E\n" "X\n" "I\n" "T" msgstr "" msgid "" "C\n" "A\n" "M\n" "P\n" "A\n" "I\n" "G\n" "N" msgstr "" msgid "" "S\n" "T\n" "A\n" "N\n" "D\n" "A\n" "R\n" "D" msgstr "" msgid "Warrior" msgstr "Strijder" msgid "Builder" msgstr "Bouwer" msgid "Explorer" msgstr "Ontdekker" msgid "None" msgstr "Geen" msgid "" "A few\n" "%{monster}" msgstr "" "Een paar\n" "%{monster}" msgid "" "Several\n" "%{monster}" msgstr "" "Meerdere\n" "%{monster}" msgid "" "A pack of\n" "%{monster}" msgstr "" "Een groep van\n" "%{monster}" msgid "" "Lots of\n" "%{monster}" msgstr "" "Veel\n" "%{monster}" msgid "" "A horde of\n" "%{monster}" msgstr "" "Een horde van\n" "%{monster}" msgid "" "A throng of\n" "%{monster}" msgstr "" "Een menigte van\n" "%{monster}" msgid "" "A swarm of\n" "%{monster}" msgstr "" "Een zwerm van\n" "%{monster}" msgid "" "Zounds...\n" "%{monster}" msgstr "" "Honderden\n" "%{monster}" msgid "" "A legion of\n" "%{monster}" msgstr "" "Een legioen van\n" "%{monster}" msgid "army|Few" msgstr "Een paar" msgid "army|Several" msgstr "Meerdere" msgid "army|Pack" msgstr "Groep" msgid "army|Lots" msgstr "Veel" msgid "army|Horde" msgstr "Horde" msgid "army|Throng" msgstr "Menigte" msgid "army|Swarm" msgstr "Zwerm" msgid "army|Zounds" msgstr "Honderden" msgid "army|Legion" msgstr "Legioen" msgid "All %{race} troops +1" msgstr "Alle %{ras} troepen +1" msgid "Multiple" msgstr "Meerdere" msgid "Troops of %{count} alignments -%{penalty}" msgstr "Troepen van %{count} uitlijningen -%{penalty}" #, fuzzy msgid "Some undead in army -1" msgstr "Enkele ondoden in groepen -1" msgid "View %{name}" msgstr "Zie %{name}" msgid "Move the %{name} " msgstr "Beweeg de %{name} " msgid "Move or right click to redistribute %{name}" msgstr "Beweeg of rechter click om %{name} te herverdelen" msgid "Combine %{name} armies" msgstr "Combineer %{name} legers" msgid "Exchange %{name2} with %{name}" msgstr "Ruil %{name2} met %{name}" msgid "Select %{name}" msgstr "Selecteer %{name}" msgid "Cannot move last troop" msgstr "Kan laatste troep niet verplaatsen" msgid "Move the %{name}" msgstr "Verplaats de %{name}" msgid "Set Count" msgstr "Zet Tel" msgid "%{name} destroys half the enemy troops!" msgid_plural "%{name} destroy half the enemy troops!" msgstr[0] "%{name} vernietigd helft van de veihandelijke troepen!" msgstr[1] "%{name} vernietigd helft van de veihandelijke troepen!" msgid "%{name} has turned off the auto battle" msgstr "%{name} heeft de automatische wedstrijd uitgezet" msgid "%{name} has turned on the auto battle" msgstr "%{name} heeft de automatische wedstrijd aangezet!" msgid "Spell failed!" msgstr "Spreuk gefaald!" msgid "" "The Sphere of Negation artifact is in effect for this battle, disabling all " "combat spells." msgstr "" "Het Sphere of Negation artefact is in effect voor deze wedstrijd, alle " "strijd spreuken zijn uitgeschakeld." msgid "You have already cast a spell this round." msgstr "Je hebt all een spreuk uitgesproken deze ronde." #, fuzzy msgid "That spell will have no effect!" msgstr "Die spreuk zal niemand beïnvloeden!" msgid "You may only summon one type of elemental per combat." msgstr "Je kan maar 1 type van elemental oproepen per strijd." #, fuzzy msgid "" "There is no open space adjacent to your hero where you can summon an " "Elemental to." msgstr "" "Er is geen open plek aangrenzend bij je held om een Elemental naar toe te " "oproepen." msgid "" "The Moat reduces by -%{count} the defense skill of any unit and slows to " "half movement rate." msgstr "" "De Moat verminderd by -%{count} de verdedgings vaardigheid van een eenheid " "en versloomt naar de helft van de bewegings snelheid" msgid "Speed: %{speed}" msgstr "Snelheid: %{speed}" msgid "Speed" msgstr "Snelheid" msgid "Off" msgstr "Uit" msgid "On" msgstr "Aan" #, fuzzy msgid "Turn Order" msgstr "Legerorde" msgid "Auto Spell Casting" msgstr "Automatisch spreuk uitroepen" msgid "Grid" msgstr "Rooster" msgid "Shadow Movement" msgstr "Schaduw Beweging" msgid "Shadow Cursor" msgstr "Schaduw Cursor" msgid "Audio" msgstr "" msgid "Settings" msgstr "" msgid "Configure" msgstr "" msgid "Hot Keys" msgstr "" msgid "Damage Info" msgstr "" msgid "Set the speed of combat actions and animations." msgstr "Zet de snelheid van gevechts acties en animaties." #, fuzzy msgid "Toggle to display the turn order during the battle." msgstr "Schakel om het legerorde weer te geven tijdens het gevecht." msgid "" "Toggle whether or not the computer will cast spells for you when auto combat " "is on. (Note: This does not affect spell casting for computer players in any " "way, nor does it affect quick combat.)" msgstr "" "Schakel wel of niet de computer spreuken voor je zal uitspreken waneer " "automatisch vechten aan staat. (Opmerking: Dit heeft geen invloed op " "spreuken uitspreken voor computer spelers hoe dan ook, het heeft ook geen " "invloed op snel vechten.)" msgid "" "Toggle the hex grid on or off. The hex grid always underlies movement, even " "if turned off. This switch only determines if the grid is visible." msgstr "" "Schakel het hex raster aan of uit. Het hex raster onderlijnt altijd " "beweeging, even als uitgeschakeld. Deze schakel bepaalt alleen of het raster " "zichtbaar is." msgid "" "Toggle on or off shadows showing where your creatures can move and attack." msgstr "" "Schakel aan of uit of schaduwen worden weergegeven waar je schepsels kunnen " "bewegen en aanvallen." msgid "" "Toggle on or off a shadow showing the current hex location of the mouse " "cursor." msgstr "" "Schakel aan of uit een schaduw die de huidige hex-locatie van de muiscursor " "toont." msgid "Change the audio settings of the game." msgstr "" msgid "Check and configure all the hot keys present in the game." msgstr "" #, fuzzy msgid "Toggle to display damage information during the battle." msgstr "Schakel om het legerorde weer te geven tijdens het gevecht." msgid "Exit this menu." msgstr "" msgid "Okay" msgstr "" msgid "The enemy has surrendered!" msgstr "De vijand heeft zich overgegeven!" msgid "Their cowardice costs them %{gold} gold." msgstr "" msgid "The enemy has fled!" msgstr "De vijand is gevlucht!" msgid "A glorious victory!" msgstr "Een glorieuze overwinning!" msgid "For valor in combat, %{name} receives %{exp} experience." msgstr "Voor moed in de strijd, %{name} ontvangt %{exp} ervaring." msgid "The cowardly %{name} flees from battle." msgstr "De laffe %{name} vlucht voor de strijd." msgid "%{name} surrenders to the enemy, and departs in shame." msgstr "%{name} geeft zich over aan de vijand en vertrekt in schaamte." #, fuzzy msgid "Your forces suffer a bitter defeat, and %{name} abandons your cause." msgstr "" "Je strijdmacht lijdt een bittere nederlaag en %{name} geeft je zaak op." #, fuzzy msgid "Your forces suffer a bitter defeat." msgstr "Je strijdmacht lijdt een bittere nederlaag." msgid "Battlefield Casualties" msgstr "Slachtoffers op het slagveld" msgid "Attacker" msgstr "Aanvaller" msgid "Defender" msgstr "Verdediger" msgid "Click to leave the battle results." msgstr "" #, fuzzy msgid "Click to restart the battle in manual mode." msgstr "" "Weet je zeker dat je het gevecht in de automatische modus wilt beëindigen?" msgid "Restart" msgstr "" msgid "You have captured an enemy artifact!" msgstr "Je hebt een vijandelijk artefact gevangen!" msgid "As you reach for the %{name}, it mysteriously disappears." msgstr "Als je naar de %{name} reikt, verdwijnt deze op mysterieuze wijze." msgid "As your enemy reaches for the %{name}, it mysteriously disappears." msgstr "" "Als je vijand naar de %{name} reikt, verdwijnt deze op mysterieuze wijze." msgid "Necromancy!" msgstr "Necromantie!" msgid "" "Practicing the dark arts of necromancy, you are able to raise %{count} of " "the enemy's dead to return under your service as %{monster}." msgstr "" "Door de duistere kunst van necromantie te beoefenen, ben je in staat om " "%{count} van de vijandelijke doden te verhogen om terug te keren onder jouw " "dienst als %{monster}." msgid "%{name} the %{race}" msgstr "%{name} de %{race}" msgid "Captain of %{name}" msgstr "Kapitein van %{name}" msgid "Attack" msgstr "Aanval" msgid "Defense" msgstr "Verdedig" msgid "Spell Power" msgstr "Spreuk Kracht" msgid "Knowledge" msgstr "Kennis" msgid "Morale" msgstr "Moreel" msgid "Luck" msgstr "Geluk" msgid "Spell Points" msgstr "Spreuk Punten" msgid "Hero's Options" msgstr "Held's Opties" msgid "Cast Spell" msgstr "Spreuk uitspreken" msgid "Retreat" msgstr "Terugtrekken" msgid "Surrender" msgstr "Overgave" msgid "Cancel" msgstr "Annuleren" msgid "Hero Screen" msgstr "Held Scherm" msgid "Captain's Options" msgstr "Kapitein's Opties" msgid "" "Cast a magical spell. You may only cast one spell per combat round. The " "round is reset when every creature has had a turn." msgstr "" "Spreek een magische spruik uit. Je kan maar 1 spreuk uitspreken per gevechts " "ronde. De ronde resets waneer elk schepsel een beurt heeft gehad." msgid "" "Retreat your hero, abandoning your creatures. Your hero will be available " "for you to recruit again, however, the hero will have only a novice hero's " "forces." msgstr "" "Trek je held terug en laat je schepsels in de steek. Je held zal beschikbaar " "zijn om opnieuw te rekruteren, maar de held zal alleen de troepen van een " "beginnende held hebben." #, fuzzy msgid "" "Surrendering costs gold. However if you pay the ransom, the hero and all of " "his or her surviving creatures will be available to recruit again. The cost " "of surrender is half of the total cost of the non-temporary troops remaining " "in the army." msgstr "" "Overgave kost geld. Echter als je het losgeld betaalt zal de held en al van " "de overlevende schepsels beschikbaar zijn om opnieuw te rekruteren." msgid "Open Hero Screen to view full information about the hero." msgstr "" "Open het heldenscherm om volledige informatie over de held te bekijken." msgid "Return to the battle." msgstr "Keer terug naar de strijd." msgid "Not enough gold (%{gold})" msgstr "Niet genoeg goud (%{gold})" msgid "%{name} states:" msgstr "%{name} staat:" msgid "Captain of %{name} states:" msgstr "De Kapitein van %{name} staat:" msgid "" "\"I will accept your surrender and grant you and your troops safe passage " "for the price of %{price} gold.\"" msgstr "" "\"Ik zal je overgave accepteren en jou en je troepen een veilige doorgang " "geven voor de prijs van %{price} goud.\"" msgid "View %{monster} info" msgstr "Bekijk de info van %{monster}" msgid "Fly %{monster} here" msgstr "Vlieg %{monster} hier" msgid "Move %{monster} here" msgstr "Beweeg %{monster} hier" msgid "Shoot %{monster}" msgstr "Schiet %{monster}" msgid "(1 shot left)" msgid_plural "(%{count} shots left)" msgstr[0] "(1 schot over)" msgstr[1] "(%{count} schoten over)" msgid "Attack %{monster}" msgstr "Val %{monster} aan" msgid "Turn %{turn}" msgstr "Draai %{turn}" msgid "Teleport here" msgstr "Teleporteer hier" msgid "Invalid teleport destination" msgstr "Ongeldige teleporteers bestemming" msgid "Cast %{spell} on %{monster}" msgstr "%{spell} uitgesproken op %{monster}" msgid "Cast %{spell}" msgstr "%{spell} uitspreken" msgid "Select spell target" msgstr "Selecteer spreuk doelwit" msgid "View Ballista info" msgstr "Ballista-info bekijken" msgid "Ballista" msgstr "Ballista" msgid "Enable auto combat" msgstr "Automatische gevechten inschakelen" msgid "Allows the computer to fight out the battle for you." msgstr "Hiermee kan de computer de strijd voor je uitvechten." msgid "Auto Combat" msgstr "Automatisch Vechten" msgid "Customize system options" msgstr "Systeem instellingen aanpassen" msgid "Allows you to customize the combat screen." msgstr "Hiermee kan je het gevechts scherm aanpassen" msgid "System Options" msgstr "Systeem Instellingen" msgid "Skip this unit" msgstr "Deze eenheid overslaan" msgid "Skip" msgstr "Overslaan" msgid "" "Skips the current creature. The current creature ends its turn and does not " "get to go again until the next round." msgstr "" "Slaat het huidige schepsel over. Het huidige schepsel eindigt zijn beurt en " "mag pas de volgende ronde weer gaan." msgid "View Captain's options" msgstr "Bekijk Kapitein's opties" msgid "View Hero's options" msgstr "Bekijk Held's opties" msgid "View opposing Captain" msgstr "Bekijk tegenstander Kapitein" msgid "View opposing Hero" msgstr "Bekijk tegenstander Held" msgid "Hide logs" msgstr "Logboeken verbergen" msgid "Show logs" msgstr "Logboeken weergeven" msgid "Message Bar" msgstr "Brichtenbalk" msgid "Shows the results of individual monster's actions." msgstr "Toont de resultaten van de acties van individuele monsters." msgid "Are you sure you want to finish the battle in auto mode?" msgstr "" "Weet je zeker dat je het gevecht in de automatische modus wilt beëindigen?" #, fuzzy msgid "Are you sure you want to enable auto combat?" msgstr "Weet je zeker dat je je wilt terugtrekken?" msgid "%{name} skip their turn." msgstr "%{name} slaat zijn beurt over." msgid "%{attacker} does %{damage} damage." msgid_plural "%{attacker} do %{damage} damage." msgstr[0] "%{attacker} doet %{damage} schade." msgstr[1] "%{attacker} doet %{damage} schade." msgid "1 creature perishes." msgid_plural "%{count} creatures perish." msgstr[0] "1 schepsel vergaat." msgstr[1] "%{count} schepsels vergaan." msgid "1 %{defender} perishes." msgid_plural "%{count} %{defender} perish." msgstr[0] "1 %{defender} vergaat." msgstr[1] "%{count} %{defender} vergaan." #, fuzzy msgid "1 soul is incorporated." msgid_plural "%{count} souls are incorporated." msgstr[0] "Het spiegelbeeld wordt gecreëerd." msgstr[1] "Het spiegelbeeld wordt gecreëerd." msgid "1 %{unit} is revived." msgid_plural "%{count} %{unit} are revived." msgstr[0] "" msgstr[1] "" msgid "Moved %{monster}: from [%{src}] to [%{dst}]." msgstr "Bewoog %{monster}: van [%{src}] naar [%{dst}]" msgid "The %{name} resist the spell!" msgstr "De %{name} weerstaat de spreuk!" msgid "%{name} casts %{spell} on the %{troop}." msgstr "%{name} spreekt %{spell} uit op %{troop}." msgid "%{name} casts %{spell}." msgstr "%{name} spreekt %{spell}." msgid "The %{spell} does %{damage} damage to one undead creature." msgstr "De %{spell} richt %{damage} schade aan een ondode schepsel." msgid "The %{spell} does %{damage} damage to all undead creatures." msgstr "De %{spell} richt %{damage} schade aan aan alle ondode schepsels." msgid "The %{spell} does %{damage} damage, %{count} creatures perish." msgstr "De %{spell} richt %{damage} schade aan, %{count} schepsels komen om." msgid "The %{spell} does %{damage} damage." msgstr "De %{spell} richt %{damage} schade aan." msgid "The %{spell} does %{damage} damage to one living creature." msgstr "De %{spell} richt %{damage} schade aan aan 1 levend schepsel." msgid "The %{spell} does %{damage} damage to all living creatures." msgstr "De %{spell} richt %{damage} schade aan aan alle levende schepsels." msgid "The %{attacker}' attack blinds the %{target}!" msgstr "De aanval van %{attacker} verblindt de %{target}!" msgid "The %{attacker}' gaze turns the %{target} to stone!" msgstr "De blik van de %{attacker} verandert het %{target} in steen!" msgid "The %{attacker}' curse falls upon the %{target}!" msgstr "De vloek van de %{attacker} valt op de %{target}!" msgid "The %{target} are paralyzed by the %{attacker}!" msgstr "De %{target} zijn verlamd door de %{attacker}!" msgid "The %{attacker} dispel all good spells on your %{target}!" msgstr "De %{attacker} verdrijft alle goede spreuken op je %{target}!" msgid "The %{attacker} cast %{spell} on %{target}!" msgstr "De %{attacker} spreekt %{spell} uit op %{target}!" msgid "Bad luck descends on the %{attacker}." msgstr "Pech komt neer op de %{attacker}." msgid "Good luck shines on the %{attacker}." msgstr "Geluk komt neer op de %{attacker}" msgid "High morale enables the %{monster} to attack again." msgstr "Een hoog moreel stelt het %{monster} in staat opnieuw aan te vallen." msgid "Low morale causes the %{monster} to freeze in panic." msgstr "Een laag moreel zorgt ervoor dat het %{monster} in paniek bevriest." msgid "%{tower} does %{damage} damage." msgstr "%{tower} doet %{damage} schade." msgid "The mirror image is created." msgstr "Het spiegelbeeld wordt gecreëerd." msgid "The mirror image is destroyed!" msgstr "Het spiegelbeeld is vernietigd!" msgid "Are you sure you want to interrupt the auto battle?" msgstr "Weet je zeker dat je het automatisch vechten wilt onderbreken?" msgid "Error" msgstr "Fout" msgid "No spells to cast." msgstr "Geen spreuken om uit te spreken." msgid "Are you sure you want to retreat?" msgstr "Weet je zeker dat je je wilt terugtrekken?" msgid "Retreat disabled" msgstr "Terugtrekken uitgeschakeld" msgid "Surrender disabled" msgstr "Overgave uitgeschakeld" msgid "Damage: %{max}" msgstr "Schade: %{max}" msgid "Damage: %{min} - %{max}" msgstr "Schade: %{min} - %{max}" msgid "Perish: %{max}" msgstr "Vergaan: %{max}" msgid "Perish: %{min} - %{max}" msgstr "Vergaan: %{min} - %{max}" msgid "" "Through eagle-eyed observation, %{name} is able to learn the magic spell " "%{spell}." msgstr "" "Door observatie met arendsogen kan %{name} de magische spreuk %{spell} leren." msgid "Human" msgstr "Mens" msgid "AI" msgstr "Computer" msgid "Start" msgstr "" #, fuzzy msgid "Start the battle." msgstr "Keer terug naar de strijd." msgid "Exit" msgstr "" msgid "Reset" msgstr "" msgid "Reset to default settings." msgstr "" msgid "Please select another hero." msgstr "Selecteer een andere held." msgid "Set Attack Skill" msgstr "Aanvalsvaardigheid instellen" msgid "Set Defense Skill" msgstr "Verdedigingsvaardigheid instellen" msgid "Set Power Skill" msgstr "Krachtsvaardigheid instellen" msgid "Set Knowledge Skill" msgstr "Kennisvaardigheid instellen" #, fuzzy msgid "%{race1} %{name1} vs %{race2} %{name2}" msgstr "%{race1} %{name1}" msgid "Monsters" msgstr "Monsters" msgid "N/A" msgstr "Nvt" msgid "Left Turret" msgstr "Linker torentje" msgid "Right Turret" msgstr "Rechter torentje" msgid "The %{name} fires with the strength of %{count} Archers" msgstr "De %{name} vuurt met de kracht van %{count} Boogschutters" msgid "each with a +%{attack} bonus to their attack skill." msgstr "elk met een +%{attack} bonus voor hun aanvalsvaardigheid." #, fuzzy msgid "The %{name} is destroyed." msgstr "Het spiegelbeeld is vernietigd!" #, fuzzy msgid "%{count} %{name} rises from the dead!" msgid_plural "%{count} %{name} rise from the dead!" msgstr[0] "%{count} %{name} opstaan uit de dood!" msgstr[1] "%{count} %{name} opstaan uit de dood!" msgid "Dwarven Alliance" msgstr "Dwergen Alliantie" msgid "Sorceress Guild" msgstr "Tovenares Gilde" msgid "Necromancer Guild" msgstr "Necromancer gilde" msgid "Ogre Alliance" msgstr "Ogre Alliantie" msgid "Dwarfbane" msgstr "Drawven Moordenaar" msgid "Dragon Alliance" msgstr "Draken Alliantie" msgid "Elven Alliance" msgstr "Elven Alliantie" msgid "Kraeger defeated" msgstr "Kraeger verslagen" msgid "Wayward Son" msgstr "Wayward zoon" msgid "Uncle Ivan" msgstr "Oom Ivan" msgid "Annexation" msgstr "Annexatie" msgid "Force of Arms" msgstr "Kracht van Wapens" msgid "Save the Dwarves" msgstr "Red de Dwarfen" msgid "Carator Mines" msgstr "Caratormijnen" msgid "Turning Point" msgstr "Draaipunt" msgid "scenarioName|Defender" msgstr "Beschermer" msgid "Corlagon's Defense" msgstr "Corlagons Verdediging" msgid "The Crown" msgstr "De Kroon" msgid "The Gauntlet" msgstr "" msgid "Betrayal" msgstr "Veraad" msgid "Final Justice" msgstr "Laatste Gerechtigheid" #, fuzzy msgid "" "Roland needs you to defeat the lords near his castle to begin his war of " "rebellion against his brother. They are not allied with each other, so they " "will spend most of their time fighting with one another. Victory is yours " "when you have defeated all of their castles and heroes." msgstr "" "Roland heeft je nodig om de heren in de buurt van zijn kasteel te verslaan " "om zijn rebellie tegen zijn broer te beginnen. Ze zijn niet met elkaar " "verbonden, dus zullen ze het grootste deel van hun tijd met elkaar vechten. " "De overwinning is van jou als je al hun kastelen en helden hebt verslagen." msgid "" "The local lords refuse to swear allegiance to Roland, and must be subdued. " "They are wealthy and powerful, so be prepared for a tough fight. Capture all " "enemy castles to win." msgstr "" "De lokale heren weigeren trouw te zweren aan Roland en moeten worden " "onderworpen. Ze zijn rijk en machtig, dus wees voorbereid op een zwaar " "gevecht. Verover alle vijandelijke kastelen om te winnen." msgid "" "Your task is to defend the Dwarves against Archibald's forces. Capture all " "of the enemy towns and castles to win, and be sure not to lose all of the " "dwarf towns at once, or the enemy will have won." msgstr "" "Jouw taak is om de Dwergen te verdedigen tegen de troepen van Archibald. " "Verover alle vijandige steden en kastelen om te winnen, en zorg ervoor dat " "je niet alle dwergsteden in één keer verliest, anders heeft de vijand " "gewonnen." msgid "" "You will face four allied enemies in a straightforward fight for resource " "and treasure. Capture all of the enemy castles for victory." msgstr "" "Je neemt het op tegen vier geallieerde vijanden in een ongecompliceerd " "gevecht om grondstoffen en schatten. Verover alle vijandelijke kastelen voor " "de overwinning." msgid "" "Your enemies are allied against you and start close by, so be ready to come " "out fighting. You will need to own all four castles in this small valley to " "win." msgstr "" "Je vijanden zijn tegen je geallieerd en beginnen dichtbij, dus wees klaar om " "te vechten. Je moet alle vier de kastelen in deze kleine vallei bezitten om " "te winnen." msgid "" "The Sorceress' guild of Noraston has requested Roland's aid against an " "attack from Archibald's allies. Capture all of the enemy castles to win, and " "don't lose Noraston, or you'll lose the scenario. (Hint: There is an enemy " "castle on an island in the ocean.)" msgstr "" "Het Tovenaarsgilde van Noraston heeft Roland om hulp gevraagd tegen een " "aanval van Archibalds bondgenoten. Verover alle vijandelijke kastelen om te " "winnen, en verlies Noraston niet, anders verlies je het scenario. (Hint: er " "is een vijandelijk kasteel op een eiland in de oceaan.)" msgid "" "Gather as large an army as possible and capture the enemy castle within 8 " "weeks. You are opposed by only one enemy, but must travel a long way to get " "to the enemy castle. Any troops you have in your army at the end of this " "scenario will be with you in the final battle." msgstr "" "Verzamel een zo groot mogelijk leger en verover het vijandelijke kasteel " "binnen 8 weken. Je wordt tegengewerkt door slechts één vijand, maar je moet " "een lange weg afleggen om bij het vijandelijke kasteel te komen. Alle " "troepen die je aan het einde van dit scenario in je leger hebt, zullen bij " "je zijn in het laatste gevecht." msgid "" "Find the Crown before Archibald's heroes find it. Roland will need the Crown " "for the final battle against Archibald." msgstr "" "Vind de kroon voordat de helden van Archibald hem vinden. Roland heeft de " "Kroon nodig voor het laatste gevecht tegen Archibald." msgid "" "Three allied enemies stand before you and victory, including Lord Corlagon. " "Roland is in a castle to the northwest, and you will lose if he falls to the " "enemy. Remember that capturing Lord Corlagon will ensure that he will not " "fight against you in the final scenario." msgstr "" "Drie geallieerde vijanden staan voor je en zegevieren, waaronder Lord " "Corlagon. Roland bevindt zich in een kasteel in het noordwesten en je zult " "verliezen als hij door de vijand valt. Onthoud dat het vangen van Lord " "Corlagon ervoor zal zorgen dat hij niet tegen je zal vechten in het laatste " "scenario." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Archibald to end the war!" msgstr "" "Dit is de laatste strijd. Zowel jij als je vijand zijn tot de tanden " "bewapend, en ze zijn allemaal tegen je geallieerd. Vang Archibald om de " "oorlog te beëindigen!" #, fuzzy msgid "" "Switching sides leaves you with three castles against the enemy's one. This " "battle will be the easiest one you will face for the rest of the war..." "traitor." msgstr "" "Als je van kant wisselt, krijg je drie kastelen tegen die van de vijand. " "Deze strijd zal de gemakkelijkste zijn die je de rest van de oorlog zult " "tegenkomen... verrader." msgid "Barbarian Wars" msgstr "Barbaarse Oorlogen" msgid "First Blood" msgstr "Eerste Bloed" msgid "Necromancers" msgstr "Necromancers" msgid "Slay the Dwarves" msgstr "Versla de Dwergen" msgid "Country Lords" msgstr "Landheren" msgid "Dragon Master" msgstr "Drakenmeester" msgid "Rebellion" msgstr "Opstand" msgid "Apocalypse" msgstr "Apocalypse" msgid "Greater Glory" msgstr "Grotere Glorie" #, fuzzy msgid "" "King Archibald requires you to defeat the three enemies in this region. They " "are not allied with one another, so they will spend most of their energy " "fighting amongst themselves. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "King Archibald vereist dat je de drie vijanden in deze regio verslaat. Ze " "zijn niet met elkaar verbonden, dus zullen ze het grootste deel van hun " "energie onder elkaar vechten. Je wint als je alle vijandelijke kastelen " "bezit en er geen helden meer zijn om tegen te vechten." msgid "" "You must unify the barbarian tribes of the north by conquering them. As in " "the previous mission, the enemy is not allied against you, but they have " "more resources at their disposal. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Je moet de barbaarse stammen van het noorden verenigen door ze te veroveren. " "Net als in de vorige missie is de vijand niet geallieerd tegen jou, maar " "hebben ze meer middelen tot hun beschikking. Je wint als je alle " "vijandelijke kastelen bezit en er geen helden meer zijn om tegen te vechten." msgid "" "Do-gooder wizards have taken the Necromancers' castle. You must retake it to " "achieve victory. Remember that while you start with a powerful army, you " "have no castle and must take one within 7 days, or lose this battle. (Hint: " "The nearest castle is to the southeast.)" msgstr "" "Weldoener tovenaars hebben het kasteel van de Necromancers ingenomen. Je " "moet het heroveren om de overwinning te behalen. Onthoud dat terwijl je " "begint met een machtig leger, je geen kasteel hebt en er binnen 7 dagen een " "moet veroveren, anders verlies je deze strijd. (Hint: het dichtstbijzijnde " "kasteel ligt in het zuidoosten.)" msgid "" "The dwarves need conquering before they can interfere in King Archibald's " "plans. Roland's forces have more than one hero and many towns to start with, " "so be ready for attack from multiple directions. You must capture all of the " "enemy towns and castles to claim victory." msgstr "" "De dwergen moeten worden overwonnen voordat ze zich kunnen bemoeien met de " "plannen van koning Archibald. Rolands strijdkrachten hebben om te beginnen " "meer dan één held en veel steden, dus wees voorbereid op aanvallen vanuit " "meerdere richtingen. Je moet alle vijandige steden en kastelen veroveren om " "de overwinning te claimen." msgid "" "You must put down a peasant revolt led by Roland's forces. All are allied " "against you, but you have Lord Corlagon, an experienced hero, to help you. " "Capture all enemy castles to win." msgstr "" "Je moet een boerenopstand neerslaan onder leiding van Rolands troepen. Ze " "zijn allemaal tegen je geallieerd, maar je hebt Lord Corlagon, een ervaren " "held, om je te helpen. Verover alle vijandelijke kastelen om te winnen." msgid "" "There are two enemies allied against you in this mission. Both are well " "armed and seek to evict you from their island. Avoid them and capture Dragon " "City to win." msgstr "" "Er zijn twee vijanden die tegen je geallieerd zijn in deze missie. Beiden " "zijn goed bewapend en proberen je van hun eiland te verdrijven. Ontwijk ze " "en verover Dragon City om te winnen." msgid "" "Your orders are to conquer the country lords that have sworn to serve " "Roland. All of the enemy castles are unified against you. Since you start " "without a castle, you must hurry to capture one before the end of the week. " "Capture all enemy castles for victory." msgstr "" "Jouw orders zijn om de landheren te veroveren die gezworen hebben om Roland " "te dienen. Alle vijandige kastelen zijn tegen je verenigd. Aangezien je " "zonder kasteel begint, moet je je haasten om er voor het einde van de week " "een te veroveren. Verover alle vijandelijke kastelen voor de overwinning." msgid "" "Find the Crown before Roland's heroes find it. Archibald will need the Crown " "for the final battle against Roland." msgstr "" "Vind de kroon voordat de helden van Roland hem vinden. Archibald heeft de " "Kroon nodig voor het laatste gevecht tegen Roland." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Roland to win the war, and be sure " "not to lose Archibald in the fight!" msgstr "" "Dit is de laatste strijd. Zowel jij als je vijand zijn tot de tanden " "bewapend, en ze zijn allemaal tegen je geallieerd. Leg Roland vast om de " "oorlog te winnen en zorg ervoor dat je Archibald niet verliest in het " "gevecht!" msgid "Arrow's Flight" msgstr "De vlucht van de Pijl" msgid "Island of Chaos" msgstr "Eiland van Chaos" msgid "The Abyss" msgstr "De Afgrond" msgid "Uprising" msgstr "Opstand" msgid "Aurora Borealis" msgstr "Noorderlicht" msgid "Betrayal's End" msgstr "Het einde van verraad" msgid "Corruption's Heart" msgstr "Het hart van corruptie" msgid "The Giant's Pass" msgstr "De Reuzenpas" msgid "" "Subdue the unruly local lords in order to provide the Empire with facilities " "to operate in this region." msgstr "" "Onderwerp de weerbarstige lokale heren om het rijk te voorzien van " "faciliteiten om in deze regio te opereren." msgid "" "Eliminate all opposition in this area. Then the first piece of the artifact " "will be yours." msgstr "" "Elimineer alle oppositie op dit gebied. Dan is het eerste stuk van het " "artefact van jou." msgid "" "The sorceresses to the northeast are rebelling! For the good of the empire " "you must quash their feeble uprising on your way to the mountains." msgstr "" "De tovenaressen in het noordoosten komen in opstand! Voor het welzijn van " "het rijk moet je hun zwakke opstand neerslaan op weg naar de bergen." msgid "" "Having prepared for your arrival, Kraeger has arranged for a force of " "necromancers to thwart your quest. You must capture the castle of Scabsdale " "before the first day of the third week, or the Necromancers will be too " "strong for you." msgstr "" "Kraeger heeft zich voorbereid op je komst en heeft een leger van " "necromancers geregeld om je zoektocht te dwarsbomen. Je moet het kasteel van " "Scabsdale veroveren voor de eerste dag van de derde week, anders zijn de " "Necromancers te sterk voor je." msgid "" "The barbarian despot in this area is, as yet, ignorant of your presence. " "Quickly, build up your forces before you are discovered and attacked! Secure " "the region by subduing all enemy forces." msgstr "" "De barbaarse despoot in dit gebied is nog niet op de hoogte van je " "aanwezigheid. Bouw snel je troepen op voordat je ontdekt en aangevallen " "wordt! Beveilig de regio door alle vijandelijke troepen te onderwerpen." msgid "" "The Empire is weak in this region. You will be unable to completely subdue " "all forces in this area, so take what you can before reprisal strikes. " "Remember, your true goal is to claim the Helmet of Anduran." msgstr "" "Het rijk is zwak in deze regio. Je zult niet in staat zijn om alle troepen " "in dit gebied volledig te bedwingen, dus pak wat je kunt voordat " "represailles toeslaan. Onthoud dat je echte doel is om de Helm van Anduran " "te claimen." msgid "For the good of the Empire, eliminate Kraeger." msgstr "Schakel Kraeger uit voor het welzijn van het rijk." msgid "" "At last, you have the opportunity and the facilities to rid the Empire of " "the necromancer's evil. Eradicate them completely, and you will be sung as a " "hero for all time." msgstr "" "Eindelijk heb je de kans en de faciliteiten om het rijk te verlossen van het " "kwaad van de necromancer. Als je ze volledig uitroeit, word je voor altijd " "als een held bezongen." msgid "Border Towns" msgstr "Grenssteden" msgid "Conquer and Unify" msgstr "Verover en verenig" msgid "Crazy Uncle Ivan" msgstr "Gekke Oom Ivan" msgid "The Wayward Son" msgstr "De Eigenzinnige Zoon" msgid "Ivory Gates" msgstr "Ivoren Poorten" msgid "The Elven Lands" msgstr "De Elfen Landen" msgid "The Epic Battle" msgstr "De Epische Strijd" msgid "The Southern War" msgstr "De Zuidelijke Oorlog" msgid "" "Conquer and unite all the enemy tribes. Don't lose the hero Jarkonas, the " "forefather of all descendants." msgstr "" "Verover en verenig alle vijandige stammen. Verlies de held Jarkonas niet, de " "voorvader van alle nakomelingen." msgid "" "Your rival, the Kingdom of Harondale, is attacking weak towns on your " "border! Recover from their first strike and crush them completely!" msgstr "" "Je rivaal, het koninkrijk Harondale, valt zwakke steden aan je grens aan! " "Herstel van hun eerste aanval en verpletter ze volledig!" msgid "" "Find your wayward son Joseph who is rumored to be living in the desolate " "lands. Do it before the first day of the third month or it will be of no " "help to your family." msgstr "" "Vind je eigenzinnige zoon Joseph, van wie wordt gezegd dat hij in de " "verlaten landen woont. Doe het voor de eerste dag van de derde maand, anders " "heeft je gezin er niets aan." msgid "" "Rescue your crazy uncle Ivan. Find him before the first day of the fourth " "month or it will be no help to your kingdom." msgstr "" "Red je gekke oom Ivan. Vind hem voor de eerste dag van de vierde maand of " "het zal je koninkrijk niet helpen." msgid "" "Destroy the barbarians who are attacking the southern border of your " "kingdom! Recover your fallen towns, and then invade the jungle kingdom. " "Leave no enemy standing." msgstr "" "Vernietig de barbaren die de zuidelijke grens van je koninkrijk aanvallen! " "Herstel je gevallen steden en val dan het junglekoninkrijk binnen. Laat geen " "vijand staan." msgid "Retake the castle of Ivory Gates, which has fallen due to treachery." msgstr "Herover het kasteel van Ivory Gates, dat door verraad is gevallen." msgid "" "Gain the favor of the elves. They will not allow trees to be chopped down, " "so they will send you wood every 2 weeks. You must complete your mission " "before the first day of the seventh month, or the kingdom will surely fall." msgstr "" "Win de gunst van de elfen. Ze laten niet toe dat bomen worden gekapt, dus " "sturen ze je elke 2 weken hout. Je moet je missie voltooien vóór de eerste " "dag van de zevende maand, anders zal het koninkrijk zeker vallen." msgid "" "This is the final battle against your rival kingdom of Harondale. Eliminate " "everyone, and don't lose the hero Jarkonas VI." msgstr "" "Dit is de laatste strijd tegen je rivaliserende koninkrijk Harondale. " "Schakel iedereen uit en verlies de held Jarkonas VI niet." msgid "Fount of Wizardry" msgstr "Bron van Tovenarij" msgid "Power's End" msgstr "Einde van de macht" msgid "The Eternal Scrolls" msgstr "De Eeuwige Rollen" msgid "The Shrouded Isles" msgstr "De Gehulde Eilanden" msgid "" "Your mission is to vanquish the warring mages in the magical Shrouded Isles. " "The completion of this task will give you a fighting chance against your " "rivals." msgstr "" "Jouw missie is om de strijdende magiërs op de magische Shrouded Isles te " "verslaan. De voltooiing van deze taak geeft je een vechtkans tegen je " "rivalen." msgid "" "The location of the great library has been discovered! You must make your " "way to it, and reclaim the city of Chronos in which it lies." msgstr "" "De locatie van de grote bibliotheek is ontdekt! Je moet je weg ernaartoe " "vinden en de stad Chronos waarin het ligt terugwinnen." #, fuzzy msgid "" "Find the Orb of negation, which is said to be buried in this land. There are " "clues inscribed on stone obelisks which will help lead you to your prize. " "Find the Orb before the first day of the sixth month, or your rivals will " "surely have gotten to the fount before you." msgstr "" "Vind de bol van ontkenning, waarvan wordt gezegd dat deze in dit land " "begraven ligt. Er zijn aanwijzingen op stenen obelisken gegraveerd die je " "naar je prijs zullen leiden. Vind de bol vóór de eerste dag van de zesde " "maand, anders zullen je rivalen zeker de bron voor je hebben bereikt." msgid "" "You must take control of the castle of Magic, where the fount of wizardry " "lies. Do this and your victory will be supreme." msgstr "" "Je moet het kasteel van Magic overnemen, waar de bron van tovenarij ligt. " "Doe dit en je overwinning zal oppermachtig zijn." msgid "Blood is Thicker" msgstr "Bloed is Dikker" msgid "King and Country" msgstr "Koning en Land" msgid "Pirate Isles" msgstr "Pirateneilanden" msgid "Stranded" msgstr "Gestrand" msgid "" "Capture the town on the island off the southeast shore in order to construct " "a boat and travel back towards the mainland. Do not lose the hero Gallavant." msgstr "" "Verover de stad op het eiland voor de zuidoostkust om een boot te bouwen en " "terug te reizen naar het vasteland. Verlies de held Gallavant niet." msgid "" "Find and defeat Martine, the pirate leader, who resides in Pirates Cove. Do " "not lose Gallavant or your quest will be over." msgstr "" "Vind en versla Martine, de piratenleider, die in Pirates Cove woont. Verlies " "Gallavant niet, anders is je zoektocht voorbij." msgid "" "Eliminate all the other forces who oppose the rule of Lord Alberon. " "Gallavant must not die." msgstr "" "Schakel alle andere krachten uit die zich verzetten tegen de heerschappij " "van Lord Alberon. Gallavant mag niet sterven." msgid "" "Overthrow the entrenched monarchy of Lord Alberon, and claim all the land in " "your name. Gallavant must not die." msgstr "" "Omver de diepgewortelde monarchie van Lord Alberon en claim al het land in " "je naam. Gallavant mag niet sterven." msgid " bane" msgstr " vloek" msgid " alliance" msgstr " alliantie" msgid "Carry-over forces" msgstr "Overdrachtskrachten" msgid " bonus" msgstr " bonus" msgid " defeated" msgstr " verslagen" msgid " will always run away from your army." msgstr " zal altijd wegrennen van je leger." msgid " will be willing to join your army." msgstr " zal bereid zijn om zich bij je leger aan te sluiten." msgid "\"%{artifact}\" artifact will be carried over the scenario." msgstr "\"%{artifact}\" artefact zal over het scenario worden overgedragen." msgid "The army will be carried over the scenario." msgstr "Het leger zal het scenario overnemen." msgid "The kingdom will have +%{count} %{resource} each day." msgstr "Het koninkrijk zal elke dag +%{count} %{resource} hebben." msgid "\"%{spell}\" spell will be carried over the scenario." msgstr "De spreuk \"%{spell}\" wordt over het scenario overgedragen." msgid "%{hero} can be hired in the scenario." msgstr "%{hero} kan worden ingehuurd in het scenario." msgid "" "%{hero} has been defeated and will not appear in the subsequent scenarios." msgstr "" "%{hero} is verslagen en zal niet verschijnen in de volgende scenario's." msgid "The dwarves recognize their allies and gladly join your forces." msgstr "De dwergen herkennen hun bondgenoten en sluiten zich graag bij je aan." msgid "The ogres recognize you as the Dwarfbane and lumber over to join you." msgstr "" "De ogres herkennen je als de Drawven Moordenaar en sjokken naar je toe." msgid "" "The dragons, snarling and growling, agree to join forces with you, their " "'Ally'." msgstr "" "De draken komen grommend en grommend overeen om hun krachten te bundelen met " "jou, hun 'bondgenoot'." #, fuzzy msgid "" "As you approach the group of elves, their leader calls them all to " "attention. He shouts to them, \"Who of you is brave enough to join this " "fearless ally of ours?\" The group explodes with cheers as they run to join " "your ranks." msgstr "" "Als je de groep elfen nadert, roept hun leider ze allemaal onder de " "aandacht. Hij roept naar hen: 'Wie van jullie is dapper genoeg om zich bij " "deze onverschrokken bondgenoot van ons aan te sluiten?' De groep explodeert " "met gejuich terwijl ze rennen om zich bij je gelederen te voegen." #, fuzzy msgid "" "The dwarves hail you, \"Any friend of Roland is a friend of ours. You may " "pass.\"" msgstr "" "De dwergen begroeten je, \"Elke vriend van Roland is een vriend van ons. Je " "mag passeren.\"" msgid "" "The ogres give you a grunt of recognition, \"Archibald's allies may pass.\"" msgstr "" "De ogres geven je een grom van herkenning, \"Archibald's bondgenoten kunnen " "passeren.\"" #, fuzzy msgid "" "The dragons see you and call out. \"Our alliance with Archibald compels us " "to join you. Unfortunately you have no room. A pity!\" They quickly scatter." msgstr "" "De draken zien je en roepen. \"Onze alliantie met Archibald dwingt ons om " "met je mee te doen. Helaas heb je geen ruimte. Jammer!\" Ze verspreiden zich " "snel." #, fuzzy msgid "" "The elves stand at attention as you approach. Their leader calls to you and " "says, \"Let us not impede your progress, ally! Move on, and may victory be " "yours.\"" msgstr "" "De elfen staan in de houding als je dichterbij komt. Hun leider roept je en " "zegt: \"Laten we je voortgang niet belemmeren, bondgenoot! Ga verder, en " "moge de overwinning van jou zijn.\"" msgid "\"The Dwarfbane!!!!, run for your lives.\"" msgstr "\"De Drawven Moordenaar!!!!, ren voor je leven.\"" msgid "campaignBonus|Animate Dead" msgstr "Dood Animeren" msgid "campaignBonus|Chain Lightning" msgstr "Ketting Bliksem" msgid "campaignBonus|Fireblast" msgstr "Vuurstraal" msgid "campaignBonus|Mass Curse" msgstr "Massa Vloek" msgid "campaignBonus|Mass Haste" msgstr "Massale Haast" msgid "campaignBonus|Mirror Image" msgstr "Spiegelbeeld" msgid "campaignBonus|Resurrect" msgstr "Herrijzen" msgid "campaignBonus|Steelskin" msgstr "Staalhuid" msgid "campaignBonus|Summon Earth" msgstr "Aarde Oproepen" msgid "campaignBonus|View Heroes" msgstr "Bekijk Helden" msgid "campaignBonus|Ballista" msgstr "Ballista" msgid "campaignBonus|Black Pearl" msgstr "Zwarte Parel" msgid "campaignBonus|Caster's Bracelet" msgstr "Magiers's Armband" msgid "campaignBonus|Defender Helm" msgstr "Verdediger Helm" msgid "campaignBonus|Breastplate" msgstr "Borstplaat" msgid "campaignBonus|Dragon Sword" msgstr "Drakenzwaard" msgid "campaignBonus|Fizbin Medal" msgstr "Fizbin Medaille" msgid "campaignBonus|Foremost Scroll" msgstr "Voorste Scroll" #, fuzzy msgid "campaignBonus|Gauntlets" msgstr "Ballista" msgid "campaignBonus|Hideous Mask" msgstr "Afschuwelijk Masker" msgid "campaignBonus|Mage's Ring" msgstr "Magiër's Ring" msgid "campaignBonus|Major Scroll" msgstr "Grote Rol" msgid "campaignBonus|Medal of Honor" msgstr "Medaille van Eer" msgid "campaignBonus|Medal of Valor" msgstr "Medaille van Moed" msgid "campaignBonus|Minor Scroll" msgstr "Kleine Rol" msgid "campaignBonus|Nomad Boots" msgstr "Nomadenlaarzen" msgid "campaignBonus|Power Axe" msgstr "Machtsbijl" msgid "campaignBonus|Spiked Shield" msgstr "Puntig Schild" msgid "campaignBonus|Stealth Shield" msgstr "Stealth-schild" msgid "campaignBonus|Tax Lien" msgstr "Belastingrecht" msgid "campaignBonus|Thunder Mace" msgstr "Donderstaf" msgid "campaignBonus|Traveler's Boots" msgstr "Reizigerslaarzen" msgid "campaignBonus|White Pearl" msgstr "Witte Parel" msgid "campaignBonus|Basic Archery" msgstr "Basis Boogschieten" msgid "campaignBonus|Advanced Archery" msgstr "Geavanceerd Boogschieten" msgid "campaignBonus|Expert Archery" msgstr "Expert Boogschieten" msgid "campaignBonus|Basic Ballistics" msgstr "Basis Ballistiek" msgid "campaignBonus|Advanced Ballistics" msgstr "Geavanceerd Ballistiek" msgid "campaignBonus|Expert Ballistics" msgstr "Expert Ballistiek" msgid "campaignBonus|Basic Diplomacy" msgstr "Basis Diplomatie" msgid "campaignBonus|Advanced Diplomacy" msgstr "Geavanceerd Diplomatie" msgid "campaignBonus|Expert Diplomacy" msgstr "Expert Diplomatie" msgid "campaignBonus|Basic Eagle Eye" msgstr "Basis Adelaarsoog" msgid "campaignBonus|Advanced Eagle Eye" msgstr "Geavanceerd Adelaarsoog" msgid "campaignBonus|Expert Eagle Eye" msgstr "Expert Adelaarsoog" msgid "campaignBonus|Basic Estates" msgstr "Basis Landgoederen" msgid "campaignBonus|Advanced Estates" msgstr "Geavanceerd Landgoederen" msgid "campaignBonus|Expert Estates" msgstr "Expert Landgoederen" msgid "campaignBonus|Basic Leadership" msgstr "Basis Leiderschap" msgid "campaignBonus|Advanced Leadership" msgstr "Geavanceerd Leiderschap" msgid "campaignBonus|Expert Leadership" msgstr "Expert Leiderschap" msgid "campaignBonus|Basic Logistics" msgstr "Basis Logistiek" msgid "campaignBonus|Advanced Logistics" msgstr "Geavanceerd Logistiek" msgid "campaignBonus|Expert Logistics" msgstr "Expert Logistiek" msgid "campaignBonus|Basic Luck" msgstr "Basis Geluk" msgid "campaignBonus|Advanced Luck" msgstr "Geavanceerd Geluk" msgid "campaignBonus|Expert Luck" msgstr "Expert Geluk" msgid "campaignBonus|Basic Mysticism" msgstr "Basis Mystiek" msgid "campaignBonus|Advanced Mysticism" msgstr "Geavanceerd Mystiek" msgid "campaignBonus|Expert Mysticism" msgstr "Expert Mystiek" msgid "campaignBonus|Basic Navigation" msgstr "Basis Navigatie" msgid "campaignBonus|Advanced Navigation" msgstr "Geavanceerd Navigatie" msgid "campaignBonus|Expert Navigation" msgstr "Expert Navigatie" msgid "campaignBonus|Basic Necromancy" msgstr "Basis Necromantie" msgid "campaignBonus|Advanced Necromancy" msgstr "Geavanceerd Necromantie" msgid "campaignBonus|Expert Necromancy" msgstr "Expert Necromantie" msgid "campaignBonus|Basic Pathfinding" msgstr "Basis Padvinden" msgid "campaignBonus|Advanced Pathfinding" msgstr "Geavanceerd Padvinden" msgid "campaignBonus|Expert Pathfinding" msgstr "Expert Padvinden" msgid "campaignBonus|Basic Scouting" msgstr "Basis Scouting" msgid "campaignBonus|Advanced Scouting" msgstr "Geavanceerd Scouting" msgid "campaignBonus|Expert Scouting" msgstr "Expert Scouting" msgid "campaignBonus|Basic Wisdom" msgstr "Basis Wijsdom" msgid "campaignBonus|Advanced Wisdom" msgstr "Geavanceerd Wijsdom" msgid "campaignBonus|Expert Wisdom" msgstr "Expert Wijsdom" msgid "" "The main hero will have \"%{artifact}\" artifact at the start of the " "scenario." msgstr "" "De hoofdheld zal het \"%{artifact}\" artefact hebben aan het begin van het " "scenario." msgid "" "The kingdom will receive %{amount} additional %{resource} at the start of " "the scenario." msgstr "" "Het koninkrijk zal aan het begin van het scenario %{amount} extra " "%{resource} ontvangen." msgid "" "The kingdom will have %{amount} less %{resource} at the start of the " "scenario." msgstr "" "Het koninkrijk zal %{amount} minder %{resource} hebben aan het begin van het " "scenario." msgid "" "The main hero will have %{count} %{monster} at the start of the scenario." msgstr "De hoofdheld heeft %{count} %{monster} aan het begin van het scenario." msgid "" "The main hero will have \"%{spell}\" spell at the start of the scenario." msgstr "" "De hoofdheld heeft de spreuk \"%{spell}\" aan het begin van het scenario." msgid "The starting race of the scenario will be %{race}." msgstr "De startrace van het scenario is %{race}." msgid "" "The main hero will have additional %{count} %{skill} at the start of the " "scenario." msgstr "" "De hoofdheld heeft extra %{count} %{skill} aan het begin van het scenario." msgid "The main hero will have %{skill} at the start of the scenario." msgstr "De hoofdheld heeft %{skill} aan het begin van het scenario." msgid "Roland" msgstr "Roland" msgid "Archibald" msgstr "Archibald" #, fuzzy msgid "Price of Loyalty" msgstr "De Prijs van Loyaliteit" msgid "Voyage Home" msgstr "Rijs Naar Huis" msgid "Wizard's Isle" msgstr "Tovenaarseiland" msgid "Descendants" msgstr "Afstammelingen" msgid "The %{building} produces %{monster}." msgstr "Het %{building} produceert %{monster}." msgid "Requires:" msgstr "Vereist:" msgid "Exit this menu without doing anything." msgstr "" msgid "Cannot build. You have already built here today." msgstr "Kan niet bouwen. Je hebt hier vandaag al gebouwd." msgid "For this action it is necessary to build a castle first." msgstr "Voor deze actie moet je eerst een kasteel bouwen." #, fuzzy msgid "Cannot build %{name} because the castle is too far away from an ocean." msgstr "Kan %{name} niet bouwen omdat het kasteel te ver van water is." msgid "disable build." msgstr "bouwen uitschakelen." msgid "Cannot afford %{name}." msgstr "Kan %{name} niet betalen." msgid "%{name} is already built." msgstr "%{name} is al gebouwd." msgid "Cannot build %{name}." msgstr "Kan %{name} niet bouwen." msgid "Build %{name}." msgstr "Bouw %{name}." msgid "Blackridge" msgstr "" msgid "Hillstone" msgstr "" msgid "Pinehurst" msgstr "" msgid "Whiteshield" msgstr "" msgid "Woodhaven" msgstr "" msgid "Blackwind" msgstr "" msgid "Bloodreign" msgstr "" msgid "Dragontooth" msgstr "Drakentand" msgid "Greywind" msgstr "" msgid "Portsmith" msgstr "" msgid "Atlantium" msgstr "" msgid "Middle Gate" msgstr "" msgid "Sansobar" msgstr "" msgid "Tundara" msgstr "" msgid "Vulcania" msgstr "" msgid "Baywatch" msgstr "" msgid "Fountainhead" msgstr "" msgid "Vertigo" msgstr "" msgid "Wildabar" msgstr "" msgid "Winterkill" msgstr "" msgid "Brindamoor" msgstr "" msgid "Lakeside" msgstr "" msgid "Nightshadow" msgstr "" msgid "Olympus" msgstr "" msgid "Sandcaster" msgstr "" msgid "Alamar" msgstr "" msgid "Burlock" msgstr "" msgid "Dragadune" msgstr "" msgid "Kalindra" msgstr "" msgid "Xabran" msgstr "" msgid "Algary" msgstr "" msgid "Basenji" msgstr "" msgid "Blackfang" msgstr "" msgid "New Dawn" msgstr "" msgid "Sorpigal" msgstr "" msgid "Avone" msgstr "" msgid "Big Oak" msgstr "" msgid "Chandler" msgstr "" msgid "Erliquin" msgstr "" msgid "Hampshire" msgstr "" msgid "Antioch" msgstr "" msgid "Avalon" msgstr "" msgid "Roc Haven" msgstr "" msgid "South Mill" msgstr "" msgid "Weed Patch" msgstr "" msgid "Brownston" msgstr "" msgid "Hilltop" msgstr "" msgid "Weddington" msgstr "" msgid "Westfork" msgstr "" msgid "Whittingham" msgstr "" msgid "Cathcart" msgstr "" msgid "Elk's Head" msgstr "" msgid "Roscomon" msgstr "" msgid "Sherman" msgstr "" msgid "Yorksford" msgstr "" msgid "Blackburn" msgstr "" msgid "Blacksford" msgstr "" msgid "Burton" msgstr "" msgid "Pig's Eye" msgstr "" msgid "Viper's Nest" msgstr "" msgid "Fenton" msgstr "" msgid "Lankershire" msgstr "" msgid "Lombard" msgstr "" msgid "Timberhill" msgstr "" msgid "Troy" msgstr "" msgid "Forder Oaks" msgstr "" msgid "Meramec" msgstr "" msgid "Quick Silver" msgstr "" msgid "Westmoor" msgstr "" msgid "Willow" msgstr "" msgid "Corackston" msgstr "" msgid "Sheltemburg" msgstr "" msgid "Cannot recruit - you already have a Hero in this town." msgstr "" msgid "Cannot recruit - you have too many Heroes." msgstr "" msgid "Cannot afford a Hero" msgstr "" msgid "There is no room in the garrison for this army." msgstr "" msgid "Fortifications" msgstr "" msgid "Farm" msgstr "" msgid "Thatched Hut" msgstr "" msgid "Archery Range" msgstr "" msgid "Upg. Archery Range" msgstr "" msgid "Blacksmith" msgstr "" msgid "Upg. Blacksmith" msgstr "" msgid "Armory" msgstr "" msgid "Upg. Armory" msgstr "" msgid "Jousting Arena" msgstr "" msgid "Upg. Jousting Arena" msgstr "" msgid "Cathedral" msgstr "" msgid "Upg. Cathedral" msgstr "" msgid "Coliseum" msgstr "" msgid "Garbage Heap" msgstr "" msgid "Hut" msgstr "" msgid "Stick Hut" msgstr "" msgid "Upg. Stick Hut" msgstr "" msgid "Den" msgstr "" msgid "Adobe" msgstr "" msgid "Upg. Adobe" msgstr "" msgid "Bridge" msgstr "" msgid "Upg. Bridge" msgstr "" msgid "Pyramid" msgstr "" msgid "Rainbow" msgstr "" msgid "Crystal Garden" msgstr "" msgid "Treehouse" msgstr "" msgid "Cottage" msgstr "" msgid "Upg. Cottage" msgstr "" msgid "Stonehenge" msgstr "" msgid "Upg. Stonehenge" msgstr "" msgid "Fenced Meadow" msgstr "" msgid "sorceress|Red Tower" msgstr "" msgid "Dungeon" msgstr "" msgid "Waterfall" msgstr "" msgid "Cave" msgstr "" msgid "Crypt" msgstr "" msgid "Nest" msgstr "" msgid "Maze" msgstr "" msgid "Upg. Maze" msgstr "" msgid "Swamp" msgstr "" msgid "Green Tower" msgstr "" msgid "warlock|Red Tower" msgstr "" msgid "Black Tower" msgstr "" msgid "Library" msgstr "" msgid "Orchard" msgstr "" msgid "Habitat" msgstr "" msgid "Pen" msgstr "" msgid "Foundry" msgstr "" msgid "Upg. Foundry" msgstr "" msgid "Cliff Nest" msgstr "" msgid "Ivory Tower" msgstr "" msgid "Upg. Ivory Tower" msgstr "" msgid "Cloud Castle" msgstr "" msgid "Upg. Cloud Castle" msgstr "" msgid "Storm" msgstr "" msgid "Skull Pile" msgstr "" msgid "Excavation" msgstr "" msgid "Graveyard" msgstr "" msgid "Upg. Graveyard" msgstr "" msgid "Upg. Pyramid" msgstr "" msgid "Mansion" msgstr "" msgid "Upg. Mansion" msgstr "" msgid "Mausoleum" msgstr "" msgid "Upg. Mausoleum" msgstr "" msgid "Laboratory" msgstr "" msgid "Shrine" msgstr "" msgid "" "The Fortifications increase the toughness of the walls, increasing the " "number of turns it takes to knock them down." msgstr "" msgid "The Farm increases production of Peasants by %{count} per week." msgstr "" msgid "" "The Coliseum provides inspiring spectacles to defending troops, raising " "their morale by two during combat." msgstr "" msgid "The Garbage Heap increases production of Goblins by %{count} per week." msgstr "" msgid "The Rainbow increases the luck of the defending units by two." msgstr "" msgid "" "The Crystal Garden increases production of Sprites by %{count} per week." msgstr "" msgid "The Dungeon increases the income of the town by %{count} gold per day." msgstr "" msgid "The Waterfall increases production of Centaurs by %{count} per week." msgstr "" msgid "" "The Library increases the number of spells in the Guild by one for each " "level of the guild." msgstr "" msgid "The Orchard increases production of Halflings by %{count} per week." msgstr "" msgid "The Storm adds +2 to the power of spells of a defending spell caster." msgstr "" msgid "The Skull Pile increases production of Skeletons by %{count} per week." msgstr "" msgid "Thieves' Guild" msgstr "" msgid "Tavern" msgstr "" msgid "Shipyard" msgstr "" msgid "Well" msgstr "" msgid "Statue" msgstr "" msgid "Marketplace" msgstr "" msgid "Moat" msgstr "" msgid "Castle" msgstr "" msgid "Tent" msgstr "" msgid "Captain's Quarters" msgstr "" msgid "Mage Guild, Level 1" msgstr "" msgid "Mage Guild, Level 2" msgstr "" msgid "Mage Guild, Level 3" msgstr "" msgid "Mage Guild, Level 4" msgstr "" msgid "Mage Guild, Level 5" msgstr "" msgid "" "The Shrine increases the necromancy skill of all your necromancers by 10 " "percent." msgstr "" msgid "" "The Thieves' Guild provides information on enemy players. Thieves' Guilds " "can also provide scouting information on enemy towns. Additional Guilds " "provide more information." msgstr "" msgid "The Tavern increases morale for troops defending the castle." msgstr "" msgid "The Shipyard allows ships to be built." msgstr "" msgid "" "The Well increases the growth rate of all dwellings by %{count} creatures " "per week." msgstr "" msgid "The Statue increases the town's income by %{count} gold per day." msgstr "" msgid "The Left Turret provides extra firepower during castle combat." msgstr "" msgid "The Right Turret provides extra firepower during castle combat." msgstr "" msgid "" "The Marketplace can be used to convert one type of resource into another. " "The more marketplaces you control, the better the exchange rate." msgstr "" msgid "" "The Moat slows attacking units. Any unit entering the moat must end its turn " "there and becomes more vulnerable to attack." msgstr "" msgid "" "The Castle improves the town's defense and increases its income to %{count} " "gold per day." msgstr "" msgid "" "The Tent provides workers to build a castle, provided the materials and the " "gold are available." msgstr "" msgid "" "The Captain's Quarters provides a captain to assist in the castle's defense " "when no hero is present." msgstr "" msgid "" "The Mage Guild allows heroes to learn spells and replenish their spell " "points." msgstr "" msgid "Recruit %{name}" msgstr "" msgid "Month: %{month}, Week: %{week}, Day: %{day}" msgstr "" msgid "" "You must purchase a spell book to use the mage guild, but you currently have " "no room for a spell book. Try giving one of your artifacts to another hero." msgstr "" msgid "Click to show next town." msgstr "" msgid "Show next town" msgstr "" msgid "Click to show previous town." msgstr "" msgid "Show previous town" msgstr "" msgid "This town may not be upgraded to a castle." msgstr "" msgid "Town" msgstr "" msgid "Exit Castle" msgstr "" msgid "Exit Town" msgstr "" msgid "Show Income" msgstr "" msgid "View Hero" msgstr "" msgid "The above spells are available here." msgstr "" msgid "The spells the hero can learn have been added to their book." msgstr "" msgid "A generous tip for the barkeep yields the following rumor:" msgstr "" msgid "Recruit Hero" msgstr "" msgid "%{name} is a level %{value} %{race} " msgstr "" msgid "with %{count} artifacts." msgstr "" msgid "with 1 artifact." msgstr "" msgid "without artifacts." msgstr "" msgid "Recruit %{name} the %{race}" msgstr "" msgid "" "'Spread' combat formation spreads the castle's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" msgid "" "'Grouped' combat formation bunches the castle's units together in the center " "of the castle's side of the battlefield." msgstr "" msgid "Spread Formation" msgstr "" msgid "Grouped Formation" msgstr "" msgid "Set garrison combat formation to 'Spread'" msgstr "" msgid "Set garrison combat formation to 'Grouped'" msgstr "" msgid "Exit Castle Options" msgstr "" msgid "Castle Options" msgstr "" msgid "Not enough resources to recruit creatures." msgstr "" msgid "You are unable to recruit at this time, your ranks are full." msgstr "" msgid "No creatures available for purchase." msgstr "" msgid "Recruit Creatures" msgstr "" msgid "Max" msgstr "" msgid "Hire all creatures in the town." msgstr "" msgid "Available" msgstr "" msgid "Town Population Information and Statistics" msgstr "" msgid "Damage" msgstr "" msgid "HP" msgstr "" msgid "Growth" msgstr "" msgid "week" msgstr "" msgid "View World" msgstr "" msgid "View the entire world." msgstr "" msgid "Puzzle" msgstr "" msgid "View the obelisk puzzle." msgstr "" msgid "Scenario Information" msgstr "" msgid "View information on the scenario you are currently playing." msgstr "" msgid "Dig for the Ultimate Artifact." msgstr "" msgid "Digging" msgstr "" msgid "Arena" msgstr "" msgid "" "You enter the arena and face a pack of vicious lions. You handily defeat " "them, to the wild cheers of the crowd. Impressed by your skill, the aged " "trainer of gladiators agrees to train you in a skill of your choice." msgstr "" msgid "Attack Skill" msgstr "" msgid "Defense Skill" msgstr "" msgid "Shots" msgstr "" msgid "Shots Left" msgstr "" msgid "Hit Points" msgstr "" msgid "Hit Points Left" msgstr "" msgid "You can't afford to upgrade your troops!" msgstr "" msgid "" "Your troops can be upgraded, but it will cost you dearly. Do you wish to " "upgrade them?" msgstr "" msgid "Are you sure you want to dismiss this army?" msgstr "" msgid "Upgrade" msgstr "" msgid "Upgrade your troops." msgstr "" msgid "Dismiss" msgstr "" msgid "Dismiss this army." msgstr "" msgid "" "A group of %{monster} with a desire for greater glory wish to join you.\n" "Do you accept?" msgstr "" msgid "Followers" msgstr "" msgid "" "The %{monster} is swayed by your diplomatic tongue, and offers to join your " "army for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" msgid "" "The creatures are swayed by your diplomatic\n" "tongue, and make you an offer:\n" "\n" msgstr "" msgid "" "%{offer} of the %{total} %{monster} will join your army, and the rest will " "leave you alone, for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" msgid "" "All %{offer} of the %{monster} will join your army for the sum of %{gold} " "gold.\n" "Do you accept?" msgstr "" msgid "(Rate: %{percent})" msgstr "" msgid "off" msgstr "" msgid "Music" msgstr "" msgid "Effects" msgstr "" msgid "MIDI" msgstr "" msgid "MIDI Expansion" msgstr "" msgid "External" msgstr "" msgid "Music Type" msgstr "" msgid "3D Audio" msgstr "" msgid "Toggle ambient music level." msgstr "" msgid "Toggle foreground sounds level." msgstr "" msgid "Change the type of music." msgstr "" msgid "Toggle 3D effects of foreground sounds." msgstr "" msgid "Build a new ship:" msgstr "" msgid "Resource cost:" msgstr "" msgid "Total: " msgstr "" msgid "Need: " msgstr "" msgid "Load Game" msgstr "" msgid "No save files to load." msgstr "" msgid "New Game" msgstr "" msgid "Start a single or multi-player game." msgstr "" msgid "Load a previously saved game." msgstr "" msgid "Save Game" msgstr "" msgid "Save the current game." msgstr "" msgid "Quit" msgstr "" msgid "Quit out of Heroes of Might and Magic II." msgstr "" msgid "Language" msgstr "" msgid "Graphics" msgstr "" msgid "Black & White" msgstr "" msgid "Mouse Cursor" msgstr "" msgid "Color" msgstr "" msgid "Text Support" msgstr "" msgid "Change the language of the game." msgstr "" msgid "Select Game Language" msgstr "" msgid "Change the graphics settings of the game." msgstr "" msgid "Toggle colored cursor on or off. This is only an aesthetic choice." msgstr "" msgid "" "Toggle text support mode to output extra information about windows and " "events in the game." msgstr "" msgid "" "Map\n" "Difficulty" msgstr "" msgid "" "Game\n" "Difficulty" msgstr "" msgid "Rating" msgstr "" msgid "Map Size" msgstr "" msgid "Opponents" msgstr "" msgid "Class" msgstr "" msgid "" "Victory\n" "Conditions" msgstr "" msgid "" "Loss\n" "Conditions" msgstr "" msgid "First select recipients!" msgstr "" msgid "You cannot select %{resource}!" msgstr "" msgid "Select count %{resource}:" msgstr "" msgid "Select Recipients" msgstr "" msgid "Your Funds" msgstr "" msgid "Planned Gift" msgstr "" msgid "Gift from %{name}" msgstr "" msgid "Resolution" msgstr "" msgid "Fullscreen" msgstr "" msgid "window|Mode" msgstr "" msgid "Windowed" msgstr "" msgid "V-Sync" msgstr "" #, fuzzy msgid "on" msgstr "Geen" msgid "FPS" msgstr "" #, fuzzy msgid "System Info" msgstr "Systeem Instellingen" msgid "Change the resolution of the game." msgstr "" msgid "Select Game Resolution" msgstr "" msgid "Toggle between fullscreen and windowed modes." msgstr "" msgid "" "The V-Sync option can be enabled to resolve flickering issues on some " "monitors." msgstr "" msgid "Show extra information such as FPS and current time." msgstr "" msgid "Hotkey: " msgstr "" msgid "Category: " msgstr "" msgid "Event: " msgstr "" msgid "Hot Keys:" msgstr "" msgid "Evil" msgstr "" msgid "Good" msgstr "" msgid "Interface Type" msgstr "" #, fuzzy msgid "Hide" msgstr "Horde" msgid "Show" msgstr "" msgid "Interface" msgstr "" msgid "Slow" msgstr "" msgid "Normal" msgstr "" msgid "Fast" msgstr "" msgid "Very Fast" msgstr "" msgid "Scroll Speed" msgstr "" msgid "Toggle the type of interface you want to use." msgstr "" msgid "Toggle interface visibility." msgstr "" msgid "Toggle colored cursor on or off. This is only an esthetic choice." msgstr "" msgid "Sets the speed at which you scroll the window." msgstr "" msgid "Select Game Language:" msgstr "" msgid "Click to choose the selected language." msgstr "" msgid "%{name} has gained a level." msgstr "" msgid "%{skill} +1" msgstr "" msgid "You have learned %{skill}." msgstr "" msgid "" "%{name} has gained a level.\n" "\n" "%{skill} +1" msgstr "" msgid "" "You may learn either:\n" "%{skill1}\n" "or\n" "%{skill2}" msgstr "" msgid "" "Please inspect our fine wares. If you feel like offering a trade, click on " "the items you wish to trade with and for." msgstr "" msgid "" "You have received quite a bargain. I expect to make no profit on the deal. " "Can I interest you in any of my other wares?" msgstr "" msgid "I can offer you %{count} for 1 unit of %{resfrom}." msgstr "" msgid "I can offer you 1 unit of %{resto} for %{count} units of %{resfrom}." msgstr "" msgid "Min" msgstr "" msgid "Qty to trade" msgstr "" msgid "Trading Post" msgstr "" msgid "Your Resources" msgstr "" msgid "Available Trades" msgstr "" msgid "n/a" msgstr "" msgid "guarded by %{count} %{monster}" msgstr "" msgid "guarded by " msgstr "" msgid "(available: %{count})" msgstr "" msgid "(empty)" msgstr "" msgid "already learned" msgstr "" #, fuzzy msgid "treeOfKnowledge|free" msgstr "Kennis" msgid "already claimed" msgstr "" msgid "not claimed" msgstr "" msgid "already knows this skill" msgstr "" msgid "already has max skills" msgstr "" msgid "(already visited)" msgstr "" msgid "(not visited)" msgstr "" msgid "%{color} Barrier" msgstr "" msgid "(tent visited)" msgstr "" msgid "%{color} Tent" msgstr "" msgid "Road" msgstr "" msgid "(digging ok)" msgstr "" msgid "(no digging)" msgstr "" msgid "penalty: %{cost}" msgstr "" msgid "Defenders:" msgstr "" msgid "Unknown" msgstr "" msgid "%{name} (Level %{level})" msgstr "" msgid "Attack:" msgstr "" msgid "Defense:" msgstr "" msgid "Spell Power:" msgstr "" msgid "Knowledge:" msgstr "" msgid "Spell Points:" msgstr "" msgid "Move Points:" msgstr "" msgid "Uncharted Territory" msgstr "" msgid "Cost per troop:" msgstr "" msgid "Available: %{count}" msgstr "" msgid "Number to buy:" msgstr "" msgid "Recruit selected monsters." msgstr "" msgid "Select maximum monsters to be recruited." msgstr "" msgid "Select only 1 monster to be recruited." msgstr "" msgid "Select Game Resolution:" msgstr "" msgid "Click to apply the selected resolution." msgstr "" msgid "Click to apply the entered text." msgstr "" msgid "Click to open the Virtual Keyboard dialog." msgstr "" msgid "Open Virtual Keyboard" msgstr "" msgid "How many troops to move?" msgstr "" msgid "Fast separation into slots:" msgstr "" msgid "Map: " msgstr "" msgid "" "\n" "\n" "Month: " msgstr "" msgid ", Week: " msgstr "" msgid ", Day: " msgstr "" msgid "" "\n" "\n" "Location: " msgstr "" msgid "Click to save the current game." msgstr "" msgid "Click to load a previously saved game." msgstr "" msgid "Are you sure you want to delete file:" msgstr "" msgid "Warning!" msgstr "" msgid "File to Save:" msgstr "" msgid "File to Load:" msgstr "" msgid "Accept the choice made." msgstr "" msgid "%{color} %{race} hero" msgstr "" msgid "Terrain object" msgstr "" msgid "Select Skill:" msgstr "" msgid "Select Spell:" msgstr "" msgid "Select Artifact:" msgstr "" msgid "Select Monster:" msgstr "" msgid "Select Hero:" msgstr "" #, fuzzy msgid "Select Treasure:" msgstr "Selecteer spreuk doelwit" #, fuzzy msgid "Select Ocean Object:" msgstr "Selecteer spreuk doelwit" msgid "Castle/town placing" msgstr "" msgid "doubleLinedRace|Neutral" msgstr "" msgid "race|Random" msgstr "" msgid "Click to start placing the selected castle/town." msgstr "" msgid "Click to select town placing." msgstr "" msgid "Click to select castle placing." msgstr "" msgid "%{color} %{race} %{townOrCastle}" msgstr "" msgid "race|Neutral" msgstr "" msgid "You will place" msgstr "" msgid "Click to select this class." msgstr "" msgid "Click to select this color." msgstr "" #, fuzzy msgid "Select Dwelling:" msgstr "Selecteer spreuk doelwit" #, fuzzy msgid "Select Landscape Object:" msgstr "Selecteer spreuk doelwit" msgid "Mine placing" msgstr "" msgid "" "Resource\n" "type:" msgstr "" #, fuzzy msgid "%{mineName} appearance:" msgstr "%{name} de %{race}" msgid "Click to select %{object} as the resource generator to be placed." msgstr "" #, fuzzy msgid "Select Mountain Object:" msgstr "Selecteer spreuk doelwit" #, fuzzy msgid "Select Rock Object:" msgstr "Selecteer spreuk doelwit" #, fuzzy msgid "Select Tree Object:" msgstr "Selecteer spreuk doelwit" #, fuzzy msgid "Select Power Up Object:" msgstr "Selecteer spreuk doelwit" #, fuzzy msgid "Select Adventure Object:" msgstr "Selecteer spreuk doelwit" msgid "Map Type:\n" msgstr "" msgid "The Succession Wars" msgstr "" msgid "The Price of Loyalty" msgstr "De Prijs van Loyaliteit" msgid "Resurrection" msgstr "" msgid "Lose all your heroes and towns." msgstr "" msgid "Lose a specific town." msgstr "" msgid "Lose a specific hero." msgstr "" msgid "Run out of time. Fail to win by a certain point." msgstr "" msgid "Loss Condition" msgstr "" msgid "Defeat all enemy heroes and towns." msgstr "" msgid "Capture a specific town." msgstr "" msgid "Defeat a specific hero." msgstr "" msgid "Find a specific artifact." msgstr "" msgid "Your side defeats the opposing side." msgstr "" msgid "Accumulate a large amount of gold." msgstr "" msgid "Victory Condition" msgstr "" msgid "Map difficulty:" msgstr "" msgid "N" msgstr "" msgid "No maps exist at that size" msgstr "" msgid "Small Maps" msgstr "" msgid "View only maps of size small (36 x 36)." msgstr "" msgid "Medium Maps" msgstr "" msgid "View only maps of size medium (72 x 72)." msgstr "" msgid "Large Maps" msgstr "" msgid "View only maps of size large (108 x 108)." msgstr "" msgid "Extra Large Maps" msgstr "" msgid "View only maps of size extra large (144 x 144)." msgstr "" msgid "All Maps" msgstr "" msgid "View all maps, regardless of size." msgstr "" msgid "Players Icon" msgstr "" msgid "" "Indicates how many players total are in the scenario. Any positions not " "occupied by humans will be occupied by computer players." msgstr "" msgid "" "Indicates whether the map\n" "is small (36 x 36), medium\n" "(72 x 72), large (108 x 108),\n" "or extra large (144 x 144)." msgstr "" msgid "Size Icon" msgstr "" msgid "" "Indicates whether the map is made for \"The Succession Wars\" or \"The Price " "of Loyalty\" version of the game." msgstr "" msgid "Map Type" msgstr "" msgid "Selected Name" msgstr "" msgid "The name of the currently selected map." msgstr "" msgid "Selected Map Difficulty" msgstr "" msgid "" "The map difficulty of the currently selected map. The map difficulty is " "determined by the scenario designer. More difficult maps might include more " "or stronger enemies, fewer resources, or other special conditions making " "things tougher for the human player." msgstr "" msgid "Selected Description" msgstr "" msgid "The description of the currently selected map." msgstr "" msgid "Jump" msgstr "" msgid "Hero Speed" msgstr "" msgid "Don't Show" msgstr "" msgid "Enemy Speed" msgstr "" msgid "Auto Resolve" msgstr "" msgid "Auto, No Spells" msgstr "" msgid "Battles" msgstr "" msgid "autoBattle|Manual" msgstr "" msgid "Change the speed at which your heroes move on the main screen." msgstr "" msgid "" "Sets the speed that A.I. heroes move at. You can also elect not to view A.I. " "movement at all." msgstr "" msgid "Change the interface settings of the game." msgstr "" msgid "Interface Settings" msgstr "" msgid "Toggle instant battle mode." msgstr "" msgid "Att." msgstr "" msgid "Def." msgstr "" msgid "Power" msgstr "" msgid "Knowl" msgstr "" msgid "1st" msgstr "" msgid "2nd" msgstr "" msgid "3rd" msgstr "" msgid "4th" msgstr "" msgid "5th" msgstr "" msgid "6th" msgstr "" msgid "Oracle: Player Rankings" msgstr "" msgid "Thieves' Guild: Player Rankings" msgstr "" msgid "Number of Towns:" msgstr "" msgid "Number of Castles:" msgstr "" msgid "Number of Heroes:" msgstr "" msgid "Gold in Treasury:" msgstr "" msgid "Wood & Ore:" msgstr "" msgid "Gems, Cr, Slf & Mer:" msgstr "" msgid "Obelisks Found:" msgstr "" msgid "Artifacts:" msgstr "" msgid "Total Army Strength:" msgstr "" msgid "Income:" msgstr "" msgid "Best Hero:" msgstr "" msgid "Best Hero Stats:" msgstr "" msgid "Personality:" msgstr "" msgid "Best Monster:" msgstr "" msgid "Map filename" msgstr "" msgid "" "Are you sure you want to load a new map? (Any unsaved changes to the current " "map will be lost.)" msgstr "" msgid "" "Are you sure you want to create a new map? (Any unsaved changes to the " "current map will be lost.)" msgstr "" msgid "" "Create a new map, either from scratch or using the random map generator." msgstr "" msgid "New Map" msgstr "" msgid "Load Map" msgstr "" msgid "Load an existing map." msgstr "" msgid "Save Map" msgstr "" msgid "Save the current map." msgstr "" msgid "Quit out of the map editor." msgstr "" msgid "Objects cannot be placed outside the map." msgstr "" msgid "Monsters cannot be placed on water." msgstr "" msgid "Choose a tile which does not contain any objects." msgstr "" msgid "Treasures cannot be placed on water." msgstr "" msgid "Heroes cannot be placed on water." msgstr "" msgid "" "A maximum of %{count} heroes of the same color can be placed on the map." msgstr "" msgid "Artifacts cannot be placed on water." msgstr "" msgid "Mountains cannot be placed on water." msgstr "" msgid "Rocks cannot be placed on water." msgstr "" msgid "Trees cannot be placed on water." msgstr "" msgid "Ocean object must be placed on water." msgstr "" msgid "Landscape objects cannot be placed on water." msgstr "" msgid "Towns cannot be placed on water." msgstr "" msgid "Mines cannot be placed on water." msgstr "" msgid "Dwellings cannot be placed on water." msgstr "" msgid "Power-ups cannot be placed on water." msgstr "" msgid "Adventure objects cannot be placed on water." msgstr "" msgid "Used to place %{object}." msgstr "" #, fuzzy msgid "Select object type" msgstr "Selecteer spreuk doelwit" msgid "" "Click here to\n" "select a monster." msgstr "" #, fuzzy msgid "" "Click here to\n" "select another monster." msgstr "Selecteer een andere held." msgid "" "Cell\n" "Details" msgstr "" msgid "Roads" msgstr "" msgid "Streams" msgstr "" msgid "Erase" msgstr "" msgid "Mountains" msgstr "" msgid "Rocks" msgstr "" msgid "Trees" msgstr "" msgid "Water Objects" msgstr "" msgid "Miscellaneous" msgstr "" msgid "Artifacts" msgstr "" msgid "Dwellings" msgstr "" msgid "Mines" msgstr "" #, fuzzy msgid "Power-ups" msgstr "Einde van de macht" msgid "Treasures" msgstr "" msgid "Towns" msgstr "" msgid "Heroes" msgstr "" msgid "editorErasure|Terrain" msgstr "" msgid "editorErasure|Castles" msgstr "" msgid "editorErasure|Monsters" msgstr "" msgid "editorErasure|Heroes" msgstr "" msgid "editorErasure|Artifacts" msgstr "" msgid "editorErasure|Roads" msgstr "" msgid "editorErasure|Streams" msgstr "" msgid "editorErasure|Treasures" msgstr "" msgid "" "Draws terrain in\n" "%{size} by %{size} square increments." msgstr "" msgid "" "Erases objects in\n" "%{size} by %{size} square increments." msgstr "" msgid "Small Brush" msgstr "" msgid "Medium Brush" msgstr "" msgid "Large Brush" msgstr "" msgid "Area Fill" msgstr "" msgid "Used to click and drag for filling in large areas." msgstr "" msgid "Clear Area" msgstr "" msgid "Used to click and drag for clearing large areas." msgstr "" msgid "" "Costs %{rate} times normal movement for all heroes. (Pathfinding reduces or " "eliminates the penalty.)" msgstr "" msgid "Traversable only by boat." msgstr "" msgid "No special modifiers." msgstr "" msgid "Toggle the erasure of %{type} objects." msgstr "" msgid "" "Objects of this type will be deleted with the Erase tool. Left-click here to " "deselect this type. Press and hold this button to deselect all other object " "types." msgstr "" msgid "" "Objects of this type will NOT be deleted with the Erase tool. Left-click " "here to select this type. Press and hold this button to select all other " "object types." msgstr "" msgid "Terrain Mode" msgstr "" msgid "Used to draw the underlying grass, dirt, water, etc. on the map." msgstr "" msgid "Landscape Objects Mode" msgstr "" msgid "" "Used to place landscape objects (mountains, rocks, trees, etc.) on the map." msgstr "" msgid "Detail Mode" msgstr "" msgid "Used for special editing of monsters, heroes and towns." msgstr "" msgid "Adventure Objects Mode" msgstr "" msgid "" "Used to place adventure objects (artifacts, dwellings, mines, treasures, " "etc.) on the map." msgstr "" msgid "Kingdom Objects Mode" msgstr "" msgid "Used to place kingdom objects (towns, castles and heroes) on the map." msgstr "" #, fuzzy msgid "Monsters Mode" msgstr "Monsters" msgid "Used to place monsters on the map." msgstr "" msgid "Allows you to draw streams by clicking and dragging." msgstr "" msgid "Stream Mode" msgstr "" msgid "Allows you to draw roads by clicking and dragging." msgstr "" msgid "Road Mode" msgstr "" msgid "Erase Mode" msgstr "" msgid "Used to erase objects from the map." msgstr "" msgid "Change between zoom and normal view." msgstr "" msgid "Magnify" msgstr "" msgid "Undo" msgstr "" msgid "Undo your last action." msgstr "" msgid "Create a new map either from scratch or using the random map generator." msgstr "" msgid "Edit map title, description, and other general information." msgstr "" msgid "Specifications" msgstr "" msgid "File Options" msgstr "" msgid "" "Open the file options menu, where you can save or load maps, or quit out of " "the editor." msgstr "" msgid "View the editor system options, which let you customize the editor." msgstr "" msgid "Create a map that is %{size} squares wide and %{size} squares high." msgstr "" msgid "Cancel back to the New Map menu." msgstr "" msgid "Cancel back to the main menu." msgstr "" msgid "From Scratch" msgstr "" msgid "Start from scratch with a blank map." msgstr "" msgid "Create a randomly generated map." msgstr "" msgid "Random" msgstr "" msgid "Cancel back to the Map Editor main menu." msgstr "" msgid "No maps available!" msgstr "" msgid "Warning" msgstr "Waarschuuwing" msgid "difficulty|Easy" msgstr "" msgid "difficulty|Normal" msgstr "" msgid "difficulty|Hard" msgstr "" msgid "difficulty|Expert" msgstr "" msgid "difficulty|Impossible" msgstr "" msgid "and more..." msgstr "" msgid "Easy" msgstr "" msgid "Hard" msgstr "" msgid "Campaign Difficulty" msgstr "" msgid "" "Choose this difficulty if you want to prefer game story over challenge. AI " "is weaker in comparison with normal difficulty." msgstr "" msgid "" "Choose this difficulty to enjoy the campaign as per the original design." msgstr "" msgid "" "Choose this difficulty if you want challenge. AI is stronger in comparison " "with normal difficulty." msgstr "" msgid "" "Congratulations!\n" "\n" "Days: %{days}\n" msgstr "" msgid "" "\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" msgid "" "Score: %{score}\n" "\n" "Rating:\n" "%{rating}" msgstr "" msgid "Start the selected scenario." msgstr "" msgid "View Intro" msgstr "" msgid "View Intro videos for the current state of the campaign." msgstr "" msgid "" "Select the campaign difficulty. This can be lowered at any point during the " "campaign." msgstr "" msgid "Restart the current scenario." msgstr "" msgid "Difficulty" msgstr "" msgid "" "You have changed to a lower difficulty for the campaign. You will not be " "able to revert this after this point. The high score will be calculated " "based solely on the new difficulty. Do you want to proceed?" msgstr "" msgid "Are you sure you want to restart this scenario?" msgstr "" msgid "Campaign Scenario loading failure" msgstr "" msgid "Please make sure that campaign files are correct and present." msgstr "" msgid "Days spent" msgstr "" msgid "The number of days spent on this campaign." msgstr "" msgid "Project Coordination and Core Development" msgstr "" msgid "Development" msgstr "" msgid "Visit us at " msgstr "" msgid "QA and Support" msgstr "" msgid "Dev and Support" msgstr "" msgid "Special Thanks to" msgstr "" msgid "and many other contributors!" msgstr "" msgid "and many-many other supporters!" msgstr "" msgid "Support us at" msgstr "" msgid "local-donation-platform|https://www.patreon.com/fheroes2" msgstr "" msgid "Connect with us at" msgstr "" msgid "local-social-network|https://www.facebook.com/groups/fheroes2" msgstr "" msgid "Need help with the game?" msgstr "" msgid "Original project before 0.7" msgstr "" msgid "Heroes of Might and Magic II: The Succession Wars team" msgstr "" msgid "Designed and Directed" msgstr "" msgid "Programming and Design" msgstr "" msgid "Executive Producer" msgstr "" msgid "Producer" msgstr "" msgid "Additional Design" msgstr "" msgid "Additional Programming" msgstr "" msgid "Musical Production" msgstr "" msgid "Music and Sound Design" msgstr "" msgid "Vocalists" msgstr "" msgid "Art Director" msgstr "" msgid "Assistant Art Director" msgstr "" msgid "Artists" msgstr "" msgid "QA Manager" msgstr "" msgid "QA" msgstr "" msgid "Writing" msgstr "" msgid "Manual and Helpfile" msgstr "" msgid "Scenarios" msgstr "" msgid "Heroes of Might and Magic II: The Price of Loyalty team" msgstr "" msgid "Design Lead" msgstr "" msgid "Designers" msgstr "" msgid "Programming Lead" msgstr "" msgid "Art Lead" msgstr "" msgid "Playtesters" msgstr "" msgid "Designer" msgstr "" msgid "Producers" msgstr "" msgid "QA Managers" msgstr "" msgid "Sound Design" msgstr "" msgid "Town Themes" msgstr "" msgid "Alto Sax" msgstr "" msgid "Harpsichord and Piano" msgstr "" msgid "Basso Vocal" msgstr "" msgid "Soprano Vocal" msgstr "" msgid "Recorded at %{recordingStudio}" msgstr "" msgid "credits|Manual" msgstr "" msgid "German Consultant" msgstr "" msgid "Map Designers" msgstr "" msgid "Package Design" msgstr "" msgid "Your Name" msgstr "" msgid "Unknown Hero" msgstr "" msgid "Standard" msgstr "" msgid "View High Scores for Standard Maps." msgstr "" msgid "Campaign" msgstr "" msgid "View High Scores for Campaigns." msgstr "" msgid "hotkey|default okay event" msgstr "" msgid "hotkey|default cancel event" msgstr "" msgid "hotkey|default left" msgstr "" msgid "hotkey|default right" msgstr "" msgid "hotkey|default up" msgstr "" msgid "hotkey|default down" msgstr "" msgid "hotkey|toggle fullscreen" msgstr "" msgid "hotkey|toggle text support mode" msgstr "" msgid "hotkey|new game" msgstr "" msgid "hotkey|load game" msgstr "" msgid "hotkey|highscores" msgstr "" msgid "hotkey|credits" msgstr "" msgid "hotkey|standard game" msgstr "" msgid "hotkey|campaign game" msgstr "" msgid "hotkey|multi-player game" msgstr "" msgid "hotkey|settings" msgstr "" msgid "hotkey|quit" msgstr "" msgid "hotkey|select map" msgstr "" msgid "hotkey|select small map size" msgstr "" msgid "hotkey|select medium map size" msgstr "" msgid "hotkey|select large map size" msgstr "" msgid "hotkey|select extra large map size" msgstr "" msgid "hotkey|select all map sizes" msgstr "" msgid "hotkey|hotseat game" msgstr "" msgid "hotkey|battle only game" msgstr "" msgid "hotkey|choose the original campaign" msgstr "" msgid "hotkey|choose the expansion campaign" msgstr "" msgid "hotkey|map editor main menu" msgstr "" msgid "hotkey|new map menu" msgstr "" msgid "hotkey|load map menu" msgstr "" msgid "hotkey|new map from scratch" msgstr "" msgid "hotkey|new random map" msgstr "" #, fuzzy msgid "hotkey|undo last action" msgstr "Systeem instellingen aanpassen" #, fuzzy msgid "hotkey|redo last action" msgstr "Systeem instellingen aanpassen" msgid "hotkey|roland campaign" msgstr "" msgid "hotkey|archibald campaign" msgstr "" #, fuzzy msgid "hotkey|the price of loyalty campaign" msgstr "De Prijs van Loyaliteit" msgid "hotkey|voyage home campaign" msgstr "" msgid "hotkey|wizard's isle campaign" msgstr "" msgid "hotkey|descendants campaign" msgstr "" msgid "hotkey|select first campaign bonus" msgstr "" msgid "hotkey|select second campaign bonus" msgstr "" msgid "hotkey|select third campaign bonus" msgstr "" msgid "hotkey|view campaign intro" msgstr "" msgid "hotkey|select campaign difficulty" msgstr "" msgid "hotkey|restart campaign scenario" msgstr "" msgid "hotkey|world map left" msgstr "" msgid "hotkey|world map right" msgstr "" msgid "hotkey|world map up" msgstr "" msgid "hotkey|world map down" msgstr "" msgid "hotkey|world map up left" msgstr "" msgid "hotkey|world map up right" msgstr "" msgid "hotkey|world map down left" msgstr "" msgid "hotkey|world map down right" msgstr "" msgid "hotkey|save game" msgstr "" msgid "hotkey|next hero" msgstr "" msgid "hotkey|start hero movement" msgstr "" msgid "hotkey|cast adventure spell" msgstr "" msgid "hotkey|put hero to sleep" msgstr "" msgid "hotkey|next town" msgstr "" msgid "hotkey|end turn" msgstr "" msgid "hotkey|file options" msgstr "" msgid "hotkey|adventure options" msgstr "" msgid "hotkey|puzzle map" msgstr "" msgid "hotkey|scenario information" msgstr "" msgid "hotkey|dig for artifact" msgstr "" msgid "hotkey|view world" msgstr "" msgid "hotkey|kingdom summary" msgstr "" msgid "hotkey|default action" msgstr "" msgid "hotkey|open focus" msgstr "" #, fuzzy msgid "hotkey|system options" msgstr "Systeem instellingen aanpassen" msgid "hotkey|scroll left" msgstr "" msgid "hotkey|scroll right" msgstr "" msgid "hotkey|scroll up" msgstr "" msgid "hotkey|scroll down" msgstr "" msgid "hotkey|toggle control panel" msgstr "" msgid "hotkey|toggle radar" msgstr "" msgid "hotkey|toggle buttons" msgstr "" msgid "hotkey|toggle status" msgstr "" msgid "hotkey|toggle icons" msgstr "" msgid "hotkey|transfer control to ai" msgstr "" msgid "hotkey|retreat from battle" msgstr "" msgid "hotkey|surrender during battle" msgstr "" msgid "hotkey|toggle battle auto mode" msgstr "" #, fuzzy msgid "hotkey|finish the battle in auto mode" msgstr "" "Weet je zeker dat je het gevecht in de automatische modus wilt beëindigen?" msgid "hotkey|battle options" msgstr "" msgid "hotkey|skip turn in battle" msgstr "" msgid "hotkey|cast battle spell" msgstr "" msgid "hotkey|dwelling level 1" msgstr "" msgid "hotkey|dwelling level 2" msgstr "" msgid "hotkey|dwelling level 3" msgstr "" msgid "hotkey|dwelling level 4" msgstr "" msgid "hotkey|dwelling level 5" msgstr "" msgid "hotkey|dwelling level 6" msgstr "" msgid "hotkey|well" msgstr "" msgid "hotkey|marketplace" msgstr "" msgid "hotkey|mage guild" msgstr "" msgid "hotkey|shipyard" msgstr "" msgid "hotkey|thieves guild" msgstr "" msgid "hotkey|tavern" msgstr "" msgid "hotkey|construction screen" msgstr "" msgid "hotkey|buy all monsters in well" msgstr "" msgid "hotkey|split stack by half" msgstr "" msgid "hotkey|split stack by one" msgstr "" msgid "hotkey|join stacks" msgstr "" msgid "hotkey|upgrade troop" msgstr "" msgid "hotkey|dismiss hero or troop" msgstr "" msgid "" "Do you want to regain control from AI? The effect will take place only on " "the next turn." msgstr "" "Wil je controle terug nemen van de AI? Het effect neemt alleen plaats\n" "de volgende ronde." msgid "" "Do you want to transfer control from you to the AI? The effect will take " "place only on the next turn." msgstr "" msgid "Default Actions" msgstr "" msgid "Global Actions" msgstr "" msgid "Main Menu" msgstr "" msgid "World Map" msgstr "" #, fuzzy msgid "Battle Screen" msgstr "Held Scherm" #, fuzzy msgid "Town Screen" msgstr "Held Scherm" msgid "Army Actions" msgstr "" msgid "Editor" msgstr "" #, fuzzy msgid "The save file is corrupted." msgstr "Het spiegelbeeld wordt gecreëerd." msgid "Unsupported save format: " msgstr "" msgid "Current game version: " msgstr "" msgid "Last supported version: " msgstr "" msgid "This file contains a save with an invalid game type." msgstr "" msgid "" "This save file requires \"The Price of Loyalty\" game assets, but they have " "not been provided to the engine." msgstr "" msgid "This saved game is localized to '" msgstr "" msgid "' language, but the current language of the game is '" msgstr "" msgid "Hot Seat" msgstr "" msgid "" "Play a Hot Seat game, where 2 to 4 players play around the same computer, " "switching into the 'Hot Seat' when it is their turn." msgstr "" msgid "A single player game playing out a single map." msgstr "" msgid "Standard Game" msgstr "" msgid "A single player game playing through a series of maps." msgstr "" msgid "Campaign Game" msgstr "" msgid "Multi-Player Game" msgstr "" msgid "" "A multi-player game, with several human players completing against each " "other on a single map." msgstr "" msgid "fheroes2 Resurrection Team presents" msgstr "" msgid "Greetings!" msgstr "" msgid "Welcome to Heroes of Might and Magic II powered by fheroes2 engine!" msgstr "" msgid "" "Welcome to Heroes of Might and Magic II powered by the fheroes2 engine!\n" "Before starting the game please select a game resolution." msgstr "" msgid "Please Remember" msgstr "" msgid "" "You can always change the language, resolution and settings of the game by " "clicking on the " msgstr "" msgid "door" msgstr "" msgid " on the left side of the Main Menu, or with the " msgstr "" msgid " button from the " msgstr "" msgid "NEW GAME" msgstr "" msgid "" " menu. \n" "\n" "Enjoy the game!" msgstr "" msgid "The Editor requires \"The Price of Loyalty\" expansion files to work." msgstr "" msgid "Quit Heroes of Might and Magic II and return to the operating system." msgstr "" msgid "Credits" msgstr "" msgid "View the credits screen." msgstr "" msgid "High Scores" msgstr "" msgid "View the high scores screen." msgstr "" msgid "Change language, resolution and settings of the game." msgstr "" msgid "Game Settings" msgstr "" msgid "" "Required video files for campaign selection window are missing. Please make " "sure that all necessary files are present in the system." msgstr "" msgid "" "Either Roland's or Archibald's campaign from the original Heroes of Might " "and Magic II." msgstr "" msgid "Original Campaign" msgstr "" msgid "Expansion Campaign" msgstr "" msgid "One of the four new campaigns from the Price of Loyalty expansion set." msgstr "" msgid "Loading video. Please wait..." msgstr "" msgid "Host" msgstr "" msgid "" "The host sets up the game options. There can only be one host per network " "game." msgstr "" msgid "Guest" msgstr "" msgid "" "The guest waits for the host to set up the game, then is automatically added " "in. There can be multiple guests for TCP/IP games." msgstr "" msgid "Battle Only" msgstr "" msgid "Setup and play a battle without loading any map." msgstr "" msgid "2 Players" msgstr "" msgid "" "Play with 2 human players, and optionally, up to 4 additional computer " "players." msgstr "" msgid "3 Players" msgstr "" msgid "" "Play with 3 human players, and optionally, up to 3 additional computer " "players." msgstr "" msgid "4 Players" msgstr "" msgid "" "Play with 4 human players, and optionally, up to 2 additional computer " "players." msgstr "" msgid "5 Players" msgstr "" msgid "" "Play with 5 human players, and optionally, up to 1 additional computer " "player." msgstr "" msgid "6 Players" msgstr "" msgid "Play with 6 human players." msgstr "" msgid "Dragon city has fallen! You are now the Master of the Dragons." msgstr "" msgid "" "You captured %{name}!\n" "You are victorious." msgstr "" msgid "" "You have captured the enemy hero %{name}!\n" "Your quest is complete." msgstr "" msgid "" "You have found the %{name}.\n" "Your quest is complete." msgstr "" msgid "Ultimate Artifact" msgstr "" msgid "" "The enemy is beaten.\n" "Your side has triumphed!" msgstr "" msgid "" "You have built up over %{count} gold in your treasury.\n" "All enemies bow before your wealth and power." msgstr "" msgid "Victory!" msgstr "" msgid "" "The enemy has captured %{name}!\n" "They are triumphant." msgstr "" msgid "" "The enemy has built up over %{count} gold in his treasury.\n" "You must bow done in defeat before his wealth and power." msgstr "" msgid "You have been eliminated from the game!!!" msgstr "" msgid "" "You have lost the hero %{name}.\n" "Your quest is over." msgstr "" msgid "" "You have failed to complete your quest in time.\n" "All is lost." msgstr "" #, fuzzy msgid "Defeat!" msgstr " verslagen" msgid "" "Base score: %{score}\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" msgid "Defeat all enemy heroes and capture all enemy towns and castles." msgstr "" msgid "Run out of time. (Fail to win by a certain point.)" msgstr "" msgid "You must defeat the enemy %{enemies}." msgid_plural "You must defeat the enemy alliance of %{enemies}." msgstr[0] "" msgstr[1] "" msgid "" "The alliance consisting of %{allies} and you must defeat the enemy " "%{enemies}." msgid_plural "" "The alliance consisting of %{allies} and you must defeat the enemy alliance " "of %{enemies}." msgstr[0] "" msgstr[1] "" msgid "Capture the castle '%{name}'." msgstr "" msgid "Capture the town '%{name}'." msgstr "" msgid "Defeat the hero '%{name}'." msgstr "" msgid "Find the ultimate artifact." msgstr "" msgid "Find the '%{name}' artifact." msgstr "" msgid "Accumulate %{count} gold." msgstr "" msgid "" ", or you may win by defeating all enemy heroes and capturing all enemy towns " "and castles." msgstr "" msgid "Lose the castle '%{name}'." msgstr "" msgid "Lose the town '%{name}'." msgstr "" msgid "Lose the hero: %{name}." msgstr "" msgid "Fail to win by the end of month %{month}, week %{week}, day %{day}." msgstr "" msgid "%{color} player has been vanquished!" msgstr "" msgid "Major Event!" msgstr "" msgid "Scenario:" msgstr "" msgid "Game Difficulty:" msgstr "" msgid "Opponents:" msgstr "" msgid "Class:" msgstr "" msgid "Rating %{rating}%" msgstr "" msgid "Click here to select which scenario to play." msgstr "" msgid "Scenario" msgstr "" msgid "Game Difficulty" msgstr "" msgid "" "This lets you change the starting difficulty at which you will play. Higher " "difficulty levels start you of with fewer resources, and at the higher " "settings, give extra resources to the computer." msgstr "" msgid "Difficulty Rating" msgstr "" msgid "" "The difficulty rating reflects a combination of various settings for your " "game. This number will be applied to your final score." msgstr "" msgid "Click to accept these settings and start a new game." msgstr "" msgid "Click to return to the main menu." msgstr "" msgid "Astrologers proclaim the Month of the %{name}." msgstr "" msgid "Astrologers proclaim the Week of the %{name}." msgstr "" msgid "After regular growth, the population of %{monster} is doubled!" msgstr "" msgid "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgid_plural "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgstr[0] "" msgstr[1] "" msgid "%{monster} growth +%{count}." msgstr "" msgid " All populations are halved." msgstr "" msgid " All dwellings increase population." msgstr "" msgid "New Month!" msgstr "" msgid "New Week!" msgstr "" msgid "Beware!" msgstr "" msgid "" "%{color} player, this is your last day to capture a town, or you will be " "banished from this land." msgstr "" msgid "" "%{color} player, you only have %{day} days left to capture a town, or you " "will be banished from this land." msgstr "" msgid "%{color} player's turn." msgstr "" msgid "" "%{color} player, you have lost your last town. If you do not conquer another " "town in the next week, you will be eliminated." msgstr "" msgid "" "%{color} player, your heroes abandon you, and you are banished from this " "land." msgstr "" msgid "Lord Kilburn" msgstr "" msgid "Tsabu" msgstr "" msgid "Sir Galant" msgstr "" msgid "Thundax" msgstr "" msgid "Lord Haart" msgstr "" msgid "Ariel" msgstr "" msgid "Rebecca" msgstr "" msgid "Sandro" msgstr "" msgid "Crodo" msgstr "" msgid "Barock" msgstr "" msgid "Antoine" msgstr "" msgid "Astra" msgstr "" msgid "Agar" msgstr "" msgid "Vatawna" msgstr "" msgid "Vesper" msgstr "" msgid "Ambrose" msgstr "" msgid "Troyan" msgstr "" msgid "Jojosh" msgstr "" msgid "Wrathmont" msgstr "" msgid "Maximus" msgstr "" msgid "Next Hero" msgstr "" msgid "Select the next Hero." msgstr "" #, fuzzy msgid "Hero Movement" msgstr "Schaduw Beweging" msgid "" "Start the Hero's movement along the current path or re-visit the object " "occupied by the Hero. Press and hold this button to reset the Hero's path." msgstr "" msgid "Kingdom Summary" msgstr "" msgid "View a Summary of your Kingdom." msgstr "" msgid "Cast an adventure spell." msgstr "" msgid "End Turn" msgstr "" msgid "End your turn and left the computer take its turn." msgstr "" msgid "Adventure Options" msgstr "" msgid "Bring up the adventure options menu." msgstr "" msgid "" "Bring up the file options menu, allowing you to load, save, start a new game " "or quit." msgstr "" msgid "Bring up the system options menu, allowing you to customize your game." msgstr "" msgid "" "One or more heroes may still move, are you sure you want to end your turn?" msgstr "" msgid "Are you sure you want to quit?" msgstr "" msgid "Are you sure you want to restart? (Your current game will be lost.)" msgstr "" msgid "Are you sure you want to overwrite the save with this name?" msgstr "" msgid "Game saved successfully." msgstr "" msgid "There was an issue during saving." msgstr "" msgid "" "Are you sure you want to load a new game? (Your current game will be lost.)" msgstr "" msgid "Try looking on land!!!" msgstr "" msgid "" "Searching for the Ultimate Artifact is fruitless. Your hero could not carry " "it even if he found it - all his artifact slots are full." msgstr "" msgid "Digging for artifacts requires a whole day, try again tomorrow." msgstr "" msgid "" "After spending many hours digging here, you have uncovered the %{artifact}." msgstr "" msgid "Congratulations!" msgstr "" msgid "Nothing here. Where could it be?" msgstr "" msgid "Try searching on clear ground." msgstr "" msgid "A miniature view of the known world. Left click to move viewing area." msgstr "" msgid "Month: %{month} Week: %{week}" msgstr "" msgid "Day: %{day}" msgstr "" msgid "" "You find a small\n" "quantity of %{resource}." msgstr "" msgid "Status Window" msgstr "" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date." msgstr "" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date. Left click here to cycle through these windows." msgstr "" msgid "" "This lets you change player starting positions and colors. A particular " "color will always start in a particular location. Some positions may only be " "played by a computer player or only by a human player." msgstr "" msgid "" "This lets you change the class of a player. Classes are not always " "changeable. Depending on the scenario, a player may receive additional towns " "and/or heroes not of their primary alignment." msgstr "" msgid "Handicap" msgstr "" # msgid "" "This lets you change the handicap of a particular player. Only humans may be " "handicapped. Handicapped players start with fewer resources and earn 15 or " "30% fewer resources per turn for mild and severe handicaps, respectively." msgstr "" msgid "%{color} player" msgstr "" msgid "No Handicap" msgstr "" msgid "No special restrictions on start resources and earning them per turn." msgstr "" msgid "Mild Handicap" msgstr "" # msgid "" "Mild handicapped players start with fewer resources and earn 15% fewer " "resources per turn." msgstr "" msgid "Severe Handicap" msgstr "" # msgid "" "Severe handicapped players start with fewer resources and earn 30% fewer " "resources per turn." msgstr "" msgid "View %{skill} Info" msgstr "" msgid "Keyboard|123" msgstr "" msgid "Keyboard|SPACE" msgstr "" msgid "Keyboard|ABC" msgstr "" msgid "Kingdom Income" msgstr "" msgid "Kingdom Income per day." msgstr "" msgid "For every lighthouse controlled, your ships will move further each day." msgstr "" msgid "English" msgstr "" msgid "French" msgstr "" msgid "Polish" msgstr "" msgid "German" msgstr "" msgid "Russian" msgstr "" msgid "Italian" msgstr "" msgid "Czech" msgstr "" msgid "Norwegian" msgstr "" msgid "Belarusian" msgstr "" msgid "Bulgarian" msgstr "" msgid "Ukrainian" msgstr "" msgid "Romanian" msgstr "" msgid "Spanish" msgstr "" msgid "Swedish" msgstr "" msgid "Portuguese" msgstr "" msgid "Turkish" msgstr "" msgid "Dutch" msgstr "Nederlands" msgid "Hungarian" msgstr "" msgid "Danish" msgstr "" msgid "Slovak" msgstr "" #, fuzzy msgid "Vietnamese" msgstr "Zie %{name}" msgid ", FPS: " msgstr "" msgid "%{object} robber" msgstr "" msgid "%{object} raided" msgstr "" msgid "Ector" msgstr "" msgid "Gwenneth" msgstr "" msgid "Sir Gallant" msgstr "" msgid "Tyro" msgstr "" msgid "Dimitry" msgstr "" msgid "Ruby" msgstr "" msgid "Crag Hack" msgstr "" msgid "Fineous" msgstr "" msgid "Jezebel" msgstr "" msgid "Atlas" msgstr "" msgid "Ergon" msgstr "" msgid "Jaclyn" msgstr "" msgid "Gem" msgstr "" msgid "Natasha" msgstr "" msgid "Carlawn" msgstr "" msgid "Luna" msgstr "" msgid "Arie" msgstr "" msgid "Barok" msgstr "" msgid "Kastore" msgstr "" msgid "Falagar" msgstr "" msgid "Dawn" msgstr "" msgid "Flint" msgstr "" msgid "Halon" msgstr "" msgid "Myra" msgstr "" msgid "Myrini" msgstr "" msgid "Wilfrey" msgstr "" msgid "Mandigal" msgstr "" msgid "Sarakin" msgstr "" msgid "Charity" msgstr "" msgid "Darlana" msgstr "" msgid "Ranloo" msgstr "" msgid "Rialdo" msgstr "" msgid "Zam" msgstr "" msgid "Zom" msgstr "" msgid "Celia" msgstr "" msgid "Roxana" msgstr "" msgid "Lord Corlagon" msgstr "" msgid "Lord Halton" msgstr "" msgid "Sister Eliza" msgstr "" msgid "Brother Brax" msgstr "" msgid "Dainwin" msgstr "" msgid "Joseph" msgstr "" msgid "Mog" msgstr "" msgid "Solmyr" msgstr "" msgid "Ceallach" msgstr "" msgid "Drakonia" msgstr "" msgid "Elderian" msgstr "" msgid "Gallavant" msgstr "" msgid "Jarkonas" msgstr "" msgid "Martine" msgstr "" msgid " gives you maximum morale" msgstr "" msgid " gives you maximum luck" msgstr "" msgid "You cannot pick up this artifact, you already have a full load!" msgstr "" msgid "To cast spells, you must first buy a spell book for %{gold} gold." msgstr "" msgid "Unfortunately, you seem to be a little short of cash at the moment." msgstr "" msgid "Do you wish to buy one?" msgstr "" msgid "%{count} / day" msgstr "" msgid "one" msgstr "" msgid "two" msgstr "" msgid "A whirlpool engulfs your ship. Some of your army has fallen overboard." msgstr "" msgid "Insulted by your refusal of their offer, the monsters attack!" msgstr "" msgid "" "The %{monster}, awed by the power of your forces, begin to scatter.\n" "Do you wish to pursue and engage them?" msgstr "" msgid "Ransacking an enemy camp, you discover a hidden cache of treasures." msgstr "" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with these resources, " "come back next week for more.\"" msgstr "" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there are no resources currently available. Please try " "again next week.\"" msgstr "" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with this gold, come " "back next week for more.\"" msgstr "" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there is no gold currently available. Please try again " "next week.\"" msgstr "" msgid "" "You've found an abandoned lean-to.\n" "Poking about, you discover some resources hidden nearby." msgstr "" msgid "The lean-to is long abandoned. There is nothing of value here." msgstr "" msgid "" "You catch a leprechaun foolishly sleeping amidst a cluster of magic " "mushrooms.\n" "In exchange for his freedom, he guides you to a small pot filled with " "precious things." msgstr "" msgid "" "You've found a magic garden, the kind of place that leprechauns and faeries " "like to cavort in, but there is no one here today.\n" "Perhaps you should try again next week." msgstr "" msgid "You come upon the remains of an unfortunate adventurer." msgstr "" msgid "Treasure" msgstr "" msgid "Searching through the tattered clothing, you find the %{artifact}." msgstr "" msgid "Searching through the tattered clothing, you find nothing." msgstr "" msgid "" "You come across an old wagon left by a trader who didn't quite make it to " "safe terrain." msgstr "" msgid "Unfortunately, others have found it first, and the wagon is empty." msgstr "" msgid "Searching inside, you find the %{artifact}." msgstr "" msgid "Inside, you find some of the wagon's cargo still intact." msgstr "" msgid "You search through the flotsam, and find some wood and some gold." msgstr "" msgid "You search through the flotsam, and find some wood." msgstr "" msgid "You search through the flotsam, but find nothing." msgstr "" msgid "Shrine of the 1st Circle" msgstr "" msgid "" "You come across a small shrine attended by a group of novice acolytes.\n" "In exchange for your protection, they agree to teach you a simple spell - " "'%{spell}'." msgstr "" msgid "Shrine of the 2nd Circle" msgstr "" msgid "" "You come across an ornate shrine attended by a group of rotund friars.\n" "In exchange for your protection, they agree to teach you a spell - " "'%{spell}'." msgstr "" msgid "Shrine of the 3rd Circle" msgstr "" msgid "" "You come across a lavish shrine attended by a group of high priests.\n" "In exchange for your protection, they agree to teach you a sophisticated " "spell - '%{spell}'." msgstr "" msgid "" "\n" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" msgid "" "\n" "Unfortunately, you already have knowledge of this spell, so there is nothing " "more for them to teach you." msgstr "" msgid "" "\n" "Unfortunately, you have no Magic Book to record the spell with." msgstr "" msgid "" "You approach the hut and observe a witch inside studying an ancient tome on " "%{skill}.\n" "\n" msgstr "" msgid "" "As you approach, she turns and focuses her one glass eye on you.\n" "\"You already know everything you deserve to learn!\" the witch screeches. " "\"NOW GET OUT OF MY HOUSE!\"" msgstr "" msgid "" "As you approach, she turns and speaks.\n" "\"You already know that which I would teach you. I can help you no further.\"" msgstr "" msgid "" "An ancient and immortal witch living in a hut with bird's legs for stilts " "teaches you %{skill} for her own inscrutable purposes." msgstr "" msgid "As you drink the sweet water, you gain luck for your next battle." msgstr "" msgid "You drink from the enchanted fountain, but nothing happens." msgstr "" msgid "You enter the faerie ring, but nothing happens." msgstr "" msgid "" "Upon entering the mystical faerie ring, your army gains luck for its next " "battle." msgstr "" msgid "" "You've found an ancient and weathered stone idol.\n" "It is supposed to grant luck to visitors, but since the stars are already " "smiling upon you, it does nothing." msgstr "" msgid "" "You've found an ancient and weathered stone idol.\n" "Kissing it is supposed to be lucky, so you do. The stone is very cold to the " "touch." msgstr "" msgid "The mermaids silently entice you to return later and be blessed again." msgstr "" msgid "" "The magical, soothing beauty of the Mermaids reaches you and your crew.\n" "Just for a moment, you forget your worries and bask in the beauty of the " "moment.\n" "The mermaids charms bless you with increased luck for your next combat." msgstr "" msgid "" "You come upon the pyramid of a great and ancient king.\n" "You are tempted to search it for treasure, but all the old stories warn of " "fearful curses and undead guardians.\n" "Will you search?" msgstr "" msgid "" "Upon defeating the monsters, you decipher an ancient glyph on the wall, " "telling the secret of the spell - '" msgstr "" msgid "Unfortunately, you have no Magic Book to record the spell with." msgstr "" msgid "" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" msgid "" "You come upon the pyramid of a great and ancient king.\n" "Routine exploration reveals that the pyramid is completely empty." msgstr "" msgid "" "A drink at the well is supposed to restore your spell points, but you are " "already at maximum." msgstr "" msgid "A second drink at the well in one day will not help you." msgstr "" msgid "A drink from the well has restored your spell points to maximum." msgstr "" msgid "" "\"I'm sorry sir,\" The leader of the soldiers says, \"but you already know " "everything we have to teach.\"" msgstr "" msgid "The soldiers living in the fort teach you a few new defensive tricks." msgstr "" msgid "" "You've come upon a mercenary camp practicing their tactics. \"You're too " "advanced for us,\" the mercenary captain says. \"We can teach nothing more.\"" msgstr "" msgid "" "You've come upon a mercenary camp practicing their tactics. The mercenaries " "welcome you and your troops and invite you to train with them." msgstr "" msgid "\"Go 'way!\", the witch doctor barks, \"you know all I know.\"" msgstr "" msgid "" "An Orcish witch doctor living in the hut deepens your knowledge of magic by " "showing you how to cast stones, read portents, and decipher the intricacies " "of chicken entrails." msgstr "" msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, the Druids turn you away, indicating they have nothing " "new to teach you." msgstr "" msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, they teach you new ways to cast spells." msgstr "" msgid "" "You tentatively approach the burial ground of ancient warriors. Do you want " "to search the graves?" msgstr "" msgid "" "You spend several hours searching the graves and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" msgid "Upon defeating the Zombies you search the graves and find something!" msgstr "" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the shipwreck?" msgstr "" msgid "" "You spend several hours sifting through the debris and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" msgid "" "Upon defeating the Ghosts you sift through the debris and find something!" msgstr "" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the ship?" msgstr "" msgid "" "Upon defeating the Skeletons you sift through the debris and find something!" msgstr "" msgid "Your men spot a navigational buoy, confirming that you are on course." msgstr "" msgid "" "Your men spot a navigational buoy, confirming that you are on course and " "increasing their morale." msgstr "" msgid "" "The drink at the oasis is refreshing, but offers no further benefit. The " "oasis might help again if you fought a battle first." msgstr "" msgid "" "A drink at the oasis fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" msgid "" "The drink at the watering hole is refreshing, but offers no further benefit. " "The watering hole might help again if you fought a battle first." msgstr "" msgid "" "A drink at the watering hole fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" msgid "" "It doesn't help to pray twice before a battle. Come back after you've fought." msgstr "" msgid "A visit and a prayer at the temple raises the morale of your troops." msgstr "" msgid "" "An old Knight appears on the steps of the gazebo. \"I am sorry, my liege, I " "have taught you all I can.\"" msgstr "" msgid "" "An old Knight appears on the steps of the gazebo. \"My liege, I will teach " "you all that I know to aid you in your travels.\"" msgstr "" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he says, \"I would give you an artifact as a reward, but " "you're all full.\"" msgstr "" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he rewards you for your act of kindness by giving you the " "%{art}." msgstr "" msgid "A leprechaun offers you the %{art} for the small price of %{gold} Gold." msgstr "" msgid "" "A leprechaun offers you the %{art} for the small price of %{gold} Gold and " "%{count} %{res}." msgstr "" msgid "Do you wish to buy this artifact?" msgstr "" msgid "" "You try to pay the leprechaun, but realize that you can't afford it. The " "leprechaun stamps his foot and ignores you." msgstr "" msgid "" "Insulted by your refusal of his generous offer, the leprechaun stamps his " "foot and ignores you." msgstr "" msgid "You've found the artifact: " msgstr "" msgid "" "You've found the humble dwelling of a withered hermit. The hermit tells you " "that he is willing to give the %{art} to the first wise person he meets." msgstr "" msgid "" "You've come across the spartan quarters of a retired soldier. The soldier " "tells you that he is willing to pass on the %{art} to the first true leader " "he meets." msgstr "" msgid "" "You've encountered a strange person with a hat and an owl on it. He tells " "you that he is willing to give %{art} if you have %{skill}." msgstr "" msgid "" "You come upon an ancient artifact. As you reach for it, a pack of Rogues " "leap out of the brush to guard their stolen loot." msgstr "" msgid "" "Through a clearing you observe an ancient artifact. Unfortunately, it's " "guarded by a nearby %{monster}. Do you want to fight the %{monster} for the " "artifact?" msgstr "" msgid "Victorious, you take your prize, the %{art}." msgstr "" msgid "" "Discretion is the better part of valor, and you decide to avoid this fight " "for today." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold pieces." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold and the %{art}." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it, " "only to find it empty." msgstr "" msgid "" "After scouring the area, you fall upon a hidden treasure cache. You may take " "the gold or distribute the gold to the peasants for experience. Do you wish " "to keep the gold?" msgstr "" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "%{gold} gold pieces." msgstr "" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "ancient artifact %{art}." msgstr "" msgid "" "You stumble upon a dented and tarnished lamp lodged deep in the earth. Do " "you wish to rub the lamp?" msgstr "" msgid "" "You have taken control of the local Alchemist shop. It will provide you with " "%{count} unit of Mercury per day." msgstr "" msgid "" "You gain control of a sawmill. It will provide you with %{count} units of " "wood per day." msgstr "" msgid "" "You gain control of an ore mine. It will provide you with %{count} units of " "ore per day." msgstr "" msgid "" "You gain control of a sulfur mine. It will provide you with %{count} unit of " "sulfur per day." msgstr "" msgid "" "You gain control of a crystal mine. It will provide you with %{count} unit " "of crystal per day." msgstr "" msgid "" "You gain control of a gem mine. It will provide you with %{count} unit of " "gems per day." msgstr "" msgid "" "You gain control of a gold mine. It will provide you with %{count} gold per " "day." msgstr "" msgid "" "The lighthouse is now under your control, and all of your ships will now " "move further each day." msgstr "" msgid "You gain control of a %{name}." msgstr "" msgid "" "You come upon an abandoned gold mine. The mine appears to be haunted. Do you " "wish to enter?" msgstr "" msgid "You beat the Ghosts and are able to restore the mine to production." msgstr "" msgid "" "A group of %{monster} with a desire for greater glory wish to join you. Do " "you accept?" msgstr "" msgid "As you approach the dwelling, you notice that there is no one here." msgstr "" msgid "" "You search the ruins, but the Medusas that used to live here are gone. " "Perhaps there will be more next week." msgstr "" msgid "" "You've found some Medusas living in the ruins. They are willing to join your " "army for a price. Do you want to recruit Medusas?" msgstr "" msgid "" "You've found a Sprite Tree City. Unfortunately, none of the Sprites living " "there wish to join an army. Maybe next week." msgstr "" msgid "" "Some of the Sprites living in the tree city are willing to join your army " "for a price. Do you want to recruit Sprites?" msgstr "" msgid "" "A colorful Rogues' wagon stands empty here. Perhaps more Rogues will be here " "later." msgstr "" msgid "" "Distant sounds of music and laughter draw you to a colorful wagon housing " "Rogues. Do you wish to have any Rogues join your army?" msgstr "" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. The " "tents are unoccupied. Perhaps more Nomads will be here later." msgstr "" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. Do you " "wish to have any Nomads join you during your travels?" msgstr "" msgid "The pit of mud bubbles for a minute and then lies still." msgstr "" msgid "" "As you approach the bubbling pit of mud, creatures begin to climb out and " "position themselves around it. In unison they say: \"Mother Earth would like " "to offer you a few of her troops. Do you want to recruit Earth Elementals?\"" msgstr "" msgid "You enter the structure of white stone pillars, and find nothing." msgstr "" msgid "" "White stone pillars support a roof that rises up to the sky. As you enter " "the structure, the dead air of the outside gives way to a whirling gust that " "almost pushes you back out. The air current materializes into a barely " "visible form. The creature asks, in what can only be described as a loud " "whisper: \"Why have you come? Are you here to call upon the forces of the " "air?\"" msgstr "" msgid "No Fire Elementals approach you from the lava pool." msgstr "" msgid "" "Beneath a structure that serves to hold in heat, Fire Elementals move about " "in a fiery pool of molten lava. A group of them approach you and offer their " "services. Would you like to recruit Fire Elementals?" msgstr "" msgid "A face forms in the water for a moment, and then is gone." msgstr "" msgid "" "Crystalline structures cast shadows over a small reflective pool of water. " "You peer into the pool, and a face that is not your own peers back. It asks: " "\"Would you like to call upon the powers of water?\"" msgstr "" msgid "This burial site is deathly still." msgstr "" msgid "" "Restless spirits of long dead warriors seeking their final resting place " "offer to join you in hopes of finding peace. Do you wish to recruit ghosts?" msgstr "" msgid "" "The City of the Dead is empty of life, and empty of unlife as well. Perhaps " "some undead will move in next week." msgstr "" msgid "" "Some Liches living here are willing to join your army for a price. Do you " "want to recruit Liches?" msgstr "" msgid "" "You've found the ruins of an ancient city, now inhabited solely by the " "undead. Will you search?" msgstr "" msgid "" "Some of the surviving Liches are impressed by your victory over their " "fellows, and offer to join you for a price. Do you want to recruit Liches?" msgstr "" msgid "" "You've found one of those bridges that Trolls are so fond of living under, " "but there are none here. Perhaps there will be some next week." msgstr "" msgid "" "Some Trolls living under a bridge are willing to join your army, but for a " "price. Do you want to recruit Trolls?" msgstr "" msgid "Trolls living under the bridge challenge you. Will you fight them?" msgstr "" msgid "" "A few Trolls remain, cowering under the bridge. They approach you and offer " "to join your forces as mercenaries. Do you want to buy any Trolls?" msgstr "" msgid "" "The Dragon city has no Dragons willing to join you this week. Perhaps a " "Dragon will become available next week." msgstr "" msgid "" "The Dragon city is willing to offer some Dragons for your army for a price. " "Do you wish to recruit Dragons?" msgstr "" msgid "" "You stand before the Dragon City, a place off-limits to mere humans. Do you " "wish to violate this rule and challenge the Dragons to a fight?" msgstr "" msgid "" "Having defeated the Dragon champions, the city's leaders agree to supply " "some Dragons to your army for a price. Do you wish to recruit Dragons?" msgstr "" msgid "From the observation tower, you are able to see distant lands." msgstr "" msgid "" "The spring only refills once a week, and someone's already been here this " "week." msgstr "" msgid "" "A drink at the spring is supposed to give you twice your normal spell " "points, but you are already at that level." msgstr "" msgid "" "A drink from the spring fills your blood with magic! You have twice your " "normal spell points in reserve." msgstr "" msgid "" "Recognizing you, the butler refuses to admit you. \"The master,\" he says, " "\"will not see the same student twice.\"" msgstr "" msgid "" "The butler admits you to see the master of the house. He trains you in the " "four skills a hero should know." msgstr "" msgid "" "The butler opens the door and looks you up and down. \"You are neither " "famous nor diplomatic enough to be admitted to see my master,\" he sniffs. " "\"Come back when you think yourself worthy.\"" msgstr "" msgid " and " msgstr "" msgid "" "All of the %{monsters} you have in your army have been trained by the battle " "masters of the fort. Your army now contains %{monsters2}." msgstr "" msgid "" "An unusual alliance of Ogres, Orcs, and Dwarves offer to train (upgrade) any " "such troops brought to them. Unfortunately, you have none with you." msgstr "" msgid "All of your %{monsters} have been upgraded into %{monsters2}." msgstr "" msgid "" "A blacksmith working at the foundry offers to convert all Pikemen and " "Swordsmen's weapons brought to him from iron to steel. He also says that he " "knows a process that will convert Iron Golems into Steel Golems. " "Unfortunately, you have none of these troops in your army, so he can't help " "you." msgstr "" msgid "" "The captain looks at you with surprise and says:\n" "\"You already have all the maps I know about. Let me fish in peace now.\"" msgstr "" msgid "" "A retired captain living on this refurbished fishing platform offers to sell " "you maps of the sea he made in his younger days for 1,000 gold. Do you wish " "to buy the maps?" msgstr "" msgid "" "The captain sighs. \"You don't have enough money, eh? You can't expect me " "to give my maps away for free!\"" msgstr "" msgid "" "You come upon an obelisk made from a type of stone you have never seen " "before. Staring at it intensely, the smooth surface suddenly changes to an " "inscription. The inscription is a piece of a lost ancient map. Quickly you " "copy down the piece and the inscription vanishes as abruptly as it appeared." msgstr "" msgid "You have already been to this obelisk." msgstr "" msgid "" "Upon your approach, the tree opens its eyes in delight. \"It is good to see " "you, my student. I hope my teachings have helped you.\"" msgstr "" msgid "" "Upon your approach, the tree opens its eyes in delight. \"Ahh, an " "adventurer! Allow me to teach you a little of what I have learned over the " "ages.\"" msgstr "" msgid "Upon your approach, the tree opens its eyes in delight." msgstr "" msgid "" "\"Ahh, an adventurer! I will be happy to teach you a little of what I have " "learned over the ages for a mere %{count} %{res}.\"" msgstr "" msgid "(Just bury it around my roots.)" msgstr "" msgid "Tears brim in the eyes of the tree." msgstr "" msgid "\"I need %{count} %{res}.\"" msgstr "" msgid "it whispers. (sniff) \"Well, come back when you can pay me.\"" msgstr "" msgid "" "Nestled among the trees sits a blind seer. After you explain the intent of " "your journey, the seer activates his crystal ball, allowing you to see the " "strengths and weaknesses of your opponents." msgstr "" msgid "" "The entrance to the cave is dark, and a foul, sulfurous smell issues from " "the cave mouth. Will you enter?" msgstr "" msgid "Except for evidence of a terrible battle, the cave is empty." msgstr "" msgid "" "You find a powerful and grotesque Demon in the cave. \"Today,\" it rasps, " "\"you will fight and surely die. But I will give you a choice of deaths. You " "may fight me, or you may fight my servants. Do you prefer to fight my " "servants?\"" msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points." msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points and " "%{count} gold." msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and find the %{art} in the back of the cave." msgstr "" msgid "" "Seeing that you do not have %{count} gold, the demon slashes you with its " "claws, and the last thing you see is a red haze." msgstr "" msgid "" "The Demon leaps upon you and has its claws at your throat before you can " "even draw your sword. \"Your life is mine,\" it says. \"I will sell it back " "to you for %{count} gold.\"" msgstr "" msgid "" "Upon defeating the daemon's servants, you find a hidden cache with %{count} " "gold." msgstr "" msgid "" "As you enter the Alchemist's Tower, a hobbled, graying man in a brown cloak " "makes his way towards you." msgstr "" msgid "He checks your pack, and sees that you have 1 cursed item." msgid_plural "" "He checks your pack, and sees that you have %{count} cursed items." msgstr[0] "" msgstr[1] "" msgid "For %{gold} gold, the alchemist will remove it for you. Do you pay?" msgid_plural "" "For %{gold} gold, the alchemist will remove them for you. Do you pay?" msgstr[0] "" msgstr[1] "" msgid "" "After you consent to pay the requested amount of gold, the alchemist grabs " "the cursed artifact and throws it into his magical cauldron." msgid_plural "" "After you consent to pay the requested amount of gold, the alchemist grabs " "all cursed artifacts and throws them into his magical cauldron." msgstr[0] "" msgstr[1] "" msgid "" "You hear a voice from behind the locked door, \"You don't have enough gold " "to pay for my services.\"" msgstr "" msgid "" "You hear a voice from high above in the tower, \"Go away! I can't help you!\"" msgstr "" msgid "" "The head groom speaks to you, \"That is a fine looking horse you have. I am " "afraid we can give you no better, but the horses your cavalry are riding " "look to be of poor breeding stock. We have many trained war horses which " "would aid your riders greatly. I insist you take them.\"" msgstr "" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, he will " "grow tired in a week. You must also let me give better horses to your " "mounted soldiers, their horses look shoddy and weak.\"" msgstr "" msgid "" "The head groom approaches you and speaks, \"You already have a fine horse, " "and have no inexperienced cavalry which might make use of our trained war " "horses.\"" msgstr "" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, his " "endurance will wane with a lot of heavy riding, and you must return for a " "fresh mount in a week. We also have many fine war horses which could benefit " "mounted soldiers, but you have none we can help.\"" msgstr "" msgid "The Arena guards turn you away." msgstr "" msgid "" "You have your crew stop up their ears with wax before the sirens' eerie song " "has any chance of luring them to a watery grave." msgstr "" msgid "" "As the sirens sing their eerie song, your small, determined army manages to " "overcome the urge to dive headlong into the sea." msgstr "" msgid "" "An eerie wailing song emanates from the sirens perched upon the rocks. Many " "of your crew fall under its spell, and dive into the water where they drown. " "You are now wiser for the visit, and gain %{exp} experience." msgstr "" msgid "" "In a dazzling display of daring, you break into the local jail and free the " "hero imprisoned there, who, in return, pledges loyalty to your cause." msgstr "" msgid "" "You already have %{count} heroes, and regretfully must leave the prisoner in " "this jail to languish in agony for untold days." msgstr "" msgid "" "You enter a rickety hut and talk to the magician who lives there. He tells " "you of places near and far which may aid you in your journeys." msgstr "" msgid "This eye seems to be intently studying its surroundings." msgstr "" msgid "You come across a giant Sphinx. The Sphinx remains strangely quiet." msgstr "" msgid "" "\"I have a riddle for you,\" the Sphinx says. \"Answer correctly, and you " "shall be rewarded. Answer incorrectly, and you shall be eaten. Do you accept " "the challenge?\"" msgstr "" msgid "" "The Sphinx asks you the following riddle:\n" "\n" "'%{riddle}'\n" "\n" "Your answer?" msgstr "" msgid "" "\"You guessed incorrectly,\" the Sphinx says, smiling. The Sphinx swipes at " "you with a paw, knocking you to the ground. Another blow makes the world go " "black, and you know no more." msgstr "" msgid "" "Looking somewhat disappointed, the Sphinx sighs. \"You've answered my riddle " "so here's your reward. Now begone.\"" msgstr "" msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "As you speak the magic word, the glowing barrier dissolves into nothingness." msgstr "" msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "You speak, and nothing happens." msgstr "" msgid "" "You enter the tent and see an old woman gazing into a magic gem. She looks " "up and says,\n" "\"In my travels, I have learned much in the way of arcane magic. A great " "oracle taught me his skill. I have the answer you seek.\"" msgstr "" #, fuzzy msgid "No spell book is present." msgstr "Geen spreuken om uit te spreken." msgid "" "This spell costs %{mana} spell points. You have no spell points, so you " "cannot cast it." msgstr "" msgid "" "This spell costs %{mana} spell points. You only have %{point} spell points, " "so you cannot cast it." msgstr "" msgid "The spell was not found." msgstr "" msgid "Only heroes can cast this spell." msgstr "" msgid "This hero is not able to cast adventure spells." msgstr "" msgid "Your hero is too tired to cast this spell today. Try again tomorrow." msgstr "" msgid "This spell cannot be cast on a boat." msgstr "" msgid "This spell can only be cast near an ocean." msgstr "" msgid "" "There are no boats available and no ocean adjacent to the hero where this " "spell will work." msgstr "" msgid "There are no boats available for this spell." msgstr "" #, fuzzy msgid "There is no ocean adjacent to the hero where this spell will work." msgstr "" "Er is geen open plek aangrenzend bij je held om een Elemental naar toe te " "oproepen." msgid "" "You do not own any town or castle that is not currently occupied by a hero. " "This spell will have no effect." msgstr "" #, fuzzy msgid "This hero is already in a town, so this spell will have no effect." msgstr "Die spreuk zal niemand beïnvloeden!" msgid "" "The nearest town is %{town}.\n" "\n" "This town is occupied by your hero %{hero}." msgstr "" msgid "This spell is already in effect." msgstr "" msgid "" "No opponent neither has nor can have any hero under their command anymore. " "Casting this spell will have no effect." msgstr "" msgid "" "No opponent has a hero under their command at this time. Casting this spell " "will have no effect." msgstr "" msgid "" "You must be within %{count} spaces of a monster for the Visions spell to " "work." msgstr "" msgid "" "You must be standing on the entrance to a mine (sawmills and alchemist labs " "do not count) to cast this spell." msgstr "" msgid "You must first defeat the ghosts guarding the mine to cast this spell." msgstr "" msgid "" "There are already at least as many elementals guarding the mine as this hero " "can generate. Casting this spell will have no effect." msgstr "" msgid "%{name} the %{race} (Level %{level})" msgstr "" msgid "" "'Spread' combat formation spreads the hero's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" msgid "" "'Grouped' combat formation bunches the hero's army together in the center of " "their side of the battlefield." msgstr "" msgid "Are you sure you want to dismiss this Hero?" msgstr "" msgid "View Experience Info" msgstr "" msgid "View Spell Points Info" msgstr "" msgid "Set army combat formation to 'Spread'" msgstr "" msgid "Set army combat formation to 'Grouped'" msgstr "" msgid "Exit Hero Screen" msgstr "" msgid "You cannot dismiss a hero in a castle" msgstr "" msgid "Dismissal of %{name} the %{race} is prohibited by scenario" msgstr "" msgid "Dismiss %{name} the %{race}" msgstr "" msgid "Show previous hero" msgstr "" msgid "Show next hero" msgstr "" msgid "Blood Morale" msgstr "" msgid "%{morale} Morale" msgstr "" msgid "%{luck} Luck" msgstr "" msgid "Current Luck Modifiers:" msgstr "" msgid "Current Morale Modifiers:" msgstr "" msgid "Entire army is undead, so morale does not apply." msgstr "" msgid "" "Current experience %{exp1}.\n" " Next level %{exp2}." msgstr "" msgid "Level %{level}" msgstr "" msgid "" "%{name} currently has %{point} spell points out of a maximum of %{max}. The " "maximum number of spell points is 10 times the hero's knowledge. It is " "occasionally possible for the hero to have more than their maximum spell " "points via special events." msgstr "" msgid "%{name1} meets %{name2}" msgstr "" msgid "Enemy heroes are now fully identifiable." msgstr "" msgid "Identify Hero" msgstr "" msgid "Select town to port to." msgstr "" msgid "Town Portal" msgstr "" msgid "The creatures are willing to join us!" msgstr "" msgid "All the creatures will join us..." msgstr "" msgid "The creature will join us..." msgid_plural "%{count} of the creatures will join us..." msgstr[0] "" msgstr[1] "" msgid "" "\n" " for a fee of %{gold} gold." msgstr "" msgid "These weak creatures will surely flee before us." msgstr "" msgid "I fear these creatures are in the mood for a fight." msgstr "" #, fuzzy msgid "The hero's attack skill is a bonus added to each unit's attack skill." msgstr "elk met een +%{attack} bonus voor hun aanvalsvaardigheid." msgid "The hero's defense skill is a bonus added to each unit's defense skill." msgstr "" msgid "The hero's spell power determines the duration or power of a spell." msgstr "" msgid "" " The hero's knowledge determines how many spell points the hero may have. " "Under normal circumstances, a hero is limited to 10 spell points per level " "of knowledge." msgstr "" msgid "Current Modifiers:" msgstr "" msgid "skill|Basic" msgstr "" msgid "skill|Advanced" msgstr "" msgid "skill|Expert" msgstr "" msgid "Pathfinding" msgstr "" msgid "Archery" msgstr "" msgid "Logistics" msgstr "" msgid "Scouting" msgstr "" msgid "Diplomacy" msgstr "" msgid "Navigation" msgstr "" msgid "Leadership" msgstr "" msgid "Wisdom" msgstr "" msgid "Mysticism" msgstr "" msgid "Ballistics" msgstr "" msgid "Eagle Eye" msgstr "" msgid "Necromancy" msgstr "" msgid "Estates" msgstr "" msgid "Advanced Archery" msgstr "" msgid "Advanced Pathfinding" msgstr "" msgid "Basic Archery" msgstr "" msgid "Basic Pathfinding" msgstr "" msgid "Expert Pathfinding" msgstr "" msgid "Advanced Logistics" msgstr "" msgid "Basic Logistics" msgstr "" msgid "Basic Scouting" msgstr "" msgid "Expert Archery" msgstr "" msgid "Expert Logistics" msgstr "" msgid "Advanced Diplomacy" msgstr "" msgid "Advanced Scouting" msgstr "" msgid "Basic Diplomacy" msgstr "" msgid "Expert Diplomacy" msgstr "" msgid "Expert Scouting" msgstr "" msgid "Advanced Leadership" msgstr "" msgid "Advanced Navigation" msgstr "" msgid "Basic Leadership" msgstr "" msgid "Basic Navigation" msgstr "" msgid "Expert Navigation" msgstr "" msgid "Advanced Wisdom" msgstr "" msgid "Basic Mysticism" msgstr "" msgid "Basic Wisdom" msgstr "" msgid "Expert Leadership" msgstr "" msgid "Expert Wisdom" msgstr "" msgid "Advanced Luck" msgstr "" msgid "Advanced Mysticism" msgstr "" msgid "Basic Luck" msgstr "" msgid "Expert Luck" msgstr "" msgid "Expert Mysticism" msgstr "" msgid "Advanced Ballistics" msgstr "" msgid "Advanced Eagle Eye" msgstr "" msgid "Basic Ballistics" msgstr "" msgid "Basic Eagle Eye" msgstr "" msgid "Expert Ballistics" msgstr "" msgid "Advanced Necromancy" msgstr "" msgid "Basic Estates" msgstr "" msgid "Basic Necromancy" msgstr "" msgid "Expert Eagle Eye" msgstr "" msgid "Expert Necromancy" msgstr "" msgid "Advanced Estates" msgstr "" msgid "Expert Estates" msgstr "" msgid "" "%{skill} reduces the movement penalty for rough terrain by %{count} percent." msgstr "" msgid "%{skill} eliminates the movement penalty for rough terrain." msgstr "" msgid "" "%{skill} increases the damage done by the hero's range attacking creatures " "by %{count} percent." msgstr "" msgid "%{skill} increases the hero's movement points by %{count} percent." msgstr "" msgid "%{skill} increases the hero's viewable area by one square." msgid_plural "%{skill} increases the hero's viewable area by %{count} squares." msgstr[0] "" msgstr[1] "" msgid "" "%{skill} allows the hero to negotiate with monsters who are weaker than " "their army, and reduces the cost of surrender." msgstr "" msgid "" "Approximately %{count} percent of the creatures may offer to join the hero." msgstr "" msgid "All of the creatures may offer to join the hero." msgstr "" msgid "" "The cost of surrender is reduced to %{percent} percent of the total cost of " "troops in the army." msgstr "" msgid "" "%{skill} increases the hero's movement points over water by %{count} percent." msgstr "" msgid "%{skill} increases the hero's troops morale by %{count}." msgstr "" msgid "%{skill} allows the hero to learn third level spells." msgstr "" msgid "%{skill} allows the hero to learn fourth level spells." msgstr "" msgid "%{skill} allows the hero to learn fifth level spells." msgstr "" msgid "%{skill} regenerates one additional spell point per day to the hero." msgid_plural "" "%{skill} regenerates %{count} additional spell points per day to the hero." msgstr[0] "" msgstr[1] "" msgid "%{skill} increases the hero's luck by %{count}." msgstr "" msgid "" "%{skill} gives the hero's catapult shots a greater chance to hit and do " "damage to castle walls." msgstr "" msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot has a " "greater chance to hit and do damage to castle walls." msgstr "" msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot " "automatically destroys any wall, except a fortified wall in a Knight castle." msgstr "" msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 1st or " "2nd level spell that was cast by an enemy during combat." msgstr "" msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 3rd " "level spell (or below) that was cast by an enemy during combat." msgstr "" msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 4th " "level spell (or below) that was cast by an enemy during combat." msgstr "" msgid "" "%{skill} allows %{count} percent of the creatures killed in combat to be " "brought back from the dead as Skeletons." msgstr "" msgid "" "The hero produces %{count} gold pieces per day as tax revenue from estates." msgstr "" msgid "Blue" msgstr "" msgid "Green" msgstr "" msgid "Red" msgstr "" msgid "Yellow" msgstr "" msgid "Orange" msgstr "" msgid "Purple" msgstr "" msgid "barrier|Aqua" msgstr "" msgid "barrier|Blue" msgstr "" msgid "barrier|Brown" msgstr "" msgid "barrier|Gold" msgstr "" msgid "barrier|Green" msgstr "" msgid "barrier|Orange" msgstr "" msgid "barrier|Purple" msgstr "" msgid "barrier|Red" msgstr "" msgid "tent|Aqua" msgstr "" msgid "tent|Blue" msgstr "" msgid "tent|Brown" msgstr "" msgid "tent|Gold" msgstr "" msgid "tent|Green" msgstr "" msgid "tent|Orange" msgstr "" msgid "tent|Purple" msgstr "" msgid "tent|Red" msgstr "" msgid "Experience" msgstr "" msgid "" "Experience allows your heroes to go up levels, increasing their primary and " "secondary skills." msgstr "" msgid "Hero/Stats" msgstr "" msgid "Skills" msgstr "" msgid "Town/Castle" msgstr "" msgid "Garrison" msgstr "" msgid "Gold Per Day:" msgstr "" msgid "View Heroes." msgstr "" msgid "Towns/Castles" msgstr "" msgid "View Towns and Castles." msgstr "" msgid "luck|Cursed" msgstr "" msgid "luck|Awful" msgstr "" msgid "luck|Bad" msgstr "" msgid "luck|Normal" msgstr "" msgid "luck|Good" msgstr "" msgid "luck|Great" msgstr "" msgid "luck|Irish" msgstr "" msgid "" "Bad luck sometimes falls on the hero's units in combat, causing their " "attacks to only do half damage." msgstr "" msgid "" "Neutral luck means the hero's units will never get lucky or unlucky attacks " "on the enemy." msgstr "" msgid "" "Good luck sometimes lets the hero's units get lucky attacks (double " "strength) in combat." msgstr "" msgid "morale|Treason" msgstr "" msgid "morale|Awful" msgstr "" msgid "morale|Poor" msgstr "" msgid "morale|Normal" msgstr "" msgid "morale|Good" msgstr "" msgid "morale|Great" msgstr "" msgid "morale|Blood!" msgstr "" #, fuzzy msgid "Bad morale may cause the hero's units to freeze in combat." msgstr "Een laag moreel zorgt ervoor dat het %{monster} in paniek bevriest." msgid "" "Neutral morale means the hero's units will never be blessed with extra " "attacks or freeze in combat." msgstr "" msgid "Good morale may give the hero's units extra attacks in combat." msgstr "" msgid "Knight" msgstr "" msgid "Barbarian" msgstr "" msgid "Sorceress" msgstr "" msgid "Warlock" msgstr "" msgid "Wizard" msgstr "" msgid "Necromancer" msgstr "" msgid "Multi" msgstr "" msgid "doubleLined|Knight" msgstr "" msgid "doubleLined|Barbarian" msgstr "" msgid "doubleLined|Sorceress" msgstr "" msgid "doubleLined|Warlock" msgstr "" msgid "doubleLined|Wizard" msgstr "" msgid "" "doubleLined|Necro-\n" "mancer" msgstr "" msgid "doubleLinedRace|Multi" msgstr "" msgid "doubleLinedRace|Random" msgstr "" msgid "speed|Standing" msgstr "" msgid "speed|Crawling" msgstr "" msgid "speed|Very Slow" msgstr "" msgid "speed|Slow" msgstr "" msgid "speed|Average" msgstr "" msgid "speed|Fast" msgstr "" msgid "speed|Very Fast" msgstr "" msgid "speed|Ultra Fast" msgstr "" msgid "speed|Blazing" msgstr "" msgid "speed|Instant" msgstr "" msgid "week|Squirrel" msgstr "" msgid "week|Rabbit" msgstr "" msgid "week|Gopher" msgstr "" msgid "week|Badger" msgstr "" msgid "week|Rat" msgstr "" msgid "week|Eagle" msgstr "" msgid "week|Weasel" msgstr "" msgid "week|Raven" msgstr "" msgid "week|Mongoose" msgstr "" msgid "week|Dog" msgstr "" msgid "week|Aardvark" msgstr "" msgid "week|Lizard" msgstr "" msgid "week|Tortoise" msgstr "" msgid "week|Hedgehog" msgstr "" msgid "week|Condor" msgstr "" msgid "week|Ant" msgstr "" msgid "week|Grasshopper" msgstr "" msgid "week|Dragonfly" msgstr "" msgid "week|Spider" msgstr "" msgid "week|Butterfly" msgstr "" msgid "week|Bumblebee" msgstr "" msgid "week|Locust" msgstr "" msgid "week|Earthworm" msgstr "" msgid "week|Hornet" msgstr "" msgid "week|Beetle" msgstr "" msgid "week|PLAGUE" msgstr "" msgid "week|Unnamed" msgstr "" msgid "Desert" msgstr "" msgid "Snow" msgstr "" msgid "Wasteland" msgstr "" msgid "Beach" msgstr "" msgid "Lava" msgstr "" msgid "Dirt" msgstr "" msgid "Grass" msgstr "" msgid "Ocean" msgstr "" msgid "maps|Small" msgstr "" msgid "maps|Medium" msgstr "" msgid "maps|Large" msgstr "" msgid "maps|Extra Large" msgstr "" msgid "maps|Custom Size" msgstr "" msgid "Ore Mine" msgstr "" msgid "Sulfur Mine" msgstr "" msgid "Crystal Mine" msgstr "" msgid "Gems Mine" msgstr "" msgid "Gold Mine" msgstr "" msgid "Mine" msgstr "" msgid "Burma shave." msgstr "" msgid "Next sign 50 miles." msgstr "" msgid "See Rock City." msgstr "" msgid "This space for rent." msgstr "" msgid "No object" msgstr "" msgid "Alchemist Lab" msgstr "" msgid "Sign" msgstr "" msgid "Buoy" msgstr "" msgid "Skeleton" msgstr "" msgid "Daemon Cave" msgstr "" msgid "Treasure Chest" msgstr "" msgid "Faerie Ring" msgstr "" msgid "Campfire" msgstr "" msgid "Fountain" msgstr "" msgid "Gazebo" msgstr "" msgid "Genie Lamp" msgstr "" msgid "Archer's House" msgstr "" msgid "Goblin Hut" msgstr "" msgid "Dwarf Cottage" msgstr "" msgid "Peasant Hut" msgstr "" msgid "Stables" msgstr "" msgid "Alchemist's Tower" msgstr "" msgid "Event" msgstr "" msgid "Dragon City" msgstr "" msgid "Lighthouse" msgid_plural "Lighthouses" msgstr[0] "" msgstr[1] "" msgid "Water Wheel" msgid_plural "Water Wheels" msgstr[0] "" msgstr[1] "" msgid "Monster" msgstr "" msgid "Obelisk" msgstr "" msgid "Oasis" msgstr "" msgid "Resource" msgstr "" msgid "Sawmill" msgstr "" msgid "Oracle" msgstr "" msgid "Shrine of the First Circle" msgstr "" msgid "Shipwreck" msgstr "" msgid "Sea Chest" msgstr "" msgid "Desert Tent" msgstr "" msgid "Stone Liths" msgstr "" msgid "Wagon Camp" msgstr "" msgid "Hut of the Magi" msgstr "" msgid "Whirlpool" msgstr "" msgid "Windmill" msgid_plural "Windmills" msgstr[0] "" msgstr[1] "" msgid "Artifact" msgstr "" msgid "Mermaid" msgstr "" msgid "Boat" msgstr "" msgid "Random Ultimate Artifact" msgstr "" msgid "Random Artifact" msgstr "" msgid "Random Resource" msgstr "" msgid "Random Monster" msgstr "" msgid "Random Town" msgstr "" msgid "Random Castle" msgstr "" msgid "Eye of the Magi" msgstr "" msgid "Random Monster - weak" msgstr "" msgid "Random Monster - medium" msgstr "" msgid "Random Monster - strong" msgstr "" msgid "Random Monster - very strong" msgstr "" msgid "Hero" msgstr "" msgid "Nothing Special" msgstr "" msgid "Mossy Rock" msgstr "" msgid "Watch Tower" msgstr "" msgid "Tree House" msgstr "" msgid "Tree City" msgstr "" msgid "Ruins" msgstr "" msgid "Fort" msgstr "" msgid "Abandoned Mine" msgstr "" msgid "Sirens" msgstr "" msgid "Standing Stones" msgstr "" msgid "Idol" msgstr "" msgid "Tree of Knowledge" msgstr "" msgid "Witch Doctor's Hut" msgstr "" msgid "Temple" msgstr "" msgid "Hill Fort" msgstr "" msgid "Halfling Hole" msgstr "" msgid "Mercenary Camp" msgstr "" msgid "Shrine of the Second Circle" msgstr "" msgid "Shrine of the Third Circle" msgstr "" msgid "City of the Dead" msgstr "" msgid "Sphinx" msgstr "" msgid "Wagon" msgstr "" msgid "Tar Pit" msgstr "" msgid "Artesian Spring" msgstr "" msgid "Troll Bridge" msgstr "" msgid "Watering Hole" msgstr "" msgid "Witch's Hut" msgstr "" msgid "Xanadu" msgstr "" msgid "Lean-To" msgstr "" msgid "Magellan's Maps" msgstr "" msgid "Flotsam" msgstr "" msgid "Derelict Ship" msgstr "" msgid "Shipwreck Survivor" msgstr "" msgid "Bottle" msgstr "" msgid "Magic Well" msgstr "" msgid "Magic Garden" msgid_plural "Magic Gardens" msgstr[0] "" msgstr[1] "" msgid "Observation Tower" msgstr "" msgid "Freeman's Foundry" msgstr "" msgid "Reefs" msgstr "" msgid "Volcano" msgstr "" msgid "Flowers" msgstr "" msgid "Rock" msgstr "" msgid "Water Lake" msgstr "" msgid "Mandrake" msgstr "" msgid "Dead Tree" msgstr "" msgid "Stump" msgstr "" msgid "Crater" msgstr "" msgid "Cactus" msgstr "" msgid "Mound" msgstr "" msgid "Dune" msgstr "" msgid "Lava Pool" msgstr "" msgid "Shrub" msgstr "" msgid "Barrow Mounds" msgstr "" msgid "Random Artifact - Treasure" msgstr "" msgid "Random Artifact - Minor" msgstr "" msgid "Random Artifact - Major" msgstr "" msgid "Barrier" msgstr "" msgid "Traveller's Tent" msgstr "" msgid "Jail" msgstr "" msgid "Fire Summoning Altar" msgstr "" msgid "Air Summoning Altar" msgstr "" msgid "Earth Summoning Altar" msgstr "" msgid "Water Summoning Altar" msgstr "" msgid "Unknown Monster" msgstr "" msgid "Unknown Monsters" msgstr "" msgid "Peasant" msgstr "" msgid "Peasants" msgstr "" msgid "Archer" msgstr "" msgid "Archers" msgstr "" msgid "Ranger" msgstr "" msgid "Rangers" msgstr "" msgid "Pikeman" msgstr "" msgid "Pikemen" msgstr "" msgid "Veteran Pikeman" msgstr "" msgid "Veteran Pikemen" msgstr "" msgid "Swordsman" msgstr "" msgid "Swordsmen" msgstr "" msgid "Master Swordsman" msgstr "" msgid "Master Swordsmen" msgstr "" msgid "Cavalries" msgstr "" msgid "Cavalry" msgstr "" msgid "Champion" msgstr "" msgid "Champions" msgstr "" msgid "Paladin" msgstr "" msgid "Paladins" msgstr "" msgid "Crusader" msgstr "" msgid "Crusaders" msgstr "" msgid "Goblin" msgstr "" msgid "Goblins" msgstr "" msgid "Orc" msgstr "" msgid "Orcs" msgstr "" msgid "Orc Chief" msgstr "" msgid "Orc Chiefs" msgstr "" msgid "Wolf" msgstr "" msgid "Wolves" msgstr "" msgid "Ogre" msgstr "" msgid "Ogres" msgstr "" msgid "Ogre Lord" msgstr "" msgid "Ogre Lords" msgstr "" msgid "Troll" msgstr "" msgid "Trolls" msgstr "" msgid "War Troll" msgstr "" msgid "War Trolls" msgstr "" msgid "Cyclopes" msgstr "" msgid "Cyclops" msgstr "" msgid "Sprite" msgstr "" msgid "Sprites" msgstr "" msgid "Dwarf" msgstr "" msgid "Dwarves" msgstr "" msgid "Battle Dwarf" msgstr "" msgid "Battle Dwarves" msgstr "" msgid "Elf" msgstr "" msgid "Elves" msgstr "" msgid "Grand Elf" msgstr "" msgid "Grand Elves" msgstr "" msgid "Druid" msgstr "" msgid "Druids" msgstr "" msgid "Greater Druid" msgstr "" msgid "Greater Druids" msgstr "" msgid "Unicorn" msgstr "" msgid "Unicorns" msgstr "" msgid "Phoenix" msgstr "" msgid "Phoenixes" msgstr "" msgid "Centaur" msgstr "" msgid "Centaurs" msgstr "" msgid "Gargoyle" msgstr "" msgid "Gargoyles" msgstr "" msgid "Griffin" msgstr "" msgid "Griffins" msgstr "" msgid "Minotaur" msgstr "" msgid "Minotaurs" msgstr "" msgid "Minotaur King" msgstr "" msgid "Minotaur Kings" msgstr "" msgid "Hydra" msgstr "" msgid "Hydras" msgstr "" msgid "Green Dragon" msgstr "" msgid "Green Dragons" msgstr "" msgid "Red Dragon" msgstr "" msgid "Red Dragons" msgstr "" msgid "Black Dragon" msgstr "" msgid "Black Dragons" msgstr "" msgid "Halfling" msgstr "" msgid "Halflings" msgstr "" msgid "Boar" msgstr "" msgid "Boars" msgstr "" msgid "Iron Golem" msgstr "" msgid "Iron Golems" msgstr "" msgid "Steel Golem" msgstr "" msgid "Steel Golems" msgstr "" msgid "Roc" msgstr "" msgid "Rocs" msgstr "" msgid "Mage" msgstr "" msgid "Magi" msgstr "" msgid "Archmage" msgstr "" msgid "Archmagi" msgstr "" msgid "Giant" msgstr "" msgid "Giants" msgstr "" msgid "Titan" msgstr "" msgid "Titans" msgstr "" msgid "Skeletons" msgstr "" msgid "Zombie" msgstr "" msgid "Zombies" msgstr "" msgid "Mutant Zombie" msgstr "" msgid "Mutant Zombies" msgstr "" msgid "Mummies" msgstr "" msgid "Mummy" msgstr "" msgid "Royal Mummies" msgstr "" msgid "Royal Mummy" msgstr "" msgid "Vampire" msgstr "" msgid "Vampires" msgstr "" msgid "Vampire Lord" msgstr "" msgid "Vampire Lords" msgstr "" msgid "Lich" msgstr "" msgid "Liches" msgstr "" msgid "Power Lich" msgstr "" msgid "Power Liches" msgstr "" msgid "Bone Dragon" msgstr "" msgid "Bone Dragons" msgstr "" msgid "Rogue" msgstr "" msgid "Rogues" msgstr "" msgid "Nomad" msgstr "" msgid "Nomads" msgstr "" msgid "Ghost" msgstr "" msgid "Ghosts" msgstr "" msgid "Genie" msgstr "" msgid "Genies" msgstr "" msgid "Medusa" msgstr "" msgid "Medusas" msgstr "" msgid "Earth Elemental" msgstr "" msgid "Earth Elementals" msgstr "" msgid "Air Elemental" msgstr "" msgid "Air Elementals" msgstr "" msgid "Fire Elemental" msgstr "" msgid "Fire Elementals" msgstr "" msgid "Water Elemental" msgstr "" msgid "Water Elementals" msgstr "" msgid "Random Monsters" msgstr "" msgid "Random Monster 1" msgstr "" msgid "Random Monsters 1" msgstr "" msgid "Random Monster 2" msgstr "" msgid "Random Monsters 2" msgstr "" msgid "Random Monster 3" msgstr "" msgid "Random Monsters 3" msgstr "" msgid "Random Monster 4" msgstr "" msgid "Random Monsters 4" msgstr "" msgid "Double shot" msgstr "" msgid "2-hex monster" msgstr "" msgid "Double strike" msgstr "" msgid "Double damage to Undead" msgstr "" msgid "% magic resistance" msgstr "" msgid "Immune to Mind spells" msgstr "" msgid "Immune to Elemental spells" msgstr "" msgid "Immune to Fire spells" msgstr "" msgid "Immune to Cold spells" msgstr "" msgid "Immune to " msgstr "" msgid "% immunity to %{spell} spell" msgstr "" msgid "% damage from Elemental spells" msgstr "" msgid "% chance to Dispel beneficial spells" msgstr "" msgid "% chance to Paralyze" msgstr "" msgid "% chance to Petrify" msgstr "" msgid "% chance to Blind" msgstr "" msgid "% chance to Curse" msgstr "" msgid "% chance to cast %{spell} spell" msgstr "" msgid "HP regeneration" msgstr "" msgid "Two hexes attack" msgstr "" msgid "Flyer" msgstr "" msgid "Always retaliates" msgstr "" msgid "Attacks all adjacent enemies" msgstr "" msgid "No melee penalty" msgstr "" msgid "Dragon" msgstr "" msgid "Undead" msgstr "" msgid "No enemy retaliation" msgstr "" msgid "HP drain" msgstr "" msgid "Cloud attack" msgstr "" msgid "Decreases enemy's morale by " msgstr "" msgid "% chance to halve enemy" msgstr "" msgid "Soul Eater" msgstr "" msgid "Elemental" msgstr "" msgid "No Morale" msgstr "" msgid "200% damage from Fire spells" msgstr "" msgid "200% damage from Cold spells" msgstr "" msgid "% damage from %{spell} spell" msgstr "" msgid "% immunity to " msgstr "" msgid "Lightning" msgstr "" msgid "% damage from " msgstr "" msgid "The three Anduran artifacts magically combine into one." msgstr "" msgid "View Spells" msgstr "" msgid "View %{name} Info" msgstr "" msgid "Move %{name}" msgstr "" msgid "Cannot move the Spellbook" msgstr "" msgid "This item can't be traded." msgstr "" msgid "Invalid Artifact" msgstr "" #, fuzzy msgid "The %{name} increases the hero's knowledge by %{count}." msgstr "De %{name} vuurt met de kracht van %{count} Boogschutters" msgid "Ultimate Book of Knowledge" msgstr "" #, fuzzy msgid "The %{name} increases the hero's attack skill by %{count}." msgstr "De %{name} vuurt met de kracht van %{count} Boogschutters" msgid "Ultimate Sword of Dominion" msgstr "" #, fuzzy msgid "The %{name} increases the hero's defense skill by %{count}." msgstr "De %{name} vuurt met de kracht van %{count} Boogschutters" msgid "Ultimate Cloak of Protection" msgstr "" #, fuzzy msgid "The %{name} increases the hero's spell power by %{count}." msgstr "De %{name} vuurt met de kracht van %{count} Boogschutters" msgid "Ultimate Wand of Magic" msgstr "" msgid "" "The %{name} increases the hero's attack and defense skills by %{count} each." msgstr "" msgid "Ultimate Shield" msgstr "" #, fuzzy msgid "" "The %{name} increases the hero's spell power and knowledge by %{count} each." msgstr "De %{name} vuurt met de kracht van %{count} Boogschutters" msgid "Ultimate Staff" msgstr "" msgid "" "The %{name} increases each of the hero's basic skills by %{count} points." msgstr "" msgid "Ultimate Crown" msgstr "" msgid "Golden Goose" msgstr "" msgid "The %{name} brings in an income of %{count} gold per day." msgstr "" msgid "Arcane Necklace of Magic" msgstr "" msgid "" "After rescuing a Sorceress from a cursed tomb, she rewards your heroism with " "an exquisite jeweled necklace." msgstr "" msgid "Caster's Bracelet of Magic" msgstr "" msgid "" "While searching through the rubble of a caved-in mine, you free a group of " "trapped Dwarves. Grateful, the leader gives you a golden bracelet." msgstr "" msgid "Mage's Ring of Power" msgstr "" msgid "" "A cry of pain leads you to a Centaur, caught in a trap. Upon setting the " "creature free, he hands you a small pouch. Emptying the contents, you find a " "dazzling jeweled ring." msgstr "" msgid "Witch's Broach of Magic" msgstr "" msgid "" "Alongside the remains of a burnt witch lies a beautiful broach, intricately " "designed. Approaching the corpse with caution, you add the broach to your " "inventory." msgstr "" msgid "Medal of Valor" msgstr "" #, fuzzy msgid "The %{name} increases the morale of the hero's army by %{count}." msgstr "De %{name} vuurt met de kracht van %{count} Boogschutters" msgid "" "Freeing a virtuous maiden from the clutches of an evil overlord, you are " "granted a Medal of Valor by the King's herald." msgstr "" msgid "Medal of Courage" msgstr "" msgid "" "After saving a young boy from a vicious pack of Wolves, you return him to " "his father's manor. The grateful nobleman awards you with a Medal of Courage." msgstr "" msgid "Medal of Honor" msgstr "" msgid "" "After freeing a princess of a neighboring kingdom from the evil clutches of " "despicable slavers, she awards you with a Medal of Honor." msgstr "" msgid "Medal of Distinction" msgstr "" msgid "" "Ridding the countryside of the hideous Minotaur who made a sport of eating " "noblemen's Knights, you are honored with the Medal of Distinction." msgstr "" msgid "Fizbin of Misfortune" msgstr "" msgid "" "The %{name} greatly decreases the morale of the hero's army by %{count}." msgstr "" msgid "" "You stumble upon a medal lying alongside the empty road. Adding the medal to " "your inventory, you become aware that you have acquired the undesirable " "Fizbin of Misfortune, greatly decreasing your army's morale." msgstr "" msgid "Thunder Mace of Dominion" msgstr "" msgid "" "During a sudden storm, a bolt of lightning strikes a tree, splitting it. " "Inside the tree you find a mysterious mace." msgstr "" msgid "Armored Gauntlets of Protection" msgstr "" #, fuzzy msgid "The %{name} increase the hero's defense skill by %{count}." msgstr "De %{name} vuurt met de kracht van %{count} Boogschutters" msgid "" "You encounter the infamous Black Knight! After a grueling duel ending in a " "draw, the Knight, out of respect, offers you a pair of armored gauntlets." msgstr "" msgid "Defender Helm of Protection" msgstr "" msgid "" "A glint of golden light catches your eye. Upon further investigation, you " "find a golden helm hidden under a bush." msgstr "" msgid "Giant Flail of Dominion" msgstr "" msgid "" "A clumsy Giant has killed himself with his own flail. Knowing your superior " "skill with this weapon, you confidently remove the spectacular flail from " "the fallen Giant." msgstr "" msgid "Ballista of Quickness" msgstr "" msgid "The %{name} gives the hero's catapult one extra shot per combat round." msgstr "" msgid "" "Walking through the ruins of an ancient walled city, you find the instrument " "of the city's destruction, an elaborately crafted ballista." msgstr "" msgid "Stealth Shield of Protection" msgstr "" msgid "" "A stone statue of a warrior holds a silver shield. As you remove the shield, " "the statue crumbles into dust." msgstr "" msgid "Dragon Sword of Dominion" msgstr "" msgid "" "As you are walking along a narrow path, a nearby bush suddenly bursts into " "flames. Before your eyes the flames become the image of a beautiful woman. " "She holds out a magnificent sword to you." msgstr "" msgid "Power Axe of Dominion" msgstr "" msgid "" "You see a silver axe embedded deeply in the ground. After several " "unsuccessful attempts by your army to remove the axe, you tightly grip the " "handle of the axe and effortlessly pull it free." msgstr "" msgid "Divine Breastplate of Protection" msgstr "" msgid "" "A gang of Rogues is sifting through the possessions of dead warriors. " "Scaring off the scavengers, you note the Rogues had overlooked a beautiful " "breastplate." msgstr "" msgid "Minor Scroll of Knowledge" msgstr "" msgid "" "Before you appears a levitating glass case with a scroll, perched upon a bed " "of crimson velvet. At your touch, the lid opens and the scroll floats into " "your awaiting hands." msgstr "" msgid "Major Scroll of Knowledge" msgstr "" msgid "" "Visiting a local wiseman, you explain the intent of your journey. He reaches " "into a sack and withdraws a yellowed scroll and hands it to you." msgstr "" msgid "Superior Scroll of Knowledge" msgstr "" msgid "" "You come across the remains of an ancient Druid. Bones, yellowed with age, " "peer from the ragged folds of her robe. Searching the robe, you discover a " "scroll hidden in the folds." msgstr "" msgid "Foremost Scroll of Knowledge" msgstr "" msgid "" "Mangled bones, yellowed with age, peer from the ragged folds of a dead " "Druid's robe. Searching the robe, you discover a scroll hidden within." msgstr "" msgid "Endless Sack of Gold" msgstr "" #, fuzzy msgid "The %{name} provides the hero with %{count} gold per day." msgstr "De %{name} vuurt met de kracht van %{count} Boogschutters" msgid "" "A little leprechaun dances gleefully around a magic sack. Seeing you " "approach, he stops in mid-stride. The little man screams and stamps his foot " "ferociously, vanishing into thin air. Remembering the old leprechaun saying " "'Finders Keepers', you grab the sack and leave." msgstr "" msgid "Endless Bag of Gold" msgstr "" msgid "" "A noblewoman, separated from her traveling companions, asks for your help. " "After escorting her home, she rewards you with a bag filled with gold." msgstr "" msgid "Endless Purse of Gold" msgstr "" msgid "" "In your travels, you find a leather purse filled with gold that once " "belonged to a great warrior king who had the ability to transform any " "inanimate object into gold." msgstr "" msgid "Nomad Boots of Mobility" msgstr "" msgid "The %{name} increase the hero's movement on land." msgstr "" msgid "" "A Nomad trader seeks protection from a tribe of Goblins. For your " "assistance, he gives you a finely crafted pair of boots made from the " "softest leather. Looking closely, you see fascinating ancient carvings " "engraved on the leather." msgstr "" msgid "Traveler's Boots of Mobility" msgstr "" msgid "" "Discovering a pair of beautifully beaded boots made from the finest and " "softest leather, you thank the anonymous donor and add the boots to your " "inventory." msgstr "" msgid "Lucky Rabbit's Foot" msgstr "" #, fuzzy msgid "The %{name} increases the luck of the hero's army by %{count}." msgstr "De %{name} vuurt met de kracht van %{count} Boogschutters" msgid "" "A traveling merchant offers you a rabbit's foot, made of gleaming silver " "fur, for safe passage. The merchant explains the charm will increase your " "luck in combat." msgstr "" msgid "Golden Horseshoe" msgstr "" msgid "" "An ensnared Unicorn whinnies in fright. Murmuring soothing words, you set " "her free. Snorting and stamping her front hoof once, she gallops off. " "Looking down you see a golden horseshoe." msgstr "" msgid "Gambler's Lucky Coin" msgstr "" msgid "" "You have captured a mischievous imp who has been terrorizing the region. In " "exchange for his release, he rewards you with a magical coin." msgstr "" msgid "Four-Leaf Clover" msgstr "" msgid "" "In the middle of a patch of dead and dry vegetation, to your surprise you " "find a healthy green four-leaf clover." msgstr "" msgid "True Compass of Mobility" msgstr "" msgid "The %{name} increases the hero's movement on land and sea." msgstr "" msgid "" "An old man claiming to be an inventor asks you to try his latest invention. " "He then hands you a compass." msgstr "" msgid "Sailor's Astrolabe of Mobility" msgstr "" msgid "The %{name} increases the hero's movement on sea." msgstr "" msgid "" "An old sea captain is being tortured by Ogres. You save him, and in return " "he rewards you with a wondrous instrument to measure the distance of a star." msgstr "" msgid "Evil Eye" msgstr "" msgid "The %{name} reduces the casting cost of curse spells by half." msgstr "" msgid "" "While venturing into a decrepit hut you find the Skeleton of a long dead " "witch. Investigation of the remains reveals a glass eye rolling around " "inside an empty skull." msgstr "" msgid "Enchanted Hourglass" msgstr "" msgid "" "The %{name} extends the duration of all the hero's spells by %{count} turns." msgstr "" msgid "" "A surprise turn in the landscape finds you in the midst of a grisly scene: " "Vultures picking at the aftermath of a terrible battle. Your cursory search " "of the remains turns up an enchanted hourglass." msgstr "" msgid "Gold Watch" msgstr "" msgid "The %{name} doubles the effectiveness of the hero's hypnotize spells." msgstr "" msgid "" "In reward for helping his cart out of a ditch, a traveling potion salesman " "gives you a \"magic\" gold watch. Unbeknownst to him, the watch really is " "magical." msgstr "" msgid "Skullcap" msgstr "" msgid "The %{name} halves the casting cost of all mind influencing spells." msgstr "" msgid "" "A brief stop at an improbable rural inn yields an exchange of money, tales, " "and accidentally, luggage. You find a magical skullcap in your new backpack." msgstr "" msgid "Ice Cloak" msgstr "" msgid "" "The %{name} halves all damage the hero's troops receive from cold spells." msgstr "" msgid "" "Responding to the panicked cries of a damsel in distress, you discover a " "young woman fleeing from a hungry bear. You slay the beast in the nick of " "time, and the grateful Sorceress weaves a magic cloak from the bear's hide." msgstr "" msgid "Fire Cloak" msgstr "" msgid "" "The %{name} halves all damage the hero's troops receive from fire spells." msgstr "" msgid "" "You've come upon a fight between a Necromancer and a Paladin. The " "Necromancer blasts the Paladin with a fire bolt, bringing him to his knees. " "Acting quickly, you slay the evil one before the final blow. The grateful " "Paladin gives you the fire cloak that saved him." msgstr "" msgid "Lightning Helm" msgstr "" msgid "" "The %{name} halves all damage the hero's troops receive from lightning " "spells." msgstr "" msgid "" "A traveling tinker in need of supplies offers you a helm with a thunderbolt " "design on its top in exchange for food and water. Curious, you accept, and " "later find out that the helm is magical." msgstr "" msgid "Evercold Icicle" msgstr "" msgid "" "The %{name} causes the hero's cold spells to do %{count} percent more damage " "to enemy troops." msgstr "" msgid "" "An icicle withstanding the full heat of the noonday sun attracts your " "attention. Intrigued, you break it off, and find that it does not melt in " "your hand." msgstr "" msgid "Everhot Lava Rock" msgstr "" msgid "" "The %{name} causes the hero's fire spells to do %{count} percent more damage " "to enemy troops." msgstr "" msgid "" "Your wanderings bring you into contact with a tribe of ape-like beings using " "a magical lava rock that never cools to light their fires. You take pity on " "them and teach them to make fire with sticks. Believing you to be a god, the " "apes give you their rock." msgstr "" msgid "Lightning Rod" msgstr "" msgid "" "The %{name} causes the hero's lightning spells to do %{count} percent more " "damage to enemy troops." msgstr "" msgid "" "While waiting out a storm, a lighting bolt strikes a nearby cottage's " "lightning rod, which melts and falls to the ground. The tip of the rod, " "however, survives intact and makes your hair stand on end when you touch it. " "Hmm..." msgstr "" msgid "Snake-Ring" msgstr "" msgid "The %{name} halves the casting cost of all of the hero's bless spells." msgstr "" msgid "" "You've found an oddly shaped ring on the finger of a long dead traveler. The " "ring looks like a snake biting its own tail." msgstr "" msgid "Ankh" msgstr "" msgid "" "The %{name} doubles the effectiveness of all of the hero's resurrect and " "animate spells." msgstr "" msgid "" "A fierce windstorm reveals the entrance to a buried tomb. Your investigation " "reveals that the tomb has already been looted, but the thieves overlooked an " "ankh on a silver chain in the dark." msgstr "" msgid "Book of Elements" msgstr "" msgid "" "The %{name} doubles the effectiveness of all of the hero's summoning spells." msgstr "" msgid "" "You come across a conjurer who begs to accompany you and your army awhile " "for safety. You agree, and he offers as payment a copy of the book of the " "elements." msgstr "" msgid "Elemental Ring" msgstr "" msgid "The %{name} halves the casting cost of all summoning spells." msgstr "" msgid "" "While pausing to rest, you notice a bobcat climbing a short tree to get at a " "crow's nest. On impulse, you climb the tree yourself and scare off the cat. " "When you look in the nest, you find a collection of shiny stones and a ring." msgstr "" msgid "Holy Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to curse spells." msgstr "" msgid "" "In your wanderings you come across a hermit living in a small, tidy hut. " "Impressed with your mission, he takes time out from his meditations to bless " "and give you a charm against curses." msgstr "" msgid "Pendant of Free Will" msgstr "" msgid "The %{name} makes all of the hero's troops immune to hypnotize spells." msgstr "" msgid "" "Responding to cries for help, you find river Sprites making a sport of " "dunking an old man. Feeling vengeful, you rescue the man and drag a Sprite " "onto dry land for awhile. The Sprite, uncomfortable in the air, gives you a " "magic pendant to let him go." msgstr "" msgid "Pendant of Life" msgstr "" msgid "The %{name} makes all of the hero's troops immune to death spells." msgstr "" msgid "" "A brief roadside encounter with a small caravan and a game of knucklebones " "wins a magic pendant. Its former owner says that it protects from " "Necromancers' death spells." msgstr "" msgid "Serenity Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to berserk spells." msgstr "" msgid "" "The sounds of combat draw you to the scene of a fight between an old " "Barbarian and an eight-headed Hydra. Your timely intervention swings the " "battle in favor of the man, and he rewards you with a pendant he used to use " "to calm his mind for battle." msgstr "" msgid "Seeing-eye Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to blindness spells." msgstr "" msgid "" "You come upon a very old woman, long blind from cataracts and dying alone. " "You tend to her final needs and promise a proper burial. Grateful, she gives " "you a magic pendant emblazoned with a stylized eye. It lets you see with " "your eyes closed." msgstr "" msgid "Kinetic Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to paralyze spells." msgstr "" msgid "" "You come across a golem wearing a glowing pendant and blocking your way. " "Acting on a hunch, you cut the pendant from its neck. Deprived of its power " "source, the golem breaks down, leaving you with the magical pendant." msgstr "" msgid "Pendant of Death" msgstr "" msgid "The %{name} makes all of the hero's troops immune to holy spells." msgstr "" msgid "" "A quick and deadly battle with a Necromancer wins you his magical pendant. " "Later, a Wizard tells you that the pendant protects undead under your " "control from holy word spells." msgstr "" msgid "Wand of Negation" msgstr "" msgid "" "The %{name} makes all of the hero's troops immune to dispel magic spells." msgstr "" msgid "" "You meet an old Wizard friend of yours traveling in the opposite direction. " "He presents you with a gift: A wand that prevents the use of the dispel " "magic spell on your allies." msgstr "" msgid "Golden Bow" msgstr "" msgid "" "The %{name} eliminates the %{count} percent penalty for the hero's troops " "shooting past obstacles (e.g. castle walls)." msgstr "" msgid "" "A chance meeting with a famous Archer finds you in a game of knucklebones " "pitting his bow against your horse. You win." msgstr "" msgid "Telescope" msgstr "" msgid "" "The %{name} increases the amount of terrain the hero reveals when " "adventuring by %{count} extra square." msgstr "" msgid "" "A merchant from far away lands trades you a new invention of his people for " "traveling supplies. It makes distant objects appear closer, and he calls " "it...\n" "\n" "a telescope." msgstr "" msgid "Statesman's Quill" msgstr "" msgid "" "The %{name} reduces the cost of surrender to %{count} percent of the total " "cost of troops the hero has in their army." msgstr "" msgid "" "You pause to help a diplomat with a broken axle fix his problem. In " "gratitude, he gives you a writing quill with magical properties which he " "says will \"help people see things your way\"." msgstr "" msgid "Wizard's Hat" msgstr "" #, fuzzy msgid "" "The %{name} increases the duration of the hero's spells by %{count} turns." msgstr "De %{name} vuurt met de kracht van %{count} Boogschutters" msgid "" "You see a Wizard fleeing from a Griffin and riding like the wind. The Wizard " "opens a portal and rides through, getting his hat knocked off by the edge of " "the gate. The Griffin follows; the gate closes. You pick the hat up, dust it " "off, and put it on." msgstr "" msgid "Power Ring" msgstr "" msgid "The %{name} returns %{count} extra spell points per day to the hero." msgstr "" msgid "" "You find a small tree that closely resembles the great Warlock Carnauth with " "a ring around one of its twigs. Scraps of clothing and rotting leather lead " "you to suspect that it IS Carnauth, transformed. Since you can't help him, " "you take the magic ring." msgstr "" msgid "Ammo Cart" msgstr "" msgid "" "The %{name} provides endless ammunition for all of the hero's troops that " "shoot." msgstr "" msgid "" "An ammunition cart in the middle of an old battlefield catches your eye. " "Inspection shows it to be in good working order, so you take it along." msgstr "" msgid "Tax Lien" msgstr "" #, fuzzy msgid "The %{name} costs the hero %{count} gold pieces per day." msgstr "De %{name} vuurt met de kracht van %{count} Boogschutters" msgid "" "Your big spending habits have earned you a massive tax bill that you can't " "hope to pay. The tax man takes pity and agrees to only take 250 gold a day " "from your account for life. Check here if you want one dollar to go to the " "presidential campaign election fund." msgstr "" msgid "Hideous Mask" msgstr "" msgid "The %{name} prevents all 'wandering' armies from joining the hero." msgstr "" msgid "" "Your looting of the grave of Sinfilas Gardolad, the famous shapeshifting " "Warlock, unearths his fabled mask. Trembling, you put it on and it twists " "your visage into an awful grimace! Oh no! It's actually the hideous mask of " "Gromluck Greene, and you are stuck with it." msgstr "" msgid "Endless Pouch of Sulfur" msgstr "" msgid "The %{name} provides %{count} unit of sulfur per day." msgstr "" msgid "" "You visit an alchemist who, upon seeing your army, is swayed by the " "righteousness of your cause. The newly loyal subject gives you his endless " "pouch of sulfur to help with the war effort." msgstr "" msgid "Endless Vial of Mercury" msgstr "" msgid "The %{name} provides %{count} unit of mercury per day." msgstr "" msgid "" "A brief stop at a hastily abandoned Wizard's tower turns up a magical vial " "of mercury that always has a little left on the bottom. Recognizing a " "treasure when you see one, you cap it and slip it in your pocket." msgstr "" msgid "Endless Pouch of Gems" msgstr "" msgid "The %{name} provides %{count} unit of gems per day." msgstr "" msgid "" "A short rainstorm brings forth a rainbow...and you can see the end of it. " "Riding quickly, you seize the pot of gold you find there. The leprechaun who " "owns it, unable to stop you from taking it, offers an endless pouch of gems " "for the return of his gold. You accept." msgstr "" msgid "Endless Cord of Wood" msgstr "" msgid "The %{name} provides %{count} unit of wood per day." msgstr "" msgid "" "Pausing to rest and light a cook fire, you pull wood out of a nearby pile of " "dead wood. As you keep pulling wood from the pile, you notice that it " "doesn't shrink. You realize to your delight that the wood is enchanted, so " "you take it along." msgstr "" msgid "Endless Cart of Ore" msgstr "" msgid "The %{name} provides %{count} unit of ore per day." msgstr "" msgid "" "You've found a Goblin weapon smithy making weapons for use against humans. " "With a tremendous yell you and your army descend upon their camp and drive " "them away. A search finds a magic ore cart that never runs out of iron." msgstr "" msgid "Endless Pouch of Crystal" msgstr "" msgid "The %{name} provides %{count} unit of crystal per day." msgstr "" msgid "" "Taking shelter from a storm in a small cave, you notice a small patch of " "crystal in one corner. Curious, you break a piece off and notice that the " "original crystal grows the lost piece back. You decide to stuff the entire " "patch into a pouch and take it with you." msgstr "" msgid "Spiked Helm" msgstr "" msgid "" "Your army is ambushed by a small tribe of wild (and none too bright) Orcs. " "You fend them off easily and the survivors flee in all directions. One of " "the Orcs was wearing a polished spiked helm. Figuring it will make a good " "souvenir, you take it." msgstr "" msgid "Spiked Shield" msgstr "" msgid "" "You come upon a bridge spanning a dry gully. Before you can cross, a Troll " "steps out from under the bridge and demands payment before it will permit " "you to pass. You refuse, and the Troll charges, forcing you to slay it. You " "take its spiked shield as a trophy." msgstr "" msgid "White Pearl" msgstr "" msgid "" "A walk across a dry saltwater lake bed yields an unlikely prize: A white " "pearl amidst shattered shells and debris." msgstr "" msgid "Black Pearl" msgstr "" msgid "" "Rumors of a Griffin of unusual size preying upon the countryside lead you to " "its cave lair. A quick, brutal fight dispatches the beast, and a search of " "its foul nest turns up a huge black pearl." msgstr "" msgid "Magic Book" msgstr "" #, fuzzy msgid "The %{name} enables the hero to cast spells." msgstr "De %{name} weerstaat de spreuk!" msgid "Dummy 1" msgstr "" msgid "The reserved artifact." msgstr "" msgid "Dummy 2" msgstr "" msgid "Dummy 3" msgstr "" msgid "Dummy 4" msgstr "" msgid "Spell Scroll" msgstr "" msgid "" "This %{name} gives the hero the ability to cast the %{spell} spell if the " "hero has a Magic Book." msgstr "" msgid "" "You find an elaborate container which houses an old vellum scroll. The runes " "on the container are very old, and the artistry with which it was put " "together is stunning. As you pull the scroll out, you feel imbued with " "magical power." msgstr "" msgid "Arm of the Martyr" msgstr "" msgid "" "The %{name} increases the hero's spell power by %{count} but adds the undead " "morale penalty." msgstr "" msgid "" "One of the less intelligent members of your party picks up an arm off of the " "ground. Despite its missing a body, it is still moving. Your troops find the " "dismembered arm repulsive, but you cannot bring yourself to drop it: it " "seems to hold some sort of magical power that influences your decision " "making." msgstr "" msgid "Breastplate of Anduran" msgstr "" #, fuzzy msgid "The %{name} increases the hero's defense by %{count}." msgstr "De %{name} vuurt met de kracht van %{count} Boogschutters" msgid "" "You come upon a sign. It reads: \"Here lies the body of Anduran. Bow and " "swear fealty, and you shall be rewarded.\" You decide to do as it says. As " "you stand up, you feel a coldness against your skin. Looking down, you find " "that you are suddenly wearing a gleaming, ornate breastplate." msgstr "" msgid "Broach of Shielding" msgstr "" msgid "" "The %{name} provides %{count} percent protection from Armageddon and " "Elemental Storm, but decreases spell power by 2." msgstr "" msgid "" "A kindly Sorceress thinks that your army's defenses could use a magical " "boost. She offers to enchant the Broach that you wear on your cloak, and you " "accept." msgstr "" msgid "Battle Garb of Anduran" msgstr "" msgid "" "The %{name} combines the powers of the three Anduran artifacts. It provides " "maximum luck and morale for the hero's troops and gives the hero the Town " "Portal spell." msgstr "" msgid "" "Out of pity for a poor peasant, you purchase a chest of old junk they are " "hawking for too much gold. Later, as you search through it, you find it " "contains the 3 pieces of the legendary battle garb of Anduran!" msgstr "" msgid "Crystal Ball" msgstr "" msgid "" "The %{name} lets the hero get more specific information about monsters, " "enemy heroes, and castles nearby the hero." msgstr "" msgid "" "You come upon a caravan of gypsies who are feasting and fortifying their " "bodies with mead. They call you forward and say \"If you prove that you can " "dance the Rama-Buta, we will reward you.\" You don't know it, but try " "anyway. They laugh hysterically, but admire your bravery, giving you a " "Crystal Ball." msgstr "" msgid "Heart of Fire" msgstr "" msgid "" "The %{name} provides %{count} percent protection from fire, but doubles the " "damage taken from cold." msgstr "" msgid "" "You enter a recently burned glade and come upon a Fire Elemental sitting " "atop a rock. It looks up, its flaming face contorted in a look of severe " "pain. It then tosses a glowing object at you. You put up your hands to block " "it, but it passes right through them and sears itself into your chest." msgstr "" msgid "Heart of Ice" msgstr "" msgid "" "The %{name} provides %{count} percent protection from cold, but doubles the " "damage taken from fire." msgstr "" msgid "" "Suddenly, a biting coldness engulfs your body. You seize up, falling from " "your horse. The pain subsides, but you still feel as if your chest is " "frozen. As you pick yourself up off of the ground, you hear hearty laughter. " "You turn around just in time to see a Frost Giant run off into the woods and " "disappear." msgstr "" msgid "Helmet of Anduran" msgstr "" msgid "" "You spy a gleaming object poking up out of the ground. You send a member of " "your party over to investigate. He comes back with a golden helmet in his " "hands. You realize that it must be the helmet of the legendary Anduran, the " "only man who was known to wear solid gold armor." msgstr "" msgid "Holy Hammer" msgstr "" msgid "" "You come upon a battle where a Paladin has been mortally wounded by a group " "of Zombies. He asks you to take his hammer and finish what he started. As " "you pick it up, it begins to hum, and then everything becomes a blur. The " "Zombies lie dead, the hammer dripping with blood. You strap it to your belt." msgstr "" msgid "Legendary Scepter" msgstr "" msgid "The %{name} adds %{count} points to all attributes." msgstr "" msgid "" "Upon cresting a small hill, you come upon a ridiculous looking sight. A " "Sprite is attempting to carry a Scepter that is almost as big as it is. " "Trying not to laugh, you ask, \"Need help?\" The Sprite glares at you and " "answers: \"You think this is funny? Fine. You can carry it. I much prefer " "flying anyway.\"" msgstr "" msgid "" "An old seaman tells you a tale of an enchanted masthead that he used in his " "youth to rally his crew during times of trouble. He then hands you a faded " "map that shows where he hid it. After much exploring, you find it stashed " "underneath a nearby dock." msgstr "" msgid "Masthead" msgstr "" msgid "" "The %{name} boosts the hero's troops' luck and morale by %{count} each in " "sea combat." msgstr "" msgid "Sphere of Negation" msgstr "" msgid "The %{name} disables all spell casting, for both sides, in combat." msgstr "" msgid "" "You stop to help a Peasant catch a runaway mare. To show his gratitude, he " "hands you a tiny sphere. As soon as you grasp it, you feel the magical " "energy drain from your limbs..." msgstr "" msgid "Staff of Wizardry" msgstr "" #, fuzzy msgid "The %{name} boosts the hero's spell power by %{count}." msgstr "De %{name} vuurt met de kracht van %{count} Boogschutters" msgid "" "While out scaring up game, your troops find a mysterious staff levitating " "about three feet off of the ground. They hand it to you, and you notice an " "inscription. It reads: \"Brains best brawn and magic beats might. Heed my " "words, and you'll win every fight.\"" msgstr "" msgid "Sword Breaker" msgstr "" msgid "The %{name} increases the hero's defense by %{count} and attack by 1." msgstr "" msgid "" "A former Captain of the Guard admires your quest and gives you the enchanted " "Sword Breaker that he relied on during his tour of duty." msgstr "" msgid "Sword of Anduran" msgstr "" msgid "" "A Troll stops you and says: \"Pay me 5,000 gold, or the Sword of Anduran " "will slay you where you stand.\" You refuse. The troll grabs the sword " "hanging from its belt, screams in pain, and runs away. Picking up the fabled " "sword, you give thanks that half-witted Trolls tend to grab the wrong end of " "sharp objects." msgstr "" msgid "Spade of Necromancy" msgstr "" msgid "The %{name} gives the hero increased necromancy skill." msgstr "" msgid "" "A dirty shovel has been thrust into a dirt mound nearby. Upon investigation, " "you discover it to be the enchanted shovel of the Gravediggers, long thought " "lost by mortals." msgstr "" msgid "spellBonus|selected by user" msgstr "" msgid "Wood" msgstr "" msgid "Mercury" msgstr "" msgid "Ore" msgstr "" msgid "Sulfur" msgstr "" msgid "Crystal" msgstr "" msgid "Gems" msgstr "" msgid "Gold" msgstr "" msgid "" "There are seven resources in Heroes 2, used to build and improves castles, " "purchase troops and recruit heroes. Gold is the most common, required for " "virtually everything. Wood and ore are used for most buildings. Gems, " "Mercury, Sulfur and Crystal are rare magical resources used for the most " "powerful creatures and buildings." msgstr "" msgid "" "Causes a giant fireball to strike the selected area, damaging all nearby " "creatures." msgstr "" msgid "Fireball" msgstr "" msgid "Fireblast" msgstr "" msgid "" "An improved version of fireball, fireblast affects two hexes around the " "center point of the spell, rather than one." msgstr "" msgid "Causes a bolt of electrical energy to strike the selected creature." msgstr "" msgid "Lightning Bolt" msgstr "" msgid "Chain Lightning" msgstr "" msgid "" "Causes a bolt of electrical energy to strike a selected creature, then " "strike the nearest creature with half damage, then strike the NEXT nearest " "creature with half again damage, and so on, until it becomes too weak to be " "harmful. Warning: This spell can hit your own creatures!" msgstr "" msgid "Teleport" msgstr "" msgid "" "Teleports the creature you select to any open position on the battlefield." msgstr "" msgid "Cure" msgstr "" msgid "" "Removes all negative spells cast upon one of your units, and restores up to " "%{count} HP per level of spell power." msgstr "" msgid "Mass Cure" msgstr "" msgid "" "Removes all negative spells cast upon your forces, and restores up to " "%{count} HP per level of spell power, per creature." msgstr "" msgid "Resurrect" msgstr "" msgid "Resurrects creatures from a damaged or dead unit until end of combat." msgstr "" msgid "Resurrect True" msgstr "" msgid "Resurrects creatures from a damaged or dead unit permanently." msgstr "" msgid "Haste" msgstr "" msgid "Increases the speed of any creature by %{count}." msgstr "" msgid "Increases the speed of all of your creatures by %{count}." msgstr "" msgid "Mass Haste" msgstr "" msgid "Slows target to half movement rate." msgstr "" msgid "spell|Slow" msgstr "" msgid "Mass Slow" msgstr "" msgid "Slows all enemies to half movement rate." msgstr "" msgid "Clouds the affected creatures' eyes, preventing them from moving." msgstr "" msgid "spell|Blind" msgstr "" msgid "Bless" msgstr "" msgid "Causes the selected creatures to inflict maximum damage." msgstr "" msgid "Causes all of your units to inflict maximum damage." msgstr "" msgid "Mass Bless" msgstr "" msgid "Magically increases the defense skill of the selected creatures." msgstr "" msgid "Stoneskin" msgstr "" msgid "" "Increases the defense skill of the targeted creatures. This is an improved " "version of Stoneskin." msgstr "" msgid "Steelskin" msgstr "" msgid "Causes the selected creatures to inflict minimum damage." msgstr "" msgid "Curse" msgstr "" msgid "Causes all enemy troops to inflict minimum damage." msgstr "" msgid "Mass Curse" msgstr "" msgid "Damages all undead in the battle." msgstr "" msgid "Holy Word" msgstr "" msgid "" "Damages all undead in the battle. This is an improved version of Holy Word." msgstr "" msgid "Holy Shout" msgstr "" msgid "Anti-Magic" msgstr "" msgid "Prevents harmful magic against the selected creatures." msgstr "" msgid "Dispel Magic" msgstr "" msgid "Removes all magic spells from a single target." msgstr "" msgid "Mass Dispel" msgstr "" msgid "Removes all magic spells from all creatures." msgstr "" msgid "Causes a magic arrow to strike the selected target." msgstr "" msgid "Magic Arrow" msgstr "" msgid "Berserker" msgstr "" msgid "Causes a creature to attack its nearest neighbor." msgstr "" msgid "Armageddon" msgstr "" msgid "" "Holy terror strikes the battlefield, causing severe damage to all creatures." msgstr "" msgid "Elemental Storm" msgstr "" msgid "Magical elements pour down on the battlefield, damaging all creatures." msgstr "" msgid "" "A rain of rocks strikes an area of the battlefield, damaging all nearby " "creatures." msgstr "" msgid "Meteor Shower" msgstr "" msgid "Paralyze" msgstr "" msgid "The targeted creatures are paralyzed, unable to move or retaliate." msgstr "" msgid "Hypnotize" msgstr "" msgid "" "Brings a single enemy unit under your control if its hits are less than " "%{count} times the caster's spell power." msgstr "" msgid "Cold Ray" msgstr "" msgid "Drains body heat from a single enemy unit." msgstr "" msgid "Cold Ring" msgstr "" msgid "" "Drains body heat from all units surrounding the center point, but not " "including the center point." msgstr "" msgid "Disrupting Ray" msgstr "" msgid "Reduces the defense rating of an enemy unit by three." msgstr "" msgid "Damages all living (non-undead) units in the battle." msgstr "" msgid "Death Ripple" msgstr "" msgid "Death Wave" msgstr "" msgid "" "Damages all living (non-undead) units in the battle. This spell is an " "improved version of Death Ripple." msgstr "" msgid "Dragon Slayer" msgstr "" msgid "Greatly increases a unit's attack skill vs. Dragons." msgstr "" msgid "Blood Lust" msgstr "" msgid "Increases a unit's attack skill." msgstr "" msgid "Animate Dead" msgstr "" msgid "Resurrects creatures from a damaged or dead undead unit permanently." msgstr "" msgid "Mirror Image" msgstr "" msgid "" "Creates an illusionary unit that duplicates one of your existing units. This " "illusionary unit does the same damages as the original, but will vanish if " "it takes any damage." msgstr "" msgid "Shield" msgstr "" msgid "" "Halves damage received from ranged attacks for a single unit. Does not " "affect damage received from Turrets or Ballistae." msgstr "" msgid "Mass Shield" msgstr "" msgid "" "Halves damage received from ranged attacks for all of your units. Does not " "affect damage received from Turrets or Ballistae." msgstr "" msgid "Summon Earth Elemental" msgstr "" msgid "Summons Earth Elementals to fight for your army." msgstr "" msgid "Summon Air Elemental" msgstr "" msgid "Summons Air Elementals to fight for your army." msgstr "" msgid "Summon Fire Elemental" msgstr "" msgid "Summons Fire Elementals to fight for your army." msgstr "" msgid "Summon Water Elemental" msgstr "" msgid "Summons Water Elementals to fight for your army." msgstr "" msgid "Damages castle walls." msgstr "" msgid "Earthquake" msgstr "" msgid "Causes all mines across the land to become visible." msgstr "" msgid "View Mines" msgstr "" msgid "Causes all resources across the land to become visible." msgstr "" msgid "View Resources" msgstr "" msgid "Causes all artifacts across the land to become visible." msgstr "" msgid "View Artifacts" msgstr "" msgid "Causes all towns and castles across the land to become visible." msgstr "" msgid "View Towns" msgstr "" msgid "Causes all Heroes across the land to become visible." msgstr "" msgid "View Heroes" msgstr "" msgid "Causes the entire land to become visible." msgstr "" msgid "View All" msgstr "" msgid "Allows the caster to view detailed information on enemy Heroes." msgstr "" msgid "Summon Boat" msgstr "" msgid "" "Summons the nearest unoccupied, friendly boat to an adjacent shore location. " "A friendly boat is one which you just built or were the most recent player " "to occupy." msgstr "" msgid "Allows the caster to magically transport to a nearby location." msgstr "" msgid "Dimension Door" msgstr "" msgid "Returns the caster to any town or castle currently owned." msgstr "" msgid "Town Gate" msgstr "" msgid "" "Returns the hero to the town or castle of choice, provided it is controlled " "by you." msgstr "" msgid "Visions" msgstr "" msgid "" "Visions predicts the likely outcome of an encounter with a neutral army camp." msgstr "" msgid "Haunt" msgstr "" msgid "" "Haunts a mine you control with Ghosts. This mine stops producing resources. " "(If I can't keep it, nobody will!)" msgstr "" msgid "Set Earth Guardian" msgstr "" msgid "Sets Earth Elementals to guard a mine against enemy armies." msgstr "" msgid "Set Air Guardian" msgstr "" msgid "Sets Air Elementals to guard a mine against enemy armies." msgstr "" msgid "Set Fire Guardian" msgstr "" msgid "Sets Fire Elementals to guard a mine against enemy armies." msgstr "" msgid "Set Water Guardian" msgstr "" msgid "Sets Water Elementals to guard a mine against enemy armies." msgstr "" #, fuzzy msgid "Random Spell" msgstr "Spreuk uitspreken" msgid "Randomly selected spell of any level." msgstr "" msgid "Random 1st Level Spell" msgstr "" msgid "Randomly selected 1st level spell." msgstr "" msgid "Random 2nd Level Spell" msgstr "" msgid "Randomly selected 2nd level spell." msgstr "" msgid "Random 3rd Level Spell" msgstr "" msgid "Randomly selected 3rd level spell." msgstr "" msgid "Random 4th Level Spell" msgstr "" msgid "Randomly selected 4th level spell." msgstr "" msgid "Random 5th Level Spell" msgstr "" msgid "Randomly selected 5th level spell." msgstr "" msgid "Petrification" msgstr "" msgid "" "Turns the affected creature into stone. A petrified creature receives half " "damage from a direct attack." msgstr "" msgid "You have no Magic Book, so you cannot cast a spell." msgstr "" msgid "No spell to cast." msgstr "" msgid "Your hero has %{point} spell points remaining." msgstr "" msgid "View Adventure Spells" msgstr "" msgid "View Combat Spells" msgstr "" msgid "View previous page" msgstr "" msgid "View next page" msgstr "" msgid "Close Spellbook" msgstr "" msgid "View %{spell}" msgstr "" msgid "This spell does %{damage} points of damage." msgstr "" msgid "" "This spell summons\n" "%{count} %{monster}." msgstr "" msgid "This spell restores %{hp} HP." msgstr "" msgid "This spell summons %{count} %{monster} to guard the mine." msgstr "" msgid "The nearest town is %{town}." msgstr "" msgid "This town is occupied by your hero %{hero}." msgstr "" msgid "" "This spell controls up to\n" "%{hp} HP." msgstr "" msgid "The ultimate artifact is really the %{name}." msgstr "" msgid "The ultimate artifact may be found in the %{name} regions of the world." msgstr "" msgid "north-west" msgstr "" msgid "north" msgstr "" msgid "north-east" msgstr "" msgid "west" msgstr "" msgid "center" msgstr "" msgid "east" msgstr "" msgid "south-west" msgstr "" msgid "south" msgstr "" msgid "south-east" msgstr "" msgid "The truth is out there." msgstr "" msgid "The dark side is stronger." msgstr "" msgid "The end of the world is near." msgstr "" msgid "The bones of Lord Slayer are buried in the foundation of the arena." msgstr "" msgid "A Black Dragon will take out a Titan any day of the week." msgstr "" msgid "He told her: Yada yada yada... and then she said: Blah, blah, blah..." msgstr "" msgid "An unknown force is being resurrected..." msgstr "" msgid "" "Check the newest version of the game at\n" "https://github.com/ihhub/\n" "fheroes2/releases" msgstr "" #~ msgid "vs" #~ msgstr "tegen" #~ msgid "%{race2} %{name2}" #~ msgstr "%{race2} %{name2}" fheroes2-1.0.12+dfsg/files/lang/pl.po000066400000000000000000011276311456075706000172520ustar00rootroot00000000000000# Polish translation of fheroes2 # Copyright (C) 2021 - 2022 fheroes2 team # Copyright (C) 2009 Igor Orlov # Copyright (C) 2009 Andrey Afletdinov # This file is distributed under the same license as the fheroes2 package. # msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-03 03:27+0000\n" "PO-Revision-Date: 2023-08-26 23:09+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: https://discord.com/" "channels/733093692860137523/817386375888371772\n" "Language: pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2;\n" "X-Launchpad-Export-Date: 2013-10-09 02:01+0000\n" "X-Generator: Poedit 3.3.2\n" msgid "" "BATTLE\n" "ONLY" msgstr "" "TYLKO\n" "WALKA" msgid "" "NEW\n" "GAME" msgstr "" "NOWA\n" "GRA" msgid "" "SAVE\n" "GAME" msgstr "" "ZAPISZ\n" "GRĘ" msgid "" "LOAD\n" "GAME" msgstr "" "WCZYTAJ\n" "GRĘ" msgid "INFO" msgstr "INFO" msgid "QUIT" msgstr "WYJÅšCIE" msgid "CANCEL" msgstr "ANULUJ" msgid "OKAY" msgstr "OK" msgid "smallerButton|OKAY" msgstr "OK" msgid "ACCEPT" msgstr "ZGÓDŹ SIĘ" msgid "DECLINE" msgstr "ODRZUĆ" msgid "LEARN" msgstr "NAUKA" msgid "TRADE" msgstr "WYMIEŃ" msgid "YES" msgstr "TAK" msgid "NO" msgstr "NIE" msgid "EXIT" msgstr "WYJÅšCIE" #, fuzzy msgid "smallerButton|EXIT" msgstr "OK" msgid "DISMISS" msgstr "ZWOLNIJ" msgid "UPGRADE" msgstr "ULEPSZ" msgid "RESTART" msgstr "OD NOWA" msgid "HEROES" msgstr "BOHATEROWIE" msgid "" "TOWNS/\n" "CASTLES" msgstr "" "MIASTA/\n" "ZAMKI" msgid "S" msgstr "M" msgid "M" msgstr "Åš" msgid "L" msgstr "D" msgid "X-L" msgstr "BD" msgid "ALL" msgstr "CAÅOŚĆ" msgid "SELECT" msgstr "WYBIERZ" msgid "" "STANDARD\n" "GAME" msgstr "" "GRA\n" "STANDARDOWA" msgid "" "CAMPAIGN\n" "GAME" msgstr "KAMPANIA" msgid "" "MULTI-\n" "PLAYER\n" "GAME" msgstr "" "GRA\n" "DLA WIELU\n" "OSÓB" msgid "CONFIG" msgstr "KONFIGURACJA" msgid "" "ORIGINAL\n" "CAMPAIGN" msgstr "" "ORYGINALNA\n" "KAMPANIA" msgid "" "EXPANSION\n" "CAMPAIGN" msgstr "" "KAMPANIA Z\n" "ROZSZERZENIA" msgid "HOT SEAT" msgstr "" "ZMIANA\n" "MIEJSC" msgid "2 PLAYERS" msgstr "2 GRACZY" msgid "3 PLAYERS" msgstr "3 GRACZY" msgid "4 PLAYERS" msgstr "4 GRACZY" msgid "5 PLAYERS" msgstr "5 GRACZY" msgid "6 PLAYERS" msgstr "6 GRACZY" msgid "GIFT" msgstr "WYÅšLIJ" msgid "MAX" msgstr "MAKS" msgid "DIFFICULTY" msgstr "TRUDNOŚĆ" msgid "VIEW INTRO" msgstr "" msgid "MIN" msgstr "MIN" msgid "RESET" msgstr "" #, fuzzy msgid "START" msgstr "OD NOWA" #, fuzzy msgid "CASTLE" msgstr "" "MIASTA/\n" "ZAMKI" msgid "TOWN" msgstr "" msgid "" "D\n" "I\n" "S\n" "M\n" "I\n" "S\n" "S" msgstr "" msgid "" "E\n" "X\n" "I\n" "T" msgstr "" msgid "" "C\n" "A\n" "M\n" "P\n" "A\n" "I\n" "G\n" "N" msgstr "" msgid "" "S\n" "T\n" "A\n" "N\n" "D\n" "A\n" "R\n" "D" msgstr "" msgid "Warrior" msgstr "Wojownik" msgid "Builder" msgstr "Budowniczy" msgid "Explorer" msgstr "Badacz" msgid "None" msgstr "Brak" msgid "" "A few\n" "%{monster}" msgstr "" "MaÅ‚o\n" "%{monster}" msgid "" "Several\n" "%{monster}" msgstr "" "ParÄ™\n" "%{monster}" msgid "" "A pack of\n" "%{monster}" msgstr "" "Grupa\n" "%{monster}" msgid "" "Lots of\n" "%{monster}" msgstr "" "Mnóstwo\n" "%{monster}" msgid "" "A horde of\n" "%{monster}" msgstr "" "Horda\n" "%{monster}" msgid "" "A throng of\n" "%{monster}" msgstr "" "TÅ‚um\n" "%{monster}" msgid "" "A swarm of\n" "%{monster}" msgstr "" "Mrowie\n" "%{monster}" msgid "" "Zounds...\n" "%{monster}" msgstr "" "Setki\n" "%{monster}" msgid "" "A legion of\n" "%{monster}" msgstr "" "Legion\n" "%{monster}" msgid "army|Few" msgstr "MaÅ‚o" msgid "army|Several" msgstr "ParÄ™" msgid "army|Pack" msgstr "Grupa" msgid "army|Lots" msgstr "Mnóstwo" msgid "army|Horde" msgstr "Horda" msgid "army|Throng" msgstr "TÅ‚um" msgid "army|Swarm" msgstr "Mrowie" msgid "army|Zounds" msgstr "Setki" msgid "army|Legion" msgstr "Legion" msgid "All %{race} troops +1" msgstr "Wszystkie %{race} oddziaÅ‚y +1" msgid "Multiple" msgstr "Różne" msgid "Troops of %{count} alignments -%{penalty}" msgstr "Jednostki z %{count} różnych frakcji -%{penalty}" #, fuzzy msgid "Some undead in army -1" msgstr "Nieumarli w armii -1" msgid "View %{name}" msgstr "Obejrzyj jednostkÄ™ (%{name})" msgid "Move the %{name} " msgstr "PrzenieÅ› %{name} " msgid "Move or right click to redistribute %{name}" msgstr "PrzenieÅ› lub kliknij prawym aby rozdzielić %{name}" msgid "Combine %{name} armies" msgstr "Połącz jednostki (%{name})" msgid "Exchange %{name2} with %{name}" msgstr "ZamieÅ„ pozycje jednostek: %{name2} i %{name}" msgid "Select %{name}" msgstr "Wybierz (%{name})" msgid "Cannot move last troop" msgstr "Nie można przenieść ostatniej jednostki" msgid "Move the %{name}" msgstr "PrzenieÅ› %{name}" msgid "Set Count" msgstr "Podaj liczbÄ™" msgid "%{name} destroys half the enemy troops!" msgid_plural "%{name} destroy half the enemy troops!" msgstr[0] "%{name} niszczy poÅ‚owÄ™ stworów wroga!" msgstr[1] "%{name} niszczÄ… poÅ‚owÄ™ stworów wroga!" msgstr[2] "%{name} niszczy poÅ‚owÄ™ stworów wroga!" msgid "%{name} has turned off the auto battle" msgstr "%{name} wyłączyÅ‚ automatycznÄ… walkÄ™" msgid "%{name} has turned on the auto battle" msgstr "%{name} włączyÅ‚ automatycznÄ… walkÄ™" msgid "Spell failed!" msgstr "Czar nieudany!" msgid "" "The Sphere of Negation artifact is in effect for this battle, disabling all " "combat spells." msgstr "" "W czasie bitwy dziaÅ‚a artefakt o nazwie Kula Negacji, uniemożliwiajÄ…cy " "rzucenie wszelkich zaklęć bojowych." msgid "You have already cast a spell this round." msgstr "W tej rundzie zaklÄ™cie zostaÅ‚o już rzucone." msgid "That spell will have no effect!" msgstr "To zaklÄ™cie nie da żadnego efektu!" msgid "You may only summon one type of elemental per combat." msgstr "W czasie walki możesz przywoÅ‚ać tylko jeden rodzaj żywioÅ‚aka." msgid "" "There is no open space adjacent to your hero where you can summon an " "Elemental to." msgstr "" "Obok twojego bohatera nie ma wolnego miejsca gdzie można by przywoÅ‚ać " "Å»ywioÅ‚aki." msgid "" "The Moat reduces by -%{count} the defense skill of any unit and slows to " "half movement rate." msgstr "" "Fosa zmniejsza współczynnik obrony o -%{count} oraz spowalnia jednostki o " "poÅ‚owÄ™." msgid "Speed: %{speed}" msgstr "Szybkość: %{speed}" msgid "Speed" msgstr "Szybkość" msgid "Off" msgstr "Wyłączone" msgid "On" msgstr "Włączone" #, fuzzy msgid "Turn Order" msgstr "Kolejność jednostek" msgid "Auto Spell Casting" msgstr "Auto. czary" msgid "Grid" msgstr "Siatka" msgid "Shadow Movement" msgstr "Cieniowanie pola" msgid "Shadow Cursor" msgstr "Cieniowanie kursora" msgid "Audio" msgstr "DźwiÄ™k" msgid "Settings" msgstr "Ustawienia" msgid "Configure" msgstr "Dostosuj" msgid "Hot Keys" msgstr "Skróty klaw." msgid "Damage Info" msgstr "Obrażenia" msgid "Set the speed of combat actions and animations." msgstr "Pozwala na ustawienie szybkoÅ›ci akcji i animacji w walce." #, fuzzy msgid "Toggle to display the turn order during the battle." msgstr "" "Ukrycie lub pokazanie kolejnoÅ›ci wykonywania akcji przez stwory w czasie " "walki." msgid "" "Toggle whether or not the computer will cast spells for you when auto combat " "is on. (Note: This does not affect spell casting for computer players in any " "way, nor does it affect quick combat.)" msgstr "" "Włączenie tej opcji sprawi, że komputer bÄ™dzie rzucaÅ‚ zaklÄ™cia w trybie " "walki automatycznej. (Uwaga: nie ma to wpÅ‚ywu na rzucanie zaklęć przez " "graczy komputerowych oraz na szybkÄ… walkÄ™.)" msgid "" "Toggle the hex grid on or off. The hex grid always underlies movement, even " "if turned off. This switch only determines if the grid is visible." msgstr "" "Włączenie/wyłączenie siatki pól. Na siatce zawsze widoczna jest trasa ruchu " "jednostki." msgid "" "Toggle on or off shadows showing where your creatures can move and attack." msgstr "" "Włączenie/wyłączenie cieniowania pól, na które może siÄ™ ruszyć twoja " "jednostka." msgid "" "Toggle on or off a shadow showing the current hex location of the mouse " "cursor." msgstr "" "Włączenie/wyłączenie systemu cieniowania zaznaczajÄ…cego pola, nad którymi " "znajduje siÄ™ kursor." msgid "Change the audio settings of the game." msgstr "ZmieÅ„ ustawienia dźwiÄ™kowe gry." msgid "Check and configure all the hot keys present in the game." msgstr "Zobacz i skonfiguruj wszystkie skróty klawiszowe gry." msgid "Toggle to display damage information during the battle." msgstr "Włączenie/wyłączenie informacji o zadanych obrażeniach." msgid "Exit this menu." msgstr "Wyjdź z tego menu." msgid "Okay" msgstr "Ok" msgid "The enemy has surrendered!" msgstr "Przeciwnik siÄ™ poddaÅ‚!" msgid "Their cowardice costs them %{gold} gold." msgstr "" msgid "The enemy has fled!" msgstr "Przeciwnik uciekÅ‚!" msgid "A glorious victory!" msgstr "WspaniaÅ‚e zwyciÄ™stwo!" msgid "For valor in combat, %{name} receives %{exp} experience." msgstr "Za odwagÄ™ w walce, %{name} otrzymuje %{exp} pkt. doÅ›wiadczenia." msgid "The cowardly %{name} flees from battle." msgstr "%{name} tchórzliwie ucieka z pola bitwy." msgid "%{name} surrenders to the enemy, and departs in shame." msgstr "%{name} poddaje siÄ™ wrogowi i odchodzi w haÅ„bie." #, fuzzy msgid "Your forces suffer a bitter defeat, and %{name} abandons your cause." msgstr "Twoje wojska ponoszÄ… dotkliwÄ… klÄ™skÄ™, a %{name} opuszcza ciÄ™." #, fuzzy msgid "Your forces suffer a bitter defeat." msgstr "Twoje wojska ponoszÄ… dotkliwÄ… klÄ™skÄ™." msgid "Battlefield Casualties" msgstr "Ofiary walki" msgid "Attacker" msgstr "AtakujÄ…cy" msgid "Defender" msgstr "ObroÅ„ca" #, fuzzy msgid "Click to leave the battle results." msgstr "Użyj wskazanej rozdzielczoÅ›ci." #, fuzzy msgid "Click to restart the battle in manual mode." msgstr "Kliknij, aby wrócić do głównego menu." msgid "Restart" msgstr "Rozpocznij od nowa" msgid "You have captured an enemy artifact!" msgstr "UdaÅ‚o ci siÄ™ zdobyć artefakt przeciwnika!" msgid "As you reach for the %{name}, it mysteriously disappears." msgstr "Gdy siÄ™gasz po artefakt %{name}, przedmiot w tajemniczy sposób znika." msgid "As your enemy reaches for the %{name}, it mysteriously disappears." msgstr "" "Gdy przeciwnik siÄ™ga po artefakt %{name}, przedmiot w tajemniczy sposób " "znika." msgid "Necromancy!" msgstr "Nekromancja!" msgid "" "Practicing the dark arts of necromancy, you are able to raise %{count} of " "the enemy's dead to return under your service as %{monster}." msgstr "" "KorzystajÄ…c z mrocznej sztuki nekromancji, ożywiasz %{count} martwych " "przeciwników i wcielasz ich do swojej armii jako %{monster}." msgid "%{name} the %{race}" msgstr "%{name}, %{race}" msgid "Captain of %{name}" msgstr "Kapitan, %{name}" msgid "Attack" msgstr "Atak" msgid "Defense" msgstr "Obrona" msgid "Spell Power" msgstr "Moc zaklęć" msgid "Knowledge" msgstr "Wiedza" msgid "Morale" msgstr "Morale" msgid "Luck" msgstr "Szczęście" msgid "Spell Points" msgstr "Pkt. magii" msgid "Hero's Options" msgstr "Opcje bohatera" msgid "Cast Spell" msgstr "Rzuć zaklÄ™cie" msgid "Retreat" msgstr "Ucieczka" msgid "Surrender" msgstr "Kapitulacja" msgid "Cancel" msgstr "Anuluj" msgid "Hero Screen" msgstr "Ekran bohatera" msgid "Captain's Options" msgstr "Opcje kapitana" msgid "" "Cast a magical spell. You may only cast one spell per combat round. The " "round is reset when every creature has had a turn." msgstr "" "Rzucanie magicznego zaklÄ™cia. Możesz rzucić tylko jedno zaklÄ™cie na rundÄ™ " "walki. Runda koÅ„czy siÄ™ gdy wszystkie stworzenia wykonajÄ… ruch." msgid "" "Retreat your hero, abandoning your creatures. Your hero will be available " "for you to recruit again, however, the hero will have only a novice hero's " "forces." msgstr "" "Ucieczka bohatera i porzucenie wojsk. Bohatera bÄ™dzie można nająć ponownie, " "ale bÄ™dzie posiadać jedynie poczÄ…tkowe oddziaÅ‚y." #, fuzzy msgid "" "Surrendering costs gold. However if you pay the ransom, the hero and all of " "his or her surviving creatures will be available to recruit again. The cost " "of surrender is half of the total cost of the non-temporary troops remaining " "in the army." msgstr "" "Za możliwość kapitulacji pÅ‚aci siÄ™ w zÅ‚ocie. JeÅ›li jednak zapÅ‚acisz okup, " "bÄ™dzie można nająć ponownie zarówno bohatera jak i ocalaÅ‚e jednostki." msgid "Open Hero Screen to view full information about the hero." msgstr "Otwórz ekran bohatera, aby zobaczyć peÅ‚ne informacje o nim." msgid "Return to the battle." msgstr "Powrót na pole bitwy." msgid "Not enough gold (%{gold})" msgstr "Brakuje zÅ‚ota (%{gold})" msgid "%{name} states:" msgstr "%{name} oznajmia:" msgid "Captain of %{name} states:" msgstr "Kapitan %{name} oznajmia:" msgid "" "\"I will accept your surrender and grant you and your troops safe passage " "for the price of %{price} gold.\"" msgstr "" "PrzyjmÄ™ twÄ… kapitulacjÄ™ i pozwolÄ™ ci odejść wraz z twÄ… armiÄ… za cenÄ™ " "%{price} szt. zÅ‚ota." msgid "View %{monster} info" msgstr "Informacje o jednostce: %{monster}" msgid "Fly %{monster} here" msgstr "PrzefruÅ„ jednostkÄ… (%{monster}) tutaj" msgid "Move %{monster} here" msgstr "Przemieść jednostkÄ™ (%{monster}) tutaj" msgid "Shoot %{monster}" msgstr "Strzelaj do %{monster}" msgid "(1 shot left)" msgid_plural "(%{count} shots left)" msgstr[0] "(pozostaÅ‚ %{count} strzaÅ‚)" msgstr[1] "(pozostaÅ‚y %{count} strzaÅ‚y)" msgstr[2] "(pozostaÅ‚o %{count} strzałów)" msgid "Attack %{monster}" msgstr "Zaatakuj jednostkÄ™ (%{monster})" msgid "Turn %{turn}" msgstr "Tura %{turn}" msgid "Teleport here" msgstr "Teleportuj w to miejsce" msgid "Invalid teleport destination" msgstr "Teleportacja w to miejsce jest niemożliwa" msgid "Cast %{spell} on %{monster}" msgstr "Rzuć zaklÄ™cie: %{spell} na: %{monster}" msgid "Cast %{spell}" msgstr "Rzuć zaklÄ™cie: %{spell}" msgid "Select spell target" msgstr "Wybierz cel zaklÄ™cia" msgid "View Ballista info" msgstr "Informacje o baliÅ›cie" msgid "Ballista" msgstr "Balista" msgid "Enable auto combat" msgstr "Automatyczna walka" msgid "Allows the computer to fight out the battle for you." msgstr "Pozwala komputerowi rozegrać walkÄ™ za ciebie." msgid "Auto Combat" msgstr "Automatyczna Walka" msgid "Customize system options" msgstr "Dostosuj ustawienia systemowe" msgid "Allows you to customize the combat screen." msgstr "Pozwala na zmianÄ™ wyglÄ…du ekranu walki." msgid "System Options" msgstr "Opcje systemowe" msgid "Skip this unit" msgstr "PomiÅ„ tÄ™ jednostkÄ™" msgid "Skip" msgstr "PomiÅ„" msgid "" "Skips the current creature. The current creature ends its turn and does not " "get to go again until the next round." msgstr "" "Powoduje pominiÄ™cie aktualnej jednostki. Traci ona swojÄ… turÄ™ i może ruszyć " "siÄ™ ponownie dopiero w nastÄ™pnej rundzie." msgid "View Captain's options" msgstr "Opcje kapitana" msgid "View Hero's options" msgstr "Opcje bohatera" msgid "View opposing Captain" msgstr "Obejrzyj wrogiego kapitana" msgid "View opposing Hero" msgstr "Obejrzyj wrogiego bohatera" msgid "Hide logs" msgstr "Ukryj logi" msgid "Show logs" msgstr "Pokaż logi" msgid "Message Bar" msgstr "Pasek WiadomoÅ›ci" msgid "Shows the results of individual monster's actions." msgstr "Pokazuje rezultat akcji wykonanych przez indywidualnÄ… jednostkÄ™." msgid "Are you sure you want to finish the battle in auto mode?" msgstr "Czy na pewno chcesz dokoÅ„czyć walkÄ™ w trybie automatycznym?" msgid "Are you sure you want to enable auto combat?" msgstr "Czy na pewno chcesz włączyć automatycznÄ… walkÄ™?" msgid "%{name} skip their turn." msgstr "%{name} pomija turÄ™." msgid "%{attacker} does %{damage} damage." msgid_plural "%{attacker} do %{damage} damage." msgstr[0] "Stworzenie %{attacker} zadaje %{damage} pkt. obrażeÅ„." msgstr[1] "Stworzenia %{attacker} zadajÄ… %{damage} pkt. obrażeÅ„." msgstr[2] "Stworzenia %{attacker} zadajÄ… %{damage} pkt. obrażeÅ„." msgid "1 creature perishes." msgid_plural "%{count} creatures perish." msgstr[0] "1 stworzenie ginie." msgstr[1] "%{count} stworzenia ginÄ…." msgstr[2] "%{count} stworzeÅ„ ginie." msgid "1 %{defender} perishes." msgid_plural "%{count} %{defender} perish." msgstr[0] "1 %{defender} ginie." msgstr[1] "%{count} %{defender} ginie." msgstr[2] "%{count} %{defender} ginie." msgid "1 soul is incorporated." msgid_plural "%{count} souls are incorporated." msgstr[0] "%{count} dusza wchÅ‚oniÄ™ta." msgstr[1] "%{count} dusze wchÅ‚oniÄ™te." msgstr[2] "%{count} dusz wchÅ‚oniÄ™tych." msgid "1 %{unit} is revived." msgid_plural "%{count} %{unit} are revived." msgstr[0] "%{count} %{unit} zmartwychwstaÅ‚(a)." msgstr[1] "%{count} %{unit} zmartwychwstaÅ‚o(y)." msgstr[2] "%{count} %{unit} zmartwychwstaÅ‚o(y)." msgid "Moved %{monster}: from [%{src}] to [%{dst}]." msgstr "Przeniesiono %{monster} z: [%{src}] do [%{dst}]." msgid "The %{name} resist the spell!" msgstr "%{name} odpierajÄ… zaklÄ™cie!" msgid "%{name} casts %{spell} on the %{troop}." msgstr "%{name} rzuca zaklÄ™cie '%{spell}' na: %{troop}." msgid "%{name} casts %{spell}." msgstr "%{name} rzuca zaklÄ™cie '%{spell}'." msgid "The %{spell} does %{damage} damage to one undead creature." msgstr "Czar %{spell} zadaje %{damage} pkt. obrażeÅ„ nieumarÅ‚ej istocie." msgid "The %{spell} does %{damage} damage to all undead creatures." msgstr "Czar %{spell} zadaje %{damage} pkt. obrażeÅ„ każdej nieumarÅ‚ej istocie." msgid "The %{spell} does %{damage} damage, %{count} creatures perish." msgstr "Czar %{spell} zadaje %{damage} pkt. obrażeÅ„, %{count} stworzeÅ„ ginie." msgid "The %{spell} does %{damage} damage." msgstr "%{spell} zadaje %{damage} pkt. obrażeÅ„." msgid "The %{spell} does %{damage} damage to one living creature." msgstr "%{spell} zadaje %{damage} pkt. obrażeÅ„ żywej istocie." msgid "The %{spell} does %{damage} damage to all living creatures." msgstr "%{spell} zadaje %{damage} pkt. obrażeÅ„ każdej żywej istocie." msgid "The %{attacker}' attack blinds the %{target}!" msgstr "Atak %{attacker} oÅ›lepia %{target}!" msgid "The %{attacker}' gaze turns the %{target} to stone!" msgstr "Spojrzenie %{attacker} zamienia %{target} w kamieÅ„!" msgid "The %{attacker}' curse falls upon the %{target}!" msgstr "%{attacker} przeklina %{target}!" msgid "The %{target} are paralyzed by the %{attacker}!" msgstr "%{target} zostaÅ‚ sparaliżowany przez %{attacker}!" msgid "The %{attacker} dispel all good spells on your %{target}!" msgstr "%{attacker} rozpraszajÄ… pozytywne zaklÄ™cia u %{target}!" msgid "The %{attacker} cast %{spell} on %{target}!" msgstr "%{attacker} rzuca zaklÄ™cie %{spell} na %{target}!" msgid "Bad luck descends on the %{attacker}." msgstr "Tej jednostce (%{attacker}) nie sprzyja los." msgid "Good luck shines on the %{attacker}." msgstr "Tej jednostce (%{attacker}) sprzyja szczęście." msgid "High morale enables the %{monster} to attack again." msgstr "Wysokie morale pozwala jednostce (%{monster}) na dodatkowy atak." msgid "Low morale causes the %{monster} to freeze in panic." msgstr "Niskie morale sprawia, że jednostka (%{monster}) zamiera w panice." msgid "%{tower} does %{damage} damage." msgstr "%{tower} zadaje %{damage} pkt. obrażeÅ„." msgid "The mirror image is created." msgstr "Stworzono lustrzane odbicie." msgid "The mirror image is destroyed!" msgstr "Zniszczono lustrzane odbicie!" msgid "Are you sure you want to interrupt the auto battle?" msgstr "Czy na pewno chcesz przerwać tryb automatyczny?" msgid "Error" msgstr "Błąd" msgid "No spells to cast." msgstr "Brak zaklęć." msgid "Are you sure you want to retreat?" msgstr "Czy na pewno chcesz siÄ™ wycofać?" msgid "Retreat disabled" msgstr "Ucieczka niemożliwa" msgid "Surrender disabled" msgstr "Kapitulacja niemożliwa" msgid "Damage: %{max}" msgstr "Obrażenia: %{max}" msgid "Damage: %{min} - %{max}" msgstr "Obrażenia: %{min} - %{max}" msgid "Perish: %{max}" msgstr "Zabici: %{max}" msgid "Perish: %{min} - %{max}" msgstr "Zabici: %{min} - %{max}" msgid "" "Through eagle-eyed observation, %{name} is able to learn the magic spell " "%{spell}." msgstr "" "DziÄ™ki swemu sokolemu wzrokowi, %{name} nauczyÅ‚ siÄ™ zaklÄ™cia '%{spell}'." msgid "Human" msgstr "CzÅ‚owiek" msgid "AI" msgstr "Komputer" #, fuzzy msgid "Start" msgstr "Rozpocznij od nowa" #, fuzzy msgid "Start the battle." msgstr "Powrót na pole bitwy." msgid "Exit" msgstr "WyjÅ›cie" msgid "Reset" msgstr "" msgid "Reset to default settings." msgstr "" msgid "Please select another hero." msgstr "Wybierz innego bohatera." msgid "Set Attack Skill" msgstr "Ustaw wartość Ataku" msgid "Set Defense Skill" msgstr "Ustaw wartość Obrony" msgid "Set Power Skill" msgstr "Ustaw wartość Mocy zaklęć" msgid "Set Knowledge Skill" msgstr "Ustaw wartość Wiedzy" #, fuzzy msgid "%{race1} %{name1} vs %{race2} %{name2}" msgstr "Spotkanie: %{name1} i %{name2}" msgid "Monsters" msgstr "Potwory" msgid "N/A" msgstr "N/A" msgid "Left Turret" msgstr "Lewa wieżyczka" msgid "Right Turret" msgstr "Prawa wieżyczka" msgid "The %{name} fires with the strength of %{count} Archers" msgstr "%{name} strzela z siłą %{count} Åuczników" msgid "each with a +%{attack} bonus to their attack skill." msgstr "z których każdy ma +%{attack} premii do siÅ‚y ataku." msgid "The %{name} is destroyed." msgstr "%{name} zostaÅ‚a zniszczona." msgid "%{count} %{name} rises from the dead!" msgid_plural "%{count} %{name} rise from the dead!" msgstr[0] "%{count} %{name} powstaje z martwych!" msgstr[1] "%{count} %{name} powstaje z martwych!" msgstr[2] "%{count} %{name} powstaje z martwych!" msgid "Dwarven Alliance" msgstr "Przymierze Krasnolud." msgid "Sorceress Guild" msgstr "Gildia Czarodziejek" msgid "Necromancer Guild" msgstr "Gildia Nekromantów" msgid "Ogre Alliance" msgstr "Przymierze Ogrów" msgid "Dwarfbane" msgstr "Pogromca Krasnolud." msgid "Dragon Alliance" msgstr "Przymierze Smoków" msgid "Elven Alliance" msgstr "Przymierze Elfów" msgid "Kraeger defeated" msgstr "Kraeger pokonany" msgid "Wayward Son" msgstr "KrnÄ…brny Syn" msgid "Uncle Ivan" msgstr "Wuj Ivan" msgid "Annexation" msgstr "Aneksja" msgid "Force of Arms" msgstr "SiÅ‚a oręża" msgid "Save the Dwarves" msgstr "Uratuj Krasnoludy" msgid "Carator Mines" msgstr "Kopalnie Caratora" msgid "Turning Point" msgstr "Punkt Zwrotny" msgid "scenarioName|Defender" msgstr "ObroÅ„ca" msgid "Corlagon's Defense" msgstr "Obrona Corlagona" msgid "The Crown" msgstr "Korona" msgid "The Gauntlet" msgstr "RÄ™kawica" msgid "Betrayal" msgstr "Zdrada" msgid "Final Justice" msgstr "Ostateczna Sprawiedliwość" #, fuzzy msgid "" "Roland needs you to defeat the lords near his castle to begin his war of " "rebellion against his brother. They are not allied with each other, so they " "will spend most of their time fighting with one another. Victory is yours " "when you have defeated all of their castles and heroes." msgstr "" "Aby zacząć powstanie, Roland potrzebuje pomocy w pokonaniu lordów w pobliżu " "jego zamku. Lordowie ci nie sÄ… sprzymierzeni, wiÄ™c wiÄ™kszość czasu spÄ™dzÄ… na " "potyczkach miÄ™dzy sobÄ…. Zwyciężysz, gdy pokonasz wszystkich wrogich " "bohaterów oraz zdobÄ™dziesz ich zamki." msgid "" "The local lords refuse to swear allegiance to Roland, and must be subdued. " "They are wealthy and powerful, so be prepared for a tough fight. Capture all " "enemy castles to win." msgstr "" "Miejscowi wÅ‚adcy odmawiajÄ… zÅ‚ożenia przysiÄ™gi lojalnoÅ›ci Rolandowi i muszÄ… " "zostać ujarzmieni. SÄ… potężni i bogaci, wiÄ™c przygotuj siÄ™ na ciężkÄ… walkÄ™. " "Aby wygrać, zdobÄ…dź wszystkie wrogie zamki." msgid "" "Your task is to defend the Dwarves against Archibald's forces. Capture all " "of the enemy towns and castles to win, and be sure not to lose all of the " "dwarf towns at once, or the enemy will have won." msgstr "" "Twoim zadaniem jest obrona Krasnoludów przed siÅ‚ami Archibalda. Aby " "zwyciężyć, zdobÄ…dź wszystkie zamki i miasta wroga oraz nie pozwól na utratÄ™ " "wszystkich osad krasnoludów, albo przeciwnik wygra." msgid "" "You will face four allied enemies in a straightforward fight for resource " "and treasure. Capture all of the enemy castles for victory." msgstr "" "Staniesz do walki o skarby i surowce z czterema sprzymierzonymi " "przeciwnikami. Aby zwyciężyć, zdobÄ…dź wszystkie wrogie miasta." msgid "" "Your enemies are allied against you and start close by, so be ready to come " "out fighting. You will need to own all four castles in this small valley to " "win." msgstr "" "Twoi wrogowie sprzymierzyli siÄ™ przeciw tobie i zaczynajÄ… blisko twego " "zamku, wiÄ™c przygotuj siÄ™ na ostrÄ… walkÄ™. Aby zwyciężyć, trzeba bÄ™dzie " "zdobyć wszystkie cztery zamki w tej maÅ‚ej dolinie." msgid "" "The Sorceress' guild of Noraston has requested Roland's aid against an " "attack from Archibald's allies. Capture all of the enemy castles to win, and " "don't lose Noraston, or you'll lose the scenario. (Hint: There is an enemy " "castle on an island in the ocean.)" msgstr "" "Gildia Czarodziejek z Norastonu poprosiÅ‚a o pomoc Rolanda w walce z " "sojusznikami Archibalda. ZdobÄ…dź wszystkie wrogie zamki i nie strać " "Norastonu, albo przegrasz. (Wskazówka: jeden z zamków wroga znajduje siÄ™ na " "wyspie wÅ›ród oceanu.)" msgid "" "Gather as large an army as possible and capture the enemy castle within 8 " "weeks. You are opposed by only one enemy, but must travel a long way to get " "to the enemy castle. Any troops you have in your army at the end of this " "scenario will be with you in the final battle." msgstr "" "Zbierz armiÄ™ i w ciÄ…gu 8 tygodni zdobÄ…dź zamek wroga. Walczysz tylko z " "jednym przeciwnikiem, ale musisz pokonać dÅ‚ugÄ… drogÄ™, by dostać siÄ™ do jego " "zamku. Jednostki, które bÄ™dÄ… znajdować siÄ™ w twojej armii pod koniec " "scenariusza, bÄ™dÄ… walczyć w koÅ„cowej bitwie." msgid "" "Find the Crown before Archibald's heroes find it. Roland will need the Crown " "for the final battle against Archibald." msgstr "" "Znajdź koronÄ™ zanim zrobiÄ… to bohaterowie Archibalda. Roland bÄ™dzie " "potrzebowaÅ‚ jej w koÅ„cowej bitwie." msgid "" "Three allied enemies stand before you and victory, including Lord Corlagon. " "Roland is in a castle to the northwest, and you will lose if he falls to the " "enemy. Remember that capturing Lord Corlagon will ensure that he will not " "fight against you in the final scenario." msgstr "" "Na twej drodze do zwyciÄ™stwa stoi 3 sprzymierzonych wrogów, włączajÄ…c w to " "Lorda Carlagona. Roland jest w swoim zamku na pn/zach. i przegrasz, jeÅ›li " "zostanie pokonany. PamiÄ™taj, że schwytanie Lorda Carlagona sprawi, iż nie " "bÄ™dzie walczyÅ‚ w ostatnim scenariuszu." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Archibald to end the war!" msgstr "" "To ostatnia bitwa. Obie strony sÄ… uzbrojone po zÄ™by, a wszyscy sprzymierzyli " "siÄ™ przeciw tobie. Pokonaj Archibalda i zakoÅ„cz wojnÄ™!" #, fuzzy msgid "" "Switching sides leaves you with three castles against the enemy's one. This " "battle will be the easiest one you will face for the rest of the war..." "traitor." msgstr "" "Po zmianie stron bÄ™dziesz mieć trzy zamki, a przeciwnik tylko jeden. To " "bÄ™dzie najÅ‚atwiejsza z czekajÄ…cych ciÄ™ bitew... zdrajco." msgid "Barbarian Wars" msgstr "Wojny BarbarzyÅ„ców" msgid "First Blood" msgstr "Pierwsza krew" msgid "Necromancers" msgstr "Nekromanci" msgid "Slay the Dwarves" msgstr "Zniszcz Krasnoludy" msgid "Country Lords" msgstr "Kraina WÅ‚adców" msgid "Dragon Master" msgstr "WÅ‚adca Smoków" msgid "Rebellion" msgstr "Bunt" msgid "Apocalypse" msgstr "Apokalipsa" msgid "Greater Glory" msgstr "WiÄ™ksza ChwaÅ‚a" #, fuzzy msgid "" "King Archibald requires you to defeat the three enemies in this region. They " "are not allied with one another, so they will spend most of their energy " "fighting amongst themselves. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Król Archibald życzy sobie, abyÅ› pokonaÅ‚ jego trzech wrogów w tym rejonie. " "Nie sÄ… sprzymierzeni, wiÄ™c wiÄ™kszość czasu spÄ™dzÄ… na potyczkach miÄ™dzy sobÄ…. " "Wygrasz, jeÅ›li zdobÄ™dziesz wszystkie zamki i pokonasz wszystkich bohaterów " "przeciwników." msgid "" "You must unify the barbarian tribes of the north by conquering them. As in " "the previous mission, the enemy is not allied against you, but they have " "more resources at their disposal. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Musisz zjednoczyć plemiona barbarzyÅ„ców na północy, podbijajÄ…c je. Podobnie " "jak w poprzednich misjach, wrogowie nie sprzymierzyli siÄ™ przeciw tobie, ale " "majÄ… wiÄ™cej surowców do dyspozycji. Wygrasz jeÅ›li podbijesz wszystkie wrogie " "zamki i pokonasz wszystkich bohaterów." msgid "" "Do-gooder wizards have taken the Necromancers' castle. You must retake it to " "achieve victory. Remember that while you start with a powerful army, you " "have no castle and must take one within 7 days, or lose this battle. (Hint: " "The nearest castle is to the southeast.)" msgstr "" "Czarodzieje zajÄ™li zamek nekromantów. Musisz go odbić, aby zwyciężyć. " "PamiÄ™taj, że choć zaczynasz z potężnÄ… armiÄ…, nie masz wÅ‚asnego zamku i " "musisz jakiÅ› zdobyć w ciÄ…gu 7 dni, albo przegrasz. (Wskazówka: najbliższy " "zamek jest na poÅ‚udniowym wschodzie)." msgid "" "The dwarves need conquering before they can interfere in King Archibald's " "plans. Roland's forces have more than one hero and many towns to start with, " "so be ready for attack from multiple directions. You must capture all of the " "enemy towns and castles to claim victory." msgstr "" "Należy podbić krasnoludy, zanim zdoÅ‚ajÄ… przeszkodzić w wykonaniu planów " "króla Archibalda. SiÅ‚y Rolanda posiadajÄ… od poczÄ…tku wiele miast i paru " "bohaterów, wiÄ™c przygotuj siÄ™ na ataki z kilku stron. Aby zwyciężyć, musisz " "zdobyć wszystkie wrogie miasta i zamki." msgid "" "You must put down a peasant revolt led by Roland's forces. All are allied " "against you, but you have Lord Corlagon, an experienced hero, to help you. " "Capture all enemy castles to win." msgstr "" "Musisz zdÅ‚awić bunt chÅ‚opów podżegany przez siÅ‚y Rolanda. Wszyscy " "sprzymierzyli siÄ™ przeciw tobie, ale pomaga ci Lord Corlagon - doÅ›wiadczony " "bohater. Aby wygrać, zdobÄ…dź wszystkie wrogie zamki." msgid "" "There are two enemies allied against you in this mission. Both are well " "armed and seek to evict you from their island. Avoid them and capture Dragon " "City to win." msgstr "" "W tej misji czeka na ciebie dwóch sprzymierzonych przeciwników. Obaj majÄ… " "spore siÅ‚y i chcÄ… usunąć ciÄ™ z ich wyspy. Postaraj siÄ™ ich uniknąć i zdobÄ…dź " "Smocze Miasto, aby wygrać." msgid "" "Your orders are to conquer the country lords that have sworn to serve " "Roland. All of the enemy castles are unified against you. Since you start " "without a castle, you must hurry to capture one before the end of the week. " "Capture all enemy castles for victory." msgstr "" "Masz rozkaz pokonać paru zaÅ›ciankowych lordów, którzy przysiÄ™gli sÅ‚użyć " "Rolandowi. Wszyscy przeciwnicy zjednoczyli siÄ™ przeciw tobie. Ponieważ " "zaczynasz bez zamku, musisz siÄ™ pospieszyć i opanować jakiÅ› przed koÅ„cem " "tygodnia. Aby wygrać, zdobÄ…dź wszystkie wrogie zamki." msgid "" "Find the Crown before Roland's heroes find it. Archibald will need the Crown " "for the final battle against Roland." msgstr "" "Znajdź koronÄ™ zanim zrobiÄ… to bohaterowie Rolanda. Archibald bÄ™dzie " "potrzebowaÅ‚ jej w koÅ„cowej bitwie." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Roland to win the war, and be sure " "not to lose Archibald in the fight!" msgstr "" "To ostatnia bitwa. Obie strony sÄ… uzbrojone po zÄ™by, a wszyscy sprzymierzyli " "siÄ™ przeciw tobie. Schwytaj Rolanda i zakoÅ„cz wojnÄ™, ale dopilnuj tego, aby " "w walce nie stracić Archibalda!" msgid "Arrow's Flight" msgstr "Lot StrzaÅ‚y" msgid "Island of Chaos" msgstr "Wyspa Chaosu" msgid "The Abyss" msgstr "OtchÅ‚aÅ„" msgid "Uprising" msgstr "Powstanie" msgid "Aurora Borealis" msgstr "Zorza Polarna" msgid "Betrayal's End" msgstr "Kres Zdrady" msgid "Corruption's Heart" msgstr "Serce Zepsucia" msgid "The Giant's Pass" msgstr "Przełęcz Olbrzyma" msgid "" "Subdue the unruly local lords in order to provide the Empire with facilities " "to operate in this region." msgstr "" "Podbij nieposÅ‚usznych lordów, aby zapewnić Imperium możliwość prowadzenia " "operacji w tym regionie." msgid "" "Eliminate all opposition in this area. Then the first piece of the artifact " "will be yours." msgstr "" "PozbÄ…dź siÄ™ wszystkich przeciwników na tym terenie, a pierwsza część " "artefaktu bÄ™dzie twoja." msgid "" "The sorceresses to the northeast are rebelling! For the good of the empire " "you must quash their feeble uprising on your way to the mountains." msgstr "" "Czarodziejki z północnego wschodu zaczęły siÄ™ buntować! Dla dobra Imperium, " "podczas podróży w góry, musisz zdÅ‚awić ich żaÅ‚osne powstanie." msgid "" "Having prepared for your arrival, Kraeger has arranged for a force of " "necromancers to thwart your quest. You must capture the castle of Scabsdale " "before the first day of the third week, or the Necromancers will be too " "strong for you." msgstr "" "Przygotowawszy siÄ™ na twe przybycie, Kraeger wspomógÅ‚ siÄ™ siÅ‚ami " "nekromantów. Nim nadejdzie pierwszy dzieÅ„ trzeciego tygodnia musisz zdobyć " "zamek Scabsdale, albo nekromanci bÄ™dÄ… zbyt silni dla ciebie." msgid "" "The barbarian despot in this area is, as yet, ignorant of your presence. " "Quickly, build up your forces before you are discovered and attacked! Secure " "the region by subduing all enemy forces." msgstr "" "BarbarzyÅ„ski wÅ‚adca tego terytorium nie wie na razie o twej obecnoÅ›ci. Zanim " "ciÄ™ odkryje i zaatakuje, szybko wzmocnij swojÄ… armiÄ™! Zajmij region i " "pokonaj wszystkie siÅ‚y wroga." msgid "" "The Empire is weak in this region. You will be unable to completely subdue " "all forces in this area, so take what you can before reprisal strikes. " "Remember, your true goal is to claim the Helmet of Anduran." msgstr "" "W tym regionie wpÅ‚ywy Imperium sÄ… sÅ‚abe. Nie uda ci siÄ™ caÅ‚kowicie podbić " "tego obszaru, wiÄ™c zdobÄ…dź ile możesz zanim nadejdzie odwet. PamiÄ™taj, że " "twoim prawdziwym celem jest zdobycie HeÅ‚mu Andurana." msgid "For the good of the Empire, eliminate Kraeger." msgstr "Dla dobra Imperium pozbÄ…dź siÄ™ Kraegera." msgid "" "At last, you have the opportunity and the facilities to rid the Empire of " "the necromancer's evil. Eradicate them completely, and you will be sung as a " "hero for all time." msgstr "" "Nareszcie masz okazjÄ™ i Å›rodki, by oczyÅ›cić Imperium z plugastwa " "nekromantów. Zniszcz ich caÅ‚kowicie, a o twym bohaterstwie bÄ™dzie siÄ™ " "Å›piewać przez wieki." msgid "Border Towns" msgstr "Miasta Graniczne" msgid "Conquer and Unify" msgstr "Zjednoczenie" msgid "Crazy Uncle Ivan" msgstr "Szalony Wuj Ivan" msgid "The Wayward Son" msgstr "KrnÄ…brny Syn" msgid "Ivory Gates" msgstr "Ivory Gates" msgid "The Elven Lands" msgstr "Krainy Elfów" msgid "The Epic Battle" msgstr "Epicka Bitwa" msgid "The Southern War" msgstr "Wojna na PoÅ‚udniu" msgid "" "Conquer and unite all the enemy tribes. Don't lose the hero Jarkonas, the " "forefather of all descendants." msgstr "" "Podbij i zjednocz wszystkie wrogie plemiona. Nie wolno ci utracić Jarkonasa " "- przodka caÅ‚ej rodziny." msgid "" "Your rival, the Kingdom of Harondale, is attacking weak towns on your " "border! Recover from their first strike and crush them completely!" msgstr "" "RywalizujÄ…ce królestwo Harondale atakuje sÅ‚abe miasta przy twojej granicy! " "Wytrzymaj pierwsze uderzenie i caÅ‚kowicie zmiażdż wroga!" msgid "" "Find your wayward son Joseph who is rumored to be living in the desolate " "lands. Do it before the first day of the third month or it will be of no " "help to your family." msgstr "" "Znajdź swojego krnÄ…brnego syna, Josepha, który podobno żyje gdzieÅ› na " "pustkowiu. Dokonaj tego zanim nastanie pierwszy dzieÅ„ trzeciego miesiÄ…ca, " "albo twojej rodzinie już nic nie pomoże." msgid "" "Rescue your crazy uncle Ivan. Find him before the first day of the fourth " "month or it will be no help to your kingdom." msgstr "" "Uratuj swojego szalonego wuja Ivana. Znajdź go nim nadejdzie pierwszy dzieÅ„ " "czwartego miesiÄ…ca, albo nic nie pomoże już twemu królestwu." msgid "" "Destroy the barbarians who are attacking the southern border of your " "kingdom! Recover your fallen towns, and then invade the jungle kingdom. " "Leave no enemy standing." msgstr "" "Zniszcz barbarzyÅ„ców, którzy atakujÄ… poÅ‚udniowÄ… granicÄ™ naszego paÅ„stwa! " "Odbij stracone miasta, a nastÄ™pnie uderz na poÅ‚ożone w dżungli królestwo. " "Pokonaj wszystkich wrogów." msgid "Retake the castle of Ivory Gates, which has fallen due to treachery." msgstr "Odbij zamek Ivory Gates, który padÅ‚ z powodu zdrady." msgid "" "Gain the favor of the elves. They will not allow trees to be chopped down, " "so they will send you wood every 2 weeks. You must complete your mission " "before the first day of the seventh month, or the kingdom will surely fall." msgstr "" "Zyskaj przychylność elfów. Nie pozwalajÄ… one na Å›cinanie drzew, wiÄ™c co dwa " "tygodnie przysyÅ‚ać bÄ™dziemy drewno. Musisz wykonać swÄ… misjÄ™ przed pierwszym " "dniem siódmego miesiÄ…ca, albo nasze królestwo upadnie." msgid "" "This is the final battle against your rival kingdom of Harondale. Eliminate " "everyone, and don't lose the hero Jarkonas VI." msgstr "" "To ostateczna bitwa przeciw wrogiemu królestwu Harondale. Pokonaj wszystkich " "i nie strać Jarkonasa VI." msgid "Fount of Wizardry" msgstr "ŹródÅ‚o Magii" msgid "Power's End" msgstr "Koniec PotÄ™gi" msgid "The Eternal Scrolls" msgstr "Wieczne Zwoje" msgid "The Shrouded Isles" msgstr "Zamglone Wyspy" msgid "" "Your mission is to vanquish the warring mages in the magical Shrouded Isles. " "The completion of this task will give you a fighting chance against your " "rivals." msgstr "" "Twoja misja polega na pokonaniu walczÄ…cych magów na Zamglonych Wyspach. " "Wykonanie tego zadania da ci szansÄ™ w walce przeciw twoim rywalom." msgid "" "The location of the great library has been discovered! You must make your " "way to it, and reclaim the city of Chronos in which it lies." msgstr "" "UdaÅ‚o siÄ™ odkryć miejsce poÅ‚ożenia biblioteki! Musisz do niej wyruszyć i " "zająć miasto Chronos, w którym siÄ™ znajduje." #, fuzzy msgid "" "Find the Orb of negation, which is said to be buried in this land. There are " "clues inscribed on stone obelisks which will help lead you to your prize. " "Find the Orb before the first day of the sixth month, or your rivals will " "surely have gotten to the fount before you." msgstr "" "Znajdź KulÄ™ Negacji, która podobno jest zakopana gdzieÅ› w tej krainie. Na " "kamiennych obeliskach znajdujÄ… siÄ™ wskazówki, które ciÄ™ do niej doprowadzÄ…. " "Znajdź KulÄ™ nim nastanie pierwszy dzieÅ„ szóstego miesiÄ…ca, albo twoi rywale " "zdobÄ™dÄ… jÄ… przed tobÄ…." msgid "" "You must take control of the castle of Magic, where the fount of wizardry " "lies. Do this and your victory will be supreme." msgstr "" "Musisz zająć zaklÄ™ty zamek, gdzie znajduje siÄ™ źródÅ‚o magii. Dokonaj tego, a " "odniesiesz wspaniaÅ‚e zwyciÄ™stwo." msgid "Blood is Thicker" msgstr "WiÄ™zy Krwi" msgid "King and Country" msgstr "Król i Kraj" msgid "Pirate Isles" msgstr "Wyspy Piratów" msgid "Stranded" msgstr "Rozbitek" msgid "" "Capture the town on the island off the southeast shore in order to construct " "a boat and travel back towards the mainland. Do not lose the hero Gallavant." msgstr "" "ZdobÄ…dź miasto na wyspie, przy poÅ‚udniowo-wschodnim wybrzeżu, aby zbudować " "łódź mogÄ…cÄ… dopÅ‚ynąć z tobÄ… na kontynent. Nie wolno ci stracić bohatera o " "imieniu Gallavant." msgid "" "Find and defeat Martine, the pirate leader, who resides in Pirates Cove. Do " "not lose Gallavant or your quest will be over." msgstr "" "Znajdź i pokonaj Martina, przywódcÄ™ piratów, który przebywa w Zatoce " "Piratów. Nie wolno ci stracić Gallavanta, albo twoja przygoda siÄ™ skoÅ„czy." msgid "" "Eliminate all the other forces who oppose the rule of Lord Alberon. " "Gallavant must not die." msgstr "" "Pokonaj wszystkie siÅ‚y sprzeciwiajÄ…ce siÄ™ rzÄ…dom Lorda Alberona. Gallavant " "nie może zginąć." msgid "" "Overthrow the entrenched monarchy of Lord Alberon, and claim all the land in " "your name. Gallavant must not die." msgstr "" "Obal monarchiÄ™ Lorda Alberona i zajmij caÅ‚y kraj w swoim imieniu. Gallavant " "nie może zginąć." msgid " bane" msgstr " pogromca" msgid " alliance" msgstr " sojusz" msgid "Carry-over forces" msgstr "Przeniesione siÅ‚y" msgid " bonus" msgstr " bonus" msgid " defeated" msgstr " pokonany" msgid " will always run away from your army." msgstr " zawsze ucieknie przed twojÄ… armiÄ…." msgid " will be willing to join your army." msgstr " zawsze dołączy do twojej armii." msgid "\"%{artifact}\" artifact will be carried over the scenario." msgstr "" "Artefact \"%{artifact}\" zostanie przeniesiony do nastÄ™pnego scenariusza." msgid "The army will be carried over the scenario." msgstr "Armia zostanie przeniesiona do nastÄ™pnego scenariusza." msgid "The kingdom will have +%{count} %{resource} each day." msgstr "Królestwo otrzyma +%{count} %{resource} każdego dnia." msgid "\"%{spell}\" spell will be carried over the scenario." msgstr "ZaklÄ™cie \"%{spell}\" zostanie przeniesione do nastÄ™pnego scenariusza." msgid "%{hero} can be hired in the scenario." msgstr "Bohater %{hero} może być zwerbowany w tym scenariuszu." msgid "" "%{hero} has been defeated and will not appear in the subsequent scenarios." msgstr "" "Pokonany bohater %{hero} nie bÄ™dzie dostÄ™pny w kolejnych scenariuszach." msgid "The dwarves recognize their allies and gladly join your forces." msgstr "" "Krasnoludy rozpoznajÄ… w tobie sojusznika i chÄ™tnie przyłączajÄ… siÄ™ do ciebie." msgid "The ogres recognize you as the Dwarfbane and lumber over to join you." msgstr "" "Ogry rozpoznajÄ… ciÄ™ jako PogromcÄ™ Krasnoludów i powoli zmierzajÄ… ku tobie, " "aby wesprzeć twoje siÅ‚y." msgid "" "The dragons, snarling and growling, agree to join forces with you, their " "'Ally'." msgstr "WarczÄ…ce i burczÄ…ce smoki przystajÄ… na dołączenie do twojej armii." #, fuzzy msgid "" "As you approach the group of elves, their leader calls them all to " "attention. He shouts to them, \"Who of you is brave enough to join this " "fearless ally of ours?\" The group explodes with cheers as they run to join " "your ranks." msgstr "" "Kiedy zbliżasz siÄ™ do grupy elfów, jej przywódca zwoÅ‚uje wszystkich i " "wykrzykuje, \"Kto jest na tyle odważny by dołączyć do naszego " "nieustraszonego sprzymierzeÅ„ca?\" CzÅ‚onkowie grupy wiwatujÄ… i biegnÄ… do " "ciebie by dołączyć do twoich szeregów." #, fuzzy msgid "" "The dwarves hail you, \"Any friend of Roland is a friend of ours. You may " "pass.\"" msgstr "" "WitajÄ… ciÄ™ krasnoludy, \"Przyjaciel Rolanda jest naszym przyjacielem. Możesz " "przejść.\"" msgid "" "The ogres give you a grunt of recognition, \"Archibald's allies may pass.\"" msgstr "" "Ogry chrzÄ…kajÄ… na znak uznania, \"SprzymierzeÅ„cy Archibalda mogÄ… przejść.\"" #, fuzzy msgid "" "The dragons see you and call out. \"Our alliance with Archibald compels us " "to join you. Unfortunately you have no room. A pity!\" They quickly scatter." msgstr "" "Smoki widzÄ… ciÄ™ i wykrzykujÄ…. \"Nasze przymierze z Archibaldem nakazuje nam " "przyłączyć siÄ™ do ciebie. Jednakże nie ma miejsca w twoich szeregach, co za " "szkoda!\". Smoki momentalnie siÄ™ rozpraszajÄ…." #, fuzzy msgid "" "The elves stand at attention as you approach. Their leader calls to you and " "says, \"Let us not impede your progress, ally! Move on, and may victory be " "yours.\"" msgstr "" "Elfy stajÄ… na baczność, gdy siÄ™ zbliżasz. Ich przywódca woÅ‚a ciÄ™ i mówi, " "\"Nie bÄ™dziemy wchodzić ci w drogÄ™ towarzyszu! Ruszaj dalej i niech " "zwyciÄ™stwo bÄ™dzie twoje.\"" msgid "\"The Dwarfbane!!!!, run for your lives.\"" msgstr "\"Pogromca Krasnoludów, ratuj siÄ™ kto może!!!\"" msgid "campaignBonus|Animate Dead" msgstr "Ożywienie Martwych" msgid "campaignBonus|Chain Lightning" msgstr "ÅaÅ„cuch Piorunów" msgid "campaignBonus|Fireblast" msgstr "Eksplozja Ognia" msgid "campaignBonus|Mass Curse" msgstr "Grupowa KlÄ…twa" msgid "campaignBonus|Mass Haste" msgstr "Grupowe Przyspieszenie" msgid "campaignBonus|Mirror Image" msgstr "Lustrzane Odbicie" msgid "campaignBonus|Resurrect" msgstr "Ożywienie" msgid "campaignBonus|Steelskin" msgstr "Stalowa Skóra" msgid "campaignBonus|Summon Earth" msgstr "Przyw. Ziemi" msgid "campaignBonus|View Heroes" msgstr "Pokaż Bohaterów" msgid "campaignBonus|Ballista" msgstr "Balista" msgid "campaignBonus|Black Pearl" msgstr "Czarna PerÅ‚a" msgid "campaignBonus|Caster's Bracelet" msgstr "Magiczna Bransol" msgid "campaignBonus|Defender Helm" msgstr "HeÅ‚m ObroÅ„cy" msgid "campaignBonus|Breastplate" msgstr "NapierÅ›nik" msgid "campaignBonus|Dragon Sword" msgstr "Smoczy Miecz" msgid "campaignBonus|Fizbin Medal" msgstr "Fizbin" msgid "campaignBonus|Foremost Scroll" msgstr "DoskonaÅ‚y Zwój" msgid "campaignBonus|Gauntlets" msgstr "RÄ™kawice" msgid "campaignBonus|Hideous Mask" msgstr "Potworna Maska" msgid "campaignBonus|Mage's Ring" msgstr "PierÅ›cieÅ„ Maga" msgid "campaignBonus|Major Scroll" msgstr "WiÄ™kszy Zwój" msgid "campaignBonus|Medal of Honor" msgstr "Medal Honoru" msgid "campaignBonus|Medal of Valor" msgstr "Medal MÄ™stwa" msgid "campaignBonus|Minor Scroll" msgstr "Pomniejszy Zwój" msgid "campaignBonus|Nomad Boots" msgstr "Buty Nomady" msgid "campaignBonus|Power Axe" msgstr "Potężny Topór" msgid "campaignBonus|Spiked Shield" msgstr "Rogata Tarcza" msgid "campaignBonus|Stealth Shield" msgstr "Maskuj. Tarcza" msgid "campaignBonus|Tax Lien" msgstr "Weksel Fiskalny" msgid "campaignBonus|Thunder Mace" msgstr "Grzm. KiÅ›cieÅ„" msgid "campaignBonus|Traveler's Boots" msgstr "Buty Podróżnika" msgid "campaignBonus|White Pearl" msgstr "BiaÅ‚a PerÅ‚a" msgid "campaignBonus|Basic Archery" msgstr "Podst. Å‚ucznictwo" msgid "campaignBonus|Advanced Archery" msgstr "Zaawan. Å‚ucznictwo" msgid "campaignBonus|Expert Archery" msgstr "Mistrz. Å‚ucznictwo" msgid "campaignBonus|Basic Ballistics" msgstr "Podst. balistyka" msgid "campaignBonus|Advanced Ballistics" msgstr "Zaawan. balistyka" msgid "campaignBonus|Expert Ballistics" msgstr "Mistrz. balistyka" msgid "campaignBonus|Basic Diplomacy" msgstr "Podst. dyplomacja" msgid "campaignBonus|Advanced Diplomacy" msgstr "Zaawan. dyplomacja" msgid "campaignBonus|Expert Diplomacy" msgstr "Mistrz. dyplomacja" msgid "campaignBonus|Basic Eagle Eye" msgstr "Podst. sokole oko" msgid "campaignBonus|Advanced Eagle Eye" msgstr "Zaawan. sokole oko" msgid "campaignBonus|Expert Eagle Eye" msgstr "Mistrz. sokole oko" msgid "campaignBonus|Basic Estates" msgstr "Podstawowe finanse" msgid "campaignBonus|Advanced Estates" msgstr "Zaawan. finanse" msgid "campaignBonus|Expert Estates" msgstr "Mistrzowskie finanse" msgid "campaignBonus|Basic Leadership" msgstr "Podst. dowodzenie" msgid "campaignBonus|Advanced Leadership" msgstr "Zaawan. dowodzenie" msgid "campaignBonus|Expert Leadership" msgstr "Mistrz. dowodzenie" msgid "campaignBonus|Basic Logistics" msgstr "Podst. logistyka" msgid "campaignBonus|Advanced Logistics" msgstr "Zaawan. logistyka" msgid "campaignBonus|Expert Logistics" msgstr "Mistrz. logistyka" msgid "campaignBonus|Basic Luck" msgstr "Podst. szczęście" msgid "campaignBonus|Advanced Luck" msgstr "Zaawan. szczęście" msgid "campaignBonus|Expert Luck" msgstr "Mistrz. szczęście" msgid "campaignBonus|Basic Mysticism" msgstr "Podst. mistycyzm" msgid "campaignBonus|Advanced Mysticism" msgstr "Zaawan. mistycyzm" msgid "campaignBonus|Expert Mysticism" msgstr "Mistrz. mistycyzm" msgid "campaignBonus|Basic Navigation" msgstr "Podst. nawigacja" msgid "campaignBonus|Advanced Navigation" msgstr "Zaawan. nawigacja" msgid "campaignBonus|Expert Navigation" msgstr "Mistrz. nawigacja" msgid "campaignBonus|Basic Necromancy" msgstr "Podst. nekromancja" msgid "campaignBonus|Advanced Necromancy" msgstr "Zaawan. nekromancja" msgid "campaignBonus|Expert Necromancy" msgstr "Mistrz. nekromancja" msgid "campaignBonus|Basic Pathfinding" msgstr "Podst. znaj. drogi" msgid "campaignBonus|Advanced Pathfinding" msgstr "Zaawan. znaj. drogi" msgid "campaignBonus|Expert Pathfinding" msgstr "Mistrz. znaj. drogi" msgid "campaignBonus|Basic Scouting" msgstr "Podstawowy zwiad" msgid "campaignBonus|Advanced Scouting" msgstr "Zaawan. zwiad" msgid "campaignBonus|Expert Scouting" msgstr "Mistrzowski zwiad" msgid "campaignBonus|Basic Wisdom" msgstr "Podstawowa mÄ…drość" msgid "campaignBonus|Advanced Wisdom" msgstr "Zaawan. mÄ…drość" msgid "campaignBonus|Expert Wisdom" msgstr "Mistrzowska mÄ…drość" msgid "" "The main hero will have \"%{artifact}\" artifact at the start of the " "scenario." msgstr "" "Główny bohater otrzyma artefakt \"%{artifact}\" na starcie scenariusza." msgid "" "The kingdom will receive %{amount} additional %{resource} at the start of " "the scenario." msgstr "" "Królestwo otrzyma dodatkowe %{amount} %{resource} na starcie scenariusza." msgid "" "The kingdom will have %{amount} less %{resource} at the start of the " "scenario." msgstr "" "Królestwo otrzyma dodatkowe %{amount} mniej %{resource} na starcie " "scenariusza." msgid "" "The main hero will have %{count} %{monster} at the start of the scenario." msgstr "Główny bohater otrzyma %{count} %{monster} na starcie scenariusza." msgid "" "The main hero will have \"%{spell}\" spell at the start of the scenario." msgstr "Główny bohater otrzyma zaklÄ™cie \"%{spell}\" na starcie scenariusza." msgid "The starting race of the scenario will be %{race}." msgstr "PoczÄ…tkowÄ… rasÄ… w scenariuszu bÄ™dzie %{race}." msgid "" "The main hero will have additional %{count} %{skill} at the start of the " "scenario." msgstr "" "Główny bohater otrzyma dodatkowe %{count} %{skill} na starcie scenariusza." msgid "The main hero will have %{skill} at the start of the scenario." msgstr "Główny bohater otrzyma %{skill} na starcie scenariusza." msgid "Roland" msgstr "Roland" msgid "Archibald" msgstr "Archibald" #, fuzzy msgid "Price of Loyalty" msgstr "Cena LojalnoÅ›ci" msgid "Voyage Home" msgstr "Podróż do Domu" msgid "Wizard's Isle" msgstr "Wyspa Czarodzieja" msgid "Descendants" msgstr "Potomkowie" msgid "The %{building} produces %{monster}." msgstr "%{building} - tu rekrutuje siÄ™: %{monster}." msgid "Requires:" msgstr "Wymagania:" msgid "Exit this menu without doing anything." msgstr "Wyjdź z menu nie wprowadzajÄ…c żadnych zmian." msgid "Cannot build. You have already built here today." msgstr "Nie można budować. Już to robiono w tej turze." msgid "For this action it is necessary to build a castle first." msgstr "Najpierw trzeba zbudować zamek." #, fuzzy msgid "Cannot build %{name} because the castle is too far away from an ocean." msgstr "%{name} - nie można zbudować, zamek jest za daleko od wody." msgid "disable build." msgstr "nie można zbudować." msgid "Cannot afford %{name}." msgstr "%{name} - nie stać ciÄ™ na ten budynek." msgid "%{name} is already built." msgstr "%{name} - ten budynek zostaÅ‚ już wzniesiony." msgid "Cannot build %{name}." msgstr "%{name} - nie można zbudować." msgid "Build %{name}." msgstr "Zbuduj: %{name}." msgid "Blackridge" msgstr "Blackridge" msgid "Hillstone" msgstr "Hillstone" msgid "Pinehurst" msgstr "Pinehurst" msgid "Whiteshield" msgstr "Whiteshield" msgid "Woodhaven" msgstr "Woodhaven" msgid "Blackwind" msgstr "Blackwind" msgid "Bloodreign" msgstr "Bloodreign" msgid "Dragontooth" msgstr "Dragontooth" msgid "Greywind" msgstr "Greywind" msgid "Portsmith" msgstr "Portsmith" msgid "Atlantium" msgstr "Atlantium" msgid "Middle Gate" msgstr "Middle Gate" msgid "Sansobar" msgstr "Sansobar" msgid "Tundara" msgstr "Tundara" msgid "Vulcania" msgstr "Vulcania" msgid "Baywatch" msgstr "Baywatch" msgid "Fountainhead" msgstr "Fountainhead" msgid "Vertigo" msgstr "Vertigo" msgid "Wildabar" msgstr "Wildabar" msgid "Winterkill" msgstr "Winterkill" msgid "Brindamoor" msgstr "Brindamoor" msgid "Lakeside" msgstr "Lakeside" msgid "Nightshadow" msgstr "Nightshadow" msgid "Olympus" msgstr "Olympus" msgid "Sandcaster" msgstr "Sandcaster" msgid "Alamar" msgstr "Alamar" msgid "Burlock" msgstr "Burlock" msgid "Dragadune" msgstr "Dragadune" msgid "Kalindra" msgstr "Kalindra" msgid "Xabran" msgstr "Xabran" msgid "Algary" msgstr "Algary" msgid "Basenji" msgstr "Basenji" msgid "Blackfang" msgstr "Blackfang" msgid "New Dawn" msgstr "New Dawn" msgid "Sorpigal" msgstr "Sorpigal" msgid "Avone" msgstr "Avone" msgid "Big Oak" msgstr "Big Oak" msgid "Chandler" msgstr "Chandler" msgid "Erliquin" msgstr "Erliquin" msgid "Hampshire" msgstr "Hampshire" msgid "Antioch" msgstr "Antioch" msgid "Avalon" msgstr "Avalon" msgid "Roc Haven" msgstr "Roc Haven" msgid "South Mill" msgstr "South Mill" msgid "Weed Patch" msgstr "Weed Patch" msgid "Brownston" msgstr "Brownston" msgid "Hilltop" msgstr "Hilltop" msgid "Weddington" msgstr "Weddington" msgid "Westfork" msgstr "Westfork" msgid "Whittingham" msgstr "Whittingham" msgid "Cathcart" msgstr "Cathcart" msgid "Elk's Head" msgstr "Elk's Head" msgid "Roscomon" msgstr "Roscomon" msgid "Sherman" msgstr "Sherman" msgid "Yorksford" msgstr "Yorksford" msgid "Blackburn" msgstr "Blackburn" msgid "Blacksford" msgstr "Blacksford" msgid "Burton" msgstr "Burton" msgid "Pig's Eye" msgstr "Pig's Eye" msgid "Viper's Nest" msgstr "Viper's Nest" msgid "Fenton" msgstr "Fenton" msgid "Lankershire" msgstr "Lankershire" msgid "Lombard" msgstr "Lombard" msgid "Timberhill" msgstr "Timberhill" msgid "Troy" msgstr "Troy" msgid "Forder Oaks" msgstr "Forder Oaks" msgid "Meramec" msgstr "Meramec" msgid "Quick Silver" msgstr "Quick Silver" msgid "Westmoor" msgstr "Westmoor" msgid "Willow" msgstr "Willow" msgid "Corackston" msgstr "Corackston" msgid "Sheltemburg" msgstr "Sheltemburg" msgid "Cannot recruit - you already have a Hero in this town." msgstr "Nie możesz rekrutować - w mieÅ›cie jest już bohater." msgid "Cannot recruit - you have too many Heroes." msgstr "Nie możesz rekrutować - masz za dużo bohaterów." msgid "Cannot afford a Hero" msgstr "Nie stać ciÄ™ na bohatera" msgid "There is no room in the garrison for this army." msgstr "W garnizonie nie ma miejsca na dla tej jednostki." msgid "Fortifications" msgstr "Fortyfikacje" msgid "Farm" msgstr "Farma" msgid "Thatched Hut" msgstr "Chata kryta sÅ‚omÄ…" msgid "Archery Range" msgstr "Tor strzelecki" msgid "Upg. Archery Range" msgstr "Rozb. tor strzelecki" msgid "Blacksmith" msgstr "Kuźnia" msgid "Upg. Blacksmith" msgstr "Rozb. kuźnia" msgid "Armory" msgstr "Zbrojownia" msgid "Upg. Armory" msgstr "Rozb. zbrojownia" msgid "Jousting Arena" msgstr "Plac turniejowy" msgid "Upg. Jousting Arena" msgstr "Rozb. plac turniejowy" msgid "Cathedral" msgstr "Katedra" msgid "Upg. Cathedral" msgstr "Rozb. katedra" msgid "Coliseum" msgstr "Koloseum" msgid "Garbage Heap" msgstr "Sterta Å›mieci" msgid "Hut" msgstr "Chata" msgid "Stick Hut" msgstr "Chata z patyków" msgid "Upg. Stick Hut" msgstr "Rozb. chata z patyków" msgid "Den" msgstr "Legowisko" msgid "Adobe" msgstr "Lepianka" msgid "Upg. Adobe" msgstr "Rozb. lepianka" msgid "Bridge" msgstr "Most" msgid "Upg. Bridge" msgstr "Rozb. most" msgid "Pyramid" msgstr "Piramida" msgid "Rainbow" msgstr "TÄ™cza" msgid "Crystal Garden" msgstr "KrysztaÅ‚owy ogród" msgid "Treehouse" msgstr "Dom na drzewie" msgid "Cottage" msgstr "Domek" msgid "Upg. Cottage" msgstr "Rozb. domek" msgid "Stonehenge" msgstr "Kamienny krÄ…g" msgid "Upg. Stonehenge" msgstr "Rozb. kamienny krÄ…g" msgid "Fenced Meadow" msgstr "Ogrodzona łąka" msgid "sorceress|Red Tower" msgstr "Czerwona wieża" msgid "Dungeon" msgstr "Loch" msgid "Waterfall" msgstr "Wodospad" msgid "Cave" msgstr "Jaskinia" msgid "Crypt" msgstr "Krypta" msgid "Nest" msgstr "Gniazdo" msgid "Maze" msgstr "Labirynt" msgid "Upg. Maze" msgstr "Rozb. labirynt" msgid "Swamp" msgstr "Bagno" msgid "Green Tower" msgstr "Zielona wieża" msgid "warlock|Red Tower" msgstr "Czerwona wieża" msgid "Black Tower" msgstr "Czarna wieża" msgid "Library" msgstr "Biblioteka" msgid "Orchard" msgstr "Sad" msgid "Habitat" msgstr "Pagórek" msgid "Pen" msgstr "Zagroda" msgid "Foundry" msgstr "Odlewnia" msgid "Upg. Foundry" msgstr "Rozb. odlewnia" msgid "Cliff Nest" msgstr "Gniazdo na urwisku" msgid "Ivory Tower" msgstr "Wieża z koÅ›ci sÅ‚oniowej" msgid "Upg. Ivory Tower" msgstr "Rozb. wieża" msgid "Cloud Castle" msgstr "Zamek w chmurach" msgid "Upg. Cloud Castle" msgstr "Rozb. zamek w chmurach" msgid "Storm" msgstr "Wieczna burza" msgid "Skull Pile" msgstr "Stos czaszek" msgid "Excavation" msgstr "Wykopalisko" msgid "Graveyard" msgstr "Cmentarz" msgid "Upg. Graveyard" msgstr "Rozb. cmentarz" msgid "Upg. Pyramid" msgstr "Rozb. piramida" msgid "Mansion" msgstr "PosiadÅ‚ość" msgid "Upg. Mansion" msgstr "Rozb. posiadÅ‚ość" msgid "Mausoleum" msgstr "Mauzoleum" msgid "Upg. Mausoleum" msgstr "Rozb. mauzoleum" msgid "Laboratory" msgstr "Laboratorium" msgid "Shrine" msgstr "Kapliczka" msgid "" "The Fortifications increase the toughness of the walls, increasing the " "number of turns it takes to knock them down." msgstr "" "Fortyfikacje wzmacniajÄ… mury, sprawiajÄ…c, że ich zniszczenie zajmuje wiÄ™cej " "czasu." msgid "The Farm increases production of Peasants by %{count} per week." msgstr "Farma zwiÄ™ksza przyrost ChÅ‚opów o %{count} tygodniowo." msgid "" "The Coliseum provides inspiring spectacles to defending troops, raising " "their morale by two during combat." msgstr "" "W Koloseum pokazywane sÄ… przedstawienia dla oddziałów obroÅ„ców, co zwiÄ™ksza " "ich morale w walce o dwa punkty." msgid "The Garbage Heap increases production of Goblins by %{count} per week." msgstr "Sterta Å›mieci zwiÄ™ksza przyrost Goblinów o %{count} tygodniowo." msgid "The Rainbow increases the luck of the defending units by two." msgstr "TÄ™cza zwiÄ™ksza szczęście oddziałów obroÅ„ców o dwa punkty." msgid "" "The Crystal Garden increases production of Sprites by %{count} per week." msgstr "KrysztaÅ‚owy ogród zwiÄ™ksza przyrost RusaÅ‚ek o %{count} tygodniowo." msgid "The Dungeon increases the income of the town by %{count} gold per day." msgstr "Loch zwiÄ™ksza dochody miasta o %{count} sztuk zÅ‚ota na dzieÅ„." msgid "The Waterfall increases production of Centaurs by %{count} per week." msgstr "Wodospad zwiÄ™ksza przyrost Centaurów o %{count} tygodniowo." msgid "" "The Library increases the number of spells in the Guild by one for each " "level of the guild." msgstr "" "Biblioteka zwiÄ™ksza liczbÄ™ zaklęć dostÄ™pnych w gildii o jedno na każdy jej " "poziom." msgid "The Orchard increases production of Halflings by %{count} per week." msgstr "Sad zwiÄ™ksza przyrost NizioÅ‚ków o %{count} tygodniowo." msgid "The Storm adds +2 to the power of spells of a defending spell caster." msgstr "Wieczna burza dodaje +2 do mocy zaklęć obroÅ„cy zamku." msgid "The Skull Pile increases production of Skeletons by %{count} per week." msgstr "Stos czaszek zwiÄ™ksza przyrost Szkieletów o %{count} tygodniowo." msgid "Thieves' Guild" msgstr "Gildia zÅ‚odziei" msgid "Tavern" msgstr "Karczma" msgid "Shipyard" msgstr "Stocznia" msgid "Well" msgstr "Studnia" msgid "Statue" msgstr "Statua" msgid "Marketplace" msgstr "Targowisko" msgid "Moat" msgstr "Fosa" msgid "Castle" msgstr "Zamek" msgid "Tent" msgstr "Namiot" msgid "Captain's Quarters" msgstr "Kwatera kapitana" msgid "Mage Guild, Level 1" msgstr "Gildia magów, poz. 1" msgid "Mage Guild, Level 2" msgstr "Gildia magów, poz. 2" msgid "Mage Guild, Level 3" msgstr "Gildia magów, poz. 3" msgid "Mage Guild, Level 4" msgstr "Gildia magów, poz. 4" msgid "Mage Guild, Level 5" msgstr "Gildia magów, poz. 5" msgid "" "The Shrine increases the necromancy skill of all your necromancers by 10 " "percent." msgstr "" "Kapliczka zwiÄ™ksza o 10 procent umiejÄ™tność nekromancji u wszystkich twoich " "nekromantów." msgid "" "The Thieves' Guild provides information on enemy players. Thieves' Guilds " "can also provide scouting information on enemy towns. Additional Guilds " "provide more information." msgstr "" "Gildia ZÅ‚odziei dostarcza dane na temat wrogich graczy, a także ich miast. " "Dodatkowe gildie rozszerzajÄ… zakres podawanych informacji." msgid "The Tavern increases morale for troops defending the castle." msgstr "Karczma zwiÄ™ksza morale oddziałów broniÄ…cych zamku." msgid "The Shipyard allows ships to be built." msgstr "W stoczni można budować statki." msgid "" "The Well increases the growth rate of all dwellings by %{count} creatures " "per week." msgstr "" "Studnia zwiÄ™ksza przyrost populacji wszystkich stworzeÅ„ o %{count} jednostki " "tygodniowo." #, fuzzy msgid "The Statue increases the town's income by %{count} gold per day." msgstr "Statua zwiÄ™ksza dzienny przychód miasta o %{count} sztuk zÅ‚ota." msgid "The Left Turret provides extra firepower during castle combat." msgstr "" "Lewa wieżyczka zwiÄ™ksza możliwość ostrzaÅ‚u wroga podczas oblężenia zamku." msgid "The Right Turret provides extra firepower during castle combat." msgstr "" "Prawa wieżyczka zwiÄ™ksza możliwość ostrzaÅ‚u wroga podczas oblężenia zamku." msgid "" "The Marketplace can be used to convert one type of resource into another. " "The more marketplaces you control, the better the exchange rate." msgstr "" "Targowisko pozwala na wymianÄ™ jednego typu surowca na inny. Im wiÄ™cej " "targowisk posiadasz tym lepszy kurs wymiany." msgid "" "The Moat slows attacking units. Any unit entering the moat must end its turn " "there and becomes more vulnerable to attack." msgstr "" "Fosa spowalnia atakujÄ…ce jednostki. Każdy oddziaÅ‚, który wejdzie do fosy, " "musi siÄ™ w niej zatrzymać i staje siÄ™ mniej odporny na ataki." #, fuzzy msgid "" "The Castle improves the town's defense and increases its income to %{count} " "gold per day." msgstr "" "Zamek poprawia obronÄ™ miasta i zwiÄ™ksza dzienny przychód o %{count} sztuk " "zÅ‚ota." msgid "" "The Tent provides workers to build a castle, provided the materials and the " "gold are available." msgstr "" "W namiocie mieszkajÄ… robotnicy mogÄ…cy zbudować zamek, pod warunkiem, że " "dostÄ™pne jest zÅ‚oto i inne potrzebne materiaÅ‚y." msgid "" "The Captain's Quarters provides a captain to assist in the castle's defense " "when no hero is present." msgstr "" "W kwaterze mieszka kapitan, który dowodzi obronÄ… zamku gdy nie ma w nim " "bohatera." msgid "" "The Mage Guild allows heroes to learn spells and replenish their spell " "points." msgstr "" "W gildii magów bohaterowie mogÄ… uczyć siÄ™ zaklęć i odzyskiwać punkty magii." msgid "Recruit %{name}" msgstr "Rekrutuj %{name}" msgid "Month: %{month}, Week: %{week}, Day: %{day}" msgstr "MiesiÄ…c: %{month}, TydzieÅ„: %{week}, DzieÅ„: %{day}" msgid "" "You must purchase a spell book to use the mage guild, but you currently have " "no room for a spell book. Try giving one of your artifacts to another hero." msgstr "" "Aby korzystać z gildii magów, musisz kupić KsiÄ™gÄ™ Zaklęć. W tej chwili nie " "masz jednak na niÄ… miejsca. Spróbuj przekazać jeden ze swoich artefaktów " "innemu bohaterowi." msgid "Click to show next town." msgstr "Kliknij aby przejść do nastÄ™pnego miasta." msgid "Show next town" msgstr "Pokaż nastÄ™pne miasto" msgid "Click to show previous town." msgstr "Kliknij aby przejść do poprzedniego miasta." msgid "Show previous town" msgstr "Pokaż poprzednie miasto" msgid "This town may not be upgraded to a castle." msgstr "Tego miasta nie można rozbudować do poziomu zamku." msgid "Town" msgstr "Miasto" msgid "Exit Castle" msgstr "WyjÅ›cie z zamku" msgid "Exit Town" msgstr "WyjÅ›cie z miasta" msgid "Show Income" msgstr "Pokaż przychód" msgid "View Hero" msgstr "Pokaż bohatera" msgid "The above spells are available here." msgstr "Tu sÄ… dostÄ™pne powyższe zaklÄ™cia." msgid "The spells the hero can learn have been added to their book." msgstr "" "ZaklÄ™cia, których bohater mógÅ‚ siÄ™ nauczyć, zostaÅ‚y dodane do jego KsiÄ™gi " "Zaklęć." msgid "A generous tip for the barkeep yields the following rumor:" msgstr "" "Dajesz karczmarzowi szczodry napiwek, a on w zamian przekazuje ci " "nastÄ™pujÄ…ce wieÅ›ci:" msgid "Recruit Hero" msgstr "Najmij bohatera" msgid "%{name} is a level %{value} %{race} " msgstr "%{name} ma poziom %{value}. To %{race} " msgid "with %{count} artifacts." msgstr "z %{count} artefaktami." msgid "with 1 artifact." msgstr "z jednym artefaktem." msgid "without artifacts." msgstr "bez artefaktów." msgid "Recruit %{name} the %{race}" msgstr "Rekrutuj: %{name} %{race}" #, fuzzy msgid "" "'Spread' combat formation spreads the castle's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "Wybór luźnej formacji oznacza, że twoja wojska bÄ™dÄ… stać rozproszone wzdÅ‚uż " "pola bitwy, a pomiÄ™dzy oddziaÅ‚ami bÄ™dzie przynajmniej jedno wolne pole." #, fuzzy msgid "" "'Grouped' combat formation bunches the castle's units together in the center " "of the castle's side of the battlefield." msgstr "" "Wybór zwartej formacji sprawia, że twoja armia zbierze siÄ™ razem poÅ›rodku " "twojej części pola bitwy." msgid "Spread Formation" msgstr "Luźna formacja" msgid "Grouped Formation" msgstr "Zwarta formacja" msgid "Set garrison combat formation to 'Spread'" msgstr "Ustaw szyk garnizonu na 'luźny'" msgid "Set garrison combat formation to 'Grouped'" msgstr "Ustaw szyk garnizonu na 'zwarty'" msgid "Exit Castle Options" msgstr "WyjÅ›cie z zamku" msgid "Castle Options" msgstr "Opcje zamku" msgid "Not enough resources to recruit creatures." msgstr "Brak zasobów aby kupić stworzenia." msgid "You are unable to recruit at this time, your ranks are full." msgstr "Nie możesz teraz rekrutować, w twojej armii nie ma już miejsca." msgid "No creatures available for purchase." msgstr "Brak stworzeÅ„ do zakupienia." msgid "Recruit Creatures" msgstr "Rekrutuj stworzenia" msgid "Max" msgstr "Maks" msgid "Hire all creatures in the town." msgstr "Zwerbuj wszystkie jednostki w tym mieÅ›cie." msgid "Available" msgstr "DostÄ™pne" msgid "Town Population Information and Statistics" msgstr "Statystyki populacji miasta" msgid "Damage" msgstr "Obrażenia" msgid "HP" msgstr "PÅ»" msgid "Growth" msgstr "Przyrost" msgid "week" msgstr "tydzieÅ„" msgid "View World" msgstr "Pokaż Å›wiat" msgid "View the entire world." msgstr "Pokaż caÅ‚y Å›wiat." msgid "Puzzle" msgstr "Pokaż ukÅ‚adankÄ™" msgid "View the obelisk puzzle." msgstr "Pokaż wzór na obelisku." msgid "Scenario Information" msgstr "Informacje o scenariuszu" msgid "View information on the scenario you are currently playing." msgstr "Obejrzyj informacje na temat scenariusza, w który obecnie grasz." msgid "Dig for the Ultimate Artifact." msgstr "Kop w poszukiwaniu Najpotężniejszego Artefaktu." msgid "Digging" msgstr "Poszukiwanie" msgid "Arena" msgstr "Arena" #, fuzzy msgid "" "You enter the arena and face a pack of vicious lions. You handily defeat " "them, to the wild cheers of the crowd. Impressed by your skill, the aged " "trainer of gladiators agrees to train you in a skill of your choice." msgstr "" "Wkraczasz na arenÄ™ i stajesz oko w oko z wÅ›ciekÅ‚ymi lwami. Przy gÅ‚oÅ›nym " "aplauzie gawiedzi pokonujesz je po zaciekÅ‚ej walce. Stary trener gladiatorów " "proponuje ci pomoc w rozwiniÄ™ciu wybranej przez ciebie umiejÄ™tnoÅ›ci." msgid "Attack Skill" msgstr "Atak" msgid "Defense Skill" msgstr "Obrona" msgid "Shots" msgstr "StrzaÅ‚y" msgid "Shots Left" msgstr "PozostaÅ‚e strzaÅ‚y" msgid "Hit Points" msgstr "Punkty życia" msgid "Hit Points Left" msgstr "Aktualne PÅ»" msgid "You can't afford to upgrade your troops!" msgstr "Nie stać ciÄ™ na ulepszenie oddziałów!" msgid "" "Your troops can be upgraded, but it will cost you dearly. Do you wish to " "upgrade them?" msgstr "" "Możesz ulepszyć swoje oddziaÅ‚y, ale bÄ™dzie ciÄ™ to sÅ‚ono kosztować. Czy " "chcesz je ulepszyć?" msgid "Are you sure you want to dismiss this army?" msgstr "Czy na pewno chcesz zwolnić ten oddziaÅ‚?" msgid "Upgrade" msgstr "Ulepsz" msgid "Upgrade your troops." msgstr "Ulepsz oddziaÅ‚." msgid "Dismiss" msgstr "Zwolnij" msgid "Dismiss this army." msgstr "Zwolnij ten oddziaÅ‚." msgid "" "A group of %{monster} with a desire for greater glory wish to join you.\n" "Do you accept?" msgstr "" "ChcÄ… siÄ™ do ciebie przyłączyć %{monster}, pragnÄ…c zyskać chwałę.\n" "Zgadzasz siÄ™?" msgid "Followers" msgstr "SprzymierzeÅ„cy" msgid "" "The %{monster} is swayed by your diplomatic tongue, and offers to join your " "army for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "%{monster} jest pod wrażeniem twej dyplomatycznej przemowy i proponuje, że " "przyłączy siÄ™ do twej armii za cenÄ™ %{gold} szt. zÅ‚ota. Zgadzasz siÄ™?" msgid "" "The creatures are swayed by your diplomatic\n" "tongue, and make you an offer:\n" "\n" msgstr "" "Przeciwnicy sÄ… pod wrażeniem twej dyplomatycznej przemowy i skÅ‚adajÄ… ci " "ofertÄ™:\n" "\n" msgid "" "%{offer} of the %{total} %{monster} will join your army, and the rest will " "leave you alone, for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "%{offer} z %{total} %{monster} chce dołączyć do twojej armii, zaÅ› reszta " "zostawi ciÄ™ w spokoju za cenÄ™ %{gold} szt. zÅ‚ota.\n" "Zgadzasz siÄ™?" msgid "" "All %{offer} of the %{monster} will join your army for the sum of %{gold} " "gold.\n" "Do you accept?" msgstr "" "%{offer} %{monster} chce dołączyć do twojej armii za cenÄ™ %{gold} szt. " "zÅ‚ota.\n" "Zgadzasz siÄ™?" msgid "(Rate: %{percent})" msgstr "(Przyłącza siÄ™ %{percent}% oddziaÅ‚u)" msgid "off" msgstr "wyłączone" msgid "Music" msgstr "Muzyka" msgid "Effects" msgstr "Efekty" msgid "MIDI" msgstr "MIDI" msgid "MIDI Expansion" msgstr "MIDI z dodatku" msgid "External" msgstr "ZewnÄ™trzna" msgid "Music Type" msgstr "Rodzaj muzyki" msgid "3D Audio" msgstr "DźwiÄ™k 3D" msgid "Toggle ambient music level." msgstr "ZmieÅ„ poziom gÅ‚oÅ›noÅ›ci muzyki." msgid "Toggle foreground sounds level." msgstr "ZmieÅ„ poziom gÅ‚oÅ›noÅ›ci efektów." msgid "Change the type of music." msgstr "ZmieÅ„ rodzaj muzyki." msgid "Toggle 3D effects of foreground sounds." msgstr "Włącz lub wyłącz efekty dźwiÄ™kowe 3D." msgid "Build a new ship:" msgstr "Zbuduj nowy statek:" msgid "Resource cost:" msgstr "Koszt:" msgid "Total: " msgstr "Masz: " msgid "Need: " msgstr "Potrzeba: " msgid "Load Game" msgstr "Wczytaj grÄ™" msgid "No save files to load." msgstr "Brak zapisanych gier do wczytania." msgid "New Game" msgstr "Nowa gra" msgid "Start a single or multi-player game." msgstr "Rozpocznij grÄ™ dla jednego lub wielu graczy." msgid "Load a previously saved game." msgstr "Wczytaj uprzednio zapisanÄ… grÄ™." msgid "Save Game" msgstr "Zapisz grÄ™" msgid "Save the current game." msgstr "Zapisz bieżącÄ… grÄ™." msgid "Quit" msgstr "WyjÅ›cie" msgid "Quit out of Heroes of Might and Magic II." msgstr "ZakoÅ„czenie gry Heroes of Might and Magic II." msgid "Language" msgstr "JÄ™zyk" msgid "Graphics" msgstr "Grafika" msgid "Black & White" msgstr "Czarno-biaÅ‚y" msgid "Mouse Cursor" msgstr "Kursor myszki" msgid "Color" msgstr "Kolor" msgid "Text Support" msgstr "Dodatkowe informacje" msgid "Change the language of the game." msgstr "ZmieÅ„ jÄ™zyk gry." msgid "Select Game Language" msgstr "Wybierz jÄ™zyk gry" msgid "Change the graphics settings of the game." msgstr "ZmieÅ„ ustawienia grafiki gry." msgid "Toggle colored cursor on or off. This is only an aesthetic choice." msgstr "" "ZmieÅ„ kursor na kolorowy lub czarno-biaÅ‚y. Wybór ma jedynie charakter " "estetyczny." msgid "" "Toggle text support mode to output extra information about windows and " "events in the game." msgstr "Włącza lub wyłącza dodatkowe informacje o oknach i zdarzeniach w grze." msgid "" "Map\n" "Difficulty" msgstr "" "Trudność\n" "mapy" msgid "" "Game\n" "Difficulty" msgstr "" "Trudność\n" "gry" msgid "Rating" msgstr "Ocena" msgid "Map Size" msgstr "" "Wielkość\n" "mapy" msgid "Opponents" msgstr "Przeciwnicy" msgid "Class" msgstr "Klasa" msgid "" "Victory\n" "Conditions" msgstr "" "Warunki\n" "zwyciÄ™stwa" msgid "" "Loss\n" "Conditions" msgstr "" "Warunki\n" "przegranej" msgid "First select recipients!" msgstr "Wskaż najpierw odbiorców!" msgid "You cannot select %{resource}!" msgstr "Nie możesz wybrać %{resource}!" msgid "Select count %{resource}:" msgstr "Wybierz ilość %{resource}:" msgid "Select Recipients" msgstr "Wybierz odbiorcÄ™" msgid "Your Funds" msgstr "Twoje zasoby" msgid "Planned Gift" msgstr "Przekazujesz" msgid "Gift from %{name}" msgstr "Dar od %{name}" msgid "Resolution" msgstr "Rozdzielczość" msgid "Fullscreen" msgstr "PeÅ‚ny ekran" msgid "window|Mode" msgstr "Widok" msgid "Windowed" msgstr "W oknie" msgid "V-Sync" msgstr "V-Sync" msgid "on" msgstr "włączone" msgid "FPS" msgstr "FPS" msgid "System Info" msgstr "Dodatkowe informacje" msgid "Change the resolution of the game." msgstr "ZmieÅ„ rozdzielczość gry." msgid "Select Game Resolution" msgstr "Wybierz rozdzielczość gry" msgid "Toggle between fullscreen and windowed modes." msgstr "Przełącz pomiÄ™dzy peÅ‚nym ekranem w widokiem w oknie." msgid "" "The V-Sync option can be enabled to resolve flickering issues on some " "monitors." msgstr "Włączenie V-Sync może rozwiÄ…zać problemy z migocÄ…cym ekranem." msgid "Show extra information such as FPS and current time." msgstr "Pokaż dodatkowe informacje jak na przykÅ‚ad FPS lub aktualnÄ… godzinÄ™." msgid "Hotkey: " msgstr "" msgid "Category: " msgstr "" #, fuzzy msgid "Event: " msgstr "Zdarzenie" msgid "Hot Keys:" msgstr "Skróty klawiszowe:" msgid "Evil" msgstr "ZÅ‚y" msgid "Good" msgstr "Dobry" msgid "Interface Type" msgstr "Typ interfejsu" msgid "Hide" msgstr "Ukryj" msgid "Show" msgstr "Pokaż" msgid "Interface" msgstr "Interfejs" msgid "Slow" msgstr "Wolno" msgid "Normal" msgstr "Normalnie" msgid "Fast" msgstr "Szybko" msgid "Very Fast" msgstr "Bardzo szybko" msgid "Scroll Speed" msgstr "PrÄ™dkość przewijania" msgid "Toggle the type of interface you want to use." msgstr "ZmieÅ„ typ interfejsu." msgid "Toggle interface visibility." msgstr "ZmieÅ„ widoczność interfejsu." msgid "Toggle colored cursor on or off. This is only an esthetic choice." msgstr "" "ZmieÅ„ kursor na kolorowy lub czarno-biaÅ‚y. Wybór ma jedynie charakter " "estetyczny." msgid "Sets the speed at which you scroll the window." msgstr "ZmieÅ„ prÄ™dkość przewijania okna." msgid "Select Game Language:" msgstr "Wybierz jÄ™zyk gry:" msgid "Click to choose the selected language." msgstr "Kliknij tutaj, aby ustawić wybrany jÄ™zyk." msgid "%{name} has gained a level." msgstr "%{name} zyskuje poziom." msgid "%{skill} +1" msgstr "%{skill} +1" msgid "You have learned %{skill}." msgstr "NauczyÅ‚eÅ› siÄ™ %{skill}." #, fuzzy msgid "" "%{name} has gained a level.\n" "\n" "%{skill} +1" msgstr "%{name} zyskuje poziom." #, fuzzy msgid "" "You may learn either:\n" "%{skill1}\n" "or\n" "%{skill2}" msgstr "Możesz nauczyć siÄ™:" msgid "" "Please inspect our fine wares. If you feel like offering a trade, click on " "the items you wish to trade with and for." msgstr "" "ProszÄ™, zapoznaj siÄ™ z naszÄ… ofertÄ…. JeÅ›li masz zamiar handlować, kliknij na " "towarach które chcesz wymienić." msgid "" "You have received quite a bargain. I expect to make no profit on the deal. " "Can I interest you in any of my other wares?" msgstr "" "UdaÅ‚o ci siÄ™ ubić niezÅ‚y interes. Chyba na tym nic nie zarobiÄ™. Może " "interesujÄ… ciÄ™ inne moje towary?" msgid "I can offer you %{count} for 1 unit of %{resfrom}." msgstr "MogÄ™ ci zaproponować %{count} za 1 szt. %{resfrom}." msgid "I can offer you 1 unit of %{resto} for %{count} units of %{resfrom}." msgstr "MogÄ™ ci zaproponować 1 szt. %{resto} za %{count} szt. %{resfrom}." msgid "Min" msgstr "Min" msgid "Qty to trade" msgstr "Ilość" msgid "Trading Post" msgstr "Faktoria handlowa" msgid "Your Resources" msgstr "Twoje surowce" msgid "Available Trades" msgstr "Oferta" msgid "n/a" msgstr "nd." msgid "guarded by %{count} %{monster}" msgstr "strzeżona przez %{count} %{monster}" msgid "guarded by " msgstr "strzeżone przez " msgid "(available: %{count})" msgstr "(dostÄ™pne: %{count})" msgid "(empty)" msgstr "(nie ma nikogo)" msgid "already learned" msgstr "już to umiesz" #, fuzzy msgid "treeOfKnowledge|free" msgstr "Drzewo Wiedzy" #, fuzzy msgid "already claimed" msgstr "już to umiesz" msgid "not claimed" msgstr "" msgid "already knows this skill" msgstr "już znasz tÄ™ umiejÄ™tność" msgid "already has max skills" msgstr "posiadasz maksimum umiejÄ™tnoÅ›ci" msgid "(already visited)" msgstr "(już odwiedzono)" msgid "(not visited)" msgstr "(nie odwiedzono)" msgid "%{color} Barrier" msgstr "Bariera - kolor %{color}" #, fuzzy msgid "(tent visited)" msgstr "(nie odwiedzono)" msgid "%{color} Tent" msgstr "%{color} namiot podróżnika" msgid "Road" msgstr "Droga" msgid "(digging ok)" msgstr "(można kopać)" msgid "(no digging)" msgstr "(nie można kopać)" msgid "penalty: %{cost}" msgstr "pkt. ruchu: %{cost}" msgid "Defenders:" msgstr "ObroÅ„cy:" msgid "Unknown" msgstr "Nieznany" msgid "%{name} (Level %{level})" msgstr "%{name} (poziom %{level})" msgid "Attack:" msgstr "Atak:" msgid "Defense:" msgstr "Obrona:" msgid "Spell Power:" msgstr "Moc zaklęć:" msgid "Knowledge:" msgstr "Wiedza:" msgid "Spell Points:" msgstr "Pkt. magii:" msgid "Move Points:" msgstr "Pkt. ruchu:" msgid "Uncharted Territory" msgstr "Niezbadane terytorium" msgid "Cost per troop:" msgstr "Koszt jednostki:" msgid "Available: %{count}" msgstr "DostÄ™pne: %{count}" msgid "Number to buy:" msgstr "Rekrutuj:" msgid "Recruit selected monsters." msgstr "Rekrutuj wybrane stworzenia." msgid "Select maximum monsters to be recruited." msgstr "Ustaw maksymalnÄ… liczbÄ™ stworzeÅ„ możliwych do zrekrutowania." msgid "Select only 1 monster to be recruited." msgstr "Ustaw jedno stworzenie do zrekrutowania." msgid "Select Game Resolution:" msgstr "Wybierz rozdzielczość gry:" msgid "Click to apply the selected resolution." msgstr "Użyj wskazanej rozdzielczoÅ›ci." msgid "Click to apply the entered text." msgstr "Zatwierdź podany tekst." msgid "Click to open the Virtual Keyboard dialog." msgstr "Kliknij, aby otworzyć wirtualnÄ… klawiaturÄ™." msgid "Open Virtual Keyboard" msgstr "Otwórz wirtualnÄ… klawiaturÄ™" msgid "How many troops to move?" msgstr "Ile jednostek przenieść?" msgid "Fast separation into slots:" msgstr "Szybki podziaÅ‚ na sloty:" msgid "Map: " msgstr "Nazwa mapy: " msgid "" "\n" "\n" "Month: " msgstr "" "\n" "\n" "MiesiÄ…c: " msgid ", Week: " msgstr ", TydzieÅ„: " msgid ", Day: " msgstr ", DzieÅ„: " msgid "" "\n" "\n" "Location: " msgstr "" "\n" "\n" "PoÅ‚ożenie pliku: " msgid "Click to save the current game." msgstr "Zapisz bieżącÄ… grÄ™." msgid "Click to load a previously saved game." msgstr "Wczytaj uprzednio zapisanÄ… grÄ™." msgid "Are you sure you want to delete file:" msgstr "Czy na pewno chcesz usunąć plik:" msgid "Warning!" msgstr "Uwaga!" msgid "File to Save:" msgstr "Zapisz plik:" msgid "File to Load:" msgstr "Wczytaj plik:" msgid "Accept the choice made." msgstr "Zastosuj wprowadzone zmiany." #, fuzzy msgid "%{color} %{race} hero" msgstr "%{color} gracz" #, fuzzy msgid "Terrain object" msgstr "Brak obiektu" msgid "Select Skill:" msgstr "Wybierz umiejÄ™tność:" msgid "Select Spell:" msgstr "Wybierz zaklÄ™cie:" msgid "Select Artifact:" msgstr "Wybierz artefakt:" msgid "Select Monster:" msgstr "Wybierz potwora:" msgid "Select Hero:" msgstr "Wybierz bohatera:" #, fuzzy msgid "Select Treasure:" msgstr "Wybierz bohatera:" #, fuzzy msgid "Select Ocean Object:" msgstr "Wybierz artefakt:" #, fuzzy msgid "Castle/town placing" msgstr "Opcje zamku" msgid "doubleLinedRace|Neutral" msgstr "Neutralny" msgid "race|Random" msgstr "Losowo" #, fuzzy msgid "Click to start placing the selected castle/town." msgstr "Użyj wskazanej rozdzielczoÅ›ci." #, fuzzy msgid "Click to select town placing." msgstr "Kliknij aby przejść do nastÄ™pnego miasta." #, fuzzy msgid "Click to select castle placing." msgstr "Kliknij tutaj, aby wybrać scenariusz, który chcesz rozegrać." #, fuzzy msgid "%{color} %{race} %{townOrCastle}" msgstr "%{color} gracz" msgid "race|Neutral" msgstr "Neutralny" msgid "You will place" msgstr "" #, fuzzy msgid "Click to select this class." msgstr "Kliknij tutaj, aby wybrać scenariusz, który chcesz rozegrać." #, fuzzy msgid "Click to select this color." msgstr "Kliknij tutaj, aby wybrać scenariusz, który chcesz rozegrać." #, fuzzy msgid "Select Dwelling:" msgstr "Wybierz zaklÄ™cie:" #, fuzzy msgid "Select Landscape Object:" msgstr "Wybierz artefakt:" msgid "Mine placing" msgstr "" #, fuzzy msgid "" "Resource\n" "type:" msgstr "Surowiec" #, fuzzy msgid "%{mineName} appearance:" msgstr "%{name}, %{race}" #, fuzzy msgid "Click to select %{object} as the resource generator to be placed." msgstr "Kliknij tutaj, aby wybrać scenariusz, który chcesz rozegrać." #, fuzzy msgid "Select Mountain Object:" msgstr "Wybierz artefakt:" #, fuzzy msgid "Select Rock Object:" msgstr "Wybierz artefakt:" #, fuzzy msgid "Select Tree Object:" msgstr "Wybierz artefakt:" #, fuzzy msgid "Select Power Up Object:" msgstr "Wybierz artefakt:" #, fuzzy msgid "Select Adventure Object:" msgstr "Wybierz artefakt:" msgid "Map Type:\n" msgstr "Typ Mapy:\n" msgid "The Succession Wars" msgstr "Wojna o SukcesjÄ™" msgid "The Price of Loyalty" msgstr "Cena LojalnoÅ›ci" #, fuzzy msgid "Resurrection" msgstr "Wskrzeszenie" msgid "Lose all your heroes and towns." msgstr "Utrata wszystkich bohaterów i miast." msgid "Lose a specific town." msgstr "Utrata okreÅ›lonego miasta." msgid "Lose a specific hero." msgstr "Utrata okreÅ›lonego bohatera." msgid "Run out of time. Fail to win by a certain point." msgstr "UpÅ‚yw czasu. NieukoÅ„czenie gry w okreÅ›lonym czasie." msgid "Loss Condition" msgstr "Warunki porażki" msgid "Defeat all enemy heroes and towns." msgstr "Pokonaj wszystkich wrogów i miasta." msgid "Capture a specific town." msgstr "ZdobÄ…dź okreÅ›lone miasto." msgid "Defeat a specific hero." msgstr "Pokonaj okreÅ›lonego bohatera." msgid "Find a specific artifact." msgstr "Znajdź okreÅ›lony artefakt." msgid "Your side defeats the opposing side." msgstr "Twoja strona pokonuje przeciwnÄ…." msgid "Accumulate a large amount of gold." msgstr "Zgromadź dużą ilość zÅ‚ota." msgid "Victory Condition" msgstr "Warunki zwyciÄ™stwa" msgid "Map difficulty:" msgstr "Poziom:" msgid "N" msgstr "N" msgid "No maps exist at that size" msgstr "Brak map o tym rozmiarze" msgid "Small Maps" msgstr "MaÅ‚e mapy" msgid "View only maps of size small (36 x 36)." msgstr "Pokaż tylko maÅ‚e mapy (36 x 36)." msgid "Medium Maps" msgstr "Åšrednie mapy" msgid "View only maps of size medium (72 x 72)." msgstr "Pokaż tylko Å›rednie mapy (72 x 72)." msgid "Large Maps" msgstr "Duże mapy" msgid "View only maps of size large (108 x 108)." msgstr "Pokaż tylko duże mapy (108 x 108)." msgid "Extra Large Maps" msgstr "Bardzo duże mapy" msgid "View only maps of size extra large (144 x 144)." msgstr "Pokaż tylko bardzo duże mapy (144 x 144)." msgid "All Maps" msgstr "Wszystkie mapy" msgid "View all maps, regardless of size." msgstr "Pokaż wszystkie mapy, niezależnie od wielkoÅ›ci." msgid "Players Icon" msgstr "Ikona graczy" msgid "" "Indicates how many players total are in the scenario. Any positions not " "occupied by humans will be occupied by computer players." msgstr "" "Pokazuje ilu graczy bierze udziaÅ‚ w tym scenariuszu. Każde miejsce nie " "zajÄ™te przez ludzi, zostanie zajÄ™te przez komputer." msgid "" "Indicates whether the map\n" "is small (36 x 36), medium\n" "(72 x 72), large (108 x 108),\n" "or extra large (144 x 144)." msgstr "" "Wskazuje czy mapa jest maÅ‚a (36 x 36), Å›rednia (72 x 72), duża (108 x 108), " "czy też bardzo duża (144 x 144)." msgid "Size Icon" msgstr "Ikona wielkoÅ›ci" msgid "" "Indicates whether the map is made for \"The Succession Wars\" or \"The Price " "of Loyalty\" version of the game." msgstr "Wskazuje czy mapa jest z Wojny o SukcesjÄ™ czy z Ceny LojalnoÅ›ci." msgid "Map Type" msgstr "Typ mapy" msgid "Selected Name" msgstr "Wybrana nazwa" msgid "The name of the currently selected map." msgstr "Nazwa aktualnie wybranej mapy." msgid "Selected Map Difficulty" msgstr "Trudność wybranej mapy" #, fuzzy msgid "" "The map difficulty of the currently selected map. The map difficulty is " "determined by the scenario designer. More difficult maps might include more " "or stronger enemies, fewer resources, or other special conditions making " "things tougher for the human player." msgstr "" "Poziom trudnoÅ›ci na aktualnie wybranej mapie, okreÅ›lony przez autora " "scenariusza. Na bardziej zÅ‚ożonych mapach mogÄ… wystÄ™pować silniejsi " "przeciwnicy, mniej zasobów lub inne specjalne warunki utrudniajÄ…ce rozgrywkÄ™." msgid "Selected Description" msgstr "Wybrany opis" msgid "The description of the currently selected map." msgstr "Opis aktualnie wybranej mapy." msgid "Jump" msgstr "Skok" msgid "Hero Speed" msgstr "Szybkość" msgid "Don't Show" msgstr "Nie pokazuj" msgid "Enemy Speed" msgstr "Szybk. wroga" msgid "Auto Resolve" msgstr "Automatyczna" msgid "Auto, No Spells" msgstr "Auto, bez zaklęć" msgid "Battles" msgstr "Walki" msgid "autoBattle|Manual" msgstr "RÄ™czna" msgid "Change the speed at which your heroes move on the main screen." msgstr "ZmieÅ„ szybkość z jakÄ… poruszajÄ… siÄ™ twoi bohaterowie na ekranie." #, fuzzy msgid "" "Sets the speed that A.I. heroes move at. You can also elect not to view A.I. " "movement at all." msgstr "" "ZmieÅ„ szybkość, z jakÄ… poruszajÄ… siÄ™ bohaterowie sterowani przez A.I. Możesz " "również wyłączyć podglÄ…d ruchów A.I." #, fuzzy msgid "Change the interface settings of the game." msgstr "ZmieÅ„ ustawienia grafiki gry." #, fuzzy msgid "Interface Settings" msgstr "Ustawienia gry" msgid "Toggle instant battle mode." msgstr "ZmieÅ„ tryb walki." msgid "Att." msgstr "Atk." msgid "Def." msgstr "Obr." msgid "Power" msgstr "Moc" msgid "Knowl" msgstr "Wiedz" msgid "1st" msgstr "1." msgid "2nd" msgstr "2." msgid "3rd" msgstr "3." msgid "4th" msgstr "4." msgid "5th" msgstr "5." msgid "6th" msgstr "6." msgid "Oracle: Player Rankings" msgstr "Wyrocznia - ranking graczy" msgid "Thieves' Guild: Player Rankings" msgstr "Gildia zÅ‚odziei - ranking graczy" msgid "Number of Towns:" msgstr "Liczba miast:" msgid "Number of Castles:" msgstr "Liczba zamków:" msgid "Number of Heroes:" msgstr "Liczba bohaterów:" msgid "Gold in Treasury:" msgstr "ZÅ‚oto w skarbcu:" msgid "Wood & Ore:" msgstr "Drewno i ruda:" msgid "Gems, Cr, Slf & Mer:" msgstr "Klejn., kr., siar. i rtęć:" msgid "Obelisks Found:" msgstr "Znalezione obeliski:" msgid "Artifacts:" msgstr "Artefakty:" msgid "Total Army Strength:" msgstr "CaÅ‚kowita siÅ‚a armii:" msgid "Income:" msgstr "Przychód:" msgid "Best Hero:" msgstr "Najlepszy bohater:" msgid "Best Hero Stats:" msgstr "Najlepsze cechy:" msgid "Personality:" msgstr "Osobowość:" msgid "Best Monster:" msgstr "Najlepszy potwór:" #, fuzzy msgid "Map filename" msgstr "" "Wielkość\n" "mapy" #, fuzzy msgid "" "Are you sure you want to load a new map? (Any unsaved changes to the current " "map will be lost.)" msgstr "" "Czy na pewno chcesz wczytać nowÄ… grÄ™? (aktualna rozgrywka zostanie przerwana)" #, fuzzy msgid "" "Are you sure you want to create a new map? (Any unsaved changes to the " "current map will be lost.)" msgstr "" "Czy na pewno chcesz wczytać nowÄ… grÄ™? (aktualna rozgrywka zostanie przerwana)" #, fuzzy msgid "" "Create a new map, either from scratch or using the random map generator." msgstr "Utwórz mapÄ™ albo od poczÄ…tku albo używajÄ…c generatora losowych map." msgid "New Map" msgstr "Nowa Mapa" msgid "Load Map" msgstr "Wczytaj MapÄ™" msgid "Load an existing map." msgstr "Wczytaj istniejÄ…cÄ… mapÄ™." #, fuzzy msgid "Save Map" msgstr "Zapisz grÄ™" #, fuzzy msgid "Save the current map." msgstr "Zapisz bieżącÄ… grÄ™." msgid "Quit out of the map editor." msgstr "" #, fuzzy msgid "Objects cannot be placed outside the map." msgstr "To zaklÄ™cie nie może być rzucone z Å‚odzi." msgid "Monsters cannot be placed on water." msgstr "" msgid "Choose a tile which does not contain any objects." msgstr "" #, fuzzy msgid "Treasures cannot be placed on water." msgstr "To zaklÄ™cie nie może być rzucone z Å‚odzi." msgid "Heroes cannot be placed on water." msgstr "" msgid "" "A maximum of %{count} heroes of the same color can be placed on the map." msgstr "" #, fuzzy msgid "Artifacts cannot be placed on water." msgstr "To zaklÄ™cie nie może być rzucone z Å‚odzi." #, fuzzy msgid "Mountains cannot be placed on water." msgstr "To zaklÄ™cie nie może być rzucone z Å‚odzi." #, fuzzy msgid "Rocks cannot be placed on water." msgstr "To zaklÄ™cie nie może być rzucone z Å‚odzi." #, fuzzy msgid "Trees cannot be placed on water." msgstr "To zaklÄ™cie nie może być rzucone z Å‚odzi." #, fuzzy msgid "Ocean object must be placed on water." msgstr "To zaklÄ™cie nie może być rzucone z Å‚odzi." #, fuzzy msgid "Landscape objects cannot be placed on water." msgstr "To zaklÄ™cie nie może być rzucone z Å‚odzi." #, fuzzy msgid "Towns cannot be placed on water." msgstr "To zaklÄ™cie nie może być rzucone z Å‚odzi." #, fuzzy msgid "Mines cannot be placed on water." msgstr "To zaklÄ™cie nie może być rzucone z Å‚odzi." #, fuzzy msgid "Dwellings cannot be placed on water." msgstr "To zaklÄ™cie nie może być rzucone z Å‚odzi." #, fuzzy msgid "Power-ups cannot be placed on water." msgstr "To zaklÄ™cie nie może być rzucone z Å‚odzi." #, fuzzy msgid "Adventure objects cannot be placed on water." msgstr "To zaklÄ™cie nie może być rzucone z Å‚odzi." #, fuzzy msgid "Used to place %{object}." msgstr "Znajdź najpotężniejszy artefakt." #, fuzzy msgid "Select object type" msgstr "Wybierz artefakt:" #, fuzzy msgid "" "Click here to\n" "select a monster." msgstr "Kliknij tutaj, aby wybrać scenariusz, który chcesz rozegrać." #, fuzzy msgid "" "Click here to\n" "select another monster." msgstr "Wybierz innego bohatera." msgid "" "Cell\n" "Details" msgstr "" #, fuzzy msgid "Roads" msgstr "Droga" msgid "Streams" msgstr "" msgid "Erase" msgstr "" msgid "Mountains" msgstr "Góry" #, fuzzy msgid "Rocks" msgstr "KamieÅ„" msgid "Trees" msgstr "Drzewa" #, fuzzy msgid "Water Objects" msgstr "WyschniÄ™ty" #, fuzzy msgid "Miscellaneous" msgstr "Fineous" msgid "Artifacts" msgstr "Artefakty" #, fuzzy msgid "Dwellings" msgstr "NizioÅ‚ki" msgid "Mines" msgstr "Kopalnia" #, fuzzy msgid "Power-ups" msgstr "Moc" msgid "Treasures" msgstr "Skarby" msgid "Towns" msgstr "Miasta" msgid "Heroes" msgstr "Bohaterowie" msgid "editorErasure|Terrain" msgstr "" msgid "editorErasure|Castles" msgstr "" msgid "editorErasure|Monsters" msgstr "" msgid "editorErasure|Heroes" msgstr "" msgid "editorErasure|Artifacts" msgstr "" msgid "editorErasure|Roads" msgstr "" msgid "editorErasure|Streams" msgstr "" msgid "editorErasure|Treasures" msgstr "" msgid "" "Draws terrain in\n" "%{size} by %{size} square increments." msgstr "" msgid "" "Erases objects in\n" "%{size} by %{size} square increments." msgstr "" #, fuzzy msgid "Small Brush" msgstr "MaÅ‚e mapy" #, fuzzy msgid "Medium Brush" msgstr "Åšrednie mapy" #, fuzzy msgid "Large Brush" msgstr "Duże mapy" msgid "Area Fill" msgstr "" msgid "Used to click and drag for filling in large areas." msgstr "" msgid "Clear Area" msgstr "" msgid "Used to click and drag for clearing large areas." msgstr "" msgid "" "Costs %{rate} times normal movement for all heroes. (Pathfinding reduces or " "eliminates the penalty.)" msgstr "" msgid "Traversable only by boat." msgstr "" msgid "No special modifiers." msgstr "" msgid "Toggle the erasure of %{type} objects." msgstr "" msgid "" "Objects of this type will be deleted with the Erase tool. Left-click here to " "deselect this type. Press and hold this button to deselect all other object " "types." msgstr "" msgid "" "Objects of this type will NOT be deleted with the Erase tool. Left-click " "here to select this type. Press and hold this button to select all other " "object types." msgstr "" msgid "Terrain Mode" msgstr "" msgid "Used to draw the underlying grass, dirt, water, etc. on the map." msgstr "" msgid "Landscape Objects Mode" msgstr "" msgid "" "Used to place landscape objects (mountains, rocks, trees, etc.) on the map." msgstr "" msgid "Detail Mode" msgstr "" #, fuzzy msgid "Used for special editing of monsters, heroes and towns." msgstr "Pokonaj wszystkich wrogów i miasta." #, fuzzy msgid "Adventure Objects Mode" msgstr "Opcje przygody" msgid "" "Used to place adventure objects (artifacts, dwellings, mines, treasures, " "etc.) on the map." msgstr "" #, fuzzy msgid "Kingdom Objects Mode" msgstr "Przychód" msgid "Used to place kingdom objects (towns, castles and heroes) on the map." msgstr "" #, fuzzy msgid "Monsters Mode" msgstr "Potwory" #, fuzzy msgid "Used to place monsters on the map." msgstr "Znajdź najpotężniejszy artefakt." msgid "Allows you to draw streams by clicking and dragging." msgstr "" msgid "Stream Mode" msgstr "" msgid "Allows you to draw roads by clicking and dragging." msgstr "" #, fuzzy msgid "Road Mode" msgstr "Droga" msgid "Erase Mode" msgstr "" msgid "Used to erase objects from the map." msgstr "" msgid "Change between zoom and normal view." msgstr "" msgid "Magnify" msgstr "" msgid "Undo" msgstr "" msgid "Undo your last action." msgstr "" msgid "Create a new map either from scratch or using the random map generator." msgstr "Utwórz mapÄ™ albo od poczÄ…tku albo używajÄ…c generatora losowych map." msgid "Edit map title, description, and other general information." msgstr "" #, fuzzy msgid "Specifications" msgstr "Zamiana w KamieÅ„" msgid "File Options" msgstr "Opcje plików" #, fuzzy msgid "" "Open the file options menu, where you can save or load maps, or quit out of " "the editor." msgstr "" "Pokazuje menu z opcjami plików, pozwalajÄ…c na: wczytanie gry, zapisanie gry " "rozpoczÄ™cie nowej gry lub wyjÅ›cie z bieżącej gry." #, fuzzy msgid "View the editor system options, which let you customize the editor." msgstr "" "Pokazuje menu z opcjami systemowymi, pozwalajÄ…c na zmianÄ™ ustawieÅ„ gry." msgid "Create a map that is %{size} squares wide and %{size} squares high." msgstr "Utwórz mapÄ™ o dÅ‚ugoÅ›ci %{size} i szerokoÅ›ci %{size}." msgid "Cancel back to the New Map menu." msgstr "Anuluj, aby wrócić do menu Nowa Mapa." msgid "Cancel back to the main menu." msgstr "Wróć do głównego menu." msgid "From Scratch" msgstr "Od poczÄ…tku" msgid "Start from scratch with a blank map." msgstr "Zacznij od poczÄ…tku pustÄ… mapÄ™." msgid "Create a randomly generated map." msgstr "Utwórz losowo wygenerowanÄ… mapÄ™." msgid "Random" msgstr "Losowa" msgid "Cancel back to the Map Editor main menu." msgstr "Anuluj, aby wrócić do głównego menu." msgid "No maps available!" msgstr "Mapy niedostÄ™pne!" msgid "Warning" msgstr "Uwaga" msgid "difficulty|Easy" msgstr "Åatwy" msgid "difficulty|Normal" msgstr "Normalny" msgid "difficulty|Hard" msgstr "Trudny" msgid "difficulty|Expert" msgstr "Ekspert" msgid "difficulty|Impossible" msgstr "Niemożliwy" msgid "and more..." msgstr "i wiÄ™cej..." msgid "Easy" msgstr "Åatwa" msgid "Hard" msgstr "Trudna" msgid "Campaign Difficulty" msgstr "StopieÅ„ trudnoÅ›ci kampanii" msgid "" "Choose this difficulty if you want to prefer game story over challenge. AI " "is weaker in comparison with normal difficulty." msgstr "" "Wybierz tÄ™ opcjÄ™ jeÅ›li cenisz fabułę ponad wyzwanie. AI rozgrywa misje " "gorzej niż przy normalnym poziomie trudnoÅ›ci." msgid "" "Choose this difficulty to enjoy the campaign as per the original design." msgstr "" "Wybierz tÄ™ opcjÄ™ by cieszyć siÄ™ kampaniÄ… zgodnie z jej oryginalnymi " "zaÅ‚ożeniami." msgid "" "Choose this difficulty if you want challenge. AI is stronger in comparison " "with normal difficulty." msgstr "" "Wybierz tÄ™ opcjÄ™ jeÅ›li jesteÅ› gotów na wyzwania. AI rozgrywa misje lepiej " "niż przy normalnym poziomie trudnoÅ›ci." msgid "" "Congratulations!\n" "\n" "Days: %{days}\n" msgstr "" "Gratulacje!\n" "\n" "Liczba dni: %{days}\n" msgid "" "\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" "\n" "Trudność: %{difficulty}\n" "\n" msgid "" "Score: %{score}\n" "\n" "Rating:\n" "%{rating}" msgstr "" "Wynik: %{score}\n" "\n" "Ranking:\n" "%{rating}" msgid "Start the selected scenario." msgstr "Rozpocznij wybrany scenariusz." msgid "View Intro" msgstr "Zobacz Wprowadzenie" msgid "View Intro videos for the current state of the campaign." msgstr "Zobacz filmy wprowadzajÄ…ce dla obecnego stanu kampanii." #, fuzzy msgid "" "Select the campaign difficulty. This can be lowered at any point during the " "campaign." msgstr "" "Wybierz stopieÅ„ trudnoÅ›ci. Tego ustawienia nie bÄ™dziesz mógÅ‚ później zmienić." msgid "Restart the current scenario." msgstr "Rozpocznij od nowa obecny scenariusz." #, fuzzy msgid "Difficulty" msgstr "" "Trudność\n" "mapy" msgid "" "You have changed to a lower difficulty for the campaign. You will not be " "able to revert this after this point. The high score will be calculated " "based solely on the new difficulty. Do you want to proceed?" msgstr "" msgid "Are you sure you want to restart this scenario?" msgstr "Czy na pewno chcesz rozpocząć od nowa obecny scenariusz?" msgid "Campaign Scenario loading failure" msgstr "Problem z uruchomieniem scenariusza kampanii" msgid "Please make sure that campaign files are correct and present." msgstr "Upewnij siÄ™, że pliki kampanii sÄ… poprawne." msgid "Days spent" msgstr "Ilość dni" msgid "The number of days spent on this campaign." msgstr "Ilość dni spÄ™dzonych na tej kampanii." msgid "Project Coordination and Core Development" msgstr "Koordynacja projektu i development" msgid "Development" msgstr "Development" msgid "Visit us at " msgstr "Odwiedź nas " msgid "QA and Support" msgstr "QA i wsparcie" msgid "Dev and Support" msgstr "Development i wsparcie" msgid "Special Thanks to" msgstr "Specjalnie podziÄ™kowania dla" msgid "and many other contributors!" msgstr "i wielu innych!" msgid "and many-many other supporters!" msgstr "i wielu, wielu innym!" msgid "Support us at" msgstr "Wesprzyj nas" msgid "local-donation-platform|https://www.patreon.com/fheroes2" msgstr "https://www.patreon.com/fheroes2" msgid "Connect with us at" msgstr "Odwiedź nas" msgid "local-social-network|https://www.facebook.com/groups/fheroes2" msgstr "https://www.facebook.com/groups/fheroes2" msgid "Need help with the game?" msgstr "Potrzebujesz wsparcia?" msgid "Original project before 0.7" msgstr "Przed wersjÄ… 0.7" msgid "Heroes of Might and Magic II: The Succession Wars team" msgstr "Zespół gry Heroes of Might and Magic II: The Succesion Wars" msgid "Designed and Directed" msgstr "Projekt i kierownictwo projektu" msgid "Programming and Design" msgstr "Programowanie i Projektowanie" msgid "Executive Producer" msgstr "Producent wykonawczy" msgid "Producer" msgstr "Producent" msgid "Additional Design" msgstr "Dodatkowe projekty" msgid "Additional Programming" msgstr "ProgramiÅ›ci" msgid "Musical Production" msgstr "Produkcja muzyczna" msgid "Music and Sound Design" msgstr "Muzyka i projekt dźwiÄ™ku" msgid "Vocalists" msgstr "Partie wokalne" msgid "Art Director" msgstr "Kierownik artystyczny" msgid "Assistant Art Director" msgstr "Asystent kierownika artystycznego" msgid "Artists" msgstr "Graficy" msgid "QA Manager" msgstr "Kierownik kontroli jakoÅ›ci" msgid "QA" msgstr "Kontrola jakoÅ›ci" msgid "Writing" msgstr "Teksty" msgid "Manual and Helpfile" msgstr "Instrukcja i plik pomocy" msgid "Scenarios" msgstr "Scenariusze" msgid "Heroes of Might and Magic II: The Price of Loyalty team" msgstr "Zespół gry Heroes of Might and Magic II: The Price of Loyalty" msgid "Design Lead" msgstr "Kierownik projektowania" msgid "Designers" msgstr "Projektanci" msgid "Programming Lead" msgstr "Kierownik programowania" msgid "Art Lead" msgstr "Kierownik artystów" msgid "Playtesters" msgstr "Testerzy gry" msgid "Designer" msgstr "Projektant" msgid "Producers" msgstr "Produkcja" msgid "QA Managers" msgstr "Kierownicy kontroli jakoÅ›ci" msgid "Sound Design" msgstr "Projektowanie DźwiÄ™ku" msgid "Town Themes" msgstr "Motywy muzyczne miast" msgid "Alto Sax" msgstr "Saksofon altowy" msgid "Harpsichord and Piano" msgstr "Klawesyn i fortepian" msgid "Basso Vocal" msgstr "Bas (partie wokalne)" msgid "Soprano Vocal" msgstr "Sopran (partie wokalne)" msgid "Recorded at %{recordingStudio}" msgstr "Nagrano w %{recordingStudio}" msgid "credits|Manual" msgstr "Przewodnik" msgid "German Consultant" msgstr "Konsultant ds. j. niemieckiego" msgid "Map Designers" msgstr "Projektanci Map" msgid "Package Design" msgstr "Projekt opakowania" msgid "Your Name" msgstr "Twoje imiÄ™" msgid "Unknown Hero" msgstr "Nieznany bohater" msgid "Standard" msgstr "Standard" msgid "View High Scores for Standard Maps." msgstr "Obejrzyj najlepsze wyniki dla pojedynczych scenariuszy." msgid "Campaign" msgstr "Kampania" msgid "View High Scores for Campaigns." msgstr "Obejrzyj najlepsze wyniki dla kampanii." msgid "hotkey|default okay event" msgstr "zatwierdź" msgid "hotkey|default cancel event" msgstr "anuluj" msgid "hotkey|default left" msgstr "w lewo" msgid "hotkey|default right" msgstr "w prawo" msgid "hotkey|default up" msgstr "w górÄ™" msgid "hotkey|default down" msgstr "w dół" msgid "hotkey|toggle fullscreen" msgstr "peÅ‚ny ekran/okno" msgid "hotkey|toggle text support mode" msgstr "wÅ‚./wyÅ‚. dodatkowe informacje" msgid "hotkey|new game" msgstr "nowa gra" msgid "hotkey|load game" msgstr "wczytaj grÄ™" msgid "hotkey|highscores" msgstr "najlepsze wyniki" msgid "hotkey|credits" msgstr "autorzy" msgid "hotkey|standard game" msgstr "gra standardowa" msgid "hotkey|campaign game" msgstr "kampania" msgid "hotkey|multi-player game" msgstr "gra dla wielu osób" msgid "hotkey|settings" msgstr "ustawienia" msgid "hotkey|quit" msgstr "wyjdź" msgid "hotkey|select map" msgstr "wybierz mapÄ™" msgid "hotkey|select small map size" msgstr "wybierz maÅ‚e mapy" msgid "hotkey|select medium map size" msgstr "wybierz Å›rednie mapy" msgid "hotkey|select large map size" msgstr "wybierz duże mapy" msgid "hotkey|select extra large map size" msgstr "wybierz bardzo duże mapy" msgid "hotkey|select all map sizes" msgstr "wybierz wszystkie mapy" msgid "hotkey|hotseat game" msgstr "zmiana miejsc" msgid "hotkey|battle only game" msgstr "tylko walka" msgid "hotkey|choose the original campaign" msgstr "wybierz oryginalnÄ… kampaniÄ™" msgid "hotkey|choose the expansion campaign" msgstr "wybierz kampaniÄ™ z rozszerzenia" msgid "hotkey|map editor main menu" msgstr "edytor map główne menu" msgid "hotkey|new map menu" msgstr "nowa mapa" msgid "hotkey|load map menu" msgstr "wczytaj mapÄ™" msgid "hotkey|new map from scratch" msgstr "nowa mapa od poczÄ…tku" msgid "hotkey|new random map" msgstr "nowa losowa mapa" #, fuzzy msgid "hotkey|undo last action" msgstr "domyÅ›lna akcja" #, fuzzy msgid "hotkey|redo last action" msgstr "domyÅ›lna akcja" msgid "hotkey|roland campaign" msgstr "kampania Rolanda" msgid "hotkey|archibald campaign" msgstr "kampania Archibalda" msgid "hotkey|the price of loyalty campaign" msgstr "kampania Cena LojalnoÅ›ci" msgid "hotkey|voyage home campaign" msgstr "kampania Podróż do Domu" msgid "hotkey|wizard's isle campaign" msgstr "kampania Wyspa Czarodzieja" msgid "hotkey|descendants campaign" msgstr "kampania Potomkowie" msgid "hotkey|select first campaign bonus" msgstr "pierwszy bonus w kampanii" msgid "hotkey|select second campaign bonus" msgstr "drugi bonus w kampanii" msgid "hotkey|select third campaign bonus" msgstr "trzeci bonus w kampanii" msgid "hotkey|view campaign intro" msgstr "obejrzyj intro kampanii" msgid "hotkey|select campaign difficulty" msgstr "stopieÅ„ trudnoÅ›ci kampanii" msgid "hotkey|restart campaign scenario" msgstr "rozpocznij od nowa scenariusz kampanii" msgid "hotkey|world map left" msgstr "porusz bohatera w lewo" msgid "hotkey|world map right" msgstr "porusz bohatera w prawo" msgid "hotkey|world map up" msgstr "porusz bohatera do góry" msgid "hotkey|world map down" msgstr "porusz bohatera w dów" msgid "hotkey|world map up left" msgstr "porusz bohatera do góry w lewo" msgid "hotkey|world map up right" msgstr "porusz bohatera do góry w prawo" msgid "hotkey|world map down left" msgstr "porusz bohatera w dół w lewo" msgid "hotkey|world map down right" msgstr "porusz bohatera w dół w prawo" msgid "hotkey|save game" msgstr "zapisz grÄ™" msgid "hotkey|next hero" msgstr "pokaż nastÄ™pnego bohatera" #, fuzzy msgid "hotkey|start hero movement" msgstr "kontynuuj ruch" msgid "hotkey|cast adventure spell" msgstr "rzuć zaklÄ™cie podróżne" msgid "hotkey|put hero to sleep" msgstr "uÅ›pij bohatera" msgid "hotkey|next town" msgstr "nastÄ™pne miasto" msgid "hotkey|end turn" msgstr "zakoÅ„cz turÄ™" msgid "hotkey|file options" msgstr "opcje plików" msgid "hotkey|adventure options" msgstr "opcje przygody" msgid "hotkey|puzzle map" msgstr "mapa puzzli z obelisków" msgid "hotkey|scenario information" msgstr "informacje o scenariuszu" msgid "hotkey|dig for artifact" msgstr "kop w poszukiwaniu Najpotężniejszego Artefaktu" msgid "hotkey|view world" msgstr "pokaż Å›wiat" msgid "hotkey|kingdom summary" msgstr "przeglÄ…d królestwa" msgid "hotkey|default action" msgstr "domyÅ›lna akcja" msgid "hotkey|open focus" msgstr "szczegóły bohatera lub zamku" msgid "hotkey|system options" msgstr "ustawienia systemowe" msgid "hotkey|scroll left" msgstr "skroluj w lewo" msgid "hotkey|scroll right" msgstr "skroluj w prawo" msgid "hotkey|scroll up" msgstr "skroluj w górÄ™" msgid "hotkey|scroll down" msgstr "skroluj w dół" msgid "hotkey|toggle control panel" msgstr "pokaż/ukryj panel sterowania" msgid "hotkey|toggle radar" msgstr "pokaż/ukryj minimapÄ™" msgid "hotkey|toggle buttons" msgstr "pokaż/ukryj przyciski" msgid "hotkey|toggle status" msgstr "pokaż/ukryj okno statusu" msgid "hotkey|toggle icons" msgstr "pokaż/ukryj ikony miast i bohaterów" msgid "hotkey|transfer control to ai" msgstr "przekaż kontrolÄ™ do AI" msgid "hotkey|retreat from battle" msgstr "ucieknij z walki" msgid "hotkey|surrender during battle" msgstr "poddaj walkÄ™" msgid "hotkey|toggle battle auto mode" msgstr "włącz/wyłącz automatyczny tryb walki" msgid "hotkey|finish the battle in auto mode" msgstr "zakoÅ„cz walkÄ™ w automatycznym trybie walki" msgid "hotkey|battle options" msgstr "opcje walki" msgid "hotkey|skip turn in battle" msgstr "pomiÅ„ turÄ™ w walce" msgid "hotkey|cast battle spell" msgstr "rzuć zaklÄ™cie bojowe" msgid "hotkey|dwelling level 1" msgstr "siedlisko poziom 1" msgid "hotkey|dwelling level 2" msgstr "siedlisko poziom 2" msgid "hotkey|dwelling level 3" msgstr "siedlisko poziom 3" msgid "hotkey|dwelling level 4" msgstr "siedlisko poziom 4" msgid "hotkey|dwelling level 5" msgstr "siedlisko poziom 5" msgid "hotkey|dwelling level 6" msgstr "siedlisko poziom 6" msgid "hotkey|well" msgstr "studnia" msgid "hotkey|marketplace" msgstr "targowisko" msgid "hotkey|mage guild" msgstr "gildia magów" msgid "hotkey|shipyard" msgstr "stocznia" msgid "hotkey|thieves guild" msgstr "gildia zÅ‚odziei" msgid "hotkey|tavern" msgstr "tawerna" msgid "hotkey|construction screen" msgstr "ekran budowy zamku/miasta" msgid "hotkey|buy all monsters in well" msgstr "rekrutuj wszystkie stworzenia" msgid "hotkey|split stack by half" msgstr "podziel jednostki na pół" msgid "hotkey|split stack by one" msgstr "podziel jednostki po jednej" msgid "hotkey|join stacks" msgstr "połącz jednostki" msgid "hotkey|upgrade troop" msgstr "ulepsz stworzenie" msgid "hotkey|dismiss hero or troop" msgstr "zwolnij bohatera lub stworzenie" msgid "" "Do you want to regain control from AI? The effect will take place only on " "the next turn." msgstr "" "Czy chcesz odzyskać kontrolÄ™ z rÄ…k AI? Efekt zadziaÅ‚a tylko w nastÄ™pnej " "turze." msgid "" "Do you want to transfer control from you to the AI? The effect will take " "place only on the next turn." msgstr "" "Czy chcesz przekazać AI kontrolÄ™? Efekt zadziaÅ‚a tylko w nastÄ™pnej turze." #, fuzzy msgid "Default Actions" msgstr "domyÅ›lna akcja" msgid "Global Actions" msgstr "" msgid "Main Menu" msgstr "" msgid "World Map" msgstr "Mapa Å›wiata" #, fuzzy msgid "Battle Screen" msgstr "Ekran bohatera" #, fuzzy msgid "Town Screen" msgstr "Ekran bohatera" msgid "Army Actions" msgstr "" msgid "Editor" msgstr "" msgid "The save file is corrupted." msgstr "Plik z zapisanÄ… grÄ… jest uszkodzony." msgid "Unsupported save format: " msgstr "Niewspierany format zapisanej gry: " msgid "Current game version: " msgstr "Aktualna wersja gry: " msgid "Last supported version: " msgstr "Ostatnia wspierana wersja: " msgid "This file contains a save with an invalid game type." msgstr "Ten plik zawiera zapis z nieprawidÅ‚owym typem gry." #, fuzzy msgid "" "This save file requires \"The Price of Loyalty\" game assets, but they have " "not been provided to the engine." msgstr "" "Ten plik jest zapisany w wersji gry \"Cena LojalnoÅ›ci\".\n" "Nie można wczytać pliku - do wczytania potrzebujesz \"Ceny LojalnoÅ›ci\"." msgid "This saved game is localized to '" msgstr "Zapisana gra jest w jÄ™zyku '" msgid "' language, but the current language of the game is '" msgstr ", ale bieżący jÄ™zyk to '" msgid "Hot Seat" msgstr "Na zmianÄ™" msgid "" "Play a Hot Seat game, where 2 to 4 players play around the same computer, " "switching into the 'Hot Seat' when it is their turn." msgstr "" "Graj wraz z 2-4 graczami na jednym komputerze, zamieniajÄ…c siÄ™ miejscami gdy " "przychodzi wasza kolej." msgid "A single player game playing out a single map." msgstr "Gra dla jednej osoby na pojedynczej mapie." msgid "Standard Game" msgstr "Gra standardowa" msgid "A single player game playing through a series of maps." msgstr "Gra dla jednej osoby na wielu powiÄ…zanych mapach." msgid "Campaign Game" msgstr "Kampania" msgid "Multi-Player Game" msgstr "Gra dla wielu osób" msgid "" "A multi-player game, with several human players completing against each " "other on a single map." msgstr "" "Gra dla wielu osób, gdzie kilku graczy walczy przeciw sobie na pojedynczej " "mapie." msgid "fheroes2 Resurrection Team presents" msgstr "fheroes2 Resurrection Team prezentuje" msgid "Greetings!" msgstr "Witaj!" msgid "Welcome to Heroes of Might and Magic II powered by fheroes2 engine!" msgstr "" "Witamy w grze Heroes of Might and Magic II na silniku fheroes2! Przed " "rozpoczÄ™ciem wybierz rozdzielczość gry." #, fuzzy msgid "" "Welcome to Heroes of Might and Magic II powered by the fheroes2 engine!\n" "Before starting the game please select a game resolution." msgstr "" "Witamy w grze Heroes of Might and Magic II na silniku fheroes2! Przed " "rozpoczÄ™ciem wybierz rozdzielczość gry." msgid "Please Remember" msgstr "PamiÄ™taj" # Needs extra whitespace to avoid yellow word splitting #, fuzzy msgid "" "You can always change the language, resolution and settings of the game by " "clicking on the " msgstr "" "Możesz zawsze zmienić rozdzielczość klikajÄ…c na\n" " " msgid "door" msgstr "drzwi" msgid " on the left side of the Main Menu, or with the " msgstr "" msgid " button from the " msgstr "" #, fuzzy msgid "NEW GAME" msgstr "" "NOWA\n" "GRA" msgid "" " menu. \n" "\n" "Enjoy the game!" msgstr "" #, fuzzy msgid "The Editor requires \"The Price of Loyalty\" expansion files to work." msgstr "Jedna z czterech kampani z rozszerzenia Cena LojalnoÅ›ci." msgid "Quit Heroes of Might and Magic II and return to the operating system." msgstr "" "ZakoÅ„cz grÄ™ Heroes of Might and Magic II i przejdź do systemu operacyjnego." msgid "Credits" msgstr "Autorzy" msgid "View the credits screen." msgstr "Obejrzyj autorów gry." msgid "High Scores" msgstr "Najlepsze wyniki" msgid "View the high scores screen." msgstr "Obejrzyj najlepsze wyniki." msgid "Change language, resolution and settings of the game." msgstr "Wybierz jÄ™zyk, rozdzielczość i ustawienia gry." msgid "Game Settings" msgstr "Ustawienia gry" msgid "" "Required video files for campaign selection window are missing. Please make " "sure that all necessary files are present in the system." msgstr "" "Brakuje plików video wymaganych dla ekranu wyboru kampanii. Upewnij siÄ™, czy " "wszystkie niezbÄ™dne pliki sÄ… obecne w systemie." msgid "" "Either Roland's or Archibald's campaign from the original Heroes of Might " "and Magic II." msgstr "Kampania Rolanda lub Archibalda z podstawowej wersji gry." msgid "Original Campaign" msgstr "Oryginalna kampania" msgid "Expansion Campaign" msgstr "Kampania z rozszerzenia" msgid "One of the four new campaigns from the Price of Loyalty expansion set." msgstr "Jedna z czterech kampani z rozszerzenia Cena LojalnoÅ›ci." msgid "Loading video. Please wait..." msgstr "WczytujÄ™ video. ProszÄ™ czekać..." msgid "Host" msgstr "Gospodarz" msgid "" "The host sets up the game options. There can only be one host per network " "game." msgstr "" "Gospodarz ustala opcje gry. W grze sieciowej może być tylko jeden gospodarz." msgid "Guest" msgstr "Gość" msgid "" "The guest waits for the host to set up the game, then is automatically added " "in. There can be multiple guests for TCP/IP games." msgstr "" "Gość czeka, aż gospodarz rozpocznie grÄ™, a nastÄ™pnie włącza siÄ™ do niej " "automatycznie. W grze opartej na połączeniach TCP/IP może być wielu goÅ›ci." msgid "Battle Only" msgstr "Tylko walka" msgid "Setup and play a battle without loading any map." msgstr "Ustaw i zagraj tylko walkÄ™ bez Å‚adowania mapy." msgid "2 Players" msgstr "2 graczy" msgid "" "Play with 2 human players, and optionally, up to 4 additional computer " "players." msgstr "" "Graj z 2 innymi osobami i opcjonalnie z 4 graczami sterowanymi przez " "komputer." msgid "3 Players" msgstr "3 graczy" msgid "" "Play with 3 human players, and optionally, up to 3 additional computer " "players." msgstr "" "Graj z 3 innymi osobami i opcjonalnie z 3 graczami sterowanymi przez " "komputer." msgid "4 Players" msgstr "4 graczy" msgid "" "Play with 4 human players, and optionally, up to 2 additional computer " "players." msgstr "" "Graj z 4 innymi osobami i opcjonalnie z 2 graczami sterowanymi przez " "komputer." msgid "5 Players" msgstr "5 graczy" msgid "" "Play with 5 human players, and optionally, up to 1 additional computer " "player." msgstr "" "Graj z 5 innymi osobami i opcjonalnie z 1 graczem sterowanym przez komputer." msgid "6 Players" msgstr "6 graczy" msgid "Play with 6 human players." msgstr "Grajcie w sześć osób." #, fuzzy msgid "Dragon city has fallen! You are now the Master of the Dragons." msgstr "Smocze miasto upadÅ‚o! JesteÅ› teraz WÅ‚adcÄ… Smoków." msgid "" "You captured %{name}!\n" "You are victorious." msgstr "" "UdaÅ‚o ci siÄ™ zdobyć miasto %{name}!\n" "Odnosisz zwyciÄ™stwo." msgid "" "You have captured the enemy hero %{name}!\n" "Your quest is complete." msgstr "" "UdaÅ‚o ci siÄ™ schwytać wrogiego bohatera - %{name}!\n" "Twoje zadanie zostaÅ‚o wypeÅ‚nione." msgid "" "You have found the %{name}.\n" "Your quest is complete." msgstr "" "Odnajdujesz artefakt (%{name}).\n" "Twoja misja zakoÅ„czyÅ‚a siÄ™ sukcesem." msgid "Ultimate Artifact" msgstr "Najpotężniejszy artefakt" msgid "" "The enemy is beaten.\n" "Your side has triumphed!" msgstr "" "Wróg zostaÅ‚ pobity.\n" "Twoje wojska tryumfujÄ…!" msgid "" "You have built up over %{count} gold in your treasury.\n" "All enemies bow before your wealth and power." msgstr "" "W twoim skarbcu udaÅ‚o ci siÄ™ zebrać ponad %{count} szt. zÅ‚ota.\n" "Wszyscy przeciwnicy korzÄ… siÄ™ przed twym bogactwem i potÄ™gÄ…." msgid "Victory!" msgstr "ZwyciÄ™stwo!" msgid "" "The enemy has captured %{name}!\n" "They are triumphant." msgstr "Wróg zdobyÅ‚ miasto %{name} i odnosi zwyciÄ™stwo." msgid "" "The enemy has built up over %{count} gold in his treasury.\n" "You must bow done in defeat before his wealth and power." msgstr "" "Przeciwnik zebraÅ‚ ponad %{count} szt. zÅ‚ota w swoim skarbcu.\n" "Musisz ukorzyć siÄ™ przed jego bogactwem i potÄ™gÄ…." msgid "You have been eliminated from the game!!!" msgstr "Zostajesz wyeliminowany z gry!!!" msgid "" "You have lost the hero %{name}.\n" "Your quest is over." msgstr "" "Tracisz bohatera (%{name}).\n" "Nie udaje ci siÄ™ wypeÅ‚nić misji." msgid "" "You have failed to complete your quest in time.\n" "All is lost." msgstr "" "Nie udaje ci siÄ™ wypeÅ‚nić misji w okreÅ›lonym czasie. Wszystko stracone." msgid "Defeat!" msgstr "Pokonany!" msgid "" "Base score: %{score}\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" "Bazowy wynik: %{score}\n" "Trudność: %{difficulty}\n" "\n" msgid "Defeat all enemy heroes and capture all enemy towns and castles." msgstr "" "Pokonaj wszystkich bohaterów przeciwnika oraz zajmij wszystkie miasta i " "zamki wrogów." msgid "Run out of time. (Fail to win by a certain point.)" msgstr "UpÅ‚yw czasu (nieukoÅ„czenie gry w okreÅ›lonym czasie.)" msgid "You must defeat the enemy %{enemies}." msgid_plural "You must defeat the enemy alliance of %{enemies}." msgstr[0] "Musisz pokonać %{enemies}." msgstr[1] "Musisz pokonać sprzymierzonych %{enemies}." msgstr[2] "Musisz pokonać sprzymierzonych %{enemies}." msgid "" "The alliance consisting of %{allies} and you must defeat the enemy " "%{enemies}." msgid_plural "" "The alliance consisting of %{allies} and you must defeat the enemy alliance " "of %{enemies}." msgstr[0] "Musisz pokonać %{enemies}." msgstr[1] "Ty oraz %{allies} musicie pokonać %{enemies}." msgstr[2] "Ty oraz %{allies} musicie pokonać %{enemies}." msgid "Capture the castle '%{name}'." msgstr "Przejmij zamek '%{name}'." msgid "Capture the town '%{name}'." msgstr "Przejmij miasto '%{name}'." msgid "Defeat the hero '%{name}'." msgstr "Pokonaj bohatera '%{name}'." msgid "Find the ultimate artifact." msgstr "Znajdź najpotężniejszy artefakt." msgid "Find the '%{name}' artifact." msgstr "Znajdź '%{name}' artefakt." msgid "Accumulate %{count} gold." msgstr "Zgromadź %{count} zÅ‚ota." msgid "" ", or you may win by defeating all enemy heroes and capturing all enemy towns " "and castles." msgstr "" ", możesz także wygrać pokonujÄ…c wszystkich bohaterów wroga i zdobywajÄ…c " "wszystkie jego miasta i zamki." msgid "Lose the castle '%{name}'." msgstr "StraciÅ‚eÅ› zamek '%{name}'." msgid "Lose the town '%{name}'." msgstr "StraciÅ‚eÅ› miasto '%{name}'." msgid "Lose the hero: %{name}." msgstr "Przegrywa bohater: %{name}." msgid "Fail to win by the end of month %{month}, week %{week}, day %{day}." msgstr "" "Brak zwyciÄ™stwa do koÅ„ca miesiÄ…ca %{month}, tygodnia %{week}, dnia %{day}." msgid "%{color} player has been vanquished!" msgstr "%{color} gracz zostaje wygnany!" msgid "Major Event!" msgstr "Gracz wygnany!" msgid "Scenario:" msgstr "Scenariusz:" msgid "Game Difficulty:" msgstr "Poziom trudnoÅ›ci:" msgid "Opponents:" msgstr "Przeciwnicy:" msgid "Class:" msgstr "Klasa:" msgid "Rating %{rating}%" msgstr "Poziom %{rating}%" msgid "Click here to select which scenario to play." msgstr "Kliknij tutaj, aby wybrać scenariusz, który chcesz rozegrać." msgid "Scenario" msgstr "Scenariusz" msgid "Game Difficulty" msgstr "Trudność gry" msgid "" "This lets you change the starting difficulty at which you will play. Higher " "difficulty levels start you of with fewer resources, and at the higher " "settings, give extra resources to the computer." msgstr "" "Pozwala ci zmienić poczÄ…tkowy poziom trudnoÅ›ci gry. Wyższy poziom trudnoÅ›ci " "oznacza rozpoczÄ™cie z mniejszÄ… iloÅ›ciÄ… zasobów oraz dodatkowo przekazanie " "dodatkowych surowców graczowi komputerowemu." msgid "Difficulty Rating" msgstr "Ocena trudnoÅ›ci" msgid "" "The difficulty rating reflects a combination of various settings for your " "game. This number will be applied to your final score." msgstr "" "Ocena trudnoÅ›ci odzwierciedla kombinacje różnorakich ustawieÅ„ gry. Wartość " "ta zostanie dodana do wyniku koÅ„cowego." msgid "Click to accept these settings and start a new game." msgstr "Kliknij, aby użyć tych ustawieÅ„ i rozpocząć nowÄ… grÄ™." msgid "Click to return to the main menu." msgstr "Kliknij, aby wrócić do głównego menu." msgid "Astrologers proclaim the Month of the %{name}." msgstr "Astrologowie ogÅ‚aszajÄ…, że symbolem tego miesiÄ…ca bÄ™dzie %{name}." msgid "Astrologers proclaim the Week of the %{name}." msgstr "Astrologowie ogÅ‚aszajÄ…, że symbolem tego tygodnia bÄ™dzie %{name}." msgid "After regular growth, the population of %{monster} is doubled!" msgstr "DziÄ™ki naturalnemu przyrostowi, populacja %{monster} jest podwojona!" msgid "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgid_plural "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgstr[0] "" "DziÄ™ki naturalnemu przyrostowi populacja %{monster} zwiÄ™ksza siÄ™ o %{count} " "procent!" msgstr[1] "" "DziÄ™ki naturalnemu przyrostowi populacja %{monter} zwiÄ™ksza siÄ™ o %{count} " "procent!" msgstr[2] "" "DziÄ™ki naturalnemu przyrostowi populacja %{monter} zwiÄ™ksza siÄ™ o %{count} " "procent!" # literally, this wont work in Polish. I.e. single: "Feniks", plural "Feniksy", but "Populacja Feniksów zwiÄ™ksza siÄ™ o +3."... msgid "%{monster} growth +%{count}." msgstr "%{monster} zwiÄ™kszajÄ… populacjÄ™ o +%{count}." msgid " All populations are halved." msgstr " Ginie poÅ‚owa populacji królestwa." msgid " All dwellings increase population." msgstr " Populacja caÅ‚ego królestwa zwiÄ™ksza siÄ™." msgid "New Month!" msgstr "Nowy miesiÄ…c!" msgid "New Week!" msgstr "Nowy tydzieÅ„!" msgid "Beware!" msgstr "Uwaga!" msgid "" "%{color} player, this is your last day to capture a town, or you will be " "banished from this land." msgstr "" "%{color}, zostaÅ‚ ci tylko jeden dzieÅ„ na podbicie jakiegoÅ› miasta, albo " "dojdzie do twego wygnania z tej krainy." msgid "" "%{color} player, you only have %{day} days left to capture a town, or you " "will be banished from this land." msgstr "" "%{color}, masz tylko %{day} dni na podbicie jakiegoÅ› miasta, albo dojdzie do " "twego wygnania z tej krainy." msgid "%{color} player's turn." msgstr "%{color} gracz - nowa tura." #, fuzzy msgid "" "%{color} player, you have lost your last town. If you do not conquer another " "town in the next week, you will be eliminated." msgstr "" "%{color}, twoje ostatnie miasto zostaÅ‚o podbite. JeÅ›li nie zdoÅ‚asz zdobyć " "innego miasta w ciÄ…gu tygodnia, gra siÄ™ dla ciebie skoÅ„czy." msgid "" "%{color} player, your heroes abandon you, and you are banished from this " "land." msgstr "" "%{color}, twoi bohaterowie ciÄ™ opuszczajÄ… i zostajesz wygnany z tej krainy." msgid "Lord Kilburn" msgstr "Lord Kilburn" msgid "Tsabu" msgstr "Tsabu" msgid "Sir Galant" msgstr "Sir Galant" msgid "Thundax" msgstr "Thundax" msgid "Lord Haart" msgstr "Lord Haart" msgid "Ariel" msgstr "Ariel" msgid "Rebecca" msgstr "Rebecca" msgid "Sandro" msgstr "Sandro" msgid "Crodo" msgstr "Crodo" msgid "Barock" msgstr "Barock" msgid "Antoine" msgstr "Antoine" msgid "Astra" msgstr "Astra" msgid "Agar" msgstr "Agar" msgid "Vatawna" msgstr "Vatawna" msgid "Vesper" msgstr "Vesper" msgid "Ambrose" msgstr "Ambrose" msgid "Troyan" msgstr "Troyan" msgid "Jojosh" msgstr "Jojosh" msgid "Wrathmont" msgstr "Wrathmont" msgid "Maximus" msgstr "Maximus" msgid "Next Hero" msgstr "NastÄ™pny bohater" msgid "Select the next Hero." msgstr "Wybierz nastÄ™pnego bohatera." #, fuzzy msgid "Hero Movement" msgstr "Cieniowanie pola" msgid "" "Start the Hero's movement along the current path or re-visit the object " "occupied by the Hero. Press and hold this button to reset the Hero's path." msgstr "" msgid "Kingdom Summary" msgstr "PrzeglÄ…d królestwa" msgid "View a Summary of your Kingdom." msgstr "Obejrzyj informacje na temat swego królestwa." msgid "Cast an adventure spell." msgstr "Rzuć zaklÄ™cie podróżne." msgid "End Turn" msgstr "Koniec tury" msgid "End your turn and left the computer take its turn." msgstr "ZakoÅ„cz swojÄ… turÄ™ i pozwól ruszać siÄ™ komputerowi." msgid "Adventure Options" msgstr "Opcje przygody" msgid "Bring up the adventure options menu." msgstr "Pokazuje menu z opcjami przygody." msgid "" "Bring up the file options menu, allowing you to load, save, start a new game " "or quit." msgstr "" "Pokazuje menu z opcjami plików, pozwalajÄ…c na: wczytanie gry, zapisanie gry " "rozpoczÄ™cie nowej gry lub wyjÅ›cie z bieżącej gry." msgid "Bring up the system options menu, allowing you to customize your game." msgstr "" "Pokazuje menu z opcjami systemowymi, pozwalajÄ…c na zmianÄ™ ustawieÅ„ gry." msgid "" "One or more heroes may still move, are you sure you want to end your turn?" msgstr "" "Jeden lub wiÄ™cej bohaterów wciąż może siÄ™ ruszać. Czy na pewno chcesz " "zakoÅ„czyć turÄ™?" msgid "Are you sure you want to quit?" msgstr "Czy na pewno chcesz wyjść?" msgid "Are you sure you want to restart? (Your current game will be lost.)" msgstr "" "Czy na pewno chcesz rozpocząć od nowa? (aktualna rozgrywka zostanie " "przerwana)" msgid "Are you sure you want to overwrite the save with this name?" msgstr "Czy na pewno chcesz zapisać stan gry z takÄ… samÄ… nazwÄ…?" msgid "Game saved successfully." msgstr "Stan gry zapisany." msgid "There was an issue during saving." msgstr "WystÄ…piÅ‚ problem w czasie zapisu gry." msgid "" "Are you sure you want to load a new game? (Your current game will be lost.)" msgstr "" "Czy na pewno chcesz wczytać nowÄ… grÄ™? (aktualna rozgrywka zostanie przerwana)" msgid "Try looking on land!!!" msgstr "Spróbuj na lÄ…dzie!!!" msgid "" "Searching for the Ultimate Artifact is fruitless. Your hero could not carry " "it even if he found it - all his artifact slots are full." msgstr "" "Poszukiwania Najpotężniejszego Artefaktu nie jest możliwe. Twój bohater nie " "utrzyma go nawet jeÅ›li go znajdzie - wszystkie sloty na artefakty sÄ… zajÄ™te." msgid "Digging for artifacts requires a whole day, try again tomorrow." msgstr "Szukanie artefaktu zajmuje caÅ‚y dzieÅ„, spróbuj ponownie jutro." msgid "" "After spending many hours digging here, you have uncovered the %{artifact}." msgstr "Po spÄ™dzeniu wielu godzin na kopaniu, odkryÅ‚eÅ› %{artifact}." msgid "Congratulations!" msgstr "Gratulacje!" msgid "Nothing here. Where could it be?" msgstr "Nic tu nie ma. Gdzie to może być?" msgid "Try searching on clear ground." msgstr "Spróbuj szukać na czystym terenie." msgid "A miniature view of the known world. Left click to move viewing area." msgstr "" "Miniaturowy obraz znanego Å›wiata. Kliknij lewym przyciskiem, aby przesunąć " "widoczny obszar." msgid "Month: %{month} Week: %{week}" msgstr "MiesiÄ…c: %{month} TydzieÅ„: %{week}" msgid "Day: %{day}" msgstr "DzieÅ„: %{day}" msgid "" "You find a small\n" "quantity of %{resource}." msgstr "Znajdujesz: %{resource}." msgid "Status Window" msgstr "Okno informacyjne" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date." msgstr "" "W tym oknie umieszczone sÄ… informacje na temat stanu twojego bohatera lub " "królestwa, a także aktualna data." msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date. Left click here to cycle through these windows." msgstr "" "W tym oknie umieszczone sÄ… informacje na temat stanu twojego bohatera lub " "królestwa, a także aktualna data. Kliknij tu lewym przyciskiem, aby " "przejrzeć kolejne informacje." msgid "" "This lets you change player starting positions and colors. A particular " "color will always start in a particular location. Some positions may only be " "played by a computer player or only by a human player." msgstr "" "Opcja ta pozwala na zmianÄ™ barw i startowej pozycji gracza. Dany kolor " "zawsze bÄ™dzie rozpoczynać grÄ™ w okreÅ›lonym miejscu. Niektóre pozycje mogÄ… " "być zajÄ™te jedynie przez komputer, a inne tylko przez graczy - ludzi." msgid "" "This lets you change the class of a player. Classes are not always " "changeable. Depending on the scenario, a player may receive additional towns " "and/or heroes not of their primary alignment." msgstr "" "Pozwala ci zmienić klasÄ™ gracza. Opcja ta nie zawsze jest aktywna. W " "zależnoÅ›ci od scenariusza, gracz może otrzymać dodatkowe miasta oraz/lub " "bohaterów niezgodne z przynależnÄ… im klasÄ…." msgid "Handicap" msgstr "Utrudnienie" # msgid "" "This lets you change the handicap of a particular player. Only humans may be " "handicapped. Handicapped players start with fewer resources and earn 15 or " "30% fewer resources per turn for mild and severe handicaps, respectively." msgstr "" "Tutaj można ustawić utrudnienia dla poszczególnych graczy. Tylko gracze-" "ludzie mogÄ… zostać obÅ‚ożeni utrudnieniami. Taki gracz zaczyna z mniejszÄ… " "iloÅ›ciÄ… zasobów, a także otrzymuje 15% lub 30% mniej zasobów co turÄ™, " "odpowiednio dla utrudnieÅ„ niewielkich i znacznych." msgid "%{color} player" msgstr "%{color} gracz" msgid "No Handicap" msgstr "Brak utrudnieÅ„" msgid "No special restrictions on start resources and earning them per turn." msgstr "" "Å»adnych ograniczeÅ„ w zasobach poczÄ…tkowych oraz otrzymywanych w każdej turze." msgid "Mild Handicap" msgstr "Niewielkie utrudnienie" # msgid "" "Mild handicapped players start with fewer resources and earn 15% fewer " "resources per turn." msgstr "" "Gracze z niewielkimi utrudnieniami zaczynajÄ… z mniejszÄ… iloÅ›ciÄ… zasobów oraz " "otrzymujÄ… 15% mniej zasobów co turÄ™." msgid "Severe Handicap" msgstr "Znaczne utrudnienia" # msgid "" "Severe handicapped players start with fewer resources and earn 30% fewer " "resources per turn." msgstr "" "Gracze ze znacznymi utrudnieniami zaczynajÄ… z mniejszÄ… iloÅ›ciÄ… zasobów oraz " "otrzymujÄ… o 30% mniej zasobów co turÄ™." msgid "View %{skill} Info" msgstr "WyÅ›wietl informacje o %{skill}" msgid "Keyboard|123" msgstr "123" msgid "Keyboard|SPACE" msgstr "SPACJA" msgid "Keyboard|ABC" msgstr "ABC" msgid "Kingdom Income" msgstr "Przychód" msgid "Kingdom Income per day." msgstr "Dzienny przychód królestwa." msgid "For every lighthouse controlled, your ships will move further each day." msgstr "" "Z każdÄ… kolejnÄ… latarniÄ… morskÄ…, która jest pod twojÄ… kontrolÄ…, zwiÄ™ksza siÄ™ " "zasiÄ™g twoich okrÄ™tów." msgid "English" msgstr "Angielski" msgid "French" msgstr "Francuski" msgid "Polish" msgstr "Polski" msgid "German" msgstr "Niemiecki" msgid "Russian" msgstr "Rosyjski" msgid "Italian" msgstr "WÅ‚oski" msgid "Czech" msgstr "Czeski" msgid "Norwegian" msgstr "Norweski" msgid "Belarusian" msgstr "BiaÅ‚oruski" msgid "Bulgarian" msgstr "BuÅ‚garski" msgid "Ukrainian" msgstr "UkraiÅ„ski" msgid "Romanian" msgstr "RumuÅ„ski" msgid "Spanish" msgstr "HiszpaÅ„ski" msgid "Swedish" msgstr "Szwedzki" msgid "Portuguese" msgstr "Portugalski" msgid "Turkish" msgstr "Turecki" msgid "Dutch" msgstr "Holenderski" msgid "Hungarian" msgstr "WÄ™gierski" msgid "Danish" msgstr "DuÅ„ski" msgid "Slovak" msgstr "SÅ‚owacki" msgid "Vietnamese" msgstr "Wietnamski" msgid ", FPS: " msgstr ", FPS: " msgid "%{object} robber" msgstr "RabuÅ› %{object}" msgid "%{object} raided" msgstr "SplÄ…drowana %{object}" msgid "Ector" msgstr "Ector" msgid "Gwenneth" msgstr "Gwenneth" msgid "Sir Gallant" msgstr "Sir Gallant" msgid "Tyro" msgstr "Tyro" msgid "Dimitry" msgstr "Dimitri" msgid "Ruby" msgstr "Ruby" msgid "Crag Hack" msgstr "Crag Hack" msgid "Fineous" msgstr "Fineous" msgid "Jezebel" msgstr "Jezebel" msgid "Atlas" msgstr "Atlas" msgid "Ergon" msgstr "Ergon" msgid "Jaclyn" msgstr "Jaclyn" msgid "Gem" msgstr "Gem" msgid "Natasha" msgstr "Natasha" msgid "Carlawn" msgstr "Carlawn" msgid "Luna" msgstr "Luna" msgid "Arie" msgstr "Arie" msgid "Barok" msgstr "Barok" msgid "Kastore" msgstr "Kastore" msgid "Falagar" msgstr "Falagar" msgid "Dawn" msgstr "Dawn" msgid "Flint" msgstr "Flint" msgid "Halon" msgstr "Halon" msgid "Myra" msgstr "Myra" msgid "Myrini" msgstr "Myrini" msgid "Wilfrey" msgstr "Wilfrey" msgid "Mandigal" msgstr "Mandigal" msgid "Sarakin" msgstr "Sarakin" msgid "Charity" msgstr "Charity" msgid "Darlana" msgstr "Darlana" msgid "Ranloo" msgstr "Ranloo" msgid "Rialdo" msgstr "Rialdo" msgid "Zam" msgstr "Zam" msgid "Zom" msgstr "Zom" msgid "Celia" msgstr "Celia" msgid "Roxana" msgstr "Roxana" msgid "Lord Corlagon" msgstr "Lorda Corlagon" msgid "Lord Halton" msgstr "Lord Halton" msgid "Sister Eliza" msgstr "Siostra Eliza" msgid "Brother Brax" msgstr "Brat Brax" msgid "Dainwin" msgstr "Dainwin" msgid "Joseph" msgstr "Joseph" msgid "Mog" msgstr "Mog" msgid "Solmyr" msgstr "Solmyr" msgid "Ceallach" msgstr "Ceallach" msgid "Drakonia" msgstr "Drakonia" msgid "Elderian" msgstr "Elderian" msgid "Gallavant" msgstr "Gallavant" msgid "Jarkonas" msgstr "Jarkonas" msgid "Martine" msgstr "Martine" msgid " gives you maximum morale" msgstr " zapewnia ci maksymalne morale" msgid " gives you maximum luck" msgstr " zapewnia ci maksymalne szczęście" msgid "You cannot pick up this artifact, you already have a full load!" msgstr "Nie możesz podnieść tego artefaktu, twój plecak jest peÅ‚ny!" msgid "To cast spells, you must first buy a spell book for %{gold} gold." msgstr "" "Aby móc rzucać zaklÄ™cia, musisz najpierw kupić KsiÄ™gÄ™ Zaklęć za %{gold} " "sztuk zÅ‚ota." msgid "Unfortunately, you seem to be a little short of cash at the moment." msgstr "" "Niestety wyglÄ…da na to, że w chwili obecnej brakuje ci trochÄ™ pieniÄ™dzy." msgid "Do you wish to buy one?" msgstr "Czy chcesz jÄ… kupić?" msgid "%{count} / day" msgstr "%{count} / dzieÅ„" msgid "one" msgstr "jeden" msgid "two" msgstr "dwa" msgid "A whirlpool engulfs your ship. Some of your army has fallen overboard." msgstr "" "Statek zostaje wciÄ…gniÄ™ty przez wir. Część twych wojsk wypada za pokÅ‚ad." msgid "Insulted by your refusal of their offer, the monsters attack!" msgstr "Przeciwnicy, obrażeni odrzuceniem ich propozycji, ruszajÄ… do ataku!" msgid "" "The %{monster}, awed by the power of your forces, begin to scatter.\n" "Do you wish to pursue and engage them?" msgstr "" "%{monster} uciekajÄ… w trwodze przed potÄ™gÄ… twojej armii.\n" "Czy chcesz dogonić przeciwników i z nimi walczyć?" msgid "Ransacking an enemy camp, you discover a hidden cache of treasures." msgstr "PrzetrzÄ…sajÄ…c obóz wroga odnajdujesz ukrytÄ… skrzyniÄ™ ze skarbami." msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with these resources, " "come back next week for more.\"" msgstr "" "WÅ‚aÅ›ciciel mÅ‚yna oznajmia: \"Wasza dostojność, ciężko pracowaÅ‚em, aby zebrać " "te zasoby. Wróć proszÄ™ po wiÄ™cej w przyszÅ‚ym tygodniu.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there are no resources currently available. Please try " "again next week.\"" msgstr "" "WÅ‚aÅ›ciciel mÅ‚yna oznajmia: \"Wasza dostojność, nie mam wiÄ™cej surowców. Wróć " "proszÄ™ w przyszÅ‚ym tygodniu.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with this gold, come " "back next week for more.\"" msgstr "" "WÅ‚aÅ›ciciel mÅ‚yna oznajmia: \"Wasza dostojność, ciężko pracowaÅ‚em, aby zebrać " "to zÅ‚oto. Wróć proszÄ™ po wiÄ™cej w przyszÅ‚ym tygodniu.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there is no gold currently available. Please try again " "next week.\"" msgstr "" "WÅ‚aÅ›ciciel mÅ‚yna oznajmia:\n" "\"Wasza dostojność, nie mam wiÄ™cej zÅ‚ota, wróć proszÄ™ w przyszÅ‚ym tygodniu.\"" msgid "" "You've found an abandoned lean-to.\n" "Poking about, you discover some resources hidden nearby." msgstr "" "Znajdujesz starÄ… szopÄ™.\n" "Przeszukujesz jÄ… i znajdujesz trochÄ™ przydatnych zasobów." msgid "The lean-to is long abandoned. There is nothing of value here." msgstr "Stara szopa jest opuszczona. Nie ma w niej nic wartoÅ›ciowego." msgid "" "You catch a leprechaun foolishly sleeping amidst a cluster of magic " "mushrooms.\n" "In exchange for his freedom, he guides you to a small pot filled with " "precious things." msgstr "" "Åapiesz leprekauna, który nierozważnie zasnÄ…Å‚ na polu magicznych grzybów.\n" "W zamian za uwolnienie wskazuje ci miejsce ukrycia maÅ‚ego dzbana peÅ‚nego " "kosztownoÅ›ci." msgid "" "You've found a magic garden, the kind of place that leprechauns and faeries " "like to cavort in, but there is no one here today.\n" "Perhaps you should try again next week." msgstr "" "Odnajdujesz magiczny ogród, w którym czÄ™sto bywajÄ… leprekauny i duszki, ale " "dziÅ› nikogo tu nie ma.\n" "Spróbuj przyjść tu za tydzieÅ„." msgid "You come upon the remains of an unfortunate adventurer." msgstr "DotarÅ‚eÅ› do szczÄ…tek pechowego poszukiwacza." msgid "Treasure" msgstr "Skarb" msgid "Searching through the tattered clothing, you find the %{artifact}." msgstr "Po przeszukaniu rozdartego ubrania, znajdujesz %{artifact}." msgid "Searching through the tattered clothing, you find nothing." msgstr "Nic nie znajdujesz w tym podartym ubraniu." msgid "" "You come across an old wagon left by a trader who didn't quite make it to " "safe terrain." msgstr "" "Odnajdujesz stary wóz pozostawiony przez kupca któremu nie udaÅ‚o siÄ™ dotrzeć " "na bezpieczny teren." msgid "Unfortunately, others have found it first, and the wagon is empty." msgstr "Niestety, inni odkryli go pierwsi przez co wóz jest pusty." msgid "Searching inside, you find the %{artifact}." msgstr "PrzeszukujÄ…c wnÄ™trze, znajdujesz %{artifact}." msgid "Inside, you find some of the wagon's cargo still intact." msgstr "WewnÄ…trz, odkrywasz, że część Å‚adunku jest nadal nienaruszona." msgid "You search through the flotsam, and find some wood and some gold." msgstr "Przeszukujesz wrak i znajdujesz trochÄ™ drewna i trochÄ™ zÅ‚ota." msgid "You search through the flotsam, and find some wood." msgstr "Przeszukujesz wrak i znajdujesz trochÄ™ drewna." msgid "You search through the flotsam, but find nothing." msgstr "Przeszukujesz wrak, lecz nie znajdujesz niczego." msgid "Shrine of the 1st Circle" msgstr "Kaplica pierwszego krÄ™gu" msgid "" "You come across a small shrine attended by a group of novice acolytes.\n" "In exchange for your protection, they agree to teach you a simple spell - " "'%{spell}'." msgstr "" "Trafiasz do maÅ‚ej kaplicy, którÄ… opiekuje siÄ™ grupa akolitów.\n" "W zamian za ochronÄ™ proponujÄ… nauczyć ciÄ™ prostego zaklÄ™cia - '%{spell}'." msgid "Shrine of the 2nd Circle" msgstr "Kaplica drugiego krÄ™gu" msgid "" "You come across an ornate shrine attended by a group of rotund friars.\n" "In exchange for your protection, they agree to teach you a spell - " "'%{spell}'." msgstr "" "Trafiasz do bogato zdobionej kaplicy, którÄ… opiekuje siÄ™ grupa pulchnych " "mnichów.\n" "W zamian za ochronÄ™ proponujÄ… nauczyć ciÄ™ zaklÄ™cia - '%{spell}'." msgid "Shrine of the 3rd Circle" msgstr "Kaplica trzeciego krÄ™gu" msgid "" "You come across a lavish shrine attended by a group of high priests.\n" "In exchange for your protection, they agree to teach you a sophisticated " "spell - '%{spell}'." msgstr "" "Zbliżasz siÄ™ do urzÄ…dzonej z przepychem kaplicy, którÄ… opiekuje siÄ™ kilku " "wysokich rangÄ… kapÅ‚anów.\n" "W zamian za ochronÄ™ proponujÄ… nauczyć ciÄ™ skomplikowanego zaklÄ™cia - " "'%{spell}'." msgid "" "\n" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "\n" "Niestety, twoja mÄ…drość jest zbyt maÅ‚a, by móc zrozumieć to zaklÄ™cie i nie " "jesteÅ› w stanie siÄ™ go nauczyć." msgid "" "\n" "Unfortunately, you already have knowledge of this spell, so there is nothing " "more for them to teach you." msgstr "" "\n" "Niestety, znasz już to zaklÄ™cie, wiÄ™c nie możesz nauczyć siÄ™ niczego nowego." msgid "" "\n" "Unfortunately, you have no Magic Book to record the spell with." msgstr "" "\n" "Niestety nie masz KsiÄ™gi Zaklęć, w której można zapisać zaklÄ™cie." msgid "" "You approach the hut and observe a witch inside studying an ancient tome on " "%{skill}.\n" "\n" msgstr "" "Wchodzisz do chaty i widzisz wiedźmÄ™ uczÄ…cÄ… umiejÄ™tnoÅ›ci %{skill}.\n" "\n" msgid "" "As you approach, she turns and focuses her one glass eye on you.\n" "\"You already know everything you deserve to learn!\" the witch screeches. " "\"NOW GET OUT OF MY HOUSE!\"" msgstr "" "Kiedy siÄ™ do niej zbliżasz, patrzy na ciebie szklanym okiem,\n" "\"Wiesz już wszystko, co możesz wiedzieć!\" skrzeczy wiedźma. \"A TERAZ " "WYNOCHA Z MOJEGO DOMU!\"" msgid "" "As you approach, she turns and speaks.\n" "\"You already know that which I would teach you. I can help you no further.\"" msgstr "" "Kiedy siÄ™ do niej zbliżasz odwraca siÄ™ i mówi,\n" "\"Wiesz już wszystko, czego mogÅ‚am ciÄ™ nauczyć. Nie potrafiÄ™ ci w niczym " "pomóc.\"" msgid "" "An ancient and immortal witch living in a hut with bird's legs for stilts " "teaches you %{skill} for her own inscrutable purposes." msgstr "" "Stara wiedźma mieszkajÄ…ca w chatce na kurzej Å‚apce z sobie tylko znanych " "powodów przekazuje ci wiedzÄ™ o umiejÄ™tnoÅ›ci %{skill}." msgid "As you drink the sweet water, you gain luck for your next battle." msgstr "" "Pijesz sÅ‚odkÄ… wodÄ™ i czujesz, że w nastÄ™pnej bitwie dopisze ci szczęście." msgid "You drink from the enchanted fountain, but nothing happens." msgstr "Pijesz wodÄ™ z zaklÄ™tej fontanny ale nic siÄ™ nie dzieje." msgid "You enter the faerie ring, but nothing happens." msgstr "Wchodzisz do zaklÄ™tego krÄ™gu, ale nic siÄ™ nie dzieje." msgid "" "Upon entering the mystical faerie ring, your army gains luck for its next " "battle." msgstr "" "Po wejÅ›ciu do tajemniczego zaklÄ™tego krÄ™gu, twoja armia zyskuje szczęście na " "czas kolejnej bitwy." msgid "" "You've found an ancient and weathered stone idol.\n" "It is supposed to grant luck to visitors, but since the stars are already " "smiling upon you, it does nothing." msgstr "" "Odnajdujesz zwietrzaÅ‚y posÄ…g kamiennego bóstwa.\n" "Podobno pocaÅ‚owanie go przynosi szczęście, ale kiedy to czynisz, nic " "specjalnego siÄ™ nie dzieje." msgid "" "You've found an ancient and weathered stone idol.\n" "Kissing it is supposed to be lucky, so you do. The stone is very cold to the " "touch." msgstr "" "Odnajdujesz zwietrzaÅ‚y posÄ…g kamiennego bóstwa.\n" "Podobno pocaÅ‚owanie go przynosi szczęście, czynisz wiÄ™c to. Kamienny posÄ…g " "jest bardzo zimny." msgid "The mermaids silently entice you to return later and be blessed again." msgstr "" "Syreny w ciszy zachÄ™cajÄ… ciÄ™, by wrócić do nich później po bÅ‚ogosÅ‚awieÅ„stwo." msgid "" "The magical, soothing beauty of the Mermaids reaches you and your crew.\n" "Just for a moment, you forget your worries and bask in the beauty of the " "moment.\n" "The mermaids charms bless you with increased luck for your next combat." msgstr "" "Magiczna uroda syren oczarowuje ciebie i twojÄ… zaÅ‚ogÄ™.\n" "Zapominasz o wszystkich kÅ‚opotach cieszÄ…c siÄ™ chwilÄ….\n" "Na odchodne syreny bÅ‚ogosÅ‚awiÄ… twoje wojska, co ma przynieść ci szczęście " "podczas nastÄ™pnej bitwy." msgid "" "You come upon the pyramid of a great and ancient king.\n" "You are tempted to search it for treasure, but all the old stories warn of " "fearful curses and undead guardians.\n" "Will you search?" msgstr "" "Zbliżasz siÄ™ do piramidy zbudowanej ku chwale potężnego, starożytnego " "króla.\n" "Korci ciÄ™, by jÄ… przeszukać, ale stare legendy ostrzegajÄ… przed okropnymi " "klÄ…twami i nieumarÅ‚ymi strażnikami.\n" "Czy mimo to chcesz wejść do piramidy?" msgid "" "Upon defeating the monsters, you decipher an ancient glyph on the wall, " "telling the secret of the spell - '" msgstr "" "Pokonawszy potwory, odczytujesz prastary glif na Å›cianie, dziÄ™ki czemu " "poznajesz tajemne zaklÄ™cie - '" msgid "Unfortunately, you have no Magic Book to record the spell with." msgstr "Niestety nie masz KsiÄ™gi Zaklęć, w której można zapisać zaklÄ™cie." msgid "" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "Niestety, twoja mÄ…drość jest zbyt maÅ‚a, by móc zrozumieć to zaklÄ™cie i nie " "jesteÅ› w stanie siÄ™ go nauczyć." msgid "" "You come upon the pyramid of a great and ancient king.\n" "Routine exploration reveals that the pyramid is completely empty." msgstr "" "Natrafiasz na piramidÄ™ zbudowanÄ… ku chwale potężnego, starożytnego króla.\n" "Przeszukujesz jÄ…, ale okazuje siÄ™, że jest zupeÅ‚nie pusta." msgid "" "A drink at the well is supposed to restore your spell points, but you are " "already at maximum." msgstr "" "Åyk wody z magicznej studni może przywrócić ci wszystkie punkty magii, ale " "masz już ich maksymalnÄ… liczbÄ™." msgid "A second drink at the well in one day will not help you." msgstr "" "Ponowne napicie siÄ™ ze studni w ciÄ…gu jednego dnia nie przyniesie efektów." msgid "A drink from the well has restored your spell points to maximum." msgstr "Åyk wody z magicznej studni przywróciÅ‚ ci wszystkie punkty magii." msgid "" "\"I'm sorry sir,\" The leader of the soldiers says, \"but you already know " "everything we have to teach.\"" msgstr "" "\"Wybacz panie,\" mówi dowódca żoÅ‚nierzy, \"ale wiesz już wszystko, co " "moglibyÅ›my ciÄ™ nauczyć.\"" msgid "The soldiers living in the fort teach you a few new defensive tricks." msgstr "Å»oÅ‚nierze z fortu uczÄ… ciÄ™ kilku sztuczek przydatnych w obronie." msgid "" "You've come upon a mercenary camp practicing their tactics. \"You're too " "advanced for us,\" the mercenary captain says. \"We can teach nothing more.\"" msgstr "" "Trafiasz do obozu, w którym wÅ‚aÅ›nie trenujÄ… najemnicy. \"JesteÅ› dla nas zbyt " "trudnym przeciwnikiem,\" mówi ich dowódca. \"Nie nauczymy ciÄ™ niczego nowego." "\"" msgid "" "You've come upon a mercenary camp practicing their tactics. The mercenaries " "welcome you and your troops and invite you to train with them." msgstr "" "Trafiasz do obozu, w którym wÅ‚aÅ›nie trenujÄ… najemnicy. Å»oÅ‚nierze z obozu " "witajÄ… ciÄ™ i zapraszajÄ… twoich ludzi do wspólnych ćwiczeÅ„." msgid "\"Go 'way!\", the witch doctor barks, \"you know all I know.\"" msgstr "\"Wynocha!\", warczy znachor, \"wiesz już wszystko to co ja.\"" msgid "" "An Orcish witch doctor living in the hut deepens your knowledge of magic by " "showing you how to cast stones, read portents, and decipher the intricacies " "of chicken entrails." msgstr "" "Orkowy znachor mieszkajÄ…cy w tej chatce zwiÄ™ksza twojÄ… wiedzÄ™ na temat magii " "pokazujÄ…c ci, jak wróżyć z kamieni, odczytywać znaki i wieszczyć z " "wnÄ™trznoÅ›ci kurczaka." msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, the Druids turn you away, indicating they have nothing " "new to teach you." msgstr "" "Spotykasz grupÄ™ druidów odprawiajÄ…cych obrzÄ™dy przy jednej ze swych dziwnych " "kamiennych struktur. Druidzi w milczeniu odwracajÄ…c siÄ™ od ciebie, " "pokazujÄ…c, że nie mogÄ… nauczyć ciÄ™ niczego nowego." msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, they teach you new ways to cast spells." msgstr "" "Spotykasz grupÄ™ druidów odprawiajÄ…cych obrzÄ™dy przy jednej ze swych dziwnych " "kamiennych struktur. Druidzi w milczeniu uczÄ… ciÄ™ nowych sposobów rzucania " "zaklęć." msgid "" "You tentatively approach the burial ground of ancient warriors. Do you want " "to search the graves?" msgstr "" "Ostrożnie zbliżasz siÄ™ do cmentarzyska pradawnych wojowników. Czy chcesz " "przeszukać groby?" msgid "" "You spend several hours searching the graves and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" "SpÄ™dzasz wiele godzin na przeszukiwaniu grobów, lecz nic nie znajdujesz. Ten " "akt profanacji obniża morale twojej armii." msgid "Upon defeating the Zombies you search the graves and find something!" msgstr "" "Po pokonaniu zombie przeszukujesz groby i znajdujesz coÅ› wartoÅ›ciowego!" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the shipwreck?" msgstr "" "GnijÄ…cy kadÅ‚ub olbrzymiego statku pirackiego poskrzypuje z cicha ocierajÄ…c " "siÄ™ o przybrzeżne skaÅ‚y. Czy chcesz przeszukać wrak?" msgid "" "You spend several hours sifting through the debris and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" "SpÄ™dzasz wiele godzin na przeszukiwaniu wraku, lecz nic nie znajdujesz. Ten " "akt profanacji obniża morale twojej armii." msgid "" "Upon defeating the Ghosts you sift through the debris and find something!" msgstr "Po pokonaniu Duchów przeszukujesz wrak i znajdujesz coÅ› wartoÅ›ciowego!" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the ship?" msgstr "" "GnijÄ…cy kadÅ‚ub olbrzymiego statku pirackiego poskrzypuje z cicha ocierajÄ…c " "siÄ™ o przybrzeżne skaÅ‚y. Czy chcesz przeszukać wrak?" msgid "" "Upon defeating the Skeletons you sift through the debris and find something!" msgstr "" "Po pokonaniu szkieletów przeszukujesz wrak i znajdujesz coÅ› wartoÅ›ciowego!" msgid "Your men spot a navigational buoy, confirming that you are on course." msgstr "" "Twoi ludzie zauważajÄ… bojÄ™ nawigacyjnÄ…, co potwierdza, że pÅ‚yniecie dobrym " "kursem." msgid "" "Your men spot a navigational buoy, confirming that you are on course and " "increasing their morale." msgstr "" "Twoi ludzie zauważajÄ… bojÄ™ nawigacyjnÄ…, co potwierdza, że pÅ‚yniecie dobrym " "kursem i zwiÄ™ksza ich morale." msgid "" "The drink at the oasis is refreshing, but offers no further benefit. The " "oasis might help again if you fought a battle first." msgstr "" "Pijesz wodÄ™ w oazie, ale nic siÄ™ nie dzieje. Być może trzeba tu wrócić po " "najbliższej bitwie." #, fuzzy msgid "" "A drink at the oasis fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" "OrzeźwiajÄ…ca woda z oazy wzmacnia ducha twoich żoÅ‚nierzy i dodaje im siÅ‚. " "Możesz dziÅ› jeszcze pokonać spory kawaÅ‚ek drogi." msgid "" "The drink at the watering hole is refreshing, but offers no further benefit. " "The watering hole might help again if you fought a battle first." msgstr "" "Zimna woda orzeźwia twoich żoÅ‚nierzy, ale nie dzieje siÄ™ nic specjalnego. " "Może warto przyjść tutaj po walce." msgid "" "A drink at the watering hole fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" "Zimna woda orzeźwia twoich żoÅ‚nierzy i dodaje im siÅ‚. Dzisiaj możesz pokonać " "jeszcze sporo drogi." msgid "" "It doesn't help to pray twice before a battle. Come back after you've fought." msgstr "" "Dwukrotna modlitwa przed bitwÄ… w niczym ci nie pomoże. Wróć tu po walce." msgid "A visit and a prayer at the temple raises the morale of your troops." msgstr "Wizyta i modlitwa w Å›wiÄ…tyni podnosi morale twoich oddziałów." msgid "" "An old Knight appears on the steps of the gazebo. \"I am sorry, my liege, I " "have taught you all I can.\"" msgstr "" "Na schodach altany staje sÄ™dziwy rycerz. \"Wasza dostojność wybaczy, ale " "nauczyÅ‚em jÄ… wszystkiego co umiem.\"" msgid "" "An old Knight appears on the steps of the gazebo. \"My liege, I will teach " "you all that I know to aid you in your travels.\"" msgstr "" "Na schodach altany staje sÄ™dziwy rycerz. \"Wasza dostojność, aby pomóc ci w " "twych podróżach, nauczÄ™ ciÄ™ wszystkiego co sam umiem.\"" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he says, \"I would give you an artifact as a reward, but " "you're all full.\"" msgstr "" "WyciÄ…gasz z wody wyczerpanego czÅ‚owieka ratujÄ…c go przed Å›mierciÄ… w zimnych " "wodach oceanu. WdziÄ™czny za ocalenie rozbitek mówi: \"Z chÄ™ciÄ… " "odwdziÄ™czyÅ‚bym siÄ™, dajÄ…c ci artefakt, ale widzÄ™, że nie masz już miejsca w " "swoim ekwipunku.\"" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he rewards you for your act of kindness by giving you the " "%{art}." msgstr "" "UratowaÅ‚eÅ› rozbitka przed utoniÄ™ciem. W podziÄ™ce za uratowanie, dostajesz " "%{art}." msgid "A leprechaun offers you the %{art} for the small price of %{gold} Gold." msgstr "" "%{art} - leprekaun proponuje, że sprzeda ci ten artefakt za %{gold} sztuk " "zÅ‚ota." msgid "" "A leprechaun offers you the %{art} for the small price of %{gold} Gold and " "%{count} %{res}." msgstr "" "%{art} - leprekaun proponuje, że sprzeda ci ten artefakt za %{gold} sztuk " "zÅ‚ota i %{count} jednostki surowca (%{res})." msgid "Do you wish to buy this artifact?" msgstr "Czy chcesz go kupić?" msgid "" "You try to pay the leprechaun, but realize that you can't afford it. The " "leprechaun stamps his foot and ignores you." msgstr "" "Chcesz zapÅ‚acić leprekaunowi, ale zdajesz sobie sprawÄ™, że ciÄ™ na to nie " "stać. Leprekaun tupie ze zÅ‚oÅ›ciÄ… nóżkÄ… i postanawia ciÄ™ zignorować." msgid "" "Insulted by your refusal of his generous offer, the leprechaun stamps his " "foot and ignores you." msgstr "" "Obrażony odrzuceniem hojnej oferty leprekaun tupie ze zÅ‚oÅ›ciÄ… małą nóżkÄ… i " "postanawia ciÄ™ zignorować." msgid "You've found the artifact: " msgstr "Odnajdujesz artefakt: " msgid "" "You've found the humble dwelling of a withered hermit. The hermit tells you " "that he is willing to give the %{art} to the first wise person he meets." msgstr "" "Trafiasz do skromnego domku starego pustelnika. Pustelnik oznajmia, że " "%{art} bÄ™dzie należeć do pierwszej mÄ…drej osoby, która go odwiedzi." msgid "" "You've come across the spartan quarters of a retired soldier. The soldier " "tells you that he is willing to pass on the %{art} to the first true leader " "he meets." msgstr "" "Trafiasz do spartaÅ„skiej kwatery starego żoÅ‚nierza. Å»oÅ‚nierz oznajmia ci, że " "%{art} bÄ™dzie należeć do pierwszego prawdziwego dowódcy, który go odwiedzi..." msgid "" "You've encountered a strange person with a hat and an owl on it. He tells " "you that he is willing to give %{art} if you have %{skill}." msgstr "" "Napotykasz dziwnÄ… postać z kapeluszem, na którym siedzi sowa. Oznajmia on, " "że skÅ‚onny jest podarować Ci %{art} jeÅ›li masz %{skill}." msgid "" "You come upon an ancient artifact. As you reach for it, a pack of Rogues " "leap out of the brush to guard their stolen loot." msgstr "" "Przypadkowo znajdujesz prastary artefakt. Gdy po niego siÄ™gasz, z krzaków " "wyskakuje banda rozbójników broniÄ…ca swego Å‚upu." msgid "" "Through a clearing you observe an ancient artifact. Unfortunately, it's " "guarded by a nearby %{monster}. Do you want to fight the %{monster} for the " "artifact?" msgstr "" "Na polanie zauważasz prastary artefakt. Niestety w pobliżu znajduje siÄ™ jego " "strażnik - %{monster}. Czy chcesz walczyć ze strażnikiem (%{monster}) o " "artefakt?" msgid "Victorious, you take your prize, the %{art}." msgstr "Zwyciężywszy zabierasz swojÄ… zdobycz. Jest to %{art}." msgid "" "Discretion is the better part of valor, and you decide to avoid this fight " "for today." msgstr "" "RozsÄ…dek przeważa nad mÄ™stwem i uznajesz, że lepiej dziÅ› uniknąć tej walki." msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold pieces." msgstr "" "SpÄ™dziwszy wiele godzin na próbach wyÅ‚owienia skrzyni wciÄ…gasz jÄ… w koÅ„cu na " "pokÅ‚ad i odkrywasz, iż znajduje siÄ™ w niej %{gold} sztuk zÅ‚ota." msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold and the %{art}." msgstr "" "SpÄ™dziwszy wiele godzin na próbach wyÅ‚owienia skrzyni wciÄ…gasz jÄ… w koÅ„cu na " "pokÅ‚ad i odkrywasz, iż znajduje siÄ™ w niej %{gold} sztuk zÅ‚ota oraz %{art}." msgid "" "After spending hours trying to fish the chest out of the sea, you open it, " "only to find it empty." msgstr "" "SpÄ™dziwszy wiele godzin na próbach wyÅ‚owienia skrzyni wciÄ…gasz jÄ… w koÅ„cu na " "pokÅ‚ad i odkrywasz, iż jest ona kompletnie pusta." msgid "" "After scouring the area, you fall upon a hidden treasure cache. You may take " "the gold or distribute the gold to the peasants for experience. Do you wish " "to keep the gold?" msgstr "" "PrzeszukujÄ…c okolicÄ™ odnajdujesz ukrytÄ… skrzyniÄ™ ze skarbami. Możesz zabrać " "zÅ‚oto lub oddać je wieÅ›niakom zyskujÄ…c doÅ›wiadczenie. Czy chcesz zatrzymać " "zÅ‚oto?" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "%{gold} gold pieces." msgstr "" "PrzeszukujÄ…c okolicÄ™ odnajdujesz ukrytÄ… skrzyniÄ™ zawierajÄ…cÄ… %{gold} sztuk " "zÅ‚ota." msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "ancient artifact %{art}." msgstr "" "PrzeszukujÄ…c okolicÄ™ odnajdujesz ukrytÄ… skrzyniÄ™. Okazuje siÄ™, iż kufer " "zawiera starożytny artefakt - %{art}." msgid "" "You stumble upon a dented and tarnished lamp lodged deep in the earth. Do " "you wish to rub the lamp?" msgstr "" "Znajdujesz wyszczerbionÄ… i zmatowiałą lampÄ™ na wpół zakopanÄ… w ziemi. Czy " "chcesz jÄ… potrzeć?" msgid "" "You have taken control of the local Alchemist shop. It will provide you with " "%{count} unit of Mercury per day." msgstr "" "Przejmujesz kontrolÄ™ nad sklepem miejscowego alchemika. Dostarczać ci bÄ™dzie " "%{count} rtęć dziennie." msgid "" "You gain control of a sawmill. It will provide you with %{count} units of " "wood per day." msgstr "" "Przejmujesz kontrolÄ™ nad tartakiem. Dostarczać ci bÄ™dzie %{count} drewna " "dziennie." msgid "" "You gain control of an ore mine. It will provide you with %{count} units of " "ore per day." msgstr "" "Przejmujesz kontrolÄ™ nad kopalniÄ… rudy. Dostarczać ci bÄ™dzie %{count} rudy " "dziennie." msgid "" "You gain control of a sulfur mine. It will provide you with %{count} unit of " "sulfur per day." msgstr "" "Przejmujesz kontrolÄ™ nad kopalniÄ… siarki. Dostarczać ci bÄ™dzie %{count} " "siarkÄ™ dziennie." msgid "" "You gain control of a crystal mine. It will provide you with %{count} unit " "of crystal per day." msgstr "" "Przejmujesz kontrolÄ™ nad kopalniÄ… kryształów. Dostarczać ci bÄ™dzie %{count} " "krysztaÅ‚ dziennie." msgid "" "You gain control of a gem mine. It will provide you with %{count} unit of " "gems per day." msgstr "" "Przejmujesz kontrolÄ™ nad kopalniÄ… klejnotów. Dostarczać ci bÄ™dzie %{count} " "klejnot dziennie." msgid "" "You gain control of a gold mine. It will provide you with %{count} gold per " "day." msgstr "" "Przejmujesz kontrolÄ™ nad kopalniÄ… zÅ‚ota. Dostarczać ci bÄ™dzie %{count} sztuk " "zÅ‚ota dziennie." msgid "" "The lighthouse is now under your control, and all of your ships will now " "move further each day." msgstr "" "Latarnia morska jest teraz pod twojÄ… kontrolÄ… i twoje okrÄ™ty zwiÄ™kszajÄ… " "zasiÄ™g swego ruchu w każdej turze." msgid "You gain control of a %{name}." msgstr "Przejmujesz kontrolÄ™ nad %{name}." msgid "" "You come upon an abandoned gold mine. The mine appears to be haunted. Do you " "wish to enter?" msgstr "" "Zbliżasz siÄ™ do opuszczonej kopalni zÅ‚ota. WyglÄ…da na to, że jest " "nawiedzona. Czy chcesz do niej wejść?" msgid "You beat the Ghosts and are able to restore the mine to production." msgstr "Pokonujesz Duchy i w kopalni ponownie może rozpocząć siÄ™ wydobycie." msgid "" "A group of %{monster} with a desire for greater glory wish to join you. Do " "you accept?" msgstr "" "Chce siÄ™ do ciebie dołączyć grupa stworzeÅ„ (%{monster}) pragnÄ…cych chwaÅ‚y. " "Zgadzasz siÄ™?" msgid "As you approach the dwelling, you notice that there is no one here." msgstr "Gdy zbliżasz siÄ™ do domostwa, zauważasz, że nikogo tam nie ma." msgid "" "You search the ruins, but the Medusas that used to live here are gone. " "Perhaps there will be more next week." msgstr "" "Przeszukujesz ruiny, ale zamieszkujÄ…ce je Meduzy gdzieÅ› zniknęły. Może " "pojawiÄ… siÄ™ w przyszÅ‚ym tygodniu." msgid "" "You've found some Medusas living in the ruins. They are willing to join your " "army for a price. Do you want to recruit Medusas?" msgstr "" "Odnajdujesz kilka Meduz żyjÄ…cych wÅ›ród ruin. Gotowe sÄ… dołączyć do twojej " "armii za odpowiedniÄ… cenÄ™. Czy chcesz nająć Meduzy?" msgid "" "You've found a Sprite Tree City. Unfortunately, none of the Sprites living " "there wish to join an army. Maybe next week." msgstr "" "Odnajdujesz Nadrzewne Miasto RusaÅ‚ek. Niestety żadna z mieszkajÄ…cych tu " "RusaÅ‚ek nie chce dołączyć do twojej armii. Może w przyszÅ‚ym tygodniu." msgid "" "Some of the Sprites living in the tree city are willing to join your army " "for a price. Do you want to recruit Sprites?" msgstr "" "Część RusaÅ‚ek żyjÄ…cych w Nadrzewnym MieÅ›cie chce dołączyć za pewnÄ… cenÄ™ do " "twojej armii. Czy chcesz nająć RusaÅ‚ki?" msgid "" "A colorful Rogues' wagon stands empty here. Perhaps more Rogues will be here " "later." msgstr "" "Kolorowe cygaÅ„skie wozy sÄ… puste. Może Rozbójnicy zjawiÄ… siÄ™ tu później." msgid "" "Distant sounds of music and laughter draw you to a colorful wagon housing " "Rogues. Do you wish to have any Rogues join your army?" msgstr "" "DźwiÄ™ki muzyki i Å›miechy prowadzÄ… ciÄ™ do kolorowego taboru, w którym " "mieszkajÄ… Rozbójnicy. Czy chcesz, aby przyłączyli siÄ™ do ciebie?" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. The " "tents are unoccupied. Perhaps more Nomads will be here later." msgstr "" "Stoi tu kilka poszarpanych namiotów smaganych pustynnym wiatrem. SÄ… puste. " "Może Nomadowie przyjdÄ… tu później." msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. Do you " "wish to have any Nomads join you during your travels?" msgstr "" "Stoi tu kilka poszarpanych namiotów smaganych pustynnym wiatrem. Czy chcesz, " "by przyłączyli siÄ™ do ciebie Nomadowie?" msgid "The pit of mud bubbles for a minute and then lies still." msgstr "W jamie przez chwile bulgocze bÅ‚oto po czym siÄ™ uspokaja." msgid "" "As you approach the bubbling pit of mud, creatures begin to climb out and " "position themselves around it. In unison they say: \"Mother Earth would like " "to offer you a few of her troops. Do you want to recruit Earth Elementals?\"" msgstr "" "Gdy zbliżasz siÄ™ do jamy peÅ‚nej bulgoczÄ…cego bÅ‚ota, wygrzebujÄ… siÄ™ z niej " "jakieÅ› istoty i ustawiajÄ… wokół. Jednym gÅ‚osem mówiÄ…: \"Matka Ziemia " "proponuje ci kilka z jej oddziałów. Czy chcesz nająć Å»ywioÅ‚aki Ziemi?\"" msgid "You enter the structure of white stone pillars, and find nothing." msgstr "Wchodzisz pomiÄ™dzy biaÅ‚e kolumny, ale nic nie znajdujesz." msgid "" "White stone pillars support a roof that rises up to the sky. As you enter " "the structure, the dead air of the outside gives way to a whirling gust that " "almost pushes you back out. The air current materializes into a barely " "visible form. The creature asks, in what can only be described as a loud " "whisper: \"Why have you come? Are you here to call upon the forces of the " "air?\"" msgstr "" "BiaÅ‚e, kamienne kolumny podtrzymujÄ… dach siÄ™gajÄ…cy nieba. Gdy wchodzisz do " "Å›rodka uderza ciÄ™ podmuch powietrza, niemal wypychajÄ…c ciÄ™ na zewnÄ…trz. PrÄ…d " "powietrza materializuje siÄ™ w ledwie widocznÄ… postać. Istota zwraca siÄ™ do " "ciebie gÅ‚oÅ›nym szeptem: \"Po co tu przybywasz? Czy chcesz przywoÅ‚ać siÅ‚y " "powietrza?\"" msgid "No Fire Elementals approach you from the lava pool." msgstr "Z sadzawki lawy nie wychodzi żaden Å»ywioÅ‚ak Ognia." msgid "" "Beneath a structure that serves to hold in heat, Fire Elementals move about " "in a fiery pool of molten lava. A group of them approach you and offer their " "services. Would you like to recruit Fire Elementals?" msgstr "" "Pod konstrukcjÄ…, która sÅ‚uży do utrzymania ciepÅ‚a, widzisz Å»ywioÅ‚aki Ognia " "pÅ‚ywajÄ…ce w strumieniach lawy. Część z nich zbliża siÄ™ do ciebie proponujÄ…c " "swe usÅ‚ugi. Czy chcesz nająć Å»ywioÅ‚aki Ognia?" msgid "A face forms in the water for a moment, and then is gone." msgstr "Przez chwilÄ™ w wodzie widoczna jest jakaÅ› twarz, po czym znika." msgid "" "Crystalline structures cast shadows over a small reflective pool of water. " "You peer into the pool, and a face that is not your own peers back. It asks: " "\"Would you like to call upon the powers of water?\"" msgstr "" "Krystaliczne struktury rzucajÄ… cienie na maÅ‚y staw przypominajÄ…cy taflÄ™ " "zwierciadÅ‚a. Patrzysz w wodÄ™ i widzisz odbicie nie swojej twarzy, która " "mówi: \"Czy chcesz przywoÅ‚ać moce wody?\"" msgid "This burial site is deathly still." msgstr "Na cmentarzysku panuje niczym niezmÄ…cona cisza." msgid "" "Restless spirits of long dead warriors seeking their final resting place " "offer to join you in hopes of finding peace. Do you wish to recruit ghosts?" msgstr "" "ChcÄ… siÄ™ do ciebie dołączyć niespokojne dusze martwych wojowników, którzy " "szukajÄ… miejsca ostatecznego spoczynku. Czy chcesz nająć Duchy?" msgid "" "The City of the Dead is empty of life, and empty of unlife as well. Perhaps " "some undead will move in next week." msgstr "" "W MieÅ›cie UmarÅ‚ych nie ma życia, ale i Å›mierci tu nie widać. Może nieumarli " "pojawiÄ… siÄ™ tu w przyszÅ‚ym tygodniu." msgid "" "Some Liches living here are willing to join your army for a price. Do you " "want to recruit Liches?" msgstr "" "Część mieszkajÄ…cych tu liszy chce dołączyć za pewnÄ… cenÄ™ do twojej armii. " "Czy chcesz nająć Lisze?" msgid "" "You've found the ruins of an ancient city, now inhabited solely by the " "undead. Will you search?" msgstr "" "Odnajdujesz ruiny prastarego miasta, zamieszkaÅ‚ego obecnie jedynie przez " "nieumarÅ‚ych. Czy chcesz je przeszukać?" msgid "" "Some of the surviving Liches are impressed by your victory over their " "fellows, and offer to join you for a price. Do you want to recruit Liches?" msgstr "" "Część z ocalaÅ‚ych Liszy jest pod wrażeniem twojego zwyciÄ™stwa nad nimi i " "proponujÄ… siÄ™ do ciebie przyłączyć, za pewnÄ… cenÄ™. Czy chcesz nająć Lisze?" msgid "" "You've found one of those bridges that Trolls are so fond of living under, " "but there are none here. Perhaps there will be some next week." msgstr "" "Odnajdujesz jeden z mostów, pod którymi tak lubiÄ… mieszkać Trolle, ale teraz " "nikogo tu nie ma. Może pojawiÄ… siÄ™ w przyszÅ‚ym tygodniu." msgid "" "Some Trolls living under a bridge are willing to join your army, but for a " "price. Do you want to recruit Trolls?" msgstr "" "Część Trolli żyjÄ…cych pod mostem chce dołączyć za pewnÄ… cenÄ™ do twojej " "armii. Czy chcesz nająć Trolle?" msgid "Trolls living under the bridge challenge you. Will you fight them?" msgstr "" "Trolle żyjÄ…ce pod mostem rzucajÄ… ci wyzwanie. Czy bÄ™dziesz z nimi walczyć?" msgid "" "A few Trolls remain, cowering under the bridge. They approach you and offer " "to join your forces as mercenaries. Do you want to buy any Trolls?" msgstr "" "Pod mostem kuli siÄ™ kilka Trolli. ZbliżajÄ… siÄ™ do ciebie i proponujÄ… " "dołączyć siÄ™ do twojej armii jako najemnicy. Czy chcesz nająć Trolle?" msgid "" "The Dragon city has no Dragons willing to join you this week. Perhaps a " "Dragon will become available next week." msgstr "" "W tym tygodniu w Smoczym MieÅ›cie nie ma Smoków, które mogÅ‚yby siÄ™ do ciebie " "przyłączyć. Spróbuj ponownie w przyszÅ‚ym tygodniu." msgid "" "The Dragon city is willing to offer some Dragons for your army for a price. " "Do you wish to recruit Dragons?" msgstr "" "Smocze Miasto zgadza siÄ™ przekazać kilka Smoków do twojej armii. Czy chcesz " "nająć Smoki?" msgid "" "You stand before the Dragon City, a place off-limits to mere humans. Do you " "wish to violate this rule and challenge the Dragons to a fight?" msgstr "" "Stoisz przed Smoczym Miastem, miejscem niedostÄ™pnym dla zwykÅ‚ych ludzi. Czy " "chcesz zÅ‚amać tÄ… zasadÄ™ i wyzwać Smoki na pojedynek?" msgid "" "Having defeated the Dragon champions, the city's leaders agree to supply " "some Dragons to your army for a price. Do you wish to recruit Dragons?" msgstr "" "Pokonawszy smoczych czempionów, przywódcy miasta zgadzajÄ… siÄ™ za pewnÄ… cenÄ™ " "przekazać kilka Smoków do twojej armii. Czy chcesz nająć Smoki?" msgid "From the observation tower, you are able to see distant lands." msgstr "Z wieży obserwacyjnej możesz zobaczyć odlegÅ‚e krainy." msgid "" "The spring only refills once a week, and someone's already been here this " "week." msgstr "" "Studnia wypeÅ‚nia siÄ™ tylko raz w tygodniu, a w tym tygodniu ktoÅ› już tu byÅ‚." msgid "" "A drink at the spring is supposed to give you twice your normal spell " "points, but you are already at that level." msgstr "" "Napicie siÄ™ ze studni powinno dać ci dwa razy wiÄ™cej punktów magii niż " "zwykle posiadasz, ale już masz tyle punktów." msgid "" "A drink from the spring fills your blood with magic! You have twice your " "normal spell points in reserve." msgstr "" "Åyk wody ze studni przepeÅ‚nia ciÄ™ magicznÄ… mocÄ…! Masz dwa razy wiÄ™cej " "punktów magii niż normalnie." msgid "" "Recognizing you, the butler refuses to admit you. \"The master,\" he says, " "\"will not see the same student twice.\"" msgstr "" "Lokaj rozpoznaje ciÄ™ i odmawia otwarcia drzwi. \"Mój pan,\" mówi, \"nigdy " "nie uczy dwukrotnie tej samej osoby.\"" msgid "" "The butler admits you to see the master of the house. He trains you in the " "four skills a hero should know." msgstr "" "Lokaj prowadzi ciÄ™ do pana tego domostwa, który uczy ciÄ™ czterech " "umiejÄ™tnoÅ›ci potrzebnych każdemu bohaterowi." msgid "" "The butler opens the door and looks you up and down. \"You are neither " "famous nor diplomatic enough to be admitted to see my master,\" he sniffs. " "\"Come back when you think yourself worthy.\"" msgstr "" "Lokaj otwiera drzwi i dokÅ‚adnie ci siÄ™ przyglÄ…da. \"Nie znasz siÄ™ na " "dyplomacji ani nie jesteÅ› na tyle sÅ‚awnym bohaterem, by móc spotkać siÄ™ z " "moim panem,\" mówi. \"Wróć kiedy uznasz, że na to zasÅ‚ugujesz.\"" msgid " and " msgstr " i " msgid "" "All of the %{monsters} you have in your army have been trained by the battle " "masters of the fort. Your army now contains %{monsters2}." msgstr "" "Twoje jednostki, %{monsters}, przechodzÄ… szkolenie u mistrzów walki z tego " "fortu. W twojej armii sÄ… teraz %{monsters2}." msgid "" "An unusual alliance of Ogres, Orcs, and Dwarves offer to train (upgrade) any " "such troops brought to them. Unfortunately, you have none with you." msgstr "" "Nietypowe przymierze Orków, Ogrów i Krasnoludów proponuje ci wyszkolenie " "(ulepszenie) jednostek, które tu przyprowadzisz. Niestety nie masz oddziałów " "tego typu." msgid "All of your %{monsters} have been upgraded into %{monsters2}." msgstr "" "Twoje jednostki, %{monsters}, zostajÄ… ulepszone. W twojej armii sÄ… teraz " "%{monsters2}." msgid "" "A blacksmith working at the foundry offers to convert all Pikemen and " "Swordsmen's weapons brought to him from iron to steel. He also says that he " "knows a process that will convert Iron Golems into Steel Golems. " "Unfortunately, you have none of these troops in your army, so he can't help " "you." msgstr "" "Kowal pracujÄ…cy w kuźni może ulepszyć bronie Pikinierów i Zbrojnych z " "żelaznych na stalowe. Twierdzi także, że zna metodÄ™ zamiany Å»elaznych " "Golemów w Golemy Stalowe. Niestety nie masz żadnej z tych jednostek w swojej " "armii, wiÄ™c nie może ci pomóc." msgid "" "The captain looks at you with surprise and says:\n" "\"You already have all the maps I know about. Let me fish in peace now.\"" msgstr "" "Kapitan spoglÄ…da na ciebie ze zdziwieniem i mówi:\n" "\"Masz już wszystkie znane przeze mnie mapy. A teraz daj mi poÅ‚owić w " "spokoju.\"" msgid "" "A retired captain living on this refurbished fishing platform offers to sell " "you maps of the sea he made in his younger days for 1,000 gold. Do you wish " "to buy the maps?" msgstr "" "Emerytowany kapitan mieszkajÄ…cy na przebudowanej platformie rybackiej za " "1000 sztuk zÅ‚ota oferuje ci morskie mapy, które wykonaÅ‚ w czasach mÅ‚odoÅ›ci. " "Czy chcesz je kupić?" msgid "" "The captain sighs. \"You don't have enough money, eh? You can't expect me " "to give my maps away for free!\"" msgstr "" "Kapitan wzdycha. \"Nie masz dość pieniÄ™dzy, co? Nie myÅ›l sobie, że oddam ci " "moje mapy za darmo!\"" msgid "" "You come upon an obelisk made from a type of stone you have never seen " "before. Staring at it intensely, the smooth surface suddenly changes to an " "inscription. The inscription is a piece of a lost ancient map. Quickly you " "copy down the piece and the inscription vanishes as abruptly as it appeared." msgstr "" "Natrafiasz na dziwny obelisk wykuty z nieznanego ci kamienia. OglÄ…dajÄ…c " "gÅ‚adkÄ… powierzchniÄ™, zauważasz, iż nieoczekiwanie pojawiajÄ… siÄ™ na niej " "jakieÅ› znaki ukÅ‚adajÄ…ce siÄ™ w mapÄ™. Szybko kopiujesz rysunek, który wkrótce " "znika tak nagle, jak siÄ™ pojawiÅ‚." msgid "You have already been to this obelisk." msgstr "Ten obelisk jest już ci znany." msgid "" "Upon your approach, the tree opens its eyes in delight. \"It is good to see " "you, my student. I hope my teachings have helped you.\"" msgstr "" "Drzewo obserwuje twoje przybycie z wyraźnym zadowoleniem. \"Witaj, miÅ‚o ciÄ™ " "znowu widzieć. Mam nadziejÄ™, iż moja wiedza okazaÅ‚a siÄ™ przydatna.\"" msgid "" "Upon your approach, the tree opens its eyes in delight. \"Ahh, an " "adventurer! Allow me to teach you a little of what I have learned over the " "ages.\"" msgstr "" "Drzewo obserwuje twoje przybycie z wyraźnym zadowoleniem. \"Ach, strudzony " "podróżnik! Pozwól, że przekażę ci część mojej wielowiekowej wiedzy.\"" msgid "Upon your approach, the tree opens its eyes in delight." msgstr "Drzewo obserwuje twoje przybycie z wyraźnym zadowoleniem." msgid "" "\"Ahh, an adventurer! I will be happy to teach you a little of what I have " "learned over the ages for a mere %{count} %{res}.\"" msgstr "" "\"Ah, wÄ™drowiec! BÄ™dÄ™ szczęśliwy móc nauczyć CiÄ™ tego czego sam nauczyÅ‚em " "siÄ™ przez te wszystkie lata za drobne %{count} %{res}.\"" msgid "(Just bury it around my roots.)" msgstr "(Zakop je po prostu pod moimi korzeniami.)" msgid "Tears brim in the eyes of the tree." msgstr "Do oczu drzewa napÅ‚ywajÄ… Å‚zy." msgid "\"I need %{count} %{res}.\"" msgstr "\"PotrzebujÄ™ %{count} %{res}.\"" msgid "it whispers. (sniff) \"Well, come back when you can pay me.\"" msgstr "szepcze Å‚kajÄ…c. \"No cóż, wróć kiedy je zdobÄ™dziesz.\"" msgid "" "Nestled among the trees sits a blind seer. After you explain the intent of " "your journey, the seer activates his crystal ball, allowing you to see the " "strengths and weaknesses of your opponents." msgstr "" "PomiÄ™dzy drzewami siedzi niewidomy prorok. Gdy wyjaÅ›niasz mu cel swej " "podróży, prorok uaktywnia kryszczaÅ‚owÄ… kulÄ™, w której możesz zobaczyć sÅ‚abe " "i mocne punkty twych przeciwników." msgid "" "The entrance to the cave is dark, and a foul, sulfurous smell issues from " "the cave mouth. Will you enter?" msgstr "" "WejÅ›cie do jaskini jest czarne jak smoÅ‚a, a z ciemnoÅ›ci twych nozdrzy " "dochodzi ohydny, siarkowy odór. Czy chcesz wejść?" msgid "Except for evidence of a terrible battle, the cave is empty." msgstr "Jaskinia jest pusta, nie liczÄ…c Å›ladów po straszliwej walce." msgid "" "You find a powerful and grotesque Demon in the cave. \"Today,\" it rasps, " "\"you will fight and surely die. But I will give you a choice of deaths. You " "may fight me, or you may fight my servants. Do you prefer to fight my " "servants?\"" msgstr "" "W jaskini znajdujesz potężnego i przerażajÄ…cego demona. \"DziÅ›,\" charczy " "demon, \"bÄ™dziesz walczyć i z pewnoÅ›ciÄ… zginiesz. Ale pozwolÄ™ ci wybrać " "rodzaj Å›mierci. Możesz walczyć ze mnÄ… lub z moimi sÅ‚ugami. Wolisz walczyć z " "moimi sÅ‚ugami?\"" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points." msgstr "" "Demon wydaje z siebie wyzywajÄ…cy okrzyk i atakuje! Po krótkiej bitwie, " "zabijasz potwora i otrzymjesz %{exp} punktów doÅ›wiadczenia." msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points and " "%{count} gold." msgstr "" "Demon wykrzykujÄ…c wyzywa CiÄ™ na pojedynek po czym atakuje. Po krótkiej, " "rozpaczliwej walce, zabijasz potwora i otrzymujesz %{exp} punktów " "doÅ›wiadczenia i %{count} zÅ‚ota." msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and find the %{art} in the back of the cave." msgstr "" "Demon ryczy i rusza do ataku! Po krótkiej i gwaÅ‚townej walce pokonujesz " "potwora, a w głębi jaskini znajdujesz artefakt - %{art}." msgid "" "Seeing that you do not have %{count} gold, the demon slashes you with its " "claws, and the last thing you see is a red haze." msgstr "" "WidzÄ…c, że nie masz %{count} zÅ‚ota, demon rozrywa CiÄ™ swoimi pazurami, a " "ostatniÄ… rzeczÄ… którÄ… widzisz jest czerwona mgieÅ‚ka." msgid "" "The Demon leaps upon you and has its claws at your throat before you can " "even draw your sword. \"Your life is mine,\" it says. \"I will sell it back " "to you for %{count} gold.\"" msgstr "" "Demon doskakuje do Ciebie i chwyta szponami za gardÅ‚o zanim zdążyÅ‚eÅ› " "wyciÄ…gnąć miecz. \"Twoje życie jest moje\", powiedziaÅ‚. \"Odsprzedam Ci je " "za %{count} zÅ‚ota.\"" msgid "" "Upon defeating the daemon's servants, you find a hidden cache with %{count} " "gold." msgstr "" "Po pokonaniu sÅ‚ugusów demona, odnajdujesz ukrytÄ… skrzyniÄ™ z %{count} zÅ‚ota." msgid "" "As you enter the Alchemist's Tower, a hobbled, graying man in a brown cloak " "makes his way towards you." msgstr "" "Gdy wchodzisz do Wieży Alchemika zbliża siÄ™ w TwojÄ… stronÄ™ kulejÄ…cy, " "posiwiaÅ‚y mężczyzna w brunatnej szacie." msgid "He checks your pack, and sees that you have 1 cursed item." msgid_plural "" "He checks your pack, and sees that you have %{count} cursed items." msgstr[0] "Sprawdza twój plecak i zauważa że posiadasz 1 zaklÄ™ty przedmiot." msgstr[1] "" "Sprawdza twój plecak i zauważa że posiadasz %{count} zaklÄ™tych przedmiotów." msgstr[2] "" "Sprawdza twój plecak i zauważa że posiadasz %{count} zaklÄ™tych przedmiotów." msgid "For %{gold} gold, the alchemist will remove it for you. Do you pay?" msgid_plural "" "For %{gold} gold, the alchemist will remove them for you. Do you pay?" msgstr[0] "" "Za %{gold} zÅ‚ota, alchemik usunie to za Ciebie. Czy chcesz zapÅ‚acić ?" msgstr[1] "" "Za %{gold} zÅ‚ota, alchemik usunie to za Ciebie. Czy chcesz zapÅ‚acić ?" msgstr[2] "" "Za %{gold} zÅ‚ota, alchemik usunie to za Ciebie. Czy chcesz zapÅ‚acić ?" msgid "" "After you consent to pay the requested amount of gold, the alchemist grabs " "the cursed artifact and throws it into his magical cauldron." msgid_plural "" "After you consent to pay the requested amount of gold, the alchemist grabs " "all cursed artifacts and throws them into his magical cauldron." msgstr[0] "" "Kiedy zgadasz siÄ™ zapÅ‚acić zażądanÄ… kwotÄ™ w zÅ‚ocie, alchemik chwyta " "przeklÄ™ty artefakt i wrzuca go do swego magicznego kotÅ‚a." msgstr[1] "" "Kiedy zgadasz siÄ™ zapÅ‚acić zażądanÄ… kwotÄ™ w zÅ‚ocie, alchemik chwyta " "przeklÄ™te artefakty i wrzuca je do swego magicznego kotÅ‚a." msgstr[2] "" "Kiedy zgadasz siÄ™ zapÅ‚acić zażądanÄ… kwotÄ™ w zÅ‚ocie, alchemik chwyta " "przeklÄ™te artefakty i wrzuca je do swego magicznego kotÅ‚a." msgid "" "You hear a voice from behind the locked door, \"You don't have enough gold " "to pay for my services.\"" msgstr "" "SÅ‚yszysz gÅ‚os zza zamkniÄ™tych drzwi, \"Nie masz wystarczajÄ…cej iloÅ›ci zÅ‚ota " "aby zapÅ‚acić za moje usÅ‚ugi.\"" msgid "" "You hear a voice from high above in the tower, \"Go away! I can't help you!\"" msgstr "" "SÅ‚yszysz gÅ‚os dobiegajÄ…cy z górnych piÄ™ter wieży: \"Odejdź! Nie mogÄ™ ci " "pomóc!\"" msgid "" "The head groom speaks to you, \"That is a fine looking horse you have. I am " "afraid we can give you no better, but the horses your cavalry are riding " "look to be of poor breeding stock. We have many trained war horses which " "would aid your riders greatly. I insist you take them.\"" msgstr "" "Główny koniuszy mówi do ciebie: \"Masz Å›wietnego rumaka. Obawiam siÄ™, że nie " "mogÄ™ dać ci lepszego, ale konie twojej kawalerii zdajÄ… siÄ™ pochodzić z nie " "najlepszej hodowli. Mamy wiele szkolony rumaków bojowych, które przydaÅ‚yby " "siÄ™ twoim jeźdźcom. Weź je, nalegam.\"" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, he will " "grow tired in a week. You must also let me give better horses to your " "mounted soldiers, their horses look shoddy and weak.\"" msgstr "" "Gdy zbliżasz siÄ™ do stajni, wychodzi z niej główny koniuszy prowadzÄ…cy " "piÄ™knego konia bojowego. \"Ten rumak pomoże ci w czasie twych podróży. " "Niestety, po tygodniu jazdy siÄ™ zmÄ™czy. Pozwól, że dam ci również lepsze " "konie dla twych kawalerzystów, bo te wyglÄ…dajÄ… na dość marne.\"" msgid "" "The head groom approaches you and speaks, \"You already have a fine horse, " "and have no inexperienced cavalry which might make use of our trained war " "horses.\"" msgstr "" "Podchodzi do ciebie główny koniuszy i mówi. \"Masz już Å›wietnego konia i nie " "dowodzisz niedoÅ›wiadczonÄ… kawaleriÄ…, której mogÅ‚yby siÄ™ przydać nasze " "szkolone rumaki bojowe.\"" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, his " "endurance will wane with a lot of heavy riding, and you must return for a " "fresh mount in a week. We also have many fine war horses which could benefit " "mounted soldiers, but you have none we can help.\"" msgstr "" "Gdy zbliżasz siÄ™ do stajni, wychodzi z niej główny koniuszy prowadzÄ…cy " "piÄ™knego konia bojowego. \"Ten rumak pomoże ci w czasie twych podróży. " "Niestety osÅ‚abnie w przypadku ciężkiej jazdy, wiÄ™c w przeciÄ…gu tygodnia " "musisz wrócić po nowego konia. Mamy także wiele rumaków bojowych, które " "byÅ‚yby dobre dla kawalerii, ale nie masz z sobÄ… takich oddziałów.\"" msgid "The Arena guards turn you away." msgstr "Strażnicy Areny każą ci odejść." msgid "" "You have your crew stop up their ears with wax before the sirens' eerie song " "has any chance of luring them to a watery grave." msgstr "" "Zanim pieśń syren ma szansÄ™ dotrzeć do uszu twoich ludzi i zmusić ich do " "wskoczenia w odmÄ™ty, rozkazujesz, aby wszyscy zalepili uszy woskiem." msgid "" "As the sirens sing their eerie song, your small, determined army manages to " "overcome the urge to dive headlong into the sea." msgstr "" "Chociaż pieśń syren brzmi niesamowicie, twojej zdeterminowanej zaÅ‚odze udaje " "siÄ™ pokonać obezwÅ‚adniajÄ…cÄ… chęć wyskoczenia za burtÄ™." msgid "" "An eerie wailing song emanates from the sirens perched upon the rocks. Many " "of your crew fall under its spell, and dive into the water where they drown. " "You are now wiser for the visit, and gain %{exp} experience." msgstr "" "Nad wodÄ… niesie siÄ™ dziwna, pÅ‚aczliwa pieśń syren. Wielu czÅ‚onków twojej " "zaÅ‚ogi poddaje siÄ™ czarowi pieÅ›ni i skacze za burtÄ™, pogrążajÄ…c siÄ™ na " "zawsze w odmÄ™tach oceanu. To smutne doÅ›wiadczenie daje ci %{exp} pkt. " "doÅ›wiadczenia." msgid "" "In a dazzling display of daring, you break into the local jail and free the " "hero imprisoned there, who, in return, pledges loyalty to your cause." msgstr "" "W gwaÅ‚townym porywie odwagi wyważasz drzwi wiÄ™zienia i uwalniasz zamkniÄ™tego " "w nim bohatera, który w ramach wdziÄ™cznoÅ›ci przysiÄ™ga ci lojalność." msgid "" "You already have %{count} heroes, and regretfully must leave the prisoner in " "this jail to languish in agony for untold days." msgstr "" "Dowodzisz już %{count} bohaterami, musisz wiÄ™c pozostawić więźnia w jego " "celi, skazujÄ…c go tym samym na powolnÄ… agoniÄ™ w udrÄ™ce." msgid "" "You enter a rickety hut and talk to the magician who lives there. He tells " "you of places near and far which may aid you in your journeys." msgstr "" "Wchodzisz do prymitywnej chatki i spotykasz mieszkajÄ…cego w niej magika. " "Opowiada ci o różnych odlegÅ‚ych miejscach, co może okazać siÄ™ pomocne w " "czasie twych podróży." msgid "This eye seems to be intently studying its surroundings." msgstr "Oko wydaje siÄ™ dokÅ‚adnie studiować okolicÄ™." msgid "You come across a giant Sphinx. The Sphinx remains strangely quiet." msgstr "Zbliżasz siÄ™ do wielkiego sfinksa, który tajemniczo milczy." msgid "" "\"I have a riddle for you,\" the Sphinx says. \"Answer correctly, and you " "shall be rewarded. Answer incorrectly, and you shall be eaten. Do you accept " "the challenge?\"" msgstr "" "\"Mam dla ciebie zagadkÄ™,\" mówi Sfinks. \"JeÅ›li odpowiesz dobrze, dam ci " "nagrodÄ™. JeÅ›li odpowiesz źle, pożrÄ™ ciÄ™. Podejmujesz moje wyzwanie?\"" msgid "" "The Sphinx asks you the following riddle:\n" "\n" "'%{riddle}'\n" "\n" "Your answer?" msgstr "" "Sfinks zadaje ci nastÄ™pujÄ…cÄ… zagadkÄ™: \n" "\n" "'%{riddle}'\n" "\n" " Twoja odpowiedź?" msgid "" "\"You guessed incorrectly,\" the Sphinx says, smiling. The Sphinx swipes at " "you with a paw, knocking you to the ground. Another blow makes the world go " "black, and you know no more." msgstr "" "\"Twoja odpowiedź jest błędna,\" mówi sfinks z uÅ›miechem. NastÄ™pnie powala " "ciÄ™ Å‚apÄ… i przyciska do ziemi. Po nastÄ™pnym ciosie Å›wiat ciemnieje ci przed " "oczami i nic wiÄ™cej nie pamiÄ™tasz." msgid "" "Looking somewhat disappointed, the Sphinx sighs. \"You've answered my riddle " "so here's your reward. Now begone.\"" msgstr "" "Sfinks wyglÄ…da na rozczarowanego i wzdycha \"UdaÅ‚o ci siÄ™ odpowiedzieć na " "mojÄ… zagadkÄ™, wiÄ™c oto twoja nagroda. A teraz odejdź.\"" msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "As you speak the magic word, the glowing barrier dissolves into nothingness." msgstr "" "Przed tobÄ… wznosi siÄ™ magiczna bariera, blokujÄ…ca ci drogÄ™. Runiczny napis " "gÅ‚osi, \"Wypowiedz sÅ‚owo, a otworzy siÄ™ przejÅ›cie.\"\n" "Gdy wypowiadasz magiczne sÅ‚owo, lÅ›niÄ…ca bariera rozwiewa siÄ™ w powietrzu." msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "You speak, and nothing happens." msgstr "" "Przed tobÄ… wznosi siÄ™ magiczna bariera, blokujÄ…ca ci drogÄ™. Runiczny napis " "gÅ‚osi, \"Wypowiedz sÅ‚owo, a otworzy siÄ™ przejÅ›cie.\"\n" "Wypowiadasz sÅ‚owo, ale nic siÄ™ nie dzieje." msgid "" "You enter the tent and see an old woman gazing into a magic gem. She looks " "up and says,\n" "\"In my travels, I have learned much in the way of arcane magic. A great " "oracle taught me his skill. I have the answer you seek.\"" msgstr "" "Wchodzisz do namiotu i widzisz staruszkÄ™ wpatrujÄ…cÄ… siÄ™ w magiczny klejnot. " "SpoglÄ…da na ciebie i mówi: \"W czasie mych podróży wielem siÄ™ dowiedziaÅ‚a o " "tajemnych sztukach magii. Wyrocznia przekazaÅ‚a mi swojÄ… wiedzÄ™. Znam " "odpowiedź, której szukasz.\"" #, fuzzy msgid "No spell book is present." msgstr "Nie posiadasz KsiÄ™gi Zaklęć." msgid "" "This spell costs %{mana} spell points. You have no spell points, so you " "cannot cast it." msgstr "" "To zaklÄ™cie kosztuje %{mana} pkt. magii. Nie masz w ogóle many, wiÄ™c nie " "możesz go rzucić." msgid "" "This spell costs %{mana} spell points. You only have %{point} spell points, " "so you cannot cast it." msgstr "" "To zaklÄ™cie kosztuje %{mana} pkt. magii. Masz tylko %{point} pkt. magii, " "wiÄ™c nie możesz go rzucić." msgid "The spell was not found." msgstr "ZaklÄ™cie nie zostaÅ‚o znalezione." msgid "Only heroes can cast this spell." msgstr "Tylko bohaterowie mogÄ… rzucać to zaklÄ™cie." msgid "This hero is not able to cast adventure spells." msgstr "Twój bohater nie może rzucić żadnego czaru podróżnego." msgid "Your hero is too tired to cast this spell today. Try again tomorrow." msgstr "" "Twój bohater jest zbyt zmÄ™czony, by rzucić dziÅ› to zaklÄ™cie. Spróbuj " "ponownie jutro." msgid "This spell cannot be cast on a boat." msgstr "To zaklÄ™cie nie może być rzucone z Å‚odzi." msgid "This spell can only be cast near an ocean." msgstr "To zaklÄ™cie może być rzucone tylko przy wodzie." msgid "" "There are no boats available and no ocean adjacent to the hero where this " "spell will work." msgstr "" "Nie można rzucić zaklÄ™cia, ponieważ nie ma dostÄ™pnej Å‚odzi oraz nie ma " "miejsca blisko bohatera przy brzegu." msgid "There are no boats available for this spell." msgstr "Nie możesz rzucić zaklÄ™cia, ponieważ nie ma dostÄ™pnej Å‚odzi." msgid "There is no ocean adjacent to the hero where this spell will work." msgstr "" "Nie możesz rzucić zaklÄ™cia, ponieważ nie ma miejsca koÅ‚o bohatera przy " "brzegu." #, fuzzy msgid "" "You do not own any town or castle that is not currently occupied by a hero. " "This spell will have no effect." msgstr "" "Nie posiadasz w tym momencie żadnego miasta lub zamku, z tego powodu nie " "możesz rzucić tego zaklÄ™cia." #, fuzzy msgid "This hero is already in a town, so this spell will have no effect." msgstr "Ten bohater znajduje siÄ™ już w zamku, wiÄ™c nie możesz rzucić zaklÄ™cia." msgid "" "The nearest town is %{town}.\n" "\n" "This town is occupied by your hero %{hero}." msgstr "" "Najbliższym miastem jest %{town}.\n" "\n" "To miasto jest zajÄ™te przez twojego bohatera %{hero}." msgid "This spell is already in effect." msgstr "To zaklÄ™cie zostaÅ‚o już rzucone." msgid "" "No opponent neither has nor can have any hero under their command anymore. " "Casting this spell will have no effect." msgstr "" "Å»aden przeciwnik nie ma ani nie może mieć bohatera. Użycie tego zaklÄ™cia nie " "da żadnego rezultatu." msgid "" "No opponent has a hero under their command at this time. Casting this spell " "will have no effect." msgstr "" "Å»aden przeciwnik nie ma aktualnie bohatera. Użycie tego zaklÄ™cia nie da " "żadnego rezultatu." msgid "" "You must be within %{count} spaces of a monster for the Visions spell to " "work." msgstr "" "Aby zaklÄ™cie 'Wizje' zadziaÅ‚aÅ‚o, musisz znajdować siÄ™ w zasiÄ™gu %{count} pól " "od przeciwnika." msgid "" "You must be standing on the entrance to a mine (sawmills and alchemist labs " "do not count) to cast this spell." msgstr "" "Aby móc rzucić to zaklÄ™cie, musisz stać w wejÅ›ciu do kopalni (tartaki i " "laboratoria nie sÄ… brane pod uwagÄ™)." msgid "You must first defeat the ghosts guarding the mine to cast this spell." msgstr "Aby rzucić ten czar, musisz najpierw pokonać duchy strzegÄ…ce kopalni." msgid "" "There are already at least as many elementals guarding the mine as this hero " "can generate. Casting this spell will have no effect." msgstr "" "Nie przywoÅ‚asz wiÄ™cej Å»ywioÅ‚aków, niż aktualnie strzeże kopalnii. Rzucenie " "tego zaklÄ™cia nie da żadnego rezultatu." msgid "%{name} the %{race} (Level %{level})" msgstr "%{name}, %{race} (poziom %{level})" #, fuzzy msgid "" "'Spread' combat formation spreads the hero's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "Wybór luźnej formacji oznacza, że twoja wojska bÄ™dÄ… stać rozproszone wzdÅ‚uż " "pola bitwy, a pomiÄ™dzy oddziaÅ‚ami bÄ™dzie przynajmniej jedno wolne pole." #, fuzzy msgid "" "'Grouped' combat formation bunches the hero's army together in the center of " "their side of the battlefield." msgstr "" "Wybór zwartej formacji sprawia, że twoja armia zbierze siÄ™ razem poÅ›rodku " "twojej części pola bitwy." msgid "Are you sure you want to dismiss this Hero?" msgstr "Czy na pewno chcesz zwolnić tego bohatera?" msgid "View Experience Info" msgstr "DoÅ›wiadczenie - informacje" msgid "View Spell Points Info" msgstr "Punkty magii - informacje" msgid "Set army combat formation to 'Spread'" msgstr "Ustaw armiÄ™ w luźnej formacji" msgid "Set army combat formation to 'Grouped'" msgstr "Ustaw armiÄ™ w zwartej formacji" msgid "Exit Hero Screen" msgstr "WyjÅ›cie z ekranu bohatera" msgid "You cannot dismiss a hero in a castle" msgstr "Nie możesz zwolnić bohatera w zamku" msgid "Dismissal of %{name} the %{race} is prohibited by scenario" msgstr "Zwolnienie %{name}, %{race}, jest zabronione w tym scenariuszu" msgid "Dismiss %{name} the %{race}" msgstr "Zwolnij bohatera: %{name}, %{race}" msgid "Show previous hero" msgstr "Pokaż poprzedniego bohatera" msgid "Show next hero" msgstr "Pokaż nastÄ™pnego bohatera" msgid "Blood Morale" msgstr "Wysokie morale" msgid "%{morale} Morale" msgstr "%{morale} morale" msgid "%{luck} Luck" msgstr "%{luck} szczęście" msgid "Current Luck Modifiers:" msgstr "Obecne modyfikatory szczęścia:" msgid "Current Morale Modifiers:" msgstr "Obecne modyfikatory morale:" msgid "Entire army is undead, so morale does not apply." msgstr "Wszystkie oddziaÅ‚y to nieumarli, na których nie wpÅ‚ywa morale." msgid "" "Current experience %{exp1}.\n" " Next level %{exp2}." msgstr "" "Obecne doÅ›wiadczenie %{exp1}.\n" " NastÄ™pny poziom %{exp2}." msgid "Level %{level}" msgstr "Poziom %{level}" #, fuzzy msgid "" "%{name} currently has %{point} spell points out of a maximum of %{max}. The " "maximum number of spell points is 10 times the hero's knowledge. It is " "occasionally possible for the hero to have more than their maximum spell " "points via special events." msgstr "" "%{name} posiada obecnie %{point} pkt. magii z maksimum wynoszÄ…cego %{max}. " "Maksymalna liczba to wartość współczynnika wiedzy pomnożona razy 10. Czasami " "możliwe jest posiadanie wiÄ™kszej liczby punktów magii, dziÄ™ki specjalnym " "wydarzeniom." msgid "%{name1} meets %{name2}" msgstr "Spotkanie: %{name1} i %{name2}" msgid "Enemy heroes are now fully identifiable." msgstr "Można już w peÅ‚ni zidentyfikować wrogich bohaterów." msgid "Identify Hero" msgstr "Identyfikacja bohatera" msgid "Select town to port to." msgstr "Wybierz miasto docelowe." msgid "Town Portal" msgstr "Miejski Portal" msgid "The creatures are willing to join us!" msgstr "Te stworzenia sÄ… chÄ™tne przyłączyć siÄ™ do nas!" msgid "All the creatures will join us..." msgstr "Wszystkie te stworzenia dołączÄ… do nas..." msgid "The creature will join us..." msgid_plural "%{count} of the creatures will join us..." msgstr[0] "Jedno stworzenie dołączy do nas..." msgstr[1] "Część tych stworzeÅ„ (%{count}) dołączy do nas..." msgstr[2] "Część tych stworzeÅ„ (%{count}) dołączy do nas..." msgid "" "\n" " for a fee of %{gold} gold." msgstr "" "\n" " za zapÅ‚atÄ… %{gold} zÅ‚ota." msgid "These weak creatures will surely flee before us." msgstr "Te sÅ‚abowite stworzenia na pewno przed nami ucieknÄ…." msgid "I fear these creatures are in the mood for a fight." msgstr "Obawiam siÄ™, że te stworzenia sÄ… gotowe do walki." #, fuzzy msgid "The hero's attack skill is a bonus added to each unit's attack skill." msgstr "" "Twój współczynnik ataku stanowi premiÄ™ dodawanÄ… do ataku każdej jednostki." #, fuzzy msgid "The hero's defense skill is a bonus added to each unit's defense skill." msgstr "" "Twój współczynnik obrony stanowi premiÄ™ dodawanÄ… do obrony każdej jednostki." #, fuzzy msgid "The hero's spell power determines the duration or power of a spell." msgstr "" "Od twojej mocy zaklęć zależy dÅ‚ugość i moc rzucanych przez ciebie czarów." #, fuzzy msgid "" " The hero's knowledge determines how many spell points the hero may have. " "Under normal circumstances, a hero is limited to 10 spell points per level " "of knowledge." msgstr "" "Od wiedzy zależy liczba punktów magii, jakie może posiadać bohater. W " "normalnych warunkach bohater ograniczony jest do 10 punktów magii na każdy " "poziom wiedzy." msgid "Current Modifiers:" msgstr "Aktualne modyfikatory:" msgid "skill|Basic" msgstr "Podstawowa" msgid "skill|Advanced" msgstr "Zaawansowana" msgid "skill|Expert" msgstr "Ekspert" msgid "Pathfinding" msgstr "Znaj. drogi" msgid "Archery" msgstr "Åucznictwo" msgid "Logistics" msgstr "Logistyka" msgid "Scouting" msgstr "Zwiad" msgid "Diplomacy" msgstr "Dyplomacja" msgid "Navigation" msgstr "Nawigacja" msgid "Leadership" msgstr "Dowodzenie" msgid "Wisdom" msgstr "MÄ…drość" msgid "Mysticism" msgstr "Mistycyzm" msgid "Ballistics" msgstr "Balistyka" msgid "Eagle Eye" msgstr "Sokole oko" msgid "Necromancy" msgstr "Nekromancja" msgid "Estates" msgstr "Finanse" msgid "Advanced Archery" msgstr "Zaawansowane Å‚ucznictwo" msgid "Advanced Pathfinding" msgstr "Zaawansowane znaj. drogi" msgid "Basic Archery" msgstr "Podstawowe Å‚ucznictwo" msgid "Basic Pathfinding" msgstr "Podstawowe znaj. drogi" msgid "Expert Pathfinding" msgstr "Mistrzowskie znaj. drogi" msgid "Advanced Logistics" msgstr "Zaawansowana logistyka" msgid "Basic Logistics" msgstr "Podstawowa logistyka" msgid "Basic Scouting" msgstr "Podstawowy zwiad" msgid "Expert Archery" msgstr "Mistrzowskie Å‚ucznictwo" msgid "Expert Logistics" msgstr "Mistrzowska logistyka" msgid "Advanced Diplomacy" msgstr "Zaawansowana dyplomacja" msgid "Advanced Scouting" msgstr "Zaawansowany zwiad" msgid "Basic Diplomacy" msgstr "Podstawowa dyplomacja" msgid "Expert Diplomacy" msgstr "Mistrzowska dyplomacja" msgid "Expert Scouting" msgstr "Mistrzowski zwiad" msgid "Advanced Leadership" msgstr "Zaawansowane dowodzenie" msgid "Advanced Navigation" msgstr "Zaawansowana nawigacja" msgid "Basic Leadership" msgstr "Podstawowe dowodzenie" msgid "Basic Navigation" msgstr "Podstawowa nawigacja" msgid "Expert Navigation" msgstr "Mistrzowska nawigacja" msgid "Advanced Wisdom" msgstr "Zaawansowana mÄ…drość" msgid "Basic Mysticism" msgstr "Podstawowy mistycyzm" msgid "Basic Wisdom" msgstr "Podstawowa mÄ…drość" msgid "Expert Leadership" msgstr "Mistrzowskie dowodzenie" msgid "Expert Wisdom" msgstr "Mistrzowska mÄ…drość" msgid "Advanced Luck" msgstr "Zaawansowane szczęście" msgid "Advanced Mysticism" msgstr "Zaawansowany mistycyzm" msgid "Basic Luck" msgstr "Podstawowe szczęście" msgid "Expert Luck" msgstr "Mistrzowskie szczęście" msgid "Expert Mysticism" msgstr "Mistrzowski mistycyzm" msgid "Advanced Ballistics" msgstr "Zaawansowana balistyka" msgid "Advanced Eagle Eye" msgstr "Zaawansowane sokole oko" msgid "Basic Ballistics" msgstr "Podstawowa balistyka" msgid "Basic Eagle Eye" msgstr "Podstawowe sokole oko" msgid "Expert Ballistics" msgstr "Mistrzowska balistyka" msgid "Advanced Necromancy" msgstr "Zaawansowana nekromancja" msgid "Basic Estates" msgstr "Podstawowe finanse" msgid "Basic Necromancy" msgstr "Podstawowa nekromancja" msgid "Expert Eagle Eye" msgstr "Mistrzowskie sokole oko" msgid "Expert Necromancy" msgstr "Mistrzowska nekromancja" msgid "Advanced Estates" msgstr "Zaawansowane finanse" msgid "Expert Estates" msgstr "Mistrzowskie finanse" msgid "" "%{skill} reduces the movement penalty for rough terrain by %{count} percent." msgstr "" "O %{count} procent zmniejsza ograniczenie ruchu podczas poruszania siÄ™ po " "trudnym terenie." msgid "%{skill} eliminates the movement penalty for rough terrain." msgstr "" "Eliminuje ograniczenie ruchu podczas poruszania siÄ™ po trudnym terenie." #, fuzzy msgid "" "%{skill} increases the damage done by the hero's range attacking creatures " "by %{count} percent." msgstr "" "O %{count} procent zwiÄ™ksza obrażenia zadawane przez jednostki atakujÄ…ce na " "odlegÅ‚ość." #, fuzzy msgid "%{skill} increases the hero's movement points by %{count} percent." msgstr "PowiÄ™ksza ilość punktów ruchu bohatera o %{count} percent." #, fuzzy msgid "%{skill} increases the hero's viewable area by one square." msgid_plural "%{skill} increases the hero's viewable area by %{count} squares." msgstr[0] "ZwiÄ™ksza pole widzenia bohatera o %{count}." msgstr[1] "ZwiÄ™ksza pole widzenia bohatera o %{count}." msgstr[2] "ZwiÄ™ksza pole widzenia bohatera o %{count}." #, fuzzy msgid "" "%{skill} allows the hero to negotiate with monsters who are weaker than " "their army, and reduces the cost of surrender." msgstr "Pozwala negocjować ze sÅ‚abszymi przeciwnikami. " #, fuzzy msgid "" "Approximately %{count} percent of the creatures may offer to join the hero." msgstr "" "OkoÅ‚o %{count} procent stworzeÅ„ zaproponuje dołączenie do twojej armii." #, fuzzy msgid "All of the creatures may offer to join the hero." msgstr "Wszystkie stworzenia mogÄ… zaproponować dołączenie do twojej armii." #, fuzzy msgid "" "The cost of surrender is reduced to %{percent} percent of the total cost of " "troops in the army." msgstr "" "%{name} redukuje koszt kapitulacji do %{count} procent caÅ‚ego kosztu armii " "bÄ™dÄ…cej w posiadaniu." #, fuzzy msgid "" "%{skill} increases the hero's movement points over water by %{count} percent." msgstr "PowiÄ™ksza ilość punktów ruchu bohatera na wodzie o %{count} procent." #, fuzzy msgid "%{skill} increases the hero's troops morale by %{count}." msgstr "ZwiÄ™ksza morale wszystkich oddziałów bohatera o %{count}." #, fuzzy msgid "%{skill} allows the hero to learn third level spells." msgstr "" "Pozwala twojemu bohaterowi na zrozumienie i poznanie zaklęć trzeciego " "poziomu." #, fuzzy msgid "%{skill} allows the hero to learn fourth level spells." msgstr "" "Pozwala twojemu bohaterowi na zrozumienie i poznanie zaklęć czwartego " "poziomu." #, fuzzy msgid "%{skill} allows the hero to learn fifth level spells." msgstr "Pozwala nauczyć siÄ™ twojemu bohaterowi zaklęć piÄ…tego poziomu." #, fuzzy msgid "%{skill} regenerates one additional spell point per day to the hero." msgid_plural "" "%{skill} regenerates %{count} additional spell points per day to the hero." msgstr[0] "%{skill} regeneruje %{count} punkt magii twojego bohatera na dzieÅ„." msgstr[1] "" "%{skill} regeneruje %{count} punkty magii twojego bohatera na dzieÅ„." msgstr[2] "" "%{skill} regeneruje %{count} punktów magii twojego bohatera na dzieÅ„." #, fuzzy msgid "%{skill} increases the hero's luck by %{count}." msgstr "ZwiÄ™ksza szczęście bohatera o %{count}." #, fuzzy msgid "" "%{skill} gives the hero's catapult shots a greater chance to hit and do " "damage to castle walls." msgstr "" "StrzaÅ‚y z katapulty majÄ… wiÄ™kszÄ… szansÄ… na trafienie i uszkodzenie zamkowych " "murów." #, fuzzy msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot has a " "greater chance to hit and do damage to castle walls." msgstr "" "Katapulta otrzymuje dodatkowy strzaÅ‚, a każdy ma wiÄ™kszÄ… szansÄ™ na trafienie " "i uszkodzenie zamkowych murów." #, fuzzy msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot " "automatically destroys any wall, except a fortified wall in a Knight castle." msgstr "" "Katapulta otrzymuje dodatkowy strzaÅ‚, a każdy automatycznie niszczy wszelkie " "mury, za wyjÄ…tkiem fortyfikacji w zamku Rycerza." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 1st or " "2nd level spell that was cast by an enemy during combat." msgstr "" "Daje Twojemu bohaterowi %{count} procent szans na nauczenie siÄ™ dowolnego " "czaru 1-go i 2-go poziomu rzucanego przez przeciwnika." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 3rd " "level spell (or below) that was cast by an enemy during combat." msgstr "" "Daje Twojemu bohaterowi %{count} procent szans na nauczenie siÄ™ dowolnego " "czaru 3-go (lub poniżej) poziomu rzucanego przez przeciwnika." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 4th " "level spell (or below) that was cast by an enemy during combat." msgstr "" "Daje Twojemu bohaterowi %{count} procent szans na nauczenie siÄ™ dowolnego " "czaru 4-go poziomu (lub poniżej) rzucanego przez przeciwnika." msgid "" "%{skill} allows %{count} percent of the creatures killed in combat to be " "brought back from the dead as Skeletons." msgstr "" "Pozwala wskrzesić w postaci Szkieletów %{count} procent polegÅ‚ych w walce " "stworzeÅ„." #, fuzzy msgid "" "The hero produces %{count} gold pieces per day as tax revenue from estates." msgstr "" "Każdej tury twój bohater dostarcza królestwu %{count} sztuk zÅ‚ota jako " "dochód z nieruchomoÅ›ci." msgid "Blue" msgstr "Niebieski" msgid "Green" msgstr "Zielony" msgid "Red" msgstr "Czerwony" msgid "Yellow" msgstr "Żółty" msgid "Orange" msgstr "PomaraÅ„czowy" msgid "Purple" msgstr "Fioletowy" msgid "barrier|Aqua" msgstr "seledynowy" msgid "barrier|Blue" msgstr "niebieski" msgid "barrier|Brown" msgstr "brÄ…zowy" msgid "barrier|Gold" msgstr "zÅ‚oty" msgid "barrier|Green" msgstr "zielony" msgid "barrier|Orange" msgstr "pomaraÅ„czowy" msgid "barrier|Purple" msgstr "fioletowy" msgid "barrier|Red" msgstr "czerwony" msgid "tent|Aqua" msgstr "Seledynowy" msgid "tent|Blue" msgstr "Niebieski" msgid "tent|Brown" msgstr "BrÄ…zowy" msgid "tent|Gold" msgstr "ZÅ‚oty" msgid "tent|Green" msgstr "Zielony" msgid "tent|Orange" msgstr "PomaraÅ„czowy" msgid "tent|Purple" msgstr "Fioletowy" msgid "tent|Red" msgstr "Czerwony" msgid "Experience" msgstr "DoÅ›wiadczenie" msgid "" "Experience allows your heroes to go up levels, increasing their primary and " "secondary skills." msgstr "" "DoÅ›wiadczenie pozwala twoim bohaterom awansować na wyższe poziomy, i co za " "tym idzie, ulepszać ich umiejÄ™tnoÅ›ci." msgid "Hero/Stats" msgstr "Bohater/Statystyki" msgid "Skills" msgstr "UmiejÄ™tnoÅ›ci" msgid "Town/Castle" msgstr "Miasto/Zamek" msgid "Garrison" msgstr "Garnizon" msgid "Gold Per Day:" msgstr "ZÅ‚ota na dzieÅ„:" msgid "View Heroes." msgstr "Pokaż bohaterów." msgid "Towns/Castles" msgstr "Miasta/Zamki" msgid "View Towns and Castles." msgstr "Pokaż miasta i zamki." msgid "luck|Cursed" msgstr "Beznadziejne" msgid "luck|Awful" msgstr "Bardzo niskie" msgid "luck|Bad" msgstr "Niskie" msgid "luck|Normal" msgstr "Normalne" msgid "luck|Good" msgstr "Spore" msgid "luck|Great" msgstr "Wielkie" msgid "luck|Irish" msgstr "PrzysÅ‚owiowe" #, fuzzy msgid "" "Bad luck sometimes falls on the hero's units in combat, causing their " "attacks to only do half damage." msgstr "" "Niskie szczęście na polu bitwy oznacza, że twoje jednostki zadawać bÄ™dÄ… " "poÅ‚owÄ™ obrażeÅ„." #, fuzzy msgid "" "Neutral luck means the hero's units will never get lucky or unlucky attacks " "on the enemy." msgstr "" "Normalne szczęście oznacza, iż twoje jednostki nigdy nie bÄ™dÄ… otrzymywać " "premii na polu bitwy, ale też nie bÄ™dÄ… miaÅ‚y pecha." #, fuzzy msgid "" "Good luck sometimes lets the hero's units get lucky attacks (double " "strength) in combat." msgstr "" "Wysokie szczęście sprawia, że twoim oddziaÅ‚om czasami udaje siÄ™ zadać " "podwójne obrażenia." msgid "morale|Treason" msgstr "Tragiczne" msgid "morale|Awful" msgstr "Bardzo niskie" msgid "morale|Poor" msgstr "Niskie" msgid "morale|Normal" msgstr "ZwykÅ‚e" msgid "morale|Good" msgstr "Wysokie" msgid "morale|Great" msgstr "Bardzo wysokie" msgid "morale|Blood!" msgstr "Krwi!" #, fuzzy msgid "Bad morale may cause the hero's units to freeze in combat." msgstr "" "Niskie morale może sprawić, że twoje oddziaÅ‚y zastygnÄ… w bezruchu podczas " "walki." #, fuzzy msgid "" "Neutral morale means the hero's units will never be blessed with extra " "attacks or freeze in combat." msgstr "" "ZwykÅ‚e morale oznacza, że twoje wojska nigdy nie otrzymajÄ… dodatkowego ataku " "ani też nie zastygnÄ… w bezruchu podczas walki." #, fuzzy msgid "Good morale may give the hero's units extra attacks in combat." msgstr "" "Wysokie morale może sprawić, że twoje oddziaÅ‚y otrzymajÄ… dodatkowy atak " "podczas walki." msgid "Knight" msgstr "Rycerz" msgid "Barbarian" msgstr "BarbarzyÅ„ca" msgid "Sorceress" msgstr "Czarodziejka" msgid "Warlock" msgstr "Czarnoksiężnik" msgid "Wizard" msgstr "Czarodziej" msgid "Necromancer" msgstr "Nekromanta" msgid "Multi" msgstr "Różne" msgid "doubleLined|Knight" msgstr "Rycerz" msgid "doubleLined|Barbarian" msgstr "" "Barba-\n" "rzyÅ„ca" msgid "doubleLined|Sorceress" msgstr "" "Czaro-\n" "dziejka" msgid "doubleLined|Warlock" msgstr "" "Czarno-\n" "księżnik" msgid "doubleLined|Wizard" msgstr "Czarodziej" msgid "" "doubleLined|Necro-\n" "mancer" msgstr "" "Nekro-\n" "manta" msgid "doubleLinedRace|Multi" msgstr "Różne" msgid "doubleLinedRace|Random" msgstr "Losowo" msgid "speed|Standing" msgstr "Stoi" msgid "speed|Crawling" msgstr "PeÅ‚za" msgid "speed|Very Slow" msgstr "Bardzo niska" msgid "speed|Slow" msgstr "Niska" msgid "speed|Average" msgstr "Åšrednia" msgid "speed|Fast" msgstr "Duża" msgid "speed|Very Fast" msgstr "Bardzo duża" msgid "speed|Ultra Fast" msgstr "Super duża" msgid "speed|Blazing" msgstr "Ogromna" msgid "speed|Instant" msgstr "BÅ‚yskawica" msgid "week|Squirrel" msgstr "Wiewiórka" msgid "week|Rabbit" msgstr "Królik" msgid "week|Gopher" msgstr "Åšwistak" msgid "week|Badger" msgstr "Borsuk" msgid "week|Rat" msgstr "Szczur" msgid "week|Eagle" msgstr "OrzeÅ‚" msgid "week|Weasel" msgstr "Åasica" msgid "week|Raven" msgstr "Kruk" msgid "week|Mongoose" msgstr "Mangusta" msgid "week|Dog" msgstr "Pies" msgid "week|Aardvark" msgstr "Mrówkojad" msgid "week|Lizard" msgstr "Jaszczurka" msgid "week|Tortoise" msgstr "Żółw" msgid "week|Hedgehog" msgstr "Jeż" msgid "week|Condor" msgstr "Kondor" msgid "week|Ant" msgstr "Mrówka" msgid "week|Grasshopper" msgstr "Åšwierszcz" msgid "week|Dragonfly" msgstr "Ważka" msgid "week|Spider" msgstr "PajÄ…k" msgid "week|Butterfly" msgstr "Motyl" msgid "week|Bumblebee" msgstr "Trzmiel" msgid "week|Locust" msgstr "SzaraÅ„cza" msgid "week|Earthworm" msgstr "Dżdżownica" msgid "week|Hornet" msgstr "SzerszeÅ„" msgid "week|Beetle" msgstr "Å»uk" msgid "week|PLAGUE" msgstr "PLAGA" msgid "week|Unnamed" msgstr "Nieznane" msgid "Desert" msgstr "Pustynia" msgid "Snow" msgstr "Åšnieg" msgid "Wasteland" msgstr "WyschniÄ™ty" msgid "Beach" msgstr "Plaża" msgid "Lava" msgstr "Lawa" msgid "Dirt" msgstr "PyÅ‚" msgid "Grass" msgstr "Trawa" msgid "Ocean" msgstr "Ocean" msgid "maps|Small" msgstr "MaÅ‚e" msgid "maps|Medium" msgstr "Åšrednie" msgid "maps|Large" msgstr "Duże" msgid "maps|Extra Large" msgstr "Bardzo duże" msgid "maps|Custom Size" msgstr "Inny rozmiar" msgid "Ore Mine" msgstr "Kopalnia rudy" msgid "Sulfur Mine" msgstr "Kopalnia siarki" msgid "Crystal Mine" msgstr "Kopalnia kryształów" msgid "Gems Mine" msgstr "Kopalnia klejnotów" msgid "Gold Mine" msgstr "Kopalnia zÅ‚ota" msgid "Mine" msgstr "Kopalnia" msgid "Burma shave." msgstr "WuTeKa." msgid "Next sign 50 miles." msgstr "NastÄ™pny znak za 50 mil." msgid "See Rock City." msgstr "Zobacz miasto Roków." msgid "This space for rent." msgstr "Miejsce do wynajÄ™cia." msgid "No object" msgstr "Brak obiektu" msgid "Alchemist Lab" msgstr "Laboratorium" msgid "Sign" msgstr "Drogowskaz" msgid "Buoy" msgstr "Boja" msgid "Skeleton" msgstr "Szkielet" msgid "Daemon Cave" msgstr "Jaskinia demona" msgid "Treasure Chest" msgstr "Skrzynia ze skarbem" msgid "Faerie Ring" msgstr "ZaklÄ™ty krÄ…g" msgid "Campfire" msgstr "Obozowisko" msgid "Fountain" msgstr "Fontanna" msgid "Gazebo" msgstr "Altana" msgid "Genie Lamp" msgstr "Lampa dżinna" msgid "Archer's House" msgstr "Dom Å‚uczników" msgid "Goblin Hut" msgstr "Chata Goblinów" msgid "Dwarf Cottage" msgstr "Domek krasnoludów" msgid "Peasant Hut" msgstr "ChÅ‚opska chata" msgid "Stables" msgstr "Stajnie" msgid "Alchemist's Tower" msgstr "Wieża alchemika" msgid "Event" msgstr "Zdarzenie" msgid "Dragon City" msgstr "Smocze Miasto" msgid "Lighthouse" msgid_plural "Lighthouses" msgstr[0] "Latarnia morska" msgstr[1] "Latarnie morskie" msgstr[2] "Latarni morskich" msgid "Water Wheel" msgid_plural "Water Wheels" msgstr[0] "MÅ‚yn wodny" msgstr[1] "MÅ‚yny wodne" msgstr[2] "MÅ‚ynów wodnych" msgid "Monster" msgstr "Potwór" msgid "Obelisk" msgstr "Obelisk" msgid "Oasis" msgstr "Oaza" msgid "Resource" msgstr "Surowiec" msgid "Sawmill" msgstr "Tartak" msgid "Oracle" msgstr "Wyrocznia" msgid "Shrine of the First Circle" msgstr "Kaplica pierwszego krÄ™g" msgid "Shipwreck" msgstr "Wrak" msgid "Sea Chest" msgstr "Skrzynia" msgid "Desert Tent" msgstr "Namiot na pustyni" msgid "Stone Liths" msgstr "GÅ‚azy" msgid "Wagon Camp" msgstr "Tabor" msgid "Hut of the Magi" msgstr "Chata maga" msgid "Whirlpool" msgstr "Wir" msgid "Windmill" msgid_plural "Windmills" msgstr[0] "Wiatrak" msgstr[1] "Wiatraki" msgstr[2] "Wiatraków" msgid "Artifact" msgstr "Artefakt" msgid "Mermaid" msgstr "Syrena" msgid "Boat" msgstr "Åódź" msgid "Random Ultimate Artifact" msgstr "Losowy najpotężniejszy artefakt" msgid "Random Artifact" msgstr "Losowy artefakt" msgid "Random Resource" msgstr "Losowe zasoby" msgid "Random Monster" msgstr "Losowy potwór" msgid "Random Town" msgstr "Losowe miasto" msgid "Random Castle" msgstr "Losowy zamek" msgid "Eye of the Magi" msgstr "Oko maga" msgid "Random Monster - weak" msgstr "Losowy potwór - sÅ‚aby" msgid "Random Monster - medium" msgstr "Losowy potwór - Å›redni" msgid "Random Monster - strong" msgstr "Losowy potwór - silny" msgid "Random Monster - very strong" msgstr "Losowy potwór - bardzo silny" #, fuzzy msgid "Hero" msgstr "Bohaterowie" msgid "Nothing Special" msgstr "Nic specjalnego" msgid "Mossy Rock" msgstr "MchÅ‚a skaÅ‚a" msgid "Watch Tower" msgstr "Wieża strażnicza" msgid "Tree House" msgstr "Dom na drzewie" msgid "Tree City" msgstr "Nadrzewne miasto" msgid "Ruins" msgstr "Ruiny" msgid "Fort" msgstr "Fort" msgid "Abandoned Mine" msgstr "Opuszczona kopalnia" msgid "Sirens" msgstr "Syreny" msgid "Standing Stones" msgstr "KrÄ…g kamieni" msgid "Idol" msgstr "Bożek" msgid "Tree of Knowledge" msgstr "Drzewo Wiedzy" msgid "Witch Doctor's Hut" msgstr "Chatka znachora" msgid "Temple" msgstr "ÅšwiÄ…tynia" msgid "Hill Fort" msgstr "Fort na wzgórzu" msgid "Halfling Hole" msgstr "Norka nizioÅ‚ków" msgid "Mercenary Camp" msgstr "Obóz najemników" msgid "Shrine of the Second Circle" msgstr "Kaplica drugiego krÄ™gu" msgid "Shrine of the Third Circle" msgstr "Kaplica trzeciego krÄ™gu" msgid "City of the Dead" msgstr "Miasto UmarÅ‚ych" msgid "Sphinx" msgstr "Sfinks" msgid "Wagon" msgstr "Wóz" msgid "Tar Pit" msgstr "SmoÅ‚a" msgid "Artesian Spring" msgstr "Studnia artezyjska" msgid "Troll Bridge" msgstr "Most trolli" msgid "Watering Hole" msgstr "ŹródÅ‚o" msgid "Witch's Hut" msgstr "Chatka wiedźmy" msgid "Xanadu" msgstr "Xanadu" msgid "Lean-To" msgstr "Szopa" msgid "Magellan's Maps" msgstr "Mapy Magellana" msgid "Flotsam" msgstr "SzczÄ…tki statku" msgid "Derelict Ship" msgstr "Opuszczony statek" msgid "Shipwreck Survivor" msgstr "OcalaÅ‚y rozbitek" msgid "Bottle" msgstr "Butelka" msgid "Magic Well" msgstr "Magiczna studnia" msgid "Magic Garden" msgid_plural "Magic Gardens" msgstr[0] "Magiczny ogród" msgstr[1] "Magiczne ogrody" msgstr[2] "Magicznych ogrodów" msgid "Observation Tower" msgstr "Wieża obserwacyjna" msgid "Freeman's Foundry" msgstr "Odlewnia" msgid "Reefs" msgstr "Rafy" msgid "Volcano" msgstr "Wulkan" msgid "Flowers" msgstr "Kwiaty" msgid "Rock" msgstr "KamieÅ„" msgid "Water Lake" msgstr "Jezioro" msgid "Mandrake" msgstr "Mandragora" msgid "Dead Tree" msgstr "Martwe drzewo" msgid "Stump" msgstr "Pieniek" msgid "Crater" msgstr "Krater" msgid "Cactus" msgstr "Kaktus" msgid "Mound" msgstr "Kopiec" msgid "Dune" msgstr "Wydma" msgid "Lava Pool" msgstr "Jezioro lawy" msgid "Shrub" msgstr "Krzak" msgid "Barrow Mounds" msgstr "Kurhany" msgid "Random Artifact - Treasure" msgstr "Losowy artefakt - skarb" msgid "Random Artifact - Minor" msgstr "Losowy artefakt - mniejszy" msgid "Random Artifact - Major" msgstr "Losowy artefakt - wiÄ™kszy" msgid "Barrier" msgstr "Bariera" msgid "Traveller's Tent" msgstr "Namiot podróżnika" msgid "Jail" msgstr "WiÄ™zienie" msgid "Fire Summoning Altar" msgstr "OÅ‚tarz Ognia" msgid "Air Summoning Altar" msgstr "OÅ‚tarz Powietrza" msgid "Earth Summoning Altar" msgstr "OÅ‚tarz Ziemi" msgid "Water Summoning Altar" msgstr "OÅ‚tarz Wody" msgid "Unknown Monster" msgstr "Nieznany potwór" msgid "Unknown Monsters" msgstr "Nieznane potwory" msgid "Peasant" msgstr "ChÅ‚op" msgid "Peasants" msgstr "ChÅ‚opi" msgid "Archer" msgstr "Åucznik" msgid "Archers" msgstr "Åucznicy" msgid "Ranger" msgstr "Åowca" msgid "Rangers" msgstr "Åowcy" msgid "Pikeman" msgstr "Pikinier" msgid "Pikemen" msgstr "Pikinierzy" msgid "Veteran Pikeman" msgstr "Weteran" msgid "Veteran Pikemen" msgstr "Weterani" msgid "Swordsman" msgstr "Zbrojny" msgid "Swordsmen" msgstr "Zbrojni" msgid "Master Swordsman" msgstr "Mistrz Miecza" msgid "Master Swordsmen" msgstr "Mistrzowie Miecza" msgid "Cavalries" msgstr "Konni" msgid "Cavalry" msgstr "Konny" msgid "Champion" msgstr "Czempion" msgid "Champions" msgstr "Czempioni" msgid "Paladin" msgstr "Paladyn" msgid "Paladins" msgstr "Paladyni" msgid "Crusader" msgstr "Krzyżowiec" msgid "Crusaders" msgstr "Krzyżowcy" msgid "Goblin" msgstr "Goblin" msgid "Goblins" msgstr "Gobliny" msgid "Orc" msgstr "Ork" msgid "Orcs" msgstr "Orkowie" msgid "Orc Chief" msgstr "Ork Wojownik" msgid "Orc Chiefs" msgstr "Orkowie Wojownicy" msgid "Wolf" msgstr "Wilk" msgid "Wolves" msgstr "Wilki" msgid "Ogre" msgstr "Ogr" msgid "Ogres" msgstr "Ogry" msgid "Ogre Lord" msgstr "Wielki Ogr" msgid "Ogre Lords" msgstr "Wielkie Ogry" msgid "Troll" msgstr "Troll" msgid "Trolls" msgstr "Trolle" msgid "War Troll" msgstr "Troll Bojowy" msgid "War Trolls" msgstr "Trolle Bojowe" msgid "Cyclopes" msgstr "Cyklopy" msgid "Cyclops" msgstr "Cyklop" msgid "Sprite" msgstr "RusaÅ‚ka" msgid "Sprites" msgstr "RusaÅ‚ki" msgid "Dwarf" msgstr "Krasnolud" msgid "Dwarves" msgstr "Krasnoludy" msgid "Battle Dwarf" msgstr "Krasnolud Wojownik" msgid "Battle Dwarves" msgstr "Krasnoludzcy Wojownicy" msgid "Elf" msgstr "Elf" msgid "Elves" msgstr "Elfy" msgid "Grand Elf" msgstr "Wysoki Elf" msgid "Grand Elves" msgstr "Wysokie Elfy" msgid "Druid" msgstr "Druid" msgid "Druids" msgstr "Druidzi" msgid "Greater Druid" msgstr "Wielki Druid" msgid "Greater Druids" msgstr "Wielcy Druidzi" msgid "Unicorn" msgstr "Jednorożec" msgid "Unicorns" msgstr "Jednorożce" msgid "Phoenix" msgstr "Feniks" msgid "Phoenixes" msgstr "Feniksy" msgid "Centaur" msgstr "Centaur" msgid "Centaurs" msgstr "Centaury" msgid "Gargoyle" msgstr "Gargulec" msgid "Gargoyles" msgstr "Gargulce" msgid "Griffin" msgstr "Gryf" msgid "Griffins" msgstr "Gryfy" msgid "Minotaur" msgstr "Minotaur" msgid "Minotaurs" msgstr "Minotaury" msgid "Minotaur King" msgstr "Królewski Minotaur" msgid "Minotaur Kings" msgstr "Królewskie Minotaury" msgid "Hydra" msgstr "Hydra" msgid "Hydras" msgstr "Hydry" msgid "Green Dragon" msgstr "Zielony Smok" msgid "Green Dragons" msgstr "Zielone Smoki" msgid "Red Dragon" msgstr "Czerwony Smok" msgid "Red Dragons" msgstr "Czerwone Smoki" msgid "Black Dragon" msgstr "Czarny Smok" msgid "Black Dragons" msgstr "Czarne Smoki" msgid "Halfling" msgstr "NizioÅ‚ek" msgid "Halflings" msgstr "NizioÅ‚ki" msgid "Boar" msgstr "Dzik" msgid "Boars" msgstr "Dziki" msgid "Iron Golem" msgstr "Å»elazny Golem" msgid "Iron Golems" msgstr "Å»elazne Golemy" msgid "Steel Golem" msgstr "Stalowy Golem" msgid "Steel Golems" msgstr "Stalowe Golemy" msgid "Roc" msgstr "Rok" msgid "Rocs" msgstr "Roki" msgid "Mage" msgstr "Mag" msgid "Magi" msgstr "Magowie" msgid "Archmage" msgstr "Arcymag" msgid "Archmagi" msgstr "Arcymagowie" msgid "Giant" msgstr "Olbrzym" msgid "Giants" msgstr "Olbrzymy" msgid "Titan" msgstr "Tytan" msgid "Titans" msgstr "Tytani" msgid "Skeletons" msgstr "Szkielety" msgid "Zombie" msgstr "Zombie" msgid "Zombies" msgstr "Zombie" msgid "Mutant Zombie" msgstr "Zmutowany Zombie" msgid "Mutant Zombies" msgstr "Zmutowane Zombie" msgid "Mummies" msgstr "Mumie" msgid "Mummy" msgstr "Mumia" msgid "Royal Mummies" msgstr "Królewskie Mumie" msgid "Royal Mummy" msgstr "Królewska Mumia" msgid "Vampire" msgstr "Wampir" msgid "Vampires" msgstr "Wampiry" msgid "Vampire Lord" msgstr "Wampirzy Lord" msgid "Vampire Lords" msgstr "Wampirzy Lordowie" msgid "Lich" msgstr "Lisz" msgid "Liches" msgstr "Lisze" msgid "Power Lich" msgstr "Potężny Lisz" msgid "Power Liches" msgstr "Potężne Lisze" msgid "Bone Dragon" msgstr "KoÅ›ciany Smok" msgid "Bone Dragons" msgstr "KoÅ›ciane Smoki" msgid "Rogue" msgstr "Rozbójnik" msgid "Rogues" msgstr "Rozbójnicy" msgid "Nomad" msgstr "Nomada" msgid "Nomads" msgstr "Nomadowie" msgid "Ghost" msgstr "Duch" msgid "Ghosts" msgstr "Duchy" msgid "Genie" msgstr "Dżinn" msgid "Genies" msgstr "Dżinny" msgid "Medusa" msgstr "Meduza" msgid "Medusas" msgstr "Meduzy" msgid "Earth Elemental" msgstr "Å»ywioÅ‚ak Ziemi" msgid "Earth Elementals" msgstr "Å»ywioÅ‚aki Ziemi" msgid "Air Elemental" msgstr "Å»ywioÅ‚ak Powietrza" msgid "Air Elementals" msgstr "Å»ywioÅ‚aki Powietrza" msgid "Fire Elemental" msgstr "Å»ywioÅ‚ak Ognia" msgid "Fire Elementals" msgstr "Å»ywioÅ‚aki Ognia" msgid "Water Elemental" msgstr "Å»ywioÅ‚ak Wody" msgid "Water Elementals" msgstr "Å»ywioÅ‚aki Wody" msgid "Random Monsters" msgstr "Losowy potwór" msgid "Random Monster 1" msgstr "Losowy potwór - sÅ‚aby" msgid "Random Monsters 1" msgstr "Losowe potwory - sÅ‚abe" msgid "Random Monster 2" msgstr "Losowy potwór - Å›redni" msgid "Random Monsters 2" msgstr "Losowe potwory - Å›rednie" msgid "Random Monster 3" msgstr "Losowy potwór - silny" msgid "Random Monsters 3" msgstr "Losowe potwory - silne" msgid "Random Monster 4" msgstr "Losowy potwór - bardzo silny" msgid "Random Monsters 4" msgstr "Losowe potwory - bardzo silne" msgid "Double shot" msgstr "Podwójny strzaÅ‚" msgid "2-hex monster" msgstr "Rozmiar 2-hex" msgid "Double strike" msgstr "Podwójny atak" msgid "Double damage to Undead" msgstr "Podwójne obrażenia nieumarÅ‚ym" msgid "% magic resistance" msgstr "% odpornoÅ›ci magicznej" msgid "Immune to Mind spells" msgstr "Niewrażliwy na czary umysÅ‚u" msgid "Immune to Elemental spells" msgstr "Niewrażliwy na czary żywiołów" msgid "Immune to Fire spells" msgstr "Niewrażliwy na czary ognia" msgid "Immune to Cold spells" msgstr "Niewrażliwy na czary zimna" msgid "Immune to " msgstr "Niewrażliwy na " msgid "% immunity to %{spell} spell" msgstr "% odpornoÅ›ci na czar %{spell}" msgid "% damage from Elemental spells" msgstr "% odpornoÅ›ci na czary żywiołów" msgid "% chance to Dispel beneficial spells" msgstr "% szans na Rozproszenie pozytywnych czarów" msgid "% chance to Paralyze" msgstr "% szans na Paraliż" msgid "% chance to Petrify" msgstr "% szans na zamianÄ™ w kamieÅ„" msgid "% chance to Blind" msgstr "% szans na OÅ›lepienie" msgid "% chance to Curse" msgstr "% szans na KlÄ…twÄ™" msgid "% chance to cast %{spell} spell" msgstr "% szans na rzucenie zaklÄ™cia %{spell}" msgid "HP regeneration" msgstr "Regeneruje siÄ™" msgid "Two hexes attack" msgstr "Atakuje dwa pola" msgid "Flyer" msgstr "LatajÄ…cy" msgid "Always retaliates" msgstr "Zawsze kontratakuje" msgid "Attacks all adjacent enemies" msgstr "Atakuje wszystkich przylegÅ‚ych przeciwników" msgid "No melee penalty" msgstr "Brak kar w walce wrÄ™cz" msgid "Dragon" msgstr "Smok" msgid "Undead" msgstr "NieumarÅ‚y" msgid "No enemy retaliation" msgstr "Powstrzymuje kontratak" msgid "HP drain" msgstr "Wysysa życie" msgid "Cloud attack" msgstr "Atak trujÄ…cÄ… chmurÄ…" msgid "Decreases enemy's morale by " msgstr "Obniża morale przeciwnika o " msgid "% chance to halve enemy" msgstr "% szans na unicestwienie poÅ‚owy atakowanego oddziaÅ‚u" msgid "Soul Eater" msgstr "Pożeracz Dusz" msgid "Elemental" msgstr "Å»ywioÅ‚ak" msgid "No Morale" msgstr "Neutralne morale" msgid "200% damage from Fire spells" msgstr "200% obrażeÅ„ od czarów ognia" msgid "200% damage from Cold spells" msgstr "200% obrażeÅ„ od czarów zimna" msgid "% damage from %{spell} spell" msgstr "% obrażeÅ„ od czaru %{spell}" msgid "% immunity to " msgstr "% odpornoÅ›ci na " msgid "Lightning" msgstr "bÅ‚yskawic" msgid "% damage from " msgstr "% obrażeÅ„ od " msgid "The three Anduran artifacts magically combine into one." msgstr "Trzy AnduriaÅ„skie artefakty magicznie połączone w jeden." msgid "View Spells" msgstr "Pokaż zaklÄ™cia" msgid "View %{name} Info" msgstr "Informacje o: %{name}" msgid "Move %{name}" msgstr "PrzenieÅ› %{name}" msgid "Cannot move the Spellbook" msgstr "Nie można przenieść KsiÄ™gi Zaklęć" msgid "This item can't be traded." msgstr "Nie można wymienić tego przedmiotu." msgid "Invalid Artifact" msgstr "NieprawidÅ‚owy artefakt" #, fuzzy msgid "The %{name} increases the hero's knowledge by %{count}." msgstr "%{name} zwiÄ™ksza twojÄ… wiedzÄ™ o %{count}." msgid "Ultimate Book of Knowledge" msgstr "Najzn. KsiÄ™ga Wiedzy" #, fuzzy msgid "The %{name} increases the hero's attack skill by %{count}." msgstr "%{name} zwiÄ™ksza twój atak o %{count}." msgid "Ultimate Sword of Dominion" msgstr "Najzn. Miecz Dominium" #, fuzzy msgid "The %{name} increases the hero's defense skill by %{count}." msgstr "%{name} zwiÄ™ksza twojÄ… obronÄ™ o %{count}." msgid "Ultimate Cloak of Protection" msgstr "Najzn. PÅ‚aszcz Ochrony" #, fuzzy msgid "The %{name} increases the hero's spell power by %{count}." msgstr "%{name} zwiÄ™ksza twojÄ… moc o %{count}." msgid "Ultimate Wand of Magic" msgstr "Najzn. Różdżka Magii" #, fuzzy msgid "" "The %{name} increases the hero's attack and defense skills by %{count} each." msgstr "%{name} zwiÄ™ksza twój atak i obronÄ™ o %{count} pkt." msgid "Ultimate Shield" msgstr "Najzn. Tarcza" #, fuzzy msgid "" "The %{name} increases the hero's spell power and knowledge by %{count} each." msgstr "%{name} zwiÄ™ksza twojÄ… moc i wiedzÄ™ o %{count} pkt." msgid "Ultimate Staff" msgstr "Najzn. Kostur" #, fuzzy msgid "" "The %{name} increases each of the hero's basic skills by %{count} points." msgstr "%{name} zwiÄ™ksza każdÄ… z twoich umiejÄ™tnoÅ›ci o %{count} punkty." msgid "Ultimate Crown" msgstr "Najzn. Korona" msgid "Golden Goose" msgstr "ZÅ‚ota Gęś" msgid "The %{name} brings in an income of %{count} gold per day." msgstr "%{name} dodaje przychód w wysokoÅ›ci %{count} sztuk zÅ‚ota na turÄ™." msgid "Arcane Necklace of Magic" msgstr "Tajemny Naszyjnik Magii" msgid "" "After rescuing a Sorceress from a cursed tomb, she rewards your heroism with " "an exquisite jeweled necklace." msgstr "" "Po uratowaniu Czarodziejki z przeklÄ™tego grobowca, ta nagradza twój heroizm " "bogato zdobionym naszyjnikiem." msgid "Caster's Bracelet of Magic" msgstr "Magiczna Bransoleta Czarodzieja" msgid "" "While searching through the rubble of a caved-in mine, you free a group of " "trapped Dwarves. Grateful, the leader gives you a golden bracelet." msgstr "" "Podczas przeszukiwania gruzów po zapadniÄ™tej kopalni uwalniasz grupÄ™ " "uwiÄ™zionych Krasnoludów. W wyrazie wdziÄ™cznoÅ›ci ich przywódca ofiarowuje ci " "zÅ‚otÄ… bransoletÄ™." msgid "Mage's Ring of Power" msgstr "PierÅ›cieÅ„ Mocy Maga" msgid "" "A cry of pain leads you to a Centaur, caught in a trap. Upon setting the " "creature free, he hands you a small pouch. Emptying the contents, you find a " "dazzling jeweled ring." msgstr "" "Okrzyki bólu prowadzÄ… ciÄ™ do Centaura, schwytanego w puÅ‚apkÄ™. Po uwolnieniu " "z niej, przekazuje ci niewielkÄ… sakiewkÄ™. PrzeglÄ…dajÄ…c jej zawartość " "znajdujesz skrzÄ…cy siÄ™ od klejnotów pierÅ›cieÅ„." msgid "Witch's Broach of Magic" msgstr "Wiedźmia Brosza Magii" msgid "" "Alongside the remains of a burnt witch lies a beautiful broach, intricately " "designed. Approaching the corpse with caution, you add the broach to your " "inventory." msgstr "" "Tuż obok szczÄ…tek spalonej wiedźmy leży piÄ™kna, misternie wykonana brosza. " "PodchodzÄ…c do zwÅ‚ok z najwyższÄ… ostrożnoÅ›ciÄ…, dołączasz broszÄ™ do swego " "ekwipunku." msgid "Medal of Valor" msgstr "Medal MÄ™stwa" #, fuzzy msgid "The %{name} increases the morale of the hero's army by %{count}." msgstr "%{name} zwiÄ™ksza twoje morale o %{count}." msgid "" "Freeing a virtuous maiden from the clutches of an evil overlord, you are " "granted a Medal of Valor by the King's herald." msgstr "" "Po oswobodzeniu cnotliwej niewiasty z rÄ…k zÅ‚ego wÅ‚adyki zostajesz odznaczony " "przez królewskiego wysÅ‚annika Medalem MÄ™stwa." msgid "Medal of Courage" msgstr "Medal Odwagi" msgid "" "After saving a young boy from a vicious pack of Wolves, you return him to " "his father's manor. The grateful nobleman awards you with a Medal of Courage." msgstr "" "Po uratowaniu mÅ‚odego chÅ‚opca przed watahÄ… wÅ›ciekÅ‚ych wilków, odprowadzasz " "go do posiadÅ‚oÅ›ci jego ojca. WdziÄ™czny szlachcic nagradza ciÄ™ Medalem Odwagi." msgid "Medal of Honor" msgstr "Medal Honoru" msgid "" "After freeing a princess of a neighboring kingdom from the evil clutches of " "despicable slavers, she awards you with a Medal of Honor." msgstr "" "Ratujesz księżniczkÄ™ z sÄ…siedniego królestwa z rÄ…k niegodziwych Å‚owców " "niewolników, w zamian wynagradza ciÄ™ ona Medalem Honoru." msgid "Medal of Distinction" msgstr "Medal Uznania" msgid "" "Ridding the countryside of the hideous Minotaur who made a sport of eating " "noblemen's Knights, you are honored with the Medal of Distinction." msgstr "" "Rozprawiasz siÄ™ z nÄ™kajÄ…cym okolicÄ™ zÅ‚owieszczym Minotaurem, który dla " "zabawy pożeraÅ‚ szlachetnych Rycerzy. Uhonorowano ciÄ™ w zamian Medalem " "Uznania." msgid "Fizbin of Misfortune" msgstr "Fizbin Nieszczęścia" #, fuzzy msgid "" "The %{name} greatly decreases the morale of the hero's army by %{count}." msgstr "%{name} znaczÄ…co zmniejsza twoje morale o %{count}." msgid "" "You stumble upon a medal lying alongside the empty road. Adding the medal to " "your inventory, you become aware that you have acquired the undesirable " "Fizbin of Misfortune, greatly decreasing your army's morale." msgstr "" "Potykasz siÄ™ o odznaczenie, leżące samotnie ma pustej drodze. DołączajÄ…c " "order do swego ekwipunku zdajesz sobie sprawÄ™, że wszedÅ‚eÅ› w posiadanie " "niepożądanego Fizbinu Nieszczęścia, który znacznie obniża morale twoich " "oddziałów." msgid "Thunder Mace of Dominion" msgstr "GrzmiÄ…cy KiÅ›cieÅ„ Dominium" msgid "" "During a sudden storm, a bolt of lightning strikes a tree, splitting it. " "Inside the tree you find a mysterious mace." msgstr "" "Podczas gwaÅ‚townej burzy dostrzegasz jak bÅ‚yskawica uderza w drzewo, " "rozszczepiajÄ…c je na części. WewnÄ…trz drzewa znajdujesz tajemniczy kiÅ›cieÅ„." msgid "Armored Gauntlets of Protection" msgstr "Pancerne RÄ™kawice Ochrony" #, fuzzy msgid "The %{name} increase the hero's defense skill by %{count}." msgstr "%{name} zwiÄ™kszajÄ… twojÄ… obronÄ™ o %{count}." #, fuzzy msgid "" "You encounter the infamous Black Knight! After a grueling duel ending in a " "draw, the Knight, out of respect, offers you a pair of armored gauntlets." msgstr "" "Na swej drodze napotykasz niesÅ‚awnego Czarnego Rycerza! Po wyczerpujÄ…cej " "walce, zakoÅ„czonej remisem, Rycerz, chcÄ…c okazać ci swój szacunek, " "ofiarowuje ci parÄ™ pancernych rÄ™kawic." msgid "Defender Helm of Protection" msgstr "Wzmocniony HeÅ‚m Ochrony" msgid "" "A glint of golden light catches your eye. Upon further investigation, you " "find a golden helm hidden under a bush." msgstr "" "Dostrzegasz migotliwy zÅ‚oty bÅ‚ysk. Po niewielkich poszukiwaniach znajdujesz " "zÅ‚oty heÅ‚m, ukryty wÅ›ród krzewów." msgid "Giant Flail of Dominion" msgstr "Olbrzymi Cep Bojowy Dominium" msgid "" "A clumsy Giant has killed himself with his own flail. Knowing your superior " "skill with this weapon, you confidently remove the spectacular flail from " "the fallen Giant." msgstr "" "Niezdarny Olbrzym powaliÅ‚ sam siebie przy pomocy swego wÅ‚asnego cepa " "bojowego. MajÄ…c ufność w swej biegÅ‚oÅ›ci w korzystaniu z takiej broni, pewnie " "zabierasz efektowny oręż powalonemu Olbrzymowi." msgid "Ballista of Quickness" msgstr "Balista SzybkoÅ›ci" #, fuzzy msgid "The %{name} gives the hero's catapult one extra shot per combat round." msgstr "" "%{name} pozwala twojej katapulcie strzelić jeden dodatkowy strzaÅ‚ na rundÄ™." msgid "" "Walking through the ruins of an ancient walled city, you find the instrument " "of the city's destruction, an elaborately crafted ballista." msgstr "" "PrzechadzajÄ…c siÄ™ wÅ›ród ruin starożytnego miasta, niegdyÅ› otoczonego murem, " "znajdujesz narzÄ™dzie, które sprowadziÅ‚o na miasto zagÅ‚adÄ™ - misternie " "wykonanÄ… balistÄ™." msgid "Stealth Shield of Protection" msgstr "MaskujÄ…ca Tarcza Ochrony" msgid "" "A stone statue of a warrior holds a silver shield. As you remove the shield, " "the statue crumbles into dust." msgstr "" "Kamienny posÄ…g wojownika przytrzymuje srebrnÄ… tarczÄ™. Gdy zabierasz tarczÄ™, " "posÄ…g obraca siÄ™ w pyÅ‚." msgid "Dragon Sword of Dominion" msgstr "Smoczy Miecz Dominium" msgid "" "As you are walking along a narrow path, a nearby bush suddenly bursts into " "flames. Before your eyes the flames become the image of a beautiful woman. " "She holds out a magnificent sword to you." msgstr "" "KroczÄ…c wÄ…skÄ… Å›cieżynÄ…, dostrzegasz jak pobliski krzew staje nagle w ogniu. " "Na twych oczach pÅ‚omienie przybierajÄ… postać piÄ™knej kobiety. Owa niewiasta " "wrÄ™cza ci wspaniaÅ‚y miecz." msgid "Power Axe of Dominion" msgstr "Potężny Topór Dominium" msgid "" "You see a silver axe embedded deeply in the ground. After several " "unsuccessful attempts by your army to remove the axe, you tightly grip the " "handle of the axe and effortlessly pull it free." msgstr "" "Dostrzegasz srebrny topór wbity głęboko w ziemiÄ™. Po kliku nieskutecznych " "próbach wyciÄ…gniÄ™cia tego oręża przez twe oddziaÅ‚y, samemu Å›ciskasz stylisko " "topora i bez wysiÅ‚ku wyciÄ…gasz go." msgid "Divine Breastplate of Protection" msgstr "Boski NapierÅ›nik Ochrony" msgid "" "A gang of Rogues is sifting through the possessions of dead warriors. " "Scaring off the scavengers, you note the Rogues had overlooked a beautiful " "breastplate." msgstr "" "Banda Rozbójników przeczesuje dobytek polegÅ‚ego wojownika. OdstraszajÄ…c " "szabrowników zauważasz, że Rozbójnicy przegapili przepiÄ™kny napierÅ›nik." msgid "Minor Scroll of Knowledge" msgstr "Pomniejszy Zwój Wiedzy" msgid "" "Before you appears a levitating glass case with a scroll, perched upon a bed " "of crimson velvet. At your touch, the lid opens and the scroll floats into " "your awaiting hands." msgstr "" "Tuż przed tobÄ… materializuje siÄ™ szklana skrzynka, a w niej zwój, leżący na " "karmazynowej wyÅ›ciółce z aksamitu. DotknÄ…wszy wieka, te unosi siÄ™ a zwój " "szybuje wprost do twych oczekujÄ…cych dÅ‚oni." msgid "Major Scroll of Knowledge" msgstr "WiÄ™kszy Zwój Wiedzy" msgid "" "Visiting a local wiseman, you explain the intent of your journey. He reaches " "into a sack and withdraws a yellowed scroll and hands it to you." msgstr "" "OdwiedzajÄ…c miejscowego mÄ™drca, opowiadasz mu o celu swej podróży. MÄ™drzec " "siÄ™ga do sakwy, wyciÄ…ga z niej pożółkÅ‚y zwój i podaje ci go." msgid "Superior Scroll of Knowledge" msgstr "WspaniaÅ‚y Zwój Wiedzy" msgid "" "You come across the remains of an ancient Druid. Bones, yellowed with age, " "peer from the ragged folds of her robe. Searching the robe, you discover a " "scroll hidden in the folds." msgstr "" "Napotykasz szczÄ…tki starożytnej Druidki. KoÅ›ci, pożółkÅ‚e ze staroÅ›ci, " "wystajÄ… z porwanych faÅ‚d jej szaty. PrzeszukujÄ…c pÅ‚aszcz odkrywasz ukryty za " "połą zwój." msgid "Foremost Scroll of Knowledge" msgstr "DoskonaÅ‚y Zwój Wiedzy" msgid "" "Mangled bones, yellowed with age, peer from the ragged folds of a dead " "Druid's robe. Searching the robe, you discover a scroll hidden within." msgstr "" "Zmasakrowany szkielet, pożółkÅ‚y ze staroÅ›ci, wyziera zza porwanych szat " "martwego Druida. PrzeszukujÄ…c tÄ… szatÄ™ odnajdujesz ukryty weÅ„ zwój." msgid "Endless Sack of Gold" msgstr "Bezdenny Worek ZÅ‚ota" #, fuzzy msgid "The %{name} provides the hero with %{count} gold per day." msgstr "%{name} zapewnia ci %{count} zÅ‚ota na dzieÅ„." msgid "" "A little leprechaun dances gleefully around a magic sack. Seeing you " "approach, he stops in mid-stride. The little man screams and stamps his foot " "ferociously, vanishing into thin air. Remembering the old leprechaun saying " "'Finders Keepers', you grab the sack and leave." msgstr "" "Malutki leprekaun taÅ„czy radoÅ›nie wokół magicznego worka. WidzÄ…c, że " "nadchodzisz zatrzymuje siÄ™ w pół kroku. Niewielki czÅ‚owieczek z okrzykiem " "tupie nogÄ…, rozpÅ‚ywajÄ…c siÄ™ w powietrzu. PrzypominajÄ…c sobie stare " "powiedzenie leprekaunów \"znalezione - nie kradzione\" zabierasz worek i " "odchodzisz." msgid "Endless Bag of Gold" msgstr "Bezdenna Torba ZÅ‚ota" msgid "" "A noblewoman, separated from her traveling companions, asks for your help. " "After escorting her home, she rewards you with a bag filled with gold." msgstr "" "Pewna szlachcianka, oddzieliwszy siÄ™ od swej Å›wity, prosi ciÄ™ o pomoc. " "Odprowadziwszy jÄ… do domu, zostajesz nagrodzony torbÄ… peÅ‚nÄ… zÅ‚ota." msgid "Endless Purse of Gold" msgstr "Bezdenna Sakiewka ZÅ‚ota" msgid "" "In your travels, you find a leather purse filled with gold that once " "belonged to a great warrior king who had the ability to transform any " "inanimate object into gold." msgstr "" "Podczas swych podróży odnajdujesz sakiewkÄ™, wypeÅ‚nionÄ… zÅ‚otem, która niegdyÅ› " "należaÅ‚a do wielkiego króla i wojownika, który posiadÅ‚ umiejÄ™tność zamiany " "dowolnej nieożywionej materii w zÅ‚oto." msgid "Nomad Boots of Mobility" msgstr "Buty MobilnoÅ›ci Nomady" #, fuzzy msgid "The %{name} increase the hero's movement on land." msgstr "%{name} zwiÄ™kszajÄ… twój zasiÄ™g ruchu na lÄ…dzie." msgid "" "A Nomad trader seeks protection from a tribe of Goblins. For your " "assistance, he gives you a finely crafted pair of boots made from the " "softest leather. Looking closely, you see fascinating ancient carvings " "engraved on the leather." msgstr "" "Kupiec Nomada szuka ochrony przed plemieniem Goblinów. Za twÄ… pomoc " "podarowuje ci parÄ™ doskonaÅ‚ej jakoÅ›ci butów, wykonanych z najmiÄ™kszej skóry. " "Przy bliższych oglÄ™dzinach dostrzegasz fascynujÄ…ce antyczne tÅ‚oczenia na " "skórze." msgid "Traveler's Boots of Mobility" msgstr "Buty MobilnoÅ›ci Podróżnika" msgid "" "Discovering a pair of beautifully beaded boots made from the finest and " "softest leather, you thank the anonymous donor and add the boots to your " "inventory." msgstr "" "Odkrywszy parÄ™ piÄ™knie zdobionych trzewików, wykonanych z najmiÄ™kszej i " "najprzedniejszej skóry, dziÄ™kujesz nieznanemu darczyÅ„cy i dołączasz buty do " "swego ekwipunku." msgid "Lucky Rabbit's Foot" msgstr "Szczęśliwa Królicza Åapka" #, fuzzy msgid "The %{name} increases the luck of the hero's army by %{count}." msgstr "%{name} zwiÄ™ksza twoje szczęście w walce o %{count}." msgid "" "A traveling merchant offers you a rabbit's foot, made of gleaming silver " "fur, for safe passage. The merchant explains the charm will increase your " "luck in combat." msgstr "" "WÄ™drowny kramarz oferuje ci króliczÄ… Å‚apkÄ™, wykonanÄ… ze lÅ›niÄ…cego srebrnego " "futerka, w zamian za bezpiecznÄ… przeprawÄ™. Kramarz wyjaÅ›nia, że talizman " "zwiÄ™kszy twe szczęście w potyczkach." msgid "Golden Horseshoe" msgstr "ZÅ‚ota Podkowa" msgid "" "An ensnared Unicorn whinnies in fright. Murmuring soothing words, you set " "her free. Snorting and stamping her front hoof once, she gallops off. " "Looking down you see a golden horseshoe." msgstr "" "SÅ‚yszysz peÅ‚ne strachu pojÄ™kiwanie usidlonego Jednorożca. MruczÄ…c kojÄ…ce " "sÅ‚owa, uwalniasz klacz z puÅ‚apki. ParskajÄ…c i bijÄ…c kopytem raz o ziemiÄ™, " "zwierzÄ™ odbiega. PatrzÄ…c w dół dostrzegasz zÅ‚otÄ… podkowÄ™." msgid "Gambler's Lucky Coin" msgstr "Szczęśliwa Moneta Hazardzisty" msgid "" "You have captured a mischievous imp who has been terrorizing the region. In " "exchange for his release, he rewards you with a magical coin." msgstr "" "Udaje ci siÄ™ schwytać psotnego chochlika, który terroryzowaÅ‚ okolicÄ™. W " "zamian za swÄ… wolność otrzymujesz od niego magicznÄ… monetÄ™." msgid "Four-Leaf Clover" msgstr "Czterolistna Koniczyna" msgid "" "In the middle of a patch of dead and dry vegetation, to your surprise you " "find a healthy green four-leaf clover." msgstr "" "Ku swemu zaskoczeniu, poÅ›rodku kÄ™py martwych i pousychanych roÅ›lin " "odnajdujesz zieleniejÄ…cÄ… siÄ™ żywo czterolistnÄ… koniczynkÄ™." msgid "True Compass of Mobility" msgstr "Prawdziwy Kompas MobilnoÅ›ci" #, fuzzy msgid "The %{name} increases the hero's movement on land and sea." msgstr "%{name} zwiÄ™ksza twój zasiÄ™g ruchu na lÄ…dzie i morzu." msgid "" "An old man claiming to be an inventor asks you to try his latest invention. " "He then hands you a compass." msgstr "" "Stary mężczyzna, który przedstawia siÄ™ jako wynalazca, prosi ciÄ™ o " "wypróbowanie jego najnowszego wynalazku. Ofiarowuje ci kompas." msgid "Sailor's Astrolabe of Mobility" msgstr "Å»eglarskie Astrolabium MobilnoÅ›ci" #, fuzzy msgid "The %{name} increases the hero's movement on sea." msgstr "%{name} zwiÄ™ksza twój zasiÄ™g na morzu." msgid "" "An old sea captain is being tortured by Ogres. You save him, and in return " "he rewards you with a wondrous instrument to measure the distance of a star." msgstr "" "Banda Ogrów torturuje starego morskiego kapitana. Ratujesz go z opresji, a " "on w podziÄ™ce podarowuje ci cudowne narzÄ™dzie do pomiaru poÅ‚ożenia gwiazd." msgid "Evil Eye" msgstr "ZÅ‚e Oko" msgid "The %{name} reduces the casting cost of curse spells by half." msgstr "%{name} redukuje koszty klÄ…tw o poÅ‚owÄ™." msgid "" "While venturing into a decrepit hut you find the Skeleton of a long dead " "witch. Investigation of the remains reveals a glass eye rolling around " "inside an empty skull." msgstr "" "Podczas przeczesywania walÄ…cej siÄ™ chaÅ‚upki, znajdujesz Szkielet martwej od " "dawna wiedźmy. Badania szczÄ…tek pozwalajÄ… odkryć szklane oko krÄ™cÄ…ce siÄ™ " "wewnÄ…trz pustej czaszki." msgid "Enchanted Hourglass" msgstr "ZaklÄ™ta Klepsydra" #, fuzzy msgid "" "The %{name} extends the duration of all the hero's spells by %{count} turns." msgstr "" "%{name} zwiÄ™ksza czas trwania wszystkich twoich czarów o %{count} tury." #, fuzzy msgid "" "A surprise turn in the landscape finds you in the midst of a grisly scene: " "Vultures picking at the aftermath of a terrible battle. Your cursory search " "of the remains turns up an enchanted hourglass." msgstr "" "Podczas swych wÄ™drówek krajobraz nagle zmienia siÄ™, ukazujÄ…c makabrycznÄ… " "scenÄ™: sÄ™py żerujÄ…ce na pobojowisku straszliwej batalii. Szybkie oglÄ™dziny " "szczÄ…tek owocujÄ… znalezieniem zaczarowanej klepsydry." msgid "Gold Watch" msgstr "ZÅ‚oty Zegarek" #, fuzzy msgid "The %{name} doubles the effectiveness of the hero's hypnotize spells." msgstr "%{name} podwaja efektywność twoich czarów hipnozy." msgid "" "In reward for helping his cart out of a ditch, a traveling potion salesman " "gives you a \"magic\" gold watch. Unbeknownst to him, the watch really is " "magical." msgstr "" "Za pomoc w wyciÄ…gniÄ™ciu wózka z rowu wÄ™drowny sprzedawca eliksirów ofiaruje " "ci \"magiczny\" zÅ‚oty zegarek. Nie zdawaÅ‚ sobie on jednak sprawy, że zegarek " "zaiste jest magiczny." msgid "Skullcap" msgstr "Czepiec" msgid "The %{name} halves the casting cost of all mind influencing spells." msgstr "%{name} zmniejsza o poÅ‚owÄ™ koszty czarów wpÅ‚ywajÄ…cych na umysÅ‚." msgid "" "A brief stop at an improbable rural inn yields an exchange of money, tales, " "and accidentally, luggage. You find a magical skullcap in your new backpack." msgstr "" "Krótki postój w niespotykanie wiejskiej gospodzie zaowocowaÅ‚ wymianÄ… " "pieniÄ™dzy, opowieÅ›ci i przypadkowo także bagaży. WewnÄ…trz swego nowego " "plecaka znajdujesz magiczny czepiec." msgid "Ice Cloak" msgstr "Lodowy PÅ‚aszcz" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from cold spells." msgstr "" "%{name} zmniejsza o poÅ‚owÄ™ uszkodzenia od czarów zimna zadanych twoim " "oddziaÅ‚om." msgid "" "Responding to the panicked cries of a damsel in distress, you discover a " "young woman fleeing from a hungry bear. You slay the beast in the nick of " "time, and the grateful Sorceress weaves a magic cloak from the bear's hide." msgstr "" "KierujÄ…c siÄ™ panicznymi okrzykami damy w opaÅ‚ach, zauważasz mÅ‚odÄ… kobietÄ™ " "uciekajÄ…cÄ… przed wygÅ‚odniaÅ‚ym niedźwiedziem. Zarzynasz bestiÄ™ w mgnieniu " "oka, a wdziÄ™czna Czarodziejka tka dla ciebie magiczny pÅ‚aszcz z jego skóry." msgid "Fire Cloak" msgstr "Ognisty PÅ‚aszcz" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from fire spells." msgstr "" "%{name} zmniejsza o poÅ‚owÄ™ uszkodzenia od czarów ognia zadanych twoim " "oddziaÅ‚om." msgid "" "You've come upon a fight between a Necromancer and a Paladin. The " "Necromancer blasts the Paladin with a fire bolt, bringing him to his knees. " "Acting quickly, you slay the evil one before the final blow. The grateful " "Paladin gives you the fire cloak that saved him." msgstr "" "Wpadasz w sam Å›rodek walki pomiÄ™dzy NekromantÄ… a Paladynem. Nekromanta " "strzela w Paladyna ognistym pociskiem, a rycerz pada na kolana. DziaÅ‚asz " "szybko i rozpÅ‚atasz zÅ‚oczyÅ„cÄ™ nim ten zadaje ostateczny cios. WdziÄ™czny " "Paladyn daje ci w zamian ognisty pÅ‚aszcz, który go ochroniÅ‚." msgid "Lightning Helm" msgstr "HeÅ‚m BÅ‚yskawicy" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from lightning " "spells." msgstr "" "%{name} zmniejsza o poÅ‚owÄ™ uszkodzenia od czarów burzy zadanych twoim " "oddziaÅ‚om." msgid "" "A traveling tinker in need of supplies offers you a helm with a thunderbolt " "design on its top in exchange for food and water. Curious, you accept, and " "later find out that the helm is magical." msgstr "" "WÄ™drowny majsterkowicz, bÄ™dÄ…c pozbawiony zapasów, oferuje ci heÅ‚m zdobiony " "motywem bÅ‚yskawicy w zamian za strawÄ™ i wodÄ™. ZaciekawiÅ‚ ciÄ™ ów przedmiot i " "zgadzasz siÄ™, a nastÄ™pnie odkrywasz, że heÅ‚m ten ma magiczne wÅ‚aÅ›ciwoÅ›ci." msgid "Evercold Icicle" msgstr "Wiecznie Zimny Sopel" #, fuzzy msgid "" "The %{name} causes the hero's cold spells to do %{count} percent more damage " "to enemy troops." msgstr "%{name} zwiÄ™ksza uszkodzenia twoich czarów zimna o %{count} procent." msgid "" "An icicle withstanding the full heat of the noonday sun attracts your " "attention. Intrigued, you break it off, and find that it does not melt in " "your hand." msgstr "" "TwÄ… uwagÄ™ przykuwa sopel lodu, który zdaje siÄ™ opierać sÅ‚oÅ„cu Å›wiecÄ…cemu w " "samo poÅ‚udnie. Zaintrygowany, odÅ‚amujesz go i odkrywasz, że sopel ten nie " "topi siÄ™ w twej dÅ‚oni." msgid "Everhot Lava Rock" msgstr "NiestygnÄ…ca SkaÅ‚a Wulkaniczna" #, fuzzy msgid "" "The %{name} causes the hero's fire spells to do %{count} percent more damage " "to enemy troops." msgstr "%{name} zwiÄ™ksza uszkodzenia twoich czarów ognia o %{count} procent." msgid "" "Your wanderings bring you into contact with a tribe of ape-like beings using " "a magical lava rock that never cools to light their fires. You take pity on " "them and teach them to make fire with sticks. Believing you to be a god, the " "apes give you their rock." msgstr "" "Podczas swych podróży napotykasz plemiÄ™ maÅ‚poksztaÅ‚tnych istot, które używa " "magicznej skaÅ‚y wulkanicznej do rozniecania ognia. Å»al ci tych istot i " "uczysz ich jak rozpalać ogieÅ„ przy pomocy Å‚uku ogniowego. WierzÄ…c, że jesteÅ› " "boskÄ… istotÄ…, maÅ‚poludzie oddajÄ… ci swÄ… skałę." msgid "Lightning Rod" msgstr "Piorunochron" #, fuzzy msgid "" "The %{name} causes the hero's lightning spells to do %{count} percent more " "damage to enemy troops." msgstr "%{name} zwiÄ™ksza uszkodzenia twoich czarów burzy o %{count} procent." msgid "" "While waiting out a storm, a lighting bolt strikes a nearby cottage's " "lightning rod, which melts and falls to the ground. The tip of the rod, " "however, survives intact and makes your hair stand on end when you touch it. " "Hmm..." msgstr "" "Przeczekujesz burzÄ™ i nagle bÅ‚yskawica uderza w piorunochron pobliskiej " "chaÅ‚upy, topiÄ…c go i zrzucajÄ…c na ziemiÄ™. KoÅ„cówka piorunochronu jednakowoż " "przetrwaÅ‚a uderzenie pioruna, a gdy jej dotykasz, wÅ‚osy stajÄ… ci dÄ™ba. Hmm..." msgid "Snake-Ring" msgstr "Wężowy PierÅ›cieÅ„" #, fuzzy msgid "The %{name} halves the casting cost of all of the hero's bless spells." msgstr "" "%{name} zmniejsza o poÅ‚owÄ™ koszty wszystkich twoich czarów bÅ‚ogosÅ‚awieÅ„stwa." msgid "" "You've found an oddly shaped ring on the finger of a long dead traveler. The " "ring looks like a snake biting its own tail." msgstr "" "Na palcu u dÅ‚oni dawno zmarÅ‚ego podróżnika odnajdujesz dziwnie uksztaÅ‚towany " "pierÅ›cieÅ„. Przypomina węża kÄ…sajÄ…cego swój wÅ‚asny ogon." msgid "Ankh" msgstr "Ankh" #, fuzzy msgid "" "The %{name} doubles the effectiveness of all of the hero's resurrect and " "animate spells." msgstr "" "%{name} podwaja skuteczność wszystkich twoich czarów wskrzeszania i " "ożywiania." msgid "" "A fierce windstorm reveals the entrance to a buried tomb. Your investigation " "reveals that the tomb has already been looted, but the thieves overlooked an " "ankh on a silver chain in the dark." msgstr "" "Potężny huragan odkrywa wejÅ›cie do zakopanego grobowca. Twoje oglÄ™dziny " "ujawniajÄ…, że grobowiec byÅ‚ już plÄ…drowany, ale zÅ‚odzieje przeoczyli w " "ciemnoÅ›ci znak ankh na srebrnym Å‚aÅ„cuszku." msgid "Book of Elements" msgstr "KsiÄ™ga Å»ywiołów" #, fuzzy msgid "" "The %{name} doubles the effectiveness of all of the hero's summoning spells." msgstr "%{name} podwaja skuteczność wszystkich twoich czarów przywoÅ‚ywania." msgid "" "You come across a conjurer who begs to accompany you and your army awhile " "for safety. You agree, and he offers as payment a copy of the book of the " "elements." msgstr "" "Napotykasz mÅ‚odego magika, który prosi o możliwość dołączenia do ciebie ze " "wzglÄ™du na czajÄ…ce siÄ™ wokół niebezpieczeÅ„stwa. Zgadzasz siÄ™, a on " "ofiarowuje ci jako zapÅ‚atÄ™ egzemplarz KsiÄ™gi Å»ywiołów." msgid "Elemental Ring" msgstr "PierÅ›cieÅ„ Å»ywiołów" msgid "The %{name} halves the casting cost of all summoning spells." msgstr "%{name} zmniejsza o poÅ‚owÄ™ koszty wszystkich czarów przywoÅ‚ywania." msgid "" "While pausing to rest, you notice a bobcat climbing a short tree to get at a " "crow's nest. On impulse, you climb the tree yourself and scare off the cat. " "When you look in the nest, you find a collection of shiny stones and a ring." msgstr "" "WypoczywajÄ…c w trakcie postoju, dostrzegasz jak żbik wdrapuje siÄ™ na niskie " "drzewo, by dostać siÄ™ do kruczego gniazda. DziaÅ‚ajÄ…c pod wpÅ‚ywem impulsu, " "sam wdrapujesz siÄ™ na drzewo i odstraszasz dzikiego kota. W Å›rodku gniazda " "znajdujesz kolekcjÄ™ bÅ‚yszczÄ…cych kamieni oraz pierÅ›cieÅ„." msgid "Holy Pendant" msgstr "ÅšwiÄ™ty Medalion" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to curse spells." msgstr "" "%{name} sprawia, że wszystkie twoje oddziaÅ‚y sÄ… niewrażliwe na czary klÄ…tw." msgid "" "In your wanderings you come across a hermit living in a small, tidy hut. " "Impressed with your mission, he takes time out from his meditations to bless " "and give you a charm against curses." msgstr "" "Podczas twoich wÄ™drówek natknÄ…Å‚eÅ› siÄ™ na pustelnika, mieszkajÄ…cego w maÅ‚ej, " "schludnej chaÅ‚upce. ZaimponowaÅ‚o mu podjÄ™cie przez ciebie twej misji, " "przewaÅ‚ wiÄ™c swÄ… medytacjÄ™ by dać ci swe bÅ‚ogosÅ‚awieÅ„stwo i podarować amulet " "przeciwdziaÅ‚ajÄ…cy klÄ…twom." msgid "Pendant of Free Will" msgstr "Medalion Wolnej Woli" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to hypnotize spells." msgstr "" "%{name} sprawia, że wszystkie twoje oddziaÅ‚y sÄ… niewrażliwe na czary hipnozy." msgid "" "Responding to cries for help, you find river Sprites making a sport of " "dunking an old man. Feeling vengeful, you rescue the man and drag a Sprite " "onto dry land for awhile. The Sprite, uncomfortable in the air, gives you a " "magic pendant to let him go." msgstr "" "ChcÄ…c odpowiedzieć na woÅ‚anie o pomoc, znajdujesz grupÄ™ rzecznych RusaÅ‚ek " "podtapiajÄ…cych dla zabawy starszego mężczyznÄ™. CzujÄ…c narastajÄ…cÄ… w tobie " "mÅ›ciwość, ratujesz starca i wyciÄ…gasz jednÄ… z RusaÅ‚ek na suchy lÄ…d. RusaÅ‚ka, " "czujÄ…c siÄ™ źle poza wodÄ…, oferuje ci magiczny medalion w zamian za " "puszczenie wolno." msgid "Pendant of Life" msgstr "Medalion Å»ycia" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to death spells." msgstr "" "%{name} sprawia, że wszystkie twoje oddziaÅ‚y sÄ… niewrażliwe na czary Å›mierci." msgid "" "A brief roadside encounter with a small caravan and a game of knucklebones " "wins a magic pendant. Its former owner says that it protects from " "Necromancers' death spells." msgstr "" "Krótkie spotkanie na poboczu z niewielkÄ… karawanÄ… i gra w koÅ›ci prowadzÄ… do " "wygranej magicznego medalionu. Jego poprzedni wÅ‚aÅ›ciciel twierdzi, że chroni " "on przed nekromanckimi zaklÄ™ciami Å›mierci." msgid "Serenity Pendant" msgstr "Medalion Spokoju" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to berserk spells." msgstr "" "%{name} sprawia, że wszystkie twoje oddziaÅ‚y sÄ… niewrażliwe na czary szaÅ‚u." msgid "" "The sounds of combat draw you to the scene of a fight between an old " "Barbarian and an eight-headed Hydra. Your timely intervention swings the " "battle in favor of the man, and he rewards you with a pendant he used to use " "to calm his mind for battle." msgstr "" "OdgÅ‚osy walki prowadzÄ… ciÄ™ na miejsce pojedynku pomiÄ™dzy starym BarbarzyÅ„cÄ… " "a oÅ›miogÅ‚owÄ… HydrÄ…. Twoja rychÅ‚a interwencja przechyla szalÄ™ zwyciÄ™stwa na " "korzyść mężczyzny, a ten wynagradza ci to medalionem, którego zwykÅ‚ używać " "do kojenia swego umysÅ‚u przed bitwÄ…." msgid "Seeing-eye Pendant" msgstr "Medalion Wszystkowidzenia" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to blindness spells." msgstr "" "%{name} sprawia, że wszystkie twoje oddziaÅ‚y sÄ… niewrażliwe na czary " "oÅ›lepienia." msgid "" "You come upon a very old woman, long blind from cataracts and dying alone. " "You tend to her final needs and promise a proper burial. Grateful, she gives " "you a magic pendant emblazoned with a stylized eye. It lets you see with " "your eyes closed." msgstr "" "Spotykasz bardzo starÄ…, umierajÄ…cÄ… w samotnoÅ›ci kobietÄ™, której katarakta " "dawno już odebraÅ‚a wzrok. Postanawiasz speÅ‚nić jej ostatniÄ… proÅ›bÄ™ i " "obiecujesz jej stosowny pochówek. PrzepeÅ‚niona wdziÄ™cznoÅ›ciÄ…, kobieta " "obdarowuje ciÄ™ magicznym medalionem, ozdobionym stylizowanym symbolem oka. " "Pozwala ci on widzieć nawet z zamkniÄ™tymi oczyma." msgid "Kinetic Pendant" msgstr "Kinetyczny Medalion" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to paralyze spells." msgstr "" "%{name} sprawia, że wszystkie twoje oddziaÅ‚y sÄ… niewrażliwe na czary " "paraliżu." msgid "" "You come across a golem wearing a glowing pendant and blocking your way. " "Acting on a hunch, you cut the pendant from its neck. Deprived of its power " "source, the golem breaks down, leaving you with the magical pendant." msgstr "" "Na twej drodze staje golem noszÄ…cy lÅ›niÄ…cy medalion i blokuje ci przejÅ›cie. " "DziaÅ‚ajÄ…c instynktownie, odcinasz medalion z jego szyi. Pozbawiony źródÅ‚a " "swej mocy, golem przestaje dziaÅ‚ać, pozostawiajÄ…c ciÄ™ z magicznym medalionem." msgid "Pendant of Death" msgstr "Medalion Åšmierci" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to holy spells." msgstr "" "%{name} sprawia, że wszystkie twoje oddziaÅ‚y sÄ… niewrażliwe na Å›wiÄ™te czary." msgid "" "A quick and deadly battle with a Necromancer wins you his magical pendant. " "Later, a Wizard tells you that the pendant protects undead under your " "control from holy word spells." msgstr "" "Krótka i Å›miercionoÅ›na walka z NekromantÄ… pozwala ci posiąść jego magiczny " "medalion. Później, Czarodziej wyjaÅ›nia ci, że medalion ów chroni " "kontrolowanych przez ciebie nieumarÅ‚ych przed Å›wiÄ™tymi zaklÄ™ciami." msgid "Wand of Negation" msgstr "Różdżka Negacji" #, fuzzy msgid "" "The %{name} makes all of the hero's troops immune to dispel magic spells." msgstr "" "%{name} sprawia, że wszystkie twoje oddziaÅ‚y sÄ… niewrażliwe na czary Å›mierci." #, fuzzy msgid "" "You meet an old Wizard friend of yours traveling in the opposite direction. " "He presents you with a gift: A wand that prevents the use of the dispel " "magic spell on your allies." msgstr "" "Spotykasz starego przyjaciela Czarodzieja, podróżujÄ…cego w przeciwnym " "kierunku. Obdarowuje ciÄ™ on podarkiem: różdżkÄ… zdolnÄ… ochronić twe oddziaÅ‚y " "przed zaklÄ™ciami rozproszenia magii." msgid "Golden Bow" msgstr "ZÅ‚oty Åuk" #, fuzzy msgid "" "The %{name} eliminates the %{count} percent penalty for the hero's troops " "shooting past obstacles (e.g. castle walls)." msgstr "" "%{name} eliminuje %{count} procent kary dla twoich oddziałów strzelajÄ…cych " "przez przeszkody (np. zamkowe mury)" msgid "" "A chance meeting with a famous Archer finds you in a game of knucklebones " "pitting his bow against your horse. You win." msgstr "" "Przypadkowe spotkanie ze sÅ‚awnym Åucznikiem prowadzi ciÄ™ do rozgrywki w " "koÅ›ci, jego Å‚uk przeciwko twemu wierzchowcowi. Wygrywasz." msgid "Telescope" msgstr "Teleskop" #, fuzzy msgid "" "The %{name} increases the amount of terrain the hero reveals when " "adventuring by %{count} extra square." msgstr "" "%{name} zwiÄ™ksza ilość odsÅ‚anianego terenu podczas zwiedzania o %{count} " "dodatkowe pola." msgid "" "A merchant from far away lands trades you a new invention of his people for " "traveling supplies. It makes distant objects appear closer, and he calls " "it...\n" "\n" "a telescope." msgstr "" "Kupiec z odlegÅ‚ych krain dobija z tobÄ… targu i wymienia swój nowy wynalazek " "na suchy prowiant. Przedmiot sprawia, że odlegÅ‚e obiekty wydajÄ… siÄ™ bliższe, " "a nazwaÅ‚ on to urzÄ…dzenie...\n" "\n" "teleskopem." msgid "Statesman's Quill" msgstr "Pióro Dyplomaty" #, fuzzy msgid "" "The %{name} reduces the cost of surrender to %{count} percent of the total " "cost of troops the hero has in their army." msgstr "" "%{name} redukuje koszt kapitulacji do %{count} procent caÅ‚ego kosztu armii " "bÄ™dÄ…cej w posiadaniu." msgid "" "You pause to help a diplomat with a broken axle fix his problem. In " "gratitude, he gives you a writing quill with magical properties which he " "says will \"help people see things your way\"." msgstr "" "Zatrzymujesz siÄ™ by pomóc emisariuszowi rozwiÄ…zać problem pÄ™kniÄ™tej osi. W " "dowodzie uznania ofiarowuje ci on pióro o magicznych wÅ‚aÅ›ciwoÅ›ciach - " "twierdzi, że \"pomaga innym postrzegać rzeczy po twojemu\"." msgid "Wizard's Hat" msgstr "Kapelusz Czarodzieja" #, fuzzy msgid "" "The %{name} increases the duration of the hero's spells by %{count} turns." msgstr "%{name} zwiÄ™ksza czas trwania twoich czarów o %{count} tur." msgid "" "You see a Wizard fleeing from a Griffin and riding like the wind. The Wizard " "opens a portal and rides through, getting his hat knocked off by the edge of " "the gate. The Griffin follows; the gate closes. You pick the hat up, dust it " "off, and put it on." msgstr "" "Dostrzegasz uciekajÄ…cego przed Gryfem Czarodzieja, gnajÄ…cego niczym wiatr. " "Czarodziej otwiera portal i przejeżdża przezeÅ„, strÄ…cajÄ…c jednak swój " "kapelusz o krawÄ™dź magicznej bramy. Gryf podąża za nim, a brama siÄ™ zamyka. " "Podnosisz kapelusz, otrzepujesz z pyÅ‚u i zakÅ‚adasz na wÅ‚asnÄ… gÅ‚owÄ™." msgid "Power Ring" msgstr "PierÅ›cieÅ„ Mocy" #, fuzzy msgid "The %{name} returns %{count} extra spell points per day to the hero." msgstr "" "%{name} przywraca twojemu bohaterowi dodatkowe %{count} punkty magii na " "dzieÅ„." msgid "" "You find a small tree that closely resembles the great Warlock Carnauth with " "a ring around one of its twigs. Scraps of clothing and rotting leather lead " "you to suspect that it IS Carnauth, transformed. Since you can't help him, " "you take the magic ring." msgstr "" "Odnajdujesz niewielkie drzewo, które niezwykle wrÄ™cz przypomina ci z wyglÄ…du " "wielkiego Czarnoksiężnika Carnaught'a, a na jednej z gałązek dostrzegasz " "pierÅ›cieÅ„. Porwane kawaÅ‚ki szat i przegniÅ‚a skóra sprawiajÄ…, że zaczynasz " "podejrzewać, iż to w istocie JEST Carnauth, jednak przemieniony. Jako, że i " "tak mu już nie pomożesz, zabierasz magiczny pierÅ›cieÅ„ ze sobÄ…." msgid "Ammo Cart" msgstr "Wóz z AmunicjÄ…" #, fuzzy msgid "" "The %{name} provides endless ammunition for all of the hero's troops that " "shoot." msgstr "" "%{name} zapewnia nieskoÅ„czonÄ… amunicjÄ™ dla wszystkich twoich oddziałów " "strzelajÄ…cych." msgid "" "An ammunition cart in the middle of an old battlefield catches your eye. " "Inspection shows it to be in good working order, so you take it along." msgstr "" "TwÄ… uwagÄ™ przykuwa pozostawiony na Å›rodku dawnego pola bitwy wóz z amunicjÄ…. " "OglÄ™dziny pozwalajÄ… ustalić, że jest on w caÅ‚kiem dobrym stanie, zabierasz " "go wiÄ™c ze sobÄ…." msgid "Tax Lien" msgstr "Weksel Fiskalny" #, fuzzy msgid "The %{name} costs the hero %{count} gold pieces per day." msgstr "%{name} kosztuje ciÄ™ %{count} zÅ‚ota na dzieÅ„." msgid "" "Your big spending habits have earned you a massive tax bill that you can't " "hope to pay. The tax man takes pity and agrees to only take 250 gold a day " "from your account for life. Check here if you want one dollar to go to the " "presidential campaign election fund." msgstr "" "Twa rozrzutność zrodziÅ‚a olbrzymie zadÅ‚użenie podatkowe, którego nijak nie " "możesz spÅ‚acić. Poborca podatkowy lituje siÄ™ nad tobÄ… i godzi siÄ™ na " "pobieranie z twego majÄ…tku marne 250 sztuki zÅ‚ota każdego dnia, przez resztÄ™ " "twego żywota." msgid "Hideous Mask" msgstr "Potworna Maska" #, fuzzy msgid "The %{name} prevents all 'wandering' armies from joining the hero." msgstr "" "%{name} zapobiega dołączaniu wszystkich \"wÄ™drownych\" armii do twojego " "bohatera." #, fuzzy msgid "" "Your looting of the grave of Sinfilas Gardolad, the famous shapeshifting " "Warlock, unearths his fabled mask. Trembling, you put it on and it twists " "your visage into an awful grimace! Oh no! It's actually the hideous mask of " "Gromluck Greene, and you are stuck with it." msgstr "" "PrzeczesujÄ…c grób Sinfilasa Gardolada, sÅ‚ynnego zmiennoksztaÅ‚tnego " "Czarnoksiężnika, odkrywasz jego osÅ‚awionÄ… maskÄ™. Drżąc na caÅ‚ym ciele, " "zakÅ‚adasz jÄ…, a ta wykrÄ™ca twojÄ… twarz w okropnym grymasie! O nie! OkazaÅ‚o " "siÄ™, że to tak naprawdÄ™ jest Potworna Maska Gromluck'a Greene'a, a ty w niej " "utknÄ…Å‚eÅ› na dobre." msgid "Endless Pouch of Sulfur" msgstr "Bezdenna Sakiewka Siarki" msgid "The %{name} provides %{count} unit of sulfur per day." msgstr "%{name} zapewnia %{count} jednostkÄ™ siarki dziennie." msgid "" "You visit an alchemist who, upon seeing your army, is swayed by the " "righteousness of your cause. The newly loyal subject gives you his endless " "pouch of sulfur to help with the war effort." msgstr "" "Odwiedzasz alchemika, który ujrzawszy towarzyszÄ…ce ci oddziaÅ‚y ulega " "prawoÅ›ci twojej sprawy. Åšwieżo upieczony lojalny podwÅ‚adny oddaje ci swÄ… " "bezdennÄ… sakiewkÄ™ siarki, by wspomóc ciÄ™ w wysiÅ‚ku wojennym." msgid "Endless Vial of Mercury" msgstr "Bezdenna Fiolka RtÄ™ci" msgid "The %{name} provides %{count} unit of mercury per day." msgstr "%{name} zapewnia %{count} jednostkÄ™ rtÄ™ci dziennie." msgid "" "A brief stop at a hastily abandoned Wizard's tower turns up a magical vial " "of mercury that always has a little left on the bottom. Recognizing a " "treasure when you see one, you cap it and slip it in your pocket." msgstr "" "Krótki postój w opuszczonej w najwyraźniej wielkim poÅ›piechu wieży " "Czarodzieja pozwala ci odkryć magicznÄ… fiolkÄ™ rtÄ™ci, na dnie której zawsze " "pozostaje resztka tej substancji. PotrafiÄ…c rozpoznać skarb, gdy ten leży " "przed tobÄ…, zatykasz fiolkÄ™ korkiem i wsuwasz do swej kieszeni." msgid "Endless Pouch of Gems" msgstr "Bezdenna Sakiewka Klejnotów" msgid "The %{name} provides %{count} unit of gems per day." msgstr "%{name} zapewnia %{count} jednostkÄ™ klejnotów dziennie." msgid "" "A short rainstorm brings forth a rainbow...and you can see the end of it. " "Riding quickly, you seize the pot of gold you find there. The leprechaun who " "owns it, unable to stop you from taking it, offers an endless pouch of gems " "for the return of his gold. You accept." msgstr "" "Po krótkiej burzy na niebie pojawia siÄ™ tÄ™cza... a ty możesz dostrzec jej " "koniec. JadÄ…c szybko, zagarniasz znajdujÄ…cy siÄ™ tam garniec ze zÅ‚otem. " "Leprekaun, którego byÅ‚a to wÅ‚asność, nie mogÄ…c ciÄ™ powstrzymać przed " "zabraniem skarbu, oferuje ci bezdennÄ… sakiewkÄ™ z klejnotami w zamian za " "zwrot jego zÅ‚ota. Zgadzasz siÄ™ na te warunki." msgid "Endless Cord of Wood" msgstr "Niewyczerpany Stos Drewna" msgid "The %{name} provides %{count} unit of wood per day." msgstr "%{name} zapewnia %{count} jednostkÄ™ drewna dziennie." msgid "" "Pausing to rest and light a cook fire, you pull wood out of a nearby pile of " "dead wood. As you keep pulling wood from the pile, you notice that it " "doesn't shrink. You realize to your delight that the wood is enchanted, so " "you take it along." msgstr "" "ZatrzymujÄ…c siÄ™ by odpocząć i rozpalić ogieÅ„ pod kocioÅ‚kiem, siÄ™gasz po " "bierwiono z pobliskiego stosu drewna. SiÄ™gajÄ…c po coraz to wiÄ™cej kawaÅ‚ków " "drzewa zauważasz, że stos ten siÄ™ nie zmniejsza. Dociera do ciebie, że " "drewno to musi być zaczarowane, wiÄ™c zabierasz je ze sobÄ…." msgid "Endless Cart of Ore" msgstr "Wiecznie PeÅ‚ny Wóz Rudy" msgid "The %{name} provides %{count} unit of ore per day." msgstr "%{name} zapewnia %{count} jednostkÄ™ rudy dziennie." msgid "" "You've found a Goblin weapon smithy making weapons for use against humans. " "With a tremendous yell you and your army descend upon their camp and drive " "them away. A search finds a magic ore cart that never runs out of iron." msgstr "" "Odnajdujesz goblinich mieczników, kujÄ…cych broÅ„ przeznaczonÄ… do walki z " "ludźmi. Z grzmiÄ…cym okrzykiem ty i twe oddziaÅ‚y wpadacie do ich obozu i " "przepÄ™dzacie ich w sinÄ… dal. Przeszukanie obozu owocuje znalezieniem " "magicznego wózka rudy, który nigdy nie może zostać opróżniony." msgid "Endless Pouch of Crystal" msgstr "Bezdenna Sakiewka Kryształów" msgid "The %{name} provides %{count} unit of crystal per day." msgstr "%{name} zapewnia %{count} jednostkÄ™ kryształów dziennie." #, fuzzy msgid "" "Taking shelter from a storm in a small cave, you notice a small patch of " "crystal in one corner. Curious, you break a piece off and notice that the " "original crystal grows the lost piece back. You decide to stuff the entire " "patch into a pouch and take it with you." msgstr "" "ChroniÄ…c siÄ™ przed burzÄ… w maÅ‚ej jaskini, dostrzegasz niewielkÄ… formacjÄ™ " "kryształów w jednym z zakamarków pieczary. Wiedziony ciekawoÅ›ciÄ…, odÅ‚amujesz " "kawaÅ‚ek i zauważasz, że brakujÄ…cy fragment mineraÅ‚u odrasta na nowo. " "Decydujesz siÄ™ na zapakowanie caÅ‚ego krysztaÅ‚u do sakiewki i zabierasz go ze " "sobÄ…." msgid "Spiked Helm" msgstr "Rogaty HeÅ‚m" msgid "" "Your army is ambushed by a small tribe of wild (and none too bright) Orcs. " "You fend them off easily and the survivors flee in all directions. One of " "the Orcs was wearing a polished spiked helm. Figuring it will make a good " "souvenir, you take it." msgstr "" "Twoja armia wpada w zasadzkÄ™ niewielkiego szczepu dzikich (i niezbyt " "bystrych) Orków. Odpierasz ich z Å‚atwoÅ›ciÄ…, a ocaleni uciekajÄ… we wszystkich " "kierunkach. Jeden z Orków nosiÅ‚ wypolerowany rogaty heÅ‚m. Uznajesz, że " "bÄ™dzie to dobra pamiÄ…tka i zabierasz go ze sobÄ…." msgid "Spiked Shield" msgstr "Rogata Tarcza" msgid "" "You come upon a bridge spanning a dry gully. Before you can cross, a Troll " "steps out from under the bridge and demands payment before it will permit " "you to pass. You refuse, and the Troll charges, forcing you to slay it. You " "take its spiked shield as a trophy." msgstr "" "Natrafiasz na most spinajÄ…cy brzegi wyschniÄ™tego wÄ…wozu. Nim przechodzisz na " "drugÄ… stronÄ™, spod mostu wyÅ‚ania siÄ™ Troll i żąda od ciebie zapÅ‚aty nim " "pozwoli ci skorzystać z przejÅ›cia. Odmawiasz, a wtedy Troll szarżuje na " "ciebie, nie pozostawiajÄ…c ci innego wyjÅ›cia jak zaszlachtowanie go. Jako " "trofeum zabierasz jego rogatÄ… tarczÄ™." msgid "White Pearl" msgstr "BiaÅ‚a PerÅ‚a" #, fuzzy msgid "" "A walk across a dry saltwater lake bed yields an unlikely prize: A white " "pearl amidst shattered shells and debris." msgstr "" "Spacer poprzez nieckÄ™ wyschniÄ™tego sÅ‚onego jeziora koÅ„czy siÄ™ niespodziewanÄ… " "nagrodÄ…: białą perłą skrytÄ… pomiÄ™dzy pokruszonymi muszlami i Å›mieciami." msgid "Black Pearl" msgstr "Czarna PerÅ‚a" msgid "" "Rumors of a Griffin of unusual size preying upon the countryside lead you to " "its cave lair. A quick, brutal fight dispatches the beast, and a search of " "its foul nest turns up a huge black pearl." msgstr "" "Plotki o Gryfie niespotykanych rozmiarów, żerujÄ…cym na okolicy, zaprowadziÅ‚y " "ciÄ™ do jego skalnego leża. Krótka, zaciÄ™ta walka przynosi kres bestii, a " "przeszukanie jego plugawego gniazda koÅ„czy siÄ™ znalezieniem wielkiej czarnej " "perÅ‚y." msgid "Magic Book" msgstr "KsiÄ™ga Zaklęć" #, fuzzy msgid "The %{name} enables the hero to cast spells." msgstr "%{name} pozwala ci rzucać czary." msgid "Dummy 1" msgstr "Artefakt 1" msgid "The reserved artifact." msgstr "Zarezerwowany artefakt." msgid "Dummy 2" msgstr "Artefakt 2" msgid "Dummy 3" msgstr "Artefakt 3" msgid "Dummy 4" msgstr "Artefakt 4" msgid "Spell Scroll" msgstr "Zwój z ZaklÄ™ciem" #, fuzzy msgid "" "This %{name} gives the hero the ability to cast the %{spell} spell if the " "hero has a Magic Book." msgstr "" "Ten %{name} daje twojemu bohaterowi możliwość rzucania czaru %{spell} jeÅ›li " "bohater posiada KsiÄ™gÄ™ Zaklęć." msgid "" "You find an elaborate container which houses an old vellum scroll. The runes " "on the container are very old, and the artistry with which it was put " "together is stunning. As you pull the scroll out, you feel imbued with " "magical power." msgstr "" "Odnajdujesz starÄ…, bogato zdobionÄ… szkatułę pokrytÄ… runami. Z ciekawoÅ›ci " "zaglÄ…dasz do Å›rodka i znajdujesz stary pergamin. WyciÄ…gasz go z " "niecierpliwoÅ›ciÄ… i nagle czujesz, jak twe ciaÅ‚o wypeÅ‚nia magiczna moc." msgid "Arm of the Martyr" msgstr "RamiÄ™ MÄ™czennika" #, fuzzy msgid "" "The %{name} increases the hero's spell power by %{count} but adds the undead " "morale penalty." msgstr "" "%{name} zwiÄ™ksza twojÄ… Moc o %{count} ale również dodaje ujemne morale dla " "nieumarÅ‚ych." msgid "" "One of the less intelligent members of your party picks up an arm off of the " "ground. Despite its missing a body, it is still moving. Your troops find the " "dismembered arm repulsive, but you cannot bring yourself to drop it: it " "seems to hold some sort of magical power that influences your decision " "making." msgstr "" "Jeden z twoich mniej inteligentnych żoÅ‚nierzy podnosi z ziemi ramiÄ™. Pomimo " "tego, że jest odrÄ…bane od ciaÅ‚a, caÅ‚y czas siÄ™ rusza. Mimo że ramiÄ™ jest " "obrzydliwe, nie możesz siÄ™ zmusić, by je zostawić - ma chyba jakÄ…Å› magicznÄ… " "moc, która wpÅ‚ywa na podejmowanie przez ciebie decyzji." msgid "Breastplate of Anduran" msgstr "NapierÅ›nik Andurana" #, fuzzy msgid "The %{name} increases the hero's defense by %{count}." msgstr "%{name} zwiÄ™ksza twojÄ… obronÄ™ o %{count}" msgid "" "You come upon a sign. It reads: \"Here lies the body of Anduran. Bow and " "swear fealty, and you shall be rewarded.\" You decide to do as it says. As " "you stand up, you feel a coldness against your skin. Looking down, you find " "that you are suddenly wearing a gleaming, ornate breastplate." msgstr "" "Podchodzisz do znaku, na którym znajduje siÄ™ napis: \"Tutaj spoczywa ciaÅ‚o " "Andurana. SkÅ‚oÅ„ siÄ™ i przysiÄ™gnij mu wierność, a otrzymasz nagrodÄ™.\" " "Decydujesz siÄ™ to zrobić. Gdy wstajesz, czujesz coÅ› chÅ‚odnego na swoim " "ciele. SpoglÄ…dasz w dół i widzisz, że masz na sobie zdobiony napierÅ›nik." msgid "Broach of Shielding" msgstr "Brosza OsÅ‚ony" msgid "" "The %{name} provides %{count} percent protection from Armageddon and " "Elemental Storm, but decreases spell power by 2." msgstr "" "%{name} zapewnia %{count} procent ochrony od zaklÄ™cia Armageddon i Burza " "Å»ywiołów, ale obniża moc czarów o 2." msgid "" "A kindly Sorceress thinks that your army's defenses could use a magical " "boost. She offers to enchant the Broach that you wear on your cloak, and you " "accept." msgstr "" "Pewna urocza czarodziejka uważa, że twojej armii przydaÅ‚oby siÄ™ magiczne " "wzmocnienie. Proponuje, że rzuci specjalne zaklÄ™cie na noszonÄ… przez ciebie " "broszÄ™, na co siÄ™ ochoczo zgadzasz." msgid "Battle Garb of Anduran" msgstr "Bitewny Strój Andurana" #, fuzzy msgid "" "The %{name} combines the powers of the three Anduran artifacts. It provides " "maximum luck and morale for the hero's troops and gives the hero the Town " "Portal spell." msgstr "" "%{name} łączy moce trzech AnduriaÅ„skich artefaktów. Zapewnia maksymalne " "szczęście i morale dla oddziałów oraz dodaje czar Portal Miejski." msgid "" "Out of pity for a poor peasant, you purchase a chest of old junk they are " "hawking for too much gold. Later, as you search through it, you find it " "contains the 3 pieces of the legendary battle garb of Anduran!" msgstr "" "Z litoÅ›ci dla biednego wieÅ›niaka odkupujesz od niego skrzyniÄ™ peÅ‚nÄ… starych " "rupieci. Gdy później je przeglÄ…dasz, znajdujesz wÅ›ród nich 3 części " "legendarnego stroju Andurana!" msgid "Crystal Ball" msgstr "KrysztaÅ‚owa Kula" #, fuzzy msgid "" "The %{name} lets the hero get more specific information about monsters, " "enemy heroes, and castles nearby the hero." msgstr "" "%{name} pozwala zdobyć bardziej szczegółowe informacje na temat potworów, " "wrogich armii i zamków znajdujÄ…cych siÄ™ w pobliżu bohatera który to nosi." msgid "" "You come upon a caravan of gypsies who are feasting and fortifying their " "bodies with mead. They call you forward and say \"If you prove that you can " "dance the Rama-Buta, we will reward you.\" You don't know it, but try " "anyway. They laugh hysterically, but admire your bravery, giving you a " "Crystal Ball." msgstr "" "Trafiasz do taboru, w którym odbywa siÄ™ uczta. Cyganie woÅ‚ajÄ… ciÄ™ i mówiÄ…: " "\"Udowodnij, że potrafisz zataÅ„czyć Rama-Buta, a nagrodzimy ciÄ™.\" Nie " "wiesz, o jaki taniec chodzi, ale zgadzasz siÄ™. CaÅ‚y obóz Å›mieje siÄ™ widzÄ…c " "twoje podskoki, ale doceniajÄ…c twÄ… odwagÄ™ wrÄ™czajÄ… ci krysztaÅ‚owÄ… kulÄ™." msgid "Heart of Fire" msgstr "Serce Ognia" msgid "" "The %{name} provides %{count} percent protection from fire, but doubles the " "damage taken from cold." msgstr "" "%{name} zapewnia %{count} procent ochrony od ognia, ale podwaja uszkodzeni " "otrzymane od zimna." msgid "" "You enter a recently burned glade and come upon a Fire Elemental sitting " "atop a rock. It looks up, its flaming face contorted in a look of severe " "pain. It then tosses a glowing object at you. You put up your hands to block " "it, but it passes right through them and sears itself into your chest." msgstr "" "Wchodzisz na Å›wieżo wypalonÄ… łąkÄ™ i widzisz Å»ywioÅ‚aka Ognia, siedzÄ…cego na " "gÅ‚azie. SpoglÄ…da na ciebie z wyrazem ogromnego bólu na twarzy. NastÄ™pnie " "rzuca w ciebie jakimÅ› bÅ‚yszczÄ…cym przedmiotem. Podnosisz rÄ™ce, aby siÄ™ " "osÅ‚onić, ale przedmiot przelatuje ci przez dÅ‚onie i wtapia ci siÄ™ w pierÅ›." msgid "Heart of Ice" msgstr "Serce Lodu" msgid "" "The %{name} provides %{count} percent protection from cold, but doubles the " "damage taken from fire." msgstr "" "%{name} zapewnia %{count} procent ochrony od zimna, ale podwaja uszkodzenia " "otrzymane od ognia." #, fuzzy msgid "" "Suddenly, a biting coldness engulfs your body. You seize up, falling from " "your horse. The pain subsides, but you still feel as if your chest is " "frozen. As you pick yourself up off of the ground, you hear hearty laughter. " "You turn around just in time to see a Frost Giant run off into the woods and " "disappear." msgstr "" "Nagle, caÅ‚e twoje ciaÅ‚o ogarnia lodowate zimno i spadasz z konia. Po chwili " "ból ustÄ™puje, ale czujesz jakby caÅ‚y twój tułów byÅ‚ zamrożony. Gdy podnosisz " "siÄ™ z ziemi, sÅ‚yszysz jowialny Å›miech. Obracasz siÄ™ i widzisz Å›nieżnego " "olbrzyma, który wbiega do lasu i znika ci z oczu." msgid "Helmet of Anduran" msgstr "HeÅ‚m Andurana" msgid "" "You spy a gleaming object poking up out of the ground. You send a member of " "your party over to investigate. He comes back with a golden helmet in his " "hands. You realize that it must be the helmet of the legendary Anduran, the " "only man who was known to wear solid gold armor." msgstr "" "Zauważasz lÅ›niÄ…cy przedmiot wystajÄ…cy z ziemi. WysyÅ‚asz jednego ze swych " "ludzi, aby mu siÄ™ przyjrzaÅ‚. Po chwili wraca, trzymajÄ…c w rÄ™kach zÅ‚oty heÅ‚m. " "Zdajesz sobie sprawÄ™, że musi to być heÅ‚m legendarnego Andurana, jedynego " "znanego czÅ‚owieka, który nosiÅ‚ zbrojÄ™ ze szczerego zÅ‚ota." msgid "Holy Hammer" msgstr "ÅšwiÄ™ty MÅ‚ot" #, fuzzy msgid "" "You come upon a battle where a Paladin has been mortally wounded by a group " "of Zombies. He asks you to take his hammer and finish what he started. As " "you pick it up, it begins to hum, and then everything becomes a blur. The " "Zombies lie dead, the hammer dripping with blood. You strap it to your belt." msgstr "" "Na polu bitwy znajdujesz paladyna, Å›miertelnie zranionego przez grupÄ™ " "zombie. Prosi ciÄ™ o wziÄ™cie jego mÅ‚ota i dokoÅ„czenie tego, co zaczÄ…Å‚. Gdy " "podnosisz mÅ‚ot, rozbrzmiewa dziwnym dźwiÄ™kiem, a po chwili wszystko " "rozmazuje ci siÄ™ przed oczami. Zombie leżą martwe, a z mÅ‚ota kapie krew." msgid "Legendary Scepter" msgstr "Legendarne BerÅ‚o" msgid "The %{name} adds %{count} points to all attributes." msgstr "%{name} dodaje %{count} punktów do wszystkich atrybutów." msgid "" "Upon cresting a small hill, you come upon a ridiculous looking sight. A " "Sprite is attempting to carry a Scepter that is almost as big as it is. " "Trying not to laugh, you ask, \"Need help?\" The Sprite glares at you and " "answers: \"You think this is funny? Fine. You can carry it. I much prefer " "flying anyway.\"" msgstr "" "Wspinasz siÄ™ na wzgórze i zauważasz coÅ› bardzo zabawnego. RusaÅ‚ka próbuje " "unieść berÅ‚o, które jest niemal tak duże jak ona. StarajÄ…c siÄ™ nie wybuchnąć " "Å›miechem, pytasz: \"Może pomóc?\" RusaÅ‚ka spoglÄ…da na ciebie i odpowiada: " "\"Uważasz, że to Å›mieszne? Dobrze. Trzymaj, to dla ciebie.\"" msgid "" "An old seaman tells you a tale of an enchanted masthead that he used in his " "youth to rally his crew during times of trouble. He then hands you a faded " "map that shows where he hid it. After much exploring, you find it stashed " "underneath a nearby dock." msgstr "" "Stary marynarz opowiada ci o zaklÄ™tym topie masztowym, za pomocÄ… którego " "mobilizowaÅ‚ swÄ… zaÅ‚ogÄ™ w przypadku niebezpieczeÅ„stwa. NastÄ™pnie wrÄ™cza ci " "pożółkłą mapÄ™, na której zaznaczyÅ‚ miejsce jego ukrycia. Po dÅ‚ugich " "poszukiwaniach odnajdujesz go na pobliskiej przystani." msgid "Masthead" msgstr "Top Masztowy" #, fuzzy msgid "" "The %{name} boosts the hero's troops' luck and morale by %{count} each in " "sea combat." msgstr "" "%{name} zwiÄ™ksza twoje szczęście i morale o %{count} podczas walki na morzu." msgid "Sphere of Negation" msgstr "Kula Negacji" msgid "The %{name} disables all spell casting, for both sides, in combat." msgstr "%{name} blokuje możliwość rzucania czarów dla obu stron w walce." msgid "" "You stop to help a Peasant catch a runaway mare. To show his gratitude, he " "hands you a tiny sphere. As soon as you grasp it, you feel the magical " "energy drain from your limbs..." msgstr "" "Zatrzymujesz siÄ™ aby pomóc wieÅ›niakowi zÅ‚apać uciekajÄ…cÄ… klacz. ChÅ‚op w " "podziÄ™ce wrÄ™cz ci maleÅ„kÄ… kulÄ™. Kiedy tylko bierzesz jÄ… do rÄ™ki, czujesz jak " "z twych czÅ‚onków uchodzi magiczna energia..." msgid "Staff of Wizardry" msgstr "Kostur Czarodziejstwa" #, fuzzy msgid "The %{name} boosts the hero's spell power by %{count}." msgstr "%{name} zwiÄ™ksza moc magicznÄ… o %{count}" msgid "" "While out scaring up game, your troops find a mysterious staff levitating " "about three feet off of the ground. They hand it to you, and you notice an " "inscription. It reads: \"Brains best brawn and magic beats might. Heed my " "words, and you'll win every fight.\"" msgstr "" "PolujÄ…c na zwierzynÄ™ twoje oddziaÅ‚y znajdujÄ… tajemniczy kostur, unoszÄ…cy siÄ™ " "mniej wiÄ™cej metr nad ziemiÄ… i przynoszÄ… go do ciebie. Zauważasz inskrypcjÄ™, " "która gÅ‚osi: \"Magia lepsza jest od miecza, a od siÅ‚y umysÅ‚ jest ważniejszy. " "PamiÄ™taj o moich sÅ‚owach, a bÄ™dziesz najpotężniejszy.\"" msgid "Sword Breaker" msgstr "Åamacz Mieczy" #, fuzzy msgid "The %{name} increases the hero's defense by %{count} and attack by 1." msgstr "%{name} zwiÄ™ksza twojÄ… obronÄ™ o %{count} i atak o 1." msgid "" "A former Captain of the Guard admires your quest and gives you the enchanted " "Sword Breaker that he relied on during his tour of duty." msgstr "" "ByÅ‚y kapitan straży, peÅ‚en podziwu dla twej misji, wrÄ™cza ci zaklÄ™ty Åamacz " "Mieczy, którego używaÅ‚ w czasie swojej sÅ‚użby." msgid "Sword of Anduran" msgstr "Miecz Andurana" msgid "" "A Troll stops you and says: \"Pay me 5,000 gold, or the Sword of Anduran " "will slay you where you stand.\" You refuse. The troll grabs the sword " "hanging from its belt, screams in pain, and runs away. Picking up the fabled " "sword, you give thanks that half-witted Trolls tend to grab the wrong end of " "sharp objects." msgstr "" "Zatrzymuje ciÄ™ troll, który oznajmia: \"ZapÅ‚ać mi 5000 sztuk zÅ‚ota, albo " "zginiesz od Miecza Andurana.\" Odmawiasz. Troll chwyta za miecz wiszÄ…cy mu u " "pasa, krzyczy z bólu i ucieka. Podnosisz legendarny miecz, dziÄ™kujÄ…c że " "gÅ‚upie trolle czÄ™sto chwytajÄ… ostre przedmioty z niewÅ‚aÅ›ciwej strony." msgid "Spade of Necromancy" msgstr "Szpadel Nekromancji" #, fuzzy msgid "The %{name} gives the hero increased necromancy skill." msgstr "%{name} zwiÄ™ksza twojÄ… umiejÄ™tność nekromancji." msgid "" "A dirty shovel has been thrust into a dirt mound nearby. Upon investigation, " "you discover it to be the enchanted shovel of the Gravediggers, long thought " "lost by mortals." msgstr "" "KtoÅ› wbiÅ‚ brudny szpadel w pobliski kopiec ziemi. Po dokÅ‚adniejszym zbadaniu " "przedmiotu odkrywasz, że jest to zaklÄ™ty szpadel Grabarzy, zaginiony dawno " "temu." msgid "spellBonus|selected by user" msgstr "" msgid "Wood" msgstr "Drewno" msgid "Mercury" msgstr "Rtęć" msgid "Ore" msgstr "Ruda" msgid "Sulfur" msgstr "Siarka" msgid "Crystal" msgstr "KrysztaÅ‚" msgid "Gems" msgstr "Klejnoty" msgid "Gold" msgstr "ZÅ‚oto" msgid "" "There are seven resources in Heroes 2, used to build and improves castles, " "purchase troops and recruit heroes. Gold is the most common, required for " "virtually everything. Wood and ore are used for most buildings. Gems, " "Mercury, Sulfur and Crystal are rare magical resources used for the most " "powerful creatures and buildings." msgstr "" "W Heroes 2 jest siedem surowców, używanych do budowy i ulepszania miast, " "kupowania jednostek i rekrutowania bohaterów. ZÅ‚oto jest najbardziej " "pospolitym surowcem i jest potrzebne praktycznie do wszystkiego. Drewno i " "ruda sÄ… używane do budowy wiÄ™kszoÅ›ci budynków. Klejnoty, rtęć, siarka i " "krysztaÅ‚y sÄ… rzadkimi magicznymi surowcami używanymi do budowy potężnych " "budynków i rekrutowania potężnych istot." msgid "" "Causes a giant fireball to strike the selected area, damaging all nearby " "creatures." msgstr "" "Tworzy wielkÄ… kulÄ™ ognia w wybranym obszarze uszkadzajÄ…c wszystkie pobliskie " "jednostki." msgid "Fireball" msgstr "Kula Ognia" msgid "Fireblast" msgstr "Eksplozja Ognia" msgid "" "An improved version of fireball, fireblast affects two hexes around the " "center point of the spell, rather than one." msgstr "" "Wzmocniona wersja Kuli Ognia, Eksplozja oddziaÅ‚uje na dwa pola wokół punktu " "centralnego czaru, zamiast na jeden." msgid "Causes a bolt of electrical energy to strike the selected creature." msgstr "Uderza w wybrane stworzenie za pomocÄ… energii elektrycznej." msgid "Lightning Bolt" msgstr "BÅ‚yskawica" msgid "Chain Lightning" msgstr "ÅaÅ„cuch Piorunów" #, fuzzy msgid "" "Causes a bolt of electrical energy to strike a selected creature, then " "strike the nearest creature with half damage, then strike the NEXT nearest " "creature with half again damage, and so on, until it becomes too weak to be " "harmful. Warning: This spell can hit your own creatures!" msgstr "" "Uderza w wybrane stworzenie za pomocÄ… energii elektrycznej nastÄ™pnie " "przeskakujÄ…c na kolejne stworzenie z poÅ‚owÄ… uszkodzeÅ„ i tak dalej dopóki " "energia siÄ™ nie wyczerpie.\n" "Uwaga: Czar może uszkodzić twoje stworzenia!" msgid "Teleport" msgstr "Teleportacja" msgid "" "Teleports the creature you select to any open position on the battlefield." msgstr "Teleportuje wybrane stworzenia do dowolnego miejsca na polu walki." msgid "Cure" msgstr "Leczenie" msgid "" "Removes all negative spells cast upon one of your units, and restores up to " "%{count} HP per level of spell power." msgstr "" "Usuwa wszelkie negatywne efekty czarów z wybranej jednostki, i przywraca do " "%{count} punktów życia na poziom Mocy." msgid "Mass Cure" msgstr "Grupowe Leczenie" msgid "" "Removes all negative spells cast upon your forces, and restores up to " "%{count} HP per level of spell power, per creature." msgstr "" "Usuwa wszystkie negatywne efekty czarów z twoich jednostek i przywraca " "%{count} punktów życia na poziom Mocy, każdemu stworzeniu." msgid "Resurrect" msgstr "Wskrzeszenie" msgid "Resurrects creatures from a damaged or dead unit until end of combat." msgstr "" "Wskrzesza stworzenia z uszkodzonej lub martwej jednostki do koÅ„ca walki." msgid "Resurrect True" msgstr "Prawdziwe Wskrzeszenie" msgid "Resurrects creatures from a damaged or dead unit permanently." msgstr "Na staÅ‚e wskrzesza z uszkodzonej lub zniszczonej jednostki." msgid "Haste" msgstr "Przyspieszenie" msgid "Increases the speed of any creature by %{count}." msgstr "ZwiÄ™ksza szybkość wszystkich twoich jednostek o %{count}." msgid "Increases the speed of all of your creatures by %{count}." msgstr "ZwiÄ™ksza szybkość wszystkich twoich stworzeÅ„ o %{count}." msgid "Mass Haste" msgstr "Grupowe Przyspieszenie" msgid "Slows target to half movement rate." msgstr "" "Jednostka, na którÄ… rzucone zostanie zaklÄ™cie, bÄ™dzie mieć o poÅ‚owÄ™ " "zmniejszony ruch." msgid "spell|Slow" msgstr "Spowolnienie" msgid "Mass Slow" msgstr "Grupowe Spowolnienie" msgid "Slows all enemies to half movement rate." msgstr "Wszyscy przeciwnicy majÄ… o poÅ‚owÄ™ zmniejszony ruch." msgid "Clouds the affected creatures' eyes, preventing them from moving." msgstr "OÅ›lepia wybranÄ… jednostkÄ™ nie pozwalajÄ…c jej na ruch." msgid "spell|Blind" msgstr "OÅ›lepienie" msgid "Bless" msgstr "BÅ‚ogosÅ‚awieÅ„stwo" msgid "Causes the selected creatures to inflict maximum damage." msgstr "Sprawia, że wybrane stworzenia zadajÄ… maksymalne obrażenia." msgid "Causes all of your units to inflict maximum damage." msgstr "" "Sprawia, że wszystkie twoje jednostki bÄ™dÄ… zadawać maksymalne obrażenia." msgid "Mass Bless" msgstr "Grupowe BÅ‚ogosÅ‚awieÅ„stwo" msgid "Magically increases the defense skill of the selected creatures." msgstr "Magicznie podnosi współczynnik obrony wybranych stworzeÅ„." msgid "Stoneskin" msgstr "Kamienna Skóra" #, fuzzy msgid "" "Increases the defense skill of the targeted creatures. This is an improved " "version of Stoneskin." msgstr "" "Podnosi współczynnik obrony wybranych stworzeÅ„. ZaklÄ™cie to jest ulepszonÄ… " "wersjÄ… 'Kamiennej Skóry'." msgid "Steelskin" msgstr "Stalowa Skóra" msgid "Causes the selected creatures to inflict minimum damage." msgstr "Sprawia, że wybrane stworzenia zadajÄ… minimalne obrażenia." msgid "Curse" msgstr "KlÄ…twa" msgid "Causes all enemy troops to inflict minimum damage." msgstr "Sprawia, że wszystkie wrogie oddziaÅ‚y zadajÄ… minimalne obrażenia." msgid "Mass Curse" msgstr "Grupowa KlÄ…twa" msgid "Damages all undead in the battle." msgstr "Zadaje obrażenia wszystkim nieumarÅ‚ym na polu bitwy." msgid "Holy Word" msgstr "ÅšwiÄ™te SÅ‚owo" #, fuzzy msgid "" "Damages all undead in the battle. This is an improved version of Holy Word." msgstr "" "Zadaje obrażenia wszystkim nieumarÅ‚ym na polu bitwy. ZaklÄ™cie to jest " "ulepszonÄ… wersjÄ… 'ÅšwiÄ™tego SÅ‚owa'." msgid "Holy Shout" msgstr "ÅšwiÄ™te ZawoÅ‚anie" msgid "Anti-Magic" msgstr "Antymagia" msgid "Prevents harmful magic against the selected creatures." msgstr "Neguje dziaÅ‚anie wszystkich zaklęć na wybranej jednostce." msgid "Dispel Magic" msgstr "Rozproszenie Magii" msgid "Removes all magic spells from a single target." msgstr "Przerywa dziaÅ‚anie wszystkich zaklęć rzuconych na jednostkÄ™." msgid "Mass Dispel" msgstr "Grupowe Rozproszenie" msgid "Removes all magic spells from all creatures." msgstr "Przerywa dziaÅ‚anie wszelkich zaklęć rzuconych na wszystkie jednostki." msgid "Causes a magic arrow to strike the selected target." msgstr "Tworzy magicznÄ… strzałę, która uderza w wybrany cel." msgid "Magic Arrow" msgstr "Magiczna StrzaÅ‚a" msgid "Berserker" msgstr "SzaÅ‚ Bitewny" msgid "Causes a creature to attack its nearest neighbor." msgstr "Sprawia, że jednostka atakuje najbliższy oddziaÅ‚." msgid "Armageddon" msgstr "Armagedon" msgid "" "Holy terror strikes the battlefield, causing severe damage to all creatures." msgstr "" "Na pole bitwy spada Å›wiÄ™ty kataklizm, który zadaje poważne obrażenia " "wszystkim stworzeniom." msgid "Elemental Storm" msgstr "Burza Å»ywiołów" msgid "Magical elements pour down on the battlefield, damaging all creatures." msgstr "" "Nad polem bitwy przetacza siÄ™ burza żywiołów, zadajÄ…ca obrażenia wszystkim " "stworzeniom." msgid "" "A rain of rocks strikes an area of the battlefield, damaging all nearby " "creatures." msgstr "" "W wyznaczone miejsce pola bitwy uderza deszcz meteorów, raniÄ…c wszystkie " "stworzenia w pobliżu." msgid "Meteor Shower" msgstr "Deszcz Meteorów" msgid "Paralyze" msgstr "Paraliż" msgid "The targeted creatures are paralyzed, unable to move or retaliate." msgstr "" "Stworzenia dotkniÄ™te dziaÅ‚aniem tego czaru zostajÄ… sparaliżowane. Nie mogÄ… " "siÄ™ ruszać ani kontratakować." msgid "Hypnotize" msgstr "Hipnoza" msgid "" "Brings a single enemy unit under your control if its hits are less than " "%{count} times the caster's spell power." msgstr "" "Sprawia że wybrany oddziaÅ‚ przeciwnika dostaje siÄ™ pod twojÄ… kontrolÄ™, pod " "warunkiem że jego punkty życia sÄ… %{count} razy mniejsze niż moc rzucajÄ…cego " "zaklÄ™cie." msgid "Cold Ray" msgstr "PromieÅ„ Zimna" msgid "Drains body heat from a single enemy unit." msgstr "Wysysa ciepÅ‚o z pojedynczej wrogiej jednostki." msgid "Cold Ring" msgstr "KrÄ…g Zimna" msgid "" "Drains body heat from all units surrounding the center point, but not " "including the center point." msgstr "" "Wysysa ciepÅ‚o ze wszystkich jednostek wokół punktu rzucenia zaklÄ™cia, z " "pominiÄ™ciem wÅ‚aÅ›nie punktu centralnego." msgid "Disrupting Ray" msgstr "PromieÅ„ OsÅ‚abienia" msgid "Reduces the defense rating of an enemy unit by three." msgstr "Zmniejsza współczynnik obrony wrogiej jednostki o trzy punkty." msgid "Damages all living (non-undead) units in the battle." msgstr "Zadaje obrażenia wszystkim żywym jednostkom na polu bitwy." msgid "Death Ripple" msgstr "Drgawki Åšmierci" msgid "Death Wave" msgstr "Fala Åšmierci" #, fuzzy msgid "" "Damages all living (non-undead) units in the battle. This spell is an " "improved version of Death Ripple." msgstr "" "Zadaje obrażenia wszystkim żywym jednostkom na polu bitwy. ZaklÄ™cie to jest " "ulepszonÄ… wersjÄ… 'Drgawek Åšmierci'." msgid "Dragon Slayer" msgstr "Zabójca Smoków" msgid "Greatly increases a unit's attack skill vs. Dragons." msgstr "Znacznie zwiÄ™ksza umiejÄ™tność ataku jednostki w walce ze smokami." msgid "Blood Lust" msgstr "Żądza Krwi" msgid "Increases a unit's attack skill." msgstr "ZwiÄ™ksza umiejÄ™tność ataku jednostki." msgid "Animate Dead" msgstr "Ożywienie Martwych" msgid "Resurrects creatures from a damaged or dead undead unit permanently." msgstr "" "Na staÅ‚e 'wskrzesza' stworzenia z uszkodzonej lub zniszczonej jednostki " "nieumarÅ‚ych." msgid "Mirror Image" msgstr "Lustrzane Odbicie" #, fuzzy msgid "" "Creates an illusionary unit that duplicates one of your existing units. This " "illusionary unit does the same damages as the original, but will vanish if " "it takes any damage." msgstr "" "Tworzy iluzjÄ™ jednej z twoich istniejÄ…cych jednostek. Walczy tak samo jak " "oryginalna jednostka, ale znika po otrzymaniu jakichkolwiek obrażeÅ„." msgid "Shield" msgstr "Tarcza" msgid "" "Halves damage received from ranged attacks for a single unit. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Zmniejsza o poÅ‚owÄ™ obrażenia, jakie otrzymuje jedno z twoich stworzeÅ„ z " "powodu ataków dystansowych. Nie zmienia obrażeÅ„ otrzymanych od wieżyczek." msgid "Mass Shield" msgstr "Grupowa Tarcza" msgid "" "Halves damage received from ranged attacks for all of your units. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Zmniejsza o poÅ‚owÄ™ obrażenia, jakie otrzymujÄ… wszystkie twoje stworzenia z " "powodu ataków dystansowych. Nie dotyczy obrażeÅ„ otrzymywanych od wieżyczek." msgid "Summon Earth Elemental" msgstr "PrzywoÅ‚anie Å»ywioÅ‚aka Ziemi" msgid "Summons Earth Elementals to fight for your army." msgstr "PrzywoÅ‚uje Å»ywioÅ‚aki Ziemi, które walczÄ… po twojej stronie." msgid "Summon Air Elemental" msgstr "PrzywoÅ‚anie Å»ywioÅ‚aka Powietrza" msgid "Summons Air Elementals to fight for your army." msgstr "PrzywoÅ‚uje Å»ywioÅ‚aki Powietrza, które walczÄ… po twojej stronie." msgid "Summon Fire Elemental" msgstr "PrzywoÅ‚anie Å»ywioÅ‚aka Ognia" msgid "Summons Fire Elementals to fight for your army." msgstr "PrzywoÅ‚uje Å»ywioÅ‚aki Ognia, które walczÄ… po twojej stronie." msgid "Summon Water Elemental" msgstr "PrzywoÅ‚anie Å»ywioÅ‚aka Wody" msgid "Summons Water Elementals to fight for your army." msgstr "PrzywoÅ‚uje Å»ywioÅ‚aki Wody, które walczÄ… po twojej stronie." msgid "Damages castle walls." msgstr "Uszkadza mury zamku." msgid "Earthquake" msgstr "TrzÄ™sienie ziemi" msgid "Causes all mines across the land to become visible." msgstr "Czar ten sprawia, że wszystkie kopalnie w krainie stajÄ… siÄ™ widoczne." msgid "View Mines" msgstr "Pokaż Kopalnie" msgid "Causes all resources across the land to become visible." msgstr "Czar ten sprawia, że wszystkie zasoby w krainie stajÄ… siÄ™ widoczne." msgid "View Resources" msgstr "Pokaż Zasoby" msgid "Causes all artifacts across the land to become visible." msgstr "Czar ten sprawia, że wszystkie artefakty w krainie stajÄ… siÄ™ widoczne." msgid "View Artifacts" msgstr "Pokaż Artefakty" msgid "Causes all towns and castles across the land to become visible." msgstr "" "Czar ten sprawia, że wszystkie miasta i zamki w krainie stajÄ… siÄ™ widoczne." msgid "View Towns" msgstr "Pokaż Miasta" msgid "Causes all Heroes across the land to become visible." msgstr "Czar ten sprawia, że wszyscy bohaterowie w krainie stajÄ… siÄ™ widoczni." msgid "View Heroes" msgstr "Pokaż Bohaterów" msgid "Causes the entire land to become visible." msgstr "Czar ten sprawia, że widoczna staje siÄ™ caÅ‚a kraina." msgid "View All" msgstr "Pokaż Wszystko" msgid "Allows the caster to view detailed information on enemy Heroes." msgstr "" "Pozwala osobie rzucajÄ…cej zaklÄ™cie zobaczyć szczegółowe informacje na temat " "bohaterów wroga." msgid "Summon Boat" msgstr "PrzywoÅ‚anie Åodzi" #, fuzzy msgid "" "Summons the nearest unoccupied, friendly boat to an adjacent shore location. " "A friendly boat is one which you just built or were the most recent player " "to occupy." msgstr "" "PrzywoÅ‚uje najbliższÄ…, niezajÄ™tÄ…, wÅ‚asnÄ… łódź na sÄ…siadujÄ…cy z tobÄ… fragment " "wybrzeża. WÅ‚asna łódź to taka, która zostaÅ‚a przez ciebie zbudowana, lub " "byÅ‚a ostatnio przez ciebie użytkowana." msgid "Allows the caster to magically transport to a nearby location." msgstr "" "Pozwala osobie rzucajÄ…cej zaklÄ™cie magicznie przenieść siÄ™ w inne, pobliskie " "miejsce." msgid "Dimension Door" msgstr "Brama miÄ™dzy Wymiarami" msgid "Returns the caster to any town or castle currently owned." msgstr "" "Pozwala bohaterowi rzucajÄ…cemu zaklÄ™cie przenieść siÄ™ do najbliższego " "wÅ‚asnego miasta lub zamku." msgid "Town Gate" msgstr "Miejska Brama" msgid "" "Returns the hero to the town or castle of choice, provided it is controlled " "by you." msgstr "" "Przenosi bohatera do wybranego zamku lub miasta, znajdujÄ…cego siÄ™ pod twojÄ… " "kontrolÄ…." msgid "Visions" msgstr "Wizje" msgid "" "Visions predicts the likely outcome of an encounter with a neutral army camp." msgstr "" "ZaklÄ™cie to pozwala przewidzieć prawdopodobny wynik starcia z neutralnÄ… " "armiÄ…." msgid "Haunt" msgstr "Nawiedzenie" #, fuzzy msgid "" "Haunts a mine you control with Ghosts. This mine stops producing resources. " "(If I can't keep it, nobody will!)" msgstr "" "Sprawia, że jednÄ… z twych kopalni nawiedzajÄ… Duchy. Kopalnia zaprzestaje " "wydobycia. (JeÅ›li nie mogÄ™ jej mieć, to nikt jej mieć nie bÄ™dzie!)" msgid "Set Earth Guardian" msgstr "Ustawienie Strażnika Ziemi" msgid "Sets Earth Elementals to guard a mine against enemy armies." msgstr "Wzywa Å»ywioÅ‚aki Ziemi, które pilnujÄ… kopalni przed wrogimi armiami." msgid "Set Air Guardian" msgstr "Ustawienie Strażnika Powietrza" msgid "Sets Air Elementals to guard a mine against enemy armies." msgstr "" "Wzywa Å»ywioÅ‚aki Powietrza, które pilnujÄ… kopalni przed wrogimi armiami." msgid "Set Fire Guardian" msgstr "Ustawienie Strażnika Ognia" msgid "Sets Fire Elementals to guard a mine against enemy armies." msgstr "Wzywa Å»ywioÅ‚aki Ognia, które pilnujÄ… kopalni przed wrogimi armiami." msgid "Set Water Guardian" msgstr "Ustawienie Strażnika Wody" msgid "Sets Water Elementals to guard a mine against enemy armies." msgstr "Wzywa Å»ywioÅ‚aki Wody, które pilnujÄ… kopalni przed wrogimi armiami." #, fuzzy msgid "Random Spell" msgstr "Losowa" msgid "Randomly selected spell of any level." msgstr "" #, fuzzy msgid "Random 1st Level Spell" msgstr "Losowy zamek" msgid "Randomly selected 1st level spell." msgstr "" msgid "Random 2nd Level Spell" msgstr "" msgid "Randomly selected 2nd level spell." msgstr "" msgid "Random 3rd Level Spell" msgstr "" msgid "Randomly selected 3rd level spell." msgstr "" msgid "Random 4th Level Spell" msgstr "" msgid "Randomly selected 4th level spell." msgstr "" msgid "Random 5th Level Spell" msgstr "" msgid "Randomly selected 5th level spell." msgstr "" msgid "Petrification" msgstr "Zamiana w KamieÅ„" #, fuzzy msgid "" "Turns the affected creature into stone. A petrified creature receives half " "damage from a direct attack." msgstr "" "Zmienia dotkniÄ™te stworzenie w kamieÅ„. SkamieniaÅ‚a jednostka otrzymuje " "poÅ‚owÄ™ obrażeÅ„ z ataku wrÄ™cz." msgid "You have no Magic Book, so you cannot cast a spell." msgstr "Nie posiadasz KsiÄ™gi Zaklęć, wiÄ™c nie możesz rzucić zaklÄ™cia." msgid "No spell to cast." msgstr "Brak zaklęć." msgid "Your hero has %{point} spell points remaining." msgstr "Twój bohater ma jeszcze %{point} pkt. magii." msgid "View Adventure Spells" msgstr "Obejrzyj zaklÄ™cia podróżne" msgid "View Combat Spells" msgstr "Obejrzyj zaklÄ™cia bojowe" msgid "View previous page" msgstr "Obejrzyj poprzedniÄ… stronÄ™" msgid "View next page" msgstr "Obejrzyj nastÄ™pnÄ… stronÄ™" msgid "Close Spellbook" msgstr "Zamknij KsiÄ™gÄ™ Zaklęć" msgid "View %{spell}" msgstr "Obejrzyj zaklÄ™cie %{spell}" msgid "This spell does %{damage} points of damage." msgstr "To zaklÄ™cie zada %{damage} pkt. obrażeÅ„." msgid "" "This spell summons\n" "%{count} %{monster}." msgstr "" "Ten czar przywoÅ‚uje\n" "%{count} %{monster}" msgid "This spell restores %{hp} HP." msgstr "Ten czar przywróci %{hp} punktów życia." msgid "This spell summons %{count} %{monster} to guard the mine." msgstr "Ten czar przywoÅ‚a %{count} %{monster} do pilnowania kopalni." msgid "The nearest town is %{town}." msgstr "Najbliższym miastem jest %{town}." msgid "This town is occupied by your hero %{hero}." msgstr "To miasto jest zajÄ™te przez twojego bohatera %{hero}." msgid "" "This spell controls up to\n" "%{hp} HP." msgstr "" "ZaklÄ™cie kontroluje do\n" "%{hp} punktów życia." msgid "The ultimate artifact is really the %{name}." msgstr "%{name} to naprawdÄ™ najpotężniejszy artefakt." msgid "The ultimate artifact may be found in the %{name} regions of the world." msgstr "Najpotężniejszy artefakt można znaleźć w %{name} części Å›wiata." msgid "north-west" msgstr "północno-zachodniej" msgid "north" msgstr "północnej" msgid "north-east" msgstr "północno-wschodniej" msgid "west" msgstr "zachodniej" msgid "center" msgstr "centralnej" msgid "east" msgstr "wschodniej" msgid "south-west" msgstr "poÅ‚udniowo-zachodniej" msgid "south" msgstr "poÅ‚udniowej" msgid "south-east" msgstr "poÅ‚udniowo-wschodniej" msgid "The truth is out there." msgstr "GdzieÅ› tam jest prawda." msgid "The dark side is stronger." msgstr "Mroczna strona jest silniejsza." msgid "The end of the world is near." msgstr "Zbliża siÄ™ koniec Å›wiata." msgid "The bones of Lord Slayer are buried in the foundation of the arena." msgstr "KoÅ›ci Lorda Slayera sÄ… zakopane w fundamentach areny." msgid "A Black Dragon will take out a Titan any day of the week." msgstr "Czarny smok pokona Tytana w dowolny dzieÅ„ tygodnia." #, fuzzy msgid "He told her: Yada yada yada... and then she said: Blah, blah, blah..." msgstr "" "On powiedziaÅ‚: Yada yada yada... ona odpowiedziaÅ‚a: Blah, blah, blah..." msgid "An unknown force is being resurrected..." msgstr "Nieznana siÅ‚a jest wskrzeszana…" msgid "" "Check the newest version of the game at\n" "https://github.com/ihhub/\n" "fheroes2/releases" msgstr "" "Możesz pobrać najnowszÄ… wersjÄ™ gry ze strony:\n" "https://github.com/ihhub/\n" "fheroes2/releases" #, fuzzy #~ msgid "Castles" #~ msgstr "Zamek" #, fuzzy #~ msgid "Ocean object" #~ msgstr "Brak obiektu" #, fuzzy #~ msgid "Snow Objects" #~ msgstr "Brak obiektu" #, fuzzy #~ msgid "Desert Objects" #~ msgstr "Namiot na pustyni" #~ msgid "%{race1} %{name1}" #~ msgstr "%{race1} %{name1}" #~ msgid "vs" #~ msgstr "vs" #~ msgid "%{race2} %{name2}" #~ msgstr "%{race2} %{name2}" fheroes2-1.0.12+dfsg/files/lang/pt.po000066400000000000000000006350541456075706000172640ustar00rootroot00000000000000# Brazilian Portuguese translation for fheroes2 # Copyright (C) 2022 fheroes2 team # This file is distributed under the same license as the fheroes2 package. # msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-03 03:27+0000\n" "PO-Revision-Date: 2022-10-25 08:56+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: Brazilian Portuguese \n" "Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" "X-Launchpad-Export-Date: 2010-05-27 09:12+0000\n" "X-Generator: Poedit 3.1.1\n" msgid "" "BATTLE\n" "ONLY" msgstr "" msgid "" "NEW\n" "GAME" msgstr "" msgid "" "SAVE\n" "GAME" msgstr "" msgid "" "LOAD\n" "GAME" msgstr "" msgid "INFO" msgstr "" msgid "QUIT" msgstr "" msgid "CANCEL" msgstr "" msgid "OKAY" msgstr "" msgid "smallerButton|OKAY" msgstr "" msgid "ACCEPT" msgstr "" msgid "DECLINE" msgstr "" msgid "LEARN" msgstr "" msgid "TRADE" msgstr "" msgid "YES" msgstr "" msgid "NO" msgstr "" msgid "EXIT" msgstr "" msgid "smallerButton|EXIT" msgstr "" msgid "DISMISS" msgstr "" msgid "UPGRADE" msgstr "" msgid "RESTART" msgstr "" msgid "HEROES" msgstr "" msgid "" "TOWNS/\n" "CASTLES" msgstr "" msgid "S" msgstr "" msgid "M" msgstr "" msgid "L" msgstr "" msgid "X-L" msgstr "" msgid "ALL" msgstr "" msgid "SELECT" msgstr "" msgid "" "STANDARD\n" "GAME" msgstr "" msgid "" "CAMPAIGN\n" "GAME" msgstr "" msgid "" "MULTI-\n" "PLAYER\n" "GAME" msgstr "" msgid "CONFIG" msgstr "" msgid "" "ORIGINAL\n" "CAMPAIGN" msgstr "" msgid "" "EXPANSION\n" "CAMPAIGN" msgstr "" msgid "HOT SEAT" msgstr "" msgid "2 PLAYERS" msgstr "" msgid "3 PLAYERS" msgstr "" msgid "4 PLAYERS" msgstr "" msgid "5 PLAYERS" msgstr "" msgid "6 PLAYERS" msgstr "" msgid "GIFT" msgstr "" msgid "MAX" msgstr "MÃX" msgid "DIFFICULTY" msgstr "" msgid "VIEW INTRO" msgstr "" msgid "MIN" msgstr "" msgid "RESET" msgstr "" msgid "START" msgstr "" msgid "CASTLE" msgstr "" msgid "TOWN" msgstr "" msgid "" "D\n" "I\n" "S\n" "M\n" "I\n" "S\n" "S" msgstr "" msgid "" "E\n" "X\n" "I\n" "T" msgstr "" msgid "" "C\n" "A\n" "M\n" "P\n" "A\n" "I\n" "G\n" "N" msgstr "" msgid "" "S\n" "T\n" "A\n" "N\n" "D\n" "A\n" "R\n" "D" msgstr "" msgid "Warrior" msgstr "Guerreiro" msgid "Builder" msgstr "Construtor" msgid "Explorer" msgstr "Explorador" msgid "None" msgstr "Nenhum" msgid "" "A few\n" "%{monster}" msgstr "" "Um pouco de\n" "%{monster}" msgid "" "Several\n" "%{monster}" msgstr "" "Alguns\n" "%{monster}" msgid "" "A pack of\n" "%{monster}" msgstr "" "Muito pouco de\n" "%{monster}" msgid "" "Lots of\n" "%{monster}" msgstr "" "Muitos de\n" "%{monster}" msgid "" "A horde of\n" "%{monster}" msgstr "" "Uma horada de\n" "%{monster}" msgid "" "A throng of\n" "%{monster}" msgstr "" "Uma multidão de\n" "%{monster}" msgid "" "A swarm of\n" "%{monster}" msgstr "" "Um enxame de\n" "%{monster}" msgid "" "Zounds...\n" "%{monster}" msgstr "" "Um inferno de\n" "%{monster}" msgid "" "A legion of\n" "%{monster}" msgstr "" "Uma legião de\n" "%{monster}" msgid "army|Few" msgstr "" msgid "army|Several" msgstr "" msgid "army|Pack" msgstr "" msgid "army|Lots" msgstr "" msgid "army|Horde" msgstr "" msgid "army|Throng" msgstr "" msgid "army|Swarm" msgstr "" msgid "army|Zounds" msgstr "" msgid "army|Legion" msgstr "" msgid "All %{race} troops +1" msgstr "Toda tropa de %{race} +1" msgid "Multiple" msgstr "" #, fuzzy msgid "Troops of %{count} alignments -%{penalty}" msgstr "Tropas de Nivel 3 -1" #, fuzzy msgid "Some undead in army -1" msgstr "Algum morto-vivo no grupo -1" msgid "View %{name}" msgstr "Ver %{name}" #, fuzzy msgid "Move the %{name} " msgstr "Ver %{name}" #, fuzzy msgid "Move or right click to redistribute %{name}" msgstr "Mova e clica com o botao direito para redistribuir %{name}" msgid "Combine %{name} armies" msgstr "Juntar os exércitos de %{name}" msgid "Exchange %{name2} with %{name}" msgstr "Trocar %{name2} com %{name}" msgid "Select %{name}" msgstr "Selecione %{name}" #, fuzzy msgid "Cannot move last troop" msgstr "Não é possível mover o último exército à fortaleza" #, fuzzy msgid "Move the %{name}" msgstr "Ver %{name}" msgid "Set Count" msgstr "" #, fuzzy msgid "%{name} destroys half the enemy troops!" msgid_plural "%{name} destroy half the enemy troops!" msgstr[0] "%{name} metade das tropas inimigas!" msgstr[1] "%{name} metade das tropas inimigas!" #, fuzzy msgid "%{name} has turned off the auto battle" msgstr "Retornar à batalha." #, fuzzy msgid "%{name} has turned on the auto battle" msgstr "Retornar à batalha." #, fuzzy msgid "Spell failed!" msgstr "feitiço falhou!" msgid "" "The Sphere of Negation artifact is in effect for this battle, disabling all " "combat spells." msgstr "" msgid "You have already cast a spell this round." msgstr "Você já lançou um feitiço nesse round." #, fuzzy msgid "That spell will have no effect!" msgstr "Esta magia não afetará ninguem!" msgid "You may only summon one type of elemental per combat." msgstr "Você só pode convocar um tipo de Elemento por combate." #, fuzzy msgid "" "There is no open space adjacent to your hero where you can summon an " "Elemental to." msgstr "Não há espaço adjacente aberto para o seu herói lançar um Elemento." msgid "" "The Moat reduces by -%{count} the defense skill of any unit and slows to " "half movement rate." msgstr "" msgid "Speed: %{speed}" msgstr "Rapidez: %{speed}" msgid "Speed" msgstr "Veloc." #, fuzzy msgid "Off" msgstr "off" msgid "On" msgstr "Ligado" msgid "Turn Order" msgstr "" msgid "Auto Spell Casting" msgstr "" msgid "Grid" msgstr "" #, fuzzy msgid "Shadow Movement" msgstr "Continuar a Andar" msgid "Shadow Cursor" msgstr "Sombra do cursor" msgid "Audio" msgstr "" msgid "Settings" msgstr "Configurações" msgid "Configure" msgstr "" #, fuzzy msgid "Hot Keys" msgstr "Hot Seat" #, fuzzy msgid "Damage Info" msgstr "Dano" msgid "Set the speed of combat actions and animations." msgstr "" msgid "Toggle to display the turn order during the battle." msgstr "" msgid "" "Toggle whether or not the computer will cast spells for you when auto combat " "is on. (Note: This does not affect spell casting for computer players in any " "way, nor does it affect quick combat.)" msgstr "" msgid "" "Toggle the hex grid on or off. The hex grid always underlies movement, even " "if turned off. This switch only determines if the grid is visible." msgstr "" msgid "" "Toggle on or off shadows showing where your creatures can move and attack." msgstr "" msgid "" "Toggle on or off a shadow showing the current hex location of the mouse " "cursor." msgstr "" #, fuzzy msgid "Change the audio settings of the game." msgstr "Mudar a resolução do jogo." msgid "Check and configure all the hot keys present in the game." msgstr "" msgid "Toggle to display damage information during the battle." msgstr "" #, fuzzy msgid "Exit this menu." msgstr "Passar a vez desta criatura" msgid "Okay" msgstr "OK" msgid "The enemy has surrendered!" msgstr "O inimigo desistiu!" msgid "Their cowardice costs them %{gold} gold." msgstr "" msgid "The enemy has fled!" msgstr "O inimigo fugiu!" msgid "A glorious victory!" msgstr "Uma vitoria gloriosa!" msgid "For valor in combat, %{name} receives %{exp} experience." msgstr "Pelo valor em combate, %{name} recebe %{exp} de experiência." msgid "The cowardly %{name} flees from battle." msgstr "O covarde %{name} fugiu da batalha." msgid "%{name} surrenders to the enemy, and departs in shame." msgstr "%{name} rende-se ao inimigo, e retira-se na vergonha." msgid "Your forces suffer a bitter defeat, and %{name} abandons your cause." msgstr "" msgid "Your forces suffer a bitter defeat." msgstr "" msgid "Battlefield Casualties" msgstr "Feridos em Guerras" msgid "Attacker" msgstr "Agressor" msgid "Defender" msgstr "Defensor" #, fuzzy msgid "Click to leave the battle results." msgstr "Salvar o jogo atual." #, fuzzy msgid "Click to restart the battle in manual mode." msgstr "Clique para retornar ao menu principal." msgid "Restart" msgstr "" msgid "You have captured an enemy artifact!" msgstr "Vou capturou um artefato inimigo!" msgid "As you reach for the %{name}, it mysteriously disappears." msgstr "" msgid "As your enemy reaches for the %{name}, it mysteriously disappears." msgstr "" #, fuzzy msgid "Necromancy!" msgstr "Necromante:" msgid "" "Practicing the dark arts of necromancy, you are able to raise %{count} of " "the enemy's dead to return under your service as %{monster}." msgstr "" msgid "%{name} the %{race}" msgstr "%{name} a %{race}" #, fuzzy msgid "Captain of %{name}" msgstr "Não pôde arcar com as despesas %{name}" msgid "Attack" msgstr "Ataque" msgid "Defense" msgstr "Defesa" msgid "Spell Power" msgstr "Poder de Magia" msgid "Knowledge" msgstr "Conhecimento" msgid "Morale" msgstr "Moral" msgid "Luck" msgstr "Sorte" msgid "Spell Points" msgstr "Mana" #, fuzzy msgid "Hero's Options" msgstr "Opções do Herói" msgid "Cast Spell" msgstr "Magia" msgid "Retreat" msgstr "Retirada" msgid "Surrender" msgstr "Render-se" msgid "Cancel" msgstr "Cancelar" msgid "Hero Screen" msgstr "Tela do herói" #, fuzzy msgid "Captain's Options" msgstr "Opções do Castelo" #, fuzzy msgid "" "Cast a magical spell. You may only cast one spell per combat round. The " "round is reset when every creature has had a turn." msgstr "" "Lançar uma magia. Você só pode lançar uma magia por rodada. Uma nova rodada " "é iniciada quando toda criatura tem tido a sua vez." msgid "" "Retreat your hero, abandoning your creatures. Your hero will be available " "for you to recruit again, however, the hero will have only a novice hero's " "forces." msgstr "" "Bater em retirada com seu herói, abandonando as suas criaturas. Seu herói " "estará disponível para recrutar mais uma vez, no entanto, o herói terá " "apenas as tropas iniciais." #, fuzzy msgid "" "Surrendering costs gold. However if you pay the ransom, the hero and all of " "his or her surviving creatures will be available to recruit again. The cost " "of surrender is half of the total cost of the non-temporary troops remaining " "in the army." msgstr "" "Render-se custa ouro. No entanto, se você pagar o custo, o herói e todas as " "suas criaturas vivas estarão disponíveis para recrutar novamente." msgid "Open Hero Screen to view full information about the hero." msgstr "Abra tela do herói para ver todas as informações sobre." msgid "Return to the battle." msgstr "Retornar à batalha." msgid "Not enough gold (%{gold})" msgstr "Sem ouro o suficiente (%{gold})" msgid "%{name} states:" msgstr "%{name} estados:" #, fuzzy msgid "Captain of %{name} states:" msgstr "Não pôde arcar com as despesas %{name}" msgid "" "\"I will accept your surrender and grant you and your troops safe passage " "for the price of %{price} gold.\"" msgstr "" msgid "View %{monster} info" msgstr "Ver informação de %{monster}" msgid "Fly %{monster} here" msgstr "" msgid "Move %{monster} here" msgstr "Mover %{monster} daqui" #, fuzzy msgid "Shoot %{monster}" msgstr "" "Atirar em %{monster}Muitos de\n" "%{monster}" msgid "(1 shot left)" msgid_plural "(%{count} shots left)" msgstr[0] "(Resta 1 tiro)" msgstr[1] "(Restam %{count} tiros" msgid "Attack %{monster}" msgstr "Atacar %{monster}" msgid "Turn %{turn}" msgstr "Turno %{turn}" msgid "Teleport here" msgstr "Se teleportar pra cá" msgid "Invalid teleport destination" msgstr "Destino de teleporte inválido" msgid "Cast %{spell} on %{monster}" msgstr "" msgid "Cast %{spell}" msgstr "" msgid "Select spell target" msgstr "" msgid "View Ballista info" msgstr "" msgid "Ballista" msgstr "" #, fuzzy msgid "Enable auto combat" msgstr "Combate automático" msgid "Allows the computer to fight out the battle for you." msgstr "" msgid "Auto Combat" msgstr "Combate Automático" msgid "Customize system options" msgstr "Customizar opções do sistema" msgid "Allows you to customize the combat screen." msgstr "" msgid "System Options" msgstr "Opções do Sistema" #, fuzzy msgid "Skip this unit" msgstr "Passar a vez desta criatura" msgid "Skip" msgstr "" msgid "" "Skips the current creature. The current creature ends its turn and does not " "get to go again until the next round." msgstr "" msgid "View Captain's options" msgstr "" #, fuzzy msgid "View Hero's options" msgstr "Opções do Herói" msgid "View opposing Captain" msgstr "" msgid "View opposing Hero" msgstr "Ver Heróis oponentes" msgid "Hide logs" msgstr "Esconder logs" msgid "Show logs" msgstr "Mostrar logs" msgid "Message Bar" msgstr "" msgid "Shows the results of individual monster's actions." msgstr "" #, fuzzy msgid "Are you sure you want to finish the battle in auto mode?" msgstr "Tem certeza de que quer a retirada?" #, fuzzy msgid "Are you sure you want to enable auto combat?" msgstr "Tem certeza de que quer a retirada?" #, fuzzy msgid "%{name} skip their turn." msgstr "%{name} a %{race}." #, fuzzy msgid "%{attacker} does %{damage} damage." msgid_plural "%{attacker} do %{damage} damage." msgstr[0] "%{tower} causou %{damage} damage." msgstr[1] "%{tower} causou %{damage} damage." msgid "1 creature perishes." msgid_plural "%{count} creatures perish." msgstr[0] "" msgstr[1] "" msgid "1 %{defender} perishes." msgid_plural "%{count} %{defender} perish." msgstr[0] "" msgstr[1] "" #, fuzzy msgid "1 soul is incorporated." msgid_plural "%{count} souls are incorporated." msgstr[0] "A imagem no espelho está criada." msgstr[1] "A imagem no espelho está criada." msgid "1 %{unit} is revived." msgid_plural "%{count} %{unit} are revived." msgstr[0] "" msgstr[1] "" msgid "Moved %{monster}: from [%{src}] to [%{dst}]." msgstr "" msgid "The %{name} resist the spell!" msgstr "%{name} resisitiu ao feitiço!" msgid "%{name} casts %{spell} on the %{troop}." msgstr "" msgid "%{name} casts %{spell}." msgstr "" msgid "The %{spell} does %{damage} damage to one undead creature." msgstr "" msgid "The %{spell} does %{damage} damage to all undead creatures." msgstr "" msgid "The %{spell} does %{damage} damage, %{count} creatures perish." msgstr "" msgid "The %{spell} does %{damage} damage." msgstr "O %{spell} fez %{damage} de dano." msgid "The %{spell} does %{damage} damage to one living creature." msgstr "O %{spell} fez %{damage} de dano a uma criatura viva." msgid "The %{spell} does %{damage} damage to all living creatures." msgstr "" msgid "The %{attacker}' attack blinds the %{target}!" msgstr "" msgid "The %{attacker}' gaze turns the %{target} to stone!" msgstr "" msgid "The %{attacker}' curse falls upon the %{target}!" msgstr "" #, fuzzy msgid "The %{target} are paralyzed by the %{attacker}!" msgstr "%{name} está paralisado pelos Ciclopes!" msgid "The %{attacker} dispel all good spells on your %{target}!" msgstr "" msgid "The %{attacker} cast %{spell} on %{target}!" msgstr "" msgid "Bad luck descends on the %{attacker}." msgstr "" msgid "Good luck shines on the %{attacker}." msgstr "" msgid "High morale enables the %{monster} to attack again." msgstr "" msgid "Low morale causes the %{monster} to freeze in panic." msgstr "" msgid "%{tower} does %{damage} damage." msgstr "%{tower} causou %{damage} damage." msgid "The mirror image is created." msgstr "A imagem no espelho está criada." msgid "The mirror image is destroyed!" msgstr "A imagem no espelho está destruída!" #, fuzzy msgid "Are you sure you want to interrupt the auto battle?" msgstr "Tem certeza de que quer a retirada?" msgid "Error" msgstr "Erro" msgid "No spells to cast." msgstr "" msgid "Are you sure you want to retreat?" msgstr "Tem certeza de que quer a retirada?" #, fuzzy msgid "Retreat disabled" msgstr "Retirada" #, fuzzy msgid "Surrender disabled" msgstr "Render-se" #, fuzzy msgid "Damage: %{max}" msgstr "Dia: %{day}" msgid "Damage: %{min} - %{max}" msgstr "Dano: %{min} - %{max}" #, fuzzy msgid "Perish: %{max}" msgstr "Dia: %{day}" msgid "Perish: %{min} - %{max}" msgstr "" msgid "" "Through eagle-eyed observation, %{name} is able to learn the magic spell " "%{spell}." msgstr "" msgid "Human" msgstr "" msgid "AI" msgstr "" msgid "Start" msgstr "" #, fuzzy msgid "Start the battle." msgstr "Retornar à batalha." #, fuzzy msgid "Exit" msgstr "Sair da Cidade" msgid "Reset" msgstr "" msgid "Reset to default settings." msgstr "" msgid "Please select another hero." msgstr "" #, fuzzy msgid "Set Attack Skill" msgstr "Habilidade de Ataque" #, fuzzy msgid "Set Defense Skill" msgstr "Habilidade de Defesa" #, fuzzy msgid "Set Power Skill" msgstr "Poder de Magia" #, fuzzy msgid "Set Knowledge Skill" msgstr "Conhecimento" #, fuzzy msgid "%{race1} %{name1} vs %{race2} %{name2}" msgstr "Ver %{name}" #, fuzzy msgid "Monsters" msgstr "Melhor Criatura:" msgid "N/A" msgstr "" msgid "Left Turret" msgstr "Torre Esquerda" msgid "Right Turret" msgstr "Torre Direita" msgid "The %{name} fires with the strength of %{count} Archers" msgstr "" msgid "each with a +%{attack} bonus to their attack skill." msgstr "" #, fuzzy msgid "The %{name} is destroyed." msgstr "A imagem no espelho está destruída!" msgid "%{count} %{name} rises from the dead!" msgid_plural "%{count} %{name} rise from the dead!" msgstr[0] "" msgstr[1] "" msgid "Dwarven Alliance" msgstr "" #, fuzzy msgid "Sorceress Guild" msgstr "Guilda de Ladrões" #, fuzzy msgid "Necromancer Guild" msgstr "Necromante:" msgid "Ogre Alliance" msgstr "" msgid "Dwarfbane" msgstr "" msgid "Dragon Alliance" msgstr "" msgid "Elven Alliance" msgstr "" msgid "Kraeger defeated" msgstr "" msgid "Wayward Son" msgstr "" msgid "Uncle Ivan" msgstr "" #, fuzzy msgid "Annexation" msgstr "animação" msgid "Force of Arms" msgstr "" msgid "Save the Dwarves" msgstr "" msgid "Carator Mines" msgstr "" #, fuzzy msgid "Turning Point" msgstr "HP" msgid "scenarioName|Defender" msgstr "" msgid "Corlagon's Defense" msgstr "" msgid "The Crown" msgstr "" msgid "The Gauntlet" msgstr "" msgid "Betrayal" msgstr "" msgid "Final Justice" msgstr "" msgid "" "Roland needs you to defeat the lords near his castle to begin his war of " "rebellion against his brother. They are not allied with each other, so they " "will spend most of their time fighting with one another. Victory is yours " "when you have defeated all of their castles and heroes." msgstr "" msgid "" "The local lords refuse to swear allegiance to Roland, and must be subdued. " "They are wealthy and powerful, so be prepared for a tough fight. Capture all " "enemy castles to win." msgstr "" msgid "" "Your task is to defend the Dwarves against Archibald's forces. Capture all " "of the enemy towns and castles to win, and be sure not to lose all of the " "dwarf towns at once, or the enemy will have won." msgstr "" msgid "" "You will face four allied enemies in a straightforward fight for resource " "and treasure. Capture all of the enemy castles for victory." msgstr "" msgid "" "Your enemies are allied against you and start close by, so be ready to come " "out fighting. You will need to own all four castles in this small valley to " "win." msgstr "" msgid "" "The Sorceress' guild of Noraston has requested Roland's aid against an " "attack from Archibald's allies. Capture all of the enemy castles to win, and " "don't lose Noraston, or you'll lose the scenario. (Hint: There is an enemy " "castle on an island in the ocean.)" msgstr "" msgid "" "Gather as large an army as possible and capture the enemy castle within 8 " "weeks. You are opposed by only one enemy, but must travel a long way to get " "to the enemy castle. Any troops you have in your army at the end of this " "scenario will be with you in the final battle." msgstr "" msgid "" "Find the Crown before Archibald's heroes find it. Roland will need the Crown " "for the final battle against Archibald." msgstr "" msgid "" "Three allied enemies stand before you and victory, including Lord Corlagon. " "Roland is in a castle to the northwest, and you will lose if he falls to the " "enemy. Remember that capturing Lord Corlagon will ensure that he will not " "fight against you in the final scenario." msgstr "" msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Archibald to end the war!" msgstr "" msgid "" "Switching sides leaves you with three castles against the enemy's one. This " "battle will be the easiest one you will face for the rest of the war..." "traitor." msgstr "" msgid "Barbarian Wars" msgstr "" msgid "First Blood" msgstr "" #, fuzzy msgid "Necromancers" msgstr "Necromante:" msgid "Slay the Dwarves" msgstr "" msgid "Country Lords" msgstr "" msgid "Dragon Master" msgstr "" msgid "Rebellion" msgstr "" msgid "Apocalypse" msgstr "" msgid "Greater Glory" msgstr "" msgid "" "King Archibald requires you to defeat the three enemies in this region. They " "are not allied with one another, so they will spend most of their energy " "fighting amongst themselves. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" msgid "" "You must unify the barbarian tribes of the north by conquering them. As in " "the previous mission, the enemy is not allied against you, but they have " "more resources at their disposal. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" msgid "" "Do-gooder wizards have taken the Necromancers' castle. You must retake it to " "achieve victory. Remember that while you start with a powerful army, you " "have no castle and must take one within 7 days, or lose this battle. (Hint: " "The nearest castle is to the southeast.)" msgstr "" msgid "" "The dwarves need conquering before they can interfere in King Archibald's " "plans. Roland's forces have more than one hero and many towns to start with, " "so be ready for attack from multiple directions. You must capture all of the " "enemy towns and castles to claim victory." msgstr "" msgid "" "You must put down a peasant revolt led by Roland's forces. All are allied " "against you, but you have Lord Corlagon, an experienced hero, to help you. " "Capture all enemy castles to win." msgstr "" msgid "" "There are two enemies allied against you in this mission. Both are well " "armed and seek to evict you from their island. Avoid them and capture Dragon " "City to win." msgstr "" msgid "" "Your orders are to conquer the country lords that have sworn to serve " "Roland. All of the enemy castles are unified against you. Since you start " "without a castle, you must hurry to capture one before the end of the week. " "Capture all enemy castles for victory." msgstr "" msgid "" "Find the Crown before Roland's heroes find it. Archibald will need the Crown " "for the final battle against Roland." msgstr "" msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Roland to win the war, and be sure " "not to lose Archibald in the fight!" msgstr "" msgid "Arrow's Flight" msgstr "" msgid "Island of Chaos" msgstr "" msgid "The Abyss" msgstr "" msgid "Uprising" msgstr "" msgid "Aurora Borealis" msgstr "" msgid "Betrayal's End" msgstr "" #, fuzzy msgid "Corruption's Heart" msgstr "Quartel do Capitão" msgid "The Giant's Pass" msgstr "" msgid "" "Subdue the unruly local lords in order to provide the Empire with facilities " "to operate in this region." msgstr "" msgid "" "Eliminate all opposition in this area. Then the first piece of the artifact " "will be yours." msgstr "" msgid "" "The sorceresses to the northeast are rebelling! For the good of the empire " "you must quash their feeble uprising on your way to the mountains." msgstr "" msgid "" "Having prepared for your arrival, Kraeger has arranged for a force of " "necromancers to thwart your quest. You must capture the castle of Scabsdale " "before the first day of the third week, or the Necromancers will be too " "strong for you." msgstr "" msgid "" "The barbarian despot in this area is, as yet, ignorant of your presence. " "Quickly, build up your forces before you are discovered and attacked! Secure " "the region by subduing all enemy forces." msgstr "" msgid "" "The Empire is weak in this region. You will be unable to completely subdue " "all forces in this area, so take what you can before reprisal strikes. " "Remember, your true goal is to claim the Helmet of Anduran." msgstr "" msgid "For the good of the Empire, eliminate Kraeger." msgstr "" msgid "" "At last, you have the opportunity and the facilities to rid the Empire of " "the necromancer's evil. Eradicate them completely, and you will be sung as a " "hero for all time." msgstr "" msgid "Border Towns" msgstr "" msgid "Conquer and Unify" msgstr "" msgid "Crazy Uncle Ivan" msgstr "" msgid "The Wayward Son" msgstr "" msgid "Ivory Gates" msgstr "" msgid "The Elven Lands" msgstr "" msgid "The Epic Battle" msgstr "" msgid "The Southern War" msgstr "" msgid "" "Conquer and unite all the enemy tribes. Don't lose the hero Jarkonas, the " "forefather of all descendants." msgstr "" msgid "" "Your rival, the Kingdom of Harondale, is attacking weak towns on your " "border! Recover from their first strike and crush them completely!" msgstr "" msgid "" "Find your wayward son Joseph who is rumored to be living in the desolate " "lands. Do it before the first day of the third month or it will be of no " "help to your family." msgstr "" msgid "" "Rescue your crazy uncle Ivan. Find him before the first day of the fourth " "month or it will be no help to your kingdom." msgstr "" msgid "" "Destroy the barbarians who are attacking the southern border of your " "kingdom! Recover your fallen towns, and then invade the jungle kingdom. " "Leave no enemy standing." msgstr "" msgid "Retake the castle of Ivory Gates, which has fallen due to treachery." msgstr "" msgid "" "Gain the favor of the elves. They will not allow trees to be chopped down, " "so they will send you wood every 2 weeks. You must complete your mission " "before the first day of the seventh month, or the kingdom will surely fall." msgstr "" msgid "" "This is the final battle against your rival kingdom of Harondale. Eliminate " "everyone, and don't lose the hero Jarkonas VI." msgstr "" msgid "Fount of Wizardry" msgstr "" msgid "Power's End" msgstr "" msgid "The Eternal Scrolls" msgstr "" msgid "The Shrouded Isles" msgstr "" msgid "" "Your mission is to vanquish the warring mages in the magical Shrouded Isles. " "The completion of this task will give you a fighting chance against your " "rivals." msgstr "" msgid "" "The location of the great library has been discovered! You must make your " "way to it, and reclaim the city of Chronos in which it lies." msgstr "" msgid "" "Find the Orb of negation, which is said to be buried in this land. There are " "clues inscribed on stone obelisks which will help lead you to your prize. " "Find the Orb before the first day of the sixth month, or your rivals will " "surely have gotten to the fount before you." msgstr "" msgid "" "You must take control of the castle of Magic, where the fount of wizardry " "lies. Do this and your victory will be supreme." msgstr "" msgid "Blood is Thicker" msgstr "" msgid "King and Country" msgstr "" msgid "Pirate Isles" msgstr "" msgid "Stranded" msgstr "" msgid "" "Capture the town on the island off the southeast shore in order to construct " "a boat and travel back towards the mainland. Do not lose the hero Gallavant." msgstr "" msgid "" "Find and defeat Martine, the pirate leader, who resides in Pirates Cove. Do " "not lose Gallavant or your quest will be over." msgstr "" msgid "" "Eliminate all the other forces who oppose the rule of Lord Alberon. " "Gallavant must not die." msgstr "" msgid "" "Overthrow the entrenched monarchy of Lord Alberon, and claim all the land in " "your name. Gallavant must not die." msgstr "" msgid " bane" msgstr "" msgid " alliance" msgstr "" msgid "Carry-over forces" msgstr "" msgid " bonus" msgstr "" msgid " defeated" msgstr "" msgid " will always run away from your army." msgstr "" msgid " will be willing to join your army." msgstr "" msgid "\"%{artifact}\" artifact will be carried over the scenario." msgstr "" msgid "The army will be carried over the scenario." msgstr "" msgid "The kingdom will have +%{count} %{resource} each day." msgstr "" msgid "\"%{spell}\" spell will be carried over the scenario." msgstr "" msgid "%{hero} can be hired in the scenario." msgstr "" msgid "" "%{hero} has been defeated and will not appear in the subsequent scenarios." msgstr "" msgid "The dwarves recognize their allies and gladly join your forces." msgstr "" msgid "The ogres recognize you as the Dwarfbane and lumber over to join you." msgstr "" msgid "" "The dragons, snarling and growling, agree to join forces with you, their " "'Ally'." msgstr "" msgid "" "As you approach the group of elves, their leader calls them all to " "attention. He shouts to them, \"Who of you is brave enough to join this " "fearless ally of ours?\" The group explodes with cheers as they run to join " "your ranks." msgstr "" msgid "" "The dwarves hail you, \"Any friend of Roland is a friend of ours. You may " "pass.\"" msgstr "" msgid "" "The ogres give you a grunt of recognition, \"Archibald's allies may pass.\"" msgstr "" msgid "" "The dragons see you and call out. \"Our alliance with Archibald compels us " "to join you. Unfortunately you have no room. A pity!\" They quickly scatter." msgstr "" msgid "" "The elves stand at attention as you approach. Their leader calls to you and " "says, \"Let us not impede your progress, ally! Move on, and may victory be " "yours.\"" msgstr "" msgid "\"The Dwarfbane!!!!, run for your lives.\"" msgstr "" msgid "campaignBonus|Animate Dead" msgstr "" msgid "campaignBonus|Chain Lightning" msgstr "" msgid "campaignBonus|Fireblast" msgstr "" msgid "campaignBonus|Mass Curse" msgstr "" msgid "campaignBonus|Mass Haste" msgstr "" msgid "campaignBonus|Mirror Image" msgstr "" msgid "campaignBonus|Resurrect" msgstr "" #, fuzzy msgid "campaignBonus|Steelskin" msgstr "Defensor" msgid "campaignBonus|Summon Earth" msgstr "" msgid "campaignBonus|View Heroes" msgstr "" msgid "campaignBonus|Ballista" msgstr "" msgid "campaignBonus|Black Pearl" msgstr "" msgid "campaignBonus|Caster's Bracelet" msgstr "" #, fuzzy msgid "campaignBonus|Defender Helm" msgstr "Defensor" #, fuzzy msgid "campaignBonus|Breastplate" msgstr "Mercado" msgid "campaignBonus|Dragon Sword" msgstr "" msgid "campaignBonus|Fizbin Medal" msgstr "" msgid "campaignBonus|Foremost Scroll" msgstr "" #, fuzzy msgid "campaignBonus|Gauntlets" msgstr "Mercado" msgid "campaignBonus|Hideous Mask" msgstr "" msgid "campaignBonus|Mage's Ring" msgstr "" msgid "campaignBonus|Major Scroll" msgstr "" msgid "campaignBonus|Medal of Honor" msgstr "" msgid "campaignBonus|Medal of Valor" msgstr "" msgid "campaignBonus|Minor Scroll" msgstr "" msgid "campaignBonus|Nomad Boots" msgstr "" msgid "campaignBonus|Power Axe" msgstr "" msgid "campaignBonus|Spiked Shield" msgstr "" msgid "campaignBonus|Stealth Shield" msgstr "" msgid "campaignBonus|Tax Lien" msgstr "" msgid "campaignBonus|Thunder Mace" msgstr "" msgid "campaignBonus|Traveler's Boots" msgstr "" msgid "campaignBonus|White Pearl" msgstr "" #, fuzzy msgid "campaignBonus|Basic Archery" msgstr "Mercado" #, fuzzy msgid "campaignBonus|Advanced Archery" msgstr "Defensor" #, fuzzy msgid "campaignBonus|Expert Archery" msgstr "Especialista" #, fuzzy msgid "campaignBonus|Basic Ballistics" msgstr "Mercado" #, fuzzy msgid "campaignBonus|Advanced Ballistics" msgstr "Defensor" #, fuzzy msgid "campaignBonus|Expert Ballistics" msgstr "Defensor" #, fuzzy msgid "campaignBonus|Basic Diplomacy" msgstr "Mercado" #, fuzzy msgid "campaignBonus|Advanced Diplomacy" msgstr "Defensor" #, fuzzy msgid "campaignBonus|Expert Diplomacy" msgstr "Mercado" #, fuzzy msgid "campaignBonus|Basic Eagle Eye" msgstr "Mercado" #, fuzzy msgid "campaignBonus|Advanced Eagle Eye" msgstr "Defensor" #, fuzzy msgid "campaignBonus|Expert Eagle Eye" msgstr "Mercado" #, fuzzy msgid "campaignBonus|Basic Estates" msgstr "Mercado" #, fuzzy msgid "campaignBonus|Advanced Estates" msgstr "Mercado" #, fuzzy msgid "campaignBonus|Expert Estates" msgstr "Mercado" #, fuzzy msgid "campaignBonus|Basic Leadership" msgstr "Mercado" #, fuzzy msgid "campaignBonus|Advanced Leadership" msgstr "Defensor" #, fuzzy msgid "campaignBonus|Expert Leadership" msgstr "Defensor" #, fuzzy msgid "campaignBonus|Basic Logistics" msgstr "Mercado" #, fuzzy msgid "campaignBonus|Advanced Logistics" msgstr "Defensor" #, fuzzy msgid "campaignBonus|Expert Logistics" msgstr "Defensor" #, fuzzy msgid "campaignBonus|Basic Luck" msgstr "Mercado" #, fuzzy msgid "campaignBonus|Advanced Luck" msgstr "Defensor" #, fuzzy msgid "campaignBonus|Expert Luck" msgstr "Defensor" #, fuzzy msgid "campaignBonus|Basic Mysticism" msgstr "Mercado" #, fuzzy msgid "campaignBonus|Advanced Mysticism" msgstr "Defensor" #, fuzzy msgid "campaignBonus|Expert Mysticism" msgstr "Defensor" #, fuzzy msgid "campaignBonus|Basic Navigation" msgstr "Escavação" #, fuzzy msgid "campaignBonus|Advanced Navigation" msgstr "Mercado" #, fuzzy msgid "campaignBonus|Expert Navigation" msgstr "Escavação" #, fuzzy msgid "campaignBonus|Basic Necromancy" msgstr "Necromante:" #, fuzzy msgid "campaignBonus|Advanced Necromancy" msgstr "Defensor" #, fuzzy msgid "campaignBonus|Expert Necromancy" msgstr "Necromante:" #, fuzzy msgid "campaignBonus|Basic Pathfinding" msgstr "Mercado" msgid "campaignBonus|Advanced Pathfinding" msgstr "" #, fuzzy msgid "campaignBonus|Expert Pathfinding" msgstr "Mercado" #, fuzzy msgid "campaignBonus|Basic Scouting" msgstr "Defensor" #, fuzzy msgid "campaignBonus|Advanced Scouting" msgstr "Defensor" #, fuzzy msgid "campaignBonus|Expert Scouting" msgstr "Defensor" #, fuzzy msgid "campaignBonus|Basic Wisdom" msgstr "Mercado" #, fuzzy msgid "campaignBonus|Advanced Wisdom" msgstr "Defensor" #, fuzzy msgid "campaignBonus|Expert Wisdom" msgstr "Defensor" msgid "" "The main hero will have \"%{artifact}\" artifact at the start of the " "scenario." msgstr "" msgid "" "The kingdom will receive %{amount} additional %{resource} at the start of " "the scenario." msgstr "" msgid "" "The kingdom will have %{amount} less %{resource} at the start of the " "scenario." msgstr "" msgid "" "The main hero will have %{count} %{monster} at the start of the scenario." msgstr "" msgid "" "The main hero will have \"%{spell}\" spell at the start of the scenario." msgstr "" msgid "The starting race of the scenario will be %{race}." msgstr "" msgid "" "The main hero will have additional %{count} %{skill} at the start of the " "scenario." msgstr "" msgid "The main hero will have %{skill} at the start of the scenario." msgstr "" msgid "Roland" msgstr "" msgid "Archibald" msgstr "" msgid "Price of Loyalty" msgstr "" msgid "Voyage Home" msgstr "" msgid "Wizard's Isle" msgstr "" msgid "Descendants" msgstr "" msgid "The %{building} produces %{monster}." msgstr "A %{building} produz %{monster}." msgid "Requires:" msgstr "Requer:" msgid "Exit this menu without doing anything." msgstr "Sair do menu sem fazer nada." #, fuzzy msgid "Cannot build. You have already built here today." msgstr "Não é possível construir. Já foi construída neste turno." msgid "For this action it is necessary to build a castle first." msgstr "Para esta ação, é necessário primeiro a construir um castelo." msgid "Cannot build %{name} because the castle is too far away from an ocean." msgstr "" msgid "disable build." msgstr "" #, fuzzy msgid "Cannot afford %{name}." msgstr "Não pôde arcar com as despesas %{name}" #, fuzzy msgid "%{name} is already built." msgstr "%{name} já está construída" #, fuzzy msgid "Cannot build %{name}." msgstr "Não é possivel construir %{name}" #, fuzzy msgid "Build %{name}." msgstr "Construir %{name}" #, fuzzy msgid "Blackridge" msgstr "Ponte" msgid "Hillstone" msgstr "" msgid "Pinehurst" msgstr "" msgid "Whiteshield" msgstr "" #, fuzzy msgid "Woodhaven" msgstr "Madeira" #, fuzzy msgid "Blackwind" msgstr "Ferreiro" #, fuzzy msgid "Bloodreign" msgstr "Moral" msgid "Dragontooth" msgstr "" msgid "Greywind" msgstr "" msgid "Portsmith" msgstr "" msgid "Atlantium" msgstr "" msgid "Middle Gate" msgstr "" msgid "Sansobar" msgstr "" msgid "Tundara" msgstr "" msgid "Vulcania" msgstr "" msgid "Baywatch" msgstr "" msgid "Fountainhead" msgstr "" msgid "Vertigo" msgstr "" msgid "Wildabar" msgstr "" msgid "Winterkill" msgstr "" msgid "Brindamoor" msgstr "" msgid "Lakeside" msgstr "" msgid "Nightshadow" msgstr "" msgid "Olympus" msgstr "" msgid "Sandcaster" msgstr "" msgid "Alamar" msgstr "" msgid "Burlock" msgstr "" msgid "Dragadune" msgstr "" msgid "Kalindra" msgstr "" msgid "Xabran" msgstr "" msgid "Algary" msgstr "" msgid "Basenji" msgstr "" #, fuzzy msgid "Blackfang" msgstr "Torre Negra" #, fuzzy msgid "New Dawn" msgstr "Novo Jogo" msgid "Sorpigal" msgstr "" msgid "Avone" msgstr "" msgid "Big Oak" msgstr "" msgid "Chandler" msgstr "" msgid "Erliquin" msgstr "" msgid "Hampshire" msgstr "" #, fuzzy msgid "Antioch" msgstr "animação" msgid "Avalon" msgstr "" msgid "Roc Haven" msgstr "" msgid "South Mill" msgstr "" msgid "Weed Patch" msgstr "" msgid "Brownston" msgstr "" msgid "Hilltop" msgstr "" msgid "Weddington" msgstr "" msgid "Westfork" msgstr "" #, fuzzy msgid "Whittingham" msgstr "Grau" msgid "Cathcart" msgstr "" msgid "Elk's Head" msgstr "" msgid "Roscomon" msgstr "" msgid "Sherman" msgstr "" msgid "Yorksford" msgstr "" #, fuzzy msgid "Blackburn" msgstr "Torre Negra" #, fuzzy msgid "Blacksford" msgstr "Torre Negra" msgid "Burton" msgstr "" msgid "Pig's Eye" msgstr "" msgid "Viper's Nest" msgstr "" msgid "Fenton" msgstr "" msgid "Lankershire" msgstr "" msgid "Lombard" msgstr "" msgid "Timberhill" msgstr "" msgid "Troy" msgstr "" msgid "Forder Oaks" msgstr "" msgid "Meramec" msgstr "" msgid "Quick Silver" msgstr "" msgid "Westmoor" msgstr "" msgid "Willow" msgstr "" msgid "Corackston" msgstr "" msgid "Sheltemburg" msgstr "" msgid "Cannot recruit - you already have a Hero in this town." msgstr "Impossível recrutar - você já possui um herói na cidade." msgid "Cannot recruit - you have too many Heroes." msgstr "Impossível recrutar - você tem muitos heróis." msgid "Cannot afford a Hero" msgstr "Não pôde arcar com a despesa do herói" msgid "There is no room in the garrison for this army." msgstr "" msgid "Fortifications" msgstr "Fortificações" msgid "Farm" msgstr "Fazenda" msgid "Thatched Hut" msgstr "Cabana Elaborada" msgid "Archery Range" msgstr "Campo de Arqueiros" msgid "Upg. Archery Range" msgstr "Upg. Campo de Arqueiros++" msgid "Blacksmith" msgstr "Ferreiro" msgid "Upg. Blacksmith" msgstr "Upg. Ferreiro++" msgid "Armory" msgstr "Armaria" msgid "Upg. Armory" msgstr "Upg. Armoria++" msgid "Jousting Arena" msgstr "Arena de Lanceiros" msgid "Upg. Jousting Arena" msgstr "Upg. Arena de Lanceiros++" msgid "Cathedral" msgstr "Catedral" msgid "Upg. Cathedral" msgstr "Upg. Catedral++" msgid "Coliseum" msgstr "Coliseu" msgid "Garbage Heap" msgstr "Acúmulo de Lixo" msgid "Hut" msgstr "Cabana" msgid "Stick Hut" msgstr "Cabana de Pau" msgid "Upg. Stick Hut" msgstr "Upg. Cabana de Pau++" msgid "Den" msgstr "Toca" msgid "Adobe" msgstr "Adobe" msgid "Upg. Adobe" msgstr "Upg. Adobe++" msgid "Bridge" msgstr "Ponte" msgid "Upg. Bridge" msgstr "Upg. Ponte++" msgid "Pyramid" msgstr "Pirâmide" msgid "Rainbow" msgstr "Arco-Ãris" msgid "Crystal Garden" msgstr "Jardim de Cristal" msgid "Treehouse" msgstr "Casa na Ãrvore" msgid "Cottage" msgstr "Chalé" msgid "Upg. Cottage" msgstr "Upg. Chalé++" msgid "Stonehenge" msgstr "Stonehenge" msgid "Upg. Stonehenge" msgstr "Upg. Stonehenge++" msgid "Fenced Meadow" msgstr "Campo Cercado" #, fuzzy msgid "sorceress|Red Tower" msgstr "Torre Vermelha" msgid "Dungeon" msgstr "Calabouço" msgid "Waterfall" msgstr "Cachoeira" msgid "Cave" msgstr "Caverna" msgid "Crypt" msgstr "Cripta" msgid "Nest" msgstr "Ninho" msgid "Maze" msgstr "Labirinto" msgid "Upg. Maze" msgstr "Upg. Labirinto++" msgid "Swamp" msgstr "Pântano" msgid "Green Tower" msgstr "Torre Verde" #, fuzzy msgid "warlock|Red Tower" msgstr "Torre Vermelha" msgid "Black Tower" msgstr "Torre Negra" msgid "Library" msgstr "Biblioteca" msgid "Orchard" msgstr "Pomar" msgid "Habitat" msgstr "Habitat" msgid "Pen" msgstr "Curral" msgid "Foundry" msgstr "Fundição" msgid "Upg. Foundry" msgstr "Upg. Fundição++" msgid "Cliff Nest" msgstr "Ninho no Penhasco" msgid "Ivory Tower" msgstr "Torre de Marfim" msgid "Upg. Ivory Tower" msgstr "Upg. Torre de Marfim++" msgid "Cloud Castle" msgstr "Castelo nas Nuvens" msgid "Upg. Cloud Castle" msgstr "Upg. Castelo nas Nuvens++" msgid "Storm" msgstr "Tempestade" msgid "Skull Pile" msgstr "Pilha de Caveiras" msgid "Excavation" msgstr "Escavação" msgid "Graveyard" msgstr "Cemitério" msgid "Upg. Graveyard" msgstr "Upg. Cemitério++" msgid "Upg. Pyramid" msgstr "Upg. Pirâmide++" msgid "Mansion" msgstr "Mansão" msgid "Upg. Mansion" msgstr "Upg. Mansão++" msgid "Mausoleum" msgstr "Mausoléu" msgid "Upg. Mausoleum" msgstr "Upg. Mausoléu++" msgid "Laboratory" msgstr "Laboratório" msgid "Shrine" msgstr "Abside" msgid "" "The Fortifications increase the toughness of the walls, increasing the " "number of turns it takes to knock them down." msgstr "" "As Fortificações aumentar a resistência das paredes do castelo, aumentando o " "número de rodadas que leva para derrubá-los." msgid "The Farm increases production of Peasants by %{count} per week." msgstr "" msgid "" "The Coliseum provides inspiring spectacles to defending troops, raising " "their morale by two during combat." msgstr "" "O Coliseu oferece espetáculos inspiradores para as tropas de defesa do " "castelo, levantando sua moral por em 2 (dois) durante o combate." msgid "The Garbage Heap increases production of Goblins by %{count} per week." msgstr "" msgid "The Rainbow increases the luck of the defending units by two." msgstr "" "O Arco-Ãris aumenta a sorte das tropas de defesa do castelo em por 2 (dois)." msgid "" "The Crystal Garden increases production of Sprites by %{count} per week." msgstr "" msgid "The Dungeon increases the income of the town by %{count} gold per day." msgstr "" msgid "The Waterfall increases production of Centaurs by %{count} per week." msgstr "" msgid "" "The Library increases the number of spells in the Guild by one for each " "level of the guild." msgstr "" "A Biblioteca aumenta o número de magias na Corporação dos Magos em um para " "cada nível." msgid "The Orchard increases production of Halflings by %{count} per week." msgstr "" msgid "The Storm adds +2 to the power of spells of a defending spell caster." msgstr "A Tempestade adiciona +2 ao Poder de Magia para defensor do castelo." msgid "The Skull Pile increases production of Skeletons by %{count} per week." msgstr "" msgid "Thieves' Guild" msgstr "Guilda de Ladrões" msgid "Tavern" msgstr "Taverna" msgid "Shipyard" msgstr "Estaleiro" msgid "Well" msgstr "Poço" msgid "Statue" msgstr "Estátua" msgid "Marketplace" msgstr "Mercado" msgid "Moat" msgstr "Fosso" msgid "Castle" msgstr "Castelo" msgid "Tent" msgstr "Tenda" msgid "Captain's Quarters" msgstr "Quartel do Capitão" msgid "Mage Guild, Level 1" msgstr "Corporação dos Magos, Nível 1" msgid "Mage Guild, Level 2" msgstr "Corporação dos Magos, Nível 2" msgid "Mage Guild, Level 3" msgstr "Corporação dos Magos, Nível 3" msgid "Mage Guild, Level 4" msgstr "Corporação dos Magos, Nível 4" msgid "Mage Guild, Level 5" msgstr "Corporação dos Magos, Nível 5" msgid "" "The Shrine increases the necromancy skill of all your necromancers by 10 " "percent." msgstr "" "A Abside aumenta a habilidade de Necromancia para todos os necromantes em " "10%." #, fuzzy msgid "" "The Thieves' Guild provides information on enemy players. Thieves' Guilds " "can also provide scouting information on enemy towns. Additional Guilds " "provide more information." msgstr "" "A Guilda de Ladrões fornece informações sobre os jogadores inimigo. Porém, " "também pode fornecer informações suas para para jogadores inimigos." msgid "The Tavern increases morale for troops defending the castle." msgstr "A taverna aumenta a moral das tropas que estão defendendo o castelo." msgid "The Shipyard allows ships to be built." msgstr "O estaleiro permite à construir barcos." msgid "" "The Well increases the growth rate of all dwellings by %{count} creatures " "per week." msgstr "" msgid "The Statue increases the town's income by %{count} gold per day." msgstr "" msgid "The Left Turret provides extra firepower during castle combat." msgstr "" "A Torre Esquerda prevê o poder de fogo extra durante o combate no castelo." msgid "The Right Turret provides extra firepower during castle combat." msgstr "" "A Torre Direita prevê o poder de fogo extra durante o combate no castelo." msgid "" "The Marketplace can be used to convert one type of resource into another. " "The more marketplaces you control, the better the exchange rate." msgstr "" "O mercado pode ser usado para converter um tipo de recurso em outro. Quanto " "mais mercados você controla, melhor será a taxa de câmbio." msgid "" "The Moat slows attacking units. Any unit entering the moat must end its turn " "there and becomes more vulnerable to attack." msgstr "" "O Fosso retarda unidades de ataque. Qualquer unidade que entrar no fosso " "deve terminar seu turno lá e se torna mais vulnerável a ataques." msgid "" "The Castle improves the town's defense and increases its income to %{count} " "gold per day." msgstr "" msgid "" "The Tent provides workers to build a castle, provided the materials and the " "gold are available." msgstr "" "A Tenda oferece aos trabalhadores construir um castelo, desde que os " "materiais eo ouro estejam disponíveis." msgid "" "The Captain's Quarters provides a captain to assist in the castle's defense " "when no hero is present." msgstr "" "O Quartel do Capitão fornece um capitão para ajudar na defesa do castelo, " "quando o herói não está presente." msgid "" "The Mage Guild allows heroes to learn spells and replenish their spell " "points." msgstr "" "A Comporação de Magos permite heróis a aprender magia e reabastecer sua mana." msgid "Recruit %{name}" msgstr "Recruta %{name}" msgid "Month: %{month}, Week: %{week}, Day: %{day}" msgstr "" msgid "" "You must purchase a spell book to use the mage guild, but you currently have " "no room for a spell book. Try giving one of your artifacts to another hero." msgstr "" #, fuzzy msgid "Click to show next town." msgstr "Mostrar proxima cidade" msgid "Show next town" msgstr "Mostrar proxima cidade" #, fuzzy msgid "Click to show previous town." msgstr "Mostrar cidade anterior" msgid "Show previous town" msgstr "Mostrar cidade anterior" msgid "This town may not be upgraded to a castle." msgstr "Esta cidade não pode ser transformada em castelo." msgid "Town" msgstr "Cidade" #, fuzzy msgid "Exit Castle" msgstr "Sair do Castelo" #, fuzzy msgid "Exit Town" msgstr "Sair da Cidade" #, fuzzy msgid "Show Income" msgstr "Renda:" msgid "View Hero" msgstr "Ver Herói" #, fuzzy msgid "The above spells are available here." msgstr "As magias acima foram adicionadas ao seu livro de magias." #, fuzzy msgid "The spells the hero can learn have been added to their book." msgstr "As magias acima foram adicionadas ao seu livro de magias." msgid "A generous tip for the barkeep yields the following rumor:" msgstr "A gorjeta generosa para o barman rende o boato que se segue:" msgid "Recruit Hero" msgstr "Recrutar Herói" #, fuzzy msgid "%{name} is a level %{value} %{race} " msgstr "%{name} a %{race}" msgid "with %{count} artifacts." msgstr "" #, fuzzy msgid "with 1 artifact." msgstr "Não é possível mover o último exército à fortaleza" msgid "without artifacts." msgstr "" msgid "Recruit %{name} the %{race}" msgstr "Recrutar %{name} da raça %{race}" #, fuzzy msgid "" "'Spread' combat formation spreads the castle's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "Formação de combate \"Espalhada\" espalha seus exércitos a partir do topo " "ao fundo do campo de batalha, com pelo menos um espaço vazio entre cada " "exército." #, fuzzy msgid "" "'Grouped' combat formation bunches the castle's units together in the center " "of the castle's side of the battlefield." msgstr "" "Formação de combate \"Agrupada\" seu exército fica junto no centro da linha " "inicial do campo de batalha." msgid "Spread Formation" msgstr "Formação Espalhada" msgid "Grouped Formation" msgstr "Formação Agrupada" msgid "Set garrison combat formation to 'Spread'" msgstr "Definir formação de combate na Fortaleza para \"Espalhada\"" msgid "Set garrison combat formation to 'Grouped'" msgstr "Definir formação de combate na Fortaleza para \"Agrupada\"" #, fuzzy msgid "Exit Castle Options" msgstr "Opções do Castelo" msgid "Castle Options" msgstr "Opções do Castelo" msgid "Not enough resources to recruit creatures." msgstr "" msgid "You are unable to recruit at this time, your ranks are full." msgstr "" msgid "No creatures available for purchase." msgstr "" #, fuzzy msgid "Recruit Creatures" msgstr "Recrutar Herói" #, fuzzy msgid "Max" msgstr "máximo" #, fuzzy msgid "Hire all creatures in the town." msgstr "Perder todos os seus heróis e das cidades." msgid "Available" msgstr "Disponível" msgid "Town Population Information and Statistics" msgstr "Informações da População da Cidade e Estatísticas" msgid "Damage" msgstr "Dano" msgid "HP" msgstr "HP" msgid "Growth" msgstr "Produção" msgid "week" msgstr "semana" #, fuzzy msgid "View World" msgstr "Ver Herói" msgid "View the entire world." msgstr "Ver o mundo inteiro." msgid "Puzzle" msgstr "" msgid "View the obelisk puzzle." msgstr "Ver enigma do obelisco." #, fuzzy msgid "Scenario Information" msgstr "Formação Espalhada" msgid "View information on the scenario you are currently playing." msgstr "Veja informações sobre o cenário que você está jogando atualmente." msgid "Dig for the Ultimate Artifact." msgstr "Escavar em busca do Artefato Supremo." msgid "Digging" msgstr "" msgid "Arena" msgstr "Arena" #, fuzzy msgid "" "You enter the arena and face a pack of vicious lions. You handily defeat " "them, to the wild cheers of the crowd. Impressed by your skill, the aged " "trainer of gladiators agrees to train you in a skill of your choice." msgstr "" "Você entra na arena e enfrenta um bando de leões ferozes. Você com agilidade " "derrotá-los, para os aplausos da multidão selvagem. Impressionado com sua " "habilidade, o velho treinador de gladiadores concorda em treiná-lo em uma " "habilidade de sua escolha." msgid "Attack Skill" msgstr "Habilidade de Ataque" msgid "Defense Skill" msgstr "Habilidade de Defesa" msgid "Shots" msgstr "Tiros" msgid "Shots Left" msgstr "Tiros restantes" msgid "Hit Points" msgstr "HP" msgid "Hit Points Left" msgstr "HP restantes" msgid "You can't afford to upgrade your troops!" msgstr "" msgid "" "Your troops can be upgraded, but it will cost you dearly. Do you wish to " "upgrade them?" msgstr "" msgid "Are you sure you want to dismiss this army?" msgstr "" msgid "Upgrade" msgstr "" msgid "Upgrade your troops." msgstr "" msgid "Dismiss" msgstr "" msgid "Dismiss this army." msgstr "" msgid "" "A group of %{monster} with a desire for greater glory wish to join you.\n" "Do you accept?" msgstr "" "Um grupo de %{monster} que procura uma grande gloria quer se juntar a você.\n" "Você aceita?" #, fuzzy msgid "Followers" msgstr "Poder de Magia" #, fuzzy msgid "" "The %{monster} is swayed by your diplomatic tongue, and offers to join your " "army for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "A criatura é influenciada pela sua língua diplomática, e se oferece para " "participar do seu exército pela soma de %{gold} de outro.\n" "Você aceita?" msgid "" "The creatures are swayed by your diplomatic\n" "tongue, and make you an offer:\n" "\n" msgstr "" "As criaturas são seduzidas por sua língua diplomática,\n" "e fazer-lhe uma oferta:\n" "\n" msgid "" "%{offer} of the %{total} %{monster} will join your army, and the rest will " "leave you alone, for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "%{offer} do %{total} %{monster} vai juntar o seu exército, eo resto vai " "deixá-lo sozinho,pela soma de %{gold} gold.\n" "Você aceita?" msgid "" "All %{offer} of the %{monster} will join your army for the sum of %{gold} " "gold.\n" "Do you accept?" msgstr "" "Todos %{offer} dos %{monster} vão se juntar ao seu exértico pela soma de " "%{gold} ouro.\n" "Você Aceita?" msgid "(Rate: %{percent})" msgstr "" msgid "off" msgstr "off" #, fuzzy msgid "Music" msgstr "música" msgid "Effects" msgstr "" msgid "MIDI" msgstr "" #, fuzzy msgid "MIDI Expansion" msgstr "Mansão" msgid "External" msgstr "" msgid "Music Type" msgstr "" msgid "3D Audio" msgstr "" msgid "Toggle ambient music level." msgstr "" msgid "Toggle foreground sounds level." msgstr "" msgid "Change the type of music." msgstr "" msgid "Toggle 3D effects of foreground sounds." msgstr "" msgid "Build a new ship:" msgstr "Construir um novo banrco:" msgid "Resource cost:" msgstr "Custo de recursos:" msgid "Total: " msgstr "" msgid "Need: " msgstr "" msgid "Load Game" msgstr "Carregar Jogo" msgid "No save files to load." msgstr "Sem arquivos salvos para carregar." msgid "New Game" msgstr "Novo Jogo" msgid "Start a single or multi-player game." msgstr "Iniciar um jogo em modo sozinho ou multi-jogadores." msgid "Load a previously saved game." msgstr "Carregar um jogo salvo anteriormente." #, fuzzy msgid "Save Game" msgstr "Novo Jogo" msgid "Save the current game." msgstr "Salvar o jogo atual." msgid "Quit" msgstr "Sair" #, fuzzy msgid "Quit out of Heroes of Might and Magic II." msgstr "Sair do Jogo & retornar ao sistema operacional." msgid "Language" msgstr "Idioma" msgid "Graphics" msgstr "" #, fuzzy msgid "Black & White" msgstr "Ponte" #, fuzzy msgid "Mouse Cursor" msgstr "Sombra do cursor" msgid "Color" msgstr "" msgid "Text Support" msgstr "" msgid "Change the language of the game." msgstr "Mudar o idioma do jogo." msgid "Select Game Language" msgstr "Selecione o idioma do jogo" #, fuzzy msgid "Change the graphics settings of the game." msgstr "Mudar a resolução do jogo." msgid "Toggle colored cursor on or off. This is only an aesthetic choice." msgstr "" msgid "" "Toggle text support mode to output extra information about windows and " "events in the game." msgstr "" msgid "" "Map\n" "Difficulty" msgstr "" "Dificuldade\n" "do Mapa" msgid "" "Game\n" "Difficulty" msgstr "" "Dificuldade\n" "do Jogo" msgid "Rating" msgstr "Grau" msgid "Map Size" msgstr "" "Tamano\n" "do Mapa" msgid "Opponents" msgstr "Oponentes" msgid "Class" msgstr "Classes" msgid "" "Victory\n" "Conditions" msgstr "" "Condições\n" "de\n" "Vitória" msgid "" "Loss\n" "Conditions" msgstr "" "Condições\n" "de\n" "Derrota" msgid "First select recipients!" msgstr "" msgid "You cannot select %{resource}!" msgstr "Você não pode selecionar %{resource}!" msgid "Select count %{resource}:" msgstr "" msgid "Select Recipients" msgstr "Selecione os recipientes" msgid "Your Funds" msgstr "" msgid "Planned Gift" msgstr "" #, fuzzy msgid "Gift from %{name}" msgstr "Recruta %{name}" msgid "Resolution" msgstr "Resolução" msgid "Fullscreen" msgstr "" msgid "window|Mode" msgstr "" msgid "Windowed" msgstr "" msgid "V-Sync" msgstr "" #, fuzzy msgid "on" msgstr "Nenhum" msgid "FPS" msgstr "" #, fuzzy msgid "System Info" msgstr "Opções do Sistema" msgid "Change the resolution of the game." msgstr "Mudar a resolução do jogo." msgid "Select Game Resolution" msgstr "Selecione a resolução do jogo" msgid "Toggle between fullscreen and windowed modes." msgstr "" msgid "" "The V-Sync option can be enabled to resolve flickering issues on some " "monitors." msgstr "" msgid "Show extra information such as FPS and current time." msgstr "" msgid "Hotkey: " msgstr "" msgid "Category: " msgstr "" msgid "Event: " msgstr "" msgid "Hot Keys:" msgstr "" msgid "Evil" msgstr "Mal" msgid "Good" msgstr "Bom" #, fuzzy msgid "Interface Type" msgstr "Aparência" #, fuzzy msgid "Hide" msgstr "Ponte" #, fuzzy msgid "Show" msgstr "Neve" msgid "Interface" msgstr "Aparência" msgid "Slow" msgstr "" msgid "Normal" msgstr "" msgid "Fast" msgstr "" msgid "Very Fast" msgstr "" msgid "Scroll Speed" msgstr "" msgid "Toggle the type of interface you want to use." msgstr "" msgid "Toggle interface visibility." msgstr "" msgid "Toggle colored cursor on or off. This is only an esthetic choice." msgstr "" msgid "Sets the speed at which you scroll the window." msgstr "" msgid "Select Game Language:" msgstr "Escolha o idioma do jogo:" #, fuzzy msgid "Click to choose the selected language." msgstr "Salvar o jogo atual." msgid "%{name} has gained a level." msgstr "" msgid "%{skill} +1" msgstr "" msgid "You have learned %{skill}." msgstr "Você aprendeu ${skill}." msgid "" "%{name} has gained a level.\n" "\n" "%{skill} +1" msgstr "" msgid "" "You may learn either:\n" "%{skill1}\n" "or\n" "%{skill2}" msgstr "" msgid "" "Please inspect our fine wares. If you feel like offering a trade, click on " "the items you wish to trade with and for." msgstr "" "Por favor consultar nossas mercadorias. Se você se sentir a vontade com a " "oferta, clique sobre os itens que você deseja negociar." msgid "" "You have received quite a bargain. I expect to make no profit on the deal. " "Can I interest you in any of my other wares?" msgstr "" "Você recebeu uma real pechincha. Eu espero não fazer nenhum lucro na " "transação. Interessa em quaisquer das minhas outras mercadorias?" msgid "I can offer you %{count} for 1 unit of %{resfrom}." msgstr "Eu posso oferecer à você %{count} por 1 unidade de %{resfrom}." msgid "I can offer you 1 unit of %{resto} for %{count} units of %{resfrom}." msgstr "" "Eu posso oferecer a você 1 unidade de %{resto} por %{count} unidade de " "%{resfrom}." #, fuzzy msgid "Min" msgstr "mínimo" msgid "Qty to trade" msgstr "Qtd a trocar" msgid "Trading Post" msgstr "" msgid "Your Resources" msgstr "Seus Recursos" msgid "Available Trades" msgstr "Trocas Disponíveis" msgid "n/a" msgstr "n/d" msgid "guarded by %{count} %{monster}" msgstr "" msgid "guarded by " msgstr "" msgid "(available: %{count})" msgstr "" #, fuzzy msgid "(empty)" msgstr "Vazio" msgid "already learned" msgstr "já aprendeu" #, fuzzy msgid "treeOfKnowledge|free" msgstr "Conhecimento" #, fuzzy msgid "already claimed" msgstr "já aprendeu" msgid "not claimed" msgstr "" msgid "already knows this skill" msgstr "já sabe essa habilidade" msgid "already has max skills" msgstr "já tem o máximo de habilidades" msgid "(already visited)" msgstr "(Já Visitado)" msgid "(not visited)" msgstr "(Sem Visita)" #, fuzzy msgid "%{color} Barrier" msgstr "Turno do jogador %{color}" #, fuzzy msgid "(tent visited)" msgstr "(Sem Visita)" #, fuzzy msgid "%{color} Tent" msgstr "Turno do jogador %{color}" #, fuzzy msgid "Road" msgstr "Modo Estrada" msgid "(digging ok)" msgstr "" msgid "(no digging)" msgstr "" msgid "penalty: %{cost}" msgstr "" msgid "Defenders:" msgstr "Defensores:" msgid "Unknown" msgstr "" #, fuzzy msgid "%{name} (Level %{level})" msgstr "%{name} a %{race}" #, fuzzy msgid "Attack:" msgstr "Ataque" #, fuzzy msgid "Defense:" msgstr "Defesa" #, fuzzy msgid "Spell Power:" msgstr "Poder de Magia" #, fuzzy msgid "Knowledge:" msgstr "Conhecimento" #, fuzzy msgid "Spell Points:" msgstr "Mana" #, fuzzy msgid "Move Points:" msgstr "Mana" #, fuzzy msgid "Uncharted Territory" msgstr "Território sem Lei" msgid "Cost per troop:" msgstr "" msgid "Available: %{count}" msgstr "Disponível: %{count}" msgid "Number to buy:" msgstr "Qtd. à Comprar:" msgid "Recruit selected monsters." msgstr "" msgid "Select maximum monsters to be recruited." msgstr "" msgid "Select only 1 monster to be recruited." msgstr "" msgid "Select Game Resolution:" msgstr "Selecione a resolução do jogo:" msgid "Click to apply the selected resolution." msgstr "" #, fuzzy msgid "Click to apply the entered text." msgstr "Salvar o jogo atual." msgid "Click to open the Virtual Keyboard dialog." msgstr "" msgid "Open Virtual Keyboard" msgstr "" msgid "How many troops to move?" msgstr "Mover quantas tropas?" msgid "Fast separation into slots:" msgstr "" msgid "Map: " msgstr "" msgid "" "\n" "\n" "Month: " msgstr "" msgid ", Week: " msgstr "" msgid ", Day: " msgstr "" msgid "" "\n" "\n" "Location: " msgstr "" #, fuzzy msgid "Click to save the current game." msgstr "Salvar o jogo atual." #, fuzzy msgid "Click to load a previously saved game." msgstr "Carregar um jogo salvo anteriormente." msgid "Are you sure you want to delete file:" msgstr "Você tem certeza que quer deletar este arquivo:" msgid "Warning!" msgstr "Aviso!" msgid "File to Save:" msgstr "Arquivo a Salvar:" msgid "File to Load:" msgstr "Arquivo a Carregar:" msgid "Accept the choice made." msgstr "Aceitar a escolha feita." #, fuzzy msgid "%{color} %{race} hero" msgstr "Turno do jogador %{color}" msgid "Terrain object" msgstr "" #, fuzzy msgid "Select Skill:" msgstr "Habilidade de Defesa" #, fuzzy msgid "Select Spell:" msgstr "Magia" #, fuzzy msgid "Select Artifact:" msgstr "Artefato" #, fuzzy msgid "Select Monster:" msgstr "Melhor Criatura:" #, fuzzy msgid "Select Hero:" msgstr "Melhor Herói:" #, fuzzy msgid "Select Treasure:" msgstr "Melhor Herói:" #, fuzzy msgid "Select Ocean Object:" msgstr "Artefato" #, fuzzy msgid "Castle/town placing" msgstr "Opções do Castelo" msgid "doubleLinedRace|Neutral" msgstr "" msgid "race|Random" msgstr "" #, fuzzy msgid "Click to start placing the selected castle/town." msgstr "Salvar o jogo atual." #, fuzzy msgid "Click to select town placing." msgstr "Mostrar proxima cidade" #, fuzzy msgid "Click to select castle placing." msgstr "Clique aqui para selecionar o cenário a jogar." #, fuzzy msgid "%{color} %{race} %{townOrCastle}" msgstr "Turno do jogador %{color}" msgid "race|Neutral" msgstr "" msgid "You will place" msgstr "" #, fuzzy msgid "Click to select this class." msgstr "Clique aqui para selecionar o cenário a jogar." #, fuzzy msgid "Click to select this color." msgstr "Clique aqui para selecionar o cenário a jogar." #, fuzzy msgid "Select Dwelling:" msgstr "Magia" #, fuzzy msgid "Select Landscape Object:" msgstr "Artefato" msgid "Mine placing" msgstr "" #, fuzzy msgid "" "Resource\n" "type:" msgstr "Custo de recursos:" #, fuzzy msgid "%{mineName} appearance:" msgstr "%{name} a %{race}" #, fuzzy msgid "Click to select %{object} as the resource generator to be placed." msgstr "Clique aqui para selecionar o cenário a jogar." #, fuzzy msgid "Select Mountain Object:" msgstr "Artefato" #, fuzzy msgid "Select Rock Object:" msgstr "Artefato" #, fuzzy msgid "Select Tree Object:" msgstr "Artefato" #, fuzzy msgid "Select Power Up Object:" msgstr "Artefato" #, fuzzy msgid "Select Adventure Object:" msgstr "Artefato" #, fuzzy msgid "Map Type:\n" msgstr "Tamano do Mapa" msgid "The Succession Wars" msgstr "" msgid "The Price of Loyalty" msgstr "" #, fuzzy msgid "Resurrection" msgstr "Resolução" msgid "Lose all your heroes and towns." msgstr "Perder todos os seus heróis e das cidades." msgid "Lose a specific town." msgstr "Perder uma cidade específica." msgid "Lose a specific hero." msgstr "Perder um herói específico." msgid "Run out of time. Fail to win by a certain point." msgstr "" msgid "Loss Condition" msgstr "" msgid "Defeat all enemy heroes and towns." msgstr "Derrotar todos os heróis inimigos e cidades." msgid "Capture a specific town." msgstr "Captura de uma cidade específica." msgid "Defeat a specific hero." msgstr "Derrotar um herói específico." msgid "Find a specific artifact." msgstr "Encontre um artefato específico." msgid "Your side defeats the opposing side." msgstr "Seu lado derrota o lado oposto." msgid "Accumulate a large amount of gold." msgstr "Acumular uma grande quantidade de ouro." msgid "Victory Condition" msgstr "" #, fuzzy msgid "Map difficulty:" msgstr "Dificuldade do Mapa:" msgid "N" msgstr "" msgid "No maps exist at that size" msgstr "" msgid "Small Maps" msgstr "Mapa Pequena" #, fuzzy msgid "View only maps of size small (36 x 36)." msgstr "Ver apenas mapas pequenos (36x36)." msgid "Medium Maps" msgstr "Mapas medios" #, fuzzy msgid "View only maps of size medium (72 x 72)." msgstr "Ver apenas mapas medios (72x72)." msgid "Large Maps" msgstr "Mapas Grandes" #, fuzzy msgid "View only maps of size large (108 x 108)." msgstr "Ver apenas mapas grandes (108x108)." msgid "Extra Large Maps" msgstr "Mapas Muito Grades" #, fuzzy msgid "View only maps of size extra large (144 x 144)." msgstr "Ver Apenas mapas muito grandes (144x144)." msgid "All Maps" msgstr "Todos os Mapas" msgid "View all maps, regardless of size." msgstr "Ver todos os mapas, independente do tamanho." msgid "Players Icon" msgstr "Ãcone de Jogadores" #, fuzzy msgid "" "Indicates how many players total are in the scenario. Any positions not " "occupied by humans will be occupied by computer players." msgstr "" "Indica o total de jogadores que esta definido no Editor do Cenário. Todas as " "posições não ocupadas por seres humanos serão ocupadas por jogadores de " "computador." #, fuzzy msgid "" "Indicates whether the map\n" "is small (36 x 36), medium\n" "(72 x 72), large (108 x 108),\n" "or extra large (144 x 144)." msgstr "" "Indica se os mapas é pequeno (36x36), médio (72x72), grande (108x108) ou " "muito grande (144x144)." msgid "Size Icon" msgstr "Ãcone de Tamanho" msgid "" "Indicates whether the map is made for \"The Succession Wars\" or \"The Price " "of Loyalty\" version of the game." msgstr "" #, fuzzy msgid "Map Type" msgstr "Tamano do Mapa" msgid "Selected Name" msgstr "" msgid "The name of the currently selected map." msgstr "" msgid "Selected Map Difficulty" msgstr "" msgid "" "The map difficulty of the currently selected map. The map difficulty is " "determined by the scenario designer. More difficult maps might include more " "or stronger enemies, fewer resources, or other special conditions making " "things tougher for the human player." msgstr "" msgid "Selected Description" msgstr "" msgid "The description of the currently selected map." msgstr "" msgid "Jump" msgstr "" #, fuzzy msgid "Hero Speed" msgstr "Veloc." msgid "Don't Show" msgstr "" #, fuzzy msgid "Enemy Speed" msgstr "Veloc." msgid "Auto Resolve" msgstr "" msgid "Auto, No Spells" msgstr "" msgid "Battles" msgstr "" msgid "autoBattle|Manual" msgstr "" msgid "Change the speed at which your heroes move on the main screen." msgstr "" msgid "" "Sets the speed that A.I. heroes move at. You can also elect not to view A.I. " "movement at all." msgstr "" #, fuzzy msgid "Change the interface settings of the game." msgstr "Mudar a resolução do jogo." #, fuzzy msgid "Interface Settings" msgstr "Configurações" msgid "Toggle instant battle mode." msgstr "" msgid "Att." msgstr "" msgid "Def." msgstr "" msgid "Power" msgstr "" #, fuzzy msgid "Knowl" msgstr "Conhecimento" msgid "1st" msgstr "" msgid "2nd" msgstr "" msgid "3rd" msgstr "" msgid "4th" msgstr "" msgid "5th" msgstr "" msgid "6th" msgstr "" #, fuzzy msgid "Oracle: Player Rankings" msgstr "Guilda de Ladrões: Ranking de Jogadores" #, fuzzy msgid "Thieves' Guild: Player Rankings" msgstr "Guilda de Ladrões: Ranking de Jogadores" msgid "Number of Towns:" msgstr "Numero de Cidades:" msgid "Number of Castles:" msgstr "Numero de Castelos:" msgid "Number of Heroes:" msgstr "Numero de Heróis:" msgid "Gold in Treasury:" msgstr "Ouro Total:" msgid "Wood & Ore:" msgstr "Madeira & Minério:" msgid "Gems, Cr, Slf & Mer:" msgstr "Gemas, Cri, Sulf & Mer:" msgid "Obelisks Found:" msgstr "Obeliscos encontrados:" #, fuzzy msgid "Artifacts:" msgstr "Artefato" msgid "Total Army Strength:" msgstr "Total Força de Exército:" msgid "Income:" msgstr "Renda:" msgid "Best Hero:" msgstr "Melhor Herói:" msgid "Best Hero Stats:" msgstr "Melhor Herói em Stats:" msgid "Personality:" msgstr "Personalidade:" msgid "Best Monster:" msgstr "Melhor Criatura:" #, fuzzy msgid "Map filename" msgstr "" "Tamano\n" "do Mapa" #, fuzzy msgid "" "Are you sure you want to load a new map? (Any unsaved changes to the current " "map will be lost.)" msgstr "" "Você tem certeza que quer carregar um novo jogo? (O jogo atual será perdido)" #, fuzzy msgid "" "Are you sure you want to create a new map? (Any unsaved changes to the " "current map will be lost.)" msgstr "" "Você tem certeza que quer carregar um novo jogo? (O jogo atual será perdido)" msgid "" "Create a new map, either from scratch or using the random map generator." msgstr "" #, fuzzy msgid "New Map" msgstr "Novo Jogo" #, fuzzy msgid "Load Map" msgstr "Carregar Jogo" msgid "Load an existing map." msgstr "" #, fuzzy msgid "Save Map" msgstr "Novo Jogo" #, fuzzy msgid "Save the current map." msgstr "Salvar o jogo atual." msgid "Quit out of the map editor." msgstr "" #, fuzzy msgid "Objects cannot be placed outside the map." msgstr "Esta cidade não pode ser transformada em castelo." msgid "Monsters cannot be placed on water." msgstr "" msgid "Choose a tile which does not contain any objects." msgstr "" #, fuzzy msgid "Treasures cannot be placed on water." msgstr "Esta cidade não pode ser transformada em castelo." msgid "Heroes cannot be placed on water." msgstr "" msgid "" "A maximum of %{count} heroes of the same color can be placed on the map." msgstr "" #, fuzzy msgid "Artifacts cannot be placed on water." msgstr "Esta cidade não pode ser transformada em castelo." #, fuzzy msgid "Mountains cannot be placed on water." msgstr "Esta cidade não pode ser transformada em castelo." #, fuzzy msgid "Rocks cannot be placed on water." msgstr "Esta cidade não pode ser transformada em castelo." #, fuzzy msgid "Trees cannot be placed on water." msgstr "Esta cidade não pode ser transformada em castelo." #, fuzzy msgid "Ocean object must be placed on water." msgstr "Esta cidade não pode ser transformada em castelo." #, fuzzy msgid "Landscape objects cannot be placed on water." msgstr "Esta cidade não pode ser transformada em castelo." #, fuzzy msgid "Towns cannot be placed on water." msgstr "Esta cidade não pode ser transformada em castelo." #, fuzzy msgid "Mines cannot be placed on water." msgstr "Esta cidade não pode ser transformada em castelo." #, fuzzy msgid "Dwellings cannot be placed on water." msgstr "Esta cidade não pode ser transformada em castelo." #, fuzzy msgid "Power-ups cannot be placed on water." msgstr "Esta cidade não pode ser transformada em castelo." #, fuzzy msgid "Adventure objects cannot be placed on water." msgstr "Esta cidade não pode ser transformada em castelo." #, fuzzy msgid "Used to place %{object}." msgstr "Escavar em busca do Artefato Supremo." #, fuzzy msgid "Select object type" msgstr "Artefato" #, fuzzy msgid "" "Click here to\n" "select a monster." msgstr "Clique aqui para selecionar o cenário a jogar." #, fuzzy msgid "" "Click here to\n" "select another monster." msgstr "Clique aqui para selecionar o cenário a jogar." msgid "" "Cell\n" "Details" msgstr "" #, fuzzy msgid "Roads" msgstr "Modo Estrada" msgid "Streams" msgstr "" msgid "Erase" msgstr "" msgid "Mountains" msgstr "" msgid "Rocks" msgstr "" msgid "Trees" msgstr "" #, fuzzy msgid "Water Objects" msgstr "Terreno Baldio" msgid "Miscellaneous" msgstr "" #, fuzzy msgid "Artifacts" msgstr "Artefato" msgid "Dwellings" msgstr "" msgid "Mines" msgstr "" msgid "Power-ups" msgstr "" #, fuzzy msgid "Treasures" msgstr "Tesouros" #, fuzzy msgid "Towns" msgstr "Cidade" msgid "Heroes" msgstr "Heróis" msgid "editorErasure|Terrain" msgstr "" msgid "editorErasure|Castles" msgstr "" msgid "editorErasure|Monsters" msgstr "" msgid "editorErasure|Heroes" msgstr "" msgid "editorErasure|Artifacts" msgstr "" msgid "editorErasure|Roads" msgstr "" msgid "editorErasure|Streams" msgstr "" msgid "editorErasure|Treasures" msgstr "" msgid "" "Draws terrain in\n" "%{size} by %{size} square increments." msgstr "" msgid "" "Erases objects in\n" "%{size} by %{size} square increments." msgstr "" #, fuzzy msgid "Small Brush" msgstr "Mapa Pequena" #, fuzzy msgid "Medium Brush" msgstr "Mapas medios" #, fuzzy msgid "Large Brush" msgstr "Mapas Grandes" msgid "Area Fill" msgstr "" msgid "Used to click and drag for filling in large areas." msgstr "" msgid "Clear Area" msgstr "" msgid "Used to click and drag for clearing large areas." msgstr "" msgid "" "Costs %{rate} times normal movement for all heroes. (Pathfinding reduces or " "eliminates the penalty.)" msgstr "" msgid "Traversable only by boat." msgstr "" msgid "No special modifiers." msgstr "" msgid "Toggle the erasure of %{type} objects." msgstr "" msgid "" "Objects of this type will be deleted with the Erase tool. Left-click here to " "deselect this type. Press and hold this button to deselect all other object " "types." msgstr "" msgid "" "Objects of this type will NOT be deleted with the Erase tool. Left-click " "here to select this type. Press and hold this button to select all other " "object types." msgstr "" msgid "Terrain Mode" msgstr "" msgid "Used to draw the underlying grass, dirt, water, etc. on the map." msgstr "" msgid "Landscape Objects Mode" msgstr "" msgid "" "Used to place landscape objects (mountains, rocks, trees, etc.) on the map." msgstr "" msgid "Detail Mode" msgstr "" #, fuzzy msgid "Used for special editing of monsters, heroes and towns." msgstr "Derrotar todos os heróis inimigos e cidades." #, fuzzy msgid "Adventure Objects Mode" msgstr "Opções de Aventura" msgid "" "Used to place adventure objects (artifacts, dwellings, mines, treasures, " "etc.) on the map." msgstr "" #, fuzzy msgid "Kingdom Objects Mode" msgstr "Renda:" msgid "Used to place kingdom objects (towns, castles and heroes) on the map." msgstr "" #, fuzzy msgid "Monsters Mode" msgstr "Melhor Criatura:" #, fuzzy msgid "Used to place monsters on the map." msgstr "Escavar em busca do Artefato Supremo." msgid "Allows you to draw streams by clicking and dragging." msgstr "" msgid "Stream Mode" msgstr "" msgid "Allows you to draw roads by clicking and dragging." msgstr "" #, fuzzy msgid "Road Mode" msgstr "Modo Estrada" msgid "Erase Mode" msgstr "" msgid "Used to erase objects from the map." msgstr "" msgid "Change between zoom and normal view." msgstr "" msgid "Magnify" msgstr "" msgid "Undo" msgstr "" msgid "Undo your last action." msgstr "" msgid "Create a new map either from scratch or using the random map generator." msgstr "" msgid "Edit map title, description, and other general information." msgstr "" #, fuzzy msgid "Specifications" msgstr "Fortificações" msgid "File Options" msgstr "Opções de Arquivo" #, fuzzy msgid "" "Open the file options menu, where you can save or load maps, or quit out of " "the editor." msgstr "" "Trazer o arquivo menu de opções, permitindo que você carregue menu, salvar " "etc" #, fuzzy msgid "View the editor system options, which let you customize the editor." msgstr "" "Traz o sistema de menu de opções, permitindo você personalizar seu jogo." msgid "Create a map that is %{size} squares wide and %{size} squares high." msgstr "" #, fuzzy msgid "Cancel back to the New Map menu." msgstr "Cancelar e voltar para o menu principal." msgid "Cancel back to the main menu." msgstr "Cancelar e voltar para o menu principal." msgid "From Scratch" msgstr "" msgid "Start from scratch with a blank map." msgstr "" msgid "Create a randomly generated map." msgstr "" #, fuzzy msgid "Random" msgstr "Criaturas" #, fuzzy msgid "Cancel back to the Map Editor main menu." msgstr "Cancelar e voltar para o menu principal." msgid "No maps available!" msgstr "Não há mapas disponíveis!" msgid "Warning" msgstr "Atenção" msgid "difficulty|Easy" msgstr "" msgid "difficulty|Normal" msgstr "" msgid "difficulty|Hard" msgstr "" msgid "difficulty|Expert" msgstr "" msgid "difficulty|Impossible" msgstr "" msgid "and more..." msgstr "" msgid "Easy" msgstr "" msgid "Hard" msgstr "" #, fuzzy msgid "Campaign Difficulty" msgstr "Dificuldade do Jogo" msgid "" "Choose this difficulty if you want to prefer game story over challenge. AI " "is weaker in comparison with normal difficulty." msgstr "" msgid "" "Choose this difficulty to enjoy the campaign as per the original design." msgstr "" msgid "" "Choose this difficulty if you want challenge. AI is stronger in comparison " "with normal difficulty." msgstr "" #, fuzzy msgid "" "Congratulations!\n" "\n" "Days: %{days}\n" msgstr "Parabéns!" msgid "" "\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" msgid "" "Score: %{score}\n" "\n" "Rating:\n" "%{rating}" msgstr "" msgid "Start the selected scenario." msgstr "" #, fuzzy msgid "View Intro" msgstr "Ver Herói" msgid "View Intro videos for the current state of the campaign." msgstr "" msgid "" "Select the campaign difficulty. This can be lowered at any point during the " "campaign." msgstr "" #, fuzzy msgid "Restart the current scenario." msgstr "Salvar o jogo atual." #, fuzzy msgid "Difficulty" msgstr "" "Dificuldade\n" "do Mapa" msgid "" "You have changed to a lower difficulty for the campaign. You will not be " "able to revert this after this point. The high score will be calculated " "based solely on the new difficulty. Do you want to proceed?" msgstr "" #, fuzzy msgid "Are you sure you want to restart this scenario?" msgstr "Tem certeza de que quer a retirada?" msgid "Campaign Scenario loading failure" msgstr "" msgid "Please make sure that campaign files are correct and present." msgstr "" msgid "Days spent" msgstr "" msgid "The number of days spent on this campaign." msgstr "" msgid "Project Coordination and Core Development" msgstr "" msgid "Development" msgstr "" msgid "Visit us at " msgstr "" msgid "QA and Support" msgstr "" msgid "Dev and Support" msgstr "" msgid "Special Thanks to" msgstr "" msgid "and many other contributors!" msgstr "" msgid "and many-many other supporters!" msgstr "" msgid "Support us at" msgstr "" msgid "local-donation-platform|https://www.patreon.com/fheroes2" msgstr "" msgid "Connect with us at" msgstr "" msgid "local-social-network|https://www.facebook.com/groups/fheroes2" msgstr "" msgid "Need help with the game?" msgstr "" msgid "Original project before 0.7" msgstr "" #, fuzzy msgid "Heroes of Might and Magic II: The Succession Wars team" msgstr "Sair do Jogo & retornar ao sistema operacional." msgid "Designed and Directed" msgstr "" msgid "Programming and Design" msgstr "" msgid "Executive Producer" msgstr "" msgid "Producer" msgstr "" msgid "Additional Design" msgstr "" msgid "Additional Programming" msgstr "" msgid "Musical Production" msgstr "" msgid "Music and Sound Design" msgstr "" msgid "Vocalists" msgstr "" msgid "Art Director" msgstr "" msgid "Assistant Art Director" msgstr "" #, fuzzy msgid "Artists" msgstr "Artefato" msgid "QA Manager" msgstr "" msgid "QA" msgstr "" #, fuzzy msgid "Writing" msgstr "Grau" msgid "Manual and Helpfile" msgstr "" #, fuzzy msgid "Scenarios" msgstr "Cenário" #, fuzzy msgid "Heroes of Might and Magic II: The Price of Loyalty team" msgstr "Sair do Jogo & retornar ao sistema operacional." msgid "Design Lead" msgstr "" msgid "Designers" msgstr "" msgid "Programming Lead" msgstr "" msgid "Art Lead" msgstr "" #, fuzzy msgid "Playtesters" msgstr "2 Jogadores" msgid "Designer" msgstr "" msgid "Producers" msgstr "" msgid "QA Managers" msgstr "" msgid "Sound Design" msgstr "" msgid "Town Themes" msgstr "" msgid "Alto Sax" msgstr "" msgid "Harpsichord and Piano" msgstr "" msgid "Basso Vocal" msgstr "" msgid "Soprano Vocal" msgstr "" msgid "Recorded at %{recordingStudio}" msgstr "" msgid "credits|Manual" msgstr "" msgid "German Consultant" msgstr "" msgid "Map Designers" msgstr "" msgid "Package Design" msgstr "" msgid "Your Name" msgstr "" msgid "Unknown Hero" msgstr "" #, fuzzy msgid "Standard" msgstr "Unico Jogador" #, fuzzy msgid "View High Scores for Standard Maps." msgstr "Ver a tela de Records." #, fuzzy msgid "Campaign" msgstr "Campanha" #, fuzzy msgid "View High Scores for Campaigns." msgstr "Ver a tela de Records." msgid "hotkey|default okay event" msgstr "" msgid "hotkey|default cancel event" msgstr "" msgid "hotkey|default left" msgstr "" msgid "hotkey|default right" msgstr "" msgid "hotkey|default up" msgstr "" msgid "hotkey|default down" msgstr "" msgid "hotkey|toggle fullscreen" msgstr "" msgid "hotkey|toggle text support mode" msgstr "" msgid "hotkey|new game" msgstr "" msgid "hotkey|load game" msgstr "" msgid "hotkey|highscores" msgstr "" msgid "hotkey|credits" msgstr "" #, fuzzy msgid "hotkey|standard game" msgstr "Unico Jogador" #, fuzzy msgid "hotkey|campaign game" msgstr "Campanha" #, fuzzy msgid "hotkey|multi-player game" msgstr "Mult-Jogadores" #, fuzzy msgid "hotkey|settings" msgstr "Configurações" msgid "hotkey|quit" msgstr "" msgid "hotkey|select map" msgstr "" msgid "hotkey|select small map size" msgstr "" msgid "hotkey|select medium map size" msgstr "" msgid "hotkey|select large map size" msgstr "" msgid "hotkey|select extra large map size" msgstr "" msgid "hotkey|select all map sizes" msgstr "" msgid "hotkey|hotseat game" msgstr "" msgid "hotkey|battle only game" msgstr "" msgid "hotkey|choose the original campaign" msgstr "" msgid "hotkey|choose the expansion campaign" msgstr "" #, fuzzy msgid "hotkey|map editor main menu" msgstr "Campanha" #, fuzzy msgid "hotkey|new map menu" msgstr "Mostrar proxima cidade" #, fuzzy msgid "hotkey|load map menu" msgstr "Unico Jogador" msgid "hotkey|new map from scratch" msgstr "" #, fuzzy msgid "hotkey|new random map" msgstr "Resumo do Reino" #, fuzzy msgid "hotkey|undo last action" msgstr "Mostrar proxima cidade" #, fuzzy msgid "hotkey|redo last action" msgstr "Mostrar proxima cidade" msgid "hotkey|roland campaign" msgstr "" msgid "hotkey|archibald campaign" msgstr "" msgid "hotkey|the price of loyalty campaign" msgstr "" msgid "hotkey|voyage home campaign" msgstr "" msgid "hotkey|wizard's isle campaign" msgstr "" msgid "hotkey|descendants campaign" msgstr "" msgid "hotkey|select first campaign bonus" msgstr "" msgid "hotkey|select second campaign bonus" msgstr "" msgid "hotkey|select third campaign bonus" msgstr "" msgid "hotkey|view campaign intro" msgstr "" #, fuzzy msgid "hotkey|select campaign difficulty" msgstr "Dificuldade do Jogo" msgid "hotkey|restart campaign scenario" msgstr "" msgid "hotkey|world map left" msgstr "" msgid "hotkey|world map right" msgstr "" msgid "hotkey|world map up" msgstr "" msgid "hotkey|world map down" msgstr "" msgid "hotkey|world map up left" msgstr "" msgid "hotkey|world map up right" msgstr "" msgid "hotkey|world map down left" msgstr "" msgid "hotkey|world map down right" msgstr "" msgid "hotkey|save game" msgstr "" #, fuzzy msgid "hotkey|next hero" msgstr "Mostrar proxima cidade" #, fuzzy msgid "hotkey|start hero movement" msgstr "Continuar a Andar" #, fuzzy msgid "hotkey|cast adventure spell" msgstr "Lançar uma magia." msgid "hotkey|put hero to sleep" msgstr "" #, fuzzy msgid "hotkey|next town" msgstr "Mostrar proxima cidade" msgid "hotkey|end turn" msgstr "" #, fuzzy msgid "hotkey|file options" msgstr "Opções de Arquivo" #, fuzzy msgid "hotkey|adventure options" msgstr "Opções de Aventura" msgid "hotkey|puzzle map" msgstr "" #, fuzzy msgid "hotkey|scenario information" msgstr "Formação Espalhada" msgid "hotkey|dig for artifact" msgstr "" msgid "hotkey|view world" msgstr "" #, fuzzy msgid "hotkey|kingdom summary" msgstr "Resumo do Reino" msgid "hotkey|default action" msgstr "" msgid "hotkey|open focus" msgstr "" #, fuzzy msgid "hotkey|system options" msgstr "Customizar opções do sistema" msgid "hotkey|scroll left" msgstr "" msgid "hotkey|scroll right" msgstr "" msgid "hotkey|scroll up" msgstr "" msgid "hotkey|scroll down" msgstr "" msgid "hotkey|toggle control panel" msgstr "" msgid "hotkey|toggle radar" msgstr "" msgid "hotkey|toggle buttons" msgstr "" msgid "hotkey|toggle status" msgstr "" msgid "hotkey|toggle icons" msgstr "" msgid "hotkey|transfer control to ai" msgstr "" msgid "hotkey|retreat from battle" msgstr "" msgid "hotkey|surrender during battle" msgstr "" msgid "hotkey|toggle battle auto mode" msgstr "" #, fuzzy msgid "hotkey|finish the battle in auto mode" msgstr "Tem certeza de que quer a retirada?" #, fuzzy msgid "hotkey|battle options" msgstr "Opções do Castelo" msgid "hotkey|skip turn in battle" msgstr "" msgid "hotkey|cast battle spell" msgstr "" msgid "hotkey|dwelling level 1" msgstr "" msgid "hotkey|dwelling level 2" msgstr "" msgid "hotkey|dwelling level 3" msgstr "" msgid "hotkey|dwelling level 4" msgstr "" msgid "hotkey|dwelling level 5" msgstr "" msgid "hotkey|dwelling level 6" msgstr "" msgid "hotkey|well" msgstr "" #, fuzzy msgid "hotkey|marketplace" msgstr "Mercado" msgid "hotkey|mage guild" msgstr "" #, fuzzy msgid "hotkey|shipyard" msgstr "Estaleiro" #, fuzzy msgid "hotkey|thieves guild" msgstr "Guilda de Ladrões" msgid "hotkey|tavern" msgstr "" msgid "hotkey|construction screen" msgstr "" msgid "hotkey|buy all monsters in well" msgstr "" msgid "hotkey|split stack by half" msgstr "" msgid "hotkey|split stack by one" msgstr "" msgid "hotkey|join stacks" msgstr "" msgid "hotkey|upgrade troop" msgstr "" msgid "hotkey|dismiss hero or troop" msgstr "" msgid "" "Do you want to regain control from AI? The effect will take place only on " "the next turn." msgstr "" msgid "" "Do you want to transfer control from you to the AI? The effect will take " "place only on the next turn." msgstr "" msgid "Default Actions" msgstr "" msgid "Global Actions" msgstr "" msgid "Main Menu" msgstr "" msgid "World Map" msgstr "Mapa-Múndi" #, fuzzy msgid "Battle Screen" msgstr "Tela do herói" #, fuzzy msgid "Town Screen" msgstr "Tela do herói" msgid "Army Actions" msgstr "" msgid "Editor" msgstr "" #, fuzzy msgid "The save file is corrupted." msgstr "A imagem no espelho está criada." msgid "Unsupported save format: " msgstr "" msgid "Current game version: " msgstr "" msgid "Last supported version: " msgstr "" msgid "This file contains a save with an invalid game type." msgstr "" msgid "" "This save file requires \"The Price of Loyalty\" game assets, but they have " "not been provided to the engine." msgstr "" msgid "This saved game is localized to '" msgstr "" msgid "' language, but the current language of the game is '" msgstr "" msgid "Hot Seat" msgstr "Hot Seat" msgid "" "Play a Hot Seat game, where 2 to 4 players play around the same computer, " "switching into the 'Hot Seat' when it is their turn." msgstr "Modo \"Hot Seat\", onde 2 a 4 jogadores jogam no mesmo computador." msgid "A single player game playing out a single map." msgstr "Um jogo com um único jogador." msgid "Standard Game" msgstr "Unico Jogador" msgid "A single player game playing through a series of maps." msgstr "Um único jogador jogando em uma série de mapas." msgid "Campaign Game" msgstr "Campanha" msgid "Multi-Player Game" msgstr "Mult-Jogadores" msgid "" "A multi-player game, with several human players completing against each " "other on a single map." msgstr "" "Um jogo multi-jogador, com vários jogadores humanos completando uns contra " "os outros em um único mapa." msgid "fheroes2 Resurrection Team presents" msgstr "" msgid "Greetings!" msgstr "" #, fuzzy msgid "Welcome to Heroes of Might and Magic II powered by fheroes2 engine!" msgstr "Sair do Jogo & retornar ao sistema operacional." #, fuzzy msgid "" "Welcome to Heroes of Might and Magic II powered by the fheroes2 engine!\n" "Before starting the game please select a game resolution." msgstr "Sair do Jogo & retornar ao sistema operacional." msgid "Please Remember" msgstr "" #, fuzzy msgid "" "You can always change the language, resolution and settings of the game by " "clicking on the " msgstr "Mudar a resolução do jogo." msgid "door" msgstr "" msgid " on the left side of the Main Menu, or with the " msgstr "" msgid " button from the " msgstr "" msgid "NEW GAME" msgstr "" msgid "" " menu. \n" "\n" "Enjoy the game!" msgstr "" msgid "The Editor requires \"The Price of Loyalty\" expansion files to work." msgstr "" #, fuzzy msgid "Quit Heroes of Might and Magic II and return to the operating system." msgstr "Sair do Jogo & retornar ao sistema operacional." msgid "Credits" msgstr "Créditos" msgid "View the credits screen." msgstr "Ver tela dos créditos." msgid "High Scores" msgstr "Recordes" msgid "View the high scores screen." msgstr "Ver a tela de Records." #, fuzzy msgid "Change language, resolution and settings of the game." msgstr "Mudar a resolução do jogo." #, fuzzy msgid "Game Settings" msgstr "Configurações" msgid "" "Required video files for campaign selection window are missing. Please make " "sure that all necessary files are present in the system." msgstr "" msgid "" "Either Roland's or Archibald's campaign from the original Heroes of Might " "and Magic II." msgstr "" msgid "Original Campaign" msgstr "" msgid "Expansion Campaign" msgstr "" msgid "One of the four new campaigns from the Price of Loyalty expansion set." msgstr "" msgid "Loading video. Please wait..." msgstr "" msgid "Host" msgstr "Host" msgid "" "The host sets up the game options. There can only be one host per network " "game." msgstr "" "O host configura as opções de jogo. Só pode haver um host por jogo em modo " "network." msgid "Guest" msgstr "Convidado" msgid "" "The guest waits for the host to set up the game, then is automatically added " "in. There can be multiple guests for TCP/IP games." msgstr "" "O convidado espera pelo host configurar o jogo, em seguida, é adicionado " "automaticamente dentro. Não pode haver vários convidados para Jogos TCP/IP." msgid "Battle Only" msgstr "" msgid "Setup and play a battle without loading any map." msgstr "" msgid "2 Players" msgstr "2 Jogadores" #, fuzzy msgid "" "Play with 2 human players, and optionally, up to 4 additional computer " "players." msgstr "Jogue com 2 jogadores humanos, e, opcionalmente, até 4 jogadores." msgid "3 Players" msgstr "3 Jogadores" #, fuzzy msgid "" "Play with 3 human players, and optionally, up to 3 additional computer " "players." msgstr "" "Jogue com 3 jogadores humanos, e, opcionalmente, até 3 jogadores não humanos." msgid "4 Players" msgstr "4 Jogadores" #, fuzzy msgid "" "Play with 4 human players, and optionally, up to 2 additional computer " "players." msgstr "" "Jogue com 4 jogadores humanos, e, opcionalmente, até 2 jogadores não humanos." msgid "5 Players" msgstr "5 Jogadores" #, fuzzy msgid "" "Play with 5 human players, and optionally, up to 1 additional computer " "player." msgstr "" "Jogue com 5 jogadores humanos, e, opcionalmente, até 1 jogadores não humanos." msgid "6 Players" msgstr "6 Jogadores" msgid "Play with 6 human players." msgstr "Jogue com 6 jogadores humanos." msgid "Dragon city has fallen! You are now the Master of the Dragons." msgstr "" msgid "" "You captured %{name}!\n" "You are victorious." msgstr "" "Você capturou %{name}!\n" "Você Venceu." msgid "" "You have captured the enemy hero %{name}!\n" "Your quest is complete." msgstr "" "Você capturou o herói inimigo %{name}!\n" "Missão Cumprida!" msgid "" "You have found the %{name}.\n" "Your quest is complete." msgstr "" "Você achou o %{name}.\n" "Missão Cumprida!" #, fuzzy msgid "Ultimate Artifact" msgstr "Escavar em busca do Artefato Supremo." msgid "" "The enemy is beaten.\n" "Your side has triumphed!" msgstr "" "O inimigo foi derrotado.\n" "Seu lado triunfou!" msgid "" "You have built up over %{count} gold in your treasury.\n" "All enemies bow before your wealth and power." msgstr "" "Você tem acumulou o total %{count} de ouro.\n" "Todos os inimigos curvar-se diante de sua riqueza e poder." msgid "Victory!" msgstr "" msgid "" "The enemy has captured %{name}!\n" "They are triumphant." msgstr "" "O inimigo capturou %{name}!\n" "Eles venceram o jogo." msgid "" "The enemy has built up over %{count} gold in his treasury.\n" "You must bow done in defeat before his wealth and power." msgstr "" "O inimigo acumulou o total de %{count} de ouro.\n" "Você curvou-se diante do seu poder e riqueza." msgid "You have been eliminated from the game!!!" msgstr "Você foi eliminado do do jogo!" msgid "" "You have lost the hero %{name}.\n" "Your quest is over." msgstr "" "Você perdeu o herói %{name}.\n" "Sua Missão acabou." msgid "" "You have failed to complete your quest in time.\n" "All is lost." msgstr "" "Você não conseguiu concluir sua missão no tempo.\n" "Tudo está acabado." msgid "Defeat!" msgstr "" msgid "" "Base score: %{score}\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" #, fuzzy msgid "Defeat all enemy heroes and capture all enemy towns and castles." msgstr "Derrotar todos os heróis inimigos e cidades." msgid "Run out of time. (Fail to win by a certain point.)" msgstr "Acabar o tempo. (Imposibilitado de vencer apartir de um certo ponto.)" #, fuzzy msgid "You must defeat the enemy %{enemies}." msgid_plural "You must defeat the enemy alliance of %{enemies}." msgstr[0] "Seu lado derrota o lado oposto." msgstr[1] "Seu lado derrota o lado oposto." msgid "" "The alliance consisting of %{allies} and you must defeat the enemy " "%{enemies}." msgid_plural "" "The alliance consisting of %{allies} and you must defeat the enemy alliance " "of %{enemies}." msgstr[0] "" msgstr[1] "" msgid "Capture the castle '%{name}'." msgstr "" msgid "Capture the town '%{name}'." msgstr "" msgid "Defeat the hero '%{name}'." msgstr "" #, fuzzy msgid "Find the ultimate artifact." msgstr "Escavar em busca do Artefato Supremo." #, fuzzy msgid "Find the '%{name}' artifact." msgstr "Encontre um artefato específico." #, fuzzy msgid "Accumulate %{count} gold." msgstr "Acumular uma grande quantidade de ouro." msgid "" ", or you may win by defeating all enemy heroes and capturing all enemy towns " "and castles." msgstr "" msgid "Lose the castle '%{name}'." msgstr "" msgid "Lose the town '%{name}'." msgstr "" msgid "Lose the hero: %{name}." msgstr "" msgid "Fail to win by the end of month %{month}, week %{week}, day %{day}." msgstr "" #, fuzzy msgid "%{color} player has been vanquished!" msgstr "%{color} foi derrotado!" msgid "Major Event!" msgstr "" msgid "Scenario:" msgstr "Cenário:" msgid "Game Difficulty:" msgstr "Dificuldade do Jogo:" msgid "Opponents:" msgstr "Oponentes:" msgid "Class:" msgstr "Classe:" msgid "Rating %{rating}%" msgstr "Taxa %{rating}%" msgid "Click here to select which scenario to play." msgstr "Clique aqui para selecionar o cenário a jogar." msgid "Scenario" msgstr "Cenário" msgid "Game Difficulty" msgstr "Dificuldade do Jogo" msgid "" "This lets you change the starting difficulty at which you will play. Higher " "difficulty levels start you of with fewer resources, and at the higher " "settings, give extra resources to the computer." msgstr "" "Isto permite-lhe alterar a dificuldade inicial no qual você vai jogar. " "Quanto maior o nivel de dificuldade, menos recursos você começa, e da mais " "recursos ao computador." msgid "Difficulty Rating" msgstr "Taxa de Dificuldade" msgid "" "The difficulty rating reflects a combination of various settings for your " "game. This number will be applied to your final score." msgstr "" "O grau de dificuldade reflete uma combinação de várias definições para o seu " "jogo. Este número será aplicado à sua pontuação final." msgid "Click to accept these settings and start a new game." msgstr "Clique para aceitar estas configurações e começar um novo jogo." msgid "Click to return to the main menu." msgstr "Clique para retornar ao menu principal." #, fuzzy msgid "Astrologers proclaim the Month of the %{name}." msgstr "Astrólogos proclamar o mês do %{name}." #, fuzzy msgid "Astrologers proclaim the Week of the %{name}." msgstr "Astrólogos proclamar a semana do %{name}." msgid "After regular growth, the population of %{monster} is doubled!" msgstr "" msgid "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgid_plural "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgstr[0] "" msgstr[1] "" msgid "%{monster} growth +%{count}." msgstr "" msgid " All populations are halved." msgstr " Todas as população foi reduzidas a metade." msgid " All dwellings increase population." msgstr " Todas as populações aumentaram nas habitações." msgid "New Month!" msgstr "" msgid "New Week!" msgstr "" msgid "Beware!" msgstr "" msgid "" "%{color} player, this is your last day to capture a town, or you will be " "banished from this land." msgstr "" "Jogador %{color}, este é o último dia para capturar uma cidade, ou você será " "banido desta terra." msgid "" "%{color} player, you only have %{day} days left to capture a town, or you " "will be banished from this land." msgstr "" "Jogador %{color}, você tem somente %{day} dias para capturar uma cidade, ou " "você será banido desta terra." #, fuzzy msgid "%{color} player's turn." msgstr "Turno do jogador %{color}" #, fuzzy msgid "" "%{color} player, you have lost your last town. If you do not conquer another " "town in the next week, you will be eliminated." msgstr "" "%{color}, você perdeu sua última cidade. Se você não conquistar uma outra " "cidade na próxima semana, será eliminado." msgid "" "%{color} player, your heroes abandon you, and you are banished from this " "land." msgstr "" "Jogador %{color}, seus heróis abandonaram você, e você foi banido de sua " "terra." msgid "Lord Kilburn" msgstr "" msgid "Tsabu" msgstr "" msgid "Sir Galant" msgstr "" msgid "Thundax" msgstr "" msgid "Lord Haart" msgstr "" msgid "Ariel" msgstr "" msgid "Rebecca" msgstr "" msgid "Sandro" msgstr "" msgid "Crodo" msgstr "" msgid "Barock" msgstr "" msgid "Antoine" msgstr "" msgid "Astra" msgstr "" msgid "Agar" msgstr "" msgid "Vatawna" msgstr "" msgid "Vesper" msgstr "" msgid "Ambrose" msgstr "" msgid "Troyan" msgstr "" msgid "Jojosh" msgstr "" msgid "Wrathmont" msgstr "" msgid "Maximus" msgstr "" msgid "Next Hero" msgstr "Proximo Herói" msgid "Select the next Hero." msgstr "Selecione o proximo Herói" #, fuzzy msgid "Hero Movement" msgstr "Continuar a Andar" msgid "" "Start the Hero's movement along the current path or re-visit the object " "occupied by the Hero. Press and hold this button to reset the Hero's path." msgstr "" msgid "Kingdom Summary" msgstr "Resumo do Reino" msgid "View a Summary of your Kingdom." msgstr "Ver o resumo de seu reino." msgid "Cast an adventure spell." msgstr "Lançar uma magia." msgid "End Turn" msgstr "Terminar Turno" msgid "End your turn and left the computer take its turn." msgstr "Termina o seu turno." msgid "Adventure Options" msgstr "Opções de Aventura" msgid "Bring up the adventure options menu." msgstr "Traz a menu de opções da aventura." #, fuzzy msgid "" "Bring up the file options menu, allowing you to load, save, start a new game " "or quit." msgstr "" "Trazer o arquivo menu de opções, permitindo que você carregue menu, salvar " "etc" #, fuzzy msgid "Bring up the system options menu, allowing you to customize your game." msgstr "" "Traz o sistema de menu de opções, permitindo você personalizar seu jogo." msgid "" "One or more heroes may still move, are you sure you want to end your turn?" msgstr "" "Um ou mais heróis ainda pode andar, você tem certeza que quer terminar o seu " "turno?" msgid "Are you sure you want to quit?" msgstr "Você tem certeza que deseja sair?" #, fuzzy msgid "Are you sure you want to restart? (Your current game will be lost.)" msgstr "Tem certeza que deseja reiniciar? (O jogo atual será perdido)" msgid "Are you sure you want to overwrite the save with this name?" msgstr "" msgid "Game saved successfully." msgstr "Jogo salvo com sucesso." msgid "There was an issue during saving." msgstr "" #, fuzzy msgid "" "Are you sure you want to load a new game? (Your current game will be lost.)" msgstr "" "Você tem certeza que quer carregar um novo jogo? (O jogo atual será perdido)" msgid "Try looking on land!!!" msgstr "" msgid "" "Searching for the Ultimate Artifact is fruitless. Your hero could not carry " "it even if he found it - all his artifact slots are full." msgstr "" msgid "Digging for artifacts requires a whole day, try again tomorrow." msgstr "" "Escavar em busca de artefatos requer um dia inteiro, tente novamente amanhã." #, fuzzy msgid "" "After spending many hours digging here, you have uncovered the %{artifact}." msgstr "Depois de passar muitas horas escavando aqui, você achou o " msgid "Congratulations!" msgstr "Parabéns!" msgid "Nothing here. Where could it be?" msgstr "Nada aqui. Onde poderia estar?" msgid "Try searching on clear ground." msgstr "Tente achar no chão sem obstaculos." msgid "A miniature view of the known world. Left click to move viewing area." msgstr "" "Uma visão em miniatura do mundo conhecido. Clique o botao esquerdo para " "mover área de visualização." msgid "Month: %{month} Week: %{week}" msgstr "Mês: %{month} Semana: %{week}" msgid "Day: %{day}" msgstr "Dia: %{day}" msgid "" "You find a small\n" "quantity of %{resource}." msgstr "" "Você achou uma pequena\n" "quantidade de %{resource}." msgid "Status Window" msgstr "Janela de Status" #, fuzzy msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date." msgstr "" "Esta janela fornece informações sobre o status do seu herói ou reino, e " "mostra a data. Clique o botão esquerdo para alternar as janelas." msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date. Left click here to cycle through these windows." msgstr "" "Esta janela fornece informações sobre o status do seu herói ou reino, e " "mostra a data. Clique o botão esquerdo para alternar as janelas." msgid "" "This lets you change player starting positions and colors. A particular " "color will always start in a particular location. Some positions may only be " "played by a computer player or only by a human player." msgstr "" "Isso permite que você altere o jogador a partir de posições e cores. Uma cor " "especial começará sempre em um determinado local. Algumas posições só pode " "ser jogado por um computador ou apenas por um jogador humano." msgid "" "This lets you change the class of a player. Classes are not always " "changeable. Depending on the scenario, a player may receive additional towns " "and/or heroes not of their primary alignment." msgstr "" "Isso permite que você alterar a classe de um jogador. Classes não são sempre " "alteravel. Dependendo do cenário, um jogador não pode receber cidades " "adicionais e / ou heróis do seu alinhamento primário." msgid "Handicap" msgstr "" # msgid "" "This lets you change the handicap of a particular player. Only humans may be " "handicapped. Handicapped players start with fewer resources and earn 15 or " "30% fewer resources per turn for mild and severe handicaps, respectively." msgstr "" #, fuzzy msgid "%{color} player" msgstr "Turno do jogador %{color}" msgid "No Handicap" msgstr "" msgid "No special restrictions on start resources and earning them per turn." msgstr "" msgid "Mild Handicap" msgstr "" # msgid "" "Mild handicapped players start with fewer resources and earn 15% fewer " "resources per turn." msgstr "" msgid "Severe Handicap" msgstr "" # msgid "" "Severe handicapped players start with fewer resources and earn 30% fewer " "resources per turn." msgstr "" msgid "View %{skill} Info" msgstr "" msgid "Keyboard|123" msgstr "" msgid "Keyboard|SPACE" msgstr "" msgid "Keyboard|ABC" msgstr "" #, fuzzy msgid "Kingdom Income" msgstr "Renda:" #, fuzzy msgid "Kingdom Income per day." msgstr "Renda:" msgid "For every lighthouse controlled, your ships will move further each day." msgstr "" msgid "English" msgstr "" msgid "French" msgstr "" msgid "Polish" msgstr "" msgid "German" msgstr "" msgid "Russian" msgstr "" msgid "Italian" msgstr "" msgid "Czech" msgstr "" msgid "Norwegian" msgstr "" msgid "Belarusian" msgstr "" msgid "Bulgarian" msgstr "" msgid "Ukrainian" msgstr "" msgid "Romanian" msgstr "" msgid "Spanish" msgstr "" msgid "Swedish" msgstr "" msgid "Portuguese" msgstr "Português" msgid "Turkish" msgstr "" msgid "Dutch" msgstr "" msgid "Hungarian" msgstr "" msgid "Danish" msgstr "" msgid "Slovak" msgstr "" #, fuzzy msgid "Vietnamese" msgstr "Ver %{name}" msgid ", FPS: " msgstr "" msgid "%{object} robber" msgstr "" msgid "%{object} raided" msgstr "" msgid "Ector" msgstr "" msgid "Gwenneth" msgstr "" msgid "Sir Gallant" msgstr "" msgid "Tyro" msgstr "" msgid "Dimitry" msgstr "" msgid "Ruby" msgstr "" msgid "Crag Hack" msgstr "" msgid "Fineous" msgstr "" msgid "Jezebel" msgstr "" msgid "Atlas" msgstr "" msgid "Ergon" msgstr "" msgid "Jaclyn" msgstr "" msgid "Gem" msgstr "" msgid "Natasha" msgstr "" msgid "Carlawn" msgstr "" msgid "Luna" msgstr "" msgid "Arie" msgstr "" msgid "Barok" msgstr "" msgid "Kastore" msgstr "" msgid "Falagar" msgstr "" msgid "Dawn" msgstr "" msgid "Flint" msgstr "" msgid "Halon" msgstr "" msgid "Myra" msgstr "" msgid "Myrini" msgstr "" msgid "Wilfrey" msgstr "" msgid "Mandigal" msgstr "" msgid "Sarakin" msgstr "" msgid "Charity" msgstr "" msgid "Darlana" msgstr "" msgid "Ranloo" msgstr "" msgid "Rialdo" msgstr "" msgid "Zam" msgstr "" msgid "Zom" msgstr "" msgid "Celia" msgstr "" msgid "Roxana" msgstr "" msgid "Lord Corlagon" msgstr "" msgid "Lord Halton" msgstr "" msgid "Sister Eliza" msgstr "" msgid "Brother Brax" msgstr "" msgid "Dainwin" msgstr "" msgid "Joseph" msgstr "" msgid "Mog" msgstr "" msgid "Solmyr" msgstr "" msgid "Ceallach" msgstr "" msgid "Drakonia" msgstr "" msgid "Elderian" msgstr "" msgid "Gallavant" msgstr "" msgid "Jarkonas" msgstr "" msgid "Martine" msgstr "" msgid " gives you maximum morale" msgstr "" msgid " gives you maximum luck" msgstr "" msgid "You cannot pick up this artifact, you already have a full load!" msgstr "" msgid "To cast spells, you must first buy a spell book for %{gold} gold." msgstr "" msgid "Unfortunately, you seem to be a little short of cash at the moment." msgstr "" msgid "Do you wish to buy one?" msgstr "" msgid "%{count} / day" msgstr "" msgid "one" msgstr "" msgid "two" msgstr "" msgid "A whirlpool engulfs your ship. Some of your army has fallen overboard." msgstr "" msgid "Insulted by your refusal of their offer, the monsters attack!" msgstr "Insultado pela sua recusa da oferta, as criaturas atacam!" msgid "" "The %{monster}, awed by the power of your forces, begin to scatter.\n" "Do you wish to pursue and engage them?" msgstr "" "O %{monster}, impressionado com o poder de suas forças, começam a fugir.\n" "Deseja prosseguir e ataca-los?" msgid "Ransacking an enemy camp, you discover a hidden cache of treasures." msgstr "" "Saqueando um campo inimigo, você descobre um esconderijo de tesouros " "escondidos." msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with these resources, " "come back next week for more.\"" msgstr "" "O detentor da fábrica anuncia:\n" "\"Milord, tenho trabalhado muito duro para lhe fornecer esses recursos, " "volte na próxima semana para receber mais.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there are no resources currently available. Please try " "again next week.\"" msgstr "" "O detentor da fábrica anuncia:\n" "\"Milord, lamento, não há recursos disponíveis no momento. Por favor, tente " "novamente na próxima semana.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with this gold, come " "back next week for more.\"" msgstr "" "O detentor da fábrica anuncia:\n" "\"Milord, tenho estado a trabalhar arduamente para lhe proporcionar este " "ouro, volte na próxima semana para receber mais.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there is no gold currently available. Please try again " "next week.\"" msgstr "" "O detentor da fábrica anuncia:\n" "\"Milord, lamento, não há ouro atualmente disponível. Por favor, tente " "novamente na próxima semana.\"" msgid "" "You've found an abandoned lean-to.\n" "Poking about, you discover some resources hidden nearby." msgstr "" msgid "The lean-to is long abandoned. There is nothing of value here." msgstr "" msgid "" "You catch a leprechaun foolishly sleeping amidst a cluster of magic " "mushrooms.\n" "In exchange for his freedom, he guides you to a small pot filled with " "precious things." msgstr "" msgid "" "You've found a magic garden, the kind of place that leprechauns and faeries " "like to cavort in, but there is no one here today.\n" "Perhaps you should try again next week." msgstr "" #, fuzzy msgid "You come upon the remains of an unfortunate adventurer." msgstr "" "Você vai sobre os restos de um aventureiro infeliz.\n" "Olhando através da roupa esfarrapada, você encontra nada." #, fuzzy msgid "Treasure" msgstr "Tesouros" #, fuzzy msgid "Searching through the tattered clothing, you find the %{artifact}." msgstr "" "Você vai sobre os restos de um aventureiro infeliz.\n" "Olhando através da roupa esfarrapada, você encontra um artefato %{artifact}." #, fuzzy msgid "Searching through the tattered clothing, you find nothing." msgstr "Você procura através dos destroços, mas não acha nada." #, fuzzy msgid "" "You come across an old wagon left by a trader who didn't quite make it to " "safe terrain." msgstr "" "Você se deparar com uma carroça velha deixada por um comerciante que não " "chegou a um lugar seguro.\n" "Olhando dentro, você encontra o artefato %{artifact}." #, fuzzy msgid "Unfortunately, others have found it first, and the wagon is empty." msgstr "" "Você se deparar com uma carroça velha deixada por um comerciante que não " "chegou a um lugar seguro.\n" "Infelismente, outros acharam isso primeiro. A carroça está vazia." msgid "Searching inside, you find the %{artifact}." msgstr "" msgid "Inside, you find some of the wagon's cargo still intact." msgstr "" msgid "You search through the flotsam, and find some wood and some gold." msgstr "" "Você procura através dos destroços, e encontrar um pouco de madeira e um " "pouco de ouro." msgid "You search through the flotsam, and find some wood." msgstr "Você procura através dos destroços, e encontrar um pouco de madeira." msgid "You search through the flotsam, but find nothing." msgstr "Você procura através dos destroços, mas não acha nada." msgid "Shrine of the 1st Circle" msgstr "Abside no Primeiro Ciclo." msgid "" "You come across a small shrine attended by a group of novice acolytes.\n" "In exchange for your protection, they agree to teach you a simple spell - " "'%{spell}'." msgstr "" "Você se deparar com um pequeno santuário com a participação de um grupo de " "noviços acólitos.\n" "Em troca de proteção, eles concordam em ensinar-lhe uma magia simples - " "'%{spell}'." msgid "Shrine of the 2nd Circle" msgstr "" msgid "" "You come across an ornate shrine attended by a group of rotund friars.\n" "In exchange for your protection, they agree to teach you a spell - " "'%{spell}'." msgstr "" "Você se deparar com um santuário ornamentado com a participação de um grupo " "de frades rotundo.\n" "Em troca de proteção, eles concordam em ensinar-lhe uma magia - '%{spell}'." msgid "Shrine of the 3rd Circle" msgstr "" msgid "" "You come across a lavish shrine attended by a group of high priests.\n" "In exchange for your protection, they agree to teach you a sophisticated " "spell - '%{spell}'." msgstr "" "Você se depara com um santuário generoso com a participação de um grupo de " "sacerdotes.\n" "Em troca de sua proteção, eles concordam em ensinar-lhe uma magia " "sofisticada - '%{spell}'." msgid "" "\n" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "\n" "Infelizmente, você não tem a sabedoria para compreender a magia, e você é " "incapaz de aprender." msgid "" "\n" "Unfortunately, you already have knowledge of this spell, so there is nothing " "more for them to teach you." msgstr "" msgid "" "\n" "Unfortunately, you have no Magic Book to record the spell with." msgstr "" "\n" "Infelizmente, você não tem nenhum livro de magia para gravar com a magia." msgid "" "You approach the hut and observe a witch inside studying an ancient tome on " "%{skill}.\n" "\n" msgstr "" "Você aproxima da cabana e observa uma bruxa dentro, estudando um antigo " "livro em %{skill}.\n" "\n" msgid "" "As you approach, she turns and focuses her one glass eye on you.\n" "\"You already know everything you deserve to learn!\" the witch screeches. " "\"NOW GET OUT OF MY HOUSE!\"" msgstr "" "Quando você se aproxima, ela se vira e se concentra um olho de vidro em " "você.\n" "\"Você já sabe tudo o que você merece a aprender!\" os gritos da bruxa. " "\"Agora sai da minha casa!\"" msgid "" "As you approach, she turns and speaks.\n" "\"You already know that which I would teach you. I can help you no further.\"" msgstr "" "Quando você se aproxima, ela se vira e fala.\n" "\"Você já sabe o que eu ia te ensinar. Eu não posso te ajudar mais.\"" msgid "" "An ancient and immortal witch living in a hut with bird's legs for stilts " "teaches you %{skill} for her own inscrutable purposes." msgstr "" "Uma bruxa antiga e imortal vivendo em uma cabana com patas de pássaro de " "palafitas ensina %{skill} pelos seus próprios propósitos incompreensíveis." msgid "As you drink the sweet water, you gain luck for your next battle." msgstr "" "Como você bebeu a água doce, você ganha sorte para sua proxima batalha." msgid "You drink from the enchanted fountain, but nothing happens." msgstr "Você bebe da fonte encantada, mas nada acontece." msgid "You enter the faerie ring, but nothing happens." msgstr "Você entra no círculo das fadas, mas nada que acontece." msgid "" "Upon entering the mystical faerie ring, your army gains luck for its next " "battle." msgstr "" "Ao entrar no círculo da fada mística, o seu exército ganha sorte para a sua " "próxima batalha." msgid "" "You've found an ancient and weathered stone idol.\n" "It is supposed to grant luck to visitors, but since the stars are already " "smiling upon you, it does nothing." msgstr "" "Você encontrou um ídolo de pedra antiga e desgastada.\n" "Supõe-se isso garante sorte aos visitantes, mas desde que as estrelas já " "estão sorrindo em cima de você, não fez nada." msgid "" "You've found an ancient and weathered stone idol.\n" "Kissing it is supposed to be lucky, so you do. The stone is very cold to the " "touch." msgstr "" "Você encontrou um ídolo de pedra antiga e desgastada.\n" "Beijar é Supõe-se ter sorte, assim que você faz. A pedra é muito fria ao " "toque." msgid "The mermaids silently entice you to return later and be blessed again." msgstr "" "As sereias silenciosamente seduzi-o a retornar mais tarde e ser abençoada " "novamente." msgid "" "The magical, soothing beauty of the Mermaids reaches you and your crew.\n" "Just for a moment, you forget your worries and bask in the beauty of the " "moment.\n" "The mermaids charms bless you with increased luck for your next combat." msgstr "" "A beleza, mágica e tranquila das Sereias chega a você e sua tripulação.\n" "Só por um momento, você esquece das preocupações e relaxar na beleza do " "momento.\n" "As sereias encanta e abençoa com maior sorte para o seu próximo combate." msgid "" "You come upon the pyramid of a great and ancient king.\n" "You are tempted to search it for treasure, but all the old stories warn of " "fearful curses and undead guardians.\n" "Will you search?" msgstr "" "Você chega em cima da pirâmide de um grande rei antigo.\n" "Está tentado a buscar um tesouro, mas todas as velhas histórias de maldições " "alerta sobre temerosos e guardiões mortos-vivos.\n" "Você vai buscar?" msgid "" "Upon defeating the monsters, you decipher an ancient glyph on the wall, " "telling the secret of the spell - '" msgstr "" "Após derrotar as criaturas, você decifrar um hieróglifo antigo na parede, " "contando o segredo da magia - '" msgid "Unfortunately, you have no Magic Book to record the spell with." msgstr "" "Infelizmente, você não tem nenhum livro mágico para gravar com a magia." msgid "" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "Infelizmente, você não tem a sabedoria para compreender a magia, e você é " "incapaz de aprender." msgid "" "You come upon the pyramid of a great and ancient king.\n" "Routine exploration reveals that the pyramid is completely empty." msgstr "" "Você chega em cima da pirâmide de um grande rei antigo.\n" "Exploração de rotina revela que a pirâmide está completamente vazia." msgid "" "A drink at the well is supposed to restore your spell points, but you are " "already at maximum." msgstr "" "Ao beber do poço, era suposto restaurar sua mana, mas você já está no máximo." msgid "A second drink at the well in one day will not help you." msgstr "A segunda bebida do poço em um dia não vai te ajudar." msgid "A drink from the well has restored your spell points to maximum." msgstr "Gole da agua do poço restaurou sua mana ao máximo." msgid "" "\"I'm sorry sir,\" The leader of the soldiers says, \"but you already know " "everything we have to teach.\"" msgstr "" "\"Me desculpe, senhor\", o líder dos soldados diz, \"mas você já sabe tudo o " "que temos para ensinar.\"" msgid "The soldiers living in the fort teach you a few new defensive tricks." msgstr "" "Os soldados que vivem no forte lhe ensinaa alguns novos truques de defesa." msgid "" "You've come upon a mercenary camp practicing their tactics. \"You're too " "advanced for us,\" the mercenary captain says. \"We can teach nothing more.\"" msgstr "" "Você vai ate um campo de mercenários praticando suas táticas. \"Você é muito " "avançado para nós\", diz o capitão mercenário. \"Nada poderemos lhe ensinar." "\"" msgid "" "You've come upon a mercenary camp practicing their tactics. The mercenaries " "welcome you and your troops and invite you to train with them." msgstr "" "Você vai ate um campo de mercenários praticando suas táticas. Os mercenários " "recebê-lo e convidá-lo à treinar com eles." msgid "\"Go 'way!\", the witch doctor barks, \"you know all I know.\"" msgstr "\"Vá Embora! Você sabe tudo o que sei.\"" msgid "" "An Orcish witch doctor living in the hut deepens your knowledge of magic by " "showing you how to cast stones, read portents, and decipher the intricacies " "of chicken entrails." msgstr "" "Um feiticeiro Orcish vivendo na cabana aprofunda seus conhecimentos de " "magia, mostrando-lhe como atirar pedras, leia portentos, e decifrar os " "meandros de vísceras de frango." msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, the Druids turn you away, indicating they have nothing " "new to teach you." msgstr "" "Você encontrou um grupo de druidas adorando em um de seus edifícios de pedra " "estranha. Silenciosamente, os druidas transformá-lo afastado, indicando que " "não têm nada de novo para ensinar-lhe." msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, they teach you new ways to cast spells." msgstr "" "Você encontrou um grupo de druidas adorando em um de seus edifícios de pedra " "estranha. Silenciosamente, eles lhe ensinam novas formas de magias." msgid "" "You tentatively approach the burial ground of ancient warriors. Do you want " "to search the graves?" msgstr "" "Você avista um grupo de querreiros antigos. Você deseja pesquisar as " "sepulturas?" msgid "" "You spend several hours searching the graves and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" "Após derrotar os zumbis que você passar várias horas pesquisando as " "sepulturas e não encontrar nada. Um ato tão desprezível reduz a moral de " "seu exército." #, fuzzy msgid "Upon defeating the Zombies you search the graves and find something!" msgstr "" "Após derrotar o zumbis você pesquisar os túmulos e encontrar alguma coisa!" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the shipwreck?" msgstr "" "A podridão do casco de um navio pirata range assustadoramente grande quando " "ele é empurrado contra as rochas. Do que você deseja pesquisar o naufrágio?" msgid "" "You spend several hours sifting through the debris and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" "Após derrotar os fantasmas que passar várias horas peneirando os detritos e " "não encontrar nada. Um ato tão desprezível reduz a moral de seu exército." msgid "" "Upon defeating the Ghosts you sift through the debris and find something!" msgstr "" "Após derrotar os fantasmas que peneira os detritos e encontrar alguma coisa!" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the ship?" msgstr "" "A podridão do casco de um navio pirata range assustadoramente grande quando " "ele é empurrado contra as rochas. Você deseja pesquisar o navio?" msgid "" "Upon defeating the Skeletons you sift through the debris and find something!" msgstr "" msgid "Your men spot a navigational buoy, confirming that you are on course." msgstr "" msgid "" "Your men spot a navigational buoy, confirming that you are on course and " "increasing their morale." msgstr "" msgid "" "The drink at the oasis is refreshing, but offers no further benefit. The " "oasis might help again if you fought a battle first." msgstr "" msgid "" "A drink at the oasis fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" msgid "" "The drink at the watering hole is refreshing, but offers no further benefit. " "The watering hole might help again if you fought a battle first." msgstr "" msgid "" "A drink at the watering hole fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" msgid "" "It doesn't help to pray twice before a battle. Come back after you've fought." msgstr "" msgid "A visit and a prayer at the temple raises the morale of your troops." msgstr "" msgid "" "An old Knight appears on the steps of the gazebo. \"I am sorry, my liege, I " "have taught you all I can.\"" msgstr "" msgid "" "An old Knight appears on the steps of the gazebo. \"My liege, I will teach " "you all that I know to aid you in your travels.\"" msgstr "" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he says, \"I would give you an artifact as a reward, but " "you're all full.\"" msgstr "" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he rewards you for your act of kindness by giving you the " "%{art}." msgstr "" msgid "A leprechaun offers you the %{art} for the small price of %{gold} Gold." msgstr "" msgid "" "A leprechaun offers you the %{art} for the small price of %{gold} Gold and " "%{count} %{res}." msgstr "" msgid "Do you wish to buy this artifact?" msgstr "" msgid "" "You try to pay the leprechaun, but realize that you can't afford it. The " "leprechaun stamps his foot and ignores you." msgstr "" msgid "" "Insulted by your refusal of his generous offer, the leprechaun stamps his " "foot and ignores you." msgstr "" msgid "You've found the artifact: " msgstr "" msgid "" "You've found the humble dwelling of a withered hermit. The hermit tells you " "that he is willing to give the %{art} to the first wise person he meets." msgstr "" msgid "" "You've come across the spartan quarters of a retired soldier. The soldier " "tells you that he is willing to pass on the %{art} to the first true leader " "he meets." msgstr "" msgid "" "You've encountered a strange person with a hat and an owl on it. He tells " "you that he is willing to give %{art} if you have %{skill}." msgstr "" msgid "" "You come upon an ancient artifact. As you reach for it, a pack of Rogues " "leap out of the brush to guard their stolen loot." msgstr "" msgid "" "Through a clearing you observe an ancient artifact. Unfortunately, it's " "guarded by a nearby %{monster}. Do you want to fight the %{monster} for the " "artifact?" msgstr "" msgid "Victorious, you take your prize, the %{art}." msgstr "" msgid "" "Discretion is the better part of valor, and you decide to avoid this fight " "for today." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold pieces." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold and the %{art}." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it, " "only to find it empty." msgstr "" msgid "" "After scouring the area, you fall upon a hidden treasure cache. You may take " "the gold or distribute the gold to the peasants for experience. Do you wish " "to keep the gold?" msgstr "" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "%{gold} gold pieces." msgstr "" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "ancient artifact %{art}." msgstr "" msgid "" "You stumble upon a dented and tarnished lamp lodged deep in the earth. Do " "you wish to rub the lamp?" msgstr "" msgid "" "You have taken control of the local Alchemist shop. It will provide you with " "%{count} unit of Mercury per day." msgstr "" msgid "" "You gain control of a sawmill. It will provide you with %{count} units of " "wood per day." msgstr "" msgid "" "You gain control of an ore mine. It will provide you with %{count} units of " "ore per day." msgstr "" msgid "" "You gain control of a sulfur mine. It will provide you with %{count} unit of " "sulfur per day." msgstr "" msgid "" "You gain control of a crystal mine. It will provide you with %{count} unit " "of crystal per day." msgstr "" msgid "" "You gain control of a gem mine. It will provide you with %{count} unit of " "gems per day." msgstr "" msgid "" "You gain control of a gold mine. It will provide you with %{count} gold per " "day." msgstr "" msgid "" "The lighthouse is now under your control, and all of your ships will now " "move further each day." msgstr "" msgid "You gain control of a %{name}." msgstr "" msgid "" "You come upon an abandoned gold mine. The mine appears to be haunted. Do you " "wish to enter?" msgstr "" msgid "You beat the Ghosts and are able to restore the mine to production." msgstr "" msgid "" "A group of %{monster} with a desire for greater glory wish to join you. Do " "you accept?" msgstr "" msgid "As you approach the dwelling, you notice that there is no one here." msgstr "" msgid "" "You search the ruins, but the Medusas that used to live here are gone. " "Perhaps there will be more next week." msgstr "" msgid "" "You've found some Medusas living in the ruins. They are willing to join your " "army for a price. Do you want to recruit Medusas?" msgstr "" msgid "" "You've found a Sprite Tree City. Unfortunately, none of the Sprites living " "there wish to join an army. Maybe next week." msgstr "" msgid "" "Some of the Sprites living in the tree city are willing to join your army " "for a price. Do you want to recruit Sprites?" msgstr "" msgid "" "A colorful Rogues' wagon stands empty here. Perhaps more Rogues will be here " "later." msgstr "" msgid "" "Distant sounds of music and laughter draw you to a colorful wagon housing " "Rogues. Do you wish to have any Rogues join your army?" msgstr "" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. The " "tents are unoccupied. Perhaps more Nomads will be here later." msgstr "" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. Do you " "wish to have any Nomads join you during your travels?" msgstr "" msgid "The pit of mud bubbles for a minute and then lies still." msgstr "" msgid "" "As you approach the bubbling pit of mud, creatures begin to climb out and " "position themselves around it. In unison they say: \"Mother Earth would like " "to offer you a few of her troops. Do you want to recruit Earth Elementals?\"" msgstr "" msgid "You enter the structure of white stone pillars, and find nothing." msgstr "" msgid "" "White stone pillars support a roof that rises up to the sky. As you enter " "the structure, the dead air of the outside gives way to a whirling gust that " "almost pushes you back out. The air current materializes into a barely " "visible form. The creature asks, in what can only be described as a loud " "whisper: \"Why have you come? Are you here to call upon the forces of the " "air?\"" msgstr "" msgid "No Fire Elementals approach you from the lava pool." msgstr "" msgid "" "Beneath a structure that serves to hold in heat, Fire Elementals move about " "in a fiery pool of molten lava. A group of them approach you and offer their " "services. Would you like to recruit Fire Elementals?" msgstr "" msgid "A face forms in the water for a moment, and then is gone." msgstr "" msgid "" "Crystalline structures cast shadows over a small reflective pool of water. " "You peer into the pool, and a face that is not your own peers back. It asks: " "\"Would you like to call upon the powers of water?\"" msgstr "" msgid "This burial site is deathly still." msgstr "" msgid "" "Restless spirits of long dead warriors seeking their final resting place " "offer to join you in hopes of finding peace. Do you wish to recruit ghosts?" msgstr "" msgid "" "The City of the Dead is empty of life, and empty of unlife as well. Perhaps " "some undead will move in next week." msgstr "" msgid "" "Some Liches living here are willing to join your army for a price. Do you " "want to recruit Liches?" msgstr "" msgid "" "You've found the ruins of an ancient city, now inhabited solely by the " "undead. Will you search?" msgstr "" msgid "" "Some of the surviving Liches are impressed by your victory over their " "fellows, and offer to join you for a price. Do you want to recruit Liches?" msgstr "" msgid "" "You've found one of those bridges that Trolls are so fond of living under, " "but there are none here. Perhaps there will be some next week." msgstr "" msgid "" "Some Trolls living under a bridge are willing to join your army, but for a " "price. Do you want to recruit Trolls?" msgstr "" msgid "Trolls living under the bridge challenge you. Will you fight them?" msgstr "" msgid "" "A few Trolls remain, cowering under the bridge. They approach you and offer " "to join your forces as mercenaries. Do you want to buy any Trolls?" msgstr "" msgid "" "The Dragon city has no Dragons willing to join you this week. Perhaps a " "Dragon will become available next week." msgstr "" msgid "" "The Dragon city is willing to offer some Dragons for your army for a price. " "Do you wish to recruit Dragons?" msgstr "" msgid "" "You stand before the Dragon City, a place off-limits to mere humans. Do you " "wish to violate this rule and challenge the Dragons to a fight?" msgstr "" msgid "" "Having defeated the Dragon champions, the city's leaders agree to supply " "some Dragons to your army for a price. Do you wish to recruit Dragons?" msgstr "" msgid "From the observation tower, you are able to see distant lands." msgstr "" msgid "" "The spring only refills once a week, and someone's already been here this " "week." msgstr "" msgid "" "A drink at the spring is supposed to give you twice your normal spell " "points, but you are already at that level." msgstr "" msgid "" "A drink from the spring fills your blood with magic! You have twice your " "normal spell points in reserve." msgstr "" msgid "" "Recognizing you, the butler refuses to admit you. \"The master,\" he says, " "\"will not see the same student twice.\"" msgstr "" msgid "" "The butler admits you to see the master of the house. He trains you in the " "four skills a hero should know." msgstr "" msgid "" "The butler opens the door and looks you up and down. \"You are neither " "famous nor diplomatic enough to be admitted to see my master,\" he sniffs. " "\"Come back when you think yourself worthy.\"" msgstr "" msgid " and " msgstr "" msgid "" "All of the %{monsters} you have in your army have been trained by the battle " "masters of the fort. Your army now contains %{monsters2}." msgstr "" msgid "" "An unusual alliance of Ogres, Orcs, and Dwarves offer to train (upgrade) any " "such troops brought to them. Unfortunately, you have none with you." msgstr "" msgid "All of your %{monsters} have been upgraded into %{monsters2}." msgstr "" msgid "" "A blacksmith working at the foundry offers to convert all Pikemen and " "Swordsmen's weapons brought to him from iron to steel. He also says that he " "knows a process that will convert Iron Golems into Steel Golems. " "Unfortunately, you have none of these troops in your army, so he can't help " "you." msgstr "" msgid "" "The captain looks at you with surprise and says:\n" "\"You already have all the maps I know about. Let me fish in peace now.\"" msgstr "" msgid "" "A retired captain living on this refurbished fishing platform offers to sell " "you maps of the sea he made in his younger days for 1,000 gold. Do you wish " "to buy the maps?" msgstr "" msgid "" "The captain sighs. \"You don't have enough money, eh? You can't expect me " "to give my maps away for free!\"" msgstr "" msgid "" "You come upon an obelisk made from a type of stone you have never seen " "before. Staring at it intensely, the smooth surface suddenly changes to an " "inscription. The inscription is a piece of a lost ancient map. Quickly you " "copy down the piece and the inscription vanishes as abruptly as it appeared." msgstr "" msgid "You have already been to this obelisk." msgstr "" msgid "" "Upon your approach, the tree opens its eyes in delight. \"It is good to see " "you, my student. I hope my teachings have helped you.\"" msgstr "" msgid "" "Upon your approach, the tree opens its eyes in delight. \"Ahh, an " "adventurer! Allow me to teach you a little of what I have learned over the " "ages.\"" msgstr "" msgid "Upon your approach, the tree opens its eyes in delight." msgstr "" msgid "" "\"Ahh, an adventurer! I will be happy to teach you a little of what I have " "learned over the ages for a mere %{count} %{res}.\"" msgstr "" msgid "(Just bury it around my roots.)" msgstr "" msgid "Tears brim in the eyes of the tree." msgstr "" msgid "\"I need %{count} %{res}.\"" msgstr "" msgid "it whispers. (sniff) \"Well, come back when you can pay me.\"" msgstr "" msgid "" "Nestled among the trees sits a blind seer. After you explain the intent of " "your journey, the seer activates his crystal ball, allowing you to see the " "strengths and weaknesses of your opponents." msgstr "" msgid "" "The entrance to the cave is dark, and a foul, sulfurous smell issues from " "the cave mouth. Will you enter?" msgstr "" msgid "Except for evidence of a terrible battle, the cave is empty." msgstr "" msgid "" "You find a powerful and grotesque Demon in the cave. \"Today,\" it rasps, " "\"you will fight and surely die. But I will give you a choice of deaths. You " "may fight me, or you may fight my servants. Do you prefer to fight my " "servants?\"" msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points." msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points and " "%{count} gold." msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and find the %{art} in the back of the cave." msgstr "" msgid "" "Seeing that you do not have %{count} gold, the demon slashes you with its " "claws, and the last thing you see is a red haze." msgstr "" msgid "" "The Demon leaps upon you and has its claws at your throat before you can " "even draw your sword. \"Your life is mine,\" it says. \"I will sell it back " "to you for %{count} gold.\"" msgstr "" msgid "" "Upon defeating the daemon's servants, you find a hidden cache with %{count} " "gold." msgstr "" msgid "" "As you enter the Alchemist's Tower, a hobbled, graying man in a brown cloak " "makes his way towards you." msgstr "" msgid "He checks your pack, and sees that you have 1 cursed item." msgid_plural "" "He checks your pack, and sees that you have %{count} cursed items." msgstr[0] "" msgstr[1] "" msgid "For %{gold} gold, the alchemist will remove it for you. Do you pay?" msgid_plural "" "For %{gold} gold, the alchemist will remove them for you. Do you pay?" msgstr[0] "" msgstr[1] "" msgid "" "After you consent to pay the requested amount of gold, the alchemist grabs " "the cursed artifact and throws it into his magical cauldron." msgid_plural "" "After you consent to pay the requested amount of gold, the alchemist grabs " "all cursed artifacts and throws them into his magical cauldron." msgstr[0] "" msgstr[1] "" msgid "" "You hear a voice from behind the locked door, \"You don't have enough gold " "to pay for my services.\"" msgstr "" msgid "" "You hear a voice from high above in the tower, \"Go away! I can't help you!\"" msgstr "" msgid "" "The head groom speaks to you, \"That is a fine looking horse you have. I am " "afraid we can give you no better, but the horses your cavalry are riding " "look to be of poor breeding stock. We have many trained war horses which " "would aid your riders greatly. I insist you take them.\"" msgstr "" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, he will " "grow tired in a week. You must also let me give better horses to your " "mounted soldiers, their horses look shoddy and weak.\"" msgstr "" msgid "" "The head groom approaches you and speaks, \"You already have a fine horse, " "and have no inexperienced cavalry which might make use of our trained war " "horses.\"" msgstr "" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, his " "endurance will wane with a lot of heavy riding, and you must return for a " "fresh mount in a week. We also have many fine war horses which could benefit " "mounted soldiers, but you have none we can help.\"" msgstr "" msgid "The Arena guards turn you away." msgstr "" msgid "" "You have your crew stop up their ears with wax before the sirens' eerie song " "has any chance of luring them to a watery grave." msgstr "" msgid "" "As the sirens sing their eerie song, your small, determined army manages to " "overcome the urge to dive headlong into the sea." msgstr "" msgid "" "An eerie wailing song emanates from the sirens perched upon the rocks. Many " "of your crew fall under its spell, and dive into the water where they drown. " "You are now wiser for the visit, and gain %{exp} experience." msgstr "" msgid "" "In a dazzling display of daring, you break into the local jail and free the " "hero imprisoned there, who, in return, pledges loyalty to your cause." msgstr "" msgid "" "You already have %{count} heroes, and regretfully must leave the prisoner in " "this jail to languish in agony for untold days." msgstr "" msgid "" "You enter a rickety hut and talk to the magician who lives there. He tells " "you of places near and far which may aid you in your journeys." msgstr "" msgid "This eye seems to be intently studying its surroundings." msgstr "" msgid "You come across a giant Sphinx. The Sphinx remains strangely quiet." msgstr "" msgid "" "\"I have a riddle for you,\" the Sphinx says. \"Answer correctly, and you " "shall be rewarded. Answer incorrectly, and you shall be eaten. Do you accept " "the challenge?\"" msgstr "" msgid "" "The Sphinx asks you the following riddle:\n" "\n" "'%{riddle}'\n" "\n" "Your answer?" msgstr "" msgid "" "\"You guessed incorrectly,\" the Sphinx says, smiling. The Sphinx swipes at " "you with a paw, knocking you to the ground. Another blow makes the world go " "black, and you know no more." msgstr "" msgid "" "Looking somewhat disappointed, the Sphinx sighs. \"You've answered my riddle " "so here's your reward. Now begone.\"" msgstr "" msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "As you speak the magic word, the glowing barrier dissolves into nothingness." msgstr "" msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "You speak, and nothing happens." msgstr "" msgid "" "You enter the tent and see an old woman gazing into a magic gem. She looks " "up and says,\n" "\"In my travels, I have learned much in the way of arcane magic. A great " "oracle taught me his skill. I have the answer you seek.\"" msgstr "" msgid "No spell book is present." msgstr "" msgid "" "This spell costs %{mana} spell points. You have no spell points, so you " "cannot cast it." msgstr "" msgid "" "This spell costs %{mana} spell points. You only have %{point} spell points, " "so you cannot cast it." msgstr "" #, fuzzy msgid "The spell was not found." msgstr "Esta magia não afetará ninguem!" msgid "Only heroes can cast this spell." msgstr "" #, fuzzy msgid "This hero is not able to cast adventure spells." msgstr "Lançar uma magia." msgid "Your hero is too tired to cast this spell today. Try again tomorrow." msgstr "" #, fuzzy msgid "This spell cannot be cast on a boat." msgstr "Esta cidade não pode ser transformada em castelo." #, fuzzy msgid "This spell can only be cast near an ocean." msgstr "Esta cidade não pode ser transformada em castelo." msgid "" "There are no boats available and no ocean adjacent to the hero where this " "spell will work." msgstr "" #, fuzzy msgid "There are no boats available for this spell." msgstr "Lançar uma magia." #, fuzzy msgid "There is no ocean adjacent to the hero where this spell will work." msgstr "Não há espaço adjacente aberto para o seu herói lançar um Elemento." msgid "" "You do not own any town or castle that is not currently occupied by a hero. " "This spell will have no effect." msgstr "" #, fuzzy msgid "This hero is already in a town, so this spell will have no effect." msgstr "Lançar uma magia." msgid "" "The nearest town is %{town}.\n" "\n" "This town is occupied by your hero %{hero}." msgstr "" msgid "This spell is already in effect." msgstr "" msgid "" "No opponent neither has nor can have any hero under their command anymore. " "Casting this spell will have no effect." msgstr "" msgid "" "No opponent has a hero under their command at this time. Casting this spell " "will have no effect." msgstr "" msgid "" "You must be within %{count} spaces of a monster for the Visions spell to " "work." msgstr "" msgid "" "You must be standing on the entrance to a mine (sawmills and alchemist labs " "do not count) to cast this spell." msgstr "" msgid "You must first defeat the ghosts guarding the mine to cast this spell." msgstr "" msgid "" "There are already at least as many elementals guarding the mine as this hero " "can generate. Casting this spell will have no effect." msgstr "" #, fuzzy msgid "%{name} the %{race} (Level %{level})" msgstr "%{name} a %{race}" #, fuzzy msgid "" "'Spread' combat formation spreads the hero's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "Formação de combate \"Espalhada\" espalha seus exércitos a partir do topo " "ao fundo do campo de batalha, com pelo menos um espaço vazio entre cada " "exército." #, fuzzy msgid "" "'Grouped' combat formation bunches the hero's army together in the center of " "their side of the battlefield." msgstr "" "Formação de combate \"Agrupada\" seu exército fica junto no centro da linha " "inicial do campo de batalha." msgid "Are you sure you want to dismiss this Hero?" msgstr "" msgid "View Experience Info" msgstr "" msgid "View Spell Points Info" msgstr "" msgid "Set army combat formation to 'Spread'" msgstr "" msgid "Set army combat formation to 'Grouped'" msgstr "" msgid "Exit Hero Screen" msgstr "" msgid "You cannot dismiss a hero in a castle" msgstr "" msgid "Dismissal of %{name} the %{race} is prohibited by scenario" msgstr "" #, fuzzy msgid "Dismiss %{name} the %{race}" msgstr "%{name} a %{race}" #, fuzzy msgid "Show previous hero" msgstr "Mostrar cidade anterior" #, fuzzy msgid "Show next hero" msgstr "Mostrar proxima cidade" #, fuzzy msgid "Blood Morale" msgstr "Moral" msgid "%{morale} Morale" msgstr "" msgid "%{luck} Luck" msgstr "" msgid "Current Luck Modifiers:" msgstr "" msgid "Current Morale Modifiers:" msgstr "" msgid "Entire army is undead, so morale does not apply." msgstr "" msgid "" "Current experience %{exp1}.\n" " Next level %{exp2}." msgstr "" msgid "Level %{level}" msgstr "" msgid "" "%{name} currently has %{point} spell points out of a maximum of %{max}. The " "maximum number of spell points is 10 times the hero's knowledge. It is " "occasionally possible for the hero to have more than their maximum spell " "points via special events." msgstr "" msgid "%{name1} meets %{name2}" msgstr "" msgid "Enemy heroes are now fully identifiable." msgstr "" msgid "Identify Hero" msgstr "" msgid "Select town to port to." msgstr "" msgid "Town Portal" msgstr "" msgid "The creatures are willing to join us!" msgstr "" msgid "All the creatures will join us..." msgstr "" msgid "The creature will join us..." msgid_plural "%{count} of the creatures will join us..." msgstr[0] "" msgstr[1] "" msgid "" "\n" " for a fee of %{gold} gold." msgstr "" msgid "These weak creatures will surely flee before us." msgstr "" msgid "I fear these creatures are in the mood for a fight." msgstr "" msgid "The hero's attack skill is a bonus added to each unit's attack skill." msgstr "" msgid "The hero's defense skill is a bonus added to each unit's defense skill." msgstr "" msgid "The hero's spell power determines the duration or power of a spell." msgstr "" msgid "" " The hero's knowledge determines how many spell points the hero may have. " "Under normal circumstances, a hero is limited to 10 spell points per level " "of knowledge." msgstr "" msgid "Current Modifiers:" msgstr "" msgid "skill|Basic" msgstr "" msgid "skill|Advanced" msgstr "" #, fuzzy msgid "skill|Expert" msgstr "Especialista" msgid "Pathfinding" msgstr "" msgid "Archery" msgstr "" msgid "Logistics" msgstr "" msgid "Scouting" msgstr "" msgid "Diplomacy" msgstr "" msgid "Navigation" msgstr "" msgid "Leadership" msgstr "" msgid "Wisdom" msgstr "" msgid "Mysticism" msgstr "" msgid "Ballistics" msgstr "" msgid "Eagle Eye" msgstr "" msgid "Necromancy" msgstr "" msgid "Estates" msgstr "" msgid "Advanced Archery" msgstr "" msgid "Advanced Pathfinding" msgstr "" msgid "Basic Archery" msgstr "" msgid "Basic Pathfinding" msgstr "" msgid "Expert Pathfinding" msgstr "" msgid "Advanced Logistics" msgstr "" msgid "Basic Logistics" msgstr "" msgid "Basic Scouting" msgstr "" #, fuzzy msgid "Expert Archery" msgstr "Especialista" msgid "Expert Logistics" msgstr "" msgid "Advanced Diplomacy" msgstr "" msgid "Advanced Scouting" msgstr "" msgid "Basic Diplomacy" msgstr "" msgid "Expert Diplomacy" msgstr "" msgid "Expert Scouting" msgstr "" msgid "Advanced Leadership" msgstr "" msgid "Advanced Navigation" msgstr "" msgid "Basic Leadership" msgstr "" #, fuzzy msgid "Basic Navigation" msgstr "Escavação" #, fuzzy msgid "Expert Navigation" msgstr "Escavação" msgid "Advanced Wisdom" msgstr "" msgid "Basic Mysticism" msgstr "" msgid "Basic Wisdom" msgstr "" msgid "Expert Leadership" msgstr "" #, fuzzy msgid "Expert Wisdom" msgstr "Especialista" msgid "Advanced Luck" msgstr "" msgid "Advanced Mysticism" msgstr "" msgid "Basic Luck" msgstr "" #, fuzzy msgid "Expert Luck" msgstr "Especialista" msgid "Expert Mysticism" msgstr "" msgid "Advanced Ballistics" msgstr "" msgid "Advanced Eagle Eye" msgstr "" msgid "Basic Ballistics" msgstr "" msgid "Basic Eagle Eye" msgstr "" msgid "Expert Ballistics" msgstr "" msgid "Advanced Necromancy" msgstr "" msgid "Basic Estates" msgstr "" #, fuzzy msgid "Basic Necromancy" msgstr "Necromante:" msgid "Expert Eagle Eye" msgstr "" #, fuzzy msgid "Expert Necromancy" msgstr "Necromante:" msgid "Advanced Estates" msgstr "" #, fuzzy msgid "Expert Estates" msgstr "Especialista" msgid "" "%{skill} reduces the movement penalty for rough terrain by %{count} percent." msgstr "" msgid "%{skill} eliminates the movement penalty for rough terrain." msgstr "" msgid "" "%{skill} increases the damage done by the hero's range attacking creatures " "by %{count} percent." msgstr "" msgid "%{skill} increases the hero's movement points by %{count} percent." msgstr "" msgid "%{skill} increases the hero's viewable area by one square." msgid_plural "%{skill} increases the hero's viewable area by %{count} squares." msgstr[0] "" msgstr[1] "" msgid "" "%{skill} allows the hero to negotiate with monsters who are weaker than " "their army, and reduces the cost of surrender." msgstr "" msgid "" "Approximately %{count} percent of the creatures may offer to join the hero." msgstr "" msgid "All of the creatures may offer to join the hero." msgstr "" msgid "" "The cost of surrender is reduced to %{percent} percent of the total cost of " "troops in the army." msgstr "" msgid "" "%{skill} increases the hero's movement points over water by %{count} percent." msgstr "" msgid "%{skill} increases the hero's troops morale by %{count}." msgstr "" msgid "%{skill} allows the hero to learn third level spells." msgstr "" msgid "%{skill} allows the hero to learn fourth level spells." msgstr "" msgid "%{skill} allows the hero to learn fifth level spells." msgstr "" msgid "%{skill} regenerates one additional spell point per day to the hero." msgid_plural "" "%{skill} regenerates %{count} additional spell points per day to the hero." msgstr[0] "" msgstr[1] "" msgid "%{skill} increases the hero's luck by %{count}." msgstr "" msgid "" "%{skill} gives the hero's catapult shots a greater chance to hit and do " "damage to castle walls." msgstr "" msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot has a " "greater chance to hit and do damage to castle walls." msgstr "" msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot " "automatically destroys any wall, except a fortified wall in a Knight castle." msgstr "" msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 1st or " "2nd level spell that was cast by an enemy during combat." msgstr "" msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 3rd " "level spell (or below) that was cast by an enemy during combat." msgstr "" msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 4th " "level spell (or below) that was cast by an enemy during combat." msgstr "" msgid "" "%{skill} allows %{count} percent of the creatures killed in combat to be " "brought back from the dead as Skeletons." msgstr "" msgid "" "The hero produces %{count} gold pieces per day as tax revenue from estates." msgstr "" msgid "Blue" msgstr "" msgid "Green" msgstr "" msgid "Red" msgstr "" msgid "Yellow" msgstr "" msgid "Orange" msgstr "" msgid "Purple" msgstr "" msgid "barrier|Aqua" msgstr "" msgid "barrier|Blue" msgstr "" msgid "barrier|Brown" msgstr "" msgid "barrier|Gold" msgstr "" msgid "barrier|Green" msgstr "" msgid "barrier|Orange" msgstr "" msgid "barrier|Purple" msgstr "" msgid "barrier|Red" msgstr "" msgid "tent|Aqua" msgstr "" msgid "tent|Blue" msgstr "" #, fuzzy msgid "tent|Brown" msgstr "animação" msgid "tent|Gold" msgstr "" msgid "tent|Green" msgstr "" msgid "tent|Orange" msgstr "" msgid "tent|Purple" msgstr "" msgid "tent|Red" msgstr "" #, fuzzy msgid "Experience" msgstr "Experimental" msgid "" "Experience allows your heroes to go up levels, increasing their primary and " "secondary skills." msgstr "" #, fuzzy msgid "Hero/Stats" msgstr "Melhor Herói em Stats:" msgid "Skills" msgstr "" #, fuzzy msgid "Town/Castle" msgstr "Castelo" msgid "Garrison" msgstr "" #, fuzzy msgid "Gold Per Day:" msgstr "Ouro Total:" #, fuzzy msgid "View Heroes." msgstr "Ver Herói" #, fuzzy msgid "Towns/Castles" msgstr "Castelo" msgid "View Towns and Castles." msgstr "" msgid "luck|Cursed" msgstr "" msgid "luck|Awful" msgstr "" msgid "luck|Bad" msgstr "" msgid "luck|Normal" msgstr "" msgid "luck|Good" msgstr "" msgid "luck|Great" msgstr "" msgid "luck|Irish" msgstr "" msgid "" "Bad luck sometimes falls on the hero's units in combat, causing their " "attacks to only do half damage." msgstr "" msgid "" "Neutral luck means the hero's units will never get lucky or unlucky attacks " "on the enemy." msgstr "" msgid "" "Good luck sometimes lets the hero's units get lucky attacks (double " "strength) in combat." msgstr "" msgid "morale|Treason" msgstr "" msgid "morale|Awful" msgstr "" msgid "morale|Poor" msgstr "" msgid "morale|Normal" msgstr "" msgid "morale|Good" msgstr "" msgid "morale|Great" msgstr "" msgid "morale|Blood!" msgstr "" msgid "Bad morale may cause the hero's units to freeze in combat." msgstr "" msgid "" "Neutral morale means the hero's units will never be blessed with extra " "attacks or freeze in combat." msgstr "" msgid "Good morale may give the hero's units extra attacks in combat." msgstr "" msgid "Knight" msgstr "" msgid "Barbarian" msgstr "" msgid "Sorceress" msgstr "" msgid "Warlock" msgstr "" msgid "Wizard" msgstr "" msgid "Necromancer" msgstr "" msgid "Multi" msgstr "" msgid "doubleLined|Knight" msgstr "" msgid "doubleLined|Barbarian" msgstr "" msgid "doubleLined|Sorceress" msgstr "" msgid "doubleLined|Warlock" msgstr "" msgid "doubleLined|Wizard" msgstr "" msgid "" "doubleLined|Necro-\n" "mancer" msgstr "" msgid "doubleLinedRace|Multi" msgstr "" msgid "doubleLinedRace|Random" msgstr "" msgid "speed|Standing" msgstr "" msgid "speed|Crawling" msgstr "" msgid "speed|Very Slow" msgstr "" msgid "speed|Slow" msgstr "" msgid "speed|Average" msgstr "" msgid "speed|Fast" msgstr "" msgid "speed|Very Fast" msgstr "" msgid "speed|Ultra Fast" msgstr "" msgid "speed|Blazing" msgstr "" msgid "speed|Instant" msgstr "" msgid "week|Squirrel" msgstr "" msgid "week|Rabbit" msgstr "" msgid "week|Gopher" msgstr "" msgid "week|Badger" msgstr "" #, fuzzy msgid "week|Rat" msgstr "semana" msgid "week|Eagle" msgstr "" msgid "week|Weasel" msgstr "" msgid "week|Raven" msgstr "" msgid "week|Mongoose" msgstr "" #, fuzzy msgid "week|Dog" msgstr "semana" msgid "week|Aardvark" msgstr "" msgid "week|Lizard" msgstr "" msgid "week|Tortoise" msgstr "" msgid "week|Hedgehog" msgstr "" msgid "week|Condor" msgstr "" msgid "week|Ant" msgstr "" msgid "week|Grasshopper" msgstr "" msgid "week|Dragonfly" msgstr "" msgid "week|Spider" msgstr "" msgid "week|Butterfly" msgstr "" msgid "week|Bumblebee" msgstr "" msgid "week|Locust" msgstr "" msgid "week|Earthworm" msgstr "" msgid "week|Hornet" msgstr "" msgid "week|Beetle" msgstr "" msgid "week|PLAGUE" msgstr "" msgid "week|Unnamed" msgstr "" msgid "Desert" msgstr "Deserto" msgid "Snow" msgstr "Neve" msgid "Wasteland" msgstr "Terreno Baldio" msgid "Beach" msgstr "Praia" msgid "Lava" msgstr "Lava" msgid "Dirt" msgstr "Lama" msgid "Grass" msgstr "Grama" msgid "Ocean" msgstr "" msgid "maps|Small" msgstr "" msgid "maps|Medium" msgstr "" msgid "maps|Large" msgstr "" msgid "maps|Extra Large" msgstr "" msgid "maps|Custom Size" msgstr "" msgid "Ore Mine" msgstr "" msgid "Sulfur Mine" msgstr "" msgid "Crystal Mine" msgstr "" msgid "Gems Mine" msgstr "" msgid "Gold Mine" msgstr "" msgid "Mine" msgstr "" msgid "Burma shave." msgstr "" msgid "Next sign 50 miles." msgstr "" msgid "See Rock City." msgstr "" msgid "This space for rent." msgstr "" msgid "No object" msgstr "" msgid "Alchemist Lab" msgstr "" msgid "Sign" msgstr "" msgid "Buoy" msgstr "" msgid "Skeleton" msgstr "" msgid "Daemon Cave" msgstr "" msgid "Treasure Chest" msgstr "" msgid "Faerie Ring" msgstr "" msgid "Campfire" msgstr "" msgid "Fountain" msgstr "" msgid "Gazebo" msgstr "" msgid "Genie Lamp" msgstr "" msgid "Archer's House" msgstr "" msgid "Goblin Hut" msgstr "" msgid "Dwarf Cottage" msgstr "" msgid "Peasant Hut" msgstr "" msgid "Stables" msgstr "" msgid "Alchemist's Tower" msgstr "" msgid "Event" msgstr "" msgid "Dragon City" msgstr "" msgid "Lighthouse" msgid_plural "Lighthouses" msgstr[0] "" msgstr[1] "" msgid "Water Wheel" msgid_plural "Water Wheels" msgstr[0] "" msgstr[1] "" msgid "Monster" msgstr "" msgid "Obelisk" msgstr "" msgid "Oasis" msgstr "" msgid "Resource" msgstr "" msgid "Sawmill" msgstr "" msgid "Oracle" msgstr "" msgid "Shrine of the First Circle" msgstr "" msgid "Shipwreck" msgstr "" msgid "Sea Chest" msgstr "" msgid "Desert Tent" msgstr "" msgid "Stone Liths" msgstr "" msgid "Wagon Camp" msgstr "" msgid "Hut of the Magi" msgstr "" msgid "Whirlpool" msgstr "" msgid "Windmill" msgid_plural "Windmills" msgstr[0] "" msgstr[1] "" msgid "Artifact" msgstr "Artefato" msgid "Mermaid" msgstr "" msgid "Boat" msgstr "" #, fuzzy msgid "Random Ultimate Artifact" msgstr "Escavar em busca do Artefato Supremo." #, fuzzy msgid "Random Artifact" msgstr "Artefato" #, fuzzy msgid "Random Resource" msgstr "Seus Recursos" #, fuzzy msgid "Random Monster" msgstr "Criaturas" msgid "Random Town" msgstr "" msgid "Random Castle" msgstr "" msgid "Eye of the Magi" msgstr "" #, fuzzy msgid "Random Monster - weak" msgstr "Criaturas" #, fuzzy msgid "Random Monster - medium" msgstr "Criaturas" #, fuzzy msgid "Random Monster - strong" msgstr "Criaturas" #, fuzzy msgid "Random Monster - very strong" msgstr "Criaturas" #, fuzzy msgid "Hero" msgstr "Heróis" msgid "Nothing Special" msgstr "" msgid "Mossy Rock" msgstr "" msgid "Watch Tower" msgstr "" msgid "Tree House" msgstr "" msgid "Tree City" msgstr "" msgid "Ruins" msgstr "" msgid "Fort" msgstr "" msgid "Abandoned Mine" msgstr "" msgid "Sirens" msgstr "" msgid "Standing Stones" msgstr "" msgid "Idol" msgstr "" msgid "Tree of Knowledge" msgstr "" msgid "Witch Doctor's Hut" msgstr "" msgid "Temple" msgstr "" msgid "Hill Fort" msgstr "" msgid "Halfling Hole" msgstr "" msgid "Mercenary Camp" msgstr "" msgid "Shrine of the Second Circle" msgstr "" msgid "Shrine of the Third Circle" msgstr "" msgid "City of the Dead" msgstr "" msgid "Sphinx" msgstr "" msgid "Wagon" msgstr "" msgid "Tar Pit" msgstr "" msgid "Artesian Spring" msgstr "" msgid "Troll Bridge" msgstr "" msgid "Watering Hole" msgstr "" msgid "Witch's Hut" msgstr "" msgid "Xanadu" msgstr "" msgid "Lean-To" msgstr "" msgid "Magellan's Maps" msgstr "" msgid "Flotsam" msgstr "" msgid "Derelict Ship" msgstr "" msgid "Shipwreck Survivor" msgstr "" msgid "Bottle" msgstr "" msgid "Magic Well" msgstr "" msgid "Magic Garden" msgid_plural "Magic Gardens" msgstr[0] "" msgstr[1] "" msgid "Observation Tower" msgstr "" msgid "Freeman's Foundry" msgstr "" msgid "Reefs" msgstr "" msgid "Volcano" msgstr "" msgid "Flowers" msgstr "" msgid "Rock" msgstr "" msgid "Water Lake" msgstr "" msgid "Mandrake" msgstr "" msgid "Dead Tree" msgstr "" msgid "Stump" msgstr "" msgid "Crater" msgstr "" msgid "Cactus" msgstr "" msgid "Mound" msgstr "" msgid "Dune" msgstr "" msgid "Lava Pool" msgstr "" msgid "Shrub" msgstr "" msgid "Barrow Mounds" msgstr "" msgid "Random Artifact - Treasure" msgstr "" msgid "Random Artifact - Minor" msgstr "" msgid "Random Artifact - Major" msgstr "" msgid "Barrier" msgstr "" msgid "Traveller's Tent" msgstr "" msgid "Jail" msgstr "" msgid "Fire Summoning Altar" msgstr "" msgid "Air Summoning Altar" msgstr "" msgid "Earth Summoning Altar" msgstr "" msgid "Water Summoning Altar" msgstr "" #, fuzzy msgid "Unknown Monster" msgstr "Criaturas" #, fuzzy msgid "Unknown Monsters" msgstr "Criaturas" msgid "Peasant" msgstr "" msgid "Peasants" msgstr "" #, fuzzy msgid "Archer" msgstr "Campo de Arqueiros" #, fuzzy msgid "Archers" msgstr "Campo de Arqueiros" msgid "Ranger" msgstr "" msgid "Rangers" msgstr "" msgid "Pikeman" msgstr "" msgid "Pikemen" msgstr "" msgid "Veteran Pikeman" msgstr "" msgid "Veteran Pikemen" msgstr "" msgid "Swordsman" msgstr "" msgid "Swordsmen" msgstr "" msgid "Master Swordsman" msgstr "" msgid "Master Swordsmen" msgstr "" msgid "Cavalries" msgstr "" msgid "Cavalry" msgstr "" msgid "Champion" msgstr "" #, fuzzy msgid "Champions" msgstr "Opções do Castelo" msgid "Paladin" msgstr "" msgid "Paladins" msgstr "" msgid "Crusader" msgstr "" msgid "Crusaders" msgstr "" msgid "Goblin" msgstr "" msgid "Goblins" msgstr "" #, fuzzy msgid "Orc" msgstr "Pomar" msgid "Orcs" msgstr "" msgid "Orc Chief" msgstr "" msgid "Orc Chiefs" msgstr "" msgid "Wolf" msgstr "" msgid "Wolves" msgstr "" msgid "Ogre" msgstr "" msgid "Ogres" msgstr "" msgid "Ogre Lord" msgstr "" msgid "Ogre Lords" msgstr "" msgid "Troll" msgstr "" msgid "Trolls" msgstr "" msgid "War Troll" msgstr "" msgid "War Trolls" msgstr "" msgid "Cyclopes" msgstr "" msgid "Cyclops" msgstr "" msgid "Sprite" msgstr "" msgid "Sprites" msgstr "" msgid "Dwarf" msgstr "" msgid "Dwarves" msgstr "" msgid "Battle Dwarf" msgstr "" msgid "Battle Dwarves" msgstr "" msgid "Elf" msgstr "" msgid "Elves" msgstr "" msgid "Grand Elf" msgstr "" msgid "Grand Elves" msgstr "" msgid "Druid" msgstr "" msgid "Druids" msgstr "" msgid "Greater Druid" msgstr "" msgid "Greater Druids" msgstr "" msgid "Unicorn" msgstr "" msgid "Unicorns" msgstr "" msgid "Phoenix" msgstr "" msgid "Phoenixes" msgstr "" msgid "Centaur" msgstr "" msgid "Centaurs" msgstr "" msgid "Gargoyle" msgstr "" msgid "Gargoyles" msgstr "" msgid "Griffin" msgstr "" msgid "Griffins" msgstr "" msgid "Minotaur" msgstr "" msgid "Minotaurs" msgstr "" msgid "Minotaur King" msgstr "" msgid "Minotaur Kings" msgstr "" msgid "Hydra" msgstr "" msgid "Hydras" msgstr "" #, fuzzy msgid "Green Dragon" msgstr "Torre Verde" msgid "Green Dragons" msgstr "" msgid "Red Dragon" msgstr "" msgid "Red Dragons" msgstr "" #, fuzzy msgid "Black Dragon" msgstr "Torre Negra" msgid "Black Dragons" msgstr "" msgid "Halfling" msgstr "" msgid "Halflings" msgstr "" msgid "Boar" msgstr "" msgid "Boars" msgstr "" msgid "Iron Golem" msgstr "" msgid "Iron Golems" msgstr "" msgid "Steel Golem" msgstr "" msgid "Steel Golems" msgstr "" msgid "Roc" msgstr "" msgid "Rocs" msgstr "" msgid "Mage" msgstr "" msgid "Magi" msgstr "" #, fuzzy msgid "Archmage" msgstr "Campo de Arqueiros" msgid "Archmagi" msgstr "" msgid "Giant" msgstr "" msgid "Giants" msgstr "" msgid "Titan" msgstr "" msgid "Titans" msgstr "" msgid "Skeletons" msgstr "" msgid "Zombie" msgstr "" msgid "Zombies" msgstr "" msgid "Mutant Zombie" msgstr "" msgid "Mutant Zombies" msgstr "" msgid "Mummies" msgstr "" msgid "Mummy" msgstr "" msgid "Royal Mummies" msgstr "" msgid "Royal Mummy" msgstr "" msgid "Vampire" msgstr "" msgid "Vampires" msgstr "" msgid "Vampire Lord" msgstr "" msgid "Vampire Lords" msgstr "" msgid "Lich" msgstr "" msgid "Liches" msgstr "" msgid "Power Lich" msgstr "" msgid "Power Liches" msgstr "" msgid "Bone Dragon" msgstr "" msgid "Bone Dragons" msgstr "" msgid "Rogue" msgstr "" msgid "Rogues" msgstr "" msgid "Nomad" msgstr "" msgid "Nomads" msgstr "" msgid "Ghost" msgstr "" msgid "Ghosts" msgstr "" msgid "Genie" msgstr "" msgid "Genies" msgstr "" msgid "Medusa" msgstr "" msgid "Medusas" msgstr "" msgid "Earth Elemental" msgstr "" msgid "Earth Elementals" msgstr "" msgid "Air Elemental" msgstr "" msgid "Air Elementals" msgstr "" msgid "Fire Elemental" msgstr "" msgid "Fire Elementals" msgstr "" msgid "Water Elemental" msgstr "" #, fuzzy msgid "Water Elementals" msgstr "Objetos de água" #, fuzzy msgid "Random Monsters" msgstr "Criaturas" msgid "Random Monster 1" msgstr "" #, fuzzy msgid "Random Monsters 1" msgstr "Criaturas" msgid "Random Monster 2" msgstr "" #, fuzzy msgid "Random Monsters 2" msgstr "Criaturas" msgid "Random Monster 3" msgstr "" #, fuzzy msgid "Random Monsters 3" msgstr "Criaturas" msgid "Random Monster 4" msgstr "" #, fuzzy msgid "Random Monsters 4" msgstr "Criaturas" msgid "Double shot" msgstr "" #, fuzzy msgid "2-hex monster" msgstr "" "Muitos de\n" "%{monster}" msgid "Double strike" msgstr "" msgid "Double damage to Undead" msgstr "" msgid "% magic resistance" msgstr "" msgid "Immune to Mind spells" msgstr "" msgid "Immune to Elemental spells" msgstr "" msgid "Immune to Fire spells" msgstr "" msgid "Immune to Cold spells" msgstr "" msgid "Immune to " msgstr "" msgid "% immunity to %{spell} spell" msgstr "" msgid "% damage from Elemental spells" msgstr "" msgid "% chance to Dispel beneficial spells" msgstr "" msgid "% chance to Paralyze" msgstr "" msgid "% chance to Petrify" msgstr "" msgid "% chance to Blind" msgstr "" msgid "% chance to Curse" msgstr "" msgid "% chance to cast %{spell} spell" msgstr "" msgid "HP regeneration" msgstr "" msgid "Two hexes attack" msgstr "" msgid "Flyer" msgstr "" msgid "Always retaliates" msgstr "" msgid "Attacks all adjacent enemies" msgstr "" msgid "No melee penalty" msgstr "" msgid "Dragon" msgstr "" msgid "Undead" msgstr "" msgid "No enemy retaliation" msgstr "" msgid "HP drain" msgstr "" #, fuzzy msgid "Cloud attack" msgstr "Castelo nas Nuvens" msgid "Decreases enemy's morale by " msgstr "" msgid "% chance to halve enemy" msgstr "" msgid "Soul Eater" msgstr "" msgid "Elemental" msgstr "" #, fuzzy msgid "No Morale" msgstr "Moral" msgid "200% damage from Fire spells" msgstr "" msgid "200% damage from Cold spells" msgstr "" msgid "% damage from %{spell} spell" msgstr "" msgid "% immunity to " msgstr "" msgid "Lightning" msgstr "" msgid "% damage from " msgstr "" msgid "The three Anduran artifacts magically combine into one." msgstr "" #, fuzzy msgid "View Spells" msgstr "Melhor Herói em Stats:" #, fuzzy msgid "View %{name} Info" msgstr "Ver %{name}" #, fuzzy msgid "Move %{name}" msgstr "Ver %{name}" #, fuzzy msgid "Cannot move the Spellbook" msgstr "Não é possível mover o último exército à fortaleza" msgid "This item can't be traded." msgstr "" #, fuzzy msgid "Invalid Artifact" msgstr "Artefato" msgid "The %{name} increases the hero's knowledge by %{count}." msgstr "" msgid "Ultimate Book of Knowledge" msgstr "" msgid "The %{name} increases the hero's attack skill by %{count}." msgstr "" msgid "Ultimate Sword of Dominion" msgstr "" #, fuzzy msgid "The %{name} increases the hero's defense skill by %{count}." msgstr "" "O Arco-Ãris aumenta a sorte das tropas de defesa do castelo em por 2 (dois)." msgid "Ultimate Cloak of Protection" msgstr "" #, fuzzy msgid "The %{name} increases the hero's spell power by %{count}." msgstr "%{name} resisitiu ao feitiço!" msgid "Ultimate Wand of Magic" msgstr "" msgid "" "The %{name} increases the hero's attack and defense skills by %{count} each." msgstr "" msgid "Ultimate Shield" msgstr "" msgid "" "The %{name} increases the hero's spell power and knowledge by %{count} each." msgstr "" msgid "Ultimate Staff" msgstr "" msgid "" "The %{name} increases each of the hero's basic skills by %{count} points." msgstr "" msgid "Ultimate Crown" msgstr "" msgid "Golden Goose" msgstr "" msgid "The %{name} brings in an income of %{count} gold per day." msgstr "" msgid "Arcane Necklace of Magic" msgstr "" msgid "" "After rescuing a Sorceress from a cursed tomb, she rewards your heroism with " "an exquisite jeweled necklace." msgstr "" msgid "Caster's Bracelet of Magic" msgstr "" msgid "" "While searching through the rubble of a caved-in mine, you free a group of " "trapped Dwarves. Grateful, the leader gives you a golden bracelet." msgstr "" msgid "Mage's Ring of Power" msgstr "" msgid "" "A cry of pain leads you to a Centaur, caught in a trap. Upon setting the " "creature free, he hands you a small pouch. Emptying the contents, you find a " "dazzling jeweled ring." msgstr "" msgid "Witch's Broach of Magic" msgstr "" msgid "" "Alongside the remains of a burnt witch lies a beautiful broach, intricately " "designed. Approaching the corpse with caution, you add the broach to your " "inventory." msgstr "" msgid "Medal of Valor" msgstr "" msgid "The %{name} increases the morale of the hero's army by %{count}." msgstr "" msgid "" "Freeing a virtuous maiden from the clutches of an evil overlord, you are " "granted a Medal of Valor by the King's herald." msgstr "" msgid "Medal of Courage" msgstr "" msgid "" "After saving a young boy from a vicious pack of Wolves, you return him to " "his father's manor. The grateful nobleman awards you with a Medal of Courage." msgstr "" msgid "Medal of Honor" msgstr "" msgid "" "After freeing a princess of a neighboring kingdom from the evil clutches of " "despicable slavers, she awards you with a Medal of Honor." msgstr "" msgid "Medal of Distinction" msgstr "" msgid "" "Ridding the countryside of the hideous Minotaur who made a sport of eating " "noblemen's Knights, you are honored with the Medal of Distinction." msgstr "" msgid "Fizbin of Misfortune" msgstr "" msgid "" "The %{name} greatly decreases the morale of the hero's army by %{count}." msgstr "" msgid "" "You stumble upon a medal lying alongside the empty road. Adding the medal to " "your inventory, you become aware that you have acquired the undesirable " "Fizbin of Misfortune, greatly decreasing your army's morale." msgstr "" msgid "Thunder Mace of Dominion" msgstr "" msgid "" "During a sudden storm, a bolt of lightning strikes a tree, splitting it. " "Inside the tree you find a mysterious mace." msgstr "" msgid "Armored Gauntlets of Protection" msgstr "" msgid "The %{name} increase the hero's defense skill by %{count}." msgstr "" msgid "" "You encounter the infamous Black Knight! After a grueling duel ending in a " "draw, the Knight, out of respect, offers you a pair of armored gauntlets." msgstr "" msgid "Defender Helm of Protection" msgstr "" msgid "" "A glint of golden light catches your eye. Upon further investigation, you " "find a golden helm hidden under a bush." msgstr "" msgid "Giant Flail of Dominion" msgstr "" msgid "" "A clumsy Giant has killed himself with his own flail. Knowing your superior " "skill with this weapon, you confidently remove the spectacular flail from " "the fallen Giant." msgstr "" msgid "Ballista of Quickness" msgstr "" msgid "The %{name} gives the hero's catapult one extra shot per combat round." msgstr "" msgid "" "Walking through the ruins of an ancient walled city, you find the instrument " "of the city's destruction, an elaborately crafted ballista." msgstr "" msgid "Stealth Shield of Protection" msgstr "" msgid "" "A stone statue of a warrior holds a silver shield. As you remove the shield, " "the statue crumbles into dust." msgstr "" msgid "Dragon Sword of Dominion" msgstr "" msgid "" "As you are walking along a narrow path, a nearby bush suddenly bursts into " "flames. Before your eyes the flames become the image of a beautiful woman. " "She holds out a magnificent sword to you." msgstr "" msgid "Power Axe of Dominion" msgstr "" msgid "" "You see a silver axe embedded deeply in the ground. After several " "unsuccessful attempts by your army to remove the axe, you tightly grip the " "handle of the axe and effortlessly pull it free." msgstr "" msgid "Divine Breastplate of Protection" msgstr "" msgid "" "A gang of Rogues is sifting through the possessions of dead warriors. " "Scaring off the scavengers, you note the Rogues had overlooked a beautiful " "breastplate." msgstr "" msgid "Minor Scroll of Knowledge" msgstr "" msgid "" "Before you appears a levitating glass case with a scroll, perched upon a bed " "of crimson velvet. At your touch, the lid opens and the scroll floats into " "your awaiting hands." msgstr "" msgid "Major Scroll of Knowledge" msgstr "" msgid "" "Visiting a local wiseman, you explain the intent of your journey. He reaches " "into a sack and withdraws a yellowed scroll and hands it to you." msgstr "" msgid "Superior Scroll of Knowledge" msgstr "" msgid "" "You come across the remains of an ancient Druid. Bones, yellowed with age, " "peer from the ragged folds of her robe. Searching the robe, you discover a " "scroll hidden in the folds." msgstr "" msgid "Foremost Scroll of Knowledge" msgstr "" msgid "" "Mangled bones, yellowed with age, peer from the ragged folds of a dead " "Druid's robe. Searching the robe, you discover a scroll hidden within." msgstr "" msgid "Endless Sack of Gold" msgstr "" msgid "The %{name} provides the hero with %{count} gold per day." msgstr "" msgid "" "A little leprechaun dances gleefully around a magic sack. Seeing you " "approach, he stops in mid-stride. The little man screams and stamps his foot " "ferociously, vanishing into thin air. Remembering the old leprechaun saying " "'Finders Keepers', you grab the sack and leave." msgstr "" msgid "Endless Bag of Gold" msgstr "" msgid "" "A noblewoman, separated from her traveling companions, asks for your help. " "After escorting her home, she rewards you with a bag filled with gold." msgstr "" msgid "Endless Purse of Gold" msgstr "" msgid "" "In your travels, you find a leather purse filled with gold that once " "belonged to a great warrior king who had the ability to transform any " "inanimate object into gold." msgstr "" msgid "Nomad Boots of Mobility" msgstr "" msgid "The %{name} increase the hero's movement on land." msgstr "" msgid "" "A Nomad trader seeks protection from a tribe of Goblins. For your " "assistance, he gives you a finely crafted pair of boots made from the " "softest leather. Looking closely, you see fascinating ancient carvings " "engraved on the leather." msgstr "" msgid "Traveler's Boots of Mobility" msgstr "" msgid "" "Discovering a pair of beautifully beaded boots made from the finest and " "softest leather, you thank the anonymous donor and add the boots to your " "inventory." msgstr "" msgid "Lucky Rabbit's Foot" msgstr "" #, fuzzy msgid "The %{name} increases the luck of the hero's army by %{count}." msgstr "" "O Arco-Ãris aumenta a sorte das tropas de defesa do castelo em por 2 (dois)." msgid "" "A traveling merchant offers you a rabbit's foot, made of gleaming silver " "fur, for safe passage. The merchant explains the charm will increase your " "luck in combat." msgstr "" msgid "Golden Horseshoe" msgstr "" msgid "" "An ensnared Unicorn whinnies in fright. Murmuring soothing words, you set " "her free. Snorting and stamping her front hoof once, she gallops off. " "Looking down you see a golden horseshoe." msgstr "" msgid "Gambler's Lucky Coin" msgstr "" msgid "" "You have captured a mischievous imp who has been terrorizing the region. In " "exchange for his release, he rewards you with a magical coin." msgstr "" msgid "Four-Leaf Clover" msgstr "" msgid "" "In the middle of a patch of dead and dry vegetation, to your surprise you " "find a healthy green four-leaf clover." msgstr "" msgid "True Compass of Mobility" msgstr "" msgid "The %{name} increases the hero's movement on land and sea." msgstr "" msgid "" "An old man claiming to be an inventor asks you to try his latest invention. " "He then hands you a compass." msgstr "" msgid "Sailor's Astrolabe of Mobility" msgstr "" msgid "The %{name} increases the hero's movement on sea." msgstr "" msgid "" "An old sea captain is being tortured by Ogres. You save him, and in return " "he rewards you with a wondrous instrument to measure the distance of a star." msgstr "" msgid "Evil Eye" msgstr "" msgid "The %{name} reduces the casting cost of curse spells by half." msgstr "" msgid "" "While venturing into a decrepit hut you find the Skeleton of a long dead " "witch. Investigation of the remains reveals a glass eye rolling around " "inside an empty skull." msgstr "" msgid "Enchanted Hourglass" msgstr "" msgid "" "The %{name} extends the duration of all the hero's spells by %{count} turns." msgstr "" msgid "" "A surprise turn in the landscape finds you in the midst of a grisly scene: " "Vultures picking at the aftermath of a terrible battle. Your cursory search " "of the remains turns up an enchanted hourglass." msgstr "" msgid "Gold Watch" msgstr "" msgid "The %{name} doubles the effectiveness of the hero's hypnotize spells." msgstr "" msgid "" "In reward for helping his cart out of a ditch, a traveling potion salesman " "gives you a \"magic\" gold watch. Unbeknownst to him, the watch really is " "magical." msgstr "" msgid "Skullcap" msgstr "" msgid "The %{name} halves the casting cost of all mind influencing spells." msgstr "" msgid "" "A brief stop at an improbable rural inn yields an exchange of money, tales, " "and accidentally, luggage. You find a magical skullcap in your new backpack." msgstr "" msgid "Ice Cloak" msgstr "" msgid "" "The %{name} halves all damage the hero's troops receive from cold spells." msgstr "" msgid "" "Responding to the panicked cries of a damsel in distress, you discover a " "young woman fleeing from a hungry bear. You slay the beast in the nick of " "time, and the grateful Sorceress weaves a magic cloak from the bear's hide." msgstr "" msgid "Fire Cloak" msgstr "" msgid "" "The %{name} halves all damage the hero's troops receive from fire spells." msgstr "" msgid "" "You've come upon a fight between a Necromancer and a Paladin. The " "Necromancer blasts the Paladin with a fire bolt, bringing him to his knees. " "Acting quickly, you slay the evil one before the final blow. The grateful " "Paladin gives you the fire cloak that saved him." msgstr "" msgid "Lightning Helm" msgstr "" msgid "" "The %{name} halves all damage the hero's troops receive from lightning " "spells." msgstr "" msgid "" "A traveling tinker in need of supplies offers you a helm with a thunderbolt " "design on its top in exchange for food and water. Curious, you accept, and " "later find out that the helm is magical." msgstr "" msgid "Evercold Icicle" msgstr "" msgid "" "The %{name} causes the hero's cold spells to do %{count} percent more damage " "to enemy troops." msgstr "" msgid "" "An icicle withstanding the full heat of the noonday sun attracts your " "attention. Intrigued, you break it off, and find that it does not melt in " "your hand." msgstr "" msgid "Everhot Lava Rock" msgstr "" msgid "" "The %{name} causes the hero's fire spells to do %{count} percent more damage " "to enemy troops." msgstr "" msgid "" "Your wanderings bring you into contact with a tribe of ape-like beings using " "a magical lava rock that never cools to light their fires. You take pity on " "them and teach them to make fire with sticks. Believing you to be a god, the " "apes give you their rock." msgstr "" msgid "Lightning Rod" msgstr "" msgid "" "The %{name} causes the hero's lightning spells to do %{count} percent more " "damage to enemy troops." msgstr "" msgid "" "While waiting out a storm, a lighting bolt strikes a nearby cottage's " "lightning rod, which melts and falls to the ground. The tip of the rod, " "however, survives intact and makes your hair stand on end when you touch it. " "Hmm..." msgstr "" msgid "Snake-Ring" msgstr "" msgid "The %{name} halves the casting cost of all of the hero's bless spells." msgstr "" msgid "" "You've found an oddly shaped ring on the finger of a long dead traveler. The " "ring looks like a snake biting its own tail." msgstr "" msgid "Ankh" msgstr "" msgid "" "The %{name} doubles the effectiveness of all of the hero's resurrect and " "animate spells." msgstr "" msgid "" "A fierce windstorm reveals the entrance to a buried tomb. Your investigation " "reveals that the tomb has already been looted, but the thieves overlooked an " "ankh on a silver chain in the dark." msgstr "" msgid "Book of Elements" msgstr "" msgid "" "The %{name} doubles the effectiveness of all of the hero's summoning spells." msgstr "" msgid "" "You come across a conjurer who begs to accompany you and your army awhile " "for safety. You agree, and he offers as payment a copy of the book of the " "elements." msgstr "" msgid "Elemental Ring" msgstr "" msgid "The %{name} halves the casting cost of all summoning spells." msgstr "" msgid "" "While pausing to rest, you notice a bobcat climbing a short tree to get at a " "crow's nest. On impulse, you climb the tree yourself and scare off the cat. " "When you look in the nest, you find a collection of shiny stones and a ring." msgstr "" msgid "Holy Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to curse spells." msgstr "" msgid "" "In your wanderings you come across a hermit living in a small, tidy hut. " "Impressed with your mission, he takes time out from his meditations to bless " "and give you a charm against curses." msgstr "" msgid "Pendant of Free Will" msgstr "" msgid "The %{name} makes all of the hero's troops immune to hypnotize spells." msgstr "" msgid "" "Responding to cries for help, you find river Sprites making a sport of " "dunking an old man. Feeling vengeful, you rescue the man and drag a Sprite " "onto dry land for awhile. The Sprite, uncomfortable in the air, gives you a " "magic pendant to let him go." msgstr "" msgid "Pendant of Life" msgstr "" msgid "The %{name} makes all of the hero's troops immune to death spells." msgstr "" msgid "" "A brief roadside encounter with a small caravan and a game of knucklebones " "wins a magic pendant. Its former owner says that it protects from " "Necromancers' death spells." msgstr "" msgid "Serenity Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to berserk spells." msgstr "" msgid "" "The sounds of combat draw you to the scene of a fight between an old " "Barbarian and an eight-headed Hydra. Your timely intervention swings the " "battle in favor of the man, and he rewards you with a pendant he used to use " "to calm his mind for battle." msgstr "" msgid "Seeing-eye Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to blindness spells." msgstr "" msgid "" "You come upon a very old woman, long blind from cataracts and dying alone. " "You tend to her final needs and promise a proper burial. Grateful, she gives " "you a magic pendant emblazoned with a stylized eye. It lets you see with " "your eyes closed." msgstr "" msgid "Kinetic Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to paralyze spells." msgstr "" msgid "" "You come across a golem wearing a glowing pendant and blocking your way. " "Acting on a hunch, you cut the pendant from its neck. Deprived of its power " "source, the golem breaks down, leaving you with the magical pendant." msgstr "" msgid "Pendant of Death" msgstr "" msgid "The %{name} makes all of the hero's troops immune to holy spells." msgstr "" msgid "" "A quick and deadly battle with a Necromancer wins you his magical pendant. " "Later, a Wizard tells you that the pendant protects undead under your " "control from holy word spells." msgstr "" msgid "Wand of Negation" msgstr "" msgid "" "The %{name} makes all of the hero's troops immune to dispel magic spells." msgstr "" msgid "" "You meet an old Wizard friend of yours traveling in the opposite direction. " "He presents you with a gift: A wand that prevents the use of the dispel " "magic spell on your allies." msgstr "" msgid "Golden Bow" msgstr "" msgid "" "The %{name} eliminates the %{count} percent penalty for the hero's troops " "shooting past obstacles (e.g. castle walls)." msgstr "" msgid "" "A chance meeting with a famous Archer finds you in a game of knucklebones " "pitting his bow against your horse. You win." msgstr "" msgid "Telescope" msgstr "" msgid "" "The %{name} increases the amount of terrain the hero reveals when " "adventuring by %{count} extra square." msgstr "" msgid "" "A merchant from far away lands trades you a new invention of his people for " "traveling supplies. It makes distant objects appear closer, and he calls " "it...\n" "\n" "a telescope." msgstr "" msgid "Statesman's Quill" msgstr "" msgid "" "The %{name} reduces the cost of surrender to %{count} percent of the total " "cost of troops the hero has in their army." msgstr "" msgid "" "You pause to help a diplomat with a broken axle fix his problem. In " "gratitude, he gives you a writing quill with magical properties which he " "says will \"help people see things your way\"." msgstr "" msgid "Wizard's Hat" msgstr "" msgid "" "The %{name} increases the duration of the hero's spells by %{count} turns." msgstr "" msgid "" "You see a Wizard fleeing from a Griffin and riding like the wind. The Wizard " "opens a portal and rides through, getting his hat knocked off by the edge of " "the gate. The Griffin follows; the gate closes. You pick the hat up, dust it " "off, and put it on." msgstr "" msgid "Power Ring" msgstr "" msgid "The %{name} returns %{count} extra spell points per day to the hero." msgstr "" msgid "" "You find a small tree that closely resembles the great Warlock Carnauth with " "a ring around one of its twigs. Scraps of clothing and rotting leather lead " "you to suspect that it IS Carnauth, transformed. Since you can't help him, " "you take the magic ring." msgstr "" msgid "Ammo Cart" msgstr "" msgid "" "The %{name} provides endless ammunition for all of the hero's troops that " "shoot." msgstr "" msgid "" "An ammunition cart in the middle of an old battlefield catches your eye. " "Inspection shows it to be in good working order, so you take it along." msgstr "" msgid "Tax Lien" msgstr "" msgid "The %{name} costs the hero %{count} gold pieces per day." msgstr "" msgid "" "Your big spending habits have earned you a massive tax bill that you can't " "hope to pay. The tax man takes pity and agrees to only take 250 gold a day " "from your account for life. Check here if you want one dollar to go to the " "presidential campaign election fund." msgstr "" msgid "Hideous Mask" msgstr "" msgid "The %{name} prevents all 'wandering' armies from joining the hero." msgstr "" msgid "" "Your looting of the grave of Sinfilas Gardolad, the famous shapeshifting " "Warlock, unearths his fabled mask. Trembling, you put it on and it twists " "your visage into an awful grimace! Oh no! It's actually the hideous mask of " "Gromluck Greene, and you are stuck with it." msgstr "" msgid "Endless Pouch of Sulfur" msgstr "" msgid "The %{name} provides %{count} unit of sulfur per day." msgstr "" msgid "" "You visit an alchemist who, upon seeing your army, is swayed by the " "righteousness of your cause. The newly loyal subject gives you his endless " "pouch of sulfur to help with the war effort." msgstr "" msgid "Endless Vial of Mercury" msgstr "" msgid "The %{name} provides %{count} unit of mercury per day." msgstr "" msgid "" "A brief stop at a hastily abandoned Wizard's tower turns up a magical vial " "of mercury that always has a little left on the bottom. Recognizing a " "treasure when you see one, you cap it and slip it in your pocket." msgstr "" msgid "Endless Pouch of Gems" msgstr "" msgid "The %{name} provides %{count} unit of gems per day." msgstr "" msgid "" "A short rainstorm brings forth a rainbow...and you can see the end of it. " "Riding quickly, you seize the pot of gold you find there. The leprechaun who " "owns it, unable to stop you from taking it, offers an endless pouch of gems " "for the return of his gold. You accept." msgstr "" msgid "Endless Cord of Wood" msgstr "" msgid "The %{name} provides %{count} unit of wood per day." msgstr "" msgid "" "Pausing to rest and light a cook fire, you pull wood out of a nearby pile of " "dead wood. As you keep pulling wood from the pile, you notice that it " "doesn't shrink. You realize to your delight that the wood is enchanted, so " "you take it along." msgstr "" msgid "Endless Cart of Ore" msgstr "" msgid "The %{name} provides %{count} unit of ore per day." msgstr "" msgid "" "You've found a Goblin weapon smithy making weapons for use against humans. " "With a tremendous yell you and your army descend upon their camp and drive " "them away. A search finds a magic ore cart that never runs out of iron." msgstr "" msgid "Endless Pouch of Crystal" msgstr "" msgid "The %{name} provides %{count} unit of crystal per day." msgstr "" msgid "" "Taking shelter from a storm in a small cave, you notice a small patch of " "crystal in one corner. Curious, you break a piece off and notice that the " "original crystal grows the lost piece back. You decide to stuff the entire " "patch into a pouch and take it with you." msgstr "" msgid "Spiked Helm" msgstr "" msgid "" "Your army is ambushed by a small tribe of wild (and none too bright) Orcs. " "You fend them off easily and the survivors flee in all directions. One of " "the Orcs was wearing a polished spiked helm. Figuring it will make a good " "souvenir, you take it." msgstr "" msgid "Spiked Shield" msgstr "" msgid "" "You come upon a bridge spanning a dry gully. Before you can cross, a Troll " "steps out from under the bridge and demands payment before it will permit " "you to pass. You refuse, and the Troll charges, forcing you to slay it. You " "take its spiked shield as a trophy." msgstr "" msgid "White Pearl" msgstr "" msgid "" "A walk across a dry saltwater lake bed yields an unlikely prize: A white " "pearl amidst shattered shells and debris." msgstr "" msgid "Black Pearl" msgstr "" msgid "" "Rumors of a Griffin of unusual size preying upon the countryside lead you to " "its cave lair. A quick, brutal fight dispatches the beast, and a search of " "its foul nest turns up a huge black pearl." msgstr "" msgid "Magic Book" msgstr "" #, fuzzy msgid "The %{name} enables the hero to cast spells." msgstr "%{name} resisitiu ao feitiço!" msgid "Dummy 1" msgstr "" #, fuzzy msgid "The reserved artifact." msgstr "Não é possível mover o último exército à fortaleza" msgid "Dummy 2" msgstr "" msgid "Dummy 3" msgstr "" msgid "Dummy 4" msgstr "" msgid "Spell Scroll" msgstr "" msgid "" "This %{name} gives the hero the ability to cast the %{spell} spell if the " "hero has a Magic Book." msgstr "" msgid "" "You find an elaborate container which houses an old vellum scroll. The runes " "on the container are very old, and the artistry with which it was put " "together is stunning. As you pull the scroll out, you feel imbued with " "magical power." msgstr "" msgid "Arm of the Martyr" msgstr "" msgid "" "The %{name} increases the hero's spell power by %{count} but adds the undead " "morale penalty." msgstr "" msgid "" "One of the less intelligent members of your party picks up an arm off of the " "ground. Despite its missing a body, it is still moving. Your troops find the " "dismembered arm repulsive, but you cannot bring yourself to drop it: it " "seems to hold some sort of magical power that influences your decision " "making." msgstr "" msgid "Breastplate of Anduran" msgstr "" #, fuzzy msgid "The %{name} increases the hero's defense by %{count}." msgstr "" "O Arco-Ãris aumenta a sorte das tropas de defesa do castelo em por 2 (dois)." msgid "" "You come upon a sign. It reads: \"Here lies the body of Anduran. Bow and " "swear fealty, and you shall be rewarded.\" You decide to do as it says. As " "you stand up, you feel a coldness against your skin. Looking down, you find " "that you are suddenly wearing a gleaming, ornate breastplate." msgstr "" msgid "Broach of Shielding" msgstr "" msgid "" "The %{name} provides %{count} percent protection from Armageddon and " "Elemental Storm, but decreases spell power by 2." msgstr "" msgid "" "A kindly Sorceress thinks that your army's defenses could use a magical " "boost. She offers to enchant the Broach that you wear on your cloak, and you " "accept." msgstr "" msgid "Battle Garb of Anduran" msgstr "" msgid "" "The %{name} combines the powers of the three Anduran artifacts. It provides " "maximum luck and morale for the hero's troops and gives the hero the Town " "Portal spell." msgstr "" msgid "" "Out of pity for a poor peasant, you purchase a chest of old junk they are " "hawking for too much gold. Later, as you search through it, you find it " "contains the 3 pieces of the legendary battle garb of Anduran!" msgstr "" msgid "Crystal Ball" msgstr "" msgid "" "The %{name} lets the hero get more specific information about monsters, " "enemy heroes, and castles nearby the hero." msgstr "" msgid "" "You come upon a caravan of gypsies who are feasting and fortifying their " "bodies with mead. They call you forward and say \"If you prove that you can " "dance the Rama-Buta, we will reward you.\" You don't know it, but try " "anyway. They laugh hysterically, but admire your bravery, giving you a " "Crystal Ball." msgstr "" msgid "Heart of Fire" msgstr "" msgid "" "The %{name} provides %{count} percent protection from fire, but doubles the " "damage taken from cold." msgstr "" msgid "" "You enter a recently burned glade and come upon a Fire Elemental sitting " "atop a rock. It looks up, its flaming face contorted in a look of severe " "pain. It then tosses a glowing object at you. You put up your hands to block " "it, but it passes right through them and sears itself into your chest." msgstr "" msgid "Heart of Ice" msgstr "" msgid "" "The %{name} provides %{count} percent protection from cold, but doubles the " "damage taken from fire." msgstr "" msgid "" "Suddenly, a biting coldness engulfs your body. You seize up, falling from " "your horse. The pain subsides, but you still feel as if your chest is " "frozen. As you pick yourself up off of the ground, you hear hearty laughter. " "You turn around just in time to see a Frost Giant run off into the woods and " "disappear." msgstr "" msgid "Helmet of Anduran" msgstr "" msgid "" "You spy a gleaming object poking up out of the ground. You send a member of " "your party over to investigate. He comes back with a golden helmet in his " "hands. You realize that it must be the helmet of the legendary Anduran, the " "only man who was known to wear solid gold armor." msgstr "" msgid "Holy Hammer" msgstr "" msgid "" "You come upon a battle where a Paladin has been mortally wounded by a group " "of Zombies. He asks you to take his hammer and finish what he started. As " "you pick it up, it begins to hum, and then everything becomes a blur. The " "Zombies lie dead, the hammer dripping with blood. You strap it to your belt." msgstr "" msgid "Legendary Scepter" msgstr "" msgid "The %{name} adds %{count} points to all attributes." msgstr "" msgid "" "Upon cresting a small hill, you come upon a ridiculous looking sight. A " "Sprite is attempting to carry a Scepter that is almost as big as it is. " "Trying not to laugh, you ask, \"Need help?\" The Sprite glares at you and " "answers: \"You think this is funny? Fine. You can carry it. I much prefer " "flying anyway.\"" msgstr "" msgid "" "An old seaman tells you a tale of an enchanted masthead that he used in his " "youth to rally his crew during times of trouble. He then hands you a faded " "map that shows where he hid it. After much exploring, you find it stashed " "underneath a nearby dock." msgstr "" msgid "Masthead" msgstr "" msgid "" "The %{name} boosts the hero's troops' luck and morale by %{count} each in " "sea combat." msgstr "" msgid "Sphere of Negation" msgstr "" msgid "The %{name} disables all spell casting, for both sides, in combat." msgstr "" msgid "" "You stop to help a Peasant catch a runaway mare. To show his gratitude, he " "hands you a tiny sphere. As soon as you grasp it, you feel the magical " "energy drain from your limbs..." msgstr "" msgid "Staff of Wizardry" msgstr "" msgid "The %{name} boosts the hero's spell power by %{count}." msgstr "" msgid "" "While out scaring up game, your troops find a mysterious staff levitating " "about three feet off of the ground. They hand it to you, and you notice an " "inscription. It reads: \"Brains best brawn and magic beats might. Heed my " "words, and you'll win every fight.\"" msgstr "" msgid "Sword Breaker" msgstr "" msgid "The %{name} increases the hero's defense by %{count} and attack by 1." msgstr "" msgid "" "A former Captain of the Guard admires your quest and gives you the enchanted " "Sword Breaker that he relied on during his tour of duty." msgstr "" msgid "Sword of Anduran" msgstr "" msgid "" "A Troll stops you and says: \"Pay me 5,000 gold, or the Sword of Anduran " "will slay you where you stand.\" You refuse. The troll grabs the sword " "hanging from its belt, screams in pain, and runs away. Picking up the fabled " "sword, you give thanks that half-witted Trolls tend to grab the wrong end of " "sharp objects." msgstr "" msgid "Spade of Necromancy" msgstr "" msgid "The %{name} gives the hero increased necromancy skill." msgstr "" msgid "" "A dirty shovel has been thrust into a dirt mound nearby. Upon investigation, " "you discover it to be the enchanted shovel of the Gravediggers, long thought " "lost by mortals." msgstr "" msgid "spellBonus|selected by user" msgstr "" msgid "Wood" msgstr "Madeira" msgid "Mercury" msgstr "Mercúrio" msgid "Ore" msgstr "" msgid "Sulfur" msgstr "" msgid "Crystal" msgstr "Cristal" msgid "Gems" msgstr "Gemas" msgid "Gold" msgstr "" msgid "" "There are seven resources in Heroes 2, used to build and improves castles, " "purchase troops and recruit heroes. Gold is the most common, required for " "virtually everything. Wood and ore are used for most buildings. Gems, " "Mercury, Sulfur and Crystal are rare magical resources used for the most " "powerful creatures and buildings." msgstr "" msgid "" "Causes a giant fireball to strike the selected area, damaging all nearby " "creatures." msgstr "" msgid "Fireball" msgstr "" msgid "Fireblast" msgstr "" msgid "" "An improved version of fireball, fireblast affects two hexes around the " "center point of the spell, rather than one." msgstr "" msgid "Causes a bolt of electrical energy to strike the selected creature." msgstr "" msgid "Lightning Bolt" msgstr "" msgid "Chain Lightning" msgstr "" msgid "" "Causes a bolt of electrical energy to strike a selected creature, then " "strike the nearest creature with half damage, then strike the NEXT nearest " "creature with half again damage, and so on, until it becomes too weak to be " "harmful. Warning: This spell can hit your own creatures!" msgstr "" msgid "Teleport" msgstr "" msgid "" "Teleports the creature you select to any open position on the battlefield." msgstr "" msgid "Cure" msgstr "" msgid "" "Removes all negative spells cast upon one of your units, and restores up to " "%{count} HP per level of spell power." msgstr "" msgid "Mass Cure" msgstr "" msgid "" "Removes all negative spells cast upon your forces, and restores up to " "%{count} HP per level of spell power, per creature." msgstr "" msgid "Resurrect" msgstr "" msgid "Resurrects creatures from a damaged or dead unit until end of combat." msgstr "" msgid "Resurrect True" msgstr "" msgid "Resurrects creatures from a damaged or dead unit permanently." msgstr "" msgid "Haste" msgstr "" msgid "Increases the speed of any creature by %{count}." msgstr "" msgid "Increases the speed of all of your creatures by %{count}." msgstr "" msgid "Mass Haste" msgstr "" msgid "Slows target to half movement rate." msgstr "" msgid "spell|Slow" msgstr "" msgid "Mass Slow" msgstr "" msgid "Slows all enemies to half movement rate." msgstr "" msgid "Clouds the affected creatures' eyes, preventing them from moving." msgstr "" msgid "spell|Blind" msgstr "" msgid "Bless" msgstr "" msgid "Causes the selected creatures to inflict maximum damage." msgstr "" msgid "Causes all of your units to inflict maximum damage." msgstr "" msgid "Mass Bless" msgstr "" msgid "Magically increases the defense skill of the selected creatures." msgstr "" msgid "Stoneskin" msgstr "" msgid "" "Increases the defense skill of the targeted creatures. This is an improved " "version of Stoneskin." msgstr "" msgid "Steelskin" msgstr "" msgid "Causes the selected creatures to inflict minimum damage." msgstr "" msgid "Curse" msgstr "" msgid "Causes all enemy troops to inflict minimum damage." msgstr "" msgid "Mass Curse" msgstr "" msgid "Damages all undead in the battle." msgstr "" msgid "Holy Word" msgstr "" msgid "" "Damages all undead in the battle. This is an improved version of Holy Word." msgstr "" msgid "Holy Shout" msgstr "" msgid "Anti-Magic" msgstr "" msgid "Prevents harmful magic against the selected creatures." msgstr "" msgid "Dispel Magic" msgstr "" msgid "Removes all magic spells from a single target." msgstr "" msgid "Mass Dispel" msgstr "" msgid "Removes all magic spells from all creatures." msgstr "" msgid "Causes a magic arrow to strike the selected target." msgstr "" msgid "Magic Arrow" msgstr "" msgid "Berserker" msgstr "" msgid "Causes a creature to attack its nearest neighbor." msgstr "" msgid "Armageddon" msgstr "" msgid "" "Holy terror strikes the battlefield, causing severe damage to all creatures." msgstr "" msgid "Elemental Storm" msgstr "" msgid "Magical elements pour down on the battlefield, damaging all creatures." msgstr "" msgid "" "A rain of rocks strikes an area of the battlefield, damaging all nearby " "creatures." msgstr "" msgid "Meteor Shower" msgstr "" msgid "Paralyze" msgstr "" msgid "The targeted creatures are paralyzed, unable to move or retaliate." msgstr "" msgid "Hypnotize" msgstr "" msgid "" "Brings a single enemy unit under your control if its hits are less than " "%{count} times the caster's spell power." msgstr "" msgid "Cold Ray" msgstr "" msgid "Drains body heat from a single enemy unit." msgstr "" msgid "Cold Ring" msgstr "" msgid "" "Drains body heat from all units surrounding the center point, but not " "including the center point." msgstr "" msgid "Disrupting Ray" msgstr "" msgid "Reduces the defense rating of an enemy unit by three." msgstr "" msgid "Damages all living (non-undead) units in the battle." msgstr "" msgid "Death Ripple" msgstr "" msgid "Death Wave" msgstr "" msgid "" "Damages all living (non-undead) units in the battle. This spell is an " "improved version of Death Ripple." msgstr "" msgid "Dragon Slayer" msgstr "" msgid "Greatly increases a unit's attack skill vs. Dragons." msgstr "" msgid "Blood Lust" msgstr "" msgid "Increases a unit's attack skill." msgstr "" msgid "Animate Dead" msgstr "" msgid "Resurrects creatures from a damaged or dead undead unit permanently." msgstr "" msgid "Mirror Image" msgstr "" msgid "" "Creates an illusionary unit that duplicates one of your existing units. This " "illusionary unit does the same damages as the original, but will vanish if " "it takes any damage." msgstr "" msgid "Shield" msgstr "" msgid "" "Halves damage received from ranged attacks for a single unit. Does not " "affect damage received from Turrets or Ballistae." msgstr "" msgid "Mass Shield" msgstr "" msgid "" "Halves damage received from ranged attacks for all of your units. Does not " "affect damage received from Turrets or Ballistae." msgstr "" msgid "Summon Earth Elemental" msgstr "" msgid "Summons Earth Elementals to fight for your army." msgstr "" msgid "Summon Air Elemental" msgstr "" msgid "Summons Air Elementals to fight for your army." msgstr "" msgid "Summon Fire Elemental" msgstr "" msgid "Summons Fire Elementals to fight for your army." msgstr "" msgid "Summon Water Elemental" msgstr "" msgid "Summons Water Elementals to fight for your army." msgstr "" msgid "Damages castle walls." msgstr "" msgid "Earthquake" msgstr "" msgid "Causes all mines across the land to become visible." msgstr "" msgid "View Mines" msgstr "" msgid "Causes all resources across the land to become visible." msgstr "" msgid "View Resources" msgstr "" msgid "Causes all artifacts across the land to become visible." msgstr "" msgid "View Artifacts" msgstr "" msgid "Causes all towns and castles across the land to become visible." msgstr "" msgid "View Towns" msgstr "" msgid "Causes all Heroes across the land to become visible." msgstr "" msgid "View Heroes" msgstr "" msgid "Causes the entire land to become visible." msgstr "" msgid "View All" msgstr "" msgid "Allows the caster to view detailed information on enemy Heroes." msgstr "" msgid "Summon Boat" msgstr "" msgid "" "Summons the nearest unoccupied, friendly boat to an adjacent shore location. " "A friendly boat is one which you just built or were the most recent player " "to occupy." msgstr "" msgid "Allows the caster to magically transport to a nearby location." msgstr "" msgid "Dimension Door" msgstr "" msgid "Returns the caster to any town or castle currently owned." msgstr "" msgid "Town Gate" msgstr "" msgid "" "Returns the hero to the town or castle of choice, provided it is controlled " "by you." msgstr "" msgid "Visions" msgstr "" msgid "" "Visions predicts the likely outcome of an encounter with a neutral army camp." msgstr "" msgid "Haunt" msgstr "" msgid "" "Haunts a mine you control with Ghosts. This mine stops producing resources. " "(If I can't keep it, nobody will!)" msgstr "" msgid "Set Earth Guardian" msgstr "" msgid "Sets Earth Elementals to guard a mine against enemy armies." msgstr "" msgid "Set Air Guardian" msgstr "" msgid "Sets Air Elementals to guard a mine against enemy armies." msgstr "" msgid "Set Fire Guardian" msgstr "" msgid "Sets Fire Elementals to guard a mine against enemy armies." msgstr "" msgid "Set Water Guardian" msgstr "" msgid "Sets Water Elementals to guard a mine against enemy armies." msgstr "" #, fuzzy msgid "Random Spell" msgstr "Criaturas" msgid "Randomly selected spell of any level." msgstr "" msgid "Random 1st Level Spell" msgstr "" msgid "Randomly selected 1st level spell." msgstr "" msgid "Random 2nd Level Spell" msgstr "" msgid "Randomly selected 2nd level spell." msgstr "" msgid "Random 3rd Level Spell" msgstr "" msgid "Randomly selected 3rd level spell." msgstr "" msgid "Random 4th Level Spell" msgstr "" msgid "Randomly selected 4th level spell." msgstr "" msgid "Random 5th Level Spell" msgstr "" msgid "Randomly selected 5th level spell." msgstr "" #, fuzzy msgid "Petrification" msgstr "Fortificações" msgid "" "Turns the affected creature into stone. A petrified creature receives half " "damage from a direct attack." msgstr "" msgid "You have no Magic Book, so you cannot cast a spell." msgstr "" msgid "No spell to cast." msgstr "" msgid "Your hero has %{point} spell points remaining." msgstr "" #, fuzzy msgid "View Adventure Spells" msgstr "Opções de Aventura" #, fuzzy msgid "View Combat Spells" msgstr "Magia" #, fuzzy msgid "View previous page" msgstr "Mostrar cidade anterior" #, fuzzy msgid "View next page" msgstr "Ver %{name}" #, fuzzy msgid "Close Spellbook" msgstr "Magia" #, fuzzy msgid "View %{spell}" msgstr "Ver %{name}" #, fuzzy msgid "This spell does %{damage} points of damage." msgstr "O %{spell} fez %{damage} de dano." msgid "" "This spell summons\n" "%{count} %{monster}." msgstr "" msgid "This spell restores %{hp} HP." msgstr "" msgid "This spell summons %{count} %{monster} to guard the mine." msgstr "" msgid "The nearest town is %{town}." msgstr "" msgid "This town is occupied by your hero %{hero}." msgstr "" msgid "" "This spell controls up to\n" "%{hp} HP." msgstr "" msgid "The ultimate artifact is really the %{name}." msgstr "" msgid "The ultimate artifact may be found in the %{name} regions of the world." msgstr "" msgid "north-west" msgstr "" msgid "north" msgstr "" msgid "north-east" msgstr "" #, fuzzy msgid "west" msgstr "Ninho" #, fuzzy msgid "center" msgstr "Tenda" #, fuzzy msgid "east" msgstr "Ninho" msgid "south-west" msgstr "" #, fuzzy msgid "south" msgstr "som" msgid "south-east" msgstr "" msgid "The truth is out there." msgstr "" msgid "The dark side is stronger." msgstr "" msgid "The end of the world is near." msgstr "O fim do mundo está próximo." msgid "The bones of Lord Slayer are buried in the foundation of the arena." msgstr "" msgid "A Black Dragon will take out a Titan any day of the week." msgstr "" #, fuzzy msgid "He told her: Yada yada yada... and then she said: Blah, blah, blah..." msgstr "" "Ele disse à ela: Yada yada yada... e então ela disse: Blah, blah, blah..." msgid "An unknown force is being resurrected..." msgstr "Uma força misteriosa está ressuscitando..." msgid "" "Check the newest version of the game at\n" "https://github.com/ihhub/\n" "fheroes2/releases" msgstr "" #, fuzzy #~ msgid "Castles" #~ msgstr "Castelo" #, fuzzy #~ msgid "Ocean object" #~ msgstr "Terreno Baldio" #, fuzzy #~ msgid "Desert Objects" #~ msgstr "Deserto" #, fuzzy #~ msgid "%{race2} %{name2}" #~ msgstr "Ver %{name}" fheroes2-1.0.12+dfsg/files/lang/ro.po000066400000000000000000005651421456075706000172610ustar00rootroot00000000000000# Romanian translation of fheroes2 # Copyright (C) 2022 fheroes2 team # This file is distributed under the same license as the fheroes2 package. # msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-03 03:27+0000\n" "PO-Revision-Date: 2022-10-25 08:56+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" "Language: ro\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n==0 || (n!=1 && n%100>=1 && " "n%100<=19) ? 1 : 2);\n" "X-Generator: Poedit 3.1.1\n" msgid "" "BATTLE\n" "ONLY" msgstr "" "DOAR\n" "BÄ‚TÄ‚LIE" msgid "" "NEW\n" "GAME" msgstr "" msgid "" "SAVE\n" "GAME" msgstr "" msgid "" "LOAD\n" "GAME" msgstr "" msgid "INFO" msgstr "" msgid "QUIT" msgstr "" msgid "CANCEL" msgstr "" msgid "OKAY" msgstr "" msgid "smallerButton|OKAY" msgstr "" msgid "ACCEPT" msgstr "" msgid "DECLINE" msgstr "" msgid "LEARN" msgstr "" msgid "TRADE" msgstr "" msgid "YES" msgstr "" msgid "NO" msgstr "" msgid "EXIT" msgstr "" msgid "smallerButton|EXIT" msgstr "" msgid "DISMISS" msgstr "" msgid "UPGRADE" msgstr "" msgid "RESTART" msgstr "" msgid "HEROES" msgstr "" msgid "" "TOWNS/\n" "CASTLES" msgstr "" msgid "S" msgstr "" msgid "M" msgstr "" msgid "L" msgstr "" msgid "X-L" msgstr "" msgid "ALL" msgstr "" msgid "SELECT" msgstr "" msgid "" "STANDARD\n" "GAME" msgstr "" msgid "" "CAMPAIGN\n" "GAME" msgstr "" msgid "" "MULTI-\n" "PLAYER\n" "GAME" msgstr "" msgid "CONFIG" msgstr "" msgid "" "ORIGINAL\n" "CAMPAIGN" msgstr "" msgid "" "EXPANSION\n" "CAMPAIGN" msgstr "" msgid "HOT SEAT" msgstr "" msgid "2 PLAYERS" msgstr "" msgid "3 PLAYERS" msgstr "" msgid "4 PLAYERS" msgstr "" msgid "5 PLAYERS" msgstr "" msgid "6 PLAYERS" msgstr "" msgid "GIFT" msgstr "OFERÄ‚" msgid "MAX" msgstr "" msgid "DIFFICULTY" msgstr "DIFICULTATE" msgid "VIEW INTRO" msgstr "" msgid "MIN" msgstr "MIN" msgid "RESET" msgstr "" msgid "START" msgstr "" msgid "CASTLE" msgstr "" msgid "TOWN" msgstr "" msgid "" "D\n" "I\n" "S\n" "M\n" "I\n" "S\n" "S" msgstr "" msgid "" "E\n" "X\n" "I\n" "T" msgstr "" msgid "" "C\n" "A\n" "M\n" "P\n" "A\n" "I\n" "G\n" "N" msgstr "" msgid "" "S\n" "T\n" "A\n" "N\n" "D\n" "A\n" "R\n" "D" msgstr "" msgid "Warrior" msgstr "Războinic" msgid "Builder" msgstr "Constructor" msgid "Explorer" msgstr "Explorator" msgid "None" msgstr "Nimic" msgid "" "A few\n" "%{monster}" msgstr "" "Câțiva\n" "%{monster}" msgid "" "Several\n" "%{monster}" msgstr "" "Mai mulÈ›i\n" "%{monster}" msgid "" "A pack of\n" "%{monster}" msgstr "" "O haita de\n" "%{monster}" msgid "" "Lots of\n" "%{monster}" msgstr "" "MulÈ›i\n" "%{monster}" msgid "" "A horde of\n" "%{monster}" msgstr "" "O hoardă de\n" "%{monster}" msgid "" "A throng of\n" "%{monster}" msgstr "" "O înghesuială de\n" "%{monster}" msgid "" "A swarm of\n" "%{monster}" msgstr "" "Un roi de\n" "%{monster}" msgid "" "Zounds...\n" "%{monster}" msgstr "" "Extrem de mulÈ›i\n" "%{monster}" msgid "" "A legion of\n" "%{monster}" msgstr "" "O legiune de\n" "%{monster}" msgid "army|Few" msgstr "(1-4)" msgid "army|Several" msgstr "(4-9)" msgid "army|Pack" msgstr "(10-19)" msgid "army|Lots" msgstr "(20-49)" msgid "army|Horde" msgstr "(50-99)" msgid "army|Throng" msgstr "(100-249)" msgid "army|Swarm" msgstr "(1000+)" msgid "army|Zounds" msgstr "(500-999)" msgid "army|Legion" msgstr "(250-499)" msgid "All %{race} troops +1" msgstr "Toate %{race} forÈ›ele +1" msgid "Multiple" msgstr "Multiple" msgid "Troops of %{count} alignments -%{penalty}" msgstr "Trupe cu %{count} aliniamente -%{penalty}" #, fuzzy msgid "Some undead in army -1" msgstr "NiÈ™te strigoi în grup -1" msgid "View %{name}" msgstr "Vezi %{name}" msgid "Move the %{name} " msgstr "Mută %{name}" msgid "Move or right click to redistribute %{name}" msgstr "Mută sau fă click dreapta pentru a redistribui %{name} " msgid "Combine %{name} armies" msgstr "Combină %{name} armate" msgid "Exchange %{name2} with %{name}" msgstr "Schimbă %{name2} cu %{name}" msgid "Select %{name}" msgstr "Selectează %{name}" msgid "Cannot move last troop" msgstr "Nu poÈ›i muta ultima trupă" msgid "Move the %{name}" msgstr "Mută %{name}" msgid "Set Count" msgstr "Setează număratoarea" msgid "%{name} destroys half the enemy troops!" msgid_plural "%{name} destroy half the enemy troops!" msgstr[0] "%{name} distruge jumătate din trupele inamice!" msgstr[1] "%{name} distrug jumătate din trupele inamice!" msgstr[2] "%{name} distrug jumătate din trupele inamice!" msgid "%{name} has turned off the auto battle" msgstr "%{name} a oprit lupta automată" msgid "%{name} has turned on the auto battle" msgstr "%{name} a pornit lupta automată" msgid "Spell failed!" msgstr "Vraja a eÈ™uat!" msgid "" "The Sphere of Negation artifact is in effect for this battle, disabling all " "combat spells." msgstr "" "Artefactul Sfera de Negare este în vigoare pentru această bătălie, " "dezactivând toate vrăjile de luptă." msgid "You have already cast a spell this round." msgstr "Ai aruncat deja o vrajă în această rundă." #, fuzzy msgid "That spell will have no effect!" msgstr "Vraja aceea nu va afecta pe nimeni!" msgid "You may only summon one type of elemental per combat." msgstr "PoÈ›i invoca doar un singur tip de elemental pe luptă." #, fuzzy msgid "" "There is no open space adjacent to your hero where you can summon an " "Elemental to." msgstr "" "Nu există niciun spaÈ›iu deschis adiacent eroului tău în care să invoci un " "elemental." msgid "" "The Moat reduces by -%{count} the defense skill of any unit and slows to " "half movement rate." msgstr "" "ȘanÈ›ul reduce cu -%{count} abilitatea de apărare a oricărei unități È™i " "reduce la jumătate viteza de miÈ™care." msgid "Speed: %{speed}" msgstr "Viteză: %{speed}" msgid "Speed" msgstr "Viteză" msgid "Off" msgstr "Oprit" msgid "On" msgstr "Pornit" #, fuzzy msgid "Turn Order" msgstr "Ordin al armatei" msgid "Auto Spell Casting" msgstr "Aruncarea vrăjilor automata" msgid "Grid" msgstr "Grilă" msgid "Shadow Movement" msgstr "MiÈ™care a umbrelor" msgid "Shadow Cursor" msgstr "Cursor de umbră" msgid "Audio" msgstr "" msgid "Settings" msgstr "" msgid "Configure" msgstr "" msgid "Hot Keys" msgstr "" msgid "Damage Info" msgstr "" msgid "Set the speed of combat actions and animations." msgstr "Setează viteza acÈ›iunilor de luptă È™i a animaÈ›iilor." #, fuzzy msgid "Toggle to display the turn order during the battle." msgstr "Comută pentru a afiÈ™a ordinea armatei în timpul bătăliei." msgid "" "Toggle whether or not the computer will cast spells for you when auto combat " "is on. (Note: This does not affect spell casting for computer players in any " "way, nor does it affect quick combat.)" msgstr "" "Comută dacă computerul va arunca sau nu vrăji pentru tine atunci când este " "activată lupta automată. (Notă: Acest lucru nu afectează în niciun fel " "aruncarea de vrăji pentru jucătorii computerului È™i nici nu afectează lupta " "rapidă)." msgid "" "Toggle the hex grid on or off. The hex grid always underlies movement, even " "if turned off. This switch only determines if the grid is visible." msgstr "" "Activează sau dezactivează grila hexagonală. Grila hexagonală se află " "întotdeauna la baza miÈ™cării, chiar dacă este dezactivată. Acest comutator " "determină doar dacă grila este vizibilă." msgid "" "Toggle on or off shadows showing where your creatures can move and attack." msgstr "" "Activează sau dezactivează umbrele care arată unde se pot miÈ™ca È™i ataca " "creaturile tale." msgid "" "Toggle on or off a shadow showing the current hex location of the mouse " "cursor." msgstr "" "Activează sau dezactivează o umbră care arată locaÈ›ia hexagonală curentă a " "cursorului mouse-ului." msgid "Change the audio settings of the game." msgstr "" msgid "Check and configure all the hot keys present in the game." msgstr "" #, fuzzy msgid "Toggle to display damage information during the battle." msgstr "Comută pentru a afiÈ™a ordinea armatei în timpul bătăliei." msgid "Exit this menu." msgstr "" msgid "Okay" msgstr "" msgid "The enemy has surrendered!" msgstr "Inamicul s-a predat!" msgid "Their cowardice costs them %{gold} gold." msgstr "" msgid "The enemy has fled!" msgstr "Inamicul a fugit!" msgid "A glorious victory!" msgstr "O victorie glorioasă!" msgid "For valor in combat, %{name} receives %{exp} experience." msgstr "Pentru curaj în luptă, %{name} primeÈ™te %{exp} experiență." msgid "The cowardly %{name} flees from battle." msgstr "LaÈ™ul %{name} fuge de luptă." msgid "%{name} surrenders to the enemy, and departs in shame." msgstr "%{name} se predă inamicului È™i pleacă ruÈ™inat." #, fuzzy msgid "Your forces suffer a bitter defeat, and %{name} abandons your cause." msgstr "" "ForÈ›ele tale suferă o înfrângere amară, iar %{name} îți abandonează cauza." #, fuzzy msgid "Your forces suffer a bitter defeat." msgstr "ForÈ›a ta a suferit o înfrângere amară." msgid "Battlefield Casualties" msgstr "Victimele de pe câmpul de luptă" msgid "Attacker" msgstr "Atacator" msgid "Defender" msgstr "Apărător" msgid "Click to leave the battle results." msgstr "" #, fuzzy msgid "Click to restart the battle in manual mode." msgstr "EÈ™ti sigur că vrei să termini bătălia în modul automat?" msgid "Restart" msgstr "" msgid "You have captured an enemy artifact!" msgstr "Ai capturat un artefact inamic!" msgid "As you reach for the %{name}, it mysteriously disappears." msgstr "Atunci când te apropii de %{name}, acesta dispare în mod misterios." msgid "As your enemy reaches for the %{name}, it mysteriously disappears." msgstr "" "ÃŽn timp ce inamicul tău ajunge la %{name}, acesta dispare în mod misterios." msgid "Necromancy!" msgstr "NecromanÈ›ie!" msgid "" "Practicing the dark arts of necromancy, you are able to raise %{count} of " "the enemy's dead to return under your service as %{monster}." msgstr "" "Practicând artele întunecate ale necromanÈ›iei, eÈ™ti capabil să trezeÈ™ti " "%{numărul} de morÈ›i ai inamicului să se întoarcă în slujba ta ca %{monstru}." msgid "%{name} the %{race}" msgstr "%{name} cel %{race}" msgid "Captain of %{name}" msgstr "Căpitan al %{name}" msgid "Attack" msgstr "Atac" msgid "Defense" msgstr "Apărare" msgid "Spell Power" msgstr "Puterea vrăjii" msgid "Knowledge" msgstr "CunoÈ™tinÈ›e" msgid "Morale" msgstr "Moral" msgid "Luck" msgstr "Noroc" msgid "Spell Points" msgstr "Puncte de vrajă" msgid "Hero's Options" msgstr "OpÈ›iunile eroului" msgid "Cast Spell" msgstr "Aruncă vraja" msgid "Retreat" msgstr "Retragere" msgid "Surrender" msgstr "Predare" msgid "Cancel" msgstr "Anulează" msgid "Hero Screen" msgstr "Ecranul eroului" msgid "Captain's Options" msgstr "OpÈ›iunile căpitanului" msgid "" "Cast a magical spell. You may only cast one spell per combat round. The " "round is reset when every creature has had a turn." msgstr "" "Aruncă o vrajă magică. PoÈ›i lansa doar o singură vrajă pe rundă de luptă. " "Runda se resetează atunci când fiecare creatură a avut o rundă." msgid "" "Retreat your hero, abandoning your creatures. Your hero will be available " "for you to recruit again, however, the hero will have only a novice hero's " "forces." msgstr "" "Retrage-È›i eroul, abandonându-È›i creaturile. Eroul tău va fi disponibil " "pentru a fi recrutat din nou, însă eroul va avea doar forÈ›ele unui erou " "începător." #, fuzzy msgid "" "Surrendering costs gold. However if you pay the ransom, the hero and all of " "his or her surviving creatures will be available to recruit again. The cost " "of surrender is half of the total cost of the non-temporary troops remaining " "in the army." msgstr "" "Predarea costă aur. Cu toate acestea, dacă plăteÈ™ti răscumpărarea, eroul È™i " "toate creaturile sale supravieÈ›uitoare vor fi disponibile pentru a fi " "recrutate din nou." msgid "Open Hero Screen to view full information about the hero." msgstr "" "Deschide ecranul eroului pentru a vedea informaÈ›ii complete despre erou." msgid "Return to the battle." msgstr "ÃŽntoarce-te la luptă." msgid "Not enough gold (%{gold})" msgstr "ÃŽntoarce-te la luptă.Nu este suficient aur (%{gold})" msgid "%{name} states:" msgstr "%{name} declară:" msgid "Captain of %{name} states:" msgstr "Căpitanul de stat %{name}:" msgid "" "\"I will accept your surrender and grant you and your troops safe passage " "for the price of %{price} gold.\"" msgstr "" "\"Voi accepta capitularea ta È™i îți voi acorda È›ie È™i trupelor tale o " "trecere sigură pentru preÈ›ul de %{price} aur.\"" msgid "View %{monster} info" msgstr "Vezi %{monster} info" msgid "Fly %{monster} here" msgstr "Zboară %{monster} aici" msgid "Move %{monster} here" msgstr "Mută %{monster} aici" msgid "Shoot %{monster}" msgstr "Trage în %{monster}" msgid "(1 shot left)" msgid_plural "(%{count} shots left)" msgstr[0] "(o lovitură ramasă)" msgstr[1] "(%{count} lovituri rămase)" msgstr[2] "(%{count} lovituri rămase)" msgid "Attack %{monster}" msgstr "Atacă %{monster}" msgid "Turn %{turn}" msgstr "Runda %{turn}" msgid "Teleport here" msgstr "Teleportează-te aici" msgid "Invalid teleport destination" msgstr "DestinaÈ›ie de teleportare invalidă" msgid "Cast %{spell} on %{monster}" msgstr "Aruncă %{spell} pe %{monster}" msgid "Cast %{spell}" msgstr "Aruncă %{spell}" msgid "Select spell target" msgstr "SelectaÈ›i È›inta vrăjii" msgid "View Ballista info" msgstr "Vezi informaÈ›ii despre Ballista" msgid "Ballista" msgstr "Ballista" msgid "Enable auto combat" msgstr "Activează lupta automată" msgid "Allows the computer to fight out the battle for you." msgstr "Permite computerului să lupte în locul tău." msgid "Auto Combat" msgstr "Luptă automată" msgid "Customize system options" msgstr "Personalizează opÈ›iunile sistemului" msgid "Allows you to customize the combat screen." msgstr "ÃŽÈ›i permite să personalizezi ecranul de luptă." msgid "System Options" msgstr "System Options" msgid "Skip this unit" msgstr "Sari peste această unitate" msgid "Skip" msgstr "Sari" msgid "" "Skips the current creature. The current creature ends its turn and does not " "get to go again until the next round." msgstr "" "Trece peste creatura curentă. Creatura curentă își termină rândul È™i nu mai " "are posibilitatea de a merge din nou până în runda următoare." msgid "View Captain's options" msgstr "Vezi opÈ›iunile căpitanului" msgid "View Hero's options" msgstr "Vezi opÈ›iunile eroului" msgid "View opposing Captain" msgstr "Vezi căpitanul care se opune" msgid "View opposing Hero" msgstr "Vezi eroul care se opune" msgid "Hide logs" msgstr "Ascunde jurnalele" msgid "Show logs" msgstr "AfiÈ™ează jurnalele" msgid "Message Bar" msgstr "Bara de mesaje" msgid "Shows the results of individual monster's actions." msgstr "AfiÈ™ează rezultatele acÈ›iunilor individuale ale monÈ™trilor." msgid "Are you sure you want to finish the battle in auto mode?" msgstr "EÈ™ti sigur că vrei să termini bătălia în modul automat?" #, fuzzy msgid "Are you sure you want to enable auto combat?" msgstr "EÈ™ti sigur că vrei să te retragi?" msgid "%{name} skip their turn." msgstr "%{name} È™i-a sărit rândul." msgid "%{attacker} does %{damage} damage." msgid_plural "%{attacker} do %{damage} damage." msgstr[0] "%{attacker} provoacă %{damage} pagube." msgstr[1] "%{attacker} face %{damage} pagube." msgstr[2] "" msgid "1 creature perishes." msgid_plural "%{count} creatures perish." msgstr[0] "1 creatură piere." msgstr[1] "%{count} creaturi pier." msgstr[2] "%{count} creaturi pier." msgid "1 %{defender} perishes." msgid_plural "%{count} %{defender} perish." msgstr[0] "1 %{defender} piere." msgstr[1] "%{count} %{defender} %{defender} piere." msgstr[2] "%{count} %{defender} %{defender} piere." #, fuzzy msgid "1 soul is incorporated." msgid_plural "%{count} souls are incorporated." msgstr[0] "Se creează o imagine în oglindă." msgstr[1] "Se creează o imagine în oglindă." msgstr[2] "Se creează o imagine în oglindă." msgid "1 %{unit} is revived." msgid_plural "%{count} %{unit} are revived." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "Moved %{monster}: from [%{src}] to [%{dst}]." msgstr "%{monster} mutat: de la [%{src}] la [%{dst}]." msgid "The %{name} resist the spell!" msgstr "%{name} rezistă la vrajă!" msgid "%{name} casts %{spell} on the %{troop}." msgstr "%{name} aruncă %{spell} asupra lui %{troop}." msgid "%{name} casts %{spell}." msgstr "%{name} aruncă %{spell}." msgid "The %{spell} does %{damage} damage to one undead creature." msgstr "Vraja %{spell} provoacă daune %{damage} unei creaturi strigoi." msgid "The %{spell} does %{damage} damage to all undead creatures." msgstr "Vraja %{spell} provoacă daune %{damage} tuturor creaturilor strigoi." msgid "The %{spell} does %{damage} damage, %{count} creatures perish." msgstr "Vraja %{spell} face daune %{damage}, %{count} creaturi pier." msgid "The %{spell} does %{damage} damage." msgstr "Vraja %{spell} face daune %{damage}." msgid "The %{spell} does %{damage} damage to one living creature." msgstr "Vraja %{spell} provoacă daune %{damage} unei creaturi vii." msgid "The %{spell} does %{damage} damage to all living creatures." msgstr "Vraja %{spell} provoacă daune %{damage} tuturor creaturilor vii." msgid "The %{attacker}' attack blinds the %{target}!" msgstr "Atacul %{attacker}' îl orbeÈ™te pe %{target}!" msgid "The %{attacker}' gaze turns the %{target} to stone!" msgstr "Privirea %{attacker}' îl transformă pe %{target} în piatră!" msgid "The %{attacker}' curse falls upon the %{target}!" msgstr "Blestemul lui %{attacker}' cade asupra lui %{target}!" msgid "The %{target} are paralyzed by the %{attacker}!" msgstr "%{target} sunt paralizaÈ›i de %{attacker}!" msgid "The %{attacker} dispel all good spells on your %{target}!" msgstr "%{attacker} risipeÈ™te toate vrăjile bune asupra %{target} tale!" msgid "The %{attacker} cast %{spell} on %{target}!" msgstr "%{attacker} a aruncat %{spell} pe %{target}!" msgid "Bad luck descends on the %{attacker}." msgstr "Ghinionul coboară asupra %{attacker}." msgid "Good luck shines on the %{attacker}." msgstr "Norocul străluceÈ™te asupra %{attacker}." msgid "High morale enables the %{monster} to attack again." msgstr "Un moral ridicat permite %{monster} să atace din nou." msgid "Low morale causes the %{monster} to freeze in panic." msgstr "Moralul scăzut face ca %{monster} să îngheÈ›e de panică." msgid "%{tower} does %{damage} damage." msgstr "%{tower} face %{damage} daune." msgid "The mirror image is created." msgstr "Se creează o imagine în oglindă." msgid "The mirror image is destroyed!" msgstr "Imaginea în oglindă este distrusă!" msgid "Are you sure you want to interrupt the auto battle?" msgstr "EÈ™ti sigur că vrei să întrerupi lupta automată?" msgid "Error" msgstr "Eroare" msgid "No spells to cast." msgstr "Nu sunt vrăji de aruncat." msgid "Are you sure you want to retreat?" msgstr "EÈ™ti sigur că vrei să te retragi?" msgid "Retreat disabled" msgstr "Retragerea oprită" msgid "Surrender disabled" msgstr "Predarea oprită" msgid "Damage: %{max}" msgstr "Daune: %{max}" msgid "Damage: %{min} - %{max}" msgstr "Daune: %{min} - %{max}" msgid "Perish: %{max}" msgstr "Fatalități: %{max}" msgid "Perish: %{min} - %{max}" msgstr "Fatalități: %{min} - %{max}" msgid "" "Through eagle-eyed observation, %{name} is able to learn the magic spell " "%{spell}." msgstr "" "Printr-o observaÈ›ie cu ochi de vultur, %{name} este capabil să înveÈ›e vraja " "magică %{spell}." msgid "Human" msgstr "Om" msgid "AI" msgstr "AI" msgid "Start" msgstr "" #, fuzzy msgid "Start the battle." msgstr "ÃŽntoarce-te la luptă." msgid "Exit" msgstr "" msgid "Reset" msgstr "" msgid "Reset to default settings." msgstr "" msgid "Please select another hero." msgstr "Te rog selectează un alt erou." msgid "Set Attack Skill" msgstr "Setează abilitățile de atac" msgid "Set Defense Skill" msgstr "Setează abilitățile de apărare" msgid "Set Power Skill" msgstr "Setează abilitățile de putere" msgid "Set Knowledge Skill" msgstr "Setează abilitățile de cunoaÈ™tere" #, fuzzy msgid "%{race1} %{name1} vs %{race2} %{name2}" msgstr "%{race1} %{name1}" msgid "Monsters" msgstr "MonÈ™tri" msgid "N/A" msgstr "N/A" msgid "Left Turret" msgstr "Turela stângă" msgid "Right Turret" msgstr "Turela dreaptă" msgid "The %{name} fires with the strength of %{count} Archers" msgstr "%{name} trage cu puterea de %{count} arcaÈ™i" msgid "each with a +%{attack} bonus to their attack skill." msgstr "fiecare cu un bonus de +%{attack} la abilitatea lor de atac." #, fuzzy msgid "The %{name} is destroyed." msgstr "Imaginea în oglindă este distrusă!" #, fuzzy msgid "%{count} %{name} rises from the dead!" msgid_plural "%{count} %{name} rise from the dead!" msgstr[0] "%{count} %{name} învie din morÈ›i!" msgstr[1] "%{count} %{name} învie din morÈ›i!" msgstr[2] "%{count} %{name} învie din morÈ›i!" msgid "Dwarven Alliance" msgstr "AlianÈ›a Piticilor" msgid "Sorceress Guild" msgstr "Breasla Vrăjitoarelor" msgid "Necromancer Guild" msgstr "Breasla Necromancerilor" msgid "Ogre Alliance" msgstr "AlianÈ›a Ogrilor" msgid "Dwarfbane" msgstr "Blestemul Piticilor" msgid "Dragon Alliance" msgstr "AlianÈ›a Dragonilor" msgid "Elven Alliance" msgstr "AlianÈ›a Elfilor" msgid "Kraeger defeated" msgstr "Kraeger este învins" msgid "Wayward Son" msgstr "Fiul Rătăcitor" msgid "Uncle Ivan" msgstr "Unchiul Ivan" msgid "Annexation" msgstr "Anexare" msgid "Force of Arms" msgstr "ForÈ›a armelor" msgid "Save the Dwarves" msgstr "SalvaÈ›i piticii" msgid "Carator Mines" msgstr "Minele Carator" msgid "Turning Point" msgstr "Punct de cotitură" msgid "scenarioName|Defender" msgstr "" msgid "Corlagon's Defense" msgstr "Apărarea lui Corlagon" msgid "The Crown" msgstr "Coroane" msgid "The Gauntlet" msgstr "MănuÈ™a" msgid "Betrayal" msgstr "Trădare" msgid "Final Justice" msgstr "JustiÈ›ie finală" #, fuzzy msgid "" "Roland needs you to defeat the lords near his castle to begin his war of " "rebellion against his brother. They are not allied with each other, so they " "will spend most of their time fighting with one another. Victory is yours " "when you have defeated all of their castles and heroes." msgstr "" "Roland are nevoie de tine pentru a-i învinge pe lorzii din apropierea " "castelului său pentru a începe războiul de rebeliune împotriva fratelui " "său. Nu sunt aliaÈ›i între ei, aÈ™a că își vor petrece mare parte a timpului " "luptându-se unul cu celălalt. Victoria este a ta atunci când ai învins " "toate castelele È™i eroii lor." msgid "" "The local lords refuse to swear allegiance to Roland, and must be subdued. " "They are wealthy and powerful, so be prepared for a tough fight. Capture all " "enemy castles to win." msgstr "" "Lorzii locali refuză să jure credință lui Roland È™i trebuie supuÈ™i. Sunt " "bogaÈ›i È™i puternici, aÈ™a că fii pregătit pentru o luptă grea. Capturează " "toate castelele inamice pentru a câștiga." msgid "" "Your task is to defend the Dwarves against Archibald's forces. Capture all " "of the enemy towns and castles to win, and be sure not to lose all of the " "dwarf towns at once, or the enemy will have won." msgstr "" "Sarcina ta este de a apăra Piticii împotriva forÈ›elor lui Archibald. " "Capturează toate oraÈ™ele È™i castelele inamice pentru a câștiga È™i asigură-te " "că nu pierzi toate oraÈ™ele piticilor deodată, altfel inamicul va câștiga." msgid "" "You will face four allied enemies in a straightforward fight for resource " "and treasure. Capture all of the enemy castles for victory." msgstr "" "Te vei confrunta cu patru inamici aliaÈ›i într-o luptă directă pentru resurse " "È™i comori. Capturează toate castelele inamice pentru a obÈ›ine victoria." msgid "" "Your enemies are allied against you and start close by, so be ready to come " "out fighting. You will need to own all four castles in this small valley to " "win." msgstr "" "DuÈ™manii tăi sunt aliaÈ›i împotriva ta È™i pornesc din apropiere, aÈ™a că " "pregăteÈ™te-te să ieÈ™i la luptă. Va trebui să deÈ›ii toate cele patru castele " "din această mică vale pentru a câștiga." msgid "" "The Sorceress' guild of Noraston has requested Roland's aid against an " "attack from Archibald's allies. Capture all of the enemy castles to win, and " "don't lose Noraston, or you'll lose the scenario. (Hint: There is an enemy " "castle on an island in the ocean.)" msgstr "" "Breasla vrăjitoarelor din Noraston a cerut ajutorul lui Roland împotriva " "unui atac al aliaÈ›ilor lui Archibald. Capturează toate castelele inamice " "pentru a câștiga È™i nu pierde Noraston, altfel vei pierde scenariul. " "(Sugestie: există un castel inamic pe o insulă în ocean)." msgid "" "Gather as large an army as possible and capture the enemy castle within 8 " "weeks. You are opposed by only one enemy, but must travel a long way to get " "to the enemy castle. Any troops you have in your army at the end of this " "scenario will be with you in the final battle." msgstr "" "Adună o armată cât mai numeroasă È™i cucereÈ™te castelul inamic în termen de 8 " "săptămâni. Ai un singur inamic ca adversar, dar trebuie să parcurgi un drum " "lung pentru a ajunge la castelul inamic. Toate trupele pe care le ai în " "armată la sfârÈ™itul acestui scenariu vor fi alături de tine în bătălia " "finală." msgid "" "Find the Crown before Archibald's heroes find it. Roland will need the Crown " "for the final battle against Archibald." msgstr "" "GăseÈ™te Coroana înainte ca eroii lui Archibald să o găsească. Roland va avea " "nevoie de Coroană pentru bătălia finală împotriva lui Archibald." msgid "" "Three allied enemies stand before you and victory, including Lord Corlagon. " "Roland is in a castle to the northwest, and you will lose if he falls to the " "enemy. Remember that capturing Lord Corlagon will ensure that he will not " "fight against you in the final scenario." msgstr "" "Trei inamici aliaÈ›i se află în faÈ›a ta È™i a victoriei, inclusiv Lordul " "Corlagon. Roland se află într-un castel din nord-vest, iar tu vei pierde " "dacă va cădea în mâinile inamicului. Nu uita că prin capturarea Lordului " "Corlagon te vei asigura că nu va lupta împotriva ta în scenariul final." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Archibald to end the war!" msgstr "" "Aceasta este bătălia finală. Atât tu cât È™i inamicul tău sunteÈ›i înarmaÈ›i " "până în dinÈ›i È™i toÈ›i sunt aliaÈ›i împotriva ta. Capturează-l pe Archibald " "pentru a pune capăt războiului!" #, fuzzy msgid "" "Switching sides leaves you with three castles against the enemy's one. This " "battle will be the easiest one you will face for the rest of the war..." "traitor." msgstr "" "Schimbarea taberei te lasă cu trei castele împotriva castelului inamicului. " "Această bătălie va fi cea mai uÈ™oară cu care te vei confrunta pentru tot " "restul războiului... trădătorule." msgid "Barbarian Wars" msgstr "" msgid "First Blood" msgstr "" msgid "Necromancers" msgstr "" msgid "Slay the Dwarves" msgstr "" msgid "Country Lords" msgstr "" msgid "Dragon Master" msgstr "" msgid "Rebellion" msgstr "" msgid "Apocalypse" msgstr "" msgid "Greater Glory" msgstr "" #, fuzzy msgid "" "King Archibald requires you to defeat the three enemies in this region. They " "are not allied with one another, so they will spend most of their energy " "fighting amongst themselves. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Roland are nevoie de tine pentru a-i învinge pe lorzii din apropierea " "castelului său pentru a începe războiul de rebeliune împotriva fratelui " "său. Nu sunt aliaÈ›i între ei, aÈ™a că își vor petrece mare parte a timpului " "luptându-se unul cu celălalt. Victoria este a ta atunci când ai învins " "toate castelele È™i eroii lor." msgid "" "You must unify the barbarian tribes of the north by conquering them. As in " "the previous mission, the enemy is not allied against you, but they have " "more resources at their disposal. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" msgid "" "Do-gooder wizards have taken the Necromancers' castle. You must retake it to " "achieve victory. Remember that while you start with a powerful army, you " "have no castle and must take one within 7 days, or lose this battle. (Hint: " "The nearest castle is to the southeast.)" msgstr "" msgid "" "The dwarves need conquering before they can interfere in King Archibald's " "plans. Roland's forces have more than one hero and many towns to start with, " "so be ready for attack from multiple directions. You must capture all of the " "enemy towns and castles to claim victory." msgstr "" msgid "" "You must put down a peasant revolt led by Roland's forces. All are allied " "against you, but you have Lord Corlagon, an experienced hero, to help you. " "Capture all enemy castles to win." msgstr "" msgid "" "There are two enemies allied against you in this mission. Both are well " "armed and seek to evict you from their island. Avoid them and capture Dragon " "City to win." msgstr "" msgid "" "Your orders are to conquer the country lords that have sworn to serve " "Roland. All of the enemy castles are unified against you. Since you start " "without a castle, you must hurry to capture one before the end of the week. " "Capture all enemy castles for victory." msgstr "" msgid "" "Find the Crown before Roland's heroes find it. Archibald will need the Crown " "for the final battle against Roland." msgstr "" msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Roland to win the war, and be sure " "not to lose Archibald in the fight!" msgstr "" msgid "Arrow's Flight" msgstr "" msgid "Island of Chaos" msgstr "" msgid "The Abyss" msgstr "" msgid "Uprising" msgstr "" msgid "Aurora Borealis" msgstr "" msgid "Betrayal's End" msgstr "" msgid "Corruption's Heart" msgstr "" msgid "The Giant's Pass" msgstr "" msgid "" "Subdue the unruly local lords in order to provide the Empire with facilities " "to operate in this region." msgstr "" msgid "" "Eliminate all opposition in this area. Then the first piece of the artifact " "will be yours." msgstr "" msgid "" "The sorceresses to the northeast are rebelling! For the good of the empire " "you must quash their feeble uprising on your way to the mountains." msgstr "" msgid "" "Having prepared for your arrival, Kraeger has arranged for a force of " "necromancers to thwart your quest. You must capture the castle of Scabsdale " "before the first day of the third week, or the Necromancers will be too " "strong for you." msgstr "" msgid "" "The barbarian despot in this area is, as yet, ignorant of your presence. " "Quickly, build up your forces before you are discovered and attacked! Secure " "the region by subduing all enemy forces." msgstr "" msgid "" "The Empire is weak in this region. You will be unable to completely subdue " "all forces in this area, so take what you can before reprisal strikes. " "Remember, your true goal is to claim the Helmet of Anduran." msgstr "" msgid "For the good of the Empire, eliminate Kraeger." msgstr "" msgid "" "At last, you have the opportunity and the facilities to rid the Empire of " "the necromancer's evil. Eradicate them completely, and you will be sung as a " "hero for all time." msgstr "" msgid "Border Towns" msgstr "" msgid "Conquer and Unify" msgstr "" msgid "Crazy Uncle Ivan" msgstr "" msgid "The Wayward Son" msgstr "" msgid "Ivory Gates" msgstr "" msgid "The Elven Lands" msgstr "" msgid "The Epic Battle" msgstr "" msgid "The Southern War" msgstr "" msgid "" "Conquer and unite all the enemy tribes. Don't lose the hero Jarkonas, the " "forefather of all descendants." msgstr "" msgid "" "Your rival, the Kingdom of Harondale, is attacking weak towns on your " "border! Recover from their first strike and crush them completely!" msgstr "" msgid "" "Find your wayward son Joseph who is rumored to be living in the desolate " "lands. Do it before the first day of the third month or it will be of no " "help to your family." msgstr "" msgid "" "Rescue your crazy uncle Ivan. Find him before the first day of the fourth " "month or it will be no help to your kingdom." msgstr "" msgid "" "Destroy the barbarians who are attacking the southern border of your " "kingdom! Recover your fallen towns, and then invade the jungle kingdom. " "Leave no enemy standing." msgstr "" msgid "Retake the castle of Ivory Gates, which has fallen due to treachery." msgstr "" msgid "" "Gain the favor of the elves. They will not allow trees to be chopped down, " "so they will send you wood every 2 weeks. You must complete your mission " "before the first day of the seventh month, or the kingdom will surely fall." msgstr "" msgid "" "This is the final battle against your rival kingdom of Harondale. Eliminate " "everyone, and don't lose the hero Jarkonas VI." msgstr "" msgid "Fount of Wizardry" msgstr "" msgid "Power's End" msgstr "" msgid "The Eternal Scrolls" msgstr "" msgid "The Shrouded Isles" msgstr "" msgid "" "Your mission is to vanquish the warring mages in the magical Shrouded Isles. " "The completion of this task will give you a fighting chance against your " "rivals." msgstr "" msgid "" "The location of the great library has been discovered! You must make your " "way to it, and reclaim the city of Chronos in which it lies." msgstr "" msgid "" "Find the Orb of negation, which is said to be buried in this land. There are " "clues inscribed on stone obelisks which will help lead you to your prize. " "Find the Orb before the first day of the sixth month, or your rivals will " "surely have gotten to the fount before you." msgstr "" msgid "" "You must take control of the castle of Magic, where the fount of wizardry " "lies. Do this and your victory will be supreme." msgstr "" msgid "Blood is Thicker" msgstr "" msgid "King and Country" msgstr "" msgid "Pirate Isles" msgstr "" msgid "Stranded" msgstr "" msgid "" "Capture the town on the island off the southeast shore in order to construct " "a boat and travel back towards the mainland. Do not lose the hero Gallavant." msgstr "" msgid "" "Find and defeat Martine, the pirate leader, who resides in Pirates Cove. Do " "not lose Gallavant or your quest will be over." msgstr "" msgid "" "Eliminate all the other forces who oppose the rule of Lord Alberon. " "Gallavant must not die." msgstr "" msgid "" "Overthrow the entrenched monarchy of Lord Alberon, and claim all the land in " "your name. Gallavant must not die." msgstr "" msgid " bane" msgstr "" msgid " alliance" msgstr "" msgid "Carry-over forces" msgstr "" msgid " bonus" msgstr "" msgid " defeated" msgstr "" msgid " will always run away from your army." msgstr "" msgid " will be willing to join your army." msgstr "" msgid "\"%{artifact}\" artifact will be carried over the scenario." msgstr "" msgid "The army will be carried over the scenario." msgstr "" msgid "The kingdom will have +%{count} %{resource} each day." msgstr "" msgid "\"%{spell}\" spell will be carried over the scenario." msgstr "" msgid "%{hero} can be hired in the scenario." msgstr "" msgid "" "%{hero} has been defeated and will not appear in the subsequent scenarios." msgstr "" msgid "The dwarves recognize their allies and gladly join your forces." msgstr "" msgid "The ogres recognize you as the Dwarfbane and lumber over to join you." msgstr "" msgid "" "The dragons, snarling and growling, agree to join forces with you, their " "'Ally'." msgstr "" msgid "" "As you approach the group of elves, their leader calls them all to " "attention. He shouts to them, \"Who of you is brave enough to join this " "fearless ally of ours?\" The group explodes with cheers as they run to join " "your ranks." msgstr "" msgid "" "The dwarves hail you, \"Any friend of Roland is a friend of ours. You may " "pass.\"" msgstr "" msgid "" "The ogres give you a grunt of recognition, \"Archibald's allies may pass.\"" msgstr "" msgid "" "The dragons see you and call out. \"Our alliance with Archibald compels us " "to join you. Unfortunately you have no room. A pity!\" They quickly scatter." msgstr "" msgid "" "The elves stand at attention as you approach. Their leader calls to you and " "says, \"Let us not impede your progress, ally! Move on, and may victory be " "yours.\"" msgstr "" msgid "\"The Dwarfbane!!!!, run for your lives.\"" msgstr "" msgid "campaignBonus|Animate Dead" msgstr "" msgid "campaignBonus|Chain Lightning" msgstr "" msgid "campaignBonus|Fireblast" msgstr "" msgid "campaignBonus|Mass Curse" msgstr "" msgid "campaignBonus|Mass Haste" msgstr "" msgid "campaignBonus|Mirror Image" msgstr "" msgid "campaignBonus|Resurrect" msgstr "" msgid "campaignBonus|Steelskin" msgstr "" msgid "campaignBonus|Summon Earth" msgstr "" msgid "campaignBonus|View Heroes" msgstr "" msgid "campaignBonus|Ballista" msgstr "" msgid "campaignBonus|Black Pearl" msgstr "" msgid "campaignBonus|Caster's Bracelet" msgstr "" msgid "campaignBonus|Defender Helm" msgstr "" msgid "campaignBonus|Breastplate" msgstr "" msgid "campaignBonus|Dragon Sword" msgstr "" msgid "campaignBonus|Fizbin Medal" msgstr "" msgid "campaignBonus|Foremost Scroll" msgstr "" msgid "campaignBonus|Gauntlets" msgstr "" msgid "campaignBonus|Hideous Mask" msgstr "" msgid "campaignBonus|Mage's Ring" msgstr "" msgid "campaignBonus|Major Scroll" msgstr "" msgid "campaignBonus|Medal of Honor" msgstr "" msgid "campaignBonus|Medal of Valor" msgstr "" msgid "campaignBonus|Minor Scroll" msgstr "" msgid "campaignBonus|Nomad Boots" msgstr "" msgid "campaignBonus|Power Axe" msgstr "" msgid "campaignBonus|Spiked Shield" msgstr "" msgid "campaignBonus|Stealth Shield" msgstr "" msgid "campaignBonus|Tax Lien" msgstr "" msgid "campaignBonus|Thunder Mace" msgstr "" msgid "campaignBonus|Traveler's Boots" msgstr "" msgid "campaignBonus|White Pearl" msgstr "" msgid "campaignBonus|Basic Archery" msgstr "" msgid "campaignBonus|Advanced Archery" msgstr "" msgid "campaignBonus|Expert Archery" msgstr "" msgid "campaignBonus|Basic Ballistics" msgstr "" msgid "campaignBonus|Advanced Ballistics" msgstr "" msgid "campaignBonus|Expert Ballistics" msgstr "" msgid "campaignBonus|Basic Diplomacy" msgstr "" msgid "campaignBonus|Advanced Diplomacy" msgstr "" msgid "campaignBonus|Expert Diplomacy" msgstr "" msgid "campaignBonus|Basic Eagle Eye" msgstr "" msgid "campaignBonus|Advanced Eagle Eye" msgstr "" msgid "campaignBonus|Expert Eagle Eye" msgstr "" msgid "campaignBonus|Basic Estates" msgstr "" msgid "campaignBonus|Advanced Estates" msgstr "" msgid "campaignBonus|Expert Estates" msgstr "" msgid "campaignBonus|Basic Leadership" msgstr "" msgid "campaignBonus|Advanced Leadership" msgstr "" msgid "campaignBonus|Expert Leadership" msgstr "" msgid "campaignBonus|Basic Logistics" msgstr "" msgid "campaignBonus|Advanced Logistics" msgstr "" msgid "campaignBonus|Expert Logistics" msgstr "" msgid "campaignBonus|Basic Luck" msgstr "" msgid "campaignBonus|Advanced Luck" msgstr "" msgid "campaignBonus|Expert Luck" msgstr "" msgid "campaignBonus|Basic Mysticism" msgstr "" msgid "campaignBonus|Advanced Mysticism" msgstr "" msgid "campaignBonus|Expert Mysticism" msgstr "" msgid "campaignBonus|Basic Navigation" msgstr "" msgid "campaignBonus|Advanced Navigation" msgstr "" msgid "campaignBonus|Expert Navigation" msgstr "" msgid "campaignBonus|Basic Necromancy" msgstr "" msgid "campaignBonus|Advanced Necromancy" msgstr "" msgid "campaignBonus|Expert Necromancy" msgstr "" msgid "campaignBonus|Basic Pathfinding" msgstr "" msgid "campaignBonus|Advanced Pathfinding" msgstr "" msgid "campaignBonus|Expert Pathfinding" msgstr "" msgid "campaignBonus|Basic Scouting" msgstr "" msgid "campaignBonus|Advanced Scouting" msgstr "" msgid "campaignBonus|Expert Scouting" msgstr "" msgid "campaignBonus|Basic Wisdom" msgstr "" msgid "campaignBonus|Advanced Wisdom" msgstr "" msgid "campaignBonus|Expert Wisdom" msgstr "" msgid "" "The main hero will have \"%{artifact}\" artifact at the start of the " "scenario." msgstr "" msgid "" "The kingdom will receive %{amount} additional %{resource} at the start of " "the scenario." msgstr "" msgid "" "The kingdom will have %{amount} less %{resource} at the start of the " "scenario." msgstr "" msgid "" "The main hero will have %{count} %{monster} at the start of the scenario." msgstr "" msgid "" "The main hero will have \"%{spell}\" spell at the start of the scenario." msgstr "" msgid "The starting race of the scenario will be %{race}." msgstr "" msgid "" "The main hero will have additional %{count} %{skill} at the start of the " "scenario." msgstr "" msgid "The main hero will have %{skill} at the start of the scenario." msgstr "" msgid "Roland" msgstr "" msgid "Archibald" msgstr "" msgid "Price of Loyalty" msgstr "" msgid "Voyage Home" msgstr "" msgid "Wizard's Isle" msgstr "" msgid "Descendants" msgstr "" msgid "The %{building} produces %{monster}." msgstr "" msgid "Requires:" msgstr "" msgid "Exit this menu without doing anything." msgstr "" msgid "Cannot build. You have already built here today." msgstr "" msgid "For this action it is necessary to build a castle first." msgstr "" msgid "Cannot build %{name} because the castle is too far away from an ocean." msgstr "" msgid "disable build." msgstr "" msgid "Cannot afford %{name}." msgstr "" msgid "%{name} is already built." msgstr "" msgid "Cannot build %{name}." msgstr "" msgid "Build %{name}." msgstr "" msgid "Blackridge" msgstr "" msgid "Hillstone" msgstr "" msgid "Pinehurst" msgstr "" msgid "Whiteshield" msgstr "" msgid "Woodhaven" msgstr "" msgid "Blackwind" msgstr "" msgid "Bloodreign" msgstr "" msgid "Dragontooth" msgstr "" msgid "Greywind" msgstr "" msgid "Portsmith" msgstr "" msgid "Atlantium" msgstr "" msgid "Middle Gate" msgstr "" msgid "Sansobar" msgstr "" msgid "Tundara" msgstr "" msgid "Vulcania" msgstr "" msgid "Baywatch" msgstr "" msgid "Fountainhead" msgstr "" msgid "Vertigo" msgstr "" msgid "Wildabar" msgstr "" msgid "Winterkill" msgstr "" msgid "Brindamoor" msgstr "" msgid "Lakeside" msgstr "" msgid "Nightshadow" msgstr "" msgid "Olympus" msgstr "" msgid "Sandcaster" msgstr "" msgid "Alamar" msgstr "" msgid "Burlock" msgstr "" msgid "Dragadune" msgstr "" msgid "Kalindra" msgstr "" msgid "Xabran" msgstr "" msgid "Algary" msgstr "" msgid "Basenji" msgstr "" msgid "Blackfang" msgstr "" msgid "New Dawn" msgstr "" msgid "Sorpigal" msgstr "" msgid "Avone" msgstr "" msgid "Big Oak" msgstr "" msgid "Chandler" msgstr "" msgid "Erliquin" msgstr "" msgid "Hampshire" msgstr "" msgid "Antioch" msgstr "" msgid "Avalon" msgstr "" msgid "Roc Haven" msgstr "" msgid "South Mill" msgstr "" msgid "Weed Patch" msgstr "" msgid "Brownston" msgstr "" msgid "Hilltop" msgstr "" msgid "Weddington" msgstr "" msgid "Westfork" msgstr "" msgid "Whittingham" msgstr "" msgid "Cathcart" msgstr "" msgid "Elk's Head" msgstr "" msgid "Roscomon" msgstr "" msgid "Sherman" msgstr "" msgid "Yorksford" msgstr "" msgid "Blackburn" msgstr "" msgid "Blacksford" msgstr "" msgid "Burton" msgstr "" msgid "Pig's Eye" msgstr "" msgid "Viper's Nest" msgstr "" msgid "Fenton" msgstr "" msgid "Lankershire" msgstr "" msgid "Lombard" msgstr "" msgid "Timberhill" msgstr "" msgid "Troy" msgstr "" msgid "Forder Oaks" msgstr "" msgid "Meramec" msgstr "" msgid "Quick Silver" msgstr "" msgid "Westmoor" msgstr "" msgid "Willow" msgstr "" msgid "Corackston" msgstr "" msgid "Sheltemburg" msgstr "" msgid "Cannot recruit - you already have a Hero in this town." msgstr "" msgid "Cannot recruit - you have too many Heroes." msgstr "" msgid "Cannot afford a Hero" msgstr "" msgid "There is no room in the garrison for this army." msgstr "" msgid "Fortifications" msgstr "" msgid "Farm" msgstr "" msgid "Thatched Hut" msgstr "" msgid "Archery Range" msgstr "" msgid "Upg. Archery Range" msgstr "" msgid "Blacksmith" msgstr "" msgid "Upg. Blacksmith" msgstr "" msgid "Armory" msgstr "" msgid "Upg. Armory" msgstr "" msgid "Jousting Arena" msgstr "" msgid "Upg. Jousting Arena" msgstr "" msgid "Cathedral" msgstr "" msgid "Upg. Cathedral" msgstr "" msgid "Coliseum" msgstr "" msgid "Garbage Heap" msgstr "" msgid "Hut" msgstr "" msgid "Stick Hut" msgstr "" msgid "Upg. Stick Hut" msgstr "" msgid "Den" msgstr "" msgid "Adobe" msgstr "" msgid "Upg. Adobe" msgstr "" msgid "Bridge" msgstr "" msgid "Upg. Bridge" msgstr "" msgid "Pyramid" msgstr "" msgid "Rainbow" msgstr "" msgid "Crystal Garden" msgstr "" msgid "Treehouse" msgstr "" msgid "Cottage" msgstr "" msgid "Upg. Cottage" msgstr "" msgid "Stonehenge" msgstr "" msgid "Upg. Stonehenge" msgstr "" msgid "Fenced Meadow" msgstr "" msgid "sorceress|Red Tower" msgstr "" msgid "Dungeon" msgstr "" msgid "Waterfall" msgstr "" msgid "Cave" msgstr "" msgid "Crypt" msgstr "" msgid "Nest" msgstr "" msgid "Maze" msgstr "" msgid "Upg. Maze" msgstr "" msgid "Swamp" msgstr "" msgid "Green Tower" msgstr "" msgid "warlock|Red Tower" msgstr "" msgid "Black Tower" msgstr "" msgid "Library" msgstr "" msgid "Orchard" msgstr "" msgid "Habitat" msgstr "" msgid "Pen" msgstr "" msgid "Foundry" msgstr "" msgid "Upg. Foundry" msgstr "" msgid "Cliff Nest" msgstr "" msgid "Ivory Tower" msgstr "" msgid "Upg. Ivory Tower" msgstr "" msgid "Cloud Castle" msgstr "" msgid "Upg. Cloud Castle" msgstr "" msgid "Storm" msgstr "" msgid "Skull Pile" msgstr "" msgid "Excavation" msgstr "" msgid "Graveyard" msgstr "" msgid "Upg. Graveyard" msgstr "" msgid "Upg. Pyramid" msgstr "" msgid "Mansion" msgstr "" msgid "Upg. Mansion" msgstr "" msgid "Mausoleum" msgstr "" msgid "Upg. Mausoleum" msgstr "" msgid "Laboratory" msgstr "" msgid "Shrine" msgstr "" msgid "" "The Fortifications increase the toughness of the walls, increasing the " "number of turns it takes to knock them down." msgstr "" msgid "The Farm increases production of Peasants by %{count} per week." msgstr "" msgid "" "The Coliseum provides inspiring spectacles to defending troops, raising " "their morale by two during combat." msgstr "" msgid "The Garbage Heap increases production of Goblins by %{count} per week." msgstr "" msgid "The Rainbow increases the luck of the defending units by two." msgstr "" msgid "" "The Crystal Garden increases production of Sprites by %{count} per week." msgstr "" msgid "The Dungeon increases the income of the town by %{count} gold per day." msgstr "" msgid "The Waterfall increases production of Centaurs by %{count} per week." msgstr "" msgid "" "The Library increases the number of spells in the Guild by one for each " "level of the guild." msgstr "" msgid "The Orchard increases production of Halflings by %{count} per week." msgstr "" msgid "The Storm adds +2 to the power of spells of a defending spell caster." msgstr "" msgid "The Skull Pile increases production of Skeletons by %{count} per week." msgstr "" msgid "Thieves' Guild" msgstr "" msgid "Tavern" msgstr "" msgid "Shipyard" msgstr "" msgid "Well" msgstr "" msgid "Statue" msgstr "" msgid "Marketplace" msgstr "" msgid "Moat" msgstr "" msgid "Castle" msgstr "" msgid "Tent" msgstr "" msgid "Captain's Quarters" msgstr "" msgid "Mage Guild, Level 1" msgstr "" msgid "Mage Guild, Level 2" msgstr "" msgid "Mage Guild, Level 3" msgstr "" msgid "Mage Guild, Level 4" msgstr "" msgid "Mage Guild, Level 5" msgstr "" msgid "" "The Shrine increases the necromancy skill of all your necromancers by 10 " "percent." msgstr "" msgid "" "The Thieves' Guild provides information on enemy players. Thieves' Guilds " "can also provide scouting information on enemy towns. Additional Guilds " "provide more information." msgstr "" msgid "The Tavern increases morale for troops defending the castle." msgstr "" msgid "The Shipyard allows ships to be built." msgstr "" msgid "" "The Well increases the growth rate of all dwellings by %{count} creatures " "per week." msgstr "" msgid "The Statue increases the town's income by %{count} gold per day." msgstr "" msgid "The Left Turret provides extra firepower during castle combat." msgstr "" msgid "The Right Turret provides extra firepower during castle combat." msgstr "" msgid "" "The Marketplace can be used to convert one type of resource into another. " "The more marketplaces you control, the better the exchange rate." msgstr "" msgid "" "The Moat slows attacking units. Any unit entering the moat must end its turn " "there and becomes more vulnerable to attack." msgstr "" msgid "" "The Castle improves the town's defense and increases its income to %{count} " "gold per day." msgstr "" msgid "" "The Tent provides workers to build a castle, provided the materials and the " "gold are available." msgstr "" msgid "" "The Captain's Quarters provides a captain to assist in the castle's defense " "when no hero is present." msgstr "" msgid "" "The Mage Guild allows heroes to learn spells and replenish their spell " "points." msgstr "" msgid "Recruit %{name}" msgstr "" msgid "Month: %{month}, Week: %{week}, Day: %{day}" msgstr "" msgid "" "You must purchase a spell book to use the mage guild, but you currently have " "no room for a spell book. Try giving one of your artifacts to another hero." msgstr "" msgid "Click to show next town." msgstr "" msgid "Show next town" msgstr "" msgid "Click to show previous town." msgstr "" msgid "Show previous town" msgstr "" msgid "This town may not be upgraded to a castle." msgstr "" msgid "Town" msgstr "" msgid "Exit Castle" msgstr "" msgid "Exit Town" msgstr "" msgid "Show Income" msgstr "" msgid "View Hero" msgstr "" msgid "The above spells are available here." msgstr "" msgid "The spells the hero can learn have been added to their book." msgstr "" msgid "A generous tip for the barkeep yields the following rumor:" msgstr "" msgid "Recruit Hero" msgstr "" msgid "%{name} is a level %{value} %{race} " msgstr "" msgid "with %{count} artifacts." msgstr "" msgid "with 1 artifact." msgstr "" msgid "without artifacts." msgstr "" msgid "Recruit %{name} the %{race}" msgstr "" msgid "" "'Spread' combat formation spreads the castle's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" msgid "" "'Grouped' combat formation bunches the castle's units together in the center " "of the castle's side of the battlefield." msgstr "" msgid "Spread Formation" msgstr "" msgid "Grouped Formation" msgstr "" msgid "Set garrison combat formation to 'Spread'" msgstr "" msgid "Set garrison combat formation to 'Grouped'" msgstr "" msgid "Exit Castle Options" msgstr "" msgid "Castle Options" msgstr "" msgid "Not enough resources to recruit creatures." msgstr "" msgid "You are unable to recruit at this time, your ranks are full." msgstr "" msgid "No creatures available for purchase." msgstr "" msgid "Recruit Creatures" msgstr "" msgid "Max" msgstr "" msgid "Hire all creatures in the town." msgstr "" msgid "Available" msgstr "" msgid "Town Population Information and Statistics" msgstr "" msgid "Damage" msgstr "" msgid "HP" msgstr "" msgid "Growth" msgstr "" msgid "week" msgstr "" msgid "View World" msgstr "" msgid "View the entire world." msgstr "" msgid "Puzzle" msgstr "" msgid "View the obelisk puzzle." msgstr "" msgid "Scenario Information" msgstr "" msgid "View information on the scenario you are currently playing." msgstr "" msgid "Dig for the Ultimate Artifact." msgstr "" msgid "Digging" msgstr "" msgid "Arena" msgstr "" msgid "" "You enter the arena and face a pack of vicious lions. You handily defeat " "them, to the wild cheers of the crowd. Impressed by your skill, the aged " "trainer of gladiators agrees to train you in a skill of your choice." msgstr "" msgid "Attack Skill" msgstr "" msgid "Defense Skill" msgstr "" msgid "Shots" msgstr "" msgid "Shots Left" msgstr "" msgid "Hit Points" msgstr "" msgid "Hit Points Left" msgstr "" msgid "You can't afford to upgrade your troops!" msgstr "" msgid "" "Your troops can be upgraded, but it will cost you dearly. Do you wish to " "upgrade them?" msgstr "" msgid "Are you sure you want to dismiss this army?" msgstr "" msgid "Upgrade" msgstr "" msgid "Upgrade your troops." msgstr "" msgid "Dismiss" msgstr "" msgid "Dismiss this army." msgstr "" msgid "" "A group of %{monster} with a desire for greater glory wish to join you.\n" "Do you accept?" msgstr "" msgid "Followers" msgstr "" msgid "" "The %{monster} is swayed by your diplomatic tongue, and offers to join your " "army for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" msgid "" "The creatures are swayed by your diplomatic\n" "tongue, and make you an offer:\n" "\n" msgstr "" msgid "" "%{offer} of the %{total} %{monster} will join your army, and the rest will " "leave you alone, for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" msgid "" "All %{offer} of the %{monster} will join your army for the sum of %{gold} " "gold.\n" "Do you accept?" msgstr "" msgid "(Rate: %{percent})" msgstr "" msgid "off" msgstr "" msgid "Music" msgstr "" msgid "Effects" msgstr "" msgid "MIDI" msgstr "" msgid "MIDI Expansion" msgstr "" msgid "External" msgstr "" msgid "Music Type" msgstr "" msgid "3D Audio" msgstr "" msgid "Toggle ambient music level." msgstr "" msgid "Toggle foreground sounds level." msgstr "" msgid "Change the type of music." msgstr "" msgid "Toggle 3D effects of foreground sounds." msgstr "" msgid "Build a new ship:" msgstr "" msgid "Resource cost:" msgstr "" msgid "Total: " msgstr "" msgid "Need: " msgstr "" msgid "Load Game" msgstr "" msgid "No save files to load." msgstr "" msgid "New Game" msgstr "" msgid "Start a single or multi-player game." msgstr "" msgid "Load a previously saved game." msgstr "" msgid "Save Game" msgstr "" msgid "Save the current game." msgstr "" msgid "Quit" msgstr "" msgid "Quit out of Heroes of Might and Magic II." msgstr "" msgid "Language" msgstr "" msgid "Graphics" msgstr "" msgid "Black & White" msgstr "" msgid "Mouse Cursor" msgstr "" msgid "Color" msgstr "" msgid "Text Support" msgstr "" msgid "Change the language of the game." msgstr "" msgid "Select Game Language" msgstr "" msgid "Change the graphics settings of the game." msgstr "" msgid "Toggle colored cursor on or off. This is only an aesthetic choice." msgstr "" msgid "" "Toggle text support mode to output extra information about windows and " "events in the game." msgstr "" msgid "" "Map\n" "Difficulty" msgstr "" msgid "" "Game\n" "Difficulty" msgstr "" msgid "Rating" msgstr "" msgid "Map Size" msgstr "" msgid "Opponents" msgstr "" msgid "Class" msgstr "" msgid "" "Victory\n" "Conditions" msgstr "" msgid "" "Loss\n" "Conditions" msgstr "" msgid "First select recipients!" msgstr "" msgid "You cannot select %{resource}!" msgstr "" msgid "Select count %{resource}:" msgstr "" msgid "Select Recipients" msgstr "" msgid "Your Funds" msgstr "" msgid "Planned Gift" msgstr "" msgid "Gift from %{name}" msgstr "" msgid "Resolution" msgstr "" msgid "Fullscreen" msgstr "" msgid "window|Mode" msgstr "" msgid "Windowed" msgstr "" msgid "V-Sync" msgstr "" #, fuzzy msgid "on" msgstr "Nimic" msgid "FPS" msgstr "" #, fuzzy msgid "System Info" msgstr "System Options" msgid "Change the resolution of the game." msgstr "" msgid "Select Game Resolution" msgstr "" msgid "Toggle between fullscreen and windowed modes." msgstr "" msgid "" "The V-Sync option can be enabled to resolve flickering issues on some " "monitors." msgstr "" msgid "Show extra information such as FPS and current time." msgstr "" msgid "Hotkey: " msgstr "" msgid "Category: " msgstr "" msgid "Event: " msgstr "" msgid "Hot Keys:" msgstr "" msgid "Evil" msgstr "" msgid "Good" msgstr "" msgid "Interface Type" msgstr "" msgid "Hide" msgstr "" msgid "Show" msgstr "" msgid "Interface" msgstr "" msgid "Slow" msgstr "" msgid "Normal" msgstr "" msgid "Fast" msgstr "" msgid "Very Fast" msgstr "" msgid "Scroll Speed" msgstr "" msgid "Toggle the type of interface you want to use." msgstr "" msgid "Toggle interface visibility." msgstr "" msgid "Toggle colored cursor on or off. This is only an esthetic choice." msgstr "" msgid "Sets the speed at which you scroll the window." msgstr "" msgid "Select Game Language:" msgstr "" msgid "Click to choose the selected language." msgstr "" msgid "%{name} has gained a level." msgstr "" msgid "%{skill} +1" msgstr "" msgid "You have learned %{skill}." msgstr "" msgid "" "%{name} has gained a level.\n" "\n" "%{skill} +1" msgstr "" msgid "" "You may learn either:\n" "%{skill1}\n" "or\n" "%{skill2}" msgstr "" msgid "" "Please inspect our fine wares. If you feel like offering a trade, click on " "the items you wish to trade with and for." msgstr "" msgid "" "You have received quite a bargain. I expect to make no profit on the deal. " "Can I interest you in any of my other wares?" msgstr "" msgid "I can offer you %{count} for 1 unit of %{resfrom}." msgstr "" msgid "I can offer you 1 unit of %{resto} for %{count} units of %{resfrom}." msgstr "" msgid "Min" msgstr "" msgid "Qty to trade" msgstr "" msgid "Trading Post" msgstr "" msgid "Your Resources" msgstr "" msgid "Available Trades" msgstr "" msgid "n/a" msgstr "" msgid "guarded by %{count} %{monster}" msgstr "" msgid "guarded by " msgstr "" msgid "(available: %{count})" msgstr "" msgid "(empty)" msgstr "" msgid "already learned" msgstr "" #, fuzzy msgid "treeOfKnowledge|free" msgstr "CunoÈ™tinÈ›e" msgid "already claimed" msgstr "" msgid "not claimed" msgstr "" msgid "already knows this skill" msgstr "" msgid "already has max skills" msgstr "" msgid "(already visited)" msgstr "" msgid "(not visited)" msgstr "" msgid "%{color} Barrier" msgstr "" msgid "(tent visited)" msgstr "" msgid "%{color} Tent" msgstr "" msgid "Road" msgstr "" msgid "(digging ok)" msgstr "" msgid "(no digging)" msgstr "" msgid "penalty: %{cost}" msgstr "" msgid "Defenders:" msgstr "" msgid "Unknown" msgstr "" msgid "%{name} (Level %{level})" msgstr "" msgid "Attack:" msgstr "" msgid "Defense:" msgstr "" msgid "Spell Power:" msgstr "" msgid "Knowledge:" msgstr "" msgid "Spell Points:" msgstr "" msgid "Move Points:" msgstr "" msgid "Uncharted Territory" msgstr "" msgid "Cost per troop:" msgstr "" msgid "Available: %{count}" msgstr "" msgid "Number to buy:" msgstr "" msgid "Recruit selected monsters." msgstr "" msgid "Select maximum monsters to be recruited." msgstr "" msgid "Select only 1 monster to be recruited." msgstr "" msgid "Select Game Resolution:" msgstr "" msgid "Click to apply the selected resolution." msgstr "" msgid "Click to apply the entered text." msgstr "" msgid "Click to open the Virtual Keyboard dialog." msgstr "" msgid "Open Virtual Keyboard" msgstr "" msgid "How many troops to move?" msgstr "" msgid "Fast separation into slots:" msgstr "" msgid "Map: " msgstr "" msgid "" "\n" "\n" "Month: " msgstr "" msgid ", Week: " msgstr "" msgid ", Day: " msgstr "" msgid "" "\n" "\n" "Location: " msgstr "" msgid "Click to save the current game." msgstr "" msgid "Click to load a previously saved game." msgstr "" msgid "Are you sure you want to delete file:" msgstr "" msgid "Warning!" msgstr "" msgid "File to Save:" msgstr "" msgid "File to Load:" msgstr "" msgid "Accept the choice made." msgstr "" msgid "%{color} %{race} hero" msgstr "" msgid "Terrain object" msgstr "" msgid "Select Skill:" msgstr "" msgid "Select Spell:" msgstr "" msgid "Select Artifact:" msgstr "" msgid "Select Monster:" msgstr "" msgid "Select Hero:" msgstr "" #, fuzzy msgid "Select Treasure:" msgstr "SelectaÈ›i È›inta vrăjii" #, fuzzy msgid "Select Ocean Object:" msgstr "SelectaÈ›i È›inta vrăjii" msgid "Castle/town placing" msgstr "" msgid "doubleLinedRace|Neutral" msgstr "" msgid "race|Random" msgstr "" msgid "Click to start placing the selected castle/town." msgstr "" msgid "Click to select town placing." msgstr "" msgid "Click to select castle placing." msgstr "" msgid "%{color} %{race} %{townOrCastle}" msgstr "" msgid "race|Neutral" msgstr "" msgid "You will place" msgstr "" msgid "Click to select this class." msgstr "" msgid "Click to select this color." msgstr "" #, fuzzy msgid "Select Dwelling:" msgstr "SelectaÈ›i È›inta vrăjii" #, fuzzy msgid "Select Landscape Object:" msgstr "SelectaÈ›i È›inta vrăjii" msgid "Mine placing" msgstr "" msgid "" "Resource\n" "type:" msgstr "" #, fuzzy msgid "%{mineName} appearance:" msgstr "%{name} cel %{race}" msgid "Click to select %{object} as the resource generator to be placed." msgstr "" #, fuzzy msgid "Select Mountain Object:" msgstr "SelectaÈ›i È›inta vrăjii" #, fuzzy msgid "Select Rock Object:" msgstr "SelectaÈ›i È›inta vrăjii" #, fuzzy msgid "Select Tree Object:" msgstr "SelectaÈ›i È›inta vrăjii" #, fuzzy msgid "Select Power Up Object:" msgstr "SelectaÈ›i È›inta vrăjii" #, fuzzy msgid "Select Adventure Object:" msgstr "SelectaÈ›i È›inta vrăjii" msgid "Map Type:\n" msgstr "" msgid "The Succession Wars" msgstr "" msgid "The Price of Loyalty" msgstr "" msgid "Resurrection" msgstr "" msgid "Lose all your heroes and towns." msgstr "" msgid "Lose a specific town." msgstr "" msgid "Lose a specific hero." msgstr "" msgid "Run out of time. Fail to win by a certain point." msgstr "" msgid "Loss Condition" msgstr "" msgid "Defeat all enemy heroes and towns." msgstr "" msgid "Capture a specific town." msgstr "" msgid "Defeat a specific hero." msgstr "" msgid "Find a specific artifact." msgstr "" msgid "Your side defeats the opposing side." msgstr "" msgid "Accumulate a large amount of gold." msgstr "" msgid "Victory Condition" msgstr "" msgid "Map difficulty:" msgstr "" msgid "N" msgstr "" msgid "No maps exist at that size" msgstr "" msgid "Small Maps" msgstr "" msgid "View only maps of size small (36 x 36)." msgstr "" msgid "Medium Maps" msgstr "" msgid "View only maps of size medium (72 x 72)." msgstr "" msgid "Large Maps" msgstr "" msgid "View only maps of size large (108 x 108)." msgstr "" msgid "Extra Large Maps" msgstr "" msgid "View only maps of size extra large (144 x 144)." msgstr "" msgid "All Maps" msgstr "" msgid "View all maps, regardless of size." msgstr "" msgid "Players Icon" msgstr "" msgid "" "Indicates how many players total are in the scenario. Any positions not " "occupied by humans will be occupied by computer players." msgstr "" msgid "" "Indicates whether the map\n" "is small (36 x 36), medium\n" "(72 x 72), large (108 x 108),\n" "or extra large (144 x 144)." msgstr "" msgid "Size Icon" msgstr "" msgid "" "Indicates whether the map is made for \"The Succession Wars\" or \"The Price " "of Loyalty\" version of the game." msgstr "" msgid "Map Type" msgstr "" msgid "Selected Name" msgstr "" msgid "The name of the currently selected map." msgstr "" msgid "Selected Map Difficulty" msgstr "" msgid "" "The map difficulty of the currently selected map. The map difficulty is " "determined by the scenario designer. More difficult maps might include more " "or stronger enemies, fewer resources, or other special conditions making " "things tougher for the human player." msgstr "" msgid "Selected Description" msgstr "" msgid "The description of the currently selected map." msgstr "" msgid "Jump" msgstr "" msgid "Hero Speed" msgstr "" msgid "Don't Show" msgstr "" msgid "Enemy Speed" msgstr "" msgid "Auto Resolve" msgstr "" msgid "Auto, No Spells" msgstr "" msgid "Battles" msgstr "" msgid "autoBattle|Manual" msgstr "" msgid "Change the speed at which your heroes move on the main screen." msgstr "" msgid "" "Sets the speed that A.I. heroes move at. You can also elect not to view A.I. " "movement at all." msgstr "" msgid "Change the interface settings of the game." msgstr "" msgid "Interface Settings" msgstr "" msgid "Toggle instant battle mode." msgstr "" msgid "Att." msgstr "" msgid "Def." msgstr "" msgid "Power" msgstr "" msgid "Knowl" msgstr "" msgid "1st" msgstr "" msgid "2nd" msgstr "" msgid "3rd" msgstr "" msgid "4th" msgstr "" msgid "5th" msgstr "" msgid "6th" msgstr "" msgid "Oracle: Player Rankings" msgstr "" msgid "Thieves' Guild: Player Rankings" msgstr "" msgid "Number of Towns:" msgstr "" msgid "Number of Castles:" msgstr "" msgid "Number of Heroes:" msgstr "" msgid "Gold in Treasury:" msgstr "" msgid "Wood & Ore:" msgstr "" msgid "Gems, Cr, Slf & Mer:" msgstr "" msgid "Obelisks Found:" msgstr "" msgid "Artifacts:" msgstr "" msgid "Total Army Strength:" msgstr "" msgid "Income:" msgstr "" msgid "Best Hero:" msgstr "" msgid "Best Hero Stats:" msgstr "" msgid "Personality:" msgstr "" msgid "Best Monster:" msgstr "" msgid "Map filename" msgstr "" msgid "" "Are you sure you want to load a new map? (Any unsaved changes to the current " "map will be lost.)" msgstr "" msgid "" "Are you sure you want to create a new map? (Any unsaved changes to the " "current map will be lost.)" msgstr "" msgid "" "Create a new map, either from scratch or using the random map generator." msgstr "" msgid "New Map" msgstr "" msgid "Load Map" msgstr "" msgid "Load an existing map." msgstr "" msgid "Save Map" msgstr "" msgid "Save the current map." msgstr "" msgid "Quit out of the map editor." msgstr "" msgid "Objects cannot be placed outside the map." msgstr "" msgid "Monsters cannot be placed on water." msgstr "" msgid "Choose a tile which does not contain any objects." msgstr "" msgid "Treasures cannot be placed on water." msgstr "" msgid "Heroes cannot be placed on water." msgstr "" msgid "" "A maximum of %{count} heroes of the same color can be placed on the map." msgstr "" msgid "Artifacts cannot be placed on water." msgstr "" msgid "Mountains cannot be placed on water." msgstr "" msgid "Rocks cannot be placed on water." msgstr "" msgid "Trees cannot be placed on water." msgstr "" msgid "Ocean object must be placed on water." msgstr "" msgid "Landscape objects cannot be placed on water." msgstr "" msgid "Towns cannot be placed on water." msgstr "" msgid "Mines cannot be placed on water." msgstr "" msgid "Dwellings cannot be placed on water." msgstr "" msgid "Power-ups cannot be placed on water." msgstr "" msgid "Adventure objects cannot be placed on water." msgstr "" msgid "Used to place %{object}." msgstr "" #, fuzzy msgid "Select object type" msgstr "SelectaÈ›i È›inta vrăjii" msgid "" "Click here to\n" "select a monster." msgstr "" #, fuzzy msgid "" "Click here to\n" "select another monster." msgstr "Te rog selectează un alt erou." msgid "" "Cell\n" "Details" msgstr "" msgid "Roads" msgstr "" msgid "Streams" msgstr "" msgid "Erase" msgstr "" msgid "Mountains" msgstr "" msgid "Rocks" msgstr "" msgid "Trees" msgstr "" msgid "Water Objects" msgstr "" msgid "Miscellaneous" msgstr "" msgid "Artifacts" msgstr "" msgid "Dwellings" msgstr "" msgid "Mines" msgstr "" msgid "Power-ups" msgstr "" msgid "Treasures" msgstr "" msgid "Towns" msgstr "" msgid "Heroes" msgstr "" msgid "editorErasure|Terrain" msgstr "" msgid "editorErasure|Castles" msgstr "" msgid "editorErasure|Monsters" msgstr "" msgid "editorErasure|Heroes" msgstr "" msgid "editorErasure|Artifacts" msgstr "" msgid "editorErasure|Roads" msgstr "" msgid "editorErasure|Streams" msgstr "" msgid "editorErasure|Treasures" msgstr "" msgid "" "Draws terrain in\n" "%{size} by %{size} square increments." msgstr "" msgid "" "Erases objects in\n" "%{size} by %{size} square increments." msgstr "" msgid "Small Brush" msgstr "" msgid "Medium Brush" msgstr "" msgid "Large Brush" msgstr "" msgid "Area Fill" msgstr "" msgid "Used to click and drag for filling in large areas." msgstr "" msgid "Clear Area" msgstr "" msgid "Used to click and drag for clearing large areas." msgstr "" msgid "" "Costs %{rate} times normal movement for all heroes. (Pathfinding reduces or " "eliminates the penalty.)" msgstr "" msgid "Traversable only by boat." msgstr "" msgid "No special modifiers." msgstr "" msgid "Toggle the erasure of %{type} objects." msgstr "" msgid "" "Objects of this type will be deleted with the Erase tool. Left-click here to " "deselect this type. Press and hold this button to deselect all other object " "types." msgstr "" msgid "" "Objects of this type will NOT be deleted with the Erase tool. Left-click " "here to select this type. Press and hold this button to select all other " "object types." msgstr "" msgid "Terrain Mode" msgstr "" msgid "Used to draw the underlying grass, dirt, water, etc. on the map." msgstr "" msgid "Landscape Objects Mode" msgstr "" msgid "" "Used to place landscape objects (mountains, rocks, trees, etc.) on the map." msgstr "" msgid "Detail Mode" msgstr "" msgid "Used for special editing of monsters, heroes and towns." msgstr "" msgid "Adventure Objects Mode" msgstr "" msgid "" "Used to place adventure objects (artifacts, dwellings, mines, treasures, " "etc.) on the map." msgstr "" msgid "Kingdom Objects Mode" msgstr "" msgid "Used to place kingdom objects (towns, castles and heroes) on the map." msgstr "" #, fuzzy msgid "Monsters Mode" msgstr "MonÈ™tri" msgid "Used to place monsters on the map." msgstr "" msgid "Allows you to draw streams by clicking and dragging." msgstr "" msgid "Stream Mode" msgstr "" msgid "Allows you to draw roads by clicking and dragging." msgstr "" msgid "Road Mode" msgstr "" msgid "Erase Mode" msgstr "" msgid "Used to erase objects from the map." msgstr "" msgid "Change between zoom and normal view." msgstr "" msgid "Magnify" msgstr "" msgid "Undo" msgstr "" msgid "Undo your last action." msgstr "" msgid "Create a new map either from scratch or using the random map generator." msgstr "" msgid "Edit map title, description, and other general information." msgstr "" msgid "Specifications" msgstr "" msgid "File Options" msgstr "" msgid "" "Open the file options menu, where you can save or load maps, or quit out of " "the editor." msgstr "" msgid "View the editor system options, which let you customize the editor." msgstr "" msgid "Create a map that is %{size} squares wide and %{size} squares high." msgstr "" msgid "Cancel back to the New Map menu." msgstr "" msgid "Cancel back to the main menu." msgstr "" msgid "From Scratch" msgstr "" msgid "Start from scratch with a blank map." msgstr "" msgid "Create a randomly generated map." msgstr "" msgid "Random" msgstr "" msgid "Cancel back to the Map Editor main menu." msgstr "" msgid "No maps available!" msgstr "" msgid "Warning" msgstr "AtenÈ›ie" msgid "difficulty|Easy" msgstr "" msgid "difficulty|Normal" msgstr "" msgid "difficulty|Hard" msgstr "" msgid "difficulty|Expert" msgstr "" msgid "difficulty|Impossible" msgstr "" msgid "and more..." msgstr "" msgid "Easy" msgstr "" msgid "Hard" msgstr "" msgid "Campaign Difficulty" msgstr "" msgid "" "Choose this difficulty if you want to prefer game story over challenge. AI " "is weaker in comparison with normal difficulty." msgstr "" msgid "" "Choose this difficulty to enjoy the campaign as per the original design." msgstr "" msgid "" "Choose this difficulty if you want challenge. AI is stronger in comparison " "with normal difficulty." msgstr "" msgid "" "Congratulations!\n" "\n" "Days: %{days}\n" msgstr "" msgid "" "\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" msgid "" "Score: %{score}\n" "\n" "Rating:\n" "%{rating}" msgstr "" msgid "Start the selected scenario." msgstr "" msgid "View Intro" msgstr "" msgid "View Intro videos for the current state of the campaign." msgstr "" msgid "" "Select the campaign difficulty. This can be lowered at any point during the " "campaign." msgstr "" msgid "Restart the current scenario." msgstr "" msgid "Difficulty" msgstr "" msgid "" "You have changed to a lower difficulty for the campaign. You will not be " "able to revert this after this point. The high score will be calculated " "based solely on the new difficulty. Do you want to proceed?" msgstr "" msgid "Are you sure you want to restart this scenario?" msgstr "" msgid "Campaign Scenario loading failure" msgstr "" msgid "Please make sure that campaign files are correct and present." msgstr "" msgid "Days spent" msgstr "" msgid "The number of days spent on this campaign." msgstr "" msgid "Project Coordination and Core Development" msgstr "" msgid "Development" msgstr "" msgid "Visit us at " msgstr "" msgid "QA and Support" msgstr "" msgid "Dev and Support" msgstr "" msgid "Special Thanks to" msgstr "" msgid "and many other contributors!" msgstr "" msgid "and many-many other supporters!" msgstr "" msgid "Support us at" msgstr "" msgid "local-donation-platform|https://www.patreon.com/fheroes2" msgstr "" msgid "Connect with us at" msgstr "" msgid "local-social-network|https://www.facebook.com/groups/fheroes2" msgstr "" msgid "Need help with the game?" msgstr "" msgid "Original project before 0.7" msgstr "" msgid "Heroes of Might and Magic II: The Succession Wars team" msgstr "" msgid "Designed and Directed" msgstr "" msgid "Programming and Design" msgstr "" msgid "Executive Producer" msgstr "" msgid "Producer" msgstr "" msgid "Additional Design" msgstr "" msgid "Additional Programming" msgstr "" msgid "Musical Production" msgstr "" msgid "Music and Sound Design" msgstr "" msgid "Vocalists" msgstr "" msgid "Art Director" msgstr "" msgid "Assistant Art Director" msgstr "" msgid "Artists" msgstr "" msgid "QA Manager" msgstr "" msgid "QA" msgstr "" msgid "Writing" msgstr "" msgid "Manual and Helpfile" msgstr "" msgid "Scenarios" msgstr "" msgid "Heroes of Might and Magic II: The Price of Loyalty team" msgstr "" msgid "Design Lead" msgstr "" msgid "Designers" msgstr "" msgid "Programming Lead" msgstr "" msgid "Art Lead" msgstr "" msgid "Playtesters" msgstr "" msgid "Designer" msgstr "" msgid "Producers" msgstr "" msgid "QA Managers" msgstr "" msgid "Sound Design" msgstr "" msgid "Town Themes" msgstr "" msgid "Alto Sax" msgstr "" msgid "Harpsichord and Piano" msgstr "" msgid "Basso Vocal" msgstr "" msgid "Soprano Vocal" msgstr "" msgid "Recorded at %{recordingStudio}" msgstr "" msgid "credits|Manual" msgstr "" msgid "German Consultant" msgstr "" msgid "Map Designers" msgstr "" msgid "Package Design" msgstr "" msgid "Your Name" msgstr "" msgid "Unknown Hero" msgstr "" msgid "Standard" msgstr "" msgid "View High Scores for Standard Maps." msgstr "" msgid "Campaign" msgstr "" msgid "View High Scores for Campaigns." msgstr "" msgid "hotkey|default okay event" msgstr "" msgid "hotkey|default cancel event" msgstr "" msgid "hotkey|default left" msgstr "" msgid "hotkey|default right" msgstr "" msgid "hotkey|default up" msgstr "" msgid "hotkey|default down" msgstr "" msgid "hotkey|toggle fullscreen" msgstr "" msgid "hotkey|toggle text support mode" msgstr "" msgid "hotkey|new game" msgstr "" msgid "hotkey|load game" msgstr "" msgid "hotkey|highscores" msgstr "" msgid "hotkey|credits" msgstr "" msgid "hotkey|standard game" msgstr "" msgid "hotkey|campaign game" msgstr "" msgid "hotkey|multi-player game" msgstr "" msgid "hotkey|settings" msgstr "" msgid "hotkey|quit" msgstr "" msgid "hotkey|select map" msgstr "" msgid "hotkey|select small map size" msgstr "" msgid "hotkey|select medium map size" msgstr "" msgid "hotkey|select large map size" msgstr "" msgid "hotkey|select extra large map size" msgstr "" msgid "hotkey|select all map sizes" msgstr "" msgid "hotkey|hotseat game" msgstr "" msgid "hotkey|battle only game" msgstr "" msgid "hotkey|choose the original campaign" msgstr "" msgid "hotkey|choose the expansion campaign" msgstr "" msgid "hotkey|map editor main menu" msgstr "" msgid "hotkey|new map menu" msgstr "" msgid "hotkey|load map menu" msgstr "" msgid "hotkey|new map from scratch" msgstr "" msgid "hotkey|new random map" msgstr "" #, fuzzy msgid "hotkey|undo last action" msgstr "Personalizează opÈ›iunile sistemului" #, fuzzy msgid "hotkey|redo last action" msgstr "Personalizează opÈ›iunile sistemului" msgid "hotkey|roland campaign" msgstr "" msgid "hotkey|archibald campaign" msgstr "" msgid "hotkey|the price of loyalty campaign" msgstr "" msgid "hotkey|voyage home campaign" msgstr "" msgid "hotkey|wizard's isle campaign" msgstr "" msgid "hotkey|descendants campaign" msgstr "" msgid "hotkey|select first campaign bonus" msgstr "" msgid "hotkey|select second campaign bonus" msgstr "" msgid "hotkey|select third campaign bonus" msgstr "" msgid "hotkey|view campaign intro" msgstr "" msgid "hotkey|select campaign difficulty" msgstr "" msgid "hotkey|restart campaign scenario" msgstr "" msgid "hotkey|world map left" msgstr "" msgid "hotkey|world map right" msgstr "" msgid "hotkey|world map up" msgstr "" msgid "hotkey|world map down" msgstr "" msgid "hotkey|world map up left" msgstr "" msgid "hotkey|world map up right" msgstr "" msgid "hotkey|world map down left" msgstr "" msgid "hotkey|world map down right" msgstr "" msgid "hotkey|save game" msgstr "" msgid "hotkey|next hero" msgstr "" msgid "hotkey|start hero movement" msgstr "" msgid "hotkey|cast adventure spell" msgstr "" msgid "hotkey|put hero to sleep" msgstr "" msgid "hotkey|next town" msgstr "" msgid "hotkey|end turn" msgstr "" msgid "hotkey|file options" msgstr "" msgid "hotkey|adventure options" msgstr "" msgid "hotkey|puzzle map" msgstr "" msgid "hotkey|scenario information" msgstr "" msgid "hotkey|dig for artifact" msgstr "" msgid "hotkey|view world" msgstr "" msgid "hotkey|kingdom summary" msgstr "" msgid "hotkey|default action" msgstr "" msgid "hotkey|open focus" msgstr "" #, fuzzy msgid "hotkey|system options" msgstr "Personalizează opÈ›iunile sistemului" msgid "hotkey|scroll left" msgstr "" msgid "hotkey|scroll right" msgstr "" msgid "hotkey|scroll up" msgstr "" msgid "hotkey|scroll down" msgstr "" msgid "hotkey|toggle control panel" msgstr "" msgid "hotkey|toggle radar" msgstr "" msgid "hotkey|toggle buttons" msgstr "" msgid "hotkey|toggle status" msgstr "" msgid "hotkey|toggle icons" msgstr "" msgid "hotkey|transfer control to ai" msgstr "" msgid "hotkey|retreat from battle" msgstr "" msgid "hotkey|surrender during battle" msgstr "" msgid "hotkey|toggle battle auto mode" msgstr "" #, fuzzy msgid "hotkey|finish the battle in auto mode" msgstr "EÈ™ti sigur că vrei să termini bătălia în modul automat?" msgid "hotkey|battle options" msgstr "" msgid "hotkey|skip turn in battle" msgstr "" msgid "hotkey|cast battle spell" msgstr "" msgid "hotkey|dwelling level 1" msgstr "" msgid "hotkey|dwelling level 2" msgstr "" msgid "hotkey|dwelling level 3" msgstr "" msgid "hotkey|dwelling level 4" msgstr "" msgid "hotkey|dwelling level 5" msgstr "" msgid "hotkey|dwelling level 6" msgstr "" msgid "hotkey|well" msgstr "" msgid "hotkey|marketplace" msgstr "" msgid "hotkey|mage guild" msgstr "" msgid "hotkey|shipyard" msgstr "" msgid "hotkey|thieves guild" msgstr "" msgid "hotkey|tavern" msgstr "" msgid "hotkey|construction screen" msgstr "" msgid "hotkey|buy all monsters in well" msgstr "" msgid "hotkey|split stack by half" msgstr "" msgid "hotkey|split stack by one" msgstr "" msgid "hotkey|join stacks" msgstr "" msgid "hotkey|upgrade troop" msgstr "" msgid "hotkey|dismiss hero or troop" msgstr "" msgid "" "Do you want to regain control from AI? The effect will take place only on " "the next turn." msgstr "" "DoreÈ™ti să recâștigi controlul de la AI? Efectul va avea loc doar în " "următoarea rundă." msgid "" "Do you want to transfer control from you to the AI? The effect will take " "place only on the next turn." msgstr "" msgid "Default Actions" msgstr "" msgid "Global Actions" msgstr "" msgid "Main Menu" msgstr "" msgid "World Map" msgstr "" #, fuzzy msgid "Battle Screen" msgstr "Ecranul eroului" #, fuzzy msgid "Town Screen" msgstr "Ecranul eroului" msgid "Army Actions" msgstr "" msgid "Editor" msgstr "" #, fuzzy msgid "The save file is corrupted." msgstr "Se creează o imagine în oglindă." msgid "Unsupported save format: " msgstr "" msgid "Current game version: " msgstr "" msgid "Last supported version: " msgstr "" msgid "This file contains a save with an invalid game type." msgstr "" msgid "" "This save file requires \"The Price of Loyalty\" game assets, but they have " "not been provided to the engine." msgstr "" msgid "This saved game is localized to '" msgstr "" msgid "' language, but the current language of the game is '" msgstr "" msgid "Hot Seat" msgstr "" msgid "" "Play a Hot Seat game, where 2 to 4 players play around the same computer, " "switching into the 'Hot Seat' when it is their turn." msgstr "" msgid "A single player game playing out a single map." msgstr "" msgid "Standard Game" msgstr "" msgid "A single player game playing through a series of maps." msgstr "" msgid "Campaign Game" msgstr "" msgid "Multi-Player Game" msgstr "" msgid "" "A multi-player game, with several human players completing against each " "other on a single map." msgstr "" msgid "fheroes2 Resurrection Team presents" msgstr "" msgid "Greetings!" msgstr "" msgid "Welcome to Heroes of Might and Magic II powered by fheroes2 engine!" msgstr "" msgid "" "Welcome to Heroes of Might and Magic II powered by the fheroes2 engine!\n" "Before starting the game please select a game resolution." msgstr "" msgid "Please Remember" msgstr "" msgid "" "You can always change the language, resolution and settings of the game by " "clicking on the " msgstr "" msgid "door" msgstr "" msgid " on the left side of the Main Menu, or with the " msgstr "" msgid " button from the " msgstr "" msgid "NEW GAME" msgstr "" msgid "" " menu. \n" "\n" "Enjoy the game!" msgstr "" msgid "The Editor requires \"The Price of Loyalty\" expansion files to work." msgstr "" msgid "Quit Heroes of Might and Magic II and return to the operating system." msgstr "" msgid "Credits" msgstr "" msgid "View the credits screen." msgstr "" msgid "High Scores" msgstr "" msgid "View the high scores screen." msgstr "" msgid "Change language, resolution and settings of the game." msgstr "" msgid "Game Settings" msgstr "" msgid "" "Required video files for campaign selection window are missing. Please make " "sure that all necessary files are present in the system." msgstr "" msgid "" "Either Roland's or Archibald's campaign from the original Heroes of Might " "and Magic II." msgstr "" msgid "Original Campaign" msgstr "" msgid "Expansion Campaign" msgstr "" msgid "One of the four new campaigns from the Price of Loyalty expansion set." msgstr "" msgid "Loading video. Please wait..." msgstr "" msgid "Host" msgstr "" msgid "" "The host sets up the game options. There can only be one host per network " "game." msgstr "" msgid "Guest" msgstr "" msgid "" "The guest waits for the host to set up the game, then is automatically added " "in. There can be multiple guests for TCP/IP games." msgstr "" msgid "Battle Only" msgstr "" msgid "Setup and play a battle without loading any map." msgstr "" msgid "2 Players" msgstr "" msgid "" "Play with 2 human players, and optionally, up to 4 additional computer " "players." msgstr "" msgid "3 Players" msgstr "" msgid "" "Play with 3 human players, and optionally, up to 3 additional computer " "players." msgstr "" msgid "4 Players" msgstr "" msgid "" "Play with 4 human players, and optionally, up to 2 additional computer " "players." msgstr "" msgid "5 Players" msgstr "" msgid "" "Play with 5 human players, and optionally, up to 1 additional computer " "player." msgstr "" msgid "6 Players" msgstr "" msgid "Play with 6 human players." msgstr "" msgid "Dragon city has fallen! You are now the Master of the Dragons." msgstr "" msgid "" "You captured %{name}!\n" "You are victorious." msgstr "" msgid "" "You have captured the enemy hero %{name}!\n" "Your quest is complete." msgstr "" msgid "" "You have found the %{name}.\n" "Your quest is complete." msgstr "" msgid "Ultimate Artifact" msgstr "" msgid "" "The enemy is beaten.\n" "Your side has triumphed!" msgstr "" msgid "" "You have built up over %{count} gold in your treasury.\n" "All enemies bow before your wealth and power." msgstr "" msgid "Victory!" msgstr "" msgid "" "The enemy has captured %{name}!\n" "They are triumphant." msgstr "" msgid "" "The enemy has built up over %{count} gold in his treasury.\n" "You must bow done in defeat before his wealth and power." msgstr "" msgid "You have been eliminated from the game!!!" msgstr "" msgid "" "You have lost the hero %{name}.\n" "Your quest is over." msgstr "" msgid "" "You have failed to complete your quest in time.\n" "All is lost." msgstr "" msgid "Defeat!" msgstr "" msgid "" "Base score: %{score}\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" msgid "Defeat all enemy heroes and capture all enemy towns and castles." msgstr "" msgid "Run out of time. (Fail to win by a certain point.)" msgstr "" msgid "You must defeat the enemy %{enemies}." msgid_plural "You must defeat the enemy alliance of %{enemies}." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "" "The alliance consisting of %{allies} and you must defeat the enemy " "%{enemies}." msgid_plural "" "The alliance consisting of %{allies} and you must defeat the enemy alliance " "of %{enemies}." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "Capture the castle '%{name}'." msgstr "" msgid "Capture the town '%{name}'." msgstr "" msgid "Defeat the hero '%{name}'." msgstr "" msgid "Find the ultimate artifact." msgstr "" msgid "Find the '%{name}' artifact." msgstr "" msgid "Accumulate %{count} gold." msgstr "" msgid "" ", or you may win by defeating all enemy heroes and capturing all enemy towns " "and castles." msgstr "" msgid "Lose the castle '%{name}'." msgstr "" msgid "Lose the town '%{name}'." msgstr "" msgid "Lose the hero: %{name}." msgstr "" msgid "Fail to win by the end of month %{month}, week %{week}, day %{day}." msgstr "" msgid "%{color} player has been vanquished!" msgstr "" msgid "Major Event!" msgstr "" msgid "Scenario:" msgstr "" msgid "Game Difficulty:" msgstr "" msgid "Opponents:" msgstr "" msgid "Class:" msgstr "" msgid "Rating %{rating}%" msgstr "" msgid "Click here to select which scenario to play." msgstr "" msgid "Scenario" msgstr "" msgid "Game Difficulty" msgstr "" msgid "" "This lets you change the starting difficulty at which you will play. Higher " "difficulty levels start you of with fewer resources, and at the higher " "settings, give extra resources to the computer." msgstr "" msgid "Difficulty Rating" msgstr "" msgid "" "The difficulty rating reflects a combination of various settings for your " "game. This number will be applied to your final score." msgstr "" msgid "Click to accept these settings and start a new game." msgstr "" msgid "Click to return to the main menu." msgstr "" msgid "Astrologers proclaim the Month of the %{name}." msgstr "" msgid "Astrologers proclaim the Week of the %{name}." msgstr "" msgid "After regular growth, the population of %{monster} is doubled!" msgstr "" msgid "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgid_plural "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "%{monster} growth +%{count}." msgstr "" msgid " All populations are halved." msgstr "" msgid " All dwellings increase population." msgstr "" msgid "New Month!" msgstr "" msgid "New Week!" msgstr "" msgid "Beware!" msgstr "" msgid "" "%{color} player, this is your last day to capture a town, or you will be " "banished from this land." msgstr "" msgid "" "%{color} player, you only have %{day} days left to capture a town, or you " "will be banished from this land." msgstr "" msgid "%{color} player's turn." msgstr "" msgid "" "%{color} player, you have lost your last town. If you do not conquer another " "town in the next week, you will be eliminated." msgstr "" msgid "" "%{color} player, your heroes abandon you, and you are banished from this " "land." msgstr "" msgid "Lord Kilburn" msgstr "" msgid "Tsabu" msgstr "" msgid "Sir Galant" msgstr "" msgid "Thundax" msgstr "" msgid "Lord Haart" msgstr "" msgid "Ariel" msgstr "" msgid "Rebecca" msgstr "" msgid "Sandro" msgstr "" msgid "Crodo" msgstr "" msgid "Barock" msgstr "" msgid "Antoine" msgstr "" msgid "Astra" msgstr "" msgid "Agar" msgstr "" msgid "Vatawna" msgstr "" msgid "Vesper" msgstr "" msgid "Ambrose" msgstr "" msgid "Troyan" msgstr "" msgid "Jojosh" msgstr "" msgid "Wrathmont" msgstr "" msgid "Maximus" msgstr "" msgid "Next Hero" msgstr "" msgid "Select the next Hero." msgstr "" #, fuzzy msgid "Hero Movement" msgstr "MiÈ™care a umbrelor" msgid "" "Start the Hero's movement along the current path or re-visit the object " "occupied by the Hero. Press and hold this button to reset the Hero's path." msgstr "" msgid "Kingdom Summary" msgstr "" msgid "View a Summary of your Kingdom." msgstr "" msgid "Cast an adventure spell." msgstr "" msgid "End Turn" msgstr "" msgid "End your turn and left the computer take its turn." msgstr "" msgid "Adventure Options" msgstr "" msgid "Bring up the adventure options menu." msgstr "" msgid "" "Bring up the file options menu, allowing you to load, save, start a new game " "or quit." msgstr "" msgid "Bring up the system options menu, allowing you to customize your game." msgstr "" msgid "" "One or more heroes may still move, are you sure you want to end your turn?" msgstr "" msgid "Are you sure you want to quit?" msgstr "" msgid "Are you sure you want to restart? (Your current game will be lost.)" msgstr "" msgid "Are you sure you want to overwrite the save with this name?" msgstr "" msgid "Game saved successfully." msgstr "" msgid "There was an issue during saving." msgstr "" msgid "" "Are you sure you want to load a new game? (Your current game will be lost.)" msgstr "" msgid "Try looking on land!!!" msgstr "" msgid "" "Searching for the Ultimate Artifact is fruitless. Your hero could not carry " "it even if he found it - all his artifact slots are full." msgstr "" msgid "Digging for artifacts requires a whole day, try again tomorrow." msgstr "" msgid "" "After spending many hours digging here, you have uncovered the %{artifact}." msgstr "" msgid "Congratulations!" msgstr "" msgid "Nothing here. Where could it be?" msgstr "" msgid "Try searching on clear ground." msgstr "" msgid "A miniature view of the known world. Left click to move viewing area." msgstr "" msgid "Month: %{month} Week: %{week}" msgstr "" msgid "Day: %{day}" msgstr "" msgid "" "You find a small\n" "quantity of %{resource}." msgstr "" msgid "Status Window" msgstr "" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date." msgstr "" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date. Left click here to cycle through these windows." msgstr "" msgid "" "This lets you change player starting positions and colors. A particular " "color will always start in a particular location. Some positions may only be " "played by a computer player or only by a human player." msgstr "" msgid "" "This lets you change the class of a player. Classes are not always " "changeable. Depending on the scenario, a player may receive additional towns " "and/or heroes not of their primary alignment." msgstr "" msgid "Handicap" msgstr "" # msgid "" "This lets you change the handicap of a particular player. Only humans may be " "handicapped. Handicapped players start with fewer resources and earn 15 or " "30% fewer resources per turn for mild and severe handicaps, respectively." msgstr "" msgid "%{color} player" msgstr "" msgid "No Handicap" msgstr "" msgid "No special restrictions on start resources and earning them per turn." msgstr "" msgid "Mild Handicap" msgstr "" # msgid "" "Mild handicapped players start with fewer resources and earn 15% fewer " "resources per turn." msgstr "" msgid "Severe Handicap" msgstr "" # msgid "" "Severe handicapped players start with fewer resources and earn 30% fewer " "resources per turn." msgstr "" msgid "View %{skill} Info" msgstr "" msgid "Keyboard|123" msgstr "" msgid "Keyboard|SPACE" msgstr "" msgid "Keyboard|ABC" msgstr "" msgid "Kingdom Income" msgstr "" msgid "Kingdom Income per day." msgstr "" msgid "For every lighthouse controlled, your ships will move further each day." msgstr "" msgid "English" msgstr "" msgid "French" msgstr "" msgid "Polish" msgstr "" msgid "German" msgstr "" msgid "Russian" msgstr "" msgid "Italian" msgstr "" msgid "Czech" msgstr "" msgid "Norwegian" msgstr "" msgid "Belarusian" msgstr "" msgid "Bulgarian" msgstr "" msgid "Ukrainian" msgstr "" msgid "Romanian" msgstr "Română" msgid "Spanish" msgstr "" msgid "Swedish" msgstr "" msgid "Portuguese" msgstr "" msgid "Turkish" msgstr "" msgid "Dutch" msgstr "" msgid "Hungarian" msgstr "" msgid "Danish" msgstr "" msgid "Slovak" msgstr "" #, fuzzy msgid "Vietnamese" msgstr "Vezi %{name}" msgid ", FPS: " msgstr "" msgid "%{object} robber" msgstr "" msgid "%{object} raided" msgstr "" msgid "Ector" msgstr "" msgid "Gwenneth" msgstr "" msgid "Sir Gallant" msgstr "" msgid "Tyro" msgstr "" msgid "Dimitry" msgstr "" msgid "Ruby" msgstr "" msgid "Crag Hack" msgstr "" msgid "Fineous" msgstr "" msgid "Jezebel" msgstr "" msgid "Atlas" msgstr "" msgid "Ergon" msgstr "" msgid "Jaclyn" msgstr "" msgid "Gem" msgstr "" msgid "Natasha" msgstr "" msgid "Carlawn" msgstr "" msgid "Luna" msgstr "" msgid "Arie" msgstr "" msgid "Barok" msgstr "" msgid "Kastore" msgstr "" msgid "Falagar" msgstr "" msgid "Dawn" msgstr "" msgid "Flint" msgstr "" msgid "Halon" msgstr "" msgid "Myra" msgstr "" msgid "Myrini" msgstr "" msgid "Wilfrey" msgstr "" msgid "Mandigal" msgstr "" msgid "Sarakin" msgstr "" msgid "Charity" msgstr "" msgid "Darlana" msgstr "" msgid "Ranloo" msgstr "" msgid "Rialdo" msgstr "" msgid "Zam" msgstr "" msgid "Zom" msgstr "" msgid "Celia" msgstr "" msgid "Roxana" msgstr "" msgid "Lord Corlagon" msgstr "" msgid "Lord Halton" msgstr "" msgid "Sister Eliza" msgstr "" msgid "Brother Brax" msgstr "" msgid "Dainwin" msgstr "" msgid "Joseph" msgstr "" msgid "Mog" msgstr "" msgid "Solmyr" msgstr "" msgid "Ceallach" msgstr "" msgid "Drakonia" msgstr "" msgid "Elderian" msgstr "" msgid "Gallavant" msgstr "" msgid "Jarkonas" msgstr "" msgid "Martine" msgstr "" msgid " gives you maximum morale" msgstr "" msgid " gives you maximum luck" msgstr "" msgid "You cannot pick up this artifact, you already have a full load!" msgstr "" msgid "To cast spells, you must first buy a spell book for %{gold} gold." msgstr "" msgid "Unfortunately, you seem to be a little short of cash at the moment." msgstr "" msgid "Do you wish to buy one?" msgstr "" msgid "%{count} / day" msgstr "" msgid "one" msgstr "" msgid "two" msgstr "" msgid "A whirlpool engulfs your ship. Some of your army has fallen overboard." msgstr "" msgid "Insulted by your refusal of their offer, the monsters attack!" msgstr "" msgid "" "The %{monster}, awed by the power of your forces, begin to scatter.\n" "Do you wish to pursue and engage them?" msgstr "" msgid "Ransacking an enemy camp, you discover a hidden cache of treasures." msgstr "" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with these resources, " "come back next week for more.\"" msgstr "" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there are no resources currently available. Please try " "again next week.\"" msgstr "" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with this gold, come " "back next week for more.\"" msgstr "" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there is no gold currently available. Please try again " "next week.\"" msgstr "" msgid "" "You've found an abandoned lean-to.\n" "Poking about, you discover some resources hidden nearby." msgstr "" msgid "The lean-to is long abandoned. There is nothing of value here." msgstr "" msgid "" "You catch a leprechaun foolishly sleeping amidst a cluster of magic " "mushrooms.\n" "In exchange for his freedom, he guides you to a small pot filled with " "precious things." msgstr "" msgid "" "You've found a magic garden, the kind of place that leprechauns and faeries " "like to cavort in, but there is no one here today.\n" "Perhaps you should try again next week." msgstr "" msgid "You come upon the remains of an unfortunate adventurer." msgstr "" msgid "Treasure" msgstr "" msgid "Searching through the tattered clothing, you find the %{artifact}." msgstr "" msgid "Searching through the tattered clothing, you find nothing." msgstr "" msgid "" "You come across an old wagon left by a trader who didn't quite make it to " "safe terrain." msgstr "" msgid "Unfortunately, others have found it first, and the wagon is empty." msgstr "" msgid "Searching inside, you find the %{artifact}." msgstr "" msgid "Inside, you find some of the wagon's cargo still intact." msgstr "" msgid "You search through the flotsam, and find some wood and some gold." msgstr "" msgid "You search through the flotsam, and find some wood." msgstr "" msgid "You search through the flotsam, but find nothing." msgstr "" msgid "Shrine of the 1st Circle" msgstr "" msgid "" "You come across a small shrine attended by a group of novice acolytes.\n" "In exchange for your protection, they agree to teach you a simple spell - " "'%{spell}'." msgstr "" msgid "Shrine of the 2nd Circle" msgstr "" msgid "" "You come across an ornate shrine attended by a group of rotund friars.\n" "In exchange for your protection, they agree to teach you a spell - " "'%{spell}'." msgstr "" msgid "Shrine of the 3rd Circle" msgstr "" msgid "" "You come across a lavish shrine attended by a group of high priests.\n" "In exchange for your protection, they agree to teach you a sophisticated " "spell - '%{spell}'." msgstr "" msgid "" "\n" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" msgid "" "\n" "Unfortunately, you already have knowledge of this spell, so there is nothing " "more for them to teach you." msgstr "" msgid "" "\n" "Unfortunately, you have no Magic Book to record the spell with." msgstr "" msgid "" "You approach the hut and observe a witch inside studying an ancient tome on " "%{skill}.\n" "\n" msgstr "" msgid "" "As you approach, she turns and focuses her one glass eye on you.\n" "\"You already know everything you deserve to learn!\" the witch screeches. " "\"NOW GET OUT OF MY HOUSE!\"" msgstr "" msgid "" "As you approach, she turns and speaks.\n" "\"You already know that which I would teach you. I can help you no further.\"" msgstr "" msgid "" "An ancient and immortal witch living in a hut with bird's legs for stilts " "teaches you %{skill} for her own inscrutable purposes." msgstr "" msgid "As you drink the sweet water, you gain luck for your next battle." msgstr "" msgid "You drink from the enchanted fountain, but nothing happens." msgstr "" msgid "You enter the faerie ring, but nothing happens." msgstr "" msgid "" "Upon entering the mystical faerie ring, your army gains luck for its next " "battle." msgstr "" msgid "" "You've found an ancient and weathered stone idol.\n" "It is supposed to grant luck to visitors, but since the stars are already " "smiling upon you, it does nothing." msgstr "" msgid "" "You've found an ancient and weathered stone idol.\n" "Kissing it is supposed to be lucky, so you do. The stone is very cold to the " "touch." msgstr "" msgid "The mermaids silently entice you to return later and be blessed again." msgstr "" msgid "" "The magical, soothing beauty of the Mermaids reaches you and your crew.\n" "Just for a moment, you forget your worries and bask in the beauty of the " "moment.\n" "The mermaids charms bless you with increased luck for your next combat." msgstr "" msgid "" "You come upon the pyramid of a great and ancient king.\n" "You are tempted to search it for treasure, but all the old stories warn of " "fearful curses and undead guardians.\n" "Will you search?" msgstr "" msgid "" "Upon defeating the monsters, you decipher an ancient glyph on the wall, " "telling the secret of the spell - '" msgstr "" msgid "Unfortunately, you have no Magic Book to record the spell with." msgstr "" msgid "" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" msgid "" "You come upon the pyramid of a great and ancient king.\n" "Routine exploration reveals that the pyramid is completely empty." msgstr "" msgid "" "A drink at the well is supposed to restore your spell points, but you are " "already at maximum." msgstr "" msgid "A second drink at the well in one day will not help you." msgstr "" msgid "A drink from the well has restored your spell points to maximum." msgstr "" msgid "" "\"I'm sorry sir,\" The leader of the soldiers says, \"but you already know " "everything we have to teach.\"" msgstr "" msgid "The soldiers living in the fort teach you a few new defensive tricks." msgstr "" msgid "" "You've come upon a mercenary camp practicing their tactics. \"You're too " "advanced for us,\" the mercenary captain says. \"We can teach nothing more.\"" msgstr "" msgid "" "You've come upon a mercenary camp practicing their tactics. The mercenaries " "welcome you and your troops and invite you to train with them." msgstr "" msgid "\"Go 'way!\", the witch doctor barks, \"you know all I know.\"" msgstr "" msgid "" "An Orcish witch doctor living in the hut deepens your knowledge of magic by " "showing you how to cast stones, read portents, and decipher the intricacies " "of chicken entrails." msgstr "" msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, the Druids turn you away, indicating they have nothing " "new to teach you." msgstr "" msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, they teach you new ways to cast spells." msgstr "" msgid "" "You tentatively approach the burial ground of ancient warriors. Do you want " "to search the graves?" msgstr "" msgid "" "You spend several hours searching the graves and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" msgid "Upon defeating the Zombies you search the graves and find something!" msgstr "" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the shipwreck?" msgstr "" msgid "" "You spend several hours sifting through the debris and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" msgid "" "Upon defeating the Ghosts you sift through the debris and find something!" msgstr "" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the ship?" msgstr "" msgid "" "Upon defeating the Skeletons you sift through the debris and find something!" msgstr "" msgid "Your men spot a navigational buoy, confirming that you are on course." msgstr "" msgid "" "Your men spot a navigational buoy, confirming that you are on course and " "increasing their morale." msgstr "" msgid "" "The drink at the oasis is refreshing, but offers no further benefit. The " "oasis might help again if you fought a battle first." msgstr "" msgid "" "A drink at the oasis fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" msgid "" "The drink at the watering hole is refreshing, but offers no further benefit. " "The watering hole might help again if you fought a battle first." msgstr "" msgid "" "A drink at the watering hole fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" msgid "" "It doesn't help to pray twice before a battle. Come back after you've fought." msgstr "" msgid "A visit and a prayer at the temple raises the morale of your troops." msgstr "" msgid "" "An old Knight appears on the steps of the gazebo. \"I am sorry, my liege, I " "have taught you all I can.\"" msgstr "" msgid "" "An old Knight appears on the steps of the gazebo. \"My liege, I will teach " "you all that I know to aid you in your travels.\"" msgstr "" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he says, \"I would give you an artifact as a reward, but " "you're all full.\"" msgstr "" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he rewards you for your act of kindness by giving you the " "%{art}." msgstr "" msgid "A leprechaun offers you the %{art} for the small price of %{gold} Gold." msgstr "" msgid "" "A leprechaun offers you the %{art} for the small price of %{gold} Gold and " "%{count} %{res}." msgstr "" msgid "Do you wish to buy this artifact?" msgstr "" msgid "" "You try to pay the leprechaun, but realize that you can't afford it. The " "leprechaun stamps his foot and ignores you." msgstr "" msgid "" "Insulted by your refusal of his generous offer, the leprechaun stamps his " "foot and ignores you." msgstr "" msgid "You've found the artifact: " msgstr "" msgid "" "You've found the humble dwelling of a withered hermit. The hermit tells you " "that he is willing to give the %{art} to the first wise person he meets." msgstr "" msgid "" "You've come across the spartan quarters of a retired soldier. The soldier " "tells you that he is willing to pass on the %{art} to the first true leader " "he meets." msgstr "" msgid "" "You've encountered a strange person with a hat and an owl on it. He tells " "you that he is willing to give %{art} if you have %{skill}." msgstr "" msgid "" "You come upon an ancient artifact. As you reach for it, a pack of Rogues " "leap out of the brush to guard their stolen loot." msgstr "" msgid "" "Through a clearing you observe an ancient artifact. Unfortunately, it's " "guarded by a nearby %{monster}. Do you want to fight the %{monster} for the " "artifact?" msgstr "" msgid "Victorious, you take your prize, the %{art}." msgstr "" msgid "" "Discretion is the better part of valor, and you decide to avoid this fight " "for today." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold pieces." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold and the %{art}." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it, " "only to find it empty." msgstr "" msgid "" "After scouring the area, you fall upon a hidden treasure cache. You may take " "the gold or distribute the gold to the peasants for experience. Do you wish " "to keep the gold?" msgstr "" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "%{gold} gold pieces." msgstr "" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "ancient artifact %{art}." msgstr "" msgid "" "You stumble upon a dented and tarnished lamp lodged deep in the earth. Do " "you wish to rub the lamp?" msgstr "" msgid "" "You have taken control of the local Alchemist shop. It will provide you with " "%{count} unit of Mercury per day." msgstr "" msgid "" "You gain control of a sawmill. It will provide you with %{count} units of " "wood per day." msgstr "" msgid "" "You gain control of an ore mine. It will provide you with %{count} units of " "ore per day." msgstr "" msgid "" "You gain control of a sulfur mine. It will provide you with %{count} unit of " "sulfur per day." msgstr "" msgid "" "You gain control of a crystal mine. It will provide you with %{count} unit " "of crystal per day." msgstr "" msgid "" "You gain control of a gem mine. It will provide you with %{count} unit of " "gems per day." msgstr "" msgid "" "You gain control of a gold mine. It will provide you with %{count} gold per " "day." msgstr "" msgid "" "The lighthouse is now under your control, and all of your ships will now " "move further each day." msgstr "" msgid "You gain control of a %{name}." msgstr "" msgid "" "You come upon an abandoned gold mine. The mine appears to be haunted. Do you " "wish to enter?" msgstr "" msgid "You beat the Ghosts and are able to restore the mine to production." msgstr "" msgid "" "A group of %{monster} with a desire for greater glory wish to join you. Do " "you accept?" msgstr "" msgid "As you approach the dwelling, you notice that there is no one here." msgstr "" msgid "" "You search the ruins, but the Medusas that used to live here are gone. " "Perhaps there will be more next week." msgstr "" msgid "" "You've found some Medusas living in the ruins. They are willing to join your " "army for a price. Do you want to recruit Medusas?" msgstr "" msgid "" "You've found a Sprite Tree City. Unfortunately, none of the Sprites living " "there wish to join an army. Maybe next week." msgstr "" msgid "" "Some of the Sprites living in the tree city are willing to join your army " "for a price. Do you want to recruit Sprites?" msgstr "" msgid "" "A colorful Rogues' wagon stands empty here. Perhaps more Rogues will be here " "later." msgstr "" msgid "" "Distant sounds of music and laughter draw you to a colorful wagon housing " "Rogues. Do you wish to have any Rogues join your army?" msgstr "" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. The " "tents are unoccupied. Perhaps more Nomads will be here later." msgstr "" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. Do you " "wish to have any Nomads join you during your travels?" msgstr "" msgid "The pit of mud bubbles for a minute and then lies still." msgstr "" msgid "" "As you approach the bubbling pit of mud, creatures begin to climb out and " "position themselves around it. In unison they say: \"Mother Earth would like " "to offer you a few of her troops. Do you want to recruit Earth Elementals?\"" msgstr "" msgid "You enter the structure of white stone pillars, and find nothing." msgstr "" msgid "" "White stone pillars support a roof that rises up to the sky. As you enter " "the structure, the dead air of the outside gives way to a whirling gust that " "almost pushes you back out. The air current materializes into a barely " "visible form. The creature asks, in what can only be described as a loud " "whisper: \"Why have you come? Are you here to call upon the forces of the " "air?\"" msgstr "" msgid "No Fire Elementals approach you from the lava pool." msgstr "" msgid "" "Beneath a structure that serves to hold in heat, Fire Elementals move about " "in a fiery pool of molten lava. A group of them approach you and offer their " "services. Would you like to recruit Fire Elementals?" msgstr "" msgid "A face forms in the water for a moment, and then is gone." msgstr "" msgid "" "Crystalline structures cast shadows over a small reflective pool of water. " "You peer into the pool, and a face that is not your own peers back. It asks: " "\"Would you like to call upon the powers of water?\"" msgstr "" msgid "This burial site is deathly still." msgstr "" msgid "" "Restless spirits of long dead warriors seeking their final resting place " "offer to join you in hopes of finding peace. Do you wish to recruit ghosts?" msgstr "" msgid "" "The City of the Dead is empty of life, and empty of unlife as well. Perhaps " "some undead will move in next week." msgstr "" msgid "" "Some Liches living here are willing to join your army for a price. Do you " "want to recruit Liches?" msgstr "" msgid "" "You've found the ruins of an ancient city, now inhabited solely by the " "undead. Will you search?" msgstr "" msgid "" "Some of the surviving Liches are impressed by your victory over their " "fellows, and offer to join you for a price. Do you want to recruit Liches?" msgstr "" msgid "" "You've found one of those bridges that Trolls are so fond of living under, " "but there are none here. Perhaps there will be some next week." msgstr "" msgid "" "Some Trolls living under a bridge are willing to join your army, but for a " "price. Do you want to recruit Trolls?" msgstr "" msgid "Trolls living under the bridge challenge you. Will you fight them?" msgstr "" msgid "" "A few Trolls remain, cowering under the bridge. They approach you and offer " "to join your forces as mercenaries. Do you want to buy any Trolls?" msgstr "" msgid "" "The Dragon city has no Dragons willing to join you this week. Perhaps a " "Dragon will become available next week." msgstr "" msgid "" "The Dragon city is willing to offer some Dragons for your army for a price. " "Do you wish to recruit Dragons?" msgstr "" msgid "" "You stand before the Dragon City, a place off-limits to mere humans. Do you " "wish to violate this rule and challenge the Dragons to a fight?" msgstr "" msgid "" "Having defeated the Dragon champions, the city's leaders agree to supply " "some Dragons to your army for a price. Do you wish to recruit Dragons?" msgstr "" msgid "From the observation tower, you are able to see distant lands." msgstr "" msgid "" "The spring only refills once a week, and someone's already been here this " "week." msgstr "" msgid "" "A drink at the spring is supposed to give you twice your normal spell " "points, but you are already at that level." msgstr "" msgid "" "A drink from the spring fills your blood with magic! You have twice your " "normal spell points in reserve." msgstr "" msgid "" "Recognizing you, the butler refuses to admit you. \"The master,\" he says, " "\"will not see the same student twice.\"" msgstr "" msgid "" "The butler admits you to see the master of the house. He trains you in the " "four skills a hero should know." msgstr "" msgid "" "The butler opens the door and looks you up and down. \"You are neither " "famous nor diplomatic enough to be admitted to see my master,\" he sniffs. " "\"Come back when you think yourself worthy.\"" msgstr "" msgid " and " msgstr "" msgid "" "All of the %{monsters} you have in your army have been trained by the battle " "masters of the fort. Your army now contains %{monsters2}." msgstr "" msgid "" "An unusual alliance of Ogres, Orcs, and Dwarves offer to train (upgrade) any " "such troops brought to them. Unfortunately, you have none with you." msgstr "" msgid "All of your %{monsters} have been upgraded into %{monsters2}." msgstr "" msgid "" "A blacksmith working at the foundry offers to convert all Pikemen and " "Swordsmen's weapons brought to him from iron to steel. He also says that he " "knows a process that will convert Iron Golems into Steel Golems. " "Unfortunately, you have none of these troops in your army, so he can't help " "you." msgstr "" msgid "" "The captain looks at you with surprise and says:\n" "\"You already have all the maps I know about. Let me fish in peace now.\"" msgstr "" msgid "" "A retired captain living on this refurbished fishing platform offers to sell " "you maps of the sea he made in his younger days for 1,000 gold. Do you wish " "to buy the maps?" msgstr "" msgid "" "The captain sighs. \"You don't have enough money, eh? You can't expect me " "to give my maps away for free!\"" msgstr "" msgid "" "You come upon an obelisk made from a type of stone you have never seen " "before. Staring at it intensely, the smooth surface suddenly changes to an " "inscription. The inscription is a piece of a lost ancient map. Quickly you " "copy down the piece and the inscription vanishes as abruptly as it appeared." msgstr "" msgid "You have already been to this obelisk." msgstr "" msgid "" "Upon your approach, the tree opens its eyes in delight. \"It is good to see " "you, my student. I hope my teachings have helped you.\"" msgstr "" msgid "" "Upon your approach, the tree opens its eyes in delight. \"Ahh, an " "adventurer! Allow me to teach you a little of what I have learned over the " "ages.\"" msgstr "" msgid "Upon your approach, the tree opens its eyes in delight." msgstr "" msgid "" "\"Ahh, an adventurer! I will be happy to teach you a little of what I have " "learned over the ages for a mere %{count} %{res}.\"" msgstr "" msgid "(Just bury it around my roots.)" msgstr "" msgid "Tears brim in the eyes of the tree." msgstr "" msgid "\"I need %{count} %{res}.\"" msgstr "" msgid "it whispers. (sniff) \"Well, come back when you can pay me.\"" msgstr "" msgid "" "Nestled among the trees sits a blind seer. After you explain the intent of " "your journey, the seer activates his crystal ball, allowing you to see the " "strengths and weaknesses of your opponents." msgstr "" msgid "" "The entrance to the cave is dark, and a foul, sulfurous smell issues from " "the cave mouth. Will you enter?" msgstr "" msgid "Except for evidence of a terrible battle, the cave is empty." msgstr "" msgid "" "You find a powerful and grotesque Demon in the cave. \"Today,\" it rasps, " "\"you will fight and surely die. But I will give you a choice of deaths. You " "may fight me, or you may fight my servants. Do you prefer to fight my " "servants?\"" msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points." msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points and " "%{count} gold." msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and find the %{art} in the back of the cave." msgstr "" msgid "" "Seeing that you do not have %{count} gold, the demon slashes you with its " "claws, and the last thing you see is a red haze." msgstr "" msgid "" "The Demon leaps upon you and has its claws at your throat before you can " "even draw your sword. \"Your life is mine,\" it says. \"I will sell it back " "to you for %{count} gold.\"" msgstr "" msgid "" "Upon defeating the daemon's servants, you find a hidden cache with %{count} " "gold." msgstr "" msgid "" "As you enter the Alchemist's Tower, a hobbled, graying man in a brown cloak " "makes his way towards you." msgstr "" msgid "He checks your pack, and sees that you have 1 cursed item." msgid_plural "" "He checks your pack, and sees that you have %{count} cursed items." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "For %{gold} gold, the alchemist will remove it for you. Do you pay?" msgid_plural "" "For %{gold} gold, the alchemist will remove them for you. Do you pay?" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "" "After you consent to pay the requested amount of gold, the alchemist grabs " "the cursed artifact and throws it into his magical cauldron." msgid_plural "" "After you consent to pay the requested amount of gold, the alchemist grabs " "all cursed artifacts and throws them into his magical cauldron." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "" "You hear a voice from behind the locked door, \"You don't have enough gold " "to pay for my services.\"" msgstr "" msgid "" "You hear a voice from high above in the tower, \"Go away! I can't help you!\"" msgstr "" msgid "" "The head groom speaks to you, \"That is a fine looking horse you have. I am " "afraid we can give you no better, but the horses your cavalry are riding " "look to be of poor breeding stock. We have many trained war horses which " "would aid your riders greatly. I insist you take them.\"" msgstr "" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, he will " "grow tired in a week. You must also let me give better horses to your " "mounted soldiers, their horses look shoddy and weak.\"" msgstr "" msgid "" "The head groom approaches you and speaks, \"You already have a fine horse, " "and have no inexperienced cavalry which might make use of our trained war " "horses.\"" msgstr "" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, his " "endurance will wane with a lot of heavy riding, and you must return for a " "fresh mount in a week. We also have many fine war horses which could benefit " "mounted soldiers, but you have none we can help.\"" msgstr "" msgid "The Arena guards turn you away." msgstr "" msgid "" "You have your crew stop up their ears with wax before the sirens' eerie song " "has any chance of luring them to a watery grave." msgstr "" msgid "" "As the sirens sing their eerie song, your small, determined army manages to " "overcome the urge to dive headlong into the sea." msgstr "" msgid "" "An eerie wailing song emanates from the sirens perched upon the rocks. Many " "of your crew fall under its spell, and dive into the water where they drown. " "You are now wiser for the visit, and gain %{exp} experience." msgstr "" msgid "" "In a dazzling display of daring, you break into the local jail and free the " "hero imprisoned there, who, in return, pledges loyalty to your cause." msgstr "" msgid "" "You already have %{count} heroes, and regretfully must leave the prisoner in " "this jail to languish in agony for untold days." msgstr "" msgid "" "You enter a rickety hut and talk to the magician who lives there. He tells " "you of places near and far which may aid you in your journeys." msgstr "" msgid "This eye seems to be intently studying its surroundings." msgstr "" msgid "You come across a giant Sphinx. The Sphinx remains strangely quiet." msgstr "" msgid "" "\"I have a riddle for you,\" the Sphinx says. \"Answer correctly, and you " "shall be rewarded. Answer incorrectly, and you shall be eaten. Do you accept " "the challenge?\"" msgstr "" msgid "" "The Sphinx asks you the following riddle:\n" "\n" "'%{riddle}'\n" "\n" "Your answer?" msgstr "" msgid "" "\"You guessed incorrectly,\" the Sphinx says, smiling. The Sphinx swipes at " "you with a paw, knocking you to the ground. Another blow makes the world go " "black, and you know no more." msgstr "" msgid "" "Looking somewhat disappointed, the Sphinx sighs. \"You've answered my riddle " "so here's your reward. Now begone.\"" msgstr "" msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "As you speak the magic word, the glowing barrier dissolves into nothingness." msgstr "" msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "You speak, and nothing happens." msgstr "" msgid "" "You enter the tent and see an old woman gazing into a magic gem. She looks " "up and says,\n" "\"In my travels, I have learned much in the way of arcane magic. A great " "oracle taught me his skill. I have the answer you seek.\"" msgstr "" #, fuzzy msgid "No spell book is present." msgstr "Nu sunt vrăji de aruncat." msgid "" "This spell costs %{mana} spell points. You have no spell points, so you " "cannot cast it." msgstr "" msgid "" "This spell costs %{mana} spell points. You only have %{point} spell points, " "so you cannot cast it." msgstr "" msgid "The spell was not found." msgstr "" msgid "Only heroes can cast this spell." msgstr "" msgid "This hero is not able to cast adventure spells." msgstr "" msgid "Your hero is too tired to cast this spell today. Try again tomorrow." msgstr "" msgid "This spell cannot be cast on a boat." msgstr "" msgid "This spell can only be cast near an ocean." msgstr "" msgid "" "There are no boats available and no ocean adjacent to the hero where this " "spell will work." msgstr "" msgid "There are no boats available for this spell." msgstr "" #, fuzzy msgid "There is no ocean adjacent to the hero where this spell will work." msgstr "" "Nu există niciun spaÈ›iu deschis adiacent eroului tău în care să invoci un " "elemental." msgid "" "You do not own any town or castle that is not currently occupied by a hero. " "This spell will have no effect." msgstr "" #, fuzzy msgid "This hero is already in a town, so this spell will have no effect." msgstr "Vraja aceea nu va afecta pe nimeni!" msgid "" "The nearest town is %{town}.\n" "\n" "This town is occupied by your hero %{hero}." msgstr "" msgid "This spell is already in effect." msgstr "" msgid "" "No opponent neither has nor can have any hero under their command anymore. " "Casting this spell will have no effect." msgstr "" msgid "" "No opponent has a hero under their command at this time. Casting this spell " "will have no effect." msgstr "" msgid "" "You must be within %{count} spaces of a monster for the Visions spell to " "work." msgstr "" msgid "" "You must be standing on the entrance to a mine (sawmills and alchemist labs " "do not count) to cast this spell." msgstr "" msgid "You must first defeat the ghosts guarding the mine to cast this spell." msgstr "" msgid "" "There are already at least as many elementals guarding the mine as this hero " "can generate. Casting this spell will have no effect." msgstr "" msgid "%{name} the %{race} (Level %{level})" msgstr "" msgid "" "'Spread' combat formation spreads the hero's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" msgid "" "'Grouped' combat formation bunches the hero's army together in the center of " "their side of the battlefield." msgstr "" msgid "Are you sure you want to dismiss this Hero?" msgstr "" msgid "View Experience Info" msgstr "" msgid "View Spell Points Info" msgstr "" msgid "Set army combat formation to 'Spread'" msgstr "" msgid "Set army combat formation to 'Grouped'" msgstr "" msgid "Exit Hero Screen" msgstr "" msgid "You cannot dismiss a hero in a castle" msgstr "" msgid "Dismissal of %{name} the %{race} is prohibited by scenario" msgstr "" msgid "Dismiss %{name} the %{race}" msgstr "" msgid "Show previous hero" msgstr "" msgid "Show next hero" msgstr "" msgid "Blood Morale" msgstr "" msgid "%{morale} Morale" msgstr "" msgid "%{luck} Luck" msgstr "" msgid "Current Luck Modifiers:" msgstr "" msgid "Current Morale Modifiers:" msgstr "" msgid "Entire army is undead, so morale does not apply." msgstr "" msgid "" "Current experience %{exp1}.\n" " Next level %{exp2}." msgstr "" msgid "Level %{level}" msgstr "" msgid "" "%{name} currently has %{point} spell points out of a maximum of %{max}. The " "maximum number of spell points is 10 times the hero's knowledge. It is " "occasionally possible for the hero to have more than their maximum spell " "points via special events." msgstr "" msgid "%{name1} meets %{name2}" msgstr "" msgid "Enemy heroes are now fully identifiable." msgstr "" msgid "Identify Hero" msgstr "" msgid "Select town to port to." msgstr "" msgid "Town Portal" msgstr "" msgid "The creatures are willing to join us!" msgstr "" msgid "All the creatures will join us..." msgstr "" msgid "The creature will join us..." msgid_plural "%{count} of the creatures will join us..." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "" "\n" " for a fee of %{gold} gold." msgstr "" msgid "These weak creatures will surely flee before us." msgstr "" msgid "I fear these creatures are in the mood for a fight." msgstr "" #, fuzzy msgid "The hero's attack skill is a bonus added to each unit's attack skill." msgstr "fiecare cu un bonus de +%{attack} la abilitatea lor de atac." msgid "The hero's defense skill is a bonus added to each unit's defense skill." msgstr "" msgid "The hero's spell power determines the duration or power of a spell." msgstr "" msgid "" " The hero's knowledge determines how many spell points the hero may have. " "Under normal circumstances, a hero is limited to 10 spell points per level " "of knowledge." msgstr "" msgid "Current Modifiers:" msgstr "" msgid "skill|Basic" msgstr "" msgid "skill|Advanced" msgstr "" msgid "skill|Expert" msgstr "" msgid "Pathfinding" msgstr "" msgid "Archery" msgstr "" msgid "Logistics" msgstr "" msgid "Scouting" msgstr "" msgid "Diplomacy" msgstr "" msgid "Navigation" msgstr "" msgid "Leadership" msgstr "" msgid "Wisdom" msgstr "" msgid "Mysticism" msgstr "" msgid "Ballistics" msgstr "" msgid "Eagle Eye" msgstr "" msgid "Necromancy" msgstr "" msgid "Estates" msgstr "" msgid "Advanced Archery" msgstr "" msgid "Advanced Pathfinding" msgstr "" msgid "Basic Archery" msgstr "" msgid "Basic Pathfinding" msgstr "" msgid "Expert Pathfinding" msgstr "" msgid "Advanced Logistics" msgstr "" msgid "Basic Logistics" msgstr "" msgid "Basic Scouting" msgstr "" msgid "Expert Archery" msgstr "" msgid "Expert Logistics" msgstr "" msgid "Advanced Diplomacy" msgstr "" msgid "Advanced Scouting" msgstr "" msgid "Basic Diplomacy" msgstr "" msgid "Expert Diplomacy" msgstr "" msgid "Expert Scouting" msgstr "" msgid "Advanced Leadership" msgstr "" msgid "Advanced Navigation" msgstr "" msgid "Basic Leadership" msgstr "" msgid "Basic Navigation" msgstr "" msgid "Expert Navigation" msgstr "" msgid "Advanced Wisdom" msgstr "" msgid "Basic Mysticism" msgstr "" msgid "Basic Wisdom" msgstr "" msgid "Expert Leadership" msgstr "" msgid "Expert Wisdom" msgstr "" msgid "Advanced Luck" msgstr "" msgid "Advanced Mysticism" msgstr "" msgid "Basic Luck" msgstr "" msgid "Expert Luck" msgstr "" msgid "Expert Mysticism" msgstr "" msgid "Advanced Ballistics" msgstr "" msgid "Advanced Eagle Eye" msgstr "" msgid "Basic Ballistics" msgstr "" msgid "Basic Eagle Eye" msgstr "" msgid "Expert Ballistics" msgstr "" msgid "Advanced Necromancy" msgstr "" msgid "Basic Estates" msgstr "" msgid "Basic Necromancy" msgstr "" msgid "Expert Eagle Eye" msgstr "" msgid "Expert Necromancy" msgstr "" msgid "Advanced Estates" msgstr "" msgid "Expert Estates" msgstr "" msgid "" "%{skill} reduces the movement penalty for rough terrain by %{count} percent." msgstr "" msgid "%{skill} eliminates the movement penalty for rough terrain." msgstr "" msgid "" "%{skill} increases the damage done by the hero's range attacking creatures " "by %{count} percent." msgstr "" msgid "%{skill} increases the hero's movement points by %{count} percent." msgstr "" msgid "%{skill} increases the hero's viewable area by one square." msgid_plural "%{skill} increases the hero's viewable area by %{count} squares." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "" "%{skill} allows the hero to negotiate with monsters who are weaker than " "their army, and reduces the cost of surrender." msgstr "" msgid "" "Approximately %{count} percent of the creatures may offer to join the hero." msgstr "" msgid "All of the creatures may offer to join the hero." msgstr "" msgid "" "The cost of surrender is reduced to %{percent} percent of the total cost of " "troops in the army." msgstr "" msgid "" "%{skill} increases the hero's movement points over water by %{count} percent." msgstr "" msgid "%{skill} increases the hero's troops morale by %{count}." msgstr "" msgid "%{skill} allows the hero to learn third level spells." msgstr "" msgid "%{skill} allows the hero to learn fourth level spells." msgstr "" msgid "%{skill} allows the hero to learn fifth level spells." msgstr "" msgid "%{skill} regenerates one additional spell point per day to the hero." msgid_plural "" "%{skill} regenerates %{count} additional spell points per day to the hero." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "%{skill} increases the hero's luck by %{count}." msgstr "" msgid "" "%{skill} gives the hero's catapult shots a greater chance to hit and do " "damage to castle walls." msgstr "" msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot has a " "greater chance to hit and do damage to castle walls." msgstr "" msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot " "automatically destroys any wall, except a fortified wall in a Knight castle." msgstr "" msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 1st or " "2nd level spell that was cast by an enemy during combat." msgstr "" msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 3rd " "level spell (or below) that was cast by an enemy during combat." msgstr "" msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 4th " "level spell (or below) that was cast by an enemy during combat." msgstr "" msgid "" "%{skill} allows %{count} percent of the creatures killed in combat to be " "brought back from the dead as Skeletons." msgstr "" msgid "" "The hero produces %{count} gold pieces per day as tax revenue from estates." msgstr "" msgid "Blue" msgstr "" msgid "Green" msgstr "" msgid "Red" msgstr "" msgid "Yellow" msgstr "" msgid "Orange" msgstr "" msgid "Purple" msgstr "" msgid "barrier|Aqua" msgstr "" msgid "barrier|Blue" msgstr "" msgid "barrier|Brown" msgstr "" msgid "barrier|Gold" msgstr "" msgid "barrier|Green" msgstr "" msgid "barrier|Orange" msgstr "" msgid "barrier|Purple" msgstr "" msgid "barrier|Red" msgstr "" msgid "tent|Aqua" msgstr "" msgid "tent|Blue" msgstr "" msgid "tent|Brown" msgstr "" msgid "tent|Gold" msgstr "" msgid "tent|Green" msgstr "" msgid "tent|Orange" msgstr "" msgid "tent|Purple" msgstr "" msgid "tent|Red" msgstr "" msgid "Experience" msgstr "" msgid "" "Experience allows your heroes to go up levels, increasing their primary and " "secondary skills." msgstr "" msgid "Hero/Stats" msgstr "" msgid "Skills" msgstr "" msgid "Town/Castle" msgstr "" msgid "Garrison" msgstr "" msgid "Gold Per Day:" msgstr "" msgid "View Heroes." msgstr "" msgid "Towns/Castles" msgstr "" msgid "View Towns and Castles." msgstr "" msgid "luck|Cursed" msgstr "" msgid "luck|Awful" msgstr "" msgid "luck|Bad" msgstr "" msgid "luck|Normal" msgstr "" msgid "luck|Good" msgstr "" msgid "luck|Great" msgstr "" msgid "luck|Irish" msgstr "" msgid "" "Bad luck sometimes falls on the hero's units in combat, causing their " "attacks to only do half damage." msgstr "" msgid "" "Neutral luck means the hero's units will never get lucky or unlucky attacks " "on the enemy." msgstr "" msgid "" "Good luck sometimes lets the hero's units get lucky attacks (double " "strength) in combat." msgstr "" msgid "morale|Treason" msgstr "" msgid "morale|Awful" msgstr "" msgid "morale|Poor" msgstr "" msgid "morale|Normal" msgstr "" msgid "morale|Good" msgstr "" msgid "morale|Great" msgstr "" msgid "morale|Blood!" msgstr "" #, fuzzy msgid "Bad morale may cause the hero's units to freeze in combat." msgstr "Moralul scăzut face ca %{monster} să îngheÈ›e de panică." msgid "" "Neutral morale means the hero's units will never be blessed with extra " "attacks or freeze in combat." msgstr "" msgid "Good morale may give the hero's units extra attacks in combat." msgstr "" msgid "Knight" msgstr "" msgid "Barbarian" msgstr "" msgid "Sorceress" msgstr "" msgid "Warlock" msgstr "" msgid "Wizard" msgstr "" msgid "Necromancer" msgstr "" msgid "Multi" msgstr "" msgid "doubleLined|Knight" msgstr "" msgid "doubleLined|Barbarian" msgstr "" msgid "doubleLined|Sorceress" msgstr "" msgid "doubleLined|Warlock" msgstr "" msgid "doubleLined|Wizard" msgstr "" msgid "" "doubleLined|Necro-\n" "mancer" msgstr "" msgid "doubleLinedRace|Multi" msgstr "" msgid "doubleLinedRace|Random" msgstr "" msgid "speed|Standing" msgstr "" msgid "speed|Crawling" msgstr "" msgid "speed|Very Slow" msgstr "" msgid "speed|Slow" msgstr "" msgid "speed|Average" msgstr "" msgid "speed|Fast" msgstr "" msgid "speed|Very Fast" msgstr "" msgid "speed|Ultra Fast" msgstr "" msgid "speed|Blazing" msgstr "" msgid "speed|Instant" msgstr "" msgid "week|Squirrel" msgstr "" msgid "week|Rabbit" msgstr "" msgid "week|Gopher" msgstr "" msgid "week|Badger" msgstr "" msgid "week|Rat" msgstr "" msgid "week|Eagle" msgstr "" msgid "week|Weasel" msgstr "" msgid "week|Raven" msgstr "" msgid "week|Mongoose" msgstr "" msgid "week|Dog" msgstr "" msgid "week|Aardvark" msgstr "" msgid "week|Lizard" msgstr "" msgid "week|Tortoise" msgstr "" msgid "week|Hedgehog" msgstr "" msgid "week|Condor" msgstr "" msgid "week|Ant" msgstr "" msgid "week|Grasshopper" msgstr "" msgid "week|Dragonfly" msgstr "" msgid "week|Spider" msgstr "" msgid "week|Butterfly" msgstr "" msgid "week|Bumblebee" msgstr "" msgid "week|Locust" msgstr "" msgid "week|Earthworm" msgstr "" msgid "week|Hornet" msgstr "" msgid "week|Beetle" msgstr "" msgid "week|PLAGUE" msgstr "" msgid "week|Unnamed" msgstr "" msgid "Desert" msgstr "" msgid "Snow" msgstr "" msgid "Wasteland" msgstr "" msgid "Beach" msgstr "" msgid "Lava" msgstr "" msgid "Dirt" msgstr "" msgid "Grass" msgstr "" msgid "Ocean" msgstr "" msgid "maps|Small" msgstr "" msgid "maps|Medium" msgstr "" msgid "maps|Large" msgstr "" msgid "maps|Extra Large" msgstr "" msgid "maps|Custom Size" msgstr "" msgid "Ore Mine" msgstr "" msgid "Sulfur Mine" msgstr "" msgid "Crystal Mine" msgstr "" msgid "Gems Mine" msgstr "" msgid "Gold Mine" msgstr "" msgid "Mine" msgstr "" msgid "Burma shave." msgstr "" msgid "Next sign 50 miles." msgstr "" msgid "See Rock City." msgstr "" msgid "This space for rent." msgstr "" msgid "No object" msgstr "" msgid "Alchemist Lab" msgstr "" msgid "Sign" msgstr "" msgid "Buoy" msgstr "" msgid "Skeleton" msgstr "" msgid "Daemon Cave" msgstr "" msgid "Treasure Chest" msgstr "" msgid "Faerie Ring" msgstr "" msgid "Campfire" msgstr "" msgid "Fountain" msgstr "" msgid "Gazebo" msgstr "" msgid "Genie Lamp" msgstr "" msgid "Archer's House" msgstr "" msgid "Goblin Hut" msgstr "" msgid "Dwarf Cottage" msgstr "" msgid "Peasant Hut" msgstr "" msgid "Stables" msgstr "" msgid "Alchemist's Tower" msgstr "" msgid "Event" msgstr "" msgid "Dragon City" msgstr "" msgid "Lighthouse" msgid_plural "Lighthouses" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "Water Wheel" msgid_plural "Water Wheels" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "Monster" msgstr "" msgid "Obelisk" msgstr "" msgid "Oasis" msgstr "" msgid "Resource" msgstr "" msgid "Sawmill" msgstr "" msgid "Oracle" msgstr "" msgid "Shrine of the First Circle" msgstr "" msgid "Shipwreck" msgstr "" msgid "Sea Chest" msgstr "" msgid "Desert Tent" msgstr "" msgid "Stone Liths" msgstr "" msgid "Wagon Camp" msgstr "" msgid "Hut of the Magi" msgstr "" msgid "Whirlpool" msgstr "" msgid "Windmill" msgid_plural "Windmills" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "Artifact" msgstr "" msgid "Mermaid" msgstr "" msgid "Boat" msgstr "" msgid "Random Ultimate Artifact" msgstr "" msgid "Random Artifact" msgstr "" msgid "Random Resource" msgstr "" msgid "Random Monster" msgstr "" msgid "Random Town" msgstr "" msgid "Random Castle" msgstr "" msgid "Eye of the Magi" msgstr "" msgid "Random Monster - weak" msgstr "" msgid "Random Monster - medium" msgstr "" msgid "Random Monster - strong" msgstr "" msgid "Random Monster - very strong" msgstr "" msgid "Hero" msgstr "" msgid "Nothing Special" msgstr "" msgid "Mossy Rock" msgstr "" msgid "Watch Tower" msgstr "" msgid "Tree House" msgstr "" msgid "Tree City" msgstr "" msgid "Ruins" msgstr "" msgid "Fort" msgstr "" msgid "Abandoned Mine" msgstr "" msgid "Sirens" msgstr "" msgid "Standing Stones" msgstr "" msgid "Idol" msgstr "" msgid "Tree of Knowledge" msgstr "" msgid "Witch Doctor's Hut" msgstr "" msgid "Temple" msgstr "" msgid "Hill Fort" msgstr "" msgid "Halfling Hole" msgstr "" msgid "Mercenary Camp" msgstr "" msgid "Shrine of the Second Circle" msgstr "" msgid "Shrine of the Third Circle" msgstr "" msgid "City of the Dead" msgstr "" msgid "Sphinx" msgstr "" msgid "Wagon" msgstr "" msgid "Tar Pit" msgstr "" msgid "Artesian Spring" msgstr "" msgid "Troll Bridge" msgstr "" msgid "Watering Hole" msgstr "" msgid "Witch's Hut" msgstr "" msgid "Xanadu" msgstr "" msgid "Lean-To" msgstr "" msgid "Magellan's Maps" msgstr "" msgid "Flotsam" msgstr "" msgid "Derelict Ship" msgstr "" msgid "Shipwreck Survivor" msgstr "" msgid "Bottle" msgstr "" msgid "Magic Well" msgstr "" msgid "Magic Garden" msgid_plural "Magic Gardens" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "Observation Tower" msgstr "" msgid "Freeman's Foundry" msgstr "" msgid "Reefs" msgstr "" msgid "Volcano" msgstr "" msgid "Flowers" msgstr "" msgid "Rock" msgstr "" msgid "Water Lake" msgstr "" msgid "Mandrake" msgstr "" msgid "Dead Tree" msgstr "" msgid "Stump" msgstr "" msgid "Crater" msgstr "" msgid "Cactus" msgstr "" msgid "Mound" msgstr "" msgid "Dune" msgstr "" msgid "Lava Pool" msgstr "" msgid "Shrub" msgstr "" msgid "Barrow Mounds" msgstr "" msgid "Random Artifact - Treasure" msgstr "" msgid "Random Artifact - Minor" msgstr "" msgid "Random Artifact - Major" msgstr "" msgid "Barrier" msgstr "" msgid "Traveller's Tent" msgstr "" msgid "Jail" msgstr "" msgid "Fire Summoning Altar" msgstr "" msgid "Air Summoning Altar" msgstr "" msgid "Earth Summoning Altar" msgstr "" msgid "Water Summoning Altar" msgstr "" msgid "Unknown Monster" msgstr "" msgid "Unknown Monsters" msgstr "" msgid "Peasant" msgstr "" msgid "Peasants" msgstr "" msgid "Archer" msgstr "" msgid "Archers" msgstr "" msgid "Ranger" msgstr "" msgid "Rangers" msgstr "" msgid "Pikeman" msgstr "" msgid "Pikemen" msgstr "" msgid "Veteran Pikeman" msgstr "" msgid "Veteran Pikemen" msgstr "" msgid "Swordsman" msgstr "" msgid "Swordsmen" msgstr "" msgid "Master Swordsman" msgstr "" msgid "Master Swordsmen" msgstr "" msgid "Cavalries" msgstr "" msgid "Cavalry" msgstr "" msgid "Champion" msgstr "" msgid "Champions" msgstr "" msgid "Paladin" msgstr "" msgid "Paladins" msgstr "" msgid "Crusader" msgstr "" msgid "Crusaders" msgstr "" msgid "Goblin" msgstr "" msgid "Goblins" msgstr "" msgid "Orc" msgstr "" msgid "Orcs" msgstr "" msgid "Orc Chief" msgstr "" msgid "Orc Chiefs" msgstr "" msgid "Wolf" msgstr "" msgid "Wolves" msgstr "" msgid "Ogre" msgstr "" msgid "Ogres" msgstr "" msgid "Ogre Lord" msgstr "" msgid "Ogre Lords" msgstr "" msgid "Troll" msgstr "" msgid "Trolls" msgstr "" msgid "War Troll" msgstr "" msgid "War Trolls" msgstr "" msgid "Cyclopes" msgstr "" msgid "Cyclops" msgstr "" msgid "Sprite" msgstr "" msgid "Sprites" msgstr "" msgid "Dwarf" msgstr "" msgid "Dwarves" msgstr "" msgid "Battle Dwarf" msgstr "" msgid "Battle Dwarves" msgstr "" msgid "Elf" msgstr "" msgid "Elves" msgstr "" msgid "Grand Elf" msgstr "" msgid "Grand Elves" msgstr "" msgid "Druid" msgstr "" msgid "Druids" msgstr "" msgid "Greater Druid" msgstr "" msgid "Greater Druids" msgstr "" msgid "Unicorn" msgstr "" msgid "Unicorns" msgstr "" msgid "Phoenix" msgstr "" msgid "Phoenixes" msgstr "" msgid "Centaur" msgstr "" msgid "Centaurs" msgstr "" msgid "Gargoyle" msgstr "" msgid "Gargoyles" msgstr "" msgid "Griffin" msgstr "" msgid "Griffins" msgstr "" msgid "Minotaur" msgstr "" msgid "Minotaurs" msgstr "" msgid "Minotaur King" msgstr "" msgid "Minotaur Kings" msgstr "" msgid "Hydra" msgstr "" msgid "Hydras" msgstr "" msgid "Green Dragon" msgstr "" msgid "Green Dragons" msgstr "" msgid "Red Dragon" msgstr "" msgid "Red Dragons" msgstr "" msgid "Black Dragon" msgstr "" msgid "Black Dragons" msgstr "" msgid "Halfling" msgstr "" msgid "Halflings" msgstr "" msgid "Boar" msgstr "" msgid "Boars" msgstr "" msgid "Iron Golem" msgstr "" msgid "Iron Golems" msgstr "" msgid "Steel Golem" msgstr "" msgid "Steel Golems" msgstr "" msgid "Roc" msgstr "" msgid "Rocs" msgstr "" msgid "Mage" msgstr "" msgid "Magi" msgstr "" msgid "Archmage" msgstr "" msgid "Archmagi" msgstr "" msgid "Giant" msgstr "" msgid "Giants" msgstr "" msgid "Titan" msgstr "" msgid "Titans" msgstr "" msgid "Skeletons" msgstr "" msgid "Zombie" msgstr "" msgid "Zombies" msgstr "" msgid "Mutant Zombie" msgstr "" msgid "Mutant Zombies" msgstr "" msgid "Mummies" msgstr "" msgid "Mummy" msgstr "" msgid "Royal Mummies" msgstr "" msgid "Royal Mummy" msgstr "" msgid "Vampire" msgstr "" msgid "Vampires" msgstr "" msgid "Vampire Lord" msgstr "" msgid "Vampire Lords" msgstr "" msgid "Lich" msgstr "" msgid "Liches" msgstr "" msgid "Power Lich" msgstr "" msgid "Power Liches" msgstr "" msgid "Bone Dragon" msgstr "" msgid "Bone Dragons" msgstr "" msgid "Rogue" msgstr "" msgid "Rogues" msgstr "" msgid "Nomad" msgstr "" msgid "Nomads" msgstr "" msgid "Ghost" msgstr "" msgid "Ghosts" msgstr "" msgid "Genie" msgstr "" msgid "Genies" msgstr "" msgid "Medusa" msgstr "" msgid "Medusas" msgstr "" msgid "Earth Elemental" msgstr "" msgid "Earth Elementals" msgstr "" msgid "Air Elemental" msgstr "" msgid "Air Elementals" msgstr "" msgid "Fire Elemental" msgstr "" msgid "Fire Elementals" msgstr "" msgid "Water Elemental" msgstr "" msgid "Water Elementals" msgstr "" msgid "Random Monsters" msgstr "" msgid "Random Monster 1" msgstr "" msgid "Random Monsters 1" msgstr "" msgid "Random Monster 2" msgstr "" msgid "Random Monsters 2" msgstr "" msgid "Random Monster 3" msgstr "" msgid "Random Monsters 3" msgstr "" msgid "Random Monster 4" msgstr "" msgid "Random Monsters 4" msgstr "" msgid "Double shot" msgstr "" msgid "2-hex monster" msgstr "" msgid "Double strike" msgstr "" msgid "Double damage to Undead" msgstr "" msgid "% magic resistance" msgstr "" msgid "Immune to Mind spells" msgstr "" msgid "Immune to Elemental spells" msgstr "" msgid "Immune to Fire spells" msgstr "" msgid "Immune to Cold spells" msgstr "" msgid "Immune to " msgstr "" msgid "% immunity to %{spell} spell" msgstr "" msgid "% damage from Elemental spells" msgstr "" msgid "% chance to Dispel beneficial spells" msgstr "" msgid "% chance to Paralyze" msgstr "" msgid "% chance to Petrify" msgstr "" msgid "% chance to Blind" msgstr "" msgid "% chance to Curse" msgstr "" msgid "% chance to cast %{spell} spell" msgstr "" msgid "HP regeneration" msgstr "" msgid "Two hexes attack" msgstr "" msgid "Flyer" msgstr "" msgid "Always retaliates" msgstr "" msgid "Attacks all adjacent enemies" msgstr "" msgid "No melee penalty" msgstr "" msgid "Dragon" msgstr "" msgid "Undead" msgstr "" msgid "No enemy retaliation" msgstr "" msgid "HP drain" msgstr "" msgid "Cloud attack" msgstr "" msgid "Decreases enemy's morale by " msgstr "" msgid "% chance to halve enemy" msgstr "" msgid "Soul Eater" msgstr "" msgid "Elemental" msgstr "" msgid "No Morale" msgstr "" msgid "200% damage from Fire spells" msgstr "" msgid "200% damage from Cold spells" msgstr "" msgid "% damage from %{spell} spell" msgstr "" msgid "% immunity to " msgstr "" msgid "Lightning" msgstr "" msgid "% damage from " msgstr "" msgid "The three Anduran artifacts magically combine into one." msgstr "" msgid "View Spells" msgstr "" msgid "View %{name} Info" msgstr "" msgid "Move %{name}" msgstr "" msgid "Cannot move the Spellbook" msgstr "" msgid "This item can't be traded." msgstr "" msgid "Invalid Artifact" msgstr "" #, fuzzy msgid "The %{name} increases the hero's knowledge by %{count}." msgstr "%{name} trage cu puterea de %{count} arcaÈ™i" msgid "Ultimate Book of Knowledge" msgstr "" #, fuzzy msgid "The %{name} increases the hero's attack skill by %{count}." msgstr "%{name} trage cu puterea de %{count} arcaÈ™i" msgid "Ultimate Sword of Dominion" msgstr "" #, fuzzy msgid "The %{name} increases the hero's defense skill by %{count}." msgstr "%{name} trage cu puterea de %{count} arcaÈ™i" msgid "Ultimate Cloak of Protection" msgstr "" #, fuzzy msgid "The %{name} increases the hero's spell power by %{count}." msgstr "%{name} trage cu puterea de %{count} arcaÈ™i" msgid "Ultimate Wand of Magic" msgstr "" msgid "" "The %{name} increases the hero's attack and defense skills by %{count} each." msgstr "" msgid "Ultimate Shield" msgstr "" #, fuzzy msgid "" "The %{name} increases the hero's spell power and knowledge by %{count} each." msgstr "%{name} trage cu puterea de %{count} arcaÈ™i" msgid "Ultimate Staff" msgstr "" msgid "" "The %{name} increases each of the hero's basic skills by %{count} points." msgstr "" msgid "Ultimate Crown" msgstr "" msgid "Golden Goose" msgstr "" msgid "The %{name} brings in an income of %{count} gold per day." msgstr "" msgid "Arcane Necklace of Magic" msgstr "" msgid "" "After rescuing a Sorceress from a cursed tomb, she rewards your heroism with " "an exquisite jeweled necklace." msgstr "" msgid "Caster's Bracelet of Magic" msgstr "" msgid "" "While searching through the rubble of a caved-in mine, you free a group of " "trapped Dwarves. Grateful, the leader gives you a golden bracelet." msgstr "" msgid "Mage's Ring of Power" msgstr "" msgid "" "A cry of pain leads you to a Centaur, caught in a trap. Upon setting the " "creature free, he hands you a small pouch. Emptying the contents, you find a " "dazzling jeweled ring." msgstr "" msgid "Witch's Broach of Magic" msgstr "" msgid "" "Alongside the remains of a burnt witch lies a beautiful broach, intricately " "designed. Approaching the corpse with caution, you add the broach to your " "inventory." msgstr "" msgid "Medal of Valor" msgstr "" #, fuzzy msgid "The %{name} increases the morale of the hero's army by %{count}." msgstr "%{name} trage cu puterea de %{count} arcaÈ™i" msgid "" "Freeing a virtuous maiden from the clutches of an evil overlord, you are " "granted a Medal of Valor by the King's herald." msgstr "" msgid "Medal of Courage" msgstr "" msgid "" "After saving a young boy from a vicious pack of Wolves, you return him to " "his father's manor. The grateful nobleman awards you with a Medal of Courage." msgstr "" msgid "Medal of Honor" msgstr "" msgid "" "After freeing a princess of a neighboring kingdom from the evil clutches of " "despicable slavers, she awards you with a Medal of Honor." msgstr "" msgid "Medal of Distinction" msgstr "" msgid "" "Ridding the countryside of the hideous Minotaur who made a sport of eating " "noblemen's Knights, you are honored with the Medal of Distinction." msgstr "" msgid "Fizbin of Misfortune" msgstr "" msgid "" "The %{name} greatly decreases the morale of the hero's army by %{count}." msgstr "" msgid "" "You stumble upon a medal lying alongside the empty road. Adding the medal to " "your inventory, you become aware that you have acquired the undesirable " "Fizbin of Misfortune, greatly decreasing your army's morale." msgstr "" msgid "Thunder Mace of Dominion" msgstr "" msgid "" "During a sudden storm, a bolt of lightning strikes a tree, splitting it. " "Inside the tree you find a mysterious mace." msgstr "" msgid "Armored Gauntlets of Protection" msgstr "" #, fuzzy msgid "The %{name} increase the hero's defense skill by %{count}." msgstr "%{name} trage cu puterea de %{count} arcaÈ™i" msgid "" "You encounter the infamous Black Knight! After a grueling duel ending in a " "draw, the Knight, out of respect, offers you a pair of armored gauntlets." msgstr "" msgid "Defender Helm of Protection" msgstr "" msgid "" "A glint of golden light catches your eye. Upon further investigation, you " "find a golden helm hidden under a bush." msgstr "" msgid "Giant Flail of Dominion" msgstr "" msgid "" "A clumsy Giant has killed himself with his own flail. Knowing your superior " "skill with this weapon, you confidently remove the spectacular flail from " "the fallen Giant." msgstr "" msgid "Ballista of Quickness" msgstr "" msgid "The %{name} gives the hero's catapult one extra shot per combat round." msgstr "" msgid "" "Walking through the ruins of an ancient walled city, you find the instrument " "of the city's destruction, an elaborately crafted ballista." msgstr "" msgid "Stealth Shield of Protection" msgstr "" msgid "" "A stone statue of a warrior holds a silver shield. As you remove the shield, " "the statue crumbles into dust." msgstr "" msgid "Dragon Sword of Dominion" msgstr "" msgid "" "As you are walking along a narrow path, a nearby bush suddenly bursts into " "flames. Before your eyes the flames become the image of a beautiful woman. " "She holds out a magnificent sword to you." msgstr "" msgid "Power Axe of Dominion" msgstr "" msgid "" "You see a silver axe embedded deeply in the ground. After several " "unsuccessful attempts by your army to remove the axe, you tightly grip the " "handle of the axe and effortlessly pull it free." msgstr "" msgid "Divine Breastplate of Protection" msgstr "" msgid "" "A gang of Rogues is sifting through the possessions of dead warriors. " "Scaring off the scavengers, you note the Rogues had overlooked a beautiful " "breastplate." msgstr "" msgid "Minor Scroll of Knowledge" msgstr "" msgid "" "Before you appears a levitating glass case with a scroll, perched upon a bed " "of crimson velvet. At your touch, the lid opens and the scroll floats into " "your awaiting hands." msgstr "" msgid "Major Scroll of Knowledge" msgstr "" msgid "" "Visiting a local wiseman, you explain the intent of your journey. He reaches " "into a sack and withdraws a yellowed scroll and hands it to you." msgstr "" msgid "Superior Scroll of Knowledge" msgstr "" msgid "" "You come across the remains of an ancient Druid. Bones, yellowed with age, " "peer from the ragged folds of her robe. Searching the robe, you discover a " "scroll hidden in the folds." msgstr "" msgid "Foremost Scroll of Knowledge" msgstr "" msgid "" "Mangled bones, yellowed with age, peer from the ragged folds of a dead " "Druid's robe. Searching the robe, you discover a scroll hidden within." msgstr "" msgid "Endless Sack of Gold" msgstr "" #, fuzzy msgid "The %{name} provides the hero with %{count} gold per day." msgstr "%{name} trage cu puterea de %{count} arcaÈ™i" msgid "" "A little leprechaun dances gleefully around a magic sack. Seeing you " "approach, he stops in mid-stride. The little man screams and stamps his foot " "ferociously, vanishing into thin air. Remembering the old leprechaun saying " "'Finders Keepers', you grab the sack and leave." msgstr "" msgid "Endless Bag of Gold" msgstr "" msgid "" "A noblewoman, separated from her traveling companions, asks for your help. " "After escorting her home, she rewards you with a bag filled with gold." msgstr "" msgid "Endless Purse of Gold" msgstr "" msgid "" "In your travels, you find a leather purse filled with gold that once " "belonged to a great warrior king who had the ability to transform any " "inanimate object into gold." msgstr "" msgid "Nomad Boots of Mobility" msgstr "" msgid "The %{name} increase the hero's movement on land." msgstr "" msgid "" "A Nomad trader seeks protection from a tribe of Goblins. For your " "assistance, he gives you a finely crafted pair of boots made from the " "softest leather. Looking closely, you see fascinating ancient carvings " "engraved on the leather." msgstr "" msgid "Traveler's Boots of Mobility" msgstr "" msgid "" "Discovering a pair of beautifully beaded boots made from the finest and " "softest leather, you thank the anonymous donor and add the boots to your " "inventory." msgstr "" msgid "Lucky Rabbit's Foot" msgstr "" #, fuzzy msgid "The %{name} increases the luck of the hero's army by %{count}." msgstr "%{name} trage cu puterea de %{count} arcaÈ™i" msgid "" "A traveling merchant offers you a rabbit's foot, made of gleaming silver " "fur, for safe passage. The merchant explains the charm will increase your " "luck in combat." msgstr "" msgid "Golden Horseshoe" msgstr "" msgid "" "An ensnared Unicorn whinnies in fright. Murmuring soothing words, you set " "her free. Snorting and stamping her front hoof once, she gallops off. " "Looking down you see a golden horseshoe." msgstr "" msgid "Gambler's Lucky Coin" msgstr "" msgid "" "You have captured a mischievous imp who has been terrorizing the region. In " "exchange for his release, he rewards you with a magical coin." msgstr "" msgid "Four-Leaf Clover" msgstr "" msgid "" "In the middle of a patch of dead and dry vegetation, to your surprise you " "find a healthy green four-leaf clover." msgstr "" msgid "True Compass of Mobility" msgstr "" msgid "The %{name} increases the hero's movement on land and sea." msgstr "" msgid "" "An old man claiming to be an inventor asks you to try his latest invention. " "He then hands you a compass." msgstr "" msgid "Sailor's Astrolabe of Mobility" msgstr "" msgid "The %{name} increases the hero's movement on sea." msgstr "" msgid "" "An old sea captain is being tortured by Ogres. You save him, and in return " "he rewards you with a wondrous instrument to measure the distance of a star." msgstr "" msgid "Evil Eye" msgstr "" msgid "The %{name} reduces the casting cost of curse spells by half." msgstr "" msgid "" "While venturing into a decrepit hut you find the Skeleton of a long dead " "witch. Investigation of the remains reveals a glass eye rolling around " "inside an empty skull." msgstr "" msgid "Enchanted Hourglass" msgstr "" msgid "" "The %{name} extends the duration of all the hero's spells by %{count} turns." msgstr "" msgid "" "A surprise turn in the landscape finds you in the midst of a grisly scene: " "Vultures picking at the aftermath of a terrible battle. Your cursory search " "of the remains turns up an enchanted hourglass." msgstr "" msgid "Gold Watch" msgstr "" msgid "The %{name} doubles the effectiveness of the hero's hypnotize spells." msgstr "" msgid "" "In reward for helping his cart out of a ditch, a traveling potion salesman " "gives you a \"magic\" gold watch. Unbeknownst to him, the watch really is " "magical." msgstr "" msgid "Skullcap" msgstr "" msgid "The %{name} halves the casting cost of all mind influencing spells." msgstr "" msgid "" "A brief stop at an improbable rural inn yields an exchange of money, tales, " "and accidentally, luggage. You find a magical skullcap in your new backpack." msgstr "" msgid "Ice Cloak" msgstr "" msgid "" "The %{name} halves all damage the hero's troops receive from cold spells." msgstr "" msgid "" "Responding to the panicked cries of a damsel in distress, you discover a " "young woman fleeing from a hungry bear. You slay the beast in the nick of " "time, and the grateful Sorceress weaves a magic cloak from the bear's hide." msgstr "" msgid "Fire Cloak" msgstr "" msgid "" "The %{name} halves all damage the hero's troops receive from fire spells." msgstr "" msgid "" "You've come upon a fight between a Necromancer and a Paladin. The " "Necromancer blasts the Paladin with a fire bolt, bringing him to his knees. " "Acting quickly, you slay the evil one before the final blow. The grateful " "Paladin gives you the fire cloak that saved him." msgstr "" msgid "Lightning Helm" msgstr "" msgid "" "The %{name} halves all damage the hero's troops receive from lightning " "spells." msgstr "" msgid "" "A traveling tinker in need of supplies offers you a helm with a thunderbolt " "design on its top in exchange for food and water. Curious, you accept, and " "later find out that the helm is magical." msgstr "" msgid "Evercold Icicle" msgstr "" msgid "" "The %{name} causes the hero's cold spells to do %{count} percent more damage " "to enemy troops." msgstr "" msgid "" "An icicle withstanding the full heat of the noonday sun attracts your " "attention. Intrigued, you break it off, and find that it does not melt in " "your hand." msgstr "" msgid "Everhot Lava Rock" msgstr "" msgid "" "The %{name} causes the hero's fire spells to do %{count} percent more damage " "to enemy troops." msgstr "" msgid "" "Your wanderings bring you into contact with a tribe of ape-like beings using " "a magical lava rock that never cools to light their fires. You take pity on " "them and teach them to make fire with sticks. Believing you to be a god, the " "apes give you their rock." msgstr "" msgid "Lightning Rod" msgstr "" msgid "" "The %{name} causes the hero's lightning spells to do %{count} percent more " "damage to enemy troops." msgstr "" msgid "" "While waiting out a storm, a lighting bolt strikes a nearby cottage's " "lightning rod, which melts and falls to the ground. The tip of the rod, " "however, survives intact and makes your hair stand on end when you touch it. " "Hmm..." msgstr "" msgid "Snake-Ring" msgstr "" msgid "The %{name} halves the casting cost of all of the hero's bless spells." msgstr "" msgid "" "You've found an oddly shaped ring on the finger of a long dead traveler. The " "ring looks like a snake biting its own tail." msgstr "" msgid "Ankh" msgstr "" msgid "" "The %{name} doubles the effectiveness of all of the hero's resurrect and " "animate spells." msgstr "" msgid "" "A fierce windstorm reveals the entrance to a buried tomb. Your investigation " "reveals that the tomb has already been looted, but the thieves overlooked an " "ankh on a silver chain in the dark." msgstr "" msgid "Book of Elements" msgstr "" msgid "" "The %{name} doubles the effectiveness of all of the hero's summoning spells." msgstr "" msgid "" "You come across a conjurer who begs to accompany you and your army awhile " "for safety. You agree, and he offers as payment a copy of the book of the " "elements." msgstr "" msgid "Elemental Ring" msgstr "" msgid "The %{name} halves the casting cost of all summoning spells." msgstr "" msgid "" "While pausing to rest, you notice a bobcat climbing a short tree to get at a " "crow's nest. On impulse, you climb the tree yourself and scare off the cat. " "When you look in the nest, you find a collection of shiny stones and a ring." msgstr "" msgid "Holy Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to curse spells." msgstr "" msgid "" "In your wanderings you come across a hermit living in a small, tidy hut. " "Impressed with your mission, he takes time out from his meditations to bless " "and give you a charm against curses." msgstr "" msgid "Pendant of Free Will" msgstr "" msgid "The %{name} makes all of the hero's troops immune to hypnotize spells." msgstr "" msgid "" "Responding to cries for help, you find river Sprites making a sport of " "dunking an old man. Feeling vengeful, you rescue the man and drag a Sprite " "onto dry land for awhile. The Sprite, uncomfortable in the air, gives you a " "magic pendant to let him go." msgstr "" msgid "Pendant of Life" msgstr "" msgid "The %{name} makes all of the hero's troops immune to death spells." msgstr "" msgid "" "A brief roadside encounter with a small caravan and a game of knucklebones " "wins a magic pendant. Its former owner says that it protects from " "Necromancers' death spells." msgstr "" msgid "Serenity Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to berserk spells." msgstr "" msgid "" "The sounds of combat draw you to the scene of a fight between an old " "Barbarian and an eight-headed Hydra. Your timely intervention swings the " "battle in favor of the man, and he rewards you with a pendant he used to use " "to calm his mind for battle." msgstr "" msgid "Seeing-eye Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to blindness spells." msgstr "" msgid "" "You come upon a very old woman, long blind from cataracts and dying alone. " "You tend to her final needs and promise a proper burial. Grateful, she gives " "you a magic pendant emblazoned with a stylized eye. It lets you see with " "your eyes closed." msgstr "" msgid "Kinetic Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to paralyze spells." msgstr "" msgid "" "You come across a golem wearing a glowing pendant and blocking your way. " "Acting on a hunch, you cut the pendant from its neck. Deprived of its power " "source, the golem breaks down, leaving you with the magical pendant." msgstr "" msgid "Pendant of Death" msgstr "" msgid "The %{name} makes all of the hero's troops immune to holy spells." msgstr "" msgid "" "A quick and deadly battle with a Necromancer wins you his magical pendant. " "Later, a Wizard tells you that the pendant protects undead under your " "control from holy word spells." msgstr "" msgid "Wand of Negation" msgstr "" msgid "" "The %{name} makes all of the hero's troops immune to dispel magic spells." msgstr "" msgid "" "You meet an old Wizard friend of yours traveling in the opposite direction. " "He presents you with a gift: A wand that prevents the use of the dispel " "magic spell on your allies." msgstr "" msgid "Golden Bow" msgstr "" msgid "" "The %{name} eliminates the %{count} percent penalty for the hero's troops " "shooting past obstacles (e.g. castle walls)." msgstr "" msgid "" "A chance meeting with a famous Archer finds you in a game of knucklebones " "pitting his bow against your horse. You win." msgstr "" msgid "Telescope" msgstr "" msgid "" "The %{name} increases the amount of terrain the hero reveals when " "adventuring by %{count} extra square." msgstr "" msgid "" "A merchant from far away lands trades you a new invention of his people for " "traveling supplies. It makes distant objects appear closer, and he calls " "it...\n" "\n" "a telescope." msgstr "" msgid "Statesman's Quill" msgstr "" msgid "" "The %{name} reduces the cost of surrender to %{count} percent of the total " "cost of troops the hero has in their army." msgstr "" msgid "" "You pause to help a diplomat with a broken axle fix his problem. In " "gratitude, he gives you a writing quill with magical properties which he " "says will \"help people see things your way\"." msgstr "" msgid "Wizard's Hat" msgstr "" #, fuzzy msgid "" "The %{name} increases the duration of the hero's spells by %{count} turns." msgstr "%{name} trage cu puterea de %{count} arcaÈ™i" msgid "" "You see a Wizard fleeing from a Griffin and riding like the wind. The Wizard " "opens a portal and rides through, getting his hat knocked off by the edge of " "the gate. The Griffin follows; the gate closes. You pick the hat up, dust it " "off, and put it on." msgstr "" msgid "Power Ring" msgstr "" msgid "The %{name} returns %{count} extra spell points per day to the hero." msgstr "" msgid "" "You find a small tree that closely resembles the great Warlock Carnauth with " "a ring around one of its twigs. Scraps of clothing and rotting leather lead " "you to suspect that it IS Carnauth, transformed. Since you can't help him, " "you take the magic ring." msgstr "" msgid "Ammo Cart" msgstr "" msgid "" "The %{name} provides endless ammunition for all of the hero's troops that " "shoot." msgstr "" msgid "" "An ammunition cart in the middle of an old battlefield catches your eye. " "Inspection shows it to be in good working order, so you take it along." msgstr "" msgid "Tax Lien" msgstr "" #, fuzzy msgid "The %{name} costs the hero %{count} gold pieces per day." msgstr "%{name} trage cu puterea de %{count} arcaÈ™i" msgid "" "Your big spending habits have earned you a massive tax bill that you can't " "hope to pay. The tax man takes pity and agrees to only take 250 gold a day " "from your account for life. Check here if you want one dollar to go to the " "presidential campaign election fund." msgstr "" msgid "Hideous Mask" msgstr "" msgid "The %{name} prevents all 'wandering' armies from joining the hero." msgstr "" msgid "" "Your looting of the grave of Sinfilas Gardolad, the famous shapeshifting " "Warlock, unearths his fabled mask. Trembling, you put it on and it twists " "your visage into an awful grimace! Oh no! It's actually the hideous mask of " "Gromluck Greene, and you are stuck with it." msgstr "" msgid "Endless Pouch of Sulfur" msgstr "" msgid "The %{name} provides %{count} unit of sulfur per day." msgstr "" msgid "" "You visit an alchemist who, upon seeing your army, is swayed by the " "righteousness of your cause. The newly loyal subject gives you his endless " "pouch of sulfur to help with the war effort." msgstr "" msgid "Endless Vial of Mercury" msgstr "" msgid "The %{name} provides %{count} unit of mercury per day." msgstr "" msgid "" "A brief stop at a hastily abandoned Wizard's tower turns up a magical vial " "of mercury that always has a little left on the bottom. Recognizing a " "treasure when you see one, you cap it and slip it in your pocket." msgstr "" msgid "Endless Pouch of Gems" msgstr "" msgid "The %{name} provides %{count} unit of gems per day." msgstr "" msgid "" "A short rainstorm brings forth a rainbow...and you can see the end of it. " "Riding quickly, you seize the pot of gold you find there. The leprechaun who " "owns it, unable to stop you from taking it, offers an endless pouch of gems " "for the return of his gold. You accept." msgstr "" msgid "Endless Cord of Wood" msgstr "" msgid "The %{name} provides %{count} unit of wood per day." msgstr "" msgid "" "Pausing to rest and light a cook fire, you pull wood out of a nearby pile of " "dead wood. As you keep pulling wood from the pile, you notice that it " "doesn't shrink. You realize to your delight that the wood is enchanted, so " "you take it along." msgstr "" msgid "Endless Cart of Ore" msgstr "" msgid "The %{name} provides %{count} unit of ore per day." msgstr "" msgid "" "You've found a Goblin weapon smithy making weapons for use against humans. " "With a tremendous yell you and your army descend upon their camp and drive " "them away. A search finds a magic ore cart that never runs out of iron." msgstr "" msgid "Endless Pouch of Crystal" msgstr "" msgid "The %{name} provides %{count} unit of crystal per day." msgstr "" msgid "" "Taking shelter from a storm in a small cave, you notice a small patch of " "crystal in one corner. Curious, you break a piece off and notice that the " "original crystal grows the lost piece back. You decide to stuff the entire " "patch into a pouch and take it with you." msgstr "" msgid "Spiked Helm" msgstr "" msgid "" "Your army is ambushed by a small tribe of wild (and none too bright) Orcs. " "You fend them off easily and the survivors flee in all directions. One of " "the Orcs was wearing a polished spiked helm. Figuring it will make a good " "souvenir, you take it." msgstr "" msgid "Spiked Shield" msgstr "" msgid "" "You come upon a bridge spanning a dry gully. Before you can cross, a Troll " "steps out from under the bridge and demands payment before it will permit " "you to pass. You refuse, and the Troll charges, forcing you to slay it. You " "take its spiked shield as a trophy." msgstr "" msgid "White Pearl" msgstr "" msgid "" "A walk across a dry saltwater lake bed yields an unlikely prize: A white " "pearl amidst shattered shells and debris." msgstr "" msgid "Black Pearl" msgstr "" msgid "" "Rumors of a Griffin of unusual size preying upon the countryside lead you to " "its cave lair. A quick, brutal fight dispatches the beast, and a search of " "its foul nest turns up a huge black pearl." msgstr "" msgid "Magic Book" msgstr "" #, fuzzy msgid "The %{name} enables the hero to cast spells." msgstr "%{name} rezistă la vrajă!" msgid "Dummy 1" msgstr "" msgid "The reserved artifact." msgstr "" msgid "Dummy 2" msgstr "" msgid "Dummy 3" msgstr "" msgid "Dummy 4" msgstr "" msgid "Spell Scroll" msgstr "" msgid "" "This %{name} gives the hero the ability to cast the %{spell} spell if the " "hero has a Magic Book." msgstr "" msgid "" "You find an elaborate container which houses an old vellum scroll. The runes " "on the container are very old, and the artistry with which it was put " "together is stunning. As you pull the scroll out, you feel imbued with " "magical power." msgstr "" msgid "Arm of the Martyr" msgstr "" msgid "" "The %{name} increases the hero's spell power by %{count} but adds the undead " "morale penalty." msgstr "" msgid "" "One of the less intelligent members of your party picks up an arm off of the " "ground. Despite its missing a body, it is still moving. Your troops find the " "dismembered arm repulsive, but you cannot bring yourself to drop it: it " "seems to hold some sort of magical power that influences your decision " "making." msgstr "" msgid "Breastplate of Anduran" msgstr "" #, fuzzy msgid "The %{name} increases the hero's defense by %{count}." msgstr "%{name} trage cu puterea de %{count} arcaÈ™i" msgid "" "You come upon a sign. It reads: \"Here lies the body of Anduran. Bow and " "swear fealty, and you shall be rewarded.\" You decide to do as it says. As " "you stand up, you feel a coldness against your skin. Looking down, you find " "that you are suddenly wearing a gleaming, ornate breastplate." msgstr "" msgid "Broach of Shielding" msgstr "" msgid "" "The %{name} provides %{count} percent protection from Armageddon and " "Elemental Storm, but decreases spell power by 2." msgstr "" msgid "" "A kindly Sorceress thinks that your army's defenses could use a magical " "boost. She offers to enchant the Broach that you wear on your cloak, and you " "accept." msgstr "" msgid "Battle Garb of Anduran" msgstr "" msgid "" "The %{name} combines the powers of the three Anduran artifacts. It provides " "maximum luck and morale for the hero's troops and gives the hero the Town " "Portal spell." msgstr "" msgid "" "Out of pity for a poor peasant, you purchase a chest of old junk they are " "hawking for too much gold. Later, as you search through it, you find it " "contains the 3 pieces of the legendary battle garb of Anduran!" msgstr "" msgid "Crystal Ball" msgstr "" msgid "" "The %{name} lets the hero get more specific information about monsters, " "enemy heroes, and castles nearby the hero." msgstr "" msgid "" "You come upon a caravan of gypsies who are feasting and fortifying their " "bodies with mead. They call you forward and say \"If you prove that you can " "dance the Rama-Buta, we will reward you.\" You don't know it, but try " "anyway. They laugh hysterically, but admire your bravery, giving you a " "Crystal Ball." msgstr "" msgid "Heart of Fire" msgstr "" msgid "" "The %{name} provides %{count} percent protection from fire, but doubles the " "damage taken from cold." msgstr "" msgid "" "You enter a recently burned glade and come upon a Fire Elemental sitting " "atop a rock. It looks up, its flaming face contorted in a look of severe " "pain. It then tosses a glowing object at you. You put up your hands to block " "it, but it passes right through them and sears itself into your chest." msgstr "" msgid "Heart of Ice" msgstr "" msgid "" "The %{name} provides %{count} percent protection from cold, but doubles the " "damage taken from fire." msgstr "" msgid "" "Suddenly, a biting coldness engulfs your body. You seize up, falling from " "your horse. The pain subsides, but you still feel as if your chest is " "frozen. As you pick yourself up off of the ground, you hear hearty laughter. " "You turn around just in time to see a Frost Giant run off into the woods and " "disappear." msgstr "" msgid "Helmet of Anduran" msgstr "" msgid "" "You spy a gleaming object poking up out of the ground. You send a member of " "your party over to investigate. He comes back with a golden helmet in his " "hands. You realize that it must be the helmet of the legendary Anduran, the " "only man who was known to wear solid gold armor." msgstr "" msgid "Holy Hammer" msgstr "" msgid "" "You come upon a battle where a Paladin has been mortally wounded by a group " "of Zombies. He asks you to take his hammer and finish what he started. As " "you pick it up, it begins to hum, and then everything becomes a blur. The " "Zombies lie dead, the hammer dripping with blood. You strap it to your belt." msgstr "" msgid "Legendary Scepter" msgstr "" msgid "The %{name} adds %{count} points to all attributes." msgstr "" msgid "" "Upon cresting a small hill, you come upon a ridiculous looking sight. A " "Sprite is attempting to carry a Scepter that is almost as big as it is. " "Trying not to laugh, you ask, \"Need help?\" The Sprite glares at you and " "answers: \"You think this is funny? Fine. You can carry it. I much prefer " "flying anyway.\"" msgstr "" msgid "" "An old seaman tells you a tale of an enchanted masthead that he used in his " "youth to rally his crew during times of trouble. He then hands you a faded " "map that shows where he hid it. After much exploring, you find it stashed " "underneath a nearby dock." msgstr "" msgid "Masthead" msgstr "" msgid "" "The %{name} boosts the hero's troops' luck and morale by %{count} each in " "sea combat." msgstr "" msgid "Sphere of Negation" msgstr "" msgid "The %{name} disables all spell casting, for both sides, in combat." msgstr "" msgid "" "You stop to help a Peasant catch a runaway mare. To show his gratitude, he " "hands you a tiny sphere. As soon as you grasp it, you feel the magical " "energy drain from your limbs..." msgstr "" msgid "Staff of Wizardry" msgstr "" #, fuzzy msgid "The %{name} boosts the hero's spell power by %{count}." msgstr "%{name} trage cu puterea de %{count} arcaÈ™i" msgid "" "While out scaring up game, your troops find a mysterious staff levitating " "about three feet off of the ground. They hand it to you, and you notice an " "inscription. It reads: \"Brains best brawn and magic beats might. Heed my " "words, and you'll win every fight.\"" msgstr "" msgid "Sword Breaker" msgstr "" msgid "The %{name} increases the hero's defense by %{count} and attack by 1." msgstr "" msgid "" "A former Captain of the Guard admires your quest and gives you the enchanted " "Sword Breaker that he relied on during his tour of duty." msgstr "" msgid "Sword of Anduran" msgstr "" msgid "" "A Troll stops you and says: \"Pay me 5,000 gold, or the Sword of Anduran " "will slay you where you stand.\" You refuse. The troll grabs the sword " "hanging from its belt, screams in pain, and runs away. Picking up the fabled " "sword, you give thanks that half-witted Trolls tend to grab the wrong end of " "sharp objects." msgstr "" msgid "Spade of Necromancy" msgstr "" msgid "The %{name} gives the hero increased necromancy skill." msgstr "" msgid "" "A dirty shovel has been thrust into a dirt mound nearby. Upon investigation, " "you discover it to be the enchanted shovel of the Gravediggers, long thought " "lost by mortals." msgstr "" msgid "spellBonus|selected by user" msgstr "" msgid "Wood" msgstr "" msgid "Mercury" msgstr "" msgid "Ore" msgstr "" msgid "Sulfur" msgstr "" msgid "Crystal" msgstr "" msgid "Gems" msgstr "" msgid "Gold" msgstr "" msgid "" "There are seven resources in Heroes 2, used to build and improves castles, " "purchase troops and recruit heroes. Gold is the most common, required for " "virtually everything. Wood and ore are used for most buildings. Gems, " "Mercury, Sulfur and Crystal are rare magical resources used for the most " "powerful creatures and buildings." msgstr "" msgid "" "Causes a giant fireball to strike the selected area, damaging all nearby " "creatures." msgstr "" msgid "Fireball" msgstr "" msgid "Fireblast" msgstr "" msgid "" "An improved version of fireball, fireblast affects two hexes around the " "center point of the spell, rather than one." msgstr "" msgid "Causes a bolt of electrical energy to strike the selected creature." msgstr "" msgid "Lightning Bolt" msgstr "" msgid "Chain Lightning" msgstr "" msgid "" "Causes a bolt of electrical energy to strike a selected creature, then " "strike the nearest creature with half damage, then strike the NEXT nearest " "creature with half again damage, and so on, until it becomes too weak to be " "harmful. Warning: This spell can hit your own creatures!" msgstr "" msgid "Teleport" msgstr "" msgid "" "Teleports the creature you select to any open position on the battlefield." msgstr "" msgid "Cure" msgstr "" msgid "" "Removes all negative spells cast upon one of your units, and restores up to " "%{count} HP per level of spell power." msgstr "" msgid "Mass Cure" msgstr "" msgid "" "Removes all negative spells cast upon your forces, and restores up to " "%{count} HP per level of spell power, per creature." msgstr "" msgid "Resurrect" msgstr "" msgid "Resurrects creatures from a damaged or dead unit until end of combat." msgstr "" msgid "Resurrect True" msgstr "" msgid "Resurrects creatures from a damaged or dead unit permanently." msgstr "" msgid "Haste" msgstr "" msgid "Increases the speed of any creature by %{count}." msgstr "" msgid "Increases the speed of all of your creatures by %{count}." msgstr "" msgid "Mass Haste" msgstr "" msgid "Slows target to half movement rate." msgstr "" msgid "spell|Slow" msgstr "" msgid "Mass Slow" msgstr "" msgid "Slows all enemies to half movement rate." msgstr "" msgid "Clouds the affected creatures' eyes, preventing them from moving." msgstr "" msgid "spell|Blind" msgstr "" msgid "Bless" msgstr "" msgid "Causes the selected creatures to inflict maximum damage." msgstr "" msgid "Causes all of your units to inflict maximum damage." msgstr "" msgid "Mass Bless" msgstr "" msgid "Magically increases the defense skill of the selected creatures." msgstr "" msgid "Stoneskin" msgstr "" msgid "" "Increases the defense skill of the targeted creatures. This is an improved " "version of Stoneskin." msgstr "" msgid "Steelskin" msgstr "" msgid "Causes the selected creatures to inflict minimum damage." msgstr "" msgid "Curse" msgstr "" msgid "Causes all enemy troops to inflict minimum damage." msgstr "" msgid "Mass Curse" msgstr "" msgid "Damages all undead in the battle." msgstr "" msgid "Holy Word" msgstr "" msgid "" "Damages all undead in the battle. This is an improved version of Holy Word." msgstr "" msgid "Holy Shout" msgstr "" msgid "Anti-Magic" msgstr "" msgid "Prevents harmful magic against the selected creatures." msgstr "" msgid "Dispel Magic" msgstr "" msgid "Removes all magic spells from a single target." msgstr "" msgid "Mass Dispel" msgstr "" msgid "Removes all magic spells from all creatures." msgstr "" msgid "Causes a magic arrow to strike the selected target." msgstr "" msgid "Magic Arrow" msgstr "" msgid "Berserker" msgstr "" msgid "Causes a creature to attack its nearest neighbor." msgstr "" msgid "Armageddon" msgstr "" msgid "" "Holy terror strikes the battlefield, causing severe damage to all creatures." msgstr "" msgid "Elemental Storm" msgstr "" msgid "Magical elements pour down on the battlefield, damaging all creatures." msgstr "" msgid "" "A rain of rocks strikes an area of the battlefield, damaging all nearby " "creatures." msgstr "" msgid "Meteor Shower" msgstr "" msgid "Paralyze" msgstr "" msgid "The targeted creatures are paralyzed, unable to move or retaliate." msgstr "" msgid "Hypnotize" msgstr "" msgid "" "Brings a single enemy unit under your control if its hits are less than " "%{count} times the caster's spell power." msgstr "" msgid "Cold Ray" msgstr "" msgid "Drains body heat from a single enemy unit." msgstr "" msgid "Cold Ring" msgstr "" msgid "" "Drains body heat from all units surrounding the center point, but not " "including the center point." msgstr "" msgid "Disrupting Ray" msgstr "" msgid "Reduces the defense rating of an enemy unit by three." msgstr "" msgid "Damages all living (non-undead) units in the battle." msgstr "" msgid "Death Ripple" msgstr "" msgid "Death Wave" msgstr "" msgid "" "Damages all living (non-undead) units in the battle. This spell is an " "improved version of Death Ripple." msgstr "" msgid "Dragon Slayer" msgstr "" msgid "Greatly increases a unit's attack skill vs. Dragons." msgstr "" msgid "Blood Lust" msgstr "" msgid "Increases a unit's attack skill." msgstr "" msgid "Animate Dead" msgstr "" msgid "Resurrects creatures from a damaged or dead undead unit permanently." msgstr "" msgid "Mirror Image" msgstr "" msgid "" "Creates an illusionary unit that duplicates one of your existing units. This " "illusionary unit does the same damages as the original, but will vanish if " "it takes any damage." msgstr "" msgid "Shield" msgstr "" msgid "" "Halves damage received from ranged attacks for a single unit. Does not " "affect damage received from Turrets or Ballistae." msgstr "" msgid "Mass Shield" msgstr "" msgid "" "Halves damage received from ranged attacks for all of your units. Does not " "affect damage received from Turrets or Ballistae." msgstr "" msgid "Summon Earth Elemental" msgstr "" msgid "Summons Earth Elementals to fight for your army." msgstr "" msgid "Summon Air Elemental" msgstr "" msgid "Summons Air Elementals to fight for your army." msgstr "" msgid "Summon Fire Elemental" msgstr "" msgid "Summons Fire Elementals to fight for your army." msgstr "" msgid "Summon Water Elemental" msgstr "" msgid "Summons Water Elementals to fight for your army." msgstr "" msgid "Damages castle walls." msgstr "" msgid "Earthquake" msgstr "" msgid "Causes all mines across the land to become visible." msgstr "" msgid "View Mines" msgstr "" msgid "Causes all resources across the land to become visible." msgstr "" msgid "View Resources" msgstr "" msgid "Causes all artifacts across the land to become visible." msgstr "" msgid "View Artifacts" msgstr "" msgid "Causes all towns and castles across the land to become visible." msgstr "" msgid "View Towns" msgstr "" msgid "Causes all Heroes across the land to become visible." msgstr "" msgid "View Heroes" msgstr "" msgid "Causes the entire land to become visible." msgstr "" msgid "View All" msgstr "" msgid "Allows the caster to view detailed information on enemy Heroes." msgstr "" msgid "Summon Boat" msgstr "" msgid "" "Summons the nearest unoccupied, friendly boat to an adjacent shore location. " "A friendly boat is one which you just built or were the most recent player " "to occupy." msgstr "" msgid "Allows the caster to magically transport to a nearby location." msgstr "" msgid "Dimension Door" msgstr "" msgid "Returns the caster to any town or castle currently owned." msgstr "" msgid "Town Gate" msgstr "" msgid "" "Returns the hero to the town or castle of choice, provided it is controlled " "by you." msgstr "" msgid "Visions" msgstr "" msgid "" "Visions predicts the likely outcome of an encounter with a neutral army camp." msgstr "" msgid "Haunt" msgstr "" msgid "" "Haunts a mine you control with Ghosts. This mine stops producing resources. " "(If I can't keep it, nobody will!)" msgstr "" msgid "Set Earth Guardian" msgstr "" msgid "Sets Earth Elementals to guard a mine against enemy armies." msgstr "" msgid "Set Air Guardian" msgstr "" msgid "Sets Air Elementals to guard a mine against enemy armies." msgstr "" msgid "Set Fire Guardian" msgstr "" msgid "Sets Fire Elementals to guard a mine against enemy armies." msgstr "" msgid "Set Water Guardian" msgstr "" msgid "Sets Water Elementals to guard a mine against enemy armies." msgstr "" #, fuzzy msgid "Random Spell" msgstr "Aruncă vraja" msgid "Randomly selected spell of any level." msgstr "" msgid "Random 1st Level Spell" msgstr "" msgid "Randomly selected 1st level spell." msgstr "" msgid "Random 2nd Level Spell" msgstr "" msgid "Randomly selected 2nd level spell." msgstr "" msgid "Random 3rd Level Spell" msgstr "" msgid "Randomly selected 3rd level spell." msgstr "" msgid "Random 4th Level Spell" msgstr "" msgid "Randomly selected 4th level spell." msgstr "" msgid "Random 5th Level Spell" msgstr "" msgid "Randomly selected 5th level spell." msgstr "" msgid "Petrification" msgstr "" msgid "" "Turns the affected creature into stone. A petrified creature receives half " "damage from a direct attack." msgstr "" msgid "You have no Magic Book, so you cannot cast a spell." msgstr "" msgid "No spell to cast." msgstr "" msgid "Your hero has %{point} spell points remaining." msgstr "" msgid "View Adventure Spells" msgstr "" msgid "View Combat Spells" msgstr "" msgid "View previous page" msgstr "" msgid "View next page" msgstr "" msgid "Close Spellbook" msgstr "" msgid "View %{spell}" msgstr "" msgid "This spell does %{damage} points of damage." msgstr "" msgid "" "This spell summons\n" "%{count} %{monster}." msgstr "" msgid "This spell restores %{hp} HP." msgstr "" msgid "This spell summons %{count} %{monster} to guard the mine." msgstr "" msgid "The nearest town is %{town}." msgstr "" msgid "This town is occupied by your hero %{hero}." msgstr "" msgid "" "This spell controls up to\n" "%{hp} HP." msgstr "" msgid "The ultimate artifact is really the %{name}." msgstr "" msgid "The ultimate artifact may be found in the %{name} regions of the world." msgstr "" msgid "north-west" msgstr "" msgid "north" msgstr "" msgid "north-east" msgstr "" msgid "west" msgstr "" msgid "center" msgstr "" msgid "east" msgstr "" msgid "south-west" msgstr "" msgid "south" msgstr "" msgid "south-east" msgstr "" msgid "The truth is out there." msgstr "" msgid "The dark side is stronger." msgstr "" msgid "The end of the world is near." msgstr "" msgid "The bones of Lord Slayer are buried in the foundation of the arena." msgstr "" msgid "A Black Dragon will take out a Titan any day of the week." msgstr "" msgid "He told her: Yada yada yada... and then she said: Blah, blah, blah..." msgstr "" msgid "An unknown force is being resurrected..." msgstr "" msgid "" "Check the newest version of the game at\n" "https://github.com/ihhub/\n" "fheroes2/releases" msgstr "" #~ msgid "vs" #~ msgstr "contra" #~ msgid "%{race2} %{name2}" #~ msgstr "%{race2} %{name2}" fheroes2-1.0.12+dfsg/files/lang/ru.po000066400000000000000000013752221456075706000172660ustar00rootroot00000000000000# Russian translation of fheroes2 # Copyright (C) 2021 - 2023 fheroes2 team # Copyright (C) 2009 Igor Orlov # Copyright (C) 2009 Andrey Afletdinov # This file is distributed under the same license as the fheroes2 package. # msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-06 13:31+0000\n" "PO-Revision-Date: 2024-02-05 20:41+0300\n" "Last-Translator: fheroes2 team \n" "Language-Team: <>\n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Launchpad-Export-Date: 2013-10-09 02:01+0000\n" "X-Generator: Poedit 3.4.2\n" msgid "" "BATTLE\n" "ONLY" msgstr "БИТВÐ" msgid "" "NEW\n" "GAME" msgstr "" "ÐОВÐЯ\n" "ИГРÐ" msgid "" "SAVE\n" "GAME" msgstr "" " СОХРÐ-\n" "ÐИТЬ\n" "ИГРУ" msgid "" "LOAD\n" "GAME" msgstr "" " ЗÐГРУ-\n" "ЗИТЬ\n" "ИГРУ" msgid "INFO" msgstr "" " СЦЕÐÐ-\n" "РИЙ" msgid "QUIT" msgstr "ВЫЙТИ" msgid "CANCEL" msgstr "ОТМЕÐÐ" msgid "OKAY" msgstr "ОК" msgid "smallerButton|OKAY" msgstr "ОК" msgid "ACCEPT" msgstr "ПРИÐЯТЬ" msgid "DECLINE" msgstr "ОТКЛОÐИТЬ" msgid "LEARN" msgstr "ИЗУЧИТЬ" msgid "TRADE" msgstr "МЕÐЯТЬ" msgid "YES" msgstr "ДÐ" msgid "NO" msgstr "ÐЕТ" msgid "EXIT" msgstr "ВЫЙТИ" msgid "smallerButton|EXIT" msgstr "ВЫЙТИ" msgid "DISMISS" msgstr "УВОЛИТЬ" msgid "UPGRADE" msgstr "УЛУЧШИТЬ" msgid "RESTART" msgstr "СÐÐЧÐЛÐ" msgid "HEROES" msgstr "ГЕРОИ" msgid "" "TOWNS/\n" "CASTLES" msgstr "" "ГОРОДÐ/\n" "ЗÐМКИ" msgid "S" msgstr "М" msgid "M" msgstr "С" msgid "L" msgstr "Б" msgid "X-L" msgstr "О-Б" msgid "ALL" msgstr "ВСЕ" msgid "SELECT" msgstr "ВЫБОР" msgid "" "STANDARD\n" "GAME" msgstr "" "ОБЫЧÐÐЯ\n" "ИГРÐ" msgid "" "CAMPAIGN\n" "GAME" msgstr "КÐМПÐÐИЯ" msgid "" "MULTI-\n" "PLAYER\n" "GAME" msgstr "" "СОВМЕСТÐÐЯ\n" "ИГРÐ" msgid "CONFIG" msgstr "ÐÐСТРОЙКИ" msgid "" "ORIGINAL\n" "CAMPAIGN" msgstr "" "ВОЙÐЫ ЗÐ\n" "ПРЕСТОЛ" msgid "" "EXPANSION\n" "CAMPAIGN" msgstr "" "ЦЕÐÐ\n" "ВЕРÐОСТИ" msgid "HOT SEAT" msgstr "" "ЗРОДÐОЙ\n" "МÐШИÐОЙ" msgid "2 PLAYERS" msgstr "2 ИГРОКÐ" msgid "3 PLAYERS" msgstr "3 ИГРОКÐ" msgid "4 PLAYERS" msgstr "4 ИГРОКÐ" msgid "5 PLAYERS" msgstr "5 ИГРОКОВ" msgid "6 PLAYERS" msgstr "6 ИГРОКОВ" msgid "GIFT" msgstr "ОТДÐТЬ" msgid "MAX" msgstr "МÐКС" msgid "DIFFICULTY" msgstr "СЛОЖÐОСТЬ" msgid "VIEW INTRO" msgstr "ВСТУПЛЕÐИЕ" msgid "MIN" msgstr "МИÐ" msgid "RESET" msgstr "СБРОС" msgid "START" msgstr "ÐÐЧÐТЬ" msgid "CASTLE" msgstr "ЗÐМОК" msgid "TOWN" msgstr "ГОРОД" msgid "" "D\n" "I\n" "S\n" "M\n" "I\n" "S\n" "S" msgstr "" "У\n" "Ð’\n" "О\n" "Л\n" "И\n" "Т\n" "Ь" msgid "" "E\n" "X\n" "I\n" "T" msgstr "" "Ð’\n" "Ы\n" "Й\n" "Т\n" "И" msgid "" "C\n" "A\n" "M\n" "P\n" "A\n" "I\n" "G\n" "N" msgstr "" "К\n" "Ð\n" "М\n" "П\n" "Ð\n" "Ð\n" "И\n" "Я" msgid "" "S\n" "T\n" "A\n" "N\n" "D\n" "A\n" "R\n" "D" msgstr "" "О\n" "Б\n" "Ы\n" "Ч\n" "Ð\n" "Ð\n" "Я" msgid "Warrior" msgstr "Воин" msgid "Builder" msgstr "Строитель" msgid "Explorer" msgstr "Разведчик" msgid "None" msgstr "ОтÑутÑтвуют" msgid "" "A few\n" "%{monster}" msgstr "" "Мало\n" "%{monster}" msgid "" "Several\n" "%{monster}" msgstr "" "ÐеÑколько\n" "%{monster}" msgid "" "A pack of\n" "%{monster}" msgstr "" "Группа\n" "%{monster}" msgid "" "Lots of\n" "%{monster}" msgstr "" "Много\n" "%{monster}" msgid "" "A horde of\n" "%{monster}" msgstr "" "Орда\n" "%{monster}" msgid "" "A throng of\n" "%{monster}" msgstr "" "Сотни\n" "%{monster}" msgid "" "A swarm of\n" "%{monster}" msgstr "" "Туча\n" "%{monster}" msgid "" "Zounds...\n" "%{monster}" msgstr "" "Тьма\n" "%{monster}" msgid "" "A legion of\n" "%{monster}" msgstr "" "Легион\n" "%{monster}" msgid "army|Few" msgstr "Мало" msgid "army|Several" msgstr "ÐеÑколько" msgid "army|Pack" msgstr "Группа" msgid "army|Lots" msgstr "Много" msgid "army|Horde" msgstr "Орда" msgid "army|Throng" msgstr "Сотни" msgid "army|Swarm" msgstr "Туча" msgid "army|Zounds" msgstr "Тьма" msgid "army|Legion" msgstr "Легион" msgid "All %{race} troops +1" msgstr "Ð’Ñе ÑущеÑтва одной фракции +1" msgid "Multiple" msgstr "Ðейтральный" msgid "Troops of %{count} alignments -%{penalty}" msgstr "СущеÑтва %{count} разных фракций -%{penalty}" msgid "Some undead in army -1" msgstr "Ðежить в армии -1" msgid "View %{name}" msgstr "ПроÑмотреть %{name}" msgid "Move the %{name} " msgstr "ПеремеÑтить %{name}" msgid "Move or right click to redistribute %{name}" msgstr "%{name}: перемеcтить или разделить (правой кнопкой)" msgid "Combine %{name} armies" msgstr "%{name}: объединить или разделить (правой кнопкой)" msgid "Exchange %{name2} with %{name}" msgstr "%{name2} и %{name}: поменÑть меÑтами" msgid "Select %{name}" msgstr "Выбрать %{name}" msgid "Cannot move last troop" msgstr "Ðевозможно перемеÑтить поÑледний отрÑд" msgid "Move the %{name}" msgstr "ПеремеÑтить %{name}" msgid "Set Count" msgstr "КоличеÑтво" msgid "%{name} destroys half the enemy troops!" msgid_plural "%{name} destroy half the enemy troops!" msgstr[0] "%{name} убивает половину вражеÑкого отрÑда!" msgstr[1] "%{name} убивают половину вражеÑкого отрÑда!" msgstr[2] "%{name} убивают половину вражеÑкого отрÑда!" msgid "%{name} has turned off the auto battle" msgstr "%{name} выключил автобитву" msgid "%{name} has turned on the auto battle" msgstr "%{name} включил автобитву" msgid "Spell failed!" msgstr "Ðеудачный вызов магии!" msgid "" "The Sphere of Negation artifact is in effect for this battle, disabling all " "combat spells." msgstr "" "Сфера ÐžÑ‚Ñ€Ð¸Ñ†Ð°Ð½Ð¸Ñ Ð»Ð¸ÑˆÐ¸Ð»Ð° возможноÑти иÑпользовать любые Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ Ð² битве." msgid "You have already cast a spell this round." msgstr "Ð’Ñ‹ уже иÑпользовали магию на Ñтом ходу." msgid "That spell will have no effect!" msgstr "Это заклинание не подейÑтвует!" msgid "You may only summon one type of elemental per combat." msgstr "Ð’ Ñражении Ð’Ñ‹ можете призвать Ñлементалей только одного типа." msgid "" "There is no open space adjacent to your hero where you can summon an " "Elemental to." msgstr "Ð”Ð»Ñ Ð²Ñ‹Ð·Ð¾Ð²Ð° Ñлементалей у Ð’Ð°Ñ Ð½ÐµÑ‚ Ñвободного проÑтранÑтва возле героÑ." msgid "" "The Moat reduces by -%{count} the defense skill of any unit and slows to " "half movement rate." msgstr "" "Ров уменьшает на -%{count} навыки обороны любого Ð¿Ð¾Ð´Ñ€Ð°Ð·Ð´ÐµÐ»ÐµÐ½Ð¸Ñ Ð¸ замедлÑет " "его до половины ÑкороÑти движениÑ." msgid "Speed: %{speed}" msgstr "СкороÑть: %{speed}" msgid "Speed" msgstr "СкороÑть" msgid "Off" msgstr "Выкл." msgid "On" msgstr "Вкл." msgid "Turn Order" msgstr "ПорÑдок дейÑтвий" msgid "Auto Spell Casting" msgstr "ÐœÐ°Ð³Ð¸Ñ Ð² автобитве" msgid "Grid" msgstr "Сетка" msgid "Shadow Movement" msgstr "Затенение Ñетки" msgid "Shadow Cursor" msgstr "КурÑор Ñ Ñ‚ÐµÐ½ÑŒÑŽ" msgid "Audio" msgstr "Звук" msgid "Settings" msgstr "ÐаÑтройки" msgid "Configure" msgstr "ÐаÑтроить" msgid "Hot Keys" msgstr "ГорÑчие клавиши" msgid "Damage Info" msgstr "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾Ð± уроне" msgid "Set the speed of combat actions and animations." msgstr "УÑтановить ÑкороÑть дейÑтвий и анимации ÑущеÑтв в бою." msgid "Toggle to display the turn order during the battle." msgstr "Включить/выключить показ порÑдка дейÑтвий отрÑдов в бою." msgid "" "Toggle whether or not the computer will cast spells for you when auto combat " "is on. (Note: This does not affect spell casting for computer players in any " "way, nor does it affect quick combat.)" msgstr "" "Включить/выключить иÑпользование заклинаний компьютером в режиме автобитвы. " "(Эта Ð¾Ð¿Ñ†Ð¸Ñ Ð½Ðµ влиÑет на иÑпользование заклинаний компьютерным игроком и на " "режим быÑтрой битвы.)" msgid "" "Toggle the hex grid on or off. The hex grid always underlies movement, even " "if turned off. This switch only determines if the grid is visible." msgstr "" "Отобразить/Ñкрыть Ñетку Ð¿Ð¾Ð»Ñ Ð±Ð¾Ñ. Ð’Ñе Ð¿ÐµÑ€ÐµÐ¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð½Ð° поле Ð±Ð¾Ñ Ð¿Ñ€Ð¾Ð¸ÑходÑÑ‚ по " "гекÑагональной Ñетке. Эта Ð¾Ð¿Ñ†Ð¸Ñ Ð²Ð»Ð¸Ñет лишь на внешний вид Ð¿Ð¾Ð»Ñ Ð±Ð¾Ñ." msgid "" "Toggle on or off shadows showing where your creatures can move and attack." msgstr "" "Включить/выключить выделение облаÑти Ð¿Ð¾Ð»Ñ Ð±Ð¾Ñ, доÑтупной Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð¸ " "атаки ÑущеÑтвом." msgid "" "Toggle on or off a shadow showing the current hex location of the mouse " "cursor." msgstr "" "Включить/выключить отриÑовку тени под курÑором на гекÑагональной Ñетке." msgid "Change the audio settings of the game." msgstr "ÐаÑтроить параметры аудио." msgid "Check and configure all the hot keys present in the game." msgstr "Проверить и наÑтроить вÑе горÑчие клавиши, имеющиеÑÑ Ð² игре." msgid "Toggle to display damage information during the battle." msgstr "Включить/выключить отображение информации об уроне во Ð²Ñ€ÐµÐ¼Ñ Ð±Ð¸Ñ‚Ð²Ñ‹." msgid "Exit this menu." msgstr "Выход из меню." msgid "Okay" msgstr "ПринÑть" msgid "The enemy has surrendered!" msgstr "Враг капитулировал!" msgid "Their cowardice costs them %{gold} gold." msgstr "ТруÑоÑть Ñтоила противнику %{gold} золота." msgid "The enemy has fled!" msgstr "Враг Ñбежал!" msgid "A glorious victory!" msgstr "БлиÑÑ‚Ð°Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¿Ð¾Ð±ÐµÐ´Ð°!" msgid "For valor in combat, %{name} receives %{exp} experience." msgstr "За доблеÑть в бою %{name} получает %{exp} опыта." msgid "The cowardly %{name} flees from battle." msgstr "СтруÑив, %{name} бежит Ñ Ð¿Ð¾Ð»Ñ Ð±Ð¸Ñ‚Ð²Ñ‹." msgid "%{name} surrenders to the enemy, and departs in shame." msgstr "%{name} ÑдаётÑÑ Ð²Ñ€Ð°Ð³Ñƒ и уходит Ñ Ð¿Ð¾Ð·Ð¾Ñ€Ð¾Ð¼." msgid "Your forces suffer a bitter defeat, and %{name} abandons your cause." msgstr "Ваши войÑка потерпели поражение и %{name} покидает ВаÑ." msgid "Your forces suffer a bitter defeat." msgstr "Ваши войÑка потерпели поражение." msgid "Battlefield Casualties" msgstr "Потери на поле боÑ:" msgid "Attacker" msgstr "Ðападавший" msgid "Defender" msgstr "ОборонÑвшийÑÑ" msgid "Click to leave the battle results." msgstr "Ðажмите, чтобы закрыть результаты битвы." msgid "Click to restart the battle in manual mode." msgstr "Ðажмите, чтобы перезапуÑтить битву в ручном режиме." msgid "Restart" msgstr "Ðачать Ñначала" msgid "You have captured an enemy artifact!" msgstr "Ð’Ñ‹ захватили вражеÑкий артефакт!" msgid "As you reach for the %{name}, it mysteriously disappears." msgstr "" "Когда Ð’Ñ‹ притрагиваетеÑÑŒ к артефакту %{name}, он таинÑтвенным образом " "иÑчезает." msgid "As your enemy reaches for the %{name}, it mysteriously disappears." msgstr "" "Когда Ваш враг притрагиваетÑÑ Ðº артефакту %{name}, тот таинÑтвенным образом " "иÑчезает." msgid "Necromancy!" msgstr "ÐекромантиÑ!" msgid "" "Practicing the dark arts of necromancy, you are able to raise %{count} of " "the enemy's dead to return under your service as %{monster}." msgstr "" "ÐŸÑ€Ð°ÐºÑ‚Ð¸ÐºÑƒÑ Ñ‚Ñ‘Ð¼Ð½Ð¾Ðµ иÑкуÑÑтво некромантии, Ð’Ñ‹ оживлÑете\n" "%{count} мёртвых ÑущеÑтв противника, которые будут Ñлужить Вам как " "%{monster}." msgid "%{name} the %{race}" msgstr "%{name} %{race}" msgid "Captain of %{name}" msgstr "Капитан замка %{name}." msgid "Attack" msgstr "Ðтака" msgid "Defense" msgstr "Защита" msgid "Spell Power" msgstr "Сила магии" msgid "Knowledge" msgstr "ЗнаниÑ" msgid "Morale" msgstr "Мораль" msgid "Luck" msgstr "Удача" msgid "Spell Points" msgstr "Очки магии" msgid "Hero's Options" msgstr "ВозможноÑти героÑ" msgid "Cast Spell" msgstr "ПроизнеÑти заклинание" msgid "Retreat" msgstr "ОтÑтупить" msgid "Surrender" msgstr "СдатьÑÑ" msgid "Cancel" msgstr "Отмена" msgid "Hero Screen" msgstr "Экран героÑ" msgid "Captain's Options" msgstr "ВозможноÑти капитана замка" msgid "" "Cast a magical spell. You may only cast one spell per combat round. The " "round is reset when every creature has had a turn." msgstr "" "МагичеÑкое заклинание. Ð’Ñ‹ можете произноÑить только одно заклинание в " "течение раунда. Ðовый раунд начинаетÑÑ Ð¿Ð¾Ñле того, как каждое ÑущеÑтво " "Ñделает ход." msgid "" "Retreat your hero, abandoning your creatures. Your hero will be available " "for you to recruit again, however, the hero will have only a novice hero's " "forces." msgstr "" "ОтÑтупить героем, броÑив Ваших ÑущеÑтв на произвол Ñудьбы. Ð’Ñ‹ Ñможете нанÑть " "Ñтого Ð³ÐµÑ€Ð¾Ñ Ñнова, но Ñ Ð²Ð¾Ð¹Ñком, ÑоответÑтвующим герою-новичку." msgid "" "Surrendering costs gold. However if you pay the ransom, the hero and all of " "his or her surviving creatures will be available to recruit again. The cost " "of surrender is half of the total cost of the non-temporary troops remaining " "in the army." msgstr "" "КапитулÑÑ†Ð¸Ñ Ñтоит золота. Однако, еÑли Ð’Ñ‹ заплатите выкуп, Ð³ÐµÑ€Ð¾Ñ Ð¼Ð¾Ð¶Ð½Ð¾ будет " "нанÑть вмеÑте Ñо вÑеми выжившими ÑущеÑтвами. СтоимоÑть капитулÑции " "ÑоÑтавлÑет половину ÑтоимоÑти ÑущеÑтв, оÑтающихÑÑ Ð² армии, за иÑключением " "призванных ÑущеÑтв." msgid "Open Hero Screen to view full information about the hero." msgstr "Открыть Ñкран Ñ Ð¿Ð¾Ð´Ñ€Ð¾Ð±Ð½Ð¾Ð¹ информацией о герое." msgid "Return to the battle." msgstr "ВернутьÑÑ Ðº Ñражению." msgid "Not enough gold (%{gold})" msgstr "Ðе хватает %{gold} золота" msgid "%{name} states:" msgstr "%{name} объÑвлÑет:" msgid "Captain of %{name} states:" msgstr "Капитан замка %{name} объÑвлÑет:" msgid "" "\"I will accept your surrender and grant you and your troops safe passage " "for the price of %{price} gold.\"" msgstr "" "\"Я приму твою капитулÑцию и предоÑтавлю безопаÑный проход тебе и твоим " "войÑкам за %{price} золотом.\"" msgid "View %{monster} info" msgstr "Показать информацию Ð´Ð»Ñ %{monster}" msgid "Fly %{monster} here" msgstr "%{monster}: лететь Ñюда" msgid "Move %{monster} here" msgstr "%{monster}: идти Ñюда" msgid "Shoot %{monster}" msgstr "СтрелÑть в %{monster}" msgid "(1 shot left)" msgid_plural "(%{count} shots left)" msgstr[0] "(ещё %{count} выÑтрел)" msgstr[1] "(ещё %{count} выÑтрела)" msgstr[2] "(ещё %{count} выÑтрелов)" msgid "Attack %{monster}" msgstr "Ðтаковать %{monster}" msgid "Turn %{turn}" msgstr "Раунд %{turn}" msgid "Teleport here" msgstr "ТелепортироватьÑÑ Ñюда" msgid "Invalid teleport destination" msgstr "ÐÐµÐ²ÐµÑ€Ð½Ð°Ñ Ñ†ÐµÐ»ÑŒ телепорта" msgid "Cast %{spell} on %{monster}" msgstr "Применить \"%{spell}\" (цель: %{monster})" msgid "Cast %{spell}" msgstr "ПроизнеÑти \"%{spell}\"" msgid "Select spell target" msgstr "Выберите цель заклинаниÑ" msgid "View Ballista info" msgstr "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ баллиÑте" msgid "Ballista" msgstr "БаллиÑта" msgid "Enable auto combat" msgstr "Включить автобитву" msgid "Allows the computer to fight out the battle for you." msgstr "ПозволÑет компьютеру ÑражатьÑÑ Ð·Ð° ВаÑ." msgid "Auto Combat" msgstr "Ðвтобитва" msgid "Customize system options" msgstr "Изменить наÑтройки ÑиÑтемы" msgid "Allows you to customize the combat screen." msgstr "ПозволÑет Вам наÑтраивать отображение боÑ." msgid "System Options" msgstr "Параметры ÑиÑтемы" msgid "Skip this unit" msgstr "ПропуÑтить ход" msgid "Skip" msgstr "ПропуÑтить" msgid "" "Skips the current creature. The current creature ends its turn and does not " "get to go again until the next round." msgstr "" "ПропуÑтить ход Ð´Ð»Ñ Ñ‚ÐµÐºÑƒÑ‰ÐµÐ³Ð¾ отрÑда. ОтрÑд пропуÑкает ход до начала " "Ñледующего раунда." msgid "View Captain's options" msgstr "ПоÑмотреть возможноÑти капитана" msgid "View Hero's options" msgstr "ПоÑмотреть возможноÑти героÑ" msgid "View opposing Captain" msgstr "ПоÑмотреть вражеÑкого капитана" msgid "View opposing Hero" msgstr "ПроÑмотреть вражеÑкого героÑ" msgid "Hide logs" msgstr "СпрÑтать журнал" msgid "Show logs" msgstr "Показать журнал" msgid "Message Bar" msgstr "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ð¾Ð½Ð½Ð°Ñ Ñтрока" msgid "Shows the results of individual monster's actions." msgstr "ЗдеÑÑŒ отображаютÑÑ Ñ€ÐµÐ·ÑƒÐ»ÑŒÑ‚Ð°Ñ‚Ñ‹ дейÑтвий отдельных отрÑдов." msgid "Are you sure you want to finish the battle in auto mode?" msgstr "Ð’Ñ‹ уверены, что хотите закончить битву в авторежиме?" msgid "Are you sure you want to enable auto combat?" msgstr "Ð’Ñ‹ дейÑтвительно хотите включить автобитву?" msgid "%{name} skip their turn." msgstr "%{name}: пропуÑк хода." msgid "%{attacker} does %{damage} damage." msgid_plural "%{attacker} do %{damage} damage." msgstr[0] "%{attacker} наноÑит %{damage} урона." msgstr[1] "%{attacker} наноÑÑÑ‚ %{damage} урона." msgstr[2] "%{attacker} наноÑÑÑ‚ %{damage} урона." msgid "1 creature perishes." msgid_plural "%{count} creatures perish." msgstr[0] "%{count} ÑущеÑтво погибло." msgstr[1] "%{count} ÑущеÑтва погибли." msgstr[2] "%{count} ÑущеÑтв погибли." msgid "1 %{defender} perishes." msgid_plural "%{count} %{defender} perish." msgstr[0] "%{count} %{defender} погиб." msgstr[1] "%{count} %{defender} погибли." msgstr[2] "%{count} %{defender} погибли." msgid "1 soul is incorporated." msgid_plural "%{count} souls are incorporated." msgstr[0] "%{count} душа поглощена." msgstr[1] "%{count} души поглощено." msgstr[2] "%{count} душ поглощено." msgid "1 %{unit} is revived." msgid_plural "%{count} %{unit} are revived." msgstr[0] "%{count} %{unit} воÑкреÑ." msgstr[1] "%{count} %{unit} воÑкреÑли." msgstr[2] "%{count} %{unit} воÑкреÑли." msgid "Moved %{monster}: from [%{src}] to [%{dst}]." msgstr "%{monster}: перемещение Ñ [%{src}] на [%{dst}]." msgid "The %{name} resist the spell!" msgstr "%{name} защитилÑÑ Ð¾Ñ‚ магии!" msgid "%{name} casts %{spell} on the %{troop}." msgstr "%{name}: применение \"%{spell}\" на цель: %{troop}." msgid "%{name} casts %{spell}." msgstr "%{name}: применение \"%{spell}\"." msgid "The %{spell} does %{damage} damage to one undead creature." msgstr "Заклинание \"%{spell}\" наноÑит %{damage} урона одной нежити." msgid "The %{spell} does %{damage} damage to all undead creatures." msgstr "Заклинание \"%{spell}\" наноÑит %{damage} урона вÑей нежити." msgid "The %{spell} does %{damage} damage, %{count} creatures perish." msgstr "" "Заклинание \"%{spell}\" наноÑит %{damage} урона, %{count} ÑущеÑтв погибают." msgid "The %{spell} does %{damage} damage." msgstr "Заклинание \"%{spell}\" наноÑит %{damage} урона." msgid "The %{spell} does %{damage} damage to one living creature." msgstr "" "Заклинание \"%{spell}\" наноÑит %{damage} урона одному живому ÑущеÑтву." msgid "The %{spell} does %{damage} damage to all living creatures." msgstr "Заклинание \"%{spell}\" наноÑит %{damage} урона вÑем живым ÑущеÑтвам." msgid "The %{attacker}' attack blinds the %{target}!" msgstr "%{attacker}: противник \"%{target}\" оÑлеп!" msgid "The %{attacker}' gaze turns the %{target} to stone!" msgstr "%{attacker}: противник \"%{target}\" окаменел!" msgid "The %{attacker}' curse falls upon the %{target}!" msgstr "%{attacker}: противник \"%{target}\" проклÑÑ‚!" msgid "The %{target} are paralyzed by the %{attacker}!" msgstr "%{attacker}: противник \"%{target}\" парализован!" msgid "The %{attacker} dispel all good spells on your %{target}!" msgstr "СнÑты хорошие чары Ñ Ð¿Ñ€Ð¾Ñ‚Ð¸Ð²Ð½Ð¸ÐºÐ° \"%{target}\"!" msgid "The %{attacker} cast %{spell} on %{target}!" msgstr "%{attacker} применÑет \"%{spell}\" (цель: %{target})." msgid "Bad luck descends on the %{attacker}." msgstr "Удача отвернулаÑÑŒ от %{attacker}." msgid "Good luck shines on the %{attacker}." msgstr "Удача улыбнулаÑÑŒ %{attacker}." msgid "High morale enables the %{monster} to attack again." msgstr "Ð‘Ð»Ð°Ð³Ð¾Ð´Ð°Ñ€Ñ Ð²Ñ‹Ñокой морали %{monster} атакуют ещё раз." msgid "Low morale causes the %{monster} to freeze in panic." msgstr "Из-за низкой морали %{monster} заÑтыли в панике." msgid "%{tower} does %{damage} damage." msgstr "Ð‘Ð°ÑˆÐ½Ñ \"%{tower}\" наноÑит %{damage} урона." msgid "The mirror image is created." msgstr "Фантомный отрÑд Ñоздан!" msgid "The mirror image is destroyed!" msgstr "Фантомный отрÑд уничтожен!" msgid "Are you sure you want to interrupt the auto battle?" msgstr "Ð’Ñ‹ дейÑтвительно хотите прервать автобитву?" msgid "Error" msgstr "Ошибка" msgid "No spells to cast." msgstr "Ðет заклинаний." msgid "Are you sure you want to retreat?" msgstr "Ð’Ñ‹ дейÑтвительно хотите отÑтупить?" msgid "Retreat disabled" msgstr "ÐÐµÐ»ÑŒÐ·Ñ Ð¾Ñ‚Ñтупить" msgid "Surrender disabled" msgstr "ÐÐµÐ»ÑŒÐ·Ñ ÑдатьÑÑ" msgid "Damage: %{max}" msgstr "Урон: %{max}" msgid "Damage: %{min} - %{max}" msgstr "Урон: %{min} - %{max}" msgid "Perish: %{max}" msgstr "Погибнет: %{max}" msgid "Perish: %{min} - %{max}" msgstr "Погибнет: %{min} - %{max}" msgid "" "Through eagle-eyed observation, %{name} is able to learn the magic spell " "%{spell}." msgstr "" "Ð‘Ð»Ð°Ð³Ð¾Ð´Ð°Ñ€Ñ Ð¾Ñ€Ð»Ð¸Ð½Ð¾Ð¼Ñƒ взору, %{name} Ñмог выучить заклинание \"%{spell}\"." msgid "Human" msgstr "Человек" msgid "AI" msgstr "Компьютер" msgid "Start" msgstr "Ðачать" msgid "Start the battle." msgstr "Ðачать битву." msgid "Exit" msgstr "Закрыть" msgid "Reset" msgstr "СброÑить" msgid "Reset to default settings." msgstr "СброÑить до значений по умолчанию." msgid "Please select another hero." msgstr "ПожалуйÑта, выберите другого героÑ." msgid "Set Attack Skill" msgstr "Ðтака" msgid "Set Defense Skill" msgstr "Защита" msgid "Set Power Skill" msgstr "Сила магии" msgid "Set Knowledge Skill" msgstr "ЗнаниÑ" msgid "%{race1} %{name1} vs %{race2} %{name2}" msgstr "%{race1} %{name1} против %{race2} %{name2}" msgid "Monsters" msgstr "СущеÑтва" msgid "N/A" msgstr "Ð/Д" msgid "Left Turret" msgstr "Ð›ÐµÐ²Ð°Ñ Ð±Ð°ÑˆÐ½Ñ" msgid "Right Turret" msgstr "ÐŸÑ€Ð°Ð²Ð°Ñ Ð±Ð°ÑˆÐ½Ñ" msgid "The %{name} fires with the strength of %{count} Archers" msgstr "%{name} ÑтрелÑет Ñилой %{count} лучников" msgid "each with a +%{attack} bonus to their attack skill." msgstr "каждый Ñ Ð±Ð¾Ð½ÑƒÑом +%{attack} к Ñвоему навыку атаки." msgid "The %{name} is destroyed." msgstr "%{name} уничтожена!" msgid "%{count} %{name} rises from the dead!" msgid_plural "%{count} %{name} rise from the dead!" msgstr[0] "%{count} %{name} воÑкреÑает из мёртвых!" msgstr[1] "%{count} %{name} воÑкреÑают из мёртвых!" msgstr[2] "%{count} %{name} воÑкреÑают из мёртвых!" msgid "Dwarven Alliance" msgstr "ÐльÑÐ½Ñ Ð³Ð½Ð¾Ð¼Ð¾Ð²" msgid "Sorceress Guild" msgstr "Ð“Ð¸Ð»ÑŒÐ´Ð¸Ñ Ð²Ð¾Ð»ÑˆÐµÐ±Ð½Ð¸Ñ†" msgid "Necromancer Guild" msgstr "Ð“Ð¸Ð»ÑŒÐ´Ð¸Ñ Ð½ÐµÐºÑ€Ð¾Ð¼Ð°Ð½Ñ‚Ð¾Ð²" msgid "Ogre Alliance" msgstr "ÐльÑÐ½Ñ Ð¾Ð³Ñ€Ð¾Ð²" msgid "Dwarfbane" msgstr "Погибель гномов" msgid "Dragon Alliance" msgstr "ÐльÑÐ½Ñ Ð´Ñ€Ð°ÐºÐ¾Ð½Ð¾Ð²" msgid "Elven Alliance" msgstr "ÐльÑÐ½Ñ Ñльфов" msgid "Kraeger defeated" msgstr "Крейгер побеждён" msgid "Wayward Son" msgstr "Блудный Ñын" msgid "Uncle Ivan" msgstr "ДÑÐ´Ñ Ð’Ð°Ð½Ñ" msgid "Annexation" msgstr "ÐннекÑиÑ" msgid "Force of Arms" msgstr "Сила оружиÑ" msgid "Save the Dwarves" msgstr "СпаÑите гномов" msgid "Carator Mines" msgstr "Копи Каратора" msgid "Turning Point" msgstr "Переломный момент" msgid "scenarioName|Defender" msgstr "Защитник" msgid "Corlagon's Defense" msgstr "Защита Корлагона" msgid "The Crown" msgstr "Корона" msgid "The Gauntlet" msgstr "Перчатка" msgid "Betrayal" msgstr "ПредательÑтво" msgid "Final Justice" msgstr "Ðкт отчаÑниÑ" msgid "" "Roland needs you to defeat the lords near his castle to begin his war of " "rebellion against his brother. They are not allied with each other, so they " "will spend most of their time fighting with one another. Victory is yours " "when you have defeated all of their castles and heroes." msgstr "" "Прежде чем поднÑть воÑÑтание против брата, Роланд хочет, чтобы Ð’Ñ‹ одержали " "победу над окреÑтными лордами. Между ними нет единÑтва, поÑтому большую " "чаÑть времени они будут занÑты Ñтычками друг Ñ Ð´Ñ€ÑƒÐ³Ð¾Ð¼. Победа будет Вашей, " "когда Ð’Ñ‹ захватите вÑе города." msgid "" "The local lords refuse to swear allegiance to Roland, and must be subdued. " "They are wealthy and powerful, so be prepared for a tough fight. Capture all " "enemy castles to win." msgstr "" "Лорды пограничных земель отказываютÑÑ Ð¿Ñ€Ð¸Ð½ÐµÑти клÑтву верноÑти Роланду, а " "значит должны быть повержены. БогатÑтва и влаÑти им не занимать, поÑтому " "будьте готовы к нелёгкой борьбе. Чтобы победить, захватите вÑе вражеÑкие " "замки." msgid "" "Your task is to defend the Dwarves against Archibald's forces. Capture all " "of the enemy towns and castles to win, and be sure not to lose all of the " "dwarf towns at once, or the enemy will have won." msgstr "" "Ваша задача защитить гномов от Ñил Ðрчибальда. Захватите вÑе вражеÑкие " "города и замки, чтобы победить, и не дайте врагу захватить Ñразу вÑе города " "гномов, иначе он одержит победу." msgid "" "You will face four allied enemies in a straightforward fight for resource " "and treasure. Capture all of the enemy castles for victory." msgstr "" "Ð’Ñ‹ ÑтолкнетеÑÑŒ Ñ Ñ‡ÐµÑ‚Ñ‹Ñ€ÑŒÐ¼Ñ Ð²Ñ€Ð°Ð³Ð°Ð¼Ð¸-Ñоюзниками в прÑмой борьбе за реÑурÑÑ‹ и " "Ñокровища. Захватите вÑе вражеÑкие замки Ð´Ð»Ñ Ð¿Ð¾Ð±ÐµÐ´Ñ‹." msgid "" "Your enemies are allied against you and start close by, so be ready to come " "out fighting. You will need to own all four castles in this small valley to " "win." msgstr "" "Ваши враги объединены против Ð’Ð°Ñ Ð¸ находÑÑ‚ÑÑ Ñ€Ñдом, так что будьте готовы к " "боевым дейÑтвиÑм. Вам нужно будет завладеть вÑеми Ñ‡ÐµÑ‚Ñ‹Ñ€ÑŒÐ¼Ñ Ð·Ð°Ð¼ÐºÐ°Ð¼Ð¸ в Ñтой " "маленькой долине, чтобы выиграть." msgid "" "The Sorceress' guild of Noraston has requested Roland's aid against an " "attack from Archibald's allies. Capture all of the enemy castles to win, and " "don't lose Noraston, or you'll lose the scenario. (Hint: There is an enemy " "castle on an island in the ocean.)" msgstr "" "Ð“Ð¸Ð»ÑŒÐ´Ð¸Ñ Ð²Ð¾Ð»ÑˆÐµÐ±Ð½Ð¸Ñ† ÐораÑтона обратилаÑÑŒ к Роланду Ñ Ð¿Ñ€Ð¾Ñьбой о помощи в " "защите от Ð½Ð°Ð¿Ð°Ð´ÐµÐ½Ð¸Ñ Ñоюзников Ðрчибальда. Захватите вÑе вражеÑкие замки, " "чтобы выиграть, и не потерÑйте ÐораÑтон, иначе Ð’Ñ‹ проиграете Ñценарий. " "(ПодÑказка: Ðа оÑтрове в океане еÑть вражеÑкий замок.)" msgid "" "Gather as large an army as possible and capture the enemy castle within 8 " "weeks. You are opposed by only one enemy, but must travel a long way to get " "to the enemy castle. Any troops you have in your army at the end of this " "scenario will be with you in the final battle." msgstr "" "Соберите как можно больше армии, и захватите вражеÑкий замок в течение 8 " "недель. Вам противоÑтоит только один враг, но Вам нужно пройти долгий путь, " "чтобы добратьÑÑ Ð´Ð¾ вражеÑкого замка. Ð’Ñе войÑка в Вашей армии в конце Ñтого " "ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ Ð±ÑƒÐ´ÑƒÑ‚ Ñ Ð’Ð°Ð¼Ð¸ в финальной битве." msgid "" "Find the Crown before Archibald's heroes find it. Roland will need the Crown " "for the final battle against Archibald." msgstr "" "Ðайдите корону до того, как герои Ðрчибальда найдут её. Корона понадобитÑÑ " "Роланду Ð´Ð»Ñ Ñ„Ð¸Ð½Ð°Ð»ÑŒÐ½Ð¾Ð¹ битвы против Ðрчибальда." msgid "" "Three allied enemies stand before you and victory, including Lord Corlagon. " "Roland is in a castle to the northwest, and you will lose if he falls to the " "enemy. Remember that capturing Lord Corlagon will ensure that he will not " "fight against you in the final scenario." msgstr "" "Три объединённых врага ÑтоÑÑ‚ между Вами и победой, в их Ñ€Ñдах лорд Корлагон. " "Роланд находитÑÑ Ð² замке на Ñеверо-западе, и Ð’Ñ‹ проиграете, еÑли его возьмут " "в плен. Помните, что еÑли Ð’Ñ‹ одолеете лорда Корлагона, он не будет ÑражатьÑÑ " "против Ð’Ð°Ñ Ð² финальном Ñценарии." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Archibald to end the war!" msgstr "" "Это поÑледнÑÑ Ð±Ð¸Ñ‚Ð²Ð°. И Ð’Ñ‹, и Ваш враг вооружены до зубов, и вÑе враги в " "Ñоюзе против ВаÑ. Захватите Ðрчибальда, чтобы положить конец войне!" msgid "" "Switching sides leaves you with three castles against the enemy's one. This " "battle will be the easiest one you will face for the rest of the war..." "traitor." msgstr "" "ПереметнётеÑÑŒ на Ñторону противника, и окажетеÑÑŒ Ñ Ñ‚Ñ€ÐµÐ¼Ñ Ð·Ð°Ð¼ÐºÐ°Ð¼Ð¸ против " "одного. Эта битва будет Ñамой лёгкой из тех, что предÑтоÑÑ‚ до конца войны... " "изменник." msgid "Barbarian Wars" msgstr "Войны варваров" msgid "First Blood" msgstr "ÐŸÐµÑ€Ð²Ð°Ñ ÐºÑ€Ð¾Ð²ÑŒ" msgid "Necromancers" msgstr "Ðекроманты" msgid "Slay the Dwarves" msgstr "Уничтожь гномов" msgid "Country Lords" msgstr "Лорды провинций" msgid "Dragon Master" msgstr "Повелитель драконов" msgid "Rebellion" msgstr "Бунт" msgid "Apocalypse" msgstr "ÐпокалипÑиÑ" msgid "Greater Glory" msgstr "К вÑщей Ñлаве" msgid "" "King Archibald requires you to defeat the three enemies in this region. They " "are not allied with one another, so they will spend most of their energy " "fighting amongst themselves. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Король Ðрчибальд требует, чтобы Ð’Ñ‹ победили трёх врагов в Ñтом регионе. Они " "не в Ñоюзе друг Ñ Ð´Ñ€ÑƒÐ³Ð¾Ð¼, поÑтому будут тратить большую чаÑть Ñвоих Ñил на " "борьбу между Ñобой. Ð’Ñ‹ выиграете, когда захватите вÑе вражеÑкие замки и " "повергнете вÑех героев." msgid "" "You must unify the barbarian tribes of the north by conquering them. As in " "the previous mission, the enemy is not allied against you, but they have " "more resources at their disposal. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Ð’Ñ‹ должны объединить варварÑкие племена Ñевера, завоевав их. Как и в " "предыдущей миÑÑии, враги не объединены против ВаÑ, но в их раÑпорÑжении " "больше реÑурÑов. Ð’Ñ‹ выиграете, когда захватите вÑе вражеÑкие замки и " "повергнете вÑех героев." msgid "" "Do-gooder wizards have taken the Necromancers' castle. You must retake it to " "achieve victory. Remember that while you start with a powerful army, you " "have no castle and must take one within 7 days, or lose this battle. (Hint: " "The nearest castle is to the southeast.)" msgstr "" "Благодетели-волшебники взÑли замок некромантов. Ð’Ñ‹ должны вернуть его, чтобы " "добитьÑÑ Ð¿Ð¾Ð±ÐµÐ´Ñ‹. Помните, что хоть Ð’Ñ‹ начинаете Ñ Ð¼Ð¾Ñ‰Ð½Ð¾Ð¹ армией, у Ð’Ð°Ñ Ð½ÐµÑ‚ " "замка и Ð’Ñ‹ должны захватить его в течение 7 дней, или проиграете Ñту битву. " "(ПодÑказка: Ближайший замок находитÑÑ Ð½Ð° юго-воÑтоке.)" msgid "" "The dwarves need conquering before they can interfere in King Archibald's " "plans. Roland's forces have more than one hero and many towns to start with, " "so be ready for attack from multiple directions. You must capture all of the " "enemy towns and castles to claim victory." msgstr "" "Гномов нужно победить, прежде чем они поÑмеют вмешатьÑÑ Ð² планы ÐºÐ¾Ñ€Ð¾Ð»Ñ " "Ðрчибальда. Ð’ Ñ€Ñдах Роланда не один герой, а множеÑтво городов дают им " "возможноÑть начать атаку Ñ Ð½ÐµÑкольких направлений, поÑтому будьте начеку! Ð’Ñ‹ " "должны захватить вÑе вражеÑкие города и замки, чтобы претендовать на победу." msgid "" "You must put down a peasant revolt led by Roland's forces. All are allied " "against you, but you have Lord Corlagon, an experienced hero, to help you. " "Capture all enemy castles to win." msgstr "" "Ð’Ñ‹ должны положить конец креÑтьÑнÑкому воÑÑтанию во главе Ñ Ñилами Роланда. " "Ð’Ñе они объединилиÑÑŒ против ВаÑ, но Вам помогает опытный герой - лорд " "Корлагон. Захватите вÑе вражеÑкие замки, чтобы победить." msgid "" "There are two enemies allied against you in this mission. Both are well " "armed and seek to evict you from their island. Avoid them and capture Dragon " "City to win." msgstr "" "Ð’ Ñтой миÑÑии Вам противоÑтоÑÑ‚ два Ñоюзника. Оба хорошо вооружены и " "ÑтремÑÑ‚ÑÑ Ð¿Ñ€Ð¾Ð³Ð½Ð°Ñ‚ÑŒ Ð’Ð°Ñ Ñо Ñвоего оÑтрова. Избегайте их и захватите город " "драконов, чтобы победить." msgid "" "Your orders are to conquer the country lords that have sworn to serve " "Roland. All of the enemy castles are unified against you. Since you start " "without a castle, you must hurry to capture one before the end of the week. " "Capture all enemy castles for victory." msgstr "" "Вам приказано захватить Ð²Ð»Ð°Ð´ÐµÐ½Ð¸Ñ Ð»Ð¾Ñ€Ð´Ð¾Ð², которые поклÑлиÑÑŒ Ñлужить Роланду. " "Ð’Ñе вражеÑкие замки объединены против ВаÑ. Так как Ð’Ñ‹ начинаете без замка, " "Ð’Ñ‹ должны Ñпешить, чтобы захватить Ñ…Ð¾Ñ‚Ñ Ð±Ñ‹ один до конца недели. Захватите " "вÑе вражеÑкие замки Ð´Ð»Ñ Ð¿Ð¾Ð±ÐµÐ´Ñ‹." msgid "" "Find the Crown before Roland's heroes find it. Archibald will need the Crown " "for the final battle against Roland." msgstr "" "Ðайдите корону до того, как её найдут герои Роланда. Корона понадобитÑÑ " "Ðрчибальду Ð´Ð»Ñ Ñ„Ð¸Ð½Ð°Ð»ÑŒÐ½Ð¾Ð¹ битвы против Роланда." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Roland to win the war, and be sure " "not to lose Archibald in the fight!" msgstr "" "Это поÑледнÑÑ Ð±Ð¸Ñ‚Ð²Ð°. И Ð’Ñ‹, и Ваши враги вооружены до зубов, и вÑе они в " "Ñоюзе против ВаÑ. Захватите Роланда, чтобы выиграть войну, и не забудьте, " "что Ðрчибальд должен оÑтатьÑÑ Ð² живых!" msgid "Arrow's Flight" msgstr "Полёт Ñтрелы" msgid "Island of Chaos" msgstr "ОÑтров хаоÑа" msgid "The Abyss" msgstr "Бездна" msgid "Uprising" msgstr "ВоÑÑтание" msgid "Aurora Borealis" msgstr "Северное ÑиÑние" msgid "Betrayal's End" msgstr "Конец предательÑтва" msgid "Corruption's Heart" msgstr "Сердце Ñкверны" msgid "The Giant's Pass" msgstr "Перевал гигантов" msgid "" "Subdue the unruly local lords in order to provide the Empire with facilities " "to operate in this region." msgstr "" "УÑмирите непокорных меÑтных лордов, Ð´Ð»Ñ Ñ‚Ð¾Ð³Ð¾ чтобы дать Империи возможноÑть " "обоÑноватьÑÑ Ð² Ñтом регионе." msgid "" "Eliminate all opposition in this area. Then the first piece of the artifact " "will be yours." msgstr "" "Сокрушите вÑех врагов в Ñтой облаÑти. Тогда Ð¿ÐµÑ€Ð²Ð°Ñ Ñ‡Ð°Ñть артефакта будет " "Вашей." msgid "" "The sorceresses to the northeast are rebelling! For the good of the empire " "you must quash their feeble uprising on your way to the mountains." msgstr "" "Волшебницы на Ñеверо-воÑтоке поднÑли воÑÑтание! Ðа благо Империи Ð’Ñ‹ должны " "подавить их ничтожные очаги ÑÐ¾Ð¿Ñ€Ð¾Ñ‚Ð¸Ð²Ð»ÐµÐ½Ð¸Ñ Ð½Ð° пути в горы." msgid "" "Having prepared for your arrival, Kraeger has arranged for a force of " "necromancers to thwart your quest. You must capture the castle of Scabsdale " "before the first day of the third week, or the Necromancers will be too " "strong for you." msgstr "" "ПодготовившиÑÑŒ к Вашему приезду, КрÑгер воззвал к Ñилам некромантов, чтобы " "Ñорвать Ваши поиÑки. Ð’Ñ‹ должны захватить замок СкабÑдейл до первого Ð´Ð½Ñ " "третьей недели, пока некроманты не Ñтали Ñлишком Ñильны Ð´Ð»Ñ Ð’Ð°Ñ." msgid "" "The barbarian despot in this area is, as yet, ignorant of your presence. " "Quickly, build up your forces before you are discovered and attacked! Secure " "the region by subduing all enemy forces." msgstr "" "ВарварÑкий деÑпот в Ñтой облаÑти пока не знает о Вашем приÑутÑтвии. Как " "можно Ñкорее нараÑтите Ñвою мощь, прежде чем он обнаружит Ð’Ð°Ñ Ð¸ нападёт! Вам " "необходимо обезопаÑить Ñтот регион, покорив вÑе вражеÑкие Ñилы." msgid "" "The Empire is weak in this region. You will be unable to completely subdue " "all forces in this area, so take what you can before reprisal strikes. " "Remember, your true goal is to claim the Helmet of Anduran." msgstr "" "Ð˜Ð¼Ð¿ÐµÑ€Ð¸Ñ Ñлаба в Ñтом регионе. Ð’Ñ‹ не Ñможете единолично противодейÑтвовать " "вÑем Ñилам в Ñтой облаÑти, так что возьмите вÑÑ‘, что Ð’Ñ‹ Ñможете до того, как " "Ð’Ð°Ñ Ð½Ð°Ñтигнет возмездие. Помните, что Ваша иÑÑ‚Ð¸Ð½Ð½Ð°Ñ Ñ†ÐµÐ»ÑŒ ÑоÑтоит в том, " "чтобы получить шлем Ðндурана." msgid "For the good of the Empire, eliminate Kraeger." msgstr "Ðа благо Империи уничтожьте КрÑгера." msgid "" "At last, you have the opportunity and the facilities to rid the Empire of " "the necromancer's evil. Eradicate them completely, and you will be sung as a " "hero for all time." msgstr "" "Ðаконец, у Ð’Ð°Ñ ÐµÑть возможноÑть и ÑредÑтва, чтобы избавить Империю от зла " "некромантов. ИÑкорените их полноÑтью, и Ð’Ñ‹ будете воÑпеты, как герой на вÑе " "времена." msgid "Border Towns" msgstr "Пограничные города" msgid "Conquer and Unify" msgstr "Завоевать и объединить" msgid "Crazy Uncle Ivan" msgstr "Безумный дÑдÑ" msgid "The Wayward Son" msgstr "Блудный Ñын" msgid "Ivory Gates" msgstr "Ворота Слоновой КоÑти" msgid "The Elven Lands" msgstr "ЭльфийÑкие земли" msgid "The Epic Battle" msgstr "ЭпичеÑÐºÐ°Ñ Ð±Ð¸Ñ‚Ð²Ð°" msgid "The Southern War" msgstr "Ð®Ð¶Ð½Ð°Ñ Ð²Ð¾Ð¹Ð½Ð°" msgid "" "Conquer and unite all the enemy tribes. Don't lose the hero Jarkonas, the " "forefather of all descendants." msgstr "" "Завоюйте и объедините вÑе вражеÑкие племена. Ðе потерÑйте Ð³ÐµÑ€Ð¾Ñ Ð”Ð¶Ð°Ñ€ÐºÐ¾Ð½Ð°Ñа, " "Ð¿Ñ€Ð°Ñ€Ð¾Ð´Ð¸Ñ‚ÐµÐ»Ñ Ð²Ñех потомков." msgid "" "Your rival, the Kingdom of Harondale, is attacking weak towns on your " "border! Recover from their first strike and crush them completely!" msgstr "" "Ваш противник, КоролевÑтво Харондейл, атакует Ñлабые города на Вашей " "границе! ОправитеÑÑŒ от их первого удара и Ñокрушите их полноÑтью!" msgid "" "Find your wayward son Joseph who is rumored to be living in the desolate " "lands. Do it before the first day of the third month or it will be of no " "help to your family." msgstr "" "Отыщите Вашего блудного Ñына Джозефа, который, по Ñлухам, живёт в беÑплодных " "землÑÑ…. Сделайте Ñто за 8 недель, иначе будет поздно." msgid "" "Rescue your crazy uncle Ivan. Find him before the first day of the fourth " "month or it will be no help to your kingdom." msgstr "" "СпаÑите Вашего ÑумаÑшедшего дÑдю Ивана. Ðайдите его до первого Ð´Ð½Ñ " "четвертого меÑÑца, иначе он не поможет Вашему королевÑтву." msgid "" "Destroy the barbarians who are attacking the southern border of your " "kingdom! Recover your fallen towns, and then invade the jungle kingdom. " "Leave no enemy standing." msgstr "" "Уничтожьте варваров, атакующих южные границы Вашего королевÑтва. Верните " "Ñвои города, затем завоюйте варварÑкие." msgid "Retake the castle of Ivory Gates, which has fallen due to treachery." msgstr "Верните замок Ворота Слоновой КоÑти, который пал из-за предательÑтва." msgid "" "Gain the favor of the elves. They will not allow trees to be chopped down, " "so they will send you wood every 2 weeks. You must complete your mission " "before the first day of the seventh month, or the kingdom will surely fall." msgstr "" "Ð’Ñ‹ должны ÑниÑкать благоÑклонноÑть Ñльфов. Они не позволÑÑ‚ Вам вырубать леÑ, " "но будут выÑылать Вам древеÑину каждые две недели. У Ð’Ð°Ñ ÐµÑть 6 меÑÑцев." msgid "" "This is the final battle against your rival kingdom of Harondale. Eliminate " "everyone, and don't lose the hero Jarkonas VI." msgstr "" "Это поÑледнÑÑ Ð±Ð¸Ñ‚Ð²Ð° против враждебного королевÑтва Харондейл. Уничтожьте " "вÑех и не потерÑйте Ð³ÐµÑ€Ð¾Ñ Ð”Ð¶Ð°Ñ€ÐºÐ¾Ð½Ð°Ñа VI." msgid "Fount of Wizardry" msgstr "ИÑточник волшебÑтва" msgid "Power's End" msgstr "Конец влаÑти" msgid "The Eternal Scrolls" msgstr "Вечные Ñвитки" msgid "The Shrouded Isles" msgstr "ОÑтрова чародеев" msgid "" "Your mission is to vanquish the warring mages in the magical Shrouded Isles. " "The completion of this task will give you a fighting chance against your " "rivals." msgstr "" "Ваша миÑÑÐ¸Ñ - разгромить воинÑтвующих магов на волшебных Окутанных оÑтровах. " "Это увеличит Ваши шанÑÑ‹ в борьбе Ñ Ð¿Ñ€Ð¾Ñ‚Ð¸Ð²Ð½Ð¸ÐºÐ°Ð¼Ð¸." msgid "" "The location of the great library has been discovered! You must make your " "way to it, and reclaim the city of Chronos in which it lies." msgstr "" "МеÑтонахождение великой библиотеки было обнаружено! Ð’Ñ‹ должны добратьÑÑ Ð´Ð¾ " "нее и вернуть Ñебе город ХроноÑ, в котором она находитÑÑ." msgid "" "Find the Orb of negation, which is said to be buried in this land. There are " "clues inscribed on stone obelisks which will help lead you to your prize. " "Find the Orb before the first day of the sixth month, or your rivals will " "surely have gotten to the fount before you." msgstr "" "Ðайдите Сферу ОтрицаниÑ, котораÑ, как говорÑÑ‚, похоронена в Ñтой земле. Ðа " "каменных обелиÑках начертаны подÑказки, которые помогут Вам найти её. " "Ðайдите Ñферу до первого Ð´Ð½Ñ ÑˆÐµÑтого меÑÑца, иначе Ваши противники навернÑка " "доберутÑÑ Ð´Ð¾ иÑточника раньше ВаÑ." msgid "" "You must take control of the castle of Magic, where the fount of wizardry " "lies. Do this and your victory will be supreme." msgstr "" "Ð’Ñ‹ должны взÑть под контроль замок Магии, где находитÑÑ Ð¸Ñточник волшебÑтва. " "Сделайте Ñто, и Ваша победа будет окончательной." msgid "Blood is Thicker" msgstr "Кровные узы" msgid "King and Country" msgstr "Король и Ñтрана" msgid "Pirate Isles" msgstr "ПиратÑкие оÑтрова" msgid "Stranded" msgstr "Ðа мели" msgid "" "Capture the town on the island off the southeast shore in order to construct " "a boat and travel back towards the mainland. Do not lose the hero Gallavant." msgstr "" "Захватите город на оÑтрове к юго-западу от Ð¿Ð¾Ð±ÐµÑ€ÐµÐ¶ÑŒÑ Ð´Ð»Ñ Ñ‚Ð¾Ð³Ð¾, чтобы " "поÑтроить лодку, и вернитеÑÑŒ обратно на материк. Ðе потерÑйте Галаванта." msgid "" "Find and defeat Martine, the pirate leader, who resides in Pirates Cove. Do " "not lose Gallavant or your quest will be over." msgstr "" "Ðайдите и победите Мартину, предводительницу пиратов, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð¶Ð¸Ð²Ñ‘Ñ‚ в " "ПиратÑкой бухте. Ðе потерÑйте Галаванта, иначе Ваши поиÑки закончатÑÑ." msgid "" "Eliminate all the other forces who oppose the rule of Lord Alberon. " "Gallavant must not die." msgstr "" "Уничтожьте вÑе другие Ñилы, выÑтупающие против Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð»Ð¾Ñ€Ð´Ð° Ðльберона. " "Галавант не должен погибнуть." msgid "" "Overthrow the entrenched monarchy of Lord Alberon, and claim all the land in " "your name. Gallavant must not die." msgstr "" "Свергните укоренившуюÑÑ Ð¼Ð¾Ð½Ð°Ñ€Ñ…Ð¸ÑŽ лорда Ðльберона и объÑвите вÑе земли " "Ñвоими. Галавант не должен умереть." msgid " bane" msgstr " Ñд" msgid " alliance" msgstr " Ñоюз" msgid "Carry-over forces" msgstr "ÐŸÐµÑ€ÐµÐ½Ð¾Ñ Ð²Ð¾Ð¹Ñк" msgid " bonus" msgstr " бонуÑ" msgid " defeated" msgstr " поражение" msgid " will always run away from your army." msgstr " будут вÑегда убегать от Вашей армии." msgid " will be willing to join your army." msgstr " будут желать вÑтупить в Вашу армию." msgid "\"%{artifact}\" artifact will be carried over the scenario." msgstr "Ðртефакт \"%{artifact}\" будет доÑтупен в Ñтом Ñценарии." msgid "The army will be carried over the scenario." msgstr "ÐÑ€Ð¼Ð¸Ñ Ð±ÑƒÐ´ÐµÑ‚ перенеÑена в Ñледующий Ñценарий." msgid "The kingdom will have +%{count} %{resource} each day." msgstr "КоролевÑтво ежедневно будет получать +%{count} %{resource}." msgid "\"%{spell}\" spell will be carried over the scenario." msgstr "Заклинание \"%{spell}\" будет доÑтупно в Ñтом Ñценарии." msgid "%{hero} can be hired in the scenario." msgstr "%{hero} может быть нанÑÑ‚ в Ñтом Ñценарии." msgid "" "%{hero} has been defeated and will not appear in the subsequent scenarios." msgstr "" "%{hero} был повержен, и больше не будет доÑаждать Вам в Ñтом и поÑледующих " "ÑценариÑÑ…." msgid "The dwarves recognize their allies and gladly join your forces." msgstr "" "Гномы узнают Ñвоих Ñоюзников и Ñ Ñ€Ð°Ð´Ð¾Ñтью приÑоединÑÑŽÑ‚ÑÑ Ðº Вашим Ñилам." msgid "The ogres recognize you as the Dwarfbane and lumber over to join you." msgstr "" "Огры узнают в Ð’Ð°Ñ ÑƒÐ±Ð¸Ð¹Ñ†Ñƒ гномов и подходÑÑ‚, чтобы приÑоединитьÑÑ Ðº Вам." msgid "" "The dragons, snarling and growling, agree to join forces with you, their " "'Ally'." msgstr "" "Драконы, рыча и огрызаÑÑÑŒ, ÑоглашаютÑÑ Ð¾Ð±ÑŠÐµÐ´Ð¸Ð½Ð¸Ñ‚ÑŒ уÑÐ¸Ð»Ð¸Ñ Ñ Ð’Ð°Ð¼Ð¸, Ñвоим " "\"Ñоюзником\"." msgid "" "As you approach the group of elves, their leader calls them all to " "attention. He shouts to them, \"Who of you is brave enough to join this " "fearless ally of ours?\" The group explodes with cheers as they run to join " "your ranks." msgstr "" "Когда Ð’Ñ‹ приближаетеÑÑŒ к группе Ñльфов, их лидер окликает Ñвои Ñ€Ñды: \"Кто " "из Ð²Ð°Ñ Ð´Ð¾Ñтаточно храбр, чтобы приÑоединитьÑÑ Ðº нашему беÑÑтрашному Ñоюзнику?" "\". Эльфы взрываютÑÑ Ñ€Ð°Ð´Ð¾Ñтными возглаÑами и бегут, чтобы примкнуть к Вашим " "Ñ€Ñдам." msgid "" "The dwarves hail you, \"Any friend of Roland is a friend of ours. You may " "pass.\"" msgstr "Гномы приветÑтвуют ВаÑ: \"Друг Роланда - наш друг. Ð’Ñ‹ можете пройти\"." msgid "" "The ogres give you a grunt of recognition, \"Archibald's allies may pass.\"" msgstr "Огры хрюкают в знак признаниÑ: \"Союзники Ðрчибальда могут пройти\"." msgid "" "The dragons see you and call out. \"Our alliance with Archibald compels us " "to join you. Unfortunately you have no room. A pity!\" They quickly scatter." msgstr "" "Драконы видÑÑ‚ Ð’Ð°Ñ Ð¸ взывают: \"Ðаш Ñоюз Ñ Ðрчибальдом вынуждает Ð½Ð°Ñ " "приÑоединитьÑÑ Ðº Вам. К Ñожалению, в Вашей армии нет меÑта. Жаль!\" Взмахнув " "крыльÑми, они улетают прочь." msgid "" "The elves stand at attention as you approach. Their leader calls to you and " "says, \"Let us not impede your progress, ally! Move on, and may victory be " "yours.\"" msgstr "" "Эльфы замечают, как Ð’Ñ‹ приближаетеÑÑŒ. Их предводитель обращаетÑÑ Ðº Вам и " "говорит: \"Мы не будем препÑÑ‚Ñтвовать твоему продвижению, Ñоюзник! Проходи, " "и да пребудет Ñ Ñ‚Ð¾Ð±Ð¾Ð¹ победа.\"" msgid "\"The Dwarfbane!!!!, run for your lives.\"" msgstr "\"Убийца гномов!!!!, ÑпаÑайтеÑÑŒ кто может!\"" msgid "campaignBonus|Animate Dead" msgstr "ПоднÑть нежить" msgid "campaignBonus|Chain Lightning" msgstr "Цепь молний" msgid "campaignBonus|Fireblast" msgstr "Огненный взрыв" msgid "campaignBonus|Mass Curse" msgstr "Общее проклÑтие" msgid "campaignBonus|Mass Haste" msgstr "Общее уÑкорение" msgid "campaignBonus|Mirror Image" msgstr "Фантом" msgid "campaignBonus|Resurrect" msgstr "Оживление" msgid "campaignBonus|Steelskin" msgstr "Ð¡Ñ‚Ð°Ð»ÑŒÐ½Ð°Ñ ÐºÐ¾Ð¶Ð°" msgid "campaignBonus|Summon Earth" msgstr "Элементаль" msgid "campaignBonus|View Heroes" msgstr "Показать героев" msgid "campaignBonus|Ballista" msgstr "БаллиÑта" msgid "campaignBonus|Black Pearl" msgstr "Чёрн. Жемчужина" msgid "campaignBonus|Caster's Bracelet" msgstr "Маг. БраÑлет" msgid "campaignBonus|Defender Helm" msgstr "Шлем Защитника" msgid "campaignBonus|Breastplate" msgstr "Защитный ДоÑпех" msgid "campaignBonus|Dragon Sword" msgstr "Драконий Меч" msgid "campaignBonus|Fizbin Medal" msgstr "Символ Ðеудачи" msgid "campaignBonus|Foremost Scroll" msgstr "Ð’Ñ‹Ñший Свиток" msgid "campaignBonus|Gauntlets" msgstr "Перчатки" msgid "campaignBonus|Hideous Mask" msgstr "УжаÑÐ½Ð°Ñ ÐœÐ°Ñка" msgid "campaignBonus|Mage's Ring" msgstr "Кольцо Мага" msgid "campaignBonus|Major Scroll" msgstr "Большой Свиток" msgid "campaignBonus|Medal of Honor" msgstr "Медаль ДоблеÑти" msgid "campaignBonus|Medal of Valor" msgstr "Медаль Отваги" msgid "campaignBonus|Minor Scroll" msgstr "Малый Свиток" msgid "campaignBonus|Nomad Boots" msgstr "Сапоги Кочевника" msgid "campaignBonus|Power Axe" msgstr "Топор ВлаÑти" msgid "campaignBonus|Spiked Shield" msgstr "Шипованный Щит" msgid "campaignBonus|Stealth Shield" msgstr "Ðезримый Щит" msgid "campaignBonus|Tax Lien" msgstr "ПодатнаÑ" msgid "campaignBonus|Thunder Mace" msgstr "Ð“Ñ€Ð¾Ð¼Ð¾Ð²Ð°Ñ ÐŸÐ°Ð»Ð¸Ñ†Ð°" msgid "campaignBonus|Traveler's Boots" msgstr "Сапоги Путника" msgid "campaignBonus|White Pearl" msgstr "Бел. Жемчужина" msgid "campaignBonus|Basic Archery" msgstr "Баз. Ñтрельба" msgid "campaignBonus|Advanced Archery" msgstr "Прод. Ñтрельба" msgid "campaignBonus|Expert Archery" msgstr "ЭкÑп. Ñтрельба" msgid "campaignBonus|Basic Ballistics" msgstr "Баз. баллиÑтика" msgid "campaignBonus|Advanced Ballistics" msgstr "Прод. баллиÑтика" msgid "campaignBonus|Expert Ballistics" msgstr "ЭкÑп. баллиÑтика" msgid "campaignBonus|Basic Diplomacy" msgstr "Баз. дипломатиÑ" msgid "campaignBonus|Advanced Diplomacy" msgstr "Прод. дипломатиÑ" msgid "campaignBonus|Expert Diplomacy" msgstr "ЭкÑп. дипломатиÑ" msgid "campaignBonus|Basic Eagle Eye" msgstr "Баз. орлиный взор" msgid "campaignBonus|Advanced Eagle Eye" msgstr "Прод. орлиный взор" msgid "campaignBonus|Expert Eagle Eye" msgstr "ЭкÑп. орлиный взор" msgid "campaignBonus|Basic Estates" msgstr "Баз. казначей" msgid "campaignBonus|Advanced Estates" msgstr "Прод. казначей" msgid "campaignBonus|Expert Estates" msgstr "ЭкÑп. казначей" msgid "campaignBonus|Basic Leadership" msgstr "Баз. лидерÑтво" msgid "campaignBonus|Advanced Leadership" msgstr "Прод. лидерÑтво" msgid "campaignBonus|Expert Leadership" msgstr "ЭкÑп. лидерÑтво" msgid "campaignBonus|Basic Logistics" msgstr "Баз. логиÑтика" msgid "campaignBonus|Advanced Logistics" msgstr "Прод. логиÑтика" msgid "campaignBonus|Expert Logistics" msgstr "ЭкÑп. логиÑтика" msgid "campaignBonus|Basic Luck" msgstr "Баз. удача" msgid "campaignBonus|Advanced Luck" msgstr "Прод. удача" msgid "campaignBonus|Expert Luck" msgstr "ЭкÑп. удача" msgid "campaignBonus|Basic Mysticism" msgstr "Баз. миÑтицизм" msgid "campaignBonus|Advanced Mysticism" msgstr "Прод. миÑтицизм" msgid "campaignBonus|Expert Mysticism" msgstr "ЭкÑп. миÑтицизм" msgid "campaignBonus|Basic Navigation" msgstr "Баз. навигациÑ" msgid "campaignBonus|Advanced Navigation" msgstr "Прод. навигациÑ" msgid "campaignBonus|Expert Navigation" msgstr "ЭкÑп. навигациÑ" msgid "campaignBonus|Basic Necromancy" msgstr "Баз. некромантиÑ" msgid "campaignBonus|Advanced Necromancy" msgstr "Прод. некромантиÑ" msgid "campaignBonus|Expert Necromancy" msgstr "ЭкÑп. некромантиÑ" msgid "campaignBonus|Basic Pathfinding" msgstr "Баз. Ñледопыт" msgid "campaignBonus|Advanced Pathfinding" msgstr "Прод. Ñледопыт" msgid "campaignBonus|Expert Pathfinding" msgstr "ЭкÑп. Ñледопыт" msgid "campaignBonus|Basic Scouting" msgstr "Баз. разведка" msgid "campaignBonus|Advanced Scouting" msgstr "Прод. разведка" msgid "campaignBonus|Expert Scouting" msgstr "ЭкÑп. разведка" msgid "campaignBonus|Basic Wisdom" msgstr "Баз. мудроÑть" msgid "campaignBonus|Advanced Wisdom" msgstr "Прод. мудроÑть" msgid "campaignBonus|Expert Wisdom" msgstr "ЭкÑп. мудроÑть" msgid "" "The main hero will have \"%{artifact}\" artifact at the start of the " "scenario." msgstr "" "ОÑновной герой в начале ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ Ð±ÑƒÐ´ÐµÑ‚ обладать артефактом \"%{artifact}\"." msgid "" "The kingdom will receive %{amount} additional %{resource} at the start of " "the scenario." msgstr "" "КоролевÑтво в начале ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ Ð¿Ð¾Ð»ÑƒÑ‡Ð¸Ñ‚ дополнительные реÑурÑÑ‹: %{resource} +" "%{amount} ед." msgid "" "The kingdom will have %{amount} less %{resource} at the start of the " "scenario." msgstr "" "КоролевÑтво в начале ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ Ð¿Ð¾Ñ‚ÐµÑ€Ñет реÑурÑÑ‹: %{resource} -%{amount} ед." msgid "" "The main hero will have %{count} %{monster} at the start of the scenario." msgstr "" "Ðрмию оÑновного Ð³ÐµÑ€Ð¾Ñ Ð² начале ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ Ð¿Ð¾Ð¿Ð¾Ð»Ð½ÑÑ‚ %{monster} отрÑдом в " "%{count} ед." msgid "" "The main hero will have \"%{spell}\" spell at the start of the scenario." msgstr "ОÑновной герой в начале ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ Ð±ÑƒÐ´ÐµÑ‚ иметь заклинание \"%{spell}\"." msgid "The starting race of the scenario will be %{race}." msgstr "ÐÐ°Ñ‡Ð°Ð»ÑŒÐ½Ð°Ñ Ñ„Ñ€Ð°ÐºÑ†Ð¸Ñ ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ Ð±ÑƒÐ´ÐµÑ‚ %{race}." msgid "" "The main hero will have additional %{count} %{skill} at the start of the " "scenario." msgstr "" "ОÑновной герой в Ñтом Ñценарии получит Ð±Ð¾Ð½ÑƒÑ Ðº первичным навыкам: %{skill} +" "%{count}." msgid "The main hero will have %{skill} at the start of the scenario." msgstr "ОÑновной герой в начале ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ Ð±ÑƒÐ´ÐµÑ‚ иметь навык %{skill}." msgid "Roland" msgstr "Роланд" msgid "Archibald" msgstr "Ðрчибальд" msgid "Price of Loyalty" msgstr "Цена верноÑти" msgid "Voyage Home" msgstr "Дорога домой" msgid "Wizard's Isle" msgstr "ОÑтров чародеев" msgid "Descendants" msgstr "ÐаÑледники" msgid "The %{building} produces %{monster}." msgstr "" "%{building} - жилище, в котором %{monster} доÑтупны Ð´Ð»Ñ Ð½Ð°Ð¹Ð¼Ð° в Вашу армию." msgid "Requires:" msgstr "Требует:" msgid "Exit this menu without doing anything." msgstr "Выход, без изменений." msgid "Cannot build. You have already built here today." msgstr "Ðевозможно поÑтроить. Ð’Ñ‹ уже Ñтроили здеÑÑŒ ÑегоднÑ." msgid "For this action it is necessary to build a castle first." msgstr "Ð”Ð»Ñ Ñтого Вам нужно Ñперва поÑтроить замок." msgid "Cannot build %{name} because the castle is too far away from an ocean." msgstr "%{name} невозможно поÑтроить - замок Ñлишком далеко от океана." msgid "disable build." msgstr "отключить Ñтроение." msgid "Cannot afford %{name}." msgstr "ÐедоÑтаточно реÑурÑов Ð´Ð»Ñ Ð¿Ð¾Ñтройки." msgid "%{name} is already built." msgstr "Это здание уже поÑтроено." msgid "Cannot build %{name}." msgstr "Ðевозможно поÑтроить: %{name}." msgid "Build %{name}." msgstr "ПоÑтроить %{name}." msgid "Blackridge" msgstr "БлÑкридж" msgid "Hillstone" msgstr "ХилÑтон" msgid "Pinehurst" msgstr "ПайнхёрÑÑ‚" msgid "Whiteshield" msgstr "Вайтшилд" msgid "Woodhaven" msgstr "Вудхавен" msgid "Blackwind" msgstr "БлÑквинд" msgid "Bloodreign" msgstr "Бладрейн" msgid "Dragontooth" msgstr "ДрагонтуÑ" msgid "Greywind" msgstr "Грейвинд" msgid "Portsmith" msgstr "ПортÑмит" msgid "Atlantium" msgstr "Ðтлантиум" msgid "Middle Gate" msgstr "Мидлгейт" msgid "Sansobar" msgstr "Занзобар" msgid "Tundara" msgstr "Тундара" msgid "Vulcania" msgstr "ВулканиÑ" msgid "Baywatch" msgstr "Байвоч" msgid "Fountainhead" msgstr "Фонтанхед" msgid "Vertigo" msgstr "Вертиго" msgid "Wildabar" msgstr "Вилдабар" msgid "Winterkill" msgstr "Винтеркил" msgid "Brindamoor" msgstr "Бриндамур" msgid "Lakeside" msgstr "ЛÑйкÑайд" msgid "Nightshadow" msgstr "ÐайтшÑдоу" msgid "Olympus" msgstr "ОлимпуÑ" msgid "Sandcaster" msgstr "СÑндкаÑтер" msgid "Alamar" msgstr "Ðламар" msgid "Burlock" msgstr "Бурлок" msgid "Dragadune" msgstr "Драгадун" msgid "Kalindra" msgstr "Калиндра" msgid "Xabran" msgstr "КÑабран" msgid "Algary" msgstr "Ðлгари" msgid "Basenji" msgstr "БеÑенджи" msgid "Blackfang" msgstr "БлÑкфанг" msgid "New Dawn" msgstr "Ðью-Давн" msgid "Sorpigal" msgstr "Сорпигал" msgid "Avone" msgstr "Ðвон" msgid "Big Oak" msgstr "Биг-Оук" msgid "Chandler" msgstr "Чандлер" msgid "Erliquin" msgstr "Эрликуин" msgid "Hampshire" msgstr "Хампшир" msgid "Antioch" msgstr "Ðнтиох" msgid "Avalon" msgstr "Ðвалон" msgid "Roc Haven" msgstr "Рокхавен" msgid "South Mill" msgstr "СауÑмил" msgid "Weed Patch" msgstr "Вид-Патч" msgid "Brownston" msgstr "БраунÑтон" msgid "Hilltop" msgstr "Хилтоп" msgid "Weddington" msgstr "Ведингтон" msgid "Westfork" msgstr "ВеÑтфорк" msgid "Whittingham" msgstr "Витингем" msgid "Cathcart" msgstr "Кеткарт" msgid "Elk's Head" msgstr "ЭлькÑ-Хед" msgid "Roscomon" msgstr "РоÑкомон" msgid "Sherman" msgstr "Шерман" msgid "Yorksford" msgstr "ЙоркÑфорд" msgid "Blackburn" msgstr "БлÑкбёрн" msgid "Blacksford" msgstr "БлÑкÑфорд" msgid "Burton" msgstr "Бёртон" msgid "Pig's Eye" msgstr "Пигз-Ðй" msgid "Viper's Nest" msgstr "Гнездо гадюк" msgid "Fenton" msgstr "Фентон" msgid "Lankershire" msgstr "Ланкершир" msgid "Lombard" msgstr "Ломбард" msgid "Timberhill" msgstr "Тимберхил" msgid "Troy" msgstr "ТроÑ" msgid "Forder Oaks" msgstr "Фордер-ОукÑ" msgid "Meramec" msgstr "Меремек" msgid "Quick Silver" msgstr "КвикÑильвер" msgid "Westmoor" msgstr "ВеÑтмур" msgid "Willow" msgstr "Вилоу" msgid "Corackston" msgstr "КоракÑтоун" msgid "Sheltemburg" msgstr "Шелтембург" msgid "Cannot recruit - you already have a Hero in this town." msgstr "Ðайм невозможен. Ð’ замке уже еÑть герой." msgid "Cannot recruit - you have too many Heroes." msgstr "Ðайм невозможен. Ð’Ñ‹ уже имеете макÑимум героев." msgid "Cannot afford a Hero" msgstr "ÐедоÑтаточно ÑредÑтв Ð´Ð»Ñ Ð½Ð°Ð¹Ð¼Ð° героÑ." msgid "There is no room in the garrison for this army." msgstr "Ðет Ñвободного меÑта в армии." msgid "Fortifications" msgstr "УкреплениÑ" msgid "Farm" msgstr "Ферма" msgid "Thatched Hut" msgstr "Мазанка" msgid "Archery Range" msgstr "Стрельбище" msgid "Upg. Archery Range" msgstr "Полигон" msgid "Blacksmith" msgstr "Кузница" msgid "Upg. Blacksmith" msgstr "МаÑтерÑкаÑ" msgid "Armory" msgstr "ОружейнаÑ" msgid "Upg. Armory" msgstr "ÐÑ€Ñенал" msgid "Jousting Arena" msgstr "Ðрена" msgid "Upg. Jousting Arena" msgstr "РиÑталище" msgid "Cathedral" msgstr "Собор" msgid "Upg. Cathedral" msgstr "Храм" msgid "Coliseum" msgstr "Колизей" msgid "Garbage Heap" msgstr "Свалка" msgid "Hut" msgstr "Хижина" msgid "Stick Hut" msgstr "Халупа" msgid "Upg. Stick Hut" msgstr "Хибара" msgid "Den" msgstr "Логово" msgid "Adobe" msgstr "Глинобитный дом" msgid "Upg. Adobe" msgstr "Саманный дом" msgid "Bridge" msgstr "МоÑÑ‚" msgid "Upg. Bridge" msgstr "Царь-моÑÑ‚" msgid "Pyramid" msgstr "Пирамида" msgid "Rainbow" msgstr "Радуга" msgid "Crystal Garden" msgstr "ХруÑтальный Ñад" msgid "Treehouse" msgstr "Дома на дереве" msgid "Cottage" msgstr "Избушка" msgid "Upg. Cottage" msgstr "Хоромы" msgid "Stonehenge" msgstr "Менгиры" msgid "Upg. Stonehenge" msgstr "Стоунхендж" msgid "Fenced Meadow" msgstr "ÐžÐ³Ð¾Ñ€Ð¾Ð¶ÐµÐ½Ð½Ð°Ñ Ð»ÑƒÐ¶Ð°Ð¹ÐºÐ°" msgid "sorceress|Red Tower" msgstr "ÐÐ»Ð°Ñ Ð±Ð°ÑˆÐ½Ñ" msgid "Dungeon" msgstr "Сокровищница" msgid "Waterfall" msgstr "Водопад" msgid "Cave" msgstr "Пещера" msgid "Crypt" msgstr "Склеп" msgid "Nest" msgstr "Гнездо" msgid "Maze" msgstr "Лабиринт" msgid "Upg. Maze" msgstr "Большой лабиринт" msgid "Swamp" msgstr "Болото" msgid "Green Tower" msgstr "Ð—ÐµÐ»Ñ‘Ð½Ð°Ñ Ð±Ð°ÑˆÐ½Ñ" msgid "warlock|Red Tower" msgstr "КраÑÐ½Ð°Ñ Ð±Ð°ÑˆÐ½Ñ" msgid "Black Tower" msgstr "Ð§Ñ‘Ñ€Ð½Ð°Ñ Ð±Ð°ÑˆÐ½Ñ" msgid "Library" msgstr "Библиотека" msgid "Orchard" msgstr "Фруктовый Ñад" msgid "Habitat" msgstr "Ðора" msgid "Pen" msgstr "Хлев" msgid "Foundry" msgstr "ЛитейнаÑ" msgid "Upg. Foundry" msgstr "Фабрика" msgid "Cliff Nest" msgstr "Гнездовье" msgid "Ivory Tower" msgstr "Ð‘Ð°ÑˆÐ½Ñ Ñлоновой коÑти" msgid "Upg. Ivory Tower" msgstr "Обитель магов" msgid "Cloud Castle" msgstr "ÐебеÑный замок" msgid "Upg. Cloud Castle" msgstr "ÐебеÑный чертог" msgid "Storm" msgstr "БурÑ" msgid "Skull Pile" msgstr "Груда черепов" msgid "Excavation" msgstr "РаÑкопки" msgid "Graveyard" msgstr "Кладбище" msgid "Upg. Graveyard" msgstr "ПогоÑÑ‚" msgid "Upg. Pyramid" msgstr "Ð’ÐµÐ»Ð¸ÐºÐ°Ñ Ð¿Ð¸Ñ€Ð°Ð¼Ð¸Ð´Ð°" msgid "Mansion" msgstr "ОÑобнÑк" msgid "Upg. Mansion" msgstr "ПомеÑтье" msgid "Mausoleum" msgstr "Мавзолей" msgid "Upg. Mausoleum" msgstr "Ðекрополь" msgid "Laboratory" msgstr "ЛабораториÑ" msgid "Shrine" msgstr "Ðлтарь" msgid "" "The Fortifications increase the toughness of the walls, increasing the " "number of turns it takes to knock them down." msgstr "" "Ð£ÐºÑ€ÐµÐ¿Ð»ÐµÐ½Ð¸Ñ ÑƒÐ²ÐµÐ»Ð¸Ñ‡Ð¸Ð²Ð°ÑŽÑ‚ общую прочноÑть Ñтен, Ð´ÐµÐ»Ð°Ñ Ð¸Ñ… более уÑтойчивыми к " "разрушению." msgid "The Farm increases production of Peasants by %{count} per week." msgstr "Ферма увеличивает прироÑÑ‚ КреÑтьÑн на %{count} в неделю." msgid "" "The Coliseum provides inspiring spectacles to defending troops, raising " "their morale by two during combat." msgstr "" "ПредÑтавлениÑ, проводимые в колизее, увеличивают мораль защитников замка на " "2." msgid "The Garbage Heap increases production of Goblins by %{count} per week." msgstr "Свалка увеличивает прироÑÑ‚ Гоблинов на %{count} в неделю." msgid "The Rainbow increases the luck of the defending units by two." msgstr "Радуга увеличивает удачу защитников замка на 2." msgid "" "The Crystal Garden increases production of Sprites by %{count} per week." msgstr "ХруÑтальный Ñад увеличивает прироÑÑ‚ Фей на %{count} в неделю." msgid "The Dungeon increases the income of the town by %{count} gold per day." msgstr "Сокровищница приноÑит в казну %{count} золотых ежедневно." msgid "The Waterfall increases production of Centaurs by %{count} per week." msgstr "Водопад увеличивает прироÑÑ‚ Кентавров на %{count} в неделю." msgid "" "The Library increases the number of spells in the Guild by one for each " "level of the guild." msgstr "" "Библиотека увеличивает чиÑло заклинаний, доÑтупных в гильдии магов, на одно " "заклинание Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð³Ð¾ уровнÑ." msgid "The Orchard increases production of Halflings by %{count} per week." msgstr "Фруктовый Ñад увеличивает прироÑÑ‚ ПолуроÑликов на %{count} в неделю." msgid "The Storm adds +2 to the power of spells of a defending spell caster." msgstr "Ð‘ÑƒÑ€Ñ Ð´Ð¾Ð±Ð°Ð²Ð»Ñет 2 очка к Ñиле магии заклинателÑ, оборонÑющего замок." msgid "The Skull Pile increases production of Skeletons by %{count} per week." msgstr "Груда черепов увеличивает прироÑÑ‚ Скелетов на %{count} в неделю." msgid "Thieves' Guild" msgstr "Ð“Ð¸Ð»ÑŒÐ´Ð¸Ñ Ð²Ð¾Ñ€Ð¾Ð²" msgid "Tavern" msgstr "Таверна" msgid "Shipyard" msgstr "Верфь" msgid "Well" msgstr "Колодец" msgid "Statue" msgstr "СтатуÑ" msgid "Marketplace" msgstr "Рынок" msgid "Moat" msgstr "Ров" msgid "Castle" msgstr "Замок" msgid "Tent" msgstr "Шатёр" msgid "Captain's Quarters" msgstr "КапитанÑкий штаб" msgid "Mage Guild, Level 1" msgstr "Ð“Ð¸Ð»ÑŒÐ´Ð¸Ñ Ð¼Ð°Ð³Ð¾Ð² 1 ур." msgid "Mage Guild, Level 2" msgstr "Ð“Ð¸Ð»ÑŒÐ´Ð¸Ñ Ð¼Ð°Ð³Ð¾Ð² 2 ур." msgid "Mage Guild, Level 3" msgstr "Ð“Ð¸Ð»ÑŒÐ´Ð¸Ñ Ð¼Ð°Ð³Ð¾Ð² 3 ур." msgid "Mage Guild, Level 4" msgstr "Ð“Ð¸Ð»ÑŒÐ´Ð¸Ñ Ð¼Ð°Ð³Ð¾Ð² 4 ур." msgid "Mage Guild, Level 5" msgstr "Ð“Ð¸Ð»ÑŒÐ´Ð¸Ñ Ð¼Ð°Ð³Ð¾Ð² 5 ур." msgid "" "The Shrine increases the necromancy skill of all your necromancers by 10 " "percent." msgstr "Ðлтарь увеличивает навык некромантии вÑех Ваших некромантов на 10%." msgid "" "The Thieves' Guild provides information on enemy players. Thieves' Guilds " "can also provide scouting information on enemy towns. Additional Guilds " "provide more information." msgstr "" "Ð“Ð¸Ð»ÑŒÐ´Ð¸Ñ Ð²Ð¾Ñ€Ð¾Ð² предоÑтавлÑет информацию о врагах. Также, Ð“Ð¸Ð»ÑŒÐ´Ð¸Ñ Ð²Ð¾Ñ€Ð¾Ð² даёт " "разведывательную информацию о вражеÑких городах. Дополнительные гильдии дают " "больше информации." msgid "The Tavern increases morale for troops defending the castle." msgstr "Таверна повышает мораль защитников замка на 1." msgid "The Shipyard allows ships to be built." msgstr "Верфь позволÑет Вам Ñтроить корабли." msgid "" "The Well increases the growth rate of all dwellings by %{count} creatures " "per week." msgstr "Колодец увеличивает прироÑÑ‚ вÑех ÑущеÑтв в замке на %{count} в неделю." msgid "The Statue increases the town's income by %{count} gold per day." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ñ€Ð¸Ð½Ð¾Ñит в казну %{count} золотых ежедневно." msgid "The Left Turret provides extra firepower during castle combat." msgstr "Ð›ÐµÐ²Ð°Ñ Ð±Ð°ÑˆÐ½Ñ Ð½Ð°Ð½Ð¾Ñит дополнительный урон врагу во Ð²Ñ€ÐµÐ¼Ñ Ð¾Ñады замка." msgid "The Right Turret provides extra firepower during castle combat." msgstr "ÐŸÑ€Ð°Ð²Ð°Ñ Ð±Ð°ÑˆÐ½Ñ Ð½Ð°Ð½Ð¾Ñит дополнительный урон врагу во Ð²Ñ€ÐµÐ¼Ñ Ð¾Ñады замка." msgid "" "The Marketplace can be used to convert one type of resource into another. " "The more marketplaces you control, the better the exchange rate." msgstr "" "Рынок иÑпользуетÑÑ Ð´Ð»Ñ Ð¾Ð±Ð¼ÐµÐ½Ð° одного типа реÑурÑов на другой. Чем больше " "рынков, тем ниже цены." msgid "" "The Moat slows attacking units. Any unit entering the moat must end its turn " "there and becomes more vulnerable to attack." msgstr "" "Ров замедлÑет наÑтупление войÑк. ОтрÑд, желающий переÑечь ров, будет " "вынужден завершить первый ход внутри рва, Ñтав уÑзвимее к атаке." msgid "" "The Castle improves the town's defense and increases its income to %{count} " "gold per day." msgstr "" "Замок улучшает оборону города и ежедневно приноÑит в казну дополнительные " "%{count} золотых." msgid "" "The Tent provides workers to build a castle, provided the materials and the " "gold are available." msgstr "ЗдеÑÑŒ находÑÑ‚ÑÑ Ñ€Ð°Ð±Ð¾Ñ‡Ð¸Ðµ, которые могут возвеÑти замок." msgid "" "The Captain's Quarters provides a captain to assist in the castle's defense " "when no hero is present." msgstr "" "КапитанÑкий штаб позволÑет капитану руководить защитой замка, еÑли в нем " "отÑутÑтвует герой." msgid "" "The Mage Guild allows heroes to learn spells and replenish their spell " "points." msgstr "" "Ð“Ð¸Ð»ÑŒÐ´Ð¸Ñ Ð¼Ð°Ð³Ð¾Ð² позволÑет героÑм учить Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ Ð¸ воÑполнÑть очки магии." msgid "Recruit %{name}" msgstr "ÐанÑть: %{name}" msgid "Month: %{month}, Week: %{week}, Day: %{day}" msgstr "МеÑÑц: %{month}, ÐеделÑ: %{week}, День: %{day}" msgid "" "You must purchase a spell book to use the mage guild, but you currently have " "no room for a spell book. Try giving one of your artifacts to another hero." msgstr "" "Чтобы Ð’Ð°Ñ Ð¿Ñ€Ð¾Ð¿ÑƒÑтили в гильдию магов, Ð’Ñ‹ должны купить книгу заклинаний, но " "к Ñожалению, Ваша Ñумка и так переполнена артефактами. Попробуйте передать " "один из Ваших артефактов другому герою." msgid "Click to show next town." msgstr "Ðажмите, чтобы показать Ñледующий город." msgid "Show next town" msgstr "Следующий город" msgid "Click to show previous town." msgstr "Ðажмите, чтобы показать предыдущий город." msgid "Show previous town" msgstr "Предыдущий город" msgid "This town may not be upgraded to a castle." msgstr "Ð’ Ñтом городе Ð½ÐµÐ»ÑŒÐ·Ñ Ð¿Ð¾Ñтроить замок." msgid "Town" msgstr "Город" msgid "Exit Castle" msgstr "Покинуть замок" msgid "Exit Town" msgstr "Покинуть город" msgid "Show Income" msgstr "Показать доход" msgid "View Hero" msgstr "ПроÑмотреть героÑ" msgid "The above spells are available here." msgstr "ДоÑтупные заклинаниÑ." msgid "The spells the hero can learn have been added to their book." msgstr "ЗаклинаниÑ, доÑтупные герою, добавлены в Волшебную Книгу." msgid "A generous tip for the barkeep yields the following rumor:" msgstr "Щедрые чаевые вытÑнули из уÑÑ‚ трактирщика Ñледующий Ñлух:" msgid "Recruit Hero" msgstr "ÐанÑть героÑ" msgid "%{name} is a level %{value} %{race} " msgstr "%{name} - %{race} ÑƒÑ€Ð¾Ð²Ð½Ñ %{value} " msgid "with %{count} artifacts." msgstr "Ñ %{count} артефактами." msgid "with 1 artifact." msgstr "Ñ 1 артефактом." msgid "without artifacts." msgstr "без артефактов." msgid "Recruit %{name} the %{race}" msgstr "ÐанÑть %{name}, %{race}" msgid "" "'Spread' combat formation spreads the castle's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "Разомкнутый боевой порÑдок предпиÑывает армии замка занÑть позиции по вÑей " "ширине Ñтартового раÑÐ¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð²Ð¾Ð¹Ñк в бою Ñ Ð¸Ð½Ñ‚ÐµÑ€Ð²Ð°Ð»Ð°Ð¼Ð¸ между отрÑдами." msgid "" "'Grouped' combat formation bunches the castle's units together in the center " "of the castle's side of the battlefield." msgstr "" "Сомкнутый боевой порÑдок предпиÑывает отрÑдам армии замка организовать " "плотное поÑтроение поÑередине Ñтартового раÑÐ¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð²Ð¾Ð¹Ñк в бою." msgid "Spread Formation" msgstr "Разомкнутый Ñтрой" msgid "Grouped Formation" msgstr "Сомкнутый Ñтрой" msgid "Set garrison combat formation to 'Spread'" msgstr "Боевой порÑдок гарнизона - Разомкнутый" msgid "Set garrison combat formation to 'Grouped'" msgstr "Боевой порÑдок гарнизона - Сомкнутый" msgid "Exit Castle Options" msgstr "Закрыть ÐаÑтройки замка" msgid "Castle Options" msgstr "ÐаÑтройки замка" msgid "Not enough resources to recruit creatures." msgstr "ÐедоÑтаточно ÑредÑтв Ð´Ð»Ñ Ð½Ð°Ð¹Ð¼Ð° ÑущеÑтв." msgid "You are unable to recruit at this time, your ranks are full." msgstr "Ð”Ð»Ñ Ñтого отрÑда нет меÑта." msgid "No creatures available for purchase." msgstr "Ðет доÑтупных ÑущеÑтв Ð´Ð»Ñ Ð½Ð°Ð¹Ð¼Ð°." msgid "Recruit Creatures" msgstr "ÐанÑть ÑущеÑтв" msgid "Max" msgstr "МакÑимум" msgid "Hire all creatures in the town." msgstr "ÐанÑть вÑех ÑущеÑтв в городе." msgid "Available" msgstr "ДоÑтупно" msgid "Town Population Information and Statistics" msgstr "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ наÑелении города" msgid "Damage" msgstr "Урон" msgid "HP" msgstr "Здоровье" msgid "Growth" msgstr "ПрироÑÑ‚" msgid "week" msgstr "неделÑ" msgid "View World" msgstr "Обзор мира" msgid "View the entire world." msgstr "ОÑмотреть веÑÑŒ мир." msgid "Puzzle" msgstr "Головоломка" msgid "View the obelisk puzzle." msgstr "ПоÑмотреть найденные на обелиÑках фрагменты головоломки." msgid "Scenario Information" msgstr "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ Ñценарии" msgid "View information on the scenario you are currently playing." msgstr "ОпиÑание текущего ÑценариÑ." msgid "Dig for the Ultimate Artifact." msgstr "Копать в поиÑках великого артефакта." msgid "Digging" msgstr "Копать" msgid "Arena" msgstr "Ðрена" msgid "" "You enter the arena and face a pack of vicious lions. You handily defeat " "them, to the wild cheers of the crowd. Impressed by your skill, the aged " "trainer of gladiators agrees to train you in a skill of your choice." msgstr "" "Ð’Ñ‹ входите в арену и оказываетеÑÑŒ перед Ñтаей злобных львов. Ð’Ñ‹ ловко " "побеждаете их под дикие вопли толпы. Тренер гладиаторов, впечатлённый Вашим " "умением, ÑоглашаетÑÑ Ð¾Ð±ÑƒÑ‡Ð¸Ñ‚ÑŒ Ð’Ð°Ñ Ð²Ñ‹Ð±Ñ€Ð°Ð½Ð½Ð¾Ð¼Ñƒ навыку." msgid "Attack Skill" msgstr "Ðтака" msgid "Defense Skill" msgstr "Защита" msgid "Shots" msgstr "Ð’Ñ‹Ñтрелов" msgid "Shots Left" msgstr "Ð’Ñ‹Ñтрелов" msgid "Hit Points" msgstr "Здоровье" msgid "Hit Points Left" msgstr "Тек. здоровье" msgid "You can't afford to upgrade your troops!" msgstr "Ð’Ñ‹ не можете позволить Ñебе ÑƒÐ»ÑƒÑ‡ÑˆÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ð’Ð°ÑˆÐµÐ¹ армии!" msgid "" "Your troops can be upgraded, but it will cost you dearly. Do you wish to " "upgrade them?" msgstr "" "Ваши войÑка могут быть улучшены, но за Ñто придётÑÑ Ð·Ð°Ð¿Ð»Ð°Ñ‚Ð¸Ñ‚ÑŒ. Ð’Ñ‹ ÑоглаÑны?" msgid "Are you sure you want to dismiss this army?" msgstr "Ð’Ñ‹ уверены, что хотите раÑпуÑтить Ñтот отрÑд?" msgid "Upgrade" msgstr "Улучшить" msgid "Upgrade your troops." msgstr "Улучшить ваших ÑущеÑтв." msgid "Dismiss" msgstr "Уволить" msgid "Dismiss this army." msgstr "Уволить Ñтот отрÑд." msgid "" "A group of %{monster} with a desire for greater glory wish to join you.\n" "Do you accept?" msgstr "" "%{monster} в поиÑках Ñлавы желают к Вам приÑоединитьÑÑ.\n" "Ð’Ñ‹ ÑоглаÑны?" msgid "Followers" msgstr "ПоÑледователи" msgid "" "The %{monster} is swayed by your diplomatic tongue, and offers to join your " "army for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "ВоÑхищаÑÑÑŒ Вашими дипломатичеÑкими ÑпоÑобноÑÑ‚Ñми, %{monster} готовы вÑтупить " "в Вашу армию за %{gold} золотых.\n" "Ð’Ñ‹ ÑоглаÑны?" msgid "" "The creatures are swayed by your diplomatic\n" "tongue, and make you an offer:\n" "\n" msgstr "" "ВоÑхищаÑÑÑŒ Вашими дипломатичеÑкими ÑпоÑобноÑÑ‚Ñми,\n" "%{monster} решили Ñделать Вам предложение:\n" "\n" msgid "" "%{offer} of the %{total} %{monster} will join your army, and the rest will " "leave you alone, for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "Из %{total} только %{offer} приÑоединÑÑ‚ÑÑ Ðº Вашей армии, а оÑтальные оÑтавÑÑ‚ " "Ð’Ð°Ñ Ð² покое за %{gold} золотых.\n" "Ð’Ñ‹ ÑоглаÑны?" msgid "" "All %{offer} of the %{monster} will join your army for the sum of %{gold} " "gold.\n" "Do you accept?" msgstr "" "%{offer} %{monster} готовы приÑоединитьÑÑ Ðº Вашей армии за %{gold} золотых.\n" "Ð’Ñ‹ ÑоглаÑны?" msgid "(Rate: %{percent})" msgstr "%{percent}% от вÑех ÑущеÑтв" msgid "off" msgstr "выкл." msgid "Music" msgstr "Музыка" msgid "Effects" msgstr "Эффекты" msgid "MIDI" msgstr "MIDI" msgid "MIDI Expansion" msgstr "РаÑширение MIDI" msgid "External" msgstr "Внешний" msgid "Music Type" msgstr "Тип музыки" msgid "3D Audio" msgstr "3D Звук" msgid "Toggle ambient music level." msgstr "Уровень музыки." msgid "Toggle foreground sounds level." msgstr "Уровень звуков." msgid "Change the type of music." msgstr "Изменить тип музыки." msgid "Toggle 3D effects of foreground sounds." msgstr "3D Ñффекты Ð´Ð»Ñ Ð·Ð²ÑƒÐºÐ¾Ð² переднего плана." msgid "Build a new ship:" msgstr "ПоÑтроить новый корабль:" msgid "Resource cost:" msgstr "СтоимоÑть:" msgid "Total: " msgstr "Ð’ казне: " msgid "Need: " msgstr "Ðеобходимо: " msgid "Load Game" msgstr "Загрузка игры" msgid "No save files to load." msgstr "Ðет Ñохранённых игр." msgid "New Game" msgstr "ÐÐ¾Ð²Ð°Ñ Ð¸Ð³Ñ€Ð°" msgid "Start a single or multi-player game." msgstr "Ðачало одиночной или Ñетевой игры." msgid "Load a previously saved game." msgstr "Загрузка ранее Ñохранённой игры." msgid "Save Game" msgstr "Сохранить игру" msgid "Save the current game." msgstr "Сохранить текущую игру." msgid "Quit" msgstr "Выход" msgid "Quit out of Heroes of Might and Magic II." msgstr "Выйти из игры Heroes of Might and Magic II." msgid "Language" msgstr "Язык" msgid "Graphics" msgstr "Графика" msgid "Black & White" msgstr "Чёрно-белый" msgid "Mouse Cursor" msgstr "КурÑор мыши" msgid "Color" msgstr "Цветной" msgid "Text Support" msgstr "ТекÑтовое Ñопровождение" msgid "Change the language of the game." msgstr "Изменить Ñзык интерфейÑа." msgid "Select Game Language" msgstr "Выберите Ñзык игры:" msgid "Change the graphics settings of the game." msgstr "ÐаÑтроить параметры графики." msgid "Toggle colored cursor on or off. This is only an aesthetic choice." msgstr "Включить или выключить цветной курÑор. Это ÑÑтетичеÑкий выбор." msgid "" "Toggle text support mode to output extra information about windows and " "events in the game." msgstr "" "Режим текÑтовой поддержки, который выводит дополнительную информацию об " "окнах и ÑобытиÑÑ… в игре." msgid "" "Map\n" "Difficulty" msgstr "" "СложноÑть\n" "карты" msgid "" "Game\n" "Difficulty" msgstr "" "СложноÑть\n" "игры" msgid "Rating" msgstr "Рейтинг" msgid "Map Size" msgstr "" "Размер\n" "карты" msgid "Opponents" msgstr "Оппоненты" msgid "Class" msgstr "КлаÑÑ" msgid "" "Victory\n" "Conditions" msgstr "" "УÑловиÑ\n" "победы" msgid "" "Loss\n" "Conditions" msgstr "" "УÑловиÑ\n" "поражениÑ" msgid "First select recipients!" msgstr "Сначала выберите получателÑ!" msgid "You cannot select %{resource}!" msgstr "Ð’Ñ‹ не можете подарить %{resource}!" msgid "Select count %{resource}:" msgstr "Подарить %{resource}:" msgid "Select Recipients" msgstr "Выберите получателÑ" msgid "Your Funds" msgstr "Ваши реÑурÑÑ‹" msgid "Planned Gift" msgstr "Подарить" msgid "Gift from %{name}" msgstr "Дар от %{name}" msgid "Resolution" msgstr "Разрешение" msgid "Fullscreen" msgstr "ПолноÑкранный" msgid "window|Mode" msgstr "Режим" msgid "Windowed" msgstr "Ð’ окне" msgid "V-Sync" msgstr "Ð’ÐµÑ€Ñ‚Ð¸ÐºÐ°Ð»ÑŒÐ½Ð°Ñ ÑинхронизациÑ" msgid "on" msgstr "вкл." msgid "FPS" msgstr "FPS" msgid "System Info" msgstr "СиÑÑ‚ÐµÐ¼Ð½Ð°Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ" msgid "Change the resolution of the game." msgstr "Изменить разрешение Ñкрана." msgid "Select Game Resolution" msgstr "Выбор Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ñкрана" msgid "Toggle between fullscreen and windowed modes." msgstr "Переключение между полноÑкранным и оконным режимами." msgid "" "The V-Sync option can be enabled to resolve flickering issues on some " "monitors." msgstr "" "Ð’ÐµÑ€Ñ‚Ð¸ÐºÐ°Ð»ÑŒÐ½Ð°Ñ ÑÐ¸Ð½Ñ…Ñ€Ð¾Ð½Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð¼Ð¾Ð¶ÐµÑ‚ быть включена Ð´Ð»Ñ ÑƒÑÑ‚Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¼ÐµÑ€Ñ†Ð°Ð½Ð¸Ñ " "Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð½Ð° некоторых мониторах." msgid "Show extra information such as FPS and current time." msgstr "" "Показать дополнительную информацию, такую как FPS (количеÑтво отображаемых " "кадров в Ñекунду) и текущее времÑ." msgid "Hotkey: " msgstr "ГорÑÑ‡Ð°Ñ ÐºÐ»Ð°Ð²Ð¸ÑˆÐ°: " msgid "Category: " msgstr "КатегориÑ: " msgid "Event: " msgstr "Событие: " msgid "Hot Keys:" msgstr "ГорÑчие клавиши:" msgid "Evil" msgstr "Злой" msgid "Good" msgstr "Добрый" msgid "Interface Type" msgstr "ИнтерфейÑ" msgid "Hide" msgstr "Скрыть" msgid "Show" msgstr "Показать" msgid "Interface" msgstr "ИнтерфейÑ" msgid "Slow" msgstr "МедленнаÑ" msgid "Normal" msgstr "ОбычнаÑ" msgid "Fast" msgstr "БыÑтраÑ" msgid "Very Fast" msgstr "Очень быÑтраÑ" msgid "Scroll Speed" msgstr "СкороÑть прокрутки" msgid "Toggle the type of interface you want to use." msgstr "Переключить тип интерфейÑа, который Ð’Ñ‹ хотите иÑпользовать." msgid "Toggle interface visibility." msgstr "Показать/Ñкрыть интерфейÑ." msgid "Toggle colored cursor on or off. This is only an esthetic choice." msgstr "Включить или выключить цветной курÑор. Это ÑÑтетичеÑкий выбор." msgid "Sets the speed at which you scroll the window." msgstr "УÑтанавливает ÑкороÑть прокрутки окна на карте мира." msgid "Select Game Language:" msgstr "Выберите Ñзык игры:" msgid "Click to choose the selected language." msgstr "Ðажмите, чтобы применить выбранный Ñзык интерфейÑа игры." msgid "%{name} has gained a level." msgstr "%{name} получает уровень." msgid "%{skill} +1" msgstr "%{skill} +1" msgid "You have learned %{skill}." msgstr "Ð’Ñ‹ изучили %{skill}." msgid "" "%{name} has gained a level.\n" "\n" "%{skill} +1" msgstr "" "%{name} получает уровень.\n" "\n" "%{skill} +1" msgid "" "You may learn either:\n" "%{skill1}\n" "or\n" "%{skill2}" msgstr "" "Ð’Ñ‹ можете выучить:\n" "%{skill1}\n" "или\n" "%{skill2}" msgid "" "Please inspect our fine wares. If you feel like offering a trade, click on " "the items you wish to trade with and for." msgstr "" "ПоÑмотрите на наши прекраÑные товары. ЕÑли Ð’Ð°Ñ Ñ‡Ñ‚Ð¾-то заинтереÑует, нажмите " "на нужный предмет и выберите, на что хотите поменÑть." msgid "" "You have received quite a bargain. I expect to make no profit on the deal. " "Can I interest you in any of my other wares?" msgstr "" "ПоÑмотрите, Ñто довольно Ð²Ñ‹Ð³Ð¾Ð´Ð½Ð°Ñ Ñделка. Я не пытаюÑÑŒ нажитьÑÑ Ð½Ð° ней. Ð’Ð°Ñ " "интереÑует что-нибудь ещё из моих товаров?" msgid "I can offer you %{count} for 1 unit of %{resfrom}." msgstr "Я дам Вам %{count} за одну единицу %{resfrom}." msgid "I can offer you 1 unit of %{resto} for %{count} units of %{resfrom}." msgstr "" "Я могу предложить Вам\n" "1 %{resto} за %{count} %{resfrom}." msgid "Min" msgstr "Минимум" msgid "Qty to trade" msgstr "Обмен" msgid "Trading Post" msgstr "Базар" msgid "Your Resources" msgstr "Ваши реÑурÑÑ‹" msgid "Available Trades" msgstr "ДоÑтупно к обмену" msgid "n/a" msgstr "н/д" msgid "guarded by %{count} %{monster}" msgstr "охранÑет %{count} %{monster}" msgid "guarded by " msgstr "охранÑÐµÐ¼Ð°Ñ " msgid "(available: %{count})" msgstr "(ДоÑтупно: %{count})" msgid "(empty)" msgstr "(пуÑто)" msgid "already learned" msgstr "уже выучено" msgid "treeOfKnowledge|free" msgstr "беÑплатно" msgid "already claimed" msgstr "уже получены" msgid "not claimed" msgstr "не получены" msgid "already knows this skill" msgstr "уже знаете" msgid "already has max skills" msgstr "макÑимум навыков" msgid "(already visited)" msgstr "(поÑещено)" msgid "(not visited)" msgstr "(не поÑещено)" msgid "%{color} Barrier" msgstr "%{color} барьер" msgid "(tent visited)" msgstr "(шатёр поÑещён)" msgid "%{color} Tent" msgstr "" "%{color}\n" "шатёр путника" msgid "Road" msgstr "Дорога" msgid "(digging ok)" msgstr "(можно копать)" msgid "(no digging)" msgstr "(Ð½ÐµÐ»ÑŒÐ·Ñ ÐºÐ¾Ð¿Ð°Ñ‚ÑŒ)" msgid "penalty: %{cost}" msgstr "Штраф: %{cost}" msgid "Defenders:" msgstr "Защитники:" msgid "Unknown" msgstr "ÐеизвеÑтный" msgid "%{name} (Level %{level})" msgstr "%{name} (Уровень %{level})" msgid "Attack:" msgstr "Ðтака:" msgid "Defense:" msgstr "Защита:" msgid "Spell Power:" msgstr "Сила магии:" msgid "Knowledge:" msgstr "ЗнаниÑ:" msgid "Spell Points:" msgstr "Очки магии:" msgid "Move Points:" msgstr "Ð—Ð°Ð¿Ð°Ñ Ñ…Ð¾Ð´Ð°:" msgid "Uncharted Territory" msgstr "ÐÐµÐ¸Ð·Ð²ÐµÐ´Ð°Ð½Ð½Ð°Ñ Ñ‚ÐµÑ€Ñ€Ð¸Ñ‚Ð¾Ñ€Ð¸Ñ" msgid "Cost per troop:" msgstr "Цена за ÑущеÑтво:" msgid "Available: %{count}" msgstr "ДоÑтупно: %{count}" msgid "Number to buy:" msgstr "ÐанÑть:" msgid "Recruit selected monsters." msgstr "ÐанÑть выбранных ÑущеÑтв." msgid "Select maximum monsters to be recruited." msgstr "Выбрать макÑимальное количеÑтво ÑущеÑтв Ð´Ð»Ñ Ð½Ð°Ð¹Ð¼Ð°." msgid "Select only 1 monster to be recruited." msgstr "Выбрать вÑего 1 ÑущеÑтво Ð´Ð»Ñ Ð½Ð°Ð¹Ð¼Ð°." msgid "Select Game Resolution:" msgstr "Выбор Ñ€Ð°Ð·Ñ€ÐµÑˆÐµÐ½Ð¸Ñ Ñкрана:" msgid "Click to apply the selected resolution." msgstr "Ðажмите, чтобы применить выбранное разрешение Ñкрана." msgid "Click to apply the entered text." msgstr "Ðажмите, чтобы принÑть введённый текÑÑ‚." msgid "Click to open the Virtual Keyboard dialog." msgstr "Ðижмите, чтобы открыть окно виртуальной клавиатуры." msgid "Open Virtual Keyboard" msgstr "Открыть виртуальную клавиатуру" msgid "How many troops to move?" msgstr "Сколько ÑущеÑтв перенеÑти?" msgid "Fast separation into slots:" msgstr "БыÑтрое разделение:" msgid "Map: " msgstr "Карта: " msgid "" "\n" "\n" "Month: " msgstr "" "\n" "\n" "МеÑÑц: " msgid ", Week: " msgstr ", ÐеделÑ: " msgid ", Day: " msgstr ", День: " msgid "" "\n" "\n" "Location: " msgstr "" "\n" "\n" "Путь к файлу: " msgid "Click to save the current game." msgstr "Ðажмите, чтобы Ñохранить текущую игру." msgid "Click to load a previously saved game." msgstr "Ðажмите, чтобы загрузить Ñохранённую игру." msgid "Are you sure you want to delete file:" msgstr "Ð’Ñ‹ дейÑтвительно хотите удалить файл:" msgid "Warning!" msgstr "Внимание!" msgid "File to Save:" msgstr "Сохранить файл:" msgid "File to Load:" msgstr "Загрузить файл:" msgid "Accept the choice made." msgstr "Подтвердить Ваш выбор." msgid "%{color} %{race} hero" msgstr "%{color} герой - %{race}" msgid "Terrain object" msgstr "Объект ландшафта" msgid "Select Skill:" msgstr "Выберите навык:" msgid "Select Spell:" msgstr "Выберите заклинание:" msgid "Select Artifact:" msgstr "Выберите артефакт:" msgid "Select Monster:" msgstr "Выберите ÑущеÑтво:" msgid "Select Hero:" msgstr "Выберите героÑ:" msgid "Select Treasure:" msgstr "Выберите Ñокровище:" msgid "Select Ocean Object:" msgstr "Выберите объект Ð´Ð»Ñ Ð¾ÐºÐµÐ°Ð½Ð°:" msgid "Castle/town placing" msgstr "Размещение замка или города" msgid "doubleLinedRace|Neutral" msgstr "Ðейтральный" msgid "race|Random" msgstr "Случайно" msgid "Click to start placing the selected castle/town." msgstr "Ðажмите, чтобы раÑположить выбранный замок или город." msgid "Click to select town placing." msgstr "Ðажмите, чтобы выбрать размещение городов." msgid "Click to select castle placing." msgstr "Ðажмите, чтобы выбрать размещение замков." msgid "%{color} %{race} %{townOrCastle}" msgstr "%{color} %{townOrCastle} (%{race} )" msgid "race|Neutral" msgstr "Ðейтральный" msgid "You will place" msgstr "Ð’Ñ‹ будете размещать" msgid "Click to select this class." msgstr "Ðажмите Ñюда чтобы выбрать Ñту фракцию." msgid "Click to select this color." msgstr "Ðажмите Ñюда чтобы выбрать Ñтот цвет." msgid "Select Dwelling:" msgstr "Выберите жилище:" msgid "Select Landscape Object:" msgstr "Выберите объект ландшафта:" msgid "Mine placing" msgstr "Размещение иÑточника реÑурÑов" msgid "" "Resource\n" "type:" msgstr "" "Тип\n" "реÑурÑа:" msgid "%{mineName} appearance:" msgstr "%{mineName}, внешний вид:" msgid "Click to select %{object} as the resource generator to be placed." msgstr "Ðажмите, чтобы выбрать размещение иÑточника реÑурÑов - %{object}." msgid "Select Mountain Object:" msgstr "Выберите объект типа гора:" msgid "Select Rock Object:" msgstr "Выберите объект типа камень:" msgid "Select Tree Object:" msgstr "Выберите объект типа дерево:" msgid "Select Power Up Object:" msgstr "Выберите объект типа бонуÑ:" msgid "Select Adventure Object:" msgstr "Выберите интерактивный объект:" msgid "Map Type:\n" msgstr "Тип карты:\n" msgid "The Succession Wars" msgstr "Война за преÑтол" msgid "The Price of Loyalty" msgstr "Цена верноÑти" msgid "Resurrection" msgstr "Возрождение" msgid "Lose all your heroes and towns." msgstr "ПотерÑть вÑех героев и вÑе города." msgid "Lose a specific town." msgstr "ПотерÑть определённый город." msgid "Lose a specific hero." msgstr "ПотерÑть определённого героÑ." msgid "Run out of time. Fail to win by a certain point." msgstr "Ðе уÑпеть вовремÑ." msgid "Loss Condition" msgstr "УÑÐ»Ð¾Ð²Ð¸Ñ Ð¿Ð¾Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ" msgid "Defeat all enemy heroes and towns." msgstr "Победить вÑех противников и захватить вÑе города." msgid "Capture a specific town." msgstr "Захватить определённый город." msgid "Defeat a specific hero." msgstr "Победить определённого героÑ." msgid "Find a specific artifact." msgstr "Ðайти определённый артефакт." msgid "Your side defeats the opposing side." msgstr "Победить вмеÑте Ñ Ñоюзником вÑех противников." msgid "Accumulate a large amount of gold." msgstr "Ðакопить крупные запаÑÑ‹ золота." msgid "Victory Condition" msgstr "УÑÐ»Ð¾Ð²Ð¸Ñ Ð¿Ð¾Ð±ÐµÐ´Ñ‹" msgid "Map difficulty:" msgstr "СложноÑть карты:" msgid "N" msgstr "N" msgid "No maps exist at that size" msgstr "Ðет карт такого размера" msgid "Small Maps" msgstr "Маленькие карты" msgid "View only maps of size small (36 x 36)." msgstr "ПроÑмотр только маленьких карт (36Ñ…36)." msgid "Medium Maps" msgstr "Средние карты" msgid "View only maps of size medium (72 x 72)." msgstr "ПроÑмотр только Ñредних карт (72Ñ…72)." msgid "Large Maps" msgstr "Большие карты" msgid "View only maps of size large (108 x 108)." msgstr "ПроÑмотр только больших карт (108Ñ…108)." msgid "Extra Large Maps" msgstr "Огромные карты" msgid "View only maps of size extra large (144 x 144)." msgstr "ПроÑмотр только огромных карт (144Ñ…144)." msgid "All Maps" msgstr "Ð’Ñе карты" msgid "View all maps, regardless of size." msgstr "ПроÑмотр вÑех карт, незавиÑимо от размера." msgid "Players Icon" msgstr "Значок игроков" msgid "" "Indicates how many players total are in the scenario. Any positions not " "occupied by humans will be occupied by computer players." msgstr "" "Обозначает количеÑтво игроков в данном Ñценарии. При отÑутÑтвии игроков-" "людей их меÑта занимает компьютер." msgid "" "Indicates whether the map\n" "is small (36 x 36), medium\n" "(72 x 72), large (108 x 108),\n" "or extra large (144 x 144)." msgstr "" "Обозначает размер карты:\n" "Ð¼Ð°Ð»ÐµÐ½ÑŒÐºÐ°Ñ (36Ñ…36), ÑреднÑÑ\n" "(72Ñ…72), Ð±Ð¾Ð»ÑŒÑˆÐ°Ñ (108Ñ…108)\n" "или Ð¾Ð³Ñ€Ð¾Ð¼Ð½Ð°Ñ (144Ñ…144)." msgid "Size Icon" msgstr "Значок размера" msgid "" "Indicates whether the map is made for \"The Succession Wars\" or \"The Price " "of Loyalty\" version of the game." msgstr "" "Указывает, Ñоздана ли карта Ð´Ð»Ñ Ð²ÐµÑ€Ñии игры \"Война за преÑтол\" или \"Цена " "верноÑти\"." msgid "Map Type" msgstr "Тип карты" msgid "Selected Name" msgstr "Ðазвание карты" msgid "The name of the currently selected map." msgstr "Ðазвание выбранной карты." msgid "Selected Map Difficulty" msgstr "СложноÑть игры" msgid "" "The map difficulty of the currently selected map. The map difficulty is " "determined by the scenario designer. More difficult maps might include more " "or stronger enemies, fewer resources, or other special conditions making " "things tougher for the human player." msgstr "" "СложноÑть выбранной карты. Этот параметр задаётÑÑ Ð°Ð²Ñ‚Ð¾Ñ€Ð¾Ð¼ карты в редакторе. " "Более Ñложные карты могут иметь больше (или более Ñильных) врагов, меньше " "реÑурÑов или другие оÑобые уÑловиÑ, оÑложнÑющие игру человеку." msgid "Selected Description" msgstr "ОпиÑание" msgid "The description of the currently selected map." msgstr "РаÑширенное опиÑание выбранной карты." msgid "Jump" msgstr "Прыжком" msgid "Hero Speed" msgstr "Скор. героев" msgid "Don't Show" msgstr "Скрыть" msgid "Enemy Speed" msgstr "Скор. врагов" msgid "Auto Resolve" msgstr "Ðвто, Ñ Ð¼Ð°Ð³Ð¸ÐµÐ¹" msgid "Auto, No Spells" msgstr "Ðвто, без магии" msgid "Battles" msgstr "Битвы" msgid "autoBattle|Manual" msgstr "Вручную" msgid "Change the speed at which your heroes move on the main screen." msgstr "" "Изменить ÑкороÑть, Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ð¾Ð¹ Ваши герои перемещаютÑÑ Ð½Ð° главном Ñкране." msgid "" "Sets the speed that A.I. heroes move at. You can also elect not to view A.I. " "movement at all." msgstr "" "УÑтанавливает ÑкороÑть, Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ð¾Ð¹ двигаютÑÑ Ð³ÐµÑ€Ð¾Ð¸ ИИ. Ð’Ñ‹ также можете не " "проÑматривать движение ИИ вовÑе." msgid "Change the interface settings of the game." msgstr "ÐаÑтроить параметры интерфейÑа игры." msgid "Interface Settings" msgstr "ÐаÑтройки интерфейÑа" msgid "Toggle instant battle mode." msgstr "Режим мгновенного боÑ." msgid "Att." msgstr "Ðтк." msgid "Def." msgstr "Защ." msgid "Power" msgstr "Сила м." msgid "Knowl" msgstr "Знан." msgid "1st" msgstr "1-ый" msgid "2nd" msgstr "2-ой" msgid "3rd" msgstr "3-ий" msgid "4th" msgstr "4-ый" msgid "5th" msgstr "5-ый" msgid "6th" msgstr "6-ой" msgid "Oracle: Player Rankings" msgstr "Оракул: Рейтинг Игроков" msgid "Thieves' Guild: Player Rankings" msgstr "Ð“Ð¸Ð»ÑŒÐ´Ð¸Ñ Ð’Ð¾Ñ€Ð¾Ð²: Рейтинг Игроков" msgid "Number of Towns:" msgstr "Городов:" msgid "Number of Castles:" msgstr "Замков:" msgid "Number of Heroes:" msgstr "Героев:" msgid "Gold in Treasury:" msgstr "Золота в казне:" msgid "Wood & Ore:" msgstr "Дерево и руда:" msgid "Gems, Cr, Slf & Mer:" msgstr "Прочие реÑурÑÑ‹:" msgid "Obelisks Found:" msgstr "Ðайдено обелиÑков:" msgid "Artifacts:" msgstr "Ðртефакты:" msgid "Total Army Strength:" msgstr "ÐžÐ±Ñ‰Ð°Ñ Ñила армии:" msgid "Income:" msgstr "Доход:" msgid "Best Hero:" msgstr "Лучший герой:" msgid "Best Hero Stats:" msgstr "Параметры героÑ:" msgid "Personality:" msgstr "Характер:" msgid "Best Monster:" msgstr "Лучший отрÑд:" msgid "Map filename" msgstr "Ðазвание файла карты" msgid "" "Are you sure you want to load a new map? (Any unsaved changes to the current " "map will be lost.)" msgstr "" "Ð’Ñ‹ дейÑтвительно хотите загрузить новую карту? (Любые неÑохранённые " "Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð´Ð°Ð½Ð½Ð¾Ð¹ карты будут потерÑны.)" msgid "" "Are you sure you want to create a new map? (Any unsaved changes to the " "current map will be lost.)" msgstr "" "Ð’Ñ‹ дейÑтвительно хотите Ñоздать новую карту? (Любые неÑохранённые Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ " "данной карты будут потерÑны.)" msgid "" "Create a new map, either from scratch or using the random map generator." msgstr "" "Создать новую карту либо Ñ Ð½ÑƒÐ»Ñ, либо Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ генератора Ñлучайных карт." msgid "New Map" msgstr "ÐÐ¾Ð²Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð°" msgid "Load Map" msgstr "Загрузить карту" msgid "Load an existing map." msgstr "Загрузить ÑущеÑтвующую карту." msgid "Save Map" msgstr "Сохранить карту" msgid "Save the current map." msgstr "Сохранить текущую карту." msgid "Quit out of the map editor." msgstr "Выйти из редактора карт." msgid "Objects cannot be placed outside the map." msgstr "Объекты Ð½ÐµÐ»ÑŒÐ·Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‰Ð°Ñ‚ÑŒ за пределами карты." msgid "Monsters cannot be placed on water." msgstr "СущеÑтв Ð½ÐµÐ»ÑŒÐ·Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‰Ð°Ñ‚ÑŒ на воде." msgid "Choose a tile which does not contain any objects." msgstr "Выберите Ñчейку карты, на которой нет никаких объектов." msgid "Treasures cannot be placed on water." msgstr "Сокровища Ð½ÐµÐ»ÑŒÐ·Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‰Ð°Ñ‚ÑŒ на воде." msgid "Heroes cannot be placed on water." msgstr "Героев Ð½ÐµÐ»ÑŒÐ·Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‰Ð°Ñ‚ÑŒ на воде." msgid "" "A maximum of %{count} heroes of the same color can be placed on the map." msgstr "Ðа карте можно размеÑтить макÑимум %{count} героев одного цвета." msgid "Artifacts cannot be placed on water." msgstr "Ðртефакты Ð½ÐµÐ»ÑŒÐ·Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‰Ð°Ñ‚ÑŒ на воде." msgid "Mountains cannot be placed on water." msgstr "Горы Ð½ÐµÐ»ÑŒÐ·Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‰Ð°Ñ‚ÑŒ на воде." msgid "Rocks cannot be placed on water." msgstr "Камни Ð½ÐµÐ»ÑŒÐ·Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‰Ð°Ñ‚ÑŒ на воде." msgid "Trees cannot be placed on water." msgstr "Ð”ÐµÑ€ÐµÐ²ÑŒÑ Ð½ÐµÐ»ÑŒÐ·Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‰Ð°Ñ‚ÑŒ на воде." msgid "Ocean object must be placed on water." msgstr "Объекты Ð´Ð»Ñ Ð¾ÐºÐµÐ°Ð½Ð° можно размещать только на воде." msgid "Landscape objects cannot be placed on water." msgstr "Объекты ландшафта Ð½ÐµÐ»ÑŒÐ·Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‰Ð°Ñ‚ÑŒ на воде." msgid "Towns cannot be placed on water." msgstr "Города Ð½ÐµÐ»ÑŒÐ·Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‰Ð°Ñ‚ÑŒ на воде." msgid "Mines cannot be placed on water." msgstr "ИÑточники реÑурÑов Ð½ÐµÐ»ÑŒÐ·Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‰Ð°Ñ‚ÑŒ на воде." msgid "Dwellings cannot be placed on water." msgstr "Жилища Ð½ÐµÐ»ÑŒÐ·Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‰Ð°Ñ‚ÑŒ на воде." msgid "Power-ups cannot be placed on water." msgstr "БонуÑÑ‹ Ð½ÐµÐ»ÑŒÐ·Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‰Ð°Ñ‚ÑŒ на воде." msgid "Adventure objects cannot be placed on water." msgstr "Интерактивные объекты Ð½ÐµÐ»ÑŒÐ·Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‰Ð°Ñ‚ÑŒ на воде." msgid "Used to place %{object}." msgstr "ИÑпользуетÑÑ Ð´Ð»Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð¾Ð² типа %{object}." msgid "Select object type" msgstr "Выберите подтип" msgid "" "Click here to\n" "select a monster." msgstr "" "Ðажмите здеÑÑŒ,\n" "чтобы выбрать\n" "ÑущеÑтво." msgid "" "Click here to\n" "select another monster." msgstr "" "Ðажмите здеÑÑŒ,\n" "чтобы выбрать\n" "другое ÑущеÑтво." msgid "" "Cell\n" "Details" msgstr "" "Детали\n" "объекта" msgid "Roads" msgstr "Дороги" msgid "Streams" msgstr "Реки" msgid "Erase" msgstr "ЛаÑтик" msgid "Mountains" msgstr "Горы" msgid "Rocks" msgstr "Камни" msgid "Trees" msgstr "ДеревьÑ" msgid "Water Objects" msgstr "Объекты воды" msgid "Miscellaneous" msgstr "Прочие" msgid "Artifacts" msgstr "Ðртефакты" msgid "Dwellings" msgstr "Жилища" msgid "Mines" msgstr "ИÑточники реÑурÑов" msgid "Power-ups" msgstr "БонуÑÑ‹" msgid "Treasures" msgstr "Сокровища" msgid "Towns" msgstr "Города" msgid "Heroes" msgstr "Герои" msgid "editorErasure|Terrain" msgstr "" msgid "editorErasure|Castles" msgstr "" msgid "editorErasure|Monsters" msgstr "" msgid "editorErasure|Heroes" msgstr "" msgid "editorErasure|Artifacts" msgstr "" msgid "editorErasure|Roads" msgstr "" msgid "editorErasure|Streams" msgstr "" msgid "editorErasure|Treasures" msgstr "" msgid "" "Draws terrain in\n" "%{size} by %{size} square increments." msgstr "ЗаполнÑет ландшафт в облаÑти %{size} x %{size}." msgid "" "Erases objects in\n" "%{size} by %{size} square increments." msgstr "Стирает объекты в облаÑти %{size} x %{size}." msgid "Small Brush" msgstr "ÐœÐ°Ð»Ð°Ñ ÐºÐ¸Ñть" msgid "Medium Brush" msgstr "СреднÑÑ ÐºÐ¸Ñть" msgid "Large Brush" msgstr "Ð‘Ð¾Ð»ÑŒÑˆÐ°Ñ ÐºÐ¸Ñть" msgid "Area Fill" msgstr "Заполнить облаÑть" msgid "Used to click and drag for filling in large areas." msgstr "ИÑпользуетÑÑ Ð´Ð»Ñ Ð·Ð°Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ð²Ñей выделенной облаÑти." msgid "Clear Area" msgstr "ОчиÑтить облаÑть" msgid "Used to click and drag for clearing large areas." msgstr "ИÑпользуетÑÑ Ð´Ð»Ñ Ð¾Ñ‡Ð¸Ñтки вÑей выделенной облаÑти." msgid "" "Costs %{rate} times normal movement for all heroes. (Pathfinding reduces or " "eliminates the penalty.)" msgstr "" "Ð”Ð»Ñ Ð²Ñех героев очки Ð¿ÐµÑ€ÐµÐ´Ð²Ð¸Ð¶ÐµÐ½Ð¸Ñ Ñ‚Ñ€Ð°Ñ‚ÑÑ‚ÑÑ Ð² %{rate} раза больше. (Ðавык " "\"Следопыт\" Ñнижает или игнорирует данный штраф.)" msgid "Traversable only by boat." msgstr "Передвижение возможно только на кораблÑÑ…." msgid "No special modifiers." msgstr "Ðет оÑобых модификаторов." msgid "Toggle the erasure of %{type} objects." msgstr "Удаление объектов типа: %{type}." msgid "" "Objects of this type will be deleted with the Erase tool. Left-click here to " "deselect this type. Press and hold this button to deselect all other object " "types." msgstr "" "Объекты данного типа будут удалены инÑтрументом \"ЛаÑтик\". Ðажмите левую " "кнопку мыши, чтобы иÑключить удаление объектов данного типа. Ðажмите и " "удерживайте Ñту кнопку, чтобы инÑтрумент ÐЕ удалÑл объекты вÑех оÑтальных " "типов." msgid "" "Objects of this type will NOT be deleted with the Erase tool. Left-click " "here to select this type. Press and hold this button to select all other " "object types." msgstr "" "Объекты данного типа ÐЕ будут удалены инÑтрументом \"ЛаÑтик\". Ðажмите левую " "кнопку мыши, чтобы включить удаление объектов данного типа. Ðажмите и " "удерживайте Ñту кнопку, чтобы инÑтрумент удалÑл объекты вÑех оÑтальных типов." msgid "Terrain Mode" msgstr "Почва" msgid "Used to draw the underlying grass, dirt, water, etc. on the map." msgstr "ИÑпользуетÑÑ Ð´Ð»Ñ ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ñ€ÐµÐ»ÑŒÐµÑ„Ð° карты (травы, грÑзи, воды и Ñ‚.п.)." msgid "Landscape Objects Mode" msgstr "Режим Ñ€Ð°Ð·Ð¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð¾Ð² ландшафта" msgid "" "Used to place landscape objects (mountains, rocks, trees, etc.) on the map." msgstr "" "ИÑпользуетÑÑ Ð´Ð»Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð½Ð° карте объектов ландшафта (гор, камней, " "деревьев и Ñ‚.п.)." msgid "Detail Mode" msgstr "ХарактериÑтики" msgid "Used for special editing of monsters, heroes and towns." msgstr "" "ИÑпользуетÑÑ Ð´Ð»Ñ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ñпециальных параметров ÑущеÑтв, героев и " "городов." msgid "Adventure Objects Mode" msgstr "Режим Ñ€Ð°Ð·Ð¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð¸Ð½Ñ‚ÐµÑ€Ð°ÐºÑ‚Ð¸Ð²Ð½Ñ‹Ñ… объектов" msgid "" "Used to place adventure objects (artifacts, dwellings, mines, treasures, " "etc.) on the map." msgstr "" "ИÑпользуетÑÑ Ð´Ð»Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð½Ð° карте интерактивных объектов (артефактов, " "жилищ, шахт, Ñокровищ и Ñ‚.п.)." msgid "Kingdom Objects Mode" msgstr "Режим Ñ€Ð°Ð·Ð¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð¾Ð² королевÑтва" msgid "Used to place kingdom objects (towns, castles and heroes) on the map." msgstr "" "ИÑпользуетÑÑ Ð´Ð»Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð½Ð° карте объектов королевÑтва (городов, замков и " "героев)." msgid "Monsters Mode" msgstr "Режим Ñ€Ð°Ð·Ð¼ÐµÑ‰ÐµÐ½Ð¸Ñ ÑущеÑтв" msgid "Used to place monsters on the map." msgstr "ИÑпользуетÑÑ Ð´Ð»Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð¾Ñ‚Ñ€Ñдов ÑущеÑтв на карте." msgid "Allows you to draw streams by clicking and dragging." msgstr "ПозволÑет прокладывать руÑла рек." msgid "Stream Mode" msgstr "Реки" msgid "Allows you to draw roads by clicking and dragging." msgstr "ПозволÑет прокладывать дороги." msgid "Road Mode" msgstr "Дороги" msgid "Erase Mode" msgstr "ЛаÑтик" msgid "Used to erase objects from the map." msgstr "ИÑпользуетÑÑ Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð¾Ð² Ñ ÐºÐ°Ñ€Ñ‚Ñ‹." msgid "Change between zoom and normal view." msgstr "Сменить обычный режим на маÑштабированный." msgid "Magnify" msgstr "Лупа" msgid "Undo" msgstr "Отменить" msgid "Undo your last action." msgstr "Отменить поÑледнее дейÑтвие." msgid "Create a new map either from scratch or using the random map generator." msgstr "" "Создать новую карту либо Ñ Ð½ÑƒÐ»Ñ, либо Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ генератора Ñлучайных карт." msgid "Edit map title, description, and other general information." msgstr "Изменить название карты, опиÑание и прочие параметры." msgid "Specifications" msgstr "ХарактериÑтики" msgid "File Options" msgstr "ÐаÑтройки файла карты" msgid "" "Open the file options menu, where you can save or load maps, or quit out of " "the editor." msgstr "" "Открывает меню, где Ð’Ñ‹ можете Ñоздать новую карту, загрузить или Ñохранить " "уже готовую, а также выйти из редактора." msgid "View the editor system options, which let you customize the editor." msgstr "Открывает окно ÑиÑтемных наÑтроек редактора карт." msgid "Create a map that is %{size} squares wide and %{size} squares high." msgstr "Создать карту размером %{size}x%{size} клеток." msgid "Cancel back to the New Map menu." msgstr "ВернутьÑÑ Ð² меню ÑÐ¾Ð·Ð´Ð°Ð½Ð¸Ñ Ð½Ð¾Ð²Ð¾Ð¹ карты." msgid "Cancel back to the main menu." msgstr "ВернутьÑÑ Ð² главное меню." msgid "From Scratch" msgstr "С нулÑ" msgid "Start from scratch with a blank map." msgstr "Создать карту Ñ Ð½ÑƒÐ»Ñ." msgid "Create a randomly generated map." msgstr "Создать Ñлучайную карту." msgid "Random" msgstr "Ð¡Ð»ÑƒÑ‡Ð°Ð¹Ð½Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð°" msgid "Cancel back to the Map Editor main menu." msgstr "ВернутьÑÑ Ð² главное меню редактора карт." msgid "No maps available!" msgstr "Ðет доÑтупных карт!" msgid "Warning" msgstr "Внимание" msgid "difficulty|Easy" msgstr "ПроÑтаÑ" msgid "difficulty|Normal" msgstr "ÐормальнаÑ" msgid "difficulty|Hard" msgstr "СложнаÑ" msgid "difficulty|Expert" msgstr "ЭкÑперт" msgid "difficulty|Impossible" msgstr "ÐевозможнаÑ" msgid "and more..." msgstr "и Ñ‚.д." msgid "Easy" msgstr "ЛёгкаÑ" msgid "Hard" msgstr "ТÑжёлаÑ" msgid "Campaign Difficulty" msgstr "СложноÑть кампании" msgid "" "Choose this difficulty if you want to prefer game story over challenge. AI " "is weaker in comparison with normal difficulty." msgstr "" "Выберите данный уровень ÑложноÑти, еÑли Ð’Ñ‹ предпочетаете Ñюжет, а не Ñложные " "ÑражениÑ. ИИ Ñлабее, чем обычно." msgid "" "Choose this difficulty to enjoy the campaign as per the original design." msgstr "" "Выберите данный уровень ÑложноÑти, чтобы наÑладитьÑÑ ÐºÐ°Ð¼Ð¿Ð°Ð½Ð¸ÐµÐ¹, как она " "первоначально задумывалаÑÑŒ." msgid "" "Choose this difficulty if you want challenge. AI is stronger in comparison " "with normal difficulty." msgstr "" "Выберите данный уровень ÑложноÑти, еÑли хотите Ñерьёзных Ñражений. ИИ " "Ñильнее, чем обычно." msgid "" "Congratulations!\n" "\n" "Days: %{days}\n" msgstr "" "ПоздравлÑем!\n" "\n" "Дней: %{days}\n" msgid "" "\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" "\n" "СложноÑть: %{difficulty}\n" "\n" msgid "" "Score: %{score}\n" "\n" "Rating:\n" "%{rating}" msgstr "" "Очки: %{score}\n" "\n" "Рейтинг:\n" "%{rating}" msgid "Start the selected scenario." msgstr "Ðачать выбранный Ñценарий." msgid "View Intro" msgstr "ПоÑмотреть вÑтупление" msgid "View Intro videos for the current state of the campaign." msgstr "ПроÑмотреть вÑтупительное видео к текущему Ñценарию кампании." msgid "" "Select the campaign difficulty. This can be lowered at any point during the " "campaign." msgstr "Выбор ÑложноÑти кампании. СложноÑть можно понизить в любой момент." msgid "Restart the current scenario." msgstr "Ðачать Ñначала текущий Ñценарий." msgid "Difficulty" msgstr "СложноÑть" msgid "" "You have changed to a lower difficulty for the campaign. You will not be " "able to revert this after this point. The high score will be calculated " "based solely on the new difficulty. Do you want to proceed?" msgstr "" "Ð’Ñ‹ понизили ÑложноÑть кампании и позже не Ñможете вернуть её обратно. " "Рейтинг за прохождение кампании будет раÑÑчитыватьÑÑ Ð¸Ñключительно на оÑнове " "новой ÑложноÑти. Ð’Ñ‹ хотите продолжить?" msgid "Are you sure you want to restart this scenario?" msgstr "Ð’Ñ‹ дейÑтвительно хотите начать Ñтот Ñценарий Ñначала?" msgid "Campaign Scenario loading failure" msgstr "Сбой загрузки ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ ÐºÐ°Ð¼Ð¿Ð°Ð½Ð¸Ð¸" msgid "Please make sure that campaign files are correct and present." msgstr "ПожалуйÑта, убедитеÑÑŒ, что файлы кампании верны и приÑутÑтвуют." msgid "Days spent" msgstr "Затрачено дней" msgid "The number of days spent on this campaign." msgstr "КоличеÑтво дней, потраченных на Ñту кампанию." msgid "Project Coordination and Core Development" msgstr "Координатор проекта и оÑновной разработчик" msgid "Development" msgstr "Разработка" msgid "Visit us at " msgstr "ПоÑетите Ð½Ð°Ñ " msgid "QA and Support" msgstr "Контроль качеÑтва и поддержка" msgid "Dev and Support" msgstr "Разработка и поддержка" msgid "Special Thanks to" msgstr "ОÑÐ¾Ð±Ð°Ñ Ð±Ð»Ð°Ð³Ð¾Ð´Ð°Ñ€Ð½Ð¾Ñть" msgid "and many other contributors!" msgstr "и многим другим учаÑтникам!" msgid "and many-many other supporters!" msgstr "и многим другим, кто поддерживает наÑ!" msgid "Support us at" msgstr "Поддержите наÑ" msgid "local-donation-platform|https://www.patreon.com/fheroes2" msgstr "" "https://www.patreon.com/fheroes2\n" "https://boosty.to/fheroes2" msgid "Connect with us at" msgstr "ПриÑоединÑйтеÑÑŒ к нам" msgid "local-social-network|https://www.facebook.com/groups/fheroes2" msgstr "" "https://vk.com/fheroes2\n" "https://www.facebook.com/groups/fheroes2" msgid "Need help with the game?" msgstr "Вам нужна помощь по игре?" msgid "Original project before 0.7" msgstr "Разработчик до 0.7" msgid "Heroes of Might and Magic II: The Succession Wars team" msgstr "Разработчики Heroes of Might and Magic II: The Succession Wars" msgid "Designed and Directed" msgstr "Геймдизайнер" msgid "Programming and Design" msgstr "ПрограммиÑÑ‚ и дизайнер" msgid "Executive Producer" msgstr "ИÑполнительный продюÑер" msgid "Producer" msgstr "ПродюÑер" msgid "Additional Design" msgstr "Дополнительные дизайнеры" msgid "Additional Programming" msgstr "Дополнительные программиÑты" msgid "Musical Production" msgstr "Музыкальный продюÑер" msgid "Music and Sound Design" msgstr "Композиторы и звукорежиÑÑёры" msgid "Vocalists" msgstr "ВокалиÑты" msgid "Art Director" msgstr "Главный художник" msgid "Assistant Art Director" msgstr "Помощник главного художника" msgid "Artists" msgstr "Художники" msgid "QA Manager" msgstr "Менеджер по контролю качеÑтва" msgid "QA" msgstr "Контроль качеÑтва" msgid "Writing" msgstr "Главный ÑценариÑÑ‚" msgid "Manual and Helpfile" msgstr "Помощь и Ñправка" msgid "Scenarios" msgstr "Разработка карт" msgid "Heroes of Might and Magic II: The Price of Loyalty team" msgstr "Разработчики Heroes of Might and Magic II: The Price of Loyalty" msgid "Design Lead" msgstr "Ведущий дизайнер" msgid "Designers" msgstr "Дизайнеры" msgid "Programming Lead" msgstr "Ведущий программиÑÑ‚" msgid "Art Lead" msgstr "ХудожеÑтвенный руководитель" msgid "Playtesters" msgstr "ТеÑтеры игры" msgid "Designer" msgstr "Дизайнер" msgid "Producers" msgstr "ПродюÑеры" msgid "QA Managers" msgstr "Менеджеры по контролю качеÑтва" msgid "Sound Design" msgstr "Звуковое оформление" msgid "Town Themes" msgstr "Музыкальные темы городов" msgid "Alto Sax" msgstr "Ðльт-ÑакÑофон" msgid "Harpsichord and Piano" msgstr "КлавеÑин и фортепиано" msgid "Basso Vocal" msgstr "БаÑÑ Ð²Ð¾ÐºÐ°Ð»" msgid "Soprano Vocal" msgstr "Сопрано вокал" msgid "Recorded at %{recordingStudio}" msgstr "ЗапиÑано на %{recordingStudio}" msgid "credits|Manual" msgstr "РуководÑтво по игре" msgid "German Consultant" msgstr "Ðемецкий конÑультант" msgid "Map Designers" msgstr "Разработка карт" msgid "Package Design" msgstr "Дизайн упаковки" msgid "Your Name" msgstr "Ваше имÑ" msgid "Unknown Hero" msgstr "ÐеизвеÑтный герой" msgid "Standard" msgstr "ÐžÐ±Ñ‹Ñ‡Ð½Ð°Ñ Ð¸Ð³Ñ€Ð°" msgid "View High Scores for Standard Maps." msgstr "ПоÑмотреть очки рейтинга за обычные игры." msgid "Campaign" msgstr "КампаниÑ" msgid "View High Scores for Campaigns." msgstr "ПоÑмотреть очки рейтинга за кампании." msgid "hotkey|default okay event" msgstr "нажать ок в диалоге" msgid "hotkey|default cancel event" msgstr "нажать отмена в диалоге" msgid "hotkey|default left" msgstr "клавиша влево" msgid "hotkey|default right" msgstr "клавиша вправо" msgid "hotkey|default up" msgstr "клавиша вверх" msgid "hotkey|default down" msgstr "клавиша вниз" msgid "hotkey|toggle fullscreen" msgstr "переключить полноÑкранный режим" msgid "hotkey|toggle text support mode" msgstr "текÑтовое Ñопровождение (вкл./выкл.)" msgid "hotkey|new game" msgstr "Ð½Ð¾Ð²Ð°Ñ Ð¸Ð³Ñ€Ð°" msgid "hotkey|load game" msgstr "загрузить игру" msgid "hotkey|highscores" msgstr "рейтинги" msgid "hotkey|credits" msgstr "авторы" msgid "hotkey|standard game" msgstr "Ð¾Ð±Ñ‹Ñ‡Ð½Ð°Ñ Ð¸Ð³Ñ€Ð°" msgid "hotkey|campaign game" msgstr "кампаниÑ" msgid "hotkey|multi-player game" msgstr "многопользовательÑÐºÐ°Ñ Ð¸Ð³Ñ€Ð°" msgid "hotkey|settings" msgstr "наÑтройки" msgid "hotkey|quit" msgstr "выйти из игры" msgid "hotkey|select map" msgstr "выбрать карту" msgid "hotkey|select small map size" msgstr "показать маленькие карты" msgid "hotkey|select medium map size" msgstr "показать Ñредние карты" msgid "hotkey|select large map size" msgstr "показать большие карты" msgid "hotkey|select extra large map size" msgstr "показать очень большие карты" msgid "hotkey|select all map sizes" msgstr "показать карты вÑех размеров" msgid "hotkey|hotseat game" msgstr "режим за одним компьютером" msgid "hotkey|battle only game" msgstr "режим битвы" msgid "hotkey|choose the original campaign" msgstr "выбрать кампанию: войны за преÑтол" msgid "hotkey|choose the expansion campaign" msgstr "выбрать кампании Ð´Ð¾Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ñ†ÐµÐ½Ð° верноÑти" msgid "hotkey|map editor main menu" msgstr "меню редактора карт" msgid "hotkey|new map menu" msgstr "меню новой карты" msgid "hotkey|load map menu" msgstr "меню загрузки карты" msgid "hotkey|new map from scratch" msgstr "Ð½Ð¾Ð²Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð° Ñ Ð½ÑƒÐ»Ñ" msgid "hotkey|new random map" msgstr "Ð½Ð¾Ð²Ð°Ñ ÑÐ»ÑƒÑ‡Ð°Ð¹Ð½Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð°" msgid "hotkey|undo last action" msgstr "отменить поÑледнее дейÑтвие" msgid "hotkey|redo last action" msgstr "повторить поÑледнее дейÑтвие" msgid "hotkey|roland campaign" msgstr "ÐºÐ°Ð¼Ð¿Ð°Ð½Ð¸Ñ Ð·Ð° роланда" msgid "hotkey|archibald campaign" msgstr "ÐºÐ°Ð¼Ð¿Ð°Ð½Ð¸Ñ Ð·Ð° арчибальда" msgid "hotkey|the price of loyalty campaign" msgstr "кампаниÑ: цена верноÑти" msgid "hotkey|voyage home campaign" msgstr "кампаниÑ: дорога домой" msgid "hotkey|wizard's isle campaign" msgstr "кампаниÑ: оÑтров чародеев" msgid "hotkey|descendants campaign" msgstr "кампаниÑ: наÑледники" msgid "hotkey|select first campaign bonus" msgstr "выбрать первый Ð±Ð¾Ð½ÑƒÑ ÐºÐ°Ð¼Ð¿Ð°Ð½Ð¸Ð¸" msgid "hotkey|select second campaign bonus" msgstr "выбрать второй Ð±Ð¾Ð½ÑƒÑ ÐºÐ°Ð¼Ð¿Ð°Ð½Ð¸Ð¸" msgid "hotkey|select third campaign bonus" msgstr "выбрать третий Ð±Ð¾Ð½ÑƒÑ ÐºÐ°Ð¼Ð¿Ð°Ð½Ð¸Ð¸" msgid "hotkey|view campaign intro" msgstr "поÑмотреть вÑтупление кампании" msgid "hotkey|select campaign difficulty" msgstr "выбрать ÑложноÑть кампании" msgid "hotkey|restart campaign scenario" msgstr "переиграть Ñценарий кампании" msgid "hotkey|world map left" msgstr "перемеÑтить карту мира влево" msgid "hotkey|world map right" msgstr "перемеÑтить карту мира вправо" msgid "hotkey|world map up" msgstr "перемеÑтить карту мира вверх" msgid "hotkey|world map down" msgstr "перемеÑтить карту мира вниз" msgid "hotkey|world map up left" msgstr "перемеÑтить карту мира влево-вверх" msgid "hotkey|world map up right" msgstr "перемеÑтить карту мира вправо-вверх" msgid "hotkey|world map down left" msgstr "перемеÑтить карту мира влево-вниз" msgid "hotkey|world map down right" msgstr "перемеÑтить карту мира вправо-вниз" msgid "hotkey|save game" msgstr "Ñохранить игру" msgid "hotkey|next hero" msgstr "Ñледующий герой" msgid "hotkey|start hero movement" msgstr "начать движение героÑ" msgid "hotkey|cast adventure spell" msgstr "произнеÑти заклинание приключениÑ" msgid "hotkey|put hero to sleep" msgstr "уÑыпить/разбудить героÑ" msgid "hotkey|next town" msgstr "Ñледующий город" msgid "hotkey|end turn" msgstr "окончить ход" msgid "hotkey|file options" msgstr "дейÑÑ‚Ð²Ð¸Ñ Ñ Ñ„Ð°Ð¹Ð»Ð¾Ð¼ карты" msgid "hotkey|adventure options" msgstr "игровые дейÑтвиÑ" msgid "hotkey|puzzle map" msgstr "карта-головоломка" msgid "hotkey|scenario information" msgstr "Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ Ñценарии" msgid "hotkey|dig for artifact" msgstr "выкопать артефакт" msgid "hotkey|view world" msgstr "обзор мира" msgid "hotkey|kingdom summary" msgstr "обзор королевÑтва" msgid "hotkey|default action" msgstr "дейÑтвие по умолчанию" msgid "hotkey|open focus" msgstr "открыть выбранный объект" msgid "hotkey|system options" msgstr "наÑтройки ÑиÑтемы" msgid "hotkey|scroll left" msgstr "прокрутка влево" msgid "hotkey|scroll right" msgstr "прокрутка вправо" msgid "hotkey|scroll up" msgstr "прокрутка вверх" msgid "hotkey|scroll down" msgstr "прокрутка вниз" msgid "hotkey|toggle control panel" msgstr "отображение панели ÑƒÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ (при Ñкрытом интерфейÑе)" msgid "hotkey|toggle radar" msgstr "отображение мини-карты (при Ñкрытом интерфейÑе)" msgid "hotkey|toggle buttons" msgstr "отображение кнопок (при Ñкрытом интерфейÑе)" msgid "hotkey|toggle status" msgstr "отображение ÑтатуÑа (при Ñкрытом интерфейÑе)" msgid "hotkey|toggle icons" msgstr "отображение значков (при Ñкрытом интерфейÑе)" msgid "hotkey|transfer control to ai" msgstr "передать управление ии" msgid "hotkey|retreat from battle" msgstr "отÑтупление из битвы" msgid "hotkey|surrender during battle" msgstr "ÑдатьÑÑ Ð² битве" msgid "hotkey|toggle battle auto mode" msgstr "включить режим автобитвы" msgid "hotkey|finish the battle in auto mode" msgstr "закончить битву в авторежиме" msgid "hotkey|battle options" msgstr "наÑтройки битвы" msgid "hotkey|skip turn in battle" msgstr "пропуÑтить ход в битве" msgid "hotkey|cast battle spell" msgstr "произнеÑти боевое заклинание" msgid "hotkey|dwelling level 1" msgstr "поÑеление 1 уровнÑ" msgid "hotkey|dwelling level 2" msgstr "поÑеление 2 уровнÑ" msgid "hotkey|dwelling level 3" msgstr "поÑеление 3 уровнÑ" msgid "hotkey|dwelling level 4" msgstr "поÑеление 4 уровнÑ" msgid "hotkey|dwelling level 5" msgstr "поÑеление 5 уровнÑ" msgid "hotkey|dwelling level 6" msgstr "поÑеление 6 уровнÑ" msgid "hotkey|well" msgstr "колодец" msgid "hotkey|marketplace" msgstr "рынок" msgid "hotkey|mage guild" msgstr "Ð³Ð¸Ð»ÑŒÐ´Ð¸Ñ Ð¼Ð°Ð³Ð¾Ð²" msgid "hotkey|shipyard" msgstr "верфь" msgid "hotkey|thieves guild" msgstr "Ð³Ð¸Ð»ÑŒÐ´Ð¸Ñ Ð²Ð¾Ñ€Ð¾Ð²" msgid "hotkey|tavern" msgstr "таверна" msgid "hotkey|construction screen" msgstr "Ñкран ÑтроительÑтва замка" msgid "hotkey|buy all monsters in well" msgstr "купить вÑех ÑущеÑтв в колодце" msgid "hotkey|split stack by half" msgstr "разделить отрÑд пополам" msgid "hotkey|split stack by one" msgstr "отделить от отрÑда одно ÑущеÑтво" msgid "hotkey|join stacks" msgstr "объединить отрÑды" msgid "hotkey|upgrade troop" msgstr "улучшить ÑущеÑтво" msgid "hotkey|dismiss hero or troop" msgstr "уволить Ð³ÐµÑ€Ð¾Ñ Ð¸Ð»Ð¸ ÑущеÑтво" msgid "" "Do you want to regain control from AI? The effect will take place only on " "the next turn." msgstr "" "Хотите ли Ð’Ñ‹ вернуть Ñебе управление игрой? Возврат вÑтупит в Ñилу Ñо " "Ñледующего хода." msgid "" "Do you want to transfer control from you to the AI? The effect will take " "place only on the next turn." msgstr "" "Хотите ли Ð’Ñ‹ передать управление ИИ? Передача вÑтупит в Ñилу Ñо Ñледующего " "хода." msgid "Default Actions" msgstr "Стандартные дейÑтвиÑ" msgid "Global Actions" msgstr "Общие дейÑтвиÑ" msgid "Main Menu" msgstr "Главное меню" msgid "World Map" msgstr "Карта мира" msgid "Battle Screen" msgstr "Экран битвы" msgid "Town Screen" msgstr "Экран города" msgid "Army Actions" msgstr "ДейÑÑ‚Ð²Ð¸Ñ Ð°Ñ€Ð¼Ð¸Ð¸" msgid "Editor" msgstr "Редактор" msgid "The save file is corrupted." msgstr "Файл ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð²Ñ€ÐµÐ¶Ð´Ñ‘Ð½." msgid "Unsupported save format: " msgstr "Ðеподдерживаемый формат ÑохранениÑ: " msgid "Current game version: " msgstr "Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ Ð¸Ð³Ñ€Ñ‹: " msgid "Last supported version: " msgstr "ПоÑледнÑÑ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð¸Ð²Ð°ÐµÐ¼Ð°Ñ Ð²ÐµÑ€ÑиÑ: " msgid "This file contains a save with an invalid game type." msgstr "Этот файл Ñодержит Ñохранение Ñ Ð½ÐµÐºÐ¾Ñ€Ñ€ÐµÐºÑ‚Ð½Ñ‹Ð¼ типом игры." msgid "" "This save file requires \"The Price of Loyalty\" game assets, but they have " "not been provided to the engine." msgstr "" "Ð”Ð»Ñ Ð´Ð°Ð½Ð½Ð¾Ð³Ð¾ файла ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ñ‚Ñ€ÐµÐ±ÑƒÑŽÑ‚ÑÑ Ñ€ÐµÑурÑÑ‹ Ð´Ð¾Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ \"Цена верноÑти\", " "но они не обнаружены." msgid "This saved game is localized to '" msgstr "Эта ÑÐ¾Ñ…Ñ€Ð°Ð½Ñ‘Ð½Ð½Ð°Ñ Ð¸Ð³Ñ€Ð° локализована на '" msgid "' language, but the current language of the game is '" msgstr "' Ñзыке, но текущий Ñзык игры - " msgid "Hot Seat" msgstr "За одним компьютером" msgid "" "Play a Hot Seat game, where 2 to 4 players play around the same computer, " "switching into the 'Hot Seat' when it is their turn." msgstr "ПозволÑет неÑкольким игрокам играть за одним компьютером." msgid "A single player game playing out a single map." msgstr "ÐžÐ´Ð¸Ð½Ð¾Ñ‡Ð½Ð°Ñ Ð¸Ð³Ñ€Ð° на одиночной карте." msgid "Standard Game" msgstr "ÐžÐ±Ñ‹Ñ‡Ð½Ð°Ñ Ð¸Ð³Ñ€Ð°" msgid "A single player game playing through a series of maps." msgstr "ÐžÐ´Ð¸Ð½Ð¾Ñ‡Ð½Ð°Ñ Ð¸Ð³Ñ€Ð° на Ñерии карт." msgid "Campaign Game" msgstr "КампаниÑ" msgid "Multi-Player Game" msgstr "Ð¡ÐµÑ‚ÐµÐ²Ð°Ñ Ð¸Ð³Ñ€Ð°" msgid "" "A multi-player game, with several human players completing against each " "other on a single map." msgstr "Ð¡ÐµÑ‚ÐµÐ²Ð°Ñ Ð¸Ð³Ñ€Ð° Ñ Ð½ÐµÑколькими игроками-людьми на одиночной карте." msgid "fheroes2 Resurrection Team presents" msgstr "Команда fheroes2 Resurrection предÑтавлÑет" msgid "Greetings!" msgstr "Добро пожаловать!" msgid "Welcome to Heroes of Might and Magic II powered by fheroes2 engine!" msgstr "Добро пожаловать в Heroes of Might and Magic II на движке fheroes2!" msgid "" "Welcome to Heroes of Might and Magic II powered by the fheroes2 engine!\n" "Before starting the game please select a game resolution." msgstr "" "Добро пожаловать в Heroes of Might and Magic II на движке fheroes2!\n" "Перед началом игры, пожалуйÑта, выберите разрешение Ñкрана." msgid "Please Remember" msgstr "Запомните" msgid "" "You can always change the language, resolution and settings of the game by " "clicking on the " msgstr "" "Ð’Ñ‹ вÑегда можете изменить Ñзык, разрешение Ñкрана или наÑтройки игры, нажав " "на " msgid "door" msgstr "дверь" msgid " on the left side of the Main Menu, or with the " msgstr " в левой чаÑти главного меню или Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ кнопки " msgid " button from the " msgstr ", раÑположенной в меню " msgid "NEW GAME" msgstr "ÐОВÐЯ ИГРÐ" msgid "" " menu. \n" "\n" "Enjoy the game!" msgstr "" ".\n" "\n" "Желаем Вам приÑтной игры!" msgid "The Editor requires \"The Price of Loyalty\" expansion files to work." msgstr "" "Редактору карт необходимы файлы из Ð´Ð¾Ð¿Ð¾Ð»Ð½ÐµÐ½Ð¸Ñ Ðº оригинальной игре \"Цена " "верноÑти\"." msgid "Quit Heroes of Might and Magic II and return to the operating system." msgstr "Завершить игру Heroes of Might and Magic II и вернутьÑÑ Ð² ÑиÑтему." msgid "Credits" msgstr "Ðвторы" msgid "View the credits screen." msgstr "ПроÑмотр окна авторов." msgid "High Scores" msgstr "Рейтинги" msgid "View the high scores screen." msgstr "ПроÑмотр лучших результатов." msgid "Change language, resolution and settings of the game." msgstr "Сменить Ñзык, разрешение и наÑтройки игры." msgid "Game Settings" msgstr "ÐаÑтройки игры" msgid "" "Required video files for campaign selection window are missing. Please make " "sure that all necessary files are present in the system." msgstr "" "ОтÑутÑтвуют видеофайлы, необходимые Ð´Ð»Ñ Ð¾ÐºÐ½Ð° выбора кампании. ПожалуйÑта, " "убедитеÑÑŒ, что вÑе необходимые файлы приÑутÑтвуют в ÑиÑтеме." msgid "" "Either Roland's or Archibald's campaign from the original Heroes of Might " "and Magic II." msgstr "Кампании Роланда и Ðрчибальда из оригинальных Героев Меча и Магии II." msgid "Original Campaign" msgstr "Ð¡Ñ‚Ð°Ñ€Ð°Ñ ÐºÐ°Ð¼Ð¿Ð°Ð½Ð¸Ñ" msgid "Expansion Campaign" msgstr "ÐÐ¾Ð²Ð°Ñ ÐºÐ°Ð¼Ð¿Ð°Ð½Ð¸Ñ" msgid "One of the four new campaigns from the Price of Loyalty expansion set." msgstr "Одна из четырёх новых кампаний из набора дополнений \"Цена верноÑти\"." msgid "Loading video. Please wait..." msgstr "Загрузка видео. ПожалуйÑта, подождите..." msgid "Host" msgstr "ХоÑÑ‚" msgid "" "The host sets up the game options. There can only be one host per network " "game." msgstr "" "ХоÑÑ‚ задаёт параметры игры. Ð’ одной Ñетевой игре может учаÑтвовать только " "один хоÑÑ‚." msgid "Guest" msgstr "ГоÑть" msgid "" "The guest waits for the host to set up the game, then is automatically added " "in. There can be multiple guests for TCP/IP games." msgstr "" "ГоÑть ждёт пока хоÑÑ‚ наÑтроит игру, а затем автоматичеÑки приÑоединитÑÑ Ðº " "ней. Ð”Ð»Ñ Ð¸Ð³Ñ€ по протоколу TCP/IP может быть неÑколько гоÑтей." msgid "Battle Only" msgstr "ДуÑль" msgid "Setup and play a battle without loading any map." msgstr "ÐаÑтройте и играйте в битву без загрузки карты." msgid "2 Players" msgstr "2 игрока" msgid "" "Play with 2 human players, and optionally, up to 4 additional computer " "players." msgstr "2 игрока, оÑтальными управлÑет компьютер." msgid "3 Players" msgstr "3 игрока" msgid "" "Play with 3 human players, and optionally, up to 3 additional computer " "players." msgstr "3 игрока, оÑтальными управлÑет компьютер." msgid "4 Players" msgstr "4 игрока" msgid "" "Play with 4 human players, and optionally, up to 2 additional computer " "players." msgstr "4 игрока, оÑтальными управлÑет компьютер." msgid "5 Players" msgstr "5 игроков" msgid "" "Play with 5 human players, and optionally, up to 1 additional computer " "player." msgstr "5 игроков и 1 компьютер." msgid "6 Players" msgstr "6 игроков" msgid "Play with 6 human players." msgstr "Ð’Ñе игроки - люди." msgid "Dragon city has fallen! You are now the Master of the Dragons." msgstr "Город драконов пал! Теперь Ð’Ñ‹ повелитель драконов." msgid "" "You captured %{name}!\n" "You are victorious." msgstr "" "Ð’Ñ‹ захватили %{name}!\n" "Победа за вами." msgid "" "You have captured the enemy hero %{name}!\n" "Your quest is complete." msgstr "" "Ð’Ñ‹ захватили вражеÑкого Ð³ÐµÑ€Ð¾Ñ %{name}!\n" "Ваше задание выполнено." msgid "" "You have found the %{name}.\n" "Your quest is complete." msgstr "" "Ð’Ñ‹ нашли %{name}.\n" "Ваше задание выполнено." msgid "Ultimate Artifact" msgstr "Великий артефакт" msgid "" "The enemy is beaten.\n" "Your side has triumphed!" msgstr "" "Враг повержен.\n" "Ð’Ñ‹ празднуете победу!" msgid "" "You have built up over %{count} gold in your treasury.\n" "All enemies bow before your wealth and power." msgstr "" "Ð’ вашей Ñокровищнице накопилоÑÑŒ более %{count} золота.\n" "Ваши враги преклонÑÑŽÑ‚ÑÑ Ð¿ÐµÑ€ÐµÐ´ Вашим богатÑтвом и могущеÑтвом." msgid "Victory!" msgstr "Победа!" msgid "" "The enemy has captured %{name}!\n" "They are triumphant." msgstr "" "Враг захватил %{name}!\n" "Ð’Ñ‹ проиграли." msgid "" "The enemy has built up over %{count} gold in his treasury.\n" "You must bow done in defeat before his wealth and power." msgstr "" "Ð’ Ñокровищнице врага накопилоÑÑŒ более %{count} золота.\n" "Ð’Ñ‹ вынуждены преклонитьÑÑ Ð¿ÐµÑ€ÐµÐ´ его богатÑтвом и могущеÑтвом." msgid "You have been eliminated from the game!!!" msgstr "Ð’Ñ‹ были иÑключены из игры!" msgid "" "You have lost the hero %{name}.\n" "Your quest is over." msgstr "" "Ð’Ñ‹ потерÑли Ð³ÐµÑ€Ð¾Ñ %{name}.\n" "Ваше приключение окончено." msgid "" "You have failed to complete your quest in time.\n" "All is lost." msgstr "" "Ð’Ñ‹ не ÑправилиÑÑŒ Ñ Ð·Ð°Ð´Ð°Ð½Ð¸ÐµÐ¼ за отведённое времÑ.\n" "Ð’ÑÑ‘ кончено." msgid "Defeat!" msgstr "Поражение!" msgid "" "Base score: %{score}\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" "Базовые очки: %{score}\n" "СложноÑть: %{difficulty}\n" "\n" msgid "Defeat all enemy heroes and capture all enemy towns and castles." msgstr "" "Победите вÑех вражеÑких героев и захватите вÑе вражеÑкие города и замки." msgid "Run out of time. (Fail to win by a certain point.)" msgstr "Ðе уÑпеть вовремÑ." msgid "You must defeat the enemy %{enemies}." msgid_plural "You must defeat the enemy alliance of %{enemies}." msgstr[0] "" "Ваш единÑтвенный противник - %{enemies}. Одолейте его и победа будет Ваша!" msgstr[1] "%{enemies} в Ñоюзе против ВаÑ. Победите вÑех Ваших противников." msgstr[2] "%{enemies} в Ñоюзе против ВаÑ. Победите вÑех Ваших противников." msgid "" "The alliance consisting of %{allies} and you must defeat the enemy " "%{enemies}." msgid_plural "" "The alliance consisting of %{allies} and you must defeat the enemy alliance " "of %{enemies}." msgstr[0] "" "%{allies} на Вашей Ñтороне, а %{enemies} - против ВаÑ. Одолейте Вашего " "противника." msgstr[1] "" "%{allies} на Вашей Ñтороне, а %{enemies} - против ВаÑ. Одолейте вражеÑкий " "альÑнÑ." msgstr[2] "" "%{allies} на Вашей Ñтороне, а %{enemies} - против ВаÑ. Одолейте вражеÑкий " "альÑнÑ." msgid "Capture the castle '%{name}'." msgstr "Захватить замок '%{name}'." msgid "Capture the town '%{name}'." msgstr "Захватить город '%{name}'." msgid "Defeat the hero '%{name}'." msgstr "Победить Ð³ÐµÑ€Ð¾Ñ '%{name}'." msgid "Find the ultimate artifact." msgstr "Ðайти великий артефакт." msgid "Find the '%{name}' artifact." msgstr "Ðайти артефакт '%{name}'." msgid "Accumulate %{count} gold." msgstr "Ðакопить %{count} золота." msgid "" ", or you may win by defeating all enemy heroes and capturing all enemy towns " "and castles." msgstr "" ", также Ð’Ñ‹ выигрываете еÑли победите вÑех героев или захватите вÑе города " "противника." msgid "Lose the castle '%{name}'." msgstr "ПотерÑть замок '%{name}'." msgid "Lose the town '%{name}'." msgstr "ПотерÑть город '%{name}'." msgid "Lose the hero: %{name}." msgstr "ПотерÑть Ð³ÐµÑ€Ð¾Ñ Ð¿Ð¾ имени %{name}." msgid "Fail to win by the end of month %{month}, week %{week}, day %{day}." msgstr "" "Ðе уÑпеть победить до указанной даты: меÑÑц %{month}, Ð½ÐµÐ´ÐµÐ»Ñ %{week}, день " "%{day}." msgid "%{color} player has been vanquished!" msgstr "%{color} игрок побеждён!" msgid "Major Event!" msgstr "Важное Ñобытие!" msgid "Scenario:" msgstr "Сценарий:" msgid "Game Difficulty:" msgstr "СложноÑть игры:" msgid "Opponents:" msgstr "Оппоненты:" msgid "Class:" msgstr "КлаÑÑ:" msgid "Rating %{rating}%" msgstr "Рейтинг %{rating}%" msgid "Click here to select which scenario to play." msgstr "Ðажмите Ñюда чтобы выбрать Ñценарий." msgid "Scenario" msgstr "Сценарий" msgid "Game Difficulty" msgstr "СложноÑть игры" msgid "" "This lets you change the starting difficulty at which you will play. Higher " "difficulty levels start you of with fewer resources, and at the higher " "settings, give extra resources to the computer." msgstr "" "Эта Ð¾Ð¿Ñ†Ð¸Ñ Ð¿Ð¾Ð·Ð²Ð¾Ð»Ñет уÑтанавливать Ñтартовый уровень ÑложноÑти игры. Чем выше " "уровень ÑложноÑти, тем Ñ Ð¼ÐµÐ½ÑŒÑˆÐ¸Ð¼ количеÑтвом реÑурÑов Ð’Ñ‹ начнёте игру, и тем " "больше реÑурÑов получают Ваши компьютерные противники." msgid "Difficulty Rating" msgstr "Рейтинг" msgid "" "The difficulty rating reflects a combination of various settings for your " "game. This number will be applied to your final score." msgstr "" "Рейтинг отражает Ñочетание различных игровых уÑтановок. Он иÑпользуетÑÑ Ð¿Ñ€Ð¸ " "раÑчёте конечного результата доÑтигнутого игроком." msgid "Click to accept these settings and start a new game." msgstr "Подтверждает заданные уÑтановки и начинает новую игру." msgid "Click to return to the main menu." msgstr "Ðажмите Ð´Ð»Ñ Ð²Ð¾Ð·Ð²Ñ€Ð°Ñ‚Ð° в главное меню." msgid "Astrologers proclaim the Month of the %{name}." msgstr "ÐÑтрологи объÑвили МеÑÑц %{name}." msgid "Astrologers proclaim the Week of the %{name}." msgstr "ÐÑтрологи объÑвили Ðеделю %{name}." msgid "After regular growth, the population of %{monster} is doubled!" msgstr "ПоÑле обычного роÑта, %{monster} удваивает Ñвою популÑцию!" msgid "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgid_plural "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgstr[0] "" "ПоÑле обычного роÑта, популÑÑ†Ð¸Ñ %{monster} увеличиваетÑÑ Ð½Ð° %{count} процент!" msgstr[1] "" "ПоÑле обычного роÑта, популÑÑ†Ð¸Ñ %{monster} увеличиваетÑÑ Ð½Ð° %{count} " "процента!" msgstr[2] "" "ПоÑле обычного роÑта, популÑÑ†Ð¸Ñ %{monster} увеличиваетÑÑ Ð½Ð° %{count} " "процентов!" msgid "%{monster} growth +%{count}." msgstr "%{monster}: популÑÑ†Ð¸Ñ ÑƒÐ²ÐµÐ»Ð¸Ñ‡Ð¸Ð²Ð°ÐµÑ‚ÑÑ Ð½Ð° %{count}." msgid " All populations are halved." msgstr " Ð’ÑÑ‘ наÑеление уменьшилоÑÑŒ вдвое." msgid " All dwellings increase population." msgstr " ÐаÑеление вÑех жилищ возроÑло." msgid "New Month!" msgstr "Ðовый меÑÑц!" msgid "New Week!" msgstr "ÐÐ¾Ð²Ð°Ñ Ð½ÐµÐ´ÐµÐ»Ñ!" msgid "Beware!" msgstr "Внимание!" msgid "" "%{color} player, this is your last day to capture a town, or you will be " "banished from this land." msgstr "" "%{color} игрок, у Ð’Ð°Ñ Ð¾ÑталÑÑ Ð¿Ð¾Ñледний день, чтобы захватить город, иначе " "Ð’Ñ‹ будете изгнаны из Ñтих земель." msgid "" "%{color} player, you only have %{day} days left to capture a town, or you " "will be banished from this land." msgstr "" "%{color} игрок, у Ð’Ð°Ñ Ð¾ÑталоÑÑŒ %{day} Ñуток, чтобы захватить город, иначе Ð’Ñ‹ " "будете изгнаны из Ñтих земель." msgid "%{color} player's turn." msgstr "Ход игрока: %{color}" msgid "" "%{color} player, you have lost your last town. If you do not conquer another " "town in the next week, you will be eliminated." msgstr "" "%{color} игрок, Ð’Ñ‹ потерÑли поÑледний город. ЕÑли Ð’Ñ‹ не захватите какой-либо " "город за неделю, то потерпите поражение." msgid "" "%{color} player, your heroes abandon you, and you are banished from this " "land." msgstr "" "%{color} игрок, Ваши герои отвернулиÑÑŒ от ВаÑ, а Ð’Ñ‹ изгонÑетеÑÑŒ из Ñтих " "земель." msgid "Lord Kilburn" msgstr "Лорд Килбурн" msgid "Tsabu" msgstr "ТÑабу" msgid "Sir Galant" msgstr "СÑÑ€ Галант" msgid "Thundax" msgstr "ТандакÑ" msgid "Lord Haart" msgstr "Лорд Хаарт" msgid "Ariel" msgstr "ÐриÑль" msgid "Rebecca" msgstr "Ребекка" msgid "Sandro" msgstr "Сандро" msgid "Crodo" msgstr "Кродо" msgid "Barock" msgstr "Барок" msgid "Antoine" msgstr "Ðнтуан" msgid "Astra" msgstr "ÐÑтра" msgid "Agar" msgstr "Ðгар" msgid "Vatawna" msgstr "Ватавна" msgid "Vesper" msgstr "ВеÑпер" msgid "Ambrose" msgstr "ÐмброÑ" msgid "Troyan" msgstr "ТроÑн" msgid "Jojosh" msgstr "Йойош" msgid "Wrathmont" msgstr "Ратмонт" msgid "Maximus" msgstr "МакÑимуÑ" msgid "Next Hero" msgstr "Следующий герой" msgid "Select the next Hero." msgstr "Выбрать Ñледующего героÑ." msgid "Hero Movement" msgstr "Передвижение героÑ" msgid "" "Start the Hero's movement along the current path or re-visit the object " "occupied by the Hero. Press and hold this button to reset the Hero's path." msgstr "" "Ðачать движение Ð³ÐµÑ€Ð¾Ñ Ð¿Ð¾ намеченному пути или повторно поÑетить объект, " "перед которым находитÑÑ Ð³ÐµÑ€Ð¾Ð¹. Ðажмите и удерживайте Ñту кнопку, чтобы " "отменить намеченный путь." msgid "Kingdom Summary" msgstr "Обзор королевÑтва" msgid "View a Summary of your Kingdom." msgstr "ОÑмотреть Ваши владениÑ." msgid "Cast an adventure spell." msgstr "ПроизнеÑти заклинание приключениÑ." msgid "End Turn" msgstr "Окончить ход" msgid "End your turn and left the computer take its turn." msgstr "Окончить ход и передать управление Ñледующему игроку." msgid "Adventure Options" msgstr "Игровые дейÑтвиÑ" msgid "Bring up the adventure options menu." msgstr "Открыть окно доÑтупных игровых дейÑтвий." msgid "" "Bring up the file options menu, allowing you to load, save, start a new game " "or quit." msgstr "" "Открывает меню, где Ð’Ñ‹ можете загрузить, Ñохранить, начать новую игру или " "выйти из нее." msgid "Bring up the system options menu, allowing you to customize your game." msgstr "Открывает окно ÑиÑтемных наÑтроек, позволÑющих наÑтроить игру." msgid "" "One or more heroes may still move, are you sure you want to end your turn?" msgstr "" "Ðекоторые Ваши герои ещё могут ходить.\n" "Ð’Ñ‹ дейÑтвительно хотите закончить ход?" msgid "Are you sure you want to quit?" msgstr "Ð’Ñ‹ уверены, что хотите выйти?" msgid "Are you sure you want to restart? (Your current game will be lost.)" msgstr "Ð’Ñ‹ дейÑтвительно хотите начать Ñначала? (Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð¸Ð³Ñ€Ð° будет потерÑна.)" msgid "Are you sure you want to overwrite the save with this name?" msgstr "Ð’Ñ‹ хотите перезапиÑать файл игры?" msgid "Game saved successfully." msgstr "Игра уÑпешно Ñохранена." msgid "There was an issue during saving." msgstr "Возникла проблема при Ñохранении." msgid "" "Are you sure you want to load a new game? (Your current game will be lost.)" msgstr "" "Ð’Ñ‹ дейÑтвительно хотите загрузить новую игру? (Ð¢ÐµÐºÑƒÑ‰Ð°Ñ Ð¸Ð³Ñ€Ð° будет потерÑна.)" msgid "Try looking on land!!!" msgstr "Попробуйте поиÑкать на земле!!!" msgid "" "Searching for the Ultimate Artifact is fruitless. Your hero could not carry " "it even if he found it - all his artifact slots are full." msgstr "" "ПоиÑки великого артефакта будут беÑполезными. ЕÑли даже Ваш герой и найдёт " "его, то у него попроÑту нет меÑта, чтобы его унеÑти. ВеÑÑŒ инвентарь Ð³ÐµÑ€Ð¾Ñ " "занÑÑ‚." msgid "Digging for artifacts requires a whole day, try again tomorrow." msgstr "РаÑкопки артефакта занимают целый день. Попробуйте завтра." msgid "" "After spending many hours digging here, you have uncovered the %{artifact}." msgstr "" "ПоÑле множеÑтва чаÑов, потраченных на раÑкопки, Ð’Ñ‹ обнаружили %{artifact}." msgid "Congratulations!" msgstr "ПоздравлÑем!" msgid "Nothing here. Where could it be?" msgstr "ЗдеÑÑŒ ничего. Где бы Ñто могло быть?" msgid "Try searching on clear ground." msgstr "Попробуйте копать на нормальной земле." msgid "A miniature view of the known world. Left click to move viewing area." msgstr "" "Мини-карта изученного мира. Ðажатием левой кнопки можно управлÑть проÑмотром." msgid "Month: %{month} Week: %{week}" msgstr "МеÑÑц: %{month} ÐеделÑ: %{week}" msgid "Day: %{day}" msgstr "День: %{day}" msgid "" "You find a small\n" "quantity of %{resource}." msgstr "" "Ð’Ñ‹ нашли немного\n" "%{resource}." msgid "Status Window" msgstr "Окно ÑтатуÑа" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date." msgstr "" "Это окно Ñообщает информацию о ÑоÑтоÑнии Вашего Ð³ÐµÑ€Ð¾Ñ Ð¸Ð»Ð¸ королевÑтва, а " "также показывает календарь." msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date. Left click here to cycle through these windows." msgstr "" "Это окно Ñообщает информацию о ÑоÑтоÑнии Вашего Ð³ÐµÑ€Ð¾Ñ Ð¸Ð»Ð¸ королевÑтва, а " "также показывает календарь. Ðажмите левую кнопку мыши, чтобы проÑмотреть вÑÑŽ " "доÑтупную информацию." msgid "" "This lets you change player starting positions and colors. A particular " "color will always start in a particular location. Some positions may only be " "played by a computer player or only by a human player." msgstr "" "Эта Ð¾Ð¿Ñ†Ð¸Ñ Ð¿Ð¾Ð·Ð²Ð¾Ð»Ñет Вам задать цвет игрока и его Ñтартовую позицию. Каждому " "цвету ÑоответÑтвует Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»Ñ‘Ð½Ð½Ð°Ñ ÑÑ‚Ð°Ñ€Ñ‚Ð¾Ð²Ð°Ñ Ð¿Ð¾Ð·Ð¸Ñ†Ð¸Ñ. Ðекоторые цвета жёÑтко " "закреплены либо за компьютерными, либо за живыми игроками." msgid "" "This lets you change the class of a player. Classes are not always " "changeable. Depending on the scenario, a player may receive additional towns " "and/or heroes not of their primary alignment." msgstr "" "Эта Ð¾Ð¿Ñ†Ð¸Ñ Ð¿Ð¾Ð·Ð²Ð¾Ð»Ñет Вам задавать клаÑÑ Ð¸Ð³Ñ€Ð¾ÐºÐ°. КлаÑÑÑ‹ не вÑегда можно " "изменÑть. Ð’ завиÑимоÑти от ÑÑ†ÐµÐ½Ð°Ñ€Ð¸Ñ Ð¸Ð³Ñ€Ð¾Ðº может получать дополнительные " "города и/или героев, направленноÑть которых изначально не Ñовпадает Ñ " "направленноÑтью игрока." msgid "Handicap" msgstr "Фора" msgid "" "This lets you change the handicap of a particular player. Only humans may be " "handicapped. Handicapped players start with fewer resources and earn 15 or " "30% fewer resources per turn for mild and severe handicaps, respectively." msgstr "" "Эта Ð¾Ð¿Ñ†Ð¸Ñ Ð¿Ð¾Ð·Ð²Ð¾Ð»Ñет любому игроку-человеку дать фору другим игрокам. ЕÑли " "игрок даёт другим фору, он начинает игру Ñ Ð¼ÐµÐ½ÑŒÑˆÐ¸Ð¼ количеÑтвом реÑурÑов и " "каждый ход получает на 15 или 30% меньше реÑурÑов, в завиÑимоÑти от его форы." msgid "%{color} player" msgstr "%{color} игрок" msgid "No Handicap" msgstr "Ðет форы" msgid "No special restrictions on start resources and earning them per turn." msgstr "" "Ðикаких оÑобых ограничений на начальные реÑурÑÑ‹ и их поÑтупление за ход." msgid "Mild Handicap" msgstr "ÐœÐ°Ð»Ð°Ñ Ñ„Ð¾Ñ€Ð°" msgid "" "Mild handicapped players start with fewer resources and earn 15% fewer " "resources per turn." msgstr "" "Игроки Ñ Ð¼Ð°Ð»Ð¾Ð¹ форой начинают игру Ñ Ð¼ÐµÐ½ÑŒÑˆÐ¸Ð¼ количеÑтвом реÑурÑов и каждый " "ход получают на 15% меньше реÑурÑов." msgid "Severe Handicap" msgstr "Ð¡ÐµÑ€ÑŒÑ‘Ð·Ð½Ð°Ñ Ñ„Ð¾Ñ€Ð°" msgid "" "Severe handicapped players start with fewer resources and earn 30% fewer " "resources per turn." msgstr "" "Игроки Ñ Ñерьёзной форой начинают игру Ñ Ð¼ÐµÐ½ÑŒÑˆÐ¸Ð¼ количеÑтвом реÑурÑов и " "каждый ход получают на 30% меньше реÑурÑов." msgid "View %{skill} Info" msgstr "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ навыке %{skill}" msgid "Keyboard|123" msgstr "123" msgid "Keyboard|SPACE" msgstr "ПРОБЕЛ" msgid "Keyboard|ABC" msgstr "ÐБВ" msgid "Kingdom Income" msgstr "Доход королевÑтва" msgid "Kingdom Income per day." msgstr "Ежедневный доход королевÑтва." msgid "For every lighthouse controlled, your ships will move further each day." msgstr "" "Чем больше количеÑтво маÑков, которыми вы владеете, тем дальше будут " "передвигатьÑÑ Ð’Ð°ÑˆÐ¸ корабли по морю." msgid "English" msgstr "ÐнглийÑкий" msgid "French" msgstr "ФранцузÑкий" msgid "Polish" msgstr "ПольÑкий" msgid "German" msgstr "Ðемецкий" msgid "Russian" msgstr "РуÑÑкий" msgid "Italian" msgstr "ИтальÑнÑкий" msgid "Czech" msgstr "ЧешÑкий" msgid "Norwegian" msgstr "ÐорвежÑкий" msgid "Belarusian" msgstr "БелоруÑÑкий" msgid "Bulgarian" msgstr "БолгарÑкий" msgid "Ukrainian" msgstr "УкраинÑкий" msgid "Romanian" msgstr "РумынÑкий" msgid "Spanish" msgstr "ИÑпанÑкий" msgid "Swedish" msgstr "ШведÑкий" msgid "Portuguese" msgstr "ПортугальÑкий" msgid "Turkish" msgstr "Турецкий" msgid "Dutch" msgstr "ГолландÑкий" msgid "Hungarian" msgstr "ВенгерÑкий" msgid "Danish" msgstr "ДатÑкий" msgid "Slovak" msgstr "Словацкий" msgid "Vietnamese" msgstr "ВьетнамÑкий" msgid ", FPS: " msgstr ", FPS: " msgid "%{object} robber" msgstr "%{object} грабителÑ" msgid "%{object} raided" msgstr "%{object} рейд" msgid "Ector" msgstr "Эктор" msgid "Gwenneth" msgstr "Гвинет" msgid "Sir Gallant" msgstr "СÑÑ€ Галант" msgid "Tyro" msgstr "Тиро" msgid "Dimitry" msgstr "Димитрий" msgid "Ruby" msgstr "Руби" msgid "Crag Hack" msgstr "КрÑг Ð¥Ñк" msgid "Fineous" msgstr "ФинеуÑ" msgid "Jezebel" msgstr "ИзабÑль" msgid "Atlas" msgstr "ÐтлаÑ" msgid "Ergon" msgstr "Эргон" msgid "Jaclyn" msgstr "Жаклин" msgid "Gem" msgstr "ДжÑм" msgid "Natasha" msgstr "Ðаташа" msgid "Carlawn" msgstr "Карлан" msgid "Luna" msgstr "Луна" msgid "Arie" msgstr "Эйри" msgid "Barok" msgstr "Барок" msgid "Kastore" msgstr "КаÑтор" msgid "Falagar" msgstr "Фалагар" msgid "Dawn" msgstr "Дан" msgid "Flint" msgstr "Флинт" msgid "Halon" msgstr "Хейлон" msgid "Myra" msgstr "Мира" msgid "Myrini" msgstr "Мирини" msgid "Wilfrey" msgstr "Уилфри" msgid "Mandigal" msgstr "Мандигал" msgid "Sarakin" msgstr "Саракин" msgid "Charity" msgstr "ЧÑрити" msgid "Darlana" msgstr "Дарлана" msgid "Ranloo" msgstr "РÑнлу" msgid "Rialdo" msgstr "Риальдо" msgid "Zam" msgstr "Зам" msgid "Zom" msgstr "Зом" msgid "Celia" msgstr "ЦелиÑ" msgid "Roxana" msgstr "РокÑана" msgid "Lord Corlagon" msgstr "Лорд Корлагон" msgid "Lord Halton" msgstr "Лорд Халтон" msgid "Sister Eliza" msgstr "СеÑтра Элиза" msgid "Brother Brax" msgstr "Брат БракÑ" msgid "Dainwin" msgstr "Дейнвин" msgid "Joseph" msgstr "Джозеф" msgid "Mog" msgstr "Моуг" msgid "Solmyr" msgstr "Сольмир" msgid "Ceallach" msgstr "Каллах" msgid "Drakonia" msgstr "ДракониÑ" msgid "Elderian" msgstr "Элдериан" msgid "Gallavant" msgstr "Галавант" msgid "Jarkonas" msgstr "ДжарконаÑ" msgid "Martine" msgstr "Мартина" msgid " gives you maximum morale" msgstr " повышает Вашу мораль до макÑимума" msgid " gives you maximum luck" msgstr " повышает Вашу удачу до макÑимума" msgid "You cannot pick up this artifact, you already have a full load!" msgstr "Ð’Ñ‹ не можете забрать Ñтот артефакт, у Ð’Ð°Ñ Ð½ÐµÑ‚ Ñвободного меÑта!" msgid "To cast spells, you must first buy a spell book for %{gold} gold." msgstr "" "Чтобы Ð’Ð°Ñ Ð¿Ñ€Ð¾Ð¿ÑƒÑтили в гильдию магов, Ð’Ñ‹ должны купить книгу заклинаний за " "%{gold} золотых." msgid "Unfortunately, you seem to be a little short of cash at the moment." msgstr "К Ñожалению, у Ð’Ð°Ñ Ð½Ðµ хватает денег." msgid "Do you wish to buy one?" msgstr "Желаете купить?" msgid "%{count} / day" msgstr "%{count} / день" msgid "one" msgstr "1" msgid "two" msgstr "2" msgid "A whirlpool engulfs your ship. Some of your army has fallen overboard." msgstr "Ваш корабль заÑоÑало в водоворот. ЧаÑть Вашей армии Ñмыло за борт." msgid "Insulted by your refusal of their offer, the monsters attack!" msgstr "ОÑкорблённые Вашим отказом, они напали на ВаÑ!" msgid "" "The %{monster}, awed by the power of your forces, begin to scatter.\n" "Do you wish to pursue and engage them?" msgstr "" "%{monster}, ÑтрашаÑÑŒ мощи Вашего воинÑтва, броÑилиÑÑŒ враÑÑыпную.\n" "Изволите преÑледовать их и заÑтавить дратьÑÑ?" msgid "Ransacking an enemy camp, you discover a hidden cache of treasures." msgstr "ОбыÑкав вражеÑкий лагерь, Ð’Ñ‹ находите ÑпрÑтанный клад." msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with these resources, " "come back next week for more.\"" msgstr "" "Мельник обращаетÑÑ Ðº Вам Ñо Ñловами:\n" "\"Милорд, Ñ Ñ‚Ñ€ÑƒÐ´Ð¸Ð»ÑÑ Ð² поте лица и прошу Ð’Ð°Ñ Ð¿Ñ€Ð¸Ð½Ñть Ñти реÑурÑÑ‹. Приходите " "на Ñледующей неделе и получите ещё.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there are no resources currently available. Please try " "again next week.\"" msgstr "" "Мельник обращаетÑÑ Ðº Вам Ñо Ñловами:\n" "\"Сожалею, милорд, но ÑÐµÐ³Ð¾Ð´Ð½Ñ Ñƒ Ð¼ÐµÐ½Ñ Ð½ÐµÑ‚ реÑурÑов. Приходите на Ñледующей " "неделе.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with this gold, come " "back next week for more.\"" msgstr "" "Мельник обращаетÑÑ Ðº Вам Ñо Ñловами:\n" "\"Милорд, Ñ Ñ‚Ñ€ÑƒÐ´Ð¸Ð»ÑÑ Ð² поте лица и прошу Ð’Ð°Ñ Ð¿Ñ€Ð¸Ð½Ñть Ñто золото. Приходите " "на Ñледующей неделе и получите ещё.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there is no gold currently available. Please try again " "next week.\"" msgstr "" "Мельник обращаетÑÑ Ðº Вам Ñо Ñловами:\n" "\"Сожалею, милорд, но ÑÐµÐ³Ð¾Ð´Ð½Ñ Ð·Ð¾Ð»Ð¾Ñ‚Ð° у Ð¼ÐµÐ½Ñ Ð½ÐµÑ‚. Приходите на Ñледующей " "неделе.\"" msgid "" "You've found an abandoned lean-to.\n" "Poking about, you discover some resources hidden nearby." msgstr "" "Ð’Ñ‹ нашли заброшенный навеÑ.\n" "Ðемного поиÑкав, Ð’Ñ‹ обнаружили припрÑтанные Ñ€Ñдом реÑурÑÑ‹." msgid "The lean-to is long abandoned. There is nothing of value here." msgstr "ÐÐ°Ð²ÐµÑ Ð´Ð°Ð²Ð½Ð¾ заброшен. ЗдеÑÑŒ нет ничего ценного." msgid "" "You catch a leprechaun foolishly sleeping amidst a cluster of magic " "mushrooms.\n" "In exchange for his freedom, he guides you to a small pot filled with " "precious things." msgstr "" "Ð’Ñ‹ поймали лепрекона, так беÑпечно заÑнувшего под шлÑпкой волшебного гриба.\n" "Ð’ обмен на Ñвою Ñвободу он раÑÑказал Вам, где лежит горшочек Ñ " "драгоценноÑÑ‚Ñми." msgid "" "You've found a magic garden, the kind of place that leprechauns and faeries " "like to cavort in, but there is no one here today.\n" "Perhaps you should try again next week." msgstr "" "Ð’Ñ‹ забрели в волшебный Ñад. ЗдеÑÑŒ, обычно, так обожают резвитьÑÑ Ð»ÐµÐ¿Ñ€ÐµÐºÐ¾Ð½Ñ‹ Ñ " "феÑми. Ðо ÑÐµÐ³Ð¾Ð´Ð½Ñ Ð·Ð´ÐµÑÑŒ никого." msgid "You come upon the remains of an unfortunate adventurer." msgstr "Ð’Ñ‹ обнаружили оÑтанки какого-то неÑчаÑтного путника." msgid "Treasure" msgstr "Сокровище" msgid "Searching through the tattered clothing, you find the %{artifact}." msgstr "ПокопавшиÑÑŒ Ñреди лохмотьев, Ð’Ñ‹ нашли %{artifact}." msgid "Searching through the tattered clothing, you find nothing." msgstr "ПокопавшиÑÑŒ Ñреди лохмотьев, Ð’Ñ‹ ничего не нашли." msgid "" "You come across an old wagon left by a trader who didn't quite make it to " "safe terrain." msgstr "" "Ð’Ñ‹ наткнулиÑÑŒ на брошенную торговую повозку, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð½Ðµ добралаÑÑŒ до " "безопаÑной меÑтноÑти." msgid "Unfortunately, others have found it first, and the wagon is empty." msgstr "К неÑчаÑтью, кто-то здеÑÑŒ уже побывал, и в повозке ничего нет." msgid "Searching inside, you find the %{artifact}." msgstr "ПокопавшиÑÑŒ внутри, Ð’Ñ‹ нашли %{artifact}." msgid "Inside, you find some of the wagon's cargo still intact." msgstr "Внутри чаÑть груза оказалаÑÑŒ нетронутой." msgid "You search through the flotsam, and find some wood and some gold." msgstr "Ð’Ñ‹ иÑÑледовали плавающие обломки и нашли немного древеÑины и золота." msgid "You search through the flotsam, and find some wood." msgstr "Ð’Ñ‹ иÑÑледовали плавающие обломки и нашли немного древеÑины." msgid "You search through the flotsam, but find nothing." msgstr "Ð’Ñ‹ иÑÑледовали плавающие обломки и ничего не нашли." msgid "Shrine of the 1st Circle" msgstr "СвÑтилище 1-го круга" msgid "" "You come across a small shrine attended by a group of novice acolytes.\n" "In exchange for your protection, they agree to teach you a simple spell - " "'%{spell}'." msgstr "" "Ð’Ñ‹ набрели на маленькое ÑвÑтилище, где Ñлужат молодые Ð±Ñ€Ð°Ñ‚ÑŒÑ Ð²ÐµÑ€Ñ‹.\n" "Ð’ обмен на защиту они ÑоглаÑилиÑÑŒ научить Ð’Ð°Ñ Ð¿Ñ€Ð¾Ñтому заклинанию - " "'%{spell}'." msgid "Shrine of the 2nd Circle" msgstr "СвÑтилище 2-го круга" msgid "" "You come across an ornate shrine attended by a group of rotund friars.\n" "In exchange for your protection, they agree to teach you a spell - " "'%{spell}'." msgstr "" "Ð’Ñ‹ набрели на богато раÑпиÑанное ÑвÑтилище, где Ñлужат доÑтойные Ð±Ñ€Ð°Ñ‚ÑŒÑ " "веры.\n" "Ð’ обмен на защиту они ÑоглаÑилиÑÑŒ научить Ð’Ð°Ñ Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸ÑŽ - '%{spell}'." msgid "Shrine of the 3rd Circle" msgstr "СвÑтилище 3-го круга" msgid "" "You come across a lavish shrine attended by a group of high priests.\n" "In exchange for your protection, they agree to teach you a sophisticated " "spell - '%{spell}'." msgstr "" "Ð’Ñ‹ набрели на роÑкошное ÑвÑтилище, где Ñлужат верховные жрецы.\n" "Ð’ обмен на защиту они ÑоглаÑилиÑÑŒ научить Ð’Ð°Ñ Ð¿Ñ€ÐµÐ¼ÑƒÐ´Ñ€Ð¾Ð¼Ñƒ заклинанию - " "'%{spell}'." msgid "" "\n" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "\n" "К Ñожалению, у Ð’Ð°Ñ Ð½ÐµÐ´Ð¾Ñтаточно мудроÑти, чтобы понÑть заклинание, и Ð’Ñ‹ не в " "ÑоÑтоÑнии его изучить." msgid "" "\n" "Unfortunately, you already have knowledge of this spell, so there is nothing " "more for them to teach you." msgstr "" "\n" "К Ñожалению, Вам уже знакомо данное заклинание, так что им больше нечему Ð’Ð°Ñ " "учить." msgid "" "\n" "Unfortunately, you have no Magic Book to record the spell with." msgstr "" "\n" "К Ñожалению, у Ð’Ð°Ñ Ð½ÐµÑ‚ Волшебной Книги, чтобы запиÑать заклинание в неё." msgid "" "You approach the hut and observe a witch inside studying an ancient tome on " "%{skill}.\n" "\n" msgstr "" "ÐŸÐ¾Ð´Ð¾Ð¹Ð´Ñ Ðº хижине и заглÑнув в окно, Ð’Ñ‹ увидели ведьму, ÑклонившуюÑÑ Ð½Ð°Ð´ " "древней книгой под названием %{skill}.\n" "\n" msgid "" "As you approach, she turns and focuses her one glass eye on you.\n" "\"You already know everything you deserve to learn!\" the witch screeches. " "\"NOW GET OUT OF MY HOUSE!\"" msgstr "" "Когда Ð’Ñ‹ подходите, ведьма поворачиваетÑÑ Ð¸ Ñмотрит на Ð’Ð°Ñ Ñвоим ÑтеклÑнным " "глазом.\n" "\"Ты уже знаешь вÑÑ‘, что тебе положено знать!\" говорит она. \"РТЕПЕРЬ ВОР" "ИЗ МОЕГО ДОМÐ!\"" msgid "" "As you approach, she turns and speaks.\n" "\"You already know that which I would teach you. I can help you no further.\"" msgstr "" "Когда Ð’Ñ‹ подходите к хижине, ведьма поворачиваетÑÑ Ð¸ говорит.\n" "\"Ты уже знаешь то, что знаю Ñ. Больше ничем помочь не могу.\"" msgid "" "An ancient and immortal witch living in a hut with bird's legs for stilts " "teaches you %{skill} for her own inscrutable purposes." msgstr "" "ДрÑхлаÑ, но беÑÑÐ¼ÐµÑ€Ñ‚Ð½Ð°Ñ Ð²ÐµÐ´ÑŒÐ¼Ð°, Ð¶Ð¸Ð²ÑƒÑ‰Ð°Ñ Ð² Ñтой избушке на курьих ножках, по " "каким-то непонÑтным ÑоображениÑм решила, что %{skill} Вам пригодитÑÑ." msgid "As you drink the sweet water, you gain luck for your next battle." msgstr "Выпив Ñвежей воды Ð’Ñ‹ получаете Ð±Ð¾Ð½ÑƒÑ ÑƒÐ´Ð°Ñ‡Ð¸ в Ñледующем бою." msgid "You drink from the enchanted fountain, but nothing happens." msgstr "Ð’Ñ‹ пьёте из фонтана, но ничего не проиÑходит." msgid "You enter the faerie ring, but nothing happens." msgstr "Ð’Ñ‹ поÑетили кольцо фей, но ничего не произошло." msgid "" "Upon entering the mystical faerie ring, your army gains luck for its next " "battle." msgstr "" "Ваше войÑко вÑтупает в кольцо фей, чары которого принеÑут Вам удачу в " "грÑдущем Ñражении." msgid "" "You've found an ancient and weathered stone idol.\n" "It is supposed to grant luck to visitors, but since the stars are already " "smiling upon you, it does nothing." msgstr "" "Ð’Ñ‹ нашли древнего идола. ГоворÑÑ‚, он приноÑит удачу тем, кто его навещает, " "но поÑкольку звёзды и так покровительÑтвуют Вам, идол ничего нового Вам не " "дал." msgid "" "You've found an ancient and weathered stone idol.\n" "Kissing it is supposed to be lucky, so you do. The stone is very cold to the " "touch." msgstr "" "Ð’Ñ‹ нашли древнего идола.\n" "ГоворÑÑ‚, еÑли его поцеловать, то Ñто принеÑёт удачу - Ð’Ñ‹ так и поÑтупили. " "Камень оказалÑÑ Ð¾Ñ‡ÐµÐ½ÑŒ холоден Ð´Ð»Ñ Ð³ÑƒÐ±." msgid "The mermaids silently entice you to return later and be blessed again." msgstr "" "РуÑалки молчаливо дали Вам понÑть, чтобы Ð’Ñ‹ приходили в другой раз, тогда " "они благоÑловÑÑ‚ ВаÑ." msgid "" "The magical, soothing beauty of the Mermaids reaches you and your crew.\n" "Just for a moment, you forget your worries and bask in the beauty of the " "moment.\n" "The mermaids charms bless you with increased luck for your next combat." msgstr "" "ÐÐµÐ·ÐµÐ¼Ð½Ð°Ñ ÐºÑ€Ð°Ñота руÑалок очаровала Ð’Ð°Ñ Ð¸ Вашу команду.\n" "Ðа какой-то миг Ð’Ñ‹ позабыли о Ваших проблемах и наÑладилиÑÑŒ Ñтим мгновением " "краÑоты.\n" "Чары руÑалок благоÑловили Ð’Ð°Ñ Ð½Ð° удачу в Ñледующем бою." msgid "" "You come upon the pyramid of a great and ancient king.\n" "You are tempted to search it for treasure, but all the old stories warn of " "fearful curses and undead guardians.\n" "Will you search?" msgstr "" "Ð’Ñ‹ нашли пирамиду древнего великого царÑ.\n" "ИÑкушение поиÑкать в ней Ñокровища велико, но Ð’Ñ‹ Ñлышали вÑе Ñти Ñказки об " "ужаÑных проклÑтиÑÑ… и неупокоенных Ñтражах.\n" "Хотите обыÑкать пирамиду?" msgid "" "Upon defeating the monsters, you decipher an ancient glyph on the wall, " "telling the secret of the spell - '" msgstr "" "Одолев чудовищ, Ð’Ñ‹ раÑшифровали иероглифы на Ñтене, Ñодержащие Ñекрет " "Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ - '" msgid "Unfortunately, you have no Magic Book to record the spell with." msgstr "К Ñожалению Ð’Ñ‹ не имеете Волшебной Книги чтобы запиÑать заклинание." msgid "" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "К Ñожалению, Вам не хватает мудроÑти чтобы понÑть и выучить заклинание." msgid "" "You come upon the pyramid of a great and ancient king.\n" "Routine exploration reveals that the pyramid is completely empty." msgstr "" "Изучив внутренние Ð¿Ð¾Ð¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð¿Ð¸Ñ€Ð°Ð¼Ð¸Ð´Ñ‹, Ð’Ñ‹ понимаете, что здеÑÑŒ ничего нет." msgid "" "A drink at the well is supposed to restore your spell points, but you are " "already at maximum." msgstr "" "Глоток из колодца обычно воÑÑтанавливает магичеÑкую Ñнергию, но ÑÐµÐ¹Ñ‡Ð°Ñ Ð¾Ð½Ð° у " "Ð’Ð°Ñ Ð¸ так на пределе." msgid "A second drink at the well in one day will not help you." msgstr "Второй глоток за день Вам не поможет." msgid "A drink from the well has restored your spell points to maximum." msgstr "Глоток из колодца полноÑтью воÑÑтановил Вашу магичеÑкую Ñнергию." msgid "" "\"I'm sorry sir,\" The leader of the soldiers says, \"but you already know " "everything we have to teach.\"" msgstr "" "\"ПроÑтите, милорд\" - Ñказал предводитель воинов, - \"но Ð’Ñ‹ уже знаете вÑÑ‘, " "чему мы ÑпоÑобны научить.\"" msgid "The soldiers living in the fort teach you a few new defensive tricks." msgstr "Воины, живущие в Ñтом форте, научили Ð’Ð°Ñ Ð¿Ð°Ñ€Ðµ новых защитных приёмов." msgid "" "You've come upon a mercenary camp practicing their tactics. \"You're too " "advanced for us,\" the mercenary captain says. \"We can teach nothing more.\"" msgstr "" "Ð’Ñ‹ пришли в лагерь наёмников, где воины оттачивают тактику. \"Ð”Ð»Ñ Ð½Ð°Ñ Ð’Ñ‹ " "Ñлишком умелый боец,\" - Ñказал капитан наёмников. - \"Больше мы ничему " "научить Ð’Ð°Ñ Ð½Ðµ можем.\"" msgid "" "You've come upon a mercenary camp practicing their tactics. The mercenaries " "welcome you and your troops and invite you to train with them." msgstr "" "Ð’Ñ‹ пришли в лагерь наёмников, отрабатывающих Ñвою тактику. Ðаёмники " "поприветÑтвовали Ð’Ð°Ñ Ð¸ приглаÑили позаниматьÑÑ Ð²Ð¼ÐµÑте Ñ Ð½Ð¸Ð¼Ð¸." msgid "\"Go 'way!\", the witch doctor barks, \"you know all I know.\"" msgstr "\"УбирайÑÑ!\" - Ñ€Ñвкнул колдун, \"ты и так знаешь вÑÑ‘, что знаю Ñ.\"" msgid "" "An Orcish witch doctor living in the hut deepens your knowledge of magic by " "showing you how to cast stones, read portents, and decipher the intricacies " "of chicken entrails." msgstr "" "Орочий колдун, живущий в Ñтой хижине, углубил Ваши Ð¿Ð¾Ð·Ð½Ð°Ð½Ð¸Ñ Ð² магии, " "показав, как гадать на камнÑÑ…, читать Ð·Ð½Ð°Ð¼ÐµÐ½Ð¸Ñ Ð¸ извлекать ÑущноÑть Ð±Ñ‹Ñ‚Ð¸Ñ Ð¸Ð· " "хитроÑÐ¿Ð»ÐµÑ‚ÐµÐ½Ð¸Ñ Ñ†Ñ‹Ð¿Ð»Ñчьих потрохов." msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, the Druids turn you away, indicating they have nothing " "new to teach you." msgstr "" "Ð’Ñ‹ заÑтали группу друидов за обрÑдом в одном из их диковинных каменных " "Ñооружений. Друиды, не Ð½Ð°Ñ€ÑƒÑˆÐ°Ñ Ð±ÐµÐ·Ð¼Ð¾Ð»Ð²Ð¸Ñ, показали жеÑтами, что им больше " "нечему Ð’Ð°Ñ ÑƒÑ‡Ð¸Ñ‚ÑŒ." msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, they teach you new ways to cast spells." msgstr "" "Ð’Ñ‹ заÑтали группу друидов за обрÑдом в одном из их диковинных каменных " "Ñооружений. Ðе Ð½Ð°Ñ€ÑƒÑˆÐ°Ñ Ð±ÐµÐ·Ð¼Ð¾Ð»Ð²Ð¸Ñ, они научили Ð’Ð°Ñ Ð½Ð¾Ð²Ñ‹Ð¼ ÑпоÑобам колдовÑтва." msgid "" "You tentatively approach the burial ground of ancient warriors. Do you want " "to search the graves?" msgstr "" "Ð’Ñ‹ оÑторожно приближаетеÑÑŒ к захоронению древних воинов. Хотите обыÑкать их " "могилы?" msgid "" "You spend several hours searching the graves and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" "Ð’Ñ‹ тратите неÑколько чаÑов, обыÑÐºÐ¸Ð²Ð°Ñ Ð¼Ð¾Ð³Ð¸Ð»Ñ‹, но ничего не находите. Сей " "презренный акт Ñнижает мораль Вашей армии." msgid "Upon defeating the Zombies you search the graves and find something!" msgstr "ПоÑле победы над зомби, Ð’Ñ‹ обыÑкиваете могилы и удалÑетеÑÑŒ Ñ Ð½Ð°Ñ…Ð¾Ð´ÐºÐ¾Ð¹!" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the shipwreck?" msgstr "" "Гниющий оÑтов огромного пиратÑкого ÐºÐ¾Ñ€Ð°Ð±Ð»Ñ Ð·Ð»Ð¾Ð²ÐµÑ‰Ðµ поÑкрипывает, " "покачиваемый прибоем на Ñкалах. Желаете обыÑкать обломки?" msgid "" "You spend several hours sifting through the debris and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" "Ð’Ñ‹ тратите неÑколько чаÑов, обыÑÐºÐ¸Ð²Ð°Ñ Ð¾Ñтанки, но ничего не находите. Сей " "презренный акт Ñнижает мораль Вашей армии." msgid "" "Upon defeating the Ghosts you sift through the debris and find something!" msgstr "Одолев призраков, Ð’Ñ‹ обыÑкали оÑтанки ÐºÐ¾Ñ€Ð°Ð±Ð»ÐµÐºÑ€ÑƒÑˆÐµÐ½Ð¸Ñ Ð¸ кое-что нашли!" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the ship?" msgstr "" "Гниющий оÑтов огромного пиратÑкого ÐºÐ¾Ñ€Ð°Ð±Ð»Ñ Ð·Ð»Ð¾Ð²ÐµÑ‰Ðµ поÑкрипывает, " "покачиваемый прибоем на Ñкалах. Желаете обыÑкать корабль?" msgid "" "Upon defeating the Skeletons you sift through the debris and find something!" msgstr "Совладав Ñо Ñкелетами, Ð’Ñ‹ обыÑкали оÑтанки ÐºÐ¾Ñ€Ð°Ð±Ð»Ñ Ð¸ кое-что нашли!" msgid "Your men spot a navigational buoy, confirming that you are on course." msgstr "Ваши Ñпутники замечают морÑкой буй. Он указывает верный курÑ." msgid "" "Your men spot a navigational buoy, confirming that you are on course and " "increasing their morale." msgstr "" "Ваши Ñпутники замечают морÑкой буй. Он указывает верный курÑ, и Ñто повышает " "их боевой дух." msgid "" "The drink at the oasis is refreshing, but offers no further benefit. The " "oasis might help again if you fought a battle first." msgstr "" "Глоток воды в оазиÑе оÑвежает, но иной пользы не приноÑит. Этот оазиÑ, " "возможно, Ñнова поможет Вам поÑле Ñледующей битвы." msgid "" "A drink at the oasis fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" "Глоток воды из оазиÑа наполнÑет Ваших воинов Ñилой и поднимает дух. Ð¡ÐµÐ³Ð¾Ð´Ð½Ñ " "Ð’Ñ‹ Ñможете пройти чуть больше." msgid "" "The drink at the watering hole is refreshing, but offers no further benefit. " "The watering hole might help again if you fought a battle first." msgstr "" "ОÑтановка на водопое оÑвежает, но иных благ не приноÑит. Водопой Ñнова " "поможет Вам поÑле Ñледующей битвы." msgid "" "A drink at the watering hole fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" "Добрый глоток иÑполнил Ваши войÑка Ñилы и поднÑл дух. Ð¡ÐµÐ³Ð¾Ð´Ð½Ñ Ð’Ñ‹ Ñможете " "пройти чуть дальше." msgid "" "It doesn't help to pray twice before a battle. Come back after you've fought." msgstr "Ð”Ð²Ð¾Ð¹Ð½Ð°Ñ Ð¼Ð¾Ð»Ð¸Ñ‚Ð²Ð° войне не подÑпорье. Заходите поÑле битвы." msgid "A visit and a prayer at the temple raises the morale of your troops." msgstr "ПоÑещение храма и молитва поднÑли мораль Ваших войÑк." msgid "" "An old Knight appears on the steps of the gazebo. \"I am sorry, my liege, I " "have taught you all I can.\"" msgstr "" "Ðа ÑтупенÑÑ… беÑедки поÑвлÑетÑÑ Ñтарый рыцарь. \"Мне жаль храбрый воин, но Ñ " "уже научил Ñ‚ÐµÐ±Ñ Ð²Ñему, что знаю Ñам.\"" msgid "" "An old Knight appears on the steps of the gazebo. \"My liege, I will teach " "you all that I know to aid you in your travels.\"" msgstr "" "Ðа ÑтупенÑÑ… беÑедки поÑвлÑетÑÑ Ñтарый рыцарь. \"О храбрый воин, Ñ Ð½Ð°ÑƒÑ‡Ñƒ Ñ‚ÐµÐ±Ñ " "вÑему, что знаю Ñам: пуÑть мой опыт поможет тебе в твоих ÑтранÑтвиÑÑ….\"" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he says, \"I would give you an artifact as a reward, but " "you're all full.\"" msgstr "" "Ð’Ñ‹ ÑпаÑли выжившего поÑле ÐºÐ¾Ñ€Ð°Ð±Ð»ÐµÐºÑ€ÑƒÑˆÐµÐ½Ð¸Ñ Ð¾Ñ‚ неминуемой гибели в " "безжалоÑтном океане. ÐœÐ°Ñ‚Ñ€Ð¾Ñ Ñказал, \"За Ñвоё ÑпаÑение Ñ Ñ…Ð¾Ñ‚ÐµÐ» отблагодарить " "Ð’Ð°Ñ Ð°Ñ€Ñ‚ÐµÑ„Ð°ÐºÑ‚Ð¾Ð¼, но Ñ Ð²Ð¸Ð¶Ñƒ, что Ваша Ñумка и так переполнена.\"" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he rewards you for your act of kindness by giving you the " "%{art}." msgstr "" "Ð’Ñ‹ ÑпаÑли выжившего поÑле ÐºÐ¾Ñ€Ð°Ð±Ð»ÐµÐºÑ€ÑƒÑˆÐµÐ½Ð¸Ñ Ð¾Ñ‚ неминуемой гибели в " "безжалоÑтном океане. Ðаграда за вашу доброту - %{art}." msgid "A leprechaun offers you the %{art} for the small price of %{gold} Gold." msgstr "" "Лепрекон предлагает Вам артефакт \"%{art}\" за Ñкромную цену в %{gold} " "золотых." msgid "" "A leprechaun offers you the %{art} for the small price of %{gold} Gold and " "%{count} %{res}." msgstr "" "Лепрекон предлагает Вам артефакт \"%{art}\" за Ñкромную цену в %{gold} " "золотых и %{count} %{res}." msgid "Do you wish to buy this artifact?" msgstr "Ð’Ñ‹ хотите приобреÑти Ñтот артефакт?" msgid "" "You try to pay the leprechaun, but realize that you can't afford it. The " "leprechaun stamps his foot and ignores you." msgstr "" "Ð’Ñ‹ хотите заплатить лепрекону, но у Ð’Ð°Ñ Ð½Ðµ хватает ÑредÑтв. Лепрекон топает " "ножкой и демонÑтративно отворачиваетÑÑ Ð¾Ñ‚ ВаÑ." msgid "" "Insulted by your refusal of his generous offer, the leprechaun stamps his " "foot and ignores you." msgstr "" "ОÑкорблённый Вашим отказом на его щедрое предложение, лепрекон топает ножкой " "и демонÑтративно отворачиваетÑÑ Ð¾Ñ‚ ВаÑ." msgid "You've found the artifact: " msgstr "Ð’Ñ‹ нашли артефакт: " msgid "" "You've found the humble dwelling of a withered hermit. The hermit tells you " "that he is willing to give the %{art} to the first wise person he meets." msgstr "" "Ð’Ñ‹ нашли Ñкромное жилище отшельника. Отшельник говорит Вам, что он готов " "отдать %{art} первому Мудрецу, которого вÑтретит." msgid "" "You've come across the spartan quarters of a retired soldier. The soldier " "tells you that he is willing to pass on the %{art} to the first true leader " "he meets." msgstr "" "Ð’Ñ‹ поÑетили ÑпартанÑкое жилище отÑтавного ветерана. Солдат говорит Вам, что " "он готов отдать %{art} первому Лидеру, которого он вÑтретит." msgid "" "You've encountered a strange person with a hat and an owl on it. He tells " "you that he is willing to give %{art} if you have %{skill}." msgstr "" "Ð’Ñ‹ ÑтолкнулиÑÑŒ Ñо Ñтранным человеком в шлÑпе, на которой Ñидит Ñова. Он " "говорит Вам, что готов отдать %{art}, еÑли у Ð’Ð°Ñ ÐµÑть %{skill}." msgid "" "You come upon an ancient artifact. As you reach for it, a pack of Rogues " "leap out of the brush to guard their stolen loot." msgstr "" "Ð’Ñ‹ нашли древний артефакт. Как только Ð’Ñ‹ наклонилиÑÑŒ его подобрать, " "воÑпользовавшиÑÑŒ моментом, из заÑады выпрыгнула шайка разбойников..." msgid "" "Through a clearing you observe an ancient artifact. Unfortunately, it's " "guarded by a nearby %{monster}. Do you want to fight the %{monster} for the " "artifact?" msgstr "" "Ð’Ñ‹ заметили древний артефакт. К Ñожалению, Ñ€Ñдом притаилÑÑ %{monster}. " "Хотите ÑразитьÑÑ?" msgid "Victorious, you take your prize, the %{art}." msgstr "Победив в бою, Ð’Ñ‹ забираете награду - %{art}." msgid "" "Discretion is the better part of valor, and you decide to avoid this fight " "for today." msgstr "" "ОÑмотрительноÑть - Ñто Ð»ÑƒÑ‡ÑˆÐ°Ñ Ñ‡ÐµÑ€Ñ‚Ð° доблеÑти, поÑему Ð’Ñ‹ решили избежать " "ÑÑ€Ð°Ð¶ÐµÐ½Ð¸Ñ ÑегоднÑ." msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold pieces." msgstr "" "Потратив неÑколько чаÑов на попытки выловить Ñундук из воды, Ð’Ñ‹ наконец " "открыли его и нашли внутри %{gold} золотых." msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold and the %{art}." msgstr "" "Потратив неÑколько чаÑов на попытки выловить Ñундук из воды, Ð’Ñ‹ наконец " "открыли его и нашли внутри %{gold} золотых и %{art}." msgid "" "After spending hours trying to fish the chest out of the sea, you open it, " "only to find it empty." msgstr "" "Потратив неÑколько чаÑов на попытки выловить Ñундук из воды, Ð’Ñ‹ наконец " "открыли его, но он оказалÑÑ Ð¿ÑƒÑÑ‚." msgid "" "After scouring the area, you fall upon a hidden treasure cache. You may take " "the gold or distribute the gold to the peasants for experience. Do you wish " "to keep the gold?" msgstr "" "ИÑÑÐ»ÐµÐ´ÑƒÑ Ð¾ÐºÑ€ÐµÑтноÑти, Ð’Ñ‹ наткнулиÑÑŒ на древний ларец. Золото можно оÑтавить " "Ñебе или раздать креÑтьÑнам в обмен на опыт. ОÑтавите золото Ñебе?" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "%{gold} gold pieces." msgstr "" "ИÑÑÐ»ÐµÐ´ÑƒÑ Ð¾ÐºÑ€ÐµÑтноÑти, Ð’Ñ‹ наткнулиÑÑŒ на древний ларец в котором лежит %{gold} " "золотых." msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "ancient artifact %{art}." msgstr "" "ИÑÑÐ»ÐµÐ´ÑƒÑ Ð¾ÐºÑ€ÐµÑтноÑти, Ð’Ñ‹ наткнулиÑÑŒ на древний ларец в котором лежит древний " "артефакт - %{art}." msgid "" "You stumble upon a dented and tarnished lamp lodged deep in the earth. Do " "you wish to rub the lamp?" msgstr "" "Ð’Ñ‹ находите заÑыпанную землёй помÑтую и закопчённую лампу. Хотите её " "потереть?" msgid "" "You have taken control of the local Alchemist shop. It will provide you with " "%{count} unit of Mercury per day." msgstr "" "Ð’Ñ‹ Ñтали хозÑином лаборатории меÑтного алхимика. Она будет приноÑить Вам по " "%{count} мере ртути в день." msgid "" "You gain control of a sawmill. It will provide you with %{count} units of " "wood per day." msgstr "" "Ð’Ñ‹ Ñтали хозÑином леÑопилки. Она будет приноÑить Вам по %{count} меры " "древеÑины в день." msgid "" "You gain control of an ore mine. It will provide you with %{count} units of " "ore per day." msgstr "" "Ð’Ñ‹ Ñтали хозÑином рудной шахты. Она будет приноÑить Вам по %{count} меры " "руды в день." msgid "" "You gain control of a sulfur mine. It will provide you with %{count} unit of " "sulfur per day." msgstr "" "Ð’Ñ‹ Ñтали хозÑином Ñерной шахты. Она будет приноÑить Вам по %{count} мере " "Ñеры в день." msgid "" "You gain control of a crystal mine. It will provide you with %{count} unit " "of crystal per day." msgstr "" "Ð’Ñ‹ Ñтали хозÑином криÑтальной шахты. Она будет приноÑить Вам по %{count} " "мере криÑталлов в день." msgid "" "You gain control of a gem mine. It will provide you with %{count} unit of " "gems per day." msgstr "" "Ð’Ñ‹ Ñтали хозÑином Ñамоцветной шахты. Она будет приноÑить Вам по %{count} " "мере Ñамоцветов в день." msgid "" "You gain control of a gold mine. It will provide you with %{count} gold per " "day." msgstr "" "Ð’Ñ‹ Ñтали хозÑином золотой шахты. Она будет приноÑить Вам по %{count} золотых " "в день." msgid "" "The lighthouse is now under your control, and all of your ships will now " "move further each day." msgstr "" "Ð’Ñ‹ Ñтали хозÑином маÑка, теперь вÑе Ваши корабли будут передвигатьÑÑ Ð¿Ð¾ морю " "гораздо дальше." msgid "You gain control of a %{name}." msgstr "%{name} переходит под Ваш контроль." msgid "" "You come upon an abandoned gold mine. The mine appears to be haunted. Do you " "wish to enter?" msgstr "" "Ð’Ñ‹ подошли к заброшенной шахте. Возможно в ней живут ПривидениÑ. Хотите " "войти и проверить Ñто?" msgid "You beat the Ghosts and are able to restore the mine to production." msgstr "Ð’Ñ‹ выгнали призраков из шахты и воÑÑтановили её работу." msgid "" "A group of %{monster} with a desire for greater glory wish to join you. Do " "you accept?" msgstr "" "%{monster} в погоне за Ñлавой желают к Вам приÑоединитьÑÑ.\n" "Ð’Ñ‹ ÑоглаÑны?" msgid "As you approach the dwelling, you notice that there is no one here." msgstr "ПриближаÑÑÑŒ к жилищу, Ð’Ñ‹ замечаете что здеÑÑŒ никого нет." msgid "" "You search the ruins, but the Medusas that used to live here are gone. " "Perhaps there will be more next week." msgstr "" "Ð’Ñ‹ обыÑкали руины, но убедилиÑÑŒ, что жившие тут Медузы ушли. Может быть Вам " "повезет больше на Ñледующей неделе." msgid "" "You've found some Medusas living in the ruins. They are willing to join your " "army for a price. Do you want to recruit Medusas?" msgstr "" "Ð’Ñ‹ обыÑкали руины и нашли неÑкольких Медуз, обитающих тут. Они ÑоглаÑны " "вÑтупить в Вашу армию за вознаграждение. Желаете нанÑть Медуз?" msgid "" "You've found a Sprite Tree City. Unfortunately, none of the Sprites living " "there wish to join an army. Maybe next week." msgstr "" "Ð’Ñ‹ нашли древеÑный город Фей. К Ñожалению, ни одна Ð¤ÐµÑ Ð½Ðµ захотела " "приÑоединитьÑÑ Ðº Вашей армии. Может быть на Ñледующей неделе они передумают." msgid "" "Some of the Sprites living in the tree city are willing to join your army " "for a price. Do you want to recruit Sprites?" msgstr "" "ÐеÑколько Фей, из города на деревьÑÑ…, желают вÑтупить в Вашу армию, за " "некоторое вознаграждение. Желаете нанÑть Фей?" msgid "" "A colorful Rogues' wagon stands empty here. Perhaps more Rogues will be here " "later." msgstr "" "Ð Ð°Ð·Ð½Ð¾Ñ†Ð²ÐµÑ‚Ð½Ð°Ñ Ð¿Ð¾Ð²Ð¾Ð·ÐºÐ° разбойников пуÑта. Пройдет времÑ, и, быть может, здеÑÑŒ " "обоÑнуетÑÑ Ð½Ð¾Ð²Ð°Ñ ÑˆÐ°Ð¹ÐºÐ°." msgid "" "Distant sounds of music and laughter draw you to a colorful wagon housing " "Rogues. Do you wish to have any Rogues join your army?" msgstr "" "Вдалеке ÑлышитÑÑ Ð¼ÑƒÐ·Ñ‹ÐºÐ° и Ñмех. Ð’Ñ‹ идете на звуки и видите разноцветную " "повозку, в которой живут Разбойники. Ð’Ñ‹ хотите принÑть в Ваше войÑко " "Разбойников?" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. The " "tents are unoccupied. Perhaps more Nomads will be here later." msgstr "" "Ваше внимание привлек шатер, пологи которого трепещут на жарком ветру " "пуÑтыни. ЗдеÑÑŒ никого нет. Пройдет времÑ, и, быть может, Ñюда придет новый " "отрÑд Кочевников." msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. Do you " "wish to have any Nomads join you during your travels?" msgstr "" "Ваше внимание привлек шатер, пологи которого трепещут на жарком ветру " "пуÑтыни. Ð’Ñ‹ хотите принÑть в Ваше войÑко Кочевников?" msgid "The pit of mud bubbles for a minute and then lies still." msgstr "Яма клокочущей грÑзи вÑколыхнулаÑÑŒ и уÑпокоилаÑÑŒ." msgid "" "As you approach the bubbling pit of mud, creatures begin to climb out and " "position themselves around it. In unison they say: \"Mother Earth would like " "to offer you a few of her troops. Do you want to recruit Earth Elementals?\"" msgstr "" "Как только Ð’Ñ‹ подошли к Ñме клокочущей грÑзи из неё начали выползать " "ÑущеÑтва. Ð’Ñтав вокруг Ñмы, они в униÑон обратилиÑÑŒ к Вам: \"Мать Ð·ÐµÐ¼Ð»Ñ " "желает предложить Вам неÑкольких Ñвоих воинов. Желаете нанÑть Земных " "Ñлементалей?\"" msgid "You enter the structure of white stone pillars, and find nothing." msgstr "" "Ð’Ñ‹ входите в Ñооружение из белых каменных Ñтолбов, и ничего не находите." msgid "" "White stone pillars support a roof that rises up to the sky. As you enter " "the structure, the dead air of the outside gives way to a whirling gust that " "almost pushes you back out. The air current materializes into a barely " "visible form. The creature asks, in what can only be described as a loud " "whisper: \"Why have you come? Are you here to call upon the forces of the " "air?\"" msgstr "" "Белокаменные колонны подпирают крышу, уходÑщую в небеÑа. Едва Ð’Ñ‹ вошли " "внутрь, как Ñпокойный воздух превратилÑÑ Ð² мощный поток ветра, едва не " "вытолкнувший Ð’Ð°Ñ Ð½Ð°Ñ€ÑƒÐ¶Ñƒ. Воздух ÑгуÑтилÑÑ Ð² едва различимые формы. СущеÑтво " "обратилоÑÑŒ к Вам едва Ñлышным шёпотом: \"Зачем ты ÑвилÑÑ? Ты пришёл Ñюда " "иÑкать помощи Ñил воздуха?\"" msgid "No Fire Elementals approach you from the lava pool." msgstr "" "Ðи один из Огненных Ñлементалей не вышел Вам навÑтречу из огненного баÑÑейна." msgid "" "Beneath a structure that serves to hold in heat, Fire Elementals move about " "in a fiery pool of molten lava. A group of them approach you and offer their " "services. Would you like to recruit Fire Elementals?" msgstr "" "Ð’ поÑтройке, удерживающей подземный жар, Ð’Ñ‹ видите Огненных Ñлементалей, " "движущихÑÑ Ð² потоках раÑкалённой лавы. ÐеÑколько из них приблизилиÑÑŒ к Вам и " "предложили Вам Ñвою Ñлужбу. Желаете нанÑть Огненных Ñлементалей?" msgid "A face forms in the water for a moment, and then is gone." msgstr "Ðа мгновение в воде мелькнули черты чьих-то лиц, и иÑчезли." msgid "" "Crystalline structures cast shadows over a small reflective pool of water. " "You peer into the pool, and a face that is not your own peers back. It asks: " "\"Would you like to call upon the powers of water?\"" msgstr "" "ХруÑтальные поÑтройки отбраÑывают зыбкие тени на небольшой Ñверкающий " "баÑÑейн. Ð’ воде поÑвилоÑÑŒ лицо, не Ваше отражение, которое обратилоÑÑŒ к Вам " "Ñо Ñловами: \"Желаешь обратитьÑÑ Ð·Ð° помощью к Ñилам воды?\"" msgid "This burial site is deathly still." msgstr "Это захоронение таит Ñмертельную тишину." msgid "" "Restless spirits of long dead warriors seeking their final resting place " "offer to join you in hopes of finding peace. Do you wish to recruit ghosts?" msgstr "" "Ðеупокоенные духи давно погибших воинов ищут ÑƒÐ¿Ð¾ÐºÐ¾ÐµÐ½Ð¸Ñ Ð¸ ÑоглашаютÑÑ " "вÑтупить в армию Вашего Ð³ÐµÑ€Ð¾Ñ Ð² надежде найти упокоение. Желаете нанÑть " "Призраков?" msgid "" "The City of the Dead is empty of life, and empty of unlife as well. Perhaps " "some undead will move in next week." msgstr "" "Ð’ Ñтом городе мертвецов жизни не наблюдаетÑÑ, нежити тоже. Быть может на " "Ñледующей неделе кто-то из нежити забредет Ñюда." msgid "" "Some Liches living here are willing to join your army for a price. Do you " "want to recruit Liches?" msgstr "" "Кое-кто из Личей, обитающих здеÑÑŒ, желает вÑтупить в армию Вашего Ð³ÐµÑ€Ð¾Ñ Ð·Ð° " "плату. Желаете нанÑть Личей?" msgid "" "You've found the ruins of an ancient city, now inhabited solely by the " "undead. Will you search?" msgstr "" "Ð’Ñ‹ нашли руины древнего города, ныне наÑеленного лишь нежитью. Желаете " "обÑледовать город?" msgid "" "Some of the surviving Liches are impressed by your victory over their " "fellows, and offer to join you for a price. Do you want to recruit Liches?" msgstr "" "Уцелевшие Личи прониклиÑÑŒ Вашей победой над их ÑобратьÑми и предложили Ñвои " "уÑлуги за плату. Желаете нанÑть Личей?" msgid "" "You've found one of those bridges that Trolls are so fond of living under, " "but there are none here. Perhaps there will be some next week." msgstr "" "Ð’Ñ‹ нашли один из моÑтов, под которым обычно обитают Тролли, но тут никого " "нет. Возможно на Ñледующей неделе поÑвитÑÑ Ð½ÐµÑколько." msgid "" "Some Trolls living under a bridge are willing to join your army, but for a " "price. Do you want to recruit Trolls?" msgstr "" "ÐеÑколько Троллей, живущих под моÑтом, желают вÑтупить в армию Вашего героÑ, " "но не задаром. Ð’Ñ‹ хотите нанÑть Троллей?" msgid "Trolls living under the bridge challenge you. Will you fight them?" msgstr "Тролли, живущие под моÑтом, вызывают Ð’Ð°Ñ Ð½Ð° бой. Ð’Ñ‹ ÑхватитеÑÑŒ Ñ Ð½Ð¸Ð¼Ð¸?" msgid "" "A few Trolls remain, cowering under the bridge. They approach you and offer " "to join your forces as mercenaries. Do you want to buy any Trolls?" msgstr "" "ÐеÑколько Троллей уÑпели укрытьÑÑ Ð¿Ð¾Ð´ моÑтом. Они обратилиÑÑŒ к Вам Ñ " "предложением вÑтупить в армию Вашего героÑ. Ð’Ñ‹ хотите нанÑть Троллей?" msgid "" "The Dragon city has no Dragons willing to join you this week. Perhaps a " "Dragon will become available next week." msgstr "" "Ðа Ñтой неделе в городе нет Драконов, желающих примкнуть к Вам. Приходите на " "Ñледующей неделе." msgid "" "The Dragon city is willing to offer some Dragons for your army for a price. " "Do you wish to recruit Dragons?" msgstr "" "Город драконов готов предложить войÑку Вашего Ð³ÐµÑ€Ð¾Ñ Ð”Ñ€Ð°ÐºÐ¾Ð½Ð¾Ð², не беÑплатно. " "Желаете нанÑть Драконов?" msgid "" "You stand before the Dragon City, a place off-limits to mere humans. Do you " "wish to violate this rule and challenge the Dragons to a fight?" msgstr "" "Ð’Ñ‹ Ñтоите перед Драконьим городом - меÑтом, запретным Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñтых Ñмертных. " "ОÑмелитеÑÑŒ ли Ð’Ñ‹ нарушить Ñто правило и броÑить вызов Драконам?" msgid "" "Having defeated the Dragon champions, the city's leaders agree to supply " "some Dragons to your army for a price. Do you wish to recruit Dragons?" msgstr "" "ПоÑле победы над лучшими драконьими воинами отцы города ÑоглаÑилиÑÑŒ за плату " "предоÑтавить войÑку Вашего Ð³ÐµÑ€Ð¾Ñ Ð”Ñ€Ð°ÐºÐ¾Ð½Ð¾Ð². Желаете нанÑть Драконов?" msgid "From the observation tower, you are able to see distant lands." msgstr "С вершины обзорной башни Ð’Ñ‹ разглÑдели дальние земли." msgid "" "The spring only refills once a week, and someone's already been here this " "week." msgstr "" "Этот родник воÑполнÑетÑÑ Ñ€Ð°Ð· в неделю, а кто-то уже пил из него на Ñтой " "неделе." msgid "" "A drink at the spring is supposed to give you twice your normal spell " "points, but you are already at that level." msgstr "" "Глоток из родника обычно удваивает магичеÑкую Ñнергию выпившего, однако у " "Ð’Ð°Ñ ÑƒÐ¶Ðµ удвоенный уровень." msgid "" "A drink from the spring fills your blood with magic! You have twice your " "normal spell points in reserve." msgstr "" "Глоток из родника наполнÑет кровь Ð³ÐµÑ€Ð¾Ñ Ð¼Ð°Ð³Ð¸ÐµÐ¹! Теперь магичеÑкой Ñнергии у " "него в запаÑе вдвое больше обычного." msgid "" "Recognizing you, the butler refuses to admit you. \"The master,\" he says, " "\"will not see the same student twice.\"" msgstr "" "Узнав ВаÑ, лакей отказываетÑÑ Ð²Ð¿ÑƒÑтить в дом. \"Мой хозÑин не будет обучать " "Ð’Ð°Ñ Ð´Ð²Ð°Ð¶Ð´Ñ‹.\" - Ñказал он." msgid "" "The butler admits you to see the master of the house. He trains you in the " "four skills a hero should know." msgstr "" "Лакей приглаÑил Ð’Ð°Ñ Ð²ÑтретитьÑÑ Ñ Ñ…Ð¾Ð·Ñином. Тот обучил Ð’Ð°Ñ Ð²Ñем четырём " "первичным навыкам." msgid "" "The butler opens the door and looks you up and down. \"You are neither " "famous nor diplomatic enough to be admitted to see my master,\" he sniffs. " "\"Come back when you think yourself worthy.\"" msgstr "" "Лакей открыл дверь и оглÑдел Ð’Ð°Ñ Ñ Ð½Ð¾Ð³ до головы. \"Ð’Ñ‹ не так знамениты и не " "такой уж дипломат, чтобы мой хозÑин принÑл ВаÑ,\" - фыркнул он. - " "\"ВозвращайтеÑÑŒ, когда будете доÑтойны.\"" msgid " and " msgstr " и " msgid "" "All of the %{monsters} you have in your army have been trained by the battle " "masters of the fort. Your army now contains %{monsters2}." msgstr "" "Ð’Ñе %{monsters} в армии Ð³ÐµÑ€Ð¾Ñ Ð±Ñ‹Ð»Ð¸ обучены маÑтерами форта. Теперь в армии " "Ð³ÐµÑ€Ð¾Ñ ÐµÑть %{monsters2}." msgid "" "An unusual alliance of Ogres, Orcs, and Dwarves offer to train (upgrade) any " "such troops brought to them. Unfortunately, you have none with you." msgstr "" "Ðеобычный Ñоюз орков, огров и гномов предлагает герою потренировать " "(улучшить) любые подобные им войÑка. К Ñожалению, в армии Ð³ÐµÑ€Ð¾Ñ Ñ‚Ð°ÐºÐ¸Ñ… " "ÑущеÑтв нет." msgid "All of your %{monsters} have been upgraded into %{monsters2}." msgstr "Ð’Ñе %{monsters} в армии Ð³ÐµÑ€Ð¾Ñ Ð±Ñ‹Ð»Ð¸ улучшены до %{monsters2}." msgid "" "A blacksmith working at the foundry offers to convert all Pikemen and " "Swordsmen's weapons brought to him from iron to steel. He also says that he " "knows a process that will convert Iron Golems into Steel Golems. " "Unfortunately, you have none of these troops in your army, so he can't help " "you." msgstr "" "Кузнец при Ñтой литейной предлагает заменить оружие копейщиков и мечников Ñ " "железного на Ñтальное. Он также Ñказал, что владеет технологией переплавки " "железных големов в Ñтальные. К Ñожалению, никого из них нет в армии героÑ, и " "он не может Вам ничем помочь." msgid "" "The captain looks at you with surprise and says:\n" "\"You already have all the maps I know about. Let me fish in peace now.\"" msgstr "" "Капитан Ñмотрит на Ð’Ð°Ñ ÑƒÐ´Ð¸Ð²Ð»Ñ‘Ð½Ð½Ð¾ и говорит:\n" "\"У Ñ‚ÐµÐ±Ñ ÑƒÐ¶Ðµ еÑть вÑе карты, о которых Ñ Ð·Ð½Ð°ÑŽ. Дай мне теперь Ñпокойно " "порыбачить\"." msgid "" "A retired captain living on this refurbished fishing platform offers to sell " "you maps of the sea he made in his younger days for 1,000 gold. Do you wish " "to buy the maps?" msgstr "" "Бывший капитан, живущий на Ñтом подновленном рыболовном причале, предлагает " "Вам карты, ÑоÑтавленные им в прежние дни, за 1000 золотых. Желаете их купить?" msgid "" "The captain sighs. \"You don't have enough money, eh? You can't expect me " "to give my maps away for free!\"" msgstr "" "Капитан вздыхает. \"Вам не хватает денег, да? Ð’Ñ‹ же не думаете, что Ñ Ð¾Ñ‚Ð´Ð°Ð¼ " "их Вам проÑто так?!\"" msgid "" "You come upon an obelisk made from a type of stone you have never seen " "before. Staring at it intensely, the smooth surface suddenly changes to an " "inscription. The inscription is a piece of a lost ancient map. Quickly you " "copy down the piece and the inscription vanishes as abruptly as it appeared." msgstr "" "Перед Вами обелиÑк, выÑеченный из невиданного камнÑ. Ð’Ñ‹ вглÑдываетеÑÑŒ в его " "гладкую поверхноÑть и вдруг замечаете, что на ней начинают проÑтупать " "таинÑтвенные знаки. Знаки ÑкладываютÑÑ Ð²Ð¾ фрагмент древней карты. Ð’Ñ‹ " "торопливо ÑриÑовываете его, и знаки иÑчезают так же внезапно, как и " "поÑвилиÑÑŒ." msgid "You have already been to this obelisk." msgstr "Ð’Ñ‹ уже поÑещали Ñтот обелиÑк." msgid "" "Upon your approach, the tree opens its eyes in delight. \"It is good to see " "you, my student. I hope my teachings have helped you.\"" msgstr "" "При приближении Ð³ÐµÑ€Ð¾Ñ Ð´Ñ€ÐµÐ²ÐµÑные глаза заÑветилиÑÑŒ воÑторгом. \"Рад Ñнова " "Ñ‚ÐµÐ±Ñ Ð²Ð¸Ð´ÐµÑ‚ÑŒ, мой ученик. Я надеюÑÑŒ мои Ð·Ð½Ð°Ð½Ð¸Ñ Ñ‚ÐµÐ±Ðµ помогли.\"" msgid "" "Upon your approach, the tree opens its eyes in delight. \"Ahh, an " "adventurer! Allow me to teach you a little of what I have learned over the " "ages.\"" msgstr "" "При приближении Ð³ÐµÑ€Ð¾Ñ Ð´Ñ€ÐµÐ²ÐµÑные глаза заÑветилиÑÑŒ воÑторгом. \"Ð, Ñтранник! " "Позволь преподать тебе малую толику того, что Ñ Ð²Ñ‹ÑƒÑ‡Ð¸Ð» за годы.\"" msgid "Upon your approach, the tree opens its eyes in delight." msgstr "При приближении Ð³ÐµÑ€Ð¾Ñ Ð´Ñ€ÐµÐ²ÐµÑные глаза заÑветилиÑÑŒ воÑторгом." msgid "" "\"Ahh, an adventurer! I will be happy to teach you a little of what I have " "learned over the ages for a mere %{count} %{res}.\"" msgstr "" "\"Ооо, дорогой путник! Я буду ÑчаÑтлив научить Ñ‚ÐµÐ±Ñ Ð½ÐµÐ±Ð¾Ð»ÑŒÑˆÐ¾Ð¹ чаÑти моих " "знаний вÑего-лишь за %{count} %{res}.\"" msgid "(Just bury it around my roots.)" msgstr "(ПроÑто зарой их вокруг моих корней.)" msgid "Tears brim in the eyes of the tree." msgstr "Слёзы потекли по краÑм глаз дерева." msgid "\"I need %{count} %{res}.\"" msgstr "\"Мне нужно %{count} %{res}.\"" msgid "it whispers. (sniff) \"Well, come back when you can pay me.\"" msgstr "" "прошептало оно, вÑхлипнув. \"Ðу, возвращайÑÑ ÐºÐ¾Ð³Ð´Ð° Ñможешь мне заплатить.\"" msgid "" "Nestled among the trees sits a blind seer. After you explain the intent of " "your journey, the seer activates his crystal ball, allowing you to see the " "strengths and weaknesses of your opponents." msgstr "" "Среди деревьев Ñидит Ñлепой провидец. Когда Ð’Ñ‹ объÑÑнили ему цель Ñвоего " "путешеÑтвиÑ, провидец активировал Ñвой хруÑтальный шар, позволив увидеть " "Ñильные и Ñлабые Ñтороны Ваших противников." msgid "" "The entrance to the cave is dark, and a foul, sulfurous smell issues from " "the cave mouth. Will you enter?" msgstr "" "Вход в пещеру зиÑет чёрной дырой, из которой Ñ‚Ñнет тошнотворным ÑерниÑтым " "зловонием. ОтважитеÑÑŒ ли Ð’Ñ‹ войти?" msgid "Except for evidence of a terrible battle, the cave is empty." msgstr "ЕÑли не Ñчитать Ñледов ужаÑной битвы, пещера пуÑта." msgid "" "You find a powerful and grotesque Demon in the cave. \"Today,\" it rasps, " "\"you will fight and surely die. But I will give you a choice of deaths. You " "may fight me, or you may fight my servants. Do you prefer to fight my " "servants?\"" msgstr "" "Ð’ пещере Ð’Ñ‹ обнаружили грозного до нелепоÑти демона. \"СегоднÑ,\" - прорычал " "он, - \"Ñ‚ÐµÐ±Ñ Ð¶Ð´ÑƒÑ‚ бой и Ð²ÐµÑ€Ð½Ð°Ñ Ñмерть, но Ñ Ð´Ð°Ð¼ тебе выбор. Ты можешь " "дратьÑÑ Ñо мной или же Ñ Ð¼Ð¾Ð¸Ð¼Ð¸ Ñлугами. Предпочитаешь ÑразитьÑÑ Ñ Ð¼Ð¾Ð¸Ð¼Ð¸ " "Ñлугами?\"" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points." msgstr "" "Демон жутко кричит и броÑаетÑÑ Ð² атаку! ПоÑле жеÑтокой, но короткой битвы Ð’Ñ‹ " "победили монÑтра и получили %{exp} очков опыта." msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points and " "%{count} gold." msgstr "" "Демон жутко кричит и броÑаетÑÑ Ð² атаку! ПоÑле жеÑтокой, но короткой битвы Ð’Ñ‹ " "победили монÑтра и получили %{exp} очков опыта и %{count} золота." msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and find the %{art} in the back of the cave." msgstr "" "Демон выкрикнул Ñвой вызов и броÑилÑÑ Ð² бой! ПоÑле краткой, но отчаÑнной " "Ñхватки Ð’Ñ‹ прикончили чудовище и нашли %{art} в глубине пещеры." msgid "" "Seeing that you do not have %{count} gold, the demon slashes you with its " "claws, and the last thing you see is a red haze." msgstr "" "Даже %{count} жалких монет не наберетÑÑ Ñƒ ВаÑ. Демон Ñжимает когти и перед " "глазами вÑтает краÑÐ½Ð°Ñ Ð¿ÐµÐ»ÐµÐ½Ð°. Больше Ð’Ñ‹ ничего не видите..." msgid "" "The Demon leaps upon you and has its claws at your throat before you can " "even draw your sword. \"Your life is mine,\" it says. \"I will sell it back " "to you for %{count} gold.\"" msgstr "" "\"Демон прыгает на Ð³ÐµÑ€Ð¾Ñ Ð¸ мгновенно вонзает когти в его глотку. \"Ð¢Ð²Ð¾Ñ " "жизнь в моих рукаххх,\" - шипит он. - \"давай золото, %{count} монет, и " "убирайÑÑ, пока Ñ Ð´Ð¾Ð±Ñ€Ñ‹Ð¹.\"" msgid "" "Upon defeating the daemon's servants, you find a hidden cache with %{count} " "gold." msgstr "" "ПоÑле победы над Ñлугами демона Ð’Ñ‹ обнаружили тайник Ñ Ð·Ð¾Ð»Ð¾Ñ‚Ð¾Ð¼ из %{count} " "монет." msgid "" "As you enter the Alchemist's Tower, a hobbled, graying man in a brown cloak " "makes his way towards you." msgstr "" "Когда Ð’Ñ‹ входите в башню алхимика, хромаÑ, к Вам направлÑетÑÑ Ñедеющий " "Ñтарец в коричневом плаще." msgid "He checks your pack, and sees that you have 1 cursed item." msgid_plural "" "He checks your pack, and sees that you have %{count} cursed items." msgstr[0] "Он проверÑет Ð’Ð°Ñ Ð¸ видит %{count} проклÑтый артефакт." msgstr[1] "Он проверÑет Ð’Ð°Ñ Ð¸ видит %{count} проклÑтых артефакта." msgstr[2] "Он проверÑет Ð’Ð°Ñ Ð¸ видит %{count} проклÑтых артефактов." msgid "For %{gold} gold, the alchemist will remove it for you. Do you pay?" msgid_plural "" "For %{gold} gold, the alchemist will remove them for you. Do you pay?" msgstr[0] "" "За %{gold} золотом, алхимик поможет Вам уничтожить его. Ð’Ñ‹ заплатите?" msgstr[1] "" "За %{gold} золотом, алхимик поможет Вам уничтожить каждый. Ð’Ñ‹ заплатите?" msgstr[2] "" "За %{gold} золотом, алхимик поможет Вам уничтожить каждый. Ð’Ñ‹ заплатите?" msgid "" "After you consent to pay the requested amount of gold, the alchemist grabs " "the cursed artifact and throws it into his magical cauldron." msgid_plural "" "After you consent to pay the requested amount of gold, the alchemist grabs " "all cursed artifacts and throws them into his magical cauldron." msgstr[0] "" "ПоÑле того, как Ð’Ñ‹ ÑоглаÑилиÑÑŒ заплатить запрошенную Ñумму золота, алхимик " "хватает проклÑтый артефакт и броÑает его в Ñвой волшебный котёл." msgstr[1] "" "ПоÑле того, как Ð’Ñ‹ ÑоглаÑилиÑÑŒ заплатить запрошенную Ñумму золота, алхимик " "хватает проклÑтые артефакты и броÑает их в Ñвой волшебный котёл." msgstr[2] "" "ПоÑле того, как Ð’Ñ‹ ÑоглаÑилиÑÑŒ заплатить запрошенную Ñумму золота, алхимик " "хватает проклÑтые артефакты и броÑает их в Ñвой волшебный котёл." msgid "" "You hear a voice from behind the locked door, \"You don't have enough gold " "to pay for my services.\"" msgstr "" "Ð’Ñ‹ Ñлышите Ð³Ð¾Ð»Ð¾Ñ Ð¸Ð·-за закрытой двери, \"Проходите мимо. У Ð’Ð°Ñ Ð½ÐµÐ´Ð¾Ñтаточно " "золота, чтобы заплатить за мои уÑлуги. \"" msgid "" "You hear a voice from high above in the tower, \"Go away! I can't help you!\"" msgstr "" "Ð’Ñ‹ уÑлышали Ð³Ð¾Ð»Ð¾Ñ Ñ Ð²ÐµÑ€ÑˆÐ¸Ð½Ñ‹ башни: \"УбирайтеÑÑŒ! Я не могу Вам помочь!\"" msgid "" "The head groom speaks to you, \"That is a fine looking horse you have. I am " "afraid we can give you no better, but the horses your cavalry are riding " "look to be of poor breeding stock. We have many trained war horses which " "would aid your riders greatly. I insist you take them.\"" msgstr "" "К герою подошёл главный конюх. \"Какой у Ð’Ð°Ñ Ð¿Ñ€ÐµÐºÑ€Ð°Ñный конь. БоюÑÑŒ, Ñ Ð½Ðµ " "могу предложить Вам ничего лучше, но зато мы можем дать тренированных боевых " "коней Вашим вÑадникам.\"" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, he will " "grow tired in a week. You must also let me give better horses to your " "mounted soldiers, their horses look shoddy and weak.\"" msgstr "" "Когда Ваш герой приблизилÑÑ Ðº конюшнÑм, поÑвилÑÑ Ð³Ð»Ð°Ð²Ð½Ñ‹Ð¹ конюх, ведущий " "краÑивого боевого Ñкакуна. \"Этот конь поможет Вам быÑтрее добратьÑÑ Ð´Ð¾ " "нужного меÑта, но его Ñил хватит на 7 дней. К тому же, мы можем дать новых " "коней и Вашим вÑадникам.\"" msgid "" "The head groom approaches you and speaks, \"You already have a fine horse, " "and have no inexperienced cavalry which might make use of our trained war " "horses.\"" msgstr "" "ПоÑвилÑÑ Ñтарший конюх, ведущий краÑивого боевого Ñкакуна. \"У Ð’Ð°Ñ Ð¾Ñ‚Ð»Ð¸Ñ‡Ð½Ñ‹Ð¹ " "конь под Ñедлом, и в Вашей армии нет неопытных вÑадников, кого мы могли бы " "обучить иÑкуÑÑтву езды на тренированных боевых Ñкакунах.\"" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, his " "endurance will wane with a lot of heavy riding, and you must return for a " "fresh mount in a week. We also have many fine war horses which could benefit " "mounted soldiers, but you have none we can help.\"" msgstr "" "Когда Ваш герой приблизилÑÑ Ðº конюшнÑм, поÑвилÑÑ Ð³Ð»Ð°Ð²Ð½Ñ‹Ð¹ конюх, ведущий " "краÑивого боевого Ñкакуна. \"Этот конь поможет Вам быÑтрее добратьÑÑ Ð´Ð¾ " "нужного меÑта, но его Ñил хватит на 7 дней. У Ð½Ð°Ñ Ñ‚Ð°ÐºÐ¶Ðµ много тренированных " "боевых коней, которые могли бы принеÑти пользу Вашим вÑадникам, но у Ð’Ð°Ñ Ð½ÐµÑ‚ " "никого, кому мы могли бы помочь. \"" msgid "The Arena guards turn you away." msgstr "Стража арены преградила Вам дорогу и не пропуÑтила внутрь." msgid "" "You have your crew stop up their ears with wax before the sirens' eerie song " "has any chance of luring them to a watery grave." msgstr "" "Ð’Ñ‹ приказали Ñвоей команде залить уши воÑком, прежде чем подплыть к Ñиренам, " "чтобы поÑлушать их пение, которое запроÑто может погубить вÑех в морÑкой " "пучине." msgid "" "As the sirens sing their eerie song, your small, determined army manages to " "overcome the urge to dive headlong into the sea." msgstr "" "Едва Ñирены затÑнули Ñвою ужаÑную пеÑню, как команда Ð³ÐµÑ€Ð¾Ñ Ð¿Ð¾Ñильнее налегла " "на вёÑла и увела корабль подальше в открытое море." msgid "" "An eerie wailing song emanates from the sirens perched upon the rocks. Many " "of your crew fall under its spell, and dive into the water where they drown. " "You are now wiser for the visit, and gain %{exp} experience." msgstr "" "Ðад камнÑми разнеÑлаÑÑŒ Ð¶ÑƒÑ‚ÐºÐ°Ñ Ð¿ÐµÑÐ½Ñ Ñирен. Многие из команды Ð³ÐµÑ€Ð¾Ñ Ð±Ñ€Ð¾ÑилиÑÑŒ " "в воду под дейÑтвием Ñтих чар и утонули в морÑкой пучине. Ð”Ð»Ñ Ð³ÐµÑ€Ð¾Ñ Ñто был " "хороший урок, давший %{exp} очков опыта." msgid "" "In a dazzling display of daring, you break into the local jail and free the " "hero imprisoned there, who, in return, pledges loyalty to your cause." msgstr "" "С оÑлепительной отвагой Ð’Ñ‹ ворвалиÑÑŒ в меÑтную темницу и оÑвободили героÑ, " "томÑщегоÑÑ Ð·Ð´ÐµÑÑŒ. Ð’ благодарноÑть он поклÑлÑÑ Ñлужить Вам." msgid "" "You already have %{count} heroes, and regretfully must leave the prisoner in " "this jail to languish in agony for untold days." msgstr "" "У Ð’Ð°Ñ ÑƒÐ¶Ðµ %{count} героев. К Ñожалению, Вам придётÑÑ Ð¾Ñтавить томитьÑÑ Ñтого " "Ð³ÐµÑ€Ð¾Ñ Ð² темнице ещё неопределённое времÑ." msgid "" "You enter a rickety hut and talk to the magician who lives there. He tells " "you of places near and far which may aid you in your journeys." msgstr "" "Герой зашёл в ветхую хибару и заговорил Ñ Ð¼Ð°Ð³Ð¾Ð¼, живущим тут. Маг поведал " "герою о меÑтах, которые ему доводилоÑÑŒ видеть. Это может пригодитÑÑ Ð’Ð°Ð¼ в " "Ваших путешеÑтвиÑÑ…." msgid "This eye seems to be intently studying its surroundings." msgstr "КажетÑÑ, Ñтот глаз внимательно изучает окреÑтноÑти." msgid "You come across a giant Sphinx. The Sphinx remains strangely quiet." msgstr "Ð’Ñ‹ подошли к огромному СфинкÑу, но он даже не шелохнулÑÑ." msgid "" "\"I have a riddle for you,\" the Sphinx says. \"Answer correctly, and you " "shall be rewarded. Answer incorrectly, and you shall be eaten. Do you accept " "the challenge?\"" msgstr "" "\"ЕÑть у Ð¼ÐµÐ½Ñ Ð·Ð°Ð³Ð°Ð´ÐºÐ° Ð´Ð»Ñ Ñ‚ÐµÐ±Ñ,\" - Ñказал СфинкÑ. \"Ответишь верно - " "получишь награду. ОшибешьÑÑ - и Ñ Ñ‚ÐµÐ±Ñ Ñъем. Принимаешь ли ты мой вызов?" msgid "" "The Sphinx asks you the following riddle:\n" "\n" "'%{riddle}'\n" "\n" "Your answer?" msgstr "" "Ð¡Ñ„Ð¸Ð½ÐºÑ Ð·Ð°Ð³Ð°Ð´Ð°Ð» Вам Ñледующую загадку: \n" "\n" "'%{riddle}'\n" "\n" "Ваш ответ?" msgid "" "\"You guessed incorrectly,\" the Sphinx says, smiling. The Sphinx swipes at " "you with a paw, knocking you to the ground. Another blow makes the world go " "black, and you know no more." msgstr "" "\"Ð¢Ð²Ð¾Ñ Ð´Ð¾Ð³Ð°Ð´ÐºÐ° ошибочна,\" - Ñказал, улыбаÑÑÑŒ, СфинкÑ. УхмылÑющийÑÑ Ð¡Ñ„Ð¸Ð½ÐºÑ " "повалил Ð’Ð°Ñ Ð½Ð° землю и мир окутала непроглÑÐ´Ð½Ð°Ñ Ñ‚ÑŒÐ¼Ð°." msgid "" "Looking somewhat disappointed, the Sphinx sighs. \"You've answered my riddle " "so here's your reward. Now begone.\"" msgstr "" "ÐеÑколько разочарованно Ð¡Ñ„Ð¸Ð½ÐºÑ Ð¿Ñ€Ð¾Ð¼Ð¾Ð»Ð²Ð¸Ð»: \"Ты дал верный ответ, вот Ñ‚Ð²Ð¾Ñ " "награда. Ртеперь убирайÑÑ.\"" msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "As you speak the magic word, the glowing barrier dissolves into nothingness." msgstr "" "Вам преграждает путь магичеÑкий барьер. Вдруг, на нём, магичеÑкими рунами, " "выÑветилаÑÑŒ надпиÑÑŒ:\n" " \"ПроизнеÑи волшебное Ñлово и Ñможешь пройти\"\n" " Ð’Ñ‹ произноÑите волшебное Ñлово и магичеÑкий барьер иÑчезает." msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "You speak, and nothing happens." msgstr "" "Вам преграждает путь магичеÑкий барьер. Вдруг, на нем, магичеÑкими рунами, " "выÑветилаÑÑŒ надпиÑÑŒ:\n" " \"ПроизнеÑи волшебное Ñлово и Ñможешь пройти\"\n" " Ð’Ñ‹ произноÑите разные Ñлова, но ничего не проиÑходит." msgid "" "You enter the tent and see an old woman gazing into a magic gem. She looks " "up and says,\n" "\"In my travels, I have learned much in the way of arcane magic. A great " "oracle taught me his skill. I have the answer you seek.\"" msgstr "" "Ð’Ñ‹ входите в палатку и видите Ñтарую женщину, она приÑтально Ñмотрит в " "волшебный камень. Она Ñмотрит и говорит: \"Ð’ дальних Ñтранах Ñ Ð¾Ð±ÑƒÑ‡Ð°Ð»Ð°ÑÑŒ " "тайной магии. Сам великий оракул обучал Ð¼ÐµÐ½Ñ Ð´Ñ€ÐµÐ²Ð½Ð¸Ð¼ хитроÑÑ‚Ñм. У Ð¼ÐµÐ½Ñ ÐµÑть " "ответ, который Ð’Ñ‹ ищете.\"" msgid "No spell book is present." msgstr "Книга заклинаний отÑутÑтвует." msgid "" "This spell costs %{mana} spell points. You have no spell points, so you " "cannot cast it." msgstr "" "Заклинание требует %{mana} очков магии. У Ð’Ð°Ñ Ð½ÐµÑ‚ очков магии, поÑтому Ð’Ñ‹ не " "можете произнеÑти заклинание." msgid "" "This spell costs %{mana} spell points. You only have %{point} spell points, " "so you cannot cast it." msgstr "" "Заклинание требует %{mana} очков магии. У Ð’Ð°Ñ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ %{point}, поÑтому Ð’Ñ‹ не " "можете произнеÑти заклинание." msgid "The spell was not found." msgstr "Заклинание не найдено." msgid "Only heroes can cast this spell." msgstr "Только герои могут колдовать данное заклинание." msgid "This hero is not able to cast adventure spells." msgstr "Ваш герой не может иÑпользовать Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ Ð½Ð° карте." msgid "Your hero is too tired to cast this spell today. Try again tomorrow." msgstr "" "Ваш герой очень уÑтал, и не Ñможет применÑть магию ÑегоднÑ. Попробуйте ещё " "раз завтра." msgid "This spell cannot be cast on a boat." msgstr "Это заклинание не дейÑтвует в море." msgid "This spell can only be cast near an ocean." msgstr "Это заклинание дейÑтвует только у воды." msgid "" "There are no boats available and no ocean adjacent to the hero where this " "spell will work." msgstr "" "Ðет Ñвободных кораблей и Ñ€Ñдом Ñ Ð³ÐµÑ€Ð¾ÐµÐ¼ нет океана или меÑта в океане, где " "Ñработает Ñто заклинание." msgid "There are no boats available for this spell." msgstr "ОтÑутÑтвуют Ñвободные корабли Ð´Ð»Ñ Ð¿Ñ€Ð¸Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñтого заклинаниÑ." msgid "There is no ocean adjacent to the hero where this spell will work." msgstr "" "РÑдом Ñ Ð³ÐµÑ€Ð¾ÐµÐ¼ нет океана или меÑта в океане, где Ñработает Ñто заклинание." msgid "" "You do not own any town or castle that is not currently occupied by a hero. " "This spell will have no effect." msgstr "" "У Ð’Ð°Ñ Ð½ÐµÑ‚ ни одного Ñвободного города или замка. Потому Ñто заклинание не " "Ñработает." msgid "This hero is already in a town, so this spell will have no effect." msgstr "Этот герой уже в замке, поÑтому Ñто заклинание не Ñработает." msgid "" "The nearest town is %{town}.\n" "\n" "This town is occupied by your hero %{hero}." msgstr "" "Ближайший к Вам город: %{town}.\n" "\n" "Ð’ Ñтом городе уже еÑть Ваш герой: %{hero}." msgid "This spell is already in effect." msgstr "Это заклинание уже дейÑтвует." msgid "" "No opponent neither has nor can have any hero under their command anymore. " "Casting this spell will have no effect." msgstr "" "Ðет ни одного противника у которого еÑть или могли бы быть герои. Это " "заклинание не Ñработает." msgid "" "No opponent has a hero under their command at this time. Casting this spell " "will have no effect." msgstr "" "Ð’ наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ Ð½Ð¸ у одного противника нет героев. Это заклинание не " "Ñработает." msgid "" "You must be within %{count} spaces of a monster for the Visions spell to " "work." msgstr "" "Ð’Ñ‹ должны находитьÑÑ Ð½Ðµ далее %{count} ходов от армии ÑущеÑтв Ð´Ð»Ñ Ð¼Ð°Ð³Ð¸Ð¸ " "Видение." msgid "" "You must be standing on the entrance to a mine (sawmills and alchemist labs " "do not count) to cast this spell." msgstr "" "Ð”Ð»Ñ Ð¸ÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ñтого Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ Ð’Ñ‹ должны подойти ко входу в шахту (на " "леÑопилки и лаборатории алхимика оно не дейÑтвует)." msgid "You must first defeat the ghosts guarding the mine to cast this spell." msgstr "" "Ð”Ð»Ñ Ð¸ÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ Ð’Ð°Ð¼ необходимо Ñначала победить призраков, " "охранÑющих шахту." msgid "" "There are already at least as many elementals guarding the mine as this hero " "can generate. Casting this spell will have no effect." msgstr "" "Эту шахту уже охранÑет не меньшее чиÑло Ñлементалей, чем Ð’Ñ‹ можете призвать. " "Это заклинание не Ñработает." msgid "%{name} the %{race} (Level %{level})" msgstr "%{race} %{name} (уровень %{level})" msgid "" "'Spread' combat formation spreads the hero's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "Разомкнутый боевой порÑдок предпиÑывает армии Ð³ÐµÑ€Ð¾Ñ Ð·Ð°Ð½Ñть позиции по вÑей " "ширине Ñтартового раÑÐ¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð²Ð¾Ð¹Ñк в бою Ñ Ð¸Ð½Ñ‚ÐµÑ€Ð²Ð°Ð»Ð°Ð¼Ð¸ между отрÑдами." msgid "" "'Grouped' combat formation bunches the hero's army together in the center of " "their side of the battlefield." msgstr "" "Сомкнутый боевой порÑдок предпиÑывает отрÑдам армии Ð³ÐµÑ€Ð¾Ñ Ð¾Ñ€Ð³Ð°Ð½Ð¸Ð·Ð¾Ð²Ð°Ñ‚ÑŒ " "плотное поÑтроение поÑередине Ñтартового раÑÐ¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð²Ð¾Ð¹Ñк в бою." msgid "Are you sure you want to dismiss this Hero?" msgstr "Ð’Ñ‹ дейÑтвительно хотите уволить героÑ?" msgid "View Experience Info" msgstr "Опыт" msgid "View Spell Points Info" msgstr "Очки магии" msgid "Set army combat formation to 'Spread'" msgstr "Разомкнутый Ñтрой" msgid "Set army combat formation to 'Grouped'" msgstr "Сомкнутый Ñтрой" msgid "Exit Hero Screen" msgstr "Закрыть Экран ГероÑ" msgid "You cannot dismiss a hero in a castle" msgstr "ÐÐµÐ»ÑŒÐ·Ñ ÑƒÐ²Ð¾Ð»Ð¸Ñ‚ÑŒ Ð³ÐµÑ€Ð¾Ñ Ð² замке" msgid "Dismissal of %{name} the %{race} is prohibited by scenario" msgstr "Увольнение %{name} %{race} запрещено Ñценарием" msgid "Dismiss %{name} the %{race}" msgstr "Уволить %{race} %{name}" msgid "Show previous hero" msgstr "Предыдущий герой" msgid "Show next hero" msgstr "Следующий герой" msgid "Blood Morale" msgstr "Ð’ Бой!" msgid "%{morale} Morale" msgstr "Мораль %{morale}" msgid "%{luck} Luck" msgstr "Удача %{luck}" msgid "Current Luck Modifiers:" msgstr "Текущие модификаторы удачи:" msgid "Current Morale Modifiers:" msgstr "Текущие модификаторы морали:" msgid "Entire army is undead, so morale does not apply." msgstr "Мораль неприменима к нежити." msgid "" "Current experience %{exp1}.\n" " Next level %{exp2}." msgstr "" "Текущий опыт %{exp1}.\n" "Следующий уровень %{exp2}." msgid "Level %{level}" msgstr "Уровень %{level}" msgid "" "%{name} currently has %{point} spell points out of a maximum of %{max}. The " "maximum number of spell points is 10 times the hero's knowledge. It is " "occasionally possible for the hero to have more than their maximum spell " "points via special events." msgstr "" "%{name} имеет %{point} очков магии из макÑимальных %{max}. МакÑимальное " "чиÑло очков магии - количеÑтво очков навыка Ð·Ð½Ð°Ð½Ð¸Ñ Ð³ÐµÑ€Ð¾Ñ Ð² деÑÑтикратном " "размере. Иногда, поÑле поÑÐµÑ‰ÐµÐ½Ð¸Ñ Ñпециальных меÑÑ‚, герой может получить " "больше очков заклинаний, чем данное макÑимальное чиÑло." msgid "%{name1} meets %{name2}" msgstr "%{name1} вÑтретил %{name2}" msgid "Enemy heroes are now fully identifiable." msgstr "Ð’Ñе вражеÑкие Герои опознаны." msgid "Identify Hero" msgstr "Опознать героÑ" msgid "Select town to port to." msgstr "Выберите, куда перенеÑтиÑÑŒ." msgid "Town Portal" msgstr "Портал города" msgid "The creatures are willing to join us!" msgstr "Эти ÑущеÑтва готовы приÑоединитьÑÑ!" msgid "All the creatures will join us..." msgstr "Ð’Ñе ÑущеÑтва приÑоединÑÑ‚ÑÑ Ðº армии героÑ..." msgid "The creature will join us..." msgid_plural "%{count} of the creatures will join us..." msgstr[0] "%{count} ÑущеÑтво приÑоединитÑÑ Ðº Вам..." msgstr[1] "%{count} ÑущеÑтва приÑоединитÑÑ Ðº Вам..." msgstr[2] "%{count} ÑущеÑтв приÑоединитÑÑ Ðº Вам..." msgid "" "\n" " for a fee of %{gold} gold." msgstr "" "\n" "за плату %{gold} золотом." msgid "These weak creatures will surely flee before us." msgstr "Эта труÑÐ»Ð¸Ð²Ð°Ñ Ð°Ñ€Ð¼Ð¸Ñ ÑƒÐ±ÐµÐ¶Ð¸Ñ‚ от ВаÑ..." msgid "I fear these creatures are in the mood for a fight." msgstr "Скорее вÑего, Ñти ÑущеÑтва наÑтроены ÑражатьÑÑ." msgid "The hero's attack skill is a bonus added to each unit's attack skill." msgstr "" "Ðавык атаки Ð³ÐµÑ€Ð¾Ñ Ð´Ð°Ñ‘Ñ‚ Ð±Ð¾Ð½ÑƒÑ Ðº навыку атаки каждого ÑущеÑтва в его армии." msgid "The hero's defense skill is a bonus added to each unit's defense skill." msgstr "" "Ðавык защиты Ð³ÐµÑ€Ð¾Ñ Ð´Ð°Ñ‘Ñ‚ Ð±Ð¾Ð½ÑƒÑ Ðº навыку защиты каждого ÑущеÑтва в его армии." msgid "The hero's spell power determines the duration or power of a spell." msgstr "" "Ðавык Ñилы магии Ð³ÐµÑ€Ð¾Ñ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»Ñет длительноÑть дейÑÑ‚Ð²Ð¸Ñ Ð¸Ð»Ð¸ Ñилу " "применённого им заклинаниÑ." msgid "" " The hero's knowledge determines how many spell points the hero may have. " "Under normal circumstances, a hero is limited to 10 spell points per level " "of knowledge." msgstr "" "Уровень знаний Ð³ÐµÑ€Ð¾Ñ Ð¾Ñ‚Ð²ÐµÑ‡Ð°ÐµÑ‚ за количеÑтво очков магии героÑ. Обычно, герой " "может получить 10 очков магии на каждый уровень знаний." msgid "Current Modifiers:" msgstr "Текущие модификаторы:" msgid "skill|Basic" msgstr "Базовый" msgid "skill|Advanced" msgstr "Продвинутый" msgid "skill|Expert" msgstr "ЭкÑперт" msgid "Pathfinding" msgstr "Следопыт" msgid "Archery" msgstr "Стрельба" msgid "Logistics" msgstr "ЛогиÑтика" msgid "Scouting" msgstr "Разведка" msgid "Diplomacy" msgstr "ДипломатиÑ" msgid "Navigation" msgstr "ÐавигациÑ" msgid "Leadership" msgstr "ЛидерÑтво" msgid "Wisdom" msgstr "МудроÑть" msgid "Mysticism" msgstr "МиÑтицизм" msgid "Ballistics" msgstr "БаллиÑтика" msgid "Eagle Eye" msgstr "Орлиный взор" msgid "Necromancy" msgstr "ÐекромантиÑ" msgid "Estates" msgstr "Казначей" msgid "Advanced Archery" msgstr "ÐŸÑ€Ð¾Ð´Ð²Ð¸Ð½ÑƒÑ‚Ð°Ñ Ñтрельба" msgid "Advanced Pathfinding" msgstr "Продвинутый Ñледопыт" msgid "Basic Archery" msgstr "Ð‘Ð°Ð·Ð¾Ð²Ð°Ñ Ñтрельба" msgid "Basic Pathfinding" msgstr "Базовый Ñледопыт" msgid "Expert Pathfinding" msgstr "ЭкÑпертный Ñледопыт" msgid "Advanced Logistics" msgstr "ÐŸÑ€Ð¾Ð´Ð²Ð¸Ð½ÑƒÑ‚Ð°Ñ Ð»Ð¾Ð³Ð¸Ñтика" msgid "Basic Logistics" msgstr "Ð‘Ð°Ð·Ð¾Ð²Ð°Ñ Ð»Ð¾Ð³Ð¸Ñтика" msgid "Basic Scouting" msgstr "Ð‘Ð°Ð·Ð¾Ð²Ð°Ñ Ñ€Ð°Ð·Ð²ÐµÐ´ÐºÐ°" msgid "Expert Archery" msgstr "ЭкÑÐ¿ÐµÑ€Ñ‚Ð½Ð°Ñ Ñтрельба" msgid "Expert Logistics" msgstr "ЭкÑÐ¿ÐµÑ€Ñ‚Ð½Ð°Ñ Ð»Ð¾Ð³Ð¸Ñтика" msgid "Advanced Diplomacy" msgstr "ÐŸÑ€Ð¾Ð´Ð²Ð¸Ð½ÑƒÑ‚Ð°Ñ Ð´Ð¸Ð¿Ð»Ð¾Ð¼Ð°Ñ‚Ð¸Ñ" msgid "Advanced Scouting" msgstr "ÐŸÑ€Ð¾Ð´Ð²Ð¸Ð½ÑƒÑ‚Ð°Ñ Ñ€Ð°Ð·Ð²ÐµÐ´ÐºÐ°" msgid "Basic Diplomacy" msgstr "Ð‘Ð°Ð·Ð¾Ð²Ð°Ñ Ð´Ð¸Ð¿Ð»Ð¾Ð¼Ð°Ñ‚Ð¸Ñ" msgid "Expert Diplomacy" msgstr "ЭкÑÐ¿ÐµÑ€Ñ‚Ð½Ð°Ñ Ð´Ð¸Ð¿Ð»Ð¾Ð¼Ð°Ñ‚Ð¸Ñ" msgid "Expert Scouting" msgstr "ЭкÑÐ¿ÐµÑ€Ñ‚Ð½Ð°Ñ Ñ€Ð°Ð·Ð²ÐµÐ´ÐºÐ°" msgid "Advanced Leadership" msgstr "Продвинутое лидерÑтво" msgid "Advanced Navigation" msgstr "ÐŸÑ€Ð¾Ð´Ð²Ð¸Ð½ÑƒÑ‚Ð°Ñ Ð½Ð°Ð²Ð¸Ð³Ð°Ñ†Ð¸Ñ" msgid "Basic Leadership" msgstr "Базовое лидерÑтво" msgid "Basic Navigation" msgstr "Ð‘Ð°Ð·Ð¾Ð²Ð°Ñ Ð½Ð°Ð²Ð¸Ð³Ð°Ñ†Ð¸Ñ" msgid "Expert Navigation" msgstr "ЭкÑÐ¿ÐµÑ€Ñ‚Ð½Ð°Ñ Ð½Ð°Ð²Ð¸Ð³Ð°Ñ†Ð¸Ñ" msgid "Advanced Wisdom" msgstr "ÐŸÑ€Ð¾Ð´Ð²Ð¸Ð½ÑƒÑ‚Ð°Ñ Ð¼ÑƒÐ´Ñ€Ð¾Ñть" msgid "Basic Mysticism" msgstr "Базовый миÑтицизм" msgid "Basic Wisdom" msgstr "Ð‘Ð°Ð·Ð¾Ð²Ð°Ñ Ð¼ÑƒÐ´Ñ€Ð¾Ñть" msgid "Expert Leadership" msgstr "ЭкÑпертное лидерÑтво" msgid "Expert Wisdom" msgstr "ЭкÑÐ¿ÐµÑ€Ñ‚Ð½Ð°Ñ Ð¼ÑƒÐ´Ñ€Ð¾Ñть" msgid "Advanced Luck" msgstr "ÐŸÑ€Ð¾Ð´Ð²Ð¸Ð½ÑƒÑ‚Ð°Ñ ÑƒÐ´Ð°Ñ‡Ð°" msgid "Advanced Mysticism" msgstr "Продвинутый миÑтицизм" msgid "Basic Luck" msgstr "Ð‘Ð°Ð·Ð¾Ð²Ð°Ñ ÑƒÐ´Ð°Ñ‡Ð°" msgid "Expert Luck" msgstr "ЭкÑÐ¿ÐµÑ€Ñ‚Ð½Ð°Ñ ÑƒÐ´Ð°Ñ‡Ð°" msgid "Expert Mysticism" msgstr "ЭкÑпертный миÑтицизм" msgid "Advanced Ballistics" msgstr "ÐŸÑ€Ð¾Ð´Ð²Ð¸Ð½ÑƒÑ‚Ð°Ñ Ð±Ð°Ð»Ð»Ð¸Ñтика" msgid "Advanced Eagle Eye" msgstr "Продвинутый орлиный взор" msgid "Basic Ballistics" msgstr "Ð‘Ð°Ð·Ð¾Ð²Ð°Ñ Ð±Ð°Ð»Ð»Ð¸Ñтика" msgid "Basic Eagle Eye" msgstr "Базовый орлиный взор" msgid "Expert Ballistics" msgstr "ЭкÑÐ¿ÐµÑ€Ñ‚Ð½Ð°Ñ Ð±Ð°Ð»Ð»Ð¸Ñтика" msgid "Advanced Necromancy" msgstr "ÐŸÑ€Ð¾Ð´Ð²Ð¸Ð½ÑƒÑ‚Ð°Ñ Ð½ÐµÐºÑ€Ð¾Ð¼Ð°Ð½Ñ‚Ð¸Ñ" msgid "Basic Estates" msgstr "Базовый казначей" msgid "Basic Necromancy" msgstr "Ð‘Ð°Ð·Ð¾Ð²Ð°Ñ Ð½ÐµÐºÑ€Ð¾Ð¼Ð°Ð½Ñ‚Ð¸Ñ" msgid "Expert Eagle Eye" msgstr "ЭкÑпертный орлиный взор" msgid "Expert Necromancy" msgstr "ЭкÑÐ¿ÐµÑ€Ñ‚Ð½Ð°Ñ Ð½ÐµÐºÑ€Ð¾Ð¼Ð°Ð½Ñ‚Ð¸Ñ" msgid "Advanced Estates" msgstr "Продвинутый казначей" msgid "Expert Estates" msgstr "ЭкÑперт казначей" msgid "" "%{skill} reduces the movement penalty for rough terrain by %{count} percent." msgstr "" "%{skill} уменьшает штраф при движении по переÑечённой меÑтноÑти на %{count} " "процентов." msgid "%{skill} eliminates the movement penalty for rough terrain." msgstr "%{skill} уÑтранÑет штраф при передвижении по переÑечённой меÑтноÑти." msgid "" "%{skill} increases the damage done by the hero's range attacking creatures " "by %{count} percent." msgstr "" "%{skill} увеличивает урон от лучников в армии Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count} процентов." msgid "%{skill} increases the hero's movement points by %{count} percent." msgstr "%{skill} увеличивает очки Ð´Ð²Ð¸Ð¶ÐµÐ½Ð¸Ñ Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count} процентов." msgid "%{skill} increases the hero's viewable area by one square." msgid_plural "%{skill} increases the hero's viewable area by %{count} squares." msgstr[0] "%{skill} увеличивает Ñ€Ð°Ð´Ð¸ÑƒÑ Ð¾Ð±Ð·Ð¾Ñ€Ð° Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count} квадрат." msgstr[1] "%{skill} увеличивает Ñ€Ð°Ð´Ð¸ÑƒÑ Ð¾Ð±Ð·Ð¾Ñ€Ð° Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count} квадрата." msgstr[2] "%{skill} увеличивает Ñ€Ð°Ð´Ð¸ÑƒÑ Ð¾Ð±Ð·Ð¾Ñ€Ð° Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count} квадратов." msgid "" "%{skill} allows the hero to negotiate with monsters who are weaker than " "their army, and reduces the cost of surrender." msgstr "" "%{skill} позволÑет герою веÑти переговоры Ñ ÑущеÑтвами, которые Ñлабее его " "армии, и Ñнижает ÑтоимоÑть капитулÑции." msgid "" "Approximately %{count} percent of the creatures may offer to join the hero." msgstr "" "Примерно %{count} процентов ÑущеÑтв могут предложить приÑоединитьÑÑ Ðº армии " "героÑ." msgid "All of the creatures may offer to join the hero." msgstr "Ð’Ñе ÑущеÑтва могут предложить приÑоединитьÑÑ Ðº армии героÑ." msgid "" "The cost of surrender is reduced to %{percent} percent of the total cost of " "troops in the army." msgstr "" "СтоимоÑть капитулÑции Ñнижена до %{percent} процентов от общей ÑтоимоÑти " "войÑк в армии героÑ." msgid "" "%{skill} increases the hero's movement points over water by %{count} percent." msgstr "%{skill} увеличивает передвижение Ð³ÐµÑ€Ð¾Ñ Ð¿Ð¾ воде на %{count} процентов." msgid "%{skill} increases the hero's troops morale by %{count}." msgstr "%{skill} увеличивает мораль войÑк Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count}." msgid "%{skill} allows the hero to learn third level spells." msgstr "%{skill} позволÑет герою изучать Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ 3-го круга." msgid "%{skill} allows the hero to learn fourth level spells." msgstr "%{skill} позволÑет герою изучать Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ 4-го круга." msgid "%{skill} allows the hero to learn fifth level spells." msgstr "%{skill} позволÑет герою изучать вÑе возможные заклинаниÑ." msgid "%{skill} regenerates one additional spell point per day to the hero." msgid_plural "" "%{skill} regenerates %{count} additional spell points per day to the hero." msgstr[0] "%{skill} воÑÑтанавливает %{count} пункт магии Ð³ÐµÑ€Ð¾Ñ Ð² день." msgstr[1] "%{skill} воÑÑтанавливает %{count} пункта магии Ð³ÐµÑ€Ð¾Ñ Ð² день." msgstr[2] "%{skill} воÑÑтанавливает %{count} пунктов магии Ð³ÐµÑ€Ð¾Ñ Ð² день." msgid "%{skill} increases the hero's luck by %{count}." msgstr "%{skill} увеличивает удачу войÑк Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count}." msgid "" "%{skill} gives the hero's catapult shots a greater chance to hit and do " "damage to castle walls." msgstr "" "%{skill} даёт выÑтрелам катапульты больший ÑˆÐ°Ð½Ñ Ð¿Ð¾Ñ€Ð°Ð·Ð¸Ñ‚ÑŒ и причинить вред " "Ñтенам замка." msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot has a " "greater chance to hit and do damage to castle walls." msgstr "" "%{skill} даёт катапульте Ñделать дополнительный выÑтрел, а также каждому " "выÑтрелу придаёт больший ÑˆÐ°Ð½Ñ Ð¿Ð¾Ñ€Ð°Ð·Ð¸Ñ‚ÑŒ и причинить вред Ñтенам замка." msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot " "automatically destroys any wall, except a fortified wall in a Knight castle." msgstr "" "%{skill} даёт катапульте Ñделать дополнительный выÑтрел, и каждый выÑтрел " "гарантированно разрушит любую Ñтену, за иÑключением укреплений в рыцарÑком " "замке." msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 1st or " "2nd level spell that was cast by an enemy during combat." msgstr "" "%{skill} даёт герою %{count} процентов возможноÑти выучить заклинание 1-го " "или 2-го круга, применённое в бою противником." msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 3rd " "level spell (or below) that was cast by an enemy during combat." msgstr "" "%{skill} даёт герою %{count} процентов возможноÑти выучить заклинание 1-го, " "2-го или 3-го круга, применённое в бою противником." msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 4th " "level spell (or below) that was cast by an enemy during combat." msgstr "" "%{skill} даёт герою %{count} процентов возможноÑти выучить заклинание 4-го " "круга и ниже, применённое в бою противником." msgid "" "%{skill} allows %{count} percent of the creatures killed in combat to be " "brought back from the dead as Skeletons." msgstr "" "%{skill} может вернуть к жизни %{count} процентов ÑущеÑтв, убитых в " "Ñражении, в виде Скелетов." msgid "" "The hero produces %{count} gold pieces per day as tax revenue from estates." msgstr "" "Герой ежедневно Ñобирает налоги Ñо Ñвоих владений в размере %{count} золота." msgid "Blue" msgstr "Синий" msgid "Green" msgstr "Зелёный" msgid "Red" msgstr "КраÑный" msgid "Yellow" msgstr "Жёлтый" msgid "Orange" msgstr "Оранжевый" msgid "Purple" msgstr "Фиолетовый" msgid "barrier|Aqua" msgstr "Бирюзовый" msgid "barrier|Blue" msgstr "Синий" msgid "barrier|Brown" msgstr "Коричневый" msgid "barrier|Gold" msgstr "Жёлтый" msgid "barrier|Green" msgstr "Зелёный" msgid "barrier|Orange" msgstr "Оранжевый" msgid "barrier|Purple" msgstr "Фиолетовый" msgid "barrier|Red" msgstr "КраÑный" msgid "tent|Aqua" msgstr "Бирюзовый" msgid "tent|Blue" msgstr "Синий" msgid "tent|Brown" msgstr "Коричневый" msgid "tent|Gold" msgstr "Жёлтый" msgid "tent|Green" msgstr "Зелёный" msgid "tent|Orange" msgstr "Оранжевый" msgid "tent|Purple" msgstr "Фиолетовый" msgid "tent|Red" msgstr "КраÑный" msgid "Experience" msgstr "Опыт" msgid "" "Experience allows your heroes to go up levels, increasing their primary and " "secondary skills." msgstr "" "Опыт позволÑет Вашим героÑм повышать Ñвои уровни, Ñ€Ð°Ð·Ð²Ð¸Ð²Ð°Ñ Ñвои первичные и " "вторичные навыки." msgid "Hero/Stats" msgstr "Герой/ХарактериÑтика" msgid "Skills" msgstr "Ðавыки" msgid "Town/Castle" msgstr "Город/Замок" msgid "Garrison" msgstr "Гарнизон" msgid "Gold Per Day:" msgstr "Доход золота в день:" msgid "View Heroes." msgstr "ПроÑмотреть вÑех Ваших героев." msgid "Towns/Castles" msgstr "Города" msgid "View Towns and Castles." msgstr "ПроÑмотреть вÑе Ваши города и замки." msgid "luck|Cursed" msgstr "ПроклÑÑ‚!" msgid "luck|Awful" msgstr "УжаÑнаÑ" msgid "luck|Bad" msgstr "ПлохаÑ" msgid "luck|Normal" msgstr "ÐормальнаÑ" msgid "luck|Good" msgstr "ХорошаÑ" msgid "luck|Great" msgstr "ОтличнаÑ" msgid "luck|Irish" msgstr "Фортуна!" msgid "" "Bad luck sometimes falls on the hero's units in combat, causing their " "attacks to only do half damage." msgstr "" "ÐŸÐ»Ð¾Ñ…Ð°Ñ ÑƒÐ´Ð°Ñ‡Ð° иногда поÑещает войÑка Ð³ÐµÑ€Ð¾Ñ Ð² бою, неудача вынуждает отрÑд " "атаковать только вполÑилы." msgid "" "Neutral luck means the hero's units will never get lucky or unlucky attacks " "on the enemy." msgstr "" "ÐÐ¾Ñ€Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ ÑƒÐ´Ð°Ñ‡Ð° подразумевает, что войÑка Ð³ÐµÑ€Ð¾Ñ Ð½Ðµ будут проводить как " "удачные, так и неудачные атаки." msgid "" "Good luck sometimes lets the hero's units get lucky attacks (double " "strength) in combat." msgstr "" "Ð¥Ð¾Ñ€Ð¾ÑˆÐ°Ñ ÑƒÐ´Ð°Ñ‡Ð° иногда позволÑет отрÑдам Ð³ÐµÑ€Ð¾Ñ Ð¿Ñ€Ð¾Ð²Ð¾Ð´Ð¸Ñ‚ÑŒ удачные атаки в " "Ñражении, наноÑÑщие двойной урон." msgid "morale|Treason" msgstr "Ð’ панике!" msgid "morale|Awful" msgstr "УжаÑнаÑ" msgid "morale|Poor" msgstr "ПлохаÑ" msgid "morale|Normal" msgstr "ÐормальнаÑ" msgid "morale|Good" msgstr "ХорошаÑ" msgid "morale|Great" msgstr "ОтличнаÑ" msgid "morale|Blood!" msgstr "ЭйфориÑ!" msgid "Bad morale may cause the hero's units to freeze in combat." msgstr "ÐŸÐ»Ð¾Ñ…Ð°Ñ Ð¼Ð¾Ñ€Ð°Ð»ÑŒ может заÑтавить отрÑд Ð³ÐµÑ€Ð¾Ñ Ð·Ð°Ñтыть на меÑте от ужаÑа." msgid "" "Neutral morale means the hero's units will never be blessed with extra " "attacks or freeze in combat." msgstr "" "ÐžÐ±Ñ‹Ñ‡Ð½Ð°Ñ Ð¼Ð¾Ñ€Ð°Ð»ÑŒ подразумевает, что отрÑды Ð³ÐµÑ€Ð¾Ñ Ð½Ðµ будут поощрÑтьÑÑ " "дополнительной атакой или пропуÑкать ход." msgid "Good morale may give the hero's units extra attacks in combat." msgstr "" "Ð¥Ð¾Ñ€Ð¾ÑˆÐ°Ñ Ð¼Ð¾Ñ€Ð°Ð»ÑŒ может дать войÑкам Ð³ÐµÑ€Ð¾Ñ Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾Ñть дополнительной атаки в " "Ñражении." msgid "Knight" msgstr "Рыцарь" msgid "Barbarian" msgstr "Варвар" msgid "Sorceress" msgstr "КолдуньÑ" msgid "Warlock" msgstr "Чернокнижник" msgid "Wizard" msgstr "Чародей" msgid "Necromancer" msgstr "Ðекромант" msgid "Multi" msgstr "По Ñценарию" msgid "doubleLined|Knight" msgstr "Рыцарь" msgid "doubleLined|Barbarian" msgstr "Варвар" msgid "doubleLined|Sorceress" msgstr "КолдуньÑ" msgid "doubleLined|Warlock" msgstr "" "Черно-\n" "книжник" msgid "doubleLined|Wizard" msgstr "Чародей" msgid "" "doubleLined|Necro-\n" "mancer" msgstr "" "Ðекро-\n" "мант" msgid "doubleLinedRace|Multi" msgstr "" "По\n" "Ñценарию" msgid "doubleLinedRace|Random" msgstr "Случайно" msgid "speed|Standing" msgstr "Ðа меÑте" msgid "speed|Crawling" msgstr "ЧерепашьÑ" msgid "speed|Very Slow" msgstr "ПолзущаÑ" msgid "speed|Slow" msgstr "ÐизкаÑ" msgid "speed|Average" msgstr "СреднÑÑ" msgid "speed|Fast" msgstr "БыÑтраÑ" msgid "speed|Very Fast" msgstr "ШуÑтраÑ" msgid "speed|Ultra Fast" msgstr "СкороÑтнаÑ" msgid "speed|Blazing" msgstr "МолниеноÑнаÑ" msgid "speed|Instant" msgstr "МгновеннаÑ" msgid "week|Squirrel" msgstr "Белки" msgid "week|Rabbit" msgstr "Кролика" msgid "week|Gopher" msgstr "СуÑлика" msgid "week|Badger" msgstr "БарÑука" msgid "week|Rat" msgstr "КрыÑÑ‹" msgid "week|Eagle" msgstr "Орла" msgid "week|Weasel" msgstr "Хорька" msgid "week|Raven" msgstr "Ворона" msgid "week|Mongoose" msgstr "МангуÑта" msgid "week|Dog" msgstr "Собаки" msgid "week|Aardvark" msgstr "Муравьеда" msgid "week|Lizard" msgstr "Ящера" msgid "week|Tortoise" msgstr "Черепахи" msgid "week|Hedgehog" msgstr "Ðжика" msgid "week|Condor" msgstr "Кондора" msgid "week|Ant" msgstr "МуравьÑ" msgid "week|Grasshopper" msgstr "Кузнечика" msgid "week|Dragonfly" msgstr "Стрекозы" msgid "week|Spider" msgstr "Паука" msgid "week|Butterfly" msgstr "Бабочки" msgid "week|Bumblebee" msgstr "ГуÑеницы" msgid "week|Locust" msgstr "Цикад" msgid "week|Earthworm" msgstr "ЗемлÑного червÑ" msgid "week|Hornet" msgstr "ШершнÑ" msgid "week|Beetle" msgstr "Жука" msgid "week|PLAGUE" msgstr "ЧУМЫ" msgid "week|Unnamed" msgstr "Без имени" msgid "Desert" msgstr "ПуÑтынÑ" msgid "Snow" msgstr "Снег" msgid "Wasteland" msgstr "ПуÑтошь" msgid "Beach" msgstr "Побережье" msgid "Lava" msgstr "Лава" msgid "Dirt" msgstr "ГрÑзь" msgid "Grass" msgstr "Трава" msgid "Ocean" msgstr "Океан" msgid "maps|Small" msgstr "МаленькаÑ" msgid "maps|Medium" msgstr "СреднÑÑ" msgid "maps|Large" msgstr "БольшаÑ" msgid "maps|Extra Large" msgstr "ОгромнаÑ" msgid "maps|Custom Size" msgstr "ОÑобый размер" msgid "Ore Mine" msgstr "Ð ÑƒÐ´Ð½Ð°Ñ ÑˆÐ°Ñ…Ñ‚Ð°" msgid "Sulfur Mine" msgstr "Ð¡ÐµÑ€Ð½Ð°Ñ ÑˆÐ°Ñ…Ñ‚Ð°" msgid "Crystal Mine" msgstr "КриÑÑ‚Ð°Ð»ÑŒÐ½Ð°Ñ ÑˆÐ°Ñ…Ñ‚Ð°" msgid "Gems Mine" msgstr "Ð¡Ð°Ð¼Ð¾Ñ†Ð²ÐµÑ‚Ð½Ð°Ñ ÑˆÐ°Ñ…Ñ‚Ð°" msgid "Gold Mine" msgstr "Ð—Ð¾Ð»Ð¾Ñ‚Ð°Ñ ÑˆÐ°Ñ…Ñ‚Ð°" msgid "Mine" msgstr "Шахта" msgid "Burma shave." msgstr "ЗдеÑÑŒ была Ñ€Ð¾Ð·Ð¾Ð²Ð°Ñ Ð¿Ð¾Ð½Ð¸." msgid "Next sign 50 miles." msgstr "Следующий знак в 50 милÑÑ…." msgid "See Rock City." msgstr "Тот, кто читает Ñту надпиÑÑŒ, у того Ñамые краÑивые глаза." msgid "This space for rent." msgstr "Герои Меча и Магии - интереÑÐ½Ð°Ñ Ð¸Ð³Ñ€Ð°, но отдыхать глазам тоже надо." msgid "No object" msgstr "Ðет объекта" msgid "Alchemist Lab" msgstr "Ð›Ð°Ð±Ð¾Ñ€Ð°Ñ‚Ð¾Ñ€Ð¸Ñ Ð°Ð»Ñ…Ð¸Ð¼Ð¸ÐºÐ°" msgid "Sign" msgstr "Указатель" msgid "Buoy" msgstr "Буй" msgid "Skeleton" msgstr "Скелет" msgid "Daemon Cave" msgstr "Пещера демона" msgid "Treasure Chest" msgstr "Ларец Ñ Ñокровищами" msgid "Faerie Ring" msgstr "Кольцо фей" msgid "Campfire" msgstr "КоÑтёр" msgid "Fountain" msgstr "Фонтан" msgid "Gazebo" msgstr "БеÑедка" msgid "Genie Lamp" msgstr "ДревнÑÑ Ð»Ð°Ð¼Ð¿Ð°" msgid "Archer's House" msgstr "Дом Ñтрелков" msgid "Goblin Hut" msgstr "Хибара гоблина" msgid "Dwarf Cottage" msgstr "Избушка гномов" msgid "Peasant Hut" msgstr "Мазанка креÑтьÑн" msgid "Stables" msgstr "Конюшни" msgid "Alchemist's Tower" msgstr "Ð‘Ð°ÑˆÐ½Ñ Ð°Ð»Ñ…Ð¸Ð¼Ð¸ÐºÐ°" msgid "Event" msgstr "Событие" msgid "Dragon City" msgstr "Драконий город" msgid "Lighthouse" msgid_plural "Lighthouses" msgstr[0] "МаÑк" msgstr[1] "МаÑка" msgstr[2] "МаÑков" msgid "Water Wheel" msgid_plural "Water Wheels" msgstr[0] "ВодÑное колеÑо" msgstr[1] "ВодÑных колеÑа" msgstr[2] "ВодÑных колёÑ" msgid "Monster" msgstr "ОтрÑд" msgid "Obelisk" msgstr "ОбелиÑк" msgid "Oasis" msgstr "ОазиÑ" msgid "Resource" msgstr "РеÑурÑ" msgid "Sawmill" msgstr "ЛеÑопилка" msgid "Oracle" msgstr "Оракул" msgid "Shrine of the First Circle" msgstr "СвÑтилище 1-го круга" msgid "Shipwreck" msgstr "Кораблекрушение" msgid "Sea Chest" msgstr "МорÑкой Ñундук" msgid "Desert Tent" msgstr "Шатёр" msgid "Stone Liths" msgstr "Монолиты" msgid "Wagon Camp" msgstr "Лагерь у фургонов" msgid "Hut of the Magi" msgstr "Лачуга волхва" msgid "Whirlpool" msgstr "Водоворот" msgid "Windmill" msgid_plural "Windmills" msgstr[0] "ВетрÑÐ½Ð°Ñ Ð¼ÐµÐ»ÑŒÐ½Ð¸Ñ†Ð°" msgstr[1] "ВетрÑные мельницы" msgstr[2] "ВетрÑных мельниц" msgid "Artifact" msgstr "Ðртефакт" msgid "Mermaid" msgstr "РуÑалка" msgid "Boat" msgstr "Корабль" msgid "Random Ultimate Artifact" msgstr "Случайный великий артефакт" msgid "Random Artifact" msgstr "Случайный артефакт" msgid "Random Resource" msgstr "Случайный реÑурÑ" msgid "Random Monster" msgstr "Случайное ÑущеÑтво" msgid "Random Town" msgstr "Случайный город" msgid "Random Castle" msgstr "Случайный замок" msgid "Eye of the Magi" msgstr "Око волхва" msgid "Random Monster - weak" msgstr "Случайное Ñлабое ÑущеÑтво" msgid "Random Monster - medium" msgstr "Случайное Ñреднее ÑущеÑтво" msgid "Random Monster - strong" msgstr "Случайное Ñильное ÑущеÑтво" msgid "Random Monster - very strong" msgstr "Случайное великое ÑущеÑтво" msgid "Hero" msgstr "Герой" msgid "Nothing Special" msgstr "Ðичего оÑобенного" msgid "Mossy Rock" msgstr "МшиÑÑ‚Ð°Ñ Ñкала" msgid "Watch Tower" msgstr "ÐžÐ±Ð·Ð¾Ñ€Ð½Ð°Ñ Ð±Ð°ÑˆÐ½Ñ" msgid "Tree House" msgstr "Домик на дереве" msgid "Tree City" msgstr "Древо-город" msgid "Ruins" msgstr "Руины" msgid "Fort" msgstr "Форт" msgid "Abandoned Mine" msgstr "Ð—Ð°Ð±Ñ€Ð¾ÑˆÐµÐ½Ð½Ð°Ñ ÑˆÐ°Ñ…Ñ‚Ð°" msgid "Sirens" msgstr "Сирены" msgid "Standing Stones" msgstr "СтоÑчие камни" msgid "Idol" msgstr "Идол" msgid "Tree of Knowledge" msgstr "Древо знаний" msgid "Witch Doctor's Hut" msgstr "Хижина колдуна" msgid "Temple" msgstr "Храм" msgid "Hill Fort" msgstr "Форт на холме" msgid "Halfling Hole" msgstr "Ðора полуроÑликов" msgid "Mercenary Camp" msgstr "Лагерь наёмников" msgid "Shrine of the Second Circle" msgstr "СвÑтилище 2-го круга" msgid "Shrine of the Third Circle" msgstr "СвÑтилище 3-го круга" msgid "City of the Dead" msgstr "Город мёртвых" msgid "Sphinx" msgstr "СфинкÑ" msgid "Wagon" msgstr "Тележка" msgid "Tar Pit" msgstr "СмолÑÐ½Ð°Ñ Ñма" msgid "Artesian Spring" msgstr "ÐртезианÑкий иÑточник" msgid "Troll Bridge" msgstr "МоÑÑ‚ троллей" msgid "Watering Hole" msgstr "Промоина" msgid "Witch's Hut" msgstr "Хижина ведьмы" msgid "Xanadu" msgstr "КÑанаду" msgid "Lean-To" msgstr "ÐавеÑ" msgid "Magellan's Maps" msgstr "Магеллан" msgid "Flotsam" msgstr "Обломки" msgid "Derelict Ship" msgstr "Заброшенный корабль" msgid "Shipwreck Survivor" msgstr "Тонущий матроÑ" msgid "Bottle" msgstr "Бутылка" msgid "Magic Well" msgstr "Волшебный колодец" msgid "Magic Garden" msgid_plural "Magic Gardens" msgstr[0] "Волшебный Ñад" msgstr[1] "Волшебных Ñада" msgstr[2] "Волшебных Ñадов" msgid "Observation Tower" msgstr "ÐžÐ±Ð·Ð¾Ñ€Ð½Ð°Ñ Ð±Ð°ÑˆÐ½Ñ" msgid "Freeman's Foundry" msgstr "ЛитейнаÑ" msgid "Reefs" msgstr "Рифы" msgid "Volcano" msgstr "Вулкан" msgid "Flowers" msgstr "Цветы" msgid "Rock" msgstr "Камень" msgid "Water Lake" msgstr "Озеро" msgid "Mandrake" msgstr "Мандрагора" msgid "Dead Tree" msgstr "Сушина" msgid "Stump" msgstr "Пень" msgid "Crater" msgstr "Кратер" msgid "Cactus" msgstr "КактуÑ" msgid "Mound" msgstr "Курган" msgid "Dune" msgstr "Дюна" msgid "Lava Pool" msgstr "Лавовый баÑÑейн" msgid "Shrub" msgstr "КуÑтарник" msgid "Barrow Mounds" msgstr "Могильные курганы" msgid "Random Artifact - Treasure" msgstr "Случайный артефакт-Ñокровище" msgid "Random Artifact - Minor" msgstr "Случайный малый артефакт" msgid "Random Artifact - Major" msgstr "Случайный Ñильный артефакт" msgid "Barrier" msgstr "Барьер" msgid "Traveller's Tent" msgstr "Шатёр путника" msgid "Jail" msgstr "Тюрьма" msgid "Fire Summoning Altar" msgstr "Ðлтарь ОгнÑ" msgid "Air Summoning Altar" msgstr "Ðлтарь Воздуха" msgid "Earth Summoning Altar" msgstr "Ðлтарь Земли" msgid "Water Summoning Altar" msgstr "Ðлтарь Воды" msgid "Unknown Monster" msgstr "ÐеизвеÑтное ÑущеÑтво" msgid "Unknown Monsters" msgstr "ÐеизвеÑтные ÑущеÑтва" msgid "Peasant" msgstr "КреÑтьÑнин" msgid "Peasants" msgstr "КреÑтьÑне" msgid "Archer" msgstr "Стрелок" msgid "Archers" msgstr "Стрелки" msgid "Ranger" msgstr "Рейнджер" msgid "Rangers" msgstr "Рейнджеры" msgid "Pikeman" msgstr "Копейщик" msgid "Pikemen" msgstr "Копейщики" msgid "Veteran Pikeman" msgstr "Опытный копейщик" msgid "Veteran Pikemen" msgstr "Опытные копейщики" msgid "Swordsman" msgstr "Мечник" msgid "Swordsmen" msgstr "Мечники" msgid "Master Swordsman" msgstr "Опытный мечник" msgid "Master Swordsmen" msgstr "Опытные мечники" msgid "Cavalries" msgstr "Ð’Ñадники" msgid "Cavalry" msgstr "Ð’Ñадник" msgid "Champion" msgstr "Чемпион" msgid "Champions" msgstr "Чемпионы" msgid "Paladin" msgstr "Паладин" msgid "Paladins" msgstr "Паладины" msgid "Crusader" msgstr "КреÑтоноÑец" msgid "Crusaders" msgstr "КреÑтоноÑцы" msgid "Goblin" msgstr "Гоблин" msgid "Goblins" msgstr "Гоблины" msgid "Orc" msgstr "Орк" msgid "Orcs" msgstr "Орки" msgid "Orc Chief" msgstr "Вождь орков" msgid "Orc Chiefs" msgstr "Вожди орков" msgid "Wolf" msgstr "Волк" msgid "Wolves" msgstr "Волки" msgid "Ogre" msgstr "Огр" msgid "Ogres" msgstr "Огры" msgid "Ogre Lord" msgstr "Лорд огров" msgid "Ogre Lords" msgstr "Лорды огров" msgid "Troll" msgstr "Тролль" msgid "Trolls" msgstr "Тролли" msgid "War Troll" msgstr "Боевой тролль" msgid "War Trolls" msgstr "Боевые тролли" msgid "Cyclopes" msgstr "Циклопы" msgid "Cyclops" msgstr "Циклоп" msgid "Sprite" msgstr "ФеÑ" msgid "Sprites" msgstr "Феи" msgid "Dwarf" msgstr "Гном" msgid "Dwarves" msgstr "Гномы" msgid "Battle Dwarf" msgstr "Боевой гном" msgid "Battle Dwarves" msgstr "Боевые гномы" msgid "Elf" msgstr "Эльф" msgid "Elves" msgstr "Эльфы" msgid "Grand Elf" msgstr "Ð’Ñ‹Ñший Ñльф" msgid "Grand Elves" msgstr "Ð’Ñ‹Ñшие Ñльфы" msgid "Druid" msgstr "Друид" msgid "Druids" msgstr "Друиды" msgid "Greater Druid" msgstr "Старший друид" msgid "Greater Druids" msgstr "Старшие друиды" msgid "Unicorn" msgstr "Единорог" msgid "Unicorns" msgstr "Единороги" msgid "Phoenix" msgstr "ФеникÑ" msgid "Phoenixes" msgstr "ФеникÑÑ‹" msgid "Centaur" msgstr "Кентавр" msgid "Centaurs" msgstr "Кентавры" msgid "Gargoyle" msgstr "ГоргульÑ" msgid "Gargoyles" msgstr "Горгульи" msgid "Griffin" msgstr "Грифон" msgid "Griffins" msgstr "Грифоны" msgid "Minotaur" msgstr "Минотавр" msgid "Minotaurs" msgstr "Минотавры" msgid "Minotaur King" msgstr "Царь минотавров" msgid "Minotaur Kings" msgstr "Цари минотавров" msgid "Hydra" msgstr "Гидра" msgid "Hydras" msgstr "Гидры" msgid "Green Dragon" msgstr "Зелёный дракон" msgid "Green Dragons" msgstr "Зелёные драконы" msgid "Red Dragon" msgstr "КраÑный дракон" msgid "Red Dragons" msgstr "КраÑные драконы" msgid "Black Dragon" msgstr "Чёрный дракон" msgid "Black Dragons" msgstr "Чёрные драконы" msgid "Halfling" msgstr "ПолуроÑлик" msgid "Halflings" msgstr "ПолуроÑлики" msgid "Boar" msgstr "Вепрь" msgid "Boars" msgstr "Вепри" msgid "Iron Golem" msgstr "Железный голем" msgid "Iron Golems" msgstr "Железные големы" msgid "Steel Golem" msgstr "Стальной голем" msgid "Steel Golems" msgstr "Стальные големы" msgid "Roc" msgstr "Птица Рух" msgid "Rocs" msgstr "Птицы Рух" msgid "Mage" msgstr "Маг" msgid "Magi" msgstr "Маги" msgid "Archmage" msgstr "Ðрхимаг" msgid "Archmagi" msgstr "Ðрхимаги" msgid "Giant" msgstr "Гигант" msgid "Giants" msgstr "Гиганты" msgid "Titan" msgstr "Титан" msgid "Titans" msgstr "Титаны" msgid "Skeletons" msgstr "Скелеты" msgid "Zombie" msgstr "Зомби" msgid "Zombies" msgstr "Зомби" msgid "Mutant Zombie" msgstr "Зомби-мутант" msgid "Mutant Zombies" msgstr "Зомби-мутанты" msgid "Mummies" msgstr "Мумии" msgid "Mummy" msgstr "МумиÑ" msgid "Royal Mummies" msgstr "КоролевÑкие мумии" msgid "Royal Mummy" msgstr "КоролевÑÐºÐ°Ñ Ð¼ÑƒÐ¼Ð¸Ñ" msgid "Vampire" msgstr "Вампир" msgid "Vampires" msgstr "Вампиры" msgid "Vampire Lord" msgstr "Лорд вампиров" msgid "Vampire Lords" msgstr "Лорды вампиров" msgid "Lich" msgstr "Лич" msgid "Liches" msgstr "Личи" msgid "Power Lich" msgstr "Могучий лич" msgid "Power Liches" msgstr "Могучие личи" msgid "Bone Dragon" msgstr "КоÑÑ‚Ñной дракон" msgid "Bone Dragons" msgstr "КоÑÑ‚Ñные драконы" msgid "Rogue" msgstr "Разбойник" msgid "Rogues" msgstr "Разбойники" msgid "Nomad" msgstr "Кочевник" msgid "Nomads" msgstr "Кочевники" msgid "Ghost" msgstr "Призрак" msgid "Ghosts" msgstr "Призраки" msgid "Genie" msgstr "Джинн" msgid "Genies" msgstr "Джинны" msgid "Medusa" msgstr "Медуза" msgid "Medusas" msgstr "Медузы" msgid "Earth Elemental" msgstr "Земной Ñлементаль" msgid "Earth Elementals" msgstr "Земные Ñлементали" msgid "Air Elemental" msgstr "Воздушный Ñлементаль" msgid "Air Elementals" msgstr "Воздушные Ñлементали" msgid "Fire Elemental" msgstr "Огненный Ñлементаль" msgid "Fire Elementals" msgstr "Огненные Ñлементали" msgid "Water Elemental" msgstr "Водный Ñлементаль" msgid "Water Elementals" msgstr "Водные Ñлементали" msgid "Random Monsters" msgstr "Случайные ÑущеÑтва" msgid "Random Monster 1" msgstr "Случайное ÑущеÑтво 1" msgid "Random Monsters 1" msgstr "Случайные ÑущеÑтва 1" msgid "Random Monster 2" msgstr "Случайное ÑущеÑтво 2" msgid "Random Monsters 2" msgstr "Случайные ÑущеÑтва 2" msgid "Random Monster 3" msgstr "Случайное ÑущеÑтво 3" msgid "Random Monsters 3" msgstr "Случайные ÑущеÑтва 3" msgid "Random Monster 4" msgstr "Случайное ÑущеÑтво 4" msgid "Random Monsters 4" msgstr "Случайные ÑущеÑтва 4" msgid "Double shot" msgstr "Двойной выÑтрел" msgid "2-hex monster" msgstr "Занимает 2 клетки" msgid "Double strike" msgstr "Двойной удар" msgid "Double damage to Undead" msgstr "Двойной урон по нежити" msgid "% magic resistance" msgstr "% ÑÐ¾Ð¿Ñ€Ð¾Ñ‚Ð¸Ð²Ð»ÐµÐ½Ð¸Ñ Ðº магии" msgid "Immune to Mind spells" msgstr "ÐевоÑприимчивы к магии разума" msgid "Immune to Elemental spells" msgstr "ÐевоÑприимчивы к магии Ñтихий" msgid "Immune to Fire spells" msgstr "ÐевоÑприимчивы к магии огнÑ" msgid "Immune to Cold spells" msgstr "ÐевоÑприимчивы к магии холода" msgid "Immune to " msgstr "ÐевоÑприимчивы к " msgid "% immunity to %{spell} spell" msgstr "% уÑтойчивоÑти к заклинанию %{spell}" msgid "% damage from Elemental spells" msgstr "% урона от заклинаний" msgid "% chance to Dispel beneficial spells" msgstr "% ÑˆÐ°Ð½Ñ Ñ€Ð°Ð·Ð²ÐµÑть полезные заклинаниÑ" msgid "% chance to Paralyze" msgstr "% ÑˆÐ°Ð½Ñ Ð¿Ð°Ñ€Ð°Ð»Ð¸Ð·Ð¾Ð²Ð°Ñ‚ÑŒ" msgid "% chance to Petrify" msgstr "% ÑˆÐ°Ð½Ñ Ð½Ð°Ð»Ð¾Ð¶Ð¸Ñ‚ÑŒ окаменение" msgid "% chance to Blind" msgstr "% ÑˆÐ°Ð½Ñ Ð¾Ñлепить" msgid "% chance to Curse" msgstr "% ÑˆÐ°Ð½Ñ Ð½Ð°Ð»Ð¾Ð¶Ð¸Ñ‚ÑŒ проклÑтие" msgid "% chance to cast %{spell} spell" msgstr "% ÑˆÐ°Ð½Ñ Ð½Ð°Ð»Ð¾Ð¶Ð¸Ñ‚ÑŒ заклинание %{spell}" msgid "HP regeneration" msgstr "Ð ÐµÐ³ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ð·Ð´Ð¾Ñ€Ð¾Ð²ÑŒÑ" msgid "Two hexes attack" msgstr "Ðтака на две клетки" msgid "Flyer" msgstr "Летает" msgid "Always retaliates" msgstr "Ð’Ñегда отвечает на удар" msgid "Attacks all adjacent enemies" msgstr "Ðтакует вÑех ÑоÑедних врагов" msgid "No melee penalty" msgstr "Ðет штрафа в ближнем бою" msgid "Dragon" msgstr "Дракон" msgid "Undead" msgstr "Ðежить" msgid "No enemy retaliation" msgstr "Враг не отвечает на атаку" msgid "HP drain" msgstr "Вампиризм" msgid "Cloud attack" msgstr "ÐžÐ±Ð»Ð°Ñ‡Ð½Ð°Ñ Ð°Ñ‚Ð°ÐºÐ°" msgid "Decreases enemy's morale by " msgstr "Снижает моральный дух противника на " msgid "% chance to halve enemy" msgstr "% ÑˆÐ°Ð½Ñ ÑƒÐ±Ð¸Ñ‚ÑŒ половину отрÑда" msgid "Soul Eater" msgstr "Пожиратель душ" msgid "Elemental" msgstr "Элементаль" msgid "No Morale" msgstr "Ðет морали" msgid "200% damage from Fire spells" msgstr "200% урона от заклинаний огнÑ" msgid "200% damage from Cold spells" msgstr "200% урона от заклинаний холода" msgid "% damage from %{spell} spell" msgstr "% урона от Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ %{spell}" msgid "% immunity to " msgstr "% невоÑприимчивоÑти к " msgid "Lightning" msgstr "МолниÑ" msgid "% damage from " msgstr "% урона от " msgid "The three Anduran artifacts magically combine into one." msgstr "Три магичеÑких артефакта Ðндурана, объединÑÑŽÑ‚ÑÑ Ð² один." msgid "View Spells" msgstr "ПроÑмотреть заклинаниÑ" msgid "View %{name} Info" msgstr "Ð˜Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ %{name}" msgid "Move %{name}" msgstr "ПеремеÑтить %{name}" msgid "Cannot move the Spellbook" msgstr "Ðевозможно перемеÑтить книгу заклинаний" msgid "This item can't be traded." msgstr "Эту вещь Ð½ÐµÐ»ÑŒÐ·Ñ Ð¿Ñ€Ð¾Ð´Ð°Ñ‚ÑŒ." msgid "Invalid Artifact" msgstr "Ðекорректный артефакт" msgid "The %{name} increases the hero's knowledge by %{count}." msgstr "%{name} увеличивает Ð·Ð½Ð°Ð½Ð¸Ñ Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count}." msgid "Ultimate Book of Knowledge" msgstr "Книга Ð’ÑезнаниÑ" msgid "The %{name} increases the hero's attack skill by %{count}." msgstr "%{name} увеличивает навык атаки Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count}." msgid "Ultimate Sword of Dominion" msgstr "Меч Ð’ÑевлаÑтиÑ" msgid "The %{name} increases the hero's defense skill by %{count}." msgstr "%{name} увеличивает навык защиты Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count}." msgid "Ultimate Cloak of Protection" msgstr "Ð—Ð°Ñ‰Ð¸Ñ‚Ð½Ð°Ñ Ðакидка" msgid "The %{name} increases the hero's spell power by %{count}." msgstr "%{name} увеличивает Ñилу магии Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count}." msgid "Ultimate Wand of Magic" msgstr "Жезл Магии" msgid "" "The %{name} increases the hero's attack and defense skills by %{count} each." msgstr "%{name} увеличивает навыки атаки и защиты Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count}." msgid "Ultimate Shield" msgstr "Ð’Ñемогущий Щит" msgid "" "The %{name} increases the hero's spell power and knowledge by %{count} each." msgstr "%{name} увеличивает Ñилу магии и Ð·Ð½Ð°Ð½Ð¸Ñ Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count}." msgid "Ultimate Staff" msgstr "Ð’Ñемогущий ПоÑох" msgid "" "The %{name} increases each of the hero's basic skills by %{count} points." msgstr "%{name} увеличивает вÑе оÑновные параметры Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count}." msgid "Ultimate Crown" msgstr "Корона Ð’ÑевлаÑтиÑ" msgid "Golden Goose" msgstr "Золотой ГуÑÑŒ" msgid "The %{name} brings in an income of %{count} gold per day." msgstr "%{name} приноÑит каждый день по %{count} золотых." msgid "Arcane Necklace of Magic" msgstr "Ожерелье Тайной Магии" msgid "" "After rescuing a Sorceress from a cursed tomb, she rewards your heroism with " "an exquisite jeweled necklace." msgstr "" "Волшебница, ÑпаÑÑ‘Ð½Ð½Ð°Ñ Ð¸Ð· проклÑтой гробницы, вознаграждает Ваш героизм " "изыÑканным ожерельем, украшенным драгоценными камнÑми." msgid "Caster's Bracelet of Magic" msgstr "МагичеÑкий БраÑлет" msgid "" "While searching through the rubble of a caved-in mine, you free a group of " "trapped Dwarves. Grateful, the leader gives you a golden bracelet." msgstr "" "Ð˜Ð·ÑƒÑ‡Ð°Ñ Ð·Ð°Ð²Ð°Ð»Ñ‹ в заброшенной шахте, Ð’Ñ‹ ÑпаÑаете артель гномов, попавших в " "ловушку. Ð’ знак благодарноÑти их Ñтаршина дарит Вам золотой браÑлет." msgid "Mage's Ring of Power" msgstr "Кольцо Мага" msgid "" "A cry of pain leads you to a Centaur, caught in a trap. Upon setting the " "creature free, he hands you a small pouch. Emptying the contents, you find a " "dazzling jeweled ring." msgstr "" "Ð’Ñ‹ Ñпешите на звук отчаÑнного Ð²Ð¾Ð¿Ð»Ñ Ð±Ð¾Ð»Ð¸ и видите кентавра, попавшего в " "западню. Ð’Ñ‹ помогаете ему оÑвободитьÑÑ, и он вручает Вам мешочек. ЗаглÑнув " "внутрь, Ð’Ñ‹ видите оÑлепительное бриллиантовое кольцо." msgid "Witch's Broach of Magic" msgstr "Брошь Ведьмы" msgid "" "Alongside the remains of a burnt witch lies a beautiful broach, intricately " "designed. Approaching the corpse with caution, you add the broach to your " "inventory." msgstr "" "РÑдом Ñ Ð¾Ñтанками Ñожжённой колдуньи лежит изÑÑ‰Ð½Ð°Ñ Ð±Ñ€Ð¾ÑˆÑŒ прекраÑной работы. " "ОÑторожно приблизившиÑÑŒ к обугленному трупу, Ð’Ñ‹ забираете брошь Ñебе." msgid "Medal of Valor" msgstr "Медаль Отваги" msgid "The %{name} increases the morale of the hero's army by %{count}." msgstr "%{name} увеличивает мораль армии Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count}." msgid "" "Freeing a virtuous maiden from the clutches of an evil overlord, you are " "granted a Medal of Valor by the King's herald." msgstr "" "Ð’ награду за ÑпаÑение прекраÑной девы от поÑÑгательÑтв ненавиÑтного барона " "королевÑкий герольд вручает Вам Медаль Отваги." msgid "Medal of Courage" msgstr "Медаль МужеÑтва" msgid "" "After saving a young boy from a vicious pack of Wolves, you return him to " "his father's manor. The grateful nobleman awards you with a Medal of Courage." msgstr "" "Ð’Ñ‹ ÑпаÑаете маленького мальчика от Ñтаи кровожадных волков и провожаете в " "имение его родителей. СчаÑтливый отец награждает Ð’Ð°Ñ ÐœÐµÐ´Ð°Ð»ÑŒÑŽ МужеÑтва." msgid "Medal of Honor" msgstr "Медаль ДоблеÑти" msgid "" "After freeing a princess of a neighboring kingdom from the evil clutches of " "despicable slavers, she awards you with a Medal of Honor." msgstr "" "Ð’Ñ‹ оÑвобождаете принцеÑÑу ÑоÑеднего королевÑтва из мерзких лап презренных " "работорговцев, и в награду за подвиг получаете Медаль ДоблеÑти." msgid "Medal of Distinction" msgstr "Медаль Почёта" msgid "" "Ridding the countryside of the hideous Minotaur who made a sport of eating " "noblemen's Knights, you are honored with the Medal of Distinction." msgstr "" "Ð’Ñ‹ избавлÑете округу от ужаÑного минотавра, добычей которому Ñлужили " "благородные рыцари, и ÑтановитеÑÑŒ кавалером Медали Почёта." msgid "Fizbin of Misfortune" msgstr "Символ Ðеудачи" msgid "" "The %{name} greatly decreases the morale of the hero's army by %{count}." msgstr "%{name} значительно Ñнижает мораль армии Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count}." msgid "" "You stumble upon a medal lying alongside the empty road. Adding the medal to " "your inventory, you become aware that you have acquired the undesirable " "Fizbin of Misfortune, greatly decreasing your army's morale." msgstr "" "Ðа обочине пуÑтынной дороги Ð’Ñ‹ замечаете медаль. Подобрав её, Ð’Ñ‹ оÑознаёте, " "что Ñтали неÑчаÑтным обладателем Символа Ðеудачи, который значительно " "Ñнижает мораль Вашей армии." msgid "Thunder Mace of Dominion" msgstr "Ð“Ñ€Ð¾Ð¼Ð¾Ð²Ð°Ñ ÐŸÐ°Ð»Ð¸Ñ†Ð°" msgid "" "During a sudden storm, a bolt of lightning strikes a tree, splitting it. " "Inside the tree you find a mysterious mace." msgstr "" "Во Ð²Ñ€ÐµÐ¼Ñ Ð¶ÑƒÑ‚ÐºÐ¾Ð¹ грозы Ð¼Ð¾Ð»Ð½Ð¸Ñ Ð±ÑŒÑ‘Ñ‚ в дерево, разноÑÑ ÐµÐ³Ð¾ на мелкие щепки. " "Среди обломков Ð’Ñ‹ обнаруживаете таинÑтвенную палицу." msgid "Armored Gauntlets of Protection" msgstr "Ð—Ð°Ñ‰Ð¸Ñ‚Ð½Ð°Ñ ÐŸÐµÑ€Ñ‡Ð°Ñ‚ÐºÐ°" msgid "The %{name} increase the hero's defense skill by %{count}." msgstr "%{name} увеличивает навык защиты Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count}." msgid "" "You encounter the infamous Black Knight! After a grueling duel ending in a " "draw, the Knight, out of respect, offers you a pair of armored gauntlets." msgstr "" "Ð’Ñ‹ повÑтречали печально извеÑтного Чёрного РыцарÑ! Ваш поединок " "заканчиваетÑÑ Ð²Ð½Ð¸Ñ‡ÑŒÑŽ, и рыцарь в знак ÑƒÐ²Ð°Ð¶ÐµÐ½Ð¸Ñ Ð´Ð°Ñ€Ð¸Ñ‚ Вам пару латных " "перчаток." msgid "Defender Helm of Protection" msgstr "Шлем Защитника" msgid "" "A glint of golden light catches your eye. Upon further investigation, you " "find a golden helm hidden under a bush." msgstr "" "Краем глаза Ð’Ñ‹ замечаете золотиÑтый блеÑк Ñреди пышной зелени. ПриглÑдевшиÑÑŒ " "внимательнее, Ð’Ñ‹ находите под куÑтами великолепный золотой шлем." msgid "Giant Flail of Dominion" msgstr "ГигантÑкий Цеп" msgid "" "A clumsy Giant has killed himself with his own flail. Knowing your superior " "skill with this weapon, you confidently remove the spectacular flail from " "the fallen Giant." msgstr "" "Ðеуклюжий гигант Ð½Ð°Ð½Ñ‘Ñ Ñебе Ñмертельную рану ÑобÑтвенным боевым цепом. Ð’Ñ‹ " "прекраÑно владеете Ñтим оружием и Ñ ÑƒÐ²ÐµÑ€ÐµÐ½Ð½Ð¾Ñтью вынимаете цеп из мёртвых " "рук гиганта." msgid "Ballista of Quickness" msgstr "БаллиÑта" msgid "The %{name} gives the hero's catapult one extra shot per combat round." msgstr "" "%{name} позволÑет произвеÑти один дополнительный выÑтрел из катапульты за " "ход." msgid "" "Walking through the ruins of an ancient walled city, you find the instrument " "of the city's destruction, an elaborately crafted ballista." msgstr "" "ПробираÑÑÑŒ через развалины древней крепоÑти, Ð’Ñ‹ находите орудие, которое " "превратило её в руины, удивительную баллиÑту замыÑловатой конÑтрукции." msgid "Stealth Shield of Protection" msgstr "Ðезримый Щит" msgid "" "A stone statue of a warrior holds a silver shield. As you remove the shield, " "the statue crumbles into dust." msgstr "" "Ð’ руках у каменной Ñтатуи воина - великолепный ÑеребрÑный щит. Как только Ð’Ñ‹ " "забираете щит Ñебе, ÑÑ‚Ð°Ñ‚ÑƒÑ Ñ€Ð°ÑÑыпаетÑÑ Ð² прах." msgid "Dragon Sword of Dominion" msgstr "Драконий Меч" msgid "" "As you are walking along a narrow path, a nearby bush suddenly bursts into " "flames. Before your eyes the flames become the image of a beautiful woman. " "She holds out a magnificent sword to you." msgstr "" "Ð’Ñ‹ пробираетеÑÑŒ узкой тропой, как вдруг ближайший куÑÑ‚ загораетÑÑ Ñрким " "пламенем. Ð’ огненном Ñмерче поÑвлÑетÑÑ Ð¿Ñ€ÐµÐºÑ€Ð°ÑÐ½Ð°Ñ Ð´Ð°Ð¼Ð°, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð¿Ñ€Ð¾Ñ‚Ñгивает " "Вам волшебный меч." msgid "Power Axe of Dominion" msgstr "Топор ВлаÑти" msgid "" "You see a silver axe embedded deeply in the ground. After several " "unsuccessful attempts by your army to remove the axe, you tightly grip the " "handle of the axe and effortlessly pull it free." msgstr "" "Ð’Ñ‹ видите ÑеребрÑный топор, вогнанный в землю по Ñамую рукоÑть. Ваши воины " "пытаютÑÑ Ð²Ñ‹Ð´ÐµÑ€Ð½ÑƒÑ‚ÑŒ его, но уÑÐ¸Ð»Ð¸Ñ Ð¸Ñ… тщетны. Вам же хватило одного уÑÐ¸Ð»Ð¸Ñ Ð¸ " "топор у Ð’Ð°Ñ Ð² руках!" msgid "Divine Breastplate of Protection" msgstr "БожеÑтвенный ДоÑпех" msgid "" "A gang of Rogues is sifting through the possessions of dead warriors. " "Scaring off the scavengers, you note the Rogues had overlooked a beautiful " "breastplate." msgstr "" "Шайка разбойников обыÑкивает тела мёртвых воинов. Ð’Ñ‹ разгонÑете мародёров и " "вдруг замечаете, что в Ñпешке они не заметили великолепный доÑпех." msgid "Minor Scroll of Knowledge" msgstr "Малый Свиток ЗнаниÑ" msgid "" "Before you appears a levitating glass case with a scroll, perched upon a bed " "of crimson velvet. At your touch, the lid opens and the scroll floats into " "your awaiting hands." msgstr "" "Перед Вами возникает парÑщий в воздухе ÑтеклÑнный ларец Ñо Ñвитком внутри, " "лежащем на подушке из пурпурного бархата. От прикоÑÐ½Ð¾Ð²ÐµÐ½Ð¸Ñ ÐºÑ€Ñ‹ÑˆÐºÐ° ларца " "открываетÑÑ, и Ñвиток оказываетÑÑ Ñƒ Ð’Ð°Ñ Ð² руках." msgid "Major Scroll of Knowledge" msgstr "Большой Свиток ЗнаниÑ" msgid "" "Visiting a local wiseman, you explain the intent of your journey. He reaches " "into a sack and withdraws a yellowed scroll and hands it to you." msgstr "" "Ð’Ñ‹ навещаете меÑтного мудреца и раÑÑказываете о цели Вашего путешеÑтвиÑ. Он " "доÑтаёт из мешка пожелтевший Ñвиток и передаёт его Вам." msgid "Superior Scroll of Knowledge" msgstr "МогущеÑтвенный Свиток ЗнаниÑ" msgid "" "You come across the remains of an ancient Druid. Bones, yellowed with age, " "peer from the ragged folds of her robe. Searching the robe, you discover a " "scroll hidden in the folds." msgstr "" "Ð’Ñ‹ Ñтоите перед оÑтанками давно умершей жрицы друидов. Пожелтевшие от " "времени коÑти проглÑдывают через прорехи иÑтлевшего одеÑниÑ. Пошевелив груду " "ветоши, Ð’Ñ‹ находите древний Ñвиток." msgid "Foremost Scroll of Knowledge" msgstr "Свиток Ð’Ñ‹Ñшего ЗнаниÑ" msgid "" "Mangled bones, yellowed with age, peer from the ragged folds of a dead " "Druid's robe. Searching the robe, you discover a scroll hidden within." msgstr "" "Груда пожелтевших коÑтей и обрывки иÑтлевшей материи - вот вÑÑ‘, что оÑталоÑÑŒ " "от жрицы друидов. Среди Ñтих оÑтанков Ð’Ñ‹ замечаете таинÑтвенный Ñвиток." msgid "Endless Sack of Gold" msgstr "Бездонный Мешок Золота" msgid "The %{name} provides the hero with %{count} gold per day." msgstr "%{name} ежедневно приноÑит в казну дополнительные %{count} золотых." msgid "" "A little leprechaun dances gleefully around a magic sack. Seeing you " "approach, he stops in mid-stride. The little man screams and stamps his foot " "ferociously, vanishing into thin air. Remembering the old leprechaun saying " "'Finders Keepers', you grab the sack and leave." msgstr "" "Маленький лепрекон пританцовывает у волшебного мешка. Завидев ВаÑ, он " "замирает на меÑте, затем издаёт возмущённый возглаÑ, топает ножкой и " "раÑтворÑетÑÑ Ð² воздухе. Ð’Ñ‹ забираете мешок Ñебе." msgid "Endless Bag of Gold" msgstr "Ð‘ÐµÐ·Ð´Ð¾Ð½Ð½Ð°Ñ Ð¡ÑƒÐ¼Ð° Золота" msgid "" "A noblewoman, separated from her traveling companions, asks for your help. " "After escorting her home, she rewards you with a bag filled with gold." msgstr "" "Ð‘Ð»Ð°Ð³Ð¾Ñ€Ð¾Ð´Ð½Ð°Ñ Ð¿ÑƒÑ‚ÐµÑˆÐµÑтвенница, отÑÑ‚Ð°Ð²ÑˆÐ°Ñ Ð¾Ñ‚ Ñпутников, проÑит Ð’Ð°Ñ Ð¾ помощи. " "Проводив её до дома, Ð’Ñ‹ получаете в награду Ñуму, полную золота." msgid "Endless Purse of Gold" msgstr "Бездонный Кошель" msgid "" "In your travels, you find a leather purse filled with gold that once " "belonged to a great warrior king who had the ability to transform any " "inanimate object into gold." msgstr "" "Однажды Вам в руки попадает наполненный золотом кожаный кошель, " "принадлежавший великому королю, который умел превращать любой предмет в " "золото." msgid "Nomad Boots of Mobility" msgstr "Башмаки Кочевника" msgid "The %{name} increase the hero's movement on land." msgstr "%{name} увеличивают передвижение Ð³ÐµÑ€Ð¾Ñ Ð¿Ð¾ земле." msgid "" "A Nomad trader seeks protection from a tribe of Goblins. For your " "assistance, he gives you a finely crafted pair of boots made from the " "softest leather. Looking closely, you see fascinating ancient carvings " "engraved on the leather." msgstr "" "БродÑчий торговец проÑит Ð’Ð°Ñ Ð·Ð°Ñ‰Ð¸Ñ‚Ð¸Ñ‚ÑŒ его от банды гоблинов. Ð’ награду он " "дарит Вам пару изÑщных башмаков, иÑпещрённых загадочными древними пиÑьменами." msgid "Traveler's Boots of Mobility" msgstr "Сапоги Путника" msgid "" "Discovering a pair of beautifully beaded boots made from the finest and " "softest leather, you thank the anonymous donor and add the boots to your " "inventory." msgstr "" "Обнаружив пару замечательных башмаков, украшенных биÑером, Ð’Ñ‹ благодарите " "загадочного Ð±Ð»Ð°Ð³Ð¾Ð´ÐµÑ‚ÐµÐ»Ñ Ð¸ оÑтавлÑете их Ñебе." msgid "Lucky Rabbit's Foot" msgstr "ÐšÑ€Ð¾Ð»Ð¸Ñ‡ÑŒÑ Ð›Ð°Ð¿ÐºÐ°" msgid "The %{name} increases the luck of the hero's army by %{count}." msgstr "%{name} увеличивает удачу армии Ð³ÐµÑ€Ð¾Ñ Ð² бою на %{count}." msgid "" "A traveling merchant offers you a rabbit's foot, made of gleaming silver " "fur, for safe passage. The merchant explains the charm will increase your " "luck in combat." msgstr "" "Ð’ уплату за охрану в пути ÑтранÑтвующий торговец предлагает Вам лапку " "кролика. По его Ñловам, она принеÑёт Вам удачу в бою." msgid "Golden Horseshoe" msgstr "Ð—Ð¾Ð»Ð¾Ñ‚Ð°Ñ ÐŸÐ¾Ð´ÐºÐ¾Ð²Ð°" msgid "" "An ensnared Unicorn whinnies in fright. Murmuring soothing words, you set " "her free. Snorting and stamping her front hoof once, she gallops off. " "Looking down you see a golden horseshoe." msgstr "" "Попавший в ловушку единорог иÑпуганно ржёт. Ð’Ñ‹ уÑпокаиваете его и " "оÑвобождаете от пут. Ð’Ñхрапнув и ударив копытом, он уноÑитÑÑ Ð¿Ñ€Ð¾Ñ‡ÑŒ. Там, где " "он только что ÑтоÑл, оÑталаÑÑŒ лежать Ð·Ð¾Ð»Ð¾Ñ‚Ð°Ñ Ð¿Ð¾Ð´ÐºÐ¾Ð²Ð°." msgid "Gambler's Lucky Coin" msgstr "СчаÑÑ‚Ð»Ð¸Ð²Ð°Ñ ÐœÐ¾Ð½ÐµÑ‚Ð°" msgid "" "You have captured a mischievous imp who has been terrorizing the region. In " "exchange for his release, he rewards you with a magical coin." msgstr "" "Ð’Ñ‹ поймали озорного беÑёнка, который не давал Ð¿Ð¾ÐºÐ¾Ñ Ð²Ñей округе. Ð’ обмен на " "Ñвободу он предлагает Вам волшебную монету." msgid "Four-Leaf Clover" msgstr "ЧетырёхлиÑтник" msgid "" "In the middle of a patch of dead and dry vegetation, to your surprise you " "find a healthy green four-leaf clover." msgstr "" "ПоÑреди мёртвой лощины, заполненной иÑÑохшей раÑтительноÑтью, Ð’Ñ‹, к Ñвоему " "удивлению, замечаете веÑёлый зелёный побег четырёхлиÑтного клевера." msgid "True Compass of Mobility" msgstr "КомпаÑ" msgid "The %{name} increases the hero's movement on land and sea." msgstr "%{name} увеличивает передвижение Ð³ÐµÑ€Ð¾Ñ Ð¿Ð¾ земле и воде." msgid "" "An old man claiming to be an inventor asks you to try his latest invention. " "He then hands you a compass." msgstr "" "Странноватый Ñтарикашка утверждает, что он - великий изобретатель, и проÑит " "Ð’Ð°Ñ Ð¸Ñпытать его новое творение. ÐадувшиÑÑŒ от важноÑти, он вручает Вам " "компаÑ." msgid "Sailor's Astrolabe of Mobility" msgstr "ÐÑтролÑбиÑ" msgid "The %{name} increases the hero's movement on sea." msgstr "%{name} увеличивает передвижение Ð³ÐµÑ€Ð¾Ñ Ð¿Ð¾ воде." msgid "" "An old sea captain is being tortured by Ogres. You save him, and in return " "he rewards you with a wondrous instrument to measure the distance of a star." msgstr "" "Старый мореход Ñтал добычей людоедов. Ð’Ñ‹ ÑпаÑаете его, и в знак " "благодарноÑти он дарит Вам чудеÑный инÑтрумент, позволÑющий определÑть " "меÑтоположение по звёздам." msgid "Evil Eye" msgstr "Дурной Глаз" msgid "The %{name} reduces the casting cost of curse spells by half." msgstr "%{name} Ñнижает ÑтоимоÑть заклинаний проклÑÑ‚Ð¸Ñ Ð½Ð° половину." msgid "" "While venturing into a decrepit hut you find the Skeleton of a long dead " "witch. Investigation of the remains reveals a glass eye rolling around " "inside an empty skull." msgstr "" "Ð’ заброшенной хижине Ð’Ñ‹ находите Ñкелет давно почившей колдуньи. " "ПриглÑдевшиÑÑŒ, Ð’Ñ‹ замечаете, что в глазнице пожелтевшего черепа зловеще " "вращаетÑÑ ÑтеклÑнный глаз." msgid "Enchanted Hourglass" msgstr "Зачарованные ЧаÑÑ‹" msgid "" "The %{name} extends the duration of all the hero's spells by %{count} turns." msgstr "" "%{name} увеличивает длительноÑть вÑех заклинаний Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count} ход." msgid "" "A surprise turn in the landscape finds you in the midst of a grisly scene: " "Vultures picking at the aftermath of a terrible battle. Your cursory search " "of the remains turns up an enchanted hourglass." msgstr "" "За невыÑоким холмом перед Вами открываетÑÑ Ð·Ð»Ð¾Ð²ÐµÑ‰Ð°Ñ ÐºÐ°Ñ€Ñ‚Ð¸Ð½Ð° - Ñтаи " "ÑтервÑтников пируют на поле недавней битвы. Среди тел поверженных воинов Ð’Ñ‹ " "находите волшебные пеÑочные чаÑÑ‹." msgid "Gold Watch" msgstr "Золотые ЧаÑÑ‹" msgid "The %{name} doubles the effectiveness of the hero's hypnotize spells." msgstr "%{name} удваивают Ñффект заклинаний гипноза." msgid "" "In reward for helping his cart out of a ditch, a traveling potion salesman " "gives you a \"magic\" gold watch. Unbeknownst to him, the watch really is " "magical." msgstr "" "Ð’Ñ‹ помогаете бродÑчему торговцу ÑнадобьÑми вытащить повозку из придорожной " "канавы. Ð’ знак благодарноÑти он вручает Вам золотые чаÑÑ‹. Он и не " "подозревал, что чаÑÑ‹ волшебные!" msgid "Skullcap" msgstr "Ермолка" msgid "The %{name} halves the casting cost of all mind influencing spells." msgstr "%{name} Ñнижает вдвое ÑтоимоÑть вÑех заклинаний, влиÑющих на разум." msgid "" "A brief stop at an improbable rural inn yields an exchange of money, tales, " "and accidentally, luggage. You find a magical skullcap in your new backpack." msgstr "" "Ð’Ñ‹ делаете короткую оÑтановку в маленькой придорожной харчевне. Под звон " "монет проиÑходит обмен новоÑÑ‚Ñми, а то и редкими вещицами. Вот таким-то " "образом в Вашем багаже и оказываетÑÑ Ð²Ð¾Ð»ÑˆÐµÐ±Ð½Ð°Ñ ÑˆÐ°Ð¿Ð¾Ñ‡ÐºÐ°." msgid "Ice Cloak" msgstr "ЛедÑÐ½Ð°Ñ Ðакидка" msgid "" "The %{name} halves all damage the hero's troops receive from cold spells." msgstr "" "%{name} Ñнижает вдвое веÑÑŒ урон нанеÑённый отрÑдам Ð³ÐµÑ€Ð¾Ñ Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñми холода." msgid "" "Responding to the panicked cries of a damsel in distress, you discover a " "young woman fleeing from a hungry bear. You slay the beast in the nick of " "time, and the grateful Sorceress weaves a magic cloak from the bear's hide." msgstr "" "Ð’Ñ‹ Ñпешите на отчаÑнные крики и видите очаровательную девушку, за которой " "гонитÑÑ Ñ€Ð°Ð·ÑŠÑрённый медведь. Через мгновение зверь повержен, и Ð±Ð»Ð°Ð³Ð¾Ð´Ð°Ñ€Ð½Ð°Ñ " "волшебница шьёт Вам из его шкуры волшебный плащ." msgid "Fire Cloak" msgstr "ÐžÐ³Ð½ÐµÐ½Ð½Ð°Ñ Ðакидка" msgid "" "The %{name} halves all damage the hero's troops receive from fire spells." msgstr "" "%{name} Ñнижает вдвое веÑÑŒ урон нанеÑённый отрÑдам Ð³ÐµÑ€Ð¾Ñ Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñми огнÑ." msgid "" "You've come upon a fight between a Necromancer and a Paladin. The " "Necromancer blasts the Paladin with a fire bolt, bringing him to his knees. " "Acting quickly, you slay the evil one before the final blow. The grateful " "Paladin gives you the fire cloak that saved him." msgstr "" "За поворотом дороги Ð’Ñ‹ видите ÑражающихÑÑ Ð½ÐµÐºÑ€Ð¾Ð¼Ð°Ð½Ñ‚Ð° и паладина. Ðекромант " "пытаетÑÑ Ñжечь паладина, но тот, отшатнувшиÑÑŒ, лишь падает на колени. Ð’Ñ‹ " "ÑпаÑаете жизнь паладину, ÑƒÐ±Ð¸Ð²Ð°Ñ ÐµÐ³Ð¾ врага, замахнувшегоÑÑ Ð´Ð»Ñ Ð¿Ð¾Ñледнего " "удара. Ð’ благодарноÑть паладин дарит Вам Ñвою огненно-краÑную накидку, " "ÑпаÑшую его от огнÑ." msgid "Lightning Helm" msgstr "Громовой Шлем" msgid "" "The %{name} halves all damage the hero's troops receive from lightning " "spells." msgstr "" "%{name} Ñнижает вдвое веÑÑŒ урон нанеÑённый отрÑдам Ð³ÐµÑ€Ð¾Ñ Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñми молний." msgid "" "A traveling tinker in need of supplies offers you a helm with a thunderbolt " "design on its top in exchange for food and water. Curious, you accept, and " "later find out that the helm is magical." msgstr "" "БродÑчий медник, у которого кончилаÑÑŒ провизиÑ, предлагает Вам шлем Ñ " "гребнем в виде молнии в обмен на еду и питье. Ð’Ñ‹ ÑоглашаетеÑÑŒ на обмен, а " "вÑкоре обнаруживаете, что шлем обладает ещё и магичеÑкими ÑвойÑтвами." msgid "Evercold Icicle" msgstr "Ðетающий Лёд" msgid "" "The %{name} causes the hero's cold spells to do %{count} percent more damage " "to enemy troops." msgstr "" "%{name} увеличивает на %{count} процентов урон от заклинаний холода, " "произнеÑённых героем." msgid "" "An icicle withstanding the full heat of the noonday sun attracts your " "attention. Intrigued, you break it off, and find that it does not melt in " "your hand." msgstr "" "Ваше внимание привлекает ледÑÐ½Ð°Ñ ÑоÑулька, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð½Ðµ тает, неÑÐ¼Ð¾Ñ‚Ñ€Ñ Ð½Ð° " "полуденный зной. Ð’Ñ‹ отламываете её от карниза и Ñ ÑƒÐ´Ð¸Ð²Ð»ÐµÐ½Ð¸ÐµÐ¼ обнаруживаете, " "что даже тепло Ваших рук ей нипочём." msgid "Everhot Lava Rock" msgstr "ГорÑчий Камень" msgid "" "The %{name} causes the hero's fire spells to do %{count} percent more damage " "to enemy troops." msgstr "" "%{name} увеличивает на %{count} процентов урон от заклинаний огнÑ, " "произнеÑённых героем." msgid "" "Your wanderings bring you into contact with a tribe of ape-like beings using " "a magical lava rock that never cools to light their fires. You take pity on " "them and teach them to make fire with sticks. Believing you to be a god, the " "apes give you their rock." msgstr "" "Ð’ дальней Ñтране Ð’Ñ‹ вÑтречаете Ð¿Ð»ÐµÐ¼Ñ Ð¿Ñ€Ð¸Ð¼Ð°Ñ‚Ð¾Ð². Они разжигают коÑтры при " "помощи волшебного куÑка лавы. Ð’Ñ‹ научили их добывать огонь обычным ÑпоÑобом. " "ОбезьÑны Ñчитают Ð’Ð°Ñ Ð±Ð¾Ð³Ð¾Ð¼ и дарÑÑ‚ Ñвой заветный куÑок лавы." msgid "Lightning Rod" msgstr "Жезл Молний" msgid "" "The %{name} causes the hero's lightning spells to do %{count} percent more " "damage to enemy troops." msgstr "" "%{name} увеличивает на %{count} процентов урон от заклинаний молнии, " "произнеÑённых героем." msgid "" "While waiting out a storm, a lighting bolt strikes a nearby cottage's " "lightning rod, which melts and falls to the ground. The tip of the rod, " "however, survives intact and makes your hair stand on end when you touch it. " "Hmm..." msgstr "" "Во Ð²Ñ€ÐµÐ¼Ñ ÑƒÐ¶Ð°Ñной грозы на Ваших глазах в громоотвод дома бьёт молниÑ. " "Оплавленный громоотвод падает на землю, но его наконечник, похожий на жезл, " "оÑтаётÑÑ Ñ†ÐµÐ»Ñ‹Ð¼ и невредимым. Ваши волоÑÑ‹ вÑтают дыбом, когда к Ð’Ñ‹ к нему " "прикаÑаетеÑÑŒ. Хмм..." msgid "Snake-Ring" msgstr "Кольцо Змеи" msgid "The %{name} halves the casting cost of all of the hero's bless spells." msgstr "%{name} Ñнижает ÑтоимоÑть вÑех заклинаний благоÑловениÑ." msgid "" "You've found an oddly shaped ring on the finger of a long dead traveler. The " "ring looks like a snake biting its own tail." msgstr "" "Ðа пальце мёртвого Ñтранника Ð’Ñ‹ видите необычное кольцо. Оно имеет форму " "змеи, вцепившейÑÑ Ð·ÑƒÐ±Ð°Ð¼Ð¸ в ÑобÑтвенный хвоÑÑ‚." msgid "Ankh" msgstr "Символ Жизни" msgid "" "The %{name} doubles the effectiveness of all of the hero's resurrect and " "animate spells." msgstr "" "%{name} удваивает ÑффективноÑть вÑех заклинаний оживлениÑ, произнеÑённых " "героем." msgid "" "A fierce windstorm reveals the entrance to a buried tomb. Your investigation " "reveals that the tomb has already been looted, but the thieves overlooked an " "ankh on a silver chain in the dark." msgstr "" "ПеÑÑ‡Ð°Ð½Ð°Ñ Ð±ÑƒÑ€Ñ Ð¾Ð±Ð½Ð°Ð¶Ð¸Ð»Ð° вход в подземную гробницу. Ð’Ñ‹ ÑпуÑкаетеÑÑŒ внутрь и " "обнаруживаете, что здеÑÑŒ уже побывали грабители, однако в темноте они не " "заметили Символ Жизни, виÑÑщий на ÑеребрÑной цепи." msgid "Book of Elements" msgstr "Книга Стихий" msgid "" "The %{name} doubles the effectiveness of all of the hero's summoning spells." msgstr "" "%{name} удваивает ÑффективноÑть вÑех заклинаний призыва, произнеÑённых " "героем." msgid "" "You come across a conjurer who begs to accompany you and your army awhile " "for safety. You agree, and he offers as payment a copy of the book of the " "elements." msgstr "" "Ð’Ñ‹ вÑтречаете заклинателÑ, который проÑит разрешить ему воÑпользоватьÑÑ " "Вашим покровительÑтвом на опаÑном учаÑтке пути. Ð’Ñ‹ ÑоглашаетеÑÑŒ, и в награду " "он дарит Вам Книгу Стихий." msgid "Elemental Ring" msgstr "Кольцо Стихий" msgid "The %{name} halves the casting cost of all summoning spells." msgstr "" "%{name} Ñнижает наполовину ÑтоимоÑть вÑех заклинаний призыва, произнеÑённых " "героем." msgid "" "While pausing to rest, you notice a bobcat climbing a short tree to get at a " "crow's nest. On impulse, you climb the tree yourself and scare off the cat. " "When you look in the nest, you find a collection of shiny stones and a ring." msgstr "" "РаÑположившиÑÑŒ на отдых под невыÑоким деревом, Ð’Ñ‹ замечаете дикого кота, " "который подбираетÑÑ Ðº гнезду Ñороки. Ð’Ñ‹ прогонÑете кота и Ñ Ð»ÑŽÐ±Ð¾Ð¿Ñ‹Ñ‚Ñтвом " "заглÑдываете туда. Ð’ гнезде Ð’Ñ‹ обнаруживаете множеÑтво блеÑÑ‚Ñщих камушков, а " "также кольцо тонкой работы." msgid "Holy Pendant" msgstr "СвÑтой Кулон" msgid "The %{name} makes all of the hero's troops immune to curse spells." msgstr "" "%{name} делает вÑе отрÑды Ð³ÐµÑ€Ð¾Ñ Ð½ÐµÐ²Ð¾Ñприимчивыми к заклинаниÑм проклÑтиÑ." msgid "" "In your wanderings you come across a hermit living in a small, tidy hut. " "Impressed with your mission, he takes time out from his meditations to bless " "and give you a charm against curses." msgstr "" "СтранÑÑ‚Ð²ÑƒÑ Ð¿Ð¾ дальним землÑм, Ð’Ñ‹ вÑтречаете отшельника, живущего в маленькой " "аккуратной хижине. Узнав о цели Ваших Ñкитаний, он прерывает Ñвои " "размышлениÑ, благоÑловлÑет Ð’Ð°Ñ Ð¸ дарит амулет, защищающий от злых чар." msgid "Pendant of Free Will" msgstr "ПодвеÑка Свободы" msgid "The %{name} makes all of the hero's troops immune to hypnotize spells." msgstr "" "%{name} воли делает вÑе отрÑды Ð³ÐµÑ€Ð¾Ñ Ð½ÐµÐ²Ð¾Ñприимчивыми к заклинаниÑм гипноза." msgid "" "Responding to cries for help, you find river Sprites making a sport of " "dunking an old man. Feeling vengeful, you rescue the man and drag a Sprite " "onto dry land for awhile. The Sprite, uncomfortable in the air, gives you a " "magic pendant to let him go." msgstr "" "Ð’Ñ‹ Ñлышите крики о помощи и, поÑпешив на берег реки, видите речных фей, " "потешающихÑÑ Ð½Ð°Ð´ Ñтариком, Ð¾ÐºÑƒÐ½Ð°Ñ ÐµÐ³Ð¾ в воду. Ð’Ñ‹ выручаете Ñтарика из беды и " "вытаÑкиваете одну фею на берег. Ð’ обмен на Ñвободу она отдаёт Вам волшебную " "подвеÑку." msgid "Pendant of Life" msgstr "Кулон Жизни" msgid "The %{name} makes all of the hero's troops immune to death spells." msgstr "%{name} делает вÑе отрÑды Ð³ÐµÑ€Ð¾Ñ Ð½ÐµÐ²Ð¾Ñприимчивыми к заклинаниÑм Ñмерти." msgid "" "A brief roadside encounter with a small caravan and a game of knucklebones " "wins a magic pendant. Its former owner says that it protects from " "Necromancers' death spells." msgstr "" "Ð’ дороге Ð’Ñ‹ вÑтречаете небольшой караван. Сыграв Ñ Ñ…Ð¾Ð·Ñином каравана в " "коÑти, Ð’Ñ‹ выигрываете волшебную подвеÑку. Её прежний владелец утверждает, " "что она может противоÑтоÑть чарам Ñмерти некромантов." msgid "Serenity Pendant" msgstr "ПодвеÑка ПокоÑ" msgid "The %{name} makes all of the hero's troops immune to berserk spells." msgstr "" "%{name} делает вÑе отрÑды Ð³ÐµÑ€Ð¾Ñ Ð½ÐµÐ²Ð¾Ñприимчивыми к заклинанию берÑерка." msgid "" "The sounds of combat draw you to the scene of a fight between an old " "Barbarian and an eight-headed Hydra. Your timely intervention swings the " "battle in favor of the man, and he rewards you with a pendant he used to use " "to calm his mind for battle." msgstr "" "Ð’Ñ‹ Ñпешите на шум ÑÑ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¸ видите Ñтарика-варвара, который Ñ Ñ‚Ñ€ÑƒÐ´Ð¾Ð¼ " "отбиваетÑÑ Ð¾Ñ‚ гидры. Ваше неожиданное поÑвление отвлекает чудовище, и варвар " "оÑобо удачной атакой добивает его. Ð’ награду за помощь он дарит Вам " "волшебный кулон, которым обычно пользовалÑÑ Ð´Ð»Ñ ÑƒÑÐ¿Ð¾ÐºÐ¾ÐµÐ½Ð¸Ñ Ñ€Ð°Ð·ÑƒÐ¼Ð° перед " "битвой." msgid "Seeing-eye Pendant" msgstr "Ð’ÑевидÑщий Глаз" msgid "The %{name} makes all of the hero's troops immune to blindness spells." msgstr "" "%{name} делает вÑе отрÑды Ð³ÐµÑ€Ð¾Ñ Ð½ÐµÐ²Ð¾Ñприимчивыми к заклинанию оÑлеплениÑ." msgid "" "You come upon a very old woman, long blind from cataracts and dying alone. " "You tend to her final needs and promise a proper burial. Grateful, she gives " "you a magic pendant emblazoned with a stylized eye. It lets you see with " "your eyes closed." msgstr "" "Ð’ хижине у дороги Ð’Ñ‹ находите Ñлепую Ñтаруху, умирающую в полном " "одиночеÑтве. Ð’Ñ‹ обещаете уÑтроить ей доÑтойные похороны. Ð’ знак " "благодарноÑти она дарит Вам волшебную подвеÑку." msgid "Kinetic Pendant" msgstr "Кулон СтремительноÑти" msgid "The %{name} makes all of the hero's troops immune to paralyze spells." msgstr "" "%{name} делает вÑе отрÑды Ð³ÐµÑ€Ð¾Ñ Ð½ÐµÐ²Ð¾Ñприимчивыми к заклинанию паралича." msgid "" "You come across a golem wearing a glowing pendant and blocking your way. " "Acting on a hunch, you cut the pendant from its neck. Deprived of its power " "source, the golem breaks down, leaving you with the magical pendant." msgstr "" "Дорогу Вам преграждает голем, на шее которого Ñверкает кулон. Ð’Ñ‹ перерезаете " "шнурок, и он падает на землю. Голем раÑÑыпаетÑÑ Ñƒ Ð’Ð°Ñ Ð½Ð° глазах, а кулон " "доÑтаётÑÑ Ð’Ð°Ð¼." msgid "Pendant of Death" msgstr "Кулон Смерти" msgid "The %{name} makes all of the hero's troops immune to holy spells." msgstr "" "%{name} делает вÑе отрÑды Ð³ÐµÑ€Ð¾Ñ Ð½ÐµÐ²Ð¾Ñприимчивыми к заклинаниÑм Ð¸Ð·Ð³Ð½Ð°Ð½Ð¸Ñ " "нежити." msgid "" "A quick and deadly battle with a Necromancer wins you his magical pendant. " "Later, a Wizard tells you that the pendant protects undead under your " "control from holy word spells." msgstr "" "ПоÑле короткой ожеÑточённой Ñхватки Ñ Ð½ÐµÐºÑ€Ð¾Ð¼Ð°Ð½Ñ‚Ð¾Ð¼ у Ð’Ð°Ñ Ð² руках оÑтаётÑÑ ÐµÐ³Ð¾ " "волшебный кулон. Знакомый чародей объÑÑнÑет Вам, что Ñтот кулон защищает " "нежить, ÑоÑтоÑщую в Вашей армии, от ÑвÑтого Ñлова." msgid "Wand of Negation" msgstr "ПоÑох ОтрицаниÑ" msgid "" "The %{name} makes all of the hero's troops immune to dispel magic spells." msgstr "" "%{name} делает вÑе отрÑды Ð³ÐµÑ€Ð¾Ñ Ð½ÐµÐ²Ð¾Ñприимчивыми к заклинаниÑм ÑнÑÑ‚Ð¸Ñ Ñ‡Ð°Ñ€." msgid "" "You meet an old Wizard friend of yours traveling in the opposite direction. " "He presents you with a gift: A wand that prevents the use of the dispel " "magic spell on your allies." msgstr "" "ÐавÑтречу Вам попадаетÑÑ Ñтарый друг-чародей. Он вручает Вам подарок - " "волшебный жезл, который делает невозможным применение Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ ÑнÑÑ‚Ð¸Ñ Ñ‡Ð°Ñ€ " "против Ваших Ñоратников." msgid "Golden Bow" msgstr "Золотой Лук" msgid "" "The %{name} eliminates the %{count} percent penalty for the hero's troops " "shooting past obstacles (e.g. castle walls)." msgstr "" "%{name} уменьшает на %{count} процентов штраф при Ñтрельбе за преграды." msgid "" "A chance meeting with a famous Archer finds you in a game of knucklebones " "pitting his bow against your horse. You win." msgstr "" "Ð’Ñ‹ Ñлучайно вÑтречаете знаменитого Ñтрелка и предлагаете ему Ñыграть в " "коÑти. Он ÑоглашаетÑÑ Ð¸ Ñтавит Ñвой лук против Вашего конÑ. Ð’Ñ‹ выигрываете." msgid "Telescope" msgstr "ТелеÑкоп" msgid "" "The %{name} increases the amount of terrain the hero reveals when " "adventuring by %{count} extra square." msgstr "%{name} увеличивает Ñ€Ð°Ð´Ð¸ÑƒÑ Ð¾Ð±Ð·Ð¾Ñ€Ð° Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count} клетку." msgid "" "A merchant from far away lands trades you a new invention of his people for " "traveling supplies. It makes distant objects appear closer, and he calls " "it...\n" "\n" "a telescope." msgstr "" "Торговец из далёких земель предлагает Вам новейшее изобретение Ñвоего народа " "в обмен на ÑъеÑтные припаÑÑ‹. Эту штуку, Ð±Ð»Ð°Ð³Ð¾Ð´Ð°Ñ€Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ð¾Ð¹ удалённые предметы " "кажутÑÑ Ð±Ð»Ð¸Ð¶Ðµ, он называет...\n" "\n" "телеÑкопом." msgid "Statesman's Quill" msgstr "Перо Дипломата" msgid "" "The %{name} reduces the cost of surrender to %{count} percent of the total " "cost of troops the hero has in their army." msgstr "" "%{name} Ñнижает ÑтоимоÑть капитулÑции до %{count} процентов от общей " "ÑтоимоÑти войÑк в армии героÑ." msgid "" "You pause to help a diplomat with a broken axle fix his problem. In " "gratitude, he gives you a writing quill with magical properties which he " "says will \"help people see things your way\"." msgstr "" "Ð’Ñ‹ помогаете дипломату починить Ñломанную оÑÑŒ в его Ñкипаже, и в знак " "благодарноÑти он дарит Вам перо. Он говорит, что Ñто перо заÑтавлÑет людей " "Ñмотреть на вещи глазами его обладателÑ." msgid "Wizard's Hat" msgstr "ШлÑпа Мага" msgid "" "The %{name} increases the duration of the hero's spells by %{count} turns." msgstr "" "%{name} увеличивает продолжительноÑть вÑех заклинаний Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count} " "ходов." msgid "" "You see a Wizard fleeing from a Griffin and riding like the wind. The Wizard " "opens a portal and rides through, getting his hat knocked off by the edge of " "the gate. The Griffin follows; the gate closes. You pick the hat up, dust it " "off, and put it on." msgstr "" "Ð’Ñ‹ видите чародеÑ, который удирает от грифона. Вот он раÑпахнул портал и " "ринулÑÑ Ð²Ð½ÑƒÑ‚Ñ€ÑŒ, но при Ñтом зацепилÑÑ ÑˆÐ»Ñпой, и она упала на землю. Ð’Ñ‹ " "поднимаете шлÑпу, отрÑхиваете её от пыли и оÑтавлÑете Ñебе." msgid "Power Ring" msgstr "Кольцо Силы" msgid "The %{name} returns %{count} extra spell points per day to the hero." msgstr "%{name} воÑÑтанавливает %{count} очка магии в день." msgid "" "You find a small tree that closely resembles the great Warlock Carnauth with " "a ring around one of its twigs. Scraps of clothing and rotting leather lead " "you to suspect that it IS Carnauth, transformed. Since you can't help him, " "you take the magic ring." msgstr "" "Ð’Ñ‹ замечаете дерево, похожее на чернокнижника Карнота. Ðа одной из его веток " "Ñверкает кольцо. Ð’Ñ‹ вÑÑ‘ равно ничем не можете ему помочь, и поÑтому " "забираете кольцо Ñебе." msgid "Ammo Cart" msgstr "Оружейный Обоз" msgid "" "The %{name} provides endless ammunition for all of the hero's troops that " "shoot." msgstr "" "%{name} обеÑпечивает беÑконечными боеприпаÑами вÑех Ñтрелков в армии героÑ." msgid "" "An ammunition cart in the middle of an old battlefield catches your eye. " "Inspection shows it to be in good working order, so you take it along." msgstr "" "Ваше внимание привлекает повозка Ñ Ð±Ð¾ÐµÐ¿Ñ€Ð¸Ð¿Ð°Ñами, ÑтоÑÑ‰Ð°Ñ Ð¿Ð¾Ñреди полÑ, где " "когда-то гремела битва. УбедившиÑÑŒ, что она в хорошем ÑоÑтоÑнии, Ð’Ñ‹ " "приÑоединÑете её к Ñвоему обозу." msgid "Tax Lien" msgstr "Ð”Ð¾Ð»Ð³Ð¾Ð²Ð°Ñ Ð Ð°ÑпиÑка" msgid "The %{name} costs the hero %{count} gold pieces per day." msgstr "%{name} Ñтоит герою %{count} золотых ежедневно." msgid "" "Your big spending habits have earned you a massive tax bill that you can't " "hope to pay. The tax man takes pity and agrees to only take 250 gold a day " "from your account for life. Check here if you want one dollar to go to the " "presidential campaign election fund." msgstr "" "Ваша любовь к большим тратам наградила Ð’Ð°Ñ Ð¾Ð³Ñ€Ð¾Ð¼Ð½Ñ‹Ð¼Ð¸ долгами, которые Ð’Ñ‹ " "можете и не мечтать оплатить. РоÑтовщик ÑжалилÑÑ Ð¸ ÑоглаÑилÑÑ Ð¿Ð¾Ð¶Ð¸Ð·Ð½ÐµÐ½Ð½Ð¾ " "Ñнимать только 250 золотых в день Ñ Ð’Ð°ÑˆÐµÐ³Ð¾ Ñчёта. Этот договор впредь будет " "напоминать Вам о надлежащем иÑпользовании Ваших финанÑов." msgid "Hideous Mask" msgstr "УжаÑÐ½Ð°Ñ ÐœÐ°Ñка" msgid "The %{name} prevents all 'wandering' armies from joining the hero." msgstr "%{name} не позволÑет нейтральным войÑкам вÑтупать в армию героÑ." msgid "" "Your looting of the grave of Sinfilas Gardolad, the famous shapeshifting " "Warlock, unearths his fabled mask. Trembling, you put it on and it twists " "your visage into an awful grimace! Oh no! It's actually the hideous mask of " "Gromluck Greene, and you are stuck with it." msgstr "" "Ð’Ñ‹ вÑкрыли могилу Ð¡Ð¸Ð½Ñ„Ð¸Ð»Ð¸Ñ Ð“Ð°Ñ€Ð´Ð¾Ð»Ð°Ð´Ð°, знаменитого чернокнижника, и " "обнаружили его легендарную маÑку. Взволнованно Ð’Ñ‹ надеваете её, но она " "превращает Ваше лицо в ужаÑную гримаÑу. О нет! Ðа Ñамом деле Ñто " "Ð¾Ñ‚Ð²Ñ€Ð°Ñ‚Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ð¼Ð°Ñка Громлака Грина. Теперь от неё Вам не избавитьÑÑ!" msgid "Endless Pouch of Sulfur" msgstr "Бездонный Мешочек Серы" msgid "The %{name} provides %{count} unit of sulfur per day." msgstr "%{name} ежедневно приноÑит в казну дополнительно %{count} меру Ñеры." msgid "" "You visit an alchemist who, upon seeing your army, is swayed by the " "righteousness of your cause. The newly loyal subject gives you his endless " "pouch of sulfur to help with the war effort." msgstr "" "Ð’Ñ‹ поÑещаете алхимика, который при виде Вашей армии незамедлительно признаёт " "Ð’Ð°Ñ Ð´Ð¾Ñтойнейшим из доÑтойных. Ðовый подданный дарит Вам Бездонный Мешочек " "Серы, который Вам очень даже пригодитÑÑ." msgid "Endless Vial of Mercury" msgstr "Ð‘ÐµÐ·Ð´Ð¾Ð½Ð½Ð°Ñ ÐšÐ¾Ð»Ð±Ð° Ртути" msgid "The %{name} provides %{count} unit of mercury per day." msgstr "%{name} ежедневно приноÑит в казну дополнительно %{count} меру ртути." msgid "" "A brief stop at a hastily abandoned Wizard's tower turns up a magical vial " "of mercury that always has a little left on the bottom. Recognizing a " "treasure when you see one, you cap it and slip it in your pocket." msgstr "" "Ð’Ñ‹ делаете короткий привал в башне чародеÑ, покинутой хозÑином, и находите " "волшебный ÑоÑуд Ñ Ñ€Ñ‚ÑƒÑ‚ÑŒÑŽ, Ñодержимое которого никогда не кончаетÑÑ. " "Безошибочно раÑпознав в нём Ñокровище, Ð’Ñ‹ хватаете его и кладёте в Ñвой " "карман." msgid "Endless Pouch of Gems" msgstr "Бездонный Мешочек Самоцветов" msgid "The %{name} provides %{count} unit of gems per day." msgstr "" "%{name} ежедневно приноÑит в казну дополнительно %{count} меру Ñамоцветов." msgid "" "A short rainstorm brings forth a rainbow...and you can see the end of it. " "Riding quickly, you seize the pot of gold you find there. The leprechaun who " "owns it, unable to stop you from taking it, offers an endless pouch of gems " "for the return of his gold. You accept." msgstr "" "ПоÑле короткого Ð»Ð¸Ð²Ð½Ñ Ð½Ð° небе поÑвлÑетÑÑ Ñ€Ð°Ð´ÑƒÐ³Ð°. Заметив меÑто, где она " "упираетÑÑ Ð² землю, Ð’Ñ‹ находите там горшок золота. Его хозÑин, маленький " "лепрекон, предлагает взамен Бездонный Мешочек Самоцветов. Ð’Ñ‹ его принимаете." msgid "Endless Cord of Wood" msgstr "ÐеÑÐºÐ¾Ð½Ñ‡Ð°ÐµÐ¼Ð°Ñ Ð’Ñзанка Дров" msgid "The %{name} provides %{count} unit of wood per day." msgstr "" "%{name} ежедневно приноÑит в казну дополнительно %{count} меру древеÑины." msgid "" "Pausing to rest and light a cook fire, you pull wood out of a nearby pile of " "dead wood. As you keep pulling wood from the pile, you notice that it " "doesn't shrink. You realize to your delight that the wood is enchanted, so " "you take it along." msgstr "" "Ð’Ñ‹ оÑтанавливаетеÑÑŒ на отдых и разводите коÑтёр. Ðеподалёку лежит куча дров. " "Ð’Ñ‹ берёте одно полено за другим, но куча не уменьшаетÑÑ. Ð’Ñ‹ Ñ Ñ€Ð°Ð´Ð¾Ñтью " "понимаете, что дрова зачарованы, и забираете их Ñебе." msgid "Endless Cart of Ore" msgstr "Ð‘ÐµÐ·Ð´Ð¾Ð½Ð½Ð°Ñ Ð’Ð°Ð³Ð¾Ð½ÐµÑ‚ÐºÐ° Руды" msgid "The %{name} provides %{count} unit of ore per day." msgstr "%{name} ежедневно приноÑит в казну дополнительно %{count} меру руды." msgid "" "You've found a Goblin weapon smithy making weapons for use against humans. " "With a tremendous yell you and your army descend upon their camp and drive " "them away. A search finds a magic ore cart that never runs out of iron." msgstr "" "Ð’Ñ‹ находите кузницу гоблинов, где они куют оружие. С воинÑтвенным кличем, " "Ваши воины нападают на их лагерь и убивают вÑех врагов. ОÑмотрев трофеи, Ð’Ñ‹ " "обнаруживаете волшебную вагонетку Ñ Ñ€ÑƒÐ´Ð¾Ð¹." msgid "Endless Pouch of Crystal" msgstr "Бездонный Мешочек КриÑталлов" msgid "The %{name} provides %{count} unit of crystal per day." msgstr "" "%{name} ежедневно приноÑит в казну дополнительно %{count} меру криÑталлов." msgid "" "Taking shelter from a storm in a small cave, you notice a small patch of " "crystal in one corner. Curious, you break a piece off and notice that the " "original crystal grows the lost piece back. You decide to stuff the entire " "patch into a pouch and take it with you." msgstr "" "УкрывшиÑÑŒ от бури в небольшой пещерке, Ð’Ñ‹ замечаете в углу друзу криÑталлов. " "Ð’Ñ‹ отламываете куÑок, а на его меÑте выраÑтает новый криÑталл. Ð’Ñ‹ прÑчете " "вÑÑ‘ в мешочек и забираете Ñто Ñокровище Ñ Ñобой." msgid "Spiked Helm" msgstr "Шипованный Шлем" msgid "" "Your army is ambushed by a small tribe of wild (and none too bright) Orcs. " "You fend them off easily and the survivors flee in all directions. One of " "the Orcs was wearing a polished spiked helm. Figuring it will make a good " "souvenir, you take it." msgstr "" "Ðебольшой отрÑд орков нападает на Вашу армию. Ð’Ñ‹ без труда отбиваете атаку. " "Ðа теле одного из нападавших Ð’Ñ‹ видите блеÑÑ‚Ñщий шлем Ñ ÑˆÐ¸Ð¿Ð°Ð¼Ð¸." msgid "Spiked Shield" msgstr "Шипованный Щит" msgid "" "You come upon a bridge spanning a dry gully. Before you can cross, a Troll " "steps out from under the bridge and demands payment before it will permit " "you to pass. You refuse, and the Troll charges, forcing you to slay it. You " "take its spiked shield as a trophy." msgstr "" "Ð’Ñ‹ приближаетеÑÑŒ к моÑту через глубокий овраг. Ðеожиданно из-под моÑта " "поÑвлÑетÑÑ Ñ‚Ñ€Ð¾Ð»Ð»ÑŒ и требует плату за проход. Получив отказ, тролль " "ÑамонадеÑнно нападает на ВаÑ. И теперь в Вашей коллекции новый трофей - его " "щит Ñ ÑˆÐ¸Ð¿Ð°Ð¼Ð¸." msgid "White Pearl" msgstr "Ð‘ÐµÐ»Ð°Ñ Ð–ÐµÐ¼Ñ‡ÑƒÐ¶Ð¸Ð½Ð°" msgid "" "A walk across a dry saltwater lake bed yields an unlikely prize: A white " "pearl amidst shattered shells and debris." msgstr "" "Ð’Ñ‹ переÑекаете переÑохшее ÑолÑное озеро, и вдруг Ñреди обломков ракушек и " "куÑков коралла замечаете великолепную белую жемчужину." msgid "Black Pearl" msgstr "Ð§Ñ‘Ñ€Ð½Ð°Ñ Ð–ÐµÐ¼Ñ‡ÑƒÐ¶Ð¸Ð½Ð°" msgid "" "Rumors of a Griffin of unusual size preying upon the countryside lead you to " "its cave lair. A quick, brutal fight dispatches the beast, and a search of " "its foul nest turns up a huge black pearl." msgstr "" "Слухи об огромном грифоне, нагонÑющем ÑƒÐ¶Ð°Ñ Ð½Ð° вÑÑŽ округу, приводÑÑ‚ Ð’Ð°Ñ Ð² его " "логово. ЖеÑÑ‚Ð¾ÐºÐ°Ñ Ñхватка заканчиваетÑÑ Ð’Ð°ÑˆÐµÐ¹ победой, и в опуÑтевшем гнезде " "Ð’Ñ‹ находите чёрную жемчужину." msgid "Magic Book" msgstr "Ð’Ð¾Ð»ÑˆÐµÐ±Ð½Ð°Ñ ÐšÐ½Ð¸Ð³Ð°" msgid "The %{name} enables the hero to cast spells." msgstr "%{name} позволÑет иÑпользовать заклинаниÑ." msgid "Dummy 1" msgstr "Болванка 1" msgid "The reserved artifact." msgstr "Забронированный артефакт." msgid "Dummy 2" msgstr "Болванка 2" msgid "Dummy 3" msgstr "Болванка 3" msgid "Dummy 4" msgstr "Болванка 4" msgid "Spell Scroll" msgstr "Свиток ЗаклинаниÑ" msgid "" "This %{name} gives the hero the ability to cast the %{spell} spell if the " "hero has a Magic Book." msgstr "" "%{name} позволÑет герою произноÑить заклинание %{spell}, еÑли он имеет " "Волшебную Книгу." msgid "" "You find an elaborate container which houses an old vellum scroll. The runes " "on the container are very old, and the artistry with which it was put " "together is stunning. As you pull the scroll out, you feel imbued with " "magical power." msgstr "" "Ð’Ñ‹ находите замыÑловатый ÑоÑуд, в котором лежит Ñтарый пергаментный Ñвиток. " "Руны на нём очень древние, а маÑтерÑтво, Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ð¼ он был Ñобран, поражает " "воображение. Когда Ð’Ñ‹ доÑтаёте Ñвиток, Ð’Ñ‹ чувÑтвуете, что пропитаны " "магичеÑкой Ñилой." msgid "Arm of the Martyr" msgstr "Рука Мученика" msgid "" "The %{name} increases the hero's spell power by %{count} but adds the undead " "morale penalty." msgstr "" "%{name} увеличивает Ñилу магии Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count}, но добавлÑет штраф к " "морали, как за нахождение нежити в отрÑде." msgid "" "One of the less intelligent members of your party picks up an arm off of the " "ground. Despite its missing a body, it is still moving. Your troops find the " "dismembered arm repulsive, but you cannot bring yourself to drop it: it " "seems to hold some sort of magical power that influences your decision " "making." msgstr "" "Один из менее Ñообразительных воинов поднимает Ñ Ð·ÐµÐ¼Ð»Ð¸ руку. ÐеÑÐ¼Ð¾Ñ‚Ñ€Ñ Ð½Ð° " "отÑутÑтвие тела, она вÑÑ‘ ещё двигаетÑÑ. Ваши Ñолдаты находÑÑ‚ отрубленную " "руку омерзительной, но Ð’Ñ‹ не можете заÑтавить ÑÐµÐ±Ñ Ð±Ñ€Ð¾Ñить её: кажетÑÑ, что " "она обладает какой-то магичеÑкой Ñилой, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð²Ð»Ð¸Ñет на принÑтие Вами " "решений." msgid "Breastplate of Anduran" msgstr "КираÑа Ðндурана" msgid "The %{name} increases the hero's defense by %{count}." msgstr "%{name} увеличивает навык защиты Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count}." msgid "" "You come upon a sign. It reads: \"Here lies the body of Anduran. Bow and " "swear fealty, and you shall be rewarded.\" You decide to do as it says. As " "you stand up, you feel a coldness against your skin. Looking down, you find " "that you are suddenly wearing a gleaming, ornate breastplate." msgstr "" "Указатель глаÑит: \"ЗдеÑÑŒ лежит тело Ðндурана. СклониÑÑŒ и поклÑниÑÑŒ в " "верноÑти и будешь вознагражден.\" Ð’Ñ‹ решили Ñделать точно также. Ð’Ñ‹ вÑтаете " "и чувÑтвуете холод на коже. ОÑÐ¼Ð°Ñ‚Ñ€Ð¸Ð²Ð°Ñ ÑебÑ, Ð’Ñ‹ обнаружили, что Ð’Ñ‹ ноÑите " "блеÑÑ‚Ñщую броню." msgid "Broach of Shielding" msgstr "Ð—Ð°Ñ‰Ð¸Ñ‚Ð½Ð°Ñ Ð‘Ñ€Ð¾ÑˆÑŒ" msgid "" "The %{name} provides %{count} percent protection from Armageddon and " "Elemental Storm, but decreases spell power by 2." msgstr "" "%{name} увеличивает защиту от 'Ðрмагеддона' и 'Бури Ñтихий' на %{count} " "процентов, но также уменьшает Ñилу магии на 2 пункта." msgid "" "A kindly Sorceress thinks that your army's defenses could use a magical " "boost. She offers to enchant the Broach that you wear on your cloak, and you " "accept." msgstr "" "ÐšÐ¾Ð»Ð´ÑƒÐ½ÑŒÑ Ñчитает, что Ð´Ð»Ñ Ð·Ð°Ñ‰Ð¸Ñ‚Ñ‹ Вашей армии можно иÑпользовать магичеÑкую " "поддержку. Она предлагает заколдовать брошь, что Ð’Ñ‹ ноÑите на Вашем плаще, и " "Ð’Ñ‹ ÑоглашаетеÑÑŒ." msgid "Battle Garb of Anduran" msgstr "Боевой ДоÑпех Ðндурана" msgid "" "The %{name} combines the powers of the three Anduran artifacts. It provides " "maximum luck and morale for the hero's troops and gives the hero the Town " "Portal spell." msgstr "" "%{name} Ñочетает в Ñебе Ñилу трёх артефактов Ðндурана. Он увеличивает удачу " "и мораль войÑк героÑ, а также даёт герою заклинание 'Портал города'." msgid "" "Out of pity for a poor peasant, you purchase a chest of old junk they are " "hawking for too much gold. Later, as you search through it, you find it " "contains the 3 pieces of the legendary battle garb of Anduran!" msgstr "" "Из жалоÑти к бедным креÑтьÑнам, Ð’Ñ‹ приобретаете груду ÑтарьÑ. Позже, Ð’Ñ‹ " "находите в нём 3 чаÑти легендарного боевого доÑпеха Ðндурана!" msgid "Crystal Ball" msgstr "КриÑтальный Шар" msgid "" "The %{name} lets the hero get more specific information about monsters, " "enemy heroes, and castles nearby the hero." msgstr "" "%{name} предоÑтавлÑет подробную информацию о ÑущеÑтвах, вражеÑких героÑÑ… и " "замках, Ñ€Ñдом Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ð¼Ð¸ Ñтоит герой." msgid "" "You come upon a caravan of gypsies who are feasting and fortifying their " "bodies with mead. They call you forward and say \"If you prove that you can " "dance the Rama-Buta, we will reward you.\" You don't know it, but try " "anyway. They laugh hysterically, but admire your bravery, giving you a " "Crystal Ball." msgstr "" "Ð’Ñ‹ поÑетили караван цыган. У них идёт пирушка, пьÑные цыгане потерÑли Ñтрах, " "и Ð’Ð°Ñ Ð²Ñ‹Ð·Ñ‹Ð²Ð°ÑŽÑ‚: \"Докажешь, что ты танцуешь как Рама Бута, и мы наградим " "тебÑ.\"\n" "Ð’Ñ‹ решаете попробовать... Цыгане ÑмеютÑÑ Ð½Ð°Ð´ Вашими попытками, но воÑхищаÑÑÑŒ " "Вашим мужеÑтвом, отдают Вам 'КриÑтальный Шар'." msgid "Heart of Fire" msgstr "Сердце ОгнÑ" msgid "" "The %{name} provides %{count} percent protection from fire, but doubles the " "damage taken from cold." msgstr "" "%{name} увеличивает защиту от магии Ð¾Ð³Ð½Ñ Ð½Ð° %{count} процентов, но также в " "два раза увеличивает урон от магии холода." msgid "" "You enter a recently burned glade and come upon a Fire Elemental sitting " "atop a rock. It looks up, its flaming face contorted in a look of severe " "pain. It then tosses a glowing object at you. You put up your hands to block " "it, but it passes right through them and sears itself into your chest." msgstr "" "Ð’Ñ‹ поднимаетеÑÑŒ на вершину Ñкалы, где Ñидит Огненный Ñлементаль. Его " "пылающее лицо иÑкажено от Ñильной боли. Затем он броÑает ÑветÑщийÑÑ ÑгуÑток " "на ВаÑ. Ð’Ñ‹ делаете жеÑÑ‚ руками, чтобы заблокировать его, но ÑгуÑток проходит " "через Ваши руки и тело." msgid "Heart of Ice" msgstr "Сердце Льда" msgid "" "The %{name} provides %{count} percent protection from cold, but doubles the " "damage taken from fire." msgstr "" "%{name} увеличивает защиту от магии холода на %{count} процентов, но также в " "два раза увеличивает урон от магии огнÑ." msgid "" "Suddenly, a biting coldness engulfs your body. You seize up, falling from " "your horse. The pain subsides, but you still feel as if your chest is " "frozen. As you pick yourself up off of the ground, you hear hearty laughter. " "You turn around just in time to see a Frost Giant run off into the woods and " "disappear." msgstr "" "Вдруг резкий холод охватывает Ваше тело, и Ð’Ñ‹ падаете Ñ Ð»Ð¾ÑˆÐ°Ð´Ð¸. Боль " "утихает, Ð’Ñ‹ поднимаетеÑÑŒ и Ñлышите Ñмех. Ð’Ñ‹ уÑпеваете обернутьÑÑ ÐºÐ°Ðº раз " "вовремÑ, чтобы увидеть ЛедÑного Гиганта ÑкрывающегоÑÑ Ð² леÑу." msgid "Helmet of Anduran" msgstr "Шлем Ðндурана" msgid "" "You spy a gleaming object poking up out of the ground. You send a member of " "your party over to investigate. He comes back with a golden helmet in his " "hands. You realize that it must be the helmet of the legendary Anduran, the " "only man who was known to wear solid gold armor." msgstr "" "Ð’Ñ‹ заметили блеÑÑ‚Ñщий объект недалеко в земле, и отправлÑете Ñвоего " "помощника раÑкопать его. Он возвращаетÑÑ Ñ Ð·Ð¾Ð»Ð¾Ñ‚Ñ‹Ð¼ шлемом в руках. Ð’Ñ‹ " "узнаёте легендарный шлем Ðндурана." msgid "Holy Hammer" msgstr "СвÑтой Молот" msgid "" "You come upon a battle where a Paladin has been mortally wounded by a group " "of Zombies. He asks you to take his hammer and finish what he started. As " "you pick it up, it begins to hum, and then everything becomes a blur. The " "Zombies lie dead, the hammer dripping with blood. You strap it to your belt." msgstr "" "Ð’Ñ‹ подъезжаете к полю боÑ. Паладин был Ñмертельно ранен группой Зомби. Он " "проÑит Ð’Ð°Ñ Ð·Ð°Ð±Ñ€Ð°Ñ‚ÑŒ его молот и закончить битву. Ð’Ñ‹ подбираете магичеÑкий " "молот, он начинает издавать мелодичные звуки, и потом вÑе вокруг рушитÑÑ. " "Ð’Ñе Зомби уничтожены. Залитый кровью молот Ð’Ñ‹ прицеплÑете к Ñвоему ремню." msgid "Legendary Scepter" msgstr "Легендарный Скипетр" msgid "The %{name} adds %{count} points to all attributes." msgstr "%{name} увеличивает вÑе оÑновные параметры на %{count}." msgid "" "Upon cresting a small hill, you come upon a ridiculous looking sight. A " "Sprite is attempting to carry a Scepter that is almost as big as it is. " "Trying not to laugh, you ask, \"Need help?\" The Sprite glares at you and " "answers: \"You think this is funny? Fine. You can carry it. I much prefer " "flying anyway.\"" msgstr "" "Ðа небольшом холме, Ð’Ñ‹ видите Ñмешную Ñцену. Ð¤ÐµÑ Ð¿Ñ‹Ñ‚Ð°ÐµÑ‚ÑÑ Ð¿Ð¾Ð´Ð½Ñть большой " "Ñкипетр.\n" "СтараÑÑÑŒ не раÑÑмеÑтьÑÑ, Ð’Ñ‹ Ñпрашиваете: \"Возможно нужна помощь?\"\n" "Ð¤ÐµÑ Ñмотрит на Ð’Ð°Ñ Ð¸ отвечает: \"Ð’Ñ‹ думаете, Ñто Ñмешно? Хорошо. Забирайте " "его Ñебе, а Ñ ÑƒÐ»ÐµÑ‚Ð°ÑŽ.\"" msgid "" "An old seaman tells you a tale of an enchanted masthead that he used in his " "youth to rally his crew during times of trouble. He then hands you a faded " "map that shows where he hid it. After much exploring, you find it stashed " "underneath a nearby dock." msgstr "" "Старый морÑк раÑÑказал Вам иÑторию о заколдованной шлÑпе, которую он " "иÑпользовал во Ð²Ñ€ÐµÐ¼Ñ ÑˆÑ‚Ð¾Ñ€Ð¼Ð¾Ð². Затем он вручает Вам карту, на которой " "отмечено, где он ÑпрÑтал её. ПоÑле продолжительных поиÑков, Ð’Ñ‹ находите " "шлÑпу." msgid "Masthead" msgstr "Ðаконечник Мачты" msgid "" "The %{name} boosts the hero's troops' luck and morale by %{count} each in " "sea combat." msgstr "" "%{name}, при ÑражениÑÑ… на воде, увеличивает удачу и мораль войÑк Ð³ÐµÑ€Ð¾Ñ Ð½Ð° " "%{count}." msgid "Sphere of Negation" msgstr "Сфера ОтрицаниÑ" msgid "The %{name} disables all spell casting, for both sides, in combat." msgstr "%{name} запрещает вÑем на поле Ð±Ð¾Ñ Ð¸Ñпользовать магию." msgid "" "You stop to help a Peasant catch a runaway mare. To show his gratitude, he " "hands you a tiny sphere. As soon as you grasp it, you feel the magical " "energy drain from your limbs..." msgstr "" "Ð’Ñ‹ оÑтановилиÑÑŒ помочь КреÑтьÑнам поймать Ñбежавшую лошадь. Чтобы выразить " "благодарноÑть, они вручают Вам крохотную Ñферу. Лишь только взÑв её, Ð’Ñ‹ " "чувÑтвуете, как магичеÑÐºÐ°Ñ ÑÐ½ÐµÑ€Ð³Ð¸Ñ Ð²Ñ‹ÑаÑываетÑÑ Ð¸Ð· Ваших рук." msgid "Staff of Wizardry" msgstr "Волшебный ПоÑох" msgid "The %{name} boosts the hero's spell power by %{count}." msgstr "%{name} увеличивает Ñилу магии Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count}." msgid "" "While out scaring up game, your troops find a mysterious staff levitating " "about three feet off of the ground. They hand it to you, and you notice an " "inscription. It reads: \"Brains best brawn and magic beats might. Heed my " "words, and you'll win every fight.\"" msgstr "" "Ð’Ñ‹ замечаете таинÑтвенный поÑох, парÑщий около трёх футов от земли, " "раÑпугавший вÑÑŽ дичь в округе. Ðа нём Ð’Ñ‹ заметили надпиÑÑŒ: \"Ум лучше " "муÑкулов, а Ð¼Ð°Ð³Ð¸Ñ Ð¿Ð¾Ð±ÐµÐ´Ð¸Ñ‚ Ñилу. Ð’Ñегда помни Ñто.\"" msgid "Sword Breaker" msgstr "Мечелом" msgid "The %{name} increases the hero's defense by %{count} and attack by 1." msgstr "%{name} увеличивает навык защиты Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count}, и атаки на 1." msgid "" "A former Captain of the Guard admires your quest and gives you the enchanted " "Sword Breaker that he relied on during his tour of duty." msgstr "" "Бывший капитан Ñтражи воÑхищён Вашими приключениÑми и дарит Вам зачарованный " "Крушитель Клинков, на который он полагалÑÑ Ð²Ð¾ Ð²Ñ€ÐµÐ¼Ñ Ñлужбы." msgid "Sword of Anduran" msgstr "Меч Ðндурана" msgid "" "A Troll stops you and says: \"Pay me 5,000 gold, or the Sword of Anduran " "will slay you where you stand.\" You refuse. The troll grabs the sword " "hanging from its belt, screams in pain, and runs away. Picking up the fabled " "sword, you give thanks that half-witted Trolls tend to grab the wrong end of " "sharp objects." msgstr "" "Ð’Ð°Ñ Ð¾Ñтанавливает Тролль и говорит: \"Плати 5 тыÑÑч золотых или будешь убит " "на меÑте мечом Ðндурана\". Ð’Ñ‹ отказываетеÑÑŒ. Тролль выхватывает меч, " "ÑвиÑающий Ñ Ð¿Ð¾ÑÑа, вÑкрикивает от боли и убегает. ÐŸÐ¾Ð´Ð½Ð¸Ð¼Ð°Ñ Ð»ÐµÐ³ÐµÐ½Ð´Ð°Ñ€Ð½Ñ‹Ð¹ меч, " "Ð’Ñ‹ благодарите Ñудьбу за то, что недалекие Тролли имеют обыкновение хватать " "оÑтрые предметы не за тот конец." msgid "Spade of Necromancy" msgstr "Лопата Могильщика" msgid "The %{name} gives the hero increased necromancy skill." msgstr "%{name} даёт герою повышенный навык некромантии." msgid "" "A dirty shovel has been thrust into a dirt mound nearby. Upon investigation, " "you discover it to be the enchanted shovel of the Gravediggers, long thought " "lost by mortals." msgstr "" "ГрÑÐ·Ð½Ð°Ñ Ð»Ð¾Ð¿Ð°Ñ‚Ð° торчала из могильного холма. ПоÑле иÑÑледованиÑ, оказалоÑÑŒ, " "что Ñто Ð·Ð°Ñ‡Ð°Ñ€Ð¾Ð²Ð°Ð½Ð½Ð°Ñ Ð»Ð¾Ð¿Ð°Ñ‚Ð° гробокопателей, ÑчитавшаÑÑÑ Ð´Ð°Ð²Ð½Ñ‹Ð¼-давно " "потерÑнной Ñмертными." msgid "spellBonus|selected by user" msgstr ", выбранное пользователем" msgid "Wood" msgstr "ДревеÑина" msgid "Mercury" msgstr "Ртуть" msgid "Ore" msgstr "Руда" msgid "Sulfur" msgstr "Сера" msgid "Crystal" msgstr "КриÑталлы" msgid "Gems" msgstr "Самоцветы" msgid "Gold" msgstr "Золото" msgid "" "There are seven resources in Heroes 2, used to build and improves castles, " "purchase troops and recruit heroes. Gold is the most common, required for " "virtually everything. Wood and ore are used for most buildings. Gems, " "Mercury, Sulfur and Crystal are rare magical resources used for the most " "powerful creatures and buildings." msgstr "" "Ð’ Heroes II еÑть 7 типов реÑурÑов, иÑпользуемых Ð´Ð»Ñ Ð²Ð¾Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¿Ð¾Ñтроек, " "улучшений замков, покупки наиболее Ñильных ÑущеÑтв и героев. Самый " "раÑпроÑтранённый реÑÑƒÑ€Ñ - золото, необходимый практичеÑки везде. ДревеÑина и " "руда иÑпользуютÑÑ Ð´Ð»Ñ Ð²Ð¾Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð±Ð¾Ð»ÑŒÑˆÐ¸Ð½Ñтва поÑтроек. Самоцветы, ртуть, " "Ñера и криÑталлы - редкие магичеÑкие реÑурÑÑ‹, нужные Ð´Ð»Ñ Ð²Ð¾Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð»ÑƒÑ‡ÑˆÐ¸Ñ… " "поÑтроек и покупки Ñильных ÑущеÑтв." msgid "" "Causes a giant fireball to strike the selected area, damaging all nearby " "creatures." msgstr "" "Огромный огненный шар взрываетÑÑ Ð½Ð°Ð´ выбранной облаÑтью, Ð¿Ð¾Ñ€Ð°Ð¶Ð°Ñ Ð²Ñех, кого " "накрыло пламÑ." msgid "Fireball" msgstr "Огненный шар" msgid "Fireblast" msgstr "Огненный взрыв" msgid "" "An improved version of fireball, fireblast affects two hexes around the " "center point of the spell, rather than one." msgstr "" "Огненный взрыв - Ñто ÑƒÐ»ÑƒÑ‡ÑˆÐµÐ½Ð½Ð°Ñ Ð²ÐµÑ€ÑÐ¸Ñ Ð¾Ð³Ð½ÐµÐ½Ð½Ð¾Ð³Ð¾ шара, Ð¿Ð¾Ñ€Ð°Ð¶Ð°ÑŽÑ‰Ð°Ñ Ð²Ñех на " "значительном раÑÑтоÑнии от Ñпицентра." msgid "Causes a bolt of electrical energy to strike the selected creature." msgstr "Мощный ÑлектричеÑкий разрÑд поражает выбранный отрÑд противника." msgid "Lightning Bolt" msgstr "МолниÑ" msgid "Chain Lightning" msgstr "Цепь молний" msgid "" "Causes a bolt of electrical energy to strike a selected creature, then " "strike the nearest creature with half damage, then strike the NEXT nearest " "creature with half again damage, and so on, until it becomes too weak to be " "harmful. Warning: This spell can hit your own creatures!" msgstr "" "Цепь молний поражает цель, а затем переÑкакивает на ближайший отрÑд, " "причинÑÑ Ð¿Ð¾Ð»Ð¾Ð²Ð¸Ð½Ñƒ урона от предыдущего, и так до 4-Ñ… отрÑдов. " "Предупреждение: Ñто заклинание может поразить Ваши ÑобÑтвенные отрÑды!" msgid "Teleport" msgstr "Телепорт" msgid "" "Teleports the creature you select to any open position on the battlefield." msgstr "Телепортирует выбранное ÑущеÑтво в любую Ñвободную точку на поле." msgid "Cure" msgstr "Лечение" msgid "" "Removes all negative spells cast upon one of your units, and restores up to " "%{count} HP per level of spell power." msgstr "" "Убирает вÑе негативные Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ (кроме разрушающего луча) Ñ Ð’Ð°ÑˆÐµÐ³Ð¾ отрÑда " "и воÑÑтанавливает %{count}*SP здоровьÑ." msgid "Mass Cure" msgstr "Общее лечение" msgid "" "Removes all negative spells cast upon your forces, and restores up to " "%{count} HP per level of spell power, per creature." msgstr "" "Убирает вÑе негативные Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ (кроме разрушающего луча) Ñ Ð’Ð°ÑˆÐ¸Ñ… отрÑдов, " "и воÑÑтанавливает %{count}*SP здоровьÑ." msgid "Resurrect" msgstr "Оживление" msgid "Resurrects creatures from a damaged or dead unit until end of combat." msgstr "" "До конца Ð±Ð¾Ñ Ð¾Ð¶Ð¸Ð²Ð»Ñет воинов в отрÑде, которому был нанеÑён урон или который " "был уничтожен." msgid "Resurrect True" msgstr "ВоÑкрешение" msgid "Resurrects creatures from a damaged or dead unit permanently." msgstr "" "ÐавÑегда оживлÑет воинов в отрÑде, которому был нанеÑён урон или который был " "уничтожен." msgid "Haste" msgstr "УÑкорение" msgid "Increases the speed of any creature by %{count}." msgstr "Увеличивает ÑкороÑть Вашего отрÑда на %{count}." msgid "Increases the speed of all of your creatures by %{count}." msgstr "Увеличивает ÑкороÑть вÑех Ваших отрÑдов на %{count}." msgid "Mass Haste" msgstr "Общее уÑкорение" msgid "Slows target to half movement rate." msgstr "ЗамедлÑет вражеÑкий отрÑд на 2 Ñтупени." msgid "spell|Slow" msgstr "Замедление" msgid "Mass Slow" msgstr "Общее замедление" msgid "Slows all enemies to half movement rate." msgstr "ЗамедлÑет вÑе вражеÑкие отрÑды на 2 Ñтупени." msgid "Clouds the affected creatures' eyes, preventing them from moving." msgstr "" "ОÑлеплённый отрÑд пропуÑкает ходы до тех пор, пока не получит урон или не " "окончитÑÑ Ð´ÐµÐ¹Ñтвие заклинаниÑ." msgid "spell|Blind" msgstr "Слепота" msgid "Bless" msgstr "БлагоÑловение" msgid "Causes the selected creatures to inflict maximum damage." msgstr "Выбранный отрÑд будет наноÑить макÑимальный урон противнику." msgid "Causes all of your units to inflict maximum damage." msgstr "Ð’Ñе Ваши отрÑды будут наноÑить макÑимальный урон противнику." msgid "Mass Bless" msgstr "Общее благоÑловение" msgid "Magically increases the defense skill of the selected creatures." msgstr "МагичеÑки увеличивает защиту выбранного отрÑда на 3." msgid "Stoneskin" msgstr "ÐšÐ°Ð¼ÐµÐ½Ð½Ð°Ñ ÐºÐ¾Ð¶Ð°" msgid "" "Increases the defense skill of the targeted creatures. This is an improved " "version of Stoneskin." msgstr "" "Увеличивает защиту выбранного отрÑда на 5. Это заклинание ÑвлÑетÑÑ Ð±Ð¾Ð»ÐµÐµ " "мощной верÑией Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ 'ÐšÐ°Ð¼ÐµÐ½Ð½Ð°Ñ ÐºÐ¾Ð¶Ð°'." msgid "Steelskin" msgstr "Ð¡Ñ‚Ð°Ð»ÑŒÐ½Ð°Ñ ÐºÐ¾Ð¶Ð°" msgid "Causes the selected creatures to inflict minimum damage." msgstr "ЗаÑтавлÑет вражеÑкий отрÑд при атаке наноÑить минимальный урон." msgid "Curse" msgstr "ПроклÑтие" msgid "Causes all enemy troops to inflict minimum damage." msgstr "ЗаÑтавлÑет вÑе вражеÑкие отрÑды при атаке наноÑить минимальный урон." msgid "Mass Curse" msgstr "Общее проклÑтие" msgid "Damages all undead in the battle." msgstr "ÐаноÑит урон вÑей нежити, находÑщейÑÑ Ð½Ð° поле боÑ." msgid "Holy Word" msgstr "СвÑтое Ñлово" msgid "" "Damages all undead in the battle. This is an improved version of Holy Word." msgstr "" "УÑовершенÑтвованный вариант ÑвÑтого Ñлова, который причинÑет Ñильные " "Ð¿Ð¾Ð²Ñ€ÐµÐ¶Ð´ÐµÐ½Ð¸Ñ Ð²Ñей нежити, находÑщейÑÑ Ð½Ð° поле боÑ." msgid "Holy Shout" msgstr "Экзорцизм" msgid "Anti-Magic" msgstr "ÐнтимагиÑ" msgid "Prevents harmful magic against the selected creatures." msgstr "Защищает выбранный отрÑд от дейÑÑ‚Ð²Ð¸Ñ Ð²Ñех заклинаний." msgid "Dispel Magic" msgstr "СнÑтие чар" msgid "Removes all magic spells from a single target." msgstr "" "Снимает Ñ Ð²Ñ‹Ð±Ñ€Ð°Ð½Ð½Ð¾Ð³Ð¾ отрÑда дейÑтвие вÑех заклинаний, как полезных, так и " "вредных." msgid "Mass Dispel" msgstr "Общее ÑнÑтие чар" msgid "Removes all magic spells from all creatures." msgstr "" "Снимает дейÑÑ‚Ð²Ð¸Ñ Ð²Ñех заклинаний, как полезных, так и вредных, Ñо вÑех " "приÑутÑтвующих на поле Ð±Ð¾Ñ Ð¾Ñ‚Ñ€Ñдов." msgid "Causes a magic arrow to strike the selected target." msgstr "Ð’Ð¾Ð»ÑˆÐµÐ±Ð½Ð°Ñ Ñтрела поражает отрÑд противника." msgid "Magic Arrow" msgstr "Ð’Ð¾Ð»ÑˆÐµÐ±Ð½Ð°Ñ Ñтрела" msgid "Berserker" msgstr "БерÑерк" msgid "Causes a creature to attack its nearest neighbor." msgstr "" "Выбранный отрÑд атакует любой ближайший к нему отрÑд, но поÑле атаки " "дейÑтвие Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ Ð¿Ñ€ÐµÐºÑ€Ð°Ñ‰Ð°ÐµÑ‚ÑÑ." msgid "Armageddon" msgstr "Ðрмагеддон" msgid "" "Holy terror strikes the battlefield, causing severe damage to all creatures." msgstr "Ð¥Ð°Ð¾Ñ Ð¾Ð±Ñ€ÑƒÑˆÐ¸Ð²Ð°ÐµÑ‚ÑÑ Ð½Ð° боле боÑ, причинÑÑ Ð²Ñем и вÑÑ Ð½ÐµÐ²ÐµÑ€Ð¾Ñтный урон." msgid "Elemental Storm" msgstr "Ð‘ÑƒÑ€Ñ Ñтихий" msgid "Magical elements pour down on the battlefield, damaging all creatures." msgstr "" "Силы Ñтихий обрушиваютÑÑ Ð½Ð° поле боÑ, Ð¿Ð¾Ñ€Ð°Ð¶Ð°Ñ Ð²Ñех учаÑтников ÑражениÑ." msgid "" "A rain of rocks strikes an area of the battlefield, damaging all nearby " "creatures." msgstr "" "Призывает дождь из камней, наноÑÑщий урон вÑем ÑущеÑтвам, находÑщимÑÑ Ð² поле " "дейÑÑ‚Ð²Ð¸Ñ Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ." msgid "Meteor Shower" msgstr "Камнепад" msgid "Paralyze" msgstr "Паралич" msgid "The targeted creatures are paralyzed, unable to move or retaliate." msgstr "" "ОтрÑд, против которого направленно Ñто заклинание, Ñковывает паралич, и он " "терÑет ÑпоÑобноÑть передвигатьÑÑ Ð¸ отвечать на удары." msgid "Hypnotize" msgstr "Гипноз" msgid "" "Brings a single enemy unit under your control if its hits are less than " "%{count} times the caster's spell power." msgstr "" "Выбранный вражеÑкий отрÑд переходит под Ваш полный контроль, еÑли его " "здоровье не превышает %{count}*SP." msgid "Cold Ray" msgstr "ЛедÑной луч" msgid "Drains body heat from a single enemy unit." msgstr "Ð’Ñ‹ÑаÑывает жизненное тепло из вражеÑкого отрÑда." msgid "Cold Ring" msgstr "ЛедÑное кольцо" msgid "" "Drains body heat from all units surrounding the center point, but not " "including the center point." msgstr "" "Ð’Ñ‹ÑаÑывает жизненное тепло из вражеÑких отрÑдов, находÑщихÑÑ Ð² зоне дейÑÑ‚Ð²Ð¸Ñ " "заклинаниÑ, за иÑключением его центра." msgid "Disrupting Ray" msgstr "Разрушающий луч" msgid "Reduces the defense rating of an enemy unit by three." msgstr "Снижает защиту выбранного отрÑда на 3." msgid "Damages all living (non-undead) units in the battle." msgstr "ÐаноÑит урон вÑем живым ÑущеÑтвам на поле боÑ, кроме нежити." msgid "Death Ripple" msgstr "Дыхание Ñмерти" msgid "Death Wave" msgstr "Волна Ñмерти" msgid "" "Damages all living (non-undead) units in the battle. This spell is an " "improved version of Death Ripple." msgstr "" "ÐаноÑит значительный урон вÑем живым ÑущеÑтвам на поле боÑ, кроме нежити. " "Это заклинание ÑвлÑетÑÑ Ð±Ð¾Ð»ÐµÐµ мощной верÑией Ð·Ð°ÐºÐ»Ð¸Ð½Ð°Ð½Ð¸Ñ 'Дыхание Ñмерти'." msgid "Dragon Slayer" msgstr "Убийца драконов" msgid "Greatly increases a unit's attack skill vs. Dragons." msgstr "Увеличивает атаку выбранного ÑущеÑтва в Ñхватке Ñ Ð´Ñ€Ð°ÐºÐ¾Ð½Ð°Ð¼Ð¸ на 5." msgid "Blood Lust" msgstr "Жажда крови" msgid "Increases a unit's attack skill." msgstr "Увеличивает атаку выбранного отрÑда на 3." msgid "Animate Dead" msgstr "ПоднÑть нежить" msgid "Resurrects creatures from a damaged or dead undead unit permanently." msgstr "ÐавÑегда излечивает и поднимает павшую нежить." msgid "Mirror Image" msgstr "Фантом" msgid "" "Creates an illusionary unit that duplicates one of your existing units. This " "illusionary unit does the same damages as the original, but will vanish if " "it takes any damage." msgstr "" "Создаёт дубликат одного из Ваших отрÑдов. Эта Ð¸Ð»Ð»ÑŽÐ·Ð¸Ñ Ð¼Ð¾Ð¶ÐµÑ‚ нанеÑти такой же " "урон как и оригинал, но будет развеÑна от малейшего повреждениÑ." msgid "Shield" msgstr "Щит" msgid "" "Halves damage received from ranged attacks for a single unit. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Принимает на ÑÐµÐ±Ñ Ð¿Ð¾Ð»Ð¾Ð²Ð¸Ð½Ñƒ урона от вÑех Ñтрелковых атак врага, направленных " "на выбранный отрÑд. Ðе влиÑет на урон от замковых башен или баллиÑты." msgid "Mass Shield" msgstr "Общий щит" msgid "" "Halves damage received from ranged attacks for all of your units. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Принимает на ÑÐµÐ±Ñ Ð¿Ð¾Ð»Ð¾Ð²Ð¸Ð½Ñƒ урона Ñтрелковых атак врага, направленных на вÑех " "Ваших ÑущеÑтв. Ðе влиÑет на урон от замковых башен или баллиÑты." msgid "Summon Earth Elemental" msgstr "Земной Ñлементаль" msgid "Summons Earth Elementals to fight for your army." msgstr "Призывает на поле Ð±Ð¾Ñ Ð—ÐµÐ¼Ð½Ñ‹Ñ… Ñлементалей ÑражатьÑÑ Ð½Ð° Вашей Ñтороне." msgid "Summon Air Elemental" msgstr "Воздушный Ñлементаль" msgid "Summons Air Elementals to fight for your army." msgstr "" "Призывает на поле Ð±Ð¾Ñ Ð’Ð¾Ð·Ð´ÑƒÑˆÐ½Ñ‹Ñ… Ñлементалей ÑражатьÑÑ Ð½Ð° Вашей Ñтороне." msgid "Summon Fire Elemental" msgstr "Огненный Ñлементаль" msgid "Summons Fire Elementals to fight for your army." msgstr "Призывает на поле Ð±Ð¾Ñ ÐžÐ³Ð½ÐµÐ½Ð½Ñ‹Ñ… Ñлементалей ÑражатьÑÑ Ð½Ð° Вашей Ñтороне." msgid "Summon Water Elemental" msgstr "Водный Ñлементаль" msgid "Summons Water Elementals to fight for your army." msgstr "Призывает на поле Ð±Ð¾Ñ Ð’Ð¾Ð´Ð½Ñ‹Ñ… Ñлементалей ÑражатьÑÑ Ð½Ð° Вашей Ñтороне." msgid "Damages castle walls." msgstr "ÐаноÑит урон Ñтенам замка." msgid "Earthquake" msgstr "ЗемлетрÑÑение" msgid "Causes all mines across the land to become visible." msgstr "Делает видимыми вÑе шахты, раÑположенные на карте." msgid "View Mines" msgstr "Показать шахты" msgid "Causes all resources across the land to become visible." msgstr "Делает видимыми вÑе реÑурÑÑ‹, раÑположенные на карте." msgid "View Resources" msgstr "Показать реÑурÑÑ‹" msgid "Causes all artifacts across the land to become visible." msgstr "Делает видимыми вÑе артефакты, раÑположенные на карте." msgid "View Artifacts" msgstr "Показать артефакты" msgid "Causes all towns and castles across the land to become visible." msgstr "Делает видимыми вÑе города и замки, раÑположенные на карте." msgid "View Towns" msgstr "Показать города" msgid "Causes all Heroes across the land to become visible." msgstr "Делает видимыми вÑех героев, раÑположенных на карте." msgid "View Heroes" msgstr "Показать героев" msgid "Causes the entire land to become visible." msgstr "Делает видимой вÑÑŽ карту." msgid "View All" msgstr "Показать вÑÑ‘" msgid "Allows the caster to view detailed information on enemy Heroes." msgstr "ПозволÑет получить подробную информацию о героÑÑ… противника." msgid "Summon Boat" msgstr "Призвать корабль" msgid "" "Summons the nearest unoccupied, friendly boat to an adjacent shore location. " "A friendly boat is one which you just built or were the most recent player " "to occupy." msgstr "" "Перемещает Ваш ближайший незанÑтый корабль в ближайшую точку побережьÑ. " "Корабль ÑчитаетÑÑ Ð’Ð°ÑˆÐ¸Ð¼, еÑли Ð’Ñ‹ его только что поÑтроили, или тот на " "котором Ð’Ñ‹ плавали поÑледним." msgid "Allows the caster to magically transport to a nearby location." msgstr "ПереноÑит Ð³ÐµÑ€Ð¾Ñ Ð² раÑположенную поблизоÑти точку на карте." msgid "Dimension Door" msgstr "Портал" msgid "Returns the caster to any town or castle currently owned." msgstr "" "Возвращает Ð³ÐµÑ€Ð¾Ñ Ð² ближайший город или замок, еÑли там уже не находитÑÑ " "другой герой." msgid "Town Gate" msgstr "Врата в город" msgid "" "Returns the hero to the town or castle of choice, provided it is controlled " "by you." msgstr "" "Возвращает Ð³ÐµÑ€Ð¾Ñ Ð² любой город или замок на выбор, еÑли там уже не находитÑÑ " "другой герой." msgid "Visions" msgstr "ВидениÑ" msgid "" "Visions predicts the likely outcome of an encounter with a neutral army camp." msgstr "" "ПозволÑет видеть герою (не дальше 3-Ñ… шагов) количеÑтво ÑущеÑтв в " "нейтральном войÑке, а также их намерениÑ." msgid "Haunt" msgstr "ЗапуÑтение" msgid "" "Haunts a mine you control with Ghosts. This mine stops producing resources. " "(If I can't keep it, nobody will!)" msgstr "" "ÐаводнÑет принадлежащую игроку шахту призраками, поÑле чего работа шахты " "прекращаетÑÑ. (Так не доÑтавайÑÑ Ð¶Ðµ ты никому!)" msgid "Set Earth Guardian" msgstr "Страж земли" msgid "Sets Earth Elementals to guard a mine against enemy armies." msgstr "Призывает Земных Ñлементалей охранÑть Вашу шахту от врагов." msgid "Set Air Guardian" msgstr "Страж воздуха" msgid "Sets Air Elementals to guard a mine against enemy armies." msgstr "Призывает Воздушных Ñлементалей охранÑть Вашу шахту от врагов." msgid "Set Fire Guardian" msgstr "Страж огнÑ" msgid "Sets Fire Elementals to guard a mine against enemy armies." msgstr "Призывает Огненных Ñлементалей охранÑть Вашу шахту от врагов." msgid "Set Water Guardian" msgstr "Страж воды" msgid "Sets Water Elementals to guard a mine against enemy armies." msgstr "Призывает Водных Ñлементалей охранÑть Вашу шахту от врагов." msgid "Random Spell" msgstr "Случайное заклинание" msgid "Randomly selected spell of any level." msgstr "Определённое Ñлучайным образом заклинание любого уровнÑ." msgid "Random 1st Level Spell" msgstr "Случайное заклинание 1-го уровнÑ" msgid "Randomly selected 1st level spell." msgstr "Определённое Ñлучайным образом заклинание 1-го уровнÑ." msgid "Random 2nd Level Spell" msgstr "Случайное заклинание 2-го уровнÑ" msgid "Randomly selected 2nd level spell." msgstr "Определённое Ñлучайным образом заклинание 2-го уровнÑ." msgid "Random 3rd Level Spell" msgstr "Случайное заклинание 3-го уровнÑ" msgid "Randomly selected 3rd level spell." msgstr "Определённое Ñлучайным образом заклинание 3-го уровнÑ." msgid "Random 4th Level Spell" msgstr "Случайное заклинание 4-го уровнÑ" msgid "Randomly selected 4th level spell." msgstr "Определённое Ñлучайным образом заклинание 4-го уровнÑ." msgid "Random 5th Level Spell" msgstr "Случайное заклинание 5-го уровнÑ" msgid "Randomly selected 5th level spell." msgstr "Определённое Ñлучайным образом заклинание 5-го уровнÑ." msgid "Petrification" msgstr "Окаменение" msgid "" "Turns the affected creature into stone. A petrified creature receives half " "damage from a direct attack." msgstr "" "Превращает поражённое ÑущеÑтво в камень. Окаменевшее ÑущеÑтво получает " "половину урона от прÑмой атаки." msgid "You have no Magic Book, so you cannot cast a spell." msgstr "У Ð’Ð°Ñ Ð½ÐµÑ‚ Волшебной Книги, поÑтому Ð’Ñ‹ не можете применÑть заклинаниÑ." msgid "No spell to cast." msgstr "Ðет заклинаний Ð´Ð»Ñ Ð¿Ñ€Ð¸Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ." msgid "Your hero has %{point} spell points remaining." msgstr "У Вашего Ð³ÐµÑ€Ð¾Ñ Ð¾ÑталоÑÑŒ %{point} очков магии." msgid "View Adventure Spells" msgstr "ПроÑмотр походных заклинаний" msgid "View Combat Spells" msgstr "ПроÑмотр боевых заклинаний" msgid "View previous page" msgstr "ÐŸÑ€ÐµÐ´Ñ‹Ð´ÑƒÑ‰Ð°Ñ Ñтраница" msgid "View next page" msgstr "Ð¡Ð»ÐµÐ´ÑƒÑŽÑ‰Ð°Ñ Ñтраница" msgid "Close Spellbook" msgstr "Закрыть книгу заклинаний" msgid "View %{spell}" msgstr "ПроÑмотреть %{spell}" msgid "This spell does %{damage} points of damage." msgstr "Это заклинание наноÑит %{damage} урона." msgid "" "This spell summons\n" "%{count} %{monster}." msgstr "" "Это заклинание призывает\n" "%{count} %{monster}." msgid "This spell restores %{hp} HP." msgstr "Это заклинание воÑÑтанавливает %{hp} здоровьÑ." msgid "This spell summons %{count} %{monster} to guard the mine." msgstr "Это заклинание вызывает %{count} %{monster} Ð´Ð»Ñ Ð¾Ñ…Ñ€Ð°Ð½Ñ‹ шахты." msgid "The nearest town is %{town}." msgstr "Это заклинание перенеÑёт Ð³ÐµÑ€Ð¾Ñ Ð² %{town}" msgid "This town is occupied by your hero %{hero}." msgstr "Этот город занÑÑ‚ Вашим героем: %{hero}." msgid "" "This spell controls up to\n" "%{hp} HP." msgstr "" "Это заклинание позволÑет взÑть под контроль отрÑд,\n" "Ñуммарное здоровье которого не превышает\n" "%{hp} здоровьÑ." msgid "The ultimate artifact is really the %{name}." msgstr "Уникальный Ðртефакт - Ñто %{name}." msgid "The ultimate artifact may be found in the %{name} regions of the world." msgstr "Уникальный Ðртефакт ÑпрÑтан на %{name}е." msgid "north-west" msgstr "Ñеверо-запад" msgid "north" msgstr "Ñевер" msgid "north-east" msgstr "Ñеверо-воÑток" msgid "west" msgstr "запад" msgid "center" msgstr "Ñередин" msgid "east" msgstr "воÑток" msgid "south-west" msgstr "юго-запад" msgid "south" msgstr "юг" msgid "south-east" msgstr "юго-воÑток" msgid "The truth is out there." msgstr "ИÑтина где-то Ñ€Ñдом." msgid "The dark side is stronger." msgstr "Ð¢Ñ‘Ð¼Ð½Ð°Ñ Ñторона Ñильнее." msgid "The end of the world is near." msgstr "Конец Ñвета близок." msgid "The bones of Lord Slayer are buried in the foundation of the arena." msgstr "ОÑтанки лорда закопаны у оÑÐ½Ð¾Ð²Ð°Ð½Ð¸Ñ Ð°Ñ€ÐµÐ½Ñ‹." msgid "A Black Dragon will take out a Titan any day of the week." msgstr "Чёрный Дракон победит Титана в любой день недели." msgid "He told her: Yada yada yada... and then she said: Blah, blah, blah..." msgstr "Он Ñказал ей: Бе бе бе... а потом она Ñказала: Бла, бла, бла..." msgid "An unknown force is being resurrected..." msgstr "ВозрождаетÑÑ Ð½ÐµÐ²ÐµÐ´Ð¾Ð¼Ð°Ñ Ñила..." msgid "" "Check the newest version of the game at\n" "https://github.com/ihhub/\n" "fheroes2/releases" msgstr "" "Ðовую верÑию игры можно найти на Ñайте:\n" "https://github.com/ihhub/\n" "fheroes2/releases" #~ msgid "Terrain" #~ msgstr "Почва" #~ msgid "Castles" #~ msgstr "Замки" #, fuzzy #~ msgid "Ocean object" #~ msgstr "Объекты океана" #~ msgid "Ocean Objects" #~ msgstr "Объекты океана" #~ msgid "Grass Objects" #~ msgstr "Объекты травы" #~ msgid "Snow Objects" #~ msgstr "Объекты Ñнега" #~ msgid "Swamp Objects" #~ msgstr "Объекты болота" #~ msgid "Lava Objects" #~ msgstr "Объекты лавы" #~ msgid "Desert Objects" #~ msgstr "Объекты пуÑтыни" #~ msgid "Dirt Objects" #~ msgstr "Объекты грÑзи" #~ msgid "Beach Objects" #~ msgstr "Объекты побережьÑ" #~ msgid "Used to place objects most appropriate for use on %{terrain}." #~ msgstr "" #~ "ИÑпользуетÑÑ Ð´Ð»Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð¾Ð², наиболее подходÑщих ландшафту: " #~ "%{terrain}." #~ msgid "" #~ "Used to place\n" #~ "a town or castle." #~ msgstr "ИÑпользуетÑÑ Ð´Ð»Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð·Ð°Ð¼ÐºÐ¾Ð² или городов." #~ msgid "Used to place an artifact." #~ msgstr "ИÑпользуетÑÑ Ð´Ð»Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ð°Ñ€Ñ‚ÐµÑ„Ð°ÐºÑ‚Ð¾Ð²." #~ msgid "" #~ "Used to place\n" #~ "a resource or treasure." #~ msgstr "ИÑпользуетÑÑ Ð´Ð»Ñ Ñ€Ð°Ð·Ð¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ñокровищ или реÑурÑов." #~ msgid "%{race1} %{name1}" #~ msgstr "%{race1} %{name1}" #~ msgid "vs" #~ msgstr "против" #~ msgid "%{race2} %{name2}" #~ msgstr "%{race2} %{name2}" fheroes2-1.0.12+dfsg/files/lang/sk.po000066400000000000000000011221151456075706000172440ustar00rootroot00000000000000# Slovak translation of fheroes2 # Copyright (C) 2023 fheroes2 team # This file is distributed under the same license as the fheroes2 package. # msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-03 03:27+0000\n" "PO-Revision-Date: 2023-09-11 20:22+0200\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" "Language: sk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=( ( n == 1 ) ? 0 : ( n >= 2 && n <= 4 ) ? " "1 : 2 );\n" "X-Generator: Poedit 3.3.2\n" msgid "" "BATTLE\n" "ONLY" msgstr "" "RÃCHLY\n" "BOJ" msgid "" "NEW\n" "GAME" msgstr "" "NOVÃ\n" "HRA" msgid "" "SAVE\n" "GAME" msgstr "" "ULOŽIŤ\n" "HRU" msgid "" "LOAD\n" "GAME" msgstr "" "NA-\n" "HRAŤ\n" "HRU" msgid "INFO" msgstr "INFO" msgid "QUIT" msgstr "" "OPUSŤ\n" "HRU" msgid "CANCEL" msgstr "ZRUÅ IŤ" msgid "OKAY" msgstr "OK" msgid "smallerButton|OKAY" msgstr "OK" msgid "ACCEPT" msgstr "PRIJAŤ" msgid "DECLINE" msgstr "ODMIETNUŤ" msgid "LEARN" msgstr "UÄŒIŤ" msgid "TRADE" msgstr "VYMENIŤ" msgid "YES" msgstr "ÃNO" msgid "NO" msgstr "NIE" msgid "EXIT" msgstr "PREÄŒ" msgid "smallerButton|EXIT" msgstr "ODÃÄŽ" msgid "DISMISS" msgstr "PREPUSŤ" msgid "UPGRADE" msgstr "POVÃÅ " msgid "RESTART" msgstr "REÅ TART" msgid "HEROES" msgstr "" "HRDI-\n" "NOVIA" msgid "" "TOWNS/\n" "CASTLES" msgstr "" "MESTÃ/\n" "HRADY" msgid "S" msgstr "S" msgid "M" msgstr "M" msgid "L" msgstr "L" msgid "X-L" msgstr "X-L" msgid "ALL" msgstr "CELÃ" msgid "SELECT" msgstr "ZVOĽ" msgid "" "STANDARD\n" "GAME" msgstr "" "BEŽNÃ\n" "HRA" msgid "" "CAMPAIGN\n" "GAME" msgstr "KAMPAŇ" msgid "" "MULTI-\n" "PLAYER\n" "GAME" msgstr "" "HRA\n" "VIAC\n" "HRÃÄŒOV" msgid "CONFIG" msgstr "NASTAV" msgid "" "ORIGINAL\n" "CAMPAIGN" msgstr "" "PÔVODNÃ\n" "KAMPAŇ" msgid "" "EXPANSION\n" "CAMPAIGN" msgstr "" "KAMPAŇ\n" "EXPANZIE" msgid "HOT SEAT" msgstr "" "HORÚCE\n" "KRESLO" msgid "2 PLAYERS" msgstr "2 HRÃÄŒI" msgid "3 PLAYERS" msgstr "3 HRÃÄŒI" msgid "4 PLAYERS" msgstr "4 HRÃÄŒI" msgid "5 PLAYERS" msgstr "5 HRÃÄŒOV" msgid "6 PLAYERS" msgstr "6 HRÃÄŒOV" msgid "GIFT" msgstr "DARUJ" msgid "MAX" msgstr "MAX" msgid "DIFFICULTY" msgstr "OBTIAŽNOSŤ" msgid "VIEW INTRO" msgstr "ZOBRAZIŤ INTRO" msgid "MIN" msgstr "MIN" msgid "RESET" msgstr "" #, fuzzy msgid "START" msgstr "REÅ TART" #, fuzzy msgid "CASTLE" msgstr "" "MESTÃ/\n" "HRADY" msgid "TOWN" msgstr "" msgid "" "D\n" "I\n" "S\n" "M\n" "I\n" "S\n" "S" msgstr "" "P\n" "R\n" "E\n" "P\n" "U\n" "S\n" "Ť" msgid "" "E\n" "X\n" "I\n" "T" msgstr "" "P\n" "R\n" "E\n" "ÄŒ" msgid "" "C\n" "A\n" "M\n" "P\n" "A\n" "I\n" "G\n" "N" msgstr "" "K\n" "A\n" "M\n" "P\n" "A\n" "Ň" msgid "" "S\n" "T\n" "A\n" "N\n" "D\n" "A\n" "R\n" "D" msgstr "" "Å \n" "T\n" "A\n" "N\n" "D\n" "A\n" "R\n" "D" msgid "Warrior" msgstr "Bojovník" msgid "Builder" msgstr "Staviteľ" msgid "Explorer" msgstr "Prieskumník" msgid "None" msgstr "Žiadny" msgid "" "A few\n" "%{monster}" msgstr "" "%{monster}\n" "(málo)" msgid "" "Several\n" "%{monster}" msgstr "" "%{monster}\n" "(niekoľko)" msgid "" "A pack of\n" "%{monster}" msgstr "" "%{monster}\n" "(skupina)" msgid "" "Lots of\n" "%{monster}" msgstr "" "%{monster}\n" "(veľa)" msgid "" "A horde of\n" "%{monster}" msgstr "" "%{monster}\n" "(horda)" msgid "" "A throng of\n" "%{monster}" msgstr "" "%{monster}\n" "(zástup)" msgid "" "A swarm of\n" "%{monster}" msgstr "" "%{monster}\n" "(dav)" msgid "" "Zounds...\n" "%{monster}" msgstr "" "%{monster}\n" "(mraÄná)" msgid "" "A legion of\n" "%{monster}" msgstr "" "%{monster}\n" "(légia)" msgid "army|Few" msgstr "Málo" msgid "army|Several" msgstr "Niekoľko" msgid "army|Pack" msgstr "Skupina" msgid "army|Lots" msgstr "Veľa" msgid "army|Horde" msgstr "Horda" msgid "army|Throng" msgstr "Zástup" msgid "army|Swarm" msgstr "Dav" msgid "army|Zounds" msgstr "MraÄná" msgid "army|Legion" msgstr "Légia" msgid "All %{race} troops +1" msgstr "VÅ¡etky jednotky typu %{race} +1" msgid "Multiple" msgstr "Viaceré" msgid "Troops of %{count} alignments -%{penalty}" msgstr "Jednotky %{count} zoskupení -%{penalty}" #, fuzzy msgid "Some undead in army -1" msgstr "NemÅ•tvi v skupine -1" msgid "View %{name}" msgstr "Zobraz: %{name}" msgid "Move the %{name} " msgstr "Premiestni %{name} " msgid "Move or right click to redistribute %{name}" msgstr "Pohyb alebo pravý klik prerozdelí: %{name}" msgid "Combine %{name} armies" msgstr "Skombinuj armádu: %{name}" msgid "Exchange %{name2} with %{name}" msgstr "Vymeň: %{name2} za: %{name}" msgid "Select %{name}" msgstr "Vyber: %{name}" msgid "Cannot move last troop" msgstr "Nemožné presunúť" msgid "Move the %{name}" msgstr "Premiestni: %{name}" msgid "Set Count" msgstr "Nastav poÄet" msgid "%{name} destroys half the enemy troops!" msgid_plural "%{name} destroy half the enemy troops!" msgstr[0] "%{name} zniÄil polovicu nepriateľov!" msgstr[1] "%{name} zniÄili polovicu nepriateľov!" msgstr[2] "%{name} zniÄili polovicu nepriateľov!" msgid "%{name} has turned off the auto battle" msgstr "%{name} vypol automatizovaný boj" msgid "%{name} has turned on the auto battle" msgstr "%{name} zapol automatizovaný boj" msgid "Spell failed!" msgstr "Kúzlo zlyhalo!" msgid "" "The Sphere of Negation artifact is in effect for this battle, disabling all " "combat spells." msgstr "" "Tento boj je ovplyvnený artefaktom Guľa negácie, ktorá znemožní kúzliÅ¥." msgid "You have already cast a spell this round." msgstr "V tomto kole už bolo použité kúzlo." msgid "That spell will have no effect!" msgstr "Kúzlo nikoho neovplyvní!" msgid "You may only summon one type of elemental per combat." msgstr "SmieÅ¡ vyvolaÅ¥ len jeden typ Elementála za boj." msgid "" "There is no open space adjacent to your hero where you can summon an " "Elemental to." msgstr "Vedľa hrdinu nie je voľné miesto pre vyvolanie Elementála." msgid "" "The Moat reduces by -%{count} the defense skill of any unit and slows to " "half movement rate." msgstr "" "Priekopa znižuje obranu každého o -%{count} a spomaľuje pohyb na polovicu." msgid "Speed: %{speed}" msgstr "RýchlosÅ¥: %{speed}" msgid "Speed" msgstr "RýchlosÅ¥" msgid "Off" msgstr "Vypnuté" msgid "On" msgstr "Zapnuté" msgid "Turn Order" msgstr "Poradie" msgid "Auto Spell Casting" msgstr "Kúzla poÄas auto boja" msgid "Grid" msgstr "Mriežka" msgid "Shadow Movement" msgstr "Tieň pohybu" msgid "Shadow Cursor" msgstr "Tieň kurzora" msgid "Audio" msgstr "Zvuk" msgid "Settings" msgstr "Nastavenie" msgid "Configure" msgstr "Nastav" msgid "Hot Keys" msgstr "Skratky" msgid "Damage Info" msgstr "Info Å¡kôd" msgid "Set the speed of combat actions and animations." msgstr "Nastav rýchlosÅ¥ bojových akcií a animácií." msgid "Toggle to display the turn order during the battle." msgstr "Prepni zobrazenie poradia armády poÄas boja." msgid "" "Toggle whether or not the computer will cast spells for you when auto combat " "is on. (Note: This does not affect spell casting for computer players in any " "way, nor does it affect quick combat.)" msgstr "" "Zmeň možnosÅ¥ kúzlenia poÄas automatizovaného boja. (ViÄ: Toto nemá vplyv na " "kúzlenie hráÄov ovládaných poÄítaÄom ani na rýchly boj.)" msgid "" "Toggle the hex grid on or off. The hex grid always underlies movement, even " "if turned off. This switch only determines if the grid is visible." msgstr "" "Zmeň zobrazenie mriežky. Å esÅ¥hranná mriežka je vždy základom pohybu, aj keÄ " "je vypnutá. Toto nastavenie má vplyv len na viditeľnosÅ¥ mriežky." msgid "" "Toggle on or off shadows showing where your creatures can move and attack." msgstr "" "Zapne alebo vypne tiene ukazujúce kde sa bojovníci smú pohnúť alebo útoÄiÅ¥." msgid "" "Toggle on or off a shadow showing the current hex location of the mouse " "cursor." msgstr "Zapne alebo vypne tieň zobrazujúci aktuálnu pozíciu kurzora v mriežke." msgid "Change the audio settings of the game." msgstr "Zmeň nastavenie zvuku hry." msgid "Check and configure all the hot keys present in the game." msgstr "Prezri a nastav vÅ¡etky klávesové skratky hry." msgid "Toggle to display damage information during the battle." msgstr "Zmeň zobrazenie informácie o poÅ¡kodení poÄas boja." msgid "Exit this menu." msgstr "OdÃ­Ä z menu." msgid "Okay" msgstr "Ok" msgid "The enemy has surrendered!" msgstr "Nepriateľ sa vzdal!" msgid "Their cowardice costs them %{gold} gold." msgstr "" msgid "The enemy has fled!" msgstr "Nepriateľ utiekol!" msgid "A glorious victory!" msgstr "Slávne víťazstvo!" msgid "For valor in combat, %{name} receives %{exp} experience." msgstr "Za odvahu v boji, %{name} získava %{exp} skúseností." msgid "The cowardly %{name} flees from battle." msgstr "%{name} zbabelo uteká z boja." msgid "%{name} surrenders to the enemy, and departs in shame." msgstr "%{name} sa vzdáva nepriateľovi a hanobne odchádza." #, fuzzy msgid "Your forces suffer a bitter defeat, and %{name} abandons your cause." msgstr "Tvoje vojská sú porazené a %{name} Å¥a opúšťa." #, fuzzy msgid "Your forces suffer a bitter defeat." msgstr "Tvoje vojská sú porazené." msgid "Battlefield Casualties" msgstr "Obete boja" msgid "Attacker" msgstr "ÚtoÄník" msgid "Defender" msgstr "Obranca" #, fuzzy msgid "Click to leave the battle results." msgstr "Klikni pre výber zvoleného rozlíšenia." #, fuzzy msgid "Click to restart the battle in manual mode." msgstr "Kliknutím sa vrátiÅ¡ do hlavnej ponuky." msgid "Restart" msgstr "ReÅ¡tart" msgid "You have captured an enemy artifact!" msgstr "Získal si nepriateľov artefakt!" msgid "As you reach for the %{name}, it mysteriously disappears." msgstr "Akonáhle siahaÅ¡ po %{name}, náhle mizne." msgid "As your enemy reaches for the %{name}, it mysteriously disappears." msgstr "Akonáhle nepriateľ siaha po %{name}, náhle mizne." msgid "Necromancy!" msgstr "Nekromancia!" msgid "" "Practicing the dark arts of necromancy, you are able to raise %{count} of " "the enemy's dead to return under your service as %{monster}." msgstr "" "Praktizovaním temného umenia nekromancie dokážeÅ¡ vzkriesiÅ¥ %{count} z " "mÅ•tvych nepriateľa aby ti slúžili ako %{monster}." msgid "%{name} the %{race}" msgstr "%{race} %{name}" msgid "Captain of %{name}" msgstr "Kapitán: %{name}" msgid "Attack" msgstr "Útok" msgid "Defense" msgstr "Obrana" msgid "Spell Power" msgstr "Sila kúziel" msgid "Knowledge" msgstr "Vedomosti" msgid "Morale" msgstr "Morálka" msgid "Luck" msgstr "Šťastie" msgid "Spell Points" msgstr "Body kúziel" msgid "Hero's Options" msgstr "Ukáž hrdinu" msgid "Cast Spell" msgstr "Kúzli" msgid "Retreat" msgstr "UteÄ" msgid "Surrender" msgstr "Vzdaj sa" msgid "Cancel" msgstr "ZruÅ¡iÅ¥" msgid "Hero Screen" msgstr "Okno hrdinu" msgid "Captain's Options" msgstr "Možnosti kapitána" msgid "" "Cast a magical spell. You may only cast one spell per combat round. The " "round is reset when every creature has had a turn." msgstr "" "ÄŒarovaÅ¥ magické kúzlo. SmieÅ¡ zoslaÅ¥ len jedno kúzlo poÄas kola. Po skonÄení " "pohybu vÅ¡etkých bojovníkov je kolo obnovené." msgid "" "Retreat your hero, abandoning your creatures. Your hero will be available " "for you to recruit again, however, the hero will have only a novice hero's " "forces." msgstr "" "Ustúp s hrdinom, Äo opustí tvoje jednotky. Hrdina bude k dispozícii na " "najatie, ale bude maÅ¥ len základné jednotky." #, fuzzy msgid "" "Surrendering costs gold. However if you pay the ransom, the hero and all of " "his or her surviving creatures will be available to recruit again. The cost " "of surrender is half of the total cost of the non-temporary troops remaining " "in the army." msgstr "" "Vzdanie sa stojí zlato. AvÅ¡ak pokiaľ zaplatíš, hrdina a zostávajúce jednotky " "budú k dispozícii k nájmu." msgid "Open Hero Screen to view full information about the hero." msgstr "Otvor okno hrdinu pre zobrazenie informácií o hrdinovi." msgid "Return to the battle." msgstr "Späť do boja." msgid "Not enough gold (%{gold})" msgstr "Málo zlata (%{gold})" msgid "%{name} states:" msgstr "%{name} hovorí:" msgid "Captain of %{name} states:" msgstr "Kapitán hradu %{name} hovorí:" msgid "" "\"I will accept your surrender and grant you and your troops safe passage " "for the price of %{price} gold.\"" msgstr "" "\"Prijmem tvoju kapituláciu a poskytnem tebe a tvojim jednotkám bezpeÄný " "odchod za %{price} zlata.\"" msgid "View %{monster} info" msgstr "Zobraz info: %{monster}" msgid "Fly %{monster} here" msgstr "LeÅ¥ sem s: %{monster}" msgid "Move %{monster} here" msgstr "Premiestni: %{monster}" msgid "Shoot %{monster}" msgstr "Streľ do: %{monster}" msgid "(1 shot left)" msgid_plural "(%{count} shots left)" msgstr[0] "(ostáva posledná strela)" msgstr[1] "(ostávajú %{count} strely)" msgstr[2] "(ostáva %{count} striel)" msgid "Attack %{monster}" msgstr "Útok na: %{monster}" msgid "Turn %{turn}" msgstr "%{turn}. kolo" msgid "Teleport here" msgstr "Teleport sem" msgid "Invalid teleport destination" msgstr "Neplatný cieľ teleportu" msgid "Cast %{spell} on %{monster}" msgstr "Kúzli %{spell} na: %{monster}" msgid "Cast %{spell}" msgstr "Kúzli %{spell}" msgid "Select spell target" msgstr "UrÄi cieľ kúzla" msgid "View Ballista info" msgstr "Zobraz info o balistovi" msgid "Ballista" msgstr "Balista" msgid "Enable auto combat" msgstr "Povoľ automatizovaný boj" msgid "Allows the computer to fight out the battle for you." msgstr "Umožní poÄítaÄu vybojovaÅ¥ boj za teba." msgid "Auto Combat" msgstr "Auto boj" msgid "Customize system options" msgstr "Nastav systémové možnosti" msgid "Allows you to customize the combat screen." msgstr "Umožní ti nastaviÅ¥ obrazovku boja." msgid "System Options" msgstr "Systémové nastavenia" msgid "Skip this unit" msgstr "PreskoÄ túto jednotku" msgid "Skip" msgstr "PreskoÄ" msgid "" "Skips the current creature. The current creature ends its turn and does not " "get to go again until the next round." msgstr "" "PreskoÄ bojovníka. Terajší bojovník ukonÄí svoje kolo a Äalej nebude k " "dispozícii do ÄalÅ¡ieho kola." msgid "View Captain's options" msgstr "Zobraz možnosti kapitána" msgid "View Hero's options" msgstr "Zobraz možnosti hrdinu" msgid "View opposing Captain" msgstr "Zobraz kapitána nepriateľa" msgid "View opposing Hero" msgstr "Zobraz hrdinu nepriateľa" msgid "Hide logs" msgstr "Skry zápis" msgid "Show logs" msgstr "Ukáž zápis" msgid "Message Bar" msgstr "Panel správ" msgid "Shows the results of individual monster's actions." msgstr "Ukáže výsledky Äinností jednotlivých bojovníkov." msgid "Are you sure you want to finish the battle in auto mode?" msgstr "UrÄite chceÅ¡ dokonÄiÅ¥ boj v automatizovanom móde?" msgid "Are you sure you want to enable auto combat?" msgstr "UrÄite chceÅ¡ povoliÅ¥ automatický boj?" msgid "%{name} skip their turn." msgstr "%{name} vynecháva kolo." msgid "%{attacker} does %{damage} damage." msgid_plural "%{attacker} do %{damage} damage." msgstr[0] "%{attacker} pôsobí %{damage} Å¡kôd." msgstr[1] "%{attacker} pôsobia %{damage} Å¡kôd." msgstr[2] "%{attacker} pôsobia %{damage} Å¡kôd." msgid "1 creature perishes." msgid_plural "%{count} creatures perish." msgstr[0] "Jeden bojovník zahynul." msgstr[1] "%{count} bojovníci zahynuli." msgstr[2] "%{count} bojovníkov zahynulo." msgid "1 %{defender} perishes." msgid_plural "%{count} %{defender} perish." msgstr[0] "1 %{defender} hynie." msgstr[1] "%{count} %{defender} zahynuli." msgstr[2] "%{defender} zahynuli (%{count})." msgid "1 soul is incorporated." msgid_plural "%{count} souls are incorporated." msgstr[0] "Jedna duÅ¡a je zaÄlenená." msgstr[1] "%{count} duÅ¡e sú zaÄlenené." msgstr[2] "%{count} duší je zaÄlenených." msgid "1 %{unit} is revived." msgid_plural "%{count} %{unit} are revived." msgstr[0] "Oživenie: 1 %{unit}." msgstr[1] "Oživenie: %{count} %{unit}." msgstr[2] "Oživenie: %{unit} (%{count} )." msgid "Moved %{monster}: from [%{src}] to [%{dst}]." msgstr "Presun - %{monster}: od [%{src}] na [%{dst}]." msgid "The %{name} resist the spell!" msgstr "Jednotka %{name} odoláva kúzlu!" msgid "%{name} casts %{spell} on the %{troop}." msgstr "%{name} kúzli %{spell} na: %{troop}." msgid "%{name} casts %{spell}." msgstr "%{name} kúzli %{spell}." msgid "The %{spell} does %{damage} damage to one undead creature." msgstr "Kúzlo %{spell} pôsobí %{damage} poÅ¡kodenia nemÅ•tvej jednotke." msgid "The %{spell} does %{damage} damage to all undead creatures." msgstr "Kúzlo %{spell} pôsobí %{damage} poÅ¡kodenia vÅ¡etkým nemÅ•tvym." msgid "The %{spell} does %{damage} damage, %{count} creatures perish." msgstr "Kúzlo %{spell} pôsobí %{damage} poÅ¡kodenia, %{count} jednotiek hynie." msgid "The %{spell} does %{damage} damage." msgstr "Kúzlo %{spell} pôsobí %{damage} poÅ¡kodenia." msgid "The %{spell} does %{damage} damage to one living creature." msgstr "Kúzlo %{spell} pôsobí %{damage} poÅ¡kodenia živej jednotke." msgid "The %{spell} does %{damage} damage to all living creatures." msgstr "Kúzlo %{spell} pôsobí %{damage} poÅ¡kodenia vÅ¡etkým živým." msgid "The %{attacker}' attack blinds the %{target}!" msgstr "Útok od: %{attacker} oslepuje: %{target}!" msgid "The %{attacker}' gaze turns the %{target} to stone!" msgstr "Pohľad od: %{attacker} mení: %{target} na kameň!" msgid "The %{attacker}' curse falls upon the %{target}!" msgstr "Kliatba: %{attacker} padá na: %{target}!" msgid "The %{target} are paralyzed by the %{attacker}!" msgstr "Útok od: %{attacker} paralyzuje: %{target}!" msgid "The %{attacker} dispel all good spells on your %{target}!" msgstr "Útok od: %{attacker} ruší vÅ¡etky dobré kúzla, ktoré má: %{target}!" msgid "The %{attacker} cast %{spell} on %{target}!" msgstr "%{attacker} kúzli/a %{spell} na: %{target}!" msgid "Bad luck descends on the %{attacker}." msgstr "Smola padá na: %{attacker}." msgid "Good luck shines on the %{attacker}." msgstr "Šťastie je na strane: %{attacker}." msgid "High morale enables the %{monster} to attack again." msgstr "Vysoká morálka umožňuje Å¥ahaÅ¥ znovu pre: %{monster}." msgid "Low morale causes the %{monster} to freeze in panic." msgstr "Nízka morálka spôsobuje, že jednotka %{monster} mrzne v panike." msgid "%{tower} does %{damage} damage." msgstr "%{tower} pôsobí %{damage} poÅ¡kodenia." msgid "The mirror image is created." msgstr "Zrkadlový obraz je vytvorený." msgid "The mirror image is destroyed!" msgstr "Zrkadlový obraz je zniÄený!" msgid "Are you sure you want to interrupt the auto battle?" msgstr "UrÄite chceÅ¡ preruÅ¡iÅ¥ automatizovaný súboj?" msgid "Error" msgstr "Chyba" msgid "No spells to cast." msgstr "Žiadne kúzla na Äarovanie." msgid "Are you sure you want to retreat?" msgstr "UrÄite chceÅ¡ ustúpiÅ¥?" msgid "Retreat disabled" msgstr "Ústup znemožnený" msgid "Surrender disabled" msgstr "Vzdanie sa znemožnené" msgid "Damage: %{max}" msgstr "PoÅ¡kodenie: %{max}" msgid "Damage: %{min} - %{max}" msgstr "PoÅ¡kodenie: %{min} - %{max}" msgid "Perish: %{max}" msgstr "Zahynie: %{max}" msgid "Perish: %{min} - %{max}" msgstr "Zahynie: %{min} - %{max}" msgid "" "Through eagle-eyed observation, %{name} is able to learn the magic spell " "%{spell}." msgstr "Pozorovaním orlím zrakom, %{name} sa dokáže nauÄiÅ¥ kúzlo %{spell}." msgid "Human" msgstr "ÄŒlovek" msgid "AI" msgstr "UI" #, fuzzy msgid "Start" msgstr "ReÅ¡tart" #, fuzzy msgid "Start the battle." msgstr "Späť do boja." msgid "Exit" msgstr "PreÄ" msgid "Reset" msgstr "" msgid "Reset to default settings." msgstr "" msgid "Please select another hero." msgstr "Prosím zvoľ ÄalÅ¡ieho hrdinu." msgid "Set Attack Skill" msgstr "Zvoľ zruÄnosÅ¥ útoku" msgid "Set Defense Skill" msgstr "Zvoľ zruÄnosÅ¥ obrany" msgid "Set Power Skill" msgstr "Zvoľ zruÄnosÅ¥ moci" msgid "Set Knowledge Skill" msgstr "Zvoľ zruÄnosÅ¥ vedomostí" #, fuzzy msgid "%{race1} %{name1} vs %{race2} %{name2}" msgstr "%{name1} a %{name2} sa stretávajú" msgid "Monsters" msgstr "Bojovníci" msgid "N/A" msgstr "nie je" msgid "Left Turret" msgstr "Ľavá veža" msgid "Right Turret" msgstr "Pravá veža" msgid "The %{name} fires with the strength of %{count} Archers" msgstr "%{name} strieľa so silou %{count} lukostrelcov" msgid "each with a +%{attack} bonus to their attack skill." msgstr "s bonusom +%{attack} k ich zruÄnosti útoku." msgid "The %{name} is destroyed." msgstr "%{name} je zniÄený/á." msgid "%{count} %{name} rises from the dead!" msgid_plural "%{count} %{name} rise from the dead!" msgstr[0] "%{count} %{name} vstáva z mÅ•tvych!" msgstr[1] "%{count} %{name} vstávajú z mÅ•tvych!" msgstr[2] "%{count} %{name} vstáva z mÅ•tvych!" msgid "Dwarven Alliance" msgstr "Aliancia trpaslíkov" msgid "Sorceress Guild" msgstr "Cech Äarodejníc" msgid "Necromancer Guild" msgstr "Cech nekromantov" msgid "Ogre Alliance" msgstr "Cech zlobrov" msgid "Dwarfbane" msgstr "TrpasliÄí zmar" msgid "Dragon Alliance" msgstr "DraÄia aliancia" msgid "Elven Alliance" msgstr "Aliancia elfov" msgid "Kraeger defeated" msgstr "Kraeger porazený" msgid "Wayward Son" msgstr "Spurný syn" msgid "Uncle Ivan" msgstr "Strýko Ivan" msgid "Annexation" msgstr "Anexia" msgid "Force of Arms" msgstr "Sila zbraní" msgid "Save the Dwarves" msgstr "Zachráň trpaslíkov" msgid "Carator Mines" msgstr "Karátorské bane" msgid "Turning Point" msgstr "Zvrat" msgid "scenarioName|Defender" msgstr "Obranca" msgid "Corlagon's Defense" msgstr "Corlagonova obrana" msgid "The Crown" msgstr "Koruna" msgid "The Gauntlet" msgstr "Výzva" msgid "Betrayal" msgstr "Zrada" msgid "Final Justice" msgstr "KoneÄná spravodlivosÅ¥" msgid "" "Roland needs you to defeat the lords near his castle to begin his war of " "rebellion against his brother. They are not allied with each other, so they " "will spend most of their time fighting with one another. Victory is yours " "when you have defeated all of their castles and heroes." msgstr "" "Roland potrebuje, aby si porazil pánov v blízkosti jeho hradu a tým zaÄal " "povstaleckú vojnu proti svojmu bratovi. Nie sú navzájom spojencami, takže " "väÄÅ¡inu Äasu strávia bojom medzi sebou. Víťazstvo je tvoje, keÄ porazíš " "vÅ¡etky ich hrady a hrdinov." msgid "" "The local lords refuse to swear allegiance to Roland, and must be subdued. " "They are wealthy and powerful, so be prepared for a tough fight. Capture all " "enemy castles to win." msgstr "" "Miestni lordi odmietajú prisahaÅ¥ vernosÅ¥ Rolandovi a musia byÅ¥ pokorení. Sú " "bohatí a mocní, takže sa priprav na tvrdý boj. Pre víťazstvo ovládni vÅ¡etky " "nepriateľské hrady." msgid "" "Your task is to defend the Dwarves against Archibald's forces. Capture all " "of the enemy towns and castles to win, and be sure not to lose all of the " "dwarf towns at once, or the enemy will have won." msgstr "" "Tvojou úlohou je ubrániÅ¥ trpaslíkov proti Archibaldovým silám. ObsaÄ vÅ¡etky " "nepriateľské mestá a hrady pre výhru, no uisti sa, že nestratíš vÅ¡etky " "trpasliÄie mestá naraz, inak nepriateľ vyhrá." msgid "" "You will face four allied enemies in a straightforward fight for resource " "and treasure. Capture all of the enemy castles for victory." msgstr "" "V priamom boji o zdroje a poklady budeÅ¡ ÄeliÅ¥ Å¡tyrom nepriateľom v " "spojenectve. Pre víťazstvo ovládni vÅ¡etky nepriateľské hrady." msgid "" "Your enemies are allied against you and start close by, so be ready to come " "out fighting. You will need to own all four castles in this small valley to " "win." msgstr "" "Tvoji nepriatelia sú proti tebe v spojení a zaÄínajú blízko, takže sa " "priprav vyraziÅ¥ do boja. Ak chceÅ¡ vyhraÅ¥, budeÅ¡ musieÅ¥ vlastniÅ¥ vÅ¡etky Å¡tyri " "hrady v tomto malom údolí." msgid "" "The Sorceress' guild of Noraston has requested Roland's aid against an " "attack from Archibald's allies. Capture all of the enemy castles to win, and " "don't lose Noraston, or you'll lose the scenario. (Hint: There is an enemy " "castle on an island in the ocean.)" msgstr "" "Cech Äarodejníc z Norastonu požiadal Rolanda o pomoc proti útoku od " "Archibaldových spojencov. ObsaÄ vÅ¡etky nepriateľské hrady pre výhru a " "nestraÅ¥ Noraston, inak prehráš boj. (Tip: Na ostrove v oceáne je " "nepriateľský hrad.)" msgid "" "Gather as large an army as possible and capture the enemy castle within 8 " "weeks. You are opposed by only one enemy, but must travel a long way to get " "to the enemy castle. Any troops you have in your army at the end of this " "scenario will be with you in the final battle." msgstr "" "Zhromaždi Äo najväÄÅ¡iu armádu a doby nepriateľský hrad do 8 týždňov. Proti " "tebe stojí iba jeden nepriateľ, ale musíš prejsÅ¥ dlhú cestu kým dorazíš do " "nepriateľského hradu. VÅ¡etky jednotky, ktoré máš vo svojej armáde na konci " "tohto scenára, budú s tebou v závereÄnom boji." msgid "" "Find the Crown before Archibald's heroes find it. Roland will need the Crown " "for the final battle against Archibald." msgstr "" "Nájdi Korunu skôr, ako ju nájdu Archibaldovi hrdinovia. Roland bude " "potrebovaÅ¥ Korunu na posledný boj proti Archibaldovi." msgid "" "Three allied enemies stand before you and victory, including Lord Corlagon. " "Roland is in a castle to the northwest, and you will lose if he falls to the " "enemy. Remember that capturing Lord Corlagon will ensure that he will not " "fight against you in the final scenario." msgstr "" "Pred tebou a víťazstvom stoja traja nepriatelia v spojenectve, vrátane lorda " "Corlagona. Roland je v hrade na severozápade a prehráš, ak padne do rúk " "nepriateľa. Pamätaj, že zajatie lorda Corlagona zabezpeÄí, že v koneÄnom " "scenári nebude bojovaÅ¥ proti tebe." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Archibald to end the war!" msgstr "" "Toto je posledný boj. Ty aj tvoj nepriateľ ste po zuby ozbrojení a vÅ¡etci sú " "proti tebe v spojenectve. Zajmi Archibalda a ukonÄi vojnu!" msgid "" "Switching sides leaves you with three castles against the enemy's one. This " "battle will be the easiest one you will face for the rest of the war..." "traitor." msgstr "" "Po zmene strany máš tri hrady proti jednému nepriateľskému. Táto bitka bude " "tou najľahÅ¡ou, ktorej budeÅ¡ do konca vojny ÄeliÅ¥... zradca." msgid "Barbarian Wars" msgstr "Barbarské vojny" msgid "First Blood" msgstr "Prvá krv" msgid "Necromancers" msgstr "Nekromanti" msgid "Slay the Dwarves" msgstr "Pobi trpaslíkov" msgid "Country Lords" msgstr "Vládcovia krajov" msgid "Dragon Master" msgstr "DraÄí majster" msgid "Rebellion" msgstr "Vzbura" msgid "Apocalypse" msgstr "Apokalypsa" msgid "Greater Glory" msgstr "VäÄÅ¡ia sláva" msgid "" "King Archibald requires you to defeat the three enemies in this region. They " "are not allied with one another, so they will spend most of their energy " "fighting amongst themselves. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Kráľ Archibald od teba vyžaduje víťazstvo nad tromi nepriateľmi v tejto " "oblasti. Nie sú medzi sebou spojencami, takže väÄÅ¡inu energie vynaložia na " "boj medzi sebou. Vyhráš, keÄ budeÅ¡ vlastniÅ¥ vÅ¡etky nepriateľské hrady a " "nezostane už žiadny hrdina, s ktorým by si mohol bojovaÅ¥." msgid "" "You must unify the barbarian tribes of the north by conquering them. As in " "the previous mission, the enemy is not allied against you, but they have " "more resources at their disposal. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Musíš zjednotiÅ¥ barbarské kmene na severe ich dobytím. Podobne ako v " "predchádzajúcej misii, nepriateľ nie je proti tebe v spojenectve, ale má k " "dispozícii viac zdrojov. Vyhráš, keÄ budeÅ¡ vlastniÅ¥ vÅ¡etky nepriateľské " "hrady a nezostane už žiadny hrdina, ktorý by mohol bojovaÅ¥." msgid "" "Do-gooder wizards have taken the Necromancers' castle. You must retake it to " "achieve victory. Remember that while you start with a powerful army, you " "have no castle and must take one within 7 days, or lose this battle. (Hint: " "The nearest castle is to the southeast.)" msgstr "" "Mágovia dobrodinci obsadili hrad Nekromantov. Musíš ho znovu dobyÅ¥, na " "dosiahnutie víťazstva. Pamätaj, že hoci zaÄínaÅ¡ so silnou armádou, nemáš " "žiadny hrad a musíš nejaký dobyÅ¥ do 7 dní, inak túto bitku prehráš. (Tip: " "Najbližší hrad je na juhovýchode.)" msgid "" "The dwarves need conquering before they can interfere in King Archibald's " "plans. Roland's forces have more than one hero and many towns to start with, " "so be ready for attack from multiple directions. You must capture all of the " "enemy towns and castles to claim victory." msgstr "" "Treba dobyÅ¥ trpaslíkov, kým budú môcÅ¥ zasiahnuÅ¥ do plánov kráľa Archibalda. " "Rolandove sily majú na zaÄiatok viac ako jedného hrdinu a veľa miest, takže " "buÄ v strehu a oÄakávaj útok z viacerých strán. Musíš dobyÅ¥ vÅ¡etky " "nepriateľské mestá a hrady pre zisk víťazstva." msgid "" "You must put down a peasant revolt led by Roland's forces. All are allied " "against you, but you have Lord Corlagon, an experienced hero, to help you. " "Capture all enemy castles to win." msgstr "" "Musíte potlaÄiÅ¥ roľnícku vzburu vedenú Rolandovými silami. VÅ¡etci sú proti " "tebe v spojení, ale máš na pomoc skúseného hrdinu, lorda Corlagona. Pre " "víťazstvo ovládni vÅ¡etky nepriateľské hrady." msgid "" "There are two enemies allied against you in this mission. Both are well " "armed and seek to evict you from their island. Avoid them and capture Dragon " "City to win." msgstr "" "V tejto misii sú proti tebe v spojení dvaja nepriatelia. Obaja sú dobre " "vyzbrojení a snažia sa Å¥a vyhnaÅ¥ zo svojho ostrova. Vyhni sa im a doby " "DraÄie mesto, aby bola výhra dosiahnutá." msgid "" "Your orders are to conquer the country lords that have sworn to serve " "Roland. All of the enemy castles are unified against you. Since you start " "without a castle, you must hurry to capture one before the end of the week. " "Capture all enemy castles for victory." msgstr "" "Máš splniÅ¥ rozkaz dobyÅ¥ vládcov krajiny, ktorí prisahali slúžiÅ¥ Rolandovi. " "VÅ¡etky nepriateľské hrady sú zjednotené proti tebe. KeÄže zaÄínaÅ¡ bez hradu, " "musíš sa poponáhľaÅ¥ s dobytím jedného z nich do konca týždňa. ObsaÄ vÅ¡etky " "nepriateľské hrady pre víťazstvo." msgid "" "Find the Crown before Roland's heroes find it. Archibald will need the Crown " "for the final battle against Roland." msgstr "" "Nájdi Korunu skôr, ako ju nájdu Rolandovi hrdinovia. Archibald bude " "potrebovaÅ¥ Korunu na posledný boj proti Rolandovi." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Roland to win the war, and be sure " "not to lose Archibald in the fight!" msgstr "" "Toto je posledný boj. Ty aj tvoj nepriateľ ste po zuby ozbrojení a vÅ¡etci sú " "proti tebe v spojenectve. Zajmi Rolanda, aby vojna bola vyhratá, a uisti sa, " "že nestratíš Archibalda v boji!" msgid "Arrow's Flight" msgstr "Let šípu" msgid "Island of Chaos" msgstr "Ostrov chaosu" msgid "The Abyss" msgstr "PriepasÅ¥" msgid "Uprising" msgstr "Povstanie" msgid "Aurora Borealis" msgstr "Polárna žiara" msgid "Betrayal's End" msgstr "Koniec zrady" msgid "Corruption's Heart" msgstr "Srdce korupcie" msgid "The Giant's Pass" msgstr "Obrov priesmyk" msgid "" "Subdue the unruly local lords in order to provide the Empire with facilities " "to operate in this region." msgstr "" "Podmaň si neposluÅ¡ných miestnych lordov, aby bolo Impériu poskytnuté zázemie " "na pôsobenie v tomto regióne." msgid "" "Eliminate all opposition in this area. Then the first piece of the artifact " "will be yours." msgstr "" "Odstráň protivníkov v tejto oblasti. Potom bude prvý kus artefaktu tvoj." msgid "" "The sorceresses to the northeast are rebelling! For the good of the empire " "you must quash their feeble uprising on your way to the mountains." msgstr "" "ÄŒarodejnice na severovýchode sa búria! Pre dobro Impéria musíš potlaÄiÅ¥ ich " "slabé povstanie na tvojej ceste do hôr." msgid "" "Having prepared for your arrival, Kraeger has arranged for a force of " "necromancers to thwart your quest. You must capture the castle of Scabsdale " "before the first day of the third week, or the Necromancers will be too " "strong for you." msgstr "" "S prípravou na tvoj príchod Kraeger zariadil, aby nekromanti prekazili tvoju " "výpravu. Musíš dobyÅ¥ hrad Scabsdale pred prvým dňom tretieho týždňa, inak " "budú na teba nekromanti príliÅ¡ silní." msgid "" "The barbarian despot in this area is, as yet, ignorant of your presence. " "Quickly, build up your forces before you are discovered and attacked! Secure " "the region by subduing all enemy forces." msgstr "" "Barbarský despota v tejto oblasti zatiaľ neuvažuje o tvojej prítomnosti. " "Rýchlo vybuduj svoje sily skôr, ako Å¥a objavia a zaútoÄia! ZabezpeÄ región " "podmanením vÅ¡etkých nepriateľských síl." msgid "" "The Empire is weak in this region. You will be unable to completely subdue " "all forces in this area, so take what you can before reprisal strikes. " "Remember, your true goal is to claim the Helmet of Anduran." msgstr "" "Impérium je v tomto regióne slabé. NebudeÅ¡ maÅ¥ schopnosÅ¥ úplne si podmaniÅ¥ " "vÅ¡etky sily v tejto oblasti, takže si vezmi, Äo môžeÅ¡, kým zaútoÄí odveta. " "Pamätaj, že tvojim skutoÄným cieľom je získaÅ¥ Anduranovu prilbu." msgid "For the good of the Empire, eliminate Kraeger." msgstr "Pre dobro Impéria zlikviduj Kraegera." msgid "" "At last, you have the opportunity and the facilities to rid the Empire of " "the necromancer's evil. Eradicate them completely, and you will be sung as a " "hero for all time." msgstr "" "KoneÄne máš príležitosÅ¥ a možnosti zbaviÅ¥ Impérium nekromantovho zla. Úplne " "ho vyhub a budeÅ¡ navždy ospevovaný ako hrdina." msgid "Border Towns" msgstr "PohraniÄné mestá" msgid "Conquer and Unify" msgstr "DobyÅ¥ a zjednotiÅ¥" msgid "Crazy Uncle Ivan" msgstr "Bláznivý strýko Ivan" msgid "The Wayward Son" msgstr "Zblúdilý syn" msgid "Ivory Gates" msgstr "Slonovinové Brány" msgid "The Elven Lands" msgstr "Krajiny elfov" msgid "The Epic Battle" msgstr "Epický boj" msgid "The Southern War" msgstr "Južná vojna" msgid "" "Conquer and unite all the enemy tribes. Don't lose the hero Jarkonas, the " "forefather of all descendants." msgstr "" "Doby a zjednoÅ¥ vÅ¡etky nepriateľské kmene. NestraÅ¥ hrdinu Jarkonasa, praotca " "vÅ¡etkých potomkov." msgid "" "Your rival, the Kingdom of Harondale, is attacking weak towns on your " "border! Recover from their first strike and crush them completely!" msgstr "" "Tvoj súper, kráľovstvo Harondale, útoÄí na slabé mestá na tvojej hranici! " "Zotav sa z ich prvého úderu a rozdrv ich úplne!" msgid "" "Find your wayward son Joseph who is rumored to be living in the desolate " "lands. Do it before the first day of the third month or it will be of no " "help to your family." msgstr "" "Nájdi svojho neposluÅ¡ného syna Jozefa, o ktorom sa hovorí, že žije v pustých " "krajinách. Urob tak pred prvým dňom tretieho mesiaca, inak to tvojej rodine " "nepomôže." msgid "" "Rescue your crazy uncle Ivan. Find him before the first day of the fourth " "month or it will be no help to your kingdom." msgstr "" "Zachráň svojho bláznivého strýka Ivana. Nájdi ho pred prvým dňom Å¡tvrtého " "mesiaca, inak to tvojmu kráľovstvu nepomôže." msgid "" "Destroy the barbarians who are attacking the southern border of your " "kingdom! Recover your fallen towns, and then invade the jungle kingdom. " "Leave no enemy standing." msgstr "" "ZniÄ barbarov, ktorí útoÄia na južnú hranicu tvojho kráľovstva! Obnov svoje " "padlé mestá a potom prepadni kráľovstvo džungle. Nenechaj žiadneho " "nepriateľa nažive." msgid "Retake the castle of Ivory Gates, which has fallen due to treachery." msgstr "Získaj späť hrad Ivory Gates, ktorý padol v dôsledku zrady." msgid "" "Gain the favor of the elves. They will not allow trees to be chopped down, " "so they will send you wood every 2 weeks. You must complete your mission " "before the first day of the seventh month, or the kingdom will surely fall." msgstr "" "Získaj si priazeň elfov. Stromy rúbaÅ¥ nedovolia, preto ti poÅ¡lú drevo každé " "2 týždne. Svoju misiu musíš dokonÄiÅ¥ pred prvým dňom siedmeho mesiaca, inak " "kráľovstvo urÄite padne." msgid "" "This is the final battle against your rival kingdom of Harondale. Eliminate " "everyone, and don't lose the hero Jarkonas VI." msgstr "" "Toto je posledný boj proti tvojmu súperiacemu kráľovstvu Harondale. " "ZneÅ¡kodni vÅ¡etkých a nestraÅ¥ hrdinu Jarkonasa VI." msgid "Fount of Wizardry" msgstr "Prameň Äarodejníctva" msgid "Power's End" msgstr "Koniec moci" msgid "The Eternal Scrolls" msgstr "VeÄné zvitky" msgid "The Shrouded Isles" msgstr "Zahalené ostrovy" msgid "" "Your mission is to vanquish the warring mages in the magical Shrouded Isles. " "The completion of this task will give you a fighting chance against your " "rivals." msgstr "" "Tvojou úlohou je poraziÅ¥ bojujúcich mágov na Äarovných Zahalených ostrovoch. " "Splnenie tejto úlohy ti dá Å¡ancu bojovaÅ¥ proti tvojim súperom." msgid "" "The location of the great library has been discovered! You must make your " "way to it, and reclaim the city of Chronos in which it lies." msgstr "" "Poloha veľkej knižnice bola odhalená! Musíš sa k nej dostaÅ¥ a získaÅ¥ mesto " "Chronos, v ktorom leží." #, fuzzy msgid "" "Find the Orb of negation, which is said to be buried in this land. There are " "clues inscribed on stone obelisks which will help lead you to your prize. " "Find the Orb before the first day of the sixth month, or your rivals will " "surely have gotten to the fount before you." msgstr "" "Nájdi Orb negácie, ktorý je údajne zakopaný v tejto krajine. Na kamenných " "obeliskoch sú napísané stopy, ktoré Å¥a dovedú k tvojej cene. Nájdi orb pred " "prvým dňom Å¡iesteho mesiaca, inak sa tvoji súperi už urÄite dostali k nemu " "pred tebou." msgid "" "You must take control of the castle of Magic, where the fount of wizardry " "lies. Do this and your victory will be supreme." msgstr "" "Musíš prevziaÅ¥ kontrolu nad hradom mágie, kde leží prameň Äarodejníctva. " "Urob tak a tvoje víťazstvo bude mimoriadne." msgid "Blood is Thicker" msgstr "Krv je hustejÅ¡ia" msgid "King and Country" msgstr "Kráľ a krajina" msgid "Pirate Isles" msgstr "Pirátske ostrovy" msgid "Stranded" msgstr "Uviaznutý" msgid "" "Capture the town on the island off the southeast shore in order to construct " "a boat and travel back towards the mainland. Do not lose the hero Gallavant." msgstr "" "ObsaÄ mesto na ostrove pri juhovýchodnom pobreží za úÄelom postaviÅ¥ loÄ a " "doplaviÅ¥ sa späť na pevninu. NestraÅ¥ hrdinu Gallavanta." msgid "" "Find and defeat Martine, the pirate leader, who resides in Pirates Cove. Do " "not lose Gallavant or your quest will be over." msgstr "" "Nájdi a poraz Martine, pirátsku vodkyňu, ktorá sídli v Zátoke pirátov. " "NestraÅ¥ Gallavanta lebo tvoje pátranie sa skonÄí." msgid "" "Eliminate all the other forces who oppose the rule of Lord Alberon. " "Gallavant must not die." msgstr "" "Zlikviduj vÅ¡etky ostatné sily, ktoré sú proti vláde lorda Alberona. " "Gallavant nesmie padnúť." msgid "" "Overthrow the entrenched monarchy of Lord Alberon, and claim all the land in " "your name. Gallavant must not die." msgstr "" "Zvrhni zakorenenú monarchiu lorda Alberona a získaj vÅ¡etku zem vo svojom " "mene. Gallavant nesmie padnúť." msgid " bane" msgstr " zmar" msgid " alliance" msgstr " aliancia" msgid "Carry-over forces" msgstr "Prenosné jednotky" msgid " bonus" msgstr " bonus" msgid " defeated" msgstr " porazený" msgid " will always run away from your army." msgstr " vždy uteÄú pred tvojou armádou." msgid " will be willing to join your army." msgstr " sa ochotne pridajú k tvojej armáde." msgid "\"%{artifact}\" artifact will be carried over the scenario." msgstr "Artefakt \"%{artifact}\" sa prenesie cez scenár." msgid "The army will be carried over the scenario." msgstr "Armáda sa prenesie cez scenár." msgid "The kingdom will have +%{count} %{resource} each day." msgstr "Kráľovstvo bude každý deň maÅ¥ zvýšené zásoby: +%{count} %{resource}." msgid "\"%{spell}\" spell will be carried over the scenario." msgstr "Kúzlo \"%{spell}\" sa prenesie cez scenár." msgid "%{hero} can be hired in the scenario." msgstr "%{hero} bude k dispozícii v tomto scenári." msgid "" "%{hero} has been defeated and will not appear in the subsequent scenarios." msgstr "%{hero} bol porazený a neobjaví sa v nasledujúcich scenároch." msgid "The dwarves recognize their allies and gladly join your forces." msgstr "" "Trpaslíci spoznávajú svojich spojencov a radi sa pridajú k tvojim silám." msgid "The ogres recognize you as the Dwarfbane and lumber over to join you." msgstr "" "Zlobri Å¥a poznajú ako TrpasliÄí zmar a pristupujú aby sa k tebe pridali." msgid "" "The dragons, snarling and growling, agree to join forces with you, their " "'Ally'." msgstr "" "VrÄiaci draci súhlasia s pridaním sa k tvojim jednotkám, ich 'Spojencom'." msgid "" "As you approach the group of elves, their leader calls them all to " "attention. He shouts to them, \"Who of you is brave enough to join this " "fearless ally of ours?\" The group explodes with cheers as they run to join " "your ranks." msgstr "" "KeÄ sa blížiÅ¡ k skupine elfov, ich vodca ich vÅ¡etkých zvolá do pozoru. " "ZakriÄí na nich: \"Kto z vás je dosÅ¥ odvážny, aby sa pridal k tomuto nášmu " "nebojácnemu spojencovi?\" Skupina prepuká v jasot, priÄom sa rozbehne, aby " "sa pridala do tvojich radov." msgid "" "The dwarves hail you, \"Any friend of Roland is a friend of ours. You may " "pass.\"" msgstr "" "Trpaslíci Å¥a zdravia: \"Každý Rolandov priateľ je náš priateľ. SmieÅ¡ prejsÅ¥." "\"" msgid "" "The ogres give you a grunt of recognition, \"Archibald's allies may pass.\"" msgstr "" "Zlobri na teba uznanlivo zavrÄia: \"Archibaldovi spojenci môžu prejsÅ¥.\"" msgid "" "The dragons see you and call out. \"Our alliance with Archibald compels us " "to join you. Unfortunately you have no room. A pity!\" They quickly scatter." msgstr "" "Draci Å¥a uvidia a ozvú sa. \"NaÅ¡e spojenectvo s Archibaldom nás núti pridaÅ¥ " "sa k vám. Nanešťastie nemáte miesto. Å koda!\" Rýchlo sa rozptýlia." msgid "" "The elves stand at attention as you approach. Their leader calls to you and " "says, \"Let us not impede your progress, ally! Move on, and may victory be " "yours.\"" msgstr "" "Elfovia sa postavia do pozoru, keÄ sa blížiÅ¡. Ich vodca na teba zavolá a " "povie: \"Nebudeme ti brániÅ¥ v postupe, spojenec! PokraÄuj a nech je " "víťazstvo tvoje.\"" msgid "\"The Dwarfbane!!!!, run for your lives.\"" msgstr "\"TrpasliÄí zmar! Utekajte o život!\"" msgid "campaignBonus|Animate Dead" msgstr "Animácia mÅ•tvych" msgid "campaignBonus|Chain Lightning" msgstr "ReÅ¥azový blesk" msgid "campaignBonus|Fireblast" msgstr "Výbuch ohňa" msgid "campaignBonus|Mass Curse" msgstr "Hromadná kliatba" msgid "campaignBonus|Mass Haste" msgstr "Hromadný zhon" msgid "campaignBonus|Mirror Image" msgstr "Zrkadlový obraz" msgid "campaignBonus|Resurrect" msgstr "Vzkriesenie" msgid "campaignBonus|Steelskin" msgstr "Oceľová koža" msgid "campaignBonus|Summon Earth" msgstr "Vyvolaj zem" msgid "campaignBonus|View Heroes" msgstr "Zobraz hrdinov" msgid "campaignBonus|Ballista" msgstr "Balista" msgid "campaignBonus|Black Pearl" msgstr "ÄŒierna perla" msgid "campaignBonus|Caster's Bracelet" msgstr "ÄŒarodejov náramok" msgid "campaignBonus|Defender Helm" msgstr "Helma obrancu" msgid "campaignBonus|Breastplate" msgstr "Náprsné brnenie" msgid "campaignBonus|Dragon Sword" msgstr "DraÄí meÄ" msgid "campaignBonus|Fizbin Medal" msgstr "Fizbinova medaila" msgid "campaignBonus|Foremost Scroll" msgstr "Najprednejší zvitok" msgid "campaignBonus|Gauntlets" msgstr "Rukavice" msgid "campaignBonus|Hideous Mask" msgstr "Hnusná maska" msgid "campaignBonus|Mage's Ring" msgstr "Prsteň mága" msgid "campaignBonus|Major Scroll" msgstr "Hlavný zvitok" msgid "campaignBonus|Medal of Honor" msgstr "Medaila cti" msgid "campaignBonus|Medal of Valor" msgstr "Medaila za statoÄnosÅ¥" msgid "campaignBonus|Minor Scroll" msgstr "Menší zvitok" msgid "campaignBonus|Nomad Boots" msgstr "Nomádske Äižmy" msgid "campaignBonus|Power Axe" msgstr "Sekera moci" msgid "campaignBonus|Spiked Shield" msgstr "Ostnatý Å¡tít" msgid "campaignBonus|Stealth Shield" msgstr "Å tít prikrádania" msgid "campaignBonus|Tax Lien" msgstr "Daňové záložné právo" msgid "campaignBonus|Thunder Mace" msgstr "Palcát hromu" msgid "campaignBonus|Traveler's Boots" msgstr "Cestovateľove topánky" msgid "campaignBonus|White Pearl" msgstr "Biela perla" msgid "campaignBonus|Basic Archery" msgstr "Základná lukostreľba" msgid "campaignBonus|Advanced Archery" msgstr "PokroÄilá lukostreľba" msgid "campaignBonus|Expert Archery" msgstr "Expert lukostreľby" msgid "campaignBonus|Basic Ballistics" msgstr "Základná balistika" msgid "campaignBonus|Advanced Ballistics" msgstr "PokroÄilá balistika" msgid "campaignBonus|Expert Ballistics" msgstr "Expert balistiky" msgid "campaignBonus|Basic Diplomacy" msgstr "Základná diplomacia" msgid "campaignBonus|Advanced Diplomacy" msgstr "PokroÄilá diplomacia" msgid "campaignBonus|Expert Diplomacy" msgstr "Expert diplomacie" msgid "campaignBonus|Basic Eagle Eye" msgstr "Základný orlí zrak" msgid "campaignBonus|Advanced Eagle Eye" msgstr "PokroÄilý orlí zrak" msgid "campaignBonus|Expert Eagle Eye" msgstr "Expert orlieho zraku" msgid "campaignBonus|Basic Estates" msgstr "Základné statky" msgid "campaignBonus|Advanced Estates" msgstr "PokroÄilé statky" msgid "campaignBonus|Expert Estates" msgstr "Expert statkov" msgid "campaignBonus|Basic Leadership" msgstr "Základné vedenie" msgid "campaignBonus|Advanced Leadership" msgstr "PokroÄilé vedenie" msgid "campaignBonus|Expert Leadership" msgstr "Expert vedenia" msgid "campaignBonus|Basic Logistics" msgstr "Základná logistika" msgid "campaignBonus|Advanced Logistics" msgstr "PokroÄilá logistika" msgid "campaignBonus|Expert Logistics" msgstr "Expert logistiky" msgid "campaignBonus|Basic Luck" msgstr "Základné šťastie" msgid "campaignBonus|Advanced Luck" msgstr "PokroÄilé šťastie" msgid "campaignBonus|Expert Luck" msgstr "Expert šťastia" msgid "campaignBonus|Basic Mysticism" msgstr "Základná mystika" msgid "campaignBonus|Advanced Mysticism" msgstr "PokroÄilá mystika" msgid "campaignBonus|Expert Mysticism" msgstr "Expert mystiky" msgid "campaignBonus|Basic Navigation" msgstr "Základná navigácia" msgid "campaignBonus|Advanced Navigation" msgstr "PokroÄilá navigácia" msgid "campaignBonus|Expert Navigation" msgstr "Expert navigácie" msgid "campaignBonus|Basic Necromancy" msgstr "Základná nekromancia" msgid "campaignBonus|Advanced Necromancy" msgstr "PokroÄilá nekromancia" msgid "campaignBonus|Expert Necromancy" msgstr "Expert nekromancie" msgid "campaignBonus|Basic Pathfinding" msgstr "Základné hľadanie cesty" msgid "campaignBonus|Advanced Pathfinding" msgstr "PokroÄilé hľadanie cesty" msgid "campaignBonus|Expert Pathfinding" msgstr "Expert hľadania cesty" msgid "campaignBonus|Basic Scouting" msgstr "Základný prieskum" msgid "campaignBonus|Advanced Scouting" msgstr "PokroÄilý prieskum" msgid "campaignBonus|Expert Scouting" msgstr "Expert prieskumu" msgid "campaignBonus|Basic Wisdom" msgstr "Základná múdrosÅ¥" msgid "campaignBonus|Advanced Wisdom" msgstr "PokroÄilá múdrosÅ¥" msgid "campaignBonus|Expert Wisdom" msgstr "Expert múdrosti" msgid "" "The main hero will have \"%{artifact}\" artifact at the start of the " "scenario." msgstr "Hlavný hrdina bude maÅ¥ artefakt \"%{artifact}\" na zaÄiatku scenára." msgid "" "The kingdom will receive %{amount} additional %{resource} at the start of " "the scenario." msgstr "" "Kráľovstvo dostane na zaÄiatku scenára suroviny navyÅ¡e: + %{amount} " "%{resource}." msgid "" "The kingdom will have %{amount} less %{resource} at the start of the " "scenario." msgstr "" "Kráľovstvu budú na zaÄiatku scenára odobrané suroviny: - %{amount} " "%{resource}." msgid "" "The main hero will have %{count} %{monster} at the start of the scenario." msgstr "" "Hlavný hrdina obdrží jednotky navyÅ¡e na zaÄiatku scenára: + %{count} " "%{monster}" msgid "" "The main hero will have \"%{spell}\" spell at the start of the scenario." msgstr "Hlavný hrdina bude ovládaÅ¥ kúzlo \"%{spell}\" na zaÄiatku scenára." msgid "The starting race of the scenario will be %{race}." msgstr "ZaÄiatoÄnou rasou scenára bude %{race}." msgid "" "The main hero will have additional %{count} %{skill} at the start of the " "scenario." msgstr "" "Hlavný hrdina obdrží skúsenosÅ¥ navyÅ¡e na zaÄiatku scenára: + %{count} " "%{skill}" msgid "The main hero will have %{skill} at the start of the scenario." msgstr "Hlavný hrdina bude ovládaÅ¥ %{skill} na zaÄiatku scenára." msgid "Roland" msgstr "Roland" msgid "Archibald" msgstr "Archibald" #, fuzzy msgid "Price of Loyalty" msgstr "Cena vernosti" msgid "Voyage Home" msgstr "Plavba domov" msgid "Wizard's Isle" msgstr "ÄŒarodejov ostrov" msgid "Descendants" msgstr "Potomkovia" msgid "The %{building} produces %{monster}." msgstr "%{building} produkuje: %{monster}." msgid "Requires:" msgstr "Potrebuje:" msgid "Exit this menu without doing anything." msgstr "Opusti túto ponuku bez vykonania akcie." msgid "Cannot build. You have already built here today." msgstr "Nedá sa stavaÅ¥. Dnes tu už bola budova postavená." msgid "For this action it is necessary to build a castle first." msgstr "Pre tento krok je potrebné najprv postaviÅ¥ hrad." msgid "Cannot build %{name} because the castle is too far away from an ocean." msgstr "Nedá sa postaviÅ¥ %{name} pretože hrad je priÄaleko od vodnej plochy." msgid "disable build." msgstr "budovanie zakázané." msgid "Cannot afford %{name}." msgstr "Budovu %{name} si nemôžeÅ¡ dovoliÅ¥." msgid "%{name} is already built." msgstr "Budova %{name} je už postavená." msgid "Cannot build %{name}." msgstr "Nedá sa postaviÅ¥ budovu %{name}." msgid "Build %{name}." msgstr "Postav budovu %{name}." msgid "Blackridge" msgstr "ÄŒierny Hrebeň" msgid "Hillstone" msgstr "Horský Kameň" msgid "Pinehurst" msgstr "Borovicový KopÄek" msgid "Whiteshield" msgstr "Biely Å tít" msgid "Woodhaven" msgstr "Drevený Prístav" msgid "Blackwind" msgstr "ÄŒierny Vietor" msgid "Bloodreign" msgstr "Krvomoc" msgid "Dragontooth" msgstr "DraÄí Zub" msgid "Greywind" msgstr "Sivý Vietor" msgid "Portsmith" msgstr "Kutý Prístav" msgid "Atlantium" msgstr "Atlantium" msgid "Middle Gate" msgstr "Stredná Brána" msgid "Sansobar" msgstr "Sansobar" msgid "Tundara" msgstr "Tundara" msgid "Vulcania" msgstr "Vulkánia" msgid "Baywatch" msgstr "Zálivná Stráž" msgid "Fountainhead" msgstr "Prameň" msgid "Vertigo" msgstr "Závrat" msgid "Wildabar" msgstr "Wildabar" msgid "Winterkill" msgstr "Zimomor" msgid "Brindamoor" msgstr "BrindamoÄ" msgid "Lakeside" msgstr "Jazerné Brehy" msgid "Nightshadow" msgstr "NoÄný Tieň" msgid "Olympus" msgstr "Olymp" msgid "Sandcaster" msgstr "Pieskoval" msgid "Alamar" msgstr "Alamar" msgid "Burlock" msgstr "Burlock" msgid "Dragadune" msgstr "Dragaduna" msgid "Kalindra" msgstr "Kalindra" msgid "Xabran" msgstr "Xabran" msgid "Algary" msgstr "Algária" msgid "Basenji" msgstr "Basendži" msgid "Blackfang" msgstr "ÄŒierny Tesák" msgid "New Dawn" msgstr "Nový Úsvit" msgid "Sorpigal" msgstr "Sorpigal" msgid "Avone" msgstr "Zdravá Voda" msgid "Big Oak" msgstr "Veľký Dub" msgid "Chandler" msgstr "ÄŒendler" msgid "Erliquin" msgstr "Erlikín" msgid "Hampshire" msgstr "StudniÄka" msgid "Antioch" msgstr "Antiochia" msgid "Avalon" msgstr "Avalon" msgid "Roc Haven" msgstr "Rochovník" msgid "South Mill" msgstr "Južný Mlyn" msgid "Weed Patch" msgstr "Záhon Buriny" msgid "Brownston" msgstr "Hnedany" msgid "Hilltop" msgstr "Vrchol" msgid "Weddington" msgstr "BohuÅ¡ove Panstvo" msgid "Westfork" msgstr "Západné Rozcestie" msgid "Whittingham" msgstr "Vítova Ves" msgid "Cathcart" msgstr "Vozov" msgid "Elk's Head" msgstr "Losia Hlava" msgid "Roscomon" msgstr "Karolov Vŕšok" msgid "Sherman" msgstr "Å erman" msgid "Yorksford" msgstr "KanÄí Brod" msgid "Blackburn" msgstr "ÄŒernopal" msgid "Blacksford" msgstr "ÄŒernobrod" msgid "Burton" msgstr "Dvorná" msgid "Pig's Eye" msgstr "Svinské Oko" msgid "Viper's Nest" msgstr "Hniezdo Zmije" msgid "Fenton" msgstr "MoÄiarno" msgid "Lankershire" msgstr "Krivokraj" msgid "Lombard" msgstr "Lombard" msgid "Timberhill" msgstr "Drevený Kopec" msgid "Troy" msgstr "Trója" msgid "Forder Oaks" msgstr "Dubobrod" msgid "Meramec" msgstr "Horká Voda" msgid "Quick Silver" msgstr "Ortutie" msgid "Westmoor" msgstr "Západný MoÄiar" msgid "Willow" msgstr "Vrbina" msgid "Corackston" msgstr "Starý Lom" msgid "Sheltemburg" msgstr "Å títnica" msgid "Cannot recruit - you already have a Hero in this town." msgstr "Nedá sa najaÅ¥ - v tomto meste už máš hrdinu." msgid "Cannot recruit - you have too many Heroes." msgstr "Nedá sa najaÅ¥ - máš príliÅ¡ mnoho hrdinov." msgid "Cannot afford a Hero" msgstr "Hrdinu si nemôžeÅ¡ dovoliÅ¥" msgid "There is no room in the garrison for this army." msgstr "V posádke nie je miesto pre túto armádu." msgid "Fortifications" msgstr "Opevnenia" msgid "Farm" msgstr "Farma" msgid "Thatched Hut" msgstr "Slamená chatrÄ" msgid "Archery Range" msgstr "Luková strelnica" msgid "Upg. Archery Range" msgstr "Pov. Luková strelnica" msgid "Blacksmith" msgstr "KováÄ" msgid "Upg. Blacksmith" msgstr "Pov. KováÄ" msgid "Armory" msgstr "Zbrojnica" msgid "Upg. Armory" msgstr "Pov. Zbrojnica" msgid "Jousting Arena" msgstr "Turnajová aréna" msgid "Upg. Jousting Arena" msgstr "Pov. Turnajová aréna" msgid "Cathedral" msgstr "Katedrála" msgid "Upg. Cathedral" msgstr "Pov. Katedrála" msgid "Coliseum" msgstr "Koloseum" msgid "Garbage Heap" msgstr "Smetisko" msgid "Hut" msgstr "ChatrÄ" msgid "Stick Hut" msgstr "Drevenica" msgid "Upg. Stick Hut" msgstr "Pov. Drevenica" msgid "Den" msgstr "Brloh" msgid "Adobe" msgstr "Tehlový dom" msgid "Upg. Adobe" msgstr "Pov. Tehlový dom" msgid "Bridge" msgstr "Most" msgid "Upg. Bridge" msgstr "Pov. Most" msgid "Pyramid" msgstr "Pyramída" msgid "Rainbow" msgstr "Dúha" msgid "Crystal Garden" msgstr "KriÅ¡táľová záhrada" msgid "Treehouse" msgstr "Dom na strome" msgid "Cottage" msgstr "Chalupa" msgid "Upg. Cottage" msgstr "Pov. Chalupa" msgid "Stonehenge" msgstr "Stonehenge" msgid "Upg. Stonehenge" msgstr "Pov. Stonehenge" msgid "Fenced Meadow" msgstr "Oplotená lúka" msgid "sorceress|Red Tower" msgstr "ÄŒervená veža" msgid "Dungeon" msgstr "Žalár" msgid "Waterfall" msgstr "Vodopád" msgid "Cave" msgstr "Jaskyňa" msgid "Crypt" msgstr "Krypta" msgid "Nest" msgstr "Hniezdo" msgid "Maze" msgstr "Labyrint" msgid "Upg. Maze" msgstr "Pov. Labyrint" msgid "Swamp" msgstr "MoÄiar" msgid "Green Tower" msgstr "Zelená veža" msgid "warlock|Red Tower" msgstr "ÄŒervená veža" msgid "Black Tower" msgstr "ÄŒierna veža" msgid "Library" msgstr "Knižnica" msgid "Orchard" msgstr "Ovocný sad" msgid "Habitat" msgstr "Habitát" msgid "Pen" msgstr "Ohrada" msgid "Foundry" msgstr "Zlievareň" msgid "Upg. Foundry" msgstr "Pov. Zlievareň" msgid "Cliff Nest" msgstr "Hniezdo na útese" msgid "Ivory Tower" msgstr "Veža zo slonoviny" msgid "Upg. Ivory Tower" msgstr "Pov. Veža zo slonoviny" msgid "Cloud Castle" msgstr "OblaÄné mesto" msgid "Upg. Cloud Castle" msgstr "Pov. OblaÄné mesto" msgid "Storm" msgstr "Búrka" msgid "Skull Pile" msgstr "Hromada lebiek" msgid "Excavation" msgstr "Vykopávka" msgid "Graveyard" msgstr "Cintorín" msgid "Upg. Graveyard" msgstr "Pov. Cintorín" msgid "Upg. Pyramid" msgstr "Pov. Pyramída" msgid "Mansion" msgstr "KaÅ¡tieľ" msgid "Upg. Mansion" msgstr "Pov. KaÅ¡tieľ" msgid "Mausoleum" msgstr "Mauzóleum" msgid "Upg. Mausoleum" msgstr "Pov. Mauzóleum" msgid "Laboratory" msgstr "Laboratórium" msgid "Shrine" msgstr "Svätyňa" msgid "" "The Fortifications increase the toughness of the walls, increasing the " "number of turns it takes to knock them down." msgstr "" "Opevnenia zvyÅ¡ujú húževnatosÅ¥ múrov a zvyÅ¡ujú poÄet kôl potrebných na ich " "zbúranie." msgid "The Farm increases production of Peasants by %{count} per week." msgstr "Farma zvyÅ¡uje produkciu roľníkov o %{count} za týždeň." msgid "" "The Coliseum provides inspiring spectacles to defending troops, raising " "their morale by two during combat." msgstr "" "Koloseum poskytuje obranným jednotkám inÅ¡piratívne predstavenie, ktoré poÄas " "boja zvýši ich morálku o dve." msgid "The Garbage Heap increases production of Goblins by %{count} per week." msgstr "Smetisko zvyÅ¡uje produkciu goblinov o %{count} za týždeň." msgid "The Rainbow increases the luck of the defending units by two." msgstr "Dúha zvyÅ¡uje šťastie obranných jednotiek o dve." msgid "" "The Crystal Garden increases production of Sprites by %{count} per week." msgstr "KriÅ¡táľová záhrada zvyÅ¡uje produkciu víl o %{count} za týždeň." msgid "The Dungeon increases the income of the town by %{count} gold per day." msgstr "Žalár zvyÅ¡uje príjem mesta o %{count} zlata za deň." msgid "The Waterfall increases production of Centaurs by %{count} per week." msgstr "Vodopád zvyÅ¡uje produkciu kentaurov o %{count} za týždeň." msgid "" "The Library increases the number of spells in the Guild by one for each " "level of the guild." msgstr "Knižnica zvyÅ¡uje poÄet kúziel v cechu o jedno za každú úroveň cechu." msgid "The Orchard increases production of Halflings by %{count} per week." msgstr "Ovocný sad zvyÅ¡uje produkciu polÄíkov o %{count} za týždeň." msgid "The Storm adds +2 to the power of spells of a defending spell caster." msgstr "Búrka pridáva +2 k sile kúziel brániaceho kúzelníka." msgid "The Skull Pile increases production of Skeletons by %{count} per week." msgstr "Hromada lebiek zvyÅ¡uje produkciu kostlivcov o %{count} za týždeň." msgid "Thieves' Guild" msgstr "Cech zlodejov" msgid "Tavern" msgstr "KrÄma" msgid "Shipyard" msgstr "Lodenica" msgid "Well" msgstr "Studňa" msgid "Statue" msgstr "Socha" msgid "Marketplace" msgstr "Trhovisko" msgid "Moat" msgstr "Priekopa" msgid "Castle" msgstr "Hrad" msgid "Tent" msgstr "Stan" msgid "Captain's Quarters" msgstr "Kapitánova ubytovňa" msgid "Mage Guild, Level 1" msgstr "Cech mágov, úroveň 1" msgid "Mage Guild, Level 2" msgstr "Cech mágov, úroveň 2" msgid "Mage Guild, Level 3" msgstr "Cech mágov, úroveň 3" msgid "Mage Guild, Level 4" msgstr "Cech mágov, úroveň 4" msgid "Mage Guild, Level 5" msgstr "Cech mágov, úroveň 5" msgid "" "The Shrine increases the necromancy skill of all your necromancers by 10 " "percent." msgstr "" "Svätyňa zvyÅ¡uje zruÄnosÅ¥ nekromancie vÅ¡etkých tvojich nekromantov o 10 " "percent." msgid "" "The Thieves' Guild provides information on enemy players. Thieves' Guilds " "can also provide scouting information on enemy towns. Additional Guilds " "provide more information." msgstr "" "Cech zlodejov poskytuje informácie o nepriateľských hráÄoch. Cech zlodejov " "tiež poskytuje prieskumné informácie o nepriateľských mestách. DodatoÄné " "cechy poskytnú viac informácií." msgid "The Tavern increases morale for troops defending the castle." msgstr "KrÄma zvyÅ¡uje morálku pre jednotky brániace hrad." msgid "The Shipyard allows ships to be built." msgstr "Lodenica umožňuje stavbu lodí." msgid "" "The Well increases the growth rate of all dwellings by %{count} creatures " "per week." msgstr "" "Studňa zvyÅ¡uje rýchlosÅ¥ nárastu bojovníkov vÅ¡etkých obydlí o %{count} za " "týždeň." #, fuzzy msgid "The Statue increases the town's income by %{count} gold per day." msgstr "Socha zvyÅ¡uje príjem tvojho mesta o %{count} zlata za deň." msgid "The Left Turret provides extra firepower during castle combat." msgstr "Ľavá veža poskytuje palebnú silu navyÅ¡e poÄas boja o hrad." msgid "The Right Turret provides extra firepower during castle combat." msgstr "Pravá veža poskytuje palebnú silu navyÅ¡e poÄas boja o hrad." msgid "" "The Marketplace can be used to convert one type of resource into another. " "The more marketplaces you control, the better the exchange rate." msgstr "" "Trhovisko možno použiÅ¥ na zámenu jedného typu zdroja za iný. Čím viac " "trhovísk vlastníš, tým lepší je výmenný kurz." msgid "" "The Moat slows attacking units. Any unit entering the moat must end its turn " "there and becomes more vulnerable to attack." msgstr "" "Priekopa spomaľuje útoÄiace jednotky. Každá jednotka, ktorá vstúpi do " "priekopy, tam musí svoj Å¥ah ukonÄiÅ¥ a stane sa zraniteľnejÅ¡ou voÄi útoku." #, fuzzy msgid "" "The Castle improves the town's defense and increases its income to %{count} " "gold per day." msgstr "Hrad zlepÅ¡uje obranu mesta a zvyÅ¡uje príjem na %{count} zlata za deň." msgid "" "The Tent provides workers to build a castle, provided the materials and the " "gold are available." msgstr "" "Stan poskytuje robotníkov na stavbu hradu za predpokladu, že sú k dispozícii " "materiály a zlato." msgid "" "The Captain's Quarters provides a captain to assist in the castle's defense " "when no hero is present." msgstr "" "Kapitánova ubytovňa poskytuje kapitána, ktorý pomáha pri obrane hradu, keÄ " "nie je prítomný žiadny hrdina." msgid "" "The Mage Guild allows heroes to learn spells and replenish their spell " "points." msgstr "Cech mágov umožňuje hrdinom uÄiÅ¥ sa kúzla a dopĺňaÅ¥ si body kúziel." msgid "Recruit %{name}" msgstr "NajaÅ¥: %{name}" msgid "Month: %{month}, Week: %{week}, Day: %{day}" msgstr "Mesiac: %{month}, Týždeň: %{week}, Deň: %{day}" msgid "" "You must purchase a spell book to use the mage guild, but you currently have " "no room for a spell book. Try giving one of your artifacts to another hero." msgstr "" "Ak chceÅ¡ používaÅ¥ cech mágov, musíš si kúpiÅ¥ knihu kúziel, ale momentálne " "nemáš miesto pre knihu kúziel. Skús daÅ¥ jeden zo svojich artefaktov inému " "hrdinovi." msgid "Click to show next town." msgstr "Kliknutím zobraz ÄalÅ¡ie mesto." msgid "Show next town" msgstr "Ukáž ÄalÅ¡ie mesto" msgid "Click to show previous town." msgstr "Kliknutím zobraz predoÅ¡lé mesto." msgid "Show previous town" msgstr "Ukáž predoÅ¡lé mesto" msgid "This town may not be upgraded to a castle." msgstr "Toto mesto nemôže byÅ¥ povýšené na hrad." msgid "Town" msgstr "Mesto" msgid "Exit Castle" msgstr "PreÄ z hradu" msgid "Exit Town" msgstr "PreÄ z mesta" msgid "Show Income" msgstr "Ukáž príjem" msgid "View Hero" msgstr "Ukáž hrdinu" msgid "The above spells are available here." msgstr "Vyššie uvedené kúzla sú tu dostupné." msgid "The spells the hero can learn have been added to their book." msgstr "Do knihy boli pridané kúzla, ktoré sa hrdina môže nauÄiÅ¥." msgid "A generous tip for the barkeep yields the following rumor:" msgstr "Å tedré prepitné pre barmana prináša nasledujúcu fámu:" msgid "Recruit Hero" msgstr "NajaÅ¥ hrdinu" msgid "%{name} is a level %{value} %{race} " msgstr "%{name} je %{race} na úrovni %{value} " msgid "with %{count} artifacts." msgstr "s %{count} artefaktmi." msgid "with 1 artifact." msgstr "s jedným artefaktom." msgid "without artifacts." msgstr "bez artefaktov." msgid "Recruit %{name} the %{race}" msgstr "Najmi: %{race} %{name}" #, fuzzy msgid "" "'Spread' combat formation spreads the castle's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "'Å iroká' bojová formácia rozprestiera tvoje armády zhora nadol na bojisko, " "priÄom medzi každou armádou je aspoň jedno prázdne miesto." #, fuzzy msgid "" "'Grouped' combat formation bunches the castle's units together in the center " "of the castle's side of the battlefield." msgstr "" "'Zoskupená' bojová formácia spája tvoju armádu v strede tvojej strany " "bojiska." msgid "Spread Formation" msgstr "Å iroká formácia" msgid "Grouped Formation" msgstr "Zoskupená formácia" msgid "Set garrison combat formation to 'Spread'" msgstr "NastaviÅ¥ bojovú formáciu posádky na 'Å iroká'" msgid "Set garrison combat formation to 'Grouped'" msgstr "NastaviÅ¥ bojovú formáciu posádky na 'Zoskupená'" msgid "Exit Castle Options" msgstr "PreÄ z možností hradu" msgid "Castle Options" msgstr "Možnosti hradu" msgid "Not enough resources to recruit creatures." msgstr "Nie je dostatok zdrojov na nábor bojovníkov." msgid "You are unable to recruit at this time, your ranks are full." msgstr "Momentálne nemôžeÅ¡ robiÅ¥ nábor, tvoje rady sú plné." msgid "No creatures available for purchase." msgstr "Žiadni bojovníci nie sú k dispozícii na nákup." msgid "Recruit Creatures" msgstr "NajaÅ¥ bojovníkov" msgid "Max" msgstr "Max" msgid "Hire all creatures in the town." msgstr "Najmi vÅ¡etkých bojovníkov v meste." msgid "Available" msgstr "Dostupné" msgid "Town Population Information and Statistics" msgstr "Informácie o obyvateľstve a Å¡tatistika mesta" msgid "Damage" msgstr "PoÅ¡kodenie" msgid "HP" msgstr "Zdravie" msgid "Growth" msgstr "Nárast" msgid "week" msgstr "týždeň" msgid "View World" msgstr "Ukáž svet" msgid "View the entire world." msgstr "Ukáž celý svet." msgid "Puzzle" msgstr "Rébus" msgid "View the obelisk puzzle." msgstr "Ukáž rébus obeliskov." msgid "Scenario Information" msgstr "Informácie o hre" msgid "View information on the scenario you are currently playing." msgstr "Pozri si informácie o hre, ktorú práve hráš." msgid "Dig for the Ultimate Artifact." msgstr "Kop za ultimátnym artefaktom." msgid "Digging" msgstr "Kopanie" msgid "Arena" msgstr "Aréna" msgid "" "You enter the arena and face a pack of vicious lions. You handily defeat " "them, to the wild cheers of the crowd. Impressed by your skill, the aged " "trainer of gladiators agrees to train you in a skill of your choice." msgstr "" "Po vstupe do arény Äelíš svorke zúrivých levov. Za búrlivého povzbudzovania " "davu ich ľahko porazíš. Starý tréner gladiátorov, na ktorého urobili dojem " "tvoje schopnosti, súhlasí, že Å¥a vyÅ¡kolí v zruÄnosti podľa tvojho výberu." msgid "Attack Skill" msgstr "Útok" msgid "Defense Skill" msgstr "Obrana" msgid "Shots" msgstr "Strely" msgid "Shots Left" msgstr "Ostáva striel" msgid "Hit Points" msgstr "Zdravie max" msgid "Hit Points Left" msgstr "Zdravie teraz" msgid "You can't afford to upgrade your troops!" msgstr "NemôžeÅ¡ si dovoliÅ¥ povýšiÅ¥ svoje jednotky!" msgid "" "Your troops can be upgraded, but it will cost you dearly. Do you wish to " "upgrade them?" msgstr "" "Tvoje jednotky môžu byÅ¥ vylepÅ¡ené, ale bude Å¥a to stáť draho. ChceÅ¡ ich " "povýšiÅ¥?" msgid "Are you sure you want to dismiss this army?" msgstr "UrÄite chceÅ¡ prepustiÅ¥ túto armádu?" msgid "Upgrade" msgstr "Povýš" msgid "Upgrade your troops." msgstr "Povýš svoje jednotky." msgid "Dismiss" msgstr "Prepusti" msgid "Dismiss this army." msgstr "Prepusti túto armádu." msgid "" "A group of %{monster} with a desire for greater glory wish to join you.\n" "Do you accept?" msgstr "" "%{monster}, s túžbou po väÄÅ¡ej sláve, sa k tebe chcú pridaÅ¥.\n" "PrijímaÅ¡ ich?" msgid "Followers" msgstr "Nasledovníci" msgid "" "The %{monster} is swayed by your diplomatic tongue, and offers to join your " "army for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "%{monster} je ovplyvnený/á tvojim diplomatickým jazykom a ponúka ti vstup do " "tvojej armády za sumu %{gold} zlata.\n" "PrijímaÅ¡ ponuku?" msgid "" "The creatures are swayed by your diplomatic\n" "tongue, and make you an offer:\n" "\n" msgstr "" "Bojovníci sú pohnutí tvojim diplomatickým\n" "jazykom a ponúkajú:\n" "\n" msgid "" "%{offer} of the %{total} %{monster} will join your army, and the rest will " "leave you alone, for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "%{monster}, presnejÅ¡ie %{offer} z celkových %{total} sa pripoja k tvojej " "armáde a zvyÅ¡ok Å¥a nechá na pokoji za sumu %{gold} zlata.\n" "PrijímaÅ¡ ponuku?" msgid "" "All %{offer} of the %{monster} will join your army for the sum of %{gold} " "gold.\n" "Do you accept?" msgstr "" "Celá skupina - %{offer} %{monster}, sa pripojí k tvojej armáde za sumu " "%{gold} zlata.\n" "PrijímaÅ¡ ponuku?" msgid "(Rate: %{percent})" msgstr "%{percent}% vÅ¡etkých bojovníkov" msgid "off" msgstr "vypnuté" msgid "Music" msgstr "Hudba" msgid "Effects" msgstr "Zvukové efekty" msgid "MIDI" msgstr "MIDI" msgid "MIDI Expansion" msgstr "Rozšírenie MIDI" msgid "External" msgstr "Externý" msgid "Music Type" msgstr "Typ hudby" msgid "3D Audio" msgstr "3D zvuk" msgid "Toggle ambient music level." msgstr "Zmeň úroveň zvukov okolia." msgid "Toggle foreground sounds level." msgstr "Zmeň úroveň zvukov popredia." msgid "Change the type of music." msgstr "Zmeň typ hudby." msgid "Toggle 3D effects of foreground sounds." msgstr "Zmeň 3D efekty zvukov popredia." msgid "Build a new ship:" msgstr "Postav novú loÄ:" msgid "Resource cost:" msgstr "Náklady:" msgid "Total: " msgstr "Celkovo: " msgid "Need: " msgstr "Potreba: " msgid "Load Game" msgstr "Nahraj hru" msgid "No save files to load." msgstr "Žiadne uložené hry." msgid "New Game" msgstr "Nová hra" msgid "Start a single or multi-player game." msgstr "ZaÄni hru jedného alebo viac hráÄov." msgid "Load a previously saved game." msgstr "Nahraj predtým uloženú hru." msgid "Save Game" msgstr "Ulož hru" msgid "Save the current game." msgstr "Ulož terajÅ¡iu hru." msgid "Quit" msgstr "Koniec" msgid "Quit out of Heroes of Might and Magic II." msgstr "OdÃ­Ä z Heroes of Might and Magic II." msgid "Language" msgstr "Jazyk" msgid "Graphics" msgstr "Grafika" msgid "Black & White" msgstr "ÄŒiernobiely" msgid "Mouse Cursor" msgstr "Kurzor myÅ¡i" msgid "Color" msgstr "Farebný" msgid "Text Support" msgstr "Podpora textu" msgid "Change the language of the game." msgstr "Zmeň jazyk hry." msgid "Select Game Language" msgstr "Vyber jazyk hry" msgid "Change the graphics settings of the game." msgstr "Zmeň nastavenie grafiky hry." msgid "Toggle colored cursor on or off. This is only an aesthetic choice." msgstr "Zapnutie alebo vypnutie farebného kurzora. Ide len o estetickú voľbu." msgid "" "Toggle text support mode to output extra information about windows and " "events in the game." msgstr "" "Prepni režim podpory textu na výstup dodatoÄných informácií o oknách a " "udalostiach v hre." msgid "" "Map\n" "Difficulty" msgstr "" "ObtiažnosÅ¥\n" "mapy" msgid "" "Game\n" "Difficulty" msgstr "" "ObtiažnosÅ¥\n" "hry" msgid "Rating" msgstr "Hodnotenie" msgid "Map Size" msgstr "VeľkosÅ¥ mapy" msgid "Opponents" msgstr "Oponenti" msgid "Class" msgstr "Trieda" msgid "" "Victory\n" "Conditions" msgstr "" "Podmienky\n" "víťazstva" msgid "" "Loss\n" "Conditions" msgstr "" "Podmienky\n" "prehry" msgid "First select recipients!" msgstr "Najprv zvoľ príjemcov!" msgid "You cannot select %{resource}!" msgstr "NemôžeÅ¡ zvoliÅ¥ %{resource}!" msgid "Select count %{resource}:" msgstr "Zvoľ poÄet - %{resource}:" msgid "Select Recipients" msgstr "Zvoľ príjemcov" msgid "Your Funds" msgstr "Tvoj majetok" msgid "Planned Gift" msgstr "Plánovaný dar" msgid "Gift from %{name}" msgstr "Dar od: %{name}" msgid "Resolution" msgstr "Rozlíšenie" msgid "Fullscreen" msgstr "Celá obrazovka" msgid "window|Mode" msgstr "Mód" msgid "Windowed" msgstr "Okno" msgid "V-Sync" msgstr "V-Sync" msgid "on" msgstr "zapnuté" msgid "FPS" msgstr "FPS" msgid "System Info" msgstr "Systémové info" msgid "Change the resolution of the game." msgstr "Zmeň rozlíšenie hry." msgid "Select Game Resolution" msgstr "Zvoľ rozlíšenie hry" msgid "Toggle between fullscreen and windowed modes." msgstr "Prepni medzi módom celej obrazovky a oknom." msgid "" "The V-Sync option can be enabled to resolve flickering issues on some " "monitors." msgstr "" "MožnosÅ¥ V-Sync dokáže vyrieÅ¡iÅ¥ problém s blikaním na niektorých monitoroch." msgid "Show extra information such as FPS and current time." msgstr "Ukáž informácie navyÅ¡e ako FPS a momentálny Äas." msgid "Hotkey: " msgstr "" msgid "Category: " msgstr "" #, fuzzy msgid "Event: " msgstr "UdalosÅ¥" msgid "Hot Keys:" msgstr "Skratky:" msgid "Evil" msgstr "Zlo" msgid "Good" msgstr "Dobro" msgid "Interface Type" msgstr "Typ rozhrania" msgid "Hide" msgstr "Skry" msgid "Show" msgstr "Zobraz" msgid "Interface" msgstr "Rozhranie" msgid "Slow" msgstr "Pomalá" msgid "Normal" msgstr "Normálna" msgid "Fast" msgstr "Rýchla" msgid "Very Fast" msgstr "Veľmi rýchla" msgid "Scroll Speed" msgstr "RýchlosÅ¥ posunu" msgid "Toggle the type of interface you want to use." msgstr "Prepni typ rozhrania, ktorý chceÅ¡ vidieÅ¥." msgid "Toggle interface visibility." msgstr "Prepni viditeľnosÅ¥ rozhrania." msgid "Toggle colored cursor on or off. This is only an esthetic choice." msgstr "Prepni farebný kurzor. Toto je len estetická voľba." msgid "Sets the speed at which you scroll the window." msgstr "Nastaví rýchlosÅ¥, ktorou sa posúva okno." msgid "Select Game Language:" msgstr "Zvoľ jazyk hry:" msgid "Click to choose the selected language." msgstr "Klikni pre výber zvoleného jazyka." msgid "%{name} has gained a level." msgstr "%{name} získava úroveň." msgid "%{skill} +1" msgstr "%{skill} +1" msgid "You have learned %{skill}." msgstr "NauÄená zruÄnosÅ¥ %{skill}." #, fuzzy msgid "" "%{name} has gained a level.\n" "\n" "%{skill} +1" msgstr "%{name} získava úroveň." #, fuzzy msgid "" "You may learn either:\n" "%{skill1}\n" "or\n" "%{skill2}" msgstr "SmieÅ¡ sa nauÄiÅ¥ buÄ:" msgid "" "Please inspect our fine wares. If you feel like offering a trade, click on " "the items you wish to trade with and for." msgstr "" "Prosím, prezri si náš kvalitný tovar. Ak chceÅ¡ ponúknuÅ¥ výmenu, klikni na " "predmety, s ktorými chceÅ¡ obchodovaÅ¥." msgid "" "You have received quite a bargain. I expect to make no profit on the deal. " "Can I interest you in any of my other wares?" msgstr "" "Získali ste poriadne výhodnú ponuku. NeoÄakávam, že na tomto obchode " "zarobím. Môžem vám ponúknuÅ¥ nejaký Äalší tovar?" msgid "I can offer you %{count} for 1 unit of %{resfrom}." msgstr "Ponúkam %{count} za 1 kus typu %{resfrom}." msgid "I can offer you 1 unit of %{resto} for %{count} units of %{resfrom}." msgstr "Ponúkam 1 kus typu %{resto} za %{count} kusov typu %{resfrom}." msgid "Min" msgstr "Min" msgid "Qty to trade" msgstr "Množstvo" msgid "Trading Post" msgstr "Obchodník" msgid "Your Resources" msgstr "Tvoje zdroje" msgid "Available Trades" msgstr "Možné obchody" msgid "n/a" msgstr "nie je" msgid "guarded by %{count} %{monster}" msgstr "strážené: %{monster} (%{count})" msgid "guarded by " msgstr "strážené: " msgid "(available: %{count})" msgstr "(dostupné: %{count})" msgid "(empty)" msgstr "(prázdne)" msgid "already learned" msgstr "už nauÄené" #, fuzzy msgid "treeOfKnowledge|free" msgstr "Strom poznania" #, fuzzy msgid "already claimed" msgstr "už nauÄené" msgid "not claimed" msgstr "" msgid "already knows this skill" msgstr "už má túto zruÄnosÅ¥" msgid "already has max skills" msgstr "už má plno zruÄností" msgid "(already visited)" msgstr "(už navÅ¡tívené)" msgid "(not visited)" msgstr "(nenavÅ¡tívené)" msgid "%{color} Barrier" msgstr "%{color} bariéra" #, fuzzy msgid "(tent visited)" msgstr "(nenavÅ¡tívené)" msgid "%{color} Tent" msgstr "%{color} stan" msgid "Road" msgstr "Cesta" msgid "(digging ok)" msgstr "(dá sa kopaÅ¥)" msgid "(no digging)" msgstr "(nedá sa kopaÅ¥)" msgid "penalty: %{cost}" msgstr "penalta: %{cost}" msgid "Defenders:" msgstr "Obrancovia:" msgid "Unknown" msgstr "Neznáme" msgid "%{name} (Level %{level})" msgstr "%{name} na úrovni %{level}" msgid "Attack:" msgstr "Útok:" msgid "Defense:" msgstr "Obrana:" msgid "Spell Power:" msgstr "Sila kúziel:" msgid "Knowledge:" msgstr "Vedomosti:" msgid "Spell Points:" msgstr "Body kúziel:" msgid "Move Points:" msgstr "Body pohybu:" msgid "Uncharted Territory" msgstr "Nepreskúmané územie" msgid "Cost per troop:" msgstr "Cena za jednotku:" msgid "Available: %{count}" msgstr "Dostupné: %{count}" msgid "Number to buy:" msgstr "PoÄet na nákup:" msgid "Recruit selected monsters." msgstr "Najmi zvolených bojovníkov." msgid "Select maximum monsters to be recruited." msgstr "Zvoľ najväÄší možný poÄet bojovníkov k nájmu." msgid "Select only 1 monster to be recruited." msgstr "Zvoľ iba jedného bojovníka k nájmu." msgid "Select Game Resolution:" msgstr "Zvoľ rozlíšenie hry:" msgid "Click to apply the selected resolution." msgstr "Klikni pre výber zvoleného rozlíšenia." msgid "Click to apply the entered text." msgstr "Klikni pre použitie zadaného textu." msgid "Click to open the Virtual Keyboard dialog." msgstr "Klikni pre otvorenie dialógu virtuálnej klávesnice." msgid "Open Virtual Keyboard" msgstr "Otvor virtuálnu klávesnicu" msgid "How many troops to move?" msgstr "Koľko jednotiek na presun?" msgid "Fast separation into slots:" msgstr "Rýchle rozdelenie do polí:" msgid "Map: " msgstr "Mapa: " msgid "" "\n" "\n" "Month: " msgstr "" "\n" "\n" "Mesiac: " msgid ", Week: " msgstr ", Týždeň: " msgid ", Day: " msgstr ", Deň: " msgid "" "\n" "\n" "Location: " msgstr "" "\n" "\n" "Umiestnenie: " msgid "Click to save the current game." msgstr "Klikni pre uloženie terajÅ¡ej hry." msgid "Click to load a previously saved game." msgstr "Klikni pre nahranie vopred uloženej hry." msgid "Are you sure you want to delete file:" msgstr "UrÄite chceÅ¡ vymazaÅ¥ súbor:" msgid "Warning!" msgstr "Pozor!" msgid "File to Save:" msgstr "UložiÅ¥ súbor:" msgid "File to Load:" msgstr "NahraÅ¥ súbor:" msgid "Accept the choice made." msgstr "PrijaÅ¥ uskutoÄnený výber." #, fuzzy msgid "%{color} %{race} hero" msgstr "%{color} hráÄ" #, fuzzy msgid "Terrain object" msgstr "Režim terénu" msgid "Select Skill:" msgstr "Zvoľ zruÄnosÅ¥:" msgid "Select Spell:" msgstr "Zvoľ kúzlo:" msgid "Select Artifact:" msgstr "Zvoľ artefakt:" msgid "Select Monster:" msgstr "Zvoľ bojovníka:" msgid "Select Hero:" msgstr "Zvoľ hrdinu:" #, fuzzy msgid "Select Treasure:" msgstr "Zvoľ hrdinu:" #, fuzzy msgid "Select Ocean Object:" msgstr "Objekty oceánu" #, fuzzy msgid "Castle/town placing" msgstr "Možnosti hradu" msgid "doubleLinedRace|Neutral" msgstr "Neutrálna" msgid "race|Random" msgstr "Náhodná" #, fuzzy msgid "Click to start placing the selected castle/town." msgstr "Klikni pre výber zvoleného rozlíšenia." #, fuzzy msgid "Click to select town placing." msgstr "Kliknutím zobraz ÄalÅ¡ie mesto." #, fuzzy msgid "Click to select castle placing." msgstr "Klikni sem a vyber scenár, ktorý chceÅ¡ hraÅ¥." #, fuzzy msgid "%{color} %{race} %{townOrCastle}" msgstr "%{color} hráÄ" msgid "race|Neutral" msgstr "Neutrálna" msgid "You will place" msgstr "" #, fuzzy msgid "Click to select this class." msgstr "Klikni sem a vyber scenár, ktorý chceÅ¡ hraÅ¥." #, fuzzy msgid "Click to select this color." msgstr "Klikni sem a vyber scenár, ktorý chceÅ¡ hraÅ¥." #, fuzzy msgid "Select Dwelling:" msgstr "Zvoľ kúzlo:" #, fuzzy msgid "Select Landscape Object:" msgstr "Objekty oceánu" msgid "Mine placing" msgstr "" #, fuzzy msgid "" "Resource\n" "type:" msgstr "Surovina" #, fuzzy msgid "%{mineName} appearance:" msgstr "%{race} %{name}" #, fuzzy msgid "Click to select %{object} as the resource generator to be placed." msgstr "Klikni sem a vyber scenár, ktorý chceÅ¡ hraÅ¥." #, fuzzy msgid "Select Mountain Object:" msgstr "Objekty oceánu" #, fuzzy msgid "Select Rock Object:" msgstr "Objekty oceánu" #, fuzzy msgid "Select Tree Object:" msgstr "Objekty oceánu" #, fuzzy msgid "Select Power Up Object:" msgstr "Objekty oceánu" #, fuzzy msgid "Select Adventure Object:" msgstr "Objekty oceánu" msgid "Map Type:\n" msgstr "Typ mapy:\n" msgid "The Succession Wars" msgstr "Vojna o dediÄstvo" msgid "The Price of Loyalty" msgstr "Cena vernosti" #, fuzzy msgid "Resurrection" msgstr "Vzkriesenie" msgid "Lose all your heroes and towns." msgstr "StraÅ¥ vÅ¡etkých hrdinov a mestá." msgid "Lose a specific town." msgstr "StraÅ¥ konkrétne mesto." msgid "Lose a specific hero." msgstr "StraÅ¥ konkrétneho hrdinu." msgid "Run out of time. Fail to win by a certain point." msgstr "Po uplynutí urÄitej doby stále nevyhráš." msgid "Loss Condition" msgstr "Podmienka prehry" msgid "Defeat all enemy heroes and towns." msgstr "Poraz nepriateľských hrdinov a mestá." msgid "Capture a specific town." msgstr "Doby konkrétne mesto." msgid "Defeat a specific hero." msgstr "Poraz konkrétneho hrdinu." msgid "Find a specific artifact." msgstr "Nájdi konkrétny artefakt." msgid "Your side defeats the opposing side." msgstr "Tvoja strana porazí opozíciu." msgid "Accumulate a large amount of gold." msgstr "NahromaÄ obrovské množstvo zlata." msgid "Victory Condition" msgstr "Podmienka výhry" msgid "Map difficulty:" msgstr "ObtiažnosÅ¥ mapy:" msgid "N" msgstr "N" msgid "No maps exist at that size" msgstr "Mapy tejto veľkosti nie sú" msgid "Small Maps" msgstr "Malé mapy" msgid "View only maps of size small (36 x 36)." msgstr "Zobraz iba mapy malej veľkosti (36 x 36)." msgid "Medium Maps" msgstr "Stredné mapy" msgid "View only maps of size medium (72 x 72)." msgstr "Zobraz iba mapy strednej veľkosti (72 x 72)." msgid "Large Maps" msgstr "Veľké mapy" msgid "View only maps of size large (108 x 108)." msgstr "Zobraz iba mapy veľkej veľkosti (108 x 108)." msgid "Extra Large Maps" msgstr "Extra veľké mapy" msgid "View only maps of size extra large (144 x 144)." msgstr "Zobraz iba mapy extra veľkej veľkosti (144 x 144)." msgid "All Maps" msgstr "VÅ¡etky mapy" msgid "View all maps, regardless of size." msgstr "Zobraz vÅ¡etky mapy bez ohľadu na veľkosÅ¥." msgid "Players Icon" msgstr "Ikona hráÄa" msgid "" "Indicates how many players total are in the scenario. Any positions not " "occupied by humans will be occupied by computer players." msgstr "" "ZnaÄí, koľko hráÄov je celkovo v scenári. Akékoľvek pozície, ktoré nie sú " "obsadené ľuÄmi, budú obsadené poÄítaÄovými hráÄmi." msgid "" "Indicates whether the map\n" "is small (36 x 36), medium\n" "(72 x 72), large (108 x 108),\n" "or extra large (144 x 144)." msgstr "" "ZnaÄí, Äi je mapa malá (36 x 36), stredná (72 x 72), veľká (108 x 108), " "alebo extra veľká (144 x 144)." msgid "Size Icon" msgstr "Ikona veľkosti" msgid "" "Indicates whether the map is made for \"The Succession Wars\" or \"The Price " "of Loyalty\" version of the game." msgstr "" "ZnaÄí, Äi je mapa tvorená pre verziu hry \"The Succession Wars\" alebo \"The " "Price of Loyalty\"." msgid "Map Type" msgstr "Typ mapy" msgid "Selected Name" msgstr "Zvolené meno" msgid "The name of the currently selected map." msgstr "Názov momentálne zvolenej mapy." msgid "Selected Map Difficulty" msgstr "Zvolená obtiažnosÅ¥ mapy" msgid "" "The map difficulty of the currently selected map. The map difficulty is " "determined by the scenario designer. More difficult maps might include more " "or stronger enemies, fewer resources, or other special conditions making " "things tougher for the human player." msgstr "" "ObtiažnosÅ¥ mapy aktuálne vybranej mapy. ObtiažnosÅ¥ mapy urÄuje tvorca " "scenára. Ťažšie mapy môžu obsahovaÅ¥ viac alebo silnejších nepriateľov, menej " "zdrojov alebo iné Å¡peciálne podmienky, ktoré sÅ¥ažujú situáciu pre ľudského " "hráÄa." msgid "Selected Description" msgstr "Zvolený opis" msgid "The description of the currently selected map." msgstr "Opis aktuálne zvolenej mapy." msgid "Jump" msgstr "Skok" msgid "Hero Speed" msgstr "RýchlosÅ¥ hrdinu" msgid "Don't Show" msgstr "Neukazuj" msgid "Enemy Speed" msgstr "RýchlosÅ¥ nepriateľa" msgid "Auto Resolve" msgstr "Automaticky" msgid "Auto, No Spells" msgstr "Auto bez kúziel" msgid "Battles" msgstr "Boje" msgid "autoBattle|Manual" msgstr "Manuálne" msgid "Change the speed at which your heroes move on the main screen." msgstr "Zmeň rýchlosÅ¥, ktorou sa tvoji hrdinovia pohybujú v hlavnom okne." msgid "" "Sets the speed that A.I. heroes move at. You can also elect not to view A.I. " "movement at all." msgstr "" "Nastaví rýchlosÅ¥, akou sa hýbu poÄítaÄom riadení hrdinovia. MôžeÅ¡ tiež " "nastaviÅ¥, aby sa tento pohyb vôbec nezobrazoval" msgid "Change the interface settings of the game." msgstr "Zmeň nastavenie rozhrania hry." msgid "Interface Settings" msgstr "Nastavenie rozhrania" msgid "Toggle instant battle mode." msgstr "Prepni mód rýchleho boja." msgid "Att." msgstr "Útok" msgid "Def." msgstr "Obr." msgid "Power" msgstr "Sila" msgid "Knowl" msgstr "Ved." msgid "1st" msgstr "Prvý" msgid "2nd" msgstr "Druhý" msgid "3rd" msgstr "Tretí" msgid "4th" msgstr "Å tvrtý" msgid "5th" msgstr "Piaty" msgid "6th" msgstr "Å iesty" msgid "Oracle: Player Rankings" msgstr "Prorok: RebríÄek hráÄov" msgid "Thieves' Guild: Player Rankings" msgstr "Cech zlodejov: RebríÄek hráÄov" msgid "Number of Towns:" msgstr "PoÄet miest:" msgid "Number of Castles:" msgstr "PoÄet hradov:" msgid "Number of Heroes:" msgstr "PoÄet hrdinov:" msgid "Gold in Treasury:" msgstr "Zlato v pokladnici:" msgid "Wood & Ore:" msgstr "Drevo a ruda:" msgid "Gems, Cr, Slf & Mer:" msgstr "Å peciálne suroviny:" msgid "Obelisks Found:" msgstr "Nájdených obeliskov:" msgid "Artifacts:" msgstr "Artefakty:" msgid "Total Army Strength:" msgstr "Celková sila armády:" msgid "Income:" msgstr "Príjem:" msgid "Best Hero:" msgstr "Najlepší hrdina:" msgid "Best Hero Stats:" msgstr "Vlastn. naj hrdinu:" msgid "Personality:" msgstr "OsobnosÅ¥:" msgid "Best Monster:" msgstr "Najlepší bojovník:" #, fuzzy msgid "Map filename" msgstr "VeľkosÅ¥ mapy" msgid "" "Are you sure you want to load a new map? (Any unsaved changes to the current " "map will be lost.)" msgstr "" "UrÄite chceÅ¡ naÄítaÅ¥ novú mapu? (VÅ¡etky neuložené zmeny v aktuálnej mape sa " "stratia.)" msgid "" "Are you sure you want to create a new map? (Any unsaved changes to the " "current map will be lost.)" msgstr "" "UrÄite chceÅ¡ vytvoriÅ¥ novú mapu? (VÅ¡etky neuložené zmeny v aktuálnej mape sa " "stratia.)" msgid "" "Create a new map, either from scratch or using the random map generator." msgstr "" "VytvoriÅ¥ novú mapu buÄ od zaÄiatku, alebo pomocou generátora náhodných máp." msgid "New Map" msgstr "Nová mapa" msgid "Load Map" msgstr "Nahraj mapu" msgid "Load an existing map." msgstr "NaÄítaÅ¥ existujúcu mapu." msgid "Save Map" msgstr "Ulož mapu" msgid "Save the current map." msgstr "UložiÅ¥ aktuálnu mapu." msgid "Quit out of the map editor." msgstr "UkonÄiÅ¥ prácu v editore máp." #, fuzzy msgid "Objects cannot be placed outside the map." msgstr "Toto kúzlo nemožno použiÅ¥ na lodi." msgid "Monsters cannot be placed on water." msgstr "" msgid "Choose a tile which does not contain any objects." msgstr "" #, fuzzy msgid "Treasures cannot be placed on water." msgstr "Toto kúzlo nemožno použiÅ¥ na lodi." msgid "Heroes cannot be placed on water." msgstr "" msgid "" "A maximum of %{count} heroes of the same color can be placed on the map." msgstr "" #, fuzzy msgid "Artifacts cannot be placed on water." msgstr "Toto kúzlo nemožno použiÅ¥ na lodi." #, fuzzy msgid "Mountains cannot be placed on water." msgstr "Toto kúzlo nemožno použiÅ¥ na lodi." #, fuzzy msgid "Rocks cannot be placed on water." msgstr "Toto kúzlo nemožno použiÅ¥ na lodi." #, fuzzy msgid "Trees cannot be placed on water." msgstr "Toto kúzlo nemožno použiÅ¥ na lodi." #, fuzzy msgid "Ocean object must be placed on water." msgstr "Toto kúzlo nemožno použiÅ¥ na lodi." #, fuzzy msgid "Landscape objects cannot be placed on water." msgstr "Toto kúzlo nemožno použiÅ¥ na lodi." #, fuzzy msgid "Towns cannot be placed on water." msgstr "Toto kúzlo nemožno použiÅ¥ na lodi." #, fuzzy msgid "Mines cannot be placed on water." msgstr "Toto kúzlo nemožno použiÅ¥ na lodi." #, fuzzy msgid "Dwellings cannot be placed on water." msgstr "Toto kúzlo nemožno použiÅ¥ na lodi." #, fuzzy msgid "Power-ups cannot be placed on water." msgstr "Toto kúzlo nemožno použiÅ¥ na lodi." #, fuzzy msgid "Adventure objects cannot be placed on water." msgstr "Toto kúzlo nemožno použiÅ¥ na lodi." #, fuzzy msgid "Used to place %{object}." msgstr "Slúži na umiestnenie hrdinu." #, fuzzy msgid "Select object type" msgstr "Objekty oceánu" #, fuzzy msgid "" "Click here to\n" "select a monster." msgstr "Klikni sem a vyber scenár, ktorý chceÅ¡ hraÅ¥." #, fuzzy msgid "" "Click here to\n" "select another monster." msgstr "Prosím zvoľ ÄalÅ¡ieho hrdinu." msgid "" "Cell\n" "Details" msgstr "" #, fuzzy msgid "Roads" msgstr "Cesta" #, fuzzy msgid "Streams" msgstr "Režim tokov" #, fuzzy msgid "Erase" msgstr "Režim mazania" msgid "Mountains" msgstr "Hory" #, fuzzy msgid "Rocks" msgstr "Skala" msgid "Trees" msgstr "Stromy" #, fuzzy msgid "Water Objects" msgstr "Objekty pustatiny" #, fuzzy msgid "Miscellaneous" msgstr "Fineous" msgid "Artifacts" msgstr "Artefakty" #, fuzzy msgid "Dwellings" msgstr "PolÄíci" msgid "Mines" msgstr "Bane" #, fuzzy msgid "Power-ups" msgstr "Sila" msgid "Treasures" msgstr "Poklady" msgid "Towns" msgstr "Mestá" msgid "Heroes" msgstr "Hrdinovia" msgid "editorErasure|Terrain" msgstr "" msgid "editorErasure|Castles" msgstr "" msgid "editorErasure|Monsters" msgstr "" msgid "editorErasure|Heroes" msgstr "" msgid "editorErasure|Artifacts" msgstr "" msgid "editorErasure|Roads" msgstr "" msgid "editorErasure|Streams" msgstr "" msgid "editorErasure|Treasures" msgstr "" msgid "" "Draws terrain in\n" "%{size} by %{size} square increments." msgstr "" "Kreslí terén v\n" "%{size} krát %{size} Å¡tvorcových krokoch." msgid "" "Erases objects in\n" "%{size} by %{size} square increments." msgstr "" "Maže terén v\n" "%{size} krát %{size} Å¡tvorcových krokoch." msgid "Small Brush" msgstr "Malý Å¡tetec" msgid "Medium Brush" msgstr "Stredný Å¡tetec" msgid "Large Brush" msgstr "Veľký Å¡tetec" msgid "Area Fill" msgstr "Výplň oblasti" msgid "Used to click and drag for filling in large areas." msgstr "Slúži na kliknutie a Å¥ahanie na vyplnenie veľkých plôch." msgid "Clear Area" msgstr "VyÄistiÅ¥ oblasÅ¥" msgid "Used to click and drag for clearing large areas." msgstr "Slúži na kliknutie a Å¥ahanie na vyÄistenie veľkých plôch." msgid "" "Costs %{rate} times normal movement for all heroes. (Pathfinding reduces or " "eliminates the penalty.)" msgstr "" "Stojí %{rate} krát normálny pohyb pre vÅ¡etkých hrdinov. (Hľadanie cesty " "znižuje alebo odstraňuje tento postih.)" msgid "Traversable only by boat." msgstr "Prejazdné len loÄou." msgid "No special modifiers." msgstr "Žiadne zvláštne modifikátory." msgid "Toggle the erasure of %{type} objects." msgstr "" msgid "" "Objects of this type will be deleted with the Erase tool. Left-click here to " "deselect this type. Press and hold this button to deselect all other object " "types." msgstr "" msgid "" "Objects of this type will NOT be deleted with the Erase tool. Left-click " "here to select this type. Press and hold this button to select all other " "object types." msgstr "" msgid "Terrain Mode" msgstr "Režim terénu" msgid "Used to draw the underlying grass, dirt, water, etc. on the map." msgstr "Slúži na kreslenie podkladovej trávy, hliny, vody atÄ. na mapu." #, fuzzy msgid "Landscape Objects Mode" msgstr "Objektový režim" #, fuzzy msgid "" "Used to place landscape objects (mountains, rocks, trees, etc.) on the map." msgstr "Slúži na umiestnenie objektov (hôr, stromov, pokladov atÄ.) na mapu." msgid "Detail Mode" msgstr "Režim detailov" msgid "Used for special editing of monsters, heroes and towns." msgstr "Slúži na Å¡peciálne úpravy príšer, hrdinov a miest." #, fuzzy msgid "Adventure Objects Mode" msgstr "Možnosti hry" #, fuzzy msgid "" "Used to place adventure objects (artifacts, dwellings, mines, treasures, " "etc.) on the map." msgstr "Slúži na umiestnenie objektov (hôr, stromov, pokladov atÄ.) na mapu." #, fuzzy msgid "Kingdom Objects Mode" msgstr "Objektový režim" #, fuzzy msgid "Used to place kingdom objects (towns, castles and heroes) on the map." msgstr "Slúži na umiestnenie objektov (hôr, stromov, pokladov atÄ.) na mapu." #, fuzzy msgid "Monsters Mode" msgstr "Bojovníci" #, fuzzy msgid "Used to place monsters on the map." msgstr "" "Slúži na umiestnenie\n" "skupiny tvorov." msgid "Allows you to draw streams by clicking and dragging." msgstr "Umožňuje kresliÅ¥ toky kliknutím a Å¥ahaním." msgid "Stream Mode" msgstr "Režim tokov" msgid "Allows you to draw roads by clicking and dragging." msgstr "Umožňuje kresliÅ¥ cesty kliknutím a Å¥ahaním." msgid "Road Mode" msgstr "Cestný režim" msgid "Erase Mode" msgstr "Režim mazania" #, fuzzy msgid "Used to erase objects from the map." msgstr "Slúži na vymazanie objektov z mapy." msgid "Change between zoom and normal view." msgstr "Prepínanie medzi priblížením a normálnym zobrazením." msgid "Magnify" msgstr "ZväÄÅ¡iÅ¥" msgid "Undo" msgstr "VrátiÅ¥" msgid "Undo your last action." msgstr "" msgid "Create a new map either from scratch or using the random map generator." msgstr "" "VytvoriÅ¥ novú mapu buÄ od zaÄiatku, alebo pomocou generátora náhodných máp." msgid "Edit map title, description, and other general information." msgstr "UpraviÅ¥ názov mapy, popis a ÄalÅ¡ie vÅ¡eobecné informácie." msgid "Specifications" msgstr "Parametre" msgid "File Options" msgstr "Súborové menu" msgid "" "Open the file options menu, where you can save or load maps, or quit out of " "the editor." msgstr "" "Otvorte súborové menu, v ktorom môžete ukladaÅ¥ alebo naÄítavaÅ¥ mapy, " "prípadne ukonÄiÅ¥ prácu s editorom." msgid "View the editor system options, which let you customize the editor." msgstr "" "ZobraziÅ¥ systémové možnosti editora, ktoré umožňujú prispôsobiÅ¥ editor." msgid "Create a map that is %{size} squares wide and %{size} squares high." msgstr "" "VytvoriÅ¥ mapu, ktorá má šírku %{size} Å¡tvorcov a výšku %{size} Å¡tvorcov." msgid "Cancel back to the New Map menu." msgstr "ZruÅ¡enie a návrat do ponuky Nová mapa." msgid "Cancel back to the main menu." msgstr "ZruÅ¡iÅ¥ a späť do hlavného menu." msgid "From Scratch" msgstr "Od nuly" msgid "Start from scratch with a blank map." msgstr "ZaÄaÅ¥ od nuly s prázdnou mapou." msgid "Create a randomly generated map." msgstr "VytvoriÅ¥ náhodne vygenerovanú mapu." msgid "Random" msgstr "Náhodná" msgid "Cancel back to the Map Editor main menu." msgstr "ZruÅ¡enie a návrat do hlavnej ponuky Editora máp." msgid "No maps available!" msgstr "Žiadne dostupné mapy!" msgid "Warning" msgstr "Pozor" msgid "difficulty|Easy" msgstr "Ľahká" msgid "difficulty|Normal" msgstr "Normálna" msgid "difficulty|Hard" msgstr "Ťažká" msgid "difficulty|Expert" msgstr "Expertná" msgid "difficulty|Impossible" msgstr "Nemožná" msgid "and more..." msgstr "a viac..." msgid "Easy" msgstr "Ľahká" msgid "Hard" msgstr "Ťažká" msgid "Campaign Difficulty" msgstr "ObtiažnosÅ¥ kampane" msgid "" "Choose this difficulty if you want to prefer game story over challenge. AI " "is weaker in comparison with normal difficulty." msgstr "" "Zvoľ túto obtiažnosÅ¥ pokiaľ preferujeÅ¡ príbeh nad výzvou. PoÄítaÄ je slabší " "v porovnaní s normálnou obtiažnosÅ¥ou." msgid "" "Choose this difficulty to enjoy the campaign as per the original design." msgstr "Zvoľ túto obtiažnosÅ¥ pre pôžitok z pôvodne mieneného návrhu." msgid "" "Choose this difficulty if you want challenge. AI is stronger in comparison " "with normal difficulty." msgstr "" "Zvoľ túto obtiažnosÅ¥ pokiaľ chceÅ¡ výzvu. PoÄítaÄ je silnejší v porovnaní s " "normálnou obtiažnosÅ¥ou." msgid "" "Congratulations!\n" "\n" "Days: %{days}\n" msgstr "" "Gratulujem!\n" "\n" "PoÄet dní: %{days}\n" msgid "" "\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" "\n" "ObtiažnosÅ¥: %{difficulty}\n" "\n" msgid "" "Score: %{score}\n" "\n" "Rating:\n" "%{rating}" msgstr "" "Skóre: %{score}\n" "\n" "HodnosÅ¥:\n" "%{rating}" msgid "Start the selected scenario." msgstr "ZaÄni zvolený scenár." msgid "View Intro" msgstr "Ukáž intro" msgid "View Intro videos for the current state of the campaign." msgstr "Ukáž intro video pre terajší stav kampane." msgid "" "Select the campaign difficulty. This can be lowered at any point during the " "campaign." msgstr "Zvoľ obtiažnosÅ¥ kampane. Neskôr sa bude daÅ¥ kedykoľvek znížiÅ¥." msgid "Restart the current scenario." msgstr "ZaÄni terajší scenár od zaÄiatku." msgid "Difficulty" msgstr "ObtiažnosÅ¥" msgid "" "You have changed to a lower difficulty for the campaign. You will not be " "able to revert this after this point. The high score will be calculated " "based solely on the new difficulty. Do you want to proceed?" msgstr "" "V kampani prechádzaÅ¡ na nižšiu obtiažnosÅ¥. Po tomto bode to už nebudete môcÅ¥ " "vrátiÅ¥ späť. NajlepÅ¡ie skóre sa vypoÄíta výluÄne na základe novej " "obtiažnosti. ChceÅ¡ pokraÄovaÅ¥?" msgid "Are you sure you want to restart this scenario?" msgstr "UrÄite chceÅ¡ zaÄaÅ¥ terajší scenár od zaÄiatku?" msgid "Campaign Scenario loading failure" msgstr "Chyba pri nahrávaní scenára kampane" msgid "Please make sure that campaign files are correct and present." msgstr "Uisti sa, že súbory kampane sú správne a sú prítomné." msgid "Days spent" msgstr "Strávené dni" msgid "The number of days spent on this campaign." msgstr "PoÄet dní strávených na tejto kampani." msgid "Project Coordination and Core Development" msgstr "Koordinácia projektu a hlavný vývoj" msgid "Development" msgstr "Vývoj" msgid "Visit us at " msgstr "NavÅ¡tív nás na " msgid "QA and Support" msgstr "ZabezpeÄenie kvality a podpora" msgid "Dev and Support" msgstr "Vývoj a podpora" msgid "Special Thanks to" msgstr "Å peciálne poÄakovanie pre" msgid "and many other contributors!" msgstr "a mnoho Äalších prispievateľov!" msgid "and many-many other supporters!" msgstr "a mnoho Äalších podporovateľov!" msgid "Support us at" msgstr "Prispej na" msgid "local-donation-platform|https://www.patreon.com/fheroes2" msgstr "https://www.patreon.com/fheroes2" msgid "Connect with us at" msgstr "Spoj sa s nami na" msgid "local-social-network|https://www.facebook.com/groups/fheroes2" msgstr "https://www.facebook.com/groups/fheroes2" msgid "Need help with the game?" msgstr "PotrebujeÅ¡ pomoc s hrou?" msgid "Original project before 0.7" msgstr "Originálny projekt pred 0.7" msgid "Heroes of Might and Magic II: The Succession Wars team" msgstr "Heroes of Might and Magic II: The Succession Wars tím" msgid "Designed and Directed" msgstr "Navrhnuté a režírované" msgid "Programming and Design" msgstr "Programovanie a dizajn" msgid "Executive Producer" msgstr "Výkonný producent" msgid "Producer" msgstr "Producent" msgid "Additional Design" msgstr "DodatoÄný dizajn" msgid "Additional Programming" msgstr "DodatoÄné programovanie" msgid "Musical Production" msgstr "Hudobná produkcia" msgid "Music and Sound Design" msgstr "Hudobný a zvukový dizajn" msgid "Vocalists" msgstr "Vokalisti" msgid "Art Director" msgstr "Réžia umenia" msgid "Assistant Art Director" msgstr "Asistent réžii umenia" msgid "Artists" msgstr "Umelci" msgid "QA Manager" msgstr "Manažér zabezpeÄenia kvality" msgid "QA" msgstr "ZabezpeÄenie kvality" msgid "Writing" msgstr "Písanie" msgid "Manual and Helpfile" msgstr "Manuál a súbor pomoci" msgid "Scenarios" msgstr "Scenáre" msgid "Heroes of Might and Magic II: The Price of Loyalty team" msgstr "Heroes of Might and Magic II: The Price of Loyalty tím" msgid "Design Lead" msgstr "Vedenie dizajnu" msgid "Designers" msgstr "Dizajnéri" msgid "Programming Lead" msgstr "Vedenie programovania" msgid "Art Lead" msgstr "Vedenie umenia" msgid "Playtesters" msgstr "Testovanie hry" msgid "Designer" msgstr "Dizajnér" msgid "Producers" msgstr "Producenti" msgid "QA Managers" msgstr "Manažéri zabezpeÄenia kvality" msgid "Sound Design" msgstr "Zvukový dizajn" msgid "Town Themes" msgstr "Hudba miest" msgid "Alto Sax" msgstr "Alto Saxofón" msgid "Harpsichord and Piano" msgstr "ÄŒembalo a klavír" msgid "Basso Vocal" msgstr "Basso spev" msgid "Soprano Vocal" msgstr "Sopránový spev" msgid "Recorded at %{recordingStudio}" msgstr "Nahrané v %{recordingStudio}" msgid "credits|Manual" msgstr "Manuál" msgid "German Consultant" msgstr "Nemecký konzultant" msgid "Map Designers" msgstr "Návrhári máp" msgid "Package Design" msgstr "Dizajn balíkov" msgid "Your Name" msgstr "Tvoje meno" msgid "Unknown Hero" msgstr "Neznámy hrdina" msgid "Standard" msgstr "Å tandard" msgid "View High Scores for Standard Maps." msgstr "Zobraz najvyššie skóre pre Å¡tandardné mapy." msgid "Campaign" msgstr "Kampaň" msgid "View High Scores for Campaigns." msgstr "Zobraz najvyššie skóre pre kampane." msgid "hotkey|default okay event" msgstr "predvolené ok" msgid "hotkey|default cancel event" msgstr "predvolené zruÅ¡" msgid "hotkey|default left" msgstr "predvolené vľavo" msgid "hotkey|default right" msgstr "predvolené vpravo" msgid "hotkey|default up" msgstr "predvolené hore" msgid "hotkey|default down" msgstr "predvolené dole" msgid "hotkey|toggle fullscreen" msgstr "prepni celú obrazovku" msgid "hotkey|toggle text support mode" msgstr "prepni mód podpory textu" msgid "hotkey|new game" msgstr "nová hra" msgid "hotkey|load game" msgstr "nahraj hru" msgid "hotkey|highscores" msgstr "najvyššie skóre" msgid "hotkey|credits" msgstr "zásluhy" msgid "hotkey|standard game" msgstr "bežná hra" msgid "hotkey|campaign game" msgstr "kampaň" msgid "hotkey|multi-player game" msgstr "hra viacero hráÄov" msgid "hotkey|settings" msgstr "nastavenie" msgid "hotkey|quit" msgstr "ukonÄi" msgid "hotkey|select map" msgstr "zvoľ mapu" msgid "hotkey|select small map size" msgstr "zvoľ mapy veľkosti malá" msgid "hotkey|select medium map size" msgstr "zvoľ mapy veľkosti stredná" msgid "hotkey|select large map size" msgstr "zvoľ mapy veľkosti veľká" msgid "hotkey|select extra large map size" msgstr "zvoľ mapy veľkosti extra veľká" msgid "hotkey|select all map sizes" msgstr "zvoľ mapy vÅ¡etkých veľkostí" msgid "hotkey|hotseat game" msgstr "horúce kreslo" msgid "hotkey|battle only game" msgstr "rýchly boj" msgid "hotkey|choose the original campaign" msgstr "zvoľ pôvodnú kampaň" msgid "hotkey|choose the expansion campaign" msgstr "zvoľ rozšírenú kampaň" msgid "hotkey|map editor main menu" msgstr "hlavné menu editora" msgid "hotkey|new map menu" msgstr "nová mapa" msgid "hotkey|load map menu" msgstr "nahraj mapu" msgid "hotkey|new map from scratch" msgstr "nová mapa, od nuly" msgid "hotkey|new random map" msgstr "nová mapa, náhodná" #, fuzzy msgid "hotkey|undo last action" msgstr "predvolená ÄinnosÅ¥" #, fuzzy msgid "hotkey|redo last action" msgstr "predvolená ÄinnosÅ¥" msgid "hotkey|roland campaign" msgstr "kampaň roland" msgid "hotkey|archibald campaign" msgstr "kampaň archibald" msgid "hotkey|the price of loyalty campaign" msgstr "kampaň cena vernosti" msgid "hotkey|voyage home campaign" msgstr "kampaň plavba domov" msgid "hotkey|wizard's isle campaign" msgstr "kampaň Äarodejov ostrov" msgid "hotkey|descendants campaign" msgstr "kampaň potomkovia" msgid "hotkey|select first campaign bonus" msgstr "zvoľ prvý bonus kampane" msgid "hotkey|select second campaign bonus" msgstr "zvoľ druhý bonus kampane" msgid "hotkey|select third campaign bonus" msgstr "zvoľ tretí bonus kampane" msgid "hotkey|view campaign intro" msgstr "ukáž intro kampane" msgid "hotkey|select campaign difficulty" msgstr "zvoľ obtiažnosÅ¥ kampane" msgid "hotkey|restart campaign scenario" msgstr "reÅ¡tartuj scenár kampane" msgid "hotkey|world map left" msgstr "mapa vľavo" msgid "hotkey|world map right" msgstr "mapa vpravo" msgid "hotkey|world map up" msgstr "mapa hore" msgid "hotkey|world map down" msgstr "mapa dole" msgid "hotkey|world map up left" msgstr "mapa vľavo hore" msgid "hotkey|world map up right" msgstr "mapa vpravo hore" msgid "hotkey|world map down left" msgstr "mapa vľavo dole" msgid "hotkey|world map down right" msgstr "mapa vpravo dole" msgid "hotkey|save game" msgstr "ulož hru" msgid "hotkey|next hero" msgstr "Äalší hrdina" #, fuzzy msgid "hotkey|start hero movement" msgstr "pokraÄuj v pohybe hrdinom" msgid "hotkey|cast adventure spell" msgstr "vyÄaruj výpravné kúzlo" msgid "hotkey|put hero to sleep" msgstr "uspi hrdinu" msgid "hotkey|next town" msgstr "ÄalÅ¡ie mesto" msgid "hotkey|end turn" msgstr "ukonÄi kolo" msgid "hotkey|file options" msgstr "súborové menu" msgid "hotkey|adventure options" msgstr "možnosti výpravy" msgid "hotkey|puzzle map" msgstr "mapa rébusu" msgid "hotkey|scenario information" msgstr "informácie o scenári" msgid "hotkey|dig for artifact" msgstr "kop za artefaktom" msgid "hotkey|view world" msgstr "ukáž svet" msgid "hotkey|kingdom summary" msgstr "súhrn kráľovstva" msgid "hotkey|default action" msgstr "predvolená ÄinnosÅ¥" msgid "hotkey|open focus" msgstr "otvor zaostrenie" msgid "hotkey|system options" msgstr "systémové možnosti" msgid "hotkey|scroll left" msgstr "posun naľavo" msgid "hotkey|scroll right" msgstr "posun napravo" msgid "hotkey|scroll up" msgstr "posun nahor" msgid "hotkey|scroll down" msgstr "posun nadol" msgid "hotkey|toggle control panel" msgstr "prepni kontrolný panel" msgid "hotkey|toggle radar" msgstr "prepni radar" msgid "hotkey|toggle buttons" msgstr "prepni klávesy" msgid "hotkey|toggle status" msgstr "prepni status" msgid "hotkey|toggle icons" msgstr "prepni ikony" msgid "hotkey|transfer control to ai" msgstr "presuň kontrolu poÄítaÄu" msgid "hotkey|retreat from battle" msgstr "uteÄ z boja" msgid "hotkey|surrender during battle" msgstr "vzdaj sa poÄas boja" msgid "hotkey|toggle battle auto mode" msgstr "prepni boj do automatického módu" msgid "hotkey|finish the battle in auto mode" msgstr "ukonÄi boj v automatickom móde" msgid "hotkey|battle options" msgstr "možnosti boja" msgid "hotkey|skip turn in battle" msgstr "preskoÄ kolo v boji" msgid "hotkey|cast battle spell" msgstr "vyÄaruj bojové kúzlo" msgid "hotkey|dwelling level 1" msgstr "obydlie úrovne 1" msgid "hotkey|dwelling level 2" msgstr "obydlie úrovne 2" msgid "hotkey|dwelling level 3" msgstr "obydlie úrovne 3" msgid "hotkey|dwelling level 4" msgstr "obydlie úrovne 4" msgid "hotkey|dwelling level 5" msgstr "obydlie úrovne 5" msgid "hotkey|dwelling level 6" msgstr "obydlie úrovne 6" msgid "hotkey|well" msgstr "studňa" msgid "hotkey|marketplace" msgstr "trhovisko" msgid "hotkey|mage guild" msgstr "cech mágov" msgid "hotkey|shipyard" msgstr "lodenica" msgid "hotkey|thieves guild" msgstr "cech zlodejov" msgid "hotkey|tavern" msgstr "krÄma" msgid "hotkey|construction screen" msgstr "okno stavania" msgid "hotkey|buy all monsters in well" msgstr "kúp vÅ¡etkých v studni" msgid "hotkey|split stack by half" msgstr "rozdeľ skupinu na polovicu" msgid "hotkey|split stack by one" msgstr "rozdeľ skupinu po jednom" msgid "hotkey|join stacks" msgstr "zlÃºÄ skupinu" msgid "hotkey|upgrade troop" msgstr "povýš jednotku" msgid "hotkey|dismiss hero or troop" msgstr "prepusti jednotku alebo hrdinu" msgid "" "Do you want to regain control from AI? The effect will take place only on " "the next turn." msgstr "ChceÅ¡ prevziaÅ¥ kontrolu nad hráÄom? Efekt sa prejaví v ÄalÅ¡om kole." msgid "" "Do you want to transfer control from you to the AI? The effect will take " "place only on the next turn." msgstr "" "ChceÅ¡ preniesÅ¥ kontrolu z teba na umelú inteligenciu? ÚÄinok sa prejaví až " "od ÄalÅ¡ieho kola." #, fuzzy msgid "Default Actions" msgstr "predvolená ÄinnosÅ¥" msgid "Global Actions" msgstr "" msgid "Main Menu" msgstr "" msgid "World Map" msgstr "Mapa sveta" #, fuzzy msgid "Battle Screen" msgstr "Okno hrdinu" #, fuzzy msgid "Town Screen" msgstr "Okno hrdinu" msgid "Army Actions" msgstr "" msgid "Editor" msgstr "" msgid "The save file is corrupted." msgstr "Uložený súbor je poÅ¡kodený." msgid "Unsupported save format: " msgstr "Nepodporovaný formát uloženia: " msgid "Current game version: " msgstr "Aktuálna verzia hry: " msgid "Last supported version: " msgstr "Posledná podporovaná verzia: " msgid "This file contains a save with an invalid game type." msgstr "Tento súbor obsahuje uloženie s neplatným typom hry." #, fuzzy msgid "" "This save file requires \"The Price of Loyalty\" game assets, but they have " "not been provided to the engine." msgstr "" "Tento súbor bol uložený pre mapu pre \"The Price of Loyalty\", ale prísluÅ¡né " "herné prostriedky neboli enginu poskytnuté." msgid "This saved game is localized to '" msgstr "Táto uložená hra je zlokalizovaná na jazyk '" msgid "' language, but the current language of the game is '" msgstr "', ale aktuálny jazyk hry je '" msgid "Hot Seat" msgstr "Horúce kreslo" msgid "" "Play a Hot Seat game, where 2 to 4 players play around the same computer, " "switching into the 'Hot Seat' when it is their turn." msgstr "" "Zahrajte si hru Horúce kreslo, v ktorej 2 až 4 hráÄi hrajú pri tom istom " "poÄítaÄi a keÄ sú na rade, prepnú sa do \"horúceho kresla\"." msgid "A single player game playing out a single map." msgstr "Hra pre jedného hráÄa na jednej mape." msgid "Standard Game" msgstr "Bežná hra" msgid "A single player game playing through a series of maps." msgstr "Hra pre jedného hráÄa so sériou máp." msgid "Campaign Game" msgstr "Kampaň" msgid "Multi-Player Game" msgstr "Hra viacero hráÄov" msgid "" "A multi-player game, with several human players completing against each " "other on a single map." msgstr "" "Hra pre viacerých hráÄov, v ktorej proti sebe na jednej mape nastupuje " "niekoľko ľudských hráÄov." msgid "fheroes2 Resurrection Team presents" msgstr "Tím fheroes2 Resurrection uvádza" msgid "Greetings!" msgstr "Zdravím Å¥a!" msgid "Welcome to Heroes of Might and Magic II powered by fheroes2 engine!" msgstr "Vitaj v hre Heroes of Might and Magic II poháňanej enginom fheroes2!" #, fuzzy msgid "" "Welcome to Heroes of Might and Magic II powered by the fheroes2 engine!\n" "Before starting the game please select a game resolution." msgstr "" "Vitaj v hre Heroes of Might and Magic II poháňanej enginom fheroes2! Pred " "spustením hry si vyber rozlíšenie." msgid "Please Remember" msgstr "Nezabudni, prosím" #, fuzzy msgid "" "You can always change the language, resolution and settings of the game by " "clicking on the " msgstr "Rozlíšenie hry si vždy môžeÅ¡ zmeniÅ¥ kliknutím na " msgid "door" msgstr "dvere" msgid " on the left side of the Main Menu, or with the " msgstr "" msgid " button from the " msgstr "" #, fuzzy msgid "NEW GAME" msgstr "" "NOVÃ\n" "HRA" msgid "" " menu. \n" "\n" "Enjoy the game!" msgstr "" #, fuzzy msgid "The Editor requires \"The Price of Loyalty\" expansion files to work." msgstr "Jedna zo Å¡tyroch nových kampaní z rozÅ¡irujúcej sady Price of Loyalty." msgid "Quit Heroes of Might and Magic II and return to the operating system." msgstr "" "UkonÄiÅ¥ hru Heroes of Might and Magic II a vrátiÅ¥ sa do operaÄného systému." msgid "Credits" msgstr "Zásluhy" msgid "View the credits screen." msgstr "Zobrazenie obrazovky so zásluhami." msgid "High Scores" msgstr "Najvyššie skóre" msgid "View the high scores screen." msgstr "Zobrazenie obrazovky s najvyššími výsledkami." msgid "Change language, resolution and settings of the game." msgstr "Zmena jazyka, rozlíšenia a nastavení hry." msgid "Game Settings" msgstr "Nastavenie hry" msgid "" "Required video files for campaign selection window are missing. Please make " "sure that all necessary files are present in the system." msgstr "" "Chýbajú požadované video súbory pre okno výberu kampane. Uisti sa prosím, že " "máš v systéme vÅ¡etky potrebné súbory." msgid "" "Either Roland's or Archibald's campaign from the original Heroes of Might " "and Magic II." msgstr "" "BuÄ Rolandova, alebo Archibaldova kampaň z pôvodnej hry Heroes of Might and " "Magic II." msgid "Original Campaign" msgstr "Pôvodná kampaň" msgid "Expansion Campaign" msgstr "Kampaň expanzie" msgid "One of the four new campaigns from the Price of Loyalty expansion set." msgstr "Jedna zo Å¡tyroch nových kampaní z rozÅ¡irujúcej sady Price of Loyalty." msgid "Loading video. Please wait..." msgstr "NaÄítanie videa. Chvíľku vydrž, prosím..." msgid "Host" msgstr "Hostiteľ" msgid "" "The host sets up the game options. There can only be one host per network " "game." msgstr "" "Hostiteľ nastaví možnosti hry. V jednej sieÅ¥ovej hre môže byÅ¥ len jeden " "hostiteľ." msgid "Guest" msgstr "HosÅ¥" msgid "" "The guest waits for the host to set up the game, then is automatically added " "in. There can be multiple guests for TCP/IP games." msgstr "" "HosÅ¥ poÄká, kým hostiteľ nastaví hru, a potom sa automaticky pridá do hry. " "Pri hrách cez TCP/IP môže byÅ¥ viacero hostí." msgid "Battle Only" msgstr "Rýchly boj" msgid "Setup and play a battle without loading any map." msgstr "NastaviÅ¥ a hraÅ¥ bitku bez naÄítania mapy." msgid "2 Players" msgstr "2 hráÄi" msgid "" "Play with 2 human players, and optionally, up to 4 additional computer " "players." msgstr "" "Hraj s 2 ľudskými hráÄmi a prípadne až so 4 Äalšími poÄítaÄovými hráÄmi." msgid "3 Players" msgstr "3 hráÄi" msgid "" "Play with 3 human players, and optionally, up to 3 additional computer " "players." msgstr "" "Hraj s 3 ľudskými hráÄmi a prípadne až s 3 Äalšími poÄítaÄovými hráÄmi." msgid "4 Players" msgstr "4 hráÄi" msgid "" "Play with 4 human players, and optionally, up to 2 additional computer " "players." msgstr "" "Hraj so 4 ľudskými hráÄmi a prípadne až s 2 Äalšími poÄítaÄovými hráÄmi." msgid "5 Players" msgstr "5 hráÄov" msgid "" "Play with 5 human players, and optionally, up to 1 additional computer " "player." msgstr "Hraj s 5 ľudskými hráÄmi a prípadne až s 1 Äalším poÄítaÄovým hráÄom." msgid "6 Players" msgstr "6 hráÄov" msgid "Play with 6 human players." msgstr "Hraj so 6 hráÄmi." msgid "Dragon city has fallen! You are now the Master of the Dragons." msgstr "DraÄie mesto padlo! Teraz si Majstrom drakov." msgid "" "You captured %{name}!\n" "You are victorious." msgstr "" "Dobyl/a si %{name}!\n" "Víťazíš." msgid "" "You have captured the enemy hero %{name}!\n" "Your quest is complete." msgstr "" "Zajal/a si nepriateľského hrdinu %{name}!\n" "Tvoja úloha je splnená." msgid "" "You have found the %{name}.\n" "Your quest is complete." msgstr "" "Podarilo sa ti nájsÅ¥ %{name}.\n" "Tvoja úloha je splnená." msgid "Ultimate Artifact" msgstr "Ultimátny artefakt" msgid "" "The enemy is beaten.\n" "Your side has triumphed!" msgstr "" "Nepriateľ je porazený.\n" "Tvoja strana zvíťazila!" msgid "" "You have built up over %{count} gold in your treasury.\n" "All enemies bow before your wealth and power." msgstr "" "Vo svojej pokladnici si nahromadil viac ako %{count} zlata.\n" "VÅ¡etci nepriatelia sa skláňajú pred tvojím bohatstvom a mocou." msgid "Victory!" msgstr "Víťazstvo!" msgid "" "The enemy has captured %{name}!\n" "They are triumphant." msgstr "" "Nepriateľ obsadil %{name}!\n" "Triumfálne zvíťazili." msgid "" "The enemy has built up over %{count} gold in his treasury.\n" "You must bow done in defeat before his wealth and power." msgstr "" "Nepriateľ si vo svojej pokladnici nahromadil viac ako %{count} zlata.\n" "Musíš sa porazene skloniÅ¥ pred jeho bohatstvom a mocou." msgid "You have been eliminated from the game!!!" msgstr "Bol/a si eliminovaný/á z hry!!!" msgid "" "You have lost the hero %{name}.\n" "Your quest is over." msgstr "" "Hrdina %{name} bol stratený.\n" "Tvoja výprava sa skonÄila." msgid "" "You have failed to complete your quest in time.\n" "All is lost." msgstr "" "Nepodarilo sa ti vÄas dokonÄiÅ¥ úlohu.\n" "VÅ¡etko je stratené." msgid "Defeat!" msgstr "Porážka!" msgid "" "Base score: %{score}\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" "Základné skóre: %{score}\n" "ObtiažnosÅ¥: %{difficulty}\n" "\n" msgid "Defeat all enemy heroes and capture all enemy towns and castles." msgstr "" "Poraz vÅ¡etkých nepriateľských hrdinov a získaj vÅ¡etky nepriateľské mestá a " "hrady." msgid "Run out of time. (Fail to win by a certain point.)" msgstr "Vyprší ti Äas. (Nevyhráš do urÄitého bodu)." msgid "You must defeat the enemy %{enemies}." msgid_plural "You must defeat the enemy alliance of %{enemies}." msgstr[0] "Musíš poraziÅ¥ nepriateľa: %{enemies}." msgstr[1] "Musíš poraziÅ¥ nepriateľskú alianciu: %{enemies}." msgstr[2] "Musíš poraziÅ¥ nepriateľskú alianciu: %{enemies}." msgid "" "The alliance consisting of %{allies} and you must defeat the enemy " "%{enemies}." msgid_plural "" "The alliance consisting of %{allies} and you must defeat the enemy alliance " "of %{enemies}." msgstr[0] "" "Aliancia pozostávajúca z %{allies} a teba musí poraziÅ¥ nepriateľa %{enemies}." msgstr[1] "" "Aliancia pozostávajúca z %{allies} a teba musí poraziÅ¥ alianciu z %{enemies}." msgstr[2] "" "Aliancia pozostávajúca z %{allies} a teba musí poraziÅ¥ alianciu z %{enemies}." msgid "Capture the castle '%{name}'." msgstr "ObsaÄ hrad '%{name}'." msgid "Capture the town '%{name}'." msgstr "ObsaÄ mesto '%{name}'." msgid "Defeat the hero '%{name}'." msgstr "Poraz hrdinu '%{name}'." msgid "Find the ultimate artifact." msgstr "Nájdi ultimátny artefakt." msgid "Find the '%{name}' artifact." msgstr "Nájdi artefakt '%{name}'." msgid "Accumulate %{count} gold." msgstr "NahromaÄ %{count} zlata." msgid "" ", or you may win by defeating all enemy heroes and capturing all enemy towns " "and castles." msgstr "" "alebo môžeÅ¡ vyhraÅ¥, ak porazíš vÅ¡etkých nepriateľských hrdinov a dobyjeÅ¡ " "vÅ¡etky nepriateľské mestá a hrady." msgid "Lose the castle '%{name}'." msgstr "Stratíš hrad '%{name}'." msgid "Lose the town '%{name}'." msgstr "Stratíš mesto '%{name}'." msgid "Lose the hero: %{name}." msgstr "Stratíš hrdinu '%{name}'." msgid "Fail to win by the end of month %{month}, week %{week}, day %{day}." msgstr "" "Nepodarí sa ti vyhraÅ¥ do konca %{month}. mesiaca, %{week}. týždňa a %{day}. " "dňa." msgid "%{color} player has been vanquished!" msgstr "%{color} hrÃ¡Ä bol porazený!" msgid "Major Event!" msgstr "Významná udalosÅ¥!" msgid "Scenario:" msgstr "Scenár:" msgid "Game Difficulty:" msgstr "ObtiažnosÅ¥ hry:" msgid "Opponents:" msgstr "Súperi:" msgid "Class:" msgstr "Trieda:" msgid "Rating %{rating}%" msgstr "Hodnotenie: %{rating}%" msgid "Click here to select which scenario to play." msgstr "Klikni sem a vyber scenár, ktorý chceÅ¡ hraÅ¥." msgid "Scenario" msgstr "Scenár" msgid "Game Difficulty" msgstr "ObtiažnosÅ¥ hry" msgid "" "This lets you change the starting difficulty at which you will play. Higher " "difficulty levels start you of with fewer resources, and at the higher " "settings, give extra resources to the computer." msgstr "" "Tu si môžeÅ¡ zmeniÅ¥ poÄiatoÄnú obtiažnosÅ¥, s ktorou budeÅ¡ hraÅ¥. Pri vyšších " "úrovniach obtiažnosti zaÄneÅ¡ s menším množstvom zdrojov a pri vyšších " "nastaveniach dostane ÄalÅ¡ie zdroje poÄítaÄ." msgid "Difficulty Rating" msgstr "Hodnotenie obtiažnosti" msgid "" "The difficulty rating reflects a combination of various settings for your " "game. This number will be applied to your final score." msgstr "" "Hodnotenie obtiažnosti odráža kombináciu rôznych nastavení pre tvoju hru. " "Toto Äíslo sa použije na tvoje koneÄné skóre." msgid "Click to accept these settings and start a new game." msgstr "Kliknutím akceptujeÅ¡ tieto nastavenia a spustíš novú hru." msgid "Click to return to the main menu." msgstr "Kliknutím sa vrátiÅ¡ do hlavnej ponuky." msgid "Astrologers proclaim the Month of the %{name}." msgstr "Astrológovia vyhlasujú mesiac %{name}." msgid "Astrologers proclaim the Week of the %{name}." msgstr "Astrológovia vyhlasujú týždeň %{name}." msgid "After regular growth, the population of %{monster} is doubled!" msgstr "%{monster} budú maÅ¥ po bežnom raste dvojnásobnú populáciu!" msgid "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgid_plural "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgstr[0] "%{monster} budú maÅ¥ po bežnom raste nárast o %{count} percento!" msgstr[1] "%{monster} budú maÅ¥ po bežnom raste nárast o %{count} percentá!" msgstr[2] "%{monster} budú maÅ¥ po bežnom raste nárast o %{count} percent!" msgid "%{monster} growth +%{count}." msgstr "%{monster} vzrástli o %{count}." msgid " All populations are halved." msgstr " VÅ¡etky populácie sú znížené na polovicu." msgid " All dwellings increase population." msgstr " VÅ¡etky obydlia zvyÅ¡ujú poÄet obyvateľov." msgid "New Month!" msgstr "Nový mesiac!" msgid "New Week!" msgstr "Nový týždeň!" msgid "Beware!" msgstr "Pozor!" msgid "" "%{color} player, this is your last day to capture a town, or you will be " "banished from this land." msgstr "" "%{color} hráÄ, toto je tvoj posledný deň na dobytie mesta, inak budeÅ¡ " "vyhnaný z tejto krajiny." msgid "" "%{color} player, you only have %{day} days left to capture a town, or you " "will be banished from this land." msgstr "" "%{color} hráÄ, máš už len %{day} dni na dobytie mesta, inak budeÅ¡ vyhostený " "z tejto krajiny." msgid "%{color} player's turn." msgstr "%{color} hrÃ¡Ä je na Å¥ahu." #, fuzzy msgid "" "%{color} player, you have lost your last town. If you do not conquer another " "town in the next week, you will be eliminated." msgstr "" "%{color} hráÄ, stratil si svoje posledné mesto. Ak v nasledujúcom týždni " "nedobyjeÅ¡ ÄalÅ¡ie mesto, budeÅ¡ eliminovaný." msgid "" "%{color} player, your heroes abandon you, and you are banished from this " "land." msgstr "" "%{color} hráÄ, tvoji hrdinovia Å¥a opustili a ty si vyhostený z tejto krajiny." msgid "Lord Kilburn" msgstr "Lord Kilburn" msgid "Tsabu" msgstr "Tsabu" msgid "Sir Galant" msgstr "Sir Galant" msgid "Thundax" msgstr "Thundax" msgid "Lord Haart" msgstr "Lord Haart" msgid "Ariel" msgstr "Ariel" msgid "Rebecca" msgstr "Rebecca" msgid "Sandro" msgstr "Sandro" msgid "Crodo" msgstr "Crodo" msgid "Barock" msgstr "Barock" msgid "Antoine" msgstr "Antoine" msgid "Astra" msgstr "Astra" msgid "Agar" msgstr "Agar" msgid "Vatawna" msgstr "Vatawna" msgid "Vesper" msgstr "Vesper" msgid "Ambrose" msgstr "Ambrose" msgid "Troyan" msgstr "Troyan" msgid "Jojosh" msgstr "Jojosh" msgid "Wrathmont" msgstr "Wrathmont" msgid "Maximus" msgstr "Maximus" msgid "Next Hero" msgstr "ÄŽalší hrdina" msgid "Select the next Hero." msgstr "VybraÅ¥ ÄalÅ¡ieho Hrdinu." #, fuzzy msgid "Hero Movement" msgstr "Tieň pohybu" msgid "" "Start the Hero's movement along the current path or re-visit the object " "occupied by the Hero. Press and hold this button to reset the Hero's path." msgstr "" msgid "Kingdom Summary" msgstr "Zhrnutie kráľovstva" msgid "View a Summary of your Kingdom." msgstr "Pozri si prehľad svojho kráľovstva." msgid "Cast an adventure spell." msgstr "ZoslaÅ¥ kúzlo." msgid "End Turn" msgstr "UkonÄiÅ¥ Å¥ah" msgid "End your turn and left the computer take its turn." msgstr "UkonÄi svoj Å¥ah a nechaj tiahnuÅ¥ poÄítaÄ." msgid "Adventure Options" msgstr "Možnosti hry" msgid "Bring up the adventure options menu." msgstr "ZobraziÅ¥ menu dobrodružstva." msgid "" "Bring up the file options menu, allowing you to load, save, start a new game " "or quit." msgstr "" "ZobraziÅ¥ voľby pre uloženie a naÄítanie hry, spustenie novej hry alebo " "ukonÄenie hry." msgid "Bring up the system options menu, allowing you to customize your game." msgstr "" "ZobraziÅ¥ ponuku systémových možností, ktorá umožňuje prispôsobiÅ¥ si hru." msgid "" "One or more heroes may still move, are you sure you want to end your turn?" msgstr "" "Jeden alebo viac hrdinov sa eÅ¡te môže pohnúť, naozaj chceÅ¡ ukonÄiÅ¥ svoj Å¥ah?" msgid "Are you sure you want to quit?" msgstr "UrÄite chceÅ¡ skonÄiÅ¥?" msgid "Are you sure you want to restart? (Your current game will be lost.)" msgstr "UrÄite chceÅ¡ reÅ¡tartovaÅ¥ hru? (Tvoja aktuálna hra sa stratí.)" msgid "Are you sure you want to overwrite the save with this name?" msgstr "Naozaj chceÅ¡ prepísaÅ¥ uložený súbor s týmto názvom?" msgid "Game saved successfully." msgstr "Hra bola úspeÅ¡ne uložená." msgid "There was an issue during saving." msgstr "Pri ukladaní nastal problém." msgid "" "Are you sure you want to load a new game? (Your current game will be lost.)" msgstr "UrÄite chceÅ¡ naÄítaÅ¥ novú hru? (Tvoja aktuálna hra sa stratí.)" msgid "Try looking on land!!!" msgstr "Skús hľadaÅ¥ na súši!!!" msgid "" "Searching for the Ultimate Artifact is fruitless. Your hero could not carry " "it even if he found it - all his artifact slots are full." msgstr "" "Hľadanie ultimátneho artefaktu je márne. Tvoj hrdina by ho nemohol nosiÅ¥, " "ani keby ho naÅ¡iel - vÅ¡etky jeho sloty na artefakty sú plné." msgid "Digging for artifacts requires a whole day, try again tomorrow." msgstr "Kopanie artefaktov si vyžaduje celý deň, skús to zajtra." msgid "" "After spending many hours digging here, you have uncovered the %{artifact}." msgstr "" "Po mnohých hodinách strávených kopaním na tomto mieste odkrývaÅ¡ %{artifact}." msgid "Congratulations!" msgstr "Gratulujem!" msgid "Nothing here. Where could it be?" msgstr "Tu niÄ nie je. Kde by to mohlo byÅ¥?" msgid "Try searching on clear ground." msgstr "Skús hľadaÅ¥ na Äistom teréne." msgid "A miniature view of the known world. Left click to move viewing area." msgstr "" "Miniatúrny pohľad na známy svet. Kliknutím ľavým tlaÄidlom myÅ¡i presunieÅ¡ " "oblasÅ¥ zobrazenia." msgid "Month: %{month} Week: %{week}" msgstr "Mesiac: %{month} Týždeň: %{week}" msgid "Day: %{day}" msgstr "Deň: %{day}" msgid "" "You find a small\n" "quantity of %{resource}." msgstr "" "NachádzaÅ¡ malé\n" "množstvo: %{resource}." msgid "Status Window" msgstr "Stavové okno" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date." msgstr "" "Toto okno poskytuje informácie o stave tvojho hrdinu alebo kráľovstva a " "zobrazuje dátum." msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date. Left click here to cycle through these windows." msgstr "" "Toto okno poskytuje informácie o stave tvojho hrdinu alebo kráľovstva a " "zobrazuje dátum. Kliknutím ľavým tlaÄidlom myÅ¡i tu môžeÅ¡ prechádzaÅ¥ týmito " "oknami." msgid "" "This lets you change player starting positions and colors. A particular " "color will always start in a particular location. Some positions may only be " "played by a computer player or only by a human player." msgstr "" "Tu môžeÅ¡ meniÅ¥ poÄiatoÄné pozície a farby hráÄov. UrÄitá farba bude vždy " "zaÄínaÅ¥ na urÄitom mieste. Niektoré pozície môže hraÅ¥ iba poÄítaÄový hrÃ¡Ä " "alebo iba ľudský hráÄ." msgid "" "This lets you change the class of a player. Classes are not always " "changeable. Depending on the scenario, a player may receive additional towns " "and/or heroes not of their primary alignment." msgstr "" "Toto ti umožní zmeniÅ¥ triedu hráÄa. Triedy nie je vždy možné meniÅ¥. V " "závislosti od scenára môže hrÃ¡Ä získaÅ¥ ÄalÅ¡ie mestá a/alebo hrdinov, ktorí " "nepatria do jeho primárnej línie." msgid "Handicap" msgstr "Hendikep" # msgid "" "This lets you change the handicap of a particular player. Only humans may be " "handicapped. Handicapped players start with fewer resources and earn 15 or " "30% fewer resources per turn for mild and severe handicaps, respectively." msgstr "" "Tu je možné zmeniÅ¥ hendikep konkrétneho hráÄa. Hendikep môžu maÅ¥ len ľudia. " "Hendikepovaní hráÄi zaÄínajú s menším množstvom zdrojov a získavajú o 15, " "resp. 30% menej zdrojov za Å¥ah v prípade mierneho a Å¥ažkého hendikepu." msgid "%{color} player" msgstr "%{color} hráÄ" msgid "No Handicap" msgstr "Bez hendikepu" msgid "No special restrictions on start resources and earning them per turn." msgstr "" "Žiadne osobitné obmedzenia týkajúce sa poÄiatoÄných zdrojov a ich získavania " "za Å¥ah." msgid "Mild Handicap" msgstr "Mierny hendikep" # msgid "" "Mild handicapped players start with fewer resources and earn 15% fewer " "resources per turn." msgstr "" "HráÄi s miernym hendikepom zaÄínajú s menším poÄtom zdrojov a získavajú o " "15% menej zdrojov za Å¥ah." msgid "Severe Handicap" msgstr "Ťažký hendikep" # msgid "" "Severe handicapped players start with fewer resources and earn 30% fewer " "resources per turn." msgstr "" "HráÄi s Å¥ažkým hendikepom zaÄínajú s menším poÄtom zdrojov a získavajú o 30% " "menej zdrojov za Å¥ah." msgid "View %{skill} Info" msgstr "Zobraz info: %{skill}" msgid "Keyboard|123" msgstr "123" msgid "Keyboard|SPACE" msgstr "MEDZERNÃK" msgid "Keyboard|ABC" msgstr "ABC" msgid "Kingdom Income" msgstr "Príjmy kráľovstva" msgid "Kingdom Income per day." msgstr "Denné príjmy kráľovstva." msgid "For every lighthouse controlled, your ships will move further each day." msgstr "Za každý kontrolovaný maják sa tvoje lode každý deň dostanú Äalej." msgid "English" msgstr "Anglicky" msgid "French" msgstr "Francúzsky" msgid "Polish" msgstr "Poľsky" msgid "German" msgstr "Nemecky" msgid "Russian" msgstr "Rusky" msgid "Italian" msgstr "Taliansky" msgid "Czech" msgstr "ÄŒesky" msgid "Norwegian" msgstr "Nórsky" msgid "Belarusian" msgstr "Bielorusky" msgid "Bulgarian" msgstr "Bulharsky" msgid "Ukrainian" msgstr "Ukrajinsky" msgid "Romanian" msgstr "Rumunsky" msgid "Spanish" msgstr "Å panielsky" msgid "Swedish" msgstr "Å védsky" msgid "Portuguese" msgstr "Portugalsky" msgid "Turkish" msgstr "Turecky" msgid "Dutch" msgstr "Holandsky" msgid "Hungarian" msgstr "MaÄarsky" msgid "Danish" msgstr "Dánsky" msgid "Slovak" msgstr "Slovensky" msgid "Vietnamese" msgstr "Vietnamsky" msgid ", FPS: " msgstr ", FPS: " msgid "%{object} robber" msgstr "%{object} vykradnutý" msgid "%{object} raided" msgstr "%{object} vyplienená" msgid "Ector" msgstr "Ector" msgid "Gwenneth" msgstr "Gwenneth" msgid "Sir Gallant" msgstr "Sir Gallant" msgid "Tyro" msgstr "Tyro" msgid "Dimitry" msgstr "Dimitri" msgid "Ruby" msgstr "Ruby" msgid "Crag Hack" msgstr "Crag Hack" msgid "Fineous" msgstr "Fineous" msgid "Jezebel" msgstr "Jezabel" msgid "Atlas" msgstr "Atlas" msgid "Ergon" msgstr "Ergon" msgid "Jaclyn" msgstr "Jaclyn" msgid "Gem" msgstr "Zafíra" msgid "Natasha" msgstr "NataÅ¡a" msgid "Carlawn" msgstr "Carlawn" msgid "Luna" msgstr "Luna" msgid "Arie" msgstr "Arie" msgid "Barok" msgstr "Barok" msgid "Kastore" msgstr "Kastore" msgid "Falagar" msgstr "Falagar" msgid "Dawn" msgstr "Dawn" msgid "Flint" msgstr "Flint" msgid "Halon" msgstr "Halon" msgid "Myra" msgstr "Myra" msgid "Myrini" msgstr "Myrini" msgid "Wilfrey" msgstr "Wilfrey" msgid "Mandigal" msgstr "Mandigal" msgid "Sarakin" msgstr "Sarakin" msgid "Charity" msgstr "Charity" msgid "Darlana" msgstr "Darlana" msgid "Ranloo" msgstr "Ranloo" msgid "Rialdo" msgstr "Rialdo" msgid "Zam" msgstr "Zam" msgid "Zom" msgstr "Zom" msgid "Celia" msgstr "Celia" msgid "Roxana" msgstr "Roxana" msgid "Lord Corlagon" msgstr "Lord Corlagon" msgid "Lord Halton" msgstr "Lord Halton" msgid "Sister Eliza" msgstr "Sestra Eliza" msgid "Brother Brax" msgstr "Brat Brax" msgid "Dainwin" msgstr "Dainwin" msgid "Joseph" msgstr "Joseph" msgid "Mog" msgstr "Mog" msgid "Solmyr" msgstr "Solmyr" msgid "Ceallach" msgstr "Ceallach" msgid "Drakonia" msgstr "Drakonia" msgid "Elderian" msgstr "Elderian" msgid "Gallavant" msgstr "Gallavant" msgid "Jarkonas" msgstr "Jarkonas" msgid "Martine" msgstr "Martine" msgid " gives you maximum morale" msgstr " dáva maximálnu morálku" msgid " gives you maximum luck" msgstr " dáva maximálne šťastie" msgid "You cannot pick up this artifact, you already have a full load!" msgstr "Tento artefakt nemôžeÅ¡ zdvihnúť, už máš plný náklad!" msgid "To cast spells, you must first buy a spell book for %{gold} gold." msgstr "" "Ak chceÅ¡ zosielaÅ¥ kúzla, musíš si najprv kúpiÅ¥ Knihu kúziel za %{gold} zlata." msgid "Unfortunately, you seem to be a little short of cash at the moment." msgstr "Nanešťastie sa zdá, že momentálne nemáš dostatok hotovosti." msgid "Do you wish to buy one?" msgstr "ChceÅ¡ si jednu kúpiÅ¥?" msgid "%{count} / day" msgstr "%{count} / deň" msgid "one" msgstr "1" msgid "two" msgstr "2" msgid "A whirlpool engulfs your ship. Some of your army has fallen overboard." msgstr "Tvoju loÄ pohlcuje vír. ÄŒasÅ¥ tvojej armády padá cez palubu." msgid "Insulted by your refusal of their offer, the monsters attack!" msgstr "Bojovníci, urazení tvojím odmietnutím ich ponuky, zaútoÄia!" msgid "" "The %{monster}, awed by the power of your forces, begin to scatter.\n" "Do you wish to pursue and engage them?" msgstr "" "%{monster} sa zaÄnú rozptyľovaÅ¥, ohromení silou tvojich vojsk.\n" "ChceÅ¡ ich prenasledovaÅ¥ a bojovaÅ¥ s nimi?" msgid "Ransacking an enemy camp, you discover a hidden cache of treasures." msgstr "Pri plienení nepriateľského tábora objavíš skrytú skrýšu s pokladmi." msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with these resources, " "come back next week for more.\"" msgstr "" "Správca mlyna oznamuje:\n" "\"Milord, pracoval som veľmi tvrdo, aby som vám poskytol tieto suroviny, " "príÄte si na budúci týždeň pre ÄalÅ¡ie.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there are no resources currently available. Please try " "again next week.\"" msgstr "" "Správca mlyna oznamuje:\n" "\"Milord, je mi ľúto, ale momentálne nemáme k dispozícii žiadne zdroje. " "Prosím, skúste to znova budúci týždeň.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with this gold, come " "back next week for more.\"" msgstr "" "Správca mlyna oznamuje:\n" "\"Milord, pracoval som veľmi tvrdo, aby som vám zabezpeÄil toto zlato, " "príÄte si na budúci týždeň po ÄalÅ¡ie.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there is no gold currently available. Please try again " "next week.\"" msgstr "" "Správca mlyna oznamuje:\n" "\" Milord, je mi ľúto, ale momentálne nie je k dispozícii žiadne zlato. " "Prosím, skúste to znova o týždeň.\"" msgid "" "You've found an abandoned lean-to.\n" "Poking about, you discover some resources hidden nearby." msgstr "" "NachádzaÅ¡ opustený prístreÅ¡ok.\n" "KeÄ sa okolo neho obzeráš, objavíš nejaké suroviny ukryté neÄaleko." msgid "The lean-to is long abandoned. There is nothing of value here." msgstr "PrístreÅ¡ok je už dávno opustený. Nenachádza sa tu niÄ cenné." msgid "" "You catch a leprechaun foolishly sleeping amidst a cluster of magic " "mushrooms.\n" "In exchange for his freedom, he guides you to a small pot filled with " "precious things." msgstr "" "Prichytíš leprikóna, ktorý nerozumne zaspal uprostred skupinky Äarovných " "húb.\n" "Výmenou za svoju slobodu Å¥a dovedie k malému hrncu plného vzácnych vecí." msgid "" "You've found a magic garden, the kind of place that leprechauns and faeries " "like to cavort in, but there is no one here today.\n" "Perhaps you should try again next week." msgstr "" "NachádzaÅ¡ Äarovnú záhradu, v ktorej sa radi preháňajú leprikóny a víly, ale " "dnes tu nikto nie je.\n" "Možno bude lepÅ¡ie skúsiÅ¥ to znova na budúci týždeň." msgid "You come upon the remains of an unfortunate adventurer." msgstr "Narazíš na pozostatky nešťastného dobrodruha." msgid "Treasure" msgstr "Poklad" msgid "Searching through the tattered clothing, you find the %{artifact}." msgstr "Pri prehľadávaní roztrhaného obleÄenia nájdeÅ¡ %{artifact}." msgid "Searching through the tattered clothing, you find nothing." msgstr "Pri prehľadávaní roztrhaného obleÄenia niÄ nenachádzaÅ¡." msgid "" "You come across an old wagon left by a trader who didn't quite make it to " "safe terrain." msgstr "" "Narazíš na starý voz, ktorý tu zanechal obchodník, ktorý sa úplne nedokázal " "dostaÅ¥ do bezpeÄia." msgid "Unfortunately, others have found it first, and the wagon is empty." msgstr "Bohužiaľ, ostatní ho naÅ¡li skôr a voz je prázdny." msgid "Searching inside, you find the %{artifact}." msgstr "Pri hľadaní vo vnútri nájdeÅ¡ %{artifact}." msgid "Inside, you find some of the wagon's cargo still intact." msgstr "Vnútri nájdeÅ¡ ÄasÅ¥ nákladu voza, ktorý je stále neporuÅ¡ený." msgid "You search through the flotsam, and find some wood and some gold." msgstr "Prehľadáš vyhodený náklad a nájdeÅ¡ nejaké drevo a zlato." msgid "You search through the flotsam, and find some wood." msgstr "Prehľadáš vyhodený náklad a nájdeÅ¡ nejaké drevo." msgid "You search through the flotsam, but find nothing." msgstr "PrehľadávaÅ¡ vyhodený náklad, ale niÄ nenachádzaÅ¡." msgid "Shrine of the 1st Circle" msgstr "Svätyňa 1. kruhu" msgid "" "You come across a small shrine attended by a group of novice acolytes.\n" "In exchange for your protection, they agree to teach you a simple spell - " "'%{spell}'." msgstr "" "Narazíš na malú svätyňu, ktorú navÅ¡tevuje skupina zaÄínajúcich akolytov.\n" "Výmenou za tvoju ochranu súhlasia, že Å¥a nauÄia jednoduché kúzlo - " "'%{spell}'." msgid "Shrine of the 2nd Circle" msgstr "Svätyňa 2. kruhu" msgid "" "You come across an ornate shrine attended by a group of rotund friars.\n" "In exchange for your protection, they agree to teach you a spell - " "'%{spell}'." msgstr "" "Narazíš na zdobenú svätyňu, ktorú navÅ¡tevuje skupina zavalitých mníchov.\n" "Výmenou za tvoju ochranu súhlasia, že Å¥a nauÄia kúzlo - '%{spell}'." msgid "Shrine of the 3rd Circle" msgstr "Svätyňa 3. kruhu" msgid "" "You come across a lavish shrine attended by a group of high priests.\n" "In exchange for your protection, they agree to teach you a sophisticated " "spell - '%{spell}'." msgstr "" "Narazíš na honosnú svätyňu, ktorú navÅ¡tevuje skupina veľkňazov.\n" "Výmenou za tvoju ochranu súhlasia, že Å¥a nauÄia zložité kúzlo - '%{spell}'." msgid "" "\n" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "\n" "Nanešťastie nemáš dostatok múdrosti na pochopenie tohto kúzla a nedokážeÅ¡ sa " "ho nauÄiÅ¥." msgid "" "\n" "Unfortunately, you already have knowledge of this spell, so there is nothing " "more for them to teach you." msgstr "" "\n" "Nanešťastie, toto kúzlo už ovládaÅ¡, takže Å¥a už nemajú Äo nauÄiÅ¥." msgid "" "\n" "Unfortunately, you have no Magic Book to record the spell with." msgstr "" "\n" "Nanešťastie nemáš Magickú knihu na zaznamenanie kúzla." msgid "" "You approach the hut and observe a witch inside studying an ancient tome on " "%{skill}.\n" "\n" msgstr "" "PristúpiÅ¡ k chatrÄi a zbadáš Äarodejnicu, ako vo vnútri Å¡tuduje starobylý " "zväzok na tému %{skill}.\n" "\n" msgid "" "As you approach, she turns and focuses her one glass eye on you.\n" "\"You already know everything you deserve to learn!\" the witch screeches. " "\"NOW GET OUT OF MY HOUSE!\"" msgstr "" "KeÄ sa blížiÅ¡, otoÄí sa a uprie na teba svoje jediné sklenené oko.\n" "\"Už vieÅ¡ vÅ¡etko, Äo si zaslúžiÅ¡ vedieÅ¥!\" vykríkne Äarodejnica. \"A TERAZ " "VYPADNI Z MÔJHO DOMU!\"" msgid "" "As you approach, she turns and speaks.\n" "\"You already know that which I would teach you. I can help you no further.\"" msgstr "" "KeÄ sa blížiÅ¡, otoÄí sa a prehovorí.\n" "\"To, Äo som Å¥a chcela nauÄiÅ¥, už vieÅ¡. Nemôžem ti už pomôcÅ¥.\"" msgid "" "An ancient and immortal witch living in a hut with bird's legs for stilts " "teaches you %{skill} for her own inscrutable purposes." msgstr "" "Prastará a nesmrteľná Äarodejnica žijúca v domÄeku na vtáÄích nohách Å¥a uÄí " "%{skill} pre svoje vlastné nepochopiteľné úÄely." msgid "As you drink the sweet water, you gain luck for your next battle." msgstr "KeÄ sa napijeÅ¡ sladkej vody, získaÅ¡ šťastie do ÄalÅ¡ieho boja." msgid "You drink from the enchanted fountain, but nothing happens." msgstr "NapijeÅ¡ sa zo zakliatej fontány, ale niÄ sa nestane." msgid "You enter the faerie ring, but nothing happens." msgstr "VstúpiÅ¡ do prsteňa víl, ale niÄ sa nedeje." msgid "" "Upon entering the mystical faerie ring, your army gains luck for its next " "battle." msgstr "" "Po vstupe do mystického kruhu víl získava tvoja armáda šťastie do ÄalÅ¡ej " "bitky." msgid "" "You've found an ancient and weathered stone idol.\n" "It is supposed to grant luck to visitors, but since the stars are already " "smiling upon you, it does nothing." msgstr "" "NachádzaÅ¡ starobylú a zvetranú kamennú modlu.\n" "Mala by návÅ¡tevníkom prinášaÅ¥ šťastie, ale keÄže sa na teba už hviezdy " "usmievajú, niÄ nerobí." msgid "" "You've found an ancient and weathered stone idol.\n" "Kissing it is supposed to be lucky, so you do. The stone is very cold to the " "touch." msgstr "" "NachádzaÅ¡ starobylú a zvetranú kamennú modlu.\n" "Jej pobozkanie má priniesÅ¥ šťastie, a tak to aj urobíš. Kameň je na dotyk " "veľmi studený." msgid "The mermaids silently entice you to return later and be blessed again." msgstr "" "Morské panny Å¥a ticho lákajú, aby si sa neskôr vrátil a nechal sa znova " "požehnaÅ¥." msgid "" "The magical, soothing beauty of the Mermaids reaches you and your crew.\n" "Just for a moment, you forget your worries and bask in the beauty of the " "moment.\n" "The mermaids charms bless you with increased luck for your next combat." msgstr "" "Magická, upokojujúca krása morských panien sa dostane k tebe a tvojej " "posádke.\n" "Na chvíľu zabúdaÅ¡ na svoje starosti a vychutnávaÅ¡ si krásu okamihu.\n" "ÄŒaro morských panien ti žehná väÄším šťastím do ÄalÅ¡ieho boja." msgid "" "You come upon the pyramid of a great and ancient king.\n" "You are tempted to search it for treasure, but all the old stories warn of " "fearful curses and undead guardians.\n" "Will you search?" msgstr "" "Narazíš na pyramídu veľkého a starobylého kráľa.\n" "Láka Å¥a hľadaÅ¥ v nej poklad, ale vÅ¡etky staré príbehy varujú pred straÅ¡nými " "kliatbami a nemÅ•tvymi strážcami.\n" "BudeÅ¡ hľadaÅ¥?" msgid "" "Upon defeating the monsters, you decipher an ancient glyph on the wall, " "telling the secret of the spell - '" msgstr "" "Po porážke príšer rozlúštiÅ¡ starodávny symbol na stene, ktorý ti prezradí " "tajomstvo kúzla - '" msgid "Unfortunately, you have no Magic Book to record the spell with." msgstr "" "Nanešťastie vÅ¡ak nemáš Magickú knihu, do ktorej by sa dalo kúzlo zaznamenaÅ¥." msgid "" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "Nanešťastie nemáš dostatok múdrosti k pochopeniu tohto kúzla a nedokážeÅ¡ sa " "ho nauÄiÅ¥." msgid "" "You come upon the pyramid of a great and ancient king.\n" "Routine exploration reveals that the pyramid is completely empty." msgstr "" "Narazíš na pyramídu veľkého a starobylého kráľa.\n" "Pri obvyklom prieskume sa ukáže, že pyramída je úplne prázdna." msgid "" "A drink at the well is supposed to restore your spell points, but you are " "already at maximum." msgstr "Pitie zo studne ti má obnoviÅ¥ body kúzliel, ale ty už máš maximum." msgid "A second drink at the well in one day will not help you." msgstr "Druhé pitie v studni v jeden deň ti nepomôže." msgid "A drink from the well has restored your spell points to maximum." msgstr "Pitie zo studne obnovilo tvoje body kúziel na maximum." msgid "" "\"I'm sorry sir,\" The leader of the soldiers says, \"but you already know " "everything we have to teach.\"" msgstr "" "\"Je mi ľúto\" hovorí veliteľ vojakov, \"ale vy už viete vÅ¡etko, Äo vás " "môžeme nauÄiÅ¥.\"" msgid "The soldiers living in the fort teach you a few new defensive tricks." msgstr "Vojaci žijúci v pevnosti Å¥a nauÄia niekoľko nových obranných trikov." msgid "" "You've come upon a mercenary camp practicing their tactics. \"You're too " "advanced for us,\" the mercenary captain says. \"We can teach nothing more.\"" msgstr "" "NachádzaÅ¡ sa v tábore žoldnierov, ktorí si precviÄujú taktiku. \"Ste pre nás " "príliÅ¡ pokroÄilí,\" hovorí kapitán žoldnierov. \"Už vás nemôžeme niÄ nauÄiÅ¥." "\"" msgid "" "You've come upon a mercenary camp practicing their tactics. The mercenaries " "welcome you and your troops and invite you to train with them." msgstr "" "NachádzaÅ¡ sa v tábore žoldnierov, ktorí si precviÄujú taktiku. Žoldnieri " "vítajú teba a tvojich vojakov a pozývajú vás, aby ste s nimi trénovali." msgid "\"Go 'way!\", the witch doctor barks, \"you know all I know.\"" msgstr "\"Vypadni!\", vyÅ¡tekne lieÄiteľ, \"vieÅ¡ vÅ¡etko, Äo viem.\"" msgid "" "An Orcish witch doctor living in the hut deepens your knowledge of magic by " "showing you how to cast stones, read portents, and decipher the intricacies " "of chicken entrails." msgstr "" "Orkský lieÄiteľ žijúci v chatrÄi prehĺbi tvoje znalosti mágie tým, že ti " "ukáže, ako hodiÅ¥ kameňmi, rozpoznaÅ¥ znamenia a rozlúštiÅ¥ zákutia kuracích " "vnútorností." msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, the Druids turn you away, indicating they have nothing " "new to teach you." msgstr "" "NachádzaÅ¡ skupinu druidov, ktorí sa klaňajú v jednej zo svojich zvláštnych " "kamenných stavieb. Druidi Å¥a mlÄky odvrátia a naznaÄia, že Å¥a nemajú Äo nové " "nauÄiÅ¥." msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, they teach you new ways to cast spells." msgstr "" "NachádzaÅ¡ skupinu druidov, ktorí sa klaňajú v jednej zo svojich zvláštnych " "kamenných stavieb. V tichosti Å¥a uÄia nové spôsoby zaklínania." msgid "" "You tentatively approach the burial ground of ancient warriors. Do you want " "to search the graves?" msgstr "" "Neisto sa približujeÅ¡ k pohrebisku dávnych bojovníkov. ChceÅ¡ prehľadaÅ¥ hroby?" msgid "" "You spend several hours searching the graves and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" "StráviÅ¡ niekoľko hodín prehľadávaním hrobov a niÄ nenachádzaÅ¡. Takýto podlý " "Äin znižuje morálku tvojej armády." msgid "Upon defeating the Zombies you search the graves and find something!" msgstr "Po porážke Zombíkov prehľadáš hroby a nieÄo nájdeÅ¡!" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the shipwreck?" msgstr "" "Hnijúci trup veľkej pirátskej lode straÅ¡idelne vÅ•zga, tlaÄiac sa na skaly. " "ChceÅ¡ prehľadaÅ¥ vrak lode?" msgid "" "You spend several hours sifting through the debris and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" "StráviÅ¡ niekoľko hodín prehľadávaním trosiek a niÄ nenájdeÅ¡. Takýto podlý " "Äin znižuje morálku tvojej armády." msgid "" "Upon defeating the Ghosts you sift through the debris and find something!" msgstr "Po porážke Duchov sa prehrabeÅ¡ troskami a nieÄo nájdeÅ¡!" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the ship?" msgstr "" "Hnijúci trup veľkej pirátskej lode straÅ¡idelne vÅ•zga, tlaÄiac sa na skaly. " "ChceÅ¡ prehľadaÅ¥ loÄ?" msgid "" "Upon defeating the Skeletons you sift through the debris and find something!" msgstr "Po porážke Kostlivcov sa prehrabeÅ¡ troskami a nieÄo nájdeÅ¡!" msgid "Your men spot a navigational buoy, confirming that you are on course." msgstr "" "Tvoji muži spozorujú navigaÄnú bóju, ktorá potvrdzuje, že ste na správnom " "kurze." msgid "" "Your men spot a navigational buoy, confirming that you are on course and " "increasing their morale." msgstr "" "Tvoji muži spozorujú navigaÄnú bóju, ktorá potvrdzuje, že ste na správnom " "kurze, a zvyÅ¡uje ich morálku." msgid "" "The drink at the oasis is refreshing, but offers no further benefit. The " "oasis might help again if you fought a battle first." msgstr "" "Nápoj v oáze je osviežujúci, ale neponúka žiadny Äalší úžitok. Oáza by mohla " "opäť pomôcÅ¥, ak by si najprv vybojoval/a bitku." msgid "" "A drink at the oasis fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" "Pitie v oáze dodá tvojim vojakom silu a zdvihne im náladu. Dnes môžeÅ¡ " "cestovaÅ¥ o kúsok Äalej." msgid "" "The drink at the watering hole is refreshing, but offers no further benefit. " "The watering hole might help again if you fought a battle first." msgstr "" "Pitie z napájadla je osviežujúce, ale neprináša žiadny Äalší úžitok. " "Napájadlo by mohlo opäť pomôcÅ¥, ak by si najprv vybojoval/a bitku." msgid "" "A drink at the watering hole fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" "Pitie z napájadla dodá tvojim vojakom silu a zdvihne im náladu. Dnes môžeÅ¡ " "cestovaÅ¥ o kúsok Äalej." msgid "" "It doesn't help to pray twice before a battle. Come back after you've fought." msgstr "Pred bitkou nepomáha modliÅ¥ sa dvakrát. Vráť sa až po boji." msgid "A visit and a prayer at the temple raises the morale of your troops." msgstr "NávÅ¡teva a modlitba v chráme zvyÅ¡uje morálku tvojich vojakov." msgid "" "An old Knight appears on the steps of the gazebo. \"I am sorry, my liege, I " "have taught you all I can.\"" msgstr "" "Na schodoch altánku sa objaví starý Rytier. \"Je mi ľúto, môj pane, nauÄil " "som vás vÅ¡etko, Äo som mohol.\"" msgid "" "An old Knight appears on the steps of the gazebo. \"My liege, I will teach " "you all that I know to aid you in your travels.\"" msgstr "" "Na schodoch altánku sa objaví starý Rytier. \"Môj pane, nauÄím vás vÅ¡etko, " "Äo viem, aby som vám pomohol na vaÅ¡ich cestách.\"" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he says, \"I would give you an artifact as a reward, but " "you're all full.\"" msgstr "" "Z istej smrti v neľútostnom oceáne vyÅ¥ahujeÅ¡ Äloveka, ktorý prežil " "stroskotanie lode. VÄaÄne ti povie: \"Dal by som vám za odmenu artefakt, ale " "už máte plno.\"" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he rewards you for your act of kindness by giving you the " "%{art}." msgstr "" "Z istej smrti v neľútostnom oceáne vyÅ¥ahujeÅ¡ Äloveka, ktorý prežil " "stroskotanie lode. VÄaÄne Å¥a za tvoj láskavý Äin odmení tým, že ti dá %{art}." msgid "A leprechaun offers you the %{art} for the small price of %{gold} Gold." msgstr "Leprikón ti ponúka %{art} za malú cenu %{gold} zlatých." msgid "" "A leprechaun offers you the %{art} for the small price of %{gold} Gold and " "%{count} %{res}." msgstr "" "Leprikón ti ponúka %{art} za malú cenu %{gold} zlatých a %{count} %{res}." msgid "Do you wish to buy this artifact?" msgstr "Želáš si tento artefakt kúpiÅ¥?" msgid "" "You try to pay the leprechaun, but realize that you can't afford it. The " "leprechaun stamps his foot and ignores you." msgstr "" "Snažíš sa leprikónovi zaplatiÅ¥, ale zisÅ¥ujeÅ¡, že si to nemôžeÅ¡ dovoliÅ¥. " "Leprikón si dupne nohou a ignoruje Å¥a." msgid "" "Insulted by your refusal of his generous offer, the leprechaun stamps his " "foot and ignores you." msgstr "" "Leprikón, urazený tvojím odmietnutím jeho veľkorysej ponuky, si dupne nohou " "a ignoruje Å¥a." msgid "You've found the artifact: " msgstr "NachádzaÅ¡ artefakt: " msgid "" "You've found the humble dwelling of a withered hermit. The hermit tells you " "that he is willing to give the %{art} to the first wise person he meets." msgstr "" "Podarilo sa ti nájsÅ¥ skromný príbytok zvädnutého pustovníka. Pustovník ti " "povie, že je ochotný daÅ¥ %{art} prvému múdremu Äloveku, ktorého stretne." msgid "" "You've come across the spartan quarters of a retired soldier. The soldier " "tells you that he is willing to pass on the %{art} to the first true leader " "he meets." msgstr "" "Narazíš na sparÅ¥anské priestory vyslúžilého vojaka. Vojak ti povie, že je " "ochotný odovzdaÅ¥ %{art} prvému skutoÄnému vodcovi, ktorého stretne." msgid "" "You've encountered a strange person with a hat and an owl on it. He tells " "you that he is willing to give %{art} if you have %{skill}." msgstr "" "StretávaÅ¡ zvláštnu osobu v klobúku so sovou na vrchu. Povie ti, že je " "ochotný daÅ¥ ti %{art}, ak máš %{skill}." msgid "" "You come upon an ancient artifact. As you reach for it, a pack of Rogues " "leap out of the brush to guard their stolen loot." msgstr "" "Natrafíš na starobylý artefakt. KeÄ sa poň naÄiahneÅ¡, z krovia vyskoÄí " "svorka Tulákov, ktorí si strážia ukradnutú korisÅ¥." msgid "" "Through a clearing you observe an ancient artifact. Unfortunately, it's " "guarded by a nearby %{monster}. Do you want to fight the %{monster} for the " "artifact?" msgstr "" "Cez mýtinu pozorujeÅ¡ starobylý artefakt. Nanešťastie ho stráži neÄaleký " "%{monster}. ChceÅ¡ s ním o artefakt bojovaÅ¥?" msgid "Victorious, you take your prize, the %{art}." msgstr "Víťazne si prevezmeÅ¡ svoju cenu, %{art}." msgid "" "Discretion is the better part of valor, and you decide to avoid this fight " "for today." msgstr "" "ObozretnosÅ¥ je lepÅ¡ia ÄasÅ¥ statoÄnosti a rozhodneÅ¡ sa tomuto boju pre dneÅ¡ok " "vyhnúť." msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold pieces." msgstr "" "Po hodinách strávených snahou vyloviÅ¥ truhlicu z mora ju otvoríš a nájdeÅ¡ " "%{gold} zlatých." msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold and the %{art}." msgstr "" "Po hodinách strávených snahou vyloviÅ¥ truhlicu z mora ju otvoríš a nájdeÅ¡ " "%{gold} zlatých a %{art}." msgid "" "After spending hours trying to fish the chest out of the sea, you open it, " "only to find it empty." msgstr "" "Po hodinách strávených snahou vyloviÅ¥ truhlicu z mora ju otvoríš a zistíš, " "že je prázdna." msgid "" "After scouring the area, you fall upon a hidden treasure cache. You may take " "the gold or distribute the gold to the peasants for experience. Do you wish " "to keep the gold?" msgstr "" "Po prehľadaní oblasti narazíš na skrytú skrýšu s pokladom. Zlato si môžeÅ¡ " "vziaÅ¥ alebo ho rozdaÅ¥ sedliakom za skúsenosti. ChceÅ¡ si zlato nechaÅ¥?" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "%{gold} gold pieces." msgstr "" "Po prehľadaní oblasti narazíš na skrytú truhlicu, ktorá obsahuje %{gold} " "zlatých." msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "ancient artifact %{art}." msgstr "" "Po prehľadaní oblasti narazíš na skrytú truhlicu, ktorá obsahuje starobylý " "artefakt %{art}." msgid "" "You stumble upon a dented and tarnished lamp lodged deep in the earth. Do " "you wish to rub the lamp?" msgstr "" "Narazíš na preliaÄenú a zneÄistenú lampu zapichnutú hlboko v zemi. ChceÅ¡ tú " "lampu poutieraÅ¥?" msgid "" "You have taken control of the local Alchemist shop. It will provide you with " "%{count} unit of Mercury per day." msgstr "" "ZískavaÅ¡ kontrolu nad miestnou alchymistickou dielňou. Bude ti poskytovaÅ¥ " "%{count} jednotku ortuti denne." msgid "" "You gain control of a sawmill. It will provide you with %{count} units of " "wood per day." msgstr "" "ZískavaÅ¡ kontrolu nad pílou. Denne ti poskytne %{count} jednotky dreva.." msgid "" "You gain control of an ore mine. It will provide you with %{count} units of " "ore per day." msgstr "" "ZískavaÅ¡ kontrolu nad rudnou baňou. Denne ti poskytne %{count} jednotky rudy." msgid "" "You gain control of a sulfur mine. It will provide you with %{count} unit of " "sulfur per day." msgstr "" "ZískavaÅ¡ kontrolu nad baňou na síru. Denne ti poskytne %{count} jednotku " "síry." msgid "" "You gain control of a crystal mine. It will provide you with %{count} unit " "of crystal per day." msgstr "" "ZískavaÅ¡ kontrolu nad kryÅ¡tálovou baňou. Denne ti poskytne %{count} jednotku " "kryÅ¡tálu." msgid "" "You gain control of a gem mine. It will provide you with %{count} unit of " "gems per day." msgstr "" "ZískavaÅ¡ kontrolu nad baňou na drahokamy. Denne ti poskytne %{count} " "jednotku drahokamov.." msgid "" "You gain control of a gold mine. It will provide you with %{count} gold per " "day." msgstr "" "ZískavaÅ¡ kontrolu nad zlatou baňou. Denne ti poskytne %{count} zlatých." msgid "" "The lighthouse is now under your control, and all of your ships will now " "move further each day." msgstr "" "Maják je teraz pod tvojou kontrolou a vÅ¡etky tvoje lode sa odteraz každý deň " "dostanú Äalej." msgid "You gain control of a %{name}." msgstr "%{name} je odteraz pod tvojou kontrolou." msgid "" "You come upon an abandoned gold mine. The mine appears to be haunted. Do you " "wish to enter?" msgstr "" "Narazíš na opustenú zlatú baňu. Zdá sa, že v bani straší. ChceÅ¡ vstúpiÅ¥?" msgid "You beat the Ghosts and are able to restore the mine to production." msgstr "PorážaÅ¡ Duchov a darí sa ti obnoviÅ¥ Å¥ažbu v bani." msgid "" "A group of %{monster} with a desire for greater glory wish to join you. Do " "you accept?" msgstr "" "%{monster} s túžbou po väÄÅ¡ej sláve si želajú pripojiÅ¥ sa k tebe. PrijmeÅ¡ " "ich?" msgid "As you approach the dwelling, you notice that there is no one here." msgstr "KeÄ sa blížiÅ¡ k obydliu, vÅ¡imneÅ¡ si, že tu nikto nie je." msgid "" "You search the ruins, but the Medusas that used to live here are gone. " "Perhaps there will be more next week." msgstr "" "PrehľadávaÅ¡ ruiny, ale Medúzy, ktoré tu kedysi žili, sú preÄ. Možno pribudnú " "na budúci týždeň." msgid "" "You've found some Medusas living in the ruins. They are willing to join your " "army for a price. Do you want to recruit Medusas?" msgstr "" "V ruinách nachádzaÅ¡ niekoľko Medúz, ktoré tu žijú. Sú ochotné pridaÅ¥ sa k " "tvojej armáde za urÄitú cenu. ChceÅ¡ naverbovaÅ¥ Medúzy?" msgid "" "You've found a Sprite Tree City. Unfortunately, none of the Sprites living " "there wish to join an army. Maybe next week." msgstr "" "Podarilo sa ti nájsÅ¥ Stromové mesto víl. Bohužiaľ, nikto z víl, ktoré tam " "žijú, sa nechce pridaÅ¥ k armáde. Možno na budúci týždeň." msgid "" "Some of the Sprites living in the tree city are willing to join your army " "for a price. Do you want to recruit Sprites?" msgstr "" "Niektoré zo Å¡printov žijúcich v meste stromov sú ochotné pridaÅ¥ sa k tvojej " "armáde za urÄitú cenu. ChceÅ¡ naverbovaÅ¥ Víly?" msgid "" "A colorful Rogues' wagon stands empty here. Perhaps more Rogues will be here " "later." msgstr "" "Farebný voz Tulákov tu stojí prázdny. Možno sa tu neskôr nejakí Tuláci " "objavia." msgid "" "Distant sounds of music and laughter draw you to a colorful wagon housing " "Rogues. Do you wish to have any Rogues join your army?" msgstr "" "Vzdialené zvuky hudby a smiechu Å¥a prilákajú k farebnému vozu, v ktorom sa " "zdržiavajú Tuláci. ChceÅ¡, aby sa niektorí Tuláci pridali k tvojmu vojsku?" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. The " "tents are unoccupied. Perhaps more Nomads will be here later." msgstr "" "Láka Å¥a skupina oÅ¡arpaných stanov, ktoré vejú v piesoÄnom vetre. Stany sú " "neobsadené. Možno tu neskôr budú Äalší Nomádi." msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. Do you " "wish to have any Nomads join you during your travels?" msgstr "" "Láka Å¥a skupina oÅ¡arpaných stanov, ktoré vejú v piesoÄnom vetre. ChceÅ¡, aby " "sa k tebe na tvojich cestách pridali nejakí Nomádi?" msgid "The pit of mud bubbles for a minute and then lies still." msgstr "Jama s bahnom chvíľu buble a potom zostane ležaÅ¥." msgid "" "As you approach the bubbling pit of mud, creatures begin to climb out and " "position themselves around it. In unison they say: \"Mother Earth would like " "to offer you a few of her troops. Do you want to recruit Earth Elementals?\"" msgstr "" "KeÄ sa priblížiÅ¡ k bublajúcej jame s bahnom, zaÄnú z nej vyliezaÅ¥ tvory a " "rozmiestňovaÅ¥ sa okolo nej. Jednohlasne hovoria: \"Matka Zem by ti rada " "ponúkla niekoľko svojich vojakov. ChceÅ¡ naverbovaÅ¥ Zemských elementálov?\"" msgid "You enter the structure of white stone pillars, and find nothing." msgstr "VstúpiÅ¡ do stavby z bielych kamenných stĺpov a niÄ nenájdeÅ¡." msgid "" "White stone pillars support a roof that rises up to the sky. As you enter " "the structure, the dead air of the outside gives way to a whirling gust that " "almost pushes you back out. The air current materializes into a barely " "visible form. The creature asks, in what can only be described as a loud " "whisper: \"Why have you come? Are you here to call upon the forces of the " "air?\"" msgstr "" "Biele kamenné stĺpy podopierajú strechu, ktorá sa dvíha k nebu. KeÄ vstúpiÅ¡ " "do stavby, bezvetrie vonkajÅ¡ka ustúpi vírivému vetru, ktorý Å¥a takmer " "vytlaÄí späť von. Prúd vzduchu sa zhmotní do sotva viditeľnej podoby. Tvor " "sa pýta nieÄím, Äo sa dá opísaÅ¥ len ako hlasný Å¡epot: \"PreÄo prichádzaÅ¡? Si " "tu, lebo chceÅ¡ privolaÅ¥ vzduÅ¡né sily?\"" msgid "No Fire Elementals approach you from the lava pool." msgstr "Z lávového jazierka sa k tebe nepriblížia žiadni Ohniví elementáli." msgid "" "Beneath a structure that serves to hold in heat, Fire Elementals move about " "in a fiery pool of molten lava. A group of them approach you and offer their " "services. Would you like to recruit Fire Elementals?" msgstr "" "Pod Å¡truktúrou, ktorá slúži na udržiavanie tepla, sa ohnivé živly pohybujú v " "ohnivej kaluži roztavenej lávy. Skupina týchto bytostí sa k tebe priblíži a " "ponúkne ti svoje služby. ChceÅ¡ naverbovaÅ¥ Ohnivých elementálov?" msgid "A face forms in the water for a moment, and then is gone." msgstr "Vo vode sa na chvíľu objaví tvár a potom zmizne." msgid "" "Crystalline structures cast shadows over a small reflective pool of water. " "You peer into the pool, and a face that is not your own peers back. It asks: " "\"Would you like to call upon the powers of water?\"" msgstr "" "KryÅ¡talické Å¡truktúry vrhajú tiene na malý odrazový bazén s vodou. Zahľadíš " "sa do bazéna a tvár, ktorá nie je tvoja, sa pozerá späť. Pýta sa: \"PrajeÅ¡ " "si privolaÅ¥ silu vody?\"" msgid "This burial site is deathly still." msgstr "Toto pohrebisko je mÅ•tvolne tiché." msgid "" "Restless spirits of long dead warriors seeking their final resting place " "offer to join you in hopes of finding peace. Do you wish to recruit ghosts?" msgstr "" "Nepokojní duchovia dávno mÅ•tvych bojovníkov, ktorí hľadajú miesto svojho " "posledného odpoÄinku, sa k tebe pridajú v nádeji, že nájdu pokoj. Chcete " "naverbovaÅ¥ Duchov?" msgid "" "The City of the Dead is empty of life, and empty of unlife as well. Perhaps " "some undead will move in next week." msgstr "" "Mesto mÅ•tvych je prázdne od života a prázdne aj od neživota. Možno sa sem na " "budúci týždeň nasÅ¥ahujú nejakí nemÅ•tvi." msgid "" "Some Liches living here are willing to join your army for a price. Do you " "want to recruit Liches?" msgstr "" "Niektorí Kostejovia, ktorí tu žijú, sú ochotní pridaÅ¥ sa k tvojej armáde za " "odmenu. Chcete naverbovaÅ¥ Kostejov?" msgid "" "You've found the ruins of an ancient city, now inhabited solely by the " "undead. Will you search?" msgstr "" "NachádzaÅ¡ ruiny starobylého mesta, ktoré teraz obývajú výluÄne nemÅ•tvi. " "BudeÅ¡ pátraÅ¥?" msgid "" "Some of the surviving Liches are impressed by your victory over their " "fellows, and offer to join you for a price. Do you want to recruit Liches?" msgstr "" "Niektorí z preživších Kostejov sú ohromení tvojim víťazstvom nad svojimi " "druhmi a ponúknu ti, že sa k tebe za odmenu pridajú. ChceÅ¡ naverbovaÅ¥ " "Kostejov?" msgid "" "You've found one of those bridges that Trolls are so fond of living under, " "but there are none here. Perhaps there will be some next week." msgstr "" "NachádzaÅ¡ jeden z tých mostov, pod ktorými Trolovia tak radi žijú, ale tu " "žiadni nie sú. Možno sa nejakí objavia na budúci týždeň." msgid "" "Some Trolls living under a bridge are willing to join your army, but for a " "price. Do you want to recruit Trolls?" msgstr "" "Niektorí Trolovia žijúci pod mostom sú ochotní pridaÅ¥ sa k tvojej armáde, " "ale za urÄitú cenu. ChceÅ¡ naverbovaÅ¥ Trolov?" msgid "Trolls living under the bridge challenge you. Will you fight them?" msgstr "Trolovia žijúci pod mostom Å¥a vyzývajú. BudeÅ¡ s nimi bojovaÅ¥?" msgid "" "A few Trolls remain, cowering under the bridge. They approach you and offer " "to join your forces as mercenaries. Do you want to buy any Trolls?" msgstr "" "Stále zostáva zopár Trolov, ktorí sa skrývajú pod mostom. Pristúpia k tebe a " "ponúknu ti, že sa pridajú k tvojim jednotkám ako žoldnieri. ChceÅ¡ kúpiÅ¥ " "nejakých Trolov?" msgid "" "The Dragon city has no Dragons willing to join you this week. Perhaps a " "Dragon will become available next week." msgstr "" "V DraÄom meste nie sú draci, ktorí by sa k tebe chceli tento týždeň pridaÅ¥. " "Možno sa nejaký drak uvoľní budúci týždeň." msgid "" "The Dragon city is willing to offer some Dragons for your army for a price. " "Do you wish to recruit Dragons?" msgstr "" "DraÄie mesto je ochotné ponúknuÅ¥ niekoľko Drakov pre tvoju armádu za urÄitú " "cenu. ChceÅ¡ naverbovaÅ¥ Drakov?" msgid "" "You stand before the Dragon City, a place off-limits to mere humans. Do you " "wish to violate this rule and challenge the Dragons to a fight?" msgstr "" "Stojíš pred DraÄím mestom, miestom, ktoré je pre obyÄajných ľudí " "neprístupné. ChceÅ¡ toto pravidlo poruÅ¡iÅ¥ a vyzvaÅ¥ drakov na súboj?" msgid "" "Having defeated the Dragon champions, the city's leaders agree to supply " "some Dragons to your army for a price. Do you wish to recruit Dragons?" msgstr "" "Po porážke DraÄích Å¡ampiónov vodcovia mesta súhlasia s tým, že za odmenu " "dodajú niekoľko Drakov do tvojej armády. ChceÅ¡ naverbovaÅ¥ Drakov?" msgid "From the observation tower, you are able to see distant lands." msgstr "Z vyhliadkovej veže vidíš vzdialené konÄiny." msgid "" "The spring only refills once a week, and someone's already been here this " "week." msgstr "Prameň sa obnovuje len raz týždenne a tento týždeň tu už niekto bol." msgid "" "A drink at the spring is supposed to give you twice your normal spell " "points, but you are already at that level." msgstr "" "Pitie z prameňa ti má priniesÅ¥ dvojnásobok bežných bodov kúziel, ale ty ich " "už máš na tejto úrovni." msgid "" "A drink from the spring fills your blood with magic! You have twice your " "normal spell points in reserve." msgstr "" "Pitie z prameňa naplní tvoju krv mágiou! V rezerve máš dvojnásobok svojich " "bežných bodov kúziel." msgid "" "Recognizing you, the butler refuses to admit you. \"The master,\" he says, " "\"will not see the same student twice.\"" msgstr "" "Majordómus Å¥a spoznáva, ale odmieta Å¥a prijaÅ¥. \"Pán,\" hovorí, \"neprijme " "dvakrát toho istého Å¡tudenta.\"" msgid "" "The butler admits you to see the master of the house. He trains you in the " "four skills a hero should know." msgstr "" "Majordómus Å¥a pustí k pánovi domu. Ten Å¥a zaÅ¡kolí v Å¡tyroch zruÄnostiach, " "ktoré by mal hrdina ovládaÅ¥." msgid "" "The butler opens the door and looks you up and down. \"You are neither " "famous nor diplomatic enough to be admitted to see my master,\" he sniffs. " "\"Come back when you think yourself worthy.\"" msgstr "" "Majordómus otvorí dvere a premeria si vás pohľadom. \"Nie ste ani dostatoÄne " "slávny, ani dostatoÄne diplomatický, aby ste boli prijatý k môjmu pánovi,\" " "odfrkne si. \"Vráťte sa, keÄ sa budete považovaÅ¥ za hodného.\"" msgid " and " msgstr " a " msgid "" "All of the %{monsters} you have in your army have been trained by the battle " "masters of the fort. Your army now contains %{monsters2}." msgstr "" "%{monsters}, v tvojej armáde, boli vycviÄení bojovými majstrami pevnosti. " "Teraz sú to už %{monsters2}." msgid "" "An unusual alliance of Ogres, Orcs, and Dwarves offer to train (upgrade) any " "such troops brought to them. Unfortunately, you have none with you." msgstr "" "NezvyÄajná aliancia Zlobrov, Orkov a Trpaslíkov ponúka výcvik (vylepÅ¡enie) " "vÅ¡etkých takýchto jednotiek, ktoré k nim privedieÅ¡. Nanešťastie so sebou " "žiadne nemáš." msgid "All of your %{monsters} have been upgraded into %{monsters2}." msgstr "%{monsters} sa vylepÅ¡ili na %{monsters2}." msgid "" "A blacksmith working at the foundry offers to convert all Pikemen and " "Swordsmen's weapons brought to him from iron to steel. He also says that he " "knows a process that will convert Iron Golems into Steel Golems. " "Unfortunately, you have none of these troops in your army, so he can't help " "you." msgstr "" "KovÃ¡Ä pracujúci v zlievarni sa ponúkne, že vÅ¡etky zbrane Kopijníkov a " "Å ermiarov, ktoré mu prinesieÅ¡, prerobí zo železa na oceľ. Tiež hovorí, že " "pozná postup, ktorý premení Železných golemov na Oceľových golemov. " "Bohužiaľ, vo svojej armáde nemáš ani jedného z týchto vojakov, takže ti " "nemôže pomôcÅ¥." msgid "" "The captain looks at you with surprise and says:\n" "\"You already have all the maps I know about. Let me fish in peace now.\"" msgstr "" "Kapitán sa na teba prekvapene pozrie a povie:\n" "\"Už máš vÅ¡etky mapy, o ktorých viem. Nechaj ma teraz v pokoji loviÅ¥ ryby.\"" msgid "" "A retired captain living on this refurbished fishing platform offers to sell " "you maps of the sea he made in his younger days for 1,000 gold. Do you wish " "to buy the maps?" msgstr "" "Kapitán na dôchodku, ktorý žije na tejto zrekonÅ¡truovanej rybárskej ploÅ¡ine, " "ti za 1000 zlatých ponúkne na predaj mapy mora, ktoré vytvoril za svojich " "mladších Äias. ChceÅ¡ si tieto mapy kúpiÅ¥?" msgid "" "The captain sighs. \"You don't have enough money, eh? You can't expect me " "to give my maps away for free!\"" msgstr "" "Kapitán si vzdychne. \"Nemáš dosÅ¥ peňazí, Äo? NemôžeÅ¡ predsa oÄakávaÅ¥, že " "budem rozdávaÅ¥ svoje mapy zadarmo!\"" msgid "" "You come upon an obelisk made from a type of stone you have never seen " "before. Staring at it intensely, the smooth surface suddenly changes to an " "inscription. The inscription is a piece of a lost ancient map. Quickly you " "copy down the piece and the inscription vanishes as abruptly as it appeared." msgstr "" "Narazíš na obelisk z kameňa, aký si nikdy predtým nevidel. Pozorne sa naň " "zadívaÅ¡ a hladký povrch sa zrazu zmení na rytinu. Nápis je kúskom stratenej " "prastarej mapy. Rýchlo si ten kúsok odkreslíš a rytina zmizne tak náhle, ako " "sa objavila." msgid "You have already been to this obelisk." msgstr "Pri tomto obelisku si už bol/a." msgid "" "Upon your approach, the tree opens its eyes in delight. \"It is good to see " "you, my student. I hope my teachings have helped you.\"" msgstr "" "KeÄ sa k nemu priblížiÅ¡, strom s radosÅ¥ou otvorí oÄi. \"Rád Å¥a opäť vidím, " "Å¡tudent/ka môj/moja. Dúfam, že ti moje uÄenie pomohlo.\"" msgid "" "Upon your approach, the tree opens its eyes in delight. \"Ahh, an " "adventurer! Allow me to teach you a little of what I have learned over the " "ages.\"" msgstr "" "KeÄ sa k nemu priblížiÅ¡, strom s radosÅ¥ou otvorí oÄi. \"Ach, dobrodružný " "Älovek! Dovoľ mi, aby som Å¥a nauÄil nieÄo z toho, Äo som sa nauÄil za celé " "veky.\"" msgid "Upon your approach, the tree opens its eyes in delight." msgstr "KeÄ sa k nemu priblížiÅ¡, strom s radosÅ¥ou otvorí oÄi." msgid "" "\"Ahh, an adventurer! I will be happy to teach you a little of what I have " "learned over the ages for a mere %{count} %{res}.\"" msgstr "" "\"Ach, dobrodružný Älovek! Rád Å¥a nauÄím nieÄo z toho, Äo som sa nauÄil za " "celé veky, len za %{count} %{res}.\"" msgid "(Just bury it around my roots.)" msgstr "(Len to zakop okolo mojich koreňov.)" msgid "Tears brim in the eyes of the tree." msgstr "V oÄiach stromu sa zalesknú slzy." msgid "\"I need %{count} %{res}.\"" msgstr "\"Potrebujem %{count} %{res}.\"" msgid "it whispers. (sniff) \"Well, come back when you can pay me.\"" msgstr "Å¡epká. (fňuk) \"No, vráť sa, keÄ mi budeÅ¡ môcÅ¥ zaplatiÅ¥.\"" msgid "" "Nestled among the trees sits a blind seer. After you explain the intent of " "your journey, the seer activates his crystal ball, allowing you to see the " "strengths and weaknesses of your opponents." msgstr "" "Medzi stromami sedí slepý veÅ¡tec. KeÄ mu vysvetlíš zámer tvojej cesty, " "aktivuje svoju kriÅ¡táľovú guľu a umožní ti vidieÅ¥ silné a slabé stránky " "tvojich protivníkov." msgid "" "The entrance to the cave is dark, and a foul, sulfurous smell issues from " "the cave mouth. Will you enter?" msgstr "" "Vchod do jaskyne je tmavý a z ústia jaskyne sa šíri nepríjemný sírny zápach. " "VstúpiÅ¡?" msgid "Except for evidence of a terrible battle, the cave is empty." msgstr "Až na stopy po straÅ¡nej bitke je jaskyňa prázdna." msgid "" "You find a powerful and grotesque Demon in the cave. \"Today,\" it rasps, " "\"you will fight and surely die. But I will give you a choice of deaths. You " "may fight me, or you may fight my servants. Do you prefer to fight my " "servants?\"" msgstr "" "V jaskyni nájdeÅ¡ mocného a ohavného démona. \"Dnes,\" zavrÄí, \"budeÅ¡ " "bojovaÅ¥ a urÄite zomrieÅ¡. Dám ti vÅ¡ak na výber z viacerých spôsobov smrti. " "MôžeÅ¡ bojovaÅ¥ so mnou, alebo s mojimi služobníkmi. Dáš prednosÅ¥ boju s " "mojimi sluhami?\"" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points." msgstr "" "Démon zakriÄí svoju výzvu a zaútoÄí! Po krátkom, zúfalom boji príšeru " "zabijeÅ¡ a získavaÅ¡ %{exp} skúsenostných bodov." msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points and " "%{count} gold." msgstr "" "Démon zakriÄí svoju výzvu a zaútoÄí! Po krátkom, zúfalom boji príšeru " "zabijeÅ¡ a získavaÅ¡ %{exp} skúsenostných bodov a %{count} zlata." msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and find the %{art} in the back of the cave." msgstr "" "Démon zakriÄí svoju výzvu a zaútoÄí! Po krátkom zúfalom boji príšeru zabijeÅ¡ " "a v zadnej Äasti jaskyne nájdeÅ¡ %{art}." msgid "" "Seeing that you do not have %{count} gold, the demon slashes you with its " "claws, and the last thing you see is a red haze." msgstr "" "Vidiac, že nemáš %{count} zlata, démon Å¥a sekne svojimi pazúrmi a posledné, " "Äo vidíš, je Äervená hmla." msgid "" "The Demon leaps upon you and has its claws at your throat before you can " "even draw your sword. \"Your life is mine,\" it says. \"I will sell it back " "to you for %{count} gold.\"" msgstr "" "Démon na teba skoÄí a chňapne po tvojom hrdle skôr, ako stihneÅ¡ vytasiÅ¥ meÄ. " "\"Tvoj život je môj,\" hovorí. \"Predám ti ho späť za %{count} zlata.\"" msgid "" "Upon defeating the daemon's servants, you find a hidden cache with %{count} " "gold." msgstr "" "Po porážke démonových služobníkov nájdeÅ¡ skrytú skrýšu s %{count} zlata." msgid "" "As you enter the Alchemist's Tower, a hobbled, graying man in a brown cloak " "makes his way towards you." msgstr "" "KeÄ vstupujeÅ¡ do Alchymistickej veže, približuje sa k tebe chromý, Å¡edivý " "muž v hnedom plášti." msgid "He checks your pack, and sees that you have 1 cursed item." msgid_plural "" "He checks your pack, and sees that you have %{count} cursed items." msgstr[0] "Skontroluje tvoj batoh a zistí, že máš jeden prekliaty predmet." msgstr[1] "Skontroluje tvoj batoh a zistí, že máš %{count} prekliate predmety." msgstr[2] "" "Skontroluje tvoj batoh a zistí, že máš %{count} prekliatych predmetov." msgid "For %{gold} gold, the alchemist will remove it for you. Do you pay?" msgid_plural "" "For %{gold} gold, the alchemist will remove them for you. Do you pay?" msgstr[0] "Alchymista ti ho za %{gold} zlatých odstráni. Zaplatíš mu?" msgstr[1] "Alchymista ti ich za %{gold} zlatých odstráni. Zaplatíš mu?" msgstr[2] "Alchymista ti ich za %{gold} zlatých odstráni. Zaplatíš mu?" msgid "" "After you consent to pay the requested amount of gold, the alchemist grabs " "the cursed artifact and throws it into his magical cauldron." msgid_plural "" "After you consent to pay the requested amount of gold, the alchemist grabs " "all cursed artifacts and throws them into his magical cauldron." msgstr[0] "" "Po tom, Äo súhlasíš so zaplatením požadovanej sumy zlata, alchymista vezme " "prekliaty artefakt a hodí ho do svojho magického kotla." msgstr[1] "" "Po tom, Äo súhlasíš so zaplatením požadovanej sumy zlata, alchymista vezme " "prekliate artefakty a hodí ich do svojho magického kotla." msgstr[2] "" "Po tom, Äo súhlasíš so zaplatením požadovanej sumy zlata, alchymista vezme " "prekliate artefakty a hodí ich do svojho magického kotla." msgid "" "You hear a voice from behind the locked door, \"You don't have enough gold " "to pay for my services.\"" msgstr "" "Spoza zamknutých dverí poÄujeÅ¡ hlas: \"Nemáš dosÅ¥ zlata, aby si zaplatil za " "moje služby.\"" msgid "" "You hear a voice from high above in the tower, \"Go away! I can't help you!\"" msgstr "Z veže poÄujeÅ¡ hlas: \"ChoÄ preÄ! Nemôžem ti pomôcÅ¥!\"" msgid "" "The head groom speaks to you, \"That is a fine looking horse you have. I am " "afraid we can give you no better, but the horses your cavalry are riding " "look to be of poor breeding stock. We have many trained war horses which " "would aid your riders greatly. I insist you take them.\"" msgstr "" "Hlavný koniar k tebe prehovorí: \"Máte pekného koňa. Obávam sa, že vám " "nemôžeme daÅ¥ lepÅ¡ieho, ale kone, na ktorých jazdí vaÅ¡a kavaléria, vyzerajú " "byÅ¥ zo zlého chovu. Máme veľa vycviÄených vojnových koní, ktoré by vaÅ¡im " "jazdcom veľmi pomohli. Trvám na tom, aby ste si ich vzali.\"" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, he will " "grow tired in a week. You must also let me give better horses to your " "mounted soldiers, their horses look shoddy and weak.\"" msgstr "" "KeÄ sa blížiÅ¡ k stajniam, objaví sa hlavný koniar, ktorý vedie pekne " "vyzerajúceho vojnového koňa. \"Tento kôň vám pomôže urýchliÅ¥ vaÅ¡e putovanie. " "Žiaľ, za týždeň sa unaví. Musíte mi tiež dovoliÅ¥, aby som vaÅ¡im jazdeckým " "vojakom dal lepÅ¡ie kone, ich kone vyzerajú chatrne a slabo.\"" msgid "" "The head groom approaches you and speaks, \"You already have a fine horse, " "and have no inexperienced cavalry which might make use of our trained war " "horses.\"" msgstr "" "Pristúpi k tebe hlavný koniar a prehovorí: \"Už máte pekného koňa a nemáte " "neskúsenú kavalériu, ktorá by mohla využiÅ¥ naÅ¡e vycviÄené vojnové kone.\"" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, his " "endurance will wane with a lot of heavy riding, and you must return for a " "fresh mount in a week. We also have many fine war horses which could benefit " "mounted soldiers, but you have none we can help.\"" msgstr "" "KeÄ sa blížiÅ¡ k stajniam, objaví sa hlavný koniar, ktorý vedie pekne " "vyzerajúceho vojnového koňa. \"Tento kôň vám pomôže urýchliÅ¥ vaÅ¡e cesty. " "Žiaľ, jeho výdrž sa pri Å¥ažkej jazde oslabí a o týždeň sa musíte vrátiÅ¥ pre " "nového koňa. Máme aj veľa pekných vojnových koní, ktoré by mohli byÅ¥ " "užitoÄné pre vojenských jazdcov, ale nemáte žiadneho, ktorému by sme mohli " "pomôcÅ¥.\"" msgid "The Arena guards turn you away." msgstr "Strážcovia arény Å¥a odvrátia." msgid "" "You have your crew stop up their ears with wax before the sirens' eerie song " "has any chance of luring them to a watery grave." msgstr "" "PrikážeÅ¡ svojej posádke, aby si zapchala uÅ¡i voskom skôr, než ich má " "straÅ¡idelná pieseň sirén Å¡ancu zlákaÅ¥ do ich vodného hrobu." msgid "" "As the sirens sing their eerie song, your small, determined army manages to " "overcome the urge to dive headlong into the sea." msgstr "" "Kým sirény spievajú svoju hrôzostraÅ¡nú pieseň, tvoja malá, odhodlaná armáda " "dokáže prekonaÅ¥ nutkanie vrhnúť sa bezhlavo do mora." msgid "" "An eerie wailing song emanates from the sirens perched upon the rocks. Many " "of your crew fall under its spell, and dive into the water where they drown. " "You are now wiser for the visit, and gain %{exp} experience." msgstr "" "Od sirén sediacich na skalách sa ozýva straÅ¡idelný kvílivý spev. Mnohí z " "tvojej posádky podľahnú ich kúzlu a ponoria sa do vody, kde sa utopia. Teraz " "si múdrejší a získavaÅ¡ %{exp} skúseností." msgid "" "In a dazzling display of daring, you break into the local jail and free the " "hero imprisoned there, who, in return, pledges loyalty to your cause." msgstr "" "Oslnivo odvážne sa vlámeÅ¡ do miestneho žalára a oslobodíš tam uväzneného " "hrdinu, ktorý ti na oplátku sľúbi vernosÅ¥." msgid "" "You already have %{count} heroes, and regretfully must leave the prisoner in " "this jail to languish in agony for untold days." msgstr "" "Už máš %{count} hrdinov a s poľutovaním musíš nechaÅ¥ väzňa v tomto žalári " "trápiÅ¥ sa po nespoÄetné dni." msgid "" "You enter a rickety hut and talk to the magician who lives there. He tells " "you of places near and far which may aid you in your journeys." msgstr "" "VstúpiÅ¡ do polorozpadnutej chatrÄe a porozprávaÅ¡ sa s kúzelníkom, ktorý tam " "žije. Povie ti o blízkych i vzdialených miestach, ktoré ti môžu pomôcÅ¥ na " "tvojich cestách." msgid "This eye seems to be intently studying its surroundings." msgstr "Zdá sa, že toto oko pozorne skúma svoje okolie." msgid "You come across a giant Sphinx. The Sphinx remains strangely quiet." msgstr "Narazíš na obrovskú sfingu. Sfinga zostáva podivne tichá." msgid "" "\"I have a riddle for you,\" the Sphinx says. \"Answer correctly, and you " "shall be rewarded. Answer incorrectly, and you shall be eaten. Do you accept " "the challenge?\"" msgstr "" "\"Mám pre teba hádanku,\" hovorí Sfinga. \"Odpovedz správne a dostane sa ti " "odmeny. Ak odpovieÅ¡ nesprávne, zjem Å¥a. PrijímaÅ¡ túto výzvu?\"" msgid "" "The Sphinx asks you the following riddle:\n" "\n" "'%{riddle}'\n" "\n" "Your answer?" msgstr "" "Sfinga ti položí nasledujúcu hádanku:\n" "\n" "'%{riddle}'\n" "\n" "Tvoja odpoveÄ?" msgid "" "\"You guessed incorrectly,\" the Sphinx says, smiling. The Sphinx swipes at " "you with a paw, knocking you to the ground. Another blow makes the world go " "black, and you know no more." msgstr "" "\"Nesprávna odpoveÄ,\" povie Sfinga a usmeje sa. Sfinga po tebe Å¡vihne labou " "a zhodí Å¥a na zem. ÄŽalší úder spôsobí, že svet sÄernie, a ty už niÄ nevieÅ¡." msgid "" "Looking somewhat disappointed, the Sphinx sighs. \"You've answered my riddle " "so here's your reward. Now begone.\"" msgstr "" "Sfinga si trochu sklamane povzdychne. \"Tvoja odpoveÄ na moju hádanku je " "správna, takže tu je tvoja odmena. Teraz odíÄ.\"" msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "As you speak the magic word, the glowing barrier dissolves into nothingness." msgstr "" "Pred tebou sa týÄi magická bariéra, ktorá ti blokuje cestu. Na oblúku je " "runami napísané,\n" "\"Vyslov kÄ¾ÃºÄ a môžeÅ¡ prejsÅ¥.\"\n" "KeÄ vyslovíš magické slovo, žiariaca bariéra sa rozplynie do niÄoty." msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "You speak, and nothing happens." msgstr "" "Pred tebou sa týÄi magická bariéra, ktorá ti blokuje cestu. Na oblúku je " "runami napísané,\n" "\"Vyslov kÄ¾ÃºÄ a môžeÅ¡ prejsÅ¥.\"\n" "Vyslovíš ho a niÄ sa nestane." msgid "" "You enter the tent and see an old woman gazing into a magic gem. She looks " "up and says,\n" "\"In my travels, I have learned much in the way of arcane magic. A great " "oracle taught me his skill. I have the answer you seek.\"" msgstr "" "VstúpiÅ¡ do stanu a uvidíš starú ženu, ktorá sa pozerá do Äarovného " "drahokamu. Pozrie sa hore a povie,\n" "\"Na svojich cestách som sa nauÄila veľa z oblasti tajomnej mágie. Moju " "schopnosÅ¥ ma nauÄil jeden veľký veÅ¡tec. Mám odpoveÄ, ktorú hľadáš.\"" msgid "No spell book is present." msgstr "Kniha kúziel nie je k dispozícii." msgid "" "This spell costs %{mana} spell points. You have no spell points, so you " "cannot cast it." msgstr "" "Toto kúzlo stojí %{mana} bodov kúziel. Nemáš žiadne body kúziel, takže ho " "nemôžeÅ¡ zoslaÅ¥." msgid "" "This spell costs %{mana} spell points. You only have %{point} spell points, " "so you cannot cast it." msgstr "" "Toto kúzlo stojí %{mana} bodov kúziel. Máš len %{point} bodov kúziel, takže " "ho nemôžeÅ¡ zoslaÅ¥." msgid "The spell was not found." msgstr "Zaklínadlo sa nenaÅ¡lo." msgid "Only heroes can cast this spell." msgstr "Toto kúzlo môžu zoslaÅ¥ len hrdinovia." msgid "This hero is not able to cast adventure spells." msgstr "Tento hrdina nie je schopný zosielaÅ¥ dobrodružné kúzla." msgid "Your hero is too tired to cast this spell today. Try again tomorrow." msgstr "" "Tvoj hrdina je príliÅ¡ unavený na to, aby dnes toto kúzlo zoslal. Skús to " "opäť zajtra." msgid "This spell cannot be cast on a boat." msgstr "Toto kúzlo nemožno použiÅ¥ na lodi." msgid "This spell can only be cast near an ocean." msgstr "Toto kúzlo možno zoslaÅ¥ len v blízkosti oceánu." msgid "" "There are no boats available and no ocean adjacent to the hero where this " "spell will work." msgstr "" "V blízkosti hrdinu nie sú k dispozícii žiadne lode ani oceán, kde by toto " "kúzlo fungovalo." msgid "There are no boats available for this spell." msgstr "Na zoslanie tohto kúzla už nie je k dispozícii žiadna loÄ." msgid "There is no ocean adjacent to the hero where this spell will work." msgstr "Vedľa hrdinu nie je miesto v oceáne, kde by toto kúzlo fungovalo." #, fuzzy msgid "" "You do not own any town or castle that is not currently occupied by a hero. " "This spell will have no effect." msgstr "" "Momentálne nevlastníš žiadne mesto ani hrad, takže toto kúzlo nemôžeÅ¡ zoslaÅ¥." #, fuzzy msgid "This hero is already in a town, so this spell will have no effect." msgstr "" "Tento hrdina sa už v meste nachádza, takže toto kúzlo nie je možné zoslaÅ¥." msgid "" "The nearest town is %{town}.\n" "\n" "This town is occupied by your hero %{hero}." msgstr "" "Najbližšie mesto je %{town}.\n" "\n" "V tomto meste sa vÅ¡ak už nachádza hrdina %{hero}." msgid "This spell is already in effect." msgstr "Toto kúzlo je už v platnosti." msgid "" "No opponent neither has nor can have any hero under their command anymore. " "Casting this spell will have no effect." msgstr "" "Žiadny protivník už nemá ani nemôže maÅ¥ pod svojím velením žiadneho hrdinu. " "Zoslanie tohto kúzla nebude maÅ¥ žiadny úÄinok." msgid "" "No opponent has a hero under their command at this time. Casting this spell " "will have no effect." msgstr "" "Žiadny protivník nemá v tejto chvíli pod svojím velením hrdinu. Zoslanie " "tohto kúzla nebude maÅ¥ žiadny úÄinok." msgid "" "You must be within %{count} spaces of a monster for the Visions spell to " "work." msgstr "" "Aby kúzlo Vízie fungovalo, musíš sa nachádzaÅ¥ do vzdialenosti %{count} " "políÄok od bojovníkov." msgid "" "You must be standing on the entrance to a mine (sawmills and alchemist labs " "do not count) to cast this spell." msgstr "" "Na zoslanie tohto kúzla musíš stáť pri vchode do bane (píly a alchymistické " "laboratóriá sa nepoÄítajú)." msgid "You must first defeat the ghosts guarding the mine to cast this spell." msgstr "" "Aby sa toto kúzlo dalo použiÅ¥, musíš najprv poraziÅ¥ Duchov, ktorí strážia " "baňu." msgid "" "There are already at least as many elementals guarding the mine as this hero " "can generate. Casting this spell will have no effect." msgstr "" "V bani už stráži minimálne toľko elementálov, koľko ich tento hrdina dokáže " "vygenerovaÅ¥. Zoslanie tohto kúzla nebude maÅ¥ žiadny úÄinok." msgid "%{name} the %{race} (Level %{level})" msgstr "%{race} %{name} (Úroveň %{level})" #, fuzzy msgid "" "'Spread' combat formation spreads the hero's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "'Å iroká' bojová formácia rozprestiera tvoje armády zhora nadol na bojisko, " "priÄom medzi každou armádou je aspoň jedno prázdne miesto." #, fuzzy msgid "" "'Grouped' combat formation bunches the hero's army together in the center of " "their side of the battlefield." msgstr "" "'Zoskupená' bojová formácia spája tvoju armádu v strede tvojej strany " "bojiska." msgid "Are you sure you want to dismiss this Hero?" msgstr "UrÄite chceÅ¡ tohto hrdinu prepustiÅ¥?" msgid "View Experience Info" msgstr "Zobraz info o skúsenostiach" msgid "View Spell Points Info" msgstr "Zobraz info o bodoch kúziel" msgid "Set army combat formation to 'Spread'" msgstr "NastaviÅ¥ bojovú formáciu na 'Å iroká'" msgid "Set army combat formation to 'Grouped'" msgstr "NastaviÅ¥ bojovú formáciu na 'Zoskupená'" msgid "Exit Hero Screen" msgstr "ZavrieÅ¥ obrazovku hrdinu" msgid "You cannot dismiss a hero in a castle" msgstr "NemôžeÅ¡ prepustiÅ¥ hrdinu v hrade" msgid "Dismissal of %{name} the %{race} is prohibited by scenario" msgstr "%{race} %{name} nesmie byÅ¥ podľa scenára prepustený" msgid "Dismiss %{name} the %{race}" msgstr "PrepustiÅ¥: %{race} %{name}" msgid "Show previous hero" msgstr "Ukáž predoÅ¡lého hrdinu" msgid "Show next hero" msgstr "Ukáž ÄalÅ¡ieho hrdinu" msgid "Blood Morale" msgstr "Krvná Morálka" msgid "%{morale} Morale" msgstr "%{morale} Morálka" msgid "%{luck} Luck" msgstr "%{luck} Šťastie" msgid "Current Luck Modifiers:" msgstr "Aktuálne modifikátory šťastia:" msgid "Current Morale Modifiers:" msgstr "Aktuálne modifikátory morálky:" msgid "Entire army is undead, so morale does not apply." msgstr "Celá armáda je nemÅ•tva, takže morálka sa na ňu nevzÅ¥ahuje." msgid "" "Current experience %{exp1}.\n" " Next level %{exp2}." msgstr "" "SúÄasné skúsenosti %{exp1}.\n" " ÄŽalÅ¡ia úroveň %{exp2}." msgid "Level %{level}" msgstr "Úroveň %{level}" #, fuzzy msgid "" "%{name} currently has %{point} spell points out of a maximum of %{max}. The " "maximum number of spell points is 10 times the hero's knowledge. It is " "occasionally possible for the hero to have more than their maximum spell " "points via special events." msgstr "" "%{name} má v súÄasnosti %{point} bodov kúziel z maximálneho poÄtu %{max}. " "Maximálny poÄet bodov kúziel je 10-násobok tvojich znalostí. Príležitostne " "je možné maÅ¥ viac, ako je tvoj maximálny poÄet bodov kúziel, prostredníctvom " "Å¡peciálnych udalostí." msgid "%{name1} meets %{name2}" msgstr "%{name1} a %{name2} sa stretávajú" msgid "Enemy heroes are now fully identifiable." msgstr "Nepriateľskí hrdinovia sú teraz plne identifikovateľní." msgid "Identify Hero" msgstr "IdentifikovaÅ¥ hrdinu" msgid "Select town to port to." msgstr "Vyber mesto, do ktorého sa chceÅ¡ preniesÅ¥." msgid "Town Portal" msgstr "Mestský portál" msgid "The creatures are willing to join us!" msgstr "Bojovníci sú ochotní sa k nám pridaÅ¥!" msgid "All the creatures will join us..." msgstr "VÅ¡etci bojovníci sa k nám pridajú..." msgid "The creature will join us..." msgid_plural "%{count} of the creatures will join us..." msgstr[0] "Bojovník sa k nám pridá..." msgstr[1] "%{count} bojovníci sa k nám pridajú..." msgstr[2] "%{count} bojovníkov sa k nám pridá..." msgid "" "\n" " for a fee of %{gold} gold." msgstr "" "\n" " za poplatok %{gold} zlata." msgid "These weak creatures will surely flee before us." msgstr "Títo slabí bojovníci pred nami urÄite uteÄú." msgid "I fear these creatures are in the mood for a fight." msgstr "Obávam sa, že títo bojovníci sú naladení na boj." #, fuzzy msgid "The hero's attack skill is a bonus added to each unit's attack skill." msgstr "" "Tvoja útoÄná schopnosÅ¥ je bonus pridaný k útoÄnej schopnosti každého " "bojovníka." #, fuzzy msgid "The hero's defense skill is a bonus added to each unit's defense skill." msgstr "" "Tvoja obranná schopnosÅ¥ je bonus pridaný k obrannej schopnosti každého " "bojovníka." #, fuzzy msgid "The hero's spell power determines the duration or power of a spell." msgstr "Tvoja Sila kúziel urÄuje dĺžku alebo silu kúzla." #, fuzzy msgid "" " The hero's knowledge determines how many spell points the hero may have. " "Under normal circumstances, a hero is limited to 10 spell points per level " "of knowledge." msgstr "" "Tvoje vedomosti urÄujú, koľko bodov kúziel môže maÅ¥ tvoj hrdina. Za " "normálnych okolností je hrdina obmedzený na 10 bodov kúziel na úroveň " "vedomosti." msgid "Current Modifiers:" msgstr "SúÄasné modifikátory:" msgid "skill|Basic" msgstr "Základ" msgid "skill|Advanced" msgstr "PokroÄilý" msgid "skill|Expert" msgstr "Expert" msgid "Pathfinding" msgstr "Hľad. ciest" msgid "Archery" msgstr "Lukostreľba" msgid "Logistics" msgstr "Logistika" msgid "Scouting" msgstr "Prieskum" msgid "Diplomacy" msgstr "Diplomacia" msgid "Navigation" msgstr "Navigácia" msgid "Leadership" msgstr "Vedenie" msgid "Wisdom" msgstr "MúdrosÅ¥" msgid "Mysticism" msgstr "Mystika" msgid "Ballistics" msgstr "Balistika" msgid "Eagle Eye" msgstr "Orlí zrak" msgid "Necromancy" msgstr "Nekromancia" msgid "Estates" msgstr "Statky" msgid "Advanced Archery" msgstr "PokroÄilá lukostreľba" msgid "Advanced Pathfinding" msgstr "PokroÄilé hľadanie ciest" msgid "Basic Archery" msgstr "Základná lukostreľba" msgid "Basic Pathfinding" msgstr "Základné hľadanie ciest" msgid "Expert Pathfinding" msgstr "Expertné hľadanie ciest" msgid "Advanced Logistics" msgstr "PokroÄilá logistika" msgid "Basic Logistics" msgstr "Základná logistika" msgid "Basic Scouting" msgstr "Základný prieskum" msgid "Expert Archery" msgstr "Expertná lukostreľba" msgid "Expert Logistics" msgstr "Expertná logistika" msgid "Advanced Diplomacy" msgstr "PokroÄilá diplomacia" msgid "Advanced Scouting" msgstr "PokroÄilý prieskum" msgid "Basic Diplomacy" msgstr "Základná diplomacia" msgid "Expert Diplomacy" msgstr "Expertná diplomacia" msgid "Expert Scouting" msgstr "Expertný prieskum" msgid "Advanced Leadership" msgstr "PokroÄilé vedenie" msgid "Advanced Navigation" msgstr "PokroÄilá navigácia" msgid "Basic Leadership" msgstr "Základné vedenie" msgid "Basic Navigation" msgstr "Základná navigácia" msgid "Expert Navigation" msgstr "Expertná navigácia" msgid "Advanced Wisdom" msgstr "PokroÄilá múdrosÅ¥" msgid "Basic Mysticism" msgstr "Základná mystika" msgid "Basic Wisdom" msgstr "Základná múdrosÅ¥" msgid "Expert Leadership" msgstr "Expertné vedenie" msgid "Expert Wisdom" msgstr "Expertná múdrosÅ¥" msgid "Advanced Luck" msgstr "PokroÄilé šťastie" msgid "Advanced Mysticism" msgstr "PokroÄilá mystika" msgid "Basic Luck" msgstr "Základné šťastie" msgid "Expert Luck" msgstr "Expertné šťastie" msgid "Expert Mysticism" msgstr "Expertná mystika" msgid "Advanced Ballistics" msgstr "PokroÄilá balistika" msgid "Advanced Eagle Eye" msgstr "PokroÄilý orlí zrak" msgid "Basic Ballistics" msgstr "Základná balistika" msgid "Basic Eagle Eye" msgstr "Základný orlí zrak" msgid "Expert Ballistics" msgstr "Expertná balistika" msgid "Advanced Necromancy" msgstr "PokroÄilá nekromancia" msgid "Basic Estates" msgstr "Základné statky" msgid "Basic Necromancy" msgstr "Základná nekromancia" msgid "Expert Eagle Eye" msgstr "Expertný orlí zrak" msgid "Expert Necromancy" msgstr "Expertná nekromancia" msgid "Advanced Estates" msgstr "PokroÄilé statky" msgid "Expert Estates" msgstr "Expertné statky" msgid "" "%{skill} reduces the movement penalty for rough terrain by %{count} percent." msgstr "" "%{skill} znižuje postih za pohyb po nerovnom teréne o %{count} percent." msgid "%{skill} eliminates the movement penalty for rough terrain." msgstr "%{skill} eliminuje postih za pohyb po nerovnom teréne." #, fuzzy msgid "" "%{skill} increases the damage done by the hero's range attacking creatures " "by %{count} percent." msgstr "" "%{skill} zvyÅ¡uje poÅ¡kodenie spôsobené bojovníkmi útoÄiacimi na diaľku o " "%{count} percent." #, fuzzy msgid "%{skill} increases the hero's movement points by %{count} percent." msgstr "%{skill} zvýši pohybové body tvojho hrdinu o %{count} percent." #, fuzzy msgid "%{skill} increases the hero's viewable area by one square." msgid_plural "%{skill} increases the hero's viewable area by %{count} squares." msgstr[0] "%{skill} zväÄší dohľadnú oblasÅ¥ tvojho hrdinu o jeden Å¡tvorec." msgstr[1] "%{skill} zväÄší dohľadnú oblasÅ¥ tvojho hrdinu o %{count} Å¡tvorce." msgstr[2] "%{skill} zväÄší dohľadnú oblasÅ¥ tvojho hrdinu o %{count} Å¡tvorcov." #, fuzzy msgid "" "%{skill} allows the hero to negotiate with monsters who are weaker than " "their army, and reduces the cost of surrender." msgstr "" "%{skill} ti umožňuje vyjednávanie s príšerami, ktoré sú slabÅ¡ie ako tvoja " "skupina. " #, fuzzy msgid "" "Approximately %{count} percent of the creatures may offer to join the hero." msgstr "" "Približne %{count} percent bojovníkov ti môže ponúknuÅ¥, že sa k tebe pridá." #, fuzzy msgid "All of the creatures may offer to join the hero." msgstr "VÅ¡etci bojovníci ti môžu ponúknuÅ¥, že sa k tebe pridajú." #, fuzzy msgid "" "The cost of surrender is reduced to %{percent} percent of the total cost of " "troops in the army." msgstr "" "%{name} znižuje cenu vzdania sa o %{count} percent celkovej ceny jednotiek, " "ktoré máš v armáde." #, fuzzy msgid "" "%{skill} increases the hero's movement points over water by %{count} percent." msgstr "%{skill} zvýši pohybové body tvojho hrdinu po vode o %{count} percent." #, fuzzy msgid "%{skill} increases the hero's troops morale by %{count}." msgstr "%{skill} zvýši morálku vojska tvojho hrdinu o %{count}." #, fuzzy msgid "%{skill} allows the hero to learn third level spells." msgstr "%{skill} umožňuje tvojmu hrdinovi nauÄiÅ¥ sa kúzla tretej úrovne." #, fuzzy msgid "%{skill} allows the hero to learn fourth level spells." msgstr "%{skill} umožňuje tvojmu hrdinovi nauÄiÅ¥ sa kúzla Å¡tvrtej úrovne." #, fuzzy msgid "%{skill} allows the hero to learn fifth level spells." msgstr "%{skill} umožňuje tvojmu hrdinovi nauÄiÅ¥ sa kúzla piatej úrovne." #, fuzzy msgid "%{skill} regenerates one additional spell point per day to the hero." msgid_plural "" "%{skill} regenerates %{count} additional spell points per day to the hero." msgstr[0] "" "%{skill} regeneruje tvojmu hrdinovi o %{count} bod kúziel za deň viac." msgstr[1] "" "%{skill} regeneruje tvojmu hrdinovi o %{count} body kúziel za deň viac." msgstr[2] "" "%{skill} regeneruje tvojmu hrdinovi o %{count} bodov kúziel za deň viac." #, fuzzy msgid "%{skill} increases the hero's luck by %{count}." msgstr "%{skill} zvyÅ¡uje šťastie tvojho hrdinu o %{count}." #, fuzzy msgid "" "%{skill} gives the hero's catapult shots a greater chance to hit and do " "damage to castle walls." msgstr "" "%{skill} dáva výstrelom z katapultu tvojho hrdinu väÄÅ¡iu Å¡ancu zasiahnuÅ¥ a " "poÅ¡kodiÅ¥ hradné múry." #, fuzzy msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot has a " "greater chance to hit and do damage to castle walls." msgstr "" "%{skill} dáva katapultu tvojho hrdinu jeden výstrel navyÅ¡e a každý výstrel " "má väÄÅ¡iu Å¡ancu zasiahnuÅ¥ a poÅ¡kodiÅ¥ hradné múry." #, fuzzy msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot " "automatically destroys any wall, except a fortified wall in a Knight castle." msgstr "" "%{skill} dáva katapultu tvojho hrdinu jeden výstrel navyÅ¡e a každý výstrel " "automaticky zniÄí akúkoľvek stenu, okrem opevnenej steny v Rytierskom hrade." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 1st or " "2nd level spell that was cast by an enemy during combat." msgstr "" "%{skill} dáva tvojmu hrdinovi %{count}-percentnú Å¡ancu nauÄiÅ¥ sa akékoľvek " "kúzlo 1. alebo 2. úrovne, ktoré bolo zoslané nepriateľom poÄas boja." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 3rd " "level spell (or below) that was cast by an enemy during combat." msgstr "" "%{skill} dáva tvojmu hrdinovi %{count}-percentnú Å¡ancu nauÄiÅ¥ sa akékoľvek " "kúzlo 3. úrovne (alebo nižšej), ktoré bolo zoslané nepriateľom poÄas boja." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 4th " "level spell (or below) that was cast by an enemy during combat." msgstr "" "%{skill} dáva tvojmu hrdinovi %{count}-percentnú Å¡ancu nauÄiÅ¥ sa akékoľvek " "kúzlo 4. úrovne (alebo nižšej), ktoré bolo zoslané nepriateľom poÄas boja." msgid "" "%{skill} allows %{count} percent of the creatures killed in combat to be " "brought back from the dead as Skeletons." msgstr "" "%{skill} umožňuje, aby %{count} percent bojovníkov zabitých v boji vstalo z " "mÅ•tvych ako Kostlivci." #, fuzzy msgid "" "The hero produces %{count} gold pieces per day as tax revenue from estates." msgstr "" "Tvoj hrdina vyprodukuje %{count} zlata za deň ako daňový príjem z majetku." msgid "Blue" msgstr "Modrý" msgid "Green" msgstr "Zelený" msgid "Red" msgstr "ÄŒervený" msgid "Yellow" msgstr "Žltý" msgid "Orange" msgstr "Oranžový" msgid "Purple" msgstr "Fialový" msgid "barrier|Aqua" msgstr "Tyrkysová" msgid "barrier|Blue" msgstr "Modrá" msgid "barrier|Brown" msgstr "Hnedá" msgid "barrier|Gold" msgstr "Zlatá" msgid "barrier|Green" msgstr "Zelená" msgid "barrier|Orange" msgstr "Oranžová" msgid "barrier|Purple" msgstr "Fialová" msgid "barrier|Red" msgstr "ÄŒervená" msgid "tent|Aqua" msgstr "Tyrkysový" msgid "tent|Blue" msgstr "Modrý" msgid "tent|Brown" msgstr "Hnedý" msgid "tent|Gold" msgstr "Zlatý" msgid "tent|Green" msgstr "Zelený" msgid "tent|Orange" msgstr "Oranžový" msgid "tent|Purple" msgstr "Fialový" msgid "tent|Red" msgstr "ÄŒervený" msgid "Experience" msgstr "Skúsenosti" msgid "" "Experience allows your heroes to go up levels, increasing their primary and " "secondary skills." msgstr "" "Skúsenosti umožňujú hrdinom zvyÅ¡ovaÅ¥ úroveň, Äím sa zvyÅ¡ujú ich prvotné a " "druhotné zruÄnosti." msgid "Hero/Stats" msgstr "Hrdina/Å tatistika" msgid "Skills" msgstr "ZruÄnosti" msgid "Town/Castle" msgstr "Mesto/Hrad" msgid "Garrison" msgstr "Posádka" msgid "Gold Per Day:" msgstr "Zlato za deň:" msgid "View Heroes." msgstr "Ukáž hrdinov." msgid "Towns/Castles" msgstr "Mestá/Hrady" msgid "View Towns and Castles." msgstr "Ukáž mestá a hrady." msgid "luck|Cursed" msgstr "Prekliate" msgid "luck|Awful" msgstr "Hrozné" msgid "luck|Bad" msgstr "Zlé" msgid "luck|Normal" msgstr "Normálne" msgid "luck|Good" msgstr "Dobré" msgid "luck|Great" msgstr "Skvelé" msgid "luck|Irish" msgstr "Ãrske" #, fuzzy msgid "" "Bad luck sometimes falls on the hero's units in combat, causing their " "attacks to only do half damage." msgstr "" "V boji sa na tvoje vojsko niekedy obráti smola a jeho útoky spôsobia len " "poloviÄné poÅ¡kodenie." #, fuzzy msgid "" "Neutral luck means the hero's units will never get lucky or unlucky attacks " "on the enemy." msgstr "" "Neutrálne šťastie znamená, že tvoje vojsko nikdy nebude maÅ¥ šťastie alebo " "smolu pri útokoch na nepriateľa." #, fuzzy msgid "" "Good luck sometimes lets the hero's units get lucky attacks (double " "strength) in combat." msgstr "" "Šťastie niekedy umožňuje tvojmu vojsku získaÅ¥ šťastné útoky (dvojnásobnú " "silu) v boji." msgid "morale|Treason" msgstr "Zrada" msgid "morale|Awful" msgstr "Hrozná" msgid "morale|Poor" msgstr "Slabá" msgid "morale|Normal" msgstr "Normálna" msgid "morale|Good" msgstr "Dobrá" msgid "morale|Great" msgstr "Skvelá" msgid "morale|Blood!" msgstr "Krvná!" #, fuzzy msgid "Bad morale may cause the hero's units to freeze in combat." msgstr "Zlá morálka môže spôsobiÅ¥, že sa tvoje vojsko v boji zastaví." #, fuzzy msgid "" "Neutral morale means the hero's units will never be blessed with extra " "attacks or freeze in combat." msgstr "" "Neutrálna morálka znamená, že tvoje vojsko nebude nikdy obdarené extra " "útokmi ani nezamrzne v boji." #, fuzzy msgid "Good morale may give the hero's units extra attacks in combat." msgstr "Dobrá morálka môže tvojmu vojsku poskytnúť extra útoky v boji." msgid "Knight" msgstr "Rytier" msgid "Barbarian" msgstr "Barbar" msgid "Sorceress" msgstr "ZaklínaÄka" msgid "Warlock" msgstr "ÄŒernokňažník" msgid "Wizard" msgstr "Mág" msgid "Necromancer" msgstr "Nekromant" msgid "Multi" msgstr "Multi" msgid "doubleLined|Knight" msgstr "Rytier" msgid "doubleLined|Barbarian" msgstr "Barbar" msgid "doubleLined|Sorceress" msgstr "ZaklínaÄka" msgid "doubleLined|Warlock" msgstr "" "ÄŒerno-\n" "kňažník" msgid "doubleLined|Wizard" msgstr "Mág" msgid "" "doubleLined|Necro-\n" "mancer" msgstr "Nekromant" msgid "doubleLinedRace|Multi" msgstr "Multi" msgid "doubleLinedRace|Random" msgstr "Náhodná" msgid "speed|Standing" msgstr "Stojaca" msgid "speed|Crawling" msgstr "Plaziaca" msgid "speed|Very Slow" msgstr "Veľmi pomalá" msgid "speed|Slow" msgstr "Pomalá" msgid "speed|Average" msgstr "Priemerná" msgid "speed|Fast" msgstr "Rýchla" msgid "speed|Very Fast" msgstr "Veľmi rýchla" msgid "speed|Ultra Fast" msgstr "Ultrarýchla" msgid "speed|Blazing" msgstr "Spaľujúca" msgid "speed|Instant" msgstr "Okamžitá" msgid "week|Squirrel" msgstr "Veverice" msgid "week|Rabbit" msgstr "Zajaca" msgid "week|Gopher" msgstr "Svišťa" msgid "week|Badger" msgstr "Jazveca" msgid "week|Rat" msgstr "Potkana" msgid "week|Eagle" msgstr "Orla" msgid "week|Weasel" msgstr "Lasice" msgid "week|Raven" msgstr "Havrana" msgid "week|Mongoose" msgstr "Mangusty" msgid "week|Dog" msgstr "Psa" msgid "week|Aardvark" msgstr "MravÄiara" msgid "week|Lizard" msgstr "JaÅ¡terice" msgid "week|Tortoise" msgstr "KorytnaÄky" msgid "week|Hedgehog" msgstr "Ježa" msgid "week|Condor" msgstr "Kondora" msgid "week|Ant" msgstr "Mravca" msgid "week|Grasshopper" msgstr "LúÄneho koníka" msgid "week|Dragonfly" msgstr "Vážky" msgid "week|Spider" msgstr "Pavúka" msgid "week|Butterfly" msgstr "Motýľa" msgid "week|Bumblebee" msgstr "ÄŒmeliaka" msgid "week|Locust" msgstr "Kobylky" msgid "week|Earthworm" msgstr "DážÄovky" msgid "week|Hornet" msgstr "Sršňa" msgid "week|Beetle" msgstr "Chrobáka" msgid "week|PLAGUE" msgstr "POHROMY" msgid "week|Unnamed" msgstr "Bez mena" msgid "Desert" msgstr "Púšť" msgid "Snow" msgstr "Sneh" msgid "Wasteland" msgstr "Pustatina" msgid "Beach" msgstr "Pláž" msgid "Lava" msgstr "Láva" msgid "Dirt" msgstr "Hlina" msgid "Grass" msgstr "Tráva" msgid "Ocean" msgstr "Oceán" msgid "maps|Small" msgstr "Malá" msgid "maps|Medium" msgstr "Stredná" msgid "maps|Large" msgstr "Veľká" msgid "maps|Extra Large" msgstr "Extra veľká" msgid "maps|Custom Size" msgstr "Vlastná veľkosÅ¥" msgid "Ore Mine" msgstr "Baňa na rudu" msgid "Sulfur Mine" msgstr "Baňa na síru" msgid "Crystal Mine" msgstr "KryÅ¡tálová baňa" msgid "Gems Mine" msgstr "Baňa na drahokamy" msgid "Gold Mine" msgstr "Zlatá baňa" msgid "Mine" msgstr "Baňa" msgid "Burma shave." msgstr "Barmské holenie." msgid "Next sign 50 miles." msgstr "ÄŽalÅ¡ia znaÄka o 50 míľ." msgid "See Rock City." msgstr "NavÅ¡tívte Rock City." msgid "This space for rent." msgstr "Tieto priestory na prenájom." msgid "No object" msgstr "NiÄ" msgid "Alchemist Lab" msgstr "Alchymistické laboratórium" msgid "Sign" msgstr "ZnaÄka" msgid "Buoy" msgstr "Bója" msgid "Skeleton" msgstr "Kostlivec" msgid "Daemon Cave" msgstr "Jaskyňa démona" msgid "Treasure Chest" msgstr "Truhlica s pokladom" msgid "Faerie Ring" msgstr "Vílý kruh" msgid "Campfire" msgstr "Táborák" msgid "Fountain" msgstr "Fontána" msgid "Gazebo" msgstr "Altánok" msgid "Genie Lamp" msgstr "Džinova lampa" msgid "Archer's House" msgstr "Lukostrelecký dom" msgid "Goblin Hut" msgstr "Chata Goblinov" msgid "Dwarf Cottage" msgstr "TrpasliÄia chalúpka" msgid "Peasant Hut" msgstr "Sedliacka chatrÄ" msgid "Stables" msgstr "Stajne" msgid "Alchemist's Tower" msgstr "Alchymistova veža" msgid "Event" msgstr "UdalosÅ¥" msgid "Dragon City" msgstr "DraÄie mesto" msgid "Lighthouse" msgid_plural "Lighthouses" msgstr[0] "Maják" msgstr[1] "Majáky" msgstr[2] "Majákov" msgid "Water Wheel" msgid_plural "Water Wheels" msgstr[0] "Vodné koleso" msgstr[1] "Vodné kolesá" msgstr[2] "Vodných kolies" msgid "Monster" msgstr "Tvor" msgid "Obelisk" msgstr "Obelisk" msgid "Oasis" msgstr "Oáza" msgid "Resource" msgstr "Surovina" msgid "Sawmill" msgstr "Píla" msgid "Oracle" msgstr "VeÅ¡tec" msgid "Shrine of the First Circle" msgstr "Svätyňa prvého kruhu" msgid "Shipwreck" msgstr "Lodný vrak" msgid "Sea Chest" msgstr "Morská truhlica" msgid "Desert Tent" msgstr "Púštny stan" msgid "Stone Liths" msgstr "Kamenné pylóny" msgid "Wagon Camp" msgstr "Vozový tábor" msgid "Hut of the Magi" msgstr "Chata mágov" msgid "Whirlpool" msgstr "Vodný vír" msgid "Windmill" msgid_plural "Windmills" msgstr[0] "Veterný mlyn" msgstr[1] "Veterné mlyny" msgstr[2] "Veterných mlynov" msgid "Artifact" msgstr "Artefakt" msgid "Mermaid" msgstr "Morská panna" msgid "Boat" msgstr "LoÄ" msgid "Random Ultimate Artifact" msgstr "Náhodný ultimátny artefakt" msgid "Random Artifact" msgstr "Náhodný artefakt" msgid "Random Resource" msgstr "Náhodná surovina" msgid "Random Monster" msgstr "Náhodný tvor" msgid "Random Town" msgstr "Náhodné mesto" msgid "Random Castle" msgstr "Náhodný hrad" msgid "Eye of the Magi" msgstr "Oko mágov" msgid "Random Monster - weak" msgstr "Náhodný tvor - slabý" msgid "Random Monster - medium" msgstr "Náhodný tvor - stredný" msgid "Random Monster - strong" msgstr "Náhodný tvor - silný" msgid "Random Monster - very strong" msgstr "Náhodný tvor - veľmi silný" #, fuzzy msgid "Hero" msgstr "Hrdinovia" msgid "Nothing Special" msgstr "NiÄ zvláštne" msgid "Mossy Rock" msgstr "Machový kameň" msgid "Watch Tower" msgstr "Strážna veža" msgid "Tree House" msgstr "Dom na strome" msgid "Tree City" msgstr "Stromové mesto" msgid "Ruins" msgstr "Ruiny" msgid "Fort" msgstr "PevnosÅ¥" msgid "Abandoned Mine" msgstr "Opustená baňa" msgid "Sirens" msgstr "Sirény" msgid "Standing Stones" msgstr "Stojace kamene" msgid "Idol" msgstr "Modla" msgid "Tree of Knowledge" msgstr "Strom poznania" msgid "Witch Doctor's Hut" msgstr "LieÄiteľova chatrÄ" msgid "Temple" msgstr "Chrám" msgid "Hill Fort" msgstr "Hradisko" msgid "Halfling Hole" msgstr "PolÄíÄia diera" msgid "Mercenary Camp" msgstr "Žoldniersky tábor" msgid "Shrine of the Second Circle" msgstr "Svätyňa druhého kruhu" msgid "Shrine of the Third Circle" msgstr "Svätyňa tretieho kruhu" msgid "City of the Dead" msgstr "Mesto mÅ•tvych" msgid "Sphinx" msgstr "Sfinga" msgid "Wagon" msgstr "Voz" msgid "Tar Pit" msgstr "Smolná jama" msgid "Artesian Spring" msgstr "Artézsky prameň" msgid "Troll Bridge" msgstr "Trolí most" msgid "Watering Hole" msgstr "Napájadlo" msgid "Witch's Hut" msgstr "Ježibabin domÄek" msgid "Xanadu" msgstr "Xanadu" msgid "Lean-To" msgstr "PrístreÅ¡ok" msgid "Magellan's Maps" msgstr "Magalhaesove mapy" msgid "Flotsam" msgstr "Vyhodený náklad" msgid "Derelict Ship" msgstr "Opustená loÄ" msgid "Shipwreck Survivor" msgstr "TroseÄník" msgid "Bottle" msgstr "FľaÅ¡a" msgid "Magic Well" msgstr "ÄŒarovná studňa" msgid "Magic Garden" msgid_plural "Magic Gardens" msgstr[0] "ÄŒarovná záhrada" msgstr[1] "ÄŒarovné záhrady" msgstr[2] "ÄŒarovných záhrad" msgid "Observation Tower" msgstr "Rozhľadňa" msgid "Freeman's Foundry" msgstr "Súkromná zlievareň" msgid "Reefs" msgstr "Útesy" msgid "Volcano" msgstr "Sopka" msgid "Flowers" msgstr "Kvety" msgid "Rock" msgstr "Skala" msgid "Water Lake" msgstr "Jazero s vodou" msgid "Mandrake" msgstr "Mandragora" msgid "Dead Tree" msgstr "Odumretý strom" msgid "Stump" msgstr "Peň" msgid "Crater" msgstr "Kráter" msgid "Cactus" msgstr "Kaktus" msgid "Mound" msgstr "Mohyla" msgid "Dune" msgstr "Duna" msgid "Lava Pool" msgstr "Lávové jazierko" msgid "Shrub" msgstr "Krík" msgid "Barrow Mounds" msgstr "Norné mohyly" msgid "Random Artifact - Treasure" msgstr "Náhodný artefakt - Poklad" msgid "Random Artifact - Minor" msgstr "Náhodný artefakt - Menší" msgid "Random Artifact - Major" msgstr "Náhodný artefakt - VäÄší" msgid "Barrier" msgstr "Bariéra" msgid "Traveller's Tent" msgstr "Cestovateľský stan" msgid "Jail" msgstr "Žalár" msgid "Fire Summoning Altar" msgstr "Oltár vyvolávania ohňa" msgid "Air Summoning Altar" msgstr "Oltár vyvolávania vzuchu" msgid "Earth Summoning Altar" msgstr "Oltár vyvolávania zeme" msgid "Water Summoning Altar" msgstr "Oltár vyvolávania vody" msgid "Unknown Monster" msgstr "Neznámy bojovník" msgid "Unknown Monsters" msgstr "Neznámi bojovníci" msgid "Peasant" msgstr "Roľník" msgid "Peasants" msgstr "Roľníci" msgid "Archer" msgstr "Lukostrelec" msgid "Archers" msgstr "Lukostrelci" msgid "Ranger" msgstr "HraniÄiar" msgid "Rangers" msgstr "HraniÄiari" msgid "Pikeman" msgstr "Kopijník" msgid "Pikemen" msgstr "Kopijníci" msgid "Veteran Pikeman" msgstr "Veterán kopijník" msgid "Veteran Pikemen" msgstr "Veteráni kopijníci" msgid "Swordsman" msgstr "Å ermiar" msgid "Swordsmen" msgstr "Å ermiari" msgid "Master Swordsman" msgstr "Majster Å¡ermiar" msgid "Master Swordsmen" msgstr "Majstri Å¡ermiari" msgid "Cavalries" msgstr "Kavaléri" msgid "Cavalry" msgstr "Kavalér" msgid "Champion" msgstr "Å ampión" msgid "Champions" msgstr "Å ampióni" msgid "Paladin" msgstr "Paladín" msgid "Paladins" msgstr "Paladíni" msgid "Crusader" msgstr "Križiak" msgid "Crusaders" msgstr "Križiaci" msgid "Goblin" msgstr "Goblin" msgid "Goblins" msgstr "Goblini" msgid "Orc" msgstr "Ork" msgid "Orcs" msgstr "Orkovia" msgid "Orc Chief" msgstr "NáÄelník orkov" msgid "Orc Chiefs" msgstr "NáÄelníci orkov" msgid "Wolf" msgstr "Vlk" msgid "Wolves" msgstr "Vlky" msgid "Ogre" msgstr "Zlobor" msgid "Ogres" msgstr "Zlobri" msgid "Ogre Lord" msgstr "Lord zlobrov" msgid "Ogre Lords" msgstr "Lordi zlobrov" msgid "Troll" msgstr "Trol" msgid "Trolls" msgstr "Trolovia" msgid "War Troll" msgstr "Bojový trol" msgid "War Trolls" msgstr "Bojoví trolovia" msgid "Cyclopes" msgstr "Kyklopi" msgid "Cyclops" msgstr "Kyklop" msgid "Sprite" msgstr "Víla" msgid "Sprites" msgstr "Víly" msgid "Dwarf" msgstr "Trpaslík" msgid "Dwarves" msgstr "Trpaslíci" msgid "Battle Dwarf" msgstr "Bojový trpaslík" msgid "Battle Dwarves" msgstr "Bojoví trpaslíci" msgid "Elf" msgstr "Elf" msgid "Elves" msgstr "Elfovia" msgid "Grand Elf" msgstr "VzneÅ¡ený elf" msgid "Grand Elves" msgstr "VzneÅ¡ení elfovia" msgid "Druid" msgstr "Druid" msgid "Druids" msgstr "Druidi" msgid "Greater Druid" msgstr "Arcidruid" msgid "Greater Druids" msgstr "Arcidruidi" msgid "Unicorn" msgstr "Jednorožec" msgid "Unicorns" msgstr "Jednorožci" msgid "Phoenix" msgstr "Fénix" msgid "Phoenixes" msgstr "Fénixovia" msgid "Centaur" msgstr "Kentaur" msgid "Centaurs" msgstr "Kentauri" msgid "Gargoyle" msgstr "ChrliÄ" msgid "Gargoyles" msgstr "ChrliÄe" msgid "Griffin" msgstr "Gryf" msgid "Griffins" msgstr "Gryfy" msgid "Minotaur" msgstr "Minotaur" msgid "Minotaurs" msgstr "Minotauri" msgid "Minotaur King" msgstr "Minotaurí kráľ" msgid "Minotaur Kings" msgstr "Minotaurí králi" msgid "Hydra" msgstr "Hydra" msgid "Hydras" msgstr "Hydry" msgid "Green Dragon" msgstr "Zelený drak" msgid "Green Dragons" msgstr "Zelení draci" msgid "Red Dragon" msgstr "ÄŒervený drak" msgid "Red Dragons" msgstr "ÄŒervení draci" msgid "Black Dragon" msgstr "ÄŒierny drak" msgid "Black Dragons" msgstr "ÄŒierni draci" msgid "Halfling" msgstr "PolÄík" msgid "Halflings" msgstr "PolÄíci" msgid "Boar" msgstr "Kanec" msgid "Boars" msgstr "Kance" msgid "Iron Golem" msgstr "Železný golem" msgid "Iron Golems" msgstr "Železní golemovia" msgid "Steel Golem" msgstr "Oceľový golem" msgid "Steel Golems" msgstr "Oceľoví golemovia" msgid "Roc" msgstr "Roch" msgid "Rocs" msgstr "Rochy" msgid "Mage" msgstr "Mág" msgid "Magi" msgstr "Mágovia" msgid "Archmage" msgstr "Arcimág" msgid "Archmagi" msgstr "Arcimágovia" msgid "Giant" msgstr "Obor" msgid "Giants" msgstr "Obri" msgid "Titan" msgstr "Titán" msgid "Titans" msgstr "Titáni" msgid "Skeletons" msgstr "Kostlivci" msgid "Zombie" msgstr "Zombík" msgid "Zombies" msgstr "Zombíci" msgid "Mutant Zombie" msgstr "Zombík mutant" msgid "Mutant Zombies" msgstr "Zombíci mutanti" msgid "Mummies" msgstr "Múmie" msgid "Mummy" msgstr "Múmia" msgid "Royal Mummies" msgstr "Kráľovské múmie" msgid "Royal Mummy" msgstr "Kráľovská múmia" msgid "Vampire" msgstr "Upír" msgid "Vampires" msgstr "Upíri" msgid "Vampire Lord" msgstr "Upírí lord" msgid "Vampire Lords" msgstr "Upírí lordi" msgid "Lich" msgstr "Kostej" msgid "Liches" msgstr "Kostejovia" msgid "Power Lich" msgstr "Mocný kostej" msgid "Power Liches" msgstr "Mocní kostejovia" msgid "Bone Dragon" msgstr "Kostnatý drak" msgid "Bone Dragons" msgstr "Kostnatí draci" msgid "Rogue" msgstr "Tulák" msgid "Rogues" msgstr "Tuláci" msgid "Nomad" msgstr "Nomád" msgid "Nomads" msgstr "Nomádi" msgid "Ghost" msgstr "Duch" msgid "Ghosts" msgstr "Duchovia" msgid "Genie" msgstr "Džin" msgid "Genies" msgstr "Džinovia" msgid "Medusa" msgstr "Medúza" msgid "Medusas" msgstr "Medúzy" msgid "Earth Elemental" msgstr "Zemský elementál" msgid "Earth Elementals" msgstr "Zemskí elementáli" msgid "Air Elemental" msgstr "VzduÅ¡ný elementál" msgid "Air Elementals" msgstr "VzduÅ¡ní elementáli" msgid "Fire Elemental" msgstr "Ohnivý elementál" msgid "Fire Elementals" msgstr "Ohniví elementáli" msgid "Water Elemental" msgstr "Vodný elementál" msgid "Water Elementals" msgstr "Vodní elementáli" msgid "Random Monsters" msgstr "Náhodné tvory" msgid "Random Monster 1" msgstr "Náhodný tvor 1" msgid "Random Monsters 1" msgstr "Náhodné tvory 1" msgid "Random Monster 2" msgstr "Náhodný tvor 2" msgid "Random Monsters 2" msgstr "Náhodné tvory 2" msgid "Random Monster 3" msgstr "Náhodný tvor 3" msgid "Random Monsters 3" msgstr "Náhodné tvory 3" msgid "Random Monster 4" msgstr "Náhodný tvor 4" msgid "Random Monsters 4" msgstr "Náhodné tvory 4" msgid "Double shot" msgstr "Dvojitý výstrel" msgid "2-hex monster" msgstr "Bojovník zaberá 2 polia" msgid "Double strike" msgstr "Dvojitý úder" msgid "Double damage to Undead" msgstr "Dvojité poÅ¡kodenie nemÅ•tvym" msgid "% magic resistance" msgstr "% magická odolnosÅ¥" msgid "Immune to Mind spells" msgstr "Imunita voÄi kúzlam mysli" msgid "Immune to Elemental spells" msgstr "Imunita voÄi elementálnym kúzlam" msgid "Immune to Fire spells" msgstr "Imunita voÄi kúzlam ohňa" msgid "Immune to Cold spells" msgstr "Imunita voÄi kúzlam chladu" msgid "Immune to " msgstr "Imunita voÄi efektu " msgid "% immunity to %{spell} spell" msgstr "% imunity voÄi kúzlu %{spell}" msgid "% damage from Elemental spells" msgstr "% poÅ¡kodenia od elementálnych kúziel" msgid "% chance to Dispel beneficial spells" msgstr "% Å¡anca na rozptýlenie prospeÅ¡ných kúziel" msgid "% chance to Paralyze" msgstr "% Å¡anca na paralyzovanie" msgid "% chance to Petrify" msgstr "% Å¡anca na vydesenie" msgid "% chance to Blind" msgstr "% Å¡anca na oslepenie" msgid "% chance to Curse" msgstr "% Å¡anca na prekliatie" msgid "% chance to cast %{spell} spell" msgstr "% Å¡anca na vyÄarovanie kúzla %{spell}" msgid "HP regeneration" msgstr "Regenerácia zdravia" msgid "Two hexes attack" msgstr "Útok cez dve polia" msgid "Flyer" msgstr "Lieta" msgid "Always retaliates" msgstr "Vždy vracia úder" msgid "Attacks all adjacent enemies" msgstr "ÚtoÄí na vÅ¡etkých susedných nepriateľov" msgid "No melee penalty" msgstr "Bez penalizácie na blízko" msgid "Dragon" msgstr "Drak" msgid "Undead" msgstr "NemÅ•tvy" msgid "No enemy retaliation" msgstr "Nepriateľ nevracia úder" msgid "HP drain" msgstr "ÄŒerpanie zdravia" msgid "Cloud attack" msgstr "Útok mraÄna" msgid "Decreases enemy's morale by " msgstr "Znižuje morálku nepriateľa o " msgid "% chance to halve enemy" msgstr "% Å¡anca na rozpolenie nepriateľa" msgid "Soul Eater" msgstr "HltaÄ duší" msgid "Elemental" msgstr "Elementál" msgid "No Morale" msgstr "Žiadna morálka" msgid "200% damage from Fire spells" msgstr "Dvojité poÅ¡kodenie od kúziel ohňa" msgid "200% damage from Cold spells" msgstr "Dvojité poÅ¡kodenie od kúziel chladu" msgid "% damage from %{spell} spell" msgstr "% poÅ¡kodenia od kúzla %{spell}" msgid "% immunity to " msgstr "% imunity voÄi: " msgid "Lightning" msgstr "Blesk" msgid "% damage from " msgstr "% poÅ¡kodenie od: " msgid "The three Anduran artifacts magically combine into one." msgstr "Tri Anduranove artefakty sa Äarovne spojili do jedného." msgid "View Spells" msgstr "Zobraz kúzla" msgid "View %{name} Info" msgstr "Zobraz info: %{name}" msgid "Move %{name}" msgstr "Presuň %{name}" msgid "Cannot move the Spellbook" msgstr "Kniha kúziel sa nedá presunúť" msgid "This item can't be traded." msgstr "Tento predmet sa nedá zameniÅ¥." msgid "Invalid Artifact" msgstr "Neplatný artefakt" #, fuzzy msgid "The %{name} increases the hero's knowledge by %{count}." msgstr "%{name} zvyÅ¡uje tvoje vedomosti o %{count}." msgid "Ultimate Book of Knowledge" msgstr "Ultimátna kniha vedomostí" #, fuzzy msgid "The %{name} increases the hero's attack skill by %{count}." msgstr "%{name} zvyÅ¡uje tvoju útoÄnú zruÄnosÅ¥ o %{count}." msgid "Ultimate Sword of Dominion" msgstr "Ultimátny meÄ nadvlády" #, fuzzy msgid "The %{name} increases the hero's defense skill by %{count}." msgstr "%{name} zvyÅ¡uje tvoju obrannú zruÄnosÅ¥ o %{count}." msgid "Ultimate Cloak of Protection" msgstr "Ultimátny plášť ochrany" #, fuzzy msgid "The %{name} increases the hero's spell power by %{count}." msgstr "%{name} zvyÅ¡uje tvoju silu kúziel o %{count}." msgid "Ultimate Wand of Magic" msgstr "Ultimátny Äarovný prútik" #, fuzzy msgid "" "The %{name} increases the hero's attack and defense skills by %{count} each." msgstr "%{name} zvyÅ¡uje tvoju útoÄnú aj obrannú zruÄnosÅ¥ o %{count}." msgid "Ultimate Shield" msgstr "Ultimátny Å¡tít" #, fuzzy msgid "" "The %{name} increases the hero's spell power and knowledge by %{count} each." msgstr "%{name} zvyÅ¡uje tvoje vedomosti aj silu kúziel o %{count}." msgid "Ultimate Staff" msgstr "Ultimátna palica" #, fuzzy msgid "" "The %{name} increases each of the hero's basic skills by %{count} points." msgstr "%{name} zvyÅ¡uje vÅ¡etky tvoje zruÄnosti o %{count}." msgid "Ultimate Crown" msgstr "Ultimátna koruna" msgid "Golden Goose" msgstr "Zlatá hus" msgid "The %{name} brings in an income of %{count} gold per day." msgstr "%{name} zvyÅ¡uje príjem o %{count} zlata za deň." msgid "Arcane Necklace of Magic" msgstr "Tajomný magický náhrdelník" msgid "" "After rescuing a Sorceress from a cursed tomb, she rewards your heroism with " "an exquisite jeweled necklace." msgstr "" "Po záchrane Äarodejnice z prekliatej hrobky odmení tvoje hrdinstvo nádherným " "Å¡perkovým náhrdelníkom." msgid "Caster's Bracelet of Magic" msgstr "ZaklínaÄov náramok mágie" msgid "" "While searching through the rubble of a caved-in mine, you free a group of " "trapped Dwarves. Grateful, the leader gives you a golden bracelet." msgstr "" "Pri prehľadávaní trosiek zavalenej bane oslobodíš skupinu uväznených " "Trpaslíkov. Ich vodca ti s vÄakou daruje zlatý náramok." msgid "Mage's Ring of Power" msgstr "ÄŒarodejov prsteň moci" msgid "" "A cry of pain leads you to a Centaur, caught in a trap. Upon setting the " "creature free, he hands you a small pouch. Emptying the contents, you find a " "dazzling jeweled ring." msgstr "" "Výkrik bolesti Å¥a privedie ku Kentaurovi, ktorý sa chytil do pasce. KeÄ ho " "oslobodíš, podá ti malé vrecúško. VyprázdniÅ¡ obsah a nájdeÅ¡ oslnivý prsteň s " "drahokamami." msgid "Witch's Broach of Magic" msgstr "Bosorkina brošňa mágie" msgid "" "Alongside the remains of a burnt witch lies a beautiful broach, intricately " "designed. Approaching the corpse with caution, you add the broach to your " "inventory." msgstr "" "Vedľa pozostatkov spálenej bosorky leží krásna brošňa so zložitým dizajnom. " "Opatrne sa priblížiÅ¡ k mÅ•tvole a pridáš brošňu do svojho inventára." msgid "Medal of Valor" msgstr "Medaila za statoÄnosÅ¥" #, fuzzy msgid "The %{name} increases the morale of the hero's army by %{count}." msgstr "%{name} zvyÅ¡uje tvoju morálku o %{count}." msgid "" "Freeing a virtuous maiden from the clutches of an evil overlord, you are " "granted a Medal of Valor by the King's herald." msgstr "" "Za oslobodenie cnostnej panny z pazúrov zlého vladára vám kráľovský herold " "udelí Medailu za statoÄnosÅ¥." msgid "Medal of Courage" msgstr "Medaila za odvahu" msgid "" "After saving a young boy from a vicious pack of Wolves, you return him to " "his father's manor. The grateful nobleman awards you with a Medal of Courage." msgstr "" "Po záchrane malého chlapca pred zúrivou VlÄou svorkou ho vrátiÅ¡ na panstvo " "jeho otca. VÄaÄný šľachtic ti udelí Medailu za odvahu." msgid "Medal of Honor" msgstr "Medaila cti" msgid "" "After freeing a princess of a neighboring kingdom from the evil clutches of " "despicable slavers, she awards you with a Medal of Honor." msgstr "" "Po oslobodení princeznej susedného kráľovstva z pazúrov podlých otrokárov ti " "udelí Medailu cti." msgid "Medal of Distinction" msgstr "Medaila za zásluhy" msgid "" "Ridding the countryside of the hideous Minotaur who made a sport of eating " "noblemen's Knights, you are honored with the Medal of Distinction." msgstr "" "Zbavil/a si krajinu ohavného Minotaura, ktorý sa vyžíval v požieraní " "šľachtických rytierov a dostávaÅ¡ vyznamenanie Medaila za zásluhy." msgid "Fizbin of Misfortune" msgstr "Fizbin nešťastia" #, fuzzy msgid "" "The %{name} greatly decreases the morale of the hero's army by %{count}." msgstr "%{name} znižuje tvoju morálku o %{count}." msgid "" "You stumble upon a medal lying alongside the empty road. Adding the medal to " "your inventory, you become aware that you have acquired the undesirable " "Fizbin of Misfortune, greatly decreasing your army's morale." msgstr "" "Narazíš na medailu, ktorá leží pri prázdnej ceste. KeÄ medailu pridáš do " "svojho inventára, zistíš, že si získal/a nežiaduci Fizbin nešťastia, ktorý " "výrazne znižuje morálku tvojej armády." msgid "Thunder Mace of Dominion" msgstr "Hromový palcát nadvlády" msgid "" "During a sudden storm, a bolt of lightning strikes a tree, splitting it. " "Inside the tree you find a mysterious mace." msgstr "" "PoÄas náhlej búrky udrie blesk do stromu a rozÅ¡tiepi ho. Vo vnútri stromu " "nájdeÅ¡ záhadný palcát." msgid "Armored Gauntlets of Protection" msgstr "Obrnené ochranné rukavice" #, fuzzy msgid "The %{name} increase the hero's defense skill by %{count}." msgstr "%{name} zvyÅ¡uje tvoju obrannú zruÄnosÅ¥ o %{count}." msgid "" "You encounter the infamous Black Knight! After a grueling duel ending in a " "draw, the Knight, out of respect, offers you a pair of armored gauntlets." msgstr "" "StretávaÅ¡ sa s neslávne známym ÄŒiernym rytierom! Po vyÄerpávajúcom súboji, " "ktorý sa skonÄil remízou, ti rytier z úcty ponúkne pár obrnených rukavíc." msgid "Defender Helm of Protection" msgstr "Ochranná prilba obrancu" msgid "" "A glint of golden light catches your eye. Upon further investigation, you " "find a golden helm hidden under a bush." msgstr "" "Do oÄí ti padne odlesk zlatého svetla. Pri ÄalÅ¡om skúmaní nájdeÅ¡ zlatú " "prilbu ukrytú pod kríkom." msgid "Giant Flail of Dominion" msgstr "Obrovský cep nadvlády" msgid "" "A clumsy Giant has killed himself with his own flail. Knowing your superior " "skill with this weapon, you confidently remove the spectacular flail from " "the fallen Giant." msgstr "" "Nemotorný Obor sa zabil vlastným cepom. KeÄže vieÅ¡, že s touto zbraňou vieÅ¡ " "narábaÅ¥ lepÅ¡ie, s istotou odoberieÅ¡ padlému Obrovi ten veľkolepý cep." msgid "Ballista of Quickness" msgstr "Balista rýchlosti" #, fuzzy msgid "The %{name} gives the hero's catapult one extra shot per combat round." msgstr "%{name} dáva tvojmu katapultu jednu strelu navyÅ¡e každé kolo boja." msgid "" "Walking through the ruins of an ancient walled city, you find the instrument " "of the city's destruction, an elaborately crafted ballista." msgstr "" "Prechádzajúc ruinami starobylého opevneného mesta nájdeÅ¡ nástroj jeho " "zniÄenia, dômyselne vyrobenú balistu." msgid "Stealth Shield of Protection" msgstr "Ochranný Å¡tít prikrádania" msgid "" "A stone statue of a warrior holds a silver shield. As you remove the shield, " "the statue crumbles into dust." msgstr "" "Kamenná socha bojovníka drží strieborný Å¡tít. KeÄ Å¡tít odstrániÅ¡, socha sa " "rozpadne na prach." msgid "Dragon Sword of Dominion" msgstr "DraÄí meÄ nadvlády" msgid "" "As you are walking along a narrow path, a nearby bush suddenly bursts into " "flames. Before your eyes the flames become the image of a beautiful woman. " "She holds out a magnificent sword to you." msgstr "" "KeÄ kráÄaÅ¡ po úzkej cestiÄke, neÄaleký krík náhle vzplanie. Pred tvojimi " "oÄami sa z plameňov stane obraz krásnej ženy. Tá k tebe vystiera nádherný " "meÄ." msgid "Power Axe of Dominion" msgstr "Mocná sekera nadvlády" msgid "" "You see a silver axe embedded deeply in the ground. After several " "unsuccessful attempts by your army to remove the axe, you tightly grip the " "handle of the axe and effortlessly pull it free." msgstr "" "Vidíš striebornú sekeru zaseknutú hlboko do zeme. Po niekoľkých neúspeÅ¡ných " "pokusoch tvojho vojska odstrániÅ¥ sekeru pevne uchopíš rukoväť sekery a bez " "námahy ju vytiahneÅ¡." msgid "Divine Breastplate of Protection" msgstr "Božský náprsník ochrany" msgid "" "A gang of Rogues is sifting through the possessions of dead warriors. " "Scaring off the scavengers, you note the Rogues had overlooked a beautiful " "breastplate." msgstr "" "Banda Tulákov prehľadáva majetok mÅ•tvych bojovníkov. Odplašíš tých smetiarov " "a vÅ¡imneÅ¡ si, že Tuláci prehliadli krásny hrudný plát." msgid "Minor Scroll of Knowledge" msgstr "Menší zvitok vedomostí" msgid "" "Before you appears a levitating glass case with a scroll, perched upon a bed " "of crimson velvet. At your touch, the lid opens and the scroll floats into " "your awaiting hands." msgstr "" "Pred tebou sa objaví levitujúca sklenená skrinka so zvitkom, posadená na " "posteli z karmínového zamatu. Na tvoj dotyk sa veko otvorí a zvitok sa " "vznesie do tvojich Äakajúcich rúk." msgid "Major Scroll of Knowledge" msgstr "Hlavný zvitok vedomostí" msgid "" "Visiting a local wiseman, you explain the intent of your journey. He reaches " "into a sack and withdraws a yellowed scroll and hands it to you." msgstr "" "Pri návÅ¡teve miestneho mudrca mu vysvetlíš zámer svojej cesty. Siahne do " "vreca, vytiahne zažltnutý zvitok a podá ti ho." msgid "Superior Scroll of Knowledge" msgstr "Nadradený zvitok vedomostí" msgid "" "You come across the remains of an ancient Druid. Bones, yellowed with age, " "peer from the ragged folds of her robe. Searching the robe, you discover a " "scroll hidden in the folds." msgstr "" "Narazíš na pozostatky starobylej Druidky. Z ošúchaných záhybov jej rúcha " "vykúkajú vekom zožltnuté kosti. Pri prehľadávaní rúcha objavíš zvitok ukrytý " "v záhyboch." msgid "Foremost Scroll of Knowledge" msgstr "Najprednejší zvitok vedomostí" msgid "" "Mangled bones, yellowed with age, peer from the ragged folds of a dead " "Druid's robe. Searching the robe, you discover a scroll hidden within." msgstr "" "Z ošúchaných záhybov mÅ•tveho Druida vykúkajú vekom zožltnuté kosti. Pri " "prehľadávaní rúcha objavíš zvitok, ktorý je v ňom ukrytý." msgid "Endless Sack of Gold" msgstr "Bezodné vrecko zlata" #, fuzzy msgid "The %{name} provides the hero with %{count} gold per day." msgstr "%{name} ti poskytuje %{count} zlata za deň." msgid "" "A little leprechaun dances gleefully around a magic sack. Seeing you " "approach, he stops in mid-stride. The little man screams and stamps his foot " "ferociously, vanishing into thin air. Remembering the old leprechaun saying " "'Finders Keepers', you grab the sack and leave." msgstr "" "Malý leprikón veselo tancuje okolo Äarovného vreca. KeÄ Å¥a zbadá, zastaví sa " "uprostred kroku. Malý mužík skríkne, zúrivo dupne nohou a rozplynie sa vo " "vzduchu. SpomenieÅ¡ si na staré Å¡kriatkovské príslovie \"Kto si naÅ¡iel nech " "si nechá.\", chytíš vrece a odchádzaÅ¡." msgid "Endless Bag of Gold" msgstr "Bezodný meÅ¡ec zlata" msgid "" "A noblewoman, separated from her traveling companions, asks for your help. " "After escorting her home, she rewards you with a bag filled with gold." msgstr "" "ŠľachtiÄná, ktorá sa oddelila od svojich spoloÄníkov, Å¥a žiada o pomoc. Po " "tom, Äo ju odprevadíš domov, Å¥a odmení meÅ¡com plným zlata." msgid "Endless Purse of Gold" msgstr "Bezodné vrece zlata" msgid "" "In your travels, you find a leather purse filled with gold that once " "belonged to a great warrior king who had the ability to transform any " "inanimate object into gold." msgstr "" "Na svojich cestách nájdeÅ¡ kožený meÅ¡ec plný zlata, ktorý kedysi patril " "veľkému kráľovi bojovníkovi, ktorý mal schopnosÅ¥ premeniÅ¥ akýkoľvek neživý " "predmet na zlato." msgid "Nomad Boots of Mobility" msgstr "Nomádske topánky mobility" #, fuzzy msgid "The %{name} increase the hero's movement on land." msgstr "%{name} zvyÅ¡ujú tvoj pohyb po súši." msgid "" "A Nomad trader seeks protection from a tribe of Goblins. For your " "assistance, he gives you a finely crafted pair of boots made from the " "softest leather. Looking closely, you see fascinating ancient carvings " "engraved on the leather." msgstr "" "Nomádsky obchodník hľadá ochranu pred kmeňom Goblinov. Za tvoju pomoc ti dá " "precízne vyrobený pár topánok z tej najjemnejÅ¡ej kože. Pri pozornom pohľade " "vidíš v koži vyryté fascinujúce starobylé rytiny." msgid "Traveler's Boots of Mobility" msgstr "Cestovateľove topánky mobility" msgid "" "Discovering a pair of beautifully beaded boots made from the finest and " "softest leather, you thank the anonymous donor and add the boots to your " "inventory." msgstr "" "Objaviac pár nádherných topánok s koráľami vyrobených z najlepÅ¡ej a " "najjemnejÅ¡ej kože, poÄakujeÅ¡ anonymnému darcovi a pridáš topánky do svojho " "inventára." msgid "Lucky Rabbit's Foot" msgstr "ZajaÄia labka" #, fuzzy msgid "The %{name} increases the luck of the hero's army by %{count}." msgstr "%{name} zvyÅ¡uje tvoje šťastie v boji o %{count}." msgid "" "A traveling merchant offers you a rabbit's foot, made of gleaming silver " "fur, for safe passage. The merchant explains the charm will increase your " "luck in combat." msgstr "" "Cestujúci obchodník ti ponúkne králiÄiu nohu z lesklej striebornej kožuÅ¡iny, " "aby si bezpeÄne preÅ¡iel. Obchodník ti vysvetľuje, že tento amulet ti " "prinesie viac šťastia v boji." msgid "Golden Horseshoe" msgstr "Zlatá podkova" msgid "" "An ensnared Unicorn whinnies in fright. Murmuring soothing words, you set " "her free. Snorting and stamping her front hoof once, she gallops off. " "Looking down you see a golden horseshoe." msgstr "" "Chytený Jednorožec vystraÅ¡ene zakňuÄal. Å epkajúc upokojujúce slová ju " "vyslobodíš. Odfrkne si a raz dupne predným kopytom a odcvála. Pri pohľade " "dolu vidíš zlatú podkovu." msgid "Gambler's Lucky Coin" msgstr "Hazardérova šťastná minca" msgid "" "You have captured a mischievous imp who has been terrorizing the region. In " "exchange for his release, he rewards you with a magical coin." msgstr "" "Chytil/a si zákerného zloducha, ktorý terorizuje celý región. Výmenou za " "jeho prepustenie sa ti odvÄaÄí magickou mincou." msgid "Four-Leaf Clover" msgstr "Å tvorlístok" msgid "" "In the middle of a patch of dead and dry vegetation, to your surprise you " "find a healthy green four-leaf clover." msgstr "" "Uprostred mÅ•tveho a suchého porastu nájdeÅ¡ na svoje prekvapenie zdravý " "zelený Å tvorlístok." msgid "True Compass of Mobility" msgstr "Kompas mobility" #, fuzzy msgid "The %{name} increases the hero's movement on land and sea." msgstr "%{name} zlepÅ¡uje tvoj pohyb na súši aj na mori." msgid "" "An old man claiming to be an inventor asks you to try his latest invention. " "He then hands you a compass." msgstr "" "Starý muž, ktorý sa vyhlasuje za vynálezcu, Å¥a požiada, aby si vyskúšal jeho " "najnovší vynález. Potom ti podá kompas." msgid "Sailor's Astrolabe of Mobility" msgstr "Námorníkov astroláb mobility" #, fuzzy msgid "The %{name} increases the hero's movement on sea." msgstr "%{name} zvyÅ¡uje tvoj pohyb na mori." msgid "" "An old sea captain is being tortured by Ogres. You save him, and in return " "he rewards you with a wondrous instrument to measure the distance of a star." msgstr "" "Starého námorného kapitána muÄia Zlobri. ZachrániÅ¡ ho a on Å¥a na oplátku " "odmení úchvatným prístrojom na meranie vzdialenosti hviezd." msgid "Evil Eye" msgstr "Zlé oko" msgid "The %{name} reduces the casting cost of curse spells by half." msgstr "%{name} znižuje cenu za Äarovanie zaklínadiel o polovicu." msgid "" "While venturing into a decrepit hut you find the Skeleton of a long dead " "witch. Investigation of the remains reveals a glass eye rolling around " "inside an empty skull." msgstr "" "Pri vstupe do schátranej chatrÄe nájdeÅ¡ kostru dávno mÅ•tvej bosorky. Pri " "skúmaní pozostatkov objavíš sklenené oko, ktoré sa váľa v prázdnej lebke." msgid "Enchanted Hourglass" msgstr "ZaÄarované presýpacie hodiny" #, fuzzy msgid "" "The %{name} extends the duration of all the hero's spells by %{count} turns." msgstr "%{name} predlžuje dobu trvania vÅ¡etkých tvojich kúziel o %{count} kôl." msgid "" "A surprise turn in the landscape finds you in the midst of a grisly scene: " "Vultures picking at the aftermath of a terrible battle. Your cursory search " "of the remains turns up an enchanted hourglass." msgstr "" "Pri neÄakanej zákrute v krajine sa ocitneÅ¡ uprostred hrôzostraÅ¡nej scény: " "Supy si pochutnávajú na následkoch straÅ¡nej bitky. Pri zbežnom prehľadávaní " "pozostatkov objavíš ZaÄarované presýpacie hodiny." msgid "Gold Watch" msgstr "Zlaté hodinky" #, fuzzy msgid "The %{name} doubles the effectiveness of the hero's hypnotize spells." msgstr "%{name} zdvojnásobujú efektivitu tvojich kúziel hypnózy." msgid "" "In reward for helping his cart out of a ditch, a traveling potion salesman " "gives you a \"magic\" gold watch. Unbeknownst to him, the watch really is " "magical." msgstr "" "Obchodník s elixírmi ti za pomoc pri vyÅ¥ahovaní vozíka z priekopy dáva " "\"Äarovné\" zlaté hodinky. Nevie vÅ¡ak, že hodinky sú naozaj Äarovné." msgid "Skullcap" msgstr "ÄŒiapka" msgid "The %{name} halves the casting cost of all mind influencing spells." msgstr "" "%{name} znižuje cenu za Äarovanie kúziel ovplyvňujúcich myseľ o polovicu." msgid "" "A brief stop at an improbable rural inn yields an exchange of money, tales, " "and accidentally, luggage. You find a magical skullcap in your new backpack." msgstr "" "Krátka zastávka v nepredvídateľnom vidieckom hostinci prináša výmenu peňazí, " "príbehov a náhodou aj batožiny. Vo svojom novom batohu nájdeÅ¡ Äarovnú Äiapku." msgid "Ice Cloak" msgstr "Ľadový plášť" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from cold spells." msgstr "" "%{name} znižuje poÅ¡kodenie, ktoré tvoje jednotky utŕžia od kúziel chladu." msgid "" "Responding to the panicked cries of a damsel in distress, you discover a " "young woman fleeing from a hungry bear. You slay the beast in the nick of " "time, and the grateful Sorceress weaves a magic cloak from the bear's hide." msgstr "" "PoÄujeÅ¡ panické výkriky dievÄiny v núdzi a objavíš mladú ženu, ktorá uteká " "pred hladným medveÄom. Zviera v okamihu zabijeÅ¡ a vÄaÄná ZaklínaÄka ti z " "medvedej kože utká magický plášť." msgid "Fire Cloak" msgstr "Ohnivý plášť" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from fire spells." msgstr "" "%{name} znižuje poÅ¡kodenie, ktoré tvoje jednotky utŕžia od kúziel ohňa." msgid "" "You've come upon a fight between a Necromancer and a Paladin. The " "Necromancer blasts the Paladin with a fire bolt, bringing him to his knees. " "Acting quickly, you slay the evil one before the final blow. The grateful " "Paladin gives you the fire cloak that saved him." msgstr "" "Natrafíš na boj medzi Nekromantom a Paladinom. Nekromant zasiahne Paladina " "ohnivým bleskom a zrazí ho na kolená. Konáš rýchlo a pred posledným úderom " "zlosyna zabijeÅ¡. VÄaÄný paladin ti dá ohnivý plášť, ktorý ho zachránil." msgid "Lightning Helm" msgstr "Helma blesku" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from lightning " "spells." msgstr "" "%{name} znižuje poÅ¡kodenie, ktoré tvoje jednotky utŕžia od kúziel blesku." msgid "" "A traveling tinker in need of supplies offers you a helm with a thunderbolt " "design on its top in exchange for food and water. Curious, you accept, and " "later find out that the helm is magical." msgstr "" "Potulný drotár, ktorý potrebuje zásoby, ti výmenou za jedlo a vodu ponúkne " "prilbu s motívom blesku na vrchu. Zo zvedavosti ju prijímaÅ¡ a neskôr zistíš, " "že prilba je magická." msgid "Evercold Icicle" msgstr "VeÄne chladný cencúľ" #, fuzzy msgid "" "The %{name} causes the hero's cold spells to do %{count} percent more damage " "to enemy troops." msgstr "" "%{name} zapríÄiní, že tvoje kúzla chladu spôsobia o %{count} percent viac " "poÅ¡kodenia nepriateľským jednotkám." msgid "" "An icicle withstanding the full heat of the noonday sun attracts your " "attention. Intrigued, you break it off, and find that it does not melt in " "your hand." msgstr "" "Tvoju pozornosÅ¥ upúta cencúľ, ktorý odoláva plnému žiaru poludňajÅ¡ieho " "slnka. Zaujato ho odlomíš a zistíš, že sa ti v ruke neroztopí." msgid "Everhot Lava Rock" msgstr "VeÄne horúci lávový kameň" #, fuzzy msgid "" "The %{name} causes the hero's fire spells to do %{count} percent more damage " "to enemy troops." msgstr "" "%{name} zapríÄiní, že tvoje kúzla ohňa spôsobia o %{count} percent viac " "poÅ¡kodenia nepriateľským jednotkám." msgid "" "Your wanderings bring you into contact with a tribe of ape-like beings using " "a magical lava rock that never cools to light their fires. You take pity on " "them and teach them to make fire with sticks. Believing you to be a god, the " "apes give you their rock." msgstr "" "Tvoje putovanie Å¥a privedie do kontaktu s kmeňom ľudoopov, ktorí používajú " "magickú lávovú horninu, ktorá nikdy nevychladne, na zapaľovanie ohňa. " "ZľutujeÅ¡ sa nad nimi a nauÄíš ich rozkladaÅ¥ oheň pomocou palíc. Opice Å¥a " "považujú za boha a dajú ti svoj kameň." msgid "Lightning Rod" msgstr "Bleskozvod" #, fuzzy msgid "" "The %{name} causes the hero's lightning spells to do %{count} percent more " "damage to enemy troops." msgstr "" "%{name} zapríÄiní, že tvoje kúzla blesku spôsobia o %{count} percent viac " "poÅ¡kodenia nepriateľským jednotkám." msgid "" "While waiting out a storm, a lighting bolt strikes a nearby cottage's " "lightning rod, which melts and falls to the ground. The tip of the rod, " "however, survives intact and makes your hair stand on end when you touch it. " "Hmm..." msgstr "" "PoÄas Äakania na koniec búrky udrie blesk do bleskozvodu neÄalekej chaty, " "ten sa roztopí a spadne na zem. Å piÄka bleskozvodu vÅ¡ak prežije neporuÅ¡ená a " "keÄ sa jej dotýkaÅ¡, vstávajú ti vlasy dupkom. Hmm..." msgid "Snake-Ring" msgstr "Hadí prsteň" #, fuzzy msgid "The %{name} halves the casting cost of all of the hero's bless spells." msgstr "%{name} znižuje cenu za Äarovanie kúziel žehnania o polovicu." msgid "" "You've found an oddly shaped ring on the finger of a long dead traveler. The " "ring looks like a snake biting its own tail." msgstr "" "Na prste dávno mÅ•tveho cestovateľa nájdeÅ¡ prsteň zvláštneho tvaru. Prsteň " "vyzerá ako had, ktorý si hryzie vlastný chvost." msgid "Ankh" msgstr "Anch" #, fuzzy msgid "" "The %{name} doubles the effectiveness of all of the hero's resurrect and " "animate spells." msgstr "" "%{name} zdvojnásobujú efektivitu tvojich kúziel vzkriesenia a oživenia." msgid "" "A fierce windstorm reveals the entrance to a buried tomb. Your investigation " "reveals that the tomb has already been looted, but the thieves overlooked an " "ankh on a silver chain in the dark." msgstr "" "Prudká víchrica odhalí vchod do zasypanej hrobky. Tvoje vyÅ¡etrovanie odhalí, " "že hrobka už bola vyrabovaná, ale zlodeji v tme prehliadli anch na " "striebornej retiazke." msgid "Book of Elements" msgstr "Kniha elementov" #, fuzzy msgid "" "The %{name} doubles the effectiveness of all of the hero's summoning spells." msgstr "%{name} zdvojnásobujú efektivitu tvojich kúziel vyvolávania." msgid "" "You come across a conjurer who begs to accompany you and your army awhile " "for safety. You agree, and he offers as payment a copy of the book of the " "elements." msgstr "" "Narazíš na zaklínaÄa, ktorý Å¥a prosí, aby teba a tvoju armádu mohol, z " "dôvodu bezpeÄnosti, na nejaký Äas sprevádzaÅ¥. Súhlasíš a on ti ako odmenu " "ponúkne kópiu Knihy živlov." msgid "Elemental Ring" msgstr "Prsteň elementov" msgid "The %{name} halves the casting cost of all summoning spells." msgstr "%{name} znižuje cenu za Äarovanie kúziel vyvolávania o polovicu." msgid "" "While pausing to rest, you notice a bobcat climbing a short tree to get at a " "crow's nest. On impulse, you climb the tree yourself and scare off the cat. " "When you look in the nest, you find a collection of shiny stones and a ring." msgstr "" "PoÄas odpoÄinku si vÅ¡imneÅ¡ rysa, ktorý vylieza na krátky strom, aby sa " "dostal k hniezdu vrany. Impulzívne vylezieÅ¡ na strom a toho rysa odplašíš. " "KeÄ sa pozrieÅ¡ do hniezda, nájdeÅ¡ zbierku lesklých kameňov a prsteň." msgid "Holy Pendant" msgstr "Svätý prívesok" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to curse spells." msgstr "%{name} robí vÅ¡etky tvoje jednotky imúnnymi voÄi kliatbam." msgid "" "In your wanderings you come across a hermit living in a small, tidy hut. " "Impressed with your mission, he takes time out from his meditations to bless " "and give you a charm against curses." msgstr "" "Na svojich potulkách narazíš na pustovníka, ktorý žije v malej upratanej " "chatrÄi. Zapôsobí naňho tvoje poslanie, a tak si nájde Äas na meditáciu, aby " "ti požehnal a dal amulet proti kliatbám." msgid "Pendant of Free Will" msgstr "Prívesok slobodnej vôle" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to hypnotize spells." msgstr "%{name} robí vÅ¡etky tvoje jednotky imúnnymi voÄi kúzlam hypnózy." msgid "" "Responding to cries for help, you find river Sprites making a sport of " "dunking an old man. Feeling vengeful, you rescue the man and drag a Sprite " "onto dry land for awhile. The Sprite, uncomfortable in the air, gives you a " "magic pendant to let him go." msgstr "" "PoÄujeÅ¡ volanie o pomoc, a náchádzaÅ¡ rieÄne Víly, ako sa zabávajú namáÄaním " "starého muža. CítiÅ¡ sa pomstychtivo, zachrániÅ¡ muža a na chvíľu vytiahneÅ¡ " "Vílu na suchú zem. Víla, cítiaca sa vo vzduchu nepohodlne, ti dá Äarovný " "prívesok, aby si ju pustil." msgid "Pendant of Life" msgstr "Prívesok života" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to death spells." msgstr "%{name} robí vÅ¡etky tvoje jednotky imúnnymi voÄi kúzlam smrti." msgid "" "A brief roadside encounter with a small caravan and a game of knucklebones " "wins a magic pendant. Its former owner says that it protects from " "Necromancers' death spells." msgstr "" "Krátke stretnutie s malou karavánou pri ceste a astragaly ti vyhrávajú " "Äarovný prívesok. Jeho bývalý majiteľ tvrdí, že chráni pred kúzlami smrti " "nekromantov." msgid "Serenity Pendant" msgstr "Prívesok pokoja" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to berserk spells." msgstr "%{name} robí vÅ¡etky tvoje jednotky imúnnymi voÄi kúzlam Å¡ialenstva." msgid "" "The sounds of combat draw you to the scene of a fight between an old " "Barbarian and an eight-headed Hydra. Your timely intervention swings the " "battle in favor of the man, and he rewards you with a pendant he used to use " "to calm his mind for battle." msgstr "" "Zvuky boja Å¥a prilákajú na scénu boja medzi starým barbarom a osemhlavou " "hydrou. Tvoj vÄasný zásah zvráti bitku v prospech muža a ten sa ti odvÄaÄí " "príveskom, ktorý používal na upokojenie mysle v boji." msgid "Seeing-eye Pendant" msgstr "Prívesok vidiaceho oka" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to blindness spells." msgstr "%{name} robí vÅ¡etky tvoje jednotky imúnnymi voÄi kúzlam slepoty." msgid "" "You come upon a very old woman, long blind from cataracts and dying alone. " "You tend to her final needs and promise a proper burial. Grateful, she gives " "you a magic pendant emblazoned with a stylized eye. It lets you see with " "your eyes closed." msgstr "" "Narazíš na veľmi starú ženu, ktorá už dávno oslepla na Å¡edý zákal a osamelo " "zomiera. Postaráš sa o jej posledné potreby a sľúbiÅ¡ jej riadny pohreb. " "VÄaÄne ti dá Äarovný prívesok so Å¡tylizovaným okom. Umožní ti vidieÅ¥ so " "zatvorenými oÄami." msgid "Kinetic Pendant" msgstr "Kinetický prívesok" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to paralyze spells." msgstr "%{name} robí vÅ¡etky tvoje jednotky imúnnymi voÄi kúzlam paralýzy." msgid "" "You come across a golem wearing a glowing pendant and blocking your way. " "Acting on a hunch, you cut the pendant from its neck. Deprived of its power " "source, the golem breaks down, leaving you with the magical pendant." msgstr "" "Narazíš na golema so žiarivým príveskom, ktorý ti blokuje cestu. Na základe " "predtuchy mu prívesok z krku odrežeÅ¡. Golem sa rozpadne, keÄ príde o zdroj " "energie, a magický prívesok zostane tebe." msgid "Pendant of Death" msgstr "Prívesok smrti" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to holy spells." msgstr "%{name} robí vÅ¡etky tvoje jednotky imúnnymi voÄi svätým kúzlam." msgid "" "A quick and deadly battle with a Necromancer wins you his magical pendant. " "Later, a Wizard tells you that the pendant protects undead under your " "control from holy word spells." msgstr "" "Rýchly a smrtiaci súboj s Nekromantom ti prinesie jeho magický prívesok. " "Neskôr ti Mág povie, že prívesok chráni nemÅ•tvych pod tvojou kontrolou pred " "kúzlami svätého slova." msgid "Wand of Negation" msgstr "NegaÄný prútik" #, fuzzy msgid "" "The %{name} makes all of the hero's troops immune to dispel magic spells." msgstr "%{name} robí vÅ¡etky tvoje jednotky imúnnymi voÄi kúzlam smrti." msgid "" "You meet an old Wizard friend of yours traveling in the opposite direction. " "He presents you with a gift: A wand that prevents the use of the dispel " "magic spell on your allies." msgstr "" "StretneÅ¡ svojho starého priateľa-Äarodejníka, ktorý cestuje opaÄným smerom. " "Dá ti darÄek: Prútik, ktorý zabráni použitiu kúzla rozptýlenia mágie na " "tvojich spojencov." msgid "Golden Bow" msgstr "Zlatý luk" #, fuzzy msgid "" "The %{name} eliminates the %{count} percent penalty for the hero's troops " "shooting past obstacles (e.g. castle walls)." msgstr "" "%{name} odstraňuje %{count} percentnú penaltu tvojich jednotiek za streľbu " "spoza prekážok." msgid "" "A chance meeting with a famous Archer finds you in a game of knucklebones " "pitting his bow against your horse. You win." msgstr "" "Pri náhodnom stretnutí so slávnym lukostrelcom si zahráte astragaly o jeho " "luk proti tvojmu koňovi. Vyhráš." msgid "Telescope" msgstr "Teleskop" #, fuzzy msgid "" "The %{name} increases the amount of terrain the hero reveals when " "adventuring by %{count} extra square." msgstr "" "%{name} zvyÅ¡uje množstvo terénu, ktorý tvoj hrdina odhalí poÄas prechodu o " "%{count} polí navyÅ¡e." msgid "" "A merchant from far away lands trades you a new invention of his people for " "traveling supplies. It makes distant objects appear closer, and he calls " "it...\n" "\n" "a telescope." msgstr "" "Obchodník z Äalekých krajín ti vymení nový vynález svojho ľudu za cestovné " "zásoby. VÄaka nemu sa vzdialené predmety zdajú byÅ¥ bližšie a on ho " "nazýva...\n" "\n" "teleskop." msgid "Statesman's Quill" msgstr "Å tátnikovo brko" #, fuzzy msgid "" "The %{name} reduces the cost of surrender to %{count} percent of the total " "cost of troops the hero has in their army." msgstr "" "%{name} znižuje cenu vzdania sa o %{count} percent celkovej ceny jednotiek, " "ktoré máš v armáde." msgid "" "You pause to help a diplomat with a broken axle fix his problem. In " "gratitude, he gives you a writing quill with magical properties which he " "says will \"help people see things your way\"." msgstr "" "Zastavíš sa na pomoc diplomatovi so zlomenou nápravou, a vyrieÅ¡iÅ¡ jeho " "problém. Z vÄaky ti dá brko na písanie s magickými vlastnosÅ¥ami, ktoré podľa " "neho \"pomôže ľuÄom vidieÅ¥ veci tak, ako ich vidíš ty\"." msgid "Wizard's Hat" msgstr "ÄŒarodejníkov klobúk" #, fuzzy msgid "" "The %{name} increases the duration of the hero's spells by %{count} turns." msgstr "%{name} predlžuje dobu trvania tvojich kúziel o %{count} kôl." msgid "" "You see a Wizard fleeing from a Griffin and riding like the wind. The Wizard " "opens a portal and rides through, getting his hat knocked off by the edge of " "the gate. The Griffin follows; the gate closes. You pick the hat up, dust it " "off, and put it on." msgstr "" "Vidíš Mága, ktorý uteká pred Gryfom a uháňa ako vietor. Mág otvorí portál a " "prejde ním, priÄom mu okraj toho portálu zrazí klobúk z hlavy. Gryf ho " "nasleduje; portál sa zatvorí. ZdvihneÅ¡ klobúk, oprášiÅ¡ ho a nasadíš si ho." msgid "Power Ring" msgstr "Prsteň moci" #, fuzzy msgid "The %{name} returns %{count} extra spell points per day to the hero." msgstr "%{name} vráti %{count} bodov kúziel za deň tvojmu hrdinovi." msgid "" "You find a small tree that closely resembles the great Warlock Carnauth with " "a ring around one of its twigs. Scraps of clothing and rotting leather lead " "you to suspect that it IS Carnauth, transformed. Since you can't help him, " "you take the magic ring." msgstr "" "NájdeÅ¡ malý strom, ktorý sa veľmi podobá veľkému ÄŒernokňažníkovi Carnauthovi " "s prsteňom okolo jednej z jeho vetviÄiek. Úlomky obleÄenia a hnijúca koža Å¥a " "vedú k podozreniu, že to JE premenený Carnauth. KeÄže mu nemôžeÅ¡ pomôcÅ¥, " "vezmeÅ¡ si Äarovný prsteň." msgid "Ammo Cart" msgstr "MuniÄný vozík" #, fuzzy msgid "" "The %{name} provides endless ammunition for all of the hero's troops that " "shoot." msgstr "" "%{name} poskytuje nekoneÄnú muníciu vÅ¡etkým tvojim jednotkám, ktoré " "strieľajú." msgid "" "An ammunition cart in the middle of an old battlefield catches your eye. " "Inspection shows it to be in good working order, so you take it along." msgstr "" "Upúta Å¥a vozík s muníciou uprostred starého bojiska. Kontrola ukáže, že je v " "dobrom stave, a tak ho beriete so sebou." msgid "Tax Lien" msgstr "Daňové záložné právo" #, fuzzy msgid "The %{name} costs the hero %{count} gold pieces per day." msgstr "%{name} Å¥a stojí %{count} zlata za deň." msgid "" "Your big spending habits have earned you a massive tax bill that you can't " "hope to pay. The tax man takes pity and agrees to only take 250 gold a day " "from your account for life. Check here if you want one dollar to go to the " "presidential campaign election fund." msgstr "" "Kvôli tvojim veľkým zvykom v oblasti míňania ti vznikol obrovský daňový " "úÄet, ktorý nedokážeÅ¡ zaplatiÅ¥. Daňový úradník sa zľutuje a súhlasí s tým, " "že si z tvojho úÄtu bude doživotne braÅ¥ len 250 zlatých denne. Tu oznaÄ, ak " "chceÅ¡, aby jeden dolár iÅ¡iel na volebný fond prezidentskej kampane." msgid "Hideous Mask" msgstr "Ohavná maska" #, fuzzy msgid "The %{name} prevents all 'wandering' armies from joining the hero." msgstr "" "%{name} zabraňuje vÅ¡etkým putovným jednotkám, aby sa pridali k tvojmu " "hrdinovi." msgid "" "Your looting of the grave of Sinfilas Gardolad, the famous shapeshifting " "Warlock, unearths his fabled mask. Trembling, you put it on and it twists " "your visage into an awful grimace! Oh no! It's actually the hideous mask of " "Gromluck Greene, and you are stuck with it." msgstr "" "Pri vykrádaní hrobu Sinfilasa Gardolada, slávneho ÄŒernokňažníka meniaceho " "podobu, objavíš jeho legendárnu masku. Chvejúc sa, si ju nasadíš a ona ti " "skrúti tvár do straÅ¡nej grimasy! Ale nie! V skutoÄnosti je to ohavná maska " "Gromlucka Greena a ty si s ňou v pasci." msgid "Endless Pouch of Sulfur" msgstr "Bezodný meÅ¡ec síry" msgid "The %{name} provides %{count} unit of sulfur per day." msgstr "%{name} poskytuje %{count} jednotky síry za deň." msgid "" "You visit an alchemist who, upon seeing your army, is swayed by the " "righteousness of your cause. The newly loyal subject gives you his endless " "pouch of sulfur to help with the war effort." msgstr "" "NavÅ¡tíviÅ¡ alchymistu, ktorý sa pri pohľade na tvoju armádu presvedÄí o " "spravodlivosti tvojej veci. Nový lojálny poddaný ti dá svoj bezodný meÅ¡ec " "síry, aby ti pomohol s vojnovým úsilím." msgid "Endless Vial of Mercury" msgstr "NekoneÄná fľaÅ¡tiÄka s ortuÅ¥ou" msgid "The %{name} provides %{count} unit of mercury per day." msgstr "%{name} poskytuje %{count} jednotky ortuti za deň." msgid "" "A brief stop at a hastily abandoned Wizard's tower turns up a magical vial " "of mercury that always has a little left on the bottom. Recognizing a " "treasure when you see one, you cap it and slip it in your pocket." msgstr "" "Pri krátkej zastávke v narýchlo opustenej Magickej veži sa nájde Äarovná " "fľaÅ¡tiÄka s ortuÅ¥ou, v ktorej vždy zostane trochu na dne. HneÄ ako rozpoznáš " "poklad, uzavrieÅ¡ ju a strÄíš si ju do vrecka." msgid "Endless Pouch of Gems" msgstr "Bezodný meÅ¡ec drahokamov" msgid "The %{name} provides %{count} unit of gems per day." msgstr "%{name} poskytuje %{count} jednotky drahokamov za deň." msgid "" "A short rainstorm brings forth a rainbow...and you can see the end of it. " "Riding quickly, you seize the pot of gold you find there. The leprechaun who " "owns it, unable to stop you from taking it, offers an endless pouch of gems " "for the return of his gold. You accept." msgstr "" "Krátky dÃ¡Å¾Ä prinesie dúhu... a ty môžeÅ¡ vidieÅ¥ jej koniec. Na koni sa rýchlo " "zmocníš hrnca zlata, ktorý tam nájdeÅ¡. Leprikón, ktorému patrí, ti nedokáže " "zabrániÅ¥, aby si si ho vzal, a za vrátenie zlata ti ponúka nekoneÄný meÅ¡ec " "drahokamov. Ty súhlasíš." msgid "Endless Cord of Wood" msgstr "NekoneÄná fúra dreva" msgid "The %{name} provides %{count} unit of wood per day." msgstr "%{name} poskytuje %{count} jednotky dreva za deň." msgid "" "Pausing to rest and light a cook fire, you pull wood out of a nearby pile of " "dead wood. As you keep pulling wood from the pile, you notice that it " "doesn't shrink. You realize to your delight that the wood is enchanted, so " "you take it along." msgstr "" "PoÄas odpoÄinku a zakladania ohňa na varenie vytiahneÅ¡ drevo z neÄalekej " "hromady suchého dreva. Ako z hromady vyÅ¥ahujeÅ¡ drevo, vÅ¡imneÅ¡ si, že sa " "nezmenÅ¡uje. Na vlastnú radosÅ¥ si uvedomíš, že drevo je zaÄarované, a tak si " "ho vezmeÅ¡ so sebou." msgid "Endless Cart of Ore" msgstr "NekoneÄný vozík s rudou" msgid "The %{name} provides %{count} unit of ore per day." msgstr "%{name} poskytuje %{count} jednotky rudy za deň." msgid "" "You've found a Goblin weapon smithy making weapons for use against humans. " "With a tremendous yell you and your army descend upon their camp and drive " "them away. A search finds a magic ore cart that never runs out of iron." msgstr "" "NájdeÅ¡ Gobliniu kováÄsku dielňu, kdea sa vyrábajú zbrane na použitie proti " "ľuÄom. S mohutným krikom sa so svojou armádou spustíte na ich tábor a " "zaženiete ich preÄ. Pri pátraní nájdeÅ¡ Äarovný vozík na rudu, v ktorom nikdy " "nedochádza železo." msgid "Endless Pouch of Crystal" msgstr "MeÅ¡ec nekoneÄného kryÅ¡tálu" msgid "The %{name} provides %{count} unit of crystal per day." msgstr "%{name} poskytuje %{count} jednotky kryÅ¡tálu za deň." msgid "" "Taking shelter from a storm in a small cave, you notice a small patch of " "crystal in one corner. Curious, you break a piece off and notice that the " "original crystal grows the lost piece back. You decide to stuff the entire " "patch into a pouch and take it with you." msgstr "" "KeÄ sa ukrývaÅ¡ pred búrkou v malej jaskyni, v jednom rohu si vÅ¡imneÅ¡ malý " "kúsok kryÅ¡tálu. Zo zvedavosti kúsok odlomíš a vÅ¡imneÅ¡ si, že pôvodný kryÅ¡tál " "dorastá späť. RozhodneÅ¡ sa celý kúsok vložiÅ¥ do vrecka a vziaÅ¥ si ho so " "sebou." msgid "Spiked Helm" msgstr "Ostnatá prilba" msgid "" "Your army is ambushed by a small tribe of wild (and none too bright) Orcs. " "You fend them off easily and the survivors flee in all directions. One of " "the Orcs was wearing a polished spiked helm. Figuring it will make a good " "souvenir, you take it." msgstr "" "Tvoju armádu prepadne malý kmeň divokých (a nie príliÅ¡ bystrých) Orkov. " "Ľahko ich odrazíte a tí, Äo prežili, utekajú na vÅ¡etky strany. Jeden z orkov " "mal na hlave vyleÅ¡tenú prilbu s ostňami. UsudzujeÅ¡, že to bude dobrý " "suvenír, a berieÅ¡ si ju." msgid "Spiked Shield" msgstr "Ostnatý Å¡tít" msgid "" "You come upon a bridge spanning a dry gully. Before you can cross, a Troll " "steps out from under the bridge and demands payment before it will permit " "you to pass. You refuse, and the Troll charges, forcing you to slay it. You " "take its spiked shield as a trophy." msgstr "" "Natrafíš na most, ktorý preklenuje suchú roklinu. Skôr ako sa ti podarí " "prejsÅ¥, spod mosta vystúpi Trol a žiada platbu, kým ti dovolí prejsÅ¥. KeÄ " "odmietneÅ¡, Trol na teba zaútoÄí a prinúti Å¥a ho zabiÅ¥. Jeho Å¡tít s ostňami " "si zoberieÅ¡ ako trofej." msgid "White Pearl" msgstr "Biela perla" msgid "" "A walk across a dry saltwater lake bed yields an unlikely prize: A white " "pearl amidst shattered shells and debris." msgstr "" "Prechádzka po vyschnutom dne slaného jazera prináša nepravdepodobnú cenu: " "Medzi rozbitými mušľami a troskami objavíš bielu perlu." msgid "Black Pearl" msgstr "ÄŒierna perla" msgid "" "Rumors of a Griffin of unusual size preying upon the countryside lead you to " "its cave lair. A quick, brutal fight dispatches the beast, and a search of " "its foul nest turns up a huge black pearl." msgstr "" "Zvesti o Gryfovi nezvyÄajnej veľkosti, ktorý loví na vidieku, Å¥a zavedú do " "jeho jaskynného brlohu. V rýchlom a brutálnom boji sa ti podarí Å¡elmu " "zlikvidovaÅ¥ a pri prehľadávaní jej ohavného hniezda nájdeÅ¡ obrovskú Äiernu " "perlu." msgid "Magic Book" msgstr "Kniha kúziel" #, fuzzy msgid "The %{name} enables the hero to cast spells." msgstr "%{name} ti umožňuje zosielaÅ¥ kúzla." msgid "Dummy 1" msgstr "Atrapa 1" msgid "The reserved artifact." msgstr "Vyhradený artefakt." msgid "Dummy 2" msgstr "Atrapa 2" msgid "Dummy 3" msgstr "Atrapa 3" msgid "Dummy 4" msgstr "Atrapa 4" msgid "Spell Scroll" msgstr "Zvitok s kúzlom" #, fuzzy msgid "" "This %{name} gives the hero the ability to cast the %{spell} spell if the " "hero has a Magic Book." msgstr "" "Tento %{name} dáva tvojmu hrdinovi schopnosÅ¥ zoslaÅ¥ kúzlo %{spell}, ak má " "tvoj hrdina Magickú knihu." msgid "" "You find an elaborate container which houses an old vellum scroll. The runes " "on the container are very old, and the artistry with which it was put " "together is stunning. As you pull the scroll out, you feel imbued with " "magical power." msgstr "" "NájdeÅ¡ zložitú nádobu, v ktorej je starý pergamenový zvitok. Runy na nádobe " "sú veľmi staré a majstrovstvo, s akým bola nádoba zostavená, je ohromujúce. " "KeÄ zvitok vytiahneÅ¡, cítiÅ¡, že do teba preniká magická sila." msgid "Arm of the Martyr" msgstr "Paža muÄeníka" #, fuzzy msgid "" "The %{name} increases the hero's spell power by %{count} but adds the undead " "morale penalty." msgstr "" "%{name} zvyÅ¡uje tvoju Silu kúziel o %{count}, ale pridáva postih morálky za " "nemÅ•tvych v skupine." msgid "" "One of the less intelligent members of your party picks up an arm off of the " "ground. Despite its missing a body, it is still moving. Your troops find the " "dismembered arm repulsive, but you cannot bring yourself to drop it: it " "seems to hold some sort of magical power that influences your decision " "making." msgstr "" "Jeden z menej inteligentných Älenov tvojej skupiny zdvihne zo zeme ruku. " "Napriek tomu, že jej chýba telo, stále sa hýbe. Tvoji vojaci považujú " "rozÅ¡tvrtenú ruku za odpudzujúcu, ale ty sa nemôžeÅ¡ prinútiÅ¥ ju pustiÅ¥: zdá " "sa, že má akúsi magickú moc, ktorá ovplyvňuje tvoje rozhodovanie." msgid "Breastplate of Anduran" msgstr "Anduranove prsné brnenie" #, fuzzy msgid "The %{name} increases the hero's defense by %{count}." msgstr "%{name} zvyÅ¡uje tvoju obranu o %{count}." msgid "" "You come upon a sign. It reads: \"Here lies the body of Anduran. Bow and " "swear fealty, and you shall be rewarded.\" You decide to do as it says. As " "you stand up, you feel a coldness against your skin. Looking down, you find " "that you are suddenly wearing a gleaming, ornate breastplate." msgstr "" "Narazíš na znaÄku. Je na nej napísané: \"Tu leží Anduranovo telo. Pokloň sa " "a prisahaj vernosÅ¥ a budeÅ¡ odmenený.\" RozhodneÅ¡ sa urobiÅ¥ tak. KeÄ sa " "postavíš, pocítiÅ¡ na koži chlad. KeÄ sa pozrieÅ¡ dolu, zistíš, že zrazu máš " "na sebe lesklé, zdobené prsné brnenie." msgid "Broach of Shielding" msgstr "Brošňa ochrany" msgid "" "The %{name} provides %{count} percent protection from Armageddon and " "Elemental Storm, but decreases spell power by 2." msgstr "" "%{name} poskytuje %{count} percent ochrany pred Armagedonom a Búrkou živlov, " "ale znižuje Silu kúzliel o 2." msgid "" "A kindly Sorceress thinks that your army's defenses could use a magical " "boost. She offers to enchant the Broach that you wear on your cloak, and you " "accept." msgstr "" "Láskavá ZaklínaÄka si myslí, že obrana tvojej armády by potrebovala magickú " "podporu. Ponúka ti, že ti oÄarí brošňu, ktorú nosíš na plášti, a ty súhlasíš." msgid "Battle Garb of Anduran" msgstr "Anduranov bojový odev" #, fuzzy msgid "" "The %{name} combines the powers of the three Anduran artifacts. It provides " "maximum luck and morale for the hero's troops and gives the hero the Town " "Portal spell." msgstr "" "%{name} kombinuje sily troch Anduranových artefaktov. Poskytuje maximálne " "šťastie a morálku pre tvoje vojsko a dáva ti kúzlo Mestský portál." msgid "" "Out of pity for a poor peasant, you purchase a chest of old junk they are " "hawking for too much gold. Later, as you search through it, you find it " "contains the 3 pieces of the legendary battle garb of Anduran!" msgstr "" "Zo súcitu s chudobným sedliakom kúpiÅ¡ truhlicu starého haraburdia, ktoré " "predáva za príliÅ¡ veľa zlata. Neskôr, keÄ ju prehľadáš, zistíš, že obsahuje " "3 kusy legendárneho Anduranovho bojového odevu!" msgid "Crystal Ball" msgstr "KriÅ¡táľová guľa" #, fuzzy msgid "" "The %{name} lets the hero get more specific information about monsters, " "enemy heroes, and castles nearby the hero." msgstr "" "%{name} umožňuje získaÅ¥ konkrétnejÅ¡ie informácie o bojovníkoch, " "nepriateľských hrdinoch a hradoch v blízkosti hrdinu, ktorý ju drží." msgid "" "You come upon a caravan of gypsies who are feasting and fortifying their " "bodies with mead. They call you forward and say \"If you prove that you can " "dance the Rama-Buta, we will reward you.\" You don't know it, but try " "anyway. They laugh hysterically, but admire your bravery, giving you a " "Crystal Ball." msgstr "" "Narazíš na karavánu Rómov, ktorí hodujú a posilňujú sa medovinou. Zavolajú " "Å¥a a hovoria: \"Ak dokážeÅ¡, že vieÅ¡ tancovaÅ¥ Rama-Butu, odmeníme Å¥a.\" " "NevieÅ¡ to, ale aj tak to skúsiÅ¡. Hystericky sa zasmejú, ale obdivujú tvoju " "odvahu a dajú ti KriÅ¡táľovú guľu." msgid "Heart of Fire" msgstr "Ohnivé srdce" msgid "" "The %{name} provides %{count} percent protection from fire, but doubles the " "damage taken from cold." msgstr "" "%{name} poskytuje %{count}-percentnú ochranu pred ohňom, ale zdvojnásobuje " "poÅ¡kodenie spôsobené chladom." msgid "" "You enter a recently burned glade and come upon a Fire Elemental sitting " "atop a rock. It looks up, its flaming face contorted in a look of severe " "pain. It then tosses a glowing object at you. You put up your hands to block " "it, but it passes right through them and sears itself into your chest." msgstr "" "VstúpiÅ¡ na nedávno vypálenú paseku a narazíš na Ohnivého elementála, ktorý " "sedí na vrchole skaly. Pozerá sa na teba a jeho ohnivá tvár je skreslená " "výrazom silnej bolesti. Potom na teba hodí žiariaci predmet. ZdvihneÅ¡ ruky, " "aby si ho zablokoval, ale prejde cez ne a zabodne sa ti do hrude." msgid "Heart of Ice" msgstr "Ľadové srdce" msgid "" "The %{name} provides %{count} percent protection from cold, but doubles the " "damage taken from fire." msgstr "" "%{name} poskytuje %{count}-percentnú ochranu pred chladom, ale zdvojnásobuje " "poÅ¡kodenie spôsobené ohňom." msgid "" "Suddenly, a biting coldness engulfs your body. You seize up, falling from " "your horse. The pain subsides, but you still feel as if your chest is " "frozen. As you pick yourself up off of the ground, you hear hearty laughter. " "You turn around just in time to see a Frost Giant run off into the woods and " "disappear." msgstr "" "Tvoje telo zrazu pohltí Å¡tipľavý chlad. Zmocní sa Å¥a strach a spadneÅ¡ z " "koňa. BolesÅ¥ ustupuje, ale stále máš pocit, akoby ti zamrzol hrudník. KeÄ sa " "zdvihneÅ¡ zo zeme, poÄujeÅ¡ silný smiech. OtoÄíš sa práve vÄas, aby si videl, " "ako mrazivý obor uteká do lesa a mizne." msgid "Helmet of Anduran" msgstr "Anduranova prilba" msgid "" "You spy a gleaming object poking up out of the ground. You send a member of " "your party over to investigate. He comes back with a golden helmet in his " "hands. You realize that it must be the helmet of the legendary Anduran, the " "only man who was known to wear solid gold armor." msgstr "" "Zo zeme vyÄnieva lesklý predmet. PoÅ¡leÅ¡ jedného Älena svojej skupiny, aby to " "preskúmal. Vráti sa so zlatou prilbou v rukách. UvedomujeÅ¡ si, že to musí " "byÅ¥ prilba legendárneho Andurana, jediného Äloveka, o ktorom sa vedelo, že " "nosí zbroj z Äistého zlata." msgid "Holy Hammer" msgstr "Sväté kladivo" msgid "" "You come upon a battle where a Paladin has been mortally wounded by a group " "of Zombies. He asks you to take his hammer and finish what he started. As " "you pick it up, it begins to hum, and then everything becomes a blur. The " "Zombies lie dead, the hammer dripping with blood. You strap it to your belt." msgstr "" "Natrafíš na bitku, v ktorej skupina Zombíkov smrteľne zranila Paladína. " "Požiada o prevzatie jeho kladiva a dokonÄenie toho, Äo zaÄal. KeÄ ho " "zdvihneÅ¡, zaÄne huÄaÅ¥ a potom sa vÅ¡etko rozmazáva. Zombíci ležia mÅ•tvi a z " "kladiva kvapká krv. Pripevníš si ho k opasku." msgid "Legendary Scepter" msgstr "Legendárne žezlo" msgid "The %{name} adds %{count} points to all attributes." msgstr "%{name} pridáva %{count} bodov ku vÅ¡etkým atribútom." msgid "" "Upon cresting a small hill, you come upon a ridiculous looking sight. A " "Sprite is attempting to carry a Scepter that is almost as big as it is. " "Trying not to laugh, you ask, \"Need help?\" The Sprite glares at you and " "answers: \"You think this is funny? Fine. You can carry it. I much prefer " "flying anyway.\"" msgstr "" "KeÄ zdoláš menší kopec, uvidíš smieÅ¡ne vyzerajúci úkaz. Víla sa pokúša niesÅ¥ " "žezlo, ktoré je takmer také veľké ako ona sama. Snažíš sa nesmiaÅ¥ a pýtaÅ¡ " "sa: \"PotrebujeÅ¡ pomoc?\" Víla sa na teba pozrie a odpovie: \"Myslíš si, že " "je to smieÅ¡ne? Dobre. MôžeÅ¡ to niesÅ¥ ty. Aj tak mám radÅ¡ej lietanie.\"" msgid "" "An old seaman tells you a tale of an enchanted masthead that he used in his " "youth to rally his crew during times of trouble. He then hands you a faded " "map that shows where he hid it. After much exploring, you find it stashed " "underneath a nearby dock." msgstr "" "Starý námorník ti rozpovie príbeh o zaÄarovanom sÅ¥ažni, ktorý v mladosti " "používal na zhromaždenie posádky v Å¥ažkých Äasoch. Potom ti podá vyblednutú " "mapu, ktorá ukazuje, kde ho ukryl. Po dlhom skúmaní ho nájdeÅ¡ ukrytý pod " "neÄalekým prístavom." msgid "Masthead" msgstr "Hrot sÅ¥ažňa" #, fuzzy msgid "" "The %{name} boosts the hero's troops' luck and morale by %{count} each in " "sea combat." msgstr "%{name} zvyÅ¡uje tvoje šťastie a morálku v námornom boji o %{count}." msgid "Sphere of Negation" msgstr "Guľa negácie" msgid "The %{name} disables all spell casting, for both sides, in combat." msgstr "%{name} znemožňuje obom stranám v boji zosielanie vÅ¡etkých kúziel." msgid "" "You stop to help a Peasant catch a runaway mare. To show his gratitude, he " "hands you a tiny sphere. As soon as you grasp it, you feel the magical " "energy drain from your limbs..." msgstr "" "Zastavíš, a pomôžeÅ¡ sedliakovi chytiÅ¥ zabehnutú kobylu. Na znak vÄaky ti " "podá malú guľôÄku. Len Äo ju uchopíš, pocítiÅ¡, ako ti z konÄatín vyprcháva " "magická energia..." msgid "Staff of Wizardry" msgstr "ÄŒarodejnícka palica" #, fuzzy msgid "The %{name} boosts the hero's spell power by %{count}." msgstr "%{name} zvýši tvoju Silu kúziel o %{count}." msgid "" "While out scaring up game, your troops find a mysterious staff levitating " "about three feet off of the ground. They hand it to you, and you notice an " "inscription. It reads: \"Brains best brawn and magic beats might. Heed my " "words, and you'll win every fight.\"" msgstr "" "PoÄas plaÅ¡enia zveri nájdu tvoji vojaci záhadnú palicu, ktorá levituje asi " "tri stopy nad zemou. Podajú ti ju a ty si vÅ¡imneÅ¡ nápis. Stojí na ňom: " "\"Mozog prekonáva silu a mágia poráža moc. PoÄúvaj moje slová a vyhráš každý " "boj.\"" msgid "Sword Breaker" msgstr "LámaÄ meÄov" #, fuzzy msgid "The %{name} increases the hero's defense by %{count} and attack by 1." msgstr "%{name} zvyÅ¡uje tvoju obranu o %{count} a útok o 1." msgid "" "A former Captain of the Guard admires your quest and gives you the enchanted " "Sword Breaker that he relied on during his tour of duty." msgstr "" "Bývalý kapitán stráže obdivuje tvoju misiu a daruje ti zakliaty LamaÄ meÄov, " "na ktorý sa spoliehal poÄas svojej služby." msgid "Sword of Anduran" msgstr "Anduranov meÄ" msgid "" "A Troll stops you and says: \"Pay me 5,000 gold, or the Sword of Anduran " "will slay you where you stand.\" You refuse. The troll grabs the sword " "hanging from its belt, screams in pain, and runs away. Picking up the fabled " "sword, you give thanks that half-witted Trolls tend to grab the wrong end of " "sharp objects." msgstr "" "Zastaví Å¥a Trol a povie: \"ZaplaÅ¥ mi 5000 zlatých, inak Å¥a Anduranov meÄ " "zabije na mieste.\" OdmietneÅ¡. Trol chytí meÄ zavesený na opasku, skríkne od " "bolesti a uteÄie. ZdvihneÅ¡ bájny meÄ a poÄakujeÅ¡ sa, že polointeligentní " "trollovia majú tendenciu uchopiÅ¥ ostré predmety za nesprávny koniec." msgid "Spade of Necromancy" msgstr "Rýľ nekromancie" #, fuzzy msgid "The %{name} gives the hero increased necromancy skill." msgstr "%{name} ti dáva zvýšenú schopnosÅ¥ nekromancie." msgid "" "A dirty shovel has been thrust into a dirt mound nearby. Upon investigation, " "you discover it to be the enchanted shovel of the Gravediggers, long thought " "lost by mortals." msgstr "" "Do neÄalekej kopy hliny bola zapichnutá Å¡pinavá lopata. Po preskúmaní " "zistíš, že je to zakliata lopata Hrobárov, ktorú smrteľníci dlho považovali " "za stratenú." msgid "spellBonus|selected by user" msgstr "" msgid "Wood" msgstr "Drevo" msgid "Mercury" msgstr "OrtuÅ¥" msgid "Ore" msgstr "Ruda" msgid "Sulfur" msgstr "Síra" msgid "Crystal" msgstr "KryÅ¡tál" msgid "Gems" msgstr "Drahokamy" msgid "Gold" msgstr "Zlato" msgid "" "There are seven resources in Heroes 2, used to build and improves castles, " "purchase troops and recruit heroes. Gold is the most common, required for " "virtually everything. Wood and ore are used for most buildings. Gems, " "Mercury, Sulfur and Crystal are rare magical resources used for the most " "powerful creatures and buildings." msgstr "" "V Heroes 2 je sedem zdrojov, ktoré sa používajú na stavbu a vylepÅ¡ovanie " "hradov, nákup jednotiek a nábor hrdinov. Zlato je najbežnejÅ¡ie, vyžaduje sa " "prakticky na vÅ¡etko. Na väÄÅ¡inu budov sa používa Drevo a Ruda. Drahokamy, " "OrtuÅ¥, Síra a KryÅ¡tál sú vzácne magické zdroje používané pre najmocnejÅ¡ie " "bytosti a budovy." msgid "" "Causes a giant fireball to strike the selected area, damaging all nearby " "creatures." msgstr "" "Spôsobí, že vybranú oblasÅ¥ zasiahne obrovská ohnivá guľa, ktorá poÅ¡kodí " "vÅ¡etky okolité tvory." msgid "Fireball" msgstr "Ohnivá guľa" msgid "Fireblast" msgstr "Výbuch ohňa" msgid "" "An improved version of fireball, fireblast affects two hexes around the " "center point of the spell, rather than one." msgstr "" "VylepÅ¡ená verzia ohnivej gule, ohnivý výbuch pôsobí na dva hexy okolo " "stredového bodu kúzla namiesto jedného." msgid "Causes a bolt of electrical energy to strike the selected creature." msgstr "Spôsobí, že vybraného tvora zasiahne výboj elektrickej energie." msgid "Lightning Bolt" msgstr "Blesk" msgid "Chain Lightning" msgstr "ReÅ¥azový blesk" msgid "" "Causes a bolt of electrical energy to strike a selected creature, then " "strike the nearest creature with half damage, then strike the NEXT nearest " "creature with half again damage, and so on, until it becomes too weak to be " "harmful. Warning: This spell can hit your own creatures!" msgstr "" "Spôsobí, že výboj elektrickej energie zasiahne vybraného tvora, potom " "zasiahne najbližšieho tvora s poloviÄným poÅ¡kodením, potom zasiahne ÄŽALÅ IEHO " "najbližšieho tvora opäť s poloviÄným poÅ¡kodením a tak Äalej, až kým nie je " "príliÅ¡ slabý na to, aby bol Å¡kodlivý. Varovanie: Toto kúzlo môže zasiahnuÅ¥ " "aj tvoje tvory!" msgid "Teleport" msgstr "Teleport" msgid "" "Teleports the creature you select to any open position on the battlefield." msgstr "Teleportuje vybranú bytosÅ¥ na ľubovoľné voľné miesto na bojisku." msgid "Cure" msgstr "LieÄenie" msgid "" "Removes all negative spells cast upon one of your units, and restores up to " "%{count} HP per level of spell power." msgstr "" "Odstráni vÅ¡etky negatívne kúzla zoslané na jednu z vaÅ¡ich jednotiek a obnoví " "až %{count} zdravia za úroveň Sily kúziel." msgid "Mass Cure" msgstr "Hromadné lieÄenie" msgid "" "Removes all negative spells cast upon your forces, and restores up to " "%{count} HP per level of spell power, per creature." msgstr "" "Odstráni vÅ¡etky negatívne kúzla zoslané na tvoje vojsko a obnoví až %{count} " "zdravia na úroveň sily kúziel, na každého tvora." msgid "Resurrect" msgstr "Vzkriesenie" msgid "Resurrects creatures from a damaged or dead unit until end of combat." msgstr "Oživuje bytosti z poÅ¡kodenej alebo mÅ•tvej jednotky až do konca boja." msgid "Resurrect True" msgstr "Vzkriesenie pravé" msgid "Resurrects creatures from a damaged or dead unit permanently." msgstr "Trvalo oživuje bytosti z poÅ¡kodenej alebo mÅ•tvej jednotky." msgid "Haste" msgstr "Zhon" msgid "Increases the speed of any creature by %{count}." msgstr "Zvýši rýchlosÅ¥ ľubovoľného tvora o %{count}." msgid "Increases the speed of all of your creatures by %{count}." msgstr "ZvyÅ¡uje rýchlosÅ¥ vÅ¡etkých tvojich bytostí o %{count}." msgid "Mass Haste" msgstr "Hromadný zhon" msgid "Slows target to half movement rate." msgstr "Spomalí cieľ na polovicu rýchlosti pohybu." msgid "spell|Slow" msgstr "Spomalenie" msgid "Mass Slow" msgstr "Hromadné spomalenie" msgid "Slows all enemies to half movement rate." msgstr "Spomaľuje vÅ¡etkých nepriateľov na polovicu rýchlosti pohybu." msgid "Clouds the affected creatures' eyes, preventing them from moving." msgstr "Zakryje oÄi postihnutých tvorov a zabráni im v pohybe." msgid "spell|Blind" msgstr "Slepota" msgid "Bless" msgstr "Požehnanie" msgid "Causes the selected creatures to inflict maximum damage." msgstr "ZapríÄiní, že vybrané bytosti spôsobia maximálne poÅ¡kodenie." msgid "Causes all of your units to inflict maximum damage." msgstr "ZapríÄiní, že vÅ¡etky tvoje jednotky spôsobia maximálne poÅ¡kodenie." msgid "Mass Bless" msgstr "Hromadné požehnanie" msgid "Magically increases the defense skill of the selected creatures." msgstr "Magicky zvýši obrannú schopnosÅ¥ vybraných tvorov." msgid "Stoneskin" msgstr "Kamenná koža" msgid "" "Increases the defense skill of the targeted creatures. This is an improved " "version of Stoneskin." msgstr "" "ZvyÅ¡uje obrannú schopnosÅ¥ cielených tvorov. Ide o vylepÅ¡enú verziu Kamennej " "kože." msgid "Steelskin" msgstr "Oceľová koža" msgid "Causes the selected creatures to inflict minimum damage." msgstr "ZapríÄiní, že vybrané bytosti spôsobia minimálne poÅ¡kodenie." msgid "Curse" msgstr "Kliatba" msgid "Causes all enemy troops to inflict minimum damage." msgstr "ZapríÄiní, že vÅ¡etci nepriatelia spôsobia minimálne poÅ¡kodenie." msgid "Mass Curse" msgstr "Hromadná kliatba" msgid "Damages all undead in the battle." msgstr "PoÅ¡kodí vÅ¡etkých nemÅ•tvych v boji." msgid "Holy Word" msgstr "Sväté slovo" msgid "" "Damages all undead in the battle. This is an improved version of Holy Word." msgstr "" "PoÅ¡kodí vÅ¡etkých nemÅ•tvych v boji. Toto je vylepÅ¡ená verzia Svätého slova." msgid "Holy Shout" msgstr "Svätý výkrik" msgid "Anti-Magic" msgstr "Protimágia" msgid "Prevents harmful magic against the selected creatures." msgstr "Bráni Å¡kodlivým kúzlam proti vybraným tvorom." msgid "Dispel Magic" msgstr "RozptýliÅ¥ mágiu" msgid "Removes all magic spells from a single target." msgstr "Odstráni z jedného cieľa vÅ¡etky kúzla." msgid "Mass Dispel" msgstr "Hromadné rozptýlenie" msgid "Removes all magic spells from all creatures." msgstr "Odstráni vÅ¡etky kúzla zo vÅ¡etkých tvorov." msgid "Causes a magic arrow to strike the selected target." msgstr "Spôsobí, že magická strela zasiahne vybraný cieľ." msgid "Magic Arrow" msgstr "ÄŒarovný šíp" msgid "Berserker" msgstr "Amok" msgid "Causes a creature to attack its nearest neighbor." msgstr "Spôsobí, že tvor zaútoÄí na svojho najbližšieho suseda." msgid "Armageddon" msgstr "Armagedon" msgid "" "Holy terror strikes the battlefield, causing severe damage to all creatures." msgstr "" "Svätý teror zasiahne bojisko a spôsobí vážne poÅ¡kodenie vÅ¡etkým stvoreniam." msgid "Elemental Storm" msgstr "Búrka živlov" msgid "Magical elements pour down on the battlefield, damaging all creatures." msgstr "Magické živly sa valia na bojisko a poÅ¡kodzujú vÅ¡etky bytosti." msgid "" "A rain of rocks strikes an area of the battlefield, damaging all nearby " "creatures." msgstr "" "Na oblasÅ¥ bojiska dopadne dÃ¡Å¾Ä kameňov, ktorý poÅ¡kodí vÅ¡etky okolité tvory." msgid "Meteor Shower" msgstr "Pád kameňov" msgid "Paralyze" msgstr "Paralýza" msgid "The targeted creatures are paralyzed, unable to move or retaliate." msgstr "Cieľové bytosti sú paralyzované, neschopné pohybu alebo odvety." msgid "Hypnotize" msgstr "Hypnóza" msgid "" "Brings a single enemy unit under your control if its hits are less than " "%{count} times the caster's spell power." msgstr "" "Privedie jednu nepriateľskú jednotku pod tvoju kontrolu, ak je jej HP menej " "ako %{count} násobok Sily kúziel zosielateľa." msgid "Cold Ray" msgstr "LÃºÄ chladu" msgid "Drains body heat from a single enemy unit." msgstr "OdÄerpá telesné teplo z jednej nepriateľskej jednotky." msgid "Cold Ring" msgstr "Prstenec chladu" msgid "" "Drains body heat from all units surrounding the center point, but not " "including the center point." msgstr "" "OdÄerpá telesné teplo zo vÅ¡etkých jednotiek v okolí stredového bodu, ale nie " "z tohto bodu." msgid "Disrupting Ray" msgstr "NaruÅ¡ujúci lúÄ" msgid "Reduces the defense rating of an enemy unit by three." msgstr "Zníži obranyschopnosÅ¥ nepriateľskej jednotky o tri." msgid "Damages all living (non-undead) units in the battle." msgstr "PoÅ¡kodí vÅ¡etky živé (nie nemÅ•tve) jednotky v bitke." msgid "Death Ripple" msgstr "Poryv smrti" msgid "Death Wave" msgstr "Vlna smrti" msgid "" "Damages all living (non-undead) units in the battle. This spell is an " "improved version of Death Ripple." msgstr "" "PoÅ¡kodí vÅ¡etky živé (nie nemÅ•tve) jednotky v bitke. Toto kúzlo je vylepÅ¡enou " "verziou Poryvu smrti." msgid "Dragon Slayer" msgstr "Drakobijec" msgid "Greatly increases a unit's attack skill vs. Dragons." msgstr "Výrazne zvyÅ¡uje útoÄné schopnosti jednotky proti drakom." msgid "Blood Lust" msgstr "KrvilaÄnosÅ¥" msgid "Increases a unit's attack skill." msgstr "ZvyÅ¡uje útoÄné schopnosti jednotky." msgid "Animate Dead" msgstr "Animácia mÅ•tvych" msgid "Resurrects creatures from a damaged or dead undead unit permanently." msgstr "Trvalo oživí bytosti z poÅ¡kodenej alebo zabitej nemÅ•tvej jednotky." msgid "Mirror Image" msgstr "Zrkadlový obraz" msgid "" "Creates an illusionary unit that duplicates one of your existing units. This " "illusionary unit does the same damages as the original, but will vanish if " "it takes any damage." msgstr "" "Vytvorí iluzórnu jednotku, ktorá duplikuje jednu z tvojich existujúcich " "jednotiek. Táto iluzórna jednotka spôsobuje rovnaké poÅ¡kodenie ako pôvodná " "jednotka, ale ak dostane akékoľvek poÅ¡kodenie, zmizne." msgid "Shield" msgstr "Å tít" msgid "" "Halves damage received from ranged attacks for a single unit. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Znižuje poÅ¡kodenie z útokov na diaľku o polovicu pre jednu jednotku. " "Nechráni pred vežami a balistami." msgid "Mass Shield" msgstr "Hromadný Å¡tít" msgid "" "Halves damage received from ranged attacks for all of your units. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "O polovicu znižuje poÅ¡kodenie spôsobené útokom na diaľku pre vÅ¡etky tvoje " "jednotky. Nechráni pred vežami a balistami." msgid "Summon Earth Elemental" msgstr "Vyvolaj Zemského elementála" msgid "Summons Earth Elementals to fight for your army." msgstr "Vyvolá Zemských elementálov, aby bojovali za tvoju armádu." msgid "Summon Air Elemental" msgstr "Vyvolaj VzduÅ¡ného elementála" msgid "Summons Air Elementals to fight for your army." msgstr "Vyvolá VzduÅ¡ných elementálov, aby bojovali za tvoju armádu." msgid "Summon Fire Elemental" msgstr "Vyvolaj Ohnivého elementála" msgid "Summons Fire Elementals to fight for your army." msgstr "Vyvolá Ohnivých elementálov, aby bojovali za tvoju armádu." msgid "Summon Water Elemental" msgstr "Vyvolaj Vodného elementála" msgid "Summons Water Elementals to fight for your army." msgstr "Vyvolá Vodných elementálov, aby bojovali za tvoju armádu." msgid "Damages castle walls." msgstr "PoÅ¡kodí hradné múry." msgid "Earthquake" msgstr "Zemetrasenie" msgid "Causes all mines across the land to become visible." msgstr "Spôsobí, že sa vÅ¡etky bane v krajine zviditeľnia." msgid "View Mines" msgstr "Ukáž bane" msgid "Causes all resources across the land to become visible." msgstr "Spôsobí, že sa vÅ¡etky suroviny v celej krajine zviditeľnia." msgid "View Resources" msgstr "Ukáž suroviny" msgid "Causes all artifacts across the land to become visible." msgstr "Spôsobí, že vÅ¡etky artefakty v krajine budú viditeľné." msgid "View Artifacts" msgstr "Ukáž artefakty" msgid "Causes all towns and castles across the land to become visible." msgstr "Spôsobí, že sa zviditeľnia vÅ¡etky mestá a hrady v celej krajine." msgid "View Towns" msgstr "Ukáž mestá" msgid "Causes all Heroes across the land to become visible." msgstr "Spôsobí, že sa vÅ¡etci hrdinovia v celej krajine zviditeľnia." msgid "View Heroes" msgstr "Ukáž hrdinov" msgid "Causes the entire land to become visible." msgstr "Spôsobí zviditeľnenie celej krajiny." msgid "View All" msgstr "Ukáž vÅ¡etko" msgid "Allows the caster to view detailed information on enemy Heroes." msgstr "" "Umožňuje zosielateľovi zobraziÅ¥ podrobné informácie o nepriateľských " "hrdinoch." msgid "Summon Boat" msgstr "Vyvolaj loÄ" msgid "" "Summons the nearest unoccupied, friendly boat to an adjacent shore location. " "A friendly boat is one which you just built or were the most recent player " "to occupy." msgstr "" "Privolá najbližšiu neobsadenú priateľskú loÄ na priľahlé miesto na pobreží. " "Priateľská loÄ je loÄ, ktorú si práve postavil alebo si ju obsadil ako " "posledný hráÄ." msgid "Allows the caster to magically transport to a nearby location." msgstr "Umožňuje zaklínaÄovi magicky sa preniesÅ¥ na blízke miesto." msgid "Dimension Door" msgstr "Dimenzionálne dvere" msgid "Returns the caster to any town or castle currently owned." msgstr "" "Vráti zosielateľa do ktoréhokoľvek mesta alebo hradu, ktorý práve vlastníš." msgid "Town Gate" msgstr "Mestská brána" msgid "" "Returns the hero to the town or castle of choice, provided it is controlled " "by you." msgstr "Vráti hrdinu do vybraného mesta alebo hradu, ak ho ovládaÅ¡." msgid "Visions" msgstr "Vízie" msgid "" "Visions predicts the likely outcome of an encounter with a neutral army camp." msgstr "" "Vízie predpovedajú pravdepodobný výsledok stretnutia s táborom neutrálnej " "armády." msgid "Haunt" msgstr "ZakliaÅ¥ baňu" msgid "" "Haunts a mine you control with Ghosts. This mine stops producing resources. " "(If I can't keep it, nobody will!)" msgstr "" "Zakľaje baňu, ktorú ovládaÅ¡ pomocou Duchov. Táto baňa prestane produkovaÅ¥ " "suroviny. (Ak ju nebudem môcÅ¥ maÅ¥ ja, nebude ju maÅ¥ nikto!)" msgid "Set Earth Guardian" msgstr "NasadiÅ¥ zemskú stráž" msgid "Sets Earth Elementals to guard a mine against enemy armies." msgstr "" "Nasadí Zemských elementálov, aby strážili baňu pred nepriateľskými armádami." msgid "Set Air Guardian" msgstr "NasadiÅ¥ vzduÅ¡nú stráž" msgid "Sets Air Elementals to guard a mine against enemy armies." msgstr "" "Nasadí VzduÅ¡ných elementálov, aby strážili baňu pred nepriateľskými armádami." msgid "Set Fire Guardian" msgstr "NasadiÅ¥ ohnivú stráž" msgid "Sets Fire Elementals to guard a mine against enemy armies." msgstr "" "Nasadí Ohnivých elementálov, aby strážili baňu pred nepriateľskými armádami." msgid "Set Water Guardian" msgstr "NasadiÅ¥ vodnú stráž" msgid "Sets Water Elementals to guard a mine against enemy armies." msgstr "" "Nasadí Vodných elementálov, aby strážili baňu pred nepriateľskými armádami." #, fuzzy msgid "Random Spell" msgstr "Náhodná" msgid "Randomly selected spell of any level." msgstr "" #, fuzzy msgid "Random 1st Level Spell" msgstr "Náhodný hrad" msgid "Randomly selected 1st level spell." msgstr "" msgid "Random 2nd Level Spell" msgstr "" msgid "Randomly selected 2nd level spell." msgstr "" msgid "Random 3rd Level Spell" msgstr "" msgid "Randomly selected 3rd level spell." msgstr "" msgid "Random 4th Level Spell" msgstr "" msgid "Randomly selected 4th level spell." msgstr "" msgid "Random 5th Level Spell" msgstr "" msgid "Randomly selected 5th level spell." msgstr "" msgid "Petrification" msgstr "Skamenenie" msgid "" "Turns the affected creature into stone. A petrified creature receives half " "damage from a direct attack." msgstr "" "Zmení zasiahnutého tvora na kameň. Skamenený tvor dostane poloviÄné " "poÅ¡kodenie od priameho útoku." msgid "You have no Magic Book, so you cannot cast a spell." msgstr "Nemáš knihu kúziel, takže nemôžeÅ¡ zosielaÅ¥ kúzla." msgid "No spell to cast." msgstr "Žiadne kúzla na zoslanie." msgid "Your hero has %{point} spell points remaining." msgstr "Tvojmu hrdinovi zostáva %{point} bodov kúziel." msgid "View Adventure Spells" msgstr "ZobraziÅ¥ dobrodružné kúzla" msgid "View Combat Spells" msgstr "ZobraziÅ¥ bojové kúzla" msgid "View previous page" msgstr "ZobraziÅ¥ predchádzajúcu stranu" msgid "View next page" msgstr "ZobraziÅ¥ ÄalÅ¡iu stranu" msgid "Close Spellbook" msgstr "ZavrieÅ¥ knihu kúziel" msgid "View %{spell}" msgstr "ZobraziÅ¥ %{spell}" msgid "This spell does %{damage} points of damage." msgstr "Toto kúzlo spôsobí %{damage} bodov poÅ¡kodenia." msgid "" "This spell summons\n" "%{count} %{monster}." msgstr "Pomocou tohto kúzla povoláš %{count} bojovníkov (%{monster})." msgid "This spell restores %{hp} HP." msgstr "Toto kúzlo obnoví %{hp} zdravia." msgid "This spell summons %{count} %{monster} to guard the mine." msgstr "" "Pomocou tohto kúzla povoláš %{count} bojovníkov (%{monster}), aby strážili " "baňu." msgid "The nearest town is %{town}." msgstr "Najbližšie mesto je %{town}." msgid "This town is occupied by your hero %{hero}." msgstr "Tvoj hrdina %{hero} obsadil toto mesto." msgid "" "This spell controls up to\n" "%{hp} HP." msgstr "" "Toto kúzlo ovláda do\n" "%{hp} zdravia." msgid "The ultimate artifact is really the %{name}." msgstr "Ultimátny artefakt je v skutoÄnosti %{name}." msgid "The ultimate artifact may be found in the %{name} regions of the world." msgstr "Ultimátny artefakt sa nachádza v regióne %{name}." msgid "north-west" msgstr "severozápad" msgid "north" msgstr "sever" msgid "north-east" msgstr "severovýchod" msgid "west" msgstr "západ" msgid "center" msgstr "stred" msgid "east" msgstr "východ" msgid "south-west" msgstr "juhozápad" msgid "south" msgstr "juh" msgid "south-east" msgstr "juhovýchod" msgid "The truth is out there." msgstr "Pravda je tam vonku." msgid "The dark side is stronger." msgstr "Temná sila je silnejÅ¡ia." msgid "The end of the world is near." msgstr "Koniec sveta sa blíži." msgid "The bones of Lord Slayer are buried in the foundation of the arena." msgstr "Kosti Lorda Slayera sú zahrabané v základoch arény." msgid "A Black Dragon will take out a Titan any day of the week." msgstr "ÄŒierny drak zloží Titána aj o polnoci." msgid "He told her: Yada yada yada... and then she said: Blah, blah, blah..." msgstr "Povedal jej: Jada jada jada... a ona na to že: Blá, blá, blá..." msgid "An unknown force is being resurrected..." msgstr "Neznáma sila je vzkriesená..." msgid "" "Check the newest version of the game at\n" "https://github.com/ihhub/\n" "fheroes2/releases" msgstr "" "Pozri si najnovÅ¡iu verziu hry na\n" "https://github.com/ihhub/\n" "fheroes2/releases" #, fuzzy #~ msgid "Terrain" #~ msgstr "Režim terénu" #, fuzzy #~ msgid "Castles" #~ msgstr "Hrad" #, fuzzy #~ msgid "Ocean object" #~ msgstr "Objekty oceánu" #~ msgid "Ocean Objects" #~ msgstr "Objekty oceánu" #~ msgid "Grass Objects" #~ msgstr "Objekty trávy" #~ msgid "Snow Objects" #~ msgstr "Objekty snehu" #~ msgid "Swamp Objects" #~ msgstr "Objekty moÄiara" #~ msgid "Lava Objects" #~ msgstr "Objekty lávy" #~ msgid "Desert Objects" #~ msgstr "Objekty púšte" #~ msgid "Dirt Objects" #~ msgstr "Objekty hliny" #~ msgid "Beach Objects" #~ msgstr "Objekty pláže" #~ msgid "Used to place objects most appropriate for use on %{terrain}." #~ msgstr "" #~ "Slúži na umiestnenie objektov najvhodnejších na použitie na %{terrain}." #~ msgid "" #~ "Used to place\n" #~ "a town or castle." #~ msgstr "" #~ "Slúži na umiestnenie\n" #~ "mesta alebo hradu." #~ msgid "Used to place an artifact." #~ msgstr "Slúži na umiestnenie artefaktu." #~ msgid "" #~ "Used to place\n" #~ "a resource or treasure." #~ msgstr "" #~ "Slúži na umiestnenie\n" #~ "zdroja alebo pokladu." #~ msgid "%{race1} %{name1}" #~ msgstr "%{race1} %{name1}" #~ msgid "vs" #~ msgstr "proti" #~ msgid "%{race2} %{name2}" #~ msgstr "%{race2} %{name2}" fheroes2-1.0.12+dfsg/files/lang/sv.po000066400000000000000000011146031456075706000172620ustar00rootroot00000000000000# Swedish translation for fheroes2 # Copyright (C) 2024 fheroes2 team # This file is distributed under the same license as the fheroes2 package. # msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-03 03:27+0000\n" "PO-Revision-Date: 2023-12-25 14:31+0100\n" "Last-Translator: fheroes2 team \n" "Language-Team: Swedish \n" "Language: sv\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Launchpad-Export-Date: 2013-10-09 02:01+0000\n" "X-Generator: Poedit 3.4.2\n" msgid "" "BATTLE\n" "ONLY" msgstr "" "ENDAST\n" "STRID" msgid "" "NEW\n" "GAME" msgstr "" "NYTT\n" "SPEL" msgid "" "SAVE\n" "GAME" msgstr "" "SPARA\n" "SPEL" msgid "" "LOAD\n" "GAME" msgstr "" "LADDA\n" "SPEL" msgid "INFO" msgstr "INFO" msgid "QUIT" msgstr "AVSLUT." msgid "CANCEL" msgstr "AVBRYT" msgid "OKAY" msgstr "OKEJ" msgid "smallerButton|OKAY" msgstr "OKEJ" msgid "ACCEPT" msgstr "ACCEPTERA" msgid "DECLINE" msgstr "AVBÖJ" msgid "LEARN" msgstr "VÄLJ" msgid "TRADE" msgstr "HANDLA" msgid "YES" msgstr "JA" msgid "NO" msgstr "NEJ" msgid "EXIT" msgstr "LÄMNA" msgid "smallerButton|EXIT" msgstr "LÄMN." msgid "DISMISS" msgstr "AVSKEDA" msgid "UPGRADE" msgstr "UPPG." msgid "RESTART" msgstr "OMSTART" msgid "HEROES" msgstr "" "HJÄLT-\n" "AR" msgid "" "TOWNS/\n" "CASTLES" msgstr "" "STÄDER/\n" "SLOTT" msgid "S" msgstr "S" msgid "M" msgstr "M" msgid "L" msgstr "L" msgid "X-L" msgstr "XL" msgid "ALL" msgstr "ALLA" msgid "SELECT" msgstr "VÄLJ" msgid "" "STANDARD\n" "GAME" msgstr "SOLOSPEL" msgid "" "CAMPAIGN\n" "GAME" msgstr "KAMPANJ" msgid "" "MULTI-\n" "PLAYER\n" "GAME" msgstr "" "MULTI-\n" "SPEL" msgid "CONFIG" msgstr "" "INSTÄLL-\n" "NINGAR" msgid "" "ORIGINAL\n" "CAMPAIGN" msgstr "" "ORGINAL-\n" "KAMPANJ" msgid "" "EXPANSION\n" "CAMPAIGN" msgstr "" "EXPANSIONS-\n" "KAMPANJ" msgid "HOT SEAT" msgstr "HETA STOLEN" msgid "2 PLAYERS" msgstr "2 SPELARE" msgid "3 PLAYERS" msgstr "3 SPELARE" msgid "4 PLAYERS" msgstr "4 SPELARE" msgid "5 PLAYERS" msgstr "5 SPELARE" msgid "6 PLAYERS" msgstr "6 SPELARE" msgid "GIFT" msgstr "GÃ…VA" msgid "MAX" msgstr "MAX" msgid "DIFFICULTY" msgstr "SVÃ…RIGHETSGRAD" msgid "VIEW INTRO" msgstr "VISA INTRO" msgid "MIN" msgstr "MIN" msgid "RESET" msgstr "Ã…TERS." msgid "START" msgstr "STARTA" msgid "CASTLE" msgstr "SLOTT" msgid "TOWN" msgstr "STAD" msgid "" "D\n" "I\n" "S\n" "M\n" "I\n" "S\n" "S" msgstr "" "A\n" "V\n" "S\n" "K\n" "E\n" "D\n" "A" msgid "" "E\n" "X\n" "I\n" "T" msgstr "" "L\n" "Ä\n" "M\n" "N\n" "A" msgid "" "C\n" "A\n" "M\n" "P\n" "A\n" "I\n" "G\n" "N" msgstr "" "K\n" "A\n" "M\n" "P\n" "A\n" "N\n" "J" msgid "" "S\n" "T\n" "A\n" "N\n" "D\n" "A\n" "R\n" "D" msgstr "" "S\n" "O\n" "L\n" "O\n" "S\n" "P\n" "E\n" "L" msgid "Warrior" msgstr "Krigare" msgid "Builder" msgstr "Byggare" msgid "Explorer" msgstr "Utforskare" msgid "None" msgstr "Inga" msgid "" "A few\n" "%{monster}" msgstr "" "Ett par\n" "%{monster}" msgid "" "Several\n" "%{monster}" msgstr "" "NÃ¥gra\n" "%{monster}" msgid "" "A pack of\n" "%{monster}" msgstr "" "En flock\n" "%{monster}" msgid "" "Lots of\n" "%{monster}" msgstr "" "Mängder av\n" "%{monster}" msgid "" "A horde of\n" "%{monster}" msgstr "" "En hord av\n" "%{monster}" msgid "" "A throng of\n" "%{monster}" msgstr "" "Ett myller av\n" "%{monster}" msgid "" "A swarm of\n" "%{monster}" msgstr "" "En svärm av\n" "%{monster}" msgid "" "Zounds...\n" "%{monster}" msgstr "" "En skock\n" "%{monster}" msgid "" "A legion of\n" "%{monster}" msgstr "" "En legion av\n" "%{monster}" msgid "army|Few" msgstr "NÃ¥gra" msgid "army|Several" msgstr "Flera" msgid "army|Pack" msgstr "Flock" msgid "army|Lots" msgstr "Mängder" msgid "army|Horde" msgstr "Hord" msgid "army|Throng" msgstr "Myller" msgid "army|Swarm" msgstr "Svärm" msgid "army|Zounds" msgstr "Skock" msgid "army|Legion" msgstr "Legion" msgid "All %{race} troops +1" msgstr "Endast %{race}trupper +1" msgid "Multiple" msgstr "Flera" msgid "Troops of %{count} alignments -%{penalty}" msgstr "Armén bestÃ¥r av trupper frÃ¥n %{count} olika klasser, ger -%{penalty}" msgid "Some undead in army -1" msgstr "NÃ¥gra odöda i armén -1" msgid "View %{name}" msgstr "Visa %{name}" msgid "Move the %{name} " msgstr "Flytta %{name} " msgid "Move or right click to redistribute %{name}" msgstr "Flytta eller högerklicka för att fördela %{name}" msgid "Combine %{name} armies" msgstr "Kombinera %{name} arméer" msgid "Exchange %{name2} with %{name}" msgstr "Byt ut %{name2} mot %{name}" msgid "Select %{name}" msgstr "Välj %{name}" msgid "Cannot move last troop" msgstr "Kan ej flytta sista truppen" msgid "Move the %{name}" msgstr "Flytta %{name}" msgid "Set Count" msgstr "Ange Antal" msgid "%{name} destroys half the enemy troops!" msgid_plural "%{name} destroy half the enemy troops!" msgstr[0] "%{name} förgör hälften av fiendetrupperna!" msgstr[1] "%{name} förgör hälften av fiendetrupperna!" msgid "%{name} has turned off the auto battle" msgstr "%{name} har stängt av automatisk strid" msgid "%{name} has turned on the auto battle" msgstr "%{name} har aktiverat automatisk strid" msgid "Spell failed!" msgstr "Besvärjelse misslyckades!" msgid "" "The Sphere of Negation artifact is in effect for this battle, disabling all " "combat spells." msgstr "" "Negationsklotsartefakten är i effekt för denna striden och inaktiverar alla " "stridsbesvärjelser." msgid "You have already cast a spell this round." msgstr "Du har redan kastat en besvärjelse denna runda." msgid "That spell will have no effect!" msgstr "Besvärjelsen har ingen effekt!" msgid "You may only summon one type of elemental per combat." msgstr "Du kan endast inkalla en elementtyp per strid." msgid "" "There is no open space adjacent to your hero where you can summon an " "Elemental to." msgstr "" "Det finns ingen fri plats intill din hjälte för att inkalla ett elementväsen." msgid "" "The Moat reduces by -%{count} the defense skill of any unit and slows to " "half movement rate." msgstr "" "Vallgraven sänker en varelses Försvar med -%{count} och halverar dennes " "hastighet." msgid "Speed: %{speed}" msgstr "Hastighet: %{speed}" msgid "Speed" msgstr "Hastighet" msgid "Off" msgstr "Av" msgid "On" msgstr "PÃ¥" msgid "Turn Order" msgstr "Turordning" msgid "Auto Spell Casting" msgstr "" "Autom.\n" "besvärj.kast." msgid "Grid" msgstr "Rutnät" msgid "Shadow Movement" msgstr "Skuggförflyttning" msgid "Shadow Cursor" msgstr "Skuggmarkör" msgid "Audio" msgstr "Ljud" msgid "Settings" msgstr "Inställningar" msgid "Configure" msgstr "Anpassa" msgid "Hot Keys" msgstr "" "Snabb-\n" "tangenter" msgid "Damage Info" msgstr "" "Skade-\n" "information" msgid "Set the speed of combat actions and animations." msgstr "Ange hastigheten för animationer pÃ¥ slagfältet." msgid "Toggle to display the turn order during the battle." msgstr "Visa varelsernas turordning pÃ¥ slagfältet." msgid "" "Toggle whether or not the computer will cast spells for you when auto combat " "is on. (Note: This does not affect spell casting for computer players in any " "way, nor does it affect quick combat.)" msgstr "" "LÃ¥t datorn kasta besvärjelser för dig när automatisk strid är aktiverat. " "(Detta pÃ¥verkar inte fiendedatorns besvärjelsekastning, inte heller pÃ¥verkar " "det snabbstrid.)" msgid "" "Toggle the hex grid on or off. The hex grid always underlies movement, even " "if turned off. This switch only determines if the grid is visible." msgstr "" "Visa rutnät. Rutnätet används alltid, denna inställning bestämmer bara om " "nätet ska vara synligt eller ej." msgid "" "Toggle on or off shadows showing where your creatures can move and attack." msgstr "" "Välj om skuggförflyttning ska vara aktiverat. Skuggförflyttning visar var " "dina varelser kan flytta till och var de kan anfalla." msgid "" "Toggle on or off a shadow showing the current hex location of the mouse " "cursor." msgstr "" "Välj om skuggmarkör skall vara aktiverat. Muspekaren visar en skugga pÃ¥ " "stridsfältets rutnät." msgid "Change the audio settings of the game." msgstr "Ändra spelets ljudinställningar." msgid "Check and configure all the hot keys present in the game." msgstr "Anpassa spelets snabbtangenter." msgid "Toggle to display damage information during the battle." msgstr "Visa skadeinformation under strid." msgid "Exit this menu." msgstr "Stäng menyn." msgid "Okay" msgstr "OK" msgid "The enemy has surrendered!" msgstr "Fienden har kapitulerat!" msgid "Their cowardice costs them %{gold} gold." msgstr "Deras feghet kostar dem %{gold} guld." msgid "The enemy has fled!" msgstr "Fienden har flytt!" msgid "A glorious victory!" msgstr "En ärorik seger!" msgid "For valor in combat, %{name} receives %{exp} experience." msgstr "För sin tapperhet i strid erhÃ¥ller %{name} %{exp} erfarenhetspoäng." msgid "The cowardly %{name} flees from battle." msgstr "Fegisen %{name} flyr frÃ¥n striden." msgid "%{name} surrenders to the enemy, and departs in shame." msgstr "%{name} ger sig till fienden och ger sig av i skam." msgid "Your forces suffer a bitter defeat, and %{name} abandons your cause." msgstr "" "Dina trupper lider en bitter förlust och %{name} lämnar dig och dina trupper." msgid "Your forces suffer a bitter defeat." msgstr "Dina trupper lider en bitter förlust." msgid "Battlefield Casualties" msgstr "Slagfältsoffer" msgid "Attacker" msgstr "Angripare" msgid "Defender" msgstr "Försvarare" msgid "Click to leave the battle results." msgstr "Klicka här för att lämna stridsresultaten." msgid "Click to restart the battle in manual mode." msgstr "Klicka här för att omstarta striden manuellt." msgid "Restart" msgstr "Omstart" msgid "You have captured an enemy artifact!" msgstr "Du har tillfÃ¥ngatagit fiendens artefakt!" msgid "As you reach for the %{name}, it mysteriously disappears." msgstr "När du försöker ta %{name}, sÃ¥ försvinner den plötsligt." msgid "As your enemy reaches for the %{name}, it mysteriously disappears." msgstr "När fienden försöker ta %{name}, sÃ¥ försvinner den plöstligt." msgid "Necromancy!" msgstr "Svartkonst!" msgid "" "Practicing the dark arts of necromancy, you are able to raise %{count} of " "the enemy's dead to return under your service as %{monster}." msgstr "" "Genom att utöva svartkonst, sÃ¥ Ã¥teruppväcker du %{count} av fiendens stupade " "som %{monster} för att tjäna dig." msgid "%{name} the %{race}" msgstr "%{race}n %{name}" msgid "Captain of %{name}" msgstr "Kapten över %{name}" msgid "Attack" msgstr "Anfall" msgid "Defense" msgstr "Försvar" msgid "Spell Power" msgstr "Trollkonst" msgid "Knowledge" msgstr "Kunskap" msgid "Morale" msgstr "Stridsmoral" msgid "Luck" msgstr "Tur" msgid "Spell Points" msgstr "Besvärjelsepoäng" msgid "Hero's Options" msgstr "Hjältealternativ" msgid "Cast Spell" msgstr "Kasta besvärjelse" msgid "Retreat" msgstr "Retirera" msgid "Surrender" msgstr "Kapitulera" msgid "Cancel" msgstr "Avbryt" msgid "Hero Screen" msgstr "Hjältevy" msgid "Captain's Options" msgstr "Kaptensalternativ" msgid "" "Cast a magical spell. You may only cast one spell per combat round. The " "round is reset when every creature has had a turn." msgstr "" "Kasta en besvärjelse. Du kan endast kasta en besvärjelse per runda. Rundan " "nollställs när varje varelse har haft sin runda." msgid "" "Retreat your hero, abandoning your creatures. Your hero will be available " "for you to recruit again, however, the hero will have only a novice hero's " "forces." msgstr "" "Retirera din hjälte, överge dina kreatur. Din hjälte kommer att vara " "tillgänglig för dig att rekrytera igen, men hjälten kommer endast att ha en " "novishjältes styrkor." msgid "" "Surrendering costs gold. However if you pay the ransom, the hero and all of " "his or her surviving creatures will be available to recruit again. The cost " "of surrender is half of the total cost of the non-temporary troops remaining " "in the army." msgstr "" "Att kapitulera kostar guld. Men om du betalar lösensumman kommer hjälten och " "alla dennes överlevande varelser kunna rekryteras igen. " "Kapitulationskostnaden är halva totalkostnaden av alla icke tillfälliga " "trupper i armén." msgid "Open Hero Screen to view full information about the hero." msgstr "Öppna Hjältevyn för att fÃ¥ full information om hjälten." msgid "Return to the battle." msgstr "Ã…tergÃ¥ till striden." msgid "Not enough gold (%{gold})" msgstr "För lite guld (%{gold})" msgid "%{name} states:" msgstr "%{name}s förklarar:" msgid "Captain of %{name} states:" msgstr "Kaptenen över %{name} förklarar:" msgid "" "\"I will accept your surrender and grant you and your troops safe passage " "for the price of %{price} gold.\"" msgstr "" "\"Jag kommer att acceptera din kapitulation och bevilja dig och dina trupper " "en fri lejd för ett pris pÃ¥ %{price} guld.\"" msgid "View %{monster} info" msgstr "Visa information om %{monster}" msgid "Fly %{monster} here" msgstr "Flyg %{monster} hit" msgid "Move %{monster} here" msgstr "Flytta %{monster} hit" msgid "Shoot %{monster}" msgstr "Skjut %{monster}" msgid "(1 shot left)" msgid_plural "(%{count} shots left)" msgstr[0] "(1 projektil kvar)" msgstr[1] "(%{count} projektiler kvar)" msgid "Attack %{monster}" msgstr "Anfall %{monster}" msgid "Turn %{turn}" msgstr "Runda %{turn}" msgid "Teleport here" msgstr "Teleportera hit" msgid "Invalid teleport destination" msgstr "Ogiltig teleporteringsdestination" msgid "Cast %{spell} on %{monster}" msgstr "Kasta %{spell} pÃ¥ %{monster}" msgid "Cast %{spell}" msgstr "Kasta %{spell}" msgid "Select spell target" msgstr "Välj besvärjelsemÃ¥l" msgid "View Ballista info" msgstr "Visa Ballistinformation" msgid "Ballista" msgstr "Ballist" msgid "Enable auto combat" msgstr "Aktivera autom. strid" msgid "Allows the computer to fight out the battle for you." msgstr "TillÃ¥ter datorn att fortsätta striden Ã¥t dig." msgid "Auto Combat" msgstr "Autom. Strid" msgid "Customize system options" msgstr "Skräddarsy systeminställningarna" msgid "Allows you to customize the combat screen." msgstr "TillÃ¥ter dig att skräddarsy stridsvyn." msgid "System Options" msgstr "Systemalternativ" msgid "Skip this unit" msgstr "Hoppa över denna enhet" msgid "Skip" msgstr "Hoppa över" msgid "" "Skips the current creature. The current creature ends its turn and does not " "get to go again until the next round." msgstr "" "Hoppar över nuvarande varelse. Nuvarande varelse avslutar sin runda och " "fortsätter först vid sin nästa runda." msgid "View Captain's options" msgstr "Visa Kaptensalternativ" msgid "View Hero's options" msgstr "Visa Hjältealternativ" msgid "View opposing Captain" msgstr "Visa motstÃ¥ndarkapten" msgid "View opposing Hero" msgstr "Visa motstÃ¥ndarhjälte" msgid "Hide logs" msgstr "Dölj logg" msgid "Show logs" msgstr "Visa logg" msgid "Message Bar" msgstr "Textfält" msgid "Shows the results of individual monster's actions." msgstr "Visa resultaten av en varelses handlingar." msgid "Are you sure you want to finish the battle in auto mode?" msgstr "Är du säker pÃ¥ att du vill fortsätta striden i automatiskt läge?" msgid "Are you sure you want to enable auto combat?" msgstr "Är du säker pÃ¥ att du vill aktivera automatisk strid?" msgid "%{name} skip their turn." msgstr "%{name} hoppar över rundan." msgid "%{attacker} does %{damage} damage." msgid_plural "%{attacker} do %{damage} damage." msgstr[0] "%{attacker} ger %{damage} skada." msgstr[1] "%{attacker} ger %{damage} skada." msgid "1 creature perishes." msgid_plural "%{count} creatures perish." msgstr[0] "en varelse stupar." msgstr[1] "%{count} varelser stupar." msgid "1 %{defender} perishes." msgid_plural "%{count} %{defender} perish." msgstr[0] "en %{defender} stupar." msgstr[1] "%{count} %{defender} stupar." msgid "1 soul is incorporated." msgid_plural "%{count} souls are incorporated." msgstr[0] "1 själ absorberas." msgstr[1] "%{count} själar absorberas." msgid "1 %{unit} is revived." msgid_plural "%{count} %{unit} are revived." msgstr[0] "1 %{unit} Ã¥teruppväcks." msgstr[1] "%{count} %{unit} Ã¥teruppväcks." msgid "Moved %{monster}: from [%{src}] to [%{dst}]." msgstr "Flyttade %{monster}: frÃ¥n [%{src}] till [%{dst}]." msgid "The %{name} resist the spell!" msgstr "%{name} motstÃ¥r besvärjelsen!" msgid "%{name} casts %{spell} on the %{troop}." msgstr "%{name} kastar %{spell} pÃ¥ %{troop}." msgid "%{name} casts %{spell}." msgstr "%{name} kastar %{spell}." msgid "The %{spell} does %{damage} damage to one undead creature." msgstr "Besvärjelsen %{spell} ger %{damage} skada till en odöd varelse." msgid "The %{spell} does %{damage} damage to all undead creatures." msgstr "Besvärjelsen %{spell} ger %{damage} skada till alla odöda varelser." msgid "The %{spell} does %{damage} damage, %{count} creatures perish." msgstr "Besvärjelsen %{spell} ger %{damage} skada, %{count} varelser stupar." msgid "The %{spell} does %{damage} damage." msgstr "Besvärjelsen %{spell} ger %{damage} skada." msgid "The %{spell} does %{damage} damage to one living creature." msgstr "Besvärjelsen %{spell} ger %{damage} skada till en levande varelse." msgid "The %{spell} does %{damage} damage to all living creatures." msgstr "Besvärjelsen %{spell} ger %{damage} skada till alla levande varelser." msgid "The %{attacker}' attack blinds the %{target}!" msgstr "%{attacker}nas anfall förblindar %{target}na!" msgid "The %{attacker}' gaze turns the %{target} to stone!" msgstr "%{attacker}nas blick förvandlar %{target} till sten!" msgid "The %{attacker}' curse falls upon the %{target}!" msgstr "%{attacker}nas förbannelse drabbar %{target}" msgid "The %{target} are paralyzed by the %{attacker}!" msgstr "%{name} blev paralyserade av %{attacker}na!" msgid "The %{attacker} dispel all good spells on your %{target}!" msgstr "%{attacker} skingrar alla godartade besvärjelser frÃ¥n %{target}!" msgid "The %{attacker} cast %{spell} on %{target}!" msgstr "%{attacker} kastar %{spell} pÃ¥ %{target}!" msgid "Bad luck descends on the %{attacker}." msgstr "Otur drabbar %{attacker}." msgid "Good luck shines on the %{attacker}." msgstr "Turen skiner pÃ¥ %{attacker}." msgid "High morale enables the %{monster} to attack again." msgstr "Hög stridsmoral lÃ¥ter %{monster} attackera igen." msgid "Low morale causes the %{monster} to freeze in panic." msgstr "LÃ¥g stridsmoral fÃ¥r %{monster} att stelna i panik." msgid "%{tower} does %{damage} damage." msgstr "%{tower} ger %{damage} skada." msgid "The mirror image is created." msgstr "Spegelbild skapas." msgid "The mirror image is destroyed!" msgstr "Spegelbilden förstörs!" msgid "Are you sure you want to interrupt the auto battle?" msgstr "Är du säker pÃ¥ att du vill avbryta den automatiska striden?" msgid "Error" msgstr "Fel" msgid "No spells to cast." msgstr "Inga besvärjelser att kasta." msgid "Are you sure you want to retreat?" msgstr "Är du säker pÃ¥ att du vill retirera?" msgid "Retreat disabled" msgstr "Retirering inaktiverat" msgid "Surrender disabled" msgstr "Kapitulation inaktiverat" msgid "Damage: %{max}" msgstr "Skada: %{max}" msgid "Damage: %{min} - %{max}" msgstr "Skada: %{min} - %{max}" msgid "Perish: %{max}" msgstr "Stupar: %{max}" msgid "Perish: %{min} - %{max}" msgstr "Stupar: %{min} - %{max}" msgid "" "Through eagle-eyed observation, %{name} is able to learn the magic spell " "%{spell}." msgstr "Genom sin falkblick sÃ¥ kan %{name} lära sig besvärjelsen %{spell}." msgid "Human" msgstr "Spelare" msgid "AI" msgstr "AI" msgid "Start" msgstr "Starta" msgid "Start the battle." msgstr "Starta striden." msgid "Exit" msgstr "Lämna staden" msgid "Reset" msgstr "Ã…terställ" msgid "Reset to default settings." msgstr "Ã…terställ till standardinställningar." msgid "Please select another hero." msgstr "Välj en annan hjälte." msgid "Set Attack Skill" msgstr "Ange AnfallsförmÃ¥ga" msgid "Set Defense Skill" msgstr "Ange FörsvarsförmÃ¥ga" msgid "Set Power Skill" msgstr "Ange Trollkonst" msgid "Set Knowledge Skill" msgstr "Ange KunskapsnivÃ¥" msgid "%{race1} %{name1} vs %{race2} %{name2}" msgstr "%{race1} %{name1} mot %{race2} %{name2}" msgid "Monsters" msgstr "Monster" msgid "N/A" msgstr "Ingen" msgid "Left Turret" msgstr "Vänster Torn" msgid "Right Turret" msgstr "Höger Torn" msgid "The %{name} fires with the strength of %{count} Archers" msgstr "%{name} avfyrar med styrkan av %{count} BÃ¥gskyttar" msgid "each with a +%{attack} bonus to their attack skill." msgstr "varje med en +%{attack} bonus till deras Anfall." msgid "The %{name} is destroyed." msgstr "%{name} förstörs." msgid "%{count} %{name} rises from the dead!" msgid_plural "%{count} %{name} rise from the dead!" msgstr[0] "%{count} %{name} Ã¥teruppstÃ¥r frÃ¥n de döda!" msgstr[1] "%{count} %{name} Ã¥teruppstÃ¥r frÃ¥n de döda!" msgid "Dwarven Alliance" msgstr "Dvärgallians" msgid "Sorceress Guild" msgstr "Häxcirkel" msgid "Necromancer Guild" msgstr "" "Svartkonstnärnas\n" "kult" msgid "Ogre Alliance" msgstr "Jätteallians" msgid "Dwarfbane" msgstr "Dvärgdöd" msgid "Dragon Alliance" msgstr "Drakallians" msgid "Elven Alliance" msgstr "Alvallians" msgid "Kraeger defeated" msgstr "Kraeger besegrad" msgid "Wayward Son" msgstr "Egensinnige Sonen" msgid "Uncle Ivan" msgstr "Farbror Ivan" msgid "Annexation" msgstr "Annektering" msgid "Force of Arms" msgstr "Vapenmakt" msgid "Save the Dwarves" msgstr "Rädda dvärgarna" msgid "Carator Mines" msgstr "Caratorgruvorna" msgid "Turning Point" msgstr "Vändpunkt" msgid "scenarioName|Defender" msgstr "Försvararen" msgid "Corlagon's Defense" msgstr "Corgalons Försvar" msgid "The Crown" msgstr "Kronan" # Gauntlet as in race. msgid "The Gauntlet" msgstr "Gatloppet" msgid "Betrayal" msgstr "Förräderiet" msgid "Final Justice" msgstr "Slutlig Rättvisa" msgid "" "Roland needs you to defeat the lords near his castle to begin his war of " "rebellion against his brother. They are not allied with each other, so they " "will spend most of their time fighting with one another. Victory is yours " "when you have defeated all of their castles and heroes." msgstr "" "Roland behöver dig för att besegra herrarna nära hans slott för att kunna " "pÃ¥börja upproret mot sin broder. De är inte allierade med varandra, sÃ¥ de " "kommer mest strida inbördes. Segern är din när du har besegrat alla deras " "hjältar och intagit deras slott." msgid "" "The local lords refuse to swear allegiance to Roland, and must be subdued. " "They are wealthy and powerful, so be prepared for a tough fight. Capture all " "enemy castles to win." msgstr "" "De lokala herrarna vägrar att alliera sig med Roland och mÃ¥ste kuvas. De är " "rika och mäktiga sÃ¥ var förberedd pÃ¥ hÃ¥rt motstÃ¥nd. \n" "Inta alla fiendeslott för att vinna." msgid "" "Your task is to defend the Dwarves against Archibald's forces. Capture all " "of the enemy towns and castles to win, and be sure not to lose all of the " "dwarf towns at once, or the enemy will have won." msgstr "" "Din uppgift är att försvara Dvärgarna mot Archibalds styrkor. Inta alla " "fiendestäder och slott för att vinna, och försäkra dig om att inte förlora " "kontrollen över samtliga dvärgstäder för i sÃ¥ fall har fienden vunnit." msgid "" "You will face four allied enemies in a straightforward fight for resource " "and treasure. Capture all of the enemy castles for victory." msgstr "" "Du kommer att möta fyra allierade fiender i en kamp om resurser och skatter. " "Inta alla fiendeslott för seger." msgid "" "Your enemies are allied against you and start close by, so be ready to come " "out fighting. You will need to own all four castles in this small valley to " "win." msgstr "" "Dina fiender har allierat sig emot dig och de börjar nära sÃ¥ var redo pÃ¥ " "strid. Du behöver kontrollera alla fyra slott i den lilla dalen för att " "vinna." msgid "" "The Sorceress' guild of Noraston has requested Roland's aid against an " "attack from Archibald's allies. Capture all of the enemy castles to win, and " "don't lose Noraston, or you'll lose the scenario. (Hint: There is an enemy " "castle on an island in the ocean.)" msgstr "" "Norastons Häxcirkel har begärt Rolands hjälp mot ett anfall frÃ¥n Arcibalds " "allierade. Inta alla fiendeslott för att vinna. Förlora inte kontrollen över " "Noraston, för dÃ¥ förlorar du. (Tips: Det finns ett fiendeslott pÃ¥ en ö i " "havet.)" msgid "" "Gather as large an army as possible and capture the enemy castle within 8 " "weeks. You are opposed by only one enemy, but must travel a long way to get " "to the enemy castle. Any troops you have in your army at the end of this " "scenario will be with you in the final battle." msgstr "" "Ansamla en sÃ¥ stor armé som möjligt och inta fiendeslottet inom 8 veckor. Du " "har endast en motstÃ¥ndare, men du mÃ¥ste färdas lÃ¥ngt för att nÃ¥ " "fiendeslottet. Dina trupper som du har i scenariots slut följer med dig till " "slutstriden." msgid "" "Find the Crown before Archibald's heroes find it. Roland will need the Crown " "for the final battle against Archibald." msgstr "" "Finn Kronan före Archibalds hjältar hittar den. Roland behöver Kronan för " "slutstriden mot Archibald." msgid "" "Three allied enemies stand before you and victory, including Lord Corlagon. " "Roland is in a castle to the northwest, and you will lose if he falls to the " "enemy. Remember that capturing Lord Corlagon will ensure that he will not " "fight against you in the final scenario." msgstr "" "Tre allierade fiender, även Lord Corlagon, stÃ¥r mellan dig och segern. " "Roland befinner sig i ett slott i nordväst, och du kommer förlora om han " "hamnar i fiendernas klor. Kom igÃ¥ng att om du tillfÃ¥ngatar Lord Corlagon sÃ¥ " "kommer han inte slÃ¥ss mot dig i slutstriden." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Archibald to end the war!" msgstr "" "Det här är slutstriden. BÃ¥de du och dina fiender är beväpnade till tänderna " "och de har gÃ¥tt samman emot dig. TillfÃ¥ngata Archibald för att avsluta " "kriget!" msgid "" "Switching sides leaves you with three castles against the enemy's one. This " "battle will be the easiest one you will face for the rest of the war..." "traitor." msgstr "" "Lojalitetsbyte lämnar dig med tre slott mot fiendens enda. Denna strid " "kommer bli din enklaste strid för resten av kriget... förrädare." msgid "Barbarian Wars" msgstr "Barbarkrigen" msgid "First Blood" msgstr "Första Blodspillan" msgid "Necromancers" msgstr "Svartkonstnärer" msgid "Slay the Dwarves" msgstr "Dräp Dvärgarna" msgid "Country Lords" msgstr "Landshövdingar" msgid "Dragon Master" msgstr "Drakmästare" msgid "Rebellion" msgstr "Uppror" msgid "Apocalypse" msgstr "Apokalypsen" msgid "Greater Glory" msgstr "Större Ära" msgid "" "King Archibald requires you to defeat the three enemies in this region. They " "are not allied with one another, so they will spend most of their energy " "fighting amongst themselves. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Kung Archibald kräver att du besegrar tre fiender i denna regionen. De är " "inte allierade med varandra, sÃ¥ de kommer mest strida inbördes. Segern är " "din när du har besegrat alla deras hjältar och intagit deras slott." msgid "" "You must unify the barbarian tribes of the north by conquering them. As in " "the previous mission, the enemy is not allied against you, but they have " "more resources at their disposal. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Du mÃ¥ste förena babarstammarna i norr genom att erövra dem. SÃ¥ som i " "föregÃ¥ende uppdrag sÃ¥ är inte fienderna allierade mer varandra, men de har " "mer resurser till sitt förfogande. Du vinner när du kontrollerar alla " "fiendeslott och alla fiendehjältar är besegrade." msgid "" "Do-gooder wizards have taken the Necromancers' castle. You must retake it to " "achieve victory. Remember that while you start with a powerful army, you " "have no castle and must take one within 7 days, or lose this battle. (Hint: " "The nearest castle is to the southeast.)" msgstr "" "De skenheliga trollkarlarna har tagit Svartkonstnärernas slott i besittning. " "Du mÃ¥ste Ã¥tererövra slottet för att uppnÃ¥ seger. Kom ihÃ¥g att du startar med " "en stark armé, men du har inget slott och mÃ¥ste erövra ett inom 7 dagar, " "annars förlorar du. (Tips: Det närmaste slottet finns i sydost.)" msgid "" "The dwarves need conquering before they can interfere in King Archibald's " "plans. Roland's forces have more than one hero and many towns to start with, " "so be ready for attack from multiple directions. You must capture all of the " "enemy towns and castles to claim victory." msgstr "" "Dvärgarna behöver besegras innan de kan störa Kung Archibalds planer. " "Rolands styrkor startar mer fler än en hjälte och flera städer, sÃ¥ var " "beredd pÃ¥ angrepp pÃ¥ flera fronter. Du mÃ¥ste inta alla fiendestäder och " "slott för att utropa seger." msgid "" "You must put down a peasant revolt led by Roland's forces. All are allied " "against you, but you have Lord Corlagon, an experienced hero, to help you. " "Capture all enemy castles to win." msgstr "" "Du mÃ¥ste slÃ¥ ner ett bondeuppror lett av Rolands styrkor. De är allierade " "mot dig, men du har Lord Corlagon, en erfaren hjälte till din hjälp. Erövra " "alla fiendeslott för att vinna." msgid "" "There are two enemies allied against you in this mission. Both are well " "armed and seek to evict you from their island. Avoid them and capture Dragon " "City to win." msgstr "" "Det är tvÃ¥ fiender som har gÃ¥tt samman mot dig i detta uppdrag. BÃ¥da är väl " "rustade och vill driva bort dig frÃ¥n deras ö. Undvik dem och inta Drakstaden " "för att segra." msgid "" "Your orders are to conquer the country lords that have sworn to serve " "Roland. All of the enemy castles are unified against you. Since you start " "without a castle, you must hurry to capture one before the end of the week. " "Capture all enemy castles for victory." msgstr "" "Dina order är att erövra de herrars land som svurit trohet till Roland. Alla " "fiendestäder har förenats emot dig. Eftersom du börjar utan ett slott sÃ¥ " "mÃ¥ste du skynda dig erövra ett innan veckans slut. Erövra alla fiendeslott " "för segern." msgid "" "Find the Crown before Roland's heroes find it. Archibald will need the Crown " "for the final battle against Roland." msgstr "" "Finn Kronan innan Rolands hjältar hittar den. Archibald behöver Kronan för " "slutstriden mot Roland" msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Roland to win the war, and be sure " "not to lose Archibald in the fight!" msgstr "" "Det här är slutstriden. BÃ¥de du och dina fiender är beväpnade till tänderna " "och de har gÃ¥tt samman emot dig. TillfÃ¥ngata Roland för att avsluta kriget, " "och försäkra dig om att inte förlora Archibald i strid!" msgid "Arrow's Flight" msgstr "Pilens Bana" msgid "Island of Chaos" msgstr "Kaosön" msgid "The Abyss" msgstr "Avgrunden" msgid "Uprising" msgstr "Upproret" msgid "Aurora Borealis" msgstr "Norrsken" msgid "Betrayal's End" msgstr "Förräderiet" msgid "Corruption's Heart" msgstr "Korruptionens Hjärta" msgid "The Giant's Pass" msgstr "Jättens Pass" msgid "" "Subdue the unruly local lords in order to provide the Empire with facilities " "to operate in this region." msgstr "" "Kuva de bÃ¥ngstyriga lokala herrarna för att ge Imperiet Ã¥tkomst till " "anläggningar för att styra regionen." msgid "" "Eliminate all opposition in this area. Then the first piece of the artifact " "will be yours." msgstr "" "Eliminera all opposition i omrÃ¥det, sÃ¥ blir den första delen av artefakten " "din." msgid "" "The sorceresses to the northeast are rebelling! For the good of the empire " "you must quash their feeble uprising on your way to the mountains." msgstr "" "Häxan i nordost gör uppror. För imperiets bästa sÃ¥ mÃ¥ste du kväsa deras veka " "uppror pÃ¥ din väg till bergen" msgid "" "Having prepared for your arrival, Kraeger has arranged for a force of " "necromancers to thwart your quest. You must capture the castle of Scabsdale " "before the first day of the third week, or the Necromancers will be too " "strong for you." msgstr "" "Kraeger är förberedd inför din ankomst och har samlat en styrka med " "svartkonstnärer för att stoppa dig. Du mÃ¥ste inta slottet Scabsdale innan " "första dagen pÃ¥ tredje veckan, annars kommer Svartkonstnärerna ha blivit för " "starka." msgid "" "The barbarian despot in this area is, as yet, ignorant of your presence. " "Quickly, build up your forces before you are discovered and attacked! Secure " "the region by subduing all enemy forces." msgstr "" "En barbardespot i omrÃ¥det är ovetandes om din närvaro. Bygg hastigt upp dina " "styrkor innan du är upptäckt och anfallen. Säkra regionen genom att besegra " "alla fiendestyrkor." msgid "" "The Empire is weak in this region. You will be unable to completely subdue " "all forces in this area, so take what you can before reprisal strikes. " "Remember, your true goal is to claim the Helmet of Anduran." msgstr "" "Imperiet är svagt i denna regionen. Du kommer inte kunna besegra alla " "fiender i omrÃ¥det sÃ¥ ta vad du kan innan vedergällningen kommer. Kom ihÃ¥g " "att ditt uppdrag är att finna Andurans Hjälm." msgid "For the good of the Empire, eliminate Kraeger." msgstr "För Imperiets bästa, utplÃ¥na Kraeger." msgid "" "At last, you have the opportunity and the facilities to rid the Empire of " "the necromancer's evil. Eradicate them completely, and you will be sung as a " "hero for all time." msgstr "" "Till slut har du möjligheten och resurserna att rensa imperiet frÃ¥n " "Svartkonstnärernas ondska. Utrota dem helt och du kommer bli inskriven i " "historien som imperiets hjälte." msgid "Border Towns" msgstr "Gränsstäder" msgid "Conquer and Unify" msgstr "Erövra och Förena" msgid "Crazy Uncle Ivan" msgstr "Galne Farbror Ivan" msgid "The Wayward Son" msgstr "Den Egensinnige Sonen" msgid "Ivory Gates" msgstr "Elfenbensportarna" msgid "The Elven Lands" msgstr "Alvländerna" msgid "The Epic Battle" msgstr "Slutstriden" msgid "The Southern War" msgstr "Sydkriget" msgid "" "Conquer and unite all the enemy tribes. Don't lose the hero Jarkonas, the " "forefather of all descendants." msgstr "" "Erövra och förena alla fienderstammar. Förlora inte hjälten Jarkonas, " "förfadern till alla ättlingar." msgid "" "Your rival, the Kingdom of Harondale, is attacking weak towns on your " "border! Recover from their first strike and crush them completely!" msgstr "" "Din rival, kungariket Harondale, attackerer din svaga gränsstäder! Ã…terhämta " "dig frÃ¥n deras första slag och krossa dem helt!" msgid "" "Find your wayward son Joseph who is rumored to be living in the desolate " "lands. Do it before the first day of the third month or it will be of no " "help to your family." msgstr "" "Finn din egensinnige son Joseph som ryktas befinna sig i ensligt land. Finn " "honom innan den tredje mÃ¥nadens första dag annars är det för sent." msgid "" "Rescue your crazy uncle Ivan. Find him before the first day of the fourth " "month or it will be no help to your kingdom." msgstr "" "Rädda din farbror Ivan. Hitta honom inom 4 mÃ¥nader, annars kommer det inte " "till nÃ¥gon hjälp för ditt kungarike." msgid "" "Destroy the barbarians who are attacking the southern border of your " "kingdom! Recover your fallen towns, and then invade the jungle kingdom. " "Leave no enemy standing." msgstr "" "Förgör barbarerna som attackerar ditt rikes sydliga gräns. Ã…terta erövrade " "städer och invadera sedan djungelriket. Lämna inga fiender levande." msgid "Retake the castle of Ivory Gates, which has fallen due to treachery." msgstr "" "Ã…terta slottet Elfenbensportarna, som har fallit pÃ¥ grund av förräderi." msgid "" "Gain the favor of the elves. They will not allow trees to be chopped down, " "so they will send you wood every 2 weeks. You must complete your mission " "before the first day of the seventh month, or the kingdom will surely fall." msgstr "" "FÃ¥ alvernas gunst. De tillÃ¥ter inte att träd huggs ner sÃ¥ de kommer att " "förse dig med Trä varannan vecka. Du mÃ¥ste vara klar med ditt uppdrag innan " "första dagen pÃ¥ sjunde mÃ¥naden, annars sÃ¥ lär kungariket falla." msgid "" "This is the final battle against your rival kingdom of Harondale. Eliminate " "everyone, and don't lose the hero Jarkonas VI." msgstr "" "Det här är slutstriden mot det rivaliserande kungadömet Harondale. Utrota " "allihop och förlora inte hjälten Jarkonas VI." msgid "Fount of Wizardry" msgstr "Trollkonstens Källa" msgid "Power's End" msgstr "Maktens Slut" msgid "The Eternal Scrolls" msgstr "De Eviga Rullarna" msgid "The Shrouded Isles" msgstr "De Dolda Öarna" msgid "" "Your mission is to vanquish the warring mages in the magical Shrouded Isles. " "The completion of this task will give you a fighting chance against your " "rivals." msgstr "" "Ditt uppdrag är att besegra de krigande trollkarlarna i de magiska Dolda " "Öarna. När uppdraget är gjort sÃ¥ kommer du ha en rimlig chans mot dina " "rivaler." msgid "" "The location of the great library has been discovered! You must make your " "way to it, and reclaim the city of Chronos in which it lies." msgstr "" "Det stora Bibliotekets plats har upptäckts. Du mÃ¥ste nÃ¥ fram till det och " "Ã¥terta staden Chronos som det ligger i." msgid "" "Find the Orb of negation, which is said to be buried in this land. There are " "clues inscribed on stone obelisks which will help lead you to your prize. " "Find the Orb before the first day of the sixth month, or your rivals will " "surely have gotten to the fount before you." msgstr "" "Hitta Upphävningsklotet som sägs vara begravet nÃ¥gonstans i detta land. Det " "finns ledtrÃ¥dar inristade pÃ¥ stenobelisker som kan hjälpa dig finna det. " "Hitta Klotet innan första dagen pÃ¥ den sjätte mÃ¥naden, annars lär dina " "rivaler ha hittat det före dig." msgid "" "You must take control of the castle of Magic, where the fount of wizardry " "lies. Do this and your victory will be supreme." msgstr "" "Du mÃ¥ste ta kontroll över det Förtrollade slottet, där trollkonsten källa " "ligger. Gör detta och segern är din." msgid "Blood is Thicker" msgstr "Blod är Tjockare" msgid "King and Country" msgstr "Kung och Fosterland" msgid "Pirate Isles" msgstr "Piratöarna" msgid "Stranded" msgstr "Skeppsbruten" msgid "" "Capture the town on the island off the southeast shore in order to construct " "a boat and travel back towards the mainland. Do not lose the hero Gallavant." msgstr "" "Inta staden pÃ¥ ön pÃ¥ den sydöstra stranden för att kunna bygga ett skepp som " "tar dig tillbaka till fastlandet. Förlora inte hjälten Gallavant." msgid "" "Find and defeat Martine, the pirate leader, who resides in Pirates Cove. Do " "not lose Gallavant or your quest will be over." msgstr "" "Finn och besegra piratkaptenen Martine, som hÃ¥ller till i Piratviken. " "Förlora inte Gallavant för dÃ¥ har du förlorat." msgid "" "Eliminate all the other forces who oppose the rule of Lord Alberon. " "Gallavant must not die." msgstr "" "Besegra alla styrkor som opponerar sig mot Lord Alberons styre. Gallavant " "fÃ¥r inte stupa." msgid "" "Overthrow the entrenched monarchy of Lord Alberon, and claim all the land in " "your name. Gallavant must not die." msgstr "" "Största den besuttna monarken Lord Alberon, och hävda din rätt att styra " "över landet. Gallavant fÃ¥r inte stupa." msgid " bane" msgstr "bane" msgid " alliance" msgstr "allians" msgid "Carry-over forces" msgstr "Medföljande Styrkor" msgid " bonus" msgstr " bonus" msgid " defeated" msgstr " besegrad" msgid " will always run away from your army." msgstr " kommer alltid att fly frÃ¥n din armé." msgid " will be willing to join your army." msgstr " är villiga att ansluta till din armé." msgid "\"%{artifact}\" artifact will be carried over the scenario." msgstr "\"%{artifact}\" kommer följa med till nästa scenario." msgid "The army will be carried over the scenario." msgstr "Armén kommer följa med till nästa scenerio." msgid "The kingdom will have +%{count} %{resource} each day." msgstr "Riket kommer fÃ¥ +%{count} %{resource} varje dag." msgid "\"%{spell}\" spell will be carried over the scenario." msgstr "%{spell}besvärjelsen kommer följa med till nästa scenario." msgid "%{hero} can be hired in the scenario." msgstr "%{hero} kan rekryteras i detta scenario." msgid "" "%{hero} has been defeated and will not appear in the subsequent scenarios." msgstr "" "%{hero} har besegrats och kommer inte vara med i efterföljande scenarier." msgid "The dwarves recognize their allies and gladly join your forces." msgstr "" "Dvärgarna känner igen sina allierade och gladeligen ansluter till din armé." msgid "The ogres recognize you as the Dwarfbane and lumber over to join you." msgstr "" "Jättarna förstÃ¥r att du är den som kallas Dvärgdräparen och ansluter sig " "till dig." msgid "" "The dragons, snarling and growling, agree to join forces with you, their " "'Ally'." msgstr "" "Drakarna rytandes och frustandes gÃ¥r med pÃ¥ att ansluta sig till dig, som " "sin\"Allierade\"." msgid "" "As you approach the group of elves, their leader calls them all to " "attention. He shouts to them, \"Who of you is brave enough to join this " "fearless ally of ours?\" The group explodes with cheers as they run to join " "your ranks." msgstr "" "När du närmar dig en grupp med alver, sÃ¥ ropar deras ledare. \"Vilka av er " "är modiga nog att ansluta er till denna orädde frände till oss?\" Gruppen " "utbrister glädje när de springer och ansluter sig till din armé." msgid "" "The dwarves hail you, \"Any friend of Roland is a friend of ours. You may " "pass.\"" msgstr "Dvärgarna hälsar dig, \"Rolands vän är vÃ¥ran vän. Du mÃ¥ passera.\"" msgid "" "The ogres give you a grunt of recognition, \"Archibald's allies may pass.\"" msgstr "Jättarna brölar ut, \"Archibalds allierade mÃ¥ passera.\"" msgid "" "The dragons see you and call out. \"Our alliance with Archibald compels us " "to join you. Unfortunately you have no room. A pity!\" They quickly scatter." msgstr "" "Drakarna ser dig och ropar. \"VÃ¥r allians med Archibald kräver att vi " "ansluter oss till dig. Tyvärr har du inte nog med plats för oss. Synd!\" De " "skringras sedan snabbt." msgid "" "The elves stand at attention as you approach. Their leader calls to you and " "says, \"Let us not impede your progress, ally! Move on, and may victory be " "yours.\"" msgstr "" "Alverna stÃ¥r givakt när du närmar dig. Deras ledare säger, \"LÃ¥t inte oss " "hindra din kamp, frände. Passera och segern mÃ¥ vara din.\"" msgid "\"The Dwarfbane!!!!, run for your lives.\"" msgstr "\"Dvärgdräparen!!!! Spring för livet!\"" msgid "campaignBonus|Animate Dead" msgstr "Animera Död" msgid "campaignBonus|Chain Lightning" msgstr "Kedjeblixt" msgid "campaignBonus|Fireblast" msgstr "Eldexplosion" msgid "campaignBonus|Mass Curse" msgstr "" "Mass-\n" "förbannelse" msgid "campaignBonus|Mass Haste" msgstr "" "Mass-\n" "skyndning" msgid "campaignBonus|Mirror Image" msgstr "Spegelbild" msgid "campaignBonus|Resurrect" msgstr "Ã…teruppliva" msgid "campaignBonus|Steelskin" msgstr "StÃ¥lhud" msgid "campaignBonus|Summon Earth" msgstr "Framkalla Jordväsen" msgid "campaignBonus|View Heroes" msgstr "Visa Hjältar" msgid "campaignBonus|Ballista" msgstr "Ballist" msgid "campaignBonus|Black Pearl" msgstr "Svart Pärla" msgid "campaignBonus|Caster's Bracelet" msgstr "Besvärjarens Armband" msgid "campaignBonus|Defender Helm" msgstr "Livgardets Skyddshjälm" msgid "campaignBonus|Breastplate" msgstr "Bröstharnesk" msgid "campaignBonus|Dragon Sword" msgstr "Draksvärd" msgid "campaignBonus|Fizbin Medal" msgstr "Olycksfizbin" msgid "campaignBonus|Foremost Scroll" msgstr "Särskild Kunskapsrulle" msgid "campaignBonus|Gauntlets" msgstr "Skyddstridshandskar" msgid "campaignBonus|Hideous Mask" msgstr "Otäck mask" msgid "campaignBonus|Mage's Ring" msgstr "Trollkarlens Ring" msgid "campaignBonus|Major Scroll" msgstr "Större Kunskapsrulle" msgid "campaignBonus|Medal of Honor" msgstr "Hedersmedalj" msgid "campaignBonus|Medal of Valor" msgstr "Tapperhetsmedalj" msgid "campaignBonus|Minor Scroll" msgstr "Mindre Kunskapsrulle" msgid "campaignBonus|Nomad Boots" msgstr "Nomadens Kängor" msgid "campaignBonus|Power Axe" msgstr "Kraftyxa" msgid "campaignBonus|Spiked Shield" msgstr "Spiksköld" msgid "campaignBonus|Stealth Shield" msgstr "Skyddssköld" msgid "campaignBonus|Tax Lien" msgstr "Skuldebrev" msgid "campaignBonus|Thunder Mace" msgstr "Ã…skans Stridsgissel" msgid "campaignBonus|Traveler's Boots" msgstr "Vandrarens Kängor" msgid "campaignBonus|White Pearl" msgstr "Vit Pärla" msgid "campaignBonus|Basic Archery" msgstr "Basalt Skytte" msgid "campaignBonus|Advanced Archery" msgstr "Avancerat Skytte" msgid "campaignBonus|Expert Archery" msgstr "Expert Skytte" msgid "campaignBonus|Basic Ballistics" msgstr "Basal Ballistik" msgid "campaignBonus|Advanced Ballistics" msgstr "Avancerad Ballistik" msgid "campaignBonus|Expert Ballistics" msgstr "Expert Ballistik" msgid "campaignBonus|Basic Diplomacy" msgstr "Basal Diplomati" msgid "campaignBonus|Advanced Diplomacy" msgstr "Avancerad Diplomati" msgid "campaignBonus|Expert Diplomacy" msgstr "Expert Diplomati" msgid "campaignBonus|Basic Eagle Eye" msgstr "Basal Falkblick" msgid "campaignBonus|Advanced Eagle Eye" msgstr "Avancerad Falkblick" msgid "campaignBonus|Expert Eagle Eye" msgstr "Expert Falkblick" msgid "campaignBonus|Basic Estates" msgstr "Basala Ägor" msgid "campaignBonus|Advanced Estates" msgstr "Avancerade Ägor" msgid "campaignBonus|Expert Estates" msgstr "Expert Ägor" msgid "campaignBonus|Basic Leadership" msgstr "Basalt Ledarskap" msgid "campaignBonus|Advanced Leadership" msgstr "Avancerat Ledarskap" msgid "campaignBonus|Expert Leadership" msgstr "Expert Ledarskap" msgid "campaignBonus|Basic Logistics" msgstr "Basal Logistik" msgid "campaignBonus|Advanced Logistics" msgstr "Avancerad Logistik" msgid "campaignBonus|Expert Logistics" msgstr "Expert Logistik" msgid "campaignBonus|Basic Luck" msgstr "Basal Tur" msgid "campaignBonus|Advanced Luck" msgstr "Avancerad Tur" msgid "campaignBonus|Expert Luck" msgstr "Expert Tur" msgid "campaignBonus|Basic Mysticism" msgstr "Basal Mystik" msgid "campaignBonus|Advanced Mysticism" msgstr "Avancerad Mystik" msgid "campaignBonus|Expert Mysticism" msgstr "Expert Mystik" msgid "campaignBonus|Basic Navigation" msgstr "Basal Navigering" msgid "campaignBonus|Advanced Navigation" msgstr "Avancerad Navigering" msgid "campaignBonus|Expert Navigation" msgstr "Expert Navigering" msgid "campaignBonus|Basic Necromancy" msgstr "Basal Svartkonst" msgid "campaignBonus|Advanced Necromancy" msgstr "Avancerad Svartkonst" msgid "campaignBonus|Expert Necromancy" msgstr "Expert Svartkonst" msgid "campaignBonus|Basic Pathfinding" msgstr "Basal Vägvisning" msgid "campaignBonus|Advanced Pathfinding" msgstr "Avancerad Vägvisning" msgid "campaignBonus|Expert Pathfinding" msgstr "Expert Vägvisning" msgid "campaignBonus|Basic Scouting" msgstr "Basal Spaning" msgid "campaignBonus|Advanced Scouting" msgstr "Avancerad Spaning" msgid "campaignBonus|Expert Scouting" msgstr "Expert Spaning" msgid "campaignBonus|Basic Wisdom" msgstr "Basal Visdom" msgid "campaignBonus|Advanced Wisdom" msgstr "Avancerad Visdom" msgid "campaignBonus|Expert Wisdom" msgstr "Expert Visdom" msgid "" "The main hero will have \"%{artifact}\" artifact at the start of the " "scenario." msgstr "Huvudhjälten kommer ha \"%{artifact}\" i början av scenariot." msgid "" "The kingdom will receive %{amount} additional %{resource} at the start of " "the scenario." msgstr "" "Riket kommer fÃ¥ ytterligare %{amount} %{resource} i början av scenariot." msgid "" "The kingdom will have %{amount} less %{resource} at the start of the " "scenario." msgstr "Riket kommer ha %{amount} mindre %{resource} i början av scenariot." msgid "" "The main hero will have %{count} %{monster} at the start of the scenario." msgstr "Huvudhjälten kommer ha %{count} %{monster} i början av scenariot." msgid "" "The main hero will have \"%{spell}\" spell at the start of the scenario." msgstr "" "Huvudhjälten kommer ha \"%{spell}\"-besvärjelsen i början av scenariot." msgid "The starting race of the scenario will be %{race}." msgstr "Scenariots startklass är %{race}." msgid "" "The main hero will have additional %{count} %{skill} at the start of the " "scenario." msgstr "" "Huvudhjälten kommer ha ytterligare %{count} i %{skill} i början av scenariot." msgid "The main hero will have %{skill} at the start of the scenario." msgstr "Huvudhjälten kommer ha %{skill} i början av scenariot." msgid "Roland" msgstr "Roland" msgid "Archibald" msgstr "Archibald" msgid "Price of Loyalty" msgstr "Lojalitetens Pris" msgid "Voyage Home" msgstr "Hemresan" msgid "Wizard's Isle" msgstr "Trollkarlsön" msgid "Descendants" msgstr "Ättlingar" msgid "The %{building} produces %{monster}." msgstr "%{building} producerar %{monster}." msgid "Requires:" msgstr "Kräver:" msgid "Exit this menu without doing anything." msgstr "Stäng denna meny utan att göra nÃ¥gonting." msgid "Cannot build. You have already built here today." msgstr "Kan inte byggas. Du har redan byggt här idag." msgid "For this action it is necessary to build a castle first." msgstr "För denna Ã¥tgärd är det nödvändigt att först bygga ett slott." msgid "Cannot build %{name} because the castle is too far away from an ocean." msgstr "Kan ej bygga %{name}. Slottet är för lÃ¥ngt borta frÃ¥n hav." msgid "disable build." msgstr "Inaktivera bygge" msgid "Cannot afford %{name}." msgstr "Har inte rÃ¥d med %{name}." msgid "%{name} is already built." msgstr "%{name} har redan byggts." msgid "Cannot build %{name}." msgstr "Kan inte bygga %{name}." msgid "Build %{name}." msgstr "Bygg %{name}." msgid "Blackridge" msgstr "Blackridge" msgid "Hillstone" msgstr "Hillstone" msgid "Pinehurst" msgstr "Pinehurst" msgid "Whiteshield" msgstr "Whiteshield" msgid "Woodhaven" msgstr "Woodhaven" msgid "Blackwind" msgstr "Blackwind" msgid "Bloodreign" msgstr "Bloodreign" msgid "Dragontooth" msgstr "Dragontooth" msgid "Greywind" msgstr "Greywind" msgid "Portsmith" msgstr "Portsmith" msgid "Atlantium" msgstr "Atlantium" msgid "Middle Gate" msgstr "Middle Gate" msgid "Sansobar" msgstr "Sansobar" msgid "Tundara" msgstr "Tundara" msgid "Vulcania" msgstr "Vulcania" msgid "Baywatch" msgstr "Baywatch" msgid "Fountainhead" msgstr "Fountainhead" msgid "Vertigo" msgstr "Vertigo" msgid "Wildabar" msgstr "Wildabar" msgid "Winterkill" msgstr "Winterkill" msgid "Brindamoor" msgstr "Brindamoor" msgid "Lakeside" msgstr "Lakeside" msgid "Nightshadow" msgstr "Nightshadow" msgid "Olympus" msgstr "Olympus" msgid "Sandcaster" msgstr "Sandcaster" msgid "Alamar" msgstr "Alamar" msgid "Burlock" msgstr "Burlock" msgid "Dragadune" msgstr "Dragadune" msgid "Kalindra" msgstr "Kalindra" msgid "Xabran" msgstr "Xabran" msgid "Algary" msgstr "Algary" msgid "Basenji" msgstr "Basenji" msgid "Blackfang" msgstr "Blackfang" msgid "New Dawn" msgstr "New Dawn" msgid "Sorpigal" msgstr "Sorpigal" msgid "Avone" msgstr "Avone" msgid "Big Oak" msgstr "Big Oak" msgid "Chandler" msgstr "Chandler" msgid "Erliquin" msgstr "Erliquin" msgid "Hampshire" msgstr "Hampshire" msgid "Antioch" msgstr "Antioch" msgid "Avalon" msgstr "Avalon" msgid "Roc Haven" msgstr "Roc Haven" msgid "South Mill" msgstr "South Mill" msgid "Weed Patch" msgstr "Weed Patch" msgid "Brownston" msgstr "Brownston" msgid "Hilltop" msgstr "Hilltop" msgid "Weddington" msgstr "Weddington" msgid "Westfork" msgstr "Westfork" msgid "Whittingham" msgstr "Whittingham" msgid "Cathcart" msgstr "Cathcart" msgid "Elk's Head" msgstr "Elk's Head" msgid "Roscomon" msgstr "Roscomon" msgid "Sherman" msgstr "Sherman" msgid "Yorksford" msgstr "Yorksford" msgid "Blackburn" msgstr "Blackburn" msgid "Blacksford" msgstr "Blacksford" msgid "Burton" msgstr "Burton" msgid "Pig's Eye" msgstr "Pig's Eye" msgid "Viper's Nest" msgstr "Viper's Nest" msgid "Fenton" msgstr "Fenton" msgid "Lankershire" msgstr "Lankershire" msgid "Lombard" msgstr "Lombard" msgid "Timberhill" msgstr "Timberhill" msgid "Troy" msgstr "Troy" msgid "Forder Oaks" msgstr "Forder Oaks" msgid "Meramec" msgstr "Meramec" msgid "Quick Silver" msgstr "Quick Silver" msgid "Westmoor" msgstr "Westmoor" msgid "Willow" msgstr "Willow" msgid "Corackston" msgstr "Corackston" msgid "Sheltemburg" msgstr "Sheltemburg" msgid "Cannot recruit - you already have a Hero in this town." msgstr "Kan inte rekrytera - du har redan en Hjälte i denna stad." msgid "Cannot recruit - you have too many Heroes." msgstr "Kan inte rekrytera - du har för mÃ¥nga Hjältar." msgid "Cannot afford a Hero" msgstr "Har inte rÃ¥d med en hjälte" msgid "There is no room in the garrison for this army." msgstr "Det finns ingen plats i garnisonen för denna armé." msgid "Fortifications" msgstr "Befästningar" msgid "Farm" msgstr "Farm" msgid "Thatched Hut" msgstr "Halmtäckt hydda" msgid "Archery Range" msgstr "BÃ¥gskyttebana" msgid "Upg. Archery Range" msgstr "Uppg. BÃ¥gskyttebana" msgid "Blacksmith" msgstr "Smedja" msgid "Upg. Blacksmith" msgstr "Uppg. Smedja" msgid "Armory" msgstr "Rustkammare" msgid "Upg. Armory" msgstr "Uppg. Rustkammare" msgid "Jousting Arena" msgstr "Turneringsarena" msgid "Upg. Jousting Arena" msgstr "Uppg. Turneringsarena" msgid "Cathedral" msgstr "Katedral" msgid "Upg. Cathedral" msgstr "Uppg. Katedral" msgid "Coliseum" msgstr "Kolosseum" msgid "Garbage Heap" msgstr "Kökkenmödding" msgid "Hut" msgstr "Hydda" msgid "Stick Hut" msgstr "Kvisthydda" msgid "Upg. Stick Hut" msgstr "Uppg. Kvisthydda" msgid "Den" msgstr "Lya" msgid "Adobe" msgstr "Adobe" msgid "Upg. Adobe" msgstr "Uppg. Adobe" msgid "Bridge" msgstr "Bro" msgid "Upg. Bridge" msgstr "Uppg. Bro" msgid "Pyramid" msgstr "Pyramid" msgid "Rainbow" msgstr "RegnbÃ¥ge" msgid "Crystal Garden" msgstr "KristallträdgÃ¥rd" msgid "Treehouse" msgstr "Trädhus" msgid "Cottage" msgstr "Stuga" msgid "Upg. Cottage" msgstr "Uppg. Stuga" msgid "Stonehenge" msgstr "Stonehenge" msgid "Upg. Stonehenge" msgstr "Uppg. Stonehenge" msgid "Fenced Meadow" msgstr "Inhägnad Äng" msgid "sorceress|Red Tower" msgstr "Rött Torn" msgid "Dungeon" msgstr "FängelsehÃ¥la" msgid "Waterfall" msgstr "Vattenfall" msgid "Cave" msgstr "Grotta" msgid "Crypt" msgstr "Krypta" msgid "Nest" msgstr "Näste" msgid "Maze" msgstr "Labyrint" msgid "Upg. Maze" msgstr "Uppg. Labyrint" msgid "Swamp" msgstr "Träsk" msgid "Green Tower" msgstr "Grönt Torn" msgid "warlock|Red Tower" msgstr "Rött Torn" msgid "Black Tower" msgstr "Svart Torn" msgid "Library" msgstr "Bibliotek" msgid "Orchard" msgstr "Fruktodling" msgid "Habitat" msgstr "HobhÃ¥la" msgid "Pen" msgstr "Hägn" msgid "Foundry" msgstr "Gjuteri" msgid "Upg. Foundry" msgstr "Uppg. Gjuteri" msgid "Cliff Nest" msgstr "Klippnäste" msgid "Ivory Tower" msgstr "Elfenbenstorn" msgid "Upg. Ivory Tower" msgstr "Uppg. Elfenbenstorn" msgid "Cloud Castle" msgstr "Molnslott" msgid "Upg. Cloud Castle" msgstr "Uppg. Molnslott" msgid "Storm" msgstr "Storm" msgid "Skull Pile" msgstr "Benhög" msgid "Excavation" msgstr "Utgrävning" msgid "Graveyard" msgstr "KyrkogÃ¥rd" msgid "Upg. Graveyard" msgstr "Uppg. KyrkogÃ¥rd" msgid "Upg. Pyramid" msgstr "Uppg. Pyramid" msgid "Mansion" msgstr "HerrgÃ¥rd" msgid "Upg. Mansion" msgstr "Uppg. HerrgÃ¥rd" msgid "Mausoleum" msgstr "Mausoleum" msgid "Upg. Mausoleum" msgstr "Uppg. Mausoleum" msgid "Laboratory" msgstr "Laboratorium" msgid "Shrine" msgstr "Helgedom" msgid "" "The Fortifications increase the toughness of the walls, increasing the " "number of turns it takes to knock them down." msgstr "" "Befästningar ökar dina slottsmurars styrka, ökar antal rundor det krävs för " "att riva dem." msgid "The Farm increases production of Peasants by %{count} per week." msgstr "Farmen ökar produktionen av Bönder med %{count} per vecka." msgid "" "The Coliseum provides inspiring spectacles to defending troops, raising " "their morale by two during combat." msgstr "" "Kolosseum erbjuder spännande och inspirerande gladiatorspel Ã¥t de " "försvarande trupperna, och höjer stridsmoralen med 2." msgid "The Garbage Heap increases production of Goblins by %{count} per week." msgstr "Kökkenmöddingen ökar produktionen av Vättar med %{count} per vecka." msgid "The Rainbow increases the luck of the defending units by two." msgstr "RegnbÃ¥gen ökar turen hos försvarstrupperna med 2." msgid "" "The Crystal Garden increases production of Sprites by %{count} per week." msgstr "KristallträdgÃ¥rden ökar produktionen av Féer med %{count} per vecka." msgid "The Dungeon increases the income of the town by %{count} gold per day." msgstr "FängelsehÃ¥lan ökar stadens inkomst med %{count} guld per dag." msgid "The Waterfall increases production of Centaurs by %{count} per week." msgstr "Vattenfallet ökar produktionen av Kentaurer med %{count} per vecka." msgid "" "The Library increases the number of spells in the Guild by one for each " "level of the guild." msgstr "" "Biblioteket ökar besvärjelser i Magisamfundet med ytterligare en för varje " "nivÃ¥." msgid "The Orchard increases production of Halflings by %{count} per week." msgstr "Fruktodlingen ökar produktionen av Hober med %{count} per vecka." msgid "The Storm adds +2 to the power of spells of a defending spell caster." msgstr "Stormen ökar Trollkonsten för en försvarande besvärjelsekastare med 2." msgid "The Skull Pile increases production of Skeletons by %{count} per week." msgstr "Benhögen ökar produktionen av Skelett med %{count} per vecka." msgid "Thieves' Guild" msgstr "Tjuvgille" msgid "Tavern" msgstr "Värdshus" msgid "Shipyard" msgstr "Skeppsvarv" msgid "Well" msgstr "Brunn" msgid "Statue" msgstr "Staty" msgid "Marketplace" msgstr "Marknad" msgid "Moat" msgstr "Vallgrav" msgid "Castle" msgstr "Slott" msgid "Tent" msgstr "Tält" msgid "Captain's Quarters" msgstr "Kaptensbostad" msgid "Mage Guild, Level 1" msgstr "Magisamfund, nivÃ¥ 1" msgid "Mage Guild, Level 2" msgstr "Magisamfund, nivÃ¥ 2" msgid "Mage Guild, Level 3" msgstr "Magisamfund, nivÃ¥ 3" msgid "Mage Guild, Level 4" msgstr "Magisamfund, nivÃ¥ 4" msgid "Mage Guild, Level 5" msgstr "Magisamfund, nivÃ¥ 5" msgid "" "The Shrine increases the necromancy skill of all your necromancers by 10 " "percent." msgstr "" "Helgedomen ökar SvartkonstförmÃ¥gan hos dina svartkonstnärer med 10 procent." msgid "" "The Thieves' Guild provides information on enemy players. Thieves' Guilds " "can also provide scouting information on enemy towns. Additional Guilds " "provide more information." msgstr "" "Tjuvgillet ger infomation om fiendespelare. Tjuvgillet erbjuder även " "spaningsinformation om fiendestäder. Fler Gillen ger bättre information." msgid "The Tavern increases morale for troops defending the castle." msgstr "Värdshuset höjer stridsmoralen för trupperna som försvarar slottet." msgid "The Shipyard allows ships to be built." msgstr "Skeppsvarvet bygger skepp." msgid "" "The Well increases the growth rate of all dwellings by %{count} creatures " "per week." msgstr "" "Brunnen ökar produktionen i alla boningar med %{count} varelser per vecka." msgid "The Statue increases the town's income by %{count} gold per day." msgstr "Statyn ökar stadens inkomst med %{count} guld per dag." msgid "The Left Turret provides extra firepower during castle combat." msgstr "Vänster Torn ger extra mycket eldkraft under slottstrid." msgid "The Right Turret provides extra firepower during castle combat." msgstr "Höger Torn ger extra mycket eldkraft under slottstrid." msgid "" "The Marketplace can be used to convert one type of resource into another. " "The more marketplaces you control, the better the exchange rate." msgstr "" "Marknaden används för att växla en resurs till en annan. Ju fler marknader " "du kontrollerar desto bättre växlingskurs." msgid "" "The Moat slows attacking units. Any unit entering the moat must end its turn " "there and becomes more vulnerable to attack." msgstr "" "Vallgraven saktar ner anfallande trupper. En varelse som kliver i vallgraven " "avslutar sin tur och blir lätt utsatt för ett anfall." msgid "" "The Castle improves the town's defense and increases its income to %{count} " "gold per day." msgstr "" "Slottet förbättrar stadens försvar och ökar dess inkomst till %{count} guld " "per dag." msgid "" "The Tent provides workers to build a castle, provided the materials and the " "gold are available." msgstr "" "Tältet lÃ¥ter arbetare bygga ett slott, förutsatt att det finns tillräckligt " "med guld och resurser." msgid "" "The Captain's Quarters provides a captain to assist in the castle's defense " "when no hero is present." msgstr "" "Kaptensbostaden tillÃ¥ter att en Kapten att assistera i slottets försvar när " "ingen hjälte är närvarande." msgid "" "The Mage Guild allows heroes to learn spells and replenish their spell " "points." msgstr "" "Magisamfundet lÃ¥ter hjältar att lära sig besvärjelser och Ã¥terhämta " "besvärjelsepoäng." msgid "Recruit %{name}" msgstr "Rekrytera %{name}" msgid "Month: %{month}, Week: %{week}, Day: %{day}" msgstr "MÃ¥nad: %{month}, Vecka: %{week}, Dag: %{day}" msgid "" "You must purchase a spell book to use the mage guild, but you currently have " "no room for a spell book. Try giving one of your artifacts to another hero." msgstr "" "Du mÃ¥ste köpa en Magibok för att fÃ¥ tillgÃ¥ng till Magikersamfundet. Tyvärr " "har du ingen plats för en Magibok. Försök ge en artefakt till en annan " "hjälte." msgid "Click to show next town." msgstr "Klicka för att visa nästa stad." msgid "Show next town" msgstr "Visa nästa stad" msgid "Click to show previous town." msgstr "Klicka för att visa föregÃ¥ende stad." msgid "Show previous town" msgstr "Visa föregÃ¥ende stad" msgid "This town may not be upgraded to a castle." msgstr "Denna stad kan inte uppgraderas till ett slott." msgid "Town" msgstr "Stad" msgid "Exit Castle" msgstr "Lämna Slottet" msgid "Exit Town" msgstr "Lämna Staden" msgid "Show Income" msgstr "Visa Inkomst" msgid "View Hero" msgstr "Visa Hjälte" msgid "The above spells are available here." msgstr "OvanstÃ¥ende besvärjelser är tillgängliga här." msgid "The spells the hero can learn have been added to their book." msgstr "Besvärjelser som hjälten kan lära sig har lagts till dennes bok." msgid "A generous tip for the barkeep yields the following rumor:" msgstr "Ett generöst tips frÃ¥n bartendern som uppger detta rykte:" msgid "Recruit Hero" msgstr "Rekrytera Hjälte" msgid "%{name} is a level %{value} %{race} " msgstr "%{name} är en nivÃ¥ %{value} %{race} " msgid "with %{count} artifacts." msgstr "med %{count} artefakter." msgid "with 1 artifact." msgstr "med 1 artefakt." msgid "without artifacts." msgstr "utan artefakter." msgid "Recruit %{name} the %{race}" msgstr "Rekrytera %{race}n %{name}" msgid "" "'Spread' combat formation spreads the castle's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "'Utspridd' stridsformation sprider ut hjältens trupper över hela slagfältet " "med minst ett tomt mellanrum mellan varje trupp." msgid "" "'Grouped' combat formation bunches the castle's units together in the center " "of the castle's side of the battlefield." msgstr "" "'Grupperad' stridsformation buntar ihop hjältens trupper till mitten av " "dennes sida av slagfältet." msgid "Spread Formation" msgstr "Utspridd formation" msgid "Grouped Formation" msgstr "Grupperad formation" msgid "Set garrison combat formation to 'Spread'" msgstr "Ändra granisonstruppernas stridsformation till 'Utspridd'" msgid "Set garrison combat formation to 'Grouped'" msgstr "Ändra granisonstruppernas stridsformation till 'Grupperad'" msgid "Exit Castle Options" msgstr "Lämna Slottsalternativ" msgid "Castle Options" msgstr "Slottalternativ" msgid "Not enough resources to recruit creatures." msgstr "För lite resurser för att rekrytera varelser." msgid "You are unable to recruit at this time, your ranks are full." msgstr "Du kan inte rekrytera just nu. Dina led är fulla." msgid "No creatures available for purchase." msgstr "Inga varelser tillgängliga för köp." msgid "Recruit Creatures" msgstr "Rekrytera Varelser" msgid "Max" msgstr "Maximalt" msgid "Hire all creatures in the town." msgstr "Rekrytera alla varelser i staden." msgid "Available" msgstr "Tillgängligt" msgid "Town Population Information and Statistics" msgstr "Stadsbefolkning- och Statistikinfo." msgid "Damage" msgstr "Skada" msgid "HP" msgstr "HP" msgid "Growth" msgstr "Tillväxt" msgid "week" msgstr "vecka" msgid "View World" msgstr "Visa Karta" msgid "View the entire world." msgstr "Visa hela kartan." msgid "Puzzle" msgstr "Pussel" msgid "View the obelisk puzzle." msgstr "Visa obeliskpusslet." msgid "Scenario Information" msgstr "Scenarioinformation" msgid "View information on the scenario you are currently playing." msgstr "Visa information om scenariot som du spelar." msgid "Dig for the Ultimate Artifact." msgstr "Gräv efter den Främsta Artefakten." msgid "Digging" msgstr "Gräver" msgid "Arena" msgstr "Arena" msgid "" "You enter the arena and face a pack of vicious lions. You handily defeat " "them, to the wild cheers of the crowd. Impressed by your skill, the aged " "trainer of gladiators agrees to train you in a skill of your choice." msgstr "" "Du kliver in i arenan och möter en flock lejon. Du besegrar dem utan större " "ansträngning och publiken hurrar. Den Ã¥ldrade gladiatortränaren gÃ¥r med pÃ¥ " "att träna dig i en valfri färdighet." msgid "Attack Skill" msgstr "Anfall" msgid "Defense Skill" msgstr "Försvar" msgid "Shots" msgstr "Projektiler" msgid "Shots Left" msgstr "Projektiler kvar" msgid "Hit Points" msgstr "Hälsopoäng" msgid "Hit Points Left" msgstr "Hälsopoäng kvar" msgid "You can't afford to upgrade your troops!" msgstr "Du har inte rÃ¥d att upgradera dina trupper!" msgid "" "Your troops can be upgraded, but it will cost you dearly. Do you wish to " "upgrade them?" msgstr "" "Dina trupper kan uppgraderas, men det kommer att bli dyrt. Vill du " "uppgradera dem?" msgid "Are you sure you want to dismiss this army?" msgstr "Är du säker pÃ¥ att du vill avskeda denna armé?" msgid "Upgrade" msgstr "Uppgradera" msgid "Upgrade your troops." msgstr "Uppgradera dina trupper." msgid "Dismiss" msgstr "Avskeda" msgid "Dismiss this army." msgstr "Avskeda armén." msgid "" "A group of %{monster} with a desire for greater glory wish to join you.\n" "Do you accept?" msgstr "" "En grupp %{monster} med en längtan efter ära och berömmelse önskar att göra " "dig sällskap.\n" "Accepterar du?" msgid "Followers" msgstr "Anhängare" msgid "" "The %{monster} is swayed by your diplomatic tongue, and offers to join your " "army for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "Alla %{monster} vill förena sig med din armé för en summa pÃ¥ %{gold} guld.\n" "Accepterar du det?" msgid "" "The creatures are swayed by your diplomatic\n" "tongue, and make you an offer:\n" "\n" msgstr "" "Varelserna är tagna av din diplomatiska förmÃ¥ga\n" "och kommer med ett förslag:\n" "\n" msgid "" "%{offer} of the %{total} %{monster} will join your army, and the rest will " "leave you alone, for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "%{offer} av %{total} %{monster} förenar sig med din armé, resten lämnar dig " "ifred, för en summa pÃ¥ %{gold} guld.\n" "Accepterar du det?" msgid "" "All %{offer} of the %{monster} will join your army for the sum of %{gold} " "gold.\n" "Do you accept?" msgstr "" "Alla %{offer} %{monster} vill förena sig med din armé för en summa pÃ¥ " "%{gold} guld.\n" "Accepterar du det?" msgid "(Rate: %{percent})" msgstr "(Andel: %{percent} procent)" msgid "off" msgstr "av" msgid "Music" msgstr "Musik" msgid "Effects" msgstr "Effekter" msgid "MIDI" msgstr "MIDI" msgid "MIDI Expansion" msgstr "MIDI-expansion" msgid "External" msgstr "Extern" msgid "Music Type" msgstr "Musiktyp" msgid "3D Audio" msgstr "3D-ljud" msgid "Toggle ambient music level." msgstr "Välj ljudnivÃ¥ för bakgrundsmusik." msgid "Toggle foreground sounds level." msgstr "Välj ljudnivÃ¥ för ljudeffekter." msgid "Change the type of music." msgstr "Ändra musiktyp." msgid "Toggle 3D effects of foreground sounds." msgstr "Aktivera 3D-funktion för ljudeffekter." msgid "Build a new ship:" msgstr "Bygg ett nytt skepp:" msgid "Resource cost:" msgstr "Resurskostnad:" msgid "Total: " msgstr "Totalt: " msgid "Need: " msgstr "Behöver: " msgid "Load Game" msgstr "Ladda Spel" msgid "No save files to load." msgstr "Inga sparfiler att ladda." msgid "New Game" msgstr "Nytt Spel" msgid "Start a single or multi-player game." msgstr "Starta ett spel med en eller flera spelare." msgid "Load a previously saved game." msgstr "Ladda ett tidigare sparat spel." msgid "Save Game" msgstr "Spara Spel" msgid "Save the current game." msgstr "Spara nuvarande spel." msgid "Quit" msgstr "Avsluta" msgid "Quit out of Heroes of Might and Magic II." msgstr "Avsluta Heroes of Might and Magic II." msgid "Language" msgstr "SprÃ¥k" msgid "Graphics" msgstr "Grafik" msgid "Black & White" msgstr "Svart & Vitt" msgid "Mouse Cursor" msgstr "Muspekare" msgid "Color" msgstr "Färg" msgid "Text Support" msgstr "Texthjälp" msgid "Change the language of the game." msgstr "Ändra spelets sprÃ¥k" msgid "Select Game Language" msgstr "Välj spelsprÃ¥k" msgid "Change the graphics settings of the game." msgstr "Ändra spelets grafikinställningar" msgid "Toggle colored cursor on or off. This is only an aesthetic choice." msgstr "" "Välj färglagd eller svartvit muspekare. Detta är bara ett estetiskt val." msgid "" "Toggle text support mode to output extra information about windows and " "events in the game." msgstr "" "Aktivera textstöd för att visa ytterligare information om fönster och " "händelser i spelet." msgid "" "Map\n" "Difficulty" msgstr "" "Kartans\n" "SvÃ¥righet" msgid "" "Game\n" "Difficulty" msgstr "" "Spelets\n" "SvÃ¥righet" msgid "Rating" msgstr "Rang" msgid "Map Size" msgstr "Kartstorlek" msgid "Opponents" msgstr "MotstÃ¥ndare" msgid "Class" msgstr "Klass" msgid "" "Victory\n" "Conditions" msgstr "" "Seger-\n" "villkor" msgid "" "Loss\n" "Conditions" msgstr "" "Förlust-\n" "villkor" msgid "First select recipients!" msgstr "Först välj mottagare!" msgid "You cannot select %{resource}!" msgstr "Du kan inte välja %{resource}!" msgid "Select count %{resource}:" msgstr "Välj antalet %{resource}:" msgid "Select Recipients" msgstr "Välj mottagare" msgid "Your Funds" msgstr "Dina Resurser" msgid "Planned Gift" msgstr "GÃ¥va" msgid "Gift from %{name}" msgstr "GÃ¥va frÃ¥n %{name}" msgid "Resolution" msgstr "Upplösning" msgid "Fullscreen" msgstr "Fullskärm" msgid "window|Mode" msgstr "Fönsterläge" msgid "Windowed" msgstr "Fönsterläge" msgid "V-Sync" msgstr "V-Sync" msgid "on" msgstr "pÃ¥" msgid "FPS" msgstr "FPS" msgid "System Info" msgstr "Systeminfo." msgid "Change the resolution of the game." msgstr "Ändra spelets upplösning." msgid "Select Game Resolution" msgstr "Välj spelupplösning" msgid "Toggle between fullscreen and windowed modes." msgstr "Växla mellan fullskärmsläge och fönsterläge." msgid "" "The V-Sync option can be enabled to resolve flickering issues on some " "monitors." msgstr "V-Sync kan aktiveras för att lösa flimmerproblem pÃ¥ vissa skärmar." msgid "Show extra information such as FPS and current time." msgstr "Visa extra information som FPS och nuvarande tid." msgid "Hotkey: " msgstr "Snabbtangent: " msgid "Category: " msgstr "Kategori: " msgid "Event: " msgstr "Händelse: " msgid "Hot Keys:" msgstr "Snabbtangenter:" msgid "Evil" msgstr "Ond" msgid "Good" msgstr "God" msgid "Interface Type" msgstr "Gränssnittsstil" msgid "Hide" msgstr "Dölj" msgid "Show" msgstr "Visa" msgid "Interface" msgstr "Gränssnitt" msgid "Slow" msgstr "LÃ¥ngsam" msgid "Normal" msgstr "Normal" msgid "Fast" msgstr "Snabb" msgid "Very Fast" msgstr "Mkt. snabb" msgid "Scroll Speed" msgstr "" "Skroll-\n" "hastighet" msgid "Toggle the type of interface you want to use." msgstr "Välj vilket gränssnitt du vill använda." msgid "Toggle interface visibility." msgstr "Aktivera gränssnittsynlighet." msgid "Toggle colored cursor on or off. This is only an esthetic choice." msgstr "Välj färglagd eller svartvit muspekare." msgid "Sets the speed at which you scroll the window." msgstr "Välj skrollhastigheten." msgid "Select Game Language:" msgstr "Välj spelsprÃ¥k:" msgid "Click to choose the selected language." msgstr "Klicka för att anta valt sprÃ¥k." msgid "%{name} has gained a level." msgstr "%{name} har gÃ¥tt upp en nivÃ¥." msgid "%{skill} +1" msgstr "%{skill} +1" msgid "You have learned %{skill}." msgstr "Du har lärt dig %{skill}." msgid "" "%{name} has gained a level.\n" "\n" "%{skill} +1" msgstr "" "%{name} har gÃ¥tt upp en nivÃ¥.\n" "\n" "%{skill} +1" msgid "" "You may learn either:\n" "%{skill1}\n" "or\n" "%{skill2}" msgstr "" "Du kan lära dig antingen:\n" "%{skill1}\n" "eller\n" "%{skill2}" msgid "" "Please inspect our fine wares. If you feel like offering a trade, click on " "the items you wish to trade with and for." msgstr "" "Var god granska vÃ¥ra fina varor. Om du vill köpa nÃ¥gonting, klickar du pÃ¥ " "föremÃ¥let du vill köpa med och för." msgid "" "You have received quite a bargain. I expect to make no profit on the deal. " "Can I interest you in any of my other wares?" msgstr "" "Du har fÃ¥tt ett riktigt fynd. Jag förväntar mig inte att göra nÃ¥gon vinst pÃ¥ " "affären. Kan jag intressera dig med nÃ¥gra andra varor?" msgid "I can offer you %{count} for 1 unit of %{resfrom}." msgstr "Jag kan erbjuda dig %{count} för 1 %{resfrom}." msgid "I can offer you 1 unit of %{resto} for %{count} units of %{resfrom}." msgstr "Jag kan erbjuda dig 1 %{resto} för %{count} %{resfrom}." msgid "Min" msgstr "Min" msgid "Qty to trade" msgstr "Byt antal" msgid "Trading Post" msgstr "Handelsstation" msgid "Your Resources" msgstr "Dina resurser" msgid "Available Trades" msgstr "Tillgänglig handel" msgid "n/a" msgstr "Inga" msgid "guarded by %{count} %{monster}" msgstr "vaktas av %{count} %{monster}" msgid "guarded by " msgstr "vaktas av " msgid "(available: %{count})" msgstr "(tillgängligt: %{count})" msgid "(empty)" msgstr "(tom)" msgid "already learned" msgstr "redan inlärt" msgid "treeOfKnowledge|free" msgstr "Gratis" msgid "already claimed" msgstr "redan inlärt" msgid "not claimed" msgstr "inte inlärt" msgid "already knows this skill" msgstr "kan redan denna färdighet" msgid "already has max skills" msgstr "har redan maximalt antal färdigheter" msgid "(already visited)" msgstr "(redan besökt)" msgid "(not visited)" msgstr "(inte besökt)" msgid "%{color} Barrier" msgstr "%{color} Barriär" msgid "(tent visited)" msgstr "(tält besökt)" msgid "%{color} Tent" msgstr "%{color} Tält" msgid "Road" msgstr "Väg" msgid "(digging ok)" msgstr "(grävning okej)" msgid "(no digging)" msgstr "(ingen grävning)" msgid "penalty: %{cost}" msgstr "straff: %{cost}" msgid "Defenders:" msgstr "Försvarare:" msgid "Unknown" msgstr "Okänd" msgid "%{name} (Level %{level})" msgstr "%{name} (nivÃ¥ %{level})" msgid "Attack:" msgstr "Anfall:" msgid "Defense:" msgstr "Försvar:" msgid "Spell Power:" msgstr "Trollkonst:" msgid "Knowledge:" msgstr "Kunskap:" msgid "Spell Points:" msgstr "Besvärjelsepoäng:" msgid "Move Points:" msgstr "Steg:" msgid "Uncharted Territory" msgstr "Outforskat OmrÃ¥de" msgid "Cost per troop:" msgstr "Kostnad per trupp:" msgid "Available: %{count}" msgstr "Tillgängligt: %{count}" msgid "Number to buy:" msgstr "Antal att köpa:" msgid "Recruit selected monsters." msgstr "Rekrytera valda monster." msgid "Select maximum monsters to be recruited." msgstr "Välj maximalt antal monster att rekrytera." msgid "Select only 1 monster to be recruited." msgstr "Välj endast 1 monster att rekrytera." msgid "Select Game Resolution:" msgstr "Ange Spelupplösning:" msgid "Click to apply the selected resolution." msgstr "Klicka här för att anta den valda upplösningen." msgid "Click to apply the entered text." msgstr "Klicka här för att anta den inmatade texten." msgid "Click to open the Virtual Keyboard dialog." msgstr "Klicka här för att öppna det virtuella tangentbordet." msgid "Open Virtual Keyboard" msgstr "Öppna virtuellt tangentbord." msgid "How many troops to move?" msgstr "Antal trupper att flytta?" msgid "Fast separation into slots:" msgstr "Snabbseparering till platser:" msgid "Map: " msgstr "Karta: " msgid "" "\n" "\n" "Month: " msgstr "" "\n" "\n" "MÃ¥nad: " msgid ", Week: " msgstr ", Vecka: " msgid ", Day: " msgstr ", Dag: " msgid "" "\n" "\n" "Location: " msgstr "" "\n" "\n" "Plats: " msgid "Click to save the current game." msgstr "Klicka för att spara nuvarande spel." msgid "Click to load a previously saved game." msgstr "Klicka för att ladda ett tidigare sparat spel." msgid "Are you sure you want to delete file:" msgstr "Är du säker pÃ¥ att du vill radera filen:" msgid "Warning!" msgstr "Varning!" msgid "File to Save:" msgstr "Fil att Spara:" msgid "File to Load:" msgstr "Fil att Ladda:" msgid "Accept the choice made." msgstr "Acceptera alla ändringar." msgid "%{color} %{race} hero" msgstr "%{color} %{race}hjälte" msgid "Terrain object" msgstr "TerrängföremÃ¥l" msgid "Select Skill:" msgstr "Välj FörmÃ¥ga:" msgid "Select Spell:" msgstr "Välj Besvärjelse" msgid "Select Artifact:" msgstr "Välj Artefakt:" msgid "Select Monster:" msgstr "Välj Monster:" msgid "Select Hero:" msgstr "Välj Hjälte:" msgid "Select Treasure:" msgstr "Välj Skatt:" msgid "Select Ocean Object:" msgstr "Välj HavsföremÃ¥l:" msgid "Castle/town placing" msgstr "Slott/stadsplacering" msgid "doubleLinedRace|Neutral" msgstr "Neutral" msgid "race|Random" msgstr "Slumpad" msgid "Click to start placing the selected castle/town." msgstr "Klicka här för att placera det valda slottet/staden." msgid "Click to select town placing." msgstr "Klicka för att välja stadsplacering." msgid "Click to select castle placing." msgstr "Klicka här för att välja slottplacering." msgid "%{color} %{race} %{townOrCastle}" msgstr "%{color} %{race} %{townOrCastle}" msgid "race|Neutral" msgstr "Neutral" msgid "You will place" msgstr "Du kommer placera" msgid "Click to select this class." msgstr "Klicka här för att välja denna klass." msgid "Click to select this color." msgstr "Klicka här för att välja denna färgen." msgid "Select Dwelling:" msgstr "Välj Bostad:" msgid "Select Landscape Object:" msgstr "Välj LandskapsföremÃ¥l:" msgid "Mine placing" msgstr "Gruvplacering" msgid "" "Resource\n" "type:" msgstr "Resurstyp:" msgid "%{mineName} appearance:" msgstr "%{mineName} utseende:" msgid "Click to select %{object} as the resource generator to be placed." msgstr "Klicka här för att välja %{object} som objekt att placera." msgid "Select Mountain Object:" msgstr "Välj BergsföremÃ¥l:" msgid "Select Rock Object:" msgstr "Välj BlockföremÃ¥l:" msgid "Select Tree Object:" msgstr "Välj TrädföremÃ¥l:" msgid "Select Power Up Object:" msgstr "Välj KraftökningsföremÃ¥l:" msgid "Select Adventure Object:" msgstr "Välj ÄventyrsföremÃ¥l:" msgid "Map Type:\n" msgstr "Karttyp:\n" msgid "The Succession Wars" msgstr "Successionskrigen" msgid "The Price of Loyalty" msgstr "Lojalitetens Pris" msgid "Resurrection" msgstr "Resurrection" msgid "Lose all your heroes and towns." msgstr "Förlora alla dina hjältar och städer." msgid "Lose a specific town." msgstr "Förlora en viss stad." msgid "Lose a specific hero." msgstr "Förlora en viss hjälte." msgid "Run out of time. Fail to win by a certain point." msgstr "FÃ¥ slut pÃ¥ tid. Vinn innan en viss tidpunkt." msgid "Loss Condition" msgstr "Förlustvillkor" msgid "Defeat all enemy heroes and towns." msgstr "Besegra alla fiendehjältar och städer." msgid "Capture a specific town." msgstr "Erövra en viss stad." msgid "Defeat a specific hero." msgstr "Besegra en viss hjälte." msgid "Find a specific artifact." msgstr "Hitta en viss artefakt." msgid "Your side defeats the opposing side." msgstr "Din sida besegrar motstÃ¥ndarsidan." msgid "Accumulate a large amount of gold." msgstr "Samla ihop en stor summa guld." msgid "Victory Condition" msgstr "Segervillkor" msgid "Map difficulty:" msgstr "KartsvÃ¥righet:" msgid "N" msgstr "N" msgid "No maps exist at that size" msgstr "Finns inga kartor av den storleken" msgid "Small Maps" msgstr "SmÃ¥ Kartor" msgid "View only maps of size small (36 x 36)." msgstr "Visa bara kartor med storleken (36 x 36)." msgid "Medium Maps" msgstr "Medelstora Kartor" msgid "View only maps of size medium (72 x 72)." msgstr "Visa bara kartor med storleken (72 x 72)." msgid "Large Maps" msgstr "Stora Kartor" msgid "View only maps of size large (108 x 108)." msgstr "Visa bara kartor med storleken (108 x 108)." msgid "Extra Large Maps" msgstr "Extrastora Kartor" msgid "View only maps of size extra large (144 x 144)." msgstr "Visa bara kartor med storleken (144 x 144)." msgid "All Maps" msgstr "Alla Kartor" msgid "View all maps, regardless of size." msgstr "Visa alla kartor, oavsett storlek." msgid "Players Icon" msgstr "Spelarikon" msgid "" "Indicates how many players total are in the scenario. Any positions not " "occupied by humans will be occupied by computer players." msgstr "" "Anger hur mÃ¥nga spelare totalt det är i kartan. Platser som inte upptas av " "en människa kommer spelas av datorkontrollerade spelare." msgid "" "Indicates whether the map\n" "is small (36 x 36), medium\n" "(72 x 72), large (108 x 108),\n" "or extra large (144 x 144)." msgstr "" "Anger om kartan är liten (36 x 36),\n" "medel (72 x 72), stor (108 x 108)\n" "eller extrastor (144 x 144)." msgid "Size Icon" msgstr "Ikonstorlek" msgid "" "Indicates whether the map is made for \"The Succession Wars\" or \"The Price " "of Loyalty\" version of the game." msgstr "" "Visar om kartan är gjord för \"The Succession Wars\" eller \n" "expansionen \"The Price of Loyalty\"." msgid "Map Type" msgstr "Karttyp" msgid "Selected Name" msgstr "Valt Namn" msgid "The name of the currently selected map." msgstr "Den valda kartans namn." msgid "Selected Map Difficulty" msgstr "Vald kartsvÃ¥righet" msgid "" "The map difficulty of the currently selected map. The map difficulty is " "determined by the scenario designer. More difficult maps might include more " "or stronger enemies, fewer resources, or other special conditions making " "things tougher for the human player." msgstr "" "SvÃ¥righetsgraden för den valda kartan. Kartans svÃ¥righet bestäms av kartans " "kreatör. SvÃ¥rare kartor kan innehÃ¥ller fler och/eller starkare fienden, " "färre resurser eller andra specialvillkor som gör det svÃ¥rare för spelaren." msgid "Selected Description" msgstr "Vald beskrivning" msgid "The description of the currently selected map." msgstr "Den valda kartans beskrivning." msgid "Jump" msgstr "Hoppa" msgid "Hero Speed" msgstr "" "Hjälte-\n" "hastighet" msgid "Don't Show" msgstr "Visa ej" msgid "Enemy Speed" msgstr "" "Fiende-\n" "hastighet" msgid "Auto Resolve" msgstr "Snabbstrid" msgid "Auto, No Spells" msgstr "" "Snabbstrid, \n" "Inga Besvärjelser" msgid "Battles" msgstr "Strider" msgid "autoBattle|Manual" msgstr "Manuell" msgid "Change the speed at which your heroes move on the main screen." msgstr "Ändra hastigheten för dina hjältars rörelser pÃ¥ kartan." msgid "" "Sets the speed that A.I. heroes move at. You can also elect not to view A.I. " "movement at all." msgstr "" "Välj hastigheten för datorns rörelser pÃ¥ kartan. Du kan välja inte visa " "datorns rörelser alls." msgid "Change the interface settings of the game." msgstr "Ändra spelets gränsnittsinställningar." msgid "Interface Settings" msgstr "Gränssnittsinställningar" msgid "Toggle instant battle mode." msgstr "Aktivera Snabbstrid." msgid "Att." msgstr "Anfall" msgid "Def." msgstr "Försv." msgid "Power" msgstr "Trollk." msgid "Knowl" msgstr "Kunskap" msgid "1st" msgstr "1:a" msgid "2nd" msgstr "2:a" msgid "3rd" msgstr "3:e" msgid "4th" msgstr "4:e" msgid "5th" msgstr "5:e" msgid "6th" msgstr "6:e" msgid "Oracle: Player Rankings" msgstr "Orakel: Spelarrang" msgid "Thieves' Guild: Player Rankings" msgstr "Tjuvgille: Spelarrang" msgid "Number of Towns:" msgstr "Antal Städer:" msgid "Number of Castles:" msgstr "Antal Slott:" msgid "Number of Heroes:" msgstr "Antal Hjältar:" msgid "Gold in Treasury:" msgstr "Guld i Skattkammaren:" msgid "Wood & Ore:" msgstr "Trä & Malm:" msgid "Gems, Cr, Slf & Mer:" msgstr "Andra Resurser:" msgid "Obelisks Found:" msgstr "Funna Obelisker:" msgid "Artifacts:" msgstr "Artefakter:" msgid "Total Army Strength:" msgstr "Total Arméstyrka:" msgid "Income:" msgstr "Inkomst:" msgid "Best Hero:" msgstr "Bästa Hjälte:" msgid "Best Hero Stats:" msgstr "Status för Bästa hjälte:" msgid "Personality:" msgstr "Personlighet:" msgid "Best Monster:" msgstr "Bästa Varelse:" msgid "Map filename" msgstr "Kartfilnamn" msgid "" "Are you sure you want to load a new map? (Any unsaved changes to the current " "map will be lost.)" msgstr "" "Är du säker pÃ¥ att du vill ladda en ny karta? (Osparade ändringar för din " "nuvarande karta kommer försvinna.)" msgid "" "Are you sure you want to create a new map? (Any unsaved changes to the " "current map will be lost.)" msgstr "" "Är du säker pÃ¥ att du vill skapa en ny karta? (Osparade ändringar för din " "nuvarande karta kommer försvinna.)" msgid "" "Create a new map, either from scratch or using the random map generator." msgstr "" "Skapa ny karta, antingen en blank karta eller genom att använda " "kartgeneratorn." msgid "New Map" msgstr "Ny Karta" msgid "Load Map" msgstr "Ladda Karta" msgid "Load an existing map." msgstr "Laddar en karta." msgid "Save Map" msgstr "Spara Karta" msgid "Save the current map." msgstr "Spara nuvarande karta." msgid "Quit out of the map editor." msgstr "Lämna kartredigeraren." msgid "Objects cannot be placed outside the map." msgstr "FöremÃ¥l kan inte placeras utanför kartan." msgid "Monsters cannot be placed on water." msgstr "Varelser kan inte placeras pÃ¥ vatten." msgid "Choose a tile which does not contain any objects." msgstr "Välj en ruta som inte har nÃ¥gra objekt." msgid "Treasures cannot be placed on water." msgstr "Skatter kan inte placeras pÃ¥ vatten." msgid "Heroes cannot be placed on water." msgstr "Hjältar kan inte placeras pÃ¥ vatten." msgid "" "A maximum of %{count} heroes of the same color can be placed on the map." msgstr "Maximalt %{count} hjältar med samma färg kan placeras pÃ¥ kartan." msgid "Artifacts cannot be placed on water." msgstr "Artefakter kan inte placeras pÃ¥ vatten." msgid "Mountains cannot be placed on water." msgstr "Berg kan inte placeras pÃ¥ vatten." msgid "Rocks cannot be placed on water." msgstr "Block kan inte placeras pÃ¥ vatten." msgid "Trees cannot be placed on water." msgstr "Träd kan inte placeras pÃ¥ vatten." msgid "Ocean object must be placed on water." msgstr "HavsföremÃ¥l mÃ¥ste placeras pÃ¥ vatten." msgid "Landscape objects cannot be placed on water." msgstr "LandskapsföremÃ¥l kan inte placeras pÃ¥ vatten." msgid "Towns cannot be placed on water." msgstr "Städer kan inte placeras pÃ¥ vatten." msgid "Mines cannot be placed on water." msgstr "Gruvor kan inte placeras pÃ¥ vatten." msgid "Dwellings cannot be placed on water." msgstr "Bostäder kan inte placeras pÃ¥ vatten." msgid "Power-ups cannot be placed on water." msgstr "Kraftökningar kan inte placeras pÃ¥ vatten." msgid "Adventure objects cannot be placed on water." msgstr "ÄventyrsföremÃ¥l kan inte placeras pÃ¥ vatten." msgid "Used to place %{object}." msgstr "Används för att placera %{object}." msgid "Select object type" msgstr "Välj objekttyp" msgid "" "Click here to\n" "select a monster." msgstr "" "Klicka här för att\n" "välja en varelse." msgid "" "Click here to\n" "select another monster." msgstr "" "Klicka här för att\n" " \n" "välja en annan varelse." msgid "" "Cell\n" "Details" msgstr "" "Cell-\n" "detaljer" msgid "Roads" msgstr "Vägar" msgid "Streams" msgstr "Floder" msgid "Erase" msgstr "Radera" msgid "Mountains" msgstr "Berg" msgid "Rocks" msgstr "Block" msgid "Trees" msgstr "Träd" msgid "Water Objects" msgstr "HavsföremÃ¥l" msgid "Miscellaneous" msgstr "Diverse" msgid "Artifacts" msgstr "Artefakter" msgid "Dwellings" msgstr "Bostäder" msgid "Mines" msgstr "Gruvor" msgid "Power-ups" msgstr "Kraftökningar" msgid "Treasures" msgstr "Skatter" msgid "Towns" msgstr "Städer" msgid "Heroes" msgstr "Hjältar" msgid "editorErasure|Terrain" msgstr "" msgid "editorErasure|Castles" msgstr "" msgid "editorErasure|Monsters" msgstr "" msgid "editorErasure|Heroes" msgstr "" msgid "editorErasure|Artifacts" msgstr "" msgid "editorErasure|Roads" msgstr "" msgid "editorErasure|Streams" msgstr "" msgid "editorErasure|Treasures" msgstr "" msgid "" "Draws terrain in\n" "%{size} by %{size} square increments." msgstr "" "Ritar terräng i\n" "rutstorlek om %{size} gÃ¥nger %{size}." msgid "" "Erases objects in\n" "%{size} by %{size} square increments." msgstr "" "Raderar terräng i\n" "rutstorlek om %{size} gÃ¥nger %{size}." msgid "Small Brush" msgstr "Liten Pensel" msgid "Medium Brush" msgstr "Medelstor Pensel" msgid "Large Brush" msgstr "Stor Pensel" msgid "Area Fill" msgstr "Fyll OmrÃ¥de" msgid "Used to click and drag for filling in large areas." msgstr "Används för att klicka och dra för att fylla stora omrÃ¥den." msgid "Clear Area" msgstr "Rensa OmrÃ¥de" msgid "Used to click and drag for clearing large areas." msgstr "Används för att klicka och dra för rensa stora omrÃ¥den." msgid "" "Costs %{rate} times normal movement for all heroes. (Pathfinding reduces or " "eliminates the penalty.)" msgstr "" "Kostar %{rate} gÃ¥nger normal förflyttning för alla hjältar. (Vägvisning " "sänker eller tar bort handikappet.)" msgid "Traversable only by boat." msgstr "Endast farbar med skepp." msgid "No special modifiers." msgstr "Ger inget förflyttningsstraff." msgid "Toggle the erasure of %{type} objects." msgstr "Aktivera raderingen av %{type}objekt." msgid "" "Objects of this type will be deleted with the Erase tool. Left-click here to " "deselect this type. Press and hold this button to deselect all other object " "types." msgstr "" "Objekt av denna typ kommer raderas med Raderingsverktyget. Vänsterklicka här " "för att avmarkera denna objekttyp. Tryck och hÃ¥ll denna knapp för att " "avmarkera alla andra objekttyper." msgid "" "Objects of this type will NOT be deleted with the Erase tool. Left-click " "here to select this type. Press and hold this button to select all other " "object types." msgstr "" "Objekt av denna typ kommer INTE att raderas med Raderingsverktyget. " "Vänsterklicka här för att markera denna objekttyp. Tryck och hÃ¥ll denna " "knapp för att markera alla andra objektyper." msgid "Terrain Mode" msgstr "Terrängläge" msgid "Used to draw the underlying grass, dirt, water, etc. on the map." msgstr "Används för att rita terrängen sÃ¥ som gräs, jord, hav osv pÃ¥ kartan." msgid "Landscape Objects Mode" msgstr "Landskapsobjektsläge" msgid "" "Used to place landscape objects (mountains, rocks, trees, etc.) on the map." msgstr "" "Används för att placera landskapsföremÃ¥l (berg, block, träd osv) pÃ¥ kartan." msgid "Detail Mode" msgstr "Detaljläge" msgid "Used for special editing of monsters, heroes and towns." msgstr "Används för att redigera varelser, hjältar och städer." msgid "Adventure Objects Mode" msgstr "Äventyrsobjektsläge" msgid "" "Used to place adventure objects (artifacts, dwellings, mines, treasures, " "etc.) on the map." msgstr "" "Används för att placera äventyrsföremÃ¥l (artefakter, boningar, gruvor, " "skatter osv) pÃ¥ kartan." msgid "Kingdom Objects Mode" msgstr "RikesföremÃ¥lsläge" msgid "Used to place kingdom objects (towns, castles and heroes) on the map." msgstr "" "Används för att placera RikesföremÃ¥l (städer, slott och hjältar) pÃ¥ kartan." msgid "Monsters Mode" msgstr "Varelseläge" msgid "Used to place monsters on the map." msgstr "Används för att placera varelser pÃ¥ kartan." msgid "Allows you to draw streams by clicking and dragging." msgstr "Används för att dra floder genom att klicka och dra." msgid "Stream Mode" msgstr "Flodläge" msgid "Allows you to draw roads by clicking and dragging." msgstr "Används för att dra vägar genom att klicka och dra." msgid "Road Mode" msgstr "Vägläge" msgid "Erase Mode" msgstr "Raderarläge" msgid "Used to erase objects from the map." msgstr "Används för att radera föremÃ¥l frÃ¥n kartan." msgid "Change between zoom and normal view." msgstr "Ändra mellan inzoomad vy eller normal vy." msgid "Magnify" msgstr "Förstora" msgid "Undo" msgstr "Ã…ngra" msgid "Undo your last action." msgstr "Ã…ngra din sista Ã¥tgärd." msgid "Create a new map either from scratch or using the random map generator." msgstr "Skapa en ny blank karta eller en slumpad karta med kartgeneratorn." msgid "Edit map title, description, and other general information." msgstr "Redigera karttiteln, beskrivning och annan information." msgid "Specifications" msgstr "Specifikationer" msgid "File Options" msgstr "Filalternativ" msgid "" "Open the file options menu, where you can save or load maps, or quit out of " "the editor." msgstr "" "Tar fram filalternativsmenyn som lÃ¥ter dig spara eller ladda kartor eller " "lämna kartredigeraren." msgid "View the editor system options, which let you customize the editor." msgstr "" "Tar fram systemalternativsmenyn som lÃ¥ter dig skräddarsy kartredigeraren." msgid "Create a map that is %{size} squares wide and %{size} squares high." msgstr "Skapa en karta som är %{size} rutor bred och %{size} rutor hög." msgid "Cancel back to the New Map menu." msgstr "Avbryt och gÃ¥ tillbaka till Ny Karta-menyn." msgid "Cancel back to the main menu." msgstr "Avbryt och gÃ¥ tillbaka till huvudmenyn." msgid "From Scratch" msgstr "Blank karta" msgid "Start from scratch with a blank map." msgstr "Starta med en blank karta." msgid "Create a randomly generated map." msgstr "Skapa en slumpmässigt genererad karta." msgid "Random" msgstr "Slumpad" msgid "Cancel back to the Map Editor main menu." msgstr "Avbryt och gÃ¥ tillbaka till kartredigerarens huvudmeny." msgid "No maps available!" msgstr "Inga kartor tillgängliga!" msgid "Warning" msgstr "Varning" msgid "difficulty|Easy" msgstr "Lätt" msgid "difficulty|Normal" msgstr "Normal" msgid "difficulty|Hard" msgstr "SvÃ¥r" msgid "difficulty|Expert" msgstr "Professionell" msgid "difficulty|Impossible" msgstr "Omöjligt" msgid "and more..." msgstr "och mer..." msgid "Easy" msgstr "Lätt" msgid "Hard" msgstr "SvÃ¥r" msgid "Campaign Difficulty" msgstr "KampanjsvÃ¥righet" msgid "" "Choose this difficulty if you want to prefer game story over challenge. AI " "is weaker in comparison with normal difficulty." msgstr "" "Välj denna svÃ¥righetsgrad om du föredrar att följa historien framför " "utmaningen. Datorn är svagare i jämförelse med normal svÃ¥righetsgrad." msgid "" "Choose this difficulty to enjoy the campaign as per the original design." msgstr "" "Välj denna svÃ¥righetsgrad för att uppleva kampanjen som den är tänkt att " "upplevas." msgid "" "Choose this difficulty if you want challenge. AI is stronger in comparison " "with normal difficulty." msgstr "" "Välj denna svÃ¥righetsgrad om du vill ha en utmaning. Datorn är starka i " "jämförelse med normal svÃ¥righetsgrad." msgid "" "Congratulations!\n" "\n" "Days: %{days}\n" msgstr "" "Gratulerar!\n" "\n" "Dagar: %{days}\n" msgid "" "\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" "\n" "SvÃ¥righetsgrad: %{difficulty}\n" "\n" msgid "" "Score: %{score}\n" "\n" "Rating:\n" "%{rating}" msgstr "" "Poängställning: %{score}\n" "\n" "Rang:\n" "%{rating}" msgid "Start the selected scenario." msgstr "PÃ¥börja valt scenario." msgid "View Intro" msgstr "Visa Intro" msgid "View Intro videos for the current state of the campaign." msgstr "Visa introvideor för nuvarande kampanjkarta." msgid "" "Select the campaign difficulty. This can be lowered at any point during the " "campaign." msgstr "" "Välj kampanjsvÃ¥righet. SvÃ¥righetsgraden kan sänkas senare under kampanjen." msgid "Restart the current scenario." msgstr "Omstarta nuvarande scenario." msgid "Difficulty" msgstr "SvÃ¥righetsgrad" msgid "" "You have changed to a lower difficulty for the campaign. You will not be " "able to revert this after this point. The high score will be calculated " "based solely on the new difficulty. Do you want to proceed?" msgstr "" "Du har sänkt svÃ¥righetsgraden för kampanjen. Du kan inte ändra tillbaka " "senare. Poängställningen kommer enbart beräknas med den nya " "svÃ¥righetsgraden. Vill du fortsätta?" msgid "Are you sure you want to restart this scenario?" msgstr "Är du säker pÃ¥ att du vill omstarta detta scernaiot?" msgid "Campaign Scenario loading failure" msgstr "Ladda Kampanjscenariot misslyckades" msgid "Please make sure that campaign files are correct and present." msgstr "Kolla om Kampanjfilerna finns och är korrekta." msgid "Days spent" msgstr "GÃ¥ngna dagar" msgid "The number of days spent on this campaign." msgstr "Antal gÃ¥ngna dagar för denna kampanj." msgid "Project Coordination and Core Development" msgstr "Projektkoordination och huvudutveckling" msgid "Development" msgstr "Utveckling" msgid "Visit us at " msgstr "Besök oss pÃ¥ " msgid "QA and Support" msgstr "Kvalitétssäkring och Support" msgid "Dev and Support" msgstr "Utveckling och Support" msgid "Special Thanks to" msgstr "Extra Tack till" msgid "and many other contributors!" msgstr "och mÃ¥nga andra som bidragit!" msgid "and many-many other supporters!" msgstr "och mÃ¥nga mÃ¥nga andra som gett sitt stöd!" msgid "Support us at" msgstr "Stöd oss pÃ¥" msgid "local-donation-platform|https://www.patreon.com/fheroes2" msgstr "https://www.patreon.com/fheroes2" msgid "Connect with us at" msgstr "Kontakta oss pÃ¥" msgid "local-social-network|https://www.facebook.com/groups/fheroes2" msgstr "https://www.facebook.com/groups/fheroes2" msgid "Need help with the game?" msgstr "Behöver du hjälp med spelet?" msgid "Original project before 0.7" msgstr "Orginalprojekt före version 0.7" msgid "Heroes of Might and Magic II: The Succession Wars team" msgstr "Heroes of Might and Magic II: The Succession Wars teamet" msgid "Designed and Directed" msgstr "Design och Regi" msgid "Programming and Design" msgstr "Programmering och Formgivning" msgid "Executive Producer" msgstr "Chefsproducent" msgid "Producer" msgstr "Producent" msgid "Additional Design" msgstr "Övrig Formgivning" msgid "Additional Programming" msgstr "Övrig Programmering" msgid "Musical Production" msgstr "Musikproduktion" msgid "Music and Sound Design" msgstr "Musik och Ljuddesign" msgid "Vocalists" msgstr "Vocalister" msgid "Art Director" msgstr "Konstnärlig Ledare" msgid "Assistant Art Director" msgstr "Assisterande Konstnärlig Ledare" msgid "Artists" msgstr "Artister" msgid "QA Manager" msgstr "Koordinator för Kvalitétsäkring" msgid "QA" msgstr "Kvalitétsäkrare" msgid "Writing" msgstr "Manus" msgid "Manual and Helpfile" msgstr "Manual och Hjälpfil" msgid "Scenarios" msgstr "Scenarier" msgid "Heroes of Might and Magic II: The Price of Loyalty team" msgstr "Heroes of Might and Magic II: The Price of Loyalty teamet" msgid "Design Lead" msgstr "Huvuddesigner" msgid "Designers" msgstr "Formgivare" msgid "Programming Lead" msgstr "Huvudprogrammerare" msgid "Art Lead" msgstr "Grafisk Huvudformgivare" msgid "Playtesters" msgstr "Testspelare" msgid "Designer" msgstr "Formgivare" msgid "Producers" msgstr "Producenter" msgid "QA Managers" msgstr "Koordinatorer för Kvalitétsäkring" msgid "Sound Design" msgstr "Ljuddesign" msgid "Town Themes" msgstr "Stadsteman" msgid "Alto Sax" msgstr "Altsaxofon" msgid "Harpsichord and Piano" msgstr "Cembalo och Piano" msgid "Basso Vocal" msgstr "Basstämma" msgid "Soprano Vocal" msgstr "Sopranstämma" msgid "Recorded at %{recordingStudio}" msgstr "Inspelat i %{recordingStudio}" msgid "credits|Manual" msgstr "Manual" msgid "German Consultant" msgstr "Tyskkonsult" msgid "Map Designers" msgstr "Kartkreatörer" msgid "Package Design" msgstr "Paketdesign" msgid "Your Name" msgstr "Ditt Namn" msgid "Unknown Hero" msgstr "Okänd Hjälte" msgid "Standard" msgstr "Standardspel" msgid "View High Scores for Standard Maps." msgstr "Visa Poänglistan för standardkartor." msgid "Campaign" msgstr "Kampanjspel" msgid "View High Scores for Campaigns." msgstr "Visa Poänglistan för kampanjer." msgid "hotkey|default okay event" msgstr "Standardhändelse: Okej" msgid "hotkey|default cancel event" msgstr "Standardhändelse: Avbryt" msgid "hotkey|default left" msgstr "Standard: Vänster" msgid "hotkey|default right" msgstr "Standard: Höger" msgid "hotkey|default up" msgstr "Standard: Upp" msgid "hotkey|default down" msgstr "Standard: Ner" msgid "hotkey|toggle fullscreen" msgstr "Visa fullskärm" msgid "hotkey|toggle text support mode" msgstr "Aktivera textstöd" msgid "hotkey|new game" msgstr "Nytt spel" msgid "hotkey|load game" msgstr "Ladda spel" msgid "hotkey|highscores" msgstr "Poänglista" msgid "hotkey|credits" msgstr "Credits" msgid "hotkey|standard game" msgstr "Standardspel" msgid "hotkey|campaign game" msgstr "Kampanjspel" msgid "hotkey|multi-player game" msgstr "Flerspelarspel" msgid "hotkey|settings" msgstr "Inställningar" msgid "hotkey|quit" msgstr "Avsluta" msgid "hotkey|select map" msgstr "Välj karta" msgid "hotkey|select small map size" msgstr "Välj liten kartstorlek" msgid "hotkey|select medium map size" msgstr "Välj medel kartstorlek" msgid "hotkey|select large map size" msgstr "Välj stor kartstorlek" msgid "hotkey|select extra large map size" msgstr "Välj extra stor kartstorlek" msgid "hotkey|select all map sizes" msgstr "Välj alla kartstorlekar" msgid "hotkey|hotseat game" msgstr "Heta stolen" msgid "hotkey|battle only game" msgstr "Endast strid" msgid "hotkey|choose the original campaign" msgstr "Välj orginalkampanj" msgid "hotkey|choose the expansion campaign" msgstr "Välj expansionskampanj" msgid "hotkey|map editor main menu" msgstr "Kartredigerarens huvudmeny" msgid "hotkey|new map menu" msgstr "Nytt karta" msgid "hotkey|load map menu" msgstr "Ladda karta" msgid "hotkey|new map from scratch" msgstr "Ny blank karta" msgid "hotkey|new random map" msgstr "Ny slumpad karta" msgid "hotkey|undo last action" msgstr "Ã…ngra senaste Ã¥tgärden" msgid "hotkey|redo last action" msgstr "Gör om senaste Ã¥tgärden" msgid "hotkey|roland campaign" msgstr "Rolands kampanj" msgid "hotkey|archibald campaign" msgstr "Archibalds kampanj" msgid "hotkey|the price of loyalty campaign" msgstr "Lojalitetens pris kampanj" msgid "hotkey|voyage home campaign" msgstr "Hemresekampanjen" msgid "hotkey|wizard's isle campaign" msgstr "Trollkarlsöarnakampanjen" msgid "hotkey|descendants campaign" msgstr "Ättlingarnas Kampanj" msgid "hotkey|select first campaign bonus" msgstr "Välj första kampanjbonusen" msgid "hotkey|select second campaign bonus" msgstr "Välj andra kampanjbonusen" msgid "hotkey|select third campaign bonus" msgstr "Välj tredje kampanjbonusen" msgid "hotkey|view campaign intro" msgstr "Visa kampanjintro" msgid "hotkey|select campaign difficulty" msgstr "Välj kampanjsvÃ¥righet" msgid "hotkey|restart campaign scenario" msgstr "Omstarta kampanjscenario" msgid "hotkey|world map left" msgstr "Kartvy: Väster" msgid "hotkey|world map right" msgstr "Kartvy: Öster" msgid "hotkey|world map up" msgstr "Kartvy: Norr" msgid "hotkey|world map down" msgstr "Kartvy: Söder" msgid "hotkey|world map up left" msgstr "Kartvy: Nordväst" msgid "hotkey|world map up right" msgstr "Kartvy: Nordost" msgid "hotkey|world map down left" msgstr "Kartvy: Sydväst" msgid "hotkey|world map down right" msgstr "Kartvy: Sydost" msgid "hotkey|save game" msgstr "Spara spel" msgid "hotkey|next hero" msgstr "Nästa hjälte" msgid "hotkey|start hero movement" msgstr "PÃ¥börja hjälteförflyttning" msgid "hotkey|cast adventure spell" msgstr "Kasta en kartbesvärjelse." msgid "hotkey|put hero to sleep" msgstr "Söv hjälte" msgid "hotkey|next town" msgstr "Nästa stad" msgid "hotkey|end turn" msgstr "Avsluta runda" msgid "hotkey|file options" msgstr "Filalternativ" msgid "hotkey|adventure options" msgstr "Äventyrsinställningar" msgid "hotkey|puzzle map" msgstr "Pusselkarta" msgid "hotkey|scenario information" msgstr "Scenarioinformation" msgid "hotkey|dig for artifact" msgstr "Gräv efter artefakt" msgid "hotkey|view world" msgstr "Visa kartvy" msgid "hotkey|kingdom summary" msgstr "Rikessammanfattning" msgid "hotkey|default action" msgstr "StandardÃ¥tgärd" msgid "hotkey|open focus" msgstr "Öppna markerat" msgid "hotkey|system options" msgstr "Systeminställningar" msgid "hotkey|scroll left" msgstr "Skrolla vänster" msgid "hotkey|scroll right" msgstr "Skrolla höger" msgid "hotkey|scroll up" msgstr "Skrolla upp" msgid "hotkey|scroll down" msgstr "Skrolla ner" msgid "hotkey|toggle control panel" msgstr "Aktivera kontrollpanel" msgid "hotkey|toggle radar" msgstr "Aktivera radar" msgid "hotkey|toggle buttons" msgstr "Aktivera knappar" msgid "hotkey|toggle status" msgstr "Aktivera status" msgid "hotkey|toggle icons" msgstr "Aktivera ikoner" msgid "hotkey|transfer control to ai" msgstr "Överför kontrollen till datorn" msgid "hotkey|retreat from battle" msgstr "Retirera frÃ¥n strid" msgid "hotkey|surrender during battle" msgstr "Kapitulera under strid" msgid "hotkey|toggle battle auto mode" msgstr "Aktivera Automatisk Strid" msgid "hotkey|finish the battle in auto mode" msgstr "LÃ¥t datorn fortsätta striden" msgid "hotkey|battle options" msgstr "Stridsalternativ" msgid "hotkey|skip turn in battle" msgstr "Hoppa över runda i strid" msgid "hotkey|cast battle spell" msgstr "Kasta stridsbesvärjelse" msgid "hotkey|dwelling level 1" msgstr "Bostad, nivÃ¥ 1" msgid "hotkey|dwelling level 2" msgstr "Bostad, nivÃ¥ 2" msgid "hotkey|dwelling level 3" msgstr "Bostad, nivÃ¥ 3" msgid "hotkey|dwelling level 4" msgstr "Bostad, nivÃ¥ 4" msgid "hotkey|dwelling level 5" msgstr "Bostad, nivÃ¥ 5" msgid "hotkey|dwelling level 6" msgstr "Bostad, nivÃ¥ 6" msgid "hotkey|well" msgstr "Brunn" msgid "hotkey|marketplace" msgstr "Marknad" msgid "hotkey|mage guild" msgstr "Magisamfund" msgid "hotkey|shipyard" msgstr "Skeppsvarv" msgid "hotkey|thieves guild" msgstr "Tjuvgille" msgid "hotkey|tavern" msgstr "Värdshus" msgid "hotkey|construction screen" msgstr "Byggvy" msgid "hotkey|buy all monsters in well" msgstr "Rekrytera alla varelser i brunnen" msgid "hotkey|split stack by half" msgstr "Dela truppen till hälften" msgid "hotkey|split stack by one" msgstr "Dela truppen med ett" msgid "hotkey|join stacks" msgstr "Anslut trupper" msgid "hotkey|upgrade troop" msgstr "Uppgradera trupp" msgid "hotkey|dismiss hero or troop" msgstr "Avskeda hjälte eller varelse" msgid "" "Do you want to regain control from AI? The effect will take place only on " "the next turn." msgstr "" "Vill du Ã¥terta kontrollen frÃ¥n AI:n? Denna effekt gäller bara under nästa " "runda." msgid "" "Do you want to transfer control from you to the AI? The effect will take " "place only on the next turn." msgstr "" "Vill du ge datorn kontrollen? Denna effekt gäller bara för nästa runda." msgid "Default Actions" msgstr "StandardÃ¥tgärder" msgid "Global Actions" msgstr "Globala Ã…tgärder" msgid "Main Menu" msgstr "Huvudmeny" msgid "World Map" msgstr "Världskarta" msgid "Battle Screen" msgstr "Stridsvy" msgid "Town Screen" msgstr "Stadsvy" msgid "Army Actions" msgstr "Arméåtgärder" msgid "Editor" msgstr "Redigerare" msgid "The save file is corrupted." msgstr "Sparfilen är korrupt." msgid "Unsupported save format: " msgstr "Ostödda sparfilsformat: " msgid "Current game version: " msgstr "Nuvarande spelversion: " msgid "Last supported version: " msgstr "Senast stödda version: " msgid "This file contains a save with an invalid game type." msgstr "Denna fil innehÃ¥ller en sparning med en ogiltig speltyp." msgid "" "This save file requires \"The Price of Loyalty\" game assets, but they have " "not been provided to the engine." msgstr "" "Denna sparfil kräver \"The Price of Loyalty\"s speltillgÃ¥ngar, men de har " "inte tillhandahÃ¥llits till spelmotorn." msgid "This saved game is localized to '" msgstr "Denna spelomgÃ¥ng är översatt till '" msgid "' language, but the current language of the game is '" msgstr "' men nuvarande spelsprÃ¥k är " msgid "Hot Seat" msgstr "Heta stolen" msgid "" "Play a Hot Seat game, where 2 to 4 players play around the same computer, " "switching into the 'Hot Seat' when it is their turn." msgstr "" "Spela Heta stolen, där 2 till 4 spelare spelar pÃ¥ samma dator och sätter sig " "i den 'Heta stolen' när det är sin tur." msgid "A single player game playing out a single map." msgstr "En ensam spelare gÃ¥r igenom en karta." msgid "Standard Game" msgstr "Standardspel" msgid "A single player game playing through a series of maps." msgstr "En ensam spelare gÃ¥r igenom en serie kartor." msgid "Campaign Game" msgstr "Kampanjspel" msgid "Multi-Player Game" msgstr "Flerspelarläge" msgid "" "A multi-player game, with several human players completing against each " "other on a single map." msgstr "" "Ett spel med flera spelare, där flera mänskliga spelare som tävlar mot " "varandra pÃ¥ en karta." msgid "fheroes2 Resurrection Team presents" msgstr "fheroes2 Resurrection Team presenterar" msgid "Greetings!" msgstr "Var Hälsad!" msgid "Welcome to Heroes of Might and Magic II powered by fheroes2 engine!" msgstr "Välkommen till Heroes of Might and Magic II genom fheroes2 spelmotor!" msgid "" "Welcome to Heroes of Might and Magic II powered by the fheroes2 engine!\n" "Before starting the game please select a game resolution." msgstr "" "Välkommen till Heroes of Might and Magic II genom fheroes2 spelmotor. Välj " "spelupplösning innan du börjar spelet." msgid "Please Remember" msgstr "Kom ihÃ¥g" msgid "" "You can always change the language, resolution and settings of the game by " "clicking on the " msgstr "" "Du kan alltid ändra sprÃ¥k, spelupplösning och inställningar genom att klicka " "pÃ¥ " msgid "door" msgstr "dörren" msgid " on the left side of the Main Menu, or with the " msgstr " vid vänster om huvudmenyn eller med " msgid " button from the " msgstr " knappen i " msgid "NEW GAME" msgstr "" "NYTT\n" "SPEL" msgid "" " menu. \n" "\n" "Enjoy the game!" msgstr "" " menyn. \n" "Spela pÃ¥!" msgid "The Editor requires \"The Price of Loyalty\" expansion files to work." msgstr "" "Kartredigeraren kräver \"The Price of Loyalty\"-expansionens filer för att " "fungera." msgid "Quit Heroes of Might and Magic II and return to the operating system." msgstr "Avsluta Heroes of Might and Magic II och Ã¥tergÃ¥ till operativsystemet." msgid "Credits" msgstr "Tack till" msgid "View the credits screen." msgstr "Visa \"Tack till\"-skärmen." msgid "High Scores" msgstr "Poänglistan" msgid "View the high scores screen." msgstr "Visa Poänglistan." msgid "Change language, resolution and settings of the game." msgstr "Ändra spelets sprÃ¥k, upplösning och inställningar." msgid "Game Settings" msgstr "Spelinställningar" msgid "" "Required video files for campaign selection window are missing. Please make " "sure that all necessary files are present in the system." msgstr "" "Videofilmerna som krävs för kampanjmenyn saknas. Kontrollera att samtliga " "nödvändiga filer finns i systemet." msgid "" "Either Roland's or Archibald's campaign from the original Heroes of Might " "and Magic II." msgstr "" "Antingen Rolands eller Archibalds kampanj frÃ¥n original Heroes of Might and " "Magic II." msgid "Original Campaign" msgstr "Orginalkampanj" msgid "Expansion Campaign" msgstr "Expansionskampanj" msgid "One of the four new campaigns from the Price of Loyalty expansion set." msgstr "En av de fyra nya kampanjer frÃ¥n Lojalitetens Pris expansionen." msgid "Loading video. Please wait..." msgstr "Laddar film. Vänta..." msgid "Host" msgstr "Värd" msgid "" "The host sets up the game options. There can only be one host per network " "game." msgstr "" "Värden anger spelinställningarna. Det kan endast finnas en värd per " "nätverksspel." msgid "Guest" msgstr "Gäst" msgid "" "The guest waits for the host to set up the game, then is automatically added " "in. There can be multiple guests for TCP/IP games." msgstr "" "Gästen väntar pÃ¥ att värden ska sätta igÃ¥ng spelet, sen läggs till " "automatiskt. Det kan finnas flera gäster för TCP/IP-spel." msgid "Battle Only" msgstr "Endast Strid" msgid "Setup and play a battle without loading any map." msgstr "Konfigurera och spela en strid utan nÃ¥gon karta." msgid "2 Players" msgstr "2 Spelare" msgid "" "Play with 2 human players, and optionally, up to 4 additional computer " "players." msgstr "" "Spela med 2 mänskliga spelare, och eventuellt upp till 4 datorstyrda spelare." msgid "3 Players" msgstr "3 Spelare" msgid "" "Play with 3 human players, and optionally, up to 3 additional computer " "players." msgstr "" "Spela med 3 mänskliga spelare, och eventuellt upp till 3 datorstyrda spelare." msgid "4 Players" msgstr "4 Spelare" msgid "" "Play with 4 human players, and optionally, up to 2 additional computer " "players." msgstr "" "Spela med 4 mänskliga spelare, och eventuellt upp till 2 datorstyrda spelare." msgid "5 Players" msgstr "5 Spelare" msgid "" "Play with 5 human players, and optionally, up to 1 additional computer " "player." msgstr "" "Spela med 5 mänskliga spelare, och eventuellt med 1 datorstyrd spelare." msgid "6 Players" msgstr "6 Spelare" msgid "Play with 6 human players." msgstr "Spela med 6 mänskliga spelare." msgid "Dragon city has fallen! You are now the Master of the Dragons." msgstr "Drakstaden har fallit! Du är nu Drakmästaren." msgid "" "You captured %{name}!\n" "You are victorious." msgstr "" "Du fÃ¥ngade %{name}!\n" "Du segrar." msgid "" "You have captured the enemy hero %{name}!\n" "Your quest is complete." msgstr "" "Du har fÃ¥ngat fiendehjälten %{name}!\n" "Ditt uppdrag är slutfört." msgid "" "You have found the %{name}.\n" "Your quest is complete." msgstr "" "Du har hittat %{name}.\n" "Ditt uppdrag är slufört." msgid "Ultimate Artifact" msgstr "Den Främsta Artefakten" msgid "" "The enemy is beaten.\n" "Your side has triumphed!" msgstr "" "Fienden är besegrad.\n" "Din sida segrar!" msgid "" "You have built up over %{count} gold in your treasury.\n" "All enemies bow before your wealth and power." msgstr "" "Du har byggt upp en förmögenhet pÃ¥ över %{count} i din skattkammare.\n" "Alla fiender bugar inför din rikedom och makt." msgid "Victory!" msgstr "Seger!" msgid "" "The enemy has captured %{name}!\n" "They are triumphant." msgstr "" "Fienden har tillfÃ¥ngatagit %{name}!\n" "Dem jublar." msgid "" "The enemy has built up over %{count} gold in his treasury.\n" "You must bow done in defeat before his wealth and power." msgstr "" "Fienden har byggt upp en förmögenhet pÃ¥ över %{count} guld i sin " "skattkammare.\n" "Du mÃ¥ste buga i förlust inför deras rikedom och makt." msgid "You have been eliminated from the game!!!" msgstr "Du har blivit eliminerad frÃ¥n spelet!!!" msgid "" "You have lost the hero %{name}.\n" "Your quest is over." msgstr "" "Du har förlorat hjälten %{name}.\n" "Ditt uppdrag är över." msgid "" "You have failed to complete your quest in time.\n" "All is lost." msgstr "" "Du har misslyckats att slutföra ditt uppdrag i tid.\n" "Allting är förlorat." msgid "Defeat!" msgstr "Nederlag!" msgid "" "Base score: %{score}\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" "Grundpoängställning: %{score}\n" "SvÃ¥righetsgrad: %{difficulty}\n" "\n" msgid "Defeat all enemy heroes and capture all enemy towns and castles." msgstr "Besegra alla fiendehjältar och inta alla fiendestäder och slott." msgid "Run out of time. (Fail to win by a certain point.)" msgstr "FÃ¥ slut pÃ¥ tid. (Misslyckas att vinna vid en bestämd punkt.)" msgid "You must defeat the enemy %{enemies}." msgid_plural "You must defeat the enemy alliance of %{enemies}." msgstr[0] "Du mÃ¥ste besegra fienden %{enemies}." msgstr[1] "Du mÃ¥ste besegra fienderna %{enemies}." msgid "" "The alliance consisting of %{allies} and you must defeat the enemy " "%{enemies}." msgid_plural "" "The alliance consisting of %{allies} and you must defeat the enemy alliance " "of %{enemies}." msgstr[0] "" "Alliansen bestÃ¥r av %{allies} and du mÃ¥ste besegra fienden %{enemies}." msgstr[1] "" "Alliansen bestÃ¥r av %{allies} and du mÃ¥ste besegra fiendepakten som bestÃ¥r " "av %{enemies}." msgid "Capture the castle '%{name}'." msgstr "Erövra slottet '%{name}'." msgid "Capture the town '%{name}'." msgstr "Erövra staden '%{name}'." msgid "Defeat the hero '%{name}'." msgstr "Besegra hjälten '%{name}'." msgid "Find the ultimate artifact." msgstr "Hitta den Främsta Artefakten." msgid "Find the '%{name}' artifact." msgstr "Hitta artefakten '%{name}'." msgid "Accumulate %{count} gold." msgstr "Samla ihop %{count} guld." msgid "" ", or you may win by defeating all enemy heroes and capturing all enemy towns " "and castles." msgstr "" ", eller sÃ¥ kan du vinna genom att besegra alla fiendehjältar och inta alla " "fiendestäder och slott." msgid "Lose the castle '%{name}'." msgstr "Förlora slottet '%{name}'." msgid "Lose the town '%{name}'." msgstr "Förlora staden '%{name}'." msgid "Lose the hero: %{name}." msgstr "Förlora hjälten: %{name}." msgid "Fail to win by the end of month %{month}, week %{week}, day %{day}." msgstr "" "Misslyckas att vinna vid slutet av mÃ¥naden %{month}, vecka %{week}, dag " "%{day}." msgid "%{color} player has been vanquished!" msgstr "%{color} har blivit besegrad!" msgid "Major Event!" msgstr "Stor Händelse!" msgid "Scenario:" msgstr "Scenario:" msgid "Game Difficulty:" msgstr "SpelsvÃ¥righet:" msgid "Opponents:" msgstr "MotstÃ¥ndare:" msgid "Class:" msgstr "Klass:" msgid "Rating %{rating}%" msgstr "Ranking %{rating}%" msgid "Click here to select which scenario to play." msgstr "Klicka här för att välja vilket scenario som ska spelas." msgid "Scenario" msgstr "Scenario" msgid "Game Difficulty" msgstr "SpelsvÃ¥righet" msgid "" "This lets you change the starting difficulty at which you will play. Higher " "difficulty levels start you of with fewer resources, and at the higher " "settings, give extra resources to the computer." msgstr "" "LÃ¥ter dig ändra svÃ¥righetsgraden för spelet. Vid högre svÃ¥righetsgrad sÃ¥ " "startar du med färre resurser och vid ännu svÃ¥rare sÃ¥ fÃ¥r datorn mer " "resurser." msgid "Difficulty Rating" msgstr "SvÃ¥righetsgrad" msgid "" "The difficulty rating reflects a combination of various settings for your " "game. This number will be applied to your final score." msgstr "" "SvÃ¥righetsgraden reflekterar en kombination av blandande inställningar för " "ditt spel. Denna siffra kommer att multipliceras till i dina slutpoäng." msgid "Click to accept these settings and start a new game." msgstr "Klicka för att acceptera dessa inställningar och starta ett nytt spel." msgid "Click to return to the main menu." msgstr "Klicka för att gÃ¥ tillbaka till huvudmenyn." msgid "Astrologers proclaim the Month of the %{name}." msgstr "Astrologerna utropar %{name} mÃ¥nad." msgid "Astrologers proclaim the Week of the %{name}." msgstr "Astrologerna utropar %{name} vecka." msgid "After regular growth, the population of %{monster} is doubled!" msgstr "Efter en vanlig ökning har bestÃ¥ndet av %{monster} fördubblats!" msgid "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgid_plural "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgstr[0] "" "Efter en vanlig ökning har bestÃ¥ndet av %{monster} ökat med %{count} procent!" msgstr[1] "" "Efter en vanlig ökning har bestÃ¥ndet av %{monster} ökat med %{count} procent!" msgid "%{monster} growth +%{count}." msgstr "Populationen av %{monster} ökar med +%{count}." msgid " All populations are halved." msgstr " All populationer är halverade." msgid " All dwellings increase population." msgstr " Alla boningar ökar sin population." msgid "New Month!" msgstr "Ny MÃ¥nad!" msgid "New Week!" msgstr "Ny Vecka!" msgid "Beware!" msgstr "Varsko!" msgid "" "%{color} player, this is your last day to capture a town, or you will be " "banished from this land." msgstr "" "%{color} spelare, detta är din sista dag att erövra en stad, annars kommer " "du att bannlysas frÃ¥n detta land." msgid "" "%{color} player, you only have %{day} days left to capture a town, or you " "will be banished from this land." msgstr "" "%{color} spelare, du har %{day} dagar kvar att erövra en stad. Annars kommer " "du att bli bannlyst frÃ¥n detta land." msgid "%{color} player's turn." msgstr "%{color} spelares tur." msgid "" "%{color} player, you have lost your last town. If you do not conquer another " "town in the next week, you will be eliminated." msgstr "" "%{color} spelare, du har förlorat din sista stad. Om du inte erövrar en stad " "nästa vecka sÃ¥ kommer du att bli eliminerad." msgid "" "%{color} player, your heroes abandon you, and you are banished from this " "land." msgstr "" "%{color} spelare, dina hjältar överger dig och du utvisas frÃ¥n detta land." msgid "Lord Kilburn" msgstr "Lord Kilburn" msgid "Tsabu" msgstr "Tsabu" msgid "Sir Galant" msgstr "Sir Gallanth" msgid "Thundax" msgstr "Thundax" msgid "Lord Haart" msgstr "Lord Haart" msgid "Ariel" msgstr "Ariel" msgid "Rebecca" msgstr "Rebecca" msgid "Sandro" msgstr "Sandro" msgid "Crodo" msgstr "Crodo" msgid "Barock" msgstr "Barock" msgid "Antoine" msgstr "Antoine" msgid "Astra" msgstr "Astra" msgid "Agar" msgstr "Agar" msgid "Vatawna" msgstr "Vatawna" msgid "Vesper" msgstr "Vesper" msgid "Ambrose" msgstr "Ambrose" msgid "Troyan" msgstr "Troyan" msgid "Jojosh" msgstr "Jojosh" msgid "Wrathmont" msgstr "Wrathmont" msgid "Maximus" msgstr "Maximus" msgid "Next Hero" msgstr "Nästa Hjälte" msgid "Select the next Hero." msgstr "Välj nästa Hjälte." msgid "Hero Movement" msgstr "Hjälteförflyttning" msgid "" "Start the Hero's movement along the current path or re-visit the object " "occupied by the Hero. Press and hold this button to reset the Hero's path." msgstr "" "PÃ¥börja Hjältens förflyttning längs den givna vägsträckan eller Ã¥terbesök " "ett objekt som Hjälten stÃ¥r vid. Tryck och hÃ¥ll denna knapp för att " "Ã¥terställa Hjältens vägsträckning." msgid "Kingdom Summary" msgstr "Rikessammanfattning" msgid "View a Summary of your Kingdom." msgstr "Visa en sammanfattning av ditt Rike." msgid "Cast an adventure spell." msgstr "Kasta en kartbesvärjelse." msgid "End Turn" msgstr "Avsluta Runda" msgid "End your turn and left the computer take its turn." msgstr "Avsluta din runda och lÃ¥t datorn göra sin runda." msgid "Adventure Options" msgstr "Äventyrsinställningar" msgid "Bring up the adventure options menu." msgstr "Visar menyn för äventyrinställningar." msgid "" "Bring up the file options menu, allowing you to load, save, start a new game " "or quit." msgstr "" "Tar fram filalternativsmenyn som lÃ¥ter dig ladda, spara, starta ett nytt " "spel eller avsluta." msgid "Bring up the system options menu, allowing you to customize your game." msgstr "" "Tar fram systemalternativsmenyn som lÃ¥ter dig skräddarsy ditt spelande." msgid "" "One or more heroes may still move, are you sure you want to end your turn?" msgstr "" "En eller flera hjältar kan fortfarande flytta sig. Är du säker pÃ¥ att du " "vill avsluta din runda?" msgid "Are you sure you want to quit?" msgstr "Är du säker pÃ¥ att du vill avsluta?" msgid "Are you sure you want to restart? (Your current game will be lost.)" msgstr "" "Är du säker pÃ¥ att du vill starta om? (Ditt nuvarande spel kommer att " "förloras.)" msgid "Are you sure you want to overwrite the save with this name?" msgstr "Är du säker pÃ¥ att du vill skriva över sparningen med detta namn?" msgid "Game saved successfully." msgstr "Sparning lyckades." msgid "There was an issue during saving." msgstr "Det uppstod ett problem under sparningen." msgid "" "Are you sure you want to load a new game? (Your current game will be lost.)" msgstr "" "Är du säker pÃ¥ att du vill ladda ett nytt spel? (Ditt nuvarande spel kommer " "att försvinna.)" msgid "Try looking on land!!!" msgstr "Försök att titta pÃ¥ land!!!" msgid "" "Searching for the Ultimate Artifact is fruitless. Your hero could not carry " "it even if he found it - all his artifact slots are full." msgstr "" "Letandet efter den Främsta Artefakten är fruktlöst. Din hjälte kan inte ta " "en ens om hjälten fann den - hjältens packning är full." msgid "Digging for artifacts requires a whole day, try again tomorrow." msgstr "Att gräva efter artefakter tar en hel dag. Försök igen imorgon." msgid "" "After spending many hours digging here, you have uncovered the %{artifact}." msgstr "" "Efter att ha spenderat mÃ¥nga timmar pÃ¥ att gräva här, har du avtäckt " "%{artifact}." msgid "Congratulations!" msgstr "Gratulerar!" msgid "Nothing here. Where could it be?" msgstr "Ingenting här. Var kan den vara?" msgid "Try searching on clear ground." msgstr "Försök att gräva pÃ¥ bar mark." msgid "A miniature view of the known world. Left click to move viewing area." msgstr "" "En miniatyrvy av den kända världen. Vänsterklicka för att flytta " "visningsomrÃ¥det." msgid "Month: %{month} Week: %{week}" msgstr "MÃ¥nad: %{month} Vecka: %{week}" msgid "Day: %{day}" msgstr "Dag: %{day}" msgid "" "You find a small\n" "quantity of %{resource}." msgstr "" "Du hittade en liten\n" "summa %{resource}." msgid "Status Window" msgstr "Statusfönster" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date." msgstr "Detta fönster visar datum och information om din hjälte och ditt rike." msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date. Left click here to cycle through these windows." msgstr "" "Detta fönster visar datum och information om din hjälte och ditt rike. " "Vänsterklicka här för att bläddra genom dessa fönster." msgid "" "This lets you change player starting positions and colors. A particular " "color will always start in a particular location. Some positions may only be " "played by a computer player or only by a human player." msgstr "" "Detta lÃ¥ter dig ändra spelarnas startpositioner och färger. En speciell färg " "startar alltid vid en viss plats. Vissa positioner kan endast spelas av en " "datorstyrd eller mänsklig spelare." msgid "" "This lets you change the class of a player. Classes are not always " "changeable. Depending on the scenario, a player may receive additional towns " "and/or heroes not of their primary alignment." msgstr "" "Detta lÃ¥ter dig ändra spelarnas startpositioner och färger. En speciell färg " "startar alltid vid en viss plats. Vissa positioner kan endast spelas av en " "datorstyrd eller mänsklig spelare." msgid "Handicap" msgstr "Handikapp" # msgid "" "This lets you change the handicap of a particular player. Only humans may be " "handicapped. Handicapped players start with fewer resources and earn 15 or " "30% fewer resources per turn for mild and severe handicaps, respectively." msgstr "" "Ändra spelarens handikapp. Bara spelare kontrollerade av en människa kan ha " "handikapp. Handikappade spelare startar med färre resurser och tjänar 15 % " "mindre för milt och 30 % mindre för gravt handikapp." msgid "%{color} player" msgstr "%{color} spelare" msgid "No Handicap" msgstr "Inget Handikapp" msgid "No special restrictions on start resources and earning them per turn." msgstr "Inga restriktioner för startresurser eller resursintäkter per dag." msgid "Mild Handicap" msgstr "Milt Handikapp" # msgid "" "Mild handicapped players start with fewer resources and earn 15% fewer " "resources per turn." msgstr "" "Spelare med milt handikapp startar med färre resurser och tjänar 15% färre " "resurser per dag." msgid "Severe Handicap" msgstr "Gravt Handikapp" # msgid "" "Severe handicapped players start with fewer resources and earn 30% fewer " "resources per turn." msgstr "" "Spelare med gravt handikapp startar med färre resurser och tjänar 30% färre " "resurser per dag." msgid "View %{skill} Info" msgstr "Visa information om %{skill}" msgid "Keyboard|123" msgstr "123" msgid "Keyboard|SPACE" msgstr "MELLANSLAG" msgid "Keyboard|ABC" msgstr "ABC" msgid "Kingdom Income" msgstr "Inkomst" msgid "Kingdom Income per day." msgstr "Rikets Dagsinkomst." msgid "For every lighthouse controlled, your ships will move further each day." msgstr "" "För varje fyrtorn du kontrollerar sÃ¥ kommer dina skepp segla längre per dag." msgid "English" msgstr "Engelska" msgid "French" msgstr "Franska" msgid "Polish" msgstr "Polska" msgid "German" msgstr "Tyska" msgid "Russian" msgstr "Ryska" msgid "Italian" msgstr "Italienska" msgid "Czech" msgstr "Tjeckiska" msgid "Norwegian" msgstr "Norska" msgid "Belarusian" msgstr "Vitryska" msgid "Bulgarian" msgstr "Bulgariska" msgid "Ukrainian" msgstr "Ukrainska" msgid "Romanian" msgstr "Rumänska" msgid "Spanish" msgstr "Spanska" msgid "Swedish" msgstr "Svenska" msgid "Portuguese" msgstr "Portugisiska" msgid "Turkish" msgstr "Turkiska" msgid "Dutch" msgstr "Nederländska" msgid "Hungarian" msgstr "Ungerska" msgid "Danish" msgstr "Danska" msgid "Slovak" msgstr "Slovakiska" msgid "Vietnamese" msgstr "Vietnamesiska" msgid ", FPS: " msgstr ", FPS: " msgid "%{object} robber" msgstr "%{object} plundrare" msgid "%{object} raided" msgstr "%{object} plundrat" msgid "Ector" msgstr "Ector" msgid "Gwenneth" msgstr "Gwenneth" msgid "Sir Gallant" msgstr "Sir Gallanth" msgid "Tyro" msgstr "Tyro" msgid "Dimitry" msgstr "Dimitry" msgid "Ruby" msgstr "Ruby" msgid "Crag Hack" msgstr "Crag Hack" msgid "Fineous" msgstr "Fineous" msgid "Jezebel" msgstr "Jezebel" msgid "Atlas" msgstr "Atlas" msgid "Ergon" msgstr "Ergon" msgid "Jaclyn" msgstr "Jaclyn" msgid "Gem" msgstr "Gem" msgid "Natasha" msgstr "Natasha" msgid "Carlawn" msgstr "Carlawn" msgid "Luna" msgstr "Luna" msgid "Arie" msgstr "Arie" msgid "Barok" msgstr "Barok" msgid "Kastore" msgstr "Kastore" msgid "Falagar" msgstr "Falagar" msgid "Dawn" msgstr "Dawn" msgid "Flint" msgstr "Flint" msgid "Halon" msgstr "Halon" msgid "Myra" msgstr "Myra" msgid "Myrini" msgstr "Myrini" msgid "Wilfrey" msgstr "Wilfrey" msgid "Mandigal" msgstr "Mandigal" msgid "Sarakin" msgstr "Sarakin" msgid "Charity" msgstr "Charity" msgid "Darlana" msgstr "Darlana" msgid "Ranloo" msgstr "Ranloo" msgid "Rialdo" msgstr "Rialdo" msgid "Zam" msgstr "Zam" msgid "Zom" msgstr "Zom" msgid "Celia" msgstr "Celia" msgid "Roxana" msgstr "Roxana" msgid "Lord Corlagon" msgstr "Lord Corlagon" msgid "Lord Halton" msgstr "Lord Halton" msgid "Sister Eliza" msgstr "Syster Eliza" msgid "Brother Brax" msgstr "Broder Brax" msgid "Dainwin" msgstr "Dainwin" msgid "Joseph" msgstr "Joseph" msgid "Mog" msgstr "Mog" msgid "Solmyr" msgstr "Solmyr" msgid "Ceallach" msgstr "Ceallach" msgid "Drakonia" msgstr "Drakonia" msgid "Elderian" msgstr "Elderian" msgid "Gallavant" msgstr "Gallavant" msgid "Jarkonas" msgstr "Jarkonas" msgid "Martine" msgstr "Martine" msgid " gives you maximum morale" msgstr " ger dig maximalt med stridsmoral" msgid " gives you maximum luck" msgstr " ger dig maximalt med tur" msgid "You cannot pick up this artifact, you already have a full load!" msgstr "" "Hjälten kan inte plocka upp denna artefakten. Hjälten har redan full " "packning!" msgid "To cast spells, you must first buy a spell book for %{gold} gold." msgstr "" "För att kasta besvärjelser mÃ¥ste Hjälten först och främst köpa en magibok " "för %{gold} guld." msgid "Unfortunately, you seem to be a little short of cash at the moment." msgstr "Tyvärr verkar du för tillfället vara pÃ¥ grön kvist." msgid "Do you wish to buy one?" msgstr "Vill du köpa en?" msgid "%{count} / day" msgstr "%{count} / dag" msgid "one" msgstr "en" msgid "two" msgstr "tvÃ¥" msgid "A whirlpool engulfs your ship. Some of your army has fallen overboard." msgstr "" "En strömvirvel uppslukar ditt skepp. En del av din armé har ramlat överbord." msgid "Insulted by your refusal of their offer, the monsters attack!" msgstr "" "Förolämpade av ditt avvisande av deras erbjudande, attackerar monsterna!" msgid "" "The %{monster}, awed by the power of your forces, begin to scatter.\n" "Do you wish to pursue and engage them?" msgstr "" "Varelserna blir rädda för din armés styrka och skingras.\n" "Vill du förfölja och strida mot dem?" msgid "Ransacking an enemy camp, you discover a hidden cache of treasures." msgstr "" "När du genomsöker ett fiendeläger hittar du ett gömt förrÃ¥d av skatter." msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with these resources, " "come back next week for more.\"" msgstr "" "Kvarnens skötare meddelar:\n" "\"Herre, jag har jobbat hÃ¥rt för att förse dig med dessa resurser, kom " "tillbaka nästa vecka för mer.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there are no resources currently available. Please try " "again next week.\"" msgstr "" "Kvarnens skötare meddelar:\n" "\"Herre, jag är ledsen, det finns för tillfället inga resurser tillgänliga. " "Vänligen försök nästa vecka.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with this gold, come " "back next week for more.\"" msgstr "" "Kvarnens skötare meddelar:\n" "\"Herre, jag har jobbat hÃ¥rt för att förse dig med detta guld, kom tillbaka " "nästa vecka för mer.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there is no gold currently available. Please try again " "next week.\"" msgstr "" "Kvarnens skötare meddelar:\n" "\"Herre, jag är ledsen, det finns för tillfället inget guld tillgänligt. " "Vänligen försök nästa vecka.\"" msgid "" "You've found an abandoned lean-to.\n" "Poking about, you discover some resources hidden nearby." msgstr "" "Du har hittat ett övergivet skjul.\n" "Efter att ha kikat igenom hittar du nÃ¥gra gömda resurser i närheten." msgid "The lean-to is long abandoned. There is nothing of value here." msgstr "" "Skjulet är övergivet sedan länge tillbaka. Det finns ingenting av värde här." msgid "" "You catch a leprechaun foolishly sleeping amidst a cluster of magic " "mushrooms.\n" "In exchange for his freedom, he guides you to a small pot filled with " "precious things." msgstr "" "Du fÃ¥ngar en Pyssling som slumrat till i ett kluster av magiska svampar.\n" "I utbyte mot hans frihet sÃ¥ leder han dig till en kruka fylld med fina saker." msgid "" "You've found a magic garden, the kind of place that leprechauns and faeries " "like to cavort in, but there is no one here today.\n" "Perhaps you should try again next week." msgstr "" "Du har stött pÃ¥ en förtrollad trädgÃ¥rd, den typ av plats som Pysslingar och " "Féer brukar befinna sig. Men du ser ingen här idag.\n" "Kanske bättre lycka nästa vecka," msgid "You come upon the remains of an unfortunate adventurer." msgstr "Du stöter pÃ¥ kvarlevorna av en olycklig äventyrare." msgid "Treasure" msgstr "Skatt" msgid "Searching through the tattered clothing, you find the %{artifact}." msgstr "När du söker igenom de trasiga kläderna hittar du %{artifact}." msgid "Searching through the tattered clothing, you find nothing." msgstr "När du söker igenom de trasiga kläderna hittar du ingenting." msgid "" "You come across an old wagon left by a trader who didn't quite make it to " "safe terrain." msgstr "" "Du stöter pÃ¥ en gammal vagn som har övergetts av en försäljare som inte " "riktig lyckades komma in pÃ¥ säker terräng." msgid "Unfortunately, others have found it first, and the wagon is empty." msgstr "Tyvärr har andra hittat den tidigare, och vagnen är tom." msgid "Searching inside, you find the %{artifact}." msgstr "När du söker igenom den, hittar du %{artifact}." msgid "Inside, you find some of the wagon's cargo still intact." msgstr "Inuti ser du att nÃ¥gra av vagnens packningar fortfarande är orörda." msgid "You search through the flotsam, and find some wood and some gold." msgstr "Du söker igenom vrakgodset, du hittar lite ved och guld." msgid "You search through the flotsam, and find some wood." msgstr "Du söker igenom vrakgodset, du hittar lite ved." msgid "You search through the flotsam, but find nothing." msgstr "Du söker igenom vrakgodset, men du hittar ingenting." msgid "Shrine of the 1st Circle" msgstr "1:a kretsens tempel" msgid "" "You come across a small shrine attended by a group of novice acolytes.\n" "In exchange for your protection, they agree to teach you a simple spell - " "'%{spell}'." msgstr "" "Du hittar ett litet tempel, bevistat av en grupp följeslagare.\n" "I utbyte mot ditt beskyddande gÃ¥r de med pÃ¥ att lära dig en komplicerad " "besvärjelse - '%{spell}'." msgid "Shrine of the 2nd Circle" msgstr "2:a kretsens tempel" msgid "" "You come across an ornate shrine attended by a group of rotund friars.\n" "In exchange for your protection, they agree to teach you a spell - " "'%{spell}'." msgstr "" "Du hittar ett utsmyckat tempel, bevistat av en grupp runda munkar.\n" "I utbyte mot ditt beskyddande gÃ¥r de med pÃ¥ att lära dig en komplicerad " "besvärjelse - '%{spell}'." msgid "Shrine of the 3rd Circle" msgstr "3:e kretsens tempel" msgid "" "You come across a lavish shrine attended by a group of high priests.\n" "In exchange for your protection, they agree to teach you a sophisticated " "spell - '%{spell}'." msgstr "" "Du hittar ett frikostigt tempel, bevistat av en grupp höga präster.\n" "I utbyte mot ditt beskyddande gÃ¥r de med pÃ¥ att lära dig en komplicerad " "besvärjelse - '%{spell}'." msgid "" "\n" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "\n" "Tyvärr är du inte vis nog att förstÃ¥ besvärjelsen, sÃ¥ kan inte lära dig den." msgid "" "\n" "Unfortunately, you already have knowledge of this spell, so there is nothing " "more for them to teach you." msgstr "" "\n" "Tyvärr har du redan lärt dig denna besvärjelse. De kan inte lära dig " "nÃ¥gonting mer." msgid "" "\n" "Unfortunately, you have no Magic Book to record the spell with." msgstr "" "\n" "Tyvärr har du ingen magibok att anteckna besvärjelsen med." msgid "" "You approach the hut and observe a witch inside studying an ancient tome on " "%{skill}.\n" "\n" msgstr "" "Du närmar dig hyddan och ser en häxa där inne som studerar en urÃ¥ldrig volym " "av %{skill}.\n" "\n" msgid "" "As you approach, she turns and focuses her one glass eye on you.\n" "\"You already know everything you deserve to learn!\" the witch screeches. " "\"NOW GET OUT OF MY HOUSE!\"" msgstr "" "När du närmar dig, vänder hon sig om och stirrar med sitt glasöga pÃ¥ dig.\n" "\"Du kan redan allting du förtjänar att veta!\" tjuter häxan. \"UT FRÃ…N MITT " "HUS!\"" msgid "" "As you approach, she turns and speaks.\n" "\"You already know that which I would teach you. I can help you no further.\"" msgstr "" "När du närmar dig, vänder hon sig om och säger.\n" "\"Du kan redan allting som jag kan lära dig. Jag kan inte hjälpa dig vidare." "\"" msgid "" "An ancient and immortal witch living in a hut with bird's legs for stilts " "teaches you %{skill} for her own inscrutable purposes." msgstr "" "En gammal och odödlig häxa bor i en hydda med fÃ¥gelben som styltor lär dig " "%{skill} för hennes egna gÃ¥tfulla ändamÃ¥l." msgid "As you drink the sweet water, you gain luck for your next battle." msgstr "När du dricker det söta vattnet, ökar din tur inför nästa strid." msgid "You drink from the enchanted fountain, but nothing happens." msgstr "Du dricker frÃ¥n den förtrollande fontänen, men ingenting händer." msgid "You enter the faerie ring, but nothing happens." msgstr "Du kliver in i häxringen, men ingenting händer." msgid "" "Upon entering the mystical faerie ring, your army gains luck for its next " "battle." msgstr "" "När du kliver in i den mystiska häxringen, ökar din armés tur inför nästa " "strid." msgid "" "You've found an ancient and weathered stone idol.\n" "It is supposed to grant luck to visitors, but since the stars are already " "smiling upon you, it does nothing." msgstr "" "Du har hittat en urÃ¥ldrig och väderbiten stenavgud.\n" "Den är menad för att lyckoönska besökare, men eftersom stjärnorna redan ler " "Ã¥t dig gör den ingenting." msgid "" "You've found an ancient and weathered stone idol.\n" "Kissing it is supposed to be lucky, so you do. The stone is very cold to the " "touch." msgstr "" "Du har hittat en gammal och vittrad stenuthuggen avgud.\n" "Om man kysser den ska den medföra lycka. Stenen är kall vid beröringen." msgid "The mermaids silently entice you to return later and be blessed again." msgstr "" "Sjöjungfrurna lockar dig tyst att komma tillbaka senare och bli välsignad " "igen." msgid "" "The magical, soothing beauty of the Mermaids reaches you and your crew.\n" "Just for a moment, you forget your worries and bask in the beauty of the " "moment.\n" "The mermaids charms bless you with increased luck for your next combat." msgstr "" "NÃ¥gra magiska och sköna Sjöjungfrur hälsar pÃ¥ dig och din besättning.\n" "För ett ögonblick sÃ¥ glömmer du bort dina bekymmer och njuter av en härlig " "stund.\n" "Sjöjungfrurna välsignar dig och din besättning med ökad tur i nästa strid." msgid "" "You come upon the pyramid of a great and ancient king.\n" "You are tempted to search it for treasure, but all the old stories warn of " "fearful curses and undead guardians.\n" "Will you search?" msgstr "" "Du stöter pÃ¥ pyramiden som har tillhört en stor och urÃ¥ldrig kung.\n" "Du frestas att gÃ¥ in och söka efter skatter, men alla gamla historier varnar " "om hemska förbannelser och odöda väktare.\n" "Vill du gÃ¥ in och söka?" msgid "" "Upon defeating the monsters, you decipher an ancient glyph on the wall, " "telling the secret of the spell - '" msgstr "" "Medan du besegrar monstren, dechiffrerar du en urÃ¥ldrig hieroglyf pÃ¥ väggen " "som berättar hemligheten om besvärjelsen - '" msgid "Unfortunately, you have no Magic Book to record the spell with." msgstr "Tyvärr har du ingen magibok att anteckna besvärjelsen med." msgid "" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "Tyvärr är du inte vis nog att förstÃ¥ besvärjelsen, sÃ¥ kan inte lära dig den." msgid "" "You come upon the pyramid of a great and ancient king.\n" "Routine exploration reveals that the pyramid is completely empty." msgstr "" "Du stöter pÃ¥ en pyramid av en stor och gammal kung.\n" "En rutinerad utforskning avslöjar att pyramiden är helt tom." msgid "" "A drink at the well is supposed to restore your spell points, but you are " "already at maximum." msgstr "" "En klunk frÃ¥n brunnen ska Ã¥terställa dina besvärjelsepoäng, men du har redan " "maximalt antal besvärjelsepoäng." msgid "A second drink at the well in one day will not help you." msgstr "En andra klunk frÃ¥n brunnen pÃ¥ samma dag kommer inte att hjälpa dig." msgid "A drink from the well has restored your spell points to maximum." msgstr "En klunk frÃ¥n brunnen har Ã¥terställt dina besvärjelsepoäng till max." msgid "" "\"I'm sorry sir,\" The leader of the soldiers says, \"but you already know " "everything we have to teach.\"" msgstr "" "\"Jag är ledsen herrn,\" säger legosoldatsledaren, \"du vet redan allt vi " "kan lära dig\"." msgid "The soldiers living in the fort teach you a few new defensive tricks." msgstr "Soldaterna som bor i fortet lär dig nÃ¥gra nya försvarstricks." msgid "" "You've come upon a mercenary camp practicing their tactics. \"You're too " "advanced for us,\" the mercenary captain says. \"We can teach nothing more.\"" msgstr "" "Du har kommit fram till ett legosoldatsläger som tränar sina taktiker. \"Ni " "är för avancerade för oss,\" säger legosoldatskaptenen. \"Vi kan inte lära " "er nÃ¥gonting mer.\"" msgid "" "You've come upon a mercenary camp practicing their tactics. The mercenaries " "welcome you and your troops and invite you to train with them." msgstr "" "Du stöter pÃ¥ ett legoknektsläger där de övar pÃ¥ sin stridskonst. " "Legoknektarna välkomnar dig och dina trupper att öva med dem." msgid "\"Go 'way!\", the witch doctor barks, \"you know all I know.\"" msgstr "\"Ge dig iväg!\", ryter häxdoktorn, \"du vet allt jag vet.\"" msgid "" "An Orcish witch doctor living in the hut deepens your knowledge of magic by " "showing you how to cast stones, read portents, and decipher the intricacies " "of chicken entrails." msgstr "" "En orchisk häxdoktor som lever i hyddan, fördjupar dina kunskaper i " "trollkonst genom att visa dig hur du kastar stenar, läser järtecken och spÃ¥r " "i kycklingars inälvor." msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, the Druids turn you away, indicating they have nothing " "new to teach you." msgstr "" "Du har hittat en grupp druider som dyrkar en av deras mystiska " "stenbyggnader. Under tystnad avvisar de dig och pÃ¥pekar att de inte har " "nÃ¥gonting nytt att lära dig." msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, they teach you new ways to cast spells." msgstr "" "Du har hittat en grupp druider som dyrkar en av deras mystiska " "stenbyggnader. Under tystnad lär de dig nya sätt att kasta besvärjelser." msgid "" "You tentatively approach the burial ground of ancient warriors. Do you want " "to search the graves?" msgstr "" "Du närmar dig försiktigt gravplatsen som tillhör en urÃ¥ldrig krigare. Vill " "du söka igenom gravarna?" msgid "" "You spend several hours searching the graves and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" "Medan du besegrar Zombierna spenderar du nÃ¥gra timmar pÃ¥ att söka igenom " "gravarna och hittar ingenting! En sÃ¥dan föraktlig handling reducerar din " "armés moral." msgid "Upon defeating the Zombies you search the graves and find something!" msgstr "" "Medan du besegrar Zombierna söker du igenom gravarna och hittar nÃ¥gonting!" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the shipwreck?" msgstr "" "De ruttnande skrovet frÃ¥n ett stort piratskepp knarrar när de slÃ¥s mot " "klipporna. Vill du genomsöka vraket?" msgid "" "You spend several hours sifting through the debris and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" "Medan du besegrar Spökena spenderar du nÃ¥gra timmar pÃ¥ att söka igenom " "ruinerna och hittar ingenting! En sÃ¥dan föraktlig handling reducerar din " "armés moral." msgid "" "Upon defeating the Ghosts you sift through the debris and find something!" msgstr "" "Medan du besegrar Spökena kikar du igenom ruinerna och hittar nÃ¥gonting!" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the ship?" msgstr "" "De ruttnande skrovet frÃ¥n ett stort piratskepp knarrar när de slÃ¥s mot " "klipporna. Vill du genomsöka skeppet?" msgid "" "Upon defeating the Skeletons you sift through the debris and find something!" msgstr "" "Medan du besegrar Skeletten söker du igenom ruinerna och hittar nÃ¥gonting!" msgid "Your men spot a navigational buoy, confirming that you are on course." msgstr "Dina män hittar en navigeringsboj som bekräftar att du är pÃ¥ rätt väg." msgid "" "Your men spot a navigational buoy, confirming that you are on course and " "increasing their morale." msgstr "" "Dina män hittar en navigeringsboj som bekräftar att du är pÃ¥ rätt väg och " "höjer deras moral." msgid "" "The drink at the oasis is refreshing, but offers no further benefit. The " "oasis might help again if you fought a battle first." msgstr "" "En klunk frÃ¥n oasen är uppfriskande, men den erbjuder ingen ytterligare " "förmÃ¥n. Oasen kan hjälpa till igen om du slÃ¥ss i en strid först." msgid "" "A drink at the oasis fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" "En klunk frÃ¥n oasen fyller din trupper med styrka och lyfter deras stämning. " "Du kan resa en bit till idag." msgid "" "The drink at the watering hole is refreshing, but offers no further benefit. " "The watering hole might help again if you fought a battle first." msgstr "" "En klunk frÃ¥n vattenhÃ¥let är uppfriskande, men den erbjuder ingen " "ytterligare förmÃ¥n. Oasen kan hjälpa till igen om du slÃ¥ss i en strid först." msgid "" "A drink at the watering hole fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" "En klunk frÃ¥n vattenhÃ¥let fyller din trupper med styrka och lyfter deras " "stämning. Du kan resa en bit till idag." msgid "" "It doesn't help to pray twice before a battle. Come back after you've fought." msgstr "" "Det hjälper inte att be tvÃ¥ gÃ¥nger före en strid. Kom tillbaka när du har " "stridit." msgid "A visit and a prayer at the temple raises the morale of your troops." msgstr "Ett besök och en bön i templet höjer dina truppers moral." msgid "" "An old Knight appears on the steps of the gazebo. \"I am sorry, my liege, I " "have taught you all I can.\"" msgstr "" "En gammal riddare dyker upp pÃ¥ lusthusets trapp. \"Jag är ledsen, min herre. " "Jag har lärt dig allt jag kan.\"" msgid "" "An old Knight appears on the steps of the gazebo. \"My liege, I will teach " "you all that I know to aid you in your travels.\"" msgstr "" "En gammal riddare dyker upp pÃ¥ lusthusets trapp. \"Min herre, jag ska lära " "dig allt jag kan för att hjälpa dig pÃ¥ dina resor.\"" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he says, \"I would give you an artifact as a reward, but " "you're all full.\"" msgstr "" "Du har dragit ut en skeppsbruten man frÃ¥n en säker död i en oförlÃ¥tande " "ocean. Han säger tacksamt, \"Jag skulle ge dig en artefakt som belöning, men " "du har ingen plats för den.\"" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he rewards you for your act of kindness by giving you the " "%{art}." msgstr "" "Du har dragit ut en skeppsbruten man frÃ¥n en säker död i en oförlÃ¥tande " "ocean. Tacksamt ger han dig %{art} för din vänlighet." msgid "A leprechaun offers you the %{art} for the small price of %{gold} Gold." msgstr "En pyssling erbjuder dig en %{art} för en lite summa pÃ¥ %{gold} guld." msgid "" "A leprechaun offers you the %{art} for the small price of %{gold} Gold and " "%{count} %{res}." msgstr "" "En pyssling erbjuder dig en %{art} för en lite summa pÃ¥ %{gold} guld och " "%{count} %{res}." msgid "Do you wish to buy this artifact?" msgstr "Vill du köpa denna artefakt?" msgid "" "You try to pay the leprechaun, but realize that you can't afford it. The " "leprechaun stamps his foot and ignores you." msgstr "" "Du försöker att betala pysslingen, men du inser att du inte har rÃ¥d. " "Pysslingen stampar med sin fot och ignorerar dig." msgid "" "Insulted by your refusal of his generous offer, the leprechaun stamps his " "foot and ignores you." msgstr "" "Förolämpad av din vägran att neka hans generösa erbjudande, stampar " "pysslingen med sin fot och ignorerar dig." msgid "You've found the artifact: " msgstr "Du har hittat artefakten: " msgid "" "You've found the humble dwelling of a withered hermit. The hermit tells you " "that he is willing to give the %{art} to the first wise person he meets." msgstr "" "Du har funnit en enkel boning där en väderbiten eremit är bosatt. Eremiten " "berättar för dig att han är villig att ge %{art} till den fösta visa person " "som han träffar." msgid "" "You've come across the spartan quarters of a retired soldier. The soldier " "tells you that he is willing to pass on the %{art} to the first true leader " "he meets." msgstr "" "Du rider förbi ett spartanskt hus där det bor en pensionerad soldat. " "Soldaten berättar för dig att han tänker ge %{art} till den första riktiga " "ledaren han träffar." msgid "" "You've encountered a strange person with a hat and an owl on it. He tells " "you that he is willing to give %{art} if you have %{skill}." msgstr "" "Du stöter pÃ¥ en märklig hattklädd figur med en uggla pÃ¥ hatten. Han berättar " "att han kan ge dig %{art} om du besitter förmÃ¥gan %{skill}." msgid "" "You come upon an ancient artifact. As you reach for it, a pack of Rogues " "leap out of the brush to guard their stolen loot." msgstr "" "Du stöter pÃ¥ en urÃ¥ldrig artefakt. När du närmar dig den dyker ett gäng " "Rövare upp frÃ¥n buskarna för att vakta deras stöldgods." msgid "" "Through a clearing you observe an ancient artifact. Unfortunately, it's " "guarded by a nearby %{monster}. Do you want to fight the %{monster} for the " "artifact?" msgstr "" "Genom en glänta ser du en urÃ¥ldrig artefakt. Tyvärr vaktas artefakten av en " "%{monster}. Vill du strida mot %{monster} för artefakten?" msgid "Victorious, you take your prize, the %{art}." msgstr "Segrande tar du ditt pris, %{art}." msgid "" "Discretion is the better part of valor, and you decide to avoid this fight " "for today." msgstr "" "Det är bättre att fly än att illa fäkta, sÃ¥ du bestämmer dig för att undvika " "denna strid för idag." msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold pieces." msgstr "" "Efter att ha spenderat flera timmar pÃ¥ att försöka fiska upp kistan ur sjön, " "öppnar du den och hittar %{gold} guldmynt." msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold and the %{art}." msgstr "" "Efter att ha spenderat flera timmar pÃ¥ att försöka fiska upp kistan ur sjön, " "öppnar du den och hittar %{gold} guld och %{art}." msgid "" "After spending hours trying to fish the chest out of the sea, you open it, " "only to find it empty." msgstr "" "Efter att ha spenderat timmar pÃ¥ att försöka fiska upp skattkistan ur sjön, " "öppnar du den, bara för att hitta den tom." msgid "" "After scouring the area, you fall upon a hidden treasure cache. You may take " "the gold or distribute the gold to the peasants for experience. Do you wish " "to keep the gold?" msgstr "" "Efter att ha letat igenom omrÃ¥det hittar du en skattgömma. Du kan välja att " "beslagta skatten själv eller dela ut skatten med bönderna för erfarenhet.\n" "Vill du behÃ¥lla skatten själv?" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "%{gold} gold pieces." msgstr "" "Efter att ha renskrubbat omrÃ¥det, finner du en gömd kista som innehÃ¥ller de " "%{gold} guldmynten." msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "ancient artifact %{art}." msgstr "" "Efter att ha renskrubbat omrÃ¥det, finner du en gömd kista som innehÃ¥ller den " "antika artefakten %{art}." msgid "" "You stumble upon a dented and tarnished lamp lodged deep in the earth. Do " "you wish to rub the lamp?" msgstr "" "Du stöter pÃ¥ en bucklig och matt lampa djupt ner i jorden. Vill du gnida " "lampan?" msgid "" "You have taken control of the local Alchemist shop. It will provide you with " "%{count} unit of Mercury per day." msgstr "" "Du har tagit kontroll över den lokala Alkemistaffären. Den kommer att förse " "dig med %{count} enhet kvicksilver per dag." msgid "" "You gain control of a sawmill. It will provide you with %{count} units of " "wood per day." msgstr "" "Du tar kontroll över ett sÃ¥gverk. Den kommer att förse dig med %{count} " "enheter av ved per dag." msgid "" "You gain control of an ore mine. It will provide you with %{count} units of " "ore per day." msgstr "" "Du tar kontroll över en malmgruva. Den kommer att förse dig med %{count} " "enheter av malm per dag." msgid "" "You gain control of a sulfur mine. It will provide you with %{count} unit of " "sulfur per day." msgstr "" "Du tar kontroll över en svavelgruva. Den kommer att förse dig med %{count} " "enhet av svavel per dag." msgid "" "You gain control of a crystal mine. It will provide you with %{count} unit " "of crystal per day." msgstr "" "Du tar kontroll över en kristallgruva. Den kommer att förse dig med %{count} " "enhet av kristaller per dag." msgid "" "You gain control of a gem mine. It will provide you with %{count} unit of " "gems per day." msgstr "" "Du tar kontroll över en ädelstensgruva. Den kommer att förse dig med " "%{count} enhet av ädelstenar per dag." msgid "" "You gain control of a gold mine. It will provide you with %{count} gold per " "day." msgstr "" "Du tar kontroll över en guldgruva. Den kommer att förse dig med %{count} " "guld per dag." msgid "" "The lighthouse is now under your control, and all of your ships will now " "move further each day." msgstr "" "Fyrtornet är nu under din kontroll och alla dina skepp kommer nu förflytta " "sig längre per dag." msgid "You gain control of a %{name}." msgstr "Du fick kontroll över en %{name}." msgid "" "You come upon an abandoned gold mine. The mine appears to be haunted. Do you " "wish to enter?" msgstr "" "Du stöter pÃ¥ en övergiven guldgruva. Gruvan verkar vara hemsökt. Vill du gÃ¥ " "in?" msgid "You beat the Ghosts and are able to restore the mine to production." msgstr "Du besegrade spökena och kan Ã¥terställa gruvans produktion." msgid "" "A group of %{monster} with a desire for greater glory wish to join you. Do " "you accept?" msgstr "" "En grupp %{monster} med en önskan för större ära vill följa med dig. " "Accepterar du det?" msgid "As you approach the dwelling, you notice that there is no one here." msgstr "När du närmar dig boningen upptäcker du att det inte är nÃ¥gon där." msgid "" "You search the ruins, but the Medusas that used to live here are gone. " "Perhaps there will be more next week." msgstr "" "Du söker igenom ruinerna, men Medusorna som brukade bo här är borta. Kanske " "det kommer att finnas nÃ¥gra där nästa vecka." msgid "" "You've found some Medusas living in the ruins. They are willing to join your " "army for a price. Do you want to recruit Medusas?" msgstr "" "Du har hittat nÃ¥gra Medusor som bor i ruinerna. Dem är beredd att förena sig " "med din armé för ett pris. Vill du rekrytera Medusorna?" msgid "" "You've found a Sprite Tree City. Unfortunately, none of the Sprites living " "there wish to join an army. Maybe next week." msgstr "" "Du har hittat ett Trädhus. Tyvärr, vill ingen av Féerna ansluta sig till din " "armé. Kanske bättre lycka nästa vecka." msgid "" "Some of the Sprites living in the tree city are willing to join your army " "for a price. Do you want to recruit Sprites?" msgstr "" "NÃ¥gra Féer som bor i en trädhusby vill ansluta sig till din armé för en " "kostnad. Vill du rekrytera Féerna?" msgid "" "A colorful Rogues' wagon stands empty here. Perhaps more Rogues will be here " "later." msgstr "" "En färgglad Rövarvagn stÃ¥r tom här. Kanske det finns fler Rövare här senare." msgid "" "Distant sounds of music and laughter draw you to a colorful wagon housing " "Rogues. Do you wish to have any Rogues join your army?" msgstr "" "Avlägsna ljud av musik och skratt som kommer frÃ¥n en färgglad Rövarvagn. " "Vill du rekrytera nÃ¥gra Rövare?" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. The " "tents are unoccupied. Perhaps more Nomads will be here later." msgstr "" "Du ser en lägerplats med slitna tält som fladdrar i vinden. Tälten är " "obebodda för tillfället. Kanske kommer Nomaderna tillbaka senare." msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. Do you " "wish to have any Nomads join you during your travels?" msgstr "" "Du ser en lägerplats med slitna tält som fladdrar i vinden. En grupp Nomader " "som lever i tälten erbjuder sig sina tjänster. Vill du rekrytera Nomader?" msgid "The pit of mud bubbles for a minute and then lies still." msgstr "Lerpölen bubblar för en stund innan den förblir stilla." msgid "" "As you approach the bubbling pit of mud, creatures begin to climb out and " "position themselves around it. In unison they say: \"Mother Earth would like " "to offer you a few of her troops. Do you want to recruit Earth Elementals?\"" msgstr "" "När du närmar dig den bubblande lerpölen börjar varelser stiga upp och " "ställa sig runt den. I samklang säger de: \"Moder Jord vill erbjuda dig " "nÃ¥gra av hennes trupper. Vill du rekrytera Jordväsen?\"" msgid "You enter the structure of white stone pillars, and find nothing." msgstr "Du kliver in i byggnaden av vita pelare, men du hittar ingenting." msgid "" "White stone pillars support a roof that rises up to the sky. As you enter " "the structure, the dead air of the outside gives way to a whirling gust that " "almost pushes you back out. The air current materializes into a barely " "visible form. The creature asks, in what can only be described as a loud " "whisper: \"Why have you come? Are you here to call upon the forces of the " "air?\"" msgstr "" "Vita stenpelare stöder ett tak som stiger upp mot himmelen. När du gÃ¥r in sÃ¥ " "möts du av en kraftig vindpust som nästan fÃ¥r dig omkull. Vinden " "materialiserar sig inför dig i en halvt skenbar form. Varelsen frÃ¥gar dig " "med en röst som bäst kan beskrivas som en högljudd viskning: \"Varför har du " "kommit? Är du här för att framkalla vindens och luftens styrka?\"" msgid "No Fire Elementals approach you from the lava pool." msgstr "Inga Eldväsen kommer upp ur lavapölen." msgid "" "Beneath a structure that serves to hold in heat, Fire Elementals move about " "in a fiery pool of molten lava. A group of them approach you and offer their " "services. Would you like to recruit Fire Elementals?" msgstr "" "Du stöter pÃ¥ en krater med glödande lava och eld där Eldväsen hoppar i och " "ur en pöl med lava. En grupp av Eldväsen gÃ¥r fram till dig och erbjuder dig " "sina tjänster. Vill du rekrytera Eldväsen?" msgid "A face forms in the water for a moment, and then is gone." msgstr "" "Ett ansikte formar sig i vattnet för ett ögonblick, bara för att försvinna " "sen." msgid "" "Crystalline structures cast shadows over a small reflective pool of water. " "You peer into the pool, and a face that is not your own peers back. It asks: " "\"Would you like to call upon the powers of water?\"" msgstr "" "Kristallstrukturer kastar skuggor över en liten reflekterande vattenpöl. Du " "tittar ned i pölen och ett ansikte som inte är ditt tittar tillbaka. Den " "frÃ¥gar: \"Vill du kalla pÃ¥ vattnets krafter?\"" msgid "This burial site is deathly still." msgstr "Denna begravningsplats är dödstyst." msgid "" "Restless spirits of long dead warriors seeking their final resting place " "offer to join you in hopes of finding peace. Do you wish to recruit ghosts?" msgstr "" "Rastlösa själar frÃ¥n döda krigare söker efter viloplats och erbjuder sig att " "ansluta sig till dig i hopp och att finna frid. Vill du rekrytera Spöken?" msgid "" "The City of the Dead is empty of life, and empty of unlife as well. Perhaps " "some undead will move in next week." msgstr "" "Dödens Stad är tom pÃ¥ liv, den är även tom pÃ¥ dött liv. Kanske nästa vecka " "har det dykt upp nÃ¥gra odöda." msgid "" "Some Liches living here are willing to join your army for a price. Do you " "want to recruit Liches?" msgstr "" "NÃ¥gra Lichar som befinner sig är villiga att lÃ¥ta sig rekryteras för ett " "pris. Vill du rekrytera Lichar?" msgid "" "You've found the ruins of an ancient city, now inhabited solely by the " "undead. Will you search?" msgstr "" "Du har hittat ruinerna av en antik stad, som nu är endast är bebodd av de " "odöda. Vill du söka?" msgid "" "Some of the surviving Liches are impressed by your victory over their " "fellows, and offer to join you for a price. Do you want to recruit Liches?" msgstr "" "NÃ¥gra Lichar är imponerade över din seger över deras gelikar och erbjuder " "sig att ansluta till din armé för ett pris. Vill du rekrytera Lichar?" msgid "" "You've found one of those bridges that Trolls are so fond of living under, " "but there are none here. Perhaps there will be some next week." msgstr "" "Du har hittat en av dem där broarna som Troll brukar bo under, men det finns " "inga där. Kanske det finns nÃ¥gra där nästa vecka." msgid "" "Some Trolls living under a bridge are willing to join your army, but for a " "price. Do you want to recruit Trolls?" msgstr "" "NÃ¥gra troll som bor under bron är beredd pÃ¥ att förena sig med din armé mot " "ett pris. Vill du rekrytera troll?" msgid "Trolls living under the bridge challenge you. Will you fight them?" msgstr "Trollen som bor under bron utmanar dig. Vill du slÃ¥ss mot dem?" msgid "" "A few Trolls remain, cowering under the bridge. They approach you and offer " "to join your forces as mercenaries. Do you want to buy any Trolls?" msgstr "" "NÃ¥gra fÃ¥ Troll kvarstÃ¥r som tagit skydd under bron. De gÃ¥r fram till dig och " "erbjuder sig att ansluta sig till din armé som legoknektar. Vill du köpa " "nÃ¥gra Troll till din armé?" msgid "" "The Dragon city has no Dragons willing to join you this week. Perhaps a " "Dragon will become available next week." msgstr "" "Drakstaden har inga Drakar som kan förena sig med dig denna vecka. Det " "kanske finns en tillgänglig Drake där nästa vecka." msgid "" "The Dragon city is willing to offer some Dragons for your army for a price. " "Do you wish to recruit Dragons?" msgstr "" "Drakstaden är beredd att erbjuda nÃ¥gra drakar till din armé för ett pris. " "Vill du rekrytera drakar?" msgid "" "You stand before the Dragon City, a place off-limits to mere humans. Do you " "wish to violate this rule and challenge the Dragons to a fight?" msgstr "" "Du stÃ¥r inför Drakstaden, en plats pÃ¥ förbjudet omrÃ¥de för människor. Vill " "du strida mot denna regel och utmana drakarna i en strid?" msgid "" "Having defeated the Dragon champions, the city's leaders agree to supply " "some Dragons to your army for a price. Do you wish to recruit Dragons?" msgstr "" "Efter att ha besegrat Drakkrigarna gÃ¥r stadens ledare med pÃ¥ att förse nÃ¥gra " "Drakar till din armé till ett pris. Vill du rekrytera Drakar?" msgid "From the observation tower, you are able to see distant lands." msgstr "FrÃ¥n observationstornet kan du se avlägsna länder." msgid "" "The spring only refills once a week, and someone's already been here this " "week." msgstr "" "Källan fylls bara pÃ¥ en gÃ¥ng per vecka och nÃ¥gon har redan varit här denna " "vecka." msgid "" "A drink at the spring is supposed to give you twice your normal spell " "points, but you are already at that level." msgstr "" "En klunk frÃ¥n källan ska ge hjälten dubbelt sÃ¥ mÃ¥nga besvärjelsepoäng, men " "hjälten har redan sÃ¥ mÃ¥nga besvärjelsepoäng." msgid "" "A drink from the spring fills your blood with magic! You have twice your " "normal spell points in reserve." msgstr "" "En klunk frÃ¥n källan fyller ditt blod med magi! Du har nu dubbelt sÃ¥ fler " "besvärjelsepoäng." msgid "" "Recognizing you, the butler refuses to admit you. \"The master,\" he says, " "\"will not see the same student twice.\"" msgstr "" "Butlern känner igen dig och vägrar släpa in dig. \"Mästaren,\" säger han, " "\"vill inte se samma student tvÃ¥ gÃ¥nger.\"" msgid "" "The butler admits you to see the master of the house. He trains you in the " "four skills a hero should know." msgstr "" "Butlern släpper in dig för att fÃ¥ träffa husets mästaren. Han tränar dig i " "de fyra färdigheterna som en hjälte borde veta." msgid "" "The butler opens the door and looks you up and down. \"You are neither " "famous nor diplomatic enough to be admitted to see my master,\" he sniffs. " "\"Come back when you think yourself worthy.\"" msgstr "" "Butlern öppnar dörren och tittar upp och ned pÃ¥ dig. \"Du är varken berömd " "eller diplomatisk nog för att bli insläppt och för att fÃ¥ se min mästare,\" " "han sniffar. \"Kom tillbaka när du tror att du är dig själv värdig.\"" msgid " and " msgstr " och " msgid "" "All of the %{monsters} you have in your army have been trained by the battle " "masters of the fort. Your army now contains %{monsters2}." msgstr "" "Alla %{monsters} du har i din armé har tränats av stridsmästarna i fortet. " "Din armé innehÃ¥ller nu %{monsters2}." msgid "" "An unusual alliance of Ogres, Orcs, and Dwarves offer to train (upgrade) any " "such troops brought to them. Unfortunately, you have none with you." msgstr "" "En brokig allians av Jättar, Orcher och Dvärgar erbjuder att träna " "(uppgradera) om du har nÃ¥gra sÃ¥dana trupper i din armé. Tyvärr har du inga " "sÃ¥dana trupper." msgid "All of your %{monsters} have been upgraded into %{monsters2}." msgstr "Alla dina %{monsters} har blivit uppgraderade till %{monsters2}." msgid "" "A blacksmith working at the foundry offers to convert all Pikemen and " "Swordsmen's weapons brought to him from iron to steel. He also says that he " "knows a process that will convert Iron Golems into Steel Golems. " "Unfortunately, you have none of these troops in your army, so he can't help " "you." msgstr "" "En smed som jobbar pÃ¥ gjuteriet erbjuder att konvertera Pikenerarnas och " "Svärdsmännens alla vapen frÃ¥n järn till stÃ¥l. Han säger även att han vet en " "process som konverterar Järngolems till StÃ¥lgolems. Olyckligtvis har du inte " "nÃ¥gon av dessa i dina arméer, sÃ¥ han kan inte hjälpa dig." msgid "" "The captain looks at you with surprise and says:\n" "\"You already have all the maps I know about. Let me fish in peace now.\"" msgstr "" "Kaptenen ser pÃ¥ dig med en överraskad blick och säger:\n" "\"Du har redan bättre kartor än vad jag har. LÃ¥t mig fÃ¥ fiska ifred nu.\"" msgid "" "A retired captain living on this refurbished fishing platform offers to sell " "you maps of the sea he made in his younger days for 1,000 gold. Do you wish " "to buy the maps?" msgstr "" "En pensionerad kapten bor pÃ¥ denna renovererade fiskeplattform. Han erbjuder " "dig att köpa hans kartor, som han själv har gjort i hans yngre dagar, för " "1,000 guld. Vill du köpa kartorna?" msgid "" "The captain sighs. \"You don't have enough money, eh? You can't expect me " "to give my maps away for free!\"" msgstr "" "Kaptenen suckar. \"Du har inte tillräckligt med pengar, va? Du kan inte " "förvänta mig att ge bort mina kartor gratis!\"" msgid "" "You come upon an obelisk made from a type of stone you have never seen " "before. Staring at it intensely, the smooth surface suddenly changes to an " "inscription. The inscription is a piece of a lost ancient map. Quickly you " "copy down the piece and the inscription vanishes as abruptly as it appeared." msgstr "" "Du stöter pÃ¥ en obelisk gjord av en stentyp som du aldrig tidigare skÃ¥dat. " "När du stirrar pÃ¥ den lena ytan ändras den till en inskription. " "Inskriptionen är en del av en förlorad antik karta. Du kopierar snabbt av " "den och inskriptionen försvinner lika tvärt som den kom dit." msgid "You have already been to this obelisk." msgstr "Du har redan varit till denna obelisk." msgid "" "Upon your approach, the tree opens its eyes in delight. \"It is good to see " "you, my student. I hope my teachings have helped you.\"" msgstr "" "När du närmar dig öppnar trädet sina ögon i glädje. \"Vad kul att se dig, " "min student. Jag hoppas mina undervisningar har hjälpt dig.\"" msgid "" "Upon your approach, the tree opens its eyes in delight. \"Ahh, an " "adventurer! Allow me to teach you a little of what I have learned over the " "ages.\"" msgstr "" "När du närmar dig öppnar trädet sina ögon i glädje. \"Ã…h, en äventyrare! LÃ¥t " "mig lära dig lite grann om vad jag har lärt mig under Ã¥ren.\"" msgid "Upon your approach, the tree opens its eyes in delight." msgstr "När du närmar dig öppnar trädet sina ögon i glädje." msgid "" "\"Ahh, an adventurer! I will be happy to teach you a little of what I have " "learned over the ages for a mere %{count} %{res}.\"" msgstr "" "\"Aha, en äventyrare! Jag skulle gladeligen lära dig lite om vad jag har " "lärt mig under alla Ã¥r för endast %{count} %{res}.\"" msgid "(Just bury it around my roots.)" msgstr "(Begrav det bara under mina rötter.)" msgid "Tears brim in the eyes of the tree." msgstr "TÃ¥rar fyller trädets ögon." msgid "\"I need %{count} %{res}.\"" msgstr "\"Jag behöver %{count} %{res}.\"" msgid "it whispers. (sniff) \"Well, come back when you can pay me.\"" msgstr "viskar den. (Sniff) \"NÃ¥ja, kom tillbaka när du kan betala mig.\"" msgid "" "Nestled among the trees sits a blind seer. After you explain the intent of " "your journey, the seer activates his crystal ball, allowing you to see the " "strengths and weaknesses of your opponents." msgstr "" "Hukad inne bland nÃ¥gra träd sitter en blind sierska. När du förklarat ditt " "mÃ¥l med dina resor sÃ¥ tar sierskan fram sin kristallkula och lÃ¥ter dig se " "dina fienders styrkor och svagheter." msgid "" "The entrance to the cave is dark, and a foul, sulfurous smell issues from " "the cave mouth. Will you enter?" msgstr "" "IngÃ¥ngen till grottan är mörk och en otäck, svavelaktig lukt strömmar frÃ¥n " "grottans mynning. Vill du gÃ¥ in?" msgid "Except for evidence of a terrible battle, the cave is empty." msgstr "" "Förutom bevis pÃ¥ att det har varit ett förskräcklig strid här, är grottan " "tom." msgid "" "You find a powerful and grotesque Demon in the cave. \"Today,\" it rasps, " "\"you will fight and surely die. But I will give you a choice of deaths. You " "may fight me, or you may fight my servants. Do you prefer to fight my " "servants?\"" msgstr "" "Du finner en mäktig och grotesk Demon i en Grotta. \"Idag\" rasslar den till " "\"kommer du att strida och säkerligen dö. Men jag kommer ge dig valet för " "hur du vill dö. Du kan välja att möta mig eller mina undersÃ¥tar i strid. " "Föredrar du att slÃ¥ss mot mina undersÃ¥tar?\"" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points." msgstr "" "Demonen vrÃ¥lar och attackerar! Efter en kort intensiv strid sÃ¥ dräper du " "monstret och fÃ¥r %{exp} erfarenhetspoäng." msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points and " "%{count} gold." msgstr "" "Demonen vrÃ¥lar och attackerar! Efter en kort intensiv strid sÃ¥ dräper du " "monstret och fÃ¥r %{exp} erfarenhetspoäng och %{count} guld." msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and find the %{art} in the back of the cave." msgstr "" "Demonen vrÃ¥lar och attackerar! Efter en kort intensiv strid sÃ¥ dräper du " "monstret och finner artefakten %{art} i änden av grottan." msgid "" "Seeing that you do not have %{count} gold, the demon slashes you with its " "claws, and the last thing you see is a red haze." msgstr "" "Demonen ser att du inte har %{count} guld och river dig med sina klor. Det " "sista du ser är en röd dimma." msgid "" "The Demon leaps upon you and has its claws at your throat before you can " "even draw your sword. \"Your life is mine,\" it says. \"I will sell it back " "to you for %{count} gold.\"" msgstr "" "Demonen hoppar pÃ¥ dig och har din hals i sina klor innan du ens hinner " "reagera. \"Ditt liv är mitt\" säger den. \"Jag kan sälja tillbaka ditt liv " "till dig för %{count} guld.\"" msgid "" "Upon defeating the daemon's servants, you find a hidden cache with %{count} " "gold." msgstr "" "Medan du besegrar demonens underhuggare hittar du en dolt förrÃ¥d med " "%{count} guld." msgid "" "As you enter the Alchemist's Tower, a hobbled, graying man in a brown cloak " "makes his way towards you." msgstr "" "När du kliver in i Alkemistens Torn sÃ¥ träder en hukad och grÃ¥nad man i brun " "kappa fram och gÃ¥r emot dig." msgid "He checks your pack, and sees that you have 1 cursed item." msgid_plural "" "He checks your pack, and sees that you have %{count} cursed items." msgstr[0] "" "Han kontrollerar din packning, och upptäcker att du har 1 förbannat objekt." msgstr[1] "" "Han kontrollerar din packning, och upptäcker att du har %{count} förbannade " "objekt." msgid "For %{gold} gold, the alchemist will remove it for you. Do you pay?" msgid_plural "" "For %{gold} gold, the alchemist will remove them for you. Do you pay?" msgstr[0] "För %{gold} guld kan alkemisten ta bort den Ã¥t dig. Vill du betala?" msgstr[1] "För %{gold} guld kan alkemisten ta bort dom Ã¥t dig. Vill du betala?" msgid "" "After you consent to pay the requested amount of gold, the alchemist grabs " "the cursed artifact and throws it into his magical cauldron." msgid_plural "" "After you consent to pay the requested amount of gold, the alchemist grabs " "all cursed artifacts and throws them into his magical cauldron." msgstr[0] "" "Efter du kommit överens med att betala guldsumman, sÃ¥ tar alkemisten den " "förbannade artefakten och slänger ner den i hans magiska kittel." msgstr[1] "" "Efter du kommit överens med att betala guldsumman, sÃ¥ tar alkemisten de " "förbannade artefakterna och slänger ner de i hans magiska kittel." msgid "" "You hear a voice from behind the locked door, \"You don't have enough gold " "to pay for my services.\"" msgstr "" "Du hör en röst bakom den lÃ¥sta dörren, \"Du har inte tillräckligt mycket " "guld att betala mina tjänster.\"" msgid "" "You hear a voice from high above in the tower, \"Go away! I can't help you!\"" msgstr "" "Du hör en röst frÃ¥n högt ovan i tornet, \"Ge dig väg! Jag kan inte hjälpa " "dig!\"" msgid "" "The head groom speaks to you, \"That is a fine looking horse you have. I am " "afraid we can give you no better, but the horses your cavalry are riding " "look to be of poor breeding stock. We have many trained war horses which " "would aid your riders greatly. I insist you take them.\"" msgstr "" "Stallmästaren talar till dig, \"Det är en fin häst du har. Tyvärr är jag " "rädd att jag inte kan erbjuda nÃ¥gon bättre, men ditt kavalleris hästar har " "sett bättre dagar. Vi har mÃ¥nga tränade krigshästar som skulle vara " "behjälpliga dina ryttare. Jag insisterar att du tar dem.\"" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, he will " "grow tired in a week. You must also let me give better horses to your " "mounted soldiers, their horses look shoddy and weak.\"" msgstr "" "När du närmar dig stallet sÃ¥ dyker stallmästaren, upp ledandes en fin " "krigshäst. \"Denna hingst kommer hjälpa dig att pÃ¥ dina resor genom landet. " "Men han kommer bli trött efter en vecka. Du mÃ¥ste lÃ¥ta mig förse ditt " "rytteri med bättre och utvilade hästar.\"" msgid "" "The head groom approaches you and speaks, \"You already have a fine horse, " "and have no inexperienced cavalry which might make use of our trained war " "horses.\"" msgstr "" "Huvudhästskötaren närmar sig dig och säger, \"Du har redan en fin häst, och " "inget oerfaret kavalleri som kanske skulle kunna ha nytta av vÃ¥ra tränade " "stridshästar.\"" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, his " "endurance will wane with a lot of heavy riding, and you must return for a " "fresh mount in a week. We also have many fine war horses which could benefit " "mounted soldiers, but you have none we can help.\"" msgstr "" "När du närmar dig stallet sÃ¥ dyker stallmästaren, upp ledandes en fin " "krigshäst. \"Denna hingst kommer hjälpa dig att pÃ¥ dina resor genom landet. " "Men hans uthÃ¥llighet räcker bara en vecka men tung packning, sÃ¥ du fÃ¥r byta " "ut honom om en vecka. Vi har ocksÃ¥ mÃ¥nga fina krighästar som skulle hjälpa " "ditt rytteri men du har ingen som vi kan hjälpa.\"" msgid "The Arena guards turn you away." msgstr "Arenavakterna avvisar dig." msgid "" "You have your crew stop up their ears with wax before the sirens' eerie song " "has any chance of luring them to a watery grave." msgstr "" "Du lÃ¥ter din besättning fylla sina öron med vax innan sirenernas sällsamma " "sÃ¥ng fÃ¥r nÃ¥gon chans att locka ner de i djupet." msgid "" "As the sirens sing their eerie song, your small, determined army manages to " "overcome the urge to dive headlong into the sea." msgstr "" "När sirenerna sjunger sin spökliga sÃ¥ng lyckas din din lilla beslutsamma " "armé överkomma uppmaningen att dyka med huvudet först i havet." msgid "" "An eerie wailing song emanates from the sirens perched upon the rocks. Many " "of your crew fall under its spell, and dive into the water where they drown. " "You are now wiser for the visit, and gain %{exp} experience." msgstr "" "Du lÃ¥ter din besättning stoppa sina öron med vax innan sirenernas spökliga " "sÃ¥ng fÃ¥r tillfälle att lura dem in i en vattengrav. En spöklik jämrande sÃ¥ng " "kommer frÃ¥n sirenerna som balanserar pÃ¥ stenarna. MÃ¥nga i din besättning " "faller i trans och dyker i vattnet, där de drunknar. Du är nu klokare för " "besöket och tjänar %{exp} erfarenhet." msgid "" "In a dazzling display of daring, you break into the local jail and free the " "hero imprisoned there, who, in return, pledges loyalty to your cause." msgstr "" "Genom en djärv inbrytning i det lokala fängelset sÃ¥ friger du en hjälte som " "är fängslad där. I utbyte mot friheten sÃ¥ lÃ¥ter sig hjälten tjäna dig under " "ditt fälttÃ¥g." msgid "" "You already have %{count} heroes, and regretfully must leave the prisoner in " "this jail to languish in agony for untold days." msgstr "" "Du har redan %{count} hjältar och mÃ¥ste Ã¥ngerfullt lämna fÃ¥ngarna i detta " "fängelse för att tvina i smärta för outsägliga dagar." msgid "" "You enter a rickety hut and talk to the magician who lives there. He tells " "you of places near and far which may aid you in your journeys." msgstr "" "Du gÃ¥r in i en ranglig hydda och talar med magikern som bor där. Han " "berättar om ställen när och fjärran som kan vara dig behjälpligt under dina " "resor." msgid "This eye seems to be intently studying its surroundings." msgstr "Detta öga verkar koncentrerat studera sin omgivning." msgid "You come across a giant Sphinx. The Sphinx remains strangely quiet." msgstr "Du möter en gigantisk sfinx. Sfinxen kvarstÃ¥r ovanligt tyst." msgid "" "\"I have a riddle for you,\" the Sphinx says. \"Answer correctly, and you " "shall be rewarded. Answer incorrectly, and you shall be eaten. Do you accept " "the challenge?\"" msgstr "" "\"Jag har en gÃ¥ta till dig,\" säger sfinxen. \"Om du svarar rätt, kommer du " "att bli belönad. Svarar du fel, kommer du att bli uppäten. Accepterar du " "utmaningen?\"" msgid "" "The Sphinx asks you the following riddle:\n" "\n" "'%{riddle}'\n" "\n" "Your answer?" msgstr "" "Sfinxen frÃ¥gar dig följande gÃ¥ta:\n" "\n" "'%{riddle}'\n" "\n" "Vad är ditt svar?" msgid "" "\"You guessed incorrectly,\" the Sphinx says, smiling. The Sphinx swipes at " "you with a paw, knocking you to the ground. Another blow makes the world go " "black, and you know no more." msgstr "" "\"Du gissade fel,\" säger sfinxen med ett leende. Sfinxen slÃ¥r ned dig till " "marken med en tass. Ett till slag fÃ¥r världen att slockna och du vet inte av " "nÃ¥gonting mer." msgid "" "Looking somewhat disappointed, the Sphinx sighs. \"You've answered my riddle " "so here's your reward. Now begone.\"" msgstr "" "NÃ¥got besviken säger sfinxen. \"Du har svarat pÃ¥ min gÃ¥ta sÃ¥ här är din " "belöning. Ge sig av nu.\"." msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "As you speak the magic word, the glowing barrier dissolves into nothingness." msgstr "" "En magisk barriär stÃ¥r framför sig och blockerar din väg. Runor pÃ¥ valvet " "säger,\n" "\"Säg nyckeln och du kan passera.\"\n" "När du säger det magiska ordet tonar den glöder barriären bort." msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "You speak, and nothing happens." msgstr "" "En magisk barriär stÃ¥r framför sig och blockerar din väg. Runor pÃ¥ valvet " "säger,\n" "\"Säg nyckeln och du kan passera.\"\n" "Du pratar, men ingenting händer." msgid "" "You enter the tent and see an old woman gazing into a magic gem. She looks " "up and says,\n" "\"In my travels, I have learned much in the way of arcane magic. A great " "oracle taught me his skill. I have the answer you seek.\"" msgstr "" "Du kliver in i tältet och ser en gammal kvinna stirra pÃ¥ en magisk ädelsten. " "Hon tittar upp och säger,\n" "\"I mina resor har jag lärt mig mycket om mystisk magi. Ett stor orakel " "lärde mig denna färdighet. Jag har svaret du söker.\"" msgid "No spell book is present." msgstr "Det finns ingen Magibok." msgid "" "This spell costs %{mana} spell points. You have no spell points, so you " "cannot cast it." msgstr "" "Den besvärjelsen kostar %{mana} besvärjelsepoäng. Hjälten har slut pÃ¥ " "besvärjelsepoäng sÃ¥ denna kan inte kasta besvärjelsen." msgid "" "This spell costs %{mana} spell points. You only have %{point} spell points, " "so you cannot cast it." msgstr "" "Den besvärjelsen kostar %{mana} besvärjelsepoäng. Hjälten har bara %{point} " "besvärjelsepoäng, sÃ¥ hjälten kan inte kasta besvärjelsen." msgid "The spell was not found." msgstr "Besvärjelsen hittades ej." msgid "Only heroes can cast this spell." msgstr "Endast hjältar kan kasta denna besvärjelsen." msgid "This hero is not able to cast adventure spells." msgstr "Hjälten kan inte kasta kartbesvärjelser." msgid "Your hero is too tired to cast this spell today. Try again tomorrow." msgstr "" "Din hjälte är för trött för att kasta en besvärjelse idag. Försök igen " "imorgon." msgid "This spell cannot be cast on a boat." msgstr "Denna besvärjelse kan inte kastas frÃ¥n skepp." msgid "This spell can only be cast near an ocean." msgstr "Denna besvärjelse kan bara kastas nära hav." msgid "" "There are no boats available and no ocean adjacent to the hero where this " "spell will work." msgstr "" "Det finns inget skepp tillgängligt och inget ledig strand i närheten för att " "hjälten skall kunna kasta denna besvärjelse." msgid "There are no boats available for this spell." msgstr "Det finns inget tillgängligt skepp för att kasta denna besvärjelsen." msgid "There is no ocean adjacent to the hero where this spell will work." msgstr "" "Det finns ingen ledig strand i närheten för att hjälten skall kunna kasta " "denna besvärjelse." msgid "" "You do not own any town or castle that is not currently occupied by a hero. " "This spell will have no effect." msgstr "" "Du äger för närvarande inte nÃ¥gon stad eller slott som inte är upptaget. " "Besvärjelsen har ingen verkan." msgid "This hero is already in a town, so this spell will have no effect." msgstr "" "Hjälten är redan i en stad, sÃ¥ besvärjelsen kommer inte ha nÃ¥gon verkan." msgid "" "The nearest town is %{town}.\n" "\n" "This town is occupied by your hero %{hero}." msgstr "" "Närmsta stad är %{town}.\n" "\n" "Denna stad besöks av din hjälte %{hero}." msgid "This spell is already in effect." msgstr "Denna besvärjelse används redan." msgid "" "No opponent neither has nor can have any hero under their command anymore. " "Casting this spell will have no effect." msgstr "" "Ingen motstÃ¥ndare kan ha hjältar under dennes befäl längre. Att kasta denna " "besvärjelsen bringar ingen fördel." msgid "" "No opponent has a hero under their command at this time. Casting this spell " "will have no effect." msgstr "" "Ingen motstÃ¥ndare har en hjälte under dennes befäl för tillfället. Att kasta " "denna besvärjelse bringar ingen fördel." msgid "" "You must be within %{count} spaces of a monster for the Visions spell to " "work." msgstr "" "Hjälten mÃ¥ste befinna sig inom %{count} rutor frÃ¥n ett monster för att " "Synerbesvärjelsen ska ge effekt." msgid "" "You must be standing on the entrance to a mine (sawmills and alchemist labs " "do not count) to cast this spell." msgstr "" "Hjälten mÃ¥ste stÃ¥ vid ingÃ¥ngen till en gruva (sÃ¥gverk och alkemistlabb " "räknas ej) för att kasta denna besvärjelse." msgid "You must first defeat the ghosts guarding the mine to cast this spell." msgstr "" "DÃ¥ mÃ¥ste först besegra Spökena som vaktar gruvan för att hjälten ska kasta " "besvärjelsen." msgid "" "There are already at least as many elementals guarding the mine as this hero " "can generate. Casting this spell will have no effect." msgstr "" "Det finns redan minst sÃ¥ mÃ¥nga Elementväsen som vaktar gruvan som " "besvärjelsen kan frambringa. Att kasta denne besvärjelsen kommer inte ge " "nÃ¥gon ytterligare fördel." msgid "%{name} the %{race} (Level %{level})" msgstr "%{race}n %{name} (NivÃ¥ %{level})" msgid "" "'Spread' combat formation spreads the hero's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "'Utspridd' stridsformation sprider ut hjältens trupper över hela slagfältet " "med minst ett tomt mellanrum mellan varje trupp." msgid "" "'Grouped' combat formation bunches the hero's army together in the center of " "their side of the battlefield." msgstr "" "'Grupperad' stridsformation buntar ihop hjältens trupper till mitten av " "dennes sida av slagfältet." msgid "Are you sure you want to dismiss this Hero?" msgstr "Är du säker pÃ¥ att du vill avskeda denna Hjälte?" msgid "View Experience Info" msgstr "Visa information för erfarenhet" msgid "View Spell Points Info" msgstr "Visa information för besvärjelsepoäng" msgid "Set army combat formation to 'Spread'" msgstr "Ställ in arméns stridsformation pÃ¥ 'Utspridd'" msgid "Set army combat formation to 'Grouped'" msgstr "Ställ in arméns stridsformation pÃ¥ 'Grupperad'" msgid "Exit Hero Screen" msgstr "Lämna Hjältevyn" msgid "You cannot dismiss a hero in a castle" msgstr "Du kan inte avskeda en Hjälte i ett slott." msgid "Dismissal of %{name} the %{race} is prohibited by scenario" msgstr "Avskedandet av %{race}n %{name} är förbjudet i detta scernario" msgid "Dismiss %{name} the %{race}" msgstr "Avskeda %{race}n %{name}" msgid "Show previous hero" msgstr "Visa föregÃ¥ende hjälte" msgid "Show next hero" msgstr "Visa nästa hjälte" msgid "Blood Morale" msgstr "Blodtörst" msgid "%{morale} Morale" msgstr "%{morale} Stridsmoral" msgid "%{luck} Luck" msgstr "%{luck} Tur" msgid "Current Luck Modifiers:" msgstr "PÃ¥verkar Tur:" msgid "Current Morale Modifiers:" msgstr "PÃ¥verkar Stridsmoral:" msgid "Entire army is undead, so morale does not apply." msgstr "Hela armén är odöd, sÃ¥ stridsmoral gäller inte." msgid "" "Current experience %{exp1}.\n" " Next level %{exp2}." msgstr "" "Nuvarande erfarenhet: %{exp1}. \n" "Nästa nivÃ¥: %{exp2}." msgid "Level %{level}" msgstr "NivÃ¥ %{level}" msgid "" "%{name} currently has %{point} spell points out of a maximum of %{max}. The " "maximum number of spell points is 10 times the hero's knowledge. It is " "occasionally possible for the hero to have more than their maximum spell " "points via special events." msgstr "" "%{name} har för närvarande %{point} besvärjelsepoäng av totalt %{max}. " "Maximalt antal besvärjelsepoäng är 10 gÃ¥nger hjältens KunskapssnivÃ¥. \n" "\n" "\n" "Det är möjligt att fÃ¥ mer än maximalt antal besvärjelsepoäng genom särskilda " "händelser." msgid "%{name1} meets %{name2}" msgstr "%{name1} möter %{name2}" msgid "Enemy heroes are now fully identifiable." msgstr "Fiendehjältar är nu fullt identifierade." msgid "Identify Hero" msgstr "Identifiera Hjälte" msgid "Select town to port to." msgstr "Välj stad att teleportera till." msgid "Town Portal" msgstr "Stadsportal" msgid "The creatures are willing to join us!" msgstr "Varelserna är villiga att följa med oss!" msgid "All the creatures will join us..." msgstr "Alla varelserna vill följa med oss..." msgid "The creature will join us..." msgid_plural "%{count} of the creatures will join us..." msgstr[0] "Varelsen vill följa med oss..." msgstr[1] "%{count} varelser vill följa med oss..." msgid "" "\n" " for a fee of %{gold} gold." msgstr "" "\n" "för en avgift pÃ¥ %{gold} guld." msgid "These weak creatures will surely flee before us." msgstr "Dessa svaga varelser kommer säkert att fly frÃ¥n oss." msgid "I fear these creatures are in the mood for a fight." msgstr "Jag är rädd för att dessa varelser är sugna pÃ¥ strid." msgid "The hero's attack skill is a bonus added to each unit's attack skill." msgstr "Hjältens Anfall ger en bonus som läggs till pÃ¥ varje varelses Anfall." msgid "The hero's defense skill is a bonus added to each unit's defense skill." msgstr "Hjältens Försvar ger en bonus till varje varelses Försvar." msgid "The hero's spell power determines the duration or power of a spell." msgstr "Hjältens Trollkonst bestämmer besvärjelsens längd eller styrka." msgid "" " The hero's knowledge determines how many spell points the hero may have. " "Under normal circumstances, a hero is limited to 10 spell points per level " "of knowledge." msgstr "" "Hjältens Kunskap bestämmer hur mÃ¥nga besvärjelsepoäng hjälten kan ha. " "Vanligtvis är hjältens besvärjelsepoäng begränsad till 10 poäng per " "KunskapsnivÃ¥." msgid "Current Modifiers:" msgstr "PÃ¥verkan:" msgid "skill|Basic" msgstr "Basal" msgid "skill|Advanced" msgstr "Avancerad" msgid "skill|Expert" msgstr "Expert" msgid "Pathfinding" msgstr "Vägvisning" msgid "Archery" msgstr "Skytte" msgid "Logistics" msgstr "Logistik" msgid "Scouting" msgstr "Spaning" msgid "Diplomacy" msgstr "Diplomati" msgid "Navigation" msgstr "Navigering" msgid "Leadership" msgstr "Ledarskap" msgid "Wisdom" msgstr "Visdom" msgid "Mysticism" msgstr "Mystik" msgid "Ballistics" msgstr "Ballistik" msgid "Eagle Eye" msgstr "Falkblick" msgid "Necromancy" msgstr "Svartkonst" msgid "Estates" msgstr "Ägor" msgid "Advanced Archery" msgstr "Avancerat Skytte" msgid "Advanced Pathfinding" msgstr "Avancerad Vägvisning" msgid "Basic Archery" msgstr "Basalt Skytte" msgid "Basic Pathfinding" msgstr "Basal Vägvisning" msgid "Expert Pathfinding" msgstr "Expert Vägvisning" msgid "Advanced Logistics" msgstr "Avancerad Logistik" msgid "Basic Logistics" msgstr "Basal Logistik" msgid "Basic Scouting" msgstr "Basal Spaning" msgid "Expert Archery" msgstr "Expert Skytte" msgid "Expert Logistics" msgstr "Expert Logistik" msgid "Advanced Diplomacy" msgstr "Avancerad Diplomati" msgid "Advanced Scouting" msgstr "Avancerad Spaning" msgid "Basic Diplomacy" msgstr "Basal Diplomati" msgid "Expert Diplomacy" msgstr "Expert Diplomati" msgid "Expert Scouting" msgstr "Expert Spaning" msgid "Advanced Leadership" msgstr "Avancerat Ledarskap" msgid "Advanced Navigation" msgstr "Avancerad Navigering" msgid "Basic Leadership" msgstr "Basalt Ledarskap" msgid "Basic Navigation" msgstr "Basal Navigering" msgid "Expert Navigation" msgstr "Expert Navigation" msgid "Advanced Wisdom" msgstr "Avancerad Visdom" msgid "Basic Mysticism" msgstr "Basal Mystik" msgid "Basic Wisdom" msgstr "Basal Visdom" msgid "Expert Leadership" msgstr "Expert Ledarskap" msgid "Expert Wisdom" msgstr "Expert Visdom" msgid "Advanced Luck" msgstr "Avancerad Tur" msgid "Advanced Mysticism" msgstr "Avancerad Mystik" msgid "Basic Luck" msgstr "Basal Tur" msgid "Expert Luck" msgstr "Expert Tur" msgid "Expert Mysticism" msgstr "Expert Mystik" msgid "Advanced Ballistics" msgstr "Avancerad Ballistik" msgid "Advanced Eagle Eye" msgstr "Avancerad Falkblick" msgid "Basic Ballistics" msgstr "Basal Ballistik" msgid "Basic Eagle Eye" msgstr "Basal Falkblick" msgid "Expert Ballistics" msgstr "Expert Ballistik" msgid "Advanced Necromancy" msgstr "Avancerad Svartkonst" msgid "Basic Estates" msgstr "Basala Ägor" msgid "Basic Necromancy" msgstr "Basal Svartkonst" msgid "Expert Eagle Eye" msgstr "Expert Falkblick" msgid "Expert Necromancy" msgstr "Expert Svartkonst" msgid "Advanced Estates" msgstr "Avancerade Ägor" msgid "Expert Estates" msgstr "Expert Ägor" msgid "" "%{skill} reduces the movement penalty for rough terrain by %{count} percent." msgstr "" "%{skill} sänker hjältens förflyttningshandikapp för grov terräng med " "%{count} procent." msgid "%{skill} eliminates the movement penalty for rough terrain." msgstr "%{skill} tar bort hjältens förflyttningshandikapp för grov terräng." msgid "" "%{skill} increases the damage done by the hero's range attacking creatures " "by %{count} percent." msgstr "" "%{skill} ökar skadan för varelser som avfyrar projektiler med %{count} " "procent." msgid "%{skill} increases the hero's movement points by %{count} percent." msgstr "%{skill} ökar hjältens steg med %{count} procent." msgid "%{skill} increases the hero's viewable area by one square." msgid_plural "%{skill} increases the hero's viewable area by %{count} squares." msgstr[0] "%{skill} ökar hjältens siktradie med %{count} ruta." msgstr[1] "%{skill} ökar hjältens siktradie med %{count} rutor." msgid "" "%{skill} allows the hero to negotiate with monsters who are weaker than " "their army, and reduces the cost of surrender." msgstr "" "%{skill} lÃ¥ter hjälten förhandla med varelser som är svagare än dennes armé " "och sänker kapitulationskostnaden." msgid "" "Approximately %{count} percent of the creatures may offer to join the hero." msgstr "" "Uppskattningsvis sÃ¥ kan %{count} procent av varelserna välja att ansluta sig " "till hjälten." msgid "All of the creatures may offer to join the hero." msgstr "Alla varelserna kan ansluta sig till hjälten." msgid "" "The cost of surrender is reduced to %{percent} percent of the total cost of " "troops in the army." msgstr "" "%{name} sänker kostnaden för kapitulation med %{count} procent av " "totalkostnaden för trupperna i hjältens armé." msgid "" "%{skill} increases the hero's movement points over water by %{count} percent." msgstr "%{skill} ökar hjältens steg över vatten med %{count} procent." msgid "%{skill} increases the hero's troops morale by %{count}." msgstr "%{skill} höjer hjältens truppers stridsmoral med %{count}." msgid "%{skill} allows the hero to learn third level spells." msgstr "%{skill} lÃ¥ter hjälten att lära sig %{count}:e nivÃ¥ns besvärjelser." msgid "%{skill} allows the hero to learn fourth level spells." msgstr "%{skill} lÃ¥ter hjälten att lära sig %{count}:e nivÃ¥ns besvärjelser." msgid "%{skill} allows the hero to learn fifth level spells." msgstr "%{skill} lÃ¥ter hjälten att lära sig %{count}:e nivÃ¥ns besvärjelser." msgid "%{skill} regenerates one additional spell point per day to the hero." msgid_plural "" "%{skill} regenerates %{count} additional spell points per day to the hero." msgstr[0] "" "%{skill} Ã¥terhämtar ytterligare %{count} av hjältens besvärjelsepoäng per " "dag." msgstr[1] "" "%{skill} Ã¥terhämtar ytterligare %{count} av hjältens besvärjelsepoäng per " "dag." msgid "%{skill} increases the hero's luck by %{count}." msgstr "%{skill} höjer hjältens tur med %{count}." msgid "" "%{skill} gives the hero's catapult shots a greater chance to hit and do " "damage to castle walls." msgstr "" "%{skill} ökar katapultens sannolikhet att träffa och skada slottsmurar." msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot has a " "greater chance to hit and do damage to castle walls." msgstr "" "%{skill} ger katapulten en extra avfyrning, och varje avfyrning har en " "större chans att träffa och skada slottsmurar." msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot " "automatically destroys any wall, except a fortified wall in a Knight castle." msgstr "" "%{skill} ger katapulten en extra avfyrning och varje avfyrning förstör muren " "förutom Riddarens uppgraderade slottsmur." msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 1st or " "2nd level spell that was cast by an enemy during combat." msgstr "" "%{skill} ger hjälten en %{count} procents chans att lära sig upp till 2:a " "nivÃ¥ns besvärjelser som kastas av fienden i strid." msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 3rd " "level spell (or below) that was cast by an enemy during combat." msgstr "" "%{skill} ger hjälten en %{count} procents chans att lära sig upp till 3:e " "nivÃ¥ns besvärjelser som kastas av fienden i strid." msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 4th " "level spell (or below) that was cast by an enemy during combat." msgstr "" "%{skill} ger hjälten en %{count} procent schans att lära sig upp till 4:e " "nivÃ¥ns besvärjelser som kastas av fienden i strid." msgid "" "%{skill} allows %{count} percent of the creatures killed in combat to be " "brought back from the dead as Skeletons." msgstr "" "%{skill} Ã¥teruppväcker %{count} procent av de stupade varelserna i strid som " "Skelett." msgid "" "The hero produces %{count} gold pieces per day as tax revenue from estates." msgstr "Hjälten fÃ¥r %{count} guld per dag i skatteintäkter frÃ¥n sina ägor." msgid "Blue" msgstr "BlÃ¥" msgid "Green" msgstr "Grön" msgid "Red" msgstr "Röd" msgid "Yellow" msgstr "Gul" msgid "Orange" msgstr "Brandgul" msgid "Purple" msgstr "Lila" msgid "barrier|Aqua" msgstr "LjusblÃ¥" msgid "barrier|Blue" msgstr "BlÃ¥" msgid "barrier|Brown" msgstr "Brun" msgid "barrier|Gold" msgstr "Gyllene" msgid "barrier|Green" msgstr "Grön" msgid "barrier|Orange" msgstr "Brandgul" msgid "barrier|Purple" msgstr "Lila" msgid "barrier|Red" msgstr "Röd" msgid "tent|Aqua" msgstr "LjusblÃ¥tt" msgid "tent|Blue" msgstr "BlÃ¥" msgid "tent|Brown" msgstr "Brunt" msgid "tent|Gold" msgstr "Gyllene" msgid "tent|Green" msgstr "Grönt" msgid "tent|Orange" msgstr "Brandgult" msgid "tent|Purple" msgstr "Lila" msgid "tent|Red" msgstr "Rött" msgid "Experience" msgstr "Erfarenhet" msgid "" "Experience allows your heroes to go up levels, increasing their primary and " "secondary skills." msgstr "" "Erfarenhet lÃ¥ter hjälten öka i nivÃ¥ som höjer dennes primära och sekundära " "förmÃ¥gor." msgid "Hero/Stats" msgstr "Hjälte/Data" msgid "Skills" msgstr "FörmÃ¥gor" msgid "Town/Castle" msgstr "Stad/Slott" msgid "Garrison" msgstr "Garnison" msgid "Gold Per Day:" msgstr "Guld per Dag:" msgid "View Heroes." msgstr "Visa Hjältar." msgid "Towns/Castles" msgstr "Städer/Slott" msgid "View Towns and Castles." msgstr "Visa Städer och Slott" msgid "luck|Cursed" msgstr "Förhäxad" msgid "luck|Awful" msgstr "Fruktansvärd" msgid "luck|Bad" msgstr "DÃ¥lig" msgid "luck|Normal" msgstr "Normal" msgid "luck|Good" msgstr "Bra" msgid "luck|Great" msgstr "Fantastisk" msgid "luck|Irish" msgstr "Irländsk" msgid "" "Bad luck sometimes falls on the hero's units in combat, causing their " "attacks to only do half damage." msgstr "" "Otur drabbar ibland hjältens trupper i strid, som fÃ¥r de att bara gör " "hälften i skada." msgid "" "Neutral luck means the hero's units will never get lucky or unlucky attacks " "on the enemy." msgstr "" "Normal tur innebär att hjältens trupper kommer varken att fÃ¥ lyckosamma " "eller oturliga attacker." msgid "" "Good luck sometimes lets the hero's units get lucky attacks (double " "strength) in combat." msgstr "" "Tursamhet lÃ¥ter hjältens trupper fÃ¥ tursamma attacker (dubbel styrka) ibland." msgid "morale|Treason" msgstr "Förräderi" msgid "morale|Awful" msgstr "Fruktansvärd" msgid "morale|Poor" msgstr "DÃ¥lig" msgid "morale|Normal" msgstr "Normal" msgid "morale|Good" msgstr "Bra" msgid "morale|Great" msgstr "Fantastisk" msgid "morale|Blood!" msgstr "Blod!" msgid "Bad morale may cause the hero's units to freeze in combat." msgstr "LÃ¥g stridsmoral kan fÃ¥ hjältens trupper att lÃ¥sa sig i strid." msgid "" "Neutral morale means the hero's units will never be blessed with extra " "attacks or freeze in combat." msgstr "" "Neutral stridsmoral innebär att hjältens arméer aldrig kommer att bli " "välsignade eller lÃ¥sta i strid." msgid "Good morale may give the hero's units extra attacks in combat." msgstr "Hög stridsmoral kan ge hjältens trupper extra attacker i strid." msgid "Knight" msgstr "Riddare" msgid "Barbarian" msgstr "Barbar" msgid "Sorceress" msgstr "Häxa" msgid "Warlock" msgstr "Häxmästare" msgid "Wizard" msgstr "Trollkarl" msgid "Necromancer" msgstr "Svartkonstnär" msgid "Multi" msgstr "Flera" msgid "doubleLined|Knight" msgstr "Riddare" msgid "doubleLined|Barbarian" msgstr "Barbar" msgid "doubleLined|Sorceress" msgstr "Häxa" msgid "doubleLined|Warlock" msgstr "" "Häx-\n" "mästare" msgid "doubleLined|Wizard" msgstr "Trollkarl" msgid "" "doubleLined|Necro-\n" "mancer" msgstr "" "Svart-\n" "konstnär" msgid "doubleLinedRace|Multi" msgstr "Flera" msgid "doubleLinedRace|Random" msgstr "Slumpad" msgid "speed|Standing" msgstr "StÃ¥ende" msgid "speed|Crawling" msgstr "Krypande" msgid "speed|Very Slow" msgstr "Mkt. lÃ¥ngsam" msgid "speed|Slow" msgstr "LÃ¥ngsam" msgid "speed|Average" msgstr "Vanlig" msgid "speed|Fast" msgstr "Snabb" msgid "speed|Very Fast" msgstr "Mkt. snabb" msgid "speed|Ultra Fast" msgstr "Jättesnabb" msgid "speed|Blazing" msgstr "Blixtsnabb" msgid "speed|Instant" msgstr "Ögonblicklig" msgid "week|Squirrel" msgstr "Ekorrens" msgid "week|Rabbit" msgstr "Harens" msgid "week|Gopher" msgstr "Siselns" msgid "week|Badger" msgstr "Grävlingens" msgid "week|Rat" msgstr "RÃ¥ttans" msgid "week|Eagle" msgstr "Örnens" msgid "week|Weasel" msgstr "Vesslans" msgid "week|Raven" msgstr "Korpens" msgid "week|Mongoose" msgstr "Mungons" msgid "week|Dog" msgstr "Hundens" msgid "week|Aardvark" msgstr "Jordsvinets" msgid "week|Lizard" msgstr "Ödlans" msgid "week|Tortoise" msgstr "Sköldpaddans" msgid "week|Hedgehog" msgstr "Igelkottens" msgid "week|Condor" msgstr "Kondorens" msgid "week|Ant" msgstr "Myrans" msgid "week|Grasshopper" msgstr "Gräshoppans" msgid "week|Dragonfly" msgstr "Trollsländans" msgid "week|Spider" msgstr "Spindelns" msgid "week|Butterfly" msgstr "Fjärilens" msgid "week|Bumblebee" msgstr "Humlans" msgid "week|Locust" msgstr "Vandringsgräshoppans" msgid "week|Earthworm" msgstr "Daggmaskens" msgid "week|Hornet" msgstr "BÃ¥lgetingens" msgid "week|Beetle" msgstr "Skalbaggens" msgid "week|PLAGUE" msgstr "FARSOTENS" msgid "week|Unnamed" msgstr "de Namnlösas" msgid "Desert" msgstr "Öken" msgid "Snow" msgstr "Snö" msgid "Wasteland" msgstr "Ödemark" msgid "Beach" msgstr "Strand" msgid "Lava" msgstr "Lava" msgid "Dirt" msgstr "Jord" msgid "Grass" msgstr "Gräs" msgid "Ocean" msgstr "Hav" msgid "maps|Small" msgstr "Liten" msgid "maps|Medium" msgstr "Medel" msgid "maps|Large" msgstr "Stor" msgid "maps|Extra Large" msgstr "Extrastor" msgid "maps|Custom Size" msgstr "Anpassad Storlek" msgid "Ore Mine" msgstr "Malmgruva" msgid "Sulfur Mine" msgstr "Svavelgruva" msgid "Crystal Mine" msgstr "Kristallgruva" msgid "Gems Mine" msgstr "Ädelstensgruva" msgid "Gold Mine" msgstr "Guldgruva" msgid "Mine" msgstr "Gruva" msgid "Burma shave." msgstr "LÃ¥ngbortistan" msgid "Next sign 50 miles." msgstr "Nästa skylt 50 km." msgid "See Rock City." msgstr "See Rock City." msgid "This space for rent." msgstr "Uthyres" msgid "No object" msgstr "Inget Objekt" msgid "Alchemist Lab" msgstr "Alkemistlabb" msgid "Sign" msgstr "Skylt" msgid "Buoy" msgstr "Boj" msgid "Skeleton" msgstr "Skelett" msgid "Daemon Cave" msgstr "Demongrotta" msgid "Treasure Chest" msgstr "Skattkista" msgid "Faerie Ring" msgstr "Häxring" msgid "Campfire" msgstr "Lägereld" msgid "Fountain" msgstr "Fontän" msgid "Gazebo" msgstr "Lusthus" msgid "Genie Lamp" msgstr "Andens Lampa" msgid "Archer's House" msgstr "BÃ¥gskyttens Hus" msgid "Goblin Hut" msgstr "Vätthydda" msgid "Dwarf Cottage" msgstr "Dvärgstuga" msgid "Peasant Hut" msgstr "Torp" msgid "Stables" msgstr "Stall" msgid "Alchemist's Tower" msgstr "Alkemistens Torn" msgid "Event" msgstr "Händelse" msgid "Dragon City" msgstr "Drakstad" msgid "Lighthouse" msgid_plural "Lighthouses" msgstr[0] "Fyr" msgstr[1] "Fyrar" msgid "Water Wheel" msgid_plural "Water Wheels" msgstr[0] "Vattenkvarn" msgstr[1] "Vattenkvarnar" msgid "Monster" msgstr "Varelse" msgid "Obelisk" msgstr "Obelisk" msgid "Oasis" msgstr "Oas" msgid "Resource" msgstr "Resurs" msgid "Sawmill" msgstr "SÃ¥gverk" msgid "Oracle" msgstr "Orakel" msgid "Shrine of the First Circle" msgstr "Första kretsens Helgedom" msgid "Shipwreck" msgstr "Skeppsvrak" msgid "Sea Chest" msgstr "Sjökista" msgid "Desert Tent" msgstr "Ökentält" msgid "Stone Liths" msgstr "Stenstoder" msgid "Wagon Camp" msgstr "Godsvagnläger" msgid "Hut of the Magi" msgstr "De vise männens stuga" msgid "Whirlpool" msgstr "Strömvirvel" msgid "Windmill" msgid_plural "Windmills" msgstr[0] "Väderkvarn" msgstr[1] "Väderkvarnar" msgid "Artifact" msgstr "Artefakt" msgid "Mermaid" msgstr "Sjöjungfru" msgid "Boat" msgstr "Skepp" msgid "Random Ultimate Artifact" msgstr "Slumpad Ultimat artefakt" msgid "Random Artifact" msgstr "Slumpad Artefakt" msgid "Random Resource" msgstr "Slumpad Resurs" msgid "Random Monster" msgstr "Slumpat Monster" msgid "Random Town" msgstr "Slumpad Stad" msgid "Random Castle" msgstr "Slumpat Slott" msgid "Eye of the Magi" msgstr "De vise männens öga" msgid "Random Monster - weak" msgstr "Slumpat Monster - svag" msgid "Random Monster - medium" msgstr "Slumpat Monster - medel" msgid "Random Monster - strong" msgstr "Slumpat Monster - starkt" msgid "Random Monster - very strong" msgstr "Slumpat Monster - väldigt starkt" msgid "Hero" msgstr "Hjälte" msgid "Nothing Special" msgstr "Ingenting Speciellt" msgid "Mossy Rock" msgstr "Mossigt Stenblock" msgid "Watch Tower" msgstr "Vakttorn" msgid "Tree House" msgstr "Trädhus" msgid "Tree City" msgstr "Trädstad" msgid "Ruins" msgstr "Ruiner" msgid "Fort" msgstr "Fort" msgid "Abandoned Mine" msgstr "Övergiven gruva" msgid "Sirens" msgstr "Sirener" msgid "Standing Stones" msgstr "Stenblock" msgid "Idol" msgstr "Avgud" msgid "Tree of Knowledge" msgstr "Kunskapens Träd" msgid "Witch Doctor's Hut" msgstr "Häxdoktorns Hydda" msgid "Temple" msgstr "Tempel" msgid "Hill Fort" msgstr "Fornborg" msgid "Halfling Hole" msgstr "HobhÃ¥la" msgid "Mercenary Camp" msgstr "Legosoldatsläger" msgid "Shrine of the Second Circle" msgstr "Andra kretsens Helgedom" msgid "Shrine of the Third Circle" msgstr "Tredje kretsens Helgedom" msgid "City of the Dead" msgstr "Dödens Stad" msgid "Sphinx" msgstr "Sfinx" msgid "Wagon" msgstr "Godsvagn" msgid "Tar Pit" msgstr "TjärhÃ¥la" msgid "Artesian Spring" msgstr "Artesisk Källa" msgid "Troll Bridge" msgstr "Trollbro" msgid "Watering Hole" msgstr "VattenhÃ¥l" msgid "Witch's Hut" msgstr "Häxhydda" msgid "Xanadu" msgstr "Xanadu" msgid "Lean-To" msgstr "Vindskydd" msgid "Magellan's Maps" msgstr "Magellans Kartor" msgid "Flotsam" msgstr "Vrakgods" msgid "Derelict Ship" msgstr "Övergivet Skepp" msgid "Shipwreck Survivor" msgstr "Skeppsbruten Överlevare" msgid "Bottle" msgstr "Flaska" msgid "Magic Well" msgstr "Magisk Brunn" msgid "Magic Garden" msgid_plural "Magic Gardens" msgstr[0] "Förtrollad TrädgÃ¥rd" msgstr[1] "Förtrollade TrädgÃ¥rdar" msgid "Observation Tower" msgstr "Observationstorn" msgid "Freeman's Foundry" msgstr "Hedersmedborgarnas gjuteri" msgid "Reefs" msgstr "Rev" msgid "Volcano" msgstr "Vulkan" msgid "Flowers" msgstr "Blommor" msgid "Rock" msgstr "Sten" msgid "Water Lake" msgstr "Sjö" msgid "Mandrake" msgstr "Alruna" msgid "Dead Tree" msgstr "Dött Träd" msgid "Stump" msgstr "Stubbe" msgid "Crater" msgstr "Krater" msgid "Cactus" msgstr "Kaktus" msgid "Mound" msgstr "Kulle" msgid "Dune" msgstr "Dyn" msgid "Lava Pool" msgstr "Lavapöl" msgid "Shrub" msgstr "Buske" msgid "Barrow Mounds" msgstr "Gravhög" msgid "Random Artifact - Treasure" msgstr "Slumpad Artefakt - Skatt" msgid "Random Artifact - Minor" msgstr "Slumpad Artefakt - Liten" msgid "Random Artifact - Major" msgstr "Slumpad Artefakt - Stor" msgid "Barrier" msgstr "Barriär" msgid "Traveller's Tent" msgstr "Resenärstält" msgid "Jail" msgstr "Fängelse" msgid "Fire Summoning Altar" msgstr "Eldaltare" msgid "Air Summoning Altar" msgstr "Luftaltare" msgid "Earth Summoning Altar" msgstr "Jordaltare" msgid "Water Summoning Altar" msgstr "Vattenaltare" msgid "Unknown Monster" msgstr "Okänt Monster" msgid "Unknown Monsters" msgstr "Okända Monster" msgid "Peasant" msgstr "Bonde" msgid "Peasants" msgstr "Bönder" msgid "Archer" msgstr "BÃ¥gskytt" msgid "Archers" msgstr "BÃ¥gskyttar" msgid "Ranger" msgstr "Vandringsman" msgid "Rangers" msgstr "Vandringsmän" msgid "Pikeman" msgstr "Pikenerare" msgid "Pikemen" msgstr "Pikenerare" msgid "Veteran Pikeman" msgstr "Veteranpikenerare" msgid "Veteran Pikemen" msgstr "Veteranpikenerare" msgid "Swordsman" msgstr "Svärdsman" msgid "Swordsmen" msgstr "Svärdsmän" msgid "Master Swordsman" msgstr "Mästersvärdsman" msgid "Master Swordsmen" msgstr "Mästersvärdsmän" msgid "Cavalries" msgstr "Kavallerister" msgid "Cavalry" msgstr "Kavalleri" msgid "Champion" msgstr "Lansiär" msgid "Champions" msgstr "Lansiärer" msgid "Paladin" msgstr "Paladin" msgid "Paladins" msgstr "Paladiner" msgid "Crusader" msgstr "Korsfarare" msgid "Crusaders" msgstr "Korsfarare" msgid "Goblin" msgstr "Vätte" msgid "Goblins" msgstr "Vättar" msgid "Orc" msgstr "Orch" msgid "Orcs" msgstr "Orcher" msgid "Orc Chief" msgstr "Orchhövding" msgid "Orc Chiefs" msgstr "Orchhövdingar" msgid "Wolf" msgstr "Varg" msgid "Wolves" msgstr "Vargar" msgid "Ogre" msgstr "Jätte" msgid "Ogres" msgstr "Jättar" msgid "Ogre Lord" msgstr "Jättehövding" msgid "Ogre Lords" msgstr "Jättehövdingar" msgid "Troll" msgstr "Troll" msgid "Trolls" msgstr "Troll" msgid "War Troll" msgstr "Krigstroll" msgid "War Trolls" msgstr "Krigstroll" msgid "Cyclopes" msgstr "Cykloper" msgid "Cyclops" msgstr "Cyklop" msgid "Sprite" msgstr "Fé" msgid "Sprites" msgstr "Féer" msgid "Dwarf" msgstr "Dvärg" msgid "Dwarves" msgstr "Dvärgar" msgid "Battle Dwarf" msgstr "Stridsdvärgar" msgid "Battle Dwarves" msgstr "Stridsdvärgar" msgid "Elf" msgstr "Alv" msgid "Elves" msgstr "Alver" msgid "Grand Elf" msgstr "Ärkealv" msgid "Grand Elves" msgstr "Ärkealver" msgid "Druid" msgstr "Druid" msgid "Druids" msgstr "Druider" msgid "Greater Druid" msgstr "Mästerdruid" msgid "Greater Druids" msgstr "Mästerdruider" msgid "Unicorn" msgstr "Enhörning" msgid "Unicorns" msgstr "Enhörningar" msgid "Phoenix" msgstr "Fenix" msgid "Phoenixes" msgstr "Fenixar" msgid "Centaur" msgstr "Kentaur" msgid "Centaurs" msgstr "Kentaurer" msgid "Gargoyle" msgstr "Stenstaty" msgid "Gargoyles" msgstr "Stenstatyer" msgid "Griffin" msgstr "Grip" msgid "Griffins" msgstr "Gripar" msgid "Minotaur" msgstr "Minotaur" msgid "Minotaurs" msgstr "Minotaurer" msgid "Minotaur King" msgstr "Minotaurkung" msgid "Minotaur Kings" msgstr "Minotaurkungar" msgid "Hydra" msgstr "Hydra" msgid "Hydras" msgstr "Hydror" msgid "Green Dragon" msgstr "Grön Drake" msgid "Green Dragons" msgstr "Gröna Drakar" msgid "Red Dragon" msgstr "Röd Drake" msgid "Red Dragons" msgstr "Röda Drakar" msgid "Black Dragon" msgstr "Svart Drake" msgid "Black Dragons" msgstr "Svarta Drakar" msgid "Halfling" msgstr "Hob" msgid "Halflings" msgstr "Hober" msgid "Boar" msgstr "Vildsvin" msgid "Boars" msgstr "Vildsvin" msgid "Iron Golem" msgstr "Järngolem" msgid "Iron Golems" msgstr "Järngolem" msgid "Steel Golem" msgstr "StÃ¥lgolem" msgid "Steel Golems" msgstr "StÃ¥lgolem" msgid "Roc" msgstr "RocfÃ¥gel" msgid "Rocs" msgstr "RocfÃ¥glar" msgid "Mage" msgstr "Magiker" msgid "Magi" msgstr "Magiker" msgid "Archmage" msgstr "Ärkemagiker" msgid "Archmagi" msgstr "Ärkemagiker" msgid "Giant" msgstr "Koloss" msgid "Giants" msgstr "Kolosser" msgid "Titan" msgstr "Titan" msgid "Titans" msgstr "Titaner" msgid "Skeletons" msgstr "Skelett" msgid "Zombie" msgstr "Zombie" msgid "Zombies" msgstr "Zombier" msgid "Mutant Zombie" msgstr "Mutantzombie" msgid "Mutant Zombies" msgstr "Mutantzombier" msgid "Mummies" msgstr "Mumier" msgid "Mummy" msgstr "Mumie" msgid "Royal Mummies" msgstr "Kungsmumier" msgid "Royal Mummy" msgstr "Kungsmumie" msgid "Vampire" msgstr "Vampyr" msgid "Vampires" msgstr "Vampyrer" msgid "Vampire Lord" msgstr "Vampyrbaron" msgid "Vampire Lords" msgstr "Vampyrbaroner" msgid "Lich" msgstr "Lich" msgid "Liches" msgstr "Licher" msgid "Power Lich" msgstr "Ärkelich" msgid "Power Liches" msgstr "Ärkelichar" msgid "Bone Dragon" msgstr "Bendrake" msgid "Bone Dragons" msgstr "Bendrakar" msgid "Rogue" msgstr "Rövare" msgid "Rogues" msgstr "Rövare" msgid "Nomad" msgstr "Nomad" msgid "Nomads" msgstr "Nomader" msgid "Ghost" msgstr "Spöke" msgid "Ghosts" msgstr "Spöken" msgid "Genie" msgstr "Ande" msgid "Genies" msgstr "Andar" msgid "Medusa" msgstr "Medusa" msgid "Medusas" msgstr "Medusor" msgid "Earth Elemental" msgstr "Jordväsen" msgid "Earth Elementals" msgstr "Jordväsen" msgid "Air Elemental" msgstr "Luftväsen" msgid "Air Elementals" msgstr "Luftväsen" msgid "Fire Elemental" msgstr "Eldväsen" msgid "Fire Elementals" msgstr "Eldväsen" msgid "Water Elemental" msgstr "Vattenväsen" msgid "Water Elementals" msgstr "Vattenväsen" msgid "Random Monsters" msgstr "Slumpade Monster" msgid "Random Monster 1" msgstr "Slumpat Monster 1" msgid "Random Monsters 1" msgstr "Slumpade Monster 1" msgid "Random Monster 2" msgstr "Slumpat Monster 2" msgid "Random Monsters 2" msgstr "Slumpade Monster 2" msgid "Random Monster 3" msgstr "Slumpat Monster 3" msgid "Random Monsters 3" msgstr "Slumpade Monster 3" msgid "Random Monster 4" msgstr "Slumpat Monster 4" msgid "Random Monsters 4" msgstr "Slumpade Monster 4" msgid "Double shot" msgstr "Dubbel avfyrning" msgid "2-hex monster" msgstr "Stor varelse" msgid "Double strike" msgstr "Dubbelslag" msgid "Double damage to Undead" msgstr "Dubbel skada till Odöda" msgid "% magic resistance" msgstr "% besvärjelseresistens" msgid "Immune to Mind spells" msgstr "Immun mot Sinnesbesvärjelser" msgid "Immune to Elemental spells" msgstr "Immun mot Elementbesvärjelser" msgid "Immune to Fire spells" msgstr "Immun mot Eldbesvärjelser" msgid "Immune to Cold spells" msgstr "Immun mot Köldbesvärjelser" msgid "Immune to " msgstr "Immun mot " msgid "% immunity to %{spell} spell" msgstr "% resistens mot %{spell}besvärjelse" msgid "% damage from Elemental spells" msgstr "% skada frÃ¥n Elementbesvärjelser" msgid "% chance to Dispel beneficial spells" msgstr "% chans att Skingra godartade besvärjelser" msgid "% chance to Paralyze" msgstr "% chans att Paralysera" msgid "% chance to Petrify" msgstr "% chans att Förstena" msgid "% chance to Blind" msgstr "% chans att Förblinda" msgid "% chance to Curse" msgstr "% chans att kasta Förbannelse" msgid "% chance to cast %{spell} spell" msgstr "% chans att kasta %{spell}besvärjelse" msgid "HP regeneration" msgstr "HälsoÃ¥terhämtning" msgid "Two hexes attack" msgstr "TvÃ¥-rutsanfall" msgid "Flyer" msgstr "Flygare" msgid "Always retaliates" msgstr "Vedergäller alltid" msgid "Attacks all adjacent enemies" msgstr "Anfaller alla närliggande fiender" msgid "No melee penalty" msgstr "Inget närstridshandikapp" msgid "Dragon" msgstr "Drake" msgid "Undead" msgstr "Odöd" msgid "No enemy retaliation" msgstr "Ingen fiendevedergällning" msgid "HP drain" msgstr "Suger hälsopoäng" msgid "Cloud attack" msgstr "Molnattack" msgid "Decreases enemy's morale by " msgstr "Sänker fiendens stridmoral med " msgid "% chance to halve enemy" msgstr "% chans att halvera fienden" msgid "Soul Eater" msgstr "Själslukare" msgid "Elemental" msgstr "Elementande" msgid "No Morale" msgstr "Ingen Stridsmoral" msgid "200% damage from Fire spells" msgstr "200% skada frÃ¥n Eldbesvärjelser" msgid "200% damage from Cold spells" msgstr "200% skada frÃ¥n Köldbesvärjelser" msgid "% damage from %{spell} spell" msgstr "% skada frÃ¥n %{spell}besvärjelse" msgid "% immunity to " msgstr "% resistens mot " msgid "Lightning" msgstr "Blixt" msgid "% damage from " msgstr "% skada frÃ¥n " msgid "The three Anduran artifacts magically combine into one." msgstr "De tre Anduranartefakterna kombineras magiskt till en." msgid "View Spells" msgstr "Visa Besvärjelser" msgid "View %{name} Info" msgstr "Visa %{name}information" msgid "Move %{name}" msgstr "Flytta %{name}" msgid "Cannot move the Spellbook" msgstr "Kan inte flytta Magibok" msgid "This item can't be traded." msgstr "Detta föremÃ¥l kan inte bytas." msgid "Invalid Artifact" msgstr "Ogiltig Artefakt" msgid "The %{name} increases the hero's knowledge by %{count}." msgstr "%{name} ökar hjältens Kunskap med %{count}." msgid "Ultimate Book of Knowledge" msgstr "Främsta Kunskapsboken" msgid "The %{name} increases the hero's attack skill by %{count}." msgstr "%{name} höjer hjältens Anfall med %{count}." msgid "Ultimate Sword of Dominion" msgstr "Främsta Herraväldessvärdet" msgid "The %{name} increases the hero's defense skill by %{count}." msgstr "%{name} höjer hjältens Försvar med %{count}." msgid "Ultimate Cloak of Protection" msgstr "Främsta Skyddsmanteln" msgid "The %{name} increases the hero's spell power by %{count}." msgstr "%{name} höjer hjältens Trollkonst med %{count}." msgid "Ultimate Wand of Magic" msgstr "Främsta Trollstaven" msgid "" "The %{name} increases the hero's attack and defense skills by %{count} each." msgstr "%{name} höjer hjältens Anfall- och Försvar med %{count} vardera." msgid "Ultimate Shield" msgstr "Främsta Skölden" msgid "" "The %{name} increases the hero's spell power and knowledge by %{count} each." msgstr "%{name} höjer hjältens Trollkonst och Kunskap med %{count} vardera." msgid "Ultimate Staff" msgstr "Främsta Staven" msgid "" "The %{name} increases each of the hero's basic skills by %{count} points." msgstr "%{name} höjer alla av hjältens primära förmÃ¥gor med %{count}." msgid "Ultimate Crown" msgstr "Främsta Kronan" msgid "Golden Goose" msgstr "Gyllene GÃ¥sen" msgid "The %{name} brings in an income of %{count} gold per day." msgstr "%{name} inbringar en intäkt pÃ¥ %{count} guld per dag." msgid "Arcane Necklace of Magic" msgstr "Mystiskt Halsband" msgid "" "After rescuing a Sorceress from a cursed tomb, she rewards your heroism with " "an exquisite jeweled necklace." msgstr "" "Efter att ha räddat en Häxa ur en förbannad gravkista, belönar hon ditt " "hjältemod med ett utsökt juvelhalsband." msgid "Caster's Bracelet of Magic" msgstr "Besvärjarens Armband" msgid "" "While searching through the rubble of a caved-in mine, you free a group of " "trapped Dwarves. Grateful, the leader gives you a golden bracelet." msgstr "" "När du genomsöker brÃ¥te vid en inrasad gruva, finner du en grupp Dvärgar som " "du räddar. Tacksam för din hjälp, ger dess ledare ett armband av guld." msgid "Mage's Ring of Power" msgstr "Trollkarlens Ring" msgid "" "A cry of pain leads you to a Centaur, caught in a trap. Upon setting the " "creature free, he hands you a small pouch. Emptying the contents, you find a " "dazzling jeweled ring." msgstr "" "Ett plÃ¥gsamt rop leder dig till en kentaur, infÃ¥ngad i en fälla. När du " "släpper lös kentauren, räcker han dig en liten pung. Inuti hittar du en " "skimrande juvelring." msgid "Witch's Broach of Magic" msgstr "Magisk Häxbrosch" msgid "" "Alongside the remains of a burnt witch lies a beautiful broach, intricately " "designed. Approaching the corpse with caution, you add the broach to your " "inventory." msgstr "" "I askan av en bränd häxa ligger en vacker brosch oskadad av elden. Du lägger " "till den till din samling." msgid "Medal of Valor" msgstr "Tapperhetsmedalj" msgid "The %{name} increases the morale of the hero's army by %{count}." msgstr "%{name} höjer hjältens armés stridsmoral med %{count}." msgid "" "Freeing a virtuous maiden from the clutches of an evil overlord, you are " "granted a Medal of Valor by the King's herald." msgstr "" "Genom fritagandet av en dygdig ungmö frÃ¥n en ond ärkeskurks fÃ¥ngenskap sÃ¥ " "blir du tilldelad en Tapperhetsmedalj av konungens härold." msgid "Medal of Courage" msgstr "Djärvhetsmedalj" msgid "" "After saving a young boy from a vicious pack of Wolves, you return him to " "his father's manor. The grateful nobleman awards you with a Medal of Courage." msgstr "" "Efter att ha räddat en pojke frÃ¥n en flock glupska Vargar lämnar du av honom " "vid hans fars ägor. Den tacksamma adelsmannen tilldelar dig en " "Djärvhetsmedalj." msgid "Medal of Honor" msgstr "Hedersmedalj" msgid "" "After freeing a princess of a neighboring kingdom from the evil clutches of " "despicable slavers, she awards you with a Medal of Honor." msgstr "" "Efter att ha räddat en prinsessa frÃ¥n klorna av avskyvärda slavhandlare, sÃ¥ " "ger hon dig en Hedersmedalj." msgid "Medal of Distinction" msgstr "Utmärkelsemedalj" msgid "" "Ridding the countryside of the hideous Minotaur who made a sport of eating " "noblemen's Knights, you are honored with the Medal of Distinction." msgstr "" "Efter att ta rensat bygden frÃ¥n en hemsk minotaur som hade för vana att äta " "adelns riddare, blir du hedrad med en Utmärkelsemedalj." msgid "Fizbin of Misfortune" msgstr "Olycksfizbin" msgid "" "The %{name} greatly decreases the morale of the hero's army by %{count}." msgstr "%{name} minskar kraftigt hjältens armés stridsmoral med %{count}." msgid "" "You stumble upon a medal lying alongside the empty road. Adding the medal to " "your inventory, you become aware that you have acquired the undesirable " "Fizbin of Misfortune, greatly decreasing your army's morale." msgstr "" "Du fÃ¥r förbi en medalj som ligger bredvid vägen du gÃ¥r pÃ¥. Du plockar upp " "den och lägger den till din samling. Du inser att du precis plockat pÃ¥ dig " "den oönskade Olycksfizbin, som gravt sänker din armés stridsmoral." msgid "Thunder Mace of Dominion" msgstr "Ã…skans Stridsgissel" msgid "" "During a sudden storm, a bolt of lightning strikes a tree, splitting it. " "Inside the tree you find a mysterious mace." msgstr "" "Under ett plötsligt oväder sÃ¥ slÃ¥r en blixt ner i ett träd och delar det " "mitt i tu. Inuti trädet finner du ett mystiskt stridsgissel." msgid "Armored Gauntlets of Protection" msgstr "Skyddstridshandskar" msgid "The %{name} increase the hero's defense skill by %{count}." msgstr "%{name} ökar hjältens Försvar med %{count}." msgid "" "You encounter the infamous Black Knight! After a grueling duel ending in a " "draw, the Knight, out of respect, offers you a pair of armored gauntlets." msgstr "" "Du stöter pÃ¥ den ökända Svarta Riddaren! Efter en ansträngande duell som " "slutar oavgjort, sÃ¥ skänker dig Riddaren sina Skyddstridshandskar utav " "respekt för bra duell." msgid "Defender Helm of Protection" msgstr "Livgardets Skyddshjälm" msgid "" "A glint of golden light catches your eye. Upon further investigation, you " "find a golden helm hidden under a bush." msgstr "" "NÃ¥gonting glimmar till i ett tätt buskage. Vid närmare inspektion hittar du " "en gyllene hjälm glömd under busken." msgid "Giant Flail of Dominion" msgstr "Jättens Stridsgissel" msgid "" "A clumsy Giant has killed himself with his own flail. Knowing your superior " "skill with this weapon, you confidently remove the spectacular flail from " "the fallen Giant." msgstr "" "En klumpig Jätte träffar sig själv i huvudet med sitt stridsgissel och " "faller ner död. Med dina överlägsna kunskaper i stridskonst sÃ¥ är du säker " "pÃ¥ att du kan hantera det mycket bättre. Du lossar gisslet som fastnat i " "Jättens huvud och provsvingar det lite." msgid "Ballista of Quickness" msgstr "Snabbhetens Ballist" msgid "The %{name} gives the hero's catapult one extra shot per combat round." msgstr "%{name} lÃ¥ter hjälten att avfyra katapulten tvÃ¥ gÃ¥nger per runda." msgid "" "Walking through the ruins of an ancient walled city, you find the instrument " "of the city's destruction, an elaborately crafted ballista." msgstr "" "Du vandrar runt i ruinerna av en urÃ¥ldrig stad med en förstörd ringmur. Du " "hittar svaret till stadens ödeläggelse, en intrikat konstruerad ballist." msgid "Stealth Shield of Protection" msgstr "Skyddssköld" msgid "" "A stone statue of a warrior holds a silver shield. As you remove the shield, " "the statue crumbles into dust." msgstr "" "Du stöter pÃ¥ en stenstaty föreställande en krigare med en silversköld. När " "du tar skölden frÃ¥n statyn sÃ¥, rasar statyn ihop till grus och damm." msgid "Dragon Sword of Dominion" msgstr "Draksvärd" msgid "" "As you are walking along a narrow path, a nearby bush suddenly bursts into " "flames. Before your eyes the flames become the image of a beautiful woman. " "She holds out a magnificent sword to you." msgstr "" "När du gÃ¥r längs med en smal stig sÃ¥ fattar plötsligt en buske i närheten " "eld. Inför dina ögon sÃ¥ tar eldflammorna formen av en vacker kvinna. Hon " "hÃ¥ller fram ett fint svärd Ã¥t dig." msgid "Power Axe of Dominion" msgstr "Kraftyxa" msgid "" "You see a silver axe embedded deeply in the ground. After several " "unsuccessful attempts by your army to remove the axe, you tightly grip the " "handle of the axe and effortlessly pull it free." msgstr "" "Du ser en silveryxa djupt nergrävd i marken. Efter mÃ¥nga misslyckade försök " "av din armé att ta yxan, sÃ¥ försöker du själv ta den. Ett stadigt tag kring " "skaftet och utan ansträngning sÃ¥ drar du loss den." msgid "Divine Breastplate of Protection" msgstr "Gudomlig Bröstharnesk" msgid "" "A gang of Rogues is sifting through the possessions of dead warriors. " "Scaring off the scavengers, you note the Rogues had overlooked a beautiful " "breastplate." msgstr "" "Ett gäng med Rövare söker igenom tillhörigheterna pÃ¥ nÃ¥gra döda krigare. Du " "skrämmer bort plundrarna och ser att Rövarna har missat en vacker glänsande " "bröstharnesk." msgid "Minor Scroll of Knowledge" msgstr "Mindre Kunskapsrulle" msgid "" "Before you appears a levitating glass case with a scroll, perched upon a bed " "of crimson velvet. At your touch, the lid opens and the scroll floats into " "your awaiting hands." msgstr "" "Framför dig uppenbarar sig en svävande glaslÃ¥da pÃ¥ en mörkröd sammetskudde. " "Inuti glaslÃ¥dan finns en rulle. När du rör vid den sÃ¥ öppnar sig lÃ¥dan och " "rullen svävar genom luften och ner i dina händer." msgid "Major Scroll of Knowledge" msgstr "Större Kunskapsrulle" msgid "" "Visiting a local wiseman, you explain the intent of your journey. He reaches " "into a sack and withdraws a yellowed scroll and hands it to you." msgstr "" "Du besöker en lokal vis Ã¥ldersman som du förklarar syftet med dina resor. " "Han tar fram en gulblekt rulle ur en säck och ger den till dig." msgid "Superior Scroll of Knowledge" msgstr "Överordnad Kunskapsrulle" msgid "" "You come across the remains of an ancient Druid. Bones, yellowed with age, " "peer from the ragged folds of her robe. Searching the robe, you discover a " "scroll hidden in the folds." msgstr "" "Du gÃ¥r förbi lämningarna av en gammal Druid. Benen, gulnande av Ã¥lder, " "sticker ut genom resterna av hennes gamla kappa. Du söker igenom kappan och " "du finner en rulle med inskriptioner." msgid "Foremost Scroll of Knowledge" msgstr "Särskild Kunskapsrulle" msgid "" "Mangled bones, yellowed with age, peer from the ragged folds of a dead " "Druid's robe. Searching the robe, you discover a scroll hidden within." msgstr "" "Vittrade ben, gulblekta av tidens tand sticker upp ut ur lämningarna av en " "död Druids kappa. Vid genomsökning av kappan finner du en besvärjelserulle." msgid "Endless Sack of Gold" msgstr "Bottenlös Guldsäck" msgid "The %{name} provides the hero with %{count} gold per day." msgstr "%{name} förser hjälten med %{count} guld per dag." msgid "" "A little leprechaun dances gleefully around a magic sack. Seeing you " "approach, he stops in mid-stride. The little man screams and stamps his foot " "ferociously, vanishing into thin air. Remembering the old leprechaun saying " "'Finders Keepers', you grab the sack and leave." msgstr "" "En liten pyssling dansar glatt kring en magisk säck. När han upptäcker dig, " "stannar han mitt i sin rörelse. Han skriker och stampar till med sin fot och " "försvinner upp i rök. Du minns det gamla pysslingsordsprÃ¥ket \"Funnet är " "vunnet\" och tar säcken och gÃ¥r." msgid "Endless Bag of Gold" msgstr "Bottenlös Guldväska" msgid "" "A noblewoman, separated from her traveling companions, asks for your help. " "After escorting her home, she rewards you with a bag filled with gold." msgstr "" "En adelskvinna som har kommit ifrÃ¥n sitt följe ber dig om din hjälp. Du " "eskorterar henne hem och hon belönar dig med en säck fylld med guld." msgid "Endless Purse of Gold" msgstr "Bottenlös Guldbörs" msgid "" "In your travels, you find a leather purse filled with gold that once " "belonged to a great warrior king who had the ability to transform any " "inanimate object into gold." msgstr "" "Under dina resor sÃ¥ finner du en läderbörs fylld med guld. Den ägdes en gÃ¥ng " "i tiden av en stor Krigsherre som hade förmÃ¥gan att förvandla ting till det " "renaste guld." msgid "Nomad Boots of Mobility" msgstr "Nomadens Kängor" msgid "The %{name} increase the hero's movement on land." msgstr "%{name} ökar hjältens förflyttning pÃ¥ mark." msgid "" "A Nomad trader seeks protection from a tribe of Goblins. For your " "assistance, he gives you a finely crafted pair of boots made from the " "softest leather. Looking closely, you see fascinating ancient carvings " "engraved on the leather." msgstr "" "En handelsresande söker skydd frÃ¥n en stam med Vättar. För din hjälp ger han " "dig ett par fint sydda kängor gjorda av det mjukaste läder. Vid en närmare " "titt sÃ¥ ser du fascinerande urÃ¥ldriga inskriptioner pÃ¥ lädret." msgid "Traveler's Boots of Mobility" msgstr "Vandrarens Kängor" msgid "" "Discovering a pair of beautifully beaded boots made from the finest and " "softest leather, you thank the anonymous donor and add the boots to your " "inventory." msgstr "" "Du fÃ¥r att par vackert sydda kängor gjorda av de finaste och mjukaste läder " "av en främmande man, innan du hinner tacka för gÃ¥van har mannen gett sig av." msgid "Lucky Rabbit's Foot" msgstr "Hartass" msgid "The %{name} increases the luck of the hero's army by %{count}." msgstr "%{name} ökar hjältens armés Tur med %{count}." msgid "" "A traveling merchant offers you a rabbit's foot, made of gleaming silver " "fur, for safe passage. The merchant explains the charm will increase your " "luck in combat." msgstr "" "En handelsresande erbjuder dig en hartass av skimrande silverpäls för ditt " "skydd en passage. Handelsmannen förklarar att berlocken kommer ge dig tur i " "strid." msgid "Golden Horseshoe" msgstr "Gyllene Hästsko" msgid "" "An ensnared Unicorn whinnies in fright. Murmuring soothing words, you set " "her free. Snorting and stamping her front hoof once, she gallops off. " "Looking down you see a golden horseshoe." msgstr "" "En fastsnarad Enhörning gnäggar av fruktan. Med en lugnande röst sÃ¥ släpper " "du enhörningen fri. Enhörningen frustar och stampar med ena framhoven en " "gÃ¥ng innan den rusar iväg. Du blickar ner och ser en gyllende hästsko ligga " "pÃ¥ marken." msgid "Gambler's Lucky Coin" msgstr "Spelarens Turmynt" msgid "" "You have captured a mischievous imp who has been terrorizing the region. In " "exchange for his release, he rewards you with a magical coin." msgstr "" "Du har fÃ¥ngat en lilldjävul som har terroriserat regionen. I utbyte mot " "lilldjävulens frihet sÃ¥ erbjuder han dig ett förtrollat mynt." msgid "Four-Leaf Clover" msgstr "Fyrklöver" msgid "" "In the middle of a patch of dead and dry vegetation, to your surprise you " "find a healthy green four-leaf clover." msgstr "" "I mitten av en fläck med torr och död vegetation, sÃ¥ hittar du ett ensamt " "litet grönt och fint fyrklöver." msgid "True Compass of Mobility" msgstr "Mobilitetens Kompass" msgid "The %{name} increases the hero's movement on land and sea." msgstr "%{name} ökar hjältens förflyttningskapacitet pÃ¥ bÃ¥de land och hav." msgid "" "An old man claiming to be an inventor asks you to try his latest invention. " "He then hands you a compass." msgstr "" "En gammal man som pÃ¥stÃ¥r sig vara en uppfinnare, frÃ¥gar dig om du vill pröva " "hans senaste mackapär. Sen ger han dig en kompass." msgid "Sailor's Astrolabe of Mobility" msgstr "Seglarens mobila astrolabium" msgid "The %{name} increases the hero's movement on sea." msgstr "%{name} ökar hjältens förflyttning pÃ¥ havet." msgid "" "An old sea captain is being tortured by Ogres. You save him, and in return " "he rewards you with a wondrous instrument to measure the distance of a star." msgstr "" "En gammal sjökapten blir torterad av nÃ¥gra Jättar. Du räddar honom och han " "belönar dig med ett fantastiskt instrument som kan mäta avstÃ¥ndet mellan " "stjärnor." msgid "Evil Eye" msgstr "Onda Ögat" msgid "The %{name} reduces the casting cost of curse spells by half." msgstr "%{name} sänker besvärjelsekostnaden för Förbannelser till hälften." msgid "" "While venturing into a decrepit hut you find the Skeleton of a long dead " "witch. Investigation of the remains reveals a glass eye rolling around " "inside an empty skull." msgstr "" "När du träder i in i en skröplig stuga sÃ¥ finner du ett skelett frÃ¥n en " "sedan länge avliden häxa. Vid genomsökning av kvarlevorna hittar du ett " "glasöga som rullar runt inuti en skalle." msgid "Enchanted Hourglass" msgstr "Förtrollat Timglas" msgid "" "The %{name} extends the duration of all the hero's spells by %{count} turns." msgstr "" "%{name} förlänger hjältens besvärjelsers varaktighet med %{count} rundor." msgid "" "A surprise turn in the landscape finds you in the midst of a grisly scene: " "Vultures picking at the aftermath of a terrible battle. Your cursory search " "of the remains turns up an enchanted hourglass." msgstr "" "Plötsligt stöter du pÃ¥ en makaber scen: Gamar som hackar pÃ¥ resterna av en " "brutal strid. Vid en snabb genomsökning av resterna finner du ett förtrollat " "timglas." msgid "Gold Watch" msgstr "Guldklocka" msgid "The %{name} doubles the effectiveness of the hero's hypnotize spells." msgstr "" "%{name} fördubblar effektiviteten för hjältens Hynotiseringsbesvärjelser." msgid "" "In reward for helping his cart out of a ditch, a traveling potion salesman " "gives you a \"magic\" gold watch. Unbeknownst to him, the watch really is " "magical." msgstr "" "Som belöning för att hjälpt en kvacksalvande handelsmans kärra ut ur diket, " "ger han dig en \"förtrollad\" guldklocka. Han verkar dock inte inse att " "klockan faktiskt är förtrollad." msgid "Skullcap" msgstr "Hätta" msgid "The %{name} halves the casting cost of all mind influencing spells." msgstr "%{name} halverar besvärjelsekostnaden för alla Sinnesbesvärjelser." msgid "" "A brief stop at an improbable rural inn yields an exchange of money, tales, " "and accidentally, luggage. You find a magical skullcap in your new backpack." msgstr "" "Vid ett kort stopp vid ett osannolikt gästgiveri byts det guld, historier " "och baggage. Du hittar en magisk hätta i din nya ryggsäck." msgid "Ice Cloak" msgstr "Ismantel" msgid "" "The %{name} halves all damage the hero's troops receive from cold spells." msgstr "%{name} halverar skadan hjältens trupper fÃ¥r frÃ¥n Köldbesvärjelser." msgid "" "Responding to the panicked cries of a damsel in distress, you discover a " "young woman fleeing from a hungry bear. You slay the beast in the nick of " "time, and the grateful Sorceress weaves a magic cloak from the bear's hide." msgstr "" "Du hörsammar de panikartade ropen frÃ¥n en dam i nöd och finner en ung kvinna " "flyendes frÃ¥n en hungrig björn. Du dräper besten i sista stund och den " "tacksamma Häxan syr en magisk kappa frÃ¥n björnens fäll." msgid "Fire Cloak" msgstr "Eldmantel" msgid "" "The %{name} halves all damage the hero's troops receive from fire spells." msgstr "%{name} halverar skadan hjältens trupper fÃ¥r frÃ¥n Eldbesvärjelser." msgid "" "You've come upon a fight between a Necromancer and a Paladin. The " "Necromancer blasts the Paladin with a fire bolt, bringing him to his knees. " "Acting quickly, you slay the evil one before the final blow. The grateful " "Paladin gives you the fire cloak that saved him." msgstr "" "Du stöter pÃ¥ en strid mellan en Svartkonstnär och en Paladin. " "Svartkonstnären blästrar paladinen med ett eldklot och fÃ¥r honom pÃ¥ knä. " "Genom snabbt agerande sÃ¥ hugger du huvudet av svartkonstnären innan denne " "hinner dräpa Paladinen. Den tacksamme Paladinen ger dig hans Eldmantel som " "skyddade honom under striden." msgid "Lightning Helm" msgstr "Blixthjälm" msgid "" "The %{name} halves all damage the hero's troops receive from lightning " "spells." msgstr "%{name} halverar skadan hjältens trupper fÃ¥r frÃ¥n Blixtbesvärjelser." msgid "" "A traveling tinker in need of supplies offers you a helm with a thunderbolt " "design on its top in exchange for food and water. Curious, you accept, and " "later find out that the helm is magical." msgstr "" "En gÃ¥rdsfarihandlare i nöd erbjuder dig en hjälm utsmyckad men en blixt i " "utbyte mot mat och vatten. Nyfiken sÃ¥ antar du budet och finner senare att " "hjälmen är förtrollad." msgid "Evercold Icicle" msgstr "Osmältbar Istapp" msgid "" "The %{name} causes the hero's cold spells to do %{count} percent more damage " "to enemy troops." msgstr "" "%{name} fÃ¥r hjältens köldbesvärjelser att göra %{count} procent mer skada " "till fiendetrupper." msgid "" "An icicle withstanding the full heat of the noonday sun attracts your " "attention. Intrigued, you break it off, and find that it does not melt in " "your hand." msgstr "" "En istapp fÃ¥ngar ditt intresse, den verkar helt kunna motstÃ¥ " "eftermiddagsolens strÃ¥lar. Du bryter loss den och den verkar inte heller " "smälta frÃ¥n värmen i din hand." msgid "Everhot Lava Rock" msgstr "SkÃ¥llhet Lavasten" msgid "" "The %{name} causes the hero's fire spells to do %{count} percent more damage " "to enemy troops." msgstr "" "%{name} fÃ¥r hjältens eldbesvärjelser att orsaka %{count} procent mer skada " "till fiendetrupper." msgid "" "Your wanderings bring you into contact with a tribe of ape-like beings using " "a magical lava rock that never cools to light their fires. You take pity on " "them and teach them to make fire with sticks. Believing you to be a god, the " "apes give you their rock." msgstr "" "Genom dina strövtÃ¥g sÃ¥ kommer du i kontakt med en klan av aplika varelser " "som använder sig av en aldrig avsvalnande lavasten för att göra upp eld. Du " "känner sympati för varelserna och lär dem att göra upp eld med hjälp av " "pinnar. I tron av att du är en gud sÃ¥ ger de dig sin sten." msgid "Lightning Rod" msgstr "Blixtstav" msgid "" "The %{name} causes the hero's lightning spells to do %{count} percent more " "damage to enemy troops." msgstr "" "%{name} fÃ¥r hjältens blixtbesvärjelser att göra %{count} procent mer skada " "till fiendetrupper." msgid "" "While waiting out a storm, a lighting bolt strikes a nearby cottage's " "lightning rod, which melts and falls to the ground. The tip of the rod, " "however, survives intact and makes your hair stand on end when you touch it. " "Hmm..." msgstr "" "Väntandes ut en storm, sÃ¥ slÃ¥r en blixt ner i en närliggande stugas " "Ã¥skledare som smälter och faller till marken. Ã…skledarens topp är dock " "intakt och fÃ¥r ditt hÃ¥r att resa sig när du rör vid den. Hmm..." msgid "Snake-Ring" msgstr "Ormring" msgid "The %{name} halves the casting cost of all of the hero's bless spells." msgstr "" "%{name}en halverar kostnaden för alla hjältens välsignelsebesvärjelser." msgid "" "You've found an oddly shaped ring on the finger of a long dead traveler. The " "ring looks like a snake biting its own tail." msgstr "" "Du hittar en annorlunda ring pÃ¥ ett fingerben av en för längesedan avliden " "äventyrare. Ringen har formen av en orm som biter sin egen svans." msgid "Ankh" msgstr "Ankh" msgid "" "The %{name} doubles the effectiveness of all of the hero's resurrect and " "animate spells." msgstr "" "%{name} dubblar styrkan för hjältens Ã…terupplivnings och Animera Död-" "besvärjelser." msgid "" "A fierce windstorm reveals the entrance to a buried tomb. Your investigation " "reveals that the tomb has already been looted, but the thieves overlooked an " "ankh on a silver chain in the dark." msgstr "" "En kraftig storm uppenbarar en ingÃ¥ng till en gravkammare. Vid din " "undersökning av gravkammaren visar dig att den redan är plundrad, men " "plundrarna har glömt ett ankhkors hängades pÃ¥ en silverkedja i mörkret." msgid "Book of Elements" msgstr "Elementbok" msgid "" "The %{name} doubles the effectiveness of all of the hero's summoning spells." msgstr "%{name} dubblerar styrkan för hjältens Framkallningsbesvärjelser." msgid "" "You come across a conjurer who begs to accompany you and your army awhile " "for safety. You agree, and he offers as payment a copy of the book of the " "elements." msgstr "" "Du möter en trollkarl som ber dig om ditt beskydd för ett tag. Du gÃ¥r med pÃ¥ " "att ge honom skydd och han erbjuder sig att ge dig en kopia av Elementboken." msgid "Elemental Ring" msgstr "Elementring" msgid "The %{name} halves the casting cost of all summoning spells." msgstr "%{name} halverar kostnaden för Framkallningsbesvärjelser." msgid "" "While pausing to rest, you notice a bobcat climbing a short tree to get at a " "crow's nest. On impulse, you climb the tree yourself and scare off the cat. " "When you look in the nest, you find a collection of shiny stones and a ring." msgstr "" "Vid en rast ser du ett lodjur som klättrar i ett träd i ett försök att nÃ¥ " "ett krÃ¥kbo. Impulsivt sÃ¥ klättrar du själv upp i trädet och skrämmer bort " "lodjuret. När du blickar ner i krÃ¥kboet sÃ¥ finner du en liten samling " "glänsande stenar och en ring." msgid "Holy Pendant" msgstr "Heligt Hängsmycke" msgid "The %{name} makes all of the hero's troops immune to curse spells." msgstr "%{name} gör hjältens trupper immuna mot Förbannelsebesvärjelser." msgid "" "In your wanderings you come across a hermit living in a small, tidy hut. " "Impressed with your mission, he takes time out from his meditations to bless " "and give you a charm against curses." msgstr "" "Genom dina resor stöter du pÃ¥ en eremit som lever i en liten prydlig stuga. " "Imponerad av ditt uppdrag sÃ¥ ger han dig en amulett som skydd mot " "förbannelser och fortsätter sedan att meditera." msgid "Pendant of Free Will" msgstr "Fria Viljans Hängsmycke" msgid "The %{name} makes all of the hero's troops immune to hypnotize spells." msgstr "%{name} gör hjältens trupper immuna mot Hynotiseringsbesvärjelser." msgid "" "Responding to cries for help, you find river Sprites making a sport of " "dunking an old man. Feeling vengeful, you rescue the man and drag a Sprite " "onto dry land for awhile. The Sprite, uncomfortable in the air, gives you a " "magic pendant to let him go." msgstr "" "Vid svar pÃ¥ ett rop pÃ¥ hjälp, finner du nÃ¥gra älvféer som har som nöje att " "dränka en gammal herre. Vredgad sÃ¥ räddar du mannen och drar upp en fé pÃ¥ " "torra land. Féen känner sig obekväm pÃ¥ fast mark och ger dig ett magiskt " "hängsmycke om du släpper den fri." msgid "Pendant of Life" msgstr "Livsmycke" msgid "The %{name} makes all of the hero's troops immune to death spells." msgstr "%{name}t gör hjältens trupper immuna mot Dödsbesvärjelser." msgid "" "A brief roadside encounter with a small caravan and a game of knucklebones " "wins a magic pendant. Its former owner says that it protects from " "Necromancers' death spells." msgstr "" "Vid ett hastigt möte med en liten karavan sÃ¥ deltar du i ett parti tärning. " "Vinnaren fÃ¥r ett förtrollat smycke. Du vinner och den trumpne tidigare " "ägaren berättar att det skyddar mot svartkonstnärernas dödsbesvärjelser." msgid "Serenity Pendant" msgstr "Stillhetens Hängsmycke" msgid "The %{name} makes all of the hero's troops immune to berserk spells." msgstr "%{name}t gör hjältens trupper immuna mot Bärsärkbesvärjelser." msgid "" "The sounds of combat draw you to the scene of a fight between an old " "Barbarian and an eight-headed Hydra. Your timely intervention swings the " "battle in favor of the man, and he rewards you with a pendant he used to use " "to calm his mind for battle." msgstr "" "Ljudet frÃ¥n en strid söker dig till en strid mellan en gammal Barbar och en " "Ã¥ttahövdad hydra. Du ingriper till Barbarens favör och han belönar dig med " "ett hängsmycke som han brukar använda innan strid för att lugna sitt inre." msgid "Seeing-eye Pendant" msgstr "Seende Hängsmycke" msgid "The %{name} makes all of the hero's troops immune to blindness spells." msgstr "%{name}t gör hjältens trupper immuna mot Förblindningsbesvärjelser." msgid "" "You come upon a very old woman, long blind from cataracts and dying alone. " "You tend to her final needs and promise a proper burial. Grateful, she gives " "you a magic pendant emblazoned with a stylized eye. It lets you see with " "your eyes closed." msgstr "" "Du stöter pÃ¥ en mycket Ã¥lderstigen tant, sedan länge blind av grÃ¥starr och " "levandes sina sista dagar. Du hjälper henne med nÃ¥gra bestyr och lovar henne " "en riktig begravning. Tacksam sÃ¥ ger hon dig ett magiskt hängsmycke, " "dekorerat med ett stiliserat öga. Det lÃ¥ter dig se med dina ögon stängda." msgid "Kinetic Pendant" msgstr "Kinematiskt Hängsmycke" msgid "The %{name} makes all of the hero's troops immune to paralyze spells." msgstr "%{name}t gör hjältens trupper immuna mot Paralyseringsbesvärjelser." msgid "" "You come across a golem wearing a glowing pendant and blocking your way. " "Acting on a hunch, you cut the pendant from its neck. Deprived of its power " "source, the golem breaks down, leaving you with the magical pendant." msgstr "" "Du stöter pÃ¥ en Golem bärandes ett lysande hängsmycke som blockerar din väg. " "Instinktivt sÃ¥ slÃ¥r du av smycket frÃ¥n dess nacke. I avsaknad frÃ¥n kraften " "frÃ¥n smycket sÃ¥ ramlar Golemen ner till grus och stoft. Du plockar pÃ¥ dig " "smycket." msgid "Pendant of Death" msgstr "Dödssmycke" msgid "The %{name} makes all of the hero's troops immune to holy spells." msgstr "%{name}t gör hjältens trupper immuna mot Heliga besvärjelser." msgid "" "A quick and deadly battle with a Necromancer wins you his magical pendant. " "Later, a Wizard tells you that the pendant protects undead under your " "control from holy word spells." msgstr "" "Efter en snabb och dödlig batalj med en Svartkonstnär sÃ¥ tar du hans " "hängsmycke. Senare sÃ¥ berättar en Trollkarl att smycket skyddar dess bärares " "odöda undersÃ¥tar frÃ¥n heliga besvärjelser." msgid "Wand of Negation" msgstr "Upphävningstrollstav" msgid "" "The %{name} makes all of the hero's troops immune to dispel magic spells." msgstr "%{name}t gör hjältens trupper immuna mot Skingringsbesvärjelser." msgid "" "You meet an old Wizard friend of yours traveling in the opposite direction. " "He presents you with a gift: A wand that prevents the use of the dispel " "magic spell on your allies." msgstr "" "Du möter en gammal Trollkarlsvän som ska Ã¥t motsatt riktning. Han ger dig en " "gÃ¥va: en Trollstav som förhindrar att fienden kastar " "förskingringsbesvärjelser pÃ¥ dig och dina allierade." msgid "Golden Bow" msgstr "Gyllene BÃ¥ge" msgid "" "The %{name} eliminates the %{count} percent penalty for the hero's troops " "shooting past obstacles (e.g. castle walls)." msgstr "" "%{name} sänker handikappet med %{count} procent för hjältens varelsers " "projektiler när de passerar hinder, exempelvis slottsmurar." msgid "" "A chance meeting with a famous Archer finds you in a game of knucklebones " "pitting his bow against your horse. You win." msgstr "" "I ett möte med en känd BÃ¥gskytt i ett parti benkastning föreslÃ¥r han att " "satsa sin pilbÃ¥ge mot din häst. Du vinner!" msgid "Telescope" msgstr "Teleskop" msgid "" "The %{name} increases the amount of terrain the hero reveals when " "adventuring by %{count} extra square." msgstr "%{name} ökar radien som hjälten ser pÃ¥ kartan med %{count} ruta." msgid "" "A merchant from far away lands trades you a new invention of his people for " "traveling supplies. It makes distant objects appear closer, and he calls " "it...\n" "\n" "a telescope." msgstr "" "Med en handelsman frÃ¥n fjärran land byter du till en uppfinning frÃ¥n hans " "hemland. Den fÃ¥r ting i fjärran att tillsynes se närmare ut genom den. Han " "kallas den...\n" "\n" "ett teleskop." msgid "Statesman's Quill" msgstr "Statsmannens Fjäderpenna" msgid "" "The %{name} reduces the cost of surrender to %{count} percent of the total " "cost of troops the hero has in their army." msgstr "" "%{name} sänker kostnaden för kapitulation med %{count} procent av " "totalkostnaden för trupperna hjälten har i sin armé." msgid "" "You pause to help a diplomat with a broken axle fix his problem. In " "gratitude, he gives you a writing quill with magical properties which he " "says will \"help people see things your way\"." msgstr "" "Du stannar till för att hjälpa en diplomat med en bruten axel. Av tacksamhet " "skänker han dig en fjäderpenna med magiska egenskaper som han säger " "\"hjälper andra att se saker ur din synpunkt\"." msgid "Wizard's Hat" msgstr "Trollkarlshatt" msgid "" "The %{name} increases the duration of the hero's spells by %{count} turns." msgstr "" "%{name}en ökar varaktigheten för hjältens besvärjelser med %{count} rundor." msgid "" "You see a Wizard fleeing from a Griffin and riding like the wind. The Wizard " "opens a portal and rides through, getting his hat knocked off by the edge of " "the gate. The Griffin follows; the gate closes. You pick the hat up, dust it " "off, and put it on." msgstr "" "Du ser en Trollkarl som flyr genom luften frÃ¥n en Grip. Trollkarlen öppnar " "en portal och flyger igenom den. Hans hatt träffar portalens övre kant och " "faller av. Gripen följer efter genom portalen som sedan stängs. Du plockar " "upp hatten, borstar den rent frÃ¥n smuts och sätter den pÃ¥ ditt huvud." msgid "Power Ring" msgstr "Kraftring" msgid "The %{name} returns %{count} extra spell points per day to the hero." msgstr "" "%{name} Ã¥terhämtar %{count} extra besvärjelsepoäng per dag till hjälten." msgid "" "You find a small tree that closely resembles the great Warlock Carnauth with " "a ring around one of its twigs. Scraps of clothing and rotting leather lead " "you to suspect that it IS Carnauth, transformed. Since you can't help him, " "you take the magic ring." msgstr "" "Du finner ett träd som pÃ¥minner starkt om den store Häxmästaren Carnauth med " "en ring runt en av dess smÃ¥ grenar. Trasor av gamla kläder och ruttnade " "läder fÃ¥r dig att inse att trädet faktiskt är Carnauth som är förvandlad. " "Eftersom du inte kan hjälpa honom sÃ¥ tar du ringen, dÃ¥ du tycker att du har " "bättre nytta av den." msgid "Ammo Cart" msgstr "Ammunitionsvagn" msgid "" "The %{name} provides endless ammunition for all of the hero's troops that " "shoot." msgstr "%{name} förser hjältens trupper med oändligt antal projektiler." msgid "" "An ammunition cart in the middle of an old battlefield catches your eye. " "Inspection shows it to be in good working order, so you take it along." msgstr "" "En vagn full med pilar och projektiler stÃ¥r övergiven mitt pÃ¥ ett gammalt " "slagfält. Vid en inspektion sÃ¥ verkar vagnen bara i fullt fungerande skick, " "sÃ¥ du tar med dig den." msgid "Tax Lien" msgstr "Skuldebrev" msgid "The %{name} costs the hero %{count} gold pieces per day." msgstr "%{name} kostar hjälten -%{count} guld per dag." msgid "" "Your big spending habits have earned you a massive tax bill that you can't " "hope to pay. The tax man takes pity and agrees to only take 250 gold a day " "from your account for life. Check here if you want one dollar to go to the " "presidential campaign election fund." msgstr "" "Ditt slösaktiga beteende har gett din en stor skatteskuld som du inte klarar " "av att betala. Skattemasen känner sympati för dig och gÃ¥r med pÃ¥ att endast " "begära 250 guld per dag frÃ¥n dig... för resten av ditt liv." msgid "Hideous Mask" msgstr "Otäck Mask" msgid "The %{name} prevents all 'wandering' armies from joining the hero." msgstr "" "%{name} förhindrar alla varelser pÃ¥ kartan att ansluta sig till hjältens " "armé." msgid "" "Your looting of the grave of Sinfilas Gardolad, the famous shapeshifting " "Warlock, unearths his fabled mask. Trembling, you put it on and it twists " "your visage into an awful grimace! Oh no! It's actually the hideous mask of " "Gromluck Greene, and you are stuck with it." msgstr "" "Vid din plundring av den kände hamnskiftaren Sinfilas Gardolads grav finner " "du hans omskrivne mask. Skakande sÃ¥ sätter du pÃ¥ dig masken och den " "förvrider ditt anlete till en ful grimas. Oh nej! Det visar sig att det " "faktiskt är Gromluck Greenes otäcka mask och den sitter fast." msgid "Endless Pouch of Sulfur" msgstr "Bottenlös SvavelpÃ¥se" msgid "The %{name} provides %{count} unit of sulfur per day." msgstr "%{name} förser hjälten med %{count} enheter Svavel per dag." msgid "" "You visit an alchemist who, upon seeing your army, is swayed by the " "righteousness of your cause. The newly loyal subject gives you his endless " "pouch of sulfur to help with the war effort." msgstr "" "Du besöker en alkemist som vid Ã¥synen av din armé övertalas av ditt fälttÃ¥gs " "rättfärdiga skäl. Alkemisten ger dig hans Bottenlösa SvavelpÃ¥se för att " "hjälpa dig med ditt krigsföretag." msgid "Endless Vial of Mercury" msgstr "Bottenlös Kvicksilverflaska" msgid "The %{name} provides %{count} unit of mercury per day." msgstr "%{name} förser hjälten med %{count} enheter Kvicksilver per dag." msgid "" "A brief stop at a hastily abandoned Wizard's tower turns up a magical vial " "of mercury that always has a little left on the bottom. Recognizing a " "treasure when you see one, you cap it and slip it in your pocket." msgstr "" "En kort stopp vid ett övergivet Trollkarlstorn hittar du en liten flaska med " "Kvicksilver som alltid verkar ha lite kvar i botten, oavsett hur mycket man " "häller ur. Du inser värdet av ditt fynd och stoppar ner den i din ficka." msgid "Endless Pouch of Gems" msgstr "Bottenlös Ädelstenspung" msgid "The %{name} provides %{count} unit of gems per day." msgstr "%{name} förser hjälten med %{count} enheter Ädelstenar per dag." msgid "" "A short rainstorm brings forth a rainbow...and you can see the end of it. " "Riding quickly, you seize the pot of gold you find there. The leprechaun who " "owns it, unable to stop you from taking it, offers an endless pouch of gems " "for the return of his gold. You accept." msgstr "" "En kort regnskur frambringar en regnbÃ¥ge och du kan se var den slutar. Du " "rider snabbt dit och lägger beslag pÃ¥ en kruka med guld där. Pysslingen som " "äger den är oförmögen att stoppa dig och erbjuder dig en Bottenlös " "Ädelstenspung i utbyte mot hans guld. Du tar budet." msgid "Endless Cord of Wood" msgstr "Ändlös Trätrave" msgid "The %{name} provides %{count} unit of wood per day." msgstr "%{name} ger %{count} enheter med Trä per dag." msgid "" "Pausing to rest and light a cook fire, you pull wood out of a nearby pile of " "dead wood. As you keep pulling wood from the pile, you notice that it " "doesn't shrink. You realize to your delight that the wood is enchanted, so " "you take it along." msgstr "" "Vid ett uppehÃ¥ll för vila och mat sÃ¥ gör du upp en eld. När du sedan matar " "elden med vedträn taget frÃ¥n en trave i närheten, sÃ¥ upptäcker du att traven " "aldrig blir mindre. Du inser glatt att vedtraven är förtrollad sÃ¥ du lastar " "med dig vedtraven." msgid "Endless Cart of Ore" msgstr "Bottenlös Malmkärra" msgid "The %{name} provides %{count} unit of ore per day." msgstr "%{name} förser hjälten med %{count} enheter Malm per dag." msgid "" "You've found a Goblin weapon smithy making weapons for use against humans. " "With a tremendous yell you and your army descend upon their camp and drive " "them away. A search finds a magic ore cart that never runs out of iron." msgstr "" "Du har hittat en vapensmedja med vättar som gör vapen för att använda mot " "människor. Med ett kraftfullt rop anfaller du med din armé mot smedjan och " "alla vättar flyr i skräck. Vid en genomletning sÃ¥ finner du en förtrollad " "malmkärra som aldrig blir tom pÃ¥ malm." msgid "Endless Pouch of Crystal" msgstr "Bottenlös Kristallpung" msgid "The %{name} provides %{count} unit of crystal per day." msgstr "%{name} förser hjälten med %{count} enheter med Kristaller per dag." msgid "" "Taking shelter from a storm in a small cave, you notice a small patch of " "crystal in one corner. Curious, you break a piece off and notice that the " "original crystal grows the lost piece back. You decide to stuff the entire " "patch into a pouch and take it with you." msgstr "" "Du har tagit skydd i en grotta undan en storm, i grottan ser du en liten bit " "kristall i ett hörn. Nyfiken sÃ¥ bryter du loss en bit och märker att " "kristallen växer tillbaka. Du väljer att bryta loss hela kristallen och ta " "den med dig." msgid "Spiked Helm" msgstr "Spikhjälm" msgid "" "Your army is ambushed by a small tribe of wild (and none too bright) Orcs. " "You fend them off easily and the survivors flee in all directions. One of " "the Orcs was wearing a polished spiked helm. Figuring it will make a good " "souvenir, you take it." msgstr "" "Din armé hamnar i ett bakhÃ¥ll för en stam med vilda (och dumma) Orcher. Det " "är ett enkelt motstÃ¥nd och de överlevande orcherna flyr i alla riktningar. " "En av orcherna bar en putsad Spikhjäm. Du tycker det blir en fin souvenir " "och tar den." msgid "Spiked Shield" msgstr "Spiksköld" msgid "" "You come upon a bridge spanning a dry gully. Before you can cross, a Troll " "steps out from under the bridge and demands payment before it will permit " "you to pass. You refuse, and the Troll charges, forcing you to slay it. You " "take its spiked shield as a trophy." msgstr "" "Du kommer fram till en bro som sträcker sig över en ravin. Innan du beträder " "bron sÃ¥ kommer ett Troll fram under bron och kräver tull innan du kan " "passera. Du vägrar och Trollet anfaller som tvingar dig att dräpa det. Du " "tar dess Spiksköld som en trofé." msgid "White Pearl" msgstr "Vit Pärla" msgid "" "A walk across a dry saltwater lake bed yields an unlikely prize: A white " "pearl amidst shattered shells and debris." msgstr "" "En promenad tvärs över en gammal uttorkad sjöbotten belönas med att du " "hittar en Vit Pärla mitt bland snäckskal och brÃ¥te." msgid "Black Pearl" msgstr "Svart Pärla" msgid "" "Rumors of a Griffin of unusual size preying upon the countryside lead you to " "its cave lair. A quick, brutal fight dispatches the beast, and a search of " "its foul nest turns up a huge black pearl." msgstr "" "Rykten om att en ovanligt stor Grip som jagar pÃ¥ landsbygden fÃ¥r dig att " "leta upp dess bo. Efter en kort men brutal strid sÃ¥ avlivar du besten och " "söker igenom dess illaluktande bo och hittar en enorm Svart Pärla." msgid "Magic Book" msgstr "Magibok" msgid "The %{name} enables the hero to cast spells." msgstr "%{name}en lÃ¥ter hjälten att kasta besvärjelser." msgid "Dummy 1" msgstr "Atrapp 1" msgid "The reserved artifact." msgstr "Reseverad artefakt." msgid "Dummy 2" msgstr "Atrapp 2" msgid "Dummy 3" msgstr "Atrapp 3" msgid "Dummy 4" msgstr "Atrapp 4" msgid "Spell Scroll" msgstr "Besvärjelserulle" msgid "" "This %{name} gives the hero the ability to cast the %{spell} spell if the " "hero has a Magic Book." msgstr "" "%{name} ger hjälten förmÃ¥gan att kasta besvärjelsen %{spell} om hjälten har " "en Magibok." msgid "" "You find an elaborate container which houses an old vellum scroll. The runes " "on the container are very old, and the artistry with which it was put " "together is stunning. As you pull the scroll out, you feel imbued with " "magical power." msgstr "" "Du finner en välbyggd lÃ¥da som innehÃ¥ller en gammal pergamentrulle. Runorna " "pÃ¥ lÃ¥dan är mycket gamla och hantverket är imponerande. När du tar ut rullen " "sÃ¥ känner du dess magiska krafter i handen." msgid "Arm of the Martyr" msgstr "Martyrens Arm" msgid "" "The %{name} increases the hero's spell power by %{count} but adds the undead " "morale penalty." msgstr "" "%{name} ökar hjältens Trollkonst med %{count}, men sänker stridsmoralen för " "dennes levande trupper." msgid "" "One of the less intelligent members of your party picks up an arm off of the " "ground. Despite its missing a body, it is still moving. Your troops find the " "dismembered arm repulsive, but you cannot bring yourself to drop it: it " "seems to hold some sort of magical power that influences your decision " "making." msgstr "" "En av de mindre begÃ¥vade ur din armé plockar upp en avhuggen arm frÃ¥n " "marken. Trots att resten av kroppen saknas sÃ¥ rör den sig fortfarande. Dina " "trupper finner den avhuggna armen motbjudande. Men du kan inte förmÃ¥ dig " "själv att göra dig av med den eftersom den verkar besitta nÃ¥gon förtrollning " "som pÃ¥verkar din beslutsförmÃ¥ga." msgid "Breastplate of Anduran" msgstr "Andurans Bröstharnesk" msgid "The %{name} increases the hero's defense by %{count}." msgstr "%{name} ökar hjältens Försvar med %{count}." msgid "" "You come upon a sign. It reads: \"Here lies the body of Anduran. Bow and " "swear fealty, and you shall be rewarded.\" You decide to do as it says. As " "you stand up, you feel a coldness against your skin. Looking down, you find " "that you are suddenly wearing a gleaming, ornate breastplate." msgstr "" "Du stöter pÃ¥ en skylt där det stÃ¥r: \"Här ligger Andurans kropp. Buga dig " "och svär din trohet sÃ¥ ska du bli belönad.\" Du bestämmer att gör som det " "stÃ¥r. När du ställer dig upp känner du kyla mot din hud. Du upptäcker att du " "plötsligt bär ett skimrande utsmyckat bröstharnesk." msgid "Broach of Shielding" msgstr "Skyddsbrosch" msgid "" "The %{name} provides %{count} percent protection from Armageddon and " "Elemental Storm, but decreases spell power by 2." msgstr "" "%{name} ger %{count} procent skydd mot Harmageddon och Elementstorm " "besvärjelser, men sänker Trollkonsten för hjälten med 2." msgid "" "A kindly Sorceress thinks that your army's defenses could use a magical " "boost. She offers to enchant the Broach that you wear on your cloak, and you " "accept." msgstr "" "En vänlig häxa tycker att din armés försvar skulle kunna behöva en magisk " "förstärkning. Hon erbjuder sig att förtrolla broschen som du bär pÃ¥ din " "kappa och du accepterar erbjudandet." msgid "Battle Garb of Anduran" msgstr "Andurans Stridsrustning" msgid "" "The %{name} combines the powers of the three Anduran artifacts. It provides " "maximum luck and morale for the hero's troops and gives the hero the Town " "Portal spell." msgstr "" "%{name} förenar krafterna frÃ¥n Andurans tre artefakter. Den ger maximalt med " "tur och stridsmoral till hjältens trupper och ger hjälten förmÃ¥gan att kasta " "besvärjelsen Stadsportal." msgid "" "Out of pity for a poor peasant, you purchase a chest of old junk they are " "hawking for too much gold. Later, as you search through it, you find it " "contains the 3 pieces of the legendary battle garb of Anduran!" msgstr "" "Av medömkan för en fattig bonde sÃ¥ köper du en kista med bröte för alldeles " "för mycket guld. Senare när du kollar igenom kistan, finner du de tre delar " "av Andurans legendariska Stridsrustning." msgid "Crystal Ball" msgstr "Kristallkula" msgid "" "The %{name} lets the hero get more specific information about monsters, " "enemy heroes, and castles nearby the hero." msgstr "" "%{name} ger mer specifik information om varelser, fiendehjältar och slott " "som befinner sig nära hjälten." msgid "" "You come upon a caravan of gypsies who are feasting and fortifying their " "bodies with mead. They call you forward and say \"If you prove that you can " "dance the Rama-Buta, we will reward you.\" You don't know it, but try " "anyway. They laugh hysterically, but admire your bravery, giving you a " "Crystal Ball." msgstr "" "Du stöter pÃ¥ en karavan med zigenare som festar och stärker sina kroppar med " "mjöd. De ropar pÃ¥ dig och säger \"Om du kan bevisa att du kan dansa Rama-" "Buta sÃ¥ belönar vi dig.\" Det kan du inte men du försöker ändÃ¥. De skrattar " "hysteriskt, men de uppskattar ditt mod och ger dig en Kristallkula." msgid "Heart of Fire" msgstr "Eldhjärta" msgid "" "The %{name} provides %{count} percent protection from fire, but doubles the " "damage taken from cold." msgstr "" "%{name} ger hjältens trupper %{count} procents skydd mot Eld, men dubblar " "skadan frÃ¥n Köld." msgid "" "You enter a recently burned glade and come upon a Fire Elemental sitting " "atop a rock. It looks up, its flaming face contorted in a look of severe " "pain. It then tosses a glowing object at you. You put up your hands to block " "it, but it passes right through them and sears itself into your chest." msgstr "" "Du kliver in i en nyligen nerbrunnen glänta och stöter pÃ¥ ett Eldväsen " "sittandes pÃ¥ett stenblock. Den tittar pÃ¥ dig med en plÃ¥gad blick och kastar " "ett brinnande klot mot dig. Du försöker skydda dig med dina händer men du är " "för lÃ¥ngsam och eldklotet träffar dig mitt pÃ¥ bröstet." msgid "Heart of Ice" msgstr "Ishjärta" msgid "" "The %{name} provides %{count} percent protection from cold, but doubles the " "damage taken from fire." msgstr "" "%{name} ger hjältens trupper %{count} procents skydd mot Köld, men dubblar " "skadan frÃ¥n Eld." msgid "" "Suddenly, a biting coldness engulfs your body. You seize up, falling from " "your horse. The pain subsides, but you still feel as if your chest is " "frozen. As you pick yourself up off of the ground, you hear hearty laughter. " "You turn around just in time to see a Frost Giant run off into the woods and " "disappear." msgstr "" "Plötsligt sÃ¥ drabbas din kropp av en isande kylda. Du försöker härda ut men " "du faller frÃ¥n din häst. Smärtan försvinner men du känner fortfarande som om " "ditt bröst är fruset. När du försöker resa dig upp hör du ett hjärtligt " "skratt. Du vänder dig om och hinner precis se skymten av en Frostjätte innan " "den springer in i skogen och försvinner." msgid "Helmet of Anduran" msgstr "Andurans Hjälm" msgid "" "You spy a gleaming object poking up out of the ground. You send a member of " "your party over to investigate. He comes back with a golden helmet in his " "hands. You realize that it must be the helmet of the legendary Anduran, the " "only man who was known to wear solid gold armor." msgstr "" "Du ser nÃ¥got glimmande föremÃ¥l sticka upp ur marken. Du skickar iväg en " "undersÃ¥te att kolla vad det är. Han kommer tillbaka med en gyllene hjälm i " "sina händer. Du inser att hjälmen mÃ¥ste vara frÃ¥n den legendariske Anduran, " "den enda mannen som var känd för att bära rustning i det renaste guld." msgid "Holy Hammer" msgstr "Helig Hammare" msgid "" "You come upon a battle where a Paladin has been mortally wounded by a group " "of Zombies. He asks you to take his hammer and finish what he started. As " "you pick it up, it begins to hum, and then everything becomes a blur. The " "Zombies lie dead, the hammer dripping with blood. You strap it to your belt." msgstr "" "Du stöter pÃ¥ en strid där en Paladin är dödligt sÃ¥rad frÃ¥n en grupp med " "Zombier. Han ber dig att ta hans stridshammare och avsluta det han pÃ¥börjat. " "När du tar hammaren sÃ¥ börjar den surra och sen blir allting en oklar dimma. " "Zombierna ligger döda och hammaren är blodstänkt. Du tar hammaren." msgid "Legendary Scepter" msgstr "Legendarisk Spira" msgid "The %{name} adds %{count} points to all attributes." msgstr "%{name} ger hjälten %{count} till alla dennes primära förmÃ¥gor." msgid "" "Upon cresting a small hill, you come upon a ridiculous looking sight. A " "Sprite is attempting to carry a Scepter that is almost as big as it is. " "Trying not to laugh, you ask, \"Need help?\" The Sprite glares at you and " "answers: \"You think this is funny? Fine. You can carry it. I much prefer " "flying anyway.\"" msgstr "" "När du är pÃ¥ väg upp till toppen av en liten kulle stöter du pÃ¥ en löjlig " "syn. En fe försöker att bära en spira som nästan är större än den själv. Du " "försöker lÃ¥ta bli att skratta när du frÃ¥gar, \"Behöver du hjälp?\" Fen " "blänger pÃ¥ dig och svarar: \"Tror du att det är roligt? Visst, du kan bära " "den själv. Jag föredrar ändÃ¥ att flyga.\"" msgid "" "An old seaman tells you a tale of an enchanted masthead that he used in his " "youth to rally his crew during times of trouble. He then hands you a faded " "map that shows where he hid it. After much exploring, you find it stashed " "underneath a nearby dock." msgstr "" "En gammal sjöman berättar för dig om en förtrollad Mastlykta som han i sina " "yngre dagar samlade sin besättning kring i brÃ¥da tider. Sen visar han en " "gammal blekt karta och visar var han glömde den. Efter mycket letande hittar " "du den under en närliggande kaj." msgid "Masthead" msgstr "Mastlykta" msgid "" "The %{name} boosts the hero's troops' luck and morale by %{count} each in " "sea combat." msgstr "" "%{name} ökar hjältens truppers Tur och höjer deras Stridsmoral med %{count} " "i sjöstrider." msgid "Sphere of Negation" msgstr "Upphävningsklot" msgid "The %{name} disables all spell casting, for both sides, in combat." msgstr "" "%{name} förhindrar all besvärjelsekastning i strid för hjälten och fiendens " "besvärjelsekastare." msgid "" "You stop to help a Peasant catch a runaway mare. To show his gratitude, he " "hands you a tiny sphere. As soon as you grasp it, you feel the magical " "energy drain from your limbs..." msgstr "" "Du stannar för att hjälpa en bonde att infÃ¥nga ett förrymt sto. För att visa " "sin uppskattning ger han dig en liten kula. När du tar den i din hand känner " "du hur dina magiska krafter rinner ur dig..." msgid "Staff of Wizardry" msgstr "Trollkonststav" msgid "The %{name} boosts the hero's spell power by %{count}." msgstr "%{name} ökar hjältens Trollkonst med %{count}." msgid "" "While out scaring up game, your troops find a mysterious staff levitating " "about three feet off of the ground. They hand it to you, and you notice an " "inscription. It reads: \"Brains best brawn and magic beats might. Heed my " "words, and you'll win every fight.\"" msgstr "" "När dina trupper hÃ¥lla pÃ¥ att passa vild sÃ¥ finner de en mystisk stav sväva " "tre fot ovanför marken. De ger den till dig och du lägger märke till en " "inskription som lyder: \"Snille framför styrka och magi framför muskler. " "Lyssna till mina ord och du kommer aldrig förlora en strid.\"" msgid "Sword Breaker" msgstr "Svärdsbrytare" msgid "The %{name} increases the hero's defense by %{count} and attack by 1." msgstr "%{name} ökar hjältens Försvar med %{count} och dennes Anfall med 1." msgid "" "A former Captain of the Guard admires your quest and gives you the enchanted " "Sword Breaker that he relied on during his tour of duty." msgstr "" "En föredetta kaptensvakt beundrar ditt företag och ger dig den förtrollade " "Svärdbrytaren som han bar under sin tjänstgöring." msgid "Sword of Anduran" msgstr "Andurans Svärd" msgid "" "A Troll stops you and says: \"Pay me 5,000 gold, or the Sword of Anduran " "will slay you where you stand.\" You refuse. The troll grabs the sword " "hanging from its belt, screams in pain, and runs away. Picking up the fabled " "sword, you give thanks that half-witted Trolls tend to grab the wrong end of " "sharp objects." msgstr "" "Ett troll stoppar dig och säger: \"Ge mig 5,000 guld, annars kommer Andurans " "svärd att dräpa dig där du stÃ¥r.\" Du nekar. Trollet tar tag i svärdet som " "hänger frÃ¥n bältet, skriker i smärta och springer iväg. Du plockar pÃ¥ det " "förtäljda svärdet och tackar det sinnessvaga trollets försök att ta tag i " "fel ände av det vassa objektet." msgid "Spade of Necromancy" msgstr "Svartkonstens spade" msgid "The %{name} gives the hero increased necromancy skill." msgstr "%{name} ökar hjältens SvartkonstförmÃ¥ga." msgid "" "A dirty shovel has been thrust into a dirt mound nearby. Upon investigation, " "you discover it to be the enchanted shovel of the Gravediggers, long thought " "lost by mortals." msgstr "" "En skitig spade är nersatt bredvid en jordhög. Vid inspektion upptäcker du " "att det faktiskt är Dödgrävarnas förtrollande spade som sedan länge ansetts " "vara förlorad." msgid "spellBonus|selected by user" msgstr "vald av spelaren" msgid "Wood" msgstr "Trä" msgid "Mercury" msgstr "Kvicksilver" msgid "Ore" msgstr "Malm" msgid "Sulfur" msgstr "Svavel" msgid "Crystal" msgstr "Kristall" msgid "Gems" msgstr "Ädelstenar" msgid "Gold" msgstr "Guld" msgid "" "There are seven resources in Heroes 2, used to build and improves castles, " "purchase troops and recruit heroes. Gold is the most common, required for " "virtually everything. Wood and ore are used for most buildings. Gems, " "Mercury, Sulfur and Crystal are rare magical resources used for the most " "powerful creatures and buildings." msgstr "" "Det finns 7 olika resurser i Heroes och Might and Magic 2 som används för " "att bygga och förbättra slott, rekrytera trupper och hjältar. Guld är den " "vanligaste, som behövs till det mesta. Trä och Malm används mest till " "byggnader. Ädelstenar, Kvicksilver, Svavel och Kristaller är sällsynta " "magiska resurser som används till de mest kraftfulla varelser och byggnader." msgid "" "Causes a giant fireball to strike the selected area, damaging all nearby " "creatures." msgstr "" "Skapar ett stort eldklot som träffar ett utvalt omrÃ¥de och skadar alla " "varelser i närheten." msgid "Fireball" msgstr "Eldklot" msgid "Fireblast" msgstr "Eldexplosion" msgid "" "An improved version of fireball, fireblast affects two hexes around the " "center point of the spell, rather than one." msgstr "" "En förbättrad variant av Eldklot. Eldexplosion angriper tvÃ¥ hexagonaler " "runtomkring den centrala punkten, istället för en." msgid "Causes a bolt of electrical energy to strike the selected creature." msgstr "FÃ¥r en blixt att slÃ¥ ned pÃ¥ vald varelse." msgid "Lightning Bolt" msgstr "Ljusblixt" msgid "Chain Lightning" msgstr "Kedjeblixt" msgid "" "Causes a bolt of electrical energy to strike a selected creature, then " "strike the nearest creature with half damage, then strike the NEXT nearest " "creature with half again damage, and so on, until it becomes too weak to be " "harmful. Warning: This spell can hit your own creatures!" msgstr "" "Framkallar en blixt av elektrisk energi som slÃ¥r ned pÃ¥ en vald varelse, " "sedan slÃ¥ ned pÃ¥ de närmaste varelserna med halverad skada, sedan slÃ¥ ned pÃ¥ " "de NÄST närmaste varelserna med halverad skada, o.s.v., tills den blir för " "svag för att skada. Varning: Denna besvärjelse kan träffa dina egna varelser!" msgid "Teleport" msgstr "Teleportera" msgid "" "Teleports the creature you select to any open position on the battlefield." msgstr "Teleporterar vald varelse till valfri fri position pÃ¥ slagfältet." msgid "Cure" msgstr "Hela" msgid "" "Removes all negative spells cast upon one of your units, and restores up to " "%{count} HP per level of spell power." msgstr "" "Skingrar alla onda besvärjelser frÃ¥n en av dina varelser, Ã¥terställer upp " "till %{count} hälsopoäng per Trollkonststyrka." msgid "Mass Cure" msgstr "Masshelning" msgid "" "Removes all negative spells cast upon your forces, and restores up to " "%{count} HP per level of spell power, per creature." msgstr "" "Tar bort alla onda besvärjelser som har kastats pÃ¥ dina trupper och " "Ã¥terställer upp till %{count} hälsopoäng per Trollkonststyrka per varelse." msgid "Resurrect" msgstr "" "Ã…ter-\n" "upplivning" msgid "Resurrects creatures from a damaged or dead unit until end of combat." msgstr "" "Ã…terupplivar varelser frÃ¥n en skadad eller död enhet tills striden slutar." msgid "Resurrect True" msgstr "" "Äkta\n" "Ã…terupplivning" msgid "Resurrects creatures from a damaged or dead unit permanently." msgstr "Ã…terupplivar varelser frÃ¥n en skadad eller död varelse permanent." msgid "Haste" msgstr "Skynda" msgid "Increases the speed of any creature by %{count}." msgstr "Höjer en varelses hastighet med %{count}." msgid "Increases the speed of all of your creatures by %{count}." msgstr "Höjer alla dina varelsers hastighet med %{count}." msgid "Mass Haste" msgstr "" "Mass-\n" "skyndning" msgid "Slows target to half movement rate." msgstr "Saktar ned varelsens hastighet till hälften." msgid "spell|Slow" msgstr "Förslöa" msgid "Mass Slow" msgstr "" "Mass-\n" "förslöelse" msgid "Slows all enemies to half movement rate." msgstr "Saktar ned alla fiendevarelser till hälften." msgid "Clouds the affected creatures' eyes, preventing them from moving." msgstr "Grumlar varelsens ögon som förhindrar den frÃ¥n att röra sig." msgid "spell|Blind" msgstr "Förblinda" msgid "Bless" msgstr "Välsignelse" msgid "Causes the selected creatures to inflict maximum damage." msgstr "FÃ¥r en varelse att utdela maximal skada." msgid "Causes all of your units to inflict maximum damage." msgstr "FÃ¥r alla dina varelser att utdela maximal skada." msgid "Mass Bless" msgstr "" "Mass-\n" "-välsignelse" msgid "Magically increases the defense skill of the selected creatures." msgstr "Ökar vald varelses Försvar genom förtrollning." msgid "Stoneskin" msgstr "Stenhud" msgid "" "Increases the defense skill of the targeted creatures. This is an improved " "version of Stoneskin." msgstr "" "Ökar Försvaret för vald varelse. Detta är en förbättrad variant av Stenhud." msgid "Steelskin" msgstr "StÃ¥lhud" msgid "Causes the selected creatures to inflict minimum damage." msgstr "FÃ¥r en varelse att utdela minimal skada." msgid "Curse" msgstr "Förbannelse" msgid "Causes all enemy troops to inflict minimum damage." msgstr "FÃ¥r alla fiendetrupper att utdela minimal skada." msgid "Mass Curse" msgstr "" "Mass-\n" "förbannelse" msgid "Damages all undead in the battle." msgstr "Skadar alla odöda i striden." msgid "Holy Word" msgstr "Heligt Ord" msgid "" "Damages all undead in the battle. This is an improved version of Holy Word." msgstr "" "Skadar alla odöda i striden. Det är en förbättrad version av Heligt Ord." msgid "Holy Shout" msgstr "Heligt Rop" msgid "Anti-Magic" msgstr "Antimagi" msgid "Prevents harmful magic against the selected creatures." msgstr "Förhindrar onda besvärjelser mot vald varelse." msgid "Dispel Magic" msgstr "Skingra Magi" msgid "Removes all magic spells from a single target." msgstr "Skingrar alla besvärjelser, goda som onda frÃ¥n en vald varelse." msgid "Mass Dispel" msgstr "" "Mass-\n" "skingring" msgid "Removes all magic spells from all creatures." msgstr "Tar bort alla magiska besvärjelser frÃ¥n alla varelser." msgid "Causes a magic arrow to strike the selected target." msgstr "Skjuter en magisk pil pÃ¥ valt mÃ¥l." msgid "Magic Arrow" msgstr "Magisk Pil" msgid "Berserker" msgstr "Bärsärk" msgid "Causes a creature to attack its nearest neighbor." msgstr "FÃ¥r en varelse att anfalla sin närmaste granne." msgid "Armageddon" msgstr "Harmageddon" msgid "" "Holy terror strikes the battlefield, causing severe damage to all creatures." msgstr "" "Gudomlig fasa drabbar slagfältet som orsakar stor skada till samtliga " "varelser." msgid "Elemental Storm" msgstr "" "Element-\n" "storm" msgid "Magical elements pour down on the battlefield, damaging all creatures." msgstr "Magiska element vräker ned pÃ¥ slagfältet, skadar alla varelser." msgid "" "A rain of rocks strikes an area of the battlefield, damaging all nearby " "creatures." msgstr "" "Ett stenregn slÃ¥r ned pÃ¥ en omrÃ¥de pÃ¥ slagfältet och skadar alla varelser i " "närheten." msgid "Meteor Shower" msgstr "Meteorregn" msgid "Paralyze" msgstr "Paralysera" msgid "The targeted creatures are paralyzed, unable to move or retaliate." msgstr "" "Valda varelser är paralyserade, de kan varken förflytta sig eller hämnas." msgid "Hypnotize" msgstr "Hypnotisera" msgid "" "Brings a single enemy unit under your control if its hits are less than " "%{count} times the caster's spell power." msgstr "" "Du tar kontroll över en enhet om dess hälsopoäng är mindre än %{count} " "gÃ¥nger hjältens Trollkonst." msgid "Cold Ray" msgstr "KöldstrÃ¥le" msgid "Drains body heat from a single enemy unit." msgstr "Tömmer kroppsvärme frÃ¥n en enskild fiendetrupp." msgid "Cold Ring" msgstr "Köldring" msgid "" "Drains body heat from all units surrounding the center point, but not " "including the center point." msgstr "" "Tömmer kroppsvärme frÃ¥n omgivande enheter runt mittpunkten, exklusive " "mittpunkten." msgid "Disrupting Ray" msgstr "StörstrÃ¥le" msgid "Reduces the defense rating of an enemy unit by three." msgstr "Sänker varelsens Försvar med tre enheter." msgid "Damages all living (non-undead) units in the battle." msgstr "Skadar alla levande (icke-odöda) varelser pÃ¥ slagfältet." msgid "Death Ripple" msgstr "Dödskrusning" msgid "Death Wave" msgstr "DödsvÃ¥g" msgid "" "Damages all living (non-undead) units in the battle. This spell is an " "improved version of Death Ripple." msgstr "" "Skadar alla levande (icke-odöda) varelser pÃ¥ slagfältet. Denna besvärjelse " "är en förbättrad variant av Dödskrusningen." msgid "Dragon Slayer" msgstr "Drakdräpare" msgid "Greatly increases a unit's attack skill vs. Dragons." msgstr "Höjer kraftigt en varelses Anfall mot Drakar." msgid "Blood Lust" msgstr "Blodtörst" msgid "Increases a unit's attack skill." msgstr "Höjer en varelses Anfall." msgid "Animate Dead" msgstr "Animera Död" msgid "Resurrects creatures from a damaged or dead undead unit permanently." msgstr "Ã…teruppväcker skadade eller stupade odöda varelser permanent." msgid "Mirror Image" msgstr "Spegelbild" msgid "" "Creates an illusionary unit that duplicates one of your existing units. This " "illusionary unit does the same damages as the original, but will vanish if " "it takes any damage." msgstr "" "Skapar en illusorisk enhet som duplicerar en av dina existerande enheter. " "Denna illusoriska enhet ger lika mycket skada som originalet, men försvinner " "om de tar skada." msgid "Shield" msgstr "Sköld" msgid "" "Halves damage received from ranged attacks for a single unit. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Halverar skada frÃ¥n projektilsattacker för en varelse. Projektiler frÃ¥n torn " "och ballistor räknas ej." msgid "Mass Shield" msgstr "Massköld" msgid "" "Halves damage received from ranged attacks for all of your units. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Halverar skadan till alla dina trupper frÃ¥n projektilattacker. Projektiler " "frÃ¥n torn och ballistor räknas ej." msgid "Summon Earth Elemental" msgstr "Framkalla Jordväsen" msgid "Summons Earth Elementals to fight for your army." msgstr "Kallar in Jordväsen för att strida för hjältens armé." msgid "Summon Air Elemental" msgstr "Framkalla Luftväsen" msgid "Summons Air Elementals to fight for your army." msgstr "Kallar in Luftväsen för att strida för hjältens armé." msgid "Summon Fire Elemental" msgstr "Framkalla Eldväsen" msgid "Summons Fire Elementals to fight for your army." msgstr "Kallar in Eldväsen för att strida för hjältens armé." msgid "Summon Water Elemental" msgstr "Framkalla Vattenväsen" msgid "Summons Water Elementals to fight for your army." msgstr "Kallar in Vattenväsen för att strida för hjältens armé." msgid "Damages castle walls." msgstr "Skadar slottsmurar." msgid "Earthquake" msgstr "Jordbävning" msgid "Causes all mines across the land to become visible." msgstr "Gör alla gruvor synliga över hela landet." msgid "View Mines" msgstr "" "Visa\n" "Gruvor" msgid "Causes all resources across the land to become visible." msgstr "Gör alla resurser synliga över hela landet." msgid "View Resources" msgstr "Visa Resurser" msgid "Causes all artifacts across the land to become visible." msgstr "Gör alla artefakter synliga över hela landet." msgid "View Artifacts" msgstr "Visa Artefakter" msgid "Causes all towns and castles across the land to become visible." msgstr "Gör alla städer och slott synliga över hela landet." msgid "View Towns" msgstr "Visa Städer" msgid "Causes all Heroes across the land to become visible." msgstr "Gör alla Hjältar synliga över hela landet." msgid "View Heroes" msgstr "" "Visa \n" "Hjältar" msgid "Causes the entire land to become visible." msgstr "Gör hela landet synligt." msgid "View All" msgstr "Visa Allt" msgid "Allows the caster to view detailed information on enemy Heroes." msgstr "LÃ¥ter kastaren fÃ¥ detaljerad information om fiendehjältar." msgid "Summon Boat" msgstr "" "Inkalla\n" "Skepp" msgid "" "Summons the nearest unoccupied, friendly boat to an adjacent shore location. " "A friendly boat is one which you just built or were the most recent player " "to occupy." msgstr "" "Framkallar närmsta lediga skepp till en närliggande strand. Bara skepp som " "du nyligen byggt eller var den senaste att kontrollera kan framkallas." msgid "Allows the caster to magically transport to a nearby location." msgstr "LÃ¥ter kastaren magiskt transportera sig till en närliggande plats." msgid "Dimension Door" msgstr "" "Dimensions-\n" "portal" msgid "Returns the caster to any town or castle currently owned." msgstr "För tillbaka Hjälten till nÃ¥gon stad eller slott som du kontrollerar." msgid "Town Gate" msgstr "Stadsport" msgid "" "Returns the hero to the town or castle of choice, provided it is controlled " "by you." msgstr "För tillbaka Hjälten till valfri stad eller slott som du kontrollerar." msgid "Visions" msgstr "Syner" msgid "" "Visions predicts the likely outcome of an encounter with a neutral army camp." msgstr "" "Syner förutspÃ¥r det sannolika utfallet av en strid mot ett neutralt " "fiendeläger." msgid "Haunt" msgstr "Hemsök" msgid "" "Haunts a mine you control with Ghosts. This mine stops producing resources. " "(If I can't keep it, nobody will!)" msgstr "" "Hemsöker en gruva som du kontrollerar med Spöken. Denna gruva slutar att " "producera resurser. (Om inte jag kan behÃ¥lla den, kommer ingen att kunna " "det!)" msgid "Set Earth Guardian" msgstr "Placera Jordväktare" msgid "Sets Earth Elementals to guard a mine against enemy armies." msgstr "Placerar Jordväsen att vakta en gruva mot fiendearméer." msgid "Set Air Guardian" msgstr "Placera Luftväktare" msgid "Sets Air Elementals to guard a mine against enemy armies." msgstr "Placerar Luftväsen att vakta en gruva mot fiendearméer." msgid "Set Fire Guardian" msgstr "Placera Eldväktare" msgid "Sets Fire Elementals to guard a mine against enemy armies." msgstr "Placerar Eldväsen att vakta en gruva mot fiendearméer." msgid "Set Water Guardian" msgstr "Placera Vattenväktare" msgid "Sets Water Elementals to guard a mine against enemy armies." msgstr "Placerar Vattenväsen att vakta en gruva mot fiendearméer." msgid "Random Spell" msgstr "Slumpad Besvärjelse" msgid "Randomly selected spell of any level." msgstr "Slumpmässigt vald besvärjelse frÃ¥n alla nivÃ¥er." msgid "Random 1st Level Spell" msgstr "Slumpad NivÃ¥ 1-Besvärjelse" msgid "Randomly selected 1st level spell." msgstr "Slumpmässigt vald nivÃ¥ 1-besvärjelse." msgid "Random 2nd Level Spell" msgstr "Slumpad NivÃ¥ 2-Besvärjelse" msgid "Randomly selected 2nd level spell." msgstr "Slumpmässigt vald nivÃ¥ 2-besvärjelse." msgid "Random 3rd Level Spell" msgstr "Slumpad NivÃ¥ 3-Besvärjelse" msgid "Randomly selected 3rd level spell." msgstr "Slumpmässigt vald nivÃ¥ 3-besvärjelse." msgid "Random 4th Level Spell" msgstr "Slumpad NivÃ¥ 4-Besvärjelse" msgid "Randomly selected 4th level spell." msgstr "Slumpmässigt vald nivÃ¥ 4-besvärjelse." msgid "Random 5th Level Spell" msgstr "Slumpad NivÃ¥ 5-Besvärjelse" msgid "Randomly selected 5th level spell." msgstr "Slumpmässigt vald nivÃ¥ 5-besvärjelse." msgid "Petrification" msgstr "Förstening" msgid "" "Turns the affected creature into stone. A petrified creature receives half " "damage from a direct attack." msgstr "" "Förvandlar varelsen till sten. En förstenad varelse mottar bara hälften " "skada frÃ¥n en direktattack." msgid "You have no Magic Book, so you cannot cast a spell." msgstr "" "Hjälten har ingen Magibok, sÃ¥ hjälten kan inte kasta nÃ¥gon besvärjelse." msgid "No spell to cast." msgstr "Inga besvärjelser att kasta." msgid "Your hero has %{point} spell points remaining." msgstr "Hjälten har %{point} besvärjelsepoäng kvar." msgid "View Adventure Spells" msgstr "Visa Kartbesvärjelser" msgid "View Combat Spells" msgstr "Visa Stridsbesvärjelser" msgid "View previous page" msgstr "Visa föregÃ¥ende blad" msgid "View next page" msgstr "Visa nästa blad" msgid "Close Spellbook" msgstr "Stäng Magibok" msgid "View %{spell}" msgstr "Visa %{spell}" msgid "This spell does %{damage} points of damage." msgstr "Denna besvärjelsen ger %{damage} skada." msgid "" "This spell summons\n" "%{count} %{monster}." msgstr "" "Denna besvärjelse framkallar\n" "%{count} %{monster}." msgid "This spell restores %{hp} HP." msgstr "Denna besvärjelse Ã¥terställer %{hp} hälsopoäng." msgid "This spell summons %{count} %{monster} to guard the mine." msgstr "Denna besvärjelse framkallar %{count} %{monster} för att vakta gruvan." msgid "The nearest town is %{town}." msgstr "Närmsta stad är %{town}." msgid "This town is occupied by your hero %{hero}." msgstr "Denna stad besöks av din hjälte %{hero}." msgid "" "This spell controls up to\n" "%{hp} HP." msgstr "" "Denna besvärjelse kontrollerar upp till\n" "%{hp} hälsopoäng." msgid "The ultimate artifact is really the %{name}." msgstr "Den Främsta Artefakten är den %{name}." msgid "The ultimate artifact may be found in the %{name} regions of the world." msgstr "Den främsta artefakten finnes i %{name}-regionen." msgid "north-west" msgstr "nordväst" msgid "north" msgstr "norr" msgid "north-east" msgstr "nordost" msgid "west" msgstr "väst" msgid "center" msgstr "mitten" msgid "east" msgstr "öst" msgid "south-west" msgstr "sydväst" msgid "south" msgstr "syd" msgid "south-east" msgstr "sydost" msgid "The truth is out there." msgstr "Sanningen finns där ute." msgid "The dark side is stronger." msgstr "Den mörka sidan är starkare." msgid "The end of the world is near." msgstr "Världens undergÃ¥ng är nära." msgid "The bones of Lord Slayer are buried in the foundation of the arena." msgstr "Benen frÃ¥n Mästerdräparen är begravda i arenans grund." msgid "A Black Dragon will take out a Titan any day of the week." msgstr "En Svart Drake spöar en Titan alla dagar i veckan." msgid "He told her: Yada yada yada... and then she said: Blah, blah, blah..." msgstr "Han sa till henne: Jag bah... och hon svarade: Du bah..." msgid "An unknown force is being resurrected..." msgstr "En okänd kraft Ã¥teruppstÃ¥r..." msgid "" "Check the newest version of the game at\n" "https://github.com/ihhub/\n" "fheroes2/releases" msgstr "" "Den senaste versionen av spelet finns pÃ¥:\n" "https://github.com/ihhub/\n" "fheroes2/releases" #~ msgid "Terrain" #~ msgstr "Terräng" #~ msgid "Castles" #~ msgstr "Slott" #~ msgid "Ocean object" #~ msgstr "HavsföremÃ¥l" #~ msgid "Ocean Objects" #~ msgstr "HavsföremÃ¥l" #~ msgid "Grass Objects" #~ msgstr "GräsföremÃ¥l" #~ msgid "Snow Objects" #~ msgstr "SnöföremÃ¥l" #~ msgid "Swamp Objects" #~ msgstr "TräskföremÃ¥l" #~ msgid "Lava Objects" #~ msgstr "LavaföremÃ¥l" #~ msgid "Desert Objects" #~ msgstr "ÖkenföremÃ¥l" #~ msgid "Dirt Objects" #~ msgstr "JordföremÃ¥l" #~ msgid "Beach Objects" #~ msgstr "StrandföremÃ¥l" #~ msgid "Used to place objects most appropriate for use on %{terrain}." #~ msgstr "Används för att placera de lämpligare objekten för %{terrain}." #~ msgid "" #~ "Used to place\n" #~ "a town or castle." #~ msgstr "Används för att placera en stad eller slott." #~ msgid "Used to place an artifact." #~ msgstr "Används för att placera en artefakt." #~ msgid "" #~ "Used to place\n" #~ "a resource or treasure." #~ msgstr "Används för att placera en resurs eller skatt." #~ msgid "%{race1} %{name1}" #~ msgstr "%{name1} (%{race1})" #~ msgid "vs" #~ msgstr "vs" #~ msgid "%{race2} %{name2}" #~ msgstr "%{name2} (%{race2})" fheroes2-1.0.12+dfsg/files/lang/tr.po000066400000000000000000005554751456075706000172760ustar00rootroot00000000000000# Turkish translation for fheroes2 # Copyright (C) 2022 fheroes2 team # This file is distributed under the same license as the fheroes2 package. # msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-03 03:27+0000\n" "PO-Revision-Date: 2022-10-01 20:20+0300\n" "Last-Translator: fheroes2 team \n" "Language-Team: Turkish \n" "Language: tr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Launchpad-Export-Date: 2010-05-27 09:12+0000\n" "X-Generator: Poedit 3.1.1\n" msgid "" "BATTLE\n" "ONLY" msgstr "" "SADECE\n" "SAVAÅž" msgid "" "NEW\n" "GAME" msgstr "" msgid "" "SAVE\n" "GAME" msgstr "" msgid "" "LOAD\n" "GAME" msgstr "" msgid "INFO" msgstr "" msgid "QUIT" msgstr "" msgid "CANCEL" msgstr "" msgid "OKAY" msgstr "" msgid "smallerButton|OKAY" msgstr "" msgid "ACCEPT" msgstr "" msgid "DECLINE" msgstr "" msgid "LEARN" msgstr "" msgid "TRADE" msgstr "" msgid "YES" msgstr "" msgid "NO" msgstr "" msgid "EXIT" msgstr "" msgid "smallerButton|EXIT" msgstr "" msgid "DISMISS" msgstr "" msgid "UPGRADE" msgstr "" msgid "RESTART" msgstr "" msgid "HEROES" msgstr "" msgid "" "TOWNS/\n" "CASTLES" msgstr "" msgid "S" msgstr "" msgid "M" msgstr "" msgid "L" msgstr "" msgid "X-L" msgstr "" msgid "ALL" msgstr "" msgid "SELECT" msgstr "" msgid "" "STANDARD\n" "GAME" msgstr "" msgid "" "CAMPAIGN\n" "GAME" msgstr "" msgid "" "MULTI-\n" "PLAYER\n" "GAME" msgstr "" msgid "CONFIG" msgstr "" msgid "" "ORIGINAL\n" "CAMPAIGN" msgstr "" msgid "" "EXPANSION\n" "CAMPAIGN" msgstr "" msgid "HOT SEAT" msgstr "" msgid "2 PLAYERS" msgstr "" msgid "3 PLAYERS" msgstr "" msgid "4 PLAYERS" msgstr "" msgid "5 PLAYERS" msgstr "" msgid "6 PLAYERS" msgstr "" msgid "GIFT" msgstr "HEDİYE" msgid "MAX" msgstr "" msgid "DIFFICULTY" msgstr "ZORLUK" msgid "VIEW INTRO" msgstr "" msgid "MIN" msgstr "MİN" msgid "RESET" msgstr "" msgid "START" msgstr "" msgid "CASTLE" msgstr "" msgid "TOWN" msgstr "" msgid "" "D\n" "I\n" "S\n" "M\n" "I\n" "S\n" "S" msgstr "" msgid "" "E\n" "X\n" "I\n" "T" msgstr "" msgid "" "C\n" "A\n" "M\n" "P\n" "A\n" "I\n" "G\n" "N" msgstr "" msgid "" "S\n" "T\n" "A\n" "N\n" "D\n" "A\n" "R\n" "D" msgstr "" msgid "Warrior" msgstr "Savaşçı" msgid "Builder" msgstr "İnÅŸaatçı" msgid "Explorer" msgstr "KaÅŸif" msgid "None" msgstr "Hiç" msgid "" "A few\n" "%{monster}" msgstr "" "Birkaç\n" "%{monster}" msgid "" "Several\n" "%{monster}" msgstr "" "Birçok\n" "%{monster}" msgid "" "A pack of\n" "%{monster}" msgstr "" "Bir yığın\n" "%{monster}" msgid "" "Lots of\n" "%{monster}" msgstr "" "Fazla\n" "%{monster}" msgid "" "A horde of\n" "%{monster}" msgstr "" "Bir sürü\n" "%{monster}" msgid "" "A throng of\n" "%{monster}" msgstr "" "Çok fazla\n" "%{monster}" msgid "" "A swarm of\n" "%{monster}" msgstr "" "Bir tabur\n" "%{monster}" msgid "" "Zounds...\n" "%{monster}" msgstr "" "Aşırı fazla\n" "%{monster}" msgid "" "A legion of\n" "%{monster}" msgstr "" "Bir lejyon\n" "%{monster}" msgid "army|Few" msgstr "Birkaç" msgid "army|Several" msgstr "Birçok" msgid "army|Pack" msgstr "Yığın" msgid "army|Lots" msgstr "Fazla" msgid "army|Horde" msgstr "Sürü" msgid "army|Throng" msgstr "Çok Fazla" msgid "army|Swarm" msgstr "Tabur" msgid "army|Zounds" msgstr "Aşırı" msgid "army|Legion" msgstr "Lejyon" msgid "All %{race} troops +1" msgstr "Bütün %{race} askerler +1" msgid "Multiple" msgstr "Çoklu" msgid "Troops of %{count} alignments -%{penalty}" msgstr "Birlikleri %{count} hizala -%{penalty}" #, fuzzy msgid "Some undead in army -1" msgstr "Gruptaki bazı ölümsüzler -1" msgid "View %{name}" msgstr "Görünüm %{name}" msgid "Move the %{name} " msgstr "%{name} 'i hareket ettir " msgid "Move or right click to redistribute %{name}" msgstr "%{name} Yeniden dağıtmak için oynatın veya saÄŸ tıklayın" msgid "Combine %{name} armies" msgstr "%{name} orduları birleÅŸtir" msgid "Exchange %{name2} with %{name}" msgstr "%{name2} 'ı %{name} ile deÄŸiÅŸtir" msgid "Select %{name}" msgstr "%{name}'ı seç" msgid "Cannot move last troop" msgstr "Son birlik hareket ettirilemiyor" msgid "Move the %{name}" msgstr "%{name} 'i hareket ettir" msgid "Set Count" msgstr "Sayıyı Ayarla" msgid "%{name} destroys half the enemy troops!" msgid_plural "%{name} destroy half the enemy troops!" msgstr[0] "%{name} düşman birliklerinin yarısını yok etti!" msgstr[1] "%{name} düşman birliklerinin yarısını yok etti!" msgid "%{name} has turned off the auto battle" msgstr "%{name} otomatik savaşı kapattı" msgid "%{name} has turned on the auto battle" msgstr "%{name} otomatik savaşı açtı" msgid "Spell failed!" msgstr "Büyü baÅŸarısız oldu!" msgid "" "The Sphere of Negation artifact is in effect for this battle, disabling all " "combat spells." msgstr "" "Bu savaÅŸta İnkar Alanı Eseri aktif, bütün savaÅŸ büyülerini devre dışı " "bırakıyor." msgid "You have already cast a spell this round." msgstr "Bu turda zaten bir büyü yaptın." #, fuzzy msgid "That spell will have no effect!" msgstr "Bu büyü hiç kimseyi etkilemeyecek!" msgid "You may only summon one type of elemental per combat." msgstr "SavaÅŸ başına yalnızca bir tür elemental çağırabilirsiniz." #, fuzzy msgid "" "There is no open space adjacent to your hero where you can summon an " "Elemental to." msgstr "Kahramanınızın yanında bir Elemental çağırmak için açık bir alan yok." msgid "" "The Moat reduces by -%{count} the defense skill of any unit and slows to " "half movement rate." msgstr "" "Hendek savunma becerisi herhangi bir birimin savunma becerisi ve hareket " "hızını -%{count} düşürüyor." msgid "Speed: %{speed}" msgstr "Hız: %{speed}" msgid "Speed" msgstr "Hız" msgid "Off" msgstr "Kapalı" msgid "On" msgstr "Açık" #, fuzzy msgid "Turn Order" msgstr "Ordu Düzeni" msgid "Auto Spell Casting" msgstr "Otomatik Büyü Yapma" msgid "Grid" msgstr "Kafes" msgid "Shadow Movement" msgstr "Gölge Hareketi" msgid "Shadow Cursor" msgstr "Gölge İmleci" msgid "Audio" msgstr "" msgid "Settings" msgstr "" msgid "Configure" msgstr "" msgid "Hot Keys" msgstr "" msgid "Damage Info" msgstr "" msgid "Set the speed of combat actions and animations." msgstr "SavaÅŸ eylemlerinin ve animasyonların hızını ayarlayın." #, fuzzy msgid "Toggle to display the turn order during the battle." msgstr "SavaÅŸ sırasında ordu düzenini görüntülemek için geçiÅŸ yapın." msgid "" "Toggle whether or not the computer will cast spells for you when auto combat " "is on. (Note: This does not affect spell casting for computer players in any " "way, nor does it affect quick combat.)" msgstr "" "Otomatik savaÅŸ açıkken bilgisayarın sizin için büyü yapıp yapmayacağını " "deÄŸiÅŸtirin.(Not: Bu, bilgisayar oyuncuları için büyü yapmayı hiçbir ÅŸekilde " "etkilemez ve hızlı savaşı etkilemez.)" msgid "" "Toggle the hex grid on or off. The hex grid always underlies movement, even " "if turned off. This switch only determines if the grid is visible." msgstr "" "Altıgen ızgarayı aç veya kapat.Altıgen ızgara, kapatılsa bile her zaman " "hareketin temelini oluÅŸturur. Bu anahtar yalnızca ızgaranın görünür olup " "olmadığını belirler." msgid "" "Toggle on or off shadows showing where your creatures can move and attack." msgstr "" "Yaratıklarınızın nereye hareket edip saldırabileceÄŸini gösteren gölgeleri " "açın veya kapatın." msgid "" "Toggle on or off a shadow showing the current hex location of the mouse " "cursor." msgstr "" "Fare imlecinin mevcut altıgen konumunu gösteren bir gölgeyi açın veya " "kapatın." msgid "Change the audio settings of the game." msgstr "" msgid "Check and configure all the hot keys present in the game." msgstr "" #, fuzzy msgid "Toggle to display damage information during the battle." msgstr "SavaÅŸ sırasında ordu düzenini görüntülemek için geçiÅŸ yapın." msgid "Exit this menu." msgstr "" msgid "Okay" msgstr "" msgid "The enemy has surrendered!" msgstr "Düşman teslim oldu!" msgid "Their cowardice costs them %{gold} gold." msgstr "" msgid "The enemy has fled!" msgstr "Düşman kaçtı!" msgid "A glorious victory!" msgstr "Åžanlı bir zafer!" msgid "For valor in combat, %{name} receives %{exp} experience." msgstr "SavaÅŸ'taki cesareti için %{name} %{exp} tecrübe kazandı." msgid "The cowardly %{name} flees from battle." msgstr "Korkak %{name} savaÅŸtan kaçtı." msgid "%{name} surrenders to the enemy, and departs in shame." msgstr "%{name} düşmana teslim oldu, ve utanç içinde ayrıldı." #, fuzzy msgid "Your forces suffer a bitter defeat, and %{name} abandons your cause." msgstr "Kuvvetleriniz acı bir yenilgiye uÄŸradı, ve %{name} davanızı terk etti." #, fuzzy msgid "Your forces suffer a bitter defeat." msgstr "Kuvvetleriniz acı bir yenilgiye uÄŸradı." msgid "Battlefield Casualties" msgstr "SavaÅŸ Alanı Kayıpları" msgid "Attacker" msgstr "Saldıran" msgid "Defender" msgstr "Savunan" msgid "Click to leave the battle results." msgstr "" #, fuzzy msgid "Click to restart the battle in manual mode." msgstr "Savaşı otomatik modda bitirmek istediÄŸinizden emin misiniz?" msgid "Restart" msgstr "" msgid "You have captured an enemy artifact!" msgstr "Bir düşman eseri ele geçirdiniz!" msgid "As you reach for the %{name}, it mysteriously disappears." msgstr "%{name}' ye ulaÅŸtığınızda,gizemli bir ÅŸekilde kayboldu." msgid "As your enemy reaches for the %{name}, it mysteriously disappears." msgstr "Düşman %{name}' ye ulaÅŸtığında,gizemli bir ÅŸekilde kayboldu." msgid "Necromancy!" msgstr "Ölü Büyücülüğü!" msgid "" "Practicing the dark arts of necromancy, you are able to raise %{count} of " "the enemy's dead to return under your service as %{monster}." msgstr "" "Karanlık büyücülük sanatlarını uygulayarak, %{canavar} olarak hizmetinize " "geri dönmek için düşmanın ölülerinin %{count} kadarını çağırabilirsiniz." msgid "%{name} the %{race}" msgstr "%{name} %{race}" msgid "Captain of %{name}" msgstr "%{name} Kaptanı" msgid "Attack" msgstr "Saldırı" msgid "Defense" msgstr "Savunma" msgid "Spell Power" msgstr "Büyü Gücü" msgid "Knowledge" msgstr "Bilgi" msgid "Morale" msgstr "Moral" msgid "Luck" msgstr "Åžans" msgid "Spell Points" msgstr "Büyü Puanları" msgid "Hero's Options" msgstr "Kahramanın Seçenekleri" msgid "Cast Spell" msgstr "Yapılan Büyü" msgid "Retreat" msgstr "Geri Çekil" msgid "Surrender" msgstr "Teslim Olmak" msgid "Cancel" msgstr "Vazgeç" msgid "Hero Screen" msgstr "Kahraman Ekranı" msgid "Captain's Options" msgstr "Kaptanın Seçenekleri" msgid "" "Cast a magical spell. You may only cast one spell per combat round. The " "round is reset when every creature has had a turn." msgstr "" "Bir büyü yap. Her savaÅŸ turunda yalnızca bir büyü yapabilirsiniz. Her " "yaratık bir sıra aldığında tur sıfırlanır." msgid "" "Retreat your hero, abandoning your creatures. Your hero will be available " "for you to recruit again, however, the hero will have only a novice hero's " "forces." msgstr "" "Kahramanını geri çek, yaratıklarını terk et. Kahramanınız tekrar askere " "almanız için hazır olacak, ancak kahramanın yalnızca acemi bir kahramanın " "güçleri olacak." #, fuzzy msgid "" "Surrendering costs gold. However if you pay the ransom, the hero and all of " "his or her surviving creatures will be available to recruit again. The cost " "of surrender is half of the total cost of the non-temporary troops remaining " "in the army." msgstr "" "Teslim olmak Altın'a mal olur. Ancak fidyeyi öderseniz, kahraman ve hayatta " "kalan tüm yaratıkları yeniden askere alınabilir." msgid "Open Hero Screen to view full information about the hero." msgstr "" "Kahraman hakkında tüm bilgileri görüntülemek için Kahraman Ekranını açın." msgid "Return to the battle." msgstr "SavaÅŸa geri dön." msgid "Not enough gold (%{gold})" msgstr "Yeterli altın yok (%{gold})" msgid "%{name} states:" msgstr "%{name} diyor:" msgid "Captain of %{name} states:" msgstr "%{name} Kaptanı diyor:" msgid "" "\"I will accept your surrender and grant you and your troops safe passage " "for the price of %{price} gold.\"" msgstr "" "\"Teslim olmanızı kabul ediyorum ve %{price} altın karşılığında size ve " "birliklerinize güvenli geçiÅŸ izni veriyorum.\"" msgid "View %{monster} info" msgstr "%{monster} bilgisini görüntüle" msgid "Fly %{monster} here" msgstr "%{monster} Buraya uçur" msgid "Move %{monster} here" msgstr "%{monster} Buraya hareket ettir" msgid "Shoot %{monster}" msgstr "%{monster} vur" msgid "(1 shot left)" msgid_plural "(%{count} shots left)" msgstr[0] "(1 vuruÅŸ kaldı)" msgstr[1] "(%{count} vuruÅŸlar kaldı)" msgid "Attack %{monster}" msgstr "%{monster} Saldır" msgid "Turn %{turn}" msgstr "%{turn} Dön" msgid "Teleport here" msgstr "Buraya ışınlan" msgid "Invalid teleport destination" msgstr "Geçersiz ışınlanma hedefi" msgid "Cast %{spell} on %{monster}" msgstr "%{monster}'a %{spell} uygula" msgid "Cast %{spell}" msgstr "%{spell} uygula" msgid "Select spell target" msgstr "Büyü hedefini seç" msgid "View Ballista info" msgstr "Mancınık bilgilerini görüntüle" msgid "Ballista" msgstr "Mancınık" msgid "Enable auto combat" msgstr "Otomatik savacı aç" msgid "Allows the computer to fight out the battle for you." msgstr "Bilgisayarın sizin için savaÅŸmasına izin verir." msgid "Auto Combat" msgstr "Otomatik savaÅŸ" msgid "Customize system options" msgstr "Sistem seçeneklerini özelleÅŸtirin" msgid "Allows you to customize the combat screen." msgstr "SavaÅŸ ekranını özelleÅŸtirmenizi saÄŸlar." msgid "System Options" msgstr "Sistem Seçenekleri" msgid "Skip this unit" msgstr "Bu birimi geç" msgid "Skip" msgstr "Geç" msgid "" "Skips the current creature. The current creature ends its turn and does not " "get to go again until the next round." msgstr "" "Mevcut yaratığı atlar. Mevcut yaratığın sırası sona erer ve bir sonraki tura " "kadar tekrar hareket edemez." msgid "View Captain's options" msgstr "Kaptanın seçeneklerini görüntüle" msgid "View Hero's options" msgstr "Kahramanın seçeneklerini görüntüle" msgid "View opposing Captain" msgstr "Rakip Kaptanı görüntüle" msgid "View opposing Hero" msgstr "Karşı Kahramanı görüntüle" msgid "Hide logs" msgstr "Günlükleri gizle" msgid "Show logs" msgstr "Günlükleri göster" msgid "Message Bar" msgstr "Mesaj ÇubuÄŸu" msgid "Shows the results of individual monster's actions." msgstr "Bireysel canavar eylemlerinin sonuçlarını gösterir." msgid "Are you sure you want to finish the battle in auto mode?" msgstr "Savaşı otomatik modda bitirmek istediÄŸinizden emin misiniz?" #, fuzzy msgid "Are you sure you want to enable auto combat?" msgstr "Geri çekilmek istediÄŸine emin misin?" msgid "%{name} skip their turn." msgstr "%{name} sırasını geçti." msgid "%{attacker} does %{damage} damage." msgid_plural "%{attacker} do %{damage} damage." msgstr[0] "%{attacker} %{damage} hasar verdi." msgstr[1] "%{attacker} %{damage} hasar verdi." msgid "1 creature perishes." msgid_plural "%{count} creatures perish." msgstr[0] "1 yaratik yok oldu." msgstr[1] "%{count} yaratık yok oldu." msgid "1 %{defender} perishes." msgid_plural "%{count} %{defender} perish." msgstr[0] "1 %{defender} yok oldu." msgstr[1] "%{count} %{defender} yok oldu." #, fuzzy msgid "1 soul is incorporated." msgid_plural "%{count} souls are incorporated." msgstr[0] "Ayna görüntüsü oluÅŸturuldu." msgstr[1] "Ayna görüntüsü oluÅŸturuldu." msgid "1 %{unit} is revived." msgid_plural "%{count} %{unit} are revived." msgstr[0] "" msgstr[1] "" msgid "Moved %{monster}: from [%{src}] to [%{dst}]." msgstr "%{monster}: [%{src}] buradan [%{dst}] buraya hareket ettirdi." msgid "The %{name} resist the spell!" msgstr "%{name} büyüye direndi!" msgid "%{name} casts %{spell} on the %{troop}." msgstr "%{name} %{troop} 'a %{spell} uyguladı." msgid "%{name} casts %{spell}." msgstr "%{name} %{spell} uyguladı." msgid "The %{spell} does %{damage} damage to one undead creature." msgstr "%{spell} büyüsü bir ölümsüz canavara %{damage} hasar verdi." msgid "The %{spell} does %{damage} damage to all undead creatures." msgstr "%{spell} büyüsü bütün ölümsüz canavarlara %{damage} hasar verdi." msgid "The %{spell} does %{damage} damage, %{count} creatures perish." msgstr "%{spell} büyüsü %{damage} hasar verdi, %{count} canavarlar yok oldu." msgid "The %{spell} does %{damage} damage." msgstr "%{spell} büyüsü %{damage} hasar verdi." msgid "The %{spell} does %{damage} damage to one living creature." msgstr "%{spell} büyüsü bir yaÅŸayan canavara %{damage} hasar verdi." msgid "The %{spell} does %{damage} damage to all living creatures." msgstr "%{spell} büyüsü bütün yaÅŸayan canavarlara %{damage} hasar verdi." msgid "The %{attacker}' attack blinds the %{target}!" msgstr "%{attacker}' saldırısı %{target}'ı kör etti!" msgid "The %{attacker}' gaze turns the %{target} to stone!" msgstr "%{attacker}'ın bakışları %{target}'ı taÅŸa çevirdi!" msgid "The %{attacker}' curse falls upon the %{target}!" msgstr "%{attacker}'ın laneti %{target}'in üzerine geldi!" msgid "The %{target} are paralyzed by the %{attacker}!" msgstr "%{attacker}' saldırısı %{target}'ı sakatladı!" msgid "The %{attacker} dispel all good spells on your %{target}!" msgstr "%{attacker} %{target} üzerindeki tüm iyi büyüleri yok etti!" msgid "The %{attacker} cast %{spell} on %{target}!" msgstr "%{attacker} %{target}'a %{spell} uyguladı!" msgid "Bad luck descends on the %{attacker}." msgstr "Kötü ÅŸans %{attacker} üzerine çöktü." msgid "Good luck shines on the %{attacker}." msgstr "İyi ÅŸans %{attacker} üzerine parladı." msgid "High morale enables the %{monster} to attack again." msgstr "Yüksek moral %{monster}'ın tekrar saldırmasını saÄŸlar." msgid "Low morale causes the %{monster} to freeze in panic." msgstr "Düşük moral %{monster}'ın panikten donmasına yol açtı." msgid "%{tower} does %{damage} damage." msgstr "%{tower} %{damage} hasar verir." msgid "The mirror image is created." msgstr "Ayna görüntüsü oluÅŸturuldu." msgid "The mirror image is destroyed!" msgstr "Ayna görüntüsü yok edildi!" msgid "Are you sure you want to interrupt the auto battle?" msgstr "Otomatik savaşı kesmek istediÄŸine emin misin?" msgid "Error" msgstr "Hata" msgid "No spells to cast." msgstr "Yapılacak büyü yok." msgid "Are you sure you want to retreat?" msgstr "Geri çekilmek istediÄŸine emin misin?" msgid "Retreat disabled" msgstr "Geri çekilme engellendi" msgid "Surrender disabled" msgstr "Teslim olma engellendi" msgid "Damage: %{max}" msgstr "Hasar: %{max}" msgid "Damage: %{min} - %{max}" msgstr "Hasar: %{min} - %{max}" msgid "Perish: %{max}" msgstr "Yok oldu: %{max}" msgid "Perish: %{min} - %{max}" msgstr "Yok oldu: %{min} - %{max}" msgid "" "Through eagle-eyed observation, %{name} is able to learn the magic spell " "%{spell}." msgstr "" "Kartal gözlü gözlem sayesinde, %{name} %{spell} sihirli büyüsünü öğrendi." msgid "Human" msgstr "İnsan" msgid "AI" msgstr "Bilgisayar" msgid "Start" msgstr "" #, fuzzy msgid "Start the battle." msgstr "SavaÅŸa geri dön." msgid "Exit" msgstr "" msgid "Reset" msgstr "" msgid "Reset to default settings." msgstr "" msgid "Please select another hero." msgstr "Lütfen baÅŸka bir kahraman seç." msgid "Set Attack Skill" msgstr "Saldırı Becerisini Ayarla" msgid "Set Defense Skill" msgstr "Savunma Becerisini Ayarla" msgid "Set Power Skill" msgstr "Güç Becerisini Ayarla" msgid "Set Knowledge Skill" msgstr "Bilgi Becerisini Ayarla" #, fuzzy msgid "%{race1} %{name1} vs %{race2} %{name2}" msgstr "%{race1} %{name1}" msgid "Monsters" msgstr "Canavarlar" msgid "N/A" msgstr "N/A" msgid "Left Turret" msgstr "Sol Taret" msgid "Right Turret" msgstr "SaÄŸ Taret" msgid "The %{name} fires with the strength of %{count} Archers" msgstr "%{name} %{count} Okçu gücüyle ateÅŸ ediyor" msgid "each with a +%{attack} bonus to their attack skill." msgstr "her biri saldırı becerilerine +%{attack} bonusu ekler." #, fuzzy msgid "The %{name} is destroyed." msgstr "Ayna görüntüsü yok edildi!" msgid "%{count} %{name} rises from the dead!" msgid_plural "%{count} %{name} rise from the dead!" msgstr[0] "" msgstr[1] "" msgid "Dwarven Alliance" msgstr "Cüce İttifakı" msgid "Sorceress Guild" msgstr "Büyücü Loncası" msgid "Necromancer Guild" msgstr "Ölüm Büyücüleri Loncası" msgid "Ogre Alliance" msgstr "Dev İttifakı" msgid "Dwarfbane" msgstr "" msgid "Dragon Alliance" msgstr "Ejderha İttifakı" msgid "Elven Alliance" msgstr "Elf İttifakı" msgid "Kraeger defeated" msgstr "" msgid "Wayward Son" msgstr "" msgid "Uncle Ivan" msgstr "Ivan Amca" msgid "Annexation" msgstr "" msgid "Force of Arms" msgstr "" msgid "Save the Dwarves" msgstr "" msgid "Carator Mines" msgstr "" msgid "Turning Point" msgstr "Dönüm Noktası" msgid "scenarioName|Defender" msgstr "" msgid "Corlagon's Defense" msgstr "" msgid "The Crown" msgstr "Taç" msgid "The Gauntlet" msgstr "Eldiven" msgid "Betrayal" msgstr "İhanet" msgid "Final Justice" msgstr "" msgid "" "Roland needs you to defeat the lords near his castle to begin his war of " "rebellion against his brother. They are not allied with each other, so they " "will spend most of their time fighting with one another. Victory is yours " "when you have defeated all of their castles and heroes." msgstr "" msgid "" "The local lords refuse to swear allegiance to Roland, and must be subdued. " "They are wealthy and powerful, so be prepared for a tough fight. Capture all " "enemy castles to win." msgstr "" msgid "" "Your task is to defend the Dwarves against Archibald's forces. Capture all " "of the enemy towns and castles to win, and be sure not to lose all of the " "dwarf towns at once, or the enemy will have won." msgstr "" msgid "" "You will face four allied enemies in a straightforward fight for resource " "and treasure. Capture all of the enemy castles for victory." msgstr "" msgid "" "Your enemies are allied against you and start close by, so be ready to come " "out fighting. You will need to own all four castles in this small valley to " "win." msgstr "" msgid "" "The Sorceress' guild of Noraston has requested Roland's aid against an " "attack from Archibald's allies. Capture all of the enemy castles to win, and " "don't lose Noraston, or you'll lose the scenario. (Hint: There is an enemy " "castle on an island in the ocean.)" msgstr "" msgid "" "Gather as large an army as possible and capture the enemy castle within 8 " "weeks. You are opposed by only one enemy, but must travel a long way to get " "to the enemy castle. Any troops you have in your army at the end of this " "scenario will be with you in the final battle." msgstr "" msgid "" "Find the Crown before Archibald's heroes find it. Roland will need the Crown " "for the final battle against Archibald." msgstr "" msgid "" "Three allied enemies stand before you and victory, including Lord Corlagon. " "Roland is in a castle to the northwest, and you will lose if he falls to the " "enemy. Remember that capturing Lord Corlagon will ensure that he will not " "fight against you in the final scenario." msgstr "" msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Archibald to end the war!" msgstr "" msgid "" "Switching sides leaves you with three castles against the enemy's one. This " "battle will be the easiest one you will face for the rest of the war..." "traitor." msgstr "" msgid "Barbarian Wars" msgstr "" msgid "First Blood" msgstr "" msgid "Necromancers" msgstr "" msgid "Slay the Dwarves" msgstr "" msgid "Country Lords" msgstr "" msgid "Dragon Master" msgstr "" msgid "Rebellion" msgstr "" msgid "Apocalypse" msgstr "" msgid "Greater Glory" msgstr "" msgid "" "King Archibald requires you to defeat the three enemies in this region. They " "are not allied with one another, so they will spend most of their energy " "fighting amongst themselves. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" msgid "" "You must unify the barbarian tribes of the north by conquering them. As in " "the previous mission, the enemy is not allied against you, but they have " "more resources at their disposal. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" msgid "" "Do-gooder wizards have taken the Necromancers' castle. You must retake it to " "achieve victory. Remember that while you start with a powerful army, you " "have no castle and must take one within 7 days, or lose this battle. (Hint: " "The nearest castle is to the southeast.)" msgstr "" msgid "" "The dwarves need conquering before they can interfere in King Archibald's " "plans. Roland's forces have more than one hero and many towns to start with, " "so be ready for attack from multiple directions. You must capture all of the " "enemy towns and castles to claim victory." msgstr "" msgid "" "You must put down a peasant revolt led by Roland's forces. All are allied " "against you, but you have Lord Corlagon, an experienced hero, to help you. " "Capture all enemy castles to win." msgstr "" msgid "" "There are two enemies allied against you in this mission. Both are well " "armed and seek to evict you from their island. Avoid them and capture Dragon " "City to win." msgstr "" msgid "" "Your orders are to conquer the country lords that have sworn to serve " "Roland. All of the enemy castles are unified against you. Since you start " "without a castle, you must hurry to capture one before the end of the week. " "Capture all enemy castles for victory." msgstr "" msgid "" "Find the Crown before Roland's heroes find it. Archibald will need the Crown " "for the final battle against Roland." msgstr "" msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Roland to win the war, and be sure " "not to lose Archibald in the fight!" msgstr "" msgid "Arrow's Flight" msgstr "" msgid "Island of Chaos" msgstr "" msgid "The Abyss" msgstr "" msgid "Uprising" msgstr "" msgid "Aurora Borealis" msgstr "" msgid "Betrayal's End" msgstr "" msgid "Corruption's Heart" msgstr "" msgid "The Giant's Pass" msgstr "" msgid "" "Subdue the unruly local lords in order to provide the Empire with facilities " "to operate in this region." msgstr "" msgid "" "Eliminate all opposition in this area. Then the first piece of the artifact " "will be yours." msgstr "" msgid "" "The sorceresses to the northeast are rebelling! For the good of the empire " "you must quash their feeble uprising on your way to the mountains." msgstr "" msgid "" "Having prepared for your arrival, Kraeger has arranged for a force of " "necromancers to thwart your quest. You must capture the castle of Scabsdale " "before the first day of the third week, or the Necromancers will be too " "strong for you." msgstr "" msgid "" "The barbarian despot in this area is, as yet, ignorant of your presence. " "Quickly, build up your forces before you are discovered and attacked! Secure " "the region by subduing all enemy forces." msgstr "" msgid "" "The Empire is weak in this region. You will be unable to completely subdue " "all forces in this area, so take what you can before reprisal strikes. " "Remember, your true goal is to claim the Helmet of Anduran." msgstr "" msgid "For the good of the Empire, eliminate Kraeger." msgstr "" msgid "" "At last, you have the opportunity and the facilities to rid the Empire of " "the necromancer's evil. Eradicate them completely, and you will be sung as a " "hero for all time." msgstr "" msgid "Border Towns" msgstr "" msgid "Conquer and Unify" msgstr "" msgid "Crazy Uncle Ivan" msgstr "" msgid "The Wayward Son" msgstr "" msgid "Ivory Gates" msgstr "" msgid "The Elven Lands" msgstr "Elf Diyarları" msgid "The Epic Battle" msgstr "Epik SavaÅŸ" msgid "The Southern War" msgstr "" msgid "" "Conquer and unite all the enemy tribes. Don't lose the hero Jarkonas, the " "forefather of all descendants." msgstr "" msgid "" "Your rival, the Kingdom of Harondale, is attacking weak towns on your " "border! Recover from their first strike and crush them completely!" msgstr "" msgid "" "Find your wayward son Joseph who is rumored to be living in the desolate " "lands. Do it before the first day of the third month or it will be of no " "help to your family." msgstr "" msgid "" "Rescue your crazy uncle Ivan. Find him before the first day of the fourth " "month or it will be no help to your kingdom." msgstr "" msgid "" "Destroy the barbarians who are attacking the southern border of your " "kingdom! Recover your fallen towns, and then invade the jungle kingdom. " "Leave no enemy standing." msgstr "" msgid "Retake the castle of Ivory Gates, which has fallen due to treachery." msgstr "" msgid "" "Gain the favor of the elves. They will not allow trees to be chopped down, " "so they will send you wood every 2 weeks. You must complete your mission " "before the first day of the seventh month, or the kingdom will surely fall." msgstr "" msgid "" "This is the final battle against your rival kingdom of Harondale. Eliminate " "everyone, and don't lose the hero Jarkonas VI." msgstr "" msgid "Fount of Wizardry" msgstr "" msgid "Power's End" msgstr "" msgid "The Eternal Scrolls" msgstr "" msgid "The Shrouded Isles" msgstr "" msgid "" "Your mission is to vanquish the warring mages in the magical Shrouded Isles. " "The completion of this task will give you a fighting chance against your " "rivals." msgstr "" msgid "" "The location of the great library has been discovered! You must make your " "way to it, and reclaim the city of Chronos in which it lies." msgstr "" msgid "" "Find the Orb of negation, which is said to be buried in this land. There are " "clues inscribed on stone obelisks which will help lead you to your prize. " "Find the Orb before the first day of the sixth month, or your rivals will " "surely have gotten to the fount before you." msgstr "" msgid "" "You must take control of the castle of Magic, where the fount of wizardry " "lies. Do this and your victory will be supreme." msgstr "" msgid "Blood is Thicker" msgstr "" msgid "King and Country" msgstr "" msgid "Pirate Isles" msgstr "" msgid "Stranded" msgstr "" msgid "" "Capture the town on the island off the southeast shore in order to construct " "a boat and travel back towards the mainland. Do not lose the hero Gallavant." msgstr "" msgid "" "Find and defeat Martine, the pirate leader, who resides in Pirates Cove. Do " "not lose Gallavant or your quest will be over." msgstr "" msgid "" "Eliminate all the other forces who oppose the rule of Lord Alberon. " "Gallavant must not die." msgstr "" msgid "" "Overthrow the entrenched monarchy of Lord Alberon, and claim all the land in " "your name. Gallavant must not die." msgstr "" msgid " bane" msgstr "" msgid " alliance" msgstr "" msgid "Carry-over forces" msgstr "" msgid " bonus" msgstr "" msgid " defeated" msgstr " yenildi" msgid " will always run away from your army." msgstr "" msgid " will be willing to join your army." msgstr "" msgid "\"%{artifact}\" artifact will be carried over the scenario." msgstr "" msgid "The army will be carried over the scenario." msgstr "" msgid "The kingdom will have +%{count} %{resource} each day." msgstr "" msgid "\"%{spell}\" spell will be carried over the scenario." msgstr "" msgid "%{hero} can be hired in the scenario." msgstr "" msgid "" "%{hero} has been defeated and will not appear in the subsequent scenarios." msgstr "" msgid "The dwarves recognize their allies and gladly join your forces." msgstr "" msgid "The ogres recognize you as the Dwarfbane and lumber over to join you." msgstr "" msgid "" "The dragons, snarling and growling, agree to join forces with you, their " "'Ally'." msgstr "" msgid "" "As you approach the group of elves, their leader calls them all to " "attention. He shouts to them, \"Who of you is brave enough to join this " "fearless ally of ours?\" The group explodes with cheers as they run to join " "your ranks." msgstr "" msgid "" "The dwarves hail you, \"Any friend of Roland is a friend of ours. You may " "pass.\"" msgstr "" msgid "" "The ogres give you a grunt of recognition, \"Archibald's allies may pass.\"" msgstr "" msgid "" "The dragons see you and call out. \"Our alliance with Archibald compels us " "to join you. Unfortunately you have no room. A pity!\" They quickly scatter." msgstr "" msgid "" "The elves stand at attention as you approach. Their leader calls to you and " "says, \"Let us not impede your progress, ally! Move on, and may victory be " "yours.\"" msgstr "" msgid "\"The Dwarfbane!!!!, run for your lives.\"" msgstr "" msgid "campaignBonus|Animate Dead" msgstr "" msgid "campaignBonus|Chain Lightning" msgstr "" msgid "campaignBonus|Fireblast" msgstr "" msgid "campaignBonus|Mass Curse" msgstr "" msgid "campaignBonus|Mass Haste" msgstr "" msgid "campaignBonus|Mirror Image" msgstr "" msgid "campaignBonus|Resurrect" msgstr "" msgid "campaignBonus|Steelskin" msgstr "" msgid "campaignBonus|Summon Earth" msgstr "" msgid "campaignBonus|View Heroes" msgstr "" msgid "campaignBonus|Ballista" msgstr "" msgid "campaignBonus|Black Pearl" msgstr "" msgid "campaignBonus|Caster's Bracelet" msgstr "" msgid "campaignBonus|Defender Helm" msgstr "" msgid "campaignBonus|Breastplate" msgstr "" msgid "campaignBonus|Dragon Sword" msgstr "" msgid "campaignBonus|Fizbin Medal" msgstr "" msgid "campaignBonus|Foremost Scroll" msgstr "" msgid "campaignBonus|Gauntlets" msgstr "" msgid "campaignBonus|Hideous Mask" msgstr "" msgid "campaignBonus|Mage's Ring" msgstr "" msgid "campaignBonus|Major Scroll" msgstr "" msgid "campaignBonus|Medal of Honor" msgstr "" msgid "campaignBonus|Medal of Valor" msgstr "" msgid "campaignBonus|Minor Scroll" msgstr "" msgid "campaignBonus|Nomad Boots" msgstr "" msgid "campaignBonus|Power Axe" msgstr "" msgid "campaignBonus|Spiked Shield" msgstr "" msgid "campaignBonus|Stealth Shield" msgstr "" msgid "campaignBonus|Tax Lien" msgstr "" msgid "campaignBonus|Thunder Mace" msgstr "" msgid "campaignBonus|Traveler's Boots" msgstr "" msgid "campaignBonus|White Pearl" msgstr "" msgid "campaignBonus|Basic Archery" msgstr "" msgid "campaignBonus|Advanced Archery" msgstr "" msgid "campaignBonus|Expert Archery" msgstr "" msgid "campaignBonus|Basic Ballistics" msgstr "" msgid "campaignBonus|Advanced Ballistics" msgstr "" msgid "campaignBonus|Expert Ballistics" msgstr "" msgid "campaignBonus|Basic Diplomacy" msgstr "" msgid "campaignBonus|Advanced Diplomacy" msgstr "" msgid "campaignBonus|Expert Diplomacy" msgstr "" msgid "campaignBonus|Basic Eagle Eye" msgstr "" msgid "campaignBonus|Advanced Eagle Eye" msgstr "" msgid "campaignBonus|Expert Eagle Eye" msgstr "" msgid "campaignBonus|Basic Estates" msgstr "" msgid "campaignBonus|Advanced Estates" msgstr "" msgid "campaignBonus|Expert Estates" msgstr "" msgid "campaignBonus|Basic Leadership" msgstr "" msgid "campaignBonus|Advanced Leadership" msgstr "" msgid "campaignBonus|Expert Leadership" msgstr "" msgid "campaignBonus|Basic Logistics" msgstr "" msgid "campaignBonus|Advanced Logistics" msgstr "" msgid "campaignBonus|Expert Logistics" msgstr "" msgid "campaignBonus|Basic Luck" msgstr "" msgid "campaignBonus|Advanced Luck" msgstr "" msgid "campaignBonus|Expert Luck" msgstr "" msgid "campaignBonus|Basic Mysticism" msgstr "" msgid "campaignBonus|Advanced Mysticism" msgstr "" msgid "campaignBonus|Expert Mysticism" msgstr "" msgid "campaignBonus|Basic Navigation" msgstr "" msgid "campaignBonus|Advanced Navigation" msgstr "" msgid "campaignBonus|Expert Navigation" msgstr "" msgid "campaignBonus|Basic Necromancy" msgstr "" msgid "campaignBonus|Advanced Necromancy" msgstr "" msgid "campaignBonus|Expert Necromancy" msgstr "" msgid "campaignBonus|Basic Pathfinding" msgstr "" msgid "campaignBonus|Advanced Pathfinding" msgstr "" msgid "campaignBonus|Expert Pathfinding" msgstr "" msgid "campaignBonus|Basic Scouting" msgstr "" msgid "campaignBonus|Advanced Scouting" msgstr "" msgid "campaignBonus|Expert Scouting" msgstr "" msgid "campaignBonus|Basic Wisdom" msgstr "" msgid "campaignBonus|Advanced Wisdom" msgstr "" msgid "campaignBonus|Expert Wisdom" msgstr "" msgid "" "The main hero will have \"%{artifact}\" artifact at the start of the " "scenario." msgstr "" msgid "" "The kingdom will receive %{amount} additional %{resource} at the start of " "the scenario." msgstr "" msgid "" "The kingdom will have %{amount} less %{resource} at the start of the " "scenario." msgstr "" msgid "" "The main hero will have %{count} %{monster} at the start of the scenario." msgstr "" msgid "" "The main hero will have \"%{spell}\" spell at the start of the scenario." msgstr "" msgid "The starting race of the scenario will be %{race}." msgstr "" msgid "" "The main hero will have additional %{count} %{skill} at the start of the " "scenario." msgstr "" msgid "The main hero will have %{skill} at the start of the scenario." msgstr "" msgid "Roland" msgstr "" msgid "Archibald" msgstr "" msgid "Price of Loyalty" msgstr "" msgid "Voyage Home" msgstr "" msgid "Wizard's Isle" msgstr "" msgid "Descendants" msgstr "" msgid "The %{building} produces %{monster}." msgstr "" msgid "Requires:" msgstr "" msgid "Exit this menu without doing anything." msgstr "" msgid "Cannot build. You have already built here today." msgstr "" msgid "For this action it is necessary to build a castle first." msgstr "" msgid "Cannot build %{name} because the castle is too far away from an ocean." msgstr "" msgid "disable build." msgstr "" msgid "Cannot afford %{name}." msgstr "" msgid "%{name} is already built." msgstr "" msgid "Cannot build %{name}." msgstr "" msgid "Build %{name}." msgstr "" msgid "Blackridge" msgstr "" msgid "Hillstone" msgstr "" msgid "Pinehurst" msgstr "" msgid "Whiteshield" msgstr "" msgid "Woodhaven" msgstr "" msgid "Blackwind" msgstr "" msgid "Bloodreign" msgstr "" msgid "Dragontooth" msgstr "" msgid "Greywind" msgstr "" msgid "Portsmith" msgstr "" msgid "Atlantium" msgstr "" msgid "Middle Gate" msgstr "" msgid "Sansobar" msgstr "" msgid "Tundara" msgstr "" msgid "Vulcania" msgstr "" msgid "Baywatch" msgstr "" msgid "Fountainhead" msgstr "" msgid "Vertigo" msgstr "" msgid "Wildabar" msgstr "" msgid "Winterkill" msgstr "" msgid "Brindamoor" msgstr "" msgid "Lakeside" msgstr "" msgid "Nightshadow" msgstr "" msgid "Olympus" msgstr "" msgid "Sandcaster" msgstr "" msgid "Alamar" msgstr "" msgid "Burlock" msgstr "" msgid "Dragadune" msgstr "" msgid "Kalindra" msgstr "" msgid "Xabran" msgstr "" msgid "Algary" msgstr "" msgid "Basenji" msgstr "" msgid "Blackfang" msgstr "" msgid "New Dawn" msgstr "" msgid "Sorpigal" msgstr "" msgid "Avone" msgstr "" msgid "Big Oak" msgstr "" msgid "Chandler" msgstr "" msgid "Erliquin" msgstr "" msgid "Hampshire" msgstr "" msgid "Antioch" msgstr "" msgid "Avalon" msgstr "" msgid "Roc Haven" msgstr "" msgid "South Mill" msgstr "" msgid "Weed Patch" msgstr "" msgid "Brownston" msgstr "" msgid "Hilltop" msgstr "" msgid "Weddington" msgstr "" msgid "Westfork" msgstr "" msgid "Whittingham" msgstr "" msgid "Cathcart" msgstr "" msgid "Elk's Head" msgstr "" msgid "Roscomon" msgstr "" msgid "Sherman" msgstr "" msgid "Yorksford" msgstr "" msgid "Blackburn" msgstr "" msgid "Blacksford" msgstr "" msgid "Burton" msgstr "" msgid "Pig's Eye" msgstr "" msgid "Viper's Nest" msgstr "" msgid "Fenton" msgstr "" msgid "Lankershire" msgstr "" msgid "Lombard" msgstr "" msgid "Timberhill" msgstr "" msgid "Troy" msgstr "" msgid "Forder Oaks" msgstr "" msgid "Meramec" msgstr "" msgid "Quick Silver" msgstr "" msgid "Westmoor" msgstr "" msgid "Willow" msgstr "" msgid "Corackston" msgstr "" msgid "Sheltemburg" msgstr "" msgid "Cannot recruit - you already have a Hero in this town." msgstr "" msgid "Cannot recruit - you have too many Heroes." msgstr "" msgid "Cannot afford a Hero" msgstr "" msgid "There is no room in the garrison for this army." msgstr "" msgid "Fortifications" msgstr "" msgid "Farm" msgstr "" msgid "Thatched Hut" msgstr "" msgid "Archery Range" msgstr "" msgid "Upg. Archery Range" msgstr "" msgid "Blacksmith" msgstr "" msgid "Upg. Blacksmith" msgstr "" msgid "Armory" msgstr "" msgid "Upg. Armory" msgstr "" msgid "Jousting Arena" msgstr "" msgid "Upg. Jousting Arena" msgstr "" msgid "Cathedral" msgstr "" msgid "Upg. Cathedral" msgstr "" msgid "Coliseum" msgstr "" msgid "Garbage Heap" msgstr "" msgid "Hut" msgstr "" msgid "Stick Hut" msgstr "" msgid "Upg. Stick Hut" msgstr "" msgid "Den" msgstr "" msgid "Adobe" msgstr "" msgid "Upg. Adobe" msgstr "" msgid "Bridge" msgstr "Köprü" msgid "Upg. Bridge" msgstr "" msgid "Pyramid" msgstr "" msgid "Rainbow" msgstr "" msgid "Crystal Garden" msgstr "" msgid "Treehouse" msgstr "" msgid "Cottage" msgstr "" msgid "Upg. Cottage" msgstr "" msgid "Stonehenge" msgstr "" msgid "Upg. Stonehenge" msgstr "" msgid "Fenced Meadow" msgstr "" msgid "sorceress|Red Tower" msgstr "" msgid "Dungeon" msgstr "" msgid "Waterfall" msgstr "Åželale" msgid "Cave" msgstr "MaÄŸara" msgid "Crypt" msgstr "" msgid "Nest" msgstr "Yuva" msgid "Maze" msgstr "Labirent" msgid "Upg. Maze" msgstr "" msgid "Swamp" msgstr "Bataklık" msgid "Green Tower" msgstr "YeÅŸil Kule" msgid "warlock|Red Tower" msgstr "" msgid "Black Tower" msgstr "" msgid "Library" msgstr "Kütüphane" msgid "Orchard" msgstr "" msgid "Habitat" msgstr "" msgid "Pen" msgstr "" msgid "Foundry" msgstr "" msgid "Upg. Foundry" msgstr "" msgid "Cliff Nest" msgstr "" msgid "Ivory Tower" msgstr "" msgid "Upg. Ivory Tower" msgstr "" msgid "Cloud Castle" msgstr "" msgid "Upg. Cloud Castle" msgstr "" msgid "Storm" msgstr "" msgid "Skull Pile" msgstr "" msgid "Excavation" msgstr "" msgid "Graveyard" msgstr "" msgid "Upg. Graveyard" msgstr "" msgid "Upg. Pyramid" msgstr "" msgid "Mansion" msgstr "" msgid "Upg. Mansion" msgstr "" msgid "Mausoleum" msgstr "" msgid "Upg. Mausoleum" msgstr "" msgid "Laboratory" msgstr "" msgid "Shrine" msgstr "" msgid "" "The Fortifications increase the toughness of the walls, increasing the " "number of turns it takes to knock them down." msgstr "" msgid "The Farm increases production of Peasants by %{count} per week." msgstr "" msgid "" "The Coliseum provides inspiring spectacles to defending troops, raising " "their morale by two during combat." msgstr "" msgid "The Garbage Heap increases production of Goblins by %{count} per week." msgstr "" msgid "The Rainbow increases the luck of the defending units by two." msgstr "" msgid "" "The Crystal Garden increases production of Sprites by %{count} per week." msgstr "" msgid "The Dungeon increases the income of the town by %{count} gold per day." msgstr "" msgid "The Waterfall increases production of Centaurs by %{count} per week." msgstr "" msgid "" "The Library increases the number of spells in the Guild by one for each " "level of the guild." msgstr "" msgid "The Orchard increases production of Halflings by %{count} per week." msgstr "" msgid "The Storm adds +2 to the power of spells of a defending spell caster." msgstr "" msgid "The Skull Pile increases production of Skeletons by %{count} per week." msgstr "" msgid "Thieves' Guild" msgstr "" msgid "Tavern" msgstr "" msgid "Shipyard" msgstr "" msgid "Well" msgstr "" msgid "Statue" msgstr "" msgid "Marketplace" msgstr "" msgid "Moat" msgstr "" msgid "Castle" msgstr "" msgid "Tent" msgstr "" msgid "Captain's Quarters" msgstr "" msgid "Mage Guild, Level 1" msgstr "" msgid "Mage Guild, Level 2" msgstr "" msgid "Mage Guild, Level 3" msgstr "" msgid "Mage Guild, Level 4" msgstr "" msgid "Mage Guild, Level 5" msgstr "" msgid "" "The Shrine increases the necromancy skill of all your necromancers by 10 " "percent." msgstr "" msgid "" "The Thieves' Guild provides information on enemy players. Thieves' Guilds " "can also provide scouting information on enemy towns. Additional Guilds " "provide more information." msgstr "" msgid "The Tavern increases morale for troops defending the castle." msgstr "" msgid "The Shipyard allows ships to be built." msgstr "" msgid "" "The Well increases the growth rate of all dwellings by %{count} creatures " "per week." msgstr "" msgid "The Statue increases the town's income by %{count} gold per day." msgstr "" msgid "The Left Turret provides extra firepower during castle combat." msgstr "" msgid "The Right Turret provides extra firepower during castle combat." msgstr "" msgid "" "The Marketplace can be used to convert one type of resource into another. " "The more marketplaces you control, the better the exchange rate." msgstr "" msgid "" "The Moat slows attacking units. Any unit entering the moat must end its turn " "there and becomes more vulnerable to attack." msgstr "" msgid "" "The Castle improves the town's defense and increases its income to %{count} " "gold per day." msgstr "" msgid "" "The Tent provides workers to build a castle, provided the materials and the " "gold are available." msgstr "" msgid "" "The Captain's Quarters provides a captain to assist in the castle's defense " "when no hero is present." msgstr "" msgid "" "The Mage Guild allows heroes to learn spells and replenish their spell " "points." msgstr "" msgid "Recruit %{name}" msgstr "" msgid "Month: %{month}, Week: %{week}, Day: %{day}" msgstr "" msgid "" "You must purchase a spell book to use the mage guild, but you currently have " "no room for a spell book. Try giving one of your artifacts to another hero." msgstr "" msgid "Click to show next town." msgstr "" msgid "Show next town" msgstr "" msgid "Click to show previous town." msgstr "" msgid "Show previous town" msgstr "" msgid "This town may not be upgraded to a castle." msgstr "" msgid "Town" msgstr "" msgid "Exit Castle" msgstr "" msgid "Exit Town" msgstr "" msgid "Show Income" msgstr "" msgid "View Hero" msgstr "" msgid "The above spells are available here." msgstr "" msgid "The spells the hero can learn have been added to their book." msgstr "" msgid "A generous tip for the barkeep yields the following rumor:" msgstr "" msgid "Recruit Hero" msgstr "" msgid "%{name} is a level %{value} %{race} " msgstr "" msgid "with %{count} artifacts." msgstr "" msgid "with 1 artifact." msgstr "" msgid "without artifacts." msgstr "" msgid "Recruit %{name} the %{race}" msgstr "" msgid "" "'Spread' combat formation spreads the castle's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" msgid "" "'Grouped' combat formation bunches the castle's units together in the center " "of the castle's side of the battlefield." msgstr "" msgid "Spread Formation" msgstr "" msgid "Grouped Formation" msgstr "" msgid "Set garrison combat formation to 'Spread'" msgstr "" msgid "Set garrison combat formation to 'Grouped'" msgstr "" msgid "Exit Castle Options" msgstr "" msgid "Castle Options" msgstr "" msgid "Not enough resources to recruit creatures." msgstr "" msgid "You are unable to recruit at this time, your ranks are full." msgstr "" msgid "No creatures available for purchase." msgstr "" msgid "Recruit Creatures" msgstr "" msgid "Max" msgstr "" msgid "Hire all creatures in the town." msgstr "" msgid "Available" msgstr "" msgid "Town Population Information and Statistics" msgstr "" msgid "Damage" msgstr "" msgid "HP" msgstr "" msgid "Growth" msgstr "" msgid "week" msgstr "" msgid "View World" msgstr "" msgid "View the entire world." msgstr "" msgid "Puzzle" msgstr "" msgid "View the obelisk puzzle." msgstr "" msgid "Scenario Information" msgstr "" msgid "View information on the scenario you are currently playing." msgstr "" msgid "Dig for the Ultimate Artifact." msgstr "" msgid "Digging" msgstr "" msgid "Arena" msgstr "" msgid "" "You enter the arena and face a pack of vicious lions. You handily defeat " "them, to the wild cheers of the crowd. Impressed by your skill, the aged " "trainer of gladiators agrees to train you in a skill of your choice." msgstr "" msgid "Attack Skill" msgstr "" msgid "Defense Skill" msgstr "" msgid "Shots" msgstr "" msgid "Shots Left" msgstr "" msgid "Hit Points" msgstr "" msgid "Hit Points Left" msgstr "" msgid "You can't afford to upgrade your troops!" msgstr "" msgid "" "Your troops can be upgraded, but it will cost you dearly. Do you wish to " "upgrade them?" msgstr "" msgid "Are you sure you want to dismiss this army?" msgstr "" msgid "Upgrade" msgstr "" msgid "Upgrade your troops." msgstr "" msgid "Dismiss" msgstr "" msgid "Dismiss this army." msgstr "" msgid "" "A group of %{monster} with a desire for greater glory wish to join you.\n" "Do you accept?" msgstr "" msgid "Followers" msgstr "" msgid "" "The %{monster} is swayed by your diplomatic tongue, and offers to join your " "army for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" msgid "" "The creatures are swayed by your diplomatic\n" "tongue, and make you an offer:\n" "\n" msgstr "" msgid "" "%{offer} of the %{total} %{monster} will join your army, and the rest will " "leave you alone, for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" msgid "" "All %{offer} of the %{monster} will join your army for the sum of %{gold} " "gold.\n" "Do you accept?" msgstr "" msgid "(Rate: %{percent})" msgstr "" msgid "off" msgstr "" msgid "Music" msgstr "" msgid "Effects" msgstr "" msgid "MIDI" msgstr "" msgid "MIDI Expansion" msgstr "" msgid "External" msgstr "" msgid "Music Type" msgstr "" msgid "3D Audio" msgstr "" msgid "Toggle ambient music level." msgstr "" msgid "Toggle foreground sounds level." msgstr "" msgid "Change the type of music." msgstr "" msgid "Toggle 3D effects of foreground sounds." msgstr "" msgid "Build a new ship:" msgstr "" msgid "Resource cost:" msgstr "" msgid "Total: " msgstr "" msgid "Need: " msgstr "" msgid "Load Game" msgstr "" msgid "No save files to load." msgstr "" msgid "New Game" msgstr "" msgid "Start a single or multi-player game." msgstr "" msgid "Load a previously saved game." msgstr "" msgid "Save Game" msgstr "" msgid "Save the current game." msgstr "" msgid "Quit" msgstr "" msgid "Quit out of Heroes of Might and Magic II." msgstr "" msgid "Language" msgstr "" msgid "Graphics" msgstr "" msgid "Black & White" msgstr "" msgid "Mouse Cursor" msgstr "" msgid "Color" msgstr "" msgid "Text Support" msgstr "" msgid "Change the language of the game." msgstr "" msgid "Select Game Language" msgstr "" msgid "Change the graphics settings of the game." msgstr "" msgid "Toggle colored cursor on or off. This is only an aesthetic choice." msgstr "" msgid "" "Toggle text support mode to output extra information about windows and " "events in the game." msgstr "" msgid "" "Map\n" "Difficulty" msgstr "" msgid "" "Game\n" "Difficulty" msgstr "" msgid "Rating" msgstr "" msgid "Map Size" msgstr "" msgid "Opponents" msgstr "" msgid "Class" msgstr "" msgid "" "Victory\n" "Conditions" msgstr "" msgid "" "Loss\n" "Conditions" msgstr "" msgid "First select recipients!" msgstr "" msgid "You cannot select %{resource}!" msgstr "" msgid "Select count %{resource}:" msgstr "" msgid "Select Recipients" msgstr "" msgid "Your Funds" msgstr "" msgid "Planned Gift" msgstr "" msgid "Gift from %{name}" msgstr "" msgid "Resolution" msgstr "" msgid "Fullscreen" msgstr "" msgid "window|Mode" msgstr "" msgid "Windowed" msgstr "" msgid "V-Sync" msgstr "" #, fuzzy msgid "on" msgstr "Hiç" msgid "FPS" msgstr "" #, fuzzy msgid "System Info" msgstr "Sistem Seçenekleri" msgid "Change the resolution of the game." msgstr "" msgid "Select Game Resolution" msgstr "" msgid "Toggle between fullscreen and windowed modes." msgstr "" msgid "" "The V-Sync option can be enabled to resolve flickering issues on some " "monitors." msgstr "" msgid "Show extra information such as FPS and current time." msgstr "" msgid "Hotkey: " msgstr "" msgid "Category: " msgstr "" msgid "Event: " msgstr "" msgid "Hot Keys:" msgstr "" msgid "Evil" msgstr "" msgid "Good" msgstr "" msgid "Interface Type" msgstr "" msgid "Hide" msgstr "" msgid "Show" msgstr "" msgid "Interface" msgstr "" msgid "Slow" msgstr "" msgid "Normal" msgstr "" msgid "Fast" msgstr "" msgid "Very Fast" msgstr "" msgid "Scroll Speed" msgstr "" msgid "Toggle the type of interface you want to use." msgstr "" msgid "Toggle interface visibility." msgstr "" msgid "Toggle colored cursor on or off. This is only an esthetic choice." msgstr "" msgid "Sets the speed at which you scroll the window." msgstr "" msgid "Select Game Language:" msgstr "" msgid "Click to choose the selected language." msgstr "" msgid "%{name} has gained a level." msgstr "" msgid "%{skill} +1" msgstr "" msgid "You have learned %{skill}." msgstr "" msgid "" "%{name} has gained a level.\n" "\n" "%{skill} +1" msgstr "" msgid "" "You may learn either:\n" "%{skill1}\n" "or\n" "%{skill2}" msgstr "" msgid "" "Please inspect our fine wares. If you feel like offering a trade, click on " "the items you wish to trade with and for." msgstr "" msgid "" "You have received quite a bargain. I expect to make no profit on the deal. " "Can I interest you in any of my other wares?" msgstr "" msgid "I can offer you %{count} for 1 unit of %{resfrom}." msgstr "" msgid "I can offer you 1 unit of %{resto} for %{count} units of %{resfrom}." msgstr "" msgid "Min" msgstr "" msgid "Qty to trade" msgstr "" msgid "Trading Post" msgstr "" msgid "Your Resources" msgstr "" msgid "Available Trades" msgstr "" msgid "n/a" msgstr "" msgid "guarded by %{count} %{monster}" msgstr "" msgid "guarded by " msgstr "" msgid "(available: %{count})" msgstr "" msgid "(empty)" msgstr "" msgid "already learned" msgstr "" #, fuzzy msgid "treeOfKnowledge|free" msgstr "Bilgi" msgid "already claimed" msgstr "" msgid "not claimed" msgstr "" msgid "already knows this skill" msgstr "" msgid "already has max skills" msgstr "" msgid "(already visited)" msgstr "" msgid "(not visited)" msgstr "" msgid "%{color} Barrier" msgstr "" msgid "(tent visited)" msgstr "" msgid "%{color} Tent" msgstr "" msgid "Road" msgstr "" msgid "(digging ok)" msgstr "" msgid "(no digging)" msgstr "" msgid "penalty: %{cost}" msgstr "" msgid "Defenders:" msgstr "" msgid "Unknown" msgstr "" msgid "%{name} (Level %{level})" msgstr "" msgid "Attack:" msgstr "" msgid "Defense:" msgstr "" msgid "Spell Power:" msgstr "" msgid "Knowledge:" msgstr "" msgid "Spell Points:" msgstr "" msgid "Move Points:" msgstr "" msgid "Uncharted Territory" msgstr "" msgid "Cost per troop:" msgstr "" msgid "Available: %{count}" msgstr "" msgid "Number to buy:" msgstr "" msgid "Recruit selected monsters." msgstr "" msgid "Select maximum monsters to be recruited." msgstr "" msgid "Select only 1 monster to be recruited." msgstr "" msgid "Select Game Resolution:" msgstr "" msgid "Click to apply the selected resolution." msgstr "" msgid "Click to apply the entered text." msgstr "" msgid "Click to open the Virtual Keyboard dialog." msgstr "" msgid "Open Virtual Keyboard" msgstr "" msgid "How many troops to move?" msgstr "" msgid "Fast separation into slots:" msgstr "" msgid "Map: " msgstr "" msgid "" "\n" "\n" "Month: " msgstr "" msgid ", Week: " msgstr "" msgid ", Day: " msgstr "" msgid "" "\n" "\n" "Location: " msgstr "" msgid "Click to save the current game." msgstr "" msgid "Click to load a previously saved game." msgstr "" msgid "Are you sure you want to delete file:" msgstr "" msgid "Warning!" msgstr "" msgid "File to Save:" msgstr "" msgid "File to Load:" msgstr "" msgid "Accept the choice made." msgstr "" msgid "%{color} %{race} hero" msgstr "" msgid "Terrain object" msgstr "" msgid "Select Skill:" msgstr "" msgid "Select Spell:" msgstr "" msgid "Select Artifact:" msgstr "" msgid "Select Monster:" msgstr "" msgid "Select Hero:" msgstr "" #, fuzzy msgid "Select Treasure:" msgstr "Büyü hedefini seç" #, fuzzy msgid "Select Ocean Object:" msgstr "Büyü hedefini seç" msgid "Castle/town placing" msgstr "" msgid "doubleLinedRace|Neutral" msgstr "" msgid "race|Random" msgstr "" msgid "Click to start placing the selected castle/town." msgstr "" msgid "Click to select town placing." msgstr "" msgid "Click to select castle placing." msgstr "" msgid "%{color} %{race} %{townOrCastle}" msgstr "" msgid "race|Neutral" msgstr "" msgid "You will place" msgstr "" msgid "Click to select this class." msgstr "" msgid "Click to select this color." msgstr "" #, fuzzy msgid "Select Dwelling:" msgstr "Büyü hedefini seç" #, fuzzy msgid "Select Landscape Object:" msgstr "Büyü hedefini seç" msgid "Mine placing" msgstr "" msgid "" "Resource\n" "type:" msgstr "" #, fuzzy msgid "%{mineName} appearance:" msgstr "%{name} %{race}" msgid "Click to select %{object} as the resource generator to be placed." msgstr "" #, fuzzy msgid "Select Mountain Object:" msgstr "Büyü hedefini seç" #, fuzzy msgid "Select Rock Object:" msgstr "Büyü hedefini seç" #, fuzzy msgid "Select Tree Object:" msgstr "Büyü hedefini seç" #, fuzzy msgid "Select Power Up Object:" msgstr "Büyü hedefini seç" #, fuzzy msgid "Select Adventure Object:" msgstr "Büyü hedefini seç" msgid "Map Type:\n" msgstr "" msgid "The Succession Wars" msgstr "" msgid "The Price of Loyalty" msgstr "" msgid "Resurrection" msgstr "" msgid "Lose all your heroes and towns." msgstr "" msgid "Lose a specific town." msgstr "" msgid "Lose a specific hero." msgstr "" msgid "Run out of time. Fail to win by a certain point." msgstr "" msgid "Loss Condition" msgstr "" msgid "Defeat all enemy heroes and towns." msgstr "" msgid "Capture a specific town." msgstr "" msgid "Defeat a specific hero." msgstr "" msgid "Find a specific artifact." msgstr "" msgid "Your side defeats the opposing side." msgstr "" msgid "Accumulate a large amount of gold." msgstr "" msgid "Victory Condition" msgstr "" msgid "Map difficulty:" msgstr "" msgid "N" msgstr "" msgid "No maps exist at that size" msgstr "" msgid "Small Maps" msgstr "" msgid "View only maps of size small (36 x 36)." msgstr "" msgid "Medium Maps" msgstr "" msgid "View only maps of size medium (72 x 72)." msgstr "" msgid "Large Maps" msgstr "" msgid "View only maps of size large (108 x 108)." msgstr "" msgid "Extra Large Maps" msgstr "" msgid "View only maps of size extra large (144 x 144)." msgstr "" msgid "All Maps" msgstr "" msgid "View all maps, regardless of size." msgstr "" msgid "Players Icon" msgstr "" msgid "" "Indicates how many players total are in the scenario. Any positions not " "occupied by humans will be occupied by computer players." msgstr "" msgid "" "Indicates whether the map\n" "is small (36 x 36), medium\n" "(72 x 72), large (108 x 108),\n" "or extra large (144 x 144)." msgstr "" msgid "Size Icon" msgstr "" msgid "" "Indicates whether the map is made for \"The Succession Wars\" or \"The Price " "of Loyalty\" version of the game." msgstr "" msgid "Map Type" msgstr "" msgid "Selected Name" msgstr "" msgid "The name of the currently selected map." msgstr "" msgid "Selected Map Difficulty" msgstr "" msgid "" "The map difficulty of the currently selected map. The map difficulty is " "determined by the scenario designer. More difficult maps might include more " "or stronger enemies, fewer resources, or other special conditions making " "things tougher for the human player." msgstr "" msgid "Selected Description" msgstr "" msgid "The description of the currently selected map." msgstr "" msgid "Jump" msgstr "" msgid "Hero Speed" msgstr "" msgid "Don't Show" msgstr "" msgid "Enemy Speed" msgstr "" msgid "Auto Resolve" msgstr "" msgid "Auto, No Spells" msgstr "" msgid "Battles" msgstr "" msgid "autoBattle|Manual" msgstr "" msgid "Change the speed at which your heroes move on the main screen." msgstr "" msgid "" "Sets the speed that A.I. heroes move at. You can also elect not to view A.I. " "movement at all." msgstr "" msgid "Change the interface settings of the game." msgstr "" msgid "Interface Settings" msgstr "" msgid "Toggle instant battle mode." msgstr "" msgid "Att." msgstr "" msgid "Def." msgstr "" msgid "Power" msgstr "" msgid "Knowl" msgstr "" msgid "1st" msgstr "" msgid "2nd" msgstr "" msgid "3rd" msgstr "" msgid "4th" msgstr "" msgid "5th" msgstr "" msgid "6th" msgstr "" msgid "Oracle: Player Rankings" msgstr "" msgid "Thieves' Guild: Player Rankings" msgstr "" msgid "Number of Towns:" msgstr "" msgid "Number of Castles:" msgstr "" msgid "Number of Heroes:" msgstr "" msgid "Gold in Treasury:" msgstr "" msgid "Wood & Ore:" msgstr "" msgid "Gems, Cr, Slf & Mer:" msgstr "" msgid "Obelisks Found:" msgstr "" msgid "Artifacts:" msgstr "" msgid "Total Army Strength:" msgstr "" msgid "Income:" msgstr "" msgid "Best Hero:" msgstr "" msgid "Best Hero Stats:" msgstr "" msgid "Personality:" msgstr "" msgid "Best Monster:" msgstr "" msgid "Map filename" msgstr "" msgid "" "Are you sure you want to load a new map? (Any unsaved changes to the current " "map will be lost.)" msgstr "" msgid "" "Are you sure you want to create a new map? (Any unsaved changes to the " "current map will be lost.)" msgstr "" msgid "" "Create a new map, either from scratch or using the random map generator." msgstr "" msgid "New Map" msgstr "" msgid "Load Map" msgstr "" msgid "Load an existing map." msgstr "" msgid "Save Map" msgstr "" msgid "Save the current map." msgstr "" msgid "Quit out of the map editor." msgstr "" msgid "Objects cannot be placed outside the map." msgstr "" msgid "Monsters cannot be placed on water." msgstr "" msgid "Choose a tile which does not contain any objects." msgstr "" msgid "Treasures cannot be placed on water." msgstr "" msgid "Heroes cannot be placed on water." msgstr "" msgid "" "A maximum of %{count} heroes of the same color can be placed on the map." msgstr "" msgid "Artifacts cannot be placed on water." msgstr "" msgid "Mountains cannot be placed on water." msgstr "" msgid "Rocks cannot be placed on water." msgstr "" msgid "Trees cannot be placed on water." msgstr "" msgid "Ocean object must be placed on water." msgstr "" msgid "Landscape objects cannot be placed on water." msgstr "" msgid "Towns cannot be placed on water." msgstr "" msgid "Mines cannot be placed on water." msgstr "" msgid "Dwellings cannot be placed on water." msgstr "" msgid "Power-ups cannot be placed on water." msgstr "" msgid "Adventure objects cannot be placed on water." msgstr "" msgid "Used to place %{object}." msgstr "" #, fuzzy msgid "Select object type" msgstr "Büyü hedefini seç" msgid "" "Click here to\n" "select a monster." msgstr "" #, fuzzy msgid "" "Click here to\n" "select another monster." msgstr "Lütfen baÅŸka bir kahraman seç." msgid "" "Cell\n" "Details" msgstr "" msgid "Roads" msgstr "" msgid "Streams" msgstr "" msgid "Erase" msgstr "" msgid "Mountains" msgstr "" msgid "Rocks" msgstr "" msgid "Trees" msgstr "" msgid "Water Objects" msgstr "" msgid "Miscellaneous" msgstr "" msgid "Artifacts" msgstr "" msgid "Dwellings" msgstr "" msgid "Mines" msgstr "" msgid "Power-ups" msgstr "" msgid "Treasures" msgstr "" msgid "Towns" msgstr "" msgid "Heroes" msgstr "" msgid "editorErasure|Terrain" msgstr "" msgid "editorErasure|Castles" msgstr "" msgid "editorErasure|Monsters" msgstr "" msgid "editorErasure|Heroes" msgstr "" msgid "editorErasure|Artifacts" msgstr "" msgid "editorErasure|Roads" msgstr "" msgid "editorErasure|Streams" msgstr "" msgid "editorErasure|Treasures" msgstr "" msgid "" "Draws terrain in\n" "%{size} by %{size} square increments." msgstr "" msgid "" "Erases objects in\n" "%{size} by %{size} square increments." msgstr "" msgid "Small Brush" msgstr "" msgid "Medium Brush" msgstr "" msgid "Large Brush" msgstr "" msgid "Area Fill" msgstr "" msgid "Used to click and drag for filling in large areas." msgstr "" msgid "Clear Area" msgstr "" msgid "Used to click and drag for clearing large areas." msgstr "" msgid "" "Costs %{rate} times normal movement for all heroes. (Pathfinding reduces or " "eliminates the penalty.)" msgstr "" msgid "Traversable only by boat." msgstr "" msgid "No special modifiers." msgstr "" msgid "Toggle the erasure of %{type} objects." msgstr "" msgid "" "Objects of this type will be deleted with the Erase tool. Left-click here to " "deselect this type. Press and hold this button to deselect all other object " "types." msgstr "" msgid "" "Objects of this type will NOT be deleted with the Erase tool. Left-click " "here to select this type. Press and hold this button to select all other " "object types." msgstr "" msgid "Terrain Mode" msgstr "" msgid "Used to draw the underlying grass, dirt, water, etc. on the map." msgstr "" msgid "Landscape Objects Mode" msgstr "" msgid "" "Used to place landscape objects (mountains, rocks, trees, etc.) on the map." msgstr "" msgid "Detail Mode" msgstr "" msgid "Used for special editing of monsters, heroes and towns." msgstr "" msgid "Adventure Objects Mode" msgstr "" msgid "" "Used to place adventure objects (artifacts, dwellings, mines, treasures, " "etc.) on the map." msgstr "" msgid "Kingdom Objects Mode" msgstr "" msgid "Used to place kingdom objects (towns, castles and heroes) on the map." msgstr "" #, fuzzy msgid "Monsters Mode" msgstr "Canavarlar" msgid "Used to place monsters on the map." msgstr "" msgid "Allows you to draw streams by clicking and dragging." msgstr "" msgid "Stream Mode" msgstr "" msgid "Allows you to draw roads by clicking and dragging." msgstr "" msgid "Road Mode" msgstr "" msgid "Erase Mode" msgstr "" msgid "Used to erase objects from the map." msgstr "" msgid "Change between zoom and normal view." msgstr "" msgid "Magnify" msgstr "" msgid "Undo" msgstr "" msgid "Undo your last action." msgstr "" msgid "Create a new map either from scratch or using the random map generator." msgstr "" msgid "Edit map title, description, and other general information." msgstr "" msgid "Specifications" msgstr "" msgid "File Options" msgstr "" msgid "" "Open the file options menu, where you can save or load maps, or quit out of " "the editor." msgstr "" msgid "View the editor system options, which let you customize the editor." msgstr "" msgid "Create a map that is %{size} squares wide and %{size} squares high." msgstr "" msgid "Cancel back to the New Map menu." msgstr "" msgid "Cancel back to the main menu." msgstr "" msgid "From Scratch" msgstr "" msgid "Start from scratch with a blank map." msgstr "" msgid "Create a randomly generated map." msgstr "" msgid "Random" msgstr "" msgid "Cancel back to the Map Editor main menu." msgstr "" msgid "No maps available!" msgstr "" msgid "Warning" msgstr "Uyarı" msgid "difficulty|Easy" msgstr "" msgid "difficulty|Normal" msgstr "" msgid "difficulty|Hard" msgstr "" msgid "difficulty|Expert" msgstr "" msgid "difficulty|Impossible" msgstr "" msgid "and more..." msgstr "" msgid "Easy" msgstr "" msgid "Hard" msgstr "" msgid "Campaign Difficulty" msgstr "" msgid "" "Choose this difficulty if you want to prefer game story over challenge. AI " "is weaker in comparison with normal difficulty." msgstr "" msgid "" "Choose this difficulty to enjoy the campaign as per the original design." msgstr "" msgid "" "Choose this difficulty if you want challenge. AI is stronger in comparison " "with normal difficulty." msgstr "" msgid "" "Congratulations!\n" "\n" "Days: %{days}\n" msgstr "" msgid "" "\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" msgid "" "Score: %{score}\n" "\n" "Rating:\n" "%{rating}" msgstr "" msgid "Start the selected scenario." msgstr "" msgid "View Intro" msgstr "" msgid "View Intro videos for the current state of the campaign." msgstr "" msgid "" "Select the campaign difficulty. This can be lowered at any point during the " "campaign." msgstr "" msgid "Restart the current scenario." msgstr "" msgid "Difficulty" msgstr "" msgid "" "You have changed to a lower difficulty for the campaign. You will not be " "able to revert this after this point. The high score will be calculated " "based solely on the new difficulty. Do you want to proceed?" msgstr "" msgid "Are you sure you want to restart this scenario?" msgstr "" msgid "Campaign Scenario loading failure" msgstr "" msgid "Please make sure that campaign files are correct and present." msgstr "" msgid "Days spent" msgstr "" msgid "The number of days spent on this campaign." msgstr "" msgid "Project Coordination and Core Development" msgstr "" msgid "Development" msgstr "" msgid "Visit us at " msgstr "" msgid "QA and Support" msgstr "" msgid "Dev and Support" msgstr "" msgid "Special Thanks to" msgstr "" msgid "and many other contributors!" msgstr "" msgid "and many-many other supporters!" msgstr "" msgid "Support us at" msgstr "" msgid "local-donation-platform|https://www.patreon.com/fheroes2" msgstr "" msgid "Connect with us at" msgstr "" msgid "local-social-network|https://www.facebook.com/groups/fheroes2" msgstr "" msgid "Need help with the game?" msgstr "" msgid "Original project before 0.7" msgstr "" msgid "Heroes of Might and Magic II: The Succession Wars team" msgstr "" msgid "Designed and Directed" msgstr "" msgid "Programming and Design" msgstr "" msgid "Executive Producer" msgstr "" msgid "Producer" msgstr "" msgid "Additional Design" msgstr "" msgid "Additional Programming" msgstr "" msgid "Musical Production" msgstr "" msgid "Music and Sound Design" msgstr "" msgid "Vocalists" msgstr "" msgid "Art Director" msgstr "" msgid "Assistant Art Director" msgstr "" msgid "Artists" msgstr "" msgid "QA Manager" msgstr "" msgid "QA" msgstr "" msgid "Writing" msgstr "" msgid "Manual and Helpfile" msgstr "" msgid "Scenarios" msgstr "" msgid "Heroes of Might and Magic II: The Price of Loyalty team" msgstr "" msgid "Design Lead" msgstr "" msgid "Designers" msgstr "" msgid "Programming Lead" msgstr "" msgid "Art Lead" msgstr "" msgid "Playtesters" msgstr "" msgid "Designer" msgstr "" msgid "Producers" msgstr "" msgid "QA Managers" msgstr "" msgid "Sound Design" msgstr "" msgid "Town Themes" msgstr "" msgid "Alto Sax" msgstr "" msgid "Harpsichord and Piano" msgstr "" msgid "Basso Vocal" msgstr "" msgid "Soprano Vocal" msgstr "" msgid "Recorded at %{recordingStudio}" msgstr "" msgid "credits|Manual" msgstr "" msgid "German Consultant" msgstr "" msgid "Map Designers" msgstr "" msgid "Package Design" msgstr "" msgid "Your Name" msgstr "" msgid "Unknown Hero" msgstr "" msgid "Standard" msgstr "" msgid "View High Scores for Standard Maps." msgstr "" msgid "Campaign" msgstr "" msgid "View High Scores for Campaigns." msgstr "" msgid "hotkey|default okay event" msgstr "" msgid "hotkey|default cancel event" msgstr "" msgid "hotkey|default left" msgstr "" msgid "hotkey|default right" msgstr "" msgid "hotkey|default up" msgstr "" msgid "hotkey|default down" msgstr "" msgid "hotkey|toggle fullscreen" msgstr "" msgid "hotkey|toggle text support mode" msgstr "" msgid "hotkey|new game" msgstr "" msgid "hotkey|load game" msgstr "" msgid "hotkey|highscores" msgstr "" msgid "hotkey|credits" msgstr "" msgid "hotkey|standard game" msgstr "" msgid "hotkey|campaign game" msgstr "" msgid "hotkey|multi-player game" msgstr "" msgid "hotkey|settings" msgstr "" msgid "hotkey|quit" msgstr "" msgid "hotkey|select map" msgstr "" msgid "hotkey|select small map size" msgstr "" msgid "hotkey|select medium map size" msgstr "" msgid "hotkey|select large map size" msgstr "" msgid "hotkey|select extra large map size" msgstr "" msgid "hotkey|select all map sizes" msgstr "" msgid "hotkey|hotseat game" msgstr "" msgid "hotkey|battle only game" msgstr "" msgid "hotkey|choose the original campaign" msgstr "" msgid "hotkey|choose the expansion campaign" msgstr "" msgid "hotkey|map editor main menu" msgstr "" msgid "hotkey|new map menu" msgstr "" msgid "hotkey|load map menu" msgstr "" msgid "hotkey|new map from scratch" msgstr "" msgid "hotkey|new random map" msgstr "" #, fuzzy msgid "hotkey|undo last action" msgstr "Sistem seçeneklerini özelleÅŸtirin" #, fuzzy msgid "hotkey|redo last action" msgstr "Sistem seçeneklerini özelleÅŸtirin" msgid "hotkey|roland campaign" msgstr "" msgid "hotkey|archibald campaign" msgstr "" msgid "hotkey|the price of loyalty campaign" msgstr "" msgid "hotkey|voyage home campaign" msgstr "" msgid "hotkey|wizard's isle campaign" msgstr "" msgid "hotkey|descendants campaign" msgstr "" msgid "hotkey|select first campaign bonus" msgstr "" msgid "hotkey|select second campaign bonus" msgstr "" msgid "hotkey|select third campaign bonus" msgstr "" msgid "hotkey|view campaign intro" msgstr "" msgid "hotkey|select campaign difficulty" msgstr "" msgid "hotkey|restart campaign scenario" msgstr "" msgid "hotkey|world map left" msgstr "" msgid "hotkey|world map right" msgstr "" msgid "hotkey|world map up" msgstr "" msgid "hotkey|world map down" msgstr "" msgid "hotkey|world map up left" msgstr "" msgid "hotkey|world map up right" msgstr "" msgid "hotkey|world map down left" msgstr "" msgid "hotkey|world map down right" msgstr "" msgid "hotkey|save game" msgstr "" msgid "hotkey|next hero" msgstr "" msgid "hotkey|start hero movement" msgstr "" msgid "hotkey|cast adventure spell" msgstr "" msgid "hotkey|put hero to sleep" msgstr "" msgid "hotkey|next town" msgstr "" msgid "hotkey|end turn" msgstr "" msgid "hotkey|file options" msgstr "" msgid "hotkey|adventure options" msgstr "" msgid "hotkey|puzzle map" msgstr "" msgid "hotkey|scenario information" msgstr "" msgid "hotkey|dig for artifact" msgstr "" msgid "hotkey|view world" msgstr "" msgid "hotkey|kingdom summary" msgstr "" msgid "hotkey|default action" msgstr "" msgid "hotkey|open focus" msgstr "" #, fuzzy msgid "hotkey|system options" msgstr "Sistem seçeneklerini özelleÅŸtirin" msgid "hotkey|scroll left" msgstr "" msgid "hotkey|scroll right" msgstr "" msgid "hotkey|scroll up" msgstr "" msgid "hotkey|scroll down" msgstr "" msgid "hotkey|toggle control panel" msgstr "" msgid "hotkey|toggle radar" msgstr "" msgid "hotkey|toggle buttons" msgstr "" msgid "hotkey|toggle status" msgstr "" msgid "hotkey|toggle icons" msgstr "" msgid "hotkey|transfer control to ai" msgstr "" msgid "hotkey|retreat from battle" msgstr "" msgid "hotkey|surrender during battle" msgstr "" msgid "hotkey|toggle battle auto mode" msgstr "" #, fuzzy msgid "hotkey|finish the battle in auto mode" msgstr "Savaşı otomatik modda bitirmek istediÄŸinizden emin misiniz?" msgid "hotkey|battle options" msgstr "" msgid "hotkey|skip turn in battle" msgstr "" msgid "hotkey|cast battle spell" msgstr "" msgid "hotkey|dwelling level 1" msgstr "" msgid "hotkey|dwelling level 2" msgstr "" msgid "hotkey|dwelling level 3" msgstr "" msgid "hotkey|dwelling level 4" msgstr "" msgid "hotkey|dwelling level 5" msgstr "" msgid "hotkey|dwelling level 6" msgstr "" msgid "hotkey|well" msgstr "" msgid "hotkey|marketplace" msgstr "" msgid "hotkey|mage guild" msgstr "" msgid "hotkey|shipyard" msgstr "" msgid "hotkey|thieves guild" msgstr "" msgid "hotkey|tavern" msgstr "" msgid "hotkey|construction screen" msgstr "" msgid "hotkey|buy all monsters in well" msgstr "" msgid "hotkey|split stack by half" msgstr "" msgid "hotkey|split stack by one" msgstr "" msgid "hotkey|join stacks" msgstr "" msgid "hotkey|upgrade troop" msgstr "" msgid "hotkey|dismiss hero or troop" msgstr "" msgid "" "Do you want to regain control from AI? The effect will take place only on " "the next turn." msgstr "" "Bilgisayardan kontrolü yeniden kazanmak istiyor musunuz? Etki bir sonraki " "tur'da gerçekleÅŸecek." msgid "" "Do you want to transfer control from you to the AI? The effect will take " "place only on the next turn." msgstr "" msgid "Default Actions" msgstr "" msgid "Global Actions" msgstr "" msgid "Main Menu" msgstr "" msgid "World Map" msgstr "" #, fuzzy msgid "Battle Screen" msgstr "Kahraman Ekranı" #, fuzzy msgid "Town Screen" msgstr "Kahraman Ekranı" msgid "Army Actions" msgstr "" msgid "Editor" msgstr "" #, fuzzy msgid "The save file is corrupted." msgstr "Ayna görüntüsü oluÅŸturuldu." msgid "Unsupported save format: " msgstr "" msgid "Current game version: " msgstr "" msgid "Last supported version: " msgstr "" msgid "This file contains a save with an invalid game type." msgstr "" msgid "" "This save file requires \"The Price of Loyalty\" game assets, but they have " "not been provided to the engine." msgstr "" msgid "This saved game is localized to '" msgstr "" msgid "' language, but the current language of the game is '" msgstr "" msgid "Hot Seat" msgstr "" msgid "" "Play a Hot Seat game, where 2 to 4 players play around the same computer, " "switching into the 'Hot Seat' when it is their turn." msgstr "" msgid "A single player game playing out a single map." msgstr "" msgid "Standard Game" msgstr "" msgid "A single player game playing through a series of maps." msgstr "" msgid "Campaign Game" msgstr "" msgid "Multi-Player Game" msgstr "" msgid "" "A multi-player game, with several human players completing against each " "other on a single map." msgstr "" msgid "fheroes2 Resurrection Team presents" msgstr "" msgid "Greetings!" msgstr "" msgid "Welcome to Heroes of Might and Magic II powered by fheroes2 engine!" msgstr "" msgid "" "Welcome to Heroes of Might and Magic II powered by the fheroes2 engine!\n" "Before starting the game please select a game resolution." msgstr "" msgid "Please Remember" msgstr "" msgid "" "You can always change the language, resolution and settings of the game by " "clicking on the " msgstr "" msgid "door" msgstr "" msgid " on the left side of the Main Menu, or with the " msgstr "" msgid " button from the " msgstr "" msgid "NEW GAME" msgstr "" msgid "" " menu. \n" "\n" "Enjoy the game!" msgstr "" msgid "The Editor requires \"The Price of Loyalty\" expansion files to work." msgstr "" msgid "Quit Heroes of Might and Magic II and return to the operating system." msgstr "" msgid "Credits" msgstr "" msgid "View the credits screen." msgstr "" msgid "High Scores" msgstr "" msgid "View the high scores screen." msgstr "" msgid "Change language, resolution and settings of the game." msgstr "" msgid "Game Settings" msgstr "" msgid "" "Required video files for campaign selection window are missing. Please make " "sure that all necessary files are present in the system." msgstr "" msgid "" "Either Roland's or Archibald's campaign from the original Heroes of Might " "and Magic II." msgstr "" msgid "Original Campaign" msgstr "" msgid "Expansion Campaign" msgstr "" msgid "One of the four new campaigns from the Price of Loyalty expansion set." msgstr "" msgid "Loading video. Please wait..." msgstr "" msgid "Host" msgstr "" msgid "" "The host sets up the game options. There can only be one host per network " "game." msgstr "" msgid "Guest" msgstr "" msgid "" "The guest waits for the host to set up the game, then is automatically added " "in. There can be multiple guests for TCP/IP games." msgstr "" msgid "Battle Only" msgstr "" msgid "Setup and play a battle without loading any map." msgstr "" msgid "2 Players" msgstr "" msgid "" "Play with 2 human players, and optionally, up to 4 additional computer " "players." msgstr "" msgid "3 Players" msgstr "" msgid "" "Play with 3 human players, and optionally, up to 3 additional computer " "players." msgstr "" msgid "4 Players" msgstr "" msgid "" "Play with 4 human players, and optionally, up to 2 additional computer " "players." msgstr "" msgid "5 Players" msgstr "" msgid "" "Play with 5 human players, and optionally, up to 1 additional computer " "player." msgstr "" msgid "6 Players" msgstr "" msgid "Play with 6 human players." msgstr "" msgid "Dragon city has fallen! You are now the Master of the Dragons." msgstr "" msgid "" "You captured %{name}!\n" "You are victorious." msgstr "" msgid "" "You have captured the enemy hero %{name}!\n" "Your quest is complete." msgstr "" msgid "" "You have found the %{name}.\n" "Your quest is complete." msgstr "" msgid "Ultimate Artifact" msgstr "" msgid "" "The enemy is beaten.\n" "Your side has triumphed!" msgstr "" msgid "" "You have built up over %{count} gold in your treasury.\n" "All enemies bow before your wealth and power." msgstr "" msgid "Victory!" msgstr "" msgid "" "The enemy has captured %{name}!\n" "They are triumphant." msgstr "" msgid "" "The enemy has built up over %{count} gold in his treasury.\n" "You must bow done in defeat before his wealth and power." msgstr "" msgid "You have been eliminated from the game!!!" msgstr "" msgid "" "You have lost the hero %{name}.\n" "Your quest is over." msgstr "" msgid "" "You have failed to complete your quest in time.\n" "All is lost." msgstr "" #, fuzzy msgid "Defeat!" msgstr " yenildi" msgid "" "Base score: %{score}\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" msgid "Defeat all enemy heroes and capture all enemy towns and castles." msgstr "" msgid "Run out of time. (Fail to win by a certain point.)" msgstr "" msgid "You must defeat the enemy %{enemies}." msgid_plural "You must defeat the enemy alliance of %{enemies}." msgstr[0] "" msgstr[1] "" msgid "" "The alliance consisting of %{allies} and you must defeat the enemy " "%{enemies}." msgid_plural "" "The alliance consisting of %{allies} and you must defeat the enemy alliance " "of %{enemies}." msgstr[0] "" msgstr[1] "" msgid "Capture the castle '%{name}'." msgstr "" msgid "Capture the town '%{name}'." msgstr "" msgid "Defeat the hero '%{name}'." msgstr "" msgid "Find the ultimate artifact." msgstr "" msgid "Find the '%{name}' artifact." msgstr "" msgid "Accumulate %{count} gold." msgstr "" msgid "" ", or you may win by defeating all enemy heroes and capturing all enemy towns " "and castles." msgstr "" msgid "Lose the castle '%{name}'." msgstr "" msgid "Lose the town '%{name}'." msgstr "" msgid "Lose the hero: %{name}." msgstr "" msgid "Fail to win by the end of month %{month}, week %{week}, day %{day}." msgstr "" msgid "%{color} player has been vanquished!" msgstr "" msgid "Major Event!" msgstr "" msgid "Scenario:" msgstr "" msgid "Game Difficulty:" msgstr "" msgid "Opponents:" msgstr "" msgid "Class:" msgstr "" msgid "Rating %{rating}%" msgstr "" msgid "Click here to select which scenario to play." msgstr "" msgid "Scenario" msgstr "" msgid "Game Difficulty" msgstr "" msgid "" "This lets you change the starting difficulty at which you will play. Higher " "difficulty levels start you of with fewer resources, and at the higher " "settings, give extra resources to the computer." msgstr "" msgid "Difficulty Rating" msgstr "" msgid "" "The difficulty rating reflects a combination of various settings for your " "game. This number will be applied to your final score." msgstr "" msgid "Click to accept these settings and start a new game." msgstr "" msgid "Click to return to the main menu." msgstr "" msgid "Astrologers proclaim the Month of the %{name}." msgstr "" msgid "Astrologers proclaim the Week of the %{name}." msgstr "" msgid "After regular growth, the population of %{monster} is doubled!" msgstr "" msgid "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgid_plural "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgstr[0] "" msgstr[1] "" msgid "%{monster} growth +%{count}." msgstr "" msgid " All populations are halved." msgstr "" msgid " All dwellings increase population." msgstr "" msgid "New Month!" msgstr "" msgid "New Week!" msgstr "" msgid "Beware!" msgstr "" msgid "" "%{color} player, this is your last day to capture a town, or you will be " "banished from this land." msgstr "" msgid "" "%{color} player, you only have %{day} days left to capture a town, or you " "will be banished from this land." msgstr "" msgid "%{color} player's turn." msgstr "" msgid "" "%{color} player, you have lost your last town. If you do not conquer another " "town in the next week, you will be eliminated." msgstr "" msgid "" "%{color} player, your heroes abandon you, and you are banished from this " "land." msgstr "" msgid "Lord Kilburn" msgstr "" msgid "Tsabu" msgstr "" msgid "Sir Galant" msgstr "" msgid "Thundax" msgstr "" msgid "Lord Haart" msgstr "" msgid "Ariel" msgstr "" msgid "Rebecca" msgstr "" msgid "Sandro" msgstr "" msgid "Crodo" msgstr "" msgid "Barock" msgstr "" msgid "Antoine" msgstr "" msgid "Astra" msgstr "" msgid "Agar" msgstr "" msgid "Vatawna" msgstr "" msgid "Vesper" msgstr "" msgid "Ambrose" msgstr "" msgid "Troyan" msgstr "" msgid "Jojosh" msgstr "" msgid "Wrathmont" msgstr "" msgid "Maximus" msgstr "" msgid "Next Hero" msgstr "" msgid "Select the next Hero." msgstr "" #, fuzzy msgid "Hero Movement" msgstr "Gölge Hareketi" msgid "" "Start the Hero's movement along the current path or re-visit the object " "occupied by the Hero. Press and hold this button to reset the Hero's path." msgstr "" msgid "Kingdom Summary" msgstr "" msgid "View a Summary of your Kingdom." msgstr "" msgid "Cast an adventure spell." msgstr "" msgid "End Turn" msgstr "" msgid "End your turn and left the computer take its turn." msgstr "" msgid "Adventure Options" msgstr "" msgid "Bring up the adventure options menu." msgstr "" msgid "" "Bring up the file options menu, allowing you to load, save, start a new game " "or quit." msgstr "" msgid "Bring up the system options menu, allowing you to customize your game." msgstr "" msgid "" "One or more heroes may still move, are you sure you want to end your turn?" msgstr "" msgid "Are you sure you want to quit?" msgstr "" msgid "Are you sure you want to restart? (Your current game will be lost.)" msgstr "" msgid "Are you sure you want to overwrite the save with this name?" msgstr "" msgid "Game saved successfully." msgstr "" msgid "There was an issue during saving." msgstr "" msgid "" "Are you sure you want to load a new game? (Your current game will be lost.)" msgstr "" msgid "Try looking on land!!!" msgstr "" msgid "" "Searching for the Ultimate Artifact is fruitless. Your hero could not carry " "it even if he found it - all his artifact slots are full." msgstr "" msgid "Digging for artifacts requires a whole day, try again tomorrow." msgstr "" msgid "" "After spending many hours digging here, you have uncovered the %{artifact}." msgstr "" msgid "Congratulations!" msgstr "" msgid "Nothing here. Where could it be?" msgstr "" msgid "Try searching on clear ground." msgstr "" msgid "A miniature view of the known world. Left click to move viewing area." msgstr "" msgid "Month: %{month} Week: %{week}" msgstr "" msgid "Day: %{day}" msgstr "" msgid "" "You find a small\n" "quantity of %{resource}." msgstr "" msgid "Status Window" msgstr "" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date." msgstr "" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date. Left click here to cycle through these windows." msgstr "" msgid "" "This lets you change player starting positions and colors. A particular " "color will always start in a particular location. Some positions may only be " "played by a computer player or only by a human player." msgstr "" msgid "" "This lets you change the class of a player. Classes are not always " "changeable. Depending on the scenario, a player may receive additional towns " "and/or heroes not of their primary alignment." msgstr "" msgid "Handicap" msgstr "" # msgid "" "This lets you change the handicap of a particular player. Only humans may be " "handicapped. Handicapped players start with fewer resources and earn 15 or " "30% fewer resources per turn for mild and severe handicaps, respectively." msgstr "" msgid "%{color} player" msgstr "" msgid "No Handicap" msgstr "" msgid "No special restrictions on start resources and earning them per turn." msgstr "" msgid "Mild Handicap" msgstr "" # msgid "" "Mild handicapped players start with fewer resources and earn 15% fewer " "resources per turn." msgstr "" msgid "Severe Handicap" msgstr "" # msgid "" "Severe handicapped players start with fewer resources and earn 30% fewer " "resources per turn." msgstr "" msgid "View %{skill} Info" msgstr "" msgid "Keyboard|123" msgstr "" msgid "Keyboard|SPACE" msgstr "" msgid "Keyboard|ABC" msgstr "" msgid "Kingdom Income" msgstr "" msgid "Kingdom Income per day." msgstr "" msgid "For every lighthouse controlled, your ships will move further each day." msgstr "" msgid "English" msgstr "" msgid "French" msgstr "" msgid "Polish" msgstr "" msgid "German" msgstr "" msgid "Russian" msgstr "" msgid "Italian" msgstr "" msgid "Czech" msgstr "" msgid "Norwegian" msgstr "" msgid "Belarusian" msgstr "" msgid "Bulgarian" msgstr "" msgid "Ukrainian" msgstr "" msgid "Romanian" msgstr "" msgid "Spanish" msgstr "" msgid "Swedish" msgstr "" msgid "Portuguese" msgstr "" msgid "Turkish" msgstr "Türkçe" msgid "Dutch" msgstr "" msgid "Hungarian" msgstr "" msgid "Danish" msgstr "" msgid "Slovak" msgstr "" #, fuzzy msgid "Vietnamese" msgstr "Görünüm %{name}" msgid ", FPS: " msgstr "" msgid "%{object} robber" msgstr "" msgid "%{object} raided" msgstr "" msgid "Ector" msgstr "" msgid "Gwenneth" msgstr "" msgid "Sir Gallant" msgstr "" msgid "Tyro" msgstr "" msgid "Dimitry" msgstr "" msgid "Ruby" msgstr "" msgid "Crag Hack" msgstr "" msgid "Fineous" msgstr "" msgid "Jezebel" msgstr "" msgid "Atlas" msgstr "" msgid "Ergon" msgstr "" msgid "Jaclyn" msgstr "" msgid "Gem" msgstr "" msgid "Natasha" msgstr "" msgid "Carlawn" msgstr "" msgid "Luna" msgstr "" msgid "Arie" msgstr "" msgid "Barok" msgstr "" msgid "Kastore" msgstr "" msgid "Falagar" msgstr "" msgid "Dawn" msgstr "" msgid "Flint" msgstr "" msgid "Halon" msgstr "" msgid "Myra" msgstr "" msgid "Myrini" msgstr "" msgid "Wilfrey" msgstr "" msgid "Mandigal" msgstr "" msgid "Sarakin" msgstr "" msgid "Charity" msgstr "" msgid "Darlana" msgstr "" msgid "Ranloo" msgstr "" msgid "Rialdo" msgstr "" msgid "Zam" msgstr "" msgid "Zom" msgstr "" msgid "Celia" msgstr "" msgid "Roxana" msgstr "" msgid "Lord Corlagon" msgstr "" msgid "Lord Halton" msgstr "" msgid "Sister Eliza" msgstr "" msgid "Brother Brax" msgstr "" msgid "Dainwin" msgstr "" msgid "Joseph" msgstr "" msgid "Mog" msgstr "" msgid "Solmyr" msgstr "" msgid "Ceallach" msgstr "" msgid "Drakonia" msgstr "" msgid "Elderian" msgstr "" msgid "Gallavant" msgstr "" msgid "Jarkonas" msgstr "" msgid "Martine" msgstr "" msgid " gives you maximum morale" msgstr "" msgid " gives you maximum luck" msgstr "" msgid "You cannot pick up this artifact, you already have a full load!" msgstr "" msgid "To cast spells, you must first buy a spell book for %{gold} gold." msgstr "" msgid "Unfortunately, you seem to be a little short of cash at the moment." msgstr "" msgid "Do you wish to buy one?" msgstr "" msgid "%{count} / day" msgstr "" msgid "one" msgstr "" msgid "two" msgstr "" msgid "A whirlpool engulfs your ship. Some of your army has fallen overboard." msgstr "" msgid "Insulted by your refusal of their offer, the monsters attack!" msgstr "" msgid "" "The %{monster}, awed by the power of your forces, begin to scatter.\n" "Do you wish to pursue and engage them?" msgstr "" msgid "Ransacking an enemy camp, you discover a hidden cache of treasures." msgstr "" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with these resources, " "come back next week for more.\"" msgstr "" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there are no resources currently available. Please try " "again next week.\"" msgstr "" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with this gold, come " "back next week for more.\"" msgstr "" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there is no gold currently available. Please try again " "next week.\"" msgstr "" msgid "" "You've found an abandoned lean-to.\n" "Poking about, you discover some resources hidden nearby." msgstr "" msgid "The lean-to is long abandoned. There is nothing of value here." msgstr "" msgid "" "You catch a leprechaun foolishly sleeping amidst a cluster of magic " "mushrooms.\n" "In exchange for his freedom, he guides you to a small pot filled with " "precious things." msgstr "" msgid "" "You've found a magic garden, the kind of place that leprechauns and faeries " "like to cavort in, but there is no one here today.\n" "Perhaps you should try again next week." msgstr "" msgid "You come upon the remains of an unfortunate adventurer." msgstr "" msgid "Treasure" msgstr "" msgid "Searching through the tattered clothing, you find the %{artifact}." msgstr "" msgid "Searching through the tattered clothing, you find nothing." msgstr "" msgid "" "You come across an old wagon left by a trader who didn't quite make it to " "safe terrain." msgstr "" msgid "Unfortunately, others have found it first, and the wagon is empty." msgstr "" msgid "Searching inside, you find the %{artifact}." msgstr "" msgid "Inside, you find some of the wagon's cargo still intact." msgstr "" msgid "You search through the flotsam, and find some wood and some gold." msgstr "" msgid "You search through the flotsam, and find some wood." msgstr "" msgid "You search through the flotsam, but find nothing." msgstr "" msgid "Shrine of the 1st Circle" msgstr "" msgid "" "You come across a small shrine attended by a group of novice acolytes.\n" "In exchange for your protection, they agree to teach you a simple spell - " "'%{spell}'." msgstr "" msgid "Shrine of the 2nd Circle" msgstr "" msgid "" "You come across an ornate shrine attended by a group of rotund friars.\n" "In exchange for your protection, they agree to teach you a spell - " "'%{spell}'." msgstr "" msgid "Shrine of the 3rd Circle" msgstr "" msgid "" "You come across a lavish shrine attended by a group of high priests.\n" "In exchange for your protection, they agree to teach you a sophisticated " "spell - '%{spell}'." msgstr "" msgid "" "\n" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" msgid "" "\n" "Unfortunately, you already have knowledge of this spell, so there is nothing " "more for them to teach you." msgstr "" msgid "" "\n" "Unfortunately, you have no Magic Book to record the spell with." msgstr "" msgid "" "You approach the hut and observe a witch inside studying an ancient tome on " "%{skill}.\n" "\n" msgstr "" msgid "" "As you approach, she turns and focuses her one glass eye on you.\n" "\"You already know everything you deserve to learn!\" the witch screeches. " "\"NOW GET OUT OF MY HOUSE!\"" msgstr "" msgid "" "As you approach, she turns and speaks.\n" "\"You already know that which I would teach you. I can help you no further.\"" msgstr "" msgid "" "An ancient and immortal witch living in a hut with bird's legs for stilts " "teaches you %{skill} for her own inscrutable purposes." msgstr "" msgid "As you drink the sweet water, you gain luck for your next battle." msgstr "" msgid "You drink from the enchanted fountain, but nothing happens." msgstr "" msgid "You enter the faerie ring, but nothing happens." msgstr "" msgid "" "Upon entering the mystical faerie ring, your army gains luck for its next " "battle." msgstr "" msgid "" "You've found an ancient and weathered stone idol.\n" "It is supposed to grant luck to visitors, but since the stars are already " "smiling upon you, it does nothing." msgstr "" msgid "" "You've found an ancient and weathered stone idol.\n" "Kissing it is supposed to be lucky, so you do. The stone is very cold to the " "touch." msgstr "" msgid "The mermaids silently entice you to return later and be blessed again." msgstr "" msgid "" "The magical, soothing beauty of the Mermaids reaches you and your crew.\n" "Just for a moment, you forget your worries and bask in the beauty of the " "moment.\n" "The mermaids charms bless you with increased luck for your next combat." msgstr "" msgid "" "You come upon the pyramid of a great and ancient king.\n" "You are tempted to search it for treasure, but all the old stories warn of " "fearful curses and undead guardians.\n" "Will you search?" msgstr "" msgid "" "Upon defeating the monsters, you decipher an ancient glyph on the wall, " "telling the secret of the spell - '" msgstr "" msgid "Unfortunately, you have no Magic Book to record the spell with." msgstr "" msgid "" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" msgid "" "You come upon the pyramid of a great and ancient king.\n" "Routine exploration reveals that the pyramid is completely empty." msgstr "" msgid "" "A drink at the well is supposed to restore your spell points, but you are " "already at maximum." msgstr "" msgid "A second drink at the well in one day will not help you." msgstr "" msgid "A drink from the well has restored your spell points to maximum." msgstr "" msgid "" "\"I'm sorry sir,\" The leader of the soldiers says, \"but you already know " "everything we have to teach.\"" msgstr "" msgid "The soldiers living in the fort teach you a few new defensive tricks." msgstr "" msgid "" "You've come upon a mercenary camp practicing their tactics. \"You're too " "advanced for us,\" the mercenary captain says. \"We can teach nothing more.\"" msgstr "" msgid "" "You've come upon a mercenary camp practicing their tactics. The mercenaries " "welcome you and your troops and invite you to train with them." msgstr "" msgid "\"Go 'way!\", the witch doctor barks, \"you know all I know.\"" msgstr "" msgid "" "An Orcish witch doctor living in the hut deepens your knowledge of magic by " "showing you how to cast stones, read portents, and decipher the intricacies " "of chicken entrails." msgstr "" msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, the Druids turn you away, indicating they have nothing " "new to teach you." msgstr "" msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, they teach you new ways to cast spells." msgstr "" msgid "" "You tentatively approach the burial ground of ancient warriors. Do you want " "to search the graves?" msgstr "" msgid "" "You spend several hours searching the graves and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" msgid "Upon defeating the Zombies you search the graves and find something!" msgstr "" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the shipwreck?" msgstr "" msgid "" "You spend several hours sifting through the debris and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" msgid "" "Upon defeating the Ghosts you sift through the debris and find something!" msgstr "" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the ship?" msgstr "" msgid "" "Upon defeating the Skeletons you sift through the debris and find something!" msgstr "" msgid "Your men spot a navigational buoy, confirming that you are on course." msgstr "" msgid "" "Your men spot a navigational buoy, confirming that you are on course and " "increasing their morale." msgstr "" msgid "" "The drink at the oasis is refreshing, but offers no further benefit. The " "oasis might help again if you fought a battle first." msgstr "" msgid "" "A drink at the oasis fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" msgid "" "The drink at the watering hole is refreshing, but offers no further benefit. " "The watering hole might help again if you fought a battle first." msgstr "" msgid "" "A drink at the watering hole fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" msgid "" "It doesn't help to pray twice before a battle. Come back after you've fought." msgstr "" msgid "A visit and a prayer at the temple raises the morale of your troops." msgstr "" msgid "" "An old Knight appears on the steps of the gazebo. \"I am sorry, my liege, I " "have taught you all I can.\"" msgstr "" msgid "" "An old Knight appears on the steps of the gazebo. \"My liege, I will teach " "you all that I know to aid you in your travels.\"" msgstr "" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he says, \"I would give you an artifact as a reward, but " "you're all full.\"" msgstr "" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he rewards you for your act of kindness by giving you the " "%{art}." msgstr "" msgid "A leprechaun offers you the %{art} for the small price of %{gold} Gold." msgstr "" msgid "" "A leprechaun offers you the %{art} for the small price of %{gold} Gold and " "%{count} %{res}." msgstr "" msgid "Do you wish to buy this artifact?" msgstr "" msgid "" "You try to pay the leprechaun, but realize that you can't afford it. The " "leprechaun stamps his foot and ignores you." msgstr "" msgid "" "Insulted by your refusal of his generous offer, the leprechaun stamps his " "foot and ignores you." msgstr "" msgid "You've found the artifact: " msgstr "" msgid "" "You've found the humble dwelling of a withered hermit. The hermit tells you " "that he is willing to give the %{art} to the first wise person he meets." msgstr "" msgid "" "You've come across the spartan quarters of a retired soldier. The soldier " "tells you that he is willing to pass on the %{art} to the first true leader " "he meets." msgstr "" msgid "" "You've encountered a strange person with a hat and an owl on it. He tells " "you that he is willing to give %{art} if you have %{skill}." msgstr "" msgid "" "You come upon an ancient artifact. As you reach for it, a pack of Rogues " "leap out of the brush to guard their stolen loot." msgstr "" msgid "" "Through a clearing you observe an ancient artifact. Unfortunately, it's " "guarded by a nearby %{monster}. Do you want to fight the %{monster} for the " "artifact?" msgstr "" msgid "Victorious, you take your prize, the %{art}." msgstr "" msgid "" "Discretion is the better part of valor, and you decide to avoid this fight " "for today." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold pieces." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold and the %{art}." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it, " "only to find it empty." msgstr "" msgid "" "After scouring the area, you fall upon a hidden treasure cache. You may take " "the gold or distribute the gold to the peasants for experience. Do you wish " "to keep the gold?" msgstr "" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "%{gold} gold pieces." msgstr "" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "ancient artifact %{art}." msgstr "" msgid "" "You stumble upon a dented and tarnished lamp lodged deep in the earth. Do " "you wish to rub the lamp?" msgstr "" msgid "" "You have taken control of the local Alchemist shop. It will provide you with " "%{count} unit of Mercury per day." msgstr "" msgid "" "You gain control of a sawmill. It will provide you with %{count} units of " "wood per day." msgstr "" msgid "" "You gain control of an ore mine. It will provide you with %{count} units of " "ore per day." msgstr "" msgid "" "You gain control of a sulfur mine. It will provide you with %{count} unit of " "sulfur per day." msgstr "" msgid "" "You gain control of a crystal mine. It will provide you with %{count} unit " "of crystal per day." msgstr "" msgid "" "You gain control of a gem mine. It will provide you with %{count} unit of " "gems per day." msgstr "" msgid "" "You gain control of a gold mine. It will provide you with %{count} gold per " "day." msgstr "" msgid "" "The lighthouse is now under your control, and all of your ships will now " "move further each day." msgstr "" msgid "You gain control of a %{name}." msgstr "" msgid "" "You come upon an abandoned gold mine. The mine appears to be haunted. Do you " "wish to enter?" msgstr "" msgid "You beat the Ghosts and are able to restore the mine to production." msgstr "" msgid "" "A group of %{monster} with a desire for greater glory wish to join you. Do " "you accept?" msgstr "" msgid "As you approach the dwelling, you notice that there is no one here." msgstr "" msgid "" "You search the ruins, but the Medusas that used to live here are gone. " "Perhaps there will be more next week." msgstr "" msgid "" "You've found some Medusas living in the ruins. They are willing to join your " "army for a price. Do you want to recruit Medusas?" msgstr "" msgid "" "You've found a Sprite Tree City. Unfortunately, none of the Sprites living " "there wish to join an army. Maybe next week." msgstr "" msgid "" "Some of the Sprites living in the tree city are willing to join your army " "for a price. Do you want to recruit Sprites?" msgstr "" msgid "" "A colorful Rogues' wagon stands empty here. Perhaps more Rogues will be here " "later." msgstr "" msgid "" "Distant sounds of music and laughter draw you to a colorful wagon housing " "Rogues. Do you wish to have any Rogues join your army?" msgstr "" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. The " "tents are unoccupied. Perhaps more Nomads will be here later." msgstr "" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. Do you " "wish to have any Nomads join you during your travels?" msgstr "" msgid "The pit of mud bubbles for a minute and then lies still." msgstr "" msgid "" "As you approach the bubbling pit of mud, creatures begin to climb out and " "position themselves around it. In unison they say: \"Mother Earth would like " "to offer you a few of her troops. Do you want to recruit Earth Elementals?\"" msgstr "" msgid "You enter the structure of white stone pillars, and find nothing." msgstr "" msgid "" "White stone pillars support a roof that rises up to the sky. As you enter " "the structure, the dead air of the outside gives way to a whirling gust that " "almost pushes you back out. The air current materializes into a barely " "visible form. The creature asks, in what can only be described as a loud " "whisper: \"Why have you come? Are you here to call upon the forces of the " "air?\"" msgstr "" msgid "No Fire Elementals approach you from the lava pool." msgstr "" msgid "" "Beneath a structure that serves to hold in heat, Fire Elementals move about " "in a fiery pool of molten lava. A group of them approach you and offer their " "services. Would you like to recruit Fire Elementals?" msgstr "" msgid "A face forms in the water for a moment, and then is gone." msgstr "" msgid "" "Crystalline structures cast shadows over a small reflective pool of water. " "You peer into the pool, and a face that is not your own peers back. It asks: " "\"Would you like to call upon the powers of water?\"" msgstr "" msgid "This burial site is deathly still." msgstr "" msgid "" "Restless spirits of long dead warriors seeking their final resting place " "offer to join you in hopes of finding peace. Do you wish to recruit ghosts?" msgstr "" msgid "" "The City of the Dead is empty of life, and empty of unlife as well. Perhaps " "some undead will move in next week." msgstr "" msgid "" "Some Liches living here are willing to join your army for a price. Do you " "want to recruit Liches?" msgstr "" msgid "" "You've found the ruins of an ancient city, now inhabited solely by the " "undead. Will you search?" msgstr "" msgid "" "Some of the surviving Liches are impressed by your victory over their " "fellows, and offer to join you for a price. Do you want to recruit Liches?" msgstr "" msgid "" "You've found one of those bridges that Trolls are so fond of living under, " "but there are none here. Perhaps there will be some next week." msgstr "" msgid "" "Some Trolls living under a bridge are willing to join your army, but for a " "price. Do you want to recruit Trolls?" msgstr "" msgid "Trolls living under the bridge challenge you. Will you fight them?" msgstr "" msgid "" "A few Trolls remain, cowering under the bridge. They approach you and offer " "to join your forces as mercenaries. Do you want to buy any Trolls?" msgstr "" msgid "" "The Dragon city has no Dragons willing to join you this week. Perhaps a " "Dragon will become available next week." msgstr "" msgid "" "The Dragon city is willing to offer some Dragons for your army for a price. " "Do you wish to recruit Dragons?" msgstr "" msgid "" "You stand before the Dragon City, a place off-limits to mere humans. Do you " "wish to violate this rule and challenge the Dragons to a fight?" msgstr "" msgid "" "Having defeated the Dragon champions, the city's leaders agree to supply " "some Dragons to your army for a price. Do you wish to recruit Dragons?" msgstr "" msgid "From the observation tower, you are able to see distant lands." msgstr "" msgid "" "The spring only refills once a week, and someone's already been here this " "week." msgstr "" msgid "" "A drink at the spring is supposed to give you twice your normal spell " "points, but you are already at that level." msgstr "" msgid "" "A drink from the spring fills your blood with magic! You have twice your " "normal spell points in reserve." msgstr "" msgid "" "Recognizing you, the butler refuses to admit you. \"The master,\" he says, " "\"will not see the same student twice.\"" msgstr "" msgid "" "The butler admits you to see the master of the house. He trains you in the " "four skills a hero should know." msgstr "" msgid "" "The butler opens the door and looks you up and down. \"You are neither " "famous nor diplomatic enough to be admitted to see my master,\" he sniffs. " "\"Come back when you think yourself worthy.\"" msgstr "" msgid " and " msgstr "" msgid "" "All of the %{monsters} you have in your army have been trained by the battle " "masters of the fort. Your army now contains %{monsters2}." msgstr "" msgid "" "An unusual alliance of Ogres, Orcs, and Dwarves offer to train (upgrade) any " "such troops brought to them. Unfortunately, you have none with you." msgstr "" msgid "All of your %{monsters} have been upgraded into %{monsters2}." msgstr "" msgid "" "A blacksmith working at the foundry offers to convert all Pikemen and " "Swordsmen's weapons brought to him from iron to steel. He also says that he " "knows a process that will convert Iron Golems into Steel Golems. " "Unfortunately, you have none of these troops in your army, so he can't help " "you." msgstr "" msgid "" "The captain looks at you with surprise and says:\n" "\"You already have all the maps I know about. Let me fish in peace now.\"" msgstr "" msgid "" "A retired captain living on this refurbished fishing platform offers to sell " "you maps of the sea he made in his younger days for 1,000 gold. Do you wish " "to buy the maps?" msgstr "" msgid "" "The captain sighs. \"You don't have enough money, eh? You can't expect me " "to give my maps away for free!\"" msgstr "" msgid "" "You come upon an obelisk made from a type of stone you have never seen " "before. Staring at it intensely, the smooth surface suddenly changes to an " "inscription. The inscription is a piece of a lost ancient map. Quickly you " "copy down the piece and the inscription vanishes as abruptly as it appeared." msgstr "" msgid "You have already been to this obelisk." msgstr "" msgid "" "Upon your approach, the tree opens its eyes in delight. \"It is good to see " "you, my student. I hope my teachings have helped you.\"" msgstr "" msgid "" "Upon your approach, the tree opens its eyes in delight. \"Ahh, an " "adventurer! Allow me to teach you a little of what I have learned over the " "ages.\"" msgstr "" msgid "Upon your approach, the tree opens its eyes in delight." msgstr "" msgid "" "\"Ahh, an adventurer! I will be happy to teach you a little of what I have " "learned over the ages for a mere %{count} %{res}.\"" msgstr "" msgid "(Just bury it around my roots.)" msgstr "" msgid "Tears brim in the eyes of the tree." msgstr "" msgid "\"I need %{count} %{res}.\"" msgstr "" msgid "it whispers. (sniff) \"Well, come back when you can pay me.\"" msgstr "" msgid "" "Nestled among the trees sits a blind seer. After you explain the intent of " "your journey, the seer activates his crystal ball, allowing you to see the " "strengths and weaknesses of your opponents." msgstr "" msgid "" "The entrance to the cave is dark, and a foul, sulfurous smell issues from " "the cave mouth. Will you enter?" msgstr "" msgid "Except for evidence of a terrible battle, the cave is empty." msgstr "" msgid "" "You find a powerful and grotesque Demon in the cave. \"Today,\" it rasps, " "\"you will fight and surely die. But I will give you a choice of deaths. You " "may fight me, or you may fight my servants. Do you prefer to fight my " "servants?\"" msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points." msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points and " "%{count} gold." msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and find the %{art} in the back of the cave." msgstr "" msgid "" "Seeing that you do not have %{count} gold, the demon slashes you with its " "claws, and the last thing you see is a red haze." msgstr "" msgid "" "The Demon leaps upon you and has its claws at your throat before you can " "even draw your sword. \"Your life is mine,\" it says. \"I will sell it back " "to you for %{count} gold.\"" msgstr "" msgid "" "Upon defeating the daemon's servants, you find a hidden cache with %{count} " "gold." msgstr "" msgid "" "As you enter the Alchemist's Tower, a hobbled, graying man in a brown cloak " "makes his way towards you." msgstr "" msgid "He checks your pack, and sees that you have 1 cursed item." msgid_plural "" "He checks your pack, and sees that you have %{count} cursed items." msgstr[0] "" msgstr[1] "" msgid "For %{gold} gold, the alchemist will remove it for you. Do you pay?" msgid_plural "" "For %{gold} gold, the alchemist will remove them for you. Do you pay?" msgstr[0] "" msgstr[1] "" msgid "" "After you consent to pay the requested amount of gold, the alchemist grabs " "the cursed artifact and throws it into his magical cauldron." msgid_plural "" "After you consent to pay the requested amount of gold, the alchemist grabs " "all cursed artifacts and throws them into his magical cauldron." msgstr[0] "" msgstr[1] "" msgid "" "You hear a voice from behind the locked door, \"You don't have enough gold " "to pay for my services.\"" msgstr "" msgid "" "You hear a voice from high above in the tower, \"Go away! I can't help you!\"" msgstr "" msgid "" "The head groom speaks to you, \"That is a fine looking horse you have. I am " "afraid we can give you no better, but the horses your cavalry are riding " "look to be of poor breeding stock. We have many trained war horses which " "would aid your riders greatly. I insist you take them.\"" msgstr "" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, he will " "grow tired in a week. You must also let me give better horses to your " "mounted soldiers, their horses look shoddy and weak.\"" msgstr "" msgid "" "The head groom approaches you and speaks, \"You already have a fine horse, " "and have no inexperienced cavalry which might make use of our trained war " "horses.\"" msgstr "" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, his " "endurance will wane with a lot of heavy riding, and you must return for a " "fresh mount in a week. We also have many fine war horses which could benefit " "mounted soldiers, but you have none we can help.\"" msgstr "" msgid "The Arena guards turn you away." msgstr "" msgid "" "You have your crew stop up their ears with wax before the sirens' eerie song " "has any chance of luring them to a watery grave." msgstr "" msgid "" "As the sirens sing their eerie song, your small, determined army manages to " "overcome the urge to dive headlong into the sea." msgstr "" msgid "" "An eerie wailing song emanates from the sirens perched upon the rocks. Many " "of your crew fall under its spell, and dive into the water where they drown. " "You are now wiser for the visit, and gain %{exp} experience." msgstr "" msgid "" "In a dazzling display of daring, you break into the local jail and free the " "hero imprisoned there, who, in return, pledges loyalty to your cause." msgstr "" msgid "" "You already have %{count} heroes, and regretfully must leave the prisoner in " "this jail to languish in agony for untold days." msgstr "" msgid "" "You enter a rickety hut and talk to the magician who lives there. He tells " "you of places near and far which may aid you in your journeys." msgstr "" msgid "This eye seems to be intently studying its surroundings." msgstr "" msgid "You come across a giant Sphinx. The Sphinx remains strangely quiet." msgstr "" msgid "" "\"I have a riddle for you,\" the Sphinx says. \"Answer correctly, and you " "shall be rewarded. Answer incorrectly, and you shall be eaten. Do you accept " "the challenge?\"" msgstr "" msgid "" "The Sphinx asks you the following riddle:\n" "\n" "'%{riddle}'\n" "\n" "Your answer?" msgstr "" msgid "" "\"You guessed incorrectly,\" the Sphinx says, smiling. The Sphinx swipes at " "you with a paw, knocking you to the ground. Another blow makes the world go " "black, and you know no more." msgstr "" msgid "" "Looking somewhat disappointed, the Sphinx sighs. \"You've answered my riddle " "so here's your reward. Now begone.\"" msgstr "" msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "As you speak the magic word, the glowing barrier dissolves into nothingness." msgstr "" msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "You speak, and nothing happens." msgstr "" msgid "" "You enter the tent and see an old woman gazing into a magic gem. She looks " "up and says,\n" "\"In my travels, I have learned much in the way of arcane magic. A great " "oracle taught me his skill. I have the answer you seek.\"" msgstr "" #, fuzzy msgid "No spell book is present." msgstr "Yapılacak büyü yok." msgid "" "This spell costs %{mana} spell points. You have no spell points, so you " "cannot cast it." msgstr "" msgid "" "This spell costs %{mana} spell points. You only have %{point} spell points, " "so you cannot cast it." msgstr "" msgid "The spell was not found." msgstr "" msgid "Only heroes can cast this spell." msgstr "" msgid "This hero is not able to cast adventure spells." msgstr "" msgid "Your hero is too tired to cast this spell today. Try again tomorrow." msgstr "" msgid "This spell cannot be cast on a boat." msgstr "" msgid "This spell can only be cast near an ocean." msgstr "" msgid "" "There are no boats available and no ocean adjacent to the hero where this " "spell will work." msgstr "" msgid "There are no boats available for this spell." msgstr "" #, fuzzy msgid "There is no ocean adjacent to the hero where this spell will work." msgstr "Kahramanınızın yanında bir Elemental çağırmak için açık bir alan yok." msgid "" "You do not own any town or castle that is not currently occupied by a hero. " "This spell will have no effect." msgstr "" #, fuzzy msgid "This hero is already in a town, so this spell will have no effect." msgstr "Bu büyü hiç kimseyi etkilemeyecek!" msgid "" "The nearest town is %{town}.\n" "\n" "This town is occupied by your hero %{hero}." msgstr "" msgid "This spell is already in effect." msgstr "" msgid "" "No opponent neither has nor can have any hero under their command anymore. " "Casting this spell will have no effect." msgstr "" msgid "" "No opponent has a hero under their command at this time. Casting this spell " "will have no effect." msgstr "" msgid "" "You must be within %{count} spaces of a monster for the Visions spell to " "work." msgstr "" msgid "" "You must be standing on the entrance to a mine (sawmills and alchemist labs " "do not count) to cast this spell." msgstr "" msgid "You must first defeat the ghosts guarding the mine to cast this spell." msgstr "" msgid "" "There are already at least as many elementals guarding the mine as this hero " "can generate. Casting this spell will have no effect." msgstr "" msgid "%{name} the %{race} (Level %{level})" msgstr "" msgid "" "'Spread' combat formation spreads the hero's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" msgid "" "'Grouped' combat formation bunches the hero's army together in the center of " "their side of the battlefield." msgstr "" msgid "Are you sure you want to dismiss this Hero?" msgstr "" msgid "View Experience Info" msgstr "" msgid "View Spell Points Info" msgstr "" msgid "Set army combat formation to 'Spread'" msgstr "" msgid "Set army combat formation to 'Grouped'" msgstr "" msgid "Exit Hero Screen" msgstr "" msgid "You cannot dismiss a hero in a castle" msgstr "" msgid "Dismissal of %{name} the %{race} is prohibited by scenario" msgstr "" msgid "Dismiss %{name} the %{race}" msgstr "" msgid "Show previous hero" msgstr "" msgid "Show next hero" msgstr "" msgid "Blood Morale" msgstr "" msgid "%{morale} Morale" msgstr "" msgid "%{luck} Luck" msgstr "" msgid "Current Luck Modifiers:" msgstr "" msgid "Current Morale Modifiers:" msgstr "" msgid "Entire army is undead, so morale does not apply." msgstr "" msgid "" "Current experience %{exp1}.\n" " Next level %{exp2}." msgstr "" msgid "Level %{level}" msgstr "" msgid "" "%{name} currently has %{point} spell points out of a maximum of %{max}. The " "maximum number of spell points is 10 times the hero's knowledge. It is " "occasionally possible for the hero to have more than their maximum spell " "points via special events." msgstr "" msgid "%{name1} meets %{name2}" msgstr "" msgid "Enemy heroes are now fully identifiable." msgstr "" msgid "Identify Hero" msgstr "" msgid "Select town to port to." msgstr "" msgid "Town Portal" msgstr "" msgid "The creatures are willing to join us!" msgstr "" msgid "All the creatures will join us..." msgstr "" msgid "The creature will join us..." msgid_plural "%{count} of the creatures will join us..." msgstr[0] "" msgstr[1] "" msgid "" "\n" " for a fee of %{gold} gold." msgstr "" msgid "These weak creatures will surely flee before us." msgstr "" msgid "I fear these creatures are in the mood for a fight." msgstr "" #, fuzzy msgid "The hero's attack skill is a bonus added to each unit's attack skill." msgstr "her biri saldırı becerilerine +%{attack} bonusu ekler." msgid "The hero's defense skill is a bonus added to each unit's defense skill." msgstr "" msgid "The hero's spell power determines the duration or power of a spell." msgstr "" msgid "" " The hero's knowledge determines how many spell points the hero may have. " "Under normal circumstances, a hero is limited to 10 spell points per level " "of knowledge." msgstr "" msgid "Current Modifiers:" msgstr "" msgid "skill|Basic" msgstr "" msgid "skill|Advanced" msgstr "" msgid "skill|Expert" msgstr "" msgid "Pathfinding" msgstr "" msgid "Archery" msgstr "" msgid "Logistics" msgstr "" msgid "Scouting" msgstr "" msgid "Diplomacy" msgstr "" msgid "Navigation" msgstr "" msgid "Leadership" msgstr "" msgid "Wisdom" msgstr "" msgid "Mysticism" msgstr "" msgid "Ballistics" msgstr "" msgid "Eagle Eye" msgstr "" msgid "Necromancy" msgstr "" msgid "Estates" msgstr "" msgid "Advanced Archery" msgstr "" msgid "Advanced Pathfinding" msgstr "" msgid "Basic Archery" msgstr "" msgid "Basic Pathfinding" msgstr "" msgid "Expert Pathfinding" msgstr "" msgid "Advanced Logistics" msgstr "" msgid "Basic Logistics" msgstr "" msgid "Basic Scouting" msgstr "" msgid "Expert Archery" msgstr "" msgid "Expert Logistics" msgstr "" msgid "Advanced Diplomacy" msgstr "" msgid "Advanced Scouting" msgstr "" msgid "Basic Diplomacy" msgstr "" msgid "Expert Diplomacy" msgstr "" msgid "Expert Scouting" msgstr "" msgid "Advanced Leadership" msgstr "" msgid "Advanced Navigation" msgstr "" msgid "Basic Leadership" msgstr "" msgid "Basic Navigation" msgstr "" msgid "Expert Navigation" msgstr "" msgid "Advanced Wisdom" msgstr "" msgid "Basic Mysticism" msgstr "" msgid "Basic Wisdom" msgstr "" msgid "Expert Leadership" msgstr "" msgid "Expert Wisdom" msgstr "" msgid "Advanced Luck" msgstr "" msgid "Advanced Mysticism" msgstr "" msgid "Basic Luck" msgstr "" msgid "Expert Luck" msgstr "" msgid "Expert Mysticism" msgstr "" msgid "Advanced Ballistics" msgstr "" msgid "Advanced Eagle Eye" msgstr "" msgid "Basic Ballistics" msgstr "" msgid "Basic Eagle Eye" msgstr "" msgid "Expert Ballistics" msgstr "" msgid "Advanced Necromancy" msgstr "" msgid "Basic Estates" msgstr "" msgid "Basic Necromancy" msgstr "" msgid "Expert Eagle Eye" msgstr "" msgid "Expert Necromancy" msgstr "" msgid "Advanced Estates" msgstr "" msgid "Expert Estates" msgstr "" msgid "" "%{skill} reduces the movement penalty for rough terrain by %{count} percent." msgstr "" msgid "%{skill} eliminates the movement penalty for rough terrain." msgstr "" msgid "" "%{skill} increases the damage done by the hero's range attacking creatures " "by %{count} percent." msgstr "" msgid "%{skill} increases the hero's movement points by %{count} percent." msgstr "" msgid "%{skill} increases the hero's viewable area by one square." msgid_plural "%{skill} increases the hero's viewable area by %{count} squares." msgstr[0] "" msgstr[1] "" msgid "" "%{skill} allows the hero to negotiate with monsters who are weaker than " "their army, and reduces the cost of surrender." msgstr "" msgid "" "Approximately %{count} percent of the creatures may offer to join the hero." msgstr "" msgid "All of the creatures may offer to join the hero." msgstr "" msgid "" "The cost of surrender is reduced to %{percent} percent of the total cost of " "troops in the army." msgstr "" msgid "" "%{skill} increases the hero's movement points over water by %{count} percent." msgstr "" msgid "%{skill} increases the hero's troops morale by %{count}." msgstr "" msgid "%{skill} allows the hero to learn third level spells." msgstr "" msgid "%{skill} allows the hero to learn fourth level spells." msgstr "" msgid "%{skill} allows the hero to learn fifth level spells." msgstr "" msgid "%{skill} regenerates one additional spell point per day to the hero." msgid_plural "" "%{skill} regenerates %{count} additional spell points per day to the hero." msgstr[0] "" msgstr[1] "" msgid "%{skill} increases the hero's luck by %{count}." msgstr "" msgid "" "%{skill} gives the hero's catapult shots a greater chance to hit and do " "damage to castle walls." msgstr "" msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot has a " "greater chance to hit and do damage to castle walls." msgstr "" msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot " "automatically destroys any wall, except a fortified wall in a Knight castle." msgstr "" msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 1st or " "2nd level spell that was cast by an enemy during combat." msgstr "" msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 3rd " "level spell (or below) that was cast by an enemy during combat." msgstr "" msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 4th " "level spell (or below) that was cast by an enemy during combat." msgstr "" msgid "" "%{skill} allows %{count} percent of the creatures killed in combat to be " "brought back from the dead as Skeletons." msgstr "" msgid "" "The hero produces %{count} gold pieces per day as tax revenue from estates." msgstr "" msgid "Blue" msgstr "" msgid "Green" msgstr "" msgid "Red" msgstr "" msgid "Yellow" msgstr "" msgid "Orange" msgstr "" msgid "Purple" msgstr "" msgid "barrier|Aqua" msgstr "" msgid "barrier|Blue" msgstr "" msgid "barrier|Brown" msgstr "" msgid "barrier|Gold" msgstr "" msgid "barrier|Green" msgstr "" msgid "barrier|Orange" msgstr "" msgid "barrier|Purple" msgstr "" msgid "barrier|Red" msgstr "" msgid "tent|Aqua" msgstr "" msgid "tent|Blue" msgstr "" msgid "tent|Brown" msgstr "" msgid "tent|Gold" msgstr "" msgid "tent|Green" msgstr "" msgid "tent|Orange" msgstr "" msgid "tent|Purple" msgstr "" msgid "tent|Red" msgstr "" msgid "Experience" msgstr "" msgid "" "Experience allows your heroes to go up levels, increasing their primary and " "secondary skills." msgstr "" msgid "Hero/Stats" msgstr "" msgid "Skills" msgstr "" msgid "Town/Castle" msgstr "" msgid "Garrison" msgstr "" msgid "Gold Per Day:" msgstr "" msgid "View Heroes." msgstr "" msgid "Towns/Castles" msgstr "" msgid "View Towns and Castles." msgstr "" msgid "luck|Cursed" msgstr "" msgid "luck|Awful" msgstr "" msgid "luck|Bad" msgstr "" msgid "luck|Normal" msgstr "" msgid "luck|Good" msgstr "" msgid "luck|Great" msgstr "" msgid "luck|Irish" msgstr "" msgid "" "Bad luck sometimes falls on the hero's units in combat, causing their " "attacks to only do half damage." msgstr "" msgid "" "Neutral luck means the hero's units will never get lucky or unlucky attacks " "on the enemy." msgstr "" msgid "" "Good luck sometimes lets the hero's units get lucky attacks (double " "strength) in combat." msgstr "" msgid "morale|Treason" msgstr "" msgid "morale|Awful" msgstr "" msgid "morale|Poor" msgstr "" msgid "morale|Normal" msgstr "" msgid "morale|Good" msgstr "" msgid "morale|Great" msgstr "" msgid "morale|Blood!" msgstr "" #, fuzzy msgid "Bad morale may cause the hero's units to freeze in combat." msgstr "Düşük moral %{monster}'ın panikten donmasına yol açtı." msgid "" "Neutral morale means the hero's units will never be blessed with extra " "attacks or freeze in combat." msgstr "" msgid "Good morale may give the hero's units extra attacks in combat." msgstr "" msgid "Knight" msgstr "" msgid "Barbarian" msgstr "" msgid "Sorceress" msgstr "" msgid "Warlock" msgstr "" msgid "Wizard" msgstr "" msgid "Necromancer" msgstr "" msgid "Multi" msgstr "" msgid "doubleLined|Knight" msgstr "" msgid "doubleLined|Barbarian" msgstr "" msgid "doubleLined|Sorceress" msgstr "" msgid "doubleLined|Warlock" msgstr "" msgid "doubleLined|Wizard" msgstr "" msgid "" "doubleLined|Necro-\n" "mancer" msgstr "" msgid "doubleLinedRace|Multi" msgstr "" msgid "doubleLinedRace|Random" msgstr "" msgid "speed|Standing" msgstr "" msgid "speed|Crawling" msgstr "" msgid "speed|Very Slow" msgstr "" msgid "speed|Slow" msgstr "" msgid "speed|Average" msgstr "" msgid "speed|Fast" msgstr "" msgid "speed|Very Fast" msgstr "" msgid "speed|Ultra Fast" msgstr "" msgid "speed|Blazing" msgstr "" msgid "speed|Instant" msgstr "" msgid "week|Squirrel" msgstr "" msgid "week|Rabbit" msgstr "" msgid "week|Gopher" msgstr "" msgid "week|Badger" msgstr "" msgid "week|Rat" msgstr "" msgid "week|Eagle" msgstr "" msgid "week|Weasel" msgstr "" msgid "week|Raven" msgstr "" msgid "week|Mongoose" msgstr "" msgid "week|Dog" msgstr "" msgid "week|Aardvark" msgstr "" msgid "week|Lizard" msgstr "" msgid "week|Tortoise" msgstr "" msgid "week|Hedgehog" msgstr "" msgid "week|Condor" msgstr "" msgid "week|Ant" msgstr "" msgid "week|Grasshopper" msgstr "" msgid "week|Dragonfly" msgstr "" msgid "week|Spider" msgstr "" msgid "week|Butterfly" msgstr "" msgid "week|Bumblebee" msgstr "" msgid "week|Locust" msgstr "" msgid "week|Earthworm" msgstr "" msgid "week|Hornet" msgstr "" msgid "week|Beetle" msgstr "" msgid "week|PLAGUE" msgstr "" msgid "week|Unnamed" msgstr "" msgid "Desert" msgstr "" msgid "Snow" msgstr "" msgid "Wasteland" msgstr "" msgid "Beach" msgstr "" msgid "Lava" msgstr "" msgid "Dirt" msgstr "" msgid "Grass" msgstr "" msgid "Ocean" msgstr "" msgid "maps|Small" msgstr "" msgid "maps|Medium" msgstr "" msgid "maps|Large" msgstr "" msgid "maps|Extra Large" msgstr "" msgid "maps|Custom Size" msgstr "" msgid "Ore Mine" msgstr "" msgid "Sulfur Mine" msgstr "" msgid "Crystal Mine" msgstr "" msgid "Gems Mine" msgstr "" msgid "Gold Mine" msgstr "" msgid "Mine" msgstr "" msgid "Burma shave." msgstr "" msgid "Next sign 50 miles." msgstr "" msgid "See Rock City." msgstr "" msgid "This space for rent." msgstr "" msgid "No object" msgstr "" msgid "Alchemist Lab" msgstr "" msgid "Sign" msgstr "" msgid "Buoy" msgstr "" msgid "Skeleton" msgstr "" msgid "Daemon Cave" msgstr "" msgid "Treasure Chest" msgstr "" msgid "Faerie Ring" msgstr "" msgid "Campfire" msgstr "" msgid "Fountain" msgstr "" msgid "Gazebo" msgstr "" msgid "Genie Lamp" msgstr "" msgid "Archer's House" msgstr "" msgid "Goblin Hut" msgstr "" msgid "Dwarf Cottage" msgstr "" msgid "Peasant Hut" msgstr "" msgid "Stables" msgstr "" msgid "Alchemist's Tower" msgstr "" msgid "Event" msgstr "" msgid "Dragon City" msgstr "" msgid "Lighthouse" msgid_plural "Lighthouses" msgstr[0] "" msgstr[1] "" msgid "Water Wheel" msgid_plural "Water Wheels" msgstr[0] "" msgstr[1] "" msgid "Monster" msgstr "" msgid "Obelisk" msgstr "" msgid "Oasis" msgstr "" msgid "Resource" msgstr "" msgid "Sawmill" msgstr "" msgid "Oracle" msgstr "" msgid "Shrine of the First Circle" msgstr "" msgid "Shipwreck" msgstr "" msgid "Sea Chest" msgstr "" msgid "Desert Tent" msgstr "" msgid "Stone Liths" msgstr "" msgid "Wagon Camp" msgstr "" msgid "Hut of the Magi" msgstr "" msgid "Whirlpool" msgstr "" msgid "Windmill" msgid_plural "Windmills" msgstr[0] "" msgstr[1] "" msgid "Artifact" msgstr "" msgid "Mermaid" msgstr "" msgid "Boat" msgstr "" msgid "Random Ultimate Artifact" msgstr "" msgid "Random Artifact" msgstr "" msgid "Random Resource" msgstr "" msgid "Random Monster" msgstr "" msgid "Random Town" msgstr "" msgid "Random Castle" msgstr "" msgid "Eye of the Magi" msgstr "" msgid "Random Monster - weak" msgstr "" msgid "Random Monster - medium" msgstr "" msgid "Random Monster - strong" msgstr "" msgid "Random Monster - very strong" msgstr "" msgid "Hero" msgstr "" msgid "Nothing Special" msgstr "" msgid "Mossy Rock" msgstr "" msgid "Watch Tower" msgstr "" msgid "Tree House" msgstr "" msgid "Tree City" msgstr "" msgid "Ruins" msgstr "" msgid "Fort" msgstr "" msgid "Abandoned Mine" msgstr "" msgid "Sirens" msgstr "" msgid "Standing Stones" msgstr "" msgid "Idol" msgstr "" msgid "Tree of Knowledge" msgstr "" msgid "Witch Doctor's Hut" msgstr "" msgid "Temple" msgstr "" msgid "Hill Fort" msgstr "" msgid "Halfling Hole" msgstr "" msgid "Mercenary Camp" msgstr "" msgid "Shrine of the Second Circle" msgstr "" msgid "Shrine of the Third Circle" msgstr "" msgid "City of the Dead" msgstr "" msgid "Sphinx" msgstr "" msgid "Wagon" msgstr "" msgid "Tar Pit" msgstr "" msgid "Artesian Spring" msgstr "" msgid "Troll Bridge" msgstr "" msgid "Watering Hole" msgstr "" msgid "Witch's Hut" msgstr "" msgid "Xanadu" msgstr "" msgid "Lean-To" msgstr "" msgid "Magellan's Maps" msgstr "" msgid "Flotsam" msgstr "" msgid "Derelict Ship" msgstr "" msgid "Shipwreck Survivor" msgstr "" msgid "Bottle" msgstr "" msgid "Magic Well" msgstr "" msgid "Magic Garden" msgid_plural "Magic Gardens" msgstr[0] "" msgstr[1] "" msgid "Observation Tower" msgstr "" msgid "Freeman's Foundry" msgstr "" msgid "Reefs" msgstr "" msgid "Volcano" msgstr "" msgid "Flowers" msgstr "" msgid "Rock" msgstr "" msgid "Water Lake" msgstr "" msgid "Mandrake" msgstr "" msgid "Dead Tree" msgstr "" msgid "Stump" msgstr "" msgid "Crater" msgstr "" msgid "Cactus" msgstr "" msgid "Mound" msgstr "" msgid "Dune" msgstr "" msgid "Lava Pool" msgstr "" msgid "Shrub" msgstr "" msgid "Barrow Mounds" msgstr "" msgid "Random Artifact - Treasure" msgstr "" msgid "Random Artifact - Minor" msgstr "" msgid "Random Artifact - Major" msgstr "" msgid "Barrier" msgstr "" msgid "Traveller's Tent" msgstr "" msgid "Jail" msgstr "" msgid "Fire Summoning Altar" msgstr "" msgid "Air Summoning Altar" msgstr "" msgid "Earth Summoning Altar" msgstr "" msgid "Water Summoning Altar" msgstr "" msgid "Unknown Monster" msgstr "" msgid "Unknown Monsters" msgstr "" msgid "Peasant" msgstr "" msgid "Peasants" msgstr "" msgid "Archer" msgstr "" msgid "Archers" msgstr "" msgid "Ranger" msgstr "" msgid "Rangers" msgstr "" msgid "Pikeman" msgstr "" msgid "Pikemen" msgstr "" msgid "Veteran Pikeman" msgstr "" msgid "Veteran Pikemen" msgstr "" msgid "Swordsman" msgstr "" msgid "Swordsmen" msgstr "" msgid "Master Swordsman" msgstr "" msgid "Master Swordsmen" msgstr "" msgid "Cavalries" msgstr "" msgid "Cavalry" msgstr "" msgid "Champion" msgstr "" msgid "Champions" msgstr "" msgid "Paladin" msgstr "" msgid "Paladins" msgstr "" msgid "Crusader" msgstr "" msgid "Crusaders" msgstr "" msgid "Goblin" msgstr "" msgid "Goblins" msgstr "" msgid "Orc" msgstr "" msgid "Orcs" msgstr "" msgid "Orc Chief" msgstr "" msgid "Orc Chiefs" msgstr "" msgid "Wolf" msgstr "" msgid "Wolves" msgstr "" msgid "Ogre" msgstr "" msgid "Ogres" msgstr "" msgid "Ogre Lord" msgstr "" msgid "Ogre Lords" msgstr "" msgid "Troll" msgstr "" msgid "Trolls" msgstr "" msgid "War Troll" msgstr "" msgid "War Trolls" msgstr "" msgid "Cyclopes" msgstr "" msgid "Cyclops" msgstr "" msgid "Sprite" msgstr "" msgid "Sprites" msgstr "" msgid "Dwarf" msgstr "" msgid "Dwarves" msgstr "" msgid "Battle Dwarf" msgstr "" msgid "Battle Dwarves" msgstr "" msgid "Elf" msgstr "" msgid "Elves" msgstr "" msgid "Grand Elf" msgstr "" msgid "Grand Elves" msgstr "" msgid "Druid" msgstr "" msgid "Druids" msgstr "" msgid "Greater Druid" msgstr "" msgid "Greater Druids" msgstr "" msgid "Unicorn" msgstr "" msgid "Unicorns" msgstr "" msgid "Phoenix" msgstr "" msgid "Phoenixes" msgstr "" msgid "Centaur" msgstr "" msgid "Centaurs" msgstr "" msgid "Gargoyle" msgstr "" msgid "Gargoyles" msgstr "" msgid "Griffin" msgstr "" msgid "Griffins" msgstr "" msgid "Minotaur" msgstr "" msgid "Minotaurs" msgstr "" msgid "Minotaur King" msgstr "" msgid "Minotaur Kings" msgstr "" msgid "Hydra" msgstr "" msgid "Hydras" msgstr "" msgid "Green Dragon" msgstr "" msgid "Green Dragons" msgstr "" msgid "Red Dragon" msgstr "" msgid "Red Dragons" msgstr "" msgid "Black Dragon" msgstr "" msgid "Black Dragons" msgstr "" msgid "Halfling" msgstr "" msgid "Halflings" msgstr "" msgid "Boar" msgstr "" msgid "Boars" msgstr "" msgid "Iron Golem" msgstr "" msgid "Iron Golems" msgstr "" msgid "Steel Golem" msgstr "" msgid "Steel Golems" msgstr "" msgid "Roc" msgstr "" msgid "Rocs" msgstr "" msgid "Mage" msgstr "" msgid "Magi" msgstr "" msgid "Archmage" msgstr "" msgid "Archmagi" msgstr "" msgid "Giant" msgstr "" msgid "Giants" msgstr "" msgid "Titan" msgstr "" msgid "Titans" msgstr "" msgid "Skeletons" msgstr "" msgid "Zombie" msgstr "" msgid "Zombies" msgstr "" msgid "Mutant Zombie" msgstr "" msgid "Mutant Zombies" msgstr "" msgid "Mummies" msgstr "" msgid "Mummy" msgstr "" msgid "Royal Mummies" msgstr "" msgid "Royal Mummy" msgstr "" msgid "Vampire" msgstr "" msgid "Vampires" msgstr "" msgid "Vampire Lord" msgstr "" msgid "Vampire Lords" msgstr "" msgid "Lich" msgstr "" msgid "Liches" msgstr "" msgid "Power Lich" msgstr "" msgid "Power Liches" msgstr "" msgid "Bone Dragon" msgstr "" msgid "Bone Dragons" msgstr "" msgid "Rogue" msgstr "" msgid "Rogues" msgstr "" msgid "Nomad" msgstr "" msgid "Nomads" msgstr "" msgid "Ghost" msgstr "" msgid "Ghosts" msgstr "" msgid "Genie" msgstr "" msgid "Genies" msgstr "" msgid "Medusa" msgstr "" msgid "Medusas" msgstr "" msgid "Earth Elemental" msgstr "" msgid "Earth Elementals" msgstr "" msgid "Air Elemental" msgstr "" msgid "Air Elementals" msgstr "" msgid "Fire Elemental" msgstr "" msgid "Fire Elementals" msgstr "" msgid "Water Elemental" msgstr "" msgid "Water Elementals" msgstr "" msgid "Random Monsters" msgstr "" msgid "Random Monster 1" msgstr "" msgid "Random Monsters 1" msgstr "" msgid "Random Monster 2" msgstr "" msgid "Random Monsters 2" msgstr "" msgid "Random Monster 3" msgstr "" msgid "Random Monsters 3" msgstr "" msgid "Random Monster 4" msgstr "" msgid "Random Monsters 4" msgstr "" msgid "Double shot" msgstr "" msgid "2-hex monster" msgstr "" msgid "Double strike" msgstr "" msgid "Double damage to Undead" msgstr "" msgid "% magic resistance" msgstr "" msgid "Immune to Mind spells" msgstr "" msgid "Immune to Elemental spells" msgstr "" msgid "Immune to Fire spells" msgstr "" msgid "Immune to Cold spells" msgstr "" msgid "Immune to " msgstr "" msgid "% immunity to %{spell} spell" msgstr "" msgid "% damage from Elemental spells" msgstr "" msgid "% chance to Dispel beneficial spells" msgstr "" msgid "% chance to Paralyze" msgstr "" msgid "% chance to Petrify" msgstr "" msgid "% chance to Blind" msgstr "" msgid "% chance to Curse" msgstr "" msgid "% chance to cast %{spell} spell" msgstr "" msgid "HP regeneration" msgstr "" msgid "Two hexes attack" msgstr "" msgid "Flyer" msgstr "" msgid "Always retaliates" msgstr "" msgid "Attacks all adjacent enemies" msgstr "" msgid "No melee penalty" msgstr "" msgid "Dragon" msgstr "" msgid "Undead" msgstr "" msgid "No enemy retaliation" msgstr "" msgid "HP drain" msgstr "" msgid "Cloud attack" msgstr "" msgid "Decreases enemy's morale by " msgstr "" msgid "% chance to halve enemy" msgstr "" msgid "Soul Eater" msgstr "" msgid "Elemental" msgstr "" msgid "No Morale" msgstr "" msgid "200% damage from Fire spells" msgstr "" msgid "200% damage from Cold spells" msgstr "" msgid "% damage from %{spell} spell" msgstr "" msgid "% immunity to " msgstr "" msgid "Lightning" msgstr "" msgid "% damage from " msgstr "" msgid "The three Anduran artifacts magically combine into one." msgstr "" msgid "View Spells" msgstr "" msgid "View %{name} Info" msgstr "" msgid "Move %{name}" msgstr "" msgid "Cannot move the Spellbook" msgstr "" msgid "This item can't be traded." msgstr "" msgid "Invalid Artifact" msgstr "" #, fuzzy msgid "The %{name} increases the hero's knowledge by %{count}." msgstr "%{name} %{count} Okçu gücüyle ateÅŸ ediyor" msgid "Ultimate Book of Knowledge" msgstr "" #, fuzzy msgid "The %{name} increases the hero's attack skill by %{count}." msgstr "%{name} %{count} Okçu gücüyle ateÅŸ ediyor" msgid "Ultimate Sword of Dominion" msgstr "" #, fuzzy msgid "The %{name} increases the hero's defense skill by %{count}." msgstr "%{name} %{count} Okçu gücüyle ateÅŸ ediyor" msgid "Ultimate Cloak of Protection" msgstr "" #, fuzzy msgid "The %{name} increases the hero's spell power by %{count}." msgstr "%{name} %{count} Okçu gücüyle ateÅŸ ediyor" msgid "Ultimate Wand of Magic" msgstr "" msgid "" "The %{name} increases the hero's attack and defense skills by %{count} each." msgstr "" msgid "Ultimate Shield" msgstr "" #, fuzzy msgid "" "The %{name} increases the hero's spell power and knowledge by %{count} each." msgstr "%{name} %{count} Okçu gücüyle ateÅŸ ediyor" msgid "Ultimate Staff" msgstr "" msgid "" "The %{name} increases each of the hero's basic skills by %{count} points." msgstr "" msgid "Ultimate Crown" msgstr "" msgid "Golden Goose" msgstr "" msgid "The %{name} brings in an income of %{count} gold per day." msgstr "" msgid "Arcane Necklace of Magic" msgstr "" msgid "" "After rescuing a Sorceress from a cursed tomb, she rewards your heroism with " "an exquisite jeweled necklace." msgstr "" msgid "Caster's Bracelet of Magic" msgstr "" msgid "" "While searching through the rubble of a caved-in mine, you free a group of " "trapped Dwarves. Grateful, the leader gives you a golden bracelet." msgstr "" msgid "Mage's Ring of Power" msgstr "" msgid "" "A cry of pain leads you to a Centaur, caught in a trap. Upon setting the " "creature free, he hands you a small pouch. Emptying the contents, you find a " "dazzling jeweled ring." msgstr "" msgid "Witch's Broach of Magic" msgstr "" msgid "" "Alongside the remains of a burnt witch lies a beautiful broach, intricately " "designed. Approaching the corpse with caution, you add the broach to your " "inventory." msgstr "" msgid "Medal of Valor" msgstr "" #, fuzzy msgid "The %{name} increases the morale of the hero's army by %{count}." msgstr "%{name} %{count} Okçu gücüyle ateÅŸ ediyor" msgid "" "Freeing a virtuous maiden from the clutches of an evil overlord, you are " "granted a Medal of Valor by the King's herald." msgstr "" msgid "Medal of Courage" msgstr "" msgid "" "After saving a young boy from a vicious pack of Wolves, you return him to " "his father's manor. The grateful nobleman awards you with a Medal of Courage." msgstr "" msgid "Medal of Honor" msgstr "" msgid "" "After freeing a princess of a neighboring kingdom from the evil clutches of " "despicable slavers, she awards you with a Medal of Honor." msgstr "" msgid "Medal of Distinction" msgstr "" msgid "" "Ridding the countryside of the hideous Minotaur who made a sport of eating " "noblemen's Knights, you are honored with the Medal of Distinction." msgstr "" msgid "Fizbin of Misfortune" msgstr "" msgid "" "The %{name} greatly decreases the morale of the hero's army by %{count}." msgstr "" msgid "" "You stumble upon a medal lying alongside the empty road. Adding the medal to " "your inventory, you become aware that you have acquired the undesirable " "Fizbin of Misfortune, greatly decreasing your army's morale." msgstr "" msgid "Thunder Mace of Dominion" msgstr "" msgid "" "During a sudden storm, a bolt of lightning strikes a tree, splitting it. " "Inside the tree you find a mysterious mace." msgstr "" msgid "Armored Gauntlets of Protection" msgstr "" #, fuzzy msgid "The %{name} increase the hero's defense skill by %{count}." msgstr "%{name} %{count} Okçu gücüyle ateÅŸ ediyor" msgid "" "You encounter the infamous Black Knight! After a grueling duel ending in a " "draw, the Knight, out of respect, offers you a pair of armored gauntlets." msgstr "" msgid "Defender Helm of Protection" msgstr "" msgid "" "A glint of golden light catches your eye. Upon further investigation, you " "find a golden helm hidden under a bush." msgstr "" msgid "Giant Flail of Dominion" msgstr "" msgid "" "A clumsy Giant has killed himself with his own flail. Knowing your superior " "skill with this weapon, you confidently remove the spectacular flail from " "the fallen Giant." msgstr "" msgid "Ballista of Quickness" msgstr "" msgid "The %{name} gives the hero's catapult one extra shot per combat round." msgstr "" msgid "" "Walking through the ruins of an ancient walled city, you find the instrument " "of the city's destruction, an elaborately crafted ballista." msgstr "" msgid "Stealth Shield of Protection" msgstr "" msgid "" "A stone statue of a warrior holds a silver shield. As you remove the shield, " "the statue crumbles into dust." msgstr "" msgid "Dragon Sword of Dominion" msgstr "" msgid "" "As you are walking along a narrow path, a nearby bush suddenly bursts into " "flames. Before your eyes the flames become the image of a beautiful woman. " "She holds out a magnificent sword to you." msgstr "" msgid "Power Axe of Dominion" msgstr "" msgid "" "You see a silver axe embedded deeply in the ground. After several " "unsuccessful attempts by your army to remove the axe, you tightly grip the " "handle of the axe and effortlessly pull it free." msgstr "" msgid "Divine Breastplate of Protection" msgstr "" msgid "" "A gang of Rogues is sifting through the possessions of dead warriors. " "Scaring off the scavengers, you note the Rogues had overlooked a beautiful " "breastplate." msgstr "" msgid "Minor Scroll of Knowledge" msgstr "" msgid "" "Before you appears a levitating glass case with a scroll, perched upon a bed " "of crimson velvet. At your touch, the lid opens and the scroll floats into " "your awaiting hands." msgstr "" msgid "Major Scroll of Knowledge" msgstr "" msgid "" "Visiting a local wiseman, you explain the intent of your journey. He reaches " "into a sack and withdraws a yellowed scroll and hands it to you." msgstr "" msgid "Superior Scroll of Knowledge" msgstr "" msgid "" "You come across the remains of an ancient Druid. Bones, yellowed with age, " "peer from the ragged folds of her robe. Searching the robe, you discover a " "scroll hidden in the folds." msgstr "" msgid "Foremost Scroll of Knowledge" msgstr "" msgid "" "Mangled bones, yellowed with age, peer from the ragged folds of a dead " "Druid's robe. Searching the robe, you discover a scroll hidden within." msgstr "" msgid "Endless Sack of Gold" msgstr "" #, fuzzy msgid "The %{name} provides the hero with %{count} gold per day." msgstr "%{name} %{count} Okçu gücüyle ateÅŸ ediyor" msgid "" "A little leprechaun dances gleefully around a magic sack. Seeing you " "approach, he stops in mid-stride. The little man screams and stamps his foot " "ferociously, vanishing into thin air. Remembering the old leprechaun saying " "'Finders Keepers', you grab the sack and leave." msgstr "" msgid "Endless Bag of Gold" msgstr "" msgid "" "A noblewoman, separated from her traveling companions, asks for your help. " "After escorting her home, she rewards you with a bag filled with gold." msgstr "" msgid "Endless Purse of Gold" msgstr "" msgid "" "In your travels, you find a leather purse filled with gold that once " "belonged to a great warrior king who had the ability to transform any " "inanimate object into gold." msgstr "" msgid "Nomad Boots of Mobility" msgstr "" msgid "The %{name} increase the hero's movement on land." msgstr "" msgid "" "A Nomad trader seeks protection from a tribe of Goblins. For your " "assistance, he gives you a finely crafted pair of boots made from the " "softest leather. Looking closely, you see fascinating ancient carvings " "engraved on the leather." msgstr "" msgid "Traveler's Boots of Mobility" msgstr "" msgid "" "Discovering a pair of beautifully beaded boots made from the finest and " "softest leather, you thank the anonymous donor and add the boots to your " "inventory." msgstr "" msgid "Lucky Rabbit's Foot" msgstr "" #, fuzzy msgid "The %{name} increases the luck of the hero's army by %{count}." msgstr "%{name} %{count} Okçu gücüyle ateÅŸ ediyor" msgid "" "A traveling merchant offers you a rabbit's foot, made of gleaming silver " "fur, for safe passage. The merchant explains the charm will increase your " "luck in combat." msgstr "" msgid "Golden Horseshoe" msgstr "" msgid "" "An ensnared Unicorn whinnies in fright. Murmuring soothing words, you set " "her free. Snorting and stamping her front hoof once, she gallops off. " "Looking down you see a golden horseshoe." msgstr "" msgid "Gambler's Lucky Coin" msgstr "" msgid "" "You have captured a mischievous imp who has been terrorizing the region. In " "exchange for his release, he rewards you with a magical coin." msgstr "" msgid "Four-Leaf Clover" msgstr "" msgid "" "In the middle of a patch of dead and dry vegetation, to your surprise you " "find a healthy green four-leaf clover." msgstr "" msgid "True Compass of Mobility" msgstr "" msgid "The %{name} increases the hero's movement on land and sea." msgstr "" msgid "" "An old man claiming to be an inventor asks you to try his latest invention. " "He then hands you a compass." msgstr "" msgid "Sailor's Astrolabe of Mobility" msgstr "" msgid "The %{name} increases the hero's movement on sea." msgstr "" msgid "" "An old sea captain is being tortured by Ogres. You save him, and in return " "he rewards you with a wondrous instrument to measure the distance of a star." msgstr "" msgid "Evil Eye" msgstr "" msgid "The %{name} reduces the casting cost of curse spells by half." msgstr "" msgid "" "While venturing into a decrepit hut you find the Skeleton of a long dead " "witch. Investigation of the remains reveals a glass eye rolling around " "inside an empty skull." msgstr "" msgid "Enchanted Hourglass" msgstr "" msgid "" "The %{name} extends the duration of all the hero's spells by %{count} turns." msgstr "" msgid "" "A surprise turn in the landscape finds you in the midst of a grisly scene: " "Vultures picking at the aftermath of a terrible battle. Your cursory search " "of the remains turns up an enchanted hourglass." msgstr "" msgid "Gold Watch" msgstr "" msgid "The %{name} doubles the effectiveness of the hero's hypnotize spells." msgstr "" msgid "" "In reward for helping his cart out of a ditch, a traveling potion salesman " "gives you a \"magic\" gold watch. Unbeknownst to him, the watch really is " "magical." msgstr "" msgid "Skullcap" msgstr "" msgid "The %{name} halves the casting cost of all mind influencing spells." msgstr "" msgid "" "A brief stop at an improbable rural inn yields an exchange of money, tales, " "and accidentally, luggage. You find a magical skullcap in your new backpack." msgstr "" msgid "Ice Cloak" msgstr "" msgid "" "The %{name} halves all damage the hero's troops receive from cold spells." msgstr "" msgid "" "Responding to the panicked cries of a damsel in distress, you discover a " "young woman fleeing from a hungry bear. You slay the beast in the nick of " "time, and the grateful Sorceress weaves a magic cloak from the bear's hide." msgstr "" msgid "Fire Cloak" msgstr "" msgid "" "The %{name} halves all damage the hero's troops receive from fire spells." msgstr "" msgid "" "You've come upon a fight between a Necromancer and a Paladin. The " "Necromancer blasts the Paladin with a fire bolt, bringing him to his knees. " "Acting quickly, you slay the evil one before the final blow. The grateful " "Paladin gives you the fire cloak that saved him." msgstr "" msgid "Lightning Helm" msgstr "" msgid "" "The %{name} halves all damage the hero's troops receive from lightning " "spells." msgstr "" msgid "" "A traveling tinker in need of supplies offers you a helm with a thunderbolt " "design on its top in exchange for food and water. Curious, you accept, and " "later find out that the helm is magical." msgstr "" msgid "Evercold Icicle" msgstr "" msgid "" "The %{name} causes the hero's cold spells to do %{count} percent more damage " "to enemy troops." msgstr "" msgid "" "An icicle withstanding the full heat of the noonday sun attracts your " "attention. Intrigued, you break it off, and find that it does not melt in " "your hand." msgstr "" msgid "Everhot Lava Rock" msgstr "" msgid "" "The %{name} causes the hero's fire spells to do %{count} percent more damage " "to enemy troops." msgstr "" msgid "" "Your wanderings bring you into contact with a tribe of ape-like beings using " "a magical lava rock that never cools to light their fires. You take pity on " "them and teach them to make fire with sticks. Believing you to be a god, the " "apes give you their rock." msgstr "" msgid "Lightning Rod" msgstr "" msgid "" "The %{name} causes the hero's lightning spells to do %{count} percent more " "damage to enemy troops." msgstr "" msgid "" "While waiting out a storm, a lighting bolt strikes a nearby cottage's " "lightning rod, which melts and falls to the ground. The tip of the rod, " "however, survives intact and makes your hair stand on end when you touch it. " "Hmm..." msgstr "" msgid "Snake-Ring" msgstr "" msgid "The %{name} halves the casting cost of all of the hero's bless spells." msgstr "" msgid "" "You've found an oddly shaped ring on the finger of a long dead traveler. The " "ring looks like a snake biting its own tail." msgstr "" msgid "Ankh" msgstr "" msgid "" "The %{name} doubles the effectiveness of all of the hero's resurrect and " "animate spells." msgstr "" msgid "" "A fierce windstorm reveals the entrance to a buried tomb. Your investigation " "reveals that the tomb has already been looted, but the thieves overlooked an " "ankh on a silver chain in the dark." msgstr "" msgid "Book of Elements" msgstr "" msgid "" "The %{name} doubles the effectiveness of all of the hero's summoning spells." msgstr "" msgid "" "You come across a conjurer who begs to accompany you and your army awhile " "for safety. You agree, and he offers as payment a copy of the book of the " "elements." msgstr "" msgid "Elemental Ring" msgstr "" msgid "The %{name} halves the casting cost of all summoning spells." msgstr "" msgid "" "While pausing to rest, you notice a bobcat climbing a short tree to get at a " "crow's nest. On impulse, you climb the tree yourself and scare off the cat. " "When you look in the nest, you find a collection of shiny stones and a ring." msgstr "" msgid "Holy Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to curse spells." msgstr "" msgid "" "In your wanderings you come across a hermit living in a small, tidy hut. " "Impressed with your mission, he takes time out from his meditations to bless " "and give you a charm against curses." msgstr "" msgid "Pendant of Free Will" msgstr "" msgid "The %{name} makes all of the hero's troops immune to hypnotize spells." msgstr "" msgid "" "Responding to cries for help, you find river Sprites making a sport of " "dunking an old man. Feeling vengeful, you rescue the man and drag a Sprite " "onto dry land for awhile. The Sprite, uncomfortable in the air, gives you a " "magic pendant to let him go." msgstr "" msgid "Pendant of Life" msgstr "" msgid "The %{name} makes all of the hero's troops immune to death spells." msgstr "" msgid "" "A brief roadside encounter with a small caravan and a game of knucklebones " "wins a magic pendant. Its former owner says that it protects from " "Necromancers' death spells." msgstr "" msgid "Serenity Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to berserk spells." msgstr "" msgid "" "The sounds of combat draw you to the scene of a fight between an old " "Barbarian and an eight-headed Hydra. Your timely intervention swings the " "battle in favor of the man, and he rewards you with a pendant he used to use " "to calm his mind for battle." msgstr "" msgid "Seeing-eye Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to blindness spells." msgstr "" msgid "" "You come upon a very old woman, long blind from cataracts and dying alone. " "You tend to her final needs and promise a proper burial. Grateful, she gives " "you a magic pendant emblazoned with a stylized eye. It lets you see with " "your eyes closed." msgstr "" msgid "Kinetic Pendant" msgstr "" msgid "The %{name} makes all of the hero's troops immune to paralyze spells." msgstr "" msgid "" "You come across a golem wearing a glowing pendant and blocking your way. " "Acting on a hunch, you cut the pendant from its neck. Deprived of its power " "source, the golem breaks down, leaving you with the magical pendant." msgstr "" msgid "Pendant of Death" msgstr "" msgid "The %{name} makes all of the hero's troops immune to holy spells." msgstr "" msgid "" "A quick and deadly battle with a Necromancer wins you his magical pendant. " "Later, a Wizard tells you that the pendant protects undead under your " "control from holy word spells." msgstr "" msgid "Wand of Negation" msgstr "" msgid "" "The %{name} makes all of the hero's troops immune to dispel magic spells." msgstr "" msgid "" "You meet an old Wizard friend of yours traveling in the opposite direction. " "He presents you with a gift: A wand that prevents the use of the dispel " "magic spell on your allies." msgstr "" msgid "Golden Bow" msgstr "" msgid "" "The %{name} eliminates the %{count} percent penalty for the hero's troops " "shooting past obstacles (e.g. castle walls)." msgstr "" msgid "" "A chance meeting with a famous Archer finds you in a game of knucklebones " "pitting his bow against your horse. You win." msgstr "" msgid "Telescope" msgstr "" msgid "" "The %{name} increases the amount of terrain the hero reveals when " "adventuring by %{count} extra square." msgstr "" msgid "" "A merchant from far away lands trades you a new invention of his people for " "traveling supplies. It makes distant objects appear closer, and he calls " "it...\n" "\n" "a telescope." msgstr "" msgid "Statesman's Quill" msgstr "" msgid "" "The %{name} reduces the cost of surrender to %{count} percent of the total " "cost of troops the hero has in their army." msgstr "" msgid "" "You pause to help a diplomat with a broken axle fix his problem. In " "gratitude, he gives you a writing quill with magical properties which he " "says will \"help people see things your way\"." msgstr "" msgid "Wizard's Hat" msgstr "" #, fuzzy msgid "" "The %{name} increases the duration of the hero's spells by %{count} turns." msgstr "%{name} %{count} Okçu gücüyle ateÅŸ ediyor" msgid "" "You see a Wizard fleeing from a Griffin and riding like the wind. The Wizard " "opens a portal and rides through, getting his hat knocked off by the edge of " "the gate. The Griffin follows; the gate closes. You pick the hat up, dust it " "off, and put it on." msgstr "" msgid "Power Ring" msgstr "" msgid "The %{name} returns %{count} extra spell points per day to the hero." msgstr "" msgid "" "You find a small tree that closely resembles the great Warlock Carnauth with " "a ring around one of its twigs. Scraps of clothing and rotting leather lead " "you to suspect that it IS Carnauth, transformed. Since you can't help him, " "you take the magic ring." msgstr "" msgid "Ammo Cart" msgstr "" msgid "" "The %{name} provides endless ammunition for all of the hero's troops that " "shoot." msgstr "" msgid "" "An ammunition cart in the middle of an old battlefield catches your eye. " "Inspection shows it to be in good working order, so you take it along." msgstr "" msgid "Tax Lien" msgstr "" #, fuzzy msgid "The %{name} costs the hero %{count} gold pieces per day." msgstr "%{name} %{count} Okçu gücüyle ateÅŸ ediyor" msgid "" "Your big spending habits have earned you a massive tax bill that you can't " "hope to pay. The tax man takes pity and agrees to only take 250 gold a day " "from your account for life. Check here if you want one dollar to go to the " "presidential campaign election fund." msgstr "" msgid "Hideous Mask" msgstr "" msgid "The %{name} prevents all 'wandering' armies from joining the hero." msgstr "" msgid "" "Your looting of the grave of Sinfilas Gardolad, the famous shapeshifting " "Warlock, unearths his fabled mask. Trembling, you put it on and it twists " "your visage into an awful grimace! Oh no! It's actually the hideous mask of " "Gromluck Greene, and you are stuck with it." msgstr "" msgid "Endless Pouch of Sulfur" msgstr "" msgid "The %{name} provides %{count} unit of sulfur per day." msgstr "" msgid "" "You visit an alchemist who, upon seeing your army, is swayed by the " "righteousness of your cause. The newly loyal subject gives you his endless " "pouch of sulfur to help with the war effort." msgstr "" msgid "Endless Vial of Mercury" msgstr "" msgid "The %{name} provides %{count} unit of mercury per day." msgstr "" msgid "" "A brief stop at a hastily abandoned Wizard's tower turns up a magical vial " "of mercury that always has a little left on the bottom. Recognizing a " "treasure when you see one, you cap it and slip it in your pocket." msgstr "" msgid "Endless Pouch of Gems" msgstr "" msgid "The %{name} provides %{count} unit of gems per day." msgstr "" msgid "" "A short rainstorm brings forth a rainbow...and you can see the end of it. " "Riding quickly, you seize the pot of gold you find there. The leprechaun who " "owns it, unable to stop you from taking it, offers an endless pouch of gems " "for the return of his gold. You accept." msgstr "" msgid "Endless Cord of Wood" msgstr "" msgid "The %{name} provides %{count} unit of wood per day." msgstr "" msgid "" "Pausing to rest and light a cook fire, you pull wood out of a nearby pile of " "dead wood. As you keep pulling wood from the pile, you notice that it " "doesn't shrink. You realize to your delight that the wood is enchanted, so " "you take it along." msgstr "" msgid "Endless Cart of Ore" msgstr "" msgid "The %{name} provides %{count} unit of ore per day." msgstr "" msgid "" "You've found a Goblin weapon smithy making weapons for use against humans. " "With a tremendous yell you and your army descend upon their camp and drive " "them away. A search finds a magic ore cart that never runs out of iron." msgstr "" msgid "Endless Pouch of Crystal" msgstr "" msgid "The %{name} provides %{count} unit of crystal per day." msgstr "" msgid "" "Taking shelter from a storm in a small cave, you notice a small patch of " "crystal in one corner. Curious, you break a piece off and notice that the " "original crystal grows the lost piece back. You decide to stuff the entire " "patch into a pouch and take it with you." msgstr "" msgid "Spiked Helm" msgstr "" msgid "" "Your army is ambushed by a small tribe of wild (and none too bright) Orcs. " "You fend them off easily and the survivors flee in all directions. One of " "the Orcs was wearing a polished spiked helm. Figuring it will make a good " "souvenir, you take it." msgstr "" msgid "Spiked Shield" msgstr "" msgid "" "You come upon a bridge spanning a dry gully. Before you can cross, a Troll " "steps out from under the bridge and demands payment before it will permit " "you to pass. You refuse, and the Troll charges, forcing you to slay it. You " "take its spiked shield as a trophy." msgstr "" msgid "White Pearl" msgstr "" msgid "" "A walk across a dry saltwater lake bed yields an unlikely prize: A white " "pearl amidst shattered shells and debris." msgstr "" msgid "Black Pearl" msgstr "" msgid "" "Rumors of a Griffin of unusual size preying upon the countryside lead you to " "its cave lair. A quick, brutal fight dispatches the beast, and a search of " "its foul nest turns up a huge black pearl." msgstr "" msgid "Magic Book" msgstr "" #, fuzzy msgid "The %{name} enables the hero to cast spells." msgstr "%{name} büyüye direndi!" msgid "Dummy 1" msgstr "" msgid "The reserved artifact." msgstr "" msgid "Dummy 2" msgstr "" msgid "Dummy 3" msgstr "" msgid "Dummy 4" msgstr "" msgid "Spell Scroll" msgstr "" msgid "" "This %{name} gives the hero the ability to cast the %{spell} spell if the " "hero has a Magic Book." msgstr "" msgid "" "You find an elaborate container which houses an old vellum scroll. The runes " "on the container are very old, and the artistry with which it was put " "together is stunning. As you pull the scroll out, you feel imbued with " "magical power." msgstr "" msgid "Arm of the Martyr" msgstr "" msgid "" "The %{name} increases the hero's spell power by %{count} but adds the undead " "morale penalty." msgstr "" msgid "" "One of the less intelligent members of your party picks up an arm off of the " "ground. Despite its missing a body, it is still moving. Your troops find the " "dismembered arm repulsive, but you cannot bring yourself to drop it: it " "seems to hold some sort of magical power that influences your decision " "making." msgstr "" msgid "Breastplate of Anduran" msgstr "" #, fuzzy msgid "The %{name} increases the hero's defense by %{count}." msgstr "%{name} %{count} Okçu gücüyle ateÅŸ ediyor" msgid "" "You come upon a sign. It reads: \"Here lies the body of Anduran. Bow and " "swear fealty, and you shall be rewarded.\" You decide to do as it says. As " "you stand up, you feel a coldness against your skin. Looking down, you find " "that you are suddenly wearing a gleaming, ornate breastplate." msgstr "" msgid "Broach of Shielding" msgstr "" msgid "" "The %{name} provides %{count} percent protection from Armageddon and " "Elemental Storm, but decreases spell power by 2." msgstr "" msgid "" "A kindly Sorceress thinks that your army's defenses could use a magical " "boost. She offers to enchant the Broach that you wear on your cloak, and you " "accept." msgstr "" msgid "Battle Garb of Anduran" msgstr "" msgid "" "The %{name} combines the powers of the three Anduran artifacts. It provides " "maximum luck and morale for the hero's troops and gives the hero the Town " "Portal spell." msgstr "" msgid "" "Out of pity for a poor peasant, you purchase a chest of old junk they are " "hawking for too much gold. Later, as you search through it, you find it " "contains the 3 pieces of the legendary battle garb of Anduran!" msgstr "" msgid "Crystal Ball" msgstr "" msgid "" "The %{name} lets the hero get more specific information about monsters, " "enemy heroes, and castles nearby the hero." msgstr "" msgid "" "You come upon a caravan of gypsies who are feasting and fortifying their " "bodies with mead. They call you forward and say \"If you prove that you can " "dance the Rama-Buta, we will reward you.\" You don't know it, but try " "anyway. They laugh hysterically, but admire your bravery, giving you a " "Crystal Ball." msgstr "" msgid "Heart of Fire" msgstr "" msgid "" "The %{name} provides %{count} percent protection from fire, but doubles the " "damage taken from cold." msgstr "" msgid "" "You enter a recently burned glade and come upon a Fire Elemental sitting " "atop a rock. It looks up, its flaming face contorted in a look of severe " "pain. It then tosses a glowing object at you. You put up your hands to block " "it, but it passes right through them and sears itself into your chest." msgstr "" msgid "Heart of Ice" msgstr "" msgid "" "The %{name} provides %{count} percent protection from cold, but doubles the " "damage taken from fire." msgstr "" msgid "" "Suddenly, a biting coldness engulfs your body. You seize up, falling from " "your horse. The pain subsides, but you still feel as if your chest is " "frozen. As you pick yourself up off of the ground, you hear hearty laughter. " "You turn around just in time to see a Frost Giant run off into the woods and " "disappear." msgstr "" msgid "Helmet of Anduran" msgstr "" msgid "" "You spy a gleaming object poking up out of the ground. You send a member of " "your party over to investigate. He comes back with a golden helmet in his " "hands. You realize that it must be the helmet of the legendary Anduran, the " "only man who was known to wear solid gold armor." msgstr "" msgid "Holy Hammer" msgstr "" msgid "" "You come upon a battle where a Paladin has been mortally wounded by a group " "of Zombies. He asks you to take his hammer and finish what he started. As " "you pick it up, it begins to hum, and then everything becomes a blur. The " "Zombies lie dead, the hammer dripping with blood. You strap it to your belt." msgstr "" msgid "Legendary Scepter" msgstr "" msgid "The %{name} adds %{count} points to all attributes." msgstr "" msgid "" "Upon cresting a small hill, you come upon a ridiculous looking sight. A " "Sprite is attempting to carry a Scepter that is almost as big as it is. " "Trying not to laugh, you ask, \"Need help?\" The Sprite glares at you and " "answers: \"You think this is funny? Fine. You can carry it. I much prefer " "flying anyway.\"" msgstr "" msgid "" "An old seaman tells you a tale of an enchanted masthead that he used in his " "youth to rally his crew during times of trouble. He then hands you a faded " "map that shows where he hid it. After much exploring, you find it stashed " "underneath a nearby dock." msgstr "" msgid "Masthead" msgstr "" msgid "" "The %{name} boosts the hero's troops' luck and morale by %{count} each in " "sea combat." msgstr "" msgid "Sphere of Negation" msgstr "" msgid "The %{name} disables all spell casting, for both sides, in combat." msgstr "" msgid "" "You stop to help a Peasant catch a runaway mare. To show his gratitude, he " "hands you a tiny sphere. As soon as you grasp it, you feel the magical " "energy drain from your limbs..." msgstr "" msgid "Staff of Wizardry" msgstr "" #, fuzzy msgid "The %{name} boosts the hero's spell power by %{count}." msgstr "%{name} %{count} Okçu gücüyle ateÅŸ ediyor" msgid "" "While out scaring up game, your troops find a mysterious staff levitating " "about three feet off of the ground. They hand it to you, and you notice an " "inscription. It reads: \"Brains best brawn and magic beats might. Heed my " "words, and you'll win every fight.\"" msgstr "" msgid "Sword Breaker" msgstr "" msgid "The %{name} increases the hero's defense by %{count} and attack by 1." msgstr "" msgid "" "A former Captain of the Guard admires your quest and gives you the enchanted " "Sword Breaker that he relied on during his tour of duty." msgstr "" msgid "Sword of Anduran" msgstr "" msgid "" "A Troll stops you and says: \"Pay me 5,000 gold, or the Sword of Anduran " "will slay you where you stand.\" You refuse. The troll grabs the sword " "hanging from its belt, screams in pain, and runs away. Picking up the fabled " "sword, you give thanks that half-witted Trolls tend to grab the wrong end of " "sharp objects." msgstr "" msgid "Spade of Necromancy" msgstr "" msgid "The %{name} gives the hero increased necromancy skill." msgstr "" msgid "" "A dirty shovel has been thrust into a dirt mound nearby. Upon investigation, " "you discover it to be the enchanted shovel of the Gravediggers, long thought " "lost by mortals." msgstr "" msgid "spellBonus|selected by user" msgstr "" msgid "Wood" msgstr "" msgid "Mercury" msgstr "" msgid "Ore" msgstr "" msgid "Sulfur" msgstr "" msgid "Crystal" msgstr "" msgid "Gems" msgstr "" msgid "Gold" msgstr "" msgid "" "There are seven resources in Heroes 2, used to build and improves castles, " "purchase troops and recruit heroes. Gold is the most common, required for " "virtually everything. Wood and ore are used for most buildings. Gems, " "Mercury, Sulfur and Crystal are rare magical resources used for the most " "powerful creatures and buildings." msgstr "" msgid "" "Causes a giant fireball to strike the selected area, damaging all nearby " "creatures." msgstr "" msgid "Fireball" msgstr "" msgid "Fireblast" msgstr "" msgid "" "An improved version of fireball, fireblast affects two hexes around the " "center point of the spell, rather than one." msgstr "" msgid "Causes a bolt of electrical energy to strike the selected creature." msgstr "" msgid "Lightning Bolt" msgstr "" msgid "Chain Lightning" msgstr "" msgid "" "Causes a bolt of electrical energy to strike a selected creature, then " "strike the nearest creature with half damage, then strike the NEXT nearest " "creature with half again damage, and so on, until it becomes too weak to be " "harmful. Warning: This spell can hit your own creatures!" msgstr "" msgid "Teleport" msgstr "" msgid "" "Teleports the creature you select to any open position on the battlefield." msgstr "" msgid "Cure" msgstr "" msgid "" "Removes all negative spells cast upon one of your units, and restores up to " "%{count} HP per level of spell power." msgstr "" msgid "Mass Cure" msgstr "" msgid "" "Removes all negative spells cast upon your forces, and restores up to " "%{count} HP per level of spell power, per creature." msgstr "" msgid "Resurrect" msgstr "" msgid "Resurrects creatures from a damaged or dead unit until end of combat." msgstr "" msgid "Resurrect True" msgstr "" msgid "Resurrects creatures from a damaged or dead unit permanently." msgstr "" msgid "Haste" msgstr "" msgid "Increases the speed of any creature by %{count}." msgstr "" msgid "Increases the speed of all of your creatures by %{count}." msgstr "" msgid "Mass Haste" msgstr "" msgid "Slows target to half movement rate." msgstr "" msgid "spell|Slow" msgstr "" msgid "Mass Slow" msgstr "" msgid "Slows all enemies to half movement rate." msgstr "" msgid "Clouds the affected creatures' eyes, preventing them from moving." msgstr "" msgid "spell|Blind" msgstr "" msgid "Bless" msgstr "" msgid "Causes the selected creatures to inflict maximum damage." msgstr "" msgid "Causes all of your units to inflict maximum damage." msgstr "" msgid "Mass Bless" msgstr "" msgid "Magically increases the defense skill of the selected creatures." msgstr "" msgid "Stoneskin" msgstr "" msgid "" "Increases the defense skill of the targeted creatures. This is an improved " "version of Stoneskin." msgstr "" msgid "Steelskin" msgstr "" msgid "Causes the selected creatures to inflict minimum damage." msgstr "" msgid "Curse" msgstr "" msgid "Causes all enemy troops to inflict minimum damage." msgstr "" msgid "Mass Curse" msgstr "" msgid "Damages all undead in the battle." msgstr "" msgid "Holy Word" msgstr "" msgid "" "Damages all undead in the battle. This is an improved version of Holy Word." msgstr "" msgid "Holy Shout" msgstr "" msgid "Anti-Magic" msgstr "" msgid "Prevents harmful magic against the selected creatures." msgstr "" msgid "Dispel Magic" msgstr "" msgid "Removes all magic spells from a single target." msgstr "" msgid "Mass Dispel" msgstr "" msgid "Removes all magic spells from all creatures." msgstr "" msgid "Causes a magic arrow to strike the selected target." msgstr "" msgid "Magic Arrow" msgstr "" msgid "Berserker" msgstr "" msgid "Causes a creature to attack its nearest neighbor." msgstr "" msgid "Armageddon" msgstr "" msgid "" "Holy terror strikes the battlefield, causing severe damage to all creatures." msgstr "" msgid "Elemental Storm" msgstr "" msgid "Magical elements pour down on the battlefield, damaging all creatures." msgstr "" msgid "" "A rain of rocks strikes an area of the battlefield, damaging all nearby " "creatures." msgstr "" msgid "Meteor Shower" msgstr "" msgid "Paralyze" msgstr "" msgid "The targeted creatures are paralyzed, unable to move or retaliate." msgstr "" msgid "Hypnotize" msgstr "" msgid "" "Brings a single enemy unit under your control if its hits are less than " "%{count} times the caster's spell power." msgstr "" msgid "Cold Ray" msgstr "" msgid "Drains body heat from a single enemy unit." msgstr "" msgid "Cold Ring" msgstr "" msgid "" "Drains body heat from all units surrounding the center point, but not " "including the center point." msgstr "" msgid "Disrupting Ray" msgstr "" msgid "Reduces the defense rating of an enemy unit by three." msgstr "" msgid "Damages all living (non-undead) units in the battle." msgstr "" msgid "Death Ripple" msgstr "" msgid "Death Wave" msgstr "" msgid "" "Damages all living (non-undead) units in the battle. This spell is an " "improved version of Death Ripple." msgstr "" msgid "Dragon Slayer" msgstr "" msgid "Greatly increases a unit's attack skill vs. Dragons." msgstr "" msgid "Blood Lust" msgstr "" msgid "Increases a unit's attack skill." msgstr "" msgid "Animate Dead" msgstr "" msgid "Resurrects creatures from a damaged or dead undead unit permanently." msgstr "" msgid "Mirror Image" msgstr "" msgid "" "Creates an illusionary unit that duplicates one of your existing units. This " "illusionary unit does the same damages as the original, but will vanish if " "it takes any damage." msgstr "" msgid "Shield" msgstr "" msgid "" "Halves damage received from ranged attacks for a single unit. Does not " "affect damage received from Turrets or Ballistae." msgstr "" msgid "Mass Shield" msgstr "" msgid "" "Halves damage received from ranged attacks for all of your units. Does not " "affect damage received from Turrets or Ballistae." msgstr "" msgid "Summon Earth Elemental" msgstr "" msgid "Summons Earth Elementals to fight for your army." msgstr "" msgid "Summon Air Elemental" msgstr "" msgid "Summons Air Elementals to fight for your army." msgstr "" msgid "Summon Fire Elemental" msgstr "" msgid "Summons Fire Elementals to fight for your army." msgstr "" msgid "Summon Water Elemental" msgstr "" msgid "Summons Water Elementals to fight for your army." msgstr "" msgid "Damages castle walls." msgstr "" msgid "Earthquake" msgstr "" msgid "Causes all mines across the land to become visible." msgstr "" msgid "View Mines" msgstr "" msgid "Causes all resources across the land to become visible." msgstr "" msgid "View Resources" msgstr "" msgid "Causes all artifacts across the land to become visible." msgstr "" msgid "View Artifacts" msgstr "" msgid "Causes all towns and castles across the land to become visible." msgstr "" msgid "View Towns" msgstr "" msgid "Causes all Heroes across the land to become visible." msgstr "" msgid "View Heroes" msgstr "" msgid "Causes the entire land to become visible." msgstr "" msgid "View All" msgstr "" msgid "Allows the caster to view detailed information on enemy Heroes." msgstr "" msgid "Summon Boat" msgstr "" msgid "" "Summons the nearest unoccupied, friendly boat to an adjacent shore location. " "A friendly boat is one which you just built or were the most recent player " "to occupy." msgstr "" msgid "Allows the caster to magically transport to a nearby location." msgstr "" msgid "Dimension Door" msgstr "" msgid "Returns the caster to any town or castle currently owned." msgstr "" msgid "Town Gate" msgstr "" msgid "" "Returns the hero to the town or castle of choice, provided it is controlled " "by you." msgstr "" msgid "Visions" msgstr "" msgid "" "Visions predicts the likely outcome of an encounter with a neutral army camp." msgstr "" msgid "Haunt" msgstr "" msgid "" "Haunts a mine you control with Ghosts. This mine stops producing resources. " "(If I can't keep it, nobody will!)" msgstr "" msgid "Set Earth Guardian" msgstr "" msgid "Sets Earth Elementals to guard a mine against enemy armies." msgstr "" msgid "Set Air Guardian" msgstr "" msgid "Sets Air Elementals to guard a mine against enemy armies." msgstr "" msgid "Set Fire Guardian" msgstr "" msgid "Sets Fire Elementals to guard a mine against enemy armies." msgstr "" msgid "Set Water Guardian" msgstr "" msgid "Sets Water Elementals to guard a mine against enemy armies." msgstr "" #, fuzzy msgid "Random Spell" msgstr "Yapılan Büyü" msgid "Randomly selected spell of any level." msgstr "" msgid "Random 1st Level Spell" msgstr "" msgid "Randomly selected 1st level spell." msgstr "" msgid "Random 2nd Level Spell" msgstr "" msgid "Randomly selected 2nd level spell." msgstr "" msgid "Random 3rd Level Spell" msgstr "" msgid "Randomly selected 3rd level spell." msgstr "" msgid "Random 4th Level Spell" msgstr "" msgid "Randomly selected 4th level spell." msgstr "" msgid "Random 5th Level Spell" msgstr "" msgid "Randomly selected 5th level spell." msgstr "" msgid "Petrification" msgstr "" msgid "" "Turns the affected creature into stone. A petrified creature receives half " "damage from a direct attack." msgstr "" msgid "You have no Magic Book, so you cannot cast a spell." msgstr "" msgid "No spell to cast." msgstr "" msgid "Your hero has %{point} spell points remaining." msgstr "" msgid "View Adventure Spells" msgstr "" msgid "View Combat Spells" msgstr "" msgid "View previous page" msgstr "" msgid "View next page" msgstr "" msgid "Close Spellbook" msgstr "" msgid "View %{spell}" msgstr "" msgid "This spell does %{damage} points of damage." msgstr "" msgid "" "This spell summons\n" "%{count} %{monster}." msgstr "" msgid "This spell restores %{hp} HP." msgstr "" msgid "This spell summons %{count} %{monster} to guard the mine." msgstr "" msgid "The nearest town is %{town}." msgstr "" msgid "This town is occupied by your hero %{hero}." msgstr "" msgid "" "This spell controls up to\n" "%{hp} HP." msgstr "" msgid "The ultimate artifact is really the %{name}." msgstr "" msgid "The ultimate artifact may be found in the %{name} regions of the world." msgstr "" msgid "north-west" msgstr "" msgid "north" msgstr "kuzey" msgid "north-east" msgstr "kuzey-doÄŸu" msgid "west" msgstr "batı" msgid "center" msgstr "merkez" msgid "east" msgstr "doÄŸu" msgid "south-west" msgstr "güney-batı" msgid "south" msgstr "güney" msgid "south-east" msgstr "güney-doÄŸu" msgid "The truth is out there." msgstr "" msgid "The dark side is stronger." msgstr "" msgid "The end of the world is near." msgstr "" msgid "The bones of Lord Slayer are buried in the foundation of the arena." msgstr "" msgid "A Black Dragon will take out a Titan any day of the week." msgstr "" msgid "He told her: Yada yada yada... and then she said: Blah, blah, blah..." msgstr "" msgid "An unknown force is being resurrected..." msgstr "" msgid "" "Check the newest version of the game at\n" "https://github.com/ihhub/\n" "fheroes2/releases" msgstr "" #~ msgid "vs" #~ msgstr "karşı" #~ msgid "%{race2} %{name2}" #~ msgstr "%{race2} %{name2}" fheroes2-1.0.12+dfsg/files/lang/uk.po000066400000000000000000010457011456075706000172530ustar00rootroot00000000000000# Ukrainian translation of fheroes2 # Copyright (C) 2022 fheroes2 team # This file is distributed under the same license as the fheroes2 package. # msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-03 03:27+0000\n" "PO-Revision-Date: 2023-09-06 08:41+0300\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" "Language: uk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" "X-Generator: Poedit 3.4\n" msgid "" "BATTLE\n" "ONLY" msgstr "" "ЛИШЕ\n" "БИТВÐ" msgid "" "NEW\n" "GAME" msgstr "" "ÐОВÐ\n" "ГРÐ" msgid "" "SAVE\n" "GAME" msgstr "" "ЗÐПИС\n" "ГРИ" msgid "" "LOAD\n" "GAME" msgstr "" "ЗÐÐ’ÐÐ-\n" "ТÐЖИТИ\n" "ГРУ" msgid "INFO" msgstr "ІÐФО" msgid "QUIT" msgstr "ВИЙТИ" msgid "CANCEL" msgstr "ВІДМІÐÐ" msgid "OKAY" msgstr "ДОБРЕ" msgid "smallerButton|OKAY" msgstr "ДОБРЕ" msgid "ACCEPT" msgstr "ПРИЙÐЯТИ" msgid "DECLINE" msgstr "ВІДМІÐИТИ" msgid "LEARN" msgstr "ВИВЧИТИ" msgid "TRADE" msgstr "ОБМІÐЯТИ" msgid "YES" msgstr "ТÐК" msgid "NO" msgstr "ÐІ" msgid "EXIT" msgstr "ВИХІД" msgid "smallerButton|EXIT" msgstr "ВИХІД" msgid "DISMISS" msgstr "ЗВІЛЬÐИТИ" msgid "UPGRADE" msgstr "ПОКРÐЩИТИ" msgid "RESTART" msgstr "РЕСТÐРТ" msgid "HEROES" msgstr "ГЕРОЇ" msgid "" "TOWNS/\n" "CASTLES" msgstr "" "МІСТÐ/\n" "ЗÐМКИ" msgid "S" msgstr "М" msgid "M" msgstr "С" msgid "L" msgstr "Ð’" msgid "X-L" msgstr "ÐÐ’" msgid "ALL" msgstr "ВСІ" msgid "SELECT" msgstr "ОБРÐТИ" msgid "" "STANDARD\n" "GAME" msgstr "" "ЗВИЧÐЙÐÐ\n" "ГРÐ" msgid "" "CAMPAIGN\n" "GAME" msgstr "КÐМПÐÐІЯ" msgid "" "MULTI-\n" "PLAYER\n" "GAME" msgstr "" "ДЕКІЛЬКÐ\n" "ГРÐВЦІВ" msgid "CONFIG" msgstr "ПÐРÐМЕТРИ" msgid "" "ORIGINAL\n" "CAMPAIGN" msgstr "" "ВІЙÐИ ЗÐ\n" "СПÐДЩИÐУ" msgid "" "EXPANSION\n" "CAMPAIGN" msgstr "" "КÐМПÐÐІЇ\n" "ДОПОВÐЕÐÐЯ" msgid "HOT SEAT" msgstr "" "ГÐРЯЧИЙ \n" "СТІЛЕЦЬ" msgid "2 PLAYERS" msgstr "2 ГРÐВЦІ" msgid "3 PLAYERS" msgstr "3 ГРÐВЦЯ" msgid "4 PLAYERS" msgstr "4 ГРÐВЦЯ" msgid "5 PLAYERS" msgstr "5 ГРÐВЦІВ" msgid "6 PLAYERS" msgstr "6 ГРÐВЦІВ" msgid "GIFT" msgstr "ДÐРУÐОК" msgid "MAX" msgstr "МÐКС" msgid "DIFFICULTY" msgstr "СКЛÐДÐІСТЬ" msgid "VIEW INTRO" msgstr "ПОВТОР ІÐТРО" msgid "MIN" msgstr "МІÐ" msgid "RESET" msgstr "" #, fuzzy msgid "START" msgstr "РЕСТÐРТ" #, fuzzy msgid "CASTLE" msgstr "" "МІСТÐ/\n" "ЗÐМКИ" msgid "TOWN" msgstr "" msgid "" "D\n" "I\n" "S\n" "M\n" "I\n" "S\n" "S" msgstr "" "З\n" "Ð’\n" "І\n" "Л\n" "Ь\n" "Ð." msgid "" "E\n" "X\n" "I\n" "T" msgstr "" "Ð’\n" "И\n" "Ð¥\n" "І\n" "Д" msgid "" "C\n" "A\n" "M\n" "P\n" "A\n" "I\n" "G\n" "N" msgstr "" "К\n" "Ð\n" "М\n" "П\n" "Ð\n" "Ð\n" "І\n" "Я" msgid "" "S\n" "T\n" "A\n" "N\n" "D\n" "A\n" "R\n" "D" msgstr "" "С\n" "Т\n" "Ð\n" "Ð\n" "Д\n" "Ð\n" "Р\n" "Т" msgid "Warrior" msgstr "Воїн" msgid "Builder" msgstr "Будівник" msgid "Explorer" msgstr "Розвідник" msgid "None" msgstr "ВідÑутні" msgid "" "A few\n" "%{monster}" msgstr "" "Мало\n" "%{monster}" msgid "" "Several\n" "%{monster}" msgstr "" "Кілька\n" "%{monster}" msgid "" "A pack of\n" "%{monster}" msgstr "" "ЗграÑ\n" "%{monster}" msgid "" "Lots of\n" "%{monster}" msgstr "" "Багато\n" "%{monster}" msgid "" "A horde of\n" "%{monster}" msgstr "" "Орда\n" "%{monster}" msgid "" "A throng of\n" "%{monster}" msgstr "" "Ðатовп\n" "%{monster}" msgid "" "A swarm of\n" "%{monster}" msgstr "" "Купа\n" "%{monster}" msgid "" "Zounds...\n" "%{monster}" msgstr "" "Безліч\n" "%{monster}" msgid "" "A legion of\n" "%{monster}" msgstr "" "Легіон\n" "%{monster}" msgid "army|Few" msgstr "Мало" msgid "army|Several" msgstr "Кілька" msgid "army|Pack" msgstr "ЗграÑ" msgid "army|Lots" msgstr "Багато" msgid "army|Horde" msgstr "Орда" msgid "army|Throng" msgstr "Ðатовп" msgid "army|Swarm" msgstr "Купа" msgid "army|Zounds" msgstr "Безліч" msgid "army|Legion" msgstr "Легіон" msgid "All %{race} troops +1" msgstr "УÑÑ– війÑька фракції %{race} +1" #, fuzzy msgid "Multiple" msgstr "Різноманітні" msgid "Troops of %{count} alignments -%{penalty}" msgstr "ВійÑька з %{count} Ñ€Ð°Ñ - %{penalty}" #, fuzzy msgid "Some undead in army -1" msgstr "ÐаÑвніÑть невмерлих в армії -1" msgid "View %{name}" msgstr "ПереглÑнути %{name}" msgid "Move the %{name} " msgstr "%{name}: переміÑтити загін" msgid "Move or right click to redistribute %{name}" msgstr "ПереміÑтити %{name}, правий клік щоб поділити" msgid "Combine %{name} armies" msgstr "%{name}: Поєднати загони" msgid "Exchange %{name2} with %{name}" msgstr "ОбмінÑти %{name2} на %{name}" msgid "Select %{name}" msgstr "Обрати %{name}" msgid "Cannot move last troop" msgstr "Ðе вдалоÑÑŒ переміÑтити оÑтанній загін" msgid "Move the %{name}" msgstr "ПереміÑтити: %{name}" msgid "Set Count" msgstr "Ð’Ñтановити КількіÑть" msgid "%{name} destroys half the enemy troops!" msgid_plural "%{name} destroy half the enemy troops!" msgstr[0] "%{name} знищує половину ворожого загону!" msgstr[1] "%{name} знищують половину ворожого загону!" msgstr[2] "%{name} знищують половину ворожого загону!" msgid "%{name} has turned off the auto battle" msgstr "%{name} вимкнув автобій" msgid "%{name} has turned on the auto battle" msgstr "%{name} увімкнув автобій" msgid "Spell failed!" msgstr "Чари не Ñпрацювали!" msgid "" "The Sphere of Negation artifact is in effect for this battle, disabling all " "combat spells." msgstr "" "Ðа поле бою діє артефакт \"Сфера ЗапереченнÑ. ВикориÑÑ‚Ð°Ð½Ð½Ñ Ð±Ð¾Ð¹Ð¾Ð²Ð¾Ñ— магії " "заборонене." msgid "You have already cast a spell this round." msgstr "Ви вже викориÑтали чари протÑгом цього раунду." msgid "That spell will have no effect!" msgstr "Ці чари не матимуть ефекту!" msgid "You may only summon one type of elemental per combat." msgstr "Ви можете викликати елементалів лиш одного типу протÑгом битви." msgid "" "There is no open space adjacent to your hero where you can summon an " "Elemental to." msgstr "Ð‘Ñ–Ð»Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ герою нема вільного міÑÑ†Ñ Ð´Ð»Ñ Ð²Ð¸ÐºÐ»Ð¸ÐºÑƒ Елементалів." msgid "" "The Moat reduces by -%{count} the defense skill of any unit and slows to " "half movement rate." msgstr "" "Рів зменшує захиÑну здатніÑть будь-Ñкого загону на -%{count} Ñ– Ñповільнює " "його вдвічі." msgid "Speed: %{speed}" msgstr "ШвидкіÑть: %{speed}" msgid "Speed" msgstr "ШвидкіÑть" msgid "Off" msgstr "Вимк." msgid "On" msgstr "Увімк." msgid "Turn Order" msgstr "ПорÑдок Ходів" msgid "Auto Spell Casting" msgstr "Ðвтоматичне ЧаклуваннÑ" msgid "Grid" msgstr "Сітка" msgid "Shadow Movement" msgstr "Ð—Ð°Ñ‚Ñ–Ð½ÐµÐ½Ð½Ñ Ð¡Ñ–Ñ‚ÐºÐ¸" msgid "Shadow Cursor" msgstr "Тінь курÑора" msgid "Audio" msgstr "Ðудіо" msgid "Settings" msgstr "ÐалаштуваннÑ" msgid "Configure" msgstr "Ðалаштувати" msgid "Hot Keys" msgstr "ГарÑчі клавіші" msgid "Damage Info" msgstr "" "Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾\n" "пошкодженнÑ" msgid "Set the speed of combat actions and animations." msgstr "Ð’Ñтановити швидкіÑть бойових дій та анімації." msgid "Toggle to display the turn order during the battle." msgstr "Перемкнути Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ñ‡ÐµÑ€Ð³Ð¾Ð²Ð¾Ñті ходів загонів під Ñ‡Ð°Ñ Ð±Ð¸Ñ‚Ð²Ð¸." msgid "" "Toggle whether or not the computer will cast spells for you when auto combat " "is on. (Note: This does not affect spell casting for computer players in any " "way, nor does it affect quick combat.)" msgstr "" "Перемкнути викориÑÑ‚Ð°Ð½Ð½Ñ ÐºÐ¾Ð¼Ð¿'ютером магії під Ñ‡Ð°Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡Ð½Ð¾ проведених " "битв. (Примітка: Ð¦Ñ Ñ„ÑƒÐ½ÐºÑ†Ñ–Ñ Ð¶Ð¾Ð´Ð½Ð¸Ð¼ чином не впливає на викориÑÑ‚Ð°Ð½Ð½Ñ Ð¼Ð°Ð³Ñ–Ñ— " "комп'ютерними гравцÑми, а також не впливає на швидкий бій)." msgid "" "Toggle the hex grid on or off. The hex grid always underlies movement, even " "if turned off. This switch only determines if the grid is visible." msgstr "" "Перемкнути Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ñітки Ð¿Ð¾Ð»Ñ Ð±Ð¾ÑŽ. Сітка з шеÑтигранних клітинок завжди " "визначає можливоÑті руху, навіть Ñкщо Ñ—Ñ— Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð²Ð¸Ð¼ÐºÐ½ÐµÐ½Ðµ. Перемикач " "лише робить Ñ—Ñ— видимою." msgid "" "Toggle on or off shadows showing where your creatures can move and attack." msgstr "" "Перемкнути Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð·Ð¾Ð½Ð¸, в межах Ñкої ваші Ñ–Ñтоти можуть переÑуватиÑÑ " "та атакувати." msgid "" "Toggle on or off a shadow showing the current hex location of the mouse " "cursor." msgstr "Перемкнути Ð·Ð°Ñ‚Ñ–Ð½ÐµÐ½Ð½Ñ ÐºÐ»Ñ–Ñ‚Ð¸Ð½Ð¸, на Ñку наведений курÑор миші." msgid "Change the audio settings of the game." msgstr "Змінити звукові Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð³Ñ€Ð¸." msgid "Check and configure all the hot keys present in the game." msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð²ÑÑ–Ñ… гарÑчих клавіш у грі." msgid "Toggle to display damage information during the battle." msgstr "" "Перемкнути Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ— про Ð¿Ð¾ÑˆÐºÐ¾Ð´Ð¶ÐµÐ½Ð½Ñ Ð¿Ñ–Ð´ Ñ‡Ð°Ñ Ð±Ð¸Ñ‚Ð²Ð¸. Якщо " "Ñ„ÑƒÐ½ÐºÑ†Ñ–Ñ Ð¿Ñ€Ð°Ñ†ÑŽÑ”, наведіть курÑором на бажану ціль - через кілька Ñекунд " "вирине вікно з розрахунком можливих пошкоджень, що завдаÑть ваш загін." msgid "Exit this menu." msgstr "Вийти з цього меню." msgid "Okay" msgstr "Добре" msgid "The enemy has surrendered!" msgstr "Ворог капітулював!" msgid "Their cowardice costs them %{gold} gold." msgstr "" msgid "The enemy has fled!" msgstr "Ворог втік!" msgid "A glorious victory!" msgstr "Славетна перемога!" msgid "For valor in combat, %{name} receives %{exp} experience." msgstr "За доблеÑть у бою, %{name} отримує %{exp} доÑвіду." msgid "The cowardly %{name} flees from battle." msgstr "БоÑгузливий %{name} тікає від бою." msgid "%{name} surrenders to the enemy, and departs in shame." msgstr "%{name} здаєтьÑÑ Ð²Ð¾Ñ€Ð¾Ð³Ñƒ та відходить з ганьбою." #, fuzzy msgid "Your forces suffer a bitter defeat, and %{name} abandons your cause." msgstr "Ваше війÑько зазнає гіркої поразки Ñ– %{name} полишає ВаÑ." #, fuzzy msgid "Your forces suffer a bitter defeat." msgstr "Ваше війÑько зазнає гіркої поразки." msgid "Battlefield Casualties" msgstr "Жертви на полі бою" msgid "Attacker" msgstr "Ðападник" msgid "Defender" msgstr "ЗахиÑник" msgid "Click to leave the battle results." msgstr "" #, fuzzy msgid "Click to restart the battle in manual mode." msgstr "завершити битву у автоматичному режимі" msgid "Restart" msgstr "" msgid "You have captured an enemy artifact!" msgstr "Ви заволоділи артефактом ворога!" msgid "As you reach for the %{name}, it mysteriously disappears." msgstr "%{name} загадково щезає, щойно ви Ñпробували його забрати." msgid "As your enemy reaches for the %{name}, it mysteriously disappears." msgstr "%{name} загадково щезає, щойно ваш ворог Ñпробував його забрати." msgid "Necromancy!" msgstr "ÐекромантіÑ!" msgid "" "Practicing the dark arts of necromancy, you are able to raise %{count} of " "the enemy's dead to return under your service as %{monster}." msgstr "" "Практикуючи темне миÑтецтво некромантії, ви піднÑли %{count} мертвих " "ворогів, Ñкі тепер будуть Ñлугувати вам Ñк %{monster}." msgid "%{name} the %{race}" msgstr "%{name} %{race}" msgid "Captain of %{name}" msgstr "Капітан замку %{name}" msgid "Attack" msgstr "Ðтака" msgid "Defense" msgstr "ЗахиÑÑ‚" msgid "Spell Power" msgstr "Сила Магії" msgid "Knowledge" msgstr "ЗнаннÑ" msgid "Morale" msgstr "Мораль" msgid "Luck" msgstr "Удача" msgid "Spell Points" msgstr "Мана" msgid "Hero's Options" msgstr "Дії ГероÑ" msgid "Cast Spell" msgstr "ÐаклаÑти чари" msgid "Retreat" msgstr "ВідÑтупити" msgid "Surrender" msgstr "ЗдатиÑÑ" msgid "Cancel" msgstr "СкаÑувати" msgid "Hero Screen" msgstr "Вікно ГероÑ" msgid "Captain's Options" msgstr "Дії Капітана" msgid "" "Cast a magical spell. You may only cast one spell per combat round. The " "round is reset when every creature has had a turn." msgstr "" "ЗаÑтоÑувати магічне заклÑттÑ. Ви можете наÑлати лиш одне заклÑÑ‚Ñ‚Ñ ÐºÐ¾Ð¶ÐµÐ½ " "раунд. Ðовий раунд наÑтає щоразу, коли кожна Ñ–Ñтота робить (або пропуÑкає) " "Ñвій хід." msgid "" "Retreat your hero, abandoning your creatures. Your hero will be available " "for you to recruit again, however, the hero will have only a novice hero's " "forces." msgstr "" "ВідÑтупити героєм, кинувши війÑька напризволÑще. Вашого Ð³ÐµÑ€Ð¾Ñ Ð¼Ð¾Ð¶Ð½Ð° буде " "негайно найнÑти знову, однак, герой матиме такі ж війÑька, Ñк кожен " "новобранець." #, fuzzy msgid "" "Surrendering costs gold. However if you pay the ransom, the hero and all of " "his or her surviving creatures will be available to recruit again. The cost " "of surrender is half of the total cost of the non-temporary troops remaining " "in the army." msgstr "" "КапітулÑÑ†Ñ–Ñ Ð²Ð¸Ð¼Ð°Ð³Ð°Ñ‚Ð¸Ð¼Ðµ оплати золотом. Однак, Ñкщо ви Ñплатите викуп, герой " "разом з уÑіма Ñвоїми артефактами та вцілілими війÑьками буде доÑтупний Ð´Ð»Ñ " "повторного найму." msgid "Open Hero Screen to view full information about the hero." msgstr "Відкрити Вікно Ð“ÐµÑ€Ð¾Ñ Ñ‰Ð¾Ð± переглÑнути повну інформацію про нього." msgid "Return to the battle." msgstr "ПовернутиÑÑ Ð´Ð¾ битви." msgid "Not enough gold (%{gold})" msgstr "ÐедоÑтатньо золота (%{gold})" msgid "%{name} states:" msgstr "%{name}, характериÑтики:" msgid "Captain of %{name} states:" msgstr "Капітан міÑта %{name}, характериÑтики:" msgid "" "\"I will accept your surrender and grant you and your troops safe passage " "for the price of %{price} gold.\"" msgstr "" "\"Я прийму вашу капітулÑцію Ñ– дам безпечний прохід твоїм війÑькам за " "%{price} золота.\"" msgid "View %{monster} info" msgstr "ПереглÑнути інформацію про %{monster}" msgid "Fly %{monster} here" msgstr "%{monster}: перелетіти Ñюди" msgid "Move %{monster} here" msgstr "%{monster} переміÑÑ‚ÑтьÑÑ Ñюди" msgid "Shoot %{monster}" msgstr "Зробити поÑтріл у %{monster}" msgid "(1 shot left)" msgid_plural "(%{count} shots left)" msgstr[0] "(ЗалишивÑÑ 1 поÑтріл)" msgstr[1] "(ЗалишилоÑÑ %{count} поÑтріла)" msgstr[2] "(ЗалишилоÑÑ %{count} поÑтрілів)" msgid "Attack %{monster}" msgstr "Ðтакувати %{monster}" msgid "Turn %{turn}" msgstr "Раунд %{turn}" msgid "Teleport here" msgstr "ТелепортуватиÑÑ Ñюди" msgid "Invalid teleport destination" msgstr "Ð¢ÐµÐ»ÐµÐ¿Ð¾Ñ€Ñ‚Ð°Ñ†Ñ–Ñ Ñюди неможлива" msgid "Cast %{spell} on %{monster}" msgstr "ÐаклаÑти %{spell} на %{monster}" msgid "Cast %{spell}" msgstr "ÐаклаÑти %{spell}" msgid "Select spell target" msgstr "Обрати ціль заклÑттÑ" msgid "View Ballista info" msgstr "Дані по БаліÑті" msgid "Ballista" msgstr "БаліÑта" msgid "Enable auto combat" msgstr "Увімкнути автобій" msgid "Allows the computer to fight out the battle for you." msgstr "ДозволÑÑ” комп'ютеру веÑти битву за ваÑ." msgid "Auto Combat" msgstr "Ðвтобій" msgid "Customize system options" msgstr "Ðалаштувати параметри ÑиÑтеми" msgid "Allows you to customize the combat screen." msgstr "ДозволÑÑ” налаштувати екран бою." msgid "System Options" msgstr "СиÑтемні Параметри" msgid "Skip this unit" msgstr "ПропуÑтити хід" msgid "Skip" msgstr "ПропуÑтити" msgid "" "Skips the current creature. The current creature ends its turn and does not " "get to go again until the next round." msgstr "" "ПропуÑтити хід Ñ–Ñтоти. Поточна Ñ–Ñтота закінчить хід та не ходитиме до " "наÑтупного раунду." msgid "View Captain's options" msgstr "Відкрити вікно дій Капітана" msgid "View Hero's options" msgstr "Відкрити вікно дій ГероÑ" msgid "View opposing Captain" msgstr "ПереглÑнути ворожого Капітана" msgid "View opposing Hero" msgstr "ПереглÑнути ворожого ГероÑ" msgid "Hide logs" msgstr "Сховати журнал" msgid "Show logs" msgstr "ДивитиÑÑ Ð¶ÑƒÑ€Ð½Ð°Ð»" msgid "Message Bar" msgstr "Табло повідомлень" msgid "Shows the results of individual monster's actions." msgstr "Відображує кожну дію та ефект, що мають міÑце на полі бою." msgid "Are you sure you want to finish the battle in auto mode?" msgstr "Ви впевнені, що хочете завершити бій в автоматичному режимі?" msgid "Are you sure you want to enable auto combat?" msgstr "Ви впевнені, що хочете ввімкнути автобитву?" msgid "%{name} skip their turn." msgstr "%{name} пропуÑкають Ñвій хід." msgid "%{attacker} does %{damage} damage." msgid_plural "%{attacker} do %{damage} damage." msgstr[0] "%{attacker} завдає %{damage} шкоди." msgstr[1] "%{attacker} завдають %{damage} шкоди." msgstr[2] "%{attacker} завдають %{damage} шкоди." msgid "1 creature perishes." msgid_plural "%{count} creatures perish." msgstr[0] "1 Ñ–Ñтота загинула." msgstr[1] "%{count} Ñ–Ñтоти загинули." msgstr[2] "%{count} Ñ–Ñтот загинуло." msgid "1 %{defender} perishes." msgid_plural "%{count} %{defender} perish." msgstr[0] "1 %{defender} загинув." msgstr[1] "%{count} %{defender} загинули." msgstr[2] "%{count} %{defender} загинуло." msgid "1 soul is incorporated." msgid_plural "%{count} souls are incorporated." msgstr[0] "Одна душа поглинута." msgstr[1] "%{count} душі поглинуті." msgstr[2] "%{count} душ поглинуто." msgid "1 %{unit} is revived." msgid_plural "%{count} %{unit} are revived." msgstr[0] "1 %{unit} відродивÑÑ." msgstr[1] "%{count} %{unit} відродилоÑÑ." msgstr[2] "%{count} %{unit} відродилоÑÑ." msgid "Moved %{monster}: from [%{src}] to [%{dst}]." msgstr "%{monster}: ÐŸÐµÑ€ÐµÐ¼Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð· [%{src}] до [%{dst}]." msgid "The %{name} resist the spell!" msgstr "%{name} опираютьÑÑ Ñ‡Ð°Ñ€Ð°Ð¼!" msgid "%{name} casts %{spell} on the %{troop}." msgstr "%{name} чаклує %{spell} на %{troop}." msgid "%{name} casts %{spell}." msgstr "%{name} чаклує %{spell}." msgid "The %{spell} does %{damage} damage to one undead creature." msgstr "%{spell} завдає %{damage} шкоди одній Ñ–Ñтоті невмерлих." msgid "The %{spell} does %{damage} damage to all undead creatures." msgstr "%{spell} завдає %{damage} шкоди уÑім Ñ–Ñтотам невмерлих." msgid "The %{spell} does %{damage} damage, %{count} creatures perish." msgstr "%{spell} завдає %{damage} шкоди, %{count} Ñ–Ñтот загинуло." msgid "The %{spell} does %{damage} damage." msgstr "%{spell} завдає %{damage} шкоди." msgid "The %{spell} does %{damage} damage to one living creature." msgstr "%{spell} завдає %{damage} шкоди одній живій Ñ–Ñтоті." msgid "The %{spell} does %{damage} damage to all living creatures." msgstr "%{spell} завдає %{damage} шкоди уÑім живим Ñ–Ñтотам." msgid "The %{attacker}' attack blinds the %{target}!" msgstr "Ðтака %{attacker} оÑліплює %{target}!" msgid "The %{attacker}' gaze turns the %{target} to stone!" msgstr "ПоглÑд %{attacker} перетворює %{target} на камінь!" msgid "The %{attacker}' curse falls upon the %{target}!" msgstr "%{attacker} наÑилає проклÑÑ‚Ñ‚Ñ Ð½Ð° %{target}!" msgid "The %{target} are paralyzed by the %{attacker}!" msgstr "%{attacker} паралізує %{target}!" msgid "The %{attacker} dispel all good spells on your %{target}!" msgstr "%{attacker} розвіює вÑÑ– добрі чари, що були накладені на %{target}!" msgid "The %{attacker} cast %{spell} on %{target}!" msgstr "%{attacker} викориÑтав заклÑÑ‚Ñ‚Ñ \"%{spell}\"! Ціль: %{target}." msgid "Bad luck descends on the %{attacker}." msgstr "Удача відвернулаÑÑŒ від %{attacker}." msgid "Good luck shines on the %{attacker}." msgstr "Удача поÑміхнулаÑÑŒ %{attacker}." msgid "High morale enables the %{monster} to attack again." msgstr "ЗавдÑки виÑокій моралі %{monster} атакує знову." msgid "Low morale causes the %{monster} to freeze in panic." msgstr "Через низьку мораль %{monster} завмер у паніці." msgid "%{tower} does %{damage} damage." msgstr "%{tower} завдає %{damage} шкоди." msgid "The mirror image is created." msgstr "Створено Дзеркального Фантома." msgid "The mirror image is destroyed!" msgstr "Фантомний загін знищений!" msgid "Are you sure you want to interrupt the auto battle?" msgstr "Ви впевнені, що хочете перервати автоматичний бій?" msgid "Error" msgstr "Помилка" msgid "No spells to cast." msgstr "Ðема чар Ð´Ð»Ñ Ð²Ð¸ÐºÐ¾Ñ€Ð¸ÑтаннÑ." msgid "Are you sure you want to retreat?" msgstr "Ви впевнені, що хочете відÑтупити?" msgid "Retreat disabled" msgstr "ВідÑтуп неможливий" msgid "Surrender disabled" msgstr "КапітулÑÑ†Ñ–Ñ Ð½ÐµÐ¼Ð¾Ð¶Ð»Ð¸Ð²Ð°" msgid "Damage: %{max}" msgstr "Шкода: %{max}" msgid "Damage: %{min} - %{max}" msgstr "Шкода: %{min} - %{max}" msgid "Perish: %{max}" msgstr "Загине %{max}" msgid "Perish: %{min} - %{max}" msgstr "Загине %{min} - %{max}" msgid "" "Through eagle-eyed observation, %{name} is able to learn the magic spell " "%{spell}." msgstr "ЗавдÑки орлиному зору %{name} зміг вивчити магічне заклÑÑ‚Ñ‚Ñ %{spell}." msgid "Human" msgstr "Людина" msgid "AI" msgstr "Комп'ютер" msgid "Start" msgstr "" #, fuzzy msgid "Start the battle." msgstr "ПовернутиÑÑ Ð´Ð¾ битви." msgid "Exit" msgstr "Вийти" msgid "Reset" msgstr "" msgid "Reset to default settings." msgstr "" msgid "Please select another hero." msgstr "Будь лаÑка, оберіть іншого героÑ." msgid "Set Attack Skill" msgstr "Ð’Ñтановити Ðтаку" msgid "Set Defense Skill" msgstr "Ð’Ñтановити ЗахиÑÑ‚" msgid "Set Power Skill" msgstr "Ð’Ñтановити Силу Магії" msgid "Set Knowledge Skill" msgstr "Ð’Ñтановити ЗнаннÑ" #, fuzzy msgid "%{race1} %{name1} vs %{race2} %{name2}" msgstr "%{race1} %{name1}" msgid "Monsters" msgstr "МонÑтри" msgid "N/A" msgstr "Ð/Д" msgid "Left Turret" msgstr "Ліва Вежа" msgid "Right Turret" msgstr "Права Вежа" msgid "The %{name} fires with the strength of %{count} Archers" msgstr "%{name} б'Ñ” із Ñилою %{count} Лучників" msgid "each with a +%{attack} bonus to their attack skill." msgstr "кожна з бонуÑом +%{attack} до Ð²Ð¼Ñ–Ð½Ð½Ñ Ðтаки." msgid "The %{name} is destroyed." msgstr "%{name} знищено." msgid "%{count} %{name} rises from the dead!" msgid_plural "%{count} %{name} rise from the dead!" msgstr[0] "%{count} %{name} піднÑто з мертвих!" msgstr[1] "%{count} %{name} піднÑто з мертвих!" msgstr[2] "%{count} %{name} піднÑто з мертвих!" msgid "Dwarven Alliance" msgstr "ÐльÑÐ½Ñ Ð“Ð½Ð¾Ð¼Ñ–Ð²" msgid "Sorceress Guild" msgstr "Ð“Ñ–Ð»ÑŒÐ´Ñ–Ñ Ð§Ð°ÐºÐ»ÑƒÐ½Ð¾Ðº" msgid "Necromancer Guild" msgstr "Ð“Ñ–Ð»ÑŒÐ´Ñ–Ñ Ðекромантів" msgid "Ogre Alliance" msgstr "ÐльÑÐ½Ñ ÐžÐ³Ñ€Ñ–Ð²" msgid "Dwarfbane" msgstr "Гномогубець" msgid "Dragon Alliance" msgstr "ÐльÑÐ½Ñ Ð”Ñ€Ð°ÐºÐ¾Ð½Ñ–Ð²" msgid "Elven Alliance" msgstr "ЕльфійÑький ÐльÑнÑ" msgid "Kraeger defeated" msgstr "Краегер подоланий" msgid "Wayward Son" msgstr "Син Вейварда" msgid "Uncle Ivan" msgstr "ДÑдько Іван" msgid "Annexation" msgstr "ÐннекÑÑ–Ñ" msgid "Force of Arms" msgstr "Сила Зброї" msgid "Save the Dwarves" msgstr "ПорÑтунок Гномів" msgid "Carator Mines" msgstr "КараторÑькі Копальні" msgid "Turning Point" msgstr "Поворотний Момент" msgid "scenarioName|Defender" msgstr "Оборонець" msgid "Corlagon's Defense" msgstr "Оборона Корлагона" msgid "The Crown" msgstr "Корона" msgid "The Gauntlet" msgstr "РукавицÑ" msgid "Betrayal" msgstr "Зрада" msgid "Final Justice" msgstr "ОÑтаточне ПравоÑуддÑ" msgid "" "Roland needs you to defeat the lords near his castle to begin his war of " "rebellion against his brother. They are not allied with each other, so they " "will spend most of their time fighting with one another. Victory is yours " "when you have defeated all of their castles and heroes." msgstr "" "Ви потрібні Роландові щоб перемогти лордів Ð±Ñ–Ð»Ñ Ð¹Ð¾Ð³Ð¾ замку, тим Ñамим " "почавши його повÑÑ‚Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾Ñ‚Ð¸ брата. Вони не укладали Ñоюзу, то ж переважно " "воюватимуть одне з одним. Ви переможете, коли захопите вÑÑ– Ñ—Ñ… замки та " "переможете вÑÑ–Ñ… Ñ—Ñ… героїв." msgid "" "The local lords refuse to swear allegiance to Roland, and must be subdued. " "They are wealthy and powerful, so be prepared for a tough fight. Capture all " "enemy castles to win." msgstr "" "МіÑцеві лорди відмовилиÑÑŒ приÑÑгнути Роландові на вірніÑть, тому Ñ—Ñ… треба " "підкорити. Вони багаті та могутні, то ж приготуйтеÑÑŒ до запеклого Ñпротиву. " "Захопіть вÑÑ– Ñ—Ñ… замки, щоб перемогти." msgid "" "Your task is to defend the Dwarves against Archibald's forces. Capture all " "of the enemy towns and castles to win, and be sure not to lose all of the " "dwarf towns at once, or the enemy will have won." msgstr "" "Ваше Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð¿Ð¾Ð»Ñгає у захиÑті Гномів від Ñил Ðрчибальда. Захопіть вÑÑ– " "ворожі міÑта та замки щоб перемогти, та переконайтеÑÑŒ, що ворог не " "володітиме вÑіма міÑтами гномів одночаÑно, інакше він виграє." msgid "" "You will face four allied enemies in a straightforward fight for resource " "and treasure. Capture all of the enemy castles for victory." msgstr "" "Ви зітнетеÑÑŒ з чотирма об'єднаними ворогами у відкритому протиÑтоÑнні за " "реÑурÑи та Ñкарби. Захопіть вÑÑ– ворожі замки, щоб перемогти." msgid "" "Your enemies are allied against you and start close by, so be ready to come " "out fighting. You will need to own all four castles in this small valley to " "win." msgstr "" "Ваші вороги об'єдналиÑÑŒ, та розташували Ñвої Ñили неподалік від ваÑ, то ж " "готуйтеÑÑŒ до запеклої боротьби. Вам треба захопити вÑÑ– чотири замки цієї " "маленької долини, щоб перемогти." msgid "" "The Sorceress' guild of Noraston has requested Roland's aid against an " "attack from Archibald's allies. Capture all of the enemy castles to win, and " "don't lose Noraston, or you'll lose the scenario. (Hint: There is an enemy " "castle on an island in the ocean.)" msgstr "" "Ð“Ñ–Ð»ÑŒÐ´Ñ–Ñ Ð§Ð°ÐºÐ»ÑƒÐ½Ð¾Ðº ÐораÑтону надіÑлала Роландові запит про допомогу у боротьбі " "проти атакуючих Ñоюзників Ðрчибальда. Захопіть вÑÑ– ворожі замки щоб " "перемогти, та не втратьте ÐораÑтон, інакше програєте. (Підказка: Один з " "ворожих замків розташований в океані)." msgid "" "Gather as large an army as possible and capture the enemy castle within 8 " "weeks. You are opposed by only one enemy, but must travel a long way to get " "to the enemy castle. Any troops you have in your army at the end of this " "scenario will be with you in the final battle." msgstr "" "Зберіть Ñкомога більше війÑько та захопіть ворожий замок протÑгом 8 тижнів. " "Вам протиÑтоїть лише один ворог, однак ви маєте пройти довгий шлÑÑ…, щоб " "доÑÑгти його замку. Ð’ÑÑ– війÑька, що залишатьÑÑ Ð· вами піÑÐ»Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ " "Ñценарію будуть з вами у фінальному Ñценарії." msgid "" "Find the Crown before Archibald's heroes find it. Roland will need the Crown " "for the final battle against Archibald." msgstr "" "Знайдіть Корону перш, ніж це зроблÑть герої Ðрчибальда. Роландові " "знадобитьÑÑ ÐšÐ¾Ñ€Ð¾Ð½Ð° Ð´Ð»Ñ Ñ„Ñ–Ð½Ð°Ð»ÑŒÐ½Ð¾Ð³Ð¾ бою проти Ðрчибальда." msgid "" "Three allied enemies stand before you and victory, including Lord Corlagon. " "Roland is in a castle to the northwest, and you will lose if he falls to the " "enemy. Remember that capturing Lord Corlagon will ensure that he will not " "fight against you in the final scenario." msgstr "" "Три об'єднані Ñоюзом вороги ÑтоÑть між вами та перемогою. Серед них Ñ” Ñ– Лорд " "Корлагон. Роланд знаходитьÑÑ Ñƒ замку на північний захід, Ñ– ви програєте, " "Ñкщо він потрапить до ворожих рук. Пам'Ñтайте, що Ð·Ð°Ñ…Ð¾Ð¿Ð»ÐµÐ½Ð½Ñ Ð›Ð¾Ñ€Ð´Ð° Корлагона " "не дозволить йому битиÑÑŒ проти Ð²Ð°Ñ Ñƒ фінальному Ñценарії." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Archibald to end the war!" msgstr "" "Це фінальна битва. Як Ñ– ви, ваші вороги озброєні до зубів, Ñ– вÑÑ– вони у " "Ñоюзі проти ваÑ. Захопіть Ðрчибальда щоб Ñкінчити війну, та не втратьте " "Роланда у бою!" msgid "" "Switching sides leaves you with three castles against the enemy's one. This " "battle will be the easiest one you will face for the rest of the war..." "traitor." msgstr "" "Перехід на інший бік залишив вашого ворога з одним замком проти ваших трьох. " "Ð¦Ñ Ð±Ð¸Ñ‚Ð²Ð° буде найлегшою з тих, що чекають на Ð²Ð°Ñ Ñƒ цій війні... зраднику." msgid "Barbarian Wars" msgstr "ВарварÑькі Війни" msgid "First Blood" msgstr "Перша Кров" msgid "Necromancers" msgstr "Ðекроманти" msgid "Slay the Dwarves" msgstr "Ð’Ð¸Ð½Ð¸Ñ‰ÐµÐ½Ð½Ñ Ð“Ð½Ð¾Ð¼Ñ–Ð²" msgid "Country Lords" msgstr "Землі Володарів" msgid "Dragon Master" msgstr "Повелитель Драконів" msgid "Rebellion" msgstr "ПовÑтаннÑ" msgid "Apocalypse" msgstr "ÐпокаліпÑиÑ" msgid "Greater Glory" msgstr "Ðайбільша Слава" msgid "" "King Archibald requires you to defeat the three enemies in this region. They " "are not allied with one another, so they will spend most of their energy " "fighting amongst themselves. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Король Ðрчибальд вимагає, щоб ви здолали трьох ворогів у цьому регіоні. Вони " "не поєднані Ñоюзом, то ж більшу чаÑтину Ñвоїх Ñил витратÑть на боротьбу одне " "з одним. Ви переможете, коли заволодієте уÑіма ворожими замками, та не " "залишитьÑÑ Ð³ÐµÑ€Ð¾Ñ—Ð², з Ñкими можна битиÑÑ." msgid "" "You must unify the barbarian tribes of the north by conquering them. As in " "the previous mission, the enemy is not allied against you, but they have " "more resources at their disposal. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Ви муÑите об'єднати варварÑькі племена півночі, завоювавши Ñ—Ñ…. Як Ñ– в " "попередньому завданні, вороги не об'єднані проти ваÑ, однак у них в " "розпорÑдженні більше реÑурÑів. Ви переможете, коли заволодієте уÑіма " "ворожими замками, Ñ– коли не залишитьÑÑ Ð³ÐµÑ€Ð¾Ñ—Ð², з Ñкими можна битиÑÑ." msgid "" "Do-gooder wizards have taken the Necromancers' castle. You must retake it to " "achieve victory. Remember that while you start with a powerful army, you " "have no castle and must take one within 7 days, or lose this battle. (Hint: " "The nearest castle is to the southeast.)" msgstr "" "Добрі чарівники захопили замок Ðекромантів. Ви маєте повернути його, щоб " "доÑÑгти перемоги. Пам'Ñтайте, що хоча ви й починаєте з потужним війÑьком, у " "Ð²Ð°Ñ Ð½Ðµ буде замку, то ж ви муÑите захопити будь-Ñкий замок протÑгом 7 днів, " "інакше програєте. (Підказка: Ðайближчий замок знаходитьÑÑ Ð½Ð° північний Ñхід " "від ваÑ)." msgid "" "The dwarves need conquering before they can interfere in King Archibald's " "plans. Roland's forces have more than one hero and many towns to start with, " "so be ready for attack from multiple directions. You must capture all of the " "enemy towns and castles to claim victory." msgstr "" "Гномів потрібно завоювати перш, ніж вони зможуть втрутитиÑÑŒ у плани ÐšÐ¾Ñ€Ð¾Ð»Ñ " "Ðрчибальда. Сили Роланда мають більше ніж одного героÑ, та починають з " "багатьма міÑтами, то ж будьте готові до нападів з різних напрÑмків. Ви " "муÑите захопити вÑÑ– ворожі міÑта та замки, щоб доÑÑгти перемоги." msgid "" "You must put down a peasant revolt led by Roland's forces. All are allied " "against you, but you have Lord Corlagon, an experienced hero, to help you. " "Capture all enemy castles to win." msgstr "" "Ви маєте поклаÑти край ÑелÑнÑькому повÑтанню, очоленому Ñилами Роланда. Ð’ÑÑ– " "об'єднані проти ваÑ, однак на вашому боці Лорд Корлагон, доÑвідчений герой, " "що допоможе вам. Захопіть вÑÑ– ворожі замки, щоб перемогти." msgid "" "There are two enemies allied against you in this mission. Both are well " "armed and seek to evict you from their island. Avoid them and capture Dragon " "City to win." msgstr "" "У цьому завданні проти Ð²Ð°Ñ Ð¾Ð±'єдналиÑÑ Ð´Ð²Ð¾Ñ” ворогів. Обидва вони добре " "озброєні, та шукають ÑпоÑобу вигнати Ð²Ð°Ñ Ð· Ñ—Ñ… оÑтрова. Уникайте Ñ—Ñ…, та " "захопіть МіÑто Драконів щоб перемогти." msgid "" "Your orders are to conquer the country lords that have sworn to serve " "Roland. All of the enemy castles are unified against you. Since you start " "without a castle, you must hurry to capture one before the end of the week. " "Capture all enemy castles for victory." msgstr "" "Вам наказано завоювати землі володарів, що поклÑлиÑÑ Ñлужити Роланду. Ð’ÑÑ– " "ворожі замки об'єднані проти ваÑ. ОÑкільки ви починаєте без замку, ви маєте " "поквапитиÑÑ Ñ‚Ð° захопити один перш, ніж мине тиждень. Захопіть вÑÑ– ворожі " "замки Ð·Ð°Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ¼Ð¾Ð³Ð¸." msgid "" "Find the Crown before Roland's heroes find it. Archibald will need the Crown " "for the final battle against Roland." msgstr "" "Знайдіть Корону перш, ніж це зроблÑть герої Роланда. Ðрчибальду знадобитьÑÑ " "Корона у фінальній битві проти Роланда." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Roland to win the war, and be sure " "not to lose Archibald in the fight!" msgstr "" "Це фінальна битва. Як Ñ– ви, ваші вороги озброєні до зубів, Ñ– вÑÑ– вони у " "Ñоюзі проти ваÑ. Захопіть Роланда щоб Ñкінчити війну, та переконайтеÑÑŒ, що " "ви не загубите Ðрчибальда у битві!" msgid "Arrow's Flight" msgstr "Політ Стріли" msgid "Island of Chaos" msgstr "ОÑтрів ХаоÑу" msgid "The Abyss" msgstr "БезоднÑ" msgid "Uprising" msgstr "ПовÑтаннÑ" msgid "Aurora Borealis" msgstr "Північне СÑйво" msgid "Betrayal's End" msgstr "Кінець Зради" msgid "Corruption's Heart" msgstr "Серце Скверни" msgid "The Giant's Pass" msgstr "Перевал Велетнів" msgid "" "Subdue the unruly local lords in order to provide the Empire with facilities " "to operate in this region." msgstr "" "Підкоріть непокірних міÑцевих лордів, щоб забезпечити Імперії можливоÑті Ð´Ð»Ñ " "діÑльноÑті у цьому регіоні." msgid "" "Eliminate all opposition in this area. Then the first piece of the artifact " "will be yours." msgstr "" "УÑуньте веÑÑŒ Ñпротив у цих землÑÑ…. Тоді ви заволодієте першою чаÑтиною " "артефакту." msgid "" "The sorceresses to the northeast are rebelling! For the good of the empire " "you must quash their feeble uprising on your way to the mountains." msgstr "" "Чаклунки на північний Ñхід повÑтали! Ð—Ð°Ð´Ð»Ñ Ð±Ð»Ð°Ð³Ð° вашої імперії ви маєте " "придушити Ñ—Ñ… дрібне повÑÑ‚Ð°Ð½Ð½Ñ Ð½Ð° шлÑху до гір." msgid "" "Having prepared for your arrival, Kraeger has arranged for a force of " "necromancers to thwart your quest. You must capture the castle of Scabsdale " "before the first day of the third week, or the Necromancers will be too " "strong for you." msgstr "" "ПідготувавшиÑÑŒ до вашого прибуттÑ, Краегер зібрав Ñили некромантів щоб " "перешкодити вашим пошукам. Ви маєте захопити замок СкабÑдейл перш ніж " "наÑтане перший день третього тижнÑ, інакше Ðекроманти Ñтануть надто " "могутніми Ð´Ð»Ñ Ð²Ð°Ñ." msgid "" "The barbarian despot in this area is, as yet, ignorant of your presence. " "Quickly, build up your forces before you are discovered and attacked! Secure " "the region by subduing all enemy forces." msgstr "" "ВарварÑький деÑпот, що править цими землÑми, доÑÑ– не знає про вашу " "приÑутніÑть. Якнайшвидше нароÑтіть Ñвої Ñили перш, ніж Ð²Ð°Ñ Ñ€Ð¾Ð·ÐºÑ€Ð¸ÑŽÑ‚ÑŒ та " "атакують! ЗахиÑтіть регіон, впокоривши вÑÑ– ворожі Ñили." msgid "" "The Empire is weak in this region. You will be unable to completely subdue " "all forces in this area, so take what you can before reprisal strikes. " "Remember, your true goal is to claim the Helmet of Anduran." msgstr "" "Ð†Ð¼Ð¿ÐµÑ€Ñ–Ñ Ñлабка у цьому регіоні. Ви не зможете здолати вÑÑ– наÑвні ворожі " "Ñили, тому хапайте вÑе, що зможете перш, ніж вам завдадуть удару у " "відповідь. Пам'Ñтайте, що ваша головна мета - заволодіти Шоломом Ðндурана." msgid "For the good of the Empire, eliminate Kraeger." msgstr "Ðа благо Імперії знищте Краегера." msgid "" "At last, you have the opportunity and the facilities to rid the Empire of " "the necromancer's evil. Eradicate them completely, and you will be sung as a " "hero for all time." msgstr "" "Ðарешті, у Ð²Ð°Ñ Ñ” можливіÑть та заÑоби щоб позбавити Імперію від зла " "некромантів. Викорініть Ñ—Ñ…, щоб Ñ– Ñліду не лишилоÑÑŒ, Ñ– Ð²Ð°Ñ Ð¾Ñпівуватимуть Ñк " "Ð³ÐµÑ€Ð¾Ñ Ð²ÑÑ–Ñ… чаÑів." msgid "Border Towns" msgstr "Прикордонні МіÑта" msgid "Conquer and Unify" msgstr "Завойовувати та Об'єднувати" msgid "Crazy Uncle Ivan" msgstr "Божевільний ДÑдько Іван" msgid "The Wayward Son" msgstr "Блудний Син" msgid "Ivory Gates" msgstr "Брама Слонової КіÑтки" msgid "The Elven Lands" msgstr "ЕльфійÑькі Землі" msgid "The Epic Battle" msgstr "Епічна Битва" msgid "The Southern War" msgstr "Південна Війна" msgid "" "Conquer and unite all the enemy tribes. Don't lose the hero Jarkonas, the " "forefather of all descendants." msgstr "" "Завоюйте та об'єднайте ворожі племена. Ðе втратьте Ð³ÐµÑ€Ð¾Ñ Ð”Ð¶Ð°Ñ€ÐºÐ¾Ð½Ð°Ñа, " "прабатька вÑÑ–Ñ… нащадків." msgid "" "Your rival, the Kingdom of Harondale, is attacking weak towns on your " "border! Recover from their first strike and crush them completely!" msgstr "" "Ваш Ñуперник, КоролівÑтво Харондейл, атакує Ñлабкі міÑта на ваших кордонах! " "ОговтайтеÑÑŒ від Ñ—Ñ… першого удару та розбийте Ñ—Ñ… вщент!" msgid "" "Find your wayward son Joseph who is rumored to be living in the desolate " "lands. Do it before the first day of the third month or it will be of no " "help to your family." msgstr "" "Знайдіть вашого блудного Ñина Джозефа, що, за чутками, живе у ÑпуÑтошених " "землÑÑ…. Зробіть це перш, ніж наÑтане перший день третього міÑÑцÑ, інакше " "вашій родині вже не можна буде допомогти." msgid "" "Rescue your crazy uncle Ivan. Find him before the first day of the fourth " "month or it will be no help to your kingdom." msgstr "" "Ð’Ñ€Ñтуйте вашого божевільного дÑдька Івана. Знайдіть його до наÑÑ‚Ð°Ð½Ð½Ñ Ð¿ÐµÑ€ÑˆÐ¾Ð³Ð¾ " "Ð´Ð½Ñ Ñ‡ÐµÑ‚Ð²ÐµÑ€Ñ‚Ð¾Ð³Ð¾ міÑÑцÑ, інакше вашому королівÑтву вже не можна буде допомогти." msgid "" "Destroy the barbarians who are attacking the southern border of your " "kingdom! Recover your fallen towns, and then invade the jungle kingdom. " "Leave no enemy standing." msgstr "" "Знищте варварів, що атакують південні кордони вашого королівÑтва! Відновіть " "ваші захоплені міÑта, а тоді наÑтупайте на королівÑтво у джунглÑÑ…. Ðе " "лишайте жодного ворога в живих." msgid "Retake the castle of Ivory Gates, which has fallen due to treachery." msgstr "Поверніть замок Брама Слонової КіÑтки, що був втрачений через зраду." msgid "" "Gain the favor of the elves. They will not allow trees to be chopped down, " "so they will send you wood every 2 weeks. You must complete your mission " "before the first day of the seventh month, or the kingdom will surely fall." msgstr "" "Завоюйте прихильніÑть ельфів. Вони не дозволÑють рубати дерева, однак " "надÑилатимуть вам деревину кожні 2 тижні. Ви маєте виконати вашу міÑÑ–ÑŽ до " "першого Ð´Ð½Ñ Ñьомого міÑÑцÑ, інакше королівÑтво напевне загине." msgid "" "This is the final battle against your rival kingdom of Harondale. Eliminate " "everyone, and don't lose the hero Jarkonas VI." msgstr "" "Це оÑÑ‚Ð°Ð½Ð½Ñ Ð±Ð¸Ñ‚Ð²Ð° проти вашого Ñуперника - королівÑтва Харондейл. Винищте " "уÑÑ–Ñ…, але не втратьте Ð³ÐµÑ€Ð¾Ñ Ð”Ð¶Ð°Ñ€ÐºÐ¾Ð½Ð°Ñа ШоÑтого." msgid "Fount of Wizardry" msgstr "Джерело Магії" msgid "Power's End" msgstr "Кінець МогутноÑті" msgid "The Eternal Scrolls" msgstr "Вічні Сувої" msgid "The Shrouded Isles" msgstr "Закутані ОÑтрови" msgid "" "Your mission is to vanquish the warring mages in the magical Shrouded Isles. " "The completion of this task will give you a fighting chance against your " "rivals." msgstr "" "Ваше Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ - знищити ворогуючих магів на магічних Закутаних ОÑтровах. " "Ð’Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð´Ð°Ñть вам ÑˆÐ°Ð½Ñ Ð¿ÐµÑ€ÐµÐ¼Ð¾Ð³Ñ‚Ð¸ ваших Ñуперників." msgid "" "The location of the great library has been discovered! You must make your " "way to it, and reclaim the city of Chronos in which it lies." msgstr "" "МіÑÑ†ÐµÐ·Ð½Ð°Ñ…Ð¾Ð´Ð¶ÐµÐ½Ð½Ñ Ð²ÐµÐ»Ð¸ÐºÐ¾Ñ— бібліотеки розкрите! Ви маєте проклаÑти Ñвій шлÑÑ… " "до неї, та повернути міÑто ХроноÑ, в Ñкому вона розташована." #, fuzzy msgid "" "Find the Orb of negation, which is said to be buried in this land. There are " "clues inscribed on stone obelisks which will help lead you to your prize. " "Find the Orb before the first day of the sixth month, or your rivals will " "surely have gotten to the fount before you." msgstr "" "Знайдіть Сферу ЗапереченнÑ, що, Ñк кажуть, похована деÑÑŒ у цих землÑÑ…. " "Підказки виÑічені на кам'Ñних обеліÑках, що допоможуть вам доÑÑгти мети. " "Знайдіть Сферу раніше, ніж мине шоÑтий міÑÑць, інакше ваші Ñуперники точно " "знайдуть Ñ—Ñ— раніше за ваÑ." msgid "" "You must take control of the castle of Magic, where the fount of wizardry " "lies. Do this and your victory will be supreme." msgstr "" "Ви муÑите захопити замок МагіÑ, у Ñкому знаходитьÑÑ Ð”Ð¶ÐµÑ€ÐµÐ»Ð¾ Магії. Зробіть " "це, Ñ– ваша перемога буде оÑтаточною." msgid "Blood is Thicker" msgstr "Кров ГуÑтіша" msgid "King and Country" msgstr "Король та Країна" msgid "Pirate Isles" msgstr "ПіратÑькі ОÑтрови" msgid "Stranded" msgstr "Ðа Мілині" msgid "" "Capture the town on the island off the southeast shore in order to construct " "a boat and travel back towards the mainland. Do not lose the hero Gallavant." msgstr "" "Захопіть міÑто на оÑтрові Ð±Ñ–Ð»Ñ Ð¿Ñ–Ð²Ð´ÐµÐ½Ð½Ð¾-Ñхідного берега, щоб збудувати " "корабель та відбути назад на материк. Ðе втратьте Ð³ÐµÑ€Ð¾Ñ - Галлаванта." msgid "" "Find and defeat Martine, the pirate leader, who resides in Pirates Cove. Do " "not lose Gallavant or your quest will be over." msgstr "" "Знайдіть та здолайте Мартіна, піратÑького ватага, що мешкає у ПіратÑькій " "Бухті. Ðе втратьте Галлаванта, інакше ваші пошуки завершатьÑÑ." msgid "" "Eliminate all the other forces who oppose the rule of Lord Alberon. " "Gallavant must not die." msgstr "" "Знищте вÑÑ– Ñили, що протиÑтоÑть Лорду Ðльберону. Галлавант муÑить вціліти." msgid "" "Overthrow the entrenched monarchy of Lord Alberon, and claim all the land in " "your name. Gallavant must not die." msgstr "" "Поваліть уÑталену монархію Лорда Ðльберона, та захопіть цей край Ð´Ð»Ñ Ñебе. " "Галлавант муÑить вціліти." msgid " bane" msgstr " згуба" msgid " alliance" msgstr " альÑнÑ" msgid "Carry-over forces" msgstr "Збережені війÑька" msgid " bonus" msgstr " бонуÑ" msgid " defeated" msgstr " подоланий" msgid " will always run away from your army." msgstr " завжди тікатимуть від вашого війÑька." msgid " will be willing to join your army." msgstr " завжди волітимуть приєднатиÑÑŒ до вашого війÑька." msgid "\"%{artifact}\" artifact will be carried over the scenario." msgstr "Ðртефакт \"%{artifact}\" буде перенеÑений до Ñценарію." msgid "The army will be carried over the scenario." msgstr "ВійÑько буде перенеÑене до Ñценарію." msgid "The kingdom will have +%{count} %{resource} each day." msgstr "КоролівÑтво отримає додатковий прибуток: + %{count} %{resource} щоднÑ." msgid "\"%{spell}\" spell will be carried over the scenario." msgstr "ЗаклÑÑ‚Ñ‚Ñ \"%{spell}\" буде перенеÑено до Ñценарію." msgid "%{hero} can be hired in the scenario." msgstr "Герой %{hero} буде доÑтупним Ð´Ð»Ñ Ð½Ð°Ð¹Ð¼Ñƒ у Ñценарії." msgid "" "%{hero} has been defeated and will not appear in the subsequent scenarios." msgstr "" "Герой %{hero} був подоланий, Ñ– не зможе з'ÑвитиÑÑ Ñƒ подальших ÑценаріÑÑ…." msgid "The dwarves recognize their allies and gladly join your forces." msgstr "" "Гноми розпізнали Ñвого Ñоюзника, та з радіÑтю приєднаютьÑÑ Ð´Ð¾ ваших Ñил." msgid "The ogres recognize you as the Dwarfbane and lumber over to join you." msgstr "Огри впізнали у Ð²Ð°Ñ Ð“Ð½Ð¾Ð¼Ð¾Ð³ÑƒÐ±Ñ†Ñ, Ñ– воліють приєднатиÑÑŒ до ваÑ." msgid "" "The dragons, snarling and growling, agree to join forces with you, their " "'Ally'." msgstr "" "З незадоволеним гарчаннÑм дракони погоджуютьÑÑ Ð¿Ñ€Ð¸Ñ”Ð´Ð½Ð°Ñ‚Ð¸ÑÑŒ до ваÑ, Ñ—Ñ… " "\"Ñоюзника\"." msgid "" "As you approach the group of elves, their leader calls them all to " "attention. He shouts to them, \"Who of you is brave enough to join this " "fearless ally of ours?\" The group explodes with cheers as they run to join " "your ranks." msgstr "" "Коли ви наближаєтеÑÑŒ до групи ельфів, Ñ—Ñ… лідер проÑить уваги та звертаєтьÑÑ " "до них: \"Хто з Ð²Ð°Ñ Ð´Ð¾Ñтатньо хоробрий, щоб приєднатиÑÑŒ до нашого " "безÑтрашного Ñоюзника?\" Група вибухає радіÑними вітаннÑми, вливаючиÑÑŒ до " "ваших лав." msgid "" "The dwarves hail you, \"Any friend of Roland is a friend of ours. You may " "pass.\"" msgstr "Гноми вітають ваÑ: \"Друзі Роланда - наші друзі. Ви можете пройти\"." msgid "" "The ogres give you a grunt of recognition, \"Archibald's allies may pass.\"" msgstr "" "Огри Ñхвально рохкають, впізнавши ваÑ: \"Союзники Ðрчібальда можуть пройти\"." msgid "" "The dragons see you and call out. \"Our alliance with Archibald compels us " "to join you. Unfortunately you have no room. A pity!\" They quickly scatter." msgstr "" "Дракони, помітивши ваше війÑько, звернулиÑÑŒ до ваÑ: \"Ðаш альÑÐ½Ñ Ð· " "Ðрчібальдом змушує Ð½Ð°Ñ Ð¿Ñ€Ð¸Ñ”Ð´Ð½Ð°Ñ‚Ð¸ÑÑŒ до ваÑ. Шкода, що у Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” міÑцÑ. Як " "прикро!\" - з цими Ñловами вони швидко розлітаютьÑÑ Ð³ÐµÑ‚ÑŒ." msgid "" "The elves stand at attention as you approach. Their leader calls to you and " "says, \"Let us not impede your progress, ally! Move on, and may victory be " "yours.\"" msgstr "" "Ельфи уважно дивлÑтьÑÑ Ð½Ð° ваÑ, поки ви наближаєтеÑÑŒ. Їх лідер звернувÑÑ Ð´Ð¾ " "ваÑ: \"Ми не заважатимемо вашому проÑуванню, Ñоюзнику! Йдіть вперед, Ñ– нехай " "перемога буде за вами\"." msgid "\"The Dwarfbane!!!!, run for your lives.\"" msgstr "\"Гномогубець!!!! РÑтуйтеÑÑŒ хто може!\"" msgid "campaignBonus|Animate Dead" msgstr "ÐžÐ¶Ð¸Ð²Ð»ÐµÐ½Ð½Ñ ÐœÐµÑ€Ñ†Ñ–Ð²" msgid "campaignBonus|Chain Lightning" msgstr "Ланцюгова БлиÑкавка" msgid "campaignBonus|Fireblast" msgstr "ВогнÑний Вибух" msgid "campaignBonus|Mass Curse" msgstr "МаÑове ПроклÑттÑ" msgid "campaignBonus|Mass Haste" msgstr "МаÑовий ПоÑпіх" msgid "campaignBonus|Mirror Image" msgstr "Дзеркальний Фантом" msgid "campaignBonus|Resurrect" msgstr "ВоÑкреÑіннÑ" msgid "campaignBonus|Steelskin" msgstr "Сталева Шкіра" msgid "campaignBonus|Summon Earth" msgstr "Виклик Землі" msgid "campaignBonus|View Heroes" msgstr "ОглÑд Героїв" msgid "campaignBonus|Ballista" msgstr "БаліÑта" msgid "campaignBonus|Black Pearl" msgstr "Чорна Перлина" msgid "campaignBonus|Caster's Bracelet" msgstr "БраÑлет ЗаклинателÑ" msgid "campaignBonus|Defender Helm" msgstr "Шолом ЗахиÑника" msgid "campaignBonus|Breastplate" msgstr "Ðагрудник" msgid "campaignBonus|Dragon Sword" msgstr "Меч Дракона" msgid "campaignBonus|Fizbin Medal" msgstr "Медаль Фізбін" msgid "campaignBonus|Foremost Scroll" msgstr "Древній Сувій" msgid "campaignBonus|Gauntlets" msgstr "РукавицÑ" msgid "campaignBonus|Hideous Mask" msgstr "Мерзенна МаÑка" msgid "campaignBonus|Mage's Ring" msgstr "Магічний ПерÑтень" msgid "campaignBonus|Major Scroll" msgstr "Великий Сувій" msgid "campaignBonus|Medal of Honor" msgstr "Медаль за Відзнаку" msgid "campaignBonus|Medal of Valor" msgstr "Медаль за Відвагу" msgid "campaignBonus|Minor Scroll" msgstr "Малий Сувій" msgid "campaignBonus|Nomad Boots" msgstr "Чоботи Кочівника" msgid "campaignBonus|Power Axe" msgstr "Силова Сокира" msgid "campaignBonus|Spiked Shield" msgstr "ШпичаÑтий Щит" msgid "campaignBonus|Stealth Shield" msgstr "Ðезримий Щит" msgid "campaignBonus|Tax Lien" msgstr "Податкова ЗаÑтава" msgid "campaignBonus|Thunder Mace" msgstr "Громова Булава" msgid "campaignBonus|Traveler's Boots" msgstr "Чоботи Мандрівника" msgid "campaignBonus|White Pearl" msgstr "Біла Перлина" msgid "campaignBonus|Basic Archery" msgstr "Базова Стрільба" msgid "campaignBonus|Advanced Archery" msgstr "ПроÑунута Стрільба" msgid "campaignBonus|Expert Archery" msgstr "ЕкÑпертна Стрільба" msgid "campaignBonus|Basic Ballistics" msgstr "Базова БаліÑтика" msgid "campaignBonus|Advanced Ballistics" msgstr "ПроÑунута БаліÑтика" msgid "campaignBonus|Expert Ballistics" msgstr "ЕкÑпертна БаліÑтика" msgid "campaignBonus|Basic Diplomacy" msgstr "Базова ДипломатіÑ" msgid "campaignBonus|Advanced Diplomacy" msgstr "ПроÑунута ДипломатіÑ" msgid "campaignBonus|Expert Diplomacy" msgstr "ЕкÑпертна ДипломатіÑ" msgid "campaignBonus|Basic Eagle Eye" msgstr "Базовий Орлиний Зір" msgid "campaignBonus|Advanced Eagle Eye" msgstr "ПроÑунутий Орлиний Зір" msgid "campaignBonus|Expert Eagle Eye" msgstr "ЕкÑпертний Орлиний Зір" msgid "campaignBonus|Basic Estates" msgstr "Базова ШлÑхетніÑть" msgid "campaignBonus|Advanced Estates" msgstr "ПроÑунута ШлÑхетніÑть" msgid "campaignBonus|Expert Estates" msgstr "ЕкÑпертна ШлÑхетніÑть" msgid "campaignBonus|Basic Leadership" msgstr "Базове ЛідерÑтво" msgid "campaignBonus|Advanced Leadership" msgstr "ПроÑунуте ЛідерÑтво" msgid "campaignBonus|Expert Leadership" msgstr "ЕкÑпертне ЛідерÑтво" msgid "campaignBonus|Basic Logistics" msgstr "Базова ЛогіÑтика" msgid "campaignBonus|Advanced Logistics" msgstr "ПроÑунута ЛогіÑтика" msgid "campaignBonus|Expert Logistics" msgstr "ЕкÑпертна ЛогіÑтика" msgid "campaignBonus|Basic Luck" msgstr "Базова Удача" msgid "campaignBonus|Advanced Luck" msgstr "ПроÑунута Удача" msgid "campaignBonus|Expert Luck" msgstr "ЕÑпертна Удача" msgid "campaignBonus|Basic Mysticism" msgstr "Базовий МіÑтицизм" msgid "campaignBonus|Advanced Mysticism" msgstr "ПроÑунутий МіÑтицизм" msgid "campaignBonus|Expert Mysticism" msgstr "ЕкÑпертний МіÑтицизм" msgid "campaignBonus|Basic Navigation" msgstr "Базова ÐавігаціÑ" msgid "campaignBonus|Advanced Navigation" msgstr "ПроÑунута ÐавігаціÑ" msgid "campaignBonus|Expert Navigation" msgstr "ЕкÑпертна ÐавігаціÑ" msgid "campaignBonus|Basic Necromancy" msgstr "Базова ÐекромантіÑ" msgid "campaignBonus|Advanced Necromancy" msgstr "ПроÑунута ÐекромантіÑ" msgid "campaignBonus|Expert Necromancy" msgstr "ЕкÑпертна ÐекромантіÑ" msgid "campaignBonus|Basic Pathfinding" msgstr "Базовий Пошук шлÑху" msgid "campaignBonus|Advanced Pathfinding" msgstr "ПроÑунутий Пошук шлÑху" msgid "campaignBonus|Expert Pathfinding" msgstr "ЕкÑпертний Пошук шлÑху" msgid "campaignBonus|Basic Scouting" msgstr "Базова Розвідка" msgid "campaignBonus|Advanced Scouting" msgstr "ПроÑунута Розвідка" msgid "campaignBonus|Expert Scouting" msgstr "ЕкÑпертна Розвідка" msgid "campaignBonus|Basic Wisdom" msgstr "Базова МудріÑть" msgid "campaignBonus|Advanced Wisdom" msgstr "ПроÑунута МудріÑть" msgid "campaignBonus|Expert Wisdom" msgstr "ЕкÑпертна МудріÑть" msgid "" "The main hero will have \"%{artifact}\" artifact at the start of the " "scenario." msgstr "Головний герой отримає артефакт \"%{artifact}\" на початку Ñценарію." msgid "" "The kingdom will receive %{amount} additional %{resource} at the start of " "the scenario." msgstr "" "Ðа початку Ñценарію королівÑтво отримає на %{amount} од. більше реÑурÑу: " "%{resource}." msgid "" "The kingdom will have %{amount} less %{resource} at the start of the " "scenario." msgstr "" "Ðа початку Ñценарію королівÑтво отримає на %{amount} од. менше реÑурÑу: " "%{resource}." msgid "" "The main hero will have %{count} %{monster} at the start of the scenario." msgstr "Головний герой отримає %{count} %{monster} на початку Ñценарію." msgid "" "The main hero will have \"%{spell}\" spell at the start of the scenario." msgstr "Головний герой буде знати чари \"%{spell}\" на початку Ñценарію." msgid "The starting race of the scenario will be %{race}." msgstr "Початковою раÑою Ñценарію буде %{race}." msgid "" "The main hero will have additional %{count} %{skill} at the start of the " "scenario." msgstr "Головний герой матиме додатково %{count} %{skill} на початку Ñценарію." msgid "The main hero will have %{skill} at the start of the scenario." msgstr "Головний герой матиме %{skill} на початку Ñценарію." msgid "Roland" msgstr "Роланд" msgid "Archibald" msgstr "Ðрчібальд" #, fuzzy msgid "Price of Loyalty" msgstr "Ціна ВірноÑті" msgid "Voyage Home" msgstr "Подорож Додому" msgid "Wizard's Isle" msgstr "ОÑтрів Мага" msgid "Descendants" msgstr "Ðащадки" msgid "The %{building} produces %{monster}." msgstr "%{building} - житло, в Ñкому доÑтупні Ð´Ð»Ñ Ð½Ð°Ð¹Ð¼Ñƒ %{monster}." msgid "Requires:" msgstr "Потрібно:" msgid "Exit this menu without doing anything." msgstr "Вийти з меню без змін." msgid "Cannot build. You have already built here today." msgstr "Ðеможливо будувати. Ви вже будували тут Ñьогодні." msgid "For this action it is necessary to build a castle first." msgstr "Ð”Ð»Ñ Ñ†Ñ–Ñ”Ñ— дії треба Ñпочатку побудувати замок." msgid "Cannot build %{name} because the castle is too far away from an ocean." msgstr "Ðеможливо побудувати %{name}, бо замок далеко від води." msgid "disable build." msgstr "будівництво недоÑтупне." msgid "Cannot afford %{name}." msgstr "%{name} - Ñпоруда потребує більше реÑурÑів." msgid "%{name} is already built." msgstr "У міÑті вже Ñ” %{name}." msgid "Cannot build %{name}." msgstr "%{name} - будівництво неможливе." msgid "Build %{name}." msgstr "ЗвеÑти Ñпоруду: %{name}" msgid "Blackridge" msgstr "Блекрідж" msgid "Hillstone" msgstr "ХілÑтон" msgid "Pinehurst" msgstr "ПайнхерÑÑ‚" msgid "Whiteshield" msgstr "Вайтшилд" msgid "Woodhaven" msgstr "Вудхевен" msgid "Blackwind" msgstr "Блеквінд" msgid "Bloodreign" msgstr "Блодрейн" msgid "Dragontooth" msgstr "Драгонтуф" msgid "Greywind" msgstr "Грейвінд" msgid "Portsmith" msgstr "ПортÑміт" msgid "Atlantium" msgstr "Ðтлантіум" msgid "Middle Gate" msgstr "Мідлгейт" msgid "Sansobar" msgstr "Санзобар" msgid "Tundara" msgstr "Тундара" msgid "Vulcania" msgstr "ВулканіÑ" msgid "Baywatch" msgstr "Бейвотч" msgid "Fountainhead" msgstr "Фаунтейнхед" msgid "Vertigo" msgstr "Вертіго" msgid "Wildabar" msgstr "Вайлдабар" msgid "Winterkill" msgstr "Вінтеркілл" msgid "Brindamoor" msgstr "Бріндамур" msgid "Lakeside" msgstr "ЛейкÑайд" msgid "Nightshadow" msgstr "Ðайтшедоу" msgid "Olympus" msgstr "ОлімпуÑ" msgid "Sandcaster" msgstr "СендкаÑтер" msgid "Alamar" msgstr "Ðламар" msgid "Burlock" msgstr "Бурлок" msgid "Dragadune" msgstr "Драгадун" msgid "Kalindra" msgstr "Каліндра" msgid "Xabran" msgstr "КÑабран" msgid "Algary" msgstr "Ðлгарі" msgid "Basenji" msgstr "БаÑенджі" msgid "Blackfang" msgstr "Блекфанг" msgid "New Dawn" msgstr "Ðью-Давн" msgid "Sorpigal" msgstr "Сорпігал" msgid "Avone" msgstr "Ейвон" msgid "Big Oak" msgstr "Біг-Оук" msgid "Chandler" msgstr "Чандлер" msgid "Erliquin" msgstr "Ерліквін" msgid "Hampshire" msgstr "Гемпшир" msgid "Antioch" msgstr "Ðнтіох" msgid "Avalon" msgstr "Ðвалон" msgid "Roc Haven" msgstr "Рокхевен" msgid "South Mill" msgstr "Сауфмілл" msgid "Weed Patch" msgstr "Уідпатч" msgid "Brownston" msgstr "БраунÑтон" msgid "Hilltop" msgstr "Хіллтоп" msgid "Weddington" msgstr "Веддінгтон" msgid "Westfork" msgstr "ВеÑтфорк" msgid "Whittingham" msgstr "Уіттінгем" msgid "Cathcart" msgstr "Кеткарт" msgid "Elk's Head" msgstr "ЕлкÑхед" msgid "Roscomon" msgstr "РоÑкомон" msgid "Sherman" msgstr "Шерман" msgid "Yorksford" msgstr "ЙоркÑфорд" msgid "Blackburn" msgstr "Блекбурн" msgid "Blacksford" msgstr "БлекÑфорд" msgid "Burton" msgstr "Бьортон" msgid "Pig's Eye" msgstr "Пігз-Ðй" msgid "Viper's Nest" msgstr "ВайперÑнеÑÑ‚" msgid "Fenton" msgstr "Фентон" msgid "Lankershire" msgstr "Ланкершир" msgid "Lombard" msgstr "ЛомбардÑбург" msgid "Timberhill" msgstr "Тімберхілл" msgid "Troy" msgstr "ТроÑ" msgid "Forder Oaks" msgstr "ФордероукÑ" msgid "Meramec" msgstr "Мерамек" msgid "Quick Silver" msgstr "КвікÑілвер" msgid "Westmoor" msgstr "ВеÑтмур" msgid "Willow" msgstr "Віллоу" msgid "Corackston" msgstr "КоракÑтон" msgid "Sheltemburg" msgstr "Шелтембург" msgid "Cannot recruit - you already have a Hero in this town." msgstr "" msgid "Cannot recruit - you have too many Heroes." msgstr "" msgid "Cannot afford a Hero" msgstr "" msgid "There is no room in the garrison for this army." msgstr "" msgid "Fortifications" msgstr "УкріпленнÑ" msgid "Farm" msgstr "Ферма" msgid "Thatched Hut" msgstr "Солом'Ñна Хата" msgid "Archery Range" msgstr "Стрільбище" msgid "Upg. Archery Range" msgstr "Полігон" msgid "Blacksmith" msgstr "МайÑтернÑ" msgid "Upg. Blacksmith" msgstr "МайÑÑ‚ÐµÑ€Ð½Ñ Ð’ÐµÑ‚ÐµÑ€Ð°Ð½Ñ–Ð²" msgid "Armory" msgstr "ЗброÑрнÑ" msgid "Upg. Armory" msgstr "ЗброÑÑ€Ð½Ñ Ð’ÐµÑ‚ÐµÑ€Ð°Ð½Ñ–Ð²" msgid "Jousting Arena" msgstr "Іподром" msgid "Upg. Jousting Arena" msgstr "Турнірна Ðрена" msgid "Cathedral" msgstr "Катедра" msgid "Upg. Cathedral" msgstr "Собор" msgid "Coliseum" msgstr "Колізей" msgid "Garbage Heap" msgstr "Смітник" msgid "Hut" msgstr "Хата" msgid "Stick Hut" msgstr "Хижка" msgid "Upg. Stick Hut" msgstr "Халупа" msgid "Den" msgstr "Лігво" msgid "Adobe" msgstr "ГлинÑнка" msgid "Upg. Adobe" msgstr "Мазанка" msgid "Bridge" msgstr "МіÑÑ‚" msgid "Upg. Bridge" msgstr "Великий МіÑÑ‚" msgid "Pyramid" msgstr "Піраміда" msgid "Rainbow" msgstr "Райдуга" msgid "Crystal Garden" msgstr "Кришталевий Ñад" msgid "Treehouse" msgstr "Хатинка на Дереві" msgid "Cottage" msgstr "Хатинка" msgid "Upg. Cottage" msgstr "Котедж" msgid "Stonehenge" msgstr "Кромлех" msgid "Upg. Stonehenge" msgstr "Стоунгендж" msgid "Fenced Meadow" msgstr "Огороджена ГалÑвина" msgid "sorceress|Red Tower" msgstr "Червона Вежа" msgid "Dungeon" msgstr "ПідземеллÑ" msgid "Waterfall" msgstr "ВодоÑпад" msgid "Cave" msgstr "Печера" msgid "Crypt" msgstr "Крипта" msgid "Nest" msgstr "Гніздо" msgid "Maze" msgstr "Лабіринт" msgid "Upg. Maze" msgstr "Лабіринт Королів" msgid "Swamp" msgstr "Болото" msgid "Green Tower" msgstr "Зелена Вежа" msgid "warlock|Red Tower" msgstr "Червона Вежа" msgid "Black Tower" msgstr "Чорна Вежа" msgid "Library" msgstr "Бібліотека" msgid "Orchard" msgstr "Фруктовий Сад" msgid "Habitat" msgstr "ОÑелÑ" msgid "Pen" msgstr "Хлів" msgid "Foundry" msgstr "ЛиварнÑ" msgid "Upg. Foundry" msgstr "Ливарний Завод" msgid "Cliff Nest" msgstr "Скельне Гніздо" msgid "Ivory Tower" msgstr "Вежа Магів" msgid "Upg. Ivory Tower" msgstr "Вежа Ðрхімагів" msgid "Cloud Castle" msgstr "Хмарна ФортецÑ" msgid "Upg. Cloud Castle" msgstr "Хмарний Замок" msgid "Storm" msgstr "Шторм" msgid "Skull Pile" msgstr "Купа Черепів" msgid "Excavation" msgstr "Розкопки" msgid "Graveyard" msgstr "Цвинтар" msgid "Upg. Graveyard" msgstr "Кладовище" msgid "Upg. Pyramid" msgstr "ПроклÑта Піраміда" msgid "Mansion" msgstr "Садиба" msgid "Upg. Mansion" msgstr "Маєток" msgid "Mausoleum" msgstr "Мавзолей" msgid "Upg. Mausoleum" msgstr "ПроклÑтий Мавзолей" msgid "Laboratory" msgstr "ЛабораторіÑ" msgid "Shrine" msgstr "СвÑтинÑ" msgid "" "The Fortifications increase the toughness of the walls, increasing the " "number of turns it takes to knock them down." msgstr "" msgid "The Farm increases production of Peasants by %{count} per week." msgstr "" msgid "" "The Coliseum provides inspiring spectacles to defending troops, raising " "their morale by two during combat." msgstr "" msgid "The Garbage Heap increases production of Goblins by %{count} per week." msgstr "" msgid "The Rainbow increases the luck of the defending units by two." msgstr "" msgid "" "The Crystal Garden increases production of Sprites by %{count} per week." msgstr "" msgid "The Dungeon increases the income of the town by %{count} gold per day." msgstr "ÐŸÑ–Ð´Ð·ÐµÐ¼ÐµÐ»Ð»Ñ Ð¿Ñ–Ð´Ð²Ð¸Ñ‰ÑƒÑ” прибуток міÑта на %{count} в день." msgid "The Waterfall increases production of Centaurs by %{count} per week." msgstr "ВодоÑпад підвищує приріÑÑ‚ Кентаврів на %{count} за тиждень." msgid "" "The Library increases the number of spells in the Guild by one for each " "level of the guild." msgstr "" "Бібліотека підвищує кількіÑть заклинань в Гільдії на одне Ð´Ð»Ñ ÐºÐ¾Ð¶Ð½Ð¾Ð³Ð¾ Ñ€Ñ–Ð²Ð½Ñ " "гільдії." msgid "The Orchard increases production of Halflings by %{count} per week." msgstr "" msgid "The Storm adds +2 to the power of spells of a defending spell caster." msgstr "" msgid "The Skull Pile increases production of Skeletons by %{count} per week." msgstr "" msgid "Thieves' Guild" msgstr "Ð“Ñ–Ð»ÑŒÐ´Ñ–Ñ Ð—Ð»Ð¾Ð´Ñ–Ñ—Ð²" msgid "Tavern" msgstr "Таверна" msgid "Shipyard" msgstr "Верф" msgid "Well" msgstr "КолодÑзь" msgid "Statue" msgstr "СтатуÑ" msgid "Marketplace" msgstr "Ринок" msgid "Moat" msgstr "Рів" msgid "Castle" msgstr "Замок" msgid "Tent" msgstr "Ðамет" msgid "Captain's Quarters" msgstr "ÐŸÐ¾Ð¼ÐµÑˆÐºÐ°Ð½Ð½Ñ ÐšÐ°Ð¿Ñ–Ñ‚Ð°Ð½Ð°" msgid "Mage Guild, Level 1" msgstr "Ð“Ñ–Ð»ÑŒÐ´Ñ–Ñ ÐœÐ°Ð³Ñ–Ð², 1 рівень" msgid "Mage Guild, Level 2" msgstr "Ð“Ñ–Ð»ÑŒÐ´Ñ–Ñ ÐœÐ°Ð³Ñ–Ð², 2 рівень" msgid "Mage Guild, Level 3" msgstr "Ð“Ñ–Ð»ÑŒÐ´Ñ–Ñ ÐœÐ°Ð³Ñ–Ð², 3 рівень" msgid "Mage Guild, Level 4" msgstr "Ð“Ñ–Ð»ÑŒÐ´Ñ–Ñ ÐœÐ°Ð³Ñ–Ð², 4 рівень" msgid "Mage Guild, Level 5" msgstr "Ð“Ñ–Ð»ÑŒÐ´Ñ–Ñ ÐœÐ°Ð³Ñ–Ð², 5 рівень" msgid "" "The Shrine increases the necromancy skill of all your necromancers by 10 " "percent." msgstr "СвÑÑ‚Ð¸Ð½Ñ Ð¿Ñ–Ð´Ð²Ð¸Ñ‰ÑƒÑ” Ð²Ð¼Ñ–Ð½Ð½Ñ Ðекромантії вÑÑ–Ñ… некромантів на 10 відÑотків." msgid "" "The Thieves' Guild provides information on enemy players. Thieves' Guilds " "can also provide scouting information on enemy towns. Additional Guilds " "provide more information." msgstr "" "Ð“Ñ–Ð»ÑŒÐ´Ñ–Ñ Ð·Ð»Ð¾Ð´Ñ–Ñ—Ð² надає інформацію щодо ворожих гравців. Ð“Ñ–Ð»ÑŒÐ´Ñ–Ñ Ð·Ð»Ð¾Ð´Ñ–Ñ—Ð² також " "надає розвідницьку інформацію про ворожі міÑта. Додаткові Гільдії надають " "більше інформації." msgid "The Tavern increases morale for troops defending the castle." msgstr "Таверна збільшує мораль захиÑників замка." msgid "The Shipyard allows ships to be built." msgstr "Верф дозволÑÑ” будувати кораблі." msgid "" "The Well increases the growth rate of all dwellings by %{count} creatures " "per week." msgstr "КолодÑзь підвищує приріÑÑ‚ в уÑÑ–Ñ… житлах на %{count} Ñ–Ñтот на тиждень." #, fuzzy msgid "The Statue increases the town's income by %{count} gold per day." msgstr "Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð¿Ñ–Ð´Ð²Ð¸Ñ‰ÑƒÑ” прибуток Вашого міÑта на %{count} золота в день." msgid "The Left Turret provides extra firepower during castle combat." msgstr "Ліва Вежа завдає додаткову шкоду ворогові під Ñ‡Ð°Ñ Ð¾Ð±Ð»Ð¾Ð³Ð¸." msgid "The Right Turret provides extra firepower during castle combat." msgstr "Права Вежа завдає додаткову шкоду ворогу під Ñ‡Ð°Ñ Ð¾Ð±Ð»Ð¾Ð³Ð¸." msgid "" "The Marketplace can be used to convert one type of resource into another. " "The more marketplaces you control, the better the exchange rate." msgstr "" "Ринок можна викориÑтовувати щоб обмінÑти один реÑÑƒÑ€Ñ Ð½Ð° інший. Чим більше " "ринків ви контролюєте, тим краще ÐºÑƒÑ€Ñ Ð¾Ð±Ð¼Ñ–Ð½Ñƒ." msgid "" "The Moat slows attacking units. Any unit entering the moat must end its turn " "there and becomes more vulnerable to attack." msgstr "" "Рів Ñповільнює ворожі загони. Кожний загін, що зайде у рів, завершить хід у " "ньому Ñ– Ñтане вразливішим до нападу." #, fuzzy msgid "" "The Castle improves the town's defense and increases its income to %{count} " "gold per day." msgstr "" "Замок покращує захиÑÑ‚ міÑта та підвищує прибуток на %{count} золота в день." msgid "" "The Tent provides workers to build a castle, provided the materials and the " "gold are available." msgstr "" "Ðамет надає робітників, що будуватимуть замок, Ñкщо надати матеріали та " "золото." msgid "" "The Captain's Quarters provides a captain to assist in the castle's defense " "when no hero is present." msgstr "" "КапітанÑький штаб надає капітана, що допомагає захищати замок за відÑутноÑті " "героÑ." msgid "" "The Mage Guild allows heroes to learn spells and replenish their spell " "points." msgstr "Ð“Ñ–Ð»ÑŒÐ´Ñ–Ñ ÐœÐ°Ð³Ñ–Ð² дозволÑÑ” героÑм вивчати чари та відновлювати ману." msgid "Recruit %{name}" msgstr "ÐайнÑти %{name}" msgid "Month: %{month}, Week: %{week}, Day: %{day}" msgstr "МіÑÑць: %{month}, Тиждень: %{week}, День: %{day}" msgid "" "You must purchase a spell book to use the mage guild, but you currently have " "no room for a spell book. Try giving one of your artifacts to another hero." msgstr "" "Ви маєте придбати книгу чарів, щоб викориÑтовувати гільдію магів, але наразі " "у Ð’Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” вільного міÑÑ†Ñ Ð¿Ñ–Ð´ книгу. Спробуйте передати один із артефактів " "іншому герою." msgid "Click to show next town." msgstr "ÐатиÑніть щоб побачити наÑтупне міÑто." msgid "Show next town" msgstr "Показати наÑтупне міÑто" msgid "Click to show previous town." msgstr "ÐатиÑніть щоб побачити попереднє міÑто." msgid "Show previous town" msgstr "Показати попереднє міÑто" msgid "This town may not be upgraded to a castle." msgstr "Це МіÑто не можна покращити до Замку." msgid "Town" msgstr "МіÑто" msgid "Exit Castle" msgstr "Вийти з Замку" msgid "Exit Town" msgstr "Вийти з МіÑта" msgid "Show Income" msgstr "" msgid "View Hero" msgstr "" msgid "The above spells are available here." msgstr "" msgid "The spells the hero can learn have been added to their book." msgstr "" msgid "A generous tip for the barkeep yields the following rumor:" msgstr "" msgid "Recruit Hero" msgstr "ÐайнÑти ГероÑ" msgid "%{name} is a level %{value} %{race} " msgstr "%{name} - %{race} Ñ€Ñ–Ð²Ð½Ñ %{value} " msgid "with %{count} artifacts." msgstr "з %{count} артефактами." msgid "with 1 artifact." msgstr "з одним артефактом." msgid "without artifacts." msgstr "без артефактів." msgid "Recruit %{name} the %{race}" msgstr "" msgid "" "'Spread' combat formation spreads the castle's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" msgid "" "'Grouped' combat formation bunches the castle's units together in the center " "of the castle's side of the battlefield." msgstr "" msgid "Spread Formation" msgstr "" msgid "Grouped Formation" msgstr "" msgid "Set garrison combat formation to 'Spread'" msgstr "" msgid "Set garrison combat formation to 'Grouped'" msgstr "" msgid "Exit Castle Options" msgstr "" msgid "Castle Options" msgstr "" msgid "Not enough resources to recruit creatures." msgstr "" msgid "You are unable to recruit at this time, your ranks are full." msgstr "" msgid "No creatures available for purchase." msgstr "" msgid "Recruit Creatures" msgstr "ÐайнÑти Створінь" msgid "Max" msgstr "МакÑ" msgid "Hire all creatures in the town." msgstr "" msgid "Available" msgstr "" msgid "Town Population Information and Statistics" msgstr "Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ñ‚Ð° ÑтатиÑтика по наÑеленню міÑÑ‚" msgid "Damage" msgstr "" msgid "HP" msgstr "" msgid "Growth" msgstr "" msgid "week" msgstr "тиждень" msgid "View World" msgstr "" msgid "View the entire world." msgstr "" msgid "Puzzle" msgstr "" msgid "View the obelisk puzzle." msgstr "" msgid "Scenario Information" msgstr "Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ð¾ Ñценарію" msgid "View information on the scenario you are currently playing." msgstr "" msgid "Dig for the Ultimate Artifact." msgstr "" msgid "Digging" msgstr "" msgid "Arena" msgstr "" msgid "" "You enter the arena and face a pack of vicious lions. You handily defeat " "them, to the wild cheers of the crowd. Impressed by your skill, the aged " "trainer of gladiators agrees to train you in a skill of your choice." msgstr "" msgid "Attack Skill" msgstr "Ðтака" msgid "Defense Skill" msgstr "ЗахиÑÑ‚" msgid "Shots" msgstr "" msgid "Shots Left" msgstr "" msgid "Hit Points" msgstr "" msgid "Hit Points Left" msgstr "" msgid "You can't afford to upgrade your troops!" msgstr "" msgid "" "Your troops can be upgraded, but it will cost you dearly. Do you wish to " "upgrade them?" msgstr "" msgid "Are you sure you want to dismiss this army?" msgstr "" msgid "Upgrade" msgstr "" msgid "Upgrade your troops." msgstr "" msgid "Dismiss" msgstr "" msgid "Dismiss this army." msgstr "" msgid "" "A group of %{monster} with a desire for greater glory wish to join you.\n" "Do you accept?" msgstr "" msgid "Followers" msgstr "" msgid "" "The %{monster} is swayed by your diplomatic tongue, and offers to join your " "army for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" msgid "" "The creatures are swayed by your diplomatic\n" "tongue, and make you an offer:\n" "\n" msgstr "" msgid "" "%{offer} of the %{total} %{monster} will join your army, and the rest will " "leave you alone, for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" msgid "" "All %{offer} of the %{monster} will join your army for the sum of %{gold} " "gold.\n" "Do you accept?" msgstr "" msgid "(Rate: %{percent})" msgstr "" msgid "off" msgstr "" msgid "Music" msgstr "Музика" msgid "Effects" msgstr "Ефекти" msgid "MIDI" msgstr "" msgid "MIDI Expansion" msgstr "" msgid "External" msgstr "" msgid "Music Type" msgstr "Тип музики" msgid "3D Audio" msgstr "" msgid "Toggle ambient music level." msgstr "" msgid "Toggle foreground sounds level." msgstr "" msgid "Change the type of music." msgstr "" msgid "Toggle 3D effects of foreground sounds." msgstr "" msgid "Build a new ship:" msgstr "Побудувати новий корабель:" msgid "Resource cost:" msgstr "" msgid "Total: " msgstr "Загалом: " msgid "Need: " msgstr "" msgid "Load Game" msgstr "Завантажити Гру" msgid "No save files to load." msgstr "Ðемає файлів Ð·Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ Ð´Ð»Ñ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ." msgid "New Game" msgstr "Ðова Гра" msgid "Start a single or multi-player game." msgstr "" msgid "Load a previously saved game." msgstr "Завантажити раніше збережену гру." msgid "Save Game" msgstr "Зберегти Гру" msgid "Save the current game." msgstr "Збережіть поточну гру." msgid "Quit" msgstr "Вихід" msgid "Quit out of Heroes of Might and Magic II." msgstr "Залишити Heroes of Might and Magic II." msgid "Language" msgstr "Мова" msgid "Graphics" msgstr "" msgid "Black & White" msgstr "" msgid "Mouse Cursor" msgstr "" msgid "Color" msgstr "" msgid "Text Support" msgstr "" msgid "Change the language of the game." msgstr "Змінити мову гри." msgid "Select Game Language" msgstr "Обрати мову гри" msgid "Change the graphics settings of the game." msgstr "Змінити графічні Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð³Ñ€Ð¸." msgid "Toggle colored cursor on or off. This is only an aesthetic choice." msgstr "" msgid "" "Toggle text support mode to output extra information about windows and " "events in the game." msgstr "" msgid "" "Map\n" "Difficulty" msgstr "" "СкладніÑть\n" "Мапи" msgid "" "Game\n" "Difficulty" msgstr "" "СкладніÑть\n" "Гри" msgid "Rating" msgstr "" msgid "Map Size" msgstr "Розмір Мапи" msgid "Opponents" msgstr "" msgid "Class" msgstr "" msgid "" "Victory\n" "Conditions" msgstr "" msgid "" "Loss\n" "Conditions" msgstr "" msgid "First select recipients!" msgstr "" msgid "You cannot select %{resource}!" msgstr "" msgid "Select count %{resource}:" msgstr "" msgid "Select Recipients" msgstr "" msgid "Your Funds" msgstr "" msgid "Planned Gift" msgstr "" msgid "Gift from %{name}" msgstr "" msgid "Resolution" msgstr "" msgid "Fullscreen" msgstr "" msgid "window|Mode" msgstr "" msgid "Windowed" msgstr "" msgid "V-Sync" msgstr "" msgid "on" msgstr "увімк." msgid "FPS" msgstr "" msgid "System Info" msgstr "СиÑтемні Параметри" msgid "Change the resolution of the game." msgstr "Змінити розділову здатніÑть гри." msgid "Select Game Resolution" msgstr "" msgid "Toggle between fullscreen and windowed modes." msgstr "" msgid "" "The V-Sync option can be enabled to resolve flickering issues on some " "monitors." msgstr "" msgid "Show extra information such as FPS and current time." msgstr "" msgid "Hotkey: " msgstr "" msgid "Category: " msgstr "" #, fuzzy msgid "Event: " msgstr "ПодіÑ" msgid "Hot Keys:" msgstr "" msgid "Evil" msgstr "" msgid "Good" msgstr "" msgid "Interface Type" msgstr "Тип ІнтерфейÑу" msgid "Hide" msgstr "" msgid "Show" msgstr "" msgid "Interface" msgstr "" msgid "Slow" msgstr "" msgid "Normal" msgstr "Звичайна" msgid "Fast" msgstr "" msgid "Very Fast" msgstr "" msgid "Scroll Speed" msgstr "" msgid "Toggle the type of interface you want to use." msgstr "" msgid "Toggle interface visibility." msgstr "" msgid "Toggle colored cursor on or off. This is only an esthetic choice." msgstr "" msgid "Sets the speed at which you scroll the window." msgstr "" msgid "Select Game Language:" msgstr "Обрати мову гри:" msgid "Click to choose the selected language." msgstr "" msgid "%{name} has gained a level." msgstr "" msgid "%{skill} +1" msgstr "" msgid "You have learned %{skill}." msgstr "" msgid "" "%{name} has gained a level.\n" "\n" "%{skill} +1" msgstr "" msgid "" "You may learn either:\n" "%{skill1}\n" "or\n" "%{skill2}" msgstr "" msgid "" "Please inspect our fine wares. If you feel like offering a trade, click on " "the items you wish to trade with and for." msgstr "" msgid "" "You have received quite a bargain. I expect to make no profit on the deal. " "Can I interest you in any of my other wares?" msgstr "" msgid "I can offer you %{count} for 1 unit of %{resfrom}." msgstr "" msgid "I can offer you 1 unit of %{resto} for %{count} units of %{resfrom}." msgstr "" msgid "Min" msgstr "Мін" msgid "Qty to trade" msgstr "" msgid "Trading Post" msgstr "" msgid "Your Resources" msgstr "" msgid "Available Trades" msgstr "" msgid "n/a" msgstr "" msgid "guarded by %{count} %{monster}" msgstr "" msgid "guarded by " msgstr "" msgid "(available: %{count})" msgstr "" msgid "(empty)" msgstr "" msgid "already learned" msgstr "" #, fuzzy msgid "treeOfKnowledge|free" msgstr "Дерево Знань" msgid "already claimed" msgstr "" msgid "not claimed" msgstr "" msgid "already knows this skill" msgstr "" msgid "already has max skills" msgstr "" msgid "(already visited)" msgstr "" msgid "(not visited)" msgstr "" msgid "%{color} Barrier" msgstr "" msgid "(tent visited)" msgstr "" msgid "%{color} Tent" msgstr "" msgid "Road" msgstr "" msgid "(digging ok)" msgstr "" msgid "(no digging)" msgstr "" msgid "penalty: %{cost}" msgstr "" msgid "Defenders:" msgstr "ЗахиÑники:" msgid "Unknown" msgstr "" msgid "%{name} (Level %{level})" msgstr "" msgid "Attack:" msgstr "Ðтака:" msgid "Defense:" msgstr "ЗахиÑÑ‚:" msgid "Spell Power:" msgstr "Сила Чарів:" msgid "Knowledge:" msgstr "ЗнаннÑ:" msgid "Spell Points:" msgstr "Одиниці Мани:" msgid "Move Points:" msgstr "Очки Руху:" msgid "Uncharted Territory" msgstr "" msgid "Cost per troop:" msgstr "" msgid "Available: %{count}" msgstr "" msgid "Number to buy:" msgstr "" msgid "Recruit selected monsters." msgstr "" msgid "Select maximum monsters to be recruited." msgstr "" msgid "Select only 1 monster to be recruited." msgstr "" msgid "Select Game Resolution:" msgstr "Обрати Розділову ЗдатніÑть:" msgid "Click to apply the selected resolution." msgstr "" msgid "Click to apply the entered text." msgstr "" msgid "Click to open the Virtual Keyboard dialog." msgstr "" msgid "Open Virtual Keyboard" msgstr "" msgid "How many troops to move?" msgstr "" msgid "Fast separation into slots:" msgstr "" msgid "Map: " msgstr "Мапа: " msgid "" "\n" "\n" "Month: " msgstr "" msgid ", Week: " msgstr "" msgid ", Day: " msgstr "" msgid "" "\n" "\n" "Location: " msgstr "" msgid "Click to save the current game." msgstr "" msgid "Click to load a previously saved game." msgstr "" msgid "Are you sure you want to delete file:" msgstr "" msgid "Warning!" msgstr "" msgid "File to Save:" msgstr "" msgid "File to Load:" msgstr "" msgid "Accept the choice made." msgstr "" msgid "%{color} %{race} hero" msgstr "" #, fuzzy msgid "Terrain object" msgstr "Порожнє міÑце" msgid "Select Skill:" msgstr "Оберіть Ðавички:" msgid "Select Spell:" msgstr "Оберіть ЗаклÑттÑ:" msgid "Select Artifact:" msgstr "Оберіть Ðртефакти:" msgid "Select Monster:" msgstr "Оберіть МонÑтра:" msgid "Select Hero:" msgstr "Оберіть ГероÑ:" #, fuzzy msgid "Select Treasure:" msgstr "Оберіть ГероÑ:" #, fuzzy msgid "Select Ocean Object:" msgstr "Оберіть Ðртефакти:" msgid "Castle/town placing" msgstr "" msgid "doubleLinedRace|Neutral" msgstr "" msgid "race|Random" msgstr "" msgid "Click to start placing the selected castle/town." msgstr "" #, fuzzy msgid "Click to select town placing." msgstr "ÐатиÑніть щоб побачити наÑтупне міÑто." msgid "Click to select castle placing." msgstr "" msgid "%{color} %{race} %{townOrCastle}" msgstr "" msgid "race|Neutral" msgstr "" msgid "You will place" msgstr "" msgid "Click to select this class." msgstr "" #, fuzzy msgid "Click to select this color." msgstr "ÐатиÑніть щоб побачити наÑтупне міÑто." #, fuzzy msgid "Select Dwelling:" msgstr "Оберіть ЗаклÑттÑ:" #, fuzzy msgid "Select Landscape Object:" msgstr "Оберіть Ðртефакти:" msgid "Mine placing" msgstr "" #, fuzzy msgid "" "Resource\n" "type:" msgstr "РеÑурÑ" #, fuzzy msgid "%{mineName} appearance:" msgstr "%{name} %{race}" msgid "Click to select %{object} as the resource generator to be placed." msgstr "" #, fuzzy msgid "Select Mountain Object:" msgstr "Оберіть Ðртефакти:" #, fuzzy msgid "Select Rock Object:" msgstr "Оберіть Ðртефакти:" #, fuzzy msgid "Select Tree Object:" msgstr "Оберіть Ðртефакти:" #, fuzzy msgid "Select Power Up Object:" msgstr "Оберіть Ðртефакти:" #, fuzzy msgid "Select Adventure Object:" msgstr "Оберіть Ðртефакти:" msgid "Map Type:\n" msgstr "Тип мапи:\n" msgid "The Succession Wars" msgstr "Війни за Спадщину" msgid "The Price of Loyalty" msgstr "Ціна ВірноÑті" #, fuzzy msgid "Resurrection" msgstr "ВоÑкреÑіннÑ" msgid "Lose all your heroes and towns." msgstr "" msgid "Lose a specific town." msgstr "" msgid "Lose a specific hero." msgstr "" msgid "Run out of time. Fail to win by a certain point." msgstr "" msgid "Loss Condition" msgstr "" msgid "Defeat all enemy heroes and towns." msgstr "" msgid "Capture a specific town." msgstr "" msgid "Defeat a specific hero." msgstr "" msgid "Find a specific artifact." msgstr "" msgid "Your side defeats the opposing side." msgstr "" msgid "Accumulate a large amount of gold." msgstr "" msgid "Victory Condition" msgstr "" msgid "Map difficulty:" msgstr "СкладніÑть мапи:" msgid "N" msgstr "" msgid "No maps exist at that size" msgstr "Ðемає мап такого розміру" msgid "Small Maps" msgstr "Маленькі Мапи" msgid "View only maps of size small (36 x 36)." msgstr "ДивитиÑÑ Ð»Ð¸ÑˆÐµ мапи малого розміру (36 x 36)." msgid "Medium Maps" msgstr "" msgid "View only maps of size medium (72 x 72)." msgstr "" msgid "Large Maps" msgstr "" msgid "View only maps of size large (108 x 108)." msgstr "" msgid "Extra Large Maps" msgstr "Ðадвеликі Мапи" msgid "View only maps of size extra large (144 x 144)." msgstr "Показати лише мапи надвеликого розміру (144 x 144)." msgid "All Maps" msgstr "УÑÑ– мапи" msgid "View all maps, regardless of size." msgstr "" msgid "Players Icon" msgstr "" msgid "" "Indicates how many players total are in the scenario. Any positions not " "occupied by humans will be occupied by computer players." msgstr "" msgid "" "Indicates whether the map\n" "is small (36 x 36), medium\n" "(72 x 72), large (108 x 108),\n" "or extra large (144 x 144)." msgstr "" msgid "Size Icon" msgstr "" msgid "" "Indicates whether the map is made for \"The Succession Wars\" or \"The Price " "of Loyalty\" version of the game." msgstr "" msgid "Map Type" msgstr "Тип мапи" msgid "Selected Name" msgstr "" msgid "The name of the currently selected map." msgstr "" msgid "Selected Map Difficulty" msgstr "" msgid "" "The map difficulty of the currently selected map. The map difficulty is " "determined by the scenario designer. More difficult maps might include more " "or stronger enemies, fewer resources, or other special conditions making " "things tougher for the human player." msgstr "" msgid "Selected Description" msgstr "" msgid "The description of the currently selected map." msgstr "" msgid "Jump" msgstr "" msgid "Hero Speed" msgstr "" msgid "Don't Show" msgstr "" msgid "Enemy Speed" msgstr "" msgid "Auto Resolve" msgstr "" msgid "Auto, No Spells" msgstr "" msgid "Battles" msgstr "" msgid "autoBattle|Manual" msgstr "" msgid "Change the speed at which your heroes move on the main screen." msgstr "" msgid "" "Sets the speed that A.I. heroes move at. You can also elect not to view A.I. " "movement at all." msgstr "" msgid "Change the interface settings of the game." msgstr "Змінити Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñ–Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñу гри." msgid "Interface Settings" msgstr "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð†Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñу" msgid "Toggle instant battle mode." msgstr "" msgid "Att." msgstr "" msgid "Def." msgstr "" msgid "Power" msgstr "" msgid "Knowl" msgstr "" msgid "1st" msgstr "" msgid "2nd" msgstr "" msgid "3rd" msgstr "" msgid "4th" msgstr "" msgid "5th" msgstr "" msgid "6th" msgstr "" msgid "Oracle: Player Rankings" msgstr "" msgid "Thieves' Guild: Player Rankings" msgstr "" msgid "Number of Towns:" msgstr "" msgid "Number of Castles:" msgstr "" msgid "Number of Heroes:" msgstr "" msgid "Gold in Treasury:" msgstr "" msgid "Wood & Ore:" msgstr "" msgid "Gems, Cr, Slf & Mer:" msgstr "" msgid "Obelisks Found:" msgstr "" msgid "Artifacts:" msgstr "" msgid "Total Army Strength:" msgstr "" msgid "Income:" msgstr "" msgid "Best Hero:" msgstr "" msgid "Best Hero Stats:" msgstr "" msgid "Personality:" msgstr "" msgid "Best Monster:" msgstr "" #, fuzzy msgid "Map filename" msgstr "Розмір Мапи" msgid "" "Are you sure you want to load a new map? (Any unsaved changes to the current " "map will be lost.)" msgstr "" msgid "" "Are you sure you want to create a new map? (Any unsaved changes to the " "current map will be lost.)" msgstr "" msgid "" "Create a new map, either from scratch or using the random map generator." msgstr "" msgid "New Map" msgstr "Ðова Гра" msgid "Load Map" msgstr "Завантажити Гру" msgid "Load an existing map." msgstr "" msgid "Save Map" msgstr "Зберегти Мапу" msgid "Save the current map." msgstr "Збережіть поточну мапу." msgid "Quit out of the map editor." msgstr "" msgid "Objects cannot be placed outside the map." msgstr "" msgid "Monsters cannot be placed on water." msgstr "" msgid "Choose a tile which does not contain any objects." msgstr "" msgid "Treasures cannot be placed on water." msgstr "" msgid "Heroes cannot be placed on water." msgstr "" msgid "" "A maximum of %{count} heroes of the same color can be placed on the map." msgstr "" msgid "Artifacts cannot be placed on water." msgstr "" #, fuzzy msgid "Mountains cannot be placed on water." msgstr "Це МіÑто не можна покращити до Замку." #, fuzzy msgid "Rocks cannot be placed on water." msgstr "Це МіÑто не можна покращити до Замку." #, fuzzy msgid "Trees cannot be placed on water." msgstr "Це МіÑто не можна покращити до Замку." msgid "Ocean object must be placed on water." msgstr "" #, fuzzy msgid "Landscape objects cannot be placed on water." msgstr "Це МіÑто не можна покращити до Замку." #, fuzzy msgid "Towns cannot be placed on water." msgstr "Це МіÑто не можна покращити до Замку." #, fuzzy msgid "Mines cannot be placed on water." msgstr "Це МіÑто не можна покращити до Замку." #, fuzzy msgid "Dwellings cannot be placed on water." msgstr "Це МіÑто не можна покращити до Замку." #, fuzzy msgid "Power-ups cannot be placed on water." msgstr "Це МіÑто не можна покращити до Замку." #, fuzzy msgid "Adventure objects cannot be placed on water." msgstr "Це МіÑто не можна покращити до Замку." msgid "Used to place %{object}." msgstr "" #, fuzzy msgid "Select object type" msgstr "Оберіть Ðртефакти:" #, fuzzy msgid "" "Click here to\n" "select a monster." msgstr "ÐатиÑніть щоб побачити наÑтупне міÑто." #, fuzzy msgid "" "Click here to\n" "select another monster." msgstr "Будь лаÑка, оберіть іншого героÑ." msgid "" "Cell\n" "Details" msgstr "" msgid "Roads" msgstr "" msgid "Streams" msgstr "" msgid "Erase" msgstr "" msgid "Mountains" msgstr "Гори" #, fuzzy msgid "Rocks" msgstr "СкелÑ" msgid "Trees" msgstr "Дерева" #, fuzzy msgid "Water Objects" msgstr "Об'єкти ПуÑтки" msgid "Miscellaneous" msgstr "" msgid "Artifacts" msgstr "" #, fuzzy msgid "Dwellings" msgstr "ÐапівроÑлики" msgid "Mines" msgstr "Копальні" #, fuzzy msgid "Power-ups" msgstr "Квіти" msgid "Treasures" msgstr "Скарби" msgid "Towns" msgstr "МіÑта" msgid "Heroes" msgstr "" msgid "editorErasure|Terrain" msgstr "" msgid "editorErasure|Castles" msgstr "" msgid "editorErasure|Monsters" msgstr "" msgid "editorErasure|Heroes" msgstr "" msgid "editorErasure|Artifacts" msgstr "" msgid "editorErasure|Roads" msgstr "" msgid "editorErasure|Streams" msgstr "" msgid "editorErasure|Treasures" msgstr "" msgid "" "Draws terrain in\n" "%{size} by %{size} square increments." msgstr "" msgid "" "Erases objects in\n" "%{size} by %{size} square increments." msgstr "" msgid "Small Brush" msgstr "Маленька киÑть" msgid "Medium Brush" msgstr "" msgid "Large Brush" msgstr "" msgid "Area Fill" msgstr "" msgid "Used to click and drag for filling in large areas." msgstr "" msgid "Clear Area" msgstr "" msgid "Used to click and drag for clearing large areas." msgstr "" msgid "" "Costs %{rate} times normal movement for all heroes. (Pathfinding reduces or " "eliminates the penalty.)" msgstr "" msgid "Traversable only by boat." msgstr "" msgid "No special modifiers." msgstr "" msgid "Toggle the erasure of %{type} objects." msgstr "" msgid "" "Objects of this type will be deleted with the Erase tool. Left-click here to " "deselect this type. Press and hold this button to deselect all other object " "types." msgstr "" msgid "" "Objects of this type will NOT be deleted with the Erase tool. Left-click " "here to select this type. Press and hold this button to select all other " "object types." msgstr "" msgid "Terrain Mode" msgstr "" msgid "Used to draw the underlying grass, dirt, water, etc. on the map." msgstr "" msgid "Landscape Objects Mode" msgstr "" msgid "" "Used to place landscape objects (mountains, rocks, trees, etc.) on the map." msgstr "" msgid "Detail Mode" msgstr "" msgid "Used for special editing of monsters, heroes and towns." msgstr "" msgid "Adventure Objects Mode" msgstr "" msgid "" "Used to place adventure objects (artifacts, dwellings, mines, treasures, " "etc.) on the map." msgstr "" #, fuzzy msgid "Kingdom Objects Mode" msgstr "Об'єкти ЗаÑніжених Полонин" msgid "Used to place kingdom objects (towns, castles and heroes) on the map." msgstr "" #, fuzzy msgid "Monsters Mode" msgstr "МонÑтри" msgid "Used to place monsters on the map." msgstr "" msgid "Allows you to draw streams by clicking and dragging." msgstr "" msgid "Stream Mode" msgstr "" msgid "Allows you to draw roads by clicking and dragging." msgstr "" msgid "Road Mode" msgstr "" msgid "Erase Mode" msgstr "" msgid "Used to erase objects from the map." msgstr "" msgid "Change between zoom and normal view." msgstr "" msgid "Magnify" msgstr "" msgid "Undo" msgstr "" msgid "Undo your last action." msgstr "" msgid "Create a new map either from scratch or using the random map generator." msgstr "" msgid "Edit map title, description, and other general information." msgstr "" msgid "Specifications" msgstr "ÐалаштуваннÑ" msgid "File Options" msgstr "" msgid "" "Open the file options menu, where you can save or load maps, or quit out of " "the editor." msgstr "" msgid "View the editor system options, which let you customize the editor." msgstr "" msgid "Create a map that is %{size} squares wide and %{size} squares high." msgstr "" msgid "Cancel back to the New Map menu." msgstr "" msgid "Cancel back to the main menu." msgstr "" msgid "From Scratch" msgstr "" msgid "Start from scratch with a blank map." msgstr "" msgid "Create a randomly generated map." msgstr "" msgid "Random" msgstr "Випадкове міÑто" msgid "Cancel back to the Map Editor main menu." msgstr "" msgid "No maps available!" msgstr "" msgid "Warning" msgstr "" msgid "difficulty|Easy" msgstr "" msgid "difficulty|Normal" msgstr "" msgid "difficulty|Hard" msgstr "" msgid "difficulty|Expert" msgstr "" msgid "difficulty|Impossible" msgstr "" msgid "and more..." msgstr "" msgid "Easy" msgstr "" msgid "Hard" msgstr "" msgid "Campaign Difficulty" msgstr "" "СкладніÑть\n" "Кампанії" msgid "" "Choose this difficulty if you want to prefer game story over challenge. AI " "is weaker in comparison with normal difficulty." msgstr "" msgid "" "Choose this difficulty to enjoy the campaign as per the original design." msgstr "" msgid "" "Choose this difficulty if you want challenge. AI is stronger in comparison " "with normal difficulty." msgstr "" msgid "" "Congratulations!\n" "\n" "Days: %{days}\n" msgstr "" msgid "" "\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" msgid "" "Score: %{score}\n" "\n" "Rating:\n" "%{rating}" msgstr "" msgid "Start the selected scenario." msgstr "" msgid "View Intro" msgstr "" msgid "View Intro videos for the current state of the campaign." msgstr "" msgid "" "Select the campaign difficulty. This can be lowered at any point during the " "campaign." msgstr "" msgid "Restart the current scenario." msgstr "" msgid "Difficulty" msgstr "" "СкладніÑть\n" "Мапи" msgid "" "You have changed to a lower difficulty for the campaign. You will not be " "able to revert this after this point. The high score will be calculated " "based solely on the new difficulty. Do you want to proceed?" msgstr "" msgid "Are you sure you want to restart this scenario?" msgstr "" msgid "Campaign Scenario loading failure" msgstr "" msgid "Please make sure that campaign files are correct and present." msgstr "" msgid "Days spent" msgstr "" msgid "The number of days spent on this campaign." msgstr "" msgid "Project Coordination and Core Development" msgstr "" msgid "Development" msgstr "Розробка" msgid "Visit us at " msgstr "" msgid "QA and Support" msgstr "" msgid "Dev and Support" msgstr "" msgid "Special Thanks to" msgstr "" msgid "and many other contributors!" msgstr "" msgid "and many-many other supporters!" msgstr "" msgid "Support us at" msgstr "" msgid "local-donation-platform|https://www.patreon.com/fheroes2" msgstr "" msgid "Connect with us at" msgstr "" msgid "local-social-network|https://www.facebook.com/groups/fheroes2" msgstr "" msgid "Need help with the game?" msgstr "" msgid "Original project before 0.7" msgstr "" msgid "Heroes of Might and Magic II: The Succession Wars team" msgstr "Команда розробки Героїв Меча та Магії 2: Війна за Спадщину" msgid "Designed and Directed" msgstr "" msgid "Programming and Design" msgstr "" msgid "Executive Producer" msgstr "" msgid "Producer" msgstr "" msgid "Additional Design" msgstr "" msgid "Additional Programming" msgstr "" msgid "Musical Production" msgstr "" msgid "Music and Sound Design" msgstr "" msgid "Vocalists" msgstr "" msgid "Art Director" msgstr "" msgid "Assistant Art Director" msgstr "" msgid "Artists" msgstr "" msgid "QA Manager" msgstr "" msgid "QA" msgstr "" msgid "Writing" msgstr "" msgid "Manual and Helpfile" msgstr "" msgid "Scenarios" msgstr "" msgid "Heroes of Might and Magic II: The Price of Loyalty team" msgstr "Команда розробки Героїв Меча та Магії 2: Ціна ВірноÑті" msgid "Design Lead" msgstr "" msgid "Designers" msgstr "" msgid "Programming Lead" msgstr "" msgid "Art Lead" msgstr "" msgid "Playtesters" msgstr "" msgid "Designer" msgstr "" msgid "Producers" msgstr "" msgid "QA Managers" msgstr "Менеджери із Ð·Ð°Ð±ÐµÐ·Ð¿ÐµÑ‡ÐµÐ½Ð½Ñ ÑкоÑті" msgid "Sound Design" msgstr "" msgid "Town Themes" msgstr "" msgid "Alto Sax" msgstr "" msgid "Harpsichord and Piano" msgstr "" msgid "Basso Vocal" msgstr "" msgid "Soprano Vocal" msgstr "" msgid "Recorded at %{recordingStudio}" msgstr "" msgid "credits|Manual" msgstr "" msgid "German Consultant" msgstr "" msgid "Map Designers" msgstr "" msgid "Package Design" msgstr "" msgid "Your Name" msgstr "Ваше Ім'Ñ" msgid "Unknown Hero" msgstr "" msgid "Standard" msgstr "" msgid "View High Scores for Standard Maps." msgstr "" msgid "Campaign" msgstr "" msgid "View High Scores for Campaigns." msgstr "" msgid "hotkey|default okay event" msgstr "" msgid "hotkey|default cancel event" msgstr "" msgid "hotkey|default left" msgstr "" msgid "hotkey|default right" msgstr "" msgid "hotkey|default up" msgstr "" msgid "hotkey|default down" msgstr "" msgid "hotkey|toggle fullscreen" msgstr "" msgid "hotkey|toggle text support mode" msgstr "" msgid "hotkey|new game" msgstr "" msgid "hotkey|load game" msgstr "" msgid "hotkey|highscores" msgstr "" msgid "hotkey|credits" msgstr "" msgid "hotkey|standard game" msgstr "звичайна Гра" msgid "hotkey|campaign game" msgstr "" msgid "hotkey|multi-player game" msgstr "" msgid "hotkey|settings" msgstr "" msgid "hotkey|quit" msgstr "" msgid "hotkey|select map" msgstr "" msgid "hotkey|select small map size" msgstr "" msgid "hotkey|select medium map size" msgstr "" msgid "hotkey|select large map size" msgstr "" msgid "hotkey|select extra large map size" msgstr "" msgid "hotkey|select all map sizes" msgstr "" msgid "hotkey|hotseat game" msgstr "" msgid "hotkey|battle only game" msgstr "" msgid "hotkey|choose the original campaign" msgstr "" msgid "hotkey|choose the expansion campaign" msgstr "" msgid "hotkey|map editor main menu" msgstr "" msgid "hotkey|new map menu" msgstr "нова гра" msgid "hotkey|load map menu" msgstr "завантажити гру" msgid "hotkey|new map from scratch" msgstr "" msgid "hotkey|new random map" msgstr "" msgid "hotkey|undo last action" msgstr "відмінити оÑтанню дію" msgid "hotkey|redo last action" msgstr "повторити оÑтанню дію" msgid "hotkey|roland campaign" msgstr "" msgid "hotkey|archibald campaign" msgstr "" msgid "hotkey|the price of loyalty campaign" msgstr "Ціна ВірноÑті" msgid "hotkey|voyage home campaign" msgstr "" msgid "hotkey|wizard's isle campaign" msgstr "" msgid "hotkey|descendants campaign" msgstr "" msgid "hotkey|select first campaign bonus" msgstr "" msgid "hotkey|select second campaign bonus" msgstr "" msgid "hotkey|select third campaign bonus" msgstr "" msgid "hotkey|view campaign intro" msgstr "" msgid "hotkey|select campaign difficulty" msgstr "Оберати ÑкладніÑть кампанії" msgid "hotkey|restart campaign scenario" msgstr "" msgid "hotkey|world map left" msgstr "" msgid "hotkey|world map right" msgstr "" msgid "hotkey|world map up" msgstr "" msgid "hotkey|world map down" msgstr "" msgid "hotkey|world map up left" msgstr "" msgid "hotkey|world map up right" msgstr "" msgid "hotkey|world map down left" msgstr "" msgid "hotkey|world map down right" msgstr "" msgid "hotkey|save game" msgstr "" msgid "hotkey|next hero" msgstr "" #, fuzzy msgid "hotkey|start hero movement" msgstr "звичайна Гра" msgid "hotkey|cast adventure spell" msgstr "" msgid "hotkey|put hero to sleep" msgstr "" msgid "hotkey|next town" msgstr "показати наÑтупне міÑто" msgid "hotkey|end turn" msgstr "" msgid "hotkey|file options" msgstr "" msgid "hotkey|adventure options" msgstr "" msgid "hotkey|puzzle map" msgstr "" msgid "hotkey|scenario information" msgstr "Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ Ñценарій" msgid "hotkey|dig for artifact" msgstr "" msgid "hotkey|view world" msgstr "" msgid "hotkey|kingdom summary" msgstr "" msgid "hotkey|default action" msgstr "" msgid "hotkey|open focus" msgstr "" msgid "hotkey|system options" msgstr "параметри ÑиÑтеми" msgid "hotkey|scroll left" msgstr "" msgid "hotkey|scroll right" msgstr "" msgid "hotkey|scroll up" msgstr "" msgid "hotkey|scroll down" msgstr "" msgid "hotkey|toggle control panel" msgstr "" msgid "hotkey|toggle radar" msgstr "" msgid "hotkey|toggle buttons" msgstr "" msgid "hotkey|toggle status" msgstr "" msgid "hotkey|toggle icons" msgstr "" msgid "hotkey|transfer control to ai" msgstr "" msgid "hotkey|retreat from battle" msgstr "" msgid "hotkey|surrender during battle" msgstr "капітулювати під Ñ‡Ð°Ñ Ð±Ð¸Ñ‚Ð²Ð¸" msgid "hotkey|toggle battle auto mode" msgstr "" msgid "hotkey|finish the battle in auto mode" msgstr "завершити битву у автоматичному режимі" msgid "hotkey|battle options" msgstr "" msgid "hotkey|skip turn in battle" msgstr "" msgid "hotkey|cast battle spell" msgstr "" msgid "hotkey|dwelling level 1" msgstr "" msgid "hotkey|dwelling level 2" msgstr "" msgid "hotkey|dwelling level 3" msgstr "" msgid "hotkey|dwelling level 4" msgstr "" msgid "hotkey|dwelling level 5" msgstr "" msgid "hotkey|dwelling level 6" msgstr "" msgid "hotkey|well" msgstr "" msgid "hotkey|marketplace" msgstr "ринок" msgid "hotkey|mage guild" msgstr "" msgid "hotkey|shipyard" msgstr "верф" msgid "hotkey|thieves guild" msgstr "Ð³Ñ–Ð»ÑŒÐ´Ñ–Ñ Ð·Ð»Ð¾Ð´Ñ–Ñ—Ð²" msgid "hotkey|tavern" msgstr "" msgid "hotkey|construction screen" msgstr "" msgid "hotkey|buy all monsters in well" msgstr "" msgid "hotkey|split stack by half" msgstr "" msgid "hotkey|split stack by one" msgstr "" msgid "hotkey|join stacks" msgstr "" msgid "hotkey|upgrade troop" msgstr "" msgid "hotkey|dismiss hero or troop" msgstr "" msgid "" "Do you want to regain control from AI? The effect will take place only on " "the next turn." msgstr "" msgid "" "Do you want to transfer control from you to the AI? The effect will take " "place only on the next turn." msgstr "" msgid "Default Actions" msgstr "" msgid "Global Actions" msgstr "" msgid "Main Menu" msgstr "" msgid "World Map" msgstr "" #, fuzzy msgid "Battle Screen" msgstr "Вікно ГероÑ" #, fuzzy msgid "Town Screen" msgstr "Вікно ГероÑ" msgid "Army Actions" msgstr "" msgid "Editor" msgstr "" msgid "The save file is corrupted." msgstr "Файл Ð·Ð±ÐµÑ€ÐµÐ¶ÐµÐ½Ð½Ñ Ð¿Ð¾ÑˆÐºÐ¾Ð´Ð¶ÐµÐ½Ð¸Ð¹." msgid "Unsupported save format: " msgstr "" msgid "Current game version: " msgstr "" msgid "Last supported version: " msgstr "" msgid "This file contains a save with an invalid game type." msgstr "" msgid "" "This save file requires \"The Price of Loyalty\" game assets, but they have " "not been provided to the engine." msgstr "" msgid "This saved game is localized to '" msgstr "" msgid "' language, but the current language of the game is '" msgstr "" msgid "Hot Seat" msgstr "" msgid "" "Play a Hot Seat game, where 2 to 4 players play around the same computer, " "switching into the 'Hot Seat' when it is their turn." msgstr "" msgid "A single player game playing out a single map." msgstr "" msgid "Standard Game" msgstr "Стандартна Гра" msgid "A single player game playing through a series of maps." msgstr "" msgid "Campaign Game" msgstr "" msgid "Multi-Player Game" msgstr "" msgid "" "A multi-player game, with several human players completing against each " "other on a single map." msgstr "" msgid "fheroes2 Resurrection Team presents" msgstr "" msgid "Greetings!" msgstr "" msgid "Welcome to Heroes of Might and Magic II powered by fheroes2 engine!" msgstr "ЛаÑкаво проÑимо до Героїв Меча та Магії 2 на оÑнові Ñ€ÑƒÑˆÑ–Ñ fheroes2!" #, fuzzy msgid "" "Welcome to Heroes of Might and Magic II powered by the fheroes2 engine!\n" "Before starting the game please select a game resolution." msgstr "ЛаÑкаво проÑимо до Героїв Меча та Магії 2 на оÑнові Ñ€ÑƒÑˆÑ–Ñ fheroes2!" msgid "Please Remember" msgstr "" msgid "" "You can always change the language, resolution and settings of the game by " "clicking on the " msgstr "" msgid "door" msgstr "" msgid " on the left side of the Main Menu, or with the " msgstr "" msgid " button from the " msgstr "" #, fuzzy msgid "NEW GAME" msgstr "" "ÐОВÐ\n" "ГРÐ" msgid "" " menu. \n" "\n" "Enjoy the game!" msgstr "" msgid "The Editor requires \"The Price of Loyalty\" expansion files to work." msgstr "" msgid "Quit Heroes of Might and Magic II and return to the operating system." msgstr "" "Залишити Heroes of Might and Magic II Ñ– повернутиÑÑ Ð´Ð¾ операційної ÑиÑтеми." msgid "Credits" msgstr "" msgid "View the credits screen." msgstr "" msgid "High Scores" msgstr "" msgid "View the high scores screen." msgstr "" msgid "Change language, resolution and settings of the game." msgstr "" msgid "Game Settings" msgstr "" msgid "" "Required video files for campaign selection window are missing. Please make " "sure that all necessary files are present in the system." msgstr "" msgid "" "Either Roland's or Archibald's campaign from the original Heroes of Might " "and Magic II." msgstr "" msgid "Original Campaign" msgstr "" msgid "Expansion Campaign" msgstr "" msgid "One of the four new campaigns from the Price of Loyalty expansion set." msgstr "" msgid "Loading video. Please wait..." msgstr "" msgid "Host" msgstr "" msgid "" "The host sets up the game options. There can only be one host per network " "game." msgstr "" msgid "Guest" msgstr "" msgid "" "The guest waits for the host to set up the game, then is automatically added " "in. There can be multiple guests for TCP/IP games." msgstr "" msgid "Battle Only" msgstr "" msgid "Setup and play a battle without loading any map." msgstr "" msgid "2 Players" msgstr "" msgid "" "Play with 2 human players, and optionally, up to 4 additional computer " "players." msgstr "" msgid "3 Players" msgstr "" msgid "" "Play with 3 human players, and optionally, up to 3 additional computer " "players." msgstr "" msgid "4 Players" msgstr "" msgid "" "Play with 4 human players, and optionally, up to 2 additional computer " "players." msgstr "" msgid "5 Players" msgstr "" msgid "" "Play with 5 human players, and optionally, up to 1 additional computer " "player." msgstr "" msgid "6 Players" msgstr "" msgid "Play with 6 human players." msgstr "" msgid "Dragon city has fallen! You are now the Master of the Dragons." msgstr "" msgid "" "You captured %{name}!\n" "You are victorious." msgstr "" msgid "" "You have captured the enemy hero %{name}!\n" "Your quest is complete." msgstr "" msgid "" "You have found the %{name}.\n" "Your quest is complete." msgstr "" msgid "Ultimate Artifact" msgstr "" msgid "" "The enemy is beaten.\n" "Your side has triumphed!" msgstr "" msgid "" "You have built up over %{count} gold in your treasury.\n" "All enemies bow before your wealth and power." msgstr "" msgid "Victory!" msgstr "Перемога!" msgid "" "The enemy has captured %{name}!\n" "They are triumphant." msgstr "" msgid "" "The enemy has built up over %{count} gold in his treasury.\n" "You must bow done in defeat before his wealth and power." msgstr "" msgid "You have been eliminated from the game!!!" msgstr "" msgid "" "You have lost the hero %{name}.\n" "Your quest is over." msgstr "" msgid "" "You have failed to complete your quest in time.\n" "All is lost." msgstr "" msgid "Defeat!" msgstr "" msgid "" "Base score: %{score}\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" msgid "Defeat all enemy heroes and capture all enemy towns and castles." msgstr "" msgid "Run out of time. (Fail to win by a certain point.)" msgstr "" msgid "You must defeat the enemy %{enemies}." msgid_plural "You must defeat the enemy alliance of %{enemies}." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "" "The alliance consisting of %{allies} and you must defeat the enemy " "%{enemies}." msgid_plural "" "The alliance consisting of %{allies} and you must defeat the enemy alliance " "of %{enemies}." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "Capture the castle '%{name}'." msgstr "" msgid "Capture the town '%{name}'." msgstr "" msgid "Defeat the hero '%{name}'." msgstr "" msgid "Find the ultimate artifact." msgstr "" msgid "Find the '%{name}' artifact." msgstr "" msgid "Accumulate %{count} gold." msgstr "" msgid "" ", or you may win by defeating all enemy heroes and capturing all enemy towns " "and castles." msgstr "" msgid "Lose the castle '%{name}'." msgstr "" msgid "Lose the town '%{name}'." msgstr "" msgid "Lose the hero: %{name}." msgstr "" msgid "Fail to win by the end of month %{month}, week %{week}, day %{day}." msgstr "" msgid "%{color} player has been vanquished!" msgstr "" msgid "Major Event!" msgstr "" msgid "Scenario:" msgstr "" msgid "Game Difficulty:" msgstr "" msgid "Opponents:" msgstr "" msgid "Class:" msgstr "" msgid "Rating %{rating}%" msgstr "" msgid "Click here to select which scenario to play." msgstr "" msgid "Scenario" msgstr "" msgid "Game Difficulty" msgstr "" msgid "" "This lets you change the starting difficulty at which you will play. Higher " "difficulty levels start you of with fewer resources, and at the higher " "settings, give extra resources to the computer." msgstr "" msgid "Difficulty Rating" msgstr "" msgid "" "The difficulty rating reflects a combination of various settings for your " "game. This number will be applied to your final score." msgstr "" msgid "Click to accept these settings and start a new game." msgstr "" msgid "Click to return to the main menu." msgstr "" msgid "Astrologers proclaim the Month of the %{name}." msgstr "" msgid "Astrologers proclaim the Week of the %{name}." msgstr "" msgid "After regular growth, the population of %{monster} is doubled!" msgstr "" msgid "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgid_plural "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "%{monster} growth +%{count}." msgstr "" msgid " All populations are halved." msgstr "" msgid " All dwellings increase population." msgstr "" msgid "New Month!" msgstr "" msgid "New Week!" msgstr "" msgid "Beware!" msgstr "" msgid "" "%{color} player, this is your last day to capture a town, or you will be " "banished from this land." msgstr "" msgid "" "%{color} player, you only have %{day} days left to capture a town, or you " "will be banished from this land." msgstr "" msgid "%{color} player's turn." msgstr "" msgid "" "%{color} player, you have lost your last town. If you do not conquer another " "town in the next week, you will be eliminated." msgstr "" msgid "" "%{color} player, your heroes abandon you, and you are banished from this " "land." msgstr "" msgid "Lord Kilburn" msgstr "Лорд Кілбурн" msgid "Tsabu" msgstr "" msgid "Sir Galant" msgstr "Сір Галлант" msgid "Thundax" msgstr "" msgid "Lord Haart" msgstr "" msgid "Ariel" msgstr "" msgid "Rebecca" msgstr "" msgid "Sandro" msgstr "" msgid "Crodo" msgstr "" msgid "Barock" msgstr "" msgid "Antoine" msgstr "" msgid "Astra" msgstr "" msgid "Agar" msgstr "" msgid "Vatawna" msgstr "" msgid "Vesper" msgstr "" msgid "Ambrose" msgstr "" msgid "Troyan" msgstr "" msgid "Jojosh" msgstr "" msgid "Wrathmont" msgstr "" msgid "Maximus" msgstr "" msgid "Next Hero" msgstr "" msgid "Select the next Hero." msgstr "" #, fuzzy msgid "Hero Movement" msgstr "Ð—Ð°Ñ‚Ñ–Ð½ÐµÐ½Ð½Ñ Ð¡Ñ–Ñ‚ÐºÐ¸" msgid "" "Start the Hero's movement along the current path or re-visit the object " "occupied by the Hero. Press and hold this button to reset the Hero's path." msgstr "" msgid "Kingdom Summary" msgstr "" msgid "View a Summary of your Kingdom." msgstr "" msgid "Cast an adventure spell." msgstr "" msgid "End Turn" msgstr "" msgid "End your turn and left the computer take its turn." msgstr "" msgid "Adventure Options" msgstr "" msgid "Bring up the adventure options menu." msgstr "" msgid "" "Bring up the file options menu, allowing you to load, save, start a new game " "or quit." msgstr "" msgid "Bring up the system options menu, allowing you to customize your game." msgstr "" msgid "" "One or more heroes may still move, are you sure you want to end your turn?" msgstr "" msgid "Are you sure you want to quit?" msgstr "" msgid "Are you sure you want to restart? (Your current game will be lost.)" msgstr "" msgid "Are you sure you want to overwrite the save with this name?" msgstr "" msgid "Game saved successfully." msgstr "" msgid "There was an issue during saving." msgstr "" msgid "" "Are you sure you want to load a new game? (Your current game will be lost.)" msgstr "" msgid "Try looking on land!!!" msgstr "" msgid "" "Searching for the Ultimate Artifact is fruitless. Your hero could not carry " "it even if he found it - all his artifact slots are full." msgstr "" msgid "Digging for artifacts requires a whole day, try again tomorrow." msgstr "" msgid "" "After spending many hours digging here, you have uncovered the %{artifact}." msgstr "" msgid "Congratulations!" msgstr "" msgid "Nothing here. Where could it be?" msgstr "" msgid "Try searching on clear ground." msgstr "" msgid "A miniature view of the known world. Left click to move viewing area." msgstr "" msgid "Month: %{month} Week: %{week}" msgstr "" msgid "Day: %{day}" msgstr "" msgid "" "You find a small\n" "quantity of %{resource}." msgstr "" msgid "Status Window" msgstr "" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date." msgstr "" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date. Left click here to cycle through these windows." msgstr "" msgid "" "This lets you change player starting positions and colors. A particular " "color will always start in a particular location. Some positions may only be " "played by a computer player or only by a human player." msgstr "" msgid "" "This lets you change the class of a player. Classes are not always " "changeable. Depending on the scenario, a player may receive additional towns " "and/or heroes not of their primary alignment." msgstr "" msgid "Handicap" msgstr "" # msgid "" "This lets you change the handicap of a particular player. Only humans may be " "handicapped. Handicapped players start with fewer resources and earn 15 or " "30% fewer resources per turn for mild and severe handicaps, respectively." msgstr "" msgid "%{color} player" msgstr "" msgid "No Handicap" msgstr "" msgid "No special restrictions on start resources and earning them per turn." msgstr "" msgid "Mild Handicap" msgstr "" # msgid "" "Mild handicapped players start with fewer resources and earn 15% fewer " "resources per turn." msgstr "" msgid "Severe Handicap" msgstr "" # msgid "" "Severe handicapped players start with fewer resources and earn 30% fewer " "resources per turn." msgstr "" msgid "View %{skill} Info" msgstr "" msgid "Keyboard|123" msgstr "" msgid "Keyboard|SPACE" msgstr "" msgid "Keyboard|ABC" msgstr "" msgid "Kingdom Income" msgstr "" msgid "Kingdom Income per day." msgstr "" msgid "For every lighthouse controlled, your ships will move further each day." msgstr "" msgid "English" msgstr "" msgid "French" msgstr "" msgid "Polish" msgstr "" msgid "German" msgstr "" msgid "Russian" msgstr "" msgid "Italian" msgstr "" msgid "Czech" msgstr "" msgid "Norwegian" msgstr "" msgid "Belarusian" msgstr "" msgid "Bulgarian" msgstr "" msgid "Ukrainian" msgstr "УкраїнÑька" msgid "Romanian" msgstr "" msgid "Spanish" msgstr "" msgid "Swedish" msgstr "" msgid "Portuguese" msgstr "" msgid "Turkish" msgstr "" msgid "Dutch" msgstr "" msgid "Hungarian" msgstr "" msgid "Danish" msgstr "" msgid "Slovak" msgstr "" msgid "Vietnamese" msgstr "" msgid ", FPS: " msgstr "" msgid "%{object} robber" msgstr "" msgid "%{object} raided" msgstr "" msgid "Ector" msgstr "Ектор" msgid "Gwenneth" msgstr "" msgid "Sir Gallant" msgstr "Сір Галлант" msgid "Tyro" msgstr "" msgid "Dimitry" msgstr "" msgid "Ruby" msgstr "" msgid "Crag Hack" msgstr "" msgid "Fineous" msgstr "" msgid "Jezebel" msgstr "" msgid "Atlas" msgstr "" msgid "Ergon" msgstr "" msgid "Jaclyn" msgstr "" msgid "Gem" msgstr "" msgid "Natasha" msgstr "" msgid "Carlawn" msgstr "" msgid "Luna" msgstr "" msgid "Arie" msgstr "" msgid "Barok" msgstr "" msgid "Kastore" msgstr "" msgid "Falagar" msgstr "" msgid "Dawn" msgstr "" msgid "Flint" msgstr "" msgid "Halon" msgstr "" msgid "Myra" msgstr "" msgid "Myrini" msgstr "" msgid "Wilfrey" msgstr "" msgid "Mandigal" msgstr "" msgid "Sarakin" msgstr "" msgid "Charity" msgstr "" msgid "Darlana" msgstr "" msgid "Ranloo" msgstr "" msgid "Rialdo" msgstr "" msgid "Zam" msgstr "Зам" msgid "Zom" msgstr "Зом" msgid "Celia" msgstr "" msgid "Roxana" msgstr "РокÑана" msgid "Lord Corlagon" msgstr "" msgid "Lord Halton" msgstr "" msgid "Sister Eliza" msgstr "" msgid "Brother Brax" msgstr "" msgid "Dainwin" msgstr "" msgid "Joseph" msgstr "Джозеф" msgid "Mog" msgstr "" msgid "Solmyr" msgstr "Солмир" msgid "Ceallach" msgstr "" msgid "Drakonia" msgstr "" msgid "Elderian" msgstr "" msgid "Gallavant" msgstr "" msgid "Jarkonas" msgstr "" msgid "Martine" msgstr "" msgid " gives you maximum morale" msgstr "" msgid " gives you maximum luck" msgstr "" msgid "You cannot pick up this artifact, you already have a full load!" msgstr "" msgid "To cast spells, you must first buy a spell book for %{gold} gold." msgstr "" msgid "Unfortunately, you seem to be a little short of cash at the moment." msgstr "" msgid "Do you wish to buy one?" msgstr "" msgid "%{count} / day" msgstr "" msgid "one" msgstr "" msgid "two" msgstr "" msgid "A whirlpool engulfs your ship. Some of your army has fallen overboard." msgstr "" msgid "Insulted by your refusal of their offer, the monsters attack!" msgstr "" msgid "" "The %{monster}, awed by the power of your forces, begin to scatter.\n" "Do you wish to pursue and engage them?" msgstr "" msgid "Ransacking an enemy camp, you discover a hidden cache of treasures." msgstr "" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with these resources, " "come back next week for more.\"" msgstr "" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there are no resources currently available. Please try " "again next week.\"" msgstr "" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with this gold, come " "back next week for more.\"" msgstr "" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there is no gold currently available. Please try again " "next week.\"" msgstr "" msgid "" "You've found an abandoned lean-to.\n" "Poking about, you discover some resources hidden nearby." msgstr "" msgid "The lean-to is long abandoned. There is nothing of value here." msgstr "" msgid "" "You catch a leprechaun foolishly sleeping amidst a cluster of magic " "mushrooms.\n" "In exchange for his freedom, he guides you to a small pot filled with " "precious things." msgstr "" msgid "" "You've found a magic garden, the kind of place that leprechauns and faeries " "like to cavort in, but there is no one here today.\n" "Perhaps you should try again next week." msgstr "" msgid "You come upon the remains of an unfortunate adventurer." msgstr "" msgid "Treasure" msgstr "Скарб" msgid "Searching through the tattered clothing, you find the %{artifact}." msgstr "" msgid "Searching through the tattered clothing, you find nothing." msgstr "" msgid "" "You come across an old wagon left by a trader who didn't quite make it to " "safe terrain." msgstr "" msgid "Unfortunately, others have found it first, and the wagon is empty." msgstr "" msgid "Searching inside, you find the %{artifact}." msgstr "" msgid "Inside, you find some of the wagon's cargo still intact." msgstr "" msgid "You search through the flotsam, and find some wood and some gold." msgstr "" msgid "You search through the flotsam, and find some wood." msgstr "" msgid "You search through the flotsam, but find nothing." msgstr "" msgid "Shrine of the 1st Circle" msgstr "" msgid "" "You come across a small shrine attended by a group of novice acolytes.\n" "In exchange for your protection, they agree to teach you a simple spell - " "'%{spell}'." msgstr "" msgid "Shrine of the 2nd Circle" msgstr "" msgid "" "You come across an ornate shrine attended by a group of rotund friars.\n" "In exchange for your protection, they agree to teach you a spell - " "'%{spell}'." msgstr "" msgid "Shrine of the 3rd Circle" msgstr "" msgid "" "You come across a lavish shrine attended by a group of high priests.\n" "In exchange for your protection, they agree to teach you a sophisticated " "spell - '%{spell}'." msgstr "" msgid "" "\n" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" msgid "" "\n" "Unfortunately, you already have knowledge of this spell, so there is nothing " "more for them to teach you." msgstr "" msgid "" "\n" "Unfortunately, you have no Magic Book to record the spell with." msgstr "" "\n" "Ðажаль, ви не маєте Книги Магії щоб запиÑати це заклÑттÑ." msgid "" "You approach the hut and observe a witch inside studying an ancient tome on " "%{skill}.\n" "\n" msgstr "" msgid "" "As you approach, she turns and focuses her one glass eye on you.\n" "\"You already know everything you deserve to learn!\" the witch screeches. " "\"NOW GET OUT OF MY HOUSE!\"" msgstr "" msgid "" "As you approach, she turns and speaks.\n" "\"You already know that which I would teach you. I can help you no further.\"" msgstr "" msgid "" "An ancient and immortal witch living in a hut with bird's legs for stilts " "teaches you %{skill} for her own inscrutable purposes." msgstr "" msgid "As you drink the sweet water, you gain luck for your next battle." msgstr "" msgid "You drink from the enchanted fountain, but nothing happens." msgstr "" msgid "You enter the faerie ring, but nothing happens." msgstr "" msgid "" "Upon entering the mystical faerie ring, your army gains luck for its next " "battle." msgstr "" msgid "" "You've found an ancient and weathered stone idol.\n" "It is supposed to grant luck to visitors, but since the stars are already " "smiling upon you, it does nothing." msgstr "" msgid "" "You've found an ancient and weathered stone idol.\n" "Kissing it is supposed to be lucky, so you do. The stone is very cold to the " "touch." msgstr "" msgid "The mermaids silently entice you to return later and be blessed again." msgstr "" msgid "" "The magical, soothing beauty of the Mermaids reaches you and your crew.\n" "Just for a moment, you forget your worries and bask in the beauty of the " "moment.\n" "The mermaids charms bless you with increased luck for your next combat." msgstr "" msgid "" "You come upon the pyramid of a great and ancient king.\n" "You are tempted to search it for treasure, but all the old stories warn of " "fearful curses and undead guardians.\n" "Will you search?" msgstr "" msgid "" "Upon defeating the monsters, you decipher an ancient glyph on the wall, " "telling the secret of the spell - '" msgstr "" msgid "Unfortunately, you have no Magic Book to record the spell with." msgstr "" msgid "" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" msgid "" "You come upon the pyramid of a great and ancient king.\n" "Routine exploration reveals that the pyramid is completely empty." msgstr "" msgid "" "A drink at the well is supposed to restore your spell points, but you are " "already at maximum." msgstr "" msgid "A second drink at the well in one day will not help you." msgstr "" msgid "A drink from the well has restored your spell points to maximum." msgstr "" msgid "" "\"I'm sorry sir,\" The leader of the soldiers says, \"but you already know " "everything we have to teach.\"" msgstr "" msgid "The soldiers living in the fort teach you a few new defensive tricks." msgstr "" msgid "" "You've come upon a mercenary camp practicing their tactics. \"You're too " "advanced for us,\" the mercenary captain says. \"We can teach nothing more.\"" msgstr "" msgid "" "You've come upon a mercenary camp practicing their tactics. The mercenaries " "welcome you and your troops and invite you to train with them." msgstr "" msgid "\"Go 'way!\", the witch doctor barks, \"you know all I know.\"" msgstr "" msgid "" "An Orcish witch doctor living in the hut deepens your knowledge of magic by " "showing you how to cast stones, read portents, and decipher the intricacies " "of chicken entrails." msgstr "" msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, the Druids turn you away, indicating they have nothing " "new to teach you." msgstr "" msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, they teach you new ways to cast spells." msgstr "" msgid "" "You tentatively approach the burial ground of ancient warriors. Do you want " "to search the graves?" msgstr "" msgid "" "You spend several hours searching the graves and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" msgid "Upon defeating the Zombies you search the graves and find something!" msgstr "" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the shipwreck?" msgstr "" msgid "" "You spend several hours sifting through the debris and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" msgid "" "Upon defeating the Ghosts you sift through the debris and find something!" msgstr "" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the ship?" msgstr "" msgid "" "Upon defeating the Skeletons you sift through the debris and find something!" msgstr "" msgid "Your men spot a navigational buoy, confirming that you are on course." msgstr "" msgid "" "Your men spot a navigational buoy, confirming that you are on course and " "increasing their morale." msgstr "" msgid "" "The drink at the oasis is refreshing, but offers no further benefit. The " "oasis might help again if you fought a battle first." msgstr "" msgid "" "A drink at the oasis fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" msgid "" "The drink at the watering hole is refreshing, but offers no further benefit. " "The watering hole might help again if you fought a battle first." msgstr "" msgid "" "A drink at the watering hole fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" msgid "" "It doesn't help to pray twice before a battle. Come back after you've fought." msgstr "" msgid "A visit and a prayer at the temple raises the morale of your troops." msgstr "" msgid "" "An old Knight appears on the steps of the gazebo. \"I am sorry, my liege, I " "have taught you all I can.\"" msgstr "" msgid "" "An old Knight appears on the steps of the gazebo. \"My liege, I will teach " "you all that I know to aid you in your travels.\"" msgstr "" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he says, \"I would give you an artifact as a reward, but " "you're all full.\"" msgstr "" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he rewards you for your act of kindness by giving you the " "%{art}." msgstr "" msgid "A leprechaun offers you the %{art} for the small price of %{gold} Gold." msgstr "" msgid "" "A leprechaun offers you the %{art} for the small price of %{gold} Gold and " "%{count} %{res}." msgstr "" msgid "Do you wish to buy this artifact?" msgstr "" msgid "" "You try to pay the leprechaun, but realize that you can't afford it. The " "leprechaun stamps his foot and ignores you." msgstr "" msgid "" "Insulted by your refusal of his generous offer, the leprechaun stamps his " "foot and ignores you." msgstr "" msgid "You've found the artifact: " msgstr "" msgid "" "You've found the humble dwelling of a withered hermit. The hermit tells you " "that he is willing to give the %{art} to the first wise person he meets." msgstr "" msgid "" "You've come across the spartan quarters of a retired soldier. The soldier " "tells you that he is willing to pass on the %{art} to the first true leader " "he meets." msgstr "" msgid "" "You've encountered a strange person with a hat and an owl on it. He tells " "you that he is willing to give %{art} if you have %{skill}." msgstr "" msgid "" "You come upon an ancient artifact. As you reach for it, a pack of Rogues " "leap out of the brush to guard their stolen loot." msgstr "" msgid "" "Through a clearing you observe an ancient artifact. Unfortunately, it's " "guarded by a nearby %{monster}. Do you want to fight the %{monster} for the " "artifact?" msgstr "" msgid "Victorious, you take your prize, the %{art}." msgstr "" msgid "" "Discretion is the better part of valor, and you decide to avoid this fight " "for today." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold pieces." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold and the %{art}." msgstr "" msgid "" "After spending hours trying to fish the chest out of the sea, you open it, " "only to find it empty." msgstr "" msgid "" "After scouring the area, you fall upon a hidden treasure cache. You may take " "the gold or distribute the gold to the peasants for experience. Do you wish " "to keep the gold?" msgstr "" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "%{gold} gold pieces." msgstr "" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "ancient artifact %{art}." msgstr "" msgid "" "You stumble upon a dented and tarnished lamp lodged deep in the earth. Do " "you wish to rub the lamp?" msgstr "" msgid "" "You have taken control of the local Alchemist shop. It will provide you with " "%{count} unit of Mercury per day." msgstr "" msgid "" "You gain control of a sawmill. It will provide you with %{count} units of " "wood per day." msgstr "" msgid "" "You gain control of an ore mine. It will provide you with %{count} units of " "ore per day." msgstr "" msgid "" "You gain control of a sulfur mine. It will provide you with %{count} unit of " "sulfur per day." msgstr "" msgid "" "You gain control of a crystal mine. It will provide you with %{count} unit " "of crystal per day." msgstr "" msgid "" "You gain control of a gem mine. It will provide you with %{count} unit of " "gems per day." msgstr "" msgid "" "You gain control of a gold mine. It will provide you with %{count} gold per " "day." msgstr "" msgid "" "The lighthouse is now under your control, and all of your ships will now " "move further each day." msgstr "" msgid "You gain control of a %{name}." msgstr "" msgid "" "You come upon an abandoned gold mine. The mine appears to be haunted. Do you " "wish to enter?" msgstr "" msgid "You beat the Ghosts and are able to restore the mine to production." msgstr "" msgid "" "A group of %{monster} with a desire for greater glory wish to join you. Do " "you accept?" msgstr "" msgid "As you approach the dwelling, you notice that there is no one here." msgstr "" msgid "" "You search the ruins, but the Medusas that used to live here are gone. " "Perhaps there will be more next week." msgstr "" msgid "" "You've found some Medusas living in the ruins. They are willing to join your " "army for a price. Do you want to recruit Medusas?" msgstr "" msgid "" "You've found a Sprite Tree City. Unfortunately, none of the Sprites living " "there wish to join an army. Maybe next week." msgstr "" msgid "" "Some of the Sprites living in the tree city are willing to join your army " "for a price. Do you want to recruit Sprites?" msgstr "" msgid "" "A colorful Rogues' wagon stands empty here. Perhaps more Rogues will be here " "later." msgstr "" msgid "" "Distant sounds of music and laughter draw you to a colorful wagon housing " "Rogues. Do you wish to have any Rogues join your army?" msgstr "" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. The " "tents are unoccupied. Perhaps more Nomads will be here later." msgstr "" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. Do you " "wish to have any Nomads join you during your travels?" msgstr "" msgid "The pit of mud bubbles for a minute and then lies still." msgstr "" msgid "" "As you approach the bubbling pit of mud, creatures begin to climb out and " "position themselves around it. In unison they say: \"Mother Earth would like " "to offer you a few of her troops. Do you want to recruit Earth Elementals?\"" msgstr "" msgid "You enter the structure of white stone pillars, and find nothing." msgstr "" msgid "" "White stone pillars support a roof that rises up to the sky. As you enter " "the structure, the dead air of the outside gives way to a whirling gust that " "almost pushes you back out. The air current materializes into a barely " "visible form. The creature asks, in what can only be described as a loud " "whisper: \"Why have you come? Are you here to call upon the forces of the " "air?\"" msgstr "" msgid "No Fire Elementals approach you from the lava pool." msgstr "" msgid "" "Beneath a structure that serves to hold in heat, Fire Elementals move about " "in a fiery pool of molten lava. A group of them approach you and offer their " "services. Would you like to recruit Fire Elementals?" msgstr "" msgid "A face forms in the water for a moment, and then is gone." msgstr "" msgid "" "Crystalline structures cast shadows over a small reflective pool of water. " "You peer into the pool, and a face that is not your own peers back. It asks: " "\"Would you like to call upon the powers of water?\"" msgstr "" msgid "This burial site is deathly still." msgstr "" msgid "" "Restless spirits of long dead warriors seeking their final resting place " "offer to join you in hopes of finding peace. Do you wish to recruit ghosts?" msgstr "" msgid "" "The City of the Dead is empty of life, and empty of unlife as well. Perhaps " "some undead will move in next week." msgstr "" msgid "" "Some Liches living here are willing to join your army for a price. Do you " "want to recruit Liches?" msgstr "" msgid "" "You've found the ruins of an ancient city, now inhabited solely by the " "undead. Will you search?" msgstr "" msgid "" "Some of the surviving Liches are impressed by your victory over their " "fellows, and offer to join you for a price. Do you want to recruit Liches?" msgstr "" msgid "" "You've found one of those bridges that Trolls are so fond of living under, " "but there are none here. Perhaps there will be some next week." msgstr "" msgid "" "Some Trolls living under a bridge are willing to join your army, but for a " "price. Do you want to recruit Trolls?" msgstr "" msgid "Trolls living under the bridge challenge you. Will you fight them?" msgstr "" msgid "" "A few Trolls remain, cowering under the bridge. They approach you and offer " "to join your forces as mercenaries. Do you want to buy any Trolls?" msgstr "" msgid "" "The Dragon city has no Dragons willing to join you this week. Perhaps a " "Dragon will become available next week." msgstr "" msgid "" "The Dragon city is willing to offer some Dragons for your army for a price. " "Do you wish to recruit Dragons?" msgstr "" msgid "" "You stand before the Dragon City, a place off-limits to mere humans. Do you " "wish to violate this rule and challenge the Dragons to a fight?" msgstr "" msgid "" "Having defeated the Dragon champions, the city's leaders agree to supply " "some Dragons to your army for a price. Do you wish to recruit Dragons?" msgstr "" msgid "From the observation tower, you are able to see distant lands." msgstr "" msgid "" "The spring only refills once a week, and someone's already been here this " "week." msgstr "" msgid "" "A drink at the spring is supposed to give you twice your normal spell " "points, but you are already at that level." msgstr "" msgid "" "A drink from the spring fills your blood with magic! You have twice your " "normal spell points in reserve." msgstr "" msgid "" "Recognizing you, the butler refuses to admit you. \"The master,\" he says, " "\"will not see the same student twice.\"" msgstr "" msgid "" "The butler admits you to see the master of the house. He trains you in the " "four skills a hero should know." msgstr "" msgid "" "The butler opens the door and looks you up and down. \"You are neither " "famous nor diplomatic enough to be admitted to see my master,\" he sniffs. " "\"Come back when you think yourself worthy.\"" msgstr "" msgid " and " msgstr "" msgid "" "All of the %{monsters} you have in your army have been trained by the battle " "masters of the fort. Your army now contains %{monsters2}." msgstr "" msgid "" "An unusual alliance of Ogres, Orcs, and Dwarves offer to train (upgrade) any " "such troops brought to them. Unfortunately, you have none with you." msgstr "" msgid "All of your %{monsters} have been upgraded into %{monsters2}." msgstr "" msgid "" "A blacksmith working at the foundry offers to convert all Pikemen and " "Swordsmen's weapons brought to him from iron to steel. He also says that he " "knows a process that will convert Iron Golems into Steel Golems. " "Unfortunately, you have none of these troops in your army, so he can't help " "you." msgstr "" msgid "" "The captain looks at you with surprise and says:\n" "\"You already have all the maps I know about. Let me fish in peace now.\"" msgstr "" msgid "" "A retired captain living on this refurbished fishing platform offers to sell " "you maps of the sea he made in his younger days for 1,000 gold. Do you wish " "to buy the maps?" msgstr "" msgid "" "The captain sighs. \"You don't have enough money, eh? You can't expect me " "to give my maps away for free!\"" msgstr "" msgid "" "You come upon an obelisk made from a type of stone you have never seen " "before. Staring at it intensely, the smooth surface suddenly changes to an " "inscription. The inscription is a piece of a lost ancient map. Quickly you " "copy down the piece and the inscription vanishes as abruptly as it appeared." msgstr "" msgid "You have already been to this obelisk." msgstr "" msgid "" "Upon your approach, the tree opens its eyes in delight. \"It is good to see " "you, my student. I hope my teachings have helped you.\"" msgstr "" msgid "" "Upon your approach, the tree opens its eyes in delight. \"Ahh, an " "adventurer! Allow me to teach you a little of what I have learned over the " "ages.\"" msgstr "" msgid "Upon your approach, the tree opens its eyes in delight." msgstr "" msgid "" "\"Ahh, an adventurer! I will be happy to teach you a little of what I have " "learned over the ages for a mere %{count} %{res}.\"" msgstr "" msgid "(Just bury it around my roots.)" msgstr "" msgid "Tears brim in the eyes of the tree." msgstr "" msgid "\"I need %{count} %{res}.\"" msgstr "" msgid "it whispers. (sniff) \"Well, come back when you can pay me.\"" msgstr "" msgid "" "Nestled among the trees sits a blind seer. After you explain the intent of " "your journey, the seer activates his crystal ball, allowing you to see the " "strengths and weaknesses of your opponents." msgstr "" msgid "" "The entrance to the cave is dark, and a foul, sulfurous smell issues from " "the cave mouth. Will you enter?" msgstr "" msgid "Except for evidence of a terrible battle, the cave is empty." msgstr "" msgid "" "You find a powerful and grotesque Demon in the cave. \"Today,\" it rasps, " "\"you will fight and surely die. But I will give you a choice of deaths. You " "may fight me, or you may fight my servants. Do you prefer to fight my " "servants?\"" msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points." msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points and " "%{count} gold." msgstr "" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and find the %{art} in the back of the cave." msgstr "" msgid "" "Seeing that you do not have %{count} gold, the demon slashes you with its " "claws, and the last thing you see is a red haze." msgstr "" msgid "" "The Demon leaps upon you and has its claws at your throat before you can " "even draw your sword. \"Your life is mine,\" it says. \"I will sell it back " "to you for %{count} gold.\"" msgstr "" msgid "" "Upon defeating the daemon's servants, you find a hidden cache with %{count} " "gold." msgstr "" msgid "" "As you enter the Alchemist's Tower, a hobbled, graying man in a brown cloak " "makes his way towards you." msgstr "" msgid "He checks your pack, and sees that you have 1 cursed item." msgid_plural "" "He checks your pack, and sees that you have %{count} cursed items." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "For %{gold} gold, the alchemist will remove it for you. Do you pay?" msgid_plural "" "For %{gold} gold, the alchemist will remove them for you. Do you pay?" msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "" "After you consent to pay the requested amount of gold, the alchemist grabs " "the cursed artifact and throws it into his magical cauldron." msgid_plural "" "After you consent to pay the requested amount of gold, the alchemist grabs " "all cursed artifacts and throws them into his magical cauldron." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "" "You hear a voice from behind the locked door, \"You don't have enough gold " "to pay for my services.\"" msgstr "" msgid "" "You hear a voice from high above in the tower, \"Go away! I can't help you!\"" msgstr "" msgid "" "The head groom speaks to you, \"That is a fine looking horse you have. I am " "afraid we can give you no better, but the horses your cavalry are riding " "look to be of poor breeding stock. We have many trained war horses which " "would aid your riders greatly. I insist you take them.\"" msgstr "" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, he will " "grow tired in a week. You must also let me give better horses to your " "mounted soldiers, their horses look shoddy and weak.\"" msgstr "" msgid "" "The head groom approaches you and speaks, \"You already have a fine horse, " "and have no inexperienced cavalry which might make use of our trained war " "horses.\"" msgstr "" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, his " "endurance will wane with a lot of heavy riding, and you must return for a " "fresh mount in a week. We also have many fine war horses which could benefit " "mounted soldiers, but you have none we can help.\"" msgstr "" msgid "The Arena guards turn you away." msgstr "" msgid "" "You have your crew stop up their ears with wax before the sirens' eerie song " "has any chance of luring them to a watery grave." msgstr "" msgid "" "As the sirens sing their eerie song, your small, determined army manages to " "overcome the urge to dive headlong into the sea." msgstr "" msgid "" "An eerie wailing song emanates from the sirens perched upon the rocks. Many " "of your crew fall under its spell, and dive into the water where they drown. " "You are now wiser for the visit, and gain %{exp} experience." msgstr "" msgid "" "In a dazzling display of daring, you break into the local jail and free the " "hero imprisoned there, who, in return, pledges loyalty to your cause." msgstr "" msgid "" "You already have %{count} heroes, and regretfully must leave the prisoner in " "this jail to languish in agony for untold days." msgstr "" msgid "" "You enter a rickety hut and talk to the magician who lives there. He tells " "you of places near and far which may aid you in your journeys." msgstr "" msgid "This eye seems to be intently studying its surroundings." msgstr "" msgid "You come across a giant Sphinx. The Sphinx remains strangely quiet." msgstr "" msgid "" "\"I have a riddle for you,\" the Sphinx says. \"Answer correctly, and you " "shall be rewarded. Answer incorrectly, and you shall be eaten. Do you accept " "the challenge?\"" msgstr "" msgid "" "The Sphinx asks you the following riddle:\n" "\n" "'%{riddle}'\n" "\n" "Your answer?" msgstr "" msgid "" "\"You guessed incorrectly,\" the Sphinx says, smiling. The Sphinx swipes at " "you with a paw, knocking you to the ground. Another blow makes the world go " "black, and you know no more." msgstr "" msgid "" "Looking somewhat disappointed, the Sphinx sighs. \"You've answered my riddle " "so here's your reward. Now begone.\"" msgstr "" msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "As you speak the magic word, the glowing barrier dissolves into nothingness." msgstr "" msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "You speak, and nothing happens." msgstr "" msgid "" "You enter the tent and see an old woman gazing into a magic gem. She looks " "up and says,\n" "\"In my travels, I have learned much in the way of arcane magic. A great " "oracle taught me his skill. I have the answer you seek.\"" msgstr "" msgid "No spell book is present." msgstr "ВідÑÑƒÑ‚Ð½Ñ ÐºÐ½Ð¸Ð³Ð° магії." msgid "" "This spell costs %{mana} spell points. You have no spell points, so you " "cannot cast it." msgstr "" msgid "" "This spell costs %{mana} spell points. You only have %{point} spell points, " "so you cannot cast it." msgstr "" msgid "The spell was not found." msgstr "" msgid "Only heroes can cast this spell." msgstr "" msgid "This hero is not able to cast adventure spells." msgstr "" msgid "Your hero is too tired to cast this spell today. Try again tomorrow." msgstr "" msgid "This spell cannot be cast on a boat." msgstr "" msgid "This spell can only be cast near an ocean." msgstr "" msgid "" "There are no boats available and no ocean adjacent to the hero where this " "spell will work." msgstr "" msgid "There are no boats available for this spell." msgstr "" msgid "There is no ocean adjacent to the hero where this spell will work." msgstr "Ð‘Ñ–Ð»Ñ Ð³ÐµÑ€Ð¾Ñ Ð½ÐµÐ¼Ð°Ñ” водойми, то ж ви не можете викориÑтати це заклÑттÑ." msgid "" "You do not own any town or castle that is not currently occupied by a hero. " "This spell will have no effect." msgstr "" #, fuzzy msgid "This hero is already in a town, so this spell will have no effect." msgstr "" "Цей герой вже перебуває у міÑті, то ж ви не можете викориÑтати це заклÑттÑ." msgid "" "The nearest town is %{town}.\n" "\n" "This town is occupied by your hero %{hero}." msgstr "" msgid "This spell is already in effect." msgstr "" msgid "" "No opponent neither has nor can have any hero under their command anymore. " "Casting this spell will have no effect." msgstr "" msgid "" "No opponent has a hero under their command at this time. Casting this spell " "will have no effect." msgstr "" msgid "" "You must be within %{count} spaces of a monster for the Visions spell to " "work." msgstr "" msgid "" "You must be standing on the entrance to a mine (sawmills and alchemist labs " "do not count) to cast this spell." msgstr "" msgid "You must first defeat the ghosts guarding the mine to cast this spell." msgstr "" msgid "" "There are already at least as many elementals guarding the mine as this hero " "can generate. Casting this spell will have no effect." msgstr "" msgid "%{name} the %{race} (Level %{level})" msgstr "" msgid "" "'Spread' combat formation spreads the hero's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" msgid "" "'Grouped' combat formation bunches the hero's army together in the center of " "their side of the battlefield." msgstr "" msgid "Are you sure you want to dismiss this Hero?" msgstr "" msgid "View Experience Info" msgstr "" msgid "View Spell Points Info" msgstr "" msgid "Set army combat formation to 'Spread'" msgstr "" msgid "Set army combat formation to 'Grouped'" msgstr "" msgid "Exit Hero Screen" msgstr "" msgid "You cannot dismiss a hero in a castle" msgstr "" msgid "Dismissal of %{name} the %{race} is prohibited by scenario" msgstr "" msgid "Dismiss %{name} the %{race}" msgstr "" msgid "Show previous hero" msgstr "" msgid "Show next hero" msgstr "" msgid "Blood Morale" msgstr "Кровожерлива" msgid "%{morale} Morale" msgstr "" msgid "%{luck} Luck" msgstr "" msgid "Current Luck Modifiers:" msgstr "" msgid "Current Morale Modifiers:" msgstr "" msgid "Entire army is undead, so morale does not apply." msgstr "" msgid "" "Current experience %{exp1}.\n" " Next level %{exp2}." msgstr "" msgid "Level %{level}" msgstr "" msgid "" "%{name} currently has %{point} spell points out of a maximum of %{max}. The " "maximum number of spell points is 10 times the hero's knowledge. It is " "occasionally possible for the hero to have more than their maximum spell " "points via special events." msgstr "" msgid "%{name1} meets %{name2}" msgstr "" msgid "Enemy heroes are now fully identifiable." msgstr "" msgid "Identify Hero" msgstr "Ідентифікувати ГероÑ" msgid "Select town to port to." msgstr "" msgid "Town Portal" msgstr "" msgid "The creatures are willing to join us!" msgstr "" msgid "All the creatures will join us..." msgstr "" msgid "The creature will join us..." msgid_plural "%{count} of the creatures will join us..." msgstr[0] "" msgstr[1] "" msgstr[2] "" msgid "" "\n" " for a fee of %{gold} gold." msgstr "" msgid "These weak creatures will surely flee before us." msgstr "" msgid "I fear these creatures are in the mood for a fight." msgstr "" #, fuzzy msgid "The hero's attack skill is a bonus added to each unit's attack skill." msgstr "кожна з бонуÑом +%{attack} до Ð²Ð¼Ñ–Ð½Ð½Ñ Ðтаки." msgid "The hero's defense skill is a bonus added to each unit's defense skill." msgstr "" msgid "The hero's spell power determines the duration or power of a spell." msgstr "" msgid "" " The hero's knowledge determines how many spell points the hero may have. " "Under normal circumstances, a hero is limited to 10 spell points per level " "of knowledge." msgstr "" msgid "Current Modifiers:" msgstr "Поточні Модифікатори:" msgid "skill|Basic" msgstr "Базовий" msgid "skill|Advanced" msgstr "ПроÑунутий" msgid "skill|Expert" msgstr "ЕкÑпертний" msgid "Pathfinding" msgstr "Пошук шлÑху" msgid "Archery" msgstr "Стрільба" msgid "Logistics" msgstr "ЛогіÑтика" msgid "Scouting" msgstr "Розвідка" msgid "Diplomacy" msgstr "ДипломатіÑ" msgid "Navigation" msgstr "ÐавігаціÑ" msgid "Leadership" msgstr "ЛідерÑтво" msgid "Wisdom" msgstr "МудріÑть" msgid "Mysticism" msgstr "МіÑтицизм" msgid "Ballistics" msgstr "БаліÑтика" msgid "Eagle Eye" msgstr "Орлиний зір" msgid "Necromancy" msgstr "ÐекромантіÑ" msgid "Estates" msgstr "Маєтки" msgid "Advanced Archery" msgstr "ПроÑнута Стрільба" msgid "Advanced Pathfinding" msgstr "ПроÑунутий Пошук шлÑху" msgid "Basic Archery" msgstr "Базова Стрільба" msgid "Basic Pathfinding" msgstr "Базовий Пошук шлÑху" msgid "Expert Pathfinding" msgstr "ЕкÑпертний Пошук шлÑху" msgid "Advanced Logistics" msgstr "ПроÑунута ЛогіÑтика" msgid "Basic Logistics" msgstr "Базова ЛогіÑтика" msgid "Basic Scouting" msgstr "Базова Розвідка" msgid "Expert Archery" msgstr "ЕкÑпертна Стрільба" msgid "Expert Logistics" msgstr "ЕкÑпертна ЛогіÑтика" msgid "Advanced Diplomacy" msgstr "ПроÑунута ДипломатіÑ" msgid "Advanced Scouting" msgstr "ПроÑунута Розвідка" msgid "Basic Diplomacy" msgstr "Базова ДипломатіÑ" msgid "Expert Diplomacy" msgstr "ЕкÑпертна ДипломатіÑ" msgid "Expert Scouting" msgstr "ЕкÑпертна Розвідка" msgid "Advanced Leadership" msgstr "ПроÑунуте ЛідерÑтво" msgid "Advanced Navigation" msgstr "ПроÑунута ÐавігаціÑ" msgid "Basic Leadership" msgstr "Базове ЛідерÑтво" msgid "Basic Navigation" msgstr "Базова ÐавігаціÑ" msgid "Expert Navigation" msgstr "ЕкÑпертна ÐавігаціÑ" msgid "Advanced Wisdom" msgstr "ПроÑунута МудріÑть" msgid "Basic Mysticism" msgstr "Базовий МіÑтицизм" msgid "Basic Wisdom" msgstr "Базова МудріÑть" msgid "Expert Leadership" msgstr "ЕкÑпертне ЛідерÑтво" msgid "Expert Wisdom" msgstr "ЕкÑпертна МудріÑть" msgid "Advanced Luck" msgstr "ПроÑунута Удача" msgid "Advanced Mysticism" msgstr "ПроÑунутий МіÑтицизм" msgid "Basic Luck" msgstr "Базова Удача" msgid "Expert Luck" msgstr "ЕкÑпертна Удача" msgid "Expert Mysticism" msgstr "ЕкÑпертний МіÑтицизм" msgid "Advanced Ballistics" msgstr "ПроÑунута БаліÑтика" msgid "Advanced Eagle Eye" msgstr "ПроÑунутий Орлиний Зір" msgid "Basic Ballistics" msgstr "Базова БаліÑтика" msgid "Basic Eagle Eye" msgstr "Базовий Орлиний Зір" msgid "Expert Ballistics" msgstr "ЕкÑпертна БаліÑтика" msgid "Advanced Necromancy" msgstr "ПроÑунута ÐекромантіÑ" msgid "Basic Estates" msgstr "Базові Маєтки" msgid "Basic Necromancy" msgstr "Базова ÐекромантіÑ" msgid "Expert Eagle Eye" msgstr "ЕкÑпертний Орлиний Зір" msgid "Expert Necromancy" msgstr "ЕкÑпертна ÐекромантіÑ" msgid "Advanced Estates" msgstr "ПроÑунуті Маєтки" msgid "Expert Estates" msgstr "ЕкÑпертні Маєтки" msgid "" "%{skill} reduces the movement penalty for rough terrain by %{count} percent." msgstr "" "%{skill} знижує штраф за Ð¿ÐµÑ€ÐµÐ¼Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð¿Ð¾ нерівній території на %{count} " "відÑотків." msgid "%{skill} eliminates the movement penalty for rough terrain." msgstr "%{skill} уÑуває штраф за Ð¿ÐµÑ€ÐµÐ¼Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð¿Ð¾ нерівній території." #, fuzzy msgid "" "%{skill} increases the damage done by the hero's range attacking creatures " "by %{count} percent." msgstr "" "%{skill} підвищує шкоду від Ñ–Ñтот, що атакують з відÑтані на %{count} " "відÑотків." #, fuzzy msgid "%{skill} increases the hero's movement points by %{count} percent." msgstr "%{skill} підвищує одиниці руху Вашого Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count} відÑотків." #, fuzzy msgid "%{skill} increases the hero's viewable area by one square." msgid_plural "%{skill} increases the hero's viewable area by %{count} squares." msgstr[0] "%{skill} підвищує Ñ€Ð°Ð´Ñ–ÑƒÑ Ð¾Ð³Ð»Ñду Ð³ÐµÑ€Ð¾Ñ Ð½Ð° один квадрат." msgstr[1] "%{skill} підвищує Ñ€Ð°Ð´Ñ–ÑƒÑ Ð¾Ð³Ð»Ñду Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count} квадрата." msgstr[2] "%{skill} підвищує Ñ€Ð°Ð´Ñ–ÑƒÑ Ð¾Ð³Ð»Ñду Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count} квадратів." #, fuzzy msgid "" "%{skill} allows the hero to negotiate with monsters who are weaker than " "their army, and reduces the cost of surrender." msgstr "" "%{skill} дозволÑÑ” Вам домовлÑтиÑÑ Ñ–Ð· Ñ–Ñтотами, що Ñлабші за ваше війÑько. " #, fuzzy msgid "" "Approximately %{count} percent of the creatures may offer to join the hero." msgstr "" "Приблизно %{count} відÑотків Ñ–Ñтот можуть запропонувати приєднатиÑÑŒ до ВаÑ." #, fuzzy msgid "All of the creatures may offer to join the hero." msgstr "Ð’ÑÑ– Ñ–Ñтоти можуть запропонувати приєднатиÑÑŒ до ВаÑ." #, fuzzy msgid "" "The cost of surrender is reduced to %{percent} percent of the total cost of " "troops in the army." msgstr "" "%{name} зменшує вартіÑть капітулÑції на %{count} відÑотків від загальної " "вартоÑті вÑÑ–Ñ… війÑьк, що Ñ” у вашій армії." #, fuzzy msgid "" "%{skill} increases the hero's movement points over water by %{count} percent." msgstr "" "%{skill} підвищує одиниці руху Вашого Ð³ÐµÑ€Ð¾Ñ Ð¿Ð¾ воді на %{count} відÑотків." #, fuzzy msgid "%{skill} increases the hero's troops morale by %{count}." msgstr "%{skill} підвищує мораль війÑьк Вашого Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count}." #, fuzzy msgid "%{skill} allows the hero to learn third level spells." msgstr "%{skill} дозволÑÑ” Вашому герою вчити заклÑÑ‚Ñ‚Ñ Ñ‚Ñ€ÐµÑ‚ÑŒÐ¾Ð³Ð¾ рівнÑ." #, fuzzy msgid "%{skill} allows the hero to learn fourth level spells." msgstr "%{skill} дозволÑÑ” Вашому герою вчити заклÑÑ‚Ñ‚Ñ Ñ‡ÐµÑ‚Ð²ÐµÑ€Ñ‚Ð¾Ð³Ð¾ рівнÑ." #, fuzzy msgid "%{skill} allows the hero to learn fifth level spells." msgstr "%{skill} дозволÑÑ” Вашому герою вчити заклÑÑ‚Ñ‚Ñ Ð¿'Ñтого рівнÑ." #, fuzzy msgid "%{skill} regenerates one additional spell point per day to the hero." msgid_plural "" "%{skill} regenerates %{count} additional spell points per day to the hero." msgstr[0] "%{skill} відновлює одину одиницю мани Ð³ÐµÑ€Ð¾Ñ Ñ‰Ð¾Ð´Ð½Ñ." msgstr[1] "%{skill} відновлює %{count} одиниці мани Ð³ÐµÑ€Ð¾Ñ Ñ‰Ð¾Ð´Ð½Ñ." msgstr[2] "%{skill} відновлює %{count} одиниць мани Ð³ÐµÑ€Ð¾Ñ Ñ‰Ð¾Ð´Ð½Ñ." #, fuzzy msgid "%{skill} increases the hero's luck by %{count}." msgstr "%{skill} підвищує удачу Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count}." #, fuzzy msgid "" "%{skill} gives the hero's catapult shots a greater chance to hit and do " "damage to castle walls." msgstr "" "%{skill} дає поÑтрілам катапульти Вашого Ð³ÐµÑ€Ð¾Ñ Ð±Ñ–Ð»ÑŒÑˆÐ¸Ð¹ ÑˆÐ°Ð½Ñ Ð²Ð»ÑƒÑ‡Ð¸Ñ‚Ð¸ та " "пошкодити Ñтіни замка." #, fuzzy msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot has a " "greater chance to hit and do damage to castle walls." msgstr "" "%{skill} дає поÑтрілам катапульти Вашого Ð³ÐµÑ€Ð¾Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ¾Ð²Ð¸Ð¹ поÑтріл, " "підвищуючи ÑˆÐ°Ð½Ñ Ð¿Ð¾ÑˆÐºÐ¾Ð´Ð¶ÐµÐ½Ð½Ñ Ñтін замка кожним поÑтрілом." #, fuzzy msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot " "automatically destroys any wall, except a fortified wall in a Knight castle." msgstr "" "%{skill} дає поÑтрілам катапульти Вашого Ð³ÐµÑ€Ð¾Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ¾Ð²Ð¸Ð¹ поÑтріл Ñ– кожний " "поÑтріл гарантовано руйнує будь-Ñку Ñтіну, окрім укріплень ЛицарÑького замка." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 1st or " "2nd level spell that was cast by an enemy during combat." msgstr "" "%{skill} дає герою %{count}% ÑˆÐ°Ð½Ñ Ð²Ð¸Ð²Ñ‡Ð¸Ñ‚Ð¸ чари 1-го або 2-го Ñ€Ñ–Ð²Ð½Ñ Ð· тих, що " "ворог викориÑтовував проти нього в бою." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 3rd " "level spell (or below) that was cast by an enemy during combat." msgstr "" "%{skill} дає герою %{count}% ÑˆÐ°Ð½Ñ Ð²Ð¸Ð²Ñ‡Ð¸Ñ‚Ð¸ чари 3-го (або нижчого рівнÑ) з " "чиÑла тих, що ворог викориÑтовував проти нього в бою." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 4th " "level spell (or below) that was cast by an enemy during combat." msgstr "" "%{skill} дає герою %{count}% ÑˆÐ°Ð½Ñ Ð²Ð¸Ð²Ñ‡Ð¸Ñ‚Ð¸ чари 4-го (або нижчого рівнÑ) з " "чиÑла тих, що ворог викориÑтовував проти нього в бою." msgid "" "%{skill} allows %{count} percent of the creatures killed in combat to be " "brought back from the dead as Skeletons." msgstr "" "%{skill} дозволÑÑ” повернути %{count} відÑотків вбитих у бою Ñ–Ñтот у виглÑді " "Скелетів." #, fuzzy msgid "" "The hero produces %{count} gold pieces per day as tax revenue from estates." msgstr "Ваш герой збирає %{count} золота податків з маєтків щоднÑ." msgid "Blue" msgstr "Синій" msgid "Green" msgstr "Зелений" msgid "Red" msgstr "Червоний" msgid "Yellow" msgstr "Жовтий" msgid "Orange" msgstr "Помаранчевий" msgid "Purple" msgstr "Ліловий" msgid "barrier|Aqua" msgstr "Бірюзовий" msgid "barrier|Blue" msgstr "Синій" msgid "barrier|Brown" msgstr "Коричневий" msgid "barrier|Gold" msgstr "Золотий" msgid "barrier|Green" msgstr "Зелений" msgid "barrier|Orange" msgstr "Помаранчевий" msgid "barrier|Purple" msgstr "Ліловий" msgid "barrier|Red" msgstr "Червоний" msgid "tent|Aqua" msgstr "Бірюзовий" msgid "tent|Blue" msgstr "Синій" msgid "tent|Brown" msgstr "Коричневий" msgid "tent|Gold" msgstr "Золотий" msgid "tent|Green" msgstr "Зелений" msgid "tent|Orange" msgstr "Помаранчевий" msgid "tent|Purple" msgstr "Ліловий" msgid "tent|Red" msgstr "Червоний" msgid "Experience" msgstr "" msgid "" "Experience allows your heroes to go up levels, increasing their primary and " "secondary skills." msgstr "" msgid "Hero/Stats" msgstr "" msgid "Skills" msgstr "" msgid "Town/Castle" msgstr "" msgid "Garrison" msgstr "" msgid "Gold Per Day:" msgstr "" msgid "View Heroes." msgstr "" msgid "Towns/Castles" msgstr "" msgid "View Towns and Castles." msgstr "" msgid "luck|Cursed" msgstr "ПроклÑта" msgid "luck|Awful" msgstr "Жахлива" msgid "luck|Bad" msgstr "Погана" msgid "luck|Normal" msgstr "Звичайна" msgid "luck|Good" msgstr "Добра" msgid "luck|Great" msgstr "Чудова" msgid "luck|Irish" msgstr "ІрландÑька" msgid "" "Bad luck sometimes falls on the hero's units in combat, causing their " "attacks to only do half damage." msgstr "" msgid "" "Neutral luck means the hero's units will never get lucky or unlucky attacks " "on the enemy." msgstr "" msgid "" "Good luck sometimes lets the hero's units get lucky attacks (double " "strength) in combat." msgstr "" msgid "morale|Treason" msgstr "Зрада" msgid "morale|Awful" msgstr "Жахлива" msgid "morale|Poor" msgstr "Погана" msgid "morale|Normal" msgstr "Звичайна" msgid "morale|Good" msgstr "Добра" msgid "morale|Great" msgstr "Чудова" msgid "morale|Blood!" msgstr "Кров!" #, fuzzy msgid "Bad morale may cause the hero's units to freeze in combat." msgstr "Через низьку мораль %{monster} завмер у паніці." msgid "" "Neutral morale means the hero's units will never be blessed with extra " "attacks or freeze in combat." msgstr "" msgid "Good morale may give the hero's units extra attacks in combat." msgstr "" msgid "Knight" msgstr "Лицар" msgid "Barbarian" msgstr "Варвар" msgid "Sorceress" msgstr "Чаклунка" msgid "Warlock" msgstr "Чорнокнижник" msgid "Wizard" msgstr "Чарівник" msgid "Necromancer" msgstr "Ðекромант" msgid "Multi" msgstr "" msgid "doubleLined|Knight" msgstr "" msgid "doubleLined|Barbarian" msgstr "Варвар" msgid "doubleLined|Sorceress" msgstr "Чаклунка" msgid "doubleLined|Warlock" msgstr "" msgid "doubleLined|Wizard" msgstr "" msgid "" "doubleLined|Necro-\n" "mancer" msgstr "" "Ðекро-\n" "мант" msgid "doubleLinedRace|Multi" msgstr "" msgid "doubleLinedRace|Random" msgstr "" msgid "speed|Standing" msgstr "Ðепорушна" msgid "speed|Crawling" msgstr "Повзуча" msgid "speed|Very Slow" msgstr "Дуже Повільна" msgid "speed|Slow" msgstr "Повільна" msgid "speed|Average" msgstr "СереднÑ" msgid "speed|Fast" msgstr "Швидка" msgid "speed|Very Fast" msgstr "Стрімка" msgid "speed|Ultra Fast" msgstr "Шалена" msgid "speed|Blazing" msgstr "БлиÑкавична" msgid "speed|Instant" msgstr "Миттєва" msgid "week|Squirrel" msgstr "Білки" msgid "week|Rabbit" msgstr "Кролика" msgid "week|Gopher" msgstr "Ховрашка" msgid "week|Badger" msgstr "БорÑука" msgid "week|Rat" msgstr "Пацюка" msgid "week|Eagle" msgstr "Орла" msgid "week|Weasel" msgstr "ЛаÑки" msgid "week|Raven" msgstr "Ворона" msgid "week|Mongoose" msgstr "МангуÑта" msgid "week|Dog" msgstr "Собаки" msgid "week|Aardvark" msgstr "Тхора" msgid "week|Lizard" msgstr "Ящірки" msgid "week|Tortoise" msgstr "Черепахи" msgid "week|Hedgehog" msgstr "Їжака" msgid "week|Condor" msgstr "Кондора" msgid "week|Ant" msgstr "Мурахи" msgid "week|Grasshopper" msgstr "Коника" msgid "week|Dragonfly" msgstr "Бабки" msgid "week|Spider" msgstr "Павука" msgid "week|Butterfly" msgstr "Метелика" msgid "week|Bumblebee" msgstr "ДжмелÑ" msgid "week|Locust" msgstr "Сарани" msgid "week|Earthworm" msgstr "Дощового Черв'Ñка" msgid "week|Hornet" msgstr "ШешрнÑ" msgid "week|Beetle" msgstr "Жука" msgid "week|PLAGUE" msgstr "ЧУМИ" msgid "week|Unnamed" msgstr "Безіменний" msgid "Desert" msgstr "ПуÑтелÑ" msgid "Snow" msgstr "Сніг" msgid "Wasteland" msgstr "ПуÑтка" msgid "Beach" msgstr "ПлÑж" msgid "Lava" msgstr "Лава" msgid "Dirt" msgstr "Багнюка" msgid "Grass" msgstr "Трава" msgid "Ocean" msgstr "Океан" msgid "maps|Small" msgstr "Мала" msgid "maps|Medium" msgstr "СереднÑ" msgid "maps|Large" msgstr "Велика" msgid "maps|Extra Large" msgstr "Ðадвелика" msgid "maps|Custom Size" msgstr "КориÑтувацький Розмір" msgid "Ore Mine" msgstr "Рудна КопальнÑ" msgid "Sulfur Mine" msgstr "Сірчана КопальнÑ" msgid "Crystal Mine" msgstr "Кришталева КопальнÑ" msgid "Gems Mine" msgstr "ÐšÐ¾Ð¿Ð°Ð»ÑŒÐ½Ñ Ð¡Ð°Ð¼Ð¾Ñ†Ð²Ñ–Ñ‚Ñ–Ð²" msgid "Gold Mine" msgstr "Золота КопальнÑ" msgid "Mine" msgstr "КопальнÑ" msgid "Burma shave." msgstr "" msgid "Next sign 50 miles." msgstr "ÐаÑтупний знак за 50 миль" msgid "See Rock City." msgstr "" msgid "This space for rent." msgstr "" msgid "No object" msgstr "Порожнє міÑце" msgid "Alchemist Lab" msgstr "Ðлхімічна ЛабораторіÑ" msgid "Sign" msgstr "Вказівник" msgid "Buoy" msgstr "Буй" msgid "Skeleton" msgstr "Скелет" msgid "Daemon Cave" msgstr "Печера Демона" msgid "Treasure Chest" msgstr "Скринька зі Скарбом" msgid "Faerie Ring" msgstr "Коло Фей" msgid "Campfire" msgstr "Вогнище" msgid "Fountain" msgstr "Фонтан" msgid "Gazebo" msgstr "Ðльтанка" msgid "Genie Lamp" msgstr "Лампа Джина" msgid "Archer's House" msgstr "Дім Лучників" msgid "Goblin Hut" msgstr "ГоблінÑька Хатина" msgid "Dwarf Cottage" msgstr "Хатинка Гнома" msgid "Peasant Hut" msgstr "СелÑнÑька Хата" msgid "Stables" msgstr "Стайні" msgid "Alchemist's Tower" msgstr "Вежа Ðлхіміка" msgid "Event" msgstr "ПодіÑ" msgid "Dragon City" msgstr "МіÑто Драконів" msgid "Lighthouse" msgid_plural "Lighthouses" msgstr[0] "МаÑк" msgstr[1] "МаÑк" msgstr[2] "МаÑк" msgid "Water Wheel" msgid_plural "Water Wheels" msgstr[0] "ВодÑне КолеÑо" msgstr[1] "ВодÑні КолеÑа" msgstr[2] "ВодÑні КолеÑа" msgid "Monster" msgstr "МонÑтр" msgid "Obelisk" msgstr "ОбеліÑк" msgid "Oasis" msgstr "Оаза" msgid "Resource" msgstr "РеÑурÑ" msgid "Sawmill" msgstr "Тартак" msgid "Oracle" msgstr "Оракул" msgid "Shrine of the First Circle" msgstr "CвÑÑ‚Ð¸Ð½Ñ 1-го Кола" msgid "Shipwreck" msgstr "Уламки КораблÑ" msgid "Sea Chest" msgstr "МорÑький Сундук" msgid "Desert Tent" msgstr "ПуÑтельний Ðамет" msgid "Stone Liths" msgstr "Моноліт" msgid "Wagon Camp" msgstr "Табір з Возів" msgid "Hut of the Magi" msgstr "Хатина Мага" msgid "Whirlpool" msgstr "Водоверть" msgid "Windmill" msgid_plural "Windmills" msgstr[0] "ВітрÑк" msgstr[1] "ВітрÑки" msgstr[2] "ВітрÑки" msgid "Artifact" msgstr "Ðртефакт" msgid "Mermaid" msgstr "РуÑалка" msgid "Boat" msgstr "Човен" msgid "Random Ultimate Artifact" msgstr "Випадковий Унікальний Ðртефакт" msgid "Random Artifact" msgstr "Випадковий Ðртефакт" msgid "Random Resource" msgstr "Випадковий РеÑурÑ" msgid "Random Monster" msgstr "Випадковий МонÑтр" msgid "Random Town" msgstr "Випадкове МіÑто" msgid "Random Castle" msgstr "Випадковий Замок" msgid "Eye of the Magi" msgstr "Око Магів" msgid "Random Monster - weak" msgstr "Випадковий МонÑтр - Ñлабкий" msgid "Random Monster - medium" msgstr "Випадковий МонÑтр - Ñередній" msgid "Random Monster - strong" msgstr "Випадковий МонÑтр - Ñильний" msgid "Random Monster - very strong" msgstr "Випадковий МонÑтр - могутній" msgid "Hero" msgstr "" msgid "Nothing Special" msgstr "Ðічого ОÑобливого" msgid "Mossy Rock" msgstr "" msgid "Watch Tower" msgstr "Вартова Вежа" msgid "Tree House" msgstr "Будинок на Дереві" msgid "Tree City" msgstr "МіÑто на Дереві" msgid "Ruins" msgstr "Руїни" msgid "Fort" msgstr "Форт" msgid "Abandoned Mine" msgstr "Покинута ШтольнÑ" msgid "Sirens" msgstr "Сирени" msgid "Standing Stones" msgstr "СтоÑчі Камені" msgid "Idol" msgstr "Ідол" msgid "Tree of Knowledge" msgstr "Дерево Знань" msgid "Witch Doctor's Hut" msgstr "Хатина ЗнахарÑ" msgid "Temple" msgstr "Храм" msgid "Hill Fort" msgstr "Форт на Пагорбі" msgid "Halfling Hole" msgstr "Ðора ÐапівроÑликів" msgid "Mercenary Camp" msgstr "Табір Ðайманців" msgid "Shrine of the Second Circle" msgstr "CвÑÑ‚Ð¸Ð½Ñ 2-го Кола" msgid "Shrine of the Third Circle" msgstr "CвÑÑ‚Ð¸Ð½Ñ 3-го Кола" msgid "City of the Dead" msgstr "МіÑто Мертвих" msgid "Sphinx" msgstr "СфінкÑ" msgid "Wagon" msgstr "Віз" msgid "Tar Pit" msgstr "СмолÑна Яма" msgid "Artesian Spring" msgstr "ÐртезіанÑьке Джерело" msgid "Troll Bridge" msgstr "Тролиний МіÑÑ‚" msgid "Watering Hole" msgstr "Джерело" msgid "Witch's Hut" msgstr "Хатина Відьми" msgid "Xanadu" msgstr "Занду" msgid "Lean-To" msgstr "ÐавіÑ" msgid "Magellan's Maps" msgstr "Мапи Магеллана" msgid "Flotsam" msgstr "Уламки" msgid "Derelict Ship" msgstr "Покинутий Корабель" msgid "Shipwreck Survivor" msgstr "Уцілілий з КораблÑ" msgid "Bottle" msgstr "ПлÑшка" msgid "Magic Well" msgstr "Чарівна КриницÑ" msgid "Magic Garden" msgid_plural "Magic Gardens" msgstr[0] "Чарівний Сад" msgstr[1] "Чарівні Сади" msgstr[2] "Чарівних Садів" msgid "Observation Tower" msgstr "ОглÑдова Вежа" msgid "Freeman's Foundry" msgstr "ЛиварнÑ" msgid "Reefs" msgstr "Рифи" msgid "Volcano" msgstr "Вулкан" msgid "Flowers" msgstr "Квіти" msgid "Rock" msgstr "СкелÑ" msgid "Water Lake" msgstr "Озеро" msgid "Mandrake" msgstr "Мандрагора" msgid "Dead Tree" msgstr "Мертве Дерево" msgid "Stump" msgstr "Пень" msgid "Crater" msgstr "Кратер" msgid "Cactus" msgstr "КактуÑ" msgid "Mound" msgstr "Курган" msgid "Dune" msgstr "Дюна" msgid "Lava Pool" msgstr "Лавовий БаÑейн" msgid "Shrub" msgstr "Кущі" msgid "Barrow Mounds" msgstr "Могильні Кургани" msgid "Random Artifact - Treasure" msgstr "Випадковий артефакт - РеліквіÑ" msgid "Random Artifact - Minor" msgstr "Випадковий артефакт - Малий" msgid "Random Artifact - Major" msgstr "Випадковий артефакт - Великий" msgid "Barrier" msgstr "Бар'єр" msgid "Traveller's Tent" msgstr "Ðамет Мандрівника" msgid "Jail" msgstr "Ð’'ÑзницÑ" msgid "Fire Summoning Altar" msgstr "Вівтар Виклику Вогню" msgid "Air Summoning Altar" msgstr "Вівтар Виклику ПовітрÑ" msgid "Earth Summoning Altar" msgstr "Вівтар Виклику Землі" msgid "Water Summoning Altar" msgstr "Вівтар Виклику Води" msgid "Unknown Monster" msgstr "Ðевідомий монÑтр" msgid "Unknown Monsters" msgstr "Ðевідомі монÑтри" msgid "Peasant" msgstr "CелÑнин" msgid "Peasants" msgstr "CелÑни" msgid "Archer" msgstr "Лучник" msgid "Archers" msgstr "Лучники" msgid "Ranger" msgstr "Рейнджер" msgid "Rangers" msgstr "Рейнджери" msgid "Pikeman" msgstr "Пікенер" msgid "Pikemen" msgstr "Пікенери" msgid "Veteran Pikeman" msgstr "Пікенер-ветеран" msgid "Veteran Pikemen" msgstr "Пікенери-ветерани" msgid "Swordsman" msgstr "Мечник" msgid "Swordsmen" msgstr "Мечники" msgid "Master Swordsman" msgstr "МайÑтер-мечник" msgid "Master Swordsmen" msgstr "МайÑтри-мечники" msgid "Cavalries" msgstr "КавалеріÑ" msgid "Cavalry" msgstr "КавалериÑÑ‚" msgid "Champion" msgstr "Чемпіон" msgid "Champions" msgstr "Чемпіони" msgid "Paladin" msgstr "Паладин" msgid "Paladins" msgstr "Паладини" msgid "Crusader" msgstr "ХреÑтоноÑець" msgid "Crusaders" msgstr "ХреÑтоноÑці" msgid "Goblin" msgstr "Гоблін" msgid "Goblins" msgstr "Гобліни" msgid "Orc" msgstr "Орк" msgid "Orcs" msgstr "Орки" msgid "Orc Chief" msgstr "Вождь Орків" msgid "Orc Chiefs" msgstr "Вожді Орків" msgid "Wolf" msgstr "Вовк" msgid "Wolves" msgstr "Вовки" msgid "Ogre" msgstr "Огр" msgid "Ogres" msgstr "Огри" msgid "Ogre Lord" msgstr "Лорд Огрів" msgid "Ogre Lords" msgstr "Лорди Огрів" msgid "Troll" msgstr "Троль" msgid "Trolls" msgstr "Тролі" msgid "War Troll" msgstr "Бойовий Троль" msgid "War Trolls" msgstr "Бойові Тролі" msgid "Cyclopes" msgstr "Циклопи" msgid "Cyclops" msgstr "Циклоп" msgid "Sprite" msgstr "ФеÑ" msgid "Sprites" msgstr "Феї" msgid "Dwarf" msgstr "Гном" msgid "Dwarves" msgstr "Гноми" msgid "Battle Dwarf" msgstr "Бойовий Гном" msgid "Battle Dwarves" msgstr "Бойові Гноми" msgid "Elf" msgstr "Ельф" msgid "Elves" msgstr "Ельфи" msgid "Grand Elf" msgstr "Вищий Ельф" msgid "Grand Elves" msgstr "Вищі Ельфи" msgid "Druid" msgstr "Друїд" msgid "Druids" msgstr "Друїди" msgid "Greater Druid" msgstr "Старший Друїд" msgid "Greater Druids" msgstr "Старші Друїди" msgid "Unicorn" msgstr "Єдиноріг" msgid "Unicorns" msgstr "Єдинороги" msgid "Phoenix" msgstr "ФенікÑ" msgid "Phoenixes" msgstr "ФенікÑи" msgid "Centaur" msgstr "Кентавр" msgid "Centaurs" msgstr "Кентаври" msgid "Gargoyle" msgstr "ГоргульÑ" msgid "Gargoyles" msgstr "Горгульї" msgid "Griffin" msgstr "Грифон" msgid "Griffins" msgstr "Грифони" msgid "Minotaur" msgstr "Мінотавр" msgid "Minotaurs" msgstr "Мінотаври" msgid "Minotaur King" msgstr "Король Мінотаврів" msgid "Minotaur Kings" msgstr "Королі Мінотаврів" msgid "Hydra" msgstr "Гідра" msgid "Hydras" msgstr "Гідри" msgid "Green Dragon" msgstr "Зелений Дракон" msgid "Green Dragons" msgstr "Зелені Дракони" msgid "Red Dragon" msgstr "Червоний Дракон" msgid "Red Dragons" msgstr "Червоні Дракони" msgid "Black Dragon" msgstr "Чорний Дракон" msgid "Black Dragons" msgstr "Чорні Дракони" msgid "Halfling" msgstr "ÐапівроÑлик" msgid "Halflings" msgstr "ÐапівроÑлики" msgid "Boar" msgstr "Кабан" msgid "Boars" msgstr "Кабани" msgid "Iron Golem" msgstr "Залізний Голем" msgid "Iron Golems" msgstr "Залізні Големи" msgid "Steel Golem" msgstr "Сталевий Голем" msgid "Steel Golems" msgstr "Сталеві Големи" msgid "Roc" msgstr "Птах Рух" msgid "Rocs" msgstr "Птахи Рух" msgid "Mage" msgstr "Маг" msgid "Magi" msgstr "Маги" msgid "Archmage" msgstr "Ðрхімаг" msgid "Archmagi" msgstr "Ðрхімаги" msgid "Giant" msgstr "Гігант" msgid "Giants" msgstr "Гіганти" msgid "Titan" msgstr "Титан" msgid "Titans" msgstr "Титани" msgid "Skeletons" msgstr "Скелети" msgid "Zombie" msgstr "Зомбі" msgid "Zombies" msgstr "Зомбі" msgid "Mutant Zombie" msgstr "Зомбі-мутант" msgid "Mutant Zombies" msgstr "Зомбі-мутанти" msgid "Mummies" msgstr "Мумії" msgid "Mummy" msgstr "МуміÑ" msgid "Royal Mummies" msgstr "КоролівÑькі Мумії" msgid "Royal Mummy" msgstr "КоролівÑька МуміÑ" msgid "Vampire" msgstr "Вампір" msgid "Vampires" msgstr "Вампіри" msgid "Vampire Lord" msgstr "Лорд Вампірів" msgid "Vampire Lords" msgstr "Лорди Вампірів" msgid "Lich" msgstr "Ліч" msgid "Liches" msgstr "Лічі" msgid "Power Lich" msgstr "Могутній Ліч" msgid "Power Liches" msgstr "Могутні Лічі" msgid "Bone Dragon" msgstr "КіÑÑ‚Ñний Дракон" msgid "Bone Dragons" msgstr "КіÑÑ‚Ñні Дракони" msgid "Rogue" msgstr "Розбійник" msgid "Rogues" msgstr "Розбійники" msgid "Nomad" msgstr "Кочівник" msgid "Nomads" msgstr "Кочівники" msgid "Ghost" msgstr "Привид" msgid "Ghosts" msgstr "Привиди" msgid "Genie" msgstr "Джин" msgid "Genies" msgstr "Джини" msgid "Medusa" msgstr "Медуза" msgid "Medusas" msgstr "Медузи" msgid "Earth Elemental" msgstr "ЗемлÑний Елементаль" msgid "Earth Elementals" msgstr "ЗемлÑні Елементалі" msgid "Air Elemental" msgstr "ПовітрÑний Елементаль" msgid "Air Elementals" msgstr "ПовітрÑні Елементалі" msgid "Fire Elemental" msgstr "ВогнÑний Елементаль" msgid "Fire Elementals" msgstr "ВогнÑні Елементалі" msgid "Water Elemental" msgstr "Водний Елементаль" msgid "Water Elementals" msgstr "Водні Елементалі" msgid "Random Monsters" msgstr "Випадкові МонÑтри" msgid "Random Monster 1" msgstr "Випадковий МонÑтр 1" msgid "Random Monsters 1" msgstr "Випадкові МонÑтри 1" msgid "Random Monster 2" msgstr "Випадковий МонÑтр 2" msgid "Random Monsters 2" msgstr "Випадкові МонÑтри 2" msgid "Random Monster 3" msgstr "Випадковий МонÑтр 3" msgid "Random Monsters 3" msgstr "Випадкові МонÑтри 3" msgid "Random Monster 4" msgstr "Випадковий МонÑтр 4" msgid "Random Monsters 4" msgstr "Випадкові МонÑтри 4" msgid "Double shot" msgstr "" msgid "2-hex monster" msgstr "2-клітинний монÑтр" msgid "Double strike" msgstr "Подвійний удар" msgid "Double damage to Undead" msgstr "" msgid "% magic resistance" msgstr "" msgid "Immune to Mind spells" msgstr "" msgid "Immune to Elemental spells" msgstr "" msgid "Immune to Fire spells" msgstr "" msgid "Immune to Cold spells" msgstr "" msgid "Immune to " msgstr "" msgid "% immunity to %{spell} spell" msgstr "" msgid "% damage from Elemental spells" msgstr "" msgid "% chance to Dispel beneficial spells" msgstr "" msgid "% chance to Paralyze" msgstr "" msgid "% chance to Petrify" msgstr "" msgid "% chance to Blind" msgstr "" msgid "% chance to Curse" msgstr "" msgid "% chance to cast %{spell} spell" msgstr "" msgid "HP regeneration" msgstr "" msgid "Two hexes attack" msgstr "" msgid "Flyer" msgstr "" msgid "Always retaliates" msgstr "" msgid "Attacks all adjacent enemies" msgstr "" msgid "No melee penalty" msgstr "" msgid "Dragon" msgstr "" msgid "Undead" msgstr "" msgid "No enemy retaliation" msgstr "" msgid "HP drain" msgstr "" msgid "Cloud attack" msgstr "" msgid "Decreases enemy's morale by " msgstr "" msgid "% chance to halve enemy" msgstr "" msgid "Soul Eater" msgstr "" msgid "Elemental" msgstr "" msgid "No Morale" msgstr "" msgid "200% damage from Fire spells" msgstr "" msgid "200% damage from Cold spells" msgstr "" msgid "% damage from %{spell} spell" msgstr "" msgid "% immunity to " msgstr "" msgid "Lightning" msgstr "" msgid "% damage from " msgstr "" msgid "The three Anduran artifacts magically combine into one." msgstr "" msgid "View Spells" msgstr "" msgid "View %{name} Info" msgstr "" msgid "Move %{name}" msgstr "" msgid "Cannot move the Spellbook" msgstr "" msgid "This item can't be traded." msgstr "" msgid "Invalid Artifact" msgstr "" #, fuzzy msgid "The %{name} increases the hero's knowledge by %{count}." msgstr "%{name} підвищує мораль війÑьк Вашого Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count}." msgid "Ultimate Book of Knowledge" msgstr "" #, fuzzy msgid "The %{name} increases the hero's attack skill by %{count}." msgstr "%{name} підвищує ваш ЗахиÑÑ‚ на %{count}." msgid "Ultimate Sword of Dominion" msgstr "" #, fuzzy msgid "The %{name} increases the hero's defense skill by %{count}." msgstr "%{name} підвищує ваш ЗахиÑÑ‚ на %{count}." msgid "Ultimate Cloak of Protection" msgstr "" #, fuzzy msgid "The %{name} increases the hero's spell power by %{count}." msgstr "%{name} поÑилює вашу Силу Магії на %{count}." msgid "Ultimate Wand of Magic" msgstr "" #, fuzzy msgid "" "The %{name} increases the hero's attack and defense skills by %{count} each." msgstr "%{name} підвищує ваш ЗахиÑÑ‚ на %{count}." msgid "Ultimate Shield" msgstr "" #, fuzzy msgid "" "The %{name} increases the hero's spell power and knowledge by %{count} each." msgstr "%{name} підвищує мораль війÑьк Вашого Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count}." msgid "Ultimate Staff" msgstr "" #, fuzzy msgid "" "The %{name} increases each of the hero's basic skills by %{count} points." msgstr "%{name} підвищує ваш ЗахиÑÑ‚ на %{count}." msgid "Ultimate Crown" msgstr "" msgid "Golden Goose" msgstr "" msgid "The %{name} brings in an income of %{count} gold per day." msgstr "" msgid "Arcane Necklace of Magic" msgstr "" msgid "" "After rescuing a Sorceress from a cursed tomb, she rewards your heroism with " "an exquisite jeweled necklace." msgstr "" msgid "Caster's Bracelet of Magic" msgstr "" msgid "" "While searching through the rubble of a caved-in mine, you free a group of " "trapped Dwarves. Grateful, the leader gives you a golden bracelet." msgstr "" msgid "Mage's Ring of Power" msgstr "Магічний ПерÑтень МогутноÑті" msgid "" "A cry of pain leads you to a Centaur, caught in a trap. Upon setting the " "creature free, he hands you a small pouch. Emptying the contents, you find a " "dazzling jeweled ring." msgstr "" "Відчайдушний крик болю приводить Ð²Ð°Ñ Ð´Ð¾ Кентавра, що потрапив у паÑтку. Ви " "звільнÑєте його, Ñ– він дарує вам маленький мішечок. Спорожнивши його, ви " "знаходите Ñліпучий, прикрашений коштовним каміннÑм перÑтень." msgid "Witch's Broach of Magic" msgstr "" msgid "" "Alongside the remains of a burnt witch lies a beautiful broach, intricately " "designed. Approaching the corpse with caution, you add the broach to your " "inventory." msgstr "" msgid "Medal of Valor" msgstr "Медаль за Відвагу" #, fuzzy msgid "The %{name} increases the morale of the hero's army by %{count}." msgstr "%{name} підвищує мораль війÑьк Вашого Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count}." msgid "" "Freeing a virtuous maiden from the clutches of an evil overlord, you are " "granted a Medal of Valor by the King's herald." msgstr "" "Звільнивши благородну леді з лещат злого володарÑ, ви отримуєте Медаль за " "Відвагу від королівÑького герольда." msgid "Medal of Courage" msgstr "" msgid "" "After saving a young boy from a vicious pack of Wolves, you return him to " "his father's manor. The grateful nobleman awards you with a Medal of Courage." msgstr "" msgid "Medal of Honor" msgstr "Медаль за ЧеÑть" msgid "" "After freeing a princess of a neighboring kingdom from the evil clutches of " "despicable slavers, she awards you with a Medal of Honor." msgstr "" "ПіÑÐ»Ñ Ð·Ð²Ñ–Ð»ÑŒÐ½ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¸Ð½Ñ†ÐµÑи ÑуÑіднього королівÑтва із лабетів підлих " "работорговців, вона нагороджує Ð²Ð°Ñ ÐœÐµÐ´Ð°Ð»Ð»ÑŽ за ЧеÑть." msgid "Medal of Distinction" msgstr "" msgid "" "Ridding the countryside of the hideous Minotaur who made a sport of eating " "noblemen's Knights, you are honored with the Medal of Distinction." msgstr "" msgid "Fizbin of Misfortune" msgstr "" #, fuzzy msgid "" "The %{name} greatly decreases the morale of the hero's army by %{count}." msgstr "%{name} підвищує мораль війÑьк Вашого Ð³ÐµÑ€Ð¾Ñ Ð½Ð° %{count}." msgid "" "You stumble upon a medal lying alongside the empty road. Adding the medal to " "your inventory, you become aware that you have acquired the undesirable " "Fizbin of Misfortune, greatly decreasing your army's morale." msgstr "" msgid "Thunder Mace of Dominion" msgstr "Громова Булава ПануваннÑ" msgid "" "During a sudden storm, a bolt of lightning strikes a tree, splitting it. " "Inside the tree you find a mysterious mace." msgstr "" "Під Ñ‡Ð°Ñ Ð½ÐµÑподіваного шторму блиÑкавка вдарÑÑ” у дерево, розколовши його. " "Ð’Ñередині ви знаходите загадкову булаву." msgid "Armored Gauntlets of Protection" msgstr "Броньована Ð ÑƒÐºÐ°Ð²Ð¸Ñ†Ñ Ð—Ð°Ñ…Ð¸Ñту" #, fuzzy msgid "The %{name} increase the hero's defense skill by %{count}." msgstr "%{name} підвищує ваш ЗахиÑÑ‚ на %{count}." msgid "" "You encounter the infamous Black Knight! After a grueling duel ending in a " "draw, the Knight, out of respect, offers you a pair of armored gauntlets." msgstr "" msgid "Defender Helm of Protection" msgstr "Шолом ЗахиÑника" msgid "" "A glint of golden light catches your eye. Upon further investigation, you " "find a golden helm hidden under a bush." msgstr "" "Золотий відблиÑк привертає вашу увагу. ПридивившиÑÑŒ уважніше, ви знаходите " "золотого шолома, що був Ñхований під кущем." msgid "Giant Flail of Dominion" msgstr "" msgid "" "A clumsy Giant has killed himself with his own flail. Knowing your superior " "skill with this weapon, you confidently remove the spectacular flail from " "the fallen Giant." msgstr "" msgid "Ballista of Quickness" msgstr "БаліÑта ШвидкоÑті" #, fuzzy msgid "The %{name} gives the hero's catapult one extra shot per combat round." msgstr "" "%{name} надає вашій катапульті додатковий поÑтріл у кожному раунді під Ñ‡Ð°Ñ " "облоги." msgid "" "Walking through the ruins of an ancient walled city, you find the instrument " "of the city's destruction, an elaborately crafted ballista." msgstr "" msgid "Stealth Shield of Protection" msgstr "Ðезримий Щит ЗахиÑту" msgid "" "A stone statue of a warrior holds a silver shield. As you remove the shield, " "the statue crumbles into dust." msgstr "" "Кам'Ñна ÑÑ‚Ð°Ñ‚ÑƒÑ Ð²Ð¾Ñ—Ð½Ð° тримає Ñрібного щита. Щойно ви забираєте щит, ÑÑ‚Ð°Ñ‚ÑƒÑ " "розпорошуєтьÑÑ Ð½Ð° пил." msgid "Dragon Sword of Dominion" msgstr "ДраконÑчий Меч ПануваннÑ" msgid "" "As you are walking along a narrow path, a nearby bush suddenly bursts into " "flames. Before your eyes the flames become the image of a beautiful woman. " "She holds out a magnificent sword to you." msgstr "" "Поки ви йдете вузькою Ñтежкою, кущ неподалік неÑподівано Ñпалахує вогнем. " "Перед вашими очима поÑтає образ вродливої жінки. Вона проÑÑ‚Ñгає вам чудового " "меча." msgid "Power Axe of Dominion" msgstr "Силова Сокира ПануваннÑ" msgid "" "You see a silver axe embedded deeply in the ground. After several " "unsuccessful attempts by your army to remove the axe, you tightly grip the " "handle of the axe and effortlessly pull it free." msgstr "" "Ви бачите Ñрібну Ñокиру, глибоко занурену у землю. ПіÑÐ»Ñ ÐºÑ–Ð»ÑŒÐºÐ¾Ñ… невдалих " "Ñпроб ваших воїнів вийнÑти Ñ—Ñ—, ви міцно хапаєтеÑÑŒ за руків'Ñ Ñокири, та без " "зуÑиль виÑмикуєте Ñ—Ñ—." msgid "Divine Breastplate of Protection" msgstr "БожеÑтвений Ðагрудник ЗахиÑту" msgid "" "A gang of Rogues is sifting through the possessions of dead warriors. " "Scaring off the scavengers, you note the Rogues had overlooked a beautiful " "breastplate." msgstr "" "Банда Розбійників копирÑаєтьÑÑ Ñƒ речах загиблих воїнів. ВідлÑкуючи " "мародерів, ви помічаєте, що Розбійники не помітили чудовий нагрудник." msgid "Minor Scroll of Knowledge" msgstr "Малий Сувій Знань" msgid "" "Before you appears a levitating glass case with a scroll, perched upon a bed " "of crimson velvet. At your touch, the lid opens and the scroll floats into " "your awaiting hands." msgstr "" "Перед вами з'ÑвлÑєтьÑÑ Ð»ÐµÐ²Ñ–Ñ‚ÑƒÑŽÑ‡Ð¸Ð¹ ÑклÑний футлÑÑ€ із Ñувоєм, що лежить на " "підÑтилці з червоного окÑамиту. Від доторку футлÑÑ€ відкриваєтьÑÑ, а Ñувій " "лине до ваших проÑÑ‚Ñгнутих рук." msgid "Major Scroll of Knowledge" msgstr "Великий Сувій Знань" msgid "" "Visiting a local wiseman, you explain the intent of your journey. He reaches " "into a sack and withdraws a yellowed scroll and hands it to you." msgstr "" "Відвідавши міÑцевого мудрецÑ, ви поÑÑнюєте йому мету вашої подорожі. " "ПокопирÑавшиÑÑŒ у мішку, він витÑгає пожовклий Ñувій та проÑÑ‚Ñгає його вам." msgid "Superior Scroll of Knowledge" msgstr "" msgid "" "You come across the remains of an ancient Druid. Bones, yellowed with age, " "peer from the ragged folds of her robe. Searching the robe, you discover a " "scroll hidden in the folds." msgstr "" msgid "Foremost Scroll of Knowledge" msgstr "Древній Сувій ЗнаннÑ" msgid "" "Mangled bones, yellowed with age, peer from the ragged folds of a dead " "Druid's robe. Searching the robe, you discover a scroll hidden within." msgstr "" "Пожовклі від чаÑу та понівечені кіÑтки визирають з-поміж пошарпаних Ñкладок " "мантії мертвого Друїда. Обшукуючи мантію, ви знаходите захований у ній Ñувій." msgid "Endless Sack of Gold" msgstr "" #, fuzzy msgid "The %{name} provides the hero with %{count} gold per day." msgstr "%{name} збільшує видобуток дерева на %{count} в день." msgid "" "A little leprechaun dances gleefully around a magic sack. Seeing you " "approach, he stops in mid-stride. The little man screams and stamps his foot " "ferociously, vanishing into thin air. Remembering the old leprechaun saying " "'Finders Keepers', you grab the sack and leave." msgstr "" msgid "Endless Bag of Gold" msgstr "" msgid "" "A noblewoman, separated from her traveling companions, asks for your help. " "After escorting her home, she rewards you with a bag filled with gold." msgstr "" msgid "Endless Purse of Gold" msgstr "" msgid "" "In your travels, you find a leather purse filled with gold that once " "belonged to a great warrior king who had the ability to transform any " "inanimate object into gold." msgstr "" msgid "Nomad Boots of Mobility" msgstr "Чоботи Кочівника" #, fuzzy msgid "The %{name} increase the hero's movement on land." msgstr "%{name} дають вашому герою додаткові очки руху." msgid "" "A Nomad trader seeks protection from a tribe of Goblins. For your " "assistance, he gives you a finely crafted pair of boots made from the " "softest leather. Looking closely, you see fascinating ancient carvings " "engraved on the leather." msgstr "" "Кочівник-торговець шукає захиÑту від племені Гоблінів. За вашу допомогу він " "дає вам пару вишуканих чобіт, зроблених з найм'Ñкішої шкіри. ПридивившиÑÑŒ, " "ви помічаєте вражаюче Ñтаровинне Ñ€Ñ–Ð·ÑŒÐ±Ð»ÐµÐ½Ð½Ñ Ð½Ð° шкірі." msgid "Traveler's Boots of Mobility" msgstr "Чоботи Мандрівника" msgid "" "Discovering a pair of beautifully beaded boots made from the finest and " "softest leather, you thank the anonymous donor and add the boots to your " "inventory." msgstr "" "Знайшовши пару прикрашених біÑером та зроблених з найкращої та найм'Ñкішої " "шкіри чобіт, ви дÑкуєте невідомому добродію та кладете чоботи до Ñвого " "інвентарÑ." msgid "Lucky Rabbit's Foot" msgstr "" #, fuzzy msgid "The %{name} increases the luck of the hero's army by %{count}." msgstr "%{name} підвищує вашу удачу у битві на %{count}." msgid "" "A traveling merchant offers you a rabbit's foot, made of gleaming silver " "fur, for safe passage. The merchant explains the charm will increase your " "luck in combat." msgstr "" msgid "Golden Horseshoe" msgstr "" msgid "" "An ensnared Unicorn whinnies in fright. Murmuring soothing words, you set " "her free. Snorting and stamping her front hoof once, she gallops off. " "Looking down you see a golden horseshoe." msgstr "" msgid "Gambler's Lucky Coin" msgstr "" msgid "" "You have captured a mischievous imp who has been terrorizing the region. In " "exchange for his release, he rewards you with a magical coin." msgstr "" msgid "Four-Leaf Clover" msgstr "" msgid "" "In the middle of a patch of dead and dry vegetation, to your surprise you " "find a healthy green four-leaf clover." msgstr "" msgid "True Compass of Mobility" msgstr "" #, fuzzy msgid "The %{name} increases the hero's movement on land and sea." msgstr "%{name} дають вашому герою додаткові очки руху." msgid "" "An old man claiming to be an inventor asks you to try his latest invention. " "He then hands you a compass." msgstr "" msgid "Sailor's Astrolabe of Mobility" msgstr "" #, fuzzy msgid "The %{name} increases the hero's movement on sea." msgstr "%{name} дають вашому герою додаткові очки руху." msgid "" "An old sea captain is being tortured by Ogres. You save him, and in return " "he rewards you with a wondrous instrument to measure the distance of a star." msgstr "" msgid "Evil Eye" msgstr "" msgid "The %{name} reduces the casting cost of curse spells by half." msgstr "" msgid "" "While venturing into a decrepit hut you find the Skeleton of a long dead " "witch. Investigation of the remains reveals a glass eye rolling around " "inside an empty skull." msgstr "" msgid "Enchanted Hourglass" msgstr "" #, fuzzy msgid "" "The %{name} extends the duration of all the hero's spells by %{count} turns." msgstr "%{name} підвищує Ñ‡Ð°Ñ Ð´Ñ–Ñ— ваших заклÑть на %{count} ходів." msgid "" "A surprise turn in the landscape finds you in the midst of a grisly scene: " "Vultures picking at the aftermath of a terrible battle. Your cursory search " "of the remains turns up an enchanted hourglass." msgstr "" msgid "Gold Watch" msgstr "" msgid "The %{name} doubles the effectiveness of the hero's hypnotize spells." msgstr "" msgid "" "In reward for helping his cart out of a ditch, a traveling potion salesman " "gives you a \"magic\" gold watch. Unbeknownst to him, the watch really is " "magical." msgstr "" msgid "Skullcap" msgstr "" msgid "The %{name} halves the casting cost of all mind influencing spells." msgstr "" msgid "" "A brief stop at an improbable rural inn yields an exchange of money, tales, " "and accidentally, luggage. You find a magical skullcap in your new backpack." msgstr "" msgid "Ice Cloak" msgstr "" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from cold spells." msgstr "%{name} надає вашим війÑькам імунітет до РозÑÑ–ÑŽÐ²Ð°Ð½Ð½Ñ ÐœÐ°Ð³Ñ–Ñ—." msgid "" "Responding to the panicked cries of a damsel in distress, you discover a " "young woman fleeing from a hungry bear. You slay the beast in the nick of " "time, and the grateful Sorceress weaves a magic cloak from the bear's hide." msgstr "" msgid "Fire Cloak" msgstr "" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from fire spells." msgstr "%{name} надає вашим війÑькам імунітет до РозÑÑ–ÑŽÐ²Ð°Ð½Ð½Ñ ÐœÐ°Ð³Ñ–Ñ—." msgid "" "You've come upon a fight between a Necromancer and a Paladin. The " "Necromancer blasts the Paladin with a fire bolt, bringing him to his knees. " "Acting quickly, you slay the evil one before the final blow. The grateful " "Paladin gives you the fire cloak that saved him." msgstr "" msgid "Lightning Helm" msgstr "" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from lightning " "spells." msgstr "%{name} надає вашим війÑькам імунітет до РозÑÑ–ÑŽÐ²Ð°Ð½Ð½Ñ ÐœÐ°Ð³Ñ–Ñ—." msgid "" "A traveling tinker in need of supplies offers you a helm with a thunderbolt " "design on its top in exchange for food and water. Curious, you accept, and " "later find out that the helm is magical." msgstr "" msgid "Evercold Icicle" msgstr "" #, fuzzy msgid "" "The %{name} causes the hero's cold spells to do %{count} percent more damage " "to enemy troops." msgstr "" "%{name} зменшує вартіÑть капітулÑції на %{count} відÑотків від загальної " "вартоÑті вÑÑ–Ñ… війÑьк, що Ñ” у вашій армії." msgid "" "An icicle withstanding the full heat of the noonday sun attracts your " "attention. Intrigued, you break it off, and find that it does not melt in " "your hand." msgstr "" msgid "Everhot Lava Rock" msgstr "" #, fuzzy msgid "" "The %{name} causes the hero's fire spells to do %{count} percent more damage " "to enemy troops." msgstr "%{name} підвищує Ñ‡Ð°Ñ Ð´Ñ–Ñ— ваших заклÑть на %{count} ходів." msgid "" "Your wanderings bring you into contact with a tribe of ape-like beings using " "a magical lava rock that never cools to light their fires. You take pity on " "them and teach them to make fire with sticks. Believing you to be a god, the " "apes give you their rock." msgstr "" msgid "Lightning Rod" msgstr "" msgid "" "The %{name} causes the hero's lightning spells to do %{count} percent more " "damage to enemy troops." msgstr "" msgid "" "While waiting out a storm, a lighting bolt strikes a nearby cottage's " "lightning rod, which melts and falls to the ground. The tip of the rod, " "however, survives intact and makes your hair stand on end when you touch it. " "Hmm..." msgstr "" msgid "Snake-Ring" msgstr "" msgid "The %{name} halves the casting cost of all of the hero's bless spells." msgstr "" msgid "" "You've found an oddly shaped ring on the finger of a long dead traveler. The " "ring looks like a snake biting its own tail." msgstr "" msgid "Ankh" msgstr "" msgid "" "The %{name} doubles the effectiveness of all of the hero's resurrect and " "animate spells." msgstr "" msgid "" "A fierce windstorm reveals the entrance to a buried tomb. Your investigation " "reveals that the tomb has already been looted, but the thieves overlooked an " "ankh on a silver chain in the dark." msgstr "" msgid "Book of Elements" msgstr "" msgid "" "The %{name} doubles the effectiveness of all of the hero's summoning spells." msgstr "" msgid "" "You come across a conjurer who begs to accompany you and your army awhile " "for safety. You agree, and he offers as payment a copy of the book of the " "elements." msgstr "" msgid "Elemental Ring" msgstr "" msgid "The %{name} halves the casting cost of all summoning spells." msgstr "" msgid "" "While pausing to rest, you notice a bobcat climbing a short tree to get at a " "crow's nest. On impulse, you climb the tree yourself and scare off the cat. " "When you look in the nest, you find a collection of shiny stones and a ring." msgstr "" msgid "Holy Pendant" msgstr "" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to curse spells." msgstr "%{name} надає вашим війÑькам імунітет до РозÑÑ–ÑŽÐ²Ð°Ð½Ð½Ñ ÐœÐ°Ð³Ñ–Ñ—." msgid "" "In your wanderings you come across a hermit living in a small, tidy hut. " "Impressed with your mission, he takes time out from his meditations to bless " "and give you a charm against curses." msgstr "" msgid "Pendant of Free Will" msgstr "" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to hypnotize spells." msgstr "%{name} надає вашим війÑькам імунітет до РозÑÑ–ÑŽÐ²Ð°Ð½Ð½Ñ ÐœÐ°Ð³Ñ–Ñ—." msgid "" "Responding to cries for help, you find river Sprites making a sport of " "dunking an old man. Feeling vengeful, you rescue the man and drag a Sprite " "onto dry land for awhile. The Sprite, uncomfortable in the air, gives you a " "magic pendant to let him go." msgstr "" msgid "Pendant of Life" msgstr "" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to death spells." msgstr "%{name} надає вашим війÑькам імунітет до РозÑÑ–ÑŽÐ²Ð°Ð½Ð½Ñ ÐœÐ°Ð³Ñ–Ñ—." msgid "" "A brief roadside encounter with a small caravan and a game of knucklebones " "wins a magic pendant. Its former owner says that it protects from " "Necromancers' death spells." msgstr "" msgid "Serenity Pendant" msgstr "" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to berserk spells." msgstr "%{name} надає вашим війÑькам імунітет до РозÑÑ–ÑŽÐ²Ð°Ð½Ð½Ñ ÐœÐ°Ð³Ñ–Ñ—." msgid "" "The sounds of combat draw you to the scene of a fight between an old " "Barbarian and an eight-headed Hydra. Your timely intervention swings the " "battle in favor of the man, and he rewards you with a pendant he used to use " "to calm his mind for battle." msgstr "" msgid "Seeing-eye Pendant" msgstr "" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to blindness spells." msgstr "%{name} надає вашим війÑькам імунітет до РозÑÑ–ÑŽÐ²Ð°Ð½Ð½Ñ ÐœÐ°Ð³Ñ–Ñ—." msgid "" "You come upon a very old woman, long blind from cataracts and dying alone. " "You tend to her final needs and promise a proper burial. Grateful, she gives " "you a magic pendant emblazoned with a stylized eye. It lets you see with " "your eyes closed." msgstr "" msgid "Kinetic Pendant" msgstr "" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to paralyze spells." msgstr "%{name} надає вашим війÑькам імунітет до РозÑÑ–ÑŽÐ²Ð°Ð½Ð½Ñ ÐœÐ°Ð³Ñ–Ñ—." msgid "" "You come across a golem wearing a glowing pendant and blocking your way. " "Acting on a hunch, you cut the pendant from its neck. Deprived of its power " "source, the golem breaks down, leaving you with the magical pendant." msgstr "" msgid "Pendant of Death" msgstr "" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to holy spells." msgstr "%{name} надає вашим війÑькам імунітет до РозÑÑ–ÑŽÐ²Ð°Ð½Ð½Ñ ÐœÐ°Ð³Ñ–Ñ—." msgid "" "A quick and deadly battle with a Necromancer wins you his magical pendant. " "Later, a Wizard tells you that the pendant protects undead under your " "control from holy word spells." msgstr "" msgid "Wand of Negation" msgstr "Жезл ЗапереченнÑ" #, fuzzy msgid "" "The %{name} makes all of the hero's troops immune to dispel magic spells." msgstr "%{name} надає вашим війÑькам імунітет до РозÑÑ–ÑŽÐ²Ð°Ð½Ð½Ñ ÐœÐ°Ð³Ñ–Ñ—." msgid "" "You meet an old Wizard friend of yours traveling in the opposite direction. " "He presents you with a gift: A wand that prevents the use of the dispel " "magic spell on your allies." msgstr "" "Ви зуÑтрічаєте Ñтарого друга-Чарівника, що подорожував у протилежному " "напрÑмку. Він дає вам подарунок: жезл, що не дозволÑÑ” знімати чари з ваших " "війÑьк." msgid "Golden Bow" msgstr "Золотий Лук" #, fuzzy msgid "" "The %{name} eliminates the %{count} percent penalty for the hero's troops " "shooting past obstacles (e.g. castle walls)." msgstr "" "%{name} прибирає %{count} відÑотків штрафу, що накладаєтьÑÑ Ð½Ð° ваші війÑька " "під Ñ‡Ð°Ñ Ð¿Ð¾Ñтрілів через перепони (подібні замковим Ñтінам)." msgid "" "A chance meeting with a famous Archer finds you in a game of knucklebones " "pitting his bow against your horse. You win." msgstr "" "ПіÑÐ»Ñ Ð²Ð¸Ð¿Ð°Ð´ÐºÐ¾Ð²Ð¾Ñ— зуÑтрічі з відомим Лучником ви й не помітили, Ñк вже граєте " "з ним у коÑті. Його лук проти вашого конÑ. Ви виграли." msgid "Telescope" msgstr "ТелеÑкоп" #, fuzzy msgid "" "The %{name} increases the amount of terrain the hero reveals when " "adventuring by %{count} extra square." msgstr "" "%{name} збільшує Ñ€Ð°Ð´Ñ–ÑƒÑ Ð¾Ð³Ð»Ñду Ð³ÐµÑ€Ð¾Ñ Ð½Ð° Ñтратегічній мапі на %{count} " "додаткову клітину." msgid "" "A merchant from far away lands trades you a new invention of his people for " "traveling supplies. It makes distant objects appear closer, and he calls " "it...\n" "\n" "a telescope." msgstr "" "Торговець з дальніх країв продає вам новий винахід його народу, кориÑний Ð´Ð»Ñ " "подорожей. З його допомогою віддалені об'єкти здаютьÑÑ Ð±Ð»Ð¸Ð¶Ñ‡Ð¸Ð¼Ð¸, Ñ– він " "називає цю річ...\n" "\n" "телеÑкопом." msgid "Statesman's Quill" msgstr "Перо Державника" #, fuzzy msgid "" "The %{name} reduces the cost of surrender to %{count} percent of the total " "cost of troops the hero has in their army." msgstr "" "%{name} зменшує вартіÑть капітулÑції на %{count} відÑотків від загальної " "вартоÑті вÑÑ–Ñ… війÑьк, що Ñ” у вашій армії." msgid "" "You pause to help a diplomat with a broken axle fix his problem. In " "gratitude, he gives you a writing quill with magical properties which he " "says will \"help people see things your way\"." msgstr "" "Ви затримуєтеÑÑŒ, щоб допомогти дипломату полагодити колеÑо його карети. Ðа " "подÑку він дає вам перо з магічними влаÑтивоÑÑ‚Ñми, Ñке, за його Ñловами: " "\"допоможе людÑм приÑтати на ваше Ñлово\"." msgid "Wizard's Hat" msgstr "Капелюх Чарівника" #, fuzzy msgid "" "The %{name} increases the duration of the hero's spells by %{count} turns." msgstr "%{name} підвищує Ñ‡Ð°Ñ Ð´Ñ–Ñ— ваших заклÑть на %{count} ходів." msgid "" "You see a Wizard fleeing from a Griffin and riding like the wind. The Wizard " "opens a portal and rides through, getting his hat knocked off by the edge of " "the gate. The Griffin follows; the gate closes. You pick the hat up, dust it " "off, and put it on." msgstr "" "Ви бачите, Ñк Чарівник верхи тікає від Грифона, обганÑючи вітер. Чарівник " "відкриває портал та проходить крізь нього, однак його капелюх чіплÑєтьÑÑ Ð¾Ð± " "край проходу та падає. Грифон пролітає Ñлідом, Ñ– брама зачинÑєтьÑÑ. Ви " "підіймаєте капелюха, обтрушуєте його, та надÑгаєте." msgid "Power Ring" msgstr "ПерÑтень МогутноÑті" #, fuzzy msgid "The %{name} returns %{count} extra spell points per day to the hero." msgstr "%{name} Ñ‰Ð¾Ð´Ð½Ñ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÑŽÑ” очки мани у кількоÑті: %{count} од." msgid "" "You find a small tree that closely resembles the great Warlock Carnauth with " "a ring around one of its twigs. Scraps of clothing and rotting leather lead " "you to suspect that it IS Carnauth, transformed. Since you can't help him, " "you take the magic ring." msgstr "" "Ви знаходите невеличке дерево з перÑтнем на гілці. РоÑлина підозріло нагадує " "великого Чаклуна Карнаута. Залишки одÑгу та зогнилої шкіри підтверджують " "ваші підозри, що це Ñ– Ñ” обернений на дерево Карнаут. Ви вже не зможете йому " "допомогти, то ж забираєте його магічного перÑтнÑ." msgid "Ammo Cart" msgstr "Візок з БоєприпаÑами" #, fuzzy msgid "" "The %{name} provides endless ammunition for all of the hero's troops that " "shoot." msgstr "%{name} забезпечує ваші Ñтрілецькі війÑька неÑкінченними боєприпаÑами." msgid "" "An ammunition cart in the middle of an old battlefield catches your eye. " "Inspection shows it to be in good working order, so you take it along." msgstr "" "Ваш поглÑд затримуєтьÑÑ Ð½Ð° візку з боєприпаÑами, що Ñтоїть поÑеред Ñтарого " "бойовища. Перевірка вÑтановлює, що він придатний Ð´Ð»Ñ Ð²Ð¸ÐºÐ¾Ñ€Ð¸ÑÑ‚Ð°Ð½Ð½Ñ Ð·Ð° " "призначеннÑм, то ж ви забираєте його з Ñобою." msgid "Tax Lien" msgstr "Податкова ЗаÑтава" #, fuzzy msgid "The %{name} costs the hero %{count} gold pieces per day." msgstr "%{name} коштує вам %{count} золотих щоднÑ." msgid "" "Your big spending habits have earned you a massive tax bill that you can't " "hope to pay. The tax man takes pity and agrees to only take 250 gold a day " "from your account for life. Check here if you want one dollar to go to the " "presidential campaign election fund." msgstr "" "Ваша ÑхильніÑть до надмірних витрат призводить до Ð²Ñ€ÑƒÑ‡ÐµÐ½Ð½Ñ Ð²Ð°Ð¼ великого " "податкового рахунку, Ñкий ви ніколи не зможете Ñплатити. Податківець " "зглÑнувÑÑ, та погодивÑÑ Ð´Ð¾Ð·Ð²Ð¾Ð»Ð¸Ñ‚Ð¸ вам тихеÑенько втекти в ліÑ, а потім " "ÑÑ‚Ñгувати з Ð²Ð°Ñ Ð»Ð¸Ñˆ по 250 золотих щоднÑ. ПідпишітьÑÑ Ñ‚ÑƒÑ‚, щоб передати " "чаÑтину цих грошей на Ð¿Ñ€Ð¸Ð´Ð±Ð°Ð½Ð½Ñ Ð³Ñ€ÐµÑ‡ÐºÐ¸ під наÑтупні вибори." msgid "Hideous Mask" msgstr "Мерзенна МаÑка" #, fuzzy msgid "The %{name} prevents all 'wandering' armies from joining the hero." msgstr "" "%{name} не дозволÑÑ” вÑім \"нейтральним\" арміÑм приєднуватиÑÑŒ до вашого " "героÑ." msgid "" "Your looting of the grave of Sinfilas Gardolad, the famous shapeshifting " "Warlock, unearths his fabled mask. Trembling, you put it on and it twists " "your visage into an awful grimace! Oh no! It's actually the hideous mask of " "Gromluck Greene, and you are stuck with it." msgstr "" "Ви грабуєте могилу СінфілаÑа Гардолада, знаменитого Чаклуна-перевертнÑ, та " "діÑтаєте з-під землі його легендарну маÑку. Ледве Ñтримуючи тремтіннÑ, ви " "вдÑгаєте Ñ—Ñ—, Ñ– вона Ñпотворює ваше обличчÑ, перетворивши його на жахливу " "гримаÑу! О ні! ÐаÑправді це жахлива маÑка Громлука Гріна, Ñ– ви заÑтрÑгли в " "ній." msgid "Endless Pouch of Sulfur" msgstr "ÐеÑкінченний Мішечок з Сіркою" msgid "The %{name} provides %{count} unit of sulfur per day." msgstr "%{name} збільшує видобуток Ñірки на %{count} в день." msgid "" "You visit an alchemist who, upon seeing your army, is swayed by the " "righteousness of your cause. The newly loyal subject gives you his endless " "pouch of sulfur to help with the war effort." msgstr "" "Ви відвідуєте алхіміка, Ñкий, побачивши ваше війÑько, захопивÑÑ Ð¿Ñ€Ð°Ð²ÐµÐ´Ð½Ñ–Ñтю " "вашої Ñправи. Ваш новий підданий віддає вам неÑкінченний мішечок з Ñіркою, " "щоб допомогти у війні." msgid "Endless Vial of Mercury" msgstr "ÐеÑкінченний Флакон з Ртуттю" msgid "The %{name} provides %{count} unit of mercury per day." msgstr "%{name} збільшує видобуток ртуті на %{count} в день." msgid "" "A brief stop at a hastily abandoned Wizard's tower turns up a magical vial " "of mercury that always has a little left on the bottom. Recognizing a " "treasure when you see one, you cap it and slip it in your pocket." msgstr "" "Коротка зупинка Ð±Ñ–Ð»Ñ Ð¿Ð¾Ñпіхом залишеної вежі Чарівника призводить до " "знахідки - флакон, в Ñкому завжди залишаєтьÑÑ Ñ‚Ñ€Ð¾Ñ…Ð¸ ртуті на дні. Зрозумівши " "що те, що ви найшли - це Ñкарб, ви закорковуєте його, та кладете до кишені." msgid "Endless Pouch of Gems" msgstr "ÐеÑкінченний Мішечок Самоцвітів" msgid "The %{name} provides %{count} unit of gems per day." msgstr "%{name} збільшує видобуток Ñамоцвітів на %{count} в день." msgid "" "A short rainstorm brings forth a rainbow...and you can see the end of it. " "Riding quickly, you seize the pot of gold you find there. The leprechaun who " "owns it, unable to stop you from taking it, offers an endless pouch of gems " "for the return of his gold. You accept." msgstr "" "ПіÑÐ»Ñ ÐºÐ¾Ñ€Ð¾Ñ‚ÐºÐ¾Ñ— зливи з'ÑвлÑєтьÑÑ Ñ€Ð°Ð¹Ð´ÑƒÐ³Ð°... Ñ– ви бачите, звідки вона " "починаєтьÑÑ. Швидко їдучи верхи, ви знаходите глечик з золотом у тому міÑці. " "Лепрекон, Ñкому належить горщик, не може завадити вам забрати його, то ж " "пропонує натоміÑть неÑкінченний мішечок з Ñамоцвітами. Ви погоджуєтеÑÑŒ." msgid "Endless Cord of Wood" msgstr "ÐеÑкінченна Ð’'Ñзанка Дров" msgid "The %{name} provides %{count} unit of wood per day." msgstr "%{name} збільшує видобуток дерева на %{count} в день." msgid "" "Pausing to rest and light a cook fire, you pull wood out of a nearby pile of " "dead wood. As you keep pulling wood from the pile, you notice that it " "doesn't shrink. You realize to your delight that the wood is enchanted, so " "you take it along." msgstr "" "ЗупинившиÑÑŒ відпочити та запаливши багаттÑ, ви витÑгаєте шматок дерева з " "найближчої купи деревини. Підкидаючи дрова до багаттÑ, ви помічаєте, що купа " "деревини не зменшуєтьÑÑ. Ви з радіÑтю уÑвідомлюєте, що Ñ†Ñ Ð´ÐµÑ€ÐµÐ²Ð¸Ð½Ð° " "зачарована, то ж збираєте Ñ—Ñ— у в'Ñзанку, та забираєте з Ñобою." msgid "Endless Cart of Ore" msgstr "ÐеÑкінченний Візок Руди" msgid "The %{name} provides %{count} unit of ore per day." msgstr "%{name} збільшує видобуток руди на %{count} в день." msgid "" "You've found a Goblin weapon smithy making weapons for use against humans. " "With a tremendous yell you and your army descend upon their camp and drive " "them away. A search finds a magic ore cart that never runs out of iron." msgstr "" "Ви знаходите ГоблінÑьку зброÑрню, де виготовлÑли зброю Ð´Ð»Ñ Ð²Ð¸ÐºÐ¾Ñ€Ð¸ÑÑ‚Ð°Ð½Ð½Ñ " "проти людей. З гучним криком ви з вашим війÑьком налітаєте на Ñ—Ñ… табір, " "виганÑючи Ñ—Ñ… геть. Обшукавши табір, ви знаходите чарівний візок з рудою, в " "Ñкому ніколи не ÑкінчаєтьÑÑ Ð·Ð°Ð»Ñ–Ð·Ð¾." msgid "Endless Pouch of Crystal" msgstr "ÐеÑкінченний Мішечок КриÑталів" msgid "The %{name} provides %{count} unit of crystal per day." msgstr "%{name} збільшує видобуток криÑталів на %{count} в день." msgid "" "Taking shelter from a storm in a small cave, you notice a small patch of " "crystal in one corner. Curious, you break a piece off and notice that the " "original crystal grows the lost piece back. You decide to stuff the entire " "patch into a pouch and take it with you." msgstr "" "ХоваючиÑÑŒ від буревію у маленькій печері, ви помічаєте в одному кутку " "невеличкий шматок криÑталу. ЗацікавившиÑÑŒ, ви відламуєте шматочок, та " "помічаєте, що на його міÑці одразу ж з'ÑвлÑєтьÑÑ Ð½Ð¾Ð²Ð¸Ð¹ криÑтал. Ви вирішуєте " "поклаÑти веÑÑŒ шматок у мішечок, та забрати його з Ñобою." msgid "Spiked Helm" msgstr "ШпичаÑтий Шолом" msgid "" "Your army is ambushed by a small tribe of wild (and none too bright) Orcs. " "You fend them off easily and the survivors flee in all directions. One of " "the Orcs was wearing a polished spiked helm. Figuring it will make a good " "souvenir, you take it." msgstr "" "Ваше війÑько втрапило у заÑідку невеличкого племені диких (та не надто " "розумних) Орків. Ви легко відбиваєте напад, вцілілі нападники розбігаютьÑÑ " "хто куди. Один з Орків ноÑив на Ñобі полірований шпичаÑтий шолом. Зробивши " "виÑновок, що він Ñтане чудовим Ñувеніром, ви забираєте його." msgid "Spiked Shield" msgstr "ШпичаÑтий Щит" msgid "" "You come upon a bridge spanning a dry gully. Before you can cross, a Troll " "steps out from under the bridge and demands payment before it will permit " "you to pass. You refuse, and the Troll charges, forcing you to slay it. You " "take its spiked shield as a trophy." msgstr "" "Ви приходите до моÑту, перекинутого через Ñухий ÑÑ€. Щойно ви збираєтеÑÑŒ " "перейти, з-під моÑту виходить Троль, та вимагає у Ð²Ð°Ñ Ð¿Ð»Ð°Ñ‚Ð½Ñ– за прохід. Ви " "відмовлÑєтеÑÑŒ, Ñ– Троль нападає на ваÑ. Ви не маєте іншого виходу ,окрім Ñк " "вбити його. Його шпичаÑтий щит ви забираєте Ñобі за трофей." msgid "White Pearl" msgstr "Біла Перлина" msgid "" "A walk across a dry saltwater lake bed yields an unlikely prize: A white " "pearl amidst shattered shells and debris." msgstr "" "ПрогулÑнка по Ñухому Ñоленому озеру приноÑить вам неочікуваний подарунок: " "Білу перлину Ñеред розбитих мушель та уламків." msgid "Black Pearl" msgstr "Чорна Перлина" msgid "" "Rumors of a Griffin of unusual size preying upon the countryside lead you to " "its cave lair. A quick, brutal fight dispatches the beast, and a search of " "its foul nest turns up a huge black pearl." msgstr "" "Чутки про незвично великого Грифона, що полює на околицÑÑ…, приводÑть Ð²Ð°Ñ Ð´Ð¾ " "його печерного лігва. ПротÑгом швидкого, але жорÑтокого бою, ви вбиваєте " "монÑтра, а обшук його брудного гнізда приноÑить вам велику чорну перлину." msgid "Magic Book" msgstr "Книга Магії" #, fuzzy msgid "The %{name} enables the hero to cast spells." msgstr "%{name} дозволÑÑ” вам викориÑтовувати заклÑттÑ." msgid "Dummy 1" msgstr "Заповнювач 1" msgid "The reserved artifact." msgstr "Зарезервований артефакт." msgid "Dummy 2" msgstr "Заповнювач 2" msgid "Dummy 3" msgstr "Заповнювач 3" msgid "Dummy 4" msgstr "Заповнювач 4" msgid "Spell Scroll" msgstr "Чарівний Сувій" #, fuzzy msgid "" "This %{name} gives the hero the ability to cast the %{spell} spell if the " "hero has a Magic Book." msgstr "" "Цей %{name} дозволÑÑ” вашому герою викориÑтовувати заклÑÑ‚Ñ‚Ñ %{spell}, Ñкщо у " "вашого Ð³ÐµÑ€Ð¾Ñ Ñ” Книга Магії." msgid "" "You find an elaborate container which houses an old vellum scroll. The runes " "on the container are very old, and the artistry with which it was put " "together is stunning. As you pull the scroll out, you feel imbued with " "magical power." msgstr "" "Ви знаходите вишуканий контейнер, що міÑтить Ñтаровинний пергаментний Ñувій. " "Руни на контейнері дуже Ñтарі, а майÑтерніÑть, з Ñкою вони були нанеÑені - " "вражаюча. Щойно ви діÑтаєте Ñувій з контейнера, ви відчуваєте, Ñк Ð²Ð°Ñ " "Ñповнює магічна Ñила." msgid "Arm of the Martyr" msgstr "Рука Мученика" #, fuzzy msgid "" "The %{name} increases the hero's spell power by %{count} but adds the undead " "morale penalty." msgstr "" "%{name} збільшує вашу Ñилу магії на %{count}, однак додає штраф до моралі за " "приÑутніÑть невмерлих у війÑьку." msgid "" "One of the less intelligent members of your party picks up an arm off of the " "ground. Despite its missing a body, it is still moving. Your troops find the " "dismembered arm repulsive, but you cannot bring yourself to drop it: it " "seems to hold some sort of magical power that influences your decision " "making." msgstr "" "Один не надто розумний приÑлужник підіймає з землі руку. Хоча й відрубана " "від тіла, вона доÑÑ– рухаєтьÑÑ. Ваші воїни відчувають відразу до відрізаної " "кінцівки, однак ви не можете змуÑити Ñебе наказати викинути Ñ—Ñ—. Схоже, вона " "має ÑкуÑÑŒ магічну Ñилу, що впливає на ваші рішеннÑ." msgid "Breastplate of Anduran" msgstr "Ðагрудник Ðндурана" #, fuzzy msgid "The %{name} increases the hero's defense by %{count}." msgstr "%{name} збільшує ваш ЗахиÑÑ‚ на %{count}." msgid "" "You come upon a sign. It reads: \"Here lies the body of Anduran. Bow and " "swear fealty, and you shall be rewarded.\" You decide to do as it says. As " "you stand up, you feel a coldness against your skin. Looking down, you find " "that you are suddenly wearing a gleaming, ornate breastplate." msgstr "" "Ви наштовхуєтеÑÑŒ на знак. Ðа ньому напиÑано: \"Тут лежить тіло Ðндурана. " "ВклонітьÑÑ Ñ‚Ð° приÑÑгніть на вірніÑть, Ñ– будете винагороджені\". Ви вирішуєте " "зробити так, Ñк Ñказано. Щойно ви Ñтаєте на ноги, ви відчуваєте холод на " "Ñвоїй шкірі. ПодивившиÑÑŒ донизу, ви помічаєте, що на Ð²Ð°Ñ Ð½Ð°Ð´Ñ–Ñ‚Ð¸Ð¹ ÑÑючий, " "багато прикрашений нагрудник." msgid "Broach of Shielding" msgstr "ЗахиÑна Брошка" msgid "" "The %{name} provides %{count} percent protection from Armageddon and " "Elemental Storm, but decreases spell power by 2." msgstr "" "%{name} надає %{count}-відÑотковий захиÑÑ‚ від заклÑть Ðрмагеддон та " "Стихійний Шторм, однак зменшує Силу Магії на 2." msgid "" "A kindly Sorceress thinks that your army's defenses could use a magical " "boost. She offers to enchant the Broach that you wear on your cloak, and you " "accept." msgstr "" "Добра Чаклунка вважає, що вашому війÑьку буде не зайвим додатковий магічний " "захиÑÑ‚. То ж вона пропонує вам зачаровану Брошку, Ñку можна ноÑити на Ñвоїй " "мантії. Ви приймаєте пропозицію." msgid "Battle Garb of Anduran" msgstr "Бойове Ð’Ð±Ñ€Ð°Ð½Ð½Ñ Ðндурана" #, fuzzy msgid "" "The %{name} combines the powers of the three Anduran artifacts. It provides " "maximum luck and morale for the hero's troops and gives the hero the Town " "Portal spell." msgstr "" "%{name} поєднує у Ñобі Ñилу трьох артефактів Ðндурана. Воно надає вашим " "війÑькам макÑимальну удачу та мораль, а також надає вам заклÑÑ‚Ñ‚Ñ ÐœÑ–Ñького " "Порталу." msgid "" "Out of pity for a poor peasant, you purchase a chest of old junk they are " "hawking for too much gold. Later, as you search through it, you find it " "contains the 3 pieces of the legendary battle garb of Anduran!" msgstr "" "З жалю до бідного ÑелÑнина, ви придбали в нього Ñкриню Ñтарого лантуху, за " "Ñку він проÑив завелику кількіÑть золота. Згодом, обшукавши Ñ—Ñ—, ви знайшли " "три чаÑтини легендарного бойового ÑпорÑÐ´Ð¶ÐµÐ½Ð½Ñ Ðндурана!" msgid "Crystal Ball" msgstr "Кришталева КулÑ" #, fuzzy msgid "" "The %{name} lets the hero get more specific information about monsters, " "enemy heroes, and castles nearby the hero." msgstr "" "%{name} надає вам більш детальну інформацію про монÑтрів, ворожих героїв та " "замки, що знаходÑтьÑÑ Ð½ÐµÐ¿Ð¾Ð´Ð°Ð»Ñ–Ðº від влаÑника артефакту." msgid "" "You come upon a caravan of gypsies who are feasting and fortifying their " "bodies with mead. They call you forward and say \"If you prove that you can " "dance the Rama-Buta, we will reward you.\" You don't know it, but try " "anyway. They laugh hysterically, but admire your bravery, giving you a " "Crystal Ball." msgstr "" "Ви натикаєтеÑÑŒ на табір циган, що ÑвÑткують та Ñмакують медовуху. Вони " "заохочують Ð²Ð°Ñ Ð¿Ñ–Ð´Ñ–Ð¹Ñ‚Ð¸, та кажуть: \"Якщо ви доведете, що можете Ñтанцювати " "\"Рама-Буту\", ми нагородимо тебе\". Ви понÑÑ‚Ñ‚Ñ Ð½Ðµ маєте, що це за танок, " "однак вÑе одно намагаєтеÑÑŒ щоÑÑŒ зобразити. ГлÑдачі падають зі Ñміху від " "побаченого, однак визнають вашу хоробріÑть, та дарують вам Кришталеву Кулю." msgid "Heart of Fire" msgstr "Полум'Ñне Серце" msgid "" "The %{name} provides %{count} percent protection from fire, but doubles the " "damage taken from cold." msgstr "" "%{name} наділÑÑ” ваші війÑька %{count}-відÑотковим захиÑтом від вогню, однак " "збільшує вдвічі пошкодженнÑ, завдані холодом." msgid "" "You enter a recently burned glade and come upon a Fire Elemental sitting " "atop a rock. It looks up, its flaming face contorted in a look of severe " "pain. It then tosses a glowing object at you. You put up your hands to block " "it, but it passes right through them and sears itself into your chest." msgstr "" "Ви входите до нещодавно випаленої галÑвини, та бачите ВогнÑного ЕлементалÑ, " "що Ñидить на Ñкелі. Він здіймає поглÑд, його полум'Ñне Ð¾Ð±Ð»Ð¸Ñ‡Ñ‡Ñ Ñпотворене " "виразом лютого болю. Тоді він кидає ÑÑючий предмет у ваÑ. Ви здіймаєте руки " "щоб захиÑтитиÑÑ, однак та річ проходить крізь них, та врізаєтьÑÑ Ñƒ ваші " "груди." msgid "Heart of Ice" msgstr "Крижане Серце" msgid "" "The %{name} provides %{count} percent protection from cold, but doubles the " "damage taken from fire." msgstr "" "%{name} наділÑÑ” ваші війÑька %{count}-відÑотковим захиÑтом від холоду, однак " "збільшує вдвічі пошкодженнÑ, завдані вогнем." msgid "" "Suddenly, a biting coldness engulfs your body. You seize up, falling from " "your horse. The pain subsides, but you still feel as if your chest is " "frozen. As you pick yourself up off of the ground, you hear hearty laughter. " "You turn around just in time to see a Frost Giant run off into the woods and " "disappear." msgstr "" "Раптово ваше тіло охоплює пронизлива холоднеча. Ви ÑхоплюєтеÑÑŒ, падаючи з " "конÑ. Біль вщухає, та ви доÑÑ– почуваєтеÑÑŒ так, наче ваші груди заморожені. " "Коли ви вÑтаєте на ноги, ви чуєте оглушливий регіт. Ви обертаєтеÑÑŒ Ñкраз " "вчаÑно, щоб помітити, Ñк Крижаний Гігант втікає до ліÑу та зникає." msgid "Helmet of Anduran" msgstr "Шолом Ðндурана" msgid "" "You spy a gleaming object poking up out of the ground. You send a member of " "your party over to investigate. He comes back with a golden helmet in his " "hands. You realize that it must be the helmet of the legendary Anduran, the " "only man who was known to wear solid gold armor." msgstr "" "Ви помітили блиÑкучий предмет, що Ñтирчав з землі. Ви поÑлали одного зі " "Ñвоїх підручних обÑтежити його. Він повернувÑÑ Ñ–Ð· золотим шоломом у руках. " "Ви зрозуміли, що це, мабуть, Ñ– Ñ” той Ñамий шолом легендарного Ðндурана, що " "був відомий Ñвоїм обладунком з чиÑтого золота." msgid "Holy Hammer" msgstr "СвÑтий Молот" msgid "" "You come upon a battle where a Paladin has been mortally wounded by a group " "of Zombies. He asks you to take his hammer and finish what he started. As " "you pick it up, it begins to hum, and then everything becomes a blur. The " "Zombies lie dead, the hammer dripping with blood. You strap it to your belt." msgstr "" "Ðа Ñвоєму шлÑху ви натраплÑєте на бойовище, на Ñкому група Зомбі Ñмертельно " "поранили Паладина. Він попроÑив Ð²Ð°Ñ Ð·Ð°Ð±Ñ€Ð°Ñ‚Ð¸ його молот та завершити те, що " "він почав. Щойно ви взÑли його до рук, він почав дзижчати, а ваш поглÑд " "затуманивÑÑ. Зомбі знищені, а з молота Ñкрапує Ñ—Ñ… гнила кров. Ви чіплÑєте " "його до паÑка." msgid "Legendary Scepter" msgstr "Легендарний Скіпетр" msgid "The %{name} adds %{count} points to all attributes." msgstr "%{name} додає %{count} очок до вÑÑ–Ñ… оÑновних параметрів." msgid "" "Upon cresting a small hill, you come upon a ridiculous looking sight. A " "Sprite is attempting to carry a Scepter that is almost as big as it is. " "Trying not to laugh, you ask, \"Need help?\" The Sprite glares at you and " "answers: \"You think this is funny? Fine. You can carry it. I much prefer " "flying anyway.\"" msgstr "" "Коли ви перетинали вершину невеличкого пагорба, вам відкрилоÑÑ Ð²ÐºÑ€Ð°Ð¹ Ñмішне " "видовище. Ð¤ÐµÑ Ð½Ð°Ð¼Ð°Ð³Ð°Ð»Ð°ÑÑ Ð¿Ñ–Ð´Ð½Ñти Скіпетр, що був завбільшки майже Ñк вона. " "ÐамагаючиÑÑŒ не заÑміÑтиÑÑ, ви питаєте: \"Потрібна допомога?\" Ð¤ÐµÑ " "витріщаєтьÑÑ Ð½Ð° Ð²Ð°Ñ Ñ‚Ð° відповідає: \"Гадаєш це Ñмішно? Гаразд. Можеш " "забирати Ñобі. Мені однаково значно більше до вподоби літати\"." msgid "" "An old seaman tells you a tale of an enchanted masthead that he used in his " "youth to rally his crew during times of trouble. He then hands you a faded " "map that shows where he hid it. After much exploring, you find it stashed " "underneath a nearby dock." msgstr "" "Старий морÑк розповідає вам Ñ–Ñторію про зачарований вершечок щогли, Ñкий він " "викориÑтовував щоб згуртувати команду у Ñ‡Ð°Ñ Ñкрути. Тоді він передає вам " "вицвілу мапу, де вказане Ñ—Ñ— міÑцезнаходженнÑ. Витративши чимало чаÑу на " "доÑлідженнÑ, ви знайшли Ñ—Ñ— у Ñховку під ÑуÑіднім доком." msgid "Masthead" msgstr "Вершечок Щогли" #, fuzzy msgid "" "The %{name} boosts the hero's troops' luck and morale by %{count} each in " "sea combat." msgstr "%{name} піднімає ваші удачу та мораль на %{count} у морÑьких битвах." msgid "Sphere of Negation" msgstr "Сфера ЗапереченнÑ" msgid "The %{name} disables all spell casting, for both sides, in combat." msgstr "%{name} заборонÑÑ” будь-Ñке заÑтоÑÑƒÐ²Ð°Ð½Ð½Ñ Ð¼Ð°Ð³Ñ–Ñ— у бою." msgid "" "You stop to help a Peasant catch a runaway mare. To show his gratitude, he " "hands you a tiny sphere. As soon as you grasp it, you feel the magical " "energy drain from your limbs..." msgstr "" "Ви ÑпинилиÑÑ, щоб допомогти СелÑнину зловити кобилу-втікачку. Ðа знак подÑки " "він проÑÑ‚Ñгає вам невеличку Ñферу. Щойно ви взÑли Ñ—Ñ— до рук, ви відчули, Ñк " "магічна ÐµÐ½ÐµÑ€Ð³Ñ–Ñ Ð²Ð¸Ñ‚Ñ–ÐºÐ°Ñ” з ваших кінцівок..." msgid "Staff of Wizardry" msgstr "ÐŸÐ°Ñ‚ÐµÑ€Ð¸Ñ†Ñ Ð§Ð°ÐºÐ»ÑƒÐ½Ñтва" #, fuzzy msgid "The %{name} boosts the hero's spell power by %{count}." msgstr "%{name} поÑилює вашу Силу Магії на %{count}." msgid "" "While out scaring up game, your troops find a mysterious staff levitating " "about three feet off of the ground. They hand it to you, and you notice an " "inscription. It reads: \"Brains best brawn and magic beats might. Heed my " "words, and you'll win every fight.\"" msgstr "" "ЗаганÑючи дичину, ваші війÑька знайшли таємничу патерицю, що левітувала " "майже у метрі над землею. Вони передали Ñ—Ñ— вам, Ñ– ви помітили примітку. Ð’ " "ній Ñказано: \"Мозок Ñильніший за м'Ñзи, а Ð¼Ð°Ð³Ñ–Ñ Ð¿ÐµÑ€ÐµÐ¼Ð°Ð³Ð°Ñ” меча. ДоÑлухайÑÑ " "до цих Ñлів, Ñ– перемагатимеш у кожному бою\"." msgid "Sword Breaker" msgstr "Мечолам" #, fuzzy msgid "The %{name} increases the hero's defense by %{count} and attack by 1." msgstr "%{name} збільшує ваш ЗахиÑÑ‚ на %{count} та Ðтаку на 1." msgid "" "A former Captain of the Guard admires your quest and gives you the enchanted " "Sword Breaker that he relied on during his tour of duty." msgstr "" "Колишній Капітан Варти захоплюєтьÑÑ Ð²Ð°ÑˆÐ¸Ð¼Ð¸ діÑннÑми, та дає вам зачарований " "Мечолам, на Ñкий він покладавÑÑ Ð½Ð° Ñлужбі." msgid "Sword of Anduran" msgstr "Меч Ðндурана" msgid "" "A Troll stops you and says: \"Pay me 5,000 gold, or the Sword of Anduran " "will slay you where you stand.\" You refuse. The troll grabs the sword " "hanging from its belt, screams in pain, and runs away. Picking up the fabled " "sword, you give thanks that half-witted Trolls tend to grab the wrong end of " "sharp objects." msgstr "" "Тролль ÑпинÑÑ” ваÑ, та каже: \"Плати мені 5,000 золотих, або Меч Ðндурана " "вб'Ñ” тебе на міÑці\". Ви відмовлÑєтеÑÑŒ. Троль хапає меча, що виÑить в нього " "на поÑÑÑ–, Ñкрикує від болю та тікає геть. Піднімаючи уÑлавленого меча, ви " "подумки дÑкуєте, що недоумкуваті тролі мають звичку хапати гоÑтрі предмети " "не за той кінець." msgid "Spade of Necromancy" msgstr "Лопата Ðекромантії" #, fuzzy msgid "The %{name} gives the hero increased necromancy skill." msgstr "%{name} збільшує навичку Ðекромантії героÑ-влаÑника." msgid "" "A dirty shovel has been thrust into a dirt mound nearby. Upon investigation, " "you discover it to be the enchanted shovel of the Gravediggers, long thought " "lost by mortals." msgstr "" "Брудна лопата Ñтирчить з кургану неподалік. ОбÑтеживши Ñ—Ñ—, ви зрозуміли, що " "це зачарована лопата Гробарів, Ñку Ñмертні давно вважали втраченою." msgid "spellBonus|selected by user" msgstr "" msgid "Wood" msgstr "Дерево" msgid "Mercury" msgstr "Ртуть" msgid "Ore" msgstr "Руда" msgid "Sulfur" msgstr "Сірка" msgid "Crystal" msgstr "КриÑтали" msgid "Gems" msgstr "Самоцвіти" msgid "Gold" msgstr "Золото" msgid "" "There are seven resources in Heroes 2, used to build and improves castles, " "purchase troops and recruit heroes. Gold is the most common, required for " "virtually everything. Wood and ore are used for most buildings. Gems, " "Mercury, Sulfur and Crystal are rare magical resources used for the most " "powerful creatures and buildings." msgstr "" "У ГероÑÑ… 2 Ñ–Ñнує Ñім видів реÑурÑів, що Ñ—Ñ… можна викориÑтати Ð´Ð»Ñ Ð·Ð²ÐµÐ´ÐµÐ½Ð½Ñ Ñ‚Ð° " "Ð¿Ð¾ÐºÑ€Ð°Ñ‰ÐµÐ½Ð½Ñ Ð±ÑƒÐ´Ñ–Ð²ÐµÐ»ÑŒ у замках, Ð¿Ñ€Ð¸Ð´Ð±Ð°Ð½Ð½Ñ Ð²Ñ–Ð¹Ñьк та найму героїв. Золото - " "найбільш поширений реÑурÑ, потрібний практично Ð´Ð»Ñ Ð²Ñього. Дерево та Руда " "викориÑтовуютьÑÑ Ð´Ð»Ñ Ð·Ð²ÐµÐ´ÐµÐ½Ð½Ñ Ð±Ñ–Ð»ÑŒÑˆÐ¾Ñті будівель. Самоцвіти, Ртуть, Сірка та " "КриÑтали - це рідкіÑні магічні реÑурÑи, необхідні Ð´Ð»Ñ Ð½Ð°Ð¹Ð±Ñ–Ð»ÑŒÑˆ могутніх " "Ñ–Ñтот та будівель." msgid "" "Causes a giant fireball to strike the selected area, damaging all nearby " "creatures." msgstr "" "Велика вогнÑна ÐºÑƒÐ»Ñ Ð²Ñ€Ð°Ð¶Ð°Ñ” обрану облаÑть, завдаючи шкоди вÑім ÑтворіннÑм у " "полі дії." msgid "Fireball" msgstr "ВогнÑна КулÑ" msgid "Fireblast" msgstr "ВогнÑний Вибух" msgid "" "An improved version of fireball, fireblast affects two hexes around the " "center point of the spell, rather than one." msgstr "" "Покращена верÑÑ–Ñ Ð’Ð¾Ð³Ð½Ñної Кулі, що завдає шкоду враженим загонам у радіуÑÑ– " "двох клітин довкола епіцентру, заміÑть однієї." msgid "Causes a bolt of electrical energy to strike the selected creature." msgstr "ВдарÑÑ” обраний ворожий загін розрÑдом електричної енергії." msgid "Lightning Bolt" msgstr "БлиÑкавка" msgid "Chain Lightning" msgstr "Ланцюгова БлиÑкавка" msgid "" "Causes a bolt of electrical energy to strike a selected creature, then " "strike the nearest creature with half damage, then strike the NEXT nearest " "creature with half again damage, and so on, until it becomes too weak to be " "harmful. Warning: This spell can hit your own creatures!" msgstr "" "ВдарÑÑ” розрÑдом електричної енергії обраний ворожий загін, піÑÐ»Ñ Ñ‡Ð¾Ð³Ð¾ удар " "перекидаєтьÑÑ Ð½Ð° наÑтупну найближчу Ñ–Ñтоту, завдаючи половину шкоди Ñ– так " "далі, загалом пошкоджуючи 4 цілі, аж доки блиÑкавка не Ñтане заÑлабкою, щоб " "комуÑÑŒ зашкодити. Увага: Це заклÑÑ‚Ñ‚Ñ Ð·Ð´Ð°Ñ‚Ð½Ðµ зачепити ваші влаÑні війÑька!" msgid "Teleport" msgstr "Телепорт" msgid "" "Teleports the creature you select to any open position on the battlefield." msgstr "Телепортує обраний дружній загін в будь-Ñку вільну точку Ð¿Ð¾Ð»Ñ Ð±Ð¾ÑŽ." msgid "Cure" msgstr "ЗціленнÑ" msgid "" "Removes all negative spells cast upon one of your units, and restores up to " "%{count} HP per level of spell power." msgstr "" "Знімає вÑÑ– негативні заклÑÑ‚Ñ‚Ñ Ð· обраного дружнього загону, відновлюючи по " "%{count} ОЗ за кожен рівень Сили Магії." msgid "Mass Cure" msgstr "МаÑове ЗціленнÑ" msgid "" "Removes all negative spells cast upon your forces, and restores up to " "%{count} HP per level of spell power, per creature." msgstr "" "Знімає вÑÑ– негативні заклÑÑ‚Ñ‚Ñ Ð· ваших війÑьк, відновлюючи у кожному загоні " "по %{count} ОЗ за кожен рівень Сили Магії." msgid "Resurrect" msgstr "ВоÑкреÑіннÑ" msgid "Resurrects creatures from a damaged or dead unit until end of combat." msgstr "ВоÑкрешає Ñ–Ñтот у пошкодженому або знищеному загоні до ÐºÑ–Ð½Ñ†Ñ Ð±Ð¸Ñ‚Ð²Ð¸." msgid "Resurrect True" msgstr "ІÑтинне ВоÑкреÑіннÑ" msgid "Resurrects creatures from a damaged or dead unit permanently." msgstr "Ðазавжди воÑкрешає Ñ–Ñтот у пошкодженому або знищеному загоні." msgid "Haste" msgstr "ПоÑпіх" msgid "Increases the speed of any creature by %{count}." msgstr "Збільшує швидкіÑть обраного дружнього загону на %{count}." msgid "Increases the speed of all of your creatures by %{count}." msgstr "Збільшує швидкіÑть вÑÑ–Ñ… ваших загонів на %{count}." msgid "Mass Haste" msgstr "МаÑовий ПоÑпіх" msgid "Slows target to half movement rate." msgstr "Сповільнює обраний ворожий загін вдвічі." msgid "spell|Slow" msgstr "УповільненнÑ" msgid "Mass Slow" msgstr "МаÑове УповільненнÑ" msgid "Slows all enemies to half movement rate." msgstr "Зменшує швидкіÑть переÑÑƒÐ²Ð°Ð½Ð½Ñ Ð²ÑÑ–Ñ… ворожих загонів вдвічі." msgid "Clouds the affected creatures' eyes, preventing them from moving." msgstr "Затьмарює очі вражених Ñ–Ñтот, не дозволÑючи їм рухатиÑÑŒ." msgid "spell|Blind" msgstr "Сліпота" msgid "Bless" msgstr "БлагоÑловеннÑ" msgid "Causes the selected creatures to inflict maximum damage." msgstr "ДозволÑÑ” обраному дружньому загону завдавати макÑимальної шкоди." msgid "Causes all of your units to inflict maximum damage." msgstr "ДозволÑÑ” вÑім дружнім загонам завдавати макÑимальної шкоди." msgid "Mass Bless" msgstr "МаÑове БлагоÑловеннÑ" msgid "Magically increases the defense skill of the selected creatures." msgstr "Магічно збільшує навичку захиÑту обраного дружнього загону." msgid "Stoneskin" msgstr "Кам'Ñна Шкіра" msgid "" "Increases the defense skill of the targeted creatures. This is an improved " "version of Stoneskin." msgstr "" "Збільшує навичку захиÑту обраного дружнього загону. Це заклÑÑ‚Ñ‚Ñ - покращена " "верÑÑ–Ñ ÐšÐ°Ð¼'Ñної Шкіри." msgid "Steelskin" msgstr "Сталева Шкіра" msgid "Causes the selected creatures to inflict minimum damage." msgstr "Змушує обраний ворожий загон завдавати мінімальної шкоди." msgid "Curse" msgstr "ПроклÑттÑ" msgid "Causes all enemy troops to inflict minimum damage." msgstr "Змушує вÑÑ– ворожі загони завдавати мінімальної шкоди." msgid "Mass Curse" msgstr "МаÑове ПроклÑттÑ" msgid "Damages all undead in the battle." msgstr "Завдає шкоди вÑім невмерлим на полі бою." msgid "Holy Word" msgstr "СвÑте Слово" msgid "" "Damages all undead in the battle. This is an improved version of Holy Word." msgstr "" "Завдає шкоди вÑім невмерлим на полі бою. Це заклÑÑ‚Ñ‚Ñ - покращена верÑÑ–Ñ " "СвÑтого Слова." msgid "Holy Shout" msgstr "СвÑтий Клич" msgid "Anti-Magic" msgstr "ÐнтимагіÑ" msgid "Prevents harmful magic against the selected creatures." msgstr "Запобігає заÑтоÑуванню магії проти обраного загону." msgid "Dispel Magic" msgstr "РозÑÑ–ÑŽÐ²Ð°Ð½Ð½Ñ ÐœÐ°Ð³Ñ–Ñ—" msgid "Removes all magic spells from a single target." msgstr "Знімає вÑÑ– магічні ефекти з обраного загону." msgid "Mass Dispel" msgstr "МаÑове РозÑіюваннÑ" msgid "Removes all magic spells from all creatures." msgstr "Знімає вÑÑ– заклÑÑ‚Ñ‚Ñ Ð· уÑÑ–Ñ… Ñ–Ñтот." msgid "Causes a magic arrow to strike the selected target." msgstr "ЗапуÑкає магічну Ñтрілу в обраний ворожий загін." msgid "Magic Arrow" msgstr "Магічна Стріла" msgid "Berserker" msgstr "БерÑерк" msgid "Causes a creature to attack its nearest neighbor." msgstr "Змушує зачарованих Ñ–Ñтот атакувати найближчі до нього загони." msgid "Armageddon" msgstr "Ðрмагеддон" msgid "" "Holy terror strikes the battlefield, causing severe damage to all creatures." msgstr "СвÑщенний жах вражає поле бою, завдаючи значної шкоди вÑім Ñ–Ñтотам." msgid "Elemental Storm" msgstr "Стихійний Шторм" msgid "Magical elements pour down on the battlefield, damaging all creatures." msgstr "Магічні Ñтихії проливаютьÑÑ Ð½Ð° поле бою, завдаючи шкоди вÑім Ñ–Ñтотам." msgid "" "A rain of rocks strikes an area of the battlefield, damaging all nearby " "creatures." msgstr "" "Потоки ÐºÐ°Ð¼Ñ–Ð½Ð½Ñ Ð²Ð´Ð°Ñ€Ñють по чаÑтині Ð¿Ð¾Ð»Ñ Ð±Ð¾ÑŽ, завдаючи шкоди вÑім Ñ–Ñтотам у " "полі дії." msgid "Meteor Shower" msgstr "Метеоритний Дощ" msgid "Paralyze" msgstr "Параліч" msgid "The targeted creatures are paralyzed, unable to move or retaliate." msgstr "" "Паралізує вражених Ñ–Ñтот, не дозволÑючи їм рухатиÑÑŒ або завдавати удару в " "відповідь." msgid "Hypnotize" msgstr "Гіпноз" msgid "" "Brings a single enemy unit under your control if its hits are less than " "%{count} times the caster's spell power." msgstr "" "Переводить обраний ворожий загін під ваш контроль, Ñкщо кількіÑть його очок " "здоров'Ñ Ð¼ÐµÐ½Ñˆ ніж у %{count} разів перевищує Силу Магії заклинача." msgid "Cold Ray" msgstr "Крижаний Промінь" msgid "Drains body heat from a single enemy unit." msgstr "Ð’Ñотує тепло тіла з обраного ворожого загону." msgid "Cold Ring" msgstr "Крижане Коло" msgid "" "Drains body heat from all units surrounding the center point, but not " "including the center point." msgstr "" "Ð’Ñотує тепло тіла з уÑÑ–Ñ… загонів довкола обраної клітинки, не завдаючи шкоди " "на ній." msgid "Disrupting Ray" msgstr "Розриваючий Промінь" msgid "Reduces the defense rating of an enemy unit by three." msgstr "" "Зменшує показник захиÑту ворожого загону на 3 одиниці. ЗаклÑÑ‚Ñ‚Ñ Ð¼Ð¾Ð¶Ð½Ð° " "накладати багаторазово, його також неможливо знÑти до ÐºÑ–Ð½Ñ†Ñ Ð±Ð¸Ñ‚Ð²Ð¸ будь-Ñким " "чином." msgid "Damages all living (non-undead) units in the battle." msgstr "Пошкоджує вÑÑ– живі (не невмерлі) загони на полі бою." msgid "Death Ripple" msgstr "Смертельне ТремтіннÑ" msgid "Death Wave" msgstr "Смертельна ХвилÑ" msgid "" "Damages all living (non-undead) units in the battle. This spell is an " "improved version of Death Ripple." msgstr "" "Пошкоджує вÑÑ– живі (не невмерлі) загони на полі бою. Це заклÑÑ‚Ñ‚Ñ - покращена " "верÑÑ–Ñ Ð¡Ð¼ÐµÑ€Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ð³Ð¾ ТремтіннÑ." msgid "Dragon Slayer" msgstr "ДраконовбивцÑ" msgid "Greatly increases a unit's attack skill vs. Dragons." msgstr "" "Значно збільшує навичку атаки зачарованого загону у бою проти Драконів." msgid "Blood Lust" msgstr "КровожерливіÑть" msgid "Increases a unit's attack skill." msgstr "Збільшує навичку атаки обраного дружнього загону." msgid "Animate Dead" msgstr "ÐžÐ¶Ð¸Ð²Ð»ÐµÐ½Ð½Ñ ÐœÐµÑ€Ñ†Ñ–Ð²" msgid "Resurrects creatures from a damaged or dead undead unit permanently." msgstr "" "Ðазавжди відновлює Ñ–Ñтот у пошкодженому або знищеному загоні невмерлих." msgid "Mirror Image" msgstr "Дзеркальний Фантом" msgid "" "Creates an illusionary unit that duplicates one of your existing units. This " "illusionary unit does the same damages as the original, but will vanish if " "it takes any damage." msgstr "" "Створює ілюзорну копію обраного дружнього загону. Загін-Ñ–Ð»ÑŽÐ·Ñ–Ñ Ð·Ð°Ð²Ð´Ð°Ð²Ð°Ñ‚Ð¸Ð¼Ðµ " "тої ж шкоди, що й оригінал, однак щезне, отримавши будь-Ñке пошкодженнÑ." msgid "Shield" msgstr "Щит" msgid "" "Halves damage received from ranged attacks for a single unit. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Зменшує вдвічі Ð¿Ð¾ÑˆÐºÐ¾Ð´Ð¶ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ дальніх атак Ð´Ð»Ñ Ð¾Ð±Ñ€Ð°Ð½Ð¾Ð³Ð¾ дружнього загону. " "Ðе впливає на атаки з боку замкових веж." msgid "Mass Shield" msgstr "МаÑовий Щит" msgid "" "Halves damage received from ranged attacks for all of your units. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Зменшує вдвічі Ð¿Ð¾ÑˆÐºÐ¾Ð´Ð¶ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ дальніх атак Ð´Ð»Ñ Ð²ÑÑ–Ñ… ваших загонів. Ðе " "впливає на атаки з боку замкових веж." msgid "Summon Earth Elemental" msgstr "Виклик Земних Елементалів" msgid "Summons Earth Elementals to fight for your army." msgstr "Викликає дружніх Земних Елементалів на поле бою." msgid "Summon Air Elemental" msgstr "Виклик ПовітрÑних Елементалів" msgid "Summons Air Elementals to fight for your army." msgstr "Викликає дружніх ПовітрÑних Елементалів на поле бою." msgid "Summon Fire Elemental" msgstr "Виклик ВогнÑних Елементалів" msgid "Summons Fire Elementals to fight for your army." msgstr "Викликає дружніх ВогнÑних Елементалів на поле бою." msgid "Summon Water Elemental" msgstr "Виклик Водних Елементалів" msgid "Summons Water Elementals to fight for your army." msgstr "Викликає дружніх Водних Елементалів на поле бою." msgid "Damages castle walls." msgstr "Пошкоджує Ñтіни замку." msgid "Earthquake" msgstr "ЗемлетруÑ" msgid "Causes all mines across the land to become visible." msgstr "Показує Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð½Ñ Ð²ÑÑ–Ñ… копалень на мапі." msgid "View Mines" msgstr "ОглÑд Копалень" msgid "Causes all resources across the land to become visible." msgstr "Показує Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð½Ñ Ð²ÑÑ–Ñ… реÑурÑів на мапі." msgid "View Resources" msgstr "ОглÑд РеÑурÑів" msgid "Causes all artifacts across the land to become visible." msgstr "Показує Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð½Ñ Ð²ÑÑ–Ñ… артефактів на мапі." msgid "View Artifacts" msgstr "ОглÑд Ðртефактів" msgid "Causes all towns and castles across the land to become visible." msgstr "Показує Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð½Ñ Ð²ÑÑ–Ñ… міÑÑ‚ на мапі." msgid "View Towns" msgstr "ОглÑд МіÑÑ‚" msgid "Causes all Heroes across the land to become visible." msgstr "Показує вÑÑ–Ñ… героїв на карті." msgid "View Heroes" msgstr "Показати Героїв" msgid "Causes the entire land to become visible." msgstr "Відкриває вÑÑŽ карту." msgid "View All" msgstr "Показати Ð’Ñе" msgid "Allows the caster to view detailed information on enemy Heroes." msgstr "ДозволÑÑ” заклиначу побачити повну інформацію про ворожих Героїв." msgid "Summon Boat" msgstr "Виклик КораблÑ" msgid "" "Summons the nearest unoccupied, friendly boat to an adjacent shore location. " "A friendly boat is one which you just built or were the most recent player " "to occupy." msgstr "" "Викликає найближчий дружній незайнÑтий корабель на розташовану поруч з " "героєм водойму. Корабель вважаєтьÑÑ Ð´Ñ€ÑƒÐ¶Ð½Ñ–Ð¼, Ñкщо він був збудований вами, " "або Ñкщо воÑтаннє ним кориÑтавÑÑ Ð²Ð°Ñˆ герой." msgid "Allows the caster to magically transport to a nearby location." msgstr "Магічно переноÑить заклинача на невелику відÑтань." msgid "Dimension Door" msgstr "ПроÑторова Брама" msgid "Returns the caster to any town or castle currently owned." msgstr "Повертає заклинача до будь-Ñкого міÑта або замку, що належить вам." msgid "Town Gate" msgstr "МіÑький Портал" msgid "" "Returns the hero to the town or castle of choice, provided it is controlled " "by you." msgstr "Повертає Ð³ÐµÑ€Ð¾Ñ Ð´Ð¾ міÑта або замку на вибір, Ñкщо ви його контролюєте." msgid "Visions" msgstr "ВидіннÑ" msgid "" "Visions predicts the likely outcome of an encounter with a neutral army camp." msgstr "Ð’Ð¸Ð´Ñ–Ð½Ð½Ñ Ð¿ÐµÑ€ÐµÐ´Ð±Ð°Ñ‡Ð°Ñ” ймовірний результат битви з нейтральною армією." msgid "Haunt" msgstr "ПроклÑта КопальнÑ" msgid "" "Haunts a mine you control with Ghosts. This mine stops producing resources. " "(If I can't keep it, nobody will!)" msgstr "" "Проклинає підконтрольну вам копальню, наÑелÑючи Ñ—Ñ— Привидами. Виробництво у " "копальні припинÑєтьÑÑ. (І Ñам не гам, Ñ– другому не дам!)" msgid "Set Earth Guardian" msgstr "Виклик Земних Вартових" msgid "Sets Earth Elementals to guard a mine against enemy armies." msgstr "Викликає Земних Елементалів Ð´Ð»Ñ Ð¾Ñ…Ð¾Ñ€Ð¾Ð½Ð¸ копальні від ворожих війÑьк." msgid "Set Air Guardian" msgstr "Виклик ПовітрÑних Вартових" msgid "Sets Air Elementals to guard a mine against enemy armies." msgstr "" "Викликає ПовітрÑних Елементалів Ð´Ð»Ñ Ð¾Ñ…Ð¾Ñ€Ð¾Ð½Ð¸ копальні від ворожих війÑьк." msgid "Set Fire Guardian" msgstr "Виклик ВогнÑних Вартових" msgid "Sets Fire Elementals to guard a mine against enemy armies." msgstr "Викликає ВогнÑних Елементалів Ð´Ð»Ñ Ð¾Ñ…Ð¾Ñ€Ð¾Ð½Ð¸ копальні від ворожих війÑьк." msgid "Set Water Guardian" msgstr "Виклик Водних Вартових" msgid "Sets Water Elementals to guard a mine against enemy armies." msgstr "Викликає Водних Елементалів Ð´Ð»Ñ Ð¾Ñ…Ð¾Ñ€Ð¾Ð½Ð¸ копальні від ворожих війÑьк." #, fuzzy msgid "Random Spell" msgstr "Випадкове міÑто" msgid "Randomly selected spell of any level." msgstr "" #, fuzzy msgid "Random 1st Level Spell" msgstr "Випадковий Замок" msgid "Randomly selected 1st level spell." msgstr "" msgid "Random 2nd Level Spell" msgstr "" msgid "Randomly selected 2nd level spell." msgstr "" msgid "Random 3rd Level Spell" msgstr "" msgid "Randomly selected 3rd level spell." msgstr "" msgid "Random 4th Level Spell" msgstr "" msgid "Randomly selected 4th level spell." msgstr "" msgid "Random 5th Level Spell" msgstr "" msgid "Randomly selected 5th level spell." msgstr "" msgid "Petrification" msgstr "Скам'ÑніннÑ" msgid "" "Turns the affected creature into stone. A petrified creature receives half " "damage from a direct attack." msgstr "" "Обертає уражену Ñ–Ñтоту на камінь. Скам'Ñніла Ñ–Ñтота отримує половину " "ушкоджень від безпоÑередньої атаки." msgid "You have no Magic Book, so you cannot cast a spell." msgstr "У Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” Книги Магії, то ж ви не можете вичаклувати заклÑттÑ." msgid "No spell to cast." msgstr "Ðемає чарів Ð´Ð»Ñ Ð·Ð°ÑтоÑуваннÑ." msgid "Your hero has %{point} spell points remaining." msgstr "У вашого Ð³ÐµÑ€Ð¾Ñ Ð·Ð°Ð»Ð¸ÑˆÐ¸Ð»Ð¾ÑÑŒ %{point} оч. мани." msgid "View Adventure Spells" msgstr "Показати Ðебойові ЗаклÑттÑ" msgid "View Combat Spells" msgstr "Показати Бойові ЗаклÑттÑ" msgid "View previous page" msgstr "Показати попередню Ñторінку" msgid "View next page" msgstr "Показати наÑтупну Ñторінку" msgid "Close Spellbook" msgstr "Закрити Книгу ЗаклÑть" msgid "View %{spell}" msgstr "Показати %{spell}" msgid "This spell does %{damage} points of damage." msgstr "Це заклÑÑ‚Ñ‚Ñ Ð·Ð°Ð²Ð´Ð°Ñ” %{damage} очок шкоди." msgid "" "This spell summons\n" "%{count} %{monster}." msgstr "" "Це заклÑÑ‚Ñ‚Ñ Ð²Ð¸ÐºÐ»Ð¸ÐºÐ°Ñ” \n" "%{count} %{monster}." msgid "This spell restores %{hp} HP." msgstr "Це заклÑÑ‚Ñ‚Ñ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÑŽÑ” %{hp} ОЗ." msgid "This spell summons %{count} %{monster} to guard the mine." msgstr "Це заклÑÑ‚Ñ‚Ñ Ð²Ð¸ÐºÐ»Ð¸ÐºÐ°Ñ” %{count} %{monster} Ð´Ð»Ñ Ð¾Ñ…Ð¾Ñ€Ð¾Ð½Ð¸ шахти." msgid "The nearest town is %{town}." msgstr "Ðайближче міÑто - %{town}." msgid "This town is occupied by your hero %{hero}." msgstr "Це міÑто вже зайнÑте вашим героєм %{hero}." msgid "" "This spell controls up to\n" "%{hp} HP." msgstr "" "Це заклÑÑ‚Ñ‚Ñ Ð´Ð¾Ð·Ð²Ð¾Ð»ÑÑ” керувати ворожим загоном, чиє Ñумарне здоров'Ñ Ð½Ðµ " "перевищує \n" "%{hp} ОЗ." msgid "The ultimate artifact is really the %{name}." msgstr "Унікальний Ðртефакт це Ñправді %{name}." msgid "The ultimate artifact may be found in the %{name} regions of the world." msgstr "Унікальний Ðртефакт можна знайти там, де %{name}." msgid "north-west" msgstr "північний захід" msgid "north" msgstr "північ" msgid "north-east" msgstr "північний Ñхід" msgid "west" msgstr "захід" msgid "center" msgstr "центр" msgid "east" msgstr "Ñхід" msgid "south-west" msgstr "південний захід" msgid "south" msgstr "південь" msgid "south-east" msgstr "південний Ñхід" msgid "The truth is out there." msgstr "Правда деÑÑŒ там." msgid "The dark side is stronger." msgstr "Темний бік Ñильніший." msgid "The end of the world is near." msgstr "Скоро наÑтане кінець Ñвіту." msgid "The bones of Lord Slayer are buried in the foundation of the arena." msgstr "КіÑтки Лорда Слейера поховані у фундаменті арени." msgid "A Black Dragon will take out a Titan any day of the week." msgstr "Чорний Дракон залюбки здолає Титана." msgid "He told her: Yada yada yada... and then she said: Blah, blah, blah..." msgstr "" "Вона Ñказала йому: \"Бла-бла-бла...\" Ртоді він відповів: Я не кажу: \"Бла-" "бла-бла!\"" msgid "An unknown force is being resurrected..." msgstr "Ðевідома Ñила поÑтає знов..." msgid "" "Check the newest version of the game at\n" "https://github.com/ihhub/\n" "fheroes2/releases" msgstr "" "Перевірити нову верÑÑ–ÑŽ гри на\n" "https://github.com/ihhub/\n" "fheroes2/releases" #, fuzzy #~ msgid "Castles" #~ msgstr "Замок" #, fuzzy #~ msgid "Ocean object" #~ msgstr "Порожнє міÑце" #~ msgid "Desert Objects" #~ msgstr "Об'єкти ПуÑтелі" #~ msgid "vs" #~ msgstr "проти" #~ msgid "%{race2} %{name2}" #~ msgstr "%{race2} %{name2}" fheroes2-1.0.12+dfsg/files/lang/vi.po000066400000000000000000012431711456075706000172530ustar00rootroot00000000000000# Vietnamese translation of fheroes2 # Copyright (C) 2023 fheroes2 team # This file is distributed under the same license as the fheroes2 package. # msgid "" msgstr "" "Project-Id-Version: fheroes2\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2024-02-03 03:27+0000\n" "PO-Revision-Date: 2023-12-20 19:47+0700\n" "Last-Translator: fheroes2 team \n" "Language-Team: \n" "Language: vi\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Poedit 3.4.1\n" msgid "" "BATTLE\n" "ONLY" msgstr "" "CHỈ\n" "CHIẾN ÄẤU" msgid "" "NEW\n" "GAME" msgstr "" "TẠO\n" "GAME" msgid "" "SAVE\n" "GAME" msgstr "" "LƯU\n" "GAME" msgid "" "LOAD\n" "GAME" msgstr "" "TẢI\n" "GAME" msgid "INFO" msgstr "THÔNG TIN" msgid "QUIT" msgstr "RỜI KHỎI" msgid "CANCEL" msgstr "TỪ BỎ" msgid "OKAY" msgstr "ÄÆ¯á»¢C" msgid "smallerButton|OKAY" msgstr "ÄÆ¯á»¢C" msgid "ACCEPT" msgstr "Äá»’NG Ã" msgid "DECLINE" msgstr "TỪ CHá»I" msgid "LEARN" msgstr "HỌC TẬP" msgid "TRADE" msgstr "TRAO Äá»”I" msgid "YES" msgstr "CÓ" msgid "NO" msgstr "KHÔNG" msgid "EXIT" msgstr "THOÃT" msgid "smallerButton|EXIT" msgstr "THOÃT" msgid "DISMISS" msgstr "GIẢI TÃN" msgid "UPGRADE" msgstr "NÂNG CẤP" msgid "RESTART" msgstr "LÀM LẠI" msgid "HEROES" msgstr "TƯỚNG" msgid "" "TOWNS/\n" "CASTLES" msgstr "" "THỊ TRẤN/\n" "LÂU ÄÀI" msgid "S" msgstr "S" msgid "M" msgstr "M" msgid "L" msgstr "L" msgid "X-L" msgstr "X-L" msgid "ALL" msgstr "TẤT CẢ" msgid "SELECT" msgstr "CHỌN" msgid "" "STANDARD\n" "GAME" msgstr "" "GAME\n" "THƯỜNG" msgid "" "CAMPAIGN\n" "GAME" msgstr "" "CHIẾN\n" "DỊCH" msgid "" "MULTI-\n" "PLAYER\n" "GAME" msgstr "" "CHÆ I\n" "NHIỀU\n" "NGƯỜI" msgid "CONFIG" msgstr "ÄIỀU CHỈNH" msgid "" "ORIGINAL\n" "CAMPAIGN" msgstr "" "CHIẾN DỊCH\n" "THƯỜNG" msgid "" "EXPANSION\n" "CAMPAIGN" msgstr "" "CHIẾN DỊCH\n" "MỞ RỘNG" msgid "HOT SEAT" msgstr "GHẾ NÓNG" msgid "2 PLAYERS" msgstr "2 NGƯỜI CHÆ I" msgid "3 PLAYERS" msgstr "3 NGƯỜI CHÆ I" msgid "4 PLAYERS" msgstr "4 NGƯỜI CHÆ I" msgid "5 PLAYERS" msgstr "5 NGƯỜI CHÆ I" msgid "6 PLAYERS" msgstr "6 NGƯỜI CHÆ I" msgid "GIFT" msgstr "QUÀ" msgid "MAX" msgstr "MAX" msgid "DIFFICULTY" msgstr "ÄỘ KHÓ" msgid "VIEW INTRO" msgstr "XEM GIỚI THIỆU" msgid "MIN" msgstr "MIN" msgid "RESET" msgstr "CÀI LẠI" #, fuzzy msgid "START" msgstr "LÀM LẠI" msgid "CASTLE" msgstr "LÂU ÄÀI" msgid "TOWN" msgstr "THỊ TRẤN" msgid "" "D\n" "I\n" "S\n" "M\n" "I\n" "S\n" "S" msgstr "" "G\n" "I\n" "Ả\n" "I\n" "T\n" "Ã\n" "N" msgid "" "E\n" "X\n" "I\n" "T" msgstr "" "T\n" "H\n" "O\n" "Ã\n" "T" msgid "" "C\n" "A\n" "M\n" "P\n" "A\n" "I\n" "G\n" "N" msgstr "" "C\n" "H\n" "I\n" "Ế\n" "N\n" "D\n" "Ị\n" "C\n" "H" msgid "" "S\n" "T\n" "A\n" "N\n" "D\n" "A\n" "R\n" "D" msgstr "" "T\n" "H\n" "Ư\n" "Ờ\n" "N\n" "G" msgid "Warrior" msgstr "Chiến binh" msgid "Builder" msgstr "Thợ xây" msgid "Explorer" msgstr "Thám hiểm" msgid "None" msgstr "Không" msgid "" "A few\n" "%{monster}" msgstr "" "Má»™t vài\n" "%{monster}" msgid "" "Several\n" "%{monster}" msgstr "" "Má»™t số\n" "%{monster}" msgid "" "A pack of\n" "%{monster}" msgstr "" "Má»™t nhóm\n" "%{monster}" msgid "" "Lots of\n" "%{monster}" msgstr "" "Khá nhiá»u\n" "%{monster}" msgid "" "A horde of\n" "%{monster}" msgstr "" "Má»™t bầy\n" "%{monster}" msgid "" "A throng of\n" "%{monster}" msgstr "" "Má»™t bầy đàn\n" "%{monster}" msgid "" "A swarm of\n" "%{monster}" msgstr "" "Rất nhiá»u đàn\n" "%{monster}" msgid "" "Zounds...\n" "%{monster}" msgstr "" "Quy mô\n" "%{monster}" msgid "" "A legion of\n" "%{monster}" msgstr "" "Quân Ä‘oàn\n" "%{monster}" msgid "army|Few" msgstr "Má»™t vài" msgid "army|Several" msgstr "Má»™t số" msgid "army|Pack" msgstr "Má»™t nhóm" msgid "army|Lots" msgstr "Khá nhiá»u" msgid "army|Horde" msgstr "Má»™t bầy" msgid "army|Throng" msgstr "Má»™t bầy đàn" msgid "army|Swarm" msgstr "Rất nhiá»u đàn" msgid "army|Zounds" msgstr "Quy mô" msgid "army|Legion" msgstr "Quân Ä‘oàn" msgid "All %{race} troops +1" msgstr "Tất cả binh lính %{race} +1" msgid "Multiple" msgstr "Bá»™i số" msgid "Troops of %{count} alignments -%{penalty}" msgstr "Quân cá»§a %{count} sắp hàng -%{penalty}" #, fuzzy msgid "Some undead in army -1" msgstr "Trong đội có xác sống -1" msgid "View %{name}" msgstr "Xem %{name}" msgid "Move the %{name} " msgstr "Di chuyển %{name} " msgid "Move or right click to redistribute %{name}" msgstr "Kéo %{name} hoặc nhấp chuá»™t phải để chia" msgid "Combine %{name} armies" msgstr "Gá»™p binh lính %{name}" msgid "Exchange %{name2} with %{name}" msgstr "Trao đổi %{name2} vá»›i %{name}" msgid "Select %{name}" msgstr "Chá»n %{name}" msgid "Cannot move last troop" msgstr "Không thể di chuyển quân cuối cùng" msgid "Move the %{name}" msgstr "Di chuyển %{name}" msgid "Set Count" msgstr "Äặt Số lượng" msgid "%{name} destroys half the enemy troops!" msgid_plural "%{name} destroy half the enemy troops!" msgstr[0] "%{name} tiêu diệt má»™t ná»­a quân địch!" msgid "%{name} has turned off the auto battle" msgstr "%{name} đã tắt tá»± động chiến đấu" msgid "%{name} has turned on the auto battle" msgstr "%{name} đã bật tá»± động chiến đấu" msgid "Spell failed!" msgstr "Niệm chú thất bại!" msgid "" "The Sphere of Negation artifact is in effect for this battle, disabling all " "combat spells." msgstr "" "Bảo vật Quả cầu Phá»§ định có hiệu lá»±c trong trận chiến này, vô hiệu hóa tất " "cả các phép thuật chiến đấu." msgid "You have already cast a spell this round." msgstr "Ngươi đã sá»­ dụng má»™t câu thần chú trong vòng này." msgid "That spell will have no effect!" msgstr "Phép này sẽ không ảnh hưởng đến ai!" msgid "You may only summon one type of elemental per combat." msgstr "Ngươi chỉ có thể triệu hồi má»™t loại nguyên tố trong má»—i trận chiến." msgid "" "There is no open space adjacent to your hero where you can summon an " "Elemental to." msgstr "" "Không có không gian trống nào gần tướng cá»§a ngươi để triệu hồi má»™t Nguyên tố." msgid "" "The Moat reduces by -%{count} the defense skill of any unit and slows to " "half movement rate." msgstr "" "Hào giảm -%{count} kỹ năng phòng thá»§ cá»§a bất kỳ đơn vị nào và làm chậm má»™t " "ná»­a tốc độ di chuyển." msgid "Speed: %{speed}" msgstr "Tốc độ: %{speed}" msgid "Speed" msgstr "Tốc độ" msgid "Off" msgstr "Tắt" msgid "On" msgstr "Bật" msgid "Turn Order" msgstr "Thứ tá»± Xếp hàng" msgid "Auto Spell Casting" msgstr "Tá»± động Niệm Phép" msgid "Grid" msgstr "Lưới" msgid "Shadow Movement" msgstr "Bóng Di chuyển" msgid "Shadow Cursor" msgstr "Bóng Con trá»" msgid "Audio" msgstr "Âm thanh" msgid "Settings" msgstr "Cài đặt" msgid "Configure" msgstr "Cấu hình" msgid "Hot Keys" msgstr "Phím tắt" msgid "Damage Info" msgstr "Thông tin Thiệt hại" msgid "Set the speed of combat actions and animations." msgstr "Äặt tốc độ cá»§a hành động chiến đấu và hình ảnh động." msgid "Toggle to display the turn order during the battle." msgstr "Chuyển đổi để hiển thị thứ tá»± xếp hàng trong trận chiến." msgid "" "Toggle whether or not the computer will cast spells for you when auto combat " "is on. (Note: This does not affect spell casting for computer players in any " "way, nor does it affect quick combat.)" msgstr "" "Chuyển đổi xem máy tính có sá»­ dụng phép thuật cho ngươi khi bật chế độ tá»± " "động chiến đấu hay không. (Lưu ý: Äiá»u này không ảnh hưởng đến việc sá»­ dụng " "phép thuật cho ngưá»i chÆ¡i máy tính theo bất kỳ cách nào, cÅ©ng như không ảnh " "hưởng đến chiến đấu nhanh.)" msgid "" "Toggle the hex grid on or off. The hex grid always underlies movement, even " "if turned off. This switch only determines if the grid is visible." msgstr "" "Bật hoặc tắt lưới lục giác. Lưới lục giác luôn làm cÆ¡ sở cho chuyển động, " "ngay cả khi bị tắt. Công tắc này chỉ xác định xem lưới có hiển thị hay không." msgid "" "Toggle on or off shadows showing where your creatures can move and attack." msgstr "" "Bật hoặc tắt bóng hiển thị nÆ¡i sinh vật cá»§a ngươi có thể di chuyển và tấn " "công." msgid "" "Toggle on or off a shadow showing the current hex location of the mouse " "cursor." msgstr "" "Bật hoặc tắt bóng hiển thị vị trí cá»§a con trá» chuá»™t trên lưới lục giác." msgid "Change the audio settings of the game." msgstr "Thay đổi cài đặt âm thanh cá»§a trò chÆ¡i." msgid "Check and configure all the hot keys present in the game." msgstr "Kiểm tra và định cấu hình tất cả các phím nóng có trong trò chÆ¡i." msgid "Toggle to display damage information during the battle." msgstr "Chuyển đổi để hiển thị thông tin thiệt hại trong trận chiến." msgid "Exit this menu." msgstr "Thoát khá»i trình đơn này." msgid "Okay" msgstr "OK" msgid "The enemy has surrendered!" msgstr "Kẻ địch đã đầu hàng!" msgid "Their cowardice costs them %{gold} gold." msgstr "" msgid "The enemy has fled!" msgstr "Kẻ thù đã bá» chạy!" msgid "A glorious victory!" msgstr "Má»™t chiến thắng vinh quang!" msgid "For valor in combat, %{name} receives %{exp} experience." msgstr "" "Vá»›i lòng dÅ©ng cảm trong chiến đấu, %{name} nhận được %{exp} kinh nghiệm." msgid "The cowardly %{name} flees from battle." msgstr "Kẻ hèn nhát %{name} chạy trốn khá»i trận chiến." msgid "%{name} surrenders to the enemy, and departs in shame." msgstr "%{name} đầu hàng kẻ thù và nhục nhã ra Ä‘i." #, fuzzy msgid "Your forces suffer a bitter defeat, and %{name} abandons your cause." msgstr "" "Lá»±c lượng cá»§a ngươi bị thất bại nặng ná» và %{name} từ bá» chính nghÄ©a cá»§a " "ngươi." #, fuzzy msgid "Your forces suffer a bitter defeat." msgstr "Lá»±c lượng cá»§a ngươi bị thất bại nặng ná»." msgid "Battlefield Casualties" msgstr "Thương vong Chiến trưá»ng" msgid "Attacker" msgstr "Bên công" msgid "Defender" msgstr "Bên thá»§" #, fuzzy msgid "Click to leave the battle results." msgstr "Nhấp để áp dụng độ phân giải đã chá»n." #, fuzzy msgid "Click to restart the battle in manual mode." msgstr "Nhấp để quay lại màn hình chính." msgid "Restart" msgstr "ChÆ¡i lại" msgid "You have captured an enemy artifact!" msgstr "Ngươi đã chiếm được má»™t bảo vật cá»§a kẻ thù!" msgid "As you reach for the %{name}, it mysteriously disappears." msgstr "Khi ngươi chạm tá»›i %{name}, nó biến mất má»™t cách bí ẩn." msgid "As your enemy reaches for the %{name}, it mysteriously disappears." msgstr "Khi kẻ thù cá»§a ngươi chạm tá»›i %{name}, nó biến mất má»™t cách bí ẩn." msgid "Necromancy!" msgstr "Chiêu hồn!" msgid "" "Practicing the dark arts of necromancy, you are able to raise %{count} of " "the enemy's dead to return under your service as %{monster}." msgstr "" "Thi triển nghệ thuật chiêu hồn hắc ám, ngươi có thể hồi sinh %{count} kẻ thù " "đã chết để trở lại dưới sá»± phục vụ cá»§a ngươi vá»›i tư cách là %{monster}." msgid "%{name} the %{race}" msgstr "%{name} cái %{race}" msgid "Captain of %{name}" msgstr "Äá»™i trưởng cá»§a %{name}" msgid "Attack" msgstr "Công" msgid "Defense" msgstr "Thá»§" msgid "Spell Power" msgstr "Sức mạnh" msgid "Knowledge" msgstr "Trí thức" msgid "Morale" msgstr "Tinh thần" msgid "Luck" msgstr "May mắn" msgid "Spell Points" msgstr "Äiểm Phép thuật" msgid "Hero's Options" msgstr "Tùy chá»n Tướng" msgid "Cast Spell" msgstr "Niệm Chú" msgid "Retreat" msgstr "Bá» chạy" msgid "Surrender" msgstr "Äầu hàng" msgid "Cancel" msgstr "Từ bá»" msgid "Hero Screen" msgstr "Màn hình Tướng" msgid "Captain's Options" msgstr "Tùy chá»n Äá»™i trưởng" msgid "" "Cast a magical spell. You may only cast one spell per combat round. The " "round is reset when every creature has had a turn." msgstr "" "Niệm má»™t phép thuật kỳ diệu. Ngươi chỉ có thể niệm má»™t câu thần chú cho má»—i " "vòng chiến đấu. Vòng được đặt lại khi má»i sinh vật đã có má»™t lượt." msgid "" "Retreat your hero, abandoning your creatures. Your hero will be available " "for you to recruit again, however, the hero will have only a novice hero's " "forces." msgstr "" "Rút lui tướng cá»§a ngươi, từ bá» sinh vật cá»§a ngươi. Tướng cá»§a ngươi sẽ có sẵn " "để ngươi chiêu má»™ lại, tuy nhiên, tướng sẽ chỉ có lá»±c lượng cá»§a má»™t tướng " "má»›i." #, fuzzy msgid "" "Surrendering costs gold. However if you pay the ransom, the hero and all of " "his or her surviving creatures will be available to recruit again. The cost " "of surrender is half of the total cost of the non-temporary troops remaining " "in the army." msgstr "" "Äầu hàng tốn vàng. Tuy nhiên, nếu ngươi trả tiá»n chuá»™c, tướng và tất cả " "những sinh vật còn sống sót cá»§a anh ta hoặc cô ta sẽ sẵn sàng để chiêu má»™ " "lại." msgid "Open Hero Screen to view full information about the hero." msgstr "Mở Màn hình Tướng để xem thông tin đầy đủ vá» Tướng." msgid "Return to the battle." msgstr "Quay lại trận chiến." msgid "Not enough gold (%{gold})" msgstr "Không đủ vàng (%{gold})" msgid "%{name} states:" msgstr "%{name} phát biểu:" msgid "Captain of %{name} states:" msgstr "Phát biểu cá»§a đội trưởng %{name}:" msgid "" "\"I will accept your surrender and grant you and your troops safe passage " "for the price of %{price} gold.\"" msgstr "" "\"Ta sẽ chấp nhận sá»± đầu hàng cá»§a ngươi và cấp cho ngươi và quân đội cá»§a " "ngươi lối Ä‘i an toàn vá»›i giá %{price} vàng.\"" msgid "View %{monster} info" msgstr "Xem thông tin %{monster}" msgid "Fly %{monster} here" msgstr "Bay %{monster} tá»›i đây" msgid "Move %{monster} here" msgstr "Chuyển %{monster} tá»›i đây" msgid "Shoot %{monster}" msgstr "Bắn %{monster}" msgid "(1 shot left)" msgid_plural "(%{count} shots left)" msgstr[0] "(còn 1 phát nữa)" msgid "Attack %{monster}" msgstr "Tấn công %{monster}" msgid "Turn %{turn}" msgstr "Lượt %{turn}" msgid "Teleport here" msgstr "Dịch chuyển tá»›i đây" msgid "Invalid teleport destination" msgstr "Äiểm đến dịch chuyển tức thá»i không hợp lệ" msgid "Cast %{spell} on %{monster}" msgstr "Niệm %{spell} lên %{monster}" msgid "Cast %{spell}" msgstr "Niệm %{spell}" msgid "Select spell target" msgstr "Chá»n mục tiêu niệm chú" msgid "View Ballista info" msgstr "Xem thông tin Máy bắn đá" msgid "Ballista" msgstr "Máy bắn đá" msgid "Enable auto combat" msgstr "Kích hoạt tá»± động chiến đấu" msgid "Allows the computer to fight out the battle for you." msgstr "Cho phép máy tính chiến đấu thay ngươi." msgid "Auto Combat" msgstr "Tá»± động Chiến đấu" msgid "Customize system options" msgstr "Tùy chỉnh tùy chá»n hệ thống" msgid "Allows you to customize the combat screen." msgstr "Cho phép ngươi tùy chỉnh màn hình chiến đấu." msgid "System Options" msgstr "Tùy chá»n Hệ thống" msgid "Skip this unit" msgstr "Bá» qua đơn vị này" msgid "Skip" msgstr "Bá» qua" msgid "" "Skips the current creature. The current creature ends its turn and does not " "get to go again until the next round." msgstr "" "Bá» qua sinh vật hiện tại. Sinh vật hiện tại kết thúc lượt cá»§a nó và không " "được Ä‘i tiếp cho đến vòng tiếp theo." msgid "View Captain's options" msgstr "Xem tùy chá»n Äá»™i trưởng" msgid "View Hero's options" msgstr "Xem tùy chá»n Tướng" msgid "View opposing Captain" msgstr "Xem đội trưởng đối phương" msgid "View opposing Hero" msgstr "Xem Tướng địch" msgid "Hide logs" msgstr "Ẩn ghi chép" msgid "Show logs" msgstr "Hiện ghi chép" msgid "Message Bar" msgstr "Thanh Tin nhắn" msgid "Shows the results of individual monster's actions." msgstr "Hiển thị kết quả hành động cá»§a từng quái vật." msgid "Are you sure you want to finish the battle in auto mode?" msgstr "Ngươi có chắc chắn muốn kết thúc trận chiến ở chế độ tá»± động không?" msgid "Are you sure you want to enable auto combat?" msgstr "Ngươi có chắc chắn muốn bật chiến đấu tá»± động?" msgid "%{name} skip their turn." msgstr "%{name} bá» lượt." msgid "%{attacker} does %{damage} damage." msgid_plural "%{attacker} do %{damage} damage." msgstr[0] "%{attacker} gây %{damage} sát thương." msgid "1 creature perishes." msgid_plural "%{count} creatures perish." msgstr[0] "1 sinh vật bị tiêu diệt." msgid "1 %{defender} perishes." msgid_plural "%{count} %{defender} perish." msgstr[0] "1 %{defender} bị tiêu diệt." msgid "1 soul is incorporated." msgid_plural "%{count} souls are incorporated." msgstr[0] "1 linh hồn được kết hợp." msgid "1 %{unit} is revived." msgid_plural "%{count} %{unit} are revived." msgstr[0] "1 %{unit} được hồi sinh." msgid "Moved %{monster}: from [%{src}] to [%{dst}]." msgstr "Chuyển %{monster}: từ [%{src}] tá»›i [%{dst}]." msgid "The %{name} resist the spell!" msgstr "Quái vật %{name} kháng phép này!" msgid "%{name} casts %{spell} on the %{troop}." msgstr "%{name} niệm phép %{spell} lên %{troop}." msgid "%{name} casts %{spell}." msgstr "%{name} niệm %{spell}." msgid "The %{spell} does %{damage} damage to one undead creature." msgstr "Phép %{spell} gây %{damage} sát thương cho má»™t sinh vật bất tá»­." msgid "The %{spell} does %{damage} damage to all undead creatures." msgstr "Phép %{spell} gây %{damage} sát thương cho tất cả sinh vật bất tá»­." msgid "The %{spell} does %{damage} damage, %{count} creatures perish." msgstr "Phép %{spell} gây %{damage} sát thương, %{count} sinh vật chết." msgid "The %{spell} does %{damage} damage." msgstr "Phép %{spell} gây %{damage} sát thương." msgid "The %{spell} does %{damage} damage to one living creature." msgstr "Phép %{spell} gây %{damage} sát thương cho má»™t sinh vật sống." msgid "The %{spell} does %{damage} damage to all living creatures." msgstr "Phép %{spell} gây %{damage} sát thương cho tất cả sinh vật sống." msgid "The %{attacker}' attack blinds the %{target}!" msgstr "Tướng %{attacker}' tấn công phép mù vào mục tiêu %{target}!" msgid "The %{attacker}' gaze turns the %{target} to stone!" msgstr "Cái nhìn cá»§a %{attacker}' biến %{target} thành đá!" msgid "The %{attacker}' curse falls upon the %{target}!" msgstr "Tướng %{attacker}' nguyá»n rá»§a lên tất cả %{target}!" msgid "The %{target} are paralyzed by the %{attacker}!" msgstr "Mục tiêu %{target} đã bị %{attacker} hóa đá!" msgid "The %{attacker} dispel all good spells on your %{target}!" msgstr "" "Tên %{attacker} xua tan má»i phép thuật tốt trên quân %{target} cá»§a ngươi!" msgid "The %{attacker} cast %{spell} on %{target}!" msgstr "Tên %{attacker} niệm phép %{spell} lên %{target}!" msgid "Bad luck descends on the %{attacker}." msgstr "Vận rá»§i giáng xuống %{attacker}." msgid "Good luck shines on the %{attacker}." msgstr "May mắn tá»a sáng trên %{attacker}." msgid "High morale enables the %{monster} to attack again." msgstr "Tinh thần cao cho phép %{monster} tấn công lần nữa." msgid "Low morale causes the %{monster} to freeze in panic." msgstr "Tinh thần xuống thấp khiến %{monster} chết cứng vì hoảng loạn." msgid "%{tower} does %{damage} damage." msgstr "%{tower} thá»±c hiện %{damage} sát thương." msgid "The mirror image is created." msgstr "Hình ảnh phản chiếu được tạo ra." msgid "The mirror image is destroyed!" msgstr "Hình ảnh phản chiếu đã mất Ä‘i!" msgid "Are you sure you want to interrupt the auto battle?" msgstr "Ngươi có chắc chắn muốn làm gián Ä‘oạn trận chiến tá»± động không?" msgid "Error" msgstr "Lá»—i" msgid "No spells to cast." msgstr "Không có phép thuật để niệm chú." msgid "Are you sure you want to retreat?" msgstr "Ngươi có chắc chắn muốn rút lui?" msgid "Retreat disabled" msgstr "Rút lui bị vô hiệu hóa" msgid "Surrender disabled" msgstr "Äầu hàng bị vô hiệu hóa" msgid "Damage: %{max}" msgstr "Sát thương: %{max}" msgid "Damage: %{min} - %{max}" msgstr "Sát thương: %{min} - %{max}" msgid "Perish: %{max}" msgstr "Chết %{max}" msgid "Perish: %{min} - %{max}" msgstr "Chết: %{min} - %{max}" msgid "" "Through eagle-eyed observation, %{name} is able to learn the magic spell " "%{spell}." msgstr "" "Thông qua khả năng quan sát bằng con mắt đại bàng, %{name} có thể há»c được " "phép thuật %{spell}." msgid "Human" msgstr "Con ngưá»i" msgid "AI" msgstr "AI" #, fuzzy msgid "Start" msgstr "ChÆ¡i lại" #, fuzzy msgid "Start the battle." msgstr "Quay lại trận chiến." msgid "Exit" msgstr "Thoát" msgid "Reset" msgstr "" msgid "Reset to default settings." msgstr "" msgid "Please select another hero." msgstr "Hãy chá»n má»™t tướng khác." msgid "Set Attack Skill" msgstr "Chá»n Kỹ năng Tấn công" msgid "Set Defense Skill" msgstr "Chá»n Kỹ năng Phòng thá»§" msgid "Set Power Skill" msgstr "Chá»n kỹ năng Sức mạnh" msgid "Set Knowledge Skill" msgstr "Chá»n Kỹ năng Trí thức" msgid "%{race1} %{name1} vs %{race2} %{name2}" msgstr "%{race1} %{name1} vs %{race2} %{name2}" msgid "Monsters" msgstr "Quái vật" msgid "N/A" msgstr "N/A" msgid "Left Turret" msgstr "Tháp pháo Trái" msgid "Right Turret" msgstr "Tháp pháo Phải" msgid "The %{name} fires with the strength of %{count} Archers" msgstr "%{name} khai há»a vá»›i sức mạnh cá»§a %{count} Cung thá»§" msgid "each with a +%{attack} bonus to their attack skill." msgstr "má»—i ngưá»i có phần thưởng +%{attack} cho kỹ năng tấn công cá»§a há»." msgid "The %{name} is destroyed." msgstr "%{name} bị phá há»§y." msgid "%{count} %{name} rises from the dead!" msgid_plural "%{count} %{name} rise from the dead!" msgstr[0] "%{count} %{name} hồi sinh từ cõi chết!" msgid "Dwarven Alliance" msgstr "Liên minh Ngưá»i lùn" msgid "Sorceress Guild" msgstr "Há»™i Phù thá»§y" msgid "Necromancer Guild" msgstr "Há»™i Chiêu hồn" msgid "Ogre Alliance" msgstr "Liên minh Yêu tinh" msgid "Dwarfbane" msgstr "Há»a Ngưá»i lùn" msgid "Dragon Alliance" msgstr "Liên minh Rồng" msgid "Elven Alliance" msgstr "Liên minh Ma quá»·" msgid "Kraeger defeated" msgstr "Kraeger bị đánh bại" msgid "Wayward Son" msgstr "Äứa con Bướng bỉnh" msgid "Uncle Ivan" msgstr "Bác Ivan" msgid "Annexation" msgstr "Thôn tính" msgid "Force of Arms" msgstr "Lá»±c lượng VÅ© trang" msgid "Save the Dwarves" msgstr "Cứu Ngưá»i lùn" msgid "Carator Mines" msgstr "Má» Carator" msgid "Turning Point" msgstr "Bước Ngoặt" msgid "scenarioName|Defender" msgstr "Bên thá»§" msgid "Corlagon's Defense" msgstr "Phòng thá»§ Corlagon" msgid "The Crown" msgstr "Vương Miện" msgid "The Gauntlet" msgstr "Thách thức" msgid "Betrayal" msgstr "Sá»± phản bá»™i" msgid "Final Justice" msgstr "Công lý Cuối cùng" msgid "" "Roland needs you to defeat the lords near his castle to begin his war of " "rebellion against his brother. They are not allied with each other, so they " "will spend most of their time fighting with one another. Victory is yours " "when you have defeated all of their castles and heroes." msgstr "" "Roland cần ngươi đánh bại các lãnh chúa gần lâu đài cá»§a anh ta để bắt đầu " "cuá»™c chiến nổi dậy chống lại anh trai mình. Há» không liên minh vá»›i nhau, vì " "vậy há» sẽ dành phần lá»›n thá»i gian để chiến đấu vá»›i nhau. Chiến thắng là cá»§a " "ngươi khi ngươi đã đánh bại tất cả các lâu đài và tướng cá»§a há»." msgid "" "The local lords refuse to swear allegiance to Roland, and must be subdued. " "They are wealthy and powerful, so be prepared for a tough fight. Capture all " "enemy castles to win." msgstr "" "Các lãnh chúa địa phương từ chối thá» trung thành vá»›i Roland, và phải khuất " "phục. Há» giàu có và quyá»n lá»±c, vì vậy hãy chuẩn bị cho má»™t cuá»™c chiến khó " "khăn. Äánh chiếm tất cả các lâu đài cá»§a kẻ thù để giành chiến thắng." msgid "" "Your task is to defend the Dwarves against Archibald's forces. Capture all " "of the enemy towns and castles to win, and be sure not to lose all of the " "dwarf towns at once, or the enemy will have won." msgstr "" "Nhiệm vụ cá»§a ngươi là bảo vệ Ngưá»i lùn chống lại lá»±c lượng cá»§a Archibald. " "Äánh chiếm tất cả các thị trấn và lâu đài cá»§a kẻ thù để giành chiến thắng, " "đồng thá»i đảm bảo không để mất tất cả các thị trấn cá»§a ngưá»i lùn cùng má»™t " "lúc, nếu không kẻ thù sẽ chiến thắng." msgid "" "You will face four allied enemies in a straightforward fight for resource " "and treasure. Capture all of the enemy castles for victory." msgstr "" "Ngươi sẽ đối mặt vá»›i bốn kẻ thù đồng minh trong má»™t cuá»™c chiến đơn giản để " "giành lấy tài nguyên và kho báu. Nắm bắt tất cả các lâu đài cá»§a kẻ thù để " "giành chiến thắng." msgid "" "Your enemies are allied against you and start close by, so be ready to come " "out fighting. You will need to own all four castles in this small valley to " "win." msgstr "" "Kẻ thù cá»§a ngươi Ä‘ang liên minh chống lại ngươi và bắt đầu áp sát, vì vậy " "hãy sẵn sàng chiến đấu. Ngươi sẽ cần phải sở hữu cả bốn lâu đài trong thung " "lÅ©ng nhá» này để giành chiến thắng." msgid "" "The Sorceress' guild of Noraston has requested Roland's aid against an " "attack from Archibald's allies. Capture all of the enemy castles to win, and " "don't lose Noraston, or you'll lose the scenario. (Hint: There is an enemy " "castle on an island in the ocean.)" msgstr "" "Há»™i phù thá»§y Noraston đã yêu cầu sá»± trợ giúp cá»§a Roland chống lại cuá»™c tấn " "công từ các đồng minh cá»§a Archibald. Äánh chiếm tất cả các lâu đài cá»§a kẻ " "thù để giành chiến thắng và đừng để mất Noraston, nếu không ngươi sẽ thua " "kịch bản. (Gợi ý: Có má»™t lâu đài cá»§a kẻ thù trên má»™t hòn đảo giữa đại dương.)" msgid "" "Gather as large an army as possible and capture the enemy castle within 8 " "weeks. You are opposed by only one enemy, but must travel a long way to get " "to the enemy castle. Any troops you have in your army at the end of this " "scenario will be with you in the final battle." msgstr "" "Tập hợp má»™t đội quân lá»›n nhất có thể và chiếm được lâu đài cá»§a kẻ thù trong " "vòng 8 tuần. Ngươi chỉ bị má»™t kẻ thù chống lại, nhưng phải Ä‘i má»™t quãng " "đưá»ng dài để đến được lâu đài cá»§a kẻ thù. Bất kỳ đội quân nào ngươi có trong " "quân đội cá»§a mình khi kết thúc kịch bản này sẽ ở bên ngươi trong trận chiến " "cuối cùng." msgid "" "Find the Crown before Archibald's heroes find it. Roland will need the Crown " "for the final battle against Archibald." msgstr "" "Tìm Vương miện trước khi các anh hùng cá»§a Archibald tìm thấy nó. Roland sẽ " "cần Vương miện cho trận chiến cuối cùng vá»›i Archibald." msgid "" "Three allied enemies stand before you and victory, including Lord Corlagon. " "Roland is in a castle to the northwest, and you will lose if he falls to the " "enemy. Remember that capturing Lord Corlagon will ensure that he will not " "fight against you in the final scenario." msgstr "" "Ba kẻ thù đồng minh đứng trước ngươi và chiến thắng, bao gồm cả Lord " "Corlagon. Roland Ä‘ang ở trong má»™t lâu đài ở phía tây bắc, và ngươi sẽ thua " "nếu anh ta rÆ¡i vào tay kẻ thù. Hãy nhá»› rằng việc bắt giữ Lord Corlagon sẽ " "đảm bảo rằng anh ta sẽ không chiến đấu chống lại ngươi trong kịch bản cuối " "cùng." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Archibald to end the war!" msgstr "" "Äây là trận chiến cuối cùng. Cả ngươi và kẻ thù cá»§a ngươi Ä‘á»u được trang bị " "tận răng, và tất cả Ä‘á»u liên minh chống lại ngươi. Bắt giữ Archibald để kết " "thúc chiến tranh!" msgid "" "Switching sides leaves you with three castles against the enemy's one. This " "battle will be the easiest one you will face for the rest of the war..." "traitor." msgstr "" "Äổi bên để lại cho ngươi ba lâu đài chống lại má»™t lâu đài cá»§a kẻ thù. Trận " "chiến này sẽ là trận chiến dá»… dàng nhất mà ngươi phải đối mặt trong phần còn " "lại cá»§a cuá»™c chiến...kẻ phản bá»™i." msgid "Barbarian Wars" msgstr "Chiến tranh Man rợ" msgid "First Blood" msgstr "Máu Äầu tiên" msgid "Necromancers" msgstr "Chiêu hồn" msgid "Slay the Dwarves" msgstr "Tiêu diệt Ngưá»i lùn" msgid "Country Lords" msgstr "Lãnh chúa Äất nước" msgid "Dragon Master" msgstr "Thá»§ lÄ©nh Rồng" msgid "Rebellion" msgstr "Nổi loạn" msgid "Apocalypse" msgstr "Tận thế" msgid "Greater Glory" msgstr "Vinh quang Lá»›n hÆ¡n" msgid "" "King Archibald requires you to defeat the three enemies in this region. They " "are not allied with one another, so they will spend most of their energy " "fighting amongst themselves. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Vua Archibald yêu cầu ngươi đánh bại ba kẻ thù trong khu vá»±c này. Há» không " "liên minh vá»›i nhau, vì vậy há» sẽ dành phần lá»›n năng lượng để chiến đấu vá»›i " "nhau. Ngươi sẽ giành chiến thắng khi sở hữu tất cả các lâu đài cá»§a kẻ thù và " "không còn anh hùng nào để chiến đấu." msgid "" "You must unify the barbarian tribes of the north by conquering them. As in " "the previous mission, the enemy is not allied against you, but they have " "more resources at their disposal. You will win when you own all of the enemy " "castles and there are no more heroes left to fight." msgstr "" "Ngươi phải thống nhất các bá»™ lạc man rợ ở phía bắc bằng cách chinh phục há». " "Như trong nhiệm vụ trước, kẻ thù không liên minh chống lại ngươi, nhưng " "chúng có nhiá»u tài nguyên hÆ¡n để sá»­ dụng. Ngươi sẽ giành chiến thắng khi sở " "hữu tất cả các lâu đài cá»§a kẻ thù và không còn anh hùng nào để chiến đấu." msgid "" "Do-gooder wizards have taken the Necromancers' castle. You must retake it to " "achieve victory. Remember that while you start with a powerful army, you " "have no castle and must take one within 7 days, or lose this battle. (Hint: " "The nearest castle is to the southeast.)" msgstr "" "Các pháp sư làm Ä‘iá»u tốt hÆ¡n đã chiếm lâu đài cá»§a các Necromancer. Ngươi " "phải chiếm lại nó để đạt được chiến thắng. Hãy nhá»› rằng trong khi ngươi bắt " "đầu vá»›i má»™t đội quân hùng mạnh, ngươi không có lâu đài nào và phải chiếm lấy " "má»™t lâu đài trong vòng 7 ngày, nếu không sẽ thua trận này. (Gợi ý: Lâu đài " "gần nhất nằm ở phía đông nam.)" msgid "" "The dwarves need conquering before they can interfere in King Archibald's " "plans. Roland's forces have more than one hero and many towns to start with, " "so be ready for attack from multiple directions. You must capture all of the " "enemy towns and castles to claim victory." msgstr "" "Những ngưá»i lùn cần chinh phục trước khi há» có thể can thiệp vào kế hoạch " "cá»§a Vua Archibald. Lá»±c lượng cá»§a Roland có nhiá»u hÆ¡n má»™t anh hùng và nhiá»u " "thị trấn để bắt đầu, vì vậy hãy sẵn sàng tấn công từ nhiá»u hướng. Ngươi phải " "chiếm được tất cả các thị trấn và lâu đài cá»§a kẻ thù để giành chiến thắng." msgid "" "You must put down a peasant revolt led by Roland's forces. All are allied " "against you, but you have Lord Corlagon, an experienced hero, to help you. " "Capture all enemy castles to win." msgstr "" "Ngươi phải dập tắt cuá»™c nổi dậy cá»§a nông dân do lá»±c lượng cá»§a Roland lãnh " "đạo. Tất cả Ä‘á»u liên minh chống lại ngươi, nhưng ngươi có Lord Corlagon, má»™t " "anh hùng giàu kinh nghiệm, để giúp ngươi. Äánh chiếm tất cả các lâu đài cá»§a " "kẻ thù để giành chiến thắng." msgid "" "There are two enemies allied against you in this mission. Both are well " "armed and seek to evict you from their island. Avoid them and capture Dragon " "City to win." msgstr "" "Có hai kẻ thù liên minh chống lại ngươi trong nhiệm vụ này. Cả hai Ä‘á»u được " "trang bị vÅ© khí tốt và tìm cách Ä‘uổi ngươi khá»i hòn đảo cá»§a há». Tránh chúng " "và chiếm Dragon City để giành chiến thắng." msgid "" "Your orders are to conquer the country lords that have sworn to serve " "Roland. All of the enemy castles are unified against you. Since you start " "without a castle, you must hurry to capture one before the end of the week. " "Capture all enemy castles for victory." msgstr "" "Mệnh lệnh cá»§a ngươi là chinh phục các lãnh chúa đất nước đã thá» phục vụ " "Roland. Tất cả các lâu đài cá»§a kẻ thù được thống nhất chống lại ngươi. Vì " "ngươi bắt đầu mà không có lâu đài, nên ngươi phải nhanh chóng chiếm được má»™t " "lâu đài trước cuối tuần. Nắm bắt tất cả các lâu đài cá»§a kẻ thù để giành " "chiến thắng." msgid "" "Find the Crown before Roland's heroes find it. Archibald will need the Crown " "for the final battle against Roland." msgstr "" "Tìm Vương miện trước khi các anh hùng cá»§a Roland tìm thấy nó. Archibald sẽ " "cần Vương miện cho trận chiến cuối cùng vá»›i Roland." msgid "" "This is the final battle. Both you and your enemy are armed to the teeth, " "and all are allied against you. Capture Roland to win the war, and be sure " "not to lose Archibald in the fight!" msgstr "" "Äây là trận chiến cuối cùng. Cả ngươi và kẻ thù cá»§a ngươi Ä‘á»u được trang bị " "tận răng, và tất cả Ä‘á»u liên minh chống lại ngươi. Bắt giữ Roland để giành " "chiến thắng trong cuá»™c chiến và đảm bảo không để thua Archibald trong cuá»™c " "chiến!" msgid "Arrow's Flight" msgstr "ÄÆ°á»ng Tên bay" msgid "Island of Chaos" msgstr "Äảo Há»—n loạn" msgid "The Abyss" msgstr "Vá»±c thẳm" msgid "Uprising" msgstr "Khởi nghÄ©a" msgid "Aurora Borealis" msgstr "Bình minh phương Bắc" msgid "Betrayal's End" msgstr "Kết thúc Phản bá»™i" msgid "Corruption's Heart" msgstr "Trái tim Tham nhÅ©ng" msgid "The Giant's Pass" msgstr "Äèo Cả" msgid "" "Subdue the unruly local lords in order to provide the Empire with facilities " "to operate in this region." msgstr "" "Khuất phục các lãnh chúa địa phương ngang ngược để cung cấp cho Äế chế các " "cÆ¡ sở hoạt động trong khu vá»±c này." msgid "" "Eliminate all opposition in this area. Then the first piece of the artifact " "will be yours." msgstr "" "Loại bá» tất cả các đối lập trong lÄ©nh vá»±c này. Phần đầu tiên cá»§a hiện vật sẽ " "là cá»§a ngươi." msgid "" "The sorceresses to the northeast are rebelling! For the good of the empire " "you must quash their feeble uprising on your way to the mountains." msgstr "" "Các phù thá»§y ở phía đông bắc Ä‘ang nổi dậy! Vì lợi ích cá»§a đế chế, ngươi phải " "dập tắt cuá»™c nổi dậy yếu á»›t cá»§a chúng trên đưá»ng đến vùng núi." msgid "" "Having prepared for your arrival, Kraeger has arranged for a force of " "necromancers to thwart your quest. You must capture the castle of Scabsdale " "before the first day of the third week, or the Necromancers will be too " "strong for you." msgstr "" "Sau khi chuẩn bị sẵn sàng cho sá»± xuất hiện cá»§a ngươi, Kraeger đã sắp xếp má»™t " "lá»±c lượng các pháp sư gá»i hồn để cản trở nhiệm vụ cá»§a ngươi. Ngươi phải " "chiếm được lâu đài Scabsdale trước ngày đầu tiên cá»§a tuần thứ ba, nếu không " "các Necromancer sẽ quá mạnh đối vá»›i ngươi." msgid "" "The barbarian despot in this area is, as yet, ignorant of your presence. " "Quickly, build up your forces before you are discovered and attacked! Secure " "the region by subduing all enemy forces." msgstr "" "Kẻ chuyên quyá»n man rợ trong khu vá»±c này vẫn không biết gì vá» sá»± hiện diện " "cá»§a ngươi. Nhanh chóng, xây dá»±ng lá»±c lượng cá»§a ngươi trước khi ngươi bị phát " "hiện và tấn công! Bảo vệ khu vá»±c bằng cách khuất phục tất cả các lá»±c lượng " "cá»§a kẻ thù." msgid "" "The Empire is weak in this region. You will be unable to completely subdue " "all forces in this area, so take what you can before reprisal strikes. " "Remember, your true goal is to claim the Helmet of Anduran." msgstr "" "Äế chế yếu trong khu vá»±c này. Ngươi sẽ không thể khuất phục hoàn toàn tất cả " "các lá»±c lượng trong khu vá»±c này, vì vậy hãy lấy những gì ngươi có thể trước " "khi tấn công trả đũa. Hãy nhá»› rằng, mục tiêu thá»±c sá»± cá»§a ngươi là giành được " "MÅ© bảo hiểm cá»§a Anduran." msgid "For the good of the Empire, eliminate Kraeger." msgstr "Vì lợi ích cá»§a Äế chế, hãy loại bá» Kraeger." msgid "" "At last, you have the opportunity and the facilities to rid the Empire of " "the necromancer's evil. Eradicate them completely, and you will be sung as a " "hero for all time." msgstr "" "Cuối cùng, ngươi có cÆ¡ há»™i và phương tiện để loại bá» Äế chế khá»i cái ác cá»§a " "kẻ phá hoại. Tiêu diệt chúng hoàn toàn, và ngươi sẽ được tôn vinh như má»™t " "anh hùng má»i thá»i đại." msgid "Border Towns" msgstr "Trấn Biên thùy" msgid "Conquer and Unify" msgstr "Chinh phục và Thống nhất" msgid "Crazy Uncle Ivan" msgstr "Ivan Äiên rồ" msgid "The Wayward Son" msgstr "Äứa con Bướng bỉnh" msgid "Ivory Gates" msgstr "Cổng Ngà" msgid "The Elven Lands" msgstr "Äảo Yêu quái" msgid "The Epic Battle" msgstr "Trận chiến Sá»­ thi" msgid "The Southern War" msgstr "Cuá»™c chiến phương Nam" msgid "" "Conquer and unite all the enemy tribes. Don't lose the hero Jarkonas, the " "forefather of all descendants." msgstr "" "Chinh phục và thống nhất tất cả các bá»™ lạc kẻ thù. Äừng đánh mất anh hùng " "Jarkonas, tổ tiên cá»§a tất cả con cháu." msgid "" "Your rival, the Kingdom of Harondale, is attacking weak towns on your " "border! Recover from their first strike and crush them completely!" msgstr "" "Äối thá»§ cá»§a ngươi, Vương quốc Harondale, Ä‘ang tấn công các thị trấn yếu ở " "biên giá»›i cá»§a ngươi! Phục hồi từ cuá»™c tấn công đầu tiên cá»§a há» và nghiá»n nát " "há» hoàn toàn!" msgid "" "Find your wayward son Joseph who is rumored to be living in the desolate " "lands. Do it before the first day of the third month or it will be of no " "help to your family." msgstr "" "Tìm đứa con trai bướng bỉnh Joseph cá»§a ngươi, ngưá»i được đồn đại là Ä‘ang " "sống ở những vùng đất hoang vắng. Hãy làm Ä‘iá»u đó trước ngày đầu tiên cá»§a " "tháng thứ ba nếu không nó sẽ chẳng giúp ích được gì cho gia đình ngươi." msgid "" "Rescue your crazy uncle Ivan. Find him before the first day of the fourth " "month or it will be no help to your kingdom." msgstr "" "Giải cứu chú Ivan Ä‘iên rồ cá»§a ngươi. Tìm anh ta trước ngày đầu tiên cá»§a " "tháng thứ tư, nếu không sẽ chẳng ích gì cho vương quốc cá»§a ngươi." msgid "" "Destroy the barbarians who are attacking the southern border of your " "kingdom! Recover your fallen towns, and then invade the jungle kingdom. " "Leave no enemy standing." msgstr "" "Tiêu diệt những kẻ man rợ Ä‘ang tấn công biên giá»›i phía nam vương quốc cá»§a " "ngươi! Khôi phục các thị trấn đã sụp đổ cá»§a ngươi, sau đó xâm chiếm vương " "quốc rừng rậm. Không để kẻ thù đứng yên." msgid "Retake the castle of Ivory Gates, which has fallen due to treachery." msgstr "Chiếm lại lâu đài Ivory Gates đã sụp đổ do bị phản bá»™i." msgid "" "Gain the favor of the elves. They will not allow trees to be chopped down, " "so they will send you wood every 2 weeks. You must complete your mission " "before the first day of the seventh month, or the kingdom will surely fall." msgstr "" "Giành được sá»± ưu ái cá»§a yêu tinh. Há» sẽ không cho phép chặt cây, vì vậy há» " "sẽ gá»­i cho ngươi gá»— 2 tuần má»™t lần. Ngươi phải hoàn thành nhiệm vụ cá»§a mình " "trước ngày đầu tiên cá»§a tháng thứ bảy, nếu không vương quốc chắc chắn sẽ sụp " "đổ." msgid "" "This is the final battle against your rival kingdom of Harondale. Eliminate " "everyone, and don't lose the hero Jarkonas VI." msgstr "" "Äây là trận chiến cuối cùng chống lại vương quốc Harondale đối thá»§ cá»§a " "ngươi. Loại bá» tất cả má»i ngưá»i và đừng để mất anh hùng Jarkonas VI." msgid "Fount of Wizardry" msgstr "Suối nguồn Ma thuật" msgid "Power's End" msgstr "Chấm dứt Quyá»n lá»±c" msgid "The Eternal Scrolls" msgstr "Phép màu VÄ©nh cá»­u" msgid "The Shrouded Isles" msgstr "Quần đảo được Bao phá»§" msgid "" "Your mission is to vanquish the warring mages in the magical Shrouded Isles. " "The completion of this task will give you a fighting chance against your " "rivals." msgstr "" "Nhiệm vụ cá»§a ngươi là đánh bại các pháp sư Ä‘ang gây chiến trong Quần đảo " "được Bao phá»§ huyá»n diệu. Việc hoàn thành nhiệm vụ này sẽ cho ngươi cÆ¡ há»™i " "chiến đấu chống lại các đối thá»§ cá»§a mình." msgid "" "The location of the great library has been discovered! You must make your " "way to it, and reclaim the city of Chronos in which it lies." msgstr "" "Vị trí cá»§a thư viện tuyệt vá»i đã được phát hiện! ngươi phải tìm đưá»ng đến đó " "và giành lại thành phố Chronos nÆ¡i nó tá»a lạc." msgid "" "Find the Orb of negation, which is said to be buried in this land. There are " "clues inscribed on stone obelisks which will help lead you to your prize. " "Find the Orb before the first day of the sixth month, or your rivals will " "surely have gotten to the fount before you." msgstr "" "Tìm Quả cầu phá»§ định, được cho là bị chôn vùi ở vùng đất này. Có những manh " "mối được ghi trên các đài tưởng niệm bằng đá sẽ giúp dẫn ngươi đến mức giá " "cá»§a mình. Tìm Quả cầu trước ngày đầu tiên cá»§a tháng thứ sáu, nếu không các " "đối thá»§ cá»§a ngươi chắc chắn sẽ đến được nguồn trước ngươi." msgid "" "You must take control of the castle of Magic, where the fount of wizardry " "lies. Do this and your victory will be supreme." msgstr "" "Ngươi phải kiểm soát lâu đài Phép thuật, nÆ¡i khởi nguồn cá»§a phép thuật. Làm " "Ä‘iá»u này và chiến thắng cá»§a ngươi sẽ là tối cao." msgid "Blood is Thicker" msgstr "Máu Dầy hÆ¡n" msgid "King and Country" msgstr "Vua và Äất nước" msgid "Pirate Isles" msgstr "Äảo Hải tặc" msgid "Stranded" msgstr "Mắc kẹt" msgid "" "Capture the town on the island off the southeast shore in order to construct " "a boat and travel back towards the mainland. Do not lose the hero Gallavant." msgstr "" "Äánh chiếm thị trấn trên hòn đảo ngoài khÆ¡i bá» biển phía đông nam để đóng " "má»™t chiếc thuyá»n và quay trở lại đất liá»n. Äừng để mất anh hùng Gallavant." msgid "" "Find and defeat Martine, the pirate leader, who resides in Pirates Cove. Do " "not lose Gallavant or your quest will be over." msgstr "" "Tìm và đánh bại Martine, thá»§ lÄ©nh cướp biển, cư trú tại Pirates Cove. Äừng " "để mất Gallavant nếu không nhiệm vụ cá»§a ngươi sẽ kết thúc." msgid "" "Eliminate all the other forces who oppose the rule of Lord Alberon. " "Gallavant must not die." msgstr "" "Loại bá» tất cả các lá»±c lượng khác chống lại sá»± cai trị cá»§a Chúa Alberon. " "Gallavant không được chết." msgid "" "Overthrow the entrenched monarchy of Lord Alberon, and claim all the land in " "your name. Gallavant must not die." msgstr "" "Lật đổ chế độ quân chá»§ cố thá»§ cá»§a Chúa Alberon và giành lấy toàn bá»™ đất Ä‘ai " "dưới tên cá»§a ngươi. Gallavant không được chết." msgid " bane" msgstr " tai ương" msgid " alliance" msgstr " đồng minh" msgid "Carry-over forces" msgstr "Lá»±c lượng chuyển giao" msgid " bonus" msgstr " thưởng" msgid " defeated" msgstr " bị đánh bại" msgid " will always run away from your army." msgstr " sẽ luôn chạy trốn khá»i quân đội cá»§a ngươi." msgid " will be willing to join your army." msgstr " sẽ sẵn sàng gia nhập quân đội cá»§a ngươi." msgid "\"%{artifact}\" artifact will be carried over the scenario." msgstr "\"%{artifact}\" bảo vật sẽ được chuyển qua màn tiếp theo." msgid "The army will be carried over the scenario." msgstr "Quân lính sẽ được chuyển qua màn tiếp theo." msgid "The kingdom will have +%{count} %{resource} each day." msgstr "Vương quốc sẽ có +%{count} %{resource} má»—i ngày." msgid "\"%{spell}\" spell will be carried over the scenario." msgstr "\"%{spell}\" phép thuật sẽ được chuyển qua màn tiếp theo." msgid "%{hero} can be hired in the scenario." msgstr "%{hero} có thể thuê ở màn này." msgid "" "%{hero} has been defeated and will not appear in the subsequent scenarios." msgstr "%{hero} đã chết và không thể xuất hiện ở các màn phụ." msgid "The dwarves recognize their allies and gladly join your forces." msgstr "" "Ngưá»i lùn nhận ra đồng minh cá»§a há» và sẵn sàng gia nhập lá»±c lượng cá»§a ngươi." msgid "The ogres recognize you as the Dwarfbane and lumber over to join you." msgstr "" "Yêu tinh nhận ra ngươi là Tai ương cá»§a Ngưá»i lùn và đến để tham gia cùng " "ngươi." msgid "" "The dragons, snarling and growling, agree to join forces with you, their " "'Ally'." msgstr "" "Những con rồng gầm gừ và gầm gừ đồng ý gia nhập lá»±c lượng vá»›i ngươi, 'Äồng " "minh' cá»§a chúng." msgid "" "As you approach the group of elves, their leader calls them all to " "attention. He shouts to them, \"Who of you is brave enough to join this " "fearless ally of ours?\" The group explodes with cheers as they run to join " "your ranks." msgstr "" "Khi ngươi đến gần nhóm yêu tinh, thá»§ lÄ©nh cá»§a há» sẽ kêu gá»i tất cả há» chú ý. " "Anh ta hét lên vá»›i há», \"Ai trong số các ngươi đủ can đảm để tham gia vào " "đồng minh dÅ©ng cảm này cá»§a chúng tôi?\" Cả nhóm bùng nổ vá»›i tiếng reo hò khi " "há» chạy đến gia nhập hàng ngÅ© cá»§a ngươi." msgid "" "The dwarves hail you, \"Any friend of Roland is a friend of ours. You may " "pass.\"" msgstr "" "Những ngưá»i lùn chào đón ngươi, \"Bất kỳ ngưá»i bạn nào cá»§a Roland Ä‘á»u là " "ngươi cá»§a chúng tôi. Ngươi có thể vượt qua.\"" msgid "" "The ogres give you a grunt of recognition, \"Archibald's allies may pass.\"" msgstr "" "Yêu tinh nhận ra ngươi và càu nhàu, \"Äồng minh cá»§a Archibald có thể vượt " "qua.\"" msgid "" "The dragons see you and call out. \"Our alliance with Archibald compels us " "to join you. Unfortunately you have no room. A pity!\" They quickly scatter." msgstr "" "Những con rồng nhìn thấy ngươi và gá»i ra. \"Liên minh cá»§a chúng tôi vá»›i " "Archibald buá»™c chúng tôi phải tham gia cùng ngươi. Thật không may, ngươi " "không có chá»—. Thật đáng tiếc!\" Há» nhanh chóng chạy tán loạn." msgid "" "The elves stand at attention as you approach. Their leader calls to you and " "says, \"Let us not impede your progress, ally! Move on, and may victory be " "yours.\"" msgstr "" "Yêu tinh đứng chú ý khi ngươi đến gần. Thá»§ lÄ©nh cá»§a há» gá»i cho ngươi và nói: " "\"Hãy để chúng tôi không cản trở bước tiến cá»§a ngươi, đồng minh! Hãy tiếp " "tục và có thể chiến thắng sẽ thuá»™c vá» ngươi.\"" msgid "\"The Dwarfbane!!!!, run for your lives.\"" msgstr "\"Tai ương Ngưá»i lùn!!!, chạy ngay Ä‘i.\"" msgid "campaignBonus|Animate Dead" msgstr "Sức sống Thần chết" msgid "campaignBonus|Chain Lightning" msgstr "Sét chuyá»n" msgid "campaignBonus|Fireblast" msgstr "Nổ lá»­a" msgid "campaignBonus|Mass Curse" msgstr "Nguyá»n rá»§a Rá»™ng" msgid "campaignBonus|Mass Haste" msgstr "Tăng tốc Rá»™ng" msgid "campaignBonus|Mirror Image" msgstr "Ảnh Gương" msgid "campaignBonus|Resurrect" msgstr "Hồi sinh" msgid "campaignBonus|Steelskin" msgstr "Da sắt" msgid "campaignBonus|Summon Earth" msgstr "Triệu hồi Äất" msgid "campaignBonus|View Heroes" msgstr "Xem Tướng" msgid "campaignBonus|Ballista" msgstr "Máy bắn đá" msgid "campaignBonus|Black Pearl" msgstr "Ngá»c trai Äen" msgid "campaignBonus|Caster's Bracelet" msgstr "Vòng tay Caster" msgid "campaignBonus|Defender Helm" msgstr "MÅ© Bảo vệ" msgid "campaignBonus|Breastplate" msgstr "Giáp ngá»±c" msgid "campaignBonus|Dragon Sword" msgstr "Kiếm Rồng" msgid "campaignBonus|Fizbin Medal" msgstr "Huân chương Fizbin" msgid "campaignBonus|Foremost Scroll" msgstr "Phép Äầu tiên" msgid "campaignBonus|Gauntlets" msgstr "Găng tay" msgid "campaignBonus|Hideous Mask" msgstr "Mặt nạ Gá»›m ghiếc" msgid "campaignBonus|Mage's Ring" msgstr "Nhẫn Ma thuật" msgid "campaignBonus|Major Scroll" msgstr "Phép Lá»›n" msgid "campaignBonus|Medal of Honor" msgstr "Huân chương Danh dá»±" msgid "campaignBonus|Medal of Valor" msgstr "Huân chương Can đảm" msgid "campaignBonus|Minor Scroll" msgstr "Phép Nhá»" msgid "campaignBonus|Nomad Boots" msgstr "Giầy Du mục" msgid "campaignBonus|Power Axe" msgstr "Rìu Sức mạnh" msgid "campaignBonus|Spiked Shield" msgstr "Khiên Gai" msgid "campaignBonus|Stealth Shield" msgstr "Khiên Tàng hình" msgid "campaignBonus|Tax Lien" msgstr "Giữ Thuế" msgid "campaignBonus|Thunder Mace" msgstr "Chùy Sấm sét" msgid "campaignBonus|Traveler's Boots" msgstr "Giầy Du khách" msgid "campaignBonus|White Pearl" msgstr "Ngá»c trai Trắng" msgid "campaignBonus|Basic Archery" msgstr "CÆ¡ bản Bắn cung" msgid "campaignBonus|Advanced Archery" msgstr "Nâng cao Bắn cung" msgid "campaignBonus|Expert Archery" msgstr "Chuyên gia Bắn cung" msgid "campaignBonus|Basic Ballistics" msgstr "CÆ¡ bản Máy bắn đá" msgid "campaignBonus|Advanced Ballistics" msgstr "Nâng cao Máy bắn đá" msgid "campaignBonus|Expert Ballistics" msgstr "Chuyên gia Máy bắn đá" msgid "campaignBonus|Basic Diplomacy" msgstr "CÆ¡ bản Ngoại giao" msgid "campaignBonus|Advanced Diplomacy" msgstr "Nâng cao Ngoại giao" msgid "campaignBonus|Expert Diplomacy" msgstr "Chuyên gia Ngoại giao" msgid "campaignBonus|Basic Eagle Eye" msgstr "CÆ¡ bản Mắt Äại bàng" msgid "campaignBonus|Advanced Eagle Eye" msgstr "Nâng cao Mắt Äại bàng" msgid "campaignBonus|Expert Eagle Eye" msgstr "Chuyên gia Mắt Äại bàng" msgid "campaignBonus|Basic Estates" msgstr "CÆ¡ bản Tài sàn" msgid "campaignBonus|Advanced Estates" msgstr "Nâng cao Tài sản" msgid "campaignBonus|Expert Estates" msgstr "Chuyên gia Tài sản" msgid "campaignBonus|Basic Leadership" msgstr "CÆ¡ bản Lãnh đạo" msgid "campaignBonus|Advanced Leadership" msgstr "Nâng cao Lãnh đạo" msgid "campaignBonus|Expert Leadership" msgstr "Chuyên gia Lãnh đạo" msgid "campaignBonus|Basic Logistics" msgstr "CÆ¡ bản Vận chuyển" msgid "campaignBonus|Advanced Logistics" msgstr "Nâng cao Vận chuyển" msgid "campaignBonus|Expert Logistics" msgstr "Chuyên gia Vận chuyển" msgid "campaignBonus|Basic Luck" msgstr "CÆ¡ bản May mắn" msgid "campaignBonus|Advanced Luck" msgstr "Nâng cao May mắn" msgid "campaignBonus|Expert Luck" msgstr "Chuyên gia May mắn" msgid "campaignBonus|Basic Mysticism" msgstr "CÆ¡ bản Thần bí" msgid "campaignBonus|Advanced Mysticism" msgstr "Nâng cao Thần bí" msgid "campaignBonus|Expert Mysticism" msgstr "Chuyên gia Thần bí" msgid "campaignBonus|Basic Navigation" msgstr "CÆ¡ bản Dẫn đưá»ng" msgid "campaignBonus|Advanced Navigation" msgstr "Nâng cao Dẫn đưá»ng" msgid "campaignBonus|Expert Navigation" msgstr "Chuyên gia Dẫn đưá»ng" msgid "campaignBonus|Basic Necromancy" msgstr "CÆ¡ bản Chiêu hồn" msgid "campaignBonus|Advanced Necromancy" msgstr "Nâng cao Chiêu hồn" msgid "campaignBonus|Expert Necromancy" msgstr "Chuyên gia Chiêu hồn" msgid "campaignBonus|Basic Pathfinding" msgstr "CÆ¡ bản Tìm đưá»ng" msgid "campaignBonus|Advanced Pathfinding" msgstr "Nâng cao Tìm đưá»ng" msgid "campaignBonus|Expert Pathfinding" msgstr "Chuyên gia Dẫn đưá»ng" msgid "campaignBonus|Basic Scouting" msgstr "CÆ¡ bản Do thám" msgid "campaignBonus|Advanced Scouting" msgstr "Nâng cao Do thám" msgid "campaignBonus|Expert Scouting" msgstr "Chuyên gia Do thám" msgid "campaignBonus|Basic Wisdom" msgstr "CÆ¡ bản Thông thái" msgid "campaignBonus|Advanced Wisdom" msgstr "Nâng cao Thông thái" msgid "campaignBonus|Expert Wisdom" msgstr "Chuyên gia Thông thái" msgid "" "The main hero will have \"%{artifact}\" artifact at the start of the " "scenario." msgstr "Tướng chính sẽ có bảo vật \"%{artifact}\" khi bắt đầu kịch bản." msgid "" "The kingdom will receive %{amount} additional %{resource} at the start of " "the scenario." msgstr "" "Vương quốc sẽ nhận được thêm má»™t khoản %{amount} tài nguyên %{resource} khi " "bắt đầu kịch bản." msgid "" "The kingdom will have %{amount} less %{resource} at the start of the " "scenario." msgstr "" "Vương quốc sẽ bị ít Ä‘i má»™t khoản %{amount} tài nguyên %{resource} khi bắt " "đầu kịch bản." msgid "" "The main hero will have %{count} %{monster} at the start of the scenario." msgstr "Tướng chính sẽ nhận %{count} quái %{monster} khi bắt đầu kịch bản." msgid "" "The main hero will have \"%{spell}\" spell at the start of the scenario." msgstr "Tướng chính sẽ có phép \"%{spell}\" khi bắt đầu kịch bản." msgid "The starting race of the scenario will be %{race}." msgstr "Chá»§ng tá»™c cá»§a kịch bản sẽ là %{race}." msgid "" "The main hero will have additional %{count} %{skill} at the start of the " "scenario." msgstr "Tướng chính sẽ có thêm %{count} kỹ năng %{skill} khi bắt đầu kịch bản." msgid "The main hero will have %{skill} at the start of the scenario." msgstr "Tướng chính sẽ có kỹ năng %{skill} khi bắt đầu kịch bản." msgid "Roland" msgstr "Roland" msgid "Archibald" msgstr "Archibald" #, fuzzy msgid "Price of Loyalty" msgstr "Giá cá»§a Lòng Trung Thành" msgid "Voyage Home" msgstr "Trở vá» Nhà" msgid "Wizard's Isle" msgstr "Äảo phù thá»§y" msgid "Descendants" msgstr "Huậ duệ" msgid "The %{building} produces %{monster}." msgstr "%{building} sinh ra %{monster}." msgid "Requires:" msgstr "Yêu cầu:" msgid "Exit this menu without doing anything." msgstr "Thoát khá»i trình đơn này và không làm gì cả." msgid "Cannot build. You have already built here today." msgstr "Không thể xây dá»±ng. Ngươi đã xây dá»±ng ở đây ngày hôm nay." msgid "For this action it is necessary to build a castle first." msgstr "Äối vá»›i hành động này, trước tiên cần phải xây dá»±ng má»™t lâu đài." msgid "Cannot build %{name} because the castle is too far away from an ocean." msgstr "Không thể xây %{name} vì lâu đài quá xa má»™t đại dương." msgid "disable build." msgstr "xây dá»±ng bị vô hiệu." msgid "Cannot afford %{name}." msgstr "Không đủ khả năng %{name}." msgid "%{name} is already built." msgstr "%{name} đã được xây." msgid "Cannot build %{name}." msgstr "Không thể xây %{name}." msgid "Build %{name}." msgstr "Xây %{name}." msgid "Blackridge" msgstr "Blackridge" msgid "Hillstone" msgstr "Hillstone" msgid "Pinehurst" msgstr "Pinehurst" msgid "Whiteshield" msgstr "Whiteshield" msgid "Woodhaven" msgstr "Woodhaven" msgid "Blackwind" msgstr "Blackwind" msgid "Bloodreign" msgstr "Bloodreign" msgid "Dragontooth" msgstr "Dragontooth" msgid "Greywind" msgstr "Greywind" msgid "Portsmith" msgstr "Portsmith" msgid "Atlantium" msgstr "Atlantium" msgid "Middle Gate" msgstr "Middle Gate" msgid "Sansobar" msgstr "Sansobar" msgid "Tundara" msgstr "Tundara" msgid "Vulcania" msgstr "Vulcania" msgid "Baywatch" msgstr "Baywatch" msgid "Fountainhead" msgstr "Fountainhead" msgid "Vertigo" msgstr "Vertigo" msgid "Wildabar" msgstr "Wildabar" msgid "Winterkill" msgstr "Winterkill" msgid "Brindamoor" msgstr "Brindamoor" msgid "Lakeside" msgstr "Lakeside" msgid "Nightshadow" msgstr "Nightshadow" msgid "Olympus" msgstr "Olympus" msgid "Sandcaster" msgstr "Sandcaster" msgid "Alamar" msgstr "Alamar" msgid "Burlock" msgstr "Burlock" msgid "Dragadune" msgstr "Dragadune" msgid "Kalindra" msgstr "Kalindra" msgid "Xabran" msgstr "Xabran" msgid "Algary" msgstr "Algary" msgid "Basenji" msgstr "Basenji" msgid "Blackfang" msgstr "Blackfang" msgid "New Dawn" msgstr "New Dawn" msgid "Sorpigal" msgstr "Sorpigal" msgid "Avone" msgstr "Avone" msgid "Big Oak" msgstr "Big Oak" msgid "Chandler" msgstr "Chandler" msgid "Erliquin" msgstr "Erliquin" msgid "Hampshire" msgstr "Hampshire" msgid "Antioch" msgstr "Antioch" msgid "Avalon" msgstr "Avalon" msgid "Roc Haven" msgstr "Roc Haven" msgid "South Mill" msgstr "South Mill" msgid "Weed Patch" msgstr "Weed Patch" msgid "Brownston" msgstr "Brownston" msgid "Hilltop" msgstr "Hilltop" msgid "Weddington" msgstr "Weddington" msgid "Westfork" msgstr "Westfork" msgid "Whittingham" msgstr "Whittingham" msgid "Cathcart" msgstr "Cathcart" msgid "Elk's Head" msgstr "Elk's Head" msgid "Roscomon" msgstr "Roscomon" msgid "Sherman" msgstr "Sherman" msgid "Yorksford" msgstr "Yorksford" msgid "Blackburn" msgstr "Blackburn" msgid "Blacksford" msgstr "Blacksford" msgid "Burton" msgstr "Burton" msgid "Pig's Eye" msgstr "Pig's Eye" msgid "Viper's Nest" msgstr "Viper's Nest" msgid "Fenton" msgstr "Fenton" msgid "Lankershire" msgstr "Lankershire" msgid "Lombard" msgstr "Lombard" msgid "Timberhill" msgstr "Timberhill" msgid "Troy" msgstr "Troy" msgid "Forder Oaks" msgstr "Forder Oaks" msgid "Meramec" msgstr "Meramec" msgid "Quick Silver" msgstr "Quick Silver" msgid "Westmoor" msgstr "Westmoor" msgid "Willow" msgstr "Willow" msgid "Corackston" msgstr "Corackston" msgid "Sheltemburg" msgstr "Sheltemburg" msgid "Cannot recruit - you already have a Hero in this town." msgstr "Không thể chiêu má»™ - ngươi Ä‘ang có má»™t Tướng trong thị trấn." msgid "Cannot recruit - you have too many Heroes." msgstr "Không thể chiêu má»™ - ngươi Ä‘ang có quá nhiá»u Tướng." msgid "Cannot afford a Hero" msgstr "Không thể có được má»™t Tướng" msgid "There is no room in the garrison for this army." msgstr "Không có chá»— trong đồn trú cho đội quân này." msgid "Fortifications" msgstr "Công sá»±" msgid "Farm" msgstr "Ruá»™ng" msgid "Thatched Hut" msgstr "Lá»u Tranh" msgid "Archery Range" msgstr "Trưá»ng Bắn cung" msgid "Upg. Archery Range" msgstr "NC. Trưá»ng Bắn cung" msgid "Blacksmith" msgstr "Lò rèn" msgid "Upg. Blacksmith" msgstr "NC. Lò rèn" msgid "Armory" msgstr "Kho vÅ© khí" msgid "Upg. Armory" msgstr "NC. Kho vÅ© khí" msgid "Jousting Arena" msgstr "Trưá»ng Ä‘ua Ngá»±a" msgid "Upg. Jousting Arena" msgstr "NC. Trưá»ng Ä‘ua Ngá»±a" msgid "Cathedral" msgstr "Thánh đưá»ng" msgid "Upg. Cathedral" msgstr "NC. Thánh đưá»ng" msgid "Coliseum" msgstr "Äấu trưá»ng" msgid "Garbage Heap" msgstr "Äống Rác" msgid "Hut" msgstr "Lá»u cá»" msgid "Stick Hut" msgstr "Lá»u Cây" msgid "Upg. Stick Hut" msgstr "NC. Lá»u Cây" msgid "Den" msgstr "Hốc" msgid "Adobe" msgstr "Nhà đất" msgid "Upg. Adobe" msgstr "NC. Nhà đất" msgid "Bridge" msgstr "Cầu" msgid "Upg. Bridge" msgstr "NC. Cầu" msgid "Pyramid" msgstr "Kim tá»± tháp" msgid "Rainbow" msgstr "Cầu vồng" msgid "Crystal Garden" msgstr "Vưá»n Pha lê" msgid "Treehouse" msgstr "Nhà cây" msgid "Cottage" msgstr "Nhà tranh" msgid "Upg. Cottage" msgstr "NC. Nhà tranh" msgid "Stonehenge" msgstr "Bãi đá" msgid "Upg. Stonehenge" msgstr "NC. Bãi đá" msgid "Fenced Meadow" msgstr "Rào chắn Äồng cá»" msgid "sorceress|Red Tower" msgstr "Tháp Äá»" msgid "Dungeon" msgstr "Ngục tối" msgid "Waterfall" msgstr "Thác nước" msgid "Cave" msgstr "Hang" msgid "Crypt" msgstr "Nhà mồ" msgid "Nest" msgstr "Tổ" msgid "Maze" msgstr "Mê cung" msgid "Upg. Maze" msgstr "NC. Mê cung" msgid "Swamp" msgstr "Äầm lầy" msgid "Green Tower" msgstr "Tháp Xanh" msgid "warlock|Red Tower" msgstr "Tháp Äá»" msgid "Black Tower" msgstr "Tháp Äen" msgid "Library" msgstr "Thư viện" msgid "Orchard" msgstr "Vưá»n quả" msgid "Habitat" msgstr "Cư trú" msgid "Pen" msgstr "Chuồng trại" msgid "Foundry" msgstr "Xưởng đúc" msgid "Upg. Foundry" msgstr "NC. Xưởng đúc" msgid "Cliff Nest" msgstr "Tổ Chim" msgid "Ivory Tower" msgstr "Tháp Ngà" msgid "Upg. Ivory Tower" msgstr "NC. Tháp Ngà" msgid "Cloud Castle" msgstr "Lâu đài Mây" msgid "Upg. Cloud Castle" msgstr "NC. Lâu đài Mây" msgid "Storm" msgstr "Bão" msgid "Skull Pile" msgstr "Äống Äầu lâu" msgid "Excavation" msgstr "Huyệt" msgid "Graveyard" msgstr "NghÄ©a địa" msgid "Upg. Graveyard" msgstr "NC. NghÄ©a địa" msgid "Upg. Pyramid" msgstr "NC. Kim tá»± tháp" msgid "Mansion" msgstr "Biệt thá»±" msgid "Upg. Mansion" msgstr "NC. Biệt thá»±" msgid "Mausoleum" msgstr "Lăng mộ" msgid "Upg. Mausoleum" msgstr "NC. Lăng mộ" msgid "Laboratory" msgstr "Phòng thí nghiệm" msgid "Shrine" msgstr "Äá»n thá»" msgid "" "The Fortifications increase the toughness of the walls, increasing the " "number of turns it takes to knock them down." msgstr "" "Công sá»± tăng cưá»ng độ dẻo dai cá»§a các bức tưá»ng, tăng số lượt cần thiết để " "đánh sập chúng." msgid "The Farm increases production of Peasants by %{count} per week." msgstr "Ruá»™ng tăng sản lượng cá»§a Nông dân thêm %{count} má»—i tuần." msgid "" "The Coliseum provides inspiring spectacles to defending troops, raising " "their morale by two during combat." msgstr "" "Äấu trưá»ng La Mã cung cấp những cảnh tượng đầy cảm hứng cho quân phòng thá»§, " "nâng cao tinh thần cá»§a há» lên gấp đôi trong khi chiến đấu." msgid "The Garbage Heap increases production of Goblins by %{count} per week." msgstr "Äống rác tăng sản lượng Yêu tinh thêm %{count} má»—i tuần." msgid "The Rainbow increases the luck of the defending units by two." msgstr "Cầu vồng tăng gấp đôi may mắn cho các đơn vị phòng thá»§." msgid "" "The Crystal Garden increases production of Sprites by %{count} per week." msgstr "Vưá»n Pha lê tăng sản lượng Sprite thêm %{count} má»—i tuần." msgid "The Dungeon increases the income of the town by %{count} gold per day." msgstr "Hầm ngục tăng thu nhập cá»§a thị trấn thêm %{count} vàng má»—i ngày." msgid "The Waterfall increases production of Centaurs by %{count} per week." msgstr "Thác nước tăng sản lượng Nhân mã thêm %{count} má»—i tuần." msgid "" "The Library increases the number of spells in the Guild by one for each " "level of the guild." msgstr "" "Thư viện tăng số lượng phép thuật trong Bang há»™i lên má»™t cho má»—i cấp độ cá»§a " "Bang há»™i." msgid "The Orchard increases production of Halflings by %{count} per week." msgstr "Vưá»n quả tăng sản lượng Halflings thêm %{count} má»—i tuần." msgid "The Storm adds +2 to the power of spells of a defending spell caster." msgstr "" "Storm tăng thêm +2 vào sức mạnh phép thuật cá»§a ngưá»i sá»­ dụng phép thuật " "phòng thá»§." msgid "The Skull Pile increases production of Skeletons by %{count} per week." msgstr "Äống đầu lâu tăng sản lượng Xương thêm %{count} má»—i tuần." msgid "Thieves' Guild" msgstr "Băng Cướp" msgid "Tavern" msgstr "Tá»­u quán" msgid "Shipyard" msgstr "Xưởng tàu" msgid "Well" msgstr "Giếng" msgid "Statue" msgstr "Tượng" msgid "Marketplace" msgstr "Chợ" msgid "Moat" msgstr "Hào" msgid "Castle" msgstr "Lâu đài" msgid "Tent" msgstr "Lá»u" msgid "Captain's Quarters" msgstr "Khu Äá»™i trưởng" msgid "Mage Guild, Level 1" msgstr "Há»™i pháp sư, L1" msgid "Mage Guild, Level 2" msgstr "Há»™i pháp sư, L2" msgid "Mage Guild, Level 3" msgstr "Há»™i pháp sư, L3" msgid "Mage Guild, Level 4" msgstr "Há»™i pháp sư, L4" msgid "Mage Guild, Level 5" msgstr "Há»™i pháp sư, L5" msgid "" "The Shrine increases the necromancy skill of all your necromancers by 10 " "percent." msgstr "" "Äá»n thá» tăng 10% kỹ năng chiêu hồn cá»§a tất cả các pháp sư chiêu hồn cá»§a " "ngươi." msgid "" "The Thieves' Guild provides information on enemy players. Thieves' Guilds " "can also provide scouting information on enemy towns. Additional Guilds " "provide more information." msgstr "" "Băng Cướp cung cấp thông tin vá» ngưá»i chÆ¡i cá»§a kẻ thù. Băng Cướp cÅ©ng có thể " "cung cấp thông tin trinh sát vá» các thị trấn cá»§a kẻ thù. Các Bang há»™i bổ " "sung cung cấp thêm thông tin." msgid "The Tavern increases morale for troops defending the castle." msgstr "Tá»­u quán nâng cao tinh thần cho binh lính bảo vệ lâu đài." msgid "The Shipyard allows ships to be built." msgstr "Xưởng đóng tàu cho phép đóng tàu." msgid "" "The Well increases the growth rate of all dwellings by %{count} creatures " "per week." msgstr "" "Giếng tăng tốc độ phát triển cá»§a tất cả các ngôi nhà thêm %{count} sinh vật " "má»—i tuần." #, fuzzy msgid "The Statue increases the town's income by %{count} gold per day." msgstr "" "Tượng tăng thu nhập cho thị trấn cá»§a ngươi thêm %{count} vàng má»—i ngày." msgid "The Left Turret provides extra firepower during castle combat." msgstr "Tháp pháo Trái cung cấp thêm há»a lá»±c khi chiến đấu trong lâu đài." msgid "The Right Turret provides extra firepower during castle combat." msgstr "Tháp pháo Phải cung cấp thêm há»a lá»±c khi chiến đấu trong lâu đài." msgid "" "The Marketplace can be used to convert one type of resource into another. " "The more marketplaces you control, the better the exchange rate." msgstr "" "Chợ có thể được sá»­ dụng để chuyển đổi má»™t loại tài nguyên thành má»™t loại tài " "nguyên khác. Ngươi càng kiểm soát nhiá»u chợ, tá»· giá hối Ä‘oái càng tốt." msgid "" "The Moat slows attacking units. Any unit entering the moat must end its turn " "there and becomes more vulnerable to attack." msgstr "" "Hào làm chậm các đơn vị tấn công. Bất kỳ đơn vị nào Ä‘i vào hào Ä‘á»u phải kết " "thúc lượt cá»§a mình ở đó và trở nên dá»… bị tấn công hÆ¡n." #, fuzzy msgid "" "The Castle improves the town's defense and increases its income to %{count} " "gold per day." msgstr "" "Lâu đài cải thiện khả năng phòng thá»§ cá»§a thị trấn và tăng thu nhập lên " "%{count} vàng má»—i ngày." msgid "" "The Tent provides workers to build a castle, provided the materials and the " "gold are available." msgstr "" "Lá»u cung cấp công nhân để xây dá»±ng lâu đài, miá»…n là có sẵn nguyên liệu và " "vàng." msgid "" "The Captain's Quarters provides a captain to assist in the castle's defense " "when no hero is present." msgstr "" "Khu Äá»™i trưởng cung cấp má»™t đội trưởng để há»— trợ việc bảo vệ lâu đài khi " "không có tướng nào." msgid "" "The Mage Guild allows heroes to learn spells and replenish their spell " "points." msgstr "" "Há»™i Pháp sư cho phép các tướng há»c phép thuật và bổ sung Ä‘iểm phép thuật cá»§a " "há»." msgid "Recruit %{name}" msgstr "Chiêu má»™ %{name}" msgid "Month: %{month}, Week: %{week}, Day: %{day}" msgstr "Tháng: %{month}, Tuần: %{week}, Ngày: %{day}" msgid "" "You must purchase a spell book to use the mage guild, but you currently have " "no room for a spell book. Try giving one of your artifacts to another hero." msgstr "" "Ngươi phải mua má»™t cuốn sách thần chú để sá»­ dụng há»™i pháp sư, nhưng hiện tại " "ngươi không có chá»— cho má»™t cuốn sách thần chú. Hãy thá»­ đưa má»™t trong những " "cổ vật cá»§a ngươi cho má»™t anh tướng khác." msgid "Click to show next town." msgstr "Nhấp để xem thị trấn sau." msgid "Show next town" msgstr "Xem thị trấn sau" msgid "Click to show previous town." msgstr "Nhấp để xem thị trấn trước." msgid "Show previous town" msgstr "Xem thị trấn trước" msgid "This town may not be upgraded to a castle." msgstr "Thị trấn này có thể không được nâng cấp thành lâu đài." msgid "Town" msgstr "Thị trấn" msgid "Exit Castle" msgstr "Thoát khá»i Lâu đài" msgid "Exit Town" msgstr "Thoát khá»i Thị trấn" msgid "Show Income" msgstr "Xem Thu nhập" msgid "View Hero" msgstr "Xem Tướng" msgid "The above spells are available here." msgstr "Các phép thuật trên có sẵn ở đây." msgid "The spells the hero can learn have been added to their book." msgstr "Các phép mà tướng có thể há»c đã được thêm vào cuốn sách cá»§a há»." msgid "A generous tip for the barkeep yields the following rumor:" msgstr "" "Má»™t khoản tiá»n boa hào phóng cho ngưá»i chá»§ quán rượu mang lại tin đồn như " "sau:" msgid "Recruit Hero" msgstr "Chiêu má»™ Tướng" msgid "%{name} is a level %{value} %{race} " msgstr "%{name} ở cấp độ %{value} %{race} " msgid "with %{count} artifacts." msgstr "vá»›i %{count} bảo vật." msgid "with 1 artifact." msgstr "vá»›i 1 bảo vật." msgid "without artifacts." msgstr "không có bảo vật." msgid "Recruit %{name} the %{race}" msgstr "Chiêu má»™ %{name} cá»§a chá»§ng tá»™c %{race}" #, fuzzy msgid "" "'Spread' combat formation spreads the castle's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "Äá»™i hình chiến đấu 'Dàn trải' dàn quân cá»§a ngươi từ trên xuống dưới cá»§a " "chiến trưá»ng, vá»›i ít nhất má»™t khoảng trống giữa má»—i quân." #, fuzzy msgid "" "'Grouped' combat formation bunches the castle's units together in the center " "of the castle's side of the battlefield." msgstr "" "Äá»™i hình chiến đấu 'Tập trung' tập hợp quân đội cá»§a ngươi lại vá»›i nhau ở " "trung tâm cá»§a phe ngươi trên chiến trưá»ng." msgid "Spread Formation" msgstr "Äá»™i hình Dàn trải" msgid "Grouped Formation" msgstr "Äá»™i hình Tập trung" msgid "Set garrison combat formation to 'Spread'" msgstr "Äặt đội hình chiến đấu đồn trú thành 'Dàn trải'" msgid "Set garrison combat formation to 'Grouped'" msgstr "Äặt đội hình chiến đấu đồn trú thành 'Tập trung'" msgid "Exit Castle Options" msgstr "Thoát khá»i Lá»±a chá»n Lâu đài" msgid "Castle Options" msgstr "Lá»±a chá»n Lâu đài" msgid "Not enough resources to recruit creatures." msgstr "Không đủ tài nguyên để chiêu má»™ sinh vật." msgid "You are unable to recruit at this time, your ranks are full." msgstr "Ngươi không thể chiêu má»™ lúc này, cấp độ ngươi đã đầy." msgid "No creatures available for purchase." msgstr "Không có sinh vật nào sẵn có để mua." msgid "Recruit Creatures" msgstr "Chiêu má»™ Quái" msgid "Max" msgstr "Max" msgid "Hire all creatures in the town." msgstr "Thuê tất cả các sinh vật trong thị trấn." msgid "Available" msgstr "Có sẵn" msgid "Town Population Information and Statistics" msgstr "Thông tin và Thống kê Dân số Thị trấn" msgid "Damage" msgstr "Thiệt hại" msgid "HP" msgstr "Máu" msgid "Growth" msgstr "Sinh trưởng" msgid "week" msgstr "tuần" msgid "View World" msgstr "Xem Thế giá»›i" msgid "View the entire world." msgstr "Xem toàn bá»™ thế giá»›i." msgid "Puzzle" msgstr "Câu đố" msgid "View the obelisk puzzle." msgstr "Xem bản đồ câu đố." msgid "Scenario Information" msgstr "Thông tin Kịch bản" msgid "View information on the scenario you are currently playing." msgstr "Xem thông tin vá» kịch bản ngươi Ä‘ang chÆ¡i." msgid "Dig for the Ultimate Artifact." msgstr "Äào kiếm Bảo vật Tối thượng." msgid "Digging" msgstr "Äang đào" msgid "Arena" msgstr "Äấu trưá»ng" msgid "" "You enter the arena and face a pack of vicious lions. You handily defeat " "them, to the wild cheers of the crowd. Impressed by your skill, the aged " "trainer of gladiators agrees to train you in a skill of your choice." msgstr "" "Ngươi bước vào đấu trưá»ng và đối mặt vá»›i má»™t bầy sư tá»­ hung ác. Ngươi dá»… " "dàng đánh bại há» trước sá»± cổ vÅ© cuồng nhiệt cá»§a đám đông. Ấn tượng vá»›i kỹ " "năng cá»§a ngươi, ngưá»i huấn luyện đấu sÄ© lâu năm đồng ý huấn luyện cho ngươi " "má»™t kỹ năng do ngươi lá»±a chá»n." msgid "Attack Skill" msgstr "Kỹ năng Tấn công" msgid "Defense Skill" msgstr "Kỹ năng Phòng thá»§" msgid "Shots" msgstr "Bắn" msgid "Shots Left" msgstr "Còn Bắn" msgid "Hit Points" msgstr "Máu" msgid "Hit Points Left" msgstr "Còn Máu" msgid "You can't afford to upgrade your troops!" msgstr "Ngươi không đủ khả năng để nâng cấp quân đội cá»§a mình!" msgid "" "Your troops can be upgraded, but it will cost you dearly. Do you wish to " "upgrade them?" msgstr "" "Quân đội cá»§a ngươi có thể được nâng cấp, nhưng ngươi sẽ phải trả giá đắt. " "Ngươi có muốn nâng cấp chúng?" msgid "Are you sure you want to dismiss this army?" msgstr "Ngươi có chắc chắn muốn giải tán đội quân này không?" msgid "Upgrade" msgstr "Nâng cấp" msgid "Upgrade your troops." msgstr "Nâng cấp quân đội cá»§a ngươi." msgid "Dismiss" msgstr "Giải tán" msgid "Dismiss this army." msgstr "Giải tán đội quân này." msgid "" "A group of %{monster} with a desire for greater glory wish to join you.\n" "Do you accept?" msgstr "" "Má»™t nhóm %{monster} khao khát đạt được vinh quang lá»›n hÆ¡n mong muốn được " "tham gia cùng ngươi.\n" "Ngươi có chấp nhận không?" msgid "Followers" msgstr "" msgid "" "The %{monster} is swayed by your diplomatic tongue, and offers to join your " "army for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "%{monster} bị dao động bởi cái lưỡi ngoại giao cá»§a ngươi, và đỠnghị gia " "nhập quân đội cá»§a ngươi vá»›i số tiá»n %{gold} vàng.\n" "Ngươi có chấp nhận không?" msgid "" "The creatures are swayed by your diplomatic\n" "tongue, and make you an offer:\n" "\n" msgstr "" "Các sinh vật bị ảnh hưởng bởi cái lưỡi ngoại giao\n" "cá»§a ngươi, và đưa ra lá»i đỠnghị cho ngươi:\n" "\n" msgid "" "%{offer} of the %{total} %{monster} will join your army, and the rest will " "leave you alone, for the sum of %{gold} gold.\n" "Do you accept?" msgstr "" "%{offer} cá»§a %{total} %{monster} sẽ gia nhập quân đội ngươi, và phần còn lại " "sẽ để ngươi yên, vá»›i tổng số là %{gold} vàng.\n" "Ngươi có chấp thuận không?" msgid "" "All %{offer} of the %{monster} will join your army for the sum of %{gold} " "gold.\n" "Do you accept?" msgstr "" "Tất cả %{offer} cá»§a %{monster} sẽ gia nhập quân đội cá»§a ngươi vá»›i giá là " "%{gold} vàng.\n" "Ngươi có chấp thuận không?" msgid "(Rate: %{percent})" msgstr "(Mức: %{percent})" msgid "off" msgstr "tắt" msgid "Music" msgstr "Âm nhạc" msgid "Effects" msgstr "Hiệu ứng" msgid "MIDI" msgstr "MIDI" msgid "MIDI Expansion" msgstr "MIDI Mở rá»™ng" msgid "External" msgstr "Bên ngoài" msgid "Music Type" msgstr "Loại nhạc" msgid "3D Audio" msgstr "Âm thanh 3D" msgid "Toggle ambient music level." msgstr "Chuyển đổi mức âm nhạc xung quanh." msgid "Toggle foreground sounds level." msgstr "Chuyển đổi mức âm thanh ná»n trước." msgid "Change the type of music." msgstr "Thay đổi loại nhạc." msgid "Toggle 3D effects of foreground sounds." msgstr "Chuyển đổi hiệu ứng 3D cá»§a âm thanh ná»n trước." msgid "Build a new ship:" msgstr "Äóng má»™t tàu má»›i:" msgid "Resource cost:" msgstr "Chi phí tài nguyên:" msgid "Total: " msgstr "Tổng: " msgid "Need: " msgstr "Cần: " msgid "Load Game" msgstr "Tải Trò chÆ¡i" msgid "No save files to load." msgstr "Không có tệp lưu để tải." msgid "New Game" msgstr "Trò chÆ¡i Má»›i" msgid "Start a single or multi-player game." msgstr "Bắt đầu má»™t trò chÆ¡i đơn hoặc nhiá»u ngưá»i chÆ¡i." msgid "Load a previously saved game." msgstr "Tải má»™t trò chÆ¡i đã lưu trước đó." msgid "Save Game" msgstr "Lưu Trò chÆ¡i" msgid "Save the current game." msgstr "Lưu trò chÆ¡i hiện tại." msgid "Quit" msgstr "Thoát" msgid "Quit out of Heroes of Might and Magic II." msgstr "Thoát khá»i Heroes of Might and Magic II." msgid "Language" msgstr "Ngôn ngữ" msgid "Graphics" msgstr "Äồ há»a" msgid "Black & White" msgstr "Äen & Trắng" msgid "Mouse Cursor" msgstr "Con trá» Chuá»™t" msgid "Color" msgstr "Màu" msgid "Text Support" msgstr "Há»— trợ Văn bản" msgid "Change the language of the game." msgstr "Thay đổi ngôn ngữ cá»§a trò chÆ¡i." msgid "Select Game Language" msgstr "Chá»n Ngôn ngữ Trò chÆ¡i" msgid "Change the graphics settings of the game." msgstr "Thay đổi đồ há»a cá»§a trò chÆ¡i." msgid "Toggle colored cursor on or off. This is only an aesthetic choice." msgstr "" "Chuyển đổi con trá» màu bật hoặc tắt. Äây chỉ là má»™t sá»± lá»±a chá»n thẩm mỹ." msgid "" "Toggle text support mode to output extra information about windows and " "events in the game." msgstr "" "Chuyển đổi chế độ há»— trợ văn bản để xuất thêm thông tin vá» các cá»­a sổ và sá»± " "kiện trong trò chÆ¡i." msgid "" "Map\n" "Difficulty" msgstr "" "Äá»™ khó\n" "Bản đồ" msgid "" "Game\n" "Difficulty" msgstr "" "Äá»™ khó\n" "Trò chÆ¡i" msgid "Rating" msgstr "Xếp hạng" msgid "Map Size" msgstr "Cỡ Bản đồ" msgid "Opponents" msgstr "Äối thá»§" msgid "Class" msgstr "Chá»§ng tá»™c" msgid "" "Victory\n" "Conditions" msgstr "" "Äiá»u kiện\n" "Chiến thắng" msgid "" "Loss\n" "Conditions" msgstr "" "Äiá»u kiện\n" "Thua" msgid "First select recipients!" msgstr "Äầu tiên chá»n ngưá»i nhận!" msgid "You cannot select %{resource}!" msgstr "Ngươi không thể chá»n %{resource}!" msgid "Select count %{resource}:" msgstr "Chá»n số lượng %{resource}:" msgid "Select Recipients" msgstr "Chá»n Ngưá»i nhận" msgid "Your Funds" msgstr "Ngân quỹ cá»§a Ngươi" msgid "Planned Gift" msgstr "Dá»± tính Quà tặng" msgid "Gift from %{name}" msgstr "Quà từ %{name}" msgid "Resolution" msgstr "Phân giải" msgid "Fullscreen" msgstr "Toàn màn hình" msgid "window|Mode" msgstr "Chế độ" msgid "Windowed" msgstr "Cá»­a sổ" msgid "V-Sync" msgstr "V-Sync" msgid "on" msgstr "bật" msgid "FPS" msgstr "FPS" msgid "System Info" msgstr "Thông tin Hệ thống" msgid "Change the resolution of the game." msgstr "Thay đổi độ phân giải cá»§a trò chÆ¡i." msgid "Select Game Resolution" msgstr "Chá»n Äá»™ phân giải Trò chÆ¡i" msgid "Toggle between fullscreen and windowed modes." msgstr "Chuyển đổi giữa chế độ toàn màn hình và cá»­a sổ." msgid "" "The V-Sync option can be enabled to resolve flickering issues on some " "monitors." msgstr "" "Tùy chá»n V-Sync có thể được bật để giải quyết vấn đỠnhấp nháy trên má»™t số " "màn hình." msgid "Show extra information such as FPS and current time." msgstr "Hiển thị thêm thông tin như FPS và thá»i gian hiện tại." msgid "Hotkey: " msgstr "Phím nóng: " msgid "Category: " msgstr "Danh mục: " msgid "Event: " msgstr "Sá»± kiện: " msgid "Hot Keys:" msgstr "Phím tắt:" msgid "Evil" msgstr "Xấu" msgid "Good" msgstr "Tốt" msgid "Interface Type" msgstr "Kiểu Giao diện" msgid "Hide" msgstr "Ẩn" msgid "Show" msgstr "Hiện" msgid "Interface" msgstr "Giao diện" msgid "Slow" msgstr "Chậm" msgid "Normal" msgstr "Thưá»ng" msgid "Fast" msgstr "Nhanh" msgid "Very Fast" msgstr "Rất nhanh" msgid "Scroll Speed" msgstr "Tốc độ Cuá»™n" msgid "Toggle the type of interface you want to use." msgstr "Chuyển đổi loại giao diện ngươi muốn sá»­ dụng." msgid "Toggle interface visibility." msgstr "Chuyển đổi khả năng hiển thị giao diện." msgid "Toggle colored cursor on or off. This is only an esthetic choice." msgstr "Bật hoặc tắt con trá» màu. Äây chỉ là má»™t sá»± lá»±a chá»n thẩm mỹ." msgid "Sets the speed at which you scroll the window." msgstr "Äặt tốc độ cuá»™n cá»­a sổ." msgid "Select Game Language:" msgstr "Chá»n Ngôn ngữ Trò chÆ¡i:" msgid "Click to choose the selected language." msgstr "Bấm để chá»n ngôn ngữ đã chá»n." msgid "%{name} has gained a level." msgstr "%{name} đã đạt được má»™t mức độ." msgid "%{skill} +1" msgstr "%{skill} +1" msgid "You have learned %{skill}." msgstr "Ngươi đã há»c %{skill}." msgid "" "%{name} has gained a level.\n" "\n" "%{skill} +1" msgstr "" "%{name} đã đạt được má»™t mức độ.\n" "\n" "%{skill} +1" msgid "" "You may learn either:\n" "%{skill1}\n" "or\n" "%{skill2}" msgstr "" "Ngươi có thể há»c má»™t trong hai:\n" "%{skill1}\n" "hoặc\n" "%{skill2}" msgid "" "Please inspect our fine wares. If you feel like offering a trade, click on " "the items you wish to trade with and for." msgstr "" "Hãy kiểm tra đồ tốt cá»§a chúng tôi. Nếu ngươi muốn cung cấp má»™t giao dịch, " "hãy nhấp vào các mặt hàng ngươi muốn giao dịch." msgid "" "You have received quite a bargain. I expect to make no profit on the deal. " "Can I interest you in any of my other wares?" msgstr "" "Ngươi đã nhận được khá há»i. Ta hy vá»ng sẽ không kiếm được lợi nhuận từ thá»a " "thuận này. Ta có thể khiến ngươi quan tâm đến bất kỳ sản phẩm nào khác cá»§a " "ta?" msgid "I can offer you %{count} for 1 unit of %{resfrom}." msgstr "Ta có thế phục vụ ngươi %{count} cho 1 đơn vị cá»§a %{resfrom}." msgid "I can offer you 1 unit of %{resto} for %{count} units of %{resfrom}." msgstr "" "Ta có thể phục vụ ngươi má»™t đơn vị cá»§a %{resto} cho %{count} đơn vị cá»§a " "%{resfrom}." msgid "Min" msgstr "Min" msgid "Qty to trade" msgstr "Số lượng trao đổi" msgid "Trading Post" msgstr "Trạm Giao dịch" msgid "Your Resources" msgstr "Tài nguyên cá»§a Ngươi" msgid "Available Trades" msgstr "Giao dịch Có sẵn" msgid "n/a" msgstr "n/a" msgid "guarded by %{count} %{monster}" msgstr "bảo vệ bởi %{count} %{monster}" msgid "guarded by " msgstr "bảo vệ bởi " msgid "(available: %{count})" msgstr "(có sẵn: %{count})" msgid "(empty)" msgstr "(rá»—ng)" msgid "already learned" msgstr "đã há»c" #, fuzzy msgid "treeOfKnowledge|free" msgstr "Cây Tri thức" #, fuzzy msgid "already claimed" msgstr "đã há»c" msgid "not claimed" msgstr "" msgid "already knows this skill" msgstr "đã biết kỹ năng này" msgid "already has max skills" msgstr "kỹ năng này đã tối Ä‘a" msgid "(already visited)" msgstr "(đã tá»›i)" msgid "(not visited)" msgstr "(chưa tá»›i)" msgid "%{color} Barrier" msgstr "%{color} Rào chắn" msgid "(tent visited)" msgstr "(lá»u đã tá»›i)" msgid "%{color} Tent" msgstr "%{color} Lá»u" msgid "Road" msgstr "ÄÆ°á»ng" msgid "(digging ok)" msgstr "(đào được)" msgid "(no digging)" msgstr "(không đào được)" msgid "penalty: %{cost}" msgstr "phạt: %{cost}" msgid "Defenders:" msgstr "Bên thá»§:" msgid "Unknown" msgstr "Không biết" msgid "%{name} (Level %{level})" msgstr "%{name} (Cấp độ %{level})" msgid "Attack:" msgstr "Tấn công:" msgid "Defense:" msgstr "Phòng thá»§:" msgid "Spell Power:" msgstr "Sức mạnh Phép:" msgid "Knowledge:" msgstr "Trí thức:" msgid "Spell Points:" msgstr "Äiểm Phép thuật:" msgid "Move Points:" msgstr "Äiểm Di chuyển:" msgid "Uncharted Territory" msgstr "Lãnh thổ Chưa khám phá" msgid "Cost per troop:" msgstr "Giá má»—i quân:" msgid "Available: %{count}" msgstr "Hiện có: %{count}" msgid "Number to buy:" msgstr "Số lượng mua:" msgid "Recruit selected monsters." msgstr "Chiêu má»™ quái vật đã chá»n." msgid "Select maximum monsters to be recruited." msgstr "Chá»n tối Ä‘a quá vật để chiêu má»™." msgid "Select only 1 monster to be recruited." msgstr "Chỉ chá»n 1 quái vật để chiêu má»™." msgid "Select Game Resolution:" msgstr "Chá»n Äá»™ phân giải:" msgid "Click to apply the selected resolution." msgstr "Nhấp để áp dụng độ phân giải đã chá»n." msgid "Click to apply the entered text." msgstr "Nhấp để áp dụng văn bản đã nhập." msgid "Click to open the Virtual Keyboard dialog." msgstr "Nhấp để mở Bàn phím Ảo." msgid "Open Virtual Keyboard" msgstr "Mở Bàn phím Ảo" msgid "How many troops to move?" msgstr "Bao nhiêu quân sẽ được di chuyển?" msgid "Fast separation into slots:" msgstr "Chia nhanh vào các ô:" msgid "Map: " msgstr "Bản đồ: " msgid "" "\n" "\n" "Month: " msgstr "" "\n" "\n" "Tháng: " msgid ", Week: " msgstr ", Tuần: " msgid ", Day: " msgstr ", Ngày: " msgid "" "\n" "\n" "Location: " msgstr "" "\n" "\n" "Vị trí: " msgid "Click to save the current game." msgstr "Nhấp để lưu trò chÆ¡i hiện tại." msgid "Click to load a previously saved game." msgstr "Nhấp để tải chò trÆ¡i đã lưu trước đây." msgid "Are you sure you want to delete file:" msgstr "Ngươi có chắc muốn xóa file:" msgid "Warning!" msgstr "Cảnh báo!" msgid "File to Save:" msgstr "Tệp để Lưu:" msgid "File to Load:" msgstr "Tệp để Tải:" msgid "Accept the choice made." msgstr "Chấp nhận sá»± lá»±a chá»n được thá»±c hiện." msgid "%{color} %{race} hero" msgstr "%{color} %{race} tướng" msgid "Terrain object" msgstr "Äối tượng địa hình" msgid "Select Skill:" msgstr "Chá»n Kỹ năng:" msgid "Select Spell:" msgstr "Chá»n Phép:" msgid "Select Artifact:" msgstr "Chá»n Bảo vật:" msgid "Select Monster:" msgstr "Chá»n Quái vật:" msgid "Select Hero:" msgstr "Chá»n Tướng:" msgid "Select Treasure:" msgstr "Chá»n Kho báu:" msgid "Select Ocean Object:" msgstr "Chá»n Äối tượng Äại dương:" msgid "Castle/town placing" msgstr "Äặt Lâu đài/thị trấn" msgid "doubleLinedRace|Neutral" msgstr "Trung lập" msgid "race|Random" msgstr "Ngẫu nhiên" msgid "Click to start placing the selected castle/town." msgstr "Nhấp để bắt đầu đặt lâu đài/thị trấn đã chá»n" msgid "Click to select town placing." msgstr "Nhấp để chá»n thị trấn đặt" msgid "Click to select castle placing." msgstr "Nhấp để chá»n lâu đài đặt" msgid "%{color} %{race} %{townOrCastle}" msgstr "%{color} %{race} %{townOrCastle}" msgid "race|Neutral" msgstr "Trung lập" msgid "You will place" msgstr "Ngươi sẽ đặt" msgid "Click to select this class." msgstr "Nhấp để chá»n chá»§ng tá»™c." msgid "Click to select this color." msgstr "Nhấp để chá»n màu này." #, fuzzy msgid "Select Dwelling:" msgstr "Chá»n Phép:" #, fuzzy msgid "Select Landscape Object:" msgstr "Chá»n Äối tượng Äại dương:" msgid "Mine placing" msgstr "" #, fuzzy msgid "" "Resource\n" "type:" msgstr "Tài nguyên" #, fuzzy msgid "%{mineName} appearance:" msgstr "%{name} cái %{race}" #, fuzzy msgid "Click to select %{object} as the resource generator to be placed." msgstr "Nhấp vào đây để chá»n kịch bản để chÆ¡i." #, fuzzy msgid "Select Mountain Object:" msgstr "Chá»n Äối tượng Äại dương:" #, fuzzy msgid "Select Rock Object:" msgstr "Chá»n Äối tượng Äại dương:" #, fuzzy msgid "Select Tree Object:" msgstr "Chá»n Äối tượng Äại dương:" #, fuzzy msgid "Select Power Up Object:" msgstr "Chá»n Äối tượng Äại dương:" #, fuzzy msgid "Select Adventure Object:" msgstr "Chá»n Äối tượng Äại dương:" msgid "Map Type:\n" msgstr "Kiểu Bản đồ:\n" msgid "The Succession Wars" msgstr "Cuá»™c chiến Kế vị" msgid "The Price of Loyalty" msgstr "Giá cá»§a Lòng Trung Thành" msgid "Resurrection" msgstr "Hồi sinh" msgid "Lose all your heroes and towns." msgstr "Mất tất cả các tướng và thành." msgid "Lose a specific town." msgstr "Mất má»™t thành cụ thể." msgid "Lose a specific hero." msgstr "Mất má»™t tướng cụ thể." msgid "Run out of time. Fail to win by a certain point." msgstr "Hết thá»i gian. Không thể giành chiến thắng vá»›i thá»i gian nhất định." msgid "Loss Condition" msgstr "Äiá»u kiện Thua" msgid "Defeat all enemy heroes and towns." msgstr "Tiêu diệt tất cả các tướng và thành địch." msgid "Capture a specific town." msgstr "Chiếm má»™t thành cụ thể." msgid "Defeat a specific hero." msgstr "Tiêu diệt má»™t tướng cụ thể." msgid "Find a specific artifact." msgstr "Tìm má»™t bảo vật cụ thể." msgid "Your side defeats the opposing side." msgstr "Phe cá»§a ngươi đánh bại phe đối lập." msgid "Accumulate a large amount of gold." msgstr "Tích lÅ©y má»™t lượng lá»›n vàng." msgid "Victory Condition" msgstr "Äiá»u kiện Thắng" msgid "Map difficulty:" msgstr "Äá»™ khó bản đồ:" msgid "N" msgstr "N" msgid "No maps exist at that size" msgstr "Không có bản đồ nào ở cỡ này" msgid "Small Maps" msgstr "Bản đồ Nhá»" msgid "View only maps of size small (36 x 36)." msgstr "Chỉ xem những bản đồ cỡ nhá» (36x36)." msgid "Medium Maps" msgstr "Bản đồ Thưá»ng" msgid "View only maps of size medium (72 x 72)." msgstr "Chỉ xem những bản đồ cỡ thưá»ng (72x72)." msgid "Large Maps" msgstr "Bản đồ Rá»™ng" msgid "View only maps of size large (108 x 108)." msgstr "Chỉ xem những bản đồ cỡ lá»›n (108x108)." msgid "Extra Large Maps" msgstr "Bản đồ Siêu rá»™ng" msgid "View only maps of size extra large (144 x 144)." msgstr "Chỉ xem những bản đồ cỡ siêu lá»›n (144x144)." msgid "All Maps" msgstr "Tất cả Bản đồ" msgid "View all maps, regardless of size." msgstr "Xem tất cả các bản đồ, bất kể kích thước." msgid "Players Icon" msgstr "Biểu tượng Ngưá»i chÆ¡i" msgid "" "Indicates how many players total are in the scenario. Any positions not " "occupied by humans will be occupied by computer players." msgstr "" "Cho biết tổng số ngưá»i chÆ¡i trong kịch bản. Bất kỳ vị trí nào không có con " "ngưá»i chiếm giữ sẽ do ngưá»i chÆ¡i máy tính chiếm giữ." msgid "" "Indicates whether the map\n" "is small (36 x 36), medium\n" "(72 x 72), large (108 x 108),\n" "or extra large (144 x 144)." msgstr "" "Cho biết bản đồ\n" "là nhá» (36 x 36), thưá»ng\n" "(72 x 72), lá»›n (108 x 108),\n" "hay cá»±c lá»›n (144 x 144)." msgid "Size Icon" msgstr "Cỡ Biểu tượng" msgid "" "Indicates whether the map is made for \"The Succession Wars\" or \"The Price " "of Loyalty\" version of the game." msgstr "" "Cho biết bản đồ được tạo cho phiên bản \"Cuá»™c chiến kế vị\" hay \"Cái giá " "cá»§a lòng trung thành\" cá»§a trò chÆ¡i." msgid "Map Type" msgstr "Kiểu Bản đồ" msgid "Selected Name" msgstr "Chá»n Tên" msgid "The name of the currently selected map." msgstr "Tên cá»§a bản đồ hiện được chá»n." msgid "Selected Map Difficulty" msgstr "Chá»n Äá»™ khó cá»§a Bản đồ" msgid "" "The map difficulty of the currently selected map. The map difficulty is " "determined by the scenario designer. More difficult maps might include more " "or stronger enemies, fewer resources, or other special conditions making " "things tougher for the human player." msgstr "" "Äá»™ khó bản đồ cá»§a bản đồ hiện được chá»n. Äá»™ khó cá»§a bản đồ được xác định bởi " "ngưá»i thiết kế kịch bản. Các bản đồ khó hÆ¡n có thể bao gồm nhiá»u kẻ thù hÆ¡n " "hoặc mạnh hÆ¡n, ít tài nguyên hÆ¡n hoặc các Ä‘iá»u kiện đặc biệt khác khiến má»i " "thứ trở nên khó khăn hÆ¡n đối vá»›i ngưá»i chÆ¡i." msgid "Selected Description" msgstr "Chá»n Mô tả" msgid "The description of the currently selected map." msgstr "Mô tả cá»§a bản đồ hiện được chá»n." msgid "Jump" msgstr "Nhảy" msgid "Hero Speed" msgstr "Tốc độ Tướng" msgid "Don't Show" msgstr "Không Hiện" msgid "Enemy Speed" msgstr "Tốc độ Äịch" msgid "Auto Resolve" msgstr "Tá»± động Giải quyết" msgid "Auto, No Spells" msgstr "Tá»± động, Không Phép" msgid "Battles" msgstr "Chiến trưá»ng" msgid "autoBattle|Manual" msgstr "Thá»§ công" msgid "Change the speed at which your heroes move on the main screen." msgstr "Thay đổi tốc độ di chuyển cá»§a các tướng trên màn hình chính." msgid "" "Sets the speed that A.I. heroes move at. You can also elect not to view A.I. " "movement at all." msgstr "" "Äặt tốc độ mà tướng A.I. di chuyển. Ngươi cÅ©ng có thể chá»n không xem tướng A." "I. di chuyển." msgid "Change the interface settings of the game." msgstr "Thay đổi giao diện cá»§a trò chÆ¡i." msgid "Interface Settings" msgstr "Cài đặt Giao diện" msgid "Toggle instant battle mode." msgstr "Chuyển đổi chế độ chiến đấu ngay lập tức." msgid "Att." msgstr "Công." msgid "Def." msgstr "Thá»§." msgid "Power" msgstr "Sức" msgid "Knowl" msgstr "Trí" msgid "1st" msgstr "Số 1" msgid "2nd" msgstr "Số 2" msgid "3rd" msgstr "Số 3" msgid "4th" msgstr "Số 4" msgid "5th" msgstr "Số 5" msgid "6th" msgstr "Số 6" msgid "Oracle: Player Rankings" msgstr "Tiên tri: Thứ hạng Ngưá»i chÆ¡i" msgid "Thieves' Guild: Player Rankings" msgstr "Băng cướp: Xếp hạng Ngưá»i chÆ¡i" msgid "Number of Towns:" msgstr "Số Thị trấn:" msgid "Number of Castles:" msgstr "Số Lâu đài:" msgid "Number of Heroes:" msgstr "Số Tướng:" msgid "Gold in Treasury:" msgstr "Vàng trong Kho:" msgid "Wood & Ore:" msgstr "Gá»— & Äá:" msgid "Gems, Cr, Slf & Mer:" msgstr "Kim cương và các thứ khác:" msgid "Obelisks Found:" msgstr "Äài tưởng niệm Tìm được:" msgid "Artifacts:" msgstr "Bảo vật:" msgid "Total Army Strength:" msgstr "Tổng Sức mạnh Quân đội:" msgid "Income:" msgstr "Thu nhập:" msgid "Best Hero:" msgstr "Tướng Tốt nhất:" msgid "Best Hero Stats:" msgstr "Tình trạng Tướng Tốt nhất:" msgid "Personality:" msgstr "Nhân cách:" msgid "Best Monster:" msgstr "Quái vật Mạnh nhất:" msgid "Map filename" msgstr "Tệp bản đồ" msgid "" "Are you sure you want to load a new map? (Any unsaved changes to the current " "map will be lost.)" msgstr "" "Ngươi có chắc chắn muốn tải bản đồ má»›i không? (Má»i thay đổi chưa được lưu " "đối vá»›i bản đồ hiện tại sẽ bị mất.)" msgid "" "Are you sure you want to create a new map? (Any unsaved changes to the " "current map will be lost.)" msgstr "" "Ngươi có chắc chắn muốn tạo bản đồ má»›i không? (Má»i thay đổi chưa được lưu " "đối vá»›i bản đồ hiện tại sẽ bị mất.)" msgid "" "Create a new map, either from scratch or using the random map generator." msgstr "Tạo bản đồ má»›i, từ đầu hoặc sá»­ dụng trình tạo bản đồ ngẫu nhiên." msgid "New Map" msgstr "Bản đồ Má»›i" msgid "Load Map" msgstr "Tải Bản đồ" msgid "Load an existing map." msgstr "Tải má»™t bản đồ đã có." msgid "Save Map" msgstr "Lưu Bản đồ" msgid "Save the current map." msgstr "Lưu bản đồ hiện tại." msgid "Quit out of the map editor." msgstr "Thoát khá»i biên tập bản đồ." msgid "Objects cannot be placed outside the map." msgstr "Äối tượng không thể đặt ra ngoài bản đồ." msgid "Monsters cannot be placed on water." msgstr "Quái vật không thể đặt trên nước." msgid "Choose a tile which does not contain any objects." msgstr "Chá»n má»™t ô mà không chứa bất kỳ đối tượng nào." msgid "Treasures cannot be placed on water." msgstr "Kho báu không thể đặt trên nước." msgid "Heroes cannot be placed on water." msgstr "Tướng không thể đặt trên nước." msgid "" "A maximum of %{count} heroes of the same color can be placed on the map." msgstr "" msgid "Artifacts cannot be placed on water." msgstr "Bảo vật không thể đặt trên nước." #, fuzzy msgid "Mountains cannot be placed on water." msgstr "Quái vật không thể đặt trên nước." #, fuzzy msgid "Rocks cannot be placed on water." msgstr "Thị trấn không thể đặt trên nước." #, fuzzy msgid "Trees cannot be placed on water." msgstr "Kho báu không thể đặt trên nước." msgid "Ocean object must be placed on water." msgstr "Äối tượng đại dương phải đặt trên mặt nước." #, fuzzy msgid "Landscape objects cannot be placed on water." msgstr "Tướng không thể đặt trên nước." msgid "Towns cannot be placed on water." msgstr "Thị trấn không thể đặt trên nước." #, fuzzy msgid "Mines cannot be placed on water." msgstr "Quái vật không thể đặt trên nước." #, fuzzy msgid "Dwellings cannot be placed on water." msgstr "Thị trấn không thể đặt trên nước." #, fuzzy msgid "Power-ups cannot be placed on water." msgstr "Thị trấn không thể đặt trên nước." #, fuzzy msgid "Adventure objects cannot be placed on water." msgstr "Tướng không thể đặt trên nước." #, fuzzy msgid "Used to place %{object}." msgstr "Dùng để đặt má»™t tướng." #, fuzzy msgid "Select object type" msgstr "Chá»n Äối tượng Äại dương:" #, fuzzy msgid "" "Click here to\n" "select a monster." msgstr "Nhấp vào đây để chá»n kịch bản để chÆ¡i." #, fuzzy msgid "" "Click here to\n" "select another monster." msgstr "Hãy chá»n má»™t tướng khác." msgid "" "Cell\n" "Details" msgstr "" msgid "Roads" msgstr "ÄÆ°á»ng" msgid "Streams" msgstr "Suối" #, fuzzy msgid "Erase" msgstr "Chế độ Xóa" msgid "Mountains" msgstr "Núi" #, fuzzy msgid "Rocks" msgstr "Äá" msgid "Trees" msgstr "Cây" #, fuzzy msgid "Water Objects" msgstr "Äối tượng Äất hoang" #, fuzzy msgid "Miscellaneous" msgstr "Fineous" msgid "Artifacts" msgstr "Bảo vật" msgid "Dwellings" msgstr "" msgid "Mines" msgstr "Má»" #, fuzzy msgid "Power-ups" msgstr "Sức" msgid "Treasures" msgstr "Kho báu" msgid "Towns" msgstr "Thị trấn" msgid "Heroes" msgstr "Tướng" msgid "editorErasure|Terrain" msgstr "" msgid "editorErasure|Castles" msgstr "" msgid "editorErasure|Monsters" msgstr "" msgid "editorErasure|Heroes" msgstr "" msgid "editorErasure|Artifacts" msgstr "" msgid "editorErasure|Roads" msgstr "" msgid "editorErasure|Streams" msgstr "" msgid "editorErasure|Treasures" msgstr "" msgid "" "Draws terrain in\n" "%{size} by %{size} square increments." msgstr "" "Vẽ địa hình vá»›i nét\n" "%{size} bằng %{size} ô vuông." msgid "" "Erases objects in\n" "%{size} by %{size} square increments." msgstr "" "Xóa đối tượng vá»›i cỡ\n" "%{size} bằng %{size} ô vuông." msgid "Small Brush" msgstr "Nét Nhá»" msgid "Medium Brush" msgstr "Nét Thưá»ng" msgid "Large Brush" msgstr "Nét To" msgid "Area Fill" msgstr "Khu vá»±c Äiá»n vào" msgid "Used to click and drag for filling in large areas." msgstr "Nhấp và kéo để Ä‘iá»n vào các khu vá»±c rá»™ng lá»›n." msgid "Clear Area" msgstr "Khu vá»±c Xóa" msgid "Used to click and drag for clearing large areas." msgstr "Nhấp và kéo để xóa các khu vá»±c rá»™ng lá»›n." msgid "" "Costs %{rate} times normal movement for all heroes. (Pathfinding reduces or " "eliminates the penalty.)" msgstr "" "Tốn %{rate} lần di chuyển bình thưá»ng cho tất cả các tướng. (Tìm đưá»ng làm " "giảm hoặc loại bá» hình phạt.)" msgid "Traversable only by boat." msgstr "Chỉ có thể di chuyển bằng thuyá»n." msgid "No special modifiers." msgstr "Không có sá»­a đổi đặc biệt." msgid "Toggle the erasure of %{type} objects." msgstr "Chuyển đổi tính năng xóa đối tượng %{type}." msgid "" "Objects of this type will be deleted with the Erase tool. Left-click here to " "deselect this type. Press and hold this button to deselect all other object " "types." msgstr "" "Các đối tượng thuá»™c loại này sẽ bị xóa bằng công cụ Xóa. Nhấn chuá»™t trái vào " "đây để bá» chá»n loại này. Nhấn và giữ nút này để bá» chá»n tất cả các loại đối " "tượng khác." msgid "" "Objects of this type will NOT be deleted with the Erase tool. Left-click " "here to select this type. Press and hold this button to select all other " "object types." msgstr "" "Các đối tượng thuá»™c loại này sẽ KHÔNG bị xóa bằng công cụ Xóa. Nhấn chuá»™t " "trái vào đây để chá»n loại này. Nhấn và giữ nút này để chá»n tất cả các loại " "đối tượng khác." msgid "Terrain Mode" msgstr "Chế độ Äịa hình" msgid "Used to draw the underlying grass, dirt, water, etc. on the map." msgstr "Dùng để vẽ cá», bụi bẩn, nước, v.v. lên bản đồ." #, fuzzy msgid "Landscape Objects Mode" msgstr "Chế độ Vật thể" #, fuzzy msgid "" "Used to place landscape objects (mountains, rocks, trees, etc.) on the map." msgstr "Dùng để đặt các vật thể (núi, cây, kho báu, v.v.v) lên bản đồ." msgid "Detail Mode" msgstr "Chế độ Chi tiết" msgid "Used for special editing of monsters, heroes and towns." msgstr "Dùng để hiệu chỉnh đặc biệt cho quái vật, tướng và thị trấn." #, fuzzy msgid "Adventure Objects Mode" msgstr "Tùy chá»n Khám phá" #, fuzzy msgid "" "Used to place adventure objects (artifacts, dwellings, mines, treasures, " "etc.) on the map." msgstr "Dùng để đặt các vật thể (núi, cây, kho báu, v.v.v) lên bản đồ." #, fuzzy msgid "Kingdom Objects Mode" msgstr "Chế độ Vật thể" #, fuzzy msgid "Used to place kingdom objects (towns, castles and heroes) on the map." msgstr "Dùng để đặt các vật thể (núi, cây, kho báu, v.v.v) lên bản đồ." #, fuzzy msgid "Monsters Mode" msgstr "Quái vật" #, fuzzy msgid "Used to place monsters on the map." msgstr "" "Dùng để đặt\n" "má»™t nhóm quái vật." msgid "Allows you to draw streams by clicking and dragging." msgstr "Cho phép vẽ suối bằng cách nhấp và kéo." msgid "Stream Mode" msgstr "Chế độ Suối" msgid "Allows you to draw roads by clicking and dragging." msgstr "Cho phép vẽ đưá»ng bằng cách nhấp và kéo." msgid "Road Mode" msgstr "Chế độ ÄÆ°á»ng" msgid "Erase Mode" msgstr "Chế độ Xóa" msgid "Used to erase objects from the map." msgstr "Dùng để xóa vật thể trên bản đồ." msgid "Change between zoom and normal view." msgstr "Thay đổi giữa chế độ nhìn phóng to và chế độ thưá»ng." msgid "Magnify" msgstr "Phóng đại" msgid "Undo" msgstr "Hoàn tác" msgid "Undo your last action." msgstr "Hoàn tác thao tác cuối cùng." msgid "Create a new map either from scratch or using the random map generator." msgstr "Tạo bản đồ má»›i từ đầu hoặc sá»­ dụng trình tạo bản đồ ngẫu nhiên." msgid "Edit map title, description, and other general information." msgstr "Chỉnh sá»­a tiêu đỠbản đồ, mô tả và thông tin chung khác." msgid "Specifications" msgstr "Thông số kỹ thuật" msgid "File Options" msgstr "Lá»±a chá»n Tệp" msgid "" "Open the file options menu, where you can save or load maps, or quit out of " "the editor." msgstr "" "Mở menu tùy chá»n tệp, nÆ¡i ngươi có thể lưu hoặc tải bản đồ hoặc thoát khá»i " "trình chỉnh sá»­a." msgid "View the editor system options, which let you customize the editor." msgstr "" "Xem các tùy chá»n hệ thống soạn thảo, cho phép ngươi tùy chỉnh trình chỉnh " "sá»­a." msgid "Create a map that is %{size} squares wide and %{size} squares high." msgstr "Tạo bản đồ rá»™ng %{size} ô vuông và cao %{size} ô vuông." msgid "Cancel back to the New Map menu." msgstr "Há»§y quay lại menu Bản đồ Má»›i." msgid "Cancel back to the main menu." msgstr "Há»§y quay lại menu chính." msgid "From Scratch" msgstr "Từ Äầu" msgid "Start from scratch with a blank map." msgstr "Bắt đầu từ đầu vá»›i má»™t bản đổ trống." msgid "Create a randomly generated map." msgstr "Tạo má»™t bản đồ được tạo ngẫu nhiên." msgid "Random" msgstr "Ngẫu nhiên" msgid "Cancel back to the Map Editor main menu." msgstr "Há»§y quay lại menu chính cá»§a Biên tập Bản đồ." msgid "No maps available!" msgstr "Không có bản đồ nào!" msgid "Warning" msgstr "Cảnh báo" msgid "difficulty|Easy" msgstr "Dá»…" msgid "difficulty|Normal" msgstr "Thưá»ng" msgid "difficulty|Hard" msgstr "Khó" msgid "difficulty|Expert" msgstr "Chuyên gia" msgid "difficulty|Impossible" msgstr "Bất khả thi" msgid "and more..." msgstr "và hÆ¡n thế nữa..." msgid "Easy" msgstr "Dá»…" msgid "Hard" msgstr "Khó" msgid "Campaign Difficulty" msgstr "Äá»™ khó Chiến dịch" msgid "" "Choose this difficulty if you want to prefer game story over challenge. AI " "is weaker in comparison with normal difficulty." msgstr "" "Chá»n độ khó này nếu ngươi muốn thích câu chuyện trò chÆ¡i hÆ¡n thá»­ thách. AI " "yếu hÆ¡n so vá»›i độ khó bình thưá»ng." msgid "" "Choose this difficulty to enjoy the campaign as per the original design." msgstr "Chá»n độ khó này để tận hưởng chiến dịch theo thiết kế ban đầu." msgid "" "Choose this difficulty if you want challenge. AI is stronger in comparison " "with normal difficulty." msgstr "" "Chá»n độ khó này nếu ngươi muốn thá»­ thách. AI mạnh hÆ¡n so vá»›i độ khó bình " "thưá»ng." msgid "" "Congratulations!\n" "\n" "Days: %{days}\n" msgstr "" "Chúc mừng!\n" "\n" "Ngày: %{days}\n" msgid "" "\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" "\n" "Äá»™ khó: %{difficulty}\n" "\n" msgid "" "Score: %{score}\n" "\n" "Rating:\n" "%{rating}" msgstr "" "Äiểm: %{score}\n" "\n" "Bậc:\n" "%{rating}" msgid "Start the selected scenario." msgstr "Bắt đầu kịch bản đã chá»n." msgid "View Intro" msgstr "Xem Giá»›i thiệu" msgid "View Intro videos for the current state of the campaign." msgstr "Xem video Giá»›i thiệu để biết trạng thái hiện tại cá»§a chiến dịch." msgid "" "Select the campaign difficulty. This can be lowered at any point during the " "campaign." msgstr "" "Chá»n độ khó cá»§a chiến dịch. Äiá»u này có thể được hạ xuống bất cứ lúc nào " "trong chiến dịch." msgid "Restart the current scenario." msgstr "ChÆ¡i lại kịch bản hiện tại." msgid "Difficulty" msgstr "Äá»™ khó" msgid "" "You have changed to a lower difficulty for the campaign. You will not be " "able to revert this after this point. The high score will be calculated " "based solely on the new difficulty. Do you want to proceed?" msgstr "" "Ngươi đã thay đổi độ khó thấp hÆ¡n cho chiến dịch. Ngươi sẽ không thể hoàn " "nguyên Ä‘iá»u này sau thá»i Ä‘iểm này. Äiểm cao sẽ được tính chỉ dá»±a trên độ khó " "má»›i. Ngươi có muốn tiếp tục?" msgid "Are you sure you want to restart this scenario?" msgstr "Ngươi có chắc muốn chÆ¡i lại kịch bản?" msgid "Campaign Scenario loading failure" msgstr "Kịch bản Chiến dịch tải thất bại" msgid "Please make sure that campaign files are correct and present." msgstr "Hãy đảm bảo tệp chiến dịch là đúng và hiện hữu." msgid "Days spent" msgstr "Ngày đã qua" msgid "The number of days spent on this campaign." msgstr "Số ngày đã qua trong chiến dịch này." msgid "Project Coordination and Core Development" msgstr "Äiá»u phối Dá»± án và Phát triển Lõi" msgid "Development" msgstr "Phát triển" msgid "Visit us at " msgstr "Tá»›i thăm chúng tôi tại " msgid "QA and Support" msgstr "QA và Há»— trợ" msgid "Dev and Support" msgstr "Phát triển và Há»— trợ" msgid "Special Thanks to" msgstr "Lá»i cảm Æ¡n Chân thành tá»›i" msgid "and many other contributors!" msgstr "và nhiá»u những ngưá»i đóng góp khác!" msgid "and many-many other supporters!" msgstr "và nhiá»u - nhiá»u những ngưá»i há»— trợ khác!" msgid "Support us at" msgstr "Há»— trợ chúng tôi tại" msgid "local-donation-platform|https://www.patreon.com/fheroes2" msgstr "" msgid "Connect with us at" msgstr "Kết nối vá»›i chúng tôi tại" msgid "local-social-network|https://www.facebook.com/groups/fheroes2" msgstr "" msgid "Need help with the game?" msgstr "Cần sá»± giúp đỡ vá»›i trò chÆ¡i?" msgid "Original project before 0.7" msgstr "Dá»± án Gốc trước 0.7" msgid "Heroes of Might and Magic II: The Succession Wars team" msgstr "Heroes of Might and Magic II: The Succession Wars team" msgid "Designed and Directed" msgstr "Thiết kế và Chỉ đạo" msgid "Programming and Design" msgstr "Lập trình và Thiết kế" msgid "Executive Producer" msgstr "Äiá»u hành Sản xuất" msgid "Producer" msgstr "Sản xuất" msgid "Additional Design" msgstr "Thiết kế Bổ sung" msgid "Additional Programming" msgstr "Lập trình Bổ sung" msgid "Musical Production" msgstr "Sản xuất Âm nhạc" msgid "Music and Sound Design" msgstr "Thiết kế Nhạc và Âm" msgid "Vocalists" msgstr "Lồng tiếng" msgid "Art Director" msgstr "Chỉ đạo Nghệ thuật" msgid "Assistant Art Director" msgstr "Trợ lý Chỉ đạo Nghệ thuật" msgid "Artists" msgstr "Nghệ sỹ" msgid "QA Manager" msgstr "Quản lý QA" msgid "QA" msgstr "QA" msgid "Writing" msgstr "Kịch bản" msgid "Manual and Helpfile" msgstr "Hướng dẫn sá»­ dụng và Tệp trợ giúp" msgid "Scenarios" msgstr "Biên kịch" msgid "Heroes of Might and Magic II: The Price of Loyalty team" msgstr "Heroes of Might and Magic II: The Price of Loyalty team" msgid "Design Lead" msgstr "Thiết kế Trưởng" msgid "Designers" msgstr "Thiết kế" msgid "Programming Lead" msgstr "Trưởng Lập trình" msgid "Art Lead" msgstr "Trưởng Nghệ thuật" msgid "Playtesters" msgstr "ChÆ¡i thá»­" msgid "Designer" msgstr "Thiết kế" msgid "Producers" msgstr "Sản xuất" msgid "QA Managers" msgstr "Quản lý QA" msgid "Sound Design" msgstr "Thiết kế Âm thanh" msgid "Town Themes" msgstr "Chá»§ đỠThị trấn" msgid "Alto Sax" msgstr "Alto Sax" msgid "Harpsichord and Piano" msgstr "Harpsichord and Piano" msgid "Basso Vocal" msgstr "Basso Vocal" msgid "Soprano Vocal" msgstr "Soprano Vocal" msgid "Recorded at %{recordingStudio}" msgstr "Thu âm tại %{recordingStudio}" msgid "credits|Manual" msgstr "Thá»§ công" msgid "German Consultant" msgstr "Tư vấn Tiếng Äức" msgid "Map Designers" msgstr "Thiết kế Bản đồ" msgid "Package Design" msgstr "Thiết kế Äóng gói" msgid "Your Name" msgstr "Tên cá»§a Ngươi" msgid "Unknown Hero" msgstr "Tướng Vô danh" msgid "Standard" msgstr "Tiêu chuẩn" msgid "View High Scores for Standard Maps." msgstr "Xem Äiểm cho chế độ Thưá»ng." msgid "Campaign" msgstr "Chiến dịch" msgid "View High Scores for Campaigns." msgstr "Xem Äiểm cho chế độ Chiến dịch." msgid "hotkey|default okay event" msgstr "sá»± kiện tốt mặc định" msgid "hotkey|default cancel event" msgstr "sá»± kiện há»§y mặc định" msgid "hotkey|default left" msgstr "mặc định trái" msgid "hotkey|default right" msgstr "mặc định phải" msgid "hotkey|default up" msgstr "mặc định lên" msgid "hotkey|default down" msgstr "mặc định xuống" msgid "hotkey|toggle fullscreen" msgstr "chuyển đổi chế độ toàn màn hình" msgid "hotkey|toggle text support mode" msgstr "chuyển đội chế độ há»— trợ văn bản" msgid "hotkey|new game" msgstr "trò chÆ¡i má»›i" msgid "hotkey|load game" msgstr "tải trò chÆ¡i" msgid "hotkey|highscores" msgstr "Ä‘iểm cao" msgid "hotkey|credits" msgstr "công trạng" msgid "hotkey|standard game" msgstr "trò chÆ¡i tiêu chuẩn" msgid "hotkey|campaign game" msgstr "trò chÆ¡i chiến dịch" msgid "hotkey|multi-player game" msgstr "trò chÆ¡i nhiá»u ngưá»i" msgid "hotkey|settings" msgstr "thiết lập" msgid "hotkey|quit" msgstr "thoát" msgid "hotkey|select map" msgstr "chá»n bản đồ" msgid "hotkey|select small map size" msgstr "chá»n bản đồ cỡ nhá»" msgid "hotkey|select medium map size" msgstr "chá»n bản đồ cỡ vừa" msgid "hotkey|select large map size" msgstr "chá»n bản đồ cỡ lá»›n" msgid "hotkey|select extra large map size" msgstr "chá»n bản đồ cỡ siêu lá»›n" msgid "hotkey|select all map sizes" msgstr "chá»n tất cả các bản đồ" msgid "hotkey|hotseat game" msgstr "trò chÆ¡i ghế nóng" msgid "hotkey|battle only game" msgstr "trò chÆ¡i chỉ chiến đấu" msgid "hotkey|choose the original campaign" msgstr "chá»n chiến dịch gốc" msgid "hotkey|choose the expansion campaign" msgstr "chá»n chiến dịch mở rá»™ng" msgid "hotkey|map editor main menu" msgstr "trình đơn biên tập bản đồ" msgid "hotkey|new map menu" msgstr "trình đơn bản đồ má»›i" msgid "hotkey|load map menu" msgstr "trình đơn tải bản đồ" msgid "hotkey|new map from scratch" msgstr "bắt đầu bản đồ má»›i" msgid "hotkey|new random map" msgstr "bản đồ má»›i ngẫu nhiên" msgid "hotkey|undo last action" msgstr "hoàn tác tác vụ cuối" msgid "hotkey|redo last action" msgstr "thá»±c hiện lại tác vụ cuối" msgid "hotkey|roland campaign" msgstr "chiến dịch roland" msgid "hotkey|archibald campaign" msgstr "chiến dịch archibald" msgid "hotkey|the price of loyalty campaign" msgstr "chiến dịch giá cá»§a lòng trung thành" msgid "hotkey|voyage home campaign" msgstr "chiến dịch đưá»ng vá» nhà" msgid "hotkey|wizard's isle campaign" msgstr "chiến dịch đảo phù thá»§y" msgid "hotkey|descendants campaign" msgstr "chiến dịch hậu duệ" msgid "hotkey|select first campaign bonus" msgstr "chá»n phần thưởng chiến dịch đầu tiên" msgid "hotkey|select second campaign bonus" msgstr "chá»n phần thưởng chiến dịch thứ 2" msgid "hotkey|select third campaign bonus" msgstr "chá»n phần thưởng chiến dịch thứ 3" msgid "hotkey|view campaign intro" msgstr "xem giá»›i thiệu chiến dịch" msgid "hotkey|select campaign difficulty" msgstr "chá»n độ khó chiến dịch" msgid "hotkey|restart campaign scenario" msgstr "khởi động lại kịch bản chiến dịch" msgid "hotkey|world map left" msgstr "bản đồ thế giá»›i trái" msgid "hotkey|world map right" msgstr "bản đồ thế giá»›i phải" msgid "hotkey|world map up" msgstr "bản đồ thế giá»›i trên" msgid "hotkey|world map down" msgstr "bản đồ thế giá»›i dưới" msgid "hotkey|world map up left" msgstr "bản đồ thế giá»›i trái trên" msgid "hotkey|world map up right" msgstr "bản đồ thế giá»›i phải trên" msgid "hotkey|world map down left" msgstr "bản đồ thế giá»›i trái dưới" msgid "hotkey|world map down right" msgstr "bản đồ thế giá»›i phải dưới" msgid "hotkey|save game" msgstr "lưu trò chÆ¡i" msgid "hotkey|next hero" msgstr "tướng tiếp theo" msgid "hotkey|start hero movement" msgstr "bắt đầu di chuyển tướng" msgid "hotkey|cast adventure spell" msgstr "niệm câu thần chú thám hiểm" msgid "hotkey|put hero to sleep" msgstr "cho tướng Ä‘i ngá»§" msgid "hotkey|next town" msgstr "thị trấn tiếp theo" msgid "hotkey|end turn" msgstr "kết thúc lượt" msgid "hotkey|file options" msgstr "lá»±a chá»n tệp" msgid "hotkey|adventure options" msgstr "tùy chá»n thám hiểm" msgid "hotkey|puzzle map" msgstr "bản đồ giải đố" msgid "hotkey|scenario information" msgstr "thông tin kịch bản" msgid "hotkey|dig for artifact" msgstr "đào bảo vật" msgid "hotkey|view world" msgstr "xem thế giá»›i" msgid "hotkey|kingdom summary" msgstr "tổng quát vương quốc" msgid "hotkey|default action" msgstr "hành động mặc định" msgid "hotkey|open focus" msgstr "mở tiêu Ä‘iểm" msgid "hotkey|system options" msgstr "tùy chá»n hệ thống" msgid "hotkey|scroll left" msgstr "cuá»™n trái" msgid "hotkey|scroll right" msgstr "cuá»™n phải" msgid "hotkey|scroll up" msgstr "cuá»™n lên" msgid "hotkey|scroll down" msgstr "cuá»™n xuống" msgid "hotkey|toggle control panel" msgstr "chuyển đổi bảng Ä‘iá»u khiển" msgid "hotkey|toggle radar" msgstr "chuyển đổi radar" msgid "hotkey|toggle buttons" msgstr "chuyển đổi nút bấm" msgid "hotkey|toggle status" msgstr "chuyển đổi trạng thái" msgid "hotkey|toggle icons" msgstr "chuyển đổi biểu tượng" msgid "hotkey|transfer control to ai" msgstr "chuyển Ä‘iá»u khiển cho AI" msgid "hotkey|retreat from battle" msgstr "rút lui khá»i trận chiến" msgid "hotkey|surrender during battle" msgstr "đầu hàng trong trận chiến" msgid "hotkey|toggle battle auto mode" msgstr "chuyển đổi chế độ tá»± động chiến đấu" msgid "hotkey|finish the battle in auto mode" msgstr "kết thúc trận chiến vá»›i chế độ tá»± động" msgid "hotkey|battle options" msgstr "lá»±a chá»n trận chiến" msgid "hotkey|skip turn in battle" msgstr "bá» lượt trong trận chiến" msgid "hotkey|cast battle spell" msgstr "niệm chú trong trận chiến" msgid "hotkey|dwelling level 1" msgstr "nhà lính cấp 1" msgid "hotkey|dwelling level 2" msgstr "nhà lính cấp 2" msgid "hotkey|dwelling level 3" msgstr "nhà lính cấp 3" msgid "hotkey|dwelling level 4" msgstr "nhà lính cấp 4" msgid "hotkey|dwelling level 5" msgstr "nhà lính cấp 5" msgid "hotkey|dwelling level 6" msgstr "nhà lính cấp 6" msgid "hotkey|well" msgstr "giếng" msgid "hotkey|marketplace" msgstr "chợ" msgid "hotkey|mage guild" msgstr "há»™i phù thá»§y" msgid "hotkey|shipyard" msgstr "bến tàu" msgid "hotkey|thieves guild" msgstr "băng cướp" msgid "hotkey|tavern" msgstr "quán rượu" msgid "hotkey|construction screen" msgstr "màn hình xây dá»±ng" msgid "hotkey|buy all monsters in well" msgstr "mua tất cả các qoái vật trong giếng" msgid "hotkey|split stack by half" msgstr "chia đạo quân làm đôi" msgid "hotkey|split stack by one" msgstr "chia đạo quân thành má»™t" msgid "hotkey|join stacks" msgstr "nhập đạo quân" msgid "hotkey|upgrade troop" msgstr "nâng cấp quân đội" msgid "hotkey|dismiss hero or troop" msgstr "giải tán tướng hoặc quân" msgid "" "Do you want to regain control from AI? The effect will take place only on " "the next turn." msgstr "" "Ngươi có muốn AI kiểm soát không? Hiệu quả sẽ bắt đầu ở lượt tiếp theo." msgid "" "Do you want to transfer control from you to the AI? The effect will take " "place only on the next turn." msgstr "" "Ngươi có muốn chuyển quyá»n kiểm soát sang cho AI không? Hiệu ứng sẽ chỉ diá»…n " "ra ở lượt tiếp theo." msgid "Default Actions" msgstr "Hành động Mặc định" msgid "Global Actions" msgstr "Hành động Toàn cầu" msgid "Main Menu" msgstr "Trình đơn Chính" msgid "World Map" msgstr "Bản đồ Thế giá»›i" msgid "Battle Screen" msgstr "Màn hình Chiến đấu" msgid "Town Screen" msgstr "Màn hình Thị trấn" msgid "Army Actions" msgstr "Hành động Quân đội" msgid "Editor" msgstr "Biên tập" msgid "The save file is corrupted." msgstr "File lưu bị há»ng." msgid "Unsupported save format: " msgstr "Äịnh dạng lưu không há»— trợ: " msgid "Current game version: " msgstr "Phiên bản trò chÆ¡i hiện tại: " msgid "Last supported version: " msgstr "Phiên bản há»— trợ cuối cùng: " msgid "This file contains a save with an invalid game type." msgstr "Tệp này chứa má»™t bản lưu có loại trò chÆ¡i không hợp lệ." msgid "" "This save file requires \"The Price of Loyalty\" game assets, but they have " "not been provided to the engine." msgstr "" "Tệp lưu này yêu cầu ná»™i dung trò chÆ¡i \"Cái giá cá»§a lòng trung thành\", " "nhưng chúng chưa được cung cấp cho công cụ." msgid "This saved game is localized to '" msgstr "Trò chÆ¡i đã lưu này được bản địa hóa thành '" msgid "' language, but the current language of the game is '" msgstr "' ngôn ngữ, nhưng ngôn ngữ hiện tại cá»§a trò chÆ¡i là '" msgid "Hot Seat" msgstr "Ghế Nóng" msgid "" "Play a Hot Seat game, where 2 to 4 players play around the same computer, " "switching into the 'Hot Seat' when it is their turn." msgstr "" "ChÆ¡i chế độ Ghế nóng, trong đó 2 đến 4 ngưá»i chÆ¡i cùng chÆ¡i quanh má»™t máy " "tính, chuyển sang 'Ghế nóng' khi đến lượt cá»§a há»." msgid "A single player game playing out a single map." msgstr "Má»™t trò chÆ¡i má»™t ngưá»i chÆ¡i chÆ¡i trên má»™t bản đồ duy nhất." msgid "Standard Game" msgstr "Trò chÆ¡i Thưá»ng" msgid "A single player game playing through a series of maps." msgstr "Má»™t trò chÆ¡i má»™t ngưá»i chÆ¡i chÆ¡i qua má»™t loạt bản đồ." msgid "Campaign Game" msgstr "Trò chÆ¡i Chiến dịch" msgid "Multi-Player Game" msgstr "Trò chÆ¡i Nhiá»u ngưá»i" msgid "" "A multi-player game, with several human players completing against each " "other on a single map." msgstr "" "Má»™t trò chÆ¡i nhiá»u ngưá»i chÆ¡i, trong đó má»™t số ngưá»i chÆ¡i sẽ đấu vá»›i nhau " "trên má»™t bản đồ." msgid "fheroes2 Resurrection Team presents" msgstr "fheroes2 Resurrection Team giá»›i thiệu" msgid "Greetings!" msgstr "Chào mừng!" msgid "Welcome to Heroes of Might and Magic II powered by fheroes2 engine!" msgstr "" "Chào mừng tá»›i Heroes of Might and Magic II cung cấp bởi mã nguồn fheroes2!" msgid "" "Welcome to Heroes of Might and Magic II powered by the fheroes2 engine!\n" "Before starting the game please select a game resolution." msgstr "" "Chào mừng tá»›i Heroes of Might and Magic II cung cấp bởi mã nguồn fheroes2! " "Trước khi bắt đầu hãy chá»n độ phân giải cá»§a trò chÆ¡i." msgid "Please Remember" msgstr "Hãy Nhá»›" msgid "" "You can always change the language, resolution and settings of the game by " "clicking on the " msgstr "Ngươi có thể thay đổi độ phân giải bằng cách nhấp vào " msgid "door" msgstr "cá»­a" msgid " on the left side of the Main Menu, or with the " msgstr " ở bên phía trái cá»§a Trình đơn Chính, hoặc vá»›i " msgid " button from the " msgstr " nút từ " msgid "NEW GAME" msgstr "TRÃ’ CHÆ I MỚI" msgid "" " menu. \n" "\n" "Enjoy the game!" msgstr "" " trình đơn.\n" "\n" "Hãy thưởng thức trò chÆ¡i!" msgid "The Editor requires \"The Price of Loyalty\" expansion files to work." msgstr "" "Biên tập cần tệp bản mở rá»™ng \"Giá cá»§a Lòng Trung thành\" để thá»±c hiện." msgid "Quit Heroes of Might and Magic II and return to the operating system." msgstr "Thoát khá»i Heroes of Might and Magic II và quay lại hệ Ä‘iá»u hành." msgid "Credits" msgstr "Công trạng" msgid "View the credits screen." msgstr "Xem màn hình giá»›i thiệu thông tin." msgid "High Scores" msgstr "Äiểm Cao" msgid "View the high scores screen." msgstr "Xem màn hình Ä‘iểm cao." msgid "Change language, resolution and settings of the game." msgstr "Thay đổi ngôn ngữ, độ phân giải và các cài đặt cá»§a trò chÆ¡i." msgid "Game Settings" msgstr "Cài đặt Trò chÆ¡i" msgid "" "Required video files for campaign selection window are missing. Please make " "sure that all necessary files are present in the system." msgstr "" "File video cần thiết cho chiến dịch bị thiếu. Hãy đảm bảo tất cả các file " "cần thiết có sẵn trong hệ thống." msgid "" "Either Roland's or Archibald's campaign from the original Heroes of Might " "and Magic II." msgstr "" "Chiến dịch cá»§a Roland hoặc Archibald từ bản gốc Heroes of Might and Magic II." msgid "Original Campaign" msgstr "Chiến dịch Gốc" msgid "Expansion Campaign" msgstr "Chiến dịch Mở rá»™ng" msgid "One of the four new campaigns from the Price of Loyalty expansion set." msgstr "Má»™t trong bốn chiến dịch má»›i từ bản mở rá»™ng Giá cá»§a Lòng Trung thành." msgid "Loading video. Please wait..." msgstr "Äang tải video. Xin đợi..." msgid "Host" msgstr "Chá»§ nhà" msgid "" "The host sets up the game options. There can only be one host per network " "game." msgstr "" "Chá»§ nhà thiết lập các lá»±a chá»n trò chÆ¡i. Chỉ có thể có má»™t máy chá»§ cho má»—i " "trò chÆ¡i mạng." msgid "Guest" msgstr "Khách" msgid "" "The guest waits for the host to set up the game, then is automatically added " "in. There can be multiple guests for TCP/IP games." msgstr "" "Khách đợi chá»§ nhà thiết lập trò chÆ¡i, sau đó sẽ tá»± động được thêm vào. Có " "thể có nhiá»u khách cho trò chÆ¡i TCP/IP." msgid "Battle Only" msgstr "Chỉ Chiến đấu" msgid "Setup and play a battle without loading any map." msgstr "" "Thiết lập và chÆ¡i má»™t trận chiến mà không cần phải tải bất cứ bản đồ nào." msgid "2 Players" msgstr "2 ngưá»i chÆ¡i" msgid "" "Play with 2 human players, and optionally, up to 4 additional computer " "players." msgstr "ChÆ¡i vá»›i 2 ngưá»i chÆ¡i và tùy chá»n, tối Ä‘a thêm 4 máy tính." msgid "3 Players" msgstr "3 ngưá»i chÆ¡i" msgid "" "Play with 3 human players, and optionally, up to 3 additional computer " "players." msgstr "ChÆ¡i vá»›i 3 ngưá»i chÆ¡i và tùy chá»n, tối Ä‘a thêm 3 máy tính." msgid "4 Players" msgstr "4 ngưá»i chÆ¡i" msgid "" "Play with 4 human players, and optionally, up to 2 additional computer " "players." msgstr "ChÆ¡i vá»›i 4 ngưá»i chÆ¡i và tùy chá»n, tối Ä‘a thêm 2 máy tính." msgid "5 Players" msgstr "5 ngưá»i chÆ¡i" msgid "" "Play with 5 human players, and optionally, up to 1 additional computer " "player." msgstr "ChÆ¡i vá»›i 5 ngưá»i chÆ¡i và tùy chá»n, tối Ä‘a thêm 1 máy tính." msgid "6 Players" msgstr "6 ngưá»i chÆ¡i" msgid "Play with 6 human players." msgstr "ChÆ¡i vá»›i 6 ngưá»i chÆ¡i." msgid "Dragon city has fallen! You are now the Master of the Dragons." msgstr "Thành phố rồng đã thất thá»§! Bây giá» ngươi đã là Bậc thầy cá»§a Rồng." msgid "" "You captured %{name}!\n" "You are victorious." msgstr "" "Ngươi chiếm được %{name}!\n" "Ngươi là ngưá»i chiến thắng." msgid "" "You have captured the enemy hero %{name}!\n" "Your quest is complete." msgstr "" "Ngươi đã bắt được tướng địch %{name}!\n" "Nhiệm vụ cá»§a ngươi đã hoàn tất." msgid "" "You have found the %{name}.\n" "Your quest is complete." msgstr "" "Ngươi đã tìm thấy %{name}.\n" "Nhiệm vụ cá»§a ngươi kết thúc." msgid "Ultimate Artifact" msgstr "Bảo vật Tối thượng" msgid "" "The enemy is beaten.\n" "Your side has triumphed!" msgstr "" "Äịch đã bị đánh bại.\n" "Phe ngươi đã chiến thắng!" msgid "" "You have built up over %{count} gold in your treasury.\n" "All enemies bow before your wealth and power." msgstr "" "Ngươi đã tích lÅ©y được hÆ¡n %{count} vàng trong kho bạc cá»§a mình.\n" "Tất cả kẻ thù Ä‘á»u cúi đầu trước sá»± giàu có và quyá»n lá»±c cá»§a ngươi." msgid "Victory!" msgstr "Chiến thắng!" msgid "" "The enemy has captured %{name}!\n" "They are triumphant." msgstr "" "Äịch đã chiếm được %{name}!\n" "Chúng đã chiến thắng." msgid "" "The enemy has built up over %{count} gold in his treasury.\n" "You must bow done in defeat before his wealth and power." msgstr "" "Kẻ thù đã tích lÅ©y được hÆ¡n %{count} vàng trong kho bạc cá»§a mình.\n" "Ngươi phải cúi đầu thất bại trước sá»± giàu có và quyá»n lá»±c cá»§a hắn ta." msgid "You have been eliminated from the game!!!" msgstr "Ngươi đã bị loại khá»i trò chÆ¡i!!!" msgid "" "You have lost the hero %{name}.\n" "Your quest is over." msgstr "" "Ngươi mất tướng %{name}.\n" "Nhiệm vụ cá»§a ngươi chấm dứt." msgid "" "You have failed to complete your quest in time.\n" "All is lost." msgstr "" "Ngươi đã không hoàn thành nhiệm vụ cá»§a mình đúng thá»i hạn.\n" "Tất cả bị mất." msgid "Defeat!" msgstr "Bị đánh bại!" msgid "" "Base score: %{score}\n" "Difficulty: %{difficulty}\n" "\n" msgstr "" "Äiểm: %{score}\n" "Khó: %{difficulty}\n" "\n" msgid "Defeat all enemy heroes and capture all enemy towns and castles." msgstr "" "Äánh bại tất cả tướng cá»§a kẻ thù và chiếm được tất cả các thị trấn và lâu " "đài cá»§a kẻ thù." msgid "Run out of time. (Fail to win by a certain point.)" msgstr "Hết thá»i gian. Không thể giành chiến thắng vá»›i thá»i gian nhất định." msgid "You must defeat the enemy %{enemies}." msgid_plural "You must defeat the enemy alliance of %{enemies}." msgstr[0] "Ngươi phải đánh bại kẻ thù %{enemies}." msgid "" "The alliance consisting of %{allies} and you must defeat the enemy " "%{enemies}." msgid_plural "" "The alliance consisting of %{allies} and you must defeat the enemy alliance " "of %{enemies}." msgstr[0] "" "Liên minh bao gồm %{allies} và ngươi phải đánh bại kẻ thù %{enemies}." msgid "Capture the castle '%{name}'." msgstr "Chiếm được lâu đài '%{name}'." msgid "Capture the town '%{name}'." msgstr "Chiếm được thị trấn '%{name}'." msgid "Defeat the hero '%{name}'." msgstr "Diệt được tướng '%{name}'." msgid "Find the ultimate artifact." msgstr "Tìm được bảo vật tối thượng." msgid "Find the '%{name}' artifact." msgstr "Tìm bảo vật '%{name}'." msgid "Accumulate %{count} gold." msgstr "Äạt được %{count} vàng." msgid "" ", or you may win by defeating all enemy heroes and capturing all enemy towns " "and castles." msgstr "" ", hoặc ngươi có thể giành chiến thắng bằng cách đánh bại tất cả tướng cá»§a kẻ " "thù và chiếm được tất cả các thị trấn và lâu đài cá»§a kẻ thù." msgid "Lose the castle '%{name}'." msgstr "Mất lâu đài '%{name}'." msgid "Lose the town '%{name}'." msgstr "Mất thị trấn '%{name}'." msgid "Lose the hero: %{name}." msgstr "Mất tướng: %{name}." msgid "Fail to win by the end of month %{month}, week %{week}, day %{day}." msgstr "" "Không giành chiến thắng vào cuối tháng %{month}, tuần %{week}, ngày %{day}." msgid "%{color} player has been vanquished!" msgstr "ngưá»i chÆ¡i màu %{color} đã bị đánh bại !" msgid "Major Event!" msgstr "Sá»± kiện Chính!" msgid "Scenario:" msgstr "Kịch bản:" msgid "Game Difficulty:" msgstr "Äá»™ khó Trò chÆ¡i:" msgid "Opponents:" msgstr "Äối thá»§:" msgid "Class:" msgstr "Chá»§ng tá»™c:" msgid "Rating %{rating}%" msgstr "Xếp hạng %{rating}%" msgid "Click here to select which scenario to play." msgstr "Nhấp vào đây để chá»n kịch bản để chÆ¡i." msgid "Scenario" msgstr "Kịch bản" msgid "Game Difficulty" msgstr "Äá»™ khó Trò chÆ¡i" msgid "" "This lets you change the starting difficulty at which you will play. Higher " "difficulty levels start you of with fewer resources, and at the higher " "settings, give extra resources to the computer." msgstr "" "Äiá»u này cho phép thay đổi độ khó ban đầu mà ngươi sẽ chÆ¡i. Mức độ khó cao " "hÆ¡n bắt đầu vá»›i ít tài nguyên hÆ¡n và ở cài đặt cao hÆ¡n, cung cấp thêm tài " "nguyên cho máy tính." msgid "Difficulty Rating" msgstr "Äánh giá Äá»™ khó" msgid "" "The difficulty rating reflects a combination of various settings for your " "game. This number will be applied to your final score." msgstr "" "Xếp hạng độ khó phản ánh sá»± kết hợp cá»§a nhiá»u cài đặt khác nhau cho trò chÆ¡i " "cá»§a ngươi. Con số này sẽ được áp dụng cho Ä‘iểm số cuối cùng cá»§a ngươi." msgid "Click to accept these settings and start a new game." msgstr "Nhấp để chấp nhận các cài đặt này và bắt đầu trò chÆ¡i má»›i." msgid "Click to return to the main menu." msgstr "Nhấp để quay lại màn hình chính." msgid "Astrologers proclaim the Month of the %{name}." msgstr "Các nhà chiêm tinh công bố Tháng cá»§a %{name}." msgid "Astrologers proclaim the Week of the %{name}." msgstr "Các nhà chiêm tinh công bố Tuần cá»§a %{name}." msgid "After regular growth, the population of %{monster} is doubled!" msgstr "Sau khi tăng trưởng Ä‘á»u đặn, dân số cá»§a %{monster} đã tăng gấp đôi!" msgid "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgid_plural "" "After regular growth, the population of %{monster} increases by %{count} " "percent!" msgstr[0] "" "Sau khi tăng trưởng Ä‘á»u đặn, dân số cá»§a %{monster} đã tăng %{count} phần " "trăm!" msgid "%{monster} growth +%{count}." msgstr "%{monster} tăng trưởng +%{count}." msgid " All populations are halved." msgstr " Tất cả dân số Ä‘á»u giảm má»™t ná»­a." msgid " All dwellings increase population." msgstr " Tất cả các nhà tăng dân số." msgid "New Month!" msgstr "Tháng Má»›i!" msgid "New Week!" msgstr "Tuần Má»›i!" msgid "Beware!" msgstr "Coi chừng!" msgid "" "%{color} player, this is your last day to capture a town, or you will be " "banished from this land." msgstr "" "Ngưá»i chÆ¡i màu %{color}, đây là ngày cuối cùng cá»§a ngươi để chiếm được má»™t " "thị trấn, nếu không ngươi sẽ bị trục xuất khá»i vùng đất này." msgid "" "%{color} player, you only have %{day} days left to capture a town, or you " "will be banished from this land." msgstr "" "Ngưá»i chÆ¡i màu %{color}, ngươi chỉ có %{day} để chiếm được má»™t thị trấn, nếu " "không ngươi sẽ bị trục xuất khá»i vùng đất này." msgid "%{color} player's turn." msgstr "Lượt ngưá»i chÆ¡i màu %{color}." #, fuzzy msgid "" "%{color} player, you have lost your last town. If you do not conquer another " "town in the next week, you will be eliminated." msgstr "" "Ngưá»i chÆ¡i %{color}, ngươi đã mất thị trấn cuối cùng cá»§a mình. Nếu ngươi " "không chinh phục được thị trấn khác vào tuần tá»›i, ngươi sẽ bị loại." msgid "" "%{color} player, your heroes abandon you, and you are banished from this " "land." msgstr "" "Ngưá»i chÆ¡i màu %{color}, các tướng đã bá» rÆ¡i ngươi và ngươi bị trục xuất " "khá»i vùng đất này." msgid "Lord Kilburn" msgstr "Lord Kilburn" msgid "Tsabu" msgstr "Tsabu" msgid "Sir Galant" msgstr "Sir Galant" msgid "Thundax" msgstr "Thundax" msgid "Lord Haart" msgstr "Lord Haart" msgid "Ariel" msgstr "Ariel" msgid "Rebecca" msgstr "Rebecca" msgid "Sandro" msgstr "Sandro" msgid "Crodo" msgstr "Crodo" msgid "Barock" msgstr "Barock" msgid "Antoine" msgstr "Antoine" msgid "Astra" msgstr "Astra" msgid "Agar" msgstr "Agar" msgid "Vatawna" msgstr "Vatawna" msgid "Vesper" msgstr "Vesper" msgid "Ambrose" msgstr "Ambrose" msgid "Troyan" msgstr "Troyan" msgid "Jojosh" msgstr "Jojosh" msgid "Wrathmont" msgstr "Wrathmont" msgid "Maximus" msgstr "Maximus" msgid "Next Hero" msgstr "Tướng Tiếp theo" msgid "Select the next Hero." msgstr "Chá»n Tướng tiếp theo." msgid "Hero Movement" msgstr "Tướng Di chuyển" msgid "" "Start the Hero's movement along the current path or re-visit the object " "occupied by the Hero. Press and hold this button to reset the Hero's path." msgstr "" "Bắt đầu di chuyển Tướng dá»c theo con đưá»ng hiện tại hoặc ghé thăm lại đối " "tượng mà Tướng chiếm giữ. Nhấn và giữ nút này để thiết lập lại đưá»ng Ä‘i cá»§a " "Tướng." msgid "Kingdom Summary" msgstr "Tổng quát Vương quốc" msgid "View a Summary of your Kingdom." msgstr "Xem Tổng quát vá» Vương quốc." msgid "Cast an adventure spell." msgstr "Niệm má»™t câu thần chú khám phá." msgid "End Turn" msgstr "Kết thúc Lượt" msgid "End your turn and left the computer take its turn." msgstr "Kết thúc lượt cá»§a ngươi và để máy tính Ä‘i lượt cá»§a nó." msgid "Adventure Options" msgstr "Tùy chá»n Khám phá" msgid "Bring up the adventure options menu." msgstr "Hiển thị menu tùy chá»n khám phá." msgid "" "Bring up the file options menu, allowing you to load, save, start a new game " "or quit." msgstr "" "Hiển thị trình đơn tùy chá»n tệp, cho phép ngươi tải, lưu, bắt đầu trò chÆ¡i " "má»›i hoặc thoát." msgid "Bring up the system options menu, allowing you to customize your game." msgstr "" "Hiển thị trình đơn tùy chá»n hệ thống, cho phép ngươi tùy chỉnh trò chÆ¡i cá»§a " "mình." msgid "" "One or more heroes may still move, are you sure you want to end your turn?" msgstr "" "Má»™t hoặc nhiá»u tướng vẫn có thể di chuyển, ngươi có chắc chắn muốn kết thúc " "lượt cá»§a mình không?" msgid "Are you sure you want to quit?" msgstr "Ngươi có muốt thoát không?" msgid "Are you sure you want to restart? (Your current game will be lost.)" msgstr "" "Ngươi có chắc là ngươi muốn bắt đầu lại không? (Trò chÆ¡i hiện tại cá»§a ngươi " "sẽ bị mất.)" msgid "Are you sure you want to overwrite the save with this name?" msgstr "Ngươi có chắc chắn muốn ghi đè lưu bằng tên này không?" msgid "Game saved successfully." msgstr "Chò trÆ¡i được lưu thành công." msgid "There was an issue during saving." msgstr "Äã xảy ra sá»± cố trong quá trình lưu." msgid "" "Are you sure you want to load a new game? (Your current game will be lost.)" msgstr "" "Ngươi có chắc là ngươi muốn tải má»™t trò chÆ¡i má»›i không? (Trò chÆ¡i hiện tại " "cá»§a ngươi sẽ bị mất.)" msgid "Try looking on land!!!" msgstr "Thá»­ tìm kiếm trên mặt đất!!!" msgid "" "Searching for the Ultimate Artifact is fruitless. Your hero could not carry " "it even if he found it - all his artifact slots are full." msgstr "" "Việc tìm kiếm Bảo vật Tối thượng không có kết quả. Tướng cá»§a ngươi không thể " "mang nó ngay cả khi anh ta tìm thấy nó - tất cả các ô tạo tác cá»§a anh ta Ä‘á»u " "đã đầy." msgid "Digging for artifacts requires a whole day, try again tomorrow." msgstr "Äào báu vật cần cả má»™t ngày, hãy thá»­ lại vào ngày mai." msgid "" "After spending many hours digging here, you have uncovered the %{artifact}." msgstr "Sau nhiá»u giỠđào bá»›i ở đây, ngươi đã phát hiện được %{artifact}." msgid "Congratulations!" msgstr "Chúc mừng!" msgid "Nothing here. Where could it be?" msgstr "Không có gì ở đây. Nó có thể ở đâu?" msgid "Try searching on clear ground." msgstr "Hãy thá»­ tìm kiếm cẩn than trên mặt đất." msgid "A miniature view of the known world. Left click to move viewing area." msgstr "" "Má»™t cái nhìn thu nhá» vá» thế giá»›i đã biết. Nhấp chuá»™t trái để di chuyển khu " "vá»±c xem." msgid "Month: %{month} Week: %{week}" msgstr "Tháng: %{month} Tuần: %{week}" msgid "Day: %{day}" msgstr "Ngày: %{day}" msgid "" "You find a small\n" "quantity of %{resource}." msgstr "" "Ngươi tìm thấy\n" "má»™t lượng nhá» %{resource}." msgid "Status Window" msgstr "Cá»­a sổ Trạng thái" msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date." msgstr "" "Cá»­a sổ này cung cấp thông tin vá» trạng thái tướng hoặc vương quốc cá»§a ngươi " "và hiển thị ngày tháng." msgid "" "This window provides information on the status of your hero or kingdom, and " "shows the date. Left click here to cycle through these windows." msgstr "" "Cá»­a sổ này cung cấp thông tin vá» trạng thái tướng hoặc vương quốc cá»§a ngươi " "và hiển thị ngày tháng. Nhấp chuá»™t trái vào đây để duyệt qua các cá»­a sổ này." msgid "" "This lets you change player starting positions and colors. A particular " "color will always start in a particular location. Some positions may only be " "played by a computer player or only by a human player." msgstr "" "Äiá»u này cho phép ngươi thay đổi vị trí và màu sắc bắt đầu cá»§a ngưá»i chÆ¡i. " "Má»™t màu cụ thể sẽ luôn bắt đầu ở má»™t vị trí cụ thể. Má»™t số vị trí chỉ có thể " "được chÆ¡i bởi ngưá»i chÆ¡i máy tính hoặc chỉ bởi ngưá»i chÆ¡i." msgid "" "This lets you change the class of a player. Classes are not always " "changeable. Depending on the scenario, a player may receive additional towns " "and/or heroes not of their primary alignment." msgstr "" "Äiá»u này cho phép ngươi thay đổi lá»›p cá»§a má»™t ngưá»i chÆ¡i. Các chá»§ng tá»™c không " "phải lúc nào cÅ©ng có thể thay đổi. Tùy thuá»™c vào kịch bản, ngưá»i chÆ¡i có thể " "nhận được các thị trấn và/hoặc tướng bổ sung không thuá»™c liên kết chính cá»§a " "há»." msgid "Handicap" msgstr "Chấp" msgid "" "This lets you change the handicap of a particular player. Only humans may be " "handicapped. Handicapped players start with fewer resources and earn 15 or " "30% fewer resources per turn for mild and severe handicaps, respectively." msgstr "" "Äiá»u này cho phép ngươi thay đổi Ä‘iểm chấp cá»§a má»™t ngưá»i chÆ¡i cụ thể. Chỉ có " "con ngưá»i má»›i có thể bị khuyết tật. Ngưá»i chÆ¡i bị khuyết tật bắt đầu vá»›i ít " "tài nguyên hÆ¡n và kiếm được ít tài nguyên hÆ¡n 15 hoặc 30% má»—i lượt tương ứng " "đối vá»›i ngưá»i khuyết tật nhẹ và nặng." msgid "%{color} player" msgstr "ngưá»i chÆ¡i mầu %{color}" msgid "No Handicap" msgstr "Không Chấp" msgid "No special restrictions on start resources and earning them per turn." msgstr "" "Không có hạn chế đặc biệt nào vá» tài nguyên bắt đầu và kiếm chúng má»—i lượt." msgid "Mild Handicap" msgstr "Chấp Ãt" msgid "" "Mild handicapped players start with fewer resources and earn 15% fewer " "resources per turn." msgstr "" "Ngưá»i chÆ¡i chấp ít bắt đầu vá»›i ít tài nguyên hÆ¡n và kiếm được ít tài nguyên " "hÆ¡n 15% má»—i lượt." msgid "Severe Handicap" msgstr "Chấp Nhiá»u" msgid "" "Severe handicapped players start with fewer resources and earn 30% fewer " "resources per turn." msgstr "" "Ngưá»i chÆ¡i chấp nhiá»u bắt đầu vá»›i ít tài nguyên hÆ¡n và kiếm được ít tài " "nguyên hÆ¡n 30% má»—i lượt." msgid "View %{skill} Info" msgstr "Xem Thông tin %{skill}" msgid "Keyboard|123" msgstr "" msgid "Keyboard|SPACE" msgstr "" msgid "Keyboard|ABC" msgstr "" msgid "Kingdom Income" msgstr "Thu nhập Vương quốc" msgid "Kingdom Income per day." msgstr "Thu nhập Vương quốc má»™t ngày." msgid "For every lighthouse controlled, your ships will move further each day." msgstr "" "Vá»›i má»—i ngá»n hải đăng được kiểm soát, tàu cá»§a ngươi sẽ di chuyển xa hÆ¡n má»—i " "ngày." msgid "English" msgstr "Tiếng Anh" msgid "French" msgstr "Tiếng Pháp" msgid "Polish" msgstr "Tiếng Ba Lan" msgid "German" msgstr "Tiếng Äức" msgid "Russian" msgstr "Tiếng Nga" msgid "Italian" msgstr "Tiếng Ã" msgid "Czech" msgstr "Tiếng Séc" msgid "Norwegian" msgstr "Tiếng Na Uy" msgid "Belarusian" msgstr "Tiếng Bê La Rút" msgid "Bulgarian" msgstr "Tiếng Bun Ga Ri" msgid "Ukrainian" msgstr "Tiếng U Cá» Rai Na" msgid "Romanian" msgstr "Tiếng Ru Ma Ni" msgid "Spanish" msgstr "Tiếng Tây Ban Nha" msgid "Swedish" msgstr "Tiếng Thụy Äiển" msgid "Portuguese" msgstr "Tiếng Bồ Äào Nha" msgid "Turkish" msgstr "Tiếng Thổ NhÄ© Kỳ" msgid "Dutch" msgstr "Tiếng Hà Lan" msgid "Hungarian" msgstr "Tiếng Hung Ga Ri" msgid "Danish" msgstr "Tiếng Äan Mạch" msgid "Slovak" msgstr "Tiếng Slovak" msgid "Vietnamese" msgstr "Tiếng Việt" msgid ", FPS: " msgstr ", FPS: " msgid "%{object} robber" msgstr "%{object} cướp" msgid "%{object} raided" msgstr "%{object} lính" msgid "Ector" msgstr "Ector" msgid "Gwenneth" msgstr "Gwenneth" msgid "Sir Gallant" msgstr "Sir Gallant" msgid "Tyro" msgstr "Tyro" msgid "Dimitry" msgstr "Dimitry" msgid "Ruby" msgstr "Ruby" msgid "Crag Hack" msgstr "Crag Hack" msgid "Fineous" msgstr "Fineous" msgid "Jezebel" msgstr "Jezebel" msgid "Atlas" msgstr "Atlas" msgid "Ergon" msgstr "Ergon" msgid "Jaclyn" msgstr "Jaclyn" msgid "Gem" msgstr "Gem" msgid "Natasha" msgstr "Natasha" msgid "Carlawn" msgstr "Carlawn" msgid "Luna" msgstr "Luna" msgid "Arie" msgstr "Arie" msgid "Barok" msgstr "Barok" msgid "Kastore" msgstr "Kastore" msgid "Falagar" msgstr "Falagar" msgid "Dawn" msgstr "Dawn" msgid "Flint" msgstr "Flint" msgid "Halon" msgstr "Halon" msgid "Myra" msgstr "Myra" msgid "Myrini" msgstr "Myrini" msgid "Wilfrey" msgstr "Wilfrey" msgid "Mandigal" msgstr "Mandigal" msgid "Sarakin" msgstr "Sarakin" msgid "Charity" msgstr "Charity" msgid "Darlana" msgstr "Darlana" msgid "Ranloo" msgstr "Ranloo" msgid "Rialdo" msgstr "Rialdo" msgid "Zam" msgstr "Zam" msgid "Zom" msgstr "Zom" msgid "Celia" msgstr "Celia" msgid "Roxana" msgstr "Roxana" msgid "Lord Corlagon" msgstr "Lord Corlagon" msgid "Lord Halton" msgstr "Lord Halton" msgid "Sister Eliza" msgstr "Sister Eliza" msgid "Brother Brax" msgstr "Brother Brax" msgid "Dainwin" msgstr "Dainwin" msgid "Joseph" msgstr "Joseph" msgid "Mog" msgstr "Mog" msgid "Solmyr" msgstr "Solmyr" msgid "Ceallach" msgstr "Ceallach" msgid "Drakonia" msgstr "Drakonia" msgid "Elderian" msgstr "Elderian" msgid "Gallavant" msgstr "Gallavant" msgid "Jarkonas" msgstr "Jarkonas" msgid "Martine" msgstr "Martine" msgid " gives you maximum morale" msgstr " cho ngươi tối Ä‘a tinh thần" msgid " gives you maximum luck" msgstr " cho ngươi tối Ä‘a may mắn" msgid "You cannot pick up this artifact, you already have a full load!" msgstr "Ngươi không thể nhặt bảo vật này, ngươi đã có đầy đủ!" msgid "To cast spells, you must first buy a spell book for %{gold} gold." msgstr "" "Äể có thể niệm chú, trước tiên ngươi phải mua má»™t quyển sách phép vá»›i giá " "%{gold} vàng." msgid "Unfortunately, you seem to be a little short of cash at the moment." msgstr "Thật không may, hiện tại ngươi có vẻ hÆ¡i thiếu tiá»n." msgid "Do you wish to buy one?" msgstr "Ngươi có muốn mua má»™t?" msgid "%{count} / day" msgstr "%{count} / ngày" msgid "one" msgstr "má»™t" msgid "two" msgstr "hai" msgid "A whirlpool engulfs your ship. Some of your army has fallen overboard." msgstr "" "Má»™t xoáy nước nhấn chìm con tàu cá»§a ngươi. Má»™t số quân đội cá»§a ngươi đã rÆ¡i " "xuống biển." msgid "Insulted by your refusal of their offer, the monsters attack!" msgstr "" "Bị xúc phạm bởi việc ngươi từ chối lá»i đỠnghị cá»§a há», lÅ© quái vật tấn công!" msgid "" "The %{monster}, awed by the power of your forces, begin to scatter.\n" "Do you wish to pursue and engage them?" msgstr "" "%{monster}, kinh hãi trước sức mạnh lá»±c lượng cá»§a ngươi, bắt đầu phân tán.\n" "Ngươi có muốn theo Ä‘uổi và thu phục há» không?" msgid "Ransacking an enemy camp, you discover a hidden cache of treasures." msgstr "Lục soát trại cá»§a kẻ thù, ngươi phát hiện ra má»™t kho báu ẩn giấu." msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with these resources, " "come back next week for more.\"" msgstr "" "Ngưá»i giữ cối xay thông báo:\n" "\"Thưa ngài, tôi đã rất cố gắng để cung cấp cho ngài những tài nguyên này, " "tuần sau hãy quay lại để lấy thêm.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there are no resources currently available. Please try " "again next week.\"" msgstr "" "Ngưá»i giữ cối xay thông báo:\n" "\"Thưa ngài, tôi xin lá»—i, hiện tại không có tài nguyên nào. Vui lòng thá»­ lại " "vào tuần tá»›i.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I have been working very hard to provide you with this gold, come " "back next week for more.\"" msgstr "" "Ngưá»i giữ cối xay thông báo:\n" "\"Thưa ngài, tôi đã làm việc rất chăm chỉ để cung cấp số vàng này cho ngài, " "tuần sau hãy quay lại để lấy thêm.\"" msgid "" "The keeper of the mill announces:\n" "\"Milord, I am sorry, there is no gold currently available. Please try again " "next week.\"" msgstr "" "Ngưá»i giữ cối xay thông báo:\n" "\"Thưa ngài, tôi xin lá»—i, hiện tại không có vàng. Vui lòng thá»­ lại vào tuần " "tá»›i.\"" msgid "" "You've found an abandoned lean-to.\n" "Poking about, you discover some resources hidden nearby." msgstr "" "Ngươi đã tìm thấy má»™t ngôi nhà bị bá» hoang.\n" "Khi khám phá, ngươi phát hiện ra má»™t số tài nguyên ẩn gần đó." msgid "The lean-to is long abandoned. There is nothing of value here." msgstr "" "Ngôi nhà dưá»ng như đã bị bá» hoang từ lâu. Chẳng có gì có giá trị ở đây cả." msgid "" "You catch a leprechaun foolishly sleeping amidst a cluster of magic " "mushrooms.\n" "In exchange for his freedom, he guides you to a small pot filled with " "precious things." msgstr "" "Ngươi bắt gặp má»™t con yêu tinh Ä‘ang ngá»§ say giữa má»™t đám nấm thần.\n" "Äể đổi lấy sá»± tá»± do cá»§a mình, anh ta dẫn ngươi đến má»™t chiếc bình nhá» chứa " "đầy những thứ quý giá." msgid "" "You've found a magic garden, the kind of place that leprechauns and faeries " "like to cavort in, but there is no one here today.\n" "Perhaps you should try again next week." msgstr "" "Ngươi đã tìm thấy má»™t khu vưá»n ma thuật, má»™t nÆ¡i mà các yêu tinh và thần " "tiên thích vui đùa, nhưng hôm nay không có ai ở đây cả.\n" "Có lẽ ngươi nên thá»­ lại vào tuần tá»›i." msgid "You come upon the remains of an unfortunate adventurer." msgstr "Ngươi tình cá» gặp hài cốt cá»§a má»™t nhà thám hiểm không may." msgid "Treasure" msgstr "Kho báu" msgid "Searching through the tattered clothing, you find the %{artifact}." msgstr "Lục lá»i trong bá»™ quần áo rách rưới, ngươi tìm thấy %{artifact}." msgid "Searching through the tattered clothing, you find nothing." msgstr "Lục lá»i trong bá»™ quần áo rách rưới, ngươi chẳng tìm được gì." msgid "" "You come across an old wagon left by a trader who didn't quite make it to " "safe terrain." msgstr "" "Ngươi bắt gặp má»™t toa xe cÅ© do má»™t thương nhân để lại, ngưá»i không thể đến " "được địa hình an toàn." msgid "Unfortunately, others have found it first, and the wagon is empty." msgstr "" "Thật không may, những kẻ khác đã tìm thấy nó trước và toa xe trống rá»—ng." msgid "Searching inside, you find the %{artifact}." msgstr "Tìm kiếm bên trong, ngươi tìm thấy %{artifact}." msgid "Inside, you find some of the wagon's cargo still intact." msgstr "Bên trong, ngươi thấy má»™t số hàng hóa cá»§a toa xe vẫn còn nguyên vẹn." msgid "You search through the flotsam, and find some wood and some gold." msgstr "Ngươi tìm kiếm khắp nÆ¡i và tìm thấy má»™t ít gá»— và má»™t ít vàng." msgid "You search through the flotsam, and find some wood." msgstr "Ngươi tìm kiếm khắp các vật trôi nổi và tìm thấy má»™t ít gá»—." msgid "You search through the flotsam, but find nothing." msgstr "Ngươi tìm kiếm khắp các vật trôi nổi, nhưng không tìm thấy gì." msgid "Shrine of the 1st Circle" msgstr "Äá»n thá» Vòng 1" msgid "" "You come across a small shrine attended by a group of novice acolytes.\n" "In exchange for your protection, they agree to teach you a simple spell - " "'%{spell}'." msgstr "" "Ngươi Ä‘i ngang qua má»™t ngôi Ä‘á»n nhá» có má»™t nhóm ngưá»i má»›i tập sá»± tham dá»±.\n" "Äể đổi lấy sá»± bảo vệ cá»§a ngươi, hỠđồng ý dạy ngươi má»™t câu thần chú đơn " "giản - '%{spell}'." msgid "Shrine of the 2nd Circle" msgstr "Äá»n thá» Vòng 2" msgid "" "You come across an ornate shrine attended by a group of rotund friars.\n" "In exchange for your protection, they agree to teach you a spell - " "'%{spell}'." msgstr "" "Ngươi Ä‘i ngang qua má»™t ngôi Ä‘á»n được trang trí công phu vá»›i sá»± tham dá»± cá»§a " "má»™t nhóm tu sÄ© tròn trịa.\n" "Äể đổi lấy sá»± bảo vệ cá»§a ngươi, hỠđồng ý dạy ngươi má»™t câu thần chú - " "'%{spell}'." msgid "Shrine of the 3rd Circle" msgstr "Äá»n thá» Vòng 3" msgid "" "You come across a lavish shrine attended by a group of high priests.\n" "In exchange for your protection, they agree to teach you a sophisticated " "spell - '%{spell}'." msgstr "" "Ngươi Ä‘i ngang qua má»™t ngôi Ä‘á»n xa hoa có sá»± tham dá»± cá»§a má»™t nhóm linh mục " "cao cấp.\n" "Äể đổi lấy sá»± bảo vệ cá»§a ngươi, hỠđồng ý dạy ngươi má»™t câu thần chú phức " "tạp - '%{spell}'." msgid "" "\n" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "\n" "Thật không may, ngươi không có trí tuệ để hiểu câu thần chú và ngươi không " "thể há»c được nó." msgid "" "\n" "Unfortunately, you already have knowledge of this spell, so there is nothing " "more for them to teach you." msgstr "" "\n" "Thật không may, ngươi đã có kiến thức vá» phép thuật này rồi nên há» không còn " "gì để dạy ngươi nữa." msgid "" "\n" "Unfortunately, you have no Magic Book to record the spell with." msgstr "" "\n" "Rất tiếc, ngươi không có Sách Phép để ghi lại lại những phép thuật." msgid "" "You approach the hut and observe a witch inside studying an ancient tome on " "%{skill}.\n" "\n" msgstr "" "Ngươi đến gần túp lá»u và quan sát má»™t phù thá»§y bên trong Ä‘ang nghiên cứu má»™t " "cuốn sách cổ vá» %{skill}.\n" "\n" msgid "" "As you approach, she turns and focuses her one glass eye on you.\n" "\"You already know everything you deserve to learn!\" the witch screeches. " "\"NOW GET OUT OF MY HOUSE!\"" msgstr "" "Khi ngươi đến gần, cô ấy quay lại và tập trung má»™t con mắt thá»§y tinh vào " "ngươi.\n" "\"Ngươi đã biết tất cả má»i thứ ngươi xứng đáng được há»c!\" mụ phù thá»§y rít " "lên. \"BÂY GIỜ RA KHỎI NHÀ CỦA TÔI!\"" msgid "" "As you approach, she turns and speaks.\n" "\"You already know that which I would teach you. I can help you no further.\"" msgstr "" "Khi ngươi đến gần, cô ấy quay lại và nói.\n" "\"Ngươi đã biết Ä‘iá»u tôi sẽ dạy cho ngươi. Tôi không thể giúp gì thêm cho " "ngươi nữa.\"" msgid "" "An ancient and immortal witch living in a hut with bird's legs for stilts " "teaches you %{skill} for her own inscrutable purposes." msgstr "" "Má»™t phù thá»§y cổ xưa và bất tá»­ sống trong má»™t túp lá»u có chân chim làm nhà " "sàn dạy ngươi %{skill} cho những mục đích khó hiểu cá»§a riêng bà ta." msgid "As you drink the sweet water, you gain luck for your next battle." msgstr "Khi uống nước ngá»t, ngươi sẽ gặp may mắn cho trận chiến tiếp theo." msgid "You drink from the enchanted fountain, but nothing happens." msgstr "Ngươi uống nước từ đài phun nước mê hoặc, nhưng không có gì xảy ra." msgid "You enter the faerie ring, but nothing happens." msgstr "Ngươi bước vào vòng tiên, nhưng không có gì xảy ra." msgid "" "Upon entering the mystical faerie ring, your army gains luck for its next " "battle." msgstr "" "Khi bước vào vòng tròn thần tiên, quân đội cá»§a ngươi sẽ gặp may mắn cho trận " "chiến tiếp theo." msgid "" "You've found an ancient and weathered stone idol.\n" "It is supposed to grant luck to visitors, but since the stars are already " "smiling upon you, it does nothing." msgstr "" "Ngươi đã tìm thấy má»™t tượng đá cổ xưa và bị phong hóa.\n" "Nó được cho là mang lại may mắn cho du khách, nhưng vì các ngôi sao đã mỉm " "cưá»i vá»›i ngươi nên nó chẳng mang lại tác dụng gì." msgid "" "You've found an ancient and weathered stone idol.\n" "Kissing it is supposed to be lucky, so you do. The stone is very cold to the " "touch." msgstr "" "Ngươi đã tìm thấy má»™t tượng đá cổ xưa và bị phong hóa.\n" "Hôn nó được cho là may mắn, ngươi cÅ©ng vậy. Äá rất lạnh khi chạm vào." msgid "The mermaids silently entice you to return later and be blessed again." msgstr "" "Các nàng tiên cá âm thầm dụ dá»— ngươi quay lại sau và được ban phước lần nữa." msgid "" "The magical, soothing beauty of the Mermaids reaches you and your crew.\n" "Just for a moment, you forget your worries and bask in the beauty of the " "moment.\n" "The mermaids charms bless you with increased luck for your next combat." msgstr "" "Vẻ đẹp huyá»n diệu, êm dịu cá»§a Nàng tiên cá sẽ đến vá»›i ngươi và phi hành Ä‘oàn " "cá»§a ngươi.\n" "Chỉ trong chốc lát, ngươi quên Ä‘i những lo lắng và đắm mình trong vẻ đẹp cá»§a " "khoảnh khắc đó.\n" "Bùa cá»§a nàng tiên cá sẽ giúp ngươi tăng thêm may mắn trong trận chiến tiếp " "theo." msgid "" "You come upon the pyramid of a great and ancient king.\n" "You are tempted to search it for treasure, but all the old stories warn of " "fearful curses and undead guardians.\n" "Will you search?" msgstr "" "Ngươi đến trên kim tá»± tháp cá»§a má»™t vị vua vÄ© đại và cổ xưa.\n" "Ngươi bị cám dá»— để tìm kiếm kho báu, nhưng tất cả những câu chuyện cổ Ä‘á»u " "cảnh báo vá» những lá»i nguyá»n đáng sợ và những ngưá»i bảo vệ bất tá»­.\n" "Ngươi sẽ tìm kiếm chứ?" msgid "" "Upon defeating the monsters, you decipher an ancient glyph on the wall, " "telling the secret of the spell - '" msgstr "" "Sau khi đánh bại lÅ© quái vật, ngươi giải mã được má»™t hình khắc cổ trên " "tưá»ng, nói lên bí mật cá»§a câu thần chú - '" msgid "Unfortunately, you have no Magic Book to record the spell with." msgstr "Rất tiếc, ngươi không có Sách Phép để ghi lại lại những phép thuật." msgid "" "Unfortunately, you do not have the wisdom to understand the spell, and you " "are unable to learn it." msgstr "" "Thật không may, ngươi không có trí tuệ để hiểu câu thần chú và ngươi không " "thể há»c được nó." msgid "" "You come upon the pyramid of a great and ancient king.\n" "Routine exploration reveals that the pyramid is completely empty." msgstr "" "Ngươi đến trên kim tá»± tháp cá»§a má»™t vị vua vÄ© đại và cổ xưa.\n" "Thăm dò định kỳ cho thấy kim tá»± tháp hoàn toàn trống rá»—ng." msgid "" "A drink at the well is supposed to restore your spell points, but you are " "already at maximum." msgstr "" "Äồ uống ở giếng được cho là sẽ khôi phục Ä‘iểm thần chú cá»§a ngươi, nhưng " "ngươi đã đạt mức tối Ä‘a rồi." msgid "A second drink at the well in one day will not help you." msgstr "" "Lần uống thứ hai bên giếng trong má»™t ngày sẽ không giúp ích được gì cho " "ngươi." msgid "A drink from the well has restored your spell points to maximum." msgstr "Äồ uống từ giếng đã khôi phục Ä‘iểm thần chú cá»§a ngươi lên mức tối Ä‘a." msgid "" "\"I'm sorry sir,\" The leader of the soldiers says, \"but you already know " "everything we have to teach.\"" msgstr "" "\"Tôi xin lá»—i thưa ngài,\" Ngưá»i chỉ huy nói, \"nhưng ngài đã biết tất cả " "những gì chúng tôi phải dạy rồi.\"" msgid "The soldiers living in the fort teach you a few new defensive tricks." msgstr "" "Những ngưá»i lính sống trong pháo đài sẽ dạy cho ngươi má»™t số thá»§ thuật phòng " "thá»§ má»›i." msgid "" "You've come upon a mercenary camp practicing their tactics. \"You're too " "advanced for us,\" the mercenary captain says. \"We can teach nothing more.\"" msgstr "" "Ngươi đã gặp má»™t trại lính đánh thuê Ä‘ang thá»±c hiện chiến thuật cá»§a há». " "\"Ngài quá tiến bá»™ so vá»›i chúng tôi,\" đội trưởng lính đánh thuê nói. " "\"Chúng tôi không thể dạy thêm gì nữa.\"" msgid "" "You've come upon a mercenary camp practicing their tactics. The mercenaries " "welcome you and your troops and invite you to train with them." msgstr "" "Ngươi đã gặp má»™t trại lính đánh thuê Ä‘ang thá»±c hành chiến thuật cá»§a há». " "Những ngưá»i lính đánh thuê chào đón ngươi và quân đội cá»§a ngươi và má»i ngươi " "huấn luyện cùng há»." msgid "\"Go 'way!\", the witch doctor barks, \"you know all I know.\"" msgstr "\"Äi Ä‘i!\", phù thá»§y gào lên, \"ngươi biết tất cả những gì Ta biết.\"" msgid "" "An Orcish witch doctor living in the hut deepens your knowledge of magic by " "showing you how to cast stones, read portents, and decipher the intricacies " "of chicken entrails." msgstr "" "Má»™t bác sÄ© phù thá»§y Orcish sống trong túp lá»u giúp ngươi đào sâu kiến thức " "vá» phép thuật bằng cách chỉ cho ngươi cách ném đá, Ä‘á»c Ä‘iá»m báo và giải mã " "sá»± phức tạp cá»§a ruá»™t gà." msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, the Druids turn you away, indicating they have nothing " "new to teach you." msgstr "" "Ngươi đã tìm thấy má»™t nhóm Tu sÄ© Ä‘ang thá» cúng tại má»™t trong những dinh thá»± " "bằng đá kỳ lạ cá»§a há». Im lặng, các Tu sÄ© từ chối ngươi, cho thấy há» không có " "gì má»›i để dạy ngươi." msgid "" "You've found a group of Druids worshipping at one of their strange stone " "edifices. Silently, they teach you new ways to cast spells." msgstr "" "Ngươi đã tìm thấy má»™t nhóm Tu sÄ© Ä‘ang thá» cúng tại má»™t trong những dinh thá»± " "bằng đá kỳ lạ cá»§a há». HỠâm thầm dạy ngươi những cách má»›i để niệm phép." msgid "" "You tentatively approach the burial ground of ancient warriors. Do you want " "to search the graves?" msgstr "" "Ngươi ngập ngừng tiếp cận nÆ¡i chôn cất các chiến binh cổ xưa. Ngươi có muốn " "tìm kiếm những ngôi má»™?" msgid "" "You spend several hours searching the graves and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" "Ngươi dành vài giá» tìm kiếm các ngôi má»™ và không tìm thấy gì. Má»™t hành động " "hèn hạ như vậy làm giảm tinh thần quân đội cá»§a ngươi." msgid "Upon defeating the Zombies you search the graves and find something!" msgstr "" "Sau khi đánh bại các Thây ma, ngươi tìm kiếm các ngôi má»™ và tìm thấy thứ gì " "đó!" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the shipwreck?" msgstr "" "Thân tàu mục nát cá»§a má»™t con tàu cướp biển lá»›n kêu cót két má»™t cách kỳ lạ " "khi nó bị đẩy vào những tảng đá. Ngươi có muốn tìm kiếm con tàu đắm?" msgid "" "You spend several hours sifting through the debris and find nothing. Such a " "despicable act reduces your army's morale." msgstr "" "Ngươi dành vài giỠđể sàng lá»c các mảnh vụn và không tìm thấy gì. Má»™t hành " "động hèn hạ như vậy làm giảm tinh thần quân đội cá»§a ngươi." msgid "" "Upon defeating the Ghosts you sift through the debris and find something!" msgstr "" "Sau khi đánh bại các Bóng ma, ngươi sàng lá»c các mảnh vỡ và tìm thấy thứ gì " "đó!" msgid "" "The rotting hulk of a great pirate ship creaks eerily as it is pushed " "against the rocks. Do you wish to search the ship?" msgstr "" "Thân tàu mục nát cá»§a má»™t con tàu cướp biển lá»›n kêu cót két má»™t cách kỳ lạ " "khi nó bị đẩy vào những tảng đá. Ngươi có muốn tìm kiếm con tàu?" msgid "" "Upon defeating the Skeletons you sift through the debris and find something!" msgstr "" "Sau khi đánh bại Bá»™ xương, ngươi sàng lá»c các mảnh vỡ và tìm thấy thứ gì đó!" msgid "Your men spot a navigational buoy, confirming that you are on course." msgstr "" "Ngưá»i cá»§a ngươi phát hiện ra má»™t chiếc phao định hướng, xác nhận rằng ngươi " "Ä‘ang Ä‘i đúng hướng." msgid "" "Your men spot a navigational buoy, confirming that you are on course and " "increasing their morale." msgstr "" "Ngưá»i cá»§a ngươi phát hiện ra má»™t chiếc phao định hướng, xác nhận rằng ngươi " "Ä‘ang Ä‘i đúng hướng và nâng cao tinh thần cá»§a há»." msgid "" "The drink at the oasis is refreshing, but offers no further benefit. The " "oasis might help again if you fought a battle first." msgstr "" "Äồ uống ở ốc đảo rất sảng khoái nhưng không mang lại lợi ích gì thêm. á»c đảo " "có thể giúp ích má»™t lần nữa nếu ngươi chiến đấu trước." msgid "" "A drink at the oasis fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" "Äồ uống tại ốc đảo sẽ tiếp thêm sức mạnh cho quân đội cá»§a ngươi và nâng cao " "tinh thần cá»§a há». Hôm nay ngươi có thể Ä‘i xa hÆ¡n má»™t chút." msgid "" "The drink at the watering hole is refreshing, but offers no further benefit. " "The watering hole might help again if you fought a battle first." msgstr "" "Äồ uống ở hố tưới nước có tác dụng giải khát nhưng không mang lại lợi ích gì " "thêm. Lá»— tưới nước có thể giúp ích má»™t lần nữa nếu ngươi chiến đấu trước." msgid "" "A drink at the watering hole fills your troops with strength and lifts their " "spirits. You can travel a bit further today." msgstr "" "Äồ uống tại hố tưới nước sẽ tiếp thêm sức mạnh cho quân đội cá»§a ngươi và " "nâng cao tinh thần cá»§a há». Hôm nay ngươi có thể Ä‘i xa hÆ¡n má»™t chút." msgid "" "It doesn't help to pray twice before a battle. Come back after you've fought." msgstr "" "Cầu nguyện hai lần trước trận chiến chẳng ích gì. Hãy quay lại sau khi chiến " "đấu xong." msgid "A visit and a prayer at the temple raises the morale of your troops." msgstr "" "Má»™t chuyến viếng thăm và cầu nguyện tại ngôi Ä‘á»n sẽ nâng cao tinh thần cho " "quân đội cá»§a ngươi." msgid "" "An old Knight appears on the steps of the gazebo. \"I am sorry, my liege, I " "have taught you all I can.\"" msgstr "" "Má»™t Hiệp sÄ© già xuất hiện trên bậc thá»m cá»§a vá»ng lâu. \"Tôi xin lá»—i, thưa bệ " "hạ, Tôi đã dạy ngài tất cả những gì có thể.\"" msgid "" "An old Knight appears on the steps of the gazebo. \"My liege, I will teach " "you all that I know to aid you in your travels.\"" msgstr "" "Má»™t Hiệp sÄ© già xuất hiện trên bậc thá»m cá»§a vá»ng lâu. \"Thưa bệ hạ, Tôi sẽ " "dạy cho ngài tất cả những gì tôi biết để há»— trợ ngài trong chuyến Ä‘i.\"" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he says, \"I would give you an artifact as a reward, but " "you're all full.\"" msgstr "" "Ngươi đã cứu má»™t ngưá»i sống sót sau vụ đắm tàu khá»i cái chết nhất định trong " "má»™t đại dương khắc nghiệt. Biết Æ¡n, anh ấy nói, \"Tôi sẽ tặng ngài má»™t hiện " "vật như má»™t phần thưởng, nhưng ngài đã đầy túi rồi.\"" msgid "" "You've pulled a shipwreck survivor from certain death in an unforgiving " "ocean. Grateful, he rewards you for your act of kindness by giving you the " "%{art}." msgstr "" "Ngươi đã cứu má»™t ngưá»i sống sót sau vụ đắm tàu khá»i cái chết nhất định trong " "má»™t đại dương khắc nghiệt. Biết Æ¡n, anh ấy thưởng cho ngươi vì hành động tá»­ " "tế cá»§a ngươi bằng cách tặng ngươi %{art}." msgid "A leprechaun offers you the %{art} for the small price of %{gold} Gold." msgstr "" "Má»™t yêu tinh cung cấp cho ngươi %{art} vá»›i mức giá nhá» là %{gold} Vàng." msgid "" "A leprechaun offers you the %{art} for the small price of %{gold} Gold and " "%{count} %{res}." msgstr "" "Má»™t yêu tinh cung cấp cho ngươi %{art} vá»›i mức giá nhá» là %{gold} Vàng và " "%{count} %{res}." msgid "Do you wish to buy this artifact?" msgstr "Ngươi có chắc muốn mua bảo vật này?" msgid "" "You try to pay the leprechaun, but realize that you can't afford it. The " "leprechaun stamps his foot and ignores you." msgstr "" "Ngươi cố gắng trả tiá»n cho yêu tinh, nhưng nhận ra rằng ngươi không đủ khả " "năng chi trả. Yêu tinh dậm chân và phá»›t lá» ngươi." msgid "" "Insulted by your refusal of his generous offer, the leprechaun stamps his " "foot and ignores you." msgstr "" "Bị xúc phạm bởi sá»± từ chối lá»i đỠnghị hào phóng cá»§a ngươi, yêu tinh dậm " "chân và phá»›t lá» ngươi." msgid "You've found the artifact: " msgstr "Ngươi đã tìm thấy bảo vật: " msgid "" "You've found the humble dwelling of a withered hermit. The hermit tells you " "that he is willing to give the %{art} to the first wise person he meets." msgstr "" "Ngươi đã tìm thấy nÆ¡i ở khiêm tốn cá»§a má»™t ẩn sÄ© khô héo. Vị ẩn sÄ© nói vá»›i " "ngươi rằng ông ấy sẵn sàng trao %{art} cho ngưá»i thông thái đầu tiên mà ông " "ấy gặp." msgid "" "You've come across the spartan quarters of a retired soldier. The soldier " "tells you that he is willing to pass on the %{art} to the first true leader " "he meets." msgstr "" "Ngươi đã Ä‘i qua khu nhà Spartan cá»§a má»™t ngưá»i lính đã nghỉ hưu. Ngưá»i lính " "nói vá»›i ngươi rằng anh ta sẵn sàng truyá»n lại %{art} cho ngưá»i lãnh đạo thá»±c " "sá»± đầu tiên mà anh ta gặp." msgid "" "You've encountered a strange person with a hat and an owl on it. He tells " "you that he is willing to give %{art} if you have %{skill}." msgstr "" "Ngươi đã gặp má»™t ngưá»i lạ vá»›i má»™t chiếc mÅ© và má»™t con cú trên đó. Anh ấy nói " "vá»›i ngươi rằng anh ấy sẵn sàng tặng %{art} nếu ngươi có %{skill}." msgid "" "You come upon an ancient artifact. As you reach for it, a pack of Rogues " "leap out of the brush to guard their stolen loot." msgstr "" "Ngươi bắt gặp má»™t hiện vật cổ xưa. Khi ngươi vá»›i lấy nó, má»™t nhóm Lừa đảo " "nhảy ra khá»i bụi rậm để bảo vệ chiến lợi phẩm bị đánh cắp cá»§a chúng." msgid "" "Through a clearing you observe an ancient artifact. Unfortunately, it's " "guarded by a nearby %{monster}. Do you want to fight the %{monster} for the " "artifact?" msgstr "" "Thông qua má»™t khoảng trống, ngươi quan sát được má»™t hiện vật cổ xưa. Thật " "không may, nó được bảo vệ bởi má»™t %{monster} gần đó. Ngươi có muốn chiến đấu " "vá»›i %{monster} để giành cổ vật không?" msgid "Victorious, you take your prize, the %{art}." msgstr "Chiến thắng, ngươi nhận được giải thưởng cá»§a mình, %{art}." msgid "" "Discretion is the better part of valor, and you decide to avoid this fight " "for today." msgstr "" "Sá»± thận trá»ng là phần tốt hÆ¡n cá»§a lòng dÅ©ng cảm và ngươi quyết định tránh " "cuá»™c chiến này ngay hôm nay." msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold pieces." msgstr "" "Sau hàng giá» cố gắng câu chiếc rương ngoài biển, ngươi mở nó ra và tìm thấy " "%{gold} vàng." msgid "" "After spending hours trying to fish the chest out of the sea, you open it " "and find %{gold} gold and the %{art}." msgstr "" "Sau hàng giá» cố gắng câu chiếc rương ngoài biển, ngươi mở nó ra và tìm thấy " "%{gold} vàng và %{art}." msgid "" "After spending hours trying to fish the chest out of the sea, you open it, " "only to find it empty." msgstr "" "Sau hàng giá» cố gắng câu chiếc rương ngoài biển, ngươi mở nó ra, và chỉ thấy " "nó trống rá»—ng." msgid "" "After scouring the area, you fall upon a hidden treasure cache. You may take " "the gold or distribute the gold to the peasants for experience. Do you wish " "to keep the gold?" msgstr "" "Sau khi lùng sục khắp khu vá»±c, ngươi sẽ phát hiện ra má»™t kho báu ẩn giấu. " "Ngươi có thể lấy vàng hoặc phân phát vàng cho nông dân để lấy kinh nghiệm. " "Ngươi có muốn giữ vàng không?" msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "%{gold} gold pieces." msgstr "" "Sau khi lùng sục khắp khu vá»±c, ngươi sẽ tìm thấy má»™t chiếc rương ẩn chứa " "những %{gold} miếng vàng." msgid "" "After scouring the area, you fall upon a hidden chest, containing the " "ancient artifact %{art}." msgstr "" "Sau khi lùng sục khắp khu vá»±c, ngươi sẽ tìm thấy má»™t chiếc rương ẩn chứa cổ " "vật %{art}." msgid "" "You stumble upon a dented and tarnished lamp lodged deep in the earth. Do " "you wish to rub the lamp?" msgstr "" "Ngươi vấp phải má»™t chiếc đèn bị móp và hoen ố nằm sâu trong lòng đất. Ngươi " "có muốn chà đèn không?" msgid "" "You have taken control of the local Alchemist shop. It will provide you with " "%{count} unit of Mercury per day." msgstr "" "Ngươi đã nắm quyá»n kiểm soát cá»­a hàng Nhà giả kim địa phương. Nó sẽ cung cấp " "cho ngươi %{count} đơn vị Thá»§y ngân má»—i ngày." msgid "" "You gain control of a sawmill. It will provide you with %{count} units of " "wood per day." msgstr "" "Ngươi giành được quyá»n kiểm soát má»™t xưởng cưa. Nó sẽ cung cấp cho ngươi " "%{count} đơn vị gá»— má»—i ngày." msgid "" "You gain control of an ore mine. It will provide you with %{count} units of " "ore per day." msgstr "" "Ngươi giành quyá»n kiểm soát má»™t má» quặng. Nó sẽ cung cấp cho ngươi %{count} " "đơn vị quặng má»—i ngày." msgid "" "You gain control of a sulfur mine. It will provide you with %{count} unit of " "sulfur per day." msgstr "" "Ngươi giành quyá»n kiểm soát má»™t má» lưu huỳnh. Nó sẽ cung cấp cho ngươi " "%{count} đơn vị lưu huỳnh má»—i ngày." msgid "" "You gain control of a crystal mine. It will provide you with %{count} unit " "of crystal per day." msgstr "" "Ngươi giành được quyá»n kiểm soát má»™t má» tinh thể. Nó sẽ cung cấp cho ngươi " "%{count} đơn vị pha lê má»—i ngày." msgid "" "You gain control of a gem mine. It will provide you with %{count} unit of " "gems per day." msgstr "" "Ngươi giành quyá»n kiểm soát má»™t mỠđá quý. Nó sẽ cung cấp cho ngươi %{count} " "đơn vị đá quý má»—i ngày." msgid "" "You gain control of a gold mine. It will provide you with %{count} gold per " "day." msgstr "" "Ngươi giành được quyá»n kiểm soát má»™t má» vàng. Nó sẽ cung cấp cho ngươi " "%{count} vàng má»—i ngày." msgid "" "The lighthouse is now under your control, and all of your ships will now " "move further each day." msgstr "" "Ngá»n hải đăng hiện nằm dưới sá»± kiểm soát cá»§a ngươi và tất cả các tàu cá»§a " "ngươi giỠđây sẽ di chuyển xa hÆ¡n má»—i ngày." msgid "You gain control of a %{name}." msgstr "Ngươi giành được quyá»n kiểm soát má»™t %{name}." msgid "" "You come upon an abandoned gold mine. The mine appears to be haunted. Do you " "wish to enter?" msgstr "" "Ngươi đến má»™t má» vàng bị bá» hoang. Khu má» dưá»ng như bị ma ám. Ngươi có muốn " "vào không?" msgid "You beat the Ghosts and are able to restore the mine to production." msgstr "Ngươi đánh bại các Bóng ma và có thể khôi phục mỠđể sản xuất." msgid "" "A group of %{monster} with a desire for greater glory wish to join you. Do " "you accept?" msgstr "" "Má»™t nhóm %{monster} khao khát đạt được vinh quang lá»›n hÆ¡n mong muốn được " "tham gia cùng ngươi. Ngươi có chấp nhận không?" msgid "As you approach the dwelling, you notice that there is no one here." msgstr "Khi ngươi đến gần ngôi nhà, ngươi nhận thấy rằng không có ai ở đây." msgid "" "You search the ruins, but the Medusas that used to live here are gone. " "Perhaps there will be more next week." msgstr "" "Ngươi tìm kiếm những tàn tích, nhưng Medusas từng sống ở đây đã biến mất. Có " "lẽ sẽ có nhiá»u hÆ¡n vào tuần tá»›i." msgid "" "You've found some Medusas living in the ruins. They are willing to join your " "army for a price. Do you want to recruit Medusas?" msgstr "" "Ngươi đã tìm thấy má»™t số Medusa sống trong đống đổ nát. Há» sẵn sàng gia nhập " "quân đội cá»§a ngươi vá»›i má»™t mức giá. Ngươi có muốn tuyển dụng Medusas?" msgid "" "You've found a Sprite Tree City. Unfortunately, none of the Sprites living " "there wish to join an army. Maybe next week." msgstr "" "Ngươi đã tìm thấy Sprite Tree City. Thật không may, không má»™t Sprite nào " "sống ở đó mong muốn gia nhập quân đội. Có thể là tuần sau." msgid "" "Some of the Sprites living in the tree city are willing to join your army " "for a price. Do you want to recruit Sprites?" msgstr "" "Má»™t số Sprites sống trong thành phố trên cây sẵn sàng gia nhập quân đội cá»§a " "ngươi vá»›i má»™t cái giá. Ngươi có muốn tuyển dụng Sprites?" msgid "" "A colorful Rogues' wagon stands empty here. Perhaps more Rogues will be here " "later." msgstr "" "Ở đây có má»™t toa xe đầy màu sắc cá»§a Lừa đảo trống rá»—ng. Có lẽ sau này sẽ có " "nhiá»u Lừa đảo hÆ¡n ở đây." msgid "" "Distant sounds of music and laughter draw you to a colorful wagon housing " "Rogues. Do you wish to have any Rogues join your army?" msgstr "" "Âm thanh xa xa cá»§a âm nhạc và tiếng cưá»i lôi kéo ngươi đến má»™t toa xe đầy " "màu sắc chứa Lừa đảo. Ngươi có muốn có bất kỳ Lừa đảo nào gia nhập quân đội " "cá»§a mình không?" msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. The " "tents are unoccupied. Perhaps more Nomads will be here later." msgstr "" "Má»™t nhóm lá»u rách nát cuồn cuá»™n trong gió cát Ä‘ang vẫy gá»i ngươi. Những căn " "lá»u không có ngưá»i ở. Có lẽ sau này sẽ có thêm nhiá»u ngưá»i du mục đến đây." msgid "" "A group of tattered tents, billowing in the sandy wind, beckons you. Do you " "wish to have any Nomads join you during your travels?" msgstr "" "Má»™t nhóm lá»u rách nát cuồn cuá»™n trong gió cát Ä‘ang vẫy gá»i ngươi. Ngươi có " "muốn có bất kỳ Ngưá»i du mục nào tham gia cùng ngươi trong chuyến du lịch cá»§a " "ngươi không?" msgid "The pit of mud bubbles for a minute and then lies still." msgstr "Hố bùn sá»§i bá»t má»™t phút rồi nằm yên." msgid "" "As you approach the bubbling pit of mud, creatures begin to climb out and " "position themselves around it. In unison they say: \"Mother Earth would like " "to offer you a few of her troops. Do you want to recruit Earth Elementals?\"" msgstr "" "Khi ngươi đến gần hố bùn sá»§i bá»t, các sinh vật bắt đầu trèo ra ngoài và định " "vị xung quanh nó. HỠđồng thanh nói: \"Mẹ Trái đất muốn cung cấp cho ngươi " "má»™t số quân cá»§a bà ấy. Ngươi có muốn chiêu má»™ các Nguyên tố Trái đất không?\"" msgid "You enter the structure of white stone pillars, and find nothing." msgstr "Ngươi bước vào cấu trúc cá»§a những cá»™t đá trắng và không tìm thấy gì." msgid "" "White stone pillars support a roof that rises up to the sky. As you enter " "the structure, the dead air of the outside gives way to a whirling gust that " "almost pushes you back out. The air current materializes into a barely " "visible form. The creature asks, in what can only be described as a loud " "whisper: \"Why have you come? Are you here to call upon the forces of the " "air?\"" msgstr "" "Những cá»™t đá trắng đỡ mái nhà vươn lên trá»i. Khi ngươi bước vào công trình, " "không khí chết chóc bên ngoài nhưá»ng chá»— cho má»™t cÆ¡n gió xoáy gần như đẩy " "ngươi ra ngoài. Dòng không khí hiện thá»±c hóa thành má»™t dạng khó nhìn thấy " "được. Sinh vật há»i, theo cách chỉ có thể được mô tả như má»™t lá»i thì thầm " "lá»›n: \"Tại sao ngươi lại đến? Ngươi đến đây để kêu gá»i lá»±c lượng trên không?" "\"" msgid "No Fire Elementals approach you from the lava pool." msgstr "Không có Nguyên tố Lá»­a nào tiếp cận ngươi từ hồ dung nham." msgid "" "Beneath a structure that serves to hold in heat, Fire Elementals move about " "in a fiery pool of molten lava. A group of them approach you and offer their " "services. Would you like to recruit Fire Elementals?" msgstr "" "Bên dưới má»™t cấu trúc dùng để giữ nhiệt, các Nguyên tố Lá»­a di chuyển trong " "má»™t hồ dung nham nóng chảy rá»±c lá»­a. Má»™t nhóm trong số há» tiếp cận ngươi và " "cung cấp dịch vụ cá»§a há». Ngươi có muốn chiêu má»™ Nguyên tố Lá»­a không?" msgid "A face forms in the water for a moment, and then is gone." msgstr "Má»™t khuôn mặt xuất hiện trong nước má»™t lúc rồi biến mất." msgid "" "Crystalline structures cast shadows over a small reflective pool of water. " "You peer into the pool, and a face that is not your own peers back. It asks: " "\"Would you like to call upon the powers of water?\"" msgstr "" "Các cấu trúc tinh thể đổ bóng lên má»™t hồ nước nhá» phản chiếu. Ngươi nhìn " "xuống hồ bÆ¡i, và má»™t khuôn mặt không phải là ngươi bè cá»§a ngươi quay lại. Nó " "há»i: \"Ngươi có muốn kêu gá»i sức mạnh cá»§a nước không?\"" msgid "This burial site is deathly still." msgstr "NÆ¡i chôn cất này vẫn còn chết chóc." msgid "" "Restless spirits of long dead warriors seeking their final resting place " "offer to join you in hopes of finding peace. Do you wish to recruit ghosts?" msgstr "" "Linh hồn không ngừng nghỉ cá»§a những chiến binh đã chết từ lâu Ä‘ang tìm kiếm " "nÆ¡i an nghỉ cuối cùng đỠnghị tham gia cùng ngươi vá»›i hy vá»ng tìm thấy hòa " "bình. Ngươi có muốn chiêu má»™ bóng ma không?" msgid "" "The City of the Dead is empty of life, and empty of unlife as well. Perhaps " "some undead will move in next week." msgstr "" "Thành phố cá»§a Ngưá»i chết không có sá»± sống và cÅ©ng không có sá»± sống. Có lẽ " "má»™t số xác sống sẽ chuyển đến vào tuần tá»›i." msgid "" "Some Liches living here are willing to join your army for a price. Do you " "want to recruit Liches?" msgstr "" "Má»™t số Lich sống ở đây sẵn sàng gia nhập quân đội cá»§a ngươi vá»›i má»™t cái giá. " "Ngươi có muốn tuyển dụng Liches không?" msgid "" "You've found the ruins of an ancient city, now inhabited solely by the " "undead. Will you search?" msgstr "" "Ngươi đã tìm thấy tàn tích cá»§a má»™t thành phố cổ, hiện chỉ có xác sống sinh " "sống. Ngươi sẽ tìm kiếm chứ?" msgid "" "Some of the surviving Liches are impressed by your victory over their " "fellows, and offer to join you for a price. Do you want to recruit Liches?" msgstr "" "Má»™t số Lich còn sống sót bị ấn tượng bởi chiến thắng cá»§a ngươi trước đồng " "đội cá»§a há» và đỠnghị tham gia cùng ngươi vá»›i má»™t mức giá. Ngươi có muốn " "tuyển dụng Liches không?" msgid "" "You've found one of those bridges that Trolls are so fond of living under, " "but there are none here. Perhaps there will be some next week." msgstr "" "Ngươi đã tìm thấy má»™t trong những cây cầu mà Trolls rất thích sống dưới đó, " "nhưng ở đây lại không có má»™t con nào cả. Có lẽ sẽ có má»™t số vào tuần tá»›i." msgid "" "Some Trolls living under a bridge are willing to join your army, but for a " "price. Do you want to recruit Trolls?" msgstr "" "Má»™t số Troll sống dưới cầu sẵn sàng gia nhập quân đội cá»§a ngươi nhưng phải " "trả giá. Ngươi có muốn tuyển dụng Troll không?" msgid "Trolls living under the bridge challenge you. Will you fight them?" msgstr "" "Troll sống dưới gầm cầu thách thức ngươi. Ngươi sẽ chiến đấu vá»›i chúng?" msgid "" "A few Trolls remain, cowering under the bridge. They approach you and offer " "to join your forces as mercenaries. Do you want to buy any Trolls?" msgstr "" "Má»™t vài con Troll vẫn còn ẩn nấp dưới gầm cầu. Há» tiếp cận ngươi và đỠnghị " "gia nhập lá»±c lượng cá»§a ngươi vá»›i tư cách là lính đánh thuê. Ngươi có muốn " "mua Troll không?" msgid "" "The Dragon city has no Dragons willing to join you this week. Perhaps a " "Dragon will become available next week." msgstr "" "Thành phố Rồng không có con Rồng nào sẵn sàng tham gia cùng ngươi trong tuần " "này. Có lẽ má»™t con Rồng sẽ có mặt vào tuần tá»›i." msgid "" "The Dragon city is willing to offer some Dragons for your army for a price. " "Do you wish to recruit Dragons?" msgstr "" "Thành phố Rồng sẵn sàng cung cấp má»™t số Rồng cho quân đội cá»§a ngươi vá»›i má»™t " "mức giá. Ngươi có muốn chiêu má»™ Rồng không?" msgid "" "You stand before the Dragon City, a place off-limits to mere humans. Do you " "wish to violate this rule and challenge the Dragons to a fight?" msgstr "" "Ngươi đứng trước Thành phố Rồng, má»™t nÆ¡i không có giá»›i hạn đối vá»›i con " "ngưá»i. Ngươi có muốn vi phạm quy tắc này và thách đấu vá»›i những con Rồng " "không?" msgid "" "Having defeated the Dragon champions, the city's leaders agree to supply " "some Dragons to your army for a price. Do you wish to recruit Dragons?" msgstr "" "Sau khi đánh bại các nhà vô địch Rồng, lãnh đạo thành phố đồng ý cung cấp " "má»™t số Rồng cho quân đội cá»§a ngươi vá»›i má»™t mức giá. Ngươi có muốn chiêu má»™ " "Rồng không?" msgid "From the observation tower, you are able to see distant lands." msgstr "Từ tháp quan sát, ngươi có thể nhìn thấy những vùng đất xa xôi." msgid "" "The spring only refills once a week, and someone's already been here this " "week." msgstr "" "Suối chỉ được nạp lại má»—i tuần má»™t lần, và tuần này đã có ngưá»i đến đây rồi." msgid "" "A drink at the spring is supposed to give you twice your normal spell " "points, but you are already at that level." msgstr "" "Uống nước ở suối được cho là sẽ mang lại cho ngươi gấp đôi Ä‘iểm thần chú " "bình thưá»ng, nhưng ngươi đã ở cấp độ đó rồi." msgid "" "A drink from the spring fills your blood with magic! You have twice your " "normal spell points in reserve." msgstr "" "Uống nước ở suối làm cho máu cá»§a ngươi tràn đầy ma thuật! Ngươi có gấp đôi " "số Ä‘iểm phép thuật bình thưá»ng để dá»± trữ." msgid "" "Recognizing you, the butler refuses to admit you. \"The master,\" he says, " "\"will not see the same student twice.\"" msgstr "" "Nhận ra ngươi, ngưá»i quản gia từ chối thừa nhận ngươi. Ông nói: \"Ngưá»i thầy " "sẽ không gặp má»™t há»c sinh hai lần\"." msgid "" "The butler admits you to see the master of the house. He trains you in the " "four skills a hero should know." msgstr "" "Ngưá»i quản gia cho phép ngươi đến gặp chá»§ nhân cá»§a ngôi nhà. Anh ấy huấn " "luyện ngươi bốn kỹ năng mà má»™t tướng nên biết." msgid "" "The butler opens the door and looks you up and down. \"You are neither " "famous nor diplomatic enough to be admitted to see my master,\" he sniffs. " "\"Come back when you think yourself worthy.\"" msgstr "" "Ngưá»i quản gia mở cá»­a và nhìn ngươi từ trên xuống dưới. \"Ngươi không đủ nổi " "tiếng cÅ©ng như không đủ tài ngoại giao để được nhận vào gặp chá»§ nhân cá»§a tôi," "\" anh khịt mÅ©i. \"Hãy quay lại khi ngươi thấy mình xứng đáng.\"" msgid " and " msgstr " và " msgid "" "All of the %{monsters} you have in your army have been trained by the battle " "masters of the fort. Your army now contains %{monsters2}." msgstr "" "Tất cả %{monsters} ngươi có trong quân đội cá»§a mình Ä‘á»u đã được huấn luyện " "bởi các bậc thầy chiến đấu cá»§a pháo đài. Quân đội cá»§a ngươi hiện có " "%{monsters2}." msgid "" "An unusual alliance of Ogres, Orcs, and Dwarves offer to train (upgrade) any " "such troops brought to them. Unfortunately, you have none with you." msgstr "" "Má»™t liên minh bất thưá»ng cá»§a Yêu tinh, Orc và Ngưá»i lùn đỠnghị huấn luyện " "(nâng cấp) bất kỳ đội quân nào như vậy được mang đến cho há». Thật không may, " "ngươi không có gì bên mình." msgid "All of your %{monsters} have been upgraded into %{monsters2}." msgstr "Tất cả %{monsters} cá»§a ngươi đã được nâng cấp thành %{monsters2}." msgid "" "A blacksmith working at the foundry offers to convert all Pikemen and " "Swordsmen's weapons brought to him from iron to steel. He also says that he " "knows a process that will convert Iron Golems into Steel Golems. " "Unfortunately, you have none of these troops in your army, so he can't help " "you." msgstr "" "Má»™t thợ rèn làm việc tại xưởng đúc đỠnghị chuyển đổi tất cả vÅ© khí cá»§a " "Pikemen và Swordsmen mang đến cho anh ta từ sắt thành thép. Anh ấy cÅ©ng nói " "rằng anh ấy biết má»™t quy trình sẽ chuyển đổi Golem sắt thành Golem thép. " "Thật không may, quân đội cá»§a ngươi không có những đội quân này nên anh ta " "không thể giúp ngươi." msgid "" "The captain looks at you with surprise and says:\n" "\"You already have all the maps I know about. Let me fish in peace now.\"" msgstr "" "Thuyá»n trưởng nhìn ngươi ngạc nhiên và nói:\n" "\"Ngươi đã có tất cả các bản đồ mà ta biết. Hãy để ta câu cá trong yên bình." "\"" msgid "" "A retired captain living on this refurbished fishing platform offers to sell " "you maps of the sea he made in his younger days for 1,000 gold. Do you wish " "to buy the maps?" msgstr "" "Má»™t thuyá»n trưởng đã nghỉ hưu sống trên giàn đánh cá đã được tân trang lại " "này đỠnghị bán cho ngươi những bản đồ biển mà ông ấy đã làm khi còn trẻ vá»›i " "giá 1.000 vàng. Ngươi có muốn mua bản đồ không?" msgid "" "The captain sighs. \"You don't have enough money, eh? You can't expect me " "to give my maps away for free!\"" msgstr "" "Thuyá»n trưởng thở dài. \"Ngươi không có đủ tiá»n phải không? Ngươi không thể " "mong đợi tôi tặng bản đồ cá»§a mình miá»…n phí!\"" msgid "" "You come upon an obelisk made from a type of stone you have never seen " "before. Staring at it intensely, the smooth surface suddenly changes to an " "inscription. The inscription is a piece of a lost ancient map. Quickly you " "copy down the piece and the inscription vanishes as abruptly as it appeared." msgstr "" "Ngươi bắt gặp má»™t đài tưởng niệm được làm từ má»™t loại đá mà ngươi chưa từng " "thấy trước đây. Nhìn chăm chú vào nó, bá» mặt nhẵn nhụi đột nhiên biến thành " "má»™t dòng chữ. Dòng chữ là má»™t mảnh cá»§a má»™t bản đồ cổ bị thất lạc. Ngươi " "nhanh chóng sao chép tác phẩm xuống và dòng chữ biến mất đột ngá»™t như khi nó " "xuất hiện." msgid "You have already been to this obelisk." msgstr "Ngươi đã tá»›i đài tưởng niệm này." msgid "" "Upon your approach, the tree opens its eyes in delight. \"It is good to see " "you, my student. I hope my teachings have helped you.\"" msgstr "" "Khi ngươi đến gần, cái cây mở mắt vui mừng. \"Thật vui được gặp lại ngươi, " "há»c trò cá»§a ta. Ta hy vá»ng những lá»i dạy cá»§a ta đã giúp ích cho ngươi.\"" msgid "" "Upon your approach, the tree opens its eyes in delight. \"Ahh, an " "adventurer! Allow me to teach you a little of what I have learned over the " "ages.\"" msgstr "" "Khi ngươi đến gần, cái cây mở mắt vui mừng. \"À, má»™t nhà thám hiểm! Cho phép " "ta dạy cho ngươi má»™t chút vá» những gì ta đã há»c được qua nhiá»u thá»i đại.\"" msgid "Upon your approach, the tree opens its eyes in delight." msgstr "Khi ngươi đến gần, cái cây mở mắt vui mừng." msgid "" "\"Ahh, an adventurer! I will be happy to teach you a little of what I have " "learned over the ages for a mere %{count} %{res}.\"" msgstr "" "\"À, má»™t nhà thám hiểm! Cho phép ta dạy cho ngươi má»™t chút vá» những gì ta đã " "há»c được qua nhiá»u thá»i đại vá»›i chỉ %{count} %{res}.\"" msgid "(Just bury it around my roots.)" msgstr "(Hãy chôn nó quanh rá»… cá»§a tôi.)" msgid "Tears brim in the eyes of the tree." msgstr "Nước mắt giàn giụa trong mắt cây." msgid "\"I need %{count} %{res}.\"" msgstr "\"Ta cần %{count} %{res}.\"" msgid "it whispers. (sniff) \"Well, come back when you can pay me.\"" msgstr "" "nó thì thầm. (khịt mÅ©i) \"Chà, hãy quay lại khi ngươi có thể trả tiá»n cho ta." "\"" msgid "" "Nestled among the trees sits a blind seer. After you explain the intent of " "your journey, the seer activates his crystal ball, allowing you to see the " "strengths and weaknesses of your opponents." msgstr "" "Ẩn mình giữa những tán cây là má»™t nhà tiên tri mù. Sau khi ngươi giải thích " "mục đích chuyến hành trình cá»§a mình, nhà tiên tri sẽ kích hoạt quả cầu pha " "lê cá»§a mình, cho phép ngươi nhìn thấy Ä‘iểm mạnh và Ä‘iểm yếu cá»§a đối thá»§." msgid "" "The entrance to the cave is dark, and a foul, sulfurous smell issues from " "the cave mouth. Will you enter?" msgstr "" "Cá»­a hang tối om, miệng hang bốc ra mùi lưu huỳnh hôi thối. Ngươi sẽ vào chứ?" msgid "Except for evidence of a terrible battle, the cave is empty." msgstr "" "Ngoại trừ bằng chứng vá» má»™t trận chiến khá»§ng khiếp, hang động trống rá»—ng." msgid "" "You find a powerful and grotesque Demon in the cave. \"Today,\" it rasps, " "\"you will fight and surely die. But I will give you a choice of deaths. You " "may fight me, or you may fight my servants. Do you prefer to fight my " "servants?\"" msgstr "" "Ngươi tìm thấy má»™t con quá»· mạnh mẽ và kỳ cục trong hang động. \"Hôm nay,\" " "nó rít lên, \"ngươi sẽ chiến đấu và chắc chắn sẽ chết. Nhưng ta sẽ cho ngươi " "lá»±a chá»n cái chết. Ngươi có thể chiến đấu vá»›i ta, hoặc ngươi có thể chiến " "đấu vá»›i những ngưá»i hầu cá»§a ta. Ngươi thích chiến đấu vá»›i những ngưá»i hầu " "cá»§a ta hÆ¡n?\"" msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points." msgstr "" "Con quá»· hét lên thách thức và tấn công! Sau má»™t trận chiến ngắn ngá»§i và " "tuyệt vá»ng, ngươi tiêu diệt được quái vật và nhận được %{exp} Ä‘iểm kinh " "nghiệm." msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and receive %{exp} experience points and " "%{count} gold." msgstr "" "Con quá»· hét lên thách thức và tấn công! Sau má»™t trận chiến ngắn ngá»§i và " "tuyệt vá»ng, ngươi tiêu diệt được quái vật và nhận được %{exp} Ä‘iểm kinh " "nghiệm và %{count} vàng." msgid "" "The Demon screams its challenge and attacks! After a short, desperate " "battle, you slay the monster and find the %{art} in the back of the cave." msgstr "" "Con quá»· hét lên thách thức và tấn công! Sau má»™t trận chiến ngắn ngá»§i và " "tuyệt vá»ng, ngươi tiêu diệt con quái vật và tìm thấy %{art} ở phía sau hang " "động." msgid "" "Seeing that you do not have %{count} gold, the demon slashes you with its " "claws, and the last thing you see is a red haze." msgstr "" "Thấy ngươi không có %{count} vàng, con quá»· dùng móng vuốt chém ngươi và Ä‘iá»u " "cuối cùng ngươi nhìn thấy là má»™t làn sương mù màu Ä‘á»." msgid "" "The Demon leaps upon you and has its claws at your throat before you can " "even draw your sword. \"Your life is mine,\" it says. \"I will sell it back " "to you for %{count} gold.\"" msgstr "" "Con quá»· lao vào ngươi và ngoạm móng vuốt vào cổ há»ng ngươi trước khi ngươi " "kịp rút kiếm. \"Cuá»™c sống cá»§a ngươi là cá»§a ta,\" nó nói. \"Ta sẽ bán lại cho " "ngươi vá»›i giá %{count} vàng.\"" msgid "" "Upon defeating the daemon's servants, you find a hidden cache with %{count} " "gold." msgstr "" "Sau khi đánh bại những ngưá»i hầu cá»§a quá»·, ngươi sẽ tìm thấy má»™t bá»™ đệm ẩn " "chứa %{count} vàng." msgid "" "As you enter the Alchemist's Tower, a hobbled, graying man in a brown cloak " "makes his way towards you." msgstr "" "Khi ngươi bước vào Tháp Nhà giả kim, má»™t ngưá»i đàn ông xám xịt, tập tá»…nh mặc " "áo choàng màu nâu tiến vá» phía ngươi." msgid "He checks your pack, and sees that you have 1 cursed item." msgid_plural "" "He checks your pack, and sees that you have %{count} cursed items." msgstr[0] "" "Anh ta kiểm tra túi cá»§a ngươi và thấy rằng ngươi có 1 vật phẩm bị nguyá»n rá»§a." msgid "For %{gold} gold, the alchemist will remove it for you. Do you pay?" msgid_plural "" "For %{gold} gold, the alchemist will remove them for you. Do you pay?" msgstr[0] "" "Vá»›i %{gold} vàng, nhà giả kim sẽ loại bá» nó cho ngươi. Ngươi có trả tiá»n " "không?" msgid "" "After you consent to pay the requested amount of gold, the alchemist grabs " "the cursed artifact and throws it into his magical cauldron." msgid_plural "" "After you consent to pay the requested amount of gold, the alchemist grabs " "all cursed artifacts and throws them into his magical cauldron." msgstr[0] "" "Sau khi ngươi đồng ý trả số vàng được yêu cầu, nhà giả kim lấy cổ vật bị " "nguyá»n rá»§a và ném nó vào chiếc vạc ma thuật cá»§a mình." msgid "" "You hear a voice from behind the locked door, \"You don't have enough gold " "to pay for my services.\"" msgstr "" "Ngươi nghe thấy má»™t giá»ng nói từ phía sau cánh cá»­a bị khóa, \"Ngươi không có " "đủ vàng để trả cho dịch vụ cá»§a ta.\"" msgid "" "You hear a voice from high above in the tower, \"Go away! I can't help you!\"" msgstr "" "Ngươi nghe thấy má»™t giá»ng nói từ trên cao trong tòa tháp, \"Biến Ä‘i! Ta " "không thể giúp ngươi!\"" msgid "" "The head groom speaks to you, \"That is a fine looking horse you have. I am " "afraid we can give you no better, but the horses your cavalry are riding " "look to be of poor breeding stock. We have many trained war horses which " "would aid your riders greatly. I insist you take them.\"" msgstr "" "Trưởng nhóm đầy tá»› nói vá»›i ngươi, \"Äó là má»™t con ngá»±a đẹp mà ngài có. Tôi e " "rằng chúng tôi không thể cho ngài tốt hÆ¡n, nhưng những con ngá»±a mà kỵ binh " "cá»§a ngài Ä‘ang cưỡi có vẻ là giống kém chất lượng. Chúng tôi có nhiá»u ngá»±a " "chiến đã được huấn luyện để có thể sá»­ dụng được.\" hãy há»— trợ các tay Ä‘ua " "cá»§a ngươi rất nhiá»u. Ta yêu cầu ngài phải đưa há» Ä‘i.\"" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, he will " "grow tired in a week. You must also let me give better horses to your " "mounted soldiers, their horses look shoddy and weak.\"" msgstr "" "Khi ngươi đến gần chuồng ngá»±a, trưởng nhóm đầy tá»› xuất hiện, dẫn theo má»™t " "con ngá»±a chiến trông rất đẹp mắt. \"Con chiến mã này sẽ giúp ngài di chuyển " "nhanh hÆ¡n. Than ôi, nó sẽ mệt má»i trong má»™t tuần nữa. Ngài cÅ©ng phải để ta " "tặng những con ngá»±a tốt hÆ¡n cho những ngưá»i lính cưỡi ngá»±a cá»§a ngài, ngá»±a " "cá»§a há» trông kém chất lượng và yếu á»›t.\"" msgid "" "The head groom approaches you and speaks, \"You already have a fine horse, " "and have no inexperienced cavalry which might make use of our trained war " "horses.\"" msgstr "" "Trưởng nhóm đầy tá»› đến gần ngươi và nói: \"Ngài đã có má»™t con ngá»±a tốt và " "không có kỵ binh thiếu kinh nghiệm nào có thể sá»­ dụng những con ngá»±a chiến " "đã được huấn luyện cá»§a chúng tôi.\"" msgid "" "As you approach the stables, the head groom appears, leading a fine looking " "war horse. \"This steed will help speed you in your travels. Alas, his " "endurance will wane with a lot of heavy riding, and you must return for a " "fresh mount in a week. We also have many fine war horses which could benefit " "mounted soldiers, but you have none we can help.\"" msgstr "" "Khi ngươi đến gần chuồng ngá»±a, trưởng nhóm đầy tá»› xuất hiện, dẫn theo má»™t " "con ngá»±a chiến trông rất đẹp mắt. “Con chiến mã này sẽ giúp ngài tăng tốc " "trong hành trình. Than ôi, sức chịu đựng cá»§a nó sẽ giảm Ä‘i sau khi cưỡi quá " "nhiá»u, và ngài phải quay lại để lấy má»™t con ngá»±a má»›i trong má»™t tuần. Chúng " "tôi cÅ©ng có nhiá»u ngá»±a chiến tốt có thể mang lại lợi ích cho những ngưá»i " "lính cưỡi ngá»±a, nhưng ngài không có gì chúng tôi có thể giúp được.\"" msgid "The Arena guards turn you away." msgstr "Những ngưá»i bảo vệ Äấu trưá»ng Ä‘uổi ngươi Ä‘i." msgid "" "You have your crew stop up their ears with wax before the sirens' eerie song " "has any chance of luring them to a watery grave." msgstr "" "Ngươi yêu cầu Ä‘oàn cá»§a mình bịt tai bằng sáp trước khi bài hát kỳ lạ cá»§a còi " "báo động có cÆ¡ há»™i dụ há» xuống má»™t ngôi má»™ đầy nước." msgid "" "As the sirens sing their eerie song, your small, determined army manages to " "overcome the urge to dive headlong into the sea." msgstr "" "Khi còi báo động hát bài hát kỳ lạ cá»§a chúng, đội quân nhá» bé nhưng đầy " "quyết tâm cá»§a ngươi đã vượt qua được mong muốn lao thẳng xuống biển." msgid "" "An eerie wailing song emanates from the sirens perched upon the rocks. Many " "of your crew fall under its spell, and dive into the water where they drown. " "You are now wiser for the visit, and gain %{exp} experience." msgstr "" "Má»™t bài hát than khóc kỳ lạ phát ra từ còi báo động đậu trên những tảng đá. " "Nhiá»u ngưá»i trong số thá»§y thá»§ Ä‘oàn cá»§a ngươi rÆ¡i vào bùa chú cá»§a nó và lao " "xuống nước và chết Ä‘uối. Bây giá» ngươi đã khôn ngoan hÆ¡n khi tham quan và " "nhận được %{exp} kinh nghiệm." msgid "" "In a dazzling display of daring, you break into the local jail and free the " "hero imprisoned there, who, in return, pledges loyalty to your cause." msgstr "" "Trong má»™t màn thể hiện táo bạo rá»±c rỡ, ngươi đột nhập vào nhà tù địa phương " "và giải thoát tướng bị giam giữ ở đó, ngưá»i này đổi lại sẽ cam kết trung " "thành vá»›i chính nghÄ©a cá»§a ngươi." msgid "" "You already have %{count} heroes, and regretfully must leave the prisoner in " "this jail to languish in agony for untold days." msgstr "" "Ngươi đã có %{count} tướng rồi, và thật đáng tiếc phải để tù nhân trong nhà " "tù này mòn má»i trong Ä‘au khổ không biết bao ngày." msgid "" "You enter a rickety hut and talk to the magician who lives there. He tells " "you of places near and far which may aid you in your journeys." msgstr "" "Ngươi bước vào má»™t túp lá»u á»p ẹp và nói chuyện vá»›i nhà ảo thuật sống ở đó. " "Anh ấy cho ngươi biết vá» những địa Ä‘iểm gần xa có thể há»— trợ ngươi trong " "cuá»™c hành trình." msgid "This eye seems to be intently studying its surroundings." msgstr "Con mắt này dưá»ng như Ä‘ang chăm chú nghiên cứu môi trưá»ng xung quanh." msgid "You come across a giant Sphinx. The Sphinx remains strangely quiet." msgstr "" "Ngươi bắt gặp má»™t tượng Nhân sư khổng lồ. Tượng Nhân Sư vẫn im lặng má»™t cách " "kỳ lạ." msgid "" "\"I have a riddle for you,\" the Sphinx says. \"Answer correctly, and you " "shall be rewarded. Answer incorrectly, and you shall be eaten. Do you accept " "the challenge?\"" msgstr "" "“Ta có má»™t câu đố dành cho ngươi,†Nhân sư nói. \"Trả lá»i đúng sẽ được khen " "thưởng. Trả lá»i sai sẽ bị ăn thịt. Ngươi có chấp nhận thá»­ thách không?\"" msgid "" "The Sphinx asks you the following riddle:\n" "\n" "'%{riddle}'\n" "\n" "Your answer?" msgstr "" "Nhân sư há»i ngươi câu đố sau:\n" " \n" "'%{riddle}'\n" " \n" "Câu trả lá»i cá»§a ngươi?" msgid "" "\"You guessed incorrectly,\" the Sphinx says, smiling. The Sphinx swipes at " "you with a paw, knocking you to the ground. Another blow makes the world go " "black, and you know no more." msgstr "" "\"Ngươi Ä‘oán sai rồi,\" Nhân sư mỉm cưá»i nói. Nhân sư dùng chân quẹt vào " "ngươi, khiến ngươi ngã xuống đất. Má»™t cú đánh nữa khiến thế giá»›i trở nên Ä‘en " "tối và ngươi không còn biết gì nữa." msgid "" "Looking somewhat disappointed, the Sphinx sighs. \"You've answered my riddle " "so here's your reward. Now begone.\"" msgstr "" "Trông có vẻ thất vá»ng, Nhân sư thở dài. \"Ngươi đã trả lá»i câu đố cá»§a tôi " "nên đây là phần thưởng cá»§a ngươi. Bây giá» hãy biến Ä‘i.\"" msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "As you speak the magic word, the glowing barrier dissolves into nothingness." msgstr "" "Má»™t rào cản ma thuật sừng sững trước mặt ngươi, chặn đưá»ng ngươi. Chữ trên " "vòm Ä‘á»c,\n" "\"Nói chìa khóa và ngươi có thể vượt qua.\"\n" "Khi ngươi nói từ ma thuật, rào cản phát sáng sẽ tan biến vào hư vô." msgid "" "A magical barrier stands tall before you, blocking your way. Runes on the " "arch read,\n" "\"Speak the key and you may pass.\"\n" "You speak, and nothing happens." msgstr "" "Má»™t rào cản ma thuật sừng sững trước mặt ngươi, chặn đưá»ng ngươi. Chữ trên " "vòm Ä‘á»c,\n" "\"Nói chìa khóa và ngươi có thể vượt qua.\"\n" "Ngươi trả lá»i, nhưng không có gì xảy ra." msgid "" "You enter the tent and see an old woman gazing into a magic gem. She looks " "up and says,\n" "\"In my travels, I have learned much in the way of arcane magic. A great " "oracle taught me his skill. I have the answer you seek.\"" msgstr "" "Ngươi bước vào lá»u và thấy má»™t bà già Ä‘ang nhìn chăm chú vào má»™t viên ngá»c " "ma thuật. Cô ấy nhìn lên và nói:\n" "\"Trong những chuyến Ä‘i cá»§a mình, tôi đã há»c được nhiá»u Ä‘iá»u vá» phép thuật " "phức tạp. Má»™t nhà tiên tri vÄ© đại đã dạy tôi kỹ năng cá»§a ông ấy. Ta có câu " "trả lá»i mà ngươi Ä‘ang tìm kiếm.\"" msgid "No spell book is present." msgstr "Không có cuốn sách thần chú nào hiện diện." msgid "" "This spell costs %{mana} spell points. You have no spell points, so you " "cannot cast it." msgstr "" "Phép thuật này tiêu tốn %{mana} Ä‘iểm phép thuật. Ngươi không có Ä‘iểm phép " "thuật nên không thể sá»­ dụng nó." msgid "" "This spell costs %{mana} spell points. You only have %{point} spell points, " "so you cannot cast it." msgstr "" "Phép thuật này tiêu tốn %{mana} Ä‘iểm phép thuật. Ngươi chỉ có %{point} Ä‘iểm " "phép thuật nên không thể sá»­ dụng nó." msgid "The spell was not found." msgstr "Không tìm thấy phép." msgid "Only heroes can cast this spell." msgstr "Chỉ có tướng má»›i có thể niệm phép này." msgid "This hero is not able to cast adventure spells." msgstr "Tướng này không thể sá»­ dụng phép thuật phiêu lưu." msgid "Your hero is too tired to cast this spell today. Try again tomorrow." msgstr "" "Tướng cá»§a ngươi đã quá mệt má»i để sá»­ dụng câu thần chú này ngày hôm nay. Hãy " "thá»­ lại vào ngày mai." msgid "This spell cannot be cast on a boat." msgstr "Phép này không thể dùng trên thuyá»n." msgid "This spell can only be cast near an ocean." msgstr "Phép này chỉ sá»­ dụng nÆ¡i gần đại dương." msgid "" "There are no boats available and no ocean adjacent to the hero where this " "spell will work." msgstr "" "Không có sẵn thuyá»n và không có đại dương gần tướng nÆ¡i phép thuật này sẽ " "hoạt động." msgid "There are no boats available for this spell." msgstr "Không có thuyá»n nào hiện hữu để cho phép này." msgid "There is no ocean adjacent to the hero where this spell will work." msgstr "Không có đại dương nào gần tướng để cho phép này thá»±c hiện." msgid "" "You do not own any town or castle that is not currently occupied by a hero. " "This spell will have no effect." msgstr "" "Ngươi không sở hữu bất kỳ thị trấn hoặc lâu đài nào hiện không có tướng " "chiếm giữ. Phép thuật này sẽ không có tác dụng." msgid "This hero is already in a town, so this spell will have no effect." msgstr "Tướng này đã ở trong thị trấn, nên phép này sẽ không có tác dụng." msgid "" "The nearest town is %{town}.\n" "\n" "This town is occupied by your hero %{hero}." msgstr "" "Thị trấn gần nhất là %{town}.\n" " \n" "Thị trấn này đã bị tướng %{hero} cá»§a ngươi chiếm đóng." msgid "This spell is already in effect." msgstr "Phép này đã có tác dụng rồi." msgid "" "No opponent neither has nor can have any hero under their command anymore. " "Casting this spell will have no effect." msgstr "" "Không có đối thá»§ nào có và không thể có bất kỳ anh hùng nào dưới sá»± chỉ huy " "cá»§a há» nữa. Sá»­ dụng phép thuật này sẽ không có tác dụng." msgid "" "No opponent has a hero under their command at this time. Casting this spell " "will have no effect." msgstr "" "Không có đối thá»§ nào có anh hùng dưới sá»± chỉ huy cá»§a há» vào thá»i Ä‘iểm này. " "Sá»­ dụng phép thuật này sẽ không có tác dụng." msgid "" "You must be within %{count} spaces of a monster for the Visions spell to " "work." msgstr "" "Ngươi phải ở trong khoảng cách %{count} cá»§a má»™t con quái vật để phép thuật " "Thấu thị hoạt động." msgid "" "You must be standing on the entrance to a mine (sawmills and alchemist labs " "do not count) to cast this spell." msgstr "" "Ngươi phải đứng ở lối vào má» (không tính xưởng cưa và phòng thí nghiệm cá»§a " "nhà giả kim) để thá»±c hiện phép thuật này." msgid "You must first defeat the ghosts guarding the mine to cast this spell." msgstr "" "Trước tiên, ngươi phải đánh bại những con ma canh giữ mỠđể sá»­ dụng phép " "thuật này." msgid "" "There are already at least as many elementals guarding the mine as this hero " "can generate. Casting this spell will have no effect." msgstr "" "Äã có ít nhất số lượng nguyên tố bảo vệ khu má» mà tướng này có thể tạo ra. " "Sá»­ dụng phép thuật này sẽ không có tác dụng." msgid "%{name} the %{race} (Level %{level})" msgstr "%{name} chá»§ng tá»™c %{race} (Cấp %{level})" #, fuzzy msgid "" "'Spread' combat formation spreads the hero's units from the top to the " "bottom of the battlefield, with at least one empty space between each unit." msgstr "" "Äá»™i hình chiến đấu 'Dàn trải' dàn quân cá»§a ngươi từ trên xuống dưới cá»§a " "chiến trưá»ng, vá»›i ít nhất má»™t khoảng trống giữa má»—i quân." #, fuzzy msgid "" "'Grouped' combat formation bunches the hero's army together in the center of " "their side of the battlefield." msgstr "" "Äá»™i hình chiến đấu 'Tập trung' tập hợp quân đội cá»§a ngươi lại vá»›i nhau ở " "trung tâm cá»§a phe ngươi trên chiến trưá»ng." msgid "Are you sure you want to dismiss this Hero?" msgstr "Ngươi có chắc chắn muốn giải tán Tướng này không?" msgid "View Experience Info" msgstr "Xem Thông tin Kinh nghiệm" msgid "View Spell Points Info" msgstr "Xem Thông tin Äiểm Phép thuật" msgid "Set army combat formation to 'Spread'" msgstr "Äặt đội hình chiến đấu cá»§a quân đội thành 'Dàn trải'" msgid "Set army combat formation to 'Grouped'" msgstr "Äặt đội hình chiến đấu cá»§a quân đội thành 'Tập trung'" msgid "Exit Hero Screen" msgstr "Thoát Màn hình Tướng" msgid "You cannot dismiss a hero in a castle" msgstr "Ngươi không thể giải tán má»™t tướng bên trong má»™t lâu đài" msgid "Dismissal of %{name} the %{race} is prohibited by scenario" msgstr "Giải tán %{name} chá»§ng tá»™c %{race} bị cấm theo kịch bản" msgid "Dismiss %{name} the %{race}" msgstr "Giải tán %{name} chá»§ng tá»™c %{race}" msgid "Show previous hero" msgstr "Xem tướng trước" msgid "Show next hero" msgstr "Xem tướng sau" msgid "Blood Morale" msgstr "Tinh thần Máu lá»­a" msgid "%{morale} Morale" msgstr "%{morale} Tinh thần" msgid "%{luck} Luck" msgstr "%{luck} May mắn" msgid "Current Luck Modifiers:" msgstr "Sá»­a đổi May mắn Hiện tại:" msgid "Current Morale Modifiers:" msgstr "Sá»­a đổi Tinh thần Hiện tại:" msgid "Entire army is undead, so morale does not apply." msgstr "Toàn bá»™ quân đội là xác sống, vì vậy tinh thần không có tác dụng." msgid "" "Current experience %{exp1}.\n" " Next level %{exp2}." msgstr "" "Kinh nghiệm hiện có %{exp1}.\n" " Cấp tiếp theo %{exp2}." msgid "Level %{level}" msgstr "Cấp bậc %{level}" #, fuzzy msgid "" "%{name} currently has %{point} spell points out of a maximum of %{max}. The " "maximum number of spell points is 10 times the hero's knowledge. It is " "occasionally possible for the hero to have more than their maximum spell " "points via special events." msgstr "" "%{name} hiện có %{point} Ä‘iểm phép thuật trong tổng số Ä‘iểm tối Ä‘a là " "%{max}. Số Ä‘iểm phép thuật tối Ä‘a gấp 10 lần kiến thức cá»§a ngươi. Äôi khi " "ngươi có thể nhận được nhiá»u hÆ¡n Ä‘iểm phép thuật tối Ä‘a thông qua các sá»± " "kiện đặc biệt." msgid "%{name1} meets %{name2}" msgstr "%{name1} gặp %{name2}" msgid "Enemy heroes are now fully identifiable." msgstr "Tướng địch bây giỠđã có thể nhận dạng đầy đủ." msgid "Identify Hero" msgstr "Nhận dạng Tướng" msgid "Select town to port to." msgstr "Chá»n thị trấn để dịch chuyển tá»›i." msgid "Town Portal" msgstr "Hồi Thành" msgid "The creatures are willing to join us!" msgstr "Sinh vật này sẽ tham gia cùng ta!" msgid "All the creatures will join us..." msgstr "Tất cả các sinh vật sẽ tham gia cùng ta..." msgid "The creature will join us..." msgid_plural "%{count} of the creatures will join us..." msgstr[0] "Sinh vật sẽ tham gia cùng ta..." msgid "" "\n" " for a fee of %{gold} gold." msgstr "" "\n" " vá»›i chi phí %{gold} vàng." msgid "These weak creatures will surely flee before us." msgstr "Những sinh vật yếu Ä‘uối này chắc chắn sẽ chạy trốn trước chúng ta." msgid "I fear these creatures are in the mood for a fight." msgstr "Tôi e rằng những sinh vật này Ä‘ang có tâm trạng muốn đánh nhau." #, fuzzy msgid "The hero's attack skill is a bonus added to each unit's attack skill." msgstr "" "Kỹ năng tấn công cá»§a ngươi là phần thưởng được thêm vào kỹ năng tấn công cá»§a " "má»—i sinh vật." #, fuzzy msgid "The hero's defense skill is a bonus added to each unit's defense skill." msgstr "" "Kỹ năng phòng thá»§ cá»§a ngươi là phần thưởng được thêm vào kỹ năng phòng thá»§ " "cá»§a má»—i sinh vật." #, fuzzy msgid "The hero's spell power determines the duration or power of a spell." msgstr "" "Sức mạnh phép thuật cá»§a ngươi quyết định độ dài hoặc sức mạnh cá»§a má»™t câu " "thần chú." #, fuzzy msgid "" " The hero's knowledge determines how many spell points the hero may have. " "Under normal circumstances, a hero is limited to 10 spell points per level " "of knowledge." msgstr "" "Kiến thức cá»§a ngươi quyết định số Ä‘iểm phép thuật mà tướng cá»§a ngươi có thể " "có. Trong trưá»ng hợp bình thưá»ng, má»™t tướng bị giá»›i hạn 10 Ä‘iểm phép thuật " "cho má»—i cấp độ kiến thức." msgid "Current Modifiers:" msgstr "Sá»­a đổi Hiện tại:" msgid "skill|Basic" msgstr "CÆ¡ bản" msgid "skill|Advanced" msgstr "Nâng cao" msgid "skill|Expert" msgstr "Chuyên gia" msgid "Pathfinding" msgstr "Tìm đưá»ng" msgid "Archery" msgstr "Bắn cung" msgid "Logistics" msgstr "Vận chuyển" msgid "Scouting" msgstr "Do thám" msgid "Diplomacy" msgstr "Ngoại giao" msgid "Navigation" msgstr "Dẫn đưá»ng" msgid "Leadership" msgstr "Lãnh đạo" msgid "Wisdom" msgstr "Thông thái" msgid "Mysticism" msgstr "Thần bí" msgid "Ballistics" msgstr "Máy bắn đá" msgid "Eagle Eye" msgstr "Mắt Äại bàng" msgid "Necromancy" msgstr "Chiêu hồn" msgid "Estates" msgstr "Tài sản" msgid "Advanced Archery" msgstr "Nâng cao Bắn cung" msgid "Advanced Pathfinding" msgstr "Nâng cao Tìm đưá»ng" msgid "Basic Archery" msgstr "CÆ¡ bản Bắn cung" msgid "Basic Pathfinding" msgstr "CÆ¡ bản Tìm đưá»ng" msgid "Expert Pathfinding" msgstr "Chuyên gia Tìm đưá»ng" msgid "Advanced Logistics" msgstr "Nâng cao Vận chuyển" msgid "Basic Logistics" msgstr "CÆ¡ bản Vận chuyển" msgid "Basic Scouting" msgstr "CÆ¡ bản Do thám" msgid "Expert Archery" msgstr "Chuyên gia Bắn cung" msgid "Expert Logistics" msgstr "Chuyên gia Vận chuyển" msgid "Advanced Diplomacy" msgstr "Nâng cao Ngoại giao" msgid "Advanced Scouting" msgstr "Nâng cao Do thám" msgid "Basic Diplomacy" msgstr "CÆ¡ bản Ngoại giao" msgid "Expert Diplomacy" msgstr "Chuyên gia Ngoại giao" msgid "Expert Scouting" msgstr "Chuyên gia Do thám" msgid "Advanced Leadership" msgstr "Nâng cao Lãnh đạo" msgid "Advanced Navigation" msgstr "Nâng cao Dẫn đưá»ng" msgid "Basic Leadership" msgstr "CÆ¡ bản Lãnh đạo" msgid "Basic Navigation" msgstr "CÆ¡ bản Dẫn đưá»ng" msgid "Expert Navigation" msgstr "Chuyên gia Dẫn đưá»ng" msgid "Advanced Wisdom" msgstr "Nâng cao Thông thái" msgid "Basic Mysticism" msgstr "CÆ¡ bản Thần bí" msgid "Basic Wisdom" msgstr "CÆ¡ bản Thông thái" msgid "Expert Leadership" msgstr "Chuyên gia Lãnh đạo" msgid "Expert Wisdom" msgstr "Chuyên gia Thông thái" msgid "Advanced Luck" msgstr "Nâng cao May mắn" msgid "Advanced Mysticism" msgstr "Nâng cao Thần bí" msgid "Basic Luck" msgstr "CÆ¡ bản May mắn" msgid "Expert Luck" msgstr "Chuyên gia May mắn" msgid "Expert Mysticism" msgstr "Chuyên gia Thần bí" msgid "Advanced Ballistics" msgstr "Nâng cao Máy bắn đá" msgid "Advanced Eagle Eye" msgstr "Nâng cao Mắt Äại bàng" msgid "Basic Ballistics" msgstr "CÆ¡ bản Máy bắn đá" msgid "Basic Eagle Eye" msgstr "CÆ¡ bản Mắt Äại bàng" msgid "Expert Ballistics" msgstr "Chuyên gia Máy bắn đá" msgid "Advanced Necromancy" msgstr "Nâng cao Chiêu hồn" msgid "Basic Estates" msgstr "CÆ¡ bản Tài sàn" msgid "Basic Necromancy" msgstr "CÆ¡ bản Chiêu hồn" msgid "Expert Eagle Eye" msgstr "Chuyên gia Mắt Äại bàng" msgid "Expert Necromancy" msgstr "Chuyên gia Chiêu hồn" msgid "Advanced Estates" msgstr "Nâng cao Tài sản" msgid "Expert Estates" msgstr "Chuyên gia Tài sản" msgid "" "%{skill} reduces the movement penalty for rough terrain by %{count} percent." msgstr "" "%{skill} giảm hình phạt di chuyển đối vá»›i địa hình gồ ghá» %{count} phần trăm." msgid "%{skill} eliminates the movement penalty for rough terrain." msgstr "%{skill} loại bá» hình phạt di chuyển đối vá»›i địa hình gồ ghá»." #, fuzzy msgid "" "%{skill} increases the damage done by the hero's range attacking creatures " "by %{count} percent." msgstr "" "%{skill} tăng sát thương do các sinh vật tấn công trong phạm vi gây ra thêm " "%{count} phần trăm." #, fuzzy msgid "%{skill} increases the hero's movement points by %{count} percent." msgstr "" "%{skill} tăng Ä‘iểm di chuyển cá»§a anh hùng cá»§a ngươi thêm %{count} phần trăm." #, fuzzy msgid "%{skill} increases the hero's viewable area by one square." msgid_plural "%{skill} increases the hero's viewable area by %{count} squares." msgstr[0] "" "%{skill} tăng diện tích có thể xem cá»§a tướng cá»§a ngươi thêm má»™t ô vuông." #, fuzzy msgid "" "%{skill} allows the hero to negotiate with monsters who are weaker than " "their army, and reduces the cost of surrender." msgstr "" "%{skill} cho phép ngươi đàm phán vá»›i những quái vật yếu hÆ¡n nhóm cá»§a ngươi. " #, fuzzy msgid "" "Approximately %{count} percent of the creatures may offer to join the hero." msgstr "Khoảng %{count} phần trăm sinh vật có thể đỠnghị tham gia cùng ngươi." #, fuzzy msgid "All of the creatures may offer to join the hero." msgstr "Tất cả các sinh vật có thể đỠnghị tham gia cùng ngươi." #, fuzzy msgid "" "The cost of surrender is reduced to %{percent} percent of the total cost of " "troops in the army." msgstr "" "%{name} giảm chi phí đầu hàng xuống %{count} phần trăm tổng chi phí quân đội " "ngươi có trong quân đội cá»§a mình." #, fuzzy msgid "" "%{skill} increases the hero's movement points over water by %{count} percent." msgstr "" "%{skill} tăng Ä‘iểm di chuyển cá»§a anh hùng trên mặt nước thêm %{count} phần " "trăm." #, fuzzy msgid "%{skill} increases the hero's troops morale by %{count}." msgstr "%{skill} tăng tinh thần quân đội cá»§a tướng cá»§a ngươi lên %{count}." #, fuzzy msgid "%{skill} allows the hero to learn third level spells." msgstr "%{skill} cho phép tướng cá»§a ngươi há»c các phép thuật cấp ba." #, fuzzy msgid "%{skill} allows the hero to learn fourth level spells." msgstr "%{skill} cho phép tướng cá»§a ngươi há»c các phép thuật cấp bốn." #, fuzzy msgid "%{skill} allows the hero to learn fifth level spells." msgstr "%{skill} cho phép tướng cá»§a ngươi há»c các phép thuật cấp năm." #, fuzzy msgid "%{skill} regenerates one additional spell point per day to the hero." msgid_plural "" "%{skill} regenerates %{count} additional spell points per day to the hero." msgstr[0] "" "%{skill} tái tạo thêm má»™t Ä‘iểm phép thuật má»—i ngày cho tướng cá»§a ngươi." #, fuzzy msgid "%{skill} increases the hero's luck by %{count}." msgstr "%{skill} tăng may mắn cho tướng cá»§a ngươi thêm %{count}." #, fuzzy msgid "" "%{skill} gives the hero's catapult shots a greater chance to hit and do " "damage to castle walls." msgstr "" "%{skill} giúp các cú bắn bằng máy bắn đá cá»§a tướng cá»§a ngươi có cÆ¡ há»™i trúng " "đích cao hÆ¡n và gây sát thương lên tưá»ng lâu đài." #, fuzzy msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot has a " "greater chance to hit and do damage to castle walls." msgstr "" "%{skill} giúp máy bắn đá cá»§a tướng cá»§a ngươi có thêm má»™t phát bắn và má»—i " "phát bắn có cÆ¡ há»™i trúng đích và gây sát thương lên tưá»ng lâu đài cao hÆ¡n." #, fuzzy msgid "" "%{skill} gives the hero's catapult an extra shot, and each shot " "automatically destroys any wall, except a fortified wall in a Knight castle." msgstr "" "%{skill} cung cấp thêm cho máy bắn đá cá»§a tướng cá»§a ngươi má»™t phát bắn và " "má»—i phát bắn sẽ tá»± động phá há»§y bất kỳ bức tưá»ng nào, ngoại trừ bức tưá»ng " "kiên cố trong lâu đài Hiệp sÄ©." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 1st or " "2nd level spell that was cast by an enemy during combat." msgstr "" "%{skill} mang lại cho tướng cá»§a ngươi %{count} phần trăm cÆ¡ há»™i há»c bất kỳ " "phép thuật cấp 1 hoặc cấp 2 được sá»­ dụng bởi kẻ thù trong trận chiến." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 3rd " "level spell (or below) that was cast by an enemy during combat." msgstr "" "%{skill} mang lại cho tướng cá»§a ngươi %{count} phần trăm cÆ¡ há»™i há»c bất kỳ " "phép thuật cấp 3 (hoặc thấp hÆ¡n) được sá»­ dụng bởi kẻ thù trong trận chiến." #, fuzzy msgid "" "%{skill} gives the hero a %{count} percent chance to learn any given 4th " "level spell (or below) that was cast by an enemy during combat." msgstr "" "%{skill} mang lại cho tướng cá»§a ngươi %{count} phần trăm cÆ¡ há»™i há»c bất kỳ " "phép thuật cấp 4 (hoặc thấp hÆ¡n) được sá»­ dụng bởi kẻ thù trong trận chiến." msgid "" "%{skill} allows %{count} percent of the creatures killed in combat to be " "brought back from the dead as Skeletons." msgstr "" "%{skill} cho phép %{count} phần trăm sinh vật bị giết trong trận chiến được " "hồi sinh từ cõi chết dưới dạng Bá»™ xương." #, fuzzy msgid "" "The hero produces %{count} gold pieces per day as tax revenue from estates." msgstr "" "Tướng cá»§a ngươi tạo ra %{count} miếng vàng má»—i ngày dưới dạng doanh thu thuế " "từ tài sản." msgid "Blue" msgstr "Lục" msgid "Green" msgstr "Xanh" msgid "Red" msgstr "Äá»" msgid "Yellow" msgstr "Vàng" msgid "Orange" msgstr "Cam" msgid "Purple" msgstr "Tím" msgid "barrier|Aqua" msgstr "Nước biển" msgid "barrier|Blue" msgstr "Lục" msgid "barrier|Brown" msgstr "Nâu" msgid "barrier|Gold" msgstr "Vàng" msgid "barrier|Green" msgstr "Xanh" msgid "barrier|Orange" msgstr "Cam" msgid "barrier|Purple" msgstr "Tím" msgid "barrier|Red" msgstr "Äá»" msgid "tent|Aqua" msgstr "Nước biển" msgid "tent|Blue" msgstr "Lục" msgid "tent|Brown" msgstr "Nâu" msgid "tent|Gold" msgstr "Vàng" msgid "tent|Green" msgstr "Xanh" msgid "tent|Orange" msgstr "Cam" msgid "tent|Purple" msgstr "Tím" msgid "tent|Red" msgstr "Äá»" msgid "Experience" msgstr "Kinh nghiệm" msgid "" "Experience allows your heroes to go up levels, increasing their primary and " "secondary skills." msgstr "" "Kinh nghiệm cho phép các tướng cá»§a ngươi tăng cấp, tăng các kỹ năng chính và " "phụ." msgid "Hero/Stats" msgstr "Tướng/Tình trạng" msgid "Skills" msgstr "KÄ© năng" msgid "Town/Castle" msgstr "Thị trấn/Lâu đài" msgid "Garrison" msgstr "Äồn trú" msgid "Gold Per Day:" msgstr "Vàng Má»—i Ngày:" msgid "View Heroes." msgstr "Xem Tướng." msgid "Towns/Castles" msgstr "Thị trấn/Lâu đài" msgid "View Towns and Castles." msgstr "Xem Thị trấn và Lâu đài." msgid "luck|Cursed" msgstr "Nguyá»n rá»§a" msgid "luck|Awful" msgstr "Tồi tệ" msgid "luck|Bad" msgstr "Xấu" msgid "luck|Normal" msgstr "Thưá»ng" msgid "luck|Good" msgstr "Tốt" msgid "luck|Great" msgstr "Tuyệt" msgid "luck|Irish" msgstr "" #, fuzzy msgid "" "Bad luck sometimes falls on the hero's units in combat, causing their " "attacks to only do half damage." msgstr "" "Vận rá»§i đôi khi rÆ¡i vào quân đội cá»§a ngươi trong chiến đấu, khiến đòn tấn " "công cá»§a há» chỉ gây sát thương má»™t ná»­a." #, fuzzy msgid "" "Neutral luck means the hero's units will never get lucky or unlucky attacks " "on the enemy." msgstr "" "May mắn trung tính có nghÄ©a là quân đội cá»§a ngươi sẽ không bao giá» gặp may " "mắn hoặc xui xẻo tấn công kẻ thù." #, fuzzy msgid "" "Good luck sometimes lets the hero's units get lucky attacks (double " "strength) in combat." msgstr "" "May mắn tốt đôi khi giúp quân đội cá»§a ngươi nhận được những đòn tấn công may " "mắn (sức mạnh gấp đôi) trong chiến đấu." msgid "morale|Treason" msgstr "Phản bá»™i" msgid "morale|Awful" msgstr "Khá»§ng hoảng" msgid "morale|Poor" msgstr "Chán nản" msgid "morale|Normal" msgstr "Bình thưá»ng" msgid "morale|Good" msgstr "Rất tốt" msgid "morale|Great" msgstr "Tuyệt vá»i" msgid "morale|Blood!" msgstr "Máu chiến!" #, fuzzy msgid "Bad morale may cause the hero's units to freeze in combat." msgstr "" "Tinh thần tồi tệ có thể khiến quân đội cá»§a ngươi tê liệt trong chiến đấu." #, fuzzy msgid "" "Neutral morale means the hero's units will never be blessed with extra " "attacks or freeze in combat." msgstr "" "Tinh thần trung lập có nghÄ©a là quân đội cá»§a ngươi sẽ không bao giỠđược ban " "phước vá»›i các cuá»™c tấn công bổ sung hoặc đóng băng trong chiến đấu." #, fuzzy msgid "Good morale may give the hero's units extra attacks in combat." msgstr "" "Tinh thần tốt có thể mang lại cho quân đội cá»§a ngươi nhiá»u đòn tấn công hÆ¡n " "trong chiến đấu." msgid "Knight" msgstr "Hiệp sÄ©" msgid "Barbarian" msgstr "Man rợ" msgid "Sorceress" msgstr "Phù thá»§y" msgid "Warlock" msgstr "Pháp sư" msgid "Wizard" msgstr "Thuật sÄ©" msgid "Necromancer" msgstr "Chiêu hồn" msgid "Multi" msgstr "Nhiá»u" msgid "doubleLined|Knight" msgstr "Hiệp sÄ©" msgid "doubleLined|Barbarian" msgstr "Man rợ" msgid "doubleLined|Sorceress" msgstr "Phù thá»§y" msgid "doubleLined|Warlock" msgstr "Pháp sư" msgid "doubleLined|Wizard" msgstr "Thuật sÄ©" msgid "" "doubleLined|Necro-\n" "mancer" msgstr "" "Chiêu\n" "hồn" msgid "doubleLinedRace|Multi" msgstr "Nhiá»u" msgid "doubleLinedRace|Random" msgstr "Ngẫu nhiên" msgid "speed|Standing" msgstr "Äứng" msgid "speed|Crawling" msgstr "Bò" msgid "speed|Very Slow" msgstr "Rất Chậm" msgid "speed|Slow" msgstr "Chậm" msgid "speed|Average" msgstr "Trung bình" msgid "speed|Fast" msgstr "Nhanh" msgid "speed|Very Fast" msgstr "Rất nhanh" msgid "speed|Ultra Fast" msgstr "Cá»±c nhanh" msgid "speed|Blazing" msgstr "Tốc độ" msgid "speed|Instant" msgstr "Tức thì" msgid "week|Squirrel" msgstr "Sóc" msgid "week|Rabbit" msgstr "Thá»" msgid "week|Gopher" msgstr "Chuá»™t túi" msgid "week|Badger" msgstr "Lá»­ng" msgid "week|Rat" msgstr "Chuá»™t" msgid "week|Eagle" msgstr "Äại bàng" msgid "week|Weasel" msgstr "Chồn" msgid "week|Raven" msgstr "Quạ" msgid "week|Mongoose" msgstr "Cầy" msgid "week|Dog" msgstr "Chó" msgid "week|Aardvark" msgstr "Lợn" msgid "week|Lizard" msgstr "Thằn lằn" msgid "week|Tortoise" msgstr "Rùa" msgid "week|Hedgehog" msgstr "Nhím" msgid "week|Condor" msgstr "Ưng" msgid "week|Ant" msgstr "Kiến" msgid "week|Grasshopper" msgstr "Cào cào" msgid "week|Dragonfly" msgstr "Chuồn chuồn" msgid "week|Spider" msgstr "Nhện" msgid "week|Butterfly" msgstr "Bướm" msgid "week|Bumblebee" msgstr "Ong vò vẽ" msgid "week|Locust" msgstr "Châu chấu" msgid "week|Earthworm" msgstr "Giun đất" msgid "week|Hornet" msgstr "Ong bắp cày" msgid "week|Beetle" msgstr "Bá»" msgid "week|PLAGUE" msgstr "DỊCH BỆNH" msgid "week|Unnamed" msgstr "Không tên" msgid "Desert" msgstr "Sa mạc" msgid "Snow" msgstr "Tuyết" msgid "Wasteland" msgstr "Äất hoang" msgid "Beach" msgstr "Biển" msgid "Lava" msgstr "Nham thạch" msgid "Dirt" msgstr "Bùn" msgid "Grass" msgstr "Cá»" msgid "Ocean" msgstr "Äại dương" msgid "maps|Small" msgstr "Nhá»" msgid "maps|Medium" msgstr "Trung" msgid "maps|Large" msgstr "Lá»›n" msgid "maps|Extra Large" msgstr "Rất Lá»›n" msgid "maps|Custom Size" msgstr "Kích thước Khác" msgid "Ore Mine" msgstr "Má» Äá" msgid "Sulfur Mine" msgstr "Má» Lưu huỳnh" msgid "Crystal Mine" msgstr "Má» Thạch anh" msgid "Gems Mine" msgstr "Má» Kim cương" msgid "Gold Mine" msgstr "Má» Vàng" msgid "Mine" msgstr "Má»" msgid "Burma shave." msgstr "" msgid "Next sign 50 miles." msgstr "Chỉ dẫn kế tiếp cách 50 dặm." msgid "See Rock City." msgstr "" msgid "This space for rent." msgstr "Chá»— này cho thuê." msgid "No object" msgstr "Không có gì" msgid "Alchemist Lab" msgstr "Phòng thí nghiệm Hóa" msgid "Sign" msgstr "Chỉ dẫn" msgid "Buoy" msgstr "Phao" msgid "Skeleton" msgstr "Xương" msgid "Daemon Cave" msgstr "Hang Quá»·" msgid "Treasure Chest" msgstr "Rương Báu vật" msgid "Faerie Ring" msgstr "Vòng Tiên" msgid "Campfire" msgstr "Lá»­a trại" msgid "Fountain" msgstr "Äài nước" msgid "Gazebo" msgstr "" msgid "Genie Lamp" msgstr "Äèn Thần" msgid "Archer's House" msgstr "Nhà Cung" msgid "Goblin Hut" msgstr "Lá»u Yêu tinh" msgid "Dwarf Cottage" msgstr "Nhà Ngưá»i lùn" msgid "Peasant Hut" msgstr "Lá»u Nông dân" msgid "Stables" msgstr "Chuồng ngá»±a" msgid "Alchemist's Tower" msgstr "Tháp Giả kim" msgid "Event" msgstr "Sá»± kiện" msgid "Dragon City" msgstr "Thành phố Rồng" msgid "Lighthouse" msgid_plural "Lighthouses" msgstr[0] "Hải đăng" msgid "Water Wheel" msgid_plural "Water Wheels" msgstr[0] "Bánh xe Nước" msgid "Monster" msgstr "Quái vật" msgid "Obelisk" msgstr "Äài tưởng niệm" msgid "Oasis" msgstr "á»c đảo" msgid "Resource" msgstr "Tài nguyên" msgid "Sawmill" msgstr "Xưởng cưa" msgid "Oracle" msgstr "Tiên tri" msgid "Shrine of the First Circle" msgstr "Äiện thá» Vòng Má»™t" msgid "Shipwreck" msgstr "Tàu đắm" msgid "Sea Chest" msgstr "Rương Biển" msgid "Desert Tent" msgstr "Lá»u Xa mạc" msgid "Stone Liths" msgstr "Äá Phiến" msgid "Wagon Camp" msgstr "Toa xe" msgid "Hut of the Magi" msgstr "Lá»u Pháp sư" msgid "Whirlpool" msgstr "Xoáy nước" msgid "Windmill" msgid_plural "Windmills" msgstr[0] "Cối xay gió" msgid "Artifact" msgstr "Bảo vật" msgid "Mermaid" msgstr "Tiên cá" msgid "Boat" msgstr "Thuyá»n" msgid "Random Ultimate Artifact" msgstr "Ngẫu nhiên Bảo vật Tối thượng" msgid "Random Artifact" msgstr "Ngẫu nhiên Bảo vật" msgid "Random Resource" msgstr "Ngẫu nhiên Tài nguyên" msgid "Random Monster" msgstr "Ngẫu nhiên Quái vật" msgid "Random Town" msgstr "Ngẫu nhiên Thị trấn" msgid "Random Castle" msgstr "Ngẫu nhiên Lâu đài" msgid "Eye of the Magi" msgstr "Mắt cá»§a Pháp sư" msgid "Random Monster - weak" msgstr "Ngẫu nhiên Quái vật - yếu" msgid "Random Monster - medium" msgstr "Ngẫu nhiên Quái vật - thưá»ng" msgid "Random Monster - strong" msgstr "Ngẫu nhiên Quái vật - mạnh" msgid "Random Monster - very strong" msgstr "Ngẫu nhiên Quái vật - rất mạnh" #, fuzzy msgid "Hero" msgstr "Tướng" msgid "Nothing Special" msgstr "Không có gì Äặc biệt" msgid "Mossy Rock" msgstr "Äá Rêu" msgid "Watch Tower" msgstr "Tháp Canh" msgid "Tree House" msgstr "Nhà trên Cây" msgid "Tree City" msgstr "Thành Cây" msgid "Ruins" msgstr "Tàn tích" msgid "Fort" msgstr "Pháo đài" msgid "Abandoned Mine" msgstr "Má» hoang" msgid "Sirens" msgstr "Nhân ngư" msgid "Standing Stones" msgstr "Äá Äứng" msgid "Idol" msgstr "Thần tượng" msgid "Tree of Knowledge" msgstr "Cây Tri thức" msgid "Witch Doctor's Hut" msgstr "Lá»u Bác há»c" msgid "Temple" msgstr "Ngôi Ä‘á»n" msgid "Hill Fort" msgstr "Pháo đài Trên đồi" msgid "Halfling Hole" msgstr "" msgid "Mercenary Camp" msgstr "Trại Lính đánh thuê" msgid "Shrine of the Second Circle" msgstr "Äiện thá» Vòng thứ Hai" msgid "Shrine of the Third Circle" msgstr "Äiện thá» Vòng thứ Ba" msgid "City of the Dead" msgstr "Thành phố Chết" msgid "Sphinx" msgstr "Nhân sư" msgid "Wagon" msgstr "Toa xe" msgid "Tar Pit" msgstr "Hố Hắc ín" msgid "Artesian Spring" msgstr "Suối Nguồn" msgid "Troll Bridge" msgstr "" msgid "Watering Hole" msgstr "Hố Nước" msgid "Witch's Hut" msgstr "" msgid "Xanadu" msgstr "" msgid "Lean-To" msgstr "" msgid "Magellan's Maps" msgstr "Bản đồ Magellan" msgid "Flotsam" msgstr "Vật trôi nổi" msgid "Derelict Ship" msgstr "Tàu Hoang" msgid "Shipwreck Survivor" msgstr "Nạn nhân Tàu đắm" msgid "Bottle" msgstr "Chai" msgid "Magic Well" msgstr "Giếng Thần" msgid "Magic Garden" msgid_plural "Magic Gardens" msgstr[0] "Vưá»n Thần" msgid "Observation Tower" msgstr "Äài Quan sát" msgid "Freeman's Foundry" msgstr "" msgid "Reefs" msgstr "Äá ngầm" msgid "Volcano" msgstr "Núi lá»­a" msgid "Flowers" msgstr "Hoa" msgid "Rock" msgstr "Äá" msgid "Water Lake" msgstr "Hồ Nước" msgid "Mandrake" msgstr "Khoai ma" msgid "Dead Tree" msgstr "Cây khô" msgid "Stump" msgstr "Gốc cây" msgid "Crater" msgstr "Miệng núi lá»­a" msgid "Cactus" msgstr "Xương rồng" msgid "Mound" msgstr "Gò đất" msgid "Dune" msgstr "Äụn cát" msgid "Lava Pool" msgstr "VÅ©ng nham thạch" msgid "Shrub" msgstr "Cây bụi" msgid "Barrow Mounds" msgstr "" msgid "Random Artifact - Treasure" msgstr "Bảo vật Ngẫu nhiên - Châu báu" msgid "Random Artifact - Minor" msgstr "Bảo vật Ngẫu nhiên - Nhá»" msgid "Random Artifact - Major" msgstr "Bảo vật Ngẫu nhiên - Lá»›n" msgid "Barrier" msgstr "Rào chắn" msgid "Traveller's Tent" msgstr "Lá»u Du khách" msgid "Jail" msgstr "Ngục" msgid "Fire Summoning Altar" msgstr "Bàn thá» Triệu hồi Lá»­a" msgid "Air Summoning Altar" msgstr "Bàn thá» Triệu hồi Khí" msgid "Earth Summoning Altar" msgstr "Bàn thá» Triệu hồi Äất" msgid "Water Summoning Altar" msgstr "Bàn thá» Triệu hồi Nước" msgid "Unknown Monster" msgstr "Quái vật Vô danh" msgid "Unknown Monsters" msgstr "Quái vật Vô danh" msgid "Peasant" msgstr "Nông dân" msgid "Peasants" msgstr "Nông dân" msgid "Archer" msgstr "Cung thá»§" msgid "Archers" msgstr "Cung thá»§" msgid "Ranger" msgstr "Kỵ binh" msgid "Rangers" msgstr "Kỵ binh" msgid "Pikeman" msgstr "Thương thá»§" msgid "Pikemen" msgstr "Thương thá»§" msgid "Veteran Pikeman" msgstr "Thương thá»§ Kỳ cá»±u" msgid "Veteran Pikemen" msgstr "Thương thá»§ Kỳ cá»±u" msgid "Swordsman" msgstr "Kiếm thá»§" msgid "Swordsmen" msgstr "Kiếm thá»§" msgid "Master Swordsman" msgstr "Kiếm thá»§ Bậc thầy" msgid "Master Swordsmen" msgstr "Kiếm thá»§ Bậc thầy" msgid "Cavalries" msgstr "Kỵ mã" msgid "Cavalry" msgstr "Kỵ mã" msgid "Champion" msgstr "Kỵ mã Bậc thầy" msgid "Champions" msgstr "Kỵ mã Bậc thầy" msgid "Paladin" msgstr "Hiệp sÄ©" msgid "Paladins" msgstr "Hiệp sÄ©" msgid "Crusader" msgstr "Hiệp sÄ© thập tá»±" msgid "Crusaders" msgstr "Hiệp sÄ© thập tá»±" msgid "Goblin" msgstr "Yêu tinh" msgid "Goblins" msgstr "Yêu tinh" msgid "Orc" msgstr "" msgid "Orcs" msgstr "" msgid "Orc Chief" msgstr "" msgid "Orc Chiefs" msgstr "" msgid "Wolf" msgstr "Sói" msgid "Wolves" msgstr "Sói" msgid "Ogre" msgstr "" msgid "Ogres" msgstr "" msgid "Ogre Lord" msgstr "" msgid "Ogre Lords" msgstr "" msgid "Troll" msgstr "Quá»· lùn" msgid "Trolls" msgstr "Quá»· lùn" msgid "War Troll" msgstr "Quá»· lùn Chiến" msgid "War Trolls" msgstr "Quá»· lùn Chiến" msgid "Cyclopes" msgstr "Äá»™c nhãn" msgid "Cyclops" msgstr "Äá»™c nhãn" msgid "Sprite" msgstr "Ma tiên" msgid "Sprites" msgstr "Ma tiên" msgid "Dwarf" msgstr "Ngưá»i lùn" msgid "Dwarves" msgstr "Ngưá»i lùn" msgid "Battle Dwarf" msgstr "Ngưá»i lùn Chiến" msgid "Battle Dwarves" msgstr "Ngưá»i lùn Chiến" msgid "Elf" msgstr "Tiên" msgid "Elves" msgstr "Tiên" msgid "Grand Elf" msgstr "Tiên Äại" msgid "Grand Elves" msgstr "Tiên Äại" msgid "Druid" msgstr "Tu sÄ©" msgid "Druids" msgstr "Tu sÄ©" msgid "Greater Druid" msgstr "Tu sÄ© Cả" msgid "Greater Druids" msgstr "Tu sÄ© Cả" msgid "Unicorn" msgstr "Kỳ lân" msgid "Unicorns" msgstr "Kỳ lân" msgid "Phoenix" msgstr "Phượng hoàng" msgid "Phoenixes" msgstr "Phượng hoàng" msgid "Centaur" msgstr "Nhân mã" msgid "Centaurs" msgstr "Nhân mã" msgid "Gargoyle" msgstr "" msgid "Gargoyles" msgstr "" msgid "Griffin" msgstr "Chim ưng" msgid "Griffins" msgstr "Chim ưng" msgid "Minotaur" msgstr "Nhân ngưu" msgid "Minotaurs" msgstr "Nhân ngưu" msgid "Minotaur King" msgstr "Nhân ngưu Vua" msgid "Minotaur Kings" msgstr "Nhân ngưu Vua" msgid "Hydra" msgstr "" msgid "Hydras" msgstr "" msgid "Green Dragon" msgstr "Rồng Xanh" msgid "Green Dragons" msgstr "Rồng Xanh" msgid "Red Dragon" msgstr "Rồng Äá»" msgid "Red Dragons" msgstr "Rồng Äá»" msgid "Black Dragon" msgstr "Rồng Äen" msgid "Black Dragons" msgstr "Rồng Äen" msgid "Halfling" msgstr "" msgid "Halflings" msgstr "" msgid "Boar" msgstr "Lợn rừng" msgid "Boars" msgstr "Lợn rừng" msgid "Iron Golem" msgstr "Yêu tinh Sắt" msgid "Iron Golems" msgstr "Yêu tinh Sắt" msgid "Steel Golem" msgstr "Yêu tinh Thép" msgid "Steel Golems" msgstr "Yêu tinh Thép" msgid "Roc" msgstr "" msgid "Rocs" msgstr "" msgid "Mage" msgstr "" msgid "Magi" msgstr "" msgid "Archmage" msgstr "" msgid "Archmagi" msgstr "" msgid "Giant" msgstr "To lá»›n" msgid "Giants" msgstr "To lá»›n" msgid "Titan" msgstr "Khổng lồ" msgid "Titans" msgstr "Khổng lồ" msgid "Skeletons" msgstr "Xương" msgid "Zombie" msgstr "Thây ma" msgid "Zombies" msgstr "Thây ma" msgid "Mutant Zombie" msgstr "Thây ma Äá»™t biến" msgid "Mutant Zombies" msgstr "Thây ma Äá»™t biến" msgid "Mummies" msgstr "Xác ướp" msgid "Mummy" msgstr "Xác ướp" msgid "Royal Mummies" msgstr "Xác ướp Hoàng gia" msgid "Royal Mummy" msgstr "Xác ướp Hoàng gia" msgid "Vampire" msgstr "Ma cà rồng" msgid "Vampires" msgstr "Ma cà rồng" msgid "Vampire Lord" msgstr "Ma cà rồng Chúa" msgid "Vampire Lords" msgstr "Ma cà rồng Chúa" msgid "Lich" msgstr "" msgid "Liches" msgstr "" msgid "Power Lich" msgstr "" msgid "Power Liches" msgstr "" msgid "Bone Dragon" msgstr "Rồng Xương" msgid "Bone Dragons" msgstr "Rồng Xương" msgid "Rogue" msgstr "Lưu manh" msgid "Rogues" msgstr "Lưu manh" msgid "Nomad" msgstr "Du mục" msgid "Nomads" msgstr "Du mục" msgid "Ghost" msgstr "Ma" msgid "Ghosts" msgstr "Ma" msgid "Genie" msgstr "Thần đèn" msgid "Genies" msgstr "Thần đèn" msgid "Medusa" msgstr "" msgid "Medusas" msgstr "" msgid "Earth Elemental" msgstr "Nguyên tố Äất" msgid "Earth Elementals" msgstr "Nguyên tố Äất" msgid "Air Elemental" msgstr "Nguyên tố Khí" msgid "Air Elementals" msgstr "Nguyên tố Khí" msgid "Fire Elemental" msgstr "Nguyên tố Lá»­a" msgid "Fire Elementals" msgstr "Nguyên tố Lá»­a" msgid "Water Elemental" msgstr "Nguyên tố Nước" msgid "Water Elementals" msgstr "Nguyên tố Nước" msgid "Random Monsters" msgstr "Quái vật Ngẫu nhiên" msgid "Random Monster 1" msgstr "Quái vật Ngẫu nhiên 1" msgid "Random Monsters 1" msgstr "Quái vật Ngẫu nhiên 1" msgid "Random Monster 2" msgstr "Quái vật Ngẫu nhiên 2" msgid "Random Monsters 2" msgstr "Quái vật Ngẫu nhiên 2" msgid "Random Monster 3" msgstr "Quái vật Ngẫu nhiên 3" msgid "Random Monsters 3" msgstr "Quái vật Ngẫu nhiên 3" msgid "Random Monster 4" msgstr "Quái vật Ngẫu nhiên 4" msgid "Random Monsters 4" msgstr "Quái vật Ngẫu nhiên 4" msgid "Double shot" msgstr "Bắn đúp" msgid "2-hex monster" msgstr "quái 2 ô lục giác" msgid "Double strike" msgstr "Tấn công kép" msgid "Double damage to Undead" msgstr "Gấp đôi sát thương cho Bất tá»­" msgid "% magic resistance" msgstr "% kháng phép" msgid "Immune to Mind spells" msgstr "Kháng các phép Tâm trí" msgid "Immune to Elemental spells" msgstr "Kháng các phép Nguyên tố" msgid "Immune to Fire spells" msgstr "Kháng phép Lá»­a" msgid "Immune to Cold spells" msgstr "Kháng các phép Lạnh" msgid "Immune to " msgstr "Kháng phép " msgid "% immunity to %{spell} spell" msgstr "% kháng phép %{spell}" msgid "% damage from Elemental spells" msgstr "% sát thương vá»›i phép Nguyên tố" msgid "% chance to Dispel beneficial spells" msgstr "% cÆ¡ há»™i Xua tan các phép thuật có lợi" msgid "% chance to Paralyze" msgstr "% cÆ¡ há»™i bị Tê liêt" msgid "% chance to Petrify" msgstr "% cÆ¡ há»™i bị Hóa đá" msgid "% chance to Blind" msgstr "% cÆ¡ há»™i bị Mù" msgid "% chance to Curse" msgstr "% cÆ¡ há»™i bị Nguyá»n rá»§a" msgid "% chance to cast %{spell} spell" msgstr "% cÆ¡ há»™i niệm phép %{spell}" msgid "HP regeneration" msgstr "Hồi máu" msgid "Two hexes attack" msgstr "Tấn công 2 ô lục giác" msgid "Flyer" msgstr "Loài bay" msgid "Always retaliates" msgstr "Luôn phản đòn" msgid "Attacks all adjacent enemies" msgstr "Tấn công tất cả kẻ thù lân cận" msgid "No melee penalty" msgstr "Không bị phạt cận chiến" msgid "Dragon" msgstr "Rồng" msgid "Undead" msgstr "Bất tá»­" msgid "No enemy retaliation" msgstr "Không có sá»± phản đòn cá»§a kẻ thù" msgid "HP drain" msgstr "Hút máu" msgid "Cloud attack" msgstr "Tấn công trên mây" msgid "Decreases enemy's morale by " msgstr "Làm giảm tinh thần cá»§a kẻ thù " msgid "% chance to halve enemy" msgstr "% cÆ¡ há»™i giảm má»™t ná»­a kẻ địch" msgid "Soul Eater" msgstr "Kẻ ăn Linh hồn" msgid "Elemental" msgstr "Nguyên tố" msgid "No Morale" msgstr "Mất Tinh thần" msgid "200% damage from Fire spells" msgstr "200% sát thương từ phép Lá»­a" msgid "200% damage from Cold spells" msgstr "200% sát thương từ phép Lạnh" msgid "% damage from %{spell} spell" msgstr "% sát thương từ phép %{spell}" msgid "% immunity to " msgstr "% kháng lại " msgid "Lightning" msgstr "Sét" msgid "% damage from " msgstr "% sát thương từ " msgid "The three Anduran artifacts magically combine into one." msgstr "Ba bảo vật Anduran kết hợp má»™t cách kỳ diệu thành má»™t." msgid "View Spells" msgstr "Xem Phép" msgid "View %{name} Info" msgstr "Xem Thông tin %{name}" msgid "Move %{name}" msgstr "Chuyển %{name}" msgid "Cannot move the Spellbook" msgstr "Không thể chuyển Sách phép" msgid "This item can't be traded." msgstr "Vật này không thể trao đổi." msgid "Invalid Artifact" msgstr "Bảo vật Không hợp lệ" #, fuzzy msgid "The %{name} increases the hero's knowledge by %{count}." msgstr "%{name} nâng cao kiến thức cá»§a ngươi thêm %{count}." msgid "Ultimate Book of Knowledge" msgstr "Sách Kiến thức Tối thượng" #, fuzzy msgid "The %{name} increases the hero's attack skill by %{count}." msgstr "%{name} tăng kỹ năng tấn công cá»§a ngươi thêm %{count}." msgid "Ultimate Sword of Dominion" msgstr "Kiếm Thống trị Tối thượng" #, fuzzy msgid "The %{name} increases the hero's defense skill by %{count}." msgstr "%{name} tăng kỹ năng phòng thá»§ cá»§a ngươi thêm %{count}." msgid "Ultimate Cloak of Protection" msgstr "Ão Bảo vệ Tối thượng" #, fuzzy msgid "The %{name} increases the hero's spell power by %{count}." msgstr "%{name} tăng sức mạnh phép thuật cá»§a ngươi thêm %{count}." msgid "Ultimate Wand of Magic" msgstr "ÄÅ©a Phép Tối thượng" #, fuzzy msgid "" "The %{name} increases the hero's attack and defense skills by %{count} each." msgstr "" "%{name} tăng kỹ năng tấn công và phòng thá»§ cá»§a ngươi thêm %{count} má»—i kỹ " "năng." msgid "Ultimate Shield" msgstr "Khiên Tối thượng" #, fuzzy msgid "" "The %{name} increases the hero's spell power and knowledge by %{count} each." msgstr "" "%{name} tăng sức mạnh phép thuật và kiến thức cá»§a ngươi thêm %{count} má»—i " "thứ." msgid "Ultimate Staff" msgstr "Quyá»n trượng Tối thượng" #, fuzzy msgid "" "The %{name} increases each of the hero's basic skills by %{count} points." msgstr "%{name} tăng má»—i kỹ năng cÆ¡ bản cá»§a ngươi thêm %{count} Ä‘iểm." msgid "Ultimate Crown" msgstr "Vương miện Tối thượng" msgid "Golden Goose" msgstr "Ngá»—ng Vàng" msgid "The %{name} brings in an income of %{count} gold per day." msgstr "%{name} mang lại thu nhập %{count} vàng má»—i ngày." msgid "Arcane Necklace of Magic" msgstr "Vòng cổ Ma thuật Bí ẩn" msgid "" "After rescuing a Sorceress from a cursed tomb, she rewards your heroism with " "an exquisite jeweled necklace." msgstr "" "Sau khi giải cứu má»™t Nữ phù thá»§y khá»i ngôi má»™ bị nguyá»n rá»§a, cô ấy thưởng " "cho tướng cá»§a ngươi má»™t chiếc vòng cổ nạm ngá»c tinh xảo." msgid "Caster's Bracelet of Magic" msgstr "Vòng tay Ma thuật cá»§a Caster" msgid "" "While searching through the rubble of a caved-in mine, you free a group of " "trapped Dwarves. Grateful, the leader gives you a golden bracelet." msgstr "" "Trong khi tìm kiếm trong đống đổ nát cá»§a má»™t khu má» bị sập, ngươi giải thoát " "má»™t nhóm Ngưá»i lùn bị mắc kẹt. Biết Æ¡n, ngưá»i lãnh đạo tặng ngươi má»™t chiếc " "vòng tay vàng." msgid "Mage's Ring of Power" msgstr "Nhẫn Quyá»n lá»±c Pháp sư" msgid "" "A cry of pain leads you to a Centaur, caught in a trap. Upon setting the " "creature free, he hands you a small pouch. Emptying the contents, you find a " "dazzling jeweled ring." msgstr "" "Tiếng kêu Ä‘au đớn sẽ dẫn ngươi tá»›i chá»— Nhân Mã Ä‘ang mắc bẫy. Sau khi thả " "sinh vật ra, anh ta đưa cho ngươi má»™t chiếc túi nhá». Dá»n sạch ná»™i dung bên " "trong, ngươi tìm thấy má»™t chiếc nhẫn nạm ngá»c rá»±c rỡ." msgid "Witch's Broach of Magic" msgstr "Lá»i khuyên Phép thuật cá»§a Phù thá»§y" msgid "" "Alongside the remains of a burnt witch lies a beautiful broach, intricately " "designed. Approaching the corpse with caution, you add the broach to your " "inventory." msgstr "" "Bên cạnh hài cốt cá»§a má»™t phù thá»§y bị cháy là má»™t chiếc trâm cài đẹp đẽ, được " "thiết kế tinh xảo. Tiếp cận xác chết má»™t cách thận trá»ng, ngươi thêm chiếc " "chuốt vào kho cá»§a mình." msgid "Medal of Valor" msgstr "Huân chương DÅ©ng cảm" #, fuzzy msgid "The %{name} increases the morale of the hero's army by %{count}." msgstr "%{name} nâng cao tinh thần cá»§a ngươi lên %{count}." msgid "" "Freeing a virtuous maiden from the clutches of an evil overlord, you are " "granted a Medal of Valor by the King's herald." msgstr "" "Giải thoát má»™t thiếu nữ đức hạnh khá»i nanh vuốt cá»§a tên chúa tể độc ác, " "ngươi được sứ giả cá»§a Nhà vua trao tặng Huân chương DÅ©ng cảm." msgid "Medal of Courage" msgstr "Huân chương Cam đảm" msgid "" "After saving a young boy from a vicious pack of Wolves, you return him to " "his father's manor. The grateful nobleman awards you with a Medal of Courage." msgstr "" "Sau khi cứu má»™t cậu bé khá»i bầy Sói hung ác, ngươi đưa cậu bé vá» trang viên " "cá»§a cha cậu. Nhà quý tá»™c biết Æ¡n sẽ trao cho ngươi Huân chương Can đảm." msgid "Medal of Honor" msgstr "Huân chương Danh dá»±" msgid "" "After freeing a princess of a neighboring kingdom from the evil clutches of " "despicable slavers, she awards you with a Medal of Honor." msgstr "" "Sau khi giải phóng công chúa cá»§a má»™t vương quốc láng giá»ng khá»i nanh vuốt " "độc ác cá»§a những kẻ nô lệ hèn hạ, cô ấy sẽ trao cho ngươi Huân chương Danh " "dá»±." msgid "Medal of Distinction" msgstr "Huân chương Xuất chúng" msgid "" "Ridding the countryside of the hideous Minotaur who made a sport of eating " "noblemen's Knights, you are honored with the Medal of Distinction." msgstr "" "Thoát khá»i vùng nông thôn cá»§a Minotaur gá»›m ghiếc, kẻ chuyên ăn thịt các Hiệp " "sÄ© cá»§a quý tá»™c, ngươi được vinh danh vá»›i Huân chương Xuất chúng." msgid "Fizbin of Misfortune" msgstr "Huy chương Bất hạnh" #, fuzzy msgid "" "The %{name} greatly decreases the morale of the hero's army by %{count}." msgstr "%{name} làm giảm đáng kể tinh thần cá»§a ngươi %{count}." msgid "" "You stumble upon a medal lying alongside the empty road. Adding the medal to " "your inventory, you become aware that you have acquired the undesirable " "Fizbin of Misfortune, greatly decreasing your army's morale." msgstr "" "Ngươi tình cá» thấy má»™t chiếc huy chương nằm dá»c con đưá»ng vắng. Khi thêm huy " "chương vào kho đồ cá»§a mình, ngươi sẽ biết rằng mình đã có được Huy chương " "Bất hạnh không mong muốn, khiến tinh thần quân đội cá»§a ngươi giảm Ä‘i đáng kể." msgid "Thunder Mace of Dominion" msgstr "Chùy Sấm sét cá»§a sá»± Thống trị" msgid "" "During a sudden storm, a bolt of lightning strikes a tree, splitting it. " "Inside the tree you find a mysterious mace." msgstr "" "Trong má»™t cÆ¡n bão bất ngá», má»™t tia sét đánh vào má»™t cái cây, làm gãy nó. Bên " "trong cái cây ngươi tìm thấy má»™t cây chùy bí ẩn." msgid "Armored Gauntlets of Protection" msgstr "Găng tay Thép Bảo vệ" #, fuzzy msgid "The %{name} increase the hero's defense skill by %{count}." msgstr "%{name} tăng kỹ năng phòng thá»§ cá»§a ngươi thêm %{count}." msgid "" "You encounter the infamous Black Knight! After a grueling duel ending in a " "draw, the Knight, out of respect, offers you a pair of armored gauntlets." msgstr "" "Ngươi gặp phải Hiệp sÄ© Äen khét tiếng! Sau khi má»™t trận đấu tay đôi mệt má»i " "kết thúc vá»›i tá»· số hòa, Hiệp sÄ©, vì tôn trá»ng, sẽ tặng ngươi má»™t đôi găng " "tay bá»c thép." msgid "Defender Helm of Protection" msgstr "MÅ© Bảo vệ cá»§a Phòng thá»§" msgid "" "A glint of golden light catches your eye. Upon further investigation, you " "find a golden helm hidden under a bush." msgstr "" "Má»™t tia sáng vàng thu hút ánh nhìn cá»§a ngươi. Sau khi Ä‘iá»u tra sâu hÆ¡n, " "ngươi tìm thấy má»™t chiếc mÅ© bảo hiểm bằng vàng ẩn dưới bụi cây." msgid "Giant Flail of Dominion" msgstr "Chùy Thống trị Khổng lồ" msgid "" "A clumsy Giant has killed himself with his own flail. Knowing your superior " "skill with this weapon, you confidently remove the spectacular flail from " "the fallen Giant." msgstr "" "Má»™t gã khổng lồ vụng vỠđã tá»± sát bằng chính cây đập lúa cá»§a mình. Biết được " "kỹ năng vượt trá»™i cá»§a mình vá»›i loại vÅ© khí này, ngươi tá»± tin loại bá» cú đập " "cây ngoạn mục từ Ngưá»i khổng lồ đã ngã xuống." msgid "Ballista of Quickness" msgstr "Máy bắn đá Nhanh" #, fuzzy msgid "The %{name} gives the hero's catapult one extra shot per combat round." msgstr "" "%{name} giúp máy bắn đá cá»§a ngươi có thêm má»™t phát bắn má»—i vòng chiến đấu." msgid "" "Walking through the ruins of an ancient walled city, you find the instrument " "of the city's destruction, an elaborately crafted ballista." msgstr "" "Äi bá»™ qua đống đổ nát cá»§a má»™t thành phố cổ có tưá»ng bao quanh, ngươi tìm " "thấy công cụ há»§y diệt thành phố, má»™t chiếc ballista được chế tạo công phu." msgid "Stealth Shield of Protection" msgstr "Lá chắn Bảo vệ Tàng hình" msgid "" "A stone statue of a warrior holds a silver shield. As you remove the shield, " "the statue crumbles into dust." msgstr "" "Bức tượng đá cá»§a má»™t chiến binh cầm má»™t chiếc khiên bạc. Khi ngươi tháo tấm " "khiên ra, bức tượng sẽ vỡ vụn thành cát bụi." msgid "Dragon Sword of Dominion" msgstr "Kiếm Rồng Thống trị" msgid "" "As you are walking along a narrow path, a nearby bush suddenly bursts into " "flames. Before your eyes the flames become the image of a beautiful woman. " "She holds out a magnificent sword to you." msgstr "" "Khi ngươi Ä‘ang Ä‘i dá»c theo má»™t con đưá»ng hẹp, má»™t bụi cây gần đó bất ngá» bốc " "cháy. Trước mắt ngươi ngá»n lá»­a trở thành hình ảnh cá»§a má»™t ngưá»i phụ nữ xinh " "đẹp. Cô ấy đưa ra má»™t thanh kiếm tuyệt đẹp cho ngươi." msgid "Power Axe of Dominion" msgstr "Rìu Sức mạnh Thống trị" msgid "" "You see a silver axe embedded deeply in the ground. After several " "unsuccessful attempts by your army to remove the axe, you tightly grip the " "handle of the axe and effortlessly pull it free." msgstr "" "Ngươi nhìn thấy má»™t chiếc rìu bạc cắm sâu vào lòng đất. Sau nhiá»u ná»— lá»±c " "không thành công cá»§a quân đội để loại bá» chiếc rìu, ngươi nắm chặt cán rìu " "và dá»… dàng kéo nó ra." msgid "Divine Breastplate of Protection" msgstr "Giáp Bảo vệ Thần thánh" msgid "" "A gang of Rogues is sifting through the possessions of dead warriors. " "Scaring off the scavengers, you note the Rogues had overlooked a beautiful " "breastplate." msgstr "" "Má»™t nhóm Rogues Ä‘ang sàng lá»c tài sản cá»§a những chiến binh đã chết. Khiến " "những ngưá»i nhặt rác sợ hãi, ngươi lưu ý rằng Rogues đã bá» qua má»™t tấm giáp " "ngá»±c tuyệt đẹp." msgid "Minor Scroll of Knowledge" msgstr "Cuá»™n Kiến thức Nhá»" msgid "" "Before you appears a levitating glass case with a scroll, perched upon a bed " "of crimson velvet. At your touch, the lid opens and the scroll floats into " "your awaiting hands." msgstr "" "Trước khi ngươi xuất hiện má»™t chiếc há»™p thá»§y tinh bay lên vá»›i má»™t cuá»™n giấy, " "nằm trên má»™t chiếc giưá»ng nhung đỠthẫm. Khi ngươi chạm vào, nắp sẽ mở ra và " "cuá»™n giấy sẽ trôi vào tay ngươi Ä‘ang chỠđợi." msgid "Major Scroll of Knowledge" msgstr "Cuá»™n Kiến thức Lá»›n" msgid "" "Visiting a local wiseman, you explain the intent of your journey. He reaches " "into a sack and withdraws a yellowed scroll and hands it to you." msgstr "" "Äến thăm má»™t nhà thông thái địa phương, ngươi giải thích mục đích chuyến Ä‘i " "cá»§a mình. Anh ta thò tay vào bao tải và rút ra má»™t cuá»™n giấy màu vàng rồi " "đưa cho ngươi." msgid "Superior Scroll of Knowledge" msgstr "Cuá»™n Kiến thức Äại" msgid "" "You come across the remains of an ancient Druid. Bones, yellowed with age, " "peer from the ragged folds of her robe. Searching the robe, you discover a " "scroll hidden in the folds." msgstr "" "Ngươi tình cá» gặp hài cốt cá»§a má»™t Tu sÄ© cổ đại. Những bá»™ xương đã ố vàng " "theo thá»i gian lá»™ ra từ những nếp gấp rách rưới trên chiếc áo choàng cá»§a cô. " "Lục soát chiếc áo choàng, ngươi phát hiện ra má»™t cuá»™n giấy được giấu trong " "các nếp gấp." msgid "Foremost Scroll of Knowledge" msgstr "Cuá»™n Kiến thức Tối cao" msgid "" "Mangled bones, yellowed with age, peer from the ragged folds of a dead " "Druid's robe. Searching the robe, you discover a scroll hidden within." msgstr "" "Những bá»™ xương xiêu vẹo, ố vàng theo thá»i gian, lá»™ ra từ những nếp gấp rách " "rưới trên chiếc áo choàng cá»§a má»™t Tu sÄ© đã chết. Lục soát chiếc áo choàng, " "ngươi phát hiện ra má»™t cuá»™n giấy ẩn bên trong." msgid "Endless Sack of Gold" msgstr "Túi Vàng Vô tận" #, fuzzy msgid "The %{name} provides the hero with %{count} gold per day." msgstr "%{name} cung cấp cho ngươi %{count} vàng má»—i ngày." msgid "" "A little leprechaun dances gleefully around a magic sack. Seeing you " "approach, he stops in mid-stride. The little man screams and stamps his foot " "ferociously, vanishing into thin air. Remembering the old leprechaun saying " "'Finders Keepers', you grab the sack and leave." msgstr "" "Má»™t chú yêu tinh nhá» nhảy múa vui vẻ quanh chiếc bao ma thuật. Thấy ngươi " "đến gần, anh ta dừng lại giữa chừng. Ngưá»i đàn ông nhá» bé hét lên và giậm " "chân dữ dá»™i, biến mất trong không khí loãng. Nhá»› đến câu nói cá»§a yêu tinh " "già 'Ngưá»i giữ ngưá»i tìm kiếm', ngươi chá»™p lấy chiếc bao và rá»i Ä‘i." msgid "Endless Bag of Gold" msgstr "Bao Vàng Vô tận" msgid "" "A noblewoman, separated from her traveling companions, asks for your help. " "After escorting her home, she rewards you with a bag filled with gold." msgstr "" "Má»™t phụ nữ quý tá»™c bị tách khá»i những ngưá»i bạn đồng hành cá»§a mình và nhá» " "ngươi giúp đỡ. Sau khi há»™ tống cô ấy vá» nhà, cô ấy thưởng cho ngươi má»™t " "chiếc túi chứa đầy vàng." msgid "Endless Purse of Gold" msgstr "Ví Vàng Vô tận" msgid "" "In your travels, you find a leather purse filled with gold that once " "belonged to a great warrior king who had the ability to transform any " "inanimate object into gold." msgstr "" "Trong chuyến du hành cá»§a mình, ngươi tìm thấy má»™t chiếc ví da chứa đầy vàng " "từng thuá»™c vá» má»™t vị vua chiến binh vÄ© đại, ngưá»i có khả năng biến bất kỳ " "vật vô tri nào thành vàng." msgid "Nomad Boots of Mobility" msgstr "Giầy Du mục cá»§a Dịch chuyển" #, fuzzy msgid "The %{name} increase the hero's movement on land." msgstr "%{name} tăng khả năng di chuyển cá»§a ngươi trên đất liá»n." msgid "" "A Nomad trader seeks protection from a tribe of Goblins. For your " "assistance, he gives you a finely crafted pair of boots made from the " "softest leather. Looking closely, you see fascinating ancient carvings " "engraved on the leather." msgstr "" "Má»™t thương nhân du mục tìm kiếm sá»± bảo vệ từ bá»™ tá»™c Yêu tinh. Äể há»— trợ " "ngươi, anh ấy sẽ đưa cho ngươi má»™t đôi bốt được chế tác tinh xảo làm từ loại " "da má»m nhất. Nhìn kỹ, ngươi sẽ thấy những hình chạm khắc cổ xưa đầy hấp dẫn " "được khắc trên da." msgid "Traveler's Boots of Mobility" msgstr "Giầy Du lich cá»§a Dịch chuyển" msgid "" "Discovering a pair of beautifully beaded boots made from the finest and " "softest leather, you thank the anonymous donor and add the boots to your " "inventory." msgstr "" "Phát hiện ra má»™t đôi bốt đính cưá»m đẹp mắt được làm từ loại da má»m và đẹp " "nhất, ngươi cảm Æ¡n nhà tài trợ ẩn danh và thêm đôi bốt vào kho cá»§a mình." msgid "Lucky Rabbit's Foot" msgstr "Chân Thá» May mắn" #, fuzzy msgid "The %{name} increases the luck of the hero's army by %{count}." msgstr "%{name} tăng may mắn cá»§a ngươi trong chiến đấu lên %{count}." msgid "" "A traveling merchant offers you a rabbit's foot, made of gleaming silver " "fur, for safe passage. The merchant explains the charm will increase your " "luck in combat." msgstr "" "Má»™t thương gia du lịch tặng ngươi má»™t bàn chân thá», làm bằng lông bạc lấp " "lánh, để Ä‘i lại an toàn. Ngưá»i thương gia giải thích rằng bùa sẽ tăng may " "mắn cho ngươi trong chiến đấu." msgid "Golden Horseshoe" msgstr "Giầy ngá»±a Vàng" msgid "" "An ensnared Unicorn whinnies in fright. Murmuring soothing words, you set " "her free. Snorting and stamping her front hoof once, she gallops off. " "Looking down you see a golden horseshoe." msgstr "" "Má»™t con Kỳ lân bị mắc bẫy Ä‘ang rên rỉ vì sợ hãi. Thì thầm những lá»i êm dịu, " "ngươi thả cô ấy ra. Khịt mÅ©i và giậm mạnh móng trước má»™t lần, cô ấy phi nước " "đại. Nhìn xuống ngươi thấy má»™t chiếc móng ngá»±a màu vàng." msgid "Gambler's Lucky Coin" msgstr "Xu May mắn cá»§a Dân cá» bạc" msgid "" "You have captured a mischievous imp who has been terrorizing the region. In " "exchange for his release, he rewards you with a magical coin." msgstr "" "Ngươi đã bắt được má»™t tên tiểu quá»· tinh quái Ä‘ang khá»§ng bố khu vá»±c. Äể đổi " "lấy việc được thả, anh ta thưởng cho ngươi má»™t đồng xu ma thuật." msgid "Four-Leaf Clover" msgstr "Cá» Bốn Lá" msgid "" "In the middle of a patch of dead and dry vegetation, to your surprise you " "find a healthy green four-leaf clover." msgstr "" "Giữa má»™t thảm thá»±c vật khô và chết, ngươi ngạc nhiên khi tìm thấy má»™t cây cá» " "bốn lá xanh tươi khá»e mạnh." msgid "True Compass of Mobility" msgstr "La bàn Di động Äích thá»±c" #, fuzzy msgid "The %{name} increases the hero's movement on land and sea." msgstr "%{name} tăng khả năng di chuyển cá»§a ngươi trên đất liá»n và trên biển." msgid "" "An old man claiming to be an inventor asks you to try his latest invention. " "He then hands you a compass." msgstr "" "Má»™t ông già tá»± xưng là nhà phát minh yêu cầu ngươi thá»­ phát minh má»›i nhất " "cá»§a ông ấy. Sau đó anh ta đưa cho ngươi má»™t chiếc la bàn." msgid "Sailor's Astrolabe of Mobility" msgstr "Bản đồ Di chuyển cá»§a Thá»§y thá»§" #, fuzzy msgid "The %{name} increases the hero's movement on sea." msgstr "%{name} tăng khả năng di chuyển cá»§a ngươi trên biển." msgid "" "An old sea captain is being tortured by Ogres. You save him, and in return " "he rewards you with a wondrous instrument to measure the distance of a star." msgstr "" "Má»™t thuyá»n trưởng già Ä‘ang bị bá»n yêu tinh tra tấn. Ngươi cứu anh ta, và đổi " "lại anh ta thưởng cho ngươi má»™t công cụ kỳ diệu để Ä‘o khoảng cách cá»§a má»™t " "ngôi sao." msgid "Evil Eye" msgstr "Mắt Quá»·" msgid "The %{name} reduces the casting cost of curse spells by half." msgstr "%{name} giảm má»™t ná»­a chi phí sá»­ dụng bùa chú." msgid "" "While venturing into a decrepit hut you find the Skeleton of a long dead " "witch. Investigation of the remains reveals a glass eye rolling around " "inside an empty skull." msgstr "" "Trong khi mạo hiểm vào má»™t túp lá»u tồi tàn, ngươi tìm thấy Bá»™ xương cá»§a má»™t " "phù thá»§y đã chết từ lâu. Äiá»u tra hài cốt cho thấy má»™t con mắt thá»§y tinh lăn " "tròn bên trong há»™p sá» trống rá»—ng." msgid "Enchanted Hourglass" msgstr "Äồng hồ cát Mê hoặc" #, fuzzy msgid "" "The %{name} extends the duration of all the hero's spells by %{count} turns." msgstr "" "%{name} kéo dài thá»i lượng cá»§a tất cả các phép thuật cá»§a ngươi thêm %{count} " "lượt." msgid "" "A surprise turn in the landscape finds you in the midst of a grisly scene: " "Vultures picking at the aftermath of a terrible battle. Your cursory search " "of the remains turns up an enchanted hourglass." msgstr "" "Má»™t bước ngoặt bất ngá» trong khung cảnh đưa ngươi đến giữa má»™t khung cảnh " "rùng rợn: Ká»n ká»n Ä‘ang nhặt nhạnh sau má»™t trận chiến khá»§ng khiếp. Việc tìm " "kiếm nhanh chóng cá»§a ngươi vá» hài cốt sẽ cho ra má»™t chiếc đồng hồ cát đầy mê " "hoặc." msgid "Gold Watch" msgstr "Äồng hồ vàng" #, fuzzy msgid "The %{name} doubles the effectiveness of the hero's hypnotize spells." msgstr "%{name} tăng gấp đôi hiệu quả cá»§a phép thuật thôi miên cá»§a ngươi." msgid "" "In reward for helping his cart out of a ditch, a traveling potion salesman " "gives you a \"magic\" gold watch. Unbeknownst to him, the watch really is " "magical." msgstr "" "Äể thưởng cho việc giúp chiếc xe cá»§a mình thoát khá»i mương, má»™t ngưá»i bán " "thuốc du lịch sẽ tặng ngươi má»™t chiếc đồng hồ vàng \"ma thuật\". Anh ta " "không há» biết rằng chiếc đồng hồ thá»±c sá»± có phép thuật." msgid "Skullcap" msgstr "MÅ© đầu lâu" msgid "The %{name} halves the casting cost of all mind influencing spells." msgstr "" "%{name} giảm má»™t ná»­a chi phí thi triển cá»§a tất cả các phép thuật ảnh hưởng " "đến tâm trí." msgid "" "A brief stop at an improbable rural inn yields an exchange of money, tales, " "and accidentally, luggage. You find a magical skullcap in your new backpack." msgstr "" "Má»™t lần dừng chân ngắn ngá»§i tại má»™t quán trá» nông thôn không mấy chắc chắn " "sẽ mang lại sá»± trao đổi tiá»n bạc, những câu chuyện và vô tình là cả hành lý. " "Ngươi tìm thấy má»™t chiếc mÅ© đầu lâu thần kỳ trong chiếc ba lô má»›i cá»§a mình." msgid "Ice Cloak" msgstr "Ão Băng" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from cold spells." msgstr "" "%{name} giảm má»™t ná»­a tất cả thiệt hại mà quân cá»§a ngươi phải chịu từ các " "phép thuật lạnh." msgid "" "Responding to the panicked cries of a damsel in distress, you discover a " "young woman fleeing from a hungry bear. You slay the beast in the nick of " "time, and the grateful Sorceress weaves a magic cloak from the bear's hide." msgstr "" "Trước tiếng kêu hoảng loạn cá»§a má»™t cô gái gặp nạn, ngươi phát hiện ra má»™t " "phụ nữ trẻ Ä‘ang chạy trốn khá»i má»™t con gấu đói. Ngươi giết chết con quái vật " "đúng lúc và mụ phù thá»§y đầy lòng biết Æ¡n đã dệt nên má»™t chiếc áo choàng ma " "thuật từ da con gấu." msgid "Fire Cloak" msgstr "Ão Lá»­a" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from fire spells." msgstr "" "%{name} giảm má»™t ná»­a tổng thiệt hại mà quân cá»§a ngươi phải gánh chịu từ các " "phép thuật lá»­a." msgid "" "You've come upon a fight between a Necromancer and a Paladin. The " "Necromancer blasts the Paladin with a fire bolt, bringing him to his knees. " "Acting quickly, you slay the evil one before the final blow. The grateful " "Paladin gives you the fire cloak that saved him." msgstr "" "Ngươi đã chứng kiến cuá»™c chiến giữa Chiêu hồn và Tráng sÄ©. Chiêu hồn bắn " "tung Tráng sÄ© bằng má»™t tia lá»­a, khiến anh ta khuỵu gối. Hành động nhanh " "chóng, ngươi tiêu diệt kẻ ác trước đòn cuối cùng. Tráng sÄ© biết Æ¡n sẽ trao " "cho ngươi chiếc áo choàng lá»­a đã cứu anh ta." msgid "Lightning Helm" msgstr "MÅ© Sét" #, fuzzy msgid "" "The %{name} halves all damage the hero's troops receive from lightning " "spells." msgstr "" "%{name} giảm má»™t ná»­a tất cả sát thương mà quân cá»§a ngươi phải gánh chịu từ " "các phép thuật sét." msgid "" "A traveling tinker in need of supplies offers you a helm with a thunderbolt " "design on its top in exchange for food and water. Curious, you accept, and " "later find out that the helm is magical." msgstr "" "Má»™t ngưá»i thợ sá»­a chữa du lịch Ä‘ang cần đồ dùng sẽ cung cấp cho ngươi má»™t " "chiếc mÅ© bảo hiểm có thiết kế hình tia sét ở trên để đổi lấy thức ăn và nước " "uống. Tò mò, ngươi chấp nhận và sau đó phát hiện ra rằng chiếc mÅ© có phép " "thuật." msgid "Evercold Icicle" msgstr "Băng lạnh Bất tận" #, fuzzy msgid "" "The %{name} causes the hero's cold spells to do %{count} percent more damage " "to enemy troops." msgstr "" "%{name} khiến các phép thuật lạnh lùng cá»§a ngươi gây thêm %{count} phần trăm " "sát thương cho quân địch." msgid "" "An icicle withstanding the full heat of the noonday sun attracts your " "attention. Intrigued, you break it off, and find that it does not melt in " "your hand." msgstr "" "Má»™t cá»™t băng chịu được toàn bá»™ sức nóng cá»§a mặt trá»i giữa trưa sẽ thu hút sá»± " "chú ý cá»§a ngươi. Tò mò, ngươi bẻ nó ra và thấy rằng nó không tan chảy trong " "tay ngươi." msgid "Everhot Lava Rock" msgstr "Nham thạch nóng Vô tận" #, fuzzy msgid "" "The %{name} causes the hero's fire spells to do %{count} percent more damage " "to enemy troops." msgstr "" "%{name} khiến phép thuật lá»­a cá»§a ngươi gây thêm %{count} phần trăm sát " "thương cho quân địch." msgid "" "Your wanderings bring you into contact with a tribe of ape-like beings using " "a magical lava rock that never cools to light their fires. You take pity on " "them and teach them to make fire with sticks. Believing you to be a god, the " "apes give you their rock." msgstr "" "Những chuyến lang thang cá»§a ngươi sẽ đưa ngươi tiếp xúc vá»›i má»™t bá»™ tá»™c sinh " "vật giống vượn Ä‘ang sá»­ dụng má»™t tảng đá nham thạch kỳ diệu không bao giá» " "nguá»™i để thắp sáng ngá»n lá»­a cá»§a há». Ngươi thương hại chúng và dạy chúng đốt " "lá»­a bằng gậy. Tin rằng ngươi là má»™t vị thần, loài vượn đưa cho ngươi hòn đá " "cá»§a chúng." msgid "Lightning Rod" msgstr "Quyá»n trượng Sét" #, fuzzy msgid "" "The %{name} causes the hero's lightning spells to do %{count} percent more " "damage to enemy troops." msgstr "" "%{name} khiến phép thuật sét cá»§a ngươi gây thêm %{count} phần trăm sát " "thương cho quân địch." msgid "" "While waiting out a storm, a lighting bolt strikes a nearby cottage's " "lightning rod, which melts and falls to the ground. The tip of the rod, " "however, survives intact and makes your hair stand on end when you touch it. " "Hmm..." msgstr "" "Trong khi chá» bão tan, má»™t tia sét đánh trúng cá»™t thu lôi cá»§a má»™t ngôi nhà " "nhá» gần đó, khiến cá»™t thu lôi tan chảy và rÆ¡i xuống đất. Tuy nhiên, đầu que " "vẫn còn nguyên vẹn và khiến tóc ngươi dá»±ng đứng khi chạm vào. Ừm..." msgid "Snake-Ring" msgstr "Vòng Rắn" #, fuzzy msgid "The %{name} halves the casting cost of all of the hero's bless spells." msgstr "" "%{name} giảm má»™t ná»­a chi phí thi triển tất cả các phép thuật ban phước cá»§a " "ngươi." msgid "" "You've found an oddly shaped ring on the finger of a long dead traveler. The " "ring looks like a snake biting its own tail." msgstr "" "Ngươi đã tìm thấy má»™t chiếc nhẫn có hình dạng kỳ lạ trên ngón tay cá»§a má»™t du " "khách đã chết từ lâu. Chiếc nhẫn trông giống như con rắn tá»± cắn Ä‘uôi mình." msgid "Ankh" msgstr "" #, fuzzy msgid "" "The %{name} doubles the effectiveness of all of the hero's resurrect and " "animate spells." msgstr "" "%{name} tăng gấp đôi hiệu quả cá»§a tất cả các phép thuật hồi sinh và tái sinh " "cá»§a ngươi." msgid "" "A fierce windstorm reveals the entrance to a buried tomb. Your investigation " "reveals that the tomb has already been looted, but the thieves overlooked an " "ankh on a silver chain in the dark." msgstr "" "Má»™t cÆ¡n bão dữ dá»™i làm lá»™ ra lối vào má»™t ngôi má»™ bị chôn vùi. Cuá»™c Ä‘iá»u tra " "cá»§a ngươi cho thấy ngôi má»™ đã bị cướp phá, nhưng bá»n trá»™m đã bá» qua má»™t " "chiếc ankh trên dây chuyá»n bạc trong bóng tối." msgid "Book of Elements" msgstr "Sách Nguyên tố" #, fuzzy msgid "" "The %{name} doubles the effectiveness of all of the hero's summoning spells." msgstr "" "%{name} tăng gấp đôi hiệu quả cá»§a tất cả các phép thuật triệu hồi cá»§a ngươi." msgid "" "You come across a conjurer who begs to accompany you and your army awhile " "for safety. You agree, and he offers as payment a copy of the book of the " "elements." msgstr "" "Ngươi tình cá» gặp má»™t pháp sư cầu xin được Ä‘i cùng ngươi và quân đội cá»§a " "ngươi má»™t thá»i gian để được an toàn. Ngươi đồng ý và anh ta đưa ra má»™t bản " "sao cá»§a cuốn sách vá» các yếu tố để thanh toán." msgid "Elemental Ring" msgstr "Nhẫn Nguyên tố" msgid "The %{name} halves the casting cost of all summoning spells." msgstr "" "%{name} giảm má»™t ná»­a chi phí thi triển cá»§a tất cả các phép thuật triệu hồi." msgid "" "While pausing to rest, you notice a bobcat climbing a short tree to get at a " "crow's nest. On impulse, you climb the tree yourself and scare off the cat. " "When you look in the nest, you find a collection of shiny stones and a ring." msgstr "" "Trong khi dừng lại để nghỉ ngÆ¡i, ngươi nhận thấy má»™t con linh miêu Ä‘ang trèo " "lên má»™t cái cây thấp để đến tổ quạ. Trong cÆ¡n bốc đồng, ngươi tá»± mình trèo " "lên cây và dá»a con mèo. Khi nhìn vào tổ, ngươi sẽ thấy má»™t bá»™ sưu tập những " "viên đá sáng bóng và má»™t chiếc nhẫn." msgid "Holy Pendant" msgstr "Mặt dây Thánh" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to curse spells." msgstr "%{name} làm cho tất cả quân đội cá»§a ngươi miá»…n nhiá»…m vá»›i các bùa chú." msgid "" "In your wanderings you come across a hermit living in a small, tidy hut. " "Impressed with your mission, he takes time out from his meditations to bless " "and give you a charm against curses." msgstr "" "Trong chuyến lang thang cá»§a mình, ngươi tình cá» gặp má»™t ẩn sÄ© sống trong má»™t " "túp lá»u nhá», ngăn nắp. Ấn tượng vá»›i sứ mệnh cá»§a ngươi, anh ấy dành thá»i gian " "thiá»n định để ban phước và ban cho ngươi má»™t tấm bùa chống lại những lá»i " "nguyá»n." msgid "Pendant of Free Will" msgstr "Mặt dây à chí Tá»± do" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to hypnotize spells." msgstr "" "%{name} làm cho tất cả quân cá»§a ngươi miá»…n nhiá»…m vá»›i các phép thuật thôi " "miên." msgid "" "Responding to cries for help, you find river Sprites making a sport of " "dunking an old man. Feeling vengeful, you rescue the man and drag a Sprite " "onto dry land for awhile. The Sprite, uncomfortable in the air, gives you a " "magic pendant to let him go." msgstr "" "Äáp lại những tiếng kêu cứu, ngươi thấy các Sprites sông Ä‘ang thá»±c hiện má»™t " "trò chÆ¡i là dìm má»™t ông già xuống nước. Cảm thấy muốn trả thù, ngươi giải " "cứu ngưá»i đàn ông và kéo má»™t Sprite lên vùng đất khô ráo má»™t lúc. Sprite, " "không thoải mái khi ở trên không, đưa cho ngươi má»™t mặt dây chuyá»n ma thuật " "để thả anh ta Ä‘i." msgid "Pendant of Life" msgstr "Mặt dây cá»§a Sá»± sống" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to death spells." msgstr "%{name} làm cho tất cả quân cá»§a ngươi miá»…n nhiá»…m vá»›i bùa chết." msgid "" "A brief roadside encounter with a small caravan and a game of knucklebones " "wins a magic pendant. Its former owner says that it protects from " "Necromancers' death spells." msgstr "" "Cuá»™c chạm trán ngắn ngá»§i bên đưá»ng vá»›i má»™t Ä‘oàn lữ hành nhá» và trò chÆ¡i đánh " "đố sẽ giành được mặt dây chuyá»n ma thuật. Ngưá»i chá»§ cÅ© cá»§a nó nói rằng nó " "bảo vệ khá»i bùa chết cá»§a các Necromancer." msgid "Serenity Pendant" msgstr "Mặt dây Thanh thản" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to berserk spells." msgstr "" "%{name} làm cho tất cả quân cá»§a ngươi miá»…n nhiá»…m vá»›i các phép thuật Ä‘iên " "cuồng." msgid "" "The sounds of combat draw you to the scene of a fight between an old " "Barbarian and an eight-headed Hydra. Your timely intervention swings the " "battle in favor of the man, and he rewards you with a pendant he used to use " "to calm his mind for battle." msgstr "" "Âm thanh cá»§a trận chiến đưa ngươi đến hiện trưá»ng cuá»™c chiến giữa má»™t " "Barbarian già và má»™t con Hydra tám đầu. Sá»± can thiệp kịp thá»i cá»§a ngươi sẽ " "giúp trận chiến có lợi cho ngưá»i đàn ông và anh ta thưởng cho ngươi má»™t " "chiếc mặt dây chuyá»n mà anh ta dùng để giúp anh ta bình tÄ©nh trong trận " "chiến." msgid "Seeing-eye Pendant" msgstr "Mặt dây Mắt nhìn" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to blindness spells." msgstr "%{name} làm cho tất cả quân cá»§a ngưá»i miá»…n nhiá»…m vá»›i bùa mù." msgid "" "You come upon a very old woman, long blind from cataracts and dying alone. " "You tend to her final needs and promise a proper burial. Grateful, she gives " "you a magic pendant emblazoned with a stylized eye. It lets you see with " "your eyes closed." msgstr "" "Ngươi tình cá» gặp má»™t bà cụ già, bị mù từ lâu vì bệnh đục thá»§y tinh thể và " "Ä‘ang chết má»™t mình. Ngươi hướng tá»›i những nhu cầu cuối cùng cá»§a bà ấy và hứa " "sẽ chôn cất đàng hoàng. Biết Æ¡n, bà ấy tặng ngươi má»™t mặt dây chuyá»n ma " "thuật được trang trí bằng má»™t con mắt cách Ä‘iệu. Nó cho phép ngươi nhìn khi " "nhắm mắt lại." msgid "Kinetic Pendant" msgstr "Mặt dây Äá»™ng há»c" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to paralyze spells." msgstr "" "%{name} làm cho tất cả quân cá»§a ngươi miá»…n nhiá»…m vá»›i các phép thuật làm tê " "liệt." msgid "" "You come across a golem wearing a glowing pendant and blocking your way. " "Acting on a hunch, you cut the pendant from its neck. Deprived of its power " "source, the golem breaks down, leaving you with the magical pendant." msgstr "" "Ngươi bắt gặp má»™t con golem Ä‘eo mặt dây chuyá»n phát sáng và chặn đưá»ng " "ngươi. Theo linh cảm, ngươi cắt mặt dây chuyá»n khá»i cổ nó. Bị mất nguồn năng " "lượng, con golem bị há»ng, để lại cho ngươi chiếc mặt dây chuyá»n ma thuật." msgid "Pendant of Death" msgstr "Mặt dây Chết chóc" #, fuzzy msgid "The %{name} makes all of the hero's troops immune to holy spells." msgstr "" "%{name} làm cho tất cả quân đội cá»§a ngươi miá»…n nhiá»…m vá»›i các phép thuật thần " "thánh." msgid "" "A quick and deadly battle with a Necromancer wins you his magical pendant. " "Later, a Wizard tells you that the pendant protects undead under your " "control from holy word spells." msgstr "" "Má»™t trận chiến nhanh chóng và chết chóc vá»›i Necromancer sẽ giúp ngươi giành " "được mặt dây chuyá»n ma thuật cá»§a hắn. Sau đó, má»™t Pháp sư nói vá»›i ngươi rằng " "mặt dây chuyá»n bảo vệ xác sống dưới sá»± kiểm soát cá»§a ngươi khá»i các phép " "thuật từ thánh." msgid "Wand of Negation" msgstr "ÄÅ©a phép Phá»§ định" #, fuzzy msgid "" "The %{name} makes all of the hero's troops immune to dispel magic spells." msgstr "" "%{name} làm cho tất cả quân cá»§a ngươi miá»…n nhiá»…m vá»›i các phép thuật xua tan." msgid "" "You meet an old Wizard friend of yours traveling in the opposite direction. " "He presents you with a gift: A wand that prevents the use of the dispel " "magic spell on your allies." msgstr "" "Ngươi gặp má»™t ngưá»i bạn cÅ© cá»§a Pháp sư ngươi Ä‘ang Ä‘i theo hướng ngược lại. " "Anh ấy tặng ngươi má»™t món quà: Má»™t cây đũa phép ngăn cản việc sá»­ dụng phép " "thuật xua tan lên đồng minh cá»§a ngươi." msgid "Golden Bow" msgstr "Cung Vàng" #, fuzzy msgid "" "The %{name} eliminates the %{count} percent penalty for the hero's troops " "shooting past obstacles (e.g. castle walls)." msgstr "" "%{name} loại bá» hình phạt %{count} phần trăm đối vá»›i quân cá»§a ngươi bắn qua " "chướng ngại vật (ví dụ: tưá»ng lâu đài)." msgid "" "A chance meeting with a famous Archer finds you in a game of knucklebones " "pitting his bow against your horse. You win." msgstr "" "Má»™t cuá»™c gặp gỡ tình cá» vá»›i má»™t Cung thá»§ nổi tiếng sẽ đưa ngươi vào trò chÆ¡i " "đỠsức cung cá»§a anh ta vá»›i ngá»±a cá»§a ngươi. Ngươi thắng." msgid "Telescope" msgstr "Kính viá»…n vá»ng" #, fuzzy msgid "" "The %{name} increases the amount of terrain the hero reveals when " "adventuring by %{count} extra square." msgstr "" "%{name} tăng số lượng địa hình mà tướng cá»§a ngươi phát hiện khi phiêu lưu " "thêm %{count} ô vuông." msgid "" "A merchant from far away lands trades you a new invention of his people for " "traveling supplies. It makes distant objects appear closer, and he calls " "it...\n" "\n" "a telescope." msgstr "" "Má»™t thương gia từ những vùng đất xa xôi trao đổi vá»›i ngươi má»™t phát minh má»›i " "cá»§a ngưá»i dân anh ta để làm vật dụng Ä‘i du lịch. Nó làm cho những vật thể ở " "xa trông gần hÆ¡n, và ông gá»i nó là...\n" "\n" "má»™t kính viá»…n vá»ng." msgid "Statesman's Quill" msgstr "Bút lông cá»§a Statesman" #, fuzzy msgid "" "The %{name} reduces the cost of surrender to %{count} percent of the total " "cost of troops the hero has in their army." msgstr "" "%{name} giảm chi phí đầu hàng xuống %{count} phần trăm tổng chi phí quân đội " "ngươi có trong quân đội cá»§a mình." msgid "" "You pause to help a diplomat with a broken axle fix his problem. In " "gratitude, he gives you a writing quill with magical properties which he " "says will \"help people see things your way\"." msgstr "" "Ngươi dừng lại để giúp má»™t nhà ngoại giao bị gãy trục khắc phục sá»± cố cá»§a " "mình. Äể tá» lòng biết Æ¡n, anh ấy đưa cho ngươi má»™t chiếc bút lông có đặc " "tính kỳ diệu mà anh ấy nói sẽ \"giúp má»i ngưá»i nhìn má»i thứ theo cách cá»§a " "ngươi\"." msgid "Wizard's Hat" msgstr "MÅ© Phù thá»§y" #, fuzzy msgid "" "The %{name} increases the duration of the hero's spells by %{count} turns." msgstr "" "%{name} tăng thá»i lượng sá»­ dụng phép thuật cá»§a ngươi thêm %{count} lượt." msgid "" "You see a Wizard fleeing from a Griffin and riding like the wind. The Wizard " "opens a portal and rides through, getting his hat knocked off by the edge of " "the gate. The Griffin follows; the gate closes. You pick the hat up, dust it " "off, and put it on." msgstr "" "Ngươi thấy má»™t Pháp sư Ä‘ang chạy trốn khá»i má»™t con Griffin và phi như gió. " "Wizard mở má»™t cánh cổng và Ä‘i qua, chiếc mÅ© cá»§a anh ta bị hất văng ra mép " "cổng. Griffin theo sau; cánh cổng đóng lại. Ngươi nhặt chiếc mÅ© lên, phá»§i " "bụi rồi đội vào." msgid "Power Ring" msgstr "Nhẫn Sức mạnh" #, fuzzy msgid "The %{name} returns %{count} extra spell points per day to the hero." msgstr "" "%{name} phục hồi %{count} Ä‘iểm phép bổ sung má»—i ngày cho tướng cá»§a ngươi." msgid "" "You find a small tree that closely resembles the great Warlock Carnauth with " "a ring around one of its twigs. Scraps of clothing and rotting leather lead " "you to suspect that it IS Carnauth, transformed. Since you can't help him, " "you take the magic ring." msgstr "" "Ngươi tìm thấy má»™t cái cây nhá» gần giống vá»›i Warlock Carnauth vÄ© đại vá»›i má»™t " "vòng bao quanh má»™t trong những cành cây cá»§a nó. Những mảnh quần áo và da mục " "nát khiến ngươi nghi ngá» rằng đó LÀ Carnauth, đã biến hình. Vì không thể " "giúp anh ta nên ngươi lấy chiếc nhẫn ma thuật." msgid "Ammo Cart" msgstr "Xe Äạn" #, fuzzy msgid "" "The %{name} provides endless ammunition for all of the hero's troops that " "shoot." msgstr "%{name} cung cấp vô số đạn dược cho tất cả quân cá»§a ngươi bắn." msgid "" "An ammunition cart in the middle of an old battlefield catches your eye. " "Inspection shows it to be in good working order, so you take it along." msgstr "" "Má»™t xe chở đạn giữa chiến trưá»ng cÅ© thu hút sá»± chú ý cá»§a ngươi. Kiểm tra cho " "thấy nó Ä‘ang hoạt động tốt nên ngươi mang nó theo." msgid "Tax Lien" msgstr "Giữ Thuế" #, fuzzy msgid "The %{name} costs the hero %{count} gold pieces per day." msgstr "%{name} làm ngươi tốn %{count} vàng má»—i ngày." msgid "" "Your big spending habits have earned you a massive tax bill that you can't " "hope to pay. The tax man takes pity and agrees to only take 250 gold a day " "from your account for life. Check here if you want one dollar to go to the " "presidential campaign election fund." msgstr "" "Thói quen chi tiêu lá»›n cá»§a ngươi đã mang lại cho ngươi má»™t hóa đơn thuế " "khổng lồ mà ngươi không thể hy vá»ng trả được. Ngưá»i đóng thuế thương hại và " "đồng ý chỉ lấy 250 vàng má»—i ngày từ tài khoản cá»§a ngươi suốt Ä‘á»i. Äánh dấu " "vào đây nếu ngươi muốn má»™t đô la được chuyển vào quỹ bầu cá»­ chiến dịch tranh " "cá»­ tổng thống." msgid "Hideous Mask" msgstr "Mặt nạ Gá»›m ghiếc" #, fuzzy msgid "The %{name} prevents all 'wandering' armies from joining the hero." msgstr "" "%{name} ngăn chặn tất cả các đội quân 'lang thang' tham gia cùng tướng cá»§a " "ngươi." msgid "" "Your looting of the grave of Sinfilas Gardolad, the famous shapeshifting " "Warlock, unearths his fabled mask. Trembling, you put it on and it twists " "your visage into an awful grimace! Oh no! It's actually the hideous mask of " "Gromluck Greene, and you are stuck with it." msgstr "" "Việc ngươi cướp má»™ cá»§a Sinfilas Gardolad, Warlock biến hình nổi tiếng, đã " "khai quật được chiếc mặt nạ huyá»n thoại cá»§a anh ta. Run rẩy, ngươi Ä‘eo nó " "vào và nó làm khuôn mặt ngươi nhăn nhó khá»§ng khiếp! Ôi không! Äó thá»±c sá»± là " "chiếc mặt nạ gá»›m ghiếc cá»§a Gromluck Greene và ngươi bị mắc kẹt vá»›i nó." msgid "Endless Pouch of Sulfur" msgstr "Túi Lưu huỳnh Vô tận" msgid "The %{name} provides %{count} unit of sulfur per day." msgstr "%{name} cung cấp %{count} đơn vị lưu huỳnh má»—i ngày." msgid "" "You visit an alchemist who, upon seeing your army, is swayed by the " "righteousness of your cause. The newly loyal subject gives you his endless " "pouch of sulfur to help with the war effort." msgstr "" "Ngươi đến thăm má»™t nhà giả kim, ngưá»i khi nhìn thấy quân đội cá»§a ngươi đã bị " "ảnh hưởng bởi sá»± chính đáng cá»§a chính nghÄ©a cá»§a ngươi. Thần dân má»›i trung " "thành đưa cho ngươi túi lưu huỳnh vô tận cá»§a anh ta để giúp đỡ ná»— lá»±c chiến " "tranh." msgid "Endless Vial of Mercury" msgstr "Lá» Thá»§y ngân Vô tận" msgid "The %{name} provides %{count} unit of mercury per day." msgstr "%{name} cung cấp %{count} đơn vị thá»§y ngân má»—i ngày." msgid "" "A brief stop at a hastily abandoned Wizard's tower turns up a magical vial " "of mercury that always has a little left on the bottom. Recognizing a " "treasure when you see one, you cap it and slip it in your pocket." msgstr "" "Khi vá»™i vã dừng chân má»™t chút tại tòa tháp Pháp sư bị bá» hoang, ngươi sẽ " "thấy má»™t lá» thá»§y ngân kỳ diệu luôn còn sót lại má»™t ít ở đáy. Nhận ra má»™t kho " "báu khi ngươi nhìn thấy nó, ngươi đậy nắp nó lại và nhét nó vào túi." msgid "Endless Pouch of Gems" msgstr "Túi Äá quý Vô tận" msgid "The %{name} provides %{count} unit of gems per day." msgstr "%{name} cung cấp %{count} đơn vị đá quý má»—i ngày." msgid "" "A short rainstorm brings forth a rainbow...and you can see the end of it. " "Riding quickly, you seize the pot of gold you find there. The leprechaun who " "owns it, unable to stop you from taking it, offers an endless pouch of gems " "for the return of his gold. You accept." msgstr "" "Má»™t cÆ¡n mưa ngắn ngá»§i sẽ tạo ra cầu vồng...và ngươi có thể thấy được Ä‘iểm " "kết thúc cá»§a nó. Cưỡi ngá»±a nhanh chóng, ngươi nắm bắt được hÅ© vàng tìm thấy " "ở đó. Yêu tinh sở hữu nó, không thể ngăn cản ngươi lấy nó, đưa ra má»™t túi đá " "quý vô tận để trả lại số vàng cá»§a mình. Ngươi chấp nhận." msgid "Endless Cord of Wood" msgstr "Dây Gá»— Vô tận" msgid "The %{name} provides %{count} unit of wood per day." msgstr "%{name} cung cấp %{count} đơn vị gá»— má»—i ngày." msgid "" "Pausing to rest and light a cook fire, you pull wood out of a nearby pile of " "dead wood. As you keep pulling wood from the pile, you notice that it " "doesn't shrink. You realize to your delight that the wood is enchanted, so " "you take it along." msgstr "" "Dừng lại để nghỉ ngÆ¡i và đốt lá»­a nấu ăn, ngươi kéo cá»§i ra khá»i đống gá»— chết " "gần đó. Khi ngươi tiếp tục kéo gá»— ra khá»i đống, ngươi nhận thấy nó không há» " "co lại. Ngươi vui mừng nhận ra rằng gá»— bị mê hoặc nên ngươi mang nó theo." msgid "Endless Cart of Ore" msgstr "Xe Quặng Vô tận" msgid "The %{name} provides %{count} unit of ore per day." msgstr "%{name} cung cấp %{count} đơn vị quặng má»—i ngày." msgid "" "You've found a Goblin weapon smithy making weapons for use against humans. " "With a tremendous yell you and your army descend upon their camp and drive " "them away. A search finds a magic ore cart that never runs out of iron." msgstr "" "Ngươi đã tìm thấy má»™t lò rèn vÅ© khí yêu tinh chế tạo vÅ© khí để sá»­ dụng chống " "lại con ngưá»i. Vá»›i má»™t tiếng hét khá»§ng khiếp, ngươi và quân đội cá»§a ngươi " "tiến vào trại cá»§a há» và đánh Ä‘uổi há» Ä‘i. Má»™t cuá»™c tìm kiếm đã tìm thấy má»™t " "chiếc xe chở quặng ma thuật không bao giá» hết sắt." msgid "Endless Pouch of Crystal" msgstr "Túi Pha lê Vô tận" msgid "The %{name} provides %{count} unit of crystal per day." msgstr "%{name} cung cấp %{count} đơn vị pha lê má»—i ngày." msgid "" "Taking shelter from a storm in a small cave, you notice a small patch of " "crystal in one corner. Curious, you break a piece off and notice that the " "original crystal grows the lost piece back. You decide to stuff the entire " "patch into a pouch and take it with you." msgstr "" "Äang trú ẩn khá»i cÆ¡n bão trong má»™t hang động nhá», ngươi nhận thấy má»™t mảng " "pha lê nhỠở má»™t góc. Tò mò, ngươi bẻ má»™t mảnh ra và nhận thấy rằng viên pha " "lê ban đầu má»c lại mảnh bị mất. Ngươi quyết định nhét toàn bá»™ miếng dán vào " "má»™t cái túi và mang theo bên mình." msgid "Spiked Helm" msgstr "MÅ© Gai" msgid "" "Your army is ambushed by a small tribe of wild (and none too bright) Orcs. " "You fend them off easily and the survivors flee in all directions. One of " "the Orcs was wearing a polished spiked helm. Figuring it will make a good " "souvenir, you take it." msgstr "" "Quân đội cá»§a ngươi bị phục kích bởi má»™t bá»™ tá»™c nhá» Orc hoang dã (và không " "quá sáng sá»§a). Ngươi chống đỡ chúng má»™t cách dá»… dàng và những ngưá»i sống sót " "chạy trốn vá» má»i hướng. Má»™t trong những tên Orc Ä‘ang đội má»™t chiếc mÅ© nhá»n " "sáng bóng. NghÄ© rằng nó sẽ là má»™t món quà lưu niệm hay, ngươi hãy lấy nó." msgid "Spiked Shield" msgstr "Khiên Gai" msgid "" "You come upon a bridge spanning a dry gully. Before you can cross, a Troll " "steps out from under the bridge and demands payment before it will permit " "you to pass. You refuse, and the Troll charges, forcing you to slay it. You " "take its spiked shield as a trophy." msgstr "" "Ngươi Ä‘i đến má»™t cây cầu bắc qua má»™t rãnh nước khô cạn. Trước khi ngươi có " "thể băng qua, má»™t con Troll bước ra từ dưới cầu và yêu cầu thanh toán trước " "khi nó cho phép ngươi Ä‘i qua. Ngươi từ chối, và lÅ© Troll tấn công, buá»™c " "ngươi phải tiêu diệt nó. Ngươi lấy chiếc khiên có gai cá»§a nó làm chiến lợi " "phẩm." msgid "White Pearl" msgstr "Ngá»c trai Trắng" msgid "" "A walk across a dry saltwater lake bed yields an unlikely prize: A white " "pearl amidst shattered shells and debris." msgstr "" "Má»™t chuyến Ä‘i bá»™ qua lòng hồ nước mặn khô sẽ mang lại má»™t giải thưởng khó có " "thể xảy ra: Má»™t viên ngá»c trai trắng giữa những mảnh vá» và mảnh vụn vỡ vụn." msgid "Black Pearl" msgstr "Ngá»c trai Äen" msgid "" "Rumors of a Griffin of unusual size preying upon the countryside lead you to " "its cave lair. A quick, brutal fight dispatches the beast, and a search of " "its foul nest turns up a huge black pearl." msgstr "" "Tin đồn vá» má»™t con Griffin có kích thước bất thưá»ng Ä‘ang săn mồi ở vùng nông " "thôn sẽ dẫn ngươi đến hang ổ cá»§a nó. Má»™t cuá»™c chiến chóng vánh, tàn khốc đã " "tiêu diệt được con thú và khi tìm kiếm trong tổ hôi cá»§a nó, ngưá»i ta tìm " "thấy má»™t viên ngá»c trai Ä‘en khổng lồ." msgid "Magic Book" msgstr "Sách Phép" #, fuzzy msgid "The %{name} enables the hero to cast spells." msgstr "%{name} cho phép ngươi sá»­ dụng phép thuật." msgid "Dummy 1" msgstr "Bù nhìn 1" msgid "The reserved artifact." msgstr "Hiện vật được bảo lưu." msgid "Dummy 2" msgstr "Bù nhìn 2" msgid "Dummy 3" msgstr "Bù nhìn 3" msgid "Dummy 4" msgstr "Bù nhìn 4" msgid "Spell Scroll" msgstr "Sách Phép" #, fuzzy msgid "" "This %{name} gives the hero the ability to cast the %{spell} spell if the " "hero has a Magic Book." msgstr "" "%{name} này cung cấp cho tướng cá»§a ngươi khả năng sá»­ dụng phép thuật " "%{spell} nếu cho tướng cá»§a ngươi có Sách Phép." msgid "" "You find an elaborate container which houses an old vellum scroll. The runes " "on the container are very old, and the artistry with which it was put " "together is stunning. As you pull the scroll out, you feel imbued with " "magical power." msgstr "" "Ngươi tìm thấy má»™t chiếc há»™p đựng phức tạp chứa má»™t cuá»™n giấy da cÅ©. Những " "chữ rune trên há»™p đựng đã rất cÅ© và tính nghệ thuật khi ghép chúng lại vá»›i " "nhau thật tuyệt vá»i. Khi ngươi kéo cuá»™n giấy ra, ngươi sẽ cảm thấy tràn ngập " "sức mạnh ma thuật." msgid "Arm of the Martyr" msgstr "Cánh tay Liệt sÄ©" #, fuzzy msgid "" "The %{name} increases the hero's spell power by %{count} but adds the undead " "morale penalty." msgstr "" "%{name} tăng sức mạnh phép thuật cá»§a ngươi thêm %{count} nhưng lại thêm hình " "phạt vá» tinh thần cho xác sống." msgid "" "One of the less intelligent members of your party picks up an arm off of the " "ground. Despite its missing a body, it is still moving. Your troops find the " "dismembered arm repulsive, but you cannot bring yourself to drop it: it " "seems to hold some sort of magical power that influences your decision " "making." msgstr "" "Má»™t trong những thành viên kém thông minh hÆ¡n trong nhóm cá»§a ngươi nhấc má»™t " "cánh tay lên khá»i mặt đất. Mặc dù mất Ä‘i cÆ¡ thể nhưng nó vẫn di chuyển. Quân " "đội cá»§a ngươi nhận thấy cánh tay bị chặt rá»i thật đáng kinh tởm, nhưng ngươi " "không thể tá»± mình đánh rÆ¡i nó: nó dưá»ng như chứa má»™t loại sức mạnh ma thuật " "nào đó ảnh hưởng đến việc đưa ra quyết định cá»§a ngươi." msgid "Breastplate of Anduran" msgstr "Ão giáp cá»§a Anduran" #, fuzzy msgid "The %{name} increases the hero's defense by %{count}." msgstr "%{name} tăng khả năng phòng thá»§ cá»§a ngươi thêm %{count}." msgid "" "You come upon a sign. It reads: \"Here lies the body of Anduran. Bow and " "swear fealty, and you shall be rewarded.\" You decide to do as it says. As " "you stand up, you feel a coldness against your skin. Looking down, you find " "that you are suddenly wearing a gleaming, ornate breastplate." msgstr "" "Ngươi bắt gặp má»™t dấu hiệu. Nó viết: \"Äây là thi thể cá»§a Anduran. Hãy cúi " "đầu và thá» trung thành, và ngươi sẽ được khen thưởng.\" Ngươi quyết định làm " "như nó nói. Khi ngươi đứng lên, ngươi cảm thấy lạnh trên da. Nhìn xuống, " "ngươi chợt nhận ra mình Ä‘ang mặc má»™t tấm giáp ngá»±c được trang trí công phu, " "lấp lánh." msgid "Broach of Shielding" msgstr "Trâm Che chắn" msgid "" "The %{name} provides %{count} percent protection from Armageddon and " "Elemental Storm, but decreases spell power by 2." msgstr "" "%{name} cung cấp khả năng bảo vệ %{count} phần trăm khá»i Tận thế và Nguyên " "tố Bão, nhưng giảm sức mạnh phép thuật Ä‘i 2." msgid "" "A kindly Sorceress thinks that your army's defenses could use a magical " "boost. She offers to enchant the Broach that you wear on your cloak, and you " "accept." msgstr "" "Má»™t Nữ phù thá»§y tốt bụng nghÄ© rằng khả năng phòng thá»§ cá»§a quân đội ngươi có " "thể sá»­ dụng phép thuật tăng cưá»ng. Cô ấy đỠnghị phù phép cho Trâm cài áo " "trên áo choàng và ngươi chấp nhận." msgid "Battle Garb of Anduran" msgstr "Chiến Bào cá»§a Anduran" #, fuzzy msgid "" "The %{name} combines the powers of the three Anduran artifacts. It provides " "maximum luck and morale for the hero's troops and gives the hero the Town " "Portal spell." msgstr "" "%{name} kết hợp sức mạnh cá»§a ba hiện vật Anduran. Nó mang lại may mắn và " "tinh thần tối Ä‘a cho quân đội cá»§a ngươi và mang lại cho ngươi phép Cổng Thị " "trấn." msgid "" "Out of pity for a poor peasant, you purchase a chest of old junk they are " "hawking for too much gold. Later, as you search through it, you find it " "contains the 3 pieces of the legendary battle garb of Anduran!" msgstr "" "Vì thương hại má»™t ngưá»i nông dân nghèo, ngươi mua má»™t chiếc rương cÅ© mà há» " "Ä‘ang bán vá»›i giá quá cao. Sau đó, khi tìm kiếm, ngươi sẽ thấy nó chứa 3 mảnh " "trang phục chiến đấu huyá»n thoại cá»§a Anduran!" msgid "Crystal Ball" msgstr "Quả cầu Pha lê" #, fuzzy msgid "" "The %{name} lets the hero get more specific information about monsters, " "enemy heroes, and castles nearby the hero." msgstr "" "%{name} cho phép ngươi nhận được thông tin cụ thể hÆ¡n vá» quái vật, tướng cá»§a " "kẻ thù và lâu đài gần tướng nắm giữ nó." msgid "" "You come upon a caravan of gypsies who are feasting and fortifying their " "bodies with mead. They call you forward and say \"If you prove that you can " "dance the Rama-Buta, we will reward you.\" You don't know it, but try " "anyway. They laugh hysterically, but admire your bravery, giving you a " "Crystal Ball." msgstr "" "Ngươi bắt gặp má»™t Ä‘oàn lữ hành gồm những ngưá»i gypsies Ä‘ang ăn uống và bồi " "bổ cÆ¡ thể bằng đồng cá». Há» gá»i ngươi vá» phía trước và nói \"Nếu ngươi chứng " "minh được rằng ngươi có thể nhảy Rama-Buta, chúng tôi sẽ thưởng cho ngươi.\" " "Ngươi không biết Ä‘iá»u đó, nhưng dù sao hãy thá»­. Há» cưá»i má»™t cách cuồng loạn " "nhưng lại khâm phục sá»± dÅ©ng cảm cá»§a ngươi, tặng ngươi má»™t Quả cầu Pha lê." msgid "Heart of Fire" msgstr "Trái tim Lá»­a" msgid "" "The %{name} provides %{count} percent protection from fire, but doubles the " "damage taken from cold." msgstr "" "%{name} cung cấp khả năng bảo vệ %{count} phần trăm khá»i lá»­a nhưng tăng gấp " "đôi thiệt hại do lạnh." msgid "" "You enter a recently burned glade and come upon a Fire Elemental sitting " "atop a rock. It looks up, its flaming face contorted in a look of severe " "pain. It then tosses a glowing object at you. You put up your hands to block " "it, but it passes right through them and sears itself into your chest." msgstr "" "Ngươi bước vào má»™t khoảng trống vừa bị đốt cháy và gặp má»™t Nguyên tố Lá»­a " "Ä‘ang ngồi trên má»™t tảng đá. Nó ngước lên, khuôn mặt rá»±c lá»­a cá»§a nó nhăn nhó " "vá»›i vẻ Ä‘au đớn tá»™t độ. Sau đó nó ném má»™t vật thể phát sáng vào ngươi. Ngươi " "giÆ¡ tay lên để chặn nó, nhưng nó xuyên thẳng qua chúng và đâm vào ngá»±c ngươi." msgid "Heart of Ice" msgstr "Trái tim Băng" msgid "" "The %{name} provides %{count} percent protection from cold, but doubles the " "damage taken from fire." msgstr "" "%{name} cung cấp %{count} phần trăm bảo vệ khá»i cái lạnh, nhưng tăng gấp đôi " "thiệt hại do lá»­a gây ra." msgid "" "Suddenly, a biting coldness engulfs your body. You seize up, falling from " "your horse. The pain subsides, but you still feel as if your chest is " "frozen. As you pick yourself up off of the ground, you hear hearty laughter. " "You turn around just in time to see a Frost Giant run off into the woods and " "disappear." msgstr "" "Äá»™t nhiên, má»™t cảm giác lạnh buốt bao trùm lấy cÆ¡ thể ngươi. Ngươi đứng dậy, " "ngã ngá»±a. CÆ¡n Ä‘au giảm dần nhưng ngươi vẫn cảm thấy như ngá»±c mình bị đóng " "băng. Khi nhấc mình lên khá»i mặt đất, ngươi nghe thấy tiếng cưá»i sảng khoái. " "Ngươi quay lại đúng lúc để thấy Frost Giant chạy vào rừng và biến mất." msgid "Helmet of Anduran" msgstr "MÅ© cá»§a Anduran" msgid "" "You spy a gleaming object poking up out of the ground. You send a member of " "your party over to investigate. He comes back with a golden helmet in his " "hands. You realize that it must be the helmet of the legendary Anduran, the " "only man who was known to wear solid gold armor." msgstr "" "Ngươi phát hiện má»™t vật thể lấp lánh nhô lên khá»i mặt đất. Ngươi gá»­i má»™t " "thành viên trong nhóm cá»§a ngươi đến để Ä‘iá»u tra. Anh ta quay lại vá»›i chiếc " "mÅ© bảo hiểm vàng trên tay. Ngươi nhận ra rằng đó hẳn là chiếc mÅ© bảo hiểm " "cá»§a Anduran huyá»n thoại, ngưá»i đàn ông duy nhất được biết đến vá»›i bá»™ áo giáp " "bằng vàng nguyên khối." msgid "Holy Hammer" msgstr "Búa Thánh" msgid "" "You come upon a battle where a Paladin has been mortally wounded by a group " "of Zombies. He asks you to take his hammer and finish what he started. As " "you pick it up, it begins to hum, and then everything becomes a blur. The " "Zombies lie dead, the hammer dripping with blood. You strap it to your belt." msgstr "" "Ngươi bắt gặp má»™t trận chiến nÆ¡i má»™t Paladin bị trá»ng thương bởi má»™t nhóm " "Zombie. Anh ta yêu cầu ngươi lấy chiếc búa cá»§a anh ta và hoàn thành những gì " "anh ta đã bắt đầu. Khi ngươi nhặt nó lên, nó bắt đầu kêu vo vo và sau đó má»i " "thứ trở nên mỠảo. LÅ© Zombie nằm chết, chiếc búa rỉ máu. Ngươi buá»™c nó vào " "thắt lưng cá»§a ngươi." msgid "Legendary Scepter" msgstr "Vương trượng Huyá»n thoại" msgid "The %{name} adds %{count} points to all attributes." msgstr "%{name} thêm %{count} Ä‘iểm cho tất cả các thuá»™c tính." msgid "" "Upon cresting a small hill, you come upon a ridiculous looking sight. A " "Sprite is attempting to carry a Scepter that is almost as big as it is. " "Trying not to laugh, you ask, \"Need help?\" The Sprite glares at you and " "answers: \"You think this is funny? Fine. You can carry it. I much prefer " "flying anyway.\"" msgstr "" "Khi leo lên má»™t ngá»n đồi nhá», ngươi sẽ bắt gặp má»™t cảnh tượng trông thật lố " "bịch. Má»™t Sprite Ä‘ang cố gắng mang theo má»™t Scepter to gần bằng nó. Cố gắng " "không cưá»i, ngươi há»i: \"Cần giúp đỡ không?\" Sprite trừng mắt nhìn ngươi và " "trả lá»i: \"ngươi nghÄ© Ä‘iá»u này thật buồn cưá»i? Tốt thôi. Ngươi có thể mang " "nó. Dù sao thì tôi cÅ©ng thích bay hÆ¡n.\"" msgid "" "An old seaman tells you a tale of an enchanted masthead that he used in his " "youth to rally his crew during times of trouble. He then hands you a faded " "map that shows where he hid it. After much exploring, you find it stashed " "underneath a nearby dock." msgstr "" "Má»™t thá»§y thá»§ già kể cho ngươi nghe câu chuyện vá» má»™t chiếc cá»™t buồm bị phù " "phép mà ông đã sá»­ dụng khi còn trẻ để tập hợp thá»§y thá»§ Ä‘oàn cá»§a mình trong " "thá»i Ä‘iểm khó khăn. Sau đó anh ta đưa cho ngươi má»™t tấm bản đồ đã má» cho " "thấy nÆ¡i anh ta giấu nó. Sau nhiá»u lần khám phá, ngươi thấy nó được giấu bên " "dưới má»™t bến tàu gần đó." msgid "Masthead" msgstr "Tiêu Ä‘á»" #, fuzzy msgid "" "The %{name} boosts the hero's troops' luck and morale by %{count} each in " "sea combat." msgstr "" "%{name} tăng cưá»ng may mắn và tinh thần cá»§a ngươi thêm %{count} trong trận " "chiến trên biển." msgid "Sphere of Negation" msgstr "Quả cầu Phá»§ định" msgid "The %{name} disables all spell casting, for both sides, in combat." msgstr "" "%{name} vô hiệu hóa tất cả các phép thuật sá»­ dụng cho cả hai bên trong chiến " "đấu." msgid "" "You stop to help a Peasant catch a runaway mare. To show his gratitude, he " "hands you a tiny sphere. As soon as you grasp it, you feel the magical " "energy drain from your limbs..." msgstr "" "Ngươi dừng lại để giúp má»™t ngưá»i Nông dân bắt má»™t con ngá»±a cái Ä‘ang chạy " "trốn. Äể tá» lòng biết Æ¡n, anh ấy đưa cho ngươi má»™t quả cầu nhá». Ngay khi " "ngươi nắm lấy nó, ngươi sẽ cảm thấy năng lượng ma thuật rút cạn khá»i tay " "chân mình..." msgid "Staff of Wizardry" msgstr "Gậy Phù thá»§y" #, fuzzy msgid "The %{name} boosts the hero's spell power by %{count}." msgstr "%{name} tăng sức mạnh phép thuật cá»§a ngươi thêm %{count}." msgid "" "While out scaring up game, your troops find a mysterious staff levitating " "about three feet off of the ground. They hand it to you, and you notice an " "inscription. It reads: \"Brains best brawn and magic beats might. Heed my " "words, and you'll win every fight.\"" msgstr "" "Trong khi Ä‘ang hù dá»a trò chÆ¡i, quân cá»§a ngươi tìm thấy má»™t cây trượng bí ẩn " "Ä‘ang bay cách mặt đất khoảng 1 mét. HỠđưa nó cho ngươi và ngươi nhận thấy " "má»™t dòng chữ. Nó viết: \"Bá»™ não có sức mạnh tốt nhất và nhịp đập ma thuật. " "Hãy chú ý đến lá»i nói cá»§a tôi và ngươi sẽ thắng má»i cuá»™c chiến.\"" msgid "Sword Breaker" msgstr "Kẻ phá Kiếm" #, fuzzy msgid "The %{name} increases the hero's defense by %{count} and attack by 1." msgstr "" "%{name} tăng khả năng phòng thá»§ cá»§a ngươi thêm %{count} và tấn công thêm 1." msgid "" "A former Captain of the Guard admires your quest and gives you the enchanted " "Sword Breaker that he relied on during his tour of duty." msgstr "" "Má»™t cá»±u Äá»™i trưởng Äá»™i cận vệ ngưỡng má»™ nhiệm vụ cá»§a ngươi và trao cho ngươi " "Kẻ phá Kiếm đầy mê hoặc mà anh ta đã dá»±a vào trong chuyến công tác cá»§a mình." msgid "Sword of Anduran" msgstr "Kiếm cá»§a Anduran" msgid "" "A Troll stops you and says: \"Pay me 5,000 gold, or the Sword of Anduran " "will slay you where you stand.\" You refuse. The troll grabs the sword " "hanging from its belt, screams in pain, and runs away. Picking up the fabled " "sword, you give thanks that half-witted Trolls tend to grab the wrong end of " "sharp objects." msgstr "" "Má»™t con Quá»· lùn ngăn ngươi lại và nói: \"Trả cho tôi 5.000 vàng, nếu không " "Thanh kiếm Anduran sẽ giết ngươi ngay tại chá»—.\" Ngươi từ chối. Con quá»· lùn " "chá»™p lấy thanh kiếm treo trên thắt lưng, hét lên Ä‘au đớn rồi bá» chạy. Nhặt " "thanh kiếm huyá»n thoại, ngươi tạ Æ¡n vì lÅ© Quá»· lùn ná»­a vá»i có xu hướng tóm " "nhầm đầu cá»§a những vật sắc nhá»n." msgid "Spade of Necromancy" msgstr "Thuổng cá»§a Chiêu hồn" #, fuzzy msgid "The %{name} gives the hero increased necromancy skill." msgstr "%{name} giúp ngươi tăng kỹ năng chiêu hồn." msgid "" "A dirty shovel has been thrust into a dirt mound nearby. Upon investigation, " "you discover it to be the enchanted shovel of the Gravediggers, long thought " "lost by mortals." msgstr "" "Má»™t chiếc xẻng bẩn đã được đẩy vào má»™t ụ đất gần đó. Sau khi Ä‘iá»u tra, ngươi " "phát hiện ra đó là chiếc xẻng bị phù phép cá»§a Gravediggers, ngưá»i phàm tưởng " "đã thất lạc từ lâu." msgid "spellBonus|selected by user" msgstr "chá»n bởi ngưá»i dùng" msgid "Wood" msgstr "Gá»—" msgid "Mercury" msgstr "Thá»§y ngân" msgid "Ore" msgstr "Äá" msgid "Sulfur" msgstr "Lưu huỳnh" msgid "Crystal" msgstr "Thạch anh" msgid "Gems" msgstr "Ngá»c" msgid "Gold" msgstr "Vàng" msgid "" "There are seven resources in Heroes 2, used to build and improves castles, " "purchase troops and recruit heroes. Gold is the most common, required for " "virtually everything. Wood and ore are used for most buildings. Gems, " "Mercury, Sulfur and Crystal are rare magical resources used for the most " "powerful creatures and buildings." msgstr "" "Có bảy tài nguyên trong Heroes 2, được sá»­ dụng để xây dá»±ng và cải thiện lâu " "đài, mua quân và chiêu má»™ anh hùng. Vàng là thứ phổ biến nhất, cần thiết cho " "hầu hết má»i thứ. Gá»— và quặng được sá»­ dụng cho hầu hết các tòa nhà. Ngá»c, " "Thá»§y ngân, Lưu huỳnh và Pha lê là những tài nguyên ma thuật quý hiếm được sá»­ " "dụng cho những sinh vật và công trình mạnh mẽ nhất." msgid "" "Causes a giant fireball to strike the selected area, damaging all nearby " "creatures." msgstr "" "Tạo ra má»™t quả cầu lá»­a khổng lồ tấn công khu vá»±c đã chá»n, gây sát thương cho " "tất cả sinh vật gần đó." msgid "Fireball" msgstr "Cầu lá»­a" msgid "Fireblast" msgstr "Vụ nổ lá»­a" msgid "" "An improved version of fireball, fireblast affects two hexes around the " "center point of the spell, rather than one." msgstr "" "Má»™t phiên bản cải tiến cá»§a quả cầu lá»­a, vụ nổ lá»­a ảnh hưởng đến hai hình lục " "giác xung quanh Ä‘iểm trung tâm cá»§a câu thần chú, thay vì má»™t." msgid "Causes a bolt of electrical energy to strike the selected creature." msgstr "Tạo ra má»™t tia năng lượng Ä‘iện tấn công sinh vật được chá»n." msgid "Lightning Bolt" msgstr "Tia chá»›p" msgid "Chain Lightning" msgstr "Sét chuyá»n" msgid "" "Causes a bolt of electrical energy to strike a selected creature, then " "strike the nearest creature with half damage, then strike the NEXT nearest " "creature with half again damage, and so on, until it becomes too weak to be " "harmful. Warning: This spell can hit your own creatures!" msgstr "" "Tạo ra má»™t tia năng lượng Ä‘iện để tấn công má»™t sinh vật đã chá»n, sau đó tấn " "công sinh vật gần nhất vá»›i má»™t ná»­a sát thương, sau đó tấn công sinh vật gần " "nhất TIẾP THEO vá»›i má»™t ná»­a sát thương, v.v., cho đến khi nó trở nên quá yếu " "để có thể gây hại. Cảnh báo: Phép thuật này có thể tấn công sinh vật cá»§a " "chính ngươi!" msgid "Teleport" msgstr "Dịch chuyển" msgid "" "Teleports the creature you select to any open position on the battlefield." msgstr "" "Dịch chuyển sinh vật ngươi chá»n đến bất kỳ vị trí mở nào trên chiến trưá»ng." msgid "Cure" msgstr "Chữa trị" msgid "" "Removes all negative spells cast upon one of your units, and restores up to " "%{count} HP per level of spell power." msgstr "" "Loại bá» tất cả các phép thuật tiêu cá»±c được sá»­ dụng trên má»™t trong các đơn " "vị cá»§a ngươi và phục hồi tối Ä‘a %{count} HP cho má»—i cấp độ sức mạnh phép " "thuật." msgid "Mass Cure" msgstr "Chữa trị Rá»™ng" msgid "" "Removes all negative spells cast upon your forces, and restores up to " "%{count} HP per level of spell power, per creature." msgstr "" "Loại bá» tất cả các phép thuật tiêu cá»±c tác dụng lên lá»±c lượng cá»§a ngươi và " "phục hồi tối Ä‘a %{count} HP cho má»—i cấp sức mạnh phép thuật, cho má»—i sinh " "vật." msgid "Resurrect" msgstr "Hồi sinh" msgid "Resurrects creatures from a damaged or dead unit until end of combat." msgstr "" "Hồi sinh các sinh vật từ đơn vị bị thương tật hoặc chết cho đến khi kết thúc " "trận chiến." msgid "Resurrect True" msgstr "Hồi sinh Thá»±c" msgid "Resurrects creatures from a damaged or dead unit permanently." msgstr "Phục hồi vÄ©nh viá»…n các sinh vật từ má»™t đơn vị bị thương tật hoặc chết." msgid "Haste" msgstr "Tăng tốc" msgid "Increases the speed of any creature by %{count}." msgstr "Tăng tốc độ cá»§a bất kỳ sinh vật nào thêm %{count}." msgid "Increases the speed of all of your creatures by %{count}." msgstr "Tăng tốc độ cá»§a tất cả sinh vật cá»§a ngươi thêm %{count}." msgid "Mass Haste" msgstr "Tăng tốc Rá»™ng" msgid "Slows target to half movement rate." msgstr "Làm chậm mục tiêu xuống má»™t ná»­a tốc độ di chuyển." msgid "spell|Slow" msgstr "Chậm" msgid "Mass Slow" msgstr "Chậm Rá»™ng" msgid "Slows all enemies to half movement rate." msgstr "Làm chậm tất cả kẻ địch xuống má»™t ná»­a tốc độ di chuyển." msgid "Clouds the affected creatures' eyes, preventing them from moving." msgstr "Che má» mắt cá»§a sinh vật bị ảnh hưởng, ngăn chúng di chuyển." msgid "spell|Blind" msgstr "Mù" msgid "Bless" msgstr "Cầu nguyện" msgid "Causes the selected creatures to inflict maximum damage." msgstr "Khiến các sinh vật được chá»n gây sát thương tối Ä‘a." msgid "Causes all of your units to inflict maximum damage." msgstr "Khiến tất cả các đơn vị cá»§a ngươi gây sát thương tối Ä‘a." msgid "Mass Bless" msgstr "Cầu nguyện Rá»™ng" msgid "Magically increases the defense skill of the selected creatures." msgstr "Tăng kỹ năng phòng thá»§ cá»§a các sinh vật được chá»n má»™t cách kỳ diệu." msgid "Stoneskin" msgstr "Thạch bì" msgid "" "Increases the defense skill of the targeted creatures. This is an improved " "version of Stoneskin." msgstr "" "Tăng kỹ năng phòng thá»§ cá»§a các sinh vật được nhắm mục tiêu. Äây là phiên bản " "cải tiến cá»§a Thạch Bì." msgid "Steelskin" msgstr "Da sắt" msgid "Causes the selected creatures to inflict minimum damage." msgstr "Khiến các sinh vật được chá»n gây ra thiệt hại tối thiểu." msgid "Curse" msgstr "Nguyá»n rá»§a" msgid "Causes all enemy troops to inflict minimum damage." msgstr "Khiến tất cả quân địch gây sát thương tối thiểu." msgid "Mass Curse" msgstr "Nguyá»n rá»§a Rá»™ng" msgid "Damages all undead in the battle." msgstr "Gây sát thương cho tất cả xác sống trong trận chiến." msgid "Holy Word" msgstr "Thánh Ngữ" msgid "" "Damages all undead in the battle. This is an improved version of Holy Word." msgstr "" "Gây sát thương cho tất cả xác sống trong trận chiến. Äây là phiên bản cải " "tiến cá»§a Thánh Ngữ." msgid "Holy Shout" msgstr "Thánh Hét" msgid "Anti-Magic" msgstr "Phản Phép" msgid "Prevents harmful magic against the selected creatures." msgstr "Ngăn chặn phép thuật có hại chống lại các sinh vật được chá»n." msgid "Dispel Magic" msgstr "Trừ Tà" msgid "Removes all magic spells from a single target." msgstr "Loại bá» tất cả phép thuật khá»i má»™t mục tiêu." msgid "Mass Dispel" msgstr "Trừ tà Rá»™ng" msgid "Removes all magic spells from all creatures." msgstr "Loại bá» tất cả các phép thuật từ tất cả các sinh vật." msgid "Causes a magic arrow to strike the selected target." msgstr "Tạo ra má»™t mÅ©i tên ma thuật tấn công mục tiêu đã chá»n." msgid "Magic Arrow" msgstr "MÅ©i tên Ma thuật" msgid "Berserker" msgstr "Kẻ Ä‘iên" msgid "Causes a creature to attack its nearest neighbor." msgstr "Khiến má»™t sinh vật tấn công ngưá»i hàng xóm gần nhất cá»§a nó." msgid "Armageddon" msgstr "Tận thế" msgid "" "Holy terror strikes the battlefield, causing severe damage to all creatures." msgstr "" "Thánh kinh khá»§ng bố tấn công chiến trưá»ng, gây ra thiệt hại nặng ná» cho má»i " "sinh vật." msgid "Elemental Storm" msgstr "Nguyên tố Bão" msgid "Magical elements pour down on the battlefield, damaging all creatures." msgstr "" "Các yếu tố ma thuật đổ xuống chiến trưá»ng, gây sát thương cho má»i sinh vật." msgid "" "A rain of rocks strikes an area of the battlefield, damaging all nearby " "creatures." msgstr "" "Má»™t cÆ¡n mưa đá tấn công má»™t khu vá»±c trên chiến trưá»ng, gây sát thương cho " "tất cả sinh vật gần đó." msgid "Meteor Shower" msgstr "Mưa Sao băng" msgid "Paralyze" msgstr "Hóa đá" msgid "The targeted creatures are paralyzed, unable to move or retaliate." msgstr "Các sinh vật mục tiêu bị tê liệt, không thể di chuyển hoặc phản kháng." msgid "Hypnotize" msgstr "Thôi miên" msgid "" "Brings a single enemy unit under your control if its hits are less than " "%{count} times the caster's spell power." msgstr "" "ÄÆ°a má»™t đơn vị kẻ thù vào tầm kiểm soát cá»§a ngươi nếu số lần tấn công cá»§a nó " "nhá» hÆ¡n %{count} lần sức mạnh phép thuật cá»§a ngưá»i thi triển." msgid "Cold Ray" msgstr "Tia Lạnh" msgid "Drains body heat from a single enemy unit." msgstr "Hút nhiệt cÆ¡ thể từ má»™t đơn vị kẻ địch." msgid "Cold Ring" msgstr "Vòng Lạnh" msgid "" "Drains body heat from all units surrounding the center point, but not " "including the center point." msgstr "" "Thoát nhiệt cÆ¡ thể từ tất cả các đơn vị xung quanh Ä‘iểm trung tâm, nhưng " "không bao gồm Ä‘iểm trung tâm." msgid "Disrupting Ray" msgstr "Tia Phá vỡ" msgid "Reduces the defense rating of an enemy unit by three." msgstr "Giảm chỉ số phòng thá»§ cá»§a đơn vị địch xuống ba." msgid "Damages all living (non-undead) units in the battle." msgstr "" "Gây sát thương cho tất cả các đơn vị sống (không phải xác sống) trong trận " "chiến." msgid "Death Ripple" msgstr "Gợn sóng Chết" msgid "Death Wave" msgstr "Sóng Chết" msgid "" "Damages all living (non-undead) units in the battle. This spell is an " "improved version of Death Ripple." msgstr "" "Gây sát thương cho tất cả các đơn vị sống (không phải xác sống) trong trận " "chiến. Phép thuật này là phiên bản cải tiến cá»§a Gợn sóng Chết." msgid "Dragon Slayer" msgstr "Kẻ giết Rồng" msgid "Greatly increases a unit's attack skill vs. Dragons." msgstr "Tăng đáng kể kỹ năng tấn công cá»§a đơn vị so vá»›i Rồng." msgid "Blood Lust" msgstr "Khát máu" msgid "Increases a unit's attack skill." msgstr "Tăng kỹ năng tấn công cá»§a má»™t đơn vị." msgid "Animate Dead" msgstr "Sức sống Thần chết" msgid "Resurrects creatures from a damaged or dead undead unit permanently." msgstr "" "Phục hồi vÄ©nh viá»…n các sinh vật từ má»™t đơn vị xác sống bị hư há»ng hoặc đã " "chết." msgid "Mirror Image" msgstr "Ảnh Gương" msgid "" "Creates an illusionary unit that duplicates one of your existing units. This " "illusionary unit does the same damages as the original, but will vanish if " "it takes any damage." msgstr "" "Tạo má»™t đơn vị ảo ảnh sao chép má»™t trong các đơn vị hiện có cá»§a ngươi. ÄÆ¡n " "vị ảo ảnh này gây sát thương tương tá»± như ban đầu, nhưng sẽ biến mất nếu " "nhận bất kỳ thiệt hại nào." msgid "Shield" msgstr "Khiên" msgid "" "Halves damage received from ranged attacks for a single unit. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Giảm má»™t ná»­a sát thương nhận được từ các đòn tấn công tầm xa cho má»™t đơn vị. " "Không ảnh hưởng đến sát thương nhận được từ Tháp pháo hoặc Cung máy." msgid "Mass Shield" msgstr "Khiên Rá»™ng" msgid "" "Halves damage received from ranged attacks for all of your units. Does not " "affect damage received from Turrets or Ballistae." msgstr "" "Giảm má»™t ná»­a sát thương nhận được từ các cuá»™c tấn công tầm xa cho tất cả các " "đơn vị cá»§a ngươi. Không ảnh hưởng đến sát thương nhận được từ Tháp pháo hoặc " "Cung máy." msgid "Summon Earth Elemental" msgstr "Triệu hồi Nguyên tố Äất" msgid "Summons Earth Elementals to fight for your army." msgstr "Triệu hồi Nguyên tố Äất tá»›i để chiến đấu cho quân đội ngươi." msgid "Summon Air Elemental" msgstr "Triệu hồi Nguyên tố Khí" msgid "Summons Air Elementals to fight for your army." msgstr "Triệu hồi Nguyên tố Khí tá»›i để chiến đấu cho quân đội ngươi." msgid "Summon Fire Elemental" msgstr "Triệu hồi Nguyên tố Lá»­a" msgid "Summons Fire Elementals to fight for your army." msgstr "Triệu hồi Nguyên tố Lá»­a tá»›i để chiến đấu cho quân đội ngươi." msgid "Summon Water Elemental" msgstr "Triệu hồi Nguyên tố Nước" msgid "Summons Water Elementals to fight for your army." msgstr "Triệu hồi Nguyên tố Nước tá»›i để chiến đấu cho quân đội ngươi." msgid "Damages castle walls." msgstr "Sát thương tưá»ng lâu đài." msgid "Earthquake" msgstr "Äá»™ng đất" msgid "Causes all mines across the land to become visible." msgstr "Làm cho tất cả các má» trên đất liá»n hiện rõ." msgid "View Mines" msgstr "Xem Má»" msgid "Causes all resources across the land to become visible." msgstr "Làm cho tất cả các tài nguyên trên đất liá»n trở nên hữu hình." msgid "View Resources" msgstr "Xem Tài nguyên" msgid "Causes all artifacts across the land to become visible." msgstr "Làm cho tất cả hiện vật trên khắp vùng đất có thể nhìn thấy được." msgid "View Artifacts" msgstr "Xem Bảo vật" msgid "Causes all towns and castles across the land to become visible." msgstr "Làm cho tất cả các thị trấn và lâu đài trên khắp vùng đất hiện rõ." msgid "View Towns" msgstr "Xem Thị trấn" msgid "Causes all Heroes across the land to become visible." msgstr "Làm cho tất cả các Anh hùng trên khắp vùng đất trở nên hữu hình." msgid "View Heroes" msgstr "Xem Tướng" msgid "Causes the entire land to become visible." msgstr "Làm cho toàn bá»™ vùng đất trở nên hữu hình." msgid "View All" msgstr "Xem Tất cả" msgid "Allows the caster to view detailed information on enemy Heroes." msgstr "Cho phép ngưá»i thi triển xem thông tin chi tiết vá» Tướng địch." msgid "Summon Boat" msgstr "Triệu hồi Thuyá»n" msgid "" "Summons the nearest unoccupied, friendly boat to an adjacent shore location. " "A friendly boat is one which you just built or were the most recent player " "to occupy." msgstr "" "Triệu tập chiếc thuyá»n thân thiện, trống gần nhất đến vị trí bá» biển liá»n " "ká». Má»™t chiếc thuyá»n thân thiện là chiếc thuyá»n mà ngươi vừa đóng hoặc là " "ngưá»i chÆ¡i gần đây nhất chiếm giữ." msgid "Allows the caster to magically transport to a nearby location." msgstr "" "Cho phép ngưá»i sá»­ dụng dịch chuyển má»™t cách kỳ diệu đến má»™t địa Ä‘iểm gần đó." msgid "Dimension Door" msgstr "Cá»­a Kích thước" msgid "Returns the caster to any town or castle currently owned." msgstr "" "Trả ngưá»i Ä‘iá»u khiển vá» bất kỳ thị trấn hoặc lâu đài nào hiện Ä‘ang sở hữu." msgid "Town Gate" msgstr "Cổng Thành" msgid "" "Returns the hero to the town or castle of choice, provided it is controlled " "by you." msgstr "" "ÄÆ°a anh hùng trở lại thị trấn hoặc lâu đài mà ngươi lá»±a chá»n, miá»…n là nó do " "ngươi Ä‘iá»u khiển." msgid "Visions" msgstr "Thấu thị" msgid "" "Visions predicts the likely outcome of an encounter with a neutral army camp." msgstr "" "Thấu thị dá»± Ä‘oán kết quả có thể xảy ra cá»§a cuá»™c chạm trán vá»›i trại quân " "trung lập." msgid "Haunt" msgstr "Ma ám" msgid "" "Haunts a mine you control with Ghosts. This mine stops producing resources. " "(If I can't keep it, nobody will!)" msgstr "" "Ãm má»™t má» mà ngươi Ä‘iá»u khiển bằng Bóng ma. Má» này ngừng sản xuất tài " "nguyên. (Nếu tôi không giữ được thì sẽ không có ai giữ được!)" msgid "Set Earth Guardian" msgstr "Äặt Ngưá»i bảo vệ Äất" msgid "Sets Earth Elementals to guard a mine against enemy armies." msgstr "Äặt các Nguyên tố Äất để bảo vệ má» chống lại quân địch." msgid "Set Air Guardian" msgstr "Äặt Ngưá»i bảo vệ Khí" msgid "Sets Air Elementals to guard a mine against enemy armies." msgstr "Äặt các Nguyên tố Khí để bảo vệ má» chống lại quân địch." msgid "Set Fire Guardian" msgstr "Äặt Ngưá»i bảo vệ Lá»­a" msgid "Sets Fire Elementals to guard a mine against enemy armies." msgstr "Äặt các Nguyên tố Lá»­a để bảo vệ má» chống lại quân địch." msgid "Set Water Guardian" msgstr "Äặt Ngưá»i bảo vệ Nước" msgid "Sets Water Elementals to guard a mine against enemy armies." msgstr "Äặt các Nguyên tố Nước để bảo vệ má» chống lại quân địch." msgid "Random Spell" msgstr "Ngẫu nhiên Phép" msgid "Randomly selected spell of any level." msgstr "Ngẫu nhiên lá»±a chá»n phép cá»§a các cấp." msgid "Random 1st Level Spell" msgstr "Ngẫu nhiên Phép Cấp 1" msgid "Randomly selected 1st level spell." msgstr "Ngẫu nhiên lá»±a chá»n pháp cấp 1." msgid "Random 2nd Level Spell" msgstr "Ngẫu nhiên Phép Cấp 2" msgid "Randomly selected 2nd level spell." msgstr "Ngẫu nhiên lá»±a chá»n pháp cấp 2." msgid "Random 3rd Level Spell" msgstr "Ngẫu nhiên Phép Cấp 3" msgid "Randomly selected 3rd level spell." msgstr "Ngẫu nhiên lá»±a chá»n pháp cấp 3." msgid "Random 4th Level Spell" msgstr "Ngẫu nhiên Phép Cấp 4" msgid "Randomly selected 4th level spell." msgstr "Ngẫu nhiên lá»±a chá»n pháp cấp 4." msgid "Random 5th Level Spell" msgstr "Ngẫu nhiên Phép Cấp 5" msgid "Randomly selected 5th level spell." msgstr "Ngẫu nhiên lá»±a chá»n pháp cấp 5." msgid "Petrification" msgstr "Hóa đá" msgid "" "Turns the affected creature into stone. A petrified creature receives half " "damage from a direct attack." msgstr "" "Biến sinh vật bị ảnh hưởng thành đá. Má»™t sinh vật hóa đá nhận má»™t ná»­a sát " "thương từ đòn tấn công trá»±c tiếp." msgid "You have no Magic Book, so you cannot cast a spell." msgstr "Ngươi không có Sách Phép, nên ngươi không thể niệm chú má»™t phép." msgid "No spell to cast." msgstr "Không có phép để niệm." msgid "Your hero has %{point} spell points remaining." msgstr "Tướng cá»§a ngươi còn có %{point} Ä‘iểm phép." msgid "View Adventure Spells" msgstr "Xem Phép Phiêu lưu" msgid "View Combat Spells" msgstr "Xem Phép Chiến đấu" msgid "View previous page" msgstr "Xem trang trước" msgid "View next page" msgstr "Xem trang sau" msgid "Close Spellbook" msgstr "Gấp sách phép" msgid "View %{spell}" msgstr "Xem %{spell}" msgid "This spell does %{damage} points of damage." msgstr "Phép thuật này gây %{damage} Ä‘iểm sát thương." msgid "" "This spell summons\n" "%{count} %{monster}." msgstr "" "Phép thuật này triệu hồi\n" "%{count} %{monster}." msgid "This spell restores %{hp} HP." msgstr "Phép thuật này hồi phục %{hp} MÃU." msgid "This spell summons %{count} %{monster} to guard the mine." msgstr "Phép thuật này triệu hồi %{count} %{monster} tá»›i canh má»." msgid "The nearest town is %{town}." msgstr "Thị trấn gần nhất là %{town}." msgid "This town is occupied by your hero %{hero}." msgstr "Thị trấn này đã bị tướng cá»§a ngươi chiếm đóng %{hero}." msgid "" "This spell controls up to\n" "%{hp} HP." msgstr "" "Phép thuật này Ä‘iá»u khiển tá»›i\n" "%{hp} MÃU." msgid "The ultimate artifact is really the %{name}." msgstr "Bảo vật tối thượng có tên thật là %{name}." msgid "The ultimate artifact may be found in the %{name} regions of the world." msgstr "Bảo vật tối thượng có thể tìm thấy ở vùng %{name} cá»§a thế giá»›i." msgid "north-west" msgstr "tây-bắc" msgid "north" msgstr "bắc" msgid "north-east" msgstr "đông-bắc" msgid "west" msgstr "tây" msgid "center" msgstr "trung tâm" msgid "east" msgstr "đông" msgid "south-west" msgstr "tây-nam" msgid "south" msgstr "nam" msgid "south-east" msgstr "đông-nam" msgid "The truth is out there." msgstr "Sá»± thật ngay ngoài kia." msgid "The dark side is stronger." msgstr "Lá»±c lượng bóng tối Ä‘ang lá»›n mạnh." msgid "The end of the world is near." msgstr "Kết thúc cá»§a thế giá»›i Ä‘ang đến gần." msgid "The bones of Lord Slayer are buried in the foundation of the arena." msgstr "Xương cá»§a Giết Chúa chôn vùi dưới móng cá»§a đấu trưá»ng." msgid "A Black Dragon will take out a Titan any day of the week." msgstr "Rồng Äen sẽ mang Khổng Lồ bất cứ ngày nào trong ngày." msgid "He told her: Yada yada yada... and then she said: Blah, blah, blah..." msgstr "" "á»”ng nói vá»›i bả: Yada yada yada... và sau đó bả nói: Blah, blah, blah..." msgid "An unknown force is being resurrected..." msgstr "Má»™t lá»±c lượng bóng tối Ä‘ang hồi sinh..." msgid "" "Check the newest version of the game at\n" "https://github.com/ihhub/\n" "fheroes2/releases" msgstr "" "Kiểm tra phiên bản má»›i nhất cá»§a trò chÆ¡i tại\n" "https://github.com/ihhub/\n" "fheroes2/releases" #~ msgid "Terrain" #~ msgstr "Äịa hình" #~ msgid "Castles" #~ msgstr "Lâu đài" #~ msgid "Ocean object" #~ msgstr "Äối tượng đại dương" #~ msgid "Ocean Objects" #~ msgstr "Äối tượng Äại dương" #~ msgid "Grass Objects" #~ msgstr "Äối tượng Cá»" #~ msgid "Snow Objects" #~ msgstr "Äối tượng Tuyết" #~ msgid "Swamp Objects" #~ msgstr "Äối tượng Äầm lầy" #~ msgid "Lava Objects" #~ msgstr "Äối tượng Nham thạch" #~ msgid "Desert Objects" #~ msgstr "Äối tượng Sa mạc" #~ msgid "Dirt Objects" #~ msgstr "Äối tượng Bùn" #~ msgid "Beach Objects" #~ msgstr "Äối tượng Biển" #~ msgid "Used to place objects most appropriate for use on %{terrain}." #~ msgstr "Dùng để đặt các vật thể thích hợp nhất để sá»­ dụng trên %{terrain}." #~ msgid "" #~ "Used to place\n" #~ "a town or castle." #~ msgstr "" #~ "Äể đặt vị trí\n" #~ "má»™t thị trấn hoặc lâu đài." #~ msgid "Used to place an artifact." #~ msgstr "Dùng để đặt má»™t bảo vật." #~ msgid "" #~ "Used to place\n" #~ "a resource or treasure." #~ msgstr "" #~ "Dùng để đặt\n" #~ "má»™t tài nguyên hoặc kho báu." #~ msgid "vs" #~ msgstr "vs" #~ msgid "%{race2} %{name2}" #~ msgstr "%{race2} %{name2}" fheroes2-1.0.12+dfsg/files/soundfonts/000077500000000000000000000000001456075706000175455ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/files/soundfonts/GeneralUser-GS-LICENSE.txt000066400000000000000000000042631456075706000241160ustar00rootroot00000000000000*** GeneralUser GS v1.471 *** *** License v2.0 *** ** License of the complete work ** You may use GeneralUser GS without restriction for your own music creation, private or commercial. This SoundFont bank is provided to the community free of charge. Please feel free to use it in your software projects, and to modify the SoundFont bank or its packaging to suit your needs. ** License of contained samples ** GeneralUser GS inherits the usage rights of the samples contained within, all of which allow full use in music production, including the ability to make profit from musical recordings created with GeneralUser GS. Many of the samples are original, but some were taken from other banks freely (and legally) available on the Internet from various SoundFont websites. Because GeneralUser GS originated as a personal project with no intention for publication, I cannot be 100% sure where all of the samples originated, although I do know that none of them came from commercially published SoundFont packages or sample CDs. Regardless, many "free" SoundFonts available on the web may indeed contain samples of questionable origin. My understanding of the copyrights of all samples is only as good as the information provided by the original sources. If you become aware of any restricted samples being used in GeneralUser GS, please let me know so I can replace them. This uncertainty may concern you if you intend to use GeneralUser GS in a commercial software product. That being said, I have never received any complaint regarding sample ownership since I published the original GeneralUser GS back in 2000, and as far as I am aware, neither have any of the companies creating commercial software products using GeneralUser GS. ** More info ** If you plan to feature GeneralUser GS on your own website, please do not link directly to my download files. Either link to my website, or provide your own local copy instead. I hope you enjoy GeneralUser GS! This SoundFont bank is the product of many years of hard work. You can find updates to GeneralUser GS and more of my virtual instruments at: http://www.schristiancollins.com I can be reached at: s_chriscollins@hotmail.com. Thank you! -~Chris fheroes2-1.0.12+dfsg/files/soundfonts/GeneralUser-GS-README.txt000066400000000000000000000063011456075706000237640ustar00rootroot00000000000000********************************* *** GeneralUser GS v1.471 *** *** By S. Christian Collins *** *** 2/7/2017 *** ********************************* GeneralUser GS is a GM and GS compatible SoundFont bank for composing, playing MIDI files, and retro gaming. It features 259 instrument presets and 11 drum kits, all while possessing a very low memory footprint (less than 30 MB of RAM). Due to its clever, detailed sound programming, GeneralUser GS can sound as good or better than SoundFonts that are 2-3 times its size. I decided on the name "GeneralUser" because the bank was designed to sound well with all kinds of music rather than specializing in only a few styles, thereby benefiting the general user. It's not a great name, but that's what I came up with almost 20 years ago, and I haven't bothered to change it. +---------------+ | Compatibility | +---------------+ Although GeneralUser GS was originally designed for use with Sound Blaster audio cards, the most recent development has been focused on SoundFont-compatible software instead. The current version of GeneralUser GS requires a SoundFont synth with support for SoundFont 2.01 modulators. Compatible synths include FluidSynth 1.0.9 or later (and other software that uses FluidSynth such as Qsynth, LMMS, and ScummVM, just to name a few), MuseScore 2.0.3 or later, SynthFont2 2.0.3.0 or later, and VSTSynthFont 1.080 or later. While the current version of GeneralUser GS can be used with the hardware synthesizer found in many Sound Blaster audio cards, you will achieve better results using a version that was designed especially for this hardware (available at http://www.schristiancollins.com/generaluser.php). Most other SoundFont synths do not support all of the programming I have used in GeneralUser GS and will cause many of the presets to sound incorrect. For these unsupported SoundFont synths, you can download an older "SoftSynth" version of GeneralUser GS from my website that cuts out a lot of the special programming and is less likely to result in broken-sounding presets. +--------------+ | Installation | +--------------+ Please follow your synth/sound card's documentation for instructions on loading GeneralUser GS as the default GM/GS bank. This usually requires GeneralUser GS to be loaded into bank 0, and any other SoundFonts loaded into bank 0 to be removed prior. +---------------+ | Miscellaneous | +---------------+ Instrument list files are currently provided for Cakewalk SONAR (Windows) and Rosegarden (Linux) sequencers in the "instrument lists" folder. If you create an instrument list for any other sequencers, please e-mail them to me at s_chriscollins@hotmail.com and I will include them in the next release package. I have also provided some free MIDI files in the "demo MIDIs" folder for your listening enjoyment. Please feel free to send me more of those as well :) You can find updates to GeneralUser GS and more of my virtual instruments at http://www.schristiancollins.com. I can be reached at s_chriscollins@hotmail.com, but please don't send me questions that are easily answered by your sound hardware/software's documentation. I won't have time to answer every question that I receive. Thank you! -~Chris Collins fheroes2-1.0.12+dfsg/files/soundfonts/README.txt000066400000000000000000000007261456075706000212500ustar00rootroot00000000000000This directory contains SoundFont files for MIDI synthesizer. ====== fheroes2.sf3 - SoundFont for MIDI synthesizer based on the GeneralUser GS v1.471 by S. Christian Collins: http://www.schristiancollins.com/generaluser.php All samples, instruments and presets that were not used in the MIDI tracks of the original game were removed, the rest was converted to SF3 format using Polyphone 2.3.1. fheroes2-1.0.12+dfsg/iwyu.map000066400000000000000000000012461456075706000157400ustar00rootroot00000000000000[ { include: [ "", private, "", public ] }, { include: [ "", private, "", public ] }, { include: [ "", private, "", public ] }, { include: [ "", private, "", public ] }, { include: [ "", private, "", public ] }, { include: [ "", private, "", public ] }, { include: [ "", private, "", public ] }, { include: [ "", private, "", public ] } ] fheroes2-1.0.12+dfsg/script/000077500000000000000000000000001456075706000155455ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/script/README.md000066400000000000000000000001571456075706000170270ustar00rootroot00000000000000Scripts ====== This directory contains helper scripts needed for installation and compilation of the project. fheroes2-1.0.12+dfsg/script/agg/000077500000000000000000000000001456075706000163035ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/script/agg/README.md000066400000000000000000000001351456075706000175610ustar00rootroot00000000000000AGG ====== This directory contains helper scripts for extracting the contents of AGG files. fheroes2-1.0.12+dfsg/script/agg/extract_agg.bat000066400000000000000000000045711456075706000212720ustar00rootroot00000000000000:: ########################################################################### :: # fheroes2: https://github.com/ihhub/fheroes2 # :: # Copyright (C) 2023 # :: # # :: # 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., # :: # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # :: ########################################################################### @echo off set PATH=%~dp0;%PATH% set EXIT_CODE=0 call :extract_icn || ^ set EXIT_CODE=1 if not "%CI%" == "true" ( pause ) exit /B %EXIT_CODE% :extract_icn extractor agg *.AGG *.agg || exit /B 1 pal2img agg\HEROES2\kb.pal palette.png || exit /B 1 82m2wav wav\HEROES2 agg\HEROES2\*.82m || exit /B 1 82m2wav wav\HEROES2X agg\HEROES2X\*.82m || exit /B 1 bin2txt txt\HEROES2 agg\HEROES2\*.bin || exit /B 1 bin2txt txt\HEROES2X agg\HEROES2X\*.bin || exit /B 1 icn2img icn\HEROES2 agg\HEROES2\kb.pal agg\HEROES2\*.icn || exit /B 1 icn2img icn\HEROES2X agg\HEROES2\kb.pal agg\HEROES2X\*.icn || exit /B 1 til2img til\HEROES2 agg\HEROES2\kb.pal agg\HEROES2\*.til || exit /B 1 til2img til\HEROES2X agg\HEROES2\kb.pal agg\HEROES2X\*.til || exit /B 1 xmi2midi midi\HEROES2 agg\HEROES2\*.xmi || exit /B 1 xmi2midi midi\HEROES2X agg\HEROES2X\*.xmi || exit /B 1 echo: echo Asset extraction completed successfully. exit /B fheroes2-1.0.12+dfsg/script/agg/extract_agg.sh000066400000000000000000000037061456075706000211350ustar00rootroot00000000000000#!/usr/bin/env bash ########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2023 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### set -e -o pipefail PATH="$(dirname "$0"):$PATH" extractor agg *.AGG *.agg pal2img agg/*/kb.pal palette.png for DIR in agg/*; do if [[ ! -d "$DIR" ]]; then continue fi 82m2wav "wav/$(basename "$DIR")" "$DIR"/*.82m bin2txt "txt/$(basename "$DIR")" "$DIR"/*.bin icn2img "icn/$(basename "$DIR")" agg/*/kb.pal "$DIR"/*.icn til2img "til/$(basename "$DIR")" agg/*/kb.pal "$DIR"/*.til xmi2midi "midi/$(basename "$DIR")" "$DIR"/*.xmi done echo -e "\nAsset extraction completed successfully." fheroes2-1.0.12+dfsg/script/demo/000077500000000000000000000000001456075706000164715ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/script/demo/README.md000066400000000000000000000003111456075706000177430ustar00rootroot00000000000000Demo ====== This directory contains helper scripts to download and unpack demo version of original game. Downloading pirated or unlicensed version of the game is strictly prohibited by copyright law. fheroes2-1.0.12+dfsg/script/demo/download_demo_version.bat000066400000000000000000000030721456075706000235430ustar00rootroot00000000000000:: ########################################################################### :: # fheroes2: https://github.com/ihhub/fheroes2 # :: # Copyright (C) 2021 - 2023 # :: # # :: # 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., # :: # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # :: ########################################################################### @echo off powershell -ExecutionPolicy Unrestricted -File "%~dp0\download_demo_version.ps1" fheroes2-1.0.12+dfsg/script/demo/download_demo_version.ps1000066400000000000000000000172141456075706000235030ustar00rootroot00000000000000########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2021 - 2023 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### $ErrorActionPreference = "Stop" $h2DemoURL = "https://archive.org/download/HeroesofMightandMagicIITheSuccessionWars_1020/h2demo.zip" $h2DemoSHA256 = "12048C8B03875C81E69534A3813AAF6340975E77B762DC1B79A4FF5514240E3C" $wing32URL = "https://wikidll.com/download/25503/wing32.zip" $wing32SHA256 = "0CD89F09C66F53F30782858DF5453F6AC4C8A6D482F558E4FDF24C26E0A05A49" try { function Get-FileViaHTTP { param ( [string]$URL, [string]$FilePath ) try { Invoke-WebRequest -Uri $URL -OutFile $FilePath } catch [System.Management.Automation.CommandNotFoundException] { if ($_.Exception.CommandName -Eq "Invoke-WebRequest") { $webClient = New-Object System.Net.WebClient $webClient.DownloadFile($URL, $FilePath) } else { throw } } } function Get-SHA256HashForFile { param ( [string]$Path ) try { return (Get-FileHash -Path $Path -Algorithm SHA256).Hash } catch [System.Management.Automation.CommandNotFoundException] { if ($_.Exception.CommandName -Eq "Get-FileHash") { try { $output = certutil.exe -hashfile $Path sha256 2>&1 return ($output | Select-String -Pattern "^[0-9A-Fa-f]{64}$").Line.ToUpper() } catch { Write-Host -ForegroundColor Yellow "WARNING: Neither the Get-FileHash cmdlet nor certutil.exe is supported on this system, the hash of the downloaded file cannot be verified" if ($null -Ne $output) { Write-Host -ForegroundColor Yellow (-Join("certutil.exe output: ", ($output | Out-String))) } return $false } } else { throw } } } # Reserve space for the Invoke-WebRequest progress indicator for ($i = 0; $i -Le 7; $i++) { Write-Host "" } Write-Host -ForegroundColor Green (-Join("This script will download the demo version of the original Heroes of Might and Magic II`r`n", ` "It may take a few minutes, please wait...`r`n")) Write-Host "[1/5] determining the destination directory" $destPath = $null if (Test-Path -Path "fheroes2.exe" -PathType Leaf) { $destPath = "." } elseif (Test-Path -Path "..\..\src" -PathType Container) { # Special hack for developers running this script from the source tree $destPath = "..\.." } try { if ($null -Eq $destPath) { throw } while ($true) { $randName = [System.IO.Path]::GetRandomFileName() if (-Not (Test-Path -Path "$destPath\$randName")) { [void](New-Item -Path "$destPath\$randName" -ItemType "directory") Remove-Item -Path "$destPath\$randName" break } } } catch { if ($null -Eq $Env:APPDATA) { Write-Host -ForegroundColor Red "FATAL ERROR: Unable to determine the destination directory" return } $destPath = "$Env:APPDATA\fheroes2" if (-Not (Test-Path -Path $destPath -PathType Container)) { [void](New-Item -Path $destPath -ItemType "directory") } } Write-Host -ForegroundColor Green (-Join("Destination directory: ", (Resolve-Path $destPath).Path)) Write-Host "[2/5] downloading the demo version" $demoPath = "$destPath\demo" if (-Not (Test-Path -Path $demoPath -PathType Container)) { [void](New-Item -Path $demoPath -ItemType "directory") } Get-FileViaHTTP -URL $h2DemoURL -FilePath "$demoPath\h2demo.zip" $result = Get-SHA256HashForFile -Path "$demoPath\h2demo.zip" if (-Not ($result -Is [Boolean]) -And ($result -Ne $h2DemoSHA256)) { Write-Host -ForegroundColor Red (-Join("FATAL ERROR: Invalid hash for HoMM2 demo archive`r`n", ` "Expected:`t$h2DemoSHA256`r`n", ` "Got:`t`t$result`r`n", ` "Installation aborted")) return } Write-Host "[3/5] downloading the wing32.dll library" Get-FileViaHTTP -URL $wing32URL -FilePath "$demoPath\wing32.zip" $result = Get-SHA256HashForFile -Path "$demoPath\wing32.zip" if (-Not ($result -Is [Boolean]) -And ($result -Ne $wing32SHA256)) { Write-Host -ForegroundColor Red (-Join("FATAL ERROR: Invalid hash for wing32.dll archive`r`n", ` "Expected:`t$wing32SHA256`r`n", ` "Got:`t`t$result`r`n", ` "Installation aborted")) return } Write-Host "[4/5] unpacking archives" $shell = New-Object -ComObject "Shell.Application" $zip = $shell.NameSpace((Resolve-Path "$demoPath\h2demo.zip").Path) foreach ($item in $zip.Items()) { $shell.Namespace((Resolve-Path $demoPath).Path).CopyHere($item, 0x14) } $zip = $shell.NameSpace((Resolve-Path "$demoPath\wing32.zip").Path) foreach ($item in $zip.Items()) { $shell.Namespace((Resolve-Path $demoPath).Path).CopyHere($item, 0x14) } Write-Host "[5/5] copying files" $dataPath = "$destPath\data" $mapsPath = "$destPath\maps" if (-Not (Test-Path -Path $dataPath -PathType Container)) { [void](New-Item -Path $dataPath -ItemType "directory") } if (-Not (Test-Path -Path $mapsPath -PathType Container)) { [void](New-Item -Path $mapsPath -ItemType "directory") } $data = $shell.NameSpace((Resolve-Path "$demoPath\DATA").Path) $maps = $shell.NameSpace((Resolve-Path "$demoPath\MAPS").Path) foreach ($item in $data.Items()) { $shell.Namespace((Resolve-Path $dataPath).Path).CopyHere($item, 0x14) } foreach ($item in $maps.Items()) { $shell.Namespace((Resolve-Path $mapsPath).Path).CopyHere($item, 0x14) } } catch { Write-Host -ForegroundColor Red (-Join("FATAL ERROR: ", ($_ | Out-String))) } finally { Write-Host "Press any key to exit..." [void][System.Console]::ReadKey($true) } fheroes2-1.0.12+dfsg/script/demo/download_demo_version.sh000066400000000000000000000070701456075706000234110ustar00rootroot00000000000000#!/usr/bin/env bash ########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2021 - 2023 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### set -e -o pipefail H2DEMO_URL="https://archive.org/download/HeroesofMightandMagicIITheSuccessionWars_1020/h2demo.zip" H2DEMO_SHA256="12048c8b03875c81e69534a3813aaf6340975e77b762dc1b79a4ff5514240e3c" function echo_red { echo -e "\033[0;31m$*\033[0m" } function echo_green { echo -e "\033[0;32m$*\033[0m" } function echo_stage { echo echo_green "$*" echo } echo_green "This script will download the demo version of the original Heroes of Might and Magic II" echo_green "It may take a few minutes, please wait..." echo_stage "[1/4] determining the destination directory" DEST_PATH="" if [[ -n "$1" ]]; then DEST_PATH="$1" elif [[ -f fheroes2 && -x fheroes2 ]]; then DEST_PATH="." elif [[ -d ../../src ]]; then # Special hack for developers running this script from the source tree DEST_PATH="../.." fi if [[ -z "$DEST_PATH" || ! -d "$DEST_PATH" || ! -w "$DEST_PATH" ]]; then if [[ "$(uname 2> /dev/null)" == "Linux" ]]; then DEST_PATH="${XDG_CONFIG_HOME:-$HOME/.local/share}/fheroes2" elif [[ -z "$1" ]]; then DEST_PATH="$HOME/.fheroes2" fi fi echo_green "Destination directory: $DEST_PATH" echo_stage "[2/4] downloading the demo version" [[ ! -d "$DEST_PATH/demo" ]] && mkdir -p "$DEST_PATH/demo" cd "$DEST_PATH/demo" if [[ -n "$(command -v wget)" ]]; then wget -O h2demo.zip "$H2DEMO_URL" elif [[ -n "$(command -v curl)" ]]; then curl -o h2demo.zip -L "$H2DEMO_URL" else echo_red "Neither wget nor curl were found in your system. Unable to download the demo version. Installation aborted." exit 1 fi echo "$H2DEMO_SHA256 *h2demo.zip" > checksums if [[ -n "$(command -v shasum)" ]]; then shasum --check --algorithm 256 checksums elif [[ -n "$(command -v sha256sum)" ]]; then sha256sum --check --strict checksums else echo_red "Neither shasum nor sha256sum were found in your system. Unable to verify the downloaded file. Installation aborted." exit 1 fi echo_stage "[3/4] unpacking archives" unzip -o h2demo.zip echo_stage "[4/4] copying files" [[ ! -d ../data ]] && mkdir ../data [[ ! -d ../maps ]] && mkdir ../maps cp -r DATA/* ../data cp -r MAPS/* ../maps fheroes2-1.0.12+dfsg/script/demo/download_demo_version_for_app_bundle.sh000066400000000000000000000032651456075706000264520ustar00rootroot00000000000000#!/bin/zsh ########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2022 - 2023 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### set -e PLATFORM_NAME="$(uname 2> /dev/null)" if [[ "${PLATFORM_NAME}" != "Darwin" ]]; then echo "This script must be run on a macOS host" exit 1 fi zsh "$(dirname "${(%):-%x}")/download_demo_version.sh" "${HOME}/Library/Application Support/fheroes2" fheroes2-1.0.12+dfsg/script/homm2/000077500000000000000000000000001456075706000165675ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/script/homm2/README.md000066400000000000000000000002201456075706000200400ustar00rootroot00000000000000HoMM2 ====== This directory contains helper scripts to extract and copy resources from the original Heroes of Might and Magic II distribution. fheroes2-1.0.12+dfsg/script/homm2/extract_homm2_resources.bat000066400000000000000000000030741456075706000241310ustar00rootroot00000000000000:: ########################################################################### :: # fheroes2: https://github.com/ihhub/fheroes2 # :: # Copyright (C) 2021 - 2023 # :: # # :: # 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., # :: # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # :: ########################################################################### @echo off powershell -ExecutionPolicy Unrestricted -File "%~dp0\extract_homm2_resources.ps1" fheroes2-1.0.12+dfsg/script/homm2/extract_homm2_resources.ps1000066400000000000000000000225661456075706000240750ustar00rootroot00000000000000########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2021 - 2023 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### $ErrorActionPreference = "Stop" try { function Test-HoMM2DirectoryPath { param ( [string]$Path ) if ((Test-Path -Path "$Path\DATA\HEROES2.AGG" -PathType Leaf) -And (Test-Path -Path "$Path\MAPS" -PathType Container)) { return $true } return $false } Write-Host -ForegroundColor Green "This script will extract and copy game resources from the original distribution of Heroes of Might and Magic II`r`n" Write-Host "[1/3] determining the destination directory" $destPath = $null if (Test-Path -Path "fheroes2.exe" -PathType Leaf) { $destPath = "." } elseif (Test-Path -Path "..\..\src" -PathType Container) { # Special hack for developers running this script from the source tree $destPath = "..\.." } try { if ($null -Eq $destPath) { throw } while ($true) { $randName = [System.IO.Path]::GetRandomFileName() if (-Not (Test-Path -Path "$destPath\$randName")) { [void](New-Item -Path "$destPath\$randName" -ItemType "directory") Remove-Item -Path "$destPath\$randName" break } } } catch { if ($null -Eq $Env:APPDATA) { Write-Host -ForegroundColor Red "FATAL ERROR: Unable to determine the destination directory" return } $destPath = "$Env:APPDATA\fheroes2" if (-Not (Test-Path -Path $destPath -PathType Container)) { [void](New-Item -Path $destPath -ItemType "directory") } } Write-Host -ForegroundColor Green (-Join("Destination directory: ", (Resolve-Path $destPath).Path)) Write-Host "[2/3] determining the HoMM2 directory" $homm2Path = $null # Some legacy HoMM2 installation use a CD drive $homm2CD = $null foreach ($key in @("HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", "HKEY_CURRENT_USER\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall", "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall", # Legacy HoMM2 installation "HKEY_LOCAL_MACHINE\SOFTWARE\New World Computing\Heroes of Might and Magic 2", "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\New World Computing\Heroes of Might and Magic 2")) { if (-Not (Test-Path -Path "Microsoft.PowerShell.Core\Registry::$key" -PathType Container)) { continue } foreach ($subkey in (Get-ChildItem -Path "Microsoft.PowerShell.Core\Registry::$key")) { $path = $subkey.GetValue("InstallLocation") if ($null -Eq $path) { # From HKLM\SOFTWARE\New World Computing\Heroes of Might and Magic 2 $path = $subkey.GetValue("AppPath") } if ($null -Ne $path) { $path = $path.TrimEnd("\") if (Test-HoMM2DirectoryPath -Path $path) { $homm2Path = $path # From HKLM\SOFTWARE\New World Computing\Heroes of Might and Magic 2 $cdDrive = $subkey.GetValue("CDDrive") if ($null -Ne $cdDrive) { # If CDDrive is not a regular disk name (e.g. F:), then it is some path (absolute or relative), we need to try to resolve it if ($cdDrive -notmatch '^[A-Za-z]:$') { $currentPath = (Get-Location).Path # If it is a relative path, then it should be resolved against the HoMM2 installation path, therefore, we will temporarily # change the current directory to the HoMM2 installation directory try { Set-Location $homm2Path $cdDrive = (Resolve-Path $cdDrive).Path # If CDDrive eventually resolves to the HoMM2 installation directory, then ignore it, because there is no need to copy # the same files twice if ((Resolve-Path $homm2Path).Path -Eq $cdDrive) { throw } } catch { $cdDrive = $null } finally { Set-Location $currentPath } } if ($null -Ne $cdDrive) { $homm2CD = $cdDrive.TrimEnd("\") } } break } } } } if ($null -Eq $homm2Path) { Write-Host -ForegroundColor Yellow "WARNING: Unable to determine the HoMM2 directory" $homm2Path = Read-Host -Prompt "Please enter the full path to the HoMM2 directory (e.g. C:\GOG Games\HoMM 2 Gold)" if (-Not (Test-HoMM2DirectoryPath -Path $homm2Path)) { Write-Host -ForegroundColor Red "FATAL ERROR: Unable to find the HoMM2 directory" return } } Write-Host -ForegroundColor Green (-Join("HoMM2 directory: ", (Resolve-Path $homm2Path).Path)) if ($null -Ne $homm2CD) { Write-Host -ForegroundColor Green "HoMM2 CD drive: $homm2CD" } Write-Host "[3/3] copying game resources" $shell = New-Object -ComObject "Shell.Application" foreach ($homm2Dir in @($homm2Path, $homm2CD)) { if ($null -Eq $homm2Dir) { continue } if (-Not (Test-Path -Path "$homm2Dir\" -PathType Container)) { continue } # fheroes2 can be installed to the HoMM2 directory, there is no need to copy files in this case if ((Resolve-Path $homm2Dir).Path -Eq (Resolve-Path $destPath).Path) { continue } foreach ($srcDir in @("HEROES2\ANIM", "ANIM", "DATA", "MAPS", "MUSIC")) { if (-Not (Test-Path -Path "$homm2Dir\$srcDir" -PathType Container)) { continue } $destDir = (Split-Path $srcDir -Leaf).ToLower() if (-Not (Test-Path -Path "$destPath\$destDir" -PathType Container)) { [void](New-Item -Path "$destPath\$destDir" -ItemType "directory") } $content = $shell.NameSpace((Resolve-Path "$homm2Dir\$srcDir").Path) foreach ($item in $content.Items()) { $shell.Namespace((Resolve-Path "$destPath\$destDir").Path).CopyHere($item, 0x14) } } } # Special case - CD image from GOG if (Test-Path -Path "$homm2Path\homm2.gog" -PathType Leaf) { foreach ($dosboxExe in @("DOSBox.exe", "DOSBOX\DOSBox.exe")) { if (-Not (Test-Path -Path "$homm2Path\$dosboxExe" -PathType Leaf)) { continue } if (-Not (Test-Path -Path "$destPath\anim" -PathType Container)) { [void](New-Item -Path "$destPath\anim" -ItemType "directory") } Write-Host -ForegroundColor Green "Running DOSBOX to extract animation resources, please wait..." Start-Process -FilePath "$homm2Path\$dosboxExe" -ArgumentList @("-c", "`"imgmount D '$homm2Path\homm2.gog' -t iso -fs iso`"", ` "-c", "`"mount E '$destPath'`"", ` "-c", "`"copy D:\HEROES2\ANIM\*.* E:\ANIM`"", ` "-c", "exit") ` -Wait break } } } catch { Write-Host -ForegroundColor Red (-Join("FATAL ERROR: ", ($_ | Out-String))) } finally { Write-Host "Press any key to exit..." [void][System.Console]::ReadKey($true) } fheroes2-1.0.12+dfsg/script/homm2/extract_homm2_resources.sh000066400000000000000000000127141456075706000237760ustar00rootroot00000000000000#!/usr/bin/env bash ########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2021 - 2023 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### set -e -o pipefail function echo_red { echo -e "\033[0;31m$*\033[0m" } function echo_green { echo -e "\033[0;32m$*\033[0m" } function echo_yellow { echo -e "\033[0;33m$*\033[0m" } function echo_stage { echo echo_green "$*" echo } function verify_homm2_path { [[ -f "$1/DATA/HEROES2.AGG" && -d "$1/MAPS" ]] } echo_green "This script will extract and copy game resources from the original distribution of Heroes of Might and Magic II" echo_stage "[1/3] determining the destination directory" DEST_PATH="" if [[ -n "$2" ]]; then DEST_PATH="$2" elif [[ -f fheroes2 && -x fheroes2 ]]; then DEST_PATH="." elif [[ -d ../../src ]]; then # Special hack for developers running this script from the source tree DEST_PATH="../.." fi if [[ -z "$DEST_PATH" || ! -d "$DEST_PATH" || ! -w "$DEST_PATH" ]]; then if [[ "$(uname 2> /dev/null)" == "Linux" ]]; then DEST_PATH="${XDG_CONFIG_HOME:-$HOME/.local/share}/fheroes2" elif [[ -z "$2" ]]; then DEST_PATH="$HOME/.fheroes2" fi fi echo_green "Destination directory: $DEST_PATH" echo_stage "[2/3] determining the HoMM2 directory or installer package" if [[ "$#" -gt "0" ]]; then HOMM2_PATH="$1" else read -e -p "Please enter the full path to the HoMM2 directory or installer package (e.g. /home/user/homm2 or /tmp/installer.exe): " HOMM2_PATH fi if [[ -f "$HOMM2_PATH" ]]; then if [[ -z "$(command -v innoextract)" ]]; then echo_red "innoextract was not found in your system. Unable to extract the installer package. Installation aborted." exit 1 fi EXTRACT_DIR="$(mktemp -d)" echo_green "Verifying the installer package, please wait..." innoextract -e -s -d "$EXTRACT_DIR" -- "$HOMM2_PATH" for ITEM in "$EXTRACT_DIR" "$EXTRACT_DIR"/*; do if verify_homm2_path "$ITEM"; then echo_green "HoMM2 installer package: $HOMM2_PATH" HOMM2_PATH="$ITEM" break fi done if [[ -f "$HOMM2_PATH" ]]; then echo_red "Unable to find HoMM2 files in this installer package. Installation aborted." exit 1 fi elif verify_homm2_path "$HOMM2_PATH"; then echo_green "HoMM2 directory: $HOMM2_PATH" else echo_red "Unable to find the HoMM2 directory or installer package. Installation aborted." exit 1 fi echo_stage "[3/3] copying game resources" [[ ! -d "$DEST_PATH" ]] && mkdir -p "$DEST_PATH" cd "$DEST_PATH" [[ ! -d anim ]] && mkdir anim [[ ! -d data ]] && mkdir data [[ ! -d maps ]] && mkdir maps [[ ! -d music ]] && mkdir music [[ -d "$HOMM2_PATH/HEROES2/ANIM" ]] && cp -r "$HOMM2_PATH/HEROES2/ANIM"/* anim [[ -d "$HOMM2_PATH/ANIM" ]] && cp -r "$HOMM2_PATH/ANIM"/* anim [[ -d "$HOMM2_PATH/DATA" ]] && cp -r "$HOMM2_PATH/DATA"/* data [[ -d "$HOMM2_PATH/MAPS" ]] && cp -r "$HOMM2_PATH/MAPS"/* maps [[ -d "$HOMM2_PATH/MUSIC" ]] && cp -r "$HOMM2_PATH/MUSIC"/* music if [[ ! -f "$HOMM2_PATH/homm2.gog" ]]; then exit 0 fi # Special case - CD image from GOG if [[ -n "$(command -v python3)" ]]; then PYTHON="python3" elif [[ -n "$(command -v python2)" ]]; then PYTHON="python2" elif [[ -n "$(command -v python)" ]]; then PYTHON="python" else echo_yellow "python was not found in your system. Please install it and re-run this script to extract animation resources." exit 0 fi if [[ -z "$(command -v bsdtar)" ]]; then echo_yellow "bsdtar was not found in your system. Please install it and re-run this script to extract animation resources." exit 0 fi echo_green "Extracting animation resources, please wait..." "$PYTHON" - << EOF with open("$HOMM2_PATH/homm2.gog", "rb") as raw_file: with open("homm2.iso", "wb") as iso_file: while True: buf = raw_file.read(2352) if not buf: break if buf[15] == 2: iso_file.write(buf[24:2072]) else: iso_file.write(buf[16:2064]) EOF bsdtar -x -f homm2.iso -C anim --include "HEROES2/ANIM/*" --strip-components=2 rm -f homm2.iso fheroes2-1.0.12+dfsg/script/homm2/extract_homm2_resources_for_app_bundle.sh000066400000000000000000000036121456075706000270320ustar00rootroot00000000000000#!/bin/zsh ########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2022 - 2023 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### set -e PLATFORM_NAME="$(uname 2> /dev/null)" if [[ "${PLATFORM_NAME}" != "Darwin" ]]; then echo "This script must be run on a macOS host" exit 1 fi if [[ "$#" == "1" ]]; then HOMM2_PATH="$1" else read "HOMM2_PATH?Please enter the full path to the HoMM2 directory or installer package (e.g. /home/user/homm2 or /tmp/installer.exe): " fi zsh "$(dirname "${(%):-%x}")/extract_homm2_resources.sh" "$HOMM2_PATH" "${HOME}/Library/Application Support/fheroes2" fheroes2-1.0.12+dfsg/script/homm2/resource_extraction_toolset.bat000066400000000000000000000031051456075706000251160ustar00rootroot00000000000000:: ########################################################################### :: # fheroes2: https://github.com/ihhub/fheroes2 # :: # Copyright (C) 2021 - 2023 # :: # # :: # 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., # :: # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # :: ########################################################################### @echo off powershell -ExecutionPolicy Unrestricted -Sta -File "%~dp0\resource_extraction_toolset.ps1" fheroes2-1.0.12+dfsg/script/homm2/resource_extraction_toolset.ps1000066400000000000000000000240531456075706000250600ustar00rootroot00000000000000########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2021 - 2023 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### $ErrorActionPreference = "Stop" try { function Test-DragDropFile { param ( [string[]]$FileDropList, [string]$AllowedExtension ) if ($FileDropList.Count -Ne 1) { return $false } if (-Not (Test-Path -Path $FileDropList[0] -PathType Leaf)) { return $false } $fileExtension = (Get-ChildItem $FileDropList[0]).Extension if ($null -Eq $fileExtension) { return $false } $fileExtension = $fileExtension.ToLower() if ($fileExtension -Ne $AllowedExtension) { return $false } return $true } function Copy-GOGMusic { param ( [string]$ArchiveName, [string]$DestPath ) Write-Host -ForegroundColor Green "Extracting GOG OST files, please wait..." $tempPath = $null while ($true) { $randName = [System.IO.Path]::GetRandomFileName() $tempPath = "$DestPath\$randName" if (-Not (Test-Path -Path $tempPath)) { [void](New-Item -Path $tempPath -ItemType "directory") break } } $shell = New-Object -ComObject "Shell.Application" $zip = $shell.NameSpace((Resolve-Path $ArchiveName).Path) foreach ($item in $zip.Items()) { $shell.Namespace((Resolve-Path $tempPath).Path).CopyHere($item, 0x14) } $tmpMusicPath = $null while ($true) { $randName = [System.IO.Path]::GetRandomFileName() $tmpMusicPath = "$DestPath\$randName" if (-Not (Test-Path -Path $tmpMusicPath)) { [void](New-Item -Path $tmpMusicPath -ItemType "directory") break } } if (-Not (Test-Path -Path $tmpMusicPath -PathType Container)) { [void](New-Item -Path $tmpMusicPath -ItemType "directory") } if (-Not (Test-Path -Path "$tmpMusicPath\pol" -PathType Container)) { [void](New-Item -Path "$tmpMusicPath\pol" -ItemType "directory") } if (-Not (Test-Path -Path "$tmpMusicPath\sw" -PathType Container)) { [void](New-Item -Path "$tmpMusicPath\sw" -ItemType "directory") } # Additional PoL soundtrack id -> SW soundtrack id $polSWMap = @{"44" = "05"; "45" = "06"; "46" = "07"; "47" = "08"; "48" = "09"; "49" = "10"} $musicType = $null foreach ($item in (Get-ChildItem -Path $tempPath -Recurse)) { if ($item.Name -notmatch "[0-9]{2}") { continue } $fileExtension = (Get-ChildItem $item.FullName).Extension if ($null -Eq $fileExtension) { continue } $fileExtension = $fileExtension.ToLower() if ($fileExtension -Eq ".flac") { $musicType = $fileExtension } elseif ($fileExtension -Eq ".mp3") { # An archive with FLAC can contain some MP3 files if ($null -Eq $musicType) { $musicType = $fileExtension } } else { continue } $trackNumber = ($item.Name | Select-String -Pattern "[0-9]{2}").Matches[0].Value # Castle soundtracks from the Succession Wars if ($polSWMap.Values -contains $trackNumber) { Copy-Item -Path $item.FullName -Destination "$tmpMusicPath\sw\Track$trackNumber$fileExtension" # Castle soundtracks from the Price of Loyalty expansion } elseif ($polSWMap.Keys -contains $trackNumber) { $trackNumber = $polSWMap[$trackNumber] Copy-Item -Path $item.FullName -Destination "$tmpMusicPath\pol\Track$trackNumber$fileExtension" } Copy-Item -Path $item.FullName -Destination "$tmpMusicPath\Track$trackNumber$fileExtension" } Remove-Item -Path $tempPath -Recurse if ($null -Eq $musicType) { Remove-Item -Path $tmpMusicPath -Recurse Write-Host -ForegroundColor Yellow "WARNING: No soundtracks were found" } else { $dirName = $null $uniqId = 0 while ($true) { if ($uniqId -Eq 0) { $dirName = "music$musicType" } else { $dirName = "music$musicType.$uniqId" } if (-Not (Test-Path -Path "$DestPath\$dirName")) { break } $uniqId++ } Rename-Item -Path $tmpMusicPath -NewName $dirName Write-Host -ForegroundColor Green (-Join("GOG OST directory: ", (Resolve-Path "$DestPath\$dirName").Path)) } } Write-Host -ForegroundColor Green "This script will run the extraction toolset to extract and copy additional game resources from the original distribution of Heroes of Might and Magic II`r`n" Write-Host "[1/2] determining the destination directory" $destPath = $null if (Test-Path -Path "fheroes2.exe" -PathType Leaf) { $destPath = "." } elseif (Test-Path -Path "..\..\src" -PathType Container) { # Special hack for developers running this script from the source tree $destPath = "..\.." } try { if ($null -Eq $destPath) { throw } while ($true) { $randName = [System.IO.Path]::GetRandomFileName() if (-Not (Test-Path -Path "$destPath\$randName")) { [void](New-Item -Path "$destPath\$randName" -ItemType "directory") Remove-Item -Path "$destPath\$randName" break } } } catch { if ($null -Eq $Env:APPDATA) { Write-Host -ForegroundColor Red "FATAL ERROR: Unable to determine the destination directory" return } $destPath = "$Env:APPDATA\fheroes2" if (-Not (Test-Path -Path $destPath -PathType Container)) { [void](New-Item -Path $destPath -ItemType "directory") } } Write-Host -ForegroundColor Green (-Join("Destination directory: ", (Resolve-Path $destPath).Path)) Write-Host "[2/2] running the resource extraction toolset" $commandToRun = $null [void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") $mainForm = New-Object -TypeName "System.Windows.Forms.Form" -Property @{Text = "Resource Extraction Toolset"} $gogOSTExtractionGroupBox = New-Object -TypeName "System.Windows.Forms.GroupBox" -Property @{ Text = "Extract a GOG OST archive" Dock = "Fill" Padding = 32 AllowDrop = $true } $gogOSTExtractionGroupBox.Add_DragEnter({ if (Test-DragDropFile -FileDropList $_.Data.GetFileDropList() -AllowedExtension ".zip") { $_.Effect = [Windows.Forms.DragDropEffects]::Copy } else { $_.Effect = [Windows.Forms.DragDropEffects]::None } }) $gogOSTExtractionGroupBox.add_DragDrop({ $fileDropList = $_.Data.GetFileDropList() if (-Not (Test-DragDropFile -FileDropList $fileDropList -AllowedExtension ".zip")) { return } $global:commandToRun = {Copy-GOGMusic -ArchiveName $fileDropList[0] -DestPath $destPath}.GetNewClosure() $mainForm.Close() }) $mainForm.Controls.Add($gogOSTExtractionGroupBox) $gogOSTExtractionBrowseButton = New-Object -TypeName "System.Windows.Forms.Button" -Property @{ Text = "Choose an OST archive file..." Dock = "Top" } $gogOSTExtractionBrowseButton.Add_Click({ $fileDialog = New-Object -TypeName "System.Windows.Forms.OpenFileDialog" -Property @{ Filter = "ZIP Archives (*.zip)|*.zip" } [void]$fileDialog.ShowDialog() if ($fileDialog.FileName -Eq "") { return } $global:commandToRun = {Copy-GOGMusic -ArchiveName $fileDialog.FileName -DestPath $destPath}.GetNewClosure() $mainForm.Close() }) $gogOSTExtractionGroupBox.Controls.Add($gogOSTExtractionBrowseButton) $gogOSTExtractionDragLabel = New-Object -TypeName "System.Windows.Forms.Label" -Property @{ Text = "Or drag && drop an OST archive file here" Dock = "Bottom" TextAlign = "MiddleCenter" } $gogOSTExtractionGroupBox.Controls.Add($gogOSTExtractionDragLabel) [System.Windows.Forms.Application]::Run($mainForm) if ($null -Ne $commandToRun) { & $commandToRun } } catch { Write-Host -ForegroundColor Red (-Join("FATAL ERROR: ", ($_ | Out-String))) } finally { Write-Host "Press any key to exit..." [void][System.Console]::ReadKey($true) } fheroes2-1.0.12+dfsg/script/linux/000077500000000000000000000000001456075706000167045ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/script/linux/README.md000066400000000000000000000001051456075706000201570ustar00rootroot00000000000000Linux ====== This directory contains helper scripts to install SDL. fheroes2-1.0.12+dfsg/script/linux/SDL_mixer.ymp000066400000000000000000000037301456075706000212640ustar00rootroot00000000000000 games openSUSE Games https://en.opensuse.org/Games https://download.opensuse.org/repositories/games/openSUSE_Leap_15.2/ openSUSE:Leap:15.2:Update Online updates for openSUSE Leap:15.2 This project is releasing the official updates for openSUSE Leap:15.2. http://download.opensuse.org/update/leap/15.2/oss/ openSUSE:Leap:15.2:NonFree Non Free parts of openSUSE Leap 15.2 distribution. Non free or non open source parts of openSUSE Leap 15.2. http://download.opensuse.org/distribution/leap/15.2/repo/non-oss/ openSUSE:Leap:15.2 openSUSE Leap 15.2 openSUSE Leap borrows packages from SLE for the base system. Please refer to https://osrt.opensuse.org/web/origin-manager/#openSUSE:Leap:15.2 for origin information http://download.opensuse.org/distribution/leap/15.2/repo/oss/ SDL_mixer SDL sound mixer library A multichannel audio mixer. It supports four channels of 16-bit stereo audio, plus a single channel of music, mixed by the popular MikMod MOD, Timidity MIDI, and SMPEG MP3 libraries. fheroes2-1.0.12+dfsg/script/linux/install_sdl2.sh000066400000000000000000000030171456075706000216330ustar00rootroot00000000000000#!/bin/bash ########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2021 - 2023 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### # Install SDL2 sudo apt-get install -y libsdl2-2.0-0 libsdl2-image-2.0-0 libsdl2-mixer-2.0-0 fheroes2-1.0.12+dfsg/script/linux/install_sdl2_dev.sh000066400000000000000000000030641456075706000224730ustar00rootroot00000000000000#!/bin/bash ########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2021 - 2023 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### # Install development dependencies targeting SDL2 sudo apt-get install -y gettext libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev fheroes2-1.0.12+dfsg/script/packaging/000077500000000000000000000000001456075706000174715ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/script/packaging/common/000077500000000000000000000000001456075706000207615ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/script/packaging/common/fheroes2.desktop000066400000000000000000000003641456075706000240740ustar00rootroot00000000000000[Desktop Entry] Type=Application Name=fheroes2 Comment=Free remake of Heroes of Might and Magic II game engine Icon=fheroes2 TryExec=fheroes2 Exec=fheroes2 Terminal=false StartupNotify=false Categories=Game;StrategyGame; Keywords=heroes;homm2; fheroes2-1.0.12+dfsg/script/packaging/common/fheroes2.metainfo.xml000066400000000000000000005636221456075706000250370ustar00rootroot00000000000000 io.github.ihhub.Fheroes2 fheroes2.desktop CC0-1.0 GPL-2.0 fheroes2 fheroes2 Resurrection team fheroes2 is a recreation of the Heroes of Might and Magic II game engine

This open source multiplatform project, written from scratch, is designed to reproduce the original game with significant improvements to the gameplay, graphics and logic (including support for high-resolution graphics, improved AI, numerous fixes and UI improvements), breathing new life into one of the most addictive turn-based strategy games.

Game StrategyGame heroes2 homm2 https://ihhub.github.io/fheroes2/ https://github.com/ihhub/fheroes2/issues https://github.com/ihhub/fheroes2/wiki/F.A.Q. https://ihhub.github.io/fheroes2/INSTALL.html https://ihhub.github.io/fheroes2/TRANSLATION.html https://www.patreon.com/fheroes2 https://discord.gg/xF85vbZ https://github.com/ihhub/fheroes2 https://github.com/ihhub/fheroes2/blob/master/CONTRIBUTING.md mild mild keyboard pointing touch https://github.com/ihhub/fheroes2/releases/tag/1.0.12

Changes in v1.0.12 (07 February 2024):

  • update the Russian translation
  • update the Spanish translation
  • correct icons alignment on the hero meeting screen
  • update the Polish translation
  • fix the retaliation of archers attacked by an ally under the influence of Berserker or Hypnotize spells, improve the AI usage of Hypnotize spell in general
  • fix incorrect Artesian Spring evaluation by the AI
  • make rules for AI surrender and retreat more unified
  • fix Oasis object passability
  • always offer one native hero at the beginning of a new week
  • puzzle and View World dialogs rendering improvements
  • implement Landscape Miscellaneous (non-action) objects placing in Editor
  • fix OKAY button translation for Interface Settings dialog after any setting change
  • make the AI not to know what objects contain
  • fix issue when Blind or Paralyze has been applied to a hypnotized unit during its turn
  • implement Adventure (action) Miscellaneous objects placing in Editor
  • tune the AI performance at different difficulty levels
  • add surrender cost to battle result dialog
  • improve overall image processing performance
  • update spell points, formations and artifacts descriptions
  • add infos on the cost of surrender
  • allow AI units covering an archer with the AREA_SHOT capability to attack neighboring units on their own
  • use proper name for PoL campaign in High Scores
  • use CP1257 code page for Lithaunian
  • offer the Wisdom skill to the heroes of "magic" classes on a mandatory basis at least once every three level-ups
  • implement Mountains, Rocks and Trees placing in the Editor
  • make the AI attack human heroes that have a greater chance to win than the AI heroes
  • implement Power-ups object placement in the Editor
  • allow AI units that ignore retaliatory strikes to attack neighboring enemy units while covering archers
  • avoid visiting Oasis and Watering Hole objects by AI heroes if they bring no benefit
  • make AI hero behavior depend on the time spent on the map
  • improve fog discovery by the AI heroes
  • adjust text in the skill popup dialog
  • implement Mines placing in the Editor
  • improve AI hero interaction with Witch's Hut object
  • allow the AI-controlled hero to surrender if his kingdom has sufficient gold reserves
  • add text width-adapted CANCEL button, change to StandardWindow for Resolution, Language, Adventure, File and Scenario Info dialogs
  • update Hungarian translation
  • add dwellings into the Editor
  • fix rare fog of war rendering issues
  • allow AI heroes to surrender if the conditions of retreat have been met, but it is impossible to retreat
  • implement the AI logic to transfer the slowest troops to the garrison at the end of the turn to try to get a movement bonus on the next turn
  • mark fheroes2 as DPI-aware on Windows
  • speed up image loading
  • speed up file loading and reduce memory usage on the Adventure Map
  • improve the AI logic for pay-to-hire dwellings on the Adventure Map
  • improve the AI heuristics of covering shooters when using defensive tactics
  • update buttons and add popup windows for them in Battle Only mode
  • use the "cautious offensive" tactics for AI only against melee-only armies without the distant attack potential (including damage spells)
  • update the Swedish translation
  • fix heroes with no names
  • add base code to load new map format files
  • use singular Mine form for the corresponding object
  • fix inaccurate Jail object removal
  • add missing rules for the plural in Hungarian language
  • restrict secondary skill name and level to the UI area
  • fix too wide text in quick hero info dialog
  • hide swap army/artifact buttons in the Battle Only mode
  • update several text descriptions
  • add Campfire object in the Editor
  • take into account SHIFT and CAPS LOCK being used at the same time
  • make radar rendering optimization
  • fix the AI spell damage estimation logic for units immune to the given spell
  • rework editor instrument groups: add new buttons, rework instrument panel, add evil interface
  • fix multiple spellbook-related issues in the Battle Only mode
  • update French translation
  • show an error when it is not possible to save a game on a hard disk
https://github.com/ihhub/fheroes2/releases/tag/1.0.11

Changes in v1.0.11 (23 December 2023):

  • improve the logic of AI defensive tactics
  • add Tree of Knowledge claimed and cost info to quick info dialog
  • add a different object popup window logic for the Editor
  • implement town and castle placing in the Editor
  • update the Spanish translation
  • update the French translation and add missing offset for an A with accent
  • update Bulgarian translation
  • increase width and height of the Campaign Difficulty selection dialog
  • make speed of fade-in & fade-out animations for AI-controlled heroes dependent on the AI speed setting
  • speed up battle pathfinder logic
  • update Spanish translation
  • add Spanish and Portuguese button font letters
  • fix invalid focus update after AI hero battle
  • update Danish translation
  • speed up the original map format reading
  • fix a crash with castle overlapping inside a map
  • add basic logic to load and save maps through Editor UI
  • add text rendering within a given ROI
  • do not show quick info for hidden buttons inside castle building info dialog
  • add missing AI team alliance to Evil campaign's 6th scenario
  • update Swedish translation
  • update Hungarian translation
  • update Vietnamese Translation
  • fix update of the morale/luck indicator when changing the hero in the Battle Only mode
  • add base map file format for the Editor
  • add Hotkeys categories for the future UI improvements
  • fix scroll location on file deletion
  • rework Battle Only mode
  • add cell selection in Details mode for the Editor
  • speed up map loading for New Game
  • allow application closure by close button if no resources available
  • fix multi-font text offset calculation
  • properly evaluate potential attack positions for wide units
  • correctly manage touch device events
  • add ability to place Ocean Objects within the Editor
https://github.com/ihhub/fheroes2/releases/tag/1.0.10

Changes in v1.0.10 (18 November 2023):

  • update Russian translation
  • rework Town Portal dialog
  • add artifact placement in the Editor
  • make Oracle dialog use selected interface mode (Good/Evil)
  • add treasure placement in the Editor
  • update hero status after digging on the Adventure Map
  • fix player names overlapping in Oracle and Thieves' Guild dialogs
  • fix dialog frames not being consistent for the same size windows
  • speed up monochrome cursors loading
  • fix rendering issue in Kingdom Overview dialog while dismissing a hero
  • fix castle and hero lists' updates after dismissing a hero in Kingdom Overview dialog
  • fix invalid attack positions from moat during battle
  • add support for right SHIFT, CTRL and ALT hotkeys
  • AI takes into account the spell immunity when calculating the effectiveness of mass damage spells
  • update Hungarian translation
  • fix incorrect AI estimation for moves during battle when a battlefield contains objects
  • update Vietnamese translation
  • fix several issues with loading ICN resources
  • add support for the future new heroes
  • fix the logic of purchasing a boat in a shipyard
  • increase the priority of those enemy units that have not yet got their turn, for proper AI decision making
  • add hero action button image
  • fix spelling in campaign text
  • display 0 shots during battle for archers with no shots left
  • fix random generator in normal and auto-combat modes
  • fix castle dialog rendering issues
  • adjust welcome window text at the first game launch
  • teach AI to use the marketplace to accelerate the development of their castles
  • update German translation
  • implement erase tool in the Editor
  • fix recruit dialog's MIN/MAX buttons when switching between creature upgrades
  • update Swedish translation
  • show proper Quick Info window for a hero during battle
  • fix troops' sprites overlapping in quick info
  • prioritize hiring those heroes whose portraits are not used by scenario specific heroes
https://github.com/ihhub/fheroes2/releases/tag/1.0.9

Changes in v1.0.9 (11 October 2023):

  • add "tent visited" info to barrier quick info dialog
  • update Vietnamese translation
  • rework Hot-Keys dialog
  • fix combat victory window creature position
  • Town Gate and Town Portal spells: use depersonalized wording
  • add an ability to place heroes in the Editor
  • add an ability to place Random Monsters in the Editor
  • fix rendering of very long scenario names in the campaign briefing dialog
  • disable the not implemented Random Map option in the Editor
  • update German translation
  • make a long press on the "continue movement" button to reset a hero's path
  • use the "continue movement" button to interact with an object a hero is standing on
  • make Grass the default terrain for new maps in the Editor
  • restrict heroes' meetings and army merging on Easy difficulty
  • AI should avoid splitting troops on Easy difficulty
  • add functionality to place monsters in the Editor
  • add a mirror image check during spell evaluation
  • teach battle AI to use shield spells
  • implement streams' placing in the Editor
  • adjust AI combat spell heuristics
  • fix rendering of long text in the Hero screen status bar
  • update Ukrainian translation
  • implement roads' placing in the Editor
  • fix the AI first strike logic
  • update Hungarian translation
  • fix battle AI protecting paralyzed archers
  • properly display player's name in the New Game window
  • fix memorizing the position in the map list when changing the map size filter
  • adjust the position of luck and morale icons in the hero info window
  • update the followers window to support text mode
  • fix the logic of ALWAYS_RETALIATE regarding paralyzed units
  • add historical changes support for the Editor
  • add the ability to confirm actions in battle for touch devices
  • update Russian translation
  • fix Editor Main Menu hotkeys
  • update Swedish translation
  • speed up overall rendering for cycling animation
  • do not render text beyond the info and status bars during battles
  • rework game settings on the Adventure Map
  • remove double spaces in translatable texts
  • update Bulgarian translation
  • update Slovak translation
  • update Czech translations
  • update Danish translation
  • use Home and End hotkeys to navigate among items within a list
  • always use the nearest cell to attack the nearby unit if a monster is under the Berserker spell
  • fix an assertion when a hero starts moving and a player immediately mouse left clicks on the Adventure Map
  • improve path calculations by AIWorldPathfinder in planning mode
  • Wand of Negation should grant immunity against the Mass Dispel spell
https://github.com/ihhub/fheroes2/releases/tag/1.0.8

Changes in v1.0.8 (11 September 2023):

  • fix number placeholders not being replaced in campaign spell bonus texts
  • fix spell rendering issues when words are divided in the middle
  • fix ? symbols appearing in single-line texts
  • generate vertical high scores buttons
  • display missing resource dialog for at least 5 seconds
  • show only skills that the hero does not already have in battle only skill selection list
  • show highscores after multiplayer game
  • improve the logic of kiting enemies with archers
  • update Vietnamese translation
  • update Hungarian Translation
  • adjust the description of the Eagle Eye skill
  • fix an empty gap in between lines in buttons font
  • update Russian translation
  • translate disabled buttons and reset them when changing languages
  • make vertical buttons translatable
  • implement terrain transitions in Editor
  • rework the Save/Load game dialog to allow variable dialog size and support Evil interface
  • change Army Order to Turn Order
  • Add Czech to original resource exception
  • do not close File options menu on cancelling load game dialog
  • fix foreign pixels on snow battlefield objects
  • do not try to scout the area if the hero is in jail and gets an artifact assigned using the map editor
  • update Slovak translation
  • cancel Hut of Magi reveal area animation when a player clicks any button
  • show the "You have been eliminated from the game" message in the multiplayer game
  • add small skill icons for Xanadu visit dialog
  • fix invalid logic for some objects on water
  • update Polish translation
  • speed up map, translation and save files loading
  • fix missing hero and town name in dialogs for non-English languages
  • fix Load/Save file title position and color
  • change Shipyard construction message
  • expand clickable area for settings in the Main Menu
  • shift text in Surrender window
  • show ... in Load/Save File dialog for long names
  • fix monster name position in monster info dialog
  • speed up rendering for specific buttons
  • fix multiple rendering issues in the Kingdom Overview dialog
  • adjust text for Quick Info dialogs
  • improve fading animation using Redmean color distance calculation
  • limit Dimension Door spell usage for AI heroes on lower difficulties
  • center text in hero battle info window
  • add A and O with diaereses to CP1252 button font
  • fix size of the button letter Æ
  • improve Mage guild status message for learnt spells
  • add "New Month!" title for new month
  • add "Victory!" title to victory dialog
  • update Danish translation
  • update Ukrainian translation
  • allow early exit from puzzle map during reveal animation
  • remember map filter selection until you quit the game
  • improve the distance estimation when an AI-controlled hero passes through objects on the map
  • update Swedish translation
  • AI heroes should decline battles if passing by
  • implement basic terrain draw in Map Editor
https://github.com/ihhub/fheroes2/releases/tag/1.0.7

Changes in v1.0.7 (15 August 2023):

  • properly update uncovered fog with all hero actions
  • fix an issue when AI hero "jumps back" during the fog discovery
  • AI heroes should not know what exactly a Treasure or Sea Chest contains
  • do not play simultaneous walk/fly sounds for battle speed 9 and 10
  • do not handle an event on the map twice
  • fix AI estimation of the castle garrison strength for priority tasks
  • rework "select from list" dialogs and also make all elements within dialogs right clickable
  • battle AI should switch to offense mode instead of passing turns
  • update Russian translation
  • add the ability to export and import save files in Android Toolset application
  • make many in-game buttons translatable
  • fix Army info button in level up dialog
  • update Ukrainian translation
  • update German translation
  • add Summon Boat spell support for AI
  • rephrase various game dialogs
  • add a dialog to confirm auto combat
  • add "Quit" title to quit dialog
  • fix Zounds grammar
  • update Norwegian Translation
  • the Shield spell should not affect the damage of the castle towers
  • apply AI growth bonus only on first AI player turn
  • show "cast down" animation for heroes on the battlefield
  • apply Cure & Mass Cure spells only if the target unit is either damaged or has negative magical effects
  • fix hero last frame delay after the spell casting animation
  • speed up several spell animations during battle
  • fix missing first animation frame of heroes on the battlefield
  • change heroes animation speed on the battlefield
  • fix puzzle screen exit button theme
  • reduce CPU usage while rendering castle's Well dialog
  • fix campaign buttons' rendering issues
  • make campaign buttons translatable
  • update Slovak translation
  • update Danish translation
  • update Swedish translation
  • disable Campaign button if no campaign resources are present
  • improve AI pathfinding logic regarding protected and guarded tiles
  • avoid engine execution with corrupted video files
  • add ability to select instrument brush and brush size in Editor UI
  • make thieves guild adapt background to chosen interface
  • fix a possible crash in Android toolset application
https://github.com/ihhub/fheroes2/releases/tag/1.0.6

Changes in v1.0.6 (15 July 2023):

  • allow to lower the campaign difficulty during playthrough
  • update Russian translation
  • reduce CPU usage while rendering High Score dialog
  • add some missing horizontal buttons to translations
  • fix missing redraw in Battle Only dialog after cancelling hero selection
  • fix last hotkey not showing up in the hotkey dialog
  • add Dismiss hotkey to hero screen
  • create basic Map Editor GUI
  • update Polish translation
  • add checks for object reachability when calculating their value for AI heroes
  • fix AI logic for visiting Shrines and Arena
  • correct spell value estimation for AI heroes
  • fix a case when a dead AI hero was trying to reinforce army in a castle
  • update priorities for AI heroes based on battle outcome and fog uncover events
  • rework the spell generation in castles' Mage Guilds
  • update Spanish translation
  • speed up Armageddon spell rendering
  • fix invalid pixels in multiple images from the original resources
  • speed up resource loading
  • update Slovak translation
  • do Main Menu fade-in before showing first game run
  • fix the fade effect on the "View World" screen
  • properly restore environment sounds and terrain music after opening a castle dialog from the Kingdom Overview screen
  • fix cases when false symbols appear after the blinking cursor in text input dialog
  • rework game credits to keep aspect ratio
  • add extra windows and greying out conditions for Summon Boat spell
  • fix several button text placements and button press rendering
https://github.com/ihhub/fheroes2/releases/tag/1.0.5

Changes in v1.0.5 (14 June 2023):

  • improve AI castle defensive strategy on Adventure Map
  • fix rating in campaign congratulations text in win video
  • update Danish translation
  • adjust battlefield Idle and Flag animation speed and avoid setting 0 animation delay for battlefield actions
  • show the currently selected bonus in the campaign scenario UI if the campaign scenario is already in progress
  • update Polish translation
  • add shadows for the Virtual Keyboard buttons
  • fix okay button not being redrawn in battle options dialog
  • improve army distribution in castles for AI players
  • make the text input cursor blink and add the ability to set the cursor position anywhere in the edited string
  • move jobless AI heroes into castles on a last day of a week
  • fix monster upgrade logic when AI was wasting resources for nothing
  • do not check whether a hero can cast a spell if spellcasting is prohibited in principle
  • fix AI heroes behavior ignoring empty castles with commanders
  • speed up AI decision making on Adventure Map
  • fix Magellan's Maps visit by AI heroes when discovered objects were not being added for exploration
  • fix missing logic for AI heroes to meet each other in the middle of a turn
  • reduce unit growth bonus for AI
  • do not play the same kill/wince sounds simultaneously
  • allow AI heroes to retreat when the kingdom has full house of heroes and when there are enough resources on the next turn
  • fix a crash when AI heroes capture a dwelling protected by monsters
  • add Editor Main Menu (only for development builds)
  • fix the Skeleton passability
  • fix the logic for defeated heroes in campaign scenarios
  • fix the logic of recognizing objects guarded by neighboring monsters
  • update hero's path after inspecting a castle
  • add Evil interface version of Monster recruitment dialog
  • update Bulgarian translation
  • add screen fade effect for transiting between game sections
  • add a check to avoid a crash for corrupted game resources
  • fix the rendering of the number of units during the castle siege
  • update Russian translation
  • speed up battlefield rendering
  • fix incorrect spell efficiency display for resurrection spells
  • set music type to MIDI if no MUSIC directory exist
  • fix cases with dropping audio volume when MUSIC directory is not present
  • fix crashes for Android devices due to using a buggy third-party package
  • fix cases when AI ignores some objects during path evaluation
  • use 22050 sound sample rate for PS Vita to fix high CPU load
  • add diacritic signs to CP-1250 button font
  • update Slovak translation
  • update Ukrainian translation
  • fix Android version for Android 7 or lower devices
  • do not reset castle or heroes scrollbars when hero moves
https://github.com/ihhub/fheroes2/releases/tag/1.0.4

Changes in v1.0.4 (13 May 2023):

  • add Polish and Russian languages support for Virtual Keyboard
  • fix absence of delay after animating creature wince and before making next move
  • make haunt, set elemental guardian, identify hero and visions spells gray out when cannot be used
  • do not stop hero's movement after the first Dimension Door jump
  • do not show that enemy town or castle has no garrison if kingdom has no Thieves' Guilds
  • fix idle animation lag while casting any spell
  • fix creature idle animation freeze during Blind / Paralyze spell casting
  • fix an issue that caused an AI-controlled hero standing next to Stone Liths to block the path of another AI-controlled hero standing on these Stone Liths
  • add checks to detect corrupted data resources
  • improve the resource value estimation algorithm for AI
  • fix logic and reward of Daemon Cave object for AI
  • add an ability to cancel waiting for the end of the pre-battle sound
  • update Polish translation
  • create small and large obstacles on the battlefield at the same time
  • do not make an empty text line at the end of phrase and fix text rendering in a wider area than given maximum width
  • fix Battlefield animations freeze while waiting for Good Luck sound to end
  • add popup windows for monster dialog buttons
  • fill main menu with background when we do not stretch the main menu main image
  • update Russian translation
  • fix not returning to load menu while switching between campaign scenarios
  • fix Shipwreck Survivor bonuses
  • fix AI pathfinding when Spell Scroll was considered as an artifact which requires to defeat an army
  • rework logic for all Adventure Map objects' metadata
  • fix adventure map fog reveal on campaign scenario restart and when loading a Hot Seat save made in the first game day
  • adjust spell info message when hero has zero (0) mana
  • fix calculation of castle towers strength for AI
  • improve dialog titles related to losing the game
  • update Bulgarian translation
  • strategic AI economy update
  • AI should check for castles and funds before retreat
  • fix software cursor freezes during certain fading animations
  • rework the search algorithm for available scaled resolutions
  • show map ratings in victory video, add subtitles and color fading animation
  • add extra checks to prevent loading renamed MX2 maps on demo version
  • add "End Turn" title to dialog when heroes can move and you are ending your turn
  • catapult and castle towers should still act, even if there are no troops in the respective armies capable of taking any action
  • update Vietnamese translation
  • fix the behavior of Berserk and Hypnotize spells
  • add language support for highscores
  • expand wide screen support
  • properly handle absolute and relative paths in the CDDrive registry key for Windows scripts
  • update Danish translation
  • update Swedish translation
  • reset battlefield grid cursor position after using the spell book
  • fix a crash on deleting last save file
  • update Czech translation
  • update Slovak translation
  • update Ukrainian translation
  • fix recurring global events
  • redraw the status window after the amount of kingdom resources has changed at the beginning of the day
  • do not render hero/boat over the bottom map border
  • add "New week!" title to dialog showing start of a new week
https://github.com/ihhub/fheroes2/releases/tag/1.0.3

Changes in v1.0.3 (11 April 2023):

  • update German translation
  • update Norwegian translation
  • preparations for Google Play Market release
  • add missing frame for hero information dialog when it is opened from hero meeting dialog
  • improve the destination indication for Teleport spell
  • translate more UI buttons within the game
  • fix endless jumping AI hero behavior while using Dimension Door spell
  • add Virtual Keyboard open button to Save Game and Player Name dialogs
  • add values to Morale and Luck stats within artifact description
  • show approximate information about the mine guardians to a non-owner player
  • adjust number of resources subtracted in event messages
  • fix projectile render position when hitting the target creature
  • reduce AI obsession with Wells
  • optimize object priority logic for AI heroes to prioritize more important locations
  • remove path icon after a hero/boat makes a turn, not before
  • show mirrored monster image in army order
  • make hotkey names translatable
  • improve AI fog discovery logic
  • fix a case when a hero has the same secondary skill but different levels of it set by an author of a map
  • add gold missing in Statue and Dungeon building description
  • add Vietnamese translation
  • fix the number of regenerated spell points with the Mysticism skill
  • focus on the AI hero before he disappears due to boarding a boat or using a Dimension Door or Town Portal spell
  • add legacy game detection on Windows x64
  • fix clicks in Android MIDI music
  • speed up Adventure Map rendering
  • fix AI hero animation being interrupted while moving inside a fog for the current player
  • fix Alchemist Lab flag and Ghosts from Abandoned Mine rendering while rest of the object is not visible on the screen
  • fix boat's shadow being rendered over a boat while sailing diagonally
  • adjust recruitment window position on castle well and kingdom overview
  • apply the commanding hero/captain bonuses to the castle towers
  • fix the number of raised skeletons by Necromancy after battle
  • update Polish translation
  • fix incorrect number of Ghosts present in Abandoned and Haunted Mines
  • add Slovak translation
  • properly handle the situation when wandering monsters and an AI hero eliminate each other
  • update battle log with special abilities of Vampire Lords and Ghosts
  • update Belarusian translation
  • add numeric speed of monsters in information window
  • fix quick info for non-action objects when their action tile is under fog
  • block Chain Lightning if first target unit resists the spell
  • fix cases when Road sprite is drawn over a hero
  • fix Hero shadow rendering on Adventure Map
  • update Russian translation
  • show Spell Points for spells in Magic Book popup dialogs
  • fix necromancy and calculation of casualties with mirrors
  • properly track the Mirror Image spell duration
  • update documentation about Android
  • update Czech translation
  • update Swedish translation
https://github.com/ihhub/fheroes2/releases/tag/1.0.2

Changes in v1.0.2 (12 March 2023):

  • fix display of Haunted Mines on mini-map and in View World
  • update the Norwegian translation
  • allow resurrected units to act on the current turn if they have not yet had their turn
  • add missing part of Captain's Quarter in Sorceress Town
  • allow to resurrect Elementals
  • fix the battle casualty report when non-true Resurrect spell was used during battle
  • redraw hero icons to display movement bar change before action dialog box is shown
  • fight the guardians on dwellings on Adventure Map only once per game
  • change the logic of reinforcement of the guest hero's army during the castle defence
  • do not show Dragon City on mini-map
  • summon boat spell will only summon friendly boats
  • allow the player to refuse to level up in the Tree of Knowledge even if this level up is for free
  • add virtual keyboard support for touch devices
  • grey out Summon Boat, Town Gate and Town Portal spells if they cannot be used
  • clean up AI priority tasks if attacker was killed
  • update sounds used while visiting dwellings on Adventure Map
  • add logic of AI heroes using town portal
  • add missing symbols to button font
  • update Polish translation
  • fix high CPU usage during battle while using highest animation speeds
  • fix battle AI tactics estimation
  • fix pathfinding not being updated after revealing the fog on visiting Magellan's Map
  • animate lighthouse in lighthouse dialog
  • fix the experience calculation for AI-controlled heroes while visiting Tree of Knowledge
  • add a frame for a hero portrait on the quick info dialog
  • add missing experience being displayed in the Tree of Knowledge window
  • fix music effects while visiting an empty object on Adventure Map
  • display the world date for save files
  • rework AI hero task priority logic
  • fix invalid popup window being displayed while clicking on an empty hero list area within Adventure Map
  • add Graphics option icon
  • fix corner cases of fog rendering
  • fix invalid calculation of AI path leading to a crash
  • show sleeping hero icon for hero's portrait within castle dialog
  • speed up rendering on Adventure Map
  • properly restore the screen after closing the hero dialog during combat
  • render background between HEROES and TOWNS/CASTLES buttons in Kingdom Overview window
  • fix possible random crashes during the gameplay
  • fix Iron and Steel Golems animation issues
  • show kingdom income when gold per day income is clicked within Kingdom Overview window
  • fix a crash while visiting a castle after an ally captured it
  • speed up file searching on Windows
  • fix the spell id calculation for a spell scroll while loading a map
  • fix puzzle map window position
  • fix View World default adventure map position
  • memorize zoom level of the View World screen
  • fix inability to move monsters after Good Morale event
  • fix unintended ability to upgrade troops during battle
  • fix huge CPU usage in hero window during battle
  • redevelop standard window generation logic
  • update Bulgarian translation
  • fix volcano sounds
  • update Russian translation (main menu and dialog buttons)
  • fix issue with the puzzle rendering
  • fix musical effects for various objects
  • fix stereo audio playing in SMK files
  • add letter "ÐŽ" and "’" sign to Cyrillic (cp1251) button font
  • add lighthouse information popup in kingdom overview
  • fix an invalid option for scenario 1 of Roland Campaign
  • optimize mini-map rendering
  • update Czech translation
  • add proper hotkeys option icon
  • fix title in German translation
  • optimize View World rendering
  • add tooltip damage info for single target attack spells during battle
https://github.com/ihhub/fheroes2/releases/tag/1.0.1

Changes in v1.0.1 (08 February 2023):

  • add the ambient sound for Abandoned Mine
  • add support for scaled resolutions
  • instantly update status bar in battle for hero's dialog options
  • fix invalid Kingdom income calculation
  • fix incorrect status bar update call
  • update Czech translation
  • add German and Russian translations of Android Toolset
  • Android Save File Manager: implement filtering of save files by type
  • fix scroll bar bottom position when dragging and fix high FPS rate when dragging scroll bar by mouse
  • make a narrower button font for cp-1251 (Cyrillic) characters
  • fix Druid projectile start position for shooting up
  • add PS Vita fixes and gamepad mapping improvements
  • add Danish translation
  • fix shooters projectile position during battle
  • fix high CPU load on Adventure map when Hero speed is set to Jump
  • Android toolset: add the ability to browse & delete save files
  • fix loss of the first frame in the after attack unit animation
  • update spell points for campaign heroes at the start of a scenario
  • uncover the fog during the hero's instantaneous movement only if the hero is actually moving
  • do not allow to dig for Ultimate Artifact second time
  • show spell effect animation so as not to cover the front troops
  • fix screen restoration after closing monster info dialog
  • fix the AI resurrect spell logic
  • fix the Dimension Door shadow on resolutions 640+x480 & 640x480+, temporarily hide auxiliary panels in "no interface" mode
  • rework pathfinding algorithm during battle to fix non-optimal paths for monsters
  • add Mossy Rock object which appears only on Swamp terrain while in the original game this object has no name
  • optimize Adventure Map rendering
  • do not do redundant redraws when dragging Adventure Map with the mouse
  • fix walking animations of certain monsters
  • fix the bridge operation when a wide friendly unit steps on it from the moat
  • fix creature wince animation for low morale and Lich cloud cases during battle
  • optimize Holy Shout spell effect rendering during battle
  • allow wide units to take a step back during battle
  • fix certain Peasant Huts being displayed as Thatched Huts with no ambient sounds on Adventure Map
  • update Polish translation
  • avoid excessing CPU usage for multiple windows in the game
  • set audio frequency to 44100 on Windows to avoid cracking sounds during playback
  • add fh2 toolset application on Android to help with setting up the main application
  • update French translation
  • reduce CPU usage in Select Language dialog
  • fix a case when external music was not being played on Ubuntu
  • fix incorrect logic for monster placement on Adventure Map
  • update German translation
  • do not lowercase creature name (or other string) when it is put first in phrase
  • fix forced Idle animation after switching to Static animation
  • fix screen flickering in SDL2
  • optimize Death Wave spell effect rendering during battle
  • automatically assemble artifacts sets if their parts were captured in the battle
  • fix controller support for PS Vita
  • add a full stop, A and E characters with accents for CP1252 button font
https://github.com/ihhub/fheroes2/releases/tag/1.0.0

Changes in v1.0.0 (21 December 2022):

  • add Ubuntu ARM support
  • update Polish translation
  • update the French translation
  • fix incorrect resource placements on certain maps
  • adjust "Battlefield Casualties" vertical position
  • fix bridge logic after morale event
  • enable the instant battle by default
  • optimize rendering in multiple places
  • update the Norwegian translation
  • add global hotkey to toggle the text support mode
  • make the regular small buttons translatable
  • make Trade button in Marketplace window disabled by default
  • add popup windows for monster recruit dialog
  • fix animation of slowed flying creatures
  • fix multiple hero related awards for campaigns
  • fix Death Wave, Holy Shout and Holy Word spell animations
  • do not mirror corpses of defeated troops during battle
  • fix rendering an extra empty line for specific texts
  • fix monster idling animation during battle
  • use logarithmic scale of sound volume
  • enable touch support for all devices
  • make separate hotkeys to cast battle & adventure spells
  • add the "Quit game" hotkey
  • handle hotkeys in the Adventure Map's File dialog
  • optimize defensive strategy for AI castles
  • fix black area not being rendering during video playback
  • reduce priority for bottles in sea for AI
  • make File Dialog and Adventure Dialog buttons translatable
  • do not stop object animation during AI turn
  • update Russian translation
  • fix text rendering for game option's names of certain translations
  • fix incorrect battlefield image restoration in the place of the previous damage popup
  • adjust several button font characters
  • rework the hotkeys concerning the hero movement
  • make the Main Menu's dialog buttons translatable for specific languages
  • display the amount of gold in the diplomacy dialog
  • show object animation during Hut of Magi visit
  • fix Dwarves death animation
  • fix very slow hero movement animation when moving a cursor with software cursor rendering
  • fix translated Cancel button in monster recruit dialog being switched to English
  • fix animation of bridge destruction during battle
  • fix the smoke animation when demolishing a castle building during battle
  • improve Adventure Map rendering performance
  • fix multiline word splitting for short phrases with a long first word
  • fix position of left barbarian's captain quarters part
  • reset the cursor position at the start of 'human turn'
  • add an option to toggle screen scaling type
  • recreate the original rainbow animation during battle
  • fix Scenario Info window elements
  • improve hexagon shading during battle
  • add Danish language option and font
  • improve rendering speed with software cursor rendering
  • fix French and Italian characters not being shown with their assets
  • set the minimum supported version to Android 5.1
  • introduce AI Scout role
  • fix fullscreen mode with DPI higher than 100% on Windows
  • fix multi-monitor fullscreen support on Windows
  • update Bulgarian Translation
  • add Android installation and usage documentation
  • fix the scenario info dialog position
  • generate resizable buttons for both interfaces
  • fix inability to toggle V-Sync option dynamically
  • do not show resolution dialog on handheld devices on the first startup
  • add mouse right click simulation on touchscreen devices
  • fix cursor being too small for certain resolutions
  • disable Adventure Map scrolling for handheld devices by default
  • change battlefield hexagon generation to match the original
  • make fullscreen mode as a default state for Android devices
  • implement all troops transfer between armies within castle dialog
  • scroll Adventure Map with mouse left button pressed
  • use parabolic catapult projectile flight trajectory
  • update credits page
  • always show the Ultimate Artifact behind one of the last four central pieces of the puzzle
  • update captain's mana on map load and when building the Captain's Quarters
  • remove and integrate the rest of experimental options
  • always center text when a maximum width is provided
  • fix UI elements for the unhired hero popup window
  • update Ballista and Turrets damage information when they are destroyed
  • correctly reflect the change of fullscreen mode in the graphics settings dialog when it was toggled using a hotkey
https://github.com/ihhub/fheroes2/releases/tag/0.9.21

Changes in v0.9.21 (12 November 2022):

  • fix audio playback issues on Windows OSes
  • do not show any hero status for AI heroes
  • add hotkeys for moving armies between heroes
  • update the Norwegian translation
  • fix monster upgrade cost formula
  • reset AI hero meeting flag every time if hero's strength has changed
  • add missing alliance conditions for the Succession Wars campaign last scenarios
  • add small optimizations for the pathfinding algorithm
  • fix incorrect evaluation of army strength where hero's abilities should not be taken into account
  • fix handicap income calculation
  • add right click quick info in more dialogs for hero and castle captain
  • fix possible assertion hit for campaign scenario info dialog
  • Luck / Morale related objects are now reset after meeting / battle for AI heroes
  • add a separate graphics options window
  • make more contrast for player objects on the minimap
  • do not override hero or castle selection focus on a game load
  • change language selection window to support many languages
  • improve the combat log's use of plural forms
  • draw the correct arrows for the paths on the adventure map
  • rework the troops arrangement mechanics for the castle defense
  • fix catapult's boulder animation
  • update position for text in difficulty selection dialog
  • add CP1252 French quotation marks
  • clear the kingdom's visited objects according to their lifetime, even if this kingdom has already been vanquished
  • update AI logic for monster distribution and recruitment
  • do not increase the number of upgraded creatures on Week Of/Month Of basic creatures
  • generate new MAX/MIN buttons for other languages
  • add Czech language option in game and add fonts
  • fix the troop splitting logic
  • include project's resource files into Android packages
  • translate language names in native language
  • add proper CP1250 font
  • update French translation
  • do not waste time to get useless monsters for an AI army
  • add audio support for Android devices
  • take Magic Book into account while visiting a castle by an AI hero
  • avoid using armies with 1 monster for AI heroes
  • lock the screen orientation to landscape for Android devices
  • fix campaign score sorting issue
  • fix an issue when kingdom overview scrollbar does not not remember last position
  • fix terrain colors on the minimap
  • memorize selected Scenario in Scenario Selection dialog
  • do not overwrite default platform's soundfonts if none is included into the package
  • update Hungarian translation
  • add Hungarian language option and font replacements
  • fix the shade used for mountains and trees
  • fix UI elements position in Select Scenario dialog
  • fix hero's fight cursor offset
  • fix heroes position on battle screen
  • add Android compilation
  • update Russian translation
  • redraw the castle's resource panel when a hero purchases the spellbook
  • add Dutch font and language option
  • update language dependent resources while changing language
  • update Bulgarian Translation
  • fix the Week Of / Month Of logic
  • update Belarusian translation
  • fix button font generation for original buttons
https://github.com/ihhub/fheroes2/releases/tag/0.9.20

Changes in v0.9.20 (10 October 2022):

  • add Russian and French button translations
  • update the Norwegian translation
  • recalculate the hero's path when the hero was selected with the mouse
  • fix stopped music in events of quickly closing dialogs with internal music
  • add proper logic for artifact exchange between AI heroes
  • add Dutch translation
  • do not let the archers perform a double attack if they are out of ammo
  • add Hotkey support for restarting Campaign's Scenario
  • AI heroes now use spells from scrolls
  • fix neutral town unit growth
  • display detailed information about the composition of alliances in the scenario info window
  • add date to logging for the future bug fixing
  • add turkish translation
  • make sure that there is a place for the Ultimate Artifact in the artifact bag before trying to dig it out
  • do not ask player's name if the score is too low for High score board
  • add Romanian translation
  • hide console window for Windows OSes and add log file for this platform
  • add Romanian font and language option
  • update Polish translation
  • add Turkish language option and CP1254 font
  • add proper icons for Petrification spell
  • speed up Adventure Map rendering
  • take into consideration a case with full artifact bag while checking AI hero's path
  • generate button fonts for different code pages
  • apply usual rules for unit growth in neutral towns/castles with custom troops
  • decrease monster growth bonus for AI
  • fix incorrect usage of Dimension Door by AI heroes
  • make logging for monster movement in battle more user-friendly
  • add UI improvements of the Settings dialog
  • fix logic of processing cells containing units available for possible AI unit attack
  • fix incorrect object evaluation for AI heroes on boats
  • add a feature to give human player control to AI (debug build only)
  • make creature names lowercased for certain dialogs
  • generate new Gift button
  • generate full button English alphabet and new Battle Only button
  • fix switching of terrain music in case of hero loss
  • update Bulgarian translation
  • add Handicap feature for single and multiplayer games
  • fix a case when a hidden scrollbar during battle is visible for one frame
  • fix sound volume for objects while using external music type
  • include rating calculation for Campaign's high score
  • fix incorrectly rendered monochrome cursor for campaign selection window
  • update French translation
https://github.com/ihhub/fheroes2/releases/tag/0.9.19

Changes in v0.9.19 (11 September 2022):

  • add popup dialogs for Well's buttons
  • generate MIN button based on newly added font
  • fix constantly moving AI heroes when they have nothing to do
  • update Polish translation
  • implement some improvements when exchanging an army between heroes
  • show intro videos like in the original game
  • fix messages displayed for captains during battle
  • update Bulgarian translation
  • correct start resources for AI players
  • add a popup dialog for Days spent info within campaign dialog
  • make Artifact evaluation for AI more granular
  • add Difficulty selection for Campaigns
  • add popup dialogs for mage guild and scenario info windows
  • fix rendering of Mine Guardians
  • do not show monsters and campfires on Puzzle screen
  • fix moving AI hero rendering
  • implement instant finishing of battles
  • update Norwegian translation
  • fix incorrect rendering of some icons in The Price of Loyalty campaign windows
  • make "Show terrain penalty" option the default
  • fix double income from captured objects
  • fix the distribution of Mutant Zombies in the Graveyard
  • fix the battle order after bad morale events
  • always use the current color of a unit when toggling auto battle (take the Hypnotize spell into account)
  • speed up file search on case-insensitive file systems
  • optimize Mini-map rendering
  • do not show boats on puzzle image
  • fix a possible crash in UI window
  • fix castle rendering on Mini-map
  • fix Ultimate Artifact placement conditions
  • fix rendering of towns for View Towns spell
  • fix tall object rendering
https://github.com/ihhub/fheroes2/releases/tag/0.9.18

Changes in v0.9.18 (10 August 2022):

  • speed up rendering of Mini Map area
  • fix missing shadows from path arrows when they are on the edge of screen
  • render correct digging holes for terrains
  • add monster fading animation when both wandering monsters and hero are dying after a battle
  • fix incorrect boat fading animation
  • do not show digging holes on Puzzle Map
  • fix out of view rendering of boats and heroes
  • fix Ultimate Artifact digging conditions
  • fix the logic of joining monsters for free and for money
  • do not draw objects over the map edge if they are in fog
  • fix cases of boats being shifted when a hero boards it
  • fix wrong rendering priority of Hero and Jail
  • do not render shadow of a tall monster over another monster nearby
  • properly render trees and Sawmill
  • fix Fountain being displayed over a moving hero, not under
  • fix certain shadows of objects being too dark
  • fix cases when hero's flag was not being displayed properly when a hero stands near a tall object
  • fix cases when some parts of heroes being displayed over other objects
  • fix monsters being displayed over mountains
  • draw a flag over Magic Garden object
  • draw Adventure Map decors in the same way as in the original game
  • fix flag rendering priority from Windmill
  • fix incorrect moving boat rendering in relation to other boats
  • fix boat being cut near Magellan Maps object
  • fix incorrect boat rendering priority
  • fix wasteland terrain object displayed over hero's sprite
  • fix Mine's flag being displayed over a hero
  • fix cases when a moving hero being drawn over other heroes
  • fix cases when some parts of a ship can be seen through terrain objects
  • fix cases when hero overlaps ship while passing nearby
  • hero does not step on route arrows anymore
  • fix the pre-battle arrangement of neutral troops
  • add separate sound font for SDL 2 builds
  • fix paused music being played with old music volume while restarting it
  • fix hero starting experience values
  • fix amount of experience from Tree of Knowledge
  • fix missing retaliation delay after last animation frame
  • fix music being too quiet while fast switching between music types (Windows OS)
  • fix crashes on Windows 8 using MIDI music
  • fix external audio not always returning to on after turning volume to zero (Windows OS)
  • fix horse's steps being faded while crossing terrain border using MIDI sound (Windows OS)
  • fix sound volume being lower during combat (Windows OS)
  • fix no sounds situation when music is off even when sounds are not (Windows OS)
  • show correct hero's level and experience on level up
  • fix incorrect castle's area leading to wrong castle's info
  • add missing window frame for hero's dialog during level up
  • fix number of ghosts and splitting for Shipwreck, Haunt and Abandoned Mine cases
  • update Bulgarian Translation
  • update treasure chest AI decision logic
  • fix a case when a hero got 2 Magic Books
  • update Russian and Hungarian translations
  • add support for loading of MIDI sound fonts
  • show upgraded monster images in Adventure Map objects upon a successful upgrade
  • fix redrawing of the status window in cases when the hero's army or the castle garrison change without changing focus
  • make best AI fighter as a champion
  • update Polish translation
https://github.com/ihhub/fheroes2/releases/tag/0.9.17

Changes in v0.9.17 (11 July 2022):

  • fix possible crashes during sound playback
  • always ask about rewriting an existing save file
  • allow to remap Hotkeys within the game
  • fix distance evaluation during Scouting
  • fix invalid logic when neutral monsters whose number was set in the Editor did not grow at all
  • add The Price of Loyalty team to credits
  • show a window stating about missing video files for campaigns
  • fix the class of a hero named Joseph
  • fix extra shadow being drawn for Hero Screen dialog on high resolutions
  • align File Options dialog position
  • fix Campaign Window button flickering
  • fix missing column of pixels in Map Scenario Info window
  • update French translation
  • allow different translations of "Red Tower"
  • implement AI courier hero role for better logistics
  • fix situation when MIDI music still continues to play during fast switching between music types
  • fix case when Adventure Map sounds are still being played while opening Main Menu
  • AI should keep some creatures in reserve for castle defence
  • update Ukrainian translation
  • update Norwegian translation
  • increase Genie special ability to 20% like it is done in the original game and correct log messages during battle
  • remember the last open page in Spell Book during battle
  • fix possible synchronization issues for audio playback
  • enhance AI logic towards defending its castles
  • improve usage of spell on Adventure Map by AI
  • fix displaying of negative resources in dialog
  • fix situations when AI was hiring too many heroes
  • respect the Ultimate Artifact's radius specified by the map creator
  • update Polish translation
  • fix the castle's well build logic for AI
  • align hero's button in level-up dialog
  • fix scenarios when ambient sounds from Adventure Map objects are still played while opening a castle's window
  • fix cases when Random Monsters appear from a different level due to invalid format of an object on the map
  • update Mine's sprite properly after defeating Ghosts in Abandoned Mine
  • fix multiple issues related to hero's info popup window and updating his position on a mini-map
  • update sounds and music before a hero making an action
  • add an environmental sound for Freeman's Foundry
  • add initial implementation of true "Evil" interface in the game
  • add missing Ultimate Artifact successful digging sound
  • do not play pre-battle sound if sounds are off
  • fix potential thread-access issues for Nintendo Switch
  • fix possible inter-thread race for Adventure Map Status window
  • fix a delay in game while crossing a terrain and playing MIDI music
https://github.com/ihhub/fheroes2/releases/tag/0.9.16

Changes in v0.9.16 (12 June 2022):

  • add Flatpak support
  • add Belarusian translation
  • swap spell icons for Death Wave and Death Ripple
  • make the "no interface" mode more usable
  • add music resume ability for MP3, OGG and FLAC formats
  • update translation guide
  • replace "C with cedilla" with "latin C" in the French translation
  • do not auto-save at the end of a turn if the player has been vanquished on that turn
  • fix Puzzle window exit button rendering
  • add sound for rocks in water with seagulls
  • fix Sorceress captain purchase building rendering
  • fix issues with Romanian translation
  • properly deal with human-only colors in single player mode
  • fix the invalid behavior of "no interface" mode
  • introduce 3D audio option
  • change adventure map environment sounds for every step hero moves
  • fix boats being considered as free tiles
  • if attacker is able to attack multiple targets at once and has a built-in spell, then this spell should be applied to any (but only one) of the targets
  • fix some typos in translation texts
  • take into account AI hero's role during meeting
  • fix false highscore entry while cancelling to continue a campaign
  • highlight latest highscore after completion scenario/campaign
  • show evil resolution icon for Evil interface
  • add truncated shadow to Scenario Information window
  • generate missing parts of Red Tower sprite
  • fix incorrect tiles being marked as road
  • fix multiple cases while using Mirror Image and Summon Elemental spells
  • fix the victory/defeat verification logic
  • update error message if hero tries to open a spell book and does not have one
  • improve status text if only one troop is selected
  • fix inability to add new entries with similar high scores
  • add Campaign high scores
  • fix dwelling popup window on adventure map
  • make AI to be aware about map conditions
  • fix incorrect hero icon in adventure interface
  • do not show languages with empty translations
  • add Swedish language option and font support
  • update Ukrainian translation
  • well MAX buy: add condition for not enough room, make recruitment happen in guest hero's army too, more text flexibility
  • add Portuguese language option and initial font generation
  • speed up AI turn
  • impose a limit of turns for an attacking AI-controlled hero
  • cache AI hero's and neutral monsters' strength during AI turn
  • add initial Spanish language support
  • make castle's flags for Knight and Wizard factions appear in a correct color
  • add window header for campaign awards and bonuses
  • update German translation
  • fix plural forms translation for some languages
  • fix character filtering when generating the save file name
  • update Polish translation
  • fix Red Tower rendering with Rainbow
  • expand Hotkey usage on adventure map
  • fix German font support
https://github.com/ihhub/fheroes2/releases/tag/0.9.15

Changes in v0.9.15 (07 May 2022):

  • add HotKey window and expand the list of system options
  • do not allow higher-level Mage Guilds to be built if the previous-level Mage Guild has not been built yet
  • Battle Only mode: always assign a random spell to the Spell Scroll
  • add initial support for Romanian language
  • add more conditions to define a blocking AI hero
  • fix incorrect video playback on Descendants campaign, scenario 5
  • change some descriptions of campaign awards and bonuses
  • do not auto save after the completion of the last scenario in a campaign
  • add images and full description of bonuses and awards by right mouse click
  • fix some incorrect campaign bonuses
  • display None if no awards available for the current scenario
  • add hot keys to select bonuses
  • fix rendering of campaign UI window as it was very slow on response on changing bonuses
  • fix missing kingdom income value for scenarios with additional bonus resources
  • game type interface for campaign window must be unique and should not depend on overall game interface
  • allow already selected scenario to appear in ALL scenarios list
  • improved Week of the creature window
  • changed town portal's text and condition to be similar to town gate
  • update Russian translation
  • properly extract animations from the GOG distribution of HoMM2 for Windows
  • update French translation
  • improve search for video files
  • add text truncation method to be used in campaign's choices and awards
  • encourage AI to explore more
  • redesign Hot Key event file handling
  • add double-lined headers to adventure map options
  • add Belarusian, Bulgarian and Ukrainian languages support
  • populate resolution list based till the lowest resolution
  • fix 1000 and 100000 short values display
  • update hero quick info popup dialog
  • disable Town Portal dialog OK button by default
  • changed AI's turn to be repeated multiple times for heroes
  • generate French 'MIN' button
  • generate French 'GIFT' button
  • add Translation Context for Secondary Skill Campaign Bonuses
  • add Ogre Alliance and Dwarfbane full strings for translation
  • fix the positioning of the damage info popup
  • update Norwegian translation
  • fix calculation of morale & luck modifiers for in-castle army without a commander
  • properly calculate the number of travel days to the destination tile for a hero
  • AI should replenish lost spell points more frequently
  • do not allow a hero to have multiple spell books in the "Battle Only" mode
  • fix rendering of the Arm of the Martyr's shadow
  • AI uses View All spell to scout fog
  • hero should have at least 69MP to cast Dimension Door, Town Gate and Town Portal spells
  • fix spacing of player squares in Select Recipients window and properly center resource icons for "Your Funds" and "Planned Gift" horizontally
  • fix French small font Cedilla c rendering
  • update Polish translation
  • reserve half of AI spell points for Dimension Door spell
  • hero should be able to cast spells that consume movement points if he is able to move from his current tile
  • fix MP3 support in Windows SDL1 packages
  • reset the mixer volume before playing video soundtracks
  • expand Test Support mode to cover all campaign related actions
  • add Black-White cursor support
  • add music control options to the configuration dialog
  • fix failing music file search
  • properly work with guardians on the objects that can be captured
  • fix call to get secondary skill name leading to a crash
  • fix multiple issues related to the interface settings
  • allow to change the auto battle settings if player's army doesn't have a commander
  • fix a crash when a hero tries to get a second magic book
  • always grab the mouse in fullscreen mode to properly support devices with notch
https://github.com/ihhub/fheroes2/releases/tag/0.9.14

Changes in v0.9.14 (09 April 2022):

  • generate Italian "Battle Only" button
  • fix line breaking in welcome message for various languages
  • update and improve Italian translation
  • generate Polish "Battle Only" button
  • fix switching fullscreen mode in Mac OS for certain resolutions
  • generate French "Battle Only" button
  • fix a crash due to an assertion failure when the shooter hits back at a unit under Berserk spell
  • fix scrolling in full-screen mode in case of an aspect ratio mismatch
  • fix rare cases for incorrect letters being generated while switching languages
  • add Norwegian language option and font generation
  • update French translation
  • add initial implementation of Dimension Door spell usage for AI
  • fix Gold Watch artifact invalid bonus
  • rework artifact logic with proper separation and calculations of artifact's bonuses and curses
  • play only one sound while getting ar artifact within an event
  • speed up castle and hero icons on adventure map
  • add "Battle Only" button generation for German version of the game
  • fix Mage Guild bottom status bar rendering
  • fix incorrect artifact discovery description
  • add in-game Italian language option and diacritics
  • fix army order option related crash
  • fix High Score Music after winning scenario
  • adjust AI spell estimates
  • fix rendering of bottom bar in Well dialog
  • update Polish translation
  • remove unnecessary call to play VICTORY music during Campaign intro
  • fix the calculation of the cost of surrender
  • fix Town Gate spell logic
  • prioritize AI castles further from enemy territory
  • take the weekly growth into account when calculating monthly population growth bonuses and fines
  • update minimap colors
  • optimize AI fighter hero priorities
  • add native macOS application generation
  • build player influence map to inform AI kingdom development decisions
  • speed up data reading from AGG files
  • update Norwegian translation
  • add a popup window for battle log
  • add a mandatory requirement of h2d file being present
  • fix incorrect logic for palette application function
  • fix position of armies for quick info window for both castle and enemy hero
  • set the direction for attacking and defending units in battle in a more OG-like way
  • fix incorrect popup monster window in Well
  • greatly speedup heavy video loading
  • do not render extra frame while playing an audio only video file
  • properly center the first sentence in the battle surrender dialog
  • dynamically update Game Language dialog
  • fix translation and UI in Tavern dialog
  • add MP3 file support
  • increase the number of sound channels to 32
  • allow a hero to surrender to the captain during the siege
  • fix rare case of Shipwreck having no battle
  • display experience amount for Tree of Knowledge
  • fix Witch's Hut message body
  • for Anduran assembly window show artifact name as a title
  • for hero level up window with one secondary skill make the skill right clickable
  • for hero level up window with no secondary skill display the primary skill icon
  • add search for FLAC files if OGG files aren't found
  • add popup dialogs for Arena's skill choices
https://github.com/ihhub/fheroes2/releases/tag/0.9.13

Changes in v0.9.13 (07 March 2022):

  • show captain quick info dialog in Kingdom Overview
  • add popup windows for campaign awards and bonuses
  • remove Map loading screen
  • add popup windows for campaign scenario dialog buttons
  • remember map size selection in New Game dialog
  • allow selecting options while restarting a campaign scenario
  • display the correct size of player-owned objects on mini-map
  • add base support for text mode
  • fix Sirens logic, AI interaction and UI
  • show missing last frame of wince animation
  • add dynamic generation of scrollbar slider
  • fix Sirens action dialogs
  • add missing artifact discovery event description
  • fix some French translations
  • fix directory and file detection on Windows
  • Kingdom Income window was changed to have Kingdom Income title and Kingdom Income per day to be more obvious for a player
  • events on land and sea now show artifact and resources in one window
  • most of objects related dialogs contain right mouse clickable icons
  • Pyramid visit outcome shows the correct horizontal position of icons and all icons are right mouse clickable
  • fix Artifact frame drawings
  • fix text if translation for a specific language was not found
  • update AI hero recruitment logic
  • allow a short name for all campaign bonus artifacts and spells
  • AI should save current unit if retreating
  • add Czech language support
  • fix monster strength heuristic regression
  • make AI more aggressive in losing situation
  • fix the logic of protecting tiles by neutral monsters
  • change music and surrounding sounds while jumping through Stone Liths
  • fix possible AI pathfinding issues while loading another scenario
  • fix battle grid and ballista info area
  • add popup dialogs for right mouse click in resolution dialog
  • fix hero name not being updated while switching languages
  • fix possible errors for translation related code
  • force to use only the original palette from the game
  • fix lowercase conversion of translations
  • AI archers should not ignore blinded units during battle
  • add missing take-off and landing sounds for Vampire and Vampire Lord
  • reset environment sounds and music theme at the beginning and at the end of the human turn only
  • fix player's allies configuration for 'Defeat the other side' victory condition
  • fix sounds of Lean-To and Skeleton objects
  • rename "Lean To" -> "Lean-To"
  • allow AI fog discovery only for teleport endpoints with reachability information
  • fix missing sounds for some objects on map
  • do not play castle's music if a terrain does not have its own music
  • fix missing sound of player's turn in Hot Seat mode
  • adjust monthly monster spawn
  • fix neutral monster growth formula
  • fix necromancer build order in castles for AI
  • allow a boat to move near the coast above the center of a town
https://github.com/ihhub/fheroes2/releases/tag/0.9.12

Changes in v0.9.12 (07 February 2022):

  • add a confirmation window for restarting a campaign scenario
  • fix wrong scenario order in "Voyage Home" campaign
  • add Betrayal missing for Roland and Archibald campaigns
  • fix logic for switching on and off Auto battle option
  • fix normal and retaliation attack of monsters with multi-cell attack abilities
  • fix missing logging during certain spell casts in battle
  • implement a heuristic for hiring heroes that does not allow to offer the same heroes for hire in several kingdoms at the same time, if there are enough free heroes
  • use 'r' key instead of Escape to Retreat during battle
  • fix Hydra behavior under Berserk or Hypnotize spell effect
  • add highlighting of cells attacked by a unit capable of attacking all neighboring cells
  • fix a penetrating attack made by a wide unit from the tail cell
  • fix attack cursor display during battle
  • fix cases when no monsters were spawned at the beginning of a month
  • display ? symbol instead of empty space for unrecognized symbols
  • fix crashes while rendering non-English text
  • fix rare cases of a screen being black on a startup of the application
  • make language area and campaign choice selection to cover text too
  • fix campaign text translation
  • modify army order setting
  • add German font generation
  • fix multiple issues related the hero recruitment process
  • update Russian translation
  • show monster info on right click in castle's well
  • fix cases when the last AI hero was blocking an entrance to Stone Liths
  • fix small font positioning for Polish GoG version
  • fix most of existing cases with passabilities
  • fix the display of properties for maps from the list in scenario selection dialog
  • update German translation
  • fix translation of saved default player names
  • improve the AI fog discovery logic
  • AI properly uses Whirlpools for path calculation
  • fix cases when a hero was able to create a path over another hero
  • display dead monster information during battle
  • fix Mass Dispel usage during battle
  • update Polish translation
  • fix Russian small k per in both fonts
  • AI heroes properly calculate paths through water
  • add translation context for color and syntax flexibility to Traveller's Tent and Barrier
  • fix Knowledge spite rendering for Arena
  • fix sounds after battle cases
  • fix missing Russian letters on Switch
  • changed captain's name spelling
  • adds missing "pourcent" in description of Golden Bow
  • fix cases when a hero was able to walk on water
  • update French translation
  • show in-game only players for Gift marketplace option
  • fix glowing red pixel in Warlock's Red Tower
  • improve AI behavior during castle defence
  • fix CPU player icon's extra color column
  • show proper language name for mismatched by language save files
  • add a spell title when no enough SP window appears
  • fix a possible crash in French letters generation
  • fix usage of plural/singular translations for map's objects
  • update the logic of re-calculating the paths of heroes on the adventure map
  • add support of Buka version of Anim2 directory for video playback
  • always enable autosave
  • generate proper Russian E with 2 dots on top
  • allow translation of "Cost per troop:" text
  • update UI elements in marketplace window
  • fix a crash during AI turn when AI army has an invalid monster slot
https://github.com/ihhub/fheroes2/releases/tag/0.9.11

Changes in v0.9.11 (23 December 2021):

  • add popup dialog for map information
  • fix hero's path not being updated properly after new monster month event
  • show available game languages in vertical order rather than horizontal
  • fix handling of national symbols in the paths to music files on Windows
  • improve AI behavior during castle defence
  • fix pressed buttons' background
  • fix incorrect output on console window for Windows users with non-English system language
  • add support of Russian language by default
  • update Polish translation
  • generate correct letters for French language
  • update Settings description to cover all actions
  • fix heroes meeting swap arrows
  • update PS Vita controls
  • fix crash for some systems after changing game resolution
  • add hotkey for settings in Main Menu
  • do not reset the hero's path in Sleeper mode, reset the Sleeper mode only with the beginning of the hero's movement
  • fix River being considered as a Road
  • allow to select player class using mouse wheel
  • add additional spell info for spells owned by a hero
  • fix button rendering artifacts after changing resolution
  • fix crash on Windows for some Archibald scenarios
  • always mark the entrance to the castle/town as a road, do not automatically mark the tile south of the entrance as a road
https://github.com/ihhub/fheroes2/releases/tag/0.9.10

Changes in v0.9.10 (05 December 2021):

  • update background music on the fly while changing it within settings
  • fix horizontal position of town name and town icon in town construction dialog
  • improve marketplace gift button placement
  • fix vertical text position in skill icon
  • fix Summon Boat logic
  • add video playback in the Price of Loyalty campaign selection window
  • play sound for visiting Observation Tower
  • fix vertical position of building construction name
  • add missing button shadows in dialogs
  • add popup window to OK button in game settings
  • play campaign music in campaign scenario selection screen
  • fix Enter button pressed state being passed from gift dialog
  • show credits at the end of campaign
  • fix passability for Dimension Door spell
  • add V-Sync option for SDL2
  • update troops split logic change
  • properly draw battle grid
  • use the OK button instead of YES button for the Tavern window
  • add Champion image for Stables object
  • play sounds from closest surrounding objects first
  • make consistent campaign info dialog
  • add default castle's name if none is specified by editor
  • show heroes on minimap
  • update Norwegian translation
  • show available creature count for the Price of Loyalty monster objects
  • fix Witch's Hut name
  • add Italian translation
  • properly handle the day-week-month routine
  • add titles for most of dialogs from map's objects
  • always mark Artesian Spring as visited, even if the hero was not able to drink from it
  • fix movement penalty calculation algorithm
  • implement last move logic for hero movement on the map
  • update hero movement points UI upon visiting multiple objects on the map
  • fix rendering for difficulty text in scenario info dialog
  • fix AI obsession with whirlpools and being always in sea
  • load palette from AGG file
  • fix monster recruit window position for multiple places
  • fix logic for Slow and Haste spell AI usage during battle
  • add random sign messages
  • fix maximum music and sound volume
  • use correct message in marketplace for first exchange when not enough resources
  • speed up for multiple image readings and for unit rendering during battle
  • allow to change Battle Speed using mouse wheel
  • update UI elements in Well's dialog
  • if a hero on the map has the custom empty army (OG editor bug/feature), give him a minimum army (one unit of level 1)
  • add translations for Battle Only mode texts
  • set correct starting army in Archibald 7 scenario
  • fix cases when AI creature doesn't move during battle
  • fix sound effects when receiving artifacts from world map
  • add hotkey to Okay button in Campaign select window
  • fix results on instant battle when Resurrect spell was being used
  • fix cases when opposing heroes still exist on the map after defeat
  • improve AI usage of Disrupting Ray spell during battle
  • add runtime language generation
  • fix terrain music being played during battle
  • fix water object passabilities
  • allow to use mouse wheel to change system options
  • set correct building requirements for Upgraded Jousting Arena and Upgraded Cathedral
  • update messages during battle
  • shorten artifacts names to be fit for campaign UI
  • always highlight the cell under the cursor in combat if the Shadow Cursor is enabled, even if this cell is not reachable by the current unit
https://github.com/ihhub/fheroes2/releases/tag/0.9.9

Changes in v0.9.9 (05 November 2021):

  • do not leak the hotkey state from the experimental settings dialog to the scenario info dialog
  • fix Stonehenge gold cost
  • fix Stonehenge building requirements
  • add buttons in castle construction window
  • fix rendering of Captain's Quarters in Warlock castle
  • fix Marketplace messaging
  • fix MIDI Expansion music option being always reset upon restarting the game
  • fix incorrect sounds while playing MIDI music in certain platforms
  • add credits to the original HoMM2 team
  • correct original Waterfall images to remove Cave
  • use original alignment for Rating, Map Size in gameinfo window
  • fix interaction passability with water objects
  • change Shipwreck, Graveyard and Derelict Ship messages
  • add the Resources Settings page to the Windows installer
  • fix passability for Reefs
  • fix invalid Stonelithts after Barrier removal
  • fix passabilities for diagonal moves
  • clear fog for allies upon visiting Magellan's maps
  • fix Archibald scenario 7 description
  • fix bad pixels on multiple images
  • verify Victory/Loss conditions after a static hero action
  • modify Magellan's maps logic and UI
  • fix monster count in Dragon City for Archibald scenario 7
  • update Victory message for Archibald scenario 7
  • fix possible crash for Russian version of the game
  • fix incorrect map description length
  • fix spell points in Battle Only mode
  • apply Bane and Alliance condition only for Human heroes
  • add extra empty line in New Month window
  • disable bonus choice for campaign scenario info window during scenario play
  • add Norwegian Translation
  • fix the position of campaign days spent text
  • close certain dialogs containing only one Exit button with Enter key
  • add Barbarian Captain Quarter's missing part
  • add hotkeys for selection of SW/PoL campaign
  • generate the default High Score values
  • fix "show damage" feature for blessed/cursed troops
  • fix high scores word
  • fix AI lust for Magellan Maps object
  • hide the OKAY button in campaign info window
  • handle default exit hotkey in Campaign scenario select / info
  • add Sphinx handling by AI
  • remove incorrect status message for hero portrait in hero screen
  • fix map size button states on hotkey press
  • restore environment music & ambient sounds after exiting from the campaign info screen to the adventure map
  • do not allow to use lower than original resolutions
  • freed from Jail AI hero starts movement at the same turn
  • add AI interaction with Alchemist Tower
  • fix incorrect visiting status of Magic Well, Stables, Artesian Spring and Bad Luck objects for AI
  • fix invalid assessment of Monster upgrade objects for AI
  • fix cases when AI hero ignores some objects while defeating an army just on the way to them
  • fix losing hero artifacts upon flee or surrender
  • enable a pressed state for View Intro button post-launch in Campaign screen
  • fix bonus artifact names in campaign screen
  • return to the Main Menu after cancelling campaign scenario
  • fix AI heroes being stuck on teleports
  • fix button rendering in Restart battle dialog
  • update Portuguese translation
  • fix inability to dig on a tile where monster was located
  • fix inability to apply Hypnotize spell on Dwarves
  • fix shooting penalty detection
  • add extra popup windows for UI elements in castle dialog
  • fix passability with shadow sprites
  • AI to use Hydra ability in battle
  • reinforce a AI hero standing in a castle at the start of turn
  • update status bar in castle screen dialog even during construction animation
  • fix incorrect active area of certain buildings in castle screen dialog due to invalid Z levels
  • hide cursor while opening a dwelling info in castle screen dialog by right mouse click
  • add Italian language support
  • update Polish translation
  • properly show the attack cursor when the attack square is located at the top of the castle sprite
  • fix infinite loop for AI trying to use Stoneliths
https://github.com/ihhub/fheroes2/releases/tag/0.9.8

Changes in v0.9.8 (05 October 2021):

  • fix a case when a hero loses his artifacts after retreat in multiplayer mode
  • fix monster position on World Map
  • correct resource position in castle window
  • new week status is not changed while reloading the same save file
  • add headers for all Daemon Cave dialogs
  • fix rewards in the Daemon Cave
  • fix Waterwheel incorrect visited status
  • fix spell points reduction while visiting a well with more than maximum spell points
  • add confirmation message in Alchemist Tower
  • add cell highlighting for monsters with double cell attack ability
  • add correct evaluation of an AI hero recruitment and meeting
  • fix artifact selection area
  • add a title to all artifact related windows
  • grey out experimental disabled option
  • show map type icons in New Map lists
  • do not place wandering troops on coast tile if there are no other coast tiles nearby
  • develop a castle during AI hero purchase
  • fix Altars' passabilities
  • fix AI hero being stuck on Stoneliths
  • fix passabilities of action objects
  • add Windows scripts to automatically copy & extract all necessary assets from original HoMM2 distributions
  • fix unresponsive application during AI turn while attacking an object with guards
  • fix active building area in castle construction window
  • AI can now visit Pyramids
  • fix scrollbar rendering in select lists
  • replace scrollbar in Experimental Settings dialog
  • unselect initial town in the Town Portal spell list
  • fix troop position in the Pyramid
  • fix bridge animation during battle
  • fix deterministic battle outcome
  • fix an empty list of heroes in the Kingdom Overview after the dismissal or loss of a hero
  • fix passability for tall objects
  • fix Roland campaign awards after ending scenarios 7 and 8
  • set 15 character limit when inputting player name in High Scores
  • correct battle speed change for monsters
  • fix incorrect level of Lightning Rod artifact
  • correct castle building' active area
  • fix passabilities related to cracks
  • align UI elements on artifact combat outcome dialog
  • add Spell description window in monster info dialog
  • fix scrollbar position after dismissing a hero
  • add logic for ENTER and SPACE keys for a hero on World Map
  • add hotkey for View World dialog
  • fix some cases with Tree passabilities
  • update Polish translation
  • fix post battle crash
  • fix incorrect rendering for game settings window with evil interface
  • fix appearance of sounds of the sea when a hero stands close to map's edge
  • display a message about Ultimate Artifact after the battle
  • fix adventure border rendering on higher resolutions
  • fix incrementing days spent value after reloading a campaign save
  • lock mouse within the window in multi-monitor setup
  • fix passability of mines
  • fix an ability to attack a monster through a barrier
  • allow hero to traverse Stone Liths and Whirlpools even if he has no movement points left
  • fix black edges in View World
  • fix passability for tiles with shadows only
https://github.com/ihhub/fheroes2/releases/tag/0.9.7

Changes in v0.9.7 (05 September 2021):

  • add popup dialogs for buttons in High Scores and save files manipulation windows
  • fix Faerie Ring passability
  • boat boarding animation speed doesn't depend on hero speed
  • add base support of German translation
  • fix medium size hero portraits
  • fix incorrect object info when a hero stands on it
  • remove flags from most of the captured objects for a lost player
  • fix alignment of scenario info window
  • fix boat froth drawing conditions
  • fix Battle Garb of Anduran missing assembly for The Price of Loyalty campaign
  • add monsters into High Score dialog
  • add short save file description window by right clicking
  • add base roles for AI heroes
  • restart of the battle should lead to the same results
  • fix logic in moral indicator
  • AI can capture Haunted mines
  • fix scrollbar position in Kingdom Overview dialog
  • allow guardian heroes upgrade their troops
  • add object count for weekly object income message
  • open game settings window while clicking on the main menu door
  • fix object passability nearby Hut of Magi
  • modify Necromancer Captain Quarters look
  • fix Radar appearance while right clicking on hero/castle
  • do not show file options window upon file saving
  • save Kingdom Overview UI selection in save files
  • add missing hotkey for Kingdom Overview
  • fix missing mine being drawn in View Mines
  • fix incorrect fading of some objects on World Map
  • fix AI behaviour during siege
  • fix cases of incorrect monster stack splitting logic
  • handle Escape/Enter keys in various settings dialogs
  • use distinct image for the "Swap heroes" button
  • properly restore captain's spell points after quick battle
  • remember UI position in Kingdom Overview dialog
  • add strict ordering of save files ignoring capitalization of letters
  • add popup dialogs in Kingdom Overview UI dialog
  • fix missing game resolutions on some machines
  • fix incorrect rendering on specific fog tile
  • display same name maps in the list of available maps
  • improve pathfinding algorithm for AI
  • place Ultimate artifact not always in the center of puzzle image
  • fix max ballista shots during battle
  • update French translation
  • add missing Farm sprite in Knight castle
  • update every scrollbar logic in the game
  • fix a crash while shooting from castle's tower during castle siege
  • fix Holy Shout being applied on Dwarves
  • properly handle cross-moat attacks during castle siege
  • do not let the player get a random artifact that allows him to win the game
  • allow player to immediately edit save file name without clicking on the input field
  • add few performance optimization in music and audio
  • update Polish translation
  • not visited obelisk remains not visited after the Ultimate Artifact has been found
  • fix polish translation encoding
  • game settings dialog: reset current language to English if game resources do not support other languages
https://github.com/ihhub/fheroes2/releases/tag/0.9.6

Changes in v0.9.6 (06 August 2021):

  • update status window if troops have fallen overboard
  • add direct preview of changes in system options dialog
  • update Polish translation
  • display the position of a hero or a castle on radar while right clicking on them
  • add titles to some dialogs
  • add translation support for missing places
  • fix monster joining logic with and without diplomacy
  • add Nintendo Switch build
  • make captured object event messages to contain yellow titles
  • fix a crash while AI uses Summon Elemental spell during battle
  • fix incorrect Alliance conditions for certain maps
  • fix a case when a hero in castle's tavern always changes after reloading a save file
  • fix incorrect icons for scenario information dialog
  • update skill order for new heroes
  • add normal Game settings dialog
  • fix inability to attack during battle
  • do not play ambient sounds during AI turn
  • add monster names, abilities and hero skills to translations
  • fix AI archer avoiding hand battle logic
  • fix UI defects in Kingdom castle view dialog
  • properly update resource and artifact sprites on World map
  • fix infinite loop after chain lightning spell
  • do not draw flags for puzzle image
  • highlight cells from where a monster will attack
  • restore background music after playing short-term music effects
  • add language selection window
  • fix Town's fog area unveiling
  • implement regular win/loss conditions for multiplayer mode
  • update View World while resizing the application
  • fix English phrasing and punctuation
  • fix simultaneous activation of buttons in hero meeting screen
  • add Town Screen and Well hotkeys
  • fix incorrect logic nearby castles when an attacking hero was actually defending the castle
  • improve win/loss checks of scenarios
  • make AI to use Lich's ability properly
  • update Russian and French translations
  • make fog discovery by AI more efficient
  • fix missing monster spell casting during battles
  • center fast separation buttons in Monster separation dialog
  • remove required spell points from scrolls in Mage Guilds
  • fix Next Hero selection logic
  • fix Wizards campaign scenario 3 with custom Ultimate Artifact
  • add 500 experience for conquering a town regardless of defending hero existence
  • change battlefield pathfinding algorithm to more stable version avoiding AI skipping some moves
  • align text evenly inside secondary skill window
  • display translated text in campaign dialog
  • fix a crash during Summon Boat spell usage
  • if the hero's army does not have any of the original troops left alive at the end of the battle, then the hero should lose
  • fix typo in Fizbin Medal
  • fix small font for Russian localization
  • fix town/castle passability for some cases
  • fix incorrect mouse cursor appearance during AI turn after a battle with Necromancy outcome
  • fix attacking monster sprite on World map
  • AI should check if an object is guarded while unveiling fog
  • add Alchemist Tower to the list of objects to be avoided by AI
  • fix a case when AI heroes did not ever capture empty castles
https://github.com/ihhub/fheroes2/releases/tag/0.9.5

Changes in v0.9.5 (04 July 2021):

  • do not hire AI heroes if no tasks exist for them
  • make AI be more aggressive if no tasks exist for them
  • all scrollbars support continuous scrolling
  • fix placement of creatures during Month of Monster event
  • add an option to restart Campaign scenario
  • do not show x marker for fully built castle icon
  • fix boat summoning logic in relation to some objects on a world map
  • do not show other types of save files on while saving or loading a save file on Windows
  • improve Sorceress castle building logic for AI
  • make AI to visit Lighthouse
  • fix displayed object info in popup dialogs
  • fix places where a hero could teleport for Dimension Door spell
  • fix passabilities of objects on World Map
  • fix multiple issues with castle music
  • fix disembarkation on coast during diagonal move
  • indicate the number of Ghost guarding Haunted and Abandoned Mines
  • add extra conditions for object visiting by AI
  • make AI to use Hut of Magi and Jail objects
  • AI heroes do not block way for other heroes anymore
  • use spacebar only for activating object
  • fix missing UI update in hero meeting dialog while moving artifacts within internal hero's dialog
  • speed up campaign game menu opening time
  • fix incorrect object rendering order on World Map
  • after loading a map and starting a new game the same map will be selected in the list of maps
  • improve AI logic to capture defendless castles
  • fix black screen for loading a campaign map without files
  • allow AI hero to revisit a castle more than once
  • fix a case of splitting monster stack for 2 monsters in stack
  • add monster descriptions
  • allow to visit Artesian Spring once a week
  • allow right click on captured artifact after battle
  • fix summoning boat logic in relation to AI
  • improve fog revealing algorithm for AI
  • fix Stables movement bonus
  • fix AI heroes interaction between each other (reset meeting flag if a hero updates an army)
  • AI heroes do not visit signs anymore
  • AI do not visit shrines for useless spells
  • fix AI spellcasting and army estimates
  • do not remove Sphere of Negation artifact while visiting Alchemist's Tower
  • fix the status of visited objects for a play with ally
  • make proper rendering of some of disabled buttons
  • fix AI behavior to visit Magellan Maps only once
  • center quickinfo dialog under mouse cursor
  • add more gamepad key mapping for PlayStation Vita
  • fix the displayed text in the army bar while splitting monster stack
  • do not let nearby monsters automatically attack hero if he moved to this location using Stone Lith, Whirlpool or Dimension Door
  • fix incorrect text position insertion while creating a save file
  • fix title background for recruit dialog
  • fix hero's mobility indicator for small number of move points
  • in scenario with random players random opponents are generated so that all races are present and different when possible
  • add new UI text rendering classes
  • fix issue when abandoned mine becomes OBJ_ZERO when captured by AI-controlled hero
  • fix missing road identification on broken by editor maps
  • make per-user config file as default option
  • allow attacking another hero located on a Temple
  • fix attacking monster sprite behavior
  • fix number of choices when splitting a troop into multiple troops
  • fix incorrect trading rate during battle
  • fix missing shadow removal of objects under a boat
  • show Dwarf's resistance message in the log only after spell completion
  • correct campaign bonuses' names
  • fix case when boat could be positioned over another boat over whirlpool
  • fix case of triggering Genie's special ability for a stack of 1 monster
  • fix the Price of Loyalty campaign missing rendering
https://github.com/ihhub/fheroes2/releases/tag/0.9.4

Changes in v0.9.4 (04 June 2021):

  • add Windows executable installers
  • use user profile directories to store files to allow multi-user access to the game
  • fix incorrect text splitting in some rare cases
  • fix crash in Roland campaign, scenario 10
  • add missing popup dialogs for system options and buttons
  • fix inaccurate object sorting by distance
  • fix multiple issues with AI and spell logic using distance evaluation
  • read campaign files in non-case sensitive manner
  • make fog uncovering task for AI more efficient
  • fix AI obsession over Observation Towers
  • fix missing path drawing while a hero moving through Whirlpool
  • mark resource generator objects as visited for allies
  • fix incorrect game ending during time loss condition
  • show extended shadow for 2-hex monsters in battle
  • do not show gray flag over Haunted mine
  • add configuration option for first game run
  • support PoL heroes and artifacts in "battle only" mode
  • fix inaccessible beach tiles
  • add hiding and showing cursor logic for SDL2
  • fix shadows for random resources and artifacts
  • draw correct castle icon in popup window
  • fix incorrect hero position facing left
  • remove unneeded AI hero animation under fog
  • do not clear morale modifiers too early for Tavern case
  • highlight the door in Main Menu for mouse over event
  • speed up and fix many places with cursor rendering
  • recruit an AI hero if none exists
  • fix cheating AI behavior for dwellings with defenders
  • add an option to replay Intro video for campaign
  • force display rendering on app activation
  • fix Stables, Alchemist's Tower and Water Wheel passabilities
  • fix puzzle drawings
  • avoid using useless spell during battle for AI
  • return to load screen after cancelling loaded campaign scenario
  • play hero's vanishing sound after AI vs human battle
  • make Ultimate Crown as artifact for campaign scenarios
  • fix incorrect hero receiving a bonus spell for campaign scenario
  • fix monster recruitment logic when a hero present in a castle
  • fix well's max button hiring logic
  • add an option to open Hero Screen within battle
  • fix move points and spell points replenishment logic for heroes from hero pool
  • set proper difficulty level for campaign scenarios
  • directly show next campaign scenario for end of scenario save
  • modernize and secure demo version installation scripts
  • add video playback for campaign scenarios
  • do not reset hero mana points to maximum in the beginning of a new week if they are above maximum
  • fix Roland, chapter 9 map conditions
  • fix Tower shooting logic during castle's siege
  • build Mage Guild before Special building for AI
  • do not count monsters under fog for Visions spell
  • mute sound by setting music and effects volume to zero instead of pausing them
  • speed up AI hero movement
  • add an option to hide AI movements
  • do not draw objects under the fog if they are far from a revealed area
  • add base code for the Price of Loyalty campaign support
  • add dismiss and upgrade hotkeys in unit dialog info
  • add shadow for Spell Book
  • unveil the fog at the start of hero's movement
  • fix extra place with hero's double shadow at adventure world
  • add extended cursor icons from 4+ to 7+
  • fix UI defect of selected creature appearing after closing meeting dialog
  • fix incorrect rendering of View World image
  • add Lose Sorceress Village Condition for The Succession Wars campaign
  • fix flags in Oracle/Thieves' Guild dialog
  • fix sound mute in background on MacOS with SDL1
  • add Home and End button support for save naming
  • fix elements layout for Oracle/Thieves dialogs
  • generate obstacles on battlefield based on the battle tile index
  • use non-plural name for 1 creature while viewing with Crystal Ball
  • add correct support of The Price of Loyalty add-on maps
  • fix double clicking between dialog in castle's window
  • speed up MIDI loading
  • fix bug with mouse cursor not visible after left-clicking on hero's secondary skill in Kingdom Overview
  • fix the "world: use unique artifacts for resource affecting" option
  • reduce CPU usage for video playback and puzzle revealing
  • fix Mix-up in Roland's Campaign Scenarios
  • fix issue when troop info window was not showing after moving a single unit between army bars using drag & drop method
  • simulate level-ups for campaign-specific heroes that start at a higher level
  • reset current music from the previous turn in the beginning of a new human turn
  • upgrade Rendering engine
  • fix broken behavior of scrollbars
  • do not offer to exchange artifact with spell book
  • disable controllers on non-console platforms
https://github.com/ihhub/fheroes2/releases/tag/0.9.3

Changes in v0.9.3 (04 May 2021):

  • add additional info to oracle dialog
  • check that unit is (im)movable after each unit action
  • save completed Campaign scenario to file
  • drop support of saves older than 0.9 version
  • remove incorrect battle background for Graveyard
  • fix catapult miss animation: show this animation near the actual target object
  • fix magic book behavior in inventory
  • fix spell indicator for hero's icon
  • add special Victory conditions for campaign scenarios
  • add initial support for Campaign Awards
  • allow switching heroes in hero dialog if it was opened from meeting dialog
  • fix issues with battles logs and sounds while Dwarves resist spell
  • fix inability to Teleport monster in battle for certain cases
  • add missing "Not enough gold (xxxx)" text in joining window with Diplomacy
  • do not generate Puzzle image on map loading
  • do not take Mage Guild level into account when calculating count of archers in castle towers
  • fix shadows for all monster mini sprites
  • allow the hero to retreat if, while in the castle, he attacks the hero outside the castle
  • show Mage Guild building always in center
  • add titles to Arena, Alchemist Tower and Stables dialogs
  • add World animation during Dimension door spell
  • show correct message for hero's option dialog buttons
  • fix bridge operation for flying units in instant battle
  • add letters for World View and View spells
  • properly handle filenames with dots
  • fix Main Menu UI redraw
  • do not run animation of lowering the bridge on bridge destruction if bridge is already lowered
  • fix the display of the army in the castle quick info window
  • display correct message when capturing abandoned mine
  • align columns and flags at oracle dialog
  • fix cursor during video playback
  • do not allow the player to scroll world map while hero is moving
  • add initial support of other languages: German, Polish, French and Russian
  • fix information displayed in castle quick info window
  • fix sphinx riddle dialogs
  • add swap buttons in hero meeting dialog
  • correct spells order in Magic Book
  • enable triggering of positive morale event after soft wait
  • fix saving scroll speed in the game config file
  • fix Artesian Springs logic
  • fix inability to use Stone Liths
  • fix incorrect heroes for surrender conditions
  • fix gold and experience dialog for the demon cave
  • remove exclamation mark in Hero info window for "Blood" morale
  • fix double shadow from heroes
  • preserve the order of catapult shots
  • highlight Lich's shot area
  • implement correct post-battle necromancy window
  • rename Coast to Beach
  • fix large object coverage in battles
  • add message when visiting Oracle
  • update hero's path while selecting next hero by an option button
  • fix Dragon City guardians
  • load correct video files to be played
  • implement the "faster monster of opposite army goes first" logic in the battle mode
  • fix excessive FPS issue in dialog of joining the army
  • fix game score calculation
  • reset the spell on tile when AI-controlled hero has successfully captured an object
  • immediately hide hero's path before executing the animation of the Dimension Door spell
  • fix Necromancy incorrect percentage output
  • fix ultimate artifact digging logic
  • speed up images loading from resource files
  • fix fading animation synchronization
  • fix a case of inability to confirm end turn while cursor is over options buttons
https://github.com/ihhub/fheroes2/releases/tag/0.9.2

Changes in v0.9.2 (04 April 2021):

  • fix full artifact bag message
  • add missing dot to sentences in few windows
  • display Lighthouse count in Kingdom Overview
  • add proper large obstacle generation for battlefields
  • fast AI units should try to get the first strike
  • fix Sphinx missing rewards
  • reset paralyze state when receiving spell damage
  • randomize attack position of AI monsters
  • fix missing hero paths after loading a save
  • fix incorrect window resolution in fullscreen mode for SDL 2
  • fix Choose Your Lord video sound
  • improve castle garrison estimation for AI
  • remove the mobility index sprite info from the hero Quick Info dialog
  • prohibit the guardian hero from casting adventure spells
  • fix merging of the castle guardian army
  • fix AI defensive unit behavior
  • add AI estimation of hero spell strength
  • force units into empty slots first during merge
  • do not always offer recruitment of heroes from the player's initial class, make recruitment more random
  • allow player to control the hero during the turn of a hypnotized enemy unit
  • archers should not lose shots when attacking in melee
  • check that the hero is able to move before focusing him and starting his movement
  • respect initial order of units (from top to bottom) when determining the order of unit moves in battle
  • add PlayStation Vita support
  • fix creature recruit window UI
  • fix rendering of Arm of the Martyr
  • fix blind spell logic
  • do not give additional 500 experience to the hero who won the battle if the opposing hero surrenders or flees
  • fix hypnotize spell behavior and the spell description
  • update positions of monster sprites and numbers on army status panel
  • fix possibly incorrect arrow direction from shooters
  • fix scrollbar position in multiple places
  • standartize icon position in town's dialog
  • fix Max/Min button position in monster recruit dialog
  • show the surrender dialog even if there is not enough gold to surrender
  • do not apply morale penalty to a hero if he wins a battle in a Graveyard, Shipwreck or Derelict Ship
  • add cross army drag split
  • reset player focus if focused hero or castle was removed
  • allow to switch players for multiplayer map selection
  • allow to use mouse wheel anywhere for split troop dialog
  • fix logic of placing monsters for a New Month
  • fix Kingdom Overview selection drawings
  • fix boat drawing issues
  • do not hide mouse cursor if the Skill Info dialog called from the Level Up dialog contains OK button
  • fix incorrect drawings for battle OKAY button
  • fix rendering of the Castle Captain's spell points bar
  • fix missing sounds within System dialog
  • fix interaction with the castle bridge for flying units
  • use rule of 9 for Ultimate Artifact placement
  • fix last focus option
  • fix incorrect cursor icon over ally's castle
  • add initial implementation of View spells
  • show normal monster names in Visions spell
  • show full 1-pixel black border for Show Interface option
  • show more detailed morale and luck description
  • AI predicts double cell attacks in combat
  • reset blind spell upon taking any damage
  • allow to place units in moat in front of the bridge
  • align spells in the magic book
  • add manual replay of battle if instant battle outcome is negative
  • sounds off when minimizing game window
  • fix playing background music during video playback on SDL1
  • change damage description log message for some mass spells
  • add executable file description and icon on Windows
  • fix pathfinding backwards move and penalty in battle
  • fix City of the dead incorrect monster info
  • update no data error message
  • do not allow to manage monsters in unhired hero dialog
  • AI supports additional harmful combat spells
  • do not open the hero screen in readonly mode if it was opened from the meeting screen, just disable the DISMISS button instead
  • do not show external music option with an absent folder
  • wait for user input at the end of winning video
  • fix double exit dialog appearance
  • fix missing cursor in Army Bar for SDL 1
  • ranged AI troops should move when blocked by a strong unit
  • if the unit attacks twice, apply the second attack to the same cell as the first attack
  • fix double hex attack target for AI troops
  • show the build window with the OKAY button disabled if there is not enough resources to build this building
  • AI uses mass damage spells in combat
  • improvements for AI wide unit pathfinding
  • fix the pathfinding near monsters: do not allow the hero to pass through the monster
  • fix MIN and MAX button drawings
  • do not give an extra day of life to a player without castles
  • preserve unit direction in battle if it moves strictly vertically
  • do not allow the visiting hero to learn Library spells if the Library hasn't been built yet
  • fix inability to reorganize troops in Kingdom View dialog
https://github.com/ihhub/fheroes2/releases/tag/0.9.1

Changes in v0.9.1 (04 March 2021):

  • add "Artifacts" category in Oracle Screen
  • add barrier fading animation
  • improve AI retreat condition
  • fix Split-related issues and implement some new techniques
  • disable retreat for auto battle mode
  • fix object's visited info and grammar
  • fix issues with path finding during combat to avoid being stopped by moat
  • fix incorrect names of Ballista and Turret during battle
  • add missing shadow for arrow cursor on SDL2
  • fix ACCEPT button font for Good Interface
  • fix AI wide units pathfinding and moat logic
  • allow to modify a hero during level up
  • fix summon boat logic
  • fix monster movement nearby moat
  • AI does not chase faster units during battle
  • fix hero shadow drawing on world map
  • fix missing animation frame for wince
  • fix object fading animation on world map
  • fix missing last lost hero condition reset
  • fix simultaneous animation of boat while during another building construction
  • change logic in monster hiring window
  • fix save loading crash for broken saves
  • add middle resolution status support
  • do not show waiting cursor for an exhausted hero
  • fix post Daemon Cave missing music
  • fix well's max button drawing
  • fix incorrect castle focus while visiting by a hero
  • fix fog drawings
  • reduce income window area in towns
  • fix autosave option logic
  • fix overlapped battleground objects
  • remember scenario difficulty while restarting or choosing a new scenario
  • fix multiple game freezes with MIDI music playback
  • add resizable window support on SDL2
  • make deterministic bonus for hero level-up
  • fix wrong controller pointer speed option name and touch coordinates translation
  • add auto battle resolve mode to the game
  • add logic to support video frame rescaling
  • optimize screen resolution logic
  • fix garrison strength estimation by AI
  • fix magic gardens priority if object capture is enabled
  • optimize AI's troop placement before the battle
  • use same colour for all battlegrounds
  • change text in map size hint, to be displayed in more pleasant manner
  • load game button from UI should lead to common load game logic
  • initial implementation of "View World"
  • fix application crash for drawings
  • change building status message for Dwellings
  • fix crash when entering ally castle
  • add Campaign continuation initial support
  • speed up rendering of World Map
  • fix spell book status on hovering
  • save fullscreen mode in configuration file while switching between modes
  • AI should avoid spellcasting if has advantage in battle
  • fix rendering with mouse emulation
  • fix extensive memory usage by dialog windows
https://github.com/ihhub/fheroes2/releases/tag/0.9.0

Changes in v0.9.0 (04 February 2021):

  • make AI to buy magic books in castles
  • fix infinite AI turns
  • optimize AI hero movements
  • do not visit useless objects by AI heroes
  • fix AI castle defence mechanics
  • restore cursor theme after HeroDialog or CastleDialog
  • do not show marketplace icon for joining creature dialog is no markets exist
  • allow to retreat for a hero with no castles
  • add more logic for AI jumping into whirlpools
  • do not block shooters with overpowered AI army
  • fix overlapped flag in Knight's castle
  • disable actions while buying a hero or during construction
  • change the text for the number of secondary skills in level up window
  • play music after showing New Week/Month dialog
  • reset hero direction when recruiting
  • align dates by left in the load file dialog
  • cap Necromancy bonus to 100% instead of 90%
  • fix multiple bugs in Kingdom View
  • fix Transcribe Spell Description Text
  • cancel spell selection with ESC button in combat
  • add a separate option for displaying terrain penalty
  • fix missing music for Load Game option
  • fix Scouting skill bugs and make crystal ball work
  • do not play dwarves' resist sound during mass spells
  • fix Berserk spell logic
  • fix Chain lightning spell logic
  • fix castle flag drawing for nearby hero
  • add empty line for visited info
  • allow Anti-magic spell on mirror-master creature
  • do not draw wall for destroyed bridge
  • fix pathfinding issues and missing path display
  • fix Kingdom overview crash
  • do no freeze building animation during construction or hero's hiring
  • show Spade of Necromancy artifact bonus in Necromancy Skill window
  • display troop counter only for static animations
  • show pointer cursor most of the time during combat
  • fix castle's object positions
  • display correct text when right-clicking on map tile
  • fix path selection for AI hero in patrol
  • fix town portal buttons in Good interface
  • add new standard window with shadow
  • fix events when AI was choosing to disembark on empty tiles
  • change highlighted cursor area for spells during battle
  • implement Alt hotkey to unite stacks of same type
  • implement CTRL split even with selected troop
  • add touchpad support
  • fix joining creature logic with the lack of gold
  • quick Info dialogs have fixed position when clicked from right bar
  • add a check for special objects that you can't move through
  • after AI loses to either castle or other player, center the view on them
  • fix animation order of creatures during combat
  • do not close File Save window while cancelling file overriding
  • fix marketplace button drawings in Surrender window
  • fix castle's battlefield drawings
  • hide Marketplace button for Surrender window
  • always draw troop counter at the top of monster sprites
  • fix a log message when a tower kills one creature
  • display battle grid for whole battlefield
  • fix object drawings on battlefield
  • Bone Dragon morale malus shows in battle and in dialog window
  • add PNG image saving support for SDL2
  • add palette support for SDL2
  • fix morale and luck sprites positioning in meeting screen
  • fix mouse cursor switching while navigating on hero or castle panel
  • always display the correct monster combat speed
  • fix Player Rankings message bar
  • add game controller support
  • add Gold version support for video playback
  • fix Earthquake spell animation
  • fix enemy castle capture action
  • speed up interface drawings for World map
  • fix Hydra's movement during battle
  • set correct size for spell point and experience point zone in hero window
  • fix income info clickable area at castle options window
  • add spells into Hero's book at the start of a map
  • add initial Nintendo Switch support
  • fix Battle Only mode UI
  • fix logic in mouse button press
  • make interface itemsbar adjustment while fixing some split logic
  • add FPS display support
  • fix clickable area for Heroes and Castles in their lists
  • differentiate save files based on game type
  • fix a crash while trying to get settings path on SDL 2
  • fix path calculation for 2-hex creatures in battle
  • add software emulation support for cursor
https://github.com/ihhub/fheroes2/releases/tag/0.8.4

Changes in v0.8.4 (23 December 2020):

  • speed up sprites allocation and deallocation (noticeable for low-profile devices)
  • add extra info in first game message about F4 key
  • fix moral and luck indicator messages
  • fix elements' layout at monster info dialog
  • disable monster attack using Dimension Door
  • remove click area around "exit" button in castle dialog
  • make elements gray for inactive game settings
  • fix cursor disappearance during resource collection on SDL 1
  • fix Summon Boat animation
  • fix Summon boat logic
  • fix popup message for Abandoned Mine
  • fix Select button movement in Scenario window
  • fix position of monsters in battle
  • modify monster upgrade window for 3 monsters
  • make proper calculation of movement points
  • fix Hide interface reset issue
  • show correct artifact transfer dialog after battle
  • fix impassable barrier
  • fix meeting hero update after exiting hero's dialog
  • adjust troop separation dialog position
  • fix incorrect hero values for Kingdom Overview screen
  • fix a crash in multiplayer game selection
  • fix Transcribing spell scrolls logic and UI
  • change info bar text at bottom of screen when mouse cursor positioned under spell book
  • fix starting unit values
  • implement smooth slider
  • add an option to reveal secondary skill info by right click
  • implement Stack split hotkeys (Ctrl and Shift)
  • fix Resurrect logic
  • ignore fog for dimension door
  • disable gift button for a single player
  • fix empty castle capture behavior
  • fix interaction with skeleton on map
  • persist random race for new games
  • fix road penalty text
  • fix incorrect town frames for evil town portal UI
  • speed up generation of few images
  • fix UI defect after closing hero meeting dialog
  • fix UI defect after using town portal spell
  • modify a message of joining 1 creature due to diplomacy
  • do not show income popup window for exit button in castle screen
  • change Tavern dialog caption color to yellow
  • fix rendering and mouse behavior for SDL2
  • fix elements layout for surrendering dialog
  • fix shadow under battle summary dialog
  • fix surrendering cost calculation
  • fix Wagon sounds
  • fix castle and hero interaction with mouse in Kingdom Overview
  • play sound only after a battle with a monster to grab an artifact
  • change window title font color to yellow
  • fix Pyramid bad luck position
  • correct few sprites of mini-monsters
  • add a popup message for View Hero button
  • remove spaces for Level messages
  • clear message log for retreat case
  • add missing Info word for artifact status bar
  • add popup messages for next/previous pages in spell book
  • use keypad Enter as a normal one
  • add magic book image to popup window for no gold case
  • add evil UI elements for "Town portal" spell window
  • show proper information about enemy based on Thieves Guild count
  • fix layout for campaign selection window
  • fix wrong title and wrong text for Freeman's Foundry window
  • fix battle log text layout
  • use Delete key to erase a part of save filename
  • fix post game conditions
  • do not reset army while moving a hero to castle
  • start battle instantly if sounds are off
  • add captain portraits in Kingdom Overview
  • correctly reduce hero army after whirlpool
  • fix Mine info dialog near hero
  • reset Adventure Options buttons when cursor move out
  • draw shadow for empty boat
  • do not show army formation with no captain
  • fix stack overflow when using spells
  • fix disappearing hero while attacking monsters standing on a barrier
  • select a hero with move points when the focus is on a castle
  • increase Meteor Shower spell animation
  • fix spell order in magic book
  • change Artesian spring logic
  • fix Alchemist Tower artifact manipulation logic
  • assemble Battle Garb of Anduran artifact for hero meeting
  • place a hero into castle at the start of game
  • change map size popup window text
  • fix primary skill frame color in hero meeting dialog
  • add a title for joining army dialog
  • fix battle false hex shadow
  • fix drawings for meeting hero dialog
  • fix map loading message typo
  • update hero availability pool after hero purchase
  • show hero's name in magic book related windows
  • fix scenario selection stuck condition
  • fix portraits/hero information display in battle only mode
  • fix normal moral icon image
  • fix enemy army threat check for AI
  • fix changing boat direction by AI at boarding
  • fix Sorceress castle background image
  • fix missing spell point digits
https://github.com/ihhub/fheroes2/releases/tag/0.8.3

Changes in v0.8.3 (04 November 2020):

  • protect higher value (based on buildings built) AI castles first
  • recruit AI heroes in castles other than the first one in the list
  • improve castle build order for AI (priority on gold income buildings)
  • improve castle monster recruitment
  • fix slowed flying creature movement
  • remember boat direction when a hero boarding it
  • update Town Portal dialog
  • fix "jump" flying animation when unit has to turn for attack
  • make one rumour per week in Taverns
  • fix a case of second attack on blinded creature
  • fix Monster Info dialog in evil interface
  • fix few castle icon sprites
  • speed up video loading
  • play sound on shipwreck pickup
  • fix Freeman Foundry UI
  • fix War Troll's stone sprite
  • fix Cavalry sprite
  • fix mage guild dialog message
  • change troop logic exchange during hero meeting
  • fix boat summoning animation
  • AI heroes are able to exchange armies and artifacts
  • add road connection drawings for Barbarian castle
  • fix castle defense trigger condition
  • do not show teleport animation in some AI cases
  • center Good and Evil interface windows
  • teach AI units to block enemy archers
  • show boat froth sprite when stationary
  • fix mine guardians sprites
  • fix battle only mode
  • improve army merge logic
  • fix lighthouse missing movement bonus
  • fix boat and hero positions
  • speed up drawings for World Map
  • fix pass image position on the world map
  • give at least 1 skeleton via necromancy after victory
  • fix Hero screen status bar message position
  • fix Hero meeting screen title position
  • change Pyramid Bad Luck message
  • fix Hero's portrait position in hiring window
  • fix town population status text position
  • fix boat animation and castle building rendering
  • fix troop counter window position
  • fix adjacent monsters when removing one
  • add after play animation
  • fix redraw order for unit count
  • use hero spell power when applying Earthquake
  • fix Lighthouse fog clearance
  • change Hut of the Magi behavior
  • add game resolution popup window
  • fix spelling in castle dialog
  • always show building requirements even if the building is already built
  • fix Knowledge message in Arena
  • five every magic guild a guaranteed damage and non-damage spell
  • fix OK button in joining army window
  • adjust few elements in resolution window
  • fix battle option text overlapping
  • adjust post battle shadowing
  • change main menu popup window descriptions
  • add a support of hero AI patrol mode set in scenario
  • make autosave with capital letters
  • disable spell animation after mine was cleared
  • save boat direction when disembarking
  • set parts of wide objects as visited
  • allow AI player to start it's move with action
  • use original difficulty bonuses for the game
  • check armies for validity before starting the battle
  • remove mud pool from cover object list
  • fix non-necro undead units decreasing morale in an all-undead army
  • remember Ghost number after the battle
  • AI is able to collect/capture multiple objects on the same move
  • adjust heuristics to make sure AI doesn't miss important objects
  • fix a bug where AI was able to buy boats in a castle without shipyard
  • fix the artifact spell damage modifiers
  • display a proper message for Sea Bottle
  • make AI more aggressive when dealing with archers during battle
  • replace "Ship Wreck" word with "Shipwreck"
  • set random starting experience for starting heroes
  • play flotsam disappearing sound after closing the UI window
  • fix income window drawings
  • when retreating, give the hero its default army instead of 1 T1 unit
  • stop battle after AI kills the last creature
  • use word 'one' instead of '1' in battle log
  • fix Thieves Guild background
  • fix visibility of AI hero moving close to fog
  • limit Player name text within given input box
  • show skip turn message during AI turns
  • allow to cast a spell with no move points
  • fix incorrect resurrected troops after battle
  • honor player count for Hot Seat mode
  • fix system options UI dialog
  • remove scrollbars from interface in battle only mode
  • add fading animation for defeated AI hero
  • fix time loss conditions in Info window
  • fix displaying of remaining move points
  • fix multiple text issues
  • modify messages for splitting army window
  • fix the clipped boat sprite
https://github.com/ihhub/fheroes2/releases/tag/0.8.2

Changes in v0.8.2 (04 October 2020):

  • make correct spawn of neutral monsters and their growth
  • display correct window while right clicking on a monster in Kingdom Overview
  • add a transition while boarding and off-boarding boats
  • make hero animation movement smooth
  • fix price of loyalty artifact selection
  • fix tents/barriers from Price of Loyalty
  • fix monster placement at the beginning of months
  • fix missing dialog box in some rare cases
  • do not show journal scrollbar when it's not open
  • fix second attack for blinded creatures
  • fix typo in description of "Knowledge" skill at hero screen
  • replace the original project AI with an enhanced version of AI (making it not that stupid)
  • save neutral army monsters after battle
  • always reduce moral for visited Graveyard and Ship Wreck
  • allow heroes to move last step with remaining move points
  • update village's shadow after a castle was built
  • fix drawing castle's shadows under roads
  • fix missing objects on puzzle map
  • fix empty puzzle map
  • fix Ultimate image on puzzle map
  • fix puzzle map color scheme
  • fix treasure location map doesn't update after visiting obelisk
  • fix amount of creatures position in monster information dialog
  • fix object and shadow drawings on the World Map
  • fix few hero's names
  • draw all dead troops on the battlefield
  • allow to open hero's dialog during heroes meeting
  • fix dialog frame generation
  • fix bookmark positions in Spell Book
  • fix text position in creature recruit window
  • add Information window by right click on buttons in File dialog
  • add the word "Skill" for "Attack Skill" and "Defense Skill" in monster window
  • fix font generation and its position
  • fix popup windows for Spell Book
  • fix cursor for Adandoned mine protected by monsters
  • fix scrollbar in Settings window
  • extend campaign support
  • add proper AI base code for battle
  • change hero's description in tavern
  • use single OKAY button for Config window
  • don't shift High Score background for high resolutions
  • shift second hero's portrait in town
  • change unit movement speed in battle under spells
  • add dynamic color for item selection
  • add a popup window for clicking on Income in castle construction screen
  • remember move points of surrendered/retreated hero
  • fix bottom-to-top scrolling issue by keyboard key
  • reset dismissed hero's army
  • focus on a new hired hero in a castle
  • check magic resistance against Chain Lighting spell
  • fix multiple UI issues in Hero's Screen
  • add proper credits
  • center Hero's Options dialog
  • display spell points in magic book from bottom to top
  • display Liches for the City of the Dead
  • fix hero button position for joining window
  • fix a crash for Mirror image creature
  • reset music after exiting a castle
  • fix Daemon Cave incorrect window
  • fix Blind spell name
  • fix victory message in battle
  • fix File Options window position
  • fix text color for joining window
  • fix monster info shadow position
  • remove extra wince animation for cold ray spell
  • fix some icon positions in hero's dialog
  • center spell point text in hero's dialog
  • add word 'player' for defeated player window
  • fix creature shadow contour issue
  • add button choice support for campaign
  • fix cycling colors for mirrored Genie
  • add external music support by default
  • fix "battle: show damage info" option
  • fix Bloodlust animation
  • speed up calculations for path finding algorithm
  • fix caption issue with map's objects
  • fix missing resolutions and black screen for SDL 1
  • add a dot at the end of multiple messages
  • fix few Phoenix frames
  • make scrollbar more user friendly in battle log
  • fix game info buttons
  • disable Next Hero button if none of heroes can move
  • fix multiplayer player count window position
  • fix graphical issues in big text font
  • fix creature animation going out of window
  • fix few scenario information window UI issues
  • optimize video playback
  • fix map size button state selection
  • fix New Game window UI elements
  • fix a crash while downgrading resolution in 8-bit mode
  • remove "castle: allow recruits special/expansion heroes" option
  • fix sky view status
  • fix generated window position
  • make Exit button disabled for popup windows in castle
  • fix Resurrection animation frame and moving dead units
https://github.com/ihhub/fheroes2/releases/tag/0.8.1

Changes in v0.8.1 (04 September 2020):

  • play video intro at the opening
  • set focus on a hero while opening spell book
  • set minimum Spell Power to be always 1
  • fix castle mini status icon
  • fix drawings of Mage Guilds in castles
  • remove multiple redundant / useless configuration settings
  • fix message dialog shadow
  • add proper support of configuration file
  • fix shift after opening and closing system options dialog
  • fix Gift button for evil interface
  • fix drawings of letters
  • fix Resurrect spell animation
  • add a support of disabled buttons
  • fix Air elemental drawings
  • remove "Manage creatures" button's shadow
  • add missing glowing effects of units in UI
  • fix screen dimming effects
  • change missing file message
  • fix Armageddon animation
  • update interface after exiting hero dialog
  • fix incorrect map extension crash
  • show diplomacy window always and fix marketplace button
  • add dot at the end of player's turn text
  • always center scenario window position
  • fix map difficulty text and its position
  • use proper config background window image
  • reset interface while changing its type
  • add game scenario dialog shadow
  • do not refocus after exiting castle's window
  • make Disabled Campaign button in High Score board
  • disable Campaign button if no files present
  • fix abandoned mine cursor
  • fix Sphinx message
  • modify Graveyard robber message
  • polish "battle only" button
  • show upgrade button if there's not enough money
  • fix lighthouse spelling
  • fix Dismiss button position in Hero's dialog
  • add missing title for graveyard message
  • fix graveyard message
  • fix marketplace text
  • show a dialog if a hero has no space for Spell Book
  • don't display artifact dialog for unknown artifact
  • fix an ability to skip button state by using a keyboard
  • apply Chain Lightning damage on allies as well
  • skip turn of resurrected troop
  • fix EXIT button position in creature window
  • fix AI turn crash
  • remove black background over army counter
  • don't show start and end position for monster log movement
  • add logic to handle inability to buy monsters in well
  • don't draw tent images in the castle window avoiding weird animation during castle construction
  • remove move penalty for visiting whirlpool/teleport
  • fix descriptions for Thief's Guild and for Pathfinding skill
  • fix logic of Town Gate spell
  • fix wrong OKAY button in System Options for Evil interface
  • skip heroes who do not have move points for move or sleep
https://github.com/ihhub/fheroes2/releases/tag/0.8

Changes in v0.8 (26 July 2020):

  • add in-game option to change resolution
  • add fade-in / fade-out animation for AI heroes
  • fix logic when all stone liths were connected to each other even with different types
  • fix logic for stone liths in regard to allies
  • fix flickering of status bar in castle
  • fix attack cursor while pointing a monster
  • fix wolf's low attack animation
  • fix monster attack and fade animation on the World Map
  • add boat building animation
  • adjust hero battle dialog to align text
  • display boat froth sprite only if away from the coast
  • use correct sprite for castle's exit button
  • do not play sounds in castle's window when restoring the application
  • fix cursor for empty enemy castle
  • fix incorrect popping window in tavern
  • fix delayed mouse click on a cell during battle
  • fix hero's level up window with single secondary skill
  • fix system menu OKAY button
  • add SMK video file support
  • reset sound only if battle is shown
  • add support for ogg music from GOG version
  • fix castle drawings on the World Map
  • add Holy Shout spell blur effect
  • fix pathfinding calculations
  • increase the amount of sound channels to 16
  • add death wave/ripple spell effect animation
  • fix Hypnotize and Berserk spell behaviors
  • correct battle hero animation
  • update elemental storm spell
  • "Summon boat" spell should not be used on a board
  • do not focus screen on a hero after adjusting scroll speed
  • fix Shipwreck empty battle
  • use only screen supported resolutions for the game
  • update focus logic for AI moving out of the fog
  • change mass spell icons
  • add frame border to highscore screen
  • reuse SDL2 texture for drawing speedup
  • adjust arena object dialog
  • set monster to static state after receiving a hit
  • fix mage guild window background
  • fix hero's status bar item locations
  • fix going out the battle window animation
  • shift battle log window by 1 pixel up
  • add a partial drawing support for disabled buttons
  • add Chain Lightning spell animation
  • add color cycling for active monster aura
  • fix incorrect hero's interaction with objects during movement
  • adjust flags animation with battle speed
  • make hero's movement smoother
  • show a proper status window message depending on resolution
  • fix last unit being hit 1 frame missing animation
  • remove Skill word from level up window text
  • add full animation in castles
  • update cursor while pointing over empty interface area
  • make "Next hero" button disable when no heroes exist
  • fix extra shifting upon landing for flying monsters
  • adjust music fade in effect and better sound selection
  • add monster animation on the World Map
  • fix fullscreen behavior in SDL 2
  • fix World Map drawings and screen jumping during hero movement
  • fix display drawing during mouse movement in SDL 2
  • fix ship shifting upon boarding it
  • add keyboard support for navigation in spell book
  • display different message in Mage Guild if no hero is in castle
  • fix text positioning in status bar
  • add lightning spell animation
  • do not run idle monster animation at every human's turn
  • fix marketplace window UI issues
  • fix creature contour drawing
  • add keyboard support for navigating between castles and heroes
  • add hero's flag animation in a world map
  • draw post attack animation in parallel so that battle animation does not look slow
  • add a flag to castle's captain
  • fix cursor offsets so that the cursor does not jump while changing its type
  • fix incorrect drawings for many interactive dialogs
  • make yellow frame for not available building info
  • fix hero hiring animation position
  • fix incorrect mouse cursor type during hero's movement by keyboard
  • add magic resist sound
  • add missing Skill word for Attack and Defense
  • fix fog drawings
  • multiple tweaks for AI
  • fix Spell Power naming in its icon and description
  • add animation of many objects on World Map
  • fix Mirror Image clone placement
  • add smooth screen scrolling during hero's movement
  • fix screen shifting when a hero initiates movement without movement points
  • complete hero's movement cell if an user clicks by mouse during hero's movement
  • add Stunning animation
  • fix Resurrect spell animation and logic
  • fix Armageddon and Earthquake animations
  • show gray text for spells which need more than current hero spell amount
  • set correct yellow and white font
  • fix BloodLust spell animation
  • fix incorrect stunning state rendering
  • add mirror image effect
  • fix puzzle view on high resolutions
  • remove Visual Studio 2015 Redistributable package dependency for 64-bit application on Windows
  • add F4 fullscreen hotkey support
  • set correct animation for Holy Word and Holy Shout spells
  • fix join/flee condition
  • fix castle siege logic
  • add an improved battle obstacle selection
  • fix multiple places with shadows while using alpha blending in SDL 1
  • hide World Map for next turn in hot seat mode
  • add an ability to use scroll by mouse in SDL 2
  • fix transparency on World Map and during Battle
  • fix message content and scrollbar for Town Portal spell
  • fix a crash during hero's dismiss
  • fix retaliation condition logic
  • fix a crash while checking hero with focused castle
  • change monster value heuristics for battle and strategic decisions
  • fix Mutant Zombie HP value
  • add color cycling for glowing effects on monsters during battle
  • show adventure map quick info by right click for dimension door spell
  • make hero's path visible after using a portal
  • add a log message for flying monsters
  • fix teleport and luck animation
  • fix ripple effect for disrupting ray and cold ray
  • update focus after hero's dismiss
  • add a condition to check down bridge for unit defense calculation
  • set World Map border size depending on resolution
  • add flag animation of neutral captain in a castle/town
  • do not move to current hero after closing hero's stats window
  • make interface background more uniform on high resolutions
  • add exit button during Dimension Door spell
  • update sounds and music upon teleport spell usage
  • add bad luck animation
  • fix blurred buttons in Adventure Panel
  • fix teleport delay in animation
  • sort spell icons by their duration in monster statistics window
  • fix defense formula for monster during battle
  • fix application exiting behavior (add support for Alt+F4 in Windows for SDL 2)
  • ignore bad spell status for Archmage attack
  • set correct post battle video sequence
  • fix hero fading animation
  • add min and max buttons for troop separation dialog
  • speed up rendering
  • fix hero's flag position in battle
  • fix inability to cast another spell upon cancellation teleport spell
  • play resource collection and sound after reading a dialog
  • fix monster frame jumps from multiple spells
  • fix medusa's stunning effect icon
  • fix Lich animation
  • fix monster count window position during battle
  • fix Archmage dispel post attack effect
  • fix Tower and Magic Arrow animation
  • fix the logic for Summon Boat spell
  • fix Identify Hero spell behavior
  • fix 0 monster count for Visions spell
  • add correct battle speed adjustment formula
  • show disable Dismiss button for a hero in a castle
  • fix idling condition check
  • add correct attacks and timing for monster animation during battle
  • give a control to an opponent over hypnotized monster
  • do not shift application window while loading a map for SDL 2
  • fix Haunt spell incorrect behavior
  • add shadows to most of dialogs
  • play animation of treasure chest after the collection
  • fix incorrect position of a stunned monster
  • remove red cross over town's icon
  • fix new dwelling building animation
  • fix an ability to continue movement after Town portal spell
  • fix quick info window position at world map
  • show reflected monster animation in a dialog for right-side monsters
  • do not auto disable dimension door spell nearby world map edges
  • remove tiny ship icon from hero's status
  • do not move a hero over a portal above another hero
  • add hero movement interruption by left click
  • use shadowing for end movement cell during battle
  • fix inability to move artifacts
  • show a message if not enough points for digging
  • fix monster attack at the start of a new game
  • add fading animation after battle
  • limit Dimension Door spell range
  • fix invisible hero's path after Dimension Door spell
  • fix landing from a ship event to attack a monster after landing
  • fix incorrectly mixed spells for hero's book
  • fix Mass Cure spell icon
  • fix spell icon size and position for monster's info
  • fix cursor behavior beyond world edge
  • set correct position of monster in dwelling info
  • fix minimap colors
  • add an option to switch music type in settings
  • fix incorrect map object naming
  • fix hero, castle and status army info
  • add support for swapping palettes
  • fix hero movement at low speeds
  • fix display info of resources with scouting
  • fix spell sorting crash and mage guild positioning
  • add an optional aspect ratio correction in full-screen mode
  • fix a crash for unhired hero
  • fix alignment of text in monster info dialog
  • fix shifted monster position in recruit window
  • fix negative resources in kingdom possession
  • rework of midi music and sounds
  • fix current unit animation freezing state during battle
  • draw main menu as a background for High Score window
  • fix a case when not destroyed castle gate remains opened after a troop from a castle crossed the bridge during battle
  • fix Phoenixes naming
  • fix EXIT button position in castle options
  • set a correct position for buttons in Configuration dialog
  • fix Genie special ability
  • fix surrender action during battle
  • fix UI value input selectors
  • fix hero animation in battle
  • fix hero position in battle
  • add monster animation in army's info dialog
  • add monster movement animation in Well
  • use BIN file information for correct monster animation
  • fix unit damage formula
  • fix morale and luck events
  • add *nix case insensitive file access
  • make Army Order window during battle optional
  • fix flying monster animation offset
  • fix a crash for hot seat game start
  • add quit dialog popup window and change File Options dialog behavior
  • fix incorrect position of recruit Creature Info window in higher than default resolutions
  • add scalable Sprites support
  • do not show text for hidden Dismiss button
  • fix multiple memory leaks
  • add double quotes to surrender dialog
https://github.com/ihhub/fheroes2/releases/tag/0.7

Changes in v0.7 (18 April 2020):

  • add an edge of the World Map
  • complete hero movement cell if a player stops it by any keyboard button
  • add hotkey 'N' to start a new game within the World Map like in the original game
  • fix crashes in multiple maps during map loading or during AI turns
  • fix game freezes during animation of resources collection
  • make human turn always first
  • do not show file save loading dialog if no saves exist
  • fix 'Adventure Options' button is being blurred after pressing it once on MacOS
  • cancel a dialog by pressing close window button
  • do not allow to move artifacts for not hired heroes in a castle
  • allow file selection by double-clicking on a list entry
  • fix MAX button state for monster change in recruitment window
  • update a cursor while stopping hero's movement
  • add monster animation in castle's well
  • set correct animation speed
  • fix scrolling behavior and add diagonal scrolling
  • display recruit monster window even for 0 creatures within castle's well
  • fix castle well UI elements
  • show hero movement arrow icons with proper colors
  • fix resizing of mini-map in fixed interface mode
  • do not show Necromancy window for 0 obtained skeletons
  • show an empty area instead of tavern building in Necromancer castle
  • fix a state of no information displayed in Signs
  • fix a crash after loading a save and starting any new game after
  • fix a crash during loading empty or newly saved configuration file
  • add initial campaign support (very early beta)
  • fix crystal resource position
  • fix hero path cursor display
  • add missing option's titles in Battle Option dialog
  • fix a popup messages for different types of resources at the World Map
  • fix application title disappearing after loading any map on SDL 2
  • fix disabled black surrender button in hero's options in SDL 2 mode
  • move creature window info to the top of the castle screen
  • make "Buy from well" as a default option
  • remove speed 0 for battle and the World Map
  • fix spell book is being displayed in semi transparent way
  • hero path is recalculated each time when an user chooses the hero
  • show creature information in recruitment building
  • fix incorrectly displayed shadows on the World Map for SDL 2
  • change AI behaviour for castle building strategy
  • fix positions of labels in in-game settings
  • remove 'Dismiss' button for a hero who is not hired yet in a castle
  • fix incorrectly shown message of found resources in the World Map bottom-right window
  • fix battle grid
  • add missing text in battle options dialog
  • fix World Map UI is being drawn inside castle options
  • fix shadow display in a battle
  • disable Spell button for castles
  • change font color for a creature's name
  • fix observation tower coverage area
  • hide dismiss button for monsters if a hero has only one stack of them
  • fix incorrect message for Shipyard by right click
  • fix missing battle hexagonal net was not appearing if enabled until next battle
  • display a warning message about missing files from the original game
  • disable Continue Movement button for a hero which does not have chosen path
  • required building names are displayed on a separate line each
  • fix no building image for already built building for right mouse click
  • fix incorrect image for Captains Quarters
  • renamed 'Free Heroes II' to 'fheroes2'
  • show disabled "Continue Movement" button for castle
  • fix number of monsters being displayed in a black box
  • add a script to download a demo version of the original game
  • set default display size to 640x480 pixels
  • add Windows build
https://github.com/ihhub/fheroes2/blob/master/docs/images/screenshots/screenshot_world_map.png?raw=true Map-Screen https://github.com/ihhub/fheroes2/blob/master/docs/images/screenshots/screenshot_castle.png?raw=true Castle-Screen https://github.com/ihhub/fheroes2/blob/master/docs/images/screenshots/screenshot_battle.png?raw=true Battle-Screen
fheroes2-1.0.12+dfsg/script/tools/000077500000000000000000000000001456075706000167055ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/script/tools/changelog_update_metainfo.py000066400000000000000000000063101456075706000244320ustar00rootroot00000000000000#!/usr/bin/env python3 ########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2023 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### import argparse import datetime import re import sys from xml.sax.saxutils import escape # # Appstream file could be validated with 'appstream-util validate io.github.ihhub.Fheroes2.metainfo.xml' # class CustomArgumentParser(argparse.ArgumentParser): def error(self, message): self.print_usage(sys.stderr) self.exit(1, "%(prog)s: error: %(message)s\n" % {"prog": self.prog, "message": message}) def parse_arguments(): parser = CustomArgumentParser() parser.add_argument("changelog_file", help="changelog.txt file") parser.add_argument("metainfo_file", help="*.metainfo.xml file") return parser.parse_args() def main(): args = parse_arguments() changelog = open(args.changelog_file, encoding='utf-8').read() metainfo = open(args.metainfo_file, encoding='utf-8').read() tpl = ''' https://github.com/ihhub/fheroes2/releases/tag/{}

Changes in v{} ({}):

    {}
\n'''.lstrip("\r\n") tmp = '' regex = r"version ([\d.]+) \(([\w ]+)\)\n(.*?)[\n]{2}" for match in re.findall(regex, changelog, re.MULTILINE | re.DOTALL): tmp += tpl.format( datetime.datetime.strptime(match[1], '%d %B %Y').strftime('%Y-%m-%d'), match[0], match[0], match[0], match[1], re.sub(r'- (.*)', r'
  • \1
  • ', escape(match[2])) ) new_metainfo = re.sub(r'(.*?)', r'\n' + tmp + ' ', metainfo, flags=re.MULTILINE | re.DOTALL ) open(args.metainfo_file, 'w', encoding='utf-8').write(new_metainfo) if __name__ == "__main__": main() fheroes2-1.0.12+dfsg/script/tools/check_code_format.sh000066400000000000000000000025201456075706000226570ustar00rootroot00000000000000#!/bin/bash # Copyright 2018 Google LLC # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Script to determine if source code in Pull Request is properly formatted. # Exits with non 0 exit code if formatting is needed. # # This script assumes to be invoked at the project root directory. set -e -o pipefail FILES_TO_CHECK=$(git diff --name-only HEAD^ | (grep -E ".*\.(cpp|cc|c\+\+|cxx|c|h|hpp|java)$" || true) \ | (grep -v "^src/thirdparty/.*/.*" || true)) if [ -z "${FILES_TO_CHECK}" ]; then echo "No source code to check for formatting." exit 0 fi FORMAT_DIFF=$(git diff -U0 HEAD^ -- ${FILES_TO_CHECK} | clang-format-diff -p1 -style=file) if [ -z "${FORMAT_DIFF}" ]; then echo "All source code in PR properly formatted." exit 0 else echo "Found formatting errors!" echo "${FORMAT_DIFF}" exit 1 fi fheroes2-1.0.12+dfsg/script/tools/check_copyright_headers.py000066400000000000000000000124601456075706000241220ustar00rootroot00000000000000#!/usr/bin/env python3 ########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2022 - 2023 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### import argparse import datetime import os import re import subprocess import sys CURRENT_YEAR = datetime.datetime.now().date().year class CustomArgumentParser(argparse.ArgumentParser): def error(self, message): self.print_usage(sys.stderr) self.exit(1, "%(prog)s: error: %(message)s\n" % {"prog": self.prog, "message": message}) def parse_arguments(): parser = CustomArgumentParser() parser.add_argument( "--handle-shebang", action="store_true", help="handle the shebang at the beginning of the source file(s)" ) parser.add_argument( "full_header_file", help="full header file" ) parser.add_argument( "header_template_file", help="header template file" ) parser.add_argument( "source_file", help="source files (at least one, can be multiple)", nargs="+" ) return parser.parse_args() def main(): # if something is wrong with the commandline arguments, # the script exits here with return code 1: args = parse_arguments() with open(args.full_header_file, "r", encoding="latin_1") as full_header_file: copyright_hdr_full = full_header_file.read().strip().replace("{YR}", f"{CURRENT_YEAR}") with open(args.header_template_file, "r", encoding="latin_1") as header_template_file: copyright_hdr_tmpl = header_template_file.read().strip() copyright_hdr_re_tmpl = copyright_hdr_tmpl for ch_to_replace in "*()": copyright_hdr_re_tmpl = copyright_hdr_re_tmpl.replace(ch_to_replace, "\\" + ch_to_replace) copyright_hdr_year_re = re.compile("^" + copyright_hdr_re_tmpl.replace("{Y1} - {Y2}", "([0-9]{4}) ")) copyright_hdr_yrng_re = re.compile("^" + copyright_hdr_re_tmpl.replace("{Y1}", "([0-9]{4})") .replace("{Y2}", "([0-9]{4})")) for file_name in args.source_file: if not os.path.exists(file_name): continue with open(file_name, "r", encoding="latin_1") as src_file: src = src_file.read() if args.handle_shebang and src.startswith("#!"): partition = src.partition("\n") shebang = partition[0] src = partition[2].lstrip("\n") else: shebang = "" year_re_match = copyright_hdr_year_re.match(src) yrng_re_match = copyright_hdr_yrng_re.match(src) src_is_ok = False if year_re_match: if year_re_match.group(1) == f"{CURRENT_YEAR}": src_is_ok = True elif yrng_re_match: if yrng_re_match.group(2) == f"{CURRENT_YEAR}": src_is_ok = True if not src_is_ok: if year_re_match: hdr = copyright_hdr_tmpl.replace("{Y1}", f"{year_re_match.group(1)}") \ .replace("{Y2}", f"{CURRENT_YEAR}") src = copyright_hdr_year_re.sub(hdr, src) elif yrng_re_match: hdr = copyright_hdr_tmpl.replace("{Y1}", f"{yrng_re_match.group(1)}") \ .replace("{Y2}", f"{CURRENT_YEAR}") src = copyright_hdr_yrng_re.sub(hdr, src) else: src = copyright_hdr_full + "\n\n" + src.lstrip("\n") if shebang: src = shebang + "\n\n" + src with open(file_name + ".tmp", "x", encoding="latin_1") as tmp_file: tmp_file.write(src) with subprocess.Popen(["diff", "-u", file_name, file_name + ".tmp"]) as diff_proc: diff_proc.wait() os.remove(file_name + ".tmp") if __name__ == "__main__": main() fheroes2-1.0.12+dfsg/script/tools/check_copyright_headers.sh000066400000000000000000000105111456075706000240770ustar00rootroot00000000000000#!/bin/bash ########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2022 - 2023 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### # Script to determine if source code in Pull Request has proper copyright headers. # Exits with non-zero exit code if formatting is needed. # # This script assumes to be invoked at the project root directory. set -e -o pipefail SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}") HEADERS_DIR="$SCRIPT_DIR/copyright_headers" C_LIKE_FILES_TO_CHECK=$(git diff --name-only HEAD^ | (grep -E ".*\.(cpp|cc|c\+\+|cxx|c|h|hpp|java|rc)$" || true) \ | (grep -v "^src/thirdparty/.*/.*" || true)) SCRIPT_FILES_TO_CHECK=$(git diff --name-only HEAD^ | (grep -E ".*(\.(sh|py|ps1)|CMakeLists.txt|Makefile[^/]*|Android.mk|Application.mk)$" || true) \ | (grep -v "^script/tools/check_code_format.sh$" || true) \ | (grep -v "^src/thirdparty/.*/.*" || true)) WINBAT_FILES_TO_CHECK=$(git diff --name-only HEAD^ | (grep -E ".*\.bat$" || true) \ | (grep -v "^android/gradlew.bat$" || true) \ | (grep -v "^src/thirdparty/.*/.*" || true)) if [ -z "$C_LIKE_FILES_TO_CHECK" ] && [ -z "$SCRIPT_FILES_TO_CHECK" ] && [ -z "$WINBAT_FILES_TO_CHECK" ]; then echo "No source code to check if the copyright headers are correct." exit 0 fi if [ -n "$C_LIKE_FILES_TO_CHECK" ]; then C_LIKE_FORMAT_DIFF=$(python3 "$SCRIPT_DIR/check_copyright_headers.py" "$HEADERS_DIR/full_header_c_like.txt" \ "$HEADERS_DIR/header_template_c_like.txt" \ $C_LIKE_FILES_TO_CHECK) fi if [ -n "$SCRIPT_FILES_TO_CHECK" ]; then SCRIPT_FORMAT_DIFF=$(python3 "$SCRIPT_DIR/check_copyright_headers.py" --handle-shebang \ "$HEADERS_DIR/full_header_script.txt" \ "$HEADERS_DIR/header_template_script.txt" \ $SCRIPT_FILES_TO_CHECK) fi if [ -n "$WINBAT_FILES_TO_CHECK" ]; then WINBAT_FORMAT_DIFF=$(python3 "$SCRIPT_DIR/check_copyright_headers.py" "$HEADERS_DIR/full_header_winbat.txt" \ "$HEADERS_DIR/header_template_winbat.txt" \ $WINBAT_FILES_TO_CHECK) fi if [ -z "$C_LIKE_FORMAT_DIFF" ] && [ -z "$SCRIPT_FORMAT_DIFF" ] && [ -z "$WINBAT_FORMAT_DIFF" ]; then echo "All source code in PR has proper copyright headers." exit 0 else echo "Found invalid copyright headers!" [ -n "$C_LIKE_FORMAT_DIFF" ] && echo "$C_LIKE_FORMAT_DIFF" [ -n "$SCRIPT_FORMAT_DIFF" ] && echo "$SCRIPT_FORMAT_DIFF" [ -n "$WINBAT_FORMAT_DIFF" ] && echo "$WINBAT_FORMAT_DIFF" exit 1 fi fheroes2-1.0.12+dfsg/script/tools/copyright_headers/000077500000000000000000000000001456075706000224105ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/script/tools/copyright_headers/full_header_c_like.txt000066400000000000000000000026701456075706000267360ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) {YR} * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ fheroes2-1.0.12+dfsg/script/tools/copyright_headers/full_header_script.txt000066400000000000000000000026441456075706000270150ustar00rootroot00000000000000########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) {YR} # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### fheroes2-1.0.12+dfsg/script/tools/copyright_headers/full_header_winbat.txt000066400000000000000000000027351456075706000267760ustar00rootroot00000000000000:: ########################################################################### :: # fheroes2: https://github.com/ihhub/fheroes2 # :: # Copyright (C) {YR} # :: # # :: # 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., # :: # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # :: ########################################################################### fheroes2-1.0.12+dfsg/script/tools/copyright_headers/header_template_c_like.txt000066400000000000000000000004641456075706000276060ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) {Y1} - {Y2} * * * fheroes2-1.0.12+dfsg/script/tools/copyright_headers/header_template_script.txt000066400000000000000000000004601456075706000276600ustar00rootroot00000000000000########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) {Y1} - {Y2} # # # fheroes2-1.0.12+dfsg/script/tools/copyright_headers/header_template_winbat.txt000066400000000000000000000004741456075706000276450ustar00rootroot00000000000000:: ########################################################################### :: # fheroes2: https://github.com/ihhub/fheroes2 # :: # Copyright (C) {Y1} - {Y2} # :: # # fheroes2-1.0.12+dfsg/sonar-project.properties000066400000000000000000000014261456075706000211500ustar00rootroot00000000000000sonar.projectKey=ihhub_fheroes2 sonar.organization=ihhub # This is the name and version displayed in the SonarCloud UI. sonar.projectName=fheroes2 # CI will automatically replace this with the contents of the version.txt file. sonar.projectVersion=%{version} # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. sonar.sources=. # Encoding of the source code. Default is default system encoding #sonar.sourceEncoding=UTF-8 # Properties specific to the C/C++ analyzer: sonar.cfamily.threads=2 sonar.links.homepage=https://github.com/ihhub/fheroes2 sonar.links.ci=https://github.com/ihhub/fheroes2/actions sonar.links.scm=https://github.com/ihhub/fheroes2 sonar.links.issue=https://github.com/ihhub/fheroes2/issues sonar.exclusions=src/thirdparty/**/* fheroes2-1.0.12+dfsg/src/000077500000000000000000000000001456075706000150305ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/CMakeLists.txt000066400000000000000000000035451456075706000175770ustar00rootroot00000000000000########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2022 - 2023 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### set( GNU_CC_WARN_OPTS -pedantic -Wall -Wextra -Wcast-align -Wextra-semi -Wfloat-conversion -Wfloat-equal -Winit-self -Wredundant-decls -Wshadow -Wundef -Wuninitialized -Wunused ) set( GNU_CXX_WARN_OPTS ${GNU_CC_WARN_OPTS} -Wctor-dtor-privacy -Woverloaded-virtual ) set( MSVC_CC_WARN_OPTS /W4 ) add_subdirectory(thirdparty) add_subdirectory(engine) add_subdirectory(fheroes2) if(ENABLE_TOOLS) add_subdirectory(tools) endif(ENABLE_TOOLS) fheroes2-1.0.12+dfsg/src/Makefile000066400000000000000000000114131456075706000164700ustar00rootroot00000000000000########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2021 - 2024 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### TARGET := fheroes2 # # Common build options for fheroes2 and third-party libraries # # Common flags for both C and C++ compilers CCFLAGS := -fsigned-char -pthread # Flags for the C compiler CFLAGS := $(CFLAGS) # Flags for the C++ compiler CXXFLAGS := $(CXXFLAGS) # Flags for the preprocessor CPPFLAGS := $(CPPFLAGS) # Flags for the linker LDFLAGS := $(LDFLAGS) -pthread # Flags for additional libraries LDLIBS := $(LDLIBS) ifdef FHEROES2_WITH_DEBUG CCFLAGS := $(CCFLAGS) -O0 -g else CCFLAGS := $(CCFLAGS) -O3 endif ifneq ($(or $(FHEROES2_WITH_ASAN),$(FHEROES2_WITH_TSAN)),) SANITIZERS := undefined ifdef FHEROES2_WITH_ASAN SANITIZERS := $(SANITIZERS),address endif ifdef FHEROES2_WITH_TSAN SANITIZERS := $(SANITIZERS),thread endif CCFLAGS := $(CCFLAGS) -fsanitize=$(SANITIZERS) LDFLAGS := $(LDFLAGS) -fsanitize=$(SANITIZERS) endif # # Platform-specific build options # ifndef PLATFORM ifndef OS OS := $(shell uname) endif ifeq ($(OS),FreeBSD) PLATFORM := bsd endif ifeq ($(OS),Darwin) PLATFORM := osx endif ifeq ($(OS),Linux) PLATFORM := all endif ifeq ($(OS),Haiku) PLATFORM := all endif endif include Makefile.$(PLATFORM) # # Build options for third-party libraries # CCFLAGS_TP := $(CCFLAGS) CFLAGS_TP := $(CFLAGS) CXXFLAGS_TP := $(CXXFLAGS) CPPFLAGS_TP := $(CPPFLAGS) # # Build options for fheroes2 # # *FLAGS_FH2 can be passed from platform-specific Makefiles CCFLAGS := $(CCFLAGS) $(CCFLAGS_FH2) CFLAGS := $(CFLAGS) $(CFLAGS_FH2) CXXFLAGS := $(CXXFLAGS) $(CXXFLAGS_FH2) -std=c++17 CPPFLAGS := $(CPPFLAGS) $(CPPFLAGS_FH2) LIBS := $(LIBS) -lz $(LDLIBS) ifdef FHEROES2_WITH_DEBUG CCFLAGS := $(CCFLAGS) -DWITH_DEBUG endif ifdef FHEROES2_WITH_IMAGE CCFLAGS := $(CCFLAGS) -DWITH_IMAGE endif ifdef FHEROES2_DATA CCFLAGS := $(CCFLAGS) -DFHEROES2_DATA="$(FHEROES2_DATA)" endif # TODO: Add -Wconversion -Wsign-conversion flags once we fix all the corresponding code smells # TODO: Add -Wdouble-promotion -Wold-style-cast -Wswitch-default flags once SDL headers can be compiled with them CCWARNOPTS := -pedantic -Wall -Wextra -Wcast-align -Wextra-semi -Wfloat-conversion -Wfloat-equal \ -Winit-self -Wredundant-decls -Wshadow -Wundef -Wuninitialized -Wunused CFLAGS := $(CFLAGS) $(CCWARNOPTS) CXXFLAGS := $(CXXFLAGS) $(CCWARNOPTS) -Wctor-dtor-privacy -Woverloaded-virtual ifdef FHEROES2_STRICT_COMPILATION CCFLAGS := $(CCFLAGS) -Werror endif # # SDL-related build options # # SDL_FLAGS can already be defined in a platform-specific Makefile ifeq ($(origin SDL_FLAGS),undefined) SDL_FLAGS := $(shell sdl2-config --cflags) ifdef FHEROES2_WITH_IMAGE SDL_FLAGS := $(SDL_FLAGS) $(shell libpng-config --cflags) endif endif # SDL_LIBS can already be defined in a platform-specific Makefile ifeq ($(origin SDL_LIBS),undefined) SDL_LIBS := -lSDL2_mixer $(shell sdl2-config --libs) ifdef FHEROES2_WITH_IMAGE SDL_LIBS := $(SDL_LIBS) -lSDL2_image $(shell libpng-config --libs) endif endif CCFLAGS := $(CCFLAGS) $(SDL_FLAGS) LIBS := $(SDL_LIBS) $(LIBS) export CC CXX AR CCFLAGS CFLAGS CXXFLAGS CPPFLAGS LDFLAGS LIBS PLATFORM .PHONY: all clean all: $(MAKE) -C thirdparty/libsmacker CCFLAGS="$(CCFLAGS_TP)" CFLAGS="$(CFLAGS_TP)" CXXFLAGS="$(CXXFLAGS_TP)" CPPFLAGS="$(CPPFLAGS_TP)" $(MAKE) -C engine $(MAKE) -C dist ifdef FHEROES2_WITH_TOOLS $(MAKE) -C tools endif $(MAKE) -C dist pot clean: $(MAKE) -C thirdparty/libsmacker clean $(MAKE) -C tools clean $(MAKE) -C dist clean $(MAKE) -C engine clean fheroes2-1.0.12+dfsg/src/Makefile.all000066400000000000000000000027011456075706000172370ustar00rootroot00000000000000########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2022 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### ifndef CXX CXX := g++ endif fheroes2-1.0.12+dfsg/src/Makefile.bsd000066400000000000000000000027401456075706000172420ustar00rootroot00000000000000########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2022 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### AR := ar CXX := g++ LDFLAGS := $(LDFLAGS) -L/usr/local/lib fheroes2-1.0.12+dfsg/src/Makefile.osx000066400000000000000000000031131456075706000172760ustar00rootroot00000000000000########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2022 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### AR := ar ifndef CXX CXX := g++ endif ifdef FHEROES2_MACOS_APP_BUNDLE CCFLAGS := $(CCFLAGS) -DMACOS_APP_BUNDLE LDFLAGS := $(LDFLAGS) -framework CoreFoundation endif fheroes2-1.0.12+dfsg/src/Makefile.switch000066400000000000000000000036521456075706000177760ustar00rootroot00000000000000########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2022 - 2023 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### include $(DEVKITPRO)/libnx/switch_rules # Common flags (for both C and C++ compilers) for both third-party libraries and fheroes2 CCFLAGS := $(CCFLAGS) $(shell sdl2-config --cflags) -fPIE LDFLAGS := $(LDFLAGS) -specs=$(DEVKITPRO)/libnx/switch.specs # Common flags (for both C and C++ compilers) for fheroes2 only CCFLAGS_FH2 := -DTARGET_NINTENDO_SWITCH LIBS := $(LIBS) -lvorbisidec -lmodplug -lmpg123 -lopusfile -lopus -logg # Prevent sdl2-config --cflags from running once again in the main Makefile SDL_FLAGS := fheroes2-1.0.12+dfsg/src/Makefile.vita000066400000000000000000000047171456075706000174430ustar00rootroot00000000000000########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2021 - 2023 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### AR := arm-vita-eabi-gcc-ar CC := arm-vita-eabi-gcc CXX := arm-vita-eabi-g++ # Common flags (for both C and C++ compilers) for both third-party libraries and fheroes2 CCFLAGS := $(CCFLAGS) -I$(VITASDK)/arm-vita-eabi/include -mcpu=cortex-a9 -mfpu=neon -fgraphite-identity -floop-nest-optimize -fno-lto -fno-tree-slp-vectorize LDFLAGS := $(LDFLAGS) -L$(VITASDK)/arm-vita-eabi/lib -L$(VITASDK)/lib -Wl,--sort-common -Wl,--whole-archive -Wl,--no-whole-archive -Wl,-q # Common flags (for both C and C++ compilers) for fheroes2 only CCFLAGS_FH2 := -DTARGET_PS_VITA SDL_FLAGS := -I$(VITASDK)/arm-vita-eabi/include/SDL2 SDL_LIBS := -L$(VITASDK)/arm-vita-eabi/lib -lSDL2_mixer -lSDL2 -lvita2d -lScePower_stub -lSceAudioIn_stub -lSceAudio_stub -lSceCtrl_stub -lSceHid_stub -lSceTouch_stub \ -lSceDisplay_stub -lSceGxm_stub -lSceAppMgr_stub -lSceSysmodule_stub -lSceCommonDialog_stub -lSceMotion_stub -lFLAC -lmikmod \ -lmodplug -lmpg123 -lvorbisfile -lvorbis -lopusfile -lopus -logg -lxmp fheroes2-1.0.12+dfsg/src/dist/000077500000000000000000000000001456075706000157735ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/dist/Makefile000066400000000000000000000045731456075706000174440ustar00rootroot00000000000000########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2021 - 2024 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### TARGET := fheroes2 LIBENGINE := ../engine/libengine.a CCFLAGS := $(CCFLAGS) -I../engine LIBENGINE := $(LIBENGINE) ../thirdparty/libsmacker/libsmacker.a CCFLAGS := $(CCFLAGS) -I../thirdparty/libsmacker SOURCEROOT := ../fheroes2 SOURCEDIR := $(filter %/,$(wildcard $(SOURCEROOT)/*/)) $(filter %/,$(wildcard $(SOURCEROOT)/*/*/)) POT := $(TARGET).pot SEARCH := $(wildcard $(SOURCEROOT)/*/*.cpp) $(wildcard $(SOURCEROOT)/*/*/*.cpp) .PHONY: all clean pot all: $(TARGET) $(TARGET): $(notdir $(patsubst %.cpp, %.o, $(SEARCH))) $(LIBENGINE) @echo "lnk: $@" $(CXX) -o $@ $^ $(LIBS) $(LDFLAGS) pot: $(wildcard $(SEARCH)) @echo "gen: $(POT)" @xgettext -d $(TARGET) -C -F -k_ -k_n:1,2 -o $(POT) $(sort $(wildcard $(SEARCH))) @sed -i~ -e 's/, c-format//' $(POT) VPATH := $(SOURCEDIR) %.o: %.cpp $(CXX) -c -MD $(addprefix -I, $(SOURCEDIR)) $< $(CCFLAGS) $(CXXFLAGS) $(CPPFLAGS) include $(wildcard *.d) clean: rm -f *.pot *.pot~ *.o *.d *.exe $(TARGET) rm -rf *.app fheroes2-1.0.12+dfsg/src/engine/000077500000000000000000000000001456075706000162755ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/engine/CMakeLists.txt000066400000000000000000000054211456075706000210370ustar00rootroot00000000000000########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2022 - 2023 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### file(GLOB_RECURSE LIBENGINE_SOURCES CONFIGURE_DEPENDS *.cpp) add_compile_options("$<$:${GNU_CC_WARN_OPTS}>") add_compile_options("$<$:${GNU_CXX_WARN_OPTS}>") add_compile_options("$<$,$>:${MSVC_CC_WARN_OPTS}>") if(ENABLE_STRICT_COMPILATION) add_compile_options($<$,$>:-Werror>) add_compile_options($<$,$>:/WX>) endif(ENABLE_STRICT_COMPILATION) add_library(engine STATIC ${LIBENGINE_SOURCES}) target_compile_definitions( engine PRIVATE # MSVC: suppress deprecation warnings $<$,$>:_CRT_SECURE_NO_WARNINGS> $<$:WITH_DEBUG> $<$:WITH_IMAGE> $<$:MACOS_APP_BUNDLE> ) target_include_directories( engine PUBLIC $ ) target_link_libraries( engine smacker ${USE_SDL_VERSION}::${USE_SDL_VERSION} ${USE_SDL_VERSION}_mixer::${USE_SDL_VERSION}_mixer $<$:${USE_SDL_VERSION}_image::${USE_SDL_VERSION}_image> $<$:PNG::PNG> Threads::Threads ZLIB::ZLIB ) fheroes2-1.0.12+dfsg/src/engine/Makefile000066400000000000000000000033251456075706000177400ustar00rootroot00000000000000########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2021 - 2022 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### TARGET := libengine CCFLAGS := $(CCFLAGS) -I../thirdparty/libsmacker .PHONY: all clean all: $(TARGET).a $(TARGET).a: $(patsubst %.cpp, %.o, $(wildcard *.cpp)) $(AR) crvs $@ $^ %.o: %.cpp $(CXX) -c -MD $< $(CCFLAGS) $(CXXFLAGS) $(CPPFLAGS) include $(wildcard *.d) clean: rm -f *.a *.so *.d *.o fheroes2-1.0.12+dfsg/src/engine/agg_file.cpp000066400000000000000000000066051456075706000205450ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include "agg_file.h" namespace fheroes2 { bool AGGFile::open( const std::string & fileName ) { if ( !_stream.open( fileName, "rb" ) ) return false; const size_t size = _stream.size(); const size_t count = _stream.getLE16(); const size_t fileRecordSize = sizeof( uint32_t ) * 3; if ( count * ( fileRecordSize + _maxFilenameSize ) >= size ) return false; StreamBuf fileEntries = _stream.toStreamBuf( count * fileRecordSize ); const size_t nameEntriesSize = _maxFilenameSize * count; _stream.seek( size - nameEntriesSize ); StreamBuf nameEntries = _stream.toStreamBuf( nameEntriesSize ); for ( size_t i = 0; i < count; ++i ) { std::string name = nameEntries.toString( _maxFilenameSize ); fileEntries.getLE32(); // skip CRC (?) part const uint32_t fileOffset = fileEntries.getLE32(); const uint32_t fileSize = fileEntries.getLE32(); _files.try_emplace( std::move( name ), std::make_pair( fileSize, fileOffset ) ); } if ( _files.size() != count ) { _files.clear(); return false; } return !_stream.fail(); } std::vector AGGFile::read( const std::string & fileName ) { auto it = _files.find( fileName ); if ( it != _files.end() ) { const auto & fileParams = it->second; if ( fileParams.first > 0 ) { _stream.seek( fileParams.second ); return _stream.getRaw( fileParams.first ); } } return std::vector(); } } StreamBase & operator>>( StreamBase & st, fheroes2::ICNHeader & icn ) { icn.offsetX = st.getLE16(); icn.offsetY = st.getLE16(); icn.width = st.getLE16(); icn.height = st.getLE16(); icn.animationFrames = st.get(); icn.offsetData = st.getLE32(); return st; } fheroes2-1.0.12+dfsg/src/engine/agg_file.h000066400000000000000000000051731456075706000202110ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef AGG_FILE_H #define AGG_FILE_H #include #include #include #include #include #include #include "serialize.h" namespace fheroes2 { class AGGFile { public: bool isGood() const { return !_stream.fail() && !_files.empty(); } bool open( const std::string & fileName ); std::vector read( const std::string & fileName ); private: static const size_t _maxFilenameSize = 15; // 8.3 ASCIIZ file name + 2-bytes padding StreamFile _stream; std::map> _files; }; struct ICNHeader { ICNHeader() : offsetX( 0 ) , offsetY( 0 ) , width( 0 ) , height( 0 ) , animationFrames( 0 ) , offsetData( 0 ) {} int16_t offsetX; int16_t offsetY; uint16_t width; uint16_t height; uint8_t animationFrames; // used for adventure map animations, this can replace ICN::AnimationFrame uint32_t offsetData; }; } StreamBase & operator>>( StreamBase & st, fheroes2::ICNHeader & icn ); #endif fheroes2-1.0.12+dfsg/src/engine/audio.cpp000066400000000000000000001032511456075706000201040ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2008 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "audio.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "dir.h" #include "logging.h" #include "system.h" #include "thread.h" #include "timing.h" namespace { struct AudioSpec { #if defined( TARGET_PS_VITA ) // Notice: The PS Vita sound resampler is CPU intensive if the value is not 22050. int frequency = 22050; #else // Notice: Value 22050 causes music distortion on Windows. int frequency = 44100; #endif uint16_t format = AUDIO_S16; // Stereo audio support int channels = 2; #if defined( ANDROID ) // Value greater than 1024 causes audio distortion on Android int chunkSize = 1024; #else int chunkSize = 2048; #endif }; std::atomic isInitialized{ false }; bool isMuted = false; int musicFadeInMs{ 0 }; std::vector savedMixerVolumes; int savedMusicVolume = 0; std::atomic mixerChannelCount{ 0 }; // This mutex protects all operations with audio. In order to avoid deadlocks, it shouldn't // be acquired in any callback functions that can be called by SDL_Mixer. std::recursive_mutex audioMutex; class SoundSampleManager { public: SoundSampleManager() = default; SoundSampleManager( const SoundSampleManager & ) = delete; ~SoundSampleManager() { // Make sure that all sound samples have been eventually freed assert( std::all_of( _channelSamples.begin(), _channelSamples.end(), []( const auto & item ) { static const decltype( item.second ) nullQueue{ nullptr, nullptr }; return item.second == nullQueue; } ) ); } SoundSampleManager & operator=( const SoundSampleManager & ) = delete; void channelStarted( const int channelId, Mix_Chunk * sample ) { assert( channelId >= 0 && sample != nullptr ); const auto iter = _channelSamples.find( channelId ); if ( iter != _channelSamples.end() ) { auto & sampleQueue = iter->second; if ( sampleQueue.first == nullptr ) { sampleQueue.first = sample; } else if ( sampleQueue.second == nullptr ) { sampleQueue.second = sample; } else { // The sample queue is already full, this shouldn't happen assert( 0 ); } return; } const auto res = _channelSamples.try_emplace( channelId, std::make_pair( sample, nullptr ) ); if ( !res.second ) { assert( 0 ); } } // This method can be called from the SDL_Mixer callback (without acquiring the audioMutex) void channelFinished( const int channelId ) { assert( channelId >= 0 ); const std::scoped_lock lock( _channelsToCleanupMutex ); _channelsToCleanup.push_back( channelId ); } void clearFinishedSamples() { std::vector channelsToCleanup; { const std::scoped_lock lock( _channelsToCleanupMutex ); std::swap( channelsToCleanup, _channelsToCleanup ); } for ( const int channel : channelsToCleanup ) { const auto iter = _channelSamples.find( channel ); assert( iter != _channelSamples.end() ); auto & sampleQueue = iter->second; assert( sampleQueue.first != nullptr ); Mix_FreeChunk( sampleQueue.first ); // Shift the sample queue sampleQueue.first = sampleQueue.second; sampleQueue.second = nullptr; } } private: std::map> _channelSamples; std::vector _channelsToCleanup; // This mutex protects operations with _channelsToCleanup std::mutex _channelsToCleanupMutex; }; SoundSampleManager soundSampleManager; // This is the callback function set by Mix_ChannelFinished(). As a rule, it is called from // a SDL_Mixer internal thread. Calls of any SDL_Mixer functions are not allowed in callbacks. void SDLCALL channelFinished( const int channelId ) { // This callback function should never be called if audio is not initialized assert( isInitialized ); soundSampleManager.channelFinished( channelId ); } int playSound( const uint8_t * ptr, const uint32_t size, const int channelId, const bool loop ) { assert( ptr != nullptr && size != 0 ); soundSampleManager.clearFinishedSamples(); SDL_RWops * rwops = SDL_RWFromConstMem( ptr, size ); if ( rwops == nullptr ) { ERROR_LOG( "Failed to create an audio chunk from memory. The error: " << SDL_GetError() ) return -1; } Mix_Chunk * sample = Mix_LoadWAV_RW( rwops, 1 ); if ( sample == nullptr ) { ERROR_LOG( "Failed to create an audio chunk from memory. The error: " << Mix_GetError() ) return -1; } const int channel = Mix_PlayChannel( channelId, sample, loop ? -1 : 0 ); if ( channel < 0 ) { ERROR_LOG( "Failed to play an audio chunk for channel " << channelId << ". The error: " << Mix_GetError() ) Mix_FreeChunk( sample ); return channel; } // There can be a maximum of two items in the sample queue for a channel: // the previous sample (if it hasn't been freed yet) and the current one soundSampleManager.channelStarted( channel, sample ); return channel; } void addSoundEffect( const int channelId, const int16_t angle, uint8_t volumePercentage ) { if ( volumePercentage > 100 ) { volumePercentage = 100; } if ( Mix_SetPosition( channelId, angle, 255 * ( volumePercentage - 100 ) ) == 0 ) { ERROR_LOG( "Failed to apply a sound effect for channel " << channelId << ". The error: " << Mix_GetError() ) } } class MusicInfo { public: explicit MusicInfo( std::vector v ) : _source( std::move( v ) ) { // Do nothing } explicit MusicInfo( std::string file ) : _source( std::move( file ) ) { // Do nothing } MusicInfo( const MusicInfo & ) = delete; ~MusicInfo() = default; MusicInfo & operator=( const MusicInfo & ) = delete; // Mix_Music objects should never be cached because they store the state of the music decoder's backend, // and should be passed to functions like Mix_PlayMusic() only once, otherwise weird things can happen. Mix_Music * createMusic() const { Mix_Music * result = nullptr; if ( std::holds_alternative>( _source ) ) { const std::vector & v = std::get>( _source ); SDL_RWops * rwops = SDL_RWFromConstMem( &v[0], static_cast( v.size() ) ); if ( rwops == nullptr ) { ERROR_LOG( "Failed to create a music track from memory. The error: " << SDL_GetError() ) } else { result = Mix_LoadMUS_RW( rwops, 0 ); if ( result == nullptr ) { ERROR_LOG( "Failed to create a music track from memory. The error: " << Mix_GetError() ) } SDL_FreeRW( rwops ); } } else if ( std::holds_alternative( _source ) ) { const std::string & file = std::get( _source ); result = Mix_LoadMUS( System::FileNameToUTF8( file ).c_str() ); if ( result == nullptr ) { ERROR_LOG( "Failed to create a music track from file " << file << ". The error: " << Mix_GetError() ) } } else { assert( 0 ); } return result; } double getPosition() const { return _position; } void setPosition( const double pos ) { _position = pos; } private: const std::variant, std::string> _source; double _position{ 0 }; }; class MusicTrackManager { public: MusicTrackManager() = default; MusicTrackManager( const MusicTrackManager & ) = delete; ~MusicTrackManager() { // Make sure that all music tracks have been eventually freed assert( _musicQueue == nullptr ); } MusicTrackManager & operator=( const MusicTrackManager & ) = delete; bool isTrackInMusicDB( const uint64_t musicUID ) const { return ( _musicDB.find( musicUID ) != _musicDB.end() ); } std::shared_ptr getTrackFromMusicDB( const uint64_t musicUID ) const { const auto iter = _musicDB.find( musicUID ); assert( iter != _musicDB.end() ); return iter->second; } void addTrackToMusicDB( const uint64_t musicUID, const std::shared_ptr & track ) { const auto res = _musicDB.try_emplace( musicUID, track ); if ( !res.second ) { assert( 0 ); } } void clearMusicDB() { _musicDB.clear(); } std::weak_ptr getCurrentTrack() const { return _currentTrack; } uint64_t getCurrentTrackUID() const { return _currentTrackUID; } Music::PlaybackMode getCurrentTrackPlaybackMode() const { return _currentTrackPlaybackMode; } // This method can be called from the SDL_Mixer callback (without acquiring the audioMutex) uint64_t getCurrentTrackChangeCounter() const { return _currentTrackChangeCounter; } double getCurrentTrackPosition() const { return _currentTrackTimer.getS(); } void updateCurrentTrack( const uint64_t musicUID, const Music::PlaybackMode trackPlaybackMode ) { _currentTrack = getTrackFromMusicDB( musicUID ); _currentTrackUID = musicUID; _currentTrackPlaybackMode = trackPlaybackMode; ++_currentTrackChangeCounter; } void resetCurrentTrack() { _currentTrack = {}; _currentTrackUID = 0; _currentTrackPlaybackMode = Music::PlaybackMode::PLAY_ONCE; ++_currentTrackChangeCounter; } void resetTimer() { _currentTrackTimer.reset(); } void musicStarted( Mix_Music * mus ) { // If the _musicQueue is not nullptr, then the music queue (consisting of only one music // track) is already full, this shouldn't happen assert( _musicQueue == nullptr ); _musicQueue = mus; } void clearFinishedMusic() { // This queue consists of only one music track if ( _musicQueue == nullptr ) { return; } Mix_FreeMusic( _musicQueue ); _musicQueue = nullptr; } private: std::map> _musicDB; std::weak_ptr _currentTrack; uint64_t _currentTrackUID{ 0 }; Music::PlaybackMode _currentTrackPlaybackMode{ Music::PlaybackMode::PLAY_ONCE }; // This counter should be incremented every time the current track or its playback mode changes std::atomic _currentTrackChangeCounter{ 0 }; fheroes2::Time _currentTrackTimer; Mix_Music * _musicQueue{ nullptr }; }; MusicTrackManager musicTrackManager; void playMusic( const uint64_t musicUID, Music::PlaybackMode playbackMode ); class MusicRestartManager final : public MultiThreading::AsyncManager { public: void restartCurrentMusicTrack() { const std::scoped_lock lock( _mutex ); _trackChangeCounter = musicTrackManager.getCurrentTrackChangeCounter(); notifyWorker(); } private: // This method is called by the worker thread and is protected by _mutex bool prepareTask() override { // Make a copy for the worker thread to ensure that this counter will // not be changed by another thread in the middle of executeTask() _taskTrackChangeCounter = _trackChangeCounter; return false; } // This method is called by the worker thread, but is not protected by _mutex void executeTask() override { const std::scoped_lock lock( audioMutex ); if ( !isInitialized ) { return; } // The current track managed to change during the start of this task if ( _taskTrackChangeCounter != musicTrackManager.getCurrentTrackChangeCounter() ) { return; } // REWIND_AND_PLAY_INFINITE should be handled by the SDL_Mixer itself if ( musicTrackManager.getCurrentTrackPlaybackMode() != Music::PlaybackMode::RESUME_AND_PLAY_INFINITE ) { return; } const std::shared_ptr currentTrack = musicTrackManager.getCurrentTrack().lock(); assert( currentTrack ); currentTrack->setPosition( 0 ); playMusic( musicTrackManager.getCurrentTrackUID(), musicTrackManager.getCurrentTrackPlaybackMode() ); } // This variable can be accessed by multiple threads and it is protected by _mutex uint64_t _trackChangeCounter{ 0 }; // This variable can be accessed only by the worker thread uint64_t _taskTrackChangeCounter{ 0 }; }; MusicRestartManager musicRestartManager; // This is the callback function set by Mix_HookMusicFinished(). As a rule, it is called from // a SDL_Mixer internal thread. Calls of any SDL_Mixer functions are not allowed in callbacks. void SDLCALL musicFinished() { // This callback function should never be called if audio is not initialized assert( isInitialized ); musicRestartManager.restartCurrentMusicTrack(); } bool isMusicResumeSupported( const Mix_Music * mus ) { assert( mus != nullptr ); const Mix_MusicType musicType = Mix_GetMusicType( mus ); return ( musicType == Mix_MusicType::MUS_OGG ) || ( musicType == Mix_MusicType::MUS_MP3 ) || ( musicType == Mix_MusicType::MUS_FLAC ); } void playMusic( const uint64_t musicUID, Music::PlaybackMode playbackMode ) { // This function should never be called if a music track is currently playing. // Thus we have a guarantee that the Mix_HookMusicFinished()'s callback will // not be called while we are modifying the current track information. assert( !Music::isPlaying() ); musicTrackManager.clearFinishedMusic(); const std::shared_ptr track = musicTrackManager.getTrackFromMusicDB( musicUID ); assert( track ); Mix_Music * mus = track->createMusic(); if ( mus == nullptr ) { musicTrackManager.resetCurrentTrack(); return; } bool resumePlayback = false; bool autoLoop = false; if ( playbackMode == Music::PlaybackMode::RESUME_AND_PLAY_INFINITE ) { if ( isMusicResumeSupported( mus ) ) { resumePlayback = true; } else { // It is impossible to resume this track, let's reflect it by changing the playback mode playbackMode = Music::PlaybackMode::REWIND_AND_PLAY_INFINITE; autoLoop = true; } } else if ( playbackMode == Music::PlaybackMode::REWIND_AND_PLAY_INFINITE ) { autoLoop = true; } // Update the current track information while the music playback is not yet started, so the // Mix_HookMusicFinished()'s callback cannot be called musicTrackManager.updateCurrentTrack( musicUID, playbackMode ); const int loopCount = autoLoop ? -1 : 0; int returnCode = -1; // Resume the music only if at least 1 second of the track has been played. if ( resumePlayback && track->getPosition() > 1 ) { returnCode = Mix_FadeInMusicPos( mus, loopCount, musicFadeInMs, track->getPosition() ); if ( returnCode != 0 ) { ERROR_LOG( "Failed to resume a music track. The error: " << Mix_GetError() ) } } // Either there is no need to resume music playback, or the resumption failed. Let's try to // start the playback from the beginning. if ( returnCode != 0 ) { returnCode = Mix_FadeInMusic( mus, loopCount, musicFadeInMs ); if ( returnCode != 0 ) { ERROR_LOG( "Failed to play a music track. The error: " << Mix_GetError() ) } } if ( returnCode != 0 ) { Mix_FreeMusic( mus ); // Since the music playback failed, the Mix_HookMusicFinished()'s callback cannot be called // here, so we can safely reset the current track information musicTrackManager.resetCurrentTrack(); return; } // For better accuracy reset the timer right after the actual playback starts musicTrackManager.resetTimer(); // There can be no more than one element in the music queue - the current track, the previous // one should already be freed musicTrackManager.musicStarted( mus ); } // By the Weber-Fechner law, humans subjective sound sensation is proportional logarithm of sound intensity. // So for linear changing sound intensity we have to change the volume exponential. // There is a good explanation at https://www.dr-lex.be/info-stuff/volumecontrols.html. // This function maps sound volumes in percents to SDL units with values [0..MIX_MAX_VOLUME] by exponential law. int normalizeToSDLVolume( const int volumePercentage ) { if ( volumePercentage < 0 ) { // Why are you passing a negative volume value? assert( 0 ); return 0; } if ( volumePercentage >= 100 ) { // Reserve an extra 0.5 dB for possible sound overloads in SDL_mixer, multiplying max volume by 50/53. return MIX_MAX_VOLUME * 50 / 53; } // MIX_MAX_VOLUME is divided by 10.6, not 10 to reserve an extra 0.5 dB for possible sound overloads in SDL_mixer. return static_cast( ( std::exp( std::log( 10 + 1 ) * volumePercentage / 100 ) - 1 ) / 10.6 * MIX_MAX_VOLUME ); } } void Audio::Init() { const std::scoped_lock lock( audioMutex ); if ( isInitialized ) { // If this assertion blows up you are trying to initialize an already initialized system. assert( 0 ); return; } if ( !fheroes2::isComponentInitialized( fheroes2::SystemInitializationComponent::Audio ) ) { ERROR_LOG( "The audio subsystem was not initialized." ) return; } const int initializationFlags = MIX_INIT_FLAC | MIX_INIT_MP3 | MIX_INIT_OGG | MIX_INIT_MID; const int initializedFlags = Mix_Init( initializationFlags ); if ( ( initializedFlags & initializationFlags ) != initializationFlags ) { DEBUG_LOG( DBG_ENGINE, DBG_WARN, "Expected music initialization flags as " << initializationFlags << " but received " << ( initializedFlags & initializationFlags ) ) if ( ( initializedFlags & MIX_INIT_FLAC ) == 0 ) { DEBUG_LOG( DBG_ENGINE, DBG_WARN, "FLAC module failed to be initialized" ) } if ( ( initializedFlags & MIX_INIT_MP3 ) == 0 ) { DEBUG_LOG( DBG_ENGINE, DBG_WARN, "MP3 module failed to be initialized" ) } if ( ( initializedFlags & MIX_INIT_OGG ) == 0 ) { DEBUG_LOG( DBG_ENGINE, DBG_WARN, "OGG module failed to be initialized" ) } if ( ( initializedFlags & MIX_INIT_MID ) == 0 ) { DEBUG_LOG( DBG_ENGINE, DBG_WARN, "MID module failed to be initialized" ) } } const AudioSpec audioSpec; if ( Mix_OpenAudio( audioSpec.frequency, audioSpec.format, audioSpec.channels, audioSpec.chunkSize ) != 0 ) { ERROR_LOG( "Failed to initialize an audio device. The error: " << Mix_GetError() ) return; } // By default this value should be MIX_CHANNELS. mixerChannelCount = Mix_AllocateChannels( -1 ); int frequency = 0; uint16_t format = 0; int channels = 0; const int deviceInitCount = Mix_QuerySpec( &frequency, &format, &channels ); if ( deviceInitCount == 0 ) { ERROR_LOG( "Failed to query an audio device specs. The error: " << Mix_GetError() ) } if ( deviceInitCount != 1 ) { // The device must be opened only once. assert( 0 ); ERROR_LOG( "Trying to initialize an audio system that has been already initialized." ) } if ( audioSpec.frequency != frequency ) { // At least on Windows the standard frequency is 48000 Hz. Sounds in the game are 22500 Hz frequency. // However, resampling is done inside SDL so this is not exactly an error. DEBUG_LOG( DBG_ENGINE, DBG_WARN, "Audio frequency is initialized as " << frequency << " instead of " << audioSpec.frequency ) } if ( audioSpec.format != format ) { ERROR_LOG( "Audio format is initialized as " << format << " instead of " << audioSpec.format ) } if ( audioSpec.channels != channels ) { ERROR_LOG( "Number of audio channels is initialized as " << channels << " instead of " << audioSpec.channels ) } musicRestartManager.createWorker(); Mix_ChannelFinished( channelFinished ); Mix_HookMusicFinished( musicFinished ); isInitialized = true; } void Audio::Quit() { { const std::scoped_lock lock( audioMutex ); if ( !isInitialized ) { // Nothing to do. return; } if ( !fheroes2::isComponentInitialized( fheroes2::SystemInitializationComponent::Audio ) ) { // Something wrong with the logic! The component must be initialized. assert( 0 ); return; } Music::Stop(); Mixer::Stop(); Mix_ChannelFinished( nullptr ); Mix_HookMusicFinished( nullptr ); soundSampleManager.clearFinishedSamples(); musicTrackManager.clearFinishedMusic(); musicTrackManager.clearMusicDB(); Mix_CloseAudio(); Mix_Quit(); mixerChannelCount = 0; isInitialized = false; } // We can't hold the audioMutex here because if MusicRestartManager's working // thread is already waiting on it, then there will be a deadlock while waiting // for it to join. The Mix_HookMusicFinished()'s callback can no longer be called // at the moment because it has been already unregistered. musicRestartManager.stopWorker(); } void Audio::Mute() { const std::scoped_lock lock( audioMutex ); if ( isMuted || !isInitialized ) { return; } isMuted = true; const size_t channelsCount = static_cast( Mix_AllocateChannels( -1 ) ); savedMixerVolumes.resize( channelsCount ); for ( size_t channel = 0; channel < channelsCount; ++channel ) { savedMixerVolumes[channel] = Mix_Volume( static_cast( channel ), 0 ); } savedMusicVolume = Mix_VolumeMusic( 0 ); } void Audio::Unmute() { const std::scoped_lock lock( audioMutex ); if ( !isMuted || !isInitialized ) { return; } isMuted = false; const size_t channelsCount = std::min( static_cast( Mix_AllocateChannels( -1 ) ), savedMixerVolumes.size() ); for ( size_t channel = 0; channel < channelsCount; ++channel ) { Mix_Volume( static_cast( channel ), savedMixerVolumes[channel] ); } Mix_VolumeMusic( savedMusicVolume ); } bool Audio::isValid() { return isInitialized; } void Mixer::SetChannels( const int num ) { const std::scoped_lock lock( audioMutex ); if ( !isInitialized ) { return; } mixerChannelCount = Mix_AllocateChannels( num ); if ( num != mixerChannelCount ) { ERROR_LOG( "Failed to allocate the required amount of channels for sound. The required number of channels " << num << " but allocated only " << mixerChannelCount ) } if ( isMuted ) { savedMixerVolumes.resize( static_cast( mixerChannelCount ), MIX_MAX_VOLUME ); Mix_Volume( -1, 0 ); } // Just to verify that we are synced with SDL. assert( Mix_AllocateChannels( -1 ) == mixerChannelCount ); } int Mixer::getChannelCount() { return mixerChannelCount; } int Mixer::Play( const uint8_t * ptr, const uint32_t size, const int channelId, const bool loop ) { if ( ptr == nullptr || size == 0 ) { // You are trying to play an empty sound. Check your logic! assert( 0 ); return -1; } const std::scoped_lock lock( audioMutex ); if ( !isInitialized ) { return -1; } return playSound( ptr, size, channelId, loop ); } int Mixer::PlayFromDistance( const uint8_t * ptr, const uint32_t size, const int channelId, const bool loop, const int16_t angle, const uint8_t volumePercentage ) { if ( ptr == nullptr || size == 0 ) { // You are trying to play an empty sound. Check your logic! assert( 0 ); return -1; } const std::scoped_lock lock( audioMutex ); if ( !isInitialized ) { return -1; } const int channel = playSound( ptr, size, channelId, loop ); if ( channel < 0 ) { return channel; } addSoundEffect( channel, angle, volumePercentage ); return channel; } int Mixer::applySoundEffect( const int channelId, const int16_t angle, const uint8_t volumePercentage ) { const std::scoped_lock lock( audioMutex ); if ( !isInitialized ) { return -1; } addSoundEffect( channelId, angle, volumePercentage ); return channelId; } void Mixer::setVolume( const int channelId, const int volumePercentage ) { const int volume = normalizeToSDLVolume( volumePercentage ); const std::scoped_lock lock( audioMutex ); if ( !isInitialized ) { return; } if ( !isMuted ) { Mix_Volume( channelId, volume ); return; } if ( channelId < 0 ) { std::fill( savedMixerVolumes.begin(), savedMixerVolumes.end(), volume ); return; } const size_t channel = static_cast( channelId ); if ( channel < savedMixerVolumes.size() ) { savedMixerVolumes[channel] = volume; } } void Mixer::Pause( const int channelId /* = -1 */ ) { const std::scoped_lock lock( audioMutex ); if ( isInitialized ) { Mix_Pause( channelId ); } } void Mixer::Resume( const int channelId /* = -1 */ ) { const std::scoped_lock lock( audioMutex ); if ( isInitialized ) { Mix_Resume( channelId ); } } void Mixer::Stop( const int channelId /* = -1 */ ) { const std::scoped_lock lock( audioMutex ); if ( isInitialized ) { Mix_HaltChannel( channelId ); } } bool Mixer::isPlaying( const int channelId ) { const std::scoped_lock lock( audioMutex ); return isInitialized && Mix_Playing( channelId ) > 0; } bool Music::Play( const uint64_t musicUID, const PlaybackMode playbackMode ) { const std::scoped_lock lock( audioMutex ); if ( !isInitialized ) { return false; } if ( musicTrackManager.isTrackInMusicDB( musicUID ) ) { Stop(); playMusic( musicUID, playbackMode ); return true; } return false; } void Music::Play( const uint64_t musicUID, const std::vector & v, const PlaybackMode playbackMode ) { if ( v.empty() ) { return; } const std::scoped_lock lock( audioMutex ); if ( !isInitialized ) { return; } musicTrackManager.addTrackToMusicDB( musicUID, std::make_shared( v ) ); Stop(); playMusic( musicUID, playbackMode ); } void Music::Play( const uint64_t musicUID, const std::string & file, const PlaybackMode playbackMode ) { if ( file.empty() ) { // Nothing to play, the file name is empty. return; } const std::scoped_lock lock( audioMutex ); if ( !isInitialized ) { return; } musicTrackManager.addTrackToMusicDB( musicUID, std::make_shared( file ) ); Stop(); playMusic( musicUID, playbackMode ); } void Music::SetFadeInMs( const int timeMs ) { if ( timeMs < 0 ) { // Why are you even setting a negative value? assert( 0 ); return; } const std::scoped_lock lock( audioMutex ); musicFadeInMs = timeMs; } void Music::setVolume( const int volumePercentage ) { const int volume = normalizeToSDLVolume( volumePercentage ); const std::scoped_lock lock( audioMutex ); if ( !isInitialized ) { return; } if ( isMuted ) { savedMusicVolume = volume; return; } Mix_VolumeMusic( volume ); } void Music::Stop() { const std::scoped_lock lock( audioMutex ); if ( !isInitialized ) { return; } if ( musicTrackManager.getCurrentTrack().expired() ) { // Nothing to do. return; } // Always returns 0. After this call we have a guarantee that the Mix_HookMusicFinished()'s // callback will not be called while we are modifying the current track information. Mix_HaltMusic(); const std::shared_ptr currentTrack = musicTrackManager.getCurrentTrack().lock(); assert( currentTrack ); // We can and should reliably calculate the current playback position, let's remember it if ( musicTrackManager.getCurrentTrackPlaybackMode() == PlaybackMode::RESUME_AND_PLAY_INFINITE ) { currentTrack->setPosition( currentTrack->getPosition() + musicTrackManager.getCurrentTrackPosition() ); } // We either shouldn't (PLAY_ONCE) or can't (REWIND_AND_PLAY_INFINITE) reliably calculate the // current playback position, let's reset it to zero else { currentTrack->setPosition( 0 ); } musicTrackManager.resetCurrentTrack(); } bool Music::isPlaying() { const std::scoped_lock lock( audioMutex ); return !musicTrackManager.getCurrentTrack().expired() && Mix_PlayingMusic(); } void Music::SetMidiSoundFonts( const ListFiles & files ) { const std::scoped_lock lock( audioMutex ); if ( !isInitialized ) { return; } std::string filePaths; for ( const std::string & file : files ) { filePaths.append( file ); filePaths.push_back( ';' ); } // Remove the last semicolon if ( !filePaths.empty() ) { assert( filePaths.back() == ';' ); filePaths.pop_back(); } if ( Mix_SetSoundFonts( System::FileNameToUTF8( filePaths ).c_str() ) == 0 ) { ERROR_LOG( "Failed to set MIDI SoundFonts using paths " << filePaths << ". The error: " << Mix_GetError() ) } } fheroes2-1.0.12+dfsg/src/engine/audio.h000066400000000000000000000116621456075706000175550ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2008 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2AUDIO_H #define H2AUDIO_H #include #include #include struct ListFiles; namespace Audio { // Audio initialization and deinitialization functions are not designed to be called // concurrently from different threads. They should be called from the main thread only. void Init(); void Quit(); void Mute(); void Unmute(); bool isValid(); } namespace Mixer { void SetChannels( const int num ); int getChannelCount(); // To play the audio in a new channel set its value to -1. Returns channel ID. A negative value (-1) in case of failure. int Play( const uint8_t * ptr, const uint32_t size, const int channelId, const bool loop ); int PlayFromDistance( const uint8_t * ptr, const uint32_t size, const int channelId, const bool loop, const int16_t angle, const uint8_t volumePercentage ); int applySoundEffect( const int channelId, const int16_t angle, const uint8_t volumePercentage ); void setVolume( const int channelId, const int volumePercentage ); void Pause( const int channelId = -1 ); void Resume( const int channelId = -1 ); void Stop( const int channelId = -1 ); bool isPlaying( const int channelId ); } namespace Music { enum class PlaybackMode : uint8_t { // Play a music track once from the beginning, do not remember the position at which // playback of this track was stopped (rewind it to the beginning when playback stops) PLAY_ONCE, // Play a music track in an endless loop, starting from its previously remembered position // (or from the beginning if this track is being played for the first time) and remember // the position at which playback of this track was stopped RESUME_AND_PLAY_INFINITE, // Play a music track in an endless loop, starting from the beginning, do not remember // the position at which playback of this track was stopped (rewind it to the beginning // when playback stops) REWIND_AND_PLAY_INFINITE }; // Music UID is used to store metadata of music tracks in the music database. It is caller's // responsibility to generate them. This function searches for a music track with the specified // UID in the database and starts playback if it is found. Returns true if the music track was // found in the database, otherwise returns false. bool Play( const uint64_t musicUID, const PlaybackMode playbackMode ); // Adds a music track from the memory buffer to the music database and starts playback. A music // track with the specified UID should not already be present in the database. void Play( const uint64_t musicUID, const std::vector & v, const PlaybackMode playbackMode ); // Adds the music track available in the specified file to the music database and starts playback. // A music track with the specified UID should not already be present in the database. void Play( const uint64_t musicUID, const std::string & file, const PlaybackMode playbackMode ); void setVolume( const int volumePercentage ); void SetFadeInMs( const int timeMs ); void Stop(); bool isPlaying(); void SetMidiSoundFonts( const ListFiles & files ); std::vector Xmi2Mid( const std::vector & buf ); } #endif fheroes2-1.0.12+dfsg/src/engine/core.cpp000066400000000000000000000130121456075706000177260ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "core.h" #include #include #include #include #include #include "audio.h" #include "localevent.h" #include "logging.h" #if defined( TARGET_PS_VITA ) #include #include // PlayStation Vita requires definition of such global variable for memory usage. int _newlib_heap_size_user = 192 * 1024 * 1024; #endif namespace { #if defined( TARGET_PS_VITA ) void initHardwareInternally() { // CPU clock speed, MHz. Possible values: 41, 83, 111, 166, 222, 333, 444, 500 scePowerSetArmClockFrequency( 444 ); // CPU memory clock speed, MHz. Possible values: 55, 83, 111, 166, 222 scePowerSetBusClockFrequency( 222 ); // GPU clock speed, MHz. Possible values: 41, 55, 83, 111, 166, 222 scePowerSetGpuClockFrequency( 222 ); // GPU memory clock speed, MHz. Possible values: 83, 111, 166 scePowerSetGpuXbarClockFrequency( 166 ); } void freeHardwareInternally() { sceKernelExitProcess( 0 ); } #else void initHardwareInternally() { // Do nothing. } void freeHardwareInternally() { // Do nothing. } #endif uint32_t convertToSDLFlag( const fheroes2::SystemInitializationComponent component ) { switch ( component ) { case fheroes2::SystemInitializationComponent::Audio: return SDL_INIT_AUDIO; case fheroes2::SystemInitializationComponent::Video: return SDL_INIT_VIDEO; case fheroes2::SystemInitializationComponent::GameController: return SDL_INIT_GAMECONTROLLER; default: // Did you add a new component? assert( 0 ); break; } return 0; } uint32_t getSDLInitFlags( const std::set & components ) { uint32_t flags = 0; for ( const fheroes2::SystemInitializationComponent component : components ) { flags |= convertToSDLFlag( component ); } return flags; } // For now only SDL library is supported. bool initCoreInternally( const std::set & components ) { const uint32_t sdlFlags = getSDLInitFlags( components ); if ( SDL_Init( sdlFlags ) < 0 ) { ERROR_LOG( SDL_GetError() ) return false; } if ( components.count( fheroes2::SystemInitializationComponent::Audio ) > 0 ) { Audio::Init(); } if ( components.count( fheroes2::SystemInitializationComponent::GameController ) > 0 ) { LocalEvent::Get().OpenController(); } LocalEvent::OpenTouchpad(); LocalEvent::setEventProcessingStates(); return true; } void freeCoreInternally() { if ( fheroes2::isComponentInitialized( fheroes2::SystemInitializationComponent::GameController ) ) { LocalEvent::Get().CloseController(); } if ( fheroes2::isComponentInitialized( fheroes2::SystemInitializationComponent::Audio ) ) { Audio::Quit(); } SDL_Quit(); } bool isComponentInitializedInternally( const fheroes2::SystemInitializationComponent component ) { const uint32_t sdlFlag = convertToSDLFlag( component ); assert( sdlFlag != 0 ); return SDL_WasInit( sdlFlag ) != 0; } } namespace fheroes2 { HardwareInitializer::HardwareInitializer() { initHardwareInternally(); } HardwareInitializer::~HardwareInitializer() { freeHardwareInternally(); } CoreInitializer::CoreInitializer( const std::set & components ) { if ( !initCoreInternally( components ) ) { throw std::logic_error( "Core module initialization failed." ); } } CoreInitializer::~CoreInitializer() { freeCoreInternally(); } bool isComponentInitialized( const SystemInitializationComponent component ) { return isComponentInitializedInternally( component ); } } fheroes2-1.0.12+dfsg/src/engine/core.h000066400000000000000000000043721456075706000174040ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include namespace fheroes2 { enum class SystemInitializationComponent : int { Audio, Video, GameController }; class HardwareInitializer { public: HardwareInitializer(); HardwareInitializer( const HardwareInitializer & ) = delete; HardwareInitializer & operator=( const HardwareInitializer & ) = delete; ~HardwareInitializer(); }; class CoreInitializer { public: explicit CoreInitializer( const std::set & components ); CoreInitializer( const CoreInitializer & ) = delete; CoreInitializer & operator=( const CoreInitializer & ) = delete; ~CoreInitializer(); }; bool isComponentInitialized( const SystemInitializationComponent component ); } fheroes2-1.0.12+dfsg/src/engine/dir.cpp000066400000000000000000000140321456075706000175570ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #if !defined( _WIN32 ) #include #endif #if defined( _WIN32 ) #define WIN32_LEAN_AND_MEAN #include #elif defined( TARGET_PS_VITA ) #include #else #include #endif #include "dir.h" #include "system.h" namespace { using StrCmp = int ( * )( const char *, const char * ); bool filterByName( const char * filename, const bool nameAsFilter, const std::string & name, const StrCmp strCmp ) { if ( !nameAsFilter || !name.empty() ) { const size_t filenameLength = strlen( filename ); if ( filenameLength < name.length() ) return true; if ( !nameAsFilter && filenameLength != name.length() ) { return true; } const char * filenamePtr = filename + filenameLength - name.length(); if ( strCmp( filenamePtr, name.c_str() ) != 0 ) return true; } return false; } void getFilesFromDirectory( const std::string & path, const std::string & name, bool sensitive, bool nameAsFilter, ListFiles & files ) { #if defined( _WIN32 ) (void)sensitive; const std::string pattern( nameAsFilter ? path + "\\*" + name : path + "\\" + name ); WIN32_FIND_DATA data; HANDLE hFind = FindFirstFileEx( pattern.c_str(), FindExInfoBasic, &data, FindExSearchNameMatch, nullptr, FIND_FIRST_EX_LARGE_FETCH ); if ( hFind == INVALID_HANDLE_VALUE ) { return; } do { // Ignore any internal directories if ( data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ) { continue; } // FindFirstFileEx() searches for both long and short variants of names, so we need additional filtering if ( filterByName( data.cFileName, nameAsFilter, name, _stricmp ) ) { continue; } files.emplace_back( System::concatPath( path, data.cFileName ) ); } while ( FindNextFile( hFind, &data ) != 0 ); FindClose( hFind ); #elif defined( TARGET_PS_VITA ) // open the directory const int uid = sceIoDopen( path.c_str() ); if ( uid <= 0 ) return; const StrCmp strCmp = sensitive ? strcmp : strcasecmp; // iterate over the directory for files, print name and size of array (always 256) // this means you use strlen() to get length of file name SceIoDirent dir; while ( sceIoDread( uid, &dir ) > 0 ) { std::string fullname = System::concatPath( path, dir.d_name ); // if not regular file if ( !SCE_S_ISREG( dir.d_stat.st_mode ) ) continue; if ( filterByName( dir.d_name, nameAsFilter, name, strCmp ) ) continue; files.emplace_back( std::move( fullname ) ); } // clean up sceIoDclose( uid ); #else std::string correctedPath; if ( !System::GetCaseInsensitivePath( path, correctedPath ) ) return; // read directory DIR * dp = opendir( correctedPath.c_str() ); if ( !dp ) { return; } const StrCmp strCmp = sensitive ? strcmp : strcasecmp; const struct dirent * ep; while ( nullptr != ( ep = readdir( dp ) ) ) { std::string fullname = System::concatPath( correctedPath, ep->d_name ); // if not regular file if ( !System::IsFile( fullname ) ) continue; if ( filterByName( ep->d_name, nameAsFilter, name, strCmp ) ) continue; files.emplace_back( std::move( fullname ) ); } closedir( dp ); #endif } } void ListFiles::Append( ListFiles && files ) { for ( std::string & file : files ) { emplace_back( std::move( file ) ); } } void ListFiles::ReadDir( const std::string & path, const std::string & filter, bool sensitive ) { getFilesFromDirectory( path, filter, sensitive, true, *this ); } void ListFiles::FindFileInDir( const std::string & path, const std::string & fileName, bool sensitive ) { getFilesFromDirectory( path, fileName, sensitive, false, *this ); } bool ListFiles::IsEmpty( const std::string & path, const std::string & filter, bool sensitive ) { ListFiles list; list.ReadDir( path, filter, sensitive ); return list.empty(); } fheroes2-1.0.12+dfsg/src/engine/dir.h000066400000000000000000000041761456075706000172340ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2DIR_H #define H2DIR_H #include #include struct ListFiles : public std::list { void Append( ListFiles && files ); void ReadDir( const std::string & path, const std::string & filter = "", bool sensitive = true ); static bool IsEmpty( const std::string & path, const std::string & filter = "", bool sensitive = true ); void FindFileInDir( const std::string & path, const std::string & fileName, bool sensitive ); }; #endif fheroes2-1.0.12+dfsg/src/engine/endian_h2.h000066400000000000000000000066601456075706000203050ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef ENDIAN_H2_H #define ENDIAN_H2_H #if defined( __linux__ ) #include // IWYU pragma: export #elif defined( __FreeBSD__ ) || defined( __OpenBSD__ ) #include // IWYU pragma: export #elif defined( _WIN32 ) #include #define BIG_ENDIAN 4321 #define LITTLE_ENDIAN 1234 #define BYTE_ORDER LITTLE_ENDIAN #define htobe16( x ) _byteswap_ushort( x ) #define htole16( x ) ( x ) #define be16toh( x ) _byteswap_ushort( x ) #define le16toh( x ) ( x ) #define htobe32( x ) _byteswap_ulong( x ) #define htole32( x ) ( x ) #define be32toh( x ) _byteswap_ulong( x ) #define le32toh( x ) ( x ) #elif defined( __APPLE__ ) #include // IWYU pragma: export #define htobe16( x ) OSSwapHostToBigInt16( x ) #define htole16( x ) OSSwapHostToLittleInt16( x ) #define be16toh( x ) OSSwapBigToHostInt16( x ) #define le16toh( x ) OSSwapLittleToHostInt16( x ) #define htobe32( x ) OSSwapHostToBigInt32( x ) #define htole32( x ) OSSwapHostToLittleInt32( x ) #define be32toh( x ) OSSwapBigToHostInt32( x ) #define le32toh( x ) OSSwapLittleToHostInt32( x ) #elif defined( TARGET_PS_VITA ) #define BIG_ENDIAN 4321 #define LITTLE_ENDIAN 1234 #define BYTE_ORDER LITTLE_ENDIAN #define htobe16( x ) __builtin_bswap16( x ) #define htole16( x ) ( x ) #define be16toh( x ) __builtin_bswap16( x ) #define le16toh( x ) ( x ) #define htobe32( x ) __builtin_bswap32( x ) #define htole32( x ) ( x ) #define be32toh( x ) __builtin_bswap32( x ) #define le32toh( x ) ( x ) #elif defined( TARGET_NINTENDO_SWITCH ) #include // IWYU pragma: export #define LITTLE_ENDIAN _LITTLE_ENDIAN #define BIG_ENDIAN _BIG_ENDIAN #define BYTE_ORDER _BYTE_ORDER #define htobe16( x ) __bswap16( x ) #define htole16( x ) ( x ) #define be16toh( x ) __bswap16( x ) #define le16toh( x ) ( x ) #define htobe32( x ) __bswap32( x ) #define htole32( x ) ( x ) #define be32toh( x ) __bswap32( x ) #define le32toh( x ) ( x ) #else #error "Unsupported platform" #endif #define IS_BIGENDIAN ( BYTE_ORDER == BIG_ENDIAN ) #endif fheroes2-1.0.12+dfsg/src/engine/exception.h000066400000000000000000000031471456075706000204510ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include namespace fheroes2 { class InvalidDataResources : public std::logic_error { using std::logic_error::logic_error; }; } fheroes2-1.0.12+dfsg/src/engine/h2d_file.cpp000066400000000000000000000166721456075706000204710ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "h2d_file.h" #include #include #include #include "image.h" namespace { // 4 bytes - file identifier // 4 bytes - number of files // 4 bytes - file offset // 4 bytes - file size // 5 bytes - file name const size_t minFileSize = 4 + 4 + 4 + 4 + 5 + 1; } namespace fheroes2 { bool H2DReader::open( const std::string & path ) { _fileNameAndOffset.clear(); _fileStream.close(); if ( !_fileStream.open( path, "rb" ) ) { return false; } const size_t fileSize = _fileStream.size(); if ( fileSize < minFileSize ) { return false; } if ( _fileStream.get() != 'H' ) { return false; } if ( _fileStream.get() != '2' ) { return false; } if ( _fileStream.get() != 'D' ) { return false; } if ( _fileStream.get() != '\0' ) { return false; } const uint32_t fileCount = _fileStream.getLE32(); if ( fileCount == 0 ) { return false; } for ( uint32_t i = 0; i < fileCount; ++i ) { const uint32_t offset = _fileStream.getLE32(); const uint32_t size = _fileStream.getLE32(); std::string name; _fileStream >> name; if ( size == 0 || offset + size > fileSize || name.empty() ) { continue; } _fileNameAndOffset.try_emplace( std::move( name ), std::make_pair( offset, size ) ); } return true; } std::vector H2DReader::getFile( const std::string & fileName ) { const auto it = _fileNameAndOffset.find( fileName ); if ( it == _fileNameAndOffset.end() ) { return std::vector(); } _fileStream.seek( it->second.first ); return _fileStream.getRaw( it->second.second ); } std::set H2DReader::getAllFileNames() const { std::set names; for ( const auto & value : _fileNameAndOffset ) { names.insert( value.first ); } return names; } bool H2DWriter::write( const std::string & path ) const { if ( _fileData.empty() ) { // Nothing to write. return false; } StreamFile fileStream; if ( !fileStream.open( path, "wb" ) ) { return false; } fileStream.put( 'H' ); fileStream.put( '2' ); fileStream.put( 'D' ); fileStream.put( '\0' ); fileStream.putLE32( static_cast( _fileData.size() ) ); // Calculate file info section size. size_t fileInfoSection = ( 4 + 4 ) * _fileData.size(); for ( const auto & data : _fileData ) { // 4 byte for string size. fileInfoSection += ( data.first.size() + 4 ); } size_t offset = fileInfoSection + 4 + 4; for ( const auto & data : _fileData ) { fileStream.putLE32( static_cast( offset ) ); fileStream.putLE32( static_cast( data.second.size() ) ); fileStream << data.first; offset += data.second.size(); } for ( const auto & data : _fileData ) { fileStream.putRaw( reinterpret_cast( data.second.data() ), data.second.size() ); } return true; } bool H2DWriter::add( const std::string & name, const std::vector & data ) { if ( name.empty() || data.empty() ) { return false; } _fileData[name] = data; return true; } bool H2DWriter::add( H2DReader & reader ) { const std::set names = reader.getAllFileNames(); for ( const std::string & name : names ) { if ( !add( name, reader.getFile( name ) ) ) { return false; } } return true; } bool readImageFromH2D( H2DReader & reader, const std::string & name, Sprite & image ) { // TODO: Store in h2d images the 'isSingleLayer' state to disable and skip transform layer for such images. assert( !image.singleLayer() ); const std::vector & data = reader.getFile( name ); if ( data.size() < 4 + 4 + 4 + 4 + 1 ) { // Empty or invalid image. return false; } StreamBuf stream( data ); const int32_t width = static_cast( stream.getLE32() ); const int32_t height = static_cast( stream.getLE32() ); const int32_t x = static_cast( stream.getLE32() ); const int32_t y = static_cast( stream.getLE32() ); if ( static_cast( width * height * 2 + 4 + 4 + 4 + 4 ) != data.size() ) { return false; } const size_t size = static_cast( width * height ); image.resize( width, height ); memcpy( image.image(), data.data() + 4 + 4 + 4 + 4, size ); memcpy( image.transform(), data.data() + 4 + 4 + 4 + 4 + size, size ); image.setPosition( x, y ); return true; } bool writeImageToH2D( H2DWriter & writer, const std::string & name, const Sprite & image ) { // TODO: Store in h2d images the 'isSingleLayer' state to disable and skip transform layer for such images. assert( !image.empty() && !image.singleLayer() ); StreamBuf stream; stream.putLE32( static_cast( image.width() ) ); stream.putLE32( static_cast( image.height() ) ); stream.putLE32( static_cast( image.x() ) ); stream.putLE32( static_cast( image.y() ) ); const size_t imageSize = static_cast( image.width() ) * static_cast( image.height() ); stream.putRaw( reinterpret_cast( image.image() ), imageSize ); stream.putRaw( reinterpret_cast( image.transform() ), imageSize ); return writer.add( name, stream.getRaw() ); } } fheroes2-1.0.12+dfsg/src/engine/h2d_file.h000066400000000000000000000060521456075706000201250ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include #include #include #include #include "serialize.h" namespace fheroes2 { class Sprite; // Heroes 2 Data (H2D) file format used for storing files needed for the project. This format is not a part of original HoMM II. class H2DReader { public: // Returns true if file opening is successful. bool open( const std::string & path ); // Returns non-empty vector if requested file exists. std::vector getFile( const std::string & fileName ); std::set getAllFileNames() const; private: // Relationship between file name in non-capital letters and its offset from the start of the archive. std::map> _fileNameAndOffset; // Stream for reading h2d file. StreamFile _fileStream; }; // This class is not designed to be performance optimized as it will be used very rarely and out of game running session. class H2DWriter { public: // Returns true if file opening is successful. bool write( const std::string & path ) const; bool add( const std::string & name, const std::vector & data ); // Add all entries from a H2D reader. bool add( H2DReader & reader ); private: std::map> _fileData; }; bool readImageFromH2D( H2DReader & reader, const std::string & name, Sprite & image ); bool writeImageToH2D( H2DWriter & writer, const std::string & name, const Sprite & image ); } fheroes2-1.0.12+dfsg/src/engine/image.cpp000066400000000000000000004226771456075706000201050ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "image.h" #include #include #include #include #include #include #include #include "image_palette.h" namespace { // 0 in shadow part means no shadow, 1 means skip any drawings so to don't waste extra CPU cycles for ( tableId - 2 ) command we just add extra fake tables // Mirror palette was modified as it was containing 238, 238, 239, 240 values instead of 238, 239, 240, 241 const uint8_t transformTable[256 * 16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 36, 36, 36, 36, 36, 36, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 62, 62, 62, 62, 62, 62, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 84, 84, 84, 84, 84, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 107, 107, 107, 107, 107, 107, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 130, 130, 130, 130, 130, 130, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 151, 151, 151, 151, 151, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 174, 174, 174, 174, 174, 174, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 197, 197, 197, 197, 197, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 213, 213, 213, 213, 214, 215, 216, 217, 218, 219, 220, 221, 225, 226, 227, 228, 229, 230, 230, 230, 230, 73, 75, 77, 79, 81, 76, 78, 74, 76, 78, 80, 244, 245, 245, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // First 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 36, 36, 36, 36, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 62, 62, 62, 62, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 84, 84, 84, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 107, 107, 107, 107, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 130, 130, 130, 130, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 151, 151, 151, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 174, 174, 174, 174, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 197, 197, 197, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 213, 213, 213, 214, 215, 216, 217, 218, 219, 220, 221, 224, 225, 226, 227, 228, 229, 230, 230, 230, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 78, 244, 245, 245, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Second 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 36, 36, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 62, 62, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 84, 84, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 107, 107, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 130, 130, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 151, 151, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 174, 174, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 197, 197, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 213, 213, 214, 215, 216, 217, 218, 219, 220, 221, 223, 224, 225, 226, 227, 228, 229, 230, 230, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 76, 243, 244, 245, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Third 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 36, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 62, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 84, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 107, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 130, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 151, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 174, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 197, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 213, 214, 215, 216, 217, 218, 219, 220, 221, 223, 224, 225, 226, 227, 228, 229, 230, 230, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 75, 243, 244, 245, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Fourth 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 11, 11, 11, 12, 13, 13, 13, 14, 14, 15, 15, 15, 16, 17, 17, 17, 18, 18, 19, 19, 20, 20, 20, 21, 21, 11, 37, 37, 37, 38, 38, 39, 39, 39, 40, 40, 41, 41, 41, 41, 42, 42, 19, 42, 20, 20, 20, 20, 20, 20, 21, 12, 131, 63, 63, 63, 64, 64, 64, 65, 65, 65, 65, 65, 242, 242, 242, 242, 242, 242, 242, 242, 242, 13, 14, 15, 15, 16, 85, 17, 85, 85, 85, 85, 19, 86, 20, 20, 20, 21, 21, 21, 21, 21, 21, 21, 10, 108, 108, 109, 109, 109, 110, 110, 110, 110, 199, 40, 41, 41, 41, 41, 41, 42, 42, 42, 42, 20, 20, 11, 11, 131, 131, 132, 132, 132, 133, 133, 134, 134, 134, 135, 135, 18, 136, 19, 19, 20, 20, 20, 10, 11, 11, 11, 12, 12, 13, 13, 13, 14, 15, 15, 15, 16, 17, 17, 17, 18, 18, 19, 19, 20, 20, 11, 175, 175, 176, 176, 38, 177, 177, 178, 178, 178, 179, 179, 179, 179, 180, 180, 180, 180, 180, 180, 21, 21, 108, 108, 38, 109, 38, 109, 39, 40, 40, 41, 41, 41, 42, 42, 42, 20, 199, 179, 180, 180, 110, 110, 40, 42, 110, 110, 86, 86, 86, 86, 18, 18, 19, 65, 65, 65, 66, 65, 66, 65, 152, 155, 65, 242, 15, 16, 17, 19, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Fifth 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 11, 12, 12, 13, 13, 14, 15, 15, 16, 16, 17, 17, 18, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, 25, 37, 37, 38, 38, 39, 39, 40, 41, 41, 41, 42, 42, 43, 43, 44, 44, 45, 45, 46, 46, 23, 24, 24, 24, 24, 24, 131, 63, 63, 64, 64, 65, 65, 66, 66, 242, 67, 67, 68, 68, 243, 243, 243, 243, 243, 243, 243, 243, 15, 15, 85, 85, 85, 85, 86, 86, 87, 87, 88, 88, 88, 88, 89, 24, 90, 25, 25, 25, 25, 25, 25, 37, 108, 109, 109, 110, 110, 111, 111, 200, 200, 201, 201, 42, 43, 43, 44, 44, 44, 45, 45, 46, 46, 46, 11, 131, 132, 132, 132, 133, 133, 134, 135, 135, 136, 242, 137, 137, 138, 243, 243, 243, 243, 243, 24, 152, 152, 153, 153, 154, 154, 155, 156, 156, 157, 158, 158, 159, 18, 19, 19, 20, 20, 21, 22, 22, 23, 24, 37, 175, 176, 176, 177, 177, 178, 179, 179, 180, 180, 180, 181, 181, 181, 182, 182, 182, 46, 47, 47, 48, 25, 108, 109, 109, 109, 198, 199, 199, 201, 201, 42, 43, 43, 44, 45, 46, 46, 201, 181, 182, 183, 111, 111, 202, 45, 111, 111, 87, 88, 88, 88, 88, 21, 22, 66, 66, 68, 68, 67, 68, 68, 152, 157, 66, 69, 16, 18, 20, 21, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Sixth 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 11, 12, 13, 14, 14, 15, 16, 17, 17, 18, 19, 20, 20, 21, 22, 23, 24, 24, 25, 26, 26, 27, 28, 29, 29, 37, 37, 38, 39, 40, 40, 41, 42, 42, 43, 44, 44, 45, 46, 46, 47, 47, 48, 48, 49, 50, 50, 27, 28, 28, 28, 63, 63, 64, 65, 65, 66, 67, 67, 68, 69, 69, 69, 70, 70, 70, 244, 71, 244, 244, 244, 244, 245, 16, 85, 85, 86, 87, 87, 88, 88, 89, 90, 90, 91, 91, 91, 92, 93, 93, 93, 29, 29, 29, 29, 29, 37, 109, 109, 110, 111, 111, 112, 113, 112, 112, 203, 203, 203, 44, 45, 46, 47, 47, 47, 48, 48, 49, 50, 131, 131, 132, 133, 133, 134, 135, 136, 136, 137, 137, 139, 139, 139, 141, 141, 141, 143, 143, 245, 245, 152, 152, 153, 154, 155, 155, 156, 157, 158, 158, 159, 160, 161, 162, 163, 163, 164, 165, 165, 166, 26, 26, 27, 175, 13, 176, 177, 178, 178, 179, 180, 181, 181, 182, 182, 183, 183, 183, 184, 184, 185, 185, 50, 50, 52, 52, 109, 109, 198, 199, 200, 201, 201, 202, 202, 44, 45, 46, 47, 48, 48, 49, 204, 205, 185, 185, 112, 112, 204, 47, 112, 113, 88, 89, 91, 92, 93, 93, 25, 66, 68, 69, 69, 68, 69, 69, 153, 159, 68, 71, 18, 242, 243, 24, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Seventh 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 13, 14, 15, 16, 17, 17, 19, 19, 20, 21, 22, 23, 24, 24, 26, 26, 27, 28, 28, 30, 30, 31, 32, 37, 38, 39, 39, 40, 41, 42, 43, 43, 44, 45, 46, 46, 47, 48, 49, 50, 50, 51, 52, 52, 53, 54, 54, 30, 31, 63, 64, 64, 65, 66, 67, 68, 69, 69, 70, 71, 71, 71, 72, 72, 72, 73, 73, 73, 168, 168, 168, 85, 85, 86, 87, 88, 88, 89, 90, 91, 91, 92, 93, 93, 94, 95, 95, 96, 96, 96, 31, 32, 32, 32, 108, 109, 198, 110, 111, 112, 113, 113, 113, 116, 117, 118, 119, 120, 121, 47, 48, 50, 50, 51, 51, 52, 52, 131, 132, 132, 133, 134, 135, 136, 137, 137, 138, 139, 140, 141, 141, 143, 143, 144, 145, 146, 147, 30, 152, 153, 153, 154, 155, 156, 157, 158, 158, 159, 160, 161, 162, 163, 164, 165, 165, 166, 167, 168, 169, 28, 29, 175, 176, 177, 177, 178, 179, 180, 181, 182, 182, 183, 184, 185, 185, 185, 186, 186, 187, 50, 52, 52, 54, 55, 109, 198, 199, 200, 201, 202, 202, 204, 204, 205, 207, 47, 49, 50, 51, 52, 206, 206, 187, 188, 113, 113, 118, 49, 222, 222, 223, 224, 225, 226, 95, 227, 228, 67, 68, 70, 71, 69, 71, 70, 153, 65, 69, 73, 242, 22, 243, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Eighth 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 11, 12, 12, 13, 14, 14, 15, 16, 16, 17, 18, 18, 19, 20, 242, 242, 22, 22, 23, 243, 243, 25, 244, 244, 244, 11, 37, 38, 38, 39, 40, 40, 41, 178, 18, 19, 20, 20, 21, 21, 22, 22, 22, 22, 22, 23, 23, 23, 23, 24, 244, 63, 63, 64, 64, 65, 65, 66, 66, 67, 67, 68, 68, 68, 69, 69, 69, 69, 69, 69, 70, 70, 70, 15, 15, 16, 85, 86, 18, 19, 19, 20, 159, 21, 21, 161, 22, 163, 163, 163, 23, 23, 165, 165, 244, 244, 37, 108, 38, 109, 109, 110, 199, 200, 199, 40, 41, 42, 42, 42, 43, 43, 44, 22, 22, 23, 23, 23, 23, 131, 131, 132, 132, 133, 133, 134, 134, 135, 135, 136, 136, 137, 137, 138, 138, 139, 139, 140, 141, 244, 152, 152, 153, 153, 154, 154, 155, 155, 156, 156, 157, 158, 158, 159, 242, 159, 161, 161, 243, 243, 243, 243, 164, 11, 175, 176, 176, 177, 177, 178, 179, 179, 180, 180, 181, 181, 182, 182, 182, 182, 183, 22, 23, 23, 23, 23, 108, 38, 38, 39, 39, 40, 40, 41, 178, 180, 42, 44, 45, 23, 23, 23, 180, 181, 181, 183, 110, 200, 42, 45, 85, 86, 87, 87, 87, 21, 22, 22, 23, 66, 66, 67, 68, 67, 68, 68, 153, 158, 67, 70, 64, 65, 242, 243, 159, 159, 159, 159, 159, 159, 159, 159, 159, 10, // Ninth 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 11, 12, 13, 14, 14, 15, 16, 16, 17, 18, 19, 19, 20, 242, 22, 22, 243, 243, 243, 244, 244, 244, 244, 245, 245, 37, 37, 38, 176, 39, 177, 41, 41, 42, 179, 20, 180, 45, 22, 23, 23, 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 63, 63, 64, 64, 65, 66, 67, 67, 68, 68, 69, 69, 70, 70, 70, 70, 70, 71, 71, 71, 71, 71, 15, 85, 85, 86, 86, 87, 20, 88, 89, 22, 161, 162, 163, 163, 164, 164, 165, 165, 166, 166, 167, 167, 167, 37, 108, 109, 109, 110, 199, 111, 111, 200, 201, 41, 43, 43, 43, 44, 44, 46, 46, 24, 24, 25, 25, 25, 131, 131, 132, 132, 133, 134, 135, 135, 136, 136, 137, 138, 138, 139, 139, 140, 140, 141, 141, 142, 143, 152, 152, 153, 153, 154, 155, 155, 156, 156, 157, 158, 158, 159, 159, 161, 161, 162, 162, 163, 164, 244, 244, 244, 175, 175, 176, 177, 177, 178, 179, 179, 180, 181, 181, 182, 182, 183, 183, 183, 184, 184, 184, 25, 25, 25, 25, 108, 109, 109, 39, 40, 41, 41, 42, 42, 43, 43, 45, 46, 47, 25, 25, 181, 182, 183, 185, 111, 111, 42, 46, 111, 87, 88, 88, 88, 22, 23, 24, 25, 66, 67, 68, 69, 68, 69, 69, 153, 0, 68, 71, 65, 242, 242, 243, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, // Tenth 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 11, 13, 13, 14, 15, 16, 16, 17, 18, 19, 20, 21, 21, 22, 23, 243, 25, 244, 244, 244, 28, 245, 245, 245, 31, 37, 38, 38, 39, 40, 41, 41, 42, 42, 180, 45, 46, 46, 47, 47, 25, 26, 26, 26, 26, 27, 27, 27, 27, 27, 245, 63, 63, 64, 65, 66, 66, 67, 68, 69, 69, 70, 70, 71, 71, 72, 72, 72, 72, 72, 73, 73, 73, 16, 85, 85, 86, 87, 88, 88, 90, 90, 91, 91, 163, 164, 164, 165, 166, 166, 167, 167, 168, 169, 169, 170, 37, 108, 109, 198, 199, 111, 112, 112, 201, 202, 202, 43, 44, 45, 45, 46, 46, 47, 48, 26, 27, 27, 27, 131, 131, 132, 133, 134, 135, 135, 136, 137, 137, 138, 139, 139, 140, 141, 141, 142, 143, 143, 144, 145, 152, 152, 153, 154, 155, 155, 156, 156, 158, 158, 159, 160, 160, 161, 162, 163, 164, 164, 165, 166, 166, 167, 167, 175, 176, 176, 177, 178, 178, 179, 180, 181, 182, 182, 183, 184, 184, 184, 185, 186, 186, 50, 51, 27, 27, 27, 109, 109, 109, 40, 40, 41, 42, 43, 43, 44, 45, 46, 47, 49, 27, 27, 182, 183, 184, 187, 112, 112, 43, 47, 112, 87, 89, 90, 91, 91, 24, 26, 26, 67, 68, 69, 70, 69, 70, 70, 153, 0, 0, 73, 65, 242, 243, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, // Eleventh 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 13, 14, 15, 16, 17, 18, 19, 20, 21, 21, 23, 243, 24, 25, 244, 244, 27, 245, 245, 31, 170, 149, 149, 37, 38, 38, 39, 40, 41, 42, 42, 44, 45, 46, 46, 47, 48, 49, 50, 51, 28, 28, 28, 29, 29, 29, 29, 29, 30, 63, 64, 65, 65, 66, 67, 68, 69, 70, 70, 71, 72, 72, 73, 73, 74, 74, 75, 75, 76, 76, 76, 85, 85, 86, 87, 88, 89, 90, 91, 91, 92, 93, 93, 166, 166, 96, 168, 168, 169, 170, 170, 171, 171, 171, 37, 109, 109, 110, 200, 111, 112, 113, 202, 202, 203, 44, 45, 46, 46, 47, 48, 49, 50, 51, 52, 29, 29, 131, 132, 133, 133, 134, 135, 136, 137, 138, 139, 139, 140, 141, 142, 142, 144, 144, 145, 145, 146, 147, 152, 153, 153, 154, 155, 156, 157, 158, 159, 159, 160, 161, 162, 163, 164, 164, 165, 166, 167, 168, 168, 168, 169, 175, 176, 177, 177, 178, 179, 180, 181, 182, 182, 183, 184, 185, 186, 186, 187, 187, 189, 189, 193, 193, 146, 146, 109, 109, 198, 199, 201, 201, 201, 44, 205, 45, 46, 47, 48, 50, 52, 29, 183, 185, 186, 189, 112, 112, 205, 49, 222, 88, 89, 91, 92, 93, 26, 27, 28, 67, 68, 70, 71, 69, 71, 71, 154, 0, 0, 75, 242, 242, 243, 244, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, // Twelfth 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 37, 37, 37, 37, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 63, 63, 63, 63, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 85, 85, 85, 85, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 108, 108, 108, 108, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 131, 131, 131, 131, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 152, 152, 152, 152, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 175, 175, 175, 175, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 198, 198, 198, 198, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 214, 215, 216, 217, 218, 219, 220, 221, 222, 222, 223, 224, 225, 226, 227, 228, 229, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 242, 243, 244, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, // Mirror 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 188, 188, 188, 188, 118, 118, 118, 118, 222, 223, 224, 225, 226, 227, 228, 229, 230, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 // No cycle }; bool Validate( const fheroes2::Image & image, const int32_t x, const int32_t y, const int32_t width, const int32_t height ) { if ( image.empty() || width <= 0 || height <= 0 ) { // What's the reason to work with empty images? return false; } if ( x < 0 || y < 0 || x + width > image.width() || y + height > image.height() ) { return false; } return true; } bool Verify( const fheroes2::Image & image, int32_t & x, int32_t & y, int32_t & width, int32_t & height ) { if ( image.empty() || width <= 0 || height <= 0 ) { // What's the reason to work with empty images? return false; } const int32_t widthOut = image.width(); const int32_t heightOut = image.height(); if ( x < 0 ) { const int32_t offsetX = -x; if ( offsetX >= width ) { return false; } x = 0; width -= offsetX; } if ( y < 0 ) { const int32_t offsetY = -y; if ( offsetY >= height ) { return false; } y = 0; height -= offsetY; } if ( x > widthOut || y > heightOut ) { return false; } if ( x + width > widthOut ) { const int32_t offsetX = x + width - widthOut; if ( offsetX >= width ) { return false; } width -= offsetX; } if ( y + height > heightOut ) { const int32_t offsetY = y + height - heightOut; if ( offsetY >= height ) { return false; } height -= offsetY; } return true; } bool Verify( int32_t & inX, int32_t & inY, int32_t & outX, int32_t & outY, int32_t & width, int32_t & height, const int32_t widthIn, const int32_t heightIn, const int32_t widthOut, const int32_t heightOut ) { if ( widthIn <= 0 || heightIn <= 0 || widthOut <= 0 || heightOut <= 0 || width <= 0 || height <= 0 ) { // What's the reason to work with empty images? return false; } if ( inX < 0 || inY < 0 || inX > widthIn || inY > heightIn ) { return false; } if ( outX < 0 ) { const int32_t offsetX = -outX; if ( offsetX >= width ) { return false; } inX += offsetX; outX = 0; width -= offsetX; } if ( outY < 0 ) { const int32_t offsetY = -outY; if ( offsetY >= height ) { return false; } inY += offsetY; outY = 0; height -= offsetY; } if ( outX > widthOut || outY > heightOut ) { return false; } if ( inX + width > widthIn ) { const int32_t offsetX = inX + width - widthIn; if ( offsetX >= width ) { return false; } width -= offsetX; } if ( inY + height > heightIn ) { const int32_t offsetY = inY + height - heightIn; if ( offsetY >= height ) { return false; } height -= offsetY; } if ( outX + width > widthOut ) { const int32_t offsetX = outX + width - widthOut; if ( offsetX >= width ) { return false; } width -= offsetX; } if ( outY + height > heightOut ) { const int32_t offsetY = outY + height - heightOut; if ( offsetY >= height ) { return false; } height -= offsetY; } return true; } bool Verify( const fheroes2::Image & in, int32_t & inX, int32_t & inY, const fheroes2::Image & out, int32_t & outX, int32_t & outY, int32_t & width, int32_t & height ) { return Verify( inX, inY, outX, outY, width, height, in.width(), in.height(), out.width(), out.height() ); } uint8_t GetPALColorId( const uint8_t red, const uint8_t green, const uint8_t blue ) { static uint8_t rgbToId[64 * 64 * 64]; static bool isInitialized = false; if ( !isInitialized ) { isInitialized = true; const uint32_t size = 64 * 64 * 64; int32_t r = 0; int32_t g = 0; int32_t b = 0; const uint8_t * gamePalette = fheroes2::getGamePalette(); for ( uint32_t id = 0; id < size; ++id ) { r = static_cast( id % 64 ); g = static_cast( id >> 6 ) % 64; b = static_cast( id >> 12 ); int32_t minDistance = INT32_MAX; uint32_t bestPos = 0; // Use the "No cycle" palette. const uint8_t * correctorX = transformTable + 256 * 15; for ( uint32_t i = 0; i < 256; ++i, ++correctorX ) { const uint8_t * palette = gamePalette + static_cast( *correctorX ) * 3; const int32_t sumRed = static_cast( *palette ) + r; const int32_t offsetRed = static_cast( *palette ) - r; ++palette; const int32_t offsetGreen = static_cast( *palette ) - g; ++palette; const int32_t offsetBlue = static_cast( *palette ) - b; ++palette; // Based on "Redmean" color distance calculation (https://www.compuphase.com/cmetric.htm). const int32_t distance = ( 2 * 2 * 256 + sumRed ) * offsetRed * offsetRed + 4 * 2 * 256 * offsetGreen * offsetGreen + ( 2 * ( 2 * 256 + 255 ) - sumRed ) * offsetBlue * offsetBlue; if ( minDistance > distance ) { minDistance = distance; bestPos = *correctorX; } } rgbToId[id] = static_cast( bestPos ); // it's safe to cast } } return rgbToId[red + green * 64 + blue * 64 * 64]; } void ApplyRawPalette( const fheroes2::Image & in, int32_t inX, int32_t inY, fheroes2::Image & out, int32_t outX, int32_t outY, int32_t width, int32_t height, const uint8_t * palette ) { if ( !Verify( in, inX, inY, out, outX, outY, width, height ) ) { return; } const int32_t widthIn = in.width(); const int32_t widthOut = out.width(); const uint8_t * imageInY = in.image() + static_cast( inY ) * widthIn + inX; uint8_t * imageOutY = out.image() + static_cast( outY ) * widthOut + outX; const uint8_t * imageInYEnd = imageInY + static_cast( height ) * widthIn; if ( in.singleLayer() ) { // All pixels in a single-layer image do not have any transform values so there is no need to check for them. for ( ; imageInY != imageInYEnd; imageInY += widthIn, imageOutY += widthOut ) { const uint8_t * imageInX = imageInY; uint8_t * imageOutX = imageOutY; const uint8_t * imageInXEnd = imageInX + width; for ( ; imageInX != imageInXEnd; ++imageInX, ++imageOutX ) { *imageOutX = palette[*imageInX]; } } } else { const uint8_t * transformInY = in.transform() + static_cast( inY ) * widthIn + inX; for ( ; imageInY != imageInYEnd; imageInY += widthIn, transformInY += widthIn, imageOutY += widthOut ) { const uint8_t * imageInX = imageInY; const uint8_t * transformInX = transformInY; uint8_t * imageOutX = imageOutY; const uint8_t * imageInXEnd = imageInX + width; for ( ; imageInX != imageInXEnd; ++imageInX, ++imageOutX, ++transformInX ) { if ( *transformInX == 0 ) { // only modify pixels with data *imageOutX = palette[*imageInX]; } } } } } } namespace fheroes2 { Image::Image( const int32_t width_, const int32_t height_ ) { Image::resize( width_, height_ ); } Image::Image( const Image & image_ ) { copy( image_ ); } Image::Image( Image && image_ ) noexcept : _data( std::move( image_._data ) ) { std::swap( _width, image_._width ); std::swap( _height, image_._height ); std::swap( _singleLayer, image_._singleLayer ); } Image & Image::operator=( const Image & image_ ) { if ( this == &image_ ) { return *this; } copy( image_ ); return *this; } Image & Image::operator=( Image && image_ ) noexcept { if ( this == &image_ ) { return *this; } std::swap( _width, image_._width ); std::swap( _height, image_._height ); std::swap( _data, image_._data ); std::swap( _singleLayer, image_._singleLayer ); return *this; } uint8_t * Image::image() { return _data.get(); } const uint8_t * Image::image() const { return _data.get(); } void Image::clear() { _data.reset(); _width = 0; _height = 0; } void Image::fill( const uint8_t value ) { if ( !empty() ) { const size_t totalSize = static_cast( _width ) * _height; memset( image(), value, totalSize ); memset( transform(), static_cast( 0 ), totalSize ); } } void Image::resize( const int32_t width_, const int32_t height_ ) { if ( width_ == _width && height_ == _height ) { return; } if ( width_ <= 0 || height_ <= 0 ) { clear(); return; } const size_t size = static_cast( width_ ) * height_ * 2; _data.reset( new uint8_t[size] ); _width = width_; _height = height_; } void Image::reset() { if ( !empty() ) { const size_t totalSize = static_cast( _width ) * _height; memset( image(), static_cast( 0 ), totalSize ); // Set the transform layer to skip all data. memset( transform(), static_cast( 1 ), totalSize ); } } void Image::copy( const Image & image ) { if ( !image._data ) { clear(); return; } const size_t size = static_cast( image._width ) * image._height * 2; _singleLayer = image._singleLayer; if ( image._width != _width || image._height != _height ) { _data.reset( new uint8_t[size] ); _width = image._width; _height = image._height; } memcpy( _data.get(), image._data.get(), size ); } Sprite::Sprite( const int32_t width_, const int32_t height_, const int32_t x_ /* = 0 */, const int32_t y_ /* = 0 */ ) : Image( width_, height_ ) , _x( x_ ) , _y( y_ ) { // Do nothing. } Sprite::Sprite( const Image & image, const int32_t x_ /* = 0 */, const int32_t y_ /* = 0 */ ) : Image( image ) , _x( x_ ) , _y( y_ ) { // Do nothing. } Sprite::Sprite( Sprite && sprite ) noexcept : Image( std::move( sprite ) ) { std::swap( _x, sprite._x ); std::swap( _y, sprite._y ); } Sprite & Sprite::operator=( const Sprite & sprite ) { if ( this == &sprite ) { return *this; } Image::operator=( sprite ); _x = sprite._x; _y = sprite._y; return *this; } Sprite & Sprite::operator=( Sprite && sprite ) noexcept { if ( this == &sprite ) { return *this; } Image::operator=( std::move( sprite ) ); std::swap( _x, sprite._x ); std::swap( _y, sprite._y ); return *this; } void Sprite::setPosition( const int32_t x_, const int32_t y_ ) { _x = x_; _y = y_; } ImageRestorer::ImageRestorer( Image & image ) : _image( image ) , _width( image.width() ) , _height( image.height() ) { _updateRoi(); if ( _image.singleLayer() ) { // The restorer is used to restore the image without the transform layer so we make a single-layer copy. _copy._disableTransformLayer(); } _copy.resize( _width, _height ); Copy( _image, 0, 0, _copy, 0, 0, _width, _height ); } ImageRestorer::ImageRestorer( Image & image, const int32_t x_, const int32_t y_, const int32_t width, const int32_t height ) : _image( image ) , _x( x_ ) , _y( y_ ) , _width( width ) , _height( height ) { _updateRoi(); if ( _image.singleLayer() ) { // The restorer is used to restore the image without the transform layer so we make a single-layer copy. _copy._disableTransformLayer(); } _copy.resize( _width, _height ); Copy( _image, _x, _y, _copy, 0, 0, _width, _height ); } ImageRestorer::~ImageRestorer() { if ( !_isRestored ) { restore(); } } void ImageRestorer::update( const int32_t x_, const int32_t y_, const int32_t width, const int32_t height ) { _isRestored = false; _x = x_; _y = y_; _width = width; _height = height; _updateRoi(); _copy.resize( _width, _height ); Copy( _image, _x, _y, _copy, 0, 0, _width, _height ); } void ImageRestorer::restore() { _isRestored = true; Copy( _copy, 0, 0, _image, _x, _y, _width, _height ); } void ImageRestorer::_updateRoi() { if ( _width < 0 ) { _width = 0; } if ( _height < 0 ) { _height = 0; } if ( _x < 0 ) { const int32_t offset = -_x; _x = 0; _width = _width < offset ? 0 : _width - offset; } if ( _y < 0 ) { const int32_t offset = -_y; _y = 0; _height = _height < offset ? 0 : _height - offset; } if ( _x >= _image.width() || _y >= _image.height() ) { _x = 0; _y = 0; _width = 0; _height = 0; return; } if ( _x + _width > _image.width() ) { const int32_t offsetX = _x + _width - _image.width(); if ( offsetX >= _width ) { _x = 0; _y = 0; _width = 0; _height = 0; return; } _width -= offsetX; } if ( _y + _height > _image.height() ) { const int32_t offsetY = _y + _height - _image.height(); if ( offsetY >= _height ) { _x = 0; _y = 0; _width = 0; _height = 0; return; } _height -= offsetY; } } void addGradientShadow( const Sprite & in, Image & out, const Point & outPos, const Point & shadowOffset ) { if ( in.empty() || out.empty() || ( shadowOffset.x == 0 && shadowOffset.y == 0 ) || ( outPos.x < 0 ) || ( outPos.y < 0 ) ) { return; } const int32_t outWidth = out.width(); const int32_t inWidth = in.width(); const int32_t inHeight = in.height(); const int32_t shadowOffsetX = std::min( shadowOffset.x, 0 ); const int32_t shadowOffsetY = std::min( shadowOffset.y, 0 ); const int32_t outStartOffset = outPos.x + shadowOffsetX + in.x() + ( outPos.y + shadowOffsetY + in.y() ) * outWidth; // The shadow should not be outside of 'out' image. assert( outStartOffset >= 0 && outWidth >= ( inWidth + outPos.x + std::max( shadowOffset.x, 0 ) ) && out.height() >= ( inHeight + outPos.y + std::max( shadowOffset.y, 0 ) ) ); const int32_t absOffsetX = std::abs( shadowOffset.x ); const int32_t absOffsetY = std::abs( shadowOffset.y ); std::vector shadowLine; shadowLine.reserve( std::max( absOffsetX, absOffsetY ) + 1 ); // Calculate shadow line from the object. if ( shadowOffset.x == 0 ) { const int32_t maxY = absOffsetY + shadowOffsetY; for ( int32_t y = shadowOffsetY; y <= maxY; ++y ) { shadowLine.emplace_back( 0, y ); } } else { const double slopeFactor = static_cast( shadowOffset.y ) / shadowOffset.x; if ( absOffsetX >= absOffsetY ) { const int32_t maxX = absOffsetX + shadowOffsetX; for ( int32_t x = shadowOffsetX; x <= maxX; ++x ) { shadowLine.emplace_back( x, static_cast( std::round( x * slopeFactor ) ) ); } } else { const int32_t maxY = absOffsetY + shadowOffsetY; for ( int32_t y = shadowOffsetY; y <= maxY; ++y ) { shadowLine.emplace_back( static_cast( std::round( y / slopeFactor ) ), y ); } } } const int32_t maxX = inWidth + absOffsetX; const int32_t maxY = inHeight + absOffsetY; // If image is single-layer then pointer to its transform layer is 'nullptr' const uint8_t * transformIn = in.singleLayer() ? nullptr : in.transform(); uint8_t * transformOut = out.singleLayer() ? nullptr : ( out.transform() + outStartOffset ); uint8_t * imageOut = out.image() + outStartOffset; const auto isTransparent = [inWidth, inHeight, transformIn]( const int32_t offsetX, const int32_t offsetY ) { if ( ( offsetX < 0 ) || ( offsetY < 0 ) || ( offsetX >= inWidth ) || ( offsetY >= inHeight ) ) { // The space out of image boundaries is considered as transparent. return true; } if ( transformIn == nullptr ) { // Single-layer images are non-transparent. return false; } // Image is transparent when its transform layer data is equal to 1. return ( *( transformIn + offsetX + static_cast( offsetY ) * inWidth ) == 1 ); }; for ( int32_t y = 0; y < maxY; ++y ) { const int32_t offsetY = y + shadowOffsetY; for ( int32_t x = 0; x < maxX; ++x ) { const int32_t offsetX = x + shadowOffsetX; if ( !isTransparent( offsetX, offsetY ) ) { // We add shadow only to visible parts of the background image. continue; } // There are 4 shadow tables: 2, 3, 4, 5. The strongest shadow is in table ID 2. uint8_t transformTableId = 6; for ( const Point & shadowLineOffset : shadowLine ) { const int32_t shadowLineOffsetX = offsetX - shadowLineOffset.x; const int32_t shadowLineOffsetY = offsetY - shadowLineOffset.y; if ( !isTransparent( shadowLineOffsetX, shadowLineOffsetY ) ) { // Increase the strength of shadow by reducing the table ID. --transformTableId; if ( transformTableId == 2 ) { // We reached the strongest shadow table ID. break; } } } if ( transformTableId == 6 ) { continue; } // The transformTableId is less than 6 so the shadow has to be applied. const int32_t outOffset = x + y * outWidth; if ( transformOut != nullptr ) { // 'out' is double-layer image because transformOut pointer is not `nullptr`. uint8_t * transformOutX = transformOut + outOffset; if ( *transformOutX == 0 ) { // Apply shadow transform to the out image. uint8_t * imageOutX = imageOut + outOffset; *imageOutX = *( transformTable + transformTableId * ptrdiff_t{ 256 } + *imageOutX ); } else if ( *transformOutX > 1 && *transformOutX < 6 ) { // Out image transform layer already has shadow data. We add the shadow strength by subtract the 'transformTableId', limited to 2. *transformOutX = ( *transformOutX < 2 + transformTableId ) ? 2 : ( *transformOutX - transformTableId ); } else { *transformOutX = transformTableId; } } else { // For single-layer 'out' image apply shadow transform to the image data. uint8_t * imageOutX = imageOut + outOffset; *imageOutX = *( transformTable + transformTableId * ptrdiff_t{ 256 } + *imageOutX ); } } } } Sprite addShadow( const Sprite & in, const Point & shadowOffset, const uint8_t transformId ) { if ( in.empty() || shadowOffset.x > 0 || shadowOffset.y < 0 ) { return in; } Sprite out = makeShadow( in, shadowOffset, transformId ); Blit( in, out, -shadowOffset.x, 0 ); return out; } void AddTransparency( Image & image, const uint8_t valueToReplace ) { ReplaceColorIdByTransformId( image, valueToReplace, 1 ); } void AlphaBlit( const Image & in, Image & out, const uint8_t alphaValue, const bool flip /* = false */ ) { AlphaBlit( in, 0, 0, out, 0, 0, in.width(), in.height(), alphaValue, flip ); } void AlphaBlit( const Image & in, Image & out, int32_t outX, int32_t outY, const uint8_t alphaValue, const bool flip /* = false */ ) { AlphaBlit( in, 0, 0, out, outX, outY, in.width(), in.height(), alphaValue, flip ); } void AlphaBlit( const Image & in, int32_t inX, int32_t inY, Image & out, int32_t outX, int32_t outY, int32_t width, int32_t height, const uint8_t alphaValue, const bool flip /* = false */ ) { if ( alphaValue == 0 ) { // There is nothing we need to do. return; } if ( alphaValue == 255 ) { Blit( in, inX, inY, out, outX, outY, width, height, flip ); return; } if ( !Verify( in, inX, inY, out, outX, outY, width, height ) ) { return; } const int32_t widthIn = in.width(); const int32_t widthOut = out.width(); const uint8_t behindValue = 255 - alphaValue; const uint8_t * gamePalette = getGamePalette(); if ( flip ) { const int32_t offsetInY = inY * widthIn + widthIn - 1 - inX; const uint8_t * imageInY = in.image() + offsetInY; const int32_t offsetOutY = outY * widthOut + outX; uint8_t * imageOutY = out.image() + offsetOutY; const uint8_t * imageOutYEnd = imageOutY + static_cast( height ) * widthOut; if ( in.singleLayer() ) { for ( ; imageOutY != imageOutYEnd; imageInY += widthIn, imageOutY += widthOut ) { const uint8_t * imageInX = imageInY; uint8_t * imageOutX = imageOutY; const uint8_t * imageOutXEnd = imageOutX + width; for ( ; imageOutX != imageOutXEnd; --imageInX, ++imageOutX ) { const uint8_t * inPAL = gamePalette + static_cast( *imageInX ) * 3; const uint8_t * outPAL = gamePalette + static_cast( *imageOutX ) * 3; const uint32_t red = static_cast( *inPAL ) * alphaValue + static_cast( *outPAL ) * behindValue; const uint32_t green = static_cast( *( inPAL + 1 ) ) * alphaValue + static_cast( *( outPAL + 1 ) ) * behindValue; const uint32_t blue = static_cast( *( inPAL + 2 ) ) * alphaValue + static_cast( *( outPAL + 2 ) ) * behindValue; *imageOutX = GetPALColorId( static_cast( red / 255 ), static_cast( green / 255 ), static_cast( blue / 255 ) ); } } } else { const uint8_t * transformInY = in.transform() + offsetInY; for ( ; imageOutY != imageOutYEnd; imageInY += widthIn, transformInY += widthIn, imageOutY += widthOut ) { const uint8_t * imageInX = imageInY; const uint8_t * transformInX = transformInY; uint8_t * imageOutX = imageOutY; const uint8_t * imageOutXEnd = imageOutX + width; for ( ; imageOutX != imageOutXEnd; --imageInX, --transformInX, ++imageOutX ) { if ( *transformInX == 1 ) { // skip pixel continue; } uint8_t inValue = *imageInX; if ( *transformInX > 1 ) { inValue = *( transformTable + static_cast( *transformInX ) * 256 + *imageOutX ); } const uint8_t * inPAL = gamePalette + static_cast( inValue ) * 3; const uint8_t * outPAL = gamePalette + static_cast( *imageOutX ) * 3; const uint32_t red = static_cast( *inPAL ) * alphaValue + static_cast( *outPAL ) * behindValue; const uint32_t green = static_cast( *( inPAL + 1 ) ) * alphaValue + static_cast( *( outPAL + 1 ) ) * behindValue; const uint32_t blue = static_cast( *( inPAL + 2 ) ) * alphaValue + static_cast( *( outPAL + 2 ) ) * behindValue; *imageOutX = GetPALColorId( static_cast( red / 255 ), static_cast( green / 255 ), static_cast( blue / 255 ) ); } } } } else { const int32_t offsetInY = inY * widthIn + inX; const uint8_t * imageInY = in.image() + offsetInY; uint8_t * imageOutY = out.image() + static_cast( outY ) * widthOut + outX; const uint8_t * imageInYEnd = imageInY + static_cast( height ) * widthIn; if ( in.singleLayer() ) { for ( ; imageInY != imageInYEnd; imageInY += widthIn, imageOutY += widthOut ) { const uint8_t * imageInX = imageInY; uint8_t * imageOutX = imageOutY; const uint8_t * imageInXEnd = imageInX + width; for ( ; imageInX != imageInXEnd; ++imageInX, ++imageOutX ) { const uint8_t * inPAL = gamePalette + static_cast( *imageInX ) * 3; const uint8_t * outPAL = gamePalette + static_cast( *imageOutX ) * 3; const uint32_t red = static_cast( *inPAL ) * alphaValue + static_cast( *outPAL ) * behindValue; const uint32_t green = static_cast( *( inPAL + 1 ) ) * alphaValue + static_cast( *( outPAL + 1 ) ) * behindValue; const uint32_t blue = static_cast( *( inPAL + 2 ) ) * alphaValue + static_cast( *( outPAL + 2 ) ) * behindValue; *imageOutX = GetPALColorId( static_cast( red / 255 ), static_cast( green / 255 ), static_cast( blue / 255 ) ); } } } else { const uint8_t * transformInY = in.transform() + offsetInY; for ( ; imageInY != imageInYEnd; imageInY += widthIn, transformInY += widthIn, imageOutY += widthOut ) { const uint8_t * imageInX = imageInY; const uint8_t * transformInX = transformInY; uint8_t * imageOutX = imageOutY; const uint8_t * imageInXEnd = imageInX + width; for ( ; imageInX != imageInXEnd; ++imageInX, ++transformInX, ++imageOutX ) { if ( *transformInX == 1 ) { // skip pixel continue; } uint8_t inValue = *imageInX; if ( *transformInX > 1 ) { inValue = *( transformTable + static_cast( *transformInX ) * 256 + *imageOutX ); } const uint8_t * inPAL = gamePalette + static_cast( inValue ) * 3; const uint8_t * outPAL = gamePalette + static_cast( *imageOutX ) * 3; const uint32_t red = static_cast( *inPAL ) * alphaValue + static_cast( *outPAL ) * behindValue; const uint32_t green = static_cast( *( inPAL + 1 ) ) * alphaValue + static_cast( *( outPAL + 1 ) ) * behindValue; const uint32_t blue = static_cast( *( inPAL + 2 ) ) * alphaValue + static_cast( *( outPAL + 2 ) ) * behindValue; *imageOutX = GetPALColorId( static_cast( red / 255 ), static_cast( green / 255 ), static_cast( blue / 255 ) ); } } } } } void ApplyPalette( Image & image, const std::vector & palette ) { ApplyPalette( image, image, palette ); } void ApplyPalette( const Image & in, Image & out, const std::vector & palette ) { if ( palette.size() != 256 ) { return; } ApplyRawPalette( in, 0, 0, out, 0, 0, in.width(), in.height(), palette.data() ); } void ApplyPalette( Image & image, const uint8_t paletteId ) { ApplyPalette( image, image, paletteId ); } void ApplyPalette( const Image & in, Image & out, const uint8_t paletteId ) { if ( paletteId > 15 ) { return; } ApplyRawPalette( in, 0, 0, out, 0, 0, in.width(), in.height(), transformTable + paletteId * 256 ); } void ApplyPalette( const Image & in, int32_t inX, int32_t inY, Image & out, int32_t outX, int32_t outY, int32_t width, int32_t height, uint8_t paletteId ) { if ( paletteId > 15 ) { return; } ApplyRawPalette( in, inX, inY, out, outX, outY, width, height, transformTable + paletteId * 256 ); } void ApplyPalette( const Image & in, int32_t inX, int32_t inY, Image & out, int32_t outX, int32_t outY, int32_t width, int32_t height, const std::vector & palette ) { if ( palette.size() != 256 ) { return; } ApplyRawPalette( in, inX, inY, out, outX, outY, width, height, palette.data() ); } void ApplyAlpha( const Image & in, Image & out, const uint8_t alpha ) { ApplyAlpha( in, 0, 0, out, 0, 0, in.width(), in.height(), alpha ); } void ApplyAlpha( const Image & in, int32_t inX, int32_t inY, Image & out, int32_t outX, int32_t outY, int32_t width, int32_t height, const uint8_t alpha ) { std::vector palette( 256 ); const uint8_t * value = getGamePalette(); for ( uint32_t i = 0; i < 256; ++i ) { const uint32_t red = static_cast( *value ) * alpha / 255; ++value; const uint32_t green = static_cast( *value ) * alpha / 255; ++value; const uint32_t blue = static_cast( *value ) * alpha / 255; ++value; palette[i] = GetPALColorId( static_cast( red ), static_cast( green ), static_cast( blue ) ); } ApplyPalette( in, inX, inY, out, outX, outY, width, height, palette ); } void ApplyTransform( Image & image, int32_t x, int32_t y, int32_t width, int32_t height, const uint8_t transformId ) { if ( !Verify( image, x, y, width, height ) ) { return; } const int32_t imageWidth = image.width(); uint8_t * imageY = image.image() + y * imageWidth + x; const uint8_t * imageYEnd = imageY + height * imageWidth; if ( image.singleLayer() ) { for ( ; imageY != imageYEnd; imageY += imageWidth ) { uint8_t * imageX = imageY; const uint8_t * imageXEnd = imageX + width; for ( ; imageX != imageXEnd; ++imageX ) { *imageX = *( transformTable + transformId * 256 + *imageX ); } } } else { const uint8_t * transformY = image.transform() + y * imageWidth + x; for ( ; imageY != imageYEnd; imageY += imageWidth, transformY += imageWidth ) { uint8_t * imageX = imageY; const uint8_t * transformX = transformY; const uint8_t * imageXEnd = imageX + width; for ( ; imageX != imageXEnd; ++imageX, ++transformX ) { if ( *transformX == 0 ) { *imageX = *( transformTable + transformId * 256 + *imageX ); } } } } } void Blit( const Image & in, Image & out, const bool flip /* = false */ ) { Blit( in, 0, 0, out, 0, 0, in.width(), in.height(), flip ); } void Blit( const Image & in, Image & out, int32_t outX, int32_t outY, const bool flip /* = false */ ) { Blit( in, 0, 0, out, outX, outY, in.width(), in.height(), flip ); } void Blit( const Image & in, int32_t inX, int32_t inY, Image & out, int32_t outX, int32_t outY, int32_t width, int32_t height, const bool flip /* = false */ ) { if ( in.singleLayer() && !flip ) { Copy( in, inX, inY, out, outX, outY, width, height ); return; } if ( !Verify( in, inX, inY, out, outX, outY, width, height ) ) { return; } const int32_t widthIn = in.width(); const int32_t widthOut = out.width(); if ( flip ) { const int32_t offsetInY = inY * widthIn + widthIn - 1 - inX; const uint8_t * imageInY = in.image() + offsetInY; const uint8_t * transformInY = in.transform() + offsetInY; const int32_t offsetOutY = outY * widthOut + outX; uint8_t * imageOutY = out.image() + offsetOutY; const uint8_t * imageOutYEnd = imageOutY + height * widthOut; if ( out.singleLayer() ) { assert( !in.singleLayer() ); for ( ; imageOutY != imageOutYEnd; imageInY += widthIn, transformInY += widthIn, imageOutY += widthOut ) { const uint8_t * imageInX = imageInY; const uint8_t * transformInX = transformInY; uint8_t * imageOutX = imageOutY; const uint8_t * imageOutXEnd = imageOutX + width; for ( ; imageOutX != imageOutXEnd; --imageInX, --transformInX, ++imageOutX ) { if ( *transformInX > 0 ) { // apply a transformation if ( *transformInX != 1 ) { // skip pixel *imageOutX = *( transformTable + ( *transformInX ) * 256 + *imageOutX ); } } else { // copy a pixel *imageOutX = *imageInX; } } } } else { uint8_t * transformOutY = out.transform() + offsetOutY; for ( ; imageOutY != imageOutYEnd; imageInY += widthIn, transformInY += widthIn, imageOutY += widthOut, transformOutY += widthOut ) { const uint8_t * imageInX = imageInY; const uint8_t * transformInX = transformInY; uint8_t * imageOutX = imageOutY; uint8_t * transformOutX = transformOutY; const uint8_t * imageOutXEnd = imageOutX + width; for ( ; imageOutX != imageOutXEnd; --imageInX, --transformInX, ++imageOutX, ++transformOutX ) { if ( *transformInX == 1 ) { // skip pixel continue; } if ( *transformInX > 0 && *transformOutX == 0 ) { // apply a transformation *imageOutX = *( transformTable + ( *transformInX ) * 256 + *imageOutX ); } else { // copy a pixel *transformOutX = *transformInX; *imageOutX = *imageInX; } } } } } else { const int32_t offsetInY = inY * widthIn + inX; const uint8_t * imageInY = in.image() + offsetInY; const uint8_t * transformInY = in.transform() + offsetInY; const int32_t offsetOutY = outY * widthOut + outX; uint8_t * imageOutY = out.image() + offsetOutY; const uint8_t * imageInYEnd = imageInY + height * widthIn; if ( out.singleLayer() ) { assert( !in.singleLayer() ); for ( ; imageInY != imageInYEnd; imageInY += widthIn, transformInY += widthIn, imageOutY += widthOut ) { const uint8_t * imageInX = imageInY; const uint8_t * transformInX = transformInY; uint8_t * imageOutX = imageOutY; const uint8_t * imageInXEnd = imageInX + width; for ( ; imageInX != imageInXEnd; ++imageInX, ++transformInX, ++imageOutX ) { if ( *transformInX > 0 ) { // apply a transformation if ( *transformInX != 1 ) { // skip pixel *imageOutX = *( transformTable + ( *transformInX ) * 256 + *imageOutX ); } } else { // copy a pixel *imageOutX = *imageInX; } } } } else { uint8_t * transformOutY = out.transform() + offsetOutY; for ( ; imageInY != imageInYEnd; imageInY += widthIn, transformInY += widthIn, imageOutY += widthOut, transformOutY += widthOut ) { const uint8_t * imageInX = imageInY; const uint8_t * transformInX = transformInY; uint8_t * imageOutX = imageOutY; uint8_t * transformOutX = transformOutY; const uint8_t * imageInXEnd = imageInX + width; for ( ; imageInX != imageInXEnd; ++imageInX, ++transformInX, ++imageOutX, ++transformOutX ) { if ( *transformInX == 1 ) { // skip pixel continue; } if ( *transformInX > 0 && *transformOutX == 0 ) { // apply a transformation *imageOutX = *( transformTable + ( *transformInX ) * 256 + *imageOutX ); } else { // copy a pixel *transformOutX = *transformInX; *imageOutX = *imageInX; } } } } } } void Blit( const Image & in, const Point & inPos, Image & out, const Point & outPos, const Size & size, const bool flip /* = false */ ) { if ( inPos.x < 0 || inPos.y < 0 ) { return; } Blit( in, inPos.x, inPos.y, out, outPos.x, outPos.y, size.width, size.height, flip ); } void Copy( const Image & in, Image & out ) { out.resize( in.width(), in.height() ); Copy( in, 0, 0, out, 0, 0, in.width(), in.height() ); } void Copy( const Image & in, int32_t inX, int32_t inY, Image & out, const Rect & outRoi ) { Copy( in, inX, inY, out, outRoi.x, outRoi.y, outRoi.width, outRoi.height ); } void Copy( const Image & in, int32_t inX, int32_t inY, Image & out, int32_t outX, int32_t outY, int32_t width, int32_t height ) { if ( !Verify( in, inX, inY, out, outX, outY, width, height ) ) { return; } const int32_t widthIn = in.width(); const int32_t widthOut = out.width(); const int32_t offsetInY = inY * widthIn + inX; const uint8_t * imageInY = in.image() + offsetInY; const int32_t offsetOutY = outY * widthOut + outX; uint8_t * imageOutY = out.image() + offsetOutY; const uint8_t * imageOutYEnd = imageOutY + height * widthOut; if ( out.singleLayer() ) { for ( ; imageOutY != imageOutYEnd; imageInY += widthIn, imageOutY += widthOut ) { memcpy( imageOutY, imageInY, static_cast( width ) ); } } else if ( in.singleLayer() ) { uint8_t * transformOutY = out.transform() + offsetOutY; for ( ; imageOutY != imageOutYEnd; imageInY += widthIn, imageOutY += widthOut, transformOutY += widthOut ) { memcpy( imageOutY, imageInY, static_cast( width ) ); memset( transformOutY, static_cast( 0 ), width ); } } else { const uint8_t * transformInY = in.transform() + offsetInY; uint8_t * transformOutY = out.transform() + offsetOutY; for ( ; imageOutY != imageOutYEnd; imageInY += widthIn, transformInY += widthIn, imageOutY += widthOut, transformOutY += widthOut ) { memcpy( imageOutY, imageInY, static_cast( width ) ); memcpy( transformOutY, transformInY, static_cast( width ) ); } } } void CopyTransformLayer( const Image & in, Image & out ) { if ( in.empty() || out.empty() || in.singleLayer() || in.width() != out.width() || in.height() != out.height() ) { assert( 0 ); return; } if ( out.singleLayer() ) { // Add the transform layer. Image temp; Copy( out, temp ); out = std::move( temp ); } memcpy( out.transform(), in.transform(), in.width() * in.height() ); } Sprite CreateContour( const Image & image, const uint8_t value ) { if ( image.empty() || image.singleLayer() ) { // A contour can be created only for non-empty images with the transform layer. assert( 0 ); return {}; } const int32_t width = image.width(); const int32_t height = image.height(); Sprite contour( width, height ); contour.reset(); if ( width < 2 || height < 2 ) { return contour; } // This assertion is needed to convince SonarQube that we will not dereference the null pointer to 'contour' image data. assert( !contour.empty() ); const uint8_t * inY = image.transform(); uint8_t * outImageY = contour.image(); uint8_t * outTransformY = contour.transform(); const int32_t reducedWidth = width - 1; const int32_t reducedHeight = height - 1; for ( int32_t y = 0; y < height; ++y, inY += width, outImageY += width, outTransformY += width ) { const uint8_t * inX = inY; const bool isNotTopRow = ( y > 0 ); const bool isNotBottomRow = ( y < reducedHeight ); for ( int32_t x = 0; x < width; ++x, ++inX ) { // Draw contour only on transparent pixel ( 1 ) or shadow ( 2 - 5 ) if ( *inX > 0 && *inX < 6 && ( ( x > 0 && *( inX - 1 ) == 0 ) || ( x < reducedWidth && *( inX + 1 ) == 0 ) || ( isNotTopRow && *( inX - width ) == 0 ) || ( isNotBottomRow && *( inX + width ) == 0 ) ) ) { outImageY[x] = value; outTransformY[x] = 0; } } } return contour; } void CreateDitheringTransition( const Image & in, int32_t inX, int32_t inY, Image & out, int32_t outX, int32_t outY, int32_t width, int32_t height, const bool isVertical, const bool isReverse ) { if ( !Verify( in, inX, inY, out, outX, outY, width, height ) ) { return; } const int32_t widthIn = in.width(); const int32_t offsetIn = inY * widthIn + inX; const uint8_t * imageIn = in.image() + offsetIn; const uint8_t * transformIn = in.singleLayer() ? nullptr : in.transform() + offsetIn; const int32_t widthOut = out.width(); const int32_t offsetOut = outY * widthOut + outX; uint8_t * imageOut = out.image() + offsetOut; uint8_t * transformOut = out.singleLayer() ? nullptr : out.transform() + offsetOut; if ( isVertical ) { // We also go in a loop from the right part of the image to its center. const uint8_t * imageInRightPoint = imageIn + width - 1; uint8_t * imageOutRightPoint = imageOut + width - 1; // If image is single-layer then pointer to its transform layer is 'nullptr' const uint8_t * transformInRightPoint = ( transformIn == nullptr ) ? nullptr : transformIn + width - 1; uint8_t * transformOutRightPoint = ( transformOut == nullptr ) ? nullptr : transformOut + width - 1; const int32_t halfWidth = width / 2; // We make a symmetric transition and if width is odd we shift one line right. if ( ( width % 2 ) == 1 ) { if ( isReverse ) { --imageOutRightPoint; --imageInRightPoint; if ( transformOutRightPoint != nullptr ) { --transformOutRightPoint; } if ( transformInRightPoint != nullptr ) { --transformInRightPoint; } } else { ++imageOut; ++imageIn; if ( transformOut != nullptr ) { ++transformOut; } if ( transformIn != nullptr ) { ++transformIn; } } } for ( int32_t x = 0; x < halfWidth; ++x ) { // The step is 2 to the power, which decreases by 1 every second line and is 1 in the center. // We limit the step power to 30 to not overflow the 32 bit 'stepY'. const int32_t stepPower = std::min( 30, ( halfWidth - x ) / 2 + 1 ); const int32_t stepY = 1 << stepPower; // The point position in dithered pattern. const int32_t patternPoint = stepY / 2 * ( ( x + halfWidth ) % 2 ); for ( int32_t y = 0; y < height; ++y ) { const int32_t offsetOutX = y * widthOut; const int32_t offsetInX = y * widthIn; const int32_t offsetY = y % stepY; if ( isReverse == ( patternPoint != offsetY ) ) { if ( transformIn != nullptr && transformOut == nullptr && ( *( transformIn + offsetInX ) == 1 ) ) { // Skip pixel. continue; } // First part of transition: we copy single pixels. *( imageOut + offsetOutX ) = *( imageIn + offsetInX ); if ( transformOut == nullptr ) { continue; } if ( transformIn == nullptr ) { // Set the copied pixel visible. *( transformOut + offsetOutX ) = 0; } else { // 'in' and 'out' images have the transform layer. *( transformOut + offsetOutX ) = *( transformIn + offsetInX ); } } else { if ( transformInRightPoint != nullptr && transformOutRightPoint == nullptr && ( *( transformInRightPoint + offsetInX ) == 1 ) ) { // Skip pixel. continue; } // Second part of transition: we copy image excluding single pixels. *( imageOutRightPoint + offsetOutX ) = *( imageInRightPoint + offsetInX ); if ( transformOutRightPoint == nullptr ) { continue; } if ( transformInRightPoint == nullptr ) { // Set the copied pixel visible. *( transformOutRightPoint + offsetOutX ) = 0; } else { // 'in' and 'out' images have the transform layer. *( transformOutRightPoint + offsetOutX ) = *( transformInRightPoint + offsetInX ); } } } ++imageOut; --imageOutRightPoint; ++imageIn; --imageInRightPoint; if ( transformOut != nullptr ) { ++transformOut; --transformOutRightPoint; } if ( transformIn != nullptr ) { ++transformIn; --transformInRightPoint; } } } else { // We also go in a loop from the bottom part of the image to its center. const int32_t offsetInBottomOffset = ( height - 1 ) * widthIn; const int32_t offsetOutYBottomOffset = ( height - 1 ) * widthOut; const uint8_t * imageInBottomPoint = imageIn + offsetInBottomOffset; uint8_t * imageOutBottomPoint = imageOut + offsetOutYBottomOffset; // If image is single-layer then pointer to its transform layer is 'nullptr'. const uint8_t * transformInBottomPoint = ( transformIn == nullptr ) ? nullptr : transformIn + offsetInBottomOffset; uint8_t * transformOutBottomPoint = ( transformOut == nullptr ) ? nullptr : transformOut + offsetOutYBottomOffset; const int32_t halfHeight = height / 2; // We make a symmetric transition and if width is odd we shift one line down. if ( ( height % 2 ) == 1 ) { if ( isReverse ) { imageOutBottomPoint -= widthOut; imageInBottomPoint -= widthIn; if ( transformOutBottomPoint != nullptr ) { transformOutBottomPoint -= widthOut; } if ( transformInBottomPoint != nullptr ) { transformInBottomPoint -= widthIn; } } else { imageOut += widthOut; imageIn += widthIn; if ( transformOut != nullptr ) { transformOut += widthOut; } if ( transformIn != nullptr ) { transformIn += widthIn; } } } for ( int32_t y = 0; y < halfHeight; ++y ) { // The step is 2 to the power, which decreases by 1 every second line and is 1 in the center. // We limit the step power to 30 to not overflow the 32 bit 'stepX'. const int32_t stepPower = std::min( 30, ( halfHeight - y ) / 2 + 1 ); const int32_t stepX = 1 << stepPower; // The point position in dithered pattern. const int32_t patternPoint = stepX / 2 * ( ( y + halfHeight ) % 2 ); for ( int32_t x = 0; x < width; ++x ) { const int32_t offsetX = x % stepX; if ( isReverse == ( patternPoint != offsetX ) ) { if ( transformIn != nullptr && transformOut == nullptr && ( *( transformIn + x ) == 1 ) ) { // Skip pixel. continue; } // First part of transition: we copy single pixels. *( imageOut + x ) = *( imageIn + x ); if ( transformOut == nullptr ) { continue; } if ( transformIn == nullptr ) { // Set the copied pixel visible. *( transformOut + x ) = 0; } else { // 'in' and 'out' images have the transform layer. *( transformOut + x ) = *( transformIn + x ); } } else { if ( transformInBottomPoint != nullptr && transformOutBottomPoint == nullptr && ( *( transformInBottomPoint + x ) == 1 ) ) { // Skip pixel. continue; } // Second part of transition: we copy image excluding single pixels. *( imageOutBottomPoint + x ) = *( imageInBottomPoint + x ); if ( transformOutBottomPoint == nullptr ) { continue; } if ( transformInBottomPoint == nullptr ) { // Set the copied pixel visible. *( transformOutBottomPoint + x ) = 0; } else { // 'in' and 'out' images have the transform layer. *( transformOutBottomPoint + x ) = *( transformInBottomPoint + x ); } } } imageOut += widthOut; imageOutBottomPoint -= widthOut; imageIn += widthIn; imageInBottomPoint -= widthIn; if ( transformOut != nullptr ) { transformOut += widthOut; transformOutBottomPoint -= widthOut; } if ( transformIn != nullptr ) { transformIn += widthIn; transformInBottomPoint -= widthIn; } } } } Sprite Crop( const Image & image, int32_t x, int32_t y, int32_t width, int32_t height ) { if ( image.empty() || width <= 0 || height <= 0 ) { return {}; } if ( x < 0 ) { const int32_t offsetX = -x; if ( offsetX >= width ) { return {}; } x = 0; width -= offsetX; } if ( y < 0 ) { const int32_t offsetY = -y; if ( offsetY >= height ) { return {}; } y = 0; height -= offsetY; } if ( x > image.width() || y > image.height() ) { return {}; } if ( x + width > image.width() ) { const int32_t offsetX = x + width - image.width(); width -= offsetX; } if ( y + height > image.height() ) { const int32_t offsetY = y + height - image.height(); height -= offsetY; } Sprite out; if ( image.singleLayer() ) { // The result of Crop should have the same layers as the input image. out._disableTransformLayer(); } out.resize( width, height ); Copy( image, x, y, out, 0, 0, width, height ); out.setPosition( x, y ); return out; } void DrawBorder( Image & image, const uint8_t value, const uint32_t skipFactor /* =0 */ ) { if ( image.empty() || image.width() < 2 || image.height() < 2 ) { return; } const int32_t width = image.width(); const int32_t height = image.height(); uint8_t * dataPointer = image.image(); // If image is single-layer then pointer to its transform layer is 'nullptr'. uint8_t * transformPointer = image.singleLayer() ? nullptr : image.transform(); if ( skipFactor < 2 ) { // top side uint8_t * data = dataPointer; const uint8_t * dataEnd = data + width; if ( transformPointer == nullptr ) { for ( ; data != dataEnd; ++data ) { *data = value; } } else { uint8_t * transform = transformPointer; for ( ; data != dataEnd; ++data, ++transform ) { *data = value; *transform = 0; } } // bottom side data = dataPointer + width * static_cast( height - 1 ); dataEnd = data + width; if ( transformPointer == nullptr ) { for ( ; data != dataEnd; ++data ) { *data = value; } } else { uint8_t * transform = transformPointer + width * static_cast( height - 1 ); for ( ; data != dataEnd; ++data, ++transform ) { *data = value; *transform = 0; } } // left side data = dataPointer + width; dataEnd = data + width * ( height - 2 ); if ( transformPointer == nullptr ) { for ( ; data != dataEnd; data += width ) { *data = value; } } else { uint8_t * transform = transformPointer + width; for ( ; data != dataEnd; data += width, transform += width ) { *data = value; *transform = 0; } } // right side data = dataPointer + width + width - 1; dataEnd = data + width * ( height - 2 ); if ( transformPointer == nullptr ) { for ( ; data != dataEnd; data += width ) { *data = value; } } else { uint8_t * transform = transformPointer + width + width - 1; for ( ; data != dataEnd; data += width, transform += width ) { *data = value; *transform = 0; } } } else { uint32_t counter = 1; // top side uint8_t * data = dataPointer; const uint8_t * dataEnd = data + width; if ( transformPointer == nullptr ) { for ( ; data != dataEnd; ++data ) { if ( counter % skipFactor != 0 ) { *data = value; } ++counter; } } else { uint8_t * transform = transformPointer; for ( ; data != dataEnd; ++data, ++transform ) { if ( counter % skipFactor != 0 ) { *data = value; *transform = 0; } ++counter; } } // right side data = dataPointer + width + width - 1; dataEnd = data + width * ( height - 2 ); if ( transformPointer == nullptr ) { for ( ; data != dataEnd; data += width ) { if ( counter % skipFactor != 0 ) { *data = value; } ++counter; } } else { uint8_t * transform = transformPointer + width + width - 1; for ( ; data != dataEnd; data += width, transform += width ) { if ( counter % skipFactor != 0 ) { *data = value; *transform = 0; } ++counter; } } // bottom side data = dataPointer + width * static_cast( height - 1 ) + width - 1; dataEnd = data - width; if ( transformPointer == nullptr ) { for ( ; data != dataEnd; --data ) { if ( counter % skipFactor != 0 ) { *data = value; } ++counter; } } else { uint8_t * transform = transformPointer + width * static_cast( height - 1 ) + width - 1; for ( ; data != dataEnd; --data, --transform ) { if ( counter % skipFactor != 0 ) { *data = value; *transform = 0; } ++counter; } } // left side data = dataPointer + width * static_cast( height - 2 ); dataEnd = dataPointer; if ( transformPointer == nullptr ) { for ( ; data != dataEnd; data -= width ) { if ( counter % skipFactor != 0 ) { *data = value; } ++counter; } } else { uint8_t * transform = transformPointer + width * static_cast( height - 2 ); for ( ; data != dataEnd; data -= width, transform -= width ) { if ( counter % skipFactor != 0 ) { *data = value; *transform = 0; } ++counter; } } } } void DrawLine( Image & image, const Point & start, const Point & end, const uint8_t value, const Rect & roi /* = Rect() */ ) { if ( image.empty() ) { return; } const int32_t width = image.width(); const int32_t height = image.height(); int32_t x1 = start.x; int32_t y1 = start.y; const int32_t x2 = end.x; const int32_t y2 = end.y; const int32_t dx = std::abs( x2 - x1 ); const int32_t dy = std::abs( y2 - y1 ); const bool isValidRoi = roi.width > 0 && roi.height > 0; const int32_t minX = isValidRoi ? std::max( roi.x, 0 ) : 0; const int32_t minY = isValidRoi ? std::max( roi.y, 0 ) : 0; int32_t maxX = isValidRoi ? roi.x + roi.width : width; int32_t maxY = isValidRoi ? roi.y + roi.height : height; if ( minX >= width || minY >= height ) { return; } if ( maxX >= width ) { maxX = width; } if ( maxY >= height ) { maxY = height; } uint8_t * data = image.image(); if ( image.singleLayer() ) { if ( dx >= dy ) { int32_t ns = dx / 2; for ( int32_t i = 0; i <= dx; ++i ) { if ( x1 >= minX && x1 < maxX && y1 >= minY && y1 < maxY ) { const int32_t offset = x1 + y1 * width; *( data + offset ) = value; } x1 < x2 ? ++x1 : --x1; ns -= dy; if ( ns < 0 ) { y1 < y2 ? ++y1 : --y1; ns += dx; } } } else { int32_t ns = dy / 2; for ( int32_t i = 0; i <= dy; ++i ) { if ( x1 >= minX && x1 < maxX && y1 >= minY && y1 < maxY ) { const int32_t offset = x1 + y1 * width; *( data + offset ) = value; } y1 < y2 ? ++y1 : --y1; ns -= dx; if ( ns < 0 ) { x1 < x2 ? ++x1 : --x1; ns += dy; } } } } else { uint8_t * transform = image.transform(); if ( dx >= dy ) { int32_t ns = dx / 2; for ( int32_t i = 0; i <= dx; ++i ) { if ( x1 >= minX && x1 < maxX && y1 >= minY && y1 < maxY ) { const int32_t offset = x1 + y1 * width; *( data + offset ) = value; *( transform + offset ) = 0; } x1 < x2 ? ++x1 : --x1; ns -= dy; if ( ns < 0 ) { y1 < y2 ? ++y1 : --y1; ns += dx; } } } else { int32_t ns = dy / 2; for ( int32_t i = 0; i <= dy; ++i ) { if ( x1 >= minX && x1 < maxX && y1 >= minY && y1 < maxY ) { const int32_t offset = x1 + y1 * width; *( data + offset ) = value; *( transform + offset ) = 0; } y1 < y2 ? ++y1 : --y1; ns -= dx; if ( ns < 0 ) { x1 < x2 ? ++x1 : --x1; ns += dy; } } } } } void DrawRect( Image & image, const Rect & roi, const uint8_t value ) { if ( image.empty() || roi.width < 1 || roi.height < 1 ) { return; } DrawLine( image, { roi.x, roi.y }, { roi.x + roi.width, roi.y }, value, roi ); DrawLine( image, { roi.x, roi.y }, { roi.x, roi.y + roi.height }, value, roi ); DrawLine( image, { roi.x + roi.width - 1, roi.y }, { roi.x + roi.width - 1, roi.y + roi.height }, value, roi ); DrawLine( image, { roi.x, roi.y + roi.height - 1 }, { roi.x + roi.width, roi.y + roi.height - 1 }, value, roi ); } void DivideImageBySquares( const Point & spriteOffset, const Image & original, const int32_t squareSize, std::vector & outputSquareId, std::vector> & outputImageInfo ) { if ( original.empty() ) { return; } if ( squareSize <= 0 ) { assert( 0 ); return; } Point offset{ spriteOffset.x / squareSize, spriteOffset.y / squareSize }; // The start of a square must be before image offset so in case of negative offset we need to decrease the ID of the start square. if ( ( spriteOffset.x < 0 ) && ( offset.x * squareSize != spriteOffset.x ) ) { --offset.x; } if ( ( spriteOffset.y < 0 ) && ( offset.y * squareSize != spriteOffset.y ) ) { --offset.y; } const Point spriteRelativeOffset{ spriteOffset.x - offset.x * squareSize, spriteOffset.y - offset.y * squareSize }; const Point stepPerDirection{ ( original.width() + spriteRelativeOffset.x + squareSize - 1 ) / squareSize, ( original.height() + spriteRelativeOffset.y + squareSize - 1 ) / squareSize }; assert( stepPerDirection.x > 0 && stepPerDirection.y > 0 ); const Rect relativeROI( spriteRelativeOffset.x, spriteRelativeOffset.y, original.width(), original.height() ); for ( int32_t y = 0; y < stepPerDirection.y; ++y ) { for ( int32_t x = 0; x < stepPerDirection.x; ++x ) { const Rect roi( x * squareSize, y * squareSize, squareSize, squareSize ); const Rect intersection = relativeROI ^ roi; assert( intersection.width > 0 && intersection.height > 0 ); outputSquareId.emplace_back( offset + Point( x, y ) ); outputImageInfo.emplace_back( fheroes2::Point( intersection.x - roi.x, intersection.y - roi.y ), fheroes2::Rect( intersection.x - spriteRelativeOffset.x, intersection.y - spriteRelativeOffset.y, intersection.width, intersection.height ) ); } } } Image ExtractCommonPattern( const std::vector & input ) { if ( input.empty() ) { return {}; } assert( input.front() != nullptr ); if ( input.size() == 1 ) { return *input.front(); } if ( input.front()->empty() ) { return {}; } const int32_t inputFrontWidth = input.front()->width(); const int32_t inputFrontHeight = input.front()->height(); for ( const Image * image : input ) { assert( image != nullptr ); if ( image->width() != inputFrontWidth || image->height() != inputFrontHeight ) { return {}; } } std::vector imageIn( input.size() ); std::vector transformIn( input.size() ); for ( size_t i = 0; i < input.size(); ++i ) { imageIn[i] = input[i]->image(); transformIn[i] = input[i]->singleLayer() ? nullptr : input[i]->transform(); } Image out( inputFrontWidth, inputFrontHeight ); out.reset(); uint8_t * imageOut = out.image(); uint8_t * transformOut = out.transform(); const uint8_t * imageOutEnd = imageOut + out.width() * out.height(); bool isEqual = false; for ( ; imageOut != imageOutEnd; ++imageOut, ++transformOut ) { isEqual = true; for ( size_t i = 1; i < input.size(); ++i ) { if ( *imageIn[0] != *imageIn[i] || ( transformIn[0] != nullptr && transformIn[i] != nullptr && ( *transformIn[0] != *transformIn[i] ) ) ) { isEqual = false; break; } } if ( isEqual ) { *imageOut = *imageIn[0]; *transformOut = ( transformIn[0] == nullptr ) ? 0 : *transformIn[0]; } for ( size_t i = 0; i < input.size(); ++i ) { ++imageIn[i]; if ( transformIn[i] != nullptr ) { ++transformIn[i]; } } } return out; } void Fill( Image & image, int32_t x, int32_t y, int32_t width, int32_t height, const uint8_t colorId ) { if ( !Verify( image, x, y, width, height ) ) { return; } if ( image.width() == width && image.height() == height ) { // We fill the whole image. image.fill( colorId ); return; } const int32_t imageWidth = image.width(); uint8_t * imageY = image.image() + y * imageWidth + x; const uint8_t * imageYEnd = imageY + height * imageWidth; if ( image.singleLayer() ) { for ( ; imageY != imageYEnd; imageY += imageWidth ) { memset( imageY, colorId, width ); } } else { uint8_t * transformY = image.transform() + static_cast( y ) * imageWidth + x; for ( ; imageY != imageYEnd; imageY += imageWidth, transformY += imageWidth ) { memset( imageY, colorId, width ); memset( transformY, static_cast( 0 ), width ); } } } void FillTransform( Image & image, int32_t x, int32_t y, int32_t width, int32_t height, const uint8_t transformId ) { if ( !Verify( image, x, y, width, height ) || image.singleLayer() ) { return; } const int32_t imageWidth = image.width(); uint8_t * imageY = image.image() + y * imageWidth + x; uint8_t * transformY = image.transform() + y * imageWidth + x; const uint8_t * imageYEnd = imageY + height * imageWidth; for ( ; imageY != imageYEnd; imageY += imageWidth, transformY += imageWidth ) { memset( imageY, static_cast( 0 ), width ); memset( transformY, transformId, width ); } } Image FilterOnePixelNoise( const Image & input ) { if ( input.width() < 3 || input.height() < 3 ) { return input; } const int32_t width = input.width(); const int32_t height = input.height(); const bool isSingleLayer = input.singleLayer(); Image output; if ( isSingleLayer ) { output._disableTransformLayer(); } output.resize( width, height ); output.reset(); const uint8_t * imageInY = input.image(); uint8_t * imageOutY = output.image(); if ( isSingleLayer ) { for ( int32_t y = 0; y < height; ++y ) { for ( int32_t x = 0; x < width; ++x ) { if ( ( x == 0 || x == width - 1 ) && ( y == 0 || y == height - 1 ) ) { *( imageOutY + x ) = *( imageInY + x ); } } imageInY += width; imageOutY += width; } } else { const uint8_t * transformInY = input.transform(); uint8_t * transformOutY = output.transform(); for ( int32_t y = 0; y < height; ++y ) { const uint8_t * transformInX = transformInY; for ( int32_t x = 0; x < width; ++x ) { if ( *transformInX == 0 && ( x == 0 || x == width - 1 || *( transformInX - 1 ) == 0 || *( transformInX + 1 ) == 0 ) && ( y == 0 || y == height - 1 || *( transformInX - width ) == 0 || *( transformInX + width ) == 0 ) ) { *( transformOutY + x ) = 0; *( imageOutY + x ) = *( imageInY + x ); } ++transformInX; } imageInY += width; transformInY += width; imageOutY += width; transformOutY += width; } } return output; } bool FitToRoi( const Image & in, Point & inPos, const Image & out, Point & outPos, Size & outputSize, const Rect & outputRoi ) { if ( !Validate( out, outputRoi.x, outputRoi.y, outputRoi.width, outputRoi.height ) ) { return false; } outPos.x -= outputRoi.x; outPos.y -= outputRoi.y; if ( !Verify( inPos.x, inPos.y, outPos.x, outPos.y, outputSize.width, outputSize.height, in.width(), in.height(), outputRoi.width, outputRoi.height ) ) { return false; } outPos.x += outputRoi.x; outPos.y += outputRoi.y; return true; } Image Flip( const Image & in, const bool horizontally, const bool vertically ) { if ( in.empty() ) { return {}; } const int32_t width = in.width(); const int32_t height = in.height(); Image out; if ( in.singleLayer() ) { // Make the result of Flip to have the same layers as the input image. out._disableTransformLayer(); } out.resize( width, height ); if ( !horizontally && !vertically ) { Copy( in, out ); return out; } Flip( in, 0, 0, out, 0, 0, width, height, horizontally, vertically ); return out; } void Flip( const Image & in, int32_t inX, int32_t inY, Image & out, int32_t outX, int32_t outY, int32_t width, int32_t height, const bool horizontally, const bool vertically ) { if ( !Verify( in, inX, inY, out, outX, outY, width, height ) ) { return; } if ( !horizontally && !vertically ) { // No flip is required. Copy( in, inX, inY, out, outX, outY, width, height ); return; } // This assertion is needed to convince SonarQube that we will not dereference the null pointer to 'in' and 'out' images data. assert( !in.empty() && !out.empty() ); const int32_t widthIn = in.width(); const int32_t widthOut = out.width(); const int32_t offsetOut = outY * widthOut + outX; const int32_t offsetIn = inY * widthIn + inX; uint8_t * imageOutY = out.image() + offsetOut; const uint8_t * imageOutYEnd = imageOutY + static_cast( widthOut ) * height; // If image is single-layer then pointer to its transform layer is 'nullptr'. uint8_t * transformOutY = out.singleLayer() ? nullptr : out.transform() + offsetOut; if ( horizontally && !vertically ) { const uint8_t * imageInY = in.image() + offsetIn + width - 1; if ( in.singleLayer() ) { for ( ; imageOutY != imageOutYEnd; imageOutY += widthOut, imageInY += widthIn ) { uint8_t * imageOutX = imageOutY; const uint8_t * imageOutXEnd = imageOutX + width; const uint8_t * imageInX = imageInY; for ( ; imageOutX != imageOutXEnd; ++imageOutX, --imageInX ) { *imageOutX = *imageInX; } if ( transformOutY != nullptr ) { // Set 'out' image transform data not to skip image data. memset( transformOutY, static_cast( 0 ), width ); transformOutY += widthOut; } } } else { const uint8_t * transformInY = in.transform() + offsetIn + width - 1; for ( ; imageOutY != imageOutYEnd; imageOutY += widthOut, imageInY += widthIn, transformInY += widthIn ) { uint8_t * imageOutX = imageOutY; const uint8_t * imageOutXEnd = imageOutX + width; const uint8_t * imageInX = imageInY; const uint8_t * transformInX = transformInY; if ( transformOutY == nullptr ) { for ( ; imageOutX != imageOutXEnd; ++imageOutX, --imageInX, --transformInX ) { // Copy image data only for non-transparent pixels. if ( *transformInX == 0 ) { *imageOutX = *imageInX; } } } else { // 'in' and 'out' images are with transform layer. uint8_t * transformOutX = transformOutY; for ( ; imageOutX != imageOutXEnd; ++imageOutX, ++transformOutX, --imageInX, --transformInX ) { *imageOutX = *imageInX; *transformOutX = *transformInX; } transformOutY += widthOut; } } } } else if ( !horizontally && vertically ) { const uint8_t * imageInY = in.image() + offsetIn + static_cast( height - 1 ) * widthIn; if ( in.singleLayer() ) { for ( ; imageOutY != imageOutYEnd; imageOutY += widthOut, imageInY -= widthIn ) { memcpy( imageOutY, imageInY, static_cast( width ) ); if ( transformOutY != nullptr ) { // Set 'out' image transform data not to skip image data. memset( transformOutY, static_cast( 0 ), width ); transformOutY += widthOut; } } } else { const uint8_t * transformInY = in.transform() + offsetIn + static_cast( height - 1 ) * widthIn; if ( transformOutY == nullptr ) { for ( ; imageOutY != imageOutYEnd; imageOutY += widthOut, imageInY -= widthIn, transformInY -= widthIn ) { uint8_t * imageOutX = imageOutY; const uint8_t * imageOutXEnd = imageOutX + width; const uint8_t * imageInX = imageInY; const uint8_t * transformInX = transformInY; for ( ; imageOutX != imageOutXEnd; ++imageOutX, ++imageInX, ++transformInX ) { // Copy image data only for non-transparent pixels. if ( *transformInX == 0 ) { *imageOutX = *imageInX; } } } } else { // 'in' and 'out' images are with transform layer. for ( ; imageOutY != imageOutYEnd; imageOutY += widthOut, transformOutY += widthOut, imageInY -= widthIn, transformInY -= widthIn ) { memcpy( imageOutY, imageInY, static_cast( width ) ); memcpy( transformOutY, transformInY, static_cast( width ) ); } } } } else { // Flip horizontally and vertically. if ( in.singleLayer() ) { const uint8_t * imageInY = in.image() + offsetIn + static_cast( height - 1 ) * widthIn + widthIn - 1; for ( ; imageOutY != imageOutYEnd; imageOutY += widthOut, imageInY -= widthIn ) { uint8_t * imageOutX = imageOutY; const uint8_t * imageOutXEnd = imageOutX + width; const uint8_t * imageInX = imageInY; for ( ; imageOutX != imageOutXEnd; ++imageOutX, --imageInX ) { *imageOutX = *imageInX; } if ( transformOutY != nullptr ) { // 'in' image is double-layer: set transform data not to skip image data. memset( transformOutY, static_cast( 0 ), width ); transformOutY += widthOut; } } } else { const uint8_t * imageInY = in.image() + offsetIn + static_cast( height - 1 ) * widthIn + widthIn - 1; const uint8_t * transformInY = in.transform() + offsetIn + static_cast( height - 1 ) * widthIn + widthIn - 1; for ( ; imageOutY != imageOutYEnd; imageOutY += widthOut, imageInY -= widthIn, transformInY -= widthIn ) { uint8_t * imageOutX = imageOutY; const uint8_t * imageOutXEnd = imageOutX + width; const uint8_t * imageInX = imageInY; const uint8_t * transformInX = transformInY; if ( transformOutY == nullptr ) { for ( ; imageOutX != imageOutXEnd; ++imageOutX, --imageInX, --transformInX ) { // Copy image data only for non-transparent pixels. if ( *transformInX == 0 ) { *imageOutX = *imageInX; } } } else { // 'in' and 'out' images are with transform layer. for ( uint8_t * transformOutX = transformOutY; imageOutX != imageOutXEnd; ++imageOutX, ++transformOutX, --imageInX, --transformInX ) { *imageOutX = *imageInX; *transformOutX = *transformInX; } transformOutY += widthOut; } } } } } Rect GetActiveROI( const Image & image, const uint8_t minTransformValue ) { if ( image.empty() || image.singleLayer() ) { return {}; } const int32_t width = image.width(); const int32_t height = image.height(); Rect area( -1, -1, -1, -1 ); // Top border const uint8_t * inY = image.transform(); for ( int32_t y = 0; y < height; ++y, inY += width ) { const uint8_t * inX = inY; for ( int32_t x = 0; x < width; ++x, ++inX ) { if ( *inX == 0 || *inX >= minTransformValue ) { area.y = y; break; } } if ( area.y >= 0 ) { break; } } if ( area.y < 0 ) { return {}; } // Bottom border inY = image.transform() + width * ( height - 1 ); for ( int32_t y = height - 1; y > area.y; --y, inY -= width ) { const uint8_t * inX = inY; for ( int32_t x = 0; x < width; ++x, ++inX ) { if ( *inX == 0 || *inX >= minTransformValue ) { area.height = y - area.y + 1; break; } } if ( area.height >= 0 ) { break; } } // Left border const uint8_t * inX = image.transform() + width * area.y; for ( int32_t x = 0; x < width; ++x, ++inX ) { inY = inX; for ( int32_t y = 0; y < area.height; ++y, inY += width ) { if ( *inY == 0 || *inY >= minTransformValue ) { area.x = x; break; } } if ( area.x >= 0 ) { break; } } // Right border inX = image.transform() + width * area.y + width - 1; for ( int32_t x = width - 1; x >= area.x; --x, --inX ) { inY = inX; for ( int32_t y = 0; y < area.height; ++y, inY += width ) { if ( *inY == 0 || *inY >= minTransformValue ) { area.width = x - area.x + 1; break; } } if ( area.width >= 0 ) { break; } } return area; } uint8_t GetColorId( const uint8_t red, const uint8_t green, const uint8_t blue ) { return GetPALColorId( red / 4, green / 4, blue / 4 ); } std::vector getTransformTable( const Image & in, const Image & out, int32_t x, int32_t y, int32_t width, int32_t height ) { std::vector table( 256 ); for ( size_t i = 0; i < table.size(); ++i ) { table[i] = static_cast( i ); } if ( !Verify( in, x, y, width, height ) || in.singleLayer() || out.singleLayer() ) { return table; } if ( in.width() != out.width() || in.height() != out.height() ) { return table; } const int32_t imageWidth = in.width(); const int32_t offset = y * imageWidth + x; const uint8_t * imageInY = in.image() + offset; const uint8_t * imageInYEnd = imageInY + height * imageWidth; const uint8_t * imageOutY = out.image() + offset; const uint8_t * transformInY = in.transform() + offset; const uint8_t * transformOutY = out.transform() + offset; for ( ; imageInY != imageInYEnd; imageInY += imageWidth, transformInY += imageWidth, imageOutY += imageWidth, transformOutY += imageWidth ) { const uint8_t * imageInX = imageInY; const uint8_t * transformInX = transformInY; const uint8_t * imageOutX = imageOutY; const uint8_t * transformOutX = transformOutY; const uint8_t * imageInXEnd = imageInX + width; for ( ; imageInX != imageInXEnd; ++imageInX, ++transformInX, ++imageOutX, ++transformOutX ) { if ( *transformInX != *transformOutX ) { continue; } if ( *transformInX != 0 ) { continue; } table[*imageInX] = *imageOutX; } } return table; } Sprite makeShadow( const Sprite & in, const Point & shadowOffset, const uint8_t transformId ) { if ( in.empty() || shadowOffset.x > 0 || shadowOffset.y < 0 ) { return {}; } const int32_t width = in.width(); const int32_t height = in.height(); // Shadow has (-x, +y) offset. Sprite out( width - shadowOffset.x, height + shadowOffset.y, in.x() + shadowOffset.x, in.y() ); out.reset(); assert( !out.empty() ); if ( in.singleLayer() ) { // In this case we add a shadow of the fully non-transparent rectangular 'in' image. FillTransform( out, 0, shadowOffset.y, width, height, transformId ); return out; } const int32_t widthOut = out.width(); const uint8_t * transformInY = in.transform(); const uint8_t * transformInYEnd = transformInY + width * height; uint8_t * transformOutY = out.transform() + shadowOffset.y * widthOut; for ( ; transformInY != transformInYEnd; transformInY += width, transformOutY += widthOut ) { const uint8_t * transformInX = transformInY; uint8_t * transformOutX = transformOutY; const uint8_t * transformInXEnd = transformInX + width; for ( ; transformInX != transformInXEnd; ++transformInX, ++transformOutX ) { if ( *transformInX == 0 ) { *transformOutX = transformId; } } } return out; } void MaskTransformLayer( const Image & mask, int32_t maskX, int32_t maskY, Image & out, int32_t outX, int32_t outY, int32_t width, int32_t height ) { if ( !Verify( mask, maskX, maskY, out, outX, outY, width, height ) || mask.singleLayer() || out.singleLayer() ) { return; } const int32_t widthMask = mask.width(); const int32_t widthOut = out.width(); const uint8_t * imageMaskY = mask.transform() + maskY * widthMask + maskX; uint8_t * imageOutY = out.transform() + outY * widthOut + outX; const uint8_t * imageMaskYEnd = imageMaskY + height * widthMask; for ( ; imageMaskY != imageMaskYEnd; imageMaskY += widthMask, imageOutY += widthOut ) { const uint8_t * imageMaskX = imageMaskY; uint8_t * imageOutX = imageOutY; const uint8_t * imageMaskXEnd = imageMaskX + width; for ( ; imageMaskX != imageMaskXEnd; ++imageMaskX, ++imageOutX ) { if ( *imageMaskX == 0 ) { *imageOutX = 1; } } } } void ReplaceColorId( Image & image, const uint8_t oldColorId, const uint8_t newColorId ) { if ( image.empty() ) { return; } uint8_t * data = image.image(); const uint8_t * dataEnd = data + image.width() * image.height(); for ( ; data != dataEnd; ++data ) { if ( *data == oldColorId ) { *data = newColorId; } } } void ReplaceColorIdByTransformId( Image & image, const uint8_t colorId, const uint8_t transformId ) { if ( transformId > 15 || image.singleLayer() ) { return; } const int32_t width = image.width(); const int32_t height = image.height(); const uint8_t * imageIn = image.image(); uint8_t * transformIn = image.transform(); const uint8_t * imageInEnd = imageIn + height * width; for ( ; imageIn != imageInEnd; ++imageIn, ++transformIn ) { if ( *transformIn == 0 && *imageIn == colorId ) { // modify pixels with transform value 0 *transformIn = transformId; } } } void ReplaceTransformIdByColorId( Image & image, const uint8_t transformId, const uint8_t colorId ) { if ( image.empty() || image.singleLayer() ) { return; } const int32_t size = image.width() * image.height(); uint8_t * imageIn = image.image(); uint8_t * transformIn = image.transform(); const uint8_t * imageInEnd = imageIn + size; for ( ; imageIn != imageInEnd; ++imageIn, ++transformIn ) { if ( *transformIn == transformId ) { *transformIn = 0U; *imageIn = colorId; } } } void Resize( const Image & in, Image & out ) { if ( in.empty() || out.empty() ) { return; } Resize( in, 0, 0, in.width(), in.height(), out, 0, 0, out.width(), out.height() ); } void Resize( const Image & in, const int32_t inX, const int32_t inY, const int32_t widthRoiIn, const int32_t heightRoiIn, Image & out, const int32_t outX, const int32_t outY, const int32_t widthRoiOut, const int32_t heightRoiOut ) { if ( !Validate( in, inX, inY, widthRoiIn, heightRoiIn ) || !Validate( out, outX, outY, widthRoiOut, heightRoiOut ) ) { return; } if ( widthRoiIn == widthRoiOut && heightRoiIn == heightRoiOut ) { Copy( in, inX, inY, out, outX, outY, widthRoiIn, heightRoiIn ); return; } const int32_t widthIn = in.width(); const int32_t widthOut = out.width(); const int32_t offsetInY = inY * widthIn + inX; const int32_t offsetOutY = outY * widthOut + outX; const uint8_t * imageInY = in.image() + offsetInY; uint8_t * imageOutY = out.image() + offsetOutY; const uint8_t * imageOutYEnd = imageOutY + static_cast( widthOut ) * heightRoiOut; int32_t idY = 0; // Pre-calculation of X position std::vector positionX( widthRoiOut ); for ( int32_t x = 0; x < widthRoiOut; ++x ) { positionX[x] = ( x * widthRoiIn ) / widthRoiOut; } if ( in.singleLayer() ) { if ( !out.singleLayer() ) { // In this case we make the output image fully non-transparent in the given output area. uint8_t * transformY = out.transform() + static_cast( outY ) * widthOut + outX; const uint8_t * transformYEnd = transformY + static_cast( heightRoiOut ) * widthOut; for ( ; transformY != transformYEnd; transformY += widthOut ) { memset( transformY, static_cast( 0 ), widthRoiOut ); } } for ( ; imageOutY != imageOutYEnd; imageOutY += widthOut, ++idY ) { uint8_t * imageOutX = imageOutY; const int32_t offset = ( ( idY * heightRoiIn ) / heightRoiOut ) * widthIn; const uint8_t * imageInX = imageInY + offset; for ( const int32_t posX : positionX ) { *imageOutX = *( imageInX + posX ); ++imageOutX; } } } else if ( out.singleLayer() ) { const uint8_t * transformInY = in.transform() + offsetInY; for ( ; imageOutY != imageOutYEnd; imageOutY += widthOut, ++idY ) { uint8_t * imageOutX = imageOutY; const int32_t offset = ( ( idY * heightRoiIn ) / heightRoiOut ) * widthIn; const uint8_t * imageInX = imageInY + offset; const uint8_t * transformInX = transformInY + offset; for ( const int32_t posX : positionX ) { const uint8_t * transformIn = transformInX + posX; if ( *transformIn > 0 ) { if ( *transformIn != 1 ) { // Apply a transformation. *imageOutX = *( transformTable + static_cast( *transformIn ) * 256 + *imageOutX ); } } else { *imageOutX = *( imageInX + posX ); } ++imageOutX; } } } else { // Both 'in' and 'out' are double-layer. const uint8_t * transformInY = in.transform() + offsetInY; uint8_t * transformOutY = out.transform() + offsetOutY; for ( ; imageOutY != imageOutYEnd; imageOutY += widthOut, transformOutY += widthOut, ++idY ) { uint8_t * imageOutX = imageOutY; uint8_t * transformOutX = transformOutY; const int32_t offset = ( ( idY * heightRoiIn ) / heightRoiOut ) * widthIn; const uint8_t * imageInX = imageInY + offset; const uint8_t * transformInX = transformInY + offset; for ( const int32_t posX : positionX ) { *imageOutX = *( imageInX + posX ); *transformOutX = *( transformInX + posX ); ++imageOutX; ++transformOutX; } } } } void SetPixel( Image & image, const int32_t x, const int32_t y, const uint8_t value ) { if ( image.empty() || x >= image.width() || y >= image.height() || x < 0 || y < 0 ) { return; } const int32_t offset = y * image.width() + x; *( image.image() + offset ) = value; if ( !image.singleLayer() ) { *( image.transform() + offset ) = 0; } } void SetPixel( Image & image, const std::vector & points, const uint8_t value ) { if ( image.empty() ) { return; } const int32_t width = image.width(); const int32_t height = image.height(); const bool isDoubleLayer = !image.singleLayer(); for ( const Point & point : points ) { if ( point.x >= width || point.y >= height || point.x < 0 || point.y < 0 ) { continue; } const int32_t offset = point.y * width + point.x; *( image.image() + offset ) = value; if ( isDoubleLayer ) { *( image.transform() + offset ) = 0; } } } void SetTransformPixel( Image & image, const int32_t x, const int32_t y, const uint8_t value ) { if ( image.empty() || image.singleLayer() || x >= image.width() || y >= image.height() || x < 0 || y < 0 ) { return; } const int32_t offset = y * image.width() + x; *( image.image() + offset ) = 0; *( image.transform() + offset ) = value; } Image Stretch( const Image & in, int32_t inX, int32_t inY, int32_t widthIn, int32_t heightIn, const int32_t widthOut, const int32_t heightOut ) { if ( !Validate( in, inX, inY, widthIn, heightIn ) || widthOut <= 0 || heightOut <= 0 ) { return {}; } Image out; if ( in.singleLayer() ) { out._disableTransformLayer(); } out.resize( widthOut, heightOut ); const int32_t minWidth = widthIn < widthOut ? widthIn : widthOut; const int32_t minHeight = heightIn < heightOut ? heightIn : heightOut; const int32_t cornerWidth = minWidth / 3; const int32_t cornerHeight = minHeight / 3; const int32_t cornerX = inX + ( widthIn - cornerWidth ) / 2; const int32_t cornerY = inY + ( heightIn - cornerHeight ) / 2; const int32_t bodyWidth = minWidth - 2 * cornerWidth; const int32_t bodyHeight = minHeight - 2 * cornerHeight; const int32_t outX = ( widthOut - ( widthOut / bodyWidth ) * bodyWidth ) / 2; const int32_t outY = ( heightOut - ( heightOut / bodyHeight ) * bodyHeight ) / 2; // Create internal area if ( bodyWidth < widthOut && bodyHeight < heightOut ) { for ( int32_t y = 0; y < ( heightOut / bodyHeight ); ++y ) { for ( int32_t x = 0; x < ( widthOut / bodyWidth ); ++x ) { Copy( in, cornerX, cornerY, out, outX + x * bodyWidth, outY + y * bodyHeight, bodyWidth, bodyHeight ); } } } // Create sides // top and bottom side for ( int32_t x = 0; x < ( widthOut / bodyWidth ); ++x ) { const int32_t offsetX = outX + x * bodyWidth; Copy( in, cornerX, inY, out, offsetX, 0, bodyWidth, cornerHeight ); Copy( in, cornerX, inY + heightIn - cornerHeight, out, offsetX, heightOut - cornerHeight, bodyWidth, cornerHeight ); } // left and right sides for ( int32_t y = 0; y < ( heightOut / bodyHeight ); ++y ) { const int32_t offsetY = outY + y * bodyHeight; Copy( in, inX, cornerY, out, 0, offsetY, cornerWidth, bodyHeight ); Copy( in, inX + widthIn - cornerWidth, cornerY, out, widthOut - cornerWidth, offsetY, cornerWidth, bodyHeight ); } // Create corners // top-left corner Copy( in, inX, inY, out, 0, 0, cornerWidth, cornerHeight ); // top-right corner Copy( in, inX + widthIn - cornerWidth, inY, out, widthOut - cornerWidth, 0, cornerWidth, cornerHeight ); // bottom-left corner Copy( in, inX, inY + heightIn - cornerHeight, out, 0, heightOut - cornerHeight, cornerWidth, cornerHeight ); // bottom-right corner Copy( in, inX + widthIn - cornerWidth, inY + heightIn - cornerHeight, out, widthOut - cornerWidth, heightOut - cornerHeight, cornerWidth, cornerHeight ); return out; } void SubpixelResize( const Image & in, Image & out ) { if ( in.empty() || out.empty() ) { return; } SubpixelResize( in, 0, 0, in.width(), in.height(), out, 0, 0, out.width(), out.height() ); } void SubpixelResize( const Image & in, const int32_t inX, const int32_t inY, const int32_t widthRoiIn, const int32_t heightRoiIn, Image & out, const int32_t outX, const int32_t outY, const int32_t widthRoiOut, const int32_t heightRoiOut ) { if ( !Validate( in, inX, inY, widthRoiIn, heightRoiIn ) || !Validate( out, outX, outY, widthRoiOut, heightRoiOut ) ) { return; } if ( widthRoiIn == widthRoiOut && heightRoiIn == heightRoiOut ) { Copy( in, inX, inY, out, outX, outY, widthRoiIn, heightRoiIn ); return; } const int32_t widthIn = in.width(); const int32_t widthOut = out.width(); const int32_t offsetInY = inY * widthIn + inX; const int32_t offsetOutY = outY * widthOut + outX; const uint8_t * imageInY = in.image() + offsetInY; uint8_t * imageOutY = out.image() + offsetOutY; std::vector positionX( widthRoiOut ); for ( int32_t x = 0; x < widthRoiOut; ++x ) { positionX[x] = static_cast( x * widthRoiIn ) / widthRoiOut; } const uint8_t * gamePalette = getGamePalette(); if ( in.singleLayer() ) { if ( !out.singleLayer() ) { // In this case we make the output image fully non-transparent in the given output area. uint8_t * transformY = out.transform() + static_cast( outY ) * widthOut + outX; const uint8_t * transformYEnd = transformY + static_cast( heightRoiOut ) * widthOut; for ( ; transformY != transformYEnd; transformY += widthOut ) { memset( transformY, static_cast( 0 ), widthRoiOut ); } } for ( int32_t y = 0; y < heightRoiOut; ++y, imageOutY += widthOut ) { const double posY = static_cast( y * heightRoiIn ) / heightRoiOut; const int32_t startY = static_cast( posY ) * widthIn; const double coeffY = posY - static_cast( posY ); uint8_t * imageOutX = imageOutY; for ( int32_t x = 0; x < widthRoiOut; ++x, ++imageOutX ) { const double posX = positionX[x]; const int32_t startX = static_cast( posX ); const int32_t offsetIn = startY + startX; const uint8_t * imageInX = imageInY + offsetIn; if ( posX < widthRoiIn - 1 && posY < heightRoiIn - 1 ) { const double coeffX = posX - startX; const double coeff1 = ( 1 - coeffX ) * ( 1 - coeffY ); const double coeff2 = coeffX * ( 1 - coeffY ); const double coeff3 = ( 1 - coeffX ) * coeffY; const double coeff4 = coeffX * coeffY; const uint8_t * id1 = gamePalette + static_cast( *imageInX ) * 3; const uint8_t * id2 = gamePalette + static_cast( *( imageInX + 1 ) ) * 3; const uint8_t * id3 = gamePalette + static_cast( *( imageInX + widthIn ) ) * 3; const uint8_t * id4 = gamePalette + static_cast( *( imageInX + widthIn + 1 ) ) * 3; const double red = *id1 * coeff1 + *id2 * coeff2 + *id3 * coeff3 + *id4 * coeff4 + 0.5; const double green = *( id1 + 1 ) * coeff1 + *( id2 + 1 ) * coeff2 + *( id3 + 1 ) * coeff3 + *( id4 + 1 ) * coeff4 + 0.5; const double blue = *( id1 + 2 ) * coeff1 + *( id2 + 2 ) * coeff2 + *( id3 + 2 ) * coeff3 + *( id4 + 2 ) * coeff4 + 0.5; *imageOutX = GetPALColorId( static_cast( red ), static_cast( green ), static_cast( blue ) ); } else { *imageOutX = *imageInX; } } } } else { const uint8_t * transformInY = in.transform() + offsetInY; const bool isOutNotSingleLayer = !out.singleLayer(); uint8_t * transformOutY = isOutNotSingleLayer ? ( out.transform() + offsetOutY ) : nullptr; for ( int32_t y = 0; y < heightRoiOut; ++y, imageOutY += widthOut ) { const double posY = static_cast( y * heightRoiIn ) / heightRoiOut; const int32_t startY = static_cast( posY ) * widthIn; const double coeffY = posY - static_cast( posY ); uint8_t * imageOutX = imageOutY; uint8_t * transformOutX = transformOutY; for ( int32_t x = 0; x < widthRoiOut; ++x, ++imageOutX ) { const double posX = positionX[x]; const int32_t startX = static_cast( posX ); const int32_t offsetIn = startY + startX; const uint8_t * imageInX = imageInY + offsetIn; const uint8_t * transformInX = transformInY + offsetIn; if ( posX < widthIn - 1 && posY < heightRoiIn - 1 && *transformInX == 0 && *( transformInX + 1 ) == 0 && *( transformInX + widthRoiIn ) == 0 && *( transformInX + widthRoiIn + 1 ) == 0 ) { const double coeffX = posX - startX; const double coeff1 = ( 1 - coeffX ) * ( 1 - coeffY ); const double coeff2 = coeffX * ( 1 - coeffY ); const double coeff3 = ( 1 - coeffX ) * coeffY; const double coeff4 = coeffX * coeffY; const uint8_t * id1 = gamePalette + static_cast( *imageInX ) * 3; const uint8_t * id2 = gamePalette + static_cast( *( imageInX + 1 ) ) * 3; const uint8_t * id3 = gamePalette + static_cast( *( imageInX + widthIn ) ) * 3; const uint8_t * id4 = gamePalette + static_cast( *( imageInX + widthIn + 1 ) ) * 3; const double red = *id1 * coeff1 + *id2 * coeff2 + *id3 * coeff3 + *id4 * coeff4 + 0.5; const double green = *( id1 + 1 ) * coeff1 + *( id2 + 1 ) * coeff2 + *( id3 + 1 ) * coeff3 + *( id4 + 1 ) * coeff4 + 0.5; const double blue = *( id1 + 2 ) * coeff1 + *( id2 + 2 ) * coeff2 + *( id3 + 2 ) * coeff3 + *( id4 + 2 ) * coeff4 + 0.5; *imageOutX = GetPALColorId( static_cast( red ), static_cast( green ), static_cast( blue ) ); } else { if ( isOutNotSingleLayer || *transformInX == 0 ) { // Output image is double-layer or single-layer with non-transparent current pixel. *imageOutX = *imageInX; } else if ( *transformInX != 1 ) { // Apply a transformation. *imageOutX = *( transformTable + static_cast( *transformInX ) * 256 + *imageOutX ); } } if ( isOutNotSingleLayer ) { *transformOutX = *transformInX; ++transformOutX; } } if ( isOutNotSingleLayer ) { transformOutY += widthOut; } } } } void Transpose( const Image & in, Image & out ) { assert( !out.empty() ); if ( in.empty() || in.width() != out.height() || in.height() != out.width() ) { out.reset(); return; } const int32_t width = in.width(); const int32_t height = in.height(); const uint8_t * imageInY = in.image(); const uint8_t * imageInYEnd = imageInY + width * height; uint8_t * imageOutX = out.image(); if ( in.singleLayer() ) { if ( out.singleLayer() ) { for ( ; imageInY != imageInYEnd; imageInY += width, ++imageOutX ) { const uint8_t * imageInX = imageInY; const uint8_t * imageInXEnd = imageInX + width; uint8_t * imageOutY = imageOutX; for ( ; imageInX != imageInXEnd; ++imageInX, imageOutY += height ) { *imageOutY = *imageInX; } } } else { uint8_t * transformOutX = out.transform(); for ( ; imageInY != imageInYEnd; imageInY += width, ++imageOutX, ++transformOutX ) { const uint8_t * imageInX = imageInY; const uint8_t * imageInXEnd = imageInX + width; uint8_t * imageOutY = imageOutX; uint8_t * transformOutY = transformOutX; for ( ; imageInX != imageInXEnd; ++imageInX, imageOutY += height, transformOutY += height ) { *imageOutY = *imageInX; *transformOutY = 0; } } } } else { const uint8_t * transformInY = in.transform(); if ( out.singleLayer() ) { for ( ; imageInY != imageInYEnd; imageInY += width, transformInY += width, ++imageOutX ) { const uint8_t * imageInX = imageInY; const uint8_t * imageInXEnd = imageInX + width; uint8_t * imageOutY = imageOutX; const uint8_t * transformInX = transformInY; for ( ; imageInX != imageInXEnd; ++imageInX, ++transformInX, imageOutY += height ) { // Copy image data only for non-transparent pixels. if ( *transformInX == 0 ) { *imageOutY = *imageInX; } } } } else { uint8_t * transformOutX = out.transform(); for ( ; imageInY != imageInYEnd; imageInY += width, transformInY += width, ++imageOutX, ++transformOutX ) { const uint8_t * imageInX = imageInY; const uint8_t * imageInXEnd = imageInX + width; uint8_t * imageOutY = imageOutX; const uint8_t * transformInX = transformInY; uint8_t * transformOutY = transformOutX; for ( ; imageInX != imageInXEnd; ++imageInX, ++transformInX, imageOutY += height, transformOutY += height ) { *imageOutY = *imageInX; *transformOutY = *transformInX; } } } } } void updateShadow( Image & image, const Point & shadowOffset, const uint8_t transformId, const bool connectCorners ) { const int32_t imageWidth = image.width(); const int32_t imageHeight = image.height(); if ( image.empty() || image.singleLayer() || ( std::abs( shadowOffset.x ) >= imageWidth ) || ( std::abs( shadowOffset.y ) >= imageHeight ) || shadowOffset == Point() ) { return; } const int32_t width = imageWidth - std::abs( shadowOffset.x ); const int32_t height = imageHeight - std::abs( shadowOffset.y ); const uint8_t * transformInY = image.transform(); uint8_t * transformOutY = image.transform(); int32_t cornerOffsetX; int32_t cornerOffsetY; if ( shadowOffset.x > 0 ) { transformOutY += shadowOffset.x; cornerOffsetX = 1; } else { transformInY -= shadowOffset.x; cornerOffsetX = -1; } if ( shadowOffset.y > 0 ) { transformOutY += imageWidth * shadowOffset.y; cornerOffsetY = imageWidth; } else { transformInY -= imageWidth * shadowOffset.y; cornerOffsetY = -imageWidth; } const uint8_t * transformOutYEnd = transformOutY + imageWidth * height; for ( ; transformOutY != transformOutYEnd; transformInY += imageWidth, transformOutY += imageWidth ) { const uint8_t * transformInX = transformInY; uint8_t * transformOutX = transformOutY; const uint8_t * transformOutXEnd = transformOutX + width; for ( ; transformOutX != transformOutXEnd; ++transformInX, ++transformOutX ) { if ( *transformOutX == 1 && ( *transformInX == 0 || ( connectCorners && *( transformInX + cornerOffsetX ) == 0 && *( transformInX + cornerOffsetY ) == 0 ) ) ) { // If 'connectCorners' is 'true' and when there are two pixels adjacent diagonally, // we also create a "shadow" pixel in the corner that is closer to the image. // Doing so there will be no "empty" pixels between the image and its shadow (for 1 pixel offset case). *transformOutX = transformId; } } } } } fheroes2-1.0.12+dfsg/src/engine/image.h000066400000000000000000000342611456075706000175360ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include #include #include "math_base.h" namespace fheroes2 { // Image always contains an image layer and if image is not a single-layer then also a transform layer. // - image layer contains visible pixels which are copy to a destination image // - transform layer is used to apply some transformation to an image on which we draw the current one. For example, shadowing class Image { public: Image() = default; Image( const int32_t width_, const int32_t height_ ); Image( const Image & image_ ); Image( Image && image_ ) noexcept; virtual ~Image() = default; Image & operator=( const Image & image_ ); Image & operator=( Image && image_ ) noexcept; virtual void resize( const int32_t width_, const int32_t height_ ); // It's safe to cast to uint32_t as width and height are always >= 0 int32_t width() const { return _width; } int32_t height() const { return _height; } virtual uint8_t * image(); virtual const uint8_t * image() const; uint8_t * transform() { return _data.get() + width() * height(); } const uint8_t * transform() const { return _data.get() + width() * height(); } bool empty() const { return !_data; } void reset(); // makes image fully transparent (transform layer is set to 1) void clear(); // makes the image empty // Fill 'image' layer with given value, setting 'transform' layer to 0. void fill( const uint8_t value ); // This is an optional indicator for image processing functions. // The whole image still consists of 2 layers but transform layer might be ignored in computations bool singleLayer() const { return _singleLayer; } // BE CAREFUL! This method disables transform layer usage. Use only for display / video related images which are for end rendering purposes! // The name of this method starts from _ on purpose to do not mix with other public methods. void _disableTransformLayer() { _singleLayer = true; } private: void copy( const Image & image ); int32_t _width{ 0 }; int32_t _height{ 0 }; std::unique_ptr _data; // holds 2 image layers // Only for images which are not used for any other operations except displaying on screen. bool _singleLayer{ false }; }; class Sprite : public Image { public: Sprite() = default; Sprite( const int32_t width_, const int32_t height_, const int32_t x_ = 0, const int32_t y_ = 0 ); Sprite( const Image & image, const int32_t x_ = 0, const int32_t y_ = 0 ); Sprite( const Sprite & sprite ) = default; Sprite( Sprite && sprite ) noexcept; ~Sprite() override = default; Sprite & operator=( const Sprite & sprite ); Sprite & operator=( Sprite && sprite ) noexcept; int32_t x() const { return _x; } int32_t y() const { return _y; } virtual void setPosition( const int32_t x_, const int32_t y_ ); private: int32_t _x{ 0 }; int32_t _y{ 0 }; }; // This class is used in situations when we draw a window within another window class ImageRestorer { public: explicit ImageRestorer( Image & image ); ImageRestorer( Image & image, const int32_t x_, const int32_t y_, const int32_t width, const int32_t height ); ImageRestorer( const ImageRestorer & ) = delete; // Restores the original image if necessary, see the implementation for details ~ImageRestorer(); void update( const int32_t x_, const int32_t y_, const int32_t width, const int32_t height ); int32_t x() const { return _x; } int32_t y() const { return _y; } int32_t width() const { return _width; } int32_t height() const { return _height; } Rect rect() const { return { _x, _y, _width, _height }; } void restore(); void reset() { _isRestored = true; } private: Image & _image; Image _copy; int32_t _x{ 0 }; int32_t _y{ 0 }; int32_t _width{ 0 }; int32_t _height{ 0 }; void _updateRoi(); bool _isRestored{ false }; }; // Apply shadow that gradually reduces strength using 'in' image shape. Shadow is applied to the 'out' image. void addGradientShadow( const Sprite & in, Image & out, const Point & outPos, const Point & shadowOffset ); // Generates a new image with a shadow of the shape of existing image. Shadow must have only (-x, +y) offset. Sprite addShadow( const Sprite & in, const Point & shadowOffset, const uint8_t transformId ); // Replace a particular pixel value by transparency value (transform layer value will be 1) void AddTransparency( Image & image, const uint8_t valueToReplace ); // make sure that output image's transform layer doesn't have skipping values (transform == 1) void AlphaBlit( const Image & in, Image & out, const uint8_t alphaValue, const bool flip = false ); void AlphaBlit( const Image & in, Image & out, int32_t outX, int32_t outY, const uint8_t alphaValue, const bool flip = false ); void AlphaBlit( const Image & in, int32_t inX, int32_t inY, Image & out, int32_t outX, int32_t outY, int32_t width, int32_t height, const uint8_t alphaValue, const bool flip = false ); // apply palette only for image layer, it doesn't affect transform part void ApplyPalette( Image & image, const std::vector & palette ); void ApplyPalette( const Image & in, Image & out, const std::vector & palette ); void ApplyPalette( Image & image, const uint8_t paletteId ); void ApplyPalette( const Image & in, Image & out, const uint8_t paletteId ); void ApplyPalette( const Image & in, int32_t inX, int32_t inY, Image & out, int32_t outX, int32_t outY, int32_t width, int32_t height, uint8_t paletteId ); void ApplyPalette( const Image & in, int32_t inX, int32_t inY, Image & out, int32_t outX, int32_t outY, int32_t width, int32_t height, const std::vector & palette ); void ApplyAlpha( const Image & in, Image & out, const uint8_t alpha ); void ApplyAlpha( const Image & in, int32_t inX, int32_t inY, Image & out, int32_t outX, int32_t outY, int32_t width, int32_t height, const uint8_t alpha ); void ApplyTransform( Image & image, int32_t x, int32_t y, int32_t width, int32_t height, const uint8_t transformId ); // draw one image onto another void Blit( const Image & in, Image & out, const bool flip = false ); void Blit( const Image & in, Image & out, int32_t outX, int32_t outY, const bool flip = false ); void Blit( const Image & in, int32_t inX, int32_t inY, Image & out, int32_t outX, int32_t outY, int32_t width, int32_t height, const bool flip = false ); // inPos must contain non-negative values void Blit( const Image & in, const Point & inPos, Image & out, const Point & outPos, const Size & size, bool flip = false ); void Copy( const Image & in, Image & out ); void Copy( const Image & in, int32_t inX, int32_t inY, Image & out, const Rect & outRoi ); void Copy( const Image & in, int32_t inX, int32_t inY, Image & out, int32_t outX, int32_t outY, int32_t width, int32_t height ); // Copies transform the layer from in to out. Both images must be of the same size. void CopyTransformLayer( const Image & in, Image & out ); Sprite CreateContour( const Image & image, const uint8_t value ); // Make a transition to "in" image from left to right or vertically - from top to bottom using dithering (https://en.wikipedia.org/wiki/Dither). // The direction of transition can be reversed. void CreateDitheringTransition( const Image & in, int32_t inX, int32_t inY, Image & out, int32_t outX, int32_t outY, int32_t width, int32_t height, const bool isVertical, const bool isReverse ); Sprite Crop( const Image & image, int32_t x, int32_t y, int32_t width, int32_t height ); // skipFactor is responsible for non-solid line. You can interpret it as skip every N pixel void DrawBorder( Image & image, const uint8_t value, const uint32_t skipFactor = 0 ); // roi is an optional parameter when you need to draw in a small than image area void DrawLine( Image & image, const Point & start, const Point & end, const uint8_t value, const Rect & roi = Rect() ); void DrawRect( Image & image, const Rect & roi, const uint8_t value ); void DivideImageBySquares( const Point & spriteOffset, const Image & original, const int32_t squareSize, std::vector & outputSquareId, std::vector> & outputImageInfo ); // Every image in the array must be the same size. Make sure that pointers aren't nullptr! Image ExtractCommonPattern( const std::vector & input ); // Please use GetColorId function if you want to use an RGB value void Fill( Image & image, int32_t x, int32_t y, int32_t width, int32_t height, const uint8_t colorId ); void FillTransform( Image & image, int32_t x, int32_t y, int32_t width, int32_t height, const uint8_t transformId ); Image FilterOnePixelNoise( const Image & input ); bool FitToRoi( const Image & in, Point & inPos, const Image & out, Point & outPos, Size & outputSize, const Rect & outputRoi ); Image Flip( const Image & in, const bool horizontally, const bool vertically ); void Flip( const Image & in, int32_t inX, int32_t inY, Image & out, int32_t outX, int32_t outY, int32_t width, int32_t height, const bool horizontally, const bool vertically ); // Return ROI with pixels which are not skipped and not used for shadow creation. 1 is to skip, 2 - 5 types of shadows Rect GetActiveROI( const Image & image, const uint8_t minTransformValue = 6 ); // Returns a closest color ID from the original game's palette uint8_t GetColorId( const uint8_t red, const uint8_t green, const uint8_t blue ); std::vector getTransformTable( const Image & in, const Image & out, int32_t x, int32_t y, int32_t width, int32_t height ); Sprite makeShadow( const Sprite & in, const Point & shadowOffset, const uint8_t transformId ); void MaskTransformLayer( const Image & mask, int32_t maskX, int32_t maskY, Image & out, int32_t outX, int32_t outY, int32_t width, int32_t height ); // This function does NOT check transform layer. If you intent to replace few colors at the same image please use ApplyPalette to be more efficient. void ReplaceColorId( Image & image, const uint8_t oldColorId, const uint8_t newColorId ); // Use this function only when you need to convert pixel value into transform layer void ReplaceColorIdByTransformId( Image & image, const uint8_t colorId, const uint8_t transformId ); // Use this function only when you need to convert transform value into non-transparent pixel with the given color. void ReplaceTransformIdByColorId( Image & image, const uint8_t transformId, const uint8_t colorId ); void Resize( const Image & in, Image & out ); void Resize( const Image & in, const int32_t inX, const int32_t inY, const int32_t widthRoiIn, const int32_t heightRoiIn, Image & out, const int32_t outX, const int32_t outY, const int32_t widthRoiOut, const int32_t heightRoiOut ); // Please use value from the main palette only void SetPixel( Image & image, const int32_t x, const int32_t y, const uint8_t value ); void SetPixel( Image & image, const std::vector & points, const uint8_t value ); // Please set value not bigger than 13! void SetTransformPixel( Image & image, const int32_t x, const int32_t y, const uint8_t value ); Image Stretch( const Image & in, int32_t inX, int32_t inY, int32_t widthIn, int32_t heightIn, const int32_t widthOut, const int32_t heightOut ); void SubpixelResize( const Image & in, Image & out ); void SubpixelResize( const Image & in, const int32_t inX, const int32_t inY, const int32_t widthRoiIn, const int32_t heightRoiIn, Image & out, const int32_t outX, const int32_t outY, const int32_t widthRoiOut, const int32_t heightRoiOut ); void Transpose( const Image & in, Image & out ); void updateShadow( Image & image, const Point & shadowOffset, const uint8_t transformId, const bool connectCorners ); } fheroes2-1.0.12+dfsg/src/engine/image_palette.cpp000066400000000000000000000171321456075706000216050ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include "image_palette.h" namespace { const size_t paletteSize = 768; struct PaletteHolder { public: static PaletteHolder & instance() { static PaletteHolder paletteHolder; return paletteHolder; } std::array gamePalette; private: PaletteHolder() { // This is a temporary palette which is used ONLY for cursor and game startup error message. A real palette must be loaded from AGG file. gamePalette = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x35, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x1f, 0x1f, 0x1c, 0x1c, 0x1c, 0x1a, 0x1a, 0x1a, 0x17, 0x17, 0x17, 0x15, 0x15, 0x15, 0x12, 0x12, 0x12, 0x10, 0x10, 0x10, 0x0e, 0x0e, 0x0e, 0x0b, 0x0b, 0x0b, 0x09, 0x09, 0x09, 0x06, 0x06, 0x06, 0x04, 0x04, 0x04, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x3f, 0x3b, 0x37, 0x3c, 0x37, 0x32, 0x3a, 0x34, 0x2e, 0x38, 0x31, 0x2a, 0x36, 0x2e, 0x26, 0x34, 0x2a, 0x22, 0x32, 0x28, 0x1e, 0x30, 0x25, 0x1b, 0x2e, 0x22, 0x18, 0x2b, 0x1f, 0x15, 0x29, 0x1c, 0x12, 0x27, 0x1a, 0x0f, 0x25, 0x18, 0x0d, 0x23, 0x15, 0x0b, 0x21, 0x13, 0x08, 0x1f, 0x11, 0x07, 0x1d, 0x0f, 0x05, 0x1a, 0x0d, 0x04, 0x18, 0x0c, 0x03, 0x16, 0x0a, 0x02, 0x14, 0x09, 0x01, 0x12, 0x07, 0x01, 0x0f, 0x06, 0x00, 0x0d, 0x05, 0x00, 0x0b, 0x04, 0x00, 0x09, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x3d, 0x34, 0x3e, 0x3a, 0x2b, 0x3d, 0x38, 0x23, 0x00, 0x00, 0x00, 0x3b, 0x35, 0x14, 0x3a, 0x33, 0x0d, 0x39, 0x32, 0x05, 0x38, 0x31, 0x00, 0x36, 0x2f, 0x08, 0x34, 0x2c, 0x07, 0x32, 0x28, 0x06, 0x2f, 0x26, 0x06, 0x2d, 0x23, 0x06, 0x2a, 0x1f, 0x05, 0x27, 0x1c, 0x04, 0x25, 0x19, 0x03, 0x22, 0x16, 0x03, 0x1f, 0x13, 0x02, 0x1d, 0x11, 0x02, 0x1a, 0x0f, 0x00, 0x18, 0x0c, 0x00, 0x15, 0x0a, 0x00, 0x13, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x39, 0x27, 0x3e, 0x36, 0x23, 0x3d, 0x34, 0x1f, 0x3c, 0x31, 0x1c, 0x3b, 0x2e, 0x18, 0x3a, 0x2b, 0x14, 0x39, 0x28, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x12, 0x02, 0x20, 0x0f, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x12, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; } }; } namespace fheroes2 { const uint8_t * getGamePalette() { return PaletteHolder::instance().gamePalette.data(); } void setGamePalette( const std::vector & palette ) { assert( palette.size() == paletteSize ); if ( palette.size() != paletteSize ) { return; } std::copy_n( palette.begin(), paletteSize, PaletteHolder::instance().gamePalette.begin() ); } } fheroes2-1.0.12+dfsg/src/engine/image_palette.h000066400000000000000000000033251456075706000212510ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include namespace fheroes2 { const uint8_t * getGamePalette(); // This function must be called only at the start of the application after loading AGG file content. void setGamePalette( const std::vector & palette ); } fheroes2-1.0.12+dfsg/src/engine/image_tool.cpp000066400000000000000000000301651456075706000211250ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined( WITH_IMAGE ) #define ENABLE_PNG #include #endif #include "image_palette.h" #include "image_tool.h" #include "logging.h" namespace { bool isPNGFilePath( const std::string_view path ) { const std::string pngExtension( ".png" ); return path.size() >= pngExtension.size() && ( path.compare( path.size() - pngExtension.size(), pngExtension.size(), pngExtension ) == 0 ); } std::vector PALPalette() { const uint8_t * gamePalette = fheroes2::getGamePalette(); std::vector palette( 256 * 3 ); for ( size_t i = 0; i < palette.size(); ++i ) { palette[i] = gamePalette[i] << 2; } return palette; } #if defined( ENABLE_PNG ) bool SaveImage( const fheroes2::Image & image, const std::string & path ) #else bool SaveImage( const fheroes2::Image & image, std::string path ) #endif { const std::vector & palette = PALPalette(); const uint8_t * currentPalette = palette.data(); const int32_t width = image.width(); const int32_t height = image.height(); SDL_Surface * surface = SDL_CreateRGBSurface( 0, width, height, 8, 0, 0, 0, 0 ); if ( surface == nullptr ) { ERROR_LOG( "Error while creating a SDL surface for an image to be saved under " << path << ". Error " << SDL_GetError() ) return false; } assert( surface->format->BitsPerPixel == 8 ); std::vector paletteSDL; paletteSDL.resize( 256 ); for ( int32_t i = 0; i < 256; ++i ) { const uint8_t * value = currentPalette + i * 3; SDL_Color & col = paletteSDL[i]; col.r = *value; col.g = *( value + 1 ); col.b = *( value + 2 ); col.a = 255; } SDL_SetPaletteColors( surface->format->palette, paletteSDL.data(), 0, 256 ); if ( surface->pitch != width ) { const uint8_t * imageIn = image.image(); for ( int32_t i = 0; i < height; ++i ) { memcpy( static_cast( surface->pixels ) + surface->pitch * i, imageIn + width * i, static_cast( width ) ); } } else { memcpy( surface->pixels, image.image(), static_cast( width * height ) ); } #if defined( ENABLE_PNG ) int res = 0; if ( isPNGFilePath( path ) ) { res = IMG_SavePNG( surface, path.c_str() ); } else { res = SDL_SaveBMP( surface, path.c_str() ); } #else if ( isPNGFilePath( path ) ) { memcpy( path.data() + path.size() - 3, "bmp", 3 ); } const int res = SDL_SaveBMP( surface, path.c_str() ); #endif SDL_FreeSurface( surface ); return res == 0; } } namespace fheroes2 { bool Save( const Image & image, const std::string & path, const uint8_t background ) { if ( image.empty() || path.empty() ) return false; Image temp( image.width(), image.height() ); temp.fill( background ); Blit( image, temp ); return SaveImage( temp, path ); } bool Save( const Image & image, const std::string & path ) { if ( image.empty() || path.empty() ) return false; return SaveImage( image, path ); } bool Load( const std::string & path, Image & image ) { if ( image.singleLayer() ) { // Output image should be double-layer! assert( 0 ); return false; } std::unique_ptr> surface( nullptr, SDL_FreeSurface ); std::unique_ptr> loadedSurface( nullptr, SDL_FreeSurface ); #if defined( ENABLE_PNG ) loadedSurface.reset( IMG_Load( path.c_str() ) ); #else loadedSurface.reset( SDL_LoadBMP( path.c_str() ) ); #endif if ( !loadedSurface ) { return false; } // SDL_PIXELFORMAT_BGRA32 and other RGBA color variants are only supported starting with SDL 2.0.5 #if !SDL_VERSION_ATLEAST( 2, 0, 5 ) #error Minimal supported SDL version is 2.0.5. #endif // Image loading functions can theoretically return SDL_Surface in any supported color format, so we will convert it to a specific format for subsequent // processing const std::unique_ptr> pixelFormat( SDL_AllocFormat( SDL_PIXELFORMAT_BGRA32 ), SDL_FreeFormat ); if ( !pixelFormat ) { return false; } surface.reset( SDL_ConvertSurface( loadedSurface.get(), pixelFormat.get(), 0 ) ); if ( !surface ) { return false; } assert( SDL_MUSTLOCK( surface.get() ) == SDL_FALSE && surface->format->BytesPerPixel == 4 ); image.resize( surface->w, surface->h ); image.reset(); const uint8_t * inY = reinterpret_cast( surface->pixels ); uint8_t * outY = image.image(); uint8_t * transformY = image.transform(); const uint8_t * inYEnd = inY + surface->h * surface->pitch; for ( ; inY != inYEnd; inY += surface->pitch, outY += surface->w, transformY += surface->w ) { const uint8_t * inX = inY; uint8_t * outX = outY; uint8_t * transformX = transformY; const uint8_t * inXEnd = inX + surface->w * 4; for ( ; inX != inXEnd; inX += 4, ++outX, ++transformX ) { const uint8_t alpha = *( inX + 3 ); if ( alpha < 255 ) { if ( alpha == 0 ) { *outX = 0; *transformX = 1; } else if ( *inX == 0 && *( inX + 1 ) == 0 && *( inX + 2 ) == 0 ) { *outX = 0; *transformX = 2; } else { *outX = GetColorId( *( inX + 2 ), *( inX + 1 ), *inX ); *transformX = 0; } } else { *outX = GetColorId( *( inX + 2 ), *( inX + 1 ), *inX ); *transformX = 0; } } } return true; } Sprite decodeICNSprite( const uint8_t * data, const uint32_t sizeData, const int32_t width, const int32_t height, const int16_t offsetX, const int16_t offsetY ) { Sprite sprite( width, height, offsetX, offsetY ); sprite.reset(); uint8_t * imageData = sprite.image(); uint8_t * imageTransform = sprite.transform(); uint32_t posX = 0; const uint8_t * dataEnd = data + sizeData; // The need for a transform layer can only be determined during ICN decoding. bool noTransformLayer = true; while ( true ) { if ( 0 == *data ) { // 0x00 - end of row noTransformLayer = noTransformLayer && ( static_cast( posX ) >= width ); imageData += width; imageTransform += width; posX = 0; ++data; } else if ( 0x80 > *data ) { // 0x01-0x7F - repeat a pixel N times const uint8_t pixelCount = *data; ++data; if ( data + pixelCount > dataEnd ) { // Image data is corrupted - we can not read data beyond dataEnd. break; } memcpy( imageData + posX, data, pixelCount ); memset( imageTransform + posX, static_cast( 0 ), pixelCount ); data += pixelCount; posX += pixelCount; } else if ( 0x80 == *data ) { // 0x80 - end of image noTransformLayer = noTransformLayer && ( static_cast( posX ) >= width ); break; } else if ( 0xC0 > *data ) { // 0xBF - empty (transparent) pixels noTransformLayer = false; posX += *data - 0x80; ++data; } else if ( 0xC0 == *data ) { // 0xC0 - transform layer noTransformLayer = false; ++data; const uint8_t transformValue = *data; const uint8_t transformType = static_cast( ( ( transformValue & 0x3C ) >> 2 ) + 2 ); // 1 is for skipping const uint32_t countValue = transformValue & 0x03; const uint32_t pixelCount = ( countValue != 0 ) ? countValue : *( ++data ); if ( ( transformValue & 0x40 ) && ( transformType <= 15 ) ) { memset( imageTransform + posX, transformType, pixelCount ); } posX += pixelCount; ++data; } else if ( 0xC1 == *data ) { // 0xC1 ++data; const uint32_t pixelCount = *data; ++data; memset( imageData + posX, *data, pixelCount ); memset( imageTransform + posX, static_cast( 0 ), pixelCount ); posX += pixelCount; ++data; } else { const uint32_t pixelCount = *data - 0xC0; ++data; memset( imageData + posX, *data, pixelCount ); memset( imageTransform + posX, static_cast( 0 ), pixelCount ); posX += pixelCount; ++data; } if ( data >= dataEnd ) { break; } } if ( noTransformLayer ) { sprite._disableTransformLayer(); } return sprite; } void decodeTILImages( const uint8_t * data, const size_t imageCount, const int32_t width, const int32_t height, std::vector & output ) { assert( data != nullptr && imageCount > 0 && width > 0 && height > 0 ); output.resize( imageCount ); const size_t imageSize = static_cast( width ) * height; for ( size_t i = 0; i < imageCount; ++i ) { Image & tilImage = output[i]; tilImage._disableTransformLayer(); tilImage.resize( width, height ); memcpy( tilImage.image(), data + i * imageSize, imageSize ); } } bool isPNGFormatSupported() { #if defined( ENABLE_PNG ) return true; #else return false; #endif } } fheroes2-1.0.12+dfsg/src/engine/image_tool.h000066400000000000000000000044441456075706000205730ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include #include #include "image.h" namespace fheroes2 { bool Save( const Image & image, const std::string & path ); // Save an image into file. 'background' represents palette index from the original palette. Recommended value is 23. bool Save( const Image & image, const std::string & path, const uint8_t background ); bool Load( const std::string & path, Image & image ); Sprite decodeICNSprite( const uint8_t * data, const uint32_t sizeData, const int32_t width, const int32_t height, const int16_t offsetX, const int16_t offsetY ); void decodeTILImages( const uint8_t * data, const size_t imageCount, const int32_t width, const int32_t height, std::vector & output ); // By default only Bitmap (.bmp) format is supported. bool isPNGFormatSupported(); } fheroes2-1.0.12+dfsg/src/engine/localevent.cpp000066400000000000000000001563341456075706000211510ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2008 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "localevent.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "audio.h" #include "image.h" #include "logging.h" #include "render_processor.h" #include "screen.h" #include "tools.h" namespace { const uint32_t globalLoopSleepTime{ 1 }; // If such or more ms has passed after pressing the mouse button, then this is a long press. const uint32_t mouseButtonLongPressTimeout{ 850 }; int getSDLKey( const fheroes2::Key key ) { switch ( key ) { case fheroes2::Key::NONE: case fheroes2::Key::LAST_KEY: return SDLK_UNKNOWN; case fheroes2::Key::KEY_BACKSPACE: return SDLK_BACKSPACE; case fheroes2::Key::KEY_ENTER: return SDLK_RETURN; case fheroes2::Key::KEY_ESCAPE: return SDLK_ESCAPE; case fheroes2::Key::KEY_SPACE: return SDLK_SPACE; case fheroes2::Key::KEY_EXCLAIM: return SDLK_EXCLAIM; case fheroes2::Key::KEY_DOUBLE_QUOTE: return SDLK_QUOTEDBL; case fheroes2::Key::KEY_HASH: return SDLK_HASH; case fheroes2::Key::KEY_DOLLAR: return SDLK_DOLLAR; case fheroes2::Key::KEY_AMPERSAND: return SDLK_AMPERSAND; case fheroes2::Key::KEY_QUOTE: return SDLK_QUOTE; case fheroes2::Key::KEY_LEFT_PARENTHESIS: return SDLK_LEFTPAREN; case fheroes2::Key::KEY_RIGHT_PARENTHESIS: return SDLK_RIGHTPAREN; case fheroes2::Key::KEY_ASTERISK: return SDLK_ASTERISK; case fheroes2::Key::KEY_PLUS: return SDLK_PLUS; case fheroes2::Key::KEY_COMMA: return SDLK_COMMA; case fheroes2::Key::KEY_MINUS: return SDLK_MINUS; case fheroes2::Key::KEY_PERIOD: return SDLK_PERIOD; case fheroes2::Key::KEY_SLASH: return SDLK_SLASH; case fheroes2::Key::KEY_COLON: return SDLK_COLON; case fheroes2::Key::KEY_SEMICOLON: return SDLK_SEMICOLON; case fheroes2::Key::KEY_LESS: return SDLK_LESS; case fheroes2::Key::KEY_EQUALS: return SDLK_EQUALS; case fheroes2::Key::KEY_GREATER: return SDLK_GREATER; case fheroes2::Key::KEY_QUESTION: return SDLK_QUESTION; case fheroes2::Key::KEY_AT: return SDLK_AT; case fheroes2::Key::KEY_LEFT_BRACKET: return SDLK_LEFTBRACKET; case fheroes2::Key::KEY_BACKSLASH: return SDLK_BACKSLASH; case fheroes2::Key::KEY_RIGHT_BRACKET: return SDLK_RIGHTBRACKET; case fheroes2::Key::KEY_CARET: return SDLK_CARET; case fheroes2::Key::KEY_UNDERSCORE: return SDLK_UNDERSCORE; case fheroes2::Key::KEY_LEFT_ALT: return SDLK_LALT; case fheroes2::Key::KEY_RIGHT_ALT: return SDLK_RALT; case fheroes2::Key::KEY_LEFT_CONTROL: return SDLK_LCTRL; case fheroes2::Key::KEY_RIGHT_CONTROL: return SDLK_RCTRL; case fheroes2::Key::KEY_LEFT_SHIFT: return SDLK_LSHIFT; case fheroes2::Key::KEY_RIGHT_SHIFT: return SDLK_RSHIFT; case fheroes2::Key::KEY_TAB: return SDLK_TAB; case fheroes2::Key::KEY_DELETE: return SDLK_DELETE; case fheroes2::Key::KEY_PAGE_UP: return SDLK_PAGEUP; case fheroes2::Key::KEY_PAGE_DOWN: return SDLK_PAGEDOWN; case fheroes2::Key::KEY_F1: return SDLK_F1; case fheroes2::Key::KEY_F2: return SDLK_F2; case fheroes2::Key::KEY_F3: return SDLK_F3; case fheroes2::Key::KEY_F4: return SDLK_F4; case fheroes2::Key::KEY_F5: return SDLK_F5; case fheroes2::Key::KEY_F6: return SDLK_F6; case fheroes2::Key::KEY_F7: return SDLK_F7; case fheroes2::Key::KEY_F8: return SDLK_F8; case fheroes2::Key::KEY_F9: return SDLK_F9; case fheroes2::Key::KEY_F10: return SDLK_F10; case fheroes2::Key::KEY_F11: return SDLK_F11; case fheroes2::Key::KEY_F12: return SDLK_F12; case fheroes2::Key::KEY_LEFT: return SDLK_LEFT; case fheroes2::Key::KEY_RIGHT: return SDLK_RIGHT; case fheroes2::Key::KEY_UP: return SDLK_UP; case fheroes2::Key::KEY_DOWN: return SDLK_DOWN; case fheroes2::Key::KEY_0: return SDLK_0; case fheroes2::Key::KEY_1: return SDLK_1; case fheroes2::Key::KEY_2: return SDLK_2; case fheroes2::Key::KEY_3: return SDLK_3; case fheroes2::Key::KEY_4: return SDLK_4; case fheroes2::Key::KEY_5: return SDLK_5; case fheroes2::Key::KEY_6: return SDLK_6; case fheroes2::Key::KEY_7: return SDLK_7; case fheroes2::Key::KEY_8: return SDLK_8; case fheroes2::Key::KEY_9: return SDLK_9; case fheroes2::Key::KEY_A: return SDLK_a; case fheroes2::Key::KEY_B: return SDLK_b; case fheroes2::Key::KEY_C: return SDLK_c; case fheroes2::Key::KEY_D: return SDLK_d; case fheroes2::Key::KEY_E: return SDLK_e; case fheroes2::Key::KEY_F: return SDLK_f; case fheroes2::Key::KEY_G: return SDLK_g; case fheroes2::Key::KEY_H: return SDLK_h; case fheroes2::Key::KEY_I: return SDLK_i; case fheroes2::Key::KEY_J: return SDLK_j; case fheroes2::Key::KEY_K: return SDLK_k; case fheroes2::Key::KEY_L: return SDLK_l; case fheroes2::Key::KEY_M: return SDLK_m; case fheroes2::Key::KEY_N: return SDLK_n; case fheroes2::Key::KEY_O: return SDLK_o; case fheroes2::Key::KEY_P: return SDLK_p; case fheroes2::Key::KEY_Q: return SDLK_q; case fheroes2::Key::KEY_R: return SDLK_r; case fheroes2::Key::KEY_S: return SDLK_s; case fheroes2::Key::KEY_T: return SDLK_t; case fheroes2::Key::KEY_U: return SDLK_u; case fheroes2::Key::KEY_V: return SDLK_v; case fheroes2::Key::KEY_W: return SDLK_w; case fheroes2::Key::KEY_X: return SDLK_x; case fheroes2::Key::KEY_Y: return SDLK_y; case fheroes2::Key::KEY_Z: return SDLK_z; case fheroes2::Key::KEY_PRINT: return SDLK_PRINTSCREEN; case fheroes2::Key::KEY_KP_0: return SDLK_KP_0; case fheroes2::Key::KEY_KP_1: return SDLK_KP_1; case fheroes2::Key::KEY_KP_2: return SDLK_KP_2; case fheroes2::Key::KEY_KP_3: return SDLK_KP_3; case fheroes2::Key::KEY_KP_4: return SDLK_KP_4; case fheroes2::Key::KEY_KP_5: return SDLK_KP_5; case fheroes2::Key::KEY_KP_6: return SDLK_KP_6; case fheroes2::Key::KEY_KP_7: return SDLK_KP_7; case fheroes2::Key::KEY_KP_8: return SDLK_KP_8; case fheroes2::Key::KEY_KP_9: return SDLK_KP_9; case fheroes2::Key::KEY_KP_PERIOD: return SDLK_KP_PERIOD; case fheroes2::Key::KEY_KP_DIVIDE: return SDLK_KP_DIVIDE; case fheroes2::Key::KEY_KP_MULTIPLY: return SDLK_KP_MULTIPLY; case fheroes2::Key::KEY_KP_MINUS: return SDLK_KP_MINUS; case fheroes2::Key::KEY_KP_PLUS: return SDLK_KP_PLUS; case fheroes2::Key::KEY_KP_ENTER: return SDLK_KP_ENTER; case fheroes2::Key::KEY_KP_EQUALS: return SDLK_KP_EQUALS; case fheroes2::Key::KEY_HOME: return SDLK_HOME; case fheroes2::Key::KEY_END: return SDLK_END; default: // Did you add a new key? Add the logic above! assert( 0 ); break; } return SDLK_UNKNOWN; } fheroes2::Key getKeyFromSDL( int sdlKey ) { // SDL interprets keyboard Numpad Enter as a separate key. However, in the game we should handle it in the same way as the normal Enter. if ( sdlKey == SDLK_KP_ENTER ) { sdlKey = SDLK_RETURN; } static std::map sdlValueToKey; if ( sdlValueToKey.empty() ) { // The map is empty let's populate it. for ( int32_t i = static_cast( fheroes2::Key::NONE ); i < static_cast( fheroes2::Key::LAST_KEY ); ++i ) { const fheroes2::Key key = static_cast( i ); sdlValueToKey.emplace( getSDLKey( key ), key ); } } auto iter = sdlValueToKey.find( sdlKey ); if ( iter == sdlValueToKey.end() ) { return fheroes2::Key::NONE; } return iter->second; } int32_t getKeyModifierFromSDL( const int sdlModifier ) { int32_t modifier = fheroes2::KeyModifier::KEY_MODIFIER_NONE; if ( sdlModifier & KMOD_CTRL ) { modifier |= fheroes2::KeyModifier::KEY_MODIFIER_CTRL; } if ( sdlModifier & KMOD_SHIFT ) { modifier |= fheroes2::KeyModifier::KEY_MODIFIER_SHIFT; } if ( sdlModifier & KMOD_ALT ) { modifier |= fheroes2::KeyModifier::KEY_MODIFIER_ALT; } if ( sdlModifier & KMOD_CAPS ) { modifier |= fheroes2::KeyModifier::KEY_MODIFIER_CAPS; } if ( sdlModifier & KMOD_NUM ) { modifier |= fheroes2::KeyModifier::KEY_MODIFIER_NUM; } return modifier; } char getCharacterFromPressedKey( const fheroes2::Key key, int32_t mod ) { if ( ( mod & fheroes2::KeyModifier::KEY_MODIFIER_SHIFT ) && ( mod & fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) ) { mod = mod & ~( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ); } switch ( key ) { case fheroes2::Key::KEY_1: return ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT & mod ? '!' : '1' ); case fheroes2::Key::KEY_2: return ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT & mod ? '@' : '2' ); case fheroes2::Key::KEY_3: return ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT & mod ? '#' : '3' ); case fheroes2::Key::KEY_4: return ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT & mod ? '$' : '4' ); case fheroes2::Key::KEY_5: return ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT & mod ? '%' : '5' ); case fheroes2::Key::KEY_6: return ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT & mod ? '^' : '6' ); case fheroes2::Key::KEY_7: return ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT & mod ? '&' : '7' ); case fheroes2::Key::KEY_8: return ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT & mod ? '*' : '8' ); case fheroes2::Key::KEY_9: return ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT & mod ? '(' : '9' ); case fheroes2::Key::KEY_0: return ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT & mod ? ')' : '0' ); case fheroes2::Key::KEY_KP_0: if ( fheroes2::KeyModifier::KEY_MODIFIER_NUM & mod ) return '0'; break; case fheroes2::Key::KEY_KP_1: if ( fheroes2::KeyModifier::KEY_MODIFIER_NUM & mod ) return '1'; break; case fheroes2::Key::KEY_KP_2: if ( fheroes2::KeyModifier::KEY_MODIFIER_NUM & mod ) return '2'; break; case fheroes2::Key::KEY_KP_3: if ( fheroes2::KeyModifier::KEY_MODIFIER_NUM & mod ) return '3'; break; case fheroes2::Key::KEY_KP_4: if ( fheroes2::KeyModifier::KEY_MODIFIER_NUM & mod ) return '4'; break; case fheroes2::Key::KEY_KP_5: if ( fheroes2::KeyModifier::KEY_MODIFIER_NUM & mod ) return '5'; break; case fheroes2::Key::KEY_KP_6: if ( fheroes2::KeyModifier::KEY_MODIFIER_NUM & mod ) return '6'; break; case fheroes2::Key::KEY_KP_7: if ( fheroes2::KeyModifier::KEY_MODIFIER_NUM & mod ) return '7'; break; case fheroes2::Key::KEY_KP_8: if ( fheroes2::KeyModifier::KEY_MODIFIER_NUM & mod ) return '8'; break; case fheroes2::Key::KEY_KP_9: if ( fheroes2::KeyModifier::KEY_MODIFIER_NUM & mod ) return '9'; break; case fheroes2::Key::KEY_MINUS: return ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT & mod ? '_' : '-' ); case fheroes2::Key::KEY_EQUALS: return ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT & mod ? '+' : '=' ); case fheroes2::Key::KEY_BACKSLASH: return ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT & mod ? '|' : '\\' ); case fheroes2::Key::KEY_LEFT_BRACKET: return ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT & mod ? '{' : '[' ); case fheroes2::Key::KEY_RIGHT_BRACKET: return ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT & mod ? '}' : ']' ); case fheroes2::Key::KEY_SEMICOLON: return ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT & mod ? ':' : ';' ); case fheroes2::Key::KEY_QUOTE: return ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT & mod ? '"' : '\'' ); case fheroes2::Key::KEY_COMMA: return ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT & mod ? '<' : ',' ); case fheroes2::Key::KEY_PERIOD: return ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT & mod ? '>' : '.' ); case fheroes2::Key::KEY_SLASH: return ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT & mod ? '?' : '/' ); case fheroes2::Key::KEY_EXCLAIM: return '!'; case fheroes2::Key::KEY_AT: return '@'; case fheroes2::Key::KEY_HASH: return '#'; case fheroes2::Key::KEY_DOLLAR: return '$'; case fheroes2::Key::KEY_AMPERSAND: return '&'; case fheroes2::Key::KEY_ASTERISK: return '*'; case fheroes2::Key::KEY_LEFT_PARENTHESIS: return '('; case fheroes2::Key::KEY_RIGHT_PARENTHESIS: return ')'; case fheroes2::Key::KEY_DOUBLE_QUOTE: return '"'; case fheroes2::Key::KEY_PLUS: return '+'; case fheroes2::Key::KEY_COLON: return ':'; case fheroes2::Key::KEY_LESS: return '<'; case fheroes2::Key::KEY_GREATER: return '>'; case fheroes2::Key::KEY_QUESTION: return '?'; case fheroes2::Key::KEY_CARET: return '^'; case fheroes2::Key::KEY_UNDERSCORE: return '_'; case fheroes2::Key::KEY_SPACE: return ' '; case fheroes2::Key::KEY_A: return ( ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) & mod ? 'A' : 'a' ); case fheroes2::Key::KEY_B: return ( ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) & mod ? 'B' : 'b' ); case fheroes2::Key::KEY_C: return ( ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) & mod ? 'C' : 'c' ); case fheroes2::Key::KEY_D: return ( ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) & mod ? 'D' : 'd' ); case fheroes2::Key::KEY_E: return ( ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) & mod ? 'E' : 'e' ); case fheroes2::Key::KEY_F: return ( ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) & mod ? 'F' : 'f' ); case fheroes2::Key::KEY_G: return ( ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) & mod ? 'G' : 'g' ); case fheroes2::Key::KEY_H: return ( ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) & mod ? 'H' : 'h' ); case fheroes2::Key::KEY_I: return ( ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) & mod ? 'I' : 'i' ); case fheroes2::Key::KEY_J: return ( ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) & mod ? 'J' : 'j' ); case fheroes2::Key::KEY_K: return ( ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) & mod ? 'K' : 'k' ); case fheroes2::Key::KEY_L: return ( ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) & mod ? 'L' : 'l' ); case fheroes2::Key::KEY_M: return ( ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) & mod ? 'M' : 'm' ); case fheroes2::Key::KEY_N: return ( ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) & mod ? 'N' : 'n' ); case fheroes2::Key::KEY_O: return ( ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) & mod ? 'O' : 'o' ); case fheroes2::Key::KEY_P: return ( ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) & mod ? 'P' : 'p' ); case fheroes2::Key::KEY_Q: return ( ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) & mod ? 'Q' : 'q' ); case fheroes2::Key::KEY_R: return ( ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) & mod ? 'R' : 'r' ); case fheroes2::Key::KEY_S: return ( ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) & mod ? 'S' : 's' ); case fheroes2::Key::KEY_T: return ( ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) & mod ? 'T' : 't' ); case fheroes2::Key::KEY_U: return ( ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) & mod ? 'U' : 'u' ); case fheroes2::Key::KEY_V: return ( ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) & mod ? 'V' : 'v' ); case fheroes2::Key::KEY_W: return ( ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) & mod ? 'W' : 'w' ); case fheroes2::Key::KEY_X: return ( ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) & mod ? 'X' : 'x' ); case fheroes2::Key::KEY_Y: return ( ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) & mod ? 'Y' : 'y' ); case fheroes2::Key::KEY_Z: return ( ( fheroes2::KeyModifier::KEY_MODIFIER_SHIFT | fheroes2::KeyModifier::KEY_MODIFIER_CAPS ) & mod ? 'Z' : 'z' ); default: break; } return 0; } std::set eventTypeStatus; void setEventProcessingState( const uint32_t eventType, const bool enable ) { eventTypeStatus.emplace( eventType ); SDL_EventState( eventType, ( enable ? SDL_ENABLE : SDL_IGNORE ) ); } } // Custom button mapping for Nintendo Switch #if defined( TARGET_NINTENDO_SWITCH ) #undef SDL_CONTROLLER_BUTTON_A #undef SDL_CONTROLLER_BUTTON_B #undef SDL_CONTROLLER_BUTTON_DPAD_LEFT #undef SDL_CONTROLLER_BUTTON_DPAD_RIGHT #undef SDL_CONTROLLER_BUTTON_DPAD_UP #undef SDL_CONTROLLER_BUTTON_DPAD_DOWN #define SDL_CONTROLLER_BUTTON_A 1 #define SDL_CONTROLLER_BUTTON_B 0 #define SDL_CONTROLLER_BUTTON_DPAD_LEFT 13 #define SDL_CONTROLLER_BUTTON_DPAD_RIGHT 14 #define SDL_CONTROLLER_BUTTON_DPAD_UP 11 #define SDL_CONTROLLER_BUTTON_DPAD_DOWN 12 enum SwitchJoyconKeys { SWITCH_BUTTON_Y = 2, SWITCH_BUTTON_X = 3, SWITCH_BUTTON_MINUS = 4, SWITCH_BUTTON_PLUS = 6, SWITCH_BUTTON_L = 9, SWITCH_BUTTON_R = 10 }; #endif namespace fheroes2 { const char * KeySymGetName( const Key key ) { return SDL_GetKeyName( static_cast( getSDLKey( key ) ) ); } bool PressIntKey( uint32_t max, uint32_t & result ) { const LocalEvent & le = LocalEvent::Get(); if ( le.KeyPress( fheroes2::Key::KEY_BACKSPACE ) ) { result /= 10; return true; } if ( !le.KeyPress() ) { // No key is pressed. return false; } if ( le.KeyValue() >= fheroes2::Key::KEY_0 && le.KeyValue() <= fheroes2::Key::KEY_9 ) { if ( max <= result ) { // We reached the maximum. return true; } result *= 10; result += static_cast( static_cast( le.KeyValue() ) - static_cast( fheroes2::Key::KEY_0 ) ); if ( result > max ) { result = max; } return true; } if ( le.KeyValue() >= fheroes2::Key::KEY_KP_0 && le.KeyValue() <= fheroes2::Key::KEY_KP_9 ) { if ( max <= result ) { // We reached the maximum. return true; } result *= 10; result += static_cast( static_cast( le.KeyValue() ) - static_cast( fheroes2::Key::KEY_KP_0 ) ); if ( result > max ) { result = max; } return true; } return false; } size_t InsertKeySym( std::string & res, size_t pos, const Key key, const int32_t mod ) { switch ( key ) { case fheroes2::Key::KEY_BACKSPACE: if ( !res.empty() && pos ) { if ( pos >= res.size() ) res.resize( res.size() - 1 ); else res.erase( pos - 1, 1 ); --pos; } break; case fheroes2::Key::KEY_DELETE: if ( !res.empty() ) { if ( pos < res.size() ) res.erase( pos, 1 ); } break; case fheroes2::Key::KEY_LEFT: if ( pos ) --pos; break; case fheroes2::Key::KEY_RIGHT: if ( pos < res.size() ) ++pos; break; case fheroes2::Key::KEY_HOME: pos = 0; break; case fheroes2::Key::KEY_END: pos = res.size(); break; default: { char c = getCharacterFromPressedKey( key, mod ); if ( c ) { res.insert( pos, 1, c ); ++pos; } } } return pos; } } LocalEvent::LocalEvent() : modes( 0 ) , key_value( fheroes2::Key::NONE ) , mouse_button( 0 ) , _mouseButtonLongPressDelay( mouseButtonLongPressTimeout ) {} void LocalEvent::OpenController() { for ( int i = 0; i < SDL_NumJoysticks(); ++i ) { if ( SDL_IsGameController( i ) ) { _gameController = SDL_GameControllerOpen( i ); if ( _gameController != nullptr ) { fheroes2::cursor().enableSoftwareEmulation( true ); break; } } } } void LocalEvent::CloseController() { if ( SDL_GameControllerGetAttached( _gameController ) ) { SDL_GameControllerClose( _gameController ); _gameController = nullptr; } } void LocalEvent::OpenTouchpad() { const int touchNumber = SDL_GetNumTouchDevices(); if ( touchNumber > 0 ) { fheroes2::cursor().enableSoftwareEmulation( true ); #if SDL_VERSION_ATLEAST( 2, 0, 10 ) SDL_SetHint( SDL_HINT_TOUCH_MOUSE_EVENTS, "0" ); #endif } } LocalEvent & LocalEvent::Get() { static LocalEvent le; return le; } LocalEvent & LocalEvent::GetClean() { LocalEvent & le = Get(); le.ResetModes( KEY_PRESSED ); le.ResetModes( MOUSE_MOTION ); le.ResetModes( MOUSE_PRESSED ); le.ResetModes( MOUSE_RELEASED ); le.ResetModes( MOUSE_WHEEL ); le.ResetModes( MOUSE_TOUCH ); le.ResetModes( KEY_HOLD ); return le; } bool LocalEvent::HandleEvents( const bool sleepAfterEventProcessing, const bool allowExit /* = false */ ) { // Event processing might be computationally heavy. // We want to make sure that we do not slow down by going into sleep mode when it is not needed. const fheroes2::Time eventProcessingTimer; // We can have more than one event which requires rendering. We must render only once and only when sleeping is excepted. fheroes2::Rect renderRoi; // Mouse area must be updated only once so we will use only the latest area for rendering. _mouseCursorRenderArea = {}; fheroes2::Display & display = fheroes2::Display::instance(); if ( fheroes2::RenderProcessor::instance().isCyclingUpdateRequired() ) { // To maintain color cycling animation we need to render the whole frame with an updated palette. renderRoi = { 0, 0, display.width(), display.height() }; } SDL_Event event; // We shouldn't reset the MOUSE_PRESSED and KEY_HOLD here because these are "ongoing" states ResetModes( KEY_PRESSED ); ResetModes( MOUSE_MOTION ); ResetModes( MOUSE_RELEASED ); ResetModes( MOUSE_WHEEL ); // MOUSE_PRESSED is an "ongoing" state, so we shouldn't reset the MOUSE_TOUCH while that state is active if ( !( modes & MOUSE_PRESSED ) ) { ResetModes( MOUSE_TOUCH ); } while ( SDL_PollEvent( &event ) ) { switch ( event.type ) { case SDL_WINDOWEVENT: if ( event.window.event == SDL_WINDOWEVENT_CLOSE ) { // A special case since we need to exit the loop. if ( allowExit ) { // Try to perform clear exit to catch all memory leaks, for example. return false; } break; } if ( HandleWindowEvent( event.window ) ) { renderRoi = { 0, 0, display.width(), display.height() }; } break; case SDL_KEYDOWN: case SDL_KEYUP: HandleKeyboardEvent( event.key ); break; case SDL_MOUSEMOTION: HandleMouseMotionEvent( event.motion ); break; case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: HandleMouseButtonEvent( event.button ); break; case SDL_MOUSEWHEEL: HandleMouseWheelEvent( event.wheel ); break; case SDL_CONTROLLERDEVICEREMOVED: if ( _gameController != nullptr ) { const SDL_GameController * removedController = SDL_GameControllerFromInstanceID( event.jdevice.which ); if ( removedController == _gameController ) { SDL_GameControllerClose( _gameController ); _gameController = nullptr; } } break; case SDL_CONTROLLERDEVICEADDED: if ( _gameController == nullptr ) { _gameController = SDL_GameControllerOpen( event.jdevice.which ); if ( _gameController != nullptr ) { fheroes2::cursor().enableSoftwareEmulation( true ); } } break; case SDL_JOYAXISMOTION: case SDL_JOYBALLMOTION: case SDL_JOYHATMOTION: case SDL_JOYBUTTONDOWN: case SDL_JOYBUTTONUP: case SDL_JOYDEVICEADDED: case SDL_JOYDEVICEREMOVED: case SDL_CONTROLLERDEVICEREMAPPED: // SDL requires joystick events to be enabled in order to handle controller events. // This is because the controller related code depends on the joystick related code. // See SDL_gamecontroller.c within SDL source code for implementation details. break; case SDL_CONTROLLERAXISMOTION: HandleControllerAxisEvent( event.caxis ); break; case SDL_CONTROLLERBUTTONDOWN: case SDL_CONTROLLERBUTTONUP: HandleControllerButtonEvent( event.cbutton ); break; case SDL_FINGERDOWN: case SDL_FINGERUP: case SDL_FINGERMOTION: HandleTouchEvent( event.tfinger ); break; case SDL_RENDER_TARGETS_RESET: // We need to just update the screen. This event usually happens when we switch between fullscreen and windowed modes. renderRoi = { 0, 0, display.width(), display.height() }; break; case SDL_RENDER_DEVICE_RESET: HandleRenderDeviceResetEvent(); renderRoi = { 0, 0, display.width(), display.height() }; break; case SDL_TEXTINPUT: // Keyboard events on Android should be processed here. Use event.text.text to extract text input. break; case SDL_TEXTEDITING: // An event when a user pressed a button on a keyboard. Not all buttons are supported. This event should be used mainly on Android devices. break; case SDL_QUIT: if ( allowExit ) { // Try to perform clear exit to catch all memory leaks, for example. return false; } break; case SDL_APP_LOWMEMORY: // According to SDL this event can only happen on Android or iOS. // We need to deallocate some memory but we need to be careful not to deallocate images that are in use at the moment. // As of now we have no logic for this so we at least log this event. DEBUG_LOG( DBG_ENGINE, DBG_WARN, "OS indicates low memory. Release some resources." ) break; default: // If this assertion blows up then we included an event type but we didn't add logic for it. assert( eventTypeStatus.count( event.type ) == 0 ); // This is a new event type which we do not handle. It might have been added in a newer version of SDL. break; } } if ( _gameController != nullptr ) { ProcessControllerAxisMotion(); } renderRoi = fheroes2::getBoundaryRect( renderRoi, _mouseCursorRenderArea ); if ( sleepAfterEventProcessing ) { if ( renderRoi != fheroes2::Rect() ) { display.render( renderRoi ); } // Make sure not to delay any further if the processing time within this function was more than the expected waiting time. if ( eventProcessingTimer.getMs() < globalLoopSleepTime ) { static_assert( globalLoopSleepTime == 1, "Make sure that you sleep for the difference between times since you change the sleep time." ); SDL_Delay( globalLoopSleepTime ); } } else { // Since rendering is going to be just after the call of this method we need to update rendering area only. if ( renderRoi != fheroes2::Rect() ) { display.updateNextRenderRoi( renderRoi ); } } return true; } void LocalEvent::StopSounds() { Audio::Mute(); } void LocalEvent::ResumeSounds() { Audio::Unmute(); } void LocalEvent::HandleMouseWheelEvent( const SDL_MouseWheelEvent & wheel ) { SetModes( MOUSE_WHEEL ); mouse_rm = mouse_cu; mouse_wm.x = wheel.x; mouse_wm.y = wheel.y; } void LocalEvent::HandleTouchEvent( const SDL_TouchFingerEvent & event ) { #if defined( TARGET_PS_VITA ) // Ignore rear touchpad on PS Vita if ( event.touchId != 0 ) return; #endif // ID of a finger here is a composite thing, and consists of a touch device id and a finger id. This // should allow gestures to be handled correctly even when using different touchpads for different // fingers. const auto eventFingerId = std::make_pair( event.touchId, event.fingerId ); switch ( event.type ) { case SDL_FINGERDOWN: if ( !_fingerIds.first ) { _fingerIds.first = eventFingerId; } else if ( !_fingerIds.second ) { _fingerIds.second = eventFingerId; } else { // Gestures of more than two fingers are not supported, ignore return; } break; case SDL_FINGERUP: case SDL_FINGERMOTION: if ( eventFingerId != _fingerIds.first && eventFingerId != _fingerIds.second ) { // An event from an unknown finger, ignore return; } break; default: // Unknown event, this should never happen assert( 0 ); return; } if ( eventFingerId == _fingerIds.first ) { const fheroes2::Display & display = fheroes2::Display::instance(); #if defined( TARGET_PS_VITA ) || defined( TARGET_NINTENDO_SWITCH ) // TODO: verify where it is even needed to do such weird woodoo magic for these targets. const fheroes2::Size screenResolution = fheroes2::engine().getCurrentScreenResolution(); // current resolution of screen const fheroes2::Rect windowRect = fheroes2::engine().getActiveWindowROI(); // scaled (logical) resolution _emulatedPointerPosX = static_cast( screenResolution.width * event.x - windowRect.x ) * ( static_cast( display.width() ) / windowRect.width ); _emulatedPointerPosY = static_cast( screenResolution.height * event.y - windowRect.y ) * ( static_cast( display.height() ) / windowRect.height ); #else _emulatedPointerPosX = static_cast( event.x ) * display.width(); _emulatedPointerPosY = static_cast( event.y ) * display.height(); #endif mouse_cu.x = static_cast( _emulatedPointerPosX ); mouse_cu.y = static_cast( _emulatedPointerPosY ); SetModes( MOUSE_MOTION ); SetModes( MOUSE_TOUCH ); if ( _globalMouseMotionEventHook ) { _mouseCursorRenderArea = _globalMouseMotionEventHook( mouse_cu.x, mouse_cu.y ); } // If there is a two-finger gesture in progress, the first finger is only used to move the cursor. // The operation of the left mouse button is not simulated. if ( !_isTwoFingerGestureInProgress ) { if ( event.type == SDL_FINGERDOWN ) { mouse_pl = mouse_cu; _mouseButtonLongPressDelay.reset(); SetModes( MOUSE_PRESSED ); } else if ( event.type == SDL_FINGERUP ) { mouse_rl = mouse_cu; ResetModes( MOUSE_PRESSED ); SetModes( MOUSE_RELEASED ); } mouse_button = SDL_BUTTON_LEFT; } } else if ( eventFingerId == _fingerIds.second ) { if ( event.type == SDL_FINGERDOWN ) { mouse_pr = mouse_cu; _mouseButtonLongPressDelay.reset(); SetModes( MOUSE_PRESSED ); SetModes( MOUSE_TOUCH ); // When the second finger touches the screen, the two-finger gesture processing begins. This // gesture simulates the operation of the right mouse button and ends when both fingers are // removed from the screen. _isTwoFingerGestureInProgress = true; } else if ( event.type == SDL_FINGERUP ) { mouse_rr = mouse_cu; ResetModes( MOUSE_PRESSED ); SetModes( MOUSE_RELEASED ); SetModes( MOUSE_TOUCH ); } mouse_button = SDL_BUTTON_RIGHT; } // The finger no longer touches the screen, reset its state if ( event.type == SDL_FINGERUP ) { if ( eventFingerId == _fingerIds.first ) { _fingerIds.first.reset(); } else if ( eventFingerId == _fingerIds.second ) { _fingerIds.second.reset(); } else { // An event from an unknown finger, this should never happen assert( 0 ); } // Both fingers are removed from the screen, cancel the two-finger gesture if ( !_fingerIds.first && !_fingerIds.second ) { _isTwoFingerGestureInProgress = false; } } } void LocalEvent::HandleControllerAxisEvent( const SDL_ControllerAxisEvent & motion ) { if ( motion.axis == SDL_CONTROLLER_AXIS_LEFTX ) { if ( std::abs( motion.value ) > CONTROLLER_L_DEADZONE ) _controllerLeftXAxis = motion.value; else _controllerLeftXAxis = 0; } else if ( motion.axis == SDL_CONTROLLER_AXIS_LEFTY ) { if ( std::abs( motion.value ) > CONTROLLER_L_DEADZONE ) _controllerLeftYAxis = motion.value; else _controllerLeftYAxis = 0; } else if ( motion.axis == SDL_CONTROLLER_AXIS_RIGHTX ) { if ( std::abs( motion.value ) > CONTROLLER_R_DEADZONE ) _controllerRightXAxis = motion.value; else _controllerRightXAxis = 0; } else if ( motion.axis == SDL_CONTROLLER_AXIS_RIGHTY ) { if ( std::abs( motion.value ) > CONTROLLER_R_DEADZONE ) _controllerRightYAxis = motion.value; else _controllerRightYAxis = 0; } } void LocalEvent::HandleControllerButtonEvent( const SDL_ControllerButtonEvent & button ) { if ( button.state == SDL_PRESSED ) SetModes( KEY_PRESSED ); else if ( button.state == SDL_RELEASED ) ResetModes( KEY_PRESSED ); if ( button.button == SDL_CONTROLLER_BUTTON_A || button.button == SDL_CONTROLLER_BUTTON_B ) { if ( modes & KEY_PRESSED ) { _mouseButtonLongPressDelay.reset(); SetModes( MOUSE_PRESSED ); } else { ResetModes( MOUSE_PRESSED ); SetModes( MOUSE_RELEASED ); } if ( button.button == SDL_CONTROLLER_BUTTON_A ) { if ( modes & KEY_PRESSED ) { mouse_pl = mouse_cu; } else { mouse_rl = mouse_cu; } mouse_button = SDL_BUTTON_LEFT; } else if ( button.button == SDL_CONTROLLER_BUTTON_B ) { if ( modes & KEY_PRESSED ) { mouse_pr = mouse_cu; } else { mouse_rr = mouse_cu; } mouse_button = SDL_BUTTON_RIGHT; } ResetModes( KEY_PRESSED ); } else if ( modes & KEY_PRESSED ) { if ( button.button == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER ) { _controllerPointerSpeed *= CONTROLLER_TRIGGER_CURSOR_SPEEDUP; key_value = fheroes2::Key::NONE; } else if ( button.button == SDL_CONTROLLER_BUTTON_DPAD_DOWN ) { key_value = fheroes2::Key::KEY_SPACE; } else if ( button.button == SDL_CONTROLLER_BUTTON_DPAD_LEFT ) { key_value = fheroes2::Key::KEY_H; } else if ( button.button == SDL_CONTROLLER_BUTTON_DPAD_RIGHT ) { key_value = fheroes2::Key::KEY_T; } else if ( button.button == SDL_CONTROLLER_BUTTON_X ) { key_value = fheroes2::Key::KEY_E; } else if ( button.button == SDL_CONTROLLER_BUTTON_Y ) { key_value = fheroes2::Key::KEY_C; } else if ( button.button == SDL_CONTROLLER_BUTTON_BACK ) { key_value = fheroes2::Key::KEY_F; } else if ( button.button == SDL_CONTROLLER_BUTTON_START ) { key_value = fheroes2::Key::KEY_ENTER; } else { key_value = fheroes2::Key::NONE; } #if defined( TARGET_NINTENDO_SWITCH ) // Custom button mapping for Nintendo Switch if ( button.button == SWITCH_BUTTON_Y ) { key_value = fheroes2::Key::KEY_ENTER; } else if ( button.button == SWITCH_BUTTON_X ) { key_value = fheroes2::Key::KEY_ESCAPE; } else if ( button.button == SWITCH_BUTTON_R ) { key_value = fheroes2::Key::KEY_T; } else if ( button.button == SWITCH_BUTTON_L ) { key_value = fheroes2::Key::KEY_H; } else if ( button.button == SWITCH_BUTTON_MINUS ) { key_value = fheroes2::Key::KEY_E; } else if ( button.button == SWITCH_BUTTON_PLUS ) { key_value = fheroes2::Key::KEY_C; } else { key_value = fheroes2::Key::NONE; } #endif } else if ( button.button == SDL_CONTROLLER_BUTTON_RIGHTSHOULDER ) { _controllerPointerSpeed /= CONTROLLER_TRIGGER_CURSOR_SPEEDUP; } } void LocalEvent::ProcessControllerAxisMotion() { const double deltaTime = _controllerTimer.getS() * 1000.0; _controllerTimer.reset(); if ( _controllerLeftXAxis != 0 || _controllerLeftYAxis != 0 ) { SetModes( MOUSE_MOTION ); const int32_t xSign = ( _controllerLeftXAxis > 0 ) - ( _controllerLeftXAxis < 0 ); const int32_t ySign = ( _controllerLeftYAxis > 0 ) - ( _controllerLeftYAxis < 0 ); _emulatedPointerPosX += pow( std::abs( _controllerLeftXAxis ), CONTROLLER_AXIS_SPEEDUP ) * xSign * deltaTime * _controllerPointerSpeed; _emulatedPointerPosY += pow( std::abs( _controllerLeftYAxis ), CONTROLLER_AXIS_SPEEDUP ) * ySign * deltaTime * _controllerPointerSpeed; const fheroes2::Display & display = fheroes2::Display::instance(); if ( _emulatedPointerPosX < 0 ) _emulatedPointerPosX = 0; else if ( _emulatedPointerPosX >= display.width() ) _emulatedPointerPosX = display.width() - 1; if ( _emulatedPointerPosY < 0 ) _emulatedPointerPosY = 0; else if ( _emulatedPointerPosY >= display.height() ) _emulatedPointerPosY = display.height() - 1; mouse_cu.x = static_cast( _emulatedPointerPosX ); mouse_cu.y = static_cast( _emulatedPointerPosY ); if ( _globalMouseMotionEventHook ) { _mouseCursorRenderArea = _globalMouseMotionEventHook( mouse_cu.x, mouse_cu.y ); } } // map scroll with right stick if ( _controllerRightXAxis != 0 || _controllerRightYAxis != 0 ) { _controllerScrollActive = true; SetModes( KEY_PRESSED ); if ( _controllerRightXAxis < 0 ) key_value = fheroes2::Key::KEY_LEFT; else if ( _controllerRightXAxis > 0 ) key_value = fheroes2::Key::KEY_RIGHT; else if ( _controllerRightYAxis < 0 ) key_value = fheroes2::Key::KEY_UP; else if ( _controllerRightYAxis > 0 ) key_value = fheroes2::Key::KEY_DOWN; } else if ( _controllerScrollActive ) { ResetModes( KEY_PRESSED ); _controllerScrollActive = false; } } bool LocalEvent::HandleWindowEvent( const SDL_WindowEvent & event ) { if ( event.event == SDL_WINDOWEVENT_FOCUS_LOST ) { StopSounds(); return false; } if ( event.event == SDL_WINDOWEVENT_FOCUS_GAINED ) { ResumeSounds(); return true; } return ( event.event == SDL_WINDOWEVENT_RESIZED ); } void LocalEvent::HandleRenderDeviceResetEvent() { // All textures has to be recreated. The only way to do it is to reset everything and render it back. fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::Image temp; assert( display.singleLayer() ); temp._disableTransformLayer(); fheroes2::Copy( display, temp ); display.release(); fheroes2::Copy( temp, display ); } bool LocalEvent::MousePressLeft() const { return ( modes & MOUSE_PRESSED ) && SDL_BUTTON_LEFT == mouse_button; } bool LocalEvent::MouseReleaseLeft() const { return ( modes & MOUSE_RELEASED ) && SDL_BUTTON_LEFT == mouse_button; } bool LocalEvent::MousePressRight() const { return ( modes & MOUSE_PRESSED ) && SDL_BUTTON_RIGHT == mouse_button; } void LocalEvent::HandleKeyboardEvent( const SDL_KeyboardEvent & event ) { const fheroes2::Key key = getKeyFromSDL( event.keysym.sym ); if ( key == fheroes2::Key::NONE ) { return; } if ( event.type == SDL_KEYDOWN ) { SetModes( KEY_PRESSED ); SetModes( KEY_HOLD ); if ( _globalKeyDownEventHook ) { _globalKeyDownEventHook( key, getKeyModifierFromSDL( event.keysym.mod ) ); } } else if ( event.type == SDL_KEYUP ) { ResetModes( KEY_PRESSED ); ResetModes( KEY_HOLD ); } key_value = key; } void LocalEvent::HandleMouseMotionEvent( const SDL_MouseMotionEvent & motion ) { SetModes( MOUSE_MOTION ); mouse_cu.x = motion.x; mouse_cu.y = motion.y; _emulatedPointerPosX = mouse_cu.x; _emulatedPointerPosY = mouse_cu.y; if ( _globalMouseMotionEventHook ) { _mouseCursorRenderArea = _globalMouseMotionEventHook( motion.x, motion.y ); } } void LocalEvent::HandleMouseButtonEvent( const SDL_MouseButtonEvent & button ) { if ( button.state == SDL_PRESSED ) { _mouseButtonLongPressDelay.reset(); SetModes( MOUSE_PRESSED ); } else { ResetModes( MOUSE_PRESSED ); SetModes( MOUSE_RELEASED ); } mouse_button = button.button; mouse_cu.x = button.x; mouse_cu.y = button.y; _emulatedPointerPosX = mouse_cu.x; _emulatedPointerPosY = mouse_cu.y; if ( modes & MOUSE_PRESSED ) { switch ( button.button ) { case SDL_BUTTON_LEFT: mouse_pl = mouse_cu; break; case SDL_BUTTON_MIDDLE: mouse_pm = mouse_cu; break; case SDL_BUTTON_RIGHT: mouse_pr = mouse_cu; break; default: break; } } // Mouse button has been released else { switch ( button.button ) { case SDL_BUTTON_LEFT: mouse_rl = mouse_cu; break; case SDL_BUTTON_MIDDLE: mouse_rm = mouse_cu; break; case SDL_BUTTON_RIGHT: mouse_rr = mouse_cu; break; default: break; } } } bool LocalEvent::MouseClickLeft() { if ( !( modes & MOUSE_RELEASED ) ) { return false; } if ( SDL_BUTTON_LEFT != mouse_button ) { return false; } if ( _mouseButtonLongPressDelay.isTriggered() ) { return false; } ResetModes( MOUSE_RELEASED ); return true; } bool LocalEvent::MouseClickLeft( const fheroes2::Rect & rt ) { if ( !( modes & MOUSE_RELEASED ) ) { return false; } if ( SDL_BUTTON_LEFT != mouse_button ) { return false; } if ( !( rt & mouse_pl ) || !( rt & mouse_rl ) ) { return false; } if ( _mouseButtonLongPressDelay.isTriggered() ) { return false; } ResetModes( MOUSE_RELEASED ); return true; } bool LocalEvent::MouseClickMiddle() { if ( !( modes & MOUSE_RELEASED ) ) { return false; } if ( SDL_BUTTON_MIDDLE != mouse_button ) { return false; } if ( _mouseButtonLongPressDelay.isTriggered() ) { return false; } ResetModes( MOUSE_RELEASED ); return true; } bool LocalEvent::MouseClickRight() { if ( !( modes & MOUSE_RELEASED ) ) { return false; } if ( SDL_BUTTON_RIGHT != mouse_button ) { return false; } if ( _mouseButtonLongPressDelay.isTriggered() ) { return false; } ResetModes( MOUSE_RELEASED ); return true; } bool LocalEvent::MouseClickRight( const fheroes2::Rect & rt ) { if ( !( modes & MOUSE_RELEASED ) ) { return false; } if ( SDL_BUTTON_RIGHT != mouse_button ) { return false; } if ( !( rt & mouse_pr ) || !( rt & mouse_rr ) ) { return false; } if ( _mouseButtonLongPressDelay.isTriggered() ) { return false; } ResetModes( MOUSE_RELEASED ); return true; } bool LocalEvent::MouseLongPressLeft( const fheroes2::Rect & rt ) { if ( !( modes & MOUSE_PRESSED ) ) { return false; } if ( SDL_BUTTON_LEFT != mouse_button ) { return false; } if ( !( rt & mouse_pl ) ) { return false; } if ( !_mouseButtonLongPressDelay.isPassed() ) { return false; } if ( _mouseButtonLongPressDelay.isTriggered() ) { return false; } _mouseButtonLongPressDelay.setTriggered(); return true; } bool LocalEvent::MouseWheelUp() const { return ( modes & MOUSE_WHEEL ) && mouse_wm.y > 0; } bool LocalEvent::MouseWheelDn() const { return ( modes & MOUSE_WHEEL ) && mouse_wm.y < 0; } int32_t LocalEvent::getCurrentKeyModifiers() { return getKeyModifierFromSDL( SDL_GetModState() ); } void LocalEvent::setEventProcessingStates() { // The list below is based on event types which require >= SDL 2.0.5. Is there a reason why you want to compile with an older SDL version? #if !SDL_VERSION_ATLEAST( 2, 0, 5 ) #error Minimal supported SDL version is 2.0.5. #endif // Full list of events and their requirements can be found at https://wiki.libsdl.org/SDL_EventType setEventProcessingState( SDL_QUIT, true ); // TODO: we don't process this event. Add the logic. setEventProcessingState( SDL_APP_TERMINATING, false ); // This is a very serious situation and we should handle it. setEventProcessingState( SDL_APP_LOWMEMORY, true ); // TODO: we don't process this event. Add the logic. setEventProcessingState( SDL_APP_WILLENTERBACKGROUND, false ); // TODO: we don't process this event. Add the logic. setEventProcessingState( SDL_APP_DIDENTERBACKGROUND, false ); // TODO: we don't process this event. Add the logic. setEventProcessingState( SDL_APP_WILLENTERFOREGROUND, false ); // TODO: we don't process this event. Add the logic. setEventProcessingState( SDL_APP_DIDENTERFOREGROUND, false ); // SDL_LOCALECHANGED is supported from SDL 2.0.14 // TODO: we don't process this event. Add the logic. setEventProcessingState( SDL_DISPLAYEVENT, false ); setEventProcessingState( SDL_WINDOWEVENT, true ); // TODO: verify why disabled processing of this event. setEventProcessingState( SDL_SYSWMEVENT, false ); setEventProcessingState( SDL_KEYDOWN, true ); setEventProcessingState( SDL_KEYUP, true ); // SDL_TEXTINPUT and SDL_TEXTEDITING are enabled and disabled by SDL_StartTextInput() and SDL_StopTextInput() functions. // Do not enable them here. setEventProcessingState( SDL_TEXTEDITING, false ); setEventProcessingState( SDL_TEXTINPUT, false ); setEventProcessingState( SDL_KEYMAPCHANGED, false ); // supported from SDL 2.0.4 // SDL_TEXTEDITING_EXT is supported only from SDL 2.0.22 setEventProcessingState( SDL_MOUSEMOTION, true ); setEventProcessingState( SDL_MOUSEBUTTONDOWN, true ); setEventProcessingState( SDL_MOUSEBUTTONUP, true ); setEventProcessingState( SDL_MOUSEWHEEL, true ); setEventProcessingState( SDL_JOYAXISMOTION, true ); setEventProcessingState( SDL_JOYBALLMOTION, true ); setEventProcessingState( SDL_JOYHATMOTION, true ); setEventProcessingState( SDL_JOYBUTTONDOWN, true ); setEventProcessingState( SDL_JOYBUTTONUP, true ); setEventProcessingState( SDL_JOYDEVICEADDED, true ); setEventProcessingState( SDL_JOYDEVICEREMOVED, true ); setEventProcessingState( SDL_CONTROLLERAXISMOTION, true ); setEventProcessingState( SDL_CONTROLLERBUTTONDOWN, true ); setEventProcessingState( SDL_CONTROLLERBUTTONUP, true ); setEventProcessingState( SDL_CONTROLLERDEVICEADDED, true ); setEventProcessingState( SDL_CONTROLLERDEVICEREMOVED, true ); setEventProcessingState( SDL_CONTROLLERDEVICEREMAPPED, true ); // SDL_CONTROLLERTOUCHPADDOWN is supported from SDL 2.0.14 // SDL_CONTROLLERTOUCHPADMOTION is supported from SDL 2.0.14 // SDL_CONTROLLERTOUCHPADUP is supported from SDL 2.0.14 // SDL_CONTROLLERSENSORUPDATE is supported from SDL 2.0.14 setEventProcessingState( SDL_FINGERDOWN, true ); setEventProcessingState( SDL_FINGERUP, true ); setEventProcessingState( SDL_FINGERMOTION, true ); // TODO: verify why disabled processing of this event. setEventProcessingState( SDL_DOLLARGESTURE, false ); // TODO: verify why disabled processing of this event. setEventProcessingState( SDL_DOLLARRECORD, false ); // TODO: verify why disabled processing of this event. setEventProcessingState( SDL_MULTIGESTURE, false ); // We do not support clipboard within the engine. setEventProcessingState( SDL_CLIPBOARDUPDATE, false ); // We do not support drag and drop capability for the application. setEventProcessingState( SDL_DROPFILE, false ); setEventProcessingState( SDL_DROPTEXT, false ); setEventProcessingState( SDL_DROPBEGIN, false ); // supported from SDL 2.0.5 setEventProcessingState( SDL_DROPCOMPLETE, false ); // supported from SDL 2.0.5 // TODO: verify why disabled processing of this event. setEventProcessingState( SDL_AUDIODEVICEADDED, false ); // supported from SDL 2.0.4 // TODO: verify why disabled processing of this event. setEventProcessingState( SDL_AUDIODEVICEREMOVED, false ); // supported from SDL 2.0.4 // TODO: verify why disabled processing of this event. setEventProcessingState( SDL_SENSORUPDATE, false ); setEventProcessingState( SDL_RENDER_TARGETS_RESET, true ); // supported from SDL 2.0.2 setEventProcessingState( SDL_RENDER_DEVICE_RESET, true ); // supported from SDL 2.0.4 // SDL_POLLSENTINEL is supported from SDL 2.0.? // We do not support custom user events as of now. setEventProcessingState( SDL_USEREVENT, false ); } fheroes2-1.0.12+dfsg/src/engine/localevent.h000066400000000000000000000277441456075706000206200ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2008 by Josh Matthews * * Copyright (C) 2006 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2LOCALEVENT_H #define H2LOCALEVENT_H #include #include #include #include #include #include #include #include #include #include "math_base.h" #include "timing.h" namespace fheroes2 { enum class Key : int32_t { NONE, KEY_BACKSPACE, KEY_ENTER, // This is for both Enter keys on keyboards. Check getKeyFromSDL() function for explanation. KEY_ESCAPE, KEY_SPACE, KEY_EXCLAIM, KEY_DOUBLE_QUOTE, KEY_HASH, KEY_DOLLAR, KEY_AMPERSAND, KEY_QUOTE, KEY_LEFT_PARENTHESIS, KEY_RIGHT_PARENTHESIS, KEY_ASTERISK, KEY_PLUS, KEY_COMMA, KEY_MINUS, KEY_PERIOD, KEY_SLASH, KEY_COLON, KEY_SEMICOLON, KEY_LESS, KEY_EQUALS, KEY_GREATER, KEY_QUESTION, KEY_AT, KEY_LEFT_BRACKET, KEY_BACKSLASH, KEY_RIGHT_BRACKET, KEY_CARET, KEY_UNDERSCORE, KEY_LEFT_ALT, KEY_RIGHT_ALT, KEY_LEFT_CONTROL, KEY_RIGHT_CONTROL, KEY_LEFT_SHIFT, KEY_RIGHT_SHIFT, KEY_TAB, KEY_DELETE, KEY_PAGE_UP, KEY_PAGE_DOWN, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN, KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z, KEY_PRINT, KEY_KP_0, KEY_KP_1, KEY_KP_2, KEY_KP_3, KEY_KP_4, KEY_KP_5, KEY_KP_6, KEY_KP_7, KEY_KP_8, KEY_KP_9, KEY_KP_PERIOD, KEY_KP_DIVIDE, KEY_KP_MULTIPLY, KEY_KP_MINUS, KEY_KP_PLUS, KEY_KP_ENTER, KEY_KP_EQUALS, KEY_HOME, KEY_END, // Put all new keys before this line. LAST_KEY }; // Key modifier is used for key combination detection. enum KeyModifier : int32_t { KEY_MODIFIER_NONE = 0, KEY_MODIFIER_CTRL = 0x1, KEY_MODIFIER_SHIFT = 0x2, KEY_MODIFIER_ALT = 0x4, KEY_MODIFIER_CAPS = 0x8, KEY_MODIFIER_NUM = 0x10 }; const char * KeySymGetName( const Key key ); bool PressIntKey( uint32_t max, uint32_t & result ); size_t InsertKeySym( std::string & res, size_t pos, const Key key, const int32_t mod ); } class LocalEvent { public: static LocalEvent & Get(); static LocalEvent & GetClean(); // reset all previous event statuses and return a reference for events void setGlobalMouseMotionEventHook( std::function hook ) { _globalMouseMotionEventHook = std::move( hook ); } void setGlobalKeyDownEventHook( std::function hook ) { _globalKeyDownEventHook = std::move( hook ); } static void setEventProcessingStates(); bool HandleEvents( const bool sleepAfterEventProcessing = true, const bool allowExit = false ); bool MouseMotion() const { return ( modes & MOUSE_MOTION ) == MOUSE_MOTION; } const fheroes2::Point & GetMouseCursor() const { return mouse_cu; } const fheroes2::Point & GetMousePressLeft() const { return mouse_pl; } bool MouseClickLeft(); bool MouseClickMiddle(); bool MouseClickRight(); bool MouseClickLeft( const fheroes2::Rect & rt ); bool MouseClickRight( const fheroes2::Rect & rt ); // The long press event is triggered only once. If this event was triggered (i.e. this method was called // and returned true), then after releasing the mouse button, the click event will not be triggered. bool MouseLongPressLeft( const fheroes2::Rect & rt ); bool MouseWheelUp() const; bool MouseWheelDn() const; bool MousePressLeft() const; bool MousePressLeft( const fheroes2::Rect & rt ) const { return MousePressLeft() && ( rt & mouse_pl ); } bool MousePressRight() const; bool MousePressRight( const fheroes2::Rect & rt ) const { return MousePressRight() && ( rt & mouse_pr ); } bool MouseReleaseLeft() const; bool MouseReleaseLeft( const fheroes2::Rect & rt ) const { return MouseReleaseLeft() && ( rt & mouse_rl ); } bool MouseWheelUp( const fheroes2::Rect & rt ) const { return MouseWheelUp() && ( rt & mouse_cu ); } bool MouseWheelDn( const fheroes2::Rect & rt ) const { return MouseWheelDn() && ( rt & mouse_cu ); } bool MouseCursor( const fheroes2::Rect & rt ) const { return rt & mouse_cu; } // Returns true if the current mouse event is triggered by the touchpad and not the mouse (in other words, // this event is emulated using the touchpad). It is assumed that there is only one mouse in the system, // and touchpad events have priority in this sense - that is, as long as the touchpad emulates pressing any // mouse button, it is assumed that all mouse events are triggered by the touchpad. bool MouseEventFromTouchpad() const { return modes & MOUSE_TOUCH; } bool KeyPress() const { return modes & KEY_PRESSED; } bool KeyPress( fheroes2::Key key ) const { return key == key_value && ( modes & KEY_PRESSED ); } bool KeyHold() const { return ( modes & KEY_HOLD ) != 0; } fheroes2::Key KeyValue() const { return key_value; } static int32_t getCurrentKeyModifiers(); void OpenController(); void CloseController(); static void OpenTouchpad(); void SetControllerPointerSpeed( const int newSpeed ) { if ( newSpeed > 0 ) { _controllerPointerSpeed = newSpeed / CONTROLLER_SPEED_MOD; } } private: LocalEvent(); void HandleMouseMotionEvent( const SDL_MouseMotionEvent & ); void HandleMouseButtonEvent( const SDL_MouseButtonEvent & ); void HandleKeyboardEvent( const SDL_KeyboardEvent & ); static void StopSounds(); static void ResumeSounds(); void HandleMouseWheelEvent( const SDL_MouseWheelEvent & ); void HandleControllerAxisEvent( const SDL_ControllerAxisEvent & motion ); void HandleControllerButtonEvent( const SDL_ControllerButtonEvent & button ); void HandleTouchEvent( const SDL_TouchFingerEvent & event ); // Returns true if frame rendering is required. static bool HandleWindowEvent( const SDL_WindowEvent & event ); static void HandleRenderDeviceResetEvent(); void ProcessControllerAxisMotion(); enum : uint32_t { // Key on the keyboard has been pressed KEY_PRESSED = 0x0001, // Mouse cursor has been moved MOUSE_MOTION = 0x0002, // Mouse button is currently pressed MOUSE_PRESSED = 0x0004, // Mouse button has just been released MOUSE_RELEASED = 0x0008, // Mouse wheel has been rotated MOUSE_WHEEL = 0x0010, // Current mouse event has been triggered by the touchpad MOUSE_TOUCH = 0x0020, // Key on the keyboard is currently being held down KEY_HOLD = 0x0040, }; enum { CONTROLLER_L_DEADZONE = 4000, CONTROLLER_R_DEADZONE = 25000 }; void SetModes( const uint32_t f ) { modes |= f; } void ResetModes( const uint32_t f ) { modes &= ~f; } uint32_t modes; fheroes2::Key key_value; int mouse_button; fheroes2::Point mouse_pl; // press left fheroes2::Point mouse_pm; // press middle fheroes2::Point mouse_pr; // press right fheroes2::Point mouse_rl; // release left fheroes2::Point mouse_rm; // release middle fheroes2::Point mouse_rr; // release right fheroes2::Point mouse_cu; // point cursor fheroes2::Point mouse_wm; // wheel movement class LongPressDelay final : public fheroes2::TimeDelay { public: using TimeDelay::TimeDelay; void reset() { TimeDelay::reset(); _triggered = false; } bool isTriggered() const { return _triggered; } void setTriggered() { _triggered = true; } private: bool _triggered{ false }; }; LongPressDelay _mouseButtonLongPressDelay; std::function _globalMouseMotionEventHook; std::function _globalKeyDownEventHook; fheroes2::Rect _mouseCursorRenderArea; // used to convert user-friendly pointer speed values into more usable ones const double CONTROLLER_SPEED_MOD = 2000000.0; double _controllerPointerSpeed = 10.0 / CONTROLLER_SPEED_MOD; double _emulatedPointerPosX = 0; double _emulatedPointerPosY = 0; // bigger value corresponds to faster pointer movement speed with bigger stick axis values const double CONTROLLER_AXIS_SPEEDUP = 1.03; const double CONTROLLER_TRIGGER_CURSOR_SPEEDUP = 2.0; SDL_GameController * _gameController = nullptr; fheroes2::Time _controllerTimer; int16_t _controllerLeftXAxis = 0; int16_t _controllerLeftYAxis = 0; int16_t _controllerRightXAxis = 0; int16_t _controllerRightYAxis = 0; bool _controllerScrollActive = false; // IDs of currently active (touching the touchpad) fingers, if any. These IDs consist of a touch device id and a finger id. std::pair>, std::optional>> _fingerIds; // Is the two-finger gesture currently being processed bool _isTwoFingerGestureInProgress = false; }; #endif fheroes2-1.0.12+dfsg/src/engine/logging.cpp000066400000000000000000000122161456075706000204310ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #if defined( _WIN32 ) #define WIN32_LEAN_AND_MEAN #include #endif #if defined( MACOS_APP_BUNDLE ) #include #endif #include "logging.h" #include "system.h" namespace { int debugLevel = DBG_ALL_WARN; bool textSupportMode = false; #if defined( _WIN32 ) // Sets the Windows console codepage to the system codepage class ConsoleCPSwitcher { public: ConsoleCPSwitcher() : _consoleOutputCP( GetConsoleOutputCP() ) { if ( _consoleOutputCP > 0 ) { SetConsoleOutputCP( GetACP() ); } } ConsoleCPSwitcher( const ConsoleCPSwitcher & ) = delete; ~ConsoleCPSwitcher() { if ( _consoleOutputCP > 0 ) { SetConsoleOutputCP( _consoleOutputCP ); } } ConsoleCPSwitcher & operator=( const ConsoleCPSwitcher & ) = delete; private: const UINT _consoleOutputCP; }; const ConsoleCPSwitcher consoleCPSwitcher; #endif } namespace Logging { #if defined( TARGET_NINTENDO_SWITCH ) || defined( _WIN32 ) std::ofstream logFile; // This mutex protects operations with logFile std::mutex logMutex; #endif const char * GetDebugOptionName( const int name ) { if ( name & DBG_ENGINE ) return "DBG_ENGINE"; else if ( name & DBG_GAME ) return "DBG_GAME"; else if ( name & DBG_BATTLE ) return "DBG_BATTLE"; else if ( name & DBG_AI ) return "DBG_AI"; else if ( name & DBG_NETWORK ) return "DBG_NETWORK"; else if ( name & DBG_OTHER ) return "DBG_OTHER"; else if ( name & DBG_DEVEL ) return "DBG_DEVEL"; return ""; } std::string GetTimeString() { const tm tmi = System::GetTM( std::time( nullptr ) ); std::array buf; const size_t writtenBytes = std::strftime( buf.data(), buf.size(), "%d.%m.%Y %H:%M:%S", &tmi ); if ( writtenBytes == 0 ) { assert( 0 ); return ""; } return std::string( buf.data() ); } void InitLog() { #if defined( TARGET_NINTENDO_SWITCH ) const std::scoped_lock lock( logMutex ); logFile.open( "fheroes2.log", std::ofstream::out ); #elif defined( _WIN32 ) const std::scoped_lock lock( logMutex ); const std::string logPath( System::concatPath( System::GetConfigDirectory( "fheroes2" ), "fheroes2.log" ) ); System::MakeDirectory( System::GetDirname( logPath ) ); logFile.open( logPath, std::ofstream::out ); #elif defined( MACOS_APP_BUNDLE ) openlog( "fheroes2", LOG_CONS | LOG_NDELAY, LOG_USER ); setlogmask( LOG_UPTO( LOG_WARNING ) ); #endif } void setDebugLevel( const int level ) { debugLevel = level; } int getDebugLevel() { return debugLevel; } void setTextSupportMode( const bool enableTextSupportMode ) { textSupportMode = enableTextSupportMode; } bool isTextSupportModeEnabled() { return textSupportMode; } } bool IS_DEBUG( const int name, const int level ) { return ( ( DBG_ENGINE & name ) && ( ( DBG_ENGINE & debugLevel ) >> 2 ) >= level ) || ( ( DBG_GAME & name ) && ( ( DBG_GAME & debugLevel ) >> 4 ) >= level ) || ( ( DBG_BATTLE & name ) && ( ( DBG_BATTLE & debugLevel ) >> 6 ) >= level ) || ( ( DBG_AI & name ) && ( ( DBG_AI & debugLevel ) >> 8 ) >= level ) || ( ( DBG_NETWORK & name ) && ( ( DBG_NETWORK & debugLevel ) >> 10 ) >= level ) || ( ( DBG_DEVEL & name ) && ( ( DBG_DEVEL & debugLevel ) >> 12 ) >= level ); } fheroes2-1.0.12+dfsg/src/engine/logging.h000066400000000000000000000301721456075706000200770ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2LOGGING_H #define H2LOGGING_H #include #include // IWYU pragma: keep #include enum { DBG_WARN = 0x0001, DBG_INFO = 0x0002, DBG_TRACE = 0x0003, DBG_ENGINE = 0x000C, DBG_GAME = 0x0030, DBG_BATTLE = 0x00C0, DBG_AI = 0x0300, DBG_NETWORK = 0x0C00, DBG_OTHER = 0x3000, DBG_DEVEL = 0xC000, DBG_ENGINE_WARN = 0x0004, DBG_GAME_WARN = 0x0010, DBG_BATTLE_WARN = 0x0040, DBG_AI_WARN = 0x0100, DBG_NETWORK_WARN = 0x0400, DBG_OTHER_WARN = 0x1000, DBG_ENGINE_INFO = 0x0008, DBG_GAME_INFO = 0x0020, DBG_BATTLE_INFO = 0x0080, DBG_AI_INFO = 0x0200, DBG_NETWORK_INFO = 0x0800, DBG_OTHER_INFO = 0x2000, DBG_ENGINE_TRACE = DBG_ENGINE, DBG_GAME_TRACE = DBG_GAME, DBG_BATTLE_TRACE = DBG_BATTLE, DBG_AI_TRACE = DBG_AI, DBG_NETWORK_TRACE = DBG_NETWORK, DBG_OTHER_TRACE = DBG_OTHER, DBG_ALL = DBG_ENGINE | DBG_GAME | DBG_BATTLE | DBG_AI | DBG_NETWORK | DBG_OTHER, DBG_ALL_WARN = DBG_ENGINE_WARN | DBG_GAME_WARN | DBG_BATTLE_WARN | DBG_AI_WARN | DBG_NETWORK_WARN | DBG_OTHER_WARN, DBG_ALL_INFO = DBG_ENGINE_INFO | DBG_GAME_INFO | DBG_BATTLE_INFO | DBG_AI_INFO | DBG_NETWORK_INFO | DBG_OTHER_INFO, DBG_ALL_TRACE = DBG_ENGINE_TRACE | DBG_GAME_TRACE | DBG_BATTLE_TRACE | DBG_AI_TRACE | DBG_NETWORK_TRACE | DBG_OTHER_TRACE }; #if defined( TARGET_NINTENDO_SWITCH ) || defined( _WIN32 ) #include #include namespace Logging { extern std::ofstream logFile; // This mutex protects operations with logFile extern std::mutex logMutex; } #endif namespace Logging { const char * GetDebugOptionName( const int name ); std::string GetTimeString(); // Initialize logging. Some systems require writing logging information into a file. void InitLog(); void setDebugLevel( const int level ); int getDebugLevel(); void setTextSupportMode( const bool enableTextSupportMode ); bool isTextSupportModeEnabled(); } #if defined( _WIN32 ) && defined( WITH_DEBUG ) #define COUT( x ) \ { \ const std::scoped_lock _logfile_lock( Logging::logMutex ); /* The name was chosen on purpose to avoid name collisions with outer code blocks. */ \ \ Logging::logFile << x << std::endl; \ Logging::logFile.flush(); \ std::cerr << x << std::endl; \ } #elif defined( TARGET_NINTENDO_SWITCH ) || defined( _WIN32 ) #define COUT( x ) \ { \ const std::scoped_lock _logfile_lock( Logging::logMutex ); /* The name was chosen on purpose to avoid name collisions with outer code blocks. */ \ \ Logging::logFile << x << std::endl; \ Logging::logFile.flush(); \ } #elif defined( TARGET_PS_VITA ) #include #define COUT( x ) \ { \ std::ostringstream osss; \ osss << x << std::endl; \ sceClibPrintf( osss.str().c_str() ); \ } #elif defined( MACOS_APP_BUNDLE ) #include #define COUT( x ) \ { \ std::ostringstream logMessage; \ logMessage << x; \ syslog( LOG_WARNING, "fheroes2_log: %s", logMessage.str().c_str() ); \ } #elif defined( ANDROID ) #include #define COUT( x ) \ { \ std::ostringstream osss; \ osss << x << std::endl; \ __android_log_print( ANDROID_LOG_INFO, "fheroes2", "%s", osss.str().c_str() ); \ } #else // Default: log to STDERR #define COUT( x ) \ { \ std::cerr << x << std::endl; \ } #endif #define VERBOSE_LOG( x ) \ { \ COUT( Logging::GetTimeString() << ": [VERBOSE]\t" << __FUNCTION__ << ": " << x ); \ } #define ERROR_LOG( x ) \ { \ COUT( Logging::GetTimeString() << ": [ERROR]\t" << __FUNCTION__ << ": " << x ); \ } #ifdef WITH_DEBUG #define DEBUG_LOG( x, y, z ) \ if ( IS_DEBUG( x, y ) ) { \ COUT( Logging::GetTimeString() << ": [" << Logging::GetDebugOptionName( x ) << "]\t" << __FUNCTION__ << ": " << z ); \ } #else #define DEBUG_LOG( x, y, z ) #endif #define IS_DEVEL() IS_DEBUG( DBG_DEVEL, DBG_INFO ) bool IS_DEBUG( const int name, const int level ); namespace Logging { // This structure simply adds text separators. It is used for Text Support Mode only. struct TextSupportLogger { TextSupportLogger(){ COUT( "----------" ) } TextSupportLogger( const TextSupportLogger & ) = delete; TextSupportLogger( const TextSupportLogger && ) = delete; TextSupportLogger & operator=( const TextSupportLogger & ) = delete; TextSupportLogger & operator=( const TextSupportLogger && ) = delete; ~TextSupportLogger() { COUT( "----------" ) } }; } // Put this macro at the beginning of code block (eg. function) which is responsible for text support mode output. #define START_TEXT_SUPPORT_MODE \ if ( !Logging::isTextSupportModeEnabled() ) { \ return; \ } \ const Logging::TextSupportLogger _temp_logger; // The name was chosen on purpose to avoid collisions with other variable names within a code block. #endif // H2LOGGING_H fheroes2-1.0.12+dfsg/src/engine/math_base.h000066400000000000000000000177301456075706000204010ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif // Below source code was partially taken from https://github.com/ihhub/penguinv open source project namespace fheroes2 { // overload this function for floating types template bool isEqual( const _Type & value1, const _Type & value2 ) { return ( value1 == value2 ); } template struct PointBase2D { PointBase2D() : x( 0 ) , y( 0 ) {} PointBase2D( _Type _x, _Type _y ) : x( _x ) , y( _y ) {} bool operator==( const PointBase2D & point ) const { return isEqual( x, point.x ) && isEqual( y, point.y ); } bool operator!=( const PointBase2D & point ) const { return !( *this == point ); } PointBase2D & operator+=( const PointBase2D & point ) { x += point.x; y += point.y; return *this; } PointBase2D & operator-=( const PointBase2D & point ) { x -= point.x; y -= point.y; return *this; } PointBase2D operator+( const PointBase2D & point ) const { return PointBase2D( x + point.x, y + point.y ); } PointBase2D operator-( const PointBase2D & point ) const { return PointBase2D( x - point.x, y - point.y ); } bool operator<( const PointBase2D & point ) const { return x == point.x ? y < point.y : x < point.x; } _Type x; _Type y; }; template struct SizeBase2D { SizeBase2D() : width( 0 ) , height( 0 ) {} SizeBase2D( _Type _width, _Type _height ) : width( _width ) , height( _height ) {} bool operator==( const SizeBase2D & size ) const { return isEqual( width, size.width ) && isEqual( height, size.height ); } bool operator!=( const SizeBase2D & size ) const { return !( *this == size ); } bool operator<( const SizeBase2D & size ) const { return width < size.width || ( width == size.width && height < size.height ); } SizeBase2D & operator+=( const SizeBase2D & size ) { width += size.width; height += size.height; return *this; } SizeBase2D & operator-=( const SizeBase2D & size ) { width -= size.width; height -= size.height; return *this; } SizeBase2D operator+( const SizeBase2D & size ) const { return SizeBase2D( width + size.width, height + size.height ); } SizeBase2D operator-( const SizeBase2D & size ) const { return SizeBase2D( width - size.width, height - size.height ); } _Type width; _Type height; }; template struct RectBase2D { RectBase2D() : x( 0 ) , y( 0 ) , width( 0 ) , height( 0 ) {} RectBase2D( _TypePoint _x, _TypePoint _y, _TypeSize _width, _TypeSize _height ) : x( _x ) , y( _y ) , width( _width ) , height( _height ) {} RectBase2D( const PointBase2D<_TypePoint> & point, const SizeBase2D<_TypeSize> & size ) : x( point.x ) , y( point.y ) , width( size.width ) , height( size.height ) {} bool operator==( const RectBase2D & rect ) const { return isEqual( x, rect.x ) && isEqual( y, rect.y ) && isEqual( width, rect.width ) && isEqual( height, rect.height ); } bool operator!=( const RectBase2D & rect ) const { return !( *this == rect ); } RectBase2D & operator+=( const PointBase2D<_TypePoint> & point ) { x += point.x; y += point.y; return *this; } RectBase2D & operator-=( const PointBase2D<_TypePoint> & point ) { x -= point.x; y -= point.y; return *this; } RectBase2D operator+( const PointBase2D<_TypePoint> & point ) const { return RectBase2D( x + point.x, y + point.y, width, height ); } RectBase2D operator-( const PointBase2D<_TypePoint> & point ) const { return RectBase2D( x - point.x, y - point.y, width, height ); } // Check whether a point within the rectangle bool operator&( const PointBase2D<_TypePoint> & point ) const { return point.x >= x && point.y >= y && point.x < ( x + width ) && point.y < ( y + height ); } // Check whether rectangles are intersecting each other bool operator&( const RectBase2D & rect ) const { return x <= rect.x + rect.width && rect.x <= x + width && y <= rect.y + rect.height && rect.y <= y + height; } // Find intersection rectangle RectBase2D operator^( const RectBase2D & rect ) const { RectBase2D output = rect; if ( output.x < x ) { const _TypePoint diff = x - output.x; output.x = x; output.width -= diff; } if ( output.y < y ) { const _TypePoint diff = y - output.y; output.y = y; output.height -= diff; } if ( output.x > x + width || output.y > y + height ) return RectBase2D(); if ( output.x + output.width > x + width ) { const _TypePoint diff = output.x + output.width - ( x + width ); output.width -= diff; } if ( output.y + output.height > y + height ) { const _TypePoint diff = output.y + output.height - ( y + height ); output.height -= diff; } return output; } PointBase2D<_TypePoint> getPosition() const { return PointBase2D<_TypePoint>( x, y ); } _TypePoint x; _TypePoint y; _TypeSize width; _TypeSize height; }; using Point = PointBase2D; using Size = SizeBase2D; using Rect = RectBase2D; } fheroes2-1.0.12+dfsg/src/engine/pal.cpp000066400000000000000000000456721456075706000175730ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "pal.h" #include #include namespace { const size_t paletteSize = 256; const std::vector yellowTextTable = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 115, 115, 116, 117, 117, 118, 119, 119, 120, 121, 121, 122, 123, 123, 124, 125, 125, 126, 127, 127, 128, 129, 129, 130, 130, 130, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; const std::vector whiteTextTable = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; const std::vector grayTextTable = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 36, 36, 36, 36, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; const std::vector tanTable = { 213, 213, 213, 213, 213, 213, 213, 213, 213, 213, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 200, 201, 203, 204, 206, 207, 208, 209, 210, 211, 213, 213, 213, 213, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 199, 200, 201, 203, 204, 205, 207, 207, 209, 210, 211, 212, 198, 198, 198, 198, 198, 201, 203, 205, 207, 208, 210, 210, 211, 212, 212, 213, 213, 213, 213, 213, 213, 213, 198, 198, 198, 199, 201, 203, 204, 206, 207, 208, 209, 210, 211, 212, 213, 213, 213, 213, 213, 213, 213, 213, 213, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 200, 201, 203, 204, 206, 207, 198, 198, 198, 198, 198, 198, 198, 198, 198, 200, 201, 203, 204, 206, 207, 208, 209, 210, 212, 213, 213, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 199, 201, 203, 204, 206, 207, 208, 209, 211, 212, 213, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 199, 201, 202, 204, 206, 207, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 199, 202, 205, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 198, 200, 203, 204, 207, 208, 209, 201, 203, 207, 209, 206, 209, 208, 198, 198, 207, 213, 198, 201, 206, 208, 213, 213, 213, 213, 213, 213, 213, 213, 213, 213 }; const std::vector noCycleTable = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 188, 188, 188, 188, 118, 118, 118, 118, 222, 223, 224, 225, 226, 227, 228, 229, 230, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 69, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 }; const std::vector grayTable = { 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, 18, 19, 20, 20, 21, 22, 22, 23, 24, 25, 26, 26, 27, 28, 29, 31, 14, 16, 17, 18, 20, 21, 22, 24, 25, 26, 28, 28, 29, 30, 31, 32, 32, 33, 33, 33, 34, 34, 16, 17, 18, 20, 21, 22, 23, 24, 25, 26, 27, 28, 30, 31, 32, 32, 33, 34, 35, 35, 36, 36, 36, 11, 11, 11, 11, 11, 12, 12, 12, 13, 13, 14, 15, 16, 17, 18, 19, 20, 21, 21, 22, 23, 24, 25, 12, 13, 14, 16, 17, 18, 19, 20, 21, 22, 24, 24, 25, 26, 27, 28, 29, 31, 32, 32, 33, 11, 12, 12, 13, 14, 14, 16, 16, 17, 18, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 29, 30, 32, 10, 11, 11, 12, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 18, 19, 20, 21, 22, 23, 24, 26, 10, 10, 11, 11, 11, 12, 12, 12, 12, 14, 16, 17, 18, 20, 22, 24, 17, 10, 12, 15, 19, 10, 10, 15, 17, 18, 20, 21, 22, 23, 25, 26, 27, 27, 24, 21, 22, 26, 26, 27, 36, 12, 18, 25, 19, 21, 24, 26, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36 }; const std::vector redTable = { 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 180, 182, 184, 186, 208, 209, 210, 210, 211, 211, 212, 213, 213, 196, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 180, 180, 182, 182, 184, 186, 208, 209, 209, 210, 210, 210, 211, 211, 193, 212, 212, 213, 213, 196, 197, 197, 197, 197, 197, 197, 208, 209, 210, 211, 193, 212, 213, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 209, 210, 210, 211, 212, 212, 213, 196, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 180, 180, 180, 180, 180, 182, 182, 183, 184, 185, 208, 188, 189, 190, 210, 191, 211, 193, 193, 194, 195, 196, 197, 182, 184, 208, 209, 209, 210, 211, 211, 212, 213, 213, 196, 197, 197, 197, 197, 197, 197, 197, 197, 197, 209, 209, 210, 210, 211, 211, 193, 212, 213, 213, 213, 196, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 180, 180, 180, 182, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 187, 211, 180, 180, 215, 191, 192, 193, 194, 213, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 211, 197, 211, 212, 196, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197, 197 }; const std::vector brownTable = { 50, 42, 42, 42, 42, 50, 50, 50, 50, 53, 42, 43, 43, 44, 44, 45, 46, 46, 47, 47, 48, 49, 50, 51, 52, 52, 53, 54, 55, 56, 57, 58, 60, 62, 62, 62, 62, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 45, 46, 47, 48, 50, 51, 52, 54, 55, 56, 58, 58, 59, 60, 60, 61, 62, 62, 62, 62, 62, 62, 47, 48, 49, 50, 51, 52, 53, 54, 55, 57, 58, 58, 60, 60, 62, 62, 62, 62, 62, 62, 62, 62, 62, 43, 43, 44, 44, 44, 45, 45, 45, 46, 47, 48, 48, 49, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 44, 44, 45, 46, 46, 47, 48, 49, 50, 51, 52, 53, 53, 54, 55, 56, 57, 58, 60, 62, 62, 43, 43, 44, 44, 45, 45, 46, 47, 47, 48, 49, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 60, 61, 43, 44, 46, 47, 48, 49, 50, 52, 53, 54, 55, 57, 58, 60, 60, 62, 62, 62, 62, 62, 62, 62, 62, 44, 44, 45, 45, 46, 47, 48, 49, 50, 51, 53, 54, 55, 57, 58, 59, 54, 58, 62, 62, 42, 45, 49, 55, 47, 49, 50, 51, 52, 53, 55, 56, 58, 51, 52, 55, 57, 54, 57, 56, 44, 48, 54, 62, 49, 51, 54, 57, 50, 50, 50, 50, 50, 50, 50, 50, 50, 42 }; const std::vector mirrorImageTable = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 37, 37, 37, 37, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 63, 63, 63, 63, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 85, 85, 85, 85, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 108, 108, 108, 108, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 131, 131, 131, 131, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 152, 152, 152, 152, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 175, 175, 175, 175, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 198, 198, 198, 198, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 214, 215, 216, 217, 218, 219, 220, 221, 222, 222, 223, 224, 225, 226, 227, 228, 229, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 242, 243, 244, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 }; const std::vector darkeningTable = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 36, 36, 36, 36, 36, 36, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 62, 62, 62, 62, 62, 62, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 84, 84, 84, 84, 84, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 107, 107, 107, 107, 107, 107, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 130, 130, 130, 130, 130, 130, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 151, 151, 151, 151, 151, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 174, 174, 174, 174, 174, 174, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 197, 197, 197, 197, 197, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 213, 213, 213, 213, 214, 215, 216, 217, 218, 219, 220, 221, 225, 226, 227, 228, 229, 230, 230, 230, 230, 73, 75, 77, 79, 81, 76, 78, 74, 76, 78, 80, 244, 245, 245, 245, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; const std::vector goodToEvilInterfaceTable = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 14, 15, 16, 17, 17, 18, 23, 23, 24, 25, 26, 26, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, 36, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 34, 35, 36, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, }; struct CyclingColorSet { uint8_t start; uint8_t length; bool forward; }; } std::vector PAL::GetCyclingPalette( const uint32_t stepId ) { std::vector palette = PAL::GetPalette( PaletteType::STANDARD ); static const std::vector cycleSet = { { 214, 4, false }, { 218, 4, false }, { 231, 5, true }, { 238, 4, false } }; for ( const CyclingColorSet & colorSet : cycleSet ) { for ( uint32_t id = 0; id < colorSet.length; ++id ) { uint32_t newColorID; if ( colorSet.forward ) { newColorID = colorSet.start + ( ( id + stepId ) % colorSet.length ); } else { const uint32_t lastColorID = colorSet.length - 1; newColorID = colorSet.start + lastColorID - ( ( lastColorID + stepId - id ) % colorSet.length ); } palette[colorSet.start + id] = static_cast( newColorID ); } } return palette; } const std::vector & PAL::GetPalette( const PaletteType type ) { switch ( type ) { case PaletteType::YELLOW_FONT: { assert( yellowTextTable.size() == paletteSize ); return yellowTextTable; } case PaletteType::WHITE_FONT: { assert( whiteTextTable.size() == paletteSize ); return whiteTextTable; } case PaletteType::GRAY_FONT: { assert( grayTextTable.size() == paletteSize ); return grayTextTable; } case PaletteType::RED: { assert( redTable.size() == paletteSize ); return redTable; } case PaletteType::GRAY: { assert( grayTable.size() == paletteSize ); return grayTable; } case PaletteType::BROWN: { assert( brownTable.size() == paletteSize ); return brownTable; } case PaletteType::TAN: { assert( tanTable.size() == paletteSize ); return tanTable; } case PaletteType::NO_CYCLE: { assert( noCycleTable.size() == paletteSize ); return noCycleTable; } case PaletteType::MIRROR_IMAGE: { assert( mirrorImageTable.size() == paletteSize ); return mirrorImageTable; } case PaletteType::DARKENING: { assert( darkeningTable.size() == paletteSize ); return darkeningTable; } case PaletteType::GOOD_TO_EVIL_INTERFACE: { assert( goodToEvilInterfaceTable.size() == paletteSize ); return goodToEvilInterfaceTable; } case PaletteType::CUSTOM: assert( 0 ); break; case PaletteType::STANDARD: break; default: // Did you add a new palette type? Add the corresponding palette for it! assert( 0 ); break; } static std::vector standard; if ( standard.empty() ) { standard.resize( paletteSize, 0 ); for ( size_t i = 0; i < paletteSize; ++i ) { standard[i] = static_cast( i ); } } return standard; } std::vector PAL::CombinePalettes( const std::vector & first, const std::vector & second ) { if ( first.size() != paletteSize || second.size() != paletteSize ) { // Make sure that the sizes of your palettes are correct! assert( 0 ); return std::vector(); } std::vector combined( paletteSize, 0 ); for ( size_t i = 0; i < paletteSize; ++i ) { combined[i] = second[first[i]]; } return combined; } fheroes2-1.0.12+dfsg/src/engine/pal.h000066400000000000000000000044151456075706000172260ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2PAL_H #define H2PAL_H #include #include namespace PAL { enum class PaletteType : int { STANDARD, // default YELLOW_FONT, WHITE_FONT, GRAY_FONT, RED, // for Blood Lust spell animation GRAY, // for Petrify spell effect BROWN, TAN, // for Puzzle image generation NO_CYCLE, MIRROR_IMAGE, DARKENING, // for disabled buttons GOOD_TO_EVIL_INTERFACE, // a custom palette for converting Good Interface images into Evil Interface images. CUSTOM }; std::vector GetCyclingPalette( const uint32_t stepId ); const std::vector & GetPalette( const PaletteType type ); std::vector CombinePalettes( const std::vector & first, const std::vector & second ); } #endif fheroes2-1.0.12+dfsg/src/engine/rand.cpp000066400000000000000000000106001456075706000177220ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "rand.h" #include #include "logging.h" std::mt19937 & Rand::CurrentThreadRandomDevice() { thread_local std::random_device rd; thread_local std::mt19937 gen( rd() ); return gen; } uint32_t Rand::Get( uint32_t from, uint32_t to ) { if ( to == 0 || from > to ) std::swap( from, to ); std::uniform_int_distribution distrib( from, to ); return distrib( CurrentThreadRandomDevice() ); } uint32_t Rand::GetWithSeed( uint32_t from, uint32_t to, uint32_t seed ) { if ( from > to ) std::swap( from, to ); std::uniform_int_distribution distrib( from, to ); std::mt19937 seededGen( seed ); return distrib( seededGen ); } uint32_t Rand::GetWithGen( uint32_t from, uint32_t to, std::mt19937 & gen ) { if ( from > to ) std::swap( from, to ); std::uniform_int_distribution distrib( from, to ); return distrib( gen ); } Rand::Queue::Queue( uint32_t size ) { reserve( size ); } void Rand::Queue::Push( int32_t value, uint32_t percent ) { if ( percent > 0 ) emplace_back( value, percent ); } size_t Rand::Queue::Size() const { return size(); } int32_t Rand::Queue::Get( const std::function & randomFunc ) { std::vector::iterator it; // get max it = begin(); uint32_t max = 0; for ( ; it != end(); ++it ) max += ( *it ).second; // set weight (from 100) if ( max > 0 ) { it = begin(); for ( ; it != end(); ++it ) ( *it ).second = 100 * ( *it ).second / max; } // get max max = 0; it = begin(); for ( ; it != end(); ++it ) max += ( *it ).second; uint32_t rand = randomFunc( max ); uint32_t amount = 0; it = begin(); for ( ; it != end(); ++it ) { amount += ( *it ).second; if ( rand <= amount ) return ( *it ).first; } ERROR_LOG( "weight not found, return 0" ) return 0; } int32_t Rand::Queue::Get() { return Rand::Queue::Get( []( uint32_t max ) { return Rand::Get( 0, max ); } ); } int32_t Rand::Queue::GetWithSeed( uint32_t seed ) { return Rand::Queue::Get( [seed]( uint32_t max ) { return Rand::GetWithSeed( 0, max, seed ); } ); } Rand::DeterministicRandomGenerator::DeterministicRandomGenerator( const uint32_t initialSeed ) : _currentSeed( initialSeed ) {} uint32_t Rand::DeterministicRandomGenerator::GetSeed() const { return _currentSeed; } void Rand::DeterministicRandomGenerator::UpdateSeed( const uint32_t seed ) { _currentSeed = seed; } uint32_t Rand::DeterministicRandomGenerator::Get( const uint32_t from, const uint32_t to /* = 0 */ ) { ++_currentSeed; return Rand::GetWithSeed( from, to, _currentSeed ); } fheroes2-1.0.12+dfsg/src/engine/rand.h000066400000000000000000000106711456075706000173770ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2RAND_H #define H2RAND_H #include #include #include #include #include #include #include #include #include namespace Rand { std::mt19937 & CurrentThreadRandomDevice(); uint32_t Get( uint32_t from, uint32_t to = 0 ); uint32_t GetWithSeed( uint32_t from, uint32_t to, uint32_t seed ); template ::value>::type * = nullptr> T GetWithSeed( const T from, const T to, const uint32_t seed ) { return static_cast( GetWithSeed( static_cast( from ), static_cast( to ), seed ) ); } uint32_t GetWithGen( uint32_t from, uint32_t to, std::mt19937 & gen ); template void Shuffle( std::vector & vec ) { std::shuffle( vec.begin(), vec.end(), CurrentThreadRandomDevice() ); } template void ShuffleWithGen( std::vector & vec, std::mt19937 & gen ) { std::shuffle( vec.begin(), vec.end(), gen ); } template const T & Get( const std::vector & vec ) { assert( !vec.empty() ); const uint32_t id = Rand::Get( static_cast( vec.size() - 1 ) ); return vec[id]; } template const T & GetWithGen( const std::vector & vec, std::mt19937 & gen ) { assert( !vec.empty() ); const uint32_t id = Rand::GetWithGen( 0, static_cast( vec.size() - 1 ), gen ); return vec[id]; } using ValuePercent = std::pair; class Queue : private std::vector { public: explicit Queue( uint32_t size = 0 ); void Push( int32_t value, uint32_t percent ); size_t Size() const; int32_t Get(); int32_t GetWithSeed( uint32_t seed ); private: int32_t Get( const std::function & randomFunc ); }; // Specific random generator that keeps and update its state class DeterministicRandomGenerator { public: explicit DeterministicRandomGenerator( const uint32_t initialSeed ); DeterministicRandomGenerator( const DeterministicRandomGenerator & ) = delete; DeterministicRandomGenerator & operator=( const DeterministicRandomGenerator & ) = delete; uint32_t GetSeed() const; void UpdateSeed( const uint32_t seed ); uint32_t Get( const uint32_t from, const uint32_t to = 0 ); template const T & Get( const std::vector & vec ) { ++_currentSeed; std::mt19937 seededGen( _currentSeed ); return Rand::GetWithGen( vec, seededGen ); } private: uint32_t _currentSeed; }; } #endif fheroes2-1.0.12+dfsg/src/engine/render_processor.cpp000066400000000000000000000053711456075706000223650ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "render_processor.h" #include #include "pal.h" namespace { // The maximum FPS the engine can handle is 125. // This means that a frame should be generated only every 8 ms. const uint64_t frameHalfInterval{ 4 }; } namespace fheroes2 { RenderProcessor & RenderProcessor::instance() { static RenderProcessor processor; return processor; } bool RenderProcessor::preRenderAction( std::vector & palette ) { if ( !_enableCycling ) { return false; } if ( _enableRenderers && _preRenderer ) { _preRenderer(); } if ( _cyclingTimer.getMs() < ( _cyclingInterval - frameHalfInterval ) ) { // If the current timer is less than cycling internal minus half of the frame generation then nothing is needed. return false; } // TODO: here we need to deduct possible time difference from the current time to have consistent FPS. _cyclingTimer.reset(); palette = PAL::GetCyclingPalette( _cyclingCounter ); ++_cyclingCounter; return true; } void RenderProcessor::postRenderAction() { _lastRenderCall.reset(); if ( _enableCycling && _enableRenderers && _postRenderer ) { _postRenderer(); } } } fheroes2-1.0.12+dfsg/src/engine/render_processor.h000066400000000000000000000061731456075706000220330ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include #include "timing.h" namespace fheroes2 { class RenderProcessor { public: RenderProcessor( const RenderProcessor & ) = delete; ~RenderProcessor() = default; RenderProcessor & operator=( const RenderProcessor & ) = delete; static RenderProcessor & instance(); void registerRenderers( const std::function & preRenderer, const std::function & postRenderer ) { _preRenderer = preRenderer; _postRenderer = postRenderer; } void unregisterRenderers() { _preRenderer = {}; _postRenderer = {}; } void enableRenderers() { _enableRenderers = true; } void disableRenderers() { _enableRenderers = false; } bool preRenderAction( std::vector & palette ); void postRenderAction(); void startColorCycling() { _enableCycling = true; } void stopColorCycling() { _enableCycling = false; } bool isCyclingUpdateRequired() const { return _enableCycling && _lastRenderCall.getMs() >= _cyclingInterval; } private: RenderProcessor() = default; std::function _preRenderer; std::function _postRenderer; fheroes2::Time _cyclingTimer; fheroes2::Time _lastRenderCall; uint32_t _cyclingCounter{ 0 }; bool _enableRenderers{ false }; bool _enableCycling{ false }; static const uint64_t _cyclingInterval{ 220 }; }; } fheroes2-1.0.12+dfsg/src/engine/screen.cpp000066400000000000000000001554501456075706000202720ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined( TARGET_PS_VITA ) #include #endif #include "image_palette.h" #include "logging.h" #include "screen.h" #include "tools.h" namespace { // Returns nearest screen supported resolution fheroes2::ResolutionInfo GetNearestResolution( fheroes2::ResolutionInfo resolutionInfo, const std::vector & resolutions ) { if ( resolutions.empty() ) { return resolutionInfo; } if ( resolutionInfo.gameWidth < 1 ) { resolutionInfo.gameWidth = 1; } if ( resolutionInfo.gameHeight < 1 ) { resolutionInfo.gameHeight = 1; } if ( resolutionInfo.screenWidth < resolutionInfo.gameWidth ) { resolutionInfo.screenWidth = resolutionInfo.gameWidth; } if ( resolutionInfo.screenHeight < resolutionInfo.gameHeight ) { resolutionInfo.screenHeight = resolutionInfo.gameHeight; } const double gameX = resolutionInfo.gameWidth; const double gameY = resolutionInfo.gameHeight; const double screenX = resolutionInfo.screenWidth; const double screenY = resolutionInfo.screenHeight; std::vector similarity( resolutions.size(), 0 ); for ( size_t i = 0; i < resolutions.size(); ++i ) { similarity[i] = std::fabs( resolutions[i].gameWidth - gameX ) / gameX + std::fabs( resolutions[i].gameHeight - gameY ) / gameY + std::fabs( resolutions[i].screenWidth - screenX ) / screenX + std::fabs( resolutions[i].screenHeight - screenY ) / screenY; } const std::vector::difference_type id = std::distance( similarity.begin(), std::min_element( similarity.begin(), similarity.end() ) ); return resolutions[id]; } bool IsLowerThanDefaultRes( const fheroes2::ResolutionInfo & value ) { return value.gameWidth < fheroes2::Display::DEFAULT_WIDTH || value.gameHeight < fheroes2::Display::DEFAULT_HEIGHT; } std::set FilterResolutions( const std::set & resolutionSet ) { static_assert( fheroes2::Display::DEFAULT_WIDTH == 640 && fheroes2::Display::DEFAULT_HEIGHT == 480, "Default resolution must be 640 x 480" ); if ( resolutionSet.empty() ) { return { { fheroes2::Display::DEFAULT_WIDTH, fheroes2::Display::DEFAULT_HEIGHT } }; } std::set resolutions; for ( const fheroes2::ResolutionInfo & resolution : resolutionSet ) { if ( IsLowerThanDefaultRes( resolution ) ) { continue; } resolutions.emplace( resolution ); } if ( resolutions.empty() ) { return { { fheroes2::Display::DEFAULT_WIDTH, fheroes2::Display::DEFAULT_HEIGHT } }; } { // TODO: add resolutions which are close to the current screen aspect ratio. // Some operating systems do not work well with SDL so they return very limited number of high resolutions. // Populate missing resolutions into the list. const std::set possibleResolutions = { { 640, 480 }, { 800, 600 }, { 1024, 768 }, { 1152, 864 }, { 1280, 600 }, { 1280, 720 }, { 1280, 768 }, { 1280, 960 }, { 1280, 1024 }, { 1360, 768 }, { 1400, 1050 }, { 1440, 900 }, { 1600, 900 }, { 1680, 1050 }, { 1920, 1080 } }; assert( !resolutions.empty() ); const fheroes2::ResolutionInfo lowestResolution = *( resolutions.begin() ); for ( const fheroes2::ResolutionInfo & resolution : possibleResolutions ) { if ( lowestResolution.gameWidth < resolution.gameWidth || lowestResolution.gameHeight < resolution.gameHeight || lowestResolution == resolution ) { continue; } resolutions.emplace( resolution ); } } std::set scaledResolutions; { // Widescreen devices support much higher resolutions but items on such resolutions are too tiny. In order to improve user // experience on these devices we are adding a special non-standard resolution with (potentially) non-integer scale. assert( !resolutions.empty() ); const fheroes2::ResolutionInfo biggestResolution = *( std::prev( resolutions.end() ) ); assert( biggestResolution.gameWidth == biggestResolution.screenWidth && biggestResolution.gameHeight == biggestResolution.screenHeight ); // This resolution should be really "widescreen" (or at least square), i.e. the width should be not less than the height, otherwise // the scaled resolution's in-game width will become less than DEFAULT_WIDTH if ( biggestResolution.gameWidth > fheroes2::Display::DEFAULT_WIDTH && biggestResolution.gameWidth >= biggestResolution.gameHeight ) { assert( biggestResolution.gameHeight >= fheroes2::Display::DEFAULT_HEIGHT ); scaledResolutions.emplace( biggestResolution.gameWidth * fheroes2::Display::DEFAULT_HEIGHT / biggestResolution.gameHeight, fheroes2::Display::DEFAULT_HEIGHT, biggestResolution.gameWidth, biggestResolution.gameHeight ); } } // Try to find and add scaled resolutions with integer scale (x2, x3, x4, etc) which correspond to existing "hardware" resolutions for ( const fheroes2::ResolutionInfo & resolution : resolutions ) { assert( resolution.gameWidth == resolution.screenWidth && resolution.gameHeight == resolution.screenHeight ); const int32_t maxScale = std::min( resolution.gameWidth / fheroes2::Display::DEFAULT_WIDTH, resolution.gameHeight / fheroes2::Display::DEFAULT_HEIGHT ); for ( int32_t scale = 2; scale <= maxScale; ++scale ) { if ( resolution.gameWidth % scale != 0 || resolution.gameHeight % scale != 0 ) { continue; } scaledResolutions.emplace( resolution.gameWidth / scale, resolution.gameHeight / scale, resolution.gameWidth, resolution.gameHeight ); } } resolutions.merge( scaledResolutions ); return resolutions; } std::vector StandardPaletteIndexes() { std::vector indexes( 256 ); for ( uint32_t i = 0; i < 256; ++i ) { indexes[i] = static_cast( i ); } return indexes; } const uint8_t * PALPalette( const bool forceDefaultPaletteUpdate = false ) { static std::vector palette; if ( palette.empty() || forceDefaultPaletteUpdate ) { const uint8_t * gamePalette = fheroes2::getGamePalette(); palette.resize( 256 * 3 ); for ( size_t i = 0; i < palette.size(); ++i ) { palette[i] = gamePalette[i] << 2; } } return palette.data(); } bool getActiveArea( fheroes2::Rect & roi, const int32_t width, const int32_t height ) { if ( roi.width <= 0 || roi.height <= 0 || roi.x >= width || roi.y >= height ) return false; if ( roi.x < 0 ) { const int32_t offsetX = -roi.x; if ( offsetX >= roi.width ) return false; roi.x = 0; roi.width -= offsetX; } if ( roi.y < 0 ) { const int32_t offsetY = -roi.y; if ( offsetY >= roi.height ) return false; roi.y = 0; roi.height -= offsetY; } if ( roi.x + roi.width > width ) { const int32_t offsetX = roi.x + roi.width - width; if ( offsetX >= roi.width ) return false; roi.width -= offsetX; } if ( roi.y + roi.height > height ) { const int32_t offsetY = roi.y + roi.height - height; if ( offsetY >= roi.height ) return false; roi.height -= offsetY; } return true; } const uint8_t * currentPalette = PALPalette(); // If SDL library is used #if !defined( TARGET_PS_VITA ) class BaseSDLRenderer { protected: std::vector _palette32Bit; std::vector _palette8Bit; void copyImageToSurface( const fheroes2::Image & image, SDL_Surface * surface, const fheroes2::Rect & roi ) { assert( surface != nullptr && !image.empty() ); if ( SDL_MUSTLOCK( surface ) ) { const int returnCode = SDL_LockSurface( surface ); if ( returnCode < 0 ) { ERROR_LOG( "Failed to lock surface. The error value: " << returnCode << ", description: " << SDL_GetError() ) } } const int32_t imageWidth = image.width(); const int32_t imageHeight = image.height(); const bool fullFrame = ( roi.width == imageWidth ) && ( roi.height == imageHeight ); const uint8_t * imageIn = image.image(); if ( fullFrame ) { if ( surface->format->BitsPerPixel == 32 ) { uint32_t * out = static_cast( surface->pixels ); const uint32_t * outEnd = out + imageWidth * imageHeight; const uint8_t * in = imageIn; const uint32_t * transform = _palette32Bit.data(); for ( ; out != outEnd; ++out, ++in ) *out = *( transform + *in ); } else if ( ( surface->format->BitsPerPixel == 8 ) && ( surface->pixels != imageIn ) ) { if ( imageWidth % 4 != 0 ) { const int32_t screenWidth = ( imageWidth / 4 ) * 4 + 4; for ( int32_t i = 0; i < imageHeight; ++i ) { memcpy( static_cast( surface->pixels ) + screenWidth * i, imageIn + imageWidth * i, static_cast( imageWidth ) ); } } else { memcpy( surface->pixels, imageIn, static_cast( imageWidth ) * imageHeight ); } } } else { if ( surface->format->BitsPerPixel == 32 ) { uint32_t * outY = static_cast( surface->pixels ); const uint32_t * outYEnd = outY + imageWidth * roi.height; const uint8_t * inY = imageIn + roi.x + roi.y * imageWidth; const uint32_t * transform = _palette32Bit.data(); for ( ; outY != outYEnd; outY += imageWidth, inY += imageWidth ) { uint32_t * outX = outY; const uint32_t * outXEnd = outX + roi.width; const uint8_t * inX = inY; for ( ; outX != outXEnd; ++outX, ++inX ) *outX = *( transform + *inX ); } } else if ( ( surface->format->BitsPerPixel == 8 ) && ( surface->pixels != imageIn ) ) { const int32_t screenWidth = ( imageWidth / 4 ) * 4 + 4; const int32_t screenOffset = roi.x + roi.y * screenWidth; const int32_t imageOffset = roi.x + roi.y * imageWidth; for ( int32_t i = 0; i < roi.height; ++i ) { memcpy( static_cast( surface->pixels ) + screenWidth * i + screenOffset, imageIn + imageOffset + imageWidth * i, static_cast( roi.width ) ); } } } if ( SDL_MUSTLOCK( surface ) ) SDL_UnlockSurface( surface ); } void generatePalette( const std::vector & colorIds, const SDL_Surface * surface ) { assert( surface != nullptr ); if ( surface->format->BitsPerPixel == 32 ) { _palette32Bit.resize( 256u ); if ( surface->format->Amask > 0 ) { for ( size_t i = 0; i < 256u; ++i ) { const uint8_t * value = currentPalette + colorIds[i] * 3; _palette32Bit[i] = SDL_MapRGBA( surface->format, *value, *( value + 1 ), *( value + 2 ), 255 ); } } else { for ( size_t i = 0; i < 256u; ++i ) { const uint8_t * value = currentPalette + colorIds[i] * 3; _palette32Bit[i] = SDL_MapRGB( surface->format, *value, *( value + 1 ), *( value + 2 ) ); } } } else if ( surface->format->BitsPerPixel == 8 ) { _palette8Bit.resize( 256 ); for ( uint32_t i = 0; i < 256; ++i ) { const uint8_t * value = currentPalette + colorIds[i] * 3; SDL_Color & col = _palette8Bit[i]; col.r = *value; col.g = *( value + 1 ); col.b = *( value + 2 ); } } else { // This is unsupported format. Please implement it. assert( 0 ); } } SDL_Surface * generateIconSurface( const fheroes2::Image & icon ) { if ( icon.empty() || icon.singleLayer() ) { // What are you trying to do? Icon should have not empty both image and transform layers. assert( 0 ); return nullptr; } SDL_Surface * surface = SDL_CreateRGBSurface( 0, icon.width(), icon.height(), 32, 0xFF, 0xFF00, 0xFF0000, 0xFF000000 ); if ( surface == nullptr ) { ERROR_LOG( "Failed to create a surface of " << icon.width() << " x " << icon.height() << " size for cursor. The error: " << SDL_GetError() ) return nullptr; } const uint32_t width = icon.width(); const uint32_t height = icon.height(); uint32_t * out = static_cast( surface->pixels ); const uint32_t * outEnd = out + width * height; const uint8_t * in = icon.image(); const uint8_t * transform = icon.transform(); if ( surface->format->Amask > 0 ) { for ( ; out != outEnd; ++out, ++in, ++transform ) { if ( *transform == 0 ) { const uint8_t * value = currentPalette + *in * 3; *out = SDL_MapRGBA( surface->format, *value, *( value + 1 ), *( value + 2 ), 255 ); } } } else { for ( ; out != outEnd; ++out, ++in, ++transform ) { if ( *transform == 0 ) { const uint8_t * value = currentPalette + *in * 3; *out = SDL_MapRGB( surface->format, *value, *( value + 1 ), *( value + 2 ) ); } else { *out = SDL_MapRGB( surface->format, 0, 0, 0 ); } } } return surface; } }; #endif } namespace { class RenderCursor final : public fheroes2::Cursor { public: RenderCursor( const RenderCursor & ) = delete; ~RenderCursor() override { clear(); } RenderCursor & operator=( const RenderCursor & ) = delete; void show( const bool enable ) override { fheroes2::Cursor::show( enable ); if ( !_emulation ) { SDL_ShowCursor( _show ? SDL_ENABLE : SDL_DISABLE ); } } bool isVisible() const override { if ( _emulation ) return fheroes2::Cursor::isVisible(); else return fheroes2::Cursor::isVisible() && ( SDL_ShowCursor( SDL_QUERY ) == SDL_ENABLE ); } void update( const fheroes2::Image & image, int32_t offsetX, int32_t offsetY ) override { if ( image.empty() || image.singleLayer() ) { // What are you trying to do? Set an invisible cursor? Use hide() method! assert( 0 ); return; } if ( _emulation ) { fheroes2::Cursor::update( image, offsetX, offsetY ); return; } SDL_Surface * surface = SDL_CreateRGBSurface( 0, image.width(), image.height(), 32, 0xFF, 0xFF00, 0xFF0000, 0xFF000000 ); if ( surface == nullptr ) { ERROR_LOG( "Failed to create a surface of " << image.width() << " x " << image.height() << " size for cursor. The error: " << SDL_GetError() ) return; } const uint32_t width = image.width(); const uint32_t height = image.height(); uint32_t * out = static_cast( surface->pixels ); const uint32_t * outEnd = out + width * height; const uint8_t * in = image.image(); const uint8_t * transform = image.transform(); if ( surface->format->Amask > 0 ) { for ( ; out != outEnd; ++out, ++in, ++transform ) { if ( *transform == 0 ) { const uint8_t * value = currentPalette + *in * 3; *out = SDL_MapRGBA( surface->format, *value, *( value + 1 ), *( value + 2 ), 255 ); } else if ( *transform > 1 ) { // SDL2 uses RGBA image on OS level separately from frame rendering. // Here we are trying to simulate cursor's shadow as close as possible to the original game. *out = SDL_MapRGBA( surface->format, 0, 0, 0, 64 ); } } } else { for ( ; out != outEnd; ++out, ++in, ++transform ) { if ( *transform == 0 ) { const uint8_t * value = currentPalette + *in * 3; *out = SDL_MapRGB( surface->format, *value, *( value + 1 ), *( value + 2 ) ); } else { *out = SDL_MapRGB( surface->format, 0, 0, 0 ); } } } SDL_Cursor * tempCursor = SDL_CreateColorCursor( surface, offsetX, offsetY ); if ( tempCursor == nullptr ) { ERROR_LOG( "Failed to create a cursor. The error description: " << SDL_GetError() ) } else { SDL_SetCursor( tempCursor ); } const int returnCode = SDL_ShowCursor( _show ? SDL_ENABLE : SDL_DISABLE ); if ( returnCode < 0 ) { ERROR_LOG( "Failed to set cursor state. The error value: " << returnCode << ", description: " << SDL_GetError() ) } SDL_FreeSurface( surface ); if ( tempCursor != nullptr ) { clear(); std::swap( _cursor, tempCursor ); } } void enableSoftwareEmulation( const bool enable ) override { if ( enable == _emulation ) return; if ( enable ) { clear(); const int returnCode = SDL_ShowCursor( SDL_DISABLE ); if ( returnCode < 0 ) { ERROR_LOG( "Failed to disable cursor. The error value: " << returnCode << ", description: " << SDL_GetError() ) } _emulation = true; } else { const int returnCode = SDL_ShowCursor( _show ? SDL_ENABLE : SDL_DISABLE ); if ( returnCode < 0 ) { ERROR_LOG( "Failed to set cursor state. The error value: " << returnCode << ", description: " << SDL_GetError() ) } _emulation = false; } if ( _cursorUpdater != nullptr ) _cursorUpdater(); } static RenderCursor * create() { return new RenderCursor; } private: SDL_Cursor * _cursor{ nullptr }; RenderCursor() { _emulation = false; const int returnCode = SDL_ShowCursor( _show ? SDL_ENABLE : SDL_DISABLE ); if ( returnCode < 0 ) { ERROR_LOG( "Failed to set cursor state. The error value: " << returnCode << ", description: " << SDL_GetError() ) } } void clear() { if ( _cursor != nullptr ) { SDL_FreeCursor( _cursor ); _cursor = nullptr; } } }; } namespace { #if defined( TARGET_PS_VITA ) class RenderEngine final : public fheroes2::BaseRenderEngine { public: RenderEngine( const RenderEngine & ) = delete; ~RenderEngine() override { clear(); } RenderEngine & operator=( const RenderEngine & ) = delete; void toggleFullScreen() override { BaseRenderEngine::toggleFullScreen(); const fheroes2::Display & display = fheroes2::Display::instance(); _calculateScreenScaling( display.width(), display.height(), isFullScreen() ); } static RenderEngine * create() { return new RenderEngine; } fheroes2::Rect getActiveWindowROI() const override { return _destRect; } fheroes2::Size getCurrentScreenResolution() const override { return { VITA_FULLSCREEN_WIDTH, VITA_FULLSCREEN_HEIGHT }; } std::vector getAvailableResolutions() const override { static const std::vector filteredResolutions = []() { std::set resolutionSet; resolutionSet.emplace( fheroes2::Display::DEFAULT_WIDTH, fheroes2::Display::DEFAULT_HEIGHT ); resolutionSet.emplace( VITA_ASPECT_CORRECTED_WIDTH, fheroes2::Display::DEFAULT_HEIGHT ); resolutionSet.emplace( VITA_FULLSCREEN_WIDTH, VITA_FULLSCREEN_HEIGHT ); resolutionSet = FilterResolutions( resolutionSet ); return std::vector{ resolutionSet.rbegin(), resolutionSet.rend() }; }(); return filteredResolutions; } protected: RenderEngine() : _window( nullptr ) , _surface( nullptr ) , _texBuffer( nullptr ) , _palettedTexturePointer( nullptr ) { // Do nothing. } enum : int32_t { VITA_FULLSCREEN_WIDTH = 960, VITA_FULLSCREEN_HEIGHT = 544, VITA_ASPECT_CORRECTED_WIDTH = 848 }; void clear() override { if ( _window != nullptr ) { SDL_DestroyWindow( _window ); _window = nullptr; } if ( _surface != nullptr ) { SDL_FreeSurface( _surface ); _surface = nullptr; } vita2d_fini(); if ( _texBuffer != nullptr ) { vita2d_free_texture( _texBuffer ); _texBuffer = nullptr; } } bool allocate( fheroes2::ResolutionInfo & resolutionInfo, bool isFullScreen ) override { clear(); const std::vector resolutions = getAvailableResolutions(); assert( !resolutions.empty() ); if ( !resolutions.empty() ) { resolutionInfo = GetNearestResolution( resolutionInfo, resolutions ); } vita2d_init(); _window = SDL_CreateWindow( "", 0, 0, resolutionInfo.gameWidth, resolutionInfo.gameHeight, 0 ); if ( _window == nullptr ) { clear(); return false; } _surface = SDL_CreateRGBSurface( 0, 1, 1, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 ); if ( _surface == nullptr || _surface->w <= 0 || _surface->h <= 0 ) { clear(); return false; } vita2d_texture_set_alloc_memblock_type( SCE_KERNEL_MEMBLOCK_TYPE_USER_CDRAM_RW ); _texBuffer = vita2d_create_empty_texture_format( resolutionInfo.gameWidth, resolutionInfo.gameHeight, SCE_GXM_TEXTURE_FORMAT_P8_ABGR ); _palettedTexturePointer = static_cast( vita2d_texture_get_datap( _texBuffer ) ); memset( _palettedTexturePointer, 0, resolutionInfo.gameWidth * resolutionInfo.gameHeight * sizeof( uint8_t ) ); _createPalette(); _calculateScreenScaling( resolutionInfo.gameWidth, resolutionInfo.gameHeight, isFullScreen ); return true; } void render( const fheroes2::Display & display, const fheroes2::Rect & roi ) override { (void)roi; if ( _texBuffer == nullptr ) return; const int32_t width = display.width(); const int32_t height = display.height(); SDL_memcpy( _palettedTexturePointer, display.image(), width * height * sizeof( uint8_t ) ); vita2d_start_drawing(); vita2d_draw_rectangle( 0, 0, VITA_FULLSCREEN_WIDTH, VITA_FULLSCREEN_HEIGHT, 0xff000000 ); vita2d_draw_texture_scale( _texBuffer, _destRect.x, _destRect.y, static_cast( _destRect.width ) / width, static_cast( _destRect.height ) / height ); vita2d_end_drawing(); vita2d_swap_buffers(); } void updatePalette( const std::vector & colorIds ) override { if ( _surface == nullptr || colorIds.size() != 256 || _texBuffer == nullptr ) return; uint32_t palette32Bit[256u]; for ( size_t i = 0; i < 256u; ++i ) { const uint8_t * value = currentPalette + colorIds[i] * 3; palette32Bit[i] = SDL_MapRGBA( _surface->format, *value, *( value + 1 ), *( value + 2 ), 255 ); } memcpy( vita2d_texture_get_palette( _texBuffer ), palette32Bit, sizeof( uint32_t ) * 256 ); } bool isMouseCursorActive() const override { return true; } private: SDL_Window * _window; SDL_Surface * _surface; vita2d_texture * _texBuffer; uint8_t * _palettedTexturePointer; fheroes2::Rect _destRect; void _createPalette() { updatePalette( StandardPaletteIndexes() ); } void _calculateScreenScaling( const int32_t width_, const int32_t height_, const bool isFullScreen ) { _destRect.x = 0; _destRect.y = 0; _destRect.width = width_; _destRect.height = height_; if ( width_ == VITA_FULLSCREEN_WIDTH && height_ == VITA_FULLSCREEN_HEIGHT ) { // Nothing to do more. return; } if ( !isFullScreen ) { // Center game area. _destRect.x = ( VITA_FULLSCREEN_WIDTH - width_ ) / 2; _destRect.y = ( VITA_FULLSCREEN_HEIGHT - height_ ) / 2; return; } const SceGxmTextureFilter textureFilter = isNearestScaling() ? SCE_GXM_TEXTURE_FILTER_POINT : SCE_GXM_TEXTURE_FILTER_LINEAR; vita2d_texture_set_filters( _texBuffer, textureFilter, textureFilter ); if ( ( static_cast( VITA_FULLSCREEN_WIDTH ) / VITA_FULLSCREEN_HEIGHT ) >= ( static_cast( width_ ) / height_ ) ) { const float scale = static_cast( VITA_FULLSCREEN_HEIGHT ) / height_; _destRect.width = static_cast( static_cast( width_ ) * scale ); _destRect.height = VITA_FULLSCREEN_HEIGHT; _destRect.x = ( VITA_FULLSCREEN_WIDTH - _destRect.width ) / 2; } else { const float scale = static_cast( VITA_FULLSCREEN_WIDTH ) / width_; _destRect.width = VITA_FULLSCREEN_WIDTH; _destRect.height = static_cast( static_cast( height_ ) * scale ); _destRect.y = ( VITA_FULLSCREEN_HEIGHT - _destRect.height ) / 2; } } }; #else class RenderEngine final : public fheroes2::BaseRenderEngine, public BaseSDLRenderer { public: RenderEngine( const RenderEngine & ) = delete; ~RenderEngine() override { clear(); } RenderEngine & operator=( const RenderEngine & ) = delete; void toggleFullScreen() override { if ( _window == nullptr ) { BaseRenderEngine::toggleFullScreen(); return; } uint32_t flags = SDL_GetWindowFlags( _window ); if ( ( flags & SDL_WINDOW_FULLSCREEN ) == SDL_WINDOW_FULLSCREEN || ( flags & SDL_WINDOW_FULLSCREEN_DESKTOP ) == SDL_WINDOW_FULLSCREEN_DESKTOP ) { flags &= ~SDL_WINDOW_FULLSCREEN_DESKTOP; flags &= ~SDL_WINDOW_FULLSCREEN; } else { #if defined( _WIN32 ) if ( fheroes2::cursor().isSoftwareEmulation() ) { flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; } else { flags |= SDL_WINDOW_FULLSCREEN; } #else flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; #endif SDL_GetWindowSize( _window, &_windowedSize.width, &_windowedSize.height ); const fheroes2::Display & display = fheroes2::Display::instance(); if ( display.width() != 0 && display.height() != 0 ) { assert( display.screenSize().width >= display.width() && display.screenSize().height >= display.height() ); SDL_SetWindowSize( _window, display.screenSize().width, display.screenSize().height ); } } const int returnCode = SDL_SetWindowFullscreen( _window, flags ); if ( returnCode < 0 ) { ERROR_LOG( "Failed to set fullscreen mode flags. The error value: " << returnCode << ", description: " << SDL_GetError() ) } _syncFullScreen(); if ( !isFullScreen() && _windowedSize.width != 0 && _windowedSize.height != 0 ) { SDL_SetWindowSize( _window, _windowedSize.width, _windowedSize.height ); } _retrieveWindowInfo(); _toggleMouseCaptureMode(); } bool isFullScreen() const override { if ( _window == nullptr ) return BaseRenderEngine::isFullScreen(); const uint32_t flags = SDL_GetWindowFlags( _window ); return ( flags & SDL_WINDOW_FULLSCREEN ) != 0 || ( flags & SDL_WINDOW_FULLSCREEN_DESKTOP ) != 0; } std::vector getAvailableResolutions() const override { static const std::vector filteredResolutions = []() { std::set resolutionSet; const int displayCount = SDL_GetNumVideoDisplays(); if ( displayCount > 0 ) { const int displayModeCount = SDL_GetNumDisplayModes( 0 ); for ( int i = 0; i < displayModeCount; ++i ) { SDL_DisplayMode videoMode; const int returnCode = SDL_GetDisplayMode( 0, i, &videoMode ); if ( returnCode < 0 ) { ERROR_LOG( "Failed to get display mode. The error value: " << returnCode << ", description: " << SDL_GetError() ) } else { resolutionSet.emplace( videoMode.w, videoMode.h ); } } } #if defined( TARGET_NINTENDO_SWITCH ) // Nintendo Switch supports arbitrary resolutions via the HW scaler // 848x480 is the smallest resolution supported by fheroes2 resolutionSet.emplace( 848, 480 ); #endif resolutionSet = FilterResolutions( resolutionSet ); return std::vector{ resolutionSet.rbegin(), resolutionSet.rend() }; }(); return filteredResolutions; } void setTitle( const std::string & title ) override { if ( _window != nullptr ) SDL_SetWindowTitle( _window, title.c_str() ); } void setIcon( const fheroes2::Image & icon ) override { if ( _window == nullptr ) return; SDL_Surface * surface = generateIconSurface( icon ); if ( surface == nullptr ) return; SDL_SetWindowIcon( _window, surface ); SDL_FreeSurface( surface ); } fheroes2::Rect getActiveWindowROI() const override { return _activeWindowROI; } fheroes2::Size getCurrentScreenResolution() const override { return _currentScreenResolution; } static RenderEngine * create() { return new RenderEngine; } void setVSync( const bool enable ) override { _isVSyncEnabled = enable; if ( _window != nullptr ) { // We do not need to rebuild window but renderer only. if ( _texture != nullptr ) { SDL_DestroyTexture( _texture ); _texture = nullptr; } if ( _renderer != nullptr ) { SDL_DestroyRenderer( _renderer ); _renderer = nullptr; } const fheroes2::Display & display = fheroes2::Display::instance(); _createRenderer( display.width(), display.height() ); } } protected: RenderEngine() : _window( nullptr ) , _surface( nullptr ) , _renderer( nullptr ) , _texture( nullptr ) , _driverIndex( -1 ) , _prevWindowPos( SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED ) , _isVSyncEnabled( false ) { // Do nothing. } void clear() override { if ( _texture != nullptr ) { SDL_DestroyTexture( _texture ); _texture = nullptr; } if ( _renderer != nullptr ) { SDL_DestroyRenderer( _renderer ); _renderer = nullptr; } if ( _window != nullptr ) { // Let's collect needed info about previous setup if ( !isFullScreen() ) { SDL_GetWindowPosition( _window, &_prevWindowPos.x, &_prevWindowPos.y ); } _previousWindowTitle = SDL_GetWindowTitle( _window ); SDL_DestroyWindow( _window ); _window = nullptr; } if ( _surface != nullptr ) { SDL_FreeSurface( _surface ); _surface = nullptr; } _windowedSize = fheroes2::Size(); _driverIndex = -1; } void render( const fheroes2::Display & display, const fheroes2::Rect & roi ) override { if ( _surface == nullptr ) return; if ( _texture == nullptr ) { if ( _renderer != nullptr ) SDL_DestroyRenderer( _renderer ); // SDL_PIXELFORMAT_INDEX8 is not supported by SDL 2 even being available in the list of formats. _renderer = SDL_CreateRenderer( _window, _driverIndex, renderFlags() ); if ( _renderer == nullptr ) { ERROR_LOG( "Failed to create a window renderer. The error: " << SDL_GetError() ) } return; } copyImageToSurface( display, _surface, roi ); const bool fullFrame = ( roi.width == display.width() ) && ( roi.height == display.height() ); if ( fullFrame ) { const int returnCode = SDL_UpdateTexture( _texture, nullptr, _surface->pixels, _surface->pitch ); if ( returnCode < 0 ) { ERROR_LOG( "Failed to update texture. The error value: " << returnCode << ", description: " << SDL_GetError() ) } } else { SDL_Rect area; area.x = roi.x; area.y = roi.y; area.w = roi.width; area.h = roi.height; const int returnCode = SDL_UpdateTexture( _texture, &area, _surface->pixels, _surface->pitch ); if ( returnCode < 0 ) { ERROR_LOG( "Failed to update texture. The error value: " << returnCode << ", description: " << SDL_GetError() ) } } int returnCode = SDL_RenderClear( _renderer ); if ( returnCode < 0 ) { ERROR_LOG( "Failed to clear renderer. The error value: " << returnCode << ", description: " << SDL_GetError() ) return; } returnCode = SDL_RenderCopy( _renderer, _texture, nullptr, nullptr ); if ( returnCode < 0 ) { ERROR_LOG( "Failed to copy render.The error value: " << returnCode << ", description: " << SDL_GetError() ) return; } SDL_RenderPresent( _renderer ); } bool allocate( fheroes2::ResolutionInfo & resolutionInfo, bool isFullScreen ) override { clear(); const std::vector resolutions = getAvailableResolutions(); assert( !resolutions.empty() ); if ( !resolutions.empty() ) { resolutionInfo = GetNearestResolution( resolutionInfo, resolutions ); } #if defined( ANDROID ) // Same as ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE if ( SDL_SetHint( SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight" ) == SDL_FALSE ) { ERROR_LOG( "Failed to set a hint for screen orientation." ) } #endif uint32_t flags = SDL_WINDOW_SHOWN; if ( isFullScreen ) { #if defined( _WIN32 ) if ( fheroes2::cursor().isSoftwareEmulation() ) { flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; } else { flags |= SDL_WINDOW_FULLSCREEN; } #else flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; #endif } flags |= SDL_WINDOW_RESIZABLE; _window = SDL_CreateWindow( _previousWindowTitle.data(), _prevWindowPos.x, _prevWindowPos.y, resolutionInfo.screenWidth, resolutionInfo.screenHeight, flags ); if ( _window == nullptr ) { ERROR_LOG( "Failed to create an application window of " << resolutionInfo.screenWidth << " x " << resolutionInfo.screenHeight << " size. The error: " << SDL_GetError() ) clear(); return false; } _syncFullScreen(); bool isPaletteModeSupported = false; SDL_RendererInfo rendererInfo; _driverIndex = -1; const uint32_t renderingFlags = renderFlags(); const int driverCount = SDL_GetNumRenderDrivers(); for ( int driverId = 0; driverId < driverCount; ++driverId ) { int returnCode = SDL_GetRenderDriverInfo( driverId, &rendererInfo ); if ( returnCode < 0 ) { ERROR_LOG( "Failed to get renderer driver info. The error value: " << returnCode << ", description: " << SDL_GetError() ) continue; } if ( ( renderingFlags & rendererInfo.flags ) != renderingFlags ) { continue; } for ( uint32_t i = 0; i < rendererInfo.num_texture_formats; ++i ) { if ( rendererInfo.texture_formats[i] == SDL_PIXELFORMAT_INDEX8 ) { // Bingo! This is the best driver and format. isPaletteModeSupported = true; _driverIndex = driverId; break; } } if ( isPaletteModeSupported ) { break; } if ( _driverIndex < 0 ) { _driverIndex = driverId; } } _surface = SDL_CreateRGBSurface( 0, resolutionInfo.gameWidth, resolutionInfo.gameHeight, isPaletteModeSupported ? 8 : 32, 0, 0, 0, 0 ); if ( _surface == nullptr ) { ERROR_LOG( "Failed to create a surface of " << resolutionInfo.gameWidth << " x " << resolutionInfo.gameHeight << " size. The error: " << SDL_GetError() ) clear(); return false; } if ( _surface->w <= 0 || _surface->h <= 0 || _surface->w != resolutionInfo.gameWidth || _surface->h != resolutionInfo.gameHeight ) { clear(); return false; } _createPalette(); return _createRenderer( resolutionInfo.gameWidth, resolutionInfo.gameHeight ); } void updatePalette( const std::vector & colorIds ) override { if ( _surface == nullptr || colorIds.size() != 256 ) return; generatePalette( colorIds, _surface ); if ( _surface->format->BitsPerPixel == 8 ) { const int returnCode = SDL_SetPaletteColors( _surface->format->palette, _palette8Bit.data(), 0, 256 ); if ( returnCode < 0 ) { ERROR_LOG( "Failed to set palette color. The error value: " << returnCode << ", description: " << SDL_GetError() ) } } } bool isMouseCursorActive() const override { return ( _window != nullptr ) && ( ( SDL_GetWindowFlags( _window ) & SDL_WINDOW_MOUSE_FOCUS ) == SDL_WINDOW_MOUSE_FOCUS ); } private: SDL_Window * _window; SDL_Surface * _surface; SDL_Renderer * _renderer; SDL_Texture * _texture; int _driverIndex; std::string _previousWindowTitle; fheroes2::Point _prevWindowPos; fheroes2::Size _currentScreenResolution; fheroes2::Rect _activeWindowROI; fheroes2::Size _windowedSize; bool _isVSyncEnabled; uint32_t renderFlags() const { if ( _isVSyncEnabled ) { return ( SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC ); } return SDL_RENDERER_ACCELERATED; } void _createPalette() { if ( _surface == nullptr ) return; updatePalette( StandardPaletteIndexes() ); if ( _surface->format->BitsPerPixel == 8 ) { if ( !SDL_MUSTLOCK( _surface ) ) { // copy the image from display buffer to SDL surface const fheroes2::Display & display = fheroes2::Display::instance(); if ( _surface->w == display.width() && _surface->h == display.height() ) { memcpy( _surface->pixels, display.image(), static_cast( display.width() * display.height() ) ); } // Display class doesn't have support for image pitch so we mustn't link display to surface if width is not divisible by 4. if ( _surface->w % 4 == 0 ) { linkRenderSurface( static_cast( _surface->pixels ) ); } } } } void _retrieveWindowInfo() { const int32_t displayIndex = SDL_GetWindowDisplayIndex( _window ); SDL_DisplayMode displayMode; const int returnCode = SDL_GetCurrentDisplayMode( displayIndex, &displayMode ); if ( returnCode < 0 ) { ERROR_LOG( "Failed to retrieve current display mode. The error value: " << returnCode << ", description: " << SDL_GetError() ) } _currentScreenResolution.width = displayMode.w; _currentScreenResolution.height = displayMode.h; #if defined( TARGET_NINTENDO_SWITCH ) // On a Nintendo Switch the game is always fullscreen _activeWindowROI = { 0, 0, _currentScreenResolution.width, _currentScreenResolution.height }; #else SDL_GetWindowPosition( _window, &_activeWindowROI.x, &_activeWindowROI.y ); SDL_GetWindowSize( _window, &_activeWindowROI.width, &_activeWindowROI.height ); #endif } void _toggleMouseCaptureMode() { // To properly support fullscreen mode on devices with multiple displays or devices with notch, // it is important to lock the mouse in the application window area. if ( isFullScreen() ) { SDL_SetWindowGrab( _window, SDL_TRUE ); } else { SDL_SetWindowGrab( _window, SDL_FALSE ); } } bool _createRenderer( const int32_t width_, const int32_t height_ ) { const uint32_t renderingFlags = renderFlags(); // SDL_PIXELFORMAT_INDEX8 is not supported by SDL 2 even being available in the list of formats. _renderer = SDL_CreateRenderer( _window, _driverIndex, renderingFlags ); if ( _renderer == nullptr ) { ERROR_LOG( "Failed to create a window renderer of " << width_ << " x " << height_ << " size. The error: " << SDL_GetError() ) clear(); return false; } int returnCode = SDL_SetRenderDrawColor( _renderer, 0, 0, 0, SDL_ALPHA_OPAQUE ); if ( returnCode < 0 ) { ERROR_LOG( "Failed to set default color for renderer. The error value: " << returnCode << ", description: " << SDL_GetError() ) } returnCode = SDL_SetRenderTarget( _renderer, nullptr ); if ( returnCode < 0 ) { ERROR_LOG( "Failed to set render target to window. The error value: " << returnCode << ", description: " << SDL_GetError() ) } if ( SDL_SetHint( SDL_HINT_RENDER_SCALE_QUALITY, ( isNearestScaling() ? "nearest" : "linear" ) ) == SDL_FALSE ) { ERROR_LOG( "Failed to set a linear scale hint for rendering." ) } // Setting this hint prevents the window to regain focus after losing it in fullscreen mode. // It also fixes issues when SDL_UpdateTexture() calls fail because of refocusing. if ( SDL_SetHint( SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0" ) == SDL_FALSE ) { ERROR_LOG( "Failed to set a linear scale hint for rendering." ) } returnCode = SDL_RenderSetLogicalSize( _renderer, width_, height_ ); if ( returnCode < 0 ) { ERROR_LOG( "Failed to create logical size of " << width_ << " x " << height_ << " size. The error value: " << returnCode << ", description: " << SDL_GetError() ) clear(); return false; } _texture = SDL_CreateTextureFromSurface( _renderer, _surface ); if ( _texture == nullptr ) { ERROR_LOG( "Failed to create a texture from a surface of " << width_ << " x " << height_ << " size. The error: " << SDL_GetError() ) clear(); return false; } _retrieveWindowInfo(); _toggleMouseCaptureMode(); return true; } void _syncFullScreen() { if ( isFullScreen() != BaseRenderEngine::isFullScreen() ) { BaseRenderEngine::toggleFullScreen(); assert( isFullScreen() == BaseRenderEngine::isFullScreen() ); } } }; #endif } namespace fheroes2 { void BaseRenderEngine::linkRenderSurface( uint8_t * surface ) const { Display::instance().linkRenderSurface( surface ); } Display::Display() : _engine( RenderEngine::create() ) , _cursor( RenderCursor::create() ) , _preprocessing( nullptr ) , _postprocessing( nullptr ) , _renderSurface( nullptr ) { _disableTransformLayer(); } void Display::resize( int32_t width_, int32_t height_ ) { assert( width_ == width() && height_ == height() ); #ifdef NDEBUG (void)width_; (void)height_; #endif } void Display::setResolution( ResolutionInfo info ) { if ( width() > 0 && height() > 0 && info.gameWidth == width() && info.gameHeight == height() && info.screenWidth == _screenSize.width && info.screenHeight == _screenSize.height ) // nothing to resize return; const bool isFullScreen = _engine->isFullScreen(); // deallocate engine resources _engine->clear(); _prevRoi = {}; // allocate engine resources if ( !_engine->allocate( info, isFullScreen ) ) { clear(); } Image::resize( info.gameWidth, info.gameHeight ); Image::reset(); _screenSize = { info.screenWidth, info.screenHeight }; } Display & Display::instance() { static Display display; return display; } void Display::render( const Rect & roi ) { Rect temp( roi ); if ( !getActiveArea( temp, width(), height() ) ) return; getActiveArea( _prevRoi, width(), height() ); if ( _cursor->isVisible() && _cursor->isSoftwareEmulation() && !_cursor->_image.empty() ) { const Sprite & cursorImage = _cursor->_image; const Sprite backup = Crop( *this, cursorImage.x(), cursorImage.y(), cursorImage.width(), cursorImage.height() ); Blit( cursorImage, *this, cursorImage.x(), cursorImage.y() ); if ( !backup.empty() ) { // ROI must include cursor's area as well, otherwise cursor won't be rendered. Rect cursorROI( cursorImage.x(), cursorImage.y(), cursorImage.width(), cursorImage.height() ); if ( getActiveArea( cursorROI, width(), height() ) ) { temp = getBoundaryRect( temp, cursorROI ); } } // Previous position of cursor must be updated as well to avoid ghost effect. _renderFrame( getBoundaryRect( temp, _prevRoi ) ); if ( _postprocessing ) { _postprocessing(); } Copy( backup, 0, 0, *this, backup.x(), backup.y(), backup.width(), backup.height() ); } else { _renderFrame( getBoundaryRect( temp, _prevRoi ) ); if ( _postprocessing ) { _postprocessing(); } } _prevRoi = temp; } void Display::updateNextRenderRoi( const Rect & roi ) { _prevRoi = getBoundaryRect( _prevRoi, roi ); } void Display::_renderFrame( const Rect & roi ) const { bool updateImage = true; if ( _preprocessing ) { std::vector palette; if ( _preprocessing( palette ) ) { _engine->updatePalette( palette ); // when we change a palette for 8-bit image we unwillingly call render so we don't need to re-render the same frame again updateImage = ( _renderSurface == nullptr ); if ( updateImage ) { // Pre-processing step is applied to the whole image so we forcefully render the full frame. _engine->render( *this, { 0, 0, width(), height() } ); return; } } } if ( updateImage ) { _engine->render( *this, roi ); } } uint8_t * Display::image() { return _renderSurface != nullptr ? _renderSurface : Image::image(); } const uint8_t * Display::image() const { return _renderSurface != nullptr ? _renderSurface : Image::image(); } void Display::release() { _engine->clear(); _cursor.reset(); clear(); _prevRoi = {}; } void Display::changePalette( const uint8_t * palette, const bool forceDefaultPaletteUpdate ) const { if ( currentPalette == palette || ( palette == nullptr && currentPalette == PALPalette() && !forceDefaultPaletteUpdate ) ) return; currentPalette = ( palette == nullptr ) ? PALPalette( forceDefaultPaletteUpdate ) : palette; _engine->updatePalette( StandardPaletteIndexes() ); } bool Cursor::isFocusActive() const { return engine().isMouseCursorActive(); } BaseRenderEngine & engine() { return *( Display::instance()._engine ); } Cursor & cursor() { return *( Display::instance()._cursor ); } } fheroes2-1.0.12+dfsg/src/engine/screen.h000066400000000000000000000222501456075706000177260ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include #include #include #include #include "image.h" #include "math_base.h" namespace fheroes2 { class Cursor; class Display; struct ResolutionInfo { ResolutionInfo() = default; ResolutionInfo( const int32_t gameWidth_, const int32_t gameHeight_ ) : gameWidth( gameWidth_ ) , gameHeight( gameHeight_ ) , screenWidth( gameWidth_ ) , screenHeight( gameHeight_ ) { // Do nothing. } ResolutionInfo( const int32_t gameWidth_, const int32_t gameHeight_, const int32_t screenWidth_, const int32_t screenHeight_ ) : gameWidth( gameWidth_ ) , gameHeight( gameHeight_ ) , screenWidth( screenWidth_ ) , screenHeight( screenHeight_ ) { // Do nothing. } bool operator==( const ResolutionInfo & info ) const { return gameWidth == info.gameWidth && gameHeight == info.gameHeight && screenWidth == info.screenWidth && screenHeight == info.screenHeight; } bool operator!=( const ResolutionInfo & info ) const { return !operator==( info ); } bool operator<( const ResolutionInfo & info ) const { return std::tie( gameWidth, gameHeight, screenWidth, screenHeight ) < std::tie( info.gameWidth, info.gameHeight, info.screenWidth, info.screenHeight ); } int32_t gameWidth{ 0 }; int32_t gameHeight{ 0 }; int32_t screenWidth{ 0 }; int32_t screenHeight{ 0 }; }; class BaseRenderEngine { public: friend class Cursor; friend class Display; virtual ~BaseRenderEngine() = default; virtual void toggleFullScreen() { _isFullScreen = !_isFullScreen; } virtual bool isFullScreen() const { return _isFullScreen; } virtual std::vector getAvailableResolutions() const { return {}; } virtual void setTitle( const std::string & ) { // Do nothing. } virtual void setIcon( const Image & ) { // Do nothing. } virtual Rect getActiveWindowROI() const { return {}; } virtual Size getCurrentScreenResolution() const { return {}; } virtual void setVSync( const bool ) { // Do nothing. } void setNearestScaling( const bool enable ) { _nearestScaling = enable; } bool isNearestScaling() const { return _nearestScaling; } protected: BaseRenderEngine() : _isFullScreen( false ) , _nearestScaling( false ) { // Do nothing. } virtual void clear() { // Do nothing. } virtual void render( const Display &, const Rect & ) { // Do nothing. } virtual bool allocate( ResolutionInfo & /*unused*/, bool /*unused*/ ) { return false; } virtual bool isMouseCursorActive() const { return false; } // To support color cycling we need to update palette. virtual void updatePalette( const std::vector & ) { // Do nothing. } void linkRenderSurface( uint8_t * surface ) const; // declaration of this method is in source file private: bool _isFullScreen; bool _nearestScaling; }; class Display : public Image { public: friend class BaseRenderEngine; enum : int32_t { DEFAULT_WIDTH = 640, DEFAULT_HEIGHT = 480 }; static Display & instance(); ~Display() override = default; // Render a full frame on screen. void render() { render( { 0, 0, width(), height() } ); } void render( const Rect & roi ); // render a part of image on screen. Prefer this method over full image if you don't draw full screen. // Update the area which will be rendered on the next render() call. void updateNextRenderRoi( const Rect & roi ); // Do not call this method. It serves as a patch over the basic class. void resize( int32_t width_, int32_t height_ ) override; void setResolution( ResolutionInfo info ); bool isDefaultSize() const { return width() == DEFAULT_WIDTH && height() == DEFAULT_HEIGHT; } // this function must return true if new palette has been generated using PreRenderProcessing = std::function & )>; using PostRenderProcessing = std::function; void subscribe( const PreRenderProcessing & preprocessing, const PostRenderProcessing & postprocessing ) { _preprocessing = preprocessing; _postprocessing = postprocessing; } // For 8-bit mode we return a pointer to direct surface which we draw on screen uint8_t * image() override; const uint8_t * image() const override; void release(); // to release all allocated resources. Should be used at the end of the application // Change the whole color representation on the screen. Make sure that palette exists all the time!!! // nullptr input parameter is used to reset palette to default one. void changePalette( const uint8_t * palette = nullptr, const bool forceDefaultPaletteUpdate = false ) const; Size screenSize() const { return _screenSize; } friend BaseRenderEngine & engine(); friend Cursor & cursor(); private: std::unique_ptr _engine; std::unique_ptr _cursor; PreRenderProcessing _preprocessing; PostRenderProcessing _postprocessing; uint8_t * _renderSurface; // Previous area drawn on the screen. Rect _prevRoi; Size _screenSize; // Only for cases of direct drawing on rendered 8-bit image. void linkRenderSurface( uint8_t * surface ) { _renderSurface = surface; } Display(); void _renderFrame( const Rect & roi ) const; // prepare and render a frame }; class Cursor { public: friend Display; virtual ~Cursor() = default; virtual void show( const bool enable ) { _show = enable; } virtual bool isVisible() const { return _show; } bool isFocusActive() const; virtual void update( const Image & image, int32_t offsetX, int32_t offsetY ) { _image = Sprite( image, offsetX, offsetY ); } void setPosition( const int32_t x, const int32_t y ) { _image.setPosition( x, y ); } // Default implementation of Cursor uses software emulation. virtual void enableSoftwareEmulation( const bool ) { // Do nothing. } bool isSoftwareEmulation() const { return _emulation; } void registerUpdater( void ( *cursorUpdater )() ) { _cursorUpdater = cursorUpdater; } protected: Sprite _image; bool _emulation; bool _show; void ( *_cursorUpdater )(); Cursor() : _emulation( true ) , _show( false ) , _cursorUpdater( nullptr ) {} }; BaseRenderEngine & engine(); Cursor & cursor(); } fheroes2-1.0.12+dfsg/src/engine/serialize.cpp000066400000000000000000000330341456075706000207730ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2012 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "serialize.h" #include #include #include #include #include #include "endian_h2.h" #include "logging.h" namespace { const size_t minBufferCapacity = 1024; } void StreamBase::setconstbuf( bool f ) { if ( f ) { _flags |= CONST_BUF; } else { _flags &= ~CONST_BUF; } } void StreamBase::setbigendian( bool f ) { if ( f ) { _flags |= BIGENDIAN; } else { _flags &= ~BIGENDIAN; } } void StreamBase::setfail( bool f ) { if ( f ) { _flags |= FAILURE; } else { _flags &= ~FAILURE; } } uint16_t StreamBase::get16() { return bigendian() ? getBE16() : getLE16(); } uint32_t StreamBase::get32() { return bigendian() ? getBE32() : getLE32(); } StreamBase & StreamBase::operator>>( bool & v ) { v = ( get8() != 0 ); return *this; } StreamBase & StreamBase::operator>>( char & v ) { v = get8(); return *this; } StreamBase & StreamBase::operator>>( uint8_t & v ) { v = get8(); return *this; } StreamBase & StreamBase::operator>>( uint16_t & v ) { v = get16(); return *this; } StreamBase & StreamBase::operator>>( int16_t & v ) { v = get16(); return *this; } StreamBase & StreamBase::operator>>( uint32_t & v ) { v = get32(); return *this; } StreamBase & StreamBase::operator>>( int32_t & v ) { v = get32(); return *this; } StreamBase & StreamBase::operator>>( std::string & v ) { uint32_t size = get32(); v.resize( size ); for ( std::string::iterator it = v.begin(); it != v.end(); ++it ) *it = get8(); return *this; } StreamBase & StreamBase::operator>>( fheroes2::Point & point_ ) { return *this >> point_.x >> point_.y; } void StreamBase::put16( uint16_t v ) { bigendian() ? putBE16( v ) : putLE16( v ); } void StreamBase::put32( uint32_t v ) { bigendian() ? putBE32( v ) : putLE32( v ); } StreamBase & StreamBase::operator<<( const bool v ) { put8( v ); return *this; } StreamBase & StreamBase::operator<<( const char v ) { put8( v ); return *this; } StreamBase & StreamBase::operator<<( const uint8_t v ) { put8( v ); return *this; } StreamBase & StreamBase::operator<<( const uint16_t v ) { put16( v ); return *this; } StreamBase & StreamBase::operator<<( const int16_t v ) { put16( v ); return *this; } StreamBase & StreamBase::operator<<( const int32_t v ) { put32( v ); return *this; } StreamBase & StreamBase::operator<<( const uint32_t v ) { put32( v ); return *this; } StreamBase & StreamBase::operator<<( const std::string & v ) { put32( static_cast( v.size() ) ); // A string is a container of bytes so it doesn't matter which endianess is being used. putRaw( v.data(), v.size() ); return *this; } StreamBase & StreamBase::operator<<( const fheroes2::Point & point_ ) { return *this << point_.x << point_.y; } StreamBuf::StreamBuf( const size_t sz ) { if ( sz ) { reallocbuf( sz ); } setbigendian( IS_BIGENDIAN ); } StreamBuf::~StreamBuf() { if ( itbeg == nullptr || isconstbuf() ) { return; } delete[] itbeg; } StreamBuf::StreamBuf( StreamBuf && stream ) noexcept : StreamBase( std::move( stream ) ) { std::swap( itbeg, stream.itbeg ); std::swap( itget, stream.itget ); std::swap( itput, stream.itput ); std::swap( itend, stream.itend ); } StreamBuf::StreamBuf( const std::vector & buf ) { itbeg = const_cast( buf.data() ); itend = itbeg + buf.size(); itget = itbeg; itput = itend; setconstbuf( true ); setbigendian( IS_BIGENDIAN ); } StreamBuf & StreamBuf::operator=( StreamBuf && stream ) noexcept { if ( this == &stream ) { return *this; } StreamBase::operator=( std::move( stream ) ); std::swap( itbeg, stream.itbeg ); std::swap( itget, stream.itget ); std::swap( itput, stream.itput ); std::swap( itend, stream.itend ); return *this; } void StreamBuf::reset() { itput = itbeg; itget = itbeg; } size_t StreamBuf::tellg() { return itget - itbeg; } size_t StreamBuf::tellp() { return itput - itbeg; } size_t StreamBuf::sizeg() { return itput - itget; } size_t StreamBuf::sizep() { return itend - itput; } void StreamBuf::reallocbuf( size_t size ) { // Instances created from read-only memory blocks are read-only and should never be reallocated assert( !isconstbuf() ); if ( !itbeg ) { if ( size < minBufferCapacity ) { size = minBufferCapacity; } itbeg = new uint8_t[size]{}; itend = itbeg + size; reset(); } else if ( sizep() < size ) { if ( size < minBufferCapacity ) { size = minBufferCapacity; } uint8_t * ptr = new uint8_t[size]{}; std::copy( itbeg, itput, ptr ); itput = ptr + tellp(); itget = ptr + tellg(); delete[] itbeg; itbeg = ptr; itend = itbeg + size; } } void StreamBuf::put8( const uint8_t v ) { if ( sizep() < 1 ) { reallocbuf( capacity() + capacity() / 2 ); } if ( sizep() < 1 ) { assert( 0 ); return; } *itput = v; ++itput; } uint8_t StreamBuf::get8() { if ( sizeg() ) return *itget++; else return 0u; } uint16_t StreamBuf::getBE16() { uint16_t result = ( static_cast( get8() ) << 8 ); result |= get8(); return result; } uint16_t StreamBuf::getLE16() { uint16_t result = get8(); result |= ( static_cast( get8() ) << 8 ); return result; } uint32_t StreamBuf::getBE32() { uint32_t result = ( static_cast( get8() ) << 24 ); result |= ( static_cast( get8() ) << 16 ); result |= ( static_cast( get8() ) << 8 ); result |= get8(); return result; } uint32_t StreamBuf::getLE32() { uint32_t result = get8(); result |= ( static_cast( get8() ) << 8 ); result |= ( static_cast( get8() ) << 16 ); result |= ( static_cast( get8() ) << 24 ); return result; } void StreamBuf::putBE16( uint16_t v ) { put8( v >> 8 ); put8( v & 0xFF ); } void StreamBuf::putLE16( uint16_t v ) { put8( v & 0xFF ); put8( v >> 8 ); } void StreamBuf::putBE32( uint32_t v ) { put8( v >> 24 ); put8( ( v >> 16 ) & 0xFF ); put8( ( v >> 8 ) & 0xFF ); put8( v & 0xFF ); } void StreamBuf::putLE32( uint32_t v ) { put8( v & 0xFF ); put8( ( v >> 8 ) & 0xFF ); put8( ( v >> 16 ) & 0xFF ); put8( v >> 24 ); } std::vector StreamBuf::getRaw( size_t sz ) { const size_t remainSize = sizeg(); const size_t dataSize = sz > 0 ? sz : remainSize; std::vector v( dataSize, 0 ); const size_t copySize = dataSize < remainSize ? dataSize : remainSize; memcpy( v.data(), itget, copySize ); itget += copySize; return v; } void StreamBuf::putRaw( const char * ptr, size_t sz ) { if ( sz == 0 ) { return; } if ( sizep() < sz ) { if ( sz < capacity() / 2 ) { reallocbuf( capacity() + capacity() / 2 ); } else { reallocbuf( capacity() + sz ); } } if ( sizep() < sz ) { assert( 0 ); return; } memcpy( itput, ptr, sz ); itput = itput + sz; } std::string StreamBuf::toString( const size_t size /* = 0 */ ) { const size_t length = ( size > 0 && size < sizeg() ) ? size : sizeg(); uint8_t * it1 = itget; itget += length; uint8_t * it2 = std::find( it1, itget, 0 ); return { it1, it2 }; } void StreamBuf::skip( size_t sz ) { itget += sz <= sizeg() ? sz : sizeg(); } bool StreamFile::open( const std::string & fn, const std::string & mode ) { _file.reset( std::fopen( fn.c_str(), mode.c_str() ) ); if ( !_file ) { ERROR_LOG( "Error opening file " << fn ) } setfail( !_file ); return !fail(); } void StreamFile::close() { _file.reset(); } size_t StreamFile::size() { if ( !_file ) { return 0; } const long pos = std::ftell( _file.get() ); if ( pos < 0 ) { setfail( true ); return 0; } if ( std::fseek( _file.get(), 0, SEEK_END ) != 0 ) { setfail( true ); return 0; } const long len = std::ftell( _file.get() ); if ( len < 0 ) { setfail( true ); return 0; } if ( std::fseek( _file.get(), pos, SEEK_SET ) != 0 ) { setfail( true ); return 0; } return static_cast( len ); } size_t StreamFile::tell() { return tellg(); } void StreamFile::seek( size_t pos ) { if ( !_file ) { return; } if ( std::fseek( _file.get(), static_cast( pos ), SEEK_SET ) != 0 ) { setfail( true ); } } size_t StreamFile::sizeg() { if ( !_file ) { return 0; } const long pos = std::ftell( _file.get() ); if ( pos < 0 ) { setfail( true ); return 0; } if ( std::fseek( _file.get(), 0, SEEK_END ) != 0 ) { setfail( true ); return 0; } const long len = std::ftell( _file.get() ); if ( len < 0 ) { setfail( true ); return 0; } if ( std::fseek( _file.get(), pos, SEEK_SET ) != 0 ) { setfail( true ); return 0; } // Something weird has happened if ( len < pos ) { setfail( true ); return 0; } return static_cast( len - pos ); } size_t StreamFile::tellg() { if ( !_file ) { return 0; } const long pos = std::ftell( _file.get() ); if ( pos < 0 ) { setfail( true ); return 0; } return static_cast( pos ); } size_t StreamFile::sizep() { return sizeg(); } size_t StreamFile::tellp() { return tellg(); } void StreamFile::skip( size_t pos ) { if ( !_file ) { return; } if ( std::fseek( _file.get(), static_cast( pos ), SEEK_CUR ) != 0 ) { setfail( true ); } } uint8_t StreamFile::get8() { return getUint(); } void StreamFile::put8( const uint8_t v ) { putUint( v ); } uint16_t StreamFile::getBE16() { return be16toh( getUint() ); } uint16_t StreamFile::getLE16() { return le16toh( getUint() ); } uint32_t StreamFile::getBE32() { return be32toh( getUint() ); } uint32_t StreamFile::getLE32() { return le32toh( getUint() ); } void StreamFile::putBE16( uint16_t val ) { putUint( htobe16( val ) ); } void StreamFile::putLE16( uint16_t val ) { putUint( htole16( val ) ); } void StreamFile::putBE32( uint32_t val ) { putUint( htobe32( val ) ); } void StreamFile::putLE32( uint32_t val ) { putUint( htole32( val ) ); } std::vector StreamFile::getRaw( const size_t size ) { const size_t chunkSize = size > 0 ? size : sizeg(); if ( chunkSize == 0 || !_file ) { return {}; } std::vector v( chunkSize ); if ( std::fread( v.data(), chunkSize, 1, _file.get() ) != 1 ) { setfail( true ); return {}; } return v; } void StreamFile::putRaw( const char * ptr, size_t sz ) { if ( !_file ) { return; } if ( std::fwrite( ptr, sz, 1, _file.get() ) != 1 ) { setfail( true ); } } StreamBuf StreamFile::toStreamBuf( const size_t size ) { const size_t chunkSize = size > 0 ? size : sizeg(); if ( chunkSize == 0 || !_file ) { return StreamBuf{}; } StreamBuf buffer( chunkSize ); if ( std::fread( buffer.data(), chunkSize, 1, _file.get() ) != 1 ) { setfail( true ); return StreamBuf{}; } buffer.advance( chunkSize ); return buffer; } std::string StreamFile::toString( const size_t size /* = 0 */ ) { const std::vector buf = getRaw( size ); const auto itend = std::find( buf.begin(), buf.end(), 0 ); return { buf.begin(), itend }; } fheroes2-1.0.12+dfsg/src/engine/serialize.h000066400000000000000000000300051456075706000204330ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2012 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2SERIALIZE_H #define H2SERIALIZE_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "endian_h2.h" #include "math_base.h" class StreamBase { public: StreamBase() = default; StreamBase( const StreamBase & ) = delete; StreamBase( StreamBase && stream ) noexcept { std::swap( _flags, stream._flags ); } virtual ~StreamBase() = default; StreamBase & operator=( const StreamBase & ) = delete; StreamBase & operator=( StreamBase && stream ) noexcept { if ( this == &stream ) { return *this; } std::swap( _flags, stream._flags ); return *this; } void setbigendian( bool f ); bool isconstbuf() const { return ( _flags & CONST_BUF ) != 0; } bool fail() const { return ( _flags & FAILURE ) != 0; } bool bigendian() const { return ( _flags & BIGENDIAN ) != 0; } virtual void skip( size_t ) = 0; virtual uint16_t getBE16() = 0; virtual uint16_t getLE16() = 0; virtual uint32_t getBE32() = 0; virtual uint32_t getLE32() = 0; virtual void putBE32( uint32_t ) = 0; virtual void putLE32( uint32_t ) = 0; virtual void putBE16( uint16_t ) = 0; virtual void putLE16( uint16_t ) = 0; virtual std::vector getRaw( size_t = 0 /* all data */ ) = 0; virtual void putRaw( const char *, size_t ) = 0; uint16_t get16(); uint32_t get32(); void put16( uint16_t ); void put32( uint32_t ); uint8_t get() { return get8(); } void put( const uint8_t ch ) { put8( ch ); } StreamBase & operator>>( bool & v ); StreamBase & operator>>( char & v ); StreamBase & operator>>( uint8_t & v ); StreamBase & operator>>( uint16_t & v ); StreamBase & operator>>( int16_t & v ); StreamBase & operator>>( uint32_t & v ); StreamBase & operator>>( int32_t & v ); StreamBase & operator>>( std::string & v ); StreamBase & operator>>( fheroes2::Point & point_ ); StreamBase & operator<<( const bool v ); StreamBase & operator<<( const char v ); StreamBase & operator<<( const uint8_t v ); StreamBase & operator<<( const uint16_t v ); StreamBase & operator<<( const int16_t v ); StreamBase & operator<<( const uint32_t v ); StreamBase & operator<<( const int32_t v ); StreamBase & operator<<( const std::string & v ); StreamBase & operator<<( const fheroes2::Point & point_ ); template StreamBase & operator>>( std::pair & p ) { return *this >> p.first >> p.second; } template StreamBase & operator>>( std::vector & v ) { const uint32_t size = get32(); v.resize( size ); for ( typename std::vector::iterator it = v.begin(); it != v.end(); ++it ) *this >> *it; return *this; } template StreamBase & operator>>( std::list & v ) { const uint32_t size = get32(); v.resize( size ); for ( typename std::list::iterator it = v.begin(); it != v.end(); ++it ) *this >> *it; return *this; } template StreamBase & operator>>( std::map & v ) { const uint32_t size = get32(); v.clear(); for ( uint32_t ii = 0; ii < size; ++ii ) { std::pair pr; *this >> pr; v.emplace( std::move( pr ) ); } return *this; } template StreamBase & operator>>( std::array & data ) { const uint32_t size = get32(); if ( size != data.size() ) { // This is a corrupted file! assert( 0 ); data = {}; return *this; } for ( auto & value : data ) { *this >> value; } return *this; } template StreamBase & operator<<( const std::pair & p ) { return *this << p.first << p.second; } template StreamBase & operator<<( const std::vector & v ) { put32( static_cast( v.size() ) ); for ( typename std::vector::const_iterator it = v.begin(); it != v.end(); ++it ) *this << *it; return *this; } template StreamBase & operator<<( const std::list & v ) { put32( static_cast( v.size() ) ); for ( typename std::list::const_iterator it = v.begin(); it != v.end(); ++it ) *this << *it; return *this; } template StreamBase & operator<<( const std::map & v ) { put32( static_cast( v.size() ) ); for ( typename std::map::const_iterator it = v.begin(); it != v.end(); ++it ) *this << *it; return *this; } template StreamBase & operator<<( const std::array & data ) { put32( static_cast( data.size() ) ); for ( const auto & value : data ) { *this << value; } return *this; } protected: virtual uint8_t get8() = 0; virtual void put8( const uint8_t ) = 0; virtual size_t sizeg() = 0; virtual size_t sizep() = 0; virtual size_t tellg() = 0; virtual size_t tellp() = 0; void setconstbuf( bool f ); void setfail( bool f ); private: enum : uint32_t { FAILURE = 0x00000001, CONST_BUF = 0x00000002, BIGENDIAN = 0x00000004 }; uint32_t _flags{ 0 }; }; class StreamBuf : public StreamBase { public: explicit StreamBuf( const size_t sz = 0 ); explicit StreamBuf( const std::vector & buf ); StreamBuf( const StreamBuf & ) = delete; StreamBuf( StreamBuf && stream ) noexcept; ~StreamBuf() override; StreamBuf & operator=( const StreamBuf & ) = delete; StreamBuf & operator=( StreamBuf && stream ) noexcept; const uint8_t * data() const { return itget; } // If you use this method to write data update the cursor by calling advance() method. uint8_t * data() { return itget; } void advance( const size_t size ) { itput += size; } size_t size() { return sizeg(); } size_t capacity() const { return itend - itbeg; } void seek( size_t sz ) { itget = itbeg + sz < itend ? itbeg + sz : itend; } void skip( size_t sz ) override; uint16_t getBE16() override; uint16_t getLE16() override; uint32_t getBE32() override; uint32_t getLE32() override; void putBE32( uint32_t v ) override; void putLE32( uint32_t v ) override; void putBE16( uint16_t v ) override; void putLE16( uint16_t v ) override; std::vector getRaw( size_t sz = 0 /* all data */ ) override; void putRaw( const char * ptr, size_t sz ) override; std::string toString( const size_t size = 0 ); protected: void reset(); size_t tellg() override; size_t tellp() override; size_t sizeg() override; size_t sizep() override; void reallocbuf( size_t size ); uint8_t get8() override; void put8( const uint8_t v ) override; friend class ZStreamBuf; uint8_t * itbeg{ nullptr }; uint8_t * itget{ nullptr }; uint8_t * itput{ nullptr }; uint8_t * itend{ nullptr }; }; class StreamFile : public StreamBase { public: StreamFile() = default; StreamFile( const StreamFile & ) = delete; ~StreamFile() override = default; StreamFile & operator=( const StreamFile & ) = delete; size_t size(); size_t tell(); bool open( const std::string & fn, const std::string & mode ); void close(); // 0 stands for full data. StreamBuf toStreamBuf( const size_t size = 0 ); void seek( size_t ); void skip( size_t ) override; uint16_t getBE16() override; uint16_t getLE16() override; uint32_t getBE32() override; uint32_t getLE32() override; void putBE16( uint16_t ) override; void putLE16( uint16_t ) override; void putBE32( uint32_t ) override; void putLE32( uint32_t ) override; // 0 stands for full data. std::vector getRaw( const size_t size = 0 ) override; void putRaw( const char *, size_t ) override; std::string toString( const size_t size = 0 ); protected: size_t sizeg() override; size_t sizep() override; size_t tellg() override; size_t tellp() override; uint8_t get8() override; void put8( const uint8_t v ) override; private: std::unique_ptr> _file{ nullptr, std::fclose }; template T getUint() { if ( !_file ) { return 0; } T val; if ( std::fread( &val, sizeof( T ), 1, _file.get() ) != 1 ) { setfail( true ); return 0; } return val; } template void putUint( const T val ) { if ( !_file ) { return; } if ( std::fwrite( &val, sizeof( T ), 1, _file.get() ) != 1 ) { setfail( true ); } } }; namespace fheroes2 { // Get a value of type T in the system byte order from the buffer in which it was originally stored in the little-endian byte order template ::value || std::is_floating_point::value>::type> T getLEValue( const char * data, const size_t base, const size_t offset = 0 ) { const char * begin = data + base + offset * sizeof( T ); const char * end = begin + sizeof( T ); T result; #if defined( BYTE_ORDER ) && defined( LITTLE_ENDIAN ) && BYTE_ORDER == LITTLE_ENDIAN std::copy( begin, end, reinterpret_cast( &result ) ); #elif defined( BYTE_ORDER ) && defined( BIG_ENDIAN ) && BYTE_ORDER == BIG_ENDIAN std::reverse_copy( begin, end, reinterpret_cast( &result ) ); #else #error "Unknown byte order" #endif return result; } } #endif fheroes2-1.0.12+dfsg/src/engine/smk_decoder.cpp000066400000000000000000000254751456075706000212750ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "smk_decoder.h" #include #include #include #include #include #include #include #include "exception.h" #include "image.h" #include "serialize.h" #include "smacker.h" namespace { const size_t audioHeaderSize = 44; void verifyVideoFile( const std::string & filePath ) { if ( filePath.empty() ) { // Why are you trying to even play a file from an empty path? assert( 0 ); return; } // Verify that the file is valid. We use C-code on purpose since libsmacker library does the same. std::unique_ptr> file( std::fopen( filePath.c_str(), "rb" ), std::fclose ); if ( file == nullptr ) { return; } std::fseek( file.get(), 0, SEEK_END ); const size_t fileSize = std::ftell( file.get() ); // According to https://wiki.multimedia.cx/index.php/Smacker the minimum size of a file must be 56 bytes. if ( fileSize < 56 ) { throw fheroes2::InvalidDataResources( "Video file " + filePath + " is being corrupted. Make sure that you own an official version of the game." ); } } } SMKVideoSequence::SMKVideoSequence( const std::string & filePath ) : _width( 0 ) , _height( 0 ) , _heightScaleFactor( 1 ) , _fps( 0 ) , _frameCount( 0 ) , _currentFrameId( 0 ) , _videoFile( nullptr ) { verifyVideoFile( filePath ); _videoFile = smk_open_file( filePath.c_str(), SMK_MODE_MEMORY ); if ( _videoFile == nullptr ) return; double usf = 0; const uint8_t audioChannelCount = 7; uint8_t trackMask = 0; uint8_t channelsPerTrack[audioChannelCount] = { 0 }; uint8_t audioBitDepth[audioChannelCount] = { 0 }; unsigned long audioRate[audioChannelCount] = { 0 }; std::vector> soundBuffer( audioChannelCount ); unsigned long width = 0; unsigned long height = 0; unsigned char scaledYMode = 1; smk_info_all( _videoFile, nullptr, &_frameCount, &usf ); smk_info_video( _videoFile, &width, &height, &scaledYMode ); _heightScaleFactor = scaledYMode; if ( _heightScaleFactor < 1 ) { // This is some corrupted video file. Let's still proceed with it. _heightScaleFactor = 1; } else if ( _heightScaleFactor > 2 ) { // None of formats supports scaling more than 2. _heightScaleFactor = 2; } _width = static_cast( width ); _height = static_cast( height ) * _heightScaleFactor; smk_info_audio( _videoFile, &trackMask, channelsPerTrack, audioBitDepth, audioRate ); if ( usf > 0 ) _fps = 1000000.0 / usf; else _fps = 15; // let's use as a default for ( uint8_t i = 0; i < audioChannelCount; ++i ) { if ( trackMask & ( 1 << i ) ) { smk_enable_audio( _videoFile, i, 1 ); } } smk_enable_video( _videoFile, 0 ); // disable video reading smk_first( _videoFile ); unsigned long currentFrame = 0; smk_info_all( _videoFile, ¤tFrame, nullptr, nullptr ); for ( uint8_t i = 0; i < audioChannelCount; ++i ) { if ( trackMask & ( 1 << i ) ) { const unsigned long length = smk_get_audio_size( _videoFile, i ); if ( length == 0 ) { continue; } if ( soundBuffer[i].empty() ) { soundBuffer[i].resize( audioHeaderSize ); } const uint8_t * data = smk_get_audio( _videoFile, i ); soundBuffer[i].insert( soundBuffer[i].end(), data, data + length ); } } for ( currentFrame = 1; currentFrame < _frameCount; ++currentFrame ) { smk_next( _videoFile ); for ( uint8_t i = 0; i < audioChannelCount; ++i ) { if ( trackMask & ( 1 << i ) ) { const unsigned long length = smk_get_audio_size( _videoFile, i ); if ( length == 0 ) { continue; } if ( soundBuffer[i].empty() ) { soundBuffer[i].resize( audioHeaderSize ); } const uint8_t * data = smk_get_audio( _videoFile, i ); soundBuffer[i].insert( soundBuffer[i].end(), data, data + length ); } } } size_t channelCount = 0; for ( size_t i = 0; i < soundBuffer.size(); ++i ) { if ( !soundBuffer[i].empty() ) { ++channelCount; } } _audioChannel.resize( channelCount ); channelCount = 0; // Compose the soundtrack for ( size_t i = 0; i < soundBuffer.size(); ++i ) { if ( !soundBuffer[i].empty() ) { std::vector & wavData = _audioChannel[channelCount]; std::swap( wavData, soundBuffer[i] ); const uint32_t originalSize = static_cast( wavData.size() - audioHeaderSize ); ++channelCount; StreamBuf wavHeader( audioHeaderSize ); wavHeader.putLE32( 0x46464952 ); // RIFF marker ("RIFF") wavHeader.putLE32( originalSize + 0x24 ); // Total size minus the size of this and previous fields wavHeader.putLE32( 0x45564157 ); // File type header ("WAVE") wavHeader.putLE32( 0x20746D66 ); // Format sub-chunk marker ("fmt ") wavHeader.putLE32( 0x10 ); // Size of the format sub-chunk wavHeader.putLE16( 0x01 ); // Audio format (1 for PCM) wavHeader.putLE16( channelsPerTrack[i] ); // Number of channels wavHeader.putLE32( audioRate[i] ); // Sample rate wavHeader.putLE32( audioRate[i] * audioBitDepth[i] * channelsPerTrack[i] / 8 ); // Byte rate wavHeader.putLE16( audioBitDepth[i] * channelsPerTrack[i] / 8 ); // Block align wavHeader.putLE16( audioBitDepth[i] ); // Bits per sample wavHeader.putLE32( 0x61746164 ); // Data sub-chunk marker ("data") wavHeader.putLE32( originalSize ); // Size of the data sub-chunk memcpy( wavData.data(), wavHeader.data(), audioHeaderSize ); } } smk_enable_video( _videoFile, 1 ); // enable video reading smk_first( _videoFile ); } SMKVideoSequence::~SMKVideoSequence() { if ( _videoFile != nullptr ) { smk_close( _videoFile ); } } int32_t SMKVideoSequence::width() const { return _width; } int32_t SMKVideoSequence::height() const { return _height; } double SMKVideoSequence::fps() const { return _fps; } unsigned long SMKVideoSequence::frameCount() const { return _frameCount; } void SMKVideoSequence::resetFrame() { if ( _videoFile == nullptr ) return; smk_first( _videoFile ); _currentFrameId = 0; } void SMKVideoSequence::getNextFrame( fheroes2::Image & image, const int32_t x, const int32_t y, int32_t & width, int32_t & height, std::vector & palette ) { if ( _videoFile == nullptr || image.empty() || x < 0 || y < 0 || x >= image.width() || y >= image.height() || !image.singleLayer() ) { width = 0; height = 0; return; } const uint8_t * data = smk_get_video( _videoFile ); const uint8_t * paletteData = smk_get_palette( _videoFile ); width = _width; height = _height; assert( _heightScaleFactor == 1 || _heightScaleFactor == 2 ); assert( ( _height % _heightScaleFactor ) == 0 ); if ( image.width() == _width && image.height() == _height && x == 0 && y == 0 ) { const size_t size = static_cast( _width ) * _height; if ( _heightScaleFactor == 2 ) { assert( ( height % _heightScaleFactor ) == 0 ); const uint8_t * inY = data; uint8_t * outY = image.image(); const uint8_t * outYEnd = outY + height * _width; for ( ; outY != outYEnd; outY += _heightScaleFactor * _width, inY += _width ) { std::copy( inY, inY + width, outY ); } } else { std::copy( data, data + size, image.image() ); } } else { if ( x + width > image.width() ) { width = image.width() - x; } if ( y + height > image.height() ) { height = image.height() - y; } const uint8_t * inY = data; const int32_t imageWidth = image.width(); uint8_t * outY = image.image() + x + y * imageWidth; const uint8_t * outYEnd = outY + ( height / _heightScaleFactor ) * _heightScaleFactor * imageWidth; if ( _heightScaleFactor == 2 ) { assert( ( height % _heightScaleFactor ) == 0 ); for ( ; outY != outYEnd; outY += _heightScaleFactor * imageWidth, inY += _width ) { std::copy( inY, inY + width, outY ); } } else { for ( ; outY != outYEnd; outY += imageWidth, inY += _width ) { std::copy( inY, inY + width, outY ); } } } palette.resize( 256 * 3 ); memcpy( palette.data(), paletteData, 256 * 3 ); ++_currentFrameId; if ( _currentFrameId < _frameCount ) { smk_next( _videoFile ); } } std::vector SMKVideoSequence::getCurrentPalette() const { const uint8_t * paletteData = smk_get_palette( _videoFile ); assert( paletteData != nullptr ); return std::vector( paletteData, paletteData + 256 * 3 ); } const std::vector> & SMKVideoSequence::getAudioChannels() const { return _audioChannel; } fheroes2-1.0.12+dfsg/src/engine/smk_decoder.h000066400000000000000000000054371456075706000207360ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include namespace fheroes2 { class Image; } class SMKVideoSequence { public: explicit SMKVideoSequence( const std::string & filePath ); ~SMKVideoSequence(); SMKVideoSequence( const SMKVideoSequence & ) = delete; SMKVideoSequence & operator=( const SMKVideoSequence & ) = delete; void resetFrame(); // Input image must be resized to accommodate the frame and also it must be a single layer image as video frames shouldn't have any transform-related information. // If the image is smaller than the frame then only a part of the frame will be drawn. void getNextFrame( fheroes2::Image & image, const int32_t x, const int32_t y, int32_t & width, int32_t & height, std::vector & palette ); std::vector getCurrentPalette() const; const std::vector> & getAudioChannels() const; int32_t width() const; int32_t height() const; double fps() const; unsigned long frameCount() const; unsigned long getCurrentFrame() const { return _currentFrameId; } private: std::vector> _audioChannel; int32_t _width; int32_t _height; int32_t _heightScaleFactor; double _fps; unsigned long _frameCount; unsigned long _currentFrameId; struct smk_t * _videoFile; }; fheroes2-1.0.12+dfsg/src/engine/system.cpp000066400000000000000000000450201456075706000203260ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "system.h" #include #include #include #include #include #include #if defined( _WIN32 ) #include #include #include #include #define WIN32_LEAN_AND_MEAN #include #else #include #include #if defined( TARGET_PS_VITA ) #include #include #else #include #endif #endif #if defined( _WIN32 ) || defined( ANDROID ) #include "logging.h" #else #include #endif #include #if defined( ANDROID ) #include #include #endif #if SDL_VERSION_ATLEAST( 2, 0, 1 ) && ( !defined( __linux__ ) || defined( ANDROID ) ) #include #include #endif #if defined( _WIN32 ) #define SEPARATOR '\\' #else #define SEPARATOR '/' #endif namespace { #if !defined( __linux__ ) || defined( ANDROID ) std::string GetHomeDirectory( const std::string & prog ) { #if defined( TARGET_PS_VITA ) return System::concatPath( "ux0:data", prog ); #elif defined( TARGET_NINTENDO_SWITCH ) return System::concatPath( "/switch", prog ); #elif defined( ANDROID ) (void)prog; const char * storagePath = SDL_AndroidGetExternalStoragePath(); if ( storagePath == nullptr ) { ERROR_LOG( "Failed to obtain the path to external storage. The error: " << SDL_GetError() ) return { "." }; } VERBOSE_LOG( "Application storage path is " << storagePath ) return storagePath; #endif const char * homeEnvPath = getenv( "HOME" ); #if defined( MACOS_APP_BUNDLE ) if ( homeEnvPath != nullptr ) { return System::concatPath( System::concatPath( homeEnvPath, "Library/Preferences" ), prog ); } return { "." }; #endif if ( homeEnvPath != nullptr ) { return System::concatPath( homeEnvPath, std::string( "." ).append( prog ) ); } const char * dataEnvPath = getenv( "APPDATA" ); if ( dataEnvPath != nullptr ) { return System::concatPath( dataEnvPath, prog ); } #if SDL_VERSION_ATLEAST( 2, 0, 1 ) char * path = SDL_GetPrefPath( "", prog.c_str() ); if ( path ) { const std::string result{ path }; SDL_free( path ); return result; } #endif return { "." }; } #endif #if !defined( _WIN32 ) && !defined( ANDROID ) std::vector splitUnixPath( const std::string & path, const std::string_view delimiter ) { std::vector result; if ( path.empty() ) { return result; } size_t pos = 0; while ( pos < path.size() ) { const size_t nextPos = path.find( delimiter, pos ); if ( nextPos == std::string::npos ) { result.push_back( path.substr( pos ) ); break; } if ( pos < nextPos ) { result.push_back( path.substr( pos, nextPos - pos ) ); } pos = nextPos + delimiter.size(); } return result; } #endif std::string_view trimTrailingSeparators( std::string_view path ) { while ( path.size() > 1 && path.back() == SEPARATOR ) { path.remove_suffix( 1 ); } return path; } bool globMatch( const std::string_view string, const std::string_view wildcard ) { size_t stringIdx = 0; size_t wildcardIdx = 0; size_t fallbackStringIdx = std::string_view::npos; size_t fallbackWildcardIdx = std::string_view::npos; while ( stringIdx < string.length() ) { const bool isWildcardNotEnded = ( wildcardIdx < wildcard.length() ); if ( isWildcardNotEnded && wildcard[wildcardIdx] == '*' ) { ++wildcardIdx; fallbackStringIdx = stringIdx; fallbackWildcardIdx = wildcardIdx; } else if ( isWildcardNotEnded && ( wildcard[wildcardIdx] == '?' || wildcard[wildcardIdx] == string[stringIdx] ) ) { ++stringIdx; ++wildcardIdx; } else { if ( fallbackWildcardIdx == std::string_view::npos ) { return false; } assert( fallbackStringIdx != std::string_view::npos ); ++fallbackStringIdx; stringIdx = fallbackStringIdx; wildcardIdx = fallbackWildcardIdx; } } for ( ; wildcardIdx < wildcard.length(); ++wildcardIdx ) { if ( wildcard[wildcardIdx] != '*' ) { break; } } return wildcardIdx == wildcard.length(); } } bool System::isHandheldDevice() { #if defined( ANDROID ) return true; #else return false; #endif } bool System::isVirtualKeyboardSupported() { #if defined( ANDROID ) || defined( TARGET_PS_VITA ) || defined( TARGET_NINTENDO_SWITCH ) return true; #else return false; #endif } bool System::isShellLevelGlobbingSupported() { #if defined( _WIN32 ) return false; #else return true; #endif } bool System::MakeDirectory( const std::string & path ) { #if defined( _WIN32 ) return _mkdir( path.c_str() ) == 0; #elif defined( TARGET_PS_VITA ) return sceIoMkdir( path.c_str(), 0777 ) == 0; #else return mkdir( path.c_str(), S_IRWXU ) == 0; #endif } std::string System::concatPath( const std::string_view left, const std::string_view right ) { // Avoid memory allocation while concatenating string. Allocate needed size at once. std::string temp; temp.reserve( left.size() + 1 + right.size() ); temp += left; temp += SEPARATOR; temp += right; return temp; } void System::appendOSSpecificDirectories( std::vector & directories ) { #if defined( TARGET_PS_VITA ) const char * path = "ux0:app/FHOMM0002"; if ( std::find( directories.begin(), directories.end(), path ) == directories.end() ) { directories.emplace_back( path ); } #else (void)directories; #endif } std::string System::GetConfigDirectory( const std::string & prog ) { #if defined( __linux__ ) && !defined( ANDROID ) const char * configEnv = getenv( "XDG_CONFIG_HOME" ); if ( configEnv ) { return System::concatPath( configEnv, prog ); } const char * homeEnv = getenv( "HOME" ); if ( homeEnv ) { return System::concatPath( System::concatPath( homeEnv, ".config" ), prog ); } return { "." }; #else return GetHomeDirectory( prog ); #endif } std::string System::GetDataDirectory( const std::string & prog ) { #if defined( __linux__ ) && !defined( ANDROID ) const char * dataEnv = getenv( "XDG_DATA_HOME" ); if ( dataEnv ) { return System::concatPath( dataEnv, prog ); } const char * homeEnv = getenv( "HOME" ); if ( homeEnv ) { return System::concatPath( System::concatPath( homeEnv, ".local/share" ), prog ); } return { "." }; #elif defined( MACOS_APP_BUNDLE ) const char * homeEnv = getenv( "HOME" ); if ( homeEnv ) { return System::concatPath( System::concatPath( homeEnv, "Library/Application Support" ), prog ); } return { "." }; #else return GetHomeDirectory( prog ); #endif } std::string System::GetDirname( std::string_view path ) { if ( path.empty() ) { return { "." }; } path = trimTrailingSeparators( path ); const size_t pos = path.rfind( SEPARATOR ); if ( pos == std::string::npos ) { return { "." }; } if ( pos == 0 ) { return { std::initializer_list{ SEPARATOR } }; } // Trailing separators should already be trimmed assert( pos != path.size() - 1 ); return std::string{ trimTrailingSeparators( path.substr( 0, pos ) ) }; } std::string System::GetBasename( std::string_view path ) { if ( path.empty() ) { return { "." }; } path = trimTrailingSeparators( path ); const size_t pos = path.rfind( SEPARATOR ); if ( pos == std::string::npos || ( pos == 0 && path.size() == 1 ) ) { return std::string{ path }; } // Trailing separators should already be trimmed assert( pos != path.size() - 1 ); return std::string{ path.substr( pos + 1 ) }; } bool System::IsFile( const std::string & path, bool writable ) { if ( path.empty() ) { // An empty path cannot be a file. return false; } #if defined( _WIN32 ) const DWORD fileAttributes = GetFileAttributes( path.c_str() ); if ( fileAttributes == INVALID_FILE_ATTRIBUTES ) { // This path doesn't exist. return false; } if ( ( fileAttributes & FILE_ATTRIBUTE_DIRECTORY ) != 0 ) { // This is a directory. return false; } return writable ? ( 0 == _access( path.c_str(), 06 ) ) : ( 0 == _access( path.c_str(), 04 ) ); #elif defined( TARGET_PS_VITA ) || defined( ANDROID ) // TODO: check if it is really a file. return writable ? 0 == access( path.c_str(), W_OK ) : 0 == access( path.c_str(), R_OK ); #else std::string correctedPath; if ( !GetCaseInsensitivePath( path, correctedPath ) ) return false; struct stat fs; if ( stat( correctedPath.c_str(), &fs ) || !S_ISREG( fs.st_mode ) ) return false; return writable ? 0 == access( correctedPath.c_str(), W_OK ) : S_IRUSR & fs.st_mode; #endif } bool System::IsDirectory( const std::string & path, bool writable ) { if ( path.empty() ) { // An empty path cannot be a directory. return false; } #if defined( _WIN32 ) const DWORD fileAttributes = GetFileAttributes( path.c_str() ); if ( fileAttributes == INVALID_FILE_ATTRIBUTES ) { // This path doesn't exist. return false; } if ( ( fileAttributes & FILE_ATTRIBUTE_DIRECTORY ) == 0 ) { // Not a directory. return false; } return writable ? ( 0 == _access( path.c_str(), 06 ) ) : ( 0 == _access( path.c_str(), 00 ) ); #elif defined( TARGET_PS_VITA ) || defined( ANDROID ) // TODO: check if it is really a directory. return writable ? 0 == access( path.c_str(), W_OK ) : 0 == access( path.c_str(), R_OK ); #else std::string correctedPath; if ( !GetCaseInsensitivePath( path, correctedPath ) ) return false; struct stat fs; if ( stat( correctedPath.c_str(), &fs ) || !S_ISDIR( fs.st_mode ) ) return false; return writable ? 0 == access( correctedPath.c_str(), W_OK ) : S_IRUSR & fs.st_mode; #endif } bool System::Unlink( const std::string & path ) { #if defined( _WIN32 ) return _unlink( path.c_str() ) == 0; #else return unlink( path.c_str() ) == 0; #endif } #if !defined( _WIN32 ) && !defined( ANDROID ) // based on: https://github.com/OneSadCookie/fcaseopen bool System::GetCaseInsensitivePath( const std::string & path, std::string & correctedPath ) { correctedPath.clear(); if ( path.empty() ) { return false; } DIR * d; bool last = false; const char * curDir = "."; const char * delimiter = "/"; if ( path[0] == delimiter[0] ) { correctedPath.append( delimiter ); d = opendir( delimiter ); } else { d = opendir( curDir ); } const std::vector splittedPath = splitUnixPath( path, delimiter ); for ( std::vector::const_iterator subPathIter = splittedPath.begin(); subPathIter != splittedPath.end(); ++subPathIter ) { if ( !d ) { return false; } if ( last ) { closedir( d ); return false; } if ( subPathIter != splittedPath.begin() ) { correctedPath.append( delimiter ); } // Avoid directory traversal and try to probe directory name directly. // Speeds up file lookup when intermediate directories have a lot of // files. Example is NixOS where file layout is: // /nix/store/...-fheroes2-${ver}/share/fheroes2/files/lang // /nix/store/...-other-package-1/... // /nix/store/...-other-package-2/... // It's not uncommon for /nix/store to have tens of thousands files. // GetCaseInsensitivePath() calls become very expensive on such systems. // // The idea is to try to open current subpath as a directory and avoid // directory traversal altogether. Otherwise fall back to linear // case-insensitive search. std::string absSubpath = correctedPath + *subPathIter; DIR * de = opendir( absSubpath.c_str() ); if ( de ) { correctedPath = std::move( absSubpath ); closedir( d ); d = opendir( correctedPath.c_str() ); closedir( de ); continue; } const struct dirent * e = readdir( d ); while ( e ) { if ( strcasecmp( ( *subPathIter ).c_str(), e->d_name ) == 0 ) { correctedPath += e->d_name; closedir( d ); d = opendir( correctedPath.c_str() ); break; } e = readdir( d ); } if ( !e ) { correctedPath += *subPathIter; last = true; } } if ( d ) { closedir( d ); } return !last; } #else bool System::GetCaseInsensitivePath( const std::string & path, std::string & correctedPath ) { correctedPath = path; return true; } #endif void System::globFiles( const std::string_view glob, std::vector & fileNames ) { const std::filesystem::path globPath( glob ); std::filesystem::path dirPath = globPath.parent_path(); if ( dirPath.empty() ) { dirPath = std::filesystem::path{ "." }; } std::error_code ec; // Using the non-throwing overload if ( !std::filesystem::is_directory( dirPath, ec ) ) { fileNames.emplace_back( glob ); return; } const std::string pattern = globPath.filename().string(); if ( pattern.find( '*' ) == std::string_view::npos && pattern.find( '?' ) == std::string_view::npos ) { fileNames.emplace_back( glob ); return; } bool isNoMatches = true; // Using the non-throwing overload for ( const std::filesystem::directory_entry & entry : std::filesystem::directory_iterator( dirPath, ec ) ) { const std::filesystem::path & entryPath = entry.path(); if ( globMatch( entryPath.filename().string(), pattern ) ) { fileNames.push_back( entryPath.string() ); isNoMatches = false; } } if ( isNoMatches ) { fileNames.emplace_back( glob ); } } std::string System::FileNameToUTF8( const std::string & name ) { #if defined( _WIN32 ) if ( name.empty() ) { return name; } thread_local std::map acpToUtf8; const auto iter = acpToUtf8.find( name ); if ( iter != acpToUtf8.end() ) { return iter->second; } // In case of any issues, the original string will be returned, so let's put it to the cache right away acpToUtf8[name] = name; const auto getLastErrorStr = []() { LPTSTR msgBuf; if ( FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, GetLastError(), 0, reinterpret_cast( &msgBuf ), 0, nullptr ) > 0 ) { const std::string result( msgBuf ); LocalFree( msgBuf ); return result; } return std::string( "FormatMessage() failed: " ) + std::to_string( GetLastError() ); }; const int wLen = MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, name.c_str(), -1, nullptr, 0 ); if ( wLen <= 0 ) { ERROR_LOG( getLastErrorStr() ) return name; } const std::unique_ptr wStr( new wchar_t[wLen] ); if ( MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, name.c_str(), -1, wStr.get(), wLen ) != wLen ) { ERROR_LOG( getLastErrorStr() ) return name; } const int uLen = WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS | WC_NO_BEST_FIT_CHARS, wStr.get(), -1, nullptr, 0, nullptr, nullptr ); if ( uLen <= 0 ) { ERROR_LOG( getLastErrorStr() ) return name; } const std::unique_ptr uStr( new char[uLen] ); if ( WideCharToMultiByte( CP_UTF8, WC_ERR_INVALID_CHARS | WC_NO_BEST_FIT_CHARS, wStr.get(), -1, uStr.get(), uLen, nullptr, nullptr ) != uLen ) { ERROR_LOG( getLastErrorStr() ) return name; } const std::string result( uStr.get() ); // Put the final result to the cache acpToUtf8[name] = result; return result; #else return name; #endif } tm System::GetTM( const time_t time ) { tm result = {}; #if defined( _WIN32 ) errno_t res = localtime_s( &result, &time ); if ( res != 0 ) { assert( 0 ); } #else const tm * res = localtime_r( &time, &result ); if ( res == nullptr ) { assert( 0 ); } #endif return result; } fheroes2-1.0.12+dfsg/src/engine/system.h000066400000000000000000000067671456075706000200120ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2013 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2SYSTEM_H #define H2SYSTEM_H #include #include #include #include namespace System { bool isHandheldDevice(); bool isVirtualKeyboardSupported(); // Returns true if target platform supports shell-level globbing (Unix-like platforms with POSIX-compatible shells). // Otherwise returns false, which means that app need to resolve wildcard patterns itself (for example, on Windows). bool isShellLevelGlobbingSupported(); bool MakeDirectory( const std::string & path ); std::string concatPath( const std::string_view left, const std::string_view right ); void appendOSSpecificDirectories( std::vector & directories ); std::string GetConfigDirectory( const std::string & prog ); std::string GetDataDirectory( const std::string & prog ); std::string GetDirname( std::string_view path ); std::string GetBasename( std::string_view path ); bool IsFile( const std::string & path, bool writable = false ); bool IsDirectory( const std::string & path, bool writable = false ); bool Unlink( const std::string & path ); bool GetCaseInsensitivePath( const std::string & path, std::string & correctedPath ); // Resolves the wildcard pattern 'glob' and appends matching paths to 'fileNames'. Supported wildcards are '?' and '*'. // These wildcards are resolved only if they are in the last element of the path. For example, they will be resolved // in the case of the 'foo/b*r?' pattern, but they will be ignored (used as is) in the case of '*/bar' pattern. If there // are no files matching the pattern, it will be appended to the 'fileNames' as is. void globFiles( const std::string_view glob, std::vector & fileNames ); std::string FileNameToUTF8( const std::string & name ); tm GetTM( const time_t time ); } #endif fheroes2-1.0.12+dfsg/src/engine/thread.cpp000066400000000000000000000064071456075706000202570ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "thread.h" #include #include namespace MultiThreading { void AsyncManager::createWorker() { if ( !_worker ) { _runFlag = true; _worker = std::make_unique( AsyncManager::_workerThread, this ); { std::unique_lock lock( _mutex ); _masterNotification.wait( lock, [this] { return !_runFlag; } ); } } } void AsyncManager::stopWorker() { if ( _worker ) { { const std::scoped_lock lock( _mutex ); _exitFlag = true; _runFlag = true; } _workerNotification.notify_all(); _worker->join(); _worker.reset(); } } void AsyncManager::notifyWorker() { _runFlag = true; _workerNotification.notify_all(); } void AsyncManager::_workerThread( AsyncManager * manager ) { assert( manager != nullptr ); { const std::scoped_lock lock( manager->_mutex ); manager->_runFlag = false; } manager->_masterNotification.notify_one(); while ( !manager->_exitFlag ) { { std::unique_lock lock( manager->_mutex ); manager->_workerNotification.wait( lock, [manager] { return manager->_runFlag; } ); } if ( manager->_exitFlag ) { break; } { const std::scoped_lock lock( manager->_mutex ); const bool moreTasks = manager->prepareTask(); if ( !moreTasks ) { manager->_runFlag = false; } } manager->executeTask(); } } } fheroes2-1.0.12+dfsg/src/engine/thread.h000066400000000000000000000060741456075706000177240ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include #include #include namespace MultiThreading { class AsyncManager { public: AsyncManager() = default; AsyncManager( const AsyncManager & ) = delete; virtual ~AsyncManager() = default; AsyncManager & operator=( const AsyncManager & ) = delete; // Create the worker thread if it doesn't exist yet. Both createWorker() and stopWorker() are not // designed to be executed concurrently. void createWorker(); // Stop and join the worker thread. This cannot be done in the destructor (directly or indirectly) due // to the potential race on the vptr since this class has virtual methods that could be called from the // worker thread. void stopWorker(); protected: std::mutex _mutex; // Notify the worker thread about a new task. The _mutex should be acquired while calling this method. void notifyWorker(); // Prepare a task which requires mutex lock. Returns true if more tasks are available. virtual bool prepareTask() = 0; // Task execution is done in non-thread safe mode! No mutex lock for any means of synchronizations are // done for this call. virtual void executeTask() = 0; private: std::unique_ptr _worker; std::condition_variable _masterNotification; std::condition_variable _workerNotification; std::atomic _exitFlag{ false }; bool _runFlag{ false }; static void _workerThread( AsyncManager * manager ); }; } fheroes2-1.0.12+dfsg/src/engine/timing.cpp000066400000000000000000000057631456075706000203030ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include "timing.h" namespace fheroes2 { Time::Time() : _startTime( std::chrono::steady_clock::now() ) {} void Time::reset() { _startTime = std::chrono::steady_clock::now(); } double Time::getS() const { const std::chrono::duration time = std::chrono::steady_clock::now() - _startTime; return time.count(); } uint64_t Time::getMs() const { const auto time = std::chrono::duration_cast( std::chrono::steady_clock::now() - _startTime ); return time.count(); } TimeDelay::TimeDelay( const uint64_t delayMs ) : _prevTime( std::chrono::steady_clock::now() ) , _delayMs( delayMs ) {} void TimeDelay::setDelay( const uint64_t delayMs ) { _delayMs = delayMs; } bool TimeDelay::isPassed() const { return isPassed( _delayMs ); } bool TimeDelay::isPassed( const uint64_t delayMs ) const { const auto time = std::chrono::duration_cast( std::chrono::steady_clock::now() - _prevTime ); const uint64_t passedMs = time.count(); return passedMs >= delayMs; } void TimeDelay::reset() { _prevTime = std::chrono::steady_clock::now(); } void TimeDelay::pass() { _prevTime = std::chrono::steady_clock::now() - std::chrono::milliseconds( 2 * _delayMs ); } void delayforMs( const uint32_t delayMs ) { std::this_thread::sleep_for( std::chrono::milliseconds( delayMs ) ); } } fheroes2-1.0.12+dfsg/src/engine/timing.h000066400000000000000000000054201456075706000177360ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include namespace fheroes2 { // IMPORTANT!!! According to https://en.cppreference.com/w/cpp/chrono/high_resolution_clock we should never use high_resolution_clock for time internal measurements // because for high_resolution_clock the time may go backwards. class Time { public: Time(); void reset(); // Returns time in seconds. double getS() const; // Returns rounded time in milliseconds. uint64_t getMs() const; private: std::chrono::time_point _startTime; }; class TimeDelay { public: TimeDelay() = delete; explicit TimeDelay( const uint64_t delayMs ); void setDelay( const uint64_t delayMs ); uint64_t getDelay() const { return _delayMs; } bool isPassed() const; bool isPassed( const uint64_t delayMs ) const; // Reset delay by starting the count from the current time. void reset(); // Explicitly set delay to passed state. Can be used in cases when first call of isPassed() must return true. void pass(); private: std::chrono::time_point _prevTime; uint64_t _delayMs; }; void delayforMs( const uint32_t delayMs ); } fheroes2-1.0.12+dfsg/src/engine/tinyconfig.cpp000066400000000000000000000220611456075706000211530ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "tinyconfig.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "serialize.h" #include "tools.h" namespace { std::string ModifyKey( const std::string & str ) { std::string key = StringTrim( StringLower( str ) ); // Replace consecutive space-like characters with only one such character key.erase( std::unique( key.begin(), key.end(), []( const unsigned char a, const unsigned char b ) { return std::isspace( a ) && std::isspace( b ); } ), key.end() ); // Replace all space-like characters with spaces std::replace_if( key.begin(), key.end(), []( const unsigned char c ) { return std::isspace( c ); }, '\x20' ); return key; } template >> bool convertToInt( const std::string_view str, T & intValue ) { const char * first = str.data(); const char * last = str.data() + str.size(); const auto [ptr, ec] = std::from_chars( first, last, intValue ); return ( ptr == last && ec == std::errc() ); } } TinyConfig::TinyConfig( char sep, char com ) : separator( sep ) , comment( com ) {} bool TinyConfig::Load( const std::string & cfile ) { StreamFile sf; if ( !sf.open( cfile, "rb" ) ) return false; std::vector rows = StringSplit( sf.toString(), '\n' ); for ( std::vector::const_iterator it = rows.begin(); it != rows.end(); ++it ) { std::string str = StringTrim( *it ); if ( str.empty() || str[0] == comment ) continue; size_t pos = str.find( separator ); if ( std::string::npos != pos ) { std::string left( str.substr( 0, pos ) ); std::string right( str.substr( pos + 1, str.length() - pos - 1 ) ); left = StringTrim( left ); right = StringTrim( right ); emplace( ModifyKey( left ), right ); } } return true; } int TinyConfig::IntParams( const std::string & key ) const { const_iterator it = find( ModifyKey( key ) ); if ( it == end() ) { return 0; } int result; if ( !convertToInt( it->second, result ) ) { return 0; } return result; } std::string TinyConfig::StrParams( const std::string & key ) const { const_iterator it = find( ModifyKey( key ) ); return it != end() ? it->second : ""; } fheroes2::Point TinyConfig::PointParams( const std::string & key, const fheroes2::Point & fallbackValue ) const { const const_iterator it = find( ModifyKey( key ) ); if ( it == end() ) { return fallbackValue; } static const std::regex pointRegex( "^\\[ *(-?[0-9]+) *, *(-?[0-9]+) *]$", std::regex_constants::extended ); std::smatch pointRegexMatch; if ( !std::regex_match( it->second, pointRegexMatch, pointRegex ) ) { return fallbackValue; } assert( pointRegexMatch.size() == 3 ); fheroes2::Point result; static_assert( std::is_integral::value && std::is_integral::value, "The type of result fields is not integer, check the logic of this method" ); if ( !convertToInt( pointRegexMatch[1].str(), result.x ) ) { return fallbackValue; } if ( !convertToInt( pointRegexMatch[2].str(), result.y ) ) { return fallbackValue; } return result; } fheroes2::ResolutionInfo TinyConfig::ResolutionParams( const std::string & key, const fheroes2::ResolutionInfo & fallbackValue ) const { const const_iterator it = find( ModifyKey( key ) ); if ( it == end() ) { return fallbackValue; } { // Current format: in-game width x in-game height : on-screen width x on-screen height static const std::regex resolutionRegex( "^ *([0-9]+) *x *([0-9]+) *: *([0-9]+) *x *([0-9]+) *$", std::regex_constants::extended ); std::smatch resolutionRegexMatch; if ( std::regex_match( it->second, resolutionRegexMatch, resolutionRegex ) ) { assert( resolutionRegexMatch.size() == 5 ); int32_t gameWidth{ 0 }; int32_t gameHeight{ 0 }; int32_t screenWidth{ 0 }; int32_t screenHeight{ 0 }; if ( !convertToInt( resolutionRegexMatch[1].str(), gameWidth ) ) { return fallbackValue; } if ( !convertToInt( resolutionRegexMatch[2].str(), gameHeight ) ) { return fallbackValue; } if ( !convertToInt( resolutionRegexMatch[3].str(), screenWidth ) ) { return fallbackValue; } if ( !convertToInt( resolutionRegexMatch[4].str(), screenHeight ) ) { return fallbackValue; } return { gameWidth, gameHeight, screenWidth, screenHeight }; } } { // Old format: in-game width x in-game height x on-screen multiplier static const std::regex resolutionRegex( "^ *([0-9]+) *x *([0-9]+) *x *([0-9]+) *$", std::regex_constants::extended ); std::smatch resolutionRegexMatch; if ( std::regex_match( it->second, resolutionRegexMatch, resolutionRegex ) ) { assert( resolutionRegexMatch.size() == 4 ); int32_t gameWidth{ 0 }; int32_t gameHeight{ 0 }; int32_t screenMultiplier{ 0 }; if ( !convertToInt( resolutionRegexMatch[1].str(), gameWidth ) ) { return fallbackValue; } if ( !convertToInt( resolutionRegexMatch[2].str(), gameHeight ) ) { return fallbackValue; } if ( !convertToInt( resolutionRegexMatch[3].str(), screenMultiplier ) ) { return fallbackValue; } assert( gameWidth >= 0 && gameHeight >= 0 && screenMultiplier >= 0 ); if ( screenMultiplier == 0 ) { return fallbackValue; } // Check for potential signed overflow on multiplication if ( gameWidth > std::numeric_limits::max() / screenMultiplier || gameHeight > std::numeric_limits::max() / screenMultiplier ) { return fallbackValue; } return { gameWidth, gameHeight, gameWidth * screenMultiplier, gameHeight * screenMultiplier }; } } { // Abbreviated format: in-game width x in-game height with on-screen multiplier 1 static const std::regex resolutionRegex( "^ *([0-9]+) *x *([0-9]+) *$", std::regex_constants::extended ); std::smatch resolutionRegexMatch; if ( std::regex_match( it->second, resolutionRegexMatch, resolutionRegex ) ) { assert( resolutionRegexMatch.size() == 3 ); int32_t gameWidth{ 0 }; int32_t gameHeight{ 0 }; if ( !convertToInt( resolutionRegexMatch[1].str(), gameWidth ) ) { return fallbackValue; } if ( !convertToInt( resolutionRegexMatch[2].str(), gameHeight ) ) { return fallbackValue; } return { gameWidth, gameHeight }; } } return fallbackValue; } bool TinyConfig::Exists( const std::string & key ) const { return end() != find( ModifyKey( key ) ); } fheroes2-1.0.12+dfsg/src/engine/tinyconfig.h000066400000000000000000000053751456075706000206310ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef TINYCONFIG_H #define TINYCONFIG_H #include #include #include "math_base.h" #include "screen.h" class TinyConfig : private std::multimap { public: TinyConfig( char sep = '=', char com = ';' ); bool Load( const std::string & cfile ); bool Exists( const std::string & key ) const; int IntParams( const std::string & key ) const; std::string StrParams( const std::string & key ) const; // Tries to find and return a Point-type struct stored as a string "[ x, y ]" for a given key. // In case of any error, the fallback value is returned. fheroes2::Point PointParams( const std::string & key, const fheroes2::Point & fallbackValue ) const; // Tries to find and return a ResolutionInfo-type struct (multiple formats are supported, see the implementation for details) // for a given key. In case of any error, the fallback value is returned. fheroes2::ResolutionInfo ResolutionParams( const std::string & key, const fheroes2::ResolutionInfo & fallbackValue ) const; private: char separator; char comment; }; #endif fheroes2-1.0.12+dfsg/src/engine/tools.cpp000066400000000000000000000341621456075706000201470ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "tools.h" #include #include #include #include #include #include #include // IWYU pragma: keep #include #include #include #include #include #include "translations.h" std::string StringTrim( std::string str ) { if ( str.empty() ) { return str; } // left std::string::iterator iter = str.begin(); while ( iter != str.end() && std::isspace( static_cast( *iter ) ) ) { ++iter; } if ( iter == str.end() ) { // Do not erase anything if we reached the end of the string. Just immediately return an empty string. return {}; } if ( iter != str.begin() ) str.erase( str.begin(), iter ); // right iter = str.end() - 1; while ( iter != str.begin() && std::isspace( static_cast( *iter ) ) ) { --iter; } if ( iter != str.end() - 1 ) { str.erase( iter + 1, str.end() ); } return str; } std::string StringLower( std::string str ) { std::transform( str.begin(), str.end(), str.begin(), []( const unsigned char c ) { return static_cast( std::tolower( c ) ); } ); return str; } std::string StringUpper( std::string str ) { std::transform( str.begin(), str.end(), str.begin(), []( const unsigned char c ) { return static_cast( std::toupper( c ) ); } ); return str; } int CountBits( uint32_t val ) { int res = 0; for ( uint32_t itr = 0x00000001; itr; itr <<= 1 ) if ( val & itr ) ++res; return res; } void StringReplaceWithLowercase( std::string & workString, const char * pattern, const std::string & patternReplacement ) { if ( pattern == nullptr ) { return; } for ( size_t position = workString.find( pattern ); position != std::string::npos; position = workString.find( pattern ) ) { // To determine if the end of a sentence was before this word we parse the character before it // for the presence of full stop, question mark, or exclamation mark, skipping whitespace characters. const char prevWordEnd = [&workString, position]() { assert( position < workString.size() ); const auto iter = std::find_if_not( workString.rbegin() + static_cast( workString.size() - position ), workString.rend(), []( const unsigned char c ) { return std::isspace( c ); } ); if ( iter != workString.rend() ) { return *iter; } // Before 'position' there is nothing, or there are only spaces. return '\0'; }(); // Also if the insert 'position' equals zero, then it is the first word in a sentence. if ( position == 0 || prevWordEnd == '.' || prevWordEnd == '?' || prevWordEnd == '!' ) { // Also, 'patternReplacement' can consist of two words (for example, "Power Liches") and if // it is placed as the first word in sentence, then we have to lowercase only the second word. // To detect this, we look for a space mark in 'patternReplacement'. const size_t spacePosition = patternReplacement.find( ' ' ); // The first (and possibly only) word of 'patternReplacement' replaces 'pattern' in 'workString'. workString.replace( position, std::strlen( pattern ), patternReplacement.substr( 0, spacePosition ) ); // Check if a space mark was found to insert the rest part of 'patternReplacement' with lowercase applied. if ( spacePosition != std::string::npos ) { workString.insert( position + spacePosition, Translation::StringLower( patternReplacement.substr( spacePosition ) ) ); } } else { // For all other cases lowercase the 'patternReplacement' and replace the 'pattern' with it in 'workString'. workString.replace( position, std::strlen( pattern ), Translation::StringLower( patternReplacement ) ); } } } void StringReplace( std::string & dst, const char * pred, const std::string & src ) { size_t pos; while ( std::string::npos != ( pos = dst.find( pred ) ) ) { dst.replace( pos, std::strlen( pred ), src ); } } std::vector StringSplit( const std::string & str, const char sep ) { std::vector vec; size_t pos1 = 0; size_t pos2 = 0; while ( pos1 < str.size() ) { pos2 = str.find( sep, pos1 ); if ( pos2 == std::string::npos ) { break; } vec.push_back( str.substr( pos1, pos2 - pos1 ) ); pos1 = pos2 + 1; } // tail if ( pos1 < str.size() ) vec.push_back( str.substr( pos1, str.size() - pos1 ) ); return vec; } std::string insertCharToString( const std::string & inputString, const size_t position, const char character ) { std::string outputString = inputString; if ( position >= inputString.size() ) { outputString.append( 1, character ); } else { outputString.insert( position, 1, character ); } return outputString; } int Sign( int s ) { return ( s < 0 ? -1 : ( s > 0 ? 1 : 0 ) ); } namespace fheroes2 { double GetAngle( const Point & start, const Point & target ) { const int dx = target.x - start.x; const int dy = target.y - start.y; double angle = atan2( -dy, dx ) * 180.0 / M_PI; // we only care about two quadrants, normalize if ( dx < 0 ) { angle = ( dy <= 0 ) ? 180 - angle : -angle - 180; } return angle; } std::vector GetEuclideanLine( const Point & pt1, const Point & pt2, const uint32_t step ) { const int dx = pt2.x - pt1.x; const int dy = pt2.y - pt1.y; const uint32_t dist = static_cast( std::hypot( dx, dy ) ); // Round up the integer division and avoid the division by zero in calculation of total line points. const uint32_t length = ( step > 0 ) ? ( dist + step / 2 ) / step : 0; std::vector line; if ( length < 2 ) { // If the length is equal to 0 than 'pt2' could be closer to 'pt1' than 'step'. // In this case we put 'pt1' as the start of the line. line.emplace_back( pt1 ); // And put 'pt2' as the end of the line only if 'pt1' is not equal to 'pt2'. if ( pt1 != pt2 ) { line.emplace_back( pt2 ); } } else { // Otherwise we calculate the euclidean line, using the determined parameters. const double moveX = dx / static_cast( length ); const double moveY = dy / static_cast( length ); line.reserve( length + 1 ); for ( uint32_t i = 0; i <= length; ++i ) { line.emplace_back( static_cast( pt1.x + i * moveX ), static_cast( pt1.y + i * moveY ) ); } } return line; } std::vector GetLinePoints( const Point & pt1, const Point & pt2, const int32_t step ) { std::vector res; const int32_t dx = std::abs( pt2.x - pt1.x ); const int32_t dy = std::abs( pt2.y - pt1.y ); int32_t ns = ( dx > dy ? dx : dy ) / 2; Point pt( pt1 ); for ( int32_t i = 0; i <= ( dx > dy ? dx : dy ); ++i ) { if ( dx > dy ) { pt.x < pt2.x ? ++pt.x : --pt.x; ns -= dy; } else { pt.y < pt2.y ? ++pt.y : --pt.y; ns -= dx; } if ( ns < 0 ) { if ( dx > dy ) { pt.y < pt2.y ? ++pt.y : --pt.y; ns += dx; } else { pt.x < pt2.x ? ++pt.x : --pt.x; ns += dy; } } if ( 0 == ( i % step ) ) res.push_back( pt ); } return res; } std::vector GetArcPoints( const Point & from, const Point & to, const int32_t arcHeight, const int32_t step ) { std::vector res; Point pt( from ); // The first projectile point is "from" res.push_back( pt ); // Calculate the number of projectile trajectory points const int32_t steps = ( to.x - from.x ) / step; // Trajectory start point coordinates const double x1 = from.x; const double y1 = from.y; // Distance to the destination point along the axes const double dx = to.x - x1; const double dy = to.y - y1; // The movement of the projectile is determined according to the parabolic // throwing approximation. The first two parabola points are "from" and // "to" with an exception that the second ("to") point is at the same // height as the start point. The parabola third point "y" coordinate is // set using the "arcHeight" parameter, which determines the height of the // parabola arc. And its "x" coordinate is taken equal to half the path // from the start point to the end point. Using this three point // coordinates, a system of three linear equations (y=a*x*x+b*x+c) in // three variables is solved by substituting these points "x" and "y". // Considering that on an isometric battlefield, the target location above // or below corresponds to a simple turn of the shooter to the left or // right, a linear movement from point "from" to point "to" is added to the // parabola ('dy/dx' in 'b' constant and '-x1*dy/dx' in 'c' constant). // Calculation of the parabola equation coefficients const double a = 4 * arcHeight / dx / dx; const double b = dy / dx - a * ( dx + 2 * x1 ); const double c = y1 + a * x1 * ( dx + x1 ) - x1 * dy / dx; for ( int32_t i = 1; i <= steps; ++i ) { pt.x += step; pt.y = static_cast( std::lround( a * pt.x * pt.x + b * pt.x + c ) ); res.push_back( pt ); } return res; } int32_t GetRectIndex( const std::vector & rects, const Point & pt ) { for ( size_t i = 0; i < rects.size(); ++i ) { if ( rects[i] & pt ) return static_cast( i ); } return -1; } Rect getBoundaryRect( const Rect & rt1, const Rect & rt2 ) { if ( rt2.width == 0 && rt2.height == 0 ) { return rt1; } if ( rt1.width == 0 && rt1.height == 0 ) { return rt2; } const int32_t x = std::min( rt1.x, rt2.x ); const int32_t y = std::min( rt1.y, rt2.y ); const int32_t width = std::max( rt1.x + rt1.width, rt2.x + rt2.width ) - x; const int32_t height = std::max( rt1.y + rt1.height, rt2.y + rt2.height ) - y; return { x, y, width, height }; } uint32_t calculateCRC32( const uint8_t * data, const size_t length ) { if ( length > std::numeric_limits::max() ) { throw std:: system_error( std::make_error_code( std::errc::value_too_large ), "Too large `length` provided to `calculateCRC32`. Must be no larger than `std::numeric_limits::max()` (usually `(1 << 32) - 1`)." ); } return static_cast( crc32( 0, data, static_cast( length ) ) ); } void replaceStringEnding( std::string & output, const char * originalEnding, const char * correctedEnding ) { assert( originalEnding != nullptr && correctedEnding != nullptr ); const size_t originalEndingSize = strlen( originalEnding ); const size_t correctedEndingSize = strlen( correctedEnding ); if ( output.size() < originalEndingSize ) { // The original string is smaller than the ending. return; } if ( memcmp( output.data() + output.size() - originalEndingSize, originalEnding, originalEndingSize ) != 0 ) { // The string does not have the required ending. return; } output.replace( output.size() - originalEndingSize, originalEndingSize, correctedEnding, correctedEndingSize ); } std::string abbreviateNumber( const int num ) { if ( std::abs( num ) >= 1000000 ) { return std::to_string( num / 1000000 ) + 'M'; } if ( std::abs( num ) >= 1000 ) { return std::to_string( num / 1000 ) + 'K'; } return std::to_string( num ); } } fheroes2-1.0.12+dfsg/src/engine/tools.h000066400000000000000000000137021456075706000176110ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2TOOLS_H #define H2TOOLS_H #include #include #include #include #include #include #include #include #include #include #include #include "math_base.h" template ::value>::type> std::string GetHexString( T value, int width = 8 ) { std::ostringstream stream; stream << "0x" << std::setw( width ) << std::setfill( '0' ) << std::hex << value; return stream.str(); } int Sign( int ); std::string StringTrim( std::string ); std::string StringLower( std::string str ); std::string StringUpper( std::string str ); std::vector StringSplit( const std::string & str, const char sep ); // Function to replace the pattern in workString with patternReplacement. Here the patternReplacement is converted to lowercase except for the first word in a sentence. void StringReplaceWithLowercase( std::string & workString, const char * pattern, const std::string & patternReplacement ); void StringReplace( std::string & dst, const char * pred, const std::string & src ); template ::value>::type> void StringReplace( std::string & dst, const char * pred, const T value ) { StringReplace( dst, pred, std::to_string( value ) ); } int CountBits( uint32_t ); // Returns a new text string with the inserted character in the input string at the specified position. std::string insertCharToString( const std::string & inputString, const size_t position, const char character ); namespace fheroes2 { double GetAngle( const Point & start, const Point & target ); std::vector GetEuclideanLine( const Point & pt1, const Point & pt2, const uint32_t step ); std::vector GetLinePoints( const Point & pt1, const Point & pt2, const int32_t step ); std::vector GetArcPoints( const Point & from, const Point & to, const int32_t arcHeight, const int32_t step ); int32_t GetRectIndex( const std::vector & rects, const Point & pt ); Rect getBoundaryRect( const Rect & rt1, const Rect & rt2 ); uint32_t calculateCRC32( const uint8_t * data, const size_t length ); template void hashCombine( uint32_t & seed, const T & v ) { std::hash hasher; seed ^= hasher( v ) + 0x9e3779b9 + ( seed << 6 ) + ( seed >> 2 ); } template std::bitset makeBitsetFromVector( const std::vector & vector ) { std::bitset result; for ( const int index : vector ) { result.set( index, true ); } return result; } void replaceStringEnding( std::string & output, const char * originalEnding, const char * correctedEnding ); std::string abbreviateNumber( const int num ); // Performs a checked conversion of an integer value of type From to an integer type To. Returns an empty std::optional if // the source value does not fit into the target type. template && std::is_integral_v>> constexpr std::optional checkedCast( const From from ) { // Both types have the same signedness if constexpr ( std::is_signed_v == std::is_signed_v ) { if ( from < std::numeric_limits::min() || from > std::numeric_limits::max() ) { return {}; } return static_cast( from ); } // From is signed, To is unsigned else if constexpr ( std::is_signed_v ) { if ( from < 0 ) { return {}; } const std::make_unsigned_t unsignedFrom = from; if ( unsignedFrom > std::numeric_limits::max() ) { return {}; } return static_cast( from ); } // From is unsigned, To is signed else { constexpr To maxTo = std::numeric_limits::max(); static_assert( maxTo >= 0 ); constexpr std::make_unsigned_t unsignedMaxTo = maxTo; if ( from > unsignedMaxTo ) { return {}; } return static_cast( from ); } } } #endif fheroes2-1.0.12+dfsg/src/engine/translations.cpp000066400000000000000000000541171456075706000215320ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "translations.h" #include #include #include #include #include #include #include #include #include #include #include "logging.h" #include "serialize.h" #include "tools.h" namespace { const char contextSeparator = '|'; // Character lookup table for custom tolower // Compatible with ASCII, custom French encoding, CP1250 and CP1251 const std::array tolowerLUT = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, // Non-printable characters 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, // SP ! " # $ % & ' ( ) * + , - . / (ASCII) // SP ! " ô û % ù ' ( ) â + , - . / (French, #$&* are ôûùâ) ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', // 0 1 2 3 4 5 6 7 8 9 : ; < = > ? (ASCII) // 0 1 2 3 4 5 6 7 8 9 : ; ï = î ? (French, <> are ïî) '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', // @ A→a B→b C→c D→d E→e F→f G→g H→h I→i J→j K→k L→l M→m N→n O→o (ASCII) // à A→a B→b C→c D→d E→e F→f G→g H→h I→i J→j K→k L→l M→m N→n O→o (French, @ is à) '@', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', // P→p Q→q R→r S→s T→t U→u V→v W→w X→x Y→y Z→z [ \ ] ^ _ (ASCII) // P→p Q→q R→r S→s T→t U→u V→v W→w X→x Y→y Z→z [ \ ] ç _ (French, ^ is ç) 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '[', '\\', ']', '^', '_', // ` a b c d e f g h i j k l m n o (ASCII) // è a b c d e f g h i j k l m n o (French, ` is è) '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', // p q r s t u v w x y z { | } ~ DEL (ASCII) // p q r s t u v w x y z { ê } é DEL (French, |~ are êé) 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', 0x7F, // € ‚ „ … † ‡ ‰ Š→š ‹ Ś→ś Ť→ť Ž→ž Ź→ź (1250, except €) // Ђ→ђ Ѓ→ѓ ‚ Ñ“ „ … † ‡ € ‰ Љ→љ ‹ Њ→њ Ќ→ќ Ћ→ћ Ð→џ (1251) 0x90, 0x83, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x9A, 0x8B, 0x9C, 0x9D, 0x9E, 0x9F, // ‘ ’ “ †• – — â„¢ Å¡ › Å› Å¥ ž ź (1250) // Ñ’ ‘ ’ “ †• – — â„¢ Ñ™ › Ñš Ñœ Ñ› ÑŸ (1251) 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, // NBS ˇ ˘ Å→ł ¤ Ą→ą ¦ § ¨ © Ş→ş « ¬ SHY ® Ż→ż (1250 except ˇ¨) // NBP Ў→ў Ñž Ј ¤ Ò Â¦ § Ð→ё © Є→є « ¬ SHY ® Ї→ї (1251 except ЈÒ) 0xA0, 0xA2, 0xA2, 0xB3, 0xA4, 0xB9, 0xA6, 0xA7, 0xB8, 0xA9, 0xBA, 0xAB, 0xAC, 0xAD, 0xAE, 0xBF, // ° ± Ë› Å‚ ´ µ ¶ · ¸ Ä… ÅŸ » Ľ→ľ Ë Ä¾ ż (1250 except Ë›Ë) // ° ± І→і Ñ– Ò‘ µ ¶ · Ñ‘ â„– Ñ” » ј Ѕ→ѕ Ñ• Ñ— (1251 except ј) 0xB0, 0xB1, 0xB3, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBE, 0xBE, 0xBE, 0xBF, // Ŕ→ŕ Ã→á Â→â Ă→ă Ä→ä Ĺ→ĺ Ć→ć Ç→ç ÄŒâ†’Ä Ã‰â†’Ã© Ę→ę Ë→ë Ě→ě Ã→í Î→î ÄŽâ†’Ä (1250) // Ð→а Б→б В→в Г→г Д→д Е→е Ж→ж З→з И→и Й→й К→к Л→л М→м Ð→н О→о П→п (1251) 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, // Ä→đ Ń→ń Ň→ň Ó→ó Ô→ô Å→ő Ö→ö × Ř→ř Ů→ů Ú→ú Ű→ű Ü→ü Ã→ý Ţ→ţ ß (1250, except ×ß) // Р→р Ð¡â†’Ñ Ð¢â†’Ñ‚ У→у Ф→ф Х→х Ц→ц Ч→ч Ш→ш Щ→щ Ъ→ъ Ы→ы Ь→ь Ð­â†’Ñ Ð®â†’ÑŽ Ð¯â†’Ñ (1251) 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, // Å• á â ă ä ĺ ć ç Ä Ã© Ä™ ë Ä› í î Ä (1250) // а б в г д е ж з и й к л м н о п (1251) 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, // Ä‘ Å„ ň ó ô Å‘ ö ÷ Å™ ů ú ű ü ý Å£ Ë™ (1250) // Ñ€ Ñ Ñ‚ у Ñ„ Ñ… ц ч ш щ ÑŠ Ñ‹ ÑŒ Ñ ÑŽ Ñ (1251) 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF }; enum class LocaleType : int { LOCALE_EN, LOCALE_AF, LOCALE_AR, LOCALE_BE, LOCALE_BG, LOCALE_CA, LOCALE_CS, LOCALE_DE, LOCALE_DK, LOCALE_EL, LOCALE_ES, LOCALE_ET, LOCALE_EU, LOCALE_FI, LOCALE_FR, LOCALE_GL, LOCALE_HE, LOCALE_HR, LOCALE_HU, LOCALE_ID, LOCALE_IT, LOCALE_LA, LOCALE_LT, LOCALE_LV, LOCALE_MK, LOCALE_NB, LOCALE_NL, LOCALE_PL, LOCALE_PT, LOCALE_RO, LOCALE_RU, LOCALE_SK, LOCALE_SL, LOCALE_SR, LOCALE_SV, LOCALE_TR, LOCALE_UK }; struct Chunk { uint32_t offset; uint32_t length; Chunk( const uint32_t off, const uint32_t len ) : offset( off ) , length( len ) { // Do nothing. } }; uint32_t crc32b( const char * msg ) { uint32_t crc = 0xFFFFFFFF; uint32_t index = 0; while ( msg[index] ) { crc ^= static_cast( msg[index] ); for ( int bit = 0; bit < 8; ++bit ) { const uint32_t poly = ( crc & 1 ) ? 0xEDB88320 : 0x0; crc = ( crc >> 1 ) ^ poly; } ++index; } return ~crc; } std::string getTag( const std::string & str, const std::string & tag, const std::string & sep ) { if ( str.size() > tag.size() && str.rfind( tag, 0 ) == 0 ) { const size_t pos = str.find( sep ); if ( pos != std::string::npos ) { return str.substr( pos + sep.size() ); } } return {}; } const char * stripContext( const char * str ) { const char * pos = std::strchr( str, contextSeparator ); return pos ? ++pos : str; } struct MOFile { // TODO: plural forms are not in use: Plural-Forms. LocaleType locale{ LocaleType::LOCALE_EN }; StreamBuf buf; std::map hash_offsets; std::string domain; std::string encoding; const char * ngettext( const char * str, size_t plural ) { std::map::const_iterator it = hash_offsets.find( crc32b( str ) ); if ( it == hash_offsets.end() ) return stripContext( str ); buf.seek( ( *it ).second.offset ); const uint8_t * ptr = buf.data(); while ( plural > 0 ) { while ( *ptr ) { ++ptr; } --plural; ++ptr; } return reinterpret_cast( ptr ); } bool open( const std::string & file ) { StreamFile sf; if ( !sf.open( file, "rb" ) ) { return false; } const size_t fileSize = sf.size(); uint32_t magicNumber = 0; sf >> magicNumber; if ( magicNumber != 0x950412de ) { ERROR_LOG( "Incorrect magic number " << GetHexString( magicNumber ) << " for " << file ) return false; } uint16_t majorVersion; uint16_t minorVersion; sf >> majorVersion >> minorVersion; if ( 0 != majorVersion ) { ERROR_LOG( "Incorrect major version " << GetHexString( majorVersion, 4 ) << " for " << file ) return false; } uint32_t originalOffset = 0; uint32_t translationOffset = 0; uint32_t stringCount = 0; // TODO: validate these values. uint32_t hashSize = 0; uint32_t hashOffset = 0; sf >> stringCount >> originalOffset >> translationOffset >> hashSize >> hashOffset; sf.seek( 0 ); buf = sf.toStreamBuf( fileSize ); sf.close(); // Parse encoding. if ( stringCount > 0 ) { buf.seek( translationOffset ); const uint32_t length2 = buf.get32(); const uint32_t offset2 = buf.get32(); buf.seek( offset2 ); const std::vector tags = StringSplit( buf.toString( length2 ), '\n' ); for ( const std::string & tag : tags ) { if ( encoding.empty() ) { encoding = getTag( tag, "Content-Type", "charset=" ); } // TODO: parse plural form information here and use it in the code. } } uint32_t totalTranslationStrings = stringCount; for ( uint32_t index = 0; index < stringCount; ++index ) { buf.seek( originalOffset + index * 8 ); const uint32_t length1 = buf.get32(); if ( length1 == 0 ) { // This is an empty original text. Skip it. --totalTranslationStrings; continue; } const uint32_t offset1 = buf.get32(); buf.seek( offset1 ); const std::string msg1 = buf.toString( length1 ); const uint32_t crc = crc32b( msg1.data() ); buf.seek( translationOffset + index * 8 ); const uint32_t length2 = buf.get32(); if ( length2 == 0 ) { // This is an empty translation. Skip it. --totalTranslationStrings; continue; } const uint32_t offset2 = buf.get32(); const auto [dummy, inserted] = hash_offsets.try_emplace( crc, Chunk{ offset2, length2 } ); if ( !inserted ) { ERROR_LOG( "Clashing hash value for: " << msg1 ) } } return ( totalTranslationStrings > 0 ); } }; MOFile * current = nullptr; std::map domains; } namespace Translation { bool bindDomain( const char * domain, const char * file ) { std::string str( domain ); // Search for already loaded domain or load from file std::map::iterator it = domains.find( str ); if ( it != domains.end() ) { current = &( *it ).second; return true; } if ( !domains[str].open( file ) ) return false; current = &domains[str]; // Update locale current->domain = str; if ( str == "af" || str == "afrikaans" ) current->locale = LocaleType::LOCALE_AF; else if ( str == "ar" || str == "arabic" ) current->locale = LocaleType::LOCALE_AR; else if ( str == "be" || str == "belarusian" ) current->locale = LocaleType::LOCALE_BE; else if ( str == "bg" || str == "bulgarian" ) current->locale = LocaleType::LOCALE_BG; else if ( str == "ca" || str == "catalan" ) current->locale = LocaleType::LOCALE_CA; else if ( str == "dk" || str == "danish" ) current->locale = LocaleType::LOCALE_DK; else if ( str == "de" || str == "german" ) current->locale = LocaleType::LOCALE_DE; else if ( str == "el" || str == "greek" ) current->locale = LocaleType::LOCALE_EL; else if ( str == "es" || str == "spanish" ) current->locale = LocaleType::LOCALE_ES; else if ( str == "et" || str == "estonian" ) current->locale = LocaleType::LOCALE_ET; else if ( str == "eu" || str == "basque" ) current->locale = LocaleType::LOCALE_EU; else if ( str == "fi" || str == "finnish" ) current->locale = LocaleType::LOCALE_FI; else if ( str == "fr" || str == "french" ) current->locale = LocaleType::LOCALE_FR; else if ( str == "gl" || str == "galician" ) current->locale = LocaleType::LOCALE_GL; else if ( str == "he" || str == "hebrew" ) current->locale = LocaleType::LOCALE_HE; else if ( str == "hr" || str == "croatian" ) current->locale = LocaleType::LOCALE_HR; else if ( str == "hu" || str == "hungarian" ) current->locale = LocaleType::LOCALE_HU; else if ( str == "id" || str == "indonesian" ) current->locale = LocaleType::LOCALE_ID; else if ( str == "it" || str == "italian" ) current->locale = LocaleType::LOCALE_IT; else if ( str == "la" || str == "latin" ) current->locale = LocaleType::LOCALE_LA; else if ( str == "lt" || str == "lithuanian" ) current->locale = LocaleType::LOCALE_LT; else if ( str == "lv" || str == "latvian" ) current->locale = LocaleType::LOCALE_LV; else if ( str == "mk" || str == "macedonia" ) current->locale = LocaleType::LOCALE_MK; else if ( str == "nb" || str == "norwegian" ) current->locale = LocaleType::LOCALE_NB; else if ( str == "nl" || str == "dutch" ) current->locale = LocaleType::LOCALE_NL; else if ( str == "pl" || str == "polish" ) current->locale = LocaleType::LOCALE_PL; else if ( str == "pt" || str == "portuguese" ) current->locale = LocaleType::LOCALE_PT; else if ( str == "ro" || str == "romanian" ) current->locale = LocaleType::LOCALE_RO; else if ( str == "ru" || str == "russian" ) current->locale = LocaleType::LOCALE_RU; else if ( str == "sk" || str == "slovak" ) current->locale = LocaleType::LOCALE_SK; else if ( str == "sl" || str == "slovenian" ) current->locale = LocaleType::LOCALE_SL; else if ( str == "sr" || str == "serbian" ) current->locale = LocaleType::LOCALE_SR; else if ( str == "sv" || str == "swedish" ) current->locale = LocaleType::LOCALE_SV; else if ( str == "tr" || str == "turkish" ) current->locale = LocaleType::LOCALE_TR; else if ( str == "uk" || str == "ukrainian" ) current->locale = LocaleType::LOCALE_UK; return true; } void reset() { current = nullptr; } const char * gettext( const std::string & str ) { const char * data = str.data(); return current ? current->ngettext( data, 0 ) : stripContext( data ); } const char * gettext( const char * str ) { return current ? current->ngettext( str, 0 ) : stripContext( str ); } const char * ngettext( const char * str, const char * plural, size_t n ) { if ( current ) switch ( current->locale ) { case LocaleType::LOCALE_AF: case LocaleType::LOCALE_BG: case LocaleType::LOCALE_DE: case LocaleType::LOCALE_DK: case LocaleType::LOCALE_ES: case LocaleType::LOCALE_ET: case LocaleType::LOCALE_EU: case LocaleType::LOCALE_FI: case LocaleType::LOCALE_GL: case LocaleType::LOCALE_HE: case LocaleType::LOCALE_HU: case LocaleType::LOCALE_ID: case LocaleType::LOCALE_IT: case LocaleType::LOCALE_LA: case LocaleType::LOCALE_NB: case LocaleType::LOCALE_NL: case LocaleType::LOCALE_SV: case LocaleType::LOCALE_TR: return current->ngettext( str, ( n != 1 ) ); case LocaleType::LOCALE_EL: case LocaleType::LOCALE_FR: case LocaleType::LOCALE_PT: return current->ngettext( str, ( n > 1 ) ); case LocaleType::LOCALE_AR: return current->ngettext( str, ( n == 0 ? 0 : n == 1 ? 1 : n == 2 ? 2 : n % 100 >= 3 && n % 100 <= 10 ? 3 : n % 100 >= 11 && n % 100 <= 99 ? 4 : 5 ) ); case LocaleType::LOCALE_RO: return current->ngettext( str, ( n == 1 ? 0 : n == 0 || ( n != 1 && n % 100 >= 1 && n % 100 <= 19 ) ? 1 : 2 ) ); case LocaleType::LOCALE_SL: return current->ngettext( str, ( n % 100 == 1 ? 0 : n % 100 == 2 ? 1 : n % 100 == 3 || n % 100 == 4 ? 2 : 3 ) ); case LocaleType::LOCALE_SR: return current->ngettext( str, ( n == 1 ? 3 : n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && ( n % 100 < 10 || n % 100 >= 20 ) ? 1 : 2 ) ); case LocaleType::LOCALE_CS: case LocaleType::LOCALE_SK: return current->ngettext( str, ( ( n == 1 ) ? 0 : ( n >= 2 && n <= 4 ) ? 1 : 2 ) ); case LocaleType::LOCALE_HR: case LocaleType::LOCALE_LV: case LocaleType::LOCALE_RU: return current->ngettext( str, ( n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && ( n % 100 < 10 || n % 100 >= 20 ) ? 1 : 2 ) ); case LocaleType::LOCALE_LT: return current->ngettext( str, ( n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && ( n % 100 < 10 || n % 100 >= 20 ) ? 1 : 2 ) ); case LocaleType::LOCALE_MK: return current->ngettext( str, ( n == 1 || n % 10 == 1 ? 0 : 1 ) ); case LocaleType::LOCALE_PL: return current->ngettext( str, ( n == 1 ? 0 : n % 10 >= 2 && n % 10 <= 4 && ( n % 100 < 10 || n % 100 >= 20 ) ? 1 : 2 ) ); case LocaleType::LOCALE_BE: case LocaleType::LOCALE_UK: return current->ngettext( str, ( n % 10 == 1 && n % 100 != 11 ? 0 : n % 10 >= 2 && n % 10 <= 4 && ( n % 100 < 12 || n % 100 > 14 ) ? 1 : 2 ) ); default: break; } return stripContext( n == 1 ? str : plural ); } std::string StringLower( std::string str ) { if ( current ) { // With German, lowercasing strings does more harm than good if ( current->locale == LocaleType::LOCALE_DE ) return str; // For CP1250/1251 codepages a custom lowercase LUT is implemented if ( ( current->encoding == "CP1250" ) || ( current->encoding == "CP1251" ) ) { std::transform( str.begin(), str.end(), str.begin(), []( const unsigned char c ) { return tolowerLUT[c]; } ); return str; } } return ::StringLower( str ); } } fheroes2-1.0.12+dfsg/src/engine/translations.h000066400000000000000000000044731456075706000211770ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2TRANSLATIONS_H #define H2TRANSLATIONS_H #include #include namespace Translation { bool bindDomain( const char * domain, const char * file ); // Reset any translation to the default language - English. void reset(); const char * gettext( const char * str ); const char * gettext( const std::string & str ); const char * ngettext( const char * str, const char * plural, size_t num ); std::string StringLower( std::string str ); } #define _( s ) Translation::gettext( s ) #define _n( a, b, c ) Translation::ngettext( a, b, c ) constexpr const char * gettext_noop( const char * s ) { return s; } #endif fheroes2-1.0.12+dfsg/src/engine/xmi2mid.cpp000066400000000000000000000533721456075706000203640ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2013 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include "audio.h" #include "logging.h" #include "serialize.h" #include "tools.h" // The original MIDI files are stored in XMI format which is not readable by SDL. You can read about this format here: https://moddingwiki.shikadi.net/wiki/XMI_Format // A conversion from XMI to MID files is required before playing files. // MIDI format is described here: http://www.music.mcgill.ca/~ich/classes/mumt306/StandardMIDIfileformat.html and here: // https://en.wikipedia.org/wiki/General_MIDI namespace { const std::array instrumentDescription = { // // Piano ( 0 - 7 ) // "Acoustic Grand Piano", "Bright Acoustic Piano", "Electric Grand Piano", "Honky-tonk Piano", "Electric Piano 1 (Rhodes Piano)", "Electric Piano 2 (Chorused Piano)", "Harpsichord", "Clavinet", // // Chromatic Percussion ( 8 - 15 ) // "Celesta", "Glockenspiel", "Music Box", "Vibraphone", "Marimba", "Xylophone", "Tubular Bells", "Dulcimer (Santur)", // // Organ ( 16 - 23 ) // "Drawbar Organ (Hammond)", "Percussive Organ", "Rock Organ", "Church Organ", "Reed Organ", "Accordion (French)", "Harmonica", "Tango Accordion (Band neon)", // // Guitar ( 24 - 31 ) // "Acoustic Guitar (nylon)", "Acoustic Guitar (steel)", "Electric Guitar (jazz)", "Electric Guitar (clean)", "Electric Guitar (muted)", "Overdriven Guitar", "Distortion Guitar", "Guitar harmonics", // // Bass ( 32 - 39 ) // "Acoustic Bass", "Electric Bass (fingered)", "Electric Bass (picked)", "Fretless Bass", "Slap Bass 1", "Slap Bass 2", "Synth Bass 1", "Synth Bass 2", // // Strings ( 40 - 47 ) // "Violin", "Viola", "Cello", "Contrabass", "Tremolo Strings", "Pizzicato Strings", "Orchestral Harp", "Timpani", // // Strings ( 48 - 55 ) // "String Ensemble 1 (strings)", "String Ensemble 2 (slow strings)", "SynthStrings 1", "SynthStrings 2", "Choir Aahs", "Voice Oohs", "Synth Voice", "Orchestra Hit", // // Brass ( 56 - 63 ) // "Trumpet", "Trombone", "Tuba", "Muted Trumpet", "French Horn", "Brass Section", "SynthBrass 1", "SynthBrass 2", // // Reed ( 64 - 71 ) // "Soprano Sax", "Alto Sax", "Tenor Sax", "Baritone Sax", "Oboe", "English Horn", "Bassoon", "Clarinet", // // Pipe ( 72 - 79 ) // "Piccolo", "Flute", "Recorder", "Pan Flute", "Blown Bottle", "Shakuhachi", "Whistle", "Ocarina", // // Synth Lead ( 80 - 87 ) // "Lead 1 (square wave)", "Lead 2 (sawtooth wave)", "Lead 3 (calliope)", "Lead 4 (chiffer)", "Lead 5 (charang)", "Lead 6 (voice solo)", "Lead 7 (fifths)", "Lead 8 (bass + lead)", // // Synth Pad ( 88 - 95 ) // "Pad 1 (new age Fantasia)", "Pad 2 (warm)", "Pad 3 (polysynth)", "Pad 4 (choir space voice)", "Pad 5 (bowed glass)", "Pad 6 (metallic pro)", "Pad 7 (halo)", "Pad 8 (sweep)", // // Synth Effects ( 96 - 103 ) // "FX 1 (rain)", "FX 2 (soundtrack)", "FX 3 (crystal)", "FX 4 (atmosphere)", "FX 5 (brightness)", "FX 6 (goblins)", "FX 7 (echoes, drops)", "FX 8 (sci-fi, star theme)", // // Ethnic ( 104 - 111 ) // "Sitar", "Banjo", "Shamisen", "Koto", "Kalimba", "Bag pipe", "Fiddle", "Shanai", // // Percussive ( 112 - 119 ) // "Tinkle Bell", "Agogo", "Steel Drums", "Woodblock", "Taiko Drum", "Melodic Tom", "Synth Drum", "Reverse Cymbal", // // Sound effects ( 120 - 127 ) // "Guitar Fret Noise", "Breath Noise", "Seashore", "Bird Tweet", "Telephone Ring", "Helicopter", "Applause", "Gunshot" }; // Some (but not all) commonly used MIDI drum kits are listed here: https://en.wikipedia.org/wiki/General_MIDI_Level_2#Drum_sounds const std::map drumKitDescription = { { 0, "Standard Kit" }, { 8, "Room Kit" }, { 16, "Power Kit" }, { 24, "Electronic Kit" }, { 25, "TR-808 Kit" }, { 32, "Jazz Kit" }, { 40, "Brush Kit" }, { 48, "Orchestral Kit" }, { 49, "Fix Room Kit" }, { 56, "Sound FX Kit" } }; enum { TAG_FORM = 0x464F524D, TAG_XDIR = 0x58444952, TAG_INFO = 0x494E464F, TAG_CAT0 = 0x43415420, TAG_XMID = 0x584D4944, TAG_TIMB = 0x54494D42, TAG_EVNT = 0x45564E54, TAG_RBRN = 0x5242524E, TAG_MTHD = 0x4D546864, TAG_MTRK = 0x4D54726B }; // Some numbers in MIDI Files are represented in a form called VARIABLE-LENGTH QUANTITY. struct VariableLengthQuantity { uint32_t value{ 0 }; uint32_t lengthInBytes{ 0 }; }; bool readVariableLengthQuantity( const uint8_t * data, const uint8_t * dataEnd, VariableLengthQuantity & quantity ) { quantity = {}; const uint8_t * p = data; while ( p < dataEnd && ( *p & 0x80 ) != 0 ) { if ( 4 <= p - data ) { // The largest number to read is 4 bytes. ERROR_LOG( "XMI format: the field is bigger than 4 bytes." ) return false; } quantity.value |= 0x0000007F & *p; quantity.value <<= 7; ++p; } if ( p < dataEnd ) { quantity.value += *p; } quantity.lengthInBytes = static_cast( p - data ) + 1; // it's safe to cast since p is always bigger or equal to data return true; } std::vector packVariableLengthQuantity( const uint32_t delta ) { const uint8_t c1 = delta & 0x0000007F; const uint8_t c2 = ( ( delta & 0x00003F80 ) >> 7 ) & 0xFF; const uint8_t c3 = ( ( delta & 0x001FC000 ) >> 14 ) & 0xFF; const uint8_t c4 = ( ( delta & 0x0FE00000 ) >> 21 ) & 0xFF; std::vector res; res.reserve( 4 ); if ( c4 ) { res.push_back( c4 | 0x80 ); res.push_back( c3 | 0x80 ); res.push_back( c2 | 0x80 ); res.push_back( c1 ); } else if ( c3 ) { res.push_back( c3 | 0x80 ); res.push_back( c2 | 0x80 ); res.push_back( c1 ); } else if ( c2 ) { res.push_back( c2 | 0x80 ); res.push_back( c1 ); } else { res.push_back( c1 ); } return res; } } struct IFFChunkHeader { uint32_t ID; // 4 upper case ASCII chars, padded with 0x20 (space) uint32_t length; // big-endian IFFChunkHeader( uint32_t id, uint32_t sz ) : ID( id ) , length( sz ) {} IFFChunkHeader() : ID( 0 ) , length( 0 ) {} }; StreamBuf & operator>>( StreamBuf & sb, IFFChunkHeader & st ) { st.ID = sb.getBE32(); st.length = sb.getBE32(); return sb; } StreamBuf & operator<<( StreamBuf & sb, const IFFChunkHeader & st ) { sb.putBE32( st.ID ); sb.putBE32( st.length ); return sb; } struct GroupChunkHeader { uint32_t ID{ 0 }; // 4 byte ASCII string, either 'FORM', 'CAT ' or 'LIST' uint32_t length{ 0 }; uint32_t type{ 0 }; // 4 byte ASCII string }; StreamBuf & operator>>( StreamBuf & sb, GroupChunkHeader & st ) { st.ID = sb.getBE32(); st.length = sb.getBE32(); st.type = sb.getBE32(); return sb; } struct XMITrack { std::vector timb; std::vector evnt; }; using XMITracks = std::list; struct XMIData { XMITracks tracks; explicit XMIData( const std::vector & buf ) { // Please refer to https://moddingwiki.shikadi.net/wiki/XMI_Format#File_format StreamBuf sb( buf ); GroupChunkHeader group; sb >> group; if ( group.ID != TAG_FORM || group.type != TAG_XDIR ) { ERROR_LOG( "XMI parsing: invalid IFF root chunk 1 (FORM:XDIR)" ) return; } IFFChunkHeader iff; sb >> iff; if ( iff.ID != TAG_INFO || iff.length != 2 ) { ERROR_LOG( "XMI parsing: expected TAG_INFO of length 2" ) return; } const int numTracks = sb.getLE16(); if ( numTracks <= 0 ) { ERROR_LOG( "XMI parsing: the number of sequences cannot be less than 1" ) return; } // CAT XMID sb >> group; if ( group.ID != TAG_CAT0 || group.type != TAG_XMID ) { ERROR_LOG( "XMI parsing: invalid IFF root chunk 2 (CAT :XMID)" ) return; } for ( int track = 0; track < numTracks; ++track ) { tracks.emplace_back(); std::vector & timb = tracks.back().timb; std::vector & evnt = tracks.back().evnt; sb >> group; // FORM XMID if ( group.ID != TAG_FORM || group.type != TAG_XMID ) { ERROR_LOG( "XMI parsing: invalid form type (FORM:XMID)" ) return; } sb >> iff; // Read TIMB cbhunk. if ( iff.ID == TAG_TIMB ) { timb = sb.getRaw( iff.length ); if ( timb.size() != iff.length ) { ERROR_LOG( "parse error: " << "out of range" ) break; } sb >> iff; } // [RBRN] if ( iff.ID == TAG_RBRN ) { sb.skip( iff.length ); sb >> iff; } // EVNT if ( iff.ID != TAG_EVNT ) { ERROR_LOG( "parse error: " << "evnt" ) break; } evnt = sb.getRaw( iff.length ); if ( evnt.size() != iff.length ) { ERROR_LOG( "parse error: " << "out of range" ) break; } } } bool isvalid() const { return !tracks.empty(); } }; struct MidiChunk { uint32_t _time; uint8_t _type; std::vector _binaryTime; std::vector _data; MidiChunk( uint32_t time, uint8_t type, uint8_t data1 ) { _time = time; _type = type; _binaryTime = packVariableLengthQuantity( time ); _data.push_back( data1 ); } MidiChunk( uint32_t time, uint8_t type, uint8_t data1, uint8_t data2 ) { _time = time; _type = type; _binaryTime = packVariableLengthQuantity( time ); _data.push_back( data1 ); _data.push_back( data2 ); } MidiChunk( uint32_t time, uint8_t meta, uint8_t subType, const uint8_t * ptr, uint8_t metaLength ) { _time = time; _type = meta; _binaryTime = packVariableLengthQuantity( time ); _data.push_back( subType ); _data.push_back( metaLength ); for ( uint8_t i = 0; i < metaLength; ++i ) { _data.push_back( *( ptr + i ) ); } } size_t size() const { return _binaryTime.size() + 1 + _data.size(); } }; static bool operator<( const MidiChunk & left, const MidiChunk & right ) { return left._time < right._time; } StreamBuf & operator<<( StreamBuf & sb, const MidiChunk & event ) { for ( std::vector::const_iterator it = event._binaryTime.begin(); it != event._binaryTime.end(); ++it ) sb << *it; sb << event._type; for ( std::vector::const_iterator it = event._data.begin(); it != event._data.end(); ++it ) sb << *it; return sb; } struct MidiEvents : public std::vector { uint32_t trackTempo = 0; size_t size() const { size_t res = 0; for ( const_iterator it = begin(); it != end(); ++it ) res += ( *it ).size(); return res; } MidiEvents() = default; bool checkDataPresence( const uint8_t * ptr, const uint8_t * end, const int32_t requiredLength ) { assert( requiredLength > 0 ); if ( end - ptr < requiredLength ) { emplace_back( 0, static_cast( 0xFF ), static_cast( 0x2F ), static_cast( 0x00 ) ); ERROR_LOG( "MIDI track: the data is truncated." ) return false; } return true; } explicit MidiEvents( const XMITrack & t ) { const uint8_t * ptr = &t.evnt[0]; const uint8_t * end = ptr + t.evnt.size(); uint32_t delta = 0; while ( ptr && ptr < end ) { // XMI delay is 7 bit values summed together if ( *ptr < 128 ) { delta += *ptr; ++ptr; continue; } if ( *ptr == 0xFF ) { if ( !checkDataPresence( ptr, end, 3 ) ) { break; } if ( *( ptr + 1 ) == 0x2F ) { if ( *( ptr + 2 ) != 0x00 ) { ERROR_LOG( "MIDI track: End of Track sequence is incorrect." ) } emplace_back( delta, *ptr, *( ptr + 1 ), static_cast( 0x00 ) ); break; } // Meta-Event, always starts from 0xFF. ++ptr; // skip 0xFF const uint8_t metaType = *( ptr++ ); const uint8_t metaLength = *( ptr++ ); if ( !checkDataPresence( ptr, end, metaLength ) ) { break; } emplace_back( delta, static_cast( 0xFF ), metaType, ptr, metaLength ); // Tempo switch if ( metaType == 0x51 && metaLength == 3 ) { // 24-bit big endian trackTempo = ( ( ( *ptr << 8 ) | *( ptr + 1 ) ) << 8 ) | *( ptr + 2 ); } ptr += metaLength; continue; } switch ( *ptr >> 4 ) { // Polyphonic Key Pressure (Aftertouch). case 0x0A: // Control Change. case 0x0B: // Pitch Wheel Change. case 0x0E: if ( !checkDataPresence( ptr, end, 3 ) ) { break; } emplace_back( delta, *ptr, *( ptr + 1 ), *( ptr + 2 ) ); ptr += 3; break; // XMI events do not have note off events. // Note On event. case 0x09: { if ( !checkDataPresence( ptr, end, 4 ) ) { break; } emplace_back( delta, *ptr, *( ptr + 1 ), *( ptr + 2 ) ); VariableLengthQuantity quantity; if ( !readVariableLengthQuantity( ptr + 3, end, quantity ) ) { break; } // note off emplace_back( delta + quantity.value, static_cast( *ptr - 0x10 ), *( ptr + 1 ), static_cast( 0x7F ) ); ptr += 3 + quantity.lengthInBytes; break; } // Program Change: in other words which instrument is going to be played. case 0x0C: { if ( !checkDataPresence( ptr, end, 2 ) ) { break; } const int32_t channelId = *ptr - 0xC0; emplace_back( delta, *ptr, *( ptr + 1 ) ); // Drum sounds are only played in channel 9 if channel ID starts from 0, or 10 if channel ID starts from 1. In our case it starts from 0. if ( channelId == 9 ) { // It is a drum kit. const uint32_t drumKitId = *( ptr + 1 ); const auto drumKitIter = drumKitDescription.find( drumKitId ); if ( drumKitIter != drumKitDescription.end() ) { DEBUG_LOG( DBG_ENGINE, DBG_TRACE, "MIDI channel " << channelId << ", drum kit ID " << drumKitId << ": " << drumKitIter->second ) } else { DEBUG_LOG( DBG_ENGINE, DBG_TRACE, "MIDI channel " << channelId << ": unknown drum kit ID " << drumKitId ) } } else { const uint32_t instrumentId = *( ptr + 1 ); if ( instrumentId < instrumentDescription.size() ) { DEBUG_LOG( DBG_ENGINE, DBG_TRACE, "MIDI channel " << channelId << ", instrument ID " << instrumentId << ": " << instrumentDescription[instrumentId] ) } else { ERROR_LOG( "MIDI channel " << channelId << ": unknown instrument ID " << instrumentId ) } } ptr += 2; break; } // Channel Pressure (After-touch). case 0x0D: if ( !checkDataPresence( ptr, end, 2 ) ) { break; } emplace_back( delta, *ptr, *( ptr + 1 ) ); ptr += 2; break; // Unknown command. default: emplace_back( 0, static_cast( 0xFF ), static_cast( 0x2F ), static_cast( 0x00 ) ); ERROR_LOG( "MIDI track: Unknown command: " << GetHexString( static_cast( *ptr ), 2 ) << ", byte: " << static_cast( &t.evnt[0] + t.evnt.size() - ptr ) ) break; } } std::stable_sort( this->begin(), this->end() ); // update duration delta = 0; for ( iterator it = this->begin(); it != this->end(); ++it ) { it->_binaryTime = packVariableLengthQuantity( it->_time - delta ); delta = it->_time; } } }; StreamBuf & operator<<( StreamBuf & sb, const MidiEvents & st ) { for ( MidiEvents::const_iterator it = st.begin(); it != st.end(); ++it ) { sb << *it; } return sb; } struct MidTrack { IFFChunkHeader mtrk; MidiEvents events; explicit MidTrack( const XMITrack & t ) : mtrk( TAG_MTRK, 0 ) , events( t ) { mtrk.length = static_cast( events.size() ); } size_t size() const { return sizeof( mtrk ) + events.size(); } }; StreamBuf & operator<<( StreamBuf & sb, const MidTrack & st ) { sb << st.mtrk; sb << st.events; return sb; } struct MidTracks : std::list { size_t count() const { return std::list::size(); } MidTracks() = default; explicit MidTracks( const XMITracks & tracks ) { for ( XMITracks::const_iterator it = tracks.begin(); it != tracks.end(); ++it ) emplace_back( *it ); } }; StreamBuf & operator<<( StreamBuf & sb, const MidTracks & st ) { for ( std::list::const_iterator it = st.begin(); it != st.end(); ++it ) sb << *it; return sb; } struct MidData { IFFChunkHeader mthd; int format; int ppqn; MidTracks tracks; explicit MidData( const XMITracks & t ) : mthd( TAG_MTHD, 6 ) , format( 0 ) , ppqn( 60 ) , tracks( t ) { // MIDI format 0 can contain only one track assert( tracks.count() == 1 ); // XMI files play MIDI at a fixed clock rate of 120 Hz if ( !tracks.empty() && tracks.front().events.trackTempo > 0 ) { ppqn = ( tracks.front().events.trackTempo * 3 / 25000 ); } } }; StreamBuf & operator<<( StreamBuf & sb, const MidData & st ) { sb << st.mthd; sb.putBE16( static_cast( st.format ) ); sb.putBE16( static_cast( st.tracks.count() ) ); sb.putBE16( static_cast( st.ppqn ) ); sb << st.tracks; return sb; } std::vector Music::Xmi2Mid( const std::vector & buf ) { XMIData xmi( buf ); StreamBuf sb( 16 * 4096 ); if ( xmi.isvalid() ) { MidData mid( xmi.tracks ); sb << mid; } return std::vector( sb.data(), sb.data() + sb.size() ); } fheroes2-1.0.12+dfsg/src/engine/zzlib.cpp000066400000000000000000000161701456075706000201400ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "zzlib.h" #include #include #include #include #include #include #include "logging.h" namespace { constexpr uint16_t FORMAT_VERSION_0 = 0; std::vector zlibDecompress( const uint8_t * src, const size_t srcSize, size_t realSize = 0 ) { if ( src == nullptr || srcSize == 0 ) { return {}; } const uLong srcSizeULong = static_cast( srcSize ); if ( srcSizeULong != srcSize ) { ERROR_LOG( "The size of the compressed data is too large" ) return {}; } std::vector res( realSize ); if ( realSize == 0 ) { constexpr size_t sizeMultiplier = 7; if ( srcSize > res.max_size() / sizeMultiplier ) { // If the multiplicated size is too large, let's start with the original size and see how it goes realSize = srcSize; } else { realSize = srcSize * sizeMultiplier; } res.resize( realSize ); } uLong dstSizeULong = static_cast( res.size() ); if ( dstSizeULong != res.size() ) { ERROR_LOG( "The size of the decompressed data is too large" ) return {}; } int ret = Z_BUF_ERROR; while ( Z_BUF_ERROR == ( ret = uncompress( res.data(), &dstSizeULong, src, srcSizeULong ) ) ) { constexpr size_t sizeMultiplier = 2; // Avoid infinite loop due to unsigned overflow on multiplication if ( res.size() > res.max_size() / sizeMultiplier ) { ERROR_LOG( "The size of the decompressed data is too large" ) return {}; } res.resize( res.size() * sizeMultiplier ); dstSizeULong = static_cast( res.size() ); if ( dstSizeULong != res.size() ) { ERROR_LOG( "The size of the decompressed data is too large" ) return {}; } } if ( ret != Z_OK ) { ERROR_LOG( "zlib error: " << ret ) return {}; } res.resize( dstSizeULong ); return res; } std::vector zlibCompress( const uint8_t * src, const size_t srcSize ) { if ( src == nullptr || srcSize == 0 ) { return {}; } const uLong srcSizeULong = static_cast( srcSize ); if ( srcSizeULong != srcSize ) { ERROR_LOG( "The size of the source data is too large" ) return {}; } std::vector res( compressBound( srcSizeULong ) ); uLong dstSizeULong = static_cast( res.size() ); if ( dstSizeULong != res.size() ) { ERROR_LOG( "The size of the compressed data is too large" ) return {}; } const int ret = compress( res.data(), &dstSizeULong, src, srcSizeULong ); if ( ret != Z_OK ) { ERROR_LOG( "zlib error: " << ret ) return {}; } res.resize( dstSizeULong ); return res; } } bool ZStreamBuf::read( const std::string & fn, const size_t offset /* = 0 */ ) { StreamFile sf; sf.setbigendian( true ); if ( !sf.open( fn, "rb" ) ) { return false; } if ( offset ) { sf.seek( offset ); } const uint32_t rawSize = sf.get32(); const uint32_t zipSize = sf.get32(); if ( zipSize == 0 ) { return false; } const uint16_t version = sf.get16(); if ( version != FORMAT_VERSION_0 ) { return false; } sf.skip( 2 ); // Unused bytes const std::vector zip = sf.getRaw( zipSize ); const std::vector raw = zlibDecompress( zip.data(), zip.size(), rawSize ); if ( raw.size() != rawSize ) { return false; } putRaw( reinterpret_cast( raw.data() ), raw.size() ); return !fail(); } bool ZStreamBuf::write( const std::string & fn, const bool append /* = false */ ) { StreamFile sf; sf.setbigendian( true ); if ( !sf.open( fn, append ? "ab" : "wb" ) ) { return false; } const std::vector zip = zlibCompress( data(), size() ); if ( zip.empty() ) { return false; } sf.put32( static_cast( size() ) ); sf.put32( static_cast( zip.size() ) ); sf.put16( FORMAT_VERSION_0 ); sf.put16( 0 ); // Unused bytes sf.putRaw( reinterpret_cast( zip.data() ), zip.size() ); return !sf.fail(); } fheroes2::Image CreateImageFromZlib( int32_t width, int32_t height, const uint8_t * imageData, size_t imageSize, bool doubleLayer ) { if ( imageData == nullptr || imageSize == 0 || width <= 0 || height <= 0 ) { return {}; } const std::vector & uncompressedData = zlibDecompress( imageData, imageSize ); if ( doubleLayer && ( uncompressedData.size() & 1 ) == 1 ) { return {}; } const size_t uncompressedSize = doubleLayer ? uncompressedData.size() / 2 : uncompressedData.size(); if ( static_cast( width * height ) != uncompressedSize ) { return {}; } fheroes2::Image out; if ( !doubleLayer ) { out._disableTransformLayer(); } out.resize( width, height ); std::memcpy( out.image(), uncompressedData.data(), uncompressedSize ); if ( doubleLayer ) { std::memcpy( out.transform(), uncompressedData.data() + uncompressedSize, uncompressedSize ); } return out; } fheroes2-1.0.12+dfsg/src/engine/zzlib.h000066400000000000000000000054241456075706000176050ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2ZLIB_H #define H2ZLIB_H #include #include #include #include "image.h" #include "serialize.h" class ZStreamBuf : public StreamBuf { public: ZStreamBuf() = default; ZStreamBuf( const ZStreamBuf & ) = delete; ~ZStreamBuf() override = default; ZStreamBuf & operator=( const ZStreamBuf & ) = delete; // Reads & unzips the zipped chunk from the specified file at the specified offset and appends // it to the end of the buffer. The current read position of the buffer does not change. Returns // true on success or false on error. bool read( const std::string & fn, const size_t offset = 0 ); // Zips the contents of the buffer from the current read position to the end of the buffer and // writes (or appends) it to the specified file. The current read position of the buffer does // not change. Returns true on success and false on error. bool write( const std::string & fn, const bool append = false ); }; fheroes2::Image CreateImageFromZlib( int32_t width, int32_t height, const uint8_t * imageData, size_t imageSize, bool doubleLayer ); #endif fheroes2-1.0.12+dfsg/src/fheroes2/000077500000000000000000000000001456075706000165455ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/fheroes2/CMakeLists.txt000066400000000000000000000105521456075706000213100ustar00rootroot00000000000000########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2022 - 2023 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### file(GLOB_RECURSE FHEROES2_SOURCES CONFIGURE_DEPENDS *.cpp) add_compile_options("$<$:${GNU_CC_WARN_OPTS}>") add_compile_options("$<$:${GNU_CXX_WARN_OPTS}>") add_compile_options("$<$,$>:${MSVC_CC_WARN_OPTS}>") if(ENABLE_STRICT_COMPILATION) add_compile_options($<$,$>:-Werror>) add_compile_options($<$,$>:/WX>) endif(ENABLE_STRICT_COMPILATION) if(MACOS_APP_BUNDLE) set(FHEROES2_ICON ${CMAKE_CURRENT_SOURCE_DIR}/../resources/fheroes2.icns) set_source_files_properties(${FHEROES2_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) add_executable(fheroes2 MACOSX_BUNDLE ${FHEROES2_ICON} ${FHEROES2_SOURCES} ${TRANSLATION_DATA}) target_compile_definitions( fheroes2 PRIVATE $<$:WITH_DEBUG> $<$:MACOS_APP_BUNDLE> ) target_link_libraries(fheroes2 "-framework CoreFoundation") set_target_properties(fheroes2 PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME ${CMAKE_PROJECT_NAME}) set_target_properties(fheroes2 PROPERTIES MACOSX_BUNDLE_EXECUTABLE_NAME ${CMAKE_PROJECT_NAME}) set_target_properties(fheroes2 PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/../resources/Info.plist.in) set_target_properties(fheroes2 PROPERTIES MACOSX_BUNDLE_ICON_FILE fheroes2.icns) set_target_properties(fheroes2 PROPERTIES MACOSX_BUNDLE_GUI_IDENTIFIER com.fheroes2.${CMAKE_PROJECT_NAME}) set_target_properties(fheroes2 PROPERTIES MACOSX_BUNDLE_SHORT_VERSION_STRING ${CMAKE_PROJECT_VERSION}) set_target_properties(fheroes2 PROPERTIES MACOSX_BUNDLE_BUNDLE_VERSION ${CMAKE_PROJECT_VERSION}) set_target_properties(fheroes2 PROPERTIES OUTPUT_NAME ${CMAKE_PROJECT_NAME}) else(MACOS_APP_BUNDLE) cmake_path( ABSOLUTE_PATH FHEROES2_DATA BASE_DIRECTORY ${CMAKE_INSTALL_PREFIX} NORMALIZE OUTPUT_VARIABLE FHEROES2_DATA_ABSOLUTE ) add_executable(fheroes2 ${FHEROES2_SOURCES}) target_compile_definitions( fheroes2 PRIVATE # MSVC: suppress deprecation warnings $<$,$>:_CRT_SECURE_NO_WARNINGS> $<$:WITH_DEBUG> FHEROES2_DATA=${FHEROES2_DATA_ABSOLUTE} ) # Copy executable to root of the project. add_custom_command( TARGET fheroes2 POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ ${PROJECT_SOURCE_DIR}/$ ) install(TARGETS fheroes2 DESTINATION ${CMAKE_INSTALL_BINDIR}) endif(MACOS_APP_BUNDLE) target_include_directories( fheroes2 PRIVATE agg ai army audio battle campaign castle dialog editor game gui h2d heroes image kingdom maps monster objects pocketpc resource spell system world ) target_link_libraries( fheroes2 engine ${USE_SDL_VERSION}::${USE_SDL_VERSION}main ) fheroes2-1.0.12+dfsg/src/fheroes2/agg/000077500000000000000000000000001456075706000173035ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/fheroes2/agg/agg.cpp000066400000000000000000000102521456075706000205450ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include "agg.h" #include "agg_file.h" #include "dir.h" #include "settings.h" #include "tools.h" namespace { fheroes2::AGGFile heroes2_agg; fheroes2::AGGFile heroes2x_agg; } std::vector AGG::getDataFromAggFile( const std::string & key ) { if ( heroes2x_agg.isGood() ) { // Make sure that the below container is not const and not a reference // so returning it from the function will invoke a move constructor instead of copy constructor. std::vector buf = heroes2x_agg.read( key ); if ( !buf.empty() ) return buf; } return heroes2_agg.read( key ); } AGG::AGGInitializer::AGGInitializer() { if ( init() ) { return; } throw std::logic_error( "No AGG data files found." ); } bool AGG::AGGInitializer::init() { const ListFiles aggFileNames = Settings::FindFiles( "data", ".agg", false ); if ( aggFileNames.empty() ) { return false; } const std::string heroes2AggFileName( "heroes2.agg" ); std::string heroes2AggFilePath; std::string aggLowerCaseFilePath; for ( const std::string & path : aggFileNames ) { if ( path.size() < heroes2AggFileName.size() ) { // Obviously this is not a correct file. continue; } std::string tempPath = StringLower( path ); if ( tempPath.compare( tempPath.size() - heroes2AggFileName.size(), heroes2AggFileName.size(), heroes2AggFileName ) == 0 ) { heroes2AggFilePath = path; aggLowerCaseFilePath = std::move( tempPath ); break; } } if ( heroes2AggFilePath.empty() ) { // The main game resource file was not found. return false; } if ( !heroes2_agg.open( heroes2AggFilePath ) ) { return false; } _originalAGGFilePath = std::move( heroes2AggFilePath ); // Find "heroes2x.agg" file. std::string heroes2XAggFilePath; fheroes2::replaceStringEnding( aggLowerCaseFilePath, ".agg", "x.agg" ); for ( const std::string & path : aggFileNames ) { const std::string tempPath = StringLower( path ); if ( tempPath == aggLowerCaseFilePath ) { heroes2XAggFilePath = path; break; } } if ( !heroes2XAggFilePath.empty() && heroes2x_agg.open( heroes2XAggFilePath ) ) { _expansionAGGFilePath = std::move( heroes2XAggFilePath ); } Settings::Get().EnablePriceOfLoyaltySupport( heroes2x_agg.isGood() ); return true; } fheroes2-1.0.12+dfsg/src/fheroes2/agg/agg.h000066400000000000000000000046751456075706000202260ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2AGG_H #define H2AGG_H #include #include #include namespace AGG { class AGGInitializer { public: AGGInitializer(); AGGInitializer( const AGGInitializer & ) = delete; AGGInitializer & operator=( const AGGInitializer & ) = delete; ~AGGInitializer() = default; const std::string & getOriginalAGGFilePath() const { return _originalAGGFilePath; } const std::string & getExpansionAGGFilePath() const { return _expansionAGGFilePath; } private: bool init(); std::string _originalAGGFilePath; std::string _expansionAGGFilePath; }; std::vector getDataFromAggFile( const std::string & key ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/agg/agg_image.cpp000066400000000000000000007626451456075706000217330ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "agg_image.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "agg.h" #include "agg_file.h" #include "battle_cell.h" #include "exception.h" #include "h2d.h" #include "icn.h" #include "image.h" #include "image_tool.h" #include "math_base.h" #include "pal.h" #include "rand.h" #include "screen.h" #include "serialize.h" #include "til.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_font.h" #include "ui_language.h" #include "ui_text.h" namespace { const std::array tilFileName = { "UNKNOWN", "CLOF32.TIL", "GROUND32.TIL", "STON.TIL" }; std::vector> _icnVsSprite( ICN::LASTICN ); std::array>, TIL::LASTTIL> _tilVsImage; const fheroes2::Sprite errorImage; const uint32_t headerSize = 6; std::map> _icnVsScaledSprite; // Some resources are language dependent. These are mostly buttons with a text of them. // Once a user changes a language we have to update resources. To do this we need to clear the existing images. const std::set languageDependentIcnId{ ICN::BUYMAX, ICN::BUTTON_NEW_GAME_GOOD, ICN::BUTTON_NEW_GAME_EVIL, ICN::BUTTON_SAVE_GAME_GOOD, ICN::BUTTON_SAVE_GAME_EVIL, ICN::BUTTON_LOAD_GAME_GOOD, ICN::BUTTON_LOAD_GAME_EVIL, ICN::BUTTON_INFO_GOOD, ICN::BUTTON_INFO_EVIL, ICN::BUTTON_QUIT_GOOD, ICN::BUTTON_QUIT_EVIL, ICN::BUTTON_SMALL_CANCEL_GOOD, ICN::BUTTON_SMALL_CANCEL_EVIL, ICN::BUTTON_SMALL_OKAY_GOOD, ICN::BUTTON_SMALL_OKAY_EVIL, ICN::BUTTON_SMALLER_OKAY_GOOD, ICN::BUTTON_SMALLER_OKAY_EVIL, ICN::BUTTON_SMALL_ACCEPT_GOOD, ICN::BUTTON_SMALL_ACCEPT_EVIL, ICN::BUTTON_SMALL_DECLINE_GOOD, ICN::BUTTON_SMALL_DECLINE_EVIL, ICN::BUTTON_SMALL_LEARN_GOOD, ICN::BUTTON_SMALL_LEARN_EVIL, ICN::BUTTON_SMALL_TRADE_GOOD, ICN::BUTTON_SMALL_TRADE_EVIL, ICN::BUTTON_SMALL_YES_GOOD, ICN::BUTTON_SMALL_YES_EVIL, ICN::BUTTON_SMALL_NO_GOOD, ICN::BUTTON_SMALL_NO_EVIL, ICN::BUTTON_SMALL_EXIT_GOOD, ICN::BUTTON_SMALL_EXIT_EVIL, ICN::BUTTON_EXIT_HEROES_MEETING, ICN::BUTTON_EXIT_TOWN, ICN::BUTTON_EXIT_PUZZLE_DIM_DOOR_GOOD, ICN::BUTTON_EXIT_PUZZLE_DIM_DOOR_EVIL, ICN::BUTTON_SMALL_DISMISS_GOOD, ICN::BUTTON_SMALL_DISMISS_EVIL, ICN::BUTTON_SMALL_UPGRADE_GOOD, ICN::BUTTON_SMALL_UPGRADE_EVIL, ICN::BUTTON_SMALL_RESTART_GOOD, ICN::BUTTON_SMALL_RESTART_EVIL, ICN::BUTTON_KINGDOM_EXIT, ICN::BUTTON_KINGDOM_HEROES, ICN::BUTTON_KINGDOM_TOWNS, ICN::BUTTON_MAPSIZE_SMALL, ICN::BUTTON_MAPSIZE_MEDIUM, ICN::BUTTON_MAPSIZE_LARGE, ICN::BUTTON_MAPSIZE_XLARGE, ICN::BUTTON_MAPSIZE_ALL, ICN::BUTTON_MAP_SELECT_GOOD, ICN::BUTTON_MAP_SELECT_EVIL, ICN::BUTTON_STANDARD_GAME, ICN::BUTTON_CAMPAIGN_GAME, ICN::BUTTON_MULTIPLAYER_GAME, ICN::BUTTON_LARGE_CANCEL, ICN::BUTTON_LARGE_CONFIG, ICN::BUTTON_ORIGINAL_CAMPAIGN, ICN::BUTTON_EXPANSION_CAMPAIGN, ICN::BUTTON_HOT_SEAT, ICN::BUTTON_2_PLAYERS, ICN::BUTTON_3_PLAYERS, ICN::BUTTON_4_PLAYERS, ICN::BUTTON_5_PLAYERS, ICN::BUTTON_6_PLAYERS, ICN::BTNBATTLEONLY, ICN::BTNGIFT_GOOD, ICN::BTNGIFT_EVIL, ICN::UNIFORM_EVIL_MAX_BUTTON, ICN::UNIFORM_EVIL_MIN_BUTTON, ICN::UNIFORM_GOOD_MAX_BUTTON, ICN::UNIFORM_GOOD_MIN_BUTTON, ICN::UNIFORM_GOOD_OKAY_BUTTON, ICN::UNIFORM_EVIL_OKAY_BUTTON, ICN::UNIFORM_GOOD_CANCEL_BUTTON, ICN::UNIFORM_EVIL_CANCEL_BUTTON, ICN::UNIFORM_GOOD_EXIT_BUTTON, ICN::UNIFORM_EVIL_EXIT_BUTTON, ICN::BUTTON_SMALL_MIN_GOOD, ICN::BUTTON_SMALL_MIN_EVIL, ICN::BUTTON_SMALL_MAX_GOOD, ICN::BUTTON_SMALL_MAX_EVIL, ICN::BUTTON_EXIT_GOOD, ICN::BUTTON_RESET_GOOD, ICN::BUTTON_START_GOOD, ICN::BUTTON_CASTLE_GOOD, ICN::BUTTON_CASTLE_EVIL, ICN::BUTTON_TOWN_GOOD, ICN::BUTTON_TOWN_EVIL, ICN::BUTTON_GUILDWELL_EXIT, ICN::GOOD_CAMPAIGN_BUTTONS, ICN::EVIL_CAMPAIGN_BUTTONS, ICN::POL_CAMPAIGN_BUTTONS, ICN::BUTTON_VIEWWORLD_EXIT_GOOD, ICN::BUTTON_VIEWWORLD_EXIT_EVIL, ICN::BUTTON_VERTICAL_DISMISS, ICN::BUTTON_VERTICAL_EXIT, ICN::BUTTON_HSCORES_VERTICAL_CAMPAIGN, ICN::BUTTON_HSCORES_VERTICAL_EXIT, ICN::BUTTON_HSCORES_VERTICAL_STANDARD, ICN::DISMISS_HERO_DISABLED_BUTTON, ICN::NEW_CAMPAIGN_DISABLED_BUTTON }; #ifndef NDEBUG bool isLanguageDependentIcnId( const int id ) { return languageDependentIcnId.count( id ) > 0; } #endif bool useOriginalResources() { const fheroes2::SupportedLanguage currentLanguage = fheroes2::getCurrentLanguage(); const fheroes2::SupportedLanguage resourceLanguage = fheroes2::getResourceLanguage(); return ( currentLanguage == fheroes2::SupportedLanguage::English && resourceLanguage == fheroes2::SupportedLanguage::English ) || ( currentLanguage == fheroes2::SupportedLanguage::Czech && resourceLanguage == fheroes2::SupportedLanguage::Czech ) || ( currentLanguage == fheroes2::SupportedLanguage::Polish && resourceLanguage == fheroes2::SupportedLanguage::Polish ) || ( currentLanguage == fheroes2::SupportedLanguage::French && resourceLanguage == fheroes2::SupportedLanguage::French ) || ( currentLanguage == fheroes2::SupportedLanguage::German && resourceLanguage == fheroes2::SupportedLanguage::German ) || ( currentLanguage == fheroes2::SupportedLanguage::Russian && resourceLanguage == fheroes2::SupportedLanguage::Russian ); } bool IsValidICNId( int id ) { return id > ICN::UNKNOWN && static_cast( id ) < _icnVsSprite.size(); } bool IsValidTILId( int id ) { return id >= 0 && static_cast( id ) < _tilVsImage.size(); } fheroes2::Image createDigit( const int32_t width, const int32_t height, const std::vector & points, const uint8_t pixelColor ) { fheroes2::Image digit( width, height ); digit.reset(); fheroes2::SetPixel( digit, points, pixelColor ); fheroes2::Blit( fheroes2::CreateContour( digit, 35 ), digit ); return digit; } fheroes2::Image addDigit( const fheroes2::Sprite & original, const fheroes2::Image & digit, const fheroes2::Point & offset ) { fheroes2::Image combined( original.width() + digit.width() + offset.x, original.height() + ( offset.y < 0 ? 0 : offset.y ) ); combined.reset(); fheroes2::Copy( original, 0, 0, combined, 0, 0, original.width(), original.height() ); fheroes2::Blit( digit, 0, 0, combined, original.width() + offset.x, combined.height() - digit.height() + ( offset.y >= 0 ? 0 : offset.y ), digit.width(), digit.height() ); return combined; } void populateCursorIcons( std::vector & output, const fheroes2::Sprite & origin, const std::vector & digits, const fheroes2::Point & offset ) { output.emplace_back( origin ); for ( const fheroes2::Image & digit : digits ) { output.emplace_back( addDigit( origin, digit, offset ) ); output.back().setPosition( output.back().width() - origin.width(), output.back().height() - origin.height() ); } } void fillRandomPixelsFromImage( const fheroes2::Image & original, const fheroes2::Rect & originalRoi, fheroes2::Image & output, const fheroes2::Rect & outputRoi, std::mt19937 & seededGen ) { for ( int x = outputRoi.x; x < outputRoi.x + outputRoi.width; ++x ) { for ( int y = outputRoi.y; y < outputRoi.y + outputRoi.height; ++y ) { const fheroes2::Point pixelLocation{ static_cast( Rand::GetWithGen( originalRoi.x, originalRoi.x + originalRoi.width - 1, seededGen ) ), static_cast( Rand::GetWithGen( originalRoi.y, originalRoi.y + originalRoi.height - 1, seededGen ) ) }; fheroes2::Copy( original, pixelLocation.x, pixelLocation.y, output, x, y, 1, 1 ); } } } // BMP files within AGG are not Bitmap images! fheroes2::Sprite loadBMPFile( const std::string & path ) { const std::vector & data = AGG::getDataFromAggFile( path ); if ( data.size() < 6 ) { // It is an invalid BMP file. return {}; } StreamBuf imageStream( data ); const uint8_t blackColor = imageStream.get(); const uint8_t whiteColor = 11; // Skip the second byte imageStream.get(); const int32_t width = imageStream.get16(); const int32_t height = imageStream.get16(); if ( static_cast( data.size() ) != 6 + width * height ) { // It is an invalid BMP file. return {}; } fheroes2::Sprite output( width, height ); const uint8_t * input = data.data() + 6; uint8_t * image = output.image(); const uint8_t * imageEnd = image + width * height; uint8_t * transform = output.transform(); for ( ; image != imageEnd; ++image, ++transform, ++input ) { if ( *input == 1 ) { *image = whiteColor; *transform = 0; } else if ( *input == 2 ) { *image = blackColor; *transform = 0; } else { *transform = 1; } } return output; } // This class serves the purpose of preserving the original alphabet which is loaded from AGG files for cases when we generate new language alphabet. class OriginalAlphabetPreserver { public: void preserve() { if ( _isPreserved ) { return; } fheroes2::AGG::GetICN( ICN::FONT, 0 ); fheroes2::AGG::GetICN( ICN::SMALFONT, 0 ); fheroes2::AGG::GetICN( ICN::BUTTON_GOOD_FONT_RELEASED, 0 ); fheroes2::AGG::GetICN( ICN::BUTTON_GOOD_FONT_PRESSED, 0 ); fheroes2::AGG::GetICN( ICN::BUTTON_EVIL_FONT_RELEASED, 0 ); fheroes2::AGG::GetICN( ICN::BUTTON_EVIL_FONT_PRESSED, 0 ); _normalFont = _icnVsSprite[ICN::FONT]; _smallFont = _icnVsSprite[ICN::SMALFONT]; _buttonGoodReleasedFont = _icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED]; _buttonGoodPressedFont = _icnVsSprite[ICN::BUTTON_GOOD_FONT_PRESSED]; _buttonEvilReleasedFont = _icnVsSprite[ICN::BUTTON_EVIL_FONT_RELEASED]; _buttonEvilPressedFont = _icnVsSprite[ICN::BUTTON_EVIL_FONT_PRESSED]; _isPreserved = true; } void restore() const { if ( !_isPreserved ) { return; } // Restore the original font. _icnVsSprite[ICN::FONT] = _normalFont; _icnVsSprite[ICN::SMALFONT] = _smallFont; _icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED] = _buttonGoodReleasedFont; _icnVsSprite[ICN::BUTTON_GOOD_FONT_PRESSED] = _buttonGoodPressedFont; _icnVsSprite[ICN::BUTTON_EVIL_FONT_RELEASED] = _buttonEvilReleasedFont; _icnVsSprite[ICN::BUTTON_EVIL_FONT_PRESSED] = _buttonEvilPressedFont; // Clear modified fonts. _icnVsSprite[ICN::YELLOW_FONT].clear(); _icnVsSprite[ICN::YELLOW_SMALLFONT].clear(); _icnVsSprite[ICN::GRAY_FONT].clear(); _icnVsSprite[ICN::GRAY_SMALL_FONT].clear(); _icnVsSprite[ICN::WHITE_LARGE_FONT].clear(); } bool isPreserved() const { return _isPreserved; } private: bool _isPreserved = false; std::vector _normalFont; std::vector _smallFont; std::vector _buttonGoodReleasedFont; std::vector _buttonGoodPressedFont; std::vector _buttonEvilReleasedFont; std::vector _buttonEvilPressedFont; }; OriginalAlphabetPreserver alphabetPreserver; // This class is used for situations when we need to remove letter-specific offsets, like when we display single letters in a row, // and then restore these offsets within the scope of the code class ButtonFontOffsetRestorer { public: ButtonFontOffsetRestorer( std::vector & font, const int32_t offsetX ); ButtonFontOffsetRestorer( const ButtonFontOffsetRestorer & ) = delete; ~ButtonFontOffsetRestorer(); ButtonFontOffsetRestorer & operator=( const ButtonFontOffsetRestorer & ) = delete; private: std::vector & _font; std::vector _originalXOffsets; }; ButtonFontOffsetRestorer::ButtonFontOffsetRestorer( std::vector & font, const int32_t offsetX ) : _font( font ) { _originalXOffsets.reserve( _font.size() ); for ( fheroes2::Sprite & characterSprite : _font ) { _originalXOffsets.emplace_back( characterSprite.x() ); characterSprite.setPosition( offsetX, characterSprite.y() ); } } ButtonFontOffsetRestorer::~ButtonFontOffsetRestorer() { if ( _originalXOffsets.size() != _font.size() ) { // If this assertion blows up then something is wrong with the fonts as they must have the same size. assert( 0 ); return; } for ( size_t i = 0; i < _font.size(); ++i ) { _font[i].setPosition( _originalXOffsets[i], _font[i].y() ); } } void invertTransparency( fheroes2::Image & image ) { if ( image.singleLayer() ) { assert( 0 ); return; } uint8_t * transform = image.transform(); const uint8_t * transformEnd = transform + image.width() * image.height(); for ( ; transform != transformEnd; ++transform ) { if ( *transform == 0 ) { *transform = 1; } else if ( *transform == 1 ) { *transform = 0; } // Other transform values are not relevant for transparency checks. } } uint8_t getButtonFillingColor( const bool isReleasedState, const bool isGoodInterface = true ) { if ( isGoodInterface ) { return isReleasedState ? fheroes2::GetColorId( 216, 184, 152 ) : fheroes2::GetColorId( 184, 136, 96 ); } return isReleasedState ? fheroes2::GetColorId( 180, 180, 180 ) : fheroes2::GetColorId( 144, 144, 144 ); } const char * getSupportedText( const char * text, const fheroes2::FontType & font ) { const char * translatedText = _( text ); return fheroes2::isFontAvailable( translatedText, font ) ? translatedText : text; } void renderTextOnButton( fheroes2::Image & releasedState, fheroes2::Image & pressedState, const char * text, const fheroes2::Point & releasedTextOffset, const fheroes2::Point & pressedTextOffset, const fheroes2::Size buttonSize, const fheroes2::FontColor fontColor ) { const fheroes2::FontType releasedFont{ fheroes2::FontSize::BUTTON_RELEASED, fontColor }; const fheroes2::FontType pressedFont{ fheroes2::FontSize::BUTTON_PRESSED, fontColor }; const char * textSupported = getSupportedText( text, releasedFont ); const fheroes2::Text releasedText( textSupported, releasedFont ); const fheroes2::Text pressedText( textSupported, pressedFont ); const fheroes2::Size releasedTextSize( releasedText.width( buttonSize.width ), releasedText.height( buttonSize.width ) ); const fheroes2::Size pressedTextSize( pressedText.width( buttonSize.width ), pressedText.height( buttonSize.width ) ); releasedText.draw( releasedTextOffset.x, releasedTextOffset.y + ( buttonSize.height - releasedTextSize.height ) / 2, buttonSize.width, releasedState ); pressedText.draw( pressedTextOffset.x, pressedTextOffset.y + ( buttonSize.height - pressedTextSize.height ) / 2, buttonSize.width, pressedState ); } void createNormalButton( fheroes2::Sprite & released, fheroes2::Sprite & pressed, int32_t textWidth, const char * text, const bool isEvilInterface ) { fheroes2::Point releasedOffset; fheroes2::Point pressedOffset; fheroes2::getCustomNormalButton( released, pressed, isEvilInterface, textWidth, releasedOffset, pressedOffset ); const fheroes2::FontColor buttonFontColor = isEvilInterface ? fheroes2::FontColor::GRAY : fheroes2::FontColor::WHITE; renderTextOnButton( released, pressed, text, releasedOffset, pressedOffset, { textWidth, fheroes2::getFontHeight( fheroes2::FontSize::BUTTON_RELEASED ) }, buttonFontColor ); } void createCampaignButtonSet( const int campaignSetIcnId, const std::array & texts ) { int emptyButtonIcnID = 0; int buttonBackgroundICN = ICN::UNKNOWN; switch ( campaignSetIcnId ) { case ICN::GOOD_CAMPAIGN_BUTTONS: emptyButtonIcnID = ICN::EMPTY_GOOD_BUTTON; buttonBackgroundICN = ICN::STONEBAK; break; case ICN::EVIL_CAMPAIGN_BUTTONS: emptyButtonIcnID = ICN::EMPTY_EVIL_BUTTON; buttonBackgroundICN = ICN::STONEBAK_EVIL; break; case ICN::POL_CAMPAIGN_BUTTONS: emptyButtonIcnID = ICN::EMPTY_POL_BUTTON; buttonBackgroundICN = ICN::STONEBAK_SMALL_POL; break; default: // Was a new set of buttons added? assert( 0 ); break; } for ( size_t i = 0; i < texts.size(); ++i ) { const size_t icnIndex = 2 * i; fheroes2::getTextAdaptedButton( _icnVsSprite[campaignSetIcnId][icnIndex], _icnVsSprite[campaignSetIcnId][icnIndex + 1], texts[i], emptyButtonIcnID, buttonBackgroundICN ); } } void convertToEvilInterface( fheroes2::Sprite & image, const fheroes2::Rect & roi ) { fheroes2::ApplyPalette( image, roi.x, roi.y, image, roi.x, roi.y, roi.width, roi.height, PAL::GetPalette( PAL::PaletteType::GOOD_TO_EVIL_INTERFACE ) ); } void copyEvilInterfaceElements( fheroes2::Sprite & image, const fheroes2::Rect & roi ) { // Evil interface has special elements at each corner of the window. const fheroes2::Sprite & original = fheroes2::AGG::GetICN( ICN::CSPANBKE, 0 ); // If this assertion blows up you are using some modded resources. Good luck! assert( original.width() == 321 && original.height() == 304 ); // Top-left corner. fheroes2::Copy( original, 0, 0, image, roi.x, roi.y, 17, 43 ); fheroes2::Copy( original, 17, 0, image, roi.x + 17, roi.y, 26, 14 ); // Top-right corner. fheroes2::Copy( original, original.width() - 43, 0, image, roi.x + roi.width - 43, roi.y, 43, 14 ); fheroes2::Copy( original, original.width() - 17, 14, image, roi.x + roi.width - 17, roi.y + 14, 17, 29 ); // Bottom-right corner. fheroes2::Copy( original, original.width() - 13, original.height() - 43, image, roi.x + roi.width - 13, roi.y + roi.height - 43, 13, 27 ); fheroes2::Copy( original, original.width() - 16, original.height() - 16, image, roi.x + roi.width - 16, roi.y + roi.height - 16, 16, 16 ); fheroes2::Copy( original, original.width() - 43, original.height() - 13, image, roi.x + roi.width - 43, roi.y + roi.height - 13, 27, 13 ); // Bottom-left corner. fheroes2::Copy( original, 0, original.height() - 43, image, roi.x, roi.y + roi.height - 43, 13, 27 ); fheroes2::Copy( original, 0, original.height() - 16, image, roi.x, roi.y + roi.height - 16, 16, 16 ); fheroes2::Copy( original, 16, original.height() - 13, image, roi.x + 16, roi.y + roi.height - 13, 27, 13 ); } // Sets the upper left (offset 3 pixels), upper right (offset 2 pixels), lower right (offset 3 pixels) corners transparent. void setButtonCornersTransparent( fheroes2::Sprite & buttonSprite ) { const ptrdiff_t imageWidth = buttonSprite.width(); const ptrdiff_t imageHeight = buttonSprite.height(); assert( imageWidth > 3 && imageHeight > 3 ); uint8_t * imageTransform = buttonSprite.transform(); const uint8_t transparencyValue = 1; std::fill( imageTransform, imageTransform + 4, transparencyValue ); std::fill( imageTransform + imageWidth - 2, imageTransform + imageWidth + 3, transparencyValue ); std::fill( imageTransform + 2 * imageWidth - 1, imageTransform + 2 * imageWidth + 2, transparencyValue ); *( imageTransform + 3 * imageWidth ) = transparencyValue; *( imageTransform + ( imageHeight - 3 ) * imageWidth - 1 ) = transparencyValue; std::fill( imageTransform + ( imageHeight - 2 ) * imageWidth - 2, imageTransform + ( imageHeight - 2 ) * imageWidth, transparencyValue ); std::fill( imageTransform + ( imageHeight - 1 ) * imageWidth - 3, imageTransform + ( imageHeight - 1 ) * imageWidth, transparencyValue ); std::fill( imageTransform + imageHeight * imageWidth - 4, imageTransform + imageHeight * imageWidth, transparencyValue ); } // Remove all shadows from the image and make them fully transparent. void setTransformLayerTransparent( fheroes2::Image & image ) { assert( !image.empty() && !image.singleLayer() ); uint8_t * transform = image.transform(); const uint8_t * transformEnd = transform + static_cast( image.width() ) * image.height(); for ( ; transform != transformEnd; ++transform ) { if ( *transform > 1 ) { *transform = 1U; } } } // Draw the given image in the center of the button images (released and pressed states) and add extra shading and brightening at the edges of the image. void drawImageOnButton( const fheroes2::Image & image, const int32_t maxImageWidth, const int32_t maxImageHeight, fheroes2::Image & releasedSprite, fheroes2::Image & pressedSprite ) { assert( !image.empty() && !releasedSprite.empty() && !pressedSprite.empty() ); fheroes2::Image newImage( std::min( maxImageWidth, image.width() + 4 ), std::min( maxImageHeight, image.height() + 4 ) ); newImage.reset(); fheroes2::Blit( image, 0, 0, newImage, 2, 2, 35, 25 ); // Remove shadow from the image. setTransformLayerTransparent( newImage ); // Add extra shading and brightening at the edges of the image. fheroes2::updateShadow( newImage, { 1, -1 }, 2U, false ); fheroes2::updateShadow( newImage, { 2, -2 }, 5U, false ); fheroes2::updateShadow( newImage, { -1, 1 }, 6U, false ); fheroes2::updateShadow( newImage, { -2, 2 }, 9U, false ); // Draw the image on the button. const int32_t offsetX = ( pressedSprite.width() - newImage.width() ) / 2; const int32_t offsetY = ( pressedSprite.height() - newImage.height() ) / 2; fheroes2::Blit( newImage, pressedSprite, offsetX + 2, offsetY + 1 ); fheroes2::ReplaceTransformIdByColorId( newImage, 6U, 10U ); fheroes2::Blit( newImage, releasedSprite, offsetX + 3, offsetY ); } } namespace fheroes2 { namespace AGG { void loadICN( const int id ); void LoadOriginalICN( const int id ) { // If this assertion blows up then something wrong with your logic and you load resources more than once! assert( _icnVsSprite[id].empty() ); const std::vector & body = ::AGG::getDataFromAggFile( ICN::GetString( id ) ); if ( body.empty() ) { return; } StreamBuf imageStream( body ); const uint32_t count = imageStream.getLE16(); const uint32_t blockSize = imageStream.getLE32(); if ( count == 0 || blockSize == 0 ) { return; } _icnVsSprite[id].resize( count ); for ( uint32_t i = 0; i < count; ++i ) { imageStream.seek( headerSize + i * 13 ); ICNHeader header1; imageStream >> header1; uint32_t sizeData = 0; if ( i + 1 != count ) { ICNHeader header2; imageStream >> header2; sizeData = header2.offsetData - header1.offsetData; } else { sizeData = blockSize - header1.offsetData; } if ( headerSize + header1.offsetData + sizeData > body.size() ) { // This is a corrupted AGG file. throw fheroes2::InvalidDataResources( "ICN Id " + std::to_string( id ) + ", index " + std::to_string( i ) + " is being corrupted. " "Make sure that you own an official version of the game." ); } const uint8_t * data = body.data() + headerSize + header1.offsetData; _icnVsSprite[id][i] = decodeICNSprite( data, sizeData, header1.width, header1.height, header1.offsetX, header1.offsetY ); } } // Helper function for LoadModifiedICN void CopyICNWithPalette( const int icnId, const int originalIcnId, const PAL::PaletteType paletteType ) { assert( icnId != originalIcnId ); GetICN( originalIcnId, 0 ); // always avoid calling LoadOriginalICN directly _icnVsSprite[icnId] = _icnVsSprite[originalIcnId]; const std::vector & palette = PAL::GetPalette( paletteType ); for ( fheroes2::Sprite & sprite : _icnVsSprite[icnId] ) { ApplyPalette( sprite, palette ); } } void generateDefaultImages( const int id ) { switch ( id ) { case ICN::BTNBATTLEONLY: { _icnVsSprite[id].resize( 2 ); for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( ICN::BTNCOM, i ); // clean button. Fill( out, 13, 11, 113, 31, getButtonFillingColor( i == 0 ) ); } renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "BATTLE\nONLY" ), { 12, 5 }, { 11, 6 }, { 117, 47 }, fheroes2::FontColor::WHITE ); break; } case ICN::BUTTON_NEW_GAME_EVIL: case ICN::BUTTON_NEW_GAME_GOOD: { _icnVsSprite[id].resize( 2 ); const bool isEvilInterface = ( id == ICN::BUTTON_NEW_GAME_EVIL ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( isEvilInterface ? ICN::CPANELE : ICN::CPANEL, 0 ); _icnVsSprite[id][1] = GetICN( isEvilInterface ? ICN::CPANELE : ICN::CPANEL, 1 ); break; } const int baseIcnId = isEvilInterface ? ICN::EMPTY_EVIL_MEDIUM_BUTTON : ICN::EMPTY_GOOD_MEDIUM_BUTTON; for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( baseIcnId, i ); } const fheroes2::FontColor buttonFontColor = isEvilInterface ? fheroes2::FontColor::GRAY : fheroes2::FontColor::WHITE; renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "NEW\nGAME" ), { 7, 5 }, { 6, 6 }, { 86, 48 }, buttonFontColor ); break; } case ICN::BUTTON_SAVE_GAME_EVIL: case ICN::BUTTON_SAVE_GAME_GOOD: { _icnVsSprite[id].resize( 2 ); const bool isEvilInterface = ( id == ICN::BUTTON_SAVE_GAME_EVIL ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( isEvilInterface ? ICN::CPANELE : ICN::CPANEL, 4 ); _icnVsSprite[id][1] = GetICN( isEvilInterface ? ICN::CPANELE : ICN::CPANEL, 5 ); break; } const int baseIcnId = isEvilInterface ? ICN::EMPTY_EVIL_MEDIUM_BUTTON : ICN::EMPTY_GOOD_MEDIUM_BUTTON; for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( baseIcnId, i ); } const fheroes2::FontColor buttonFontColor = isEvilInterface ? fheroes2::FontColor::GRAY : fheroes2::FontColor::WHITE; renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "SAVE\nGAME" ), { 7, 5 }, { 6, 6 }, { 86, 48 }, buttonFontColor ); break; } case ICN::BUTTON_LOAD_GAME_EVIL: case ICN::BUTTON_LOAD_GAME_GOOD: { _icnVsSprite[id].resize( 2 ); const bool isEvilInterface = ( id == ICN::BUTTON_LOAD_GAME_EVIL ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( isEvilInterface ? ICN::CPANELE : ICN::CPANEL, 2 ); _icnVsSprite[id][1] = GetICN( isEvilInterface ? ICN::CPANELE : ICN::CPANEL, 3 ); break; } const int baseIcnId = isEvilInterface ? ICN::EMPTY_EVIL_MEDIUM_BUTTON : ICN::EMPTY_GOOD_MEDIUM_BUTTON; for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( baseIcnId, i ); } const fheroes2::FontColor buttonFontColor = isEvilInterface ? fheroes2::FontColor::GRAY : fheroes2::FontColor::WHITE; renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "LOAD\nGAME" ), { 7, 5 }, { 6, 6 }, { 86, 48 }, buttonFontColor ); break; } case ICN::BUTTON_INFO_EVIL: case ICN::BUTTON_INFO_GOOD: { _icnVsSprite[id].resize( 2 ); const bool isEvilInterface = ( id == ICN::BUTTON_INFO_EVIL ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( isEvilInterface ? ICN::APANELE : ICN::APANEL, 4 ); _icnVsSprite[id][1] = GetICN( isEvilInterface ? ICN::APANELE : ICN::APANEL, 5 ); break; } const int baseIcnId = isEvilInterface ? ICN::EMPTY_EVIL_MEDIUM_BUTTON : ICN::EMPTY_GOOD_MEDIUM_BUTTON; for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( baseIcnId, i ); } const fheroes2::FontColor buttonFontColor = isEvilInterface ? fheroes2::FontColor::GRAY : fheroes2::FontColor::WHITE; renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "INFO" ), { 7, 5 }, { 6, 6 }, { 86, 48 }, buttonFontColor ); break; } case ICN::BUTTON_QUIT_EVIL: case ICN::BUTTON_QUIT_GOOD: { _icnVsSprite[id].resize( 2 ); const bool isEvilInterface = ( id == ICN::BUTTON_QUIT_EVIL ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( isEvilInterface ? ICN::CPANELE : ICN::CPANEL, 6 ); _icnVsSprite[id][1] = GetICN( isEvilInterface ? ICN::CPANELE : ICN::CPANEL, 7 ); break; } const int baseIcnId = isEvilInterface ? ICN::EMPTY_EVIL_MEDIUM_BUTTON : ICN::EMPTY_GOOD_MEDIUM_BUTTON; for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( baseIcnId, i ); } const fheroes2::FontColor buttonFontColor = isEvilInterface ? fheroes2::FontColor::GRAY : fheroes2::FontColor::WHITE; renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "QUIT" ), { 7, 5 }, { 6, 6 }, { 86, 48 }, buttonFontColor ); break; } case ICN::BUTTON_SMALL_CANCEL_GOOD: case ICN::BUTTON_SMALL_CANCEL_EVIL: { _icnVsSprite[id].resize( 2 ); const bool isEvilInterface = ( id == ICN::BUTTON_SMALL_CANCEL_EVIL ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( isEvilInterface ? ICN::CPANELE : ICN::CPANEL, 8 ); _icnVsSprite[id][1] = GetICN( isEvilInterface ? ICN::CPANELE : ICN::CPANEL, 9 ); // To properly generate shadows and Blit the button we need to make transparent pixels in its released state the same as in the pressed state. CopyTransformLayer( _icnVsSprite[id][0], _icnVsSprite[id][1] ); break; } getTextAdaptedButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "CANCEL" ), isEvilInterface ? ICN::EMPTY_EVIL_BUTTON : ICN::EMPTY_GOOD_BUTTON, isEvilInterface ? ICN::STONEBAK_EVIL : ICN::STONEBAK ); break; } case ICN::BUTTON_SMALL_OKAY_GOOD: case ICN::BUTTON_SMALL_OKAY_EVIL: case ICN::BUTTON_SMALLER_OKAY_GOOD: case ICN::BUTTON_SMALLER_OKAY_EVIL: { _icnVsSprite[id].resize( 2 ); const bool isEvilInterface = ( id == ICN::BUTTON_SMALL_OKAY_EVIL || id == ICN::BUTTON_SMALLER_OKAY_EVIL ); const bool isSameResourceAsLanguage = useOriginalResources(); if ( isSameResourceAsLanguage && ( id == ICN::BUTTON_SMALL_OKAY_EVIL || id == ICN::BUTTON_SMALL_OKAY_GOOD ) ) { _icnVsSprite[id][0] = GetICN( isEvilInterface ? ICN::SPANBTNE : ICN::SPANBTN, 0 ); _icnVsSprite[id][1] = GetICN( isEvilInterface ? ICN::SPANBTNE : ICN::SPANBTN, 1 ); // To properly generate shadows and Blit the button we need to make transparent pixels in its released state the same as in the pressed state. CopyTransformLayer( _icnVsSprite[id][0], _icnVsSprite[id][1] ); break; } if ( isSameResourceAsLanguage && ( id == ICN::BUTTON_SMALLER_OKAY_EVIL || id == ICN::BUTTON_SMALLER_OKAY_GOOD ) ) { _icnVsSprite[id][0] = GetICN( isEvilInterface ? ICN::WINCMBBE : ICN::WINCMBTB, 0 ); _icnVsSprite[id][1] = GetICN( isEvilInterface ? ICN::WINCMBBE : ICN::WINCMBTB, 1 ); break; } int32_t textWidth = 86; const char * text = gettext_noop( "OKAY" ); if ( id == ICN::BUTTON_SMALLER_OKAY_EVIL || id == ICN::BUTTON_SMALLER_OKAY_GOOD ) { textWidth = 70; text = gettext_noop( "smallerButton|OKAY" ); } createNormalButton( _icnVsSprite[id][0], _icnVsSprite[id][1], textWidth, text, isEvilInterface ); break; } case ICN::BUTTON_SMALL_ACCEPT_GOOD: case ICN::BUTTON_SMALL_ACCEPT_EVIL: { _icnVsSprite[id].resize( 2 ); const bool isEvilInterface = ( id == ICN::BUTTON_SMALL_ACCEPT_EVIL ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( isEvilInterface ? ICN::SURRENDE : ICN::SURRENDR, 0 ); _icnVsSprite[id][1] = GetICN( isEvilInterface ? ICN::SURRENDE : ICN::SURRENDR, 1 ); // To properly generate shadows and Blit the button we need to make transparent pixels in its released state the same as in the pressed state. CopyTransformLayer( _icnVsSprite[id][0], _icnVsSprite[id][1] ); break; } const int32_t textWidth = 108; createNormalButton( _icnVsSprite[id][0], _icnVsSprite[id][1], textWidth, gettext_noop( "ACCEPT" ), isEvilInterface ); break; } case ICN::BUTTON_SMALL_DECLINE_GOOD: case ICN::BUTTON_SMALL_DECLINE_EVIL: { _icnVsSprite[id].resize( 2 ); const bool isEvilInterface = ( id == ICN::BUTTON_SMALL_DECLINE_EVIL ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( isEvilInterface ? ICN::SURRENDE : ICN::SURRENDR, 2 ); _icnVsSprite[id][1] = GetICN( isEvilInterface ? ICN::SURRENDE : ICN::SURRENDR, 3 ); // To properly generate shadows and Blit the button we need to make transparent pixels in its released state the same as in the pressed state. CopyTransformLayer( _icnVsSprite[id][0], _icnVsSprite[id][1] ); break; } const int32_t textWidth = 108; createNormalButton( _icnVsSprite[id][0], _icnVsSprite[id][1], textWidth, gettext_noop( "DECLINE" ), isEvilInterface ); break; } case ICN::BUTTON_SMALL_LEARN_GOOD: case ICN::BUTTON_SMALL_LEARN_EVIL: { _icnVsSprite[id].resize( 2 ); const bool isEvilInterface = ( id == ICN::BUTTON_SMALL_LEARN_EVIL ); const int baseIcnID = isEvilInterface ? ICN::SYSTEME : ICN::SYSTEM; if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( baseIcnID, 9 ); _icnVsSprite[id][1] = GetICN( baseIcnID, 10 ); break; } for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( baseIcnID, 11 + i ); } const fheroes2::FontColor buttonFontColor = isEvilInterface ? fheroes2::FontColor::GRAY : fheroes2::FontColor::WHITE; renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "LEARN" ), { 7, 5 }, { 5, 6 }, { 86, 16 }, buttonFontColor ); break; } case ICN::BUTTON_SMALL_TRADE_GOOD: case ICN::BUTTON_SMALL_TRADE_EVIL: { _icnVsSprite[id].resize( 2 ); const bool isEvilInterface = ( id == ICN::BUTTON_SMALL_TRADE_EVIL ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( isEvilInterface ? ICN::TRADPOSE : ICN::TRADPOST, 15 ); _icnVsSprite[id][1] = GetICN( isEvilInterface ? ICN::TRADPOSE : ICN::TRADPOST, 16 ); break; } const int baseIcnID = isEvilInterface ? ICN::SYSTEME : ICN::SYSTEM; for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; const Sprite & originalButton = GetICN( baseIcnID, 11 + i ); const int extendedAmount = 14; out.resize( originalButton.width() + extendedAmount, originalButton.height() ); out.reset(); const int widthEndPart = 4 + 2 * i; const int widthFirstPart = originalButton.width() - widthEndPart; const int widthMiddlePart = extendedAmount + i; const int offsetXEndPart = widthFirstPart + widthMiddlePart; const int startOffsetXMiddlePart = 36; // Copy left main body of button. fheroes2::Copy( originalButton, 0, 0, out, 0, 0, widthFirstPart, originalButton.height() ); // Copy middle body of button. fheroes2::Copy( originalButton, originalButton.width() - startOffsetXMiddlePart, 0, out, widthFirstPart, 0, widthMiddlePart, originalButton.height() ); // Copy terminating right margin of the button. fheroes2::Copy( originalButton, originalButton.width() - widthEndPart, 0, out, offsetXEndPart, 0, widthEndPart, originalButton.height() ); } const fheroes2::FontColor buttonFontColor = isEvilInterface ? fheroes2::FontColor::GRAY : fheroes2::FontColor::WHITE; renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "TRADE" ), { 6, 5 }, { 4, 6 }, { 100, 16 }, buttonFontColor ); break; } case ICN::BUTTON_SMALL_YES_GOOD: case ICN::BUTTON_SMALL_YES_EVIL: { _icnVsSprite[id].resize( 2 ); const bool isEvilInterface = ( id == ICN::BUTTON_SMALL_YES_EVIL ); const int baseIcnID = isEvilInterface ? ICN::SYSTEME : ICN::SYSTEM; if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( baseIcnID, 5 ); _icnVsSprite[id][1] = GetICN( baseIcnID, 6 ); break; } for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( baseIcnID, 11 + i ); } const fheroes2::FontColor buttonFontColor = isEvilInterface ? fheroes2::FontColor::GRAY : fheroes2::FontColor::WHITE; renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "YES" ), { 6, 5 }, { 5, 6 }, { 86, 16 }, buttonFontColor ); break; } case ICN::BUTTON_SMALL_NO_GOOD: case ICN::BUTTON_SMALL_NO_EVIL: { _icnVsSprite[id].resize( 2 ); const bool isEvilInterface = ( id == ICN::BUTTON_SMALL_NO_EVIL ); const int baseIcnID = isEvilInterface ? ICN::SYSTEME : ICN::SYSTEM; if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( baseIcnID, 7 ); _icnVsSprite[id][1] = GetICN( baseIcnID, 8 ); break; } for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( baseIcnID, 11 + i ); } const fheroes2::FontColor buttonFontColor = isEvilInterface ? fheroes2::FontColor::GRAY : fheroes2::FontColor::WHITE; renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "NO" ), { 6, 5 }, { 5, 6 }, { 86, 16 }, buttonFontColor ); break; } case ICN::BUTTON_SMALL_EXIT_GOOD: case ICN::BUTTON_SMALL_EXIT_EVIL: { _icnVsSprite[id].resize( 2 ); const bool isEvilInterface = ( id == ICN::BUTTON_SMALL_EXIT_EVIL ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( isEvilInterface ? ICN::VIEWARME : ICN::VIEWARMY, 3 ); _icnVsSprite[id][1] = GetICN( isEvilInterface ? ICN::VIEWARME : ICN::VIEWARMY, 4 ); break; } const int32_t textWidth = 85; createNormalButton( _icnVsSprite[id][0], _icnVsSprite[id][1], textWidth, gettext_noop( "EXIT" ), isEvilInterface ); break; } case ICN::BUTTON_EXIT_HEROES_MEETING: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( ICN::SWAPBTN, 0 ); _icnVsSprite[id][1] = GetICN( ICN::SWAPBTN, 1 ); // fix some wrong pixels in the original pressed state setButtonCornersTransparent( _icnVsSprite[id][1] ); break; } // The heroes meeting screen has an embedded shadow so the button needs to be fixed at the same size as the original one. // TODO: Remove the embedded shadow and button in the heroes meeting screen and use getTextAdaptedButton() instead. const int32_t textWidth = 70; createNormalButton( _icnVsSprite[id][0], _icnVsSprite[id][1], textWidth, gettext_noop( "smallerButton|EXIT" ), false ); break; } case ICN::BUTTON_EXIT_TOWN: { std::vector & buttonStates = _icnVsSprite[id]; buttonStates.resize( 2 ); if ( useOriginalResources() ) { buttonStates[0] = GetICN( ICN::TREASURY, 1 ); buttonStates[1] = GetICN( ICN::TREASURY, 2 ); break; } // Needs to be generated from original assets because it needs the black background from the pressed state. // TODO: Make a way to generate buttons with black background since it is needed for MAX and EXIT in the Well and Guilds. for ( int32_t i = 0; i < static_cast( buttonStates.size() ); ++i ) { Sprite & out = buttonStates[i]; out = GetICN( ICN::TREASURY, 1 + i ); // clean the button. Fill( out, 6 - i, 4 + i, 70, 17, getButtonFillingColor( i == 0 ) ); } const int32_t textWidth = 70; renderTextOnButton( buttonStates[0], buttonStates[1], gettext_noop( "smallerButton|EXIT" ), { 7, 5 }, { 6, 6 }, { textWidth, fheroes2::getFontHeight( fheroes2::FontSize::BUTTON_RELEASED ) }, fheroes2::FontColor::WHITE ); break; } case ICN::BUTTON_EXIT_PUZZLE_DIM_DOOR_GOOD: case ICN::BUTTON_EXIT_PUZZLE_DIM_DOOR_EVIL: { std::vector & buttonStates = _icnVsSprite[id]; buttonStates.resize( 2 ); const bool isEvilInterface = ( id == ICN::BUTTON_EXIT_PUZZLE_DIM_DOOR_EVIL ); const int originalButtonICN = isEvilInterface ? ICN::LGNDXTRE : ICN::LGNDXTRA; if ( useOriginalResources() ) { buttonStates[0] = GetICN( originalButtonICN, 4 ); buttonStates[1] = GetICN( originalButtonICN, 5 ); break; } // Needs to be generated from original assets because the background has a much darker shadow than normal. // TODO: Make the button generated as normal after removing the embedded shadow on the background. for ( int32_t i = 0; i < static_cast( buttonStates.size() ); ++i ) { Sprite & out = buttonStates[i]; out = GetICN( originalButtonICN, 4 + i ); // clean the button. Fill( out, 6 - i, 4 + i, 71 - i, 17, getButtonFillingColor( i == 0, !isEvilInterface ) ); } const int32_t textWidth = 71; renderTextOnButton( buttonStates[0], buttonStates[1], gettext_noop( "smallerButton|EXIT" ), { 6, 5 }, { 5, 6 }, { textWidth, fheroes2::getFontHeight( fheroes2::FontSize::BUTTON_RELEASED ) }, isEvilInterface ? fheroes2::FontColor::GRAY : fheroes2::FontColor::WHITE ); break; } case ICN::BUTTON_SMALL_DISMISS_GOOD: case ICN::BUTTON_SMALL_DISMISS_EVIL: { _icnVsSprite[id].resize( 2 ); const bool isEvilInterface = ( id == ICN::BUTTON_SMALL_DISMISS_EVIL ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( isEvilInterface ? ICN::VIEWARME : ICN::VIEWARMY, 1 ); _icnVsSprite[id][1] = GetICN( isEvilInterface ? ICN::VIEWARME : ICN::VIEWARMY, 2 ); break; } const int32_t textWidth = 110; createNormalButton( _icnVsSprite[id][0], _icnVsSprite[id][1], textWidth, gettext_noop( "DISMISS" ), isEvilInterface ); break; } case ICN::BUTTON_SMALL_UPGRADE_GOOD: case ICN::BUTTON_SMALL_UPGRADE_EVIL: { _icnVsSprite[id].resize( 2 ); const bool isEvilInterface = ( id == ICN::BUTTON_SMALL_UPGRADE_EVIL ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( isEvilInterface ? ICN::VIEWARME : ICN::VIEWARMY, 5 ); _icnVsSprite[id][1] = GetICN( isEvilInterface ? ICN::VIEWARME : ICN::VIEWARMY, 6 ); break; } const int32_t textWidth = 110; createNormalButton( _icnVsSprite[id][0], _icnVsSprite[id][1], textWidth, gettext_noop( "UPGRADE" ), isEvilInterface ); break; } case ICN::BUTTON_SMALL_RESTART_GOOD: case ICN::BUTTON_SMALL_RESTART_EVIL: { _icnVsSprite[id].resize( 2 ); const bool isEvilInterface = ( id == ICN::BUTTON_SMALL_RESTART_EVIL ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( isEvilInterface ? ICN::NON_UNIFORM_EVIL_RESTART_BUTTON : ICN::NON_UNIFORM_GOOD_RESTART_BUTTON, 0 ); _icnVsSprite[id][1] = GetICN( isEvilInterface ? ICN::NON_UNIFORM_EVIL_RESTART_BUTTON : ICN::NON_UNIFORM_GOOD_RESTART_BUTTON, 1 ); break; } const int32_t textWidth = 102; createNormalButton( _icnVsSprite[id][0], _icnVsSprite[id][1], textWidth, gettext_noop( "RESTART" ), isEvilInterface ); break; } case ICN::BUTTON_KINGDOM_EXIT: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( ICN::OVERVIEW, 4 ); _icnVsSprite[id][1] = GetICN( ICN::OVERVIEW, 5 ); break; } // Needs to be generated from original assets because the pressed state is 1px wider than normal. for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( ICN::OVERVIEW, 4 + i ); // clean the button. Fill( out, 6 - i, 4 + i, 89, 16, getButtonFillingColor( i == 0 ) ); } renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "EXIT" ), { 6, 5 }, { 5, 6 }, { 89, 16 }, fheroes2::FontColor::WHITE ); break; } case ICN::BUTTON_KINGDOM_HEROES: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( ICN::OVERVIEW, 0 ); _icnVsSprite[id][1] = GetICN( ICN::OVERVIEW, 1 ); break; } for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( ICN::OVERVIEW, 0 + i ); // clean the button. Fill( out, 5, 10 + i, 89, 20, getButtonFillingColor( i == 0 ) ); } renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "HEROES" ), { 6, 5 }, { 5, 6 }, { 89, 34 }, fheroes2::FontColor::WHITE ); break; } case ICN::BUTTON_KINGDOM_TOWNS: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( ICN::OVERVIEW, 2 ); _icnVsSprite[id][1] = GetICN( ICN::OVERVIEW, 3 ); break; } for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( ICN::OVERVIEW, 2 + i ); // clean the button. Fill( out, 6, 6 + i, 89 - i, 30, getButtonFillingColor( i == 0 ) ); } renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "TOWNS/\nCASTLES" ), { 6, 5 }, { 5, 6 }, { 90, 34 }, fheroes2::FontColor::WHITE ); break; } case ICN::BUTTON_MAPSIZE_SMALL: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( ICN::REQUESTS, 9 ); _icnVsSprite[id][1] = GetICN( ICN::REQUESTS, 10 ); break; } const int32_t textWidth = 46; createNormalButton( _icnVsSprite[id][0], _icnVsSprite[id][1], textWidth, gettext_noop( "S" ), false ); break; } case ICN::BUTTON_MAPSIZE_MEDIUM: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( ICN::REQUESTS, 11 ); _icnVsSprite[id][1] = GetICN( ICN::REQUESTS, 12 ); break; } const int32_t textWidth = 46; createNormalButton( _icnVsSprite[id][0], _icnVsSprite[id][1], textWidth, gettext_noop( "M" ), false ); break; } case ICN::BUTTON_MAPSIZE_LARGE: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( ICN::REQUESTS, 13 ); _icnVsSprite[id][1] = GetICN( ICN::REQUESTS, 14 ); break; } const int32_t textWidth = 46; createNormalButton( _icnVsSprite[id][0], _icnVsSprite[id][1], textWidth, gettext_noop( "L" ), false ); break; } case ICN::BUTTON_MAPSIZE_XLARGE: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( ICN::REQUESTS, 15 ); _icnVsSprite[id][1] = GetICN( ICN::REQUESTS, 16 ); break; } const int32_t textWidth = 46; createNormalButton( _icnVsSprite[id][0], _icnVsSprite[id][1], textWidth, gettext_noop( "X-L" ), false ); break; } case ICN::BUTTON_MAPSIZE_ALL: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( ICN::REQUESTS, 17 ); _icnVsSprite[id][1] = GetICN( ICN::REQUESTS, 18 ); break; } const int32_t textWidth = 58; createNormalButton( _icnVsSprite[id][0], _icnVsSprite[id][1], textWidth, gettext_noop( "ALL" ), false ); break; } case ICN::BUTTON_MAP_SELECT_EVIL: case ICN::BUTTON_MAP_SELECT_GOOD: { _icnVsSprite[id].resize( 2 ); const bool isEvilInterface = ( id == ICN::BUTTON_MAP_SELECT_EVIL ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( ICN::NGEXTRA, 64 ); _icnVsSprite[id][1] = GetICN( ICN::NGEXTRA, 65 ); if ( isEvilInterface ) { const std::vector & goodToEvilPalette = PAL::GetPalette( PAL::PaletteType::GOOD_TO_EVIL_INTERFACE ); fheroes2::ApplyPalette( _icnVsSprite[id][0], goodToEvilPalette ); fheroes2::ApplyPalette( _icnVsSprite[id][1], goodToEvilPalette ); } break; } getTextAdaptedButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "SELECT" ), ICN::EMPTY_MAP_SELECT_BUTTON, ICN::UNKNOWN ); if ( isEvilInterface ) { const std::vector & goodToEvilPalette = PAL::GetPalette( PAL::PaletteType::GOOD_TO_EVIL_INTERFACE ); fheroes2::ApplyPalette( _icnVsSprite[id][0], goodToEvilPalette ); fheroes2::ApplyPalette( _icnVsSprite[id][1], goodToEvilPalette ); } break; } case ICN::BUTTON_STANDARD_GAME: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( ICN::BTNNEWGM, 0 ); _icnVsSprite[id][1] = GetICN( ICN::BTNNEWGM, 1 ); break; } for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( ICN::BTNCOM, i ); // clean button. Fill( out, 13, 11, 113, 31, getButtonFillingColor( i == 0 ) ); } renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "STANDARD\nGAME" ), { 12, 5 }, { 11, 6 }, { 120, 47 }, fheroes2::FontColor::WHITE ); break; } case ICN::BUTTON_CAMPAIGN_GAME: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( ICN::BTNNEWGM, 2 ); _icnVsSprite[id][1] = GetICN( ICN::BTNNEWGM, 3 ); break; } for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( ICN::BTNCOM, i ); // clean button. Fill( out, 13, 11, 113, 31, getButtonFillingColor( i == 0 ) ); } renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "CAMPAIGN\nGAME" ), { 11, 5 }, { 10, 6 }, { 119, 47 }, fheroes2::FontColor::WHITE ); break; } case ICN::BUTTON_MULTIPLAYER_GAME: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( ICN::BTNNEWGM, 4 ); _icnVsSprite[id][1] = GetICN( ICN::BTNNEWGM, 5 ); break; } for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( ICN::BTNCOM, i ); // clean button. Fill( out, 13, 11, 113, 31, getButtonFillingColor( i == 0 ) ); } renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "MULTI-\nPLAYER\nGAME" ), { 12, 5 }, { 11, 6 }, { 117, 47 }, fheroes2::FontColor::WHITE ); break; } case ICN::BUTTON_LARGE_CANCEL: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( ICN::BTNNEWGM, 6 ); _icnVsSprite[id][1] = GetICN( ICN::BTNNEWGM, 7 ); break; } for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( ICN::BTNCOM, i ); // clean button. Fill( out, 13, 11, 113, 31, getButtonFillingColor( i == 0 ) ); } renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "CANCEL" ), { 12, 5 }, { 11, 6 }, { 117, 47 }, fheroes2::FontColor::WHITE ); break; } case ICN::BUTTON_LARGE_CONFIG: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( ICN::BTNDCCFG, 4 ); _icnVsSprite[id][1] = GetICN( ICN::BTNDCCFG, 5 ); break; } for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( ICN::BTNCOM, i ); // clean button. Fill( out, 13, 11, 113, 31, getButtonFillingColor( i == 0 ) ); } renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "CONFIG" ), { 12, 5 }, { 11, 6 }, { 117, 47 }, fheroes2::FontColor::WHITE ); break; } case ICN::BUTTON_ORIGINAL_CAMPAIGN: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( ICN::X_LOADCM, 0 ); _icnVsSprite[id][1] = GetICN( ICN::X_LOADCM, 1 ); break; } for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( ICN::BTNCOM, i ); // clean button. Fill( out, 13, 11, 113, 31, getButtonFillingColor( i == 0 ) ); } renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "ORIGINAL\nCAMPAIGN" ), { 12, 5 }, { 11, 6 }, { 117, 47 }, fheroes2::FontColor::WHITE ); break; } case ICN::BUTTON_EXPANSION_CAMPAIGN: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( ICN::X_LOADCM, 2 ); _icnVsSprite[id][1] = GetICN( ICN::X_LOADCM, 3 ); break; } for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( ICN::BTNCOM, i ); // clean button. Fill( out, 13, 11, 113, 31, getButtonFillingColor( i == 0 ) ); } renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "EXPANSION\nCAMPAIGN" ), { 12, 5 }, { 11, 6 }, { 117, 47 }, fheroes2::FontColor::WHITE ); break; } case ICN::BUTTON_HOT_SEAT: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( ICN::BTNMP, 0 ); _icnVsSprite[id][1] = GetICN( ICN::BTNMP, 1 ); break; } for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( ICN::BTNCOM, i ); // clean button. Fill( out, 13, 11, 113, 31, getButtonFillingColor( i == 0 ) ); } renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "HOT SEAT" ), { 11, 5 }, { 10, 6 }, { 120, 47 }, fheroes2::FontColor::WHITE ); break; } case ICN::BUTTON_2_PLAYERS: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( ICN::BTNHOTST, 0 ); _icnVsSprite[id][1] = GetICN( ICN::BTNHOTST, 1 ); break; } for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( ICN::BTNCOM, i ); // clean button. Fill( out, 13, 11, 113, 31, getButtonFillingColor( i == 0 ) ); } renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "2 PLAYERS" ), { 11, 5 }, { 10, 6 }, { 120, 47 }, fheroes2::FontColor::WHITE ); break; } case ICN::BUTTON_3_PLAYERS: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( ICN::BTNHOTST, 2 ); _icnVsSprite[id][1] = GetICN( ICN::BTNHOTST, 3 ); break; } for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( ICN::BTNCOM, i ); // clean button. Fill( out, 13, 11, 113, 31, getButtonFillingColor( i == 0 ) ); } renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "3 PLAYERS" ), { 11, 5 }, { 10, 6 }, { 120, 47 }, fheroes2::FontColor::WHITE ); break; } case ICN::BUTTON_4_PLAYERS: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( ICN::BTNHOTST, 4 ); _icnVsSprite[id][1] = GetICN( ICN::BTNHOTST, 5 ); break; } for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( ICN::BTNCOM, i ); // clean button. Fill( out, 13, 11, 113, 31, getButtonFillingColor( i == 0 ) ); } renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "4 PLAYERS" ), { 11, 5 }, { 10, 6 }, { 120, 47 }, fheroes2::FontColor::WHITE ); break; } case ICN::BUTTON_5_PLAYERS: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( ICN::BTNHOTST, 6 ); _icnVsSprite[id][1] = GetICN( ICN::BTNHOTST, 7 ); break; } for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( ICN::BTNCOM, i ); // clean button. Fill( out, 13, 11, 113, 31, getButtonFillingColor( i == 0 ) ); } renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "5 PLAYERS" ), { 11, 5 }, { 10, 6 }, { 120, 47 }, fheroes2::FontColor::WHITE ); break; } case ICN::BUTTON_6_PLAYERS: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( ICN::BTNHOTST, 8 ); _icnVsSprite[id][1] = GetICN( ICN::BTNHOTST, 9 ); break; } for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( ICN::BTNCOM, i ); // clean button. Fill( out, 13, 11, 113, 31, getButtonFillingColor( i == 0 ) ); } renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "6 PLAYERS" ), { 11, 5 }, { 10, 6 }, { 120, 47 }, fheroes2::FontColor::WHITE ); break; } case ICN::BTNGIFT_GOOD: case ICN::BTNGIFT_EVIL: { _icnVsSprite[id].resize( 2 ); const bool isEvilInterface = ( id == ICN::BTNGIFT_EVIL ); const int baseIcnId = isEvilInterface ? ICN::SYSTEME : ICN::SYSTEM; for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( baseIcnId, 11 + i ); } const fheroes2::FontColor buttonFontColor = isEvilInterface ? fheroes2::FontColor::GRAY : fheroes2::FontColor::WHITE; renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "GIFT" ), { 5, 5 }, { 4, 6 }, { 88, 16 }, buttonFontColor ); break; } case ICN::BUYMAX: { _icnVsSprite[id].resize( 2 ); getTextAdaptedButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "MAX" ), ICN::EMPTY_GUILDWELL_BUTTON, ICN::UNKNOWN ); break; } case ICN::BUTTON_GUILDWELL_EXIT: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( ICN::WELLXTRA, 0 ); _icnVsSprite[id][1] = GetICN( ICN::WELLXTRA, 1 ); break; } fheroes2::getTextAdaptedButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "EXIT" ), ICN::EMPTY_GUILDWELL_BUTTON, ICN::UNKNOWN ); break; } case ICN::BUTTON_VIEWWORLD_EXIT_GOOD: case ICN::BUTTON_VIEWWORLD_EXIT_EVIL: { _icnVsSprite[id].resize( 2 ); const bool isEvilInterface = ( id == ICN::BUTTON_VIEWWORLD_EXIT_EVIL ); const int baseIcnId = isEvilInterface ? ICN::LGNDXTRE : ICN::LGNDXTRA; for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( baseIcnId, 0 + i ); // clean the button. Fill( out, 27 - i, 4 + i, 25, 26, getButtonFillingColor( i == 0, !isEvilInterface ) ); } const fheroes2::FontColor buttonFontColor = isEvilInterface ? fheroes2::FontColor::GRAY : fheroes2::FontColor::WHITE; renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "EXIT" ), { 6, 5 }, { 5, 6 }, { 71, 27 }, buttonFontColor ); break; } case ICN::GOOD_CAMPAIGN_BUTTONS: case ICN::EVIL_CAMPAIGN_BUTTONS: { _icnVsSprite[id].resize( 10 ); if ( useOriginalResources() ) { const bool isEvilInterface = id == ICN::EVIL_CAMPAIGN_BUTTONS; const int originalIcnId = isEvilInterface ? ICN::CAMPXTRE : ICN::CAMPXTRG; const int buttonBackground = isEvilInterface ? ICN::STONEBAK_EVIL : ICN::STONEBAK; // The evil buttons' released state are 2 pixels wider. const int offsetEvilX = isEvilInterface ? 2 : 0; // remove embedded shadows so that we can generate shadows with our own code later for ( uint32_t i = 0; i < 8; i += 2 ) { // released const Sprite & originalReleased = GetICN( originalIcnId, i ); Sprite & released = _icnVsSprite[id][i]; released.resize( originalReleased.width() - 6 + offsetEvilX, originalReleased.height() - 8 ); released.reset(); Copy( originalReleased, 6 - offsetEvilX, 0, released, 0, 0, originalReleased.width() - 1, originalReleased.height() - 8 ); // pressed const Sprite & originalPressed = GetICN( originalIcnId, i + 1 ); Sprite & pressed = _icnVsSprite[id][i + 1]; pressed.resize( originalPressed.width(), originalPressed.height() ); pressed.reset(); Copy( originalPressed, 0, 1, pressed, 0, 1, originalPressed.width() - 1, originalPressed.height() ); setButtonCornersTransparent( released ); fheroes2::makeTransparentBackground( released, pressed, buttonBackground ); } // Generate the DIFFICULTY button because it is not present in the original resources fheroes2::getTextAdaptedButton( _icnVsSprite[id][8], _icnVsSprite[id][9], gettext_noop( "DIFFICULTY" ), isEvilInterface ? ICN::EMPTY_EVIL_BUTTON : ICN::EMPTY_GOOD_BUTTON, buttonBackground ); break; } createCampaignButtonSet( id, { gettext_noop( "VIEW INTRO" ), gettext_noop( "RESTART" ), gettext_noop( "OKAY" ), gettext_noop( "CANCEL" ), gettext_noop( "DIFFICULTY" ) } ); break; } case ICN::POL_CAMPAIGN_BUTTONS: { _icnVsSprite[id].resize( 10 ); const int baseIcnId = ICN::X_CMPBTN; if ( useOriginalResources() ) { for ( uint32_t i = 0; i < 8; i += 2 ) { // released const Sprite & originalReleased = GetICN( baseIcnId, i ); Sprite & released = _icnVsSprite[id][i]; released.resize( originalReleased.width() + 1, originalReleased.height() + 1 ); released.reset(); Copy( originalReleased, 0, 0, released, 1, 0, originalReleased.width(), originalReleased.height() ); const Sprite & originalPressed = GetICN( baseIcnId, i + 1 ); // the released state is missing the darker borders of the pressed state Copy( originalPressed, 0, 0, released, 0, 1, 1, originalPressed.height() ); Copy( originalPressed, 0, 2, released, 0, originalPressed.height() - 1, 1, 2 ); Copy( originalPressed, 1, originalPressed.height() - 1, released, 1, originalPressed.height(), originalPressed.width(), 1 ); Copy( originalPressed, 0, 2, released, 1, originalPressed.height(), 1, 1 ); Copy( originalReleased, 0, 2, released, 1, originalPressed.height() - 2, 1, 1 ); Copy( originalReleased, 0, 2, released, 2, originalPressed.height() - 1, 1, 1 ); Copy( originalReleased, 0, 2, released, 1, originalPressed.height() - 1, 1, 1 ); Copy( originalReleased, 1, 2, released, 2, originalPressed.height() - 2, 1, 1 ); // pressed state Sprite & pressed = _icnVsSprite[id][i + 1]; pressed.resize( originalPressed.width() + 1, originalPressed.height() + 1 ); pressed.reset(); Copy( originalPressed, 0, 0, pressed, 0, 1, originalPressed.width(), originalPressed.height() ); // pressed state has incomplete lower left corner Copy( originalPressed, 0, 2, pressed, 0, originalPressed.height() - 1, 1, 2 ); Copy( originalPressed, 0, 2, pressed, 1, originalPressed.height(), 1, 1 ); Copy( originalPressed, 1, 2, pressed, 1, originalPressed.height() - 1, 1, 1 ); _icnVsSprite[id][i + 1].setPosition( 0, 0 ); fheroes2::makeTransparentBackground( released, pressed, ICN::STONEBAK_SMALL_POL ); } // generate the DIFFICULTY button as it is not present in the original resources fheroes2::getTextAdaptedButton( _icnVsSprite[id][8], _icnVsSprite[id][9], gettext_noop( "DIFFICULTY" ), ICN::EMPTY_POL_BUTTON, ICN::STONEBAK_SMALL_POL ); break; } createCampaignButtonSet( id, { gettext_noop( "VIEW INTRO" ), gettext_noop( "RESTART" ), gettext_noop( "OKAY" ), gettext_noop( "CANCEL" ), gettext_noop( "DIFFICULTY" ) } ); break; } case ICN::BUTTON_SMALL_MIN_GOOD: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { // Write the "MIN" text on original assets ICNs. for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { _icnVsSprite[id][i] = GetICN( ICN::BUTTON_SMALL_MAX_GOOD, 0 + i ); // Clean the button text. Blit( GetICN( ICN::SYSTEM, 11 + i ), 10 - i, 4 + i, _icnVsSprite[id][i], 6 - i, 4 + i, 50, 16 ); } renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "MIN" ), { 6, 5 }, { 5, 6 }, { 52, 16 }, fheroes2::FontColor::WHITE ); break; } createNormalButton( _icnVsSprite[id][0], _icnVsSprite[id][1], 61, gettext_noop( "MIN" ), false ); break; } case ICN::BUTTON_SMALL_MIN_EVIL: { _icnVsSprite[id].resize( 2 ); createNormalButton( _icnVsSprite[id][0], _icnVsSprite[id][1], 61, gettext_noop( "MIN" ), true ); break; } case ICN::BUTTON_SMALL_MAX_GOOD: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { // The original assets ICN contains button with shadow. We crop only the button. _icnVsSprite[id][0] = fheroes2::Crop( GetICN( ICN::RECRUIT, 4 ), 5, 0, 60, 25 ); _icnVsSprite[id][1] = fheroes2::Crop( GetICN( ICN::RECRUIT, 5 ), 5, 0, 60, 25 ); // To properly generate shadows and Blit the button we need to make some pixels transparent. for ( fheroes2::Sprite & image : _icnVsSprite[id] ) { setButtonCornersTransparent( image ); } break; } createNormalButton( _icnVsSprite[id][0], _icnVsSprite[id][1], 61, gettext_noop( "MAX" ), false ); break; } case ICN::BUTTON_SMALL_MAX_EVIL: { _icnVsSprite[id].resize( 2 ); createNormalButton( _icnVsSprite[id][0], _icnVsSprite[id][1], 61, gettext_noop( "MAX" ), true ); break; } case ICN::BUTTON_EXIT_GOOD: { _icnVsSprite[id].resize( 2 ); fheroes2::getTextAdaptedButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "EXIT" ), ICN::EMPTY_GOOD_BUTTON, ICN::STONEBAK ); break; } case ICN::BUTTON_RESET_GOOD: { _icnVsSprite[id].resize( 2 ); createNormalButton( _icnVsSprite[id][0], _icnVsSprite[id][1], 80, gettext_noop( "RESET" ), false ); break; } case ICN::BUTTON_START_GOOD: { _icnVsSprite[id].resize( 2 ); createNormalButton( _icnVsSprite[id][0], _icnVsSprite[id][1], 80, gettext_noop( "START" ), false ); break; } case ICN::BUTTON_CASTLE_GOOD: case ICN::BUTTON_CASTLE_EVIL: { _icnVsSprite[id].resize( 2 ); createNormalButton( _icnVsSprite[id][0], _icnVsSprite[id][1], 80, gettext_noop( "CASTLE" ), id == ICN::BUTTON_CASTLE_EVIL ); break; } case ICN::BUTTON_TOWN_GOOD: case ICN::BUTTON_TOWN_EVIL: { _icnVsSprite[id].resize( 2 ); createNormalButton( _icnVsSprite[id][0], _icnVsSprite[id][1], 80, gettext_noop( "TOWN" ), id == ICN::BUTTON_TOWN_EVIL ); break; } case ICN::UNIFORM_EVIL_MAX_BUTTON: case ICN::UNIFORM_EVIL_MIN_BUTTON: case ICN::UNIFORM_GOOD_MAX_BUTTON: case ICN::UNIFORM_GOOD_MIN_BUTTON: { _icnVsSprite[id].resize( 2 ); const bool isEvilInterface = ( id == ICN::UNIFORM_EVIL_MAX_BUTTON || id == ICN::UNIFORM_EVIL_MIN_BUTTON ); const int baseIcnId = isEvilInterface ? ICN::SYSTEME : ICN::SYSTEM; for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; const Sprite & originalButton = GetICN( ICN::RECRUIT, 4 + i ); out.resize( originalButton.width() + 4, originalButton.height() - 5 + i ); out.reset(); const Sprite & emptyButton = GetICN( baseIcnId, 11 + i ); // Copy left main body of button. fheroes2::Copy( emptyButton, 0, 0, out, 0, 0, originalButton.width() - 3 + i, originalButton.height() - 5 + i ); // Copy terminating right margin of the button. fheroes2::Copy( emptyButton, 89 + i, 0, out, 63 + i, 0, 7 - i, originalButton.height() - i ); } const char * text( ( id == ICN::UNIFORM_GOOD_MIN_BUTTON || id == ICN::UNIFORM_EVIL_MIN_BUTTON ) ? "MIN" : "MAX" ); const fheroes2::FontColor buttonFontColor = isEvilInterface ? fheroes2::FontColor::GRAY : fheroes2::FontColor::WHITE; renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( text ), { 6, 5 }, { 4, 6 }, { 62, 16 }, buttonFontColor ); break; } case ICN::UNIFORM_GOOD_OKAY_BUTTON: case ICN::UNIFORM_EVIL_OKAY_BUTTON: { _icnVsSprite[id].resize( 2 ); const bool isEvilInterface = ( id == ICN::UNIFORM_EVIL_OKAY_BUTTON ); const int baseIcnId = isEvilInterface ? ICN::SYSTEME : ICN::SYSTEM; if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( baseIcnId, 1 ); _icnVsSprite[id][1] = GetICN( baseIcnId, 2 ); break; } for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( baseIcnId, 11 + i ); } const fheroes2::FontColor buttonFontColor = isEvilInterface ? fheroes2::FontColor::GRAY : fheroes2::FontColor::WHITE; renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "OKAY" ), { 6, 5 }, { 5, 6 }, { 86, 16 }, buttonFontColor ); break; } case ICN::UNIFORM_GOOD_CANCEL_BUTTON: case ICN::UNIFORM_EVIL_CANCEL_BUTTON: { _icnVsSprite[id].resize( 2 ); const bool isEvilInterface = ( id == ICN::UNIFORM_EVIL_CANCEL_BUTTON ); if ( useOriginalResources() ) { const int baseIcnId = isEvilInterface ? ICN::SYSTEME : ICN::SYSTEM; _icnVsSprite[id][0] = GetICN( baseIcnId, 3 ); _icnVsSprite[id][1] = GetICN( baseIcnId, 4 ); break; } getTextAdaptedButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "CANCEL" ), isEvilInterface ? ICN::EMPTY_EVIL_BUTTON : ICN::EMPTY_GOOD_BUTTON, isEvilInterface ? ICN::UNIFORMBAK_EVIL : ICN::UNIFORMBAK_GOOD ); break; } case ICN::UNIFORM_GOOD_EXIT_BUTTON: case ICN::UNIFORM_EVIL_EXIT_BUTTON: { _icnVsSprite[id].resize( 2 ); const bool isEvilInterface = ( id == ICN::UNIFORM_EVIL_EXIT_BUTTON ); const int baseIcnId = isEvilInterface ? ICN::TRADPOSE : ICN::TRADPOST; if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( baseIcnId, 17 ); _icnVsSprite[id][1] = GetICN( baseIcnId, 18 ); break; } for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( isEvilInterface ? ICN::SYSTEME : ICN::SYSTEM, 11 + i ); } const fheroes2::FontColor buttonFontColor = isEvilInterface ? fheroes2::FontColor::GRAY : fheroes2::FontColor::WHITE; renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "EXIT" ), { 7, 5 }, { 6, 6 }, { 86, 16 }, buttonFontColor ); break; } case ICN::BUTTON_VERTICAL_DISMISS: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { // The original DISMISS button has a broken transform layer and thinner pressed state than released state, // so we use our empty vertical button as a template _icnVsSprite[id][0] = GetICN( ICN::EMPTY_VERTICAL_GOOD_BUTTON, 0 ); _icnVsSprite[id][1] = GetICN( ICN::EMPTY_VERTICAL_GOOD_BUTTON, 1 ); // Copy the DISMISS text back from the original button const Sprite & originalReleased = GetICN( ICN::HSBTNS, 0 ); const Sprite & originalPressed = GetICN( ICN::HSBTNS, 1 ); Copy( originalReleased, 9, 2, _icnVsSprite[id][0], 5, 2, 21, 112 ); Copy( originalPressed, 9, 5, _icnVsSprite[id][1], 4, 5, 19, 111 ); fheroes2::makeTransparentBackground( _icnVsSprite[id][0], _icnVsSprite[id][1], ICN::REDBAK_SMALL_VERTICAL ); break; } // We need to temporarily remove the letter specific X offsets in the font because if not the letters will // be off-centered when we are displaying one letter per line const ButtonFontOffsetRestorer fontReleased( _icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED], -1 ); const ButtonFontOffsetRestorer fontPressed( _icnVsSprite[ICN::BUTTON_GOOD_FONT_PRESSED], -1 ); getTextAdaptedButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "D\nI\nS\nM\nI\nS\nS" ), ICN::EMPTY_VERTICAL_GOOD_BUTTON, ICN::REDBAK_SMALL_VERTICAL ); break; } case ICN::BUTTON_VERTICAL_EXIT: { _icnVsSprite[id].resize( 2 ); if ( useOriginalResources() ) { // The original EXIT button has a broken transform layer and we need to remove the shadows, // so we use our empty vertical button as a template _icnVsSprite[id][0] = GetICN( ICN::EMPTY_VERTICAL_GOOD_BUTTON, 0 ); _icnVsSprite[id][1] = GetICN( ICN::EMPTY_VERTICAL_GOOD_BUTTON, 1 ); // Copy the EXIT text back from the original button const Sprite & originalReleased = GetICN( ICN::HSBTNS, 2 ); const Sprite & originalPressed = GetICN( ICN::HSBTNS, 3 ); Copy( originalReleased, 4, 2, _icnVsSprite[id][0], 5, 2, 21, 112 ); Copy( originalPressed, 3, 5, _icnVsSprite[id][1], 4, 5, 19, 111 ); fheroes2::makeTransparentBackground( _icnVsSprite[id][0], _icnVsSprite[id][1], ICN::REDBAK_SMALL_VERTICAL ); break; } // We need to temporarily remove the letter specific X offsets in the font because if not the letters will // be off-centered when we are displaying one letter per line const ButtonFontOffsetRestorer fontReleased( _icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED], -1 ); const ButtonFontOffsetRestorer fontPressed( _icnVsSprite[ICN::BUTTON_GOOD_FONT_PRESSED], -1 ); getTextAdaptedButton( _icnVsSprite[id][0], _icnVsSprite[id][1], gettext_noop( "E\nX\nI\nT" ), ICN::EMPTY_VERTICAL_GOOD_BUTTON, ICN::REDBAK_SMALL_VERTICAL ); break; } case ICN::BUTTON_HSCORES_VERTICAL_CAMPAIGN: case ICN::BUTTON_HSCORES_VERTICAL_EXIT: case ICN::BUTTON_HSCORES_VERTICAL_STANDARD: { _icnVsSprite[id].resize( 2 ); const int originalID = ICN::HISCORE; uint32_t originalICNIndex = 0; if ( id == ICN::BUTTON_HSCORES_VERTICAL_STANDARD ) { originalICNIndex = 2; } else if ( id == ICN::BUTTON_HSCORES_VERTICAL_EXIT ) { originalICNIndex = 4; } else { assert( id == ICN::BUTTON_HSCORES_VERTICAL_CAMPAIGN ); } if ( useOriginalResources() ) { _icnVsSprite[id][0] = GetICN( originalID, originalICNIndex ); _icnVsSprite[id][1] = GetICN( originalID, originalICNIndex + 1 ); break; } for ( size_t i = 0; i < _icnVsSprite[id].size(); ++i ) { const Sprite & originalButton = GetICN( originalID, originalICNIndex + static_cast( i ) ); Sprite & out = _icnVsSprite[id][i]; out = originalButton; // Clean the button Fill( out, 4 - static_cast( i ), 4 + static_cast( i ), 19, 123, getButtonFillingColor( i == 0 ) ); } const char * buttonText; if ( id == ICN::BUTTON_HSCORES_VERTICAL_CAMPAIGN ) { buttonText = gettext_noop( "C\nA\nM\nP\nA\nI\nG\nN" ); } else if ( id == ICN::BUTTON_HSCORES_VERTICAL_STANDARD ) { buttonText = gettext_noop( "S\nT\nA\nN\nD\nA\nR\nD" ); } else { buttonText = gettext_noop( "E\nX\nI\nT" ); } ButtonFontOffsetRestorer fontRestorerReleased( _icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED], -1 ); ButtonFontOffsetRestorer fontRestorerPressed( _icnVsSprite[ICN::BUTTON_GOOD_FONT_PRESSED], -1 ); renderTextOnButton( _icnVsSprite[id][0], _icnVsSprite[id][1], buttonText, { 4, 4 }, { 3, 5 }, { 21, 124 }, fheroes2::FontColor::WHITE ); break; } default: // You're calling this function for non-specified ICN id. Check your logic! // Did you add a new image for one language without generating a default // for other languages? assert( 0 ); break; } } bool generateGermanSpecificImages( const int id ) { switch ( id ) { case ICN::BTNBATTLEONLY: _icnVsSprite[id].resize( 2 ); for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( ICN::BTNNEWGM, 6 + i ); // Clean the button Fill( out, 25, 18, 88, 23, getButtonFillingColor( i == 0 ) ); // Add 'K' Blit( GetICN( ICN::BTNDCCFG, 4 + i ), 34 - i, 23, out, 40 - i, 23, 12, 14 ); //'Add 'A' Blit( GetICN( ICN::BTNNEWGM, 4 + i ), 56 - i, 23, out, 52 - i, 23, 13, 14 ); Blit( out, 20, 20, out, 52 - i + 12, 25, 3, 3 ); // Add 'M' Blit( GetICN( ICN::BTNNEWGM, 4 + i ), 39 - i, 8, out, 65 - i, 23, 14, 14 ); // Add 'F' Blit( GetICN( ICN::BTNDCCFG, 4 + i ), 70 - i, 23, out, 87 - i, 23, 10, 14 ); // Add 'P' Blit( GetICN( ICN::BTNNEWGM, 4 + i ), 36 - i, 23, out, 78 - i, 23, 10, 14 ); } return true; case ICN::BUTTON_SMALL_MIN_GOOD: _icnVsSprite[id].resize( 2 ); for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( ICN::RECRUIT, 4 + i ); // clean the button Blit( GetICN( ICN::SYSTEM, 11 + i ), 10, 6 + i, out, 30 - 2 * i, 5 + i, 31, 15 ); // add 'IN' Copy( GetICN( ICN::APANEL, 4 + i ), 23 - i, 22 + i, out, 33 - i, 6 + i, 8, 14 ); // letter 'I' Copy( GetICN( ICN::APANEL, 4 + i ), 31 - i, 22 + i, out, 44 - i, 6 + i, 17, 14 ); // letter 'N' } return true; default: break; } return false; } bool generateFrenchSpecificImages( const int id ) { switch ( id ) { case ICN::BTNBATTLEONLY: _icnVsSprite[id].resize( 2 ); for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( ICN::BTNNEWGM, 6 + i ); // Clean the button Fill( out, 25, 18, 88, 23, getButtonFillingColor( i == 0 ) ); const int32_t secondLine = 28; // Add 'MODE' Blit( GetICN( ICN::BTNNEWGM, 4 + i ), 40 - i, 13, out, 45 - i, 13, 50, 15 ); // Clean up 'MODE' Blit( GetICN( ICN::BTNEMAIN, 0 + i ), 114 - i, 18, out, 94 - i, 18, 1, 10 ); // Add 'BA' Blit( GetICN( ICN::BTNBAUD, 2 + i ), 42 - i, 28, out, 28 - i, secondLine, 22, 15 ); // Clean up 'BA' Blit( GetICN( ICN::BTNBAUD, 2 + i ), 42 - i, 31, out, 39 - i, secondLine, 1, 1 ); Blit( GetICN( ICN::BTNBAUD, 2 + i ), 39 - i, 31, out, 49 - i, secondLine + 4, 1, 2 ); // Add 'T' Blit( GetICN( ICN::BTNDC, 2 + i ), 89 - i, 21, out, 50 - i, secondLine, 12, 15 ); // Clean up 'AT' Blit( GetICN( ICN::BTNDC, 2 + i ), 89 - i, 18, out, 50 - i, secondLine, 1, 1 ); Blit( GetICN( ICN::BTNDC, 2 + i ), 92 - ( 5 * i ), 27 - i, out, 49 - i, secondLine + 4 + i, 1, 3 ); // Add 'AI'. Blit( GetICN( ICN::BTNMP, 6 + i ), 56 - i, 13, out, 62 - i, secondLine, 18, 15 ); // Clean up 'TA' Blit( GetICN( ICN::BTNBAUD, 2 + i ), 51 - i, 40, out, 60 - i, secondLine + 12, 3, 3 ); // Add 'LLE' Blit( GetICN( ICN::BTNEMAIN, 0 + i ), 85 - i, 13, out, 81 - i, secondLine, 31, 15 ); // Clean up "IL" Blit( GetICN( ICN::BTNEMAIN, 0 + i ), 85 - i, 18, out, 81 - i, secondLine + 7, 1, 1 ); Blit( GetICN( ICN::BTNEMAIN, 0 + i ), 94 - i, 17, out, 80 - i, secondLine + 4, 2, 2 ); Blit( GetICN( ICN::BTNEMAIN, 0 + i ), 93 - i, 25, out, 79 - i, secondLine + 12, 3, 3 ); Blit( GetICN( ICN::BTNDC, 4 + i ), 23 - i, 8, out, 79 - i, secondLine + 5, 1, 10 ); Blit( GetICN( ICN::BTNMP, 6 + i ), 73 - i, 22, out, 79 - i, secondLine + 9, 1, 1 ); } return true; case ICN::BTNGIFT_GOOD: _icnVsSprite[id].resize( 2 ); for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( ICN::TRADPOST, 17 + i ); // clean the button Fill( out, 33, 5, 31, 16, getButtonFillingColor( i == 0 ) ); const int32_t offsetY = 5; // Add 'D' const int32_t offsetXD = 14; Blit( GetICN( ICN::CPANEL, 4 + i ), 48 - i, 28 + i, out, offsetXD - i, offsetY + i, 10, 15 ); // Clean up 'D' and restore button ornament Blit( GetICN( ICN::CPANEL, 4 + i ), 48 - i, 36, out, offsetXD - 1 - i, offsetY + 4 + i, 1, 1 ); Blit( GetICN( ICN::CPANEL, 4 + i ), 48 - i, 35, out, offsetXD - i, offsetY + 9 + i, 1, 2 ); Blit( GetICN( ICN::CPANEL, 4 + i ), 48 - i, 35, out, offsetXD - 1 - i, offsetY + 13 + i, 1, 1 ); Fill( out, offsetXD + 9 - i, offsetY + 13 + i, 1, 1, getButtonFillingColor( i == 0 ) ); Blit( GetICN( ICN::TRADPOST, 17 + i ), offsetXD, offsetY, out, offsetXD, offsetY, 1, 1 ); // Add 'O' const int32_t offsetXO = 10; Blit( GetICN( ICN::CAMPXTRG, i ), 40 - ( 7 * i ), 5 + i, out, offsetXD + offsetXO + 1 - i, offsetY + i, 13 - i, 15 ); // Clean up 'DO' Blit( GetICN( ICN::CPANEL, 4 + i ), 51 - i, 34, out, offsetXD + offsetXO - i, offsetY + 5, 2, 2 ); Blit( GetICN( ICN::CPANEL, 4 + i ), 51 - i, 34, out, offsetXD + offsetXO - i, offsetY + 7, 1, 1 + i ); Blit( GetICN( ICN::CPANEL, 4 + i ), 55 - i, 28 + i, out, offsetXD + 9 - i, offsetY + 2 + i, 3, 3 ); Fill( out, offsetXD + 11 - i, offsetY + i, 2, 2, getButtonFillingColor( i == 0 ) ); // Add 'N' const int32_t offsetXN = 13; Blit( GetICN( ICN::TRADPOST, 17 + i ), 50 - i, 5, out, offsetXD + offsetXO + offsetXN - i, offsetY, 14, 15 ); // Clean up 'ON' Fill( out, offsetXD + offsetXO + offsetXN, offsetY, 1, 1, getButtonFillingColor( i == 0 ) ); Fill( out, offsetXD + offsetXO + offsetXN - i, offsetY + 9, 1, 1, getButtonFillingColor( i == 0 ) ); // Add 'N' Blit( GetICN( ICN::TRADPOST, 17 + i ), 50 - i, 5, out, offsetXD + 10 + offsetXN + offsetXN - i, offsetY, 14, 15 ); // Clean up 'NN' Fill( out, offsetXD + offsetXO + offsetXN + offsetXN - i, offsetY + 9, 1, 1, getButtonFillingColor( i == 0 ) ); // Add 'ER' Blit( GetICN( ICN::CAMPXTRG, 2 + i ), 75 - ( 8 * i ), 5, out, offsetXD + offsetXO + offsetXN + offsetXN + offsetXN - ( 2 * i ), offsetY, 23, 15 ); // Restore button ornament Blit( GetICN( ICN::TRADPOST, 17 + i ), offsetXD + offsetXO + offsetXN + offsetXN + offsetXN + 20, offsetY, out, offsetXD + offsetXO + offsetXN + offsetXN + offsetXN + 20, offsetY, 1, 1 ); Blit( GetICN( ICN::TRADPOST, 17 + i ), offsetXD + offsetXO + offsetXN + offsetXN + offsetXN + 21, offsetY + 1, out, offsetXD + offsetXO + offsetXN + offsetXN + offsetXN + 21, offsetY + 1, 2, 3 ); Blit( GetICN( ICN::TRADPOST, 17 + i ), offsetXD + offsetXO + offsetXN + offsetXN + offsetXN + 20, offsetY, out, offsetXD + offsetXO + offsetXN + offsetXN + offsetXN + 21, offsetY + 4, 1, 1 ); } return true; case ICN::BTNGIFT_EVIL: _icnVsSprite[id].resize( 2 ); for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( ICN::TRADPOSE, 17 + i ); // clean the button Fill( out, 33, 5, 31, 16, getButtonFillingColor( i == 0, false ) ); const int32_t offsetY = 5; // Add 'D' const int32_t offsetXD = 14; Blit( GetICN( ICN::CPANELE, 4 + i ), 48 - i, 28 + i, out, offsetXD - i, offsetY + i, 10, 15 ); // Clean up 'D' and restore button ornament Blit( GetICN( ICN::CPANELE, 4 + i ), 48 - i, 36, out, offsetXD - 1 - i, offsetY + 4 + i, 1, 1 ); Blit( GetICN( ICN::CPANELE, 4 + i ), 48 - i, 35, out, offsetXD - i, offsetY + 9 + i, 1, 2 ); Blit( GetICN( ICN::CPANELE, 4 + i ), 48 - i, 35, out, offsetXD - 1 - i, offsetY + 13 + i, 1, 1 ); Fill( out, offsetXD + 9 - i, offsetY + 13 + i, 1, 1, getButtonFillingColor( i == 0, false ) ); Blit( GetICN( ICN::TRADPOSE, 17 + i ), offsetXD, offsetY, out, offsetXD, offsetY, 1, 1 ); Fill( out, offsetXD + 9 - i, offsetY + i, 1, 1, getButtonFillingColor( i == 0, false ) ); // Add 'O' const int32_t offsetXO = 10; Blit( GetICN( ICN::APANELE, 4 + i ), 50 - i, 20 + i, out, offsetXD + offsetXO + 1 - i, offsetY + i, 13 - i, 14 ); // Clean up 'DO' Blit( GetICN( ICN::CPANELE, 4 + i ), 51 - i, 34, out, offsetXD + offsetXO - i, offsetY + 5, 2, 2 ); Blit( GetICN( ICN::CPANELE, 4 + i ), 51 - i, 34, out, offsetXD + offsetXO - i, offsetY + 7, 1, 1 + i ); Blit( GetICN( ICN::CPANELE, 4 + i ), 56 - i, 28 + i, out, offsetXD + 10 - i, offsetY + 2 + i, 1, 3 ); Blit( GetICN( ICN::CPANELE, 4 + i ), 56 - i, 28 + i, out, offsetXD + 11 - i, offsetY + 3 + i, 1, 2 ); Fill( out, offsetXD + 11 - i, offsetY + i, 3, 3, getButtonFillingColor( i == 0, false ) ); Fill( out, offsetXD + 12 - i, offsetY + 3 + i, 1, 2, getButtonFillingColor( i == 0, false ) ); // Add 'N' const int32_t offsetXN = 13; Blit( GetICN( ICN::TRADPOSE, 17 + i ), 50 - i, 5, out, offsetXD + offsetXO + offsetXN - i, offsetY, 14, 15 ); // Clean up 'ON' Fill( out, offsetXD + offsetXO + offsetXN - 1 - i, offsetY + 11 + i, 1, 3, getButtonFillingColor( i == 0, false ) ); Fill( out, offsetXD + offsetXO + offsetXN - i, offsetY, 1, 1, getButtonFillingColor( i == 0, false ) ); Fill( out, offsetXD + offsetXO + offsetXN - i, offsetY + 9, 1, 1, getButtonFillingColor( i == 0, false ) ); // Add 'N' Blit( GetICN( ICN::TRADPOSE, 17 + i ), 50 - i, 5, out, offsetXD + offsetXO + offsetXN + offsetXN - i, offsetY, 13, 15 ); // Clean up 'NN' Fill( out, offsetXD + offsetXO + offsetXN + offsetXN - i, offsetY + 9, 1, 1, getButtonFillingColor( i == 0, false ) ); // Add 'ER' Blit( GetICN( ICN::APANELE, 8 + i ), 66 - ( 3 * i ), 5 + ( 2 * i ), out, offsetXD + offsetXO + offsetXN + offsetXN + offsetXN - ( 2 * i ), offsetY + ( 2 * i ), 23, 14 - i ); // Clean up 'NE' Blit( GetICN( ICN::TRADPOSE, 17 + i ), 50 - i, 5, out, offsetXD + offsetXO + offsetXN + offsetXN + offsetXN - i, offsetY, 2, 10 ); Fill( out, offsetXD + offsetXO + offsetXN + offsetXN + offsetXN - ( 2 * i ), offsetY + 9 + i, 1 + i, 2 + i, getButtonFillingColor( i == 0, false ) ); // Restore button ornament Blit( GetICN( ICN::TRADPOSE, 17 + i ), offsetXD + offsetXO + offsetXN + offsetXN + offsetXN + 20, offsetY, out, offsetXD + offsetXO + offsetXN + offsetXN + offsetXN + 20, offsetY, 1, 1 ); Blit( GetICN( ICN::TRADPOSE, 17 + i ), offsetXD + offsetXO + offsetXN + offsetXN + offsetXN + 21, offsetY + 1, out, offsetXD + offsetXO + offsetXN + offsetXN + offsetXN + 21, offsetY + 1, 2, 3 ); Blit( GetICN( ICN::TRADPOSE, 17 + i ), offsetXD + offsetXO + offsetXN + offsetXN + offsetXN + 20, offsetY, out, offsetXD + offsetXO + offsetXN + offsetXN + offsetXN + 21, offsetY + 4, 1, 1 ); } return true; case ICN::BUTTON_SMALL_MIN_GOOD: _icnVsSprite[id].resize( 2 ); for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( ICN::RECRUIT, 4 + i ); // Clean the button and leave 'M' Fill( out, 31 - 2 * i, 5 + i, 25, 15, getButtonFillingColor( i == 0 ) ); Fill( out, 29 - 2 * i, 17 + i, 2, 2, getButtonFillingColor( i == 0 ) ); // Add 'I' Blit( GetICN( ICN::APANEL, 4 + i ), 25 - i, 19 + i, out, 32 - i, 4 + i, 7 - i, 15 ); Blit( GetICN( ICN::RECRUIT, 4 + i ), 28 - i, 7 + i, out, 36 - i, 7 + i, 3, 9 ); Fill( out, 37 - i, 16 + i, 2, 3, getButtonFillingColor( i == 0 ) ); // Add 'N' Blit( GetICN( ICN::TRADPOST, 17 + i ), 50 - i, 5, out, 41 - i, 5, 14, 15 ); Fill( out, 41 - i, 5, 1, 1, getButtonFillingColor( i == 0 ) ); Fill( out, 41 - i, 5 + 9, 1, 1, getButtonFillingColor( i == 0 ) ); } return true; default: break; } return false; } bool generatePolishSpecificImages( const int id ) { switch ( id ) { case ICN::BTNBATTLEONLY: _icnVsSprite[id].resize( 2 ); for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( ICN::BTNNEWGM, 6 + i ); // clean the button Fill( out, 25, 18, 88, 23, getButtonFillingColor( i == 0 ) ); const int32_t offsetX = 46; const int32_t offsetY = 23; // Add 'BI' Blit( GetICN( ICN::BTNMCFG, 2 + i ), 58 - i, 29, out, offsetX - i, offsetY, 14, 11 ); // Add 'T' Blit( GetICN( ICN::BTNNEWGM, 0 + i ), 24 - i, 29, out, offsetX + 14 - i, offsetY, 9, 11 ); // Add 'WA' Blit( GetICN( ICN::BTNEMAIN, 0 + i ), 45 - i, 23, out, offsetX + 23 - i, offsetY, 24, 11 ); // Add pixel to 'W' Blit( GetICN( ICN::BTNEMAIN, 0 + i ), 47 - i, 23 + i, out, offsetX + 38 - i, offsetY + i, 1, 1 ); } return true; default: break; } return false; } bool generateItalianSpecificImages( const int id ) { switch ( id ) { case ICN::BTNBATTLEONLY: _icnVsSprite[id].resize( 2 ); for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( ICN::BTNNEWGM, 6 + i ); // clean the button const uint8_t buttonFillingColor = getButtonFillingColor( i == 0 ); Fill( out, 25, 18, 88, 23, buttonFillingColor ); const int32_t offsetX = 16; const int32_t offsetY = 21; // Add 'B' Blit( GetICN( ICN::BTNBAUD, 0 + i ), 42 - i, 28, out, offsetX - i, offsetY, 13, 15 ); Fill( out, offsetX + 11, offsetY + 13, 1, 2, buttonFillingColor ); // Add 'A' Blit( GetICN( ICN::BTNNEWGM, 0 + i ), 80 - i, 28, out, offsetX + 13 - i, offsetY, 14, 15 ); Fill( out, offsetX + 13 - i, offsetY + 3, 1, 4, buttonFillingColor ); // Add 'T' Blit( GetICN( ICN::BTNMP, 0 + i ), 74 - i, 5, out, offsetX + 27 - 2 * i, offsetY, 12, 15 ); // Add 'T' Blit( GetICN( ICN::BTNMP, 0 + i ), 74 - i, 5, out, offsetX + 39 - 2 * i, offsetY, 12, 15 ); // Add 'A' Blit( GetICN( ICN::BTNNEWGM, 0 + i ), 80 - i, 28, out, offsetX + 50 - i, offsetY, 14, 15 ); Fill( out, offsetX + 65 - i, offsetY + 5, 1, 2, buttonFillingColor ); Fill( out, offsetX + 65 - i, offsetY + 14, 1, 3, buttonFillingColor ); Fill( out, offsetX + 50 - i, offsetY + 3, 1, 4, buttonFillingColor ); // Add 'G' Blit( GetICN( ICN::BTNNEWGM, 0 + i ), 44 - i, 12, out, offsetX + 65 - i, offsetY, 11, 15 ); // Add 'L' Blit( GetICN( ICN::BTNDC, 4 + i ), 77 - i, 21, out, offsetX + 77 - 2 * i, offsetY, 9, 15 ); // Add 'I' Blit( GetICN( ICN::BTNNEWGM, 0 + i ), 56 - i, 12, out, offsetX + 86 - i, offsetY, 7, 15 ); // Add 'A' Blit( GetICN( ICN::BTNNEWGM, 0 + i ), 80 - i, 28, out, offsetX + 93 - i, offsetY, 14, 15 ); Fill( out, offsetX + 109 - i, offsetY + 5, 1, 2, buttonFillingColor ); Fill( out, offsetX + 93 - i, offsetY + 3, 1, 4, buttonFillingColor ); } return true; default: break; } return false; } void generateLanguageSpecificImages( int id ) { assert( isLanguageDependentIcnId( id ) ); const fheroes2::SupportedLanguage resourceLanguage = fheroes2::getResourceLanguage(); // Language-specific image generators, may fail if ( fheroes2::getCurrentLanguage() == resourceLanguage ) { switch ( resourceLanguage ) { case fheroes2::SupportedLanguage::German: if ( generateGermanSpecificImages( id ) ) { return; } break; case fheroes2::SupportedLanguage::French: if ( generateFrenchSpecificImages( id ) ) { return; } break; case fheroes2::SupportedLanguage::Polish: if ( generatePolishSpecificImages( id ) ) { return; } break; case fheroes2::SupportedLanguage::Italian: if ( generateItalianSpecificImages( id ) ) { return; } break; default: break; } } // Image generator of a last resort, must provide the generation of the "default" variant // for all image ids for which this function can be called, and must not fail. generateDefaultImages( id ); } // This function must return true is resources have been modified, false otherwise. bool LoadModifiedICN( const int id ) { // If this assertion blows up then you are calling this function in a recursion. Check your code! assert( _icnVsSprite[id].empty() ); // IMPORTANT!!! // Call LoadOriginalICN() function only if you are handling the same ICN. // If you need to load a different ICN use loadICN() function. switch ( id ) { case ICN::ROUTERED: CopyICNWithPalette( id, ICN::ROUTE, PAL::PaletteType::RED ); return true; case ICN::FONT: case ICN::SMALFONT: { LoadOriginalICN( id ); auto & imageArray = _icnVsSprite[id]; if ( imageArray.size() < 96 ) { // 96 symbols is the minimum requirement for English. throw std::logic_error( "The game resources are corrupted. Please use resources from a licensed version of Heroes of Might and Magic II." ); } // Compare '(' and ')' symbols. By size they are always the same. However, we play safe and fail if both dimensions are different. if ( ( imageArray[8].width() != imageArray[9].width() ) && ( imageArray[8].height() != imageArray[9].height() ) ) { // This is most likely a corrupted font or a pirated translation to a non-English language which causes all sorts of rendering issues. throw std::logic_error( "The game resources are corrupted. Please use resources from a licensed version of Heroes of Might and Magic II." ); } const std::vector & body = ::AGG::getDataFromAggFile( ICN::GetString( id ) ); const uint32_t crc32 = fheroes2::calculateCRC32( body.data(), body.size() ); if ( id == ICN::SMALFONT ) { // Small font in official Polish GoG version has all letters shifted 1 pixel down. if ( crc32 == 0xE9EC7A63 ) { for ( Sprite & letter : imageArray ) { letter.setPosition( letter.x(), letter.y() - 1 ); } } modifyBaseSmallFont( _icnVsSprite[id] ); } else { assert( id == ICN::FONT ); // The original images contain an issue: image layer has value 50 which is '2' in UTF-8. We must correct these (only 3) places for ( fheroes2::Sprite & fontImage : imageArray ) { ReplaceColorIdByTransformId( fontImage, 50, 2 ); } modifyBaseNormalFont( _icnVsSprite[id] ); } // Some checks that we really have CP1251 font const int32_t verifiedFontWidth = ( id == ICN::FONT ) ? 19 : 12; if ( imageArray.size() == 162 && imageArray[121].width() == verifiedFontWidth ) { // Engine expects that letter indexes correspond to charcode - 0x20. // In case CP1251 font.icn contains sprites for chars 0x20-0x7F, 0xC0-0xDF, 0xA8, 0xE0-0xFF, 0xB8 (in that order). // We rearrange sprites array for corresponding sprite indexes to charcode - 0x20. const Sprite firstSprite{ imageArray[0] }; imageArray.insert( imageArray.begin() + 96, 64, firstSprite ); std::swap( imageArray[136], imageArray[192] ); // Move sprites for chars 0xA8 std::swap( imageArray[152], imageArray[225] ); // and 0xB8 to it's places. imageArray.pop_back(); imageArray.erase( imageArray.begin() + 192 ); } // German version uses CP1252 if ( crc32 == 0x04745D1D || crc32 == 0xD0F0D852 ) { const Sprite firstSprite{ imageArray[0] }; imageArray.insert( imageArray.begin() + 96, 124, firstSprite ); std::swap( imageArray[164], imageArray[224] ); std::swap( imageArray[182], imageArray[225] ); std::swap( imageArray[188], imageArray[226] ); std::swap( imageArray[191], imageArray[223] ); std::swap( imageArray[196], imageArray[220] ); std::swap( imageArray[214], imageArray[221] ); std::swap( imageArray[220], imageArray[222] ); imageArray.erase( imageArray.begin() + 221, imageArray.end() ); } // French version has its own special encoding but should conform to CP1252 too if ( crc32 == 0xD9556567 || crc32 == 0x406967B9 ) { const Sprite firstSprite{ imageArray[0] }; imageArray.insert( imageArray.begin() + 96, 160 - 32, firstSprite ); imageArray[192 - 32] = imageArray[33]; imageArray[199 - 32] = imageArray[35]; imageArray[201 - 32] = imageArray[37]; imageArray[202 - 32] = imageArray[37]; imageArray[244 - 32] = imageArray[3]; imageArray[251 - 32] = imageArray[4]; imageArray[249 - 32] = imageArray[6]; imageArray[226 - 32] = imageArray[10]; imageArray[239 - 32] = imageArray[28]; imageArray[238 - 32] = imageArray[30]; imageArray[224 - 32] = imageArray[32]; imageArray[231 - 32] = imageArray[62]; imageArray[232 - 32] = imageArray[64]; imageArray[239 - 32] = imageArray[91]; imageArray[234 - 32] = imageArray[92]; imageArray[238 - 32] = imageArray[93]; imageArray[233 - 32] = imageArray[94]; imageArray[238 - 32] = imageArray[95]; imageArray.erase( imageArray.begin() + 252 - 32, imageArray.end() ); } // Italian version uses CP1252 if ( crc32 == 0x219B3124 || crc32 == 0x1F3C3C74 ) { const Sprite firstSprite{ imageArray[0] }; imageArray.insert( imageArray.begin() + 101, 155 - 32, firstSprite ); imageArray[192 - 32] = imageArray[33]; imageArray[200 - 32] = imageArray[37]; imageArray[201 - 32] = imageArray[37]; imageArray[204 - 32] = imageArray[41]; imageArray[210 - 32] = imageArray[47]; imageArray[217 - 32] = imageArray[53]; imageArray[224 - 32] = imageArray[96]; imageArray[232 - 32] = imageArray[97]; imageArray[233 - 32] = imageArray[69]; imageArray[236 - 32] = imageArray[98]; imageArray[242 - 32] = imageArray[99]; imageArray[249 - 32] = imageArray[100]; imageArray.erase( imageArray.begin() + 250 - 32, imageArray.end() ); } return true; } case ICN::YELLOW_FONT: CopyICNWithPalette( id, ICN::FONT, PAL::PaletteType::YELLOW_FONT ); return true; case ICN::YELLOW_SMALLFONT: CopyICNWithPalette( id, ICN::SMALFONT, PAL::PaletteType::YELLOW_FONT ); return true; case ICN::GRAY_FONT: CopyICNWithPalette( id, ICN::FONT, PAL::PaletteType::GRAY_FONT ); return true; case ICN::GRAY_SMALL_FONT: CopyICNWithPalette( id, ICN::SMALFONT, PAL::PaletteType::GRAY_FONT ); return true; case ICN::SPELLS: LoadOriginalICN( id ); if ( _icnVsSprite[id].size() != 60 ) { return true; } _icnVsSprite[id].resize( 73 ); for ( uint32_t i = 60; i < 66; ++i ) { // Mass Cure spell. ( when i == 60 ). size_t originalIndex = 6; if ( i == 61 ) { // Mass Haste spell. originalIndex = 14; } else if ( i == 62 ) { // Mass Slow spell. originalIndex = 1; } else if ( i == 63 ) { // Mass Bless spell. originalIndex = 7; } else if ( i == 64 ) { // Mass Curse spell. originalIndex = 3; } else if ( i == 65 ) { // Mass Shield spell. originalIndex = 15; } const Sprite & originalImage = _icnVsSprite[id][originalIndex]; Sprite & image = _icnVsSprite[id][i]; image.resize( originalImage.width() + 8, originalImage.height() + 8 ); image.setPosition( originalImage.x() + 4, originalImage.y() + 4 ); image.fill( 1 ); AlphaBlit( originalImage, image, 0, 0, 128 ); AlphaBlit( originalImage, image, 4, 4, 192 ); Blit( originalImage, image, 8, 8 ); AddTransparency( image, 1 ); } // The Petrification spell does not have its own icon in the original game. h2d::readImage( "petrification_spell_icon.image", _icnVsSprite[id][66] ); // Generate random spell image for Editor. { const Sprite & randomSpellImage = _icnVsSprite[id][2]; const int32_t imageWidth = randomSpellImage.width(); Copy( randomSpellImage, _icnVsSprite[id][67] ); // Add text on random spell images. for ( uint32_t i = 1; i < 6; ++i ) { Sprite & originalImage = _icnVsSprite[id][i + 67]; Copy( randomSpellImage, originalImage ); const Text text( std::to_string( i ), FontType::normalWhite() ); text.draw( ( imageWidth - text.width() ) / 2, 22, originalImage ); } } return true; case ICN::CSLMARKER: _icnVsSprite[id].resize( 3 ); for ( uint32_t i = 0; i < 3; ++i ) { _icnVsSprite[id][i] = GetICN( ICN::LOCATORS, 24 ); if ( i == 1 ) { ReplaceColorId( _icnVsSprite[id][i], 0x0A, 0xD6 ); } else if ( i == 2 ) { ReplaceColorId( _icnVsSprite[id][i], 0x0A, 0xDE ); } } return true; case ICN::BATTLESKIP: _icnVsSprite[id].resize( 2 ); for ( uint32_t i = 0; i < 2; ++i ) { Sprite & out = _icnVsSprite[id][i]; out = GetICN( ICN::TEXTBAR, 4 + i ); // clean the button Blit( GetICN( ICN::SYSTEM, 11 + i ), 3, 8, out, 3, 1, 43, 14 ); // add 'skip' Blit( GetICN( ICN::TEXTBAR, i ), 3, 10, out, 3, 0, 43, 14 ); } return true; case ICN::BUYMAX: case ICN::BUTTON_NEW_GAME_GOOD: case ICN::BUTTON_NEW_GAME_EVIL: case ICN::BUTTON_SAVE_GAME_GOOD: case ICN::BUTTON_SAVE_GAME_EVIL: case ICN::BUTTON_LOAD_GAME_GOOD: case ICN::BUTTON_LOAD_GAME_EVIL: case ICN::BUTTON_INFO_GOOD: case ICN::BUTTON_INFO_EVIL: case ICN::BUTTON_QUIT_GOOD: case ICN::BUTTON_QUIT_EVIL: case ICN::BUTTON_SMALL_CANCEL_GOOD: case ICN::BUTTON_SMALL_CANCEL_EVIL: case ICN::BUTTON_SMALL_OKAY_GOOD: case ICN::BUTTON_SMALL_OKAY_EVIL: case ICN::BUTTON_SMALLER_OKAY_GOOD: case ICN::BUTTON_SMALLER_OKAY_EVIL: case ICN::BUTTON_SMALL_ACCEPT_GOOD: case ICN::BUTTON_SMALL_ACCEPT_EVIL: case ICN::BUTTON_SMALL_DECLINE_GOOD: case ICN::BUTTON_SMALL_DECLINE_EVIL: case ICN::BUTTON_SMALL_LEARN_GOOD: case ICN::BUTTON_SMALL_LEARN_EVIL: case ICN::BUTTON_SMALL_TRADE_GOOD: case ICN::BUTTON_SMALL_TRADE_EVIL: case ICN::BUTTON_SMALL_YES_GOOD: case ICN::BUTTON_SMALL_YES_EVIL: case ICN::BUTTON_SMALL_NO_GOOD: case ICN::BUTTON_SMALL_NO_EVIL: case ICN::BUTTON_SMALL_EXIT_GOOD: case ICN::BUTTON_SMALL_EXIT_EVIL: case ICN::BUTTON_EXIT_HEROES_MEETING: case ICN::BUTTON_EXIT_TOWN: case ICN::BUTTON_EXIT_PUZZLE_DIM_DOOR_EVIL: case ICN::BUTTON_EXIT_PUZZLE_DIM_DOOR_GOOD: case ICN::BUTTON_SMALL_DISMISS_GOOD: case ICN::BUTTON_SMALL_DISMISS_EVIL: case ICN::BUTTON_SMALL_UPGRADE_GOOD: case ICN::BUTTON_SMALL_UPGRADE_EVIL: case ICN::BUTTON_SMALL_RESTART_GOOD: case ICN::BUTTON_SMALL_RESTART_EVIL: case ICN::BUTTON_KINGDOM_EXIT: case ICN::BUTTON_KINGDOM_HEROES: case ICN::BUTTON_KINGDOM_TOWNS: case ICN::BUTTON_MAPSIZE_SMALL: case ICN::BUTTON_MAPSIZE_MEDIUM: case ICN::BUTTON_MAPSIZE_LARGE: case ICN::BUTTON_MAPSIZE_XLARGE: case ICN::BUTTON_MAPSIZE_ALL: case ICN::BUTTON_MAP_SELECT_GOOD: case ICN::BUTTON_MAP_SELECT_EVIL: case ICN::BUTTON_STANDARD_GAME: case ICN::BUTTON_CAMPAIGN_GAME: case ICN::BUTTON_MULTIPLAYER_GAME: case ICN::BUTTON_LARGE_CANCEL: case ICN::BUTTON_LARGE_CONFIG: case ICN::BUTTON_ORIGINAL_CAMPAIGN: case ICN::BUTTON_EXPANSION_CAMPAIGN: case ICN::BUTTON_HOT_SEAT: case ICN::BUTTON_2_PLAYERS: case ICN::BUTTON_3_PLAYERS: case ICN::BUTTON_4_PLAYERS: case ICN::BUTTON_5_PLAYERS: case ICN::BUTTON_6_PLAYERS: case ICN::BTNBATTLEONLY: case ICN::BTNGIFT_GOOD: case ICN::BTNGIFT_EVIL: case ICN::UNIFORM_EVIL_MAX_BUTTON: case ICN::UNIFORM_EVIL_MIN_BUTTON: case ICN::UNIFORM_GOOD_MAX_BUTTON: case ICN::UNIFORM_GOOD_MIN_BUTTON: case ICN::UNIFORM_GOOD_OKAY_BUTTON: case ICN::UNIFORM_EVIL_OKAY_BUTTON: case ICN::UNIFORM_GOOD_CANCEL_BUTTON: case ICN::UNIFORM_EVIL_CANCEL_BUTTON: case ICN::UNIFORM_GOOD_EXIT_BUTTON: case ICN::UNIFORM_EVIL_EXIT_BUTTON: case ICN::BUTTON_SMALL_MIN_GOOD: case ICN::BUTTON_SMALL_MIN_EVIL: case ICN::BUTTON_SMALL_MAX_GOOD: case ICN::BUTTON_SMALL_MAX_EVIL: case ICN::BUTTON_EXIT_GOOD: case ICN::BUTTON_RESET_GOOD: case ICN::BUTTON_START_GOOD: case ICN::BUTTON_CASTLE_GOOD: case ICN::BUTTON_CASTLE_EVIL: case ICN::BUTTON_TOWN_GOOD: case ICN::BUTTON_TOWN_EVIL: case ICN::BUTTON_GUILDWELL_EXIT: case ICN::GOOD_CAMPAIGN_BUTTONS: case ICN::EVIL_CAMPAIGN_BUTTONS: case ICN::POL_CAMPAIGN_BUTTONS: case ICN::BUTTON_VIEWWORLD_EXIT_GOOD: case ICN::BUTTON_VIEWWORLD_EXIT_EVIL: case ICN::BUTTON_VERTICAL_DISMISS: case ICN::BUTTON_VERTICAL_EXIT: case ICN::BUTTON_HSCORES_VERTICAL_CAMPAIGN: case ICN::BUTTON_HSCORES_VERTICAL_EXIT: case ICN::BUTTON_HSCORES_VERTICAL_STANDARD: generateLanguageSpecificImages( id ); return true; case ICN::PHOENIX: LoadOriginalICN( id ); // First sprite has cropped shadow. We copy missing part from another 'almost' identical frame if ( _icnVsSprite[id].size() >= 32 ) { const Sprite & in = _icnVsSprite[id][32]; Copy( in, 60, 73, _icnVsSprite[id][1], 60, 73, 14, 13 ); Copy( in, 56, 72, _icnVsSprite[id][30], 56, 72, 18, 9 ); } return true; case ICN::MONH0028: // phoenix LoadOriginalICN( id ); if ( _icnVsSprite[id].size() == 1 ) { const Sprite & correctFrame = GetICN( ICN::PHOENIX, 32 ); Copy( correctFrame, 60, 73, _icnVsSprite[id][0], 58, 70, 14, 13 ); } return true; case ICN::CAVALRYR: LoadOriginalICN( id ); // Sprite 23 has incorrect colors, we need to replace them if ( _icnVsSprite[id].size() >= 23 ) { Sprite & out = _icnVsSprite[id][23]; std::vector indexes( 256 ); std::iota( indexes.begin(), indexes.end(), static_cast( 0 ) ); indexes[69] = 187; indexes[71] = 195; indexes[73] = 188; indexes[74] = 190; indexes[75] = 193; indexes[76] = 191; indexes[77] = 195; indexes[80] = 195; indexes[81] = 196; indexes[83] = 196; indexes[84] = 197; indexes[151] = 197; ApplyPalette( out, indexes ); } return true; case ICN::TITANMSL: LoadOriginalICN( id ); if ( _icnVsSprite[id].size() == 7 ) { // We need to shift Titan lightning arrow sprite position to correctly render it. _icnVsSprite[id][0].setPosition( _icnVsSprite[id][0].x(), _icnVsSprite[id][0].y() - 5 ); _icnVsSprite[id][1].setPosition( _icnVsSprite[id][1].x() - 5, _icnVsSprite[id][1].y() - 5 ); _icnVsSprite[id][2].setPosition( _icnVsSprite[id][2].x() - 10, _icnVsSprite[id][2].y() ); _icnVsSprite[id][3].setPosition( _icnVsSprite[id][3].x() - 15, _icnVsSprite[id][3].y() ); _icnVsSprite[id][4].setPosition( _icnVsSprite[id][4].x() - 10, _icnVsSprite[id][2].y() ); _icnVsSprite[id][5].setPosition( _icnVsSprite[id][5].x() - 5, _icnVsSprite[id][5].y() - 5 ); _icnVsSprite[id][6].setPosition( _icnVsSprite[id][6].x(), _icnVsSprite[id][6].y() - 5 ); } return true; case ICN::TROLLMSL: LoadOriginalICN( id ); if ( _icnVsSprite[id].size() == 1 ) { Sprite & out = _icnVsSprite[id][0]; // The original sprite contains 2 pixels which are empty if ( out.width() * out.height() > 188 && out.transform()[147] == 1 && out.transform()[188] == 1 ) { out.transform()[147] = 0; out.image()[147] = 22; out.transform()[188] = 0; out.image()[188] = 24; } } return true; case ICN::TROLL2MSL: loadICN( ICN::TROLLMSL ); if ( _icnVsSprite[ICN::TROLLMSL].size() == 1 ) { _icnVsSprite[id].resize( 1 ); Sprite & out = _icnVsSprite[id][0]; out = _icnVsSprite[ICN::TROLLMSL][0]; // The original sprite contains 2 pixels which are empty if ( out.width() * out.height() > 188 && out.transform()[147] == 1 && out.transform()[188] == 1 ) { out.transform()[147] = 0; out.image()[147] = 22; out.transform()[188] = 0; out.image()[188] = 24; } std::vector indexes( 256 ); std::iota( indexes.begin(), indexes.end(), static_cast( 0 ) ); indexes[10] = 152; indexes[11] = 153; indexes[12] = 154; indexes[13] = 155; indexes[14] = 155; indexes[15] = 156; indexes[16] = 157; indexes[17] = 158; indexes[18] = 159; indexes[19] = 160; indexes[20] = 160; indexes[21] = 161; indexes[22] = 162; indexes[23] = 163; indexes[24] = 164; indexes[25] = 165; indexes[26] = 166; indexes[27] = 166; indexes[28] = 167; indexes[29] = 168; indexes[30] = 169; indexes[31] = 170; indexes[32] = 171; indexes[33] = 172; indexes[34] = 172; indexes[35] = 173; ApplyPalette( out, indexes ); } return true; case ICN::GOLEM: case ICN::GOLEM2: LoadOriginalICN( id ); // Original Golem ICN contains 40 frames. We make the corrections only for original sprite. if ( _icnVsSprite[id].size() == 40 ) { // Movement animation fix for Iron and Steel Golem: its 'MOVE_MAIN' animation is missing 1/4 of animation start. // The 'MOVE_START' (for first and one cell move) has this 1/4 of animation, but 'MOVE_TILE_START` is empty, // so we make a copy of 'MOVE_MAIN' frames to the end of sprite vector and correct their 'x' coordinate // to cover the whole cell except the last frame, that has correct coordinates. const size_t golemICNSize = _icnVsSprite[id].size(); // 'MOVE_MAIN' has 7 frames and we copy only first 6. const int32_t copyFramesNum = 6; _icnVsSprite[id].reserve( golemICNSize + copyFramesNum ); // 'MOVE_MAIN' frames starts from the 6th frame in Golem ICN sprites. size_t copyFrame = 6; for ( int32_t i = 0; i < copyFramesNum; ++i, ++copyFrame ) { // IMPORTANT: we MUST do a copy of a vector element if we want to insert it to the same vector. fheroes2::Sprite originalFrame = _icnVsSprite[id][copyFrame]; _icnVsSprite[id].emplace_back( std::move( originalFrame ) ); const size_t frameID = golemICNSize + i; // We have 7 'MOVE_MAIN' frames and 1/4 of cell to expand the horizontal movement, so we shift the first copied frame by "6*CELLW/(4*7)" to the // left and reduce this shift every next frame by "CELLW/(7*4)". _icnVsSprite[id][frameID].setPosition( _icnVsSprite[id][frameID].x() - ( copyFramesNum - i ) * CELLW / 28, _icnVsSprite[id][frameID].y() ); } } return true; case ICN::LOCATORE: case ICN::LOCATORS: LoadOriginalICN( id ); if ( _icnVsSprite[id].size() > 15 ) { if ( _icnVsSprite[id][12].width() == 47 ) { Sprite & out = _icnVsSprite[id][12]; out = Crop( out, 0, 0, out.width() - 1, out.height() ); } if ( _icnVsSprite[id][15].width() == 47 ) { Sprite & out = _icnVsSprite[id][15]; out = Crop( out, 0, 0, out.width() - 1, out.height() ); } } return true; case ICN::TOWNBKG2: LoadOriginalICN( id ); if ( _icnVsSprite[id].size() == 1 ) { Sprite & out = _icnVsSprite[id][0]; // The first pixel of the original sprite has incorrect color. if ( !out.empty() ) { out._disableTransformLayer(); out.image()[0] = 10; } } return true; case ICN::HSICONS: LoadOriginalICN( id ); if ( _icnVsSprite[id].size() > 7 ) { Sprite & out = _icnVsSprite[id][7]; if ( out.width() == 34 && out.height() == 19 ) { Sprite temp; std::swap( temp, out ); out.resize( temp.width() + 1, temp.height() ); out.reset(); Copy( temp, 0, 0, out, 1, 0, temp.width(), temp.height() ); Copy( temp, temp.width() - 1, 10, out, 0, 10, 1, 3 ); } } return true; case ICN::LISTBOX_EVIL: CopyICNWithPalette( id, ICN::LISTBOX, PAL::PaletteType::GRAY ); for ( fheroes2::Sprite & sprite : _icnVsSprite[id] ) { ApplyPalette( sprite, 2 ); } return true; case ICN::MONS32: LoadOriginalICN( id ); if ( _icnVsSprite[id].size() > 4 ) { // Veteran Pikeman Sprite & modified = _icnVsSprite[id][4]; Sprite temp( modified.width(), modified.height() + 1 ); temp.reset(); Blit( modified, 0, 0, temp, 0, 1, modified.width(), modified.height() ); modified = std::move( temp ); Fill( modified, 7, 0, 4, 1, 36 ); } if ( _icnVsSprite[id].size() > 6 ) { // Master Swordsman Sprite & modified = _icnVsSprite[id][6]; Sprite temp( modified.width(), modified.height() + 1 ); temp.reset(); Blit( modified, 0, 0, temp, 0, 1, modified.width(), modified.height() ); modified = std::move( temp ); Fill( modified, 2, 0, 5, 1, 36 ); } if ( _icnVsSprite[id].size() > 8 ) { // Champion Sprite & modified = _icnVsSprite[id][8]; Sprite temp( modified.width(), modified.height() + 1 ); temp.reset(); Blit( modified, 0, 0, temp, 0, 1, modified.width(), modified.height() ); modified = std::move( temp ); Fill( modified, 12, 0, 5, 1, 36 ); } if ( _icnVsSprite[id].size() > 33 ) { // Minotaur King original mini sprite has blue armlets. We make them gold to correspond the ICN::MINOTAU2. Sprite & modified = _icnVsSprite[id][33]; if ( modified.width() == 20 && modified.height() == 36 ) { // We update these pixels: 6x16, 7x16, 8x16, 5x17, 6x17, 7x17, 8x17, 6x18, 7x18, 14x18, 14x19. for ( const uint32_t pixelNumber : { 326, 327, 328, 345, 346, 347, 348, 366, 367, 374, 394 } ) { // The gold color gradient has -42 offset from blue color gradient. modified.image()[pixelNumber] -= 42; } } } if ( _icnVsSprite[id].size() > 62 ) { const Point shadowOffset( -1, 2 ); for ( size_t i = 0; i < 62; ++i ) { Sprite & modified = _icnVsSprite[id][i]; const Point originalOffset( modified.x(), modified.y() ); Sprite temp = addShadow( modified, { -1, 2 }, 2 ); temp.setPosition( originalOffset.x - 1, originalOffset.y + 2 ); const Rect area = GetActiveROI( temp, 2 ); if ( area.x > 0 || area.height != temp.height() ) { const Point offset( temp.x() - area.x, temp.y() - temp.height() + area.y + area.height ); modified = Crop( temp, area.x, area.y, area.width, area.height ); modified.setPosition( offset.x, offset.y ); } else { modified = std::move( temp ); } } } if ( _icnVsSprite[id].size() > 63 && _icnVsSprite[id][63].width() == 19 && _icnVsSprite[id][63].height() == 37 ) { // Air Elemental Sprite & modified = _icnVsSprite[id][63]; modified.image()[19 * 9 + 9] = modified.image()[19 * 5 + 11]; modified.transform()[19 * 9 + 9] = modified.transform()[19 * 5 + 11]; } return true; case ICN::MONSTER_SWITCH_LEFT_ARROW: _icnVsSprite[id].resize( 2 ); for ( uint32_t i = 0; i < 2; ++i ) { const Sprite & source = GetICN( ICN::RECRUIT, i ); Sprite & out = _icnVsSprite[id][i]; out.resize( source.height(), source.width() ); Transpose( source, out ); out = Flip( out, false, true ); out.setPosition( source.y() - static_cast( i ), source.x() ); } return true; case ICN::MONSTER_SWITCH_RIGHT_ARROW: _icnVsSprite[id].resize( 2 ); for ( uint32_t i = 0; i < 2; ++i ) { const Sprite & source = GetICN( ICN::RECRUIT, i + 2 ); Sprite & out = _icnVsSprite[id][i]; out.resize( source.height(), source.width() ); Transpose( source, out ); out = Flip( out, false, true ); out.setPosition( source.y(), source.x() ); } return true; case ICN::SURRENDR: case ICN::SURRENDE: LoadOriginalICN( id ); if ( _icnVsSprite[id].size() >= 4 ) { if ( id == ICN::SURRENDR ) { // Fix incorrect font color on good ACCEPT button. ReplaceColorId( _icnVsSprite[id][0], 28, 56 ); } // Fix pressed buttons background. for ( const uint32_t i : { 0, 2 } ) { Sprite & out = _icnVsSprite[id][i + 1]; Sprite tmp( out.width(), out.height() ); tmp.reset(); Copy( out, 0, 1, tmp, 1, 0, tmp.width() - 1, tmp.height() - 1 ); CopyTransformLayer( _icnVsSprite[id][i], tmp ); out.reset(); Copy( tmp, 1, 0, out, 0, 1, tmp.width() - 1, tmp.height() - 1 ); } } return true; case ICN::NON_UNIFORM_GOOD_RESTART_BUTTON: _icnVsSprite[id].resize( 2 ); _icnVsSprite[id][0] = Crop( GetICN( ICN::CAMPXTRG, 2 ), 6, 0, 108, 25 ); _icnVsSprite[id][0].setPosition( 0, 0 ); _icnVsSprite[id][1] = GetICN( ICN::CAMPXTRG, 3 ); _icnVsSprite[id][1].setPosition( 0, 0 ); // fix transparent corners CopyTransformLayer( _icnVsSprite[id][1], _icnVsSprite[id][0] ); return true; case ICN::NON_UNIFORM_EVIL_RESTART_BUTTON: _icnVsSprite[id].resize( 2 ); _icnVsSprite[id][0] = Crop( GetICN( ICN::CAMPXTRE, 2 ), 4, 0, 108, 25 ); _icnVsSprite[id][0].setPosition( 0, 0 ); _icnVsSprite[id][1] = GetICN( ICN::CAMPXTRE, 3 ); _icnVsSprite[id][1].setPosition( 0, 0 ); // fix transparent corners CopyTransformLayer( _icnVsSprite[id][1], _icnVsSprite[id][0] ); return true; case ICN::WHITE_LARGE_FONT: { GetICN( ICN::FONT, 0 ); const std::vector & original = _icnVsSprite[ICN::FONT]; _icnVsSprite[id].resize( original.size() ); for ( size_t i = 0; i < _icnVsSprite[id].size(); ++i ) { const Sprite & in = original[i]; Sprite & out = _icnVsSprite[id][i]; out.resize( in.width() * 2, in.height() * 2 ); SubpixelResize( in, out ); out.setPosition( in.x() * 2, in.y() * 2 ); } return true; } case ICN::SWAP_ARROW_LEFT_TO_RIGHT: case ICN::SWAP_ARROW_RIGHT_TO_LEFT: { // Since the original game does not have such resources we could generate it from hero meeting sprite. const Sprite & original = GetICN( ICN::SWAPWIN, 0 ); std::array input; const int32_t width = 45; const int32_t height = 20; for ( Image & image : input ) { image.resize( width, height ); } Copy( original, 295, 270, input[0], 0, 0, width, height ); Copy( original, 295, 291, input[1], 0, 0, width, height ); Copy( original, 295, 363, input[2], 0, 0, width, height ); Copy( original, 295, 384, input[3], 0, 0, width, height ); input[1] = Flip( input[1], true, false ); input[3] = Flip( input[3], true, false ); Image out = ExtractCommonPattern( { &input[0], &input[1], &input[2], &input[3] } ); // Here are 2 pixels which should be removed. if ( out.width() == width && out.height() == height ) { out.image()[40] = 0; out.transform()[40] = 1; out.image()[30 + 3 * width] = 0; out.transform()[30 + 3 * width] = 1; } _icnVsSprite[id].resize( 2 ); _icnVsSprite[id][0] = ( id == ICN::SWAP_ARROW_LEFT_TO_RIGHT ) ? out : Flip( out, true, false ); _icnVsSprite[id][1] = _icnVsSprite[id][0]; _icnVsSprite[id][1].setPosition( -1, 1 ); ApplyPalette( _icnVsSprite[id][1], 4 ); return true; } case ICN::EDITBTNS: LoadOriginalICN( id ); if ( _icnVsSprite[id].size() == 35 ) { // We add three buttons for new object groups: Adventure, Kingdom, Monsters. _icnVsSprite[id].resize( 41 ); // First make clean button sprites (pressed and released). Sprite released = GetICN( ICN::EDITBTNS, 4 ); Sprite pressed = GetICN( ICN::EDITBTNS, 5 ); // Clean the image from the button. Fill( released, 16, 6, 18, 24, 41U ); Fill( pressed, 16, 7, 17, 23, 46U ); for ( size_t i = 0; i < 4; i += 2 ) { _icnVsSprite[id][35 + i] = released; _icnVsSprite[id][35 + 1 + i] = pressed; } _icnVsSprite[id][39] = std::move( released ); _icnVsSprite[id][40] = std::move( pressed ); // Adventure objects button. drawImageOnButton( GetICN( ICN::X_LOC1, 0 ), 39, 29, _icnVsSprite[id][35], _icnVsSprite[id][36] ); // Kingdom objects button. drawImageOnButton( GetICN( ICN::OBJNARTI, 13 ), 39, 29, _icnVsSprite[id][37], _icnVsSprite[id][38] ); // Monsters objects button. drawImageOnButton( GetICN( ICN::MONS32, 11 ), 39, 29, _icnVsSprite[id][39], _icnVsSprite[id][40] ); } return true; case ICN::EDITBTNS_EVIL: { loadICN( ICN::EDITBTNS ); _icnVsSprite[id] = _icnVsSprite[ICN::EDITBTNS]; for ( auto & image : _icnVsSprite[id] ) { convertToEvilInterface( image, { 0, 0, image.width(), image.height() } ); } return true; } case ICN::EDITPANL: LoadOriginalICN( id ); if ( _icnVsSprite[id].size() == 6 ) { _icnVsSprite[id].resize( 18 ); // Make empty buttons for object types. _icnVsSprite[id][6].resize( 27, 27 ); _icnVsSprite[id][6]._disableTransformLayer(); _icnVsSprite[id][6].reset(); Fill( _icnVsSprite[id][6], 1, 1, 24, 24, 65U ); for ( size_t i = 7; i < _icnVsSprite[id].size(); ++i ) { _icnVsSprite[id][i] = _icnVsSprite[id][6]; } // Make Mountains objects button. Blit( GetICN( ICN::MTNCRCK, 3 ), 4, 0, _icnVsSprite[id][6], 1, 1, 24, 24 ); // Make Rocks objects button. Blit( GetICN( ICN::OBJNGRAS, 41 ), 1, 0, _icnVsSprite[id][7], 1, 9, 23, 12 ); // Make Trees object button. Also used as erase terrain objects button image. Copy( GetICN( ICN::EDITBTNS, 2 ), 13, 4, _icnVsSprite[id][8], 1, 1, 24, 24 ); // Replace image contour colors with the background color. std::vector indexes( 256 ); std::iota( indexes.begin(), indexes.end(), static_cast( 0 ) ); indexes[10] = 65U; indexes[38] = 65U; indexes[39] = 65U; indexes[40] = 65U; indexes[41] = 65U; indexes[46] = 65U; ApplyPalette( _icnVsSprite[id][8], indexes ); // Make Landscape Water objects button. Blit( GetICN( ICN::OBJNWAT2, 0 ), 0, 3, _icnVsSprite[id][9], 5, 1, 13, 3 ); Blit( GetICN( ICN::OBJNWAT2, 2 ), 5, 0, _icnVsSprite[id][9], 1, 4, 24, 21 ); // Make Landscape Miscellaneous objects button. Blit( GetICN( ICN::OBJNDIRT, 73 ), 8, 0, _icnVsSprite[id][10], 1, 1, 24, 24 ); // Make erase Dwellings button image. Blit( GetICN( ICN::OBJNMULT, 114 ), 7, 0, _icnVsSprite[id][11], 1, 1, 24, 24 ); // Make erase Mines button image. Blit( GetICN( ICN::MTNMULT, 82 ), 8, 4, _icnVsSprite[id][12], 1, 1, 24, 24 ); // Make erase Power-ups button image. Blit( GetICN( ICN::OBJNMULT, 72 ), 0, 6, _icnVsSprite[id][13], 1, 1, 24, 24 ); // Make Adventure Water objects button. Blit( GetICN( ICN::OBJNWATR, 24 ), 3, 0, _icnVsSprite[id][14], 1, 1, 24, 24 ); // Make Adventure Miscellaneous objects button. Blit( GetICN( ICN::OBJNMUL2, 198 ), 2, 0, _icnVsSprite[id][15], 1, 1, 24, 24 ); // Make erase Roads button image. Blit( GetICN( ICN::ROAD, 2 ), 0, 0, _icnVsSprite[id][16], 1, 8, 24, 5 ); Blit( GetICN( ICN::ROAD, 1 ), 1, 0, _icnVsSprite[id][16], 1, 13, 24, 5 ); // Make erase Streams button image. Blit( GetICN( ICN::STREAM, 2 ), 0, 0, _icnVsSprite[id][17], 1, 8, 24, 11 ); } return true; case ICN::EDITOR: LoadOriginalICN( id ); if ( !_icnVsSprite[id].empty() ) { // Fix the cycling colors in original editor main menu background. fheroes2::ApplyPalette( _icnVsSprite[id][0], PAL::GetPalette( PAL::PaletteType::NO_CYCLE ) ); } return true; case ICN::HEROES: LoadOriginalICN( id ); if ( !_icnVsSprite[id].empty() ) { Sprite & original = _icnVsSprite[id][0]; // This is the main menu image which shouldn't have any transform layer. original._disableTransformLayer(); if ( original.width() == 640 && original.height() == 480 ) { // Fix incorrect pixel at position 260x305. original.image()[195460] = 31; } } return true; case ICN::TOWNBKG3: // Warlock town background image contains 'empty' pixels leading to appear them as black. LoadOriginalICN( id ); if ( !_icnVsSprite[id].empty() ) { Sprite & original = _icnVsSprite[id][0]; if ( original.width() == 640 && original.height() == 256 ) { original._disableTransformLayer(); uint8_t * imageData = original.image(); imageData[51945] = 17; imageData[61828] = 25; imageData[64918] = 164; imageData[77685] = 18; imageData[84618] = 19; } } return true; case ICN::MINIPORT: // Some heroes portraits have incorrect transparent pixels. LoadOriginalICN( id ); if ( _icnVsSprite[id].size() > 60 ) { Sprite & original = _icnVsSprite[id][60]; if ( original.width() == 30 && original.height() == 22 ) { original._disableTransformLayer(); uint8_t * imageData = original.image(); imageData[5] = 75; imageData[310] = 48; imageData[358] = 64; imageData[424] = 65; } } if ( _icnVsSprite[id].size() > 61 ) { Sprite & original = _icnVsSprite[id][61]; if ( original.width() == 30 && original.height() == 22 ) { original._disableTransformLayer(); uint8_t * imageData = original.image(); imageData[51] = 30; imageData[80] = 28; imageData[81] = 30; imageData[383] = 24; imageData[445] = 24; } } if ( _icnVsSprite[id].size() > 65 ) { Sprite & original = _icnVsSprite[id][65]; if ( original.width() == 30 && original.height() == 22 ) { original._disableTransformLayer(); uint8_t * imageData = original.image(); imageData[499] = 60; imageData[601] = 24; imageData[631] = 28; } } if ( _icnVsSprite[id].size() > 67 ) { Sprite & original = _icnVsSprite[id][67]; if ( original.width() == 30 && original.height() == 22 ) { original._disableTransformLayer(); original.image()[42] = 28; } } return true; case ICN::MINICAPT: // Barbarian captain mini icon has bad pixel at position 22x2. LoadOriginalICN( id ); if ( _icnVsSprite[id].size() > 1 ) { Sprite & original = _icnVsSprite[id][1]; if ( original.width() == 30 && original.height() == 22 ) { original._disableTransformLayer(); original.image()[82] = 244; } } return true; case ICN::PORT0091: // Barbarian captain has one bad pixel. LoadOriginalICN( id ); if ( !_icnVsSprite[id].empty() ) { Sprite & original = _icnVsSprite[id][0]; if ( original.width() == 101 && original.height() == 93 ) { original._disableTransformLayer(); original.image()[9084] = 77; } } return true; case ICN::PORT0090: // Knight captain has multiple bad pixels. LoadOriginalICN( id ); if ( !_icnVsSprite[id].empty() ) { Sprite & original = _icnVsSprite[id][0]; if ( original.width() == 101 && original.height() == 93 ) { original._disableTransformLayer(); uint8_t * imageData = original.image(); imageData[2314] = 70; imageData[5160] = 71; imageData[5827] = 18; imageData[7474] = 167; } } return true; case ICN::PORT0092: // Sorceress captain has two bad transparent pixels (8x20 and 8x66). LoadOriginalICN( id ); if ( !_icnVsSprite[id].empty() ) { Sprite & original = _icnVsSprite[id][0]; if ( original.width() == 101 && original.height() == 93 ) { original._disableTransformLayer(); uint8_t * imageData = original.image(); imageData[2028] = 42; imageData[6674] = 100; } } return true; case ICN::PORT0095: // Necromancer captain have incorrect transparent pixel at position 8x22. LoadOriginalICN( id ); if ( !_icnVsSprite[id].empty() ) { Sprite & original = _icnVsSprite[id][0]; if ( original.width() == 101 && original.height() == 93 ) { original._disableTransformLayer(); original.image()[2230] = 212; } } return true; case ICN::CSTLWZRD: LoadOriginalICN( id ); if ( _icnVsSprite[id].size() >= 8 ) { // Statue image has bad pixels. Sprite & original = _icnVsSprite[id][7]; if ( original.width() == 135 && original.height() == 57 ) { original._disableTransformLayer(); uint8_t * imageData = original.image(); imageData[3687] = 50; imageData[5159] = 108; imageData[5294] = 108; } } if ( _icnVsSprite[id].size() > 28 ) { // Mage tower image has a bad pixel. for ( const uint32_t index : { 23, 28 } ) { Sprite & original = _icnVsSprite[id][index]; if ( original.width() == 135 && original.height() == 57 ) { original._disableTransformLayer(); original.image()[4333] = 23; } } } return true; case ICN::CSTLCAPK: // Knight captain has a bad pixel. LoadOriginalICN( id ); if ( _icnVsSprite[id].size() >= 2 ) { Sprite & original = _icnVsSprite[id][1]; if ( original.width() == 84 && original.height() == 81 ) { original._disableTransformLayer(); original.image()[4934] = 18; } } return true; case ICN::CSTLCAPW: // Warlock captain quarters have bad pixels. LoadOriginalICN( id ); if ( !_icnVsSprite[id].empty() ) { Sprite & original = _icnVsSprite[id][0]; if ( original.width() == 84 && original.height() == 81 ) { original._disableTransformLayer(); uint8_t * imageData = original.image(); imageData[1692] = 26; imageData[2363] = 32; imageData[2606] = 21; imageData[2608] = 21; } } return true; case ICN::CSTLSORC: LoadOriginalICN( id ); if ( _icnVsSprite[id].size() >= 14 ) { // Rainbow has bad pixels. Sprite & original = _icnVsSprite[id][13]; if ( original.width() == 135 && original.height() == 57 ) { original._disableTransformLayer(); uint8_t * imageData = original.image(); imageData[2047] = 160; imageData[2052] = 159; imageData[2055] = 160; imageData[2060] = 67; imageData[2063] = 159; imageData[2067] = 67; imageData[2184] = 67; imageData[2192] = 158; imageData[3508] = 67; imageData[3641] = 67; imageData[3773] = 69; imageData[3910] = 67; imageData[4039] = 69; imageData[4041] = 67; imageData[4172] = 67; imageData[4578] = 69; } } if ( _icnVsSprite[id].size() >= 25 ) { // Red tower has bad pixels. Sprite & original = _icnVsSprite[id][24]; if ( original.width() == 135 && original.height() == 57 ) { original._disableTransformLayer(); uint8_t * imageData = original.image(); imageData[2830] = 165; imageData[3101] = 165; imageData[3221] = 69; } } return true; case ICN::COLOR_CURSOR_ADVENTURE_MAP: case ICN::MONO_CURSOR_ADVENTURE_MAP: { // Create needed digits. const std::vector twoPoints = { { 2, 1 }, { 3, 1 }, { 1, 2 }, { 4, 2 }, { 3, 3 }, { 2, 4 }, { 1, 5 }, { 2, 5 }, { 3, 5 }, { 4, 5 } }; const std::vector threePoints = { { 1, 1 }, { 2, 1 }, { 3, 1 }, { 4, 2 }, { 1, 3 }, { 2, 3 }, { 3, 3 }, { 4, 4 }, { 1, 5 }, { 2, 5 }, { 3, 5 } }; const std::vector fourPoints = { { 1, 1 }, { 3, 1 }, { 1, 2 }, { 3, 2 }, { 1, 3 }, { 2, 3 }, { 3, 3 }, { 4, 3 }, { 3, 4 }, { 3, 5 } }; const std::vector fivePoints = { { 1, 1 }, { 2, 1 }, { 3, 1 }, { 4, 1 }, { 1, 2 }, { 1, 3 }, { 2, 3 }, { 3, 3 }, { 4, 4 }, { 1, 5 }, { 2, 5 }, { 3, 5 } }; const std::vector sixPoints = { { 2, 1 }, { 3, 1 }, { 1, 2 }, { 1, 3 }, { 2, 3 }, { 3, 3 }, { 1, 4 }, { 4, 4 }, { 2, 5 }, { 3, 5 } }; const std::vector sevenPoints = { { 1, 1 }, { 2, 1 }, { 3, 1 }, { 4, 1 }, { 4, 2 }, { 3, 3 }, { 2, 4 }, { 2, 5 } }; const std::vector plusPoints = { { 2, 1 }, { 1, 2 }, { 2, 2 }, { 3, 2 }, { 2, 3 } }; const bool isColorCursor = ( id == ICN::COLOR_CURSOR_ADVENTURE_MAP ); const uint8_t digitColor = isColorCursor ? 115 : 11; std::vector digits( 7 ); digits[0] = createDigit( 6, 7, twoPoints, digitColor ); digits[1] = createDigit( 6, 7, threePoints, digitColor ); digits[2] = createDigit( 6, 7, fourPoints, digitColor ); digits[3] = createDigit( 6, 7, fivePoints, digitColor ); digits[4] = createDigit( 6, 7, sixPoints, digitColor ); digits[5] = createDigit( 6, 7, sevenPoints, digitColor ); digits[6] = addDigit( digits[5], createDigit( 5, 5, plusPoints, digitColor ), { -1, -1 } ); _icnVsSprite[id].reserve( 7 * 8 ); const int originalCursorId = isColorCursor ? ICN::ADVMCO : ICN::MONO_CURSOR_ADVMBW; populateCursorIcons( _icnVsSprite[id], GetICN( originalCursorId, 4 ), digits, isColorCursor ? Point( -2, 1 ) : Point( -4, -6 ) ); populateCursorIcons( _icnVsSprite[id], GetICN( originalCursorId, 5 ), digits, isColorCursor ? Point( 1, 1 ) : Point( -6, -6 ) ); populateCursorIcons( _icnVsSprite[id], GetICN( originalCursorId, 6 ), digits, isColorCursor ? Point( 0, 1 ) : Point( -8, -7 ) ); populateCursorIcons( _icnVsSprite[id], GetICN( originalCursorId, 7 ), digits, isColorCursor ? Point( -2, 1 ) : Point( -15, -8 ) ); populateCursorIcons( _icnVsSprite[id], GetICN( originalCursorId, 8 ), digits, isColorCursor ? Point( 1, 1 ) : Point( -16, -11 ) ); populateCursorIcons( _icnVsSprite[id], GetICN( originalCursorId, 9 ), digits, isColorCursor ? Point( -6, 1 ) : Point( -8, -1 ) ); populateCursorIcons( _icnVsSprite[id], GetICN( originalCursorId, 28 ), digits, isColorCursor ? Point( 0, 1 ) : Point( -8, -7 ) ); return true; } case ICN::DISMISS_HERO_DISABLED_BUTTON: case ICN::NEW_CAMPAIGN_DISABLED_BUTTON: { _icnVsSprite[id].resize( 1 ); int buttonIcnId = ( id == ICN::DISMISS_HERO_DISABLED_BUTTON ) ? ICN::BUTTON_VERTICAL_DISMISS : ICN::BUTTON_CAMPAIGN_GAME; const Sprite & released = GetICN( buttonIcnId, 0 ); const Sprite & pressed = GetICN( buttonIcnId, 1 ); Sprite & output = _icnVsSprite[id][0]; output = released; ApplyPalette( output, PAL::GetPalette( PAL::PaletteType::DARKENING ) ); Image common = ExtractCommonPattern( { &released, &pressed } ); common = FilterOnePixelNoise( common ); common = FilterOnePixelNoise( common ); common = FilterOnePixelNoise( common ); Blit( common, output ); return true; } case ICN::KNIGHT_CASTLE_RIGHT_FARM: { _icnVsSprite[id].resize( 1 ); Sprite & output = _icnVsSprite[id][0]; output = GetICN( ICN::TWNKWEL2, 0 ); ApplyPalette( output, 28, 21, output, 28, 21, 39, 1, 8 ); ApplyPalette( output, 0, 22, output, 0, 22, 69, 1, 8 ); ApplyPalette( output, 0, 23, output, 0, 23, 53, 1, 8 ); ApplyPalette( output, 0, 24, output, 0, 24, 54, 1, 8 ); ApplyPalette( output, 0, 25, output, 0, 25, 62, 1, 8 ); return true; } case ICN::KNIGHT_CASTLE_LEFT_FARM: _icnVsSprite[id].resize( 1 ); h2d::readImage( "knight_castle_left_farm.image", _icnVsSprite[id][0] ); return true; case ICN::BARBARIAN_CASTLE_CAPTAIN_QUARTERS_LEFT_SIDE: _icnVsSprite[id].resize( 1 ); h2d::readImage( "barbarian_castle_captain_quarter_left_side.image", _icnVsSprite[id][0] ); return true; case ICN::SORCERESS_CASTLE_CAPTAIN_QUARTERS_LEFT_SIDE: _icnVsSprite[id].resize( 1 ); h2d::readImage( "sorceress_castle_captain_quarter_left_side.image", _icnVsSprite[id][0] ); return true; case ICN::NECROMANCER_CASTLE_STANDALONE_CAPTAIN_QUARTERS: { _icnVsSprite[id].resize( 1 ); Sprite & output = _icnVsSprite[id][0]; const Sprite & original = GetICN( ICN::TWNNCAPT, 0 ); output = Crop( original, 21, 0, original.width() - 21, original.height() ); output.setPosition( original.x() + 21, original.y() ); for ( int32_t y = 47; y < output.height(); ++y ) { SetTransformPixel( output, 0, y, 1 ); } const Sprite & castle = GetICN( ICN::TWNNCSTL, 0 ); Copy( castle, 402, 123, output, 1, 56, 2, 11 ); return true; } case ICN::NECROMANCER_CASTLE_CAPTAIN_QUARTERS_BRIDGE: { _icnVsSprite[id].resize( 1 ); Sprite & output = _icnVsSprite[id][0]; const Sprite & original = GetICN( ICN::TWNNCAPT, 0 ); output = Crop( original, 0, 0, 23, original.height() ); output.setPosition( original.x(), original.y() ); return true; } case ICN::ESCROLL: LoadOriginalICN( id ); if ( _icnVsSprite[id].size() > 4 ) { // fix missing black border on the right side of the "up" button Sprite & out = _icnVsSprite[id][4]; if ( out.width() == 16 && out.height() == 16 ) { out._disableTransformLayer(); Copy( out, 0, 0, out, 15, 0, 1, 16 ); } } return true; case ICN::MAP_TYPE_ICON: { _icnVsSprite[id].resize( 3 ); for ( Sprite & icon : _icnVsSprite[id] ) { icon._disableTransformLayer(); icon.resize( 17, 17 ); icon.fill( 0 ); } const Sprite & successionWarsIcon = GetICN( ICN::ARTFX, 6 ); const Sprite & priceOfLoyaltyIcon = GetICN( ICN::ARTFX, 90 ); const Sprite & resurrectionIcon = GetICN( ICN::ARTFX, 101 ); if ( !successionWarsIcon.empty() ) { Resize( successionWarsIcon, 0, 0, successionWarsIcon.width(), successionWarsIcon.height(), _icnVsSprite[id][0], 1, 1, 15, 15 ); } if ( !priceOfLoyaltyIcon.empty() ) { Resize( priceOfLoyaltyIcon, 0, 0, priceOfLoyaltyIcon.width(), priceOfLoyaltyIcon.height(), _icnVsSprite[id][1], 1, 1, 15, 15 ); } if ( !resurrectionIcon.empty() ) { Resize( resurrectionIcon, 0, 0, resurrectionIcon.width(), resurrectionIcon.height(), _icnVsSprite[id][2], 1, 1, 15, 15 ); } return true; } case ICN::TWNWWEL2: { LoadOriginalICN( id ); if ( _icnVsSprite[id].size() == 7 ) { if ( _icnVsSprite[id][0].width() == 122 && _icnVsSprite[id][0].height() == 226 ) { FillTransform( _icnVsSprite[id][0], 0, 57, 56, 62, 1 ); } for ( size_t i = 1; i < 7; ++i ) { Sprite & original = _icnVsSprite[id][i]; if ( original.width() == 121 && original.height() == 151 ) { FillTransform( original, 0, 0, 64, 39, 1 ); } } } return true; } case ICN::TWNWCAPT: { LoadOriginalICN( id ); if ( !_icnVsSprite[id].empty() ) { Sprite & original = _icnVsSprite[id][0]; if ( original.width() == 118 && original.height() ) { // Remove shadow from left side. FillTransform( original, 85, 84, 33, 26, 1 ); // Remove extra terrain at the bottom. FillTransform( original, 0, 114, 40, 4, 1 ); FillTransform( original, 9, 112, 51, 2, 1 ); FillTransform( original, 35, 110, 47, 2, 1 ); FillTransform( original, 57, 108, 51, 2, 1 ); } } return true; } case ICN::GOOD_ARMY_BUTTON: case ICN::GOOD_MARKET_BUTTON: { _icnVsSprite[id].resize( 2 ); loadICN( ICN::ADVBTNS ); const int releasedIndex = ( id == ICN::GOOD_ARMY_BUTTON ) ? 0 : 4; Copy( GetICN( ICN::ADVBTNS, releasedIndex ), _icnVsSprite[id][0] ); Copy( GetICN( ICN::ADVBTNS, releasedIndex + 1 ), _icnVsSprite[id][1] ); // Make all black pixels transparent. AddTransparency( _icnVsSprite[id][0], 36 ); AddTransparency( _icnVsSprite[id][1], 36 ); AddTransparency( _icnVsSprite[id][1], 61 ); // remove the extra brown border return true; } case ICN::EVIL_ARMY_BUTTON: case ICN::EVIL_MARKET_BUTTON: { _icnVsSprite[id].resize( 2 ); loadICN( ICN::ADVEBTNS ); const int releasedIndex = ( id == ICN::EVIL_ARMY_BUTTON ) ? 0 : 4; Copy( GetICN( ICN::ADVEBTNS, releasedIndex ), _icnVsSprite[id][0] ); Copy( GetICN( ICN::ADVEBTNS, releasedIndex + 1 ), _icnVsSprite[id][1] ); // Make all black pixels transparent. AddTransparency( _icnVsSprite[id][0], 36 ); AddTransparency( _icnVsSprite[id][1], 36 ); // Add the bottom-left dark border. Fill( _icnVsSprite[id][1], 1, 4, 1, 30, 36 ); Fill( _icnVsSprite[id][1], 1, 34, 31, 1, 36 ); // Restore back black pixels in the middle of the image. Copy( _icnVsSprite[ICN::ADVEBTNS][releasedIndex], 9, 6, _icnVsSprite[id][0], 9, 6, 20, 22 ); Copy( _icnVsSprite[ICN::ADVEBTNS][releasedIndex + 1], 8, 7, _icnVsSprite[id][1], 8, 7, 20, 22 ); return true; } case ICN::SPANBTN: case ICN::SPANBTNE: case ICN::CSPANBTN: case ICN::CSPANBTE: { LoadOriginalICN( id ); if ( !_icnVsSprite[id].empty() ) { // add missing part of the released button state on the left Sprite & out = _icnVsSprite[id][0]; Sprite released( out.width() + 1, out.height() ); released.reset(); const uint8_t color = id == ICN::SPANBTN || id == ICN::CSPANBTN ? 57 : 32; DrawLine( released, { 0, 3 }, { 0, out.height() - 1 }, color ); Blit( out, released, 1, 0 ); out = std::move( released ); } return true; } case ICN::TRADPOSE: { LoadOriginalICN( id ); if ( _icnVsSprite[id].size() >= 19 ) { // fix background for TRADE and EXIT buttons for ( const uint32_t i : { 16, 18 } ) { Sprite pressed; std::swap( pressed, _icnVsSprite[id][i] ); AddTransparency( pressed, 25 ); // remove too dark background // take background from the empty system button _icnVsSprite[id][i] = GetICN( ICN::SYSTEME, 12 ); // put back dark-gray pixels in the middle of the button Fill( _icnVsSprite[id][i], 5, 5, 86, 17, 25 ); Blit( pressed, _icnVsSprite[id][i] ); } } return true; } case ICN::RECRUIT: { LoadOriginalICN( id ); if ( _icnVsSprite[id].size() >= 10 ) { // fix transparent corners on released OKAY button CopyTransformLayer( _icnVsSprite[id][9], _icnVsSprite[id][8] ); } return true; } case ICN::NGEXTRA: { LoadOriginalICN( id ); std::vector & images = _icnVsSprite[id]; if ( images.size() >= 34 ) { // Fix extra column at the end of AI controlled player. for ( size_t i = 27; i < 34; ++i ) { if ( images[i].width() == 62 && images[i].height() == 58 ) { Copy( images[i], 58, 44, images[i], 59, 44, 1, 11 ); } } for ( size_t i = 39; i < 45; ++i ) { if ( images[i].width() == 62 && images[i].height() == 58 ) { Copy( images[i], 58, 44, images[i], 59, 44, 1, 11 ); } } } if ( images.size() >= 70 ) { // fix transparent corners on pressed OKAY and CANCEL buttons CopyTransformLayer( images[66], images[67] ); CopyTransformLayer( images[68], images[69] ); } return true; } case ICN::DIFFICULTY_ICON_EASY: case ICN::DIFFICULTY_ICON_NORMAL: case ICN::DIFFICULTY_ICON_HARD: case ICN::DIFFICULTY_ICON_EXPERT: case ICN::DIFFICULTY_ICON_IMPOSSIBLE: { const int originalIcnId = ICN::NGHSBKG; const fheroes2::Sprite & originalBackground = GetICN( originalIcnId, 0 ); if ( !originalBackground.empty() ) { _icnVsSprite[id].resize( 2 ); int32_t iconOffsetX = 0; switch ( id ) { case ICN::DIFFICULTY_ICON_EASY: iconOffsetX = 24; break; case ICN::DIFFICULTY_ICON_NORMAL: iconOffsetX = 101; break; case ICN::DIFFICULTY_ICON_HARD: iconOffsetX = 177; break; case ICN::DIFFICULTY_ICON_EXPERT: iconOffsetX = 254; break; case ICN::DIFFICULTY_ICON_IMPOSSIBLE: iconOffsetX = 331; break; default: // Did you add a new difficulty? assert( 0 ); } const int32_t iconSideLength = 65; const int32_t iconOffsetY = 94; _icnVsSprite[id][0] = Crop( originalBackground, iconOffsetX, iconOffsetY, iconSideLength, iconSideLength ); _icnVsSprite[id][0].setPosition( 0, 0 ); // Generate Evil Icons _icnVsSprite[id][1] = _icnVsSprite[id][0]; const std::vector & goodToEvilPalette = PAL::GetPalette( PAL::PaletteType::GOOD_TO_EVIL_INTERFACE ); fheroes2::ApplyPalette( _icnVsSprite[id][0], _icnVsSprite[id][1], goodToEvilPalette ); } return true; } case ICN::METALLIC_BORDERED_TEXTBOX_GOOD: { const int originalIcnId = ICN::NGHSBKG; const fheroes2::Sprite & originalBackground = GetICN( originalIcnId, 0 ); if ( !originalBackground.empty() ) { _icnVsSprite[id].resize( 1 ); const int32_t boxWidth = 371; const int32_t boxHeight = 30; const int32_t goodOriginalBoxOffsetX = 24; const int32_t goodOriginalBoxOffsetY = 40; _icnVsSprite[id][0] = Crop( originalBackground, goodOriginalBoxOffsetX, goodOriginalBoxOffsetY, boxWidth, boxHeight ); _icnVsSprite[id][0].setPosition( 0, 0 ); // Copy red pattern and cover up embedded button. const fheroes2::Sprite & redPart = fheroes2::Flip( Crop( originalBackground, 80, 45, 81, 19 ), true, false ); Copy( redPart, 0, 0, _icnVsSprite[id][0], 284, 5, 81, 19 ); } return true; } case ICN::METALLIC_BORDERED_TEXTBOX_EVIL: { const fheroes2::Sprite & originalEvilBackground = GetICN( ICN::CAMPBKGE, 0 ); if ( !originalEvilBackground.empty() ) { _icnVsSprite[id].resize( 1 ); const int32_t boxWidth = 371; const int32_t boxHeight = 30; _icnVsSprite[id][0].resize( boxWidth, boxHeight ); _icnVsSprite[id][0].reset(); const int32_t evilOriginalBoxOffsetX = 26; const int32_t evilOriginalBoxOffsetY = 27; const int32_t upperPartHeight = 20; // The metallic box frame in campbkge is slightly taller than the one in nghsbkg. The width is the same. Copy( originalEvilBackground, evilOriginalBoxOffsetX, evilOriginalBoxOffsetY, _icnVsSprite[id][0], 0, 0, boxWidth, upperPartHeight ); Copy( originalEvilBackground, evilOriginalBoxOffsetX, evilOriginalBoxOffsetY + upperPartHeight + 14, _icnVsSprite[id][0], 0, upperPartHeight, boxWidth, 10 ); // Copy red central part. const fheroes2::Sprite goodBox = GetICN( ICN::METALLIC_BORDERED_TEXTBOX_GOOD, 0 ); Copy( goodBox, 6, 5, _icnVsSprite[id][0], 6, 5, 359, 19 ); } return true; } case ICN::TWNWUP_5: { LoadOriginalICN( id ); if ( !_icnVsSprite[id].empty() && _icnVsSprite[id].front().width() == 84 && _icnVsSprite[id].front().height() == 256 ) { // Fix glowing red pixel. Copy( _icnVsSprite[id].front(), 52, 92, _icnVsSprite[id].front(), 54, 92, 1, 1 ); } return true; } case ICN::MONO_CURSOR_ADVMBW: { loadICN( ICN::ADVMCO ); _icnVsSprite[id].resize( _icnVsSprite[ICN::ADVMCO].size() ); for ( size_t i = 0; i < _icnVsSprite[id].size(); ++i ) { std::string digit; if ( i < 9 ) { digit += '0'; } digit += std::to_string( i + 1 ); _icnVsSprite[id][i] = loadBMPFile( std::string( "ADVMBW" ) + digit + ".BMP" ); } return true; } case ICN::MONO_CURSOR_SPELBW: { loadICN( ICN::SPELCO ); _icnVsSprite[id].resize( _icnVsSprite[ICN::SPELCO].size() ); for ( size_t i = 0; i < _icnVsSprite[id].size(); ++i ) { std::string digit; if ( i < 10 ) { digit += '0'; } digit += std::to_string( i ); _icnVsSprite[id][i] = loadBMPFile( std::string( "SPELBW" ) + digit + ".BMP" ); } return true; } case ICN::MONO_CURSOR_CMSSBW: { loadICN( ICN::CMSECO ); _icnVsSprite[id].resize( _icnVsSprite[ICN::CMSECO].size() ); for ( size_t i = 0; i < _icnVsSprite[id].size(); ++i ) { std::string digit; if ( i < 9 ) { digit += '0'; } digit += std::to_string( i + 1 ); _icnVsSprite[id][i] = loadBMPFile( std::string( "CMSEBW" ) + digit + ".BMP" ); } return true; } case ICN::ADVBTNS: case ICN::ADVEBTNS: LoadOriginalICN( id ); if ( _icnVsSprite[id].size() == 16 && _icnVsSprite[id][2].width() == 36 && _icnVsSprite[id][2].height() == 36 && _icnVsSprite[id][3].width() == 36 && _icnVsSprite[id][3].height() == 36 ) { // Add hero action button released and pressed. _icnVsSprite[id].resize( 18 ); Copy( _icnVsSprite[id][2], _icnVsSprite[id][16] ); Copy( _icnVsSprite[id][3], _icnVsSprite[id][17] ); // Get the button's icon colors. const uint8_t mainReleasedColor = _icnVsSprite[id][2].image()[7 * 36 + 26]; const uint8_t mainPressedColor = _icnVsSprite[id][3].image()[8 * 36 + 25]; const uint8_t backgroundReleasedColor = _icnVsSprite[id][2].image()[1 * 36 + 5]; const uint8_t backgroundPressedColor = _icnVsSprite[id][3].image()[5 * 36 + 6]; // Clean-up the buttons' background Fill( _icnVsSprite[id][16], 23, 5, 8, 5, backgroundReleasedColor ); Fill( _icnVsSprite[id][16], 8, 10, 24, 8, backgroundReleasedColor ); Fill( _icnVsSprite[id][16], 6, 18, 24, 10, backgroundReleasedColor ); Fill( _icnVsSprite[id][17], 22, 6, 8, 5, backgroundPressedColor ); Fill( _icnVsSprite[id][17], 7, 11, 24, 8, backgroundPressedColor ); Fill( _icnVsSprite[id][17], 5, 19, 24, 10, backgroundPressedColor ); // Get the action cursor and prepare it for button. We make it a little smaller. const Sprite & originalActionCursor = GetICN( ICN::ADVMCO, 9 ); const int32_t actionCursorWidth = originalActionCursor.width() - 1; const int32_t actionCursorHeight = originalActionCursor.height() - 3; Image actionCursor( actionCursorWidth, actionCursorHeight ); actionCursor.reset(); // Head. Copy( originalActionCursor, 19, 1, actionCursor, 17, 2, 8, 5 ); Copy( originalActionCursor, 16, 7, actionCursor, 14, 7, 12, 2 ); actionCursor.transform()[15 + 7 * actionCursorWidth] = 1U; // Tail. Copy( originalActionCursor, 1, 10, actionCursor, 1, 9, 12, 11 ); // Middle part. Copy( originalActionCursor, 14, 10, actionCursor, 13, 9, 1, 11 ); // Front legs. Copy( originalActionCursor, 16, 10, actionCursor, 14, 9, 13, 11 ); // Hind legs. Copy( originalActionCursor, 7, 22, actionCursor, 7, 19, 7, 7 ); // Make contour transparent and the horse figure filled with solid color. const int32_t actionCursorSize = actionCursorWidth * actionCursorHeight; for ( int32_t i = 0; i < actionCursorSize; ++i ) { if ( actionCursor.transform()[i] == 1U ) { // Skip transparent pixel. continue; } if ( actionCursor.image()[i] < 152U ) { // It is the contour color, make it transparent. actionCursor.transform()[i] = 1U; } else { actionCursor.image()[i] = mainPressedColor; } } // Add shadows to the horse image. updateShadow( actionCursor, { 1, -1 }, 2, true ); updateShadow( actionCursor, { -1, 1 }, 6, true ); updateShadow( actionCursor, { 2, -2 }, 4, true ); Blit( actionCursor, _icnVsSprite[id][17], 4, 4 ); // Replace colors for the released button. for ( int32_t i = 0; i < actionCursorSize; ++i ) { if ( actionCursor.transform()[i] == 6U ) { // Disable whitening transform and set white color. actionCursor.transform()[i] = 0U; actionCursor.image()[i] = 10U; } } ReplaceColorId( actionCursor, mainPressedColor, mainReleasedColor ); Blit( actionCursor, _icnVsSprite[id][16], 5, 3 ); } return true; case ICN::ARTIFACT: LoadOriginalICN( id ); if ( _icnVsSprite[id].size() > 99 ) { // This fixes "Arm of the Martyr" (#88) and " Sphere of Negation" (#99) artifacts rendering which initially has some incorrect transparent pixels. for ( const int32_t index : { 88, 99 } ) { Sprite & originalImage = _icnVsSprite[id][index]; Sprite temp( originalImage.width(), originalImage.height() ); temp.setPosition( originalImage.x(), originalImage.y() ); temp._disableTransformLayer(); temp.fill( 0 ); Blit( originalImage, temp ); originalImage = std::move( temp ); } } return true; case ICN::OBJNARTI: LoadOriginalICN( id ); if ( _icnVsSprite[id].size() == 206 ) { // If we have the Price of Loyalty assets we make a map sprite for the Magic Book artifact. _icnVsSprite[id].resize( 208 ); // Magic book sprite shadow. _icnVsSprite[id][206] = _icnVsSprite[id][162]; FillTransform( _icnVsSprite[id][206], 0, 0, 5, 1, 1U ); FillTransform( _icnVsSprite[id][206], 0, 1, 2, 1, 1U ); FillTransform( _icnVsSprite[id][206], 2, 1, 4, 1, 3U ); FillTransform( _icnVsSprite[id][206], 0, 2, 3, 1, 3U ); FillTransform( _icnVsSprite[id][206], 18, 1, 2, 1, 1U ); FillTransform( _icnVsSprite[id][206], 17, 2, 3, 1, 3U ); FillTransform( _icnVsSprite[id][206], 20, 2, 1, 1, 1U ); FillTransform( _icnVsSprite[id][206], 19, 3, 2, 1, 3U ); // Magic Book main sprite. We use sprite from the info dialog to make the map sprite. _icnVsSprite[id][207].resize( 21, 32 ); Copy( GetICN( ICN::ARTFX, 81 ), 6, 0, _icnVsSprite[id][207], 0, 0, 21, 32 ); FillTransform( _icnVsSprite[id][207], 0, 0, 12, 1, 1U ); FillTransform( _icnVsSprite[id][207], 15, 0, 6, 1, 1U ); FillTransform( _icnVsSprite[id][207], 0, 1, 9, 1, 1U ); FillTransform( _icnVsSprite[id][207], 16, 1, 5, 1, 1U ); FillTransform( _icnVsSprite[id][207], 0, 2, 6, 1, 1U ); FillTransform( _icnVsSprite[id][207], 20, 2, 1, 1, 1U ); FillTransform( _icnVsSprite[id][207], 0, 4, 1, 1, 1U ); FillTransform( _icnVsSprite[id][207], 0, 3, 3, 1, 1U ); FillTransform( _icnVsSprite[id][207], 20, 25, 1, 1, 1U ); FillTransform( _icnVsSprite[id][207], 18, 26, 3, 1, 1U ); FillTransform( _icnVsSprite[id][207], 16, 27, 5, 1, 1U ); FillTransform( _icnVsSprite[id][207], 14, 28, 9, 1, 1U ); FillTransform( _icnVsSprite[id][207], 0, 29, 1, 1, 1U ); FillTransform( _icnVsSprite[id][207], 12, 29, 11, 1, 1U ); FillTransform( _icnVsSprite[id][207], 0, 30, 3, 1, 1U ); FillTransform( _icnVsSprite[id][207], 10, 30, 13, 1, 1U ); FillTransform( _icnVsSprite[id][207], 0, 31, 5, 1, 1U ); FillTransform( _icnVsSprite[id][207], 8, 31, 15, 1, 1U ); } return true; case ICN::TWNSDW_5: LoadOriginalICN( id ); if ( !_icnVsSprite[id].empty() && _icnVsSprite[id][0].width() == 140 && _icnVsSprite[id][0].height() == 165 ) { Sprite & image = _icnVsSprite[id][0]; // Red Tower has multiple defects. // First one is the area between columns in middle of the Tower is prerendered. We need to remove it. const int32_t windowBottom = 88; FillTransform( image, 39, 68, 1, windowBottom - 68, 1 ); FillTransform( image, 40, 67, 1, windowBottom - 67, 1 ); FillTransform( image, 41, 66, 1, windowBottom - 66, 1 ); FillTransform( image, 42, 65, 1, windowBottom - 65, 1 ); FillTransform( image, 43, 66, 1, windowBottom - 66, 1 ); FillTransform( image, 44, 67, 1, windowBottom - 67, 1 ); FillTransform( image, 45, 71, 1, windowBottom - 71, 1 ); FillTransform( image, 49, 70, 1, windowBottom - 70, 1 ); FillTransform( image, 50, 68, 2, windowBottom - 68, 1 ); FillTransform( image, 52, 69, 1, windowBottom - 69, 1 ); FillTransform( image, 53, 74, 1, windowBottom - 74, 1 ); FillTransform( image, 57, 70, 1, windowBottom - 70, 1 ); FillTransform( image, 58, 67, 1, windowBottom - 67, 1 ); FillTransform( image, 59, 66, 1, windowBottom - 66, 1 ); FillTransform( image, 60, 65, 2, windowBottom - 65, 1 ); FillTransform( image, 62, 67, 1, windowBottom - 67, 1 ); FillTransform( image, 63, 69, 1, windowBottom - 69, 1 ); FillTransform( image, 64, 72, 1, windowBottom - 72, 1 ); // The lower part of the tower is truncated and blocked by partial castle's sprite. The fix is done in multiple stages. // Fix right red part of the building by copying a piece of the same wall. Copy( image, 67, 135, image, 67, 119, 1, 1 ); Copy( image, 67, 144, image, 67, 120, 2, 2 ); Copy( image, 67, 134, image, 67, 122, 3, 2 ); Copy( image, 67, 148, image, 67, 125, 1, 4 ); // Remove a part of the castle at the bottom left part of the image. FillTransform( image, 62, 157, 3, 8, 1 ); // Top part of the castle's tower touches Red Tower level separation part. Copy( image, 61, 101, image, 57, 101, 2, 1 ); Copy( image, 52, 100, image, 57, 100, 2, 1 ); // Generate programmatically the left part of the building. std::mt19937 seededGen( 751 ); // 751 is and ID of this sprite. To keep the changes constant we need to hardcode this value. fillRandomPixelsFromImage( image, { 33, 105, 4, 7 }, image, { 33, 117, 4, 39 }, seededGen ); fillRandomPixelsFromImage( image, { 41, 105, 5, 9 }, image, { 41, 121, 5, 36 }, seededGen ); fillRandomPixelsFromImage( image, { 46, 104, 4, 13 }, image, { 46, 118, 4, 39 }, seededGen ); Copy( image, 37, 113, image, 37, 115, 1, 2 ); Copy( image, 37, 104, image, 37, 117, 1, 2 ); Copy( image, 38, 104, image, 38, 118, 2, 1 ); Copy( image, 37, 113, image, 38, 117, 1, 1 ); // Create a temporary image to be a holder of pixels. Sprite temp( 4 * 2, 8 ); Copy( image, 33, 105, temp, 0, 0, 4, 8 ); Copy( image, 41, 105, temp, 4, 0, 4, 8 ); fillRandomPixelsFromImage( temp, { 0, 0, temp.width(), temp.height() }, image, { 37, 119, 4, 37 }, seededGen ); Copy( image, 35, 131, image, 35, 113, 2, 4 ); Copy( image, 43, 133, image, 43, 115, 3, 6 ); // Fix the main arc. Copy( image, 61, 102, image, 56, 102, 3, 1 ); // TODO: the distribution of light inside Red Tower is actually not uniform and follows the window on from th left. // However, generating such complex image requires a lot of code so we simply make the rest of the arc uniformed filled. fillRandomPixelsFromImage( image, { 61, 104, 2, 3 }, image, { 50, 110, 12, 47 }, seededGen ); fillRandomPixelsFromImage( image, { 61, 104, 2, 3 }, image, { 52, 107, 9, 3 }, seededGen ); fillRandomPixelsFromImage( image, { 61, 104, 2, 3 }, image, { 62, 111, 1, 46 }, seededGen ); fillRandomPixelsFromImage( image, { 61, 104, 2, 3 }, image, { 63, 113, 1, 20 }, seededGen ); fillRandomPixelsFromImage( image, { 61, 104, 2, 3 }, image, { 63, 141, 1, 16 }, seededGen ); fillRandomPixelsFromImage( image, { 61, 104, 2, 3 }, image, { 64, 115, 1, 17 }, seededGen ); fillRandomPixelsFromImage( image, { 61, 104, 2, 3 }, image, { 64, 152, 1, 5 }, seededGen ); fillRandomPixelsFromImage( image, { 61, 104, 2, 3 }, image, { 65, 116, 1, 15 }, seededGen ); fillRandomPixelsFromImage( image, { 61, 104, 2, 3 }, image, { 66, 118, 1, 12 }, seededGen ); fillRandomPixelsFromImage( image, { 61, 104, 2, 3 }, image, { 51, 108, 1, 2 }, seededGen ); fillRandomPixelsFromImage( image, { 61, 104, 2, 3 }, image, { 55, 103, 5, 4 }, seededGen ); fillRandomPixelsFromImage( image, { 61, 104, 2, 3 }, image, { 61, 109, 1, 1 }, seededGen ); fillRandomPixelsFromImage( image, { 61, 104, 2, 3 }, image, { 52, 106, 1, 1 }, seededGen ); } return true; case ICN::SCENIBKG: LoadOriginalICN( id ); if ( !_icnVsSprite[id].empty() && _icnVsSprite[id][0].width() == 436 && _icnVsSprite[id][0].height() == 476 ) { const Sprite & helper = GetICN( ICN::CSPANBKE, 1 ); if ( !helper.empty() ) { Sprite & original = _icnVsSprite[id][0]; Sprite temp( original.width(), original.height() + 12 ); temp.reset(); Copy( original, 0, 0, temp, 0, 0, original.width(), original.height() ); Copy( helper, 0, helper.height() - 12, temp, 0, temp.height() - 12, 300, 12 ); Copy( helper, helper.width() - ( temp.width() - 300 ), helper.height() - 12, temp, 300 - 16, temp.height() - 12, temp.width() - 300, 12 ); original = std::move( temp ); } } return true; case ICN::CSTLCAPS: LoadOriginalICN( id ); if ( !_icnVsSprite[id].empty() && _icnVsSprite[id][0].width() == 84 && _icnVsSprite[id][0].height() == 81 ) { const Sprite & castle = GetICN( ICN::TWNSCSTL, 0 ); if ( !castle.empty() ) { Blit( castle, 206, 106, _icnVsSprite[id][0], 2, 2, 33, 67 ); } } return true; case ICN::LGNDXTRA: // Exit button is too huge due to 1 pixel presence at the bottom of the image. LoadOriginalICN( id ); if ( _icnVsSprite[id].size() >= 6 ) { auto & original = _icnVsSprite[id]; if ( original[4].height() == 142 ) { const Point offset( original[4].x(), original[4].y() ); original[4] = Crop( original[4], 0, 0, original[4].width(), 25 ); original[4].setPosition( offset.x, offset.y ); } if ( original[5].height() == 142 ) { const Point offset( original[5].x(), original[5].y() ); original[5] = Crop( original[5], 0, 0, original[5].width(), 25 ); original[5].setPosition( offset.x, offset.y ); } } return true; case ICN::LGNDXTRE: // Exit button is too huge due to 1 pixel presence at the bottom of the image. LoadOriginalICN( id ); if ( _icnVsSprite[id].size() >= 6 ) { auto & original = _icnVsSprite[id]; if ( original[4].height() == 142 ) { const Point offset( original[4].x(), original[4].y() ); original[4] = Crop( original[4], 0, 0, original[4].width(), 25 ); original[4].setPosition( offset.x, offset.y ); } } return true; case ICN::ESPANBKG_EVIL: { _icnVsSprite[id].resize( 2 ); const Rect roi{ 28, 28, 265, 206 }; Sprite & output = _icnVsSprite[id][0]; _icnVsSprite[id][0] = GetICN( ICN::CSPANBKE, 0 ); Copy( GetICN( ICN::ESPANBKG, 0 ), roi.x, roi.y, output, roi.x, roi.y, roi.width, roi.height ); convertToEvilInterface( output, roi ); _icnVsSprite[id][1] = GetICN( ICN::ESPANBKG, 1 ); return true; } case ICN::RECR2BKG_EVIL: { GetICN( ICN::RECR2BKG, 0 ); _icnVsSprite[id] = _icnVsSprite[ICN::RECR2BKG]; if ( !_icnVsSprite[id].empty() ) { const Rect roi( 0, 0, _icnVsSprite[id][0].width(), _icnVsSprite[id][0].height() ); convertToEvilInterface( _icnVsSprite[id][0], roi ); copyEvilInterfaceElements( _icnVsSprite[id][0], roi ); } return true; } case ICN::RECRBKG_EVIL: { GetICN( ICN::RECRBKG, 0 ); _icnVsSprite[id] = _icnVsSprite[ICN::RECRBKG]; if ( !_icnVsSprite[id].empty() ) { const Rect roi( 0, 0, _icnVsSprite[id][0].width(), _icnVsSprite[id][0].height() ); convertToEvilInterface( _icnVsSprite[id][0], roi ); copyEvilInterfaceElements( _icnVsSprite[id][0], roi ); } return true; } case ICN::STONEBAK_EVIL: { GetICN( ICN::STONEBAK, 0 ); _icnVsSprite[id] = _icnVsSprite[ICN::STONEBAK]; if ( !_icnVsSprite[id].empty() ) { const Rect roi( 0, 0, _icnVsSprite[id][0].width(), _icnVsSprite[id][0].height() ); convertToEvilInterface( _icnVsSprite[id][0], roi ); } return true; } case ICN::STONEBAK_SMALL_POL: { _icnVsSprite[id].resize( 1 ); const fheroes2::Sprite & original = GetICN( ICN::X_CMPBKG, 0 ); if ( !original.empty() ) { _icnVsSprite[id][0] = Crop( original, original.width() - 272, original.height() - 52, 244, 28 ); } return true; } case ICN::REDBAK_SMALL_VERTICAL: { _icnVsSprite[id].resize( 1 ); const fheroes2::Sprite & original = GetICN( ICN::HEROBKG, 0 ); if ( !original.empty() ) { _icnVsSprite[id][0] = Crop( original, 0, 0, 37, 230 ); } return true; } case ICN::UNIFORMBAK_GOOD: case ICN::UNIFORMBAK_EVIL: { _icnVsSprite[id].resize( 1 ); const bool isEvilInterface = ( id == ICN::UNIFORMBAK_EVIL ); const fheroes2::Sprite & original = GetICN( isEvilInterface ? ICN::BUYBUILE : ICN::BUYBUILD, 1 ); if ( !original.empty() ) { _icnVsSprite[id][0].resize( 246, 45 ); _icnVsSprite[id][0].reset(); Copy( original, 0, 0, _icnVsSprite[id][0], 0, 0, 123, 45 ); Copy( original, 0, 0, _icnVsSprite[id][0], 123, 0, 123, 45 ); } return true; } case ICN::WELLBKG_EVIL: { GetICN( ICN::WELLBKG, 0 ); _icnVsSprite[id] = _icnVsSprite[ICN::WELLBKG]; if ( !_icnVsSprite[id].empty() ) { const Rect roi( 0, 0, _icnVsSprite[id][0].width(), _icnVsSprite[id][0].height() - 19 ); convertToEvilInterface( _icnVsSprite[id][0], roi ); } return true; } case ICN::CASLWIND_EVIL: { GetICN( ICN::CASLWIND, 0 ); _icnVsSprite[id] = _icnVsSprite[ICN::CASLWIND]; if ( !_icnVsSprite[id].empty() ) { const Rect roi( 0, 0, _icnVsSprite[id][0].width(), _icnVsSprite[id][0].height() ); convertToEvilInterface( _icnVsSprite[id][0], roi ); } return true; } case ICN::CASLXTRA_EVIL: { GetICN( ICN::CASLXTRA, 0 ); _icnVsSprite[id] = _icnVsSprite[ICN::CASLXTRA]; if ( !_icnVsSprite[id].empty() ) { const Rect roi( 0, 0, _icnVsSprite[id][0].width(), _icnVsSprite[id][0].height() ); convertToEvilInterface( _icnVsSprite[id][0], roi ); } return true; } case ICN::STRIP_BACKGROUND_EVIL: { _icnVsSprite[id].resize( 1 ); _icnVsSprite[id][0] = GetICN( ICN::STRIP, 11 ); const Rect roi( 0, 0, _icnVsSprite[id][0].width(), _icnVsSprite[id][0].height() - 7 ); convertToEvilInterface( _icnVsSprite[id][0], roi ); return true; } case ICN::B_BFLG32: case ICN::G_BFLG32: case ICN::R_BFLG32: case ICN::Y_BFLG32: case ICN::O_BFLG32: case ICN::P_BFLG32: LoadOriginalICN( id ); if ( _icnVsSprite[id].size() > 31 && _icnVsSprite[id][31].height() == 248 ) { Sprite & original = _icnVsSprite[id][31]; Sprite temp = Crop( original, 0, 0, original.width(), 4 ); temp.setPosition( original.x(), original.y() ); original = std::move( temp ); } return true; case ICN::FLAG32: LoadOriginalICN( id ); // Only first 14 images are properly aligned within an Adventure Map tile. The rest of images should be rendered on multiple tiles. // To keep proper rendering logic we are creating new images by diving existing ones into 2 parts and setting up new sprite offsets. // This helps to solve the problem with rendering order. _icnVsSprite[id].resize( 128 + _icnVsSprite[id].size() * 2 ); for ( int32_t i = 14; i < 14 + 7; ++i ) { const Sprite & original = _icnVsSprite[id][i]; _icnVsSprite[id][i + 128] = Crop( original, 0, 0, 32 - original.x(), original.height() ); _icnVsSprite[id][i + 128].setPosition( original.x(), 32 + original.y() ); _icnVsSprite[id][i + 128 + 7] = Crop( original, 32 - original.x(), 0, original.width(), original.height() ); _icnVsSprite[id][i + 128 + 7].setPosition( 0, 32 + original.y() ); } for ( int32_t i = 42; i < 42 + 7; ++i ) { const Sprite & original = _icnVsSprite[id][i]; _icnVsSprite[id][i + 128] = Crop( original, 0, 0, -original.x(), original.height() ); _icnVsSprite[id][i + 128].setPosition( 32 + original.x(), original.y() ); _icnVsSprite[id][i + 128 + 7] = Crop( original, -original.x(), 0, original.width(), original.height() ); _icnVsSprite[id][i + 128 + 7].setPosition( 0, original.y() ); } return true; case ICN::SHADOW32: LoadOriginalICN( id ); // The shadow sprite of hero needs to be shifted to match the hero sprite. if ( _icnVsSprite[id].size() == 86 ) { // Direction: TOP (0-8), TOP_RIGHT (9-17), RIGHT (18-26), BOTTOM_RIGHT (27-35), BOTTOM (36-44) for ( int32_t i = 0; i < 45; ++i ) { Sprite & original = _icnVsSprite[id][i]; original.setPosition( original.x(), original.y() - 3 ); } // Direction:TOP_LEFT for ( int32_t i = 59; i < 68; ++i ) { Sprite & original = _icnVsSprite[id][i]; original.setPosition( original.x() + 1, original.y() - 3 ); } // Direction:LEFT for ( int32_t i = 68; i < 77; ++i ) { Sprite & original = _icnVsSprite[id][i]; original.setPosition( original.x() - 5, original.y() - 3 ); } // Direction:BOTTOM_LEFT for ( int32_t i = 77; i < 86; ++i ) { Sprite & original = _icnVsSprite[id][i]; if ( i == 80 ) { // This sprite needs extra fix. original.setPosition( original.x() - 5, original.y() - 3 ); } else { original.setPosition( original.x() - 10, original.y() - 3 ); } } } return true; case ICN::MINI_MONSTER_IMAGE: case ICN::MINI_MONSTER_SHADOW: { // It doesn't matter which image is being called. We are generating both of them at the same time. loadICN( ICN::MINIMON ); // Minotaur King original Adventure map sprite has blue armlets. We make them gold to correspond the ICN::MINOTAU2. if ( _icnVsSprite[ICN::MINIMON].size() > 303 ) { // The gold color gradient has -42 offset from blue color gradient. if ( _icnVsSprite[ICN::MINIMON][297].width() == 38 && _icnVsSprite[ICN::MINIMON][297].height() == 34 ) { // We update these pixels: 29x15, 30x15, 31x15, 30x16. for ( const uint32_t pixelNumber : { 599, 600, 601, 638 } ) { _icnVsSprite[ICN::MINIMON][297].image()[pixelNumber] -= 42; } } for ( uint32_t icnNumber = 298; icnNumber < 300; ++icnNumber ) { if ( _icnVsSprite[ICN::MINIMON][icnNumber].width() == 44 && _icnVsSprite[ICN::MINIMON][icnNumber].height() == 32 ) { // We update these pixels: 29x17, 30x17, 32x17, 30x18, 31x18, 38x18, 38x19, 38x20. for ( const uint32_t pixelNumber : { 777, 778, 780, 822, 823, 830, 874, 918 } ) { _icnVsSprite[ICN::MINIMON][icnNumber].image()[pixelNumber] -= 42; } } } if ( _icnVsSprite[ICN::MINIMON][300].width() == 45 && _icnVsSprite[ICN::MINIMON][300].height() == 32 ) { // We update these pixels: 30x17, 31x17, 33x17, 31x18, 32x18, 39x18, 39x19, 39x20 for ( const uint32_t pixelNumber : { 795, 796, 798, 841, 842, 849, 894, 939 } ) { _icnVsSprite[ICN::MINIMON][300].image()[pixelNumber] -= 42; } } if ( _icnVsSprite[ICN::MINIMON][301].width() == 45 && _icnVsSprite[ICN::MINIMON][301].height() == 32 ) { // We update these pixels: 29x17, 30x17, 32x17, 30x18, 31x18, 39x18, 39x19, 39x20 for ( const uint32_t pixelNumber : { 794, 795, 797, 840, 841, 849, 894, 939 } ) { _icnVsSprite[ICN::MINIMON][301].image()[pixelNumber] -= 42; } } if ( _icnVsSprite[ICN::MINIMON][302].width() == 45 && _icnVsSprite[ICN::MINIMON][302].height() == 32 ) { // We update these pixels: 35x16, 29x17, 30x17, 32x17, 33x17, 34x17, 30x18, 31x18, 31x19, 32x20. for ( const uint32_t pixelNumber : { 755, 794, 795, 797, 798, 799, 840, 841, 886, 932 } ) { _icnVsSprite[ICN::MINIMON][302].image()[pixelNumber] -= 42; } } if ( _icnVsSprite[ICN::MINIMON][303].width() == 44 && _icnVsSprite[ICN::MINIMON][303].height() == 32 ) { // We update these pixels: 29x17, 30x17, 30x18, 31x18, 31x19. for ( const uint32_t pixelNumber : { 777, 778, 822, 823, 867 } ) { _icnVsSprite[ICN::MINIMON][303].image()[pixelNumber] -= 42; } } } // TODO: optimize image sizes. _icnVsSprite[ICN::MINI_MONSTER_IMAGE] = _icnVsSprite[ICN::MINIMON]; _icnVsSprite[ICN::MINI_MONSTER_SHADOW] = _icnVsSprite[ICN::MINIMON]; for ( Sprite & image : _icnVsSprite[ICN::MINI_MONSTER_IMAGE] ) { uint8_t * transform = image.transform(); const uint8_t * transformEnd = transform + image.width() * image.height(); for ( ; transform != transformEnd; ++transform ) { if ( *transform > 1 ) { *transform = 1; } } } for ( Sprite & image : _icnVsSprite[ICN::MINI_MONSTER_SHADOW] ) { uint8_t * transform = image.transform(); const uint8_t * transformEnd = transform + image.width() * image.height(); for ( ; transform != transformEnd; ++transform ) { if ( *transform == 0 ) { *transform = 1; } } } return true; } case ICN::BUTTON_GOOD_FONT_RELEASED: case ICN::BUTTON_GOOD_FONT_PRESSED: case ICN::BUTTON_EVIL_FONT_RELEASED: case ICN::BUTTON_EVIL_FONT_PRESSED: { generateBaseButtonFont( _icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED], _icnVsSprite[ICN::BUTTON_GOOD_FONT_PRESSED], _icnVsSprite[ICN::BUTTON_EVIL_FONT_RELEASED], _icnVsSprite[ICN::BUTTON_EVIL_FONT_PRESSED] ); return true; } case ICN::HISCORE: { LoadOriginalICN( id ); if ( _icnVsSprite[id].size() == 9 ) { // Campaign title bar needs to include rating. const int32_t imageHeight = _icnVsSprite[id][7].height(); const Sprite temp = Crop( _icnVsSprite[id][7], 215, 0, 300, imageHeight ); Copy( temp, 0, 0, _icnVsSprite[id][7], 215 - 57, 0, temp.width(), imageHeight ); Copy( _icnVsSprite[id][6], 324, 0, _icnVsSprite[id][7], 324, 0, _icnVsSprite[id][6].width() - 324, imageHeight ); } return true; } case ICN::SPELLINL: { LoadOriginalICN( id ); if ( _icnVsSprite[id].size() > 11 ) { // Replace petrification spell mini-icon. h2d::readImage( "petrification_spell_icon_mini.image", _icnVsSprite[id][11] ); } return true; } case ICN::EMPTY_GOOD_BUTTON: case ICN::EMPTY_EVIL_BUTTON: { const bool isGoodInterface = ( id == ICN::EMPTY_GOOD_BUTTON ); const int32_t originalId = isGoodInterface ? ICN::SYSTEM : ICN::SYSTEME; loadICN( originalId ); if ( _icnVsSprite[originalId].size() < 13 ) { return true; } _icnVsSprite[id].resize( 2 ); Sprite & released = _icnVsSprite[id][0]; Sprite & pressed = _icnVsSprite[id][1]; released = _icnVsSprite[originalId][11]; // fix single bright pixel in the left part of the text area of the released state buttons Fill( released, 8, 7, 1, 1, getButtonFillingColor( true, isGoodInterface ) ); const Sprite & originalPressed = GetICN( originalId, 12 ); if ( originalPressed.width() > 2 && originalPressed.height() > 2 ) { pressed.resize( originalPressed.width(), originalPressed.height() ); // copy the original pressed button but add the missing darker leftside border from the released state Copy( released, 0, 0, pressed, 0, 0, 1, released.height() ); Copy( originalPressed, 0, 0, pressed, 1, 0, originalPressed.width() - 1, originalPressed.height() ); // Make the background transparent. FillTransform( pressed, 1, 0, pressed.width() - 1, 1, 1 ); FillTransform( pressed, pressed.width() - 1, 1, 1, pressed.height() - 1, 1 ); FillTransform( pressed, 1, 1, 2, 1, 1 ); FillTransform( pressed, 1, 2, 1, 1, 1 ); FillTransform( pressed, pressed.width() - 3, 1, 2, 1, 1 ); FillTransform( pressed, pressed.width() - 2, 2, 1, 1, 1 ); FillTransform( pressed, pressed.width() - 4, pressed.height() - 1, 3, 1, 1 ); FillTransform( pressed, pressed.width() - 3, pressed.height() - 2, 2, 1, 1 ); FillTransform( pressed, pressed.width() - 2, pressed.height() - 3, 1, 1, 1 ); } return true; } case ICN::EMPTY_POL_BUTTON: { const int originalID = ICN::X_CMPBTN; loadICN( originalID ); if ( _icnVsSprite[originalID].size() < 8 ) { return true; } _icnVsSprite[id].resize( 2 ); // move dark border to new released state from original pressed state button const Sprite & originalReleased = GetICN( originalID, 4 ); const Sprite & originalPressed = GetICN( originalID, 5 ); if ( originalReleased.width() != 94 && originalPressed.width() != 94 && originalReleased.height() < 5 && originalPressed.height() < 5 ) { return true; } Sprite & releasedWithDarkBorder = _icnVsSprite[id][0]; releasedWithDarkBorder.resize( originalReleased.width() + 2, originalReleased.height() + 1 ); releasedWithDarkBorder.reset(); Copy( originalReleased, 0, 0, releasedWithDarkBorder, 1, 0, originalReleased.width(), originalReleased.height() ); Copy( originalReleased, 0, 2, releasedWithDarkBorder, 1, 21, 1, 1 ); Copy( originalReleased, 0, 2, releasedWithDarkBorder, 2, 22, 1, 1 ); Copy( originalPressed, 0, 2, releasedWithDarkBorder, 0, 3, 1, 19 ); Copy( originalPressed, 0, originalPressed.height() - 1, releasedWithDarkBorder, 0, originalPressed.height(), originalPressed.width(), 1 ); Copy( originalPressed, 0, 2, releasedWithDarkBorder, 1, 22, 1, 1 ); // pressed state Sprite & pressed = _icnVsSprite[id][1]; pressed.resize( originalPressed.width() + 2, originalPressed.height() + 1 ); pressed.reset(); Copy( originalPressed, 0, 0, pressed, 0, 1, originalPressed.width(), originalPressed.height() ); // the empty buttons need to be widened by 1 px so that they can be evenly divided by 3 in resizeButton() in ui_tools.cpp Copy( originalReleased, originalReleased.width() - 5, 0, releasedWithDarkBorder, releasedWithDarkBorder.width() - 5, 0, 5, originalReleased.height() ); Copy( originalPressed, originalPressed.width() - 5, 0, pressed, pressed.width() - 6, 1, 5, originalPressed.height() ); const int32_t pixelPosition = 4 * 94 + 6; Fill( releasedWithDarkBorder, 5, 3, 88, 18, originalReleased.image()[pixelPosition] ); Fill( pressed, 4, 5, 87, 17, originalPressed.image()[pixelPosition] ); return true; } case ICN::EMPTY_GUILDWELL_BUTTON: { const int originalID = ICN::WELLXTRA; loadICN( originalID ); if ( _icnVsSprite[originalID].size() < 3 ) { return true; } _icnVsSprite[id].resize( 2 ); for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { const Sprite & original = GetICN( originalID, 0 + i ); Sprite & out = _icnVsSprite[id][i]; // the empty button needs to shortened by 1 px so that when it is divided by 3 in resizeButton() in ui_tools.h it will give an integer result out.resize( original.width() - 1, original.height() ); Copy( original, 0, 0, out, 0, 0, original.width() - 4, original.height() ); Copy( original, original.width() - 3, 0, out, original.width() - 4, 0, 3, original.height() ); Fill( out, 7 - i * 2, 2 + i, 50 + i, 14, getButtonFillingColor( i == 0 ) ); } return true; } case ICN::EMPTY_GOOD_MEDIUM_BUTTON: case ICN::EMPTY_EVIL_MEDIUM_BUTTON: { const bool isGoodInterface = ( id == ICN::EMPTY_GOOD_MEDIUM_BUTTON ); const int32_t originalId = isGoodInterface ? ICN::APANEL : ICN::APANELE; loadICN( originalId ); if ( _icnVsSprite[originalId].size() < 10 ) { return true; } _icnVsSprite[id].resize( 2 ); Sprite & released = _icnVsSprite[id][0]; Sprite & pressed = _icnVsSprite[id][1]; released = _icnVsSprite[originalId][2]; pressed = _icnVsSprite[originalId][3]; if ( released.width() > 2 && released.height() > 2 && pressed.width() > 2 && pressed.height() > 2 ) { // Clean the buttons. Fill( released, 28, 15, 42, 27, getButtonFillingColor( true, isGoodInterface ) ); Fill( pressed, 27, 16, 42, 27, getButtonFillingColor( false, isGoodInterface ) ); } return true; } case ICN::EMPTY_VERTICAL_GOOD_BUTTON: { const int32_t originalId = ICN::HSBTNS; loadICN( originalId ); if ( _icnVsSprite[originalId].size() < 9 ) { return true; } _icnVsSprite[id].resize( 2 ); const Sprite & originalReleased = GetICN( originalId, 2 ); const Sprite & originalPressed = GetICN( originalId, 3 ); Sprite & released = _icnVsSprite[id][0]; Sprite & pressed = _icnVsSprite[id][1]; if ( originalReleased.width() > 2 && originalReleased.height() > 2 && originalPressed.width() > 2 && originalPressed.height() > 2 ) { released.resize( originalReleased.width() + 1, originalReleased.height() + 1 ); pressed.resize( originalPressed.width() + 1, originalPressed.height() + 1 ); released.reset(); pressed.reset(); Copy( originalReleased, 0, 0, released, 1, 0, originalReleased.width(), originalReleased.height() ); Copy( originalPressed, 0, 0, pressed, 1, 0, originalPressed.width(), originalPressed.height() ); FillTransform( released, 1, 4, 1, released.height() - 4, 1 ); // Fix the carried over broken transform layer of the original vertical button that is being used. Image exitCommonMask = ExtractCommonPattern( { &released, &pressed } ); // Fix wrong non-transparent pixels of the transform layer that ExtractCommonPattern() missed. FillTransform( exitCommonMask, 4, 2, 1, 115, 1 ); FillTransform( exitCommonMask, 5, 116, 1, 1, 1 ); FillTransform( exitCommonMask, 5, 117, 18, 1, 1 ); FillTransform( exitCommonMask, exitCommonMask.width() - 4, 114, 1, 2, 1 ); invertTransparency( exitCommonMask ); // Make the extended width and height lines transparent. FillTransform( exitCommonMask, 0, 0, 1, exitCommonMask.height(), 1 ); FillTransform( exitCommonMask, exitCommonMask.width() - 4, exitCommonMask.height() - 1, 4, 1, 1 ); CopyTransformLayer( exitCommonMask, released ); CopyTransformLayer( exitCommonMask, pressed ); // Restore dark-brown lines on the left and bottom borders of the button backgrounds. const Sprite & originalDismiss = GetICN( ICN::HSBTNS, 0 ); Copy( originalReleased, 0, 4, released, 1, 4, 1, released.height() - 4 ); Copy( originalDismiss, 6, originalDismiss.height() - 7, released, 2, released.height() - 1, 22, 1 ); Copy( originalDismiss, 6, originalDismiss.height() - 7, released, 1, released.height() - 1, 1, 1 ); Copy( originalPressed, 0, 4, pressed, 1, 4, 1, pressed.height() - 4 ); Copy( originalDismiss, 6, originalDismiss.height() - 7, pressed, 2, pressed.height() - 1, 22, 1 ); Copy( originalDismiss, 6, originalDismiss.height() - 7, pressed, 1, pressed.height() - 1, 1, 1 ); // Clean the button states' text areas. Fill( released, 6, 4, 18, 111, getButtonFillingColor( true ) ); Fill( pressed, 5, 5, 18, 111, getButtonFillingColor( false ) ); // Make the background transparent by removing remaining red background pieces. FillTransform( pressed, 5, 0, 21, 1, 1 ); FillTransform( pressed, pressed.width() - 3, 1, 2, 1, 1 ); FillTransform( pressed, pressed.width() - 2, 2, 2, 1, 1 ); FillTransform( pressed, pressed.width() - 1, 3, 1, pressed.height() - 6, 1 ); } return true; } case ICN::EMPTY_MAP_SELECT_BUTTON: { const int32_t originalId = ICN::NGEXTRA; loadICN( originalId ); if ( _icnVsSprite[originalId].size() < 80 ) { return true; } _icnVsSprite[id].resize( 2 ); for ( int32_t i = 0; i < static_cast( _icnVsSprite[id].size() ); ++i ) { const Sprite & original = GetICN( originalId, 64 + i ); Sprite & out = _icnVsSprite[id][i]; // the empty button needs to widened by 1 px so that when it is divided by 3 in resizeButton() in ui_tools.h it will // give an integer result out.resize( original.width() + 1, original.height() ); Copy( original, 0, 0, out, 0, 0, original.width() - 5, original.height() ); Copy( original, original.width() - 6, 0, out, original.width() - 5, 0, 6, original.height() ); Fill( out, 6 - i, 2 + 2 * i, 72, 15 - i, getButtonFillingColor( i == 0 ) ); } return true; } case ICN::BRCREST: { LoadOriginalICN( id ); // First sprite in this ICN has incorrect transparent pixel at position 30x5. if ( !_icnVsSprite[id].empty() ) { Sprite & original = _icnVsSprite[id][0]; if ( original.width() == 50 && original.height() == 47 ) { original._disableTransformLayer(); original.image()[280] = 117; } } // An extra image for the neutral color. if ( _icnVsSprite[id].size() == 7 ) { Sprite neutralShield( GetICN( ICN::SPELLS, 15 ) ); if ( neutralShield.width() < 2 || neutralShield.height() < 2 ) { // We can not make a new image if there is no original shield image. return true; } // Make original shield image contour transparent. ReplaceColorIdByTransformId( neutralShield, neutralShield.image()[1], 1U ); Sprite neutralColorSprite( _icnVsSprite[id][0].width(), _icnVsSprite[id][0].height() ); neutralColorSprite.reset(); Blit( neutralShield, neutralColorSprite, 8, 4 ); // Make the background. uint8_t * imageData = neutralColorSprite.image(); const uint8_t * transformData = neutralColorSprite.transform(); const int32_t imageWidth = neutralColorSprite.width(); const int32_t imageHeight = neutralColorSprite.height(); const int32_t imageSize = imageWidth * imageHeight; const int32_t startValueX = 12 * imageWidth; const int32_t startValueY = 12 * imageHeight; for ( int32_t y = 0; y < imageHeight; ++y ) { const int32_t offsetY = y * imageWidth; const int32_t offsetValueY = y * startValueX; for ( int32_t x = 0; x < imageWidth; ++x ) { if ( transformData[x + offsetY] == 0U ) { // Skip pixels with image. continue; } const uint8_t colorValue = static_cast( 10 + ( offsetValueY + ( imageWidth - x ) * startValueY ) / imageSize + ( x + y ) % 2 ); imageData[x + offsetY] = ( ( imageWidth - x - 1 ) * imageHeight > offsetY ) ? colorValue : 44U - colorValue; } } // Make all image non-transparent. neutralColorSprite._disableTransformLayer(); // We add shadow twice to make it more dark. addGradientShadow( neutralShield, neutralColorSprite, { 8, 4 }, { -2, 5 } ); addGradientShadow( neutralShield, neutralColorSprite, { 8, 4 }, { -2, 5 } ); _icnVsSprite[id].push_back( std::move( neutralColorSprite ) ); } return true; } case ICN::CBKGWATR: { // Ship battlefield background has incorrect transparent pixel at position 125x36. LoadOriginalICN( id ); if ( !_icnVsSprite[id].empty() ) { Sprite & original = _icnVsSprite[id][0]; if ( original.width() == 640 && original.height() == 443 ) { original._disableTransformLayer(); original.image()[23165] = 24; } } return true; } case ICN::SWAPWIN: case ICN::WELLBKG: { // Hero Meeting dialog and Castle Well images can be used with disabled transform layer. LoadOriginalICN( id ); if ( !_icnVsSprite[id].empty() ) { _icnVsSprite[id][0]._disableTransformLayer(); } return true; } case ICN::GAME_OPTION_ICON: { _icnVsSprite[id].resize( 2 ); h2d::readImage( "hotkeys_icon.image", _icnVsSprite[id][0] ); h2d::readImage( "graphics_icon.image", _icnVsSprite[id][1] ); return true; } case ICN::COVR0010: case ICN::COVR0011: case ICN::COVR0012: { // The original image contains some foreign pixels that do not belong to the image. LoadOriginalICN( id ); if ( !_icnVsSprite[id].empty() ) { Sprite & sprite = _icnVsSprite[id][0]; const uint8_t * image = sprite.image(); const uint8_t * imageEnd = image + static_cast( sprite.width() ) * sprite.height(); uint8_t * transform = sprite.transform(); for ( ; image != imageEnd; ++image, ++transform ) { // Mask all non white/black or brown pixels. if ( *transform == 0 && *image > 36 && ( *image < 108 || *image > 130 ) ) { *transform = 1; } } } return true; } default: break; } return false; } void loadICN( const int id ) { if ( !_icnVsSprite[id].empty() ) { // The images have been loaded. return; } if ( !LoadModifiedICN( id ) ) { LoadOriginalICN( id ); } if ( _icnVsSprite[id].empty() ) { // This could happen by one reason: asking to render an ICN that simply doesn't exist within the resources. // In order to avoid subsequent attempts to get resources from this ICN we are making it as non-empty. _icnVsSprite[id].resize( 1 ); } } size_t GetMaximumICNIndex( int id ) { loadICN( id ); return _icnVsSprite[id].size(); } size_t GetMaximumTILIndex( const int id ) { auto & tilImages = _tilVsImage[id]; if ( tilImages.empty() ) { tilImages.resize( 4 ); // 4 possible sides const std::vector & data = ::AGG::getDataFromAggFile( tilFileName[id] ); if ( data.size() < headerSize ) { // The important resource is absent! Make sure that you are using the correct version of the game. assert( 0 ); return 0; } StreamBuf buffer( data ); const size_t count = buffer.getLE16(); const int32_t width = buffer.getLE16(); const int32_t height = buffer.getLE16(); if ( count < 1 || width < 1 || height < 1 || ( headerSize + count * width * height ) != data.size() ) { return 0; } std::vector & originalTIL = tilImages[0]; decodeTILImages( data.data() + headerSize, count, width, height, originalTIL ); for ( uint32_t shapeId = 1; shapeId < 4; ++shapeId ) { tilImages[shapeId].resize( count ); } for ( size_t i = 0; i < count; ++i ) { for ( uint32_t shapeId = 1; shapeId < 4; ++shapeId ) { Image & image = tilImages[shapeId][i]; const bool horizontalFlip = ( shapeId & 2 ) != 0; const bool verticalFlip = ( shapeId & 1 ) != 0; image._disableTransformLayer(); image.resize( width, height ); Flip( originalTIL[i], 0, 0, image, 0, 0, width, height, horizontalFlip, verticalFlip ); } } } return tilImages[0].size(); } // We have few ICNs which we need to scale like some related to main screen bool IsScalableICN( const int id ) { switch ( id ) { case ICN::EDITOR: case ICN::HEROES: case ICN::BTNSHNGL: case ICN::SHNGANIM: return true; default: return false; } } const Sprite & GetScaledICN( const int icnId, const uint32_t index ) { const Sprite & originalIcn = _icnVsSprite[icnId][index]; const Display & display = Display::instance(); if ( display.width() == Display::DEFAULT_WIDTH && display.height() == Display::DEFAULT_HEIGHT ) { return originalIcn; } if ( _icnVsScaledSprite[icnId].empty() ) { _icnVsScaledSprite[icnId].resize( _icnVsSprite[icnId].size() ); } Sprite & resizedIcn = _icnVsScaledSprite[icnId][index]; if ( originalIcn.singleLayer() && !resizedIcn.singleLayer() ) { resizedIcn._disableTransformLayer(); } const double scaleFactorX = static_cast( display.width() ) / Display::DEFAULT_WIDTH; const double scaleFactorY = static_cast( display.height() ) / Display::DEFAULT_HEIGHT; const double scaleFactor = std::min( scaleFactorX, scaleFactorY ); const int32_t resizedWidth = static_cast( std::lround( originalIcn.width() * scaleFactor ) ); const int32_t resizedHeight = static_cast( std::lround( originalIcn.height() * scaleFactor ) ); const int32_t offsetX = static_cast( std::lround( display.width() - Display::DEFAULT_WIDTH * scaleFactor ) ) / 2; const int32_t offsetY = static_cast( std::lround( display.height() - Display::DEFAULT_HEIGHT * scaleFactor ) ) / 2; assert( offsetX >= 0 && offsetY >= 0 ); // Resize only if needed if ( resizedIcn.height() != resizedHeight || resizedIcn.width() != resizedWidth ) { resizedIcn.resize( resizedWidth, resizedHeight ); resizedIcn.setPosition( static_cast( std::lround( originalIcn.x() * scaleFactor ) ) + offsetX, static_cast( std::lround( originalIcn.y() * scaleFactor ) ) + offsetY ); Resize( originalIcn, resizedIcn ); } else { // No need to resize but we have to update the offset. resizedIcn.setPosition( static_cast( std::lround( originalIcn.x() * scaleFactor ) ) + offsetX, static_cast( std::lround( originalIcn.y() * scaleFactor ) ) + offsetY ); } return resizedIcn; } const Sprite & GetICN( int icnId, uint32_t index ) { if ( !IsValidICNId( icnId ) ) { return errorImage; } if ( index >= GetMaximumICNIndex( icnId ) ) { return errorImage; } if ( IsScalableICN( icnId ) ) { return GetScaledICN( icnId, index ); } return _icnVsSprite[icnId][index]; } uint32_t GetICNCount( int icnId ) { if ( !IsValidICNId( icnId ) ) { return 0; } return static_cast( GetMaximumICNIndex( icnId ) ); } const Image & GetTIL( int tilId, uint32_t index, uint32_t shapeId ) { if ( shapeId > 3 ) { return errorImage; } if ( !IsValidTILId( tilId ) ) { return errorImage; } const size_t maxTILIndex = GetMaximumTILIndex( tilId ); if ( index >= maxTILIndex ) { return errorImage; } return _tilVsImage[tilId][shapeId][index]; } int32_t GetAbsoluteICNHeight( int icnId ) { const uint32_t frameCount = GetICNCount( icnId ); if ( frameCount == 0 ) { return 0; } int32_t height = 0; for ( uint32_t i = 0; i < frameCount; ++i ) { const int32_t offset = -GetICN( icnId, i ).y(); if ( offset > height ) { height = offset; } } return height; } uint32_t getCharacterLimit( const FontSize fontSize ) { switch ( fontSize ) { case FontSize::SMALL: return static_cast( GetMaximumICNIndex( ICN::SMALFONT ) ) + 0x20 - 1; case FontSize::NORMAL: case FontSize::LARGE: return static_cast( GetMaximumICNIndex( ICN::FONT ) ) + 0x20 - 1; case FontSize::BUTTON_RELEASED: case FontSize::BUTTON_PRESSED: return static_cast( GetMaximumICNIndex( ICN::BUTTON_GOOD_FONT_RELEASED ) ) + 0x20 - 1; default: assert( 0 ); // Did you add a new font size? Please add implementation. } return 0; } const Sprite & getChar( const uint8_t character, const FontType & fontType ) { if ( character < 0x21 ) { return errorImage; } switch ( fontType.size ) { case FontSize::SMALL: switch ( fontType.color ) { case FontColor::WHITE: return GetICN( ICN::SMALFONT, character - 0x20 ); case FontColor::GRAY: return GetICN( ICN::GRAY_SMALL_FONT, character - 0x20 ); case FontColor::YELLOW: return GetICN( ICN::YELLOW_SMALLFONT, character - 0x20 ); default: // Did you add a new font color? Add the corresponding logic for it! assert( 0 ); break; } break; case FontSize::NORMAL: switch ( fontType.color ) { case FontColor::WHITE: return GetICN( ICN::FONT, character - 0x20 ); case FontColor::GRAY: return GetICN( ICN::GRAY_FONT, character - 0x20 ); case FontColor::YELLOW: return GetICN( ICN::YELLOW_FONT, character - 0x20 ); default: // Did you add a new font color? Add the corresponding logic for it! assert( 0 ); break; } break; case FontSize::LARGE: switch ( fontType.color ) { case FontColor::WHITE: return GetICN( ICN::WHITE_LARGE_FONT, character - 0x20 ); default: // Did you add a new font color? Add the corresponding logic for it! assert( 0 ); break; } break; case FontSize::BUTTON_RELEASED: switch ( fontType.color ) { case FontColor::WHITE: return GetICN( ICN::BUTTON_GOOD_FONT_RELEASED, character - 0x20 ); case FontColor::GRAY: return GetICN( ICN::BUTTON_EVIL_FONT_RELEASED, character - 0x20 ); default: // Did you add a new font color? Add the corresponding logic for it! assert( 0 ); break; } break; case FontSize::BUTTON_PRESSED: switch ( fontType.color ) { case FontColor::WHITE: return GetICN( ICN::BUTTON_GOOD_FONT_PRESSED, character - 0x20 ); case FontColor::GRAY: return GetICN( ICN::BUTTON_EVIL_FONT_PRESSED, character - 0x20 ); default: // Did you add a new font color? Add the corresponding logic for it! assert( 0 ); break; } break; default: // Did you add a new font size? Add the corresponding logic for it! assert( 0 ); break; } assert( 0 ); // Did you add a new font size? Please add implementation. return errorImage; } void updateLanguageDependentResources( const SupportedLanguage language, const bool loadOriginalAlphabet ) { if ( loadOriginalAlphabet || !isAlphabetSupported( language ) ) { if ( !alphabetPreserver.isPreserved() ) { // This can happen when we try to change a language without loading assets. alphabetPreserver.preserve(); } else { alphabetPreserver.restore(); } } else { alphabetPreserver.preserve(); // Restore original letters when changing language to avoid changes to them being carried over. alphabetPreserver.restore(); generateAlphabet( language, _icnVsSprite ); } generateButtonAlphabet( language, _icnVsSprite ); // Clear language dependent resources. for ( const int id : languageDependentIcnId ) { _icnVsSprite[id].clear(); } } } } fheroes2-1.0.12+dfsg/src/fheroes2/agg/agg_image.h000066400000000000000000000044211456075706000213550ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include namespace fheroes2 { class Image; class Sprite; enum class FontSize : uint8_t; struct FontType; enum class SupportedLanguage : uint8_t; namespace AGG { const Sprite & GetICN( int icnId, uint32_t index ); uint32_t GetICNCount( int icnId ); // shapeId could be 0, 1, 2 or 3 only const Image & GetTIL( int tilId, uint32_t index, uint32_t shapeId ); int32_t GetAbsoluteICNHeight( int icnId ); uint32_t getCharacterLimit( const FontSize fontSize ); const Sprite & getChar( const uint8_t character, const FontType & fontType ); // This function must be called only at the type of setting up a new language. void updateLanguageDependentResources( const SupportedLanguage language, const bool loadOriginalAlphabet ); } } fheroes2-1.0.12+dfsg/src/fheroes2/agg/bin_info.cpp000066400000000000000000000565101456075706000216010ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "bin_info.h" #include #include #include #include #include #include #include #include "agg.h" #include "battle_animation.h" #include "battle_cell.h" #include "logging.h" #include "monster.h" #include "monster_info.h" #include "serialize.h" namespace { template T getValue( const uint8_t * data, const size_t base, const size_t offset = 0 ) { return fheroes2::getLEValue( reinterpret_cast( data ), base, offset ); } } namespace Bin_Info { class MonsterAnimCache { public: AnimationReference createAnimReference( int monsterID ) const; MonsterAnimInfo getAnimInfo( int monsterID ); private: std::map _animMap; }; const size_t CORRECT_FRM_LENGTH = 821; // When base unit and its upgrade use the same FRM file (e.g. Archer and Ranger) // We modify animation speed value to make them go faster const double MOVE_SPEED_UPGRADE = 0.12; const double SHOOT_SPEED_UPGRADE = 0.08; const double RANGER_SHOOT_SPEED = 0.78; std::map animRefs; MonsterAnimCache _infoCache; const char * GetFilename( int monsterId ) { return fheroes2::getMonsterData( monsterId ).binFileName; } MonsterAnimInfo::MonsterAnimInfo( int monsterID, const std::vector & bytes ) : moveSpeed( 450 ) , shootSpeed( 0 ) , flightSpeed( 0 ) , troopCountOffsetLeft( 0 ) , troopCountOffsetRight( 0 ) , idleAnimationCount( 0 ) , idleAnimationDelay( 0 ) { if ( bytes.size() != Bin_Info::CORRECT_FRM_LENGTH ) { return; } const uint8_t * data = bytes.data(); eyePosition = { getValue( data, 1 ), getValue( data, 3 ) }; for ( size_t moveID = 0; moveID < 7; ++moveID ) { std::vector moveOffset; moveOffset.reserve( 16 ); for ( int frame = 0; frame < 16; ++frame ) { moveOffset.push_back( static_cast( getValue( data, 5 + moveID * 16, frame ) ) ); } frameXOffset.push_back( moveOffset ); } // Idle animations data idleAnimationCount = data[117]; if ( idleAnimationCount > 5u ) idleAnimationCount = 5u; // here we need to reset our object for ( uint32_t i = 0; i < idleAnimationCount; ++i ) idlePriority.push_back( getValue( data, 118, i ) ); for ( uint32_t i = 0; i < idleAnimationCount; ++i ) unusedIdleDelays.push_back( getValue( data, 138, i ) ); idleAnimationDelay = getValue( data, 158 ); // Monster speed data moveSpeed = getValue( data, 162 ); shootSpeed = getValue( data, 166 ); flightSpeed = getValue( data, 170 ); // Projectile data for ( size_t i = 0; i < 3; ++i ) { projectileOffset.emplace_back( getValue( data, 174 + ( i * 4 ) ), getValue( data, 176 + ( i * 4 ) ) ); } // We change all projectile start positions to match the last projectile position in shooting animation, // this position will be used to calculate the projectile path, but will not be used in rendering. if ( monsterID == Monster::ARCHER || monsterID == Monster::RANGER ) { projectileOffset[0].x -= 30; projectileOffset[0].y += 5; projectileOffset[1].x -= 46; projectileOffset[1].y -= 5; projectileOffset[2].x -= 30; projectileOffset[2].y -= 40; } if ( monsterID == Monster::ORC || monsterID == Monster::ORC_CHIEF ) { for ( fheroes2::Point & offset : projectileOffset ) { offset.x -= 20; offset.y = -36; } } if ( monsterID == Monster::TROLL || monsterID == Monster::WAR_TROLL ) { projectileOffset[0].x -= 30; projectileOffset[0].y -= 5; projectileOffset[1].x -= 14; --projectileOffset[1].y; projectileOffset[2].x -= 15; projectileOffset[2].y -= 19; } if ( monsterID == Monster::ELF || monsterID == Monster::GRAND_ELF ) { projectileOffset[0].x -= 19; projectileOffset[0].y += 22; projectileOffset[1].x -= 40; --projectileOffset[1].y; projectileOffset[2].x -= 19; // IMPORTANT: In BIN file Elves and Grand Elves have incorrect start Y position for lower shooting attack ( -1 ). projectileOffset[2].y = -54; } if ( monsterID == Monster::DRUID || monsterID == Monster::GREATER_DRUID ) { projectileOffset[0].x -= 12; projectileOffset[0].y += 23; projectileOffset[1].x -= 20; projectileOffset[2].x -= 7; projectileOffset[2].y -= 21; } if ( monsterID == Monster::CENTAUR ) { projectileOffset[0].x -= 24; projectileOffset[0].y += 31; projectileOffset[1].x -= 32; projectileOffset[1].y += 2; projectileOffset[2].x -= 24; projectileOffset[2].y -= 27; } if ( monsterID == Monster::HALFLING ) { projectileOffset[0].x -= 7; projectileOffset[0].y += 11; projectileOffset[1].x -= 21; projectileOffset[1].y += 10; projectileOffset[2].x -= 9; projectileOffset[2].y -= 14; } if ( monsterID == Monster::MAGE || monsterID == Monster::ARCHMAGE ) { projectileOffset[0].y -= 2; projectileOffset[2].y += 2; } if ( monsterID == Monster::TITAN ) { projectileOffset[0].x -= 10; projectileOffset[0].y += 22; projectileOffset[1].x -= 20; projectileOffset[1].y += 8; projectileOffset[2].x -= 15; projectileOffset[2].y -= 22; } if ( monsterID == Monster::LICH || monsterID == Monster::POWER_LICH ) { projectileOffset[0].x -= 5; projectileOffset[0].y += 6; projectileOffset[1].x -= 16; projectileOffset[1].y -= 9; projectileOffset[2].x -= 9; projectileOffset[2].y -= 7; } uint8_t projectileCount = data[186]; if ( projectileCount > 12u ) projectileCount = 12u; // here we need to reset our object for ( uint8_t i = 0; i < projectileCount; ++i ) projectileAngles.push_back( getValue( data, 187, i ) ); // Positional offsets for sprites & drawing troopCountOffsetLeft = getValue( data, 235 ); troopCountOffsetRight = getValue( data, 239 ); // Load animation sequences themselves for ( int idx = MOVE_START; idx <= SHOOT3_END; ++idx ) { std::vector anim; uint8_t count = data[243 + idx]; if ( count > 16 ) count = 16; // here we need to reset our object for ( uint8_t frame = 0; frame < count; ++frame ) { anim.push_back( static_cast( data[277 + idx * 16 + frame] ) ); } animationFrames.push_back( anim ); } if ( monsterID == Monster::WOLF ) { // Wolves have incorrect frame for lower attack animation if ( animationFrames[ATTACK3].size() == 3 && animationFrames[ATTACK3][0] == 16 ) { animationFrames[ATTACK3][0] = 2; } if ( animationFrames[ATTACK3_END].size() == 3 && animationFrames[ATTACK3_END][2] == 16 ) { animationFrames[ATTACK3_END][2] = 2; } } if ( monsterID == Monster::DWARF || monsterID == Monster::BATTLE_DWARF ) { // Dwarves and Battle Dwarves have incorrect death animation. if ( animationFrames[DEATH].size() == 8 ) { animationFrames[DEATH].clear(); for ( int frameId = 49; frameId <= 55; ++frameId ) { animationFrames[DEATH].push_back( frameId ); } } } // Modify AnimInfo for upgraded monsters without own FRM file int speedDiff = 0; switch ( monsterID ) { case Monster::RANGER: case Monster::VETERAN_PIKEMAN: case Monster::MASTER_SWORDSMAN: case Monster::CHAMPION: case Monster::CRUSADER: case Monster::ORC_CHIEF: case Monster::OGRE_LORD: case Monster::WAR_TROLL: case Monster::BATTLE_DWARF: case Monster::GRAND_ELF: case Monster::GREATER_DRUID: case Monster::MINOTAUR_KING: case Monster::STEEL_GOLEM: case Monster::ARCHMAGE: case Monster::MUTANT_ZOMBIE: case Monster::ROYAL_MUMMY: case Monster::VAMPIRE_LORD: case Monster::POWER_LICH: speedDiff = static_cast( Monster( monsterID ).GetSpeed() ) - Monster( monsterID - 1 ).GetSpeed(); break; case Monster::EARTH_ELEMENT: case Monster::AIR_ELEMENT: case Monster::WATER_ELEMENT: speedDiff = static_cast( Monster( monsterID ).GetSpeed() ) - Monster( Monster::FIRE_ELEMENT ).GetSpeed(); break; default: break; } if ( std::abs( speedDiff ) > 0 ) { moveSpeed = static_cast( ( 1 - MOVE_SPEED_UPGRADE * speedDiff ) * moveSpeed ); // Ranger is special since he gets double attack on upgrade if ( monsterID == Monster::RANGER ) { shootSpeed = static_cast( shootSpeed * RANGER_SHOOT_SPEED ); } else { shootSpeed = static_cast( ( 1 - SHOOT_SPEED_UPGRADE * speedDiff ) * shootSpeed ); } } if ( frameXOffset[MOVE_STOP][0] == 0 && frameXOffset[MOVE_TILE_END][0] != 0 ) frameXOffset[MOVE_STOP][0] = frameXOffset[MOVE_TILE_END][0]; for ( int idx = MOVE_START; idx <= MOVE_ONE; ++idx ) frameXOffset[idx].resize( animationFrames[idx].size(), 0 ); if ( frameXOffset[MOVE_STOP].size() == 1 && frameXOffset[MOVE_STOP][0] == 0 ) { if ( frameXOffset[MOVE_TILE_END].size() == 1 && frameXOffset[MOVE_TILE_END][0] != 0 ) frameXOffset[MOVE_STOP][0] = frameXOffset[MOVE_TILE_END][0]; else if ( frameXOffset[MOVE_TILE_START].size() == 1 && frameXOffset[MOVE_TILE_START][0] != 0 ) frameXOffset[MOVE_STOP][0] = 44 + frameXOffset[MOVE_TILE_START][0]; else frameXOffset[MOVE_STOP][0] = frameXOffset[MOVE_MAIN].back(); } // Movement animation fix for Iron and Steel Golem. Also check that the data sizes are correct. if ( ( monsterID == Monster::IRON_GOLEM || monsterID == Monster::STEEL_GOLEM ) && ( frameXOffset[MOVE_START].size() == 4 && frameXOffset[MOVE_MAIN].size() == 7 && animationFrames[MOVE_MAIN].size() == 7 ) ) { // the original golem info frameXOffset[MOVE_START][0] = 0; frameXOffset[MOVE_START][1] = CELLW * 1 / 8; frameXOffset[MOVE_START][2] = CELLW * 2 / 8 + 3; frameXOffset[MOVE_START][3] = CELLW * 3 / 8; // 'MOVE_MAIN' animation is missing 1/4 of animation start. 'MOVE_START' (for first and one cell move) has this 1/4 of animation, // but 'MOVE_TILE_START` (for movement after the first cell) is empty, so we move all frames except the last frame from 'MOVE_MAIN' // to the end of 'MOVE_START' animationFrames. And prepare 'MOVE_TILE_START' for new animation frame IDs. animationFrames[MOVE_TILE_START].resize( animationFrames[MOVE_TILE_START].size() + animationFrames[MOVE_MAIN].size() - 1 ); animationFrames[MOVE_START].insert( animationFrames[MOVE_START].end(), animationFrames[MOVE_MAIN].begin(), animationFrames[MOVE_MAIN].end() - 1 ); animationFrames[MOVE_MAIN].erase( animationFrames[MOVE_MAIN].begin(), animationFrames[MOVE_MAIN].end() - 1 ); // Do the same for 'x' offset vector and make a copy to 'MOVE_TILE_START'. frameXOffset[MOVE_START].insert( frameXOffset[MOVE_START].end(), frameXOffset[MOVE_MAIN].begin(), frameXOffset[MOVE_MAIN].end() - 1 ); frameXOffset[MOVE_TILE_START] = frameXOffset[MOVE_MAIN]; frameXOffset[MOVE_TILE_START].pop_back(); frameXOffset[MOVE_MAIN].erase( frameXOffset[MOVE_MAIN].begin(), frameXOffset[MOVE_MAIN].end() - 1 ); // Correct the 'x' offset by half of cell. frameXOffset[MOVE_MAIN][0] += CELLW / 2; for ( size_t id = 0; id < frameXOffset[MOVE_TILE_START].size(); ++id ) { frameXOffset[MOVE_START][id + 4] += CELLW / 2; // For 'MOVE_TILE_START' also include the correction, made in "agg_image.cpp". frameXOffset[MOVE_TILE_START][id] += CELLW / 2 - ( 6 - static_cast( id ) ) * CELLW / 28; // For 'MOVE_TILE_START' animation frames IDs use new frames, made in "agg_image.cpp", which starts from ID = 40. animationFrames[MOVE_TILE_START][id] = 40 + static_cast( id ); } } // The Ogre has a duplicate frame of the 'STATIC' animation at the start of the 'MOVE_MAIN' animation, making him to move non-smoothly. if ( ( monsterID == Monster::OGRE || monsterID == Monster::OGRE_LORD ) && frameXOffset[MOVE_MAIN].size() == 9 && animationFrames[MOVE_MAIN].size() == 9 ) { animationFrames[MOVE_MAIN].erase( animationFrames[MOVE_MAIN].begin() ); frameXOffset[MOVE_MAIN].erase( frameXOffset[MOVE_MAIN].begin() ); } // Some creatures needs their 'x' offset in moving animations to be bigger by 3px to avoid sprite shift in Well and during diagonal movement. if ( monsterID == Monster::ORC || monsterID == Monster::ORC_CHIEF || monsterID == Monster::OGRE || monsterID == Monster::OGRE_LORD || monsterID == Monster::DWARF || monsterID == Monster::BATTLE_DWARF || monsterID == Monster::UNICORN || monsterID == Monster::CENTAUR || monsterID == Monster::BOAR || monsterID == Monster::LICH || monsterID == Monster::POWER_LICH || monsterID == Monster::ROGUE ) { for ( const int animType : { MOVE_START, MOVE_TILE_START, MOVE_MAIN, MOVE_TILE_END, MOVE_STOP, MOVE_ONE } ) { for ( int & xOffset : frameXOffset[animType] ) { xOffset += 3; } } } // Archers/Rangers needs their 'x' offset in moving animations to be bigger by 1px to avoid sprite shift in Well and during diagonal movement. if ( monsterID == Monster::ARCHER || monsterID == Monster::RANGER ) { for ( const int animType : { MOVE_MAIN, MOVE_ONE } ) { for ( int & xOffset : frameXOffset[animType] ) { ++xOffset; } } } // Paladin/Crusader needs their 'x' offset in moving animations to be lower by 1px to avoid sprite shift in Well and during diagonal movement. if ( ( monsterID == Monster::PALADIN || monsterID == Monster::CRUSADER ) && frameXOffset[MOVE_MAIN].size() == 8 && animationFrames[MOVE_MAIN].size() == 8 ) { for ( const int animType : { MOVE_MAIN, MOVE_ONE } ) { for ( int & xOffset : frameXOffset[animType] ) { --xOffset; } // The 5th and 8th frames needs extra shift by 2 and 1 pixel. frameXOffset[animType][4] -= 2; --frameXOffset[animType][7]; } } // Goblins needs their 'x' offset in moving animations to be bigger by 6px to avoid sprite shift in Well and during diagonal movement. if ( monsterID == Monster::GOBLIN ) { for ( const int animType : { MOVE_TILE_START, MOVE_MAIN, MOVE_STOP, MOVE_ONE } ) { for ( int & xOffset : frameXOffset[animType] ) { xOffset += 6; } } } // Trolls needs their 'x' offset in moving animations to be bigger by 2px to avoid sprite shift in Well and during diagonal movement. if ( ( monsterID == Monster::TROLL || monsterID == Monster::WAR_TROLL ) && frameXOffset[MOVE_MAIN].size() == 14 && frameXOffset[MOVE_ONE].size() == 14 ) { for ( const int animType : { MOVE_MAIN, MOVE_ONE } ) { for ( int & xOffset : frameXOffset[animType] ) { xOffset += 2; } // The 7th and 14th frames needs extra shift by 1 px. ++frameXOffset[animType][6]; ++frameXOffset[animType][13]; } } // Giants/Titians needs their 'x' offset in moving animations to be corrected to avoid sprite shift in Well and during diagonal movement. if ( ( monsterID == Monster::GIANT || monsterID == Monster::TITAN ) && frameXOffset[MOVE_MAIN].size() == 7 && frameXOffset[MOVE_ONE].size() == 7 ) { for ( const int animType : { MOVE_MAIN, MOVE_ONE } ) { frameXOffset[animType][0] += 3; frameXOffset[animType][1] += 2; frameXOffset[animType][2] += 2; ++frameXOffset[animType][5]; ++frameXOffset[animType][6]; } } // Nomad needs their 'x' offset in moving animations to be corrected to avoid one sprite shift in Well and during diagonal movement. if ( monsterID == Monster::NOMAD && frameXOffset[MOVE_MAIN].size() == 8 && frameXOffset[MOVE_ONE].size() == 8 ) { for ( const int animType : { MOVE_MAIN, MOVE_ONE } ) { frameXOffset[animType][2] -= 2; } } // Nomad needs their 'x' offset in moving animations to be corrected to avoid one sprite shift in Well and during diagonal movement. if ( monsterID == Monster::MEDUSA && frameXOffset[MOVE_MAIN].size() == 15 && frameXOffset[MOVE_ONE].size() == 15 ) { for ( const int animType : { MOVE_MAIN, MOVE_ONE } ) { frameXOffset[animType][3] += 2; frameXOffset[animType][4] += 2; ++frameXOffset[animType][5]; ++frameXOffset[animType][9]; frameXOffset[animType][10] += 2; ++frameXOffset[animType][11]; ++frameXOffset[animType][14]; } } // Elementals needs their 'x' offset in moving animations to be corrected to avoid sprite shift in Well and during diagonal movement. if ( ( monsterID == Monster::EARTH_ELEMENT || monsterID == Monster::AIR_ELEMENT || monsterID == Monster::FIRE_ELEMENT || monsterID == Monster::WATER_ELEMENT ) && frameXOffset[MOVE_MAIN].size() == 8 && frameXOffset[MOVE_ONE].size() == 8 ) { for ( const int animType : { MOVE_MAIN, MOVE_ONE } ) { frameXOffset[animType][0] += 3; frameXOffset[animType][1] += 3; frameXOffset[animType][2] += 5; frameXOffset[animType][3] += 3; --frameXOffset[animType][5]; ++frameXOffset[animType][6]; --frameXOffset[animType][7]; } } // X offset fix for Swordsman. if ( ( monsterID == Monster::SWORDSMAN || monsterID == Monster::MASTER_SWORDSMAN ) && frameXOffset[MOVE_START].size() == 2 && frameXOffset[MOVE_STOP].size() == 1 ) { frameXOffset[MOVE_START][0] = 0; frameXOffset[MOVE_START][1] = CELLW * 1 / 8; for ( int & xOffset : frameXOffset[MOVE_MAIN] ) { xOffset += CELLW / 4 + 3; } frameXOffset[MOVE_STOP][0] = CELLW; } } MonsterAnimInfo MonsterAnimCache::getAnimInfo( int monsterID ) { std::map::iterator mapIterator = _animMap.find( monsterID ); if ( mapIterator != _animMap.end() ) { return mapIterator->second; } else { const MonsterAnimInfo info( monsterID, AGG::getDataFromAggFile( Bin_Info::GetFilename( monsterID ) ) ); if ( info.isValid() ) { _animMap[monsterID] = info; return info; } else { DEBUG_LOG( DBG_GAME, DBG_WARN, "missing BIN FRM data: " << Bin_Info::GetFilename( monsterID ) << ", index: " << monsterID ) } } return MonsterAnimInfo(); } bool MonsterAnimInfo::isValid() const { if ( animationFrames.size() != SHOOT3_END + 1 ) return false; // Absolute minimal set up const int essentialAnimations[7] = { MOVE_MAIN, STATIC, DEATH, WINCE_UP, ATTACK1, ATTACK2, ATTACK3 }; for ( int i = 0; i < 7; ++i ) { if ( animationFrames.at( essentialAnimations[i] ).empty() ) return false; } if ( idlePriority.size() != static_cast( idleAnimationCount ) ) return false; return true; } bool MonsterAnimInfo::hasAnim( int animID ) const { return animationFrames.size() == SHOOT3_END + 1 && !animationFrames.at( animID ).empty(); } size_t MonsterAnimInfo::getProjectileID( const double angle ) const { const std::vector & angles = projectileAngles; if ( angles.empty() ) return 0; for ( size_t id = 0u; id < angles.size() - 1; ++id ) { if ( angle >= static_cast( angles[id] + angles[id + 1] ) / 2.0 ) return id; } return angles.size() - 1; } AnimationReference MonsterAnimCache::createAnimReference( int monsterID ) const { return AnimationReference( monsterID ); } MonsterAnimInfo GetMonsterInfo( uint32_t monsterID ) { return _infoCache.getAnimInfo( monsterID ); } void InitBinInfo() { for ( int i = Monster::UNKNOWN; i < Monster::WATER_ELEMENT + 1; ++i ) animRefs[i] = _infoCache.createAnimReference( i ); } } fheroes2-1.0.12+dfsg/src/fheroes2/agg/bin_info.h000066400000000000000000000077751456075706000212570ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2BIN_FRM_H #define H2BIN_FRM_H #include #include #include #include #include "math_base.h" namespace Bin_Info { struct MonsterAnimInfo { enum ANIM_TYPE { MOVE_START, // Start of the moving sequence on 1st animation cycle: flyers will fly up MOVE_TILE_START, // Supposed to be played at the beginning of 2nd+ move. MOVE_MAIN, // Core animation. Most units only have this one. MOVE_TILE_END, // Cavalry & wolf. Played at the end of the cycle (2nd tile to 3rd), but not at the last one MOVE_STOP, // End of the moving sequence when arrived: landing for example MOVE_ONE, // Used when moving 1 tile. LICH and POWER_LICH doesn't have this, use MOVE_MAIN TEMPORARY, // This is an empty placeholder for combined animation built from previous parts STATIC, // Frame 1 IDLE1, IDLE2, // Idle animations: picked at random with different probabilities, rarely all 5 present IDLE3, IDLE4, IDLE5, DEATH, WINCE_UP, WINCE_END, ATTACK1, // Attacks, number represents the angle: 1 is TOP, 2 is CENTER, 3 is BOTTOM ATTACK1_END, DOUBLEHEX1, DOUBLEHEX1_END, ATTACK2, ATTACK2_END, DOUBLEHEX2, DOUBLEHEX2_END, ATTACK3, ATTACK3_END, DOUBLEHEX3, DOUBLEHEX3_END, SHOOT1, SHOOT1_END, SHOOT2, SHOOT2_END, SHOOT3, SHOOT3_END }; std::vector> frameXOffset; uint32_t moveSpeed; uint32_t shootSpeed; uint32_t flightSpeed; fheroes2::Point eyePosition; int32_t troopCountOffsetLeft; int32_t troopCountOffsetRight; std::vector projectileOffset; std::vector projectileAngles; std::vector idlePriority; std::vector unusedIdleDelays; uint32_t idleAnimationCount; uint32_t idleAnimationDelay; std::vector> animationFrames; MonsterAnimInfo( int monsterID = 0, const std::vector & bytes = std::vector() ); bool hasAnim( int animID = MonsterAnimInfo::STATIC ) const; bool isValid() const; size_t getProjectileID( const double angle ) const; }; void InitBinInfo(); MonsterAnimInfo GetMonsterInfo( uint32_t monsterID ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/agg/icn.cpp000066400000000000000000001342271456075706000205710ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "icn.h" #include #include "color.h" #include "heroes.h" #include "race.h" #include "settings.h" namespace ICN { struct icnmap_t { int type; const char * string; }; // Make sure that each item ID has exactly the same location ID! const icnmap_t icnmap[LAST_VALID_FILE_ICN] = { { UNKNOWN, "UNKNOWN" }, { ADVBORDE, "ADVBORDE.ICN" }, { ADVBORD, "ADVBORD.ICN" }, { ADVBTNS, "ADVBTNS.ICN" }, { ADVEBTNS, "ADVEBTNS.ICN" }, { ADVMCO, "ADVMCO.ICN" }, { AELEM, "AELEM.ICN" }, { APANBKGE, "APANBKGE.ICN" }, { APANBKG, "APANBKG.ICN" }, { APANELE, "APANELE.ICN" }, { APANEL, "APANEL.ICN" }, { ARCHER2, "ARCHER2.ICN" }, { ARCHER, "ARCHER.ICN" }, { ARCH_MSL, "ARCH_MSL.ICN" }, { ART32, "ART32.ICN" }, { ARTFX, "ARTFX.ICN" }, { ARTIFACT, "ARTIFACT.ICN" }, { BARB32, "BARB32.ICN" }, { B_BFLG32, "B-BFLG32.ICN" }, { BERZERK, "BERZERK.ICN" }, { B_FLAG32, "B-FLAG32.ICN" }, { BIGBAR, "BIGBAR.ICN" }, { BLDGXTRA, "BLDGXTRA.ICN" }, { BLESS, "BLESS.ICN" }, { BLIND, "BLIND.ICN" }, { BLUEFIRE, "BLUEFIRE.ICN" }, { BOAR, "BOAR.ICN" }, { BOAT32, "BOAT32.ICN" }, { BOATSHAD, "BOATSHAD.ICN" }, { BOATWIND, "BOATWIND.ICN" }, { BOOK, "BOOK.ICN" }, { BORDEDIT, "BORDEDIT.ICN" }, { BOULDER, "BOULDER.ICN" }, { BRCREST, "BRCREST.ICN" }, { BROTHERS, "BROTHERS.ICN" }, { BTNBAUD, "BTNBAUD.ICN" }, { BTNCMPGN, "BTNCMPGN.ICN" }, { BTNCOM, "BTNCOM.ICN" }, { BTNDCCFG, "BTNDCCFG.ICN" }, { BTNDC, "BTNDC.ICN" }, { BTNEMAIN, "BTNEMAIN.ICN" }, { BTNENEW, "BTNENEW.ICN" }, { BTNESIZE, "BTNESIZE.ICN" }, { BTNHOTST, "BTNHOTST.ICN" }, { BTNMCFG, "BTNMCFG.ICN" }, { BTNMODEM, "BTNMODEM.ICN" }, { BTNMP, "BTNMP.ICN" }, { BTNNET2, "BTNNET2.ICN" }, { BTNNET, "BTNNET.ICN" }, { BTNNEWGM, "BTNNEWGM.ICN" }, { BTNSHNGL, "BTNSHNGL.ICN" }, { BUILDING, "BUILDING.ICN" }, { BUYBUILD, "BUYBUILD.ICN" }, { BUYBUILE, "BUYBUILE.ICN" }, { CAMPBKGE, "CAMPBKGE.ICN" }, { CAMPBKGG, "CAMPBKGG.ICN" }, { CAMPXTRE, "CAMPXTRE.ICN" }, { CAMPXTRG, "CAMPXTRG.ICN" }, { CAPTCOVR, "CAPTCOVR.ICN" }, { CASLBAR, "CASLBAR.ICN" }, { CASLWIND, "CASLWIND.ICN" }, { CASLXTRA, "CASLXTRA.ICN" }, { CASTBKGB, "CASTBKGB.ICN" }, { CASTBKGK, "CASTBKGK.ICN" }, { CASTBKGN, "CASTBKGN.ICN" }, { CASTBKGS, "CASTBKGS.ICN" }, { CASTBKGW, "CASTBKGW.ICN" }, { CASTBKGZ, "CASTBKGZ.ICN" }, { CASTLEB, "CASTLEB.ICN" }, { CASTLEK, "CASTLEK.ICN" }, { CASTLEN, "CASTLEN.ICN" }, { CASTLES, "CASTLES.ICN" }, { CASTLEW, "CASTLEW.ICN" }, { CASTLEZ, "CASTLEZ.ICN" }, { CATAPULT, "CATAPULT.ICN" }, { CAVALRYB, "CAVALRYB.ICN" }, { CAVALRYR, "CAVALRYR.ICN" }, { CBKGBEAC, "CBKGBEAC.ICN" }, { CBKGCRCK, "CBKGCRCK.ICN" }, { CBKGDIMT, "CBKGDIMT.ICN" }, { CBKGDITR, "CBKGDITR.ICN" }, { CBKGDSRT, "CBKGDSRT.ICN" }, { CBKGGRAV, "CBKGGRAV.ICN" }, { CBKGGRMT, "CBKGGRMT.ICN" }, { CBKGGRTR, "CBKGGRTR.ICN" }, { CBKGLAVA, "CBKGLAVA.ICN" }, { CBKGSNMT, "CBKGSNMT.ICN" }, { CBKGSNTR, "CBKGSNTR.ICN" }, { CBKGSWMP, "CBKGSWMP.ICN" }, { CBKGWATR, "CBKGWATR.ICN" }, { CELLWIN, "CELLWIN.ICN" }, { CENTAUR, "CENTAUR.ICN" }, { CFLGSMAL, "CFLGSMAL.ICN" }, { CLOP32, "CLOP32.ICN" }, { CLOUDLUK, "CLOUDLUK.ICN" }, { CMBTCAPB, "CMBTCAPB.ICN" }, { CMBTCAPK, "CMBTCAPK.ICN" }, { CMBTCAPN, "CMBTCAPN.ICN" }, { CMBTCAPS, "CMBTCAPS.ICN" }, { CMBTCAPW, "CMBTCAPW.ICN" }, { CMBTCAPZ, "CMBTCAPZ.ICN" }, { CMBTFLE1, "CMBTFLE1.ICN" }, { CMBTFLE2, "CMBTFLE2.ICN" }, { CMBTFLE3, "CMBTFLE3.ICN" }, { CMBTHROB, "CMBTHROB.ICN" }, { CMBTHROK, "CMBTHROK.ICN" }, { CMBTHRON, "CMBTHRON.ICN" }, { CMBTHROS, "CMBTHROS.ICN" }, { CMBTHROW, "CMBTHROW.ICN" }, { CMBTHROZ, "CMBTHROZ.ICN" }, { CMBTLOS1, "CMBTLOS1.ICN" }, { CMBTLOS2, "CMBTLOS2.ICN" }, { CMBTLOS3, "CMBTLOS3.ICN" }, { CMBTMISC, "CMBTMISC.ICN" }, { CMBTSURR, "CMBTSURR.ICN" }, { CMSECO, "CMSECO.ICN" }, { COBJ0000, "COBJ0000.ICN" }, { COBJ0001, "COBJ0001.ICN" }, { COBJ0002, "COBJ0002.ICN" }, { COBJ0003, "COBJ0003.ICN" }, { COBJ0004, "COBJ0004.ICN" }, { COBJ0005, "COBJ0005.ICN" }, { COBJ0006, "COBJ0006.ICN" }, { COBJ0007, "COBJ0007.ICN" }, { COBJ0008, "COBJ0008.ICN" }, { COBJ0009, "COBJ0009.ICN" }, { COBJ0010, "COBJ0010.ICN" }, { COBJ0011, "COBJ0011.ICN" }, { COBJ0012, "COBJ0012.ICN" }, { COBJ0013, "COBJ0013.ICN" }, { COBJ0014, "COBJ0014.ICN" }, { COBJ0015, "COBJ0015.ICN" }, { COBJ0016, "COBJ0016.ICN" }, { COBJ0017, "COBJ0017.ICN" }, { COBJ0018, "COBJ0018.ICN" }, { COBJ0019, "COBJ0019.ICN" }, { COBJ0020, "COBJ0020.ICN" }, { COBJ0021, "COBJ0021.ICN" }, { COBJ0022, "COBJ0022.ICN" }, { COBJ0023, "COBJ0023.ICN" }, { COBJ0024, "COBJ0024.ICN" }, { COBJ0025, "COBJ0025.ICN" }, { COBJ0026, "COBJ0026.ICN" }, { COBJ0027, "COBJ0027.ICN" }, { COBJ0028, "COBJ0028.ICN" }, { COBJ0029, "COBJ0029.ICN" }, { COBJ0030, "COBJ0030.ICN" }, { COBJ0031, "COBJ0031.ICN" }, { COLDRAY, "COLDRAY.ICN" }, { COLDRING, "COLDRING.ICN" }, { CONGRATS, "CONGRATS.ICN" }, { COVR0001, "COVR0001.ICN" }, { COVR0002, "COVR0002.ICN" }, { COVR0003, "COVR0003.ICN" }, { COVR0004, "COVR0004.ICN" }, { COVR0005, "COVR0005.ICN" }, { COVR0006, "COVR0006.ICN" }, { COVR0007, "COVR0007.ICN" }, { COVR0008, "COVR0008.ICN" }, { COVR0009, "COVR0009.ICN" }, { COVR0010, "COVR0010.ICN" }, { COVR0011, "COVR0011.ICN" }, { COVR0012, "COVR0012.ICN" }, { COVR0013, "COVR0013.ICN" }, { COVR0014, "COVR0014.ICN" }, { COVR0015, "COVR0015.ICN" }, { COVR0016, "COVR0016.ICN" }, { COVR0017, "COVR0017.ICN" }, { COVR0018, "COVR0018.ICN" }, { COVR0019, "COVR0019.ICN" }, { COVR0020, "COVR0020.ICN" }, { COVR0021, "COVR0021.ICN" }, { COVR0022, "COVR0022.ICN" }, { COVR0023, "COVR0023.ICN" }, { COVR0024, "COVR0024.ICN" }, { CPANBKGE, "CPANBKGE.ICN" }, { CPANBKG, "CPANBKG.ICN" }, { CPANELE, "CPANELE.ICN" }, { CPANEL, "CPANEL.ICN" }, { CREST, "CREST.ICN" }, { CSPANBKE, "CSPANBKE.ICN" }, { CSPANBKG, "CSPANBKG.ICN" }, { CSPANBTE, "CSPANBTE.ICN" }, { CSPANBTN, "CSPANBTN.ICN" }, { CSPANEL, "CSPANEL.ICN" }, { CSTLBARB, "CSTLBARB.ICN" }, { CSTLCAPB, "CSTLCAPB.ICN" }, { CSTLCAPK, "CSTLCAPK.ICN" }, { CSTLCAPN, "CSTLCAPN.ICN" }, { CSTLCAPS, "CSTLCAPS.ICN" }, { CSTLCAPW, "CSTLCAPW.ICN" }, { CSTLCAPZ, "CSTLCAPZ.ICN" }, { CSTLKNGT, "CSTLKNGT.ICN" }, { CSTLNECR, "CSTLNECR.ICN" }, { CSTLSORC, "CSTLSORC.ICN" }, { CSTLWRLK, "CSTLWRLK.ICN" }, { CSTLWZRD, "CSTLWZRD.ICN" }, { CTRACK00, "CTRACK00.ICN" }, { CTRACK01, "CTRACK01.ICN" }, { CTRACK02, "CTRACK02.ICN" }, { CTRACK03, "CTRACK03.ICN" }, { CTRACK04, "CTRACK04.ICN" }, { CTRACK05, "CTRACK05.ICN" }, { CTRACK06, "CTRACK06.ICN" }, { CURSE, "CURSE.ICN" }, { CYCLOPS, "CYCLOPS.ICN" }, { DISRRAY, "DISRRAY.ICN" }, { DRAGBLAK, "DRAGBLAK.ICN" }, { DRAGBONE, "DRAGBONE.ICN" }, { DRAGGREE, "DRAGGREE.ICN" }, { DRAGRED, "DRAGRED.ICN" }, { DRAGSLAY, "DRAGSLAY.ICN" }, { DROPLISL, "DROPLISL.ICN" }, { DROPLIST, "DROPLIST.ICN" }, { DRUID2, "DRUID2.ICN" }, { DRUID, "DRUID.ICN" }, { DRUIDMSL, "DRUIDMSL.ICN" }, { DUMMY, "DUMMY.ICN" }, { DWARF2, "DWARF2.ICN" }, { DWARF, "DWARF.ICN" }, { ECPANEL, "ECPANEL.ICN" }, { EDITBTNS, "EDITBTNS.ICN" }, { EDITOR, "EDITOR.ICN" }, { EDITPANL, "EDITPANL.ICN" }, { EELEM, "EELEM.ICN" }, { ELECTRIC, "ELECTRIC.ICN" }, { ELF2, "ELF2.ICN" }, { ELF, "ELF.ICN" }, { ELF__MSL, "ELF__MSL.ICN" }, { ESCROLL, "ESCROLL.ICN" }, { ESPANBKG, "ESPANBKG.ICN" }, { ESPANBTN, "ESPANBTN.ICN" }, { ESPANEL, "ESPANEL.ICN" }, { EVIW_ALL, "EVIW_ALL.ICN" }, { EVIWDDOR, "EVIWDDOR.ICN" }, { EVIWHROS, "EVIWHROS.ICN" }, { EVIWMINE, "EVIWMINE.ICN" }, { EVIWPUZL, "EVIWPUZL.ICN" }, { EVIWRSRC, "EVIWRSRC.ICN" }, { EVIWRTFX, "EVIWRTFX.ICN" }, { EVIWTWNS, "EVIWTWNS.ICN" }, { EVIWWRLD, "EVIWWRLD.ICN" }, { EXPMRL, "EXPMRL.ICN" }, { EXTRAOVR, "EXTRAOVR.ICN" }, { FELEM, "FELEM.ICN" }, { FIREBAL2, "FIREBAL2.ICN" }, { FIREBALL, "FIREBALL.ICN" }, { FLAG32, "FLAG32.ICN" }, { FONT, "FONT.ICN" }, { FRNG0001, "FRNG0001.ICN" }, { FRNG0002, "FRNG0002.ICN" }, { FRNG0003, "FRNG0003.ICN" }, { FRNG0004, "FRNG0004.ICN" }, { FRNG0005, "FRNG0005.ICN" }, { FRNG0006, "FRNG0006.ICN" }, { FRNG0007, "FRNG0007.ICN" }, { FRNG0008, "FRNG0008.ICN" }, { FRNG0009, "FRNG0009.ICN" }, { FRNG0010, "FRNG0010.ICN" }, { FRNG0011, "FRNG0011.ICN" }, { FRNG0012, "FRNG0012.ICN" }, { FRNG0013, "FRNG0013.ICN" }, { FROTH, "FROTH.ICN" }, { GARGOYLE, "GARGOYLE.ICN" }, { G_BFLG32, "G-BFLG32.ICN" }, { GENIE, "GENIE.ICN" }, { G_FLAG32, "G-FLAG32.ICN" }, { GHOST, "GHOST.ICN" }, { GOBLIN, "GOBLIN.ICN" }, { GOLEM2, "GOLEM2.ICN" }, { GOLEM, "GOLEM.ICN" }, { GRIFFIN, "GRIFFIN.ICN" }, { GROUND12, "GROUND12.ICN" }, { GROUND4, "GROUND4.ICN" }, { GROUND6, "GROUND6.ICN" }, { HALFLING, "HALFLING.ICN" }, { HALFLMSL, "HALFLMSL.ICN" }, { HASTE, "HASTE.ICN" }, { HEROBKG, "HEROBKG.ICN" }, { HEROES, "HEROES.ICN" }, { HEROEXTE, "HEROEXTE.ICN" }, { HEROEXTG, "HEROEXTG.ICN" }, { HEROFL00, "HEROFL00.ICN" }, { HEROFL01, "HEROFL01.ICN" }, { HEROFL02, "HEROFL02.ICN" }, { HEROFL03, "HEROFL03.ICN" }, { HEROFL04, "HEROFL04.ICN" }, { HEROFL05, "HEROFL05.ICN" }, { HEROFL06, "HEROFL06.ICN" }, { HEROLOGE, "HEROLOGE.ICN" }, { HEROLOGO, "HEROLOGO.ICN" }, { HISCORE, "HISCORE.ICN" }, { HOURGLAS, "HOURGLAS.ICN" }, { HSBKG, "HSBKG.ICN" }, { HSBTNS, "HSBTNS.ICN" }, { HSICONS, "HSICONS.ICN" }, { HYDRA, "HYDRA.ICN" }, { HYPNOTIZ, "HYPNOTIZ.ICN" }, { ICECLOUD, "ICECLOUD.ICN" }, { KEEP, "KEEP.ICN" }, { KNGT32, "KNGT32.ICN" }, { LETTER12, "LETTER12.ICN" }, { LETTER4, "LETTER4.ICN" }, { LETTER6, "LETTER6.ICN" }, { LGNDXTRA, "LGNDXTRA.ICN" }, { LGNDXTRE, "LGNDXTRE.ICN" }, { LICH2, "LICH2.ICN" }, { LICHCLOD, "LICHCLOD.ICN" }, { LICH, "LICH.ICN" }, { LICH_MSL, "LICH_MSL.ICN" }, { LISTBOX, "LISTBOX.ICN" }, { LISTBOXS, "LISTBOXS.ICN" }, { LOCATORE, "LOCATORE.ICN" }, { LOCATORS, "LOCATORS.ICN" }, { MAGE1, "MAGE1.ICN" }, { MAGE2, "MAGE2.ICN" }, { MAGEGLDB, "MAGEGLDB.ICN" }, { MAGEGLDK, "MAGEGLDK.ICN" }, { MAGEGLDN, "MAGEGLDN.ICN" }, { MAGEGLDS, "MAGEGLDS.ICN" }, { MAGEGLDW, "MAGEGLDW.ICN" }, { MAGEGLDZ, "MAGEGLDZ.ICN" }, { MAGIC01, "MAGIC01.ICN" }, { MAGIC02, "MAGIC02.ICN" }, { MAGIC03, "MAGIC03.ICN" }, { MAGIC04, "MAGIC04.ICN" }, { MAGIC06, "MAGIC06.ICN" }, { MAGIC07, "MAGIC07.ICN" }, { MAGIC08, "MAGIC08.ICN" }, { MANA, "MANA.ICN" }, { MEDUSA, "MEDUSA.ICN" }, { METEOR, "METEOR.ICN" }, { MINICAPT, "MINICAPT.ICN" }, { MINIHERO, "MINIHERO.ICN" }, { MINILKMR, "MINILKMR.ICN" }, { MINIMON, "MINIMON.ICN" }, { MINIPORT, "MINIPORT.ICN" }, { MINISS, "MINISS.ICN" }, { MINITOWN, "MINITOWN.ICN" }, { MINOTAU2, "MINOTAU2.ICN" }, { MINOTAUR, "MINOTAUR.ICN" }, { MISC12, "MISC12.ICN" }, { MISC4, "MISC4.ICN" }, { MISC6, "MISC6.ICN" }, { MOATPART, "MOATPART.ICN" }, { MOATWHOL, "MOATWHOL.ICN" }, { MOBILITY, "MOBILITY.ICN" }, { MONH0000, "MONH0000.ICN" }, { MONH0001, "MONH0001.ICN" }, { MONH0002, "MONH0002.ICN" }, { MONH0003, "MONH0003.ICN" }, { MONH0004, "MONH0004.ICN" }, { MONH0005, "MONH0005.ICN" }, { MONH0006, "MONH0006.ICN" }, { MONH0007, "MONH0007.ICN" }, { MONH0008, "MONH0008.ICN" }, { MONH0009, "MONH0009.ICN" }, { MONH0010, "MONH0010.ICN" }, { MONH0011, "MONH0011.ICN" }, { MONH0012, "MONH0012.ICN" }, { MONH0013, "MONH0013.ICN" }, { MONH0014, "MONH0014.ICN" }, { MONH0015, "MONH0015.ICN" }, { MONH0016, "MONH0016.ICN" }, { MONH0017, "MONH0017.ICN" }, { MONH0018, "MONH0018.ICN" }, { MONH0019, "MONH0019.ICN" }, { MONH0020, "MONH0020.ICN" }, { MONH0021, "MONH0021.ICN" }, { MONH0022, "MONH0022.ICN" }, { MONH0023, "MONH0023.ICN" }, { MONH0024, "MONH0024.ICN" }, { MONH0025, "MONH0025.ICN" }, { MONH0026, "MONH0026.ICN" }, { MONH0027, "MONH0027.ICN" }, { MONH0028, "MONH0028.ICN" }, { MONH0029, "MONH0029.ICN" }, { MONH0030, "MONH0030.ICN" }, { MONH0031, "MONH0031.ICN" }, { MONH0032, "MONH0032.ICN" }, { MONH0033, "MONH0033.ICN" }, { MONH0034, "MONH0034.ICN" }, { MONH0035, "MONH0035.ICN" }, { MONH0036, "MONH0036.ICN" }, { MONH0037, "MONH0037.ICN" }, { MONH0038, "MONH0038.ICN" }, { MONH0039, "MONH0039.ICN" }, { MONH0040, "MONH0040.ICN" }, { MONH0041, "MONH0041.ICN" }, { MONH0042, "MONH0042.ICN" }, { MONH0043, "MONH0043.ICN" }, { MONH0044, "MONH0044.ICN" }, { MONH0045, "MONH0045.ICN" }, { MONH0046, "MONH0046.ICN" }, { MONH0047, "MONH0047.ICN" }, { MONH0048, "MONH0048.ICN" }, { MONH0049, "MONH0049.ICN" }, { MONH0050, "MONH0050.ICN" }, { MONH0051, "MONH0051.ICN" }, { MONH0052, "MONH0052.ICN" }, { MONH0053, "MONH0053.ICN" }, { MONH0054, "MONH0054.ICN" }, { MONH0055, "MONH0055.ICN" }, { MONH0056, "MONH0056.ICN" }, { MONH0057, "MONH0057.ICN" }, { MONH0058, "MONH0058.ICN" }, { MONH0059, "MONH0059.ICN" }, { MONH0060, "MONH0060.ICN" }, { MONH0061, "MONH0061.ICN" }, { MONH0062, "MONH0062.ICN" }, { MONH0063, "MONH0063.ICN" }, { MONH0064, "MONH0064.ICN" }, { MONH0065, "MONH0065.ICN" }, { MONS32, "MONS32.ICN" }, { MORALEB, "MORALEB.ICN" }, { MORALEG, "MORALEG.ICN" }, { MTNCRCK, "MTNCRCK.ICN" }, { MTNDIRT, "MTNDIRT.ICN" }, { MTNDSRT, "MTNDSRT.ICN" }, { MTNGRAS, "MTNGRAS.ICN" }, { MTNLAVA, "MTNLAVA.ICN" }, { MTNMULT, "MTNMULT.ICN" }, { MTNSNOW, "MTNSNOW.ICN" }, { MTNSWMP, "MTNSWMP.ICN" }, { MUMMY2, "MUMMY2.ICN" }, { MUMMYW, "MUMMYW.ICN" }, { NECR32, "NECR32.ICN" }, { NETBOX, "NETBOX.ICN" }, { NGEXTRA, "NGEXTRA.ICN" }, { NGHSBKG, "NGHSBKG.ICN" }, { NGMPBKG, "NGMPBKG.ICN" }, { NGSPBKG, "NGSPBKG.ICN" }, { NOMAD, "NOMAD.ICN" }, { O_BFLG32, "O-BFLG32.ICN" }, { OBJNARTI, "OBJNARTI.ICN" }, { OBJNCRCK, "OBJNCRCK.ICN" }, { OBJNDIRT, "OBJNDIRT.ICN" }, { OBJNDSRT, "OBJNDSRT.ICN" }, { OBJNGRA2, "OBJNGRA2.ICN" }, { OBJNGRAS, "OBJNGRAS.ICN" }, { OBJNHAUN, "OBJNHAUN.ICN" }, { OBJNLAV2, "OBJNLAV2.ICN" }, { OBJNLAV3, "OBJNLAV3.ICN" }, { OBJNLAVA, "OBJNLAVA.ICN" }, { OBJNMUL2, "OBJNMUL2.ICN" }, { OBJNMULT, "OBJNMULT.ICN" }, { OBJNRSRC, "OBJNRSRC.ICN" }, { OBJNSNOW, "OBJNSNOW.ICN" }, { OBJNSWMP, "OBJNSWMP.ICN" }, { OBJNTOWN, "OBJNTOWN.ICN" }, { OBJNTWBA, "OBJNTWBA.ICN" }, { OBJNTWRD, "OBJNTWRD.ICN" }, { OBJNTWSH, "OBJNTWSH.ICN" }, { OBJNWAT2, "OBJNWAT2.ICN" }, { OBJNWATR, "OBJNWATR.ICN" }, { OBJNXTRA, "OBJNXTRA.ICN" }, { OBJPALET, "OBJPALET.ICN" }, { O_FLAG32, "O-FLAG32.ICN" }, { OGRE2, "OGRE2.ICN" }, { OGRE, "OGRE.ICN" }, { ORC2, "ORC2.ICN" }, { ORC, "ORC.ICN" }, { ORC__MSL, "ORC__MSL.ICN" }, { OVERBACK, "OVERBACK.ICN" }, { OVERLAY, "OVERLAY.ICN" }, { OVERVIEW, "OVERVIEW.ICN" }, { PALADIN2, "PALADIN2.ICN" }, { PALADIN, "PALADIN.ICN" }, { PARALYZE, "PARALYZE.ICN" }, { P_BFLG32, "P-BFLG32.ICN" }, { PEASANT, "PEASANT.ICN" }, { P_FLAG32, "P-FLAG32.ICN" }, { PHOENIX, "PHOENIX.ICN" }, { PHYSICAL, "PHYSICAL.ICN" }, { PIKEMAN2, "PIKEMAN2.ICN" }, { PIKEMAN, "PIKEMAN.ICN" }, { PORT0000, "PORT0000.ICN" }, { PORT0001, "PORT0001.ICN" }, { PORT0002, "PORT0002.ICN" }, { PORT0003, "PORT0003.ICN" }, { PORT0004, "PORT0004.ICN" }, { PORT0005, "PORT0005.ICN" }, { PORT0006, "PORT0006.ICN" }, { PORT0007, "PORT0007.ICN" }, { PORT0008, "PORT0008.ICN" }, { PORT0009, "PORT0009.ICN" }, { PORT0010, "PORT0010.ICN" }, { PORT0011, "PORT0011.ICN" }, { PORT0012, "PORT0012.ICN" }, { PORT0013, "PORT0013.ICN" }, { PORT0014, "PORT0014.ICN" }, { PORT0015, "PORT0015.ICN" }, { PORT0016, "PORT0016.ICN" }, { PORT0017, "PORT0017.ICN" }, { PORT0018, "PORT0018.ICN" }, { PORT0019, "PORT0019.ICN" }, { PORT0020, "PORT0020.ICN" }, { PORT0021, "PORT0021.ICN" }, { PORT0022, "PORT0022.ICN" }, { PORT0023, "PORT0023.ICN" }, { PORT0024, "PORT0024.ICN" }, { PORT0025, "PORT0025.ICN" }, { PORT0026, "PORT0026.ICN" }, { PORT0027, "PORT0027.ICN" }, { PORT0028, "PORT0028.ICN" }, { PORT0029, "PORT0029.ICN" }, { PORT0030, "PORT0030.ICN" }, { PORT0031, "PORT0031.ICN" }, { PORT0032, "PORT0032.ICN" }, { PORT0033, "PORT0033.ICN" }, { PORT0034, "PORT0034.ICN" }, { PORT0035, "PORT0035.ICN" }, { PORT0036, "PORT0036.ICN" }, { PORT0037, "PORT0037.ICN" }, { PORT0038, "PORT0038.ICN" }, { PORT0039, "PORT0039.ICN" }, { PORT0040, "PORT0040.ICN" }, { PORT0041, "PORT0041.ICN" }, { PORT0042, "PORT0042.ICN" }, { PORT0043, "PORT0043.ICN" }, { PORT0044, "PORT0044.ICN" }, { PORT0045, "PORT0045.ICN" }, { PORT0046, "PORT0046.ICN" }, { PORT0047, "PORT0047.ICN" }, { PORT0048, "PORT0048.ICN" }, { PORT0049, "PORT0049.ICN" }, { PORT0050, "PORT0050.ICN" }, { PORT0051, "PORT0051.ICN" }, { PORT0052, "PORT0052.ICN" }, { PORT0053, "PORT0053.ICN" }, { PORT0054, "PORT0054.ICN" }, { PORT0055, "PORT0055.ICN" }, { PORT0056, "PORT0056.ICN" }, { PORT0057, "PORT0057.ICN" }, { PORT0058, "PORT0058.ICN" }, { PORT0059, "PORT0059.ICN" }, { PORT0060, "PORT0060.ICN" }, { PORT0061, "PORT0061.ICN" }, { PORT0062, "PORT0062.ICN" }, { PORT0063, "PORT0063.ICN" }, { PORT0064, "PORT0064.ICN" }, { PORT0065, "PORT0065.ICN" }, { PORT0066, "PORT0066.ICN" }, { PORT0067, "PORT0067.ICN" }, { PORT0068, "PORT0068.ICN" }, { PORT0069, "PORT0069.ICN" }, { PORT0070, "PORT0070.ICN" }, { PORT0090, "PORT0090.ICN" }, { PORT0091, "PORT0091.ICN" }, { PORT0092, "PORT0092.ICN" }, { PORT0093, "PORT0093.ICN" }, { PORT0094, "PORT0094.ICN" }, { PORT0095, "PORT0095.ICN" }, { PORTCFLG, "PORTCFLG.ICN" }, { PORTMEDI, "PORTMEDI.ICN" }, { PORTXTRA, "PORTXTRA.ICN" }, { PRIMSKIL, "PRIMSKIL.ICN" }, { PUZZLE, "PUZZLE.ICN" }, { QWIKHERO, "QWIKHERO.ICN" }, { QWIKINFO, "QWIKINFO.ICN" }, { QWIKTOWN, "QWIKTOWN.ICN" }, { RADAR, "RADAR.ICN" }, { R_BFLG32, "R-BFLG32.ICN" }, { RECR2BKG, "RECR2BKG.ICN" }, { RECRBKG, "RECRBKG.ICN" }, { RECRUIT, "RECRUIT.ICN" }, { REDBACK, "REDBACK.ICN" }, { REDDEATH, "REDDEATH.ICN" }, { REDFIRE, "REDFIRE.ICN" }, { REQBKG, "REQBKG.ICN" }, { REQSBKG, "REQSBKG.ICN" }, { REQUEST, "REQUEST.ICN" }, { REQUESTS, "REQUESTS.ICN" }, { RESOURCE, "RESOURCE.ICN" }, { RESSMALL, "RESSMALL.ICN" }, { R_FLAG32, "R-FLAG32.ICN" }, { ROAD, "ROAD.ICN" }, { ROC, "ROC.ICN" }, { ROGUE, "ROGUE.ICN" }, { ROUTE, "ROUTE.ICN" }, { SCENIBKG, "SCENIBKG.ICN" }, { SCROLL2, "SCROLL2.ICN" }, { SCROLLCN, "SCROLLCN.ICN" }, { SCROLLE, "SCROLLE.ICN" }, { SCROLL, "SCROLL.ICN" }, { SECSKILL, "SECSKILL.ICN" }, { SHADOW32, "SHADOW32.ICN" }, { SHIELD, "SHIELD.ICN" }, { SHNGANIM, "SHNGANIM.ICN" }, { SKELETON, "SKELETON.ICN" }, { SMALCLOD, "SMALCLOD.ICN" }, { SMALFONT, "SMALFONT.ICN" }, { SMALLBAR, "SMALLBAR.ICN" }, { SORC32, "SORC32.ICN" }, { SPANBKGE, "SPANBKGE.ICN" }, { SPANBKG, "SPANBKG.ICN" }, { SPANBTNE, "SPANBTNE.ICN" }, { SPANBTN, "SPANBTN.ICN" }, { SPANEL, "SPANEL.ICN" }, { SPARKS, "SPARKS.ICN" }, { SPELCO, "SPELCO.ICN" }, { SPELLINF, "SPELLINF.ICN" }, { SPELLINL, "SPELLINL.ICN" }, { SPELLS, "SPELLS.ICN" }, { SPRITE, "SPRITE.ICN" }, { STELSKIN, "STELSKIN.ICN" }, { STONBACK, "STONBACK.ICN" }, { STONBAKE, "STONBAKE.ICN" }, { STONEBAK, "STONEBAK.ICN" }, { STONEBK2, "STONEBK2.ICN" }, { STONSKIN, "STONSKIN.ICN" }, { STORM, "STORM.ICN" }, { STREAM, "STREAM.ICN" }, { STRIP, "STRIP.ICN" }, { SUNMOONE, "SUNMOONE.ICN" }, { SUNMOON, "SUNMOON.ICN" }, { SURDRBKE, "SURDRBKE.ICN" }, { SURDRBKG, "SURDRBKG.ICN" }, { SURRENDE, "SURRENDE.ICN" }, { SURRENDR, "SURRENDR.ICN" }, { SWAPBTN, "SWAPBTN.ICN" }, { SWAPWIN, "SWAPWIN.ICN" }, { SWORDSM2, "SWORDSM2.ICN" }, { SWORDSMN, "SWORDSMN.ICN" }, { SYSTEME, "SYSTEME.ICN" }, { SYSTEM, "SYSTEM.ICN" }, { TAVWIN, "TAVWIN.ICN" }, { TENT, "TENT.ICN" }, { TERRAINS, "TERRAINS.ICN" }, { TEXTBACK, "TEXTBACK.ICN" }, { TEXTBAK2, "TEXTBAK2.ICN" }, { TEXTBAR, "TEXTBAR.ICN" }, { TITANBLA, "TITANBLA.ICN" }, { TITANBLU, "TITANBLU.ICN" }, { TITANMSL, "TITANMSL.ICN" }, { TOWNBKG0, "TOWNBKG0.ICN" }, { TOWNBKG1, "TOWNBKG1.ICN" }, { TOWNBKG2, "TOWNBKG2.ICN" }, { TOWNBKG3, "TOWNBKG3.ICN" }, { TOWNBKG4, "TOWNBKG4.ICN" }, { TOWNBKG5, "TOWNBKG5.ICN" }, { TOWNFIX, "TOWNFIX.ICN" }, { TOWNNAME, "TOWNNAME.ICN" }, { TOWNWIND, "TOWNWIND.ICN" }, { TRADPOSE, "TRADPOSE.ICN" }, { TRADPOST, "TRADPOST.ICN" }, { TREASURY, "TREASURY.ICN" }, { TREDECI, "TREDECI.ICN" }, { TREEVIL, "TREEVIL.ICN" }, { TREFALL, "TREFALL.ICN" }, { TREFIR, "TREFIR.ICN" }, { TREJNGL, "TREJNGL.ICN" }, { TRESNOW, "TRESNOW.ICN" }, { TROLL2, "TROLL2.ICN" }, { TROLL, "TROLL.ICN" }, { TROLLMSL, "TROLLMSL.ICN" }, { TWNBBOAT, "TWNBBOAT.ICN" }, { TWNBCAPT, "TWNBCAPT.ICN" }, { TWNBCSTL, "TWNBCSTL.ICN" }, { TWNBDOCK, "TWNBDOCK.ICN" }, { TWNBDW_0, "TWNBDW_0.ICN" }, { TWNBDW_1, "TWNBDW_1.ICN" }, { TWNBDW_2, "TWNBDW_2.ICN" }, { TWNBDW_3, "TWNBDW_3.ICN" }, { TWNBDW_4, "TWNBDW_4.ICN" }, { TWNBDW_5, "TWNBDW_5.ICN" }, { TWNBEXT0, "TWNBEXT0.ICN" }, { TWNBEXT1, "TWNBEXT1.ICN" }, { TWNBEXT2, "TWNBEXT2.ICN" }, { TWNBEXT3, "TWNBEXT3.ICN" }, { TWNBLTUR, "TWNBLTUR.ICN" }, { TWNBMAGE, "TWNBMAGE.ICN" }, { TWNBMARK, "TWNBMARK.ICN" }, { TWNBMOAT, "TWNBMOAT.ICN" }, { TWNBRTUR, "TWNBRTUR.ICN" }, { TWNBSPEC, "TWNBSPEC.ICN" }, { TWNBSTAT, "TWNBSTAT.ICN" }, { TWNBTENT, "TWNBTENT.ICN" }, { TWNBTHIE, "TWNBTHIE.ICN" }, { TWNBTVRN, "TWNBTVRN.ICN" }, { TWNBUP_1, "TWNBUP_1.ICN" }, { TWNBUP_3, "TWNBUP_3.ICN" }, { TWNBUP_4, "TWNBUP_4.ICN" }, { TWNBWEL2, "TWNBWEL2.ICN" }, { TWNBWELL, "TWNBWELL.ICN" }, { TWNKBOAT, "TWNKBOAT.ICN" }, { TWNKCAPT, "TWNKCAPT.ICN" }, { TWNKCSTL, "TWNKCSTL.ICN" }, { TWNKDOCK, "TWNKDOCK.ICN" }, { TWNKDW_0, "TWNKDW_0.ICN" }, { TWNKDW_1, "TWNKDW_1.ICN" }, { TWNKDW_2, "TWNKDW_2.ICN" }, { TWNKDW_3, "TWNKDW_3.ICN" }, { TWNKDW_4, "TWNKDW_4.ICN" }, { TWNKDW_5, "TWNKDW_5.ICN" }, { TWNKEXT0, "TWNKEXT0.ICN" }, { TWNKEXT1, "TWNKEXT1.ICN" }, { TWNKEXT2, "TWNKEXT2.ICN" }, { TWNKLTUR, "TWNKLTUR.ICN" }, { TWNKMAGE, "TWNKMAGE.ICN" }, { TWNKMARK, "TWNKMARK.ICN" }, { TWNKMOAT, "TWNKMOAT.ICN" }, { TWNKRTUR, "TWNKRTUR.ICN" }, { TWNKSPEC, "TWNKSPEC.ICN" }, { TWNKSTAT, "TWNKSTAT.ICN" }, { TWNKTENT, "TWNKTENT.ICN" }, { TWNKTHIE, "TWNKTHIE.ICN" }, { TWNKTVRN, "TWNKTVRN.ICN" }, { TWNKUP_1, "TWNKUP_1.ICN" }, { TWNKUP_2, "TWNKUP_2.ICN" }, { TWNKUP_3, "TWNKUP_3.ICN" }, { TWNKUP_4, "TWNKUP_4.ICN" }, { TWNKUP_5, "TWNKUP_5.ICN" }, { TWNKWEL2, "TWNKWEL2.ICN" }, { TWNKWELL, "TWNKWELL.ICN" }, { TWNNBOAT, "TWNNBOAT.ICN" }, { TWNNCAPT, "TWNNCAPT.ICN" }, { TWNNCSTL, "TWNNCSTL.ICN" }, { TWNNDOCK, "TWNNDOCK.ICN" }, { TWNNDW_0, "TWNNDW_0.ICN" }, { TWNNDW_1, "TWNNDW_1.ICN" }, { TWNNDW_2, "TWNNDW_2.ICN" }, { TWNNDW_3, "TWNNDW_3.ICN" }, { TWNNDW_4, "TWNNDW_4.ICN" }, { TWNNDW_5, "TWNNDW_5.ICN" }, { TWNNEXT0, "TWNNEXT0.ICN" }, { TWNNLTUR, "TWNNLTUR.ICN" }, { TWNNMAGE, "TWNNMAGE.ICN" }, { TWNNMARK, "TWNNMARK.ICN" }, { TWNNMOAT, "TWNNMOAT.ICN" }, { TWNNRTUR, "TWNNRTUR.ICN" }, { TWNNSPEC, "TWNNSPEC.ICN" }, { TWNNSTAT, "TWNNSTAT.ICN" }, { TWNNTENT, "TWNNTENT.ICN" }, { TWNNTHIE, "TWNNTHIE.ICN" }, { TWNNTVRN, "TWNNTVRN.ICN" }, { TWNNUP_1, "TWNNUP_1.ICN" }, { TWNNUP_2, "TWNNUP_2.ICN" }, { TWNNUP_3, "TWNNUP_3.ICN" }, { TWNNUP_4, "TWNNUP_4.ICN" }, { TWNNWEL2, "TWNNWEL2.ICN" }, { TWNNWELL, "TWNNWELL.ICN" }, { TWNSBOAT, "TWNSBOAT.ICN" }, { TWNSCAPT, "TWNSCAPT.ICN" }, { TWNSCSTL, "TWNSCSTL.ICN" }, { TWNSDOCK, "TWNSDOCK.ICN" }, { TWNSDW_0, "TWNSDW_0.ICN" }, { TWNSDW_1, "TWNSDW_1.ICN" }, { TWNSDW_2, "TWNSDW_2.ICN" }, { TWNSDW_3, "TWNSDW_3.ICN" }, { TWNSDW_4, "TWNSDW_4.ICN" }, { TWNSDW_5, "TWNSDW_5.ICN" }, { TWNSEXT0, "TWNSEXT0.ICN" }, { TWNSEXT1, "TWNSEXT1.ICN" }, { TWNSLTUR, "TWNSLTUR.ICN" }, { TWNSMAGE, "TWNSMAGE.ICN" }, { TWNSMARK, "TWNSMARK.ICN" }, { TWNSMOAT, "TWNSMOAT.ICN" }, { TWNSRTUR, "TWNSRTUR.ICN" }, { TWNSSPEC, "TWNSSPEC.ICN" }, { TWNSSTAT, "TWNSSTAT.ICN" }, { TWNSTENT, "TWNSTENT.ICN" }, { TWNSTHIE, "TWNSTHIE.ICN" }, { TWNSTVRN, "TWNSTVRN.ICN" }, { TWNSUP_1, "TWNSUP_1.ICN" }, { TWNSUP_2, "TWNSUP_2.ICN" }, { TWNSUP_3, "TWNSUP_3.ICN" }, { TWNSWEL2, "TWNSWEL2.ICN" }, { TWNSWELL, "TWNSWELL.ICN" }, { TWNWBOAT, "TWNWBOAT.ICN" }, { TWNWCAPT, "TWNWCAPT.ICN" }, { TWNWCSTL, "TWNWCSTL.ICN" }, { TWNWDOCK, "TWNWDOCK.ICN" }, { TWNWDW_0, "TWNWDW_0.ICN" }, { TWNWDW_1, "TWNWDW_1.ICN" }, { TWNWDW_2, "TWNWDW_2.ICN" }, { TWNWDW_3, "TWNWDW_3.ICN" }, { TWNWDW_4, "TWNWDW_4.ICN" }, { TWNWDW_5, "TWNWDW_5.ICN" }, { TWNWEXT0, "TWNWEXT0.ICN" }, { TWNWLTUR, "TWNWLTUR.ICN" }, { TWNWMAGE, "TWNWMAGE.ICN" }, { TWNWMARK, "TWNWMARK.ICN" }, { TWNWMOAT, "TWNWMOAT.ICN" }, { TWNWRTUR, "TWNWRTUR.ICN" }, { TWNWSPEC, "TWNWSPEC.ICN" }, { TWNWSTAT, "TWNWSTAT.ICN" }, { TWNWTENT, "TWNWTENT.ICN" }, { TWNWTHIE, "TWNWTHIE.ICN" }, { TWNWTVRN, "TWNWTVRN.ICN" }, { TWNWUP_3, "TWNWUP_3.ICN" }, { TWNWUP5B, "TWNWUP5B.ICN" }, { TWNWUP_5, "TWNWUP_5.ICN" }, { TWNWWEL2, "TWNWWEL2.ICN" }, { TWNWWELL, "TWNWWELL.ICN" }, { TWNZBOAT, "TWNZBOAT.ICN" }, { TWNZCAPT, "TWNZCAPT.ICN" }, { TWNZCSTL, "TWNZCSTL.ICN" }, { TWNZDOCK, "TWNZDOCK.ICN" }, { TWNZDW_0, "TWNZDW_0.ICN" }, { TWNZDW_1, "TWNZDW_1.ICN" }, { TWNZDW_2, "TWNZDW_2.ICN" }, { TWNZDW_3, "TWNZDW_3.ICN" }, { TWNZDW_4, "TWNZDW_4.ICN" }, { TWNZDW_5, "TWNZDW_5.ICN" }, { TWNZEXT0, "TWNZEXT0.ICN" }, { TWNZLTUR, "TWNZLTUR.ICN" }, { TWNZMAGE, "TWNZMAGE.ICN" }, { TWNZMARK, "TWNZMARK.ICN" }, { TWNZMOAT, "TWNZMOAT.ICN" }, { TWNZRTUR, "TWNZRTUR.ICN" }, { TWNZSPEC, "TWNZSPEC.ICN" }, { TWNZSTAT, "TWNZSTAT.ICN" }, { TWNZTENT, "TWNZTENT.ICN" }, { TWNZTHIE, "TWNZTHIE.ICN" }, { TWNZTVRN, "TWNZTVRN.ICN" }, { TWNZUP_2, "TWNZUP_2.ICN" }, { TWNZUP_4, "TWNZUP_4.ICN" }, { TWNZUP_5, "TWNZUP_5.ICN" }, { TWNZWEL2, "TWNZWEL2.ICN" }, { TWNZWELL, "TWNZWELL.ICN" }, { UNICORN, "UNICORN.ICN" }, { VAMPIRE2, "VAMPIRE2.ICN" }, { VAMPIRE, "VAMPIRE.ICN" }, { VGENBKGE, "VGENBKGE.ICN" }, { VGENBKG, "VGENBKG.ICN" }, { VIEW_ALL, "VIEW_ALL.ICN" }, { VIEWARME, "VIEWARME.ICN" }, { VIEWARMY, "VIEWARMY.ICN" }, { VIEWARSM, "VIEWARSM.ICN" }, { VIEWDDOR, "VIEWDDOR.ICN" }, { VIEWGEN, "VIEWGEN.ICN" }, { VIEWHROS, "VIEWHROS.ICN" }, { VIEWMINE, "VIEWMINE.ICN" }, { VIEWPUZL, "VIEWPUZL.ICN" }, { VIEWRSRC, "VIEWRSRC.ICN" }, { VIEWRTFX, "VIEWRTFX.ICN" }, { VIEWTWNS, "VIEWTWNS.ICN" }, { VIEWWRLD, "VIEWWRLD.ICN" }, { VWFLAG12, "VWFLAG12.ICN" }, { VWFLAG4, "VWFLAG4.ICN" }, { VWFLAG6, "VWFLAG6.ICN" }, { WELEM, "WELEM.ICN" }, { WELLBKG, "WELLBKG.ICN" }, { WELLXTRA, "WELLXTRA.ICN" }, { WINCMBBE, "WINCMBBE.ICN" }, { WINCMBTB, "WINCMBTB.ICN" }, { WINCMBT, "WINCMBT.ICN" }, { WINLOSEB, "WINLOSEB.ICN" }, { WINLOSEE, "WINLOSEE.ICN" }, { WINLOSE, "WINLOSE.ICN" }, { WOLF, "WOLF.ICN" }, { WRLK32, "WRLK32.ICN" }, { WZRD32, "WZRD32.ICN" }, { X_IVY, "X_IVY.ICN" }, { X_LOADCM, "X_LOADCM.ICN" }, { X_CMPBKG, "X_CMPBKG.ICN" }, { X_CMPBTN, "X_CMPBTN.ICN" }, { X_CMPEXT, "X_CMPEXT.ICN" }, { X_TRACK1, "X_TRACK1.ICN" }, { X_TRACK2, "X_TRACK2.ICN" }, { X_TRACK3, "X_TRACK3.ICN" }, { X_TRACK4, "X_TRACK4.ICN" }, { X_LOC1, "X_LOC1.ICN" }, { X_LOC2, "X_LOC2.ICN" }, { X_LOC3, "X_LOC3.ICN" }, { XPRIMARY, "XPRIMARY.ICN" }, { Y_BFLG32, "Y-BFLG32.ICN" }, { Y_FLAG32, "Y-FLAG32.ICN" }, { YINYANG, "YINYANG.ICN" }, { ZOMBIE2, "ZOMBIE2.ICN" }, { ZOMBIE, "ZOMBIE.ICN" } }; } const char * ICN::GetString( int icn ) { return UNKNOWN <= icn && icn < LAST_VALID_FILE_ICN ? icnmap[icn].string : "CUSTOM"; } uint32_t ICN::AnimationFrame( int icn, uint32_t start, uint32_t ticket, bool quantity ) { switch ( icn ) { case TWNBBOAT: case TWNKBOAT: case TWNNBOAT: case TWNSBOAT: case TWNWBOAT: case TWNZBOAT: return 1 + ticket % 9; case CMBTCAPB: case CMBTCAPK: case CMBTCAPN: case CMBTCAPS: case CMBTCAPW: case CMBTCAPZ: return 1 + ticket % 10; case CMBTHROB: return 1 + ticket % 18; case CMBTHROK: return 1 + ticket % 19; case CMBTHRON: return 1 + ticket % 19; case CMBTHROS: return 1 + ticket % 16; case CMBTHROW: return 1 + ticket % 16; case CMBTHROZ: return 1 + ticket % 18; case HEROFL00: case HEROFL01: case HEROFL02: case HEROFL03: case HEROFL04: case HEROFL05: case HEROFL06: return ticket % 5; case TWNBDOCK: case TWNKDOCK: case TWNNDOCK: case TWNSDOCK: case TWNWDOCK: case TWNZDOCK: case TWNBEXT0: case TWNKEXT0: case TWNNEXT0: case TWNSEXT0: case TWNWEXT0: case TWNZEXT0: case TWNBCAPT: case TWNBDW_3: case TWNBDW_4: case TWNBDW_5: case TWNBEXT1: case TWNBMOAT: case TWNBUP_3: case TWNBUP_4: case TWNKCSTL: case TWNKDW_0: case TWNKLTUR: case TWNKRTUR: case TWNKTHIE: case TWNKTVRN: case TWNNCSTL: case TWNNDW_2: case TWNNUP_2: case TWNSCAPT: case TWNSCSTL: case TWNSDW_0: case TWNSDW_1: case TWNSEXT1: case TWNSTHIE: case TWNSTVRN: case TWNSUP_1: case TWNSWEL2: case TWNWCAPT: case TWNWCSTL: case TWNWMOAT: case TWNZCSTL: case TWNZDW_0: case TWNZDW_2: case TWNZTHIE: case TWNZUP_2: return 1 + ticket % 5; case TWNBCSTL: case TWNKDW_2: case TWNKUP_2: case TWNNDW_5: case TWNNWEL2: case TWNWDW_0: case TWNWWEL2: case TWNZTVRN: return 1 + ticket % 6; case TWNKDW_4: case TWNKUP_4: return 1 + ticket % 7; case TAVWIN: return 2 + ticket % 20; case CMBTLOS1: return 1 + ticket % 30; case CMBTLOS2: return 1 + ticket % 29; case CMBTLOS3: return 1 + ticket % 22; case CMBTFLE1: return 1 + ticket % 43; case CMBTFLE2: return 1 + ticket % 26; case CMBTFLE3: return 1 + ticket % 25; case CMBTSURR: return 1 + ticket % 20; case WINCMBT: return 1 + ticket % 20; case MINIMON: return start + 1 + ticket % 6; case TWNNMAGE: return start + 1 + ticket % 5; case TWNBMAGE: return 4 == start ? start + 1 + ticket % 8 : 0; case SHNGANIM: return 1 + ticket % 39; case BTNSHNGL: return start + ticket % 4; case OBJNHAUN: return ticket % 15; case OBJNWATR: switch ( start ) { // bottle case 0x00: return start + ( ticket % 11 ) + 1; // shadow case 0x0C: // chest case 0x13: // shadow case 0x26: // flotsam case 0x2D: // unkn case 0x37: // boat case 0x3E: // waves case 0x45: // seaweed case 0x4C: case 0x53: case 0x5A: case 0x61: case 0x68: // sailor-man case 0x6F: // shadow case 0xBC: // buoy case 0xC3: // broken ship (right) case 0xE2: case 0xE9: case 0xF1: case 0xF8: return start + ( ticket % 6 ) + 1; // seagull on stones case 0x76: case 0x86: case 0x96: case 0xA6: return start + ( ticket % 15 ) + 1; // whirlpool case 0xCA: case 0xCE: case 0xD2: case 0xD6: case 0xDA: case 0xDE: return start + ( ticket % 3 ) + 1; default: return 0; } case OBJNWAT2: switch ( start ) { // sail broken ship (left) case 0x03: case 0x0C: return start + ( ticket % 6 ) + 1; default: return 0; } case OBJNCRCK: switch ( start ) { // pool of oil case 0x50: case 0x5B: case 0x66: case 0x71: case 0x7C: case 0x89: case 0x94: case 0x9F: case 0xAA: // smoke from chimney case 0xBE: // shadow smoke case 0xCA: return start + ( ticket % 10 ) + 1; default: return 0; } case OBJNDIRT: switch ( start ) { // mill case 0x99: case 0x9D: case 0xA1: case 0xA5: case 0xA9: case 0xAD: case 0xB1: case 0xB5: case 0xB9: case 0xBD: return start + ( ticket % 3 ) + 1; default: return 0; } case OBJNDSRT: switch ( start ) { // campfire case 0x36: case 0x3D: return start + ( ticket % 6 ) + 1; default: return 0; } case OBJNGRA2: switch ( start ) { // mill case 0x17: case 0x1B: case 0x1F: case 0x23: case 0x27: case 0x2B: case 0x2F: case 0x33: case 0x37: case 0x3B: return start + ( ticket % 3 ) + 1; // smoke from chimney case 0x3F: case 0x46: case 0x4D: // Archer House case 0x54: // smoke from chimney case 0x5D: case 0x64: // shadow smoke case 0x6B: // Peasant Hut case 0x72: return start + ( ticket % 6 ) + 1; default: return 0; } case OBJNLAV2: switch ( start ) { // middle volcano case 0x00: // shadow case 0x07: case 0x0E: // lava case 0x15: return start + ( ticket % 6 ) + 1; // small volcano // shadow case 0x21: case 0x2C: // lava case 0x37: case 0x43: return start + ( ticket % 10 ) + 1; default: return 0; } case OBJNLAV3: // big volcano switch ( start ) { // smoke case 0x00: case 0x0F: case 0x1E: case 0x2D: case 0x3C: case 0x4B: case 0x5A: case 0x69: case 0x87: case 0x96: case 0xA5: // shadow case 0x78: case 0xB4: case 0xC3: case 0xD2: case 0xE1: return start + ( ticket % 14 ) + 1; default: return 0; } case OBJNLAVA: switch ( start ) { // shadow of lava case 0x4E: case 0x58: case 0x62: return start + ( ticket % 9 ) + 1; default: return 0; } case OBJNMUL2: switch ( start ) { // lighthouse case 0x3D: return start + ( ticket % 9 ) + 1; // Alchemist Tower case 0x1B: // watermill case 0x53: case 0x5A: case 0x62: case 0x69: // fire in Wagon Camp case 0x81: // smoke smithy (2 chimney) case 0xA6: // smoke smithy (1 chimney) case 0xAD: // shadow smoke case 0xB4: return start + ( ticket % 6 ) + 1; // magic garden case 0xBE: return quantity ? start + ( ticket % 6 ) + 1 : start + 7; default: return 0; } case OBJNMULT: switch ( start ) { // smoke case 0x05: // shadow case 0x0F: case 0x19: return start + ( ticket % 9 ) + 1; // smoke case 0x24: // shadow case 0x2D: return start + ( ticket % 8 ) + 1; // smoke case 0x5A: // shadow case 0x61: case 0x68: case 0x7C: // campfire case 0x83: return start + ( ticket % 6 ) + 1; default: return 0; } case OBJNSNOW: switch ( start ) { // fire camp case 0x04: // Alchemist Tower case 0x97: // watermill case 0xA2: case 0xA9: case 0xB1: case 0xB8: return start + ( ticket % 6 ) + 1; // mill case 0x60: case 0x64: case 0x68: case 0x6C: case 0x70: case 0x74: case 0x78: case 0x7C: case 0x80: case 0x84: return start + ( ticket % 3 ) + 1; default: return 0; } case OBJNSWMP: switch ( start ) { // shadow case 0x00: case 0x0E: case 0x2B: // smoke case 0x07: case 0x22: case 0x33: // light in window case 0x16: case 0x3A: case 0x43: case 0x4A: return start + ( ticket % 6 ) + 1; default: return 0; } // extra objects for loyalty version case X_LOC1: if ( Settings::Get().isPriceOfLoyaltySupported() ) switch ( start ) { // alchemist tower case 0x04: case 0x0D: case 0x16: // arena case 0x1F: case 0x28: case 0x32: case 0x3B: // earth altar case 0x55: case 0x5E: case 0x67: return start + ( ticket % 8 ) + 1; default: return 0; } break; // extra objects for loyalty version case X_LOC2: if ( Settings::Get().isPriceOfLoyaltySupported() ) switch ( start ) { // mermaid case 0x0A: case 0x13: case 0x1C: case 0x25: // sirens case 0x2F: case 0x38: case 0x41: case 0x4A: case 0x53: case 0x5C: case 0x66: return start + ( ticket % 8 ) + 1; default: return 0; } break; // extra objects for loyalty version case X_LOC3: if ( Settings::Get().isPriceOfLoyaltySupported() ) switch ( start ) { // hut magi case 0x00: case 0x0A: case 0x14: // eye magi case 0x20: case 0x29: case 0x32: return start + ( ticket % 8 ) + 1; // barrier case 0x3C: case 0x42: case 0x48: case 0x4E: case 0x54: case 0x5A: case 0x60: case 0x66: return start + ( ticket % 4 ) + 1; default: return 0; } break; default: break; } return 0; } int ICN::PORTxxxx( int heroId ) { switch ( heroId ) { case Heroes::UNKNOWN: return ICN::UNKNOWN; case Heroes::LORDKILBURN: return ICN::PORT0000; case Heroes::SIRGALLANTH: return ICN::PORT0001; case Heroes::ECTOR: return ICN::PORT0002; case Heroes::GVENNETH: return ICN::PORT0003; case Heroes::TYRO: return ICN::PORT0004; case Heroes::AMBROSE: return ICN::PORT0005; case Heroes::RUBY: return ICN::PORT0006; case Heroes::MAXIMUS: return ICN::PORT0007; case Heroes::DIMITRY: return ICN::PORT0008; case Heroes::THUNDAX: return ICN::PORT0009; case Heroes::FINEOUS: return ICN::PORT0010; case Heroes::JOJOSH: return ICN::PORT0011; case Heroes::CRAGHACK: return ICN::PORT0012; case Heroes::JEZEBEL: return ICN::PORT0013; case Heroes::JACLYN: return ICN::PORT0014; case Heroes::ERGON: return ICN::PORT0015; case Heroes::TSABU: return ICN::PORT0016; case Heroes::ATLAS: return ICN::PORT0017; case Heroes::ASTRA: return ICN::PORT0018; case Heroes::NATASHA: return ICN::PORT0019; case Heroes::TROYAN: return ICN::PORT0020; case Heroes::VATAWNA: return ICN::PORT0021; case Heroes::REBECCA: return ICN::PORT0022; case Heroes::GEM: return ICN::PORT0023; case Heroes::ARIEL: return ICN::PORT0024; case Heroes::CARLAWN: return ICN::PORT0025; case Heroes::LUNA: return ICN::PORT0026; case Heroes::ARIE: return ICN::PORT0027; case Heroes::ALAMAR: return ICN::PORT0028; case Heroes::VESPER: return ICN::PORT0029; case Heroes::CRODO: return ICN::PORT0030; case Heroes::BAROK: return ICN::PORT0031; case Heroes::KASTORE: return ICN::PORT0032; case Heroes::AGAR: return ICN::PORT0033; case Heroes::FALAGAR: return ICN::PORT0034; case Heroes::WRATHMONT: return ICN::PORT0035; case Heroes::MYRA: return ICN::PORT0036; case Heroes::FLINT: return ICN::PORT0037; case Heroes::DAWN: return ICN::PORT0038; case Heroes::HALON: return ICN::PORT0039; case Heroes::MYRINI: return ICN::PORT0040; case Heroes::WILFREY: return ICN::PORT0041; case Heroes::SARAKIN: return ICN::PORT0042; case Heroes::KALINDRA: return ICN::PORT0043; case Heroes::MANDIGAL: return ICN::PORT0044; case Heroes::ZOM: return ICN::PORT0045; case Heroes::DARLANA: return ICN::PORT0046; case Heroes::ZAM: return ICN::PORT0047; case Heroes::RANLOO: return ICN::PORT0048; case Heroes::CHARITY: return ICN::PORT0049; case Heroes::RIALDO: return ICN::PORT0050; case Heroes::ROXANA: return ICN::PORT0051; case Heroes::SANDRO: return ICN::PORT0052; case Heroes::CELIA: return ICN::PORT0053; case Heroes::ROLAND: return ICN::PORT0054; case Heroes::CORLAGON: return ICN::PORT0055; case Heroes::ELIZA: return ICN::PORT0056; case Heroes::ARCHIBALD: return ICN::PORT0057; case Heroes::HALTON: return ICN::PORT0058; case Heroes::BRAX: return ICN::PORT0059; case Heroes::SOLMYR: return ICN::PORT0060; case Heroes::DAINWIN: return ICN::PORT0061; case Heroes::MOG: return ICN::PORT0062; case Heroes::UNCLEIVAN: return ICN::PORT0063; case Heroes::JOSEPH: return ICN::PORT0064; case Heroes::GALLAVANT: return ICN::PORT0065; case Heroes::ELDERIAN: return ICN::PORT0066; case Heroes::CEALLACH: return ICN::PORT0067; case Heroes::DRAKONIA: return ICN::PORT0068; case Heroes::MARTINE: return ICN::PORT0069; case Heroes::JARKONAS: return ICN::PORT0070; case Heroes::DEBUG_HERO: return ICN::PORT0059; default: // Did you add a new hero? Add the logic above! assert( 0 ); break; } return ICN::UNKNOWN; } int ICN::Get4Captain( int race ) { switch ( race ) { case Race::BARB: return CSTLCAPB; case Race::KNGT: return CSTLCAPK; case Race::NECR: return CSTLCAPN; case Race::SORC: return CSTLCAPS; case Race::WRLK: return CSTLCAPW; case Race::WZRD: return CSTLCAPZ; default: break; } return UNKNOWN; } int ICN::Get4Building( int race ) { switch ( race ) { case Race::BARB: return CSTLBARB; case Race::KNGT: return CSTLKNGT; case Race::NECR: return CSTLNECR; case Race::SORC: return CSTLSORC; case Race::WRLK: return CSTLWRLK; case Race::WZRD: return CSTLWZRD; default: break; } return UNKNOWN; } int ICN::Get4Castle( int race ) { switch ( race ) { case Race::BARB: return CASTLEB; case Race::KNGT: return CASTLEK; case Race::NECR: return CASTLEN; case Race::SORC: return CASTLES; case Race::WRLK: return CASTLEW; case Race::WZRD: return CASTLEZ; default: break; } return UNKNOWN; } int ICN::GetFlagIcnId( int color ) { switch ( color ) { case Color::BLUE: return ICN::HEROFL00; case Color::GREEN: return ICN::HEROFL01; case Color::RED: return ICN::HEROFL02; case Color::YELLOW: return ICN::HEROFL03; case Color::ORANGE: return ICN::HEROFL04; case Color::PURPLE: return ICN::HEROFL05; default: return ICN::HEROFL06; } } fheroes2-1.0.12+dfsg/src/fheroes2/agg/icn.h000066400000000000000000000555211456075706000202350ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2ICN_H #define H2ICN_H #include namespace ICN { enum : int { UNKNOWN, ADVBORDE, ADVBORD, ADVBTNS, ADVEBTNS, ADVMCO, AELEM, APANBKGE, APANBKG, APANELE, APANEL, ARCHER2, ARCHER, ARCH_MSL, ART32, ARTFX, ARTIFACT, BARB32, B_BFLG32, // blue hero flag on a boat BERZERK, B_FLAG32, // blue hero flag BIGBAR, BLDGXTRA, BLESS, BLIND, BLUEFIRE, BOAR, BOAT32, BOATSHAD, BOATWIND, BOOK, BORDEDIT, BOULDER, BRCREST, BROTHERS, BTNBAUD, BTNCMPGN, BTNCOM, BTNDCCFG, BTNDC, BTNEMAIN, BTNENEW, BTNESIZE, BTNHOTST, BTNMCFG, BTNMODEM, BTNMP, BTNNET2, BTNNET, BTNNEWGM, BTNSHNGL, BUILDING, BUYBUILD, BUYBUILE, CAMPBKGE, CAMPBKGG, CAMPXTRE, CAMPXTRG, CAPTCOVR, CASLBAR, CASLWIND, CASLXTRA, CASTBKGB, CASTBKGK, CASTBKGN, CASTBKGS, CASTBKGW, CASTBKGZ, CASTLEB, CASTLEK, CASTLEN, CASTLES, CASTLEW, CASTLEZ, CATAPULT, CAVALRYB, CAVALRYR, CBKGBEAC, CBKGCRCK, CBKGDIMT, CBKGDITR, CBKGDSRT, CBKGGRAV, CBKGGRMT, CBKGGRTR, CBKGLAVA, CBKGSNMT, CBKGSNTR, CBKGSWMP, CBKGWATR, CELLWIN, CENTAUR, CFLGSMAL, CLOP32, CLOUDLUK, CMBTCAPB, CMBTCAPK, CMBTCAPN, CMBTCAPS, CMBTCAPW, CMBTCAPZ, CMBTFLE1, CMBTFLE2, CMBTFLE3, CMBTHROB, CMBTHROK, CMBTHRON, CMBTHROS, CMBTHROW, CMBTHROZ, CMBTLOS1, CMBTLOS2, CMBTLOS3, CMBTMISC, CMBTSURR, CMSECO, COBJ0000, COBJ0001, COBJ0002, COBJ0003, COBJ0004, COBJ0005, COBJ0006, COBJ0007, COBJ0008, COBJ0009, COBJ0010, COBJ0011, COBJ0012, COBJ0013, COBJ0014, COBJ0015, COBJ0016, COBJ0017, COBJ0018, COBJ0019, COBJ0020, COBJ0021, COBJ0022, COBJ0023, COBJ0024, COBJ0025, COBJ0026, COBJ0027, COBJ0028, COBJ0029, COBJ0030, COBJ0031, COLDRAY, COLDRING, CONGRATS, COVR0001, COVR0002, COVR0003, COVR0004, COVR0005, COVR0006, COVR0007, COVR0008, COVR0009, COVR0010, COVR0011, COVR0012, COVR0013, COVR0014, COVR0015, COVR0016, COVR0017, COVR0018, COVR0019, COVR0020, COVR0021, COVR0022, COVR0023, COVR0024, CPANBKGE, CPANBKG, CPANELE, CPANEL, CREST, CSPANBKE, CSPANBKG, CSPANBTE, CSPANBTN, CSPANEL, CSTLBARB, CSTLCAPB, CSTLCAPK, CSTLCAPN, CSTLCAPS, CSTLCAPW, CSTLCAPZ, CSTLKNGT, CSTLNECR, CSTLSORC, CSTLWRLK, CSTLWZRD, CTRACK00, CTRACK01, CTRACK02, CTRACK03, CTRACK04, CTRACK05, CTRACK06, CURSE, CYCLOPS, DISRRAY, DRAGBLAK, DRAGBONE, DRAGGREE, DRAGRED, DRAGSLAY, DROPLISL, DROPLIST, DRUID2, DRUID, DRUIDMSL, DUMMY, DWARF2, DWARF, ECPANEL, EDITBTNS, EDITOR, EDITPANL, EELEM, ELECTRIC, ELF2, ELF, ELF__MSL, ESCROLL, ESPANBKG, ESPANBTN, ESPANEL, EVIW_ALL, EVIWDDOR, EVIWHROS, EVIWMINE, EVIWPUZL, EVIWRSRC, EVIWRTFX, EVIWTWNS, EVIWWRLD, EXPMRL, EXTRAOVR, FELEM, FIREBAL2, FIREBALL, FLAG32, FONT, FRNG0001, FRNG0002, FRNG0003, FRNG0004, FRNG0005, FRNG0006, FRNG0007, FRNG0008, FRNG0009, FRNG0010, FRNG0011, FRNG0012, FRNG0013, FROTH, GARGOYLE, G_BFLG32, // green hero flag on a boat GENIE, G_FLAG32, // green hero flag GHOST, GOBLIN, GOLEM2, GOLEM, GRIFFIN, GROUND12, GROUND4, GROUND6, HALFLING, HALFLMSL, HASTE, HEROBKG, HEROES, HEROEXTE, HEROEXTG, HEROFL00, HEROFL01, HEROFL02, HEROFL03, HEROFL04, HEROFL05, HEROFL06, HEROLOGE, HEROLOGO, HISCORE, HOURGLAS, HSBKG, HSBTNS, HSICONS, HYDRA, HYPNOTIZ, ICECLOUD, KEEP, KNGT32, LETTER12, LETTER4, LETTER6, LGNDXTRA, LGNDXTRE, LICH2, LICHCLOD, LICH, LICH_MSL, LISTBOX, LISTBOXS, LOCATORE, LOCATORS, MAGE1, MAGE2, MAGEGLDB, MAGEGLDK, MAGEGLDN, MAGEGLDS, MAGEGLDW, MAGEGLDZ, MAGIC01, MAGIC02, MAGIC03, MAGIC04, MAGIC06, MAGIC07, MAGIC08, MANA, MEDUSA, METEOR, MINICAPT, MINIHERO, MINILKMR, MINIMON, MINIPORT, MINISS, MINITOWN, MINOTAU2, MINOTAUR, MISC12, MISC4, MISC6, MOATPART, MOATWHOL, MOBILITY, MONH0000, MONH0001, MONH0002, MONH0003, MONH0004, MONH0005, MONH0006, MONH0007, MONH0008, MONH0009, MONH0010, MONH0011, MONH0012, MONH0013, MONH0014, MONH0015, MONH0016, MONH0017, MONH0018, MONH0019, MONH0020, MONH0021, MONH0022, MONH0023, MONH0024, MONH0025, MONH0026, MONH0027, MONH0028, MONH0029, MONH0030, MONH0031, MONH0032, MONH0033, MONH0034, MONH0035, MONH0036, MONH0037, MONH0038, MONH0039, MONH0040, MONH0041, MONH0042, MONH0043, MONH0044, MONH0045, MONH0046, MONH0047, MONH0048, MONH0049, MONH0050, MONH0051, MONH0052, MONH0053, MONH0054, MONH0055, MONH0056, MONH0057, MONH0058, MONH0059, MONH0060, MONH0061, MONH0062, MONH0063, MONH0064, MONH0065, MONS32, MORALEB, MORALEG, MTNCRCK, MTNDIRT, MTNDSRT, MTNGRAS, MTNLAVA, MTNMULT, MTNSNOW, MTNSWMP, MUMMY2, MUMMYW, NECR32, NETBOX, NGEXTRA, NGHSBKG, NGMPBKG, NGSPBKG, NOMAD, O_BFLG32, // orange hero flag on a boat OBJNARTI, OBJNCRCK, OBJNDIRT, OBJNDSRT, OBJNGRA2, OBJNGRAS, OBJNHAUN, OBJNLAV2, OBJNLAV3, OBJNLAVA, OBJNMUL2, OBJNMULT, OBJNRSRC, OBJNSNOW, OBJNSWMP, OBJNTOWN, OBJNTWBA, OBJNTWRD, OBJNTWSH, OBJNWAT2, OBJNWATR, OBJNXTRA, OBJPALET, O_FLAG32, // orange hero flag OGRE2, OGRE, ORC2, ORC, ORC__MSL, OVERBACK, OVERLAY, OVERVIEW, PALADIN2, PALADIN, PARALYZE, P_BFLG32, // purple hero flag on a boat PEASANT, P_FLAG32, // purple hero flag PHOENIX, PHYSICAL, PIKEMAN2, PIKEMAN, PORT0000, PORT0001, PORT0002, PORT0003, PORT0004, PORT0005, PORT0006, PORT0007, PORT0008, PORT0009, PORT0010, PORT0011, PORT0012, PORT0013, PORT0014, PORT0015, PORT0016, PORT0017, PORT0018, PORT0019, PORT0020, PORT0021, PORT0022, PORT0023, PORT0024, PORT0025, PORT0026, PORT0027, PORT0028, PORT0029, PORT0030, PORT0031, PORT0032, PORT0033, PORT0034, PORT0035, PORT0036, PORT0037, PORT0038, PORT0039, PORT0040, PORT0041, PORT0042, PORT0043, PORT0044, PORT0045, PORT0046, PORT0047, PORT0048, PORT0049, PORT0050, PORT0051, PORT0052, PORT0053, PORT0054, PORT0055, PORT0056, PORT0057, PORT0058, PORT0059, PORT0060, PORT0061, PORT0062, PORT0063, PORT0064, PORT0065, PORT0066, PORT0067, PORT0068, PORT0069, PORT0070, PORT0090, PORT0091, PORT0092, PORT0093, PORT0094, PORT0095, PORTCFLG, PORTMEDI, PORTXTRA, PRIMSKIL, PUZZLE, QWIKHERO, QWIKINFO, QWIKTOWN, RADAR, R_BFLG32, // red hero flag on a boat RECR2BKG, RECRBKG, RECRUIT, REDBACK, REDDEATH, REDFIRE, REQBKG, REQSBKG, REQUEST, REQUESTS, RESOURCE, RESSMALL, R_FLAG32, // red hero flag ROAD, ROC, ROGUE, ROUTE, SCENIBKG, SCROLL2, SCROLLCN, SCROLLE, SCROLL, SECSKILL, SHADOW32, SHIELD, SHNGANIM, SKELETON, SMALCLOD, SMALFONT, SMALLBAR, SORC32, SPANBKGE, SPANBKG, SPANBTNE, SPANBTN, SPANEL, SPARKS, SPELCO, SPELLINF, SPELLINL, SPELLS, SPRITE, STELSKIN, STONBACK, STONBAKE, STONEBAK, STONEBK2, STONSKIN, STORM, STREAM, STRIP, SUNMOONE, SUNMOON, SURDRBKE, SURDRBKG, SURRENDE, SURRENDR, SWAPBTN, SWAPWIN, SWORDSM2, SWORDSMN, SYSTEME, // contains an empty evil interface button in the last two sprites SYSTEM, // contains an empty good interface button in the last two sprites TAVWIN, TENT, TERRAINS, TEXTBACK, TEXTBAK2, TEXTBAR, TITANBLA, TITANBLU, TITANMSL, TOWNBKG0, TOWNBKG1, TOWNBKG2, TOWNBKG3, TOWNBKG4, TOWNBKG5, TOWNFIX, TOWNNAME, TOWNWIND, TRADPOSE, TRADPOST, TREASURY, TREDECI, TREEVIL, TREFALL, TREFIR, TREJNGL, TRESNOW, TROLL2, TROLL, TROLLMSL, TWNBBOAT, TWNBCAPT, TWNBCSTL, TWNBDOCK, TWNBDW_0, TWNBDW_1, TWNBDW_2, TWNBDW_3, TWNBDW_4, TWNBDW_5, TWNBEXT0, TWNBEXT1, TWNBEXT2, TWNBEXT3, TWNBLTUR, TWNBMAGE, TWNBMARK, TWNBMOAT, TWNBRTUR, TWNBSPEC, TWNBSTAT, TWNBTENT, TWNBTHIE, TWNBTVRN, TWNBUP_1, TWNBUP_3, TWNBUP_4, TWNBWEL2, TWNBWELL, TWNKBOAT, TWNKCAPT, TWNKCSTL, TWNKDOCK, TWNKDW_0, TWNKDW_1, TWNKDW_2, TWNKDW_3, TWNKDW_4, TWNKDW_5, TWNKEXT0, TWNKEXT1, TWNKEXT2, TWNKLTUR, TWNKMAGE, TWNKMARK, TWNKMOAT, TWNKRTUR, TWNKSPEC, TWNKSTAT, TWNKTENT, TWNKTHIE, TWNKTVRN, TWNKUP_1, TWNKUP_2, TWNKUP_3, TWNKUP_4, TWNKUP_5, TWNKWEL2, TWNKWELL, TWNNBOAT, TWNNCAPT, TWNNCSTL, TWNNDOCK, TWNNDW_0, TWNNDW_1, TWNNDW_2, TWNNDW_3, TWNNDW_4, TWNNDW_5, TWNNEXT0, TWNNLTUR, TWNNMAGE, TWNNMARK, TWNNMOAT, TWNNRTUR, TWNNSPEC, TWNNSTAT, TWNNTENT, TWNNTHIE, TWNNTVRN, TWNNUP_1, TWNNUP_2, TWNNUP_3, TWNNUP_4, TWNNWEL2, TWNNWELL, TWNSBOAT, TWNSCAPT, TWNSCSTL, TWNSDOCK, TWNSDW_0, TWNSDW_1, TWNSDW_2, TWNSDW_3, TWNSDW_4, TWNSDW_5, TWNSEXT0, TWNSEXT1, TWNSLTUR, TWNSMAGE, TWNSMARK, TWNSMOAT, TWNSRTUR, TWNSSPEC, TWNSSTAT, TWNSTENT, TWNSTHIE, TWNSTVRN, TWNSUP_1, TWNSUP_2, TWNSUP_3, TWNSWEL2, TWNSWELL, TWNWBOAT, TWNWCAPT, TWNWCSTL, TWNWDOCK, TWNWDW_0, TWNWDW_1, TWNWDW_2, TWNWDW_3, TWNWDW_4, TWNWDW_5, TWNWEXT0, TWNWLTUR, TWNWMAGE, TWNWMARK, TWNWMOAT, TWNWRTUR, TWNWSPEC, TWNWSTAT, TWNWTENT, TWNWTHIE, TWNWTVRN, TWNWUP_3, TWNWUP5B, TWNWUP_5, TWNWWEL2, TWNWWELL, TWNZBOAT, TWNZCAPT, TWNZCSTL, TWNZDOCK, TWNZDW_0, TWNZDW_1, TWNZDW_2, TWNZDW_3, TWNZDW_4, TWNZDW_5, TWNZEXT0, TWNZLTUR, TWNZMAGE, TWNZMARK, TWNZMOAT, TWNZRTUR, TWNZSPEC, TWNZSTAT, TWNZTENT, TWNZTHIE, TWNZTVRN, TWNZUP_2, TWNZUP_4, TWNZUP_5, TWNZWEL2, TWNZWELL, UNICORN, VAMPIRE2, VAMPIRE, VGENBKGE, VGENBKG, VIEW_ALL, VIEWARME, VIEWARMY, VIEWARSM, VIEWDDOR, VIEWGEN, VIEWHROS, VIEWMINE, VIEWPUZL, VIEWRSRC, VIEWRTFX, VIEWTWNS, VIEWWRLD, VWFLAG12, VWFLAG4, VWFLAG6, WELEM, WELLBKG, WELLXTRA, WINCMBBE, WINCMBTB, WINCMBT, WINLOSEB, WINLOSEE, WINLOSE, WOLF, WRLK32, WZRD32, X_IVY, X_LOADCM, X_CMPBKG, X_CMPBTN, X_CMPEXT, X_TRACK1, X_TRACK2, X_TRACK3, X_TRACK4, X_LOC1, X_LOC2, X_LOC3, XPRIMARY, Y_BFLG32, // yellow hero flag on a boat Y_FLAG32, // yellow hero flag YINYANG, ZOMBIE2, ZOMBIE, LAST_VALID_FILE_ICN, // Real ICNs need a special reference to ICN files. Put generated by application ICNs at the end of this enumeration. // system ROUTERED, YELLOW_FONT, YELLOW_SMALLFONT, BATTLESKIP, BUYMAX, BTNBATTLEONLY, BTNGIFT_GOOD, BTNGIFT_EVIL, CSLMARKER, GRAY_FONT, GRAY_SMALL_FONT, TROLL2MSL, LISTBOX_EVIL, // alias to LISTBOX, but black and white colored MONSTER_SWITCH_LEFT_ARROW, MONSTER_SWITCH_RIGHT_ARROW, NON_UNIFORM_GOOD_RESTART_BUTTON, NON_UNIFORM_EVIL_RESTART_BUTTON, UNIFORM_GOOD_MAX_BUTTON, UNIFORM_GOOD_MIN_BUTTON, UNIFORM_EVIL_MAX_BUTTON, UNIFORM_EVIL_MIN_BUTTON, UNIFORM_GOOD_OKAY_BUTTON, UNIFORM_EVIL_OKAY_BUTTON, UNIFORM_GOOD_CANCEL_BUTTON, UNIFORM_EVIL_CANCEL_BUTTON, UNIFORM_GOOD_EXIT_BUTTON, UNIFORM_EVIL_EXIT_BUTTON, WHITE_LARGE_FONT, SWAP_ARROW_LEFT_TO_RIGHT, SWAP_ARROW_RIGHT_TO_LEFT, COLOR_CURSOR_ADVENTURE_MAP, MONO_CURSOR_ADVENTURE_MAP, DISMISS_HERO_DISABLED_BUTTON, NEW_CAMPAIGN_DISABLED_BUTTON, KNIGHT_CASTLE_RIGHT_FARM, KNIGHT_CASTLE_LEFT_FARM, NECROMANCER_CASTLE_STANDALONE_CAPTAIN_QUARTERS, NECROMANCER_CASTLE_CAPTAIN_QUARTERS_BRIDGE, MAP_TYPE_ICON, BARBARIAN_CASTLE_CAPTAIN_QUARTERS_LEFT_SIDE, SORCERESS_CASTLE_CAPTAIN_QUARTERS_LEFT_SIDE, GOOD_ARMY_BUTTON, GOOD_MARKET_BUTTON, EVIL_ARMY_BUTTON, EVIL_MARKET_BUTTON, MONO_CURSOR_ADVMBW, MONO_CURSOR_SPELBW, MONO_CURSOR_CMSSBW, ESPANBKG_EVIL, RECR2BKG_EVIL, STONEBAK_EVIL, STONEBAK_SMALL_POL, UNIFORMBAK_GOOD, UNIFORMBAK_EVIL, REDBAK_SMALL_VERTICAL, WELLBKG_EVIL, CASLWIND_EVIL, CASLXTRA_EVIL, RECRBKG_EVIL, STRIP_BACKGROUND_EVIL, EDITBTNS_EVIL, GOOD_CAMPAIGN_BUTTONS, EVIL_CAMPAIGN_BUTTONS, POL_CAMPAIGN_BUTTONS, MINI_MONSTER_IMAGE, MINI_MONSTER_SHADOW, BUTTON_GOOD_FONT_RELEASED, BUTTON_GOOD_FONT_PRESSED, BUTTON_EVIL_FONT_RELEASED, BUTTON_EVIL_FONT_PRESSED, EMPTY_GOOD_BUTTON, EMPTY_EVIL_BUTTON, EMPTY_GOOD_MEDIUM_BUTTON, EMPTY_EVIL_MEDIUM_BUTTON, EMPTY_POL_BUTTON, EMPTY_GUILDWELL_BUTTON, EMPTY_VERTICAL_GOOD_BUTTON, EMPTY_MAP_SELECT_BUTTON, BUTTON_STANDARD_GAME, BUTTON_CAMPAIGN_GAME, BUTTON_MULTIPLAYER_GAME, BUTTON_LARGE_CANCEL, BUTTON_LARGE_CONFIG, BUTTON_ORIGINAL_CAMPAIGN, BUTTON_EXPANSION_CAMPAIGN, BUTTON_HOT_SEAT, BUTTON_2_PLAYERS, BUTTON_3_PLAYERS, BUTTON_4_PLAYERS, BUTTON_5_PLAYERS, BUTTON_6_PLAYERS, BUTTON_NEW_GAME_GOOD, BUTTON_NEW_GAME_EVIL, BUTTON_SAVE_GAME_GOOD, BUTTON_SAVE_GAME_EVIL, BUTTON_LOAD_GAME_GOOD, BUTTON_LOAD_GAME_EVIL, BUTTON_INFO_GOOD, BUTTON_INFO_EVIL, BUTTON_QUIT_GOOD, BUTTON_QUIT_EVIL, BUTTON_SMALL_CANCEL_GOOD, BUTTON_SMALL_CANCEL_EVIL, BUTTON_SMALL_OKAY_GOOD, BUTTON_SMALL_OKAY_EVIL, BUTTON_SMALLER_OKAY_GOOD, BUTTON_SMALLER_OKAY_EVIL, BUTTON_SMALL_ACCEPT_GOOD, BUTTON_SMALL_ACCEPT_EVIL, BUTTON_SMALL_DECLINE_GOOD, BUTTON_SMALL_DECLINE_EVIL, BUTTON_SMALL_LEARN_GOOD, BUTTON_SMALL_LEARN_EVIL, BUTTON_SMALL_TRADE_GOOD, BUTTON_SMALL_TRADE_EVIL, BUTTON_SMALL_YES_GOOD, BUTTON_SMALL_YES_EVIL, BUTTON_SMALL_NO_GOOD, BUTTON_SMALL_NO_EVIL, BUTTON_SMALL_EXIT_GOOD, BUTTON_SMALL_EXIT_EVIL, BUTTON_EXIT_HEROES_MEETING, BUTTON_EXIT_TOWN, BUTTON_EXIT_PUZZLE_DIM_DOOR_GOOD, BUTTON_EXIT_PUZZLE_DIM_DOOR_EVIL, BUTTON_SMALL_DISMISS_GOOD, BUTTON_SMALL_DISMISS_EVIL, BUTTON_SMALL_UPGRADE_GOOD, BUTTON_SMALL_UPGRADE_EVIL, BUTTON_SMALL_RESTART_GOOD, BUTTON_SMALL_RESTART_EVIL, BUTTON_SMALL_MIN_GOOD, BUTTON_SMALL_MIN_EVIL, BUTTON_SMALL_MAX_GOOD, BUTTON_SMALL_MAX_EVIL, BUTTON_EXIT_GOOD, BUTTON_RESET_GOOD, BUTTON_START_GOOD, BUTTON_CASTLE_GOOD, BUTTON_CASTLE_EVIL, BUTTON_TOWN_GOOD, BUTTON_TOWN_EVIL, BUTTON_KINGDOM_EXIT, BUTTON_KINGDOM_HEROES, BUTTON_KINGDOM_TOWNS, BUTTON_MAPSIZE_SMALL, BUTTON_MAPSIZE_MEDIUM, BUTTON_MAPSIZE_LARGE, BUTTON_MAPSIZE_XLARGE, BUTTON_MAPSIZE_ALL, BUTTON_MAP_SELECT_GOOD, BUTTON_MAP_SELECT_EVIL, BUTTON_GUILDWELL_EXIT, BUTTON_VIEWWORLD_EXIT_GOOD, BUTTON_VIEWWORLD_EXIT_EVIL, BUTTON_VERTICAL_DISMISS, BUTTON_VERTICAL_EXIT, BUTTON_HSCORES_VERTICAL_CAMPAIGN, BUTTON_HSCORES_VERTICAL_EXIT, BUTTON_HSCORES_VERTICAL_STANDARD, GAME_OPTION_ICON, DIFFICULTY_ICON_EASY, DIFFICULTY_ICON_NORMAL, DIFFICULTY_ICON_HARD, DIFFICULTY_ICON_EXPERT, DIFFICULTY_ICON_IMPOSSIBLE, METALLIC_BORDERED_TEXTBOX_GOOD, METALLIC_BORDERED_TEXTBOX_EVIL, // IMPORTANT! Put any new entry just above this one. LASTICN }; const char * GetString( int ); uint32_t AnimationFrame( int icn, uint32_t start, uint32_t ticket, bool quantity = false ); int PORTxxxx( int heroId ); int Get4Captain( int race ); int Get4Building( int race ); int Get4Castle( int race ); int GetFlagIcnId( int color ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/agg/m82.cpp000066400000000000000000000421141456075706000204170ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "m82.h" #include "maps_tiles.h" #include "mp2.h" #include "spell.h" namespace M82 { const struct { int type; const char * string; } m82map[] = { { AELMATTK, "AELMATTK.82M" }, { AELMKILL, "AELMKILL.82M" }, { AELMMOVE, "AELMMOVE.82M" }, { AELMWNCE, "AELMWNCE.82M" }, { ANTIMAGK, "ANTIMAGK.82M" }, { ARCHATTK, "ARCHATTK.82M" }, { ARCHKILL, "ARCHKILL.82M" }, { ARCHMOVE, "ARCHMOVE.82M" }, { ARCHSHOT, "ARCHSHOT.82M" }, { ARCHWNCE, "ARCHWNCE.82M" }, { ARMGEDN, "ARMGEDN.82M" }, { BADLUCK, "BADLUCK.82M" }, { BADMRLE, "BADMRLE.82M" }, { BERZERK, "BERZERK.82M" }, { BLESS, "BLESS.82M" }, { BLIND, "BLIND.82M" }, { BLOODLUS, "BLOODLUS.82M" }, { BOARATTK, "BOARATTK.82M" }, { BOARKILL, "BOARKILL.82M" }, { BOARMOVE, "BOARMOVE.82M" }, { BOARWNCE, "BOARWNCE.82M" }, { BONEATTK, "BONEATTK.82M" }, { BONEKILL, "BONEKILL.82M" }, { BONEMOVE, "BONEMOVE.82M" }, { BONEWNCE, "BONEWNCE.82M" }, { BUILDTWN, "BUILDTWN.82M" }, { CATSND00, "CATSND00.82M" }, { CATSND02, "CATSND02.82M" }, { CAVLATTK, "CAVLATTK.82M" }, { CAVLKILL, "CAVLKILL.82M" }, { CAVLMOVE, "CAVLMOVE.82M" }, { CAVLWNCE, "CAVLWNCE.82M" }, { CHAINLTE, "CHAINLTE.82M" }, { CNTRATTK, "CNTRATTK.82M" }, { CNTRKILL, "CNTRKILL.82M" }, { CNTRMOVE, "CNTRMOVE.82M" }, { CNTRSHOT, "CNTRSHOT.82M" }, { CNTRWNCE, "CNTRWNCE.82M" }, { COLDRAY, "COLDRAY.82M" }, { COLDRING, "COLDRING.82M" }, { CURE, "CURE.82M" }, { CURSE, "CURSE.82M" }, { CYCLATTK, "CYCLATTK.82M" }, { CYCLKILL, "CYCLKILL.82M" }, { CYCLMOVE, "CYCLMOVE.82M" }, { CYCLWNCE, "CYCLWNCE.82M" }, { DIGSOUND, "DIGSOUND.82M" }, { DIPMAGK, "DIPMAGK.82M" }, { DISRUPTR, "DISRUPTR.82M" }, { DRAWBRG, "DRAWBRG.82M" }, { DRGNATTK, "DRGNATTK.82M" }, { DRGNKILL, "DRGNKILL.82M" }, { DRGNMOVE, "DRGNMOVE.82M" }, { DRGNSLAY, "DRGNSLAY.82M" }, { DRGNWNCE, "DRGNWNCE.82M" }, { DRUIATTK, "DRUIATTK.82M" }, { DRUIKILL, "DRUIKILL.82M" }, { DRUIMOVE, "DRUIMOVE.82M" }, { DRUISHOT, "DRUISHOT.82M" }, { DRUIWNCE, "DRUIWNCE.82M" }, { DWRFATTK, "DWRFATTK.82M" }, { DWRFKILL, "DWRFKILL.82M" }, { DWRFMOVE, "DWRFMOVE.82M" }, { DWRFWNCE, "DWRFWNCE.82M" }, { EELMATTK, "EELMATTK.82M" }, { EELMKILL, "EELMKILL.82M" }, { EELMMOVE, "EELMMOVE.82M" }, { EELMWNCE, "EELMWNCE.82M" }, { ELF_ATTK, "ELF_ATTK.82M" }, { ELF_KILL, "ELF_KILL.82M" }, { ELF_MOVE, "ELF_MOVE.82M" }, { ELF_SHOT, "ELF_SHOT.82M" }, { ELF_WNCE, "ELF_WNCE.82M" }, { ERTHQUAK, "ERTHQUAK.82M" }, { EXPERNCE, "EXPERNCE.82M" }, { FELMATTK, "FELMATTK.82M" }, { FELMKILL, "FELMKILL.82M" }, { FELMMOVE, "FELMMOVE.82M" }, { FELMWNCE, "FELMWNCE.82M" }, { FIREBALL, "FIREBALL.82M" }, { GARGATTK, "GARGATTK.82M" }, { GARGKILL, "GARGKILL.82M" }, { GARGMOVE, "GARGMOVE.82M" }, { GARGWNCE, "GARGWNCE.82M" }, { GBLNATTK, "GBLNATTK.82M" }, { GBLNKILL, "GBLNKILL.82M" }, { GBLNMOVE, "GBLNMOVE.82M" }, { GBLNWNCE, "GBLNWNCE.82M" }, { GENIATTK, "GENIATTK.82M" }, { GENIKILL, "GENIKILL.82M" }, { GENIMOVE, "GENIMOVE.82M" }, { GENIWNCE, "GENIWNCE.82M" }, { GHSTATTK, "GHSTATTK.82M" }, { GHSTKILL, "GHSTKILL.82M" }, { GHSTMOVE, "GHSTMOVE.82M" }, { GHSTWNCE, "GHSTWNCE.82M" }, { GOLMATTK, "GOLMATTK.82M" }, { GOLMKILL, "GOLMKILL.82M" }, { GOLMMOVE, "GOLMMOVE.82M" }, { GOLMWNCE, "GOLMWNCE.82M" }, { GOODLUCK, "GOODLUCK.82M" }, { GOODMRLE, "GOODMRLE.82M" }, { GRIFATTK, "GRIFATTK.82M" }, { GRIFKILL, "GRIFKILL.82M" }, { GRIFMOVE, "GRIFMOVE.82M" }, { GRIFWNCE, "GRIFWNCE.82M" }, { H2MINE, "H2MINE.82M" }, { HALFATTK, "HALFATTK.82M" }, { HALFKILL, "HALFKILL.82M" }, { HALFMOVE, "HALFMOVE.82M" }, { HALFSHOT, "HALFSHOT.82M" }, { HALFWNCE, "HALFWNCE.82M" }, { HASTE, "HASTE.82M" }, { HYDRATTK, "HYDRATTK.82M" }, { HYDRKILL, "HYDRKILL.82M" }, { HYDRMOVE, "HYDRMOVE.82M" }, { HYDRWNCE, "HYDRWNCE.82M" }, { HYPNOTIZ, "HYPNOTIZ.82M" }, { KEEPSHOT, "KEEPSHOT.82M" }, { KILLFADE, "KILLFADE.82M" }, { LICHATTK, "LICHATTK.82M" }, { LICHEXPL, "LICHEXPL.82M" }, { LICHKILL, "LICHKILL.82M" }, { LICHMOVE, "LICHMOVE.82M" }, { LICHSHOT, "LICHSHOT.82M" }, { LICHWNCE, "LICHWNCE.82M" }, { LIGHTBLT, "LIGHTBLT.82M" }, { LOOP0000, "LOOP0000.82M" }, { LOOP0001, "LOOP0001.82M" }, { LOOP0002, "LOOP0002.82M" }, { LOOP0003, "LOOP0003.82M" }, { LOOP0004, "LOOP0004.82M" }, { LOOP0005, "LOOP0005.82M" }, { LOOP0006, "LOOP0006.82M" }, { LOOP0007, "LOOP0007.82M" }, { LOOP0008, "LOOP0008.82M" }, { LOOP0009, "LOOP0009.82M" }, { LOOP0010, "LOOP0010.82M" }, { LOOP0011, "LOOP0011.82M" }, { LOOP0012, "LOOP0012.82M" }, { LOOP0013, "LOOP0013.82M" }, { LOOP0014, "LOOP0014.82M" }, { LOOP0015, "LOOP0015.82M" }, { LOOP0016, "LOOP0016.82M" }, { LOOP0017, "LOOP0017.82M" }, { LOOP0018, "LOOP0018.82M" }, { LOOP0019, "LOOP0019.82M" }, { LOOP0020, "LOOP0020.82M" }, { LOOP0021, "LOOP0021.82M" }, { LOOP0022, "LOOP0022.82M" }, { LOOP0023, "LOOP0023.82M" }, { LOOP0024, "LOOP0024.82M" }, { LOOP0025, "LOOP0025.82M" }, { LOOP0026, "LOOP0026.82M" }, { LOOP0027, "LOOP0027.82M" }, { MAGCAROW, "MAGCAROW.82M" }, { MAGEATTK, "MAGEATTK.82M" }, { MAGEKILL, "MAGEKILL.82M" }, { MAGEMOVE, "MAGEMOVE.82M" }, { MAGESHOT, "MAGESHOT.82M" }, { MAGEWNCE, "MAGEWNCE.82M" }, { MASSBLES, "MASSBLES.82M" }, { MASSCURE, "MASSCURE.82M" }, { MASSCURS, "MASSCURS.82M" }, { MASSHAST, "MASSHAST.82M" }, { MASSSHIE, "MASSSHIE.82M" }, { MASSSLOW, "MASSSLOW.82M" }, { MEDSATTK, "MEDSATTK.82M" }, { MEDSKILL, "MEDSKILL.82M" }, { MEDSMOVE, "MEDSMOVE.82M" }, { MEDSWNCE, "MEDSWNCE.82M" }, { METEOR, "METEOR~1.82M" }, { MINOATTK, "MINOATTK.82M" }, { MINOKILL, "MINOKILL.82M" }, { MINOMOVE, "MINOMOVE.82M" }, { MINOWNCE, "MINOWNCE.82M" }, { MIRRORIM, "MIRRORIM.82M" }, { MNRDEATH, "MNRDEATH.82M" }, { MUMYATTK, "MUMYATTK.82M" }, { MUMYKILL, "MUMYKILL.82M" }, { MUMYMOVE, "MUMYMOVE.82M" }, { MUMYWNCE, "MUMYWNCE.82M" }, { NMADATTK, "NMADATTK.82M" }, { NMADKILL, "NMADKILL.82M" }, { NMADMOVE, "NMADMOVE.82M" }, { NMADWNCE, "NMADWNCE.82M" }, { NWHEROLV, "NWHEROLV.82M" }, { OGREATTK, "OGREATTK.82M" }, { OGREKILL, "OGREKILL.82M" }, { OGREMOVE, "OGREMOVE.82M" }, { OGREWNCE, "OGREWNCE.82M" }, { ORC_ATTK, "ORC_ATTK.82M" }, { ORC_KILL, "ORC_KILL.82M" }, { ORC_MOVE, "ORC_MOVE.82M" }, { ORC_SHOT, "ORC_SHOT.82M" }, { ORC_WNCE, "ORC_WNCE.82M" }, { PARALIZE, "PARALIZE.82M" }, { PHOEATTK, "PHOEATTK.82M" }, { PHOEKILL, "PHOEKILL.82M" }, { PHOEMOVE, "PHOEMOVE.82M" }, { PHOEWNCE, "PHOEWNCE.82M" }, { PICKUP01, "PICKUP01.82M" }, { PICKUP02, "PICKUP02.82M" }, { PICKUP03, "PICKUP03.82M" }, { PICKUP04, "PICKUP04.82M" }, { PICKUP05, "PICKUP05.82M" }, { PICKUP06, "PICKUP06.82M" }, { PICKUP07, "PICKUP07.82M" }, { PIKEATTK, "PIKEATTK.82M" }, { PIKEKILL, "PIKEKILL.82M" }, { PIKEMOVE, "PIKEMOVE.82M" }, { PIKEWNCE, "PIKEWNCE.82M" }, { PLDNATTK, "PLDNATTK.82M" }, { PLDNKILL, "PLDNKILL.82M" }, { PLDNMOVE, "PLDNMOVE.82M" }, { PLDNWNCE, "PLDNWNCE.82M" }, { PREBATTL, "PREBATTL.82M" }, { PROTECT, "PROTECT.82M" }, { PSNTATTK, "PSNTATTK.82M" }, { PSNTKILL, "PSNTKILL.82M" }, { PSNTMOVE, "PSNTMOVE.82M" }, { PSNTWNCE, "PSNTWNCE.82M" }, { RESURECT, "RESURECT.82M" }, { RESURTRU, "RESURTRU.82M" }, { ROC_ATTK, "ROC_ATTK.82M" }, { ROC_KILL, "ROC_KILL.82M" }, { ROC_MOVE, "ROC_MOVE.82M" }, { ROC_WNCE, "ROC_WNCE.82M" }, { ROGUATTK, "ROGUATTK.82M" }, { ROGUKILL, "ROGUKILL.82M" }, { ROGUMOVE, "ROGUMOVE.82M" }, { ROGUWNCE, "ROGUWNCE.82M" }, { RSBRYFZL, "RSBRYFZL.82M" }, { SHIELD, "SHIELD.82M" }, { SKELATTK, "SKELATTK.82M" }, { SKELKILL, "SKELKILL.82M" }, { SKELMOVE, "SKELMOVE.82M" }, { SKELWNCE, "SKELWNCE.82M" }, { SLOW, "SLOW.82M" }, { SPRTATTK, "SPRTATTK.82M" }, { SPRTKILL, "SPRTKILL.82M" }, { SPRTMOVE, "SPRTMOVE.82M" }, { SPRTWNCE, "SPRTWNCE.82M" }, { STELSKIN, "STELSKIN.82M" }, { STONESKI, "STONESKI.82M" }, { STONSKIN, "STONSKIN.82M" }, { STORM, "STORM.82M" }, { SUMNELM, "SUMNELM.82M" }, { SWDMATTK, "SWDMATTK.82M" }, { SWDMKILL, "SWDMKILL.82M" }, { SWDMMOVE, "SWDMMOVE.82M" }, { SWDMWNCE, "SWDMWNCE.82M" }, { TELEIN, "TELEIN.82M" }, { TELPTIN, "TELPTIN.82M" }, { TELPTOUT, "TELPTOUT.82M" }, { TITNATTK, "TITNATTK.82M" }, { TITNKILL, "TITNKILL.82M" }, { TITNMOVE, "TITNMOVE.82M" }, { TITNSHOT, "TITNSHOT.82M" }, { TITNWNCE, "TITNWNCE.82M" }, { TREASURE, "TREASURE.82M" }, { TRLLATTK, "TRLLATTK.82M" }, { TRLLKILL, "TRLLKILL.82M" }, { TRLLMOVE, "TRLLMOVE.82M" }, { TRLLSHOT, "TRLLSHOT.82M" }, { TRLLWNCE, "TRLLWNCE.82M" }, { UNICATTK, "UNICATTK.82M" }, { UNICKILL, "UNICKILL.82M" }, { UNICMOVE, "UNICMOVE.82M" }, { UNICWNCE, "UNICWNCE.82M" }, { VAMPATTK, "VAMPATTK.82M" }, { VAMPEXT1, "VAMPEXT1.82M" }, { VAMPEXT2, "VAMPEXT2.82M" }, { VAMPKILL, "VAMPKILL.82M" }, { VAMPMOVE, "VAMPMOVE.82M" }, { VAMPWNCE, "VAMPWNCE.82M" }, { WELMATTK, "WELMATTK.82M" }, { WELMKILL, "WELMKILL.82M" }, { WELMMOVE, "WELMMOVE.82M" }, { WELMWNCE, "WELMWNCE.82M" }, { WOLFATTK, "WOLFATTK.82M" }, { WOLFKILL, "WOLFKILL.82M" }, { WOLFMOVE, "WOLFMOVE.82M" }, { WOLFWNCE, "WOLFWNCE.82M" }, { WSND00, "WSND00.82M" }, { WSND01, "WSND01.82M" }, { WSND02, "WSND02.82M" }, { WSND03, "WSND03.82M" }, { WSND04, "WSND04.82M" }, { WSND05, "WSND05.82M" }, { WSND06, "WSND06.82M" }, { WSND10, "WSND10.82M" }, { WSND11, "WSND11.82M" }, { WSND12, "WSND12.82M" }, { WSND13, "WSND13.82M" }, { WSND14, "WSND14.82M" }, { WSND15, "WSND15.82M" }, { WSND16, "WSND16.82M" }, { WSND20, "WSND20.82M" }, { WSND21, "WSND21.82M" }, { WSND22, "WSND22.82M" }, { WSND23, "WSND23.82M" }, { WSND24, "WSND24.82M" }, { WSND25, "WSND25.82M" }, { WSND26, "WSND26.82M" }, { ZOMBATTK, "ZOMBATTK.82M" }, { ZOMBKILL, "ZOMBKILL.82M" }, { ZOMBMOVE, "ZOMBMOVE.82M" }, { ZOMBWNCE, "ZOMBWNCE.82M" }, { UNKNOWN, "UNKNOWN" } }; } const char * M82::GetString( int m82 ) { return AELMATTK <= m82 && UNKNOWN > m82 ? m82map[m82].string : m82map[UNKNOWN].string; } int M82::FromSpell( const int spellID ) { switch ( spellID ) { case Spell::FIREBALL: case Spell::FIREBLAST: return FIREBALL; case Spell::LIGHTNINGBOLT: return LIGHTBLT; case Spell::CHAINLIGHTNING: return CHAINLTE; case Spell::TELEPORT: return TELEIN; case Spell::CURE: return CURE; case Spell::MASSCURE: return MASSCURE; case Spell::RESURRECT: return RESURECT; case Spell::RESURRECTTRUE: return RESURTRU; case Spell::HASTE: return HASTE; case Spell::MASSHASTE: return MASSHAST; case Spell::SLOW: return SLOW; case Spell::MASSSLOW: return MASSSLOW; case Spell::BLIND: return BLIND; case Spell::BLESS: return BLESS; case Spell::MASSBLESS: return MASSBLES; case Spell::STONESKIN: return STONSKIN; case Spell::STEELSKIN: return STELSKIN; case Spell::CURSE: return CURSE; case Spell::MASSCURSE: return MASSCURS; case Spell::ANTIMAGIC: return ANTIMAGK; case Spell::DISPEL: case Spell::MASSDISPEL: return DIPMAGK; case Spell::ARROW: return MAGCAROW; case Spell::BERSERKER: return BERZERK; case Spell::ARMAGEDDON: return ARMGEDN; case Spell::ELEMENTALSTORM: return STORM; case Spell::METEORSHOWER: return METEOR; case Spell::PARALYZE: return PARALIZE; case Spell::HYPNOTIZE: return HYPNOTIZ; case Spell::COLDRAY: return COLDRAY; case Spell::COLDRING: return COLDRING; case Spell::DISRUPTINGRAY: return DISRUPTR; case Spell::DEATHRIPPLE: case Spell::DEATHWAVE: return MNRDEATH; case Spell::DRAGONSLAYER: return DRGNSLAY; case Spell::BLOODLUST: return BLOODLUS; case Spell::ANIMATEDEAD: return RESURECT; case Spell::MIRRORIMAGE: return MIRRORIM; case Spell::SHIELD: return SHIELD; case Spell::MASSSHIELD: return MASSSHIE; case Spell::SUMMONEELEMENT: case Spell::SUMMONAELEMENT: case Spell::SUMMONFELEMENT: case Spell::SUMMONWELEMENT: return SUMNELM; case Spell::EARTHQUAKE: return ERTHQUAK; case Spell::HAUNT: return H2MINE; case Spell::PETRIFY: return PARALIZE; default: break; } return UNKNOWN; } // TODO: This function works fine for most of objects as they have only one "main" tile. However, // TODO: some objects like Oracle or Volcano can be bigger than 1 tile leading to multiple sounds // TODO: coming from the same object and these sounds might not be synchronized. This is mostly // TODO: noticeable with 3D Audio mode on. M82::SoundType M82::getAdventureMapTileSound( const Maps::Tiles & tile ) { if ( tile.isStream() ) { return LOOP0014; } switch ( tile.GetObject( false ) ) { case MP2::OBJ_BUOY: return LOOP0000; case MP2::OBJ_SHIPWRECK: case MP2::OBJ_DERELICT_SHIP: return LOOP0001; case MP2::OBJ_COAST: return LOOP0002; case MP2::OBJ_ORACLE: return LOOP0003; case MP2::OBJ_STONE_LITHS: return LOOP0004; case MP2::OBJ_VOLCANO: switch ( tile.getObjectIcnType() ) { // Tile with volcanic steam only case MP2::OBJ_ICN_TYPE_UNKNOWN: return UNKNOWN; // Small volcanoes case MP2::OBJ_ICN_TYPE_OBJNLAVA: return LOOP0005; default: break; } // Other volcanoes return LOOP0027; case MP2::OBJ_LAVAPOOL: return LOOP0006; case MP2::OBJ_ALCHEMIST_LAB: return LOOP0007; case MP2::OBJ_WATER_WHEEL: return LOOP0009; case MP2::OBJ_CAMPFIRE: return LOOP0010; case MP2::OBJ_WINDMILL: return LOOP0011; case MP2::OBJ_ARTESIAN_SPRING: case MP2::OBJ_FOUNTAIN: return LOOP0012; case MP2::OBJ_WATER_LAKE: case MP2::OBJ_WATERING_HOLE: return LOOP0013; case MP2::OBJ_MINE: return LOOP0015; case MP2::OBJ_SAWMILL: return LOOP0016; case MP2::OBJ_DAEMON_CAVE: return LOOP0017; case MP2::OBJ_SHRINE_FIRST_CIRCLE: case MP2::OBJ_SHRINE_SECOND_CIRCLE: case MP2::OBJ_SHRINE_THIRD_CIRCLE: return LOOP0018; case MP2::OBJ_ROCK: // This sound should only be played for a specific sprite belonging to Rock if ( tile.containsSprite( MP2::OBJ_ICN_TYPE_OBJNWATR, 183 ) ) { return LOOP0019; } break; case MP2::OBJ_TAR_PIT: return LOOP0021; case MP2::OBJ_TRADING_POST: return LOOP0022; case MP2::OBJ_RUINS: return LOOP0024; case MP2::OBJ_PEASANT_HUT: case MP2::OBJ_DWARF_COTTAGE: case MP2::OBJ_ARCHER_HOUSE: return LOOP0025; case MP2::OBJ_ABANDONED_MINE: case MP2::OBJ_FREEMANS_FOUNDRY: return LOOP0026; default: break; } return UNKNOWN; } fheroes2-1.0.12+dfsg/src/fheroes2/agg/m82.h000066400000000000000000000177261456075706000200770ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2M82_H #define H2M82_H namespace Maps { class Tiles; } namespace M82 { enum SoundType : int { AELMATTK, AELMKILL, AELMMOVE, AELMWNCE, ANTIMAGK, ARCHATTK, ARCHKILL, ARCHMOVE, ARCHSHOT, ARCHWNCE, ARMGEDN, BADLUCK, BADMRLE, BERZERK, BLESS, BLIND, BLOODLUS, BOARATTK, BOARKILL, BOARMOVE, BOARWNCE, BONEATTK, BONEKILL, BONEMOVE, BONEWNCE, BUILDTWN, CATSND00, CATSND02, CAVLATTK, CAVLKILL, CAVLMOVE, CAVLWNCE, CHAINLTE, CNTRATTK, CNTRKILL, CNTRMOVE, CNTRSHOT, CNTRWNCE, COLDRAY, COLDRING, CURE, CURSE, CYCLATTK, CYCLKILL, CYCLMOVE, CYCLWNCE, DIGSOUND, DIPMAGK, DISRUPTR, DRAWBRG, DRGNATTK, DRGNKILL, DRGNMOVE, DRGNSLAY, DRGNWNCE, DRUIATTK, DRUIKILL, DRUIMOVE, DRUISHOT, DRUIWNCE, DWRFATTK, DWRFKILL, DWRFMOVE, DWRFWNCE, EELMATTK, EELMKILL, EELMMOVE, EELMWNCE, ELF_ATTK, ELF_KILL, ELF_MOVE, ELF_SHOT, ELF_WNCE, ERTHQUAK, EXPERNCE, FELMATTK, FELMKILL, FELMMOVE, FELMWNCE, FIREBALL, GARGATTK, GARGKILL, GARGMOVE, GARGWNCE, GBLNATTK, GBLNKILL, GBLNMOVE, GBLNWNCE, GENIATTK, GENIKILL, GENIMOVE, GENIWNCE, GHSTATTK, GHSTKILL, GHSTMOVE, GHSTWNCE, GOLMATTK, GOLMKILL, GOLMMOVE, GOLMWNCE, GOODLUCK, GOODMRLE, GRIFATTK, GRIFKILL, GRIFMOVE, GRIFWNCE, H2MINE, HALFATTK, HALFKILL, HALFMOVE, HALFSHOT, HALFWNCE, HASTE, HYDRATTK, HYDRKILL, HYDRMOVE, HYDRWNCE, HYPNOTIZ, KEEPSHOT, KILLFADE, LICHATTK, LICHEXPL, LICHKILL, LICHMOVE, LICHSHOT, LICHWNCE, LIGHTBLT, // These sounds are used for objects on Adventure Map. LOOP0000, LOOP0001, LOOP0002, LOOP0003, LOOP0004, LOOP0005, LOOP0006, LOOP0007, LOOP0008, // UNUSED: sounds of boiling hot liquid. Most likely magma or mercury. LOOP0009, LOOP0010, LOOP0011, LOOP0012, LOOP0013, LOOP0014, LOOP0015, LOOP0016, LOOP0017, LOOP0018, LOOP0019, // A sound with seagulls. It is used for rocks with seagulls. LOOP0020, // UNUSED: waves on a beach. LOOP0021, LOOP0022, LOOP0023, // UNUSED: a moving wooden structure with water. The most suitable for Water Wheel which uses LOOP0009. LOOP0024, LOOP0025, LOOP0026, // Moving (rusty?) mechanical parts, originally used for Abandoned Mine. In fheroes2, it is used also for Freeman's Foundry. LOOP0027, MAGCAROW, MAGEATTK, MAGEKILL, MAGEMOVE, MAGESHOT, MAGEWNCE, MASSBLES, MASSCURE, MASSCURS, MASSHAST, MASSSHIE, MASSSLOW, MEDSATTK, MEDSKILL, MEDSMOVE, MEDSWNCE, METEOR, MINOATTK, MINOKILL, MINOMOVE, MINOWNCE, MIRRORIM, MNRDEATH, MUMYATTK, MUMYKILL, MUMYMOVE, MUMYWNCE, NMADATTK, NMADKILL, NMADMOVE, NMADWNCE, NWHEROLV, OGREATTK, OGREKILL, OGREMOVE, OGREWNCE, ORC_ATTK, ORC_KILL, ORC_MOVE, ORC_SHOT, ORC_WNCE, PARALIZE, PHOEATTK, PHOEKILL, PHOEMOVE, PHOEWNCE, PICKUP01, PICKUP02, PICKUP03, PICKUP04, PICKUP05, PICKUP06, PICKUP07, PIKEATTK, PIKEKILL, PIKEMOVE, PIKEWNCE, PLDNATTK, PLDNKILL, PLDNMOVE, PLDNWNCE, PREBATTL, PROTECT, // UNUSED: some sort of magic. PSNTATTK, PSNTKILL, PSNTMOVE, PSNTWNCE, RESURECT, RESURTRU, ROC_ATTK, ROC_KILL, ROC_MOVE, ROC_WNCE, ROGUATTK, ROGUKILL, ROGUMOVE, ROGUWNCE, RSBRYFZL, SHIELD, SKELATTK, SKELKILL, SKELMOVE, SKELWNCE, SLOW, SPRTATTK, SPRTKILL, SPRTMOVE, SPRTWNCE, STELSKIN, STONESKI, // UNUSED. STONSKIN, STORM, SUMNELM, SWDMATTK, SWDMKILL, SWDMMOVE, SWDMWNCE, TELEIN, TELPTIN, TELPTOUT, TITNATTK, TITNKILL, TITNMOVE, TITNSHOT, TITNWNCE, TREASURE, TRLLATTK, TRLLKILL, TRLLMOVE, TRLLSHOT, TRLLWNCE, UNICATTK, UNICKILL, UNICMOVE, UNICWNCE, VAMPATTK, VAMPEXT1, VAMPEXT2, VAMPKILL, VAMPMOVE, VAMPWNCE, WELMATTK, WELMKILL, WELMMOVE, WELMWNCE, WOLFATTK, WOLFKILL, WOLFMOVE, WOLFWNCE, // Hero's movement sounds. WSND00, WSND01, WSND02, WSND03, WSND04, WSND05, WSND06, WSND10, WSND11, WSND12, WSND13, WSND14, WSND15, WSND16, WSND20, WSND21, WSND22, WSND23, WSND24, WSND25, WSND26, ZOMBATTK, ZOMBKILL, ZOMBMOVE, ZOMBWNCE, // Put all new sounds before this line. UNKNOWN }; const char * GetString( int m82 ); int FromSpell( const int spellID ); // Returns the ambient soundtrack for a given tile or M82::UNKNOWN if there is no track SoundType getAdventureMapTileSound( const Maps::Tiles & tile ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/agg/mus.cpp000066400000000000000000000214201456075706000206120ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2008 by Josh Matthews * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "mus.h" #include #include #include #include "ground.h" #include "race.h" #include "rand.h" namespace { struct MusMapItem { int type; const char * string; }; void addTrackId( std::string & output, const int musicTrackId ) { if ( musicTrackId < 10 ) { output += '0'; } output += std::to_string( musicTrackId ); } const std::array musmap = { { { MUS::UNUSED, "" }, { MUS::DATATRACK, "" }, { MUS::BATTLE1, "Battle 1" }, { MUS::BATTLE2, "Battle 2" }, { MUS::BATTLE3, "Battle 3" }, { MUS::SORCERESS_CASTLE, "Sorceress Castle" }, { MUS::WARLOCK_CASTLE, "Warlock Castle" }, { MUS::NECROMANCER_CASTLE, "Necromancer Castle" }, { MUS::KNIGHT_CASTLE, "Knight Castle" }, { MUS::BARBARIAN_CASTLE, "Barbarian Castle" }, { MUS::WIZARD_CASTLE, "Wizard Castle" }, { MUS::LAVA, "Lava Theme" }, { MUS::WASTELAND, "Wasteland Theme" }, { MUS::DESERT, "Desert Theme" }, { MUS::SNOW, "Snow Theme" }, { MUS::SWAMP, "Swamp Theme" }, { MUS::OCEAN, "Ocean Theme" }, { MUS::DIRT, "Dirt Theme" }, { MUS::GRASS, "Grass Theme" }, { MUS::LOSTGAME, "Lost Game" }, { MUS::NEW_WEEK, "New Week" }, { MUS::NEW_MONTH, "New Month" }, { MUS::ARCHIBALD_CAMPAIGN_SCREEN, "Archibald Campaign" }, { MUS::PUZZLE, "Map Puzzle" }, { MUS::ROLAND_CAMPAIGN_SCREEN, "Roland Campaign" }, { MUS::CARAVANS, "25" }, { MUS::CARAVANS_2, "26" }, { MUS::CARAVANS_3, "27" }, { MUS::COMPUTER_TURN, "AI Turn" }, { MUS::BATTLEWIN, "Battle Won" }, { MUS::BATTLELOSE, "Battle Lost" }, { MUS::DUNGEON, "Dungeon" }, { MUS::WATERSPRING, "Waterspring" }, { MUS::ARABIAN, "Arabian" }, { MUS::HILLFORT, "Hillfort" }, { MUS::TREEHOUSE, "Treehouse" }, { MUS::DEMONCAVE, "Demoncave" }, { MUS::EXPERIENCE, "Experience" }, { MUS::SKILL, "Skill" }, { MUS::WATCHTOWER, "Watchtower" }, { MUS::XANADU, "Xanadu" }, { MUS::ULTIMATE_ARTIFACT, "Ultimate Artifact" }, { MUS::MAINMENU, "Main Menu" }, { MUS::VICTORY, "Scenario Victory" }, { MUS::UNKNOWN, "UNKNOWN" } } }; } namespace MUS { std::string getFileName( const int musicTrackId, const EXTERNAL_MUSIC_TYPE musicType, const char * fileExtension ) { assert( fileExtension != nullptr ); if ( musicTrackId <= UNUSED || musicTrackId > UNKNOWN ) { // You are passing an invalid music track ID! assert( 0 ); return {}; } if ( musicType == EXTERNAL_MUSIC_TYPE::MAPPED ) { std::string output; addTrackId( output, musicTrackId ); output += ' '; output += musmap[musicTrackId].string; output += fileExtension; return output; } if ( musicType == EXTERNAL_MUSIC_TYPE::DOS_VERSION ) { std::string output( "homm2_" ); // GOG version format, data track was ignored there so 02 becomes 01 addTrackId( output, musicTrackId - 1 ); output += fileExtension; return output; } if ( musicType == EXTERNAL_MUSIC_TYPE::WIN_VERSION ) { std::string output( "Track" ); addTrackId( output, musicTrackId ); output += fileExtension; return output; } // Did you add a new type of music? assert( 0 ); return {}; } int FromGround( const int groundType ) { switch ( groundType ) { case Maps::Ground::DESERT: return DESERT; case Maps::Ground::SNOW: return SNOW; case Maps::Ground::SWAMP: return SWAMP; case Maps::Ground::WASTELAND: return WASTELAND; case Maps::Ground::BEACH: return OCEAN; case Maps::Ground::LAVA: return LAVA; case Maps::Ground::DIRT: return DIRT; case Maps::Ground::GRASS: return GRASS; case Maps::Ground::WATER: return OCEAN; default: // Did you add a new ground type? Add the track for it! assert( 0 ); break; } return UNKNOWN; } int FromRace( const int race ) { switch ( race ) { case Race::KNGT: return KNIGHT_CASTLE; case Race::BARB: return BARBARIAN_CASTLE; case Race::SORC: return SORCERESS_CASTLE; case Race::WRLK: return WARLOCK_CASTLE; case Race::WZRD: return WIZARD_CASTLE; case Race::NECR: return NECROMANCER_CASTLE; default: // Did you add a new race? Add an appropriate music theme for it! assert( 0 ); break; } return UNKNOWN; } int GetBattleRandom() { switch ( Rand::Get( 1, 3 ) ) { case 1: return BATTLE1; case 2: return BATTLE2; case 3: return BATTLE3; default: // How is it even possible? assert( 0 ); break; } return UNKNOWN; } } fheroes2-1.0.12+dfsg/src/fheroes2/agg/mus.h000066400000000000000000000061161456075706000202640ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2008 by Josh Matthews * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2MUS_H #define H2MUS_H #include namespace MUS { enum MusicTrack : int { UNUSED, DATATRACK, // not in use BATTLE1, BATTLE2, BATTLE3, SORCERESS_CASTLE, WARLOCK_CASTLE, NECROMANCER_CASTLE, KNIGHT_CASTLE, BARBARIAN_CASTLE, WIZARD_CASTLE, LAVA, WASTELAND, DESERT, SNOW, SWAMP, OCEAN, DIRT, GRASS, LOSTGAME, NEW_WEEK, NEW_MONTH, ARCHIBALD_CAMPAIGN_SCREEN, PUZZLE, ROLAND_CAMPAIGN_SCREEN, CARAVANS, // not in use CARAVANS_2, // not in use CARAVANS_3, // not in use COMPUTER_TURN, BATTLEWIN, BATTLELOSE, DUNGEON, WATERSPRING, ARABIAN, HILLFORT, TREEHOUSE, DEMONCAVE, EXPERIENCE, SKILL, WATCHTOWER, XANADU, ULTIMATE_ARTIFACT, MAINMENU, VICTORY, // IMPORTANT!!! Put all new entries above this line. UNKNOWN }; enum class EXTERNAL_MUSIC_TYPE : int { MAPPED, DOS_VERSION, WIN_VERSION }; std::string getFileName( const int musicTrackId, const EXTERNAL_MUSIC_TYPE musicType, const char * fileExtension ); int FromGround( const int groundType ); int FromRace( const int race ); int GetBattleRandom(); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/agg/til.h000066400000000000000000000036001456075706000202430ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2TIL_H #define H2TIL_H namespace TIL { enum { UNKNOWN, CLOF32, GROUND32, STON, // IMPORTANT! Put any new entry just above this one. LASTTIL }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/agg/xmi.cpp000066400000000000000000000112521456075706000206050ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "xmi.h" #include "mus.h" namespace XMI { const struct { int type; const char * string; } xmimap[] = { { UNKNOWN, "UNKNOWN" }, { MIDI0002, "MIDI0002.XMI" }, { MIDI0003, "MIDI0003.XMI" }, { MIDI0004, "MIDI0004.XMI" }, { MIDI0005, "MIDI0005.XMI" }, { MIDI0006, "MIDI0006.XMI" }, { MIDI0007, "MIDI0007.XMI" }, { MIDI0008, "MIDI0008.XMI" }, { MIDI0009, "MIDI0009.XMI" }, { MIDI0010, "MIDI0010.XMI" }, { MIDI0011, "MIDI0011.XMI" }, { MIDI0013, "MIDI0013.XMI" }, { MIDI0014, "MIDI0014.XMI" }, { MIDI0015, "MIDI0015.XMI" }, { MIDI0017, "MIDI0017.XMI" }, { MIDI0018, "MIDI0018.XMI" }, { MIDI0042, "MIDI0042.XMI" }, { MIDI0043, "MIDI0043.XMI" }, { MIDI_ORIGINAL_KNIGHT, "MIDI0009.XMI" }, // Knight theme was used by both Barbarian and Wizard castles, so we use either MIDI0009 or MIDI0010 { MIDI_ORIGINAL_BARBARIAN, "MIDI0007.XMI" }, // Barbarian intended theme is under MIDI0007 { MIDI_ORIGINAL_SORCERESS, "MIDI0005.XMI" }, // Sorceress doesn't have own track in OG release, Warlock theme was used { MIDI_ORIGINAL_WARLOCK, "MIDI0005.XMI" }, // Warlock theme was set to Sorceress so we use MIDI0005 { MIDI_ORIGINAL_WIZARD, "MIDI0008.XMI" }, // Wizard's and Knight's tracks were switched around, so we use MIDI0008 { MIDI_ORIGINAL_NECROMANCER, "MIDI0006.XMI" } // Necromancer theme has trickled down to Warlock so we use MIDI0006 }; } const char * XMI::GetString( int track ) { return UNKNOWN < track && MIDI_ORIGINAL_NECROMANCER >= track ? xmimap[track].string : xmimap[UNKNOWN].string; } // Due to a bug in Succession Wars/demo release (HEROES2.AGG) we have to remap original MIDI tracks to intended castles int XMI::FromMUS( int track, bool expansion ) { switch ( track ) { case MUS::BATTLE1: return MIDI0002; case MUS::BATTLE2: return MIDI0003; case MUS::BATTLE3: return MIDI0004; case MUS::SORCERESS_CASTLE: // Sorceress didn't have own XMI file in original release unfortunately return MIDI0005; case MUS::WARLOCK_CASTLE: return expansion ? MIDI0006 : MIDI_ORIGINAL_WARLOCK; case MUS::NECROMANCER_CASTLE: return expansion ? MIDI0007 : MIDI_ORIGINAL_NECROMANCER; case MUS::KNIGHT_CASTLE: return expansion ? MIDI0008 : MIDI_ORIGINAL_KNIGHT; case MUS::BARBARIAN_CASTLE: return expansion ? MIDI0009 : MIDI_ORIGINAL_BARBARIAN; case MUS::WIZARD_CASTLE: return expansion ? MIDI0010 : MIDI_ORIGINAL_WIZARD; case MUS::LAVA: return MIDI0011; case MUS::DESERT: return MIDI0013; case MUS::SNOW: return MIDI0014; case MUS::SWAMP: return MIDI0015; case MUS::DIRT: return MIDI0017; case MUS::GRASS: return MIDI0018; case MUS::MAINMENU: return MIDI0042; case MUS::VICTORY: return MIDI0043; default: break; } return UNKNOWN; } fheroes2-1.0.12+dfsg/src/fheroes2/agg/xmi.h000066400000000000000000000045131456075706000202540ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2XMI_H #define H2XMI_H namespace XMI { enum { UNKNOWN, MIDI0002, MIDI0003, MIDI0004, MIDI0005, MIDI0006, MIDI0007, MIDI0008, MIDI0009, MIDI0010, MIDI0011, MIDI0013, MIDI0014, MIDI0015, MIDI0017, MIDI0018, MIDI0042, MIDI0043, MIDI_ORIGINAL_KNIGHT, MIDI_ORIGINAL_BARBARIAN, MIDI_ORIGINAL_SORCERESS, MIDI_ORIGINAL_WARLOCK, MIDI_ORIGINAL_WIZARD, MIDI_ORIGINAL_NECROMANCER }; const char * GetString( int track ); int FromMUS( int track, bool expansion ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/ai/000077500000000000000000000000001456075706000171365ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/fheroes2/ai/ai.h000066400000000000000000000202171456075706000177020ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2AI_H #define H2AI_H #include #include "castle.h" #include "mp2.h" #include "payment.h" #include "rand.h" class StreamBase; class Funds; class HeroBase; class Heroes; class Kingdom; class Army; class Spell; namespace Maps { class Tiles; } namespace Battle { class Arena; class Unit; class Actions; } namespace AI { enum class AI_TYPE : int { NORMAL }; enum AI_PERSONALITY { NONE, WARRIOR, BUILDER, EXPLORER }; // Although AI heroes are capable to find their own tasks strategic AI should be able to focus them on most critical tasks enum class PriorityTaskType : int { // AI will focus on siegeing or chasing the selected enemy castle or hero. ATTACK, // Target will usually be a friendly castle. AI will move heroes to defend and garrison it. DEFEND, // Target must be a friendly castle or hero. AI with such priority set should focus on bringing more troops to the target. REINFORCE }; const double ARMY_ADVANTAGE_DESPERATE = 0.8; const double ARMY_ADVANTAGE_SMALL = 1.3; const double ARMY_ADVANTAGE_MEDIUM = 1.5; const double ARMY_ADVANTAGE_LARGE = 1.8; class Base { public: virtual ~Base() = default; virtual void KingdomTurn( Kingdom & kingdom ) = 0; virtual void BattleTurn( Battle::Arena & arena, const Battle::Unit & unit, Battle::Actions & actions ) = 0; virtual void revealFog( const Maps::Tiles & tile, const Kingdom & kingdom ) = 0; virtual void HeroesAdd( const Heroes & hero ); virtual void HeroesRemove( const Heroes & hero ); virtual void HeroesBeginMovement( Heroes & hero ); virtual void HeroesFinishMovement( Heroes & hero ); virtual void HeroesPreBattle( HeroBase & hero, bool isAttacking ); virtual void HeroesAfterBattle( HeroBase & hero, bool wasAttacking ); virtual void HeroesPostLoad( Heroes & hero ); virtual void HeroesActionComplete( Heroes & hero, int32_t tileIndex, const MP2::MapObjectType objectType ); virtual void HeroesActionNewPosition( Heroes & hero ); virtual void HeroesLevelUp( Heroes & hero ); virtual std::string HeroesString( const Heroes & hero ); virtual void CastleAdd( const Castle & castle ); virtual void CastleRemove( const Castle & castle ); virtual void CastlePreBattle( Castle & castle ); virtual void CastleAfterBattle( Castle & castle, bool attackerWins ); virtual int GetPersonality() const; // To be utilized in future. virtual std::string GetPersonalityString() const; virtual void Reset(); virtual void resetPathfinder() = 0; virtual bool isValidHeroObject( const Heroes & hero, const int32_t index, const bool underHero ) = 0; // Should be called at the beginning of the battle even if no AI-controlled players are // involved in the battle - because of the possibility of using instant or auto battle virtual void battleBegins() = 0; virtual void tradingPostVisitEvent( Kingdom & kingdom ) = 0; protected: int _personality = NONE; Base() = default; private: friend StreamBase & operator<<( StreamBase &, const AI::Base & ); friend StreamBase & operator>>( StreamBase &, AI::Base & ); }; // AI type selector, can be used sometime in the future Base & Get( AI_TYPE type = AI_TYPE::NORMAL ); // Definitions are in the ai_hero_action.cpp void HeroesAction( Heroes & hero, const int32_t dst_index ); void HeroesMove( Heroes & hero ); // Makes it so that the 'hero' casts the Dimension Door spell to the 'targetIndex' void HeroesCastDimensionDoor( Heroes & hero, const int32_t targetIndex ); // Makes it so that the 'hero' casts the Summon Boat spell, summoning the boat at the 'boatDestinationIndex'. // Returns the index of the tile on which the boat was located before the summoning. It's the caller's // responsibility to make sure that 'hero' may cast this spell and there is a summonable boat on the map // before calling this function. int32_t HeroesCastSummonBoat( Heroes & hero, const int32_t boatDestinationIndex ); bool HeroesCastAdventureSpell( Heroes & hero, const Spell & spell ); // Definitions are in the ai_common.cpp // Builds the given building in the given castle if possible. If possible and necessary, obtains the resources // that are missing for construction through trading on the marketplace. Returns true if the construction was // successful, otherwise returns false. bool BuildIfPossible( Castle & castle, const building_t building ); // Builds the given building in the given castle if possible and if there is a sufficient supply of resources // (see the implementation for details). If possible and necessary, obtains the resources that are missing for // construction through trading on the marketplace. Returns true if the construction was successful, otherwise // returns false. bool BuildIfEnoughFunds( Castle & castle, const building_t building, const uint32_t fundsMultiplier ); // Performs the pre-battle arrangement of the given army, see the implementation for details void OptimizeTroopsOrder( Army & army ); // Transfers the slowest troops from the hero's army to the garrison to try to get a movement bonus on the next turn void transferSlowestTroopsToGarrison( Heroes * hero, Castle * castle ); // Calculates a marketplace transaction, after which the kingdom would be able to make a payment in the amount of // at least 'fundsToObtain'. Returns the corresponding transaction if it was found, otherwise returns an empty // result. In order to receive the necessary funds, the returned transaction must be deducted from the funds of // the kingdom. std::optional calculateMarketplaceTransaction( const Kingdom & kingdom, const Funds & fundsToObtain ); // Performs operations on the marketplace necessary for the kingdom to be able to make a payment in the amount // of at least 'fundsToObtain'. If the necessary funds cannot be obtained as a result of trading, then the current // funds of the kingdom remain unchanged. Returns true if the trade was successful, otherwise returns false. bool tradeAtMarketplace( Kingdom & kingdom, const Funds & fundsToObtain ); StreamBase & operator<<( StreamBase &, const AI::Base & ); StreamBase & operator>>( StreamBase &, AI::Base & ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/ai/ai_base.cpp000066400000000000000000000073061456075706000212330ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include "ai.h" #include "heroes_base.h" #include "mp2.h" #include "serialize.h" #include "translations.h" class Castle; class Heroes; namespace AI { int Base::GetPersonality() const { return _personality; } std::string Base::GetPersonalityString() const { switch ( _personality ) { case WARRIOR: return _( "Warrior" ); case BUILDER: return _( "Builder" ); case EXPLORER: return _( "Explorer" ); default: break; } return _( "None" ); } void Base::Reset() { // Do nothing. } void Base::CastlePreBattle( Castle & ) { // Do nothing. } void Base::CastleAfterBattle( Castle &, bool ) { // Do nothing. } void Base::CastleAdd( const Castle & ) { // Do nothing. } void Base::CastleRemove( const Castle & ) { // Do nothing. } void Base::HeroesAdd( const Heroes & ) { // Do nothing. } void Base::HeroesRemove( const Heroes & ) { // Do nothing. } void Base::HeroesBeginMovement( Heroes & /* hero */ ) { // Do nothing. } void Base::HeroesFinishMovement( Heroes & /* hero */ ) { // Do nothing. } void Base::HeroesPreBattle( HeroBase &, bool ) { // Do nothing. } void Base::HeroesAfterBattle( HeroBase & hero, bool /*unused*/ ) { hero.ActionAfterBattle(); } void Base::HeroesActionNewPosition( Heroes & ) { // Do nothing. } std::string Base::HeroesString( const Heroes & ) { return std::string(); } void Base::HeroesActionComplete( Heroes & /*hero*/, const int32_t /* tileIndex*/, const MP2::MapObjectType /*objectType*/ ) { // Do nothing. } void Base::HeroesLevelUp( Heroes & ) { // Do nothing. } void Base::HeroesPostLoad( Heroes & ) { // Do nothing. } StreamBase & operator<<( StreamBase & msg, const AI::Base & instance ) { return msg << instance._personality; } StreamBase & operator>>( StreamBase & msg, AI::Base & instance ) { return msg >> instance._personality; } } fheroes2-1.0.12+dfsg/src/fheroes2/ai/ai_common.cpp000066400000000000000000000330241456075706000216050ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include "ai.h" #include "army.h" #include "army_troop.h" #include "castle.h" #include "color.h" #include "difficulty.h" #include "game.h" #include "heroes.h" #include "kingdom.h" #include "logging.h" #include "normal/ai_normal.h" #include "payment.h" #include "resource.h" #include "resource_trading.h" namespace AI { Base & Get( AI_TYPE /* type */ ) { static AI::Normal normal; return normal; } bool BuildIfPossible( Castle & castle, const building_t building ) { switch ( castle.CheckBuyBuilding( building ) ) { case LACK_RESOURCES: { const Funds payment = PaymentConditions::BuyBuilding( castle.GetRace(), building ); if ( !tradeAtMarketplace( castle.GetKingdom(), payment ) ) { return false; } break; } case ALLOW_BUILD: break; default: return false; } const bool result = castle.BuyBuilding( building ); assert( result ); return result; } bool BuildIfEnoughFunds( Castle & castle, const building_t building, const uint32_t fundsMultiplier ) { if ( fundsMultiplier < 1 || fundsMultiplier > 99 ) { return false; } const Kingdom & kingdom = castle.GetKingdom(); const Funds requiredFunds = PaymentConditions::BuyBuilding( castle.GetRace(), building ) * fundsMultiplier; // Perhaps the kingdom already has the necessary supply of resources if ( !kingdom.AllowPayment( requiredFunds ) ) { // Even if the kingdom does not have the necessary supply of these resources right now, there may be enough resources of another type available to get the // missing resources as a result of resource exchange if ( !calculateMarketplaceTransaction( kingdom, requiredFunds ) ) { return false; } } return BuildIfPossible( castle, building ); } void OptimizeTroopsOrder( Army & army ) { if ( !army.isValid() ) { return; } army.MergeSameMonsterTroops(); std::vector archers; std::vector others; for ( size_t slot = 0; slot < Army::maximumTroopCount; ++slot ) { const Troop * troop = army.GetTroop( slot ); if ( troop && troop->isValid() ) { if ( troop->isArchers() ) { archers.push_back( *troop ); } else { others.push_back( *troop ); } } } // Sort troops by tactical priority. For melee units, the order of comparison is as follows: // 1. Comparison by speed (faster units first); // 2. Comparison by type (flying units first); // 3. Comparison by strength. std::sort( others.begin(), others.end(), []( const Troop & left, const Troop & right ) { if ( left.GetSpeed() != right.GetSpeed() ) { return left.GetSpeed() < right.GetSpeed(); } if ( left.isFlying() != right.isFlying() ) { return right.isFlying(); } return left.GetStrength() < right.GetStrength(); } ); // Archers are sorted solely by strength std::sort( archers.begin(), archers.end(), []( const Troop & left, const Troop & right ) { return left.GetStrength() < right.GetStrength(); } ); std::vector slotOrder = { 2, 1, 3, 0, 4 }; switch ( archers.size() ) { case 1: slotOrder = { 0, 2, 1, 3, 4 }; break; case 2: case 3: slotOrder = { 0, 4, 2, 1, 3 }; break; case 4: slotOrder = { 0, 4, 2, 3, 1 }; break; case 5: slotOrder = { 0, 4, 1, 2, 3 }; break; default: break; } army.Clean(); for ( const size_t slot : slotOrder ) { if ( !archers.empty() ) { army.GetTroop( slot )->Set( archers.back() ); archers.pop_back(); } else if ( !others.empty() ) { army.GetTroop( slot )->Set( others.back() ); others.pop_back(); } else { break; } } if ( Difficulty::allowAIToSplitWeakStacks( Game::getDifficulty() ) ) { // Complicate the task of a potential attacker army.splitStackOfWeakestUnitsIntoFreeSlots(); } } void transferSlowestTroopsToGarrison( Heroes * hero, Castle * castle ) { assert( hero != nullptr && castle != nullptr ); Army & army = hero->GetArmy(); Army & garrison = castle->GetArmy(); // Make efforts to get free slots in the garrison to move troops there garrison.MergeSameMonsterTroops(); std::vector armyTroops; armyTroops.reserve( army.Size() ); for ( size_t i = 0; i < army.Size(); ++i ) { Troop * troop = army.GetTroop( i ); assert( troop != nullptr ); if ( troop->isEmpty() ) { continue; } armyTroops.push_back( troop ); } assert( !armyTroops.empty() ); // Move the slowest units first std::sort( armyTroops.begin(), armyTroops.end(), Army::SlowestTroop ); // At least one of the fastest units should remain in the hero's army armyTroops.pop_back(); for ( Troop * troop : armyTroops ) { if ( !garrison.JoinTroop( *troop ) ) { break; } troop->Reset(); } assert( army.isValid() ); } std::optional calculateMarketplaceTransaction( const Kingdom & kingdom, const Funds & fundsToObtain ) { const uint32_t marketplaceCount = kingdom.GetCountMarketplace(); if ( marketplaceCount == 0 ) { return {}; } std::map plannedBalance; { const Funds fundsDiff = kingdom.GetFunds() - fundsToObtain; Resource::forEach( Resource::ALL, [&plannedBalance, &fundsDiff]( const int res ) { const auto [dummy, inserted] = plannedBalance.try_emplace( res, fundsDiff.Get( res ) ); if ( !inserted ) { assert( 0 ); } } ); assert( !plannedBalance.empty() ); } Funds plannedTransaction; const auto exchangeResources = [marketplaceCount, &plannedBalance, &plannedTransaction]( const int fromRes, const int toRes ) { assert( fromRes != toRes ); auto fromResBalanceIter = plannedBalance.find( fromRes ); auto toResBalanceIter = plannedBalance.find( toRes ); assert( fromResBalanceIter != plannedBalance.end() && toResBalanceIter != plannedBalance.end() ); auto & [fromResDummy, fromResBalanceAmount] = *fromResBalanceIter; auto & [toResDummy, toResBalanceAmount] = *toResBalanceIter; assert( fromResBalanceAmount > 0 && toResBalanceAmount < 0 ); const int32_t tradeCost = fheroes2::getTradeCost( marketplaceCount, fromRes, toRes ); assert( tradeCost > 0 ); int32_t * fromResTransactionAmount = plannedTransaction.GetPtr( fromRes ); int32_t * toResTransactionAmount = plannedTransaction.GetPtr( toRes ); assert( fromResTransactionAmount != nullptr && toResTransactionAmount != nullptr && fromResTransactionAmount != toResTransactionAmount ); // If resources are exchanged for gold, then by giving away one unit of the resource we get several units of gold if ( toRes == Resource::GOLD ) { fromResBalanceAmount -= 1; toResBalanceAmount += tradeCost; // Since the planned transaction must be deducted from the funds of the kingdom, the volume of exchanged resources should be positive, and the volume of // acquired resources should be negative *fromResTransactionAmount += 1; *toResTransactionAmount -= tradeCost; } // Otherwise (when exchanging gold for a resource or a resource for a resource) by giving away several units of a resource, we get only one unit of another // resource else { fromResBalanceAmount -= tradeCost; toResBalanceAmount += 1; // Since the planned transaction must be deducted from the funds of the kingdom, the volume of exchanged resources should be positive, and the volume of // acquired resources should be negative *fromResTransactionAmount += tradeCost; *toResTransactionAmount -= 1; } assert( fromResBalanceAmount >= 0 ); }; for ( const auto & [missingRes, missingResAmount] : plannedBalance ) { if ( missingResAmount >= 0 ) { continue; } while ( missingResAmount < 0 ) { // The exchange of resources takes place one unit at a time. Each time, the resource whose relative stock is currently maximum is selected for exchange // (that is, the resource whose current stock can be exchanged for the maximum amount of the target resource). int resToExchange = Resource::UNKNOWN; int32_t maxPotentialRevenue = 0; for ( const auto & [res, resAmount] : plannedBalance ) { if ( res == missingRes ) { continue; } if ( resAmount <= 0 ) { continue; } const int32_t tradeCost = fheroes2::getTradeCost( marketplaceCount, res, missingRes ); assert( tradeCost > 0 ); const int32_t potentialRevenue = ( missingRes == Resource::GOLD ? resAmount * tradeCost : resAmount / tradeCost ); if ( maxPotentialRevenue < potentialRevenue ) { resToExchange = res; maxPotentialRevenue = potentialRevenue; } } if ( resToExchange == Resource::UNKNOWN ) { return {}; } exchangeResources( resToExchange, missingRes ); } // If we exchange resources for gold, we can get a little more than the amount we need. In other cases, we should get exactly the amount we need. assert( missingRes == Resource::GOLD ? missingResAmount >= 0 : missingResAmount == 0 ); } // If the trade is successful, then we should not have any "debts" in the planned balance... assert( std::none_of( plannedBalance.cbegin(), plannedBalance.cend(), []( const auto & item ) { return ( item.second < 0 ); } ) ); // ... and the kingdom should be able to conduct the planned transaction assert( kingdom.AllowPayment( plannedTransaction ) ); return plannedTransaction; } bool tradeAtMarketplace( Kingdom & kingdom, const Funds & fundsToObtain ) { const std::optional transaction = calculateMarketplaceTransaction( kingdom, fundsToObtain ); if ( !transaction ) { DEBUG_LOG( DBG_AI, DBG_TRACE, Color::String( kingdom.GetColor() ) << " having funds " << kingdom.GetFunds().String() << " failed to obtain funds " << fundsToObtain.String() ) return false; } assert( kingdom.AllowPayment( *transaction ) ); DEBUG_LOG( DBG_AI, DBG_INFO, Color::String( kingdom.GetColor() ) << " having funds " << kingdom.GetFunds().String() << " obtains funds " << fundsToObtain.String() << " through a transaction " << transaction->String() ) kingdom.OddFundsResource( *transaction ); return true; } } fheroes2-1.0.12+dfsg/src/fheroes2/ai/ai_hero_action.cpp000066400000000000000000002346171456075706000226220ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include "ai.h" #include "army.h" #include "army_troop.h" #include "artifact.h" #include "audio_manager.h" #include "battle.h" #include "castle.h" #include "color.h" #include "direction.h" #include "game.h" #include "game_delays.h" #include "game_interface.h" #include "game_static.h" #include "heroes.h" #include "interface_base.h" #include "interface_gamearea.h" #include "kingdom.h" #include "localevent.h" #include "logging.h" #include "m82.h" #include "maps.h" #include "maps_objects.h" #include "maps_tiles.h" #include "maps_tiles_helper.h" #include "math_base.h" #include "monster.h" #include "mp2.h" #include "payment.h" #include "players.h" #include "puzzle.h" #include "race.h" #include "rand.h" #include "resource.h" #include "route.h" #include "screen.h" #include "settings.h" #include "skill.h" #include "spell.h" #include "spell_info.h" #include "visit.h" #include "world.h" namespace { uint32_t AIGetAllianceColors() { // accumulate colors uint32_t colors = 0; if ( Settings::Get().GameType() & Game::TYPE_HOTSEAT ) { const Colors vcolors( Players::HumanColors() ); for ( const int color : vcolors ) { const Player * player = Players::Get( color ); if ( player ) colors |= player->GetFriends(); } } else { const Player * player = Players::Get( Players::HumanColors() ); if ( player ) colors = player->GetFriends(); } return colors; } // Never cache the value of this function as it depends on hero's path and location. bool AIIsShowAnimationForHero( const Heroes & hero, const uint32_t colors ) { if ( Settings::Get().AIMoveSpeed() == 0 ) { return false; } if ( colors == 0 ) { return false; } const int32_t indexFrom = hero.GetIndex(); if ( !Maps::isValidAbsIndex( indexFrom ) ) { return false; } // Show AI hero animation if he is visible (or barely visible) for human player. if ( world.GetTiles( indexFrom ).getFogDirection() != DIRECTION_ALL ) { return true; } const Route::Path & path = hero.GetPath(); // Show AI hero animation if any of the tiles next to the first tile in the path is visible for human player. if ( path.isValidForMovement() && ( world.GetTiles( path.GetFrontIndex() ).getFogDirection() != DIRECTION_ALL ) ) { return true; } return false; } bool AIIsShowAnimationForTile( const Maps::Tiles & tile, const uint32_t colors ) { if ( Settings::Get().AIMoveSpeed() == 0 ) { return false; } if ( colors == 0 ) { return false; } // Show animation on this tile if it is visible (or barely visible) to the human player. return ( tile.getFogDirection() != DIRECTION_ALL ); } int AISelectSkillFromArena( const Heroes & hero ) { switch ( hero.GetRace() ) { case Race::KNGT: if ( hero.GetDefense() < 5 ) { return Skill::Primary::DEFENSE; } if ( hero.GetAttack() < 5 ) { return Skill::Primary::ATTACK; } if ( hero.GetPower() < 3 ) { return Skill::Primary::POWER; } break; case Race::BARB: if ( hero.GetAttack() < 5 ) { return Skill::Primary::ATTACK; } if ( hero.GetDefense() < 5 ) { return Skill::Primary::DEFENSE; } if ( hero.GetPower() < 3 ) { return Skill::Primary::POWER; } break; case Race::SORC: case Race::WZRD: if ( hero.GetPower() < 5 ) { return Skill::Primary::POWER; } if ( hero.GetDefense() < 3 ) { return Skill::Primary::DEFENSE; } if ( hero.GetAttack() < 3 ) { return Skill::Primary::ATTACK; } break; case Race::WRLK: case Race::NECR: if ( hero.GetPower() < 5 ) { return Skill::Primary::POWER; } if ( hero.GetAttack() < 3 ) { return Skill::Primary::ATTACK; } if ( hero.GetDefense() < 3 ) { return Skill::Primary::DEFENSE; } break; default: break; } switch ( Rand::Get( 1, 3 ) ) { case 1: return Skill::Primary::ATTACK; case 2: return Skill::Primary::DEFENSE; case 3: return Skill::Primary::POWER; default: assert( 0 ); break; } return Skill::Primary::UNKNOWN; } bool canMonsterJoinHero( const Troop & troop, Heroes & hero ) { if ( hero.GetArmy().HasMonster( troop.GetID() ) ) { return true; } if ( troop.GetStrength() < hero.getAIMinimumJoiningArmyStrength() ) { // No use to hire such a weak troop. return false; } if ( !hero.GetArmy().mergeWeakestTroopsIfNeeded() ) { // The army has no slots and we cannot rearrange it. return false; } return true; } void AITownPortal( Heroes & hero, const int32_t targetIndex ) { assert( !hero.Modes( Heroes::PATROL ) && Maps::isValidAbsIndex( targetIndex ) ); #if !defined( NDEBUG ) || defined( WITH_DEBUG ) const Castle * targetCastle = world.getCastleEntrance( Maps::GetPoint( targetIndex ) ); #endif assert( targetCastle && targetCastle->GetHero() == nullptr ); const Spell spellToUse = [&hero, targetIndex]() { const Castle * nearestCastle = fheroes2::getNearestCastleTownGate( hero ); assert( nearestCastle != nullptr ); if ( nearestCastle->GetIndex() == targetIndex && hero.CanCastSpell( Spell::TOWNGATE ) ) { return Spell::TOWNGATE; } return Spell::TOWNPORTAL; }(); assert( hero.CanCastSpell( spellToUse ) ); if ( AIIsShowAnimationForHero( hero, AIGetAllianceColors() ) ) { Interface::AdventureMap::Get().getGameArea().SetCenter( hero.GetCenter() ); hero.FadeOut( Game::AIHeroAnimSpeedMultiplier() ); } hero.Scout( targetIndex ); hero.Move2Dest( targetIndex ); hero.SpellCasted( spellToUse ); hero.GetPath().Reset(); if ( AIIsShowAnimationForHero( hero, AIGetAllianceColors() ) ) { Interface::AdventureMap::Get().getGameArea().SetCenter( hero.GetCenter() ); hero.FadeIn( Game::AIHeroAnimSpeedMultiplier() ); } AI::Get().HeroesActionComplete( hero, targetIndex, hero.getObjectTypeUnderHero() ); DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << " used the " << spellToUse.GetName() << " to reach the " << targetCastle->GetName() ) } void AIBattleLose( Heroes & hero, const Battle::Result & res, bool attacker, const fheroes2::Point * centerOn = nullptr, const bool playSound = false ) { const uint32_t reason = attacker ? res.AttackerResult() : res.DefenderResult(); if ( AIIsShowAnimationForHero( hero, AIGetAllianceColors() ) ) { if ( centerOn != nullptr ) { Interface::AdventureMap::Get().getGameArea().SetCenter( *centerOn ); } if ( playSound ) { AudioManager::PlaySound( M82::KILLFADE ); hero.FadeOut(); } else { hero.FadeOut( Game::AIHeroAnimSpeedMultiplier() ); } } hero.Dismiss( reason ); } void AIMeeting( Heroes & left, Heroes & right ) { left.markHeroMeeting( right.GetID() ); right.markHeroMeeting( left.GetID() ); // A hero with a higher role must receive an army and artifacts from another hero. // In case of the same roles a more powerful hero will receive all benefits. bool rightToLeft = true; if ( left.getAIRole() < right.getAIRole() ) { rightToLeft = false; } else if ( left.getAIRole() == right.getAIRole() ) { rightToLeft = right.getStatsValue() < left.getStatsValue(); } Heroes & giver = rightToLeft ? right : left; Heroes & taker = rightToLeft ? left : right; Army & takerArmy = taker.GetArmy(); Army & giverArmy = giver.GetArmy(); // TODO: do not transfer the whole army from one hero to another. Add logic to leave a fast unit for Scout and Courier. Also 3-5 monsters are better than // having 1 Peasant in one stack which leads to an instant death if the hero is attacked by an opponent. takerArmy.JoinStrongestFromArmy( giverArmy ); AI::OptimizeTroopsOrder( takerArmy ); AI::OptimizeTroopsOrder( giverArmy ); taker.GetBagArtifacts().exchangeArtifacts( giver.GetBagArtifacts(), taker, giver ); } void AIToCastle( Heroes & hero, const int32_t dstIndex ) { Castle * castle = world.getCastleEntrance( Maps::GetPoint( dstIndex ) ); if ( castle == nullptr ) { // This should never happen assert( 0 ); DEBUG_LOG( DBG_AI, DBG_WARN, hero.GetName() << " is trying to visit the castle on tile " << dstIndex << ", but there is no entrance to the castle on this tile" ) return; } if ( hero.GetColor() == castle->GetColor() ) { assert( hero.GetIndex() == dstIndex ); DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << " visits " << castle->GetName() ) castle->MageGuildEducateHero( hero ); hero.SetVisited( dstIndex ); return; } if ( hero.isFriends( castle->GetColor() ) ) { // This should never happen - hero should not be able to visit an allied castle assert( 0 ); DEBUG_LOG( DBG_AI, DBG_WARN, hero.GetName() << " is not allowed to visit the allied castle " << castle->GetName() ) return; } const auto captureCastle = [&hero, dstIndex, castle]() { castle->GetKingdom().RemoveCastle( castle ); hero.GetKingdom().AddCastle( castle ); world.CaptureObject( dstIndex, hero.GetColor() ); castle->Scout(); }; Army & army = castle->GetActualArmy(); // Hero is standing in front of the castle, which means that there must be an enemy army in the castle if ( hero.GetIndex() != dstIndex ) { assert( army.isValid() && army.GetColor() != hero.GetColor() ); DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << " attacks the enemy castle " << castle->GetName() ) Heroes * defender = castle->GetHero(); const bool playVanishingHeroSound = defender != nullptr && defender->isControlHuman(); castle->ActionPreBattle(); Battle::Result res = Battle::Loader( hero.GetArmy(), army, dstIndex ); castle->ActionAfterBattle( res.AttackerWins() ); // The defender was defeated if ( !res.DefenderWins() && defender ) { AIBattleLose( *defender, res, false, nullptr, playVanishingHeroSound ); } // The attacker was defeated if ( !res.AttackerWins() ) { AIBattleLose( hero, res, true, &( castle->GetCenter() ), playVanishingHeroSound ); } // The attacker won if ( res.AttackerWins() ) { captureCastle(); hero.IncreaseExperience( res.GetExperienceAttacker() ); } // The defender won else if ( res.DefenderWins() && defender ) { defender->IncreaseExperience( res.GetExperienceDefender() ); } return; } // If hero is already in the castle, then there must be his own army in the castle assert( army.isValid() && army.GetColor() == hero.GetColor() ); DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << " captures the enemy castle " << castle->GetName() ) captureCastle(); castle->MageGuildEducateHero( hero ); hero.SetVisited( dstIndex ); } void AIToHeroes( Heroes & hero, const int32_t dstIndex ) { Heroes * otherHero = world.GetTiles( dstIndex ).getHero(); if ( otherHero == nullptr ) { // This should never happen assert( 0 ); DEBUG_LOG( DBG_AI, DBG_WARN, hero.GetName() << " is trying to meet the hero on tile " << dstIndex << ", but there is no hero on this tile" ) return; } if ( hero.GetColor() == otherHero->GetColor() ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << " meets " << otherHero->GetName() ) AIMeeting( hero, *otherHero ); return; } if ( hero.isFriends( otherHero->GetColor() ) ) { // This should never happen - hero should not be able to meet an allied hero assert( 0 ); DEBUG_LOG( DBG_AI, DBG_WARN, hero.GetName() << " is not allowed to meet the allied hero " << otherHero->GetName() ) return; } if ( otherHero->inCastle() ) { AIToCastle( hero, dstIndex ); return; } DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << " attacks " << otherHero->GetName() ) const bool playVanishingHeroSound = otherHero->isControlHuman(); Battle::Result res = Battle::Loader( hero.GetArmy(), otherHero->GetArmy(), dstIndex ); // The defender was defeated if ( !res.DefenderWins() ) { AIBattleLose( *otherHero, res, false, nullptr, playVanishingHeroSound ); } // The attacker was defeated if ( !res.AttackerWins() ) { AIBattleLose( hero, res, true, ( otherHero->isActive() ? &( otherHero->GetCenter() ) : nullptr ), playVanishingHeroSound ); } // The attacker won if ( res.AttackerWins() ) { hero.IncreaseExperience( res.GetExperienceAttacker() ); } // The defender won else if ( res.DefenderWins() ) { otherHero->IncreaseExperience( res.GetExperienceDefender() ); } } void AIToMonster( Heroes & hero, int32_t dst_index ) { bool destroy = false; Maps::Tiles & tile = world.GetTiles( dst_index ); Troop troop = getTroopFromTile( tile ); const NeutralMonsterJoiningCondition join = Army::GetJoinSolution( hero, tile, troop ); if ( join.reason == NeutralMonsterJoiningCondition::Reason::Alliance ) { if ( hero.GetArmy().CanJoinTroop( troop ) ) { DEBUG_LOG( DBG_AI, DBG_INFO, troop.GetName() << " join " << hero.GetName() << " as a part of alliance" ) hero.GetArmy().JoinTroop( troop ); } else { DEBUG_LOG( DBG_AI, DBG_INFO, troop.GetName() << " unblock way for " << hero.GetName() << " as a part of alliance" ) } destroy = true; } else if ( join.reason == NeutralMonsterJoiningCondition::Reason::Bane ) { DEBUG_LOG( DBG_AI, DBG_INFO, troop.GetName() << " run away from " << hero.GetName() << " as a part of bane" ) destroy = true; } else if ( join.reason == NeutralMonsterJoiningCondition::Reason::Free ) { DEBUG_LOG( DBG_AI, DBG_INFO, troop.GetName() << " join " << hero.GetName() ) // This condition must already be met if a group of monsters wants to join assert( hero.GetArmy().CanJoinTroop( troop ) ); hero.GetArmy().JoinTroop( troop ); destroy = true; } else if ( join.reason == NeutralMonsterJoiningCondition::Reason::ForMoney ) { const int32_t joiningCost = troop.GetTotalCost().gold; DEBUG_LOG( DBG_AI, DBG_INFO, join.monsterCount << " " << troop.GetName() << " join " << hero.GetName() << " for " << joiningCost << " gold" ) // These conditions must already be met if a group of monsters wants to join assert( hero.GetArmy().CanJoinTroop( troop ) && hero.GetKingdom().AllowPayment( Funds( Resource::GOLD, joiningCost ) ) ); hero.GetArmy().JoinTroop( troop.GetMonster(), join.monsterCount, false ); hero.GetKingdom().OddFundsResource( Funds( Resource::GOLD, joiningCost ) ); destroy = true; } else if ( join.reason == NeutralMonsterJoiningCondition::Reason::RunAway ) { DEBUG_LOG( DBG_AI, DBG_INFO, troop.GetName() << " run away from " << hero.GetName() ) // TODO: AI should still chase monsters which it can defeat without losses to get extra experience. destroy = true; } // Fight if ( !destroy ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << " attacks monster " << troop.GetName() ) Army army( tile ); Battle::Result res = Battle::Loader( hero.GetArmy(), army, dst_index ); if ( res.AttackerWins() ) { hero.IncreaseExperience( res.GetExperienceAttacker() ); destroy = true; } else { AIBattleLose( hero, res, true ); // The army can include both the original monsters and their upgraded version const uint32_t monstersLeft = army.getTotalCount(); #ifndef NDEBUG const Monster originalMonster = troop.GetMonster(); const Monster upgradedMonster = originalMonster.GetUpgrade(); if ( upgradedMonster == originalMonster ) { assert( monstersLeft == army.GetCountMonsters( originalMonster ) ); } else { assert( monstersLeft == army.GetCountMonsters( originalMonster ) + army.GetCountMonsters( upgradedMonster ) ); } #endif if ( monstersLeft > 0 ) { setMonsterCountOnTile( tile, monstersLeft ); if ( Maps::isMonsterOnTileJoinConditionFree( tile ) ) { Maps::setMonsterOnTileJoinCondition( tile, Monster::JOIN_CONDITION_MONEY ); } } else { destroy = true; } } } if ( destroy ) { setMonsterCountOnTile( tile, 0 ); removeObjectSprite( tile ); tile.setAsEmpty(); } } void AIToPickupResource( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << ", object: " << MP2::StringObject( objectType ) ) Maps::Tiles & tile = world.GetTiles( dst_index ); if ( objectType != MP2::OBJ_BOTTLE ) { hero.GetKingdom().AddFundsResource( getFundsFromTile( tile ) ); } removeObjectSprite( tile ); resetObjectInfoOnTile( tile ); hero.GetPath().Reset(); } void AIToTreasureChest( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) Maps::Tiles & tile = world.GetTiles( dst_index ); const Funds funds = getFundsFromTile( tile ); assert( funds.gold > 0 || funds.GetValidItemsCount() == 0 ); uint32_t gold = funds.gold; Kingdom & kingdom = hero.GetKingdom(); if ( tile.isWater() ) { if ( gold ) { const Artifact & art = getArtifactFromTile( tile ); if ( art.isValid() && !hero.PickupArtifact( art ) ) gold = GoldInsteadArtifact( objectType ); } } else { const Artifact & art = getArtifactFromTile( tile ); if ( gold ) { const uint32_t experience = gold > 500 ? gold - 500 : 500; const Heroes::Role role = hero.getAIRole(); const int32_t kingdomGold = kingdom.GetFunds().gold; uint32_t chance = 0; if ( role == Heroes::Role::SCOUT || role == Heroes::Role::COURIER ) { // These roles usually don't choose experience. Make AI rich! if ( kingdomGold > 10000 && experience >= 1500 ) { chance = 10; } } else if ( role == Heroes::Role::CHAMPION ) { // If AI is extremely low on gold consider taking it if ( kingdomGold < 3000 ) { // Safeguard the calculation since we're working with unsigned values chance = ( std::max( experience, 500U ) - 500 ) / 15; } else { // Otherwise Champion always picks experience chance = 100; } } else if ( kingdomGold < 3000 ) { chance = ( role == Heroes::Role::FIGHTER && experience >= 1500 ) ? 10 : 0; } else { uint32_t value = ( experience > 500 ) ? experience - 500 : 0; if ( role == Heroes::Role::FIGHTER ) { value += 500; } chance = value / 15; // 33% for every 500 experience } if ( chance ) { const uint32_t randomRoll = Rand::Get( 1, 100 ); if ( randomRoll <= chance ) { gold = 0; hero.IncreaseExperience( experience ); } } } else if ( art.isValid() && !hero.PickupArtifact( art ) ) { gold = GoldInsteadArtifact( objectType ); } } if ( gold ) { kingdom.AddFundsResource( Funds( Resource::GOLD, gold ) ); } removeObjectSprite( tile ); resetObjectInfoOnTile( tile ); } void AIToCaptureObject( Heroes & hero, const MP2::MapObjectType objectType, const int32_t dstIndex ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << " object: " << MP2::StringObject( objectType ) ) #ifndef WITH_DEBUG (void)objectType; #endif Maps::Tiles & tile = world.GetTiles( dstIndex ); if ( !hero.isFriends( getColorFromTile( tile ) ) ) { const auto removeObjectProtection = [&tile]() { // Clear any metadata related to spells if ( tile.GetObject( false ) == MP2::OBJ_MINE ) { removeMineSpellFromTile( tile ); } }; const auto captureObject = [&hero, &tile, &removeObjectProtection]() { removeObjectProtection(); setColorOnTile( tile, hero.GetColor() ); }; if ( isCaptureObjectProtected( tile ) ) { Army army( tile ); Battle::Result result = Battle::Loader( hero.GetArmy(), army, dstIndex ); if ( result.AttackerWins() ) { hero.IncreaseExperience( result.GetExperienceAttacker() ); captureObject(); } else { // The army should include only the original monsters const uint32_t monstersLeft = army.getTotalCount(); assert( monstersLeft == army.GetCountMonsters( getMonsterFromTile( tile ) ) ); Troop & troop = world.GetCapturedObject( dstIndex ).GetTroop(); troop.SetCount( monstersLeft ); // If all the guards are defeated, but the hero has lost the battle, // just remove the protection from the object if ( monstersLeft == 0 ) { removeObjectProtection(); } AIBattleLose( hero, result, true ); } } else { captureObject(); } } } void AIToObjectResource( const Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << ", object: " << MP2::StringObject( objectType ) ) // TODO: remove this temporary assertion and 'defined( NDEBUG )' condition below assert( !MP2::isCaptureObject( objectType ) ); #if defined( NDEBUG ) && !defined( WITH_DEBUG ) (void)objectType; #endif Maps::Tiles & tile = world.GetTiles( dst_index ); hero.GetKingdom().AddFundsResource( getFundsFromTile( tile ) ); resetObjectInfoOnTile( tile ); hero.setVisitedForAllies( dst_index ); } void AIToSkeleton( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) Maps::Tiles & tile = world.GetTiles( dst_index ); if ( doesTileContainValuableItems( tile ) ) { const Artifact & art = getArtifactFromTile( tile ); if ( !hero.PickupArtifact( art ) ) { uint32_t gold = GoldInsteadArtifact( objectType ); hero.GetKingdom().AddFundsResource( Funds( Resource::GOLD, gold ) ); } resetObjectInfoOnTile( tile ); } hero.SetVisitedWideTile( dst_index, objectType, Visit::GLOBAL ); } void AIToWagon( Heroes & hero, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) Maps::Tiles & tile = world.GetTiles( dst_index ); if ( doesTileContainValuableItems( tile ) ) { const Artifact & art = getArtifactFromTile( tile ); if ( art.isValid() ) hero.PickupArtifact( art ); else hero.GetKingdom().AddFundsResource( getFundsFromTile( tile ) ); resetObjectInfoOnTile( tile ); } hero.SetVisited( dst_index, Visit::GLOBAL ); } void AIToFlotSam( const Heroes & hero, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) Maps::Tiles & tile = world.GetTiles( dst_index ); hero.GetKingdom().AddFundsResource( getFundsFromTile( tile ) ); removeObjectSprite( tile ); resetObjectInfoOnTile( tile ); } void AIToSign( Heroes & hero, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) hero.SetVisited( dst_index, Visit::GLOBAL ); } void AIToObservationTower( Heroes & hero, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) Maps::ClearFog( dst_index, GameStatic::getFogDiscoveryDistance( GameStatic::FogDiscoveryType::OBSERVATION_TOWER ), hero.GetColor() ); hero.SetVisited( dst_index, Visit::GLOBAL ); } void AIToMagellanMaps( Heroes & hero, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) const Funds payment( Resource::GOLD, 1000 ); Kingdom & kingdom = hero.GetKingdom(); if ( !hero.isObjectTypeVisited( MP2::OBJ_MAGELLANS_MAPS, Visit::GLOBAL ) && kingdom.AllowPayment( payment ) ) { hero.SetVisited( dst_index, Visit::GLOBAL ); hero.setVisitedForAllies( dst_index ); world.ActionForMagellanMaps( hero.GetColor() ); if ( Players::isFriends( hero.GetColor(), Players::HumanColors() ) ) { // Fully update fog directions if AI player is an ally. Interface::GameArea::updateMapFogDirections(); } kingdom.OddFundsResource( payment ); } } void AIToTeleports( Heroes & hero, const int32_t startIndex ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) assert( hero.GetPath().empty() ); const int32_t indexTo = world.NextTeleport( startIndex ); if ( startIndex == indexTo ) { DEBUG_LOG( DBG_AI, DBG_WARN, "AI hero " << hero.GetName() << " has nowhere to go through stone liths" ) return; } assert( world.GetTiles( indexTo ).GetObject() != MP2::OBJ_HERO ); if ( AIIsShowAnimationForHero( hero, AIGetAllianceColors() ) ) { // AI-controlled hero cannot activate Stone Liths from the same tile, but should move to this tile from some // other tile first, so there is no need to re-center the game area on the hero before his disappearance hero.FadeOut( Game::AIHeroAnimSpeedMultiplier() ); } hero.Scout( indexTo ); hero.Move2Dest( indexTo ); hero.GetPath().Reset(); if ( AIIsShowAnimationForHero( hero, AIGetAllianceColors() ) ) { Interface::AdventureMap::Get().getGameArea().SetCenter( hero.GetCenter() ); hero.FadeIn( Game::AIHeroAnimSpeedMultiplier() ); } hero.ActionNewPosition( false ); } void AIWhirlpoolTroopLoseEffect( Heroes & hero ) { Army & heroArmy = hero.GetArmy(); // Arrange the hero's army for the passage of the whirlpool first heroArmy.ArrangeForWhirlpool(); Troop * weakestTroop = heroArmy.GetWeakestTroop(); assert( weakestTroop != nullptr ); if ( weakestTroop == nullptr ) { return; } // Whirlpool effect affects heroes only with more than one creature in more than one slot if ( heroArmy.GetOccupiedSlotCount() == 1 && weakestTroop->GetCount() == 1 ) { return; } if ( 1 == Rand::Get( 1, 3 ) ) { if ( weakestTroop->GetCount() == 1 ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << " lost " << weakestTroop->GetCount() << " " << weakestTroop->GetName() << " in the whirlpool" ) weakestTroop->Reset(); } else { const uint32_t newCount = Monster::GetCountFromHitPoints( weakestTroop->GetID(), weakestTroop->GetHitPoints() - weakestTroop->GetHitPoints() * Game::GetWhirlpoolPercent() / 100 ); DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << " lost " << weakestTroop->GetCount() - newCount << " " << weakestTroop->GetName() << " in the whirlpool" ) weakestTroop->SetCount( newCount ); } } } void AIToWhirlpools( Heroes & hero, const int32_t startIndex ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) assert( hero.GetPath().empty() ); const int32_t indexTo = world.NextWhirlpool( startIndex ); if ( startIndex == indexTo ) { DEBUG_LOG( DBG_AI, DBG_WARN, "AI hero " << hero.GetName() << " has nowhere to go through the whirlpool" ) return; } if ( AIIsShowAnimationForHero( hero, AIGetAllianceColors() ) ) { // AI-controlled hero cannot activate Whirlpool from the same tile, but should move to this tile from some // other tile first, so there is no need to re-center the game area on the hero before his disappearance hero.FadeOut( Game::AIHeroAnimSpeedMultiplier() ); } hero.Scout( indexTo ); hero.Move2Dest( indexTo ); hero.GetPath().Reset(); AIWhirlpoolTroopLoseEffect( hero ); if ( AIIsShowAnimationForHero( hero, AIGetAllianceColors() ) ) { Interface::AdventureMap::Get().getGameArea().SetCenter( hero.GetCenter() ); hero.FadeIn( Game::AIHeroAnimSpeedMultiplier() ); } hero.ActionNewPosition( false ); } void AIToPrimarySkillObject( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << ", object: " << MP2::StringObject( objectType ) ) const Maps::Tiles & tile = world.GetTiles( dst_index ); int skill = Skill::Primary::UNKNOWN; switch ( objectType ) { case MP2::OBJ_FORT: skill = Skill::Primary::DEFENSE; break; case MP2::OBJ_MERCENARY_CAMP: skill = Skill::Primary::ATTACK; break; case MP2::OBJ_WITCH_DOCTORS_HUT: skill = Skill::Primary::KNOWLEDGE; break; case MP2::OBJ_STANDING_STONES: skill = Skill::Primary::POWER; break; case MP2::OBJ_ARENA: skill = AISelectSkillFromArena( hero ); break; default: break; } if ( ( MP2::OBJ_ARENA == objectType && !hero.isObjectTypeVisited( objectType ) ) || !hero.isVisited( tile ) ) { // increase skill hero.IncreasePrimarySkill( skill ); hero.SetVisited( dst_index ); // fix double action tile hero.SetVisitedWideTile( dst_index, objectType ); } } void AIToExperienceObject( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << ", object: " << MP2::StringObject( objectType ) ) const Maps::Tiles & tile = world.GetTiles( dst_index ); uint32_t exp = 0; switch ( objectType ) { case MP2::OBJ_GAZEBO: exp = 1000; break; default: break; } // check already visited if ( !hero.isVisited( tile ) && exp ) { hero.SetVisited( dst_index ); hero.IncreaseExperience( exp ); } } void AIToWitchsHut( Heroes & hero, const int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) const Skill::Secondary & skill = getSecondarySkillFromWitchsHut( world.GetTiles( dst_index ) ); if ( skill.isValid() ) { if ( !hero.HasMaxSecondarySkill() && !hero.HasSecondarySkill( skill.Skill() ) ) { hero.LearnSkill( skill ); if ( skill.Skill() == Skill::Secondary::SCOUTING ) { hero.Scout( hero.GetIndex() ); } } } else { // A broken object? assert( 0 ); } // It is important to mark it globally so other heroes will know about the object. hero.SetVisited( dst_index, Visit::GLOBAL ); } void AIToShrine( Heroes & hero, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) const Spell & spell = getSpellFromTile( world.GetTiles( dst_index ) ); assert( spell.isValid() ); if ( hero.HaveSpellBook() && !hero.HaveSpell( spell, true ) && // check valid level spell and wisdom skill !( 3 == spell.Level() && Skill::Level::NONE == hero.GetLevelSkill( Skill::Secondary::WISDOM ) ) ) { hero.AppendSpellToBook( spell ); } // All heroes will know which spell is here. hero.SetVisited( dst_index, Visit::GLOBAL ); } void AIToGoodLuckObject( Heroes & hero, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) hero.SetVisited( dst_index ); } void AIToGoodMoraleObject( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << ", object: " << MP2::StringObject( objectType ) ) uint32_t move = 0; switch ( objectType ) { case MP2::OBJ_OASIS: move = GameStatic::getMovementPointBonus( MP2::OBJ_OASIS ); break; case MP2::OBJ_WATERING_HOLE: move = GameStatic::getMovementPointBonus( MP2::OBJ_WATERING_HOLE ); break; default: break; } // check already visited if ( !hero.isObjectTypeVisited( objectType ) ) { // modify morale hero.SetVisited( dst_index ); if ( move ) hero.IncreaseMovePoints( move ); // fix double action tile hero.SetVisitedWideTile( dst_index, objectType ); } } void AIToMagicWell( Heroes & hero, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) const uint32_t max = hero.GetMaxSpellPoints(); if ( hero.GetSpellPoints() < max && !hero.isObjectTypeVisited( MP2::OBJ_MAGIC_WELL ) ) { hero.SetSpellPoints( max ); } hero.SetVisited( dst_index ); } void AIToArtesianSpring( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) const uint32_t max = hero.GetMaxSpellPoints(); if ( !world.isAnyKingdomVisited( objectType, dst_index ) && hero.GetSpellPoints() < max * 2 ) { hero.SetSpellPoints( max * 2 ); } hero.SetVisitedWideTile( dst_index, objectType, Visit::GLOBAL ); } void AIToXanadu( Heroes & hero, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) const Maps::Tiles & tile = world.GetTiles( dst_index ); if ( !hero.isVisited( tile ) && GameStatic::isHeroWorthyToVisitXanadu( hero ) ) { hero.IncreasePrimarySkill( Skill::Primary::ATTACK ); hero.IncreasePrimarySkill( Skill::Primary::DEFENSE ); hero.IncreasePrimarySkill( Skill::Primary::KNOWLEDGE ); hero.IncreasePrimarySkill( Skill::Primary::POWER ); hero.SetVisited( dst_index ); } } void AIToEvent( Heroes & hero, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) MapEvent * event_maps = world.GetMapEvent( Maps::GetPoint( dst_index ) ); if ( event_maps && event_maps->isAllow( hero.GetColor() ) && event_maps->computer ) { if ( event_maps->resources.GetValidItemsCount() ) hero.GetKingdom().AddFundsResource( event_maps->resources ); if ( event_maps->artifact.isValid() ) hero.PickupArtifact( event_maps->artifact ); event_maps->SetVisited( hero.GetColor() ); if ( event_maps->cancel ) { hero.setObjectTypeUnderHero( MP2::OBJ_NONE ); world.RemoveMapObject( event_maps ); } } } void AIToUpgradeArmyObject( Heroes & hero, const MP2::MapObjectType objectType, int32_t /*dst_index*/ ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << ", object: " << MP2::StringObject( objectType ) ) switch ( objectType ) { case MP2::OBJ_HILL_FORT: if ( hero.GetArmy().HasMonster( Monster::DWARF ) ) hero.GetArmy().UpgradeMonsters( Monster::DWARF ); if ( hero.GetArmy().HasMonster( Monster::ORC ) ) hero.GetArmy().UpgradeMonsters( Monster::ORC ); if ( hero.GetArmy().HasMonster( Monster::OGRE ) ) hero.GetArmy().UpgradeMonsters( Monster::OGRE ); break; case MP2::OBJ_FREEMANS_FOUNDRY: if ( hero.GetArmy().HasMonster( Monster::PIKEMAN ) ) hero.GetArmy().UpgradeMonsters( Monster::PIKEMAN ); if ( hero.GetArmy().HasMonster( Monster::SWORDSMAN ) ) hero.GetArmy().UpgradeMonsters( Monster::SWORDSMAN ); if ( hero.GetArmy().HasMonster( Monster::IRON_GOLEM ) ) hero.GetArmy().UpgradeMonsters( Monster::IRON_GOLEM ); break; default: break; } } void AIToPoorMoraleObject( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << ", object: " << MP2::StringObject( objectType ) ) assert( hero.GetIndex() == dst_index || MP2::isNeedStayFront( objectType ) ); if ( !AI::Get().isValidHeroObject( hero, dst_index, ( hero.GetIndex() == dst_index ) ) ) { // If we can't step on this tile, then we shouldn't be here at all assert( !MP2::isNeedStayFront( objectType ) ); // We're just passing through here, don't mess with this object DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << " passes through without interacting with the object" ) return; } Maps::Tiles & tile = world.GetTiles( dst_index ); const Funds funds = getFundsFromTile( tile ); assert( funds.gold > 0 || funds.GetValidItemsCount() == 0 ); uint32_t gold = funds.gold; bool complete = false; if ( gold ) { Army army( tile ); Battle::Result res = Battle::Loader( hero.GetArmy(), army, dst_index ); if ( res.AttackerWins() ) { hero.IncreaseExperience( res.GetExperienceAttacker() ); complete = true; Artifact art; if ( isArtifactObject( objectType ) ) { art = getArtifactFromTile( tile ); } if ( art.isValid() && !hero.PickupArtifact( art ) ) { gold = GoldInsteadArtifact( objectType ); } hero.GetKingdom().AddFundsResource( Funds( Resource::GOLD, gold ) ); } else { AIBattleLose( hero, res, true ); } } if ( complete ) { resetObjectInfoOnTile( tile ); } else if ( 0 == gold && !hero.isObjectTypeVisited( objectType ) ) { // Modify morale hero.SetVisited( dst_index ); hero.SetVisited( dst_index, Visit::GLOBAL ); } } void AIToPyramid( Heroes & hero, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) assert( hero.GetIndex() == dst_index ); if ( !AI::Get().isValidHeroObject( hero, dst_index, ( hero.GetIndex() == dst_index ) ) ) { // We're just passing through here, don't mess with this object DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << " passes through without interacting with the object" ) return; } Maps::Tiles & tile = world.GetTiles( dst_index ); const Spell & spell = getSpellFromTile( tile ); if ( spell.isValid() ) { // battle Army army( tile ); Battle::Result res = Battle::Loader( hero.GetArmy(), army, dst_index ); if ( res.AttackerWins() ) { hero.IncreaseExperience( res.GetExperienceAttacker() ); // check magic book if ( hero.HaveSpellBook() && // check skill level for wisdom Skill::Level::EXPERT == hero.GetLevelSkill( Skill::Secondary::WISDOM ) ) { hero.AppendSpellToBook( spell ); } resetObjectInfoOnTile( tile ); hero.SetVisited( dst_index, Visit::GLOBAL ); } else { AIBattleLose( hero, res, true ); } } else { hero.SetVisited( dst_index, Visit::LOCAL ); hero.SetVisited( dst_index, Visit::GLOBAL ); } } void AIToObelisk( Heroes & hero, const Maps::Tiles & tile ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) if ( !hero.isVisited( tile, Visit::GLOBAL ) ) { hero.SetVisited( tile.GetIndex(), Visit::GLOBAL ); Kingdom & kingdom = hero.GetKingdom(); kingdom.PuzzleMaps().Update( kingdom.CountVisitedObjects( MP2::OBJ_OBELISK ), world.CountObeliskOnMaps() ); // TODO: once any piece of the puzzle is open and the area is discovered and a hero has nothing to do make him dig around this area. // TODO: once all pieces are open add a special condition to make AI to dig the Ultimate artifact. // TODO: do not dig for an Ultimate artifact if the winning condition of the map is to find this artifact. } } void AIToTreeKnowledge( Heroes & hero, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) const Maps::Tiles & tile = world.GetTiles( dst_index ); if ( !hero.isVisited( tile ) ) { const Funds & payment = getTreeOfKnowledgeRequirement( tile ); if ( 0 == payment.GetValidItemsCount() || hero.GetKingdom().AllowPayment( payment ) ) { const int level = hero.GetLevel(); assert( level > 0 ); const uint32_t experience = Heroes::GetExperienceFromLevel( level ) - Heroes::GetExperienceFromLevel( level - 1 ); hero.GetKingdom().OddFundsResource( payment ); hero.SetVisited( dst_index ); hero.IncreaseExperience( experience ); } } } void AIToDaemonCave( Heroes & hero, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) assert( hero.GetIndex() == dst_index ); if ( !AI::Get().isValidHeroObject( hero, dst_index, ( hero.GetIndex() == dst_index ) ) ) { // We're just passing through here, don't mess with this object DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << " passes through without interacting with the object" ) return; } Maps::Tiles & tile = world.GetTiles( dst_index ); if ( doesTileContainValuableItems( tile ) ) { Army army( tile ); Battle::Result res = Battle::Loader( hero.GetArmy(), army, dst_index ); if ( res.AttackerWins() ) { hero.IncreaseExperience( res.GetExperienceAttacker() ); // Daemon Cave always gives 2500 Gold after a battle hero.GetKingdom().AddFundsResource( Funds( Resource::GOLD, 2500 ) ); } else { AIBattleLose( hero, res, true ); } resetObjectInfoOnTile( tile ); } } void AIToDwellingJoinMonster( Heroes & hero, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) Maps::Tiles & tile = world.GetTiles( dst_index ); const Troop & troop = getTroopFromTile( tile ); if ( !troop.isValid() ) { return; } if ( !canMonsterJoinHero( troop, hero ) ) { return; } if ( !hero.GetArmy().JoinTroop( troop ) ) { // How is it possible that an army has free slots but monsters cannot join it? assert( 0 ); return; } setMonsterCountOnTile( tile, 0 ); } void AIToDwellingRecruitMonster( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << ", object: " << MP2::StringObject( objectType ) ) Maps::Tiles & tile = world.GetTiles( dst_index ); const Troop & troop = getTroopFromTile( tile ); if ( !troop.isValid() ) { return; } Kingdom & kingdom = hero.GetKingdom(); const Funds singleMonsterCost = troop.GetCost(); uint32_t recruitTroopCount = kingdom.GetFunds().getLowestQuotient( singleMonsterCost ); if ( recruitTroopCount <= 0 ) { // We do not have resources to hire even a single creature. return; } const uint32_t availableTroopCount = troop.GetCount(); if ( recruitTroopCount > availableTroopCount ) { recruitTroopCount = availableTroopCount; } const Troop troopToHire{ troop.GetID(), recruitTroopCount }; if ( !canMonsterJoinHero( troopToHire, hero ) ) { return; } if ( !hero.GetArmy().JoinTroop( troopToHire ) ) { // How is it possible that an army has free slots but monsters cannot join it? assert( 0 ); return; } setMonsterCountOnTile( tile, availableTroopCount - recruitTroopCount ); kingdom.OddFundsResource( troopToHire.GetTotalCost() ); // Remove genie lamp sprite if no genies are available to hire. if ( MP2::OBJ_GENIE_LAMP == objectType && ( availableTroopCount == recruitTroopCount ) ) { removeObjectSprite( tile ); tile.setAsEmpty(); } } void AIToDwellingBattleMonster( Heroes & hero, const MP2::MapObjectType objectType, const int32_t tileIndex ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << ", object: " << MP2::StringObject( objectType ) ) assert( hero.GetIndex() == tileIndex ); if ( !AI::Get().isValidHeroObject( hero, tileIndex, ( hero.GetIndex() == tileIndex ) ) ) { // We're just passing through here, don't mess with this object DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << " passes through without interacting with the object" ) return; } Maps::Tiles & tile = world.GetTiles( tileIndex ); bool recruitmentAllowed = true; if ( getColorFromTile( tile ) == Color::NONE ) { Army army( tile ); const Battle::Result res = Battle::Loader( hero.GetArmy(), army, tileIndex ); if ( res.AttackerWins() ) { hero.IncreaseExperience( res.GetExperienceAttacker() ); // Set ownership of the dwelling to a Neutral (gray) player so that any player can recruit troops without a fight. setColorOnTile( tile, Color::UNUSED ); tile.SetObjectPassable( true ); } else { AIBattleLose( hero, res, true ); recruitmentAllowed = false; } } if ( recruitmentAllowed ) { const Troop troop = getTroopFromTile( tile ); if ( troop.isValid() ) { AIToDwellingRecruitMonster( hero, objectType, tileIndex ); } hero.SetVisited( tileIndex, Visit::GLOBAL ); } } void AIToStables( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) if ( !hero.isObjectTypeVisited( objectType ) ) { hero.SetVisited( dst_index ); hero.IncreaseMovePoints( GameStatic::getMovementPointBonus( MP2::OBJ_STABLES ) ); } if ( hero.GetArmy().HasMonster( Monster::CAVALRY ) ) { hero.GetArmy().UpgradeMonsters( Monster::CAVALRY ); } } void AIToAbandonedMine( Heroes & hero, const int32_t dstIndex ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) assert( hero.GetIndex() == dstIndex ); if ( !AI::Get().isValidHeroObject( hero, dstIndex, ( hero.GetIndex() == dstIndex ) ) ) { // We're just passing through here, don't mess with this object DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << " passes through without interacting with the object" ) return; } Maps::Tiles & tile = world.GetTiles( dstIndex ); Army army( tile ); Battle::Result result = Battle::Loader( hero.GetArmy(), army, dstIndex ); if ( result.AttackerWins() ) { hero.IncreaseExperience( result.GetExperienceAttacker() ); Maps::restoreAbandonedMine( tile, Resource::GOLD ); hero.setObjectTypeUnderHero( MP2::OBJ_MINE ); setColorOnTile( tile, hero.GetColor() ); } else { AIBattleLose( hero, result, true ); } } void AIToBarrier( const Heroes & hero, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) Maps::Tiles & tile = world.GetTiles( dst_index ); const Kingdom & kingdom = hero.GetKingdom(); if ( kingdom.IsVisitTravelersTent( getColorFromTile( tile ) ) ) { removeObjectSprite( tile ); tile.setAsEmpty(); } } void AIToTravellersTent( const Heroes & hero, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) const Maps::Tiles & tile = world.GetTiles( dst_index ); Kingdom & kingdom = hero.GetKingdom(); kingdom.SetVisitTravelersTent( getColorFromTile( tile ) ); } void AIToShipwreckSurvivor( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) Maps::Tiles & tile = world.GetTiles( dst_index ); if ( hero.IsFullBagArtifacts() ) hero.GetKingdom().AddFundsResource( Funds( Resource::GOLD, GoldInsteadArtifact( objectType ) ) ); else hero.PickupArtifact( getArtifactFromTile( tile ) ); removeObjectSprite( tile ); resetObjectInfoOnTile( tile ); } void AIToArtifact( Heroes & hero, int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) Maps::Tiles & tile = world.GetTiles( dst_index ); if ( !hero.IsFullBagArtifacts() ) { const Maps::ArtifactCaptureCondition condition = getArtifactCaptureCondition( tile ); const Artifact art = getArtifactFromTile( tile ); bool result = false; if ( condition == Maps::ArtifactCaptureCondition::PAY_2000_GOLD || condition == Maps::ArtifactCaptureCondition::PAY_2500_GOLD_AND_3_RESOURCES || condition == Maps::ArtifactCaptureCondition::PAY_3000_GOLD_AND_5_RESOURCES ) { const Funds payment = getArtifactResourceRequirement( tile ); if ( hero.GetKingdom().AllowPayment( payment ) ) { result = true; hero.GetKingdom().OddFundsResource( payment ); } } else if ( condition == Maps::ArtifactCaptureCondition::HAVE_WISDOM_SKILL || condition == Maps::ArtifactCaptureCondition::HAVE_LEADERSHIP_SKILL ) { // TODO: do we need to check this condition? result = true; } else if ( condition >= Maps::ArtifactCaptureCondition::FIGHT_50_ROGUES && condition <= Maps::ArtifactCaptureCondition::FIGHT_1_BONE_DRAGON ) { Army army( tile ); // new battle Battle::Result res = Battle::Loader( hero.GetArmy(), army, dst_index ); if ( res.AttackerWins() ) { hero.IncreaseExperience( res.GetExperienceAttacker() ); result = true; } else { AIBattleLose( hero, res, true ); } } else { result = true; } if ( result && hero.PickupArtifact( art ) ) { removeObjectSprite( tile ); resetObjectInfoOnTile( tile ); } } } void AIToBoat( Heroes & hero, const int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) if ( hero.isShipMaster() ) { return; } hero.setLastGroundRegion( world.GetTiles( hero.GetIndex() ).GetRegion() ); const fheroes2::Point offset( Maps::GetPoint( dst_index ) - hero.GetCenter() ); // Get the direction of the boat so that the direction of the hero can be set to it after boarding const int boatDirection = world.GetTiles( dst_index ).getBoatDirection(); if ( AIIsShowAnimationForHero( hero, AIGetAllianceColors() ) ) { Interface::AdventureMap::Get().getGameArea().SetCenter( hero.GetCenter() ); hero.FadeOut( Game::AIHeroAnimSpeedMultiplier(), offset ); } hero.Scout( dst_index ); hero.Move2Dest( dst_index ); hero.ResetMovePoints(); hero.GetPath().Reset(); // Set the direction of the hero to the one of the boat as the boat does not move when boarding it hero.setDirection( boatDirection ); hero.setObjectTypeUnderHero( MP2::OBJ_NONE ); world.GetTiles( dst_index ).resetObjectSprite(); hero.SetShipMaster( true ); // Boat is no longer empty so we reset color to default world.GetTiles( dst_index ).resetBoatOwnerColor(); } void AIToCoast( Heroes & hero, const int32_t dst_index ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) if ( !hero.isShipMaster() ) { return; } const int fromIndex = hero.GetIndex(); Maps::Tiles & from = world.GetTiles( fromIndex ); // Calculate the offset before making the action. const fheroes2::Point prevPos = hero.GetCenter(); const fheroes2::Point offset( Maps::GetPoint( dst_index ) - prevPos ); hero.Scout( dst_index ); hero.Move2Dest( dst_index ); hero.ResetMovePoints(); hero.GetPath().Reset(); from.setBoat( Maps::GetDirection( fromIndex, dst_index ), hero.GetColor() ); hero.SetShipMaster( false ); if ( AIIsShowAnimationForHero( hero, AIGetAllianceColors() ) ) { Interface::AdventureMap::Get().getGameArea().SetCenter( prevPos ); hero.FadeIn( Game::AIHeroAnimSpeedMultiplier(), offset ); } hero.ActionNewPosition( true ); } void AIToJail( const Heroes & hero, const int32_t tileIndex ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) const Kingdom & kingdom = hero.GetKingdom(); if ( kingdom.GetHeroes().size() < Kingdom::GetMaxHeroes() ) { Maps::Tiles & tile = world.GetTiles( tileIndex ); removeObjectSprite( tile ); tile.setAsEmpty(); Heroes * prisoner = world.FromJailHeroes( tileIndex ); if ( prisoner ) { prisoner->Recruit( hero.GetColor(), Maps::GetPoint( tileIndex ) ); AI::OptimizeTroopsOrder( prisoner->GetArmy() ); } } } void AIToHutMagi( Heroes & hero, const MP2::MapObjectType objectType, const int32_t tileIndex ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) if ( !hero.isObjectTypeVisited( objectType, Visit::GLOBAL ) ) { hero.SetVisited( tileIndex, Visit::GLOBAL ); const MapsIndexes eyeMagiIndexes = Maps::GetObjectPositions( MP2::OBJ_EYE_OF_MAGI ); const uint32_t distance = GameStatic::getFogDiscoveryDistance( GameStatic::FogDiscoveryType::MAGI_EYES ); for ( const int32_t index : eyeMagiIndexes ) { Maps::ClearFog( index, distance, hero.GetColor() ); } } } void AIToAlchemistTower( Heroes & hero ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) BagArtifacts & bag = hero.GetBagArtifacts(); const uint32_t cursed = static_cast( std::count_if( bag.begin(), bag.end(), []( const Artifact & art ) { return art.containsCurses(); } ) ); if ( cursed == 0 ) { return; } const Funds payment = PaymentConditions::ForAlchemist(); if ( hero.GetKingdom().AllowPayment( payment ) ) { hero.GetKingdom().OddFundsResource( payment ); for ( Artifact & artifact : bag ) { if ( artifact.containsCurses() ) { artifact = Artifact::UNKNOWN; } } } DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << " removed " << cursed << " artifacts" ) } void AIToSirens( Heroes & hero, const MP2::MapObjectType objectType, const int32_t objectIndex ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) if ( hero.isObjectTypeVisited( objectType ) ) { return; } const uint32_t experience = hero.GetArmy().ActionToSirens(); DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << " got " << experience << " experience" ) hero.IncreaseExperience( experience ); hero.SetVisited( objectIndex ); } void AIToTradingPost( const Heroes & hero ) { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() ) AI::Get().tradingPostVisitEvent( hero.GetKingdom() ); } } namespace AI { void HeroesAction( Heroes & hero, const int32_t dst_index ) { const Heroes::AIHeroMeetingUpdater heroMeetingUpdater( hero ); const Maps::Tiles & tile = world.GetTiles( dst_index ); const MP2::MapObjectType objectType = tile.GetObject( dst_index != hero.GetIndex() ); const bool isActionObject = MP2::isActionObject( objectType, hero.isShipMaster() ); if ( isActionObject ) hero.SetModes( Heroes::ACTION ); switch ( objectType ) { case MP2::OBJ_BOAT: AIToBoat( hero, dst_index ); break; case MP2::OBJ_COAST: // Coast is not an action object by definition but we need to do hero's animation. AIToCoast( hero, dst_index ); break; case MP2::OBJ_MONSTER: AIToMonster( hero, dst_index ); break; case MP2::OBJ_HERO: AIToHeroes( hero, dst_index ); break; case MP2::OBJ_CASTLE: AIToCastle( hero, dst_index ); break; // pickup object case MP2::OBJ_BOTTLE: case MP2::OBJ_CAMPFIRE: case MP2::OBJ_RESOURCE: AIToPickupResource( hero, objectType, dst_index ); break; case MP2::OBJ_SEA_CHEST: case MP2::OBJ_TREASURE_CHEST: AIToTreasureChest( hero, objectType, dst_index ); break; case MP2::OBJ_ARTIFACT: AIToArtifact( hero, dst_index ); break; case MP2::OBJ_LEAN_TO: case MP2::OBJ_MAGIC_GARDEN: case MP2::OBJ_WINDMILL: case MP2::OBJ_WATER_WHEEL: AIToObjectResource( hero, objectType, dst_index ); break; case MP2::OBJ_WAGON: AIToWagon( hero, dst_index ); break; case MP2::OBJ_SKELETON: AIToSkeleton( hero, objectType, dst_index ); break; case MP2::OBJ_FLOTSAM: AIToFlotSam( hero, dst_index ); break; case MP2::OBJ_ALCHEMIST_LAB: case MP2::OBJ_MINE: case MP2::OBJ_SAWMILL: case MP2::OBJ_LIGHTHOUSE: AIToCaptureObject( hero, objectType, dst_index ); break; case MP2::OBJ_ABANDONED_MINE: AIToAbandonedMine( hero, dst_index ); break; case MP2::OBJ_SHIPWRECK_SURVIVOR: AIToShipwreckSurvivor( hero, objectType, dst_index ); break; // event case MP2::OBJ_EVENT: AIToEvent( hero, dst_index ); break; case MP2::OBJ_SIGN: AIToSign( hero, dst_index ); break; // increase view case MP2::OBJ_OBSERVATION_TOWER: AIToObservationTower( hero, dst_index ); break; case MP2::OBJ_MAGELLANS_MAPS: AIToMagellanMaps( hero, dst_index ); break; // teleports case MP2::OBJ_STONE_LITHS: AIToTeleports( hero, dst_index ); break; case MP2::OBJ_WHIRLPOOL: AIToWhirlpools( hero, dst_index ); break; // primary skill modification case MP2::OBJ_FORT: case MP2::OBJ_MERCENARY_CAMP: case MP2::OBJ_WITCH_DOCTORS_HUT: case MP2::OBJ_STANDING_STONES: AIToPrimarySkillObject( hero, objectType, dst_index ); break; // experience modification case MP2::OBJ_GAZEBO: AIToExperienceObject( hero, objectType, dst_index ); break; // Witch's hut case MP2::OBJ_WITCHS_HUT: AIToWitchsHut( hero, dst_index ); break; // shrine circle case MP2::OBJ_SHRINE_FIRST_CIRCLE: case MP2::OBJ_SHRINE_SECOND_CIRCLE: case MP2::OBJ_SHRINE_THIRD_CIRCLE: AIToShrine( hero, dst_index ); break; // luck modification case MP2::OBJ_FOUNTAIN: case MP2::OBJ_FAERIE_RING: case MP2::OBJ_IDOL: case MP2::OBJ_MERMAID: AIToGoodLuckObject( hero, dst_index ); break; // morale modification case MP2::OBJ_OASIS: case MP2::OBJ_TEMPLE: case MP2::OBJ_WATERING_HOLE: case MP2::OBJ_BUOY: AIToGoodMoraleObject( hero, objectType, dst_index ); break; case MP2::OBJ_OBELISK: AIToObelisk( hero, tile ); break; // magic point case MP2::OBJ_ARTESIAN_SPRING: AIToArtesianSpring( hero, objectType, dst_index ); break; case MP2::OBJ_MAGIC_WELL: AIToMagicWell( hero, dst_index ); break; // increase skill case MP2::OBJ_XANADU: AIToXanadu( hero, dst_index ); break; case MP2::OBJ_HILL_FORT: case MP2::OBJ_FREEMANS_FOUNDRY: AIToUpgradeArmyObject( hero, objectType, dst_index ); break; case MP2::OBJ_SHIPWRECK: case MP2::OBJ_GRAVEYARD: case MP2::OBJ_DERELICT_SHIP: AIToPoorMoraleObject( hero, objectType, dst_index ); break; case MP2::OBJ_PYRAMID: AIToPyramid( hero, dst_index ); break; case MP2::OBJ_DAEMON_CAVE: AIToDaemonCave( hero, dst_index ); break; case MP2::OBJ_TREE_OF_KNOWLEDGE: AIToTreeKnowledge( hero, dst_index ); break; // accept army case MP2::OBJ_WATCH_TOWER: case MP2::OBJ_EXCAVATION: case MP2::OBJ_CAVE: case MP2::OBJ_TREE_HOUSE: case MP2::OBJ_ARCHER_HOUSE: case MP2::OBJ_GOBLIN_HUT: case MP2::OBJ_DWARF_COTTAGE: case MP2::OBJ_HALFLING_HOLE: case MP2::OBJ_PEASANT_HUT: AIToDwellingJoinMonster( hero, dst_index ); break; // recruit army case MP2::OBJ_RUINS: case MP2::OBJ_TREE_CITY: case MP2::OBJ_WAGON_CAMP: case MP2::OBJ_DESERT_TENT: // loyalty version objects case MP2::OBJ_WATER_ALTAR: case MP2::OBJ_AIR_ALTAR: case MP2::OBJ_FIRE_ALTAR: case MP2::OBJ_EARTH_ALTAR: case MP2::OBJ_BARROW_MOUNDS: AIToDwellingRecruitMonster( hero, objectType, dst_index ); break; // recruit army (battle) case MP2::OBJ_DRAGON_CITY: case MP2::OBJ_CITY_OF_DEAD: case MP2::OBJ_TROLL_BRIDGE: AIToDwellingBattleMonster( hero, objectType, dst_index ); break; // recruit genie case MP2::OBJ_GENIE_LAMP: AIToDwellingRecruitMonster( hero, objectType, dst_index ); break; case MP2::OBJ_STABLES: AIToStables( hero, objectType, dst_index ); break; case MP2::OBJ_ARENA: AIToPrimarySkillObject( hero, objectType, dst_index ); break; case MP2::OBJ_BARRIER: AIToBarrier( hero, dst_index ); break; case MP2::OBJ_TRAVELLER_TENT: AIToTravellersTent( hero, dst_index ); break; case MP2::OBJ_JAIL: AIToJail( hero, dst_index ); break; case MP2::OBJ_HUT_OF_MAGI: AIToHutMagi( hero, objectType, dst_index ); break; case MP2::OBJ_ALCHEMIST_TOWER: AIToAlchemistTower( hero ); break; case MP2::OBJ_TRADING_POST: AIToTradingPost( hero ); break; // AI has no advantage or knowledge to use these objects case MP2::OBJ_ORACLE: case MP2::OBJ_EYE_OF_MAGI: case MP2::OBJ_SPHINX: break; case MP2::OBJ_SIRENS: // AI must have some action even if it goes on this object by mistake. AIToSirens( hero, objectType, dst_index ); break; default: // AI should know what to do with this type of action object! Please add logic for it. assert( !isActionObject ); break; } if ( MP2::isNeedStayFront( objectType ) ) hero.GetPath().Reset(); // Ignore empty tiles if ( isActionObject ) { AI::Get().HeroesActionComplete( hero, dst_index, objectType ); } } void HeroesMove( Heroes & hero ) { const Route::Path & path = hero.GetPath(); if ( path.isValidForTeleportation() ) { const int32_t targetIndex = path.GetFrontIndex(); // At the moment, this kind of movement using paths generated by AI pathfinder is only possible // to the towns/castles with the use of Town Gate or Town Portal spells assert( path.GetFrontFrom() != targetIndex && world.GetTiles( targetIndex ).GetObject() == MP2::OBJ_CASTLE ); AITownPortal( hero, targetIndex ); // This is the end of a this hero's movement, even if the hero's full path doesn't end in this town or castle. // The further path of this hero will be re-planned by AI. return; } if ( !path.isValidForMovement() ) { return; } hero.SetMove( true ); Interface::AdventureMap & adventureMapInterface = Interface::AdventureMap::Get(); Interface::GameArea & gameArea = adventureMapInterface.getGameArea(); const Settings & conf = Settings::Get(); const uint32_t colors = AIGetAllianceColors(); bool recenterNeeded = true; int heroAnimationFrameCount = 0; fheroes2::Point heroAnimationOffset; int heroAnimationSpriteId = 0; const bool hideAIMovements = ( conf.AIMoveSpeed() == 0 ); const bool noMovementAnimation = ( conf.AIMoveSpeed() == 10 ); const std::vector delayTypes = { Game::CURRENT_AI_DELAY, Game::MAPS_DELAY }; fheroes2::Display & display = fheroes2::Display::instance(); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents( !hideAIMovements && Game::isDelayNeeded( delayTypes ) ) ) { if ( !hero.isActive() || !hero.isMoveEnabled() ) { break; } if ( hideAIMovements || !AIIsShowAnimationForHero( hero, colors ) ) { hero.Move( true ); recenterNeeded = true; // Render a frame only if there is a need to show one. if ( Game::validateAnimationDelay( Game::MAPS_DELAY ) ) { // Update Adventure Map objects' animation. Game::updateAdventureMapAnimationIndex(); adventureMapInterface.redraw( Interface::REDRAW_GAMEAREA ); // If this assertion blows up it means that we are holding a RedrawLocker lock for rendering which should not happen. assert( adventureMapInterface.getRedrawMask() == 0 ); display.render(); } } else if ( Game::validateAnimationDelay( Game::CURRENT_AI_DELAY ) ) { // re-center in case hero appears from the fog if ( recenterNeeded ) { gameArea.SetCenter( hero.GetCenter() ); recenterNeeded = false; } bool resetHeroSprite = false; if ( heroAnimationFrameCount > 0 ) { const int32_t heroMovementSkipValue = Game::AIHeroAnimSpeedMultiplier(); gameArea.ShiftCenter( { heroAnimationOffset.x * heroMovementSkipValue, heroAnimationOffset.y * heroMovementSkipValue } ); gameArea.SetRedraw(); heroAnimationFrameCount -= heroMovementSkipValue; if ( ( heroAnimationFrameCount & 0x3 ) == 0 ) { // % 4 hero.SetSpriteIndex( heroAnimationSpriteId ); if ( heroAnimationFrameCount == 0 ) resetHeroSprite = true; else ++heroAnimationSpriteId; } const int offsetStep = ( ( 4 - ( heroAnimationFrameCount & 0x3 ) ) & 0x3 ); // % 4 hero.SetOffset( { heroAnimationOffset.x * offsetStep, heroAnimationOffset.y * offsetStep } ); } if ( heroAnimationFrameCount == 0 ) { if ( resetHeroSprite ) { hero.SetSpriteIndex( heroAnimationSpriteId - 1 ); } if ( hero.Move( noMovementAnimation ) ) { if ( AIIsShowAnimationForHero( hero, colors ) ) { gameArea.SetCenter( hero.GetCenter() ); } } else { const fheroes2::Point movement( hero.MovementDirection() ); if ( movement != fheroes2::Point() ) { // don't waste resources for no movement const int32_t heroMovementSkipValue = Game::AIHeroAnimSpeedMultiplier(); heroAnimationOffset = movement; gameArea.ShiftCenter( movement ); heroAnimationFrameCount = 32 - heroMovementSkipValue; heroAnimationSpriteId = hero.GetSpriteIndex(); if ( heroMovementSkipValue < 4 ) { hero.SetSpriteIndex( heroAnimationSpriteId - 1 ); hero.SetOffset( { heroAnimationOffset.x * heroMovementSkipValue, heroAnimationOffset.y * heroMovementSkipValue } ); } else { ++heroAnimationSpriteId; } } } } if ( Game::validateAnimationDelay( Game::MAPS_DELAY ) ) { // Update Adventure Map objects' animation. Game::updateAdventureMapAnimationIndex(); } adventureMapInterface.redraw( Interface::REDRAW_GAMEAREA ); // If this assertion blows up it means that we are holding a RedrawLocker lock for rendering which should not happen. assert( adventureMapInterface.getRedrawMask() == 0 ); display.render(); } } hero.SetMove( false ); } void HeroesCastDimensionDoor( Heroes & hero, const int32_t targetIndex ) { if ( !Maps::isValidAbsIndex( targetIndex ) || hero.isShipMaster() != world.GetTiles( targetIndex ).isWater() ) { return; } const Spell dimensionDoor( Spell::DIMENSIONDOOR ); if ( !hero.CanCastSpell( dimensionDoor ) ) { // How is it even possible to call this function if the hero does not have this spell? assert( 0 ); return; } if ( AIIsShowAnimationForHero( hero, AIGetAllianceColors() ) ) { Interface::AdventureMap::Get().getGameArea().SetCenter( hero.GetCenter() ); hero.FadeOut( Game::AIHeroAnimSpeedMultiplier() ); } hero.Scout( targetIndex ); hero.Move2Dest( targetIndex ); hero.SpellCasted( dimensionDoor ); hero.setDimensionDoorUsage( hero.getDimensionDoorUses() + 1 ); hero.GetPath().Reset(); if ( AIIsShowAnimationForHero( hero, AIGetAllianceColors() ) ) { Interface::AdventureMap::Get().getGameArea().SetCenter( hero.GetCenter() ); hero.FadeIn( Game::AIHeroAnimSpeedMultiplier() ); } hero.ActionNewPosition( false ); DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << " moved to " << targetIndex ) } int32_t HeroesCastSummonBoat( Heroes & hero, const int32_t boatDestinationIndex ) { assert( !hero.isShipMaster() && Maps::isValidAbsIndex( boatDestinationIndex ) ); const Spell summonBoat( Spell::SUMMONBOAT ); assert( hero.CanCastSpell( summonBoat ) ); const int32_t boatSource = fheroes2::getSummonableBoat( hero ); // Player should have a summonable boat before calling this function. assert( Maps::isValidAbsIndex( boatSource ) ); hero.SpellCasted( summonBoat ); const int heroColor = hero.GetColor(); Interface::GameArea & gameArea = Interface::AdventureMap::Get().getGameArea(); Maps::Tiles & tileSource = world.GetTiles( boatSource ); if ( AIIsShowAnimationForTile( tileSource, AIGetAllianceColors() ) ) { gameArea.SetCenter( Maps::GetPoint( boatSource ) ); gameArea.runSingleObjectAnimation( std::make_shared( tileSource.GetObjectUID(), boatSource, MP2::OBJ_BOAT ) ); } else { removeObjectSprite( tileSource ); tileSource.setAsEmpty(); } Maps::Tiles & tileDest = world.GetTiles( boatDestinationIndex ); assert( tileDest.GetObject() == MP2::OBJ_NONE ); tileDest.setBoat( Direction::RIGHT, heroColor ); tileSource.resetBoatOwnerColor(); if ( AIIsShowAnimationForTile( tileDest, AIGetAllianceColors() ) ) { gameArea.SetCenter( Maps::GetPoint( boatDestinationIndex ) ); gameArea.runSingleObjectAnimation( std::make_shared( tileDest.GetObjectUID(), boatDestinationIndex, MP2::OBJ_BOAT ) ); } DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << " summoned the boat from " << boatSource << " to " << boatDestinationIndex ) return boatSource; } bool HeroesCastAdventureSpell( Heroes & hero, const Spell & spell ) { if ( !hero.CanCastSpell( spell ) ) { return false; } hero.SpellCasted( spell ); return true; } } fheroes2-1.0.12+dfsg/src/fheroes2/ai/normal/000077500000000000000000000000001456075706000204265ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/fheroes2/ai/normal/ai_normal.cpp000066400000000000000000000203471456075706000231010ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "ai_normal.h" #include #include #include "army.h" #include "heroes.h" #include "kingdom.h" #include "maps_tiles.h" #include "pairs.h" #include "profit.h" #include "rand.h" #include "resource.h" #include "route.h" #include "world.h" namespace AI { Normal::Normal() : _pathfinder( ARMY_ADVANTAGE_LARGE ) { _personality = Rand::Get( AI::WARRIOR, AI::EXPLORER ); } void Normal::resetPathfinder() { _pathfinder.reset(); } void Normal::revealFog( const Maps::Tiles & tile, const Kingdom & kingdom ) { const MP2::MapObjectType object = tile.GetObject(); if ( !MP2::isActionObject( object ) ) { return; } updateMapActionObjectCache( tile.GetIndex() ); updatePriorityAttackTarget( kingdom, tile ); // If this is an action object and one of AI heroes is moving, // we have to stop him because the new object might be more valuable than the current target. const VecHeroes & heroes = kingdom.GetHeroes(); for ( Heroes * hero : heroes ) { if ( hero == nullptr ) { // How is it even possible? assert( 0 ); continue; } if ( hero->isMoveEnabled() ) { hero->GetPath().Truncate(); break; } } } double Normal::getTargetArmyStrength( const Maps::Tiles & tile, const MP2::MapObjectType objectType ) { if ( !isMonsterStrengthCacheable( objectType ) ) { return Army( tile ).GetStrength(); } const int32_t tileId = tile.GetIndex(); auto iter = _neutralMonsterStrengthCache.find( tileId ); if ( iter != _neutralMonsterStrengthCache.end() ) { // Cache hit. return iter->second; } auto newEntry = _neutralMonsterStrengthCache.emplace( tileId, Army( tile ).GetStrength() ); return newEntry.first->second; } double Normal::getResourcePriorityModifier( const int resource, const bool isMine ) const { // Not all resources are equally valuable: 1 gold does not have the same value as 1 gemstone, so we need to // normalize the value of various resources. // For mines, let's determine the default relative priority based on the ratio of the amount of resources // extracted by these mines. For example, if a gold mine produces 1000 gold per day, an ore mine produces 2 // units of ore per day, and a gem mine produces 1 gem per day, then the priority of one unit of ore will // correspond to the priority of 500 gold, and the priority of one gemstone will correspond to the priority // of 1000 gold. Evaluate the resources from mines in proportion to the amount of resources that these mines // bring in 2 days (mines should be more valuable than just resource piles). static const std::map minePriorities = []() { std::map result; const double goldMineIncome = ProfitConditions::FromMine( Resource::GOLD ).Get( Resource::GOLD ); assert( goldMineIncome > 0 ); Resource::forEach( Resource::ALL, [&result, goldMineIncome]( const int res ) { const int32_t resMineIncome = ProfitConditions::FromMine( res ).Get( res ); assert( resMineIncome > 0 ); if ( resMineIncome <= 0 ) { return; } result[res] = goldMineIncome / resMineIncome * 2; } ); return result; }(); // For one-time resource sources (such as piles, chests, campfires and so on), let's determine the default // relative priority based on the ratio of the usual amount of resources in these sources. static const std::map pilePriorities = { // The amount of gold on the map is usually ~500-1500 { Resource::GOLD, 1 }, // The amount of wood and ore on the map is usually ~5-10 { Resource::WOOD, 125 }, { Resource::ORE, 125 }, // The amount of other resources on the map is usually ~2-5 { Resource::MERCURY, 250 }, { Resource::SULFUR, 250 }, { Resource::CRYSTAL, 250 }, { Resource::GEMS, 250 } }; const std::map & resourcePriorities = isMine ? minePriorities : pilePriorities; double prio = 1.0; const auto prioIter = resourcePriorities.find( resource ); if ( prioIter != resourcePriorities.end() ) { prio = prioIter->second; } else { // This function has been called for an unknown resource, this should never happen assert( 0 ); } for ( const BudgetEntry & budget : _budget ) { if ( budget.resource != resource ) { continue; } if ( budget.recurringCost ) { prio *= 1.5; } return ( budget.priority ) ? prio * 2.0 : prio; } return prio; } double Normal::getFundsValueBasedOnPriority( const Funds & funds ) const { double value = 0; Resource::forEach( funds.GetValidItems(), [this, &funds, &value]( const int res ) { const int amount = funds.Get( res ); if ( amount <= 0 ) { return; } value += amount * getResourcePriorityModifier( res, false ); } ); return value; } void Normal::updateMapActionObjectCache( const int mapIndex ) { const Maps::Tiles & tile = world.GetTiles( mapIndex ); const MP2::MapObjectType objectType = tile.GetObject(); auto iter = std::lower_bound( _mapActionObjects.begin(), _mapActionObjects.end(), IndexObject{ mapIndex, objectType }, []( const IndexObject & left, const IndexObject & right ) { return left.first < right.first; } ); if ( iter != _mapActionObjects.end() && iter->first == mapIndex ) { if ( MP2::isActionObject( objectType ) ) { iter->second = objectType; } else { _mapActionObjects.erase( iter ); } } else if ( MP2::isActionObject( objectType ) ) { _mapActionObjects.emplace( iter, IndexObject{ mapIndex, objectType } ); } } } fheroes2-1.0.12+dfsg/src/fheroes2/ai/normal/ai_normal.h000066400000000000000000000344061456075706000225470ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2AI_NORMAL_H #define H2AI_NORMAL_H #include #include #include #include #include #include #include #include "ai.h" #include "color.h" #include "mp2.h" #include "pairs.h" #include "resource.h" #include "world_pathfinding.h" class Castle; class HeroBase; class Heroes; class Kingdom; class Spell; namespace Battle { class Actions; class Arena; class Position; class Unit; class Units; } namespace Maps { class Tiles; } struct VecHeroes; struct VecCastles; namespace AI { // TODO: this structure is not being updated during AI heroes' actions. struct RegionStats { bool evaluated = false; double highestThreat = -1; int friendlyHeroes = 0; int friendlyCastles = 0; int enemyCastles = 0; int safetyFactor = 0; int spellLevel = 2; }; struct AICastle { Castle * castle = nullptr; bool underThreat = false; int safetyFactor = 0; int buildingValue = 0; AICastle( Castle * inCastle, bool inThreat, int inSafety, int inValue ) : castle( inCastle ) , underThreat( inThreat ) , safetyFactor( inSafety ) , buildingValue( inValue ) { assert( castle != nullptr ); } }; struct BudgetEntry { int resource = Resource::UNKNOWN; int missing = 0; bool priority = false; bool recurringCost = false; BudgetEntry( int type ) : resource( type ) {} void reset() { missing = 0; priority = false; recurringCost = false; } }; struct HeroToMove { Heroes * hero = nullptr; int patrolCenter = -1; uint32_t patrolDistance = 0; }; struct EnemyArmy { EnemyArmy() = default; EnemyArmy( const int32_t index_, const Heroes * hero_, const double strength_, const uint32_t movePoints_ ) : index( index_ ) , hero( hero_ ) , strength( strength_ ) , movePoints( movePoints_ ) {} int32_t index{ -1 }; const Heroes * hero = nullptr; double strength{ 0 }; uint32_t movePoints{ 0 }; }; struct PriorityTask { PriorityTask() = default; explicit PriorityTask( PriorityTaskType taskType ) : type( taskType ) {} PriorityTask( PriorityTaskType taskType, int secondaryTask ) : type( taskType ) { secondaryTaskTileId.insert( secondaryTask ); } PriorityTaskType type{ PriorityTaskType::ATTACK }; std::set secondaryTaskTileId; }; struct BattleTargetPair { int cell = -1; const Battle::Unit * unit = nullptr; }; struct SpellSelection { int spellID = -1; int32_t cell = -1; double value = 0.0; }; struct SpellcastOutcome { int32_t cell = -1; double value = 0.0; void updateOutcome( const double potentialValue, const int32_t targetCell, const bool isMassEffect = false ) { if ( isMassEffect ) { value += potentialValue; } else if ( potentialValue > value ) { value = potentialValue; cell = targetCell; } } }; class BattlePlanner { public: // Should be called at the beginning of the battle void battleBegins(); // Checks whether the limit of turns is exceeded for the attacking AI-controlled // hero and inserts an appropriate action to the action list if necessary bool isLimitOfTurnsExceeded( const Battle::Arena & arena, Battle::Actions & actions ); Battle::Actions planUnitTurn( Battle::Arena & arena, const Battle::Unit & currentUnit ); private: void analyzeBattleState( const Battle::Arena & arena, const Battle::Unit & currentUnit ); static Battle::Actions berserkTurn( Battle::Arena & arena, const Battle::Unit & currentUnit ); Battle::Actions archerDecision( Battle::Arena & arena, const Battle::Unit & currentUnit ) const; BattleTargetPair meleeUnitOffense( Battle::Arena & arena, const Battle::Unit & currentUnit ) const; BattleTargetPair meleeUnitDefense( Battle::Arena & arena, const Battle::Unit & currentUnit ) const; SpellSelection selectBestSpell( Battle::Arena & arena, const Battle::Unit & currentUnit, bool retreating ) const; SpellcastOutcome spellDamageValue( const Spell & spell, Battle::Arena & arena, const Battle::Unit & currentUnit, const Battle::Units & friendly, const Battle::Units & enemies, bool retreating ) const; SpellcastOutcome spellDispelValue( const Spell & spell, const Battle::Units & friendly, const Battle::Units & enemies ) const; SpellcastOutcome spellResurrectValue( const Spell & spell, const Battle::Arena & arena ) const; SpellcastOutcome spellSummonValue( const Spell & spell, const Battle::Arena & arena, const int heroColor ) const; SpellcastOutcome spellEffectValue( const Spell & spell, const Battle::Units & targets ) const; double spellEffectValue( const Spell & spell, const Battle::Unit & target, bool targetIsLast, bool forDispel ) const; double getSpellDisruptingRayRatio( const Battle::Unit & target ) const; double getSpellSlowRatio( const Battle::Unit & target ) const; double getSpellHasteRatio( const Battle::Unit & target ) const; int32_t spellDurationMultiplier( const Battle::Unit & target ) const; bool isPositionLocatedInDefendedArea( const Battle::Unit & currentUnit, const Battle::Position & pos ) const; bool isUnitFaster( const Battle::Unit & currentUnit, const Battle::Unit & target ) const; bool isCommanderCanSpellcast( const Battle::Arena & arena, const HeroBase * commander ) const; static double commanderMaximumSpellDamageValue( const HeroBase & commander ); // When this limit of turns without deaths is exceeded for an attacking AI-controlled hero, // the auto battle should be interrupted (one way or another) static const uint32_t MAX_TURNS_WITHOUT_DEATHS = 50; // Member variables related to the logic of checking the limit of the number of turns uint32_t _currentTurnNumber = 0; uint32_t _numberOfRemainingTurnsWithoutDeaths = MAX_TURNS_WITHOUT_DEATHS; uint32_t _attackerForceNumberOfDead = 0; uint32_t _defenderForceNumberOfDead = 0; // Member variables with a lifetime in one turn const HeroBase * _commander = nullptr; int _myColor = Color::NONE; double _myArmyStrength = 0; double _enemyArmyStrength = 0; double _myShootersStrength = 0; double _enemyShootersStrength = 0; double _myRangedUnitsOnly = 0; double _enemyRangedUnitsOnly = 0; double _myArmyAverageSpeed = 0; double _enemyAverageSpeed = 0; double _enemySpellStrength = 0; bool _attackingCastle = false; bool _defendingCastle = false; bool _considerRetreat = false; bool _defensiveTactics = false; bool _cautiousOffensive = false; }; class Normal : public Base { public: Normal(); void KingdomTurn( Kingdom & kingdom ) override; void BattleTurn( Battle::Arena & arena, const Battle::Unit & currentUnit, Battle::Actions & actions ) override; void revealFog( const Maps::Tiles & tile, const Kingdom & kingdom ) override; // Implements the logic of transparent casting of the Summon Boat spell at the beginning of the hero's movement void HeroesBeginMovement( Heroes & hero ) override; void HeroesPreBattle( HeroBase & hero, bool isAttacking ) override; void HeroesActionComplete( Heroes & hero, const int32_t tileIndex, const MP2::MapObjectType objectType ) override; // Implements the logic of transparent casting of the Summon Boat spell during the hero's movement void HeroesActionNewPosition( Heroes & hero ) override; void CastlePreBattle( Castle & castle ) override; bool recruitHero( Castle & castle, bool buyArmy, bool underThreat ); void reinforceHeroInCastle( Heroes & hero, Castle & castle, const Funds & budget ); void evaluateRegionSafety(); std::vector getSortedCastleList( const VecCastles & castles, const std::set & castlesInDanger ); void resetPathfinder() override; bool isValidHeroObject( const Heroes & hero, const int32_t index, const bool underHero ) override; void battleBegins() override; void tradingPostVisitEvent( Kingdom & kingdom ) override; double getObjectValue( const Heroes & hero, const int index, const int objectType, const double valueToIgnore, const uint32_t distanceToObject ) const; double getTargetArmyStrength( const Maps::Tiles & tile, const MP2::MapObjectType objectType ); bool isPriorityTask( const int32_t index ) const { return _priorityTargets.find( index ) != _priorityTargets.end(); } bool isCriticalTask( const int32_t index ) const { const auto iter = _priorityTargets.find( index ); if ( iter == _priorityTargets.end() ) { return false; } return iter->second.type == PriorityTaskType::ATTACK || iter->second.type == PriorityTaskType::DEFEND; } private: void CastleTurn( Castle & castle, const bool defensiveStrategy ); // Returns true if heroes can still do tasks but they have no move points. bool HeroesTurn( VecHeroes & heroes, const uint32_t startProgressValue, const uint32_t endProgressValue ); int getPriorityTarget( const HeroToMove & heroInfo, double & maxPriority ); double getGeneralObjectValue( const Heroes & hero, const int index, const double valueToIgnore, const uint32_t distanceToObject ) const; double getFighterObjectValue( const Heroes & hero, const int index, const double valueToIgnore, const uint32_t distanceToObject ) const; double getCourierObjectValue( const Heroes & hero, const int index, const double valueToIgnore, const uint32_t distanceToObject ) const; double getScoutObjectValue( const Heroes & hero, const int index, const double valueToIgnore, const uint32_t distanceToObject ) const; int getCourierMainTarget( const Heroes & hero, const AIWorldPathfinder & pathfinder, double lowestPossibleValue ) const; double getResourcePriorityModifier( const int resource, const bool isMine ) const; double getFundsValueBasedOnPriority( const Funds & funds ) const; void updatePriorityTargets( Heroes & hero, const int32_t tileIndex, const MP2::MapObjectType objectType ); void updateKingdomBudget( const Kingdom & kingdom ); bool purchaseNewHeroes( const std::vector & sortedCastleList, const std::set & castlesInDanger, const int32_t availableHeroCount, const bool moreTasksForHeroes ); static bool isMonsterStrengthCacheable( const MP2::MapObjectType objectType ) { return objectType == MP2::OBJ_MONSTER; } void updateMapActionObjectCache( const int mapIndex ); std::set findCastlesInDanger( const Kingdom & kingdom ); void updatePriorityForEnemyArmy( const Kingdom & kingdom, const EnemyArmy & enemyArmy ); void updatePriorityForCastle( const Castle & castle ); // Return true if the castle is in danger. // IMPORTANT!!! Do not call this method directly. Use other methods which call it internally. bool updateIndividualPriorityForCastle( const Castle & castle, const EnemyArmy & enemyArmy ); void removePriorityAttackTarget( const int32_t tileIndex ); void updatePriorityAttackTarget( const Kingdom & kingdom, const Maps::Tiles & tile ); // The following member variables should not be saved or serialized std::vector _mapActionObjects; std::map _priorityTargets; std::map _enemyArmies; std::vector _regions; std::array _budget = { Resource::WOOD, Resource::MERCURY, Resource::ORE, Resource::SULFUR, Resource::CRYSTAL, Resource::GEMS, Resource::GOLD }; AIWorldPathfinder _pathfinder; BattlePlanner _battlePlanner; // Monster strength is constant over the same turn for AI but its calculation is a heavy operation. // In order to avoid extra computations during AI turn it is important to keep cache of monster strength but update it when an action on a monster is taken. std::map _neutralMonsterStrengthCache; }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/ai/normal/ai_normal_battle.cpp000066400000000000000000002515561456075706000244440ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ai.h" #include "ai_normal.h" #include "army.h" #include "artifact.h" #include "artifact_info.h" #include "battle.h" #include "battle_arena.h" #include "battle_army.h" #include "battle_board.h" #include "battle_cell.h" #include "battle_command.h" #include "battle_tower.h" #include "battle_troop.h" #include "castle.h" #include "color.h" #include "difficulty.h" #include "game.h" #include "heroes.h" #include "heroes_base.h" #include "kingdom.h" #include "logging.h" #include "monster_info.h" #include "resource.h" #include "settings.h" #include "speed.h" #include "spell.h" using namespace Battle; namespace AI { const std::vector cellsUnderWallsIndexes = { 7, 28, 49, 72, 95 }; struct MeleeAttackOutcome { int32_t fromIndex = -1; double attackValue = INT32_MIN; double positionValue = INT32_MIN; bool canAttackImmediately = false; }; bool ValueHasImproved( double primary, double primaryMax, double secondary, double secondaryMax ) { return primaryMax < primary || ( secondaryMax < secondary && std::fabs( primaryMax - primary ) < 0.001 ); } bool IsOutcomeImproved( const MeleeAttackOutcome & newOutcome, const MeleeAttackOutcome & previous ) { // Composite priority criteria: // Primary - whether the enemy unit can be attacked during the current turn // Secondary - position value // Tertiary - enemy unit's threat return ( newOutcome.canAttackImmediately && !previous.canAttackImmediately ) || ( newOutcome.canAttackImmediately == previous.canAttackImmediately && ValueHasImproved( newOutcome.positionValue, previous.positionValue, newOutcome.attackValue, previous.attackValue ) ); } int32_t doubleCellAttackValue( const Unit & attacker, const Unit & target, const int32_t from, const int32_t targetCell ) { const Cell * behind = Board::GetCell( targetCell, Board::GetDirection( from, targetCell ) ); const Unit * secondaryTarget = ( behind != nullptr ) ? behind->GetUnit() : nullptr; if ( secondaryTarget && secondaryTarget->GetUID() != target.GetUID() && secondaryTarget->GetUID() != attacker.GetUID() ) { return secondaryTarget->evaluateThreatForUnit( attacker ); } return 0; } std::pair optimalAttackVector( const Unit & attacker, const Unit & target, const Position & attackPos ) { assert( attackPos.isValidForUnit( attacker ) ); assert( Board::CanAttackTargetFromPosition( attacker, target, attackPos.GetHead()->GetIndex() ) ); const Position & targetPos = target.GetPosition(); const std::array attackCells = { attackPos.GetHead(), attackPos.GetTail() }; const std::array targetCells = { targetPos.GetHead(), targetPos.GetTail() }; std::pair bestAttackVector{ -1, UNKNOWN }; double bestAttackValue = 0.0; for ( const Cell * attackCell : attackCells ) { if ( attackCell == nullptr ) { continue; } const int32_t attackCellIdx = attackCell->GetIndex(); if ( !Board::CanAttackFromCell( attacker, attackCellIdx ) ) { continue; } for ( const Cell * targetCell : targetCells ) { if ( targetCell == nullptr ) { continue; } const int32_t targetCellIdx = targetCell->GetIndex(); if ( !Board::isNearIndexes( attackCellIdx, targetCellIdx ) ) { continue; } if ( !attacker.isDoubleCellAttack() ) { return { targetCellIdx, Board::GetDirection( attackCellIdx, targetCellIdx ) }; } const double attackValue = doubleCellAttackValue( attacker, target, attackCellIdx, targetCellIdx ); if ( bestAttackVector.first == -1 || bestAttackValue < attackValue ) { bestAttackVector = { targetCellIdx, Board::GetDirection( attackCellIdx, targetCellIdx ) }; bestAttackValue = attackValue; } } } return bestAttackVector; } int32_t optimalAttackValue( const Unit & attacker, const Unit & target, const Position & attackPos ) { assert( attackPos.isValidForUnit( attacker ) ); if ( attacker.isAllAdjacentCellsAttack() ) { const Board * board = Arena::GetBoard(); std::set unitsUnderAttack; for ( const int32_t index : Board::GetAroundIndexes( attackPos ) ) { const Unit * unit = board->at( index ).GetUnit(); if ( unit == nullptr || unit->GetColor() == attacker.GetCurrentColor() ) { continue; } unitsUnderAttack.insert( unit ); } return std::accumulate( unitsUnderAttack.begin(), unitsUnderAttack.end(), static_cast( 0 ), [&attacker]( const int32_t total, const Unit * unit ) { return total + unit->evaluateThreatForUnit( attacker ); } ); } int32_t attackValue = target.evaluateThreatForUnit( attacker ); // A double cell attack should only be considered if the attacker is actually able to attack the target from the given attack position. Otherwise, the attacker // can at least block the target if the target is a shooter, so this position can be valuable in any case. if ( attacker.isDoubleCellAttack() && Board::CanAttackTargetFromPosition( attacker, target, attackPos.GetHead()->GetIndex() ) ) { const auto [attackTargetIdx, attackDirection] = optimalAttackVector( attacker, target, attackPos ); assert( Board::isValidDirection( attackTargetIdx, Board::GetReflectDirection( attackDirection ) ) ); attackValue += doubleCellAttackValue( attacker, target, Board::GetIndexDirection( attackTargetIdx, Board::GetReflectDirection( attackDirection ) ), attackTargetIdx ); } return attackValue; } using PositionValues = std::map; PositionValues evaluatePotentialAttackPositions( Arena & arena, const Unit & attacker ) { // Attacking unit can be under the influence of the Hypnotize spell Units enemies( arena.getEnemyForce( attacker.GetCurrentColor() ).getUnits(), Units::REMOVE_INVALID_UNITS_AND_SPECIFIED_UNIT, &attacker ); // For each position near enemy units, select the maximum attack value among neighboring enemy melee units, and then add the sum of the attack values of // neighboring enemy archers to encourage the use of attacking positions that block these archers std::sort( enemies.begin(), enemies.end(), []( const Unit * unit1, const Unit * unit2 ) { return !unit1->isArchers() && unit2->isArchers(); } ); PositionValues result; for ( const Unit * enemyUnit : enemies ) { assert( enemyUnit != nullptr && enemyUnit->isValid() ); std::set processedPositions; // Wide attacker can occupy positions from which it is able to block or attack several units at once, even if there are not one but two cells between // these units, e.g. like this: // // | | | |U| // | |A|A| | // |U| | | | // // It is necessary to correctly evaluate such a position as a position located "nearby" in relation to both units. for ( const int32_t idx : Board::GetDistanceIndexes( *enemyUnit, attacker.isWide() ? 2 : 1 ) ) { const Position pos = Position::GetPosition( attacker, idx ); if ( pos.GetHead() == nullptr ) { continue; } assert( pos.isValidForUnit( attacker ) ); const uint32_t dist = Board::GetDistance( pos, enemyUnit->GetPosition() ); assert( dist > 0 ); if ( dist != 1 ) { continue; } if ( !arena.isPositionReachable( attacker, pos, false ) ) { continue; } const auto [dummy, inserted] = processedPositions.insert( pos ); if ( !inserted ) { continue; } const int32_t attackValue = optimalAttackValue( attacker, *enemyUnit, pos ); const auto iter = result.find( pos ); if ( iter == result.end() ) { result.try_emplace( pos, attackValue ); } // If attacker is able to attack all adjacent cells, then the values of all units in adjacent cells (including archers) have already been taken into // account else if ( attacker.isAllAdjacentCellsAttack() ) { assert( iter->second == attackValue ); } else if ( enemyUnit->isArchers() ) { iter->second += attackValue; } else { iter->second = std::max( iter->second, attackValue ); } } } return result; } bool isUnitAbleToApproachPosition( const Unit * unit, const Position & pos ) { assert( unit != nullptr ); // Also consider the next turn, even if this unit has already acted during the current turn const uint32_t speed = unit->GetSpeed( false, true ); // Immovable unit is not taken into account, even if it is already near the given position if ( speed == Speed::STANDING ) { return false; } for ( const int32_t nearbyIdx : Board::GetAroundIndexes( pos ) ) { const Position nearbyPos = Position::GetReachable( *unit, nearbyIdx, speed ); if ( nearbyPos.GetHead() == nullptr ) { continue; } assert( nearbyPos.isValidForUnit( unit ) ); return true; } return false; } MeleeAttackOutcome BestAttackOutcome( const Unit & attacker, const Unit & defender, const PositionValues & valuesOfAttackPositions, const std::function & posFilter = {} ) { MeleeAttackOutcome bestOutcome; std::vector aroundDefender; aroundDefender.reserve( valuesOfAttackPositions.size() ); for ( const auto & [pos, dummy] : valuesOfAttackPositions ) { const uint32_t dist = Board::GetDistance( pos, defender.GetPosition() ); assert( dist > 0 ); if ( dist != 1 ) { continue; } aroundDefender.push_back( pos ); } // Prefer the positions closest to the current attacker's position std::sort( aroundDefender.begin(), aroundDefender.end(), [&attacker]( const Position & pos1, const Position & pos2 ) { return ( Board::GetDistance( attacker.GetPosition(), pos1 ) < Board::GetDistance( attacker.GetPosition(), pos2 ) ); } ); // Pick the best position to attack from for ( const Position & pos : aroundDefender ) { assert( pos.GetHead() != nullptr ); if ( posFilter && !posFilter( pos ) ) { continue; } const int32_t posHeadIdx = pos.GetHead()->GetIndex(); const auto posValueIter = valuesOfAttackPositions.find( pos ); assert( posValueIter != valuesOfAttackPositions.end() ); MeleeAttackOutcome current; current.attackValue = optimalAttackValue( attacker, defender, pos ); current.positionValue = posValueIter->second; current.canAttackImmediately = Board::CanAttackTargetFromPosition( attacker, defender, posHeadIdx ); // Pick target if either position has improved or unit is higher value at the same position value if ( IsOutcomeImproved( current, bestOutcome ) ) { bestOutcome.fromIndex = posHeadIdx; bestOutcome.attackValue = current.attackValue; bestOutcome.positionValue = current.positionValue; bestOutcome.canAttackImmediately = current.canAttackImmediately; } } return bestOutcome; } int32_t findOptimalPositionForSubsequentAttack( Arena & arena, const Indexes & path, const Unit & currentUnit, const Battle::Units & enemies ) { const Position & currentUnitPos = currentUnit.GetPosition(); std::vector> pathStepsThreatLevels; pathStepsThreatLevels.reserve( path.size() ); { Position stepPos = currentUnitPos; for ( const int32_t stepIdx : path ) { if ( currentUnit.isWide() ) { const Cell * stepPosTailCell = stepPos.GetTail(); assert( stepPosTailCell != nullptr ); // Reversal is not a movement if ( stepIdx == stepPosTailCell->GetIndex() ) { stepPos.Set( stepIdx, currentUnit.isWide(), !stepPos.isReflect() ); continue; } } stepPos.Set( stepIdx, currentUnit.isWide(), stepPos.isReflect() ); assert( arena.isPositionReachable( currentUnit, stepPos, true ) ); #ifdef NDEBUG (void)arena; #endif pathStepsThreatLevels.emplace_back( stepPos, 0.0 ); } } for ( const Unit * enemy : enemies ) { assert( enemy != nullptr ); // Archers and Flyers are always a threat if ( enemy->isFlying() || ( enemy->isArchers() && !enemy->isHandFighting() ) ) { continue; } for ( auto & [stepPos, stepThreatLevel] : pathStepsThreatLevels ) { if ( !isUnitAbleToApproachPosition( enemy, stepPos ) ) { continue; } // Rough estimate: the threat assessment is performed for the current position of the unit, not its new position at this step stepThreatLevel += enemy->evaluateThreatForUnit( currentUnit ); } } double lowestThreat = 0.0; int32_t targetIdx = -1; for ( const auto & [stepPos, stepThreatLevel] : pathStepsThreatLevels ) { // We need to get as close to the target as possible (taking into account the threat level) if ( targetIdx == -1 || stepThreatLevel < lowestThreat || std::fabs( stepThreatLevel - lowestThreat ) < 0.001 ) { assert( stepPos.isValidForUnit( currentUnit ) ); lowestThreat = stepThreatLevel; // When moving along the path, the direction of a wide unit at some steps may be reversed in relation to the target one. Detect this and use the proper // index. targetIdx = ( !currentUnit.isWide() || stepPos.isReflect() == currentUnitPos.isReflect() ) ? stepPos.GetHead()->GetIndex() : stepPos.GetTail()->GetIndex(); } } return targetIdx; } struct CellDistanceInfo { int32_t idx = -1; uint32_t dist = UINT32_MAX; }; CellDistanceInfo findNearestCellNextToUnit( Arena & arena, const Unit & currentUnit, const Unit & target ) { CellDistanceInfo result; for ( const int32_t idx : Board::GetDistanceIndexes( target, currentUnit.isWide() ? 2 : 1 ) ) { const Position pos = Position::GetPosition( currentUnit, idx ); if ( pos.GetHead() == nullptr ) { continue; } assert( pos.isValidForUnit( currentUnit ) ); const uint32_t dist = Board::GetDistance( pos, target.GetPosition() ); assert( dist > 0 ); if ( dist != 1 ) { continue; } if ( !arena.isPositionReachable( currentUnit, pos, false ) ) { continue; } const uint32_t moveDist = arena.CalculateMoveDistance( currentUnit, pos ); if ( result.idx == -1 || moveDist < result.dist ) { result = { idx, moveDist }; } } return result; } int32_t getUnitMovementTarget( Arena & arena, const Unit & currentUnit, const int32_t idx ) { // First try to find the position that is reachable on the current turn { const Position pos = Position::GetReachable( currentUnit, idx ); if ( pos.GetHead() != nullptr ) { assert( pos.isValidForUnit( currentUnit ) ); return pos.GetHead()->GetIndex(); } } // If there is no such position, then use the last position on the path to the cell with the specified index const Position dstPos = Position::GetPosition( currentUnit, idx ); assert( dstPos.isValidForUnit( currentUnit ) ); const Position pos = arena.getClosestReachablePosition( currentUnit, dstPos ); assert( pos.isValidForUnit( currentUnit ) ); return pos.GetHead()->GetIndex(); } void Normal::HeroesPreBattle( HeroBase & hero, bool isAttacking ) { if ( isAttacking ) { OptimizeTroopsOrder( hero.GetArmy() ); } } bool BattlePlanner::isCommanderCanSpellcast( const Arena & arena, const HeroBase * commander ) const { return commander && ( !commander->isControlHuman() || Settings::Get().BattleAutoSpellcast() ) && commander->HaveSpellBook() && !commander->Modes( Heroes::SPELLCASTED ) && !arena.isSpellcastDisabled(); } bool BattlePlanner::isUnitFaster( const Unit & currentUnit, const Unit & target ) const { if ( currentUnit.isFlying() == target.isFlying() ) return currentUnit.GetSpeed() > target.GetSpeed(); return currentUnit.isFlying(); } void BattlePlanner::battleBegins() { _currentTurnNumber = 0; _numberOfRemainingTurnsWithoutDeaths = MAX_TURNS_WITHOUT_DEATHS; _attackerForceNumberOfDead = 0; _defenderForceNumberOfDead = 0; } bool BattlePlanner::isLimitOfTurnsExceeded( const Arena & arena, Actions & actions ) { const int currentColor = arena.GetCurrentColor(); // Not the attacker's turn, no further checks if ( currentColor != arena.GetArmy1Color() ) { return false; } const uint32_t currentTurnNumber = arena.GetTurnNumber(); assert( currentTurnNumber > 0 ); // This is the beginning of a new turn and we still haven't gone beyond the limit on the number of turns without deaths if ( currentTurnNumber > _currentTurnNumber && _numberOfRemainingTurnsWithoutDeaths > 0 ) { auto prevNumbersOfDead = std::tie( _attackerForceNumberOfDead, _defenderForceNumberOfDead ); const auto currNumbersOfDead = std::make_tuple( arena.GetForce1().GetDeadCounts(), arena.GetForce2().GetDeadCounts() ); // Either we don't have numbers of dead units from the previous turn, or there were changes in these numbers compared // to the previous turn, reset the counter if ( _currentTurnNumber == 0 || currentTurnNumber - _currentTurnNumber != 1 || prevNumbersOfDead != currNumbersOfDead ) { prevNumbersOfDead = currNumbersOfDead; _numberOfRemainingTurnsWithoutDeaths = MAX_TURNS_WITHOUT_DEATHS; } // No changes in numbers of dead units compared to the previous turn, decrease the counter of the remaining turns else { _numberOfRemainingTurnsWithoutDeaths -= 1; } _currentTurnNumber = currentTurnNumber; } // We have gone beyond the limit on the number of turns without deaths and have to stop if ( _numberOfRemainingTurnsWithoutDeaths == 0 ) { // If this is an auto battle (and not the instant battle, because the battle UI is present), then turn it off until the end of the battle if ( arena.AutoBattleInProgress() && Arena::GetInterface() != nullptr ) { assert( arena.CanToggleAutoBattle() ); actions.emplace_back( Command::AUTO_SWITCH, currentColor ); DEBUG_LOG( DBG_BATTLE, DBG_INFO, Color::String( currentColor ) << " has used up the limit of turns without deaths, auto battle is turned off" ) } // Otherwise the attacker's hero should retreat else { assert( arena.CanRetreatOpponent( currentColor ) && arena.GetCurrentCommander() != nullptr ); actions.emplace_back( Command::RETREAT ); DEBUG_LOG( DBG_BATTLE, DBG_INFO, Color::String( currentColor ) << " has used up the limit of turns without deaths, " << arena.GetCurrentCommander()->GetName() << " retreats" ) } return true; } return false; } Actions BattlePlanner::planUnitTurn( Arena & arena, const Unit & currentUnit ) { if ( currentUnit.Modes( SP_BERSERKER ) ) { return berserkTurn( arena, currentUnit ); } Actions actions; // Step 1. Analyze current battle state and update variables analyzeBattleState( arena, currentUnit ); DEBUG_LOG( DBG_BATTLE, DBG_INFO, currentUnit.GetName() << " begin the turn, color: " << Color::String( _myColor ) ) // Step 2. Check retreat/surrender condition const Heroes * actualHero = dynamic_cast( _commander ); if ( actualHero ) { enum class Outcome { ContinueBattle, Retreat, Surrender }; const Outcome outcome = [this, &arena, actualHero]() { if ( !_considerRetreat ) { return Outcome::ContinueBattle; } // Human-controlled heroes should not retreat or surrender during auto/instant battles if ( actualHero->isControlHuman() ) { return Outcome::ContinueBattle; } const int gameDifficulty = Game::getDifficulty(); const bool isGameCampaign = Game::isCampaign(); // TODO: consider taking speed/turn order into account in the future if ( _myArmyStrength * Difficulty::getArmyStrengthRatioForAIRetreat( gameDifficulty ) >= _enemyArmyStrength ) { return Outcome::ContinueBattle; } const bool hasValuableArtifacts = [actualHero]() { const BagArtifacts & artifactsBag = actualHero->GetBagArtifacts(); return std::any_of( artifactsBag.begin(), artifactsBag.end(), []( const Artifact & art ) { const fheroes2::ArtifactData & artifactData = fheroes2::getArtifactData( art.GetID() ); return std::any_of( artifactData.bonuses.begin(), artifactData.bonuses.end(), []( const fheroes2::ArtifactBonus & bonus ) { return bonus.type != fheroes2::ArtifactBonusType::NONE; } ); } ); }(); const Kingdom & kingdom = actualHero->GetKingdom(); const bool considerRetreat = [this, &arena, actualHero, gameDifficulty, isGameCampaign, hasValuableArtifacts, &kingdom]() { if ( !Difficulty::allowAIToRetreat( gameDifficulty, isGameCampaign ) ) { return false; } if ( !arena.CanRetreatOpponent( _myColor ) ) { return false; } // If the hero has valuable artifacts, he should in any case consider retreating so that these artifacts do not end up at the disposal of the enemy, // especially in the case of an alliance war if ( hasValuableArtifacts ) { return true; } // Otherwise, if this hero is the last one, and the kingdom has no castles, then there is no point in retreating if ( kingdom.GetHeroes().size() == 1 ) { assert( kingdom.GetHeroes().at( 0 ) == actualHero ); if ( kingdom.GetCastles().empty() ) { return false; } } // Otherwise, if this hero is relatively experienced, then he should think about retreating so that he can be hired again later return actualHero->GetLevel() >= Difficulty::getMinHeroLevelForAIRetreat( gameDifficulty ); }(); const bool considerSurrender = [this, &arena, actualHero, gameDifficulty, isGameCampaign, hasValuableArtifacts, &kingdom]() { if ( !Difficulty::allowAIToSurrender( gameDifficulty, isGameCampaign ) ) { return false; } if ( !arena.CanSurrenderOpponent( _myColor ) ) { return false; } // If the hero has valuable artifacts, he should in any case consider surrendering so that these artifacts do not end up at the disposal of the enemy, // especially in the case of an alliance war if ( hasValuableArtifacts ) { return true; } // Otherwise, if this hero is the last one, and either the kingdom has no castles, or this hero is defending the last castle, then there is no point // in surrendering if ( kingdom.GetHeroes().size() == 1 ) { assert( kingdom.GetHeroes().at( 0 ) == actualHero ); const VecCastles & castles = kingdom.GetCastles(); if ( castles.empty() ) { return false; } const Castle * castle = actualHero->inCastle(); if ( castle && castles.size() == 1 ) { assert( castles.at( 0 ) == castle ); return false; } } // Otherwise, if this hero is relatively experienced, then he should think about surrendering so that he can be hired again later return actualHero->GetLevel() >= Difficulty::getMinHeroLevelForAIRetreat( gameDifficulty ); }(); const Force & force = arena.getForce( _myColor ); if ( !considerRetreat ) { if ( !considerSurrender ) { return Outcome::ContinueBattle; } if ( !kingdom.AllowPayment( { Resource::GOLD, force.GetSurrenderCost() } ) ) { return Outcome::ContinueBattle; } return Outcome::Surrender; } if ( !considerSurrender ) { return Outcome::Retreat; } if ( force.getStrengthOfArmyRemainingInCaseOfSurrender() < Army::getStrengthOfAverageStartingArmy( actualHero ) ) { return Outcome::Retreat; } if ( !kingdom.AllowPayment( Funds{ Resource::GOLD, force.GetSurrenderCost() } * Difficulty::getGoldReserveRatioForAISurrender( gameDifficulty ) ) ) { return Outcome::Retreat; } return Outcome::Surrender; }(); const auto farewellSpellcast = [this, &arena, ¤tUnit, &actions]() { if ( !isCommanderCanSpellcast( arena, _commander ) ) { return; } // Cast a spell with maximum damage const SpellSelection & bestSpell = selectBestSpell( arena, currentUnit, true ); if ( bestSpell.spellID == -1 ) { return; } actions.emplace_back( Command::SPELLCAST, bestSpell.spellID, bestSpell.cell ); DEBUG_LOG( DBG_BATTLE, DBG_INFO, arena.GetCurrentCommander()->GetName() << " casts " << Spell( bestSpell.spellID ).GetName() << " on cell " << bestSpell.cell ) }; switch ( outcome ) { case Outcome::ContinueBattle: break; case Outcome::Retreat: farewellSpellcast(); actions.emplace_back( Command::RETREAT ); DEBUG_LOG( DBG_BATTLE, DBG_INFO, arena.GetCurrentCommander()->GetName() << " retreats" ) return actions; case Outcome::Surrender: farewellSpellcast(); actions.emplace_back( Command::SURRENDER ); DEBUG_LOG( DBG_BATTLE, DBG_INFO, arena.GetCurrentCommander()->GetName() << " surrenders" ) return actions; default: assert( 0 ); break; } } // Step 3. Calculate spell heuristics if ( isCommanderCanSpellcast( arena, _commander ) ) { const SpellSelection & bestSpell = selectBestSpell( arena, currentUnit, false ); if ( bestSpell.spellID != -1 ) { actions.emplace_back( Command::SPELLCAST, bestSpell.spellID, bestSpell.cell ); DEBUG_LOG( DBG_BATTLE, DBG_INFO, arena.GetCurrentCommander()->GetName() << " casts " << Spell( bestSpell.spellID ).GetName() << " on cell " << bestSpell.cell ) return actions; } } // Step 4. Current unit decision tree const size_t actionsSize = actions.size(); if ( currentUnit.isArchers() ) { const Actions archerActions = archerDecision( arena, currentUnit ); actions.insert( actions.end(), archerActions.begin(), archerActions.end() ); } else { // Melee unit decision tree (both flyers and walkers) BattleTargetPair target; // Determine unit target or cell to move to if ( _defensiveTactics ) { target = meleeUnitDefense( arena, currentUnit ); } else { target = meleeUnitOffense( arena, currentUnit ); } // Melee unit final stage - add actions to the queue if ( target.cell != -1 ) { // The target cell of the movement must be the cell that the unit's head will occupy const int32_t moveTargetIdx = getUnitMovementTarget( arena, currentUnit, target.cell ); if ( target.unit ) { const Position attackPos = Position::GetReachable( currentUnit, moveTargetIdx ); assert( attackPos.isValidForUnit( currentUnit ) ); const auto [attackTargetIdx, attackDirection] = optimalAttackVector( currentUnit, *target.unit, attackPos ); actions.emplace_back( Command::ATTACK, currentUnit.GetUID(), target.unit->GetUID(), ( currentUnit.GetHeadIndex() == moveTargetIdx ? -1 : moveTargetIdx ), attackTargetIdx, attackDirection ); DEBUG_LOG( DBG_BATTLE, DBG_INFO, currentUnit.GetName() << " attacking enemy " << target.unit->GetName() << " from cell " << moveTargetIdx << ", attack vector: " << Board::GetIndexDirection( attackTargetIdx, Board::GetReflectDirection( attackDirection ) ) << " -> " << attackTargetIdx << ", threat level: " << target.unit->evaluateThreatForUnit( currentUnit ) ) } else if ( currentUnit.GetHeadIndex() != moveTargetIdx ) { actions.emplace_back( Command::MOVE, currentUnit.GetUID(), moveTargetIdx ); DEBUG_LOG( DBG_BATTLE, DBG_INFO, currentUnit.GetName() << " moving to cell " << moveTargetIdx ) } // Else skip the turn } // Else skip the turn } // No action was taken, skip the turn if ( actions.size() == actionsSize ) { actions.emplace_back( Command::SKIP, currentUnit.GetUID() ); DEBUG_LOG( DBG_BATTLE, DBG_INFO, currentUnit.GetName() << " skipping the turn" ) } return actions; } void BattlePlanner::analyzeBattleState( const Arena & arena, const Unit & currentUnit ) { _myColor = currentUnit.GetCurrentColor(); _commander = arena.getCommander( _myColor ); const Force & friendlyForce = arena.getForce( _myColor ); const Force & enemyForce = arena.getEnemyForce( _myColor ); // Friendly and enemy army analysis _myArmyStrength = 0; _enemyArmyStrength = 0; _myShootersStrength = 0; _enemyShootersStrength = 0; _myRangedUnitsOnly = 0; _enemyRangedUnitsOnly = 0; _myArmyAverageSpeed = 0; _enemyAverageSpeed = 0; _enemySpellStrength = 0; _attackingCastle = false; _defendingCastle = false; _considerRetreat = false; _defensiveTactics = false; _cautiousOffensive = false; if ( enemyForce.empty() ) { return; } double sumEnemyStr = 0.0; for ( const Unit * unit : enemyForce ) { assert( unit != nullptr ); if ( !unit->isValid() ) { continue; } const double unitStr = unit->GetStrength(); _enemyArmyStrength += unitStr; if ( unit->isArchers() && !unit->isImmovable() ) { _enemyRangedUnitsOnly += unitStr; } // The average speed is weighted by the troop strength _enemyAverageSpeed += unit->GetSpeed( false, true ) * unitStr; sumEnemyStr += unitStr; } _enemyShootersStrength = _enemyRangedUnitsOnly; if ( sumEnemyStr > 0.0 ) { _enemyAverageSpeed /= sumEnemyStr; } uint32_t initialUnitCount = 0; double sumArmyStr = 0.0; for ( const Unit * unit : friendlyForce ) { assert( unit != nullptr ); // Do not check isValid() here to handle dead troops const uint32_t count = unit->GetCount(); const uint32_t dead = unit->GetDead(); // Count all valid troops in army (both alive and dead) if ( count > 0 || dead > 0 ) { ++initialUnitCount; } const double unitStr = unit->GetStrength(); // The average speed is weighted by the troop strength _myArmyAverageSpeed += unit->GetSpeed( false, true ) * unitStr; sumArmyStr += unitStr; // Dead unit: trigger retreat condition and skip strength calculation if ( count == 0 && dead > 0 ) { _considerRetreat = true; continue; } _myArmyStrength += unitStr; if ( unit->isArchers() && !unit->isImmovable() ) { _myRangedUnitsOnly += unitStr; } } _myShootersStrength = _myRangedUnitsOnly; if ( sumArmyStr > 0.0 ) { _myArmyAverageSpeed /= sumArmyStr; } _considerRetreat = _considerRetreat || initialUnitCount < 4; // Add castle siege (and battle arena) modifiers const Castle * castle = Arena::GetCastle(); // Mark as castle siege only if any tower is present. If no towers present then nothing to defend and most likely all walls are destroyed as well. if ( castle && Arena::isAnyTowerPresent() ) { const bool attackerIgnoresCover = arena.GetForce1().GetCommander()->GetBagArtifacts().isArtifactBonusPresent( fheroes2::ArtifactBonusType::NO_SHOOTING_PENALTY ); const auto getTowerStrength = []( const Tower * tower ) { return ( tower && tower->isValid() ) ? tower->GetStrength() : 0; }; double towerStr = getTowerStrength( Arena::GetTower( TowerType::TWR_CENTER ) ); towerStr += getTowerStrength( Arena::GetTower( TowerType::TWR_LEFT ) ); towerStr += getTowerStrength( Arena::GetTower( TowerType::TWR_RIGHT ) ); DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "- Castle strength: " << towerStr ) // Tower strength can't be negative. If this assertion triggers something is wrong with the logic above. assert( towerStr >= 0 ); if ( _myColor == castle->GetColor() ) { _defendingCastle = true; _myShootersStrength += towerStr; if ( !attackerIgnoresCover ) { _enemyShootersStrength /= 1.5; } } else { _attackingCastle = true; _enemyShootersStrength += towerStr; if ( !attackerIgnoresCover ) { _myShootersStrength /= 1.5; } } } // Calculate each hero spell strength and add it to shooter values after castle modifiers were applied if ( _commander && _myShootersStrength > 1 ) { _myShootersStrength += commanderMaximumSpellDamageValue( *_commander ); } const HeroBase * enemyCommander = arena.getEnemyCommander( _myColor ); if ( enemyCommander ) { _enemySpellStrength = enemyCommander->GetMagicStrategicValue( _myArmyStrength ); _enemyShootersStrength += commanderMaximumSpellDamageValue( *enemyCommander ); } assert( _myArmyStrength > 0.0 && _enemyArmyStrength > 0.0 ); const double myArcherRatio = _myShootersStrength / _myArmyStrength; const double enemyArcherRatio = _enemyShootersStrength / _enemyArmyStrength; _defensiveTactics = [this, ¤tUnit, myArcherRatio, enemyArcherRatio]() { // Unit is already in the enemy half of the battlefield, just let it keep attacking if ( !isPositionLocatedInDefendedArea( currentUnit, currentUnit.GetPosition() ) ) { return false; } const double overPowerRatio = ( currentUnit.isFlying() ? 6 : 10 ); // When we have a X times stronger army than the enemy, then we are likely to win, there is no need to go on the defensive if ( _myArmyStrength > _enemyArmyStrength * overPowerRatio ) { return false; } // When we have fewer shooters than the enemy, it makes no sense to go on the defensive if ( _myShootersStrength < _enemyShootersStrength ) { return false; } // We have at least as many shooters as the enemy and we defend the castle under the protection of walls and towers, it makes sense to choose defensive // tactics if ( _defendingCastle ) { return true; } // If we have an unfavorable ratio of infantry and shooters for defense, then it is better to choose an offensive if ( myArcherRatio < 0.15 ) { return false; } // If the enemy has too many shooters, but not enough infantry to cover them, then it makes sense to choose an offensive if ( enemyArcherRatio > 0.66 ) { return false; } return true; }(); // If an offensive tactic is chosen, then this means that, most likely, the enemy has more shooters, which means that we should try to attack the enemy and // neutralize his shooters as quickly as possible. A cautious offensive tactics can be chosen only if our army is fighting an enemy army that has limited // distance attack capabilities. _cautiousOffensive = ( enemyArcherRatio < 0.15 ); DEBUG_LOG( DBG_BATTLE, DBG_INFO, ( _defensiveTactics ? "Defensive" : ( _cautiousOffensive ? "Cautious offensive" : "Offensive" ) ) << " tactics have been chosen. Army strength: " << _myArmyStrength << ", shooters strength: " << _myShootersStrength << ", enemy army strength: " << _enemyArmyStrength << ", enemy shooters strength: " << _enemyShootersStrength ) } Actions BattlePlanner::archerDecision( Arena & arena, const Unit & currentUnit ) const { Actions actions; // Current unit can be under the influence of the Hypnotize spell const Units enemies( arena.getEnemyForce( _myColor ).getUnits(), Units::REMOVE_INVALID_UNITS_AND_SPECIFIED_UNIT, ¤tUnit ); // Assess the current threat level and decide whether to retreat to another position const int32_t retreatPositionIndex = [&arena, ¤tUnit, &enemies]() { // There is no point in trying to retreat from flying units regardless of their speed if ( std::any_of( enemies.begin(), enemies.end(), []( const Unit * enemy ) { assert( enemy != nullptr ); return enemy->isFlying(); } ) ) { return -1; } struct PositionCharacteristics { // Indexes of the head cells of all enemy units that can potentially reach this position std::set threateningEnemiesIndexes; // Distance between this position and the nearest enemy unit uint32_t distanceToNearestEnemy{ UINT32_MAX }; }; const auto evaluatePotentialPositions = [¤tUnit, &enemies]( std::map & potentialPositions ) { class UnitRemover { public: explicit UnitRemover( const Unit & unitToRemove ) { const int32_t headIdx = unitToRemove.GetHeadIndex(); const int32_t tailIdx = unitToRemove.GetTailIndex(); assert( headIdx != -1 && ( unitToRemove.isWide() ? tailIdx != -1 : tailIdx == -1 ) ); unitToRestore = getUnitOnCell( headIdx ); assert( unitToRestore == &unitToRemove ); setUnitForCell( headIdx, nullptr ); if ( tailIdx != -1 ) { assert( unitToRestore == getUnitOnCell( tailIdx ) ); setUnitForCell( tailIdx, nullptr ); } } UnitRemover( const UnitRemover & ) = delete; ~UnitRemover() { assert( unitToRestore != nullptr ); const int32_t headIdx = unitToRestore->GetHeadIndex(); const int32_t tailIdx = unitToRestore->GetTailIndex(); assert( headIdx != -1 && ( unitToRestore->isWide() ? tailIdx != -1 : tailIdx == -1 ) ); setUnitForCell( headIdx, unitToRestore ); if ( tailIdx != -1 ) { setUnitForCell( tailIdx, unitToRestore ); } } UnitRemover & operator=( const UnitRemover & ) = delete; private: static Unit * getUnitOnCell( const int32_t idx ) { Cell * cell = Board::GetCell( idx ); assert( cell != nullptr ); return cell->GetUnit(); } static void setUnitForCell( const int32_t idx, Unit * unit ) { Cell * cell = Board::GetCell( idx ); assert( cell != nullptr ); cell->SetUnit( unit ); } Unit * unitToRestore = nullptr; }; // In order to correctly assess the safety of potential positions for the unit to be moved, we need // to temporarily remove this unit from the battlefield, otherwise, it can block the movement of enemy // units towards these new potential positions. const UnitRemover unitRemover( currentUnit ); for ( const Unit * enemy : enemies ) { assert( enemy != nullptr ); for ( auto & [position, characteristics] : potentialPositions ) { assert( position.GetHead() != nullptr ); const bool isPositionUnderEnemyThreat = [enemy]( const Position & pos ) { // Archers who not blocked by enemy units generally threaten any position, but for the purpose // of this assessment, it is assumed that they threaten only in melee, that is, in positions // directly adjacent to them if ( enemy->isArchers() && !enemy->isHandFighting() ) { const uint32_t distanceToEnemy = Board::GetDistance( pos, enemy->GetPosition() ); assert( distanceToEnemy > 0 ); return ( distanceToEnemy == 1 ); } // The potential event of enemy's good morale is not taken into account here return isUnitAbleToApproachPosition( enemy, pos ); }( position ); if ( isPositionUnderEnemyThreat ) { characteristics.threateningEnemiesIndexes.insert( enemy->GetHeadIndex() ); } characteristics.distanceToNearestEnemy = std::min( characteristics.distanceToNearestEnemy, Board::GetDistance( position, enemy->GetPosition() ) ); assert( characteristics.distanceToNearestEnemy > 0 ); } } }; std::map potentialPositions; // The current position is also considered as a potential one potentialPositions.try_emplace( currentUnit.GetPosition() ); for ( const int32_t idx : arena.getAllAvailableMoves( currentUnit ) ) { potentialPositions.try_emplace( Position::GetReachable( currentUnit, idx ) ); } evaluatePotentialPositions( potentialPositions ); { const auto currentPositionIter = potentialPositions.find( currentUnit.GetPosition() ); assert( currentPositionIter != potentialPositions.end() ); const auto & [position, characteristics] = *currentPositionIter; // If the current position is not in danger, then nothing special should be done if ( characteristics.threateningEnemiesIndexes.empty() ) { return -1; } const uint32_t currentUnitSpeed = currentUnit.GetSpeed(); assert( currentUnitSpeed > Speed::STANDING ); // The current position is in danger, let's evaluate the possibility of a retreat const bool isItWorthTryingToRetreat = std::all_of( characteristics.threateningEnemiesIndexes.begin(), characteristics.threateningEnemiesIndexes.end(), [&arena, currentUnitSpeed]( const int32_t enemyIdx ) { const Unit * enemy = arena.GetTroopBoard( enemyIdx ); assert( enemy != nullptr && !enemy->isFlying() ); // Also consider the next turn, even if this unit has already acted during the current turn const uint32_t enemySpeed = enemy->GetSpeed( false, true ); assert( enemySpeed > Speed::STANDING ); // In order for it to make sense to try to retreat from the enemy, the enemy should be somewhat // slower return ( enemySpeed + 2 < currentUnitSpeed ); } ); if ( !isItWorthTryingToRetreat ) { return -1; } } // The current position is in danger, but there is an opportunity to retreat. Let's try to find a position to retreat. int32_t safestIdx = -1; // Distance to the nearest enemy unit (the more, the better) and inverse of the distance to the central cell of the battlefield (1/x, // i.e. the smaller the x the better). The idea is that corner cells should be avoided whenever possible when retreating because they // can easily be blocked by enemy units. std::pair safestParams{ 0, 0.0 }; for ( const auto & [position, characteristics] : potentialPositions ) { assert( position.GetHead() != nullptr && characteristics.distanceToNearestEnemy > 0 ); // Only completely safe positions are considered as candidates if ( !characteristics.threateningEnemiesIndexes.empty() ) { continue; } const int32_t idx = position.GetHead()->GetIndex(); const uint32_t distanceToBattlefieldCenter = Board::GetDistance( idx, ARENASIZE / 2 ); const auto idxParams = std::make_pair( characteristics.distanceToNearestEnemy, distanceToBattlefieldCenter == 0 ? 1.0 : 1.0 / distanceToBattlefieldCenter ); if ( safestParams < idxParams ) { safestIdx = idx; safestParams = idxParams; } } return safestIdx; }(); // The current position of the archers is not safe, but there is somewhere to retreat if ( retreatPositionIndex != -1 ) { DEBUG_LOG( DBG_BATTLE, DBG_INFO, currentUnit.GetName() << " retreating from enemy, target cell: " << retreatPositionIndex ) // The target cell of the movement must be the cell that the unit's head will occupy const int32_t moveTargetIdx = getUnitMovementTarget( arena, currentUnit, retreatPositionIndex ); if ( currentUnit.GetHeadIndex() != moveTargetIdx ) { actions.emplace_back( Command::MOVE, currentUnit.GetUID(), moveTargetIdx ); DEBUG_LOG( DBG_BATTLE, DBG_INFO, currentUnit.GetName() << " moving to cell " << moveTargetIdx ) } else { // It is impossible to "retreat" to the same position you are already in assert( 0 ); } } // Archers are blocked and there is nowhere to retreat, they are fighting in melee else if ( currentUnit.isHandFighting() ) { BattleTargetPair target; int32_t bestOutcome = INT32_MIN; for ( const Unit * enemy : enemies ) { assert( enemy != nullptr ); const uint32_t dist = Board::GetDistance( currentUnit.GetPosition(), enemy->GetPosition() ); assert( dist > 0 ); if ( dist != 1 ) { continue; } const int32_t archerMeleeDmg = [¤tUnit, enemy]() { if ( currentUnit.Modes( SP_CURSE ) ) { return currentUnit.CalculateMinDamage( *enemy ); } if ( currentUnit.Modes( SP_BLESS ) ) { return currentUnit.CalculateMaxDamage( *enemy ); } return ( currentUnit.CalculateMinDamage( *enemy ) + currentUnit.CalculateMaxDamage( *enemy ) ) / 2; }(); const int32_t retaliatoryDmg = enemy->EstimateRetaliatoryDamage( archerMeleeDmg ); const int32_t damageDiff = archerMeleeDmg - retaliatoryDmg; if ( bestOutcome < damageDiff ) { bestOutcome = damageDiff; target.unit = enemy; DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "- Set melee attack priority on " << enemy->GetName() << ", value: " << damageDiff ) } } if ( target.unit ) { actions.emplace_back( Command::ATTACK, currentUnit.GetUID(), target.unit->GetUID(), -1, -1, -1 ); DEBUG_LOG( DBG_BATTLE, DBG_INFO, currentUnit.GetName() << " attacking enemy " << target.unit->GetName() << " in melee, outcome: " << bestOutcome ) } } // Archers are able to shoot else { BattleTargetPair target; double highestPriority = INT32_MIN; for ( const Unit * enemy : enemies ) { assert( enemy != nullptr ); const auto updateBestTarget = [&target, &highestPriority, enemy]( const double priority, const int32_t targetIdx ) { if ( highestPriority < priority ) { highestPriority = priority; target.cell = targetIdx; target.unit = enemy; DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "- Set shooting attack priority on " << enemy->GetName() << ", value: " << priority ) } }; if ( currentUnit.isAbilityPresent( fheroes2::MonsterAbilityType::AREA_SHOT ) ) { const auto calculateAreaShotAttackPriority = [&arena, ¤tUnit, enemy]( const int32_t targetIdx ) { double result = 0.0; // Indexes of the head cells of the units are used instead of pointers because the exact result of adding several // floating-point numbers may depend on the order of their addition, so the order of the elements must be deterministic. std::set affectedUnitsIndexes; affectedUnitsIndexes.insert( enemy->GetHeadIndex() ); for ( const int32_t cellIdx : Board::GetAroundIndexes( targetIdx ) ) { const Unit * unit = arena.GetTroopBoard( cellIdx ); if ( unit == nullptr ) { continue; } affectedUnitsIndexes.insert( unit->GetHeadIndex() ); } for ( const int32_t unitIdx : affectedUnitsIndexes ) { const Unit * unit = arena.GetTroopBoard( unitIdx ); assert( unit != nullptr ); result += unit->evaluateThreatForUnit( currentUnit ); } return result; }; const int32_t enemyHeadIdx = enemy->GetHeadIndex(); assert( enemyHeadIdx != -1 ); updateBestTarget( calculateAreaShotAttackPriority( enemyHeadIdx ), enemyHeadIdx ); if ( enemy->isWide() ) { const int32_t enemyTailIdx = enemy->GetTailIndex(); assert( enemyTailIdx != -1 ); updateBestTarget( calculateAreaShotAttackPriority( enemyTailIdx ), enemyTailIdx ); } continue; } updateBestTarget( enemy->evaluateThreatForUnit( currentUnit ), -1 ); } if ( target.unit ) { actions.emplace_back( Command::ATTACK, currentUnit.GetUID(), target.unit->GetUID(), -1, target.cell, 0 ); DEBUG_LOG( DBG_BATTLE, DBG_INFO, currentUnit.GetName() << " shooting at enemy " << target.unit->GetName() << ", value: " << highestPriority ) } } return actions; } BattleTargetPair BattlePlanner::meleeUnitOffense( Arena & arena, const Unit & currentUnit ) const { BattleTargetPair target; const PositionValues valuesOfAttackPositions = evaluatePotentialAttackPositions( arena, currentUnit ); // Current unit can be under the influence of the Hypnotize spell const Units enemies( arena.getEnemyForce( _myColor ).getUnits(), Units::REMOVE_INVALID_UNITS_AND_SPECIFIED_UNIT, ¤tUnit ); // 1. Choose the best target within reach, if any { MeleeAttackOutcome bestOutcome; for ( const Unit * enemy : enemies ) { assert( enemy != nullptr ); const MeleeAttackOutcome outcome = BestAttackOutcome( currentUnit, *enemy, valuesOfAttackPositions ); if ( !outcome.canAttackImmediately ) { continue; } if ( IsOutcomeImproved( outcome, bestOutcome ) ) { bestOutcome = outcome; target.cell = outcome.fromIndex; target.unit = enemy; DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "- Set attack priority on " << enemy->GetName() << ", attack value: " << outcome.attackValue << ", position value: " << outcome.positionValue ) } } } if ( target.unit ) { DEBUG_LOG( DBG_BATTLE, DBG_INFO, currentUnit.GetName() << " attacking " << target.unit->GetName() << " from cell " << target.cell ) return target; } // 2. For units that don't have a target within reach, choose a target depending on distance-based priority { const Castle * castle = Arena::GetCastle(); const bool isMoatBuilt = castle && castle->isBuild( BUILD_MOAT ); // The usual distance between units of different armies at the beginning of a battle is 10-14 tiles. For each tile passed, 20% of the total army value will be // lost to make sure that the difference of 4 tiles matters. const double attackDistanceModifier = _enemyArmyStrength / 5.0; double maxPriority = std::numeric_limits::lowest(); for ( const Unit * enemy : enemies ) { assert( enemy != nullptr ); const CellDistanceInfo nearestCellInfo = findNearestCellNextToUnit( arena, currentUnit, *enemy ); if ( nearestCellInfo.idx == -1 ) { continue; } // Do not pursue faster units that can move away and avoid an engagement const uint32_t dist = ( !enemy->isArchers() && isUnitFaster( *enemy, currentUnit ) ? nearestCellInfo.dist + ARENAW + ARENAH : nearestCellInfo.dist ); const double unitPriority = enemy->evaluateThreatForUnit( currentUnit ) - dist * attackDistanceModifier; if ( unitPriority < maxPriority ) { continue; } maxPriority = unitPriority; const Position pos = Position::GetPosition( currentUnit, nearestCellInfo.idx ); assert( pos.isValidForUnit( currentUnit ) ); const Indexes path = arena.GetPath( currentUnit, pos ); assert( !path.empty() ); // Unit rushes through the moat, step into the moat to get more freedom of action on the next turn if ( isMoatBuilt && Board::isMoatIndex( path.back(), currentUnit ) ) { target.cell = path.back(); DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "- Going after target " << enemy->GetName() << ", stopping in the moat at cell " << target.cell ) } else if ( _cautiousOffensive ) { target.cell = findOptimalPositionForSubsequentAttack( arena, path, currentUnit, enemies ); DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "- Going after target " << enemy->GetName() << " using a cautious offensive, stopping at cell " << target.cell ) } else { target.cell = path.back(); DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "- Going after target " << enemy->GetName() << ", stopping at cell " << target.cell ) } } } if ( target.cell != -1 ) { return target; } // 3. Try to get closer to the castle walls during the siege if ( _attackingCastle ) { uint32_t shortestDist = UINT32_MAX; for ( const int32_t cellIdx : cellsUnderWallsIndexes ) { const Position pos = Position::GetPosition( currentUnit, cellIdx ); if ( !arena.isPositionReachable( currentUnit, pos, false ) ) { continue; } assert( pos.isValidForUnit( currentUnit ) ); const uint32_t moveDist = arena.CalculateMoveDistance( currentUnit, pos ); if ( target.cell == -1 || moveDist < shortestDist ) { shortestDist = moveDist; target.cell = cellIdx; } } if ( target.cell != -1 ) { DEBUG_LOG( DBG_BATTLE, DBG_INFO, currentUnit.GetName() << " moving towards castle walls, target cell: " << target.cell ) } } return target; } BattleTargetPair BattlePlanner::meleeUnitDefense( Arena & arena, const Unit & currentUnit ) const { BattleTargetPair target; const PositionValues valuesOfAttackPositions = evaluatePotentialAttackPositions( arena, currentUnit ); const Units friendly( arena.getForce( _myColor ).getUnits(), Units::REMOVE_INVALID_UNITS_AND_SPECIFIED_UNIT, ¤tUnit ); // Current unit can be under the influence of the Hypnotize spell const Units enemies( arena.getEnemyForce( _myColor ).getUnits(), Units::REMOVE_INVALID_UNITS_AND_SPECIFIED_UNIT, ¤tUnit ); // 1. Cover our archers and attack enemy units blocking them, if there are any. Units whose affiliation has been changed should not cover the archers, because // such units will block them instead of covering them. if ( currentUnit.GetArmyColor() == _myColor ) { const bool isAnyEnemyCanBeAttackedImmediately = std::any_of( enemies.begin(), enemies.end(), [¤tUnit, &valuesOfAttackPositions]( const Unit * enemy ) { assert( enemy != nullptr ); const MeleeAttackOutcome outcome = BestAttackOutcome( currentUnit, *enemy, valuesOfAttackPositions ); return outcome.canAttackImmediately; } ); // If the army has only one stack of archers, then there is no question about which stack to cover. If the army has three or more stacks of archers, then, // most likely, melee units will be able to cover these stacks only partially. In the case of two stacks of archers, the question arises: in which cases we // should try to cover both stacks, but partially, and in which cases we should cover just one stack, but completely. // // In the current implementation, if the value of one of these archer stacks is 2 or more times greater than the value of the other, then it is worth using // all melee units to create a more reliable cover for more powerful stack of archers. // // In the case of two stacks of archers, AI will tend to initially place them on the edges of the battlefield. Thus, for melee units, the maximum distance to // the stack of archers will be approximately 5 tiles. To fulfill the above condition, a distance of 5 tiles should give a penalty of 1/3 of the total value // of the archer stacks. // // For example, suppose we have one stack of archers with the value of 200 on the tile with the index of 0, and another stack of archers with the value of 100 // on the tile with the index of 88. In this case, a melee unit from the tile with the index of 66 should overcome about 5 tiles in order to reach the more // powerful stack of archers, but it should overcome just 1 tile to reach the less powerful stack. The penalty for each tile passed in its case will be // calculated as (200 + 100) / 15 = 20, and after the penalty is applied, the value of the more powerful stack of archers will be 200 - 20 * 5 = 100, and the // value of the less powerful stack will be 100 - 20 * 1 = 80, therefore, this melee unit will go to cover the more powerful stack of archers. // // However, if the strength of the archer stacks is comparable, for example, 150 vs 100, the penalty for each tile passed will be (150 + 100) / 15 = 16.66, // and after the penalty is applied, the value of the more powerful stack of archers will be 150 - 16.66 * 5 = 66,7, and the value of the less powerful stack // will be 100 - 16.66 * 1 = 83.34, therefore, this melee unit will go to cover the less powerful stack of archers, and both stacks will be only partially // covered. const double defenseDistanceModifier = _myRangedUnitsOnly / 15.0; double bestArcherValue = std::numeric_limits::lowest(); for ( const Unit * frnd : friendly ) { assert( frnd != nullptr ); if ( !frnd->isArchers() ) { continue; } const CellDistanceInfo bestCoverCellInfo = [&arena, ¤tUnit, frnd]() -> CellDistanceInfo { const Indexes nearbyIndexes = [¤tUnit, frnd]() { Indexes result; result.reserve( 8 ); const std::array priorityDirections = [¤tUnit, frnd]() -> std::array { const bool preferToCoverFromTheSide = [¤tUnit, frnd]() { // If the covering unit is not a wide unit, then using this unit to cover the shooter from the side does not give any advantage if ( !currentUnit.isWide() ) { return false; } // It is always better to use wide units to cover wide shooters from the sides if ( frnd->isWide() ) { return true; } assert( Board::isValidIndex( frnd->GetHeadIndex() ) ); // If an ordinary shooter is located on a tile that protrudes sideways, then using a wide unit to cover this shooter from the side does // not give any advantage if ( frnd->isReflect() ) { return ( ( frnd->GetHeadIndex() / ARENAW ) % 2 == 1 ); } return ( ( frnd->GetHeadIndex() / ARENAW ) % 2 == 0 ); }(); if ( preferToCoverFromTheSide ) { if ( frnd->isReflect() ) { return { TOP_LEFT, BOTTOM_LEFT, LEFT, TOP_RIGHT, BOTTOM_RIGHT, RIGHT }; } return { TOP_RIGHT, BOTTOM_RIGHT, RIGHT, TOP_LEFT, BOTTOM_LEFT, LEFT }; } if ( frnd->isReflect() ) { return { LEFT, TOP_LEFT, BOTTOM_LEFT, TOP_RIGHT, BOTTOM_RIGHT, RIGHT }; } return { RIGHT, TOP_RIGHT, BOTTOM_RIGHT, TOP_LEFT, BOTTOM_LEFT, LEFT }; }(); for ( const int32_t idx : std::array{ frnd->GetHeadIndex(), frnd->GetTailIndex() } ) { if ( !Board::isValidIndex( idx ) ) { continue; } for ( const int dir : priorityDirections ) { if ( !Board::isValidDirection( idx, dir ) ) { continue; } const int32_t nearbyIdx = Board::GetIndexDirection( idx, dir ); if ( std::find( result.begin(), result.end(), nearbyIdx ) != result.end() ) { continue; } result.push_back( nearbyIdx ); } } return result; }(); for ( const int32_t idx : nearbyIndexes ) { const Position pos = Position::GetPosition( currentUnit, idx ); if ( pos.GetHead() == nullptr ) { continue; } assert( pos.isValidForUnit( currentUnit ) ); assert( Board::GetDistance( pos, frnd->GetPosition() ) == 1 ); if ( !arena.isPositionReachable( currentUnit, pos, false ) ) { continue; } return { idx, arena.CalculateMoveDistance( currentUnit, pos ) }; } return {}; }(); const std::vector adjacentEnemies = [&enemies, frnd]() { std::vector result; result.reserve( enemies.size() ); for ( const Unit * enemy : enemies ) { assert( enemy != nullptr ); const uint32_t dist = Board::GetDistance( frnd->GetPosition(), enemy->GetPosition() ); assert( dist > 0 ); if ( dist != 1 ) { continue; } result.push_back( enemy ); } return result; }(); // If our archer is not blocked by enemy units, but the unit nevertheless cannot cover that archer, then ignore that archer if ( bestCoverCellInfo.idx == -1 && adjacentEnemies.empty() ) { continue; } // Either the unit can cover the friendly archer, or the archer is blocked by enemy units, which do not allow our unit to approach. As the distance to // estimate the archer's value, we take the smallest of the distance that must be overcome to cover the archer and the distance that must be overcome to // approach the nearest of the enemies blocking him. const auto [eitherFrndOrAdjEnemyIsReachable, dist] = [&arena, ¤tUnit, &bestCoverCellInfo, &adjacentEnemies]() { std::pair result{ bestCoverCellInfo.idx != -1, bestCoverCellInfo.dist }; for ( const Unit * enemy : adjacentEnemies ) { assert( enemy != nullptr ); const CellDistanceInfo nearestToEnemyCellInfo = findNearestCellNextToUnit( arena, currentUnit, *enemy ); if ( nearestToEnemyCellInfo.idx == -1 ) { continue; } if ( !result.first || nearestToEnemyCellInfo.dist < result.second ) { result = { true, nearestToEnemyCellInfo.dist }; } } return result; }(); // If the unit cannot cover the archer or approach any of the enemies blocking that archer, then ignore that archer if ( !eitherFrndOrAdjEnemyIsReachable ) { continue; } // If the unit cannot cover the archer or approach any of the enemies blocking that archer within two turns (according to a rough estimate), but there is // at least one enemy unit that can be immediately attacked by this unit, then ignore that archer. Very slow units (such as Hydra) should not waste time // covering archers far from them - especially if there are other enemy units nearby worthy of their attention. if ( isAnyEnemyCanBeAttackedImmediately && !currentUnit.isFlying() && dist > currentUnit.GetSpeed() * 2 ) { continue; } const double archerValue = frnd->GetStrength() - dist * defenseDistanceModifier; if ( archerValue < bestArcherValue ) { continue; } bestArcherValue = archerValue; target.cell = bestCoverCellInfo.idx; target.unit = nullptr; // If the archer is blocked by enemy units, it is necessary to attack them immediately, or at least take the best position to attack { MeleeAttackOutcome bestOutcome; for ( const Unit * enemy : adjacentEnemies ) { assert( enemy != nullptr ); const MeleeAttackOutcome outcome = BestAttackOutcome( currentUnit, *enemy, valuesOfAttackPositions ); if ( IsOutcomeImproved( outcome, bestOutcome ) ) { bestOutcome = outcome; target.cell = outcome.fromIndex; target.unit = outcome.canAttackImmediately ? enemy : nullptr; DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "- Set attack priority on " << enemy->GetName() << ", attack value: " << outcome.attackValue << ", position value: " << outcome.positionValue ) } } } // If we have reached this point, then the unit should be able to either cover the archer or approach any of the enemies blocking that archer - although // perhaps without performing an immediate attack assert( Board::isValidIndex( target.cell ) ); // The unit is going to attack one of the enemy units that blocked the archer, nothing else needs to be done if ( target.unit != nullptr ) { continue; } // It makes sense for a unit that ignores retaliation to attack neighboring enemy units, even if it is covering an archer, since in this case it will not // receive unnecessary retaliatory damage (which could affect the duration of the cover). Also, archers with the ability to shoot at area may not always // attack enemy units in close proximity to friendly units due to fear of friendly fire, so covering friendly units should help by attacking enemy units // next to them. if ( !currentUnit.isIgnoringRetaliation() && !frnd->isAbilityPresent( fheroes2::MonsterAbilityType::AREA_SHOT ) ) { continue; } // If the decision is made to attack one of the neighboring enemy units (if any) while covering the archer, then we should choose the best target { int32_t bestAttackValue = 0; for ( const Unit * enemy : enemies ) { assert( enemy != nullptr ); if ( !Board::CanAttackTargetFromPosition( currentUnit, *enemy, target.cell ) ) { continue; } const Position pos = Position::GetReachable( currentUnit, target.cell ); assert( pos.isValidForUnit( currentUnit ) ); const int32_t attackValue = optimalAttackValue( currentUnit, *enemy, pos ); if ( bestAttackValue < attackValue ) { bestAttackValue = attackValue; target.unit = enemy; DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "- Set attack priority on " << enemy->GetName() << ", attack value: " << attackValue ) } } } } } if ( target.cell != -1 ) { if ( target.unit ) { DEBUG_LOG( DBG_BATTLE, DBG_INFO, currentUnit.GetName() << " attacking " << target.unit->GetName() << " from cell " << target.cell ) } else { DEBUG_LOG( DBG_BATTLE, DBG_INFO, currentUnit.GetName() << " covering friendly archers, moving to cell " << target.cell ) } return target; } // 2. Otherwise, try to find a suitable target that can be attacked from our half of the battlefield { MeleeAttackOutcome bestOutcome; for ( const Unit * enemy : enemies ) { assert( enemy != nullptr ); const MeleeAttackOutcome outcome = BestAttackOutcome( currentUnit, *enemy, valuesOfAttackPositions, [this, ¤tUnit]( const Position & pos ) { return isPositionLocatedInDefendedArea( currentUnit, pos ); } ); if ( !Board::isValidIndex( outcome.fromIndex ) ) { continue; } if ( IsOutcomeImproved( outcome, bestOutcome ) ) { bestOutcome = outcome; target.cell = outcome.fromIndex; target.unit = outcome.canAttackImmediately ? enemy : nullptr; DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "- Set attack priority on " << enemy->GetName() << ", attack value: " << outcome.attackValue << ", position value: " << outcome.positionValue ) } } if ( target.unit ) { DEBUG_LOG( DBG_BATTLE, DBG_INFO, currentUnit.GetName() << " attacking " << target.unit->GetName() << " from cell " << target.cell ) } } return target; } Actions BattlePlanner::berserkTurn( Arena & arena, const Unit & currentUnit ) { assert( currentUnit.Modes( SP_BERSERKER ) ); Actions actions; Board * board = Arena::GetBoard(); assert( board != nullptr ); const uint32_t currentUnitUID = currentUnit.GetUID(); const std::vector nearestUnits = board->GetNearestTroops( ¤tUnit, {} ); assert( !nearestUnits.empty() ); // If the berserker is an archer, then just shoot at the nearest unit if ( currentUnit.isArchers() && !currentUnit.isHandFighting() ) { DEBUG_LOG( DBG_BATTLE, DBG_INFO, currentUnit.GetName() << " under Berserk spell, will shoot" ) const Unit * targetUnit = nearestUnits.front(); assert( targetUnit != nullptr ); actions.emplace_back( Command::ATTACK, currentUnitUID, targetUnit->GetUID(), -1, -1, 0 ); DEBUG_LOG( DBG_BATTLE, DBG_INFO, currentUnit.GetName() << " shooting at enemy " << targetUnit->GetName() ) return actions; } BattleTargetPair targetInfo; // First, try to find a unit nearby that can be attacked on this turn for ( const Unit * nearbyUnit : nearestUnits ) { assert( nearbyUnit != nullptr ); const CellDistanceInfo nearestCellInfo = findNearestCellNextToUnit( arena, currentUnit, *nearbyUnit ); if ( nearestCellInfo.idx == -1 ) { continue; } if ( !Board::CanAttackTargetFromPosition( currentUnit, *nearbyUnit, nearestCellInfo.idx ) ) { continue; } targetInfo.cell = nearestCellInfo.idx; targetInfo.unit = nearbyUnit; break; } // If there is no unit to attack during this turn, then find the nearest one to try to attack it during subsequent turns if ( targetInfo.cell == -1 ) { for ( const Unit * nearbyUnit : nearestUnits ) { assert( nearbyUnit != nullptr ); const CellDistanceInfo nearestCellInfo = findNearestCellNextToUnit( arena, currentUnit, *nearbyUnit ); if ( nearestCellInfo.idx == -1 ) { continue; } targetInfo.cell = nearestCellInfo.idx; break; } } // There is no reachable unit in sight, skip the turn if ( targetInfo.cell == -1 ) { actions.emplace_back( Command::SKIP, currentUnitUID ); DEBUG_LOG( DBG_BATTLE, DBG_INFO, currentUnit.GetName() << " skipping the turn" ) return actions; } DEBUG_LOG( DBG_BATTLE, DBG_INFO, currentUnit.GetName() << " under Berserk spell, target cell: " << targetInfo.cell ) // The target cell of the movement must be the cell that the unit's head will occupy const int32_t moveTargetIdx = getUnitMovementTarget( arena, currentUnit, targetInfo.cell ); if ( targetInfo.unit ) { const Unit * targetUnit = targetInfo.unit; actions.emplace_back( Command::ATTACK, currentUnitUID, targetUnit->GetUID(), ( currentUnit.GetHeadIndex() == moveTargetIdx ? -1 : moveTargetIdx ), -1, -1 ); DEBUG_LOG( DBG_BATTLE, DBG_INFO, currentUnit.GetName() << " attacking enemy " << targetUnit->GetName() << " from cell " << moveTargetIdx ) } else { actions.emplace_back( Command::MOVE, currentUnitUID, moveTargetIdx ); DEBUG_LOG( DBG_BATTLE, DBG_INFO, currentUnit.GetName() << " moving to cell " << moveTargetIdx ) } return actions; } bool BattlePlanner::isPositionLocatedInDefendedArea( const Unit & currentUnit, const Position & pos ) const { assert( pos.isReflect() == currentUnit.GetPosition().isReflect() && pos.GetHead() != nullptr ); // Units whose affiliation has been changed are still looking in the direction they originally looked const bool reflect = ( currentUnit.GetArmyColor() == _myColor ? currentUnit.isReflect() : !currentUnit.isReflect() ); const auto checkIdx = [this, reflect]( const int32_t idx ) { if ( _defendingCastle ) { return Board::isCastleIndex( idx ); } return ( Board::GetDistanceFromBoardEdgeAlongXAxis( idx, reflect ) <= ARENAW / 2 ); }; if ( !checkIdx( pos.GetHead()->GetIndex() ) ) { return false; } if ( pos.GetTail() && !checkIdx( pos.GetTail()->GetIndex() ) ) { return false; } return true; } void Normal::BattleTurn( Arena & arena, const Unit & currentUnit, Actions & actions ) { // Return immediately if our limit of turns has been exceeded if ( _battlePlanner.isLimitOfTurnsExceeded( arena, actions ) ) { return; } const Actions & plannedActions = _battlePlanner.planUnitTurn( arena, currentUnit ); actions.insert( actions.end(), plannedActions.begin(), plannedActions.end() ); } void Normal::battleBegins() { _battlePlanner.battleBegins(); } } fheroes2-1.0.12+dfsg/src/fheroes2/ai/normal/ai_normal_castle.cpp000066400000000000000000000402671456075706000244370ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include "ai.h" #include "ai_normal.h" #include "army.h" #include "army_troop.h" #include "battle_tower.h" #include "castle.h" #include "difficulty.h" #include "game.h" #include "heroes.h" #include "kingdom.h" #include "maps_tiles.h" #include "monster.h" #include "payment.h" #include "race.h" #include "resource.h" #include "world.h" #include "world_regions.h" namespace { struct BuildOrder { const building_t building = BUILD_NOTHING; const uint32_t priority = 1; BuildOrder() = default; BuildOrder( const building_t b, const uint32_t p ) : building( b ) , priority( p ) {} }; const std::vector defensiveStructures = { { BUILD_LEFTTURRET, 1 }, { BUILD_RIGHTTURRET, 1 }, { BUILD_MOAT, 1 }, { BUILD_CAPTAIN, 1 } }; const std::vector supportingDefensiveStructures = { { BUILD_MAGEGUILD1, 1 }, { BUILD_SPEC, 2 }, { BUILD_TAVERN, 1 } }; const std::vector & GetIncomeStructures( int type ) { static const std::vector standard = { { BUILD_CASTLE, 1 }, { BUILD_STATUE, 1 }, { BUILD_MARKETPLACE, 1 } }; static const std::vector warlock = { { BUILD_CASTLE, 1 }, { BUILD_STATUE, 1 }, { BUILD_MARKETPLACE, 1 }, { BUILD_SPEC, 1 } }; return ( type == Race::WRLK ) ? warlock : standard; } const std::vector & GetBuildOrder( int type ) { static const std::vector genericBuildOrder = { { BUILD_CASTLE, 2 }, { BUILD_STATUE, 1 }, { BUILD_MARKETPLACE, 1 }, { DWELLING_UPGRADE7, 1 }, { DWELLING_UPGRADE6, 1 }, { DWELLING_MONSTER6, 1 }, { DWELLING_UPGRADE5, 1 }, { DWELLING_MONSTER5, 1 }, { DWELLING_UPGRADE4, 1 }, { DWELLING_MONSTER4, 1 }, { DWELLING_UPGRADE3, 2 }, { DWELLING_MONSTER3, 2 }, { DWELLING_UPGRADE2, 3 }, { DWELLING_MONSTER2, 3 }, { DWELLING_MONSTER1, 4 }, { BUILD_MAGEGUILD1, 2 }, { BUILD_WEL2, 10 }, { BUILD_TAVERN, 5 }, { BUILD_THIEVESGUILD, 10 }, { BUILD_MAGEGUILD2, 3 }, { BUILD_MAGEGUILD3, 4 }, { BUILD_MAGEGUILD4, 5 }, { BUILD_MAGEGUILD5, 5 }, { BUILD_SHIPYARD, 4 } }; static const std::vector barbarianBuildOrder = { { BUILD_CASTLE, 2 }, { BUILD_STATUE, 1 }, { BUILD_MARKETPLACE, 1 }, { DWELLING_MONSTER6, 1 }, { DWELLING_UPGRADE5, 1 }, { DWELLING_MONSTER5, 1 }, { DWELLING_UPGRADE4, 1 }, { DWELLING_MONSTER4, 1 }, { DWELLING_MONSTER3, 1 }, { DWELLING_UPGRADE2, 2 }, { DWELLING_MONSTER2, 2 }, { DWELLING_MONSTER1, 4 }, { BUILD_MAGEGUILD1, 3 }, { BUILD_WEL2, 10 }, { BUILD_TAVERN, 5 }, { BUILD_THIEVESGUILD, 10 }, { BUILD_MAGEGUILD2, 4 }, { BUILD_MAGEGUILD3, 5 }, { BUILD_MAGEGUILD4, 6 }, { BUILD_MAGEGUILD5, 7 }, { BUILD_SHIPYARD, 4 } }; static const std::vector sorceressBuildOrder = { { BUILD_CASTLE, 2 }, { BUILD_STATUE, 1 }, { BUILD_MARKETPLACE, 1 }, { DWELLING_MONSTER6, 1 }, { DWELLING_MONSTER5, 1 }, { DWELLING_MONSTER4, 1 }, { BUILD_MAGEGUILD1, 1 }, { DWELLING_MONSTER3, 1 }, { DWELLING_UPGRADE4, 1 }, { DWELLING_UPGRADE3, 2 }, { DWELLING_UPGRADE2, 5 }, { DWELLING_MONSTER2, 2 }, { BUILD_TAVERN, 2 }, { DWELLING_MONSTER1, 4 }, { BUILD_WEL2, 10 }, { BUILD_THIEVESGUILD, 10 }, { BUILD_MAGEGUILD2, 3 }, { BUILD_MAGEGUILD3, 4 }, { BUILD_MAGEGUILD4, 5 }, { BUILD_MAGEGUILD5, 5 }, { BUILD_SHIPYARD, 4 } }; // De-prioritizing dwelling 5 (you can reach 6 without it), 1 and upgrades of 3 and 4 // Well, tavern and Archery upgrade are more important static const std::vector knightBuildOrder = { { BUILD_CASTLE, 2 }, { BUILD_STATUE, 1 }, { BUILD_MARKETPLACE, 1 }, { DWELLING_UPGRADE6, 2 }, { DWELLING_MONSTER6, 1 }, { DWELLING_UPGRADE5, 2 }, { DWELLING_MONSTER5, 2 }, { DWELLING_UPGRADE4, 2 }, { DWELLING_MONSTER4, 1 }, { DWELLING_UPGRADE3, 2 }, { DWELLING_MONSTER3, 1 }, { DWELLING_UPGRADE2, 1 }, { DWELLING_MONSTER2, 3 }, { DWELLING_MONSTER1, 4 }, { BUILD_WELL, 1 }, { BUILD_TAVERN, 1 }, { BUILD_MAGEGUILD1, 2 }, { BUILD_MAGEGUILD2, 3 }, { BUILD_MAGEGUILD3, 5 }, { BUILD_MAGEGUILD4, 5 }, { BUILD_MAGEGUILD5, 5 }, { BUILD_SPEC, 5 }, { BUILD_THIEVESGUILD, 10 }, { BUILD_WEL2, 20 }, { BUILD_SHIPYARD, 4 } }; // Priority on Dwellings 5/6 and Mage guild level 2 static const std::vector necromancerBuildOrder = { { BUILD_CASTLE, 2 }, { BUILD_STATUE, 1 }, { BUILD_MARKETPLACE, 1 }, { DWELLING_UPGRADE6, 1 }, { DWELLING_MONSTER6, 1 }, { DWELLING_UPGRADE5, 2 }, { DWELLING_MONSTER5, 1 }, { BUILD_MAGEGUILD1, 1 }, { DWELLING_UPGRADE4, 2 }, { DWELLING_MONSTER4, 1 }, { DWELLING_UPGRADE3, 3 }, { DWELLING_MONSTER3, 3 }, { DWELLING_UPGRADE2, 4 }, { DWELLING_MONSTER2, 2 }, { DWELLING_MONSTER1, 3 }, { BUILD_MAGEGUILD2, 2 }, { BUILD_THIEVESGUILD, 2 }, { BUILD_WEL2, 8 }, { BUILD_MAGEGUILD3, 4 }, { BUILD_MAGEGUILD4, 5 }, { BUILD_MAGEGUILD5, 5 }, { BUILD_SHRINE, 10 }, { BUILD_SHIPYARD, 4 } }; // Priority on Mage tower/guild and library static const std::vector wizardBuildOrder = { { BUILD_CASTLE, 2 }, { BUILD_STATUE, 1 }, { BUILD_MARKETPLACE, 1 }, { DWELLING_UPGRADE6, 1 }, { DWELLING_MONSTER6, 1 }, { DWELLING_UPGRADE5, 1 }, { DWELLING_MONSTER5, 1 }, { DWELLING_MONSTER4, 1 }, { DWELLING_MONSTER3, 1 }, { DWELLING_MONSTER2, 1 }, { DWELLING_MONSTER1, 1 }, { BUILD_MAGEGUILD1, 1 }, { DWELLING_UPGRADE3, 4 }, { BUILD_SPEC, 2 }, { BUILD_WEL2, 8 }, { BUILD_MAGEGUILD2, 3 }, { BUILD_MAGEGUILD3, 4 }, { BUILD_MAGEGUILD4, 4 }, { BUILD_MAGEGUILD5, 4 }, { BUILD_TAVERN, 10 }, { BUILD_THIEVESGUILD, 10 }, { BUILD_SHIPYARD, 4 } }; switch ( type ) { case Race::KNGT: return knightBuildOrder; case Race::NECR: return necromancerBuildOrder; case Race::WZRD: return wizardBuildOrder; case Race::SORC: return sorceressBuildOrder; case Race::BARB: return barbarianBuildOrder; default: break; } return genericBuildOrder; } } namespace AI { bool Build( Castle & castle, const std::vector & buildOrderList, const uint32_t multiplier = 1 ) { const int gameDifficulty = Game::getDifficulty(); const bool isGameCampaign = Game::isCampaign(); for ( const BuildOrder & order : buildOrderList ) { if ( !Difficulty::allowAIToBuildCastleBuilding( gameDifficulty, isGameCampaign, order.building ) ) { continue; } const uint32_t fundsMultiplier = order.priority * multiplier; if ( fundsMultiplier == 1 ) { if ( BuildIfPossible( castle, order.building ) ) { return true; } } else if ( BuildIfEnoughFunds( castle, order.building, fundsMultiplier ) ) { return true; } } return false; } bool CastleDevelopment( Castle & castle, int safetyFactor, int spellLevel ) { if ( !Difficulty::allowAIToDevelopCastlesOnDay( Game::getDifficulty(), Game::isCampaign(), world.CountDay() ) ) { return false; } if ( castle.isCastle() && !castle.isBuild( BUILD_WELL ) && world.CountDay() > 6 ) { // If you can't build Well, you won't be able to build anything else return BuildIfPossible( castle, BUILD_WELL ); } if ( Build( castle, GetIncomeStructures( castle.GetRace() ) ) ) { return true; } const size_t neighbourRegions = world.getRegion( world.GetTiles( castle.GetIndex() ).GetRegion() ).getNeighboursCount(); const bool islandOrPeninsula = neighbourRegions < 3; // Force building a shipyard, +1 to cost check since we can have 0 neighbours if ( islandOrPeninsula && BuildIfEnoughFunds( castle, BUILD_SHIPYARD, static_cast( neighbourRegions + 1 ) ) ) { return true; } if ( Build( castle, GetBuildOrder( castle.GetRace() ) ) ) { return true; } if ( castle.GetLevelMageGuild() < spellLevel && safetyFactor > 0 ) { static const std::vector magicGuildUpgrades = { { BUILD_MAGEGUILD2, 2 }, { BUILD_MAGEGUILD3, 2 }, { BUILD_MAGEGUILD4, 1 }, { BUILD_MAGEGUILD5, 1 } }; if ( Build( castle, magicGuildUpgrades ) ) { return true; } } // Check whether it is worth buying a boat const bool buyBoat = [&castle, islandOrPeninsula]() { // Check if it is possible to build a boat in this castle at all (except for the solvency check) if ( !castle.AllowBuyBoat( false ) ) { return false; } Kingdom & kingdom = castle.GetKingdom(); // If there are no active heroes in the kingdom, then there will be no one to use the boat if ( kingdom.GetHeroes().empty() ) { return false; } // Perhaps the kingdom already has the necessary supply of resources const Funds requiredFunds = PaymentConditions::BuyBoat() * ( islandOrPeninsula ? 2 : 4 ); if ( kingdom.AllowPayment( requiredFunds ) ) { return true; } // Even if the kingdom does not have the necessary supply of these resources right now, there may be enough resources of another type available to get the // missing resources as a result of resource exchange if ( !calculateMarketplaceTransaction( kingdom, requiredFunds ) ) { return false; } // There are resources available to buy a boat right now if ( kingdom.AllowPayment( PaymentConditions::BuyBoat() ) ) { return true; } // There are no available resources, but it is possible to make a resource exchange if ( tradeAtMarketplace( kingdom, PaymentConditions::BuyBoat() ) ) { return true; } assert( 0 ); return false; }(); // If yes, then buy a boat if ( buyBoat ) { castle.BuyBoat(); } if ( Build( castle, defensiveStructures, 10 ) ) { return true; } return Build( castle, supportingDefensiveStructures, 10 ); } void Normal::CastlePreBattle( Castle & castle ) { Heroes * hero = world.GetHero( castle ); if ( hero == nullptr ) { return; } Army & army = hero->GetArmy(); if ( !army.ArrangeForCastleDefense( castle.GetArmy() ) ) { return; } // Optimization cannot be performed if we have not received any reinforcements from the garrison, otherwise the actual placement of units during the battle will // differ from that observed by the enemy player before the start of the battle OptimizeTroopsOrder( army ); } void Normal::updateKingdomBudget( const Kingdom & kingdom ) { // clean up first for ( BudgetEntry & budgetEntry : _budget ) { budgetEntry.reset(); } const Funds & kindgomFunds = kingdom.GetFunds(); Funds requirements; for ( const Castle * castle : kingdom.GetCastles() ) { if ( !castle ) { continue; } const int race = castle->GetRace(); const std::vector & buildOrder = GetBuildOrder( race ); for ( const BuildOrder & order : buildOrder ) { const int status = castle->CheckBuyBuilding( order.building ); if ( status == LACK_RESOURCES ) { Funds missing = PaymentConditions::BuyBuilding( race, order.building ) - kindgomFunds; requirements = requirements.max( missing ); } } if ( castle->isBuild( DWELLING_MONSTER6 ) ) { Funds bestUnitCost = Monster( race, DWELLING_MONSTER6 ).GetUpgrade().GetCost(); for ( BudgetEntry & budgetEntry : _budget ) { if ( bestUnitCost.Get( budgetEntry.resource ) > 0 ) { budgetEntry.recurringCost = true; } } } } for ( BudgetEntry & budgetEntry : _budget ) { budgetEntry.missing = requirements.Get( budgetEntry.resource ); if ( budgetEntry.missing ) { budgetEntry.priority = true; } } } void Normal::CastleTurn( Castle & castle, const bool defensiveStrategy ) { if ( defensiveStrategy ) { // Avoid building monster dwellings when defensive as they might fall into enemy's hands, unless we have a lot of resources. const Kingdom & kingdom = castle.GetKingdom(); // TODO: check if we can upgrade monsters. It is much cheaper (except Giants into Titans) to upgrade monsters than buy new ones. Troops possibleReinforcement = castle.getAvailableArmy( kingdom.GetFunds() ); double possibleReinforcementStrength = possibleReinforcement.GetStrength(); // A very rough estimation of strength. We measure the strength of possible army to hire with the strength of purchasing a turret. const Battle::Tower tower( castle, Battle::TowerType::TWR_RIGHT, 0 ); const Troop towerMonster( Monster::ARCHER, tower.GetCount() ); const double towerStrength = towerMonster.GetStrength(); if ( possibleReinforcementStrength > towerStrength ) { castle.recruitBestAvailable( kingdom.GetFunds() ); OptimizeTroopsOrder( castle.GetArmy() ); } if ( castle.GetActualArmy().getTotalCount() > 0 ) { Build( castle, defensiveStructures ); } castle.recruitBestAvailable( kingdom.GetFunds() ); OptimizeTroopsOrder( castle.GetArmy() ); if ( castle.GetActualArmy().getTotalCount() > 0 ) { Build( castle, supportingDefensiveStructures ); } } else { const uint32_t regionID = world.GetTiles( castle.GetIndex() ).GetRegion(); const RegionStats & stats = _regions[regionID]; CastleDevelopment( castle, stats.safetyFactor, stats.spellLevel ); } } } fheroes2-1.0.12+dfsg/src/fheroes2/ai/normal/ai_normal_hero.cpp000066400000000000000000003336311456075706000241210ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ai.h" #include "ai_normal.h" #include "army.h" #include "army_troop.h" #include "artifact.h" #include "castle.h" #include "color.h" #include "difficulty.h" #include "direction.h" #include "game.h" #include "game_interface.h" #include "game_over.h" #include "game_static.h" #include "gamedefs.h" #include "ground.h" #include "heroes.h" #include "interface_status.h" #include "kingdom.h" #include "logging.h" #include "luck.h" #include "maps.h" #include "maps_fileinfo.h" #include "maps_tiles.h" #include "maps_tiles_helper.h" #include "math_base.h" #include "monster.h" #include "morale.h" #include "mp2.h" #include "pairs.h" #include "payment.h" #include "players.h" #include "profit.h" #include "rand.h" #include "resource.h" #include "route.h" #include "settings.h" #include "skill.h" #include "spell.h" #include "visit.h" #include "world.h" #include "world_pathfinding.h" namespace { bool isHeroWhoseDefeatIsVictoryConditionForHumanInCastle( const Castle * castle ) { assert( castle != nullptr ); const Heroes * hero = castle->GetHero(); if ( hero && hero == world.GetHeroesCondWins() ) { return true; } return false; } bool isFindArtifactVictoryConditionForHuman( const Artifact & art ) { assert( art.isValid() ); const Maps::FileInfo & mapInfo = Settings::Get().getCurrentMapInfo(); if ( ( mapInfo.ConditionWins() & GameOver::WINS_ARTIFACT ) == 0 ) { return false; } if ( mapInfo.WinsFindUltimateArtifact() ) { return art.isUltimate(); } return ( art.GetID() == mapInfo.WinsFindArtifactID() ); } bool isCastleLossConditionForHuman( const Castle * castle ) { assert( castle != nullptr ); const Maps::FileInfo & mapInfo = Settings::Get().getCurrentMapInfo(); const bool isSinglePlayer = ( Colors( Players::HumanColors() ).size() == 1 ); if ( isSinglePlayer && ( mapInfo.ConditionLoss() & GameOver::LOSS_TOWN ) != 0 && castle->GetCenter() == mapInfo.LossMapsPositionObject() ) { // It is a loss town condition for human. return true; } if ( mapInfo.WinsCompAlsoWins() && ( mapInfo.ConditionWins() & GameOver::WINS_TOWN ) != 0 && castle->GetCenter() == mapInfo.WinsMapsPositionObject() ) { // It is a town capture winning condition for AI. return true; } return false; } uint32_t getDistanceToObject( const Heroes & hero, const AIWorldPathfinder & pathfinder, const int32_t index ) { const uint32_t dist = pathfinder.getDistance( index ); const std::list dimensionDoorSteps = pathfinder.getDimensionDoorPath( hero, index ); if ( dimensionDoorSteps.empty() ) { return dist; } const uint32_t dimensionDoorDist = Route::calculatePathPenalty( dimensionDoorSteps ); assert( dimensionDoorDist > 0 ); if ( dist == 0 || dimensionDoorDist < dist / 2 ) { return dimensionDoorDist; } return dist; } bool AIShouldVisitCastle( const Heroes & hero, int castleIndex, const double heroArmyStrength ) { const Castle * castle = world.getCastleEntrance( Maps::GetPoint( castleIndex ) ); if ( castle == nullptr ) { // How is it possible that a castle does not exist? assert( 0 ); return false; } if ( hero.GetColor() == castle->GetColor() ) { return castle->GetHero() == nullptr; } if ( hero.isFriends( castle->GetColor() ) ) { return false; } // WINS_HERO victory condition does not apply to AI-controlled players, we have to ignore the castle with this hero // to keep him alive for the human player if ( isHeroWhoseDefeatIsVictoryConditionForHumanInCastle( castle ) ) { return false; } const double advantage = hero.isLosingGame() ? AI::ARMY_ADVANTAGE_DESPERATE : AI::ARMY_ADVANTAGE_MEDIUM; const double castleStrength = castle->GetGarrisonStrength( hero ) * advantage; return heroArmyStrength > castleStrength; } bool isHeroStrongerThan( const Maps::Tiles & tile, const MP2::MapObjectType objectType, AI::Normal & ai, const double heroArmyStrength, const double targetStrengthMultiplier ) { return heroArmyStrength > ai.getTargetArmyStrength( tile, objectType ) * targetStrengthMultiplier; } bool isArmyValuableToObtain( const Troop & monster, double armyStrengthThreshold, const bool armyHasMonster ) { if ( armyHasMonster ) { // Since the army has the same monster the limit must be reduced. armyStrengthThreshold /= 2; } // Do not even care about this monster if it brings no visible advantage to the army. return monster.GetStrength() > armyStrengthThreshold; } bool isArmyValuableToHire( const Army & army, const Kingdom & kingdom, const Maps::Tiles & tile, const double armyStrengthThreshold ) { const Troop & troop = getTroopFromTile( tile ); if ( !troop.isValid() ) { return false; } const bool armyHasMonster = army.HasMonster( troop.GetMonster() ); if ( !armyHasMonster && army.isFullHouse() && army.areAllTroopsUnique() ) { return false; } const Funds singleMonsterCost = troop.GetCost(); uint32_t recruitTroopCount = kingdom.GetFunds().getLowestQuotient( singleMonsterCost ); if ( recruitTroopCount == 0 ) { // We do not have resources to hire even a single creature. return false; } const uint32_t availableTroopCount = troop.GetCount(); if ( recruitTroopCount > availableTroopCount ) { recruitTroopCount = availableTroopCount; } const Troop troopToHire{ troop.GetID(), recruitTroopCount }; return isArmyValuableToObtain( troopToHire, armyStrengthThreshold, armyHasMonster ); } bool isSpellUsedByAI( const int spellId ) { // TODO: All these spells are not used by AI at the moment. switch ( spellId ) { case Spell::EARTHQUAKE: case Spell::HAUNT: case Spell::IDENTIFYHERO: case Spell::SETAGUARDIAN: case Spell::SETEGUARDIAN: case Spell::SETFGUARDIAN: case Spell::SETWGUARDIAN: case Spell::TELEPORT: case Spell::VIEWARTIFACTS: case Spell::VIEWHEROES: case Spell::VIEWMINES: case Spell::VIEWRESOURCES: case Spell::VIEWTOWNS: case Spell::VISIONS: return false; default: break; } return true; } bool HeroesValidObject( const Heroes & hero, const double heroArmyStrength, const int32_t index, const AIWorldPathfinder & pathfinder, AI::Normal & ai, const double armyStrengthThreshold, const bool underHero ) { const Maps::Tiles & tile = world.GetTiles( index ); const MP2::MapObjectType objectType = tile.GetObject( !underHero ); // WINS_ARTIFACT victory condition does not apply to AI-controlled players, we should leave this artifact untouched for the human player if ( MP2::isArtifactObject( objectType ) ) { const Artifact art = getArtifactFromTile( tile ); if ( art.isValid() && isFindArtifactVictoryConditionForHuman( art ) ) { return false; } } const Army & army = hero.GetArmy(); const Kingdom & kingdom = hero.GetKingdom(); // If you add a new object to a group of objects sort them alphabetically. switch ( objectType ) { case MP2::OBJ_BOTTLE: case MP2::OBJ_CAMPFIRE: case MP2::OBJ_FLOTSAM: case MP2::OBJ_RESOURCE: case MP2::OBJ_SEA_CHEST: case MP2::OBJ_SHIPWRECK_SURVIVOR: case MP2::OBJ_TREASURE_CHEST: return true; case MP2::OBJ_BUOY: case MP2::OBJ_TEMPLE: return !hero.isObjectTypeVisited( objectType ) && hero.GetMorale() < Morale::BLOOD && !army.AllTroopsAreUndead(); case MP2::OBJ_MAGELLANS_MAPS: // TODO: avoid hardcoded resource values for objects. return !hero.isObjectTypeVisited( objectType, Visit::GLOBAL ) && kingdom.AllowPayment( { Resource::GOLD, 1000 } ); case MP2::OBJ_ALCHEMIST_LAB: case MP2::OBJ_LIGHTHOUSE: case MP2::OBJ_MINE: case MP2::OBJ_SAWMILL: if ( !hero.isFriends( getColorFromTile( tile ) ) ) { if ( isCaptureObjectProtected( tile ) ) { return isHeroStrongerThan( tile, objectType, ai, heroArmyStrength, AI::ARMY_ADVANTAGE_SMALL ); } return true; } break; case MP2::OBJ_ABANDONED_MINE: return isHeroStrongerThan( tile, objectType, ai, heroArmyStrength, AI::ARMY_ADVANTAGE_LARGE ); case MP2::OBJ_LEAN_TO: case MP2::OBJ_MAGIC_GARDEN: case MP2::OBJ_SKELETON: case MP2::OBJ_WAGON: case MP2::OBJ_WATER_WHEEL: case MP2::OBJ_WINDMILL: return doesTileContainValuableItems( tile ); case MP2::OBJ_ARTIFACT: { if ( hero.IsFullBagArtifacts() ) return false; const Maps::ArtifactCaptureCondition condition = getArtifactCaptureCondition( tile ); if ( condition == Maps::ArtifactCaptureCondition::PAY_2000_GOLD || condition == Maps::ArtifactCaptureCondition::PAY_2500_GOLD_AND_3_RESOURCES || condition == Maps::ArtifactCaptureCondition::PAY_3000_GOLD_AND_5_RESOURCES ) { return kingdom.AllowPayment( getArtifactResourceRequirement( tile ) ); } if ( condition == Maps::ArtifactCaptureCondition::HAVE_WISDOM_SKILL || condition == Maps::ArtifactCaptureCondition::HAVE_LEADERSHIP_SKILL ) { return hero.HasSecondarySkill( getArtifactSecondarySkillRequirement( tile ).Skill() ); } // 6 - 50 rogues, 7 - 1 gin, 8,9,10,11,12,13 - 1 monster level4 if ( condition >= Maps::ArtifactCaptureCondition::FIGHT_50_ROGUES && condition <= Maps::ArtifactCaptureCondition::FIGHT_1_BONE_DRAGON ) { return isHeroStrongerThan( tile, objectType, ai, heroArmyStrength, AI::ARMY_ADVANTAGE_LARGE ); } // No conditions to capture an artifact exist. return true; } case MP2::OBJ_OBSERVATION_TOWER: { const int32_t fogCountToUncoverByTower = Maps::getFogTileCountToBeRevealed( index, GameStatic::getFogDiscoveryDistance( GameStatic::FogDiscoveryType::OBSERVATION_TOWER ), hero.GetColor() ); return fogCountToUncoverByTower > 0; } case MP2::OBJ_OBELISK: // TODO: add the logic to dig an Ultimate artifact when a digging tile is visible. // But for now AI should not waste time visiting Obelisks. return false; case MP2::OBJ_BARRIER: return kingdom.IsVisitTravelersTent( getColorFromTile( tile ) ); case MP2::OBJ_TRAVELLER_TENT: return !kingdom.IsVisitTravelersTent( getColorFromTile( tile ) ); case MP2::OBJ_SHRINE_FIRST_CIRCLE: case MP2::OBJ_SHRINE_SECOND_CIRCLE: case MP2::OBJ_SHRINE_THIRD_CIRCLE: { const Spell & spell = getSpellFromTile( tile ); if ( !spell.isValid() ) { // The spell cannot be invalid! assert( 0 ); return false; } if ( !hero.HaveSpellBook() ) { return false; } if ( spell.Level() == 3 && hero.GetLevelSkill( Skill::Secondary::WISDOM ) == Skill::Level::NONE ) { // No reason to visit this shrine as the hero cannot learn this spell. return false; } if ( !hero.isVisited( tile, Visit::GLOBAL ) ) { // This shrine has not been visited by any hero. It's worth to do it. return true; } if ( hero.HaveSpell( spell, true ) || !isSpellUsedByAI( spell.GetID() ) ) { return false; } return true; } // Arena allows to visit only one time for the whole map. case MP2::OBJ_ARENA: return !hero.isObjectTypeVisited( objectType ); // On-time visit free Primary Skill or Experience object. case MP2::OBJ_FORT: case MP2::OBJ_GAZEBO: case MP2::OBJ_MERCENARY_CAMP: case MP2::OBJ_STANDING_STONES: case MP2::OBJ_WITCH_DOCTORS_HUT: return !hero.isVisited( tile ); // One time visit Secondary Skill object. case MP2::OBJ_WITCHS_HUT: { const Skill::Secondary & skill = getSecondarySkillFromWitchsHut( tile ); const int skillType = skill.Skill(); if ( !skill.isValid() ) { // How is it possible that no skill exist? assert( 0 ); return false; } if ( hero.HasMaxSecondarySkill() ) { return false; } if ( !hero.isVisited( tile, Visit::GLOBAL ) ) { // The AI heroes should not have prior knowledge what skill this object has. return true; } if ( hero.HasSecondarySkill( skillType ) ) { return false; } if ( army.AllTroopsAreUndead() && skillType == Skill::Secondary::LEADERSHIP ) { // For undead army it's pointless to have Leadership skill. return false; } if ( !hero.HaveSpellBook() && ( skillType == Skill::Secondary::MYSTICISM || skillType == Skill::Secondary::EAGLE_EYE ) ) { // It's useless to have Mysticism with no magic book in hands. return false; } return true; } case MP2::OBJ_TREE_OF_KNOWLEDGE: if ( !hero.isVisited( tile ) ) { const Funds & rc = getTreeOfKnowledgeRequirement( tile ); // If the payment is required do not waste all resources from the kingdom. Use them wisely. if ( rc.GetValidItemsCount() == 0 || kingdom.AllowPayment( rc * 5 ) ) { return true; } } break; // Objects increasing Luck. case MP2::OBJ_FAERIE_RING: case MP2::OBJ_FOUNTAIN: case MP2::OBJ_IDOL: case MP2::OBJ_MERMAID: return !hero.isObjectTypeVisited( objectType ) && hero.GetLuck() < Luck::IRISH; // Objects increasing Movement points and Morale. case MP2::OBJ_OASIS: case MP2::OBJ_WATERING_HOLE: { if ( hero.isObjectTypeVisited( objectType ) ) { return false; } const uint32_t distance = getDistanceToObject( hero, pathfinder, index ); if ( distance == 0 ) { return false; } const double movementPenalty = 2.0 * distance; return movementPenalty < GameStatic::getMovementPointBonus( objectType ) || hero.GetMorale() < Morale::BLOOD; } case MP2::OBJ_MAGIC_WELL: { if ( hero.isObjectTypeVisited( objectType ) ) { return false; } if ( !hero.HaveSpellBook() ) { return false; } if ( hero.GetSpellPoints() >= hero.GetMaxSpellPoints() ) { return false; } const uint32_t distance = getDistanceToObject( hero, pathfinder, index ); if ( distance == 0 ) { return false; } if ( distance > hero.GetMovePoints() && hero.getDailyRestoredSpellPoints() + hero.GetSpellPoints() >= hero.GetMaxSpellPoints() ) { // The Well is located at a distance which cannot be reached by the hero at the current turn. // But if the hero will restore all spell points by the next day there is no reason to even to visit the Well. return false; } return true; } case MP2::OBJ_ARTESIAN_SPRING: return !world.isAnyKingdomVisited( objectType, index ) && hero.HaveSpellBook() && hero.GetSpellPoints() < 2 * hero.GetMaxSpellPoints(); case MP2::OBJ_XANADU: return !hero.isVisited( tile ) && GameStatic::isHeroWorthyToVisitXanadu( hero ); // Dwellings with free army. case MP2::OBJ_ARCHER_HOUSE: case MP2::OBJ_CAVE: case MP2::OBJ_DWARF_COTTAGE: case MP2::OBJ_EXCAVATION: case MP2::OBJ_GOBLIN_HUT: case MP2::OBJ_HALFLING_HOLE: case MP2::OBJ_PEASANT_HUT: case MP2::OBJ_TREE_HOUSE: case MP2::OBJ_WATCH_TOWER: { const Troop & troop = getTroopFromTile( tile ); if ( !troop.isValid() ) { return false; } const bool armyHasMonster = army.HasMonster( troop.GetMonster() ); if ( !armyHasMonster && army.isFullHouse() && army.areAllTroopsUnique() ) { return false; } return isArmyValuableToObtain( troop, armyStrengthThreshold, armyHasMonster ); } // Dwellings where AI can recruit an army. case MP2::OBJ_AIR_ALTAR: case MP2::OBJ_BARROW_MOUNDS: case MP2::OBJ_DESERT_TENT: case MP2::OBJ_EARTH_ALTAR: case MP2::OBJ_FIRE_ALTAR: case MP2::OBJ_GENIE_LAMP: case MP2::OBJ_RUINS: case MP2::OBJ_TREE_CITY: case MP2::OBJ_WAGON_CAMP: case MP2::OBJ_WATER_ALTAR: return isArmyValuableToHire( army, kingdom, tile, armyStrengthThreshold ); // Dwellings where AI might fight monsters first before recruiting them. case MP2::OBJ_CITY_OF_DEAD: case MP2::OBJ_DRAGON_CITY: case MP2::OBJ_TROLL_BRIDGE: { if ( Color::NONE == getColorFromTile( tile ) ) { return isHeroStrongerThan( tile, objectType, ai, heroArmyStrength, AI::ARMY_ADVANTAGE_MEDIUM ); } return isArmyValuableToHire( army, kingdom, tile, armyStrengthThreshold ); } // Free army upgrade objects. case MP2::OBJ_FREEMANS_FOUNDRY: return army.HasMonster( Monster::PIKEMAN ) || army.HasMonster( Monster::SWORDSMAN ) || army.HasMonster( Monster::IRON_GOLEM ); case MP2::OBJ_HILL_FORT: return army.HasMonster( Monster::DWARF ) || army.HasMonster( Monster::ORC ) || army.HasMonster( Monster::OGRE ); // Free army upgrade and extra movement points for the rest of the week. case MP2::OBJ_STABLES: { if ( army.HasMonster( Monster::CAVALRY ) ) { return true; } if ( hero.isObjectTypeVisited( objectType ) ) { return false; } const uint32_t distance = getDistanceToObject( hero, pathfinder, index ); if ( distance == 0 ) { return false; } const int daysActive = DAYOFWEEK - world.GetDay() + 1; const double movementBonus = daysActive * GameStatic::getMovementPointBonus( objectType ) - 2.0 * distance; return movementBonus > 0; } // Objects that give goods but curse with bad morale when visiting them for subsequent times. case MP2::OBJ_DERELICT_SHIP: case MP2::OBJ_GRAVEYARD: case MP2::OBJ_SHIPWRECK: if ( !hero.isVisited( tile, Visit::GLOBAL ) && doesTileContainValuableItems( tile ) ) { Army enemy( tile ); return enemy.isValid() && isHeroStrongerThan( tile, objectType, ai, heroArmyStrength, 2 ); } break; case MP2::OBJ_PYRAMID: if ( !hero.isVisited( tile, Visit::GLOBAL ) && doesTileContainValuableItems( tile ) ) { Army enemy( tile ); return enemy.isValid() && Skill::Level::EXPERT == hero.GetLevelSkill( Skill::Secondary::WISDOM ) && isHeroStrongerThan( tile, objectType, ai, heroArmyStrength, AI::ARMY_ADVANTAGE_LARGE ); } break; case MP2::OBJ_DAEMON_CAVE: if ( doesTileContainValuableItems( tile ) ) { // AI always chooses to fight the demon's servants and doesn't roll the dice return isHeroStrongerThan( tile, objectType, ai, heroArmyStrength, AI::ARMY_ADVANTAGE_MEDIUM ); } break; case MP2::OBJ_MONSTER: return isHeroStrongerThan( tile, objectType, ai, heroArmyStrength, ( hero.isLosingGame() ? 1.0 : AI::ARMY_ADVANTAGE_MEDIUM ) ); case MP2::OBJ_HERO: { const Heroes * otherHero = tile.getHero(); assert( otherHero != nullptr ); const bool otherHeroInCastle = ( otherHero->inCastle() != nullptr ); if ( hero.GetColor() == otherHero->GetColor() ) { if ( hero.hasMetWithHero( otherHero->GetID() ) ) { return false; } return !otherHeroInCastle; } if ( hero.isFriends( otherHero->GetColor() ) ) { return false; } // WINS_HERO victory condition does not apply to AI-controlled players, we have to keep this hero alive for the human player if ( otherHero == world.GetHeroesCondWins() ) { return false; } if ( otherHeroInCastle ) { return AIShouldVisitCastle( hero, index, heroArmyStrength ); } return army.isStrongerThan( otherHero->GetArmy(), hero.isLosingGame() ? AI::ARMY_ADVANTAGE_DESPERATE : AI::ARMY_ADVANTAGE_SMALL ); } case MP2::OBJ_CASTLE: return AIShouldVisitCastle( hero, index, heroArmyStrength ); case MP2::OBJ_JAIL: return kingdom.GetHeroes().size() < Kingdom::GetMaxHeroes(); case MP2::OBJ_HUT_OF_MAGI: return !hero.isObjectTypeVisited( objectType, Visit::GLOBAL ) && Maps::doesObjectExistOnMap( MP2::OBJ_EYE_OF_MAGI ); case MP2::OBJ_ALCHEMIST_TOWER: { const BagArtifacts & bag = hero.GetBagArtifacts(); const uint32_t cursed = static_cast( std::count_if( bag.begin(), bag.end(), []( const Artifact & art ) { return art.containsCurses(); } ) ); if ( cursed == 0 ) { return false; } const Funds payment = PaymentConditions::ForAlchemist(); return kingdom.AllowPayment( payment ); } // AI should never consider a boat as a destination point. It uses them only to make a path. case MP2::OBJ_BOAT: // Eye of Magi is not an action object at all. case MP2::OBJ_EYE_OF_MAGI: // No use of these object for AI. case MP2::OBJ_ORACLE: // AI has no brains to do anything from sign messages. case MP2::OBJ_SIGN: // AI has no brains to handle Sirens object. case MP2::OBJ_SIRENS: // TODO: AI doesn't know how it use Sphinx object properly. case MP2::OBJ_SPHINX: // AI should never consider a stone lith as a destination point. It uses them only to make a path. case MP2::OBJ_STONE_LITHS: // TODO: AI doesn't know how it use Trading Post object properly. case MP2::OBJ_TRADING_POST: // AI should never consider a whirlpool as a destination point. It uses them only to make a path. case MP2::OBJ_WHIRLPOOL: return false; default: // Did you add a new action object but forget to add AI interaction for it? assert( 0 ); break; } return false; } void addHeroToMove( Heroes * hero, std::vector & availableHeroes ) { if ( hero->Modes( Heroes::PATROL ) && ( hero->GetPatrolDistance() == 0 ) ) { DEBUG_LOG( DBG_AI, DBG_TRACE, hero->GetName() << " standing still. Skip turn." ) return; } if ( hero->MayStillMove( false, false ) ) { availableHeroes.emplace_back(); AI::HeroToMove & heroInfo = availableHeroes.back(); heroInfo.hero = hero; if ( hero->Modes( Heroes::PATROL ) ) { heroInfo.patrolCenter = Maps::GetIndexFromAbsPoint( hero->GetPatrolCenter() ); heroInfo.patrolDistance = hero->GetPatrolDistance(); } } } // Used for caching object validations per hero. class ObjectValidator { public: explicit ObjectValidator( const Heroes & hero, const AIWorldPathfinder & pathfinder, AI::Normal & ai ) : _hero( hero ) , _pathfinder( pathfinder ) , _ai( ai ) , _heroArmyStrength( hero.GetArmy().GetStrength() ) , _armyStrengthThreshold( hero.getAIMinimumJoiningArmyStrength() ) { // Do nothing. } bool isValid( const int index ) { auto iter = _validObjects.find( index ); if ( iter != _validObjects.end() ) { return iter->second; } const bool valid = HeroesValidObject( _hero, _heroArmyStrength, index, _pathfinder, _ai, _armyStrengthThreshold, false ); _validObjects[index] = valid; return valid; } private: const Heroes & _hero; const AIWorldPathfinder & _pathfinder; AI::Normal & _ai; // Hero's strength value is valid till any action is done. // Since an instance of this class is used only for evaluation of the future movement it is appropriate to cache the strength. const double _heroArmyStrength; // Army strength threshold is used to decide whether getting extra monsters is useful. const double _armyStrengthThreshold; std::map _validObjects; }; // Used for caching of object value estimation per hero. class ObjectValueStorage { public: ObjectValueStorage( const Heroes & hero, const AI::Normal & ai, const double ignoreValue ) : _hero( hero ) , _ai( ai ) , _ignoreValue( ignoreValue ) {} double value( const std::pair & objectInfo, const uint32_t distance ) { auto iter = _objectValue.find( objectInfo ); if ( iter != _objectValue.end() ) { return iter->second; } const double value = _ai.getObjectValue( _hero, objectInfo.first, objectInfo.second, _ignoreValue, distance ); _objectValue[objectInfo] = value; return value; } private: const Heroes & _hero; const AI::Normal & _ai; const double _ignoreValue; std::map, double> _objectValue; }; double getMonsterUpgradeValue( const Army & army, const int monsterId ) { const uint32_t monsterCount = army.GetCountMonsters( monsterId ); if ( monsterCount == 0 ) { // Nothing to upgrade. return 0; } const Monster currentMonster( monsterId ); const Monster upgradedMonster = currentMonster.GetUpgrade(); if ( upgradedMonster == currentMonster ) { // Monster has no upgrade. return 0; } return ( upgradedMonster.GetMonsterStrength() - currentMonster.GetMonsterStrength() ) * monsterCount; } // Multiply by this value if you are getting a FREE upgrade. const double freeMonsterUpgradeModifier = 3; const double dangerousTaskPenalty = 20000.0; const double fogDiscoveryBaseValue = -10000.0; double getDistanceModifier( const MP2::MapObjectType objectType ) { // The value above 1.0 means that the object is useful only if it is nearby. // The value below 1.0 means that the object should remain in focus even on greater distances. if ( !MP2::isActionObject( objectType ) ) { return 1.0; } switch ( objectType ) { case MP2::OBJ_CASTLE: return 0.8; case MP2::OBJ_ALCHEMIST_LAB: case MP2::OBJ_ARTIFACT: case MP2::OBJ_HERO: case MP2::OBJ_MINE: case MP2::OBJ_SAWMILL: return 0.9; case MP2::OBJ_CAMPFIRE: case MP2::OBJ_FLOTSAM: case MP2::OBJ_GENIE_LAMP: case MP2::OBJ_RESOURCE: case MP2::OBJ_SEA_CHEST: case MP2::OBJ_TREASURE_CHEST: return 0.95; case MP2::OBJ_BUOY: case MP2::OBJ_TEMPLE: case MP2::OBJ_FAERIE_RING: case MP2::OBJ_FOUNTAIN: case MP2::OBJ_IDOL: case MP2::OBJ_MERMAID: // In most situations Luck and Morale modifier objects are useful to be visited when they are very close. return 1.1; default: break; } return 1.0; } double scaleWithDistanceAndTime( const double value, const uint32_t distance, const MP2::MapObjectType objectType ) { if ( distance == 0 ) { return value; } // Some objects do not loose their value drastically over distances. This allows AI heroes to keep focus on important targets. double correctedDistance = distance * getDistanceModifier( objectType ); // Distances should be corrected over time as AI heroes should focus on keeping important objects in focus. // This value shouldn't be big but very slowly increases over time. const double timeCorrectionCoeff = 1 - std::min( 0.5, world.CountDay() * 0.0001 ); correctedDistance *= timeCorrectionCoeff; // Scale non-linearly (more value lost as distance increases) return value - ( correctedDistance * std::log10( correctedDistance ) ); } double getFogDiscoveryValue( const Heroes & hero ) { switch ( hero.getAIRole() ) { case Heroes::Role::SCOUT: // Scouts do not have any penalty on map exploration as it is their main priority. return 0; case Heroes::Role::HUNTER: case Heroes::Role::FIGHTER: case Heroes::Role::CHAMPION: return fogDiscoveryBaseValue; case Heroes::Role::COURIER: return fogDiscoveryBaseValue * 2; default: // If you set a new type of a hero you must add the logic here. assert( 0 ); break; } return fogDiscoveryBaseValue; } uint32_t getTimeoutBeforeFogDiscoveryIntensification( const Heroes & hero ) { switch ( hero.getAIRole() ) { case Heroes::Role::SCOUT: return 30; case Heroes::Role::HUNTER: return 90; case Heroes::Role::FIGHTER: case Heroes::Role::CHAMPION: return 60; case Heroes::Role::COURIER: return 120; default: // If you set a new type of a hero you must add the logic here. assert( 0 ); break; } return 30; } } namespace AI { // TODO: In the future we need to come up with dynamic object value estimation based not only on a hero's role but on an outcome from movement at certain position. double Normal::getGeneralObjectValue( const Heroes & hero, const int index, const double valueToIgnore, const uint32_t distanceToObject ) const { // In the future these hardcoded values could be configured by the mod // 1 tile distance is 100.0 value approximately const Maps::Tiles & tile = world.GetTiles( index ); const MP2::MapObjectType objectType = tile.GetObject(); const std::function calculateCastleValue = [this, &hero, &calculateCastleValue]( const Castle * castle ) { assert( castle != nullptr ); double value = castle->getBuildingValue() * 150.0 + 3000; if ( hero.isLosingGame() ) { value += 15000; } if ( isCastleLossConditionForHuman( castle ) ) { value += 20000; } // This modifier shouldn't be too high to avoid players baiting AI in const double defenselessCastleModifier = 1.25; // This is our own castle in need of protection, evaluate it no worse than a similar defenseless enemy castle if ( hero.GetColor() == castle->GetColor() ) { value *= defenselessCastleModifier; } else { const int32_t castleIndex = castle->GetIndex(); // If this castle threatens our castles, then we should evaluate it no worse than the best of our castles that it threatens // (as if our best castle had to be taken back from the enemy). If a threatening castle can be attacked right now, it is // better to do it than wait until the hero hired in it captures one of our castles. if ( isCriticalTask( castleIndex ) ) { const auto iter = _priorityTargets.find( castleIndex ); assert( iter != _priorityTargets.end() ); const PriorityTask & attackTask = iter->second; assert( attackTask.type == PriorityTaskType::ATTACK ); for ( const int32_t secondaryTaskTileIdx : attackTask.secondaryTaskTileId ) { const Castle * castleUnderThreat = world.getCastleEntrance( Maps::GetPoint( secondaryTaskTileIdx ) ); if ( castleUnderThreat == nullptr ) { continue; } assert( hero.GetColor() == castleUnderThreat->GetColor() ); // Apply a bonus so that the AI prefers to eliminate the threat if possible instead of guarding its castle value = std::max( value, calculateCastleValue( castleUnderThreat ) * 2 ); } } // This castle is defenseless if ( !castle->GetActualArmy().isValid() ) { value *= defenselessCastleModifier; } } return value; }; switch ( objectType ) { case MP2::OBJ_CASTLE: { const Castle * castle = world.getCastleEntrance( Maps::GetPoint( index ) ); if ( !castle ) { // How is it even possible? assert( 0 ); return valueToIgnore; } if ( hero.GetColor() == castle->GetColor() ) { // If our castle is in danger, then we should evaluate it not from the point of view of momentary benefits // for the hero (for example, the presence of reinforcements), but from the point of view of the castle, // which then will have to be taken back from the enemy, that is, according to approximately the same logic // as enemy castles if ( isCriticalTask( index ) ) { return calculateCastleValue( castle ); } double value = castle->getVisitValue( hero ); if ( !isPriorityTask( index ) && value < 500 ) { return valueToIgnore; } return value; } // Hero should never visit castles belonging to friendly kingdoms if ( hero.isFriends( castle->GetColor() ) ) { assert( 0 ); return -dangerousTaskPenalty; } // WINS_HERO victory condition does not apply to AI-controlled players, we have to ignore the castle with this hero // to keep him alive for the human player if ( isHeroWhoseDefeatIsVictoryConditionForHumanInCastle( castle ) ) { assert( 0 ); return -dangerousTaskPenalty; } return calculateCastleValue( castle ); } case MP2::OBJ_HERO: { const Heroes * otherHero = tile.getHero(); if ( !otherHero ) { // How is it even possible? assert( 0 ); return valueToIgnore; } if ( hero.GetColor() == otherHero->GetColor() ) { if ( hero.getAIRole() > otherHero->getAIRole() ) { // The other hero has a lower role. Do not waste time for meeting. Let him to come. return valueToIgnore; } if ( hero.getAIRole() == otherHero->getAIRole() && hero.getStatsValue() + Difficulty::getMinStatDiffForAIHeroesMeeting( Game::getDifficulty() ) > otherHero->getStatsValue() ) { // Either this hero is superior or approximately equal to another hero in terms of stats. Do not waste time for meeting. Let him come instead. return valueToIgnore; } const double value = hero.getMeetingValue( *otherHero ); // limit the max value of friendly hero meeting to 30 tiles return ( value < 250 ) ? valueToIgnore : std::min( value, 10000.0 ); } // Hero should never meet heroes from friendly kingdoms if ( hero.isFriends( otherHero->GetColor() ) ) { assert( 0 ); return -dangerousTaskPenalty; } // WINS_HERO victory condition does not apply to AI-controlled players, we have to keep this hero alive for the human player if ( otherHero == world.GetHeroesCondWins() ) { assert( 0 ); return -dangerousTaskPenalty; } double value = 5000; // If this hero threatens our castles, then we should evaluate him no worse than the best of our castles that he threatens // (as if it had to be taken back from the enemy). If a threatening hero can be attacked in an open field, it is better to // do it now than to attack him later in our castle occupied by him. if ( isCriticalTask( index ) ) { const auto iter = _priorityTargets.find( index ); assert( iter != _priorityTargets.end() ); const PriorityTask & attackTask = iter->second; assert( attackTask.type == PriorityTaskType::ATTACK ); for ( const int32_t secondaryTaskTileIdx : attackTask.secondaryTaskTileId ) { const Castle * castle = world.getCastleEntrance( Maps::GetPoint( secondaryTaskTileIdx ) ); if ( castle == nullptr ) { continue; } assert( hero.GetColor() == castle->GetColor() ); // Apply a bonus so that the AI prefers to eliminate the threat if possible instead of guarding its castle value = std::max( value, calculateCastleValue( castle ) * 2 ); } } else if ( otherHero->GetControl() == CONTROL_AI ) { // AI heroes should not attack other AI heroes so aggressively as human heroes. // This is done to avoid situations when human players just wait when AI heroes kill each other. value *= 0.8; } return value; } case MP2::OBJ_MONSTER: { const Army monsters( tile ); if ( !monsters.isValid() ) { // How is it even possible? assert( 0 ); return -dangerousTaskPenalty; } // TODO: we should add logic to compare monsters and hero army strengths. return 1000.0 + monsters.getTotalHP() / 100.0; } case MP2::OBJ_ALCHEMIST_LAB: case MP2::OBJ_MINE: case MP2::OBJ_SAWMILL: case MP2::OBJ_ABANDONED_MINE: { int resourceType = Resource::UNKNOWN; int32_t resourceAmount = 0; // Abandoned mines are gold mines under the hood if ( objectType == MP2::OBJ_ABANDONED_MINE ) { resourceType = Resource::GOLD; resourceAmount = ProfitConditions::FromMine( Resource::GOLD ).Get( Resource::GOLD ); } else { if ( getColorFromTile( tile ) == hero.GetColor() ) { return valueToIgnore; // don't even attempt to go here } std::tie( resourceType, resourceAmount ) = getDailyIncomeObjectResources( tile ).getFirstValidResource(); } assert( resourceType != Resource::UNKNOWN && resourceAmount != 0 ); // Since mines constantly bring resources, they are valuable objects return resourceAmount * getResourcePriorityModifier( resourceType, true ); } case MP2::OBJ_ARTIFACT: case MP2::OBJ_SHIPWRECK_SURVIVOR: { const Artifact art = getArtifactFromTile( tile ); assert( art.isValid() ); // WINS_ARTIFACT victory condition does not apply to AI-controlled players, we should leave this artifact untouched for the human player if ( isFindArtifactVictoryConditionForHuman( art ) ) { assert( 0 ); return -dangerousTaskPenalty; } return 1000.0 * art.getArtifactValue(); } case MP2::OBJ_SEA_CHEST: case MP2::OBJ_TREASURE_CHEST: { const Artifact art = getArtifactFromTile( tile ); if ( art.isValid() && isFindArtifactVictoryConditionForHuman( art ) ) { // WINS_ARTIFACT victory condition does not apply to AI-controlled players, we should leave this object untouched for the human player. assert( 0 ); return -dangerousTaskPenalty; } // This is an average gold amount you can get from a treasure chest or sea chest. return getFundsValueBasedOnPriority( { Resource::GOLD, 1500 } ); } case MP2::OBJ_DAEMON_CAVE: { // If this cave is already empty, then we should never come here if ( !doesTileContainValuableItems( tile ) ) { assert( 0 ); return -dangerousTaskPenalty; } // Daemon Cave always gives 2500 Gold after a battle and AI always chooses to fight the demon's servants and doesn't roll the dice return getFundsValueBasedOnPriority( { Resource::GOLD, 2500 } ); } case MP2::OBJ_GRAVEYARD: case MP2::OBJ_SHIPWRECK: case MP2::OBJ_SKELETON: case MP2::OBJ_WAGON: { if ( !getArtifactFromTile( tile ).isValid() ) { // Don't waste time to go here. return -dangerousTaskPenalty; } const Artifact art = getArtifactFromTile( tile ); // WINS_ARTIFACT victory condition does not apply to AI-controlled players, we should leave this artifact untouched for the human player if ( isFindArtifactVictoryConditionForHuman( art ) ) { assert( 0 ); return -dangerousTaskPenalty; } return 1000.0 * art.getArtifactValue(); } case MP2::OBJ_BOTTLE: { // A bottle is useless to AI as it contains only a message but it might block path. return 0; } case MP2::OBJ_CAMPFIRE: { // A campfire has 4-6 random resources plus 400-600 gold so we use an average to get evaluation. // Since we should not expose the resource type let's assume that it gives 6 resources, 1 each and 400 gold. const Funds loot{ 1, 1, 1, 1, 1, 1, 400 }; const double value = getFundsValueBasedOnPriority( loot ); assert( value > 0 ); return value; } case MP2::OBJ_MAGIC_GARDEN: { // Magic Garden has visual representation whether it was visited so verify that is has resources. assert( doesTileContainValuableItems( tile ) ); // Magic Garden can have either 5 gems or 500 gold. Since, 5 is not divisible by 2, let's put 3 instead. const Funds loot{ 0, 0, 0, 0, 0, 3, 250 }; const double value = getFundsValueBasedOnPriority( loot ); assert( value > 0 ); return value; } case MP2::OBJ_RESOURCE: { const Funds loot = getFundsFromTile( tile ); // Resource object has visual representation of the type of resources. const auto & [resourceType, resourceCount] = loot.getFirstValidResource(); Funds estimatedResource; switch ( resourceType ) { case Resource::GOLD: estimatedResource = Funds( resourceType, 750 ); break; case Resource::WOOD: case Resource::ORE: estimatedResource = Funds( resourceType, 7 ); break; case Resource::MERCURY: case Resource::SULFUR: case Resource::CRYSTAL: case Resource::GEMS: estimatedResource = Funds( resourceType, 4 ); break; default: assert( 0 ); break; } const double value = getFundsValueBasedOnPriority( estimatedResource ); assert( value > 0 ); return value; } case MP2::OBJ_DERELICT_SHIP: case MP2::OBJ_LEAN_TO: case MP2::OBJ_WATER_WHEEL: case MP2::OBJ_WINDMILL: { const Funds loot = getFundsFromTile( tile ); const double value = getFundsValueBasedOnPriority( loot ); // This object could have already been visited if ( value < 1 ) { return valueToIgnore; } return value; } case MP2::OBJ_FLOTSAM: { // Flotsam has the following chances and resources: // - 25%: 500 gold + 10 wood // - 25%: 200 gold + 5 wood // - 25%: 5 wood // - 25%: empty // Therefore, use an average value as an estimation of a possible reward of picking this object. // The AI must not know precise reward from this object as it would be called cheating. // For all cases we have: 700 gold and 20 wood. So we divide by 4 to get an average number of resources. // In total, we should use 175 gold and 5 wood. const Funds loot{ 0, 5, 0, 0, 0, 0, 175 }; const double value = getFundsValueBasedOnPriority( loot ); assert( value > 0 ); return value; } case MP2::OBJ_LIGHTHOUSE: { // TODO: add more complex logic for cases when AI has boats. if ( getColorFromTile( tile ) == hero.GetColor() ) { return -dangerousTaskPenalty; // don't even attempt to go here } return 500; } case MP2::OBJ_XANADU: { return 3000; } case MP2::OBJ_SHRINE_FIRST_CIRCLE: case MP2::OBJ_SHRINE_SECOND_CIRCLE: case MP2::OBJ_SHRINE_THIRD_CIRCLE: { const Spell & spell = getSpellFromTile( tile ); return spell.getStrategicValue( hero.GetArmy().GetStrength(), hero.GetMaxSpellPoints(), hero.GetPower() ); } case MP2::OBJ_ARENA: case MP2::OBJ_FORT: case MP2::OBJ_MERCENARY_CAMP: case MP2::OBJ_STANDING_STONES: case MP2::OBJ_TREE_OF_KNOWLEDGE: case MP2::OBJ_WITCH_DOCTORS_HUT: case MP2::OBJ_WITCHS_HUT: { return 500.0; } // These are dwelling where monsters can be purchased. case MP2::OBJ_AIR_ALTAR: case MP2::OBJ_BARROW_MOUNDS: case MP2::OBJ_CITY_OF_DEAD: case MP2::OBJ_DESERT_TENT: case MP2::OBJ_DRAGON_CITY: case MP2::OBJ_EARTH_ALTAR: case MP2::OBJ_FIRE_ALTAR: case MP2::OBJ_GENIE_LAMP: case MP2::OBJ_RUINS: case MP2::OBJ_TREE_CITY: case MP2::OBJ_TROLL_BRIDGE: case MP2::OBJ_WAGON_CAMP: case MP2::OBJ_WATER_ALTAR: { const Troop & troop = getTroopFromTile( tile ); if ( !troop.isValid() ) { // This should not happen! The dwelling must have monsters to reach this code. assert( 0 ); return -dangerousTaskPenalty; } const Funds singleMonsterCost = troop.GetCost(); uint32_t recruitTroopCount = hero.GetKingdom().GetFunds().getLowestQuotient( singleMonsterCost ); if ( recruitTroopCount == 0 ) { // This can actually happen when AI spends some money in between visiting objects. return -dangerousTaskPenalty; } const uint32_t availableTroopCount = troop.GetCount(); if ( recruitTroopCount > availableTroopCount ) { recruitTroopCount = availableTroopCount; } return Troop{ troop.GetID(), recruitTroopCount }.GetStrength(); } // These are dwellings where monsters can be hired for free. case MP2::OBJ_ARCHER_HOUSE: case MP2::OBJ_CAVE: case MP2::OBJ_DWARF_COTTAGE: case MP2::OBJ_EXCAVATION: case MP2::OBJ_GOBLIN_HUT: case MP2::OBJ_HALFLING_HOLE: case MP2::OBJ_PEASANT_HUT: case MP2::OBJ_TREE_HOUSE: case MP2::OBJ_WATCH_TOWER: { return getTroopFromTile( tile ).GetStrength(); } case MP2::OBJ_STONE_LITHS: { // Stone lith is not considered by AI as an action object. If this assertion blows up something is wrong with the logic. assert( 0 ); return -dangerousTaskPenalty; } case MP2::OBJ_OBSERVATION_TOWER: { const int32_t fogCountToUncoverByTower = Maps::getFogTileCountToBeRevealed( index, GameStatic::getFogDiscoveryDistance( GameStatic::FogDiscoveryType::OBSERVATION_TOWER ), hero.GetColor() ); if ( fogCountToUncoverByTower == 0 ) { // Nothing to uncover. return -dangerousTaskPenalty; } return fogCountToUncoverByTower; } case MP2::OBJ_MAGELLANS_MAPS: { // Very valuable object. return 5000; } case MP2::OBJ_BOAT: case MP2::OBJ_COAST: case MP2::OBJ_WHIRLPOOL: { // Coast is not an object while Whirlpool and Boat are not considered by AI as an action object. // If this assertion blows up something is wrong with the logic. assert( 0 ); return -dangerousTaskPenalty; } case MP2::OBJ_ARTESIAN_SPRING: { if ( !hero.HaveSpellBook() || hero.GetSpellPoints() * 2 >= hero.GetMaxSpellPoints() ) { // No reason to visit Artesian Spring with no magic book or if no points will be gained. return -dangerousTaskPenalty; } return hero.isPotentSpellcaster() ? 1500 : 0; } case MP2::OBJ_MAGIC_WELL: { if ( !hero.HaveSpellBook() || hero.GetSpellPoints() >= hero.GetMaxSpellPoints() ) { // No reason to visit Magic Well with no magic book or if no points will be gained. return -dangerousTaskPenalty; } return hero.isPotentSpellcaster() ? 1500 : 0; } case MP2::OBJ_BUOY: case MP2::OBJ_TEMPLE: { if ( hero.GetArmy().AllTroopsAreUndead() ) { // All troops are undead, no use of Morale. return 0; } const int morale = hero.GetMorale(); if ( morale >= Morale::BLOOD ) { return -dangerousTaskPenalty; // No reason to visit with maximum morale } if ( morale == Morale::GREAT ) { return -4000; // Morale is good enough to avoid visiting this object } if ( morale == Morale::GOOD ) { return -2000; // Is it worth visiting this facility with a morale slightly better than neutral? } if ( morale == Morale::NORMAL ) { return 50; } return 100; } case MP2::OBJ_STABLES: { const int daysActive = DAYOFWEEK - world.GetDay() + 1; double movementBonus = daysActive * GameStatic::getMovementPointBonus( objectType ) - 2.0 * distanceToObject; const double upgradeValue = getMonsterUpgradeValue( hero.GetArmy(), Monster::CHAMPION ); if ( upgradeValue > 0.0001 ) { // Even if no movement bonus exist it is worth to visit just to upgrade monsters. return std::max( movementBonus, 0.0 ) + freeMonsterUpgradeModifier * upgradeValue; } if ( movementBonus < 0 ) { // Don't waste movement to even visit this object. return -dangerousTaskPenalty; } return movementBonus; } case MP2::OBJ_FREEMANS_FOUNDRY: { const double upgradePikemanValue = getMonsterUpgradeValue( hero.GetArmy(), Monster::PIKEMAN ); const double upgradeSwordsmanValue = getMonsterUpgradeValue( hero.GetArmy(), Monster::SWORDSMAN ); const double upgradeGolemValue = getMonsterUpgradeValue( hero.GetArmy(), Monster::IRON_GOLEM ); return freeMonsterUpgradeModifier * ( upgradePikemanValue + upgradeSwordsmanValue + upgradeGolemValue ); } case MP2::OBJ_HILL_FORT: { const double upgradeDwarfValue = getMonsterUpgradeValue( hero.GetArmy(), Monster::DWARF ); const double upgradeOrcValue = getMonsterUpgradeValue( hero.GetArmy(), Monster::ORC ); const double upgradeOgreValue = getMonsterUpgradeValue( hero.GetArmy(), Monster::OGRE ); return freeMonsterUpgradeModifier * ( upgradeDwarfValue + upgradeOrcValue + upgradeOgreValue ); } case MP2::OBJ_BARRIER: case MP2::OBJ_TRAVELLER_TENT: { // Most likely it'll lead to opening more land. return 1000; } case MP2::OBJ_OASIS: case MP2::OBJ_WATERING_HOLE: { const double movementBonus = GameStatic::getMovementPointBonus( objectType ) - 2.0 * distanceToObject; if ( movementBonus < 0 ) { // Don't waste movement to even visit this object. return -dangerousTaskPenalty; } return movementBonus; } case MP2::OBJ_JAIL: { // A free hero is always good and he could be very powerful. return 3000; } case MP2::OBJ_HUT_OF_MAGI: { // TODO: cache Maps::GetObjectPositions() call as this is a very heavy operation. const MapsIndexes eyeMagiIndexes = Maps::GetObjectPositions( MP2::OBJ_EYE_OF_MAGI ); int fogCountToUncover = 0; const int heroColor = hero.GetColor(); const int eyeViewDistance = GameStatic::getFogDiscoveryDistance( GameStatic::FogDiscoveryType::MAGI_EYES ); for ( const int32_t eyeIndex : eyeMagiIndexes ) { fogCountToUncover += Maps::getFogTileCountToBeRevealed( eyeIndex, eyeViewDistance, heroColor ); } return fogCountToUncover; } case MP2::OBJ_GAZEBO: { // Free 1000 experience. We need to calculate value of this object based on hero's experience. The higher hero's level the less valuable this object is. const uint32_t heroExperience = hero.GetExperience(); const uint32_t nextLevelExperience = Heroes::GetExperienceFromLevel( Heroes::GetLevelFromExperience( heroExperience ) ); const uint32_t neededExperience = nextLevelExperience - heroExperience; if ( neededExperience < 1000 ) { // A new level. Have to visit. return 1000; } return 1000.0 * 1000.0 / neededExperience; } case MP2::OBJ_PYRAMID: { return 1500; } case MP2::OBJ_FAERIE_RING: case MP2::OBJ_FOUNTAIN: case MP2::OBJ_IDOL: case MP2::OBJ_MERMAID: { const int luck = hero.GetLuck(); if ( luck >= Luck::IRISH ) { return -dangerousTaskPenalty; // No reason to visit with maximum morale } if ( luck == Luck::GREAT ) { return -4000; // Morale is good enough to avoid visiting this object } if ( luck == Luck::GOOD ) { return -2000; // Is it worth visiting this facility with a morale slightly better than neutral? } if ( luck == Luck::NORMAL ) { return 50; } return 100; } case MP2::OBJ_ALCHEMIST_TOWER: { const BagArtifacts & bag = hero.GetBagArtifacts(); const uint32_t cursed = static_cast( std::count_if( bag.begin(), bag.end(), []( const Artifact & art ) { return art.containsCurses(); } ) ); if ( cursed == 0 ) { return -dangerousTaskPenalty; } // TODO: evaluate this object properly. return 0; } case MP2::OBJ_EYE_OF_MAGI: case MP2::OBJ_ORACLE: case MP2::OBJ_SIGN: { // These objects are useless for AI. return -dangerousTaskPenalty; } case MP2::OBJ_OBELISK: case MP2::OBJ_SIRENS: case MP2::OBJ_SPHINX: case MP2::OBJ_TRADING_POST: { // TODO: add logic to evaluate these objects. // As of now these objects should be avoided by AI as they are useless. return -dangerousTaskPenalty; } default: // Did you forget to add logic for an action object? assert( !MP2::isActionObject( objectType ) ); break; } return 0; } double Normal::getFighterObjectValue( const Heroes & hero, const int index, const double valueToIgnore, const uint32_t distanceToObject ) const { // Fighters have higher priority for battles and smaller values for other objects. assert( hero.getAIRole() == Heroes::Role::FIGHTER || hero.getAIRole() == Heroes::Role::CHAMPION ); const Maps::Tiles & tile = world.GetTiles( index ); const MP2::MapObjectType objectType = tile.GetObject(); const std::function calculateCastleValue = [this, &hero, &calculateCastleValue]( const Castle * castle ) { assert( castle != nullptr ); double value = castle->getBuildingValue() * 500.0 + 15000; if ( hero.isLosingGame() ) { value += 15000; } if ( isCastleLossConditionForHuman( castle ) ) { value += 20000; } // This modifier shouldn't be too high to avoid players baiting AI in const double defenselessCastleModifier = 1.5; // This is our own castle in need of protection, evaluate it no worse than a similar defenseless enemy castle if ( hero.GetColor() == castle->GetColor() ) { value *= defenselessCastleModifier; } else { const int32_t castleIndex = castle->GetIndex(); // If this castle threatens our castles, then we should evaluate it no worse than the best of our castles that it threatens // (as if our best castle had to be taken back from the enemy). If a threatening castle can be attacked right now, it is // better to do it than wait until the hero hired in it captures one of our castles. if ( isCriticalTask( castleIndex ) ) { const auto iter = _priorityTargets.find( castleIndex ); assert( iter != _priorityTargets.end() ); const PriorityTask & attackTask = iter->second; assert( attackTask.type == PriorityTaskType::ATTACK ); for ( const int32_t secondaryTaskTileIdx : attackTask.secondaryTaskTileId ) { const Castle * castleUnderThreat = world.getCastleEntrance( Maps::GetPoint( secondaryTaskTileIdx ) ); if ( castleUnderThreat == nullptr ) { continue; } assert( hero.GetColor() == castleUnderThreat->GetColor() ); // Apply a bonus so that the AI prefers to eliminate the threat if possible instead of guarding its castle value = std::max( value, calculateCastleValue( castleUnderThreat ) * 2 ); } } // This castle is defenseless if ( !castle->GetActualArmy().isValid() ) { value *= defenselessCastleModifier; } } return value; }; switch ( objectType ) { case MP2::OBJ_CASTLE: { const Castle * castle = world.getCastleEntrance( Maps::GetPoint( index ) ); if ( !castle ) { // How is it even possible? assert( 0 ); return valueToIgnore; } if ( hero.GetColor() == castle->GetColor() ) { // If our castle is in danger, then we should evaluate it not from the point of view of momentary benefits // for the hero (for example, the presence of reinforcements), but from the point of view of the castle, // which then will have to be taken back from the enemy, that is, according to approximately the same logic // as enemy castles if ( isCriticalTask( index ) ) { return calculateCastleValue( castle ); } double value = castle->getVisitValue( hero ); if ( !isPriorityTask( index ) && value < 500 ) { return valueToIgnore; } return value / 2; } // Hero should never visit castles belonging to friendly kingdoms if ( hero.isFriends( castle->GetColor() ) ) { assert( 0 ); return -dangerousTaskPenalty; } // WINS_HERO victory condition does not apply to AI-controlled players, we have to ignore the castle with this hero // to keep him alive for the human player if ( isHeroWhoseDefeatIsVictoryConditionForHumanInCastle( castle ) ) { assert( 0 ); return -dangerousTaskPenalty; } return calculateCastleValue( castle ); } case MP2::OBJ_HERO: { const Heroes * otherHero = tile.getHero(); if ( !otherHero ) { // How is it even possible? assert( 0 ); return valueToIgnore; } if ( hero.GetColor() == otherHero->GetColor() ) { if ( hero.getAIRole() > otherHero->getAIRole() ) { // The other hero has a lower role. Do not waste time for meeting. Let him to come. return valueToIgnore; } if ( hero.getAIRole() == otherHero->getAIRole() && hero.getStatsValue() + Difficulty::getMinStatDiffForAIHeroesMeeting( Game::getDifficulty() ) + 1 > otherHero->getStatsValue() ) { // Either this hero is superior or approximately equal to another hero in terms of stats. Do not waste time for meeting. Let him come instead. return valueToIgnore; } const double value = hero.getMeetingValue( *otherHero ); // limit the max value of friendly hero meeting to 30 tiles return ( value < 250 ) ? valueToIgnore : std::min( value, 5000.0 ); } // Hero should never meet heroes from friendly kingdoms if ( hero.isFriends( otherHero->GetColor() ) ) { assert( 0 ); return -dangerousTaskPenalty; } // WINS_HERO victory condition does not apply to AI-controlled players, we have to keep this hero alive for the human player if ( otherHero == world.GetHeroesCondWins() ) { assert( 0 ); return -dangerousTaskPenalty; } double value = 12000; // If this hero threatens our castles, then we should evaluate him no worse than the best of our castles that he threatens // (as if it had to be taken back from the enemy). If a threatening hero can be attacked in an open field, it is better to // do it now than to attack him later in our castle occupied by him. if ( isCriticalTask( index ) ) { const auto iter = _priorityTargets.find( index ); assert( iter != _priorityTargets.end() ); const PriorityTask & attackTask = iter->second; assert( attackTask.type == PriorityTaskType::ATTACK ); for ( const int32_t secondaryTaskTileIdx : attackTask.secondaryTaskTileId ) { const Castle * castle = world.getCastleEntrance( Maps::GetPoint( secondaryTaskTileIdx ) ); if ( castle == nullptr ) { continue; } assert( hero.GetColor() == castle->GetColor() ); // Apply a bonus so that the AI prefers to eliminate the threat if possible instead of guarding its castle value = std::max( value, calculateCastleValue( castle ) * 2 ); } } else if ( otherHero->GetControl() == CONTROL_AI ) { // AI heroes should not attack other AI heroes so aggressively as human heroes. // This is done to avoid situations when human players just wait when AI heroes kill each other. value *= 0.8; } return value; } case MP2::OBJ_MONSTER: { const Army monsters( tile ); if ( !monsters.isValid() ) { // How is it even possible? assert( 0 ); return -dangerousTaskPenalty; } // TODO: we should add logic to compare monsters and hero army strengths. const bool anotherFriendlyHeroPresent = _regions[tile.GetRegion()].friendlyHeroes > 1; return ( anotherFriendlyHeroPresent ? 4000.0 : 1000.0 ) + monsters.getTotalHP() / 100.0; } case MP2::OBJ_ABANDONED_MINE: { return 5000.0; } case MP2::OBJ_ARTIFACT: { const Artifact art = getArtifactFromTile( tile ); assert( art.isValid() ); // WINS_ARTIFACT victory condition does not apply to AI-controlled players, we should leave this artifact untouched for the human player if ( isFindArtifactVictoryConditionForHuman( art ) ) { assert( 0 ); return -dangerousTaskPenalty; } return 1500.0 * art.getArtifactValue(); } case MP2::OBJ_CAMPFIRE: case MP2::OBJ_FLOTSAM: case MP2::OBJ_LEAN_TO: case MP2::OBJ_MAGIC_GARDEN: case MP2::OBJ_RESOURCE: case MP2::OBJ_WATER_WHEEL: case MP2::OBJ_WINDMILL: { const bool anotherFriendlyHeroPresent = _regions[tile.GetRegion()].friendlyHeroes > 1; if ( anotherFriendlyHeroPresent ) { return 100.0; } // fall through to the general value calculation break; } case MP2::OBJ_LIGHTHOUSE: { // TODO: add more complex logic for cases when AI has boats. if ( getColorFromTile( tile ) == hero.GetColor() ) { return -dangerousTaskPenalty; // don't even attempt to go here } return 250; } case MP2::OBJ_XANADU: { return 3500; } case MP2::OBJ_SHRINE_FIRST_CIRCLE: case MP2::OBJ_SHRINE_SECOND_CIRCLE: case MP2::OBJ_SHRINE_THIRD_CIRCLE: { const Spell & spell = getSpellFromTile( tile ); return spell.getStrategicValue( hero.GetArmy().GetStrength(), hero.GetMaxSpellPoints(), hero.GetPower() ) * 1.1; } case MP2::OBJ_ARENA: case MP2::OBJ_FORT: case MP2::OBJ_MERCENARY_CAMP: case MP2::OBJ_STANDING_STONES: case MP2::OBJ_TREE_OF_KNOWLEDGE: case MP2::OBJ_WITCH_DOCTORS_HUT: case MP2::OBJ_WITCHS_HUT: { return 1250.0; } case MP2::OBJ_OBSERVATION_TOWER: { return getGeneralObjectValue( hero, index, valueToIgnore, distanceToObject ) / 2; } case MP2::OBJ_ARTESIAN_SPRING: { if ( !hero.HaveSpellBook() || hero.GetSpellPoints() * 2 >= hero.GetMaxSpellPoints() ) { // No reason to visit Artesian Spring with no magic book or if no points will be gained. return -dangerousTaskPenalty; } return hero.isPotentSpellcaster() ? 2000 : 0; } case MP2::OBJ_MAGIC_WELL: { if ( !hero.HaveSpellBook() || hero.GetSpellPoints() >= hero.GetMaxSpellPoints() ) { // No reason to visit Magic Well with no magic book or if no points will be gained. return -dangerousTaskPenalty; } return hero.isPotentSpellcaster() ? 2000 : 0; } case MP2::OBJ_BUOY: case MP2::OBJ_TEMPLE: { if ( hero.GetArmy().AllTroopsAreUndead() ) { // All troops are undead, no use of Morale. return 0; } const int morale = hero.GetMorale(); if ( morale >= Morale::BLOOD ) { return -dangerousTaskPenalty; // No reason to visit with maximum morale } if ( morale == Morale::GREAT ) { return -4000; // Morale is good enough to avoid visiting this object } if ( morale == Morale::GOOD ) { return -2000; // Is it worth visiting this facility with a morale slightly better than neutral? } if ( morale == Morale::NORMAL ) { return 50; } return 200; } case MP2::OBJ_HUT_OF_MAGI: { return getGeneralObjectValue( hero, index, valueToIgnore, distanceToObject ) / 2; } case MP2::OBJ_PYRAMID: { return 10000; } default: break; } return getGeneralObjectValue( hero, index, valueToIgnore, distanceToObject ); } double Normal::getCourierObjectValue( const Heroes & hero, const int index, const double valueToIgnore, const uint32_t distanceToObject ) const { // Courier should focus on its main task and visit other objects only if it's close to the destination assert( hero.getAIRole() == Heroes::Role::COURIER ); // Values (n logn) for pre-defined distances where AI courier would consider taking a detour to visit those const double twoTiles = 500; const double fiveTiles = 1400; const double tenTiles = 3000; const Maps::Tiles & tile = world.GetTiles( index ); const MP2::MapObjectType objectType = tile.GetObject(); switch ( objectType ) { case MP2::OBJ_HERO: { const Heroes * otherHero = tile.getHero(); if ( !otherHero ) { // How is it even possible? assert( 0 ); return valueToIgnore; } if ( hero.GetColor() == otherHero->GetColor() ) { // Will be handled by the main task. return valueToIgnore; } // Hero should never meet heroes from friendly kingdoms if ( hero.isFriends( otherHero->GetColor() ) ) { assert( 0 ); return -dangerousTaskPenalty; } // WINS_HERO victory condition does not apply to AI-controlled players, we have to keep this hero alive for the human player if ( otherHero == world.GetHeroesCondWins() ) { assert( 0 ); return -dangerousTaskPenalty; } // This hero threatens our castles, use the general evaluation in this case if ( isCriticalTask( index ) ) { break; } return tenTiles; } case MP2::OBJ_MONSTER: { const Army monsters( tile ); if ( !monsters.isValid() ) { // How is it even possible? assert( 0 ); return -dangerousTaskPenalty; } // TODO: we should add logic to compare monsters and hero army strengths. return twoTiles + monsters.getTotalHP() / 100.0; } case MP2::OBJ_ALCHEMIST_LAB: case MP2::OBJ_MINE: case MP2::OBJ_SAWMILL: { if ( getColorFromTile( tile ) == hero.GetColor() ) { return -dangerousTaskPenalty; // don't even attempt to go here } return ( getDailyIncomeObjectResources( tile ).gold > 0 ) ? tenTiles : fiveTiles; } case MP2::OBJ_CAMPFIRE: case MP2::OBJ_FLOTSAM: case MP2::OBJ_GENIE_LAMP: case MP2::OBJ_RESOURCE: { return twoTiles; } case MP2::OBJ_SEA_CHEST: case MP2::OBJ_TREASURE_CHEST: { const Artifact art = getArtifactFromTile( tile ); if ( art.isValid() && isFindArtifactVictoryConditionForHuman( art ) ) { // WINS_ARTIFACT victory condition does not apply to AI-controlled players, we should leave this object untouched for the human player. assert( 0 ); return -dangerousTaskPenalty; } return twoTiles; } case MP2::OBJ_ARENA: case MP2::OBJ_FORT: case MP2::OBJ_MERCENARY_CAMP: case MP2::OBJ_STANDING_STONES: case MP2::OBJ_TREE_OF_KNOWLEDGE: case MP2::OBJ_WITCH_DOCTORS_HUT: case MP2::OBJ_WITCHS_HUT: case MP2::OBJ_XANADU: { return fiveTiles; } case MP2::OBJ_ARTESIAN_SPRING: { if ( !hero.HaveSpellBook() || hero.GetSpellPoints() * 2 >= hero.GetMaxSpellPoints() ) { // No reason to visit Artesian Spring with no magic book or if no points will be gained. return -dangerousTaskPenalty; } return hero.isPotentSpellcaster() ? fiveTiles : 0; } case MP2::OBJ_MAGIC_WELL: { if ( !hero.HaveSpellBook() || hero.GetSpellPoints() >= hero.GetMaxSpellPoints() ) { // No reason to visit Magic Well with no magic book or if no points will be gained. return -dangerousTaskPenalty; } return hero.isPotentSpellcaster() ? fiveTiles : 0; } default: break; } return getGeneralObjectValue( hero, index, valueToIgnore, distanceToObject ); } double Normal::getScoutObjectValue( const Heroes & hero, const int index, const double valueToIgnore, const uint32_t distanceToObject ) const { // Courier should focus on its main task and visit other objects only if it's close to the destination assert( hero.getAIRole() == Heroes::Role::SCOUT ); const Maps::Tiles & tile = world.GetTiles( index ); const MP2::MapObjectType objectType = tile.GetObject(); switch ( objectType ) { case MP2::OBJ_WITCHS_HUT: { if ( !hero.isVisited( tile, Visit::GLOBAL ) ) { // Since this object has not been visited use general value estimation. return getGeneralObjectValue( hero, index, valueToIgnore, distanceToObject ); } const Skill::Secondary & skill = getSecondarySkillFromWitchsHut( tile ); if ( !skill.isValid() || hero.HasMaxSecondarySkill() ) { // This mustn't happen as these checks are done prior this code. assert( 0 ); return -dangerousTaskPenalty; } const int skillType = skill.Skill(); if ( hero.HasSecondarySkill( skillType ) ) { // This mustn't happen as these checks are done prior this code. assert( 0 ); return -dangerousTaskPenalty; } double value = getGeneralObjectValue( hero, index, valueToIgnore, distanceToObject ); if ( skillType == Skill::Secondary::SCOUTING || skillType == Skill::Secondary::LOGISTICS ) { // Scouts should focus on scouting so these skills must have much higher priority. value = value * 3; } else if ( skillType == Skill::Secondary::PATHFINDING ) { value = value * 2; } return value; } default: break; } return getGeneralObjectValue( hero, index, valueToIgnore, distanceToObject ); } double Normal::getObjectValue( const Heroes & hero, const int index, const int objectType, const double valueToIgnore, const uint32_t distanceToObject ) const { assert( objectType == world.GetTiles( index ).GetObject() ); #ifdef NDEBUG (void)objectType; #endif switch ( hero.getAIRole() ) { case Heroes::Role::HUNTER: return getGeneralObjectValue( hero, index, valueToIgnore, distanceToObject ); case Heroes::Role::SCOUT: return getScoutObjectValue( hero, index, valueToIgnore, distanceToObject ); case Heroes::Role::CHAMPION: case Heroes::Role::FIGHTER: return getFighterObjectValue( hero, index, valueToIgnore, distanceToObject ); case Heroes::Role::COURIER: return getCourierObjectValue( hero, index, valueToIgnore, distanceToObject ); default: // If you set a new type of a hero you must add the logic here. assert( 0 ); break; } return 0; } int Normal::getCourierMainTarget( const Heroes & hero, const AIWorldPathfinder & pathfinder, double lowestPossibleValue ) const { assert( hero.getAIRole() == Heroes::Role::COURIER ); int targetIndex = -1; const Kingdom & kingdom = hero.GetKingdom(); const VecHeroes & allHeroes = kingdom.GetHeroes(); // Check if we have army and should bring it to friendly hero first double bestTargetValue = lowestPossibleValue; for ( const Heroes * otherHero : allHeroes ) { if ( !otherHero || hero.GetID() == otherHero->GetID() ) continue; const Heroes::Role role = otherHero->getAIRole(); if ( role == Heroes::Role::COURIER || role == Heroes::Role::SCOUT ) continue; const int currentHeroIndex = otherHero->GetIndex(); const uint32_t dist = getDistanceToObject( hero, pathfinder, currentHeroIndex ); if ( dist == 0 || hero.hasMetWithHero( otherHero->GetID() ) ) continue; double value = hero.getMeetingValue( *otherHero ); if ( value < 500 ) continue; if ( role == Heroes::Role::CHAMPION ) { value *= 2.5; } value -= dist; if ( value > bestTargetValue ) { bestTargetValue = value; targetIndex = currentHeroIndex; } } if ( targetIndex != -1 ) return targetIndex; // Reset the max value bestTargetValue = lowestPossibleValue; for ( const Castle * castle : kingdom.GetCastles() ) { if ( !castle || castle->GetHero() != nullptr ) continue; const int currentCastleIndex = castle->GetIndex(); const uint32_t dist = getDistanceToObject( hero, pathfinder, currentCastleIndex ); if ( dist == 0 ) continue; double value = castle->getVisitValue( hero ); if ( value < 250 ) continue; const int safetyFactor = _regions[world.GetTiles( currentCastleIndex ).GetRegion()].safetyFactor; if ( safetyFactor > 100 ) { value *= 2; } else if ( safetyFactor < 0 ) { value /= 2; } // additional distance scaling is not required since Couriers are meant to travel far value -= dist; if ( value > bestTargetValue ) { bestTargetValue = value; targetIndex = currentCastleIndex; } } return targetIndex; } int Normal::getPriorityTarget( const HeroToMove & heroInfo, double & maxPriority ) { Heroes & hero = *heroInfo.hero; DEBUG_LOG( DBG_AI, DBG_INFO, "Find Adventure Map target for hero " << hero.GetName() << " at current position " << hero.GetIndex() ) const double lowestPossibleValue = -1.0 * Maps::Ground::slowestMovePenalty * world.getSize(); const bool heroInPatrolMode = heroInfo.patrolCenter != -1; const double heroStrength = hero.GetArmy().GetStrength(); int priorityTarget = -1; maxPriority = lowestPossibleValue; #ifdef WITH_DEBUG MP2::MapObjectType objectType = MP2::OBJ_NONE; // If this assertion blows up then the array is not sorted and the logic below will not work as intended. assert( std::is_sorted( _mapActionObjects.begin(), _mapActionObjects.end() ) ); std::set objectIndexes; for ( const auto & actionObject : _mapActionObjects ) { if ( actionObject.second == MP2::OBJ_HERO ) { assert( world.GetTiles( actionObject.first ).getHero() != nullptr ); } const auto [dummy, inserted] = objectIndexes.emplace( actionObject.first ); if ( !inserted ) { assert( 0 ); } } #endif // pre-cache the pathfinder _pathfinder.reEvaluateIfNeeded( hero ); ObjectValidator objectValidator( hero, _pathfinder, *this ); ObjectValueStorage valueStorage( hero, *this, lowestPossibleValue ); const auto getObjectValue = [&objectValidator, &valueStorage, this, heroStrength, &hero]( const int destination, uint32_t & distance, double & value, const MP2::MapObjectType type, const bool isDimensionDoor ) { if ( !isDimensionDoor ) { // Dimension door path does not include any objects on the way. std::vector list = _pathfinder.getObjectsOnTheWay( destination ); for ( const IndexObject & pair : list ) { if ( objectValidator.isValid( pair.first ) && std::binary_search( _mapActionObjects.begin(), _mapActionObjects.end(), pair ) ) { const double extraValue = valueStorage.value( pair, 0 ); // object is on the way, we don't loose any movement points. if ( extraValue > 0 ) { // There is no need to reduce the quality of the object even if the path has others. value += extraValue; } } } } const Maps::Tiles & destinationTile = world.GetTiles( destination ); // TODO: check nearby enemy heroes and distance to them instead of relying on region stats. const RegionStats & regionStats = _regions[destinationTile.GetRegion()]; const bool isObjectReachableAtThisTurn = ( distance <= hero.GetMovePoints() ); // Go into "coward" mode only if the threat is real. Equal by strength heroes rarely attack each other. if ( heroStrength * AI::ARMY_ADVANTAGE_SMALL < regionStats.highestThreat ) { switch ( type ) { case MP2::OBJ_CASTLE: { const Castle * castle = world.getCastleEntrance( Maps::GetPoint( destination ) ); if ( castle == nullptr ) { assert( 0 ); break; } if ( castle->GetColor() == hero.GetColor() ) { // Friendly castles are always the priority so no penalty for them. break; } if ( isObjectReachableAtThisTurn ) { if ( castle->GetGarrisonStrength( hero ) > heroStrength / 2 ) { value -= dangerousTaskPenalty / 4; } else { value -= dangerousTaskPenalty / 10; } } else if ( castle->GetGarrisonStrength( hero ) > heroStrength / 2 ) { value -= dangerousTaskPenalty / 2; } else { value -= dangerousTaskPenalty / 3; } break; } case MP2::OBJ_HERO: { const Heroes * anotherHero = destinationTile.getHero(); if ( anotherHero == nullptr ) { assert( 0 ); break; } if ( anotherHero->GetColor() == hero.GetColor() ) { if ( isObjectReachableAtThisTurn ) { value -= dangerousTaskPenalty / 8; } else { value -= dangerousTaskPenalty / 4; } } else { if ( isObjectReachableAtThisTurn ) { value -= dangerousTaskPenalty / 8; } else { value -= dangerousTaskPenalty / 2; } } break; } default: // It is better to avoid all other objects if the current hero is under a big threat. value -= dangerousTaskPenalty; break; } } if ( !isObjectReachableAtThisTurn ) { // Distant object which is out of reach for the current turn must have lower priority. distance = hero.GetMovePoints() + ( distance - hero.GetMovePoints() ) * 2; } value = scaleWithDistanceAndTime( value, distance, type ); }; // Set baseline target if it's a special role if ( hero.getAIRole() == Heroes::Role::COURIER ) { const int courierTarget = getCourierMainTarget( hero, _pathfinder, lowestPossibleValue ); if ( courierTarget != -1 ) { // Anything with positive value can override the courier's main task (i.e. castle or mine capture on the way) maxPriority = 0; priorityTarget = courierTarget; #ifdef WITH_DEBUG objectType = world.GetTiles( courierTarget ).GetObject(); #endif DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << " is a courier with a main target tile at " << courierTarget ) } else { // If there's nothing to do as a Courier reset the role hero.setAIRole( Heroes::Role::HUNTER ); } } for ( const IndexObject & node : _mapActionObjects ) { // Skip if hero in patrol mode and object outside of reach if ( heroInPatrolMode && Maps::GetApproximateDistance( node.first, heroInfo.patrolCenter ) > heroInfo.patrolDistance ) continue; if ( objectValidator.isValid( node.first ) ) { uint32_t dist = _pathfinder.getDistance( node.first ); bool useDimensionDoor = false; const uint32_t dimensionDoorDist = Route::calculatePathPenalty( _pathfinder.getDimensionDoorPath( hero, node.first ) ); if ( dimensionDoorDist > 0 && ( dist == 0 || dimensionDoorDist < dist / 2 ) ) { dist = dimensionDoorDist; useDimensionDoor = true; } if ( dist == 0 ) { continue; } double value = valueStorage.value( node, dist ); getObjectValue( node.first, dist, value, static_cast( node.second ), useDimensionDoor ); if ( dist && value > maxPriority ) { maxPriority = value; priorityTarget = node.first; #ifdef WITH_DEBUG objectType = static_cast( node.second ); #endif DEBUG_LOG( DBG_AI, DBG_TRACE, hero.GetName() << ": valid object at " << node.first << " value is " << value << " (" << MP2::StringObject( static_cast( node.second ) ) << ")" ) } } } double fogDiscoveryValue = getFogDiscoveryValue( hero ); const auto [fogDiscoveryTarget, isTerritoryExpansion] = _pathfinder.getFogDiscoveryTile( hero ); if ( fogDiscoveryTarget >= 0 ) { uint32_t distanceToFogDiscovery = _pathfinder.getDistance( fogDiscoveryTarget ); // TODO: add logic to check fog discovery based on Dimension Door distance, not the nearest tile. bool useDimensionDoor = false; const uint32_t dimensionDoorDist = Route::calculatePathPenalty( _pathfinder.getDimensionDoorPath( hero, fogDiscoveryTarget ) ); if ( dimensionDoorDist > 0 && ( distanceToFogDiscovery == 0 || dimensionDoorDist < distanceToFogDiscovery / 2 ) ) { distanceToFogDiscovery = dimensionDoorDist; useDimensionDoor = true; } if ( isTerritoryExpansion ) { // Over time the AI should focus more on territory expansion. const uint32_t period = getTimeoutBeforeFogDiscoveryIntensification( hero ); assert( period > 0 ); if ( fogDiscoveryValue < 0 ) { // This is actually a very useful fog discovery action which might lead to finding of new objects. // Increase the value of this action. if ( world.CountDay() > period ) { fogDiscoveryValue = 0; } else { fogDiscoveryValue = fogDiscoveryValue / 2 * ( period - world.CountDay() ) / period; } } else { // Over time the AI should focus more on territory expansion. // Scouts must focus on expansion so they should reach maximum "attention" in a month. fogDiscoveryValue += std::min( 1000.0 * world.CountDay() / period, 1000.0 ); } } getObjectValue( fogDiscoveryTarget, distanceToFogDiscovery, fogDiscoveryValue, MP2::OBJ_NONE, useDimensionDoor ); } if ( priorityTarget != -1 ) { if ( fogDiscoveryTarget >= 0 && fogDiscoveryValue > maxPriority ) { priorityTarget = fogDiscoveryTarget; maxPriority = fogDiscoveryValue; DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << ": selected target: " << priorityTarget << " value is " << maxPriority << " (fog discovery)" ) } else { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << ": selected target: " << priorityTarget << " value is " << maxPriority << " (" << MP2::StringObject( objectType ) << ")" ) } } else if ( !heroInPatrolMode ) { priorityTarget = fogDiscoveryTarget; DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << " can't find an object. Scouting the fog of war at " << priorityTarget ) } else { DEBUG_LOG( DBG_AI, DBG_INFO, hero.GetName() << " is in Patrol mode. No movement is required." ) } return priorityTarget; } void Normal::updatePriorityTargets( Heroes & hero, int32_t tileIndex, const MP2::MapObjectType objectType ) { if ( objectType != MP2::OBJ_CASTLE && objectType != MP2::OBJ_HERO ) { // Priorities are only for castles and heroes at the moment. return; } const auto updateTile = [this, &hero, tileIndex, objectType]() { const auto updateCastle = [this, &hero, tileIndex]() { const Castle * castle = world.getCastleEntrance( Maps::GetPoint( tileIndex ) ); if ( castle == nullptr ) { // How is it possible? assert( 0 ); _enemyArmies.erase( tileIndex ); return; } if ( hero.isFriends( castle->GetColor() ) ) { _enemyArmies.erase( tileIndex ); updatePriorityForCastle( *castle ); } else { updatePriorityAttackTarget( hero.GetKingdom(), world.GetTiles( tileIndex ) ); } }; if ( objectType == MP2::OBJ_CASTLE ) { updateCastle(); } else if ( objectType == MP2::OBJ_HERO ) { const Maps::Tiles & tile = world.GetTiles( tileIndex ); const Heroes * anotherHero = tile.getHero(); if ( anotherHero == nullptr ) { // Another hero lost the battle, but he could defend a castle if ( tile.GetObject() == MP2::OBJ_CASTLE ) { updateCastle(); } else { _enemyArmies.erase( tileIndex ); } return; } if ( !hero.isFriends( anotherHero->GetColor() ) ) { updatePriorityAttackTarget( hero.GetKingdom(), tile ); } } else { // Unsupported object type! assert( 0 ); } }; auto it = _priorityTargets.find( tileIndex ); if ( it == _priorityTargets.end() ) { // If the object is not a priority we have to update it after the battle as it can become the one. // Especially, when the opposite army has grown Skeletons or Ghosts. updateTile(); // If the update did not add any priorities then nothing more to do. it = _priorityTargets.find( tileIndex ); if ( it == _priorityTargets.end() ) { return; } } const PriorityTask & task = it->second; switch ( task.type ) { case PriorityTaskType::DEFEND: case PriorityTaskType::REINFORCE: { if ( hero.GetIndex() != tileIndex ) { // Either the castle has just been captured, or the hero meets the guest hero of a friendly castle. No task should be updated. // If any of these assertions blow up, then this is not one of these cases. #ifndef NDEBUG const Maps::Tiles & tile = world.GetTiles( tileIndex ); #endif assert( tile.GetObject( false ) == MP2::OBJ_CASTLE && hero.GetColor() == Maps::getColorFromTile( tile ) ); assert( Maps::isValidDirection( tileIndex, Direction::BOTTOM ) && hero.GetIndex() == Maps::GetDirectionIndex( tileIndex, Direction::BOTTOM ) ); return; } // These tasks are only for castles at the moment! assert( objectType == MP2::OBJ_CASTLE ); // How is it even possible that a hero died while simply moving into a castle? assert( hero.isActive() ); // TODO: sort the army between the castle and hero to have maximum movement points for the next day // TODO: but also have enough army to defend the castle. hero.SetModes( Heroes::SLEEPER ); _priorityTargets.erase( tileIndex ); break; } case PriorityTaskType::ATTACK: { removePriorityAttackTarget( tileIndex ); updateTile(); break; } default: // Did you add a new type of priority task? Add the logic above! assert( 0 ); break; } } void Normal::HeroesBeginMovement( Heroes & hero ) { assert( hero.isActive() ); const Route::Path & path = hero.GetPath(); if ( !path.isValidForMovement() ) { return; } const int32_t heroIdx = hero.GetIndex(); const int frontDirection = path.GetFrontDirection(); assert( Maps::isValidDirection( heroIdx, frontDirection ) ); const int32_t nextTileIdx = Maps::GetDirectionIndex( heroIdx, frontDirection ); const Maps::Tiles & currTile = world.GetTiles( heroIdx ); const Maps::Tiles & nextTile = world.GetTiles( nextTileIdx ); if ( currTile.isWater() || !nextTile.isWater() || nextTile.GetObject() != MP2::OBJ_NONE ) { return; } // If the hero goes to the water tile, then this should be his last movement assert( path.size() == 1 ); const int32_t formerBoatIdx = HeroesCastSummonBoat( hero, nextTileIdx ); updateMapActionObjectCache( formerBoatIdx ); updateMapActionObjectCache( nextTileIdx ); } void Normal::HeroesActionComplete( Heroes & hero, const int32_t tileIndex, const MP2::MapObjectType objectType ) { // This method is called upon action completion and the hero could no longer be available. // So it is to check if the hero is still present. if ( hero.isActive() ) { Castle * castle = hero.inCastleMutable(); if ( castle ) { reinforceHeroInCastle( hero, *castle, castle->GetKingdom().GetFunds() ); } else { OptimizeTroopsOrder( hero.GetArmy() ); } } if ( isMonsterStrengthCacheable( objectType ) ) { _neutralMonsterStrengthCache.erase( tileIndex ); } updatePriorityTargets( hero, tileIndex, objectType ); updateMapActionObjectCache( tileIndex ); } void Normal::HeroesActionNewPosition( Heroes & hero ) { if ( !hero.isActive() ) { return; } Route::Path & path = hero.GetPath(); if ( !path.isValidForMovement() ) { return; } const int32_t heroIdx = hero.GetIndex(); assert( heroIdx == path.GetFrontIndex() ); const int nextStepDirection = path.GetNextStepDirection(); if ( !Maps::isValidDirection( heroIdx, nextStepDirection ) ) { return; } const int32_t nextTileIdx = Maps::GetDirectionIndex( heroIdx, nextStepDirection ); const Maps::Tiles & currTile = world.GetTiles( heroIdx ); const Maps::Tiles & nextTile = world.GetTiles( nextTileIdx ); if ( currTile.isWater() || !nextTile.isWater() || nextTile.GetObject() != MP2::OBJ_NONE ) { return; } // If the hero goes to the water tile, then this should be his last movement // (not counting the current step, which is not yet completed at the moment) assert( path.size() == 2 ); // It may happen that although the hero at the beginning of his path had enough spell points to // summon a boat, but while moving through the guarded tiles, these spell points were spent. In // this case, just stop. if ( !hero.CanCastSpell( Spell::SUMMONBOAT ) ) { path.Reset(); hero.SetMove( false ); return; } const int32_t formerBoatIdx = HeroesCastSummonBoat( hero, nextTileIdx ); updateMapActionObjectCache( formerBoatIdx ); updateMapActionObjectCache( nextTileIdx ); } bool Normal::isValidHeroObject( const Heroes & hero, const int32_t index, const bool underHero ) { return HeroesValidObject( hero, hero.GetArmy().GetStrength(), index, _pathfinder, *this, hero.getAIMinimumJoiningArmyStrength(), underHero ); } bool Normal::HeroesTurn( VecHeroes & heroes, const uint32_t startProgressValue, const uint32_t endProgressValue ) { if ( heroes.empty() ) { // No heroes so we indicate that all heroes moved. return true; } std::vector availableHeroes; for ( Heroes * hero : heroes ) { assert( hero != nullptr ); addHeroToMove( hero, availableHeroes ); } Interface::StatusWindow & status = Interface::AdventureMap::Get().getStatusWindow(); uint32_t currentProgressValue = startProgressValue; while ( !availableHeroes.empty() ) { class AIWorldPathfinderStateRestorer { public: explicit AIWorldPathfinderStateRestorer( AIWorldPathfinder & pathfinder ) : _pathfinder( pathfinder ) , _originalMinimalArmyStrengthAdvantage( _pathfinder.getMinimalArmyStrengthAdvantage() ) , _originalSpellPointsReserveRatio( _pathfinder.getSpellPointsReserveRatio() ) {} AIWorldPathfinderStateRestorer( const AIWorldPathfinderStateRestorer & ) = delete; ~AIWorldPathfinderStateRestorer() { _pathfinder.setMinimalArmyStrengthAdvantage( _originalMinimalArmyStrengthAdvantage ); _pathfinder.setSpellPointsReserveRatio( _originalSpellPointsReserveRatio ); } AIWorldPathfinderStateRestorer & operator=( const AIWorldPathfinderStateRestorer & ) = delete; private: AIWorldPathfinder & _pathfinder; const double _originalMinimalArmyStrengthAdvantage; const double _originalSpellPointsReserveRatio; }; const AIWorldPathfinderStateRestorer pathfinderStateRestorer( _pathfinder ); Heroes * bestHero = availableHeroes.front().hero; int bestTargetIndex = -1; { const bool isLosingGame = bestHero->isLosingGame(); static const std::vector> commonPathfinderConfigurations{ { ARMY_ADVANTAGE_LARGE, 0.5 }, { ARMY_ADVANTAGE_MEDIUM, 0.25 }, { ARMY_ADVANTAGE_SMALL, 0.0 } }; static const std::vector> emergencyPathfinderConfigurations{ { ARMY_ADVANTAGE_DESPERATE, 0.0 } }; for ( const auto & [minStrengthAdvantage, spReserveRatio] : isLosingGame ? emergencyPathfinderConfigurations : commonPathfinderConfigurations ) { _pathfinder.setMinimalArmyStrengthAdvantage( minStrengthAdvantage ); _pathfinder.setSpellPointsReserveRatio( spReserveRatio ); double maxPriority = 0; for ( const HeroToMove & heroInfo : availableHeroes ) { double priority = -1; const int targetIndex = getPriorityTarget( heroInfo, priority ); if ( targetIndex != -1 && ( priority > maxPriority || bestTargetIndex == -1 ) ) { maxPriority = priority; bestTargetIndex = targetIndex; bestHero = heroInfo.hero; } } if ( bestTargetIndex != -1 ) { break; } } } if ( bestTargetIndex == -1 ) { // Possibly heroes have nothing to do because one of them is blocking the way. Move a random hero randomly and see what happens. Rand::Shuffle( availableHeroes ); for ( HeroToMove & heroInfo : availableHeroes ) { // Skip heroes who are in castles or on patrol. if ( heroInfo.patrolCenter >= 0 && heroInfo.hero->inCastle() != nullptr ) { continue; } if ( !AIWorldPathfinder::isHeroPossiblyBlockingWay( *heroInfo.hero ) ) { continue; } const int targetIndex = _pathfinder.getNearestTileToMove( *heroInfo.hero ); if ( targetIndex != -1 ) { bestTargetIndex = targetIndex; bestHero = heroInfo.hero; DEBUG_LOG( DBG_AI, DBG_INFO, bestHero->GetName() << " may be blocking the way. Moving to " << bestTargetIndex ) break; } } } if ( bestTargetIndex == -1 ) { // Nothing to do. Stop everything break; } const size_t heroesBefore = heroes.size(); _pathfinder.reEvaluateIfNeeded( *bestHero ); int prevHeroPosition = bestHero->GetIndex(); // check if we want to use Dimension Door spell or move regularly std::list dimensionPath = _pathfinder.getDimensionDoorPath( *bestHero, bestTargetIndex ); uint32_t dimensionDoorDistance = Route::calculatePathPenalty( dimensionPath ); uint32_t moveDistance = _pathfinder.getDistance( bestTargetIndex ); if ( dimensionDoorDistance && ( !moveDistance || dimensionDoorDistance < moveDistance / 2 ) ) { while ( ( !moveDistance || dimensionDoorDistance < moveDistance / 2 ) && !dimensionPath.empty() && bestHero->MayStillMove( false, false ) && bestHero->CanCastSpell( Spell::DIMENSIONDOOR ) ) { HeroesCastDimensionDoor( *bestHero, dimensionPath.front().GetIndex() ); dimensionDoorDistance -= dimensionPath.front().GetPenalty(); _pathfinder.reEvaluateIfNeeded( *bestHero ); moveDistance = _pathfinder.getDistance( bestTargetIndex ); dimensionPath.pop_front(); // Hero can jump straight into the fog using the Dimension Door spell, which triggers the mechanics of fog revealing for his new tile // and this results in inserting a new hero position into the action object cache. Perform the necessary updates. assert( bestHero->isActive() && bestHero->GetIndex() != prevHeroPosition ); updateMapActionObjectCache( prevHeroPosition ); updateMapActionObjectCache( bestHero->GetIndex() ); prevHeroPosition = bestHero->GetIndex(); } if ( dimensionDoorDistance > 0 ) { // The rest of the path the hero should do by foot. bestHero->GetPath().setPath( _pathfinder.buildPath( bestTargetIndex ) ); HeroesMove( *bestHero ); } } else { bestHero->GetPath().setPath( _pathfinder.buildPath( bestTargetIndex ) ); HeroesMove( *bestHero ); } if ( !bestHero->isActive() || bestHero->GetIndex() != prevHeroPosition ) { // The hero died or moved to another position. We have to update the action object cache. updateMapActionObjectCache( prevHeroPosition ); if ( bestHero->isActive() ) { // Hero moved to another position and is still alive. updateMapActionObjectCache( bestHero->GetIndex() ); } } if ( heroes.size() > heroesBefore ) { addHeroToMove( heroes.back(), availableHeroes ); } availableHeroes.erase( std::remove_if( availableHeroes.begin(), availableHeroes.end(), []( const HeroToMove & item ) { return !item.hero->MayStillMove( false, false ); } ), availableHeroes.end() ); // The size of heroes can be increased if a new hero is released from Jail. const size_t maxHeroCount = std::max( heroes.size(), availableHeroes.size() ); if ( maxHeroCount > 0 ) { // At least one hero still exist in the kingdom. const size_t progressValue = ( endProgressValue - startProgressValue ) * ( maxHeroCount - availableHeroes.size() ) / maxHeroCount + startProgressValue; if ( currentProgressValue < progressValue ) { currentProgressValue = static_cast( progressValue ); status.DrawAITurnProgress( currentProgressValue ); } } } status.DrawAITurnProgress( endProgressValue ); return availableHeroes.empty(); } } fheroes2-1.0.12+dfsg/src/fheroes2/ai/normal/ai_normal_kingdom.cpp000066400000000000000000001126071456075706000246120ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ai.h" #include "ai_normal.h" #include "army.h" #include "army_troop.h" #include "audio.h" #include "audio_manager.h" #include "castle.h" #include "color.h" #include "difficulty.h" #include "game.h" #include "game_interface.h" #include "ground.h" #include "heroes.h" #include "heroes_recruits.h" #include "interface_status.h" #include "kingdom.h" #include "logging.h" #include "maps.h" #include "maps_tiles.h" #include "mp2.h" #include "mus.h" #include "pairs.h" #include "players.h" #include "resource.h" #include "skill.h" #include "spell.h" #include "world.h" #include "world_pathfinding.h" #include "world_regions.h" namespace { struct HeroValue { Heroes * hero = nullptr; double strength = 0.0; int stats = 0; HeroValue( Heroes * inHero, double inStrength, int inStats ) : hero( inHero ) , strength( inStrength ) , stats( inStats ) { // Do nothing. } }; class TemporaryHeroEraser { public: TemporaryHeroEraser() = delete; TemporaryHeroEraser( const TemporaryHeroEraser & ) = delete; TemporaryHeroEraser( TemporaryHeroEraser && ) = delete; explicit TemporaryHeroEraser( const std::vector & heroes ) { for ( Heroes * hero : heroes ) { assert( hero != nullptr && hero->isActive() ); Maps::Tiles & tile = world.GetTiles( hero->GetIndex() ); if ( tile.getHero() == nullptr ) { // This could happen when a hero is moving. continue; } assert( tile.getHero() == hero ); _heroes.emplace_back( hero ); tile.setHero( nullptr ); } } ~TemporaryHeroEraser() { for ( Heroes * hero : _heroes ) { Maps::Tiles & tile = world.GetTiles( hero->GetIndex() ); assert( tile.getHero() == nullptr ); tile.setHero( hero ); } } TemporaryHeroEraser & operator=( const TemporaryHeroEraser & ) = delete; TemporaryHeroEraser & operator=( TemporaryHeroEraser && ) = delete; private: std::vector _heroes; }; void setHeroRoles( VecHeroes & heroes, const int difficulty ) { if ( heroes.empty() ) { // No heroes exist. return; } if ( !Difficulty::areAIHeroRolesAllowed( difficulty ) ) { // All heroes are equal. for ( Heroes * hero : heroes ) { hero->setAIRole( Heroes::Role::HUNTER ); } return; } const Heroes * valuableHero = world.GetHeroesCondWins(); if ( heroes.size() == 1 ) { if ( valuableHero != nullptr && valuableHero == heroes[0] ) { heroes[0]->setAIRole( Heroes::Role::CHAMPION ); } else { // A single hero has no roles. heroes[0]->setAIRole( Heroes::Role::HUNTER ); } return; } // Set hero's roles. First calculate each hero strength and sort it in descending order. std::vector heroList; for ( Heroes * hero : heroes ) { // AI heroes set on patrol mode can only be fighters; ignore them otherwise if ( hero->Modes( Heroes::PATROL ) ) { hero->setAIRole( Heroes::Role::FIGHTER ); } else { heroList.emplace_back( hero, hero->GetArmy().GetStrength(), hero->getStatsValue() ); } } if ( heroList.empty() ) { // No more heroes. return; } // If there's plenty of heroes we can assign special roles if ( heroList.size() > 3 ) { std::sort( heroList.begin(), heroList.end(), []( const HeroValue & first, const HeroValue & second ) { return first.stats > second.stats; } ); if ( valuableHero == nullptr ) { heroList.front().hero->setAIRole( Heroes::Role::CHAMPION ); heroList.erase( heroList.begin() ); } // Assign the courier role and remove them so they aren't counted towards the median strength heroList.back().hero->setAIRole( Heroes::Role::COURIER ); heroList.pop_back(); if ( heroList.size() > 2 ) { // We still have a plenty of heroes. In this case lets create a Scout hero to uncover the fog. heroList.back().hero->setAIRole( Heroes::Role::SCOUT ); heroList.pop_back(); } } assert( !heroList.empty() ); std::sort( heroList.begin(), heroList.end(), []( const HeroValue & first, const HeroValue & second ) { return first.strength > second.strength; } ); const double medianStrength = heroList[heroList.size() / 2].strength; for ( HeroValue & object : heroList ) { if ( valuableHero != nullptr && object.hero == valuableHero ) { object.hero->setAIRole( Heroes::Role::CHAMPION ); continue; } if ( object.strength > medianStrength * 3 ) { object.hero->setAIRole( Heroes::Role::FIGHTER ); } else { object.hero->setAIRole( Heroes::Role::HUNTER ); } } } std::optional getEnemyArmyOnTile( const int kingdomColor, const Maps::Tiles & tile ) { const MP2::MapObjectType object = tile.GetObject(); const int32_t tileIndex = tile.GetIndex(); if ( object == MP2::OBJ_HERO ) { const Heroes * hero = tile.getHero(); // TODO: this function can be called when the game world is not fully initialized yet if ( hero == nullptr ) { return {}; } if ( hero->isFriends( kingdomColor ) ) { return {}; } // If the hero is standing in one place, then he does not pose a threat (as well as the castle in which he may be located) if ( hero->Modes( Heroes::PATROL ) && hero->GetPatrolDistance() == 0 ) { return {}; } const Castle * castle = hero->inCastle(); // Rough estimate - if the hero is in the castle, then we sum up the power of the castle garrison with the power of the hero's army const double threat = castle ? castle->GetArmy().GetStrength() + hero->GetArmy().GetStrength() : hero->GetArmy().GetStrength(); return AI::EnemyArmy( tileIndex, hero, threat, hero->GetMaxMovePoints() ); } if ( object == MP2::OBJ_CASTLE ) { const Castle * castle = world.getCastleEntrance( Maps::GetPoint( tileIndex ) ); // TODO: this function can be called when the game world is not fully initialized yet if ( castle == nullptr ) { return {}; } // Neutral castles don't pose a threat because they can't hire heroes if ( castle->GetColor() == Color::NONE || castle->isFriends( kingdomColor ) ) { return {}; } // If it's just a town where there's no way to build a castle, then there's no way to hire heroes who might pose a threat if ( !castle->isCastle() && !castle->AllowBuyBuilding( BUILD_CASTLE ) ) { return {}; } const double threat = castle->GetArmy().GetStrength(); // 1500 is slightly more than a fresh hero's maximum move points hired in a castle. return AI::EnemyArmy( tileIndex, nullptr, threat, 1500 ); } return {}; } } namespace AI { bool Normal::recruitHero( Castle & castle, bool buyArmy, bool underThreat ) { Kingdom & kingdom = castle.GetKingdom(); const Recruits & rec = kingdom.GetRecruits(); Heroes * recruit = nullptr; // Re-hiring a hero related to any of the WINS_HERO or LOSS_HERO conditions is not allowed const auto heroesToIgnore = std::make_pair( world.GetHeroesCondWins(), world.GetHeroesCondLoss() ); const auto useIfPossible = [&heroesToIgnore]( Heroes * hero ) -> Heroes * { if ( std::apply( [hero]( const auto... heroToIgnore ) { return ( ( hero == heroToIgnore ) || ... ); }, heroesToIgnore ) ) { return nullptr; } return hero; }; Heroes * firstRecruit = useIfPossible( rec.GetHero1() ); Heroes * secondRecruit = useIfPossible( rec.GetHero2() ); if ( firstRecruit && secondRecruit ) { if ( secondRecruit->getRecruitValue() > firstRecruit->getRecruitValue() ) { recruit = castle.RecruitHero( secondRecruit ); } else { recruit = castle.RecruitHero( firstRecruit ); } } else if ( firstRecruit ) { recruit = castle.RecruitHero( firstRecruit ); } else if ( secondRecruit ) { recruit = castle.RecruitHero( secondRecruit ); } if ( recruit == nullptr ) { return false; } if ( buyArmy ) { CastleTurn( castle, underThreat ); reinforceHeroInCastle( *recruit, castle, kingdom.GetFunds() ); } else { OptimizeTroopsOrder( recruit->GetArmy() ); } return true; } void Normal::reinforceHeroInCastle( Heroes & hero, Castle & castle, const Funds & budget ) { // It is impossible to reinforce dead heroes. assert( hero.isActive() ); const Heroes::AIHeroMeetingUpdater heroMeetingUpdater( hero ); if ( !hero.HaveSpellBook() && castle.GetLevelMageGuild() > 0 && !hero.IsFullBagArtifacts() ) { // this call will check if AI kingdom have enough resources to buy book hero.BuySpellBook( &castle ); } Army & heroArmy = hero.GetArmy(); Army & garrison = castle.GetArmy(); // Merge all troops in the castle to have the best army. heroArmy.JoinStrongestFromArmy( garrison ); // Upgrade troops and try to merge them again. heroArmy.UpgradeTroops( castle ); garrison.UpgradeTroops( castle ); heroArmy.JoinStrongestFromArmy( garrison ); // Recruit more troops and also merge them. castle.recruitBestAvailable( budget ); heroArmy.JoinStrongestFromArmy( garrison ); const uint32_t regionID = world.GetTiles( castle.GetIndex() ).GetRegion(); // Check if we should leave some troops in the garrison // TODO: amount of troops left could depend on region's safetyFactor if ( castle.isCastle() && _regions[regionID].safetyFactor <= 100 && !garrison.isValid() ) { auto [troopForTransferToGarrison, transferHalf] = [&hero, &heroArmy]() -> std::pair { const Heroes::Role heroRole = hero.getAIRole(); const bool isFighterRole = ( heroRole == Heroes::Role::FIGHTER || heroRole == Heroes::Role::CHAMPION ); // We need to compare a strength of troops excluding hero's stats. const double troopsStrength = Troops( heroArmy.getTroops() ).GetStrength(); const double significanceRatio = isFighterRole ? 20.0 : 10.0; { Troop * candidateTroop = heroArmy.GetSlowestTroop(); assert( candidateTroop != nullptr ); if ( candidateTroop->GetStrength() <= troopsStrength / significanceRatio ) { return { candidateTroop, false }; } } // if this is an important hero, then all his troops are significant if ( isFighterRole ) { return {}; } { Troop * candidateTroop = heroArmy.GetWeakestTroop(); assert( candidateTroop != nullptr ); if ( candidateTroop->GetStrength() <= troopsStrength / significanceRatio ) { return { candidateTroop, true }; } } return {}; }(); if ( troopForTransferToGarrison ) { assert( heroArmy.GetOccupiedSlotCount() > 1 ); const uint32_t initialCount = troopForTransferToGarrison->GetCount(); const uint32_t countToTransfer = transferHalf ? initialCount / 2 : initialCount; if ( garrison.JoinTroop( troopForTransferToGarrison->GetMonster(), countToTransfer, true ) ) { if ( countToTransfer == initialCount ) { troopForTransferToGarrison->Reset(); } else { troopForTransferToGarrison->SetCount( initialCount - countToTransfer ); } } } } OptimizeTroopsOrder( heroArmy ); OptimizeTroopsOrder( garrison ); } void Normal::evaluateRegionSafety() { std::vector> regionsToCheck; size_t lastPositive = 0; for ( size_t regionID = 0; regionID < _regions.size(); ++regionID ) { RegionStats & stats = _regions[regionID]; if ( ( stats.friendlyCastles && stats.enemyCastles ) || ( stats.highestThreat > 0 && !stats.enemyCastles ) ) { // contested space OR enemy heroes invaded our region // TODO: assess army strength to get more accurate reading stats.safetyFactor = -50; stats.evaluated = true; regionsToCheck.emplace_back( regionID, -50 ); } else if ( stats.enemyCastles ) { // straight up enemy territory stats.safetyFactor = -100; stats.evaluated = true; regionsToCheck.emplace_back( regionID, -100 ); } else if ( stats.friendlyCastles ) { // our protected castle stats.safetyFactor = 100; stats.evaluated = true; regionsToCheck.emplace_back( regionID, 100 ); ++lastPositive; } else { stats.safetyFactor = 0; stats.evaluated = false; } } std::sort( regionsToCheck.begin(), regionsToCheck.end(), []( const std::pair & left, const std::pair & right ) { return left.second > right.second; } ); size_t currentEntry = 0; size_t batchStart = 0; size_t batchEnd = lastPositive + 1; while ( currentEntry < regionsToCheck.size() ) { const MapRegion & region = world.getRegion( regionsToCheck[currentEntry].first ); for ( uint32_t secondaryID : region._neighbours ) { RegionStats & adjacentStats = _regions[secondaryID]; if ( !adjacentStats.evaluated ) { adjacentStats.evaluated = true; regionsToCheck.emplace_back( secondaryID, adjacentStats.safetyFactor ); } if ( adjacentStats.safetyFactor != 0 ) { // losing precision due to integer division is intentional here, values should be reduced to 0 eventually regionsToCheck[currentEntry].second += adjacentStats.safetyFactor / static_cast( region.getNeighboursCount() ); } } // no neighbours means it is an island; they are usually safer (or more dangerous to explore) due to boat movement penalties if ( region.getNeighboursCount() == 0 ) regionsToCheck[currentEntry].second = regionsToCheck[currentEntry].second * 3 / 2; if ( currentEntry == batchEnd - 1 ) { // Apply the calculated value in batches for ( size_t idx = batchStart; idx < batchEnd; ++idx ) { const size_t regionID = regionsToCheck[idx].first; _regions[regionID].safetyFactor = regionsToCheck[idx].second; DEBUG_LOG( DBG_AI, DBG_TRACE, "Region " << regionID << " safety factor is " << _regions[regionID].safetyFactor ) } batchStart = batchEnd; batchEnd = regionsToCheck.size(); } ++currentEntry; } } std::vector Normal::getSortedCastleList( const VecCastles & castles, const std::set & castlesInDanger ) { std::vector sortedCastleList; for ( Castle * castle : castles ) { if ( !castle ) continue; const int32_t castleIndex = castle->GetIndex(); const uint32_t regionID = world.GetTiles( castleIndex ).GetRegion(); sortedCastleList.emplace_back( castle, castlesInDanger.count( castleIndex ) > 0, _regions[regionID].safetyFactor, castle->getBuildingValue() ); } std::sort( sortedCastleList.begin(), sortedCastleList.end(), []( const AICastle & left, const AICastle & right ) { if ( !left.underThreat && !right.underThreat ) { return left.safetyFactor > right.safetyFactor; } if ( left.underThreat && !right.underThreat ) { return true; } if ( !left.underThreat && right.underThreat ) { return false; } // We have a building value of a castle and its safety factor. The higher safety factor the lower priority to defend the castle. // Since we compare 2 castles we need to use safety factor of the opposite castle. return left.buildingValue * right.safetyFactor > right.buildingValue * left.safetyFactor; } ); return sortedCastleList; } std::set Normal::findCastlesInDanger( const Kingdom & kingdom ) { std::set castlesInDanger; // Since we are estimating danger for a castle and we need to know if an enemy hero can reach it // if no our heroes exist. So we are temporary removing them from the map. const TemporaryHeroEraser heroEraser( kingdom.GetHeroes() ); for ( const auto & [dummy, enemyArmy] : _enemyArmies ) { for ( const Castle * castle : kingdom.GetCastles() ) { if ( castle == nullptr ) { // How is it even possible? Check the logic! assert( 0 ); continue; } if ( updateIndividualPriorityForCastle( *castle, enemyArmy ) ) { castlesInDanger.insert( castle->GetIndex() ); } } } return castlesInDanger; } void Normal::updatePriorityForEnemyArmy( const Kingdom & kingdom, const EnemyArmy & enemyArmy ) { // Since we are estimating danger for a castle and we need to know if an enemy hero can reach it // if no our heroes exist. So we are temporary removing them from the map. const TemporaryHeroEraser heroEraser( kingdom.GetHeroes() ); for ( const Castle * castle : kingdom.GetCastles() ) { if ( castle == nullptr ) { // How is it even possible? Check the logic! assert( 0 ); continue; } updateIndividualPriorityForCastle( *castle, enemyArmy ); } } void Normal::updatePriorityForCastle( const Castle & castle ) { // Since we are estimating danger for a castle and we need to know if an enemy hero can reach it // if no our heroes exist. So we are temporary removing them from the map. const TemporaryHeroEraser heroEraser( castle.GetKingdom().GetHeroes() ); for ( const auto & [dummy, enemyArmy] : _enemyArmies ) { updateIndividualPriorityForCastle( castle, enemyArmy ); } } bool Normal::updateIndividualPriorityForCastle( const Castle & castle, const EnemyArmy & enemyArmy ) { // 30 tiles, roughly how much maxed out hero can move in a turn. const uint32_t threatDistanceLimit = 3000; const int32_t castleIndex = castle.GetIndex(); // skip precise distance check if army is too far to be a threat if ( Maps::GetApproximateDistance( enemyArmy.index, castleIndex ) * Maps::Ground::roadPenalty > threatDistanceLimit ) { return false; } const uint32_t dist = _pathfinder.getDistance( enemyArmy.index, castleIndex, castle.GetColor(), enemyArmy.strength ); if ( dist == 0 || dist >= threatDistanceLimit ) { return false; } uint32_t daysToReach = ( dist + enemyArmy.movePoints - 1 ) / enemyArmy.movePoints; if ( daysToReach > 3 ) { // It is too far away. Ignore it. return false; } double enemyStrength = enemyArmy.strength; --daysToReach; while ( daysToReach > 0 ) { // Each day reduces enemy strength by 50%. If an enemy is too far away then there is no reason to panic. enemyStrength /= 2; --daysToReach; } auto attackTask = _priorityTargets.find( enemyArmy.index ); if ( attackTask == _priorityTargets.end() ) { _priorityTargets[enemyArmy.index] = { PriorityTaskType::ATTACK, castleIndex }; } else { attackTask->second.secondaryTaskTileId.insert( castleIndex ); } auto defenseTask = _priorityTargets.find( castleIndex ); if ( defenseTask == _priorityTargets.end() ) { _priorityTargets[castleIndex] = { PriorityTaskType::DEFEND, enemyArmy.index }; } else { defenseTask->second.secondaryTaskTileId.insert( enemyArmy.index ); } // If the castle guard (including the garrison and the guest hero) is weaker than the enemy, then the // castle is considered to be in danger if ( castle.GetGarrisonStrength( enemyArmy.hero ) < enemyStrength ) { return true; } // If the guest hero himself is not able to defeat a threatening enemy in an open field, then the castle // is considered to be in danger, and the guest hero should probably stay in it const Heroes * hero = castle.GetHero(); if ( hero && hero->GetArmy().GetStrength() <= enemyStrength * ARMY_ADVANTAGE_SMALL ) { return true; } return false; } void Normal::removePriorityAttackTarget( const int32_t tileIndex ) { const auto it = _priorityTargets.find( tileIndex ); if ( it == _priorityTargets.end() ) { return; } const PriorityTask & attackTask = it->second; if ( attackTask.type != PriorityTaskType::ATTACK ) { return; } for ( const int32_t secondaryTaskId : attackTask.secondaryTaskTileId ) { // If this assertion blows then you are attacking and defending the same tile! assert( secondaryTaskId != tileIndex ); auto defenseTask = _priorityTargets.find( secondaryTaskId ); if ( defenseTask == _priorityTargets.end() ) { continue; } if ( defenseTask->second.type != PriorityTaskType::DEFEND ) { continue; } // check if a secondary task still present std::set & defenseSecondaries = defenseTask->second.secondaryTaskTileId; defenseSecondaries.erase( tileIndex ); if ( defenseSecondaries.empty() ) { // if no one else was threatening this then we no longer have to defend _priorityTargets.erase( secondaryTaskId ); } } _priorityTargets.erase( tileIndex ); } void Normal::updatePriorityAttackTarget( const Kingdom & kingdom, const Maps::Tiles & tile ) { const int32_t tileIndex = tile.GetIndex(); _enemyArmies.erase( tileIndex ); const auto enemyArmy = getEnemyArmyOnTile( kingdom.GetColor(), tile ); if ( enemyArmy ) { _enemyArmies[enemyArmy->index] = *enemyArmy; updatePriorityForEnemyArmy( kingdom, *enemyArmy ); } } void Normal::KingdomTurn( Kingdom & kingdom ) { #if defined( WITH_DEBUG ) class AIAutoControlModeCommitter { public: explicit AIAutoControlModeCommitter( const Kingdom & kingdom ) : _kingdomColor( kingdom.GetColor() ) {} AIAutoControlModeCommitter( const AIAutoControlModeCommitter & ) = delete; ~AIAutoControlModeCommitter() { Player * player = Players::Get( _kingdomColor ); assert( player != nullptr ); if ( player->isAIAutoControlMode() ) { player->commitAIAutoControlMode(); } } AIAutoControlModeCommitter & operator=( const AIAutoControlModeCommitter & ) = delete; private: const int _kingdomColor; }; const AIAutoControlModeCommitter aiAutoControlModeCommitter( kingdom ); #endif const int myColor = kingdom.GetColor(); if ( kingdom.isLoss() || myColor == Color::NONE ) { kingdom.LossPostActions(); return; } // Reset the turn progress indicator Interface::StatusWindow & status = Interface::AdventureMap::Get().getStatusWindow(); status.DrawAITurnProgress( 0 ); AudioManager::PlayMusicAsync( MUS::COMPUTER_TURN, Music::PlaybackMode::RESUME_AND_PLAY_INFINITE ); VecHeroes & heroes = kingdom.GetHeroes(); const VecCastles & castles = kingdom.GetCastles(); // Clear the cache of neutral monsters as their strength might have changed. _neutralMonsterStrengthCache.clear(); DEBUG_LOG( DBG_AI, DBG_INFO, Color::String( myColor ) << " starts the turn: " << castles.size() << " castles, " << heroes.size() << " heroes" ) DEBUG_LOG( DBG_AI, DBG_INFO, "Funds: " << kingdom.GetFunds().String() ) // Step 1. Scan visible map (based on game difficulty), add goals and threats bool underViewSpell = false; int32_t availableHeroCount = 0; Heroes * bestHeroToViewAll = nullptr; for ( Heroes * hero : heroes ) { hero->ResetModes( Heroes::SLEEPER ); hero->setDimensionDoorUsage( 0 ); if ( !hero->Modes( Heroes::PATROL ) ) { ++availableHeroCount; } if ( hero->HaveSpell( Spell::VIEWALL ) && ( !bestHeroToViewAll || hero->HasSecondarySkill( Skill::Secondary::MYSTICISM ) ) ) { bestHeroToViewAll = hero; } } if ( bestHeroToViewAll && HeroesCastAdventureSpell( *bestHeroToViewAll, Spell::VIEWALL ) ) { underViewSpell = true; } _priorityTargets.clear(); _enemyArmies.clear(); _mapActionObjects.clear(); _regions.clear(); _regions.resize( world.getRegionCount() ); const int mapSize = world.w() * world.h(); for ( int idx = 0; idx < mapSize; ++idx ) { const Maps::Tiles & tile = world.GetTiles( idx ); MP2::MapObjectType objectType = tile.GetObject(); const uint32_t regionID = tile.GetRegion(); if ( regionID >= _regions.size() ) { assert( 0 ); continue; } RegionStats & stats = _regions[regionID]; if ( !underViewSpell && tile.isFog( myColor ) ) { continue; } if ( !MP2::isActionObject( objectType ) ) { continue; } _mapActionObjects.emplace_back( idx, objectType ); if ( objectType == MP2::OBJ_HERO ) { const Heroes * hero = tile.getHero(); assert( hero != nullptr ); if ( hero->GetColor() == myColor && !hero->Modes( Heroes::PATROL ) ) { ++stats.friendlyHeroes; const int wisdomLevel = hero->GetLevelSkill( Skill::Secondary::WISDOM ); if ( wisdomLevel + 2 > stats.spellLevel ) { stats.spellLevel = wisdomLevel + 2; } } // This hero can be in a castle objectType = tile.GetObject( false ); } if ( objectType == MP2::OBJ_CASTLE ) { const Castle * castle = world.getCastleEntrance( Maps::GetPoint( idx ) ); assert( castle != nullptr ); if ( castle->isFriends( myColor ) ) { ++stats.friendlyCastles; } else if ( castle->GetColor() != Color::NONE ) { ++stats.enemyCastles; } } const auto enemyArmy = getEnemyArmyOnTile( myColor, tile ); if ( enemyArmy ) { _enemyArmies[enemyArmy->index] = *enemyArmy; if ( stats.highestThreat < enemyArmy->strength ) { stats.highestThreat = enemyArmy->strength; } } } evaluateRegionSafety(); updateKingdomBudget( kingdom ); DEBUG_LOG( DBG_AI, DBG_TRACE, Color::String( myColor ) << " found " << _mapActionObjects.size() << " valid objects" ) uint32_t progressStatus = 1; status.DrawAITurnProgress( progressStatus ); std::vector sortedCastleList; std::set castlesInDanger; while ( true ) { // Step 2. Do some hero stuff. // If a hero is standing in a castle most likely he has nothing to do so let's try to give him more army. for ( Heroes * hero : heroes ) { HeroesActionComplete( *hero, hero->GetIndex(), MP2::OBJ_NONE ); } // Step 3. Reassign heroes roles setHeroRoles( heroes, Game::getDifficulty() ); castlesInDanger = findCastlesInDanger( kingdom ); for ( Heroes * hero : heroes ) { if ( castlesInDanger.find( hero->GetIndex() ) != castlesInDanger.end() ) { // If a hero is in a castle and this castle is in danger then the hero is most likely not able to defeat // a threatening enemy in an open field. Therefore let's make him stay in the castle. // TODO: allow the hero to still do some actions but always return to the castle at the end of the turn. HeroesActionComplete( *hero, hero->GetIndex(), hero->getObjectTypeUnderHero() ); } } sortedCastleList = getSortedCastleList( castles, castlesInDanger ); const uint32_t startProgressValue = progressStatus; const uint32_t endProgressValue = ( progressStatus == 1 ) ? 8 : std::max( progressStatus + 1U, 9U ); bool moreTaskForHeroes = HeroesTurn( heroes, startProgressValue, endProgressValue ); if ( progressStatus == 1 ) { progressStatus = 8; status.DrawAITurnProgress( progressStatus ); } // Step 4. Buy new heroes, adjust roles, sort heroes based on priority or strength if ( purchaseNewHeroes( sortedCastleList, castlesInDanger, availableHeroCount, moreTaskForHeroes ) ) { assert( !heroes.empty() && heroes.back() != nullptr ); updateMapActionObjectCache( heroes.back()->GetIndex() ); ++availableHeroCount; continue; } if ( !moreTaskForHeroes && world.LastDay() ) { // Heroes have nothing to do. In this case it is wise to move heroes to castles especially if it is the last day of a week. // So for the next day a hero will have a maximum amount of spell points as well as new troops. for ( const Castle * castle : castles ) { assert( castle != nullptr ); if ( castle->GetHero() == nullptr ) { const auto [dummy, inserted] = _priorityTargets.try_emplace( castle->GetIndex(), PriorityTaskType::REINFORCE ); if ( inserted ) { DEBUG_LOG( DBG_AI, DBG_INFO, castle->GetName() << " is designated as a priority target to reinforce nearby heroes" ) moreTaskForHeroes = true; } } } if ( moreTaskForHeroes ) { continue; } } break; } status.DrawAITurnProgress( 9 ); // Sync the list of castles (if new ones were captured during the turn) if ( castles.size() != sortedCastleList.size() ) { evaluateRegionSafety(); sortedCastleList = getSortedCastleList( castles, castlesInDanger ); } // Step 5. Castle development according to kingdom budget for ( const AICastle & entry : sortedCastleList ) { if ( entry.castle != nullptr ) { CastleTurn( *entry.castle, entry.underThreat ); } } // For heroes in castles or towns, transfer their slowest troops to the garrison at the end of the turn to try to get a movement bonus on the next turn for ( Heroes * hero : heroes ) { Castle * castle = hero->inCastleMutable(); if ( castle == nullptr ) { continue; } transferSlowestTroopsToGarrison( hero, castle ); } status.DrawAITurnProgress( 10 ); } bool Normal::purchaseNewHeroes( const std::vector & sortedCastleList, const std::set & castlesInDanger, const int32_t availableHeroCount, const bool moreTasksForHeroes ) { const bool slowEarlyGame = world.CountDay() < 5 && sortedCastleList.size() == 1; int32_t heroLimit = world.w() / Maps::SMALL + 1; if ( _personality == EXPLORER ) ++heroLimit; if ( slowEarlyGame ) heroLimit = 2; if ( availableHeroCount >= heroLimit ) { return false; } Castle * recruitmentCastle = nullptr; double bestArmyAvailable = -1.0; // search for best castle to recruit hero from for ( const AICastle & entry : sortedCastleList ) { Castle * castle = entry.castle; if ( castle && castle->isCastle() ) { const Heroes * hero = castle->GetHero(); const int mapIndex = castle->GetIndex(); // Make sure there is no hero in castle already and we're not under threat while having other heroes. if ( hero != nullptr || ( availableHeroCount > 0 && castlesInDanger.find( mapIndex ) != castlesInDanger.end() ) ) continue; const uint32_t regionID = world.GetTiles( mapIndex ).GetRegion(); const int heroesInRegion = _regions[regionID].friendlyHeroes; if ( heroesInRegion > 1 ) continue; const size_t neighboursCount = world.getRegion( regionID ).getNeighboursCount(); // don't buy another hero if there's nothing to do or castle is on an island if ( heroesInRegion > 0 && ( !moreTasksForHeroes || ( sortedCastleList.size() > 1 && neighboursCount == 0 ) ) ) { continue; } const double availableArmy = castle->getArmyRecruitmentValue(); if ( recruitmentCastle == nullptr || availableArmy > bestArmyAvailable ) { recruitmentCastle = castle; bestArmyAvailable = availableArmy; } } } // target found, buy hero return recruitmentCastle && recruitHero( *recruitmentCastle, !slowEarlyGame, false ); } void Normal::tradingPostVisitEvent( Kingdom & /*kingdom*/ ) { // Do nothing. } } fheroes2-1.0.12+dfsg/src/fheroes2/ai/normal/ai_normal_spell.cpp000066400000000000000000000613301456075706000242750ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include "ai.h" #include "ai_normal.h" #include "army_troop.h" #include "artifact.h" #include "artifact_info.h" #include "battle.h" #include "battle_arena.h" #include "battle_army.h" #include "battle_board.h" #include "battle_cell.h" #include "battle_troop.h" #include "heroes_base.h" #include "logging.h" #include "monster.h" #include "speed.h" #include "spell.h" #include "spell_info.h" #include "spell_storage.h" using namespace Battle; namespace { const double antimagicLowLimit = 200.0; } namespace AI { double ReduceEffectivenessByDistance( const Unit & unit ) { // Reduce spell effectiveness if unit already crossed the battlefield const uint32_t result = Board::GetDistanceFromBoardEdgeAlongXAxis( unit.GetHeadIndex(), unit.isReflect() ); assert( result > 0 ); return result; } SpellSelection BattlePlanner::selectBestSpell( Arena & arena, const Battle::Unit & currentUnit, bool retreating ) const { // Cast best spell with highest heuristic on target pointer saved SpellSelection bestSpell; // Commander must be set before calling this function! Check both debug/release version assert( _commander != nullptr ); if ( _commander == nullptr ) { return bestSpell; } const SpellStorage allSpells = _commander->getAllSpells(); const Units friendly( arena.getForce( _myColor ).getUnits(), Units::REMOVE_INVALID_UNITS ); const Units enemies( arena.getEnemyForce( _myColor ).getUnits(), Units::REMOVE_INVALID_UNITS ); const Units trueFriendly( arena.getForce( _myColor ).getUnits(), Units::REMOVE_INVALID_UNITS_AND_UNITS_THAT_CHANGED_SIDES ); const Units trueEnemies( arena.getEnemyForce( _myColor ).getUnits(), Units::REMOVE_INVALID_UNITS_AND_UNITS_THAT_CHANGED_SIDES ); // Hero should conserve spellpoints if already spent more than half or his army is stronger // Threshold is 0.04 when armies are equal (= 20% of single unit) double spellValueThreshold = _myArmyStrength * _myArmyStrength / _enemyArmyStrength * 0.04; if ( _enemyShootersStrength / _enemyArmyStrength > 0.5 ) { spellValueThreshold *= 0.5; } if ( _commander->GetSpellPoints() * 2 < _commander->GetMaxSpellPoints() ) { spellValueThreshold *= 2; } const auto checkSelectBestSpell = [this, retreating, spellValueThreshold, &bestSpell]( const Spell & spell, const SpellcastOutcome & outcome ) { // Diminish spell effectiveness based on spell point cost // 1. Divide cost by 3 to make level 1 spells a baseline (1:1) // 2. Use square root to make sure relationship isn't linear for high-level spells const double spellPointValue = retreating ? outcome.value : outcome.value / sqrt( spell.spellPoints( _commander ) / 3.0 ); const bool ignoreThreshold = retreating || spell.isResurrect(); DEBUG_LOG( DBG_BATTLE, DBG_TRACE, spell.GetName() << " value is " << spellPointValue << ", best target is " << outcome.cell ) if ( spellPointValue > bestSpell.value && ( ignoreThreshold || spellPointValue > spellValueThreshold ) ) { bestSpell.spellID = spell.GetID(); bestSpell.cell = outcome.cell; bestSpell.value = spellPointValue; } }; for ( const Spell & spell : allSpells ) { if ( !spell.isCombat() || arena.isDisableCastSpell( spell ) || !_commander->CanCastSpell( spell ) || ( retreating && !spell.isDamage() ) ) { continue; } if ( spell.isDamage() ) { checkSelectBestSpell( spell, spellDamageValue( spell, arena, currentUnit, friendly, enemies, retreating ) ); } else if ( spell.isEffectDispel() ) { checkSelectBestSpell( spell, spellDispelValue( spell, friendly, enemies ) ); } else if ( spell.isSummon() ) { checkSelectBestSpell( spell, spellSummonValue( spell, arena, _commander->GetColor() ) ); } else if ( spell.isResurrect() ) { checkSelectBestSpell( spell, spellResurrectValue( spell, arena ) ); } else if ( spell.isApplyToFriends() ) { checkSelectBestSpell( spell, spellEffectValue( spell, trueFriendly ) ); } else if ( spell.isApplyToEnemies() ) { checkSelectBestSpell( spell, spellEffectValue( spell, trueEnemies ) ); } } DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "Spell threshold is " << spellValueThreshold << ", unit ratio is " << ( spellValueThreshold * 5 / _myArmyStrength ) ) if ( bestSpell.spellID != -1 ) { DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "Best spell is " << Spell( bestSpell.spellID ).GetName() << ", value is " << bestSpell.value ) } return bestSpell; } SpellcastOutcome BattlePlanner::spellDamageValue( const Spell & spell, Arena & arena, const Battle::Unit & currentUnit, const Units & friendly, const Units & enemies, bool retreating ) const { if ( !spell.isDamage() ) { return {}; } const uint32_t spellDamage = fheroes2::getSpellDamage( spell, _commander->GetPower(), _commander ); const auto damageHeuristic = [this, spellDamage, &spell, retreating]( const Unit * unit, const double armyStrength, const double armySpeed ) { const uint32_t damage = spellDamage * ( 100 - unit->GetMagicResist( spell, _commander ) ) / 100; // If the unit is immune to this spell, then no one will be killed, no strength will be lost and the unit will not be woken up if it is disabled if ( damage == 0 ) { return 0.0; } // If we retreat, we are not interested in partial damage, but only in the number of units actually killed if ( retreating ) { return unit->GetMonsterStrength() * unit->HowManyWillBeKilled( damage ); } // If the unit will be completely destroyed, then use its full strength plus a bonus for destroying the stack const uint32_t hitpoints = unit->Modes( CAP_MIRRORIMAGE ) ? 1 : unit->GetHitPoints(); if ( damage >= hitpoints ) { const double bonus = ( unit->GetSpeed() > armySpeed ) ? 0.07 : 0.035; return unit->GetStrength() + armyStrength * bonus; } // Otherwise use the amount of strength lost (% of the total unit's strength) double unitPercentageLost = std::min( static_cast( damage ) / hitpoints, 1.0 ); // Penalty for waking up disabled unit (if you kill only 30%, the remaining 70% is your penalty) if ( unit->isImmovable() ) { unitPercentageLost += unitPercentageLost - 1.0; } return unitPercentageLost * unit->GetStrength(); }; SpellcastOutcome bestOutcome; if ( spell.isSingleTarget() ) { for ( const Unit * enemy : enemies ) { bestOutcome.updateOutcome( damageHeuristic( enemy, _enemyArmyStrength, _enemyAverageSpeed ), enemy->GetHeadIndex() ); } } else if ( spell.isApplyWithoutFocusObject() ) { double spellHeuristic = 0; for ( const Unit * enemy : enemies ) { spellHeuristic += damageHeuristic( enemy, _enemyArmyStrength, _enemyAverageSpeed ); } for ( const Unit * unit : friendly ) { const double valueLost = damageHeuristic( unit, _myArmyStrength, _myArmyAverageSpeed ); // check if we're retreating and will lose current unit if ( retreating && unit->isUID( currentUnit.GetUID() ) && std::fabs( valueLost - unit->GetStrength() ) < 0.001 ) { // avoid this spell and return early return bestOutcome; } spellHeuristic -= valueLost; } bestOutcome.updateOutcome( spellHeuristic, -1 ); } else { // Area of effect spells like Fireball const auto areaOfEffectCheck = [this, &damageHeuristic, &bestOutcome, ¤tUnit, retreating]( const TargetsInfo & targets, const int32_t index, int myColor ) { double spellHeuristic = 0; for ( const TargetInfo & target : targets ) { if ( target.defender->GetCurrentColor() == myColor ) { const double valueLost = damageHeuristic( target.defender, _myArmyStrength, _myArmyAverageSpeed ); // check if we're retreating and will lose current unit if ( retreating && target.defender->isUID( currentUnit.GetUID() ) && std::fabs( valueLost - target.defender->GetStrength() ) < 0.001 ) { // avoid this spell and return without updating the outcome return; } spellHeuristic -= valueLost; } else { spellHeuristic += damageHeuristic( target.defender, _enemyArmyStrength, _enemyAverageSpeed ); } } bestOutcome.updateOutcome( spellHeuristic, index ); }; if ( spell.GetID() == Spell::CHAINLIGHTNING ) { for ( const Unit * enemy : enemies ) { if ( !enemy->AllowApplySpell( spell, _commander ) ) { continue; } const int32_t index = enemy->GetHeadIndex(); areaOfEffectCheck( arena.GetTargetsForSpell( _commander, spell, index ), index, _myColor ); } } else { const Board & board = *Arena::GetBoard(); for ( const Cell & cell : board ) { const int32_t index = cell.GetIndex(); areaOfEffectCheck( arena.GetTargetsForSpell( _commander, spell, index ), index, _myColor ); } } } return bestOutcome; } int32_t BattlePlanner::spellDurationMultiplier( const Battle::Unit & target ) const { const int32_t duration = _commander->GetPower() + _commander->GetBagArtifacts().getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::EVERY_COMBAT_SPELL_DURATION ); if ( duration < 2 && target.Modes( TR_MOVED ) ) { return 0; } return 1; } // Compute a heuristic for Disrupting Ray spell usage by AI double BattlePlanner::getSpellDisruptingRayRatio( const Battle::Unit & target ) const { const double targetDefense = target.GetDefense(); if ( targetDefense <= 1 ) { // target is already at minimum defense: not useful to cast Disrupting Ray return 0.0; } double ratio = 0.2; if ( targetDefense <= Spell( Spell::DISRUPTINGRAY ).ExtraValue() ) { // disrupting ray can't have full effect const double actualDefenseChange = targetDefense - 1.0; ratio *= actualDefenseChange / Spell( Spell::DISRUPTINGRAY ).ExtraValue(); } const double targetStrength = target.GetStrength(); // if current army has much lower strength than target unit, we reduce the ratio to prioritize direct damage spells if ( _myArmyStrength < targetStrength ) { ratio *= _myArmyStrength / targetStrength; } return ratio; } // Compute a heuristic for Slow spell usage by AI double BattlePlanner::getSpellSlowRatio( const Battle::Unit & target ) const { if ( target.isArchers() || _attackingCastle ) { // Slow is useless against archers or troops defending castle return 0.01; } const int currentSpeed = target.GetSpeed( false, true ); const int newSpeed = Speed::GetSlowSpeedFromSpell( currentSpeed ); const int lostSpeed = currentSpeed - newSpeed; // usually 2 double ratio = 0.1 * lostSpeed; if ( currentSpeed < _myArmyAverageSpeed ) { // Slow isn't useful if target is already slower than our army ratio /= 2; } if ( target.Modes( SP_HASTE ) ) { ratio *= 2; } else if ( !target.isFlying() ) { ratio /= ReduceEffectivenessByDistance( target ); } return ratio; } // Compute a heuristic for Haste spell usage by AI double BattlePlanner::getSpellHasteRatio( const Battle::Unit & target ) const { const int currentSpeed = target.GetSpeed( false, true ); const int newSpeed = Speed::GetHasteSpeedFromSpell( currentSpeed ); const int gainedSpeed = newSpeed - currentSpeed; // usually 2 double ratio = 0.05 * gainedSpeed; if ( currentSpeed < _enemyAverageSpeed ) { // Haste is very useful if target is slower than army ratio *= 2; } if ( target.Modes( SP_SLOW ) ) { ratio *= 2; } // Reduce effectiveness if we don't have to move else if ( target.isArchers() || _defensiveTactics ) { ratio /= 2; } return ratio; } double BattlePlanner::spellEffectValue( const Spell & spell, const Battle::Unit & target, bool targetIsLast, bool forDispel ) const { const int spellID = spell.GetID(); // Make sure this spell can be applied to the current unit (skip check for dispel estimation) if ( !forDispel && ( ( target.isImmovable() && spellID != Spell::ANTIMAGIC ) || target.isUnderSpellEffect( spell ) || !target.AllowApplySpell( spell, _commander ) ) ) { return 0.0; } double ratio = 0.0; switch ( spellID ) { case Spell::SLOW: case Spell::MASSSLOW: ratio = getSpellSlowRatio( target ); break; case Spell::BLIND: { if ( targetIsLast ) return 0.0; ratio = 0.8; break; } case Spell::CURSE: case Spell::MASSCURSE: ratio = 0.15; break; case Spell::BERSERKER: case Spell::PARALYZE: { if ( targetIsLast ) return 0.0; ratio = 0.85; break; } case Spell::HYPNOTIZE: { ratio = 1.5; break; } case Spell::DISRUPTINGRAY: ratio = getSpellDisruptingRayRatio( target ); break; case Spell::HASTE: case Spell::MASSHASTE: ratio = getSpellHasteRatio( target ); break; case Spell::BLOODLUST: ratio = 0.1; break; case Spell::BLESS: case Spell::MASSBLESS: { if ( target.GetDamageMax() == target.GetDamageMin() ) return 0.0; ratio = 0.15; break; } case Spell::STONESKIN: ratio = 0.1; break; case Spell::STEELSKIN: ratio = 0.2; break; // Following spell usefulness is conditional; ratio will be determined later case Spell::DRAGONSLAYER: case Spell::ANTIMAGIC: case Spell::MIRRORIMAGE: case Spell::SHIELD: case Spell::MASSSHIELD: ratio = 0.0; break; default: return 0.0; } if ( target.Modes( SP_BLESS ) && ( spellID == Spell::CURSE || spellID == Spell::MASSCURSE ) ) { ratio *= 2; } else if ( target.Modes( SP_CURSE ) && ( spellID == Spell::BLESS || spellID == Spell::MASSBLESS ) ) { ratio *= 2; } else if ( spellID == Spell::ANTIMAGIC && !target.Modes( IS_GOOD_MAGIC ) && _enemySpellStrength > antimagicLowLimit ) { double ratioLimit = 0.9; const SpellStorage spellList = _commander->getAllSpells(); for ( const Spell & otherSpell : spellList ) { if ( otherSpell.isResurrect() && _commander->HaveSpellPoints( otherSpell ) && target.AllowApplySpell( otherSpell, _commander ) ) { // Can resurrect unit in the future, limit the ratio ratioLimit = 0.35; break; } } // Convert 0...5000 range into 0.0 to 0.9 ratio and clamp it ratio = std::min( _enemySpellStrength / antimagicLowLimit * 0.036, ratioLimit ); // Hero is stronger than its army, possible hit and run tactic if ( _enemySpellStrength > _enemyArmyStrength ) { ratio *= 1.5; } if ( target.Modes( IS_BAD_MAGIC ) ) { ratio *= 2; } } else if ( spellID == Spell::MIRRORIMAGE ) { if ( target.isArchers() ) { ratio = 1.0; } else { ratio = target.isFlying() ? 0.55 : 0.33; } // Slow unit might be destroyed before taking its turn if ( target.GetSpeed() < _enemyAverageSpeed ) { ratio /= 5; } } else if ( spellID == Spell::BERSERKER && !target.isArchers() ) { ratio /= ReduceEffectivenessByDistance( target ); } else if ( spellID == Spell::SHIELD || spellID == Spell::MASSSHIELD ) { ratio = _enemyRangedUnitsOnly / _enemyArmyStrength * 0.3; if ( target.isArchers() ) { ratio *= 1.25; } } else if ( spellID == Spell::DRAGONSLAYER ) { // TODO: add logic to check if the enemy army contains a dragon. } return target.GetStrength() * ratio * spellDurationMultiplier( target ); } SpellcastOutcome BattlePlanner::spellEffectValue( const Spell & spell, const Units & targets ) const { const bool isSingleTargetLeft = targets.size() == 1; const bool isMassSpell = spell.isMassActions(); SpellcastOutcome bestOutcome; for ( const Unit * unit : targets ) { bestOutcome.updateOutcome( spellEffectValue( spell, *unit, isSingleTargetLeft, false ), unit->GetHeadIndex(), isMassSpell ); } return bestOutcome; } SpellcastOutcome BattlePlanner::spellDispelValue( const Spell & spell, const Battle::Units & friendly, const Units & enemies ) const { const int spellID = spell.GetID(); const bool isMassSpell = spell.isMassActions(); const bool isDispel = spellID == Spell::DISPEL || spellID == Spell::MASSDISPEL; SpellcastOutcome bestOutcome; for ( const Unit * unit : friendly ) { if ( !unit->Modes( IS_MAGIC ) ) continue; double unitValue = 0; const std::vector & spellList = unit->getCurrentSpellEffects(); for ( const Spell & spellOnFriend : spellList ) { const double effectValue = spellEffectValue( spellOnFriend, *unit, false, true ); if ( spellOnFriend.isApplyToEnemies() ) { unitValue += effectValue; } else if ( isDispel && spellOnFriend.isApplyToFriends() ) { unitValue -= effectValue; } } bestOutcome.updateOutcome( unitValue, unit->GetHeadIndex(), isMassSpell ); } if ( isDispel ) { const bool enemyLastUnit = enemies.size() == 1; for ( const Unit * unit : enemies ) { if ( !unit->Modes( IS_MAGIC ) ) continue; double unitValue = 0; const std::vector & spellList = unit->getCurrentSpellEffects(); for ( const Spell & spellOnEnemy : spellList ) { const double effectValue = spellEffectValue( spellOnEnemy, *unit, enemyLastUnit, true ); unitValue += spellOnEnemy.isApplyToFriends() ? effectValue : -effectValue; } bestOutcome.updateOutcome( unitValue, unit->GetHeadIndex(), isMassSpell ); } } return bestOutcome; } SpellcastOutcome BattlePlanner::spellResurrectValue( const Spell & spell, const Battle::Arena & arena ) const { const uint32_t hpRestored = fheroes2::getResurrectPoints( spell, _commander->GetPower(), _commander ); SpellcastOutcome bestOutcome; const auto updateBestOutcome = [this, &spell, &bestOutcome, hpRestored]( const Unit * unit ) { uint32_t missingHP = unit->GetMissingHitPoints(); missingHP = ( missingHP < hpRestored ) ? missingHP : hpRestored; double spellValue = missingHP * unit->GetMonsterStrength() / unit->Monster::GetHitPoints(); // if we are winning battle; permanent resurrect bonus if ( _myArmyStrength > _enemyArmyStrength && spell.GetID() != Spell::RESURRECT ) { spellValue *= 2; } bestOutcome.updateOutcome( spellValue, unit->GetHeadIndex() ); }; // First consider the still alive stacks for ( const Unit * unit : arena.getForce( _myColor ) ) { assert( unit != nullptr ); if ( !unit->isValid() ) { continue; } if ( !unit->AllowApplySpell( spell, _commander ) ) { continue; } updateBestOutcome( unit ); } // Then consider the stacks from the graveyard for ( const int32_t idx : arena.GraveyardOccupiedCells() ) { if ( !arena.GraveyardAllowResurrect( idx, spell ) ) { continue; } const Unit * unit = arena.GraveyardLastTroop( idx ); assert( unit != nullptr && !unit->isValid() ); updateBestOutcome( unit ); } return bestOutcome; } SpellcastOutcome BattlePlanner::spellSummonValue( const Spell & spell, const Battle::Arena & arena, const int heroColor ) const { if ( !spell.isSummon() ) { return {}; } if ( arena.GetFreePositionNearHero( heroColor ) < 0 ) { return {}; } const Troop summon( Monster( spell ), fheroes2::getSummonMonsterCount( spell, _commander->GetPower(), _commander ) ); SpellcastOutcome bestOutcome; bestOutcome.value = summon.GetStrengthWithBonus( _commander->GetAttack(), _commander->GetDefense() ); // Spell is less effective if we already winning this battle if ( _myArmyStrength > _enemyArmyStrength * 2 ) { bestOutcome.value /= 2; } return bestOutcome; } double BattlePlanner::commanderMaximumSpellDamageValue( const HeroBase & commander ) { double bestValue = 0; for ( const Spell & spell : commander.getAllSpells() ) { if ( !spell.isCombat() || !spell.isDamage() ) { continue; } if ( commander.GetSpellPoints() < spell.spellPoints() ) { continue; } bestValue = std::max( bestValue, static_cast( fheroes2::getSpellDamage( spell, commander.GetPower(), &commander ) ) ); } return bestValue; } } fheroes2-1.0.12+dfsg/src/fheroes2/army/000077500000000000000000000000001456075706000175155ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/fheroes2/army/army.cpp000066400000000000000000001651771456075706000212120ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "army.h" #include #include #include #include #include #include #include #include #include #include #include #include "army_troop.h" #include "army_ui_helper.h" #include "artifact.h" #include "artifact_info.h" #include "campaign_data.h" #include "campaign_savedata.h" #include "campaign_scenariodata.h" #include "castle.h" #include "color.h" #include "heroes.h" #include "heroes_base.h" #include "kingdom.h" #include "logging.h" #include "luck.h" #include "maps_tiles.h" #include "maps_tiles_helper.h" #include "morale.h" #include "mp2.h" #include "race.h" #include "rand.h" #include "resource.h" #include "screen.h" #include "serialize.h" #include "settings.h" #include "skill.h" #include "tools.h" #include "translations.h" #include "world.h" namespace fheroes2 { class Image; } namespace { enum class ArmySize : uint32_t { ARMY_FEW = 1, ARMY_SEVERAL = 5, ARMY_PACK = 10, ARMY_LOTS = 20, ARMY_HORDE = 50, ARMY_THRONG = 100, ARMY_SWARM = 250, ARMY_ZOUNDS = 500, ARMY_LEGION = 1000 }; ArmySize getArmySize( const uint32_t count ) { const ArmySize countAsEnum = static_cast( count ); if ( ArmySize::ARMY_LEGION <= countAsEnum ) { return ArmySize::ARMY_LEGION; } if ( ArmySize::ARMY_ZOUNDS <= countAsEnum ) { return ArmySize::ARMY_ZOUNDS; } if ( ArmySize::ARMY_SWARM <= countAsEnum ) { return ArmySize::ARMY_SWARM; } if ( ArmySize::ARMY_THRONG <= countAsEnum ) { return ArmySize::ARMY_THRONG; } if ( ArmySize::ARMY_HORDE <= countAsEnum ) { return ArmySize::ARMY_HORDE; } if ( ArmySize::ARMY_LOTS <= countAsEnum ) { return ArmySize::ARMY_LOTS; } if ( ArmySize::ARMY_PACK <= countAsEnum ) { return ArmySize::ARMY_PACK; } if ( ArmySize::ARMY_SEVERAL <= countAsEnum ) { return ArmySize::ARMY_SEVERAL; } return ArmySize::ARMY_FEW; } std::pair getNumberOfMonstersInStartingArmy( const Monster & monster ) { switch ( monster.GetMonsterLevel() ) { case 1: switch ( monster.GetID() ) { case Monster::PEASANT: return { 30, 50 }; case Monster::GOBLIN: return { 15, 25 }; case Monster::SPRITE: return { 10, 20 }; default: return { 6, 10 }; } case 2: switch ( monster.GetID() ) { case Monster::ARCHER: case Monster::ORC: return { 3, 5 }; default: return { 2, 4 }; } default: assert( 0 ); break; } return { 0, 0 }; } } std::string Army::TroopSizeString( const Troop & troop ) { std::string str; switch ( getArmySize( troop.GetCount() ) ) { case ArmySize::ARMY_FEW: str = _( "A few\n%{monster}" ); break; case ArmySize::ARMY_SEVERAL: str = _( "Several\n%{monster}" ); break; case ArmySize::ARMY_PACK: str = _( "A pack of\n%{monster}" ); break; case ArmySize::ARMY_LOTS: str = _( "Lots of\n%{monster}" ); break; case ArmySize::ARMY_HORDE: str = _( "A horde of\n%{monster}" ); break; case ArmySize::ARMY_THRONG: str = _( "A throng of\n%{monster}" ); break; case ArmySize::ARMY_SWARM: str = _( "A swarm of\n%{monster}" ); break; case ArmySize::ARMY_ZOUNDS: str = _( "Zounds...\n%{monster}" ); break; case ArmySize::ARMY_LEGION: str = _( "A legion of\n%{monster}" ); break; default: // Are you passing the correct value? assert( 0 ); break; } StringReplaceWithLowercase( str, "%{monster}", troop.GetMultiName() ); return str; } std::string Army::SizeString( uint32_t size ) { switch ( getArmySize( size ) ) { case ArmySize::ARMY_FEW: return _( "army|Few" ); case ArmySize::ARMY_SEVERAL: return _( "army|Several" ); case ArmySize::ARMY_PACK: return _( "army|Pack" ); case ArmySize::ARMY_LOTS: return _( "army|Lots" ); case ArmySize::ARMY_HORDE: return _( "army|Horde" ); case ArmySize::ARMY_THRONG: return _( "army|Throng" ); case ArmySize::ARMY_SWARM: return _( "army|Swarm" ); case ArmySize::ARMY_ZOUNDS: return _( "army|Zounds" ); case ArmySize::ARMY_LEGION: return _( "army|Legion" ); default: // Are you passing the correct value? assert( 0 ); break; } return {}; } Troops::Troops( const Troops & troops ) : std::vector() { reserve( troops.size() ); for ( const Troop * troop : troops ) { assert( troop != nullptr ); push_back( new Troop( *troop ) ); } } Troops::~Troops() { std::for_each( begin(), end(), []( Troop * troop ) { assert( troop != nullptr ); delete troop; } ); } void Troops::Assign( const Troop * it1, const Troop * it2 ) { Clean(); iterator it = begin(); while ( it != end() && it1 != it2 ) { if ( it1->isValid() ) ( *it )->Set( *it1 ); ++it; ++it1; } } void Troops::Assign( const Troops & troops ) { Clean(); iterator it1 = begin(); const_iterator it2 = troops.begin(); while ( it1 != end() && it2 != troops.end() ) { if ( ( *it2 )->isValid() ) ( *it1 )->Set( **it2 ); ++it2; ++it1; } } void Troops::Insert( const Troops & troops ) { for ( const_iterator it = troops.begin(); it != troops.end(); ++it ) push_back( new Troop( **it ) ); } void Troops::PushBack( const Monster & mons, uint32_t count ) { push_back( new Troop( mons, count ) ); } void Troops::PopBack() { if ( !empty() ) { delete back(); pop_back(); } } Troop * Troops::GetTroop( size_t pos ) { return pos < size() ? at( pos ) : nullptr; } const Troop * Troops::GetTroop( size_t pos ) const { return pos < size() ? at( pos ) : nullptr; } void Troops::UpgradeMonsters( const Monster & m ) { for ( iterator it = begin(); it != end(); ++it ) { if ( **it == m && ( *it )->isValid() ) { ( *it )->Upgrade(); } } } uint32_t Troops::GetCountMonsters( const Monster & mons ) const { const int monsterId = mons.GetID(); uint32_t result = 0; for ( const Troop * troop : *this ) { assert( troop != nullptr ); if ( troop->isValid() && troop->isMonster( monsterId ) ) { result += troop->GetCount(); } } return result; } double Troops::getReinforcementValue( const Troops & reinforcement ) const { // NB items that are added in this vector are all of Troop* type, and not ArmyTroop* type // So the GetStrength() computation will be done based on troop strength only (not based on hero bonuses) Troops combined( *this ); const double initialValue = combined.GetStrength(); combined.Insert( reinforcement.GetOptimized() ); combined.MergeSameMonsterTroops(); combined.SortStrongest(); while ( combined.Size() > Army::maximumTroopCount ) { combined.PopBack(); } return combined.GetStrength() - initialValue; } bool Troops::isValid() const { for ( const_iterator it = begin(); it != end(); ++it ) { if ( ( *it )->isValid() ) return true; } return false; } uint32_t Troops::GetOccupiedSlotCount() const { uint32_t total = 0; for ( const_iterator it = begin(); it != end(); ++it ) { if ( ( *it )->isValid() ) ++total; } return total; } bool Troops::areAllTroopsUnique() const { std::set monsterId; for ( const Troop * troop : *this ) { assert( troop != nullptr ); if ( !troop->isValid() ) { continue; } auto [it, inserted] = monsterId.emplace( troop->GetID() ); if ( !inserted ) { return false; } } return true; } bool Troops::HasMonster( const Monster & mons ) const { const int monsterID = mons.GetID(); for ( const_iterator it = begin(); it != end(); ++it ) { if ( ( *it )->isValid() && ( *it )->isMonster( monsterID ) ) { return true; } } return false; } bool Troops::AllTroopsAreUndead() const { for ( const_iterator it = begin(); it != end(); ++it ) { if ( ( *it )->isValid() && !( *it )->isUndead() ) { return false; } } return true; } bool Troops::CanJoinTroop( const Monster & mons ) const { return std::any_of( begin(), end(), [&mons]( const Troop * troop ) { return troop->isMonster( mons.GetID() ); } ) || std::any_of( begin(), end(), []( const Troop * troop ) { return !troop->isValid(); } ); } bool Troops::JoinTroop( const Monster & mons, const uint32_t count, const bool emptySlotFirst ) { if ( !mons.isValid() || count == 0 ) { return false; } const auto findBestMatch = [this]( const auto higherPriorityPredicate, const auto lowerPriorityPredicate ) { const auto iter = std::find_if( begin(), end(), higherPriorityPredicate ); if ( iter != end() ) { return iter; } return std::find_if( begin(), end(), lowerPriorityPredicate ); }; const auto isSlotEmpty = []( const Troop * troop ) { assert( troop != nullptr ); return troop->isEmpty(); }; const auto isSameMonster = [&mons]( const Troop * troop ) { assert( troop != nullptr ); return troop->isValid() && troop->isMonster( mons.GetID() ); }; const auto iter = emptySlotFirst ? findBestMatch( isSlotEmpty, isSameMonster ) : findBestMatch( isSameMonster, isSlotEmpty ); if ( iter == end() ) { return false; } if ( ( *iter )->isValid() ) { ( *iter )->SetCount( ( *iter )->GetCount() + count ); } else { ( *iter )->Set( mons, count ); } return true; } bool Troops::JoinTroop( const Troop & troop ) { if ( !troop.isValid() ) { return false; } return JoinTroop( troop.GetMonster(), troop.GetCount(), false ); } bool Troops::AllTroopsAreTheSame() const { int firstMonsterId = Monster::UNKNOWN; for ( const Troop * troop : *this ) { if ( troop->isValid() ) { if ( firstMonsterId == Monster::UNKNOWN ) { firstMonsterId = troop->GetID(); } else if ( troop->GetID() != firstMonsterId ) { return false; } } } return true; } double Troops::GetStrength() const { double strength = 0; for ( const Troop * troop : *this ) { if ( troop && troop->isValid() ) strength += troop->GetStrength(); } return strength; } uint32_t Troops::getTotalHP() const { uint32_t hp = 0; for ( const Troop * troop : *this ) { if ( troop && troop->isValid() ) hp += troop->GetCount() * troop->GetHitPoints(); } return hp; } void Troops::Clean() { std::for_each( begin(), end(), []( Troop * troop ) { troop->Reset(); } ); } void Troops::UpgradeTroops( const Castle & castle ) const { for ( Troop * troop : *this ) { assert( troop != nullptr ); if ( !troop->isValid() ) { continue; } if ( !troop->isAllowUpgrade() ) { continue; } Kingdom & kingdom = castle.GetKingdom(); if ( castle.GetRace() != troop->GetRace() ) { continue; } if ( !castle.isBuild( troop->GetUpgrade().GetDwelling() ) ) { continue; } const Funds payment = troop->GetTotalUpgradeCost(); if ( kingdom.AllowPayment( payment ) ) { kingdom.OddFundsResource( payment ); troop->Upgrade(); } } } Troop * Troops::GetFirstValid() { iterator it = std::find_if( begin(), end(), []( const Troop * troop ) { return troop->isValid(); } ); return it == end() ? nullptr : *it; } Troop * Troops::getBestMatchToCondition( const std::function & condition ) const { const_iterator bestMatch = std::find_if( begin(), end(), []( const Troop * troop ) { assert( troop != nullptr ); return troop->isValid(); } ); if ( bestMatch == end() ) { return nullptr; } const_iterator iter = bestMatch + 1; while ( iter != end() ) { assert( *iter != nullptr ); if ( ( *iter )->isValid() && condition( *iter, *bestMatch ) ) { bestMatch = iter; } ++iter; } return *bestMatch; } Troop * Troops::GetWeakestTroop() const { return getBestMatchToCondition( Army::WeakestTroop ); } Troop * Troops::GetSlowestTroop() const { return getBestMatchToCondition( Army::SlowestTroop ); } void Troops::MergeSameMonsterTroops() { for ( size_t slot = 0; slot < size(); ++slot ) { Troop * troop = at( slot ); assert( troop != nullptr ); if ( !troop->isValid() ) { continue; } const int monsterId = troop->GetID(); for ( size_t otherSlot = slot + 1; otherSlot < size(); ++otherSlot ) { Troop * otherTroop = at( otherSlot ); assert( otherTroop != nullptr ); if ( otherTroop->isValid() && otherTroop->isMonster( monsterId ) ) { troop->SetCount( troop->GetCount() + otherTroop->GetCount() ); otherTroop->Reset(); } } } } bool Troops::MergeSameMonsterOnce() { for ( size_t slot = 0; slot < size(); ++slot ) { Troop * troop = at( slot ); assert( troop != nullptr ); if ( !troop->isValid() ) { continue; } const int monsterId = troop->GetID(); for ( size_t otherSlot = slot + 1; otherSlot < size(); ++otherSlot ) { Troop * otherTroop = at( otherSlot ); assert( otherTroop != nullptr ); if ( otherTroop->isValid() && otherTroop->isMonster( monsterId ) ) { troop->SetCount( troop->GetCount() + otherTroop->GetCount() ); otherTroop->Reset(); return true; } } } return false; } Troops Troops::GetOptimized() const { Troops result; result.reserve( size() ); for ( const Troop * troop : *this ) { assert( troop != nullptr ); if ( !troop->isValid() ) { continue; } const int monsterId = troop->GetID(); const const_iterator iter = std::find_if( result.begin(), result.end(), [monsterId]( const Troop * resultTroop ) { return resultTroop->isMonster( monsterId ); } ); if ( iter == result.end() ) { result.push_back( new Troop( *troop ) ); } else { Troop * resultTroop = *iter; assert( resultTroop != nullptr ); resultTroop->SetCount( resultTroop->GetCount() + troop->GetCount() ); } } return result; } void Troops::SortStrongest() { std::sort( begin(), end(), Army::StrongestTroop ); } void Troops::JoinStrongest( Troops & giverArmy, const bool keepAtLeastOneSlotForGiver ) { if ( this == &giverArmy ) return; // validate the size (can be different from maximumTroopCount) if ( giverArmy.size() < size() ) giverArmy.resize( size() ); // first try to keep units in the same slots for ( size_t slot = 0; slot < size(); ++slot ) { Troop * leftTroop = at( slot ); Troop * rightTroop = giverArmy[slot]; if ( rightTroop && rightTroop->isValid() ) { assert( leftTroop != nullptr ); if ( !leftTroop->isValid() ) { // if slot is empty, simply move the unit leftTroop->Set( *rightTroop ); rightTroop->Reset(); } else if ( leftTroop->GetID() == rightTroop->GetID() ) { // check if we can merge them leftTroop->SetCount( leftTroop->GetCount() + rightTroop->GetCount() ); rightTroop->Reset(); } } } // there's still unmerged units left and there's empty room for them for ( size_t slot = 0; slot < giverArmy.size(); ++slot ) { Troop * rightTroop = giverArmy[slot]; if ( rightTroop && JoinTroop( rightTroop->GetMonster(), rightTroop->GetCount(), false ) ) { rightTroop->Reset(); } } // if there's more units than slots, start optimizing if ( giverArmy.GetOccupiedSlotCount() > 0 ) { Troops rightPriority = giverArmy.GetOptimized(); giverArmy.Clean(); // strongest at the end std::sort( rightPriority.begin(), rightPriority.end(), Army::WeakestTroop ); // 1. Merge any remaining stacks to free some space MergeSameMonsterTroops(); // 2. Fill empty slots with best troops (if there are any) size_t count = GetOccupiedSlotCount(); while ( count < Army::maximumTroopCount && !rightPriority.empty() ) { if ( !JoinTroop( *rightPriority.back() ) ) { // Something is wrong with calculation of free monster slots. Check the logic! assert( 0 ); } rightPriority.PopBack(); ++count; } // 3. Swap weakest and strongest unit until there's no left while ( !rightPriority.empty() ) { Troop * weakest = GetWeakestTroop(); if ( !weakest || Army::StrongestTroop( weakest, rightPriority.back() ) ) { // we're done processing if second army units are weaker break; } Army::SwapTroops( *weakest, *rightPriority.back() ); std::sort( rightPriority.begin(), rightPriority.end(), Army::WeakestTroop ); } // 4. The rest goes back to second army while ( !rightPriority.empty() ) { if ( !giverArmy.JoinTroop( *rightPriority.back() ) ) { // We somehow cannot give the same army back. assert( 0 ); } rightPriority.PopBack(); } } if ( !keepAtLeastOneSlotForGiver || giverArmy.isValid() ) { // Either the giver army does not need an extra army or it already has some. return; } Troop * weakest = GetWeakestTroop(); if ( weakest == nullptr || !weakest->isValid() ) { // How is possible that after we have merged 2 armies no monsters are present? assert( 0 ); return; } // First check if the weakest troop is actually worth to keep. const double weakestStrength = weakest->GetStrength(); const double totalArmyStrength = Troops::GetStrength(); assert( totalArmyStrength >= weakestStrength ); // The weakest army should not be more than 5% from the overall army strength. const double strengthLimit = totalArmyStrength / 20; if ( weakestStrength < strengthLimit ) { // The weakest troop is less than limit. Just kick this weakling out. if ( !giverArmy.JoinTroop( *weakest, weakest->GetCount(), false ) ) { // The army is empty and we cannot add more troops? assert( 0 ); } weakest->Reset(); } else { uint32_t acceptableCount = static_cast( strengthLimit / weakestStrength * weakest->GetCount() ); assert( acceptableCount <= weakest->GetCount() ); if ( acceptableCount > weakest->GetCount() / 2 ) { // No more than half. acceptableCount = weakest->GetCount() / 2; } if ( acceptableCount < 1 ) { acceptableCount = 1; } if ( !giverArmy.JoinTroop( *weakest, acceptableCount, false ) ) { // The army is empty and we cannot add more troops? assert( 0 ); } weakest->SetCount( weakest->GetCount() - acceptableCount ); } } void Troops::SplitTroopIntoFreeSlots( const Troop & troop, const Troop & selectedSlot, const uint32_t slots ) { const iterator selectedSlotIterator = std::find( begin(), end(), &selectedSlot ); // this means the selected slot is actually not part of the army, which is not the intended logic if ( selectedSlotIterator == end() ) { return; } addNewTroopsToFreeSlots( troop, slots ); } void Troops::addNewTroopsToFreeSlots( const Troop & troop, uint32_t maxSlots ) { if ( maxSlots < 1 || GetOccupiedSlotCount() >= Size() ) { assert( 0 ); return; } if ( maxSlots > Size() - GetOccupiedSlotCount() ) { maxSlots = static_cast( Size() ) - GetOccupiedSlotCount(); } const uint32_t chunk = troop.GetCount() / maxSlots; uint32_t remainingCount = troop.GetCount() % maxSlots; uint32_t remainingSlots = maxSlots; const auto TryCreateTroopChunk = [&remainingSlots, &remainingCount, chunk, &troop]( Troop & newTroop ) { if ( remainingSlots <= 0 ) return; if ( !newTroop.isValid() ) { newTroop.Set( troop.GetMonster(), remainingCount > 0 ? chunk + 1 : chunk ); --remainingSlots; if ( remainingCount > 0 ) --remainingCount; } }; for ( size_t i = 0; i < Size(); ++i ) { TryCreateTroopChunk( *GetTroop( i ) ); } } bool Troops::mergeWeakestTroopsIfNeeded() { if ( !isFullHouse() ) { return true; } std::map monsterIdVsSlotCount; std::map monsterIdVsStrength; for ( const Troop * troop : *this ) { assert( troop != nullptr ); if ( !troop->isValid() ) { continue; } const int id = troop->GetID(); if ( monsterIdVsStrength.count( id ) > 0 ) { monsterIdVsStrength[id] += troop->GetStrength(); ++monsterIdVsSlotCount[id]; } else { monsterIdVsSlotCount.emplace( id, 1 ); monsterIdVsStrength.emplace( id, troop->GetStrength() ); } } if ( monsterIdVsStrength.size() == size() ) { return false; } assert( monsterIdVsSlotCount.size() == monsterIdVsStrength.size() ); std::vector> sortedMultiSlotMonsterIdVsStrength; for ( const auto & [id, slotCount] : monsterIdVsSlotCount ) { if ( slotCount > 1 ) { sortedMultiSlotMonsterIdVsStrength.emplace_back( id, monsterIdVsStrength[id] ); } } assert( !sortedMultiSlotMonsterIdVsStrength.empty() ); std::sort( sortedMultiSlotMonsterIdVsStrength.begin(), sortedMultiSlotMonsterIdVsStrength.end(), []( const auto & left, const auto & right ) { return left.second < right.second; } ); const int weakestMonsterToMerge = sortedMultiSlotMonsterIdVsStrength.front().first; // Reset the last slot. uint32_t monsterCount = 0; for ( Troop * troop : *this ) { assert( troop != nullptr ); if ( !troop->isValid() ) { continue; } if ( troop->GetID() == weakestMonsterToMerge ) { monsterCount += troop->GetCount(); troop->Reset(); } } addNewTroopsToFreeSlots( Troop( weakestMonsterToMerge, monsterCount ), monsterIdVsSlotCount[weakestMonsterToMerge] - 1 ); return true; } void Troops::splitStackOfWeakestUnitsIntoFreeSlots() { const uint32_t occupiedSlotCount = GetOccupiedSlotCount(); if ( occupiedSlotCount == size() ) { // Nothing to do as all slots are being occupied. return; } // Look for a stack consisting of the weakest units Troop * stackOfWeakestUnits = nullptr; for ( Troop * troop : *this ) { assert( troop != nullptr ); if ( !troop->isValid() ) { continue; } if ( stackOfWeakestUnits == nullptr || stackOfWeakestUnits->GetMonsterStrength() > troop->GetMonsterStrength() ) { stackOfWeakestUnits = troop; } } assert( stackOfWeakestUnits != nullptr ); assert( stackOfWeakestUnits->GetCount() > 0 ); const uint32_t count = std::min( static_cast( size() ) - occupiedSlotCount, stackOfWeakestUnits->GetCount() - 1 ); if ( count == 0 ) { return; } stackOfWeakestUnits->SetCount( stackOfWeakestUnits->GetCount() - count ); addNewTroopsToFreeSlots( { stackOfWeakestUnits->GetMonster(), count }, count ); } void Troops::AssignToFirstFreeSlot( const Troop & troopToAssign, const uint32_t count ) const { for ( Troop * troop : *this ) { assert( troop != nullptr ); if ( troop->isValid() ) { continue; } troop->Set( troopToAssign.GetMonster(), count ); break; } } void Troops::JoinAllTroopsOfType( const Troop & targetTroop ) const { const int troopID = targetTroop.GetID(); const int totalMonsterCount = GetCountMonsters( troopID ); for ( Troop * troop : *this ) { assert( troop != nullptr ); if ( !troop->isValid() || troop->GetID() != troopID ) { continue; } if ( troop == &targetTroop ) { troop->SetCount( totalMonsterCount ); } else { troop->Reset(); } } } Army::Army( HeroBase * cmdr /* = nullptr */ ) : commander( cmdr ) , _isSpreadCombatFormation( true ) , color( Color::NONE ) { reserve( maximumTroopCount ); for ( size_t i = 0; i < maximumTroopCount; ++i ) { push_back( new ArmyTroop( this ) ); } } Army::Army( const Maps::Tiles & tile ) : commander( nullptr ) , _isSpreadCombatFormation( true ) , color( Color::NONE ) { reserve( maximumTroopCount ); for ( size_t i = 0; i < maximumTroopCount; ++i ) { push_back( new ArmyTroop( this ) ); } setFromTile( tile ); } const Troops & Army::getTroops() const { return *this; } void Army::setFromTile( const Maps::Tiles & tile ) { assert( commander == nullptr ); Troops::Clean(); const bool isCaptureObject = MP2::isCaptureObject( tile.GetObject( false ) ); if ( isCaptureObject ) { color = getColorFromTile( tile ); } else { color = Color::NONE; } switch ( tile.GetObject( false ) ) { case MP2::OBJ_PYRAMID: at( 0 )->Set( Monster::VAMPIRE_LORD, 10 ); at( 1 )->Set( Monster::ROYAL_MUMMY, 10 ); at( 2 )->Set( Monster::ROYAL_MUMMY, 10 ); at( 3 )->Set( Monster::ROYAL_MUMMY, 10 ); at( 4 )->Set( Monster::VAMPIRE_LORD, 10 ); break; case MP2::OBJ_GRAVEYARD: at( 0 )->Set( Monster::MUTANT_ZOMBIE, 20 ); at( 1 )->Set( Monster::MUTANT_ZOMBIE, 20 ); at( 2 )->Set( Monster::MUTANT_ZOMBIE, 20 ); at( 3 )->Set( Monster::MUTANT_ZOMBIE, 20 ); at( 4 )->Set( Monster::MUTANT_ZOMBIE, 20 ); break; case MP2::OBJ_SHIPWRECK: { uint32_t count = 0; switch ( getShipwreckCaptureCondition( tile ) ) { case Maps::ShipwreckCaptureCondition::EMPTY: // Shipwreck guardians were defeated. return; case Maps::ShipwreckCaptureCondition::FIGHT_10_GHOSTS_AND_GET_1000_GOLD: count = 10; break; case Maps::ShipwreckCaptureCondition::FIGHT_15_GHOSTS_AND_GET_2000_GOLD: count = 15; break; case Maps::ShipwreckCaptureCondition::FIGHT_25_GHOSTS_AND_GET_5000_GOLD: count = 25; break; case Maps::ShipwreckCaptureCondition::FIGHT_50_GHOSTS_AND_GET_2000_GOLD_WITH_ARTIFACT: count = 50; break; default: assert( 0 ); break; } ArrangeForBattle( Monster::GHOST, count, tile.GetIndex(), false ); break; } case MP2::OBJ_DERELICT_SHIP: ArrangeForBattle( Monster::SKELETON, 200, tile.GetIndex(), false ); break; case MP2::OBJ_ARTIFACT: switch ( getArtifactCaptureCondition( tile ) ) { case Maps::ArtifactCaptureCondition::FIGHT_50_ROGUES: ArrangeForBattle( Monster::ROGUE, 50, tile.GetIndex(), false ); break; case Maps::ArtifactCaptureCondition::FIGHT_1_GENIE: ArrangeForBattle( Monster::GENIE, 1, tile.GetIndex(), false ); break; case Maps::ArtifactCaptureCondition::FIGHT_1_PALADIN: ArrangeForBattle( Monster::PALADIN, 1, tile.GetIndex(), false ); break; case Maps::ArtifactCaptureCondition::FIGHT_1_CYCLOPS: ArrangeForBattle( Monster::CYCLOPS, 1, tile.GetIndex(), false ); break; case Maps::ArtifactCaptureCondition::FIGHT_1_PHOENIX: ArrangeForBattle( Monster::PHOENIX, 1, tile.GetIndex(), false ); break; case Maps::ArtifactCaptureCondition::FIGHT_1_GREEN_DRAGON: ArrangeForBattle( Monster::GREEN_DRAGON, 1, tile.GetIndex(), false ); break; case Maps::ArtifactCaptureCondition::FIGHT_1_TITAN: ArrangeForBattle( Monster::TITAN, 1, tile.GetIndex(), false ); break; case Maps::ArtifactCaptureCondition::FIGHT_1_BONE_DRAGON: ArrangeForBattle( Monster::BONE_DRAGON, 1, tile.GetIndex(), false ); break; default: break; } break; case MP2::OBJ_CITY_OF_DEAD: at( 0 )->Set( Monster::ZOMBIE, 20 ); at( 1 )->Set( Monster::VAMPIRE_LORD, 5 ); at( 2 )->Set( Monster::POWER_LICH, 5 ); at( 3 )->Set( Monster::VAMPIRE_LORD, 5 ); at( 4 )->Set( Monster::ZOMBIE, 20 ); break; case MP2::OBJ_TROLL_BRIDGE: at( 0 )->Set( Monster::TROLL, 4 ); at( 1 )->Set( Monster::WAR_TROLL, 4 ); at( 2 )->Set( Monster::TROLL, 4 ); at( 3 )->Set( Monster::WAR_TROLL, 4 ); at( 4 )->Set( Monster::TROLL, 4 ); break; case MP2::OBJ_DRAGON_CITY: { uint32_t monsterCount = 1; if ( Settings::Get().isCampaignGameType() ) { const Campaign::ScenarioVictoryCondition victoryCondition = Campaign::getCurrentScenarioVictoryCondition(); if ( victoryCondition == Campaign::ScenarioVictoryCondition::CAPTURE_DRAGON_CITY ) { monsterCount = 2; } } at( 0 )->Set( Monster::GREEN_DRAGON, monsterCount ); at( 1 )->Set( Monster::GREEN_DRAGON, monsterCount ); at( 2 )->Set( Monster::GREEN_DRAGON, monsterCount ); at( 3 )->Set( Monster::RED_DRAGON, monsterCount ); at( 4 )->Set( Monster::BLACK_DRAGON, monsterCount ); break; } case MP2::OBJ_DAEMON_CAVE: at( 0 )->Set( Monster::EARTH_ELEMENT, 2 ); at( 1 )->Set( Monster::EARTH_ELEMENT, 2 ); at( 2 )->Set( Monster::EARTH_ELEMENT, 2 ); at( 3 )->Set( Monster::EARTH_ELEMENT, 2 ); break; default: if ( isCaptureObject ) { const Troop & troop = world.GetCapturedObject( tile.GetIndex() ).GetTroop(); if ( troop.isValid() ) { ArrangeForBattle( troop.GetMonster(), troop.GetCount(), tile.GetIndex(), false ); } } else { const Troop troop = getTroopFromTile( tile ); if ( troop.isValid() ) { ArrangeForBattle( troop.GetMonster(), troop.GetCount(), tile.GetIndex(), true ); } } break; } } int Army::GetColor() const { const HeroBase * currentCommander = GetCommander(); return currentCommander != nullptr ? currentCommander->GetColor() : color; } int Army::GetLuck() const { const HeroBase * currentCommander = GetCommander(); return currentCommander != nullptr ? currentCommander->GetLuck() : GetLuckModificator( nullptr ); } int Army::GetLuckModificator( std::string * strs ) const { int result = Luck::NORMAL; // check castle modificator const Castle * castle = inCastle(); if ( castle ) { result += castle->GetLuckModificator( strs ); } return result; } int Army::GetMorale() const { const HeroBase * currentCommander = GetCommander(); return currentCommander != nullptr ? currentCommander->GetMorale() : GetMoraleModificator( nullptr ); } int Army::GetMoraleModificator( std::string * strs ) const { // different race penalty std::set races; bool hasUndead = false; bool allUndead = true; for ( const Troop * troop : *this ) if ( troop->isValid() ) { races.insert( troop->GetRace() ); hasUndead = hasUndead || troop->isUndead(); allUndead = allUndead && troop->isUndead(); } if ( allUndead ) return Morale::NORMAL; int result = Morale::NORMAL; // check castle modificator const Castle * castle = inCastle(); if ( castle ) { result += castle->GetMoraleModificator( strs ); } // artifact "Arm of the Martyr" adds the undead morale penalty hasUndead = hasUndead || ( GetCommander() && GetCommander()->GetBagArtifacts().isArtifactCursePresent( fheroes2::ArtifactCurseType::UNDEAD_MORALE_PENALTY ) ); const int count = static_cast( races.size() ); switch ( count ) { case 0: case 2: break; case 1: if ( !hasUndead && !AllTroopsAreTheSame() ) { // presence of undead discards "All %{race} troops +1" bonus ++result; if ( strs ) { std::string str = _( "All %{race} troops +1" ); StringReplace( str, "%{race}", *races.begin() == Race::NONE ? _( "Multiple" ) : Race::String( *races.begin() ) ); strs->append( str ); *strs += '\n'; } } break; default: const int penalty = count - 2; result -= penalty; if ( strs ) { std::string str = _( "Troops of %{count} alignments -%{penalty}" ); StringReplace( str, "%{count}", count ); StringReplace( str, "%{penalty}", penalty ); strs->append( str ); *strs += '\n'; } break; } // undead in life group if ( hasUndead ) { result -= 1; if ( strs ) { strs->append( _( "Some undead in army -1" ) ); *strs += '\n'; } } return result; } double Army::GetStrength() const { double result = 0; const uint32_t heroArchery = ( commander != nullptr ) ? commander->GetSecondaryValues( Skill::Secondary::ARCHERY ) : 0; const int bonusAttack = ( commander ? commander->GetAttack() : 0 ); const int bonusDefense = ( commander ? commander->GetDefense() : 0 ); const int armyMorale = GetMorale(); const int armyLuck = GetLuck(); bool troopsExist = false; for ( const Troop * troop : *this ) { assert( troop != nullptr ); if ( troop->isEmpty() ) { continue; } troopsExist = true; double strength = troop->GetStrengthWithBonus( bonusAttack, bonusDefense ); if ( heroArchery > 0 && troop->isArchers() ) { strength *= sqrt( 1 + static_cast( heroArchery ) / 100 ); } if ( troop->isAffectedByMorale() ) { strength *= 1 + ( ( armyMorale < 0 ) ? armyMorale / 12.0 : armyMorale / 24.0 ); } strength *= 1 + armyLuck / 24.0; result += strength; } if ( commander != nullptr && troopsExist ) { result += commander->GetMagicStrategicValue( result ); } return result; } double Army::getStrengthOfAverageStartingArmy( const Heroes * hero ) { assert( hero != nullptr ); const int race = hero->GetRace(); double result = 0.0; for ( uint32_t dwelling : std::array{ DWELLING_MONSTER1, DWELLING_MONSTER2 } ) { const Monster monster{ race, dwelling }; assert( monster.isValid() ); const auto [min, max] = getNumberOfMonstersInStartingArmy( monster ); result += Troop{ monster, ( min + max ) / 2 }.GetStrength(); } return result; } void Army::Reset( const bool defaultArmy /* = false */ ) { Troops::Clean(); if ( commander == nullptr || !commander->isHeroes() ) { return; } const int race = commander->GetRace(); // Sometimes heroes created solely for the purpose of showing an avatar may not have a race if ( race == Race::NONE ) { return; } if ( !defaultArmy ) { JoinTroop( { race, DWELLING_MONSTER1 }, 1, false ); return; } const auto joinMonsters = [this]( const Monster & monster ) { const auto [min, max] = getNumberOfMonstersInStartingArmy( monster ); if ( !JoinTroop( monster, Rand::Get( min, max ), false ) ) { assert( 0 ); } }; joinMonsters( { race, DWELLING_MONSTER1 } ); if ( Rand::Get( 1, 10 ) == 1 ) { return; } joinMonsters( { race, DWELLING_MONSTER2 } ); } HeroBase * Army::GetCommander() { return ( !commander || ( commander->isCaptain() && !commander->isValid() ) ) ? nullptr : commander; } const Castle * Army::inCastle() const { return commander ? commander->inCastle() : nullptr; } const HeroBase * Army::GetCommander() const { return ( !commander || ( commander->isCaptain() && !commander->isValid() ) ) ? nullptr : commander; } int Army::GetControl() const { return commander ? commander->GetControl() : ( color == Color::NONE ? CONTROL_AI : Players::GetPlayerControl( color ) ); } uint32_t Army::getTotalCount() const { return std::accumulate( begin(), end(), static_cast( 0 ), []( const uint32_t count, const Troop * troop ) { return troop->isValid() ? count + troop->GetCount() : count; } ); } std::string Army::String() const { std::ostringstream os; os << "color(" << Color::String( commander ? commander->GetColor() : color ) << "), "; if ( GetCommander() ) os << "commander(" << GetCommander()->GetName() << ")"; os << ": "; for ( const_iterator it = begin(); it != end(); ++it ) if ( ( *it )->isValid() ) os << std::dec << ( *it )->GetCount() << " " << ( *it )->GetName() << ", "; return os.str(); } void Army::JoinStrongestFromArmy( Army & giver ) { const bool saveLast = ( giver.commander != nullptr ) && giver.commander->isHeroes(); JoinStrongest( giver, saveLast ); } void Army::MoveTroops( Army & from, const int monsterIdToKeep ) { assert( this != &from ); // Combine troops of the same type in one slot to leave more room for new troops to join MergeSameMonsterTroops(); // Heroes need to have at least one occupied slot in their army, while garrisons do not need this const bool fromHero = from.GetCommander() && from.GetCommander()->isHeroes(); #ifndef NDEBUG const bool toHero = GetCommander() && GetCommander()->isHeroes(); #endif assert( ( !fromHero || from.isValid() ) && ( !toHero || isValid() ) ); if ( monsterIdToKeep != Monster::UNKNOWN ) { // Find a troop stack in the destination stack set consisting of monsters we need to keep const auto keepIter = std::find_if( begin(), end(), [monsterIdToKeep]( const Troop * troop ) { assert( troop != nullptr ); return troop->isValid() && troop->GetID() == monsterIdToKeep; } ); // Find a troop stack in the source stack set consisting of monsters of a different type than the monster we need to keep const auto xchgIter = std::find_if( from.begin(), from.end(), [monsterIdToKeep]( const Troop * troop ) { assert( troop != nullptr ); return troop->isValid() && troop->GetID() != monsterIdToKeep; } ); // If we found both, then exchange the monster to keep in the destination stack set with the found troop stack in the // source stack set to concentrate all the monsters to keep in the source stack set if ( keepIter != end() && xchgIter != from.end() ) { Troop * keep = *keepIter; Troop * xchg = *xchgIter; const uint32_t count = keep->GetCount(); keep->Reset(); if ( !JoinTroop( *xchg ) ) { assert( 0 ); } xchg->Reset(); if ( !from.JoinTroop( Monster( monsterIdToKeep ), count, false ) ) { assert( 0 ); } } } const auto moveTroops = [this, &from, monsterIdToKeep, fromHero]( const bool ignoreMonstersToKeep ) { uint32_t stacksLeft = from.GetOccupiedSlotCount(); for ( Troop * troop : from ) { assert( troop != nullptr ); if ( troop->isEmpty() ) { continue; } if ( ignoreMonstersToKeep && troop->GetID() == monsterIdToKeep ) { continue; } if ( fromHero ) { // If the source stack set belongs to a hero and this is the last valid troop stack in it, // then try to join all but one monsters from this stack and then stop in any case if ( stacksLeft == 1 ) { if ( JoinTroop( troop->GetMonster(), troop->GetCount() - 1, false ) ) { troop->SetCount( 1 ); } break; } assert( stacksLeft > 1 ); } if ( JoinTroop( *troop ) ) { troop->Reset(); --stacksLeft; } } }; // First of all, try to move all the troop stacks except the stacks of monsters that we need to keep moveTroops( true ); // Then, try to move as much of monsters to keep as we can (except the last one, if we are moving them // from the stack set that belongs to a hero), if there is still a place for them moveTroops( false ); } uint32_t Army::ActionToSirens() const { uint32_t experience = 0; for ( Troop * troop : *this ) { assert( troop != nullptr ); if ( !troop->isValid() ) { continue; } const uint32_t troopCount = troop->GetCount(); if ( troopCount == 1 ) { // Sirens do not affect 1 troop stack. continue; } // 30% of stack troops will be gone. uint32_t troopToRemove = troopCount * 3 / 10; if ( troopToRemove == 0 ) { // At least one unit must go, even if it's more than 30%. troopToRemove = 1; } troop->SetCount( troopCount - troopToRemove ); experience += troopToRemove * static_cast( troop )->GetHitPoints(); } return experience; } bool Army::isStrongerThan( const Army & target, double safetyRatio /* = 1.0 */ ) const { if ( !target.isValid() ) { return true; } const double armyStrength = Army::GetStrength(); const double targetStrength = target.GetStrength() * safetyRatio; DEBUG_LOG( DBG_GAME, DBG_TRACE, "Comparing troops: " << armyStrength << " versus " << targetStrength ) return armyStrength > targetStrength; } bool Army::isMeleeDominantArmy() const { double meleeInfantry = 0; double other = 0; for ( const Troop * troop : *this ) { if ( troop != nullptr && troop->isValid() ) { if ( !troop->isArchers() && !troop->isFlying() ) { meleeInfantry += troop->GetStrength(); } else { other += troop->GetStrength(); } } } return meleeInfantry > other; } void Army::drawSingleDetailedMonsterLine( const Troops & troops, int32_t cx, int32_t cy, int32_t width ) { fheroes2::drawMiniMonsters( troops, cx, cy, width, 0, 0, false, true, false, 0, fheroes2::Display::instance() ); } void Army::drawMultipleMonsterLines( const Troops & troops, int32_t posX, int32_t posY, int32_t lineWidth, bool isCompact, const bool isDetailedView, const bool isGarrisonView /* = false */, const uint32_t thievesGuildsCount /* = 0 */ ) { const uint32_t count = troops.GetOccupiedSlotCount(); const int offsetX = lineWidth / 6; const int offsetY = isCompact ? 31 : 49; fheroes2::Image & output = fheroes2::Display::instance(); if ( count < 3 ) { fheroes2::drawMiniMonsters( troops, posX + offsetX, posY + offsetY / 2 + 1, lineWidth * 2 / 3, 0, 0, isCompact, isDetailedView, isGarrisonView, thievesGuildsCount, output ); } else { const int firstLineTroopCount = 2; const int secondLineTroopCount = count - firstLineTroopCount; const int secondLineWidth = secondLineTroopCount == 2 ? lineWidth * 2 / 3 : lineWidth; fheroes2::drawMiniMonsters( troops, posX + offsetX, posY, lineWidth * 2 / 3, 0, firstLineTroopCount, isCompact, isDetailedView, isGarrisonView, thievesGuildsCount, output ); fheroes2::drawMiniMonsters( troops, posX, posY + offsetY, secondLineWidth, firstLineTroopCount, secondLineTroopCount, isCompact, isDetailedView, isGarrisonView, thievesGuildsCount, output ); } } NeutralMonsterJoiningCondition Army::GetJoinSolution( const Heroes & hero, const Maps::Tiles & tile, const Troop & troop ) { // Check for creature alliance/bane campaign awards, campaign only and of course, for human players // creature alliance -> if we have an alliance with the appropriate creature (inc. players) they will join for free // creature curse/bane -> same as above but all of them will flee even if you have just 1 peasant if ( Settings::Get().isCampaignGameType() && hero.isControlHuman() ) { const std::vector campaignAwards = Campaign::CampaignSaveData::Get().getObtainedCampaignAwards(); for ( size_t i = 0; i < campaignAwards.size(); ++i ) { const bool isAlliance = campaignAwards[i]._type == Campaign::CampaignAwardData::TYPE_CREATURE_ALLIANCE; const bool isCurse = campaignAwards[i]._type == Campaign::CampaignAwardData::TYPE_CREATURE_CURSE; if ( !isAlliance && !isCurse ) continue; Monster monster( campaignAwards[i]._subType ); while ( true ) { if ( troop.GetID() == monster.GetID() ) { if ( isAlliance ) { return { NeutralMonsterJoiningCondition::Reason::Alliance, troop.GetCount(), Campaign::CampaignAwardData::getAllianceJoiningMessage( monster.GetID() ), Campaign::CampaignAwardData::getAllianceFleeingMessage( monster.GetID() ) }; } else { return { NeutralMonsterJoiningCondition::Reason::Bane, troop.GetCount(), nullptr, Campaign::CampaignAwardData::getBaneFleeingMessage( monster.GetID() ) }; } } // try to cycle through the creature's upgrades if ( !monster.isAllowUpgrade() ) break; monster = monster.GetUpgrade(); } } } if ( hero.GetBagArtifacts().isArtifactCursePresent( fheroes2::ArtifactCurseType::NO_JOINING_ARMIES ) ) { return { NeutralMonsterJoiningCondition::Reason::None, 0, nullptr, nullptr }; } if ( Maps::isMonsterOnTileJoinConditionSkip( tile ) || !troop.isValid() ) { return { NeutralMonsterJoiningCondition::Reason::None, 0, nullptr, nullptr }; } // Neutral monsters don't care about hero's stats. Ignoring hero's stats makes hero's army strength be smaller in eyes of neutrals and they won't join so often. const double armyStrengthRatio = Troops( hero.GetArmy().getTroops() ).GetStrength() / troop.GetStrength(); // The ability to accept monsters (a free slot or a stack of monsters of the same type) is a // mandatory condition for their joining in accordance with the mechanics of the original game if ( armyStrengthRatio > 2 && hero.GetArmy().CanJoinTroop( troop ) ) { if ( Maps::isMonsterOnTileJoinConditionFree( tile ) ) { return { NeutralMonsterJoiningCondition::Reason::Free, troop.GetCount(), nullptr, nullptr }; } if ( hero.HasSecondarySkill( Skill::Secondary::DIPLOMACY ) ) { const uint32_t amountToJoin = Monster::GetCountFromHitPoints( troop, troop.GetHitPoints() * hero.GetSecondaryValues( Skill::Secondary::DIPLOMACY ) / 100 ); // The ability to hire the entire stack of monsters is a mandatory condition for their joining // due to hero's Diplomacy skill in accordance with the mechanics of the original game if ( amountToJoin > 0 && hero.GetKingdom().AllowPayment( Funds( Resource::GOLD, troop.GetTotalCost().gold ) ) ) { return { NeutralMonsterJoiningCondition::Reason::ForMoney, amountToJoin, nullptr, nullptr }; } } } if ( armyStrengthRatio > 5 && !hero.isControlAI() ) { // ... surely flee before us return { NeutralMonsterJoiningCondition::Reason::RunAway, 0, nullptr, nullptr }; } return { NeutralMonsterJoiningCondition::Reason::None, 0, nullptr, nullptr }; } bool Army::WeakestTroop( const Troop * t1, const Troop * t2 ) { return t1->GetStrength() < t2->GetStrength(); } bool Army::StrongestTroop( const Troop * t1, const Troop * t2 ) { return t1->GetStrength() > t2->GetStrength(); } bool Army::SlowestTroop( const Troop * t1, const Troop * t2 ) { return t1->GetSpeed() < t2->GetSpeed(); } bool Army::FastestTroop( const Troop * t1, const Troop * t2 ) { return t1->GetSpeed() > t2->GetSpeed(); } void Army::SwapTroops( Troop & t1, Troop & t2 ) { std::swap( t1, t2 ); } bool Army::SaveLastTroop() const { return commander && commander->isHeroes() && 1 == GetOccupiedSlotCount(); } Monster Army::GetStrongestMonster() const { Monster monster( Monster::UNKNOWN ); for ( const Troop * troop : *this ) { if ( troop->isValid() && troop->GetMonster().GetMonsterStrength() > monster.GetMonsterStrength() ) { monster = troop->GetID(); } } return monster; } void Army::resetInvalidMonsters() const { for ( Troop * troop : *this ) { if ( troop->GetID() != Monster::UNKNOWN && !troop->isValid() ) { troop->Set( Monster::UNKNOWN, 0 ); } } } bool Army::ArrangeForCastleDefense( Army & garrison ) { assert( this != &garrison ); assert( size() == maximumTroopCount && garrison.size() == maximumTroopCount ); // This method is designed to reinforce only the armies of heroes assert( commander != nullptr && commander->isHeroes() ); // This method is designed to take reinforcements only from the garrison, because // it can leave the garrison empty assert( garrison.commander == nullptr || garrison.commander->isCaptain() ); bool result = false; // If the guest hero's army is controlled by AI, then try to squeeze as many garrison troops in as possible if ( isControlAI() ) { // Create and fill a temporary container for convenient sorting of garrison troops std::vector garrisonTroops; garrisonTroops.reserve( garrison.Size() ); for ( Troop * troop : garrison ) { assert( troop != nullptr ); if ( troop->isValid() ) { garrisonTroops.push_back( troop ); } } // Sort the garrison troops by their strength (most powerful stacks first) std::sort( garrisonTroops.begin(), garrisonTroops.end(), StrongestTroop ); // Try to reinforce the guest hero's army with garrison troops (most powerful stacks first) for ( Troop * troop : garrisonTroops ) { if ( JoinTroop( *troop ) ) { troop->Reset(); result = true; continue; } // If there is no space for another garrison stack, we will try to combine some existing stacks... if ( MergeSameMonsterOnce() ) { // ... and try again if ( JoinTroop( *troop ) ) { troop->Reset(); result = true; } else { assert( 0 ); } } } assert( size() == maximumTroopCount ); return result; } // Otherwise, try to move the garrison troops to exactly the same slots of the guest hero's army, provided // that these slots are empty for ( size_t i = 0; i < maximumTroopCount; ++i ) { Troop * troop = GetTroop( i ); Troop * garrisonTroop = garrison.GetTroop( i ); assert( troop != nullptr && garrisonTroop != nullptr ); if ( !garrisonTroop->isValid() ) { continue; } if ( troop->isValid() ) { continue; } troop->Set( garrisonTroop->GetMonster(), garrisonTroop->GetCount() ); garrisonTroop->Reset(); result = true; } return result; } void Army::ArrangeForWhirlpool() { // Make an "optimized" version first (each unit type occupies just one slot) const Troops optimizedTroops = GetOptimized(); assert( optimizedTroops.Size() > 0 && optimizedTroops.Size() <= maximumTroopCount ); // Already a full house, there is no room for further optimization if ( optimizedTroops.Size() == maximumTroopCount ) { return; } Assign( optimizedTroops ); // Look for a troop consisting of the weakest units Troop * troopOfWeakestUnits = nullptr; for ( Troop * troop : *this ) { assert( troop != nullptr ); if ( !troop->isValid() ) { continue; } if ( troopOfWeakestUnits == nullptr || troopOfWeakestUnits->GetMonsterStrength() > troop->GetMonsterStrength() ) { troopOfWeakestUnits = troop; } } assert( troopOfWeakestUnits != nullptr ); assert( troopOfWeakestUnits->GetCount() > 0 ); // There is already just one unit in this troop, let's leave it as it is if ( troopOfWeakestUnits->GetCount() == 1 ) { return; } // Move one unit from this troop's slot... troopOfWeakestUnits->SetCount( troopOfWeakestUnits->GetCount() - 1 ); // To the separate slot auto emptySlot = std::find_if( begin(), end(), []( const Troop * troop ) { return troop->isEmpty(); } ); assert( emptySlot != end() ); ( *emptySlot )->Set( Monster( troopOfWeakestUnits->GetID() ), 1 ); } void Army::ArrangeForBattle( const Monster & monster, const uint32_t monstersCount, const uint32_t stacksCount ) { assert( stacksCount > 0 && stacksCount <= size() && size() == maximumTroopCount ); assert( std::all_of( begin(), end(), []( const Troop * troop ) { return troop->isEmpty(); } ) ); size_t stacks; for ( stacks = stacksCount; stacks > 0; --stacks ) { if ( monstersCount / stacks > 0 ) { break; } } assert( stacks > 0 ); const uint32_t quotient = monstersCount / static_cast( stacks ); const uint32_t remainder = monstersCount % static_cast( stacks ); assert( quotient > 0 ); const size_t shift = ( size() - stacksCount ) / 2; for ( size_t i = 0; i < stacks; ++i ) { at( i + shift )->Set( monster, i < remainder ? quotient + 1 : quotient ); } assert( std::accumulate( begin(), end(), static_cast( 0 ), []( const uint32_t count, const Troop * troop ) { return troop->isValid() ? count + troop->GetCount() : count; } ) == monstersCount ); } void Army::ArrangeForBattle( const Monster & monster, const uint32_t monstersCount, const int32_t tileIndex, const bool allowUpgrade ) { uint32_t stacksCount = 0; // Archers should always be divided into as many stacks as possible if ( monster.isArchers() ) { stacksCount = maximumTroopCount; } else { std::mt19937 seededGen( world.GetMapSeed() + static_cast( tileIndex ) ); stacksCount = Rand::GetWithGen( 3, 5, seededGen ); } ArrangeForBattle( monster, monstersCount, stacksCount ); if ( allowUpgrade ) { assert( size() % 2 == 1 ); // An upgraded stack can be located only in the center Troop * troopToUpgrade = at( size() / 2 ); assert( troopToUpgrade != nullptr ); if ( troopToUpgrade->isValid() && troopToUpgrade->isAllowUpgrade() ) { std::mt19937 seededGen( world.GetMapSeed() + static_cast( tileIndex ) + static_cast( monster.GetID() ) ); // 50% chance to get an upgraded stack if ( Rand::GetWithGen( 0, 1, seededGen ) == 1 ) { troopToUpgrade->Upgrade(); } } } } StreamBase & operator<<( StreamBase & msg, const Army & army ) { msg << static_cast( army.size() ); // Army: fixed size for ( Army::const_iterator it = army.begin(); it != army.end(); ++it ) msg << **it; return msg << army._isSpreadCombatFormation << army.color; } StreamBase & operator>>( StreamBase & msg, Army & army ) { uint32_t armysz; msg >> armysz; for ( Army::iterator it = army.begin(); it != army.end(); ++it ) msg >> **it; msg >> army._isSpreadCombatFormation >> army.color; // set army for ( Army::iterator it = army.begin(); it != army.end(); ++it ) { ArmyTroop * troop = static_cast( *it ); if ( troop ) troop->SetArmy( army ); } // set later from owner (castle, heroes) army.commander = nullptr; return msg; } fheroes2-1.0.12+dfsg/src/fheroes2/army/army.h000066400000000000000000000240441456075706000206420ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2ARMY_H #define H2ARMY_H #include #include #include #include #include #include "monster.h" #include "players.h" class StreamBase; class Castle; class HeroBase; class Heroes; class Troop; namespace Maps { class Tiles; } class Troops : protected std::vector { public: Troops() = default; Troops( const Troops & troops ); virtual ~Troops(); Troops & operator=( const Troops & ) = delete; void Assign( const Troop *, const Troop * ); void Assign( const Troops & ); void Insert( const Troops & ); void PushBack( const Monster &, uint32_t ); void PopBack(); size_t Size() const { return size(); } Troop * GetTroop( size_t ); const Troop * GetTroop( size_t ) const; void UpgradeMonsters( const Monster & ); uint32_t GetCountMonsters( const Monster & mons ) const; double getReinforcementValue( const Troops & reinforcement ) const; uint32_t GetOccupiedSlotCount() const; bool isValid() const; bool HasMonster( const Monster & ) const; bool areAllTroopsUnique() const; bool AllTroopsAreUndead() const; // Returns true if all valid troops have the same ID or if there are no troops, otherwise returns false bool AllTroopsAreTheSame() const; bool JoinTroop( const Troop & troop ); bool JoinTroop( const Monster & mons, const uint32_t count, const bool emptySlotFirst ); bool CanJoinTroop( const Monster & ) const; virtual double GetStrength() const; uint32_t getTotalHP() const; void Clean(); void UpgradeTroops( const Castle & castle ) const; Troop * GetFirstValid(); Troop * GetWeakestTroop() const; Troop * GetSlowestTroop() const; void SortStrongest(); void SplitTroopIntoFreeSlots( const Troop & troop, const Troop & selectedSlot, const uint32_t slots ); void AssignToFirstFreeSlot( const Troop & troopToAssign, const uint32_t count ) const; void JoinAllTroopsOfType( const Troop & targetTroop ) const; void addNewTroopsToFreeSlots( const Troop & troop, uint32_t maxSlots ); bool isFullHouse() const { return GetOccupiedSlotCount() == size(); } // If the army has no slot find 2 or more slots of the same monster which is the weakest and merge them releasing one slot in troops. bool mergeWeakestTroopsIfNeeded(); // Splits the stack consisting of the weakest units into free slots (if any), 1 monster per free slot void splitStackOfWeakestUnitsIntoFreeSlots(); // Combines all stacks consisting of identical monsters void MergeSameMonsterTroops(); protected: void JoinStrongest( Troops & giverArmy, const bool keepAtLeastOneSlotForGiver ); // Combines two stacks consisting of identical monsters. Returns true if there was something to combine, otherwise returns false. bool MergeSameMonsterOnce(); // Returns an optimized version of this Troops instance, i.e. all stacks of identical monsters are combined and there are no empty slots Troops GetOptimized() const; private: // Returns the stack that best matches the specified condition or nullptr if there are no valid stacks Troop * getBestMatchToCondition( const std::function & condition ) const; }; struct NeutralMonsterJoiningCondition { enum class Reason : int { None, Free, ForMoney, RunAway, Alliance, Bane }; Reason reason; uint32_t monsterCount; // These messages are used only for Alliance and Bane reasons. const char * joiningMessage; const char * fleeingMessage; }; class Army final : public Troops, public Control { public: static const size_t maximumTroopCount = 5; static std::string SizeString( uint32_t ); static std::string TroopSizeString( const Troop & ); // Comparison functions static bool WeakestTroop( const Troop *, const Troop * ); static bool StrongestTroop( const Troop *, const Troop * ); static bool SlowestTroop( const Troop *, const Troop * ); static bool FastestTroop( const Troop *, const Troop * ); static void SwapTroops( Troop &, Troop & ); static NeutralMonsterJoiningCondition GetJoinSolution( const Heroes &, const Maps::Tiles &, const Troop & ); // Returns the strength of the average starting army for a given hero (not taking into account the hero's bonuses) static double getStrengthOfAverageStartingArmy( const Heroes * hero ); static void drawSingleDetailedMonsterLine( const Troops & troops, int32_t cx, int32_t cy, int32_t width ); static void drawMultipleMonsterLines( const Troops & troops, int32_t posX, int32_t posY, int32_t lineWidth, bool isCompact, const bool isDetailedView, const bool isGarrisonView = false, const uint32_t thievesGuildsCount = 0 ); explicit Army( HeroBase * cmdr = nullptr ); explicit Army( const Maps::Tiles & tile ); Army( const Army & ) = delete; Army( Army && ) = delete; Army & operator=( const Army & ) = delete; Army & operator=( Army && ) = delete; ~Army() override = default; const Troops & getTroops() const; // Resets the army. If the army doesn't have a commanding hero, then it makes the army empty. Otherwise, if 'defaultArmy' is set to true, then it creates a default // army of the commanding hero's faction (several units of level 1 and 2). Otherwise, a minimum army is created, consisting of exactly one monster of the first level // of the commanding hero's faction. void Reset( const bool defaultArmy = false ); void setFromTile( const Maps::Tiles & tile ); int GetColor() const; int GetControl() const override; uint32_t getTotalCount() const; double GetStrength() const override; bool isStrongerThan( const Army & target, const double safetyRatio = 1.0 ) const; bool isMeleeDominantArmy() const; void SetColor( int cl ) { color = cl; } int GetMorale() const; int GetLuck() const; int GetMoraleModificator( std::string * strs ) const; int GetLuckModificator( std::string * strs ) const; uint32_t ActionToSirens() const; const HeroBase * GetCommander() const; HeroBase * GetCommander(); void SetCommander( HeroBase * c ) { commander = c; } const Castle * inCastle() const; std::string String() const; void JoinStrongestFromArmy( Army & giver ); // Implements the necessary logic to move unit stacks from army to army in the hero's meeting dialog and in the castle dialog void MoveTroops( Army & from, const int monsterIdToKeep ); void SetSpreadFormation( const bool spread ) { _isSpreadCombatFormation = spread; } bool isSpreadFormation() const { return _isSpreadCombatFormation; } bool SaveLastTroop() const; Monster GetStrongestMonster() const; void resetInvalidMonsters() const; // Performs the pre-battle arrangement for the castle (or town) defense, trying to add reinforcements from the garrison. Returns // true if at least one unit from the garrison was moved to the army as reinforcements, otherwise returns false. The logic of // combining troops for the castle defense differs from the original game, see the implementation for details. bool ArrangeForCastleDefense( Army & garrison ); // Optimizes the arrangement of troops to pass through the whirlpool (moves one weakest unit to a separate slot, if possible) void ArrangeForWhirlpool(); private: friend StreamBase & operator<<( StreamBase &, const Army & ); friend StreamBase & operator>>( StreamBase &, Army & ); // Performs the pre-battle arrangement of given monsters in a given number, dividing them into a given number of stacks if possible void ArrangeForBattle( const Monster & monster, const uint32_t monstersCount, const uint32_t stacksCount ); // Performs the pre-battle arrangement of given monsters in a given number, dividing them into a random number of stacks (seeded by // the tile index) with a random chance to get an upgraded stack of monsters in the center (if allowed) void ArrangeForBattle( const Monster & monster, const uint32_t monstersCount, const int32_t tileIndex, const bool allowUpgrade ); HeroBase * commander; bool _isSpreadCombatFormation; int color; }; StreamBase & operator<<( StreamBase &, const Army & ); StreamBase & operator>>( StreamBase &, Army & ); #endif fheroes2-1.0.12+dfsg/src/fheroes2/army/army_bar.cpp000066400000000000000000000563401456075706000220250ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2012 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "army_bar.h" #include #include #include #include #include "agg_image.h" #include "army.h" #include "army_troop.h" #include "castle.h" #include "dialog.h" #include "dialog_selectitems.h" #include "game_hotkeys.h" #include "heroes_base.h" #include "icn.h" #include "kingdom.h" #include "localevent.h" #include "monster.h" #include "race.h" #include "tools.h" #include "translations.h" #include "ui_monster.h" #include "ui_text.h" #include "world.h" namespace { void RedistributeArmy( ArmyTroop & troopFrom, ArmyTroop & troopTarget, Army * armyTarget ) { const Army * armyFrom = troopFrom.GetArmy(); const bool saveLastTroop = armyFrom->SaveLastTroop() && armyFrom != armyTarget; const bool isSameTroopType = troopTarget.isValid() && troopFrom.GetID() == troopTarget.GetID(); const uint32_t overallCount = isSameTroopType ? troopFrom.GetCount() + troopTarget.GetCount() : troopFrom.GetCount(); assert( overallCount > 0 ); if ( overallCount == 1 ) { // prevent cross-army split if we lose the last unit in the hero army if ( saveLastTroop ) { return; } // it seems that we are just moving a single unit to a free cell assert( !troopTarget.isValid() ); Army::SwapTroops( troopFrom, troopTarget ); } else if ( !troopTarget.isValid() && troopFrom.GetCount() == 2 ) { // a player splits a slot with two monsters into an empty slot; move one monster into the source slot to the target slot. troopFrom.SetCount( 1 ); troopTarget.Set( troopFrom.GetMonster(), 1 ); } else if ( isSameTroopType && troopFrom.isValid() && troopFrom.GetCount() == 1 && troopTarget.isValid() && troopTarget.GetCount() == 1 ) { // a player splits the same troop type and both count one; move a monster from the source slot to the target slot. troopFrom.Reset(); troopTarget.SetCount( 2 ); } else { uint32_t freeSlots = static_cast( 1 + armyTarget->Size() - armyTarget->GetOccupiedSlotCount() ); if ( isSameTroopType ) ++freeSlots; const uint32_t maxCount = saveLastTroop ? troopFrom.GetCount() - 1 : troopFrom.GetCount(); uint32_t redistributeCount = isSameTroopType ? 1 : troopFrom.GetCount() / 2; bool useFastSplit = !isSameTroopType; const uint32_t slots = Dialog::ArmySplitTroop( ( freeSlots > overallCount ? overallCount : freeSlots ), maxCount, redistributeCount, useFastSplit ); if ( slots < 2 || slots > 6 ) return; uint32_t totalSplitTroopCount = troopFrom.GetCount(); if ( !useFastSplit && slots == 2 ) { // this logic is used when splitting to a stack with the same unit if ( isSameTroopType ) troopTarget.SetCount( troopTarget.GetCount() + redistributeCount ); else troopTarget.Set( troopFrom, redistributeCount ); troopFrom.SetCount( totalSplitTroopCount - redistributeCount ); } else { if ( isSameTroopType ) totalSplitTroopCount += troopTarget.GetCount(); const uint32_t troopFromSplitCount = ( totalSplitTroopCount + slots - 1 ) / slots; troopFrom.SetCount( troopFromSplitCount ); const uint32_t troopTargetSplitCount = ( totalSplitTroopCount + slots - 2 ) / slots; if ( !isSameTroopType ) troopTarget.SetMonster( troopFrom.GetID() ); troopTarget.SetCount( troopTargetSplitCount ); totalSplitTroopCount -= troopFromSplitCount; totalSplitTroopCount -= troopTargetSplitCount; if ( slots > 2 ) { armyTarget->SplitTroopIntoFreeSlots( Troop( troopFrom, totalSplitTroopCount ), troopTarget, slots - 2 ); } } } } void RedistributeTroopToFirstFreeSlot( ArmyTroop & troopFrom, const Army * armyTarget, const uint32_t count ) { // can't split up a stack with just 1 unit, and obviously on count == 0, there's no splitting at all if ( troopFrom.GetCount() <= 1 || count == 0 ) return; const size_t freeSlots = armyTarget->Size() - armyTarget->GetOccupiedSlotCount(); if ( freeSlots == 0 ) return; armyTarget->AssignToFirstFreeSlot( troopFrom, count ); troopFrom.SetCount( troopFrom.GetCount() - count ); } void RedistributeTroopByOne( ArmyTroop & troopFrom, const Army * armyTarget ) { RedistributeTroopToFirstFreeSlot( troopFrom, armyTarget, 1 ); } void RedistributeTroopEvenly( ArmyTroop & troopFrom, const Army * armyTarget ) { RedistributeTroopToFirstFreeSlot( troopFrom, armyTarget, troopFrom.GetCount() / 2 ); } bool IsSplitHotkeyUsed( ArmyTroop & troopFrom, const Army * armyTarget ) { if ( Game::HotKeyHoldEvent( Game::HotKeyEvent::ARMY_SPLIT_STACK_BY_ONE ) ) { RedistributeTroopByOne( troopFrom, armyTarget ); return true; } if ( Game::HotKeyHoldEvent( Game::HotKeyEvent::ARMY_JOIN_STACKS ) ) { armyTarget->JoinAllTroopsOfType( troopFrom ); return true; } if ( Game::HotKeyHoldEvent( Game::HotKeyEvent::ARMY_SPLIT_STACK_BY_HALF ) ) { RedistributeTroopEvenly( troopFrom, armyTarget ); return true; } return false; } } ArmyBar::ArmyBar( Army * ptr, bool mini, bool ro, bool change /* false */ ) : spcursor( fheroes2::AGG::GetICN( ICN::STRIP, 1 ) ) , _army( nullptr ) , use_mini_sprite( mini ) , read_only( ro ) , can_change( change ) , _troopWindowOffsetY( 0 ) { if ( use_mini_sprite ) SetBackground( { 43, 43 }, fheroes2::GetColorId( 0, 45, 0 ) ); else { const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::STRIP, 2 ); setSingleItemSize( { sprite.width(), sprite.height() } ); } SetArmy( ptr ); } void ArmyBar::SetArmy( Army * ptr ) { if ( _army && isSelected() ) ResetSelected(); _army = ptr; items.clear(); if ( ptr ) { for ( uint32_t ii = 0; ii < ptr->Size(); ++ii ) { ArmyTroop * troop = dynamic_cast( ptr->GetTroop( ii ) ); assert( troop != nullptr ); items.push_back( troop ); } } SetContentItems(); } bool ArmyBar::isValid() const { return _army != nullptr; } void ArmyBar::SetBackground( const fheroes2::Size & sz, const uint8_t fillColor ) { if ( !use_mini_sprite ) { // Nothing to draw. return; } setSingleItemSize( sz ); backsf.resize( sz.width, sz.height ); backsf.fill( fillColor ); fheroes2::DrawBorder( backsf, fheroes2::GetColorId( 0xd0, 0xc0, 0x48 ) ); spcursor.resize( sz.width, sz.height ); spcursor.reset(); fheroes2::DrawBorder( spcursor, 214 ); } void ArmyBar::RedrawBackground( const fheroes2::Rect & pos, fheroes2::Image & dstsf ) { if ( use_mini_sprite ) fheroes2::Blit( backsf, dstsf, pos.x, pos.y ); else fheroes2::Blit( fheroes2::AGG::GetICN( ICN::STRIP, 2 ), dstsf, pos.x, pos.y ); } void ArmyBar::RedrawItem( ArmyTroop & troop, const fheroes2::Rect & pos, bool selected, fheroes2::Image & dstsf ) { if ( !troop.isValid() ) { // Nothing to draw. return; } const fheroes2::Text text( std::to_string( troop.GetCount() ), use_mini_sprite ? fheroes2::FontType::smallWhite() : fheroes2::FontType::normalWhite() ); if ( use_mini_sprite ) { const fheroes2::Sprite & mons32 = fheroes2::AGG::GetICN( ICN::MONS32, troop.GetSpriteIndex() ); fheroes2::Rect srcrt( 0, 0, mons32.width(), mons32.height() ); if ( mons32.width() > pos.width ) { srcrt.x = ( mons32.width() - pos.width ) / 2; srcrt.width = pos.width; } if ( mons32.height() > pos.height ) { srcrt.y = ( mons32.height() - pos.height ) / 2; srcrt.height = pos.height; } fheroes2::Blit( mons32, srcrt.x, srcrt.y, dstsf, pos.x + ( pos.width - mons32.width() ) / 2, pos.y + pos.height - mons32.height() - 1, srcrt.width, srcrt.height ); text.draw( pos.x + pos.width - text.width() - 3, pos.y + pos.height - text.height() + 2, dstsf ); } else { fheroes2::renderMonsterFrame( troop, dstsf, pos.getPosition() ); text.draw( pos.x + pos.width - text.width() - 3, pos.y + pos.height - text.height() + 1, dstsf ); } if ( selected ) { spcursor.setPosition( pos.x, pos.y ); spcursor.show(); } } void ArmyBar::ResetSelected() { spcursor.hide(); Interface::ItemsActionBar::ResetSelected(); } void ArmyBar::Redraw( fheroes2::Image & dstsf ) { spcursor.hide(); Interface::ItemsActionBar::Redraw( dstsf ); } bool ArmyBar::ActionBarCursor( ArmyTroop & troop ) { if ( troop.isValid() && !read_only && LocalEvent::Get().MouseClickMiddle() ) { RedistributeTroopByOne( troop, _army ); return true; } if ( isSelected() ) { const ArmyTroop * troop2 = GetSelectedItem(); assert( troop2 != nullptr ); if ( &troop == troop2 ) { msg = _( "View %{name}" ); StringReplaceWithLowercase( msg, "%{name}", troop.GetName() ); } else if ( !troop.isValid() ) { if ( !read_only ) { if ( troop2->GetCount() == 1 ) { msg = _( "Move the %{name} " ); } else { msg = _( "Move or right click to redistribute %{name}" ); } StringReplaceWithLowercase( msg, "%{name}", troop2->GetName() ); } } else if ( troop.GetID() == troop2->GetID() ) { if ( !read_only ) { msg = _( "Combine %{name} armies" ); StringReplaceWithLowercase( msg, "%{name}", troop.GetName() ); } } else if ( !read_only ) { msg = _( "Exchange %{name2} with %{name}" ); StringReplaceWithLowercase( msg, "%{name}", troop.GetName() ); StringReplaceWithLowercase( msg, "%{name2}", troop2->GetName() ); } } else if ( troop.isValid() ) { msg = _( "Select %{name}" ); StringReplaceWithLowercase( msg, "%{name}", troop.GetName() ); } return false; } bool ArmyBar::ActionBarCursor( ArmyTroop & destTroop, ArmyTroop & selectedTroop ) { bool save_last_troop = ( selectedTroop.GetArmy()->getTotalCount() <= 1 ) && selectedTroop.GetArmy()->SaveLastTroop(); if ( destTroop.isValid() ) { if ( destTroop.GetID() != selectedTroop.GetID() ) { msg = _( "Exchange %{name2} with %{name}" ); StringReplaceWithLowercase( msg, "%{name}", destTroop.GetName() ); StringReplaceWithLowercase( msg, "%{name2}", selectedTroop.GetName() ); } else if ( save_last_troop ) msg = _( "Cannot move last troop" ); else { msg = _( "Combine %{name} armies" ); StringReplaceWithLowercase( msg, "%{name}", destTroop.GetName() ); } } else if ( save_last_troop ) msg = _( "Cannot move last troop" ); else { if ( selectedTroop.GetCount() == 1 ) { msg = _( "Move the %{name}" ); } else { msg = _( "Move or right click to redistribute %{name}" ); } StringReplaceWithLowercase( msg, "%{name}", selectedTroop.GetName() ); } return false; } bool ArmyBar::ActionBarLeftMouseSingleClick( ArmyTroop & troop ) { if ( isSelected() ) { if ( read_only ) { return false; // reset cursor } ArmyTroop * selectedTroop = GetSelectedItem(); assert( selectedTroop != nullptr ); const bool isSameTroopType = troop.isValid() && troop.GetID() == selectedTroop->GetID(); // prioritize standard split via shift hotkey if ( ( !troop.isValid() || isSameTroopType ) && Game::HotKeyHoldEvent( Game::HotKeyEvent::ARMY_SPLIT_STACK_BY_HALF ) ) { RedistributeArmy( *selectedTroop, troop, _army ); ResetSelected(); } else if ( selectedTroop && isSameTroopType ) { if ( IsSplitHotkeyUsed( troop, _army ) ) { return false; } else { // combine troop.SetCount( troop.GetCount() + selectedTroop->GetCount() ); selectedTroop->Reset(); } } // exchange else if ( selectedTroop ) { // count this as an attempt to split to a troop type that is not the same if ( Game::HotKeyHoldEvent( Game::HotKeyEvent::ARMY_SPLIT_STACK_BY_HALF ) ) ResetSelected(); else if ( IsSplitHotkeyUsed( troop, _army ) ) return false; else Army::SwapTroops( troop, *selectedTroop ); } return false; // reset cursor } else if ( troop.isValid() ) { if ( !read_only ) // select { if ( IsSplitHotkeyUsed( troop, _army ) ) return false; spcursor.hide(); } } else { if ( can_change ) // add troop { int cur = Monster::UNKNOWN; if ( _army->GetCommander() ) switch ( _army->GetCommander()->GetRace() ) { case Race::KNGT: cur = Monster::PEASANT; break; case Race::BARB: cur = Monster::GOBLIN; break; case Race::SORC: cur = Monster::SPRITE; break; case Race::WRLK: cur = Monster::CENTAUR; break; case Race::WZRD: cur = Monster::HALFLING; break; case Race::NECR: cur = Monster::SKELETON; break; default: break; } const Monster mons = Dialog::selectMonster( cur ); if ( mons.isValid() ) { uint32_t count = 1; if ( Dialog::SelectCount( _( "Set Count" ), 1, 500000, count ) ) troop.Set( mons, count ); } } return false; } return true; } bool ArmyBar::ActionBarLeftMouseSingleClick( ArmyTroop & destTroop, ArmyTroop & selectedTroop ) { const bool isSameTroopType = destTroop.isValid() && destTroop.GetID() == selectedTroop.GetID(); // specifically for shift hotkey, handle this logic before anything else // this will ensure that clicking on a different troop type while shift key is pressed will not show the split dialog, which can be ambiguous if ( Game::HotKeyHoldEvent( Game::HotKeyEvent::ARMY_SPLIT_STACK_BY_HALF ) ) { if ( destTroop.isEmpty() || isSameTroopType ) { RedistributeArmy( selectedTroop, destTroop, _army ); ResetSelected(); } return false; } if ( !destTroop.isEmpty() ) { // try to do hotkey-based splitting (except for shift, handled above) if ( IsSplitHotkeyUsed( destTroop, _army ) ) return false; if ( !isSameTroopType ) { Army::SwapTroops( destTroop, selectedTroop ); } // destination troop has units and both troops are the same creature type else { if ( selectedTroop.GetArmy()->SaveLastTroop() ) { // this is their army's only troop // move all but one units to destination destTroop.SetCount( destTroop.GetCount() + selectedTroop.GetCount() - 1 ); // leave a single unit behind selectedTroop.SetCount( 1 ); } else { // source has other troops // move all troops to the destination slot destTroop.SetCount( destTroop.GetCount() + selectedTroop.GetCount() ); // empty the source slot selectedTroop.Reset(); } } } else { // destination troop is empty, source army would be emptied by moving all if ( selectedTroop.GetArmy()->SaveLastTroop() ) { // move all but one units into the empty destination slot destTroop.Set( selectedTroop, selectedTroop.GetCount() - 1 ); selectedTroop.SetCount( 1 ); } // no risk of emptying selected troop's army, swap the troops else { Army::SwapTroops( destTroop, selectedTroop ); } } return false; // reset cursor } bool ArmyBar::ActionBarLeftMouseDoubleClick( ArmyTroop & troop ) { if ( troop.isValid() && !read_only && IsSplitHotkeyUsed( troop, _army ) ) { ResetSelected(); return true; } const ArmyTroop * troop2 = GetSelectedItem(); assert( troop2 != nullptr ); if ( &troop == troop2 ) { int flags = Dialog::BUTTONS; if ( !read_only ) { if ( !_army->SaveLastTroop() ) { flags |= Dialog::DISMISS; } if ( troop.isAllowUpgrade() ) { const Castle * castle = _army->inCastle(); if ( castle && castle->GetRace() == troop.GetRace() && castle->isBuild( troop.GetUpgrade().GetDwelling() ) ) { flags |= Dialog::UPGRADE; if ( !world.GetKingdom( _army->GetColor() ).AllowPayment( troop.GetTotalUpgradeCost() ) ) { flags |= Dialog::UPGRADE_DISABLE; } } } } switch ( Dialog::ArmyInfo( troop, flags, false, _troopWindowOffsetY ) ) { case Dialog::UPGRADE: // If this assertion blows up then you are executing this code for a monster which has no upgrades. assert( troop.isAllowUpgrade() ); world.GetKingdom( _army->GetColor() ).OddFundsResource( troop.GetTotalUpgradeCost() ); troop.Upgrade(); break; case Dialog::DISMISS: troop.Reset(); break; default: break; } } ResetSelected(); return true; } bool ArmyBar::ActionBarLeftMouseRelease( ArmyTroop & troop ) { if ( read_only ) { return true; } ArmyTroop * srcTroop = GetItem( LocalEvent::Get().GetMousePressLeft() ); if ( srcTroop == nullptr || !srcTroop->isValid() ) { return true; } if ( &troop == srcTroop ) { return true; } if ( troop.isValid() && troop.GetID() != srcTroop->GetID() ) { return true; } RedistributeArmy( *srcTroop, troop, _army ); if ( isSelected() ) { ResetSelected(); } return true; } bool ArmyBar::ActionBarLeftMouseRelease( ArmyTroop & destTroop, ArmyTroop & selectedTroop ) { assert( !read_only ); if ( isSelected() ) { ResetSelected(); } if ( !selectedTroop.isValid() ) { return false; } if ( destTroop.isValid() && selectedTroop.GetID() != destTroop.GetID() ) { return false; } assert( &selectedTroop != &destTroop ); RedistributeArmy( selectedTroop, destTroop, _army ); return true; } bool ArmyBar::ActionBarRightMouseHold( ArmyTroop & troop ) { // don't handle this event if we are going to call RedistributeArmy() on right mouse button click if ( AbleToRedistributeArmyOnRightMouseSingleClick( troop ) ) { return false; } if ( troop.isValid() ) { ResetSelected(); if ( can_change && !_army->SaveLastTroop() ) { troop.Reset(); } else { Dialog::ArmyInfo( troop, Dialog::ZERO, false, _troopWindowOffsetY ); } } return true; } bool ArmyBar::ActionBarRightMouseSingleClick( ArmyTroop & troop ) { if ( AbleToRedistributeArmyOnRightMouseSingleClick( troop ) ) { ArmyTroop * selectedTroop = GetSelectedItem(); assert( selectedTroop != nullptr ); RedistributeArmy( *selectedTroop, troop, _army ); ResetSelected(); return true; } return false; } bool ArmyBar::ActionBarRightMouseSingleClick( ArmyTroop & destTroop, ArmyTroop & selectedTroop ) { if ( !destTroop.isValid() || destTroop.GetID() == selectedTroop.GetID() ) { RedistributeArmy( selectedTroop, destTroop, _army ); ResetSelected(); } return true; } bool ArmyBar::QueueEventProcessing( std::string * str ) { msg.clear(); bool res = Interface::ItemsActionBar::QueueEventProcessing(); if ( str ) *str = msg; return res; } bool ArmyBar::QueueEventProcessing( ArmyBar & bar, std::string * str ) { msg.clear(); bool res = Interface::ItemsActionBar::QueueEventProcessing( bar ); if ( str ) *str = msg; return res; } bool ArmyBar::AbleToRedistributeArmyOnRightMouseSingleClick( const ArmyTroop & troop ) { if ( read_only ) { return false; } // try to redistribute troops if we have a selected troop if ( !isSelected() ) { return false; } const ArmyTroop * selectedTroop = GetSelectedItem(); assert( selectedTroop != nullptr ); // prevent troop from splitting into its own stack by checking against their pointers if ( &troop == selectedTroop ) { return false; } // we can redistribute troops either to an empty slot or to a slot containing the same creatures return !troop.isValid() || selectedTroop->GetID() == troop.GetID(); } fheroes2-1.0.12+dfsg/src/fheroes2/army/army_bar.h000066400000000000000000000074201456075706000214650ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2012 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2ARMYBAR_H #define H2ARMYBAR_H #include #include #include "image.h" #include "interface_itemsbar.h" #include "math_base.h" #include "ui_tool.h" class Army; class ArmyTroop; class ArmyBar : public Interface::ItemsActionBar { public: using Interface::ItemsActionBar::RedrawItem; using Interface::ItemsActionBar::ActionBarRightMouseHold; ArmyBar( Army *, bool mini, bool ro, bool change = false ); void RedrawBackground( const fheroes2::Rect &, fheroes2::Image & ) override; void RedrawItem( ArmyTroop &, const fheroes2::Rect &, bool, fheroes2::Image & ) override; void SetBackground( const fheroes2::Size & sz, const uint8_t fillColor ); void SetArmy( Army * ); void setTroopWindowOffsetY( const int32_t offsetY ) { _troopWindowOffsetY = offsetY; } bool isValid() const; void ResetSelected(); void Redraw( fheroes2::Image & dstsf ); bool ActionBarLeftMouseSingleClick( ArmyTroop & troop ) override; bool ActionBarLeftMouseSingleClick( ArmyTroop & destTroop, ArmyTroop & selectedTroop ) override; bool ActionBarLeftMouseDoubleClick( ArmyTroop & troop ) override; bool ActionBarLeftMouseRelease( ArmyTroop & troop ) override; bool ActionBarLeftMouseRelease( ArmyTroop & destTroop, ArmyTroop & troop ) override; bool ActionBarRightMouseHold( ArmyTroop & troop ) override; bool ActionBarRightMouseSingleClick( ArmyTroop & troop ) override; bool ActionBarRightMouseSingleClick( ArmyTroop & destTroop, ArmyTroop & selectedTroop ) override; bool ActionBarCursor( ArmyTroop & ) override; bool ActionBarCursor( ArmyTroop &, ArmyTroop & ) override; bool QueueEventProcessing( std::string * = nullptr ); bool QueueEventProcessing( ArmyBar &, std::string * = nullptr ); protected: fheroes2::MovableSprite spcursor; private: bool AbleToRedistributeArmyOnRightMouseSingleClick( const ArmyTroop & troop ); Army * _army; fheroes2::Image backsf; bool use_mini_sprite; bool read_only; bool can_change; std::string msg; int32_t _troopWindowOffsetY; }; #endif fheroes2-1.0.12+dfsg/src/fheroes2/army/army_troop.cpp000066400000000000000000000141461456075706000224220ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "army_troop.h" #include "army.h" #include "color.h" #include "heroes_base.h" #include "resource.h" #include "serialize.h" #include "speed.h" Troop::Troop() : Monster( Monster::UNKNOWN ) , count( 0 ) {} Troop::Troop( const Monster & m, uint32_t c ) : Monster( m ) , count( c ) {} bool Troop::operator==( const Monster & m ) const { return Monster::operator==( m ); } bool Troop::isMonster( int mons ) const { return GetID() == mons; } Monster Troop::GetMonster() const { return Monster( id ); } void Troop::Set( const Troop & t ) { SetMonster( t.GetMonster() ); SetCount( t.GetCount() ); } void Troop::Set( const Monster & m, uint32_t c ) { Set( Troop( m, c ) ); } void Troop::SetMonster( const Monster & m ) { id = m.GetID(); } void Troop::SetCount( uint32_t c ) { count = c; } void Troop::Reset() { id = Monster::UNKNOWN; count = 0; } const char * Troop::GetName() const { return Monster::GetPluralName( count ); } uint32_t Troop::GetCount() const { return count; } uint32_t Troop::GetHitPoints() const { return Monster::GetHitPoints() * count; } uint32_t Troop::GetDamageMin() const { return Monster::GetDamageMin() * count; } uint32_t Troop::GetDamageMax() const { return Monster::GetDamageMax() * count; } double Troop::GetStrength() const { return Monster::GetMonsterStrength() * count; } double Troop::GetStrengthWithBonus( int bonusAttack, int bonusDefense ) const { return Monster::GetMonsterStrength( Monster::GetAttack() + bonusAttack, Monster::GetDefense() + bonusDefense ) * count; } bool Troop::isValid() const { return Monster::isValid() && count; } bool Troop::isEmpty() const { return !isValid(); } Funds Troop::GetTotalCost() const { return GetCost() * count; } Funds Troop::GetTotalUpgradeCost() const { return GetUpgradeCost() * count; } bool Troop::isBattle() const { return false; } bool Troop::isModes( uint32_t /* unused */ ) const { return false; } std::string Troop::GetAttackString() const { return std::to_string( GetAttack() ); } std::string Troop::GetDefenseString() const { return std::to_string( GetDefense() ); } std::string Troop::GetShotString() const { return std::to_string( GetShots() ); } std::string Troop::GetSpeedString() const { return GetSpeedString( GetSpeed() ); } std::string Troop::GetSpeedString( uint32_t speed ) { std::string output( Speed::String( static_cast( speed ) ) ); output += " ("; output += std::to_string( speed ); output += ')'; return output; } uint32_t Troop::GetHitPointsLeft() const { return 0; } uint32_t Troop::GetSpeed() const { return Monster::GetSpeed(); } uint32_t Troop::GetAffectedDuration( uint32_t /* unused */ ) const { return 0; } ArmyTroop::ArmyTroop( const Army * a ) : army( a ) {} ArmyTroop::ArmyTroop( const Army * a, const Troop & t ) : Troop( t ) , army( a ) {} uint32_t ArmyTroop::GetAttack() const { return Troop::GetAttack() + ( army && army->GetCommander() ? army->GetCommander()->GetAttack() : 0 ); } uint32_t ArmyTroop::GetDefense() const { return Troop::GetDefense() + ( army && army->GetCommander() ? army->GetCommander()->GetDefense() : 0 ); } int ArmyTroop::GetColor() const { return army ? army->GetColor() : Color::NONE; } int ArmyTroop::GetMorale() const { return army && isAffectedByMorale() ? army->GetMorale() : Troop::GetMorale(); } int ArmyTroop::GetLuck() const { return army ? army->GetLuck() : Troop::GetLuck(); } void ArmyTroop::SetArmy( const Army & a ) { army = &a; } const Army * ArmyTroop::GetArmy() const { return army; } std::string ArmyTroop::GetAttackString() const { if ( Troop::GetAttack() == GetAttack() ) return std::to_string( Troop::GetAttack() ); std::string output( std::to_string( Troop::GetAttack() ) ); output += " ("; output += std::to_string( GetAttack() ); output += ')'; return output; } std::string ArmyTroop::GetDefenseString() const { if ( Troop::GetDefense() == GetDefense() ) return std::to_string( Troop::GetDefense() ); std::string output( std::to_string( Troop::GetDefense() ) ); output += " ("; output += std::to_string( GetDefense() ); output += ')'; return output; } StreamBase & operator<<( StreamBase & msg, const Troop & troop ) { return msg << troop.id << troop.count; } StreamBase & operator>>( StreamBase & msg, Troop & troop ) { return msg >> troop.id >> troop.count; } fheroes2-1.0.12+dfsg/src/fheroes2/army/army_troop.h000066400000000000000000000101151456075706000220570ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2ARMYTROOP_H #define H2ARMYTROOP_H #include #include #include "monster.h" #include "resource.h" class StreamBase; class Army; class Troop : public Monster { public: Troop(); Troop( const Monster &, uint32_t ); bool operator==( const Monster & ) const; void Set( const Troop & ); void Set( const Monster &, uint32_t ); void SetMonster( const Monster & ); void SetCount( uint32_t ); void Reset(); bool isMonster( int ) const; const char * GetName() const; uint32_t GetCount() const; uint32_t GetHitPoints() const; Monster GetMonster() const; uint32_t GetDamageMin() const; uint32_t GetDamageMax() const; Funds GetTotalCost() const; // Returns the cost of an upgrade if a monster has an upgrade. Otherwise returns no resources. // IMPORTANT!!! Make sure that you call this method after checking by isAllowUpgrade() method. Funds GetTotalUpgradeCost() const; bool isEmpty() const; virtual bool isValid() const; virtual bool isBattle() const; virtual bool isModes( uint32_t ) const; virtual std::string GetAttackString() const; virtual std::string GetDefenseString() const; virtual std::string GetShotString() const; virtual std::string GetSpeedString() const; virtual uint32_t GetHitPointsLeft() const; virtual uint32_t GetSpeed() const; virtual uint32_t GetAffectedDuration( uint32_t ) const; double GetStrength() const; double GetStrengthWithBonus( int bonusAttack, int bonusDefense ) const; protected: friend StreamBase & operator<<( StreamBase &, const Troop & ); friend StreamBase & operator>>( StreamBase &, Troop & ); static std::string GetSpeedString( uint32_t speed ); uint32_t count; }; StreamBase & operator<<( StreamBase &, const Troop & ); StreamBase & operator>>( StreamBase &, Troop & ); class ArmyTroop : public Troop { public: explicit ArmyTroop( const Army * ); ArmyTroop( const Army *, const Troop & ); ArmyTroop & operator=( const Troop & ) = delete; uint32_t GetAttack() const override; uint32_t GetDefense() const override; int GetColor() const override; int GetMorale() const override; int GetLuck() const override; void SetArmy( const Army & ); const Army * GetArmy() const; std::string GetAttackString() const override; std::string GetDefenseString() const override; protected: const Army * army; }; #endif fheroes2-1.0.12+dfsg/src/fheroes2/army/army_ui_helper.cpp000066400000000000000000000121721456075706000232300ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "army_ui_helper.h" #include #include #include #include #include "agg_image.h" #include "army.h" #include "army_troop.h" #include "game.h" #include "icn.h" #include "image.h" #include "ui_text.h" void fheroes2::drawMiniMonsters( const Troops & troops, int32_t cx, const int32_t cy, const int32_t width, uint32_t first, uint32_t count, const bool isCompact, const bool isDetailedView, const bool isGarrisonView, const uint32_t thievesGuildsCount, Image & output ) { if ( !troops.isValid() ) { return; } bool isRightToLeftRender = false; if ( count == 0 ) { // If this function was called with 'count == 0' than the count is determined by the number of troop slots. count = troops.GetOccupiedSlotCount(); if ( first == 0 ) { // This is the case when we render all the troops and do it from left to right. // It is done to make troop sprites overlapping more appealing when troops are close to each other. // This case may occur if many troops were killed during the battle (lots of summoned elementals and/or mirror image troops). isRightToLeftRender = true; } } const double chunk = width / static_cast( count ); if ( !isCompact ) { cx += static_cast( chunk / 2 ); } if ( !isRightToLeftRender ) { cx += width; } const size_t slots = troops.Size(); for ( size_t slot = 0; slot < slots; ++slot ) { if ( count == 0 ) { // We have rendered the given count of troops. There is nothing more to do. break; } const Troop * troop = troops.GetTroop( isRightToLeftRender ? ( slots - slot - 1 ) : slot ); if ( troop == nullptr || !troop->isValid() ) { continue; } if ( first != 0 ) { --first; continue; } std::string monstersCountRepresentation; if ( isDetailedView || !isGarrisonView ) { monstersCountRepresentation = Game::formatMonsterCount( troop->GetCount(), isDetailedView, isCompact ); } else { assert( thievesGuildsCount > 0 ); if ( thievesGuildsCount == 1 ) { monstersCountRepresentation = "???"; } else { monstersCountRepresentation = Army::SizeString( troop->GetCount() ); } } const fheroes2::Sprite & monster = fheroes2::AGG::GetICN( ICN::MONS32, troop->GetSpriteIndex() ); const fheroes2::Text text( std::move( monstersCountRepresentation ), fheroes2::FontType::smallWhite() ); const int32_t posX = isRightToLeftRender ? ( cx + static_cast( chunk * ( count - 1 ) ) ) : ( cx - static_cast( chunk * count ) ); // This is the drawing of army troops in compact form in the small info window beneath resources if ( isCompact ) { const int32_t offsetY = ( monster.height() < 37 ) ? 37 - monster.height() : 0; int32_t offset = ( static_cast( chunk ) - monster.width() - text.width() ) / 2; if ( offset < 0 ) { offset = 0; } fheroes2::Blit( monster, output, posX + offset, cy + offsetY + monster.y() ); text.draw( posX - text.width() - offset + static_cast( chunk ), cy + 23, output ); } else { const int32_t offsetY = 28 - monster.height() + monster.y(); fheroes2::Blit( monster, output, posX - monster.width() / 2 + monster.x() + 2, cy + offsetY ); text.draw( posX - text.width() / 2, cy + 29, output ); } --count; } } fheroes2-1.0.12+dfsg/src/fheroes2/army/army_ui_helper.h000066400000000000000000000034641456075706000227010ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include class Troops; namespace fheroes2 { class Image; void drawMiniMonsters( const Troops & troops, int32_t cx, const int32_t cy, const int32_t width, uint32_t first, uint32_t count, const bool isCompact, const bool isDetailedView, const bool isGarrisonView, const uint32_t thievesGuildsCount, Image & output ); } fheroes2-1.0.12+dfsg/src/fheroes2/audio/000077500000000000000000000000001456075706000176465ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/fheroes2/audio/audio_manager.cpp000066400000000000000000001065101456075706000231500ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "audio_manager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "agg_file.h" #include "dir.h" #include "logging.h" #include "m82.h" #include "mus.h" #include "serialize.h" #include "settings.h" #include "system.h" #include "thread.h" #include "tools.h" #include "xmi.h" namespace { struct MusicFileType { explicit MusicFileType( const MUS::EXTERNAL_MUSIC_TYPE type_ ) : type( type_ ) { // Do nothing. } MUS::EXTERNAL_MUSIC_TYPE type = MUS::EXTERNAL_MUSIC_TYPE::WIN_VERSION; std::array extension{ ".ogg", ".mp3", ".flac" }; }; bool findMusicFile( const std::vector & directories, const std::string & fileName, std::string & fullPath ) { for ( const std::string & dir : directories ) { ListFiles musicFilePaths; musicFilePaths.ReadDir( dir, fileName, false ); if ( musicFilePaths.empty() ) { continue; } const std::string correctFilePath = StringLower( System::concatPath( dir, fileName ) ); for ( std::string & path : musicFilePaths ) { if ( StringLower( path ) != correctFilePath ) { continue; } // Avoid string copy. std::swap( fullPath, path ); return true; } } return false; } std::string getExternalMusicFile( const int musicTrackId ) { static const std::vector musicDirectories = []() { std::vector result; for ( const std::string & dir : Settings::GetRootDirs() ) { std::string fullDirectoryPath = System::concatPath( dir, "music" ); if ( System::IsDirectory( fullDirectoryPath ) ) { result.emplace_back( std::move( fullDirectoryPath ) ); } } return result; }(); if ( musicDirectories.empty() ) { // Nothing to search. return {}; } thread_local std::map musicTrackIdToFilePath; const auto iter = musicTrackIdToFilePath.find( musicTrackId ); if ( iter != musicTrackIdToFilePath.end() ) { return iter->second; } const auto tryMusicFileType = [musicTrackId]( MusicFileType & musicFileType ) -> std::string { std::string fullPath; std::string fileName = MUS::getFileName( musicTrackId, musicFileType.type, musicFileType.extension[0].c_str() ); if ( findMusicFile( musicDirectories, fileName, fullPath ) ) { return fullPath; } fheroes2::replaceStringEnding( fileName, musicFileType.extension[0].c_str(), musicFileType.extension[1].c_str() ); if ( findMusicFile( musicDirectories, fileName, fullPath ) ) { // Swap extensions to improve cache hit. std::swap( musicFileType.extension[0], musicFileType.extension[1] ); return fullPath; } fheroes2::replaceStringEnding( fileName, musicFileType.extension[1].c_str(), musicFileType.extension[2].c_str() ); if ( findMusicFile( musicDirectories, fileName, fullPath ) ) { // Swap extensions to improve cache hit. std::swap( musicFileType.extension[0], musicFileType.extension[2] ); return fullPath; } // Looks like music file does not exist. return {}; }; // To avoid extra I/O calls to data storage it might be useful to remember the last successful type of music and try to search for it next time. thread_local std::array musicFileTypes{ MusicFileType( MUS::EXTERNAL_MUSIC_TYPE::DOS_VERSION ), MusicFileType( MUS::EXTERNAL_MUSIC_TYPE::WIN_VERSION ), MusicFileType( MUS::EXTERNAL_MUSIC_TYPE::MAPPED ) }; for ( size_t i = 0; i < musicFileTypes.size(); ++i ) { std::string filePath = tryMusicFileType( musicFileTypes[i] ); if ( filePath.empty() ) { continue; } if ( i > 0 ) { // Swap music types to improve cache hit. std::swap( musicFileTypes[0], musicFileTypes[i] ); } // Place the path to the cache musicTrackIdToFilePath.try_emplace( musicTrackId, filePath ); return filePath; } // Place the negative result to the cache musicTrackIdToFilePath.try_emplace( musicTrackId ); return {}; } struct ChannelAudioLoopEffectInfo : public AudioManager::AudioLoopEffectInfo { ChannelAudioLoopEffectInfo( const AudioLoopEffectInfo & info, const int channelId_ ) : AudioLoopEffectInfo( info ) , channelId( channelId_ ) { // Do nothing. } bool operator==( const AudioManager::AudioLoopEffectInfo & other ) const { return AudioManager::AudioLoopEffectInfo::operator==( other ); } int channelId{ -1 }; }; std::vector getDataFromAggFile( const std::string & key, const bool ignoreExpansion ); void LoadWAV( int m82, std::vector & v ) { DEBUG_LOG( DBG_GAME, DBG_TRACE, M82::GetString( m82 ) ) const std::vector & body = getDataFromAggFile( M82::GetString( m82 ), false ); if ( !body.empty() ) { StreamBuf wavHeader( 44 ); wavHeader.putLE32( 0x46464952 ); // RIFF marker ("RIFF") wavHeader.putLE32( static_cast( body.size() ) + 0x24 ); // Total size minus the size of this and previous fields wavHeader.putLE32( 0x45564157 ); // File type header ("WAVE") wavHeader.putLE32( 0x20746D66 ); // Format sub-chunk marker ("fmt ") wavHeader.putLE32( 0x10 ); // Size of the format sub-chunk wavHeader.putLE16( 0x01 ); // Audio format (1 for PCM) wavHeader.putLE16( 0x01 ); // Number of channels wavHeader.putLE32( 22050 ); // Sample rate wavHeader.putLE32( 22050 ); // Byte rate (SampleRate * BitsPerSample * NumberOfChannels) / 8 wavHeader.putLE16( 0x01 ); // Block align (BitsPerSample * NumberOfChannels) / 8 wavHeader.putLE16( 0x08 ); // Bits per sample wavHeader.putLE32( 0x61746164 ); // Data sub-chunk marker ("data") wavHeader.putLE32( static_cast( body.size() ) ); // Size of the data sub-chunk v.reserve( body.size() + 44 ); v.assign( wavHeader.data(), wavHeader.data() + 44 ); v.insert( v.begin() + 44, body.begin(), body.end() ); } } void LoadMID( int xmi, std::vector & v ) { DEBUG_LOG( DBG_GAME, DBG_TRACE, XMI::GetString( xmi ) ) const std::vector & body = getDataFromAggFile( XMI::GetString( xmi ), xmi >= XMI::MIDI_ORIGINAL_KNIGHT ); if ( !body.empty() ) { v = Music::Xmi2Mid( body ); } } std::map> wavDataCache; std::map> MIDDataCache; const std::vector & GetWAV( int m82 ) { std::vector & v = wavDataCache[m82]; if ( v.empty() ) { LoadWAV( m82, v ); } return v; } const std::vector & GetMID( int xmi ) { std::vector & v = MIDDataCache[xmi]; if ( v.empty() ) { LoadMID( xmi, v ); } return v; } // Returns sound Channel ID, when error - returns `-1`. int PlaySoundImp( const int m82, const int soundVolume ); void PlayMusicImp( const int trackId, const MusicSource musicType, const Music::PlaybackMode playbackMode ); void playLoopSoundsImp( std::map> soundEffects, const int soundVolume, const bool is3DAudioEnabled ); // SDL MIDI player is a single threaded library which requires a lot of time to start playing some long midi compositions. // This leads to a situation of a short application freeze while a hero crosses terrains or ending a battle. // The only way to avoid this is to fire MIDI requests asynchronously and synchronize them if needed. class AsyncSoundManager final : public MultiThreading::AsyncManager { public: void pushMusic( const int musicId, const MusicSource musicType, const Music::PlaybackMode playbackMode ) { createWorker(); const std::scoped_lock lock( _mutex ); _musicTask.emplace( musicId, musicType, playbackMode ); notifyWorker(); } void pushSound( const int m82Sound, const int soundVolume ) { createWorker(); const std::scoped_lock lock( _mutex ); _soundTasks.emplace_back( m82Sound, soundVolume ); notifyWorker(); } void pushLoopSound( std::map> vols, const int soundVolume, const bool is3DAudioEnabled ) { createWorker(); const std::scoped_lock lock( _mutex ); _loopSoundTask.emplace( std::move( vols ), soundVolume, is3DAudioEnabled ); notifyWorker(); } void removeMusicTask() { const std::scoped_lock lock( _mutex ); _musicTask.reset(); if ( _taskToExecute == TaskType::PlayMusic ) { _taskToExecute = TaskType::None; } } void removeSoundTasks() { const std::scoped_lock lock( _mutex ); _soundTasks.clear(); if ( _taskToExecute == TaskType::PlaySound ) { _taskToExecute = TaskType::None; } } void removeAllSoundTasks() { const std::scoped_lock lock( _mutex ); _soundTasks.clear(); _loopSoundTask.reset(); switch ( _taskToExecute ) { case TaskType::PlaySound: case TaskType::PlayLoopSound: _taskToExecute = TaskType::None; break; default: break; } } void removeAllTasks() { const std::scoped_lock lock( _mutex ); _musicTask.reset(); _soundTasks.clear(); _loopSoundTask.reset(); _taskToExecute = TaskType::None; } // This mutex protects operations with AudioManager's resources, such as AGG files, data caches, etc std::recursive_mutex & resourceMutex() { return _resourceMutex; } private: enum class TaskType : int { None, PlayMusic, PlaySound, PlayLoopSound }; struct MusicTask { MusicTask() = default; MusicTask( const int musicId_, const MusicSource musicType_, const Music::PlaybackMode playbackMode_ ) : musicId( musicId_ ) , musicType( musicType_ ) , playbackMode( playbackMode_ ) { // Do nothing. } int musicId{ 0 }; MusicSource musicType{ MUSIC_MIDI_ORIGINAL }; Music::PlaybackMode playbackMode{ Music::PlaybackMode::PLAY_ONCE }; }; struct SoundTask { SoundTask() = default; SoundTask( const int m82Sound_, const int soundVolume_ ) : m82Sound( m82Sound_ ) , soundVolume( soundVolume_ ) { // Do nothing. } int m82Sound{ 0 }; int soundVolume{ 0 }; }; struct LoopSoundTask { LoopSoundTask() = default; LoopSoundTask( std::map> effects, const int soundVolume_, const bool is3DAudioOn ) : soundEffects( std::move( effects ) ) , soundVolume( soundVolume_ ) , is3DAudioEnabled( is3DAudioOn ) { // Do nothing. } std::map> soundEffects; int soundVolume{ 0 }; bool is3DAudioEnabled{ false }; }; std::optional _musicTask; std::deque _soundTasks; std::optional _loopSoundTask; MusicTask _currentMusicTask; SoundTask _currentSoundTask; LoopSoundTask _currentLoopSoundTask; std::atomic _taskToExecute{ TaskType::None }; std::recursive_mutex _resourceMutex; // This method is called by the worker thread and is protected by _mutex bool prepareTask() override { if ( _musicTask ) { std::swap( _currentMusicTask, *_musicTask ); _musicTask.reset(); _taskToExecute = TaskType::PlayMusic; return true; } if ( !_soundTasks.empty() ) { std::swap( _currentSoundTask, _soundTasks.front() ); _soundTasks.pop_front(); _taskToExecute = TaskType::PlaySound; return true; } if ( _loopSoundTask ) { std::swap( _currentLoopSoundTask, *_loopSoundTask ); _loopSoundTask.reset(); _taskToExecute = TaskType::PlayLoopSound; return true; } _taskToExecute = TaskType::None; return false; } // This method is called by the worker thread, but is not protected by _mutex void executeTask() override { // Do not allow the main thread to acquire this mutex in the interval between the // _taskToExecute was checked and the task was started executing. Release it only // when the task is fully completed. const std::scoped_lock lock( _resourceMutex ); switch ( _taskToExecute ) { case TaskType::None: // Nothing to do. return; case TaskType::PlayMusic: PlayMusicImp( _currentMusicTask.musicId, _currentMusicTask.musicType, _currentMusicTask.playbackMode ); return; case TaskType::PlaySound: PlaySoundImp( _currentSoundTask.m82Sound, _currentSoundTask.soundVolume ); return; case TaskType::PlayLoopSound: playLoopSoundsImp( std::move( _currentLoopSoundTask.soundEffects ), _currentLoopSoundTask.soundVolume, _currentLoopSoundTask.is3DAudioEnabled ); return; default: // How is it even possible? Did you add a new task? assert( 0 ); break; } } }; std::map> currentAudioLoopEffects; bool is3DAudioLoopEffectsEnabled{ false }; // The music track last requested to be played int lastRequestedMusicTrackId{ MUS::UNKNOWN }; // The music track that is currently being played int currentMusicTrackId{ MUS::UNKNOWN }; fheroes2::AGGFile g_midiHeroes2AGG; fheroes2::AGGFile g_midiHeroes2xAGG; std::vector getDataFromAggFile( const std::string & key, const bool ignoreExpansion ) { if ( !ignoreExpansion && g_midiHeroes2xAGG.isGood() ) { // Make sure that the below container is not const and not a reference // so returning it from the function will invoke a move constructor instead of copy constructor. std::vector buf = g_midiHeroes2xAGG.read( key ); if ( !buf.empty() ) return buf; } return g_midiHeroes2AGG.read( key ); } AsyncSoundManager g_asyncSoundManager; int PlaySoundImp( const int m82, const int soundVolume ) { const std::scoped_lock lock( g_asyncSoundManager.resourceMutex() ); DEBUG_LOG( DBG_GAME, DBG_TRACE, "Try to play sound " << M82::GetString( m82 ) ) const std::vector & v = GetWAV( m82 ); if ( v.empty() ) { return -1; } const int channelId = Mixer::Play( &v[0], static_cast( v.size() ), -1, false ); if ( channelId < 0 ) { // Failed to get a free channel. return -1; } Mixer::Pause( channelId ); Mixer::setVolume( channelId, 100 * soundVolume / 10 ); Mixer::Resume( channelId ); return channelId; } uint64_t getMusicUID( const int trackId, const MusicSource musicType ) { static_assert( MUS::UNUSED == 0, "Why are you changing this value?" ); assert( trackId != MUS::UNUSED && trackId != MUS::UNKNOWN ); return ( static_cast( musicType ) << 32 ) + static_cast( trackId ); } void PlayMusicImp( const int trackId, const MusicSource musicType, const Music::PlaybackMode playbackMode ) { // Make sure that the music track is valid. assert( trackId != MUS::UNUSED && trackId != MUS::UNKNOWN ); const uint64_t musicUID = getMusicUID( trackId, musicType ); const std::scoped_lock lock( g_asyncSoundManager.resourceMutex() ); if ( currentMusicTrackId == trackId && Music::isPlaying() ) { return; } // Check if the music track is already available in the music database. if ( Music::Play( musicUID, playbackMode ) ) { DEBUG_LOG( DBG_GAME, DBG_TRACE, "Play music track " << trackId ) currentMusicTrackId = trackId; return; } if ( musicType == MUSIC_EXTERNAL ) { const std::string filePath = getExternalMusicFile( trackId ); if ( filePath.empty() ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Cannot find a file for " << trackId << " track." ) } else { Music::Play( musicUID, filePath, playbackMode ); currentMusicTrackId = trackId; DEBUG_LOG( DBG_GAME, DBG_TRACE, "Play music track " << MUS::getFileName( trackId, MUS::EXTERNAL_MUSIC_TYPE::MAPPED, " " ) ) return; } } int xmi = XMI::UNKNOWN; // Check if music needs to be pulled from HEROES2X if ( musicType == MUSIC_MIDI_EXPANSION ) { xmi = XMI::FromMUS( trackId, g_midiHeroes2xAGG.isGood() ); } if ( XMI::UNKNOWN == xmi ) { xmi = XMI::FromMUS( trackId, false ); } if ( XMI::UNKNOWN != xmi ) { const std::vector & v = GetMID( xmi ); if ( !v.empty() ) { Music::Play( musicUID, v, playbackMode ); currentMusicTrackId = trackId; } } DEBUG_LOG( DBG_GAME, DBG_TRACE, "Play MIDI music track " << XMI::GetString( xmi ) ) } void getClosestSoundIdPairByAngle( const std::vector & soundToAdd, const std::vector & soundToReplace, size_t & bestSoundToAddId, size_t & bestSoundToReplaceId ) { assert( !soundToAdd.empty() && !soundToReplace.empty() ); bestSoundToAddId = 0; bestSoundToReplaceId = 0; int bestAngleDiff = 360; int bestVolumeDiff = 100; for ( size_t soundToAddId = 0; soundToAddId < soundToAdd.size(); ++soundToAddId ) { for ( size_t soundToReplaceId = 0; soundToReplaceId < soundToReplace.size(); ++soundToReplaceId ) { const int angleDiff = std::abs( soundToAdd[soundToAddId].angle - soundToReplace[soundToReplaceId].angle ); const int volumeDiff = std::abs( static_cast( soundToAdd[soundToAddId].volumePercentage ) - static_cast( soundToReplace[soundToReplaceId].volumePercentage ) ); if ( bestAngleDiff >= angleDiff ) { if ( bestAngleDiff == angleDiff && volumeDiff > bestVolumeDiff ) { // The existing best pair has lower volume difference. continue; } bestAngleDiff = angleDiff; bestVolumeDiff = volumeDiff; bestSoundToAddId = soundToAddId; bestSoundToReplaceId = soundToReplaceId; } } } } void clearAllAudioLoopEffects() { for ( const auto & audioEffectPair : currentAudioLoopEffects ) { const std::vector & existingEffects = audioEffectPair.second; for ( const ChannelAudioLoopEffectInfo & info : existingEffects ) { if ( Mixer::isPlaying( info.channelId ) ) { Mixer::setVolume( info.channelId, 0 ); Mixer::Stop( info.channelId ); } } } currentAudioLoopEffects.clear(); } void playLoopSoundsImp( std::map> soundEffects, const int soundVolume, const bool is3DAudioEnabled ) { const std::scoped_lock lock( g_asyncSoundManager.resourceMutex() ); if ( soundVolume == 0 ) { // The volume is 0. Remove all existing sound effects. clearAllAudioLoopEffects(); return; } if ( is3DAudioLoopEffectsEnabled != is3DAudioEnabled ) { is3DAudioLoopEffectsEnabled = is3DAudioEnabled; clearAllAudioLoopEffects(); } // TODO: use another container for sound effects to support more efficient sort and find operations based on the code below. std::map> tempAudioLoopEffects; std::swap( tempAudioLoopEffects, currentAudioLoopEffects ); // Remove all sounds which aren't currently played anymore. This might be the case when Audio::Stop() function is called. for ( auto iter = tempAudioLoopEffects.begin(); iter != tempAudioLoopEffects.end(); ) { std::vector & existingEffects = iter->second; for ( auto effectIter = existingEffects.begin(); effectIter != existingEffects.end(); ) { if ( !Mixer::isPlaying( effectIter->channelId ) ) { effectIter = existingEffects.erase( effectIter ); } else { ++effectIter; } } if ( existingEffects.empty() ) { iter = tempAudioLoopEffects.erase( iter ); } else { ++iter; } } // First find channels with existing sounds and just update them. for ( auto iter = soundEffects.begin(); iter != soundEffects.end(); ) { const M82::SoundType soundType = iter->first; std::vector & effectsToAdd = iter->second; assert( !effectsToAdd.empty() ); auto foundSoundTypeIter = tempAudioLoopEffects.find( soundType ); if ( foundSoundTypeIter == tempAudioLoopEffects.end() ) { // This sound type does not exist. ++iter; continue; } std::vector & effectsToReplace = foundSoundTypeIter->second; // Search for an existing sound which has the exact volume and angle. for ( auto soundToAddIter = effectsToAdd.begin(); soundToAddIter != effectsToAdd.end(); ) { auto exactSoundEffect = std::find( effectsToReplace.begin(), effectsToReplace.end(), *soundToAddIter ); if ( exactSoundEffect != effectsToReplace.end() ) { // Even when no angle and sound effect volume have been changed the overall sound volume might have. Set the volume. Mixer::setVolume( exactSoundEffect->channelId, exactSoundEffect->volumePercentage * soundVolume / 10 ); currentAudioLoopEffects[soundType].emplace_back( *exactSoundEffect ); effectsToReplace.erase( exactSoundEffect ); soundToAddIter = effectsToAdd.erase( soundToAddIter ); continue; } ++soundToAddIter; } size_t effectsToReplaceCount = std::min( effectsToAdd.size(), effectsToReplace.size() ); while ( effectsToReplaceCount > 0 ) { --effectsToReplaceCount; // Find the closest angles to those which are going to be added. size_t soundToAddId = 0; size_t soundToReplaceId = 0; getClosestSoundIdPairByAngle( effectsToAdd, effectsToReplace, soundToAddId, soundToReplaceId ); currentAudioLoopEffects[soundType].emplace_back( effectsToReplace[soundToReplaceId] ); effectsToReplace.erase( effectsToReplace.begin() + static_cast( soundToReplaceId ) ); ChannelAudioLoopEffectInfo & currentInfo = currentAudioLoopEffects[soundType].back(); currentInfo = { effectsToAdd[soundToAddId], currentInfo.channelId }; effectsToAdd.erase( effectsToAdd.begin() + static_cast( soundToAddId ) ); Mixer::setVolume( currentInfo.channelId, currentInfo.volumePercentage * soundVolume / 10 ); if ( is3DAudioEnabled ) { Mixer::applySoundEffect( currentInfo.channelId, currentInfo.angle, currentInfo.volumePercentage ); } } if ( effectsToReplace.empty() ) { tempAudioLoopEffects.erase( foundSoundTypeIter ); } if ( effectsToAdd.empty() ) { iter = soundEffects.erase( iter ); } else { ++iter; } } for ( const auto & audioEffectPair : tempAudioLoopEffects ) { const std::vector & existingEffects = audioEffectPair.second; for ( const ChannelAudioLoopEffectInfo & info : existingEffects ) { Mixer::setVolume( info.channelId, 0 ); Mixer::Stop( info.channelId ); } } tempAudioLoopEffects.clear(); // Add new sound effects. for ( const auto & audioEffectPair : soundEffects ) { const M82::SoundType soundType = audioEffectPair.first; const std::vector & effects = audioEffectPair.second; assert( !effects.empty() ); for ( const AudioManager::AudioLoopEffectInfo & info : effects ) { // It is a new sound effect. Register and play it. const std::vector & audioData = GetWAV( soundType ); if ( audioData.empty() ) { // Looks like nothing to play. Ignore it. continue; } int channelId = -1; if ( is3DAudioEnabled ) { channelId = Mixer::PlayFromDistance( &audioData[0], static_cast( audioData.size() ), -1, true, info.angle, info.volumePercentage ); } else { channelId = Mixer::Play( &audioData[0], static_cast( audioData.size() ), -1, true ); } if ( channelId < 0 ) { // Unable to play this audio. It is probably an invalid audio sample. continue; } // Adjust channel based on given parameters. // TODO: this is very hacky way. We should not do this. For example in 3D audio mode when a hero moves alongside beach it is noticeable that ocean sounds // TODO: are 'jumping' in volume. Instead of such approach we need to get free channel ID which will be used for playback. Set volume for it and then // TODO: start playing. Such logic must be implemented within Audio related code. // TODO: As an alternative solution: we can use channel IDs which we freed in the previous step. However, be careful with synchronization for audio // TODO: access. Mixer::Pause( channelId ); Mixer::setVolume( channelId, info.volumePercentage * soundVolume / 10 ); Mixer::Resume( channelId ); currentAudioLoopEffects[soundType].emplace_back( info, channelId ); DEBUG_LOG( DBG_GAME, DBG_TRACE, "Playing sound " << M82::GetString( soundType ) ) } } } } namespace AudioManager { AudioInitializer::AudioInitializer( const std::string & originalAGGFilePath, const std::string & expansionAGGFilePath, const ListFiles & midiSoundFonts ) { if ( Audio::isValid() ) { Mixer::SetChannels( 32 ); // Set the volume for all channels to 0. This is required to avoid random volume spikes at the beginning of the game. Mixer::setVolume( -1, 0 ); // Some platforms (e.g. Linux) may have their own predefined soundfonts, don't overwrite them if we don't have our own if ( !midiSoundFonts.empty() ) { Music::SetMidiSoundFonts( midiSoundFonts ); } Music::setVolume( 100 * Settings::Get().MusicVolume() / 10 ); Music::SetFadeInMs( 900 ); } assert( !originalAGGFilePath.empty() ); if ( !g_midiHeroes2AGG.open( originalAGGFilePath ) ) { VERBOSE_LOG( "Failed to open HEROES2.AGG file for audio playback." ) } if ( !expansionAGGFilePath.empty() && !g_midiHeroes2xAGG.open( expansionAGGFilePath ) ) { VERBOSE_LOG( "Failed to open HEROES2X.AGG file for audio playback." ) } } AudioInitializer::~AudioInitializer() { g_asyncSoundManager.removeAllTasks(); g_asyncSoundManager.stopWorker(); wavDataCache.clear(); MIDDataCache.clear(); currentAudioLoopEffects.clear(); } MusicRestorer::MusicRestorer() : _music( lastRequestedMusicTrackId ) { // Do nothing. } MusicRestorer::~MusicRestorer() { // It is assumed that the previous track was looped and should be resumed PlayMusicAsync( _music, Music::PlaybackMode::RESUME_AND_PLAY_INFINITE ); } void playLoopSoundsAsync( std::map> soundEffects ) { if ( !Audio::isValid() ) { return; } const Settings & conf = Settings::Get(); g_asyncSoundManager.pushLoopSound( std::move( soundEffects ), conf.SoundVolume(), conf.is3DAudioEnabled() ); } int PlaySound( const int m82 ) { if ( m82 == M82::UNKNOWN ) { return -1; } if ( !Audio::isValid() ) { return -1; } g_asyncSoundManager.removeSoundTasks(); return PlaySoundImp( m82, Settings::Get().SoundVolume() ); } void PlaySoundAsync( const int m82 ) { if ( m82 == M82::UNKNOWN ) { return; } if ( !Audio::isValid() ) { return; } g_asyncSoundManager.pushSound( m82, Settings::Get().SoundVolume() ); } bool isExternalMusicFileAvailable( const int trackId ) { return !getExternalMusicFile( trackId ).empty(); } void PlayMusic( const int trackId, const Music::PlaybackMode playbackMode ) { if ( !Audio::isValid() ) { return; } lastRequestedMusicTrackId = trackId; if ( trackId == MUS::UNUSED || trackId == MUS::UNKNOWN ) { return; } g_asyncSoundManager.removeMusicTask(); PlayMusicImp( trackId, Settings::Get().MusicType(), playbackMode ); } void PlayMusicAsync( const int trackId, const Music::PlaybackMode playbackMode ) { if ( !Audio::isValid() ) { return; } lastRequestedMusicTrackId = trackId; if ( trackId == MUS::UNUSED || trackId == MUS::UNKNOWN ) { return; } g_asyncSoundManager.pushMusic( trackId, Settings::Get().MusicType(), playbackMode ); } void PlayCurrentMusic() { if ( !Audio::isValid() ) { return; } g_asyncSoundManager.removeMusicTask(); const std::scoped_lock lock( g_asyncSoundManager.resourceMutex() ); if ( currentMusicTrackId == MUS::UNUSED || currentMusicTrackId == MUS::UNKNOWN ) { return; } const int trackId = std::exchange( currentMusicTrackId, MUS::UNKNOWN ); PlayMusicImp( trackId, Settings::Get().MusicType(), Music::PlaybackMode::RESUME_AND_PLAY_INFINITE ); } void stopSounds() { if ( !Audio::isValid() ) { return; } g_asyncSoundManager.removeAllSoundTasks(); const std::scoped_lock lock( g_asyncSoundManager.resourceMutex() ); clearAllAudioLoopEffects(); Mixer::Stop(); } void ResetAudio() { if ( !Audio::isValid() ) { return; } g_asyncSoundManager.removeAllTasks(); const std::scoped_lock lock( g_asyncSoundManager.resourceMutex() ); clearAllAudioLoopEffects(); Music::Stop(); Mixer::Stop(); lastRequestedMusicTrackId = MUS::UNKNOWN; currentMusicTrackId = MUS::UNKNOWN; } } fheroes2-1.0.12+dfsg/src/fheroes2/audio/audio_manager.h000066400000000000000000000105551456075706000226200ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include #include #include "audio.h" struct ListFiles; namespace M82 { enum SoundType : int; } namespace AudioManager { class AudioInitializer { public: AudioInitializer() = delete; AudioInitializer( const std::string & originalAGGFilePath, const std::string & expansionAGGFilePath, const ListFiles & midiSoundFonts ); AudioInitializer( const AudioInitializer & ) = delete; AudioInitializer & operator=( const AudioInitializer & ) = delete; ~AudioInitializer(); }; // Useful for restoring background music after playing short-term music effects. // Please note that this class will attempt to restore playback of the music track // that was last requested to play at the time of initialization of an instance of // this class, which is not necessarily the same music track that was actually // playing at the time. class MusicRestorer { public: MusicRestorer(); MusicRestorer( const MusicRestorer & ) = delete; ~MusicRestorer(); MusicRestorer & operator=( const MusicRestorer & ) = delete; private: const int _music; }; struct AudioLoopEffectInfo { AudioLoopEffectInfo() = default; AudioLoopEffectInfo( const int16_t angle_, const uint8_t volumePercentage_ ) : angle( angle_ ) , volumePercentage( volumePercentage_ ) { // Do nothing. } bool operator==( const AudioLoopEffectInfo & other ) const { return other.angle == angle && other.volumePercentage == volumePercentage; } int16_t angle{ 0 }; uint8_t volumePercentage{ 0 }; }; void playLoopSoundsAsync( std::map> soundEffects ); // Returns the ID of the channel occupied by the sound being played, or a negative value (-1) in case of failure. int PlaySound( const int m82 ); void PlaySoundAsync( const int m82 ); // Returns true if an external music file is available for the music track with the specified ID, otherwise returns false. bool isExternalMusicFileAvailable( const int trackId ); void PlayMusic( const int trackId, const Music::PlaybackMode playbackMode ); void PlayMusicAsync( const int trackId, const Music::PlaybackMode playbackMode ); // Assumes that the current music track is looped and should be resumed. // // TODO: Is subject to a (minor) race condition when called while the playback // TODO: of a new music track is being started in the AsyncSoundManager's worker // TODO: thread. In this case, the wrong music track (the one that is actually // TODO: being played at the moment, and not the one that is being prepared by // TODO: the worker thread for playback) may be played. void PlayCurrentMusic(); void stopSounds(); void ResetAudio(); } fheroes2-1.0.12+dfsg/src/fheroes2/battle/000077500000000000000000000000001456075706000200205ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle.h000066400000000000000000000107641456075706000214540ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2BATTLE_H #define H2BATTLE_H #include #include #include "army.h" #include "gamedefs.h" #include "m82.h" namespace Battle { class Unit; enum { RESULT_LOSS = 0x01, RESULT_RETREAT = 0x02, RESULT_SURRENDER = 0x04, RESULT_WINS = 0x80 }; struct Result { uint32_t army1{ 0 }; uint32_t army2{ 0 }; uint32_t exp1{ 0 }; uint32_t exp2{ 0 }; uint32_t killed{ 0 }; bool AttackerWins() const; bool DefenderWins() const; uint32_t AttackerResult() const; uint32_t DefenderResult() const; uint32_t GetExperienceAttacker() const; uint32_t GetExperienceDefender() const; }; Result Loader( Army &, Army &, int32_t ); struct TargetInfo { Unit * defender = nullptr; uint32_t damage = 0; uint32_t killed = 0; bool resist = false; TargetInfo() = default; explicit TargetInfo( Unit * defender_ ) : defender( defender_ ) {} static bool isFinishAnimFrame( const TargetInfo & info ); }; struct TargetsInfo : public std::vector { TargetsInfo() = default; }; enum MonsterState : uint32_t { TR_RESPONDED = 0x00000001, TR_MOVED = 0x00000002, TR_SKIP = 0x00000004, // UNUSED = 0x00000008, LUCK_GOOD = 0x00000100, LUCK_BAD = 0x00000200, MORALE_GOOD = 0x00000400, MORALE_BAD = 0x00000800, CAP_TOWER = 0x00001000, CAP_SUMMONELEM = 0x00002000, CAP_MIRROROWNER = 0x00004000, CAP_MIRRORIMAGE = 0x00008000, // UNUSED = 0x00010000, SP_BLOODLUST = 0x00020000, SP_BLESS = 0x00040000, SP_HASTE = 0x00080000, SP_SHIELD = 0x00100000, SP_STONESKIN = 0x00200000, SP_DRAGONSLAYER = 0x00400000, SP_STEELSKIN = 0x00800000, SP_ANTIMAGIC = 0x01000000, SP_CURSE = 0x02000000, SP_SLOW = 0x04000000, SP_BERSERKER = 0x08000000, SP_HYPNOTIZE = 0x10000000, SP_BLIND = 0x20000000, SP_PARALYZE = 0x40000000, SP_STONE = 0x80000000, IS_GOOD_MAGIC = SP_BLOODLUST | SP_BLESS | SP_HASTE | SP_SHIELD | SP_STONESKIN | SP_DRAGONSLAYER | SP_STEELSKIN, IS_BAD_MAGIC = SP_CURSE | SP_SLOW | SP_BERSERKER | SP_HYPNOTIZE | SP_BLIND | SP_PARALYZE | SP_STONE, IS_MAGIC = IS_GOOD_MAGIC | IS_BAD_MAGIC | SP_ANTIMAGIC, IS_PARALYZE_MAGIC = SP_PARALYZE | SP_STONE, IS_MIND_MAGIC = SP_BERSERKER | SP_HYPNOTIZE | SP_BLIND | SP_PARALYZE, }; enum class CastleDefenseElement : int { NONE = 0, WALL1 = 1, WALL2 = 2, WALL3 = 3, WALL4 = 4, TOWER1 = 5, TOWER2 = 6, BRIDGE = 7, CENTRAL_TOWER = 8 }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_action.cpp000066400000000000000000001466601456075706000233510ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include "battle.h" #include "battle_arena.h" #include "battle_army.h" #include "battle_board.h" #include "battle_bridge.h" #include "battle_cell.h" #include "battle_command.h" #include "battle_grave.h" #include "battle_interface.h" #include "battle_tower.h" #include "battle_troop.h" #include "color.h" #include "heroes.h" #include "heroes_base.h" #include "kingdom.h" #include "logging.h" #include "monster.h" #include "monster_info.h" #include "players.h" #include "rand.h" #include "resource.h" #include "spell.h" #include "spell_storage.h" #include "tools.h" #include "translations.h" #include "world.h" namespace { int32_t calculateAttackTarget( const Battle::Unit & attackingUnit, const Battle::Position & attackPosition, const Battle::Unit & defendingUnit ) { const int32_t attackPositionHeadIdx = attackPosition.GetHead() ? attackPosition.GetHead()->GetIndex() : -1; const int32_t attackPositionTailIdx = attackPosition.GetTail() ? attackPosition.GetTail()->GetIndex() : -1; assert( attackPositionHeadIdx != -1 && ( attackingUnit.isWide() ? attackPositionTailIdx != -1 : attackPositionTailIdx == -1 ) ); if ( Battle::Board::CanAttackFromCell( attackingUnit, attackPositionHeadIdx ) ) { // The defender's head cell is near the head cell of the attack position if ( Battle::Board::isNearIndexes( attackPositionHeadIdx, defendingUnit.GetHeadIndex() ) ) { return defendingUnit.GetHeadIndex(); } // The defender's tail cell is near the head cell of the attack position if ( defendingUnit.isWide() && Battle::Board::isNearIndexes( attackPositionHeadIdx, defendingUnit.GetTailIndex() ) ) { return defendingUnit.GetTailIndex(); } } if ( Battle::Board::CanAttackFromCell( attackingUnit, attackPositionTailIdx ) ) { // The defender's head cell is near the tail cell of the attack position if ( Battle::Board::isNearIndexes( attackPositionTailIdx, defendingUnit.GetHeadIndex() ) ) { return defendingUnit.GetHeadIndex(); } // The defender's tail cell is near the tail cell of the attack position if ( defendingUnit.isWide() && Battle::Board::isNearIndexes( attackPositionTailIdx, defendingUnit.GetTailIndex() ) ) { return defendingUnit.GetTailIndex(); } } // Attack position is not near the defender, this is most likely a shot return defendingUnit.GetHeadIndex(); } int calculateAttackDirection( const Battle::Unit & attackingUnit, const Battle::Position & attackPosition, const int32_t attackTargetIdx ) { const int32_t attackPositionHeadIdx = attackPosition.GetHead() ? attackPosition.GetHead()->GetIndex() : -1; const int32_t attackPositionTailIdx = attackPosition.GetTail() ? attackPosition.GetTail()->GetIndex() : -1; assert( attackPositionHeadIdx != -1 && ( attackingUnit.isWide() ? attackPositionTailIdx != -1 : attackPositionTailIdx == -1 ) ); // The target cell of the attack is near the head cell of the attack position if ( Battle::Board::CanAttackFromCell( attackingUnit, attackPositionHeadIdx ) && Battle::Board::isNearIndexes( attackPositionHeadIdx, attackTargetIdx ) ) { return Battle::Board::GetDirection( attackPositionHeadIdx, attackTargetIdx ); } // The target cell of the attack is near the tail cell of the attack position if ( Battle::Board::CanAttackFromCell( attackingUnit, attackPositionTailIdx ) && Battle::Board::isNearIndexes( attackPositionTailIdx, attackTargetIdx ) ) { return Battle::Board::GetDirection( attackPositionTailIdx, attackTargetIdx ); } // Attack position is not near the defender, this is most likely a shot return Battle::UNKNOWN; } bool checkMoveParams( const Battle::Unit * unit, const int32_t dst ) { assert( unit != nullptr && unit->isValid() ); // "Moving" a unit to its current position is not allowed if ( unit->GetHeadIndex() == dst ) { return false; } const Battle::Position pos = Battle::Position::GetReachable( *unit, dst ); if ( pos.GetHead() == nullptr ) { return false; } assert( pos.isValidForUnit( unit ) ); // Index of the destination cell should correspond to the index of the head cell of the target position and nothing else if ( pos.GetHead()->GetIndex() != dst ) { return false; } return true; } std::pair getEarthquakeDamageRange( const HeroBase * commander ) { const int spellPower = commander->GetPower(); if ( ( spellPower > 0 ) && ( spellPower < 3 ) ) { return { 0, 1 }; } else if ( ( spellPower >= 3 ) && ( spellPower < 6 ) ) { return { 0, 2 }; } else if ( ( spellPower >= 6 ) && ( spellPower < 10 ) ) { return { 0, 3 }; } else if ( spellPower >= 10 ) { return { 1, 3 }; } DEBUG_LOG( DBG_BATTLE, DBG_WARN, "unexpected spellPower value: " << spellPower << " for commander " << commander ) return { 0, 0 }; } } void Battle::Arena::BattleProcess( Unit & attacker, Unit & defender, int32_t tgt /* = -1 */, int dir /* = -1 */ ) { if ( tgt < 0 ) { tgt = calculateAttackTarget( attacker, attacker.GetPosition(), defender ); } if ( dir < 0 ) { dir = calculateAttackDirection( attacker, attacker.GetPosition(), tgt ); } // UNKNOWN attack direction is only allowed for archers assert( Unit::isHandFighting( attacker, defender ) ? dir > UNKNOWN : dir == UNKNOWN ); // This is a direct attack, update the direction for both the attacker and the defender if ( dir ) { const auto directionIsValidForAttack = []( const Unit & attackingUnit, const int32_t attackTgt, const int attackDir ) { assert( attackingUnit.isWide() ); const int32_t attackSrc = Board::GetIndexDirection( attackTgt, Board::GetReflectDirection( attackDir ) ); // Attacker should attack either from his head cell or from his tail cell, otherwise something strange happens assert( attackSrc == attackingUnit.GetHeadIndex() || attackSrc == attackingUnit.GetTailIndex() ); return attackSrc == attackingUnit.GetHeadIndex(); }; if ( attacker.isWide() ) { if ( !directionIsValidForAttack( attacker, tgt, dir ) ) { attacker.SetReflection( !attacker.isReflect() ); } } else { attacker.UpdateDirection( board[tgt].GetPos() ); } if ( !attacker.isIgnoringRetaliation() && defender.AllowResponse() ) { const int32_t responseTgt = calculateAttackTarget( defender, defender.GetPosition(), attacker ); const int responseDir = calculateAttackDirection( defender, defender.GetPosition(), responseTgt ); if ( defender.isWide() ) { if ( !directionIsValidForAttack( defender, responseTgt, responseDir ) ) { defender.SetReflection( !defender.isReflect() ); } } else { defender.UpdateDirection( board[responseTgt].GetPos() ); } } } // This is a shot, update the direction for the attacker only else { // For shooters we get the target position (not the 'tgt') to take into account the wide units. attacker.UpdateDirection( defender.GetRectPosition() ); } // Update the attacker's luck right before the attack attacker.SetRandomLuck( _randomGenerator ); // Do damage first TargetsInfo attackTargets = GetTargetsForDamage( attacker, defender, tgt, dir ); if ( _interface ) { _interface->RedrawActionAttackPart1( attacker, defender, attackTargets ); } uint32_t resurrected = 0; TargetsApplyDamage( attacker, attackTargets, resurrected ); if ( _interface ) { _interface->RedrawActionAttackPart2( attacker, defender, attackTargets, resurrected ); } // Then apply the attacker's built-in spell const Spell spell = attacker.GetSpellMagic( _randomGenerator ); if ( spell.isValid() ) { // Only single target spells and special built-in only spells are allowed assert( spell.isSingleTarget() || spell.isBuiltinOnly() ); TargetsInfo spellTargets; spellTargets.reserve( attackTargets.size() ); // Filter out invalid targets and targets to which the spell cannot be applied for ( const TargetInfo & attackTarget : attackTargets ) { assert( attackTarget.defender != nullptr ); if ( !attackTarget.defender->isValid() ) { continue; } if ( !attackTarget.defender->AllowApplySpell( spell, nullptr ) ) { continue; } spellTargets.emplace_back( attackTarget.defender ); } if ( !spellTargets.empty() ) { // The built-in spell can only be applied to one target. If there are multiple // targets eligible for this spell, then we should randomly select only one. if ( spellTargets.size() > 1 ) { const Unit * selectedUnit = _randomGenerator.Get( spellTargets ).defender; spellTargets.erase( std::remove_if( spellTargets.begin(), spellTargets.end(), [selectedUnit]( const TargetInfo & v ) { return v.defender != selectedUnit; } ), spellTargets.end() ); } assert( spellTargets.size() == 1 ); Unit * spellTargetUnit = spellTargets.front().defender; assert( spellTargetUnit != nullptr ); // The built-in dispel should only remove beneficial spells from the target unit if ( spell.GetID() != Spell::DISPEL || spellTargetUnit->Modes( IS_GOOD_MAGIC ) ) { if ( _interface ) { _interface->RedrawActionSpellCastStatus( spell, spellTargetUnit->GetHeadIndex(), attacker.GetName(), spellTargets ); _interface->RedrawActionSpellCastPart1( spell, spellTargetUnit->GetHeadIndex(), nullptr, spellTargets ); } if ( spell.GetID() == Spell::DISPEL ) { spellTargetUnit->removeAffection( IS_GOOD_MAGIC ); } else { // The unit's built-in spell efficiency does not depend on its commanding hero's skills TargetsApplySpell( nullptr, spell, spellTargets ); } if ( _interface ) { _interface->RedrawActionSpellCastPart2( spell, spellTargets ); _interface->RedrawActionMonsterSpellCastStatus( spell, attacker, spellTargets.front() ); } } } } attacker.PostAttackAction( defender ); } void Battle::Arena::moveUnit( Unit * unit, const int32_t dst ) { if ( dst == -1 ) { return; } assert( checkMoveParams( unit, dst ) ); Position pos = Position::GetReachable( *unit, dst ); assert( pos.isValidForUnit( unit ) ); DEBUG_LOG( DBG_BATTLE, DBG_TRACE, unit->String() << ", dst: " << dst << ", (head: " << pos.GetHead()->GetIndex() << ", tail: " << ( unit->isWide() ? pos.GetTail()->GetIndex() : -1 ) << ")" ) Position finalPos; if ( unit->isFlying() ) { unit->UpdateDirection( pos.GetRect() ); if ( unit->isReflect() != pos.isReflect() ) { pos.Swap(); } if ( _interface ) { _interface->RedrawActionFly( *unit, pos ); } else if ( _bridge ) { const int32_t dstHead = pos.GetHead()->GetIndex(); const int32_t dstTail = unit->isWide() ? pos.GetTail()->GetIndex() : -1; // Lower the bridge if the unit needs to land on it if ( _bridge->NeedDown( *unit, dstHead ) || ( unit->isWide() && _bridge->NeedDown( *unit, dstTail ) ) ) { _bridge->ActionDown(); } unit->SetPosition( pos ); // Raise the bridge if possible after the unit has completed its movement if ( _bridge->AllowUp() ) { _bridge->ActionUp(); } } finalPos = pos; } else { const int32_t initialHead = unit->GetHeadIndex(); const Indexes path = GetPath( *unit, pos ); assert( !path.empty() ); if ( _interface ) { _interface->RedrawActionMove( *unit, path ); } else if ( _bridge ) { for ( const int32_t idx : path ) { if ( _bridge->NeedDown( *unit, idx ) ) { _bridge->ActionDown(); } if ( unit->isWide() && unit->GetTailIndex() == idx ) { unit->SetReflection( !unit->isReflect() ); } else { unit->SetPosition( idx ); } if ( _bridge->AllowUp() ) { _bridge->ActionUp(); } } } if ( unit->isWide() ) { const int32_t dstHead = path.back(); const int32_t dstTail = path.size() > 1 ? path[path.size() - 2] : initialHead; finalPos.Set( dstHead, true, ( Board::GetDirection( dstHead, dstTail ) & RIGHT_SIDE ) != 0 ); } else { finalPos.Set( path.back(), false, unit->isReflect() ); } } unit->SetPosition( finalPos ); unit->UpdateDirection(); } void Battle::Arena::ApplyAction( Command & cmd ) { switch ( cmd.GetType() ) { case CommandType::SPELLCAST: ApplyActionSpellCast( cmd ); break; case CommandType::ATTACK: ApplyActionAttack( cmd ); break; case CommandType::MOVE: ApplyActionMove( cmd ); break; case CommandType::SKIP: ApplyActionSkip( cmd ); break; case CommandType::MORALE: ApplyActionMorale( cmd ); break; case CommandType::TOWER: ApplyActionTower( cmd ); break; case CommandType::CATAPULT: ApplyActionCatapult( cmd ); break; case CommandType::RETREAT: ApplyActionRetreat( cmd ); break; case CommandType::SURRENDER: ApplyActionSurrender( cmd ); break; case CommandType::AUTO_SWITCH: ApplyActionAutoSwitch( cmd ); break; case CommandType::AUTO_FINISH: ApplyActionAutoFinish( cmd ); break; default: break; } } void Battle::Arena::ApplyActionSpellCast( Command & cmd ) { const auto checkParameters = []( const Spell & spell, const HeroBase * commander ) { if ( !spell.isCombat() ) { return false; } const Arena * arena = GetArena(); assert( arena != nullptr ); if ( arena->isDisableCastSpell( spell ) ) { return false; } if ( commander == nullptr || !commander->CanCastSpell( spell ) ) { return false; } return true; }; const Spell spell( cmd.GetNextValue() ); HeroBase * commander = GetCurrentForce().GetCommander(); if ( !checkParameters( spell, commander ) ) { ERROR_LOG( "Invalid parameters: " << spell.GetName() ) #ifdef WITH_DEBUG assert( 0 ); #endif return; } DEBUG_LOG( DBG_BATTLE, DBG_TRACE, commander->GetName() << ", color: " << Color::String( commander->GetColor() ) << ", spell: " << spell.GetName() ) switch ( spell.GetID() ) { case Spell::TELEPORT: ApplyActionSpellTeleport( cmd ); break; case Spell::EARTHQUAKE: ApplyActionSpellEarthQuake( cmd ); break; case Spell::MIRRORIMAGE: ApplyActionSpellMirrorImage( cmd ); break; case Spell::SUMMONEELEMENT: case Spell::SUMMONAELEMENT: case Spell::SUMMONFELEMENT: case Spell::SUMMONWELEMENT: ApplyActionSpellSummonElemental( cmd, spell ); break; default: ApplyActionSpellDefaults( cmd, spell ); break; } commander->SetModes( Heroes::SPELLCASTED ); commander->SpellCasted( spell ); // Save the spell for the Eagle Eye skill usage_spells.Append( spell ); } void Battle::Arena::ApplyActionAttack( Command & cmd ) { const auto checkParameters = []( const Unit * attacker, const Unit * defender, const int32_t dst, int32_t tgt, int dir ) { if ( attacker == nullptr || !attacker->isValid() ) { return false; } if ( defender == nullptr || !defender->isValid() ) { return false; } if ( attacker->Modes( TR_MOVED ) ) { return false; } if ( attacker->GetCurrentColor() == defender->GetColor() ) { return false; } // Attacker can attack from his current position without performing a move (in this case, the index of the destination cell should be -1) if ( dst != -1 && !checkMoveParams( attacker, dst ) ) { return false; } if ( attacker->isArchers() && !attacker->isHandFighting() ) { // Non-blocked archer can only attack by shooting from his current position if ( dst != -1 ) { return false; } if ( tgt < 0 ) { tgt = calculateAttackTarget( *attacker, attacker->GetPosition(), *defender ); } if ( dir < 0 ) { dir = calculateAttackDirection( *attacker, attacker->GetPosition(), tgt ); } if ( !defender->GetPosition().contains( tgt ) ) { return false; } // Non-blocked archers cannot attack "from a direction" if ( dir != UNKNOWN ) { return false; } return true; } const Position attackPos = ( dst == -1 ? attacker->GetPosition() : Position::GetReachable( *attacker, dst ) ); if ( attackPos.GetHead() == nullptr ) { return false; } assert( attackPos.isValidForUnit( attacker ) ); if ( tgt < 0 ) { tgt = calculateAttackTarget( *attacker, attackPos, *defender ); } if ( dir < 0 ) { dir = calculateAttackDirection( *attacker, attackPos, tgt ); } if ( !defender->GetPosition().contains( tgt ) ) { return false; } // Melee attacks are only possible from a certain direction if ( dir == UNKNOWN ) { return false; } const int32_t attackIdx = ( Board::isValidDirection( tgt, Board::GetReflectDirection( dir ) ) ? Board::GetIndexDirection( tgt, Board::GetReflectDirection( dir ) ) : -1 ); if ( !attackPos.contains( attackIdx ) ) { return false; } // Attack from a specified cell may be prohibited - for example, if this cell belongs to a castle moat if ( !Board::CanAttackFromCell( *attacker, attackIdx ) ) { return false; } return true; }; const uint32_t attackerUID = cmd.GetNextValue(); const uint32_t defenderUID = cmd.GetNextValue(); const int32_t dst = cmd.GetNextValue(); const int32_t tgt = cmd.GetNextValue(); const int dir = cmd.GetNextValue(); Unit * attacker = GetTroopUID( attackerUID ); Unit * defender = GetTroopUID( defenderUID ); if ( !checkParameters( attacker, defender, dst, tgt, dir ) ) { ERROR_LOG( "Invalid parameters: " << "attacker uid: " << GetHexString( attackerUID ) << ", defender uid: " << GetHexString( defenderUID ) << ", dst: " << dst << ", tgt: " << tgt << ", dir: " << dir ) #ifdef WITH_DEBUG assert( 0 ); #endif return; } moveUnit( attacker, dst ); DEBUG_LOG( DBG_BATTLE, DBG_TRACE, attacker->String() << " to " << defender->String() ) const bool handfighting = Unit::isHandFighting( *attacker, *defender ); const bool doubleAttack = attacker->isDoubleAttack(); defender->SetBlindRetaliation( defender->Modes( SP_BLIND ) ); BattleProcess( *attacker, *defender, tgt, dir ); if ( defender->isValid() ) { if ( handfighting && !attacker->isIgnoringRetaliation() && defender->AllowResponse() ) { BattleProcess( *defender, *attacker ); defender->SetResponse(); } if ( doubleAttack && attacker->isValid() && !attacker->isImmovable() ) { DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "double attack" ) BattleProcess( *attacker, *defender, tgt, dir ); } } defender->SetBlindRetaliation( false ); // Berserk and Hypnotize spells should only be removed after an attack initiated by this unit, but not after its retaliatory attack attacker->removeAffection( SP_BERSERKER | SP_HYPNOTIZE ); // Reflect attacker only if he is alive. if ( attacker->isValid() ) { attacker->UpdateDirection(); } // Reflect defender only if he is alive. if ( defender->isValid() ) { defender->UpdateDirection(); } attacker->SetModes( TR_MOVED ); } void Battle::Arena::ApplyActionMove( Command & cmd ) { const auto checkParameters = []( const Unit * unit, const int32_t dst ) { if ( unit == nullptr || !unit->isValid() ) { return false; } if ( unit->Modes( TR_MOVED ) ) { return false; } if ( !checkMoveParams( unit, dst ) ) { return false; } return true; }; const uint32_t uid = cmd.GetNextValue(); const int32_t dst = cmd.GetNextValue(); Unit * unit = GetTroopUID( uid ); if ( !checkParameters( unit, dst ) ) { ERROR_LOG( "Invalid parameters: " << "uid: " << GetHexString( uid ) << ", dst: " << dst ) #ifdef WITH_DEBUG assert( 0 ); #endif return; } moveUnit( unit, dst ); unit->SetModes( TR_MOVED ); } void Battle::Arena::ApplyActionSkip( Command & cmd ) { const auto checkParameters = []( const Unit * unit ) { if ( unit == nullptr || !unit->isValid() ) { return false; } if ( unit->Modes( TR_MOVED ) ) { return false; } return true; }; const uint32_t uid = cmd.GetNextValue(); Unit * unit = GetTroopUID( uid ); if ( !checkParameters( unit ) ) { ERROR_LOG( "Invalid parameters: " << "uid: " << GetHexString( uid ) ) #ifdef WITH_DEBUG assert( 0 ); #endif return; } DEBUG_LOG( DBG_BATTLE, DBG_TRACE, unit->String() ) unit->SetModes( TR_SKIP ); unit->SetModes( TR_MOVED ); if ( _interface ) { _interface->RedrawActionSkipStatus( *unit ); } } void Battle::Arena::ApplyActionMorale( Command & cmd ) { const auto checkParameters = []( const Unit * unit, const bool morale ) { if ( unit == nullptr || !unit->isValid() ) { return false; } if ( morale ) { if ( !unit->AllModes( TR_MOVED | MORALE_GOOD ) ) { return false; } } else { if ( !unit->Modes( MORALE_BAD ) || unit->Modes( TR_MOVED ) ) { return false; } } return true; }; const uint32_t uid = cmd.GetNextValue(); const bool morale = cmd.GetNextValue(); Unit * unit = GetTroopUID( uid ); if ( !checkParameters( unit, morale ) ) { ERROR_LOG( "Invalid parameters: " << "uid: " << GetHexString( uid ) << ", morale: " << ( morale ? "good" : "bad" ) ) #ifdef WITH_DEBUG assert( 0 ); #endif return; } DEBUG_LOG( DBG_BATTLE, DBG_TRACE, ( morale ? "good" : "bad" ) << " to " << unit->String() ) // Good morale if ( morale ) { unit->ResetModes( TR_MOVED | MORALE_GOOD ); } // Bad morale else { unit->ResetModes( MORALE_BAD ); unit->SetModes( TR_MOVED ); } if ( _interface ) { _interface->RedrawActionMorale( *unit, morale != 0 ); } } void Battle::Arena::ApplyActionRetreat( const Command & /* cmd */ ) { const int currentColor = GetCurrentColor(); if ( !CanRetreatOpponent( currentColor ) ) { ERROR_LOG( "Preconditions were not met" ) #ifdef WITH_DEBUG assert( 0 ); #endif return; } DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "color: " << Color::String( currentColor ) ) if ( _army1->GetColor() == currentColor ) { result_game.army1 = RESULT_RETREAT; } else if ( _army2->GetColor() == currentColor ) { result_game.army2 = RESULT_RETREAT; } else { assert( 0 ); } } void Battle::Arena::ApplyActionSurrender( const Command & /* cmd */ ) { const auto checkPreconditions = []( const Funds & cost ) { const Arena * arena = GetArena(); assert( arena != nullptr ); if ( !arena->CanSurrenderOpponent( arena->GetCurrentColor() ) ) { return false; } if ( !world.GetKingdom( arena->GetCurrentColor() ).AllowPayment( cost ) ) { return false; } return true; }; const int currentColor = GetCurrentColor(); if ( _army1->GetColor() == currentColor ) { Funds cost; cost.gold = _army1->GetSurrenderCost(); if ( !checkPreconditions( cost ) ) { ERROR_LOG( "Preconditions were not met" ) #ifdef WITH_DEBUG assert( 0 ); #endif return; } DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "color: " << Color::String( currentColor ) ) world.GetKingdom( _army1->GetColor() ).OddFundsResource( cost ); world.GetKingdom( _army2->GetColor() ).AddFundsResource( cost ); result_game.army1 = RESULT_SURRENDER; } else if ( _army2->GetColor() == currentColor ) { Funds cost; cost.gold = _army2->GetSurrenderCost(); if ( !checkPreconditions( cost ) ) { ERROR_LOG( "Preconditions were not met" ) #ifdef WITH_DEBUG assert( 0 ); #endif return; } DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "color: " << Color::String( currentColor ) ) world.GetKingdom( _army2->GetColor() ).OddFundsResource( cost ); world.GetKingdom( _army1->GetColor() ).AddFundsResource( cost ); result_game.army2 = RESULT_SURRENDER; } else { assert( 0 ); } } void Battle::Arena::TargetsApplyDamage( Unit & attacker, TargetsInfo & targets, uint32_t & resurrected ) { DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "number of targets: " << targets.size() ) for ( TargetInfo & target : targets ) { assert( target.defender != nullptr ); uint32_t resurrectIncrease = 0; target.defender->ApplyDamage( attacker, target.damage, target.killed, &resurrectIncrease ); resurrected += resurrectIncrease; } } Battle::TargetsInfo Battle::Arena::GetTargetsForDamage( const Unit & attacker, Unit & defender, const int32_t dst, const int dir ) const { // The attacked unit should be located on the attacked cell assert( defender.GetHeadIndex() == dst || defender.GetTailIndex() == dst ); TargetsInfo targets; targets.reserve( 8 ); TargetInfo res; // first target res.defender = &defender; res.damage = attacker.GetDamage( defender, _randomGenerator ); // Genie special attack if ( attacker.GetID() == Monster::GENIE && _randomGenerator.Get( 1, 10 ) == 2 && defender.GetHitPoints() / 2 > res.damage ) { // Replaces the damage, not adding to it if ( defender.GetCount() == 1 ) { res.damage = defender.GetHitPoints(); } else { res.damage = defender.GetHitPoints() / 2; } if ( Arena::GetInterface() ) { std::string str( _n( "%{name} destroys half the enemy troops!", "%{name} destroy half the enemy troops!", attacker.GetCount() ) ); StringReplace( str, "%{name}", attacker.GetName() ); Arena::GetInterface()->SetStatus( str, true ); } } targets.push_back( res ); std::set consideredTargets{ &defender }; // long distance attack if ( attacker.isDoubleCellAttack() ) { Cell * cell = Board::GetCell( dst, dir ); Unit * enemy = cell ? cell->GetUnit() : nullptr; if ( enemy && consideredTargets.insert( enemy ).second ) { res.defender = enemy; res.damage = attacker.GetDamage( *enemy, _randomGenerator ); targets.push_back( res ); } } // attack of all adjacent cells else if ( attacker.isAllAdjacentCellsAttack() ) { for ( const int32_t nearbyIdx : Board::GetAroundIndexes( attacker ) ) { assert( Board::GetCell( nearbyIdx ) != nullptr ); Unit * enemy = Board::GetCell( nearbyIdx )->GetUnit(); if ( enemy && enemy->GetColor() != attacker.GetCurrentColor() && consideredTargets.insert( enemy ).second ) { res.defender = enemy; res.damage = attacker.GetDamage( *enemy, _randomGenerator ); targets.push_back( res ); } } } // lich cloud damage else if ( attacker.isAbilityPresent( fheroes2::MonsterAbilityType::AREA_SHOT ) && !Unit::isHandFighting( attacker, defender ) ) { for ( const int32_t nearbyIdx : Board::GetAroundIndexes( dst ) ) { assert( Board::GetCell( nearbyIdx ) != nullptr ); Unit * enemy = Board::GetCell( nearbyIdx )->GetUnit(); if ( enemy && consideredTargets.insert( enemy ).second ) { res.defender = enemy; res.damage = attacker.GetDamage( *enemy, _randomGenerator ); targets.push_back( res ); } } } return targets; } void Battle::Arena::TargetsApplySpell( const HeroBase * hero, const Spell & spell, TargetsInfo & targets ) { DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "number of targets: " << targets.size() ) for ( TargetInfo & target : targets ) { assert( target.defender != nullptr ); target.defender->ApplySpell( spell, hero, target ); } } std::vector Battle::Arena::FindChainLightningTargetIndexes( const HeroBase * hero, Unit * firstUnit, const bool applyRandomMagicResistance ) { std::vector result = { firstUnit }; std::vector ignoredTroops = { firstUnit }; std::vector foundTroops = board.GetNearestTroops( result.back(), ignoredTroops ); // Filter those which are fully immuned for ( size_t i = 0; i < foundTroops.size(); ) { if ( foundTroops[i]->GetMagicResist( Spell::CHAINLIGHTNING, hero ) >= 100 ) { ignoredTroops.push_back( foundTroops[i] ); foundTroops.erase( foundTroops.begin() + i ); } else { ++i; } } while ( result.size() != CHAIN_LIGHTNING_CREATURE_COUNT && !foundTroops.empty() ) { bool targetFound = false; for ( size_t i = 0; i < foundTroops.size(); ++i ) { const uint32_t resist = foundTroops[i]->GetMagicResist( Spell::CHAINLIGHTNING, hero ); assert( resist < 100 ); if ( !applyRandomMagicResistance || resist < _randomGenerator.Get( 1, 100 ) ) { ignoredTroops.push_back( foundTroops[i] ); result.push_back( foundTroops[i] ); foundTroops.erase( foundTroops.begin() + i ); targetFound = true; break; } } // All targets are resisted. Choosing the nearest one. if ( !targetFound ) { ignoredTroops.push_back( foundTroops.front() ); result.push_back( foundTroops.front() ); foundTroops.erase( foundTroops.begin() ); } if ( result.size() != CHAIN_LIGHTNING_CREATURE_COUNT ) { foundTroops = board.GetNearestTroops( result.back(), ignoredTroops ); } } return result; } Battle::TargetsInfo Battle::Arena::TargetsForChainLightning( const HeroBase * hero, const int32_t attackedTroopIndex, const bool applyRandomMagicResistance ) { Unit * unit = GetTroopBoard( attackedTroopIndex ); if ( unit == nullptr ) { assert( 0 ); return TargetsInfo(); } TargetsInfo targets; const uint32_t firstUnitResist = unit->GetMagicResist( Spell::CHAINLIGHTNING, hero ); if ( firstUnitResist >= 100 || ( applyRandomMagicResistance && firstUnitResist >= _randomGenerator.Get( 1, 100 ) ) ) { targets.emplace_back(); TargetInfo & res = targets.back(); res.defender = unit; res.resist = true; return targets; } const std::vector targetUnits = FindChainLightningTargetIndexes( hero, unit, applyRandomMagicResistance ); for ( size_t i = 0; i < targetUnits.size(); ++i ) { targets.emplace_back(); TargetInfo & res = targets.back(); res.defender = targetUnits[i]; // store temp priority for calculate damage res.damage = static_cast( i ); } return targets; } Battle::TargetsInfo Battle::Arena::GetTargetsForSpell( const HeroBase * hero, const Spell & spell, const int32_t dst, bool applyRandomMagicResistance, bool * playResistSound ) { TargetsInfo targets; targets.reserve( 8 ); if ( playResistSound ) { *playResistSound = true; } Unit * target = GetTroopBoard( dst ); // from spells switch ( spell.GetID() ) { case Spell::CHAINLIGHTNING: case Spell::COLDRING: // skip center target = nullptr; break; default: break; } std::set consideredTargets; TargetInfo res; // first target if ( target && target->AllowApplySpell( spell, hero ) && consideredTargets.insert( target ).second ) { res.defender = target; targets.push_back( res ); } // resurrect spell? get target from graveyard if ( nullptr == target && GraveyardAllowResurrect( dst, spell ) ) { target = GetTroopUID( graveyard.GetLastTroopUID( dst ) ); if ( target && target->AllowApplySpell( spell, hero ) && consideredTargets.insert( target ).second ) { res.defender = target; targets.push_back( res ); } } else { // check other spells switch ( spell.GetID() ) { case Spell::CHAINLIGHTNING: { for ( const TargetInfo & spellTarget : TargetsForChainLightning( hero, dst, applyRandomMagicResistance ) ) { assert( spellTarget.defender != nullptr ); if ( consideredTargets.insert( spellTarget.defender ).second ) { targets.push_back( spellTarget ); } else { // TargetsForChainLightning() should never return duplicates assert( 0 ); } } // Magic resistance has already been applied in the process of selecting targets for Chain Lightning applyRandomMagicResistance = false; // TODO: remove this temporary assertion if ( playResistSound ) { assert( *playResistSound ); } break; } // check abroads case Spell::FIREBALL: case Spell::METEORSHOWER: case Spell::COLDRING: case Spell::FIREBLAST: { for ( const int32_t index : Board::GetDistanceIndexes( dst, ( spell == Spell::FIREBLAST ? 2 : 1 ) ) ) { Unit * targetUnit = GetTroopBoard( index ); if ( targetUnit && targetUnit->AllowApplySpell( spell, hero ) && consideredTargets.insert( targetUnit ).second ) { res.defender = targetUnit; targets.push_back( res ); } } if ( playResistSound ) { *playResistSound = false; } break; } // check all troops case Spell::DEATHRIPPLE: case Spell::DEATHWAVE: case Spell::ELEMENTALSTORM: case Spell::HOLYWORD: case Spell::HOLYSHOUT: case Spell::ARMAGEDDON: case Spell::MASSBLESS: case Spell::MASSCURE: case Spell::MASSCURSE: case Spell::MASSDISPEL: case Spell::MASSHASTE: case Spell::MASSSHIELD: case Spell::MASSSLOW: { for ( Cell & cell : board ) { target = cell.GetUnit(); if ( target && target->AllowApplySpell( spell, hero ) && consideredTargets.insert( target ).second ) { res.defender = target; targets.push_back( res ); } } if ( playResistSound ) { *playResistSound = false; } break; } default: break; } } // Mark magically resistant troops for ( auto & tgt : targets ) { const uint32_t resist = tgt.defender->GetMagicResist( spell, hero ); assert( resist < 100 ); if ( applyRandomMagicResistance && resist >= _randomGenerator.Get( 1, 100 ) ) { tgt.resist = true; } } return targets; } void Battle::Arena::ApplyActionTower( Command & cmd ) { const auto checkParameters = []( const Tower * tower, const Unit * unit ) { if ( tower == nullptr || !tower->isValid() ) { return false; } if ( unit == nullptr || !unit->isValid() ) { return false; } return true; }; const uint32_t type = cmd.GetNextValue(); const uint32_t uid = cmd.GetNextValue(); Tower * tower = GetTower( static_cast( type ) ); Unit * unit = GetTroopUID( uid ); if ( !checkParameters( tower, unit ) ) { ERROR_LOG( "Invalid parameters: " << "tower: " << type << ", uid: " << GetHexString( uid ) ) #ifdef WITH_DEBUG assert( 0 ); #endif return; } DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "tower: " << type << ", target: " << unit->String() ) TargetInfo target; target.defender = unit; target.damage = tower->GetDamage( *unit, _randomGenerator ); if ( _interface ) { _interface->RedrawActionTowerPart1( *tower, *unit ); } unit->ApplyDamage( *tower, target.damage, target.killed, nullptr ); if ( _interface ) { _interface->RedrawActionTowerPart2( *tower, target ); } } void Battle::Arena::ApplyActionCatapult( Command & cmd ) { if ( _catapult == nullptr ) { ERROR_LOG( "Preconditions were not met" ) #ifdef WITH_DEBUG assert( 0 ); #endif return; } uint32_t shots = cmd.GetNextValue(); while ( shots-- ) { const CastleDefenseElement target = static_cast( cmd.GetNextValue() ); const uint32_t damage = cmd.GetNextValue(); const bool hit = cmd.GetNextValue() != 0; if ( target == CastleDefenseElement::NONE ) { continue; } const uint32_t castleTargetValue = GetCastleTargetValue( target ); using TargetUnderlyingType = std::underlying_type_t; if ( castleTargetValue < damage ) { ERROR_LOG( "Invalid parameters: " << "target: " << static_cast( target ) << ", damage: " << damage << ", hit: " << ( hit ? "yes" : "no" ) ) #ifdef WITH_DEBUG assert( 0 ); #endif return; } DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "target: " << static_cast( target ) << ", damage: " << damage << ", hit: " << ( hit ? "yes" : "no" ) ) if ( _interface ) { _interface->RedrawActionCatapultPart1( target, hit ); } if ( !hit ) { continue; } SetCastleTargetValue( target, castleTargetValue - damage ); if ( _interface ) { // Continue animating the smoke cloud after changing the "health" of the building. _interface->RedrawActionCatapultPart2( target ); } } } void Battle::Arena::ApplyActionAutoSwitch( Command & cmd ) { const auto checkParameters = []( const int color ) { const Arena * arena = GetArena(); assert( arena != nullptr ); if ( color != arena->GetArmy1Color() && color != arena->GetArmy2Color() ) { return false; } if ( arena->getForce( color ).GetControl() & CONTROL_AI ) { return false; } return true; }; const int color = cmd.GetNextValue(); if ( !checkParameters( color ) ) { ERROR_LOG( "Invalid parameters: " << "color: " << Color::String( color ) << " (" << color << ")" ) #ifdef WITH_DEBUG assert( 0 ); #endif return; } _autoBattleColors ^= color; DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "color: " << Color::String( color ) << ", status: " << ( ( _autoBattleColors & color ) ? "on" : "off" ) ) if ( _interface ) { const Player * player = Players::Get( color ); assert( player ); std::string msg = ( _autoBattleColors & color ) ? _( "%{name} has turned on the auto battle" ) : _( "%{name} has turned off the auto battle" ); StringReplace( msg, "%{name}", player->GetName() ); _interface->SetStatus( msg, true ); } } void Battle::Arena::ApplyActionAutoFinish( const Command & /* cmd */ ) { const int army1Control = GetForce1().GetControl(); const int army2Control = GetForce2().GetControl(); if ( !( army1Control & CONTROL_HUMAN ) && !( army2Control & CONTROL_HUMAN ) ) { ERROR_LOG( "Preconditions were not met" ) #ifdef WITH_DEBUG assert( 0 ); #endif return; } DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "finishing the battle" ) const int army1Color = GetArmy1Color(); const int army2Color = GetArmy2Color(); if ( army1Control & CONTROL_HUMAN ) { _autoBattleColors |= army1Color; } if ( army2Control & CONTROL_HUMAN ) { _autoBattleColors |= army2Color; } _interface.reset(); } void Battle::Arena::ApplyActionSpellSummonElemental( const Command & /* cmd */, const Spell & spell ) { const auto checkPreconditions = []() { const Arena * arena = GetArena(); assert( arena != nullptr ); if ( arena->GetCurrentCommander() == nullptr ) { return false; } const int32_t idx = arena->GetFreePositionNearHero( arena->GetCurrentColor() ); if ( !Board::isValidIndex( idx ) ) { return false; } return true; }; if ( !checkPreconditions() ) { ERROR_LOG( "Preconditions were not met" ) #ifdef WITH_DEBUG assert( 0 ); #endif return; } Unit * elem = CreateElemental( spell ); assert( elem != nullptr ); if ( _interface ) { const HeroBase * commander = GetCurrentCommander(); assert( commander != nullptr ); _interface->RedrawActionSpellCastStatus( spell, -1, commander->GetName(), {} ); _interface->RedrawActionSummonElementalSpell( *elem ); } } void Battle::Arena::ApplyActionSpellDefaults( Command & cmd, const Spell & spell ) { const int32_t dst = cmd.GetNextValue(); const HeroBase * commander = GetCurrentCommander(); if ( commander == nullptr ) { ERROR_LOG( "Preconditions were not met" ) #ifdef WITH_DEBUG assert( 0 ); #endif return; } bool playResistSound = false; TargetsInfo targets = GetTargetsForSpell( commander, spell, dst, true, &playResistSound ); TargetsInfo resistTargets; if ( _interface ) { _interface->RedrawActionSpellCastStatus( spell, dst, commander->GetName(), targets ); for ( const auto & target : targets ) { if ( target.resist ) { resistTargets.push_back( target ); } } } targets.erase( std::remove_if( targets.begin(), targets.end(), []( const TargetInfo & v ) { return v.resist; } ), targets.end() ); if ( _interface ) { _interface->RedrawActionSpellCastPart1( spell, dst, commander, targets ); for ( const TargetInfo & target : resistTargets ) { _interface->RedrawActionResistSpell( *target.defender, playResistSound ); } } TargetsApplySpell( commander, spell, targets ); if ( _interface ) { _interface->RedrawActionSpellCastPart2( spell, targets ); } } void Battle::Arena::ApplyActionSpellTeleport( Command & cmd ) { const auto checkParameters = []( const Unit * unit, const Cell * cell ) { if ( unit == nullptr || !unit->isValid() ) { return false; } if ( !cell->isPassableForUnit( *unit ) ) { return false; } const Arena * arena = GetArena(); assert( arena != nullptr ); if ( arena->GetCurrentCommander() == nullptr ) { return false; } return true; }; const int32_t src = cmd.GetNextValue(); const int32_t dst = cmd.GetNextValue(); Unit * unit = GetTroopBoard( src ); const Cell * cell = Board::GetCell( dst ); if ( !checkParameters( unit, cell ) ) { ERROR_LOG( "Invalid parameters: " << "src: " << src << ", dst: " << dst ) #ifdef WITH_DEBUG assert( 0 ); #endif return; } DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "src: " << src << ", dst: " << dst ) const Position pos = Position::GetPosition( *unit, dst ); assert( pos.isValidForUnit( unit ) ); if ( _interface ) { const HeroBase * commander = GetCurrentCommander(); assert( commander != nullptr ); TargetInfo targetInfo; targetInfo.defender = unit; TargetsInfo targetsInfo; targetsInfo.push_back( targetInfo ); _interface->RedrawActionSpellCastStatus( Spell( Spell::TELEPORT ), src, commander->GetName(), targetsInfo ); _interface->RedrawActionTeleportSpell( *unit, pos.GetHead()->GetIndex() ); } unit->SetPosition( pos ); } void Battle::Arena::ApplyActionSpellEarthQuake( const Command & /* cmd */ ) { const HeroBase * commander = GetCurrentCommander(); if ( commander == nullptr ) { ERROR_LOG( "Preconditions were not met" ) #ifdef WITH_DEBUG assert( 0 ); #endif return; } std::vector targets = GetEarthQuakeTargets(); DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "number of targets: " << targets.size() ) if ( _interface ) { _interface->RedrawActionSpellCastStatus( Spell( Spell::EARTHQUAKE ), -1, commander->GetName(), {} ); _interface->RedrawActionEarthQuakeSpell( targets ); } const std::pair range = getEarthquakeDamageRange( commander ); const std::vector wallHexPositions = { CASTLE_FIRST_TOP_WALL_POS, CASTLE_SECOND_TOP_WALL_POS, CASTLE_THIRD_TOP_WALL_POS, CASTLE_FOURTH_TOP_WALL_POS }; for ( int position : wallHexPositions ) { const int wallCondition = board[position].GetObject(); if ( wallCondition > 0 ) { uint32_t wallDamage = _randomGenerator.Get( range.first, range.second ); if ( wallDamage > static_cast( wallCondition ) ) { wallDamage = wallCondition; } board[position].SetObject( wallCondition - wallDamage ); } } if ( _towers[0] && _towers[0]->isValid() && _randomGenerator.Get( 1 ) ) { _towers[0]->SetDestroy(); } if ( _towers[2] && _towers[2]->isValid() && _randomGenerator.Get( 1 ) ) { _towers[2]->SetDestroy(); } } void Battle::Arena::ApplyActionSpellMirrorImage( Command & cmd ) { const auto checkParameters = []( const Unit * unit ) { if ( unit == nullptr || !unit->isValid() ) { return false; } const Arena * arena = GetArena(); assert( arena != nullptr ); if ( arena->GetCurrentCommander() == nullptr ) { return false; } return true; }; const int32_t who = cmd.GetNextValue(); Unit * unit = GetTroopBoard( who ); if ( !checkParameters( unit ) ) { ERROR_LOG( "Invalid parameters: " << "who: " << who ) #ifdef WITH_DEBUG assert( 0 ); #endif return; } Indexes distances = Board::GetDistanceIndexes( unit->GetHeadIndex(), 4 ); const int32_t centerIndex = unit->GetHeadIndex(); std::sort( distances.begin(), distances.end(), [centerIndex]( const int32_t index1, const int32_t index2 ) { return Board::GetDistance( centerIndex, index1 ) < Board::GetDistance( centerIndex, index2 ); } ); Indexes::const_iterator it = std::find_if( distances.begin(), distances.end(), [unit]( const int32_t v ) { return Board::isValidMirrorImageIndex( v, unit ); } ); if ( it != distances.end() ) { const HeroBase * commander = GetCurrentCommander(); assert( commander != nullptr ); const Spell mirrorImageSpell( Spell::MIRRORIMAGE ); TargetInfo targetInfo; targetInfo.defender = unit; TargetsInfo targetsInfo; targetsInfo.push_back( targetInfo ); TargetsApplySpell( commander, mirrorImageSpell, targetsInfo ); Unit * mirrorUnit = CreateMirrorImage( *unit ); assert( mirrorUnit != nullptr ); const Position pos = Position::GetPosition( *mirrorUnit, *it ); assert( pos.isValidForUnit( mirrorUnit ) ); DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "set position: " << pos.GetHead()->GetIndex() ) if ( _interface ) { _interface->RedrawActionSpellCastStatus( mirrorImageSpell, who, commander->GetName(), targetsInfo ); _interface->RedrawActionMirrorImageSpell( *unit, pos ); } mirrorUnit->SetPosition( pos ); } else { DEBUG_LOG( DBG_BATTLE, DBG_WARN, "no suitable position found" ) if ( _interface ) { _interface->SetStatus( _( "Spell failed!" ), true ); } } } fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_animation.cpp000066400000000000000000000516321456075706000240450ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include "battle_animation.h" #include "logging.h" #include "monster.h" #include "monster_anim.h" #include "rand.h" RandomizedDelay::RandomizedDelay( const uint32_t delay ) : fheroes2::TimeDelay( delay ) , halfDelay( delay / 2 ) , timerIsSet( false ) {} bool RandomizedDelay::checkDelay() { if ( !timerIsSet ) { // Randomize delay as 0.75 to 1.25 original value setDelay( Rand::Get( 0, halfDelay ) + halfDelay * 3 / 2 ); timerIsSet = true; } const bool res = isPassed(); if ( res ) { reset(); timerIsSet = false; } return res; } AnimationSequence::AnimationSequence( const std::vector & seq ) : _seq( seq ) , _currentFrame( 0 ) {} AnimationSequence & AnimationSequence::operator=( const std::vector & rhs ) { _seq = rhs; _currentFrame = 0; return *this; } int AnimationSequence::playAnimation( bool loop ) { if ( !isValid() ) return 0; if ( isLastFrame() ) { if ( loop ) restartAnimation(); } else { ++_currentFrame; } return _seq[_currentFrame]; } int AnimationSequence::restartAnimation() { _currentFrame = 0; return getFrame(); } int AnimationSequence::getFrame() const { return isValid() ? _seq[_currentFrame] : 0; } size_t AnimationSequence::animationLength() const { return _seq.size(); } int AnimationSequence::firstFrame() const { return isValid() ? _seq.front() : 0; } double AnimationSequence::movementProgress() const { if ( !isValid() ) { return 0; } // We return the progress coefficient for moving creature sprite from one position to another. // At the start and the end of the path we also have creature's static position, // but there is no such position on the cells which creature path through. // So to make the movement start and end more smoothly we add 0.5 to the frame number (since it starts from zero). return ( static_cast( _currentFrame ) + 0.5 ) / static_cast( animationLength() ); } bool AnimationSequence::isLastFrame() const { return _currentFrame == _seq.size() - 1; } bool AnimationSequence::isValid() const { return !_seq.empty(); } AnimationReference::AnimationReference() : _monsterID( Monster::UNKNOWN ) {} AnimationReference::AnimationReference( int monsterID ) : _monsterID( monsterID ) { if ( monsterID < Monster::PEASANT || monsterID > Monster::WATER_ELEMENT ) return; _monsterInfo = Bin_Info::GetMonsterInfo( monsterID ); // STATIC is our default // appendFrames inserts to vector so ref is still valid if ( !appendFrames( _static, Bin_Info::MonsterAnimInfo::STATIC ) ) { // fall back to this, to avoid crashes _static.push_back( 1 ); } // Taking damage appendFrames( _wince, Bin_Info::MonsterAnimInfo::WINCE_UP ); appendFrames( _wince, Bin_Info::MonsterAnimInfo::WINCE_END ); // TODO: play it back together for now appendFrames( _winceUp, Bin_Info::MonsterAnimInfo::WINCE_UP ); appendFrames( _winceDown, Bin_Info::MonsterAnimInfo::WINCE_END ); appendFrames( _death, Bin_Info::MonsterAnimInfo::DEATH ); // Idle animations for ( uint32_t idx = Bin_Info::MonsterAnimInfo::IDLE1; idx < _monsterInfo.idleAnimationCount + Bin_Info::MonsterAnimInfo::IDLE1; ++idx ) { std::vector idleAnim; if ( appendFrames( idleAnim, idx ) ) { _idle.push_back( idleAnim ); } } // Movement sequences _offsetX = _monsterInfo.frameXOffset; // Every unit has MOVE_MAIN anim, use it as a base appendFrames( _moving, Bin_Info::MonsterAnimInfo::MOVE_TILE_START ); appendFrames( _moving, Bin_Info::MonsterAnimInfo::MOVE_MAIN ); appendFrames( _moving, Bin_Info::MonsterAnimInfo::MOVE_TILE_END ); if ( _monsterInfo.hasAnim( Bin_Info::MonsterAnimInfo::MOVE_ONE ) ) { appendFrames( _moveOneTile, Bin_Info::MonsterAnimInfo::MOVE_ONE ); } else { // If there is no animation for one tile movement (fix for LICH and POWER_LICH) // make it from sequent MOVE_START, MOVE_MAIN, MOVE_STOP. appendFrames( _moveOneTile, Bin_Info::MonsterAnimInfo::MOVE_START ); appendFrames( _moveOneTile, Bin_Info::MonsterAnimInfo::MOVE_MAIN ); appendFrames( _moveOneTile, Bin_Info::MonsterAnimInfo::MOVE_STOP ); } // First tile move: 1 + 3 + 4 appendFrames( _moveFirstTile, Bin_Info::MonsterAnimInfo::MOVE_START ); appendFrames( _moveFirstTile, Bin_Info::MonsterAnimInfo::MOVE_MAIN ); appendFrames( _moveFirstTile, Bin_Info::MonsterAnimInfo::MOVE_TILE_END ); // Last tile move: 2 + 3 + 5 appendFrames( _moveLastTile, Bin_Info::MonsterAnimInfo::MOVE_TILE_START ); appendFrames( _moveLastTile, Bin_Info::MonsterAnimInfo::MOVE_MAIN ); appendFrames( _moveLastTile, Bin_Info::MonsterAnimInfo::MOVE_STOP ); // Special for flyers appendFrames( _flying.start, Bin_Info::MonsterAnimInfo::MOVE_START ); appendFrames( _flying.end, Bin_Info::MonsterAnimInfo::MOVE_STOP ); // Attack sequences appendFrames( _melee[Monster_Info::TOP].start, Bin_Info::MonsterAnimInfo::ATTACK1 ); appendFrames( _melee[Monster_Info::TOP].end, Bin_Info::MonsterAnimInfo::ATTACK1_END ); appendFrames( _melee[Monster_Info::FRONT].start, Bin_Info::MonsterAnimInfo::ATTACK2 ); appendFrames( _melee[Monster_Info::FRONT].end, Bin_Info::MonsterAnimInfo::ATTACK2_END ); appendFrames( _melee[Monster_Info::BOTTOM].start, Bin_Info::MonsterAnimInfo::ATTACK3 ); appendFrames( _melee[Monster_Info::BOTTOM].end, Bin_Info::MonsterAnimInfo::ATTACK3_END ); // Use either shooting or breath attack animation as ranged if ( _monsterInfo.hasAnim( Bin_Info::MonsterAnimInfo::SHOOT2 ) ) { appendFrames( _ranged[Monster_Info::TOP].start, Bin_Info::MonsterAnimInfo::SHOOT1 ); appendFrames( _ranged[Monster_Info::TOP].end, Bin_Info::MonsterAnimInfo::SHOOT1_END ); appendFrames( _ranged[Monster_Info::FRONT].start, Bin_Info::MonsterAnimInfo::SHOOT2 ); appendFrames( _ranged[Monster_Info::FRONT].end, Bin_Info::MonsterAnimInfo::SHOOT2_END ); appendFrames( _ranged[Monster_Info::BOTTOM].start, Bin_Info::MonsterAnimInfo::SHOOT3 ); appendFrames( _ranged[Monster_Info::BOTTOM].end, Bin_Info::MonsterAnimInfo::SHOOT3_END ); } else if ( _monsterInfo.hasAnim( Bin_Info::MonsterAnimInfo::DOUBLEHEX2 ) ) { // Only 6 units should have this (in the original game) appendFrames( _ranged[Monster_Info::TOP].start, Bin_Info::MonsterAnimInfo::DOUBLEHEX1 ); appendFrames( _ranged[Monster_Info::TOP].end, Bin_Info::MonsterAnimInfo::DOUBLEHEX1_END ); appendFrames( _ranged[Monster_Info::FRONT].start, Bin_Info::MonsterAnimInfo::DOUBLEHEX2 ); appendFrames( _ranged[Monster_Info::FRONT].end, Bin_Info::MonsterAnimInfo::DOUBLEHEX2_END ); appendFrames( _ranged[Monster_Info::BOTTOM].start, Bin_Info::MonsterAnimInfo::DOUBLEHEX3 ); appendFrames( _ranged[Monster_Info::BOTTOM].end, Bin_Info::MonsterAnimInfo::DOUBLEHEX3_END ); } } bool AnimationReference::appendFrames( std::vector & target, int animID ) { if ( _monsterInfo.hasAnim( animID ) ) { target.insert( target.end(), _monsterInfo.animationFrames.at( animID ).begin(), _monsterInfo.animationFrames.at( animID ).end() ); return true; } return false; } const std::vector & AnimationReference::getAnimationVector( int animState ) const { switch ( animState ) { case Monster_Info::STAND_STILL: case Monster_Info::STATIC: return _static; case Monster_Info::IDLE: // Pick random animation if ( !_idle.empty() && _idle.size() == _monsterInfo.idlePriority.size() ) { Rand::Queue picker; for ( size_t i = 0; i < _idle.size(); ++i ) { picker.Push( static_cast( i ), static_cast( _monsterInfo.idlePriority[i] * 100 ) ); } // picker is expected to return at least 0 const size_t id = static_cast( picker.Get() ); return _idle[id]; } break; case Monster_Info::MOVE_START: return _moveFirstTile; case Monster_Info::MOVING: return _moving; case Monster_Info::MOVE_END: return _moveLastTile; case Monster_Info::MOVE_QUICK: return _moveOneTile; case Monster_Info::FLY_UP: return _flying.start; case Monster_Info::FLY_LAND: return _flying.end; case Monster_Info::MELEE_TOP: return _melee[Monster_Info::TOP].start; case Monster_Info::MELEE_TOP_END: return _melee[Monster_Info::TOP].end; case Monster_Info::MELEE_FRONT: return _melee[Monster_Info::FRONT].start; case Monster_Info::MELEE_FRONT_END: return _melee[Monster_Info::FRONT].end; case Monster_Info::MELEE_BOT: return _melee[Monster_Info::BOTTOM].start; case Monster_Info::MELEE_BOT_END: return _melee[Monster_Info::BOTTOM].end; case Monster_Info::RANG_TOP: return _ranged[Monster_Info::TOP].start; case Monster_Info::RANG_TOP_END: return _ranged[Monster_Info::TOP].end; case Monster_Info::RANG_FRONT: return _ranged[Monster_Info::FRONT].start; case Monster_Info::RANG_FRONT_END: return _ranged[Monster_Info::FRONT].end; case Monster_Info::RANG_BOT: return _ranged[Monster_Info::BOTTOM].start; case Monster_Info::RANG_BOT_END: return _ranged[Monster_Info::BOTTOM].end; case Monster_Info::WNCE_UP: return _winceUp; case Monster_Info::WNCE_DOWN: return _winceDown; case Monster_Info::WNCE: return _wince; case Monster_Info::KILL: return _death; default: break; } return _static; } std::vector AnimationReference::getAnimationOffset( int animState ) const { std::vector offset; switch ( animState ) { case Monster_Info::STAND_STILL: case Monster_Info::STATIC: offset.resize( _static.size(), 0 ); break; case Monster_Info::IDLE: offset.resize( _idle.front().size(), 0 ); break; case Monster_Info::MOVE_START: offset.insert( offset.end(), _offsetX[Bin_Info::MonsterAnimInfo::MOVE_START].begin(), _offsetX[Bin_Info::MonsterAnimInfo::MOVE_START].end() ); offset.insert( offset.end(), _offsetX[Bin_Info::MonsterAnimInfo::MOVE_MAIN].begin(), _offsetX[Bin_Info::MonsterAnimInfo::MOVE_MAIN].end() ); offset.insert( offset.end(), _offsetX[Bin_Info::MonsterAnimInfo::MOVE_TILE_END].begin(), _offsetX[Bin_Info::MonsterAnimInfo::MOVE_TILE_END].end() ); break; case Monster_Info::MOVING: offset.insert( offset.end(), _offsetX[Bin_Info::MonsterAnimInfo::MOVE_TILE_START].begin(), _offsetX[Bin_Info::MonsterAnimInfo::MOVE_TILE_START].end() ); offset.insert( offset.end(), _offsetX[Bin_Info::MonsterAnimInfo::MOVE_MAIN].begin(), _offsetX[Bin_Info::MonsterAnimInfo::MOVE_MAIN].end() ); offset.insert( offset.end(), _offsetX[Bin_Info::MonsterAnimInfo::MOVE_TILE_END].begin(), _offsetX[Bin_Info::MonsterAnimInfo::MOVE_TILE_END].end() ); break; case Monster_Info::MOVE_END: offset.insert( offset.end(), _offsetX[Bin_Info::MonsterAnimInfo::MOVE_TILE_START].begin(), _offsetX[Bin_Info::MonsterAnimInfo::MOVE_TILE_START].end() ); offset.insert( offset.end(), _offsetX[Bin_Info::MonsterAnimInfo::MOVE_MAIN].begin(), _offsetX[Bin_Info::MonsterAnimInfo::MOVE_MAIN].end() ); offset.insert( offset.end(), _offsetX[Bin_Info::MonsterAnimInfo::MOVE_STOP].begin(), _offsetX[Bin_Info::MonsterAnimInfo::MOVE_STOP].end() ); break; case Monster_Info::MOVE_QUICK: offset.insert( offset.end(), _offsetX[Bin_Info::MonsterAnimInfo::MOVE_START].begin(), _offsetX[Bin_Info::MonsterAnimInfo::MOVE_START].end() ); offset.insert( offset.end(), _offsetX[Bin_Info::MonsterAnimInfo::MOVE_MAIN].begin(), _offsetX[Bin_Info::MonsterAnimInfo::MOVE_MAIN].end() ); offset.insert( offset.end(), _offsetX[Bin_Info::MonsterAnimInfo::MOVE_STOP].begin(), _offsetX[Bin_Info::MonsterAnimInfo::MOVE_STOP].end() ); break; case Monster_Info::FLY_UP: offset.resize( _flying.start.size(), 0 ); break; case Monster_Info::FLY_LAND: offset.resize( _flying.end.size(), 0 ); break; case Monster_Info::MELEE_TOP: offset.resize( _melee[Monster_Info::TOP].start.size(), 0 ); break; case Monster_Info::MELEE_TOP_END: offset.resize( _melee[Monster_Info::TOP].end.size(), 0 ); break; case Monster_Info::MELEE_FRONT: offset.resize( _melee[Monster_Info::FRONT].start.size(), 0 ); break; case Monster_Info::MELEE_FRONT_END: offset.resize( _melee[Monster_Info::FRONT].end.size(), 0 ); break; case Monster_Info::MELEE_BOT: offset.resize( _melee[Monster_Info::BOTTOM].start.size(), 0 ); break; case Monster_Info::MELEE_BOT_END: offset.resize( _melee[Monster_Info::BOTTOM].end.size(), 0 ); break; case Monster_Info::RANG_TOP: offset.resize( _ranged[Monster_Info::TOP].start.size(), 0 ); break; case Monster_Info::RANG_TOP_END: offset.resize( _ranged[Monster_Info::TOP].end.size(), 0 ); break; case Monster_Info::RANG_FRONT: offset.resize( _ranged[Monster_Info::FRONT].start.size(), 0 ); break; case Monster_Info::RANG_FRONT_END: offset.resize( _ranged[Monster_Info::FRONT].end.size(), 0 ); break; case Monster_Info::RANG_BOT: offset.resize( _ranged[Monster_Info::BOTTOM].start.size(), 0 ); break; case Monster_Info::RANG_BOT_END: offset.resize( _ranged[Monster_Info::BOTTOM].end.size(), 0 ); break; case Monster_Info::WNCE_UP: offset.resize( _winceUp.size(), 0 ); break; case Monster_Info::WNCE_DOWN: offset.resize( _winceDown.size(), 0 ); break; case Monster_Info::WNCE: offset.resize( _wince.size(), 0 ); break; case Monster_Info::KILL: offset.resize( _death.size(), 0 ); break; default: break; } return offset; } uint32_t AnimationReference::getMoveSpeed() const { return _monsterInfo.moveSpeed; } uint32_t AnimationReference::getFlightSpeed() const { return _monsterInfo.flightSpeed; } uint32_t AnimationReference::getShootingSpeed() const { return _monsterInfo.shootSpeed; } fheroes2::Point AnimationReference::getBlindOffset() const { return _monsterInfo.eyePosition; } int AnimationReference::getTroopCountOffset( bool isReflect ) const { return isReflect ? _monsterInfo.troopCountOffsetRight : _monsterInfo.troopCountOffsetLeft; } fheroes2::Point AnimationReference::getProjectileOffset( size_t direction ) const { if ( _monsterInfo.projectileOffset.size() > direction ) { return _monsterInfo.projectileOffset[direction]; } return fheroes2::Point(); } uint32_t AnimationReference::getIdleDelay() const { return _monsterInfo.idleAnimationDelay; } AnimationState::AnimationState( int monsterID ) : AnimationReference( monsterID ) , _animState( Monster_Info::STATIC ) , _currentSequence( _static ) {} bool AnimationState::switchAnimation( int animState, bool reverse ) { std::vector seq = getAnimationVector( animState ); if ( !seq.empty() ) { _animState = animState; if ( reverse ) std::reverse( seq.begin(), seq.end() ); _currentSequence = seq; _currentSequence.restartAnimation(); return true; } else { DEBUG_LOG( DBG_GAME, DBG_WARN, " AnimationState switched to invalid anim " << animState << " length " << _currentSequence.animationLength() ) } return false; } bool AnimationState::switchAnimation( const std::vector & animationList, bool reverse ) { std::vector combinedAnimation; for ( std::vector::const_iterator it = animationList.begin(); it != animationList.end(); ++it ) { const std::vector & seq = getAnimationVector( *it ); if ( !seq.empty() ) { _animState = *it; combinedAnimation.insert( combinedAnimation.end(), seq.begin(), seq.end() ); } } if ( !combinedAnimation.empty() ) { if ( reverse ) std::reverse( combinedAnimation.begin(), combinedAnimation.end() ); _currentSequence = combinedAnimation; _currentSequence.restartAnimation(); return true; } else { DEBUG_LOG( DBG_GAME, DBG_WARN, " AnimationState switched to invalid anim list of length " << animationList.size() ) } return false; } int AnimationState::getCurrentState() const { return _animState; } int AnimationState::playAnimation( bool loop ) { return _currentSequence.playAnimation( loop ); } int AnimationState::restartAnimation() { return _currentSequence.restartAnimation(); } int AnimationState::getFrame() const { return _currentSequence.getFrame(); } size_t AnimationState::animationLength() const { return _currentSequence.animationLength(); } int AnimationState::firstFrame() const { return _currentSequence.firstFrame(); } int32_t AnimationState::getCurrentFrameXOffset() const { // Return the horizontal frame offset to use in rendering. std::vector animSubsequences; // The animations consist of some subsequences. Put into vector the animation subsequences queue. switch ( _animState ) { case Monster_Info::MOVE_START: animSubsequences = { Bin_Info::MonsterAnimInfo::MOVE_START, Bin_Info::MonsterAnimInfo::MOVE_MAIN, Bin_Info::MonsterAnimInfo::MOVE_TILE_END }; break; case Monster_Info::MOVING: { animSubsequences = { Bin_Info::MonsterAnimInfo::MOVE_TILE_START, Bin_Info::MonsterAnimInfo::MOVE_MAIN, Bin_Info::MonsterAnimInfo::MOVE_TILE_END }; break; } case Monster_Info::MOVE_END: { animSubsequences = { Bin_Info::MonsterAnimInfo::MOVE_TILE_START, Bin_Info::MonsterAnimInfo::MOVE_MAIN, Bin_Info::MonsterAnimInfo::MOVE_STOP }; break; } case Monster_Info::MOVE_QUICK: { animSubsequences = { Bin_Info::MonsterAnimInfo::MOVE_START, Bin_Info::MonsterAnimInfo::MOVE_MAIN, Bin_Info::MonsterAnimInfo::MOVE_STOP }; break; } default: // If there is no horizontal offset data for current animation state, return 0 as offset. return 0; } // The frame number of current subsequence start. size_t subequenceStart = 0; // The frame number in the full animation sequence, which include subsequences. const size_t currentFrame = _currentSequence.getCurrentFrameId(); // Get frame offset from _offsetX, analyzing in which subsequence it is. for ( const int32_t animSubsequence : animSubsequences ) { // Get the current subsequence end (it is the frame number after the last subsequence frame). const size_t subequenceEnd = _offsetX[animSubsequence].size() + subequenceStart; if ( currentFrame < subequenceEnd ) { return _offsetX[animSubsequence][currentFrame - subequenceStart]; } subequenceStart = subequenceEnd; } // If there is no horizontal offset data for currentFrame, return 0 as offset. DEBUG_LOG( DBG_GAME, DBG_WARN, "Frame " << currentFrame << " is outside _offsetX [0 - " << subequenceStart << "] for animation state " << _animState ) return 0; } double AnimationState::movementProgress() const { return _currentSequence.movementProgress(); } bool AnimationState::isLastFrame() const { return _currentSequence.isLastFrame(); } bool AnimationState::isValid() const { return _currentSequence.isValid(); } fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_animation.h000066400000000000000000000122441456075706000235060ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2BATTLE_ANIMATION_H #define H2BATTLE_ANIMATION_H #include #include #include #include "bin_info.h" #include "math_base.h" #include "timing.h" // This timer is used for randomized idle animation delays, automatically setting it in range of 75%-125% of the intended value class RandomizedDelay : protected fheroes2::TimeDelay { public: explicit RandomizedDelay( const uint32_t delay ); // This function triggers the current delay, returning true if it is passed and automatically resets the timer. bool checkDelay(); private: uint32_t halfDelay; bool timerIsSet; }; struct MonsterReturnAnim { std::vector start; std::vector end; }; class AnimationSequence final { public: explicit AnimationSequence( const std::vector & seq ); AnimationSequence( const AnimationSequence & ) = delete; ~AnimationSequence() = default; AnimationSequence & operator=( const AnimationSequence & ) = delete; AnimationSequence & operator=( const std::vector & rhs ); int playAnimation( bool loop = false ); int restartAnimation(); int getFrame() const; int firstFrame() const; size_t animationLength() const; double movementProgress() const; bool isLastFrame() const; bool isValid() const; size_t getCurrentFrameId() const { return _currentFrame; } private: std::vector _seq; size_t _currentFrame; }; class AnimationReference { public: AnimationReference(); explicit AnimationReference( int id ); AnimationReference( const AnimationReference & ) = delete; AnimationReference( AnimationReference && ) = delete; virtual ~AnimationReference() = default; AnimationReference & operator=( const AnimationReference & ) = delete; AnimationReference & operator=( AnimationReference && ) = default; const std::vector & getAnimationVector( int animState ) const; std::vector getAnimationOffset( int animState ) const; uint32_t getMoveSpeed() const; uint32_t getFlightSpeed() const; uint32_t getShootingSpeed() const; fheroes2::Point getBlindOffset() const; fheroes2::Point getProjectileOffset( size_t direction ) const; int getTroopCountOffset( bool isReflect ) const; uint32_t getIdleDelay() const; protected: int _monsterID; Bin_Info::MonsterAnimInfo _monsterInfo; std::vector _static; std::vector _moveFirstTile; std::vector _moving; std::vector _moveLastTile; std::vector _moveOneTile; MonsterReturnAnim _flying; std::vector _winceUp; std::vector _winceDown; std::vector _wince; std::vector _death; MonsterReturnAnim _melee[3]; MonsterReturnAnim _ranged[3]; std::vector> _idle; std::vector> _offsetX; bool appendFrames( std::vector & target, int animID ); }; class AnimationState final : public AnimationReference { public: explicit AnimationState( int monsterID ); AnimationState( const AnimationState & ) = delete; ~AnimationState() override = default; AnimationState & operator=( const AnimationState & ) = delete; bool switchAnimation( int animstate, bool reverse = false ); bool switchAnimation( const std::vector & animationList, bool reverse = false ); int getCurrentState() const; // pass-down methods int playAnimation( bool loop = false ); int restartAnimation(); int getFrame() const; int firstFrame() const; int32_t getCurrentFrameXOffset() const; size_t animationLength() const; double movementProgress() const; bool isLastFrame() const; bool isValid() const; private: int _animState; AnimationSequence _currentSequence; }; #endif fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_arena.cpp000066400000000000000000001217321456075706000231530ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "battle_arena.h" #include #include #include #include #include #include #include #include #include #include #include "ai.h" #include "army.h" #include "army_troop.h" #include "artifact.h" #include "artifact_info.h" #include "audio.h" #include "battle_army.h" #include "battle_bridge.h" #include "battle_catapult.h" #include "battle_cell.h" #include "battle_command.h" #include "battle_interface.h" #include "battle_tower.h" #include "battle_troop.h" #include "castle.h" #include "color.h" #include "ground.h" #include "heroes.h" #include "heroes_base.h" #include "icn.h" #include "localevent.h" #include "logging.h" #include "maps.h" #include "maps_tiles.h" #include "math_base.h" #include "monster.h" #include "players.h" #include "rand.h" #include "skill.h" #include "speed.h" #include "spell_info.h" #include "tools.h" #include "translations.h" #include "world.h" namespace { Battle::Arena * arena = nullptr; int GetCovr( int ground, std::mt19937 & gen ) { std::vector covrs; switch ( ground ) { case Maps::Ground::SNOW: covrs.push_back( ICN::COVR0007 ); covrs.push_back( ICN::COVR0008 ); covrs.push_back( ICN::COVR0009 ); covrs.push_back( ICN::COVR0010 ); covrs.push_back( ICN::COVR0011 ); covrs.push_back( ICN::COVR0012 ); break; case Maps::Ground::WASTELAND: covrs.push_back( ICN::COVR0019 ); covrs.push_back( ICN::COVR0020 ); covrs.push_back( ICN::COVR0021 ); covrs.push_back( ICN::COVR0022 ); covrs.push_back( ICN::COVR0023 ); covrs.push_back( ICN::COVR0024 ); break; case Maps::Ground::DIRT: covrs.push_back( ICN::COVR0013 ); covrs.push_back( ICN::COVR0014 ); covrs.push_back( ICN::COVR0015 ); covrs.push_back( ICN::COVR0016 ); covrs.push_back( ICN::COVR0017 ); covrs.push_back( ICN::COVR0018 ); break; case Maps::Ground::GRASS: covrs.push_back( ICN::COVR0001 ); covrs.push_back( ICN::COVR0002 ); covrs.push_back( ICN::COVR0003 ); covrs.push_back( ICN::COVR0004 ); covrs.push_back( ICN::COVR0005 ); covrs.push_back( ICN::COVR0006 ); break; default: break; } return covrs.empty() ? ICN::UNKNOWN : Rand::GetWithGen( covrs, gen ); } Battle::Unit * GetCurrentUnit( Battle::Units & units1, Battle::Units & units2, const bool units1GoFirst, const bool ordersMode ) { Battle::Unit * result = nullptr; const auto unitFilter = []( const Battle::Unit * unit ) { return unit->GetSpeed() > Speed::STANDING; }; Battle::Units::iterator it1 = std::find_if( units1.begin(), units1.end(), unitFilter ); Battle::Units::iterator it2 = std::find_if( units2.begin(), units2.end(), unitFilter ); if ( it1 != units1.end() && it2 != units2.end() ) { if ( ( *it1 )->GetSpeed() == ( *it2 )->GetSpeed() ) { result = units1GoFirst ? *it1 : *it2; } else { result = ( ( *it1 )->GetSpeed() > ( *it2 )->GetSpeed() ) ? *it1 : *it2; } } else if ( it1 != units1.end() ) { result = *it1; } else if ( it2 != units2.end() ) { result = *it2; } if ( result && ordersMode ) { if ( it1 != units1.end() && result == *it1 ) { units1.erase( it1 ); } else if ( it2 != units2.end() && result == *it2 ) { units2.erase( it2 ); } } return result; } Battle::Unit * GetCurrentUnit( const Battle::Force & army1, const Battle::Force & army2, const int preferredColor ) { Battle::Units units1( army1.getUnits(), Battle::Units::REMOVE_INVALID_UNITS ); Battle::Units units2( army2.getUnits(), Battle::Units::REMOVE_INVALID_UNITS ); units1.SortFastest(); units2.SortFastest(); Battle::Unit * result = GetCurrentUnit( units1, units2, preferredColor != army2.GetColor(), false ); if ( result == nullptr ) { return result; } assert( result->isValid() ); return result; } void UpdateOrderOfUnits( const Battle::Force & army1, const Battle::Force & army2, const Battle::Unit * currentUnit, int preferredColor, const Battle::Units & orderHistory, Battle::Units & orderOfUnits ) { orderOfUnits.assign( orderHistory.begin(), orderHistory.end() ); Battle::Units units1( army1.getUnits(), Battle::Units::REMOVE_INVALID_UNITS ); Battle::Units units2( army2.getUnits(), Battle::Units::REMOVE_INVALID_UNITS ); units1.SortFastest(); units2.SortFastest(); while ( true ) { Battle::Unit * unit = GetCurrentUnit( units1, units2, preferredColor != army2.GetColor(), true ); if ( unit == nullptr ) { break; } assert( unit->isValid() ); if ( unit == currentUnit ) { continue; } preferredColor = ( unit->GetArmyColor() == army1.GetColor() ) ? army2.GetColor() : army1.GetColor(); orderOfUnits.push_back( unit ); } } } Battle::Arena * Battle::GetArena() { return arena; } const Castle * Battle::Arena::GetCastle() { assert( arena != nullptr ); return arena->castle; } Battle::Bridge * Battle::Arena::GetBridge() { assert( arena != nullptr ); return arena->_bridge.get(); } Battle::Board * Battle::Arena::GetBoard() { assert( arena != nullptr ); return &arena->board; } Battle::Graveyard * Battle::Arena::GetGraveyard() { assert( arena != nullptr ); return &arena->graveyard; } Battle::Interface * Battle::Arena::GetInterface() { assert( arena != nullptr ); return arena->_interface.get(); } Battle::Tower * Battle::Arena::GetTower( const TowerType type ) { assert( arena != nullptr ); switch ( type ) { case TowerType::TWR_LEFT: return arena->_towers[0].get(); case TowerType::TWR_CENTER: return arena->_towers[1].get(); case TowerType::TWR_RIGHT: return arena->_towers[2].get(); default: break; } return nullptr; } bool Battle::Arena::isAnyTowerPresent() { assert( arena != nullptr ); return std::any_of( arena->_towers.begin(), arena->_towers.end(), []( const auto & twr ) { return twr && twr->isValid(); } ); } Battle::Arena::Arena( Army & army1, Army & army2, const int32_t tileIndex, const bool isShowInterface, Rand::DeterministicRandomGenerator & randomGenerator ) : castle( world.getCastleEntrance( Maps::GetPoint( tileIndex ) ) ) , _isTown( castle != nullptr ) , _randomGenerator( randomGenerator ) { usage_spells.reserve( 20 ); assert( arena == nullptr ); arena = this; _army1 = std::make_unique( army1, false, _uidGenerator ); _army2 = std::make_unique( army2, true, _uidGenerator ); // If this is a siege of a town, then there is in fact no castle if ( castle && !castle->isCastle() ) { castle = nullptr; } // init interface if ( isShowInterface ) { _interface = std::make_unique( *this, tileIndex ); board.SetArea( _interface->GetArea() ); _orderOfUnits = std::make_shared(); _orderOfUnits->reserve( 25 ); _interface->SetOrderOfUnits( _orderOfUnits ); } else { // no interface - force auto battle mode for human player if ( army1.isControlHuman() ) { _autoBattleColors |= army1.GetColor(); } if ( army2.isControlHuman() ) { _autoBattleColors |= army2.GetColor(); } } if ( castle ) { if ( castle->isBuild( BUILD_LEFTTURRET ) ) { _towers[0] = std::make_unique( *castle, TowerType::TWR_LEFT, _uidGenerator.GetUnique() ); } _towers[1] = std::make_unique( *castle, TowerType::TWR_CENTER, _uidGenerator.GetUnique() ); if ( castle->isBuild( BUILD_RIGHTTURRET ) ) { _towers[2] = std::make_unique( *castle, TowerType::TWR_RIGHT, _uidGenerator.GetUnique() ); } if ( _army1->GetCommander() ) { _catapult = std::make_unique( *_army1->GetCommander() ); } _bridge = std::make_unique(); // catapult cell board[CATAPULT_POS].SetObject( 1 ); // wall (3,2,1,0) const int wallObject = castle->isFortificationBuild() ? 3 : 2; board[CASTLE_FIRST_TOP_WALL_POS].SetObject( wallObject ); board[CASTLE_SECOND_TOP_WALL_POS].SetObject( wallObject ); board[CASTLE_THIRD_TOP_WALL_POS].SetObject( wallObject ); board[CASTLE_FOURTH_TOP_WALL_POS].SetObject( wallObject ); // tower board[CASTLE_TOP_GATE_TOWER_POS].SetObject( 2 ); board[CASTLE_BOTTOM_GATE_TOWER_POS].SetObject( 2 ); // archers tower board[CASTLE_TOP_ARCHER_TOWER_POS].SetObject( 2 ); board[CASTLE_BOTTOM_ARCHER_TOWER_POS].SetObject( 2 ); // bridge board[CASTLE_GATE_POS].SetObject( 1 ); } else // set obstacles { std::mt19937 seededGen( world.GetMapSeed() + static_cast( tileIndex ) ); _covrIcnId = Rand::GetWithGen( 0, 99, seededGen ) < 40 ? GetCovr( world.GetTiles( tileIndex ).GetGround(), seededGen ) : ICN::UNKNOWN; if ( _covrIcnId != ICN::UNKNOWN ) { board.SetCovrObjects( _covrIcnId ); } board.SetCobjObjects( world.GetTiles( tileIndex ), seededGen ); } AI::Get().battleBegins(); if ( _interface ) { _interface->fullRedraw(); // Wait for the end of M82::PREBATTL playback. Make sure that we check the music status first as HandleEvents() call is not instant. LocalEvent & le = LocalEvent::Get(); while ( Mixer::isPlaying( -1 ) && le.HandleEvents() ) { if ( le.KeyPress( fheroes2::Key::KEY_ESCAPE ) || le.MouseClickMiddle() || le.MouseClickRight() ) { // Cancel waiting for M82::PREBATTL to over and start the battle. break; } } } } Battle::Arena::~Arena() { assert( arena == this ); arena = nullptr; } void Battle::Arena::UnitTurn( const Units & orderHistory ) { assert( _currentUnit && _currentUnit->isValid() ); DEBUG_LOG( DBG_BATTLE, DBG_TRACE, _currentUnit->String( true ) ) if ( _currentUnit->isAffectedByMorale() ) { _currentUnit->SetRandomMorale( _randomGenerator ); } assert( !_currentUnit->AllModes( MORALE_GOOD | MORALE_BAD ) ); bool endOfTurn = false; while ( !endOfTurn ) { // There should be no dead units on the board at the beginning of each iteration assert( std::all_of( board.begin(), board.end(), []( const Cell & cell ) { return ( cell.GetUnit() == nullptr || cell.GetUnit()->isValid() ); } ) ); Actions actions; if ( _interface ) { _interface->getPendingActions( actions ); } if ( !actions.empty() ) { // Pending actions from the user interface (such as toggling auto battle) have "already occurred" and // therefore should be handled first, before any other actions. Just skip the rest of the branches. } else if ( _currentUnit->GetSpeed() == Speed::STANDING ) { // Unit has either finished its turn, is dead, or has become immovable due to some spell. Even if the // unit died or has become immovable during its turn without performing any action, its turn is still // considered completed. _currentUnit->SetModes( TR_MOVED ); endOfTurn = true; } else if ( _currentUnit->Modes( MORALE_BAD ) ) { actions.emplace_back( Command::MORALE, _currentUnit->GetUID(), false ); } else { // This unit will certainly perform at least one full-fledged action _lastActiveUnitArmyColor = _currentUnit->GetArmyColor(); if ( _bridge ) { _bridge->SetPassability( *_currentUnit ); } if ( ( _currentUnit->GetCurrentControl() & CONTROL_AI ) || ( _currentUnit->GetCurrentColor() & _autoBattleColors ) ) { AI::Get().BattleTurn( *this, *_currentUnit, actions ); } else { assert( _interface != nullptr ); _interface->HumanTurn( *_currentUnit, actions ); } } const uint32_t newSeed = std::accumulate( actions.cbegin(), actions.cend(), _randomGenerator.GetSeed(), []( const uint32_t seed, const Command & cmd ) { return cmd.updateSeed( seed ); } ); _randomGenerator.UpdateSeed( newSeed ); while ( !actions.empty() ) { ApplyAction( actions.front() ); actions.pop_front(); board.removeDeadUnits(); if ( _orderOfUnits ) { // Applied action could kill someone or affect the speed of some unit, update the order of units UpdateOrderOfUnits( *_army1, *_army2, _currentUnit, GetOppositeColor( _currentUnit->GetArmyColor() ), orderHistory, *_orderOfUnits ); } if ( !BattleValid() ) { endOfTurn = true; break; } if ( _currentUnit->AllModes( TR_MOVED | MORALE_GOOD ) && !_currentUnit->Modes( TR_SKIP ) && _currentUnit->GetSpeed( false, true ) > Speed::STANDING ) { actions.emplace_back( Command::MORALE, _currentUnit->GetUID(), true ); } } // There should be no dead units on the board at the end of each iteration assert( std::all_of( board.begin(), board.end(), []( const Cell & cell ) { return ( cell.GetUnit() == nullptr || cell.GetUnit()->isValid() ); } ) ); } } bool Battle::Arena::BattleValid() const { return _army1->isValid() && _army2->isValid() && 0 == result_game.army1 && 0 == result_game.army2; } void Battle::Arena::Turns() { ++_turnNumber; DEBUG_LOG( DBG_BATTLE, DBG_TRACE, _turnNumber ) if ( _interface ) { _interface->RedrawActionNewTurn(); } _army1->NewTurn(); _army2->NewTurn(); // History of unit order on the current turn Units orderHistory; if ( _orderOfUnits ) { orderHistory.reserve( 25 ); // Build the initial order of units UpdateOrderOfUnits( *_army1, *_army2, nullptr, GetOppositeColor( _lastActiveUnitArmyColor ), orderHistory, *_orderOfUnits ); } { bool towersActed = false; bool catapultActed = false; while ( BattleValid() ) { // We can get the nullptr here if there are no units left waiting for their turn _currentUnit = GetCurrentUnit( *_army1, *_army2, GetOppositeColor( _lastActiveUnitArmyColor ) ); if ( _orderOfUnits ) { // Add unit to the history if ( _currentUnit ) { orderHistory.push_back( _currentUnit ); } // Update the order of units UpdateOrderOfUnits( *_army1, *_army2, _currentUnit, GetOppositeColor( _currentUnit ? _currentUnit->GetArmyColor() : _lastActiveUnitArmyColor ), orderHistory, *_orderOfUnits ); } if ( castle ) { // Catapult acts either during the turn of the first unit from the attacking army, or at the end of the // turn if none of the units from the attacking army are able to act (for example, all are blinded) if ( !catapultActed && ( _currentUnit == nullptr || _currentUnit->GetColor() == _army1->GetColor() ) ) { CatapultAction(); catapultActed = true; } // Castle towers act either during the turn of the first unit from the defending army, or at the end of // the turn if none of the units from the defending army are able to act (for example, all are blinded) if ( !towersActed && ( _currentUnit == nullptr || _currentUnit->GetColor() == _army2->GetColor() ) ) { const auto towerAction = [this, &orderHistory]( const size_t idx ) { assert( idx < std::size( _towers ) ); if ( _towers[idx] == nullptr || !_towers[idx]->isValid() ) { return; } TowerAction( *_towers[idx] ); if ( _orderOfUnits ) { // Tower could kill someone, update the order of units UpdateOrderOfUnits( *_army1, *_army2, _currentUnit, GetOppositeColor( _currentUnit ? _currentUnit->GetArmyColor() : _lastActiveUnitArmyColor ), orderHistory, *_orderOfUnits ); } }; towerAction( 1 ); towerAction( 0 ); towerAction( 2 ); towersActed = true; // If the towers have killed the last enemy unit, the battle is over if ( !BattleValid() ) { break; } } } if ( _currentUnit == nullptr ) { // There are no units left waiting for their turn break; } UnitTurn( orderHistory ); } } // Check if the battle is over if ( !_army1->isValid() || ( result_game.army1 & ( RESULT_RETREAT | RESULT_SURRENDER ) ) ) { result_game.army1 |= RESULT_LOSS; // Check if any of the original troops in the army2 are still alive result_game.army2 = _army2->isValid( false ) ? RESULT_WINS : RESULT_LOSS; } else if ( !_army2->isValid() || ( result_game.army2 & ( RESULT_RETREAT | RESULT_SURRENDER ) ) ) { result_game.army2 |= RESULT_LOSS; // Check if any of the original troops in the army1 are still alive result_game.army1 = _army1->isValid( false ) ? RESULT_WINS : RESULT_LOSS; } // If the battle is over, calculate the experience and the number of units killed if ( result_game.army1 || result_game.army2 ) { result_game.exp1 = _army2->GetDeadHitPoints(); result_game.exp2 = _army1->GetDeadHitPoints(); const HeroBase * army1Commander = _army1->GetCommander(); const HeroBase * army2Commander = _army2->GetCommander(); // Attacker (or defender) gets an experience bonus if the enemy army was under the command of a hero who was defeated (i.e. did not retreat or surrender) if ( army1Commander && army1Commander->isHeroes() && !( result_game.army1 & ( RESULT_RETREAT | RESULT_SURRENDER ) ) ) { result_game.exp2 += 500; } if ( army2Commander && army2Commander->isHeroes() && !( result_game.army2 & ( RESULT_RETREAT | RESULT_SURRENDER ) ) ) { result_game.exp1 += 500; } // Attacker gets an additional experience bonus after successfully besieging a town or castle if ( _isTown ) { result_game.exp1 += 500; } const Force * army_loss = ( result_game.army1 & RESULT_LOSS ? _army1.get() : ( result_game.army2 & RESULT_LOSS ? _army2.get() : nullptr ) ); result_game.killed = army_loss ? army_loss->GetDeadCounts() : 0; } } void Battle::Arena::TowerAction( const Tower & twr ) { // There should be no dead units on the board at this moment assert( std::all_of( board.begin(), board.end(), []( const Cell & cell ) { return ( cell.GetUnit() == nullptr || cell.GetUnit()->isValid() ); } ) ); // Target unit and its threat level std::pair targetInfo{ nullptr, INT32_MIN }; for ( const Cell & cell : board ) { const Unit * unit = cell.GetUnit(); if ( unit == nullptr || unit->GetColor() == twr.GetColor() || ( unit->isWide() && unit->GetTailIndex() == cell.GetIndex() ) ) { continue; } const int32_t unitThreatLevel = unit->evaluateThreatForUnit( twr ); if ( targetInfo.first == nullptr || targetInfo.second < unitThreatLevel ) { targetInfo = { unit, unitThreatLevel }; } } if ( targetInfo.first == nullptr ) { DEBUG_LOG( DBG_BATTLE, DBG_INFO, "No target found for the tower" ) return; } using TowerGetTypeUnderlyingType = typename std::underlying_type_t; static_assert( std::is_same_v, "Type of Tower::GetType() has been changed, check the logic below" ); Command cmd( Command::TOWER, static_cast( twr.GetType() ), targetInfo.first->GetUID() ); ApplyAction( cmd ); board.removeDeadUnits(); } void Battle::Arena::CatapultAction() { if ( _catapult ) { uint32_t shots = _catapult->GetShots(); std::map stateOfCatapultTargets = { { CastleDefenseElement::WALL1, GetCastleTargetValue( CastleDefenseElement::WALL1 ) }, { CastleDefenseElement::WALL2, GetCastleTargetValue( CastleDefenseElement::WALL2 ) }, { CastleDefenseElement::WALL3, GetCastleTargetValue( CastleDefenseElement::WALL3 ) }, { CastleDefenseElement::WALL4, GetCastleTargetValue( CastleDefenseElement::WALL4 ) }, { CastleDefenseElement::TOWER1, GetCastleTargetValue( CastleDefenseElement::TOWER1 ) }, { CastleDefenseElement::TOWER2, GetCastleTargetValue( CastleDefenseElement::TOWER2 ) }, { CastleDefenseElement::BRIDGE, GetCastleTargetValue( CastleDefenseElement::BRIDGE ) }, { CastleDefenseElement::CENTRAL_TOWER, GetCastleTargetValue( CastleDefenseElement::CENTRAL_TOWER ) } }; Command cmd( Command::CATAPULT ); cmd << shots; while ( shots-- ) { const CastleDefenseElement target = Catapult::GetTarget( stateOfCatapultTargets, _randomGenerator ); const uint32_t damage = std::min( _catapult->GetDamage( _randomGenerator ), stateOfCatapultTargets[target] ); const bool hit = _catapult->IsNextShotHit( _randomGenerator ); using TargetUnderlyingType = std::underlying_type_t; cmd << static_cast( target ) << damage << ( hit ? 1 : 0 ); if ( hit ) { stateOfCatapultTargets[target] -= damage; } } // Preserve the order of shots - command arguments will be extracted in reverse order std::reverse( cmd.begin(), cmd.end() ); ApplyAction( cmd ); } } Battle::Indexes Battle::Arena::GetPath( const Unit & unit, const Position & position ) { const Indexes result = _battlePathfinder.buildPath( unit, position ); if ( IS_DEBUG( DBG_BATTLE, DBG_TRACE ) && !result.empty() ) { std::string pathStr; pathStr.reserve( Speed::INSTANT * 4 ); std::for_each( result.begin(), result.end(), [&pathStr]( const int32_t item ) { pathStr += std::to_string( item ); pathStr += ", "; } ); DEBUG_LOG( DBG_BATTLE, DBG_TRACE, pathStr ) } return result; } Battle::Unit * Battle::Arena::GetTroopBoard( int32_t index ) { return Board::isValidIndex( index ) ? board[index].GetUnit() : nullptr; } const Battle::Unit * Battle::Arena::GetTroopBoard( int32_t index ) const { return Board::isValidIndex( index ) ? board[index].GetUnit() : nullptr; } const HeroBase * Battle::Arena::GetCommander1() const { return _army1->GetCommander(); } const HeroBase * Battle::Arena::GetCommander2() const { return _army2->GetCommander(); } int Battle::Arena::GetArmy1Color() const { return _army1->GetColor(); } int Battle::Arena::GetArmy2Color() const { return _army2->GetColor(); } int Battle::Arena::GetCurrentColor() const { // This method should never be called in cases where there may not be an active unit if ( _currentUnit == nullptr ) { assert( 0 ); return Color::NONE; } return _currentUnit->GetCurrentOrArmyColor(); } int Battle::Arena::GetOppositeColor( const int col ) const { return col == GetArmy1Color() ? GetArmy2Color() : GetArmy1Color(); } Battle::Unit * Battle::Arena::GetTroopUID( uint32_t uid ) { Units::iterator it = std::find_if( _army1->begin(), _army1->end(), [uid]( const Unit * unit ) { return unit->isUID( uid ); } ); if ( it != _army1->end() ) return *it; it = std::find_if( _army2->begin(), _army2->end(), [uid]( const Unit * unit ) { return unit->isUID( uid ); } ); return it != _army2->end() ? *it : nullptr; } const Battle::Unit * Battle::Arena::GetTroopUID( uint32_t uid ) const { Units::const_iterator it = std::find_if( _army1->begin(), _army1->end(), [uid]( const Unit * unit ) { return unit->isUID( uid ); } ); if ( it != _army1->end() ) return *it; it = std::find_if( _army2->begin(), _army2->end(), [uid]( const Unit * unit ) { return unit->isUID( uid ); } ); return it != _army2->end() ? *it : nullptr; } void Battle::Arena::FadeArena( bool clearMessageLog ) const { if ( _interface ) _interface->FadeArena( clearMessageLog ); } const SpellStorage & Battle::Arena::GetUsageSpells() const { return usage_spells; } int32_t Battle::Arena::GetFreePositionNearHero( const int heroColor ) const { std::vector cellIds; if ( _army1->GetColor() == heroColor ) { cellIds = { 11, 22, 33 }; } else if ( _army2->GetColor() == heroColor ) { cellIds = { 21, 32, 43 }; } else { // Some third color? return -1; } assert( !cellIds.empty() ); for ( const int cellId : cellIds ) { if ( board[cellId].isPassable( true ) ) { // TODO: remove this temporary assertion assert( board[cellId].GetUnit() == nullptr ); return cellId; } } return -1; } bool Battle::Arena::CanSurrenderOpponent( int color ) const { const HeroBase * hero = getCommander( color ); const HeroBase * enemyHero = getEnemyCommander( color ); return hero && hero->isHeroes() && enemyHero && ( enemyHero->isHeroes() || enemyHero->isCaptain() ); } bool Battle::Arena::CanRetreatOpponent( int color ) const { const HeroBase * hero = getCommander( color ); return hero && hero->isHeroes() && ( color == _army1->GetColor() || hero->inCastle() == nullptr ); } bool Battle::Arena::isSpellcastDisabled() const { const HeroBase * hero1 = _army1->GetCommander(); if ( hero1 != nullptr && hero1->GetBagArtifacts().isArtifactBonusPresent( fheroes2::ArtifactBonusType::DISABLE_ALL_SPELL_COMBAT_CASTING ) ) { return true; } const HeroBase * hero2 = _army2->GetCommander(); if ( hero2 != nullptr && hero2->GetBagArtifacts().isArtifactBonusPresent( fheroes2::ArtifactBonusType::DISABLE_ALL_SPELL_COMBAT_CASTING ) ) { return true; } return false; } bool Battle::Arena::isDisableCastSpell( const Spell & spell, std::string * msg /* = nullptr */ ) const { // check sphere negation (only for heroes) if ( isSpellcastDisabled() ) { if ( msg ) { *msg = _( "The Sphere of Negation artifact is in effect for this battle, disabling all combat spells." ); } return true; } const HeroBase * current_commander = GetCurrentCommander(); // check casted if ( current_commander ) { if ( current_commander->Modes( Heroes::SPELLCASTED ) ) { if ( msg ) { *msg = _( "You have already cast a spell this round." ); } return true; } if ( spell == Spell::EARTHQUAKE && !castle ) { if ( msg ) { *msg = _( "That spell will have no effect!" ); } return true; } else if ( spell.isSummon() ) { const Monster mons( spell ); assert( mons.isValid() && mons.isElemental() ); const Unit * elem = GetCurrentForce().FindMode( CAP_SUMMONELEM ); if ( elem && elem->GetID() != mons.GetID() ) { if ( msg ) { *msg = _( "You may only summon one type of elemental per combat." ); } return true; } if ( 0 > GetFreePositionNearHero( GetCurrentColor() ) ) { if ( msg ) { *msg = _( "There is no open space adjacent to your hero where you can summon an Elemental to." ); } return true; } } else if ( spell.isValid() ) { // check army for ( Board::const_iterator it = board.begin(); it != board.end(); ++it ) { const Battle::Unit * b = ( *it ).GetUnit(); if ( b ) { if ( b->AllowApplySpell( spell, current_commander ) ) { return false; } } else { // check graveyard if ( GraveyardAllowResurrect( ( *it ).GetIndex(), spell ) ) { return false; } } } if ( msg ) { *msg = _( "That spell will have no effect!" ); } return true; } } return false; } bool Battle::Arena::GraveyardAllowResurrect( const int32_t index, const Spell & spell ) const { if ( !spell.isResurrect() ) { return false; } const HeroBase * hero = GetCurrentCommander(); if ( hero == nullptr ) { return false; } const Unit * unit = GraveyardLastTroop( index ); if ( unit == nullptr ) { return false; } if ( !unit->AllowApplySpell( spell, hero ) ) { return false; } const int headIndex = unit->GetHeadIndex(); assert( Board::isValidIndex( headIndex ) ); // No other unit should stand on a corpse if ( Board::GetCell( headIndex )->GetUnit() != nullptr ) { return false; } if ( !unit->isWide() ) { return true; } const int tailIndex = unit->GetTailIndex(); assert( Board::isValidIndex( tailIndex ) ); // No other unit should stand on a corpse if ( Board::GetCell( tailIndex )->GetUnit() != nullptr ) { return false; } return true; } const Battle::Unit * Battle::Arena::GraveyardLastTroop( const int32_t index ) const { return GetTroopUID( graveyard.GetLastTroopUID( index ) ); } std::vector Battle::Arena::GetGraveyardTroops( const int32_t index ) const { const TroopUIDs troopUIDs = graveyard.GetTroopUIDs( index ); std::vector result; result.reserve( troopUIDs.size() ); std::for_each( troopUIDs.begin(), troopUIDs.end(), [this, &result]( const uint32_t uid ) { const Unit * unit = GetTroopUID( uid ); assert( unit != nullptr ); result.push_back( unit ); } ); return result; } Battle::Indexes Battle::Arena::GraveyardOccupiedCells() const { return graveyard.GetOccupiedCells(); } void Battle::Arena::SetCastleTargetValue( const CastleDefenseElement target, const uint32_t value ) { switch ( target ) { case CastleDefenseElement::WALL1: board[CASTLE_FIRST_TOP_WALL_POS].SetObject( value ); break; case CastleDefenseElement::WALL2: board[CASTLE_SECOND_TOP_WALL_POS].SetObject( value ); break; case CastleDefenseElement::WALL3: board[CASTLE_THIRD_TOP_WALL_POS].SetObject( value ); break; case CastleDefenseElement::WALL4: board[CASTLE_FOURTH_TOP_WALL_POS].SetObject( value ); break; case CastleDefenseElement::TOWER1: if ( _towers[0] && _towers[0]->isValid() ) { _towers[0]->SetDestroy(); } break; case CastleDefenseElement::TOWER2: if ( _towers[2] && _towers[2]->isValid() ) { _towers[2]->SetDestroy(); } break; case CastleDefenseElement::CENTRAL_TOWER: if ( _towers[1] && _towers[1]->isValid() ) { _towers[1]->SetDestroy(); } break; case CastleDefenseElement::BRIDGE: if ( _bridge->isValid() ) { _bridge->SetDestroyed(); } break; default: break; } } uint32_t Battle::Arena::GetCastleTargetValue( const CastleDefenseElement target ) const { switch ( target ) { case CastleDefenseElement::WALL1: return board[CASTLE_FIRST_TOP_WALL_POS].GetObject(); case CastleDefenseElement::WALL2: return board[CASTLE_SECOND_TOP_WALL_POS].GetObject(); case CastleDefenseElement::WALL3: return board[CASTLE_THIRD_TOP_WALL_POS].GetObject(); case CastleDefenseElement::WALL4: return board[CASTLE_FOURTH_TOP_WALL_POS].GetObject(); case CastleDefenseElement::TOWER1: return _towers[0] && _towers[0]->isValid() ? 1 : 0; case CastleDefenseElement::TOWER2: return _towers[2] && _towers[2]->isValid() ? 1 : 0; case CastleDefenseElement::CENTRAL_TOWER: return _towers[1] && _towers[1]->isValid() ? 1 : 0; case CastleDefenseElement::BRIDGE: return _bridge->isValid() ? 1 : 0; default: break; } return 0; } std::vector Battle::Arena::GetEarthQuakeTargets() const { std::vector targets; targets.reserve( 8 ); if ( board[CASTLE_FIRST_TOP_WALL_POS].GetObject() > 0 ) { targets.push_back( CastleDefenseElement::WALL1 ); } if ( board[CASTLE_SECOND_TOP_WALL_POS].GetObject() > 0 ) { targets.push_back( CastleDefenseElement::WALL2 ); } if ( board[CASTLE_THIRD_TOP_WALL_POS].GetObject() > 0 ) { targets.push_back( CastleDefenseElement::WALL3 ); } if ( board[CASTLE_FOURTH_TOP_WALL_POS].GetObject() > 0 ) { targets.push_back( CastleDefenseElement::WALL4 ); } if ( _towers[0] && _towers[0]->isValid() ) { targets.push_back( CastleDefenseElement::TOWER1 ); } if ( _towers[2] && _towers[2]->isValid() ) { targets.push_back( CastleDefenseElement::TOWER2 ); } return targets; } const HeroBase * Battle::Arena::getCommander( const int color ) const { return ( _army1->GetColor() == color ) ? _army1->GetCommander() : _army2->GetCommander(); } const HeroBase * Battle::Arena::getEnemyCommander( const int color ) const { return ( _army1->GetColor() == color ) ? _army2->GetCommander() : _army1->GetCommander(); } const HeroBase * Battle::Arena::GetCurrentCommander() const { return getCommander( GetCurrentColor() ); } Battle::Unit * Battle::Arena::CreateElemental( const Spell & spell ) { // TODO: this assertion is here to thoroughly check all the complex limitations of the Summon Elemental spell assert( !isDisableCastSpell( spell ) ); const HeroBase * hero = GetCurrentCommander(); assert( hero != nullptr ); const int32_t idx = GetFreePositionNearHero( GetCurrentColor() ); assert( Board::isValidIndex( idx ) ); const Monster mons( spell ); assert( mons.isValid() && mons.isElemental() && !mons.isWide() ); DEBUG_LOG( DBG_BATTLE, DBG_TRACE, mons.GetName() << ", position: " << idx ) const bool reflect = ( hero == _army2->GetCommander() ); const uint32_t count = fheroes2::getSummonMonsterCount( spell, hero->GetPower(), hero ); Position pos; pos.Set( idx, mons.isWide(), reflect ); // An elemental could not be a wide unit assert( pos.GetHead() != nullptr && pos.GetTail() == nullptr ); Unit * elem = new Unit( Troop( mons, count ), pos, reflect, _uidGenerator.GetUnique() ); elem->SetModes( CAP_SUMMONELEM ); elem->SetArmy( hero->GetArmy() ); GetCurrentForce().push_back( elem ); return elem; } Battle::Unit * Battle::Arena::CreateMirrorImage( Unit & unit ) { Unit * mirrorUnit = new Unit( unit, {}, unit.isReflect(), _uidGenerator.GetUnique() ); mirrorUnit->SetArmy( *unit.GetArmy() ); mirrorUnit->SetMirror( &unit ); mirrorUnit->SetModes( CAP_MIRRORIMAGE ); unit.SetMirror( mirrorUnit ); unit.SetModes( CAP_MIRROROWNER ); GetCurrentForce().push_back( mirrorUnit ); return mirrorUnit; } bool Battle::Arena::IsShootingPenalty( const Unit & attacker, const Unit & defender ) const { // no castle - no castle walls, penalty does not apply if ( castle == nullptr ) { return false; } // penalty does not apply to towers if ( defender.Modes( CAP_TOWER ) || attacker.Modes( CAP_TOWER ) ) return false; // penalty does not apply if the attacker's hero has certain artifacts or skills const HeroBase * hero = attacker.GetCommander(); if ( hero ) { // golden bow artifact if ( hero->GetBagArtifacts().isArtifactBonusPresent( fheroes2::ArtifactBonusType::NO_SHOOTING_PENALTY ) ) return false; // archery skill if ( hero->GetLevelSkill( Skill::Secondary::ARCHERY ) != Skill::Level::NONE ) return false; } // penalty does not apply if the attacking unit (be it a castle attacker or a castle defender) is inside the castle walls if ( !attacker.isOutOfCastleWalls() ) { return false; } // penalty does not apply if both units are on the same side relative to the castle walls if ( attacker.isOutOfCastleWalls() == defender.isOutOfCastleWalls() ) { return false; } // penalty does not apply if the target unit is exposed due to the broken castle wall const std::vector points = GetLinePoints( attacker.GetBackPoint(), defender.GetBackPoint(), CELLW / 3 ); for ( std::vector::const_iterator it = points.begin(); it != points.end(); ++it ) { if ( ( 0 == board[CASTLE_FIRST_TOP_WALL_POS].GetObject() && ( board[CASTLE_FIRST_TOP_WALL_POS].GetPos() & *it ) ) || ( 0 == board[CASTLE_SECOND_TOP_WALL_POS].GetObject() && ( board[CASTLE_SECOND_TOP_WALL_POS].GetPos() & *it ) ) || ( 0 == board[CASTLE_THIRD_TOP_WALL_POS].GetObject() && ( board[CASTLE_THIRD_TOP_WALL_POS].GetPos() & *it ) ) || ( 0 == board[CASTLE_FOURTH_TOP_WALL_POS].GetObject() && ( board[CASTLE_FOURTH_TOP_WALL_POS].GetPos() & *it ) ) ) return false; } return true; } Battle::Force & Battle::Arena::GetForce1() const { return *_army1; } Battle::Force & Battle::Arena::GetForce2() const { return *_army2; } Battle::Force & Battle::Arena::getForce( const int color ) const { return ( _army1->GetColor() == color ) ? *_army1 : *_army2; } Battle::Force & Battle::Arena::getEnemyForce( const int color ) const { return ( _army1->GetColor() == color ) ? *_army2 : *_army1; } Battle::Force & Battle::Arena::GetCurrentForce() const { return getForce( GetCurrentColor() ); } Battle::Result & Battle::Arena::GetResult() { return result_game; } bool Battle::Arena::AutoBattleInProgress() const { if ( _currentUnit == nullptr ) { return false; } if ( _autoBattleColors & GetCurrentColor() ) { // Auto battle mode cannot be enabled for a player controlled by AI assert( !( GetCurrentForce().GetControl() & CONTROL_AI ) ); return true; } return false; } bool Battle::Arena::CanToggleAutoBattle() const { if ( _currentUnit == nullptr ) { return false; } return !( GetCurrentForce().GetControl() & CONTROL_AI ); } fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_arena.h000066400000000000000000000351601456075706000226170ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2BATTLE_ARENA_H #define H2BATTLE_ARENA_H #include #include #include #include #include #include #include "battle.h" #include "battle_board.h" #include "battle_cell.h" #include "battle_command.h" #include "battle_grave.h" #include "battle_pathfinding.h" #include "battle_tower.h" #include "icn.h" #include "spell.h" #include "spell_storage.h" class Army; class Artifact; class Castle; class HeroBase; namespace Rand { class DeterministicRandomGenerator; } namespace Battle { class Bridge; class Catapult; class Force; class Units; class Unit; class Interface; class Status; class Actions : public std::list {}; class TroopsUidGenerator { public: TroopsUidGenerator() = default; TroopsUidGenerator( const TroopsUidGenerator & ) = delete; TroopsUidGenerator & operator=( const TroopsUidGenerator & ) = delete; uint32_t GetUnique() { return _id++; } private: uint32_t _id{ 1 }; }; class Arena { public: Arena( Army & army1, Army & army2, const int32_t tileIndex, const bool isShowInterface, Rand::DeterministicRandomGenerator & randomGenerator ); Arena( const Arena & ) = delete; Arena( Arena && ) = delete; ~Arena(); Arena & operator=( const Arena & ) = delete; Arena & operator=( Arena && ) = delete; void Turns(); bool BattleValid() const; bool AutoBattleInProgress() const; bool CanToggleAutoBattle() const; uint32_t GetTurnNumber() const { return _turnNumber; } Result & GetResult(); const HeroBase * getCommander( const int color ) const; const HeroBase * getEnemyCommander( const int color ) const; const HeroBase * GetCommander1() const; const HeroBase * GetCommander2() const; const HeroBase * GetCurrentCommander() const; Force & GetForce1() const; Force & GetForce2() const; Force & getForce( const int color ) const; Force & getEnemyForce( const int color ) const; Force & GetCurrentForce() const; int GetArmy1Color() const; int GetArmy2Color() const; int GetCurrentColor() const; // Returns the color of the army opposite to the army of the given color. If there is no army of the given color, // returns the color of the attacking army. int GetOppositeColor( const int col ) const; Unit * GetTroopBoard( int32_t ); const Unit * GetTroopBoard( int32_t ) const; Unit * GetTroopUID( uint32_t ); const Unit * GetTroopUID( uint32_t ) const; const SpellStorage & GetUsageSpells() const; bool DialogBattleSummary( const Result & res, const std::vector & artifacts, const bool allowToRestart ) const; int DialogBattleHero( const HeroBase & hero, const bool buttons, Status & status ) const; static void DialogBattleNecromancy( const uint32_t raiseCount ); void FadeArena( bool clearMessageLog ) const; // Checks whether the given position is reachable for the given unit, either on the current turn or in principle bool isPositionReachable( const Unit & unit, const Position & position, const bool isOnCurrentTurn ) { return _battlePathfinder.isPositionReachable( unit, position, isOnCurrentTurn ); } // Returns the cost of moving to the given position for the given unit. It's the caller's responsibility to make // sure that this position is reachable before calling this method. uint32_t CalculateMoveCost( const Unit & unit, const Position & position ) { return _battlePathfinder.getCost( unit, position ); } // Returns the distance to the given position (i.e. the number of movements that need to be performed to get to // this position, the reversal of a wide unit is not considered as a movement) for the given unit. For flying // units, this distance is estimated as the straight line distance to the given position. It's the caller's // responsibility to make sure that this position is reachable before calling this method. uint32_t CalculateMoveDistance( const Unit & unit, const Position & position ) { return _battlePathfinder.getDistance( unit, position ); } // Returns the path (or its part) for the given unit to the given position that can be traversed during the // current turn. If this position is unreachable by this unit, then an empty path is returned. Indexes GetPath( const Unit & unit, const Position & position ); // Returns the indexes of all cells that can be occupied by the given unit's head on the current turn Indexes getAllAvailableMoves( const Unit & unit ) { return _battlePathfinder.getAllAvailableMoves( unit ); } // Returns the position on the path for the given unit to the given position, which is reachable on the current // turn and is as close as possible to the destination (excluding the current position of the unit). If the given // position is unreachable by the given unit, then an empty Position object is returned. Position getClosestReachablePosition( const Unit & unit, const Position & position ) { return _battlePathfinder.getClosestReachablePosition( unit, position ); } void ApplyAction( Command & ); // Returns a list of targets that will be affected by the given spell casted by the given hero and applied // to a cell with a given index. This method can be used by external code to evaluate the applicability of // a spell, and does not use probabilistic mechanisms to determine units resisting the given spell. TargetsInfo GetTargetsForSpell( const HeroBase * hero, const Spell & spell, const int32_t dst ) { return GetTargetsForSpell( hero, spell, dst, false, nullptr ); } bool isSpellcastDisabled() const; bool isDisableCastSpell( const Spell & spell, std::string * msg = nullptr ) const; bool GraveyardAllowResurrect( const int32_t index, const Spell & spell ) const; const Unit * GraveyardLastTroop( const int32_t index ) const; std::vector GetGraveyardTroops( const int32_t index ) const; Indexes GraveyardOccupiedCells() const; bool CanSurrenderOpponent( int color ) const; bool CanRetreatOpponent( int color ) const; bool IsShootingPenalty( const Unit &, const Unit & ) const; int GetICNCovr() const { return _covrIcnId; } uint32_t GetCastleTargetValue( const CastleDefenseElement target ) const; int32_t GetFreePositionNearHero( const int heroColor ) const; static Board * GetBoard(); static Tower * GetTower( const TowerType type ); static Bridge * GetBridge(); static const Castle * GetCastle(); static Interface * GetInterface(); static Graveyard * GetGraveyard(); static bool isAnyTowerPresent(); enum { CATAPULT_POS = 77, CASTLE_GATE_POS = 50, CASTLE_FIRST_TOP_WALL_POS = 8, CASTLE_SECOND_TOP_WALL_POS = 29, CASTLE_THIRD_TOP_WALL_POS = 73, CASTLE_FOURTH_TOP_WALL_POS = 96, CASTLE_TOP_ARCHER_TOWER_POS = 19, CASTLE_BOTTOM_ARCHER_TOWER_POS = 85, CASTLE_TOP_GATE_TOWER_POS = 40, CASTLE_BOTTOM_GATE_TOWER_POS = 62 }; private: void UnitTurn( const Units & orderHistory ); void TowerAction( const Tower & ); void SetCastleTargetValue( const CastleDefenseElement target, const uint32_t value ); void CatapultAction(); TargetsInfo GetTargetsForDamage( const Unit & attacker, Unit & defender, const int32_t dst, const int dir ) const; TargetsInfo GetTargetsForSpell( const HeroBase * hero, const Spell & spell, const int32_t dst, bool applyRandomMagicResistance, bool * playResistSound ); static void TargetsApplyDamage( Unit & attacker, TargetsInfo & targets, uint32_t & resurrected ); static void TargetsApplySpell( const HeroBase * hero, const Spell & spell, TargetsInfo & targets ); TargetsInfo TargetsForChainLightning( const HeroBase * hero, const int32_t attackedTroopIndex, const bool applyRandomMagicResistance ); std::vector FindChainLightningTargetIndexes( const HeroBase * hero, Unit * firstUnit, const bool applyRandomMagicResistance ); std::vector GetEarthQuakeTargets() const; void ApplyActionRetreat( const Command & cmd ); void ApplyActionSurrender( const Command & cmd ); void ApplyActionAttack( Command & cmd ); void ApplyActionMove( Command & cmd ); void ApplyActionSkip( Command & cmd ); void ApplyActionMorale( Command & cmd ); void ApplyActionSpellCast( Command & cmd ); void ApplyActionTower( Command & cmd ); void ApplyActionCatapult( Command & cmd ); void ApplyActionAutoSwitch( Command & cmd ); void ApplyActionAutoFinish( const Command & cmd ); void ApplyActionSpellSummonElemental( const Command & cmd, const Spell & spell ); void ApplyActionSpellMirrorImage( Command & cmd ); void ApplyActionSpellTeleport( Command & cmd ); void ApplyActionSpellEarthQuake( const Command & cmd ); void ApplyActionSpellDefaults( Command & cmd, const Spell & spell ); // Moves the given unit to a position where the index of the head cell is equal to 'dst'. If 'dst' is -1, // then this method does nothing. Otherwise, it's the caller's responsibility to make sure that this position // is reachable for the given unit on the current turn before calling this method. void moveUnit( Unit * unit, const int32_t dst ); // Performs an actual attack of one unit (defender) by another unit (attacker), applying the attacker's // built-in magic, if necessary. If the specified index of the target cell of the attack (tgt) is negative, // then an attempt will be made to calculate it automatically based on the adjacency of the unit cells. If // the specified direction of the attack (dir) is negative, then an attempt will be made to calculate it // automatically. When an attack is made by firing a shot, the dir should be UNKNOWN (zero). void BattleProcess( Unit & attacker, Unit & defender, int32_t tgt = -1, int dir = -1 ); // Creates and returns a fully combat-ready elemental, which will be already placed on the board. It's // the caller's responsibility to make sure that a given spell is capable of creating an elemental // before calling this method. Unit * CreateElemental( const Spell & spell ); // Creates and returns a mirror image of a given unit. The returned mirror image will have an invalid // position, which should be updated separately. Unit * CreateMirrorImage( Unit & unit ); std::unique_ptr _army1; std::unique_ptr _army2; std::shared_ptr _orderOfUnits; // The unit that is currently active. Please note that some battle actions (e.g. catapult or castle tower shots) can be performed without an active unit. Unit * _currentUnit{ nullptr }; // The color of the army of the last unit that performed a full-fledged action (skipping a turn due to // bad morale is not considered as such). int _lastActiveUnitArmyColor{ -1 }; const Castle * castle; // Is the battle taking place in a town or a castle const bool _isTown; std::array, 3> _towers; std::unique_ptr _catapult; std::unique_ptr _bridge; std::unique_ptr _interface; Result result_game; Graveyard graveyard; SpellStorage usage_spells; Board board; BattlePathfinder _battlePathfinder; int _covrIcnId{ ICN::UNKNOWN }; uint32_t _turnNumber{ 0 }; // A set of colors of players for whom the auto-battle mode is enabled int _autoBattleColors{ 0 }; // This random number generator should only be used in code that is equally used by both AI and the human // player - that is, in code related to the processing of battle commands. It cannot be safely used in other // places (for example, in code that performs situation assessment or AI decision-making) because in this // case the battles performed by AI will not be reproducible by a human player when performing exactly the // same actions. Rand::DeterministicRandomGenerator & _randomGenerator; TroopsUidGenerator _uidGenerator; enum { CHAIN_LIGHTNING_CREATURE_COUNT = 4 }; }; Arena * GetArena(); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_army.cpp000066400000000000000000000250371456075706000230360ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2012 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "battle_army.h" #include #include "army_troop.h" #include "artifact.h" #include "artifact_info.h" #include "battle_arena.h" #include "battle_cell.h" #include "battle_troop.h" #include "heroes.h" #include "heroes_base.h" #include "monster_anim.h" #include "resource.h" #include "skill.h" namespace { const size_t unitSizeCapacity = 16; } Battle::Units::Units() { reserve( unitSizeCapacity ); } void Battle::Units::SortFastest() { // It is important to maintain the initial order of units having the same speed for the proper operation of the unit turn queue std::stable_sort( begin(), end(), Army::FastestTroop ); } Battle::Unit * Battle::Units::FindUID( uint32_t pid ) const { const_iterator it = std::find_if( begin(), end(), [pid]( const Unit * unit ) { return unit->isUID( pid ); } ); return it == end() ? nullptr : *it; } Battle::Unit * Battle::Units::FindMode( uint32_t mod ) const { const_iterator it = std::find_if( begin(), end(), [mod]( const Unit * unit ) { return unit->Modes( mod ); } ); return it == end() ? nullptr : *it; } Battle::Force::Force( Army & parent, bool opposite, TroopsUidGenerator & generator ) : army( parent ) { uids.reserve( army.Size() ); for ( size_t i = 0; i < army.Size(); ++i ) { const Troop * troop = army.GetTroop( i ); if ( troop == nullptr || !troop->isValid() ) { uids.push_back( 0 ); continue; } int32_t idx = army.isSpreadFormation() ? static_cast( i ) * 22 : 22 + static_cast( i ) * 11; if ( opposite ) { idx += ( troop->isWide() ? 9 : 10 ); } else if ( troop->isWide() ) { idx += 1; } Position pos; pos.Set( idx, troop->isWide(), opposite ); assert( pos.GetHead() != nullptr && ( troop->isWide() ? pos.GetTail() != nullptr : pos.GetTail() == nullptr ) ); push_back( new Unit( *troop, pos, opposite, generator.GetUnique() ) ); back()->SetArmy( army ); uids.push_back( back()->GetUID() ); } } Battle::Force::~Force() { std::for_each( begin(), end(), []( Unit * unit ) { assert( unit != nullptr ); delete unit; } ); } const HeroBase * Battle::Force::GetCommander() const { return army.GetCommander(); } HeroBase * Battle::Force::GetCommander() { return army.GetCommander(); } const Battle::Units & Battle::Force::getUnits() const { return *this; } int Battle::Force::GetColor() const { return army.GetColor(); } int Battle::Force::GetControl() const { return army.GetControl(); } bool Battle::Force::isValid( const bool considerBattlefieldArmy /* = true */ ) const { // Consider the state of the army on the battlefield (including resurrected units, summoned units, etc) if ( considerBattlefieldArmy ) { return std::any_of( begin(), end(), []( const Unit * unit ) { return unit->isValid(); } ); } // Consider only the state of the original army for ( size_t index = 0; index < army.Size(); ++index ) { const Troop * troop = army.GetTroop( index ); if ( troop == nullptr || !troop->isValid() ) { continue; } const Unit * unit = FindUID( uids.at( index ) ); if ( unit == nullptr || unit->GetDead() >= unit->GetInitialCount() ) { continue; } return true; } return false; } uint32_t Battle::Force::GetSurrenderCost() const { double result = 0.0; // Consider only the units from the original army for ( size_t index = 0; index < army.Size(); ++index ) { const Troop * troop = army.GetTroop( index ); if ( troop == nullptr || !troop->isValid() ) { continue; } const Unit * unit = FindUID( uids.at( index ) ); if ( unit == nullptr || !unit->isValid() ) { continue; } result += unit->GetSurrenderCost().gold; } const HeroBase * commander = GetCommander(); if ( commander ) { const std::vector costReductionPercent = commander->GetBagArtifacts().getTotalArtifactMultipliedPercent( fheroes2::ArtifactBonusType::SURRENDER_COST_REDUCTION_PERCENT ); double mod = 0.5; if ( !costReductionPercent.empty() ) { mod = 1; for ( const int32_t value : costReductionPercent ) { mod = mod * value / 100; } } const int diplomacyLevel = commander->GetLevelSkill( Skill::Secondary::DIPLOMACY ); if ( diplomacyLevel > Skill::Level::NONE ) { // The modifier is always multiplied by two, normally to negate the 0.5 modifier from regular surrender, but also when there are // artifacts present, even if the 0.5 modifier for normal surrender already has been negated by setting mod = 1. mod *= 2; mod *= ( Skill::GetDiplomacySurrenderCostDiscount( commander->GetLevelSkill( Skill::Secondary::DIPLOMACY ) ) / 100.0 ); } result *= mod; } // Total cost should always be at least 1 gold return result >= 1 ? static_cast( result + 0.5 ) : 1; } void Battle::Force::NewTurn() { if ( GetCommander() ) GetCommander()->ResetModes( Heroes::SPELLCASTED ); std::for_each( begin(), end(), []( Unit * unit ) { unit->NewTurn(); } ); } Troops Battle::Force::GetKilledTroops() const { Troops result; for ( const Unit * unit : *this ) { assert( unit != nullptr ); result.PushBack( *unit, std::min( unit->GetInitialCount(), unit->GetDead() ) ); } return result; } bool Battle::Force::animateIdleUnits() const { bool redrawNeeded = false; for ( Unit * unit : *this ) { // Check if unit is alive. if ( unit->isValid() ) { if ( unit->isIdling() ) { // Go to 'STATIC' animation state if idle animation is over or if unit is blinded or paralyzed. if ( unit->isFinishAnimFrame() || unit->isImmovable() ) { redrawNeeded = unit->SwitchAnimation( Monster_Info::STATIC ) || redrawNeeded; } else { unit->IncreaseAnimFrame(); redrawNeeded = true; } } // checkIdleDelay() sets and checks unit's internal timer if we're ready to switch to next one. // Do not start idle animations for paralyzed or blinded units. else if ( unit->GetAnimationState() == Monster_Info::STATIC && !unit->isImmovable() && unit->checkIdleDelay() ) { redrawNeeded = unit->SwitchAnimation( Monster_Info::IDLE ) || redrawNeeded; } } } return redrawNeeded; } void Battle::Force::resetIdleAnimation() const { for ( Unit * unit : *this ) { // Check if unit is alive, not paralyzed or blinded and is in 'STATIC' animation state. if ( unit->isValid() && unit->GetAnimationState() == Monster_Info::STATIC && !unit->isImmovable() ) { unit->checkIdleDelay(); } } } bool Battle::Force::HasMonster( const Monster & mons ) const { return std::any_of( begin(), end(), [&mons]( const Unit * unit ) { return unit->isMonster( mons.GetID() ); } ); } uint32_t Battle::Force::GetDeadCounts() const { uint32_t res = 0; for ( const_iterator it = begin(); it != end(); ++it ) res += ( *it )->GetDead(); return res; } uint32_t Battle::Force::GetDeadHitPoints() const { uint32_t res = 0; for ( const_iterator it = begin(); it != end(); ++it ) { res += static_cast( *it )->GetHitPoints() * ( *it )->GetDead(); } return res; } void Battle::Force::SyncArmyCount() { for ( uint32_t index = 0; index < army.Size(); ++index ) { Troop * troop = army.GetTroop( index ); if ( troop == nullptr || !troop->isValid() ) { continue; } const Unit * unit = FindUID( uids.at( index ) ); if ( unit == nullptr ) { continue; } troop->SetCount( unit->GetDead() > unit->GetInitialCount() ? 0 : unit->GetInitialCount() - unit->GetDead() ); } } double Battle::Force::getStrengthOfArmyRemainingInCaseOfSurrender() const { double result = 0.0; // Consider only the state of the original army for ( uint32_t index = 0; index < army.Size(); ++index ) { const Troop * troop = army.GetTroop( index ); if ( troop == nullptr || !troop->isValid() ) { continue; } const Unit * unit = FindUID( uids.at( index ) ); if ( unit == nullptr ) { continue; } // Consider only the number of units that will remain in the army after the end of the battle (in particular, don't take into account the number of // non-true-resurrected units) result += Troop{ unit->GetMonster(), unit->GetDead() > unit->GetInitialCount() ? 0 : unit->GetInitialCount() - unit->GetDead() }.GetStrength(); } return result; } fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_army.h000066400000000000000000000142761456075706000225060ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2012 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2BATTLE_ARMY_H #define H2BATTLE_ARMY_H #include // IWYU pragma: keep #include #include #include #include #include #include #include "army.h" #include "battle_troop.h" #include "bitmodes.h" #include "monster.h" class HeroBase; namespace Battle { class TroopsUidGenerator; class Units : public std::vector { public: enum class FilterType { REMOVE_INVALID_UNITS, REMOVE_INVALID_UNITS_AND_SPECIFIED_UNIT, REMOVE_INVALID_UNITS_AND_UNITS_THAT_CHANGED_SIDES }; static constexpr std::integral_constant REMOVE_INVALID_UNITS{}; static constexpr std::integral_constant REMOVE_INVALID_UNITS_AND_SPECIFIED_UNIT{}; static constexpr std::integral_constant REMOVE_INVALID_UNITS_AND_UNITS_THAT_CHANGED_SIDES{}; Units(); // Creates a shallow copy of 'units' (only pointers are copied) by applying a filter according to the specified tag template Units( const Units & units, std::integral_constant /* tag */, const Types... params ) { reserve( units.size() ); const auto filterPredicateGenerator = []( const auto... filterParams ) { if constexpr ( filterType == FilterType::REMOVE_INVALID_UNITS ) { static_assert( sizeof...( filterParams ) == 0 ); return []( const Unit * unit ) { assert( unit != nullptr ); return unit->isValid(); }; } else if constexpr ( filterType == FilterType::REMOVE_INVALID_UNITS_AND_SPECIFIED_UNIT ) { static_assert( sizeof...( filterParams ) == 1 ); return [unitToRemove = std::get<0>( std::tie( filterParams... ) )]( const Unit * unit ) { assert( unit != nullptr ); return unit->isValid() && unit != unitToRemove; }; } else if constexpr ( filterType == FilterType::REMOVE_INVALID_UNITS_AND_UNITS_THAT_CHANGED_SIDES ) { static_assert( sizeof...( filterParams ) == 0 ); return []( const Unit * unit ) { assert( unit != nullptr ); return unit->isValid() && unit->GetColor() == unit->GetCurrentColor(); }; } else { // The build will fail because this lambda does not meet the requirements of UnaryPredicate return []() {}; } }; std::copy_if( units.begin(), units.end(), std::back_inserter( *this ), filterPredicateGenerator( params... ) ); } Units( const Units & ) = delete; virtual ~Units() = default; Units & operator=( const Units & ) = delete; Unit * FindMode( uint32_t mod ) const; Unit * FindUID( uint32_t pid ) const; void SortFastest(); }; class Force : public Units, public BitModes { public: Force( Army & parent, bool opposite, TroopsUidGenerator & generator ); Force( const Force & ) = delete; ~Force() override; Force & operator=( const Force & ) = delete; HeroBase * GetCommander(); const HeroBase * GetCommander() const; const Units & getUnits() const; bool isValid( const bool considerBattlefieldArmy = true ) const; bool HasMonster( const Monster & ) const; uint32_t GetDeadHitPoints() const; uint32_t GetDeadCounts() const; int GetColor() const; int GetControl() const; // Returns the cost of surrender (in units of gold) for the current army on the battlefield uint32_t GetSurrenderCost() const; // Returns the strength of the army that will remain in case of surrender (not taking into account the hero's bonuses) double getStrengthOfArmyRemainingInCaseOfSurrender() const; Troops GetKilledTroops() const; bool animateIdleUnits() const; void resetIdleAnimation() const; void NewTurn(); void SyncArmyCount(); private: Army & army; std::vector uids; }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_board.cpp000066400000000000000000000717611456075706000231620ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "battle_board.h" #include #include #include #include #include #include #include #include #include "battle_arena.h" #include "battle_bridge.h" #include "battle_troop.h" #include "castle.h" #include "game_static.h" #include "ground.h" #include "icn.h" #include "maps_tiles.h" #include "mp2.h" #include "rand.h" #include "tools.h" #include "translations.h" namespace { uint32_t GetRandomObstaclePosition( std::mt19937 & gen ) { return Rand::GetWithGen( 2, 8, gen ) + ( 11 * Rand::GetWithGen( 0, 8, gen ) ); } bool isTwoHexObject( const int icnId ) { switch ( icnId ) { case ICN::COBJ0004: case ICN::COBJ0005: case ICN::COBJ0007: case ICN::COBJ0011: case ICN::COBJ0014: case ICN::COBJ0015: case ICN::COBJ0017: case ICN::COBJ0018: case ICN::COBJ0019: case ICN::COBJ0020: case ICN::COBJ0022: case ICN::COBJ0030: case ICN::COBJ0031: return true; default: break; } return false; } bool isTallObject( const int icnId ) { switch ( icnId ) { case ICN::COBJ0002: case ICN::COBJ0009: case ICN::COBJ0013: case ICN::COBJ0021: case ICN::COBJ0027: case ICN::COBJ0028: case ICN::COBJ0031: return true; default: break; } return false; } } Battle::Board::Board() { reserve( ARENASIZE ); for ( uint32_t ii = 0; ii < ARENASIZE; ++ii ) push_back( Cell( ii ) ); } void Battle::Board::SetArea( const fheroes2::Rect & area ) { for ( iterator it = begin(); it != end(); ++it ) ( *it ).SetArea( area ); } void Battle::Board::removeDeadUnits() { for ( Cell & cell : *this ) { Unit * unit = cell.GetUnit(); if ( unit && !unit->isValid() ) { unit->PostKilledAction(); } } } uint32_t Battle::Board::GetDistance( const int32_t index1, const int32_t index2 ) { if ( !isValidIndex( index1 ) || !isValidIndex( index2 ) ) { return 0; } const int32_t x1 = index1 % ARENAW; const int32_t y1 = index1 / ARENAW; const int32_t x2 = index2 % ARENAW; const int32_t y2 = index2 / ARENAW; const int32_t du = y2 - y1; const int32_t dv = ( x2 + y2 / 2 ) - ( x1 + y1 / 2 ); if ( ( du >= 0 && dv >= 0 ) || ( du < 0 && dv < 0 ) ) { return std::max( std::abs( du ), std::abs( dv ) ); } return std::abs( du ) + std::abs( dv ); } uint32_t Battle::Board::GetDistance( const Position & pos1, const Position & pos2 ) { if ( pos1.GetHead() == nullptr || pos2.GetHead() == nullptr ) { return 0; } const int32_t head1Idx = pos1.GetHead()->GetIndex(); const int32_t tail1Idx = pos1.GetTail() ? pos1.GetTail()->GetIndex() : -1; const int32_t head2Idx = pos2.GetHead()->GetIndex(); const int32_t tail2Idx = pos2.GetTail() ? pos2.GetTail()->GetIndex() : -1; uint32_t distance = Board::GetDistance( head1Idx, head2Idx ); if ( tail2Idx != -1 ) { distance = std::min( distance, Board::GetDistance( head1Idx, tail2Idx ) ); } if ( tail1Idx != -1 ) { distance = std::min( distance, Board::GetDistance( tail1Idx, head2Idx ) ); if ( tail2Idx != -1 ) { distance = std::min( distance, Board::GetDistance( tail1Idx, tail2Idx ) ); } } return distance; } uint32_t Battle::Board::GetDistance( const Position & pos, const int32_t index ) { if ( pos.GetHead() == nullptr || !isValidIndex( index ) ) { return 0; } const int32_t headIdx = pos.GetHead()->GetIndex(); const int32_t tailIdx = pos.GetTail() ? pos.GetTail()->GetIndex() : -1; uint32_t distance = Board::GetDistance( headIdx, index ); if ( tailIdx != -1 ) { distance = std::min( distance, Board::GetDistance( tailIdx, index ) ); } return distance; } std::vector Battle::Board::GetNearestTroops( const Unit * startUnit, const std::vector & blackList ) { std::vector> foundUnits; for ( Cell & cell : *this ) { Unit * cellUnit = cell.GetUnit(); if ( cellUnit == nullptr || startUnit == cellUnit || cell.GetIndex() != cellUnit->GetHeadIndex() ) { continue; } const bool isBlackListed = std::find( blackList.begin(), blackList.end(), cellUnit ) != blackList.end(); if ( !isBlackListed ) { foundUnits.emplace_back( cellUnit, GetDistance( startUnit->GetPosition(), cellUnit->GetPosition() ) ); } } std::sort( foundUnits.begin(), foundUnits.end(), []( const std::pair & first, const std::pair & second ) { return first.second < second.second; } ); std::vector units; units.reserve( foundUnits.size() ); for ( const auto & foundUnit : foundUnits ) { units.push_back( foundUnit.first ); } return units; } int Battle::Board::GetDirection( const int32_t index1, const int32_t index2 ) { if ( !isValidIndex( index1 ) || !isValidIndex( index2 ) ) { return UNKNOWN; } if ( index1 == index2 ) { return CENTER; } for ( direction_t dir = TOP_LEFT; dir < CENTER; ++dir ) { if ( isValidDirection( index1, dir ) && index2 == GetIndexDirection( index1, dir ) ) { return dir; } } return UNKNOWN; } bool Battle::Board::isNearIndexes( const int32_t index1, const int32_t index2 ) { return index1 != index2 && UNKNOWN != GetDirection( index1, index2 ); } int Battle::Board::GetReflectDirection( const int dir ) { switch ( dir ) { case TOP_LEFT: return BOTTOM_RIGHT; case TOP_RIGHT: return BOTTOM_LEFT; case LEFT: return RIGHT; case RIGHT: return LEFT; case BOTTOM_LEFT: return TOP_RIGHT; case BOTTOM_RIGHT: return TOP_LEFT; default: break; } return UNKNOWN; } uint32_t Battle::Board::GetDistanceFromBoardEdgeAlongXAxis( const int32_t index, const bool fromRightEdge ) { assert( isValidIndex( index ) ); const uint32_t x = index % ARENAW; return ( fromRightEdge ? ARENAW - x : x + 1 ); } bool Battle::Board::isValidDirection( const int32_t index, const int dir ) { if ( !isValidIndex( index ) ) { return false; } const int32_t x = index % ARENAW; const int32_t y = index / ARENAW; switch ( dir ) { case CENTER: return true; case TOP_LEFT: return !( 0 == y || ( 0 == x && ( y % 2 ) ) ); case TOP_RIGHT: return !( 0 == y || ( ( ARENAW - 1 ) == x && !( y % 2 ) ) ); case LEFT: return !( 0 == x ); case RIGHT: return !( ( ARENAW - 1 ) == x ); case BOTTOM_LEFT: return !( ( ARENAH - 1 ) == y || ( 0 == x && ( y % 2 ) ) ); case BOTTOM_RIGHT: return !( ( ARENAH - 1 ) == y || ( ( ARENAW - 1 ) == x && !( y % 2 ) ) ); default: break; } return false; } int32_t Battle::Board::GetIndexDirection( const int32_t index, const int dir ) { if ( !isValidIndex( index ) ) { return -1; } const int32_t y = index / ARENAW; switch ( dir ) { case CENTER: return index; case TOP_LEFT: return index - ( ( y % 2 ) ? ARENAW + 1 : ARENAW ); case TOP_RIGHT: return index - ( ( y % 2 ) ? ARENAW : ARENAW - 1 ); case LEFT: return index - 1; case RIGHT: return index + 1; case BOTTOM_LEFT: return index + ( ( y % 2 ) ? ARENAW - 1 : ARENAW ); case BOTTOM_RIGHT: return index + ( ( y % 2 ) ? ARENAW : ARENAW + 1 ); default: break; } return -1; } int32_t Battle::Board::GetIndexAbsPosition( const fheroes2::Point & pt ) const { const_iterator it = begin(); for ( ; it != end(); ++it ) if ( ( *it ).isPositionIncludePoint( pt ) ) break; return it != end() ? ( *it ).GetIndex() : -1; } bool Battle::Board::isValidIndex( const int32_t index ) { return 0 <= index && index < ARENASIZE; } bool Battle::Board::isCastleIndex( const int32_t index ) { return ( ( 8 <= index && index <= 10 ) || ( 19 < index && index <= 21 ) || ( 29 <= index && index <= 32 ) || ( 40 < index && index <= 43 ) || ( 50 < index && index <= 54 ) || ( 62 < index && index <= 65 ) || ( 73 <= index && index <= 76 ) || ( 85 < index && index <= 87 ) || ( 96 <= index && index <= 98 ) ); } bool Battle::Board::isOutOfWallsIndex( const int32_t index ) { return ( ( index <= 8 ) || ( 11 <= index && index <= 19 ) || ( 22 <= index && index <= 29 ) || ( 33 <= index && index <= 40 ) || ( 44 <= index && index <= 50 ) || ( 55 <= index && index <= 62 ) || ( 66 <= index && index <= 73 ) || ( 77 <= index && index <= 85 ) || ( 88 <= index && index <= 96 ) ); } bool Battle::Board::isMoatIndex( const int32_t index, const Unit & unit ) { switch ( index ) { case 7: case 18: case 28: case 39: case 61: case 72: case 84: case 95: return true; case 49: { const Bridge * bridge = Arena::GetBridge(); return unit.isFlying() || bridge == nullptr || !bridge->isPassable( unit ); } default: break; } return false; } void Battle::Board::SetCobjObjects( const Maps::Tiles & tile, std::mt19937 & gen ) { bool grave = MP2::OBJ_GRAVEYARD == tile.GetObject( false ); int ground = tile.GetGround(); std::vector objs; if ( grave ) { objs.push_back( ICN::COBJ0000 ); objs.push_back( ICN::COBJ0001 ); objs.push_back( ICN::COBJ0025 ); } else switch ( ground ) { case Maps::Ground::DESERT: objs.push_back( ICN::COBJ0009 ); objs.push_back( ICN::COBJ0024 ); break; case Maps::Ground::SNOW: objs.push_back( ICN::COBJ0022 ); objs.push_back( ICN::COBJ0026 ); break; case Maps::Ground::SWAMP: objs.push_back( ICN::COBJ0005 ); objs.push_back( ICN::COBJ0006 ); objs.push_back( ICN::COBJ0007 ); objs.push_back( ICN::COBJ0008 ); objs.push_back( ICN::COBJ0011 ); objs.push_back( ICN::COBJ0012 ); objs.push_back( ICN::COBJ0014 ); objs.push_back( ICN::COBJ0015 ); objs.push_back( ICN::COBJ0016 ); objs.push_back( ICN::COBJ0017 ); objs.push_back( ICN::COBJ0027 ); break; case Maps::Ground::BEACH: objs.push_back( ICN::COBJ0005 ); objs.push_back( ICN::COBJ0011 ); objs.push_back( ICN::COBJ0017 ); break; case Maps::Ground::DIRT: objs.push_back( ICN::COBJ0002 ); objs.push_back( ICN::COBJ0005 ); objs.push_back( ICN::COBJ0007 ); objs.push_back( ICN::COBJ0011 ); objs.push_back( ICN::COBJ0014 ); objs.push_back( ICN::COBJ0019 ); objs.push_back( ICN::COBJ0027 ); break; case Maps::Ground::GRASS: objs.push_back( ICN::COBJ0002 ); objs.push_back( ICN::COBJ0004 ); objs.push_back( ICN::COBJ0005 ); objs.push_back( ICN::COBJ0008 ); objs.push_back( ICN::COBJ0011 ); objs.push_back( ICN::COBJ0012 ); objs.push_back( ICN::COBJ0014 ); objs.push_back( ICN::COBJ0015 ); objs.push_back( ICN::COBJ0019 ); objs.push_back( ICN::COBJ0027 ); objs.push_back( ICN::COBJ0028 ); break; case Maps::Ground::WASTELAND: objs.push_back( ICN::COBJ0009 ); objs.push_back( ICN::COBJ0013 ); objs.push_back( ICN::COBJ0018 ); objs.push_back( ICN::COBJ0020 ); objs.push_back( ICN::COBJ0021 ); objs.push_back( ICN::COBJ0024 ); break; case Maps::Ground::LAVA: objs.push_back( ICN::COBJ0007 ); objs.push_back( ICN::COBJ0029 ); objs.push_back( ICN::COBJ0031 ); break; case Maps::Ground::WATER: objs.push_back( ICN::COBJ0003 ); objs.push_back( ICN::COBJ0010 ); objs.push_back( ICN::COBJ0023 ); break; default: break; } Rand::ShuffleWithGen( objs, gen ); const auto largeObstacleHexCount = std::count_if( begin(), end(), []( const Cell & cell ) { return cell.GetObject() == 0x40; } ); uint8_t maxSmallObstacleCount = 0; uint8_t maxTwoHexObstacleCount = 0; if ( largeObstacleHexCount == 0 ) { maxSmallObstacleCount = 6; maxTwoHexObstacleCount = 3; } else if ( largeObstacleHexCount <= 7 ) { maxSmallObstacleCount = 4; maxTwoHexObstacleCount = 2; } else if ( largeObstacleHexCount <= 13 ) { maxSmallObstacleCount = 3; maxTwoHexObstacleCount = 2; } else { maxSmallObstacleCount = 2; maxTwoHexObstacleCount = 1; } // Limit the number of two-hex obstacles to maxTwoHexObstacleCount uint8_t twoHexCount = 0; for ( auto iter = objs.begin(); iter != objs.end(); ) { if ( isTwoHexObject( *iter ) ) { ++twoHexCount; if ( twoHexCount > maxTwoHexObstacleCount ) { iter = objs.erase( iter ); continue; } } ++iter; } const size_t objectsToPlace = std::min( objs.size(), static_cast( Rand::GetWithGen( 0, maxSmallObstacleCount, gen ) ) ); for ( size_t i = 0; i < objectsToPlace; ++i ) { // two-hex obstacles are not allowed on column 8 as they would cover column 9 which is reserved for units const bool checkRightCell = isTwoHexObject( objs[i] ); // tall obstacles like trees should not be placed on top 2 rows const bool isTallObstacle = isTallObject( objs[i] ); uint32_t dest; do { dest = GetRandomObstaclePosition( gen ); } while ( at( dest ).GetObject() != 0 || ( checkRightCell && ( at( dest + 1 ).GetObject() != 0 || ( dest % ARENAW ) == 8 ) ) || ( isTallObstacle && dest < ( ARENAW * 2 ) ) ); SetCobjObject( objs[i], dest ); } } void Battle::Board::SetCobjObject( const int icn, const uint32_t dst ) { at( dst ).SetObject( 0x80 + ( icn - ICN::COBJ0000 ) ); if ( isTwoHexObject( icn ) ) { assert( at( dst + 1 ).GetObject() == 0 ); at( dst + 1 ).SetObject( 0x40 ); } } void Battle::Board::SetCovrObjects( int icn ) { switch ( icn ) { case ICN::COVR0001: case ICN::COVR0007: case ICN::COVR0013: case ICN::COVR0019: at( 25 ).SetObject( 0x40 ); at( 26 ).SetObject( 0x40 ); at( 27 ).SetObject( 0x40 ); at( 28 ).SetObject( 0x40 ); at( 40 ).SetObject( 0x40 ); at( 51 ).SetObject( 0x40 ); break; case ICN::COVR0002: case ICN::COVR0008: case ICN::COVR0014: case ICN::COVR0020: at( 47 ).SetObject( 0x40 ); at( 48 ).SetObject( 0x40 ); at( 49 ).SetObject( 0x40 ); at( 50 ).SetObject( 0x40 ); at( 51 ).SetObject( 0x40 ); break; case ICN::COVR0003: case ICN::COVR0015: case ICN::COVR0021: at( 35 ).SetObject( 0x40 ); at( 41 ).SetObject( 0x40 ); at( 46 ).SetObject( 0x40 ); at( 47 ).SetObject( 0x40 ); at( 48 ).SetObject( 0x40 ); at( 49 ).SetObject( 0x40 ); at( 50 ).SetObject( 0x40 ); at( 51 ).SetObject( 0x40 ); break; case ICN::COVR0009: at( 35 ).SetObject( 0x40 ); at( 40 ).SetObject( 0x40 ); at( 46 ).SetObject( 0x40 ); at( 47 ).SetObject( 0x40 ); at( 48 ).SetObject( 0x40 ); at( 49 ).SetObject( 0x40 ); at( 50 ).SetObject( 0x40 ); break; case ICN::COVR0004: case ICN::COVR0010: case ICN::COVR0016: case ICN::COVR0022: at( 41 ).SetObject( 0x40 ); at( 51 ).SetObject( 0x40 ); at( 58 ).SetObject( 0x40 ); at( 59 ).SetObject( 0x40 ); at( 60 ).SetObject( 0x40 ); at( 61 ).SetObject( 0x40 ); at( 62 ).SetObject( 0x40 ); break; case ICN::COVR0005: case ICN::COVR0017: at( 24 ).SetObject( 0x40 ); at( 25 ).SetObject( 0x40 ); at( 26 ).SetObject( 0x40 ); at( 27 ).SetObject( 0x40 ); at( 28 ).SetObject( 0x40 ); at( 29 ).SetObject( 0x40 ); at( 30 ).SetObject( 0x40 ); at( 58 ).SetObject( 0x40 ); at( 59 ).SetObject( 0x40 ); at( 60 ).SetObject( 0x40 ); at( 61 ).SetObject( 0x40 ); at( 62 ).SetObject( 0x40 ); at( 63 ).SetObject( 0x40 ); at( 68 ).SetObject( 0x40 ); at( 74 ).SetObject( 0x40 ); break; case ICN::COVR0006: case ICN::COVR0018: at( 14 ).SetObject( 0x40 ); at( 15 ).SetObject( 0x40 ); at( 16 ).SetObject( 0x40 ); at( 17 ).SetObject( 0x40 ); at( 18 ).SetObject( 0x40 ); at( 24 ).SetObject( 0x40 ); at( 68 ).SetObject( 0x40 ); at( 80 ).SetObject( 0x40 ); at( 81 ).SetObject( 0x40 ); at( 82 ).SetObject( 0x40 ); at( 83 ).SetObject( 0x40 ); at( 84 ).SetObject( 0x40 ); break; case ICN::COVR0011: case ICN::COVR0023: at( 15 ).SetObject( 0x40 ); at( 25 ).SetObject( 0x40 ); at( 36 ).SetObject( 0x40 ); at( 51 ).SetObject( 0x40 ); at( 62 ).SetObject( 0x40 ); at( 71 ).SetObject( 0x40 ); at( 72 ).SetObject( 0x40 ); break; case ICN::COVR0012: case ICN::COVR0024: at( 18 ).SetObject( 0x40 ); at( 29 ).SetObject( 0x40 ); at( 41 ).SetObject( 0x40 ); at( 59 ).SetObject( 0x40 ); at( 70 ).SetObject( 0x40 ); at( 82 ).SetObject( 0x40 ); at( 83 ).SetObject( 0x40 ); break; default: break; } } Battle::Cell * Battle::Board::GetCell( const int32_t position, const int dir /* = CENTER */ ) { if ( !isValidDirection( position, dir ) ) { return nullptr; } Board * board = Arena::GetBoard(); assert( board != nullptr ); const int32_t idx = GetIndexDirection( position, dir ); assert( isValidIndex( idx ) ); return &board->at( idx ); } Battle::Indexes Battle::Board::GetMoveWideIndexes( const int32_t head, const bool reflect ) { if ( !isValidIndex( head ) ) { return {}; } Indexes result; result.reserve( 4 ); if ( isValidDirection( head, LEFT ) ) { result.push_back( GetIndexDirection( head, LEFT ) ); } if ( isValidDirection( head, RIGHT ) ) { result.push_back( GetIndexDirection( head, RIGHT ) ); } if ( reflect ) { if ( isValidDirection( head, TOP_LEFT ) ) { result.push_back( GetIndexDirection( head, TOP_LEFT ) ); } if ( isValidDirection( head, BOTTOM_LEFT ) ) { result.push_back( GetIndexDirection( head, BOTTOM_LEFT ) ); } } else { if ( isValidDirection( head, TOP_RIGHT ) ) { result.push_back( GetIndexDirection( head, TOP_RIGHT ) ); } if ( isValidDirection( head, BOTTOM_RIGHT ) ) { result.push_back( GetIndexDirection( head, BOTTOM_RIGHT ) ); } } return result; } Battle::Indexes Battle::Board::GetAroundIndexes( const int32_t center ) { if ( !isValidIndex( center ) ) { return {}; } Indexes result; result.reserve( 6 ); for ( direction_t dir = TOP_LEFT; dir < CENTER; ++dir ) { if ( !isValidDirection( center, dir ) ) { continue; } result.push_back( GetIndexDirection( center, dir ) ); } return result; } Battle::Indexes Battle::Board::GetAroundIndexes( const Unit & unit ) { return GetAroundIndexes( unit.GetPosition() ); } Battle::Indexes Battle::Board::GetAroundIndexes( const Position & pos ) { if ( pos.GetHead() == nullptr ) { return {}; } const int32_t headIdx = pos.GetHead()->GetIndex(); if ( pos.GetTail() == nullptr ) { return GetAroundIndexes( headIdx ); } const int32_t tailIdx = pos.GetTail()->GetIndex(); if ( !isValidIndex( headIdx ) || !isValidIndex( tailIdx ) ) { return {}; } Indexes result; result.reserve( 8 ); // Traversing cells in a clockwise direction if ( headIdx > tailIdx ) { if ( isValidDirection( tailIdx, TOP_LEFT ) ) { result.push_back( GetIndexDirection( tailIdx, TOP_LEFT ) ); } if ( isValidDirection( tailIdx, TOP_RIGHT ) ) { result.push_back( GetIndexDirection( tailIdx, TOP_RIGHT ) ); } if ( isValidDirection( headIdx, TOP_RIGHT ) ) { result.push_back( GetIndexDirection( headIdx, TOP_RIGHT ) ); } if ( isValidDirection( headIdx, RIGHT ) ) { result.push_back( GetIndexDirection( headIdx, RIGHT ) ); } if ( isValidDirection( headIdx, BOTTOM_RIGHT ) ) { result.push_back( GetIndexDirection( headIdx, BOTTOM_RIGHT ) ); } if ( isValidDirection( tailIdx, BOTTOM_RIGHT ) ) { result.push_back( GetIndexDirection( tailIdx, BOTTOM_RIGHT ) ); } if ( isValidDirection( tailIdx, BOTTOM_LEFT ) ) { result.push_back( GetIndexDirection( tailIdx, BOTTOM_LEFT ) ); } if ( isValidDirection( tailIdx, LEFT ) ) { result.push_back( GetIndexDirection( tailIdx, LEFT ) ); } } else if ( headIdx < tailIdx ) { if ( isValidDirection( headIdx, TOP_LEFT ) ) { result.push_back( GetIndexDirection( headIdx, TOP_LEFT ) ); } if ( isValidDirection( headIdx, TOP_RIGHT ) ) { result.push_back( GetIndexDirection( headIdx, TOP_RIGHT ) ); } if ( isValidDirection( tailIdx, TOP_RIGHT ) ) { result.push_back( GetIndexDirection( tailIdx, TOP_RIGHT ) ); } if ( isValidDirection( tailIdx, RIGHT ) ) { result.push_back( GetIndexDirection( tailIdx, RIGHT ) ); } if ( isValidDirection( tailIdx, BOTTOM_RIGHT ) ) { result.push_back( GetIndexDirection( tailIdx, BOTTOM_RIGHT ) ); } if ( isValidDirection( headIdx, BOTTOM_RIGHT ) ) { result.push_back( GetIndexDirection( headIdx, BOTTOM_RIGHT ) ); } if ( isValidDirection( headIdx, BOTTOM_LEFT ) ) { result.push_back( GetIndexDirection( headIdx, BOTTOM_LEFT ) ); } if ( isValidDirection( headIdx, LEFT ) ) { result.push_back( GetIndexDirection( headIdx, LEFT ) ); } } return result; } Battle::Indexes Battle::Board::GetDistanceIndexes( const int32_t center, const uint32_t radius ) { if ( !isValidIndex( center ) ) { return {}; } const int32_t centerX = center % ARENAW; const int32_t centerY = center / ARENAW; // Axial coordinates const int32_t centerQ = centerX - ( centerY + ( centerY % 2 ) ) / 2; const int32_t centerR = centerY; Indexes result; result.reserve( 3 * radius * ( radius + 1 ) + 1 ); const int32_t intRadius = radius; for ( int32_t dq = -intRadius; dq <= intRadius; ++dq ) { for ( int32_t dr = std::max( -intRadius, -intRadius - dq ); dr <= std::min( intRadius, intRadius - dq ); ++dr ) { // Center should not be included if ( dq == 0 && dr == 0 ) { continue; } const int32_t q = centerQ + dq; const int32_t r = centerR + dr; const int32_t x = q + ( r + ( r % 2 ) ) / 2; const int32_t y = r; if ( x < 0 || x >= ARENAW || y < 0 || y >= ARENAH ) { continue; } const int32_t idx = y * ARENAW + x; assert( isValidIndex( idx ) ); result.push_back( idx ); } } return result; } Battle::Indexes Battle::Board::GetDistanceIndexes( const Position & pos, const uint32_t radius ) { const std::array posIndexes = { pos.GetHead() ? pos.GetHead()->GetIndex() : -1, pos.GetTail() ? pos.GetTail()->GetIndex() : -1 }; std::set boardIndexes; for ( const int32_t posIdx : posIndexes ) { if ( !Board::isValidIndex( posIdx ) ) { continue; } for ( const int32_t idx : Board::GetDistanceIndexes( posIdx, radius ) ) { assert( Board::isValidIndex( idx ) ); if ( std::find( posIndexes.begin(), posIndexes.end(), idx ) != posIndexes.end() ) { continue; } boardIndexes.insert( idx ); } } Indexes result; result.reserve( boardIndexes.size() ); result.assign( boardIndexes.begin(), boardIndexes.end() ); return result; } Battle::Indexes Battle::Board::GetDistanceIndexes( const Unit & unit, const uint32_t radius ) { return GetDistanceIndexes( unit.GetPosition(), radius ); } bool Battle::Board::isValidMirrorImageIndex( const int32_t index, const Unit * unit ) { if ( unit == nullptr ) { return false; } const Position mirrorPos = Position::GetPosition( *unit, index ); if ( !mirrorPos.isValidForUnit( unit ) ) { return false; } if ( unit->GetPosition().contains( mirrorPos.GetHead()->GetIndex() ) ) { return false; } if ( unit->isWide() && unit->GetPosition().contains( mirrorPos.GetTail()->GetIndex() ) ) { return false; } return true; } bool Battle::Board::CanAttackFromCell( const Unit & unit, const int32_t from ) { const Position pos = Position::GetReachable( unit, from ); // Target unit cannot be attacked if out of reach if ( pos.GetHead() == nullptr ) { return false; } assert( pos.isValidForUnit( unit ) ); const Castle * castle = Arena::GetCastle(); // No moat - no further restrictions if ( !castle || !castle->isBuild( BUILD_MOAT ) ) { return true; } // Target unit isn't attacked from the moat if ( !isMoatIndex( from, unit ) ) { return true; } // The moat doesn't stop flying units if ( unit.isFlying() ) { return true; } // Attacker is already near the target if ( from == unit.GetHeadIndex() || ( unit.isWide() && from == unit.GetTailIndex() ) ) { return true; } // In all other cases, the attack is prohibited return false; } bool Battle::Board::CanAttackTargetFromPosition( const Unit & attacker, const Unit & target, const int32_t dst ) { // Get the actual position of the attacker before attacking const Position pos = Position::GetReachable( attacker, dst ); // Check that the attacker is actually capable of attacking the target from this position const std::array cells = { pos.GetHead(), pos.GetTail() }; for ( const Cell * cell : cells ) { if ( cell == nullptr ) { continue; } const int32_t cellIdx = cell->GetIndex(); if ( Board::GetDistance( target.GetPosition(), cellIdx ) > 1 ) { continue; } if ( !CanAttackFromCell( attacker, cellIdx ) ) { continue; } return true; } return false; } std::string Battle::Board::GetMoatInfo() { std::string msg = _( "The Moat reduces by -%{count} the defense skill of any unit and slows to half movement rate." ); StringReplace( msg, "%{count}", GameStatic::GetBattleMoatReduceDefense() ); return msg; } fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_board.h000066400000000000000000000144151456075706000226200ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2012 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2BATTLE_BOARD_H #define H2BATTLE_BOARD_H #include #include #include #include #include #include "battle_cell.h" #include "math_base.h" #define ARENAW 11 #define ARENAH 9 #define ARENASIZE ARENAW * ARENAH namespace Maps { class Tiles; } namespace Battle { class Unit; inline direction_t & operator++( direction_t & d ) { return d = ( CENTER == d ? TOP_LEFT : direction_t( d << 1 ) ); } inline direction_t & operator--( direction_t & d ) { return d = ( TOP_LEFT == d ? CENTER : direction_t( d >> 1 ) ); } using Indexes = std::vector; class Board : public std::vector { public: Board(); Board( const Board & ) = delete; Board & operator=( const Board & ) = delete; void removeDeadUnits(); void SetArea( const fheroes2::Rect & ); int32_t GetIndexAbsPosition( const fheroes2::Point & ) const; std::vector GetNearestTroops( const Unit * startUnit, const std::vector & blackList ); void SetCobjObjects( const Maps::Tiles & tile, std::mt19937 & gen ); void SetCovrObjects( int icn ); static std::string GetMoatInfo(); static Cell * GetCell( const int32_t position, const int dir = CENTER ); static bool isNearIndexes( const int32_t index1, const int32_t index2 ); static bool isValidIndex( const int32_t index ); // Returns true if the given index is considered to be inside the castle from the point of view of castle defense, // otherwise returns false. Indexes of destructible walls are considered to be located inside the castle. static bool isCastleIndex( const int32_t index ); static bool isMoatIndex( const int32_t index, const Unit & unit ); static bool isOutOfWallsIndex( const int32_t index ); static int GetReflectDirection( const int dir ); static int GetDirection( const int32_t index1, const int32_t index2 ); // Returns the distance to the cell with the given index from the given edge of the battlefield along the X axis. The // distance from the edges of the battlefield to the cells closest to them is counted as one. static uint32_t GetDistanceFromBoardEdgeAlongXAxis( const int32_t index, const bool fromRightEdge ); // Returns the distance between two cells with the given indexes. If any of the indexes is not valid, then returns 0. static uint32_t GetDistance( const int32_t index1, const int32_t index2 ); // Returns the distance between two given positions. This distance is calculated as the distance between the cells of // both positions closest to each other. If any of the positions is not valid, then returns 0. static uint32_t GetDistance( const Position & pos1, const Position & pos2 ); // Returns the distance between the given position and the cell with the given index. The distance is calculated as // the distance between the cell with the given index and the cell closest to it, which is part of the given position. // If either the position or the index is not valid, then returns 0. static uint32_t GetDistance( const Position & pos, const int32_t index ); static bool isValidDirection( const int32_t index, const int dir ); static int32_t GetIndexDirection( const int32_t index, const int dir ); static Indexes GetDistanceIndexes( const int32_t center, const uint32_t radius ); static Indexes GetDistanceIndexes( const Unit & unit, const uint32_t radius ); static Indexes GetDistanceIndexes( const Position & pos, const uint32_t radius ); static Indexes GetAroundIndexes( const int32_t center ); static Indexes GetAroundIndexes( const Unit & unit ); static Indexes GetAroundIndexes( const Position & pos ); static Indexes GetMoveWideIndexes( const int32_t head, const bool reflect ); static bool isValidMirrorImageIndex( const int32_t index, const Unit * unit ); // Checks whether a given unit is (in principle) capable of attacking in melee during the current turn from a cell with // a given index static bool CanAttackFromCell( const Unit & unit, const int32_t from ); // Checks whether the attacker is able to attack the target in melee during the current turn from a position corresponding // to the given index static bool CanAttackTargetFromPosition( const Unit & attacker, const Unit & target, const int32_t dst ); private: void SetCobjObject( const int icn, const uint32_t dst ); }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_bridge.cpp000066400000000000000000000104041456075706000233120ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "battle_bridge.h" #include "battle_arena.h" #include "battle_board.h" #include "battle_cell.h" #include "battle_grave.h" #include "battle_interface.h" #include "battle_troop.h" #include "castle.h" bool Battle::Bridge::isOccupied() { const Battle::Graveyard * graveyard = Arena::GetGraveyard(); // Yes if there are any troops (alive or dead) on CELL_MOAT and CELL_GATES tiles return Board::GetCell( CELL_MOAT )->GetUnit() || Board::GetCell( CELL_GATES )->GetUnit() || graveyard->GetLastTroopUID( CELL_MOAT ) || graveyard->GetLastTroopUID( CELL_GATES ); } bool Battle::Bridge::NeedDown( const Unit & unit, const int32_t dstIdx ) const { // No if bridge is destroyed or already lowered or unit does not belong to the castle or there are any troops (alive or dead) on or under the bridge if ( !isValid() || isDown() || unit.GetColor() != Arena::GetCastle()->GetColor() || isOccupied() ) { return false; } if ( unit.isFlying() ) { return dstIdx == CELL_GATES; } const int32_t headIdx = unit.GetHeadIndex(); if ( dstIdx == CELL_GATES && ( headIdx == CELL_AFTER_GATES || headIdx == CELL_BELOW_BRIDGE || headIdx == CELL_ABOVE_BRIDGE ) ) { return true; } if ( dstIdx == CELL_MOAT && headIdx != CELL_GATES ) { return true; } if ( unit.isWide() ) { const int32_t tailIdx = unit.GetTailIndex(); if ( dstIdx == CELL_BEFORE_MOAT && ( tailIdx == CELL_BELOW_BRIDGE || tailIdx == CELL_ABOVE_BRIDGE ) ) { return true; } } return false; } bool Battle::Bridge::isPassable( const Unit & unit ) const { // Yes if bridge is lowered (or destroyed), or unit belongs to the castle and there are no any troops (alive or dead) on or under the bridge return isDown() || ( unit.GetColor() == Arena::GetCastle()->GetColor() && !isOccupied() ); } void Battle::Bridge::SetDestroyed() { _isDestroyed = true; _isDown = true; Board::GetCell( CELL_GATES )->SetObject( 0 ); } void Battle::Bridge::SetPassability( const Unit & unit ) const { if ( isPassable( unit ) ) { Board::GetCell( CELL_GATES )->SetObject( 0 ); } else { Board::GetCell( CELL_GATES )->SetObject( 1 ); } } void Battle::Bridge::ActionUp() { assert( AllowUp() ); if ( Arena::GetInterface() ) { Arena::GetInterface()->RedrawBridgeAnimation( false ); } _isDown = false; } void Battle::Bridge::ActionDown() { assert( isValid() && !isDown() && !isOccupied() ); if ( Arena::GetInterface() ) { Arena::GetInterface()->RedrawBridgeAnimation( true ); } _isDown = true; } fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_bridge.h000066400000000000000000000061461456075706000227670ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2BATTLE_BRIDGE_H #define H2BATTLE_BRIDGE_H #include #include namespace Battle { class Unit; class Bridge final { public: Bridge() = default; Bridge( const Bridge & ) = delete; ~Bridge() = default; Bridge & operator=( const Bridge & ) = delete; void ActionUp(); void ActionDown(); void SetDestroyed(); void SetPassability( const Unit & unit ) const; bool AllowUp() const { // Yes if not destroyed and lowered and there are no any troops (alive or dead) on or under the bridge return isValid() && isDown() && !isOccupied(); } bool NeedDown( const Unit & unit, const int32_t dstIdx ) const; bool isPassable( const Unit & unit ) const; bool isValid() const { return !_isDestroyed; } bool isDestroyed() const { return _isDestroyed; } bool isDown() const { assert( !_isDestroyed || _isDown ); return _isDown; } private: static bool isOccupied(); bool _isDestroyed{ false }; bool _isDown{ false }; enum { CELL_ABOVE_BRIDGE = 39, CELL_BEFORE_MOAT = 48, CELL_MOAT = 49, CELL_GATES = 50, CELL_AFTER_GATES = 51, CELL_BELOW_BRIDGE = 61 }; }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_catapult.cpp000066400000000000000000000143451456075706000237030ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "battle_catapult.h" #include #include #include #include #include "artifact.h" #include "artifact_info.h" #include "heroes_base.h" #include "logging.h" #include "rand.h" #include "skill.h" Battle::Catapult::Catapult( const HeroBase & hero ) : catShots( 1 ) , doubleDamageChance( 25 ) , canMiss( true ) { switch ( hero.GetLevelSkill( Skill::Secondary::BALLISTICS ) ) { case Skill::Level::BASIC: doubleDamageChance = 50; canMiss = false; break; case Skill::Level::ADVANCED: doubleDamageChance = 50; catShots += 1; canMiss = false; break; case Skill::Level::EXPERT: doubleDamageChance = 100; catShots += 1; canMiss = false; break; default: break; } catShots += hero.GetBagArtifacts().getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::EXTRA_CATAPULT_SHOTS ); } uint32_t Battle::Catapult::GetDamage( Rand::DeterministicRandomGenerator & randomGenerator ) const { if ( doubleDamageChance == 100 || doubleDamageChance >= randomGenerator.Get( 1, 100 ) ) { DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "Catapult dealt double damage! (" << doubleDamageChance << "% chance)" ) return 2; } return 1; } fheroes2::Point Battle::Catapult::GetTargetPosition( const CastleDefenseElement target, const bool hit ) { switch ( target ) { case CastleDefenseElement::WALL1: return hit ? fheroes2::Point( 475, 45 ) : fheroes2::Point( 495, 105 ); case CastleDefenseElement::WALL2: return hit ? fheroes2::Point( 420, 115 ) : fheroes2::Point( 460, 175 ); case CastleDefenseElement::WALL3: return hit ? fheroes2::Point( 415, 280 ) : fheroes2::Point( 455, 280 ); case CastleDefenseElement::WALL4: return hit ? fheroes2::Point( 490, 390 ) : fheroes2::Point( 530, 390 ); case CastleDefenseElement::TOWER1: return hit ? fheroes2::Point( 430, 40 ) : fheroes2::Point( 490, 120 ); case CastleDefenseElement::TOWER2: return hit ? fheroes2::Point( 430, 300 ) : fheroes2::Point( 490, 340 ); case CastleDefenseElement::BRIDGE: return hit ? fheroes2::Point( 400, 195 ) : fheroes2::Point( 450, 235 ); case CastleDefenseElement::CENTRAL_TOWER: return hit ? fheroes2::Point( 580, 160 ) : fheroes2::Point( 610, 320 ); default: break; } return fheroes2::Point(); } Battle::CastleDefenseElement Battle::Catapult::GetTarget( const std::map & stateOfCatapultTargets, Rand::DeterministicRandomGenerator & randomGenerator ) { const auto checkTargetState = [&stateOfCatapultTargets]( const CastleDefenseElement target ) { const auto iter = stateOfCatapultTargets.find( target ); assert( iter != stateOfCatapultTargets.end() ); return ( iter->second > 0 ); }; std::vector targets; targets.reserve( 4 ); // Walls if ( checkTargetState( CastleDefenseElement::WALL1 ) ) { targets.push_back( CastleDefenseElement::WALL1 ); } if ( checkTargetState( CastleDefenseElement::WALL2 ) ) { targets.push_back( CastleDefenseElement::WALL2 ); } if ( checkTargetState( CastleDefenseElement::WALL3 ) ) { targets.push_back( CastleDefenseElement::WALL3 ); } if ( checkTargetState( CastleDefenseElement::WALL4 ) ) { targets.push_back( CastleDefenseElement::WALL4 ); } // Right/left towers if ( targets.empty() ) { if ( checkTargetState( CastleDefenseElement::TOWER1 ) ) { targets.push_back( CastleDefenseElement::TOWER1 ); } if ( checkTargetState( CastleDefenseElement::TOWER2 ) ) { targets.push_back( CastleDefenseElement::TOWER2 ); } } // Bridge if ( targets.empty() ) { if ( checkTargetState( CastleDefenseElement::BRIDGE ) ) { targets.push_back( CastleDefenseElement::BRIDGE ); } } // Central tower if ( targets.empty() ) { if ( checkTargetState( CastleDefenseElement::CENTRAL_TOWER ) ) { targets.push_back( CastleDefenseElement::CENTRAL_TOWER ); } } if ( !targets.empty() ) { return randomGenerator.Get( targets ); } DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "no target was found" ) return CastleDefenseElement::NONE; } bool Battle::Catapult::IsNextShotHit( Rand::DeterministicRandomGenerator & randomGenerator ) const { // Miss chance is 25% return !( canMiss && randomGenerator.Get( 1, 20 ) < 6 ); } fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_catapult.h000066400000000000000000000054211456075706000233430ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2BATTLE_CATAPULT_H #define H2BATTLE_CATAPULT_H #include #include #include "battle.h" #include "math_base.h" class HeroBase; namespace Rand { class DeterministicRandomGenerator; } namespace Battle { class Catapult { public: explicit Catapult( const HeroBase & hero ); Catapult( const Catapult & ) = delete; Catapult & operator=( const Catapult & ) = delete; static CastleDefenseElement GetTarget( const std::map & stateOfCatapultTargets, Rand::DeterministicRandomGenerator & randomGenerator ); static fheroes2::Point GetTargetPosition( const CastleDefenseElement target, const bool hit ); uint32_t GetShots() const { return catShots; } uint32_t GetDamage( Rand::DeterministicRandomGenerator & randomGenerator ) const; bool IsNextShotHit( Rand::DeterministicRandomGenerator & randomGenerator ) const; private: uint32_t catShots; uint32_t doubleDamageChance; bool canMiss; }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_cell.cpp000066400000000000000000000304041456075706000227770ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "battle_cell.h" #include #include "battle_arena.h" #include "battle_board.h" #include "battle_troop.h" #include "tools.h" namespace { bool inABC( const fheroes2::Point & base, const fheroes2::Point & pt1, const fheroes2::Point & pt2, const fheroes2::Point & pt3 ) { const int32_t a = ( pt1.x - base.x ) * ( pt2.y - pt1.y ) - ( pt2.x - pt1.x ) * ( pt1.y - base.y ); const int32_t b = ( pt2.x - base.x ) * ( pt3.y - pt2.y ) - ( pt3.x - pt2.x ) * ( pt2.y - base.y ); const int32_t c = ( pt3.x - base.x ) * ( pt1.y - pt3.y ) - ( pt1.x - pt3.x ) * ( pt3.y - base.y ); return ( ( a >= 0 && b >= 0 && c >= 0 ) || ( a < 0 && b < 0 && c < 0 ) ); } const int32_t cellHeightVerSide = 32; const int32_t infl = 12; } void Battle::Position::Set( const int32_t head, const bool wide, const bool reflect ) { first = Board::GetCell( head ); if ( first && wide ) { second = Board::GetCell( first->GetIndex(), reflect ? RIGHT : LEFT ); } else { second = nullptr; } } void Battle::Position::Swap() { if ( first == nullptr || second == nullptr ) { return; } std::swap( first, second ); } fheroes2::Rect Battle::Position::GetRect() const { if ( first == nullptr ) { return {}; } return second ? getBoundaryRect( first->GetPos(), second->GetPos() ) : first->GetPos(); } Battle::Position Battle::Position::GetPosition( const Unit & unit, const int32_t dst ) { Position result; if ( unit.isWide() ) { const auto checkCells = [&unit]( Cell * headCell, Cell * tailCell ) { Position pos; if ( headCell == nullptr || ( !unit.GetPosition().contains( headCell->GetIndex() ) && !headCell->isPassable( true ) ) ) { return pos; } if ( tailCell == nullptr || ( !unit.GetPosition().contains( tailCell->GetIndex() ) && !tailCell->isPassable( true ) ) ) { return pos; } pos.first = headCell; pos.second = tailCell; return pos; }; const int tailDirection = unit.isReflect() ? RIGHT : LEFT; if ( Board::isValidDirection( dst, tailDirection ) ) { Cell * headCell = Board::GetCell( dst ); Cell * tailCell = Board::GetCell( Board::GetIndexDirection( dst, tailDirection ) ); result = checkCells( headCell, tailCell ); } if ( result.GetHead() == nullptr || result.GetTail() == nullptr ) { const int headDirection = unit.isReflect() ? LEFT : RIGHT; if ( Board::isValidDirection( dst, headDirection ) ) { Cell * headCell = Board::GetCell( Board::GetIndexDirection( dst, headDirection ) ); Cell * tailCell = Board::GetCell( dst ); result = checkCells( headCell, tailCell ); } } } else { Cell * headCell = Board::GetCell( dst ); if ( headCell != nullptr && ( unit.GetPosition().contains( headCell->GetIndex() ) || headCell->isPassable( true ) ) ) { result.first = headCell; } } return result; } Battle::Position Battle::Position::GetReachable( const Unit & unit, const int32_t dst, const std::optional speed /* = {} */ ) { Arena * arena = GetArena(); assert( arena != nullptr ); const auto checkReachability = [&unit, speed, arena]( const Position & pos ) -> Position { if ( speed ) { if ( arena->isPositionReachable( unit, pos, false ) && arena->CalculateMoveCost( unit, pos ) <= *speed ) { return pos; } } else if ( arena->isPositionReachable( unit, pos, true ) ) { return pos; } return {}; }; if ( unit.isWide() ) { const auto checkCells = [&checkReachability]( Cell * headCell, Cell * tailCell ) -> Position { if ( headCell == nullptr || tailCell == nullptr ) { return {}; } Position pos; pos.first = headCell; pos.second = tailCell; return checkReachability( pos ); }; const auto tryHead = [&unit, dst, &checkCells]() -> Position { const int tailDirection = unit.isReflect() ? RIGHT : LEFT; if ( Board::isValidDirection( dst, tailDirection ) ) { Cell * headCell = Board::GetCell( dst ); Cell * tailCell = Board::GetCell( Board::GetIndexDirection( dst, tailDirection ) ); return checkCells( headCell, tailCell ); } return {}; }; const auto tryTail = [&unit, dst, &checkCells]() -> Position { const int headDirection = unit.isReflect() ? LEFT : RIGHT; if ( Board::isValidDirection( dst, headDirection ) ) { Cell * headCell = Board::GetCell( Board::GetIndexDirection( dst, headDirection ) ); Cell * tailCell = Board::GetCell( dst ); return checkCells( headCell, tailCell ); } return {}; }; Position headPos = tryHead(); if ( headPos.GetHead() != nullptr && headPos.GetTail() != nullptr ) { return headPos; } return tryTail(); } Cell * headCell = Board::GetCell( dst ); if ( headCell == nullptr ) { return {}; } Position pos; pos.first = headCell; return checkReachability( pos ); } bool Battle::Position::isReflect() const { return first && second && first->GetIndex() < second->GetIndex(); } bool Battle::Position::contains( const int32_t idx ) const { return ( first && first->GetIndex() == idx ) || ( second && second->GetIndex() == idx ); } bool Battle::Position::isEmpty() const { if ( first != nullptr ) { return false; } assert( second == nullptr ); return true; } bool Battle::Position::isValidForUnit( const Unit & unit ) const { if ( first == nullptr ) { return false; } return unit.isWide() ? second != nullptr : second == nullptr; } bool Battle::Position::operator<( const Position & other ) const { assert( first == nullptr || Board::isValidIndex( first->GetIndex() ) ); assert( second == nullptr || Board::isValidIndex( second->GetIndex() ) ); assert( other.first == nullptr || Board::isValidIndex( other.first->GetIndex() ) ); assert( other.second == nullptr || Board::isValidIndex( other.second->GetIndex() ) ); // It is necessary to guarantee a stable order of positions, it should depend on the indexes of cells, and not on the values of pointers to these cells const auto theseIndexes = std::make_pair( first ? first->GetIndex() : -1, second ? second->GetIndex() : -1 ); const auto otherIndexes = std::make_pair( other.first ? other.first->GetIndex() : -1, other.second ? other.second->GetIndex() : -1 ); return theseIndexes < otherIndexes; } Battle::Cell::Cell( const int32_t idx ) : _index( idx ) , _object( 0 ) , _unit( nullptr ) { SetArea( fheroes2::Rect() ); } void Battle::Cell::SetArea( const fheroes2::Rect & area ) { _pos.x = area.x + 89 - ( ( ( _index / ARENAW ) % 2 ) ? CELLW / 2 : 0 ) + CELLW * ( _index % ARENAW ); _pos.y = area.y + 62 + ( ( CELLH - ( CELLH - cellHeightVerSide ) / 2 ) * ( _index / ARENAW ) ); _pos.width = CELLW; _pos.height = CELLH; // center _coord[0] = { infl * _pos.x + infl * _pos.width / 2, infl * _pos.y + infl * _pos.height / 2 }; // coordinates _coord[1] = { infl * _pos.x, infl * _pos.y + infl * ( _pos.height - cellHeightVerSide ) / 2 }; _coord[2] = { infl * _pos.x + infl * _pos.width / 2, infl * _pos.y }; _coord[3] = { infl * _pos.x + infl * _pos.width, infl * _pos.y + infl * ( _pos.height - cellHeightVerSide ) / 2 }; _coord[4] = { infl * _pos.x + infl * _pos.width, infl * _pos.y + infl * _pos.height - infl * ( _pos.height - cellHeightVerSide ) / 2 }; _coord[5] = { infl * _pos.x + infl * _pos.width / 2, infl * _pos.y + infl * _pos.height }; _coord[6] = { infl * _pos.x, infl * _pos.y + infl * _pos.height - infl * ( _pos.height - cellHeightVerSide ) / 2 }; } Battle::direction_t Battle::Cell::GetTriangleDirection( const fheroes2::Point & dst ) const { const fheroes2::Point pt( infl * dst.x, infl * dst.y ); if ( pt == _coord[0] ) { return CENTER; } if ( inABC( pt, _coord[0], _coord[1], _coord[2] ) ) { return TOP_LEFT; } if ( inABC( pt, _coord[0], _coord[2], _coord[3] ) ) { return TOP_RIGHT; } if ( inABC( pt, _coord[0], _coord[3], _coord[4] ) ) { return RIGHT; } if ( inABC( pt, _coord[0], _coord[4], _coord[5] ) ) { return BOTTOM_RIGHT; } if ( inABC( pt, _coord[0], _coord[5], _coord[6] ) ) { return BOTTOM_LEFT; } if ( inABC( pt, _coord[0], _coord[1], _coord[6] ) ) { return LEFT; } return UNKNOWN; } bool Battle::Cell::isPositionIncludePoint( const fheroes2::Point & pt ) const { return UNKNOWN != GetTriangleDirection( pt ); } int32_t Battle::Cell::GetIndex() const { return _index; } void Battle::Cell::SetObject( const int object ) { _object = object; } int Battle::Cell::GetObject() const { return _object; } const fheroes2::Rect & Battle::Cell::GetPos() const { return _pos; } const Battle::Unit * Battle::Cell::GetUnit() const { return _unit; } Battle::Unit * Battle::Cell::GetUnit() { return _unit; } void Battle::Cell::SetUnit( Unit * unit ) { _unit = unit; } bool Battle::Cell::isPassableFromAdjacent( const Unit & unit, const Cell & adjacent ) const { assert( Board::isNearIndexes( _index, adjacent._index ) ); if ( unit.isWide() ) { const int dir = Board::GetDirection( adjacent._index, _index ); switch ( dir ) { case BOTTOM_RIGHT: case TOP_RIGHT: case BOTTOM_LEFT: case TOP_LEFT: { const bool reflect = ( ( BOTTOM_LEFT | TOP_LEFT ) & dir ) != 0; const Cell * tail = Board::GetCell( _index, reflect ? RIGHT : LEFT ); return tail && tail->isPassable( true ) && isPassable( true ); } case LEFT: case RIGHT: return isPassable( true ) || _index == unit.GetTailIndex(); default: break; } } return isPassable( true ); } bool Battle::Cell::isPassableForUnit( const Unit & unit ) const { const Position unitPos = Position::GetPosition( unit, _index ); if ( unitPos.GetHead() == nullptr ) { return false; } assert( unitPos.isValidForUnit( unit ) ); return true; } bool Battle::Cell::isPassable( const bool checkForUnit ) const { return _object == 0 && ( !checkForUnit || _unit == nullptr ); } fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_cell.h000066400000000000000000000127431456075706000224520ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2BATTLE_CELL_H #define H2BATTLE_CELL_H #include #include #include #include #include "math_base.h" #define CELLW 44 #define CELLH 52 namespace Battle { class Unit; enum direction_t { UNKNOWN = 0x00, TOP_LEFT = 0x01, TOP_RIGHT = 0x02, RIGHT = 0x04, BOTTOM_RIGHT = 0x08, BOTTOM_LEFT = 0x10, LEFT = 0x20, CENTER = 0x40, RIGHT_SIDE = TOP_RIGHT | RIGHT | BOTTOM_RIGHT, LEFT_SIDE = TOP_LEFT | LEFT | BOTTOM_LEFT, AROUND = RIGHT_SIDE | LEFT_SIDE }; class Cell final { public: explicit Cell( const int32_t idx ); Cell( const Cell & ) = delete; Cell( Cell && ) = default; ~Cell() = default; Cell & operator=( const Cell & ) = delete; Cell & operator=( Cell && ) = delete; int32_t GetIndex() const; const fheroes2::Rect & GetPos() const; int GetObject() const; const Unit * GetUnit() const; Unit * GetUnit(); direction_t GetTriangleDirection( const fheroes2::Point & dst ) const; bool isPositionIncludePoint( const fheroes2::Point & pt ) const; void SetArea( const fheroes2::Rect & area ); void SetObject( const int object ); void SetUnit( Unit * unit ); // Checks that the cell is passable for a given unit located in a certain adjacent cell bool isPassableFromAdjacent( const Unit & unit, const Cell & adjacent ) const; // Checks that the cell is passable for a given unit, i.e. unit can occupy it with his head or tail bool isPassableForUnit( const Unit & unit ) const; // Checks that the cell is passable, i.e. does not contain an obstacle or (optionally) a unit bool isPassable( const bool checkForUnit ) const; private: int32_t _index; fheroes2::Rect _pos; int _object; Unit * _unit; std::array _coord; }; class Position final : protected std::pair { public: Position() : std::pair( nullptr, nullptr ) {} void Set( const int32_t head, const bool wide, const bool reflect ); void Swap(); bool isReflect() const; bool contains( const int32_t idx ) const; // Returns the position that a given unit would occupy after moving to the cell // with a given index (without taking into account the pathfinder's info) or an // empty Position object if this index is unreachable in principle for this unit. static Position GetPosition( const Unit & unit, const int32_t dst ); // Returns the reachable position for a given unit, which corresponds to a given // index, or an empty Position object if this index is unreachable for this unit // on the current turn. if 'speed' is set, then this value will be used to check // the position reachability instead of the speed returned by 'unit'. static Position GetReachable( const Unit & unit, const int32_t dst, const std::optional speed = {} ); bool isEmpty() const; bool isValidForUnit( const Unit & unit ) const; bool isValidForUnit( const Unit * unit ) const { if ( unit == nullptr ) { return false; } return isValidForUnit( *unit ); } fheroes2::Rect GetRect() const; const Cell * GetHead() const { return first; } const Cell * GetTail() const { return second; } Cell * GetHead() { return first; } Cell * GetTail() { return second; } bool operator<( const Position & other ) const; }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_command.cpp000066400000000000000000000063731456075706000235060ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2012 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "battle_command.h" #include #include "tools.h" int Battle::Command::GetNextValue() { int val = 0; *this >> val; return val; } uint32_t Battle::Command::updateSeed( uint32_t seed ) const { switch ( _type ) { case CommandType::ATTACK: assert( size() == 5 ); fheroes2::hashCombine( seed, _type ); // Use only cell index to move and attacker & defender UIDs, because cell index to attack and attack direction may differ depending on whether the AI or the human // player gives the command fheroes2::hashCombine( seed, at( 2 ) ); fheroes2::hashCombine( seed, at( 3 ) ); fheroes2::hashCombine( seed, at( 4 ) ); break; case CommandType::MOVE: case CommandType::SPELLCAST: case CommandType::MORALE: case CommandType::CATAPULT: case CommandType::TOWER: case CommandType::RETREAT: case CommandType::SURRENDER: case CommandType::SKIP: fheroes2::hashCombine( seed, _type ); std::for_each( begin(), end(), [&seed]( const int param ) { fheroes2::hashCombine( seed, param ); } ); break; // These commands should never affect the seed generation case CommandType::AUTO_SWITCH: case CommandType::AUTO_FINISH: break; default: assert( 0 ); break; } return seed; } Battle::Command & Battle::Command::operator<<( const int val ) { push_back( val ); return *this; } Battle::Command & Battle::Command::operator>>( int & val ) { if ( !empty() ) { val = back(); pop_back(); } return *this; } fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_command.h000066400000000000000000000146721456075706000231540ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2012 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2BATTLE_COMMAND_H #define H2BATTLE_COMMAND_H #include #include #include #include #include #include #include #include "spell.h" namespace Battle { enum class CommandType : int32_t { MOVE, ATTACK, SPELLCAST, MORALE, CATAPULT, TOWER, RETREAT, SURRENDER, SKIP, AUTO_SWITCH, AUTO_FINISH }; class Command final : public std::vector { public: static constexpr std::integral_constant MOVE{}; static constexpr std::integral_constant ATTACK{}; static constexpr std::integral_constant SPELLCAST{}; static constexpr std::integral_constant MORALE{}; static constexpr std::integral_constant CATAPULT{}; static constexpr std::integral_constant TOWER{}; static constexpr std::integral_constant RETREAT{}; static constexpr std::integral_constant SURRENDER{}; static constexpr std::integral_constant SKIP{}; static constexpr std::integral_constant AUTO_SWITCH{}; static constexpr std::integral_constant AUTO_FINISH{}; template explicit Command( std::integral_constant /* tag */, const Types... params ) : _type( cmd ) { if constexpr ( cmd == CommandType::MOVE ) { // UID, cell index static_assert( sizeof...( params ) == 2 ); } else if constexpr ( cmd == CommandType::ATTACK ) { // Attacker UID, defender UID, cell index to move, cell index to attack, attack direction static_assert( sizeof...( params ) == 5 ); } else if constexpr ( cmd == CommandType::SPELLCAST ) { static_assert( sizeof...( params ) > 0 ); const int spellId = std::get<0>( std::make_tuple( params... ) ); if ( spellId == Spell::MIRRORIMAGE ) { // Spell, UID assert( sizeof...( params ) == 2 ); } else if ( spellId == Spell::TELEPORT ) { // Spell, src index, dst index assert( sizeof...( params ) == 3 ); } else { // Spell, cell index assert( sizeof...( params ) == 2 ); } } else if constexpr ( cmd == CommandType::MORALE ) { // UID, morale static_assert( sizeof...( params ) == 2 ); } else if constexpr ( cmd == CommandType::TOWER ) { // Tower type, UID static_assert( sizeof...( params ) == 2 ); } else if constexpr ( cmd == CommandType::SKIP ) { // UID static_assert( sizeof...( params ) == 1 ); } else if constexpr ( cmd == CommandType::AUTO_SWITCH ) { // Color static_assert( sizeof...( params ) == 1 ); } else { static_assert( sizeof...( params ) == 0 ); } if constexpr ( sizeof...( params ) > 0 ) { reserve( sizeof...( params ) ); // Put the elements of the parameter pack in reverse order using the right-to-left sequencing of the assignment operator int dummy = 0; (void)( ( *this << params, dummy ) = ... ); } } CommandType GetType() const { return _type; } int GetNextValue(); // Updates the specified seed using the contents of this command. Returns the updated seed (or the original seed if this command is not suitable for seed update). uint32_t updateSeed( uint32_t seed ) const; Command & operator<<( const int val ); private: Command & operator>>( int & val ); CommandType _type; }; } namespace std { template <> struct hash { std::size_t operator()( const Battle::CommandType key ) const noexcept { using UnderlyingCommandType = typename std::underlying_type::type; std::hash hasher; return hasher( static_cast( key ) ); } }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_dialogs.cpp000066400000000000000000001557761456075706000235260ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include "agg_image.h" #include "army.h" #include "artifact.h" #include "audio.h" #include "audio_manager.h" #include "battle.h" #include "battle_arena.h" #include "battle_army.h" #include "battle_interface.h" #include "color.h" #include "cursor.h" #include "dialog.h" #include "dialog_audio.h" #include "dialog_hotkeys.h" #include "game.h" #include "game_delays.h" #include "game_hotkeys.h" #include "gamedefs.h" #include "heroes.h" #include "heroes_base.h" #include "icn.h" #include "image.h" #include "kingdom.h" #include "localevent.h" #include "luck.h" #include "math_base.h" #include "monster.h" #include "morale.h" #include "mus.h" #include "players.h" #include "race.h" #include "resource.h" #include "screen.h" #include "settings.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_option_item.h" #include "ui_text.h" #include "ui_tool.h" #include "ui_window.h" namespace { // DialogBattleSummary text related values const int bsTextWidth = 303; const int bsTextXOffset = 25; const int bsTextYOffset = 160; const int bsTextIndent = 30; class LoopedAnimation { public: explicit LoopedAnimation( int icnId = 0, bool loop = false ) : _icnId( icnId ) , _frameId( 0 ) , _counter( 0 ) , _finished( false ) , _loop( loop ) { _frameId = ICN::AnimationFrame( _icnId, 1, _counter ); } uint32_t frameId() { if ( _finished ) return _frameId; ++_counter; uint32_t nextId = ICN::AnimationFrame( _icnId, 1, _counter ); if ( nextId < _frameId ) { if ( _loop ) { _counter = 0; nextId = ICN::AnimationFrame( _icnId, 1, _counter ); std::swap( nextId, _frameId ); return nextId; } else { _finished = true; } } else { std::swap( nextId, _frameId ); return nextId; } return _frameId; } bool isFinished() const { return _finished; } int id() const { return _icnId; } private: int _icnId; uint32_t _frameId; uint32_t _counter; bool _finished; bool _loop; }; class LoopedAnimationSequence { public: void push( int icnId, bool loop ) { _queue.push( LoopedAnimation( icnId, loop ) ); } uint32_t frameId() { if ( isFinished() ) return 0; return _queue.front().frameId(); } bool nextFrame() // returns true only if there is some frames left { if ( !_queue.empty() && _queue.front().isFinished() ) _queue.pop(); return _queue.empty(); } bool isFinished() const { return _queue.empty(); } int id() const { if ( isFinished() ) return 0; return _queue.front().id(); } private: std::queue _queue; }; enum class DialogAction : int { Open, AudioSettings, HotKeys, Close }; void RedrawBattleSettings( const std::vector & areas ) { assert( areas.size() == 9 ); const Settings & conf = Settings::Get(); int speed = conf.BattleSpeed(); std::string str = _( "Speed: %{speed}" ); StringReplace( str, "%{speed}", speed ); uint32_t speedIcnIndex = 0; if ( speed >= 8 ) { speedIcnIndex = 2; } else if ( speed >= 5 ) { speedIcnIndex = 1; } const fheroes2::Sprite & speedIcon = fheroes2::AGG::GetICN( ICN::CSPANEL, speedIcnIndex ); fheroes2::drawOption( areas[0], speedIcon, _( "Speed" ), str, fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); const bool isShowTurnOrderEnabled = conf.BattleShowTurnOrder(); const fheroes2::Sprite & turnOrderIcon = fheroes2::AGG::GetICN( ICN::CSPANEL, isShowTurnOrderEnabled ? 4 : 3 ); fheroes2::drawOption( areas[1], turnOrderIcon, _( "Turn Order" ), isShowTurnOrderEnabled ? _( "On" ) : _( "Off" ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); const bool isBattleAudoSpellCastEnabled = conf.BattleAutoSpellcast(); const fheroes2::Sprite & battleAutoSpellCastIcon = fheroes2::AGG::GetICN( ICN::CSPANEL, isBattleAudoSpellCastEnabled ? 7 : 6 ); fheroes2::drawOption( areas[2], battleAutoSpellCastIcon, _( "Auto Spell Casting" ), isBattleAudoSpellCastEnabled ? _( "On" ) : _( "Off" ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); const bool isShowBattleGridEnabled = conf.BattleShowGrid(); const fheroes2::Sprite & battleGridIcon = fheroes2::AGG::GetICN( ICN::CSPANEL, isShowBattleGridEnabled ? 9 : 8 ); fheroes2::drawOption( areas[3], battleGridIcon, _( "Grid" ), isShowBattleGridEnabled ? _( "On" ) : _( "Off" ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); const bool isShowMoveShadowEnabled = conf.BattleShowMoveShadow(); const fheroes2::Sprite & moveShadowIcon = fheroes2::AGG::GetICN( ICN::CSPANEL, isShowMoveShadowEnabled ? 11 : 10 ); fheroes2::drawOption( areas[4], moveShadowIcon, _( "Shadow Movement" ), isShowMoveShadowEnabled ? _( "On" ) : _( "Off" ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); const bool isShowMouseShadowEnabled = conf.BattleShowMouseShadow(); const fheroes2::Sprite & mouseShadowIcon = fheroes2::AGG::GetICN( ICN::CSPANEL, isShowMouseShadowEnabled ? 13 : 12 ); fheroes2::drawOption( areas[5], mouseShadowIcon, _( "Shadow Cursor" ), isShowMouseShadowEnabled ? _( "On" ) : _( "Off" ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); const fheroes2::Sprite & audioSettingsIcon = fheroes2::AGG::GetICN( ICN::SPANEL, 1 ); fheroes2::drawOption( areas[6], audioSettingsIcon, _( "Audio" ), _( "Settings" ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); const fheroes2::Sprite & hotkeysIcon = fheroes2::AGG::GetICN( ICN::GAME_OPTION_ICON, 0 ); fheroes2::drawOption( areas[7], hotkeysIcon, _( "Hot Keys" ), _( "Configure" ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); const bool isShowBattleDamageInfoEnabled = conf.isBattleShowDamageInfoEnabled(); const fheroes2::Sprite & damageInfoIcon = fheroes2::AGG::GetICN( ICN::CSPANEL, isShowBattleDamageInfoEnabled ? 4 : 3 ); fheroes2::drawOption( areas[8], damageInfoIcon, _( "Damage Info" ), isShowBattleDamageInfoEnabled ? _( "On" ) : _( "Off" ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); } DialogAction openBattleOptionDialog( bool & saveConfiguration ) { fheroes2::Display & display = fheroes2::Display::instance(); LocalEvent & le = LocalEvent::Get(); Settings & conf = Settings::Get(); // Set the cursor image. This dialog is called from the battlefield and does not require a cursor restorer. // Battlefield event processor will set the appropriate cursor after this dialog is closed. Cursor::Get().SetThemes( Cursor::POINTER ); const bool isEvilInterface = conf.isEvilInterfaceEnabled(); const fheroes2::Sprite & dialog = fheroes2::AGG::GetICN( ( isEvilInterface ? ICN::SPANBKGE : ICN::SPANBKG ), 0 ); const fheroes2::Sprite & dialogShadow = fheroes2::AGG::GetICN( ( isEvilInterface ? ICN::SPANBKGE : ICN::SPANBKG ), 1 ); const fheroes2::Point dialogOffset( ( display.width() - dialog.width() ) / 2, ( display.height() - dialog.height() ) / 2 ); const fheroes2::Point shadowOffset( dialogOffset.x - BORDERWIDTH, dialogOffset.y ); fheroes2::ImageRestorer back( display, shadowOffset.x, shadowOffset.y, dialog.width() + BORDERWIDTH, dialog.height() + BORDERWIDTH ); const fheroes2::Rect pos_rt( dialogOffset.x, dialogOffset.y, dialog.width(), dialog.height() ); fheroes2::Fill( display, pos_rt.x, pos_rt.y, pos_rt.width, pos_rt.height, 0 ); fheroes2::Blit( dialogShadow, display, pos_rt.x - BORDERWIDTH, pos_rt.y + BORDERWIDTH ); fheroes2::Blit( dialog, display, pos_rt.x, pos_rt.y ); const fheroes2::Sprite & panelSprite = fheroes2::AGG::GetICN( ICN::CSPANEL, 0 ); const int32_t panelWidth = panelSprite.width(); const int32_t panelHeight = panelSprite.height(); const fheroes2::Point optionOffset( 36 + pos_rt.x, 47 + pos_rt.y ); const fheroes2::Point optionStep( 92, 110 ); std::vector optionAreas; optionAreas.reserve( 9 ); for ( int32_t y = 0; y < 3; ++y ) { for ( int32_t x = 0; x < 3; ++x ) { optionAreas.emplace_back( optionOffset.x + x * optionStep.x, optionOffset.y + y * optionStep.y, panelWidth, panelHeight ); } } const fheroes2::Point buttonOffset( 112 + pos_rt.x, 362 + pos_rt.y ); fheroes2::Button buttonOkay( buttonOffset.x, buttonOffset.y, isEvilInterface ? ICN::BUTTON_SMALL_OKAY_EVIL : ICN::BUTTON_SMALL_OKAY_GOOD, 0, 1 ); buttonOkay.draw(); RedrawBattleSettings( optionAreas ); display.render(); while ( le.HandleEvents() ) { le.MousePressLeft( buttonOkay.area() ) ? buttonOkay.drawOnPress() : buttonOkay.drawOnRelease(); bool redrawScreen = false; if ( le.MouseWheelUp( optionAreas[0] ) ) { conf.SetBattleSpeed( conf.BattleSpeed() + 1 ); Game::UpdateGameSpeed(); redrawScreen = true; } else if ( le.MouseWheelDn( optionAreas[0] ) ) { conf.SetBattleSpeed( conf.BattleSpeed() - 1 ); Game::UpdateGameSpeed(); redrawScreen = true; } else if ( le.MouseClickLeft( optionAreas[0] ) ) { conf.SetBattleSpeed( conf.BattleSpeed() % 10 + 1 ); Game::UpdateGameSpeed(); redrawScreen = true; } else if ( le.MouseClickLeft( optionAreas[1] ) ) { conf.setBattleShowTurnOrder( !conf.BattleShowTurnOrder() ); redrawScreen = true; } else if ( le.MouseClickLeft( optionAreas[2] ) ) { conf.setBattleAutoSpellcast( !conf.BattleAutoSpellcast() ); redrawScreen = true; } else if ( le.MouseClickLeft( optionAreas[3] ) ) { conf.SetBattleGrid( !conf.BattleShowGrid() ); redrawScreen = true; } else if ( le.MouseClickLeft( optionAreas[4] ) ) { conf.SetBattleMovementShaded( !conf.BattleShowMoveShadow() ); redrawScreen = true; } else if ( le.MouseClickLeft( optionAreas[5] ) ) { conf.SetBattleMouseShaded( !conf.BattleShowMouseShadow() ); redrawScreen = true; } else if ( le.MouseClickLeft( optionAreas[6] ) ) { return DialogAction::AudioSettings; } if ( le.MouseClickLeft( optionAreas[7] ) ) { return DialogAction::HotKeys; } if ( le.MouseClickLeft( optionAreas[8] ) ) { conf.setBattleDamageInfo( !conf.isBattleShowDamageInfoEnabled() ); redrawScreen = true; } if ( le.MousePressRight( optionAreas[0] ) ) { fheroes2::showStandardTextMessage( _( "Speed" ), _( "Set the speed of combat actions and animations." ), 0 ); } else if ( le.MousePressRight( optionAreas[1] ) ) { fheroes2::showStandardTextMessage( _( "Turn Order" ), _( "Toggle to display the turn order during the battle." ), 0 ); } else if ( le.MousePressRight( optionAreas[2] ) ) { fheroes2::showStandardTextMessage( _( "Auto Spell Casting" ), _( "Toggle whether or not the computer will cast spells for you when auto combat is on. (Note: This does not affect spell casting for computer players in any way, nor does it affect quick combat.)" ), 0 ); } else if ( le.MousePressRight( optionAreas[3] ) ) { fheroes2::showStandardTextMessage( _( "Grid" ), _( "Toggle the hex grid on or off. The hex grid always underlies movement, even if turned off. This switch only determines if the grid is visible." ), 0 ); } else if ( le.MousePressRight( optionAreas[4] ) ) { fheroes2::showStandardTextMessage( _( "Shadow Movement" ), _( "Toggle on or off shadows showing where your creatures can move and attack." ), 0 ); } else if ( le.MousePressRight( optionAreas[5] ) ) { fheroes2::showStandardTextMessage( _( "Shadow Cursor" ), _( "Toggle on or off a shadow showing the current hex location of the mouse cursor." ), 0 ); } else if ( le.MousePressRight( optionAreas[6] ) ) { fheroes2::showStandardTextMessage( _( "Audio" ), _( "Change the audio settings of the game." ), 0 ); } else if ( le.MousePressRight( optionAreas[7] ) ) { fheroes2::showStandardTextMessage( _( "Hot Keys" ), _( "Check and configure all the hot keys present in the game." ), 0 ); } else if ( le.MousePressRight( optionAreas[8] ) ) { fheroes2::showStandardTextMessage( _( "Damage Info" ), _( "Toggle to display damage information during the battle." ), 0 ); } else if ( le.MousePressRight( buttonOkay.area() ) ) { fheroes2::showStandardTextMessage( _( "Okay" ), _( "Exit this menu." ), 0 ); } if ( Game::HotKeyCloseWindow() || le.MouseClickLeft( buttonOkay.area() ) ) { break; } if ( redrawScreen ) { fheroes2::Blit( dialog, display, pos_rt.x, pos_rt.y ); RedrawBattleSettings( optionAreas ); buttonOkay.draw(); display.render(); saveConfiguration = true; } } return DialogAction::Close; } } namespace Battle { void GetSummaryParams( const uint32_t res1, const uint32_t res2, const HeroBase * hero, const uint32_t exp, const uint32_t surrenderCost, LoopedAnimationSequence & sequence, std::string & title, std::string & surrenderText, std::string & outcomeText ); } void Battle::DialogBattleSettings() { // We should make file writing only once. bool saveConfiguration = false; const Settings & conf = Settings::Get(); DialogAction action = DialogAction::Open; while ( action != DialogAction::Close ) { switch ( action ) { case DialogAction::Open: action = openBattleOptionDialog( saveConfiguration ); break; case DialogAction::AudioSettings: Dialog::openAudioSettingsDialog( false ); action = DialogAction::Open; break; case DialogAction::HotKeys: fheroes2::openHotkeysDialog(); action = DialogAction::Open; break; default: break; } } if ( saveConfiguration ) { conf.Save( Settings::configFileName ); } } void Battle::GetSummaryParams( const uint32_t res1, const uint32_t res2, const HeroBase * hero, const uint32_t exp, const uint32_t surrenderCost, LoopedAnimationSequence & sequence, std::string & title, std::string & surrenderText, std::string & outcomeText ) { if ( res1 & RESULT_WINS ) { sequence.push( ICN::WINCMBT, true ); if ( res2 & RESULT_SURRENDER ) { title.append( _( "The enemy has surrendered!" ) ); surrenderText.append( _( "Their cowardice costs them %{gold} gold." ) ); StringReplace( surrenderText, "%{gold}", surrenderCost ); } else if ( res2 & RESULT_RETREAT ) { title.append( _( "The enemy has fled!" ) ); } else { title.append( _( "A glorious victory!" ) ); } if ( hero && hero->isHeroes() ) { outcomeText.append( _( "For valor in combat, %{name} receives %{exp} experience." ) ); StringReplace( outcomeText, "%{name}", hero->GetName() ); StringReplace( outcomeText, "%{exp}", exp ); } } else if ( res1 & RESULT_RETREAT ) { assert( hero != nullptr ); sequence.push( ICN::CMBTFLE1, false ); sequence.push( ICN::CMBTFLE2, false ); sequence.push( ICN::CMBTFLE3, false ); title.append( _( "The cowardly %{name} flees from battle." ) ); StringReplace( title, "%{name}", hero->GetName() ); } else if ( res1 & RESULT_SURRENDER ) { assert( hero != nullptr ); sequence.push( ICN::CMBTSURR, true ); title.append( _( "%{name} surrenders to the enemy, and departs in shame." ) ); StringReplace( title, "%{name}", hero->GetName() ); } else { sequence.push( ICN::CMBTLOS1, false ); sequence.push( ICN::CMBTLOS2, false ); sequence.push( ICN::CMBTLOS3, true ); if ( hero && hero->isHeroes() ) { title.append( _( "Your forces suffer a bitter defeat, and %{name} abandons your cause." ) ); StringReplace( title, "%{name}", hero->GetName() ); } else { title.append( _( "Your forces suffer a bitter defeat." ) ); } } } // Returns true if player wants to restart the battle bool Battle::Arena::DialogBattleSummary( const Result & res, const std::vector & artifacts, const bool allowToRestart ) const { const bool attackerIsHuman = _army1->GetControl() & CONTROL_HUMAN; const bool defenderIsHuman = _army2->GetControl() & CONTROL_HUMAN; if ( !attackerIsHuman && !defenderIsHuman ) { // AI vs AI battle, this dialog should not be shown assert( 0 ); return false; } fheroes2::Display & display = fheroes2::Display::instance(); // Set the cursor image. After this dialog the Game Area or the Battlefield will be shown, so it does not require a cursor restorer. Cursor::Get().SetThemes( Cursor::POINTER ); fheroes2::StandardWindow background( bsTextWidth + 32, 424, true, display ); const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); const fheroes2::Sprite & originalBorderImage = fheroes2::AGG::GetICN( isEvilInterface ? ICN::WINLOSEE : ICN::WINLOSE, 0 ); const fheroes2::Rect animationBorderRoi{ 43, 32, 231, 133 }; const fheroes2::Rect & roi( background.activeArea() ); const fheroes2::Rect animationRoi( roi.x + ( ( roi.width - animationBorderRoi.width ) / 2 ) + 4, roi.y + 21, animationBorderRoi.width, animationBorderRoi.height ); Copy( originalBorderImage, animationBorderRoi.x, animationBorderRoi.y, display, animationRoi.x - 4, animationRoi.y - 4, animationRoi.width, animationRoi.height ); // Setup summary texts according to results and get the corresponding animation sequence. std::string surrenderText; std::string outcomeText; std::string title; LoopedAnimationSequence sequence; fheroes2::FontType summaryTitleFont = fheroes2::FontType::normalWhite(); if ( ( res.army1 & RESULT_WINS ) && attackerIsHuman ) { GetSummaryParams( res.army1, res.army2, _army1->GetCommander(), res.exp1, _army2->GetSurrenderCost(), sequence, title, surrenderText, outcomeText ); summaryTitleFont = fheroes2::FontType::normalYellow(); AudioManager::PlayMusic( MUS::BATTLEWIN, Music::PlaybackMode::PLAY_ONCE ); } else if ( ( res.army2 & RESULT_WINS ) && defenderIsHuman ) { GetSummaryParams( res.army2, res.army1, _army2->GetCommander(), res.exp2, _army1->GetSurrenderCost(), sequence, title, surrenderText, outcomeText ); summaryTitleFont = fheroes2::FontType::normalYellow(); AudioManager::PlayMusic( MUS::BATTLEWIN, Music::PlaybackMode::PLAY_ONCE ); } else if ( attackerIsHuman ) { GetSummaryParams( res.army1, res.army2, _army1->GetCommander(), res.exp1, 0, sequence, title, surrenderText, outcomeText ); AudioManager::PlayMusic( MUS::BATTLELOSE, Music::PlaybackMode::PLAY_ONCE ); } else if ( defenderIsHuman ) { GetSummaryParams( res.army2, res.army1, _army2->GetCommander(), res.exp2, 0, sequence, title, surrenderText, outcomeText ); AudioManager::PlayMusic( MUS::BATTLELOSE, Music::PlaybackMode::PLAY_ONCE ); } if ( sequence.isFinished() ) { // This shouldn't happen assert( 0 ); sequence.push( ICN::UNKNOWN, false ); } // Setup animation const fheroes2::Sprite & sequenceBase = fheroes2::AGG::GetICN( sequence.id(), 0 ); const fheroes2::Sprite & sequenceStart = fheroes2::AGG::GetICN( sequence.id(), 1 ); Copy( sequenceBase, 0, 0, display, animationRoi.x, animationRoi.y, sequenceBase.width(), sequenceBase.height() ); fheroes2::Blit( sequenceStart, display, animationRoi.x + sequenceStart.x(), animationRoi.y + sequenceStart.y() ); const fheroes2::Rect summaryRoi( roi.x + 11, roi.y + bsTextYOffset, roi.width - 22, roi.height - bsTextYOffset ); fheroes2::ImageRestorer summaryBackground( display, summaryRoi.x, summaryRoi.y, roi.width, summaryRoi.height ); const int32_t casualtiesOffsetY = summaryRoi.y + 96; int32_t summaryBodyOffset = summaryRoi.y; int32_t remainingSummaryBodyHeight = casualtiesOffsetY - summaryBodyOffset; // Draw texts if ( !title.empty() ) { const fheroes2::Text box( title, summaryTitleFont ); box.draw( summaryRoi.x, summaryBodyOffset, summaryRoi.width, display ); summaryBodyOffset += box.height( summaryRoi.width ); remainingSummaryBodyHeight -= box.height( summaryRoi.width ); } const fheroes2::FontType bodyFont = fheroes2::FontType::normalWhite(); if ( !outcomeText.empty() ) { if ( !surrenderText.empty() ) { // Divide the main text area evenly between the two texts bodies by splitting it into 3 equal parts. const fheroes2::Text upperText( surrenderText, bodyFont ); const fheroes2::Text lowerText( outcomeText, bodyFont ); const int32_t inbetweenSpace = ( remainingSummaryBodyHeight - upperText.height( summaryRoi.width ) - lowerText.height( summaryRoi.width ) ) / 3; upperText.draw( summaryRoi.x, summaryBodyOffset + inbetweenSpace, summaryRoi.width, display ); lowerText.draw( summaryRoi.x, summaryBodyOffset + upperText.height( summaryRoi.width ) + inbetweenSpace * 2, summaryRoi.width, display ); } else { const fheroes2::Text upperText( outcomeText, bodyFont ); upperText.draw( summaryRoi.x, summaryBodyOffset + remainingSummaryBodyHeight / 2 - ( upperText.height( summaryRoi.width ) / 2 ), summaryRoi.width, display ); } } else if ( !surrenderText.empty() ) { const fheroes2::Text upperText( surrenderText, bodyFont ); upperText.draw( summaryRoi.x, summaryBodyOffset + remainingSummaryBodyHeight / 2 - ( upperText.height( summaryRoi.width ) / 2 ), summaryRoi.width, display ); } // Battlefield casualties const fheroes2::FontType casualtiesFont = fheroes2::FontType::smallWhite(); fheroes2::Text text( _( "Battlefield Casualties" ), casualtiesFont ); text.draw( summaryRoi.x + ( summaryRoi.width - text.width() ) / 2, casualtiesOffsetY, display ); // Attacker text.set( _( "Attacker" ), casualtiesFont ); text.draw( summaryRoi.x + ( summaryRoi.width - text.width() ) / 2, casualtiesOffsetY + 15, display ); const Troops killed1 = _army1->GetKilledTroops(); const Troops killed2 = _army2->GetKilledTroops(); if ( killed1.isValid() ) { Army::drawSingleDetailedMonsterLine( killed1, summaryRoi.x + 13, casualtiesOffsetY + 36, roi.width - 47 ); } else { text.set( _( "None" ), casualtiesFont ); text.draw( summaryRoi.x + ( summaryRoi.width - text.width() ) / 2, casualtiesOffsetY + 30, display ); } // defender text.set( _( "Defender" ), casualtiesFont ); text.draw( summaryRoi.x + ( summaryRoi.width - text.width() ) / 2, casualtiesOffsetY + 75, display ); if ( killed2.isValid() ) { Army::drawSingleDetailedMonsterLine( killed2, summaryRoi.x + 13, casualtiesOffsetY + 96, roi.width - 47 ); } else { text.set( _( "None" ), casualtiesFont ); text.draw( summaryRoi.x + ( summaryRoi.width - text.width() ) / 2, casualtiesOffsetY + 90, display ); } int32_t buttonHorizontalMargin = 0; const int32_t buttonVerticalMargin = 5; std::unique_ptr buttonRestart; if ( allowToRestart ) { buttonRestart = std::make_unique(); buttonHorizontalMargin = 23; background.renderButton( *buttonRestart, isEvilInterface ? ICN::BUTTON_SMALL_RESTART_EVIL : ICN::BUTTON_SMALL_RESTART_GOOD, 0, 1, { buttonHorizontalMargin, buttonVerticalMargin }, fheroes2::StandardWindow::Padding::BOTTOM_RIGHT ); } const int buttonOkICN = isEvilInterface ? ICN::BUTTON_SMALL_OKAY_EVIL : ICN::BUTTON_SMALL_OKAY_GOOD; fheroes2::Button buttonOk; const fheroes2::StandardWindow::Padding buttonOkPadding = allowToRestart ? fheroes2::StandardWindow::Padding::BOTTOM_LEFT : fheroes2::StandardWindow::Padding::BOTTOM_CENTER; background.renderButton( buttonOk, buttonOkICN, 0, 1, { buttonHorizontalMargin, buttonVerticalMargin }, buttonOkPadding ); display.render( background.totalArea() ); LocalEvent & le = LocalEvent::Get(); int sequenceId = sequence.id(); while ( le.HandleEvents() ) { le.MousePressLeft( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); if ( allowToRestart ) { le.MousePressLeft( buttonRestart->area() ) ? buttonRestart->drawOnPress() : buttonRestart->drawOnRelease(); } if ( Game::HotKeyCloseWindow() || le.MouseClickLeft( buttonOk.area() ) ) { break; } if ( le.MousePressRight( buttonOk.area() ) ) { fheroes2::showStandardTextMessage( _( "Okay" ), _( "Click to leave the battle results." ), Dialog::ZERO ); } else if ( allowToRestart ) { if ( le.MouseClickLeft( buttonRestart->area() ) ) { // Skip artifact transfer and return to restart the battle in manual mode return true; } if ( le.MousePressRight( buttonRestart->area() ) ) { fheroes2::showStandardTextMessage( _( "Restart" ), _( "Click to restart the battle in manual mode." ), Dialog::ZERO ); } } // Animation if ( Game::validateAnimationDelay( Game::BATTLE_DIALOG_DELAY ) && !sequence.nextFrame() ) { if ( sequenceId != sequence.id() ) { sequenceId = sequence.id(); const fheroes2::Sprite & base = fheroes2::AGG::GetICN( sequenceId, 0 ); Copy( base, 0, 0, display, animationRoi.x + base.x(), animationRoi.y + base.y(), base.width(), base.height() ); } const fheroes2::Sprite & sequenceCurrent = fheroes2::AGG::GetICN( sequence.id(), sequence.frameId() ); fheroes2::Blit( sequenceCurrent, display, animationRoi.x + sequenceCurrent.x(), animationRoi.y + sequenceCurrent.y() ); display.render( animationRoi ); } } if ( !artifacts.empty() ) { const HeroBase * winner = ( res.army1 & RESULT_WINS ? _army1->GetCommander() : ( res.army2 & RESULT_WINS ? _army2->GetCommander() : nullptr ) ); const HeroBase * loser = ( res.army1 & RESULT_LOSS ? _army1->GetCommander() : ( res.army2 & RESULT_LOSS ? _army2->GetCommander() : nullptr ) ); // Cannot transfer artifacts if ( winner == nullptr || loser == nullptr ) { return false; } const bool isWinnerHuman = winner && winner->isControlHuman(); // Nothing to do if the AI won and there are no Ultimate Artifacts. if ( !isWinnerHuman && !loser->GetBagArtifacts().ContainUltimateArtifact() ) { return false; } summaryBackground.restore(); background.renderButton( buttonOk, buttonOkICN, 0, 1, { 0, buttonVerticalMargin }, fheroes2::StandardWindow::Padding::BOTTOM_CENTER ); const fheroes2::Sprite & border = fheroes2::AGG::GetICN( ICN::WINLOSEB, 0 ); const fheroes2::Rect artifactArea( summaryRoi.x + ( summaryRoi.width - border.width() ) / 2, casualtiesOffsetY + 38, border.width(), border.height() ); Copy( border, 0, 0, display, artifactArea.x, artifactArea.y, artifactArea.width, artifactArea.height ); fheroes2::ImageRestorer artifactHeader( display, summaryRoi.x, summaryRoi.y, summaryRoi.width, 66 ); fheroes2::ImageRestorer artifactName( display, summaryRoi.x, artifactArea.y + artifactArea.height, summaryRoi.width, 18 ); std::string artMsg; bool needHeaderRedraw = false; const char * previousArtifact = ""; display.render( summaryRoi ); for ( const Artifact & art : artifacts ) { // Only the Ultimate Artifacts are shown for both the winner and loser's dialogs. Skip if it is a regular artifact and the AI won. if ( !isWinnerHuman && !art.isUltimate() ) { continue; } const char * currentArtifact = art.GetName(); if ( previousArtifact == currentArtifact ) { // Sound is never played for Ultimate Artifact messages. if ( isWinnerHuman && !art.isUltimate() ) { Game::PlayPickupSound(); } continue; } if ( !art.isUltimate() ) { // Only draw the regular artifact header once. if ( !needHeaderRedraw ) { artMsg = _( "You have captured an enemy artifact!" ); const fheroes2::Text box( artMsg, fheroes2::FontType::normalYellow() ); box.draw( summaryRoi.x, summaryRoi.y, summaryRoi.width, display ); needHeaderRedraw = true; } Game::PlayPickupSound(); } else { // Ultimate artifacts are always displayed after all the regular artifacts. if ( needHeaderRedraw ) { artifactHeader.restore(); } if ( isWinnerHuman ) { artMsg = _( "As you reach for the %{name}, it mysteriously disappears." ); } else { artMsg = _( "As your enemy reaches for the %{name}, it mysteriously disappears." ); } StringReplace( artMsg, "%{name}", currentArtifact ); const fheroes2::Text box( artMsg, fheroes2::FontType::normalYellow() ); box.draw( summaryRoi.x, summaryRoi.y, summaryRoi.width, display ); needHeaderRedraw = true; } const fheroes2::Sprite & artifact = fheroes2::AGG::GetICN( ICN::ARTIFACT, art.IndexSprite64() ); Copy( artifact, 0, 0, display, artifactArea.x + 8, artifactArea.y + 8, artifact.width(), artifact.height() ); artifactName.restore(); const fheroes2::Text artName( currentArtifact, fheroes2::FontType::smallWhite() ); artName.draw( summaryRoi.x, artifactArea.y + border.height() + 7, summaryRoi.width, display ); previousArtifact = currentArtifact; display.render( summaryRoi ); while ( le.HandleEvents() ) { le.MousePressLeft( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); // Display captured artifact info on right click if ( le.MousePressRight( artifactArea ) ) { fheroes2::ArtifactDialogElement( art ).showPopup( Dialog::ZERO ); } else if ( le.MouseClickLeft( buttonOk.area() ) || Game::HotKeyCloseWindow() ) { break; } else if ( le.MousePressRight( buttonOk.area() ) ) { fheroes2::showStandardTextMessage( _( "Okay" ), _( "Exit this menu." ), Dialog::ZERO ); } // Animation if ( Game::validateAnimationDelay( Game::BATTLE_DIALOG_DELAY ) && !sequence.nextFrame() ) { if ( sequenceId != sequence.id() ) { sequenceId = sequence.id(); const fheroes2::Sprite & base = fheroes2::AGG::GetICN( sequenceId, 0 ); Copy( base, 0, 0, display, animationRoi.x + base.x(), animationRoi.y + base.y(), base.width(), base.height() ); } const fheroes2::Sprite & sequenceCurrent = fheroes2::AGG::GetICN( sequence.id(), sequence.frameId() ); fheroes2::Blit( sequenceCurrent, display, animationRoi.x + sequenceCurrent.x(), animationRoi.y + sequenceCurrent.y() ); display.render( animationRoi ); } } } } return false; } void Battle::Arena::DialogBattleNecromancy( const uint32_t raiseCount ) { // Set the cursor image. This dialog does not require a cursor restorer. Cursor::Get().SetThemes( Cursor::POINTER ); const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); const fheroes2::Sprite & dialog = fheroes2::AGG::GetICN( ( isEvilInterface ? ICN::WINLOSEE : ICN::WINLOSE ), 0 ); const fheroes2::Sprite & dialogShadow = fheroes2::AGG::GetICN( ( isEvilInterface ? ICN::WINLOSEE : ICN::WINLOSE ), 1 ); fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::Point dialogOffset( ( display.width() - dialog.width() ) / 2, ( display.height() - dialog.height() ) / 2 ); const fheroes2::Point shadowOffset( dialogOffset.x - BORDERWIDTH, dialogOffset.y ); fheroes2::ImageRestorer back( display, shadowOffset.x, shadowOffset.y, dialog.width() + BORDERWIDTH, dialog.height() + BORDERWIDTH - 1 ); const fheroes2::Rect renderArea( dialogOffset.x, dialogOffset.y, dialog.width(), dialog.height() ); fheroes2::Blit( dialogShadow, display, renderArea.x - BORDERWIDTH, renderArea.y + BORDERWIDTH - 1 ); fheroes2::Blit( dialog, display, renderArea.x, renderArea.y ); LoopedAnimationSequence sequence; sequence.push( ICN::WINCMBT, true ); if ( sequence.isFinished() ) // Cannot be! sequence.push( ICN::UNKNOWN, false ); const fheroes2::Sprite & sequenceBase = fheroes2::AGG::GetICN( sequence.id(), 0 ); const fheroes2::Sprite & sequenceStart = fheroes2::AGG::GetICN( sequence.id(), 1 ); const fheroes2::Point sequenceRenderAreaOffset( 47, 36 ); fheroes2::Blit( sequenceBase, display, renderArea.x + sequenceRenderAreaOffset.x + sequenceBase.x(), renderArea.y + sequenceRenderAreaOffset.y + sequenceBase.y() ); fheroes2::Blit( sequenceStart, display, renderArea.x + sequenceRenderAreaOffset.x + sequenceStart.x(), renderArea.y + sequenceRenderAreaOffset.y + sequenceStart.y() ); int xOffset = renderArea.x + bsTextXOffset; int yOffset = renderArea.y + bsTextYOffset + 15; const int adjustedTextWidth = bsTextWidth - 33; const fheroes2::Text titleBox( _( "Necromancy!" ), fheroes2::FontType::normalYellow() ); titleBox.draw( xOffset, yOffset + 2, adjustedTextWidth, display ); const Monster mons( Monster::SKELETON ); std::string msg = _( "Practicing the dark arts of necromancy, you are able to raise %{count} of the enemy's dead to return under your service as %{monster}." ); StringReplace( msg, "%{count}", raiseCount ); StringReplace( msg, "%{monster}", mons.GetPluralName( raiseCount ) ); const fheroes2::Text messageBox( msg, fheroes2::FontType::normalWhite() ); yOffset += bsTextIndent; messageBox.draw( xOffset, yOffset + 2, adjustedTextWidth, display ); const fheroes2::Sprite & monsterSprite = fheroes2::AGG::GetICN( ICN::MONS32, mons.GetSpriteIndex() ); yOffset += messageBox.height( adjustedTextWidth ) + monsterSprite.height(); fheroes2::Blit( monsterSprite, display, ( display.width() - monsterSprite.width() ) / 2, yOffset ); fheroes2::Text raiseCountText( std::to_string( raiseCount ), fheroes2::FontType::smallWhite() ); raiseCountText.fitToOneRow( adjustedTextWidth ); yOffset += 30; raiseCountText.draw( ( display.width() - raiseCountText.width() ) / 2, yOffset + 2, display ); Game::PlayPickupSound(); const int buttonOffset = 121; const int buttonICN = isEvilInterface ? ICN::BUTTON_SMALLER_OKAY_EVIL : ICN::BUTTON_SMALLER_OKAY_GOOD; fheroes2::Button buttonOk( renderArea.x + buttonOffset, renderArea.y + 410, buttonICN, 0, 1 ); buttonOk.draw(); display.render(); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { le.MousePressLeft( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); // exit if ( Game::HotKeyCloseWindow() || le.MouseClickLeft( buttonOk.area() ) ) break; // animation if ( Game::validateAnimationDelay( Game::BATTLE_DIALOG_DELAY ) && !sequence.nextFrame() ) { const fheroes2::Sprite & base = fheroes2::AGG::GetICN( sequence.id(), 0 ); const fheroes2::Sprite & sequenceCurrent = fheroes2::AGG::GetICN( sequence.id(), sequence.frameId() ); fheroes2::Blit( base, display, renderArea.x + sequenceRenderAreaOffset.x + sequenceBase.x(), renderArea.y + sequenceRenderAreaOffset.y + sequenceBase.y() ); fheroes2::Blit( sequenceCurrent, display, renderArea.x + sequenceRenderAreaOffset.x + sequenceCurrent.x(), renderArea.y + sequenceRenderAreaOffset.y + sequenceCurrent.y() ); display.render(); } } } int Battle::Arena::DialogBattleHero( const HeroBase & hero, const bool buttons, Status & status ) const { const Settings & conf = Settings::Get(); Cursor & cursor = Cursor::Get(); cursor.SetThemes( Cursor::POINTER ); const int currentColor = GetCurrentColor(); const bool readonly = currentColor != hero.GetColor() || !buttons; const fheroes2::Sprite & dialog = fheroes2::AGG::GetICN( ( conf.isEvilInterfaceEnabled() ? ICN::VGENBKGE : ICN::VGENBKG ), 0 ); const fheroes2::Point dialogShadow( 15, 15 ); fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::Rect pos_rt( ( display.width() - dialog.width() - dialogShadow.x ) / 2, ( display.height() - dialog.height() - dialogShadow.y ) / 2, dialog.width(), dialog.height() ); fheroes2::ImageRestorer back( display, pos_rt.x, pos_rt.y, pos_rt.width, pos_rt.height ); fheroes2::Blit( dialog, display, pos_rt.x, pos_rt.y ); // first 15 pixels in the dialog is left shadow, skip pos_rt.x += dialogShadow.x; pos_rt.width -= dialogShadow.x; const fheroes2::Rect portraitArea( pos_rt.x + 7, pos_rt.y + 35, 113, 108 ); const Heroes * actionHero = ( currentColor == hero.GetColor() ) ? dynamic_cast( &hero ) : nullptr; hero.PortraitRedraw( pos_rt.x + 12, pos_rt.y + 42, PORT_BIG, display ); int col = ( Color::NONE == hero.GetColor() ? 1 : Color::GetIndex( hero.GetColor() ) + 1 ); fheroes2::Blit( fheroes2::AGG::GetICN( ICN::VIEWGEN, col ), display, pos_rt.x + 133, pos_rt.y + 36 ); std::string str = hero.isCaptain() ? _( "Captain of %{name}" ) : _( "%{name} the %{race}" ); StringReplace( str, "%{name}", hero.GetName() ); StringReplace( str, "%{race}", Race::String( hero.GetRace() ) ); fheroes2::Text text( str, fheroes2::FontType::smallWhite() ); fheroes2::Point tp{ pos_rt.x + ( pos_rt.width - text.width() ) / 2, pos_rt.y + 11 }; text.draw( tp.x, tp.y + 2, display ); const fheroes2::Point statsTextOffset{ pos_rt.x + 148 - dialogShadow.x, pos_rt.y + 40 }; const int32_t maxStatsTextWidth{ 111 }; const int32_t statsTextRowHeight{ 11 }; str = _( "Attack" ) + std::string( ": " ) + std::to_string( hero.GetAttack() ); text.set( str, fheroes2::FontType::smallWhite() ); tp.x = statsTextOffset.x + ( maxStatsTextWidth - text.width() ) / 2; tp.y = statsTextOffset.y; text.draw( tp.x, tp.y + 2, display ); str = _( "Defense" ) + std::string( ": " ) + std::to_string( hero.GetDefense() ); text.set( str, fheroes2::FontType::smallWhite() ); tp.x = statsTextOffset.x + ( maxStatsTextWidth - text.width() ) / 2; tp.y += statsTextRowHeight; text.draw( tp.x, tp.y + 2, display ); str = _( "Spell Power" ) + std::string( ": " ) + std::to_string( hero.GetPower() ); text.set( str, fheroes2::FontType::smallWhite() ); tp.x = statsTextOffset.x + ( maxStatsTextWidth - text.width() ) / 2; tp.y += statsTextRowHeight; text.draw( tp.x, tp.y + 2, display ); str = _( "Knowledge" ) + std::string( ": " ) + std::to_string( hero.GetKnowledge() ); text.set( str, fheroes2::FontType::smallWhite() ); tp.x = statsTextOffset.x + ( maxStatsTextWidth - text.width() ) / 2; tp.y += statsTextRowHeight; text.draw( tp.x, tp.y + 2, display ); str = _( "Morale" ) + std::string( ": " ) + Morale::String( hero.GetMorale() ); text.set( str, fheroes2::FontType::smallWhite() ); tp.x = statsTextOffset.x + ( maxStatsTextWidth - text.width() ) / 2; tp.y += statsTextRowHeight; text.draw( tp.x, tp.y + 2, display ); str = _( "Luck" ) + std::string( ": " ) + Luck::String( hero.GetLuck() ); text.set( str, fheroes2::FontType::smallWhite() ); tp.x = statsTextOffset.x + ( maxStatsTextWidth - text.width() ) / 2; tp.y += statsTextRowHeight; text.draw( tp.x, tp.y + 2, display ); str = _( "Spell Points" ) + std::string( ": " ) + std::to_string( hero.GetSpellPoints() ) + "/" + std::to_string( hero.GetMaxSpellPoints() ); text.set( str, fheroes2::FontType::smallWhite() ); tp.x = statsTextOffset.x + ( maxStatsTextWidth - text.width() ) / 2; tp.y += statsTextRowHeight * 2; text.draw( tp.x, tp.y + 2, display ); fheroes2::Button btnCast( pos_rt.x + 15, pos_rt.y + 148, ICN::VIEWGEN, 9, 10 ); fheroes2::Button btnRetreat( pos_rt.x + 74, pos_rt.y + 148, ICN::VIEWGEN, 11, 12 ); fheroes2::Button btnSurrender( pos_rt.x + 133, pos_rt.y + 148, ICN::VIEWGEN, 13, 14 ); fheroes2::Button btnClose( pos_rt.x + 192, pos_rt.y + 148, ICN::VIEWGEN, 15, 16 ); if ( readonly || !hero.HaveSpellBook() || hero.Modes( Heroes::SPELLCASTED ) ) btnCast.disable(); if ( readonly || !CanRetreatOpponent( hero.GetColor() ) ) btnRetreat.disable(); if ( readonly || !CanSurrenderOpponent( hero.GetColor() ) ) btnSurrender.disable(); btnCast.draw(); btnRetreat.draw(); btnSurrender.draw(); btnClose.draw(); int result = 0; display.render( pos_rt ); std::string statusMessage = _( "Hero's Options" ); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() && !result ) { btnCast.isEnabled() && le.MousePressLeft( btnCast.area() ) ? btnCast.drawOnPress() : btnCast.drawOnRelease(); btnRetreat.isEnabled() && le.MousePressLeft( btnRetreat.area() ) ? btnRetreat.drawOnPress() : btnRetreat.drawOnRelease(); btnSurrender.isEnabled() && le.MousePressLeft( btnSurrender.area() ) ? btnSurrender.drawOnPress() : btnSurrender.drawOnRelease(); le.MousePressLeft( btnClose.area() ) ? btnClose.drawOnPress() : btnClose.drawOnRelease(); if ( buttons ) { // The Cast Spell is available for a hero and a captain. if ( le.MouseCursor( btnCast.area() ) && currentColor == hero.GetColor() ) { statusMessage = _( "Cast Spell" ); } // The retreat is available during a player's turn only. A captain cannot retreat. else if ( le.MouseCursor( btnRetreat.area() ) && currentColor == hero.GetColor() && !hero.isCaptain() ) { statusMessage = _( "Retreat" ); } // The surrender is available during a player's turn only. A captain cannot surrender. else if ( le.MouseCursor( btnSurrender.area() ) && currentColor == hero.GetColor() && !hero.isCaptain() ) { statusMessage = _( "Surrender" ); } else if ( le.MouseCursor( btnClose.area() ) ) { statusMessage = _( "Cancel" ); } // The Hero Screen is available for a Hero only (not Captain) and when UI is not read-only. else if ( le.MouseCursor( portraitArea ) && actionHero != nullptr && actionHero->isHeroes() && !readonly ) { statusMessage = _( "Hero Screen" ); } else if ( hero.isCaptain() ) { statusMessage = _( "Captain's Options" ); } else { statusMessage = _( "Hero's Options" ); } } else { if ( !le.MousePressRight() ) { break; } continue; } if ( Game::HotKeyCloseWindow() || le.MouseClickLeft( btnClose.area() ) ) { break; } if ( Game::HotKeyPressEvent( Game::HotKeyEvent::BATTLE_CAST_SPELL ) || ( btnCast.isEnabled() && le.MouseClickLeft( btnCast.area() ) ) ) { result = 1; } if ( Game::HotKeyPressEvent( Game::HotKeyEvent::BATTLE_RETREAT ) || ( btnRetreat.isEnabled() && le.MouseClickLeft( btnRetreat.area() ) ) ) { result = 2; } if ( Game::HotKeyPressEvent( Game::HotKeyEvent::BATTLE_SURRENDER ) || ( btnSurrender.isEnabled() && le.MouseClickLeft( btnSurrender.area() ) ) ) { result = 3; } if ( le.MouseClickLeft( portraitArea ) && actionHero != nullptr ) { LocalEvent::GetClean(); // IMPORTANT!!! This is extremely dangerous but we have no choice with current code. Make sure that this trick doesn't allow user to modify the hero. const_cast( actionHero )->OpenDialog( true, true, true, true, false ); // Fade-in to restore the screen after closing the hero dialog. fheroes2::fadeInDisplay( _interface->GetInterfaceRoi(), !display.isDefaultSize() ); } if ( le.MousePressRight( btnCast.area() ) && currentColor == hero.GetColor() ) { fheroes2::showStandardTextMessage( _( "Cast Spell" ), _( "Cast a magical spell. You may only cast one spell per combat round. The round is reset when every creature has had a turn." ), Dialog::ZERO ); } else if ( le.MousePressRight( btnRetreat.area() ) && currentColor == hero.GetColor() && !hero.isCaptain() ) { fheroes2::showStandardTextMessage( _( "Retreat" ), _( "Retreat your hero, abandoning your creatures. Your hero will be available for you to recruit again, however, the hero will have only a novice hero's forces." ), Dialog::ZERO ); } else if ( le.MousePressRight( btnSurrender.area() ) && currentColor == hero.GetColor() && !hero.isCaptain() ) { fheroes2::showStandardTextMessage( _( "Surrender" ), _( "Surrendering costs gold. However if you pay the ransom, the hero and all of his or her surviving creatures will be available to recruit again. The cost of surrender is half of the total cost of the non-temporary troops remaining in the army." ), Dialog::ZERO ); } else if ( le.MousePressRight( portraitArea ) && actionHero != nullptr ) { fheroes2::showStandardTextMessage( _( "Hero Screen" ), _( "Open Hero Screen to view full information about the hero." ), Dialog::ZERO ); } else if ( le.MousePressRight( btnClose.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Return to the battle." ), Dialog::ZERO ); } if ( statusMessage != status.GetMessage() ) { status.SetMessage( statusMessage ); status.Redraw( display ); display.render( status ); } } return result; } bool Battle::DialogBattleSurrender( const HeroBase & hero, uint32_t cost, Kingdom & kingdom ) { if ( kingdom.GetColor() == hero.GetColor() ) // this is weird. You're surrending to yourself! return false; fheroes2::Display & display = fheroes2::Display::instance(); LocalEvent & le = LocalEvent::Get(); const Settings & conf = Settings::Get(); // Set the cursor image. After this dialog the Game Area or the Battlefield will be shown, so it does not require a cursor restorer. Cursor::Get().SetThemes( Cursor::POINTER ); const bool isEvilInterface = conf.isEvilInterfaceEnabled(); const fheroes2::Sprite & dialog = fheroes2::AGG::GetICN( isEvilInterface ? ICN::SURDRBKE : ICN::SURDRBKG, 0 ); fheroes2::Rect pos_rt( ( display.width() - dialog.width() + 16 ) / 2, ( display.height() - dialog.height() + 16 ) / 2, dialog.width(), dialog.height() ); fheroes2::Blit( dialog, display, pos_rt.x, pos_rt.y ); const int icnAccept = isEvilInterface ? ICN::BUTTON_SMALL_ACCEPT_EVIL : ICN::BUTTON_SMALL_ACCEPT_GOOD; const int icnDecline = isEvilInterface ? ICN::BUTTON_SMALL_DECLINE_EVIL : ICN::BUTTON_SMALL_DECLINE_GOOD; const int icnMarket = isEvilInterface ? ICN::EVIL_MARKET_BUTTON : ICN::GOOD_MARKET_BUTTON; fheroes2::ButtonSprite btnAccept = fheroes2::makeButtonWithShadow( pos_rt.x + 91, pos_rt.y + 152, fheroes2::AGG::GetICN( icnAccept, 0 ), fheroes2::AGG::GetICN( icnAccept, 1 ), display ); fheroes2::ButtonSprite btnDecline = fheroes2::makeButtonWithShadow( pos_rt.x + 295, pos_rt.y + 152, fheroes2::AGG::GetICN( icnDecline, 0 ), fheroes2::AGG::GetICN( icnDecline, 1 ), display ); fheroes2::ButtonSprite btnMarket = fheroes2::makeButtonWithShadow( pos_rt.x + ( pos_rt.width - 16 ) / 2, pos_rt.y + 145, fheroes2::AGG::GetICN( icnMarket, 0 ), fheroes2::AGG::GetICN( icnMarket, 1 ), display ); if ( !kingdom.AllowPayment( Funds( Resource::GOLD, cost ) ) ) { btnAccept.disable(); } if ( kingdom.GetCountMarketplace() ) { if ( kingdom.AllowPayment( Funds( Resource::GOLD, cost ) ) ) { btnMarket.disable(); } else { btnMarket.draw(); } } else { btnMarket.disable(); } btnAccept.draw(); btnDecline.draw(); const auto drawGoldMsg = [cost, &kingdom, &btnAccept]() { std::string str = _( "Not enough gold (%{gold})" ); StringReplace( str, "%{gold}", cost - kingdom.GetFunds().gold ); const fheroes2::Text text( str, fheroes2::FontType::smallWhite() ); const fheroes2::Rect rect = btnAccept.area(); // Since button area includes 3D effect on the left side we need to shift the text by X axis to center it in relation to the button. text.draw( rect.x + ( rect.width - text.width() ) / 2 + 2, rect.y - 13, fheroes2::Display::instance() ); }; const int icn = isEvilInterface ? ICN::SURRENDE : ICN::SURRENDR; const fheroes2::Sprite & window = fheroes2::AGG::GetICN( icn, 4 ); fheroes2::Blit( window, display, pos_rt.x + 55, pos_rt.y + 32 ); hero.PortraitRedraw( pos_rt.x + 60, pos_rt.y + 38, PORT_BIG, display ); std::string str = hero.isCaptain() ? _( "Captain of %{name} states:" ) : _( "%{name} states:" ); StringReplace( str, "%{name}", hero.GetName() ); const fheroes2::Text text( str, fheroes2::FontType::normalWhite() ); text.draw( pos_rt.x + 312 - text.width() / 2, pos_rt.y + 32, display ); str = _( "\"I will accept your surrender and grant you and your troops safe passage for the price of %{price} gold.\"" ); StringReplace( str, "%{price}", cost ); fheroes2::Text box( str, fheroes2::FontType::normalWhite() ); box.draw( pos_rt.x + 175, pos_rt.y + 52, 275, display ); fheroes2::ImageRestorer back( display, pos_rt.x, pos_rt.y, pos_rt.width, pos_rt.height ); if ( !kingdom.AllowPayment( Funds( Resource::GOLD, cost ) ) ) { drawGoldMsg(); } display.render(); bool result = false; while ( le.HandleEvents() && !result ) { if ( btnAccept.isEnabled() ) le.MousePressLeft( btnAccept.area() ) ? btnAccept.drawOnPress() : btnAccept.drawOnRelease(); le.MousePressLeft( btnDecline.area() ) ? btnDecline.drawOnPress() : btnDecline.drawOnRelease(); if ( btnMarket.isEnabled() ) le.MousePressLeft( btnMarket.area() ) ? btnMarket.drawOnPress() : btnMarket.drawOnRelease(); if ( btnAccept.isEnabled() && ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) || le.MouseClickLeft( btnAccept.area() ) ) ) { result = true; } if ( btnMarket.isEnabled() && le.MouseClickLeft( btnMarket.area() ) ) { Dialog::Marketplace( kingdom, false ); back.restore(); if ( kingdom.AllowPayment( Funds( Resource::GOLD, cost ) ) ) { btnAccept.enable(); } else { btnAccept.disable(); drawGoldMsg(); } btnAccept.draw(); display.render(); } // exit if ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( btnDecline.area() ) ) break; } return result; } fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_grave.cpp000066400000000000000000000076111456075706000231700ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2012 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "battle_grave.h" #include #include #include #include "battle_board.h" #include "battle_troop.h" Battle::Indexes Battle::Graveyard::GetOccupiedCells() const { Indexes result; result.reserve( size() ); std::for_each( begin(), end(), [&result]( const auto & item ) { const auto & [idx, troopUIDs] = item; if ( !troopUIDs.empty() ) { result.push_back( idx ); } } ); return result; } void Battle::Graveyard::AddTroop( const Unit & unit ) { assert( Board::isValidIndex( unit.GetHeadIndex() ) && ( unit.isWide() ? Board::isValidIndex( unit.GetTailIndex() ) : !Board::isValidIndex( unit.GetTailIndex() ) ) ); Graveyard & graveyard = *this; graveyard[unit.GetHeadIndex()].push_back( unit.GetUID() ); if ( unit.isWide() ) { graveyard[unit.GetTailIndex()].push_back( unit.GetUID() ); } } void Battle::Graveyard::RemoveTroop( const Unit & unit ) { assert( Board::isValidIndex( unit.GetHeadIndex() ) && ( unit.isWide() ? Board::isValidIndex( unit.GetTailIndex() ) : !Board::isValidIndex( unit.GetTailIndex() ) ) ); const auto removeUIDFromIndex = [this]( const int32_t idx, const uint32_t uid ) { const auto idxIter = find( idx ); if ( idxIter == end() ) { return; } auto & [dummy, troopUIDs] = *idxIter; const auto troopUIDIter = std::find( troopUIDs.begin(), troopUIDs.end(), uid ); if ( troopUIDIter != troopUIDs.end() ) { troopUIDs.erase( troopUIDIter ); } }; removeUIDFromIndex( unit.GetHeadIndex(), unit.GetUID() ); if ( unit.isWide() ) { removeUIDFromIndex( unit.GetTailIndex(), unit.GetUID() ); } } uint32_t Battle::Graveyard::GetLastTroopUID( const int32_t index ) const { const auto iter = find( index ); if ( iter == end() ) { return 0; } const auto & [dummy, troopUIDs] = *iter; if ( troopUIDs.empty() ) { return 0; } assert( troopUIDs.back() > 0 ); return troopUIDs.back(); } Battle::TroopUIDs Battle::Graveyard::GetTroopUIDs( const int32_t index ) const { const auto iter = find( index ); if ( iter == end() ) { return {}; } return iter->second; } fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_grave.h000066400000000000000000000046401456075706000226340ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2012 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2BATTLE_GRAVEYARD_H #define H2BATTLE_GRAVEYARD_H #include #include #include #include #include "battle_board.h" namespace Battle { class Unit; using TroopUIDs = std::vector; class Graveyard : public std::map { public: Graveyard() = default; Graveyard( const Graveyard & ) = delete; ~Graveyard() = default; Graveyard & operator=( const Graveyard & ) = delete; Indexes GetOccupiedCells() const; void AddTroop( const Unit & unit ); void RemoveTroop( const Unit & unit ); uint32_t GetLastTroopUID( const int32_t index ) const; TroopUIDs GetTroopUIDs( const int32_t index ) const; }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_interface.cpp000066400000000000000000007550171456075706000240360ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "battle_interface.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "agg_image.h" #include "audio.h" #include "audio_manager.h" #include "battle.h" #include "battle_arena.h" #include "battle_army.h" #include "battle_bridge.h" #include "battle_catapult.h" #include "battle_cell.h" #include "battle_command.h" #include "battle_tower.h" #include "battle_troop.h" #include "bin_info.h" #include "castle.h" #include "color.h" #include "game_delays.h" #include "game_hotkeys.h" #include "gamedefs.h" #include "ground.h" #include "heroes_base.h" #include "icn.h" #include "interface_list.h" #include "localevent.h" #include "logging.h" #include "m82.h" #include "maps.h" #include "maps_tiles.h" #include "monster.h" #include "monster_anim.h" #include "monster_info.h" #include "mp2.h" #include "mus.h" #include "pal.h" #include "players.h" #include "race.h" #include "rand.h" #include "screen.h" #include "settings.h" #include "spell_book.h" #include "timing.h" #include "tools.h" #include "translations.h" #include "ui_dialog.h" #include "ui_scrollbar.h" #include "ui_tool.h" #include "ui_window.h" #include "world.h" class Kingdom; namespace { const int32_t cellYOffset = -9; const int32_t turnOrderMonsterIconSize = 43; // in both directions. // The parameters of castle buildings destruction by a catapult: // Smoke cloud frame number, after which the building should be drawn as destroyed. const int32_t castleBuildingDestroyFrame = 5; // Bridge demolition second smoke cloud offset from the first one after the catapult attack. const fheroes2::Point bridgeDestroySmokeOffset( -45, 65 ); // Smoke cloud frame number, after which the bridge should be drawn as destroyed. const int32_t bridgeDestroyFrame = 6; // The number of frames the second smoke cloud is delayed by. const int32_t bridgeDestroySmokeDelay = 2; const int32_t offsetForTextBar{ 32 }; const int32_t maxElementsInBattleLog{ 6 }; // This value must be equal to the height of Normal font. const int32_t battleLogElementHeight{ 17 }; const int32_t battleLogLastElementOffset{ 4 }; const int32_t battleLogElementWidth{ fheroes2::Display::DEFAULT_WIDTH - 32 - 16 }; struct LightningPoint { explicit LightningPoint( const fheroes2::Point & p = fheroes2::Point(), const int32_t thick = 1 ) : point( p ) , thickness( thick ) {} fheroes2::Point point; int32_t thickness; }; double getDistance( const fheroes2::Point & p1, const fheroes2::Point & p2 ) { const double diffX = p1.x - p2.x; const double diffY = p1.y - p2.y; return std::sqrt( diffX * diffX + diffY * diffY ); } fheroes2::Point rotate( const fheroes2::Point & point, double angle ) { const double sinValue = sin( angle ); const double cosValue = cos( angle ); return { static_cast( point.x * cosValue - point.y * sinValue ), static_cast( point.x * sinValue + point.y * cosValue ) }; } double getAngle( const fheroes2::Point & start, const fheroes2::Point & end ) { return std::atan2( end.y - start.y, end.x - start.x ); } std::vector> GenerateLightning( const fheroes2::Point & src, const fheroes2::Point & dst ) { const int32_t distance = static_cast( getDistance( src, dst ) ); const double angle = getAngle( src, dst ); int32_t iterationCount = ( distance + 50 ) / 100; if ( iterationCount < 3 ) { iterationCount = 3; } if ( iterationCount > 5 ) { iterationCount = 5; } std::vector> lines; lines.emplace_back( LightningPoint( { 0, 0 }, 5 ), LightningPoint( { distance, 0 }, 3 ) ); int maxOffset = distance; for ( int step = 0; step < iterationCount; ++step ) { std::vector> oldLines; std::swap( lines, oldLines ); for ( const auto & [firstPoint, secondPoint] : oldLines ) { fheroes2::Point middle( firstPoint.point + secondPoint.point ); middle.x /= 2; middle.y /= 2; const bool isPositive = ( Rand::Get( 1, 2 ) == 1 ); int32_t offsetY = static_cast( Rand::Get( 1, 10 ) ) * maxOffset / 100; if ( offsetY < 1 ) { offsetY = 1; } middle.y += isPositive ? offsetY : -offsetY; const int32_t middleThickness = ( firstPoint.thickness + secondPoint.thickness ) / 2; const LightningPoint middlePoint( middle, middleThickness ); lines.emplace_back( firstPoint, middlePoint ); lines.emplace_back( middlePoint, secondPoint ); if ( Rand::Get( 1, 4 ) == 1 ) { // 25% offsetY = static_cast( Rand::Get( 1, 10 ) ) * maxOffset / 100; const int32_t x = static_cast( ( middle.x - firstPoint.point.x ) * 0.7 ) + middle.x; const int32_t y = static_cast( ( middle.y - firstPoint.point.y ) * 0.7 ) + middle.y + ( isPositive ? offsetY : -offsetY ); lines.emplace_back( middlePoint, LightningPoint( { x, y }, 1 ) ); } } maxOffset /= 2; } for ( auto & [firstPoint, secondPoint] : lines ) { firstPoint.point = rotate( firstPoint.point, angle ) + src; secondPoint.point = rotate( secondPoint.point, angle ) + src; } return lines; } void RedrawLightning( const std::vector> & lightning, const uint8_t color, fheroes2::Image & surface, const fheroes2::Rect & roi = fheroes2::Rect() ) { for ( const auto & [firstPoint, secondPoint] : lightning ) { const fheroes2::Point & first = firstPoint.point; const fheroes2::Point & second = secondPoint.point; const bool isHorizontal = std::abs( first.x - second.x ) >= std::abs( first.y - second.y ); const int32_t xOffset = isHorizontal ? 0 : 1; const int32_t yOffset = isHorizontal ? 1 : 0; fheroes2::DrawLine( surface, first, second, color, roi ); for ( int32_t thickness = 1; thickness < secondPoint.thickness; ++thickness ) { const bool isUpper = ( ( thickness % 2 ) == 1 ); const int32_t offset = isUpper ? ( thickness + 1 ) / 2 : -( thickness + 1 ) / 2; const int32_t x = xOffset * offset; const int32_t y = yOffset * offset; fheroes2::DrawLine( surface, { first.x + x, first.y + y }, { second.x + x, second.y + y }, color, roi ); } for ( int32_t thickness = secondPoint.thickness; thickness < firstPoint.thickness; ++thickness ) { const bool isUpper = ( ( thickness % 2 ) == 1 ); const int32_t offset = isUpper ? ( thickness + 1 ) / 2 : -( thickness + 1 ) / 2; fheroes2::DrawLine( surface, { first.x + xOffset * offset, first.y + yOffset * offset }, second, color, roi ); } } } void GetHalfArc( std::vector & arc, int32_t width, const int32_t height, const int32_t pow1, const int32_t pow2, const double pow2Ratio ) { // For positive width the arc will start from y = 0, for negative it will end at y = 0. const int32_t x0 = ( width < 0 ) ? width : 0; // For positive height the arc top will be y = 0, for negative - arc will be flipped for 'y' coordinate. // TODO: consider height offset from previous arc part. const int32_t y0 = ( height < 0 ) ? height : 0; // Coefficients for multipliers of 'pow1' and 'pow2' degrees. const double k1 = ( height * ( 1 - pow2Ratio ) ) / std::pow( width, pow1 ); const double k2 = ( height * pow2Ratio ) / std::pow( width, pow2 ); width = std::abs( width ); // Calculate 'y' coordinates for the arc: y = k1*(x-x0)^pow1+k2*(x-x0)^pow2 and push it to the 'arc' vector. for ( int32_t x = 0; x < width; ++x ) { arc.push_back( static_cast( k1 * std::pow( ( x + x0 ), pow1 ) + k2 * std::pow( ( x + x0 ), pow2 ) - y0 ) ); } } fheroes2::Image DrawRainbow( const std::vector & rainbowArc, const int32_t rainbowThickness, const bool isVertical, const bool flipHorizontally ) { // Rainbow image size should include the arc size plus the thickness of the rainbow. const int32_t rainbowWidth = static_cast( rainbowArc.size() ); const int32_t rainbowHeight = *std::max_element( rainbowArc.begin(), rainbowArc.end() ) + rainbowThickness; // If the rainbow is vertical - swap width and height. const int32_t rainbowImgWidth = isVertical ? rainbowHeight : rainbowWidth; const int32_t rainbowImgHeight = isVertical ? rainbowWidth : rainbowHeight; fheroes2::Image rainbow( rainbowImgWidth, rainbowImgHeight ); rainbow.reset(); std::vector::const_iterator pnt = rainbowArc.begin(); // Get the original good luck sprite, since it has a rainbow image which will be used to get line. const fheroes2::Sprite & luckSprite = fheroes2::AGG::GetICN( ICN::EXPMRL, 0 ); // Get a single rainbow line from the center of the luckSprite. fheroes2::Image croppedRainbow( 1, rainbowThickness ); fheroes2::Copy( luckSprite, luckSprite.width() / 2, 0, croppedRainbow, 0, 0, 1, rainbowThickness ); fheroes2::Image rainbowLine; if ( isVertical ) { rainbowLine = fheroes2::Image( croppedRainbow.height(), croppedRainbow.width() ); // For a vertical rainbow orientation the line needs to be transposed. fheroes2::Transpose( croppedRainbow, rainbowLine ); if ( !flipHorizontally ) { rainbowLine = fheroes2::Flip( rainbowLine, true, false ); } } else { rainbowLine = std::move( croppedRainbow ); } // Draw a rainbow image for each 'x' coordinate and corresponding '*pnt' value. for ( int32_t x = 0; pnt != rainbowArc.end(); ++x, ++pnt ) { // Set the 'x' and 'y' coordinates of the current rainbow pixel in the resulting rainbow image according to the rainbow direction. const int32_t imgX = isVertical ? ( flipHorizontally ? *pnt : rainbowImgWidth - *pnt - rainbowThickness ) : ( flipHorizontally ? rainbowImgWidth - x : x ); const int32_t imgY = isVertical ? x : *pnt; // Insert a rainbow line at the current arc position. fheroes2::Copy( rainbowLine, 0, 0, rainbow, imgX, imgY, rainbowLine.width(), rainbowLine.height() ); } return rainbow; } fheroes2::Point CalculateSpellPosition( const Battle::Unit & target, const int spellICN, const fheroes2::Sprite & spellSprite ) { const fheroes2::Rect & pos = target.GetRectPosition(); // Get the sprite for the first frame, so its center won't shift if the creature is animating (instead of target.GetFrame()). const fheroes2::Sprite & unitSprite = fheroes2::AGG::GetICN( target.GetMonsterSprite(), target.animation.firstFrame() ); // Bottom-left corner (default) position with spell offset applied fheroes2::Point result( pos.x + spellSprite.x(), pos.y + pos.height + cellYOffset + spellSprite.y() ); switch ( spellICN ) { case ICN::SHIELD: // in front of the unit result.x += target.isReflect() ? -pos.width / ( target.isWide() ? 2 : 1 ) : pos.width; result.y += unitSprite.y() / 2; break; case ICN::BLIND: { // unit's eyes const fheroes2::Point & offset = target.animation.getBlindOffset(); // calculate OG Heroes2 unit position to apply offset to const int32_t rearCenterX = ( target.isWide() && target.isReflect() ) ? pos.width * 3 / 4 : CELLW / 2; // Overwrite result with custom blind value result.x += rearCenterX + ( target.isReflect() ? -offset.x : offset.x ); result.y += offset.y; break; } case ICN::STONSKIN: case ICN::STELSKIN: // bottom center point result.x += pos.width / 2; break; case ICN::REDDEATH: // Shift spell sprite position for a wide creature to its head. result.x += pos.width / 2 + ( target.isReflect() ? ( 1 - spellSprite.width() - 2 * spellSprite.x() - pos.width / 8 ) : ( pos.width / 8 ) ); result.y -= pos.height - 4; break; case ICN::MAGIC08: // Position shifts for the Holy Shout spell to be closer to OG. result.x += pos.width / 2 + ( target.isReflect() ? 12 : 0 ); result.y += unitSprite.y() / 2 - 1; break; default: // center point of the unit result.x += pos.width / 2; result.y += unitSprite.y() / 2; break; } if ( result.y < 0 ) { const int maximumY = fheroes2::AGG::GetAbsoluteICNHeight( spellICN ); result.y = maximumY + spellSprite.y(); } return result; } } namespace Battle { int GetIndexIndicator( const Unit & unit ); int GetSwordCursorDirection( const int direction ); int GetDirectionFromCursorSword( const uint32_t sword ); int GetCursorFromSpell( const int spell ); class StatusListBox : public ::Interface::ListBox { public: using ::Interface::ListBox::ActionListDoubleClick; using ::Interface::ListBox::ActionListSingleClick; using ::Interface::ListBox::ActionListPressRight; void SetPosition( int32_t px, int32_t py ) { assert( px >= 0 && py >= 0 ); const int32_t totalElementHeight = maxElementsInBattleLog * battleLogElementHeight - battleLogLastElementOffset; border.SetPosition( px, py - totalElementHeight - 32, fheroes2::Display::DEFAULT_WIDTH - 32, totalElementHeight ); const fheroes2::Rect & area = border.GetArea(); SetTopLeft( area.getPosition() ); SetAreaMaxItems( maxElementsInBattleLog ); const int32_t ax = area.x + area.width - 20; SetScrollButtonUp( ICN::DROPLISL, 6, 7, fheroes2::Point( ax + 8, area.y - 10 ) ); SetScrollButtonDn( ICN::DROPLISL, 8, 9, fheroes2::Point( ax + 8, area.y + area.height - 11 ) ); const int32_t scrollbarSliderAreaLength = buttonPgDn.area().y - ( buttonPgUp.area().y + buttonPgUp.area().height ) - 7; setScrollBarArea( { ax + 5 + 8, buttonPgUp.area().y + buttonPgUp.area().height + 3, 12, scrollbarSliderAreaLength } ); const fheroes2::Sprite & originalSlider = fheroes2::AGG::GetICN( ICN::DROPLISL, 13 ); const fheroes2::Image scrollbarSlider = fheroes2::generateScrollbarSlider( originalSlider, false, scrollbarSliderAreaLength, VisibleItemCount(), static_cast( messages.size() ), { 0, 0, originalSlider.width(), 4 }, { 0, 4, originalSlider.width(), 8 } ); setScrollBarImage( scrollbarSlider ); _scrollbar.hide(); SetAreaItems( { area.x, area.y, area.width - 16, area.height + battleLogLastElementOffset } ); SetListContent( messages ); } const fheroes2::Rect & GetArea() const { return border.GetRect(); } void AddMessage( std::string str ) { messages.push_back( std::move( str ) ); if ( !openlog ) { _scrollbar.hide(); } SetListContent( messages ); SetCurrent( messages.size() - 1 ); const int32_t scrollbarSliderAreaLength = buttonPgDn.area().y - ( buttonPgUp.area().y + buttonPgUp.area().height ) - 7; const fheroes2::Sprite & originalSlider = fheroes2::AGG::GetICN( ICN::DROPLISL, 13 ); const fheroes2::Image scrollbarSlider = fheroes2::generateScrollbarSlider( originalSlider, false, scrollbarSliderAreaLength, VisibleItemCount(), static_cast( messages.size() ), { 0, 0, originalSlider.width(), 4 }, { 0, 4, originalSlider.width(), 8 } ); setScrollBarImage( scrollbarSlider ); SetCurrentVisible(); } void RedrawItem( const std::string & str, int32_t px, int32_t py, bool /* unused */ ) override { fheroes2::Text text( str, fheroes2::FontType::normalWhite() ); text.fitToOneRow( battleLogElementWidth ); text.draw( px, py, fheroes2::Display::instance() ); } void RedrawBackground( const fheroes2::Point & /* unused*/ ) override { fheroes2::Display & display = fheroes2::Display::instance(); const int32_t ax = buttonPgUp.area().x; const int32_t ah = buttonPgDn.area().y - ( buttonPgUp.area().y + buttonPgUp.area().height ); const fheroes2::Rect & borderRect = border.GetRect(); Dialog::FrameBorder::RenderOther( fheroes2::AGG::GetICN( ICN::TEXTBAK2, 0 ), borderRect ); const fheroes2::Sprite & sp3 = fheroes2::AGG::GetICN( ICN::DROPLISL, 11 ); for ( int32_t i = 0; i < ( ah / sp3.height() ); ++i ) { fheroes2::Copy( sp3, 0, 0, display, ax, buttonPgUp.area().y + buttonPgUp.area().height + ( sp3.height() * i ), sp3.width(), sp3.height() ); } const fheroes2::Sprite & sp1 = fheroes2::AGG::GetICN( ICN::DROPLISL, 10 ); const fheroes2::Sprite & sp2 = fheroes2::AGG::GetICN( ICN::DROPLISL, 12 ); fheroes2::Copy( sp1, 0, 0, display, ax, buttonPgUp.area().y + buttonPgUp.area().height, sp1.width(), sp1.height() ); fheroes2::Copy( sp2, 0, 0, display, ax, buttonPgDn.area().y - sp2.height(), sp2.width(), sp2.height() ); } void ActionCurrentUp() override { // Do nothing. } void ActionCurrentDn() override { // Do nothing. } void ActionListDoubleClick( std::string & /* unused */ ) override { // Do nothing. } void ActionListSingleClick( std::string & /* unused */ ) override { // Do nothing. } void ActionListPressRight( std::string & /* unused */ ) override { // Do nothing. } void SetOpenLog( const bool f ) { openlog = f; } bool isOpenLog() const { return openlog; } private: Dialog::FrameBorder border; std::vector messages; bool openlog{ false }; }; int matchHeroType( const HeroBase * base ) { if ( base->isCaptain() ) return CAPTAIN; switch ( base->GetRace() ) { case Race::BARB: return BARBARIAN; case Race::SORC: return SORCERESS; case Race::WRLK: return WARLOCK; case Race::WZRD: return WIZARD; case Race::NECR: return NECROMANCER; default: break; } return KNIGHT; } const std::vector & getHeroAnimation( const HeroBase * hero, int animation ) { static std::vector staticAnim; if ( staticAnim.empty() ) { staticAnim.push_back( 1 ); } if ( !hero || animation == OP_STATIC ) return staticAnim; const int heroType = matchHeroType( hero ); if ( animation == OP_SORROW ) { static const std::vector sorrowAnim = { 2, 3, 4, 5, 4, 5, 4, 3, 2 }; return ( heroType == CAPTAIN ) ? staticAnim : sorrowAnim; } static std::vector heroTypeAnim[7][9]; if ( heroTypeAnim[heroType][animation].empty() ) { const int sourceArray[7][9][9] = { // JOY CAST_MASS CAST_UP CAST_DOWN IDLE { { 6, 7, 8, 9, 8, 9, 8, 7, 6 }, { 10, 11 }, { 10 }, { 6, 12, 13 }, { 12, 6 }, { 2, 14 }, { 2 }, { 15, 16, 17 }, { 18, 19 } }, // KNIGHT { { 6, 7, 8, 9, 9, 8, 7, 6 }, { 6, 10, 11 }, { 10, 6 }, { 6, 12, 13 }, { 12, 6 }, { 6, 14 }, { 6 }, { 15, 16, 17 }, { 18 } }, // BARBARIAN { { 6, 7, 8, 7, 6 }, { 6, 7, 9 }, { 7, 6 }, { 6, 10, 11 }, { 10, 6 }, { 6, 12 }, { 6 }, { 13, 14, 15 }, { 16 } }, // SORCERESS { { 6, 7, 8, 9, 10, 9, 8, 7, 6 }, { 6, 7, 11, 12 }, { 11, 6 }, { 6, 7, 13 }, { 6 }, { 6, 14 }, { 6 }, { 15, 16 }, { 6 } }, // WARLOCK { { 6, 7, 8, 9, 8, 7, 6 }, { 6, 10, 11, 12, 13 }, { 12, 11, 10, 6 }, { 6, 14 }, { 6 }, { 6, 15 }, { 6 }, { 16, 17 }, { 18 } }, // WIZARD { { 6, 7, 6, 7, 6, 7 }, { 7, 8, 9, 10, 11 }, { 10, 9, 7 }, { 7, 12, 13, 14, 15 }, { 7 }, { 7, 12, 13, 14, 16 }, { 7 }, { 17 }, { 18, 19 } }, // NECROMANCER { { 1 }, { 2, 3, 4 }, { 3, 2 }, { 5, 6 }, { 5 }, { 5, 7 }, { 5 }, { 8, 9 }, { 10 } } // CAPTAIN }; for ( int frame = 0; frame < 9; ++frame ) { if ( sourceArray[heroType][animation][frame] != 0 ) { heroTypeAnim[heroType][animation].push_back( sourceArray[heroType][animation][frame] ); } } } return heroTypeAnim[heroType][animation]; } } bool CursorAttack( uint32_t theme ) { switch ( theme ) { case Cursor::WAR_ARROW: case Cursor::WAR_BROKENARROW: case Cursor::SWORD_TOPRIGHT: case Cursor::SWORD_RIGHT: case Cursor::SWORD_BOTTOMRIGHT: case Cursor::SWORD_BOTTOMLEFT: case Cursor::SWORD_LEFT: case Cursor::SWORD_TOPLEFT: case Cursor::SWORD_TOP: case Cursor::SWORD_BOTTOM: return true; default: break; } return false; } fheroes2::Image DrawHexagon( const uint8_t colorId ) { const int r = 22; const int l = 10; const int w = CELLW; const int h = CELLH; fheroes2::Image sf( w + 1, h + 1 ); sf.reset(); fheroes2::DrawLine( sf, { r - 1, 1 }, { 0, l + 1 }, colorId ); fheroes2::SetPixel( sf, r, 1, colorId ); fheroes2::DrawLine( sf, { r + 1, 1 }, { w, l + 1 }, colorId ); fheroes2::DrawLine( sf, { 0, l + 1 }, { 0, h - l }, colorId ); fheroes2::DrawLine( sf, { w, l + 1 }, { w, h - l }, colorId ); fheroes2::DrawLine( sf, { r - 1, h }, { 0, h - l }, colorId ); fheroes2::SetPixel( sf, r, h, colorId ); fheroes2::DrawLine( sf, { r + 1, h }, { w, h - l }, colorId ); return sf; } fheroes2::Image DrawHexagonShadow( const uint8_t alphaValue, const int32_t horizSpace ) { const int l = 13; const int w = CELLW; const int h = CELLH; fheroes2::Image sf( w, h ); sf.reset(); fheroes2::Rect rt( horizSpace, l - 1, w + 1 - horizSpace * 2, 2 * l + 4 ); for ( int i = 0; i < w / 2; i += 2 ) { for ( int x = 0; x < rt.width; ++x ) { for ( int y = 0; y < rt.height; ++y ) { fheroes2::SetTransformPixel( sf, rt.x + x, rt.y + y, alphaValue ); } } --rt.y; rt.height += 2; rt.x += ( i == 0 ) ? 1 : 2; rt.width -= ( i == 0 ) ? 2 : 4; } return sf; } bool Battle::TargetInfo::isFinishAnimFrame( const TargetInfo & info ) { return info.defender && info.defender->isFinishAnimFrame(); } int Battle::GetCursorFromSpell( const int spell ) { switch ( spell ) { case Spell::SLOW: return Cursor::SP_SLOW; case Spell::CURSE: return Cursor::SP_CURSE; case Spell::CURE: return Cursor::SP_CURE; case Spell::BLESS: return Cursor::SP_BLESS; case Spell::FIREBALL: return Cursor::SP_FIREBALL; case Spell::FIREBLAST: return Cursor::SP_FIREBLAST; case Spell::TELEPORT: return Cursor::SP_TELEPORT; case Spell::RESURRECT: return Cursor::SP_RESURRECT; case Spell::HASTE: return Cursor::SP_HASTE; case Spell::SHIELD: return Cursor::SP_SHIELD; case Spell::ARMAGEDDON: return Cursor::SP_ARMAGEDDON; case Spell::ANTIMAGIC: return Cursor::SP_ANTIMAGIC; case Spell::BERSERKER: return Cursor::SP_BERSERKER; case Spell::PARALYZE: return Cursor::SP_PARALYZE; case Spell::BLIND: return Cursor::SP_BLIND; case Spell::LIGHTNINGBOLT: return Cursor::SP_LIGHTNINGBOLT; case Spell::CHAINLIGHTNING: return Cursor::SP_CHAINLIGHTNING; case Spell::ELEMENTALSTORM: return Cursor::SP_ELEMENTALSTORM; case Spell::RESURRECTTRUE: return Cursor::SP_RESURRECTTRUE; case Spell::DISPEL: return Cursor::SP_DISPEL; case Spell::HOLYWORD: return Cursor::SP_HOLYWORD; case Spell::HOLYSHOUT: return Cursor::SP_HOLYSHOUT; case Spell::METEORSHOWER: return Cursor::SP_METEORSHOWER; case Spell::ANIMATEDEAD: return Cursor::SP_ANIMATEDEAD; case Spell::MIRRORIMAGE: return Cursor::SP_MIRRORIMAGE; case Spell::BLOODLUST: return Cursor::SP_BLOODLUST; case Spell::DEATHRIPPLE: return Cursor::SP_DEATHRIPPLE; case Spell::DEATHWAVE: return Cursor::SP_DEATHWAVE; case Spell::STEELSKIN: return Cursor::SP_STEELSKIN; case Spell::STONESKIN: return Cursor::SP_STONESKIN; case Spell::DRAGONSLAYER: return Cursor::SP_DRAGONSLAYER; case Spell::EARTHQUAKE: return Cursor::SP_EARTHQUAKE; case Spell::DISRUPTINGRAY: return Cursor::SP_DISRUPTINGRAY; case Spell::COLDRING: return Cursor::SP_COLDRING; case Spell::COLDRAY: return Cursor::SP_COLDRAY; case Spell::HYPNOTIZE: return Cursor::SP_HYPNOTIZE; case Spell::ARROW: return Cursor::SP_ARROW; default: break; } return Cursor::WAR_NONE; } int Battle::GetSwordCursorDirection( const int direction ) { switch ( direction ) { case BOTTOM_RIGHT: return Cursor::SWORD_TOPLEFT; case BOTTOM_LEFT: return Cursor::SWORD_TOPRIGHT; case RIGHT: return Cursor::SWORD_LEFT; case TOP_RIGHT: return Cursor::SWORD_BOTTOMLEFT; case TOP_LEFT: return Cursor::SWORD_BOTTOMRIGHT; case LEFT: return Cursor::SWORD_RIGHT; default: break; } return 0; } int Battle::GetDirectionFromCursorSword( const uint32_t sword ) { switch ( sword ) { case Cursor::SWORD_TOPLEFT: return BOTTOM_RIGHT; case Cursor::SWORD_TOPRIGHT: return BOTTOM_LEFT; case Cursor::SWORD_LEFT: return RIGHT; case Cursor::SWORD_BOTTOMLEFT: return TOP_RIGHT; case Cursor::SWORD_BOTTOMRIGHT: return TOP_LEFT; case Cursor::SWORD_RIGHT: return LEFT; default: break; } return UNKNOWN; } Battle::OpponentSprite::OpponentSprite( const fheroes2::Rect & area, const HeroBase * hero, const bool isReflect ) : base( hero ) , _currentAnim( getHeroAnimation( hero, OP_STATIC ) ) , _animationType( OP_STATIC ) , _idleTimer( 8000 ) , _heroIcnId( ICN::UNKNOWN ) , reflect( isReflect ) , _offset( area.x, area.y ) { const bool isCaptain = hero->isCaptain(); switch ( hero->GetRace() ) { case Race::KNGT: _heroIcnId = isCaptain ? ICN::CMBTCAPK : ICN::CMBTHROK; break; case Race::BARB: _heroIcnId = isCaptain ? ICN::CMBTCAPB : ICN::CMBTHROB; break; case Race::SORC: _heroIcnId = isCaptain ? ICN::CMBTCAPS : ICN::CMBTHROS; break; case Race::WRLK: _heroIcnId = isCaptain ? ICN::CMBTCAPW : ICN::CMBTHROW; break; case Race::WZRD: _heroIcnId = isCaptain ? ICN::CMBTCAPZ : ICN::CMBTHROZ; break; case Race::NECR: _heroIcnId = isCaptain ? ICN::CMBTCAPN : ICN::CMBTHRON; break; default: // Did you add a new faction? Add the logic here. assert( 0 ); break; } const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( _heroIcnId, _currentAnim.getFrame() ); if ( reflect ) { pos.x = _offset.x + fheroes2::Display::DEFAULT_WIDTH - RIGHT_HERO_X_OFFSET - ( sprite.x() + sprite.width() ); pos.y = _offset.y + RIGHT_HERO_Y_OFFSET + sprite.y(); } else { pos.x = _offset.x + LEFT_HERO_X_OFFSET + sprite.x(); pos.y = _offset.y + LEFT_HERO_Y_OFFSET + sprite.y(); } if ( isCaptain ) { if ( reflect ) pos.x += CAPTAIN_X_OFFSET; else pos.x -= CAPTAIN_X_OFFSET; pos.y += CAPTAIN_Y_OFFSET; } pos.width = sprite.width(); pos.height = sprite.height(); } void Battle::OpponentSprite::IncreaseAnimFrame( const bool loop ) { _currentAnim.playAnimation( loop ); } void Battle::OpponentSprite::SetAnimation( const int rule ) { _animationType = rule; _currentAnim = getHeroAnimation( base, rule ); } fheroes2::Point Battle::OpponentSprite::GetCastPosition() const { const bool isCaptain = base->isCaptain(); fheroes2::Point offset; switch ( base->GetRace() ) { case Race::KNGT: offset.x = isCaptain ? 0 : 13; offset.y = isCaptain ? 3 : -7; break; case Race::BARB: offset.x = isCaptain ? 0 : 16; offset.y = isCaptain ? 3 : -15; break; case Race::SORC: offset.x = isCaptain ? 0 : 11; offset.y = isCaptain ? 3 : -8; break; case Race::WRLK: offset.x = isCaptain ? 2 : 9; offset.y = isCaptain ? 5 : -11; break; case Race::WZRD: offset.x = isCaptain ? 5 : 1; offset.y = isCaptain ? 8 : -9; break; case Race::NECR: offset.x = isCaptain ? 5 : 13; offset.y = isCaptain ? 6 : -7; break; default: break; } return { pos.x + ( reflect ? offset.x : pos.width - offset.x ), pos.y + pos.height / 2 + offset.y }; } void Battle::OpponentSprite::Redraw( fheroes2::Image & dst ) const { const fheroes2::Sprite & hero = fheroes2::AGG::GetICN( _heroIcnId, _currentAnim.getFrame() ); fheroes2::Point offset( _offset ); if ( base->isCaptain() ) { if ( reflect ) { offset.x += CAPTAIN_X_OFFSET; } else { offset.x -= CAPTAIN_X_OFFSET; } offset.y += CAPTAIN_Y_OFFSET; } if ( reflect ) { fheroes2::Blit( hero, dst, offset.x + fheroes2::Display::DEFAULT_WIDTH - RIGHT_HERO_X_OFFSET - ( hero.x() + hero.width() ), offset.y + RIGHT_HERO_Y_OFFSET + hero.y(), reflect ); } else { fheroes2::Blit( hero, dst, offset.x + LEFT_HERO_X_OFFSET + hero.x(), offset.y + LEFT_HERO_Y_OFFSET + hero.y() ); } } bool Battle::OpponentSprite::updateAnimationState() { if ( _currentAnim.isLastFrame() ) { if ( _animationType != OP_STATIC ) { if ( _animationType != OP_CAST_MASS && _animationType != OP_CAST_UP && _animationType != OP_CAST_DOWN ) { SetAnimation( OP_STATIC ); return true; } } else if ( _idleTimer.checkDelay() ) { SetAnimation( ( Rand::Get( 1, 3 ) < 2 ) ? OP_IDLE2 : OP_IDLE ); return true; } return false; } IncreaseAnimFrame(); return true; } Battle::Status::Status() : back1( fheroes2::AGG::GetICN( ICN::TEXTBAR, 8 ) ) , back2( fheroes2::AGG::GetICN( ICN::TEXTBAR, 9 ) ) , listlog( nullptr ) { width = back1.width(); height = back1.height() + back2.height(); } void Battle::Status::SetPosition( const int32_t cx, const int32_t cy ) { fheroes2::Rect::x = cx; fheroes2::Rect::y = cy; } void Battle::Status::SetMessage( const std::string & messageString, const bool top ) { if ( top ) { _upperText.set( messageString, fheroes2::FontType::normalWhite() ); // The text cannot go beyond the text area so it is important to truncate it when necessary. _upperText.fitToOneRow( back1.width() - offsetForTextBar * 2 ); if ( listlog ) { listlog->AddMessage( messageString ); } } else if ( messageString != _lastMessage ) { _lowerText.set( messageString, fheroes2::FontType::normalWhite() ); // The text cannot go beyond the text area so it is important to truncate it when necessary. _lowerText.fitToOneRow( back1.width() - offsetForTextBar * 2 ); _lastMessage = messageString; } } void Battle::Status::Redraw( fheroes2::Image & output ) const { fheroes2::Copy( back1, 0, 0, output, x, y, back1.width(), back1.height() ); fheroes2::Copy( back2, 0, 0, output, x, y + back1.height(), back2.width(), back2.height() ); fheroes2::Display & display = fheroes2::Display::instance(); if ( !_upperText.empty() ) { _upperText.draw( x + ( back1.width() - _upperText.width() ) / 2, y + 4, display ); } if ( !_lowerText.empty() ) { _lowerText.draw( x + ( back2.width() - _lowerText.width() ) / 2, y + back1.height(), display ); } } void Battle::Status::clear() { _upperText.set( "", fheroes2::FontType::normalWhite() ); _lowerText.set( "", fheroes2::FontType::normalWhite() ); } Battle::TurnOrder::TurnOrder() : _army2Color( 0 ) { // Do nothing. } void Battle::TurnOrder::Set( const fheroes2::Rect & rt, const std::shared_ptr & units, const int army2Color ) { _area = rt; _orders = units; _army2Color = army2Color; if ( units ) { _rects.reserve( units->size() ); } } void Battle::TurnOrder::QueueEventProcessing( std::string & msg, const fheroes2::Point & offset ) const { LocalEvent & le = LocalEvent::Get(); for ( const auto & [unit, unitPos] : _rects ) { assert( unit != nullptr ); const fheroes2::Rect unitRoi = unitPos + offset; if ( le.MouseCursor( unitRoi ) ) { msg = _( "View %{monster} info" ); StringReplaceWithLowercase( msg, "%{monster}", unit->GetName() ); } if ( le.MouseClickLeft( unitRoi ) ) { Dialog::ArmyInfo( *unit, Dialog::BUTTONS, unit->isReflect() ); } else if ( le.MousePressRight( unitRoi ) ) { Dialog::ArmyInfo( *unit, Dialog::ZERO, unit->isReflect() ); } } } void Battle::TurnOrder::RedrawUnit( const fheroes2::Rect & pos, const Battle::Unit & unit, const bool revert, const bool isCurrentUnit, const uint8_t currentUnitColor, fheroes2::Image & output ) const { // Render background. const fheroes2::Sprite & backgroundOriginal = fheroes2::AGG::GetICN( ICN::SWAPWIN, 0 ); fheroes2::Copy( backgroundOriginal, 37, 268, output, pos.x + 1, pos.y + 1, turnOrderMonsterIconSize - 2, turnOrderMonsterIconSize - 2 ); // Draw a monster's sprite. const fheroes2::Sprite & mons32 = fheroes2::AGG::GetICN( ICN::MONS32, unit.GetSpriteIndex() ); if ( unit.Modes( Battle::CAP_MIRRORIMAGE ) ) { fheroes2::Sprite mirroredMonster = mons32; fheroes2::ApplyPalette( mirroredMonster, PAL::GetPalette( PAL::PaletteType::MIRROR_IMAGE ) ); fheroes2::Blit( mirroredMonster, output, pos.x + ( pos.width - mons32.width() ) / 2, pos.y + pos.height - mons32.height() - ( mons32.height() + 3 < pos.height ? 3 : 0 ), revert ); } else { fheroes2::Blit( mons32, output, pos.x + ( pos.width - mons32.width() ) / 2, pos.y + pos.height - mons32.height() - ( mons32.height() + 3 < pos.height ? 3 : 0 ), revert ); } const fheroes2::Text number( fheroes2::abbreviateNumber( static_cast( unit.GetCount() ) ), fheroes2::FontType::smallWhite() ); number.draw( pos.x + 2, pos.y + 4, output ); if ( isCurrentUnit ) { fheroes2::DrawRect( output, { pos.x, pos.y, turnOrderMonsterIconSize, turnOrderMonsterIconSize }, currentUnitColor ); } else { uint8_t color = 0; switch ( unit.GetCurrentColor() ) { case -1: // Berserkers color = ARMY_COLOR_BLACK; break; case Color::BLUE: color = ARMY_COLOR_BLUE; break; case Color::GREEN: color = ARMY_COLOR_GREEN; break; case Color::RED: color = ARMY_COLOR_RED; break; case Color::YELLOW: color = ARMY_COLOR_YELLOW; break; case Color::ORANGE: color = ARMY_COLOR_ORANGE; break; case Color::PURPLE: color = ARMY_COLOR_PURPLE; break; case Color::NONE: color = ARMY_COLOR_GRAY; break; default: assert( 0 ); // Did you add another player color? break; } fheroes2::DrawRect( output, { pos.x, pos.y, turnOrderMonsterIconSize, turnOrderMonsterIconSize }, color ); if ( unit.Modes( Battle::TR_MOVED ) ) { fheroes2::ApplyPalette( output, pos.x, pos.y, output, pos.x, pos.y, turnOrderMonsterIconSize, turnOrderMonsterIconSize, PAL::GetPalette( PAL::PaletteType::GRAY ) ); fheroes2::ApplyPalette( output, pos.x, pos.y, output, pos.x, pos.y, turnOrderMonsterIconSize, turnOrderMonsterIconSize, 3 ); } } } void Battle::TurnOrder::Redraw( const Unit * current, const uint8_t currentUnitColor, fheroes2::Image & output ) { if ( _orders.expired() ) { // Nothing to show. return; } const std::shared_ptr orders = _orders.lock(); const int32_t validUnitCount = static_cast( std::count_if( orders->begin(), orders->end(), []( const Unit * unit ) { assert( unit != nullptr ); return unit->isValid(); } ) ); const int32_t maximumUnitsToDraw = _area.width / turnOrderMonsterIconSize; int32_t offsetX = _area.x; if ( validUnitCount > maximumUnitsToDraw ) { offsetX += ( _area.width - turnOrderMonsterIconSize * maximumUnitsToDraw ) / 2; } else { offsetX += ( _area.width - turnOrderMonsterIconSize * validUnitCount ) / 2; } fheroes2::Rect::x = offsetX; fheroes2::Rect::y = _area.y; fheroes2::Rect::height = turnOrderMonsterIconSize; _rects.clear(); int32_t unitsDrawn = 0; int32_t unitsProcessed = 0; for ( const Unit * unit : *orders ) { if ( unitsDrawn == maximumUnitsToDraw ) { break; } assert( unit != nullptr ); if ( !unit->isValid() ) { continue; } if ( unit->Modes( Battle::TR_MOVED ) && ( validUnitCount - unitsProcessed > maximumUnitsToDraw ) ) { ++unitsProcessed; continue; } _rects.emplace_back( unit, fheroes2::Rect( offsetX, _area.y, turnOrderMonsterIconSize, turnOrderMonsterIconSize ) ); RedrawUnit( _rects.back().second, *unit, unit->GetColor() == _army2Color, current == unit, currentUnitColor, output ); offsetX += turnOrderMonsterIconSize; fheroes2::Rect::width += turnOrderMonsterIconSize; ++unitsDrawn; ++unitsProcessed; } } Battle::Interface::Interface( Arena & battleArena, const int32_t tileIndex ) : arena( battleArena ) , _surfaceInnerArea( 0, 0, fheroes2::Display::DEFAULT_WIDTH, fheroes2::Display::DEFAULT_HEIGHT ) , icn_cbkg( ICN::UNKNOWN ) , icn_frng( ICN::UNKNOWN ) , humanturn_spell( Spell::NONE ) , humanturn_exit( true ) , humanturn_redraw( true ) , animation_flags_frame( 0 ) , catapult_frame( 0 ) , _interruptAutoBattleForColor( 0 ) , _contourColor( 110 ) , _brightLandType( false ) , _contourCycle( 0 ) , _currentUnit( nullptr ) , _movingUnit( nullptr ) , _flyingUnit( nullptr ) , b_current_sprite( nullptr ) , index_pos( -1 ) , _teleportSpellSrcIdx( -1 ) , listlog( nullptr ) , _bridgeAnimation( { false, BridgeMovementAnimation::UP_POSITION } ) { Cursor::Get().SetThemes( Cursor::WAR_POINTER ); // border const fheroes2::Display & display = fheroes2::Display::instance(); _interfacePosition = { ( display.width() - fheroes2::Display::DEFAULT_WIDTH ) / 2, ( display.height() - fheroes2::Display::DEFAULT_HEIGHT ) / 2, _surfaceInnerArea.width, _surfaceInnerArea.height }; border.SetPosition( _interfacePosition.x - BORDERWIDTH, _interfacePosition.y - BORDERWIDTH, fheroes2::Display::DEFAULT_WIDTH, fheroes2::Display::DEFAULT_HEIGHT ); // damage info popup popup.setBattleUIRect( _interfacePosition ); // cover const bool trees = !Maps::ScanAroundObject( tileIndex, MP2::OBJ_TREES ).empty(); const Maps::Tiles & tile = world.GetTiles( tileIndex ); const int groundType = tile.GetGround(); _brightLandType = ( groundType == Maps::Ground::SNOW || groundType == Maps::Ground::DESERT || groundType == Maps::Ground::WASTELAND || groundType == Maps::Ground::BEACH ); if ( _brightLandType ) { _contourColor = 108; } switch ( groundType ) { case Maps::Ground::DESERT: icn_cbkg = ICN::CBKGDSRT; icn_frng = ICN::FRNG0004; break; case Maps::Ground::SNOW: icn_cbkg = trees ? ICN::CBKGSNTR : ICN::CBKGSNMT; icn_frng = trees ? ICN::FRNG0006 : ICN::FRNG0007; break; case Maps::Ground::SWAMP: icn_cbkg = ICN::CBKGSWMP; icn_frng = ICN::FRNG0008; break; case Maps::Ground::WASTELAND: icn_cbkg = ICN::CBKGCRCK; icn_frng = ICN::FRNG0003; break; case Maps::Ground::BEACH: icn_cbkg = ICN::CBKGBEAC; icn_frng = ICN::FRNG0002; break; case Maps::Ground::LAVA: icn_cbkg = ICN::CBKGLAVA; icn_frng = ICN::FRNG0005; break; case Maps::Ground::DIRT: icn_cbkg = trees ? ICN::CBKGDITR : ICN::CBKGDIMT; icn_frng = trees ? ICN::FRNG0010 : ICN::FRNG0009; break; case Maps::Ground::GRASS: icn_cbkg = trees ? ICN::CBKGGRTR : ICN::CBKGGRMT; icn_frng = trees ? ICN::FRNG0011 : ICN::FRNG0012; break; case Maps::Ground::WATER: icn_cbkg = ICN::CBKGWATR; icn_frng = ICN::FRNG0013; break; default: break; } // hexagon _hexagonGrid = DrawHexagon( fheroes2::GetColorId( 0x68, 0x8C, 0x04 ) ); // Shadow under the cursor: the first parameter is the shadow strength (smaller is stronger), the second is the distance between the hexagonal shadows. _hexagonCursorShadow = DrawHexagonShadow( 2, 1 ); // Hexagon shadow for the case when grid is disabled. _hexagonShadow = DrawHexagonShadow( 4, 2 ); // Shadow that fits the hexagon grid. _hexagonGridShadow = DrawHexagonShadow( 4, 1 ); btn_auto.setICNInfo( ICN::TEXTBAR, 4, 5 ); btn_settings.setICNInfo( ICN::TEXTBAR, 6, 7 ); // opponents _opponent1 = arena.GetCommander1() ? std::make_unique( _surfaceInnerArea, arena.GetCommander1(), false ) : nullptr; _opponent2 = arena.GetCommander2() ? std::make_unique( _surfaceInnerArea, arena.GetCommander2(), true ) : nullptr; if ( Arena::GetCastle() ) { main_tower = { 570, 145, 70, 160 }; } const fheroes2::Rect & area = border.GetArea(); const fheroes2::Rect settingsRect = btn_settings.area(); const int32_t satusOffsetY = area.y + area.height - settingsRect.height - btn_auto.area().height; btn_auto.setPosition( area.x, satusOffsetY ); btn_settings.setPosition( area.x, area.y + area.height - settingsRect.height ); btn_skip.setICNInfo( ICN::TEXTBAR, 0, 1 ); btn_skip.setPosition( area.x + area.width - btn_skip.area().width, area.y + area.height - btn_skip.area().height ); status.SetPosition( area.x + settingsRect.width, satusOffsetY ); listlog = std::make_unique(); const int32_t battlefieldHeight = area.height - status.height; if ( listlog ) { listlog->SetPosition( area.x, area.y + battlefieldHeight ); } status.SetLogs( listlog.get() ); // As `_battleGround` and '_mainSurface' are used to prepare battlefield screen to render on display they do not need to have a transform layer. _battleGround._disableTransformLayer(); _mainSurface._disableTransformLayer(); // Battlefield area excludes the lower part where the status log is located. _mainSurface.resize( area.width, battlefieldHeight ); _battleGround.resize( area.width, battlefieldHeight ); AudioManager::ResetAudio(); } Battle::Interface::~Interface() { AudioManager::ResetAudio(); // Fade-out battlefield. const bool isDefaultScreenSize = fheroes2::Display::instance().isDefaultSize(); fheroes2::fadeOutDisplay( _background->activeArea(), !isDefaultScreenSize ); // For 640x480 resolution we do screen fade-in. if ( isDefaultScreenSize ) { // Reset the battlefield dialog window to restore the previous display image from screen restorer. // We have multiple return places after the battle: the adventure map, Main Menu (from Battle only), // the battle results screen (if the battle was quick ended). _background.reset(); fheroes2::fadeInDisplay(); } } void Battle::Interface::SetOrderOfUnits( const std::shared_ptr & units ) { _turnOrder.Set( GetArea(), units, arena.GetArmy2Color() ); } fheroes2::Point Battle::Interface::GetMouseCursor() const { return LocalEvent::Get().GetMouseCursor() - _interfacePosition.getPosition(); } void Battle::Interface::SetStatus( const std::string & message, const bool top /* = false */ ) { if ( top ) { status.SetMessage( message, true ); status.SetMessage( "", false ); } else { status.SetMessage( message ); } humanturn_redraw = true; } void Battle::Interface::UpdateContourColor() { ++_contourCycle; if ( _brightLandType ) { static const std::array contourColorTable = { 108, 115, 122, 129, 122, 115 }; _contourColor = contourColorTable[_contourCycle % sizeof( contourColorTable )]; } else { static const std::array contourColorTable = { 110, 114, 118, 122, 126, 122, 118, 114 }; _contourColor = contourColorTable[_contourCycle % sizeof( contourColorTable )]; } } void Battle::Interface::fullRedraw() { if ( !_background ) { _background = std::make_unique( fheroes2::Display::DEFAULT_WIDTH, fheroes2::Display::DEFAULT_HEIGHT, false ); } fheroes2::Display & display = fheroes2::Display::instance(); // Fade-out game screen only for 640x480 resolution. const bool isDefaultScreenSize = display.isDefaultSize(); if ( isDefaultScreenSize ) { fheroes2::fadeOutDisplay(); } // Don't waste time playing the pre-battle sound if the game sounds are turned off if ( Settings::Get().SoundVolume() > 0 ) { _preBattleSoundChannelId = AudioManager::PlaySound( M82::PREBATTL ); } // Prepare the Battlefield ground. _redrawBattleGround(); RedrawPartialStart(); // We do not render battlefield display image to properly fade-in it. redrawPreRender(); // Fade-in battlefield. if ( !isDefaultScreenSize ) { // We need to expand the ROI for the next render to properly render window borders and shadow. display.updateNextRenderRoi( _background->totalArea() ); } fheroes2::fadeInDisplay( _background->activeArea(), !isDefaultScreenSize ); } void Battle::Interface::Redraw() { // Check that the pre-battle sound is over to start playing the battle music. // IMPORTANT: This implementation may suffer from the race condition as the pre-battle sound channel may be reused // by new sounds if they are played (one way or another) after the end of the pre-battle sound, but before calling // this method. Although in this case here it will not lead to serious problems as in the worst case, the launch of // battle music will be postponed for some time. if ( _preBattleSoundChannelId && ( _preBattleSoundChannelId < 0 || !Mixer::isPlaying( _preBattleSoundChannelId.value() ) ) && !Music::isPlaying() ) { // Reset the value of _preBattleSoundChannelId to skip isPlaying() checks in future as these checks could freeze // the game for some time in certain cases (e.g. slow MIDI backend). _preBattleSoundChannelId.reset(); AudioManager::PlayMusicAsync( MUS::GetBattleRandom(), Music::PlaybackMode::REWIND_AND_PLAY_INFINITE ); } RedrawPartialStart(); RedrawPartialFinish(); } void Battle::Interface::RedrawPartialStart() { RedrawCover(); RedrawArmies(); } void Battle::Interface::RedrawPartialFinish() { redrawPreRender(); fheroes2::Display::instance().render( _interfacePosition ); } void Battle::Interface::redrawPreRender() { if ( Settings::Get().BattleShowTurnOrder() ) { _turnOrder.Redraw( _currentUnit, _contourColor, _mainSurface ); } #ifdef WITH_DEBUG if ( IS_DEVEL() ) { const Board * board = Arena::GetBoard(); assert( board != nullptr ); for ( const Cell & cell : *board ) { const fheroes2::Text text( std::to_string( cell.GetIndex() ), fheroes2::FontType::smallWhite() ); text.draw( cell.GetPos().x + 20, cell.GetPos().y + 24, _mainSurface ); } } #endif fheroes2::Copy( _mainSurface, 0, 0, fheroes2::Display::instance(), _interfacePosition.x, _interfacePosition.y, _mainSurface.width(), _mainSurface.height() ); RedrawInterface(); } void Battle::Interface::RedrawInterface() { status.Redraw( fheroes2::Display::instance() ); btn_auto.draw(); btn_settings.draw(); btn_skip.draw(); popup.Redraw(); if ( listlog && listlog->isOpenLog() ) { listlog->Redraw(); } } void Battle::Interface::RedrawArmies() { // Continue the idle animation for all troops on the battlefield: update idle animation frames before rendering the troops. IdleTroopsAnimation(); const Castle * castle = Arena::GetCastle(); std::array wallCellIds{ Arena::CASTLE_FIRST_TOP_WALL_POS, Arena::CASTLE_TOP_ARCHER_TOWER_POS, Arena::CASTLE_SECOND_TOP_WALL_POS, Arena::CASTLE_TOP_GATE_TOWER_POS, Arena::CASTLE_GATE_POS, Arena::CASTLE_BOTTOM_GATE_TOWER_POS, Arena::CASTLE_THIRD_TOP_WALL_POS, Arena::CASTLE_BOTTOM_ARCHER_TOWER_POS, Arena::CASTLE_FOURTH_TOP_WALL_POS }; if ( castle == nullptr ) { RedrawKilled(); } for ( int32_t cellRowId = 0; cellRowId < ARENAH; ++cellRowId ) { // Redraw objects. for ( int32_t cellColumnId = 0; cellColumnId < ARENAW; ++cellColumnId ) { const int32_t cellId = cellRowId * ARENAW + cellColumnId; RedrawHighObjects( cellId ); } if ( castle != nullptr ) { // Redraw main tower. if ( cellRowId == 5 ) { RedrawCastleMainTower( *castle ); } else if ( cellRowId == 7 ) { // Redraw catapult. RedrawCastle( *castle, Arena::CATAPULT_POS ); } std::vector deadTroopBeforeWall; std::vector deadTroopAfterWall; std::vector troopCounterBeforeWall; std::vector troopCounterAfterWall; std::vector troopBeforeWall; std::vector troopAfterWall; std::vector movingTroopBeforeWall; std::vector movingTroopAfterWall; // Overlay sprites for troops (i.e. spell effect animation) should be rendered after rendering all troops // for current row so the next troop will not be rendered over the overlay sprite. std::vector troopOverlaySpriteBeforeWall; std::vector troopOverlaySpriteAfterWall; const int32_t wallCellId = wallCellIds[cellRowId]; for ( int32_t cellColumnId = 0; cellColumnId < ARENAW; ++cellColumnId ) { const int32_t cellId = cellRowId * ARENAW + cellColumnId; bool isCellBefore = true; if ( cellRowId < 5 ) { isCellBefore = cellId < wallCellId; } else { isCellBefore = cellId > wallCellId; if ( ( wallCellId == Arena::CASTLE_THIRD_TOP_WALL_POS || wallCellId == Arena::CASTLE_FOURTH_TOP_WALL_POS ) && Board::GetCell( wallCellId )->GetObject() == 0 ) { isCellBefore = false; } } for ( const Unit * deadUnit : arena.GetGraveyardTroops( cellId ) ) { if ( deadUnit && cellId != deadUnit->GetTailIndex() ) { if ( isCellBefore ) { deadTroopBeforeWall.emplace_back( deadUnit ); // Check for overlay sprites of dead units (i.e. Resurrect spell). for ( const Battle::UnitSpellEffectInfo & overlaySprite : _unitSpellEffectInfos ) { if ( overlaySprite.unitId == deadUnit->GetUID() ) { troopOverlaySpriteBeforeWall.emplace_back( &overlaySprite ); } } } else { deadTroopAfterWall.emplace_back( deadUnit ); // Check for overlay sprites of dead units (i.e. Resurrect spell). for ( const Battle::UnitSpellEffectInfo & overlaySprite : _unitSpellEffectInfos ) { if ( overlaySprite.unitId == deadUnit->GetUID() ) { troopOverlaySpriteAfterWall.emplace_back( &overlaySprite ); } } } } } const Unit * unitOnCell = Board::GetCell( cellId )->GetUnit(); if ( unitOnCell == nullptr || _flyingUnit == unitOnCell || cellId == unitOnCell->GetTailIndex() ) { continue; } if ( _movingUnit != unitOnCell && unitOnCell->isValid() ) { const int unitAnimState = unitOnCell->GetAnimationState(); const bool isStaticUnit = unitAnimState == Monster_Info::STATIC || unitAnimState == Monster_Info::IDLE; if ( isCellBefore ) { if ( isStaticUnit ) { troopCounterBeforeWall.emplace_back( unitOnCell ); } troopBeforeWall.emplace_back( unitOnCell ); } else { if ( isStaticUnit ) { troopCounterAfterWall.emplace_back( unitOnCell ); } troopAfterWall.emplace_back( unitOnCell ); } } else { if ( isCellBefore ) { movingTroopBeforeWall.emplace_back( unitOnCell ); } else { movingTroopAfterWall.emplace_back( unitOnCell ); } } // Check for overlay sprites for 'unitOnCell'. for ( const Battle::UnitSpellEffectInfo & overlaySprite : _unitSpellEffectInfos ) { if ( overlaySprite.unitId == unitOnCell->GetUID() ) { if ( isCellBefore ) { troopOverlaySpriteBeforeWall.emplace_back( &overlaySprite ); } else { troopOverlaySpriteAfterWall.emplace_back( &overlaySprite ); } } } } for ( const Unit * unit : deadTroopBeforeWall ) { RedrawTroopSprite( *unit ); } for ( const Unit * unit : troopBeforeWall ) { RedrawTroopSprite( *unit ); } for ( const Unit * unit : troopCounterBeforeWall ) { RedrawTroopCount( *unit ); } for ( const Unit * unit : movingTroopBeforeWall ) { RedrawTroopSprite( *unit ); } // Render the overlay sprite for units in current cell row above all units in this and upper rows. for ( const Battle::UnitSpellEffectInfo * overlaySprite : troopOverlaySpriteBeforeWall ) { assert( overlaySprite->icnId != ICN::UNKNOWN ); const fheroes2::Sprite & spellSprite = fheroes2::AGG::GetICN( overlaySprite->icnId, overlaySprite->icnIndex ); fheroes2::Blit( spellSprite, _mainSurface, overlaySprite->position.x, overlaySprite->position.y, overlaySprite->isReflectedImage ); } RedrawCastle( *castle, wallCellId ); for ( const Unit * unit : deadTroopAfterWall ) { RedrawTroopSprite( *unit ); } for ( const Unit * unit : troopAfterWall ) { RedrawTroopSprite( *unit ); } for ( const Unit * unit : troopCounterAfterWall ) { RedrawTroopCount( *unit ); } for ( const Unit * unit : movingTroopAfterWall ) { RedrawTroopSprite( *unit ); } // Render the overlay sprite for units in current cell row above all units in this and upper rows. for ( const Battle::UnitSpellEffectInfo * overlaySprite : troopOverlaySpriteAfterWall ) { assert( overlaySprite->icnId != ICN::UNKNOWN ); const fheroes2::Sprite & spellSprite = fheroes2::AGG::GetICN( overlaySprite->icnId, overlaySprite->icnIndex ); fheroes2::Blit( spellSprite, _mainSurface, overlaySprite->position.x, overlaySprite->position.y, overlaySprite->isReflectedImage ); } } else { std::vector troopCounter; std::vector troop; std::vector movingTroop; std::vector troopOverlaySprite; // Redraw monsters. for ( int32_t cellColumnId = 0; cellColumnId < ARENAW; ++cellColumnId ) { const int32_t cellId = cellRowId * ARENAW + cellColumnId; // Check for overlay sprites of dead units (i.e. Resurrect spell). for ( const Unit * deadUnit : arena.GetGraveyardTroops( cellId ) ) { for ( const Battle::UnitSpellEffectInfo & overlaySprite : _unitSpellEffectInfos ) { if ( overlaySprite.unitId == deadUnit->GetUID() ) { troopOverlaySprite.emplace_back( &overlaySprite ); } } } const Unit * unitOnCell = Board::GetCell( cellId )->GetUnit(); if ( unitOnCell == nullptr || _flyingUnit == unitOnCell || cellId == unitOnCell->GetTailIndex() ) { continue; } if ( _movingUnit != unitOnCell && unitOnCell->isValid() ) { const int unitAnimState = unitOnCell->GetAnimationState(); const bool isStaticUnit = unitAnimState == Monster_Info::STATIC || unitAnimState == Monster_Info::IDLE; if ( isStaticUnit ) { troopCounter.emplace_back( unitOnCell ); } troop.emplace_back( unitOnCell ); } else { movingTroop.emplace_back( unitOnCell ); } // Check for overlay sprites for 'unitOnCell'. for ( const Battle::UnitSpellEffectInfo & overlaySprite : _unitSpellEffectInfos ) { if ( overlaySprite.unitId == unitOnCell->GetUID() ) { troopOverlaySprite.emplace_back( &overlaySprite ); } } } // Redraw monster counters. for ( const Unit * unit : troop ) { RedrawTroopSprite( *unit ); } for ( const Unit * unit : troopCounter ) { RedrawTroopCount( *unit ); } for ( const Unit * unit : movingTroop ) { RedrawTroopSprite( *unit ); } // Render the overlay sprite for units in current cell row above all units in this and upper rows. for ( const Battle::UnitSpellEffectInfo * overlaySprite : troopOverlaySprite ) { assert( overlaySprite->icnId != ICN::UNKNOWN ); const fheroes2::Sprite & spellSprite = fheroes2::AGG::GetICN( overlaySprite->icnId, overlaySprite->icnIndex ); fheroes2::Blit( spellSprite, _mainSurface, overlaySprite->position.x, overlaySprite->position.y, overlaySprite->isReflectedImage ); } } // Redraw heroes. if ( cellRowId == 2 ) { RedrawOpponents(); } } if ( _flyingUnit ) { RedrawTroopSprite( *_flyingUnit ); } } void Battle::Interface::RedrawOpponents() { if ( _opponent1 ) _opponent1->Redraw( _mainSurface ); if ( _opponent2 ) _opponent2->Redraw( _mainSurface ); RedrawOpponentsFlags(); } void Battle::Interface::RedrawOpponentsFlags() { if ( _opponent1 ) { int icn = ICN::UNKNOWN; switch ( arena.GetArmy1Color() ) { case Color::BLUE: icn = ICN::HEROFL00; break; case Color::GREEN: icn = ICN::HEROFL01; break; case Color::RED: icn = ICN::HEROFL02; break; case Color::YELLOW: icn = ICN::HEROFL03; break; case Color::ORANGE: icn = ICN::HEROFL04; break; case Color::PURPLE: icn = ICN::HEROFL05; break; default: icn = ICN::HEROFL06; break; } const fheroes2::Sprite & flag = fheroes2::AGG::GetICN( icn, ICN::AnimationFrame( icn, 0, animation_flags_frame ) ); fheroes2::Blit( flag, _mainSurface, _opponent1->Offset().x + OpponentSprite::LEFT_HERO_X_OFFSET + flag.x(), _opponent1->Offset().y + OpponentSprite::LEFT_HERO_Y_OFFSET + flag.y() ); } if ( _opponent2 ) { int icn = ICN::UNKNOWN; switch ( arena.GetForce2().GetColor() ) { case Color::BLUE: icn = ICN::HEROFL00; break; case Color::GREEN: icn = ICN::HEROFL01; break; case Color::RED: icn = ICN::HEROFL02; break; case Color::YELLOW: icn = ICN::HEROFL03; break; case Color::ORANGE: icn = ICN::HEROFL04; break; case Color::PURPLE: icn = ICN::HEROFL05; break; default: icn = ICN::HEROFL06; break; } const fheroes2::Sprite & flag = fheroes2::AGG::GetICN( icn, ICN::AnimationFrame( icn, 0, animation_flags_frame ) ); const fheroes2::Point offset = _opponent2->Offset(); fheroes2::Blit( flag, _mainSurface, offset.x + fheroes2::Display::DEFAULT_WIDTH - OpponentSprite::RIGHT_HERO_X_OFFSET - ( flag.x() + flag.width() ), offset.y + OpponentSprite::RIGHT_HERO_Y_OFFSET + flag.y(), true ); } } fheroes2::Point GetTroopPosition( const Battle::Unit & unit, const fheroes2::Sprite & sprite ) { const fheroes2::Rect & rt = unit.GetRectPosition(); int32_t offsetX = 0; if ( unit.isReflect() ) { if ( unit.isWide() ) { offsetX = rt.x + ( rt.width / 2 + rt.width / 4 ) - sprite.width() - sprite.x() + 1; } else { offsetX = rt.x + ( rt.width / 2 ) - sprite.width() - sprite.x() + 1; } } else { if ( unit.isWide() ) { offsetX = rt.x + ( rt.width / 4 ) + sprite.x(); } else { offsetX = rt.x + ( rt.width / 2 ) + sprite.x(); } } const int32_t offsetY = rt.y + rt.height + sprite.y() + cellYOffset; return { offsetX, offsetY }; } void Battle::Interface::RedrawTroopSprite( const Unit & unit ) { if ( b_current_sprite && _currentUnit == &unit ) { drawTroopSprite( unit, *b_current_sprite ); } else if ( unit.Modes( SP_STONE ) ) { // Current monster can't be active if it's under Stunning effect. const int monsterIcnId = unit.GetMonsterSprite(); fheroes2::Sprite monsterSprite = fheroes2::AGG::GetICN( monsterIcnId, unit.GetFrame() ); fheroes2::ApplyPalette( monsterSprite, PAL::GetPalette( PAL::PaletteType::GRAY ) ); drawTroopSprite( unit, monsterSprite ); } else if ( unit.Modes( CAP_MIRRORIMAGE ) ) { fheroes2::Sprite monsterSprite; if ( _currentUnit == &unit && b_current_sprite != nullptr ) { monsterSprite = *b_current_sprite; } else { const int monsterIcnId = unit.GetMonsterSprite(); monsterSprite = fheroes2::AGG::GetICN( monsterIcnId, unit.GetFrame() ); } fheroes2::ApplyPalette( monsterSprite, PAL::GetPalette( PAL::PaletteType::MIRROR_IMAGE ) ); const fheroes2::Point drawnPosition = drawTroopSprite( unit, monsterSprite ); if ( _currentUnit == &unit && b_current_sprite == nullptr ) { // Current unit's turn which is idling. const fheroes2::Sprite & monsterContour = fheroes2::CreateContour( monsterSprite, _contourColor ); fheroes2::Blit( monsterContour, _mainSurface, drawnPosition.x, drawnPosition.y, unit.isReflect() ); } } else { const int monsterIcnId = unit.GetMonsterSprite(); const bool isCurrentMonsterAction = ( _currentUnit == &unit && b_current_sprite != nullptr ); const fheroes2::Sprite & monsterSprite = isCurrentMonsterAction ? *b_current_sprite : fheroes2::AGG::GetICN( monsterIcnId, unit.GetFrame() ); const fheroes2::Point drawnPosition = drawTroopSprite( unit, monsterSprite ); if ( _currentUnit == &unit && b_current_sprite == nullptr ) { // Current unit's turn which is idling. const fheroes2::Sprite & monsterContour = fheroes2::CreateContour( monsterSprite, _contourColor ); fheroes2::Blit( monsterContour, _mainSurface, drawnPosition.x, drawnPosition.y, unit.isReflect() ); } } } fheroes2::Point Battle::Interface::drawTroopSprite( const Unit & unit, const fheroes2::Sprite & troopSprite ) { const fheroes2::Rect & unitPosition = unit.GetRectPosition(); // Get the sprite rendering offset. fheroes2::Point offset = GetTroopPosition( unit, troopSprite ); if ( _movingUnit == &unit ) { // Monster is moving. // Unit coordinates relevant to its position are set in Sprite coordinates 'x' and 'y' (some values may be set incorrectly). // The offset data from BIN file is used in Monster info dialog and to correct horizontal movement range when moving diagonally. // IMPORTANT: The 'x' offset from BIN file cannot be used in horizontal movement animation as // it does not take into account the uneven movement during the step. Use sprite 'x' coordinate for this purpose. if ( _movingUnit->animation.animationLength() ) { // Get the horizontal and vertical movement projections. const int32_t moveX = _movingPos.x - unitPosition.x; const int32_t moveY = _movingPos.y - unitPosition.y; // If it is a slowed flying creature, then it should smoothly move horizontally. if ( _movingUnit->isAbilityPresent( fheroes2::MonsterAbilityType::FLYING ) ) { const double movementProgress = _movingUnit->animation.movementProgress(); offset.x += static_cast( movementProgress * moveX ); offset.y += static_cast( movementProgress * moveY ); } // If the creature has to move diagonally. else if ( moveY != 0 ) { offset.x -= Sign( moveX ) * ( _movingUnit->animation.getCurrentFrameXOffset() ) / 2; offset.y += static_cast( _movingUnit->animation.movementProgress() * moveY ); } } } else if ( _flyingUnit == &unit ) { // Monster is flying. // Get the horizontal and vertical movement projections. const int32_t moveX = _flyingPos.x - unitPosition.x; const int32_t moveY = _flyingPos.y - unitPosition.y; const double movementProgress = _flyingUnit->animation.movementProgress(); offset.x += moveX + static_cast( ( _movingPos.x - _flyingPos.x ) * movementProgress ); offset.y += moveY + static_cast( ( _movingPos.y - _flyingPos.y ) * movementProgress ); } fheroes2::AlphaBlit( troopSprite, _mainSurface, offset.x, offset.y, unit.GetCustomAlpha(), unit.isReflect() ); return offset; } void Battle::Interface::RedrawTroopCount( const Unit & unit ) { const fheroes2::Rect & rt = unit.GetRectPosition(); const fheroes2::Sprite & bar = fheroes2::AGG::GetICN( ICN::TEXTBAR, GetIndexIndicator( unit ) ); const bool isReflected = unit.isReflect(); const int32_t monsterIndex = unit.GetHeadIndex(); const int tileInFront = Board::GetIndexDirection( monsterIndex, isReflected ? Battle::LEFT : Battle::RIGHT ); const bool isValidFrontMonster = ( monsterIndex / ARENAW ) == ( tileInFront == ARENAW ); int32_t sx = rt.x + ( isReflected ? -7 : rt.width - 13 ); const int32_t sy = rt.y + rt.height - bar.height() - ( isReflected ? 21 : 9 ); int xOffset = unit.animation.getTroopCountOffset( isReflected ); // check if has unit standing in front if ( xOffset > 0 && isValidFrontMonster && Board::isValidIndex( tileInFront ) && Board::GetCell( tileInFront )->GetUnit() != nullptr ) xOffset = 0; sx += isReflected ? -xOffset : xOffset; fheroes2::Copy( bar, 0, 0, _mainSurface, sx, sy, bar.width(), bar.height() ); const fheroes2::Text text( fheroes2::abbreviateNumber( static_cast( unit.GetCount() ) ), fheroes2::FontType::smallWhite() ); text.draw( sx + ( bar.width() - text.width() ) / 2, sy + 2, _mainSurface ); } void Battle::Interface::RedrawCover() { _redrawCoverStatic(); const Bridge * bridge = Arena::GetBridge(); if ( bridge && ( bridge->isDown() || _bridgeAnimation.animationIsRequired ) ) { uint32_t spriteIndex = bridge->isDestroyed() ? BridgeMovementAnimation::DESTROYED : BridgeMovementAnimation::DOWN_POSITION; if ( _bridgeAnimation.animationIsRequired ) { spriteIndex = _bridgeAnimation.currentFrameId; } const fheroes2::Sprite & bridgeImage = fheroes2::AGG::GetICN( ICN::Get4Castle( Arena::GetCastle()->GetRace() ), spriteIndex ); fheroes2::Blit( bridgeImage, _mainSurface, bridgeImage.x(), bridgeImage.y() ); } const Cell * cell = Board::GetCell( index_pos ); const int cursorType = Cursor::Get().Themes(); if ( cell && _currentUnit && Settings::Get().BattleShowMouseShadow() ) { std::set highlightedCells; if ( humanturn_spell.isValid() ) { switch ( humanturn_spell.GetID() ) { case Spell::COLDRING: { for ( const int32_t & around : Board::GetAroundIndexes( index_pos ) ) { const Cell * nearbyCell = Board::GetCell( around ); if ( nearbyCell != nullptr ) { highlightedCells.emplace( nearbyCell ); } } break; } case Spell::FIREBALL: case Spell::METEORSHOWER: { highlightedCells.emplace( cell ); for ( const int32_t & around : Board::GetAroundIndexes( index_pos ) ) { const Cell * nearbyCell = Board::GetCell( around ); if ( nearbyCell != nullptr ) { highlightedCells.emplace( nearbyCell ); } } break; } case Spell::FIREBLAST: { highlightedCells.emplace( cell ); for ( const int32_t & around : Board::GetDistanceIndexes( index_pos, 2 ) ) { const Cell * nearbyCell = Board::GetCell( around ); if ( nearbyCell != nullptr ) { highlightedCells.emplace( nearbyCell ); } } break; } case Spell::TELEPORT: { switch ( cursorType ) { case Cursor::WAR_NONE: highlightedCells.emplace( cell ); break; case Cursor::SP_TELEPORT: if ( Board::isValidIndex( _teleportSpellSrcIdx ) ) { const Unit * unitToTeleport = arena.GetTroopBoard( _teleportSpellSrcIdx ); assert( unitToTeleport != nullptr ); const Position pos = Position::GetPosition( *unitToTeleport, index_pos ); assert( pos.GetHead() != nullptr ); highlightedCells.emplace( pos.GetHead() ); if ( unitToTeleport->isWide() ) { assert( pos.GetTail() != nullptr ); highlightedCells.emplace( pos.GetTail() ); } } else { highlightedCells.emplace( cell ); } break; default: // This should never happen assert( 0 ); break; } break; } default: highlightedCells.emplace( cell ); break; } } else if ( _currentUnit->isAbilityPresent( fheroes2::MonsterAbilityType::AREA_SHOT ) && ( cursorType == Cursor::WAR_ARROW || cursorType == Cursor::WAR_BROKENARROW ) ) { highlightedCells.emplace( cell ); for ( const int32_t & around : Board::GetAroundIndexes( index_pos ) ) { const Cell * nearbyCell = Board::GetCell( around ); if ( nearbyCell != nullptr ) { highlightedCells.emplace( nearbyCell ); } } } else if ( _currentUnit->isWide() && ( cursorType == Cursor::WAR_MOVE || cursorType == Cursor::WAR_FLY ) ) { const Position pos = Position::GetReachable( *_currentUnit, index_pos ); assert( pos.GetHead() != nullptr ); assert( pos.GetTail() != nullptr ); highlightedCells.emplace( pos.GetHead() ); highlightedCells.emplace( pos.GetTail() ); } else if ( cursorType == Cursor::SWORD_TOPLEFT || cursorType == Cursor::SWORD_TOPRIGHT || cursorType == Cursor::SWORD_BOTTOMLEFT || cursorType == Cursor::SWORD_BOTTOMRIGHT || cursorType == Cursor::SWORD_LEFT || cursorType == Cursor::SWORD_RIGHT ) { highlightedCells.emplace( cell ); int direction = 0; if ( cursorType == Cursor::SWORD_TOPLEFT ) { direction = BOTTOM_RIGHT; } else if ( cursorType == Cursor::SWORD_TOPRIGHT ) { direction = BOTTOM_LEFT; } else if ( cursorType == Cursor::SWORD_BOTTOMLEFT ) { direction = TOP_RIGHT; } else if ( cursorType == Cursor::SWORD_BOTTOMRIGHT ) { direction = TOP_LEFT; } else if ( cursorType == Cursor::SWORD_LEFT ) { direction = RIGHT; } else if ( cursorType == Cursor::SWORD_RIGHT ) { direction = LEFT; } else { assert( 0 ); } const Position pos = Position::GetReachable( *_currentUnit, Board::GetIndexDirection( index_pos, direction ) ); assert( pos.GetHead() != nullptr ); highlightedCells.emplace( pos.GetHead() ); if ( _currentUnit->isWide() ) { assert( pos.GetTail() != nullptr ); highlightedCells.emplace( pos.GetTail() ); } if ( _currentUnit->isDoubleCellAttack() ) { const Cell * secondAttackedCell = Board::GetCell( index_pos, Board::GetReflectDirection( direction ) ); if ( secondAttackedCell ) { highlightedCells.emplace( secondAttackedCell ); } } else if ( _currentUnit->isAllAdjacentCellsAttack() ) { for ( const int32_t nearbyIdx : Board::GetAroundIndexes( pos ) ) { // Should already be highlighted if ( nearbyIdx == index_pos ) { continue; } const Cell * nearbyCell = Board::GetCell( nearbyIdx ); assert( nearbyCell != nullptr ); const Unit * nearbyUnit = nearbyCell->GetUnit(); if ( nearbyUnit && nearbyUnit->GetColor() != _currentUnit->GetCurrentColor() ) { highlightedCells.emplace( nearbyCell ); } } } } else { highlightedCells.emplace( cell ); } assert( !highlightedCells.empty() ); const HeroBase * currentCommander = arena.GetCurrentCommander(); for ( const Cell * highlightedCell : highlightedCells ) { assert( highlightedCell != nullptr ); bool isApplicable = highlightedCell->isPassable( false ); if ( isApplicable ) { const Unit * highlightedUnit = highlightedCell->GetUnit(); isApplicable = highlightedUnit == nullptr || !humanturn_spell.isValid() || highlightedUnit->AllowApplySpell( humanturn_spell, currentCommander ); } if ( isApplicable ) { fheroes2::Blit( _hexagonCursorShadow, _mainSurface, highlightedCell->GetPos().x, highlightedCell->GetPos().y ); } } } } void Battle::Interface::_redrawBattleGround() { // Battlefield background image. if ( icn_cbkg != ICN::UNKNOWN ) { const fheroes2::Sprite & cbkg = fheroes2::AGG::GetICN( icn_cbkg, 0 ); fheroes2::Copy( cbkg, _battleGround ); } // Objects near the left and right borders of the Battlefield. if ( icn_frng != ICN::UNKNOWN ) { const fheroes2::Sprite & frng = fheroes2::AGG::GetICN( icn_frng, 0 ); fheroes2::Blit( frng, _battleGround, frng.x(), frng.y() ); } // Big obstacles in the center of the Battlefield. if ( arena.GetICNCovr() != ICN::UNKNOWN ) { const fheroes2::Sprite & cover = fheroes2::AGG::GetICN( arena.GetICNCovr(), 0 ); fheroes2::Blit( cover, _battleGround, cover.x(), cover.y() ); } const Castle * castle = Arena::GetCastle(); int castleBackgroundIcnId = ICN::UNKNOWN; if ( castle != nullptr ) { // Castle ground. switch ( castle->GetRace() ) { case Race::BARB: castleBackgroundIcnId = ICN::CASTBKGB; break; case Race::KNGT: castleBackgroundIcnId = ICN::CASTBKGK; break; case Race::NECR: castleBackgroundIcnId = ICN::CASTBKGN; break; case Race::SORC: castleBackgroundIcnId = ICN::CASTBKGS; break; case Race::WRLK: castleBackgroundIcnId = ICN::CASTBKGW; break; case Race::WZRD: castleBackgroundIcnId = ICN::CASTBKGZ; break; default: // Did you add a new race? Add the appropriate logic for it. assert( 0 ); break; } const fheroes2::Sprite & castleBackground = fheroes2::AGG::GetICN( castleBackgroundIcnId, 1 ); fheroes2::Blit( castleBackground, _battleGround, castleBackground.x(), castleBackground.y() ); // Moat. if ( castle->isBuild( BUILD_MOAT ) ) { const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::MOATWHOL, 0 ); fheroes2::Blit( sprite, _battleGround, sprite.x(), sprite.y() ); } } // Battlefield grid. if ( Settings::Get().BattleShowGrid() ) { const Board & board = *Arena::GetBoard(); for ( const Cell & cell : board ) { fheroes2::Blit( _hexagonGrid, _battleGround, cell.GetPos().x, cell.GetPos().y ); } } // Ground obstacles. for ( int32_t cellId = 0; cellId < ARENASIZE; ++cellId ) { RedrawLowObjects( cellId ); } // Castle top wall. if ( castle != nullptr ) { const fheroes2::Sprite & sprite2 = fheroes2::AGG::GetICN( castleBackgroundIcnId, castle->isFortificationBuild() ? 4 : 3 ); fheroes2::Blit( sprite2, _battleGround, sprite2.x(), sprite2.y() ); } } void Battle::Interface::_redrawCoverStatic() { fheroes2::Copy( _battleGround, _mainSurface ); const Settings & conf = Settings::Get(); // Movement shadow. if ( !_movingUnit && conf.BattleShowMoveShadow() && _currentUnit && !( _currentUnit->GetCurrentControl() & CONTROL_AI ) ) { const fheroes2::Image & shadowImage = conf.BattleShowGrid() ? _hexagonGridShadow : _hexagonShadow; const Board & board = *Arena::GetBoard(); for ( const Cell & cell : board ) { const Position pos = Position::GetReachable( *_currentUnit, cell.GetIndex() ); if ( pos.GetHead() != nullptr ) { assert( pos.isValidForUnit( _currentUnit ) ); fheroes2::Blit( shadowImage, _mainSurface, cell.GetPos().x, cell.GetPos().y ); } } } } void Battle::Interface::RedrawCastle( const Castle & castle, const int32_t cellId ) { const int castleIcnId = ICN::Get4Castle( castle.GetRace() ); if ( Arena::CATAPULT_POS == cellId ) { const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::CATAPULT, catapult_frame ); fheroes2::Blit( sprite, _mainSurface, 22 + sprite.x(), 390 + sprite.y() ); } else if ( Arena::CASTLE_GATE_POS == cellId ) { const Bridge * bridge = Arena::GetBridge(); assert( bridge != nullptr ); if ( bridge != nullptr && !bridge->isDestroyed() ) { const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( castleIcnId, 4 ); fheroes2::Blit( sprite, _mainSurface, sprite.x(), sprite.y() ); } } else if ( Arena::CASTLE_FIRST_TOP_WALL_POS == cellId || Arena::CASTLE_SECOND_TOP_WALL_POS == cellId || Arena::CASTLE_THIRD_TOP_WALL_POS == cellId || Arena::CASTLE_FOURTH_TOP_WALL_POS == cellId ) { uint32_t index = 0; switch ( cellId ) { case Arena::CASTLE_FIRST_TOP_WALL_POS: index = 5; break; case Arena::CASTLE_SECOND_TOP_WALL_POS: index = 6; break; case Arena::CASTLE_THIRD_TOP_WALL_POS: index = 7; break; case Arena::CASTLE_FOURTH_TOP_WALL_POS: index = 8; break; default: break; } if ( castle.isFortificationBuild() ) { switch ( Board::GetCell( cellId )->GetObject() ) { case 0: index += 31; break; case 1: index += 35; break; case 2: index += 27; break; case 3: index += 23; break; default: break; } } else { switch ( Board::GetCell( cellId )->GetObject() ) { case 0: index += 8; break; case 1: index += 4; break; case 2: index += 0; break; default: break; } } const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( castleIcnId, index ); fheroes2::Blit( sprite, _mainSurface, sprite.x(), sprite.y() ); } else if ( Arena::CASTLE_TOP_ARCHER_TOWER_POS == cellId ) { const Tower * ltower = Arena::GetTower( TowerType::TWR_LEFT ); uint32_t index = 17; if ( castle.isBuild( BUILD_LEFTTURRET ) && ltower ) index = ltower->isValid() ? 18 : 19; const fheroes2::Sprite & towerSprite = fheroes2::AGG::GetICN( castleIcnId, index ); fheroes2::Blit( towerSprite, _mainSurface, 443 + towerSprite.x(), 153 + towerSprite.y() ); } else if ( Arena::CASTLE_BOTTOM_ARCHER_TOWER_POS == cellId ) { const Tower * rtower = Arena::GetTower( TowerType::TWR_RIGHT ); uint32_t index = 17; if ( castle.isBuild( BUILD_RIGHTTURRET ) && rtower ) index = rtower->isValid() ? 18 : 19; const fheroes2::Sprite & towerSprite = fheroes2::AGG::GetICN( castleIcnId, index ); fheroes2::Blit( towerSprite, _mainSurface, 443 + towerSprite.x(), 405 + towerSprite.y() ); } else if ( Arena::CASTLE_TOP_GATE_TOWER_POS == cellId ) { const fheroes2::Sprite & towerSprite = fheroes2::AGG::GetICN( castleIcnId, 17 ); fheroes2::Blit( towerSprite, _mainSurface, 399 + towerSprite.x(), 237 + towerSprite.y() ); } else if ( Arena::CASTLE_BOTTOM_GATE_TOWER_POS == cellId ) { const fheroes2::Sprite & towerSprite = fheroes2::AGG::GetICN( castleIcnId, 17 ); fheroes2::Blit( towerSprite, _mainSurface, 399 + towerSprite.x(), 321 + towerSprite.y() ); } } void Battle::Interface::RedrawCastleMainTower( const Castle & castle ) { const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::Get4Castle( castle.GetRace() ), ( Arena::GetTower( TowerType::TWR_CENTER )->isValid() ? 20 : 26 ) ); fheroes2::Blit( sprite, _mainSurface, sprite.x(), sprite.y() ); } void Battle::Interface::RedrawLowObjects( const int32_t cellId ) { const Cell * cell = Board::GetCell( cellId ); if ( cell == nullptr ) return; const int cellObjectId = cell->GetObject(); if ( cellObjectId == 0 ) { // No object exists. return; } int objectIcnId = 0; switch ( cellObjectId ) { case 0x84: objectIcnId = ICN::COBJ0004; break; case 0x87: objectIcnId = ICN::COBJ0007; break; case 0x90: objectIcnId = ICN::COBJ0016; break; case 0x9E: objectIcnId = ICN::COBJ0030; break; case 0x9F: objectIcnId = ICN::COBJ0031; break; default: return; } const fheroes2::Sprite & objectSprite = fheroes2::AGG::GetICN( objectIcnId, 0 ); const fheroes2::Rect & pt = cell->GetPos(); fheroes2::Blit( objectSprite, _battleGround, pt.x + pt.width / 2 + objectSprite.x(), pt.y + pt.height + objectSprite.y() + cellYOffset ); } void Battle::Interface::RedrawHighObjects( const int32_t cellId ) { const Cell * cell = Board::GetCell( cellId ); if ( cell == nullptr ) return; const int cellObjectId = cell->GetObject(); if ( cellObjectId == 0 ) { // No object exists. return; } int objectIcnId = 0; switch ( cellObjectId ) { case 0x80: objectIcnId = ICN::COBJ0000; break; case 0x81: objectIcnId = ICN::COBJ0001; break; case 0x82: objectIcnId = ICN::COBJ0002; break; case 0x83: objectIcnId = ICN::COBJ0003; break; case 0x85: objectIcnId = ICN::COBJ0005; break; case 0x86: objectIcnId = ICN::COBJ0006; break; case 0x88: objectIcnId = ICN::COBJ0008; break; case 0x89: objectIcnId = ICN::COBJ0009; break; case 0x8A: objectIcnId = ICN::COBJ0010; break; case 0x8B: objectIcnId = ICN::COBJ0011; break; case 0x8C: objectIcnId = ICN::COBJ0012; break; case 0x8D: objectIcnId = ICN::COBJ0013; break; case 0x8E: objectIcnId = ICN::COBJ0014; break; case 0x8F: objectIcnId = ICN::COBJ0015; break; case 0x91: objectIcnId = ICN::COBJ0017; break; case 0x92: objectIcnId = ICN::COBJ0018; break; case 0x93: objectIcnId = ICN::COBJ0019; break; case 0x94: objectIcnId = ICN::COBJ0020; break; case 0x95: objectIcnId = ICN::COBJ0021; break; case 0x96: objectIcnId = ICN::COBJ0022; break; case 0x97: objectIcnId = ICN::COBJ0023; break; case 0x98: objectIcnId = ICN::COBJ0024; break; case 0x99: objectIcnId = ICN::COBJ0025; break; case 0x9A: objectIcnId = ICN::COBJ0026; break; case 0x9B: objectIcnId = ICN::COBJ0027; break; case 0x9C: objectIcnId = ICN::COBJ0028; break; case 0x9D: objectIcnId = ICN::COBJ0029; break; default: return; } const fheroes2::Sprite & objectSprite = fheroes2::AGG::GetICN( objectIcnId, 0 ); const fheroes2::Rect & pt = cell->GetPos(); fheroes2::Blit( objectSprite, _mainSurface, pt.x + pt.width / 2 + objectSprite.x(), pt.y + pt.height + objectSprite.y() + cellYOffset ); } void Battle::Interface::RedrawKilled() { // Redraw killed troops. for ( const int32_t & cell : arena.GraveyardOccupiedCells() ) { for ( const Unit * unit : arena.GetGraveyardTroops( cell ) ) { if ( unit && cell != unit->GetTailIndex() ) { RedrawTroopSprite( *unit ); } } } } int Battle::Interface::GetBattleCursor( std::string & statusMsg ) const { statusMsg.clear(); const Cell * cell = Board::GetCell( index_pos ); if ( cell && _currentUnit ) { const auto formatViewInfoMsg = []( const Unit * unit ) { assert( unit != nullptr ); std::string msg = _( "View %{monster} info" ); StringReplaceWithLowercase( msg, "%{monster}", unit->GetMultiName() ); return msg; }; const Unit * unit = cell->GetUnit(); if ( unit == nullptr || _currentUnit == unit ) { const Position pos = Position::GetReachable( *_currentUnit, index_pos ); if ( pos.GetHead() != nullptr ) { assert( pos.isValidForUnit( _currentUnit ) ); if ( pos.GetHead()->GetIndex() == _currentUnit->GetHeadIndex() ) { assert( !_currentUnit->isWide() || pos.GetTail()->GetIndex() == _currentUnit->GetTailIndex() ); statusMsg = formatViewInfoMsg( _currentUnit ); return Cursor::WAR_INFO; } statusMsg = _currentUnit->isFlying() ? _( "Fly %{monster} here" ) : _( "Move %{monster} here" ); StringReplaceWithLowercase( statusMsg, "%{monster}", _currentUnit->GetName() ); return _currentUnit->isFlying() ? Cursor::WAR_FLY : Cursor::WAR_MOVE; } } else { if ( _currentUnit->GetCurrentColor() == unit->GetColor() ) { statusMsg = formatViewInfoMsg( unit ); return Cursor::WAR_INFO; } if ( _currentUnit->isArchers() && !_currentUnit->isHandFighting() ) { statusMsg = _( "Shoot %{monster}" ); statusMsg.append( " " ); statusMsg.append( _n( "(1 shot left)", "(%{count} shots left)", _currentUnit->GetShots() ) ); StringReplaceWithLowercase( statusMsg, "%{monster}", unit->GetMultiName() ); StringReplace( statusMsg, "%{count}", _currentUnit->GetShots() ); return arena.IsShootingPenalty( *_currentUnit, *unit ) ? Cursor::WAR_BROKENARROW : Cursor::WAR_ARROW; } // Find all possible directions where the current monster can attack. std::set availableAttackDirection; for ( const int direction : { BOTTOM_RIGHT, BOTTOM_LEFT, RIGHT, TOP_RIGHT, TOP_LEFT, LEFT } ) { if ( Board::isValidDirection( index_pos, direction ) && Board::CanAttackFromCell( *_currentUnit, Board::GetIndexDirection( index_pos, direction ) ) ) { availableAttackDirection.emplace( direction ); } } if ( !availableAttackDirection.empty() ) { int currentDirection = cell->GetTriangleDirection( GetMouseCursor() ); if ( currentDirection == UNKNOWN ) { // This could happen when another window has popped up and the user moved the mouse. currentDirection = CENTER; } if ( availableAttackDirection.count( currentDirection ) == 0 ) { // This direction is not valid. Find the nearest one. if ( availableAttackDirection.size() == 1 ) { currentDirection = *availableAttackDirection.begin(); } else { // First search clockwise. direction_t clockWiseDirection = static_cast( currentDirection ); direction_t antiClockWiseDirection = static_cast( currentDirection ); while ( true ) { ++clockWiseDirection; if ( availableAttackDirection.count( clockWiseDirection ) > 0 ) { currentDirection = clockWiseDirection; break; } --antiClockWiseDirection; if ( availableAttackDirection.count( antiClockWiseDirection ) > 0 ) { currentDirection = antiClockWiseDirection; break; } } } } const int cursor = GetSwordCursorDirection( currentDirection ); statusMsg = _( "Attack %{monster}" ); StringReplaceWithLowercase( statusMsg, "%{monster}", unit->GetName() ); return cursor; } } } statusMsg = _( "Turn %{turn}" ); StringReplace( statusMsg, "%{turn}", arena.GetTurnNumber() ); return Cursor::WAR_NONE; } int Battle::Interface::GetBattleSpellCursor( std::string & statusMsg ) const { statusMsg.clear(); const Cell * cell = Board::GetCell( index_pos ); const Spell & spell = humanturn_spell; if ( cell && _currentUnit && spell.isValid() ) { const Unit * unitOnCell = cell->GetUnit(); // Cursor is over some dead unit that we can resurrect if ( unitOnCell == nullptr && arena.GraveyardAllowResurrect( index_pos, spell ) ) { unitOnCell = arena.GraveyardLastTroop( index_pos ); assert( unitOnCell != nullptr && !unitOnCell->isValid() ); } // Check the Teleport spell first if ( Board::isValidIndex( _teleportSpellSrcIdx ) ) { const Unit * unitToTeleport = arena.GetTroopBoard( _teleportSpellSrcIdx ); assert( unitToTeleport != nullptr ); if ( unitOnCell == nullptr && cell->isPassableForUnit( *unitToTeleport ) ) { statusMsg = _( "Teleport here" ); return Cursor::SP_TELEPORT; } statusMsg = _( "Invalid teleport destination" ); return Cursor::WAR_NONE; } if ( unitOnCell && unitOnCell->AllowApplySpell( spell, _currentUnit->GetCurrentOrArmyCommander() ) ) { statusMsg = _( "Cast %{spell} on %{monster}" ); StringReplace( statusMsg, "%{spell}", spell.GetName() ); StringReplaceWithLowercase( statusMsg, "%{monster}", unitOnCell->GetName() ); return GetCursorFromSpell( spell.GetID() ); } if ( !spell.isApplyToFriends() && !spell.isApplyToEnemies() && !spell.isApplyToAnyTroops() ) { statusMsg = _( "Cast %{spell}" ); StringReplace( statusMsg, "%{spell}", spell.GetName() ); return GetCursorFromSpell( spell.GetID() ); } } statusMsg = _( "Select spell target" ); return Cursor::WAR_NONE; } void Battle::Interface::getPendingActions( Actions & actions ) { if ( _interruptAutoBattleForColor ) { actions.emplace_back( Command::AUTO_SWITCH, _interruptAutoBattleForColor ); _interruptAutoBattleForColor = 0; } } void Battle::Interface::HumanTurn( const Unit & unit, Actions & actions ) { Cursor::Get().SetThemes( Cursor::WAR_POINTER ); // Reset the cursor position to avoid forcing the cursor shadow to be drawn at the last position of the previous turn. index_pos = -1; _currentUnit = &unit; humanturn_redraw = false; humanturn_exit = false; catapult_frame = 0; // in case we moved the window _interfacePosition = border.GetArea(); popup.Reset(); // Wait for previously set and not passed delays before rendering a new frame. WaitForAllActionDelays(); ResetIdleTroopAnimation(); Redraw(); std::string msg; animation_flags_frame = 0; // TODO: update delay types within the loop to avoid rendering slowdown. const std::vector delayTypes{ Game::BATTLE_FLAGS_DELAY }; const Board * board = Arena::GetBoard(); LocalEvent & le = LocalEvent::Get(); while ( !humanturn_exit && le.HandleEvents( Game::isDelayNeeded( delayTypes ) ) ) { // move cursor int32_t indexNew = -1; if ( le.MouseCursor( { _interfacePosition.x, _interfacePosition.y, _interfacePosition.width, _interfacePosition.height - status.height } ) ) { indexNew = board->GetIndexAbsPosition( GetMouseCursor() ); } if ( index_pos != indexNew ) { index_pos = indexNew; humanturn_redraw = true; } if ( humanturn_spell.isValid() ) { HumanCastSpellTurn( unit, actions, msg ); } else { HumanBattleTurn( unit, actions, msg ); } // update status if ( msg != status.GetMessage() ) { status.SetMessage( msg ); humanturn_redraw = true; } // animation troops if ( IdleTroopsAnimation() ) { humanturn_redraw = true; } CheckGlobalEvents( le ); // redraw arena if ( humanturn_redraw ) { Redraw(); humanturn_redraw = false; } } popup.Reset(); _currentUnit = nullptr; } void Battle::Interface::HumanBattleTurn( const Unit & unit, Actions & actions, std::string & msg ) { Cursor & cursor = Cursor::Get(); LocalEvent & le = LocalEvent::Get(); const Settings & conf = Settings::Get(); BoardActionIntentUpdater boardActionIntentUpdater( _boardActionIntent, le.MouseEventFromTouchpad() ); if ( le.KeyPress() ) { // Skip the turn if ( Game::HotKeyPressEvent( Game::HotKeyEvent::BATTLE_SKIP ) ) { actions.emplace_back( Command::SKIP, unit.GetUID() ); humanturn_exit = true; } // Battle options else if ( Game::HotKeyPressEvent( Game::HotKeyEvent::BATTLE_OPTIONS ) ) { EventShowOptions(); } // Switch the auto battle mode on/off else if ( Game::HotKeyPressEvent( Game::HotKeyEvent::BATTLE_AUTO_SWITCH ) ) { EventAutoSwitch( unit, actions ); } // Finish the battle in auto mode else if ( Game::HotKeyPressEvent( Game::HotKeyEvent::BATTLE_AUTO_FINISH ) ) { EventAutoFinish( actions ); } // Cast the spell else if ( Game::HotKeyPressEvent( Game::HotKeyEvent::BATTLE_CAST_SPELL ) ) { ProcessingHeroDialogResult( 1, actions ); } // Retreat else if ( Game::HotKeyPressEvent( Game::HotKeyEvent::BATTLE_RETREAT ) ) { ProcessingHeroDialogResult( 2, actions ); } // Surrender else if ( Game::HotKeyPressEvent( Game::HotKeyEvent::BATTLE_SURRENDER ) ) { ProcessingHeroDialogResult( 3, actions ); } } // Add offsets to inner objects const fheroes2::Rect mainTowerRect = main_tower + _interfacePosition.getPosition(); const fheroes2::Rect turnOrderRect = _turnOrder + _interfacePosition.getPosition(); if ( Arena::GetTower( TowerType::TWR_CENTER ) && le.MouseCursor( mainTowerRect ) ) { cursor.SetThemes( Cursor::WAR_INFO ); msg = _( "View Ballista info" ); if ( le.MouseClickLeft( mainTowerRect ) || le.MousePressRight( mainTowerRect ) ) { const Castle * cstl = Arena::GetCastle(); std::string ballistaMessage = Tower::GetInfo( *cstl ); if ( cstl->isBuild( BUILD_MOAT ) ) { ballistaMessage.append( "\n\n" ); ballistaMessage.append( Battle::Board::GetMoatInfo() ); } fheroes2::showStandardTextMessage( _( "Ballista" ), ballistaMessage, le.MousePressRight() ? Dialog::ZERO : Dialog::OK ); } } else if ( conf.BattleShowTurnOrder() && le.MouseCursor( turnOrderRect ) ) { cursor.SetThemes( Cursor::POINTER ); _turnOrder.QueueEventProcessing( msg, _interfacePosition.getPosition() ); } else if ( le.MouseCursor( btn_auto.area() ) ) { cursor.SetThemes( Cursor::WAR_POINTER ); msg = _( "Enable auto combat" ); ButtonAutoAction( unit, actions ); if ( le.MousePressRight() ) { fheroes2::showStandardTextMessage( _( "Auto Combat" ), _( "Allows the computer to fight out the battle for you." ), Dialog::ZERO ); } } else if ( le.MouseCursor( btn_settings.area() ) ) { cursor.SetThemes( Cursor::WAR_POINTER ); msg = _( "Customize system options" ); ButtonSettingsAction(); if ( le.MousePressRight() ) { fheroes2::showStandardTextMessage( _( "System Options" ), _( "Allows you to customize the combat screen." ), Dialog::ZERO ); } } else if ( le.MouseCursor( btn_skip.area() ) ) { cursor.SetThemes( Cursor::WAR_POINTER ); msg = _( "Skip this unit" ); ButtonSkipAction( actions ); if ( le.MousePressRight() ) { fheroes2::showStandardTextMessage( _( "Skip" ), _( "Skips the current creature. The current creature ends its turn and does not get to go again until the next round." ), Dialog::ZERO ); } } else if ( _opponent1 && le.MouseCursor( _opponent1->GetArea() + _interfacePosition.getPosition() ) ) { const fheroes2::Rect opponent1Area = _opponent1->GetArea() + _interfacePosition.getPosition(); if ( arena.GetCurrentColor() == arena.GetArmy1Color() ) { if ( _opponent1->GetHero()->isCaptain() ) { msg = _( "View Captain's options" ); } else { msg = _( "View Hero's options" ); } cursor.SetThemes( Cursor::WAR_HERO ); if ( le.MouseClickLeft( opponent1Area ) ) { ProcessingHeroDialogResult( arena.DialogBattleHero( *_opponent1->GetHero(), true, status ), actions ); humanturn_redraw = true; } } else { if ( _opponent1->GetHero()->isCaptain() ) { msg = _( "View opposing Captain" ); } else { msg = _( "View opposing Hero" ); } cursor.SetThemes( Cursor::WAR_INFO ); if ( le.MouseClickLeft( opponent1Area ) ) { arena.DialogBattleHero( *_opponent1->GetHero(), true, status ); humanturn_redraw = true; } } if ( le.MousePressRight( opponent1Area ) ) { arena.DialogBattleHero( *_opponent1->GetHero(), false, status ); humanturn_redraw = true; } } else if ( _opponent2 && le.MouseCursor( _opponent2->GetArea() + _interfacePosition.getPosition() ) ) { const fheroes2::Rect opponent2Area = _opponent2->GetArea() + _interfacePosition.getPosition(); if ( arena.GetCurrentColor() == arena.GetForce2().GetColor() ) { if ( _opponent2->GetHero()->isCaptain() ) { msg = _( "View Captain's options" ); } else { msg = _( "View Hero's options" ); } cursor.SetThemes( Cursor::WAR_HERO ); if ( le.MouseClickLeft( opponent2Area ) ) { ProcessingHeroDialogResult( arena.DialogBattleHero( *_opponent2->GetHero(), true, status ), actions ); humanturn_redraw = true; } } else { if ( _opponent2->GetHero()->isCaptain() ) { msg = _( "View opposing Captain" ); } else { msg = _( "View opposing Hero" ); } cursor.SetThemes( Cursor::WAR_INFO ); if ( le.MouseClickLeft( opponent2Area ) ) { arena.DialogBattleHero( *_opponent2->GetHero(), true, status ); humanturn_redraw = true; } } if ( le.MousePressRight( opponent2Area ) ) { arena.DialogBattleHero( *_opponent2->GetHero(), false, status ); humanturn_redraw = true; } } else if ( listlog && listlog->isOpenLog() && le.MouseCursor( listlog->GetArea() ) ) { cursor.SetThemes( Cursor::WAR_POINTER ); listlog->QueueEventProcessing(); } else if ( le.MouseCursor( { _interfacePosition.x, _interfacePosition.y, _interfacePosition.width, _interfacePosition.height - status.height } ) ) { const int themes = GetBattleCursor( msg ); cursor.SetThemes( themes ); const Cell * cell = Board::GetCell( index_pos ); if ( cell ) { if ( CursorAttack( themes ) ) { popup.SetAttackInfo( cell, _currentUnit, cell->GetUnit() ); } else { popup.Reset(); } boardActionIntentUpdater.setIntent( { themes, index_pos } ); if ( le.MouseClickLeft() ) { MouseLeftClickBoardAction( themes, *cell, boardActionIntentUpdater.isConfirmed(), actions ); } else if ( le.MousePressRight() ) { MousePressRightBoardAction( *cell ); } } else { le.MouseClickLeft(); le.MousePressRight(); } } else if ( le.MouseCursor( status ) ) { if ( listlog ) { msg = ( listlog->isOpenLog() ? _( "Hide logs" ) : _( "Show logs" ) ); if ( le.MouseClickLeft( status ) ) { listlog->SetOpenLog( !listlog->isOpenLog() ); } else if ( le.MousePressRight( status ) ) { fheroes2::showMessage( fheroes2::Text( _( "Message Bar" ), fheroes2::FontType::normalYellow() ), fheroes2::Text( _( "Shows the results of individual monster's actions." ), fheroes2::FontType::normalWhite() ), Dialog::ZERO ); } } cursor.SetThemes( Cursor::WAR_POINTER ); } else { cursor.SetThemes( Cursor::WAR_NONE ); le.MouseClickLeft(); le.MousePressRight(); } } void Battle::Interface::HumanCastSpellTurn( const Unit & /* unused */, Actions & actions, std::string & msg ) { Cursor & cursor = Cursor::Get(); LocalEvent & le = LocalEvent::Get(); BoardActionIntentUpdater boardActionIntentUpdater( _boardActionIntent, le.MouseEventFromTouchpad() ); // Cancel the spellcast if ( le.MousePressRight() || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { humanturn_spell = Spell::NONE; _teleportSpellSrcIdx = -1; } else if ( le.MouseCursor( _interfacePosition ) && humanturn_spell.isValid() ) { const int themes = GetBattleSpellCursor( msg ); cursor.SetThemes( themes ); const Cell * cell = Board::GetCell( index_pos ); if ( cell && _currentUnit && cell->GetUnit() ) { popup.SetSpellAttackInfo( cell, _currentUnit->GetCurrentOrArmyCommander(), cell->GetUnit(), humanturn_spell ); } else { popup.Reset(); } boardActionIntentUpdater.setIntent( { themes, index_pos } ); if ( le.MouseClickLeft() && Cursor::WAR_NONE != cursor.Themes() && boardActionIntentUpdater.isConfirmed() ) { if ( !Board::isValidIndex( index_pos ) ) { DEBUG_LOG( DBG_BATTLE, DBG_WARN, "Spell destination is out of range: " << index_pos ) return; } DEBUG_LOG( DBG_BATTLE, DBG_TRACE, humanturn_spell.GetName() << ", dst: " << index_pos ) if ( Cursor::SP_TELEPORT == cursor.Themes() ) { if ( _teleportSpellSrcIdx < 0 ) { _teleportSpellSrcIdx = index_pos; } else { actions.emplace_back( Command::SPELLCAST, Spell::TELEPORT, _teleportSpellSrcIdx, index_pos ); humanturn_spell = Spell::NONE; humanturn_exit = true; _teleportSpellSrcIdx = -1; } } else if ( Cursor::SP_MIRRORIMAGE == cursor.Themes() ) { actions.emplace_back( Command::SPELLCAST, Spell::MIRRORIMAGE, index_pos ); humanturn_spell = Spell::NONE; humanturn_exit = true; } else { actions.emplace_back( Command::SPELLCAST, humanturn_spell.GetID(), index_pos ); humanturn_spell = Spell::NONE; humanturn_exit = true; } } } else { cursor.SetThemes( Cursor::WAR_NONE ); } } void Battle::Interface::FadeArena( const bool clearMessageLog ) { AudioManager::ResetAudio(); fheroes2::Display & display = fheroes2::Display::instance(); if ( clearMessageLog ) { status.clear(); status.Redraw( display ); } Redraw(); const fheroes2::Rect srt = border.GetArea(); fheroes2::Image top; top._disableTransformLayer(); top.resize( srt.width, srt.height ); fheroes2::Copy( display, srt.x, srt.y, top, 0, 0, srt.width, srt.height ); fheroes2::FadeDisplayWithPalette( top, srt.getPosition(), 5, 300, 5 ); display.render(); } int Battle::GetIndexIndicator( const Unit & unit ) { if ( unit.Modes( IS_GOOD_MAGIC | SP_ANTIMAGIC ) ) { if ( unit.Modes( IS_BAD_MAGIC ) ) { // ICN::TEXTBAR index for yellow indicator background color. return 13; } // ICN::TEXTBAR index for green indicator background color. return 12; } if ( unit.Modes( IS_BAD_MAGIC ) ) { // ICN::TEXTBAR index for red indicator background color. return 14; } // ICN::TEXTBAR index for purple indicator background color. return 10; } void Battle::Interface::_openBattleSettingsDialog() { const Settings & conf = Settings::Get(); const bool showGrid = conf.BattleShowGrid(); DialogBattleSettings(); if ( showGrid != conf.BattleShowGrid() ) { // The grid setting has changed. Update for the Battlefield ground. _redrawBattleGround(); } } void Battle::Interface::EventShowOptions() { btn_settings.drawOnPress(); _openBattleSettingsDialog(); btn_settings.drawOnRelease(); humanturn_redraw = true; } void Battle::Interface::EventAutoSwitch( const Unit & unit, Actions & actions ) { if ( !arena.CanToggleAutoBattle() ) { return; } actions.emplace_back( Command::AUTO_SWITCH, unit.GetCurrentOrArmyColor() ); humanturn_redraw = true; humanturn_exit = true; } void Battle::Interface::EventAutoFinish( Actions & actions ) { if ( fheroes2::showMessage( fheroes2::Text( "", {} ), fheroes2::Text( _( "Are you sure you want to finish the battle in auto mode?" ), fheroes2::FontType::normalWhite() ), Dialog::YES | Dialog::NO ) != Dialog::YES ) { return; } actions.emplace_back( Command::AUTO_FINISH ); humanturn_redraw = true; humanturn_exit = true; } void Battle::Interface::ButtonAutoAction( const Unit & unit, Actions & actions ) { LocalEvent & le = LocalEvent::Get(); le.MousePressLeft( btn_auto.area() ) ? btn_auto.drawOnPress() : btn_auto.drawOnRelease(); if ( le.MouseClickLeft( btn_auto.area() ) ) { if ( fheroes2::showMessage( fheroes2::Text( "", {} ), fheroes2::Text( _( "Are you sure you want to enable auto combat?" ), fheroes2::FontType::normalWhite() ), Dialog::YES | Dialog::NO ) != Dialog::YES ) { return; } EventAutoSwitch( unit, actions ); } } void Battle::Interface::ButtonSettingsAction() { LocalEvent & le = LocalEvent::Get(); le.MousePressLeft( btn_settings.area() ) ? btn_settings.drawOnPress() : btn_settings.drawOnRelease(); if ( le.MouseClickLeft( btn_settings.area() ) ) { _openBattleSettingsDialog(); humanturn_redraw = true; } } void Battle::Interface::ButtonSkipAction( Actions & actions ) { LocalEvent & le = LocalEvent::Get(); le.MousePressLeft( btn_skip.area() ) ? btn_skip.drawOnPress() : btn_skip.drawOnRelease(); if ( le.MouseClickLeft( btn_skip.area() ) && _currentUnit ) { actions.emplace_back( Command::SKIP, _currentUnit->GetUID() ); humanturn_exit = true; } } void Battle::Interface::MousePressRightBoardAction( const Cell & cell ) const { const Unit * unitOnCell = cell.GetUnit(); if ( unitOnCell != nullptr ) { Dialog::ArmyInfo( *unitOnCell, Dialog::ZERO, unitOnCell->isReflect() ); } else { unitOnCell = arena.GraveyardLastTroop( cell.GetIndex() ); if ( unitOnCell != nullptr ) { Dialog::ArmyInfo( *unitOnCell, Dialog::ZERO, unitOnCell->isReflect() ); } } } void Battle::Interface::MouseLeftClickBoardAction( const int themes, const Cell & cell, const bool isConfirmed, Actions & actions ) { const auto fixupDestinationCell = []( const Unit & unit, const int32_t dst ) { // Only wide units may need this fixup if ( !unit.isWide() ) { return dst; } const Position pos = Position::GetReachable( unit, dst ); assert( pos.GetHead() != nullptr && pos.GetTail() != nullptr ); return pos.GetHead()->GetIndex(); }; const int32_t index = cell.GetIndex(); const Unit * unitOnCell = cell.GetUnit(); if ( _currentUnit ) { switch ( themes ) { case Cursor::WAR_FLY: case Cursor::WAR_MOVE: if ( !isConfirmed ) { break; } actions.emplace_back( Command::MOVE, _currentUnit->GetUID(), fixupDestinationCell( *_currentUnit, index ) ); humanturn_exit = true; break; case Cursor::SWORD_TOPLEFT: case Cursor::SWORD_TOPRIGHT: case Cursor::SWORD_RIGHT: case Cursor::SWORD_BOTTOMRIGHT: case Cursor::SWORD_BOTTOMLEFT: case Cursor::SWORD_LEFT: { if ( !isConfirmed ) { break; } const int dir = GetDirectionFromCursorSword( themes ); if ( unitOnCell && Board::isValidDirection( index, dir ) ) { const int32_t move = fixupDestinationCell( *_currentUnit, Board::GetIndexDirection( index, dir ) ); actions.emplace_back( Command::ATTACK, _currentUnit->GetUID(), unitOnCell->GetUID(), ( _currentUnit->GetHeadIndex() == move ? -1 : move ), index, Board::GetReflectDirection( dir ) ); humanturn_exit = true; } break; } case Cursor::WAR_BROKENARROW: case Cursor::WAR_ARROW: { if ( !isConfirmed ) { break; } if ( unitOnCell ) { actions.emplace_back( Command::ATTACK, _currentUnit->GetUID(), unitOnCell->GetUID(), -1, index, 0 ); humanturn_exit = true; } break; } case Cursor::WAR_INFO: { if ( unitOnCell ) { Dialog::ArmyInfo( *unitOnCell, Dialog::BUTTONS, unitOnCell->isReflect() ); humanturn_redraw = true; } break; } default: break; } } } void Battle::Interface::WaitForAllActionDelays() { LocalEvent & le = LocalEvent::Get(); // The array of possible delays of previous battlefield actions. const std::vector unitDelays{ Game::DelayType::BATTLE_FRAME_DELAY, Game::DelayType::BATTLE_MISSILE_DELAY, Game::DelayType::BATTLE_SPELL_DELAY, Game::DelayType::BATTLE_DISRUPTING_DELAY, Game::DelayType::BATTLE_CATAPULT_CLOUD_DELAY, Game::DelayType::BATTLE_BRIDGE_DELAY, Game::DelayType::CUSTOM_BATTLE_UNIT_MOVEMENT_DELAY }; // Wait for the delay after previous render and only after it render a new frame and proceed to the rest of this function. while ( le.HandleEvents( Game::isDelayNeeded( unitDelays ) ) ) { CheckGlobalEvents( le ); if ( Game::hasEveryDelayPassed( unitDelays ) ) { break; } } } void Battle::Interface::AnimateUnitWithDelay( Unit & unit, const bool skipLastFrameRender /* = false */ ) { if ( unit.isFinishAnimFrame() && unit.animation.animationLength() != 1 ) { // If it is the last frame in the animation sequence with more than one frame or if we have no frames. return; } LocalEvent & le = LocalEvent::Get(); // In the loop below we wait for the delay and then display the next frame. while ( le.HandleEvents( Game::isDelayNeeded( { Game::DelayType::CUSTOM_BATTLE_UNIT_MOVEMENT_DELAY } ) ) ) { CheckGlobalEvents( le ); if ( Game::validateAnimationDelay( Game::DelayType::CUSTOM_BATTLE_UNIT_MOVEMENT_DELAY ) ) { Redraw(); if ( unit.isFinishAnimFrame() ) { // We have reached the end of animation and rendered the last frame. break; } unit.IncreaseAnimFrame(); if ( skipLastFrameRender && unit.isFinishAnimFrame() ) { // We have reached the last animation frame and do not render it. break; } } } } void Battle::Interface::AnimateOpponents( OpponentSprite * hero ) { if ( hero == nullptr ) { return; } // Render the first animation frame. We do it here not to skip small animations with duration for 1 frame. // For such (one frame) animations we are already ad the end of animation and `isFinishFrame()` always will return true. Redraw(); LocalEvent & le = LocalEvent::Get(); // We need to wait this delay before rendering the first frame of hero animation. Game::AnimateResetDelay( Game::DelayType::BATTLE_OPPONENTS_DELAY ); // 'BATTLE_OPPONENTS_DELAY' is different than 'BATTLE_IDLE_DELAY', so we handle the idle animation separately in this loop. while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_OPPONENTS_DELAY, Game::BATTLE_IDLE_DELAY } ) ) ) { // Animate the idling units. if ( IdleTroopsAnimation() ) { Redraw(); } if ( Game::validateAnimationDelay( Game::BATTLE_OPPONENTS_DELAY ) ) { if ( hero->isFinishFrame() ) { // We have reached the end of animation. break; } hero->IncreaseAnimFrame(); // Render the next frame and then wait a delay before checking if it is the last frame in the animation. Redraw(); } } } void Battle::Interface::RedrawTroopDefaultDelay( Unit & unit ) { if ( unit.isFinishAnimFrame() ) { // Nothing to animate. return; } LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_FRAME_DELAY } ) ) ) { CheckGlobalEvents( le ); if ( Game::validateAnimationDelay( Game::BATTLE_FRAME_DELAY ) ) { Redraw(); if ( unit.isFinishAnimFrame() ) { // We have reached the end of animation. break; } unit.IncreaseAnimFrame(); } } } void Battle::Interface::RedrawActionSkipStatus( const Unit & unit ) { std::string msg = _( "%{name} skip their turn." ); StringReplaceWithLowercase( msg, "%{name}", unit.GetName() ); status.SetMessage( msg, true ); } void Battle::Interface::RedrawMissileAnimation( const fheroes2::Point & startPos, const fheroes2::Point & endPos, const double angle, const uint32_t monsterID ) { LocalEvent & le = LocalEvent::Get(); const bool reverse = startPos.x > endPos.x; const bool isMage = ( monsterID == Monster::MAGE || monsterID == Monster::ARCHMAGE ); fheroes2::Sprite missile; fheroes2::Point endPosShift{ 0, 0 }; // Mage is channeling the bolt; doesn't have missile sprite if ( isMage ) { fheroes2::delayforMs( Game::ApplyBattleSpeed( 115 ) ); } else { missile = fheroes2::AGG::GetICN( static_cast( Monster::GetMissileICN( monsterID ) ), static_cast( Bin_Info::GetMonsterInfo( monsterID ).getProjectileID( angle ) ) ); // The projectile has to hit the target but not go through it so its end position is shifted in the direction to the shooter. endPosShift.x = reverse ? ( missile.width() / 2 ) : -( missile.width() / 2 ); endPosShift.y = ( startPos.y > endPos.y ) ? ( missile.height() / 2 ) : -( missile.height() / 2 ); } // Lich/Power lich has projectile speed of 25 const std::vector points = GetEuclideanLine( startPos, endPos + endPosShift, isMage ? 50 : std::max( missile.width(), 25 ) ); std::vector::const_iterator pnt = points.begin(); // For most shooting creatures we do not render the first missile position to better imitate start position change depending on shooting angle. if ( !isMage && ( monsterID != Monster::TROLL ) && ( monsterID != Monster::WAR_TROLL ) ) { ++pnt; } // Shooter projectile rendering offset uses 'x' and 'y' from sprite data. const fheroes2::Point missileOffset( reverse ? ( -missile.width() - missile.x() ) : missile.x(), ( angle > 0 ) ? ( -missile.height() - missile.y() ) : missile.y() ); // Wait for previously set and not passed delays before rendering a new frame. WaitForAllActionDelays(); // convert the following code into a function/event service while ( le.HandleEvents( false ) && pnt != points.end() ) { CheckGlobalEvents( le ); if ( Game::validateAnimationDelay( Game::BATTLE_MISSILE_DELAY ) ) { RedrawPartialStart(); if ( isMage ) { fheroes2::DrawLine( _mainSurface, { startPos.x, startPos.y - 2 }, { pnt->x, pnt->y - 2 }, 0x77 ); fheroes2::DrawLine( _mainSurface, { startPos.x, startPos.y - 1 }, { pnt->x, pnt->y - 1 }, 0xB5 ); fheroes2::DrawLine( _mainSurface, startPos, *pnt, 0xBC ); fheroes2::DrawLine( _mainSurface, { startPos.x, startPos.y + 1 }, { pnt->x, pnt->y + 1 }, 0xB5 ); fheroes2::DrawLine( _mainSurface, { startPos.x, startPos.y + 2 }, { pnt->x, pnt->y + 2 }, 0x77 ); } else { // Coordinates in 'pnt' corresponds to the front side of the projectile (arrowhead). fheroes2::Blit( missile, _mainSurface, pnt->x + missileOffset.x, pnt->y + missileOffset.y, reverse ); } RedrawPartialFinish(); ++pnt; } } } void Battle::Interface::RedrawActionNewTurn() const { if ( listlog == nullptr ) { return; } std::string msg = _( "Turn %{turn}" ); StringReplace( msg, "%{turn}", arena.GetTurnNumber() ); listlog->AddMessage( std::move( msg ) ); } void Battle::Interface::RedrawActionAttackPart1( Unit & attacker, const Unit & defender, const TargetsInfo & targets ) { Cursor::Get().SetThemes( Cursor::WAR_POINTER ); _currentUnit = nullptr; _movingUnit = &attacker; _movingPos = attacker.GetRectPosition().getPosition(); // Unit 'Position' is position of the tile he's standing at const fheroes2::Rect & pos1 = attacker.GetRectPosition(); const fheroes2::Rect & pos2 = defender.GetRectPosition(); const bool archer = attacker.isArchers() && !Unit::isHandFighting( attacker, defender ); const bool isDoubleCell = attacker.isDoubleCellAttack() && 2 == targets.size(); // redraw luck animation if ( attacker.Modes( LUCK_GOOD | LUCK_BAD ) ) { RedrawActionLuck( attacker ); } AudioManager::PlaySound( attacker.M82Attk( defender ) ); // long distance attack animation if ( archer ) { // Reset the delay to wait till the next frame if is not already waiting. if ( !Game::isDelayNeeded( { Game::DelayType::CUSTOM_BATTLE_UNIT_MOVEMENT_DELAY } ) ) { Game::AnimateResetDelay( Game::DelayType::CUSTOM_BATTLE_UNIT_MOVEMENT_DELAY ); } const fheroes2::Sprite & attackerSprite = fheroes2::AGG::GetICN( attacker.GetMonsterSprite(), attacker.GetFrame() ); const fheroes2::Point attackerPos = GetTroopPosition( attacker, attackerSprite ); // For shooter position we need bottom center position of rear tile // Use cell coordinates for X because sprite width is very inconsistent (e.g. Halfling) const int rearCenterX = ( attacker.isWide() && attacker.isReflect() ) ? pos1.width * 3 / 4 : CELLW / 2; const fheroes2::Point shooterPos( pos1.x + rearCenterX, attackerPos.y - attackerSprite.y() ); // Use the front one to calculate the angle, then overwrite fheroes2::Point offset = attacker.GetStartMissileOffset( Monster_Info::FRONT ); const fheroes2::Point targetPos = defender.GetCenterPoint(); double angle = GetAngle( fheroes2::Point( shooterPos.x + offset.x, shooterPos.y + offset.y ), targetPos ); // This check is made only for situations when a target stands on the same row as shooter. if ( attacker.GetHeadIndex() / ARENAW == defender.GetHeadIndex() / ARENAW ) { angle = 0; } // Angles are used in Heroes2 as 90 (TOP) -> 0 (FRONT) -> -90 (BOT) degrees const int direction = angle >= 25.0 ? Monster_Info::TOP : ( angle <= -25.0 ) ? Monster_Info::BOTTOM : Monster_Info::FRONT; if ( direction != Monster_Info::FRONT ) { offset = attacker.GetStartMissileOffset( direction ); } // redraw archer attack animation if ( attacker.SwitchAnimation( Monster_Info::RANG_TOP + direction * 2 ) ) { // Set the delay between shooting animation frames. Game::setCustomUnitMovementDelay( Game::ApplyBattleSpeed( attacker.animation.getShootingSpeed() ) / attacker.animation.animationLength() ); // We do not render the last frame of shooting animation as all frames besides this contains the projectile. // The last frame will be rendered in RedrawMissileAnimation() function with the render of projectile. AnimateUnitWithDelay( attacker, true ); } const fheroes2::Point missileStart( shooterPos.x + ( attacker.isReflect() ? -offset.x : offset.x ), shooterPos.y + offset.y ); // draw missile animation RedrawMissileAnimation( missileStart, targetPos, angle, attacker.GetID() ); } else { int attackAnim = isDoubleCell ? Monster_Info::RANG_FRONT : Monster_Info::MELEE_FRONT; if ( pos2.y < pos1.y ) { attackAnim -= 2; } else if ( pos2.y > pos1.y ) { attackAnim += 2; } // redraw melee attack animation if ( attacker.SwitchAnimation( attackAnim ) ) { // Reset the delay to wait till the next frame. Game::AnimateResetDelay( Game::DelayType::BATTLE_FRAME_DELAY ); RedrawTroopDefaultDelay( attacker ); } } } void Battle::Interface::RedrawActionAttackPart2( Unit & attacker, const Unit & defender, const TargetsInfo & targets, const uint32_t resurrects ) { // Reset the delay to wait till the next frame. if ( !Game::isDelayNeeded( { Game::DelayType::BATTLE_FRAME_DELAY } ) ) { Game::AnimateResetDelay( Game::DelayType::BATTLE_FRAME_DELAY ); } // post attack animation int attackStart = attacker.animation.getCurrentState(); if ( attackStart >= Monster_Info::MELEE_TOP && attackStart <= Monster_Info::RANG_BOT ) { ++attackStart; attacker.SwitchAnimation( attackStart ); } // targets damage animation RedrawActionWincesKills( targets, &attacker, &defender ); RedrawTroopDefaultDelay( attacker ); attacker.SwitchAnimation( Monster_Info::STATIC ); const bool isMirror = targets.size() == 1 && targets.front().defender->isModes( CAP_MIRRORIMAGE ); // draw status for first defender if ( !isMirror && !targets.empty() ) { std::string msg( _n( "%{attacker} does %{damage} damage.", "%{attacker} do %{damage} damage.", attacker.GetCount() ) ); StringReplaceWithLowercase( msg, "%{attacker}", attacker.GetName() ); if ( 1 < targets.size() ) { uint32_t killed = 0; uint32_t damage = 0; for ( const TargetInfo & target : targets ) { if ( !target.defender->isModes( CAP_MIRRORIMAGE ) ) { killed += target.killed; damage += target.damage; } } StringReplace( msg, "%{damage}", damage ); if ( killed ) { msg.append( " " ); msg.append( _n( "1 creature perishes.", "%{count} creatures perish.", killed ) ); StringReplace( msg, "%{count}", killed ); } } else { const TargetInfo & target = targets.front(); StringReplace( msg, "%{damage}", target.damage ); if ( target.killed ) { msg.append( " " ); msg.append( _n( "1 %{defender} perishes.", "%{count} %{defender} perish.", target.killed ) ); StringReplace( msg, "%{count}", target.killed ); StringReplaceWithLowercase( msg, "%{defender}", target.defender->GetPluralName( target.killed ) ); } } status.SetMessage( msg, true ); status.SetMessage( "", false ); if ( resurrects != 0 ) { const auto updateStatusBar = []( Battle::Status & statusBar, std::string & localMsg, const uint32_t localRes, const char * localUnit ) { StringReplace( localMsg, "%{count}", localRes ); StringReplaceWithLowercase( localMsg, "%{unit}", localUnit ); statusBar.SetMessage( localMsg, true ); statusBar.SetMessage( "", false ); }; if ( attacker.isAbilityPresent( fheroes2::MonsterAbilityType::SOUL_EATER ) ) { msg = _n( "1 soul is incorporated.", "%{count} souls are incorporated.", resurrects ); updateStatusBar( status, msg, resurrects, attacker.GetPluralName( resurrects ) ); } else if ( attacker.isAbilityPresent( fheroes2::MonsterAbilityType::HP_DRAIN ) ) { msg = _n( "1 %{unit} is revived.", "%{count} %{unit} are revived.", resurrects ); updateStatusBar( status, msg, resurrects, attacker.GetPluralName( resurrects ) ); } } } _movingUnit = nullptr; } void Battle::Interface::RedrawActionWincesKills( const TargetsInfo & targets, Unit * attacker /* = nullptr */, const Unit * defender /* = nullptr */ ) { // Reset the delay to wait till the next frame. if ( !Game::isDelayNeeded( { Game::DelayType::BATTLE_FRAME_DELAY } ) ) { Game::AnimateResetDelay( Game::DelayType::BATTLE_FRAME_DELAY ); } LocalEvent & le = LocalEvent::Get(); // Number of targets to be animated with a wince or kill animation. ptrdiff_t animatingTargets = 0; int32_t deathColor = Color::UNUSED; std::vector mirrorImages; std::set resistantTarget; std::set unitSounds; // If this was a Lich attack, we should render an explosion cloud over the target unit immediately after the projectile hits the target, // along with the unit kill/wince animation. const bool drawLichCloud = ( attacker != nullptr ) && ( defender != nullptr ) && attacker->isArchers() && !Unit::isHandFighting( *attacker, *defender ) && attacker->isAbilityPresent( fheroes2::MonsterAbilityType::AREA_SHOT ); // Play sound only if it is not already playing. const auto playSoundIfNotPlaying = [&unitSounds]( const int unitSound ) { const auto [dummy, isUnique] = unitSounds.insert( unitSound ); if ( isUnique ) { AudioManager::PlaySound( unitSound ); } }; for ( const Battle::TargetInfo & target : targets ) { Unit * unit = target.defender; if ( unit == nullptr ) { continue; } if ( unit->isModes( CAP_MIRRORIMAGE ) ) { mirrorImages.push_back( unit ); } // kill animation if ( !unit->isValid() ) { // destroy linked mirror if ( unit->isModes( CAP_MIRROROWNER ) ) { mirrorImages.push_back( unit->GetMirror() ); } unit->SwitchAnimation( Monster_Info::KILL ); playSoundIfNotPlaying( unit->M82Kill() ); ++animatingTargets; deathColor = unit->GetArmyColor(); } else if ( target.damage ) { // wince animation if ( drawLichCloud ) { // The Lich cloud causes units to freeze for some time in the maximum wince state. // So we will divide the wince animation. First part: the creature stands for a couple of frames before wincing. unit->SwitchAnimation( Monster_Info::STAND_STILL ); } else { unit->SwitchAnimation( Monster_Info::WNCE ); playSoundIfNotPlaying( unit->M82Wnce() ); } ++animatingTargets; } else { // have immunity resistantTarget.insert( target.defender ); playSoundIfNotPlaying( M82::RSBRYFZL ); } } SetHeroAnimationReactionToTroopDeath( deathColor ); uint32_t lichCloudFrame = 0; const uint32_t lichCloudMaxFrame = fheroes2::AGG::GetICNCount( ICN::LICHCLOD ); // Wince animation under the Lich cloud, second part: the frame number after which the target animation will be switched to 'WNCE_UP'. const uint32_t wnceUpStartFrame = 1; // Wince animation under the Lich cloud, third part: the frame number after which the target animation will be switched to 'WNCE_DOWN'. const uint32_t wnceDownStartFrame = lichCloudMaxFrame - 3; if ( drawLichCloud ) { // Lich cloud sound. AudioManager::PlaySound( attacker->M82Expl() ); } while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_FRAME_DELAY } ) ) ) { CheckGlobalEvents( le ); if ( !Game::validateAnimationDelay( Game::BATTLE_FRAME_DELAY ) ) { continue; } RedrawPartialStart(); // Render a Lich cloud above the target unit if it is a Lich attack and if the cloud animation is not already finished. if ( drawLichCloud && lichCloudFrame < lichCloudMaxFrame ) { const fheroes2::Sprite & spellSprite = fheroes2::AGG::GetICN( ICN::LICHCLOD, lichCloudFrame ); const fheroes2::Point & pos = CalculateSpellPosition( *defender, ICN::LICHCLOD, spellSprite ); fheroes2::Blit( spellSprite, _mainSurface, pos.x, pos.y, false ); ++lichCloudFrame; } RedrawPartialFinish(); // Make a check if all animation sequences are over (after rendering the last frame) to break this render loop. const ptrdiff_t finishedAnimationCount = std::count_if( targets.begin(), targets.end(), [&resistantTarget]( const TargetInfo & info ) { if ( info.defender == nullptr ) { return false; } if ( resistantTarget.count( info.defender ) > 0 ) { return false; } const int animationState = info.defender->GetAnimationState(); if ( animationState == Monster_Info::WNCE || animationState == Monster_Info::WNCE_UP || animationState == Monster_Info::WNCE_DOWN || animationState == Monster_Info::STAND_STILL ) { return false; } if ( animationState != Monster_Info::KILL ) { return true; } return TargetInfo::isFinishAnimFrame( info ); } ); // There should not be more finished animations than when we started. assert( finishedAnimationCount <= animatingTargets ); // There should not be more Lich cloud animation frames than in the corresponding ICN. assert( lichCloudFrame <= lichCloudMaxFrame ); // IMPORTANT: The game engine can change STATIC animation to IDLE, especially for Ghosts and Zombies, // so we need also to check for IDLE where we check for STATIC. if ( ( animatingTargets == finishedAnimationCount ) && ( !drawLichCloud || ( lichCloudFrame == lichCloudMaxFrame ) ) && ( ( attacker == nullptr ) || ( attacker->animation.getCurrentState() == Monster_Info::STATIC ) || ( attacker->animation.getCurrentState() == Monster_Info::IDLE ) ) ) { // All unit animation frames are rendered and if it was a Lich attack then also its cloud frames are rendered too. break; } // Progress all units animations. if ( attacker != nullptr ) { if ( attacker->isFinishAnimFrame() ) { attacker->SwitchAnimation( Monster_Info::STATIC ); } else { attacker->IncreaseAnimFrame(); } } for ( const Battle::TargetInfo & target : targets ) { if ( target.defender ) { if ( target.defender->isFinishAnimFrame() && ( target.defender->GetAnimationState() == Monster_Info::WNCE || target.defender->GetAnimationState() == Monster_Info::WNCE_DOWN ) ) { target.defender->SwitchAnimation( Monster_Info::STATIC ); } else if ( drawLichCloud && lichCloudFrame == wnceUpStartFrame && ( target.defender->GetAnimationState() == Monster_Info::STAND_STILL ) ) { target.defender->SwitchAnimation( Monster_Info::WNCE_UP ); playSoundIfNotPlaying( target.defender->M82Wnce() ); } else if ( drawLichCloud && lichCloudFrame == wnceDownStartFrame && ( target.defender->GetAnimationState() == Monster_Info::WNCE_UP ) ) { target.defender->SwitchAnimation( Monster_Info::WNCE_DOWN ); } else { target.defender->IncreaseAnimFrame(); } } } } // Fade away animation for destroyed mirror images if ( !mirrorImages.empty() ) { RedrawActionRemoveMirrorImage( mirrorImages ); } } void Battle::Interface::SetHeroAnimationReactionToTroopDeath( const int32_t deathColor ) const { if ( deathColor == Color::UNUSED ) { return; } const bool attackersTurn = ( deathColor == arena.GetArmy2Color() ); OpponentSprite * attackingHero = attackersTurn ? _opponent1.get() : _opponent2.get(); OpponentSprite * defendingHero = attackersTurn ? _opponent2.get() : _opponent1.get(); // 60% of joyful animation if ( attackingHero && Rand::Get( 1, 5 ) < 4 ) { attackingHero->SetAnimation( OP_JOY ); } // 80% of sorrow animation otherwise else if ( defendingHero && Rand::Get( 1, 5 ) < 5 ) { defendingHero->SetAnimation( OP_SORROW ); } } void Battle::Interface::RedrawActionMove( Unit & unit, const Indexes & path ) { // If the path is empty there is no movement and so nothing to render. if ( path.empty() ) { return; } // Reset the delay to wait till the next frame if is not already waiting. if ( !Game::isDelayNeeded( { Game::DelayType::CUSTOM_BATTLE_UNIT_MOVEMENT_DELAY } ) ) { Game::AnimateResetDelay( Game::DelayType::CUSTOM_BATTLE_UNIT_MOVEMENT_DELAY ); } Cursor::Get().SetThemes( Cursor::WAR_POINTER ); Indexes::const_iterator dst = path.begin(); Bridge * bridge = Arena::GetBridge(); // Get the time to animate movement for one cell. uint32_t frameDelay = Game::ApplyBattleSpeed( unit.animation.getMoveSpeed() ); if ( unit.Modes( SP_HASTE ) && frameDelay > 1 ) { frameDelay = frameDelay * 65 / 100; // by 35% faster } else if ( unit.Modes( SP_SLOW ) ) { frameDelay = frameDelay * 150 / 100; // by 50% slower } // Set the delay between movement animation frames. This delay will be used for all types of movement animations. unit.SwitchAnimation( Monster_Info::MOVING ); Game::setCustomUnitMovementDelay( frameDelay / unit.animation.animationLength() ); std::string msg = _( "Moved %{monster}: from [%{src}] to [%{dst}]." ); StringReplaceWithLowercase( msg, "%{monster}", unit.GetName() ); StringReplace( msg, "%{src}", std::to_string( ( unit.GetHeadIndex() / ARENAW ) + 1 ) + ", " + std::to_string( ( unit.GetHeadIndex() % ARENAW ) + 1 ) ); assert( _movingUnit == nullptr && _flyingUnit == nullptr ); _currentUnit = nullptr; _movingUnit = &unit; // If it is a flying creature that acts like walking one when it is under the Slow spell. const bool canFly = unit.isAbilityPresent( fheroes2::MonsterAbilityType::FLYING ); // If it is a wide creature (cache this boolean to use in the loop). const bool isWide = unit.isWide(); const Indexes::const_iterator pathEnd = path.end(); const Indexes::const_iterator finalStep = pathEnd - 1; const bool isOneStepPath = [&unit, &path]() { if ( path.size() == 1 ) { return true; } if ( !unit.isWide() ) { return false; } // If wide unit performs 3 movements and the first movement is a turn back, then it's // path consists of only one "real" movement, because... if ( path.size() == 3 && path[0] == unit.GetTailIndex() ) { // ... its last movement should be a return to the normal position. assert( Board::GetDirection( path[1], path[2] ) == ( unit.isReflect() ? LEFT : RIGHT ) ); return true; } return false; }(); // TODO: make an analytic check with checks for wide creatures back step: is one step left before/after the bridge action. // Slowed flying creature has to fly off. if ( canFly ) { // If a flying creature has to cross the bridge during its path we have to open it before the creature flies up. // Otherwise it will freeze during the movement, waiting for the bridge to open. So we have to go the whole path // to analyze if the bridge needs to open for this creature. if ( bridge ) { const Position startPosition = unit.GetPosition(); while ( dst != pathEnd ) { if ( bridge->NeedDown( unit, *dst ) ) { // Restore the initial creature position before rendering the whole battlefield with the bridge animation. unit.SetPosition( startPosition ); bridge->ActionDown(); break; } // Fix for wide flyers - go the whole path with reflections to check the bridge. if ( isWide && ( unit.GetTailIndex() == *dst ) ) { unit.SetReflection( !unit.isReflect() ); } unit.SetPosition( *dst ); ++dst; } // If there was no bridge on the path then restore the initial creature position. if ( dst == pathEnd ) { unit.SetPosition( startPosition ); } // Restore the initial path pointer state. dst = path.begin(); } // The destination of fly off is same as the current creature position. _movingPos = unit.GetRectPosition().getPosition(); const bool isFromRightArmy = unit.isReflect(); const bool isFlyToRight = ( _movingPos.x < Board::GetCell( *dst )->GetPos().x ); // Reflect the creature if it has to fly back. unit.SetReflection( !isFlyToRight ); // For creatures, that has no 'FLY_UP' animation, like Ghosts, we check if the animation was correctly set. if ( unit.SwitchAnimation( Monster_Info::FLY_UP ) ) { AudioManager::PlaySound( unit.M82Tkof() ); AnimateUnitWithDelay( unit ); } // If a wide flyer returns back it should skip one path position (its head becomes its tail - it is already one move). if ( isWide && ( isFlyToRight == isFromRightArmy ) ) { ++dst; } // Switch animation to MOVING before going through the path. unit.SwitchAnimation( Monster_Info::MOVING ); } else { // Every ground unit should start its movement from the special 'MOVE_START' animation // or if it moves only for one cell its animation must be 'MOVE_QUICK'. So a check for 1 cell path is made. if ( isOneStepPath ) { unit.SwitchAnimation( Monster_Info::MOVE_QUICK ); } else { unit.SwitchAnimation( Monster_Info::MOVE_START ); } } // For Battle speed 9 and 10 we play one sound at a time without any simultaneous playbacks // because on these speeds the unit will end its movement before the sound is finished. const bool playSoundBySound = Settings::Get().BattleSpeed() > 8; int soundStatus = -1; const int walkSoundId = unit.M82Move(); while ( dst != pathEnd ) { // Check if a wide unit changes its horizontal direction. if ( isWide && unit.GetTailIndex() == *dst ) { // We must not reflect the flyers at the and of the path (just before the landing). if ( !canFly || ( dst != finalStep ) ) { unit.SetReflection( !unit.isReflect() ); } // After changing the direction go to the next step in the path. ++dst; continue; } const Cell * cell = Board::GetCell( *dst ); _movingPos = cell->GetPos().getPosition(); if ( !isWide ) { // Check for change the horizontal direction. Only for non-wide units, the wide units use their own algorithm. unit.UpdateDirection( cell->GetPos() ); } if ( bridge && bridge->NeedDown( unit, *dst ) ) { _movingUnit = nullptr; unit.SwitchAnimation( Monster_Info::STAND_STILL ); bridge->ActionDown(); _movingUnit = &unit; if ( dst == finalStep ) { // There is only one cell left to move after standing. unit.SwitchAnimation( Monster_Info::MOVE_QUICK ); } else { // If the path has more than one step after the bridge action then begin the movement. unit.SwitchAnimation( Monster_Info::MOVE_START ); } } // If a wide flyer is flying to the right its visual horizontal destination should be shifted to the left by one cell. if ( canFly && isWide && !unit.isReflect() ) { _movingPos.x -= CELLW; } // TODO: adjust sounds calls and synchronize them with frames. Take into account that some sounds (like for Cavalry) consists of a sequence of steps. if ( playSoundBySound ) { // Start unit move sound only if it is not already playing. if ( soundStatus < 0 || !Mixer::isPlaying( soundStatus ) ) { soundStatus = AudioManager::PlaySound( walkSoundId ); } } else { AudioManager::PlaySound( walkSoundId ); } AnimateUnitWithDelay( unit ); unit.SetPosition( *dst ); ++dst; // Do a post-move check for the bridge action and set the animation the movement to the next cell in the path. if ( canFly ) { // The animation for the next step in the path of slowed flying creatures is always "MOVING". unit.SwitchAnimation( Monster_Info::MOVING ); } else { // Check for possible bridge close action, after walking unit's end of movement to the next cell. // This check should exclude the flying creature because it can't 'hang' here to wait // for bridge to close. For this creature, the bridge should close after it lands. if ( bridge && bridge->AllowUp() ) { _movingUnit = nullptr; unit.SwitchAnimation( Monster_Info::STAND_STILL ); bridge->ActionUp(); _movingUnit = &unit; if ( dst == finalStep ) { // There is only one cell left to move after standing. unit.SwitchAnimation( Monster_Info::MOVE_QUICK ); } else { // If the path has more than one step after the bridge action then begin the movement. unit.SwitchAnimation( Monster_Info::MOVE_START ); } } else if ( dst == finalStep ) { // There is only one cell left to move. unit.SwitchAnimation( Monster_Info::MOVE_END ); } else { unit.SwitchAnimation( Monster_Info::MOVING ); } } } // Slowed flying creature has to land. if ( canFly ) { // IMPORTANT: do not combine into vector animations with the STATIC at the end: the game could randomly switch it to IDLE this way. unit.SwitchAnimation( { Monster_Info::FLY_LAND, Monster_Info::STAND_STILL } ); AudioManager::PlaySound( unit.M82Land() ); AnimateUnitWithDelay( unit ); // Close the bridge only after the creature lands. if ( bridge && bridge->AllowUp() ) { bridge->ActionUp(); } } _movingUnit = nullptr; unit.SwitchAnimation( Monster_Info::STATIC ); StringReplace( msg, "%{dst}", std::to_string( ( unit.GetHeadIndex() / ARENAW ) + 1 ) + ", " + std::to_string( ( unit.GetHeadIndex() % ARENAW ) + 1 ) ); status.SetMessage( msg, true ); assert( _currentUnit == nullptr && _movingUnit == nullptr && _flyingUnit == nullptr ); } void Battle::Interface::RedrawActionFly( Unit & unit, const Position & pos ) { const int32_t destIndex = pos.GetHead()->GetIndex(); const int32_t destTailIndex = unit.isWide() ? pos.GetTail()->GetIndex() : -1; // check if we're already there if ( unit.GetPosition().contains( destIndex ) ) { return; } // Reset the delay to wait till the next frame if is not already waiting. if ( !Game::isDelayNeeded( { Game::DelayType::CUSTOM_BATTLE_UNIT_MOVEMENT_DELAY } ) ) { Game::AnimateResetDelay( Game::DelayType::CUSTOM_BATTLE_UNIT_MOVEMENT_DELAY ); } Cursor::Get().SetThemes( Cursor::WAR_POINTER ); const fheroes2::Point destPos = unit.GetRectPosition().getPosition(); fheroes2::Point targetPos = Board::GetCell( destIndex )->GetPos().getPosition(); if ( unit.isWide() && targetPos.x > destPos.x ) { targetPos.x -= CELLW; // this is needed to avoid extra cell shifting upon landing when we move to right side } std::string msg = _( "Moved %{monster}: from [%{src}] to [%{dst}]." ); StringReplaceWithLowercase( msg, "%{monster}", unit.GetName() ); StringReplace( msg, "%{src}", std::to_string( ( unit.GetHeadIndex() / ARENAW ) + 1 ) + ", " + std::to_string( ( unit.GetHeadIndex() % ARENAW ) + 1 ) ); const uint32_t step = unit.animation.getFlightSpeed(); uint32_t frameDelay = Game::ApplyBattleSpeed( unit.animation.getMoveSpeed() ); if ( unit.Modes( SP_HASTE ) && frameDelay > 1 ) { frameDelay = frameDelay * 8 / 10; // 20% faster } else if ( unit.Modes( SP_SLOW ) ) { frameDelay = frameDelay * 12 / 10; // 20% slower } // Set the delay between movement animation frames. This delay will be used for all types of movement animations. unit.SwitchAnimation( Monster_Info::MOVING ); Game::setCustomUnitMovementDelay( frameDelay / unit.animation.animationLength() ); const std::vector points = GetEuclideanLine( destPos, targetPos, step ); std::vector::const_iterator currentPoint = points.begin(); Bridge * bridge = Arena::GetBridge(); // Lower the bridge if the unit needs to land on it if ( bridge && ( bridge->NeedDown( unit, destIndex ) || ( unit.isWide() && bridge->NeedDown( unit, destTailIndex ) ) ) ) { bridge->ActionDown(); } assert( _movingUnit == nullptr && _flyingUnit == nullptr ); // Jump up _currentUnit = nullptr; _movingUnit = &unit; _movingPos = currentPoint != points.end() ? *currentPoint : destPos; // For creatures, that has no 'FLY_UP' animation, like Ghosts, we check if the animation was correctly set. if ( unit.SwitchAnimation( Monster_Info::FLY_UP ) ) { AudioManager::PlaySound( unit.M82Tkof() ); AnimateUnitWithDelay( unit ); } _movingUnit = nullptr; _flyingUnit = &unit; _flyingPos = _movingPos; if ( currentPoint != points.end() ) { ++currentPoint; } // For Battle speeds 9 and 10 we play one sound at a time without any simultaneous playbacks // because on these speeds the unit will end its movement before the sound is finished. const bool playSoundBySound = Settings::Get().BattleSpeed() > 8; int soundStatus = -1; const int flySoundId = unit.M82Move(); unit.SwitchAnimation( Monster_Info::MOVING ); while ( currentPoint != points.end() ) { _movingPos = *currentPoint; if ( playSoundBySound ) { // Start unit move sound only if it is not already playing. if ( soundStatus < 0 || !Mixer::isPlaying( soundStatus ) ) { soundStatus = AudioManager::PlaySound( flySoundId ); } } else { AudioManager::PlaySound( flySoundId ); } unit.animation.restartAnimation(); AnimateUnitWithDelay( unit ); _flyingPos = _movingPos; ++currentPoint; } unit.SetPosition( destIndex ); // Landing _flyingUnit = nullptr; _movingUnit = &unit; _movingPos = targetPos; // IMPORTANT: do not combine into vector animations with the STATIC at the end: the game could randomly switch it to IDLE this way. unit.SwitchAnimation( { Monster_Info::FLY_LAND, Monster_Info::STAND_STILL } ); AudioManager::PlaySound( unit.M82Land() ); AnimateUnitWithDelay( unit ); unit.SwitchAnimation( Monster_Info::STATIC ); _movingUnit = nullptr; // Raise the bridge if possible after the unit has completed its movement if ( bridge && bridge->AllowUp() ) { bridge->ActionUp(); } StringReplace( msg, "%{dst}", std::to_string( ( unit.GetHeadIndex() / ARENAW ) + 1 ) + ", " + std::to_string( ( unit.GetHeadIndex() % ARENAW ) + 1 ) ); status.SetMessage( msg, true ); assert( _currentUnit == nullptr && _movingUnit == nullptr && _flyingUnit == nullptr ); } void Battle::Interface::RedrawActionResistSpell( const Unit & target, const bool playSound ) { if ( playSound ) { AudioManager::PlaySound( M82::RSBRYFZL ); } std::string str( _( "The %{name} resist the spell!" ) ); StringReplaceWithLowercase( str, "%{name}", target.GetName() ); status.SetMessage( str, true ); status.SetMessage( "", false ); } void Battle::Interface::RedrawActionSpellCastStatus( const Spell & spell, int32_t dst, const std::string & name, const TargetsInfo & targets ) { const Unit * target = !targets.empty() ? targets.front().defender : nullptr; std::string msg; if ( target && ( target->GetHeadIndex() == dst || ( target->isWide() && target->GetTailIndex() == dst ) ) ) { msg = _( "%{name} casts %{spell} on the %{troop}." ); StringReplaceWithLowercase( msg, "%{troop}", target->GetName() ); } else { msg = _( "%{name} casts %{spell}." ); } if ( !msg.empty() ) { StringReplace( msg, "%{name}", name ); StringReplace( msg, "%{spell}", spell.GetName() ); status.SetMessage( msg, true ); status.SetMessage( "", false ); } } void Battle::Interface::RedrawActionSpellCastPart1( const Spell & spell, int32_t dst, const HeroBase * caster, const TargetsInfo & targets ) { // Reset the idle animation delay timer to prevent the target unit from starting the idle animation. for ( const TargetInfo & spellTarget : targets ) { spellTarget.defender->checkIdleDelay(); } Unit * target = !targets.empty() ? targets.front().defender : nullptr; const bool isMassSpell = spell.isApplyWithoutFocusObject(); bool isCastDown = false; OpponentSprite * opponent = nullptr; // set spell cast animation if ( caster ) { const bool isLeftOpponent = caster->GetColor() == arena.GetArmy1Color(); opponent = isLeftOpponent ? _opponent1.get() : _opponent2.get(); if ( opponent != nullptr ) { if ( isMassSpell ) { opponent->SetAnimation( OP_CAST_MASS ); } else { // The cast down is applied below the 2rd battlefield row (count is started from 0) // and for the (rowNumber - 2) columns starting from the side of the hero. isCastDown = isLeftOpponent ? ( ( dst % 11 ) < dst / 11 - 2 ) : ( ( 10 - ( dst % 11 ) ) < dst / 11 - 2 ); opponent->SetAnimation( isCastDown ? OP_CAST_DOWN : OP_CAST_UP ); } AnimateOpponents( opponent ); } } // without object switch ( spell.GetID() ) { case Spell::FIREBALL: RedrawTargetsWithFrameAnimation( dst, targets, ICN::FIREBALL, M82::FromSpell( spell.GetID() ) ); break; case Spell::FIREBLAST: RedrawTargetsWithFrameAnimation( dst, targets, ICN::FIREBAL2, M82::FromSpell( spell.GetID() ) ); break; case Spell::METEORSHOWER: RedrawTargetsWithFrameAnimation( dst, targets, ICN::METEOR, M82::FromSpell( spell.GetID() ), 1 ); break; case Spell::COLDRING: RedrawActionColdRingSpell( dst, targets ); break; case Spell::MASSSHIELD: RedrawTargetsWithFrameAnimation( targets, ICN::SHIELD, M82::FromSpell( spell.GetID() ), false ); break; case Spell::MASSCURE: RedrawTargetsWithFrameAnimation( targets, ICN::MAGIC01, M82::FromSpell( spell.GetID() ), false ); break; case Spell::MASSHASTE: RedrawTargetsWithFrameAnimation( targets, ICN::HASTE, M82::FromSpell( spell.GetID() ), false ); break; case Spell::MASSSLOW: RedrawTargetsWithFrameAnimation( targets, ICN::MAGIC02, M82::FromSpell( spell.GetID() ), false ); break; case Spell::MASSBLESS: RedrawTargetsWithFrameAnimation( targets, ICN::BLESS, M82::FromSpell( spell.GetID() ), false ); break; case Spell::MASSCURSE: RedrawTargetsWithFrameAnimation( targets, ICN::CURSE, M82::FromSpell( spell.GetID() ), false ); break; case Spell::MASSDISPEL: RedrawTargetsWithFrameAnimation( targets, ICN::MAGIC07, M82::FromSpell( spell.GetID() ), false ); break; case Spell::DEATHRIPPLE: RedrawActionDeathWaveSpell( 7 ); break; case Spell::DEATHWAVE: RedrawActionDeathWaveSpell( 14 ); break; case Spell::HOLYWORD: RedrawActionHolyShoutSpell( 16 ); break; case Spell::HOLYSHOUT: RedrawActionHolyShoutSpell( 24 ); break; case Spell::ELEMENTALSTORM: RedrawActionElementalStormSpell( targets ); break; case Spell::ARMAGEDDON: RedrawActionArmageddonSpell(); // hit everything break; default: break; } // with object if ( target ) { if ( spell.isResurrect() ) RedrawActionResurrectSpell( *target, spell ); else switch ( spell.GetID() ) { // simple spell animation case Spell::BLESS: RedrawTroopWithFrameAnimation( *target, ICN::BLESS, M82::FromSpell( spell.GetID() ), NONE ); break; case Spell::BLIND: RedrawTroopWithFrameAnimation( *target, ICN::BLIND, M82::FromSpell( spell.GetID() ), NONE ); break; case Spell::CURE: RedrawTroopWithFrameAnimation( *target, ICN::MAGIC01, M82::FromSpell( spell.GetID() ), NONE ); break; case Spell::SLOW: RedrawTroopWithFrameAnimation( *target, ICN::MAGIC02, M82::FromSpell( spell.GetID() ), NONE ); break; case Spell::SHIELD: RedrawTroopWithFrameAnimation( *target, ICN::SHIELD, M82::FromSpell( spell.GetID() ), NONE ); break; case Spell::HASTE: RedrawTroopWithFrameAnimation( *target, ICN::HASTE, M82::FromSpell( spell.GetID() ), NONE ); break; case Spell::CURSE: RedrawTroopWithFrameAnimation( *target, ICN::CURSE, M82::FromSpell( spell.GetID() ), NONE ); break; case Spell::ANTIMAGIC: RedrawTroopWithFrameAnimation( *target, ICN::MAGIC06, M82::FromSpell( spell.GetID() ), NONE ); break; case Spell::DISPEL: RedrawTroopWithFrameAnimation( *target, ICN::MAGIC07, M82::FromSpell( spell.GetID() ), NONE ); break; case Spell::STONESKIN: RedrawTroopWithFrameAnimation( *target, ICN::STONSKIN, M82::FromSpell( spell.GetID() ), NONE ); break; case Spell::STEELSKIN: RedrawTroopWithFrameAnimation( *target, ICN::STELSKIN, M82::FromSpell( spell.GetID() ), NONE ); break; case Spell::PARALYZE: RedrawTroopWithFrameAnimation( *target, ICN::PARALYZE, M82::FromSpell( spell.GetID() ), NONE ); break; case Spell::HYPNOTIZE: RedrawTroopWithFrameAnimation( *target, ICN::HYPNOTIZ, M82::FromSpell( spell.GetID() ), NONE ); break; case Spell::DRAGONSLAYER: RedrawTroopWithFrameAnimation( *target, ICN::DRAGSLAY, M82::FromSpell( spell.GetID() ), NONE ); break; case Spell::BERSERKER: RedrawTroopWithFrameAnimation( *target, ICN::BERZERK, M82::FromSpell( spell.GetID() ), NONE ); break; // uniq spell animation case Spell::LIGHTNINGBOLT: RedrawActionLightningBoltSpell( *target ); break; case Spell::CHAINLIGHTNING: RedrawActionChainLightningSpell( targets ); break; case Spell::ARROW: RedrawActionArrowSpell( *target ); break; case Spell::COLDRAY: RedrawActionColdRaySpell( *target ); break; case Spell::DISRUPTINGRAY: RedrawActionDisruptingRaySpell( *target ); break; case Spell::BLOODLUST: RedrawActionBloodLustSpell( *target ); break; case Spell::PETRIFY: RedrawActionStoneSpell( *target ); break; default: break; } } if ( opponent != nullptr ) { if ( isMassSpell ) { opponent->SetAnimation( OP_CAST_MASS_RETURN ); } else { opponent->SetAnimation( isCastDown ? OP_CAST_DOWN_RETURN : OP_CAST_UP_RETURN ); } AnimateOpponents( opponent ); // Return to the static animation of hero. opponent->SetAnimation( OP_STATIC ); } } void Battle::Interface::RedrawActionSpellCastPart2( const Spell & spell, const TargetsInfo & targets ) { if ( spell.isDamage() ) { uint32_t killed = 0; uint32_t totalDamage = 0; uint32_t maximumDamage = 0; uint32_t damagedMonsters = 0; for ( const TargetInfo & target : targets ) { if ( !target.defender->isModes( CAP_MIRRORIMAGE ) ) { killed += target.killed; ++damagedMonsters; totalDamage += target.damage; if ( maximumDamage < target.damage ) { maximumDamage = target.damage; } } } // targets damage animation switch ( spell.GetID() ) { // For some spells the damage animation is done during the flame animation after the spell animation. case Spell::DEATHRIPPLE: case Spell::DEATHWAVE: RedrawTargetsWithFrameAnimation( targets, ICN::REDDEATH, M82::UNKNOWN, true ); break; case Spell::HOLYWORD: case Spell::HOLYSHOUT: RedrawTargetsWithFrameAnimation( targets, ICN::MAGIC08, M82::UNKNOWN, true ); break; default: RedrawActionWincesKills( targets ); break; } if ( totalDamage > 0 ) { assert( damagedMonsters > 0 ); std::string msg; if ( spell.isUndeadOnly() ) { if ( damagedMonsters == 1 ) { msg = _( "The %{spell} does %{damage} damage to one undead creature." ); StringReplace( msg, "%{spell}", spell.GetName() ); StringReplace( msg, "%{damage}", totalDamage ); status.SetMessage( msg, true ); if ( killed > 0 ) { msg = _n( "1 creature perishes.", "%{count} creatures perish.", killed ); StringReplace( msg, "%{count}", killed ); status.SetMessage( msg, true ); } } else { msg = _( "The %{spell} does %{damage} damage to all undead creatures." ); StringReplace( msg, "%{spell}", spell.GetName() ); StringReplace( msg, "%{damage}", maximumDamage ); status.SetMessage( msg, true ); if ( killed > 0 ) { msg = _( "The %{spell} does %{damage} damage, %{count} creatures perish." ); StringReplace( msg, "%{count}", killed ); } else { msg = _( "The %{spell} does %{damage} damage." ); } StringReplace( msg, "%{spell}", spell.GetName() ); StringReplace( msg, "%{damage}", totalDamage ); status.SetMessage( msg, true ); } } else if ( spell.isAliveOnly() ) { if ( damagedMonsters == 1 ) { msg = _( "The %{spell} does %{damage} damage to one living creature." ); StringReplace( msg, "%{spell}", spell.GetName() ); StringReplace( msg, "%{damage}", totalDamage ); status.SetMessage( msg, true ); if ( killed > 0 ) { msg = _n( "1 creature perishes.", "%{count} creatures perish.", killed ); StringReplace( msg, "%{count}", killed ); status.SetMessage( msg, true ); } } else { msg = _( "The %{spell} does %{damage} damage to all living creatures." ); StringReplace( msg, "%{spell}", spell.GetName() ); StringReplace( msg, "%{damage}", maximumDamage ); status.SetMessage( msg, true ); if ( killed > 0 ) { msg = _( "The %{spell} does %{damage} damage, %{count} creatures perish." ); StringReplace( msg, "%{count}", killed ); } else { msg = _( "The %{spell} does %{damage} damage." ); } StringReplace( msg, "%{spell}", spell.GetName() ); StringReplace( msg, "%{damage}", totalDamage ); status.SetMessage( msg, true ); } } else { msg = _( "The %{spell} does %{damage} damage." ); StringReplace( msg, "%{spell}", spell.GetName() ); StringReplace( msg, "%{damage}", totalDamage ); status.SetMessage( msg, true ); if ( killed > 0 ) { msg = _n( "1 creature perishes.", "%{count} creatures perish.", killed ); StringReplace( msg, "%{count}", killed ); status.SetMessage( msg, true ); } } } } status.SetMessage( " ", false ); // TODO: remove this temporary assertion assert( _movingUnit == nullptr ); } void Battle::Interface::RedrawActionMonsterSpellCastStatus( const Spell & spell, const Unit & attacker, const TargetInfo & target ) { std::string msg; switch ( spell.GetID() ) { case Spell::BLIND: msg = _( "The %{attacker}' attack blinds the %{target}!" ); break; case Spell::PETRIFY: msg = _( "The %{attacker}' gaze turns the %{target} to stone!" ); break; case Spell::CURSE: msg = _( "The %{attacker}' curse falls upon the %{target}!" ); break; case Spell::PARALYZE: msg = _( "The %{target} are paralyzed by the %{attacker}!" ); break; case Spell::DISPEL: msg = _( "The %{attacker} dispel all good spells on your %{target}!" ); break; default: // Did you add a new monster spell casting ability? Add the logic above! assert( 0 ); msg = _( "The %{attacker} cast %{spell} on %{target}!" ); StringReplace( msg, "%{spell}", spell.GetName() ); break; } StringReplaceWithLowercase( msg, "%{attacker}", attacker.GetMultiName() ); StringReplaceWithLowercase( msg, "%{target}", target.defender->GetName() ); status.SetMessage( msg, true ); status.SetMessage( "", false ); } void Battle::Interface::RedrawActionLuck( const Unit & unit ) { // Reset the delay to wait till the next frame if is not already waiting. if ( !Game::isDelayNeeded( { Game::DelayType::BATTLE_MISSILE_DELAY } ) ) { Game::AnimateResetDelay( Game::DelayType::BATTLE_MISSILE_DELAY ); } LocalEvent & le = LocalEvent::Get(); const bool isGoodLuck = unit.Modes( LUCK_GOOD ); const fheroes2::Rect & pos = unit.GetRectPosition(); std::string msg = isGoodLuck ? _( "Good luck shines on the %{attacker}." ) : _( "Bad luck descends on the %{attacker}." ); StringReplaceWithLowercase( msg, "%{attacker}", unit.GetName() ); status.SetMessage( msg, true ); Cursor::Get().SetThemes( Cursor::WAR_POINTER ); if ( isGoodLuck ) { const fheroes2::Rect & battleArea = border.GetArea(); const fheroes2::Point rainbowDescendPoint( pos.x + pos.width / 2, pos.y - pos.height / 2 ); // If the creature is low on the battleboard - the rainbow will be from the top (in the original game the threshold is about 140 pixels). const bool isVerticalRainbow = ( rainbowDescendPoint.y > 140 ); // Set the rainbow animation direction to match the army side. // Also, if the creature is under effect of the Berserker spell (or similar), then check its original color. bool isRainbowFromRight = ( unit.GetCurrentColor() < 0 ) ? ( unit.GetColor() == arena.GetArmy2Color() ) : ( unit.GetCurrentColor() == arena.GetArmy2Color() ); // The distance from the right or left battlefield border to the 'lucky' creature in the direction from the beginning of the animation to its end. const int32_t borderDistance = isRainbowFromRight ? battleArea.width - rainbowDescendPoint.x : rainbowDescendPoint.x; // The rainbow thickness in pixels: it must be 15 pixels to match the rainbow thickness from original sprite 'ICN::EXPMRL'. const int32_t rainbowThickness = 15; // Declare rainbow generation parameters and set default values: // Rainbow arc parameters for: y = (1-pow2ratio)*k1*(x-x0)^pow1+pow2ratio*k2*(x-x0)^pow2. // Parameters pow3, pow4, pow4ratio are the same as pow1, pow2, pow2ratio, but for the second part of the arc. const int32_t pow1{ 2 }; int32_t pow2{ 10 }; double pow2ratio{ 0.16 }; const int32_t pow3{ 2 }; int32_t pow4{ 2 }; double pow4ratio{ 0.77 }; // The distance from the start to the end of the rainbow in direction of animation (in pixels). int32_t rainbowLength{ rainbowDescendPoint.y }; // The distance from the start to the end (the 'lucky' creature) of the rainbow orthogonal to the direction of animation (in pixels). int32_t rainbowAscend{ 75 }; // The distance from the top to the end (the 'lucky' creature) of the rainbow orthogonal to the direction of animation (in pixels). int32_t rainbowDescend{ 10 }; // The coordinate where the rainbow arc changes its direction. int32_t rainbowTop{ 50 }; // Rainbow image offset from battlefield zero coordinates to "fall" onto the 'lucky' creature. int32_t drawOffset{ 10 }; // Set rainbow generation parameters. if ( isVerticalRainbow ) { rainbowAscend = static_cast( 0.4845 * rainbowLength + 156.2 ); // If the rainbow doesn't fit on the screen, then change its horizontal direction. if ( ( borderDistance + rainbowThickness / 2 ) < rainbowAscend ) { isRainbowFromRight = !isRainbowFromRight; } rainbowDescend = std::max( 1, static_cast( 0.0342 * rainbowLength - 4.868 ) ); rainbowTop = static_cast( 0.8524 * rainbowLength + 17.7 ); drawOffset = isRainbowFromRight ? ( rainbowDescendPoint.x - rainbowDescend - rainbowThickness / 2 ) : ( rainbowDescendPoint.x - rainbowDescend - rainbowAscend ); } else { pow2 = 0; pow4 = 5; pow2ratio = 0.0; pow4ratio = 0.5; rainbowLength = borderDistance; rainbowDescend = std::max( 1, static_cast( 0.1233 * rainbowLength + 0.7555 ) ); rainbowTop = static_cast( 0.6498 * rainbowLength + 11.167 ); drawOffset = std::max( 10, rainbowDescendPoint.y - rainbowDescend ); } const fheroes2::Size rainbowArcBegin( rainbowTop, rainbowDescend + rainbowAscend ); const fheroes2::Size rainbowArcEnd( rainbowLength - rainbowTop, rainbowDescend ); std::vector rainbowArc; GetHalfArc( rainbowArc, -rainbowArcBegin.width, rainbowArcBegin.height, pow1, pow2, pow2ratio ); GetHalfArc( rainbowArc, rainbowArcEnd.width, rainbowArcEnd.height, pow3, pow4, pow4ratio ); const fheroes2::Image luckSprite = DrawRainbow( rainbowArc, rainbowThickness, isVerticalRainbow, isRainbowFromRight ); const int32_t rainbowDrawSteps = 30; // Rainbow animation draw step (in original game it is random and about 7-11 pixels). // We set the constant animation time for all rainbows: rainbowLength/30 fits the rainbow sound duration on '1' speed. const double drawStep = static_cast( rainbowLength ) / rainbowDrawSteps; // Don't waste time waiting for Good Luck sound if the game sounds are turned off const bool soundOn = Settings::Get().SoundVolume() > 0; if ( soundOn ) { AudioManager::PlaySound( M82::GOODLUCK ); } // If sound is turned off we still wait for the GOODLUCK sound to over but no more the twice the rainbow animation time. // So we start counting steps from '-rainbowDrawSteps' to 'rainbowDrawSteps'. int32_t step = -rainbowDrawSteps; double x = 0; while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_MISSILE_DELAY } ) ) && ( ( ( soundOn || step < rainbowDrawSteps ) && Mixer::isPlaying( -1 ) ) || x < rainbowLength ) ) { CheckGlobalEvents( le ); if ( Game::validateAnimationDelay( Game::BATTLE_MISSILE_DELAY ) ) { // Reset all units idle animation delay during rainbow animation not to start new idle animations. // This does not affect Zombies, Genies, Medusas and Ghosts as they have permanent idle animations. ResetIdleTroopAnimation(); RedrawPartialStart(); if ( x < rainbowLength ) { x += drawStep; } const int32_t drawWidth = x > rainbowLength ? rainbowLength : static_cast( x ); // For different rainbow types use appropriate animation direction. if ( isVerticalRainbow ) { fheroes2::Blit( luckSprite, 0, 0, _mainSurface, drawOffset, 0, luckSprite.width(), drawWidth ); } else { if ( isRainbowFromRight ) { fheroes2::Blit( luckSprite, rainbowLength - drawWidth, 0, _mainSurface, battleArea.width - drawWidth, drawOffset, drawWidth, luckSprite.height() ); } else { fheroes2::Blit( luckSprite, 0, 0, _mainSurface, 0, drawOffset, drawWidth, luckSprite.height() ); } } RedrawPartialFinish(); ++step; } } } else { const int maxHeight = fheroes2::AGG::GetAbsoluteICNHeight( ICN::CLOUDLUK ); int y = pos.y + pos.height + cellYOffset; // move drawing position if it will clip outside of the battle window if ( y - maxHeight < 0 ) y = maxHeight; AudioManager::PlaySound( M82::BADLUCK ); int frameId = 0; while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_MISSILE_DELAY } ) ) && Mixer::isPlaying( -1 ) ) { CheckGlobalEvents( le ); if ( frameId < 8 && Game::validateAnimationDelay( Game::BATTLE_MISSILE_DELAY ) ) { RedrawPartialStart(); const fheroes2::Sprite & luckSprite = fheroes2::AGG::GetICN( ICN::CLOUDLUK, frameId ); fheroes2::Blit( luckSprite, _mainSurface, pos.x + pos.width / 2 + luckSprite.x(), y + luckSprite.y() ); RedrawPartialFinish(); ++frameId; } } } } void Battle::Interface::RedrawActionMorale( Unit & unit, const bool isGoodMorale ) { std::string msg; // Reset the idle animation delay timer to prevent the unit from starting the idle animation. unit.checkIdleDelay(); if ( isGoodMorale ) { msg = _( "High morale enables the %{monster} to attack again." ); StringReplaceWithLowercase( msg, "%{monster}", unit.GetName() ); status.SetMessage( msg, true ); RedrawTroopWithFrameAnimation( unit, ICN::MORALEG, M82::GOODMRLE, NONE ); } else { msg = _( "Low morale causes the %{monster} to freeze in panic." ); StringReplaceWithLowercase( msg, "%{monster}", unit.GetName() ); status.SetMessage( msg, true ); RedrawTroopWithFrameAnimation( unit, ICN::MORALEB, M82::BADMRLE, WINCE ); } } void Battle::Interface::RedrawActionTowerPart1( const Tower & tower, const Unit & defender ) { Cursor::Get().SetThemes( Cursor::WAR_POINTER ); _currentUnit = nullptr; const fheroes2::Point missileStart = tower.GetPortPosition(); const fheroes2::Point targetPos = defender.GetCenterPoint(); const double angle = GetAngle( missileStart, targetPos ); AudioManager::PlaySound( M82::KEEPSHOT ); // Keep missile == Orc missile RedrawMissileAnimation( missileStart, targetPos, angle, Monster::ORC ); } void Battle::Interface::RedrawActionTowerPart2( const Tower & tower, const TargetInfo & target ) { TargetsInfo targets; targets.push_back( target ); const bool isMirror = target.defender->isModes( CAP_MIRRORIMAGE ); // targets damage animation RedrawActionWincesKills( targets ); // draw status for first defender std::string msg = _( "%{tower} does %{damage} damage." ); StringReplace( msg, "%{tower}", tower.GetName() ); StringReplace( msg, "%{damage}", target.damage ); if ( target.killed ) { msg += ' '; msg.append( _n( "1 %{defender} perishes.", "%{count} %{defender} perish.", target.killed ) ); StringReplace( msg, "%{count}", target.killed ); StringReplaceWithLowercase( msg, "%{defender}", target.defender->GetPluralName( target.killed ) ); } if ( !isMirror ) { status.SetMessage( msg, true ); status.SetMessage( "", false ); } // TODO: remove this temporary assertion assert( _movingUnit == nullptr ); } void Battle::Interface::RedrawActionCatapultPart1( const CastleDefenseElement catapultTarget, const bool isHit ) { // Reset the delay before rendering the first frame of catapult animation. Game::AnimateResetDelay( Game::DelayType::BATTLE_CATAPULT_DELAY ); LocalEvent & le = LocalEvent::Get(); const fheroes2::Rect & area = GetArea(); AudioManager::PlaySound( M82::CATSND00 ); // catapult animation while ( le.HandleEvents( false ) && catapult_frame < 5 ) { CheckGlobalEvents( le ); if ( Game::validateAnimationDelay( Game::BATTLE_CATAPULT_DELAY ) ) { Redraw(); ++catapult_frame; } } // Reset the delay before rendering the first frame of catapult boulder animation. Game::AnimateResetDelay( Game::DelayType::BATTLE_CATAPULT_BOULDER_DELAY ); // boulder animation fheroes2::Point pt1( 30, 290 ); fheroes2::Point pt2 = Catapult::GetTargetPosition( catapultTarget, isHit ); const int32_t boulderArcStep = ( pt2.x - pt1.x ) / 30; // set the projectile arc height for each castle target and a formula for an unknown target int32_t boulderArcHeight; switch ( catapultTarget ) { case CastleDefenseElement::WALL1: boulderArcHeight = 220; break; case CastleDefenseElement::WALL2: case CastleDefenseElement::BRIDGE: boulderArcHeight = 216; break; case CastleDefenseElement::WALL3: boulderArcHeight = 204; break; case CastleDefenseElement::WALL4: boulderArcHeight = 208; break; case CastleDefenseElement::TOWER1: case CastleDefenseElement::TOWER2: boulderArcHeight = 206; break; case CastleDefenseElement::CENTRAL_TOWER: boulderArcHeight = 290; break; default: boulderArcHeight = static_cast( std::lround( 0.55 * getDistance( pt1, pt2 ) ) ); } pt1.x += area.x; pt2.x += area.x; pt1.y += area.y; pt2.y += area.y; const std::vector points = GetArcPoints( pt1, pt2, boulderArcHeight, boulderArcStep ); std::vector::const_iterator pnt = points.begin(); uint32_t boulderFrameId = 0; const uint32_t boulderFramesCount = fheroes2::AGG::GetICNCount( ICN::BOULDER ); while ( le.HandleEvents( false ) && pnt != points.end() ) { CheckGlobalEvents( le ); if ( Game::validateAnimationDelay( Game::BATTLE_CATAPULT_BOULDER_DELAY ) ) { if ( catapult_frame < 9 ) ++catapult_frame; RedrawPartialStart(); fheroes2::Blit( fheroes2::AGG::GetICN( ICN::BOULDER, boulderFrameId ), _mainSurface, pnt->x, pnt->y ); RedrawPartialFinish(); ++pnt; ++boulderFrameId; if ( boulderFrameId >= boulderFramesCount ) { boulderFrameId = 0; } } } // Reset the delay before rendering the catapult cloud. Game::AnimateResetDelay( Game::DelayType::BATTLE_CATAPULT_CLOUD_DELAY ); // draw cloud const int32_t icn = isHit ? ICN::LICHCLOD : ICN::SMALCLOD; uint32_t frame = 0; // If the building is hit, end the animation on the 5th frame to change the building state (when the smoke cloud is largest). uint32_t maxFrame = isHit ? castleBuildingDestroyFrame : fheroes2::AGG::GetICNCount( icn ); const bool isBridgeDestroyed = isHit && ( catapultTarget == CastleDefenseElement::BRIDGE ); // If the bridge is destroyed - prepare parameters for the second smoke cloud. if ( isBridgeDestroyed ) { pt1 = pt2 + bridgeDestroySmokeOffset; // Increase maxFrame to get bigger second smoke cloud. maxFrame = bridgeDestroyFrame; } AudioManager::PlaySound( M82::CATSND02 ); while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_CATAPULT_CLOUD_DELAY } ) ) && frame < maxFrame ) { CheckGlobalEvents( le ); if ( Game::validateAnimationDelay( Game::BATTLE_CATAPULT_CLOUD_DELAY ) ) { RedrawPartialStart(); // Start animation of the second smoke cloud only for the bridge and after 2 frames of the first smoke cloud. if ( isBridgeDestroyed && frame >= bridgeDestroySmokeDelay ) { const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( icn, frame - bridgeDestroySmokeDelay ); fheroes2::Blit( sprite, _mainSurface, pt1.x + sprite.x(), pt1.y + sprite.y() ); } const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( icn, frame ); fheroes2::Blit( sprite, _mainSurface, pt2.x + sprite.x(), pt2.y + sprite.y() ); RedrawPartialFinish(); ++frame; } } if ( !isHit ) { catapult_frame = 0; } } void Battle::Interface::RedrawActionCatapultPart2( const CastleDefenseElement catapultTarget ) { // Finish the smoke cloud animation after the building's state has changed after the hit and it is drawn as demolished. const fheroes2::Point pt1 = Catapult::GetTargetPosition( catapultTarget, true ) + GetArea().getPosition(); fheroes2::Point pt2; // Continue the smoke cloud animation from the 6th frame. const int32_t icnId = ICN::LICHCLOD; uint32_t frame = castleBuildingDestroyFrame; const uint32_t maxFrame = fheroes2::AGG::GetICNCount( icnId ); uint32_t maxAnimationFrame = maxFrame; const bool isBridgeDestroyed = ( catapultTarget == CastleDefenseElement::BRIDGE ); // If the bridge is destroyed - prepare parameters for the second smoke cloud. if ( isBridgeDestroyed ) { pt2 = pt1 + bridgeDestroySmokeOffset; // Increase maxAnimationFrame to finish the second smoke animation. maxAnimationFrame += bridgeDestroySmokeDelay; // Bridge smoke animation should continue from the 7th frame. frame = bridgeDestroyFrame; } LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_CATAPULT_CLOUD_DELAY } ) ) && frame < maxAnimationFrame ) { CheckGlobalEvents( le ); if ( Game::validateAnimationDelay( Game::BATTLE_CATAPULT_CLOUD_DELAY ) ) { RedrawPartialStart(); // Draw animation of the second smoke cloud only for the bridge. if ( isBridgeDestroyed ) { const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( icnId, frame - bridgeDestroySmokeDelay ); fheroes2::Blit( sprite, _mainSurface, pt2.x + sprite.x(), pt2.y + sprite.y() ); } // Don't draw the smoke cloud after its animation has ended (in case the second smoke cloud is still animating). if ( frame <= maxFrame ) { const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( icnId, frame ); fheroes2::Blit( sprite, _mainSurface, pt1.x + sprite.x(), pt1.y + sprite.y() ); } RedrawPartialFinish(); ++frame; } } catapult_frame = 0; } void Battle::Interface::RedrawActionArrowSpell( const Unit & target ) { const HeroBase * caster = arena.GetCurrentCommander(); if ( caster ) { const fheroes2::Point missileStart = caster == _opponent1->GetHero() ? _opponent1->GetCastPosition() : _opponent2->GetCastPosition(); const fheroes2::Point targetPos = target.GetCenterPoint(); const double angle = GetAngle( missileStart, targetPos ); Cursor::Get().SetThemes( Cursor::WAR_POINTER ); AudioManager::PlaySound( M82::MAGCAROW ); // Magic arrow == Archer missile RedrawMissileAnimation( missileStart, targetPos, angle, Monster::ARCHER ); } } void Battle::Interface::RedrawActionTeleportSpell( Unit & target, const int32_t dst ) { LocalEvent & le = LocalEvent::Get(); Cursor::Get().SetThemes( Cursor::WAR_POINTER ); uint8_t currentAlpha = target.GetCustomAlpha(); const uint8_t alphaStep = 15; AudioManager::PlaySound( M82::TELPTOUT ); Game::passAnimationDelay( Game::BATTLE_SPELL_DELAY ); while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_SPELL_DELAY } ) ) && Mixer::isPlaying( -1 ) ) { CheckGlobalEvents( le ); if ( currentAlpha >= alphaStep && Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) { currentAlpha -= alphaStep; target.SetCustomAlpha( currentAlpha ); Redraw(); } } currentAlpha = 0; Redraw(); target.SetPosition( dst ); AudioManager::PlaySound( M82::TELPTIN ); while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_SPELL_DELAY } ) ) && Mixer::isPlaying( -1 ) ) { CheckGlobalEvents( le ); if ( currentAlpha <= ( 255 - alphaStep ) && Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) { currentAlpha += alphaStep; target.SetCustomAlpha( currentAlpha ); Redraw(); } } target.SetCustomAlpha( 255 ); } void Battle::Interface::RedrawActionSummonElementalSpell( Unit & target ) { LocalEvent & le = LocalEvent::Get(); Cursor::Get().SetThemes( Cursor::WAR_POINTER ); uint8_t currentAlpha = 0; const uint8_t alphaStep = 20; AudioManager::PlaySound( M82::SUMNELM ); Game::passAnimationDelay( Game::BATTLE_SPELL_DELAY ); while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_SPELL_DELAY } ) ) && currentAlpha <= ( 255 - alphaStep ) ) { CheckGlobalEvents( le ); if ( Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) { currentAlpha += alphaStep; target.SetCustomAlpha( currentAlpha ); Redraw(); } } target.SetCustomAlpha( 255 ); } void Battle::Interface::RedrawActionMirrorImageSpell( const Unit & target, const Position & pos ) { LocalEvent & le = LocalEvent::Get(); fheroes2::Sprite sprite = fheroes2::AGG::GetICN( target.GetMonsterSprite(), target.GetFrame() ); fheroes2::ApplyPalette( sprite, PAL::GetPalette( PAL::PaletteType::MIRROR_IMAGE ) ); const fheroes2::Rect & rt1 = target.GetRectPosition(); const fheroes2::Rect & rt2 = pos.GetRect(); const std::vector points = GetLinePoints( rt1.getPosition(), rt2.getPosition(), 5 ); std::vector::const_iterator pnt = points.begin(); Cursor::Get().SetThemes( Cursor::WAR_POINTER ); AudioManager::PlaySound( M82::MIRRORIM ); Game::passAnimationDelay( Game::BATTLE_SPELL_DELAY ); while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_SPELL_DELAY } ) ) && pnt != points.end() ) { CheckGlobalEvents( le ); if ( Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) { const fheroes2::Point & sp = GetTroopPosition( target, sprite ); RedrawPartialStart(); fheroes2::Blit( sprite, _mainSurface, sp.x - rt1.x + ( *pnt ).x, sp.y - rt1.y + ( *pnt ).y, target.isReflect() ); RedrawPartialFinish(); ++pnt; } } status.SetMessage( _( "The mirror image is created." ), true ); } void Battle::Interface::RedrawLightningOnTargets( const std::vector & points, const fheroes2::Rect & drawRoi ) { if ( points.size() < 2 ) return; const fheroes2::Point roiOffset( drawRoi.x, drawRoi.y ); LocalEvent & le = LocalEvent::Get(); Cursor & cursor = Cursor::Get(); cursor.SetThemes( Cursor::WAR_POINTER ); AudioManager::PlaySound( points.size() > 2 ? M82::CHAINLTE : M82::LIGHTBLT ); for ( size_t i = 1; i < points.size(); ++i ) { const fheroes2::Point & startingPos = points[i - 1]; const fheroes2::Point & endPos = points[i]; const std::vector> & lightningBolt = GenerateLightning( startingPos + roiOffset, endPos + roiOffset ); fheroes2::Rect roi; const bool isHorizontalBolt = std::abs( startingPos.x - endPos.x ) > std::abs( startingPos.y - endPos.y ); const bool isForwardDirection = isHorizontalBolt ? ( endPos.x > startingPos.x ) : ( endPos.y > startingPos.y ); const int animationStep = 100; if ( isHorizontalBolt ) { roi.height = drawRoi.height; if ( isForwardDirection ) { roi.x = 0; roi.width = startingPos.x; } else { roi.x = startingPos.x; roi.width = drawRoi.width - startingPos.x; } } else { roi.width = drawRoi.width; if ( isForwardDirection ) { roi.y = 0; roi.height = startingPos.y; } else { roi.y = startingPos.y; roi.height = drawRoi.height - startingPos.y; } } while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_DISRUPTING_DELAY } ) ) && ( ( isHorizontalBolt && roi.width < drawRoi.width ) || ( !isHorizontalBolt && roi.height < drawRoi.height ) ) ) { if ( Game::validateAnimationDelay( Game::BATTLE_DISRUPTING_DELAY ) ) { if ( isHorizontalBolt ) { if ( isForwardDirection ) { roi.width += animationStep; } else { roi.width += animationStep; roi.x -= animationStep; } if ( roi.x < 0 ) roi.x = 0; if ( roi.width > drawRoi.width ) roi.width = drawRoi.width; } else { if ( isForwardDirection ) { roi.height += animationStep; } else { roi.height += animationStep; roi.y -= animationStep; } if ( roi.y < 0 ) roi.y = 0; if ( roi.height > drawRoi.height ) roi.height = drawRoi.height; } RedrawPartialStart(); RedrawLightning( lightningBolt, fheroes2::GetColorId( 0xff, 0xff, 0 ), _mainSurface, { roi.x + roiOffset.x, roi.y + roiOffset.y, roi.width, roi.height } ); fheroes2::ApplyPalette( _mainSurface, 7 ); RedrawPartialFinish(); } } } // small delay to display fully drawn lightning fheroes2::delayforMs( 100 ); uint32_t frame = 0; while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_DISRUPTING_DELAY } ) ) && frame < fheroes2::AGG::GetICNCount( ICN::SPARKS ) ) { CheckGlobalEvents( le ); if ( ( frame == 0 ) || Game::validateAnimationDelay( Game::BATTLE_DISRUPTING_DELAY ) ) { RedrawPartialStart(); const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::SPARKS, frame ); const int32_t spriteWidth = sprite.width(); for ( size_t i = 1; i < points.size(); ++i ) { const fheroes2::Point pt = points[i] - fheroes2::Point( spriteWidth / 2, 0 ) + roiOffset; fheroes2::Blit( sprite, _mainSurface, pt.x, pt.y ); } RedrawPartialFinish(); ++frame; } } } void Battle::Interface::RedrawActionLightningBoltSpell( const Unit & target ) { _currentUnit = nullptr; const fheroes2::Point startingPos = arena.GetCurrentCommander() == _opponent1->GetHero() ? _opponent1->GetCastPosition() : _opponent2->GetCastPosition(); const fheroes2::Rect & pos = target.GetRectPosition(); const fheroes2::Point endPos( pos.x + pos.width / 2, pos.y ); const std::vector points{ startingPos, endPos }; RedrawLightningOnTargets( points, _surfaceInnerArea ); } void Battle::Interface::RedrawActionChainLightningSpell( const TargetsInfo & targets ) { const fheroes2::Point startingPos = arena.GetCurrentCommander() == _opponent1->GetHero() ? _opponent1->GetCastPosition() : _opponent2->GetCastPosition(); std::vector points; points.reserve( size( targets ) + 1 ); points.push_back( startingPos ); for ( const TargetInfo & target : targets ) { const fheroes2::Rect & pos = target.defender->GetRectPosition(); points.emplace_back( pos.x + pos.width / 2, pos.y ); } RedrawLightningOnTargets( points, _surfaceInnerArea ); } void Battle::Interface::RedrawActionBloodLustSpell( const Unit & target ) { LocalEvent & le = LocalEvent::Get(); fheroes2::Sprite unitSprite = fheroes2::AGG::GetICN( target.GetMonsterSprite(), target.GetFrame() ); std::vector> originalPalette; if ( target.Modes( SP_STONE ) ) { originalPalette.push_back( PAL::GetPalette( PAL::PaletteType::GRAY ) ); } else if ( target.Modes( CAP_MIRRORIMAGE ) ) { originalPalette.push_back( PAL::GetPalette( PAL::PaletteType::MIRROR_IMAGE ) ); } if ( !originalPalette.empty() ) { for ( size_t i = 1; i < originalPalette.size(); ++i ) { originalPalette[0] = PAL::CombinePalettes( originalPalette[0], originalPalette[i] ); } fheroes2::ApplyPalette( unitSprite, originalPalette[0] ); } std::vector convert = PAL::GetPalette( PAL::PaletteType::RED ); if ( !originalPalette.empty() ) { convert = PAL::CombinePalettes( PAL::GetPalette( PAL::PaletteType::GRAY ), convert ); } fheroes2::Sprite bloodlustEffect( unitSprite ); fheroes2::ApplyPalette( bloodlustEffect, convert ); fheroes2::Sprite mixSprite( unitSprite ); Cursor::Get().SetThemes( Cursor::WAR_POINTER ); _currentUnit = ⌖ b_current_sprite = &mixSprite; const uint32_t bloodlustDelay = 1800 / 20; // duration is 1900ms AudioManager::PlaySound( M82::BLOODLUS ); uint32_t alpha = 0; uint32_t frame = 0; // Immediately indicate that the delay has passed to render first frame immediately. Game::passCustomAnimationDelay( bloodlustDelay ); // Make sure that the first run is passed immediately. assert( !Game::isCustomDelayNeeded( bloodlustDelay ) ); while ( le.HandleEvents( Game::isCustomDelayNeeded( bloodlustDelay ) ) && Mixer::isPlaying( -1 ) ) { CheckGlobalEvents( le ); if ( frame < 20 && Game::validateCustomAnimationDelay( bloodlustDelay ) ) { mixSprite = unitSprite; fheroes2::AlphaBlit( bloodlustEffect, mixSprite, static_cast( alpha ) ); Redraw(); alpha += ( frame < 10 ) ? 20 : -20; ++frame; } } _currentUnit = nullptr; b_current_sprite = nullptr; } void Battle::Interface::RedrawActionStoneSpell( const Unit & target ) { LocalEvent & le = LocalEvent::Get(); const fheroes2::Sprite & unitSprite = fheroes2::AGG::GetICN( target.GetMonsterSprite(), target.GetFrame() ); fheroes2::Sprite stoneEffect( unitSprite ); fheroes2::ApplyPalette( stoneEffect, PAL::GetPalette( PAL::PaletteType::GRAY ) ); fheroes2::Sprite mixSprite( unitSprite ); Cursor::Get().SetThemes( Cursor::WAR_POINTER ); _currentUnit = ⌖ b_current_sprite = &mixSprite; AudioManager::PlaySound( M82::PARALIZE ); uint32_t alpha = 0; uint32_t frame = 0; while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_SPELL_DELAY } ) ) && Mixer::isPlaying( -1 ) ) { CheckGlobalEvents( le ); if ( frame < 25 && Game::validateCustomAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) { mixSprite = fheroes2::Sprite( unitSprite ); fheroes2::AlphaBlit( stoneEffect, mixSprite, static_cast( alpha ) ); Redraw(); alpha += 10; ++frame; } } _currentUnit = nullptr; b_current_sprite = nullptr; } void Battle::Interface::RedrawActionResurrectSpell( Unit & target, const Spell & spell ) { if ( !target.isValid() ) { // Restore direction of the creature, since it could be killed when it was reflected. target.UpdateDirection(); Redraw(); target.IncreaseAnimFrame(); Game::passAnimationDelay( Game::BATTLE_SPELL_DELAY ); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_SPELL_DELAY } ) ) ) { CheckGlobalEvents( le ); if ( Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) { if ( target.isFinishAnimFrame() ) { // We have reached the end of animation. break; } Redraw(); target.IncreaseAnimFrame(); } } } AudioManager::PlaySound( M82::FromSpell( spell.GetID() ) ); RedrawTroopWithFrameAnimation( target, ICN::YINYANG, M82::UNKNOWN, target.GetHitPoints() == 0 ? RESURRECT : NONE ); } void Battle::Interface::RedrawActionColdRaySpell( Unit & target ) { RedrawRaySpell( target, ICN::COLDRAY, M82::COLDRAY, 18 ); RedrawTroopWithFrameAnimation( target, ICN::ICECLOUD, M82::UNKNOWN, NONE ); } void Battle::Interface::RedrawRaySpell( const Unit & target, const int spellICN, const int spellSound, const int32_t size ) { Cursor & cursor = Cursor::Get(); LocalEvent & le = LocalEvent::Get(); // Casting hero position const fheroes2::Point startingPos = arena.GetCurrentCommander() == _opponent1->GetHero() ? _opponent1->GetCastPosition() : _opponent2->GetCastPosition(); const fheroes2::Point targetPos = target.GetCenterPoint(); const std::vector path = GetEuclideanLine( startingPos, targetPos, size ); const uint32_t spriteCount = fheroes2::AGG::GetICNCount( spellICN ); cursor.SetThemes( Cursor::WAR_POINTER ); AudioManager::PlaySound( spellSound ); size_t i = 0; while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_DISRUPTING_DELAY } ) ) && i < path.size() ) { CheckGlobalEvents( le ); if ( Game::validateAnimationDelay( Game::BATTLE_DISRUPTING_DELAY ) ) { const uint32_t frame = static_cast( i * spriteCount / path.size() ); // it's safe to do such as i <= path.size() const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( spellICN, frame ); fheroes2::Blit( sprite, _mainSurface, path[i].x - sprite.width() / 2, path[i].y - sprite.height() / 2 ); RedrawPartialFinish(); ++i; } } } void Battle::Interface::RedrawActionDisruptingRaySpell( const Unit & target ) { LocalEvent & le = LocalEvent::Get(); RedrawRaySpell( target, ICN::DISRRAY, M82::DISRUPTR, 24 ); // Part 2 - ripple effect const fheroes2::Sprite & unitSprite = fheroes2::AGG::GetICN( target.GetMonsterSprite(), target.GetFrame() ); fheroes2::Sprite rippleSprite; const Unit * old_current = _currentUnit; _currentUnit = ⌖ _movingPos = { 0, 0 }; int32_t frame = 0; while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_DISRUPTING_DELAY } ) ) && frame < 60 ) { CheckGlobalEvents( le ); if ( Game::validateAnimationDelay( Game::BATTLE_DISRUPTING_DELAY ) ) { rippleSprite = fheroes2::CreateRippleEffect( unitSprite, frame ); rippleSprite.setPosition( unitSprite.x(), unitSprite.y() ); b_current_sprite = &rippleSprite; Redraw(); frame += 2; } } _currentUnit = old_current; b_current_sprite = nullptr; } void Battle::Interface::RedrawActionDeathWaveSpell( const int32_t strength ) { Cursor & cursor = Cursor::Get(); LocalEvent & le = LocalEvent::Get(); _currentUnit = nullptr; cursor.SetThemes( Cursor::WAR_POINTER ); // Set all non-dead troops animation to static and redraw the '_mainSurface'. SwitchAllUnitsAnimation( Monster_Info::STATIC ); Redraw(); fheroes2::Rect area = GetArea(); // Cut out the battle log image so we don't use it in the death wave effect. area.height -= status.height; // And if listlog is open, then cut off it too. if ( listlog && listlog->isOpenLog() ) { area.height -= listlog->GetArea().height; } fheroes2::Image battleFieldCopy; battleFieldCopy._disableTransformLayer(); battleFieldCopy.resize( area.width, area.height ); fheroes2::Copy( _mainSurface, 0, 0, battleFieldCopy, 0, 0, area.width, area.height ); // The death wave horizontal length in pixels. const int32_t waveLength = 38; const int32_t waveStep = 5; // A death wave parameter that limits the curve to one cosine period. const double waveLimit = waveLength / M_PI / 2; std::vector deathWaveCurve; deathWaveCurve.reserve( waveLength ); // Calculate the "Death Wave" curve as one period of cosine, which starts from 0 with an amplitude of 1/2 and shifted down by 0.5. // So we get a smooth hill, which is the multiplied with 'strength' and shifted after that by -1px. // (The "Death Wave" curve has to shift the image and cosine starts from 0 so we add extra 1px). for ( int32_t posX = 0; posX < waveLength; ++posX ) { deathWaveCurve.push_back( static_cast( std::round( strength * ( cos( posX / waveLimit ) / 2 - 0.5 ) ) ) - 1 ); } // Take into account that the Death Wave starts outside the battle screen. area.x -= waveLength; // The first frame of spell effect must have a part of the spell, so we start from its first position. int32_t position = waveStep; fheroes2::Display & display = fheroes2::Display::instance(); // Prepare the blank image for the Death Wave spell effect. fheroes2::Image spellEffect; spellEffect._disableTransformLayer(); spellEffect.resize( waveLength, area.height ); AudioManager::PlaySound( M82::MNRDEATH ); while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_DISRUPTING_DELAY } ) ) && position < area.width + waveLength ) { CheckGlobalEvents( le ); if ( Game::validateAnimationDelay( Game::BATTLE_DISRUPTING_DELAY ) ) { const int32_t wavePositionX = ( area.x + position < 0 ) ? 0 : ( area.x + position ); const int32_t waveWidth = position > waveLength ? ( position > area.width ? ( waveLength - position + area.width ) : waveLength ) : position; const int32_t restorePositionX = ( wavePositionX < waveStep ) ? 0 : ( wavePositionX - waveStep ); const int32_t restoreWidth = wavePositionX < waveStep ? wavePositionX : waveStep; const fheroes2::Rect renderArea( _interfacePosition.x + restorePositionX, _interfacePosition.y + area.y, waveWidth + restoreWidth, area.height ); // Place a copy of the original image where the Death Wave effect was on the previous frame. fheroes2::Copy( battleFieldCopy, restorePositionX, area.y, display, renderArea.x, renderArea.y, restoreWidth, renderArea.height ); // Place the Death Wave effect to its new position. fheroes2::CreateDeathWaveEffect( spellEffect, battleFieldCopy, position, deathWaveCurve ); fheroes2::Copy( spellEffect, 0, 0, display, renderArea.x + restoreWidth, renderArea.y, waveWidth, area.height ); // Render only the changed screen area. display.render( renderArea ); position += waveStep; } } SwitchAllUnitsAnimation( Monster_Info::STATIC ); } void Battle::Interface::RedrawActionColdRingSpell( const int32_t dst, const TargetsInfo & targets ) { LocalEvent & le = LocalEvent::Get(); const int icn = ICN::COLDRING; const int m82 = M82::FromSpell( Spell::COLDRING ); uint32_t frame = 0; const fheroes2::Rect & center = Board::GetCell( dst )->GetPos(); Cursor::Get().SetThemes( Cursor::WAR_POINTER ); // set WNCE _currentUnit = nullptr; for ( const TargetInfo & target : targets ) { if ( target.defender && target.damage ) { target.defender->SwitchAnimation( Monster_Info::WNCE ); } } AudioManager::PlaySound( m82 ); Game::passAnimationDelay( Game::BATTLE_SPELL_DELAY ); while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_SPELL_DELAY } ) ) && frame < fheroes2::AGG::GetICNCount( icn ) ) { CheckGlobalEvents( le ); if ( Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) { RedrawPartialStart(); const fheroes2::Sprite & sprite1 = fheroes2::AGG::GetICN( icn, frame ); fheroes2::Blit( sprite1, _mainSurface, center.x + center.width / 2 + sprite1.x(), center.y + center.height / 2 + sprite1.y() ); const fheroes2::Sprite & sprite2 = fheroes2::AGG::GetICN( icn, frame ); fheroes2::Blit( sprite2, _mainSurface, center.x + center.width / 2 - sprite2.width() - sprite2.x(), center.y + center.height / 2 + sprite2.y(), true ); RedrawPartialFinish(); for ( const TargetInfo & target : targets ) { if ( target.defender && target.damage ) { target.defender->IncreaseAnimFrame( false ); } } ++frame; } } for ( const TargetInfo & target : targets ) { if ( target.defender ) { target.defender->SwitchAnimation( Monster_Info::STATIC ); _currentUnit = nullptr; } } } void Battle::Interface::RedrawActionHolyShoutSpell( const uint8_t strength ) { Cursor & cursor = Cursor::Get(); LocalEvent & le = LocalEvent::Get(); cursor.SetThemes( Cursor::WAR_POINTER ); // Set all non-dead troops animation to static and redraw the '_mainSurface'. SwitchAllUnitsAnimation( Monster_Info::STATIC ); Redraw(); fheroes2::Rect area = GetArea(); // Cut out the battle log image so we don't use it in the death wave effect. area.height -= status.height; // And if listlog is open, then cut off it too. if ( listlog && listlog->isOpenLog() ) { area.height -= listlog->GetArea().height; } fheroes2::Image battleFieldCopy; battleFieldCopy._disableTransformLayer(); battleFieldCopy.resize( area.width, area.height ); fheroes2::Copy( _mainSurface, 0, 0, battleFieldCopy, 0, 0, area.width, area.height ); _currentUnit = nullptr; const uint32_t maxFrame = 20; const uint32_t halfMaxFrame = maxFrame / 2; // A vector of frames to animate the increase of the spell effect. The decrease will be shown in reverse frames order. // Initialize a vector with copies of battle field to use them in making the spell effect increase animation. std::vector spellEffect; static_assert( halfMaxFrame > 1 ); spellEffect.reserve( halfMaxFrame ); const uint32_t spellEffectLastFrame = halfMaxFrame - 1; // The similar frames number is smaller than size by 1 as the last frame will be different. while ( spellEffect.size() < spellEffectLastFrame ) { spellEffect.push_back( battleFieldCopy ); } // The last frame is the full power of spell effect. It will be used to produce other frames. spellEffect.emplace_back( fheroes2::CreateHolyShoutEffect( battleFieldCopy, 4, strength ) ); const uint32_t spellcastDelay = Game::ApplyBattleSpeed( 3000 ) / maxFrame; uint32_t frame = 0; uint8_t alpha = 30; const uint8_t alphaStep = 25; fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::Rect renderArea( _interfacePosition.x + area.x, _interfacePosition.y + area.y, area.width, area.height ); // Immediately indicate that the delay has passed to render first frame immediately. Game::passCustomAnimationDelay( spellcastDelay ); // Make sure that the first run is passed immediately. assert( !Game::isCustomDelayNeeded( spellcastDelay ) ); AudioManager::PlaySound( M82::MASSCURS ); while ( le.HandleEvents( Game::isCustomDelayNeeded( spellcastDelay ) ) && frame < maxFrame ) { CheckGlobalEvents( le ); if ( Game::validateCustomAnimationDelay( spellcastDelay ) ) { // Display the maximum spell effect for 1 more 'spellcastDelay' without rendering a frame. if ( frame != halfMaxFrame ) { // If the spell effect is increasing we generate the frame for it in the vector to use it later in decreasing animation. if ( frame < spellEffectLastFrame ) { fheroes2::AlphaBlit( spellEffect[spellEffectLastFrame], spellEffect[frame], alpha ); alpha += alphaStep; } const uint32_t spellEffectFrame = ( frame < halfMaxFrame ) ? frame : ( maxFrame - frame - 1 ); fheroes2::Copy( spellEffect[spellEffectFrame], area.x, area.y, display, renderArea.x, renderArea.y, renderArea.width, renderArea.height ); display.render( renderArea ); } ++frame; } } } void Battle::Interface::RedrawActionElementalStormSpell( const TargetsInfo & targets ) { LocalEvent & le = LocalEvent::Get(); const int icn = ICN::STORM; const int m82 = M82::FromSpell( Spell::ELEMENTALSTORM ); const int spriteSize = 54; const uint32_t icnCount = fheroes2::AGG::GetICNCount( icn ); std::vector spriteCache; spriteCache.reserve( icnCount ); for ( uint32_t i = 0; i < icnCount; ++i ) { spriteCache.push_back( fheroes2::AGG::GetICN( icn, i ) ); } Cursor::Get().SetThemes( Cursor::WAR_POINTER ); _currentUnit = nullptr; for ( const TargetInfo & target : targets ) { if ( target.defender && target.damage ) { target.defender->SwitchAnimation( Monster_Info::WNCE ); } } AudioManager::PlaySound( m82 ); Game::passAnimationDelay( Game::BATTLE_SPELL_DELAY ); uint32_t frame = 0; while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_SPELL_DELAY } ) ) && frame < 60 ) { CheckGlobalEvents( le ); if ( Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) { RedrawPartialStart(); if ( icnCount > 0 ) { for ( int x = 0; x * spriteSize < _surfaceInnerArea.width; ++x ) { const int idX = frame + x * 3; const int offsetX = x * spriteSize; for ( int y = 0; y * spriteSize < _surfaceInnerArea.height; ++y ) { const fheroes2::Sprite & sprite = spriteCache[( idX + y ) % icnCount]; fheroes2::Blit( sprite, _mainSurface, offsetX + sprite.x(), y * spriteSize + sprite.y() ); } } } RedrawPartialFinish(); for ( const TargetInfo & target : targets ) { if ( target.defender && target.damage ) { target.defender->IncreaseAnimFrame( false ); } } ++frame; } } for ( const TargetInfo & target : targets ) { if ( target.defender ) { target.defender->SwitchAnimation( Monster_Info::STATIC ); _currentUnit = nullptr; } } } void Battle::Interface::RedrawActionArmageddonSpell() { Cursor & cursor = Cursor::Get(); LocalEvent & le = LocalEvent::Get(); fheroes2::Rect area = GetArea(); area.height -= 37; fheroes2::Image spriteWhitening; spriteWhitening._disableTransformLayer(); spriteWhitening.resize( area.width, area.height ); fheroes2::Copy( _mainSurface, area.x, area.y, spriteWhitening, 0, 0, area.width, area.height ); fheroes2::Image spriteReddish = spriteWhitening; cursor.SetThemes( Cursor::WAR_POINTER ); _currentUnit = nullptr; AudioManager::PlaySound( M82::ARMGEDN ); uint32_t alpha = 10; Game::passAnimationDelay( Game::BATTLE_SPELL_DELAY ); while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_SPELL_DELAY } ) ) && alpha < 100 ) { CheckGlobalEvents( le ); if ( Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) { fheroes2::ApplyPalette( spriteWhitening, 9 ); fheroes2::Copy( spriteWhitening, 0, 0, _mainSurface, area.x, area.y, area.width, area.height ); RedrawPartialFinish(); alpha += 10; } } fheroes2::ApplyPalette( spriteReddish, PAL::GetPalette( PAL::PaletteType::RED ) ); fheroes2::Copy( spriteReddish, 0, 0, _mainSurface, area.x, area.y, area.width, area.height ); while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_SPELL_DELAY } ) ) && Mixer::isPlaying( -1 ) ) { CheckGlobalEvents( le ); if ( Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) { const int32_t offsetX = static_cast( Rand::Get( 0, 14 ) ) - 7; const int32_t offsetY = static_cast( Rand::Get( 0, 14 ) ) - 7; const fheroes2::Rect initialArea( area ); fheroes2::Rect original = initialArea ^ fheroes2::Rect( area.x + offsetX, area.y + offsetY, area.width, area.height ); fheroes2::Rect shifted( initialArea.x - original.x, initialArea.y - original.y, original.width, original.height ); if ( shifted.x < 0 ) { const int32_t offset = -shifted.x; shifted.x = 0; original.x += offset; shifted.width -= offset; shifted.x = 0; } if ( shifted.y < 0 ) { const int32_t offset = -shifted.y; shifted.y = 0; original.y += offset; shifted.height -= offset; shifted.y = 0; } fheroes2::Copy( spriteReddish, shifted.x, shifted.y, _mainSurface, original.x, original.y, shifted.width, shifted.height ); RedrawPartialFinish(); } } } void Battle::Interface::RedrawActionEarthQuakeSpell( const std::vector & targets ) { Cursor & cursor = Cursor::Get(); LocalEvent & le = LocalEvent::Get(); fheroes2::Rect area = GetArea(); uint32_t frame = 0; area.height -= 38; cursor.SetThemes( Cursor::WAR_POINTER ); fheroes2::Image sprite; sprite._disableTransformLayer(); sprite.resize( area.width, area.height ); fheroes2::Copy( _mainSurface, area.x, area.y, sprite, 0, 0, area.width, area.height ); _currentUnit = nullptr; AudioManager::PlaySound( M82::ERTHQUAK ); Game::passAnimationDelay( Game::BATTLE_SPELL_DELAY ); // draw earth quake while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_SPELL_DELAY } ) ) && frame < 18 ) { CheckGlobalEvents( le ); if ( Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) { const int32_t offsetX = static_cast( Rand::Get( 0, 14 ) ) - 7; const int32_t offsetY = static_cast( Rand::Get( 0, 14 ) ) - 7; const fheroes2::Rect initialArea( area ); fheroes2::Rect original = initialArea ^ fheroes2::Rect( area.x + offsetX, area.y + offsetY, area.width, area.height ); fheroes2::Rect shifted( initialArea.x - original.x, initialArea.y - original.y, original.width, original.height ); if ( shifted.x < 0 ) { const int32_t offset = -shifted.x; shifted.x = 0; original.x += offset; shifted.width -= offset; shifted.x = 0; } if ( shifted.y < 0 ) { const int32_t offset = -shifted.y; shifted.y = 0; original.y += offset; shifted.height -= offset; shifted.y = 0; } fheroes2::Copy( sprite, shifted.x, shifted.y, _mainSurface, original.x, original.y, shifted.width, shifted.height ); RedrawPartialFinish(); ++frame; } } // draw cloud const int icn = ICN::LICHCLOD; frame = 0; AudioManager::PlaySound( M82::CATSND02 ); Game::passAnimationDelay( Game::BATTLE_SPELL_DELAY ); while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_SPELL_DELAY } ) ) && frame < fheroes2::AGG::GetICNCount( icn ) ) { CheckGlobalEvents( le ); if ( Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) { RedrawPartialStart(); for ( const CastleDefenseElement target : targets ) { fheroes2::Point pt2 = Catapult::GetTargetPosition( target, true ); pt2.x += area.x; pt2.y += area.y; const fheroes2::Sprite & spriteCloud = fheroes2::AGG::GetICN( icn, frame ); fheroes2::Blit( spriteCloud, _mainSurface, pt2.x + spriteCloud.x(), pt2.y + spriteCloud.y() ); } RedrawPartialFinish(); ++frame; } } } void Battle::Interface::RedrawActionRemoveMirrorImage( const std::vector & mirrorImages ) { if ( mirrorImages.empty() ) // nothing to animate return; // Reset the delay to wait till the next frame if is not already waiting. if ( !Game::isDelayNeeded( { Game::DelayType::BATTLE_FRAME_DELAY } ) ) { Game::AnimateResetDelay( Game::DelayType::BATTLE_FRAME_DELAY ); } LocalEvent & le = LocalEvent::Get(); uint8_t frameId = 10; const uint8_t alphaStep = 25; while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_FRAME_DELAY } ) ) && frameId > 0 ) { CheckGlobalEvents( le ); if ( Game::validateAnimationDelay( Game::BATTLE_FRAME_DELAY ) ) { const uint8_t alpha = frameId * alphaStep; for ( Unit * unit : mirrorImages ) { if ( unit != nullptr ) { unit->SetCustomAlpha( alpha ); } } Redraw(); --frameId; } } status.SetMessage( _( "The mirror image is destroyed!" ), true ); } void Battle::Interface::RedrawTargetsWithFrameAnimation( const int32_t dst, const TargetsInfo & targets, const int icn, const int m82, int repeatCount /* = 0 */ ) { LocalEvent & le = LocalEvent::Get(); uint32_t frame = 0; const fheroes2::Rect & center = Board::GetCell( dst )->GetPos(); Cursor::Get().SetThemes( Cursor::WAR_POINTER ); _currentUnit = nullptr; for ( const TargetInfo & target : targets ) { if ( target.defender && target.damage ) { target.defender->SwitchAnimation( Monster_Info::WNCE ); } } AudioManager::PlaySound( m82 ); uint32_t frameCount = fheroes2::AGG::GetICNCount( icn ); Game::passAnimationDelay( Game::BATTLE_SPELL_DELAY ); while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_SPELL_DELAY } ) ) && frame < frameCount ) { CheckGlobalEvents( le ); if ( Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) { RedrawPartialStart(); const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( icn, frame ); fheroes2::Blit( sprite, _mainSurface, center.x + center.width / 2 + sprite.x(), center.y + center.height / 2 + sprite.y() ); RedrawPartialFinish(); for ( const TargetInfo & target : targets ) { if ( target.defender && target.damage ) { target.defender->IncreaseAnimFrame( false ); } } ++frame; if ( frame == frameCount && repeatCount > 0 ) { --repeatCount; frame = 0; } } } for ( const TargetInfo & target : targets ) { if ( target.defender ) { target.defender->SwitchAnimation( Monster_Info::STATIC ); _currentUnit = nullptr; } } } void Battle::Interface::RedrawTargetsWithFrameAnimation( const TargetsInfo & targets, const int icn, const int m82, const bool wnce ) { LocalEvent & le = LocalEvent::Get(); // A vector for creatures made by Mirror Image spell which will be destroyed by current spell. std::vector mirrorImages; Cursor::Get().SetThemes( Cursor::WAR_POINTER ); _currentUnit = nullptr; if ( wnce ) { int32_t deathColor = Color::UNUSED; std::set unitSounds; const auto playSoundIfNotPlaying = [&unitSounds]( const int unitSound ) { const auto [dummy, isUnique] = unitSounds.insert( unitSound ); if ( isUnique ) { AudioManager::PlaySound( unitSound ); } }; for ( const TargetInfo & target : targets ) { Unit * defender = target.defender; if ( defender == nullptr ) { continue; } if ( defender->isModes( CAP_MIRRORIMAGE ) ) { mirrorImages.push_back( defender ); } // If the creature was killed set its death animation. if ( !defender->isValid() ) { // Destroy linked mirror to a dead creature. if ( defender->isModes( CAP_MIRROROWNER ) ) { mirrorImages.push_back( defender->GetMirror() ); } defender->SwitchAnimation( Monster_Info::KILL ); playSoundIfNotPlaying( defender->M82Kill() ); // Set the color of the dead creature to tell heroes about it. deathColor = defender->GetArmyColor(); } // If the creature is damaged but is still alive set its wince animation. else if ( target.damage ) { defender->SwitchAnimation( Monster_Info::WNCE_UP ); playSoundIfNotPlaying( defender->M82Wnce() ); } SetHeroAnimationReactionToTroopDeath( deathColor ); } } // For certain spells reflect the spell sprite if the creature is reflected. const bool isReflectICN = ( icn == ICN::SHIELD || icn == ICN::REDDEATH || icn == ICN::MAGIC08 ); size_t overlaySpriteCount = _unitSpellEffectInfos.size(); _unitSpellEffectInfos.reserve( overlaySpriteCount + targets.size() ); for ( const Battle::TargetInfo & target : targets ) { if ( target.defender ) { _unitSpellEffectInfos.emplace_back( target.defender->GetUID(), icn, ( isReflectICN && target.defender->isReflect() ) ); } } overlaySpriteCount = _unitSpellEffectInfos.size() - overlaySpriteCount; const std::vector::iterator overlaySpriteEnd = _unitSpellEffectInfos.end(); const std::vector::iterator overlaySpriteBegin = overlaySpriteEnd - static_cast( overlaySpriteCount ); // Set the defender wince animation state. bool isDefenderAnimating = wnce; const uint32_t maxFrame = fheroes2::AGG::GetICNCount( icn ); uint32_t frame = 0; // Wait for previously set and not passed delays before rendering a new frame. WaitForAllActionDelays(); AudioManager::PlaySound( m82 ); while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_SPELL_DELAY } ) ) && ( frame < maxFrame || isDefenderAnimating ) ) { CheckGlobalEvents( le ); if ( Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) { if ( frame < maxFrame ) { std::vector::iterator overlaySpriteIter = overlaySpriteBegin; const fheroes2::Sprite & spellSprite = fheroes2::AGG::GetICN( icn, frame ); for ( const Battle::TargetInfo & target : targets ) { if ( target.defender ) { overlaySpriteIter->position = CalculateSpellPosition( *target.defender, icn, spellSprite ); overlaySpriteIter->icnIndex = frame; ++overlaySpriteIter; } } } Redraw(); // Reset the defender wince animation state. isDefenderAnimating = false; if ( wnce ) { for ( const TargetInfo & target : targets ) { if ( target.defender == nullptr ) { continue; } // Fully animate creature death. if ( !target.defender->isValid() ) { target.defender->IncreaseAnimFrame( false ); // If the death animation is still in process then set isDefenderAnimating to false. isDefenderAnimating |= !target.defender->isFinishAnimFrame(); } else if ( target.damage ) { // If the target has taken damage and is not killed. // Check if the current animation is not finished. if ( !target.defender->isFinishAnimFrame() ) { target.defender->IncreaseAnimFrame( false ); } else if ( frame >= ( maxFrame - 1 ) && target.defender->GetAnimationState() == Monster_Info::WNCE_UP ) { // If the main spell sprite animation and WNCE_UP are finished then switch unit animation to WNCE_DOWN. target.defender->SwitchAnimation( Monster_Info::WNCE_DOWN ); } else if ( target.defender->GetAnimationState() == Monster_Info::WNCE_DOWN ) { // If the WNCE_DOWN animation is finished, then set STATIC animation to the target. target.defender->SwitchAnimation( Monster_Info::STATIC ); } // If not all damaged (and not killed) units are set to STATIC animation then set isDefenderAnimating to false. // IMPORTANT: The game engine can change STATIC animation to IDLE, especially for Ghosts and Zombies, // so we need to check IDLE where we check for STATIC. const int unitAnimState = target.defender->GetAnimationState(); isDefenderAnimating |= ( unitAnimState != Monster_Info::STATIC ) && ( unitAnimState != Monster_Info::IDLE ); } } } ++frame; // Remove all overlay sprites when the animation is finished. if ( frame == maxFrame ) { _unitSpellEffectInfos.erase( overlaySpriteBegin, overlaySpriteEnd ); } } } // TODO: When there is a need to display some permanent effects with '_unitSpellEffectInfos' - remove this assertion. assert( _unitSpellEffectInfos.empty() ); if ( !mirrorImages.empty() ) { // Fade away animation for destroyed mirror images. RedrawActionRemoveMirrorImage( mirrorImages ); } } void Battle::Interface::RedrawTroopWithFrameAnimation( Unit & unit, const int icn, const int m82, const CreatureSpellAnimation animation ) { LocalEvent & le = LocalEvent::Get(); uint32_t frame = 0; const bool reflect = ( icn == ICN::SHIELD && unit.isReflect() ); Cursor::Get().SetThemes( Cursor::WAR_POINTER ); if ( animation == WINCE ) { _currentUnit = nullptr; unit.SwitchAnimation( Monster_Info::WNCE_UP ); } else if ( animation == RESURRECT ) { _currentUnit = nullptr; unit.SwitchAnimation( Monster_Info::KILL, true ); } _unitSpellEffectInfos.emplace_back( unit.GetUID(), icn, reflect ); // Wait for previously set and not passed delays before rendering a new frame. WaitForAllActionDelays(); const uint32_t maxICNFrame = fheroes2::AGG::GetICNCount( icn ); AudioManager::PlaySound( m82 ); while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_SPELL_DELAY } ) ) && ( frame < maxICNFrame || unit.GetAnimationState() == Monster_Info::WNCE_DOWN ) ) { CheckGlobalEvents( le ); if ( Game::validateAnimationDelay( Game::BATTLE_SPELL_DELAY ) ) { if ( frame < maxICNFrame ) { _unitSpellEffectInfos.back().position = CalculateSpellPosition( unit, icn, fheroes2::AGG::GetICN( icn, frame ) ); _unitSpellEffectInfos.back().icnIndex = frame; } Redraw(); if ( animation != NONE ) { if ( ( animation == RESURRECT || unit.GetAnimationState() == Monster_Info::WNCE_DOWN ) && unit.isFinishAnimFrame() ) { unit.SwitchAnimation( Monster_Info::STAND_STILL ); } unit.IncreaseAnimFrame( false ); if ( frame == maxICNFrame - 1 && animation == WINCE ) { unit.SwitchAnimation( Monster_Info::WNCE_DOWN ); } } ++frame; if ( frame == maxICNFrame ) { // Spell animation is finished, so delete the overlay sprite from unit. _unitSpellEffectInfos.pop_back(); } } } // TODO: When there is a need to display some permanent effects with '_unitSpellEffectInfos' - remove this assertion. assert( _unitSpellEffectInfos.empty() ); if ( animation != NONE ) { unit.SwitchAnimation( Monster_Info::STATIC ); _currentUnit = nullptr; } } void Battle::Interface::RedrawBridgeAnimation( const bool bridgeDownAnimation ) { // Wait for previously set and not passed delays before rendering a new frame. WaitForAllActionDelays(); LocalEvent & le = LocalEvent::Get(); _bridgeAnimation.animationIsRequired = true; _bridgeAnimation.currentFrameId = bridgeDownAnimation ? BridgeMovementAnimation::UP_POSITION : BridgeMovementAnimation::DOWN_POSITION; if ( bridgeDownAnimation ) AudioManager::PlaySound( M82::DRAWBRG ); while ( le.HandleEvents( Game::isDelayNeeded( { Game::BATTLE_BRIDGE_DELAY } ) ) ) { if ( bridgeDownAnimation ) { if ( _bridgeAnimation.currentFrameId < BridgeMovementAnimation::DOWN_POSITION ) { break; } } else { if ( _bridgeAnimation.currentFrameId > BridgeMovementAnimation::UP_POSITION ) { break; } } CheckGlobalEvents( le ); if ( Game::validateAnimationDelay( Game::BATTLE_BRIDGE_DELAY ) ) { Redraw(); if ( bridgeDownAnimation ) { --_bridgeAnimation.currentFrameId; } else { ++_bridgeAnimation.currentFrameId; } } } _bridgeAnimation.animationIsRequired = false; if ( !bridgeDownAnimation ) { AudioManager::PlaySound( M82::DRAWBRG ); } } bool Battle::Interface::IdleTroopsAnimation() const { if ( Game::validateAnimationDelay( Game::BATTLE_IDLE_DELAY ) ) { const bool redrawNeeded = arena.GetForce1().animateIdleUnits(); return arena.GetForce2().animateIdleUnits() || redrawNeeded; } return false; } void Battle::Interface::ResetIdleTroopAnimation() const { arena.GetForce1().resetIdleAnimation(); arena.GetForce2().resetIdleAnimation(); } void Battle::Interface::SwitchAllUnitsAnimation( const int32_t animationState ) const { for ( Battle::Unit * unit : arena.GetForce1() ) { if ( unit->isValid() ) { unit->SwitchAnimation( animationState ); } } for ( Battle::Unit * unit : arena.GetForce2() ) { if ( unit->isValid() ) { unit->SwitchAnimation( animationState ); } } } void Battle::Interface::CheckGlobalEvents( LocalEvent & le ) { // Animation of the currently active unit's contour if ( Game::validateAnimationDelay( Game::BATTLE_SELECTED_UNIT_DELAY ) ) { UpdateContourColor(); } // Animation of flags and heroes idle. if ( Game::validateAnimationDelay( Game::BATTLE_FLAGS_DELAY ) ) { ++animation_flags_frame; humanturn_redraw = true; // Perform heroes idle animation only if heroes are not performing any other animation (e.g. spell casting). if ( Game::hasEveryDelayPassed( { Game::BATTLE_OPPONENTS_DELAY } ) ) { if ( _opponent1 && _opponent1->updateAnimationState() ) { humanturn_redraw = true; } if ( _opponent2 && _opponent2->updateAnimationState() ) { humanturn_redraw = true; } } } // Interrupting auto battle if ( arena.AutoBattleInProgress() && arena.CanToggleAutoBattle() && ( le.MouseClickLeft( btn_auto.area() ) || ( le.KeyPress() && ( Game::HotKeyPressEvent( Game::HotKeyEvent::BATTLE_AUTO_SWITCH ) || ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) && fheroes2::showMessage( fheroes2::Text( "", {} ), fheroes2::Text( _( "Are you sure you want to interrupt the auto battle?" ), fheroes2::FontType::normalWhite() ), Dialog::YES | Dialog::NO ) == Dialog::YES ) ) ) ) ) { _interruptAutoBattleForColor = arena.GetCurrentColor(); } } void Battle::Interface::ProcessingHeroDialogResult( const int result, Actions & actions ) { switch ( result ) { // cast case 1: { const HeroBase * hero = _currentUnit->GetCurrentOrArmyCommander(); if ( hero ) { if ( hero->HaveSpellBook() ) { std::string msg; if ( arena.isDisableCastSpell( Spell::NONE, &msg ) ) { fheroes2::showStandardTextMessage( "", msg, Dialog::OK ); } else { const std::function statusCallback = [this]( const std::string & statusStr ) { status.SetMessage( statusStr ); status.Redraw( fheroes2::Display::instance() ); }; const Spell spell = hero->OpenSpellBook( SpellBook::Filter::CMBT, true, true, &statusCallback ); // Reset battlefield grid cursor position after closing the spell book. index_pos = -1; if ( spell.isValid() ) { assert( spell.isCombat() ); if ( arena.isDisableCastSpell( spell, &msg ) ) { fheroes2::showStandardTextMessage( "", msg, Dialog::OK ); } else { std::string error; if ( hero->CanCastSpell( spell, &error ) ) { if ( spell.isApplyWithoutFocusObject() ) { actions.emplace_back( Command::SPELLCAST, spell.GetID(), -1 ); humanturn_redraw = true; humanturn_exit = true; } else { humanturn_spell = spell; } } else { fheroes2::showStandardTextMessage( _( "Error" ), error, Dialog::OK ); } } } } } else fheroes2::showStandardTextMessage( "", _( "No spells to cast." ), Dialog::OK ); } break; } // retreat case 2: { if ( arena.CanRetreatOpponent( _currentUnit->GetCurrentOrArmyColor() ) ) { if ( Dialog::YES == fheroes2::showStandardTextMessage( "", _( "Are you sure you want to retreat?" ), Dialog::YES | Dialog::NO ) ) { actions.emplace_back( Command::RETREAT ); humanturn_exit = true; } } else { fheroes2::showStandardTextMessage( "", _( "Retreat disabled" ), Dialog::OK ); } break; } // surrender case 3: { if ( arena.CanSurrenderOpponent( _currentUnit->GetCurrentOrArmyColor() ) ) { const HeroBase * enemy = arena.getEnemyCommander( arena.GetCurrentColor() ); if ( enemy ) { const int32_t cost = arena.GetCurrentForce().GetSurrenderCost(); Kingdom & kingdom = world.GetKingdom( arena.GetCurrentColor() ); if ( DialogBattleSurrender( *enemy, cost, kingdom ) ) { actions.emplace_back( Command::SURRENDER ); humanturn_exit = true; } } } else { fheroes2::showStandardTextMessage( "", _( "Surrender disabled" ), Dialog::OK ); } break; } default: break; } } Battle::PopupDamageInfo::PopupDamageInfo() : Dialog::FrameBorder( 5 ) , _cell( nullptr ) , _defender( nullptr ) , _minDamage( 0 ) , _maxDamage( 0 ) , _redraw( false ) {} void Battle::PopupDamageInfo::setBattleUIRect( const fheroes2::Rect & battleUIRect ) { _battleUIRect = battleUIRect; } bool Battle::PopupDamageInfo::SetDamageInfoBase( const Cell * cell, const Unit * defender ) { if ( cell == nullptr || defender == nullptr ) { return false; } if ( !Settings::Get().isBattleShowDamageInfoEnabled() || !Game::validateAnimationDelay( Game::BATTLE_POPUP_DELAY ) ) { return false; } _cell = cell; _defender = defender; return true; } void Battle::PopupDamageInfo::SetAttackInfo( const Cell * cell, const Unit * attacker, const Unit * defender ) { if ( attacker == nullptr || !SetDamageInfoBase( cell, defender ) ) { return; } _redraw = true; _minDamage = attacker->CalculateMinDamage( *defender ); _maxDamage = attacker->CalculateMaxDamage( *defender ); if ( attacker->Modes( SP_BLESS ) ) { _minDamage = _maxDamage; } else if ( attacker->Modes( SP_CURSE ) ) { _maxDamage = _minDamage; } } void Battle::PopupDamageInfo::SetSpellAttackInfo( const Cell * cell, const HeroBase * hero, const Unit * defender, const Spell spell ) { assert( hero != nullptr ); // TODO: Currently, this functionality only supports a simple single-target spell case // We should refactor this to apply to all cases if ( !spell.isSingleTarget() || !spell.isDamage() ) { return; } if ( !SetDamageInfoBase( cell, defender ) ) { return; } // If defender unit immune to magic, do not show the tooltip if ( !defender->AllowApplySpell( spell, hero ) ) { return; } const int spellPoints = hero ? hero->GetPower() : DEFAULT_SPELL_DURATION; const uint32_t spellDamage = defender->CalculateSpellDamage( spell, spellPoints, hero, 0 /* targetInfo damage */, true /* ignore defending hero */ ); _redraw = true; _minDamage = spellDamage; _maxDamage = spellDamage; } void Battle::PopupDamageInfo::Reset() { if ( _redraw ) { _redraw = false; _cell = nullptr; _defender = nullptr; _minDamage = 0; _maxDamage = 0; } Game::AnimateResetDelay( Game::BATTLE_POPUP_DELAY ); } void Battle::PopupDamageInfo::Redraw() const { if ( !_redraw ) { return; } assert( _cell != nullptr && _defender != nullptr ); std::string str = _minDamage == _maxDamage ? _( "Damage: %{max}" ) : _( "Damage: %{min} - %{max}" ); StringReplace( str, "%{min}", std::to_string( _minDamage ) ); StringReplace( str, "%{max}", std::to_string( _maxDamage ) ); const fheroes2::Text damageText( str, fheroes2::FontType::smallWhite() ); const uint32_t minNumKilled = _defender->HowManyWillBeKilled( _minDamage ); const uint32_t maxNumKilled = _defender->HowManyWillBeKilled( _maxDamage ); assert( minNumKilled <= _defender->GetCount() && maxNumKilled <= _defender->GetCount() ); str = minNumKilled == maxNumKilled ? _( "Perish: %{max}" ) : _( "Perish: %{min} - %{max}" ); StringReplace( str, "%{min}", std::to_string( minNumKilled ) ); StringReplace( str, "%{max}", std::to_string( maxNumKilled ) ); const fheroes2::Text killedText( str, fheroes2::FontType::smallWhite() ); const fheroes2::Rect & cellRect = _cell->GetPos(); // Get the border width and set the popup parameters. const int borderWidth = BorderWidth(); const int x = _battleUIRect.x + cellRect.x + cellRect.width; const int y = _battleUIRect.y + cellRect.y; const int w = std::max( damageText.width(), killedText.width() ) + 2 * borderWidth; const int h = damageText.height() + killedText.height() + 2 * borderWidth; // If the damage info popup doesn't fit the battlefield draw surface, then try to place it on the left side of the cell const bool isLeftSidePopup = ( cellRect.x + cellRect.width + w ) > _battleUIRect.width; const fheroes2::Rect borderRect( isLeftSidePopup ? ( x - w - cellRect.width - borderWidth ) : x, y, w, h ); Dialog::FrameBorder::RenderOther( fheroes2::AGG::GetICN( ICN::CELLWIN, 1 ), borderRect ); const int leftTextBorder = borderRect.x + borderWidth; fheroes2::Display & display = fheroes2::Display::instance(); damageText.draw( leftTextBorder, borderRect.y + borderWidth + 2, display ); killedText.draw( leftTextBorder, borderRect.y + borderRect.height / 2 + 2, display ); } fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_interface.h000066400000000000000000000465401456075706000234750ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2BATTLE_INTERFACE_H #define H2BATTLE_INTERFACE_H #include #include #include #include #include #include #include "battle.h" #include "battle_animation.h" #include "battle_board.h" #include "cursor.h" #include "dialog.h" #include "icn.h" #include "image.h" #include "math_base.h" #include "spell.h" #include "ui_button.h" #include "ui_text.h" class Castle; class HeroBase; class Kingdom; class LocalEvent; namespace fheroes2 { class StandardWindow; } namespace Battle { class Actions; class Arena; class Cell; class Position; class StatusListBox; class Tower; class Unit; class Units; void DialogBattleSettings(); bool DialogBattleSurrender( const HeroBase & hero, uint32_t cost, Kingdom & kingdom ); enum HeroAnimation : uint32_t { OP_JOY, OP_CAST_MASS, OP_CAST_MASS_RETURN, OP_CAST_UP, OP_CAST_UP_RETURN, OP_CAST_DOWN, OP_CAST_DOWN_RETURN, OP_IDLE, OP_IDLE2, OP_STATIC, OP_SORROW }; enum BattleHeroType { KNIGHT, BARBARIAN, SORCERESS, WARLOCK, WIZARD, NECROMANCER, CAPTAIN }; // Sprite data to render over the unit (spell effect animation) struct UnitSpellEffectInfo { UnitSpellEffectInfo( const uint32_t setUnitId, const int32_t setIcnId, const bool setReflectedImage ) : unitId( setUnitId ) , icnId( setIcnId ) , isReflectedImage( setReflectedImage ) {} uint32_t unitId{ 0 }; int32_t icnId{ ICN::UNKNOWN }; uint32_t icnIndex{ 0 }; fheroes2::Point position; bool isReflectedImage{ false }; }; class OpponentSprite { public: OpponentSprite( const fheroes2::Rect & area, const HeroBase * hero, const bool isReflect ); OpponentSprite( const OpponentSprite & ) = delete; OpponentSprite & operator=( const OpponentSprite & ) = delete; const fheroes2::Rect & GetArea() const { return pos; } fheroes2::Point GetCastPosition() const; void Redraw( fheroes2::Image & dst ) const; // Return true is animation state was changed. bool updateAnimationState(); void SetAnimation( const int rule ); void IncreaseAnimFrame( const bool loop = false ); bool isFinishFrame() const { return _currentAnim.isLastFrame(); } const HeroBase * GetHero() const { return base; } fheroes2::Point Offset() const { return _offset; } enum { RIGHT_HERO_X_OFFSET = 29, LEFT_HERO_X_OFFSET = 30, LEFT_HERO_Y_OFFSET = 183, RIGHT_HERO_Y_OFFSET = 148, CAPTAIN_X_OFFSET = 6, CAPTAIN_Y_OFFSET = -13 }; private: const HeroBase * base; AnimationSequence _currentAnim; int _animationType; RandomizedDelay _idleTimer; int _heroIcnId; bool reflect; fheroes2::Rect pos; fheroes2::Point _offset; }; class Status : public fheroes2::Rect { public: Status(); Status( const Status & ) = delete; Status & operator=( const Status & ) = delete; void SetPosition( const int32_t cx, const int32_t cy ); void SetLogs( StatusListBox * logs ) { listlog = logs; } void SetMessage( const std::string & messageString, const bool top = false ); void Redraw( fheroes2::Image & output ) const; const std::string & GetMessage() const { return _lastMessage; } void clear(); private: fheroes2::Text _upperText; fheroes2::Text _lowerText; const fheroes2::Sprite & back1; const fheroes2::Sprite & back2; std::string _lastMessage; StatusListBox * listlog; }; class TurnOrder : public fheroes2::Rect { public: TurnOrder(); TurnOrder( const TurnOrder & ) = delete; TurnOrder & operator=( const TurnOrder & ) = delete; void Set( const fheroes2::Rect & rt, const std::shared_ptr & units, const int army2Color ); void Redraw( const Unit * current, const uint8_t currentUnitColor, fheroes2::Image & output ); void QueueEventProcessing( std::string & msg, const fheroes2::Point & offset ) const; private: enum ArmyColor : uint8_t { ARMY_COLOR_BLACK = 0x00, ARMY_COLOR_BLUE = 0x47, ARMY_COLOR_GREEN = 0x67, ARMY_COLOR_RED = 0xbd, ARMY_COLOR_YELLOW = 0x70, ARMY_COLOR_ORANGE = 0xcd, ARMY_COLOR_PURPLE = 0x87, ARMY_COLOR_GRAY = 0x10 }; using UnitPos = std::pair; void RedrawUnit( const fheroes2::Rect & pos, const Battle::Unit & unit, const bool revert, const bool isCurrentUnit, const uint8_t currentUnitColor, fheroes2::Image & output ) const; std::weak_ptr _orders; int _army2Color; fheroes2::Rect _area; std::vector _rects; }; class PopupDamageInfo : public Dialog::FrameBorder { public: PopupDamageInfo(); PopupDamageInfo( const PopupDamageInfo & ) = delete; PopupDamageInfo & operator=( const PopupDamageInfo & ) = delete; void setBattleUIRect( const fheroes2::Rect & battleUIRect ); void SetAttackInfo( const Cell * cell, const Unit * attacker, const Unit * defender ); void SetSpellAttackInfo( const Cell * cell, const HeroBase * hero, const Unit * defender, const Spell spell ); void Reset(); void Redraw() const; private: bool SetDamageInfoBase( const Cell * cell, const Unit * defender ); fheroes2::Rect _battleUIRect; const Cell * _cell; const Unit * _defender; uint32_t _minDamage; uint32_t _maxDamage; bool _redraw; }; class Interface { public: Interface( Arena & battleArena, const int32_t tileIndex ); Interface( const Interface & ) = delete; ~Interface(); Interface & operator=( const Interface & ) = delete; void fullRedraw(); // only at the start of the battle void Redraw(); void RedrawPartialStart(); void RedrawPartialFinish(); void getPendingActions( Actions & actions ); void HumanTurn( const Unit & unit, Actions & actions ); const fheroes2::Rect & GetArea() const { return _surfaceInnerArea; } // Battlefield interface ROI. const fheroes2::Rect & GetInterfaceRoi() const { return _interfacePosition; } fheroes2::Point GetMouseCursor() const; void SetStatus( const std::string & message, const bool top = false ); void SetOrderOfUnits( const std::shared_ptr & units ); void FadeArena( const bool clearMessageLog ); void RedrawActionNewTurn() const; void RedrawActionAttackPart1( Unit & attacker, const Unit & defender, const TargetsInfo & targets ); void RedrawActionAttackPart2( Unit & attacker, const Unit & defender, const TargetsInfo & targets, const uint32_t resurrects ); void RedrawActionSpellCastStatus( const Spell & spell, int32_t dst, const std::string & name, const TargetsInfo & targets ); void RedrawActionSpellCastPart1( const Spell & spell, int32_t dst, const HeroBase * caster, const TargetsInfo & targets ); void RedrawActionSpellCastPart2( const Spell & spell, const TargetsInfo & targets ); void RedrawActionResistSpell( const Unit & target, const bool playSound ); void RedrawActionMonsterSpellCastStatus( const Spell & spell, const Unit & attacker, const TargetInfo & target ); void RedrawActionMove( Unit & unit, const Indexes & path ); void RedrawActionFly( Unit & unit, const Position & pos ); void RedrawActionMorale( Unit & unit, const bool isGoodMorale ); void RedrawActionLuck( const Unit & unit ); void RedrawActionTowerPart1( const Tower & tower, const Unit & defender ); void RedrawActionTowerPart2( const Tower & tower, const TargetInfo & target ); void RedrawActionCatapultPart1( const CastleDefenseElement catapultTarget, const bool isHit ); void RedrawActionCatapultPart2( const CastleDefenseElement catapultTarget ); void RedrawActionTeleportSpell( Unit & target, const int32_t dst ); void RedrawActionEarthQuakeSpell( const std::vector & targets ); void RedrawActionSummonElementalSpell( Unit & target ); void RedrawActionMirrorImageSpell( const Unit & target, const Position & pos ); void RedrawActionSkipStatus( const Unit & unit ); void RedrawActionRemoveMirrorImage( const std::vector & mirrorImages ); void RedrawBridgeAnimation( const bool bridgeDownAnimation ); void RedrawMissileAnimation( const fheroes2::Point & startPos, const fheroes2::Point & endPos, const double angle, const uint32_t monsterID ); private: enum CreatureSpellAnimation { NONE, WINCE, RESURRECT }; void HumanBattleTurn( const Unit & unit, Actions & actions, std::string & msg ); void HumanCastSpellTurn( const Unit & /* unused */, Actions & actions, std::string & msg ); void RedrawCover(); void _redrawBattleGround(); void _redrawCoverStatic(); void RedrawLowObjects( const int32_t cellId ); void RedrawHighObjects( const int32_t cellId ); void RedrawCastle( const Castle & castle, const int32_t cellId ); void RedrawCastleMainTower( const Castle & castle ); void RedrawKilled(); void RedrawInterface(); void RedrawOpponents(); void RedrawOpponentsFlags(); void redrawPreRender(); void RedrawArmies(); void RedrawTroopSprite( const Unit & unit ); fheroes2::Point drawTroopSprite( const Unit & unit, const fheroes2::Sprite & troopSprite ); void RedrawTroopCount( const Unit & unit ); void RedrawActionWincesKills( const TargetsInfo & targets, Unit * attacker = nullptr, const Unit * defender = nullptr ); void RedrawActionArrowSpell( const Unit & target ); void RedrawActionColdRaySpell( Unit & target ); void RedrawActionDisruptingRaySpell( const Unit & target ); void RedrawActionBloodLustSpell( const Unit & target ); void RedrawActionStoneSpell( const Unit & target ); void RedrawActionColdRingSpell( const int32_t dst, const TargetsInfo & targets ); void RedrawActionElementalStormSpell( const TargetsInfo & targets ); void RedrawActionArmageddonSpell(); void RedrawActionHolyShoutSpell( const uint8_t strength ); void RedrawActionResurrectSpell( Unit & target, const Spell & spell ); void RedrawActionDeathWaveSpell( const int32_t strength ); void RedrawActionLightningBoltSpell( const Unit & target ); void RedrawActionChainLightningSpell( const TargetsInfo & targets ); void RedrawLightningOnTargets( const std::vector & points, const fheroes2::Rect & drawRoi ); // helper function void RedrawRaySpell( const Unit & target, const int spellICN, const int spellSound, const int32_t size ); // Wait for all possible battlefield action delays that could be set in previous functions to pass. // Use this if a function may be called from other functions with different render delay types. void WaitForAllActionDelays(); void AnimateOpponents( OpponentSprite * hero ); void AnimateUnitWithDelay( Unit & unit, const bool skipLastFrameRender = false ); void RedrawTroopDefaultDelay( Unit & unit ); void RedrawTroopWithFrameAnimation( Unit & unit, const int icn, const int m82, const CreatureSpellAnimation animation ); void RedrawTargetsWithFrameAnimation( const int32_t dst, const TargetsInfo & targets, const int icn, const int m82, int repeatCount = 0 ); void RedrawTargetsWithFrameAnimation( const TargetsInfo & targets, const int icn, const int m82, const bool wnce ); bool IdleTroopsAnimation() const; void ResetIdleTroopAnimation() const; void SwitchAllUnitsAnimation( const int32_t animationState ) const; void UpdateContourColor(); void CheckGlobalEvents( LocalEvent & ); void SetHeroAnimationReactionToTroopDeath( const int32_t deathColor ) const; void ProcessingHeroDialogResult( const int result, Actions & actions ); void _openBattleSettingsDialog(); void EventAutoSwitch( const Unit & unit, Actions & actions ); void EventAutoFinish( Actions & actions ); void EventShowOptions(); void ButtonAutoAction( const Unit & unit, Actions & actions ); void ButtonSettingsAction(); void ButtonSkipAction( Actions & actions ); void MouseLeftClickBoardAction( const int themes, const Cell & cell, const bool isConfirmed, Actions & actions ); void MousePressRightBoardAction( const Cell & cell ) const; int GetBattleCursor( std::string & statusMsg ) const; int GetBattleSpellCursor( std::string & statusMsg ) const; Arena & arena; Dialog::FrameBorder border; fheroes2::Rect _interfacePosition; fheroes2::Rect _surfaceInnerArea; fheroes2::Image _mainSurface; fheroes2::Image _battleGround; fheroes2::Image _hexagonGrid; fheroes2::Image _hexagonShadow; fheroes2::Image _hexagonGridShadow; fheroes2::Image _hexagonCursorShadow; int icn_cbkg; int icn_frng; fheroes2::Button btn_auto; fheroes2::Button btn_settings; fheroes2::Button btn_skip; Status status; std::unique_ptr _opponent1; std::unique_ptr _opponent2; Spell humanturn_spell; bool humanturn_exit; bool humanturn_redraw; uint32_t animation_flags_frame; int catapult_frame; int _interruptAutoBattleForColor; // The Channel ID of pre-battle sound. Used to check it is over to start the battle music. std::optional _preBattleSoundChannelId{ -1 }; uint8_t _contourColor; bool _brightLandType; // used to determine current monster contour cycling colors uint32_t _contourCycle; const Unit * _currentUnit; const Unit * _movingUnit; const Unit * _flyingUnit; const fheroes2::Sprite * b_current_sprite; fheroes2::Point _movingPos; fheroes2::Point _flyingPos; int32_t index_pos; // Index of the cell selected as the source for the Teleport spell int32_t _teleportSpellSrcIdx; fheroes2::Rect main_tower; std::unique_ptr listlog; PopupDamageInfo popup; TurnOrder _turnOrder; std::unique_ptr _background; struct BridgeMovementAnimation { enum AnimationStatusId : uint32_t { DOWN_POSITION = 21, UP_POSITION = 23, DESTROYED = 24 }; bool animationIsRequired; uint32_t currentFrameId; }; BridgeMovementAnimation _bridgeAnimation; // TODO: While currently we don't need to persist 'UnitSpellEffectInfos' between render functions, // this may be needed in the future (for example, in expansion) to display some sprites over // troops for some time (e.g. long duration spell effects or other permanent effects). std::vector _unitSpellEffectInfos; struct BoardActionIntent { int cursorTheme = Cursor::NONE; int32_t cellIndex = -1; bool operator==( const BoardActionIntent & other ) const { return cursorTheme == other.cursorTheme && cellIndex == other.cellIndex; } }; // Intents are used to confirm actions in combat performed using touch gestures BoardActionIntent _boardActionIntent; class BoardActionIntentUpdater { public: BoardActionIntentUpdater( BoardActionIntent & storedIntent, const bool isFromTouchpad ) : _storedIntent( storedIntent ) , _isFromTouchpad( isFromTouchpad ) {} BoardActionIntentUpdater( const BoardActionIntentUpdater & ) = delete; ~BoardActionIntentUpdater() { // Do not remember intermediate touch gestures as intents if ( _isFromTouchpad ) { return; } _storedIntent = _intent.value_or( BoardActionIntent{} ); } BoardActionIntentUpdater & operator=( const BoardActionIntentUpdater & ) = delete; void setIntent( const BoardActionIntent & intent ) { _intent = intent; } bool isConfirmed() { // If the mouse event has been triggered by the touchpad, it should be considered confirmed only if this event orders to // perform the same action that is already indicated on the battle board with the mouse cursor. return ( !_isFromTouchpad || _storedIntent == _intent ); } private: BoardActionIntent & _storedIntent; const bool _isFromTouchpad; std::optional _intent; }; }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_main.cpp000066400000000000000000000450121456075706000230050ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include "ai.h" #include "army.h" #include "army_troop.h" #include "artifact.h" #include "battle.h" #include "battle_arena.h" #include "battle_army.h" #include "campaign_savedata.h" #include "captain.h" #include "dialog.h" #include "game.h" #include "heroes.h" #include "heroes_base.h" #include "kingdom.h" #include "logging.h" #include "monster.h" #include "players.h" #include "rand.h" #include "resource.h" #include "settings.h" #include "skill.h" #include "spell.h" #include "spell_storage.h" #include "tools.h" #include "translations.h" #include "ui_dialog.h" #include "ui_text.h" #include "world.h" namespace { std::vector planArtifactTransfer( const BagArtifacts & winnerBag, const BagArtifacts & loserBag ) { std::vector artifacts; // Calculate how many free slots the winner has in his bag. size_t availableArtifactSlots = 0; for ( const Artifact & artifact : winnerBag ) { if ( !artifact.isValid() ) { ++availableArtifactSlots; } } for ( const Artifact & artifact : loserBag ) { if ( availableArtifactSlots == 0 ) { break; } if ( artifact.isValid() && artifact.GetID() != Artifact::MAGIC_BOOK && !artifact.isUltimate() ) { artifacts.push_back( artifact ); --availableArtifactSlots; } } // One more pass to put all the ultimate artifacts at the end. for ( const Artifact & artifact : loserBag ) { if ( artifact.isUltimate() ) { artifacts.push_back( artifact ); } } return artifacts; } void transferArtifacts( BagArtifacts & winnerBag, const std::vector & artifacts ) { size_t artifactPos = 0; for ( Artifact & artifact : winnerBag ) { if ( artifact.isValid() ) { continue; } for ( ; artifactPos < artifacts.size(); ++artifactPos ) { if ( !artifacts[artifactPos].isUltimate() ) { artifact = artifacts[artifactPos]; ++artifactPos; break; } } if ( artifactPos >= artifacts.size() ) { break; } } } void clearArtifacts( BagArtifacts & bag ) { for ( Artifact & artifact : bag ) { if ( artifact.isValid() && artifact.GetID() != Artifact::MAGIC_BOOK ) { artifact = Artifact::UNKNOWN; } } } uint32_t computeBattleSeed( const int32_t mapIndex, const uint32_t mapSeed, const Army & army1, const Army & army2 ) { uint32_t seed = static_cast( mapIndex ) + mapSeed; for ( size_t i = 0; i < army1.Size(); ++i ) { const Troop * troop = army1.GetTroop( i ); if ( troop->isValid() ) { fheroes2::hashCombine( seed, troop->GetID() ); fheroes2::hashCombine( seed, troop->GetCount() ); } else { fheroes2::hashCombine( seed, 0 ); } } for ( size_t i = 0; i < army2.Size(); ++i ) { const Troop * troop = army2.GetTroop( i ); if ( troop->isValid() ) { fheroes2::hashCombine( seed, troop->GetID() ); fheroes2::hashCombine( seed, troop->GetCount() ); } else { fheroes2::hashCombine( seed, 0 ); } } return seed; } uint32_t getBattleResult( const uint32_t army ) { if ( army & Battle::RESULT_SURRENDER ) return Battle::RESULT_SURRENDER; if ( army & Battle::RESULT_RETREAT ) return Battle::RESULT_RETREAT; if ( army & Battle::RESULT_LOSS ) return Battle::RESULT_LOSS; if ( army & Battle::RESULT_WINS ) return Battle::RESULT_WINS; return 0; } void eagleEyeSkillAction( HeroBase & hero, const SpellStorage & spells, bool local, Rand::DeterministicRandomGenerator & randomGenerator ) { if ( spells.empty() || !hero.HaveSpellBook() ) return; SpellStorage new_spells; new_spells.reserve( 10 ); const Skill::Secondary eagleeye( Skill::Secondary::EAGLE_EYE, hero.GetLevelSkill( Skill::Secondary::EAGLE_EYE ) ); // filter spells for ( const Spell & sp : spells ) { if ( hero.HaveSpell( sp ) ) { continue; } switch ( eagleeye.Level() ) { case Skill::Level::BASIC: // 20% if ( 3 > sp.Level() && eagleeye.GetValues() >= randomGenerator.Get( 1, 100 ) ) new_spells.push_back( sp ); break; case Skill::Level::ADVANCED: // 30% if ( 4 > sp.Level() && eagleeye.GetValues() >= randomGenerator.Get( 1, 100 ) ) new_spells.push_back( sp ); break; case Skill::Level::EXPERT: // 40% if ( 5 > sp.Level() && eagleeye.GetValues() >= randomGenerator.Get( 1, 100 ) ) new_spells.push_back( sp ); break; default: break; } } // add new spell if ( local ) { for ( const Spell & sp : new_spells ) { std::string msg = _( "Through eagle-eyed observation, %{name} is able to learn the magic spell %{spell}." ); StringReplace( msg, "%{name}", hero.GetName() ); StringReplace( msg, "%{spell}", sp.GetName() ); Game::PlayPickupSound(); const fheroes2::SpellDialogElement spellUI( sp, &hero ); fheroes2::showMessage( fheroes2::Text( "", {} ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK, { &spellUI } ); } } hero.AppendSpellsToBook( new_spells, true ); } void necromancySkillAction( HeroBase & hero, const uint32_t enemyTroopsKilled, const bool isControlHuman ) { Army & army = hero.GetArmy(); if ( 0 == enemyTroopsKilled || ( army.isFullHouse() && !army.HasMonster( Monster::SKELETON ) ) ) return; const uint32_t necromancyPercent = GetNecromancyPercent( hero ); uint32_t raiseCount = std::max( enemyTroopsKilled * necromancyPercent / 100, 1U ); army.JoinTroop( Monster::SKELETON, raiseCount, false ); if ( isControlHuman ) { Battle::Arena::DialogBattleNecromancy( raiseCount ); } DEBUG_LOG( DBG_BATTLE, DBG_TRACE, "raise: " << raiseCount << " skeletons" ) } Kingdom * getKingdomOfCommander( const HeroBase * commander ) { if ( commander == nullptr ) { return nullptr; } if ( commander->isHeroes() ) { const Heroes * hero = dynamic_cast( commander ); assert( hero != nullptr ); return &hero->GetKingdom(); } if ( commander->isCaptain() ) { const Captain * captain = dynamic_cast( commander ); assert( captain != nullptr ); return &world.GetKingdom( captain->GetColor() ); } assert( 0 ); return nullptr; } void restoreFundsOfCommandersKingdom( const HeroBase * commander, const Funds & initialFunds ) { Kingdom * kingdom = getKingdomOfCommander( commander ); assert( kingdom != nullptr ); const Funds fundsDiff = kingdom->GetFunds() - initialFunds; assert( kingdom->AllowPayment( fundsDiff ) ); kingdom->OddFundsResource( fundsDiff ); assert( kingdom->GetFunds() == initialFunds ); } } Battle::Result Battle::Loader( Army & army1, Army & army2, int32_t mapsindex ) { Result result; // Validate the arguments - check if battle should even load if ( !army1.isValid() || !army2.isValid() ) { // Check second army first so attacker would win by default if ( !army2.isValid() ) { result.army1 = RESULT_WINS; DEBUG_LOG( DBG_BATTLE, DBG_WARN, "Invalid battle detected! Index " << mapsindex << ", Army: " << army2.String() ) } else { result.army2 = RESULT_WINS; DEBUG_LOG( DBG_BATTLE, DBG_WARN, "Invalid battle detected! Index " << mapsindex << ", Army: " << army1.String() ) } return result; } HeroBase * commander1 = army1.GetCommander(); if ( commander1 ) { commander1->ActionPreBattle(); if ( army1.isControlAI() ) { AI::Get().HeroesPreBattle( *commander1, true ); } } const uint32_t initialSpellPoints1 = [commander1]() -> uint32_t { if ( commander1 == nullptr ) { return 0; } return commander1->GetSpellPoints(); }(); const Funds initialFunds1 = [commander1]() -> Funds { const Kingdom * kingdom = getKingdomOfCommander( commander1 ); if ( kingdom == nullptr ) { return {}; } return kingdom->GetFunds(); }(); HeroBase * commander2 = army2.GetCommander(); if ( commander2 ) { commander2->ActionPreBattle(); if ( army2.isControlAI() ) { AI::Get().HeroesPreBattle( *commander2, false ); } } const uint32_t initialSpellPoints2 = [commander2]() -> uint32_t { if ( commander2 == nullptr ) { return 0; } return commander2->GetSpellPoints(); }(); const Funds initialFunds2 = [commander2]() -> Funds { const Kingdom * kingdom = getKingdomOfCommander( commander2 ); if ( kingdom == nullptr ) { return {}; } return kingdom->GetFunds(); }(); const bool isHumanBattle = army1.isControlHuman() || army2.isControlHuman(); const Settings & conf = Settings::Get(); bool showBattle = !conf.BattleAutoResolve() && isHumanBattle; #ifdef WITH_DEBUG if ( !showBattle ) { // The battle is always shown either in battle debugging mode ... if ( IS_DEBUG( DBG_BATTLE, DBG_TRACE ) ) { showBattle = true; } // ... or when any of the participating human players are controlled by AI else { const Player * player1 = Players::Get( army1.GetColor() ); const Player * player2 = Players::Get( army2.GetColor() ); if ( ( player1 != nullptr && player1->isAIAutoControlMode() ) || ( player2 != nullptr && player2->isAIAutoControlMode() ) ) { showBattle = true; } } } #endif const uint32_t battleSeed = computeBattleSeed( mapsindex, world.GetMapSeed(), army1, army2 ); while ( true ) { Rand::DeterministicRandomGenerator randomGenerator( battleSeed ); Arena arena( army1, army2, mapsindex, showBattle, randomGenerator ); DEBUG_LOG( DBG_BATTLE, DBG_INFO, "army1 " << army1.String() ) DEBUG_LOG( DBG_BATTLE, DBG_INFO, "army2 " << army2.String() ) while ( arena.BattleValid() ) { arena.Turns(); } result = arena.GetResult(); HeroBase * const winnerHero = ( result.army1 & RESULT_WINS ? commander1 : ( result.army2 & RESULT_WINS ? commander2 : nullptr ) ); HeroBase * const loserHero = ( result.army1 & RESULT_LOSS ? commander1 : ( result.army2 & RESULT_LOSS ? commander2 : nullptr ) ); const uint32_t lossResult = result.army1 & RESULT_LOSS ? result.army1 : result.army2; const bool loserAbandoned = !( ( RESULT_RETREAT | RESULT_SURRENDER ) & lossResult ); const std::vector artifactsToTransfer = winnerHero && loserHero && loserAbandoned && winnerHero->isHeroes() && loserHero->isHeroes() ? planArtifactTransfer( winnerHero->GetBagArtifacts(), loserHero->GetBagArtifacts() ) : std::vector(); if ( showBattle ) { const bool clearMessageLog = ( result.army1 & ( RESULT_RETREAT | RESULT_SURRENDER ) ) || ( result.army2 & ( RESULT_RETREAT | RESULT_SURRENDER ) ); arena.FadeArena( clearMessageLog ); } if ( isHumanBattle && arena.DialogBattleSummary( result, artifactsToTransfer, !showBattle ) ) { // If dialog returns true we will restart battle in manual mode showBattle = true; // Reset the state of army commanders and the state of their kingdoms' finances (one of them could spend gold to surrender, and the other could accept this // gold). Please note that heroes can also surrender to castle captains. if ( commander1 ) { commander1->SetSpellPoints( initialSpellPoints1 ); restoreFundsOfCommandersKingdom( commander1, initialFunds1 ); } if ( commander2 ) { commander2->SetSpellPoints( initialSpellPoints2 ); restoreFundsOfCommandersKingdom( commander2, initialFunds2 ); } continue; } if ( loserHero != nullptr && loserAbandoned ) { // If a hero lost the battle and didn't flee or surrender, they lose all artifacts clearArtifacts( loserHero->GetBagArtifacts() ); // If the other army also had a hero, some artifacts may be captured by them if ( winnerHero != nullptr ) { BagArtifacts & bag = winnerHero->GetBagArtifacts(); transferArtifacts( bag, artifactsToTransfer ); const auto assembledArtifacts = bag.assembleArtifactSetIfPossible(); if ( winnerHero->isControlHuman() ) { std::for_each( assembledArtifacts.begin(), assembledArtifacts.end(), Dialog::ArtifactSetAssembled ); } } if ( loserHero->isControlAI() ) { const Heroes * loserAdventureHero = dynamic_cast( loserHero ); if ( loserAdventureHero != nullptr && conf.isCampaignGameType() ) { Campaign::CampaignSaveData::Get().setEnemyDefeatedAward( loserAdventureHero->GetID() ); } } } arena.GetForce1().SyncArmyCount(); arena.GetForce2().SyncArmyCount(); if ( commander1 ) { if ( army1.isControlAI() ) { AI::Get().HeroesAfterBattle( *commander1, true ); } else { commander1->ActionAfterBattle(); } } if ( commander2 ) { if ( army2.isControlAI() ) { AI::Get().HeroesAfterBattle( *commander2, false ); } else { commander2->ActionAfterBattle(); } } if ( winnerHero && loserHero && winnerHero->GetLevelSkill( Skill::Secondary::EAGLE_EYE ) && loserHero->isHeroes() ) { eagleEyeSkillAction( *winnerHero, arena.GetUsageSpells(), winnerHero->isControlHuman(), randomGenerator ); } if ( winnerHero && winnerHero->GetLevelSkill( Skill::Secondary::NECROMANCY ) ) { necromancySkillAction( *winnerHero, result.killed, winnerHero->isControlHuman() ); } if ( winnerHero ) { const Heroes * kingdomHero = dynamic_cast( winnerHero ); if ( kingdomHero ) { Kingdom & kingdom = kingdomHero->GetKingdom(); kingdom.SetLastBattleWinHero( *kingdomHero ); } } break; } DEBUG_LOG( DBG_BATTLE, DBG_INFO, "army1 " << army1.String() ) DEBUG_LOG( DBG_BATTLE, DBG_INFO, "army2 " << army2.String() ) army1.resetInvalidMonsters(); army2.resetInvalidMonsters(); // Reset the hero's army to the minimum army if the hero retreated or was defeated if ( commander1 && commander1->isHeroes() && ( !army1.isValid() || ( result.army1 & RESULT_RETREAT ) ) ) { army1.Reset( false ); } if ( commander2 && commander2->isHeroes() && ( !army2.isValid() || ( result.army2 & RESULT_RETREAT ) ) ) { army2.Reset( false ); } DEBUG_LOG( DBG_BATTLE, DBG_INFO, "army1: " << ( result.army1 & RESULT_WINS ? "wins" : "loss" ) << ", army2: " << ( result.army2 & RESULT_WINS ? "wins" : "loss" ) ) return result; } uint32_t Battle::Result::AttackerResult() const { return getBattleResult( army1 ); } uint32_t Battle::Result::DefenderResult() const { return getBattleResult( army2 ); } uint32_t Battle::Result::GetExperienceAttacker() const { return exp1; } uint32_t Battle::Result::GetExperienceDefender() const { return exp2; } bool Battle::Result::AttackerWins() const { return ( army1 & RESULT_WINS ) != 0; } bool Battle::Result::DefenderWins() const { return ( army2 & RESULT_WINS ) != 0; } fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_only.cpp000066400000000000000000000631301456075706000230430ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2011 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "battle_only.h" #include #include #include #include #include #include #include "agg_image.h" #include "army_bar.h" #include "army_troop.h" #include "battle.h" #include "color.h" #include "cursor.h" #include "dialog.h" #include "dialog_selectitems.h" #include "game_hotkeys.h" #include "heroes.h" #include "heroes_base.h" #include "heroes_indicator.h" #include "icn.h" #include "image.h" #include "localevent.h" #include "monster.h" #include "race.h" #include "screen.h" #include "settings.h" #include "skill.h" #include "skill_bar.h" #include "spell_book.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_text.h" #include "ui_window.h" #include "world.h" namespace { const uint32_t primaryMaxValue = 20; const int32_t primarySkillIconSize{ 33 }; const std::array playerColor{ Color::BLUE, Color::RED }; const std::array moraleAndLuckOffsetX{ 34, 571 }; const std::array primarySkillOffsetX{ 216, 389 }; const std::array secondarySkillOffsetX{ 22, 353 }; const std::array artifactOffsetX{ 23, 367 }; const std::array armyOffsetX{ 36, 381 }; } void Battle::ControlInfo::Redraw() const { fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::Sprite & cell = fheroes2::AGG::GetICN( ICN::CELLWIN, 1 ); const fheroes2::Sprite & mark = fheroes2::AGG::GetICN( ICN::CELLWIN, 2 ); fheroes2::Blit( cell, display, rtLocal.x, rtLocal.y ); if ( result & CONTROL_HUMAN ) fheroes2::Blit( mark, display, rtLocal.x + 3, rtLocal.y + 2 ); fheroes2::Text text( _( "Human" ), fheroes2::FontType::smallWhite() ); text.draw( rtLocal.x + cell.width() + 5, rtLocal.y + 5, display ); fheroes2::Blit( cell, display, rtAI.x, rtAI.y ); if ( result & CONTROL_AI ) fheroes2::Blit( mark, display, rtAI.x + 3, rtAI.y + 2 ); text.set( _( "AI" ), fheroes2::FontType::smallWhite() ); text.draw( rtAI.x + cell.width() + 5, rtAI.y + 5, display ); } Battle::Only::Only() { armyInfo[1].armyId = 1; for ( auto & info : armyInfo ) { info.monster.GetTroop( 0 )->Set( Monster::PEASANT, 100 ); info.monsterBackup.Assign( info.monster ); } armyInfo[0].controlType = CONTROL_HUMAN; armyInfo[0].player.SetControl( armyInfo[0].controlType ); armyInfo[0].player.SetColor( playerColor[0] ); armyInfo[1].controlType = CONTROL_AI; armyInfo[1].player.SetControl( armyInfo[1].controlType ); armyInfo[1].player.SetColor( playerColor[1] ); } bool Battle::Only::setup( const bool allowBackup, bool & reset ) { reset = false; fheroes2::Display & display = fheroes2::Display::instance(); LocalEvent & le = LocalEvent::Get(); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); const fheroes2::StandardWindow frameborder( fheroes2::Display::DEFAULT_WIDTH, fheroes2::Display::DEFAULT_HEIGHT, false ); const fheroes2::Point cur_pt( frameborder.activeArea().x, frameborder.activeArea().y ); armyInfo[0].portraitRoi = { cur_pt.x + 93, cur_pt.y + 72, 101, 93 }; armyInfo[1].portraitRoi = { cur_pt.x + 445, cur_pt.y + 72, 101, 93 }; const std::array attackRoi{ fheroes2::Rect( cur_pt.x + 215, cur_pt.y + 50, primarySkillIconSize, primarySkillIconSize ), fheroes2::Rect( cur_pt.x + 390, cur_pt.y + 50, primarySkillIconSize, primarySkillIconSize ) }; const std::array defenseRoi{ fheroes2::Rect( cur_pt.x + 215, cur_pt.y + 83, primarySkillIconSize, primarySkillIconSize ), fheroes2::Rect( cur_pt.x + 390, cur_pt.y + 83, primarySkillIconSize, primarySkillIconSize ) }; const std::array powerRoi{ fheroes2::Rect( cur_pt.x + 215, cur_pt.y + 116, primarySkillIconSize, primarySkillIconSize ), fheroes2::Rect( cur_pt.x + 390, cur_pt.y + 116, primarySkillIconSize, primarySkillIconSize ) }; const std::array knowledgeRoi{ fheroes2::Rect( cur_pt.x + 215, cur_pt.y + 149, primarySkillIconSize, primarySkillIconSize ), fheroes2::Rect( cur_pt.x + 390, cur_pt.y + 149, primarySkillIconSize, primarySkillIconSize ) }; armyInfo[0].player.SetControl( armyInfo[0].controlType ); armyInfo[1].player.SetControl( armyInfo[1].controlType ); for ( auto & info : armyInfo ) { info.hero = nullptr; info.monster.Reset(); info.monster.GetTroop( 0 )->Set( Monster::PEASANT, 100 ); if ( !_backupCompleted || !allowBackup ) { continue; } if ( info.isHeroPresent ) { info.hero = world.GetHeroes( info.heroBackup.GetID() ); info.hero->GetSecondarySkills().FillMax( Skill::Secondary() ); copyHero( info.heroBackup, *info.hero ); } else { info.monster.Assign( info.monsterBackup ); } } if ( !_backupCompleted || !allowBackup ) { armyInfo[0].hero = world.GetHeroes( Heroes::LORDKILBURN ); armyInfo[0].isHeroPresent = true; } const fheroes2::Sprite & background = fheroes2::AGG::GetICN( ICN::SWAPWIN, 0 ); fheroes2::Copy( background, 0, 0, display, cur_pt.x, cur_pt.y, background.width(), background.height() ); redrawOpponents( cur_pt ); redrawOpponentsStats( cur_pt ); for ( auto & info : armyInfo ) { if ( info.hero != nullptr ) { info.hero->GetSecondarySkills().FillMax( Skill::Secondary() ); updateHero( info, cur_pt ); } else { info.ui.army = std::make_unique( &info.monster, true, false, true ); info.ui.army->setTableSize( { 5, 1 } ); info.ui.army->setRenderingOffset( { cur_pt.x + armyOffsetX[info.armyId], cur_pt.y + 267 } ); info.ui.army->setInBetweenItemsOffset( { 2, 0 } ); } } if ( armyInfo[1].hero != nullptr ) { attackedArmyControlInfo = std::make_unique( fheroes2::Point{ cur_pt.x + 500, cur_pt.y + 425 }, armyInfo[1].player.GetControl() ); } for ( const auto & info : armyInfo ) { info.ui.redraw( display ); } if ( attackedArmyControlInfo ) { attackedArmyControlInfo->Redraw(); } // hide the swap army/artifact arrows const fheroes2::Sprite & stoneBackground = fheroes2::AGG::GetICN( ICN::STONEBAK, 0 ); fheroes2::Copy( stoneBackground, 292, 270, display, cur_pt.x + 292, cur_pt.y + 270, 48, 44 ); fheroes2::Copy( stoneBackground, 292, 363, display, cur_pt.x + 292, cur_pt.y + 363, 48, 44 ); // hide the shadow from the original EXIT button const fheroes2::Sprite buttonOverride = fheroes2::Crop( fheroes2::AGG::GetICN( ICN::SWAPWIN, 0 ), 122, 428, 84, 32 ); fheroes2::Copy( buttonOverride, 0, 0, display, cur_pt.x + 276, cur_pt.y + 428, 84, 32 ); fheroes2::Button buttonReset( cur_pt.x + 30, cur_pt.y + 428, ICN::BUTTON_RESET_GOOD, 0, 1 ); fheroes2::Button buttonStart( cur_pt.x + 178, cur_pt.y + 428, ICN::BUTTON_START_GOOD, 0, 1 ); fheroes2::Button buttonExit( cur_pt.x + 366, cur_pt.y + 428, ICN::BUTTON_EXIT_GOOD, 0, 1 ); fheroes2::addGradientShadow( fheroes2::AGG::GetICN( ICN::BUTTON_RESET_GOOD, 0 ), display, buttonReset.area().getPosition(), { -5, 5 } ); fheroes2::addGradientShadow( fheroes2::AGG::GetICN( ICN::BUTTON_START_GOOD, 0 ), display, buttonStart.area().getPosition(), { -5, 5 } ); fheroes2::addGradientShadow( fheroes2::AGG::GetICN( ICN::BUTTON_EXIT_GOOD, 0 ), display, buttonExit.area().getPosition(), { -5, 5 } ); buttonStart.draw(); buttonExit.draw(); buttonReset.draw(); display.render(); bool result = false; while ( le.HandleEvents() ) { bool updateSpellPoints = false; bool needRender = false; bool needRedrawOpponentsStats = false; bool needRedrawControlInfo = false; buttonStart.isEnabled() && le.MousePressLeft( buttonStart.area() ) ? buttonStart.drawOnPress() : buttonStart.drawOnRelease(); buttonExit.isEnabled() && le.MousePressLeft( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); buttonReset.isEnabled() && le.MousePressLeft( buttonReset.area() ) ? buttonReset.drawOnPress() : buttonReset.drawOnRelease(); if ( ( buttonStart.isEnabled() && le.MouseClickLeft( buttonStart.area() ) ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) ) { result = true; break; } if ( le.MouseClickLeft( buttonReset.area() ) ) { reset = true; result = true; break; } if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { break; } if ( le.MousePressRight( buttonStart.area() ) ) { fheroes2::showStandardTextMessage( _( "Start" ), _( "Start the battle." ), 0 ); } else if ( le.MousePressRight( buttonExit.area() ) ) { fheroes2::showStandardTextMessage( _( "Exit" ), _( "Exit this menu." ), 0 ); } else if ( le.MousePressRight( buttonReset.area() ) ) { fheroes2::showStandardTextMessage( _( "Reset" ), _( "Reset to default settings." ), 0 ); } for ( const auto & [firstId, secondId] : { std::pair( 0, 1 ), std::pair( 1, 0 ) } ) { ArmyInfo & first = armyInfo[firstId]; const ArmyInfo & second = armyInfo[secondId]; if ( le.MouseClickLeft( first.portraitRoi ) ) { const int hid = Dialog::selectHeroes( first.hero ? first.hero->GetID() : Heroes::UNKNOWN ); if ( second.hero && hid == second.hero->GetID() ) { fheroes2::showStandardTextMessage( _( "Error" ), _( "Please select another hero." ), Dialog::OK ); } else if ( Heroes::UNKNOWN != hid ) { first.hero = world.GetHeroes( hid ); if ( first.hero ) { first.hero->GetSecondarySkills().FillMax( Skill::Secondary() ); } updateHero( first, cur_pt ); } redrawOpponents( cur_pt ); first.needRedraw = true; needRedrawOpponentsStats = true; // User can not click two hero portraits at the same time so we can break the loop. break; } } if ( attackedArmyControlInfo == nullptr && armyInfo[1].hero != nullptr ) { attackedArmyControlInfo = std::make_unique( fheroes2::Point{ cur_pt.x + 500, cur_pt.y + 425 }, armyInfo[1].player.GetControl() ); needRedrawControlInfo = true; } for ( const auto & [hero, index] : { std::pair( armyInfo[0].hero, 0 ), std::pair( armyInfo[1].hero, 1 ) } ) { if ( hero == nullptr ) { continue; } if ( le.MouseClickLeft( attackRoi[index] ) ) { uint32_t value = hero->attack; if ( Dialog::SelectCount( _( "Set Attack Skill" ), 0, primaryMaxValue, value ) ) { hero->attack = static_cast( value ); needRedrawOpponentsStats = true; } } else if ( le.MouseClickLeft( defenseRoi[index] ) ) { uint32_t value = hero->defense; if ( Dialog::SelectCount( _( "Set Defense Skill" ), 0, primaryMaxValue, value ) ) { hero->defense = static_cast( value ); needRedrawOpponentsStats = true; } } else if ( le.MouseClickLeft( powerRoi[index] ) ) { uint32_t value = hero->power; if ( Dialog::SelectCount( _( "Set Power Skill" ), 0, primaryMaxValue, value ) ) { hero->power = static_cast( value ); needRedrawOpponentsStats = true; } } else if ( le.MouseClickLeft( knowledgeRoi[index] ) ) { uint32_t value = hero->knowledge; if ( Dialog::SelectCount( _( "Set Knowledge Skill" ), 0, primaryMaxValue, value ) ) { hero->knowledge = static_cast( value ); updateSpellPoints = true; needRedrawOpponentsStats = true; } } } for ( const auto & [firstId, secondId] : { std::pair{ 0, 1 }, std::pair{ 1, 0 } } ) { const ArmyUI & firstUI = armyInfo[firstId].ui; const ArmyUI & secondUI = armyInfo[secondId].ui; if ( firstUI.army != nullptr && le.MouseCursor( firstUI.army->GetArea() ) && firstUI.army->QueueEventProcessing() ) { if ( firstUI.artifact != nullptr && firstUI.artifact->isSelected() ) { firstUI.artifact->ResetSelected(); } if ( secondUI.artifact != nullptr && secondUI.artifact->isSelected() ) { secondUI.artifact->ResetSelected(); } if ( secondUI.army != nullptr && secondUI.army->isSelected() ) { secondUI.army->ResetSelected(); } armyInfo[firstId].needRedraw = true; } else if ( firstUI.artifact != nullptr && le.MouseCursor( firstUI.artifact->GetArea() ) && firstUI.artifact->QueueEventProcessing() ) { if ( firstUI.army != nullptr && firstUI.army->isSelected() ) { firstUI.army->ResetSelected(); } if ( secondUI.artifact != nullptr && secondUI.artifact->isSelected() ) { secondUI.artifact->ResetSelected(); } if ( secondUI.army != nullptr && secondUI.army->isSelected() ) { secondUI.army->ResetSelected(); } armyInfo[firstId].needRedraw = true; updateSpellPoints = true; needRedrawOpponentsStats = true; } else if ( firstUI.morale != nullptr && le.MouseCursor( firstUI.morale->GetArea() ) ) { MoraleIndicator::QueueEventProcessing( *firstUI.morale ); } else if ( firstUI.luck != nullptr && le.MouseCursor( firstUI.luck->GetArea() ) ) { LuckIndicator::QueueEventProcessing( *firstUI.luck ); } else if ( firstUI.primarySkill != nullptr && le.MouseCursor( firstUI.primarySkill->GetArea() ) ) { firstUI.primarySkill->QueueEventProcessing(); } else if ( firstUI.secondarySkill != nullptr && le.MouseCursor( firstUI.secondarySkill->GetArea() ) && firstUI.secondarySkill->QueueEventProcessing() ) { armyInfo[firstId].needRedraw = true; } } if ( attackedArmyControlInfo ) { assert( armyInfo[1].hero ); if ( le.MouseClickLeft( attackedArmyControlInfo->rtLocal ) && armyInfo[1].player.isControlAI() ) { attackedArmyControlInfo->result = CONTROL_HUMAN; armyInfo[1].player.SetControl( CONTROL_HUMAN ); needRedrawControlInfo = true; } else if ( le.MouseClickLeft( attackedArmyControlInfo->rtAI ) && armyInfo[1].player.isControlHuman() ) { attackedArmyControlInfo->result = CONTROL_AI; armyInfo[1].player.SetControl( CONTROL_AI ); needRedrawControlInfo = true; } } if ( updateSpellPoints ) { for ( Heroes * hero : { armyInfo[0].hero, armyInfo[1].hero } ) { if ( hero == nullptr ) { continue; } hero->SetSpellPoints( hero->GetMaxSpellPoints() ); } } if ( needRedrawOpponentsStats ) { redrawOpponentsStats( cur_pt ); needRender = true; } for ( const int32_t i : { 0, 1 } ) { if ( armyInfo[i].needRedraw ) { armyInfo[i].ui.redraw( display ); armyInfo[i].needRedraw = false; needRender = true; } } if ( needRedrawControlInfo ) { assert( attackedArmyControlInfo != nullptr ); attackedArmyControlInfo->Redraw(); needRender = true; } if ( needRender ) { display.render(); } } armyInfo[0].ui = {}; armyInfo[1].ui = {}; attackedArmyControlInfo.reset(); return result; } void Battle::Only::updateHero( ArmyInfo & info, const fheroes2::Point & offset ) { info.ui.resetForNewHero(); if ( info.hero == nullptr ) { return; } updateArmyUI( info.ui, info.hero, offset, info.armyId ); } void Battle::Only::redrawOpponents( const fheroes2::Point & top ) const { fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::Sprite & background = fheroes2::AGG::GetICN( ICN::SWAPWIN, 0 ); const fheroes2::Rect textRoi( top.x + 89, top.y + 27, 462, 17 ); fheroes2::Copy( background, 89, 27, display, textRoi ); std::string message = _( "%{race1} %{name1} vs %{race2} %{name2}" ); if ( armyInfo[0].hero ) { StringReplace( message, "%{name1}", armyInfo[0].hero->GetName() ); StringReplace( message, "%{race1}", std::string( Race::String( armyInfo[0].hero->GetRace() ) ) ); } else { StringReplace( message, "%{race1}", "" ); StringReplace( message, " %{name1}", _( "Monsters" ) ); } if ( armyInfo[1].hero ) { StringReplace( message, "%{name2}", armyInfo[1].hero->GetName() ); StringReplace( message, "%{race2}", std::string( Race::String( armyInfo[1].hero->GetRace() ) ) ); } else { StringReplace( message, "%{race2}", "" ); StringReplace( message, " %{name2}", _( "Monsters" ) ); } fheroes2::Text text( std::move( message ), fheroes2::FontType::normalWhite() ); text.drawInRoi( top.x + 320 - text.width() / 2, top.y + 29, display, textRoi ); for ( const size_t idx : { 0, 1 } ) { if ( armyInfo[idx].hero ) { const fheroes2::Sprite & port1 = armyInfo[idx].hero->GetPortrait( PORT_BIG ); if ( !port1.empty() ) { fheroes2::Copy( port1, 0, 0, display, armyInfo[idx].portraitRoi ); } } else { fheroes2::Fill( display, armyInfo[idx].portraitRoi.x, armyInfo[idx].portraitRoi.y, armyInfo[idx].portraitRoi.width, armyInfo[idx].portraitRoi.height, 0 ); text.set( _( "N/A" ), fheroes2::FontType::normalWhite() ); text.draw( armyInfo[idx].portraitRoi.x + ( armyInfo[idx].portraitRoi.width - text.width() ) / 2, armyInfo[idx].portraitRoi.y + armyInfo[idx].portraitRoi.height / 2 - 8, display ); } } } void Battle::Only::redrawOpponentsStats( const fheroes2::Point & top ) const { fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::Sprite & background = fheroes2::AGG::GetICN( ICN::SWAPWIN, 0 ); fheroes2::Copy( background, 262, 61, display, top.x + 262, top.y + 61, 115, 109 ); fheroes2::RedrawPrimarySkillInfo( top, armyInfo[0].ui.primarySkill.get(), armyInfo[1].ui.primarySkill.get() ); } void Battle::Only::StartBattle() { assert( armyInfo[0].hero != nullptr ); Settings & conf = Settings::Get(); conf.GetPlayers().Init( armyInfo[0].player.GetColor() | armyInfo[1].player.GetColor() ); world.InitKingdoms(); conf.SetCurrentColor( armyInfo[0].player.GetColor() ); for ( const int32_t idx : { 0, 1 } ) { Players::SetPlayerRace( armyInfo[idx].player.GetColor(), armyInfo[idx].player.GetRace() ); Players::SetPlayerControl( armyInfo[idx].player.GetColor(), armyInfo[idx].player.GetControl() ); armyInfo[idx].controlType = armyInfo[idx].player.GetControl(); armyInfo[idx].isHeroPresent = ( armyInfo[idx].hero != nullptr ); if ( !armyInfo[idx].isHeroPresent ) { armyInfo[idx].monsterBackup.Assign( armyInfo[idx].monster ); continue; } armyInfo[idx].hero->SetSpellPoints( armyInfo[idx].hero->GetMaxSpellPoints() ); armyInfo[idx].hero->Recruit( armyInfo[idx].player.GetColor(), { idx, idx } ); copyHero( *armyInfo[idx].hero, armyInfo[idx].heroBackup ); armyInfo[idx].monster.Reset(); armyInfo[idx].monster.GetTroop( 0 )->Set( Monster::PEASANT, 100 ); armyInfo[idx].monsterBackup.Assign( armyInfo[idx].monster ); } _backupCompleted = true; Battle::Loader( ( armyInfo[0].hero ? armyInfo[0].hero->GetArmy() : armyInfo[0].monster ), ( armyInfo[1].hero ? armyInfo[1].hero->GetArmy() : armyInfo[1].monster ), 1 ); conf.SetCurrentColor( Color::NONE ); } void Battle::Only::reset() { armyInfo[0].reset(); armyInfo[1].reset(); attackedArmyControlInfo.reset(); } void Battle::Only::copyHero( const Heroes & in, Heroes & out ) { out.attack = in.attack; out.defense = in.defense; out.knowledge = in.knowledge; out.power = in.power; out._id = in._id; out.portrait = in.portrait; out._race = in._race; out.secondary_skills.ToVector() = in.secondary_skills.ToVector(); out.army.Assign( in.army ); out.bag_artifacts = in.bag_artifacts; out.spell_book = in.spell_book; out.SetSpellPoints( out.GetMaxSpellPoints() ); } void Battle::Only::updateArmyUI( ArmyUI & ui, Heroes * hero, const fheroes2::Point & offset, const uint8_t armyId ) { assert( hero != nullptr ); ui.morale = std::make_unique( hero ); ui.morale->SetPos( { offset.x + moraleAndLuckOffsetX[armyId], offset.y + 75 } ); ui.luck = std::make_unique( hero ); ui.luck->SetPos( { offset.x + moraleAndLuckOffsetX[armyId], offset.y + 115 } ); ui.primarySkill = std::make_unique( hero, true ); ui.primarySkill->setTableSize( { 1, 4 } ); ui.primarySkill->setInBetweenItemsOffset( { 0, -1 } ); ui.primarySkill->SetTextOff( armyId == 0 ? 70 : -70, -25 ); ui.primarySkill->setRenderingOffset( { offset.x + primarySkillOffsetX[armyId], offset.y + 51 } ); ui.secondarySkill = std::make_unique( *hero, true, true ); ui.secondarySkill->setTableSize( { 8, 1 } ); ui.secondarySkill->setInBetweenItemsOffset( { -1, 0 } ); ui.secondarySkill->SetContent( hero->GetSecondarySkills().ToVector() ); ui.secondarySkill->setRenderingOffset( { offset.x + secondarySkillOffsetX[armyId], offset.y + 199 } ); ui.artifact = std::make_unique( hero, true, false, true, true, nullptr ); ui.artifact->setTableSize( { 7, 2 } ); ui.artifact->setInBetweenItemsOffset( { 2, 2 } ); ui.artifact->SetContent( hero->GetBagArtifacts() ); ui.artifact->setRenderingOffset( { offset.x + artifactOffsetX[armyId], offset.y + 347 } ); ui.army = std::make_unique( &hero->GetArmy(), true, false, true ); ui.army->setTableSize( { 5, 1 } ); ui.army->setRenderingOffset( { offset.x + armyOffsetX[armyId], offset.y + 267 } ); ui.army->setInBetweenItemsOffset( { 2, 0 } ); } void Battle::Only::ArmyUI::redraw( fheroes2::Image & output ) const { if ( morale ) { morale->Redraw(); } if ( luck ) { luck->Redraw(); } if ( primarySkill ) { primarySkill->Redraw( output ); } if ( secondarySkill ) { secondarySkill->Redraw( output ); } if ( artifact ) { artifact->Redraw( output ); } if ( army ) { army->Redraw( output ); } } void Battle::Only::ArmyUI::resetForNewHero() { if ( morale ) { morale->redrawOnlyBackground(); } if ( luck ) { luck->redrawOnlyBackground(); } *this = {}; } void Battle::Only::ArmyInfo::reset() { ui = {}; hero = nullptr; monster.Reset(); monster.GetTroop( 0 )->Set( Monster::PEASANT, 100 ); } fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_only.h000066400000000000000000000104651456075706000225130ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2012 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2BATTLE_ONLY_H #define H2BATTLE_ONLY_H #include #include #include #include "army.h" #include "army_bar.h" #include "artifact.h" #include "heroes.h" #include "heroes_indicator.h" #include "math_base.h" #include "players.h" #include "skill_bar.h" namespace fheroes2 { class Image; } namespace Battle { struct ControlInfo { ControlInfo( const fheroes2::Point & pt, int ctrl ) : result( ctrl ) , rtLocal( pt.x, pt.y, 24, 24 ) , rtAI( pt.x + 75, pt.y, 24, 24 ) { // Do nothing. } ControlInfo( const ControlInfo & ) = delete; ControlInfo & operator=( const ControlInfo & ) = delete; void Redraw() const; int result; const fheroes2::Rect rtLocal; const fheroes2::Rect rtAI; }; class Only { public: Only(); Only( const Only & ) = delete; Only & operator=( const Only & ) = delete; bool setup( const bool allowBackup, bool & reset ); void StartBattle(); void reset(); private: struct ArmyUI { std::unique_ptr morale; std::unique_ptr luck; std::unique_ptr primarySkill; std::unique_ptr secondarySkill; std::unique_ptr artifact; std::unique_ptr army; void redraw( fheroes2::Image & output ) const; // Resets the state to empty and removes the morale & luck indicators from the screen void resetForNewHero(); }; struct ArmyInfo { Heroes * hero{ nullptr }; Player player; int controlType{ CONTROL_HUMAN }; Army monster; Heroes heroBackup; Army monsterBackup; bool isHeroPresent{ false }; ArmyUI ui; fheroes2::Rect portraitRoi; uint8_t armyId{ 0 }; bool needRedraw{ false }; void reset(); }; std::array armyInfo; std::unique_ptr attackedArmyControlInfo; bool _backupCompleted{ false }; void redrawOpponents( const fheroes2::Point & top ) const; void redrawOpponentsStats( const fheroes2::Point & top ) const; static void updateHero( ArmyInfo & info, const fheroes2::Point & offset ); static void updateArmyUI( ArmyUI & ui, Heroes * hero, const fheroes2::Point & offset, const uint8_t armyId ); static void copyHero( const Heroes & in, Heroes & out ); }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_pathfinding.cpp000066400000000000000000000420271456075706000243570ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "battle_pathfinding.h" #include #include #include #include #include #include #include #include #include "battle_arena.h" #include "battle_cell.h" #include "battle_troop.h" #include "castle.h" #include "speed.h" namespace { const uint32_t MOAT_PENALTY = UINT16_MAX; } namespace Battle { void BattlePathfinder::reEvaluateIfNeeded( const Unit & unit ) { assert( unit.GetHeadIndex() != -1 && ( unit.isWide() ? unit.GetTailIndex() != -1 : unit.GetTailIndex() == -1 ) ); const Board * board = Arena::GetBoard(); assert( board != nullptr ); // Passability of the board cells can change during the unit's turn even without its intervention (for example, because of a hero's spell cast), // we need to keep track of this std::array boardStatus{}; for ( const Cell & cell : *board ) { const int32_t cellIdx = cell.GetIndex(); assert( Board::isValidIndex( cellIdx ) ); boardStatus[cellIdx] = cell.isPassable( true ); } auto currentSettings = std::tie( _pathStart, _speed, _isWide, _isFlying, _color, _boardStatus ); const auto newSettings = std::make_tuple( BattleNodeIndex{ unit.GetHeadIndex(), unit.GetTailIndex() }, unit.GetSpeed(), unit.isWide(), unit.isFlying(), unit.GetColor(), std::cref( boardStatus ) ); // If all the current parameters match the parameters for which the current cache was built, then there is no need to rebuild it if ( currentSettings == newSettings ) { return; } currentSettings = newSettings; const Castle * castle = Arena::GetCastle(); const bool isMoatBuilt = castle && castle->isBuild( BUILD_MOAT ); _cache.clear(); _cache.try_emplace( _pathStart ); // Flying units can land wherever they can fit if ( _isFlying ) { for ( const Cell & cell : *board ) { const Position pos = Position::GetPosition( unit, cell.GetIndex() ); if ( pos.GetHead() == nullptr ) { continue; } assert( pos.isValidForUnit( unit ) ); const int32_t headCellIdx = pos.GetHead()->GetIndex(); const int32_t tailCellIdx = pos.GetTail() ? pos.GetTail()->GetIndex() : -1; const BattleNodeIndex newNodeIdx = { headCellIdx, tailCellIdx }; if ( newNodeIdx == _pathStart ) { continue; } // Wide units can occupy overlapping positions, the distance between which is actually zero, // but since the movement takes place, we will consider the distance equal to 1 in this case const uint32_t distance = std::max( Board::GetDistance( unit.GetPosition(), pos ), 1U ); _cache.try_emplace( newNodeIdx, _pathStart, 1, distance ); } return; } // The index of that of the cells of the initial unit's position, which is located // in the moat (-1, if there is none) const int32_t pathStartMoatCellIdx = [this, &unit, isMoatBuilt]() { if ( !isMoatBuilt ) { return -1; } assert( _pathStart.first != -1 ); if ( Board::isMoatIndex( _pathStart.first, unit ) ) { return _pathStart.first; } if ( _isWide ) { assert( _pathStart.second != -1 ); if ( Board::isMoatIndex( _pathStart.second, unit ) ) { return _pathStart.second; } } return -1; }(); std::vector nodesToExplore; nodesToExplore.reserve( ARENASIZE * 2 ); nodesToExplore.push_back( _pathStart ); for ( size_t nodesToExploreIdx = 0; nodesToExploreIdx < nodesToExplore.size(); ++nodesToExploreIdx ) { const BattleNodeIndex currentNodeIdx = nodesToExplore[nodesToExploreIdx]; const BattleNode & currentNode = _cache[currentNodeIdx]; if ( _isWide ) { assert( currentNodeIdx.first != -1 && currentNodeIdx.second != -1 ); const auto [currentHeadCellIdx, currentTailCellIdx] = currentNodeIdx; const BattleNodeIndex flippedCurrentNodeIdx = { currentTailCellIdx, currentHeadCellIdx }; const Cell * currentHeadCell = Board::GetCell( currentHeadCellIdx ); assert( currentHeadCell != nullptr ); const bool isCurrentLeftDirection = ( ( Board::GetDirection( currentTailCellIdx, currentHeadCellIdx ) & LEFT_SIDE ) != 0 ); // The moat restrictions can be ignored if the wide unit originally occupied a moat cell, and at the current step any part // of this unit occupies the same moat cell const bool isIgnoreMoat = ( currentHeadCellIdx == pathStartMoatCellIdx || currentTailCellIdx == pathStartMoatCellIdx ); const bool isInMoat = isMoatBuilt && ( Board::isMoatIndex( currentHeadCellIdx, unit ) || Board::isMoatIndex( currentTailCellIdx, unit ) ); const uint32_t movementPenalty = ( !isIgnoreMoat && isInMoat ) ? MOAT_PENALTY : 1; for ( const int32_t headCellIdx : Board::GetMoveWideIndexes( currentHeadCellIdx, isCurrentLeftDirection ) ) { const Cell * cell = Board::GetCell( headCellIdx ); assert( cell != nullptr ); if ( !cell->isPassableFromAdjacent( unit, *currentHeadCell ) ) { continue; } const int32_t tailCellIdx = ( Board::GetDirection( currentHeadCellIdx, headCellIdx ) & LEFT_SIDE ) ? headCellIdx + 1 : headCellIdx - 1; const BattleNodeIndex newNodeIdx = { headCellIdx, tailCellIdx }; if ( newNodeIdx == _pathStart ) { continue; } // Reversal is not a movement const uint32_t cost = currentNode._cost + ( newNodeIdx == flippedCurrentNodeIdx ? 0 : movementPenalty ); const uint32_t distance = currentNode._distance + ( newNodeIdx == flippedCurrentNodeIdx ? 0 : 1 ); BattleNode & newNode = _cache[newNodeIdx]; if ( newNode._from == BattleNodeIndex{ -1, -1 } || newNode._cost > cost ) { newNode._from = currentNodeIdx; newNode._cost = cost; newNode._distance = distance; nodesToExplore.push_back( newNodeIdx ); } } } else { assert( currentNodeIdx.first != -1 && currentNodeIdx.second == -1 ); const auto [currentCellIdx, dummy] = currentNodeIdx; const Cell * currentCell = Board::GetCell( currentCellIdx ); assert( currentCell != nullptr ); // The moat restrictions can be ignored at the first step if the unit starts its movement from the moat const bool isIgnoreMoat = ( currentCellIdx == pathStartMoatCellIdx ); const bool isInMoat = isMoatBuilt && Board::isMoatIndex( currentCellIdx, unit ); const uint32_t movementPenalty = ( !isIgnoreMoat && isInMoat ) ? MOAT_PENALTY : 1; for ( const int32_t cellIdx : Board::GetAroundIndexes( currentCellIdx ) ) { const Cell * cell = Board::GetCell( cellIdx ); assert( cell != nullptr ); if ( !cell->isPassableFromAdjacent( unit, *currentCell ) ) { continue; } const BattleNodeIndex newNodeIdx = { cellIdx, -1 }; if ( newNodeIdx == _pathStart ) { continue; } const uint32_t cost = currentNode._cost + movementPenalty; const uint32_t distance = currentNode._distance + 1; BattleNode & newNode = _cache[newNodeIdx]; if ( newNode._from == BattleNodeIndex{ -1, -1 } || newNode._cost > cost ) { newNode._from = currentNodeIdx; newNode._cost = cost; newNode._distance = distance; nodesToExplore.push_back( newNodeIdx ); } } } } } bool BattlePathfinder::isPositionReachable( const Unit & unit, const Position & position, const bool isOnCurrentTurn ) { // Invalid positions are allowed here, but they are always unreachable if ( position.GetHead() == nullptr ) { return false; } reEvaluateIfNeeded( unit ); const BattleNodeIndex nodeIdx = { position.GetHead()->GetIndex(), position.GetTail() ? position.GetTail()->GetIndex() : -1 }; const auto iter = _cache.find( nodeIdx ); if ( iter == _cache.end() ) { return false; } const auto & [index, node] = *iter; return ( index == _pathStart || node._from != BattleNodeIndex{ -1, -1 } ) && ( !isOnCurrentTurn || node._cost <= _speed ); } uint32_t BattlePathfinder::getCost( const Unit & unit, const Position & position ) { assert( position.GetHead() != nullptr ); reEvaluateIfNeeded( unit ); const BattleNodeIndex nodeIdx = { position.GetHead()->GetIndex(), position.GetTail() ? position.GetTail()->GetIndex() : -1 }; const auto iter = _cache.find( nodeIdx ); assert( iter != _cache.end() ); const auto & [index, node] = *iter; // MSVC 2017 fails to properly expand the assert() macro without additional parentheses assert( ( index == _pathStart || node._from != BattleNodeIndex{ -1, -1 } ) ); return node._cost; } uint32_t BattlePathfinder::getDistance( const Unit & unit, const Position & position ) { assert( position.GetHead() != nullptr ); reEvaluateIfNeeded( unit ); const BattleNodeIndex nodeIdx = { position.GetHead()->GetIndex(), position.GetTail() ? position.GetTail()->GetIndex() : -1 }; const auto iter = _cache.find( nodeIdx ); assert( iter != _cache.end() ); const auto & [index, node] = *iter; // MSVC 2017 fails to properly expand the assert() macro without additional parentheses assert( ( index == _pathStart || node._from != BattleNodeIndex{ -1, -1 } ) ); return node._distance; } Indexes BattlePathfinder::getAllAvailableMoves( const Unit & unit ) { reEvaluateIfNeeded( unit ); std::set boardIndexes; for ( const auto & [index, node] : _cache ) { if ( index == _pathStart || node._from == BattleNodeIndex{ -1, -1 } || node._cost > _speed ) { continue; } assert( index.first != -1 ); boardIndexes.insert( index.first ); } Indexes result; result.reserve( boardIndexes.size() ); result.assign( boardIndexes.begin(), boardIndexes.end() ); return result; } Indexes BattlePathfinder::buildPath( const Unit & unit, const Position & position ) { assert( position.GetHead() != nullptr ); reEvaluateIfNeeded( unit ); Indexes result; result.reserve( Speed::INSTANT ); BattleNodeIndex lastReachableNodeIdx{ -1, -1 }; BattleNodeIndex nodeIdx = { position.GetHead()->GetIndex(), position.GetTail() ? position.GetTail()->GetIndex() : -1 }; for ( auto iter = _cache.find( nodeIdx ); iter != _cache.end(); iter = _cache.find( nodeIdx ) ) { const auto & [index, node] = *iter; if ( index == _pathStart ) { break; } // MSVC 2017 fails to properly expand the assert() macro without additional parentheses assert( ( node._from != BattleNodeIndex{ -1, -1 } ) ); nodeIdx = node._from; // A given position may be reachable in principle, but is not reachable on the current turn. // Skip the steps that are not reachable on this turn. if ( node._cost > _speed ) { continue; } if ( _isWide && lastReachableNodeIdx == BattleNodeIndex{ -1, -1 } ) { assert( index.first != -1 && index.second != -1 ); lastReachableNodeIdx = index; } result.push_back( index.first ); } std::reverse( result.begin(), result.end() ); // If a given position is not reachable on the current turn, then the last reachable position of // a wide unit may be reversed in regard to the target one. Detect this and add an extra U-turn. if ( _isWide && !result.empty() ) { assert( lastReachableNodeIdx.first != -1 && lastReachableNodeIdx.second != -1 ); const bool isReflect = lastReachableNodeIdx.first < lastReachableNodeIdx.second; if ( isReflect != position.isReflect() ) { // The last reachable position should not be a reversed version of the target position assert( !position.contains( lastReachableNodeIdx.first ) || !position.contains( lastReachableNodeIdx.second ) ); result.push_back( lastReachableNodeIdx.second ); } } return result; } Position BattlePathfinder::getClosestReachablePosition( const Unit & unit, const Position & position ) { assert( position.GetHead() != nullptr ); reEvaluateIfNeeded( unit ); BattleNodeIndex nodeIdx = { position.GetHead()->GetIndex(), position.GetTail() ? position.GetTail()->GetIndex() : -1 }; for ( auto iter = _cache.find( nodeIdx ); iter != _cache.end(); iter = _cache.find( nodeIdx ) ) { const auto & [index, node] = *iter; if ( index == _pathStart ) { break; } // MSVC 2017 fails to properly expand the assert() macro without additional parentheses assert( ( node._from != BattleNodeIndex{ -1, -1 } ) ); nodeIdx = node._from; // A given position may be reachable in principle, but is not reachable on the current turn. // Skip the steps that are not reachable on this turn. if ( node._cost > _speed ) { continue; } Position result; if ( _isWide ) { assert( index.first != -1 && index.second != -1 ); const bool isReflect = index.first < index.second; result.Set( index.first, true, isReflect ); assert( result.GetHead() != nullptr && result.GetHead()->GetIndex() == index.first && result.GetTail() != nullptr && result.GetTail()->GetIndex() == index.second ); // If a given position is not reachable on the current turn, then the last reachable position of // a wide unit may be reversed in regard to the target one. Detect this and turn it over. if ( isReflect != position.isReflect() ) { // The last reachable position should not be a reversed version of the target position assert( !position.contains( index.first ) || !position.contains( index.second ) ); result.Swap(); } } else { result.Set( index.first, false, false ); } return result; } return {}; } } fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_pathfinding.h000066400000000000000000000133511456075706000240220ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include #include #include #include "battle_board.h" namespace Battle { class Position; class Unit; using BattleNodeIndex = std::pair; struct BattleNodeIndexHash final { std::size_t operator()( const BattleNodeIndex & index ) const noexcept { const uint64_t f = index.first; const uint64_t s = index.second; std::hash hasher; return hasher( ( f << 32 ) + s ); } }; struct BattleNode final { BattleNodeIndex _from{ -1, -1 }; // Cost of moving to this node. May differ from _distance due to penalties (e.g. moat penalty) and // also in the case of flying units. uint32_t _cost{ 0 }; // Distance to this node, measured in the number of movements that need to be performed to get here. // The reversal of a wide unit is not considered as a movement. For flying units, this distance is // estimated as the straight line distance to the position corresponding to this node. uint32_t _distance{ 0 }; BattleNode() = default; BattleNode( BattleNodeIndex node, const uint32_t cost, const uint32_t distance ) : _from( std::move( node ) ) , _cost( cost ) , _distance( distance ) {} }; class BattlePathfinder final { public: BattlePathfinder() = default; BattlePathfinder( const BattlePathfinder & ) = delete; ~BattlePathfinder() = default; BattlePathfinder & operator=( const BattlePathfinder & ) = delete; // Checks whether the given position is reachable for the given unit, either on the current turn or in principle bool isPositionReachable( const Unit & unit, const Position & position, const bool isOnCurrentTurn ); // Returns the cost of moving to the given position for the given unit. It's the caller's responsibility to make // sure that this position is reachable before calling this method. uint32_t getCost( const Unit & unit, const Position & position ); // Returns the distance to the given position (i.e. the number of movements that need to be performed to get to // this position, the reversal of a wide unit is not considered as a movement) for the given unit. For flying // units, this distance is estimated as the straight line distance to the given position. It's the caller's // responsibility to make sure that this position is reachable before calling this method. uint32_t getDistance( const Unit & unit, const Position & position ); // Builds and returns the path (or its part) for the given unit to the given position that can be traversed during // the current turn. If this position is unreachable by this unit, then an empty path is returned. Indexes buildPath( const Unit & unit, const Position & position ); // Returns the indexes of all cells that can be occupied by the given unit's head on the current turn Indexes getAllAvailableMoves( const Unit & unit ); // Returns the position on the path for the given unit to the given position, which is reachable on the current // turn and is as close as possible to the destination (excluding the current position of the unit). If the given // position is unreachable by the given unit, then an empty Position object is returned. Position getClosestReachablePosition( const Unit & unit, const Position & position ); private: // Rebuilds the graph of available positions for the given unit if necessary (if it is not already cached) void reEvaluateIfNeeded( const Unit & unit ); std::unordered_map _cache; // Parameters of the unit for which the current cache is created BattleNodeIndex _pathStart{ -1, -1 }; uint32_t _speed{ 0 }; bool _isWide{ false }; bool _isFlying{ false }; // The unit's color (or rather, the unit's army color) affects the ability to pass the castle bridge int _color{ 0 }; // Board cells passability status at the time of current cache creation std::array _boardStatus{}; }; } fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_tower.cpp000066400000000000000000000141021456075706000232150ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "battle_tower.h" #include #include #include #include "army_troop.h" #include "battle.h" #include "battle_arena.h" #include "battle_board.h" #include "battle_cell.h" #include "castle.h" #include "monster.h" #include "tools.h" #include "translations.h" Battle::Tower::Tower( const Castle & castle, const TowerType type, const uint32_t uid ) : Unit( Troop( Monster::ARCHER, castle.CountBuildings() ), {}, false, uid ) , _towerType( type ) , _attackBonus( castle.GetLevelMageGuild() ) , _isValid( true ) { count = std::min( count, 20U ); count = std::max( _towerType == TowerType::TWR_CENTER ? count : count / 2, 1U ); // Virtual archers shooting from this tower should receive bonuses // to their attack skill from the commanding hero (if present) SetArmy( castle.GetActualArmy() ); SetModes( CAP_TOWER ); } const char * Battle::Tower::GetName() const { switch ( _towerType ) { case TowerType::TWR_LEFT: return _( "Left Turret" ); case TowerType::TWR_RIGHT: return _( "Right Turret" ); case TowerType::TWR_CENTER: return _( "Ballista" ); default: // This is not a valid Tower type! assert( 0 ); break; } return nullptr; } bool Battle::Tower::isValid() const { return _isValid; } Battle::TowerType Battle::Tower::GetType() const { return _towerType; } uint32_t Battle::Tower::GetAttackBonus() const { return _attackBonus; } uint32_t Battle::Tower::GetAttack() const { return Unit::GetAttack() + _attackBonus; } fheroes2::Point Battle::Tower::GetPortPosition() const { switch ( _towerType ) { case TowerType::TWR_LEFT: return { 410, 70 }; case TowerType::TWR_RIGHT: return { 410, 320 }; case TowerType::TWR_CENTER: return { 560, 170 }; default: break; } return {}; } void Battle::Tower::SetDestroy() { switch ( _towerType ) { case TowerType::TWR_LEFT: Board::GetCell( Arena::CASTLE_TOP_ARCHER_TOWER_POS )->SetObject( 1 ); break; case TowerType::TWR_RIGHT: Board::GetCell( Arena::CASTLE_BOTTOM_ARCHER_TOWER_POS )->SetObject( 1 ); break; default: break; } _isValid = false; } std::string Battle::Tower::GetInfo( const Castle & castle ) { if ( !castle.isBuild( BUILD_CASTLE ) ) { return {}; } std::vector towerTypes; towerTypes.push_back( TowerType::TWR_CENTER ); if ( castle.isBuild( BUILD_LEFTTURRET ) ) { towerTypes.push_back( TowerType::TWR_LEFT ); } if ( castle.isBuild( BUILD_RIGHTTURRET ) ) { towerTypes.push_back( TowerType::TWR_RIGHT ); } // This method can be called both during combat and outside of it. In the // former case, we have to check if the tower was destroyed during the siege. const auto isTowerValid = []( const TowerType towerType ) { // If the siege is in progress, we need to check the current state of the tower if ( GetArena() ) { const Tower * tower = Arena::GetTower( towerType ); assert( tower != nullptr ); return tower->isValid(); } return true; }; std::string msg; for ( std::vector::const_iterator it = towerTypes.begin(); it != towerTypes.end(); ++it ) { const TowerType towerType = *it; if ( isTowerValid( towerType ) ) { const Tower tower( castle, towerType, 0 ); msg.append( _( "The %{name} fires with the strength of %{count} Archers" ) ); StringReplace( msg, "%{name}", tower.GetName() ); StringReplace( msg, "%{count}", std::to_string( tower.GetCount() ) ); if ( tower.GetAttackBonus() ) { msg.append( ", " ); msg.append( _( "each with a +%{attack} bonus to their attack skill." ) ); StringReplace( msg, "%{attack}", std::to_string( tower.GetAttackBonus() ) ); } else { msg += '.'; } } else { assert( GetArena() != nullptr ); const Tower * tower = Arena::GetTower( towerType ); assert( tower != nullptr ); msg.append( _( "The %{name} is destroyed." ) ); StringReplace( msg, "%{name}", tower->GetName() ); } if ( ( it + 1 ) != towerTypes.end() ) { msg.append( "\n\n" ); } } return msg; } fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_tower.h000066400000000000000000000055421456075706000226720ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2BATTLE_TOWER_H #define H2BATTLE_TOWER_H #include #include #include "battle_troop.h" #include "math_base.h" class Castle; namespace Battle { enum class TowerType : uint8_t { TWR_LEFT, TWR_CENTER, TWR_RIGHT }; class Tower : public Unit { public: Tower( const Castle & castle, const TowerType type, const uint32_t uid ); Tower( const Tower & ) = delete; Tower & operator=( const Tower & ) = delete; bool isValid() const override; TowerType GetType() const; uint32_t GetAttackBonus() const; uint32_t GetAttack() const override; const char * GetName() const; void SetDestroy(); fheroes2::Point GetPortPosition() const; // Returns a text description of the parameters of the towers of a given castle. Can be // called both during combat and outside of it. In the former case, the current state of // the towers destroyed during the siege will be reflected. static std::string GetInfo( const Castle & castle ); private: TowerType _towerType; uint32_t _attackBonus; bool _isValid; }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_troop.cpp000066400000000000000000001537671456075706000232450ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "battle_troop.h" #include #include #include #include "agg_image.h" #include "army.h" #include "artifact.h" #include "artifact_info.h" #include "battle.h" #include "battle_arena.h" #include "battle_army.h" #include "battle_board.h" #include "battle_cell.h" #include "battle_grave.h" #include "battle_interface.h" #include "battle_tower.h" #include "castle.h" #include "color.h" #include "game_static.h" #include "heroes_base.h" #include "image.h" #include "logging.h" #include "m82.h" #include "monster.h" #include "monster_anim.h" #include "monster_info.h" #include "morale.h" #include "rand.h" #include "resource.h" #include "skill.h" #include "speed.h" #include "spell.h" #include "spell_info.h" #include "tools.h" #include "translations.h" namespace { Artifact getImmunityArtifactForSpell( const HeroBase * hero, const Spell & spell ) { if ( hero == nullptr ) { return { Artifact::UNKNOWN }; } switch ( spell.GetID() ) { case Spell::CURSE: case Spell::MASSCURSE: return hero->GetBagArtifacts().getFirstArtifactWithBonus( fheroes2::ArtifactBonusType::CURSE_SPELL_IMMUNITY ); case Spell::HYPNOTIZE: return hero->GetBagArtifacts().getFirstArtifactWithBonus( fheroes2::ArtifactBonusType::HYPNOTIZE_SPELL_IMMUNITY ); case Spell::DEATHRIPPLE: case Spell::DEATHWAVE: return hero->GetBagArtifacts().getFirstArtifactWithBonus( fheroes2::ArtifactBonusType::DEATH_SPELL_IMMUNITY ); case Spell::BERSERKER: return hero->GetBagArtifacts().getFirstArtifactWithBonus( fheroes2::ArtifactBonusType::BERSERK_SPELL_IMMUNITY ); case Spell::BLIND: return hero->GetBagArtifacts().getFirstArtifactWithBonus( fheroes2::ArtifactBonusType::BLIND_SPELL_IMMUNITY ); case Spell::PARALYZE: return hero->GetBagArtifacts().getFirstArtifactWithBonus( fheroes2::ArtifactBonusType::PARALYZE_SPELL_IMMUNITY ); case Spell::HOLYWORD: case Spell::HOLYSHOUT: return hero->GetBagArtifacts().getFirstArtifactWithBonus( fheroes2::ArtifactBonusType::HOLY_SPELL_IMMUNITY ); case Spell::DISPEL: case Spell::MASSDISPEL: return hero->GetBagArtifacts().getFirstArtifactWithBonus( fheroes2::ArtifactBonusType::DISPEL_SPELL_IMMUNITY ); default: break; } return { Artifact::UNKNOWN }; } } Battle::ModeDuration::ModeDuration( uint32_t mode, uint32_t duration ) : std::pair( mode, duration ) {} bool Battle::ModeDuration::isMode( uint32_t mode ) const { return ( first & mode ) != 0; } bool Battle::ModeDuration::isZeroDuration() const { return 0 == second; } void Battle::ModeDuration::DecreaseDuration() { if ( second ) { --second; } } Battle::ModesAffected::ModesAffected() { reserve( 3 ); } uint32_t Battle::ModesAffected::GetMode( uint32_t mode ) const { const_iterator it = std::find_if( begin(), end(), [mode]( const Battle::ModeDuration & v ) { return v.isMode( mode ); } ); return it == end() ? 0 : it->second; } void Battle::ModesAffected::AddMode( uint32_t mode, uint32_t duration ) { iterator it = std::find_if( begin(), end(), [mode]( const Battle::ModeDuration & v ) { return v.isMode( mode ); } ); if ( it == end() ) { emplace_back( mode, duration ); } else { it->second = duration; } } void Battle::ModesAffected::RemoveMode( uint32_t mode ) { erase( std::remove_if( begin(), end(), [mode]( const Battle::ModeDuration & v ) { return v.isMode( mode ); } ), end() ); } void Battle::ModesAffected::DecreaseDuration() { std::for_each( begin(), end(), []( Battle::ModeDuration & v ) { v.DecreaseDuration(); } ); } uint32_t Battle::ModesAffected::FindZeroDuration() const { const_iterator it = std::find_if( begin(), end(), []( const Battle::ModeDuration & v ) { return v.isZeroDuration(); } ); return it == end() ? 0 : ( *it ).first; } Battle::Unit::Unit( const Troop & t, const Position & pos, const bool ref, const uint32_t uid ) : ArmyTroop( nullptr, t ) , animation( id ) , _uid( uid ) , hp( t.GetHitPoints() ) , _initialCount( t.GetCount() ) , dead( 0 ) , shots( t.GetShots() ) , disruptingray( 0 ) , reflect( ref ) , mirror( nullptr ) , idleTimer( animation.getIdleDelay() ) , _blindRetaliation( false ) , customAlphaMask( 255 ) { SetPosition( pos ); } void Battle::Unit::SetPosition( const int32_t idx ) { if ( position.GetHead() ) { position.GetHead()->SetUnit( nullptr ); } if ( position.GetTail() ) { position.GetTail()->SetUnit( nullptr ); } position.Set( idx, isWide(), reflect ); if ( position.GetHead() ) { position.GetHead()->SetUnit( this ); } if ( position.GetTail() ) { position.GetTail()->SetUnit( this ); } } void Battle::Unit::SetPosition( const Position & pos ) { // Position may be empty if this unit is a castle tower assert( pos.isValidForUnit( this ) || pos.isEmpty() ); if ( position.GetHead() ) { position.GetHead()->SetUnit( nullptr ); } if ( position.GetTail() ) { position.GetTail()->SetUnit( nullptr ); } position = pos; if ( position.GetHead() ) { position.GetHead()->SetUnit( this ); } if ( position.GetTail() ) { position.GetTail()->SetUnit( this ); } if ( isWide() && position.GetHead() && position.GetTail() ) { reflect = GetHeadIndex() < GetTailIndex(); } } void Battle::Unit::SetReflection( bool r ) { if ( reflect != r ) position.Swap(); reflect = r; } void Battle::Unit::UpdateDirection() { const Arena * arena = GetArena(); assert( arena != nullptr ); SetReflection( arena->GetArmy1Color() != GetArmyColor() ); } bool Battle::Unit::UpdateDirection( const fheroes2::Rect & pos ) { bool need = position.GetRect().x == pos.x ? reflect : position.GetRect().x > pos.x; if ( need != reflect ) { SetReflection( need ); return true; } return false; } bool Battle::Unit::isBattle() const { return true; } bool Battle::Unit::isModes( uint32_t v ) const { return Modes( v ); } std::string Battle::Unit::GetShotString() const { if ( Troop::GetShots() == GetShots() ) return std::to_string( Troop::GetShots() ); std::string output( std::to_string( Troop::GetShots() ) ); output += " ("; output += std::to_string( GetShots() ); output += ')'; return output; } std::string Battle::Unit::GetSpeedString() const { const uint32_t speed = GetSpeed( true, false ); return Troop::GetSpeedString( speed ); } uint32_t Battle::Unit::GetInitialCount() const { return _initialCount; } uint32_t Battle::Unit::GetDead() const { return dead; } uint32_t Battle::Unit::GetHitPointsLeft() const { return GetHitPoints() - ( GetCount() - 1 ) * Monster::GetHitPoints(); } uint32_t Battle::Unit::GetMissingHitPoints() const { const uint32_t totalHitPoints = _initialCount * Monster::GetHitPoints(); assert( totalHitPoints > hp ); return totalHitPoints - hp; } uint32_t Battle::Unit::GetAffectedDuration( uint32_t mod ) const { return affected.GetMode( mod ); } uint32_t Battle::Unit::GetSpeed() const { return GetSpeed( false, false ); } int Battle::Unit::GetMorale() const { const Arena * arena = GetArena(); assert( arena != nullptr ); int armyTroopMorale = ArmyTroop::GetMorale(); // enemy Bone dragons affect morale if ( isAffectedByMorale() && arena->getEnemyForce( GetArmyColor() ).HasMonster( Monster::BONE_DRAGON ) && armyTroopMorale > Morale::TREASON ) { --armyTroopMorale; } return armyTroopMorale; } int32_t Battle::Unit::GetHeadIndex() const { return position.GetHead() ? position.GetHead()->GetIndex() : -1; } int32_t Battle::Unit::GetTailIndex() const { return position.GetTail() ? position.GetTail()->GetIndex() : -1; } void Battle::Unit::SetRandomMorale( Rand::DeterministicRandomGenerator & randomGenerator ) { const int morale = GetMorale(); if ( morale > 0 && static_cast( randomGenerator.Get( 1, 24 ) ) <= morale ) { SetModes( MORALE_GOOD ); } else if ( morale < 0 && static_cast( randomGenerator.Get( 1, 12 ) ) <= -morale ) { if ( isControlHuman() ) { SetModes( MORALE_BAD ); } // AI is given a cheeky 25% chance to avoid it - because they build armies from random troops else if ( randomGenerator.Get( 1, 4 ) != 1 ) { SetModes( MORALE_BAD ); } } } void Battle::Unit::SetRandomLuck( Rand::DeterministicRandomGenerator & randomGenerator ) { const int32_t luck = GetLuck(); const int32_t chance = static_cast( randomGenerator.Get( 1, 24 ) ); if ( luck > 0 && chance <= luck ) { SetModes( LUCK_GOOD ); } else if ( luck < 0 && chance <= -luck ) { SetModes( LUCK_BAD ); } // Bless, Curse and Luck do stack } bool Battle::Unit::isFlying() const { return ArmyTroop::isFlying() && !Modes( SP_SLOW ); } bool Battle::Unit::isOutOfCastleWalls() const { return Board::isOutOfWallsIndex( GetHeadIndex() ) || ( isWide() && Board::isOutOfWallsIndex( GetTailIndex() ) ); } bool Battle::Unit::isHandFighting() const { assert( isValid() ); // Towers never fight in close combat if ( Modes( CAP_TOWER ) ) { return false; } for ( const int32_t nearbyIdx : Board::GetAroundIndexes( *this ) ) { const Unit * nearbyUnit = Board::GetCell( nearbyIdx )->GetUnit(); if ( nearbyUnit == nullptr ) { continue; } if ( nearbyUnit->GetColor() == GetCurrentColor() ) { continue; } return true; } return false; } bool Battle::Unit::isHandFighting( const Unit & attacker, const Unit & defender ) { assert( attacker.isValid() ); // Towers never fight in close combat if ( attacker.Modes( CAP_TOWER ) || defender.Modes( CAP_TOWER ) ) { return false; } const uint32_t distance = Board::GetDistance( attacker.GetPosition(), defender.GetPosition() ); assert( distance > 0 ); // If the attacker and the defender are next to each other, then this is a melee attack, otherwise it's a shot return ( distance == 1 ); } bool Battle::Unit::isIdling() const { return GetAnimationState() == Monster_Info::IDLE; } void Battle::Unit::NewTurn() { if ( isRegenerating() ) { hp = ArmyTroop::GetHitPoints(); } ResetModes( TR_RESPONDED ); ResetModes( TR_MOVED ); ResetModes( TR_SKIP ); ResetModes( LUCK_GOOD ); ResetModes( LUCK_BAD ); ResetModes( MORALE_GOOD ); ResetModes( MORALE_BAD ); affected.DecreaseDuration(); for ( uint32_t mode = affected.FindZeroDuration(); mode != 0; mode = affected.FindZeroDuration() ) { assert( Modes( mode ) ); if ( mode == CAP_MIRROROWNER ) { assert( mirror != nullptr && mirror->Modes( CAP_MIRRORIMAGE ) && mirror->mirror == this ); if ( Arena::GetInterface() ) { Arena::GetInterface()->RedrawActionRemoveMirrorImage( { mirror } ); } mirror->hp = 0; mirror->SetCount( 0 ); // Affection will be removed here mirror->PostKilledAction(); } else { removeAffection( mode ); } } } uint32_t Battle::Unit::GetSpeed( const bool skipStandingCheck, const bool skipMovedCheck ) const { if ( !skipStandingCheck ) { uint32_t modesToCheck = SP_BLIND | IS_PARALYZE_MAGIC; if ( !skipMovedCheck ) { modesToCheck |= TR_MOVED; } if ( GetCount() == 0 || Modes( modesToCheck ) ) { return Speed::STANDING; } } const uint32_t speed = Monster::GetSpeed(); if ( Modes( SP_HASTE ) ) { return Speed::GetHasteSpeedFromSpell( speed ); } else if ( Modes( SP_SLOW ) ) { return Speed::GetSlowSpeedFromSpell( speed ); } return speed; } uint32_t Battle::Unit::EstimateRetaliatoryDamage( const uint32_t damageTaken ) const { // The entire unit is destroyed, no retaliation if ( damageTaken >= hp ) { return 0; } // Mirror images are destroyed anyway and hypnotized units never respond to an attack if ( Modes( TR_RESPONDED | CAP_MIRRORIMAGE | SP_HYPNOTIZE ) ) { return 0; } // Units with this ability retaliate even when under the influence of paralyzing spells if ( Modes( IS_PARALYZE_MAGIC ) && !isAbilityPresent( fheroes2::MonsterAbilityType::ALWAYS_RETALIATE ) ) { return 0; } const uint32_t unitsLeft = GetCountFromHitPoints( *this, hp - damageTaken ); assert( unitsLeft > 0 ); const uint32_t damagePerUnit = [this]() { if ( Modes( SP_CURSE ) ) { return Monster::GetDamageMin(); } if ( Modes( SP_BLESS ) ) { return Monster::GetDamageMax(); } return ( Monster::GetDamageMin() + Monster::GetDamageMax() ) / 2; }(); const uint32_t retaliatoryDamage = unitsLeft * damagePerUnit; // The retaliatory damage of a blinded unit is halved return ( Modes( SP_BLIND ) ? retaliatoryDamage / 2 : retaliatoryDamage ); } uint32_t Battle::Unit::CalculateMinDamage( const Unit & enemy ) const { return CalculateDamageUnit( enemy, ArmyTroop::GetDamageMin() ); } uint32_t Battle::Unit::CalculateMaxDamage( const Unit & enemy ) const { return CalculateDamageUnit( enemy, ArmyTroop::GetDamageMax() ); } uint32_t Battle::Unit::CalculateDamageUnit( const Unit & enemy, double dmg ) const { if ( isArchers() ) { // Melee penalty can be applied either if the archer is blocked by enemy units and cannot shoot, // or if he is not blocked, but was attacked by a friendly unit (for example, in the case of using // Berserk or Hypnotize spells) and deals retaliatory damage if ( !isHandFighting() && !isHandFighting( *this, enemy ) ) { // Hero's Archery skill may increase damage if ( GetCommander() ) { dmg += ( dmg * GetCommander()->GetSecondaryValues( Skill::Secondary::ARCHERY ) / 100 ); } const Arena * arena = GetArena(); assert( arena != nullptr ); // Penalty for damage to castle defenders behind the castle walls if ( arena->IsShootingPenalty( *this, enemy ) ) { dmg /= 2; } // The Shield spell does not affect the damage of the castle towers if ( !Modes( CAP_TOWER ) && enemy.Modes( SP_SHIELD ) ) { dmg /= Spell( Spell::SHIELD ).ExtraValue(); } } else if ( !isAbilityPresent( fheroes2::MonsterAbilityType::NO_MELEE_PENALTY ) ) { dmg /= 2; } } // The retaliatory damage of a blinded unit is halved if ( _blindRetaliation ) { dmg /= 2; } // A petrified unit takes only half of the damage if ( enemy.Modes( SP_STONE ) ) { dmg /= 2; } switch ( GetID() ) { case Monster::CRUSADER: if ( enemy.isUndead() ) { dmg *= 2; } break; case Monster::FIRE_ELEMENT: if ( enemy.GetID() == Monster::WATER_ELEMENT ) { dmg *= 2; } break; case Monster::WATER_ELEMENT: if ( enemy.GetID() == Monster::FIRE_ELEMENT ) { dmg *= 2; } break; case Monster::AIR_ELEMENT: if ( enemy.GetID() == Monster::EARTH_ELEMENT ) { dmg *= 2; } break; case Monster::EARTH_ELEMENT: if ( enemy.GetID() == Monster::AIR_ELEMENT ) { dmg *= 2; } break; default: break; } int r = GetAttack() - enemy.GetDefense(); if ( enemy.isDragons() && Modes( SP_DRAGONSLAYER ) ) { r += Spell( Spell::DRAGONSLAYER ).ExtraValue(); } // Attack bonus is 20% to 300% dmg *= 1 + ( 0 < r ? 0.1 * std::min( r, 20 ) : 0.05 * std::max( r, -16 ) ); return static_cast( dmg ) < 1 ? 1 : static_cast( dmg ); } uint32_t Battle::Unit::GetDamage( const Unit & enemy, Rand::DeterministicRandomGenerator & randomGenerator ) const { uint32_t res = 0; if ( Modes( SP_BLESS ) ) res = CalculateMaxDamage( enemy ); else if ( Modes( SP_CURSE ) ) res = CalculateMinDamage( enemy ); else res = randomGenerator.Get( CalculateMinDamage( enemy ), CalculateMaxDamage( enemy ) ); if ( Modes( LUCK_GOOD ) ) res = res * 2; else if ( Modes( LUCK_BAD ) ) res = res / 2; return res; } uint32_t Battle::Unit::HowManyWillBeKilled( const uint32_t dmg ) const { if ( Modes( CAP_MIRRORIMAGE ) ) { return GetCount(); } return dmg >= hp ? GetCount() : GetCount() - Monster::GetCountFromHitPoints( *this, hp - dmg ); } uint32_t Battle::Unit::ApplyDamage( const uint32_t dmg ) { assert( !AllModes( CAP_MIRROROWNER | CAP_MIRRORIMAGE ) ); if ( dmg == 0 || GetCount() == 0 ) { return 0; } const uint32_t killed = HowManyWillBeKilled( dmg ); DEBUG_LOG( DBG_BATTLE, DBG_TRACE, dmg << " to " << String() << " and killed: " << killed ) if ( Modes( IS_PARALYZE_MAGIC ) ) { // Units with this ability retaliate even when under the influence of paralyzing spells if ( !isAbilityPresent( fheroes2::MonsterAbilityType::ALWAYS_RETALIATE ) ) { SetModes( TR_RESPONDED ); } SetModes( TR_MOVED ); removeAffection( IS_PARALYZE_MAGIC ); } if ( Modes( SP_BLIND ) ) { SetModes( TR_MOVED ); removeAffection( SP_BLIND ); } if ( killed >= GetCount() ) { dead += GetCount(); SetCount( 0 ); } else { dead += killed; SetCount( GetCount() - killed ); } if ( Modes( CAP_MIRRORIMAGE ) ) { hp = 0; } else { hp -= std::min( hp, dmg ); } if ( Modes( CAP_MIRROROWNER ) && !isValid() ) { assert( mirror != nullptr && mirror->Modes( CAP_MIRRORIMAGE ) && mirror->mirror == this ); mirror->ApplyDamage( mirror->hp ); } return killed; } void Battle::Unit::PostKilledAction() { assert( !AllModes( CAP_MIRROROWNER | CAP_MIRRORIMAGE ) ); if ( Modes( CAP_MIRROROWNER ) ) { assert( mirror != nullptr && mirror->Modes( CAP_MIRRORIMAGE ) && mirror->mirror == this ); mirror = nullptr; removeAffection( CAP_MIRROROWNER ); } if ( Modes( CAP_MIRRORIMAGE ) ) { // CAP_MIRROROWNER may have already been removed from the mirror owner, // since this method may already have been called for it assert( mirror != nullptr ); // But we still need to remove it if it is present if ( mirror->Modes( CAP_MIRROROWNER ) ) { assert( mirror->mirror == this ); mirror->mirror = nullptr; mirror->removeAffection( CAP_MIRROROWNER ); } mirror = nullptr; } // Remove all spells removeAffection( IS_MAGIC ); assert( affected.empty() ); // Save to the graveyard if possible if ( !Modes( CAP_MIRRORIMAGE ) && !isElemental() ) { Graveyard * graveyard = Arena::GetGraveyard(); assert( graveyard != nullptr ); graveyard->AddTroop( *this ); } Cell * head = position.GetHead(); assert( head != nullptr ); head->SetUnit( nullptr ); if ( isWide() ) { Cell * tail = position.GetTail(); assert( tail != nullptr ); tail->SetUnit( nullptr ); } DEBUG_LOG( DBG_BATTLE, DBG_TRACE, String() ) } uint32_t Battle::Unit::Resurrect( const uint32_t points, const bool allow_overflow, const bool skip_dead ) { uint32_t resurrect = Monster::GetCountFromHitPoints( *this, hp + points ) - GetCount(); SetCount( GetCount() + resurrect ); hp += points; if ( allow_overflow ) { if ( _initialCount < GetCount() ) _initialCount = GetCount(); } else if ( GetCount() > _initialCount ) { resurrect -= GetCount() - _initialCount; SetCount( _initialCount ); hp = ArmyTroop::GetHitPoints(); } if ( !skip_dead ) dead -= ( resurrect < dead ? resurrect : dead ); return resurrect; } bool Battle::Unit::isImmovable() const { return Modes( SP_BLIND | IS_PARALYZE_MAGIC ); } uint32_t Battle::Unit::ApplyDamage( Unit & enemy, const uint32_t dmg, uint32_t & killed, uint32_t * ptrResurrected ) { killed = ApplyDamage( dmg ); if ( killed == 0 ) { if ( ptrResurrected != nullptr ) { *ptrResurrected = 0; } return killed; } uint32_t resurrected = 0; if ( enemy.isAbilityPresent( fheroes2::MonsterAbilityType::SOUL_EATER ) ) { resurrected = enemy.Resurrect( killed * enemy.Monster::GetHitPoints(), true, false ); } else if ( enemy.isAbilityPresent( fheroes2::MonsterAbilityType::HP_DRAIN ) ) { resurrected = enemy.Resurrect( killed * Monster::GetHitPoints(), false, false ); } if ( resurrected > 0 ) { DEBUG_LOG( DBG_BATTLE, DBG_TRACE, String() << ", enemy: " << enemy.String() << ", resurrected: " << resurrected ) } if ( ptrResurrected != nullptr ) { *ptrResurrected = resurrected; } return killed; } bool Battle::Unit::AllowApplySpell( const Spell & spell, const HeroBase * applyingHero, const bool forceApplyToAlly /* = false */ ) const { if ( Modes( CAP_MIRRORIMAGE ) && ( spell == Spell::ANTIMAGIC || spell == Spell::MIRRORIMAGE ) ) { return false; } if ( Modes( CAP_MIRROROWNER ) && spell == Spell::MIRRORIMAGE ) { return false; } if ( applyingHero && spell.isApplyToFriends() && GetColor() != applyingHero->GetColor() ) { return false; } if ( applyingHero && spell.isApplyToEnemies() && GetColor() == applyingHero->GetColor() && !forceApplyToAlly ) { return false; } return ( GetMagicResist( spell, applyingHero ) < 100 ); } bool Battle::Unit::isUnderSpellEffect( const Spell & spell ) const { switch ( spell.GetID() ) { case Spell::BLESS: case Spell::MASSBLESS: return Modes( SP_BLESS ); case Spell::BLOODLUST: return Modes( SP_BLOODLUST ); case Spell::CURSE: case Spell::MASSCURSE: return Modes( SP_CURSE ); case Spell::HASTE: case Spell::MASSHASTE: return Modes( SP_HASTE ); case Spell::SHIELD: case Spell::MASSSHIELD: return Modes( SP_SHIELD ); case Spell::SLOW: case Spell::MASSSLOW: return Modes( SP_SLOW ); case Spell::STONESKIN: case Spell::STEELSKIN: return Modes( SP_STONESKIN | SP_STEELSKIN ); case Spell::BLIND: case Spell::PARALYZE: case Spell::PETRIFY: return Modes( SP_BLIND | SP_PARALYZE | SP_STONE ); case Spell::DRAGONSLAYER: return Modes( SP_DRAGONSLAYER ); case Spell::ANTIMAGIC: return Modes( SP_ANTIMAGIC ); case Spell::BERSERKER: return Modes( SP_BERSERKER ); case Spell::HYPNOTIZE: return Modes( SP_HYPNOTIZE ); case Spell::MIRRORIMAGE: return Modes( CAP_MIRROROWNER ); case Spell::DISRUPTINGRAY: return GetDefense() < spell.ExtraValue(); default: break; } return false; } bool Battle::Unit::ApplySpell( const Spell & spell, const HeroBase * applyingHero, TargetInfo & target ) { // HACK!!! Chain lightning is the only spell which can't be cast on allies but could be applied on them const bool isForceApply = ( spell.GetID() == Spell::CHAINLIGHTNING ); if ( !AllowApplySpell( spell, applyingHero, isForceApply ) ) { return false; } DEBUG_LOG( DBG_BATTLE, DBG_TRACE, spell.GetName() << " to " << String() ) const uint32_t spoint = applyingHero ? applyingHero->GetPower() : DEFAULT_SPELL_DURATION; if ( spell.isDamage() ) { SpellApplyDamage( spell, spoint, applyingHero, target ); } else if ( spell.isRestore() || spell.isResurrect() ) { SpellRestoreAction( spell, spoint, applyingHero ); } else { SpellModesAction( spell, spoint, applyingHero ); } return true; } std::vector Battle::Unit::getCurrentSpellEffects() const { std::vector spellList; if ( Modes( SP_BLESS ) ) { spellList.emplace_back( Spell::BLESS ); } if ( Modes( SP_CURSE ) ) { spellList.emplace_back( Spell::CURSE ); } if ( Modes( SP_HASTE ) ) { spellList.emplace_back( Spell::HASTE ); } if ( Modes( SP_SLOW ) ) { spellList.emplace_back( Spell::SLOW ); } if ( Modes( SP_SHIELD ) ) { spellList.emplace_back( Spell::SHIELD ); } if ( Modes( SP_BLOODLUST ) ) { spellList.emplace_back( Spell::BLOODLUST ); } if ( Modes( SP_STONESKIN ) ) { spellList.emplace_back( Spell::STONESKIN ); } if ( Modes( SP_STEELSKIN ) ) { spellList.emplace_back( Spell::STEELSKIN ); } if ( Modes( SP_BLIND ) ) { spellList.emplace_back( Spell::BLIND ); } if ( Modes( SP_PARALYZE ) ) { spellList.emplace_back( Spell::PARALYZE ); } if ( Modes( SP_STONE ) ) { spellList.emplace_back( Spell::PETRIFY ); } if ( Modes( SP_DRAGONSLAYER ) ) { spellList.emplace_back( Spell::DRAGONSLAYER ); } if ( Modes( SP_BERSERKER ) ) { spellList.emplace_back( Spell::BERSERKER ); } if ( Modes( SP_HYPNOTIZE ) ) { spellList.emplace_back( Spell::HYPNOTIZE ); } if ( Modes( CAP_MIRROROWNER ) ) { spellList.emplace_back( Spell::MIRRORIMAGE ); } return spellList; } std::string Battle::Unit::String( bool more ) const { std::stringstream ss; ss << "Unit: " << "[ " << // info GetCount() << " " << GetName() << ", " << Color::String( GetColor() ) << ", pos: " << GetHeadIndex() << ", " << GetTailIndex() << ( reflect ? ", reflect" : "" ); if ( more ) ss << ", mode(" << GetHexString( modes ) << ")" << ", uid(" << GetHexString( _uid ) << ")" << ", speed(" << Speed::String( GetSpeed() ) << ", " << static_cast( GetSpeed() ) << ")" << ", hp(" << hp << ")" << ", died(" << dead << ")"; ss << " ]"; return ss.str(); } bool Battle::Unit::AllowResponse() const { // Hypnotized units never respond to an attack if ( Modes( SP_HYPNOTIZE ) ) { return false; } // Blindness can be cast by an attacking unit. There should never be any retaliation in this case. if ( Modes( SP_BLIND ) && !_blindRetaliation ) { return false; } // Paralyzing magic can be cast by an attacking unit. There should never be any retaliation in this case. if ( Modes( IS_PARALYZE_MAGIC ) ) { return false; } return ( !Modes( TR_RESPONDED ) ); } void Battle::Unit::SetResponse() { if ( isAbilityPresent( fheroes2::MonsterAbilityType::ALWAYS_RETALIATE ) ) { return; } SetModes( TR_RESPONDED ); } void Battle::Unit::PostAttackAction( const Unit & enemy ) { if ( isArchers() && !isHandFighting( *this, enemy ) ) { const HeroBase * hero = GetCommander(); if ( hero == nullptr || !hero->GetBagArtifacts().isArtifactBonusPresent( fheroes2::ArtifactBonusType::ENDLESS_AMMUNITION ) ) { assert( !Modes( CAP_TOWER ) && shots > 0 ); --shots; } } ResetModes( LUCK_GOOD | LUCK_BAD ); } void Battle::Unit::SetBlindRetaliation( bool value ) { _blindRetaliation = value; } uint32_t Battle::Unit::GetAttack() const { uint32_t res = ArmyTroop::GetAttack(); if ( Modes( SP_BLOODLUST ) ) res += Spell( Spell::BLOODLUST ).ExtraValue(); return res; } uint32_t Battle::Unit::GetDefense() const { uint32_t res = ArmyTroop::GetDefense(); if ( Modes( SP_STONESKIN ) ) res += Spell( Spell::STONESKIN ).ExtraValue(); else if ( Modes( SP_STEELSKIN ) ) res += Spell( Spell::STEELSKIN ).ExtraValue(); // disrupting ray accumulate effect if ( disruptingray ) { const uint32_t step = disruptingray * Spell( Spell::DISRUPTINGRAY ).ExtraValue(); if ( step >= res ) res = 1; else res -= step; } // check moat const Castle * castle = Arena::GetCastle(); if ( castle && castle->isBuild( BUILD_MOAT ) && ( Board::isMoatIndex( GetHeadIndex(), *this ) || Board::isMoatIndex( GetTailIndex(), *this ) ) ) { const uint32_t step = GameStatic::GetBattleMoatReduceDefense(); if ( step >= res ) res = 1; else res -= step; } return res; } int32_t Battle::Unit::evaluateThreatForUnit( const Unit & defender ) const { const Unit & attacker = *this; const uint32_t attackerDamageToDefender = [&defender, &attacker]() { if ( attacker.Modes( SP_CURSE ) ) { return attacker.CalculateMinDamage( defender ); } if ( attacker.Modes( SP_BLESS ) ) { return attacker.CalculateMaxDamage( defender ); } return ( attacker.CalculateMinDamage( defender ) + attacker.CalculateMaxDamage( defender ) ) / 2; }(); double attackerThreat = attackerDamageToDefender; { const double distanceModifier = [&defender, &attacker]() { if ( defender.Modes( CAP_TOWER ) ) { return 1.0; } if ( attacker.isFlying() || attacker.isArchers() ) { return 1.0; } const uint32_t attackerSpeed = attacker.GetSpeed( true, false ); assert( attackerSpeed > Speed::STANDING ); const uint32_t attackRange = attackerSpeed + 1; const uint32_t distance = Board::GetDistance( attacker.GetPosition(), defender.GetPosition() ); assert( distance > 0 ); if ( distance <= attackRange ) { return 1.0; } return 1.5 * static_cast( distance ) / static_cast( attackerSpeed ); }(); attackerThreat /= distanceModifier; } if ( attacker.isDoubleAttack() ) { // The following logic of accounting for potential retaliatory damage is intended only for the case when one unit directly attacks another, and will not work in // cases where one unit attacks several units at once assert( !attacker.isAbilityPresent( fheroes2::MonsterAbilityType::TWO_CELL_MELEE_ATTACK ) && !attacker.isAbilityPresent( fheroes2::MonsterAbilityType::ALL_ADJACENT_CELL_MELEE_ATTACK ) && !attacker.isAbilityPresent( fheroes2::MonsterAbilityType::AREA_SHOT ) ); const uint32_t retaliatoryDamage = [&defender, &attacker, attackerDamageToDefender]() -> uint32_t { if ( defender.Modes( CAP_TOWER ) ) { return 0; } if ( attacker.isIgnoringRetaliation() ) { return 0; } if ( attacker.isArchers() && !attacker.isHandFighting() ) { return 0; } return defender.EstimateRetaliatoryDamage( attackerDamageToDefender ); }(); // If the defender is able to retaliate the attacker after his first attack, then the attacker's second attack can cause less damage than the first if ( retaliatoryDamage > 0 ) { assert( attacker.GetHitPoints() > 0 ); // Rough but quick estimate attackerThreat += attackerThreat * ( 1.0 - std::min( static_cast( retaliatoryDamage ) / static_cast( attacker.GetHitPoints() ), 1.0 ) ); } // Otherwise, estimate the second attack as approximately equal to the first in damage else { attackerThreat *= 2; } } if ( attacker.isAbilityPresent( fheroes2::MonsterAbilityType::ENEMY_HALFING ) ) { attackerThreat *= 2; } if ( attacker.isAbilityPresent( fheroes2::MonsterAbilityType::SOUL_EATER ) ) { attackerThreat *= 3; } if ( attacker.isAbilityPresent( fheroes2::MonsterAbilityType::HP_DRAIN ) ) { attackerThreat *= 1.3; } { const std::vector & attackerAbilities = fheroes2::getMonsterData( id ).battleStats.abilities; const auto spellCasterAbilityIter = std::find( attackerAbilities.begin(), attackerAbilities.end(), fheroes2::MonsterAbility( fheroes2::MonsterAbilityType::SPELL_CASTER ) ); if ( spellCasterAbilityIter != attackerAbilities.end() ) { const auto getDefenderDamage = [&defender]() { if ( defender.Modes( SP_CURSE ) ) { return defender.GetDamageMin(); } if ( defender.Modes( SP_BLESS ) ) { return defender.GetDamageMax(); } return ( defender.GetDamageMin() + defender.GetDamageMax() ) / 2; }; switch ( spellCasterAbilityIter->value ) { case Spell::BLIND: case Spell::PARALYZE: case Spell::PETRIFY: // Creature's built-in magic resistance (not 100% immunity but resistance, as, for example, with Dwarves) never works against the built-in magic of // another creature (for example, Unicorn's Blind ability). Only the probability of triggering the built-in magic matters. if ( defender.AllowApplySpell( spellCasterAbilityIter->value, nullptr ) ) { attackerThreat += static_cast( getDefenderDamage() ) * spellCasterAbilityIter->percentage / 100.0; } break; case Spell::DISPEL: // TODO: add the logic to evaluate this spell value. break; case Spell::CURSE: // Creature's built-in magic resistance (not 100% immunity but resistance, as, for example, with Dwarves) never works against the built-in magic of // another creature (for example, Unicorn's Blind ability). Only the probability of triggering the built-in magic matters. if ( defender.AllowApplySpell( spellCasterAbilityIter->value, nullptr ) ) { attackerThreat += static_cast( getDefenderDamage() ) * spellCasterAbilityIter->percentage / 100.0 / 10.0; } break; default: // Did you add a new spell casting ability? Add the logic above! assert( 0 ); break; } } } // Give the mirror images a higher priority, as they can be destroyed in 1 hit if ( attacker.Modes( CAP_MIRRORIMAGE ) ) { attackerThreat *= 10; } // Heavy penalty for hitting our own units if ( attacker.GetColor() == defender.GetCurrentColor() ) { // TODO: remove this temporary assertion assert( dynamic_cast( this ) == nullptr ); attackerThreat *= -2; } // Negative value of units that changed the side else if ( attacker.GetColor() != attacker.GetCurrentColor() ) { attackerThreat *= -1; } // Ignore disabled enemy units else if ( attacker.isImmovable() ) { attackerThreat = 0; } // Reduce the priority of those enemy units that have already got their turn else if ( attacker.Modes( TR_MOVED ) ) { attackerThreat /= 1.25; } return static_cast( attackerThreat * 100 ); } uint32_t Battle::Unit::GetHitPoints() const { return hp; } Funds Battle::Unit::GetSurrenderCost() const { // Resurrected (not truly resurrected) units should not be taken into account when calculating the cost of surrender return GetCost() * ( GetDead() > GetInitialCount() ? 0 : GetInitialCount() - GetDead() ); } int Battle::Unit::GetControl() const { return !GetArmy() ? CONTROL_AI : GetArmy()->GetControl(); } void Battle::Unit::SpellModesAction( const Spell & spell, uint32_t duration, const HeroBase * applyingHero ) { if ( applyingHero ) { duration += applyingHero->GetBagArtifacts().getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::EVERY_COMBAT_SPELL_DURATION ); } switch ( spell.GetID() ) { case Spell::BLESS: case Spell::MASSBLESS: replaceAffection( SP_CURSE, SP_BLESS, duration ); break; case Spell::BLOODLUST: addAffection( SP_BLOODLUST, 3 ); break; case Spell::CURSE: case Spell::MASSCURSE: replaceAffection( SP_BLESS, SP_CURSE, duration ); break; case Spell::HASTE: case Spell::MASSHASTE: replaceAffection( SP_SLOW, SP_HASTE, duration ); break; case Spell::DISPEL: case Spell::MASSDISPEL: removeAffection( IS_MAGIC ); break; case Spell::SHIELD: case Spell::MASSSHIELD: addAffection( SP_SHIELD, duration ); break; case Spell::SLOW: case Spell::MASSSLOW: replaceAffection( SP_HASTE, SP_SLOW, duration ); break; case Spell::STONESKIN: replaceAffection( SP_STEELSKIN, SP_STONESKIN, duration ); break; case Spell::BLIND: addAffection( SP_BLIND, duration ); // Blindness can be cast by an attacking unit. In this case, there should be no response to the attack. _blindRetaliation = false; break; case Spell::DRAGONSLAYER: addAffection( SP_DRAGONSLAYER, duration ); break; case Spell::STEELSKIN: replaceAffection( SP_STONESKIN, SP_STEELSKIN, duration ); break; case Spell::ANTIMAGIC: replaceAffection( IS_MAGIC, SP_ANTIMAGIC, duration ); break; case Spell::PARALYZE: addAffection( SP_PARALYZE, duration ); break; case Spell::BERSERKER: replaceAffection( SP_HYPNOTIZE, SP_BERSERKER, duration ); break; case Spell::HYPNOTIZE: replaceAffection( SP_BERSERKER, SP_HYPNOTIZE, duration ); break; case Spell::PETRIFY: addAffection( SP_STONE, duration ); break; case Spell::MIRRORIMAGE: // Special case, CAP_MIRROROWNER mode will be set when mirror image unit will be created affected.AddMode( CAP_MIRROROWNER, duration ); break; case Spell::DISRUPTINGRAY: ++disruptingray; break; default: assert( 0 ); break; } } void Battle::Unit::SpellApplyDamage( const Spell & spell, const uint32_t spellPoints, const HeroBase * applyingHero, TargetInfo & target ) { assert( spell.isDamage() ); const uint32_t dmg = CalculateSpellDamage( spell, spellPoints, applyingHero, target.damage, false /* ignore defending hero */ ); // apply damage if ( dmg ) { target.damage = dmg; target.killed = ApplyDamage( dmg ); } } uint32_t Battle::Unit::CalculateSpellDamage( const Spell & spell, uint32_t spellPoints, const HeroBase * applyingHero, const uint32_t targetDamage, const bool ignoreDefendingHero ) const { assert( spell.isDamage() ); // TODO: use fheroes2::getSpellDamage function to remove code duplication. uint32_t dmg = spell.Damage() * spellPoints; switch ( GetID() ) { case Monster::IRON_GOLEM: case Monster::STEEL_GOLEM: switch ( spell.GetID() ) { // 50% damage case Spell::COLDRAY: case Spell::COLDRING: case Spell::FIREBALL: case Spell::FIREBLAST: case Spell::LIGHTNINGBOLT: case Spell::CHAINLIGHTNING: case Spell::ELEMENTALSTORM: case Spell::ARMAGEDDON: dmg /= 2; break; default: break; } break; case Monster::WATER_ELEMENT: switch ( spell.GetID() ) { // 200% damage case Spell::FIREBALL: case Spell::FIREBLAST: dmg *= 2; break; default: break; } break; case Monster::AIR_ELEMENT: switch ( spell.GetID() ) { // 200% damage case Spell::ELEMENTALSTORM: case Spell::LIGHTNINGBOLT: case Spell::CHAINLIGHTNING: dmg *= 2; break; default: break; } break; case Monster::FIRE_ELEMENT: switch ( spell.GetID() ) { // 200% damage case Spell::COLDRAY: case Spell::COLDRING: dmg *= 2; break; default: break; } break; default: break; } // check artifact if ( applyingHero ) { const HeroBase * defendingHero = GetCommander(); const bool useDefendingHeroArts = defendingHero && !ignoreDefendingHero; switch ( spell.GetID() ) { case Spell::COLDRAY: case Spell::COLDRING: { std::vector extraDamagePercent = applyingHero->GetBagArtifacts().getTotalArtifactMultipliedPercent( fheroes2::ArtifactBonusType::COLD_SPELL_EXTRA_EFFECTIVENESS_PERCENT ); for ( const int32_t value : extraDamagePercent ) { dmg = dmg * ( 100 + value ) / 100; } if ( useDefendingHeroArts ) { const std::vector damageReductionPercent = defendingHero->GetBagArtifacts().getTotalArtifactMultipliedPercent( fheroes2::ArtifactBonusType::COLD_SPELL_DAMAGE_REDUCTION_PERCENT ); for ( const int32_t value : damageReductionPercent ) { dmg = dmg * ( 100 - value ) / 100; } extraDamagePercent = defendingHero->GetBagArtifacts().getTotalArtifactMultipliedPercent( fheroes2::ArtifactCurseType::COLD_SPELL_EXTRA_DAMAGE_PERCENT ); for ( const int32_t value : extraDamagePercent ) { dmg = dmg * ( 100 + value ) / 100; } } break; } case Spell::FIREBALL: case Spell::FIREBLAST: { std::vector extraDamagePercent = applyingHero->GetBagArtifacts().getTotalArtifactMultipliedPercent( fheroes2::ArtifactBonusType::FIRE_SPELL_EXTRA_EFFECTIVENESS_PERCENT ); for ( const int32_t value : extraDamagePercent ) { dmg = dmg * ( 100 + value ) / 100; } if ( useDefendingHeroArts ) { const std::vector damageReductionPercent = defendingHero->GetBagArtifacts().getTotalArtifactMultipliedPercent( fheroes2::ArtifactBonusType::FIRE_SPELL_DAMAGE_REDUCTION_PERCENT ); for ( const int32_t value : damageReductionPercent ) { dmg = dmg * ( 100 - value ) / 100; } extraDamagePercent = defendingHero->GetBagArtifacts().getTotalArtifactMultipliedPercent( fheroes2::ArtifactCurseType::FIRE_SPELL_EXTRA_DAMAGE_PERCENT ); for ( const int32_t value : extraDamagePercent ) { dmg = dmg * ( 100 + value ) / 100; } } break; } case Spell::LIGHTNINGBOLT: case Spell::CHAINLIGHTNING: { const std::vector extraDamagePercent = applyingHero->GetBagArtifacts().getTotalArtifactMultipliedPercent( fheroes2::ArtifactBonusType::LIGHTNING_SPELL_EXTRA_EFFECTIVENESS_PERCENT ); for ( const int32_t value : extraDamagePercent ) { dmg = dmg * ( 100 + value ) / 100; } if ( useDefendingHeroArts ) { const std::vector damageReductionPercent = defendingHero->GetBagArtifacts().getTotalArtifactMultipliedPercent( fheroes2::ArtifactBonusType::LIGHTNING_SPELL_DAMAGE_REDUCTION_PERCENT ); for ( const int32_t value : damageReductionPercent ) { dmg = dmg * ( 100 - value ) / 100; } } // update orders damage if ( spell.GetID() == Spell::CHAINLIGHTNING ) { switch ( targetDamage ) { case 0: break; case 1: dmg /= 2; break; case 2: dmg /= 4; break; case 3: dmg /= 8; break; default: break; } } break; } case Spell::ELEMENTALSTORM: case Spell::ARMAGEDDON: { if ( useDefendingHeroArts ) { const std::vector damageReductionPercent = defendingHero->GetBagArtifacts().getTotalArtifactMultipliedPercent( fheroes2::ArtifactBonusType::ELEMENTAL_SPELL_DAMAGE_REDUCTION_PERCENT ); for ( const int32_t value : damageReductionPercent ) { dmg = dmg * ( 100 - value ) / 100; } } break; } default: break; } } return dmg; } void Battle::Unit::SpellRestoreAction( const Spell & spell, const uint32_t spellPoints, const HeroBase * applyingHero ) { switch ( spell.GetID() ) { case Spell::CURE: case Spell::MASSCURE: // clear bad magic removeAffection( IS_BAD_MAGIC ); // restore hp += fheroes2::getHPRestorePoints( spell, spellPoints, applyingHero ); if ( hp > ArmyTroop::GetHitPoints() ) { hp = ArmyTroop::GetHitPoints(); } break; case Spell::RESURRECT: case Spell::ANIMATEDEAD: case Spell::RESURRECTTRUE: { if ( !isValid() ) { Graveyard * graveyard = Arena::GetGraveyard(); assert( graveyard != nullptr ); graveyard->RemoveTroop( *this ); } const uint32_t restore = fheroes2::getResurrectPoints( spell, spellPoints, applyingHero ); const uint32_t resurrect = Resurrect( restore, false, ( spell == Spell::RESURRECT ) ); // Put the unit back on the board SetPosition( GetPosition() ); if ( Arena::GetInterface() ) { std::string str( _n( "%{count} %{name} rises from the dead!", "%{count} %{name} rise from the dead!", resurrect ) ); StringReplace( str, "%{count}", resurrect ); StringReplace( str, "%{name}", Monster::GetPluralName( resurrect ) ); Arena::GetInterface()->SetStatus( str, true ); } break; } default: assert( 0 ); break; } } bool Battle::Unit::isDoubleAttack() const { if ( !isArchers() || isHandFighting() ) { return isAbilityPresent( fheroes2::MonsterAbilityType::DOUBLE_MELEE_ATTACK ); } // Archers with double shooting ability can only fire a second shot if they have enough ammo if ( isAbilityPresent( fheroes2::MonsterAbilityType::DOUBLE_SHOOTING ) ) { return GetShots() > 1; } return false; } uint32_t Battle::Unit::GetMagicResist( const Spell & spell, const HeroBase * applyingHero ) const { if ( Modes( SP_ANTIMAGIC ) ) { return 100; } switch ( spell.GetID() ) { case Spell::CURE: case Spell::MASSCURE: if ( !isHaveDamage() && !( modes & IS_BAD_MAGIC ) ) { return 100; } break; case Spell::RESURRECT: case Spell::RESURRECTTRUE: case Spell::ANIMATEDEAD: if ( GetCount() == _initialCount ) { return 100; } break; case Spell::DISPEL: case Spell::MASSDISPEL: if ( !( modes & IS_MAGIC ) ) { return 100; } break; case Spell::HYPNOTIZE: assert( applyingHero != nullptr ); if ( fheroes2::getHypnotizeMonsterHPPoints( spell, applyingHero->GetPower(), applyingHero ) < hp ) { return 100; } break; default: break; } const Artifact spellImmunityArt = getImmunityArtifactForSpell( GetCommander(), spell ); if ( spellImmunityArt.isValid() ) { return 100; } return fheroes2::getSpellResistance( id, spell.GetID() ); } int Battle::Unit::GetSpellMagic( Rand::DeterministicRandomGenerator & randomGenerator ) const { const std::vector & abilities = fheroes2::getMonsterData( GetID() ).battleStats.abilities; const auto foundAbility = std::find( abilities.begin(), abilities.end(), fheroes2::MonsterAbility( fheroes2::MonsterAbilityType::SPELL_CASTER ) ); if ( foundAbility == abilities.end() ) { // Not a spell caster. return Spell::NONE; } if ( randomGenerator.Get( 1, 100 ) > foundAbility->percentage ) { // No luck to cast the spell. return Spell::NONE; } return foundAbility->value; } bool Battle::Unit::isHaveDamage() const { return hp < _initialCount * Monster::GetHitPoints(); } bool Battle::Unit::SwitchAnimation( int rule, bool reverse ) { if ( rule == Monster_Info::STATIC && GetAnimationState() != Monster_Info::IDLE ) { // Reset the delay before switching to the 'IDLE' animation from 'STATIC'. checkIdleDelay(); } // We return true if the animation was correctly changed and if it is valid. return ( animation.switchAnimation( rule, reverse ) && animation.isValid() ); } bool Battle::Unit::SwitchAnimation( const std::vector & animationList, bool reverse ) { // We return true if the animation was correctly changed and if it is valid. return ( animation.switchAnimation( animationList, reverse ) && animation.isValid() ); } int Battle::Unit::M82Attk( const Unit & enemy ) const { const fheroes2::MonsterSound & sounds = fheroes2::getMonsterData( id ).sounds; if ( isArchers() && !isHandFighting( *this, enemy ) ) { // Added a new shooter without sound? Grant him a voice! assert( sounds.rangeAttack != M82::UNKNOWN ); return sounds.rangeAttack; } assert( sounds.meleeAttack != M82::UNKNOWN ); return sounds.meleeAttack; } int Battle::Unit::M82Kill() const { return fheroes2::getMonsterData( id ).sounds.death; } int Battle::Unit::M82Move() const { return fheroes2::getMonsterData( id ).sounds.movement; } int Battle::Unit::M82Wnce() const { return fheroes2::getMonsterData( id ).sounds.wince; } int Battle::Unit::M82Expl() const { return fheroes2::getMonsterData( id ).sounds.explosion; } int Battle::Unit::M82Tkof() const { return fheroes2::getMonsterData( id ).sounds.takeoff; } int Battle::Unit::M82Land() const { return fheroes2::getMonsterData( id ).sounds.landing; } fheroes2::Point Battle::Unit::GetBackPoint() const { const fheroes2::Rect & rt = position.GetRect(); return reflect ? fheroes2::Point( rt.x + rt.width, rt.y + rt.height / 2 ) : fheroes2::Point( rt.x, rt.y + rt.height / 2 ); } fheroes2::Point Battle::Unit::GetCenterPoint() const { const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( GetMonsterSprite(), GetFrame() ); const fheroes2::Rect & pos = position.GetRect(); const int32_t centerY = pos.y + pos.height + sprite.y() / 2 - 10; return fheroes2::Point( pos.x + pos.width / 2, centerY ); } fheroes2::Point Battle::Unit::GetStartMissileOffset( size_t direction ) const { return animation.getProjectileOffset( direction ); } int Battle::Unit::GetCurrentColor() const { if ( Modes( SP_BERSERKER ) ) { return -1; // Be aware of unknown color } if ( Modes( SP_HYPNOTIZE ) ) { const Arena * arena = GetArena(); assert( arena != nullptr ); return arena->GetOppositeColor( GetArmyColor() ); } return GetColor(); } int Battle::Unit::GetCurrentOrArmyColor() const { const int color = GetCurrentColor(); // Unknown color in case of SP_BERSERKER mode if ( color < 0 ) { return GetArmyColor(); } return color; } int Battle::Unit::GetCurrentControl() const { // Let's say that berserkers belong to AI, which is not present in the battle if ( Modes( SP_BERSERKER ) ) { return CONTROL_AI; } if ( Modes( SP_HYPNOTIZE ) ) { const Arena * arena = GetArena(); assert( arena != nullptr ); return arena->getForce( GetCurrentColor() ).GetControl(); } return GetControl(); } const HeroBase * Battle::Unit::GetCommander() const { return GetArmy() ? GetArmy()->GetCommander() : nullptr; } const HeroBase * Battle::Unit::GetCurrentOrArmyCommander() const { const Arena * arena = GetArena(); assert( arena != nullptr ); return arena->getCommander( GetCurrentOrArmyColor() ); } void Battle::Unit::addAffection( const uint32_t mode, const uint32_t duration ) { assert( CountBits( mode ) == 1 ); SetModes( mode ); affected.AddMode( mode, duration ); } void Battle::Unit::removeAffection( const uint32_t mode ) { ResetModes( mode ); affected.RemoveMode( mode ); } void Battle::Unit::replaceAffection( const uint32_t modeToReplace, const uint32_t replacementMode, const uint32_t duration ) { removeAffection( modeToReplace ); addAffection( replacementMode, duration ); } fheroes2-1.0.12+dfsg/src/fheroes2/battle/battle_troop.h000066400000000000000000000306671456075706000227030ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2BATTLE_TROOP_H #define H2BATTLE_TROOP_H #include #include #include #include #include #include "army_troop.h" #include "battle_animation.h" #include "battle_cell.h" #include "bitmodes.h" #include "math_base.h" #include "players.h" #include "resource.h" class Spell; class HeroBase; namespace Rand { class DeterministicRandomGenerator; } namespace Battle { struct TargetInfo; struct ModeDuration : public std::pair { ModeDuration( uint32_t, uint32_t ); bool isMode( uint32_t ) const; bool isZeroDuration() const; void DecreaseDuration(); }; struct ModesAffected : public std::vector { ModesAffected(); uint32_t GetMode( uint32_t ) const; void AddMode( uint32_t, uint32_t ); void RemoveMode( uint32_t ); void DecreaseDuration(); uint32_t FindZeroDuration() const; }; class Unit : public ArmyTroop, public BitModes, public Control { public: Unit( const Troop & t, const Position & pos, const bool ref, const uint32_t uid ); Unit( const Unit & ) = delete; Unit & operator=( const Unit & ) = delete; bool isModes( uint32_t v ) const override; bool isBattle() const override; std::string GetShotString() const override; std::string GetSpeedString() const override; uint32_t GetHitPointsLeft() const override; virtual uint32_t GetMissingHitPoints() const; uint32_t GetAffectedDuration( uint32_t mod ) const override; uint32_t GetSpeed() const override; int GetMorale() const override; Unit * GetMirror() { return mirror; } void SetMirror( Unit * ptr ) { mirror = ptr; } void SetRandomMorale( Rand::DeterministicRandomGenerator & randomGenerator ); void SetRandomLuck( Rand::DeterministicRandomGenerator & randomGenerator ); void NewTurn(); bool isFlying() const; bool isDoubleAttack() const; bool AllowResponse() const; // Checks whether this unit is forced to fight in melee (there is an enemy unit nearby) bool isHandFighting() const; bool isReflect() const { return reflect; } bool isHaveDamage() const; bool isOutOfCastleWalls() const; std::string String( bool more = false ) const; uint32_t GetUID() const { return _uid; } bool isUID( uint32_t v ) const { return _uid == v; } int32_t GetHeadIndex() const; int32_t GetTailIndex() const; const Position & GetPosition() const { return position; } void SetPosition( const int32_t idx ); void SetPosition( const Position & pos ); void SetReflection( bool ); uint32_t GetAttack() const override; uint32_t GetDefense() const override; int GetArmyColor() const { return ArmyTroop::GetColor(); } int GetColor() const override { return GetArmyColor(); } // Returns the current color of the unit according to its current combat state (the unit // may be under a spell that changes its affiliation). int GetCurrentColor() const; // Returns the current unit color (if valid, the unit's color can be invalid if the unit // is under the Berserker spell), otherwise returns the color of the unit's army. int GetCurrentOrArmyColor() const; int GetControl() const override; int GetCurrentControl() const; // Returns the current speed of the unit, optionally performing additional checks in accordance // with the call arguments. If 'skipStandingCheck' is set to false, then the method returns // Speed::STANDING if the unit is immovable due to spells cast on it or if this unit is dead // (contains 0 fighters). Additionally, if 'skipMovedCheck' is set to false, then this method // returns Speed::STANDING if the unit has already completed its turn. If 'skipStandingCheck' // is set to true, then the value of 'skipMovedCheck' doesn't matter. uint32_t GetSpeed( const bool skipStandingCheck, const bool skipMovedCheck ) const; uint32_t GetDamage( const Unit & enemy, Rand::DeterministicRandomGenerator & randomGenerator ) const; // Returns the threat level of this unit, calculated as if it attacked the 'defender' unit. See // the implementation for details. int32_t evaluateThreatForUnit( const Unit & defender ) const; uint32_t GetInitialCount() const; uint32_t GetDead() const; uint32_t GetHitPoints() const; // Returns the cost of this unit, suitable for calculating the cost of surrendering the army (see // the implementation for details). Discounts are not applied when calculating this cost. Funds GetSurrenderCost() const; uint32_t GetShots() const override { return shots; } bool isImmovable() const; uint32_t ApplyDamage( Unit & enemy, const uint32_t dmg, uint32_t & killed, uint32_t * ptrResurrected ); uint32_t CalculateMinDamage( const Unit & ) const; uint32_t CalculateMaxDamage( const Unit & ) const; uint32_t CalculateDamageUnit( const Unit & enemy, double dmg ) const; // Returns a very rough estimate of the retaliatory damage after this unit receives the damage of the specified value. // The returned value is not suitable for accurate calculations, but only for approximate comparison with other units // in similar circumstances. uint32_t EstimateRetaliatoryDamage( const uint32_t damageTaken ) const; bool ApplySpell( const Spell & spell, const HeroBase * applyingHero, TargetInfo & target ); bool AllowApplySpell( const Spell & spell, const HeroBase * applyingHero, const bool forceApplyToAlly = false ) const; bool isUnderSpellEffect( const Spell & spell ) const; std::vector getCurrentSpellEffects() const; void PostAttackAction( const Unit & enemy ); // Sets whether a unit performs a retaliatory attack while being blinded (i.e. with reduced efficiency) void SetBlindRetaliation( bool value ); uint32_t CalculateSpellDamage( const Spell & spell, uint32_t spellPoints, const HeroBase * applyingHero, const uint32_t targetDamage, const bool ignoreDefendingHero ) const; bool SwitchAnimation( int rule, bool reverse = false ); bool SwitchAnimation( const std::vector & animationList, bool reverse = false ); void IncreaseAnimFrame( bool loop = false ) { animation.playAnimation( loop ); } bool isFinishAnimFrame() const { return animation.isLastFrame(); } int GetFrame() const { return animation.getFrame(); } uint8_t GetCustomAlpha() const { return customAlphaMask; } void SetCustomAlpha( uint8_t alpha ) { customAlphaMask = alpha; } fheroes2::Point GetStartMissileOffset( size_t ) const; int M82Attk( const Unit & enemy ) const; int M82Kill() const; int M82Move() const; int M82Wnce() const; int M82Expl() const; int M82Tkof() const; int M82Land() const; fheroes2::Point GetBackPoint() const; fheroes2::Point GetCenterPoint() const; fheroes2::Rect GetRectPosition() const { return position.GetRect(); } uint32_t HowManyWillBeKilled( const uint32_t dmg ) const; void SetResponse(); void UpdateDirection(); bool UpdateDirection( const fheroes2::Rect & ); void PostKilledAction(); uint32_t GetMagicResist( const Spell & spell, const HeroBase * applyingHero ) const; int GetSpellMagic( Rand::DeterministicRandomGenerator & randomGenerator ) const; const HeroBase * GetCommander() const; // If the color of the current unit is valid (i.e. this unit is not under the influence of a Berserker spell), then returns the commander of the army with the // corresponding color. Otherwise, returns the commander of the unit's army. const HeroBase * GetCurrentOrArmyCommander() const; // Checks whether the attacker will fight the defender in melee static bool isHandFighting( const Unit & attacker, const Unit & defender ); int GetAnimationState() const { return animation.getCurrentState(); } bool isIdling() const; bool checkIdleDelay() { return idleTimer.checkDelay(); } // Removes temporary affection(s) (usually spell effect(s)). Multiple affections can be removed using a single call. void removeAffection( const uint32_t mode ); // TODO: find a better way to expose it without a million getters/setters AnimationState animation; private: uint32_t ApplyDamage( const uint32_t dmg ); uint32_t Resurrect( const uint32_t points, const bool allow_overflow, const bool skip_dead ); // Applies a damage-causing spell to this unit void SpellApplyDamage( const Spell & spell, const uint32_t spellPoints, const HeroBase * applyingHero, TargetInfo & target ); // Applies a restoring or reviving spell to this unit void SpellRestoreAction( const Spell & spell, const uint32_t spellPoints, const HeroBase * applyingHero ); // Applies a spell to this unit that changes its parameters void SpellModesAction( const Spell & spell, uint32_t duration, const HeroBase * applyingHero ); // Adds a temporary affection (usually a spell effect) with the specified duration. Only one affection can be added. void addAffection( const uint32_t mode, const uint32_t duration ); // Replaces some temporary affection(s) with another affection. Multiple affections can be replaced by a new one (but // only one) with a single call. void replaceAffection( const uint32_t modeToReplace, const uint32_t replacementMode, const uint32_t duration ); const uint32_t _uid; uint32_t hp; uint32_t _initialCount; uint32_t dead; uint32_t shots; uint32_t disruptingray; bool reflect; Position position; ModesAffected affected; Unit * mirror; RandomizedDelay idleTimer; // Whether a unit performs a retaliatory attack while being blinded (i.e. with reduced efficiency) bool _blindRetaliation; uint8_t customAlphaMask; }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/campaign/000077500000000000000000000000001456075706000203245ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/fheroes2/campaign/campaign_data.cpp000066400000000000000000001722221456075706000236060ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include "artifact.h" #include "campaign_data.h" #include "color.h" #include "game_video_type.h" #include "heroes.h" #include "maps_fileinfo.h" #include "monster.h" #include "resource.h" #include "spell.h" #include "tools.h" #include "translations.h" namespace { const std::vector emptyPlayback; std::vector getRolandCampaignAwardData( const int scenarioID ) { std::vector obtainableAwards; switch ( scenarioID ) { case 2: obtainableAwards.emplace_back( 0, Campaign::CampaignAwardData::TYPE_CREATURE_ALLIANCE, Monster::DWARF, gettext_noop( "Dwarven Alliance" ) ); break; case 5: obtainableAwards.emplace_back( 1, Campaign::CampaignAwardData::TYPE_HIREABLE_HERO, Heroes::ELIZA, 0, 0, gettext_noop( "Sorceress Guild" ) ); break; case 6: obtainableAwards.emplace_back( 2, Campaign::CampaignAwardData::TYPE_CARRY_OVER_FORCES, 0, 0, 9 ); break; case 7: obtainableAwards.emplace_back( 3, Campaign::CampaignAwardData::TYPE_GET_ARTIFACT, Artifact::ULTIMATE_CROWN, 1, 9 ); break; case 8: obtainableAwards.emplace_back( 4, Campaign::CampaignAwardData::TYPE_DEFEAT_ENEMY_HERO, Heroes::CORLAGON, 0, 9 ); break; default: break; } return obtainableAwards; } std::vector getArchibaldCampaignAwardData( const int scenarioID ) { std::vector obtainableAwards; switch ( scenarioID ) { case 2: obtainableAwards.emplace_back( 1, Campaign::CampaignAwardData::TYPE_HIREABLE_HERO, Heroes::BRAX, 0, 0, gettext_noop( "Necromancer Guild" ) ); break; case 3: obtainableAwards.emplace_back( 2, Campaign::CampaignAwardData::TYPE_CREATURE_ALLIANCE, Monster::OGRE, gettext_noop( "Ogre Alliance" ) ); obtainableAwards.emplace_back( 3, Campaign::CampaignAwardData::TYPE_CREATURE_CURSE, Monster::DWARF, gettext_noop( "Dwarfbane" ) ); break; case 6: obtainableAwards.emplace_back( 4, Campaign::CampaignAwardData::TYPE_CREATURE_ALLIANCE, Monster::GREEN_DRAGON, gettext_noop( "Dragon Alliance" ) ); break; case 8: obtainableAwards.emplace_back( 5, Campaign::CampaignAwardData::TYPE_GET_ARTIFACT, Artifact::ULTIMATE_CROWN ); break; case 9: obtainableAwards.emplace_back( 6, Campaign::CampaignAwardData::TYPE_CARRY_OVER_FORCES, 0 ); break; default: break; } return obtainableAwards; } std::vector getPriceOfLoyaltyCampaignAwardData( const int scenarioID ) { std::vector obtainableAwards; switch ( scenarioID ) { case 1: obtainableAwards.emplace_back( 0, Campaign::CampaignAwardData::TYPE_GET_ARTIFACT, Artifact::BREASTPLATE_ANDURAN ); break; case 2: obtainableAwards.emplace_back( 1, Campaign::CampaignAwardData::TYPE_CREATURE_ALLIANCE, Monster::ELF, gettext_noop( "Elven Alliance" ) ); obtainableAwards.emplace_back( 2, Campaign::CampaignAwardData::TYPE_RESOURCE_BONUS, Resource::WOOD, 2 ); break; case 5: obtainableAwards.emplace_back( 3, Campaign::CampaignAwardData::TYPE_GET_ARTIFACT, Artifact::HELMET_ANDURAN ); break; case 6: // Will assemble Battle Garb of Anduran along with the previous Anduran set pieces // If we get all the parts, we'll obtain the Battle Garb award while removing the awards for the individual parts obtainableAwards.emplace_back( 4, Campaign::CampaignAwardData::TYPE_GET_ARTIFACT, Artifact::SWORD_ANDURAN ); // seems that Kraeger is a custom name for Dainwin in this case obtainableAwards.emplace_back( 5, Campaign::CampaignAwardData::TYPE_DEFEAT_ENEMY_HERO, Heroes::DAINWIN, gettext_noop( "Kraeger defeated" ) ); break; default: break; } return obtainableAwards; } std::vector getWizardsIsleCampaignAwardData( const int scenarioID ) { std::vector obtainableAwards; switch ( scenarioID ) { case 1: obtainableAwards.emplace_back( 0, Campaign::CampaignAwardData::TYPE_GET_SPELL, Spell::SETEGUARDIAN ); break; case 2: obtainableAwards.emplace_back( 1, Campaign::CampaignAwardData::TYPE_GET_ARTIFACT, Artifact::SPHERE_NEGATION ); break; default: break; } return obtainableAwards; } std::vector getDescendantsCampaignAwardData( const int scenarioID ) { std::vector obtainableAwards; switch ( scenarioID ) { case 2: obtainableAwards.emplace_back( 0, Campaign::CampaignAwardData::TYPE_HIREABLE_HERO, Heroes::JOSEPH, 0, 0, gettext_noop( "Wayward Son" ) ); break; case 3: obtainableAwards.emplace_back( 1, Campaign::CampaignAwardData::TYPE_HIREABLE_HERO, Heroes::UNCLEIVAN, 0, 0, gettext_noop( "Uncle Ivan" ) ); break; case 5: obtainableAwards.emplace_back( 2, Campaign::CampaignAwardData::TYPE_GET_ARTIFACT, Artifact::LEGENDARY_SCEPTER ); break; case 6: obtainableAwards.emplace_back( 3, Campaign::CampaignAwardData::TYPE_CREATURE_ALLIANCE, Monster::ELF, gettext_noop( "Elven Alliance" ) ); break; default: break; } return obtainableAwards; } Campaign::CampaignData getRolandCampaignData() { const int scenarioCount = 11; const std::array scenarioName = { gettext_noop( "Force of Arms" ), gettext_noop( "Annexation" ), gettext_noop( "Save the Dwarves" ), gettext_noop( "Carator Mines" ), gettext_noop( "Turning Point" ), gettext_noop( "scenarioName|Defender" ), gettext_noop( "The Gauntlet" ), gettext_noop( "The Crown" ), gettext_noop( "Corlagon's Defense" ), gettext_noop( "Final Justice" ), gettext_noop( "Betrayal" ) }; const std::array scenarioDescription = { gettext_noop( "Roland needs you to defeat the lords near his castle to begin his war of rebellion against his brother. They are not allied with each other, so they will spend" " most of their time fighting with one another. Victory is yours when you have defeated all of their castles and heroes." ), gettext_noop( "The local lords refuse to swear allegiance to Roland, and must be subdued. They are wealthy and powerful, so be prepared for a tough fight. Capture all enemy castles to win." ), gettext_noop( "Your task is to defend the Dwarves against Archibald's forces. Capture all of the enemy towns and castles to win, and be sure not to lose all of the dwarf towns at once, or the enemy will have won." ), gettext_noop( "You will face four allied enemies in a straightforward fight for resource and treasure. Capture all of the enemy castles for victory." ), gettext_noop( "Your enemies are allied against you and start close by, so be ready to come out fighting. You will need to own all four castles in this small valley to win." ), gettext_noop( "The Sorceress' guild of Noraston has requested Roland's aid against an attack from Archibald's allies. Capture all of the enemy castles to win, and don't lose Noraston, or you'll lose the scenario. (Hint: There is an enemy castle on an island in the ocean.)" ), gettext_noop( "Gather as large an army as possible and capture the enemy castle within 8 weeks. You are opposed by only one enemy, but must travel a long way to get to the enemy castle. Any troops you have in your army at the end of this scenario will be with you in the final battle." ), gettext_noop( "Find the Crown before Archibald's heroes find it. Roland will need the Crown for the final battle against Archibald." ), gettext_noop( "Three allied enemies stand before you and victory, including Lord Corlagon. Roland is in a castle to the northwest, and you will lose if he falls to the enemy. Remember that capturing Lord Corlagon will ensure that he will not fight against you in the final scenario." ), gettext_noop( "This is the final battle. Both you and your enemy are armed to the teeth, and all are allied against you. Capture Archibald to end the war!" ), gettext_noop( "Switching sides leaves you with three castles against the enemy's one. This battle will be the easiest one you will face for the rest of the war...traitor." ) }; std::vector scenarioDatas; scenarioDatas.reserve( scenarioCount ); std::vector scenarioInfo; scenarioInfo.reserve( scenarioCount ); for ( int i = 0; i < scenarioCount; ++i ) { scenarioInfo.emplace_back( Campaign::ROLAND_CAMPAIGN, i ); } scenarioDatas.emplace_back( scenarioInfo[0], std::vector{ scenarioInfo[1] }, "CAMPG01.H2C", scenarioName[0], scenarioDescription[0], Campaign::VideoSequence{ { "GOOD01V.SMK", Video::VideoAction::IGNORE_VIDEO }, { "GOOD01.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, emptyPlayback ); scenarioDatas.emplace_back( scenarioInfo[1], std::vector{ scenarioInfo[2], scenarioInfo[3] }, "CAMPG02.H2C", scenarioName[1], scenarioDescription[1], Campaign::VideoSequence{ { "GOOD02W.SMK", Video::VideoAction::IGNORE_VIDEO }, { "GOOD02.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, Campaign::VideoSequence{ { "GOOD03QW.SMK", Video::VideoAction::IGNORE_VIDEO }, { "GOOD03.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); scenarioDatas.emplace_back( scenarioInfo[2], std::vector{ scenarioInfo[3] }, "CAMPG03.H2C", scenarioName[2], scenarioDescription[2], emptyPlayback, Campaign::VideoSequence{ { "GOOD04W.SMK", Video::VideoAction::IGNORE_VIDEO }, { "GOOD04.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, Campaign::ScenarioVictoryCondition::STANDARD, Campaign::ScenarioLossCondition::LOSE_ALL_SORCERESS_VILLAGES ); scenarioDatas.emplace_back( scenarioInfo[3], std::vector{ scenarioInfo[4], scenarioInfo[10] }, "CAMPG04.H2C", scenarioName[3], scenarioDescription[3], emptyPlayback, Campaign::VideoSequence{ { "GOOD05V.SMK", Video::VideoAction::IGNORE_VIDEO }, { "GOOD05.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); scenarioDatas.emplace_back( scenarioInfo[4], std::vector{ scenarioInfo[5] }, "CAMPG05.H2C", scenarioName[4], scenarioDescription[4], emptyPlayback, Campaign::VideoSequence{ { "GOOD06AV.SMK", Video::VideoAction::IGNORE_VIDEO }, { "GOOD06.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); scenarioDatas.emplace_back( scenarioInfo[5], std::vector{ scenarioInfo[6], scenarioInfo[7] }, "CAMPG06.H2C", scenarioName[5], scenarioDescription[5], emptyPlayback, Campaign::VideoSequence{ { "GOOD07QW.SMK", Video::VideoAction::IGNORE_VIDEO }, { "GOOD07.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); scenarioDatas.emplace_back( scenarioInfo[6], std::vector{ scenarioInfo[8] }, "CAMPG07.H2C", scenarioName[6], scenarioDescription[6], emptyPlayback, emptyPlayback ); scenarioDatas.emplace_back( scenarioInfo[7], std::vector{ scenarioInfo[8] }, "CAMPG08.H2C", scenarioName[7], scenarioDescription[7], emptyPlayback, emptyPlayback, Campaign::ScenarioVictoryCondition::OBTAIN_ULTIMATE_CROWN ); scenarioDatas.emplace_back( scenarioInfo[8], std::vector{ scenarioInfo[9] }, "CAMPG09.H2C", scenarioName[8], scenarioDescription[8], Campaign::VideoSequence{ { "GOOD09W.SMK", Video::VideoAction::IGNORE_VIDEO }, { "GOOD09.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, emptyPlayback ); scenarioDatas.emplace_back( scenarioInfo[9], std::vector{}, "CAMPG10.H2C", scenarioName[9], scenarioDescription[9], Campaign::VideoSequence{ { "GOOD10W.SMK", Video::VideoAction::IGNORE_VIDEO }, { "GOOD10.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, Campaign::VideoSequence{ { "LIBRARYW.SMK", Video::VideoAction::IGNORE_VIDEO }, { "LIBRARY.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); // At the end of the Betrayal scenario we should start an Archibald scenario. scenarioDatas.emplace_back( scenarioInfo[10], std::vector{ Campaign::ScenarioInfoId( Campaign::ARCHIBALD_CAMPAIGN, 5 ) }, "CAMPG05B.H2C", scenarioName[10], scenarioDescription[10], emptyPlayback, Campaign::VideoSequence{ { "EVIL06BW.SMK", Video::VideoAction::IGNORE_VIDEO }, { "EVIL06.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); Campaign::CampaignData campaignData; campaignData.setCampaignID( Campaign::ROLAND_CAMPAIGN ); campaignData.setCampaignScenarios( std::move( scenarioDatas ) ); return campaignData; } Campaign::CampaignData getArchibaldCampaignData() { const int scenarioCount = 12; const std::array scenarioName = { gettext_noop( "First Blood" ), gettext_noop( "Barbarian Wars" ), gettext_noop( "Necromancers" ), gettext_noop( "Slay the Dwarves" ), gettext_noop( "Turning Point" ), gettext_noop( "Rebellion" ), gettext_noop( "Dragon Master" ), gettext_noop( "Country Lords" ), gettext_noop( "The Crown" ), gettext_noop( "Greater Glory" ), gettext_noop( "Apocalypse" ), gettext_noop( "Betrayal" ) }; const std::array scenarioDescription = { gettext_noop( "King Archibald requires you to defeat the three enemies in this region. They are not allied with one another, so they will spend most of their energy fighting" " amongst themselves. You will win when you own all of the enemy castles and there are no more heroes left to fight." ), gettext_noop( "You must unify the barbarian tribes of the north by conquering them. As in the previous mission, the enemy is not allied against you, but they have more resources at their disposal. You will win when you own all of the enemy castles and there are no more heroes left to fight." ), gettext_noop( "Do-gooder wizards have taken the Necromancers' castle. You must retake it to achieve victory. Remember that while you start with a powerful army, you have no castle and must take one within 7 days, or lose this battle. (Hint: The nearest castle is to the southeast.)" ), gettext_noop( "The dwarves need conquering before they can interfere in King Archibald's plans. Roland's forces have more than one hero and many towns to start with, so be ready for attack from multiple directions. You must capture all of the enemy towns and castles to claim victory." ), gettext_noop( "Your enemies are allied against you and start close by, so be ready to come out fighting. You will need to own all four castles in this small valley to win." ), gettext_noop( "You must put down a peasant revolt led by Roland's forces. All are allied against you, but you have Lord Corlagon, an experienced hero, to help you. Capture all enemy castles to win." ), gettext_noop( "There are two enemies allied against you in this mission. Both are well armed and seek to evict you from their island. Avoid them and capture Dragon City to win." ), gettext_noop( "Your orders are to conquer the country lords that have sworn to serve Roland. All of the enemy castles are unified against you. Since you start without a castle, you must hurry to capture one before the end of the week. Capture all enemy castles for victory." ), gettext_noop( "Find the Crown before Roland's heroes find it. Archibald will need the Crown for the final battle against Roland." ), gettext_noop( "Gather as large an army as possible and capture the enemy castle within 8 weeks. You are opposed by only one enemy, but must travel a long way to get to the enemy castle. Any troops you have in your army at the end of this scenario will be with you in the final battle." ), gettext_noop( "This is the final battle. Both you and your enemy are armed to the teeth, and all are allied against you. Capture Roland to win the war, and be sure not to lose Archibald in the fight!" ), gettext_noop( "Switching sides leaves you with three castles against the enemy's one. This battle will be the easiest one you will face for the rest of the war...traitor." ) }; std::vector scenarioDatas; scenarioDatas.reserve( scenarioCount ); std::vector scenarioInfo; scenarioInfo.reserve( scenarioCount ); for ( int i = 0; i < scenarioCount; ++i ) { scenarioInfo.emplace_back( Campaign::ARCHIBALD_CAMPAIGN, i ); } scenarioDatas.emplace_back( scenarioInfo[0], std::vector{ scenarioInfo[1] }, "CAMPE01.H2C", scenarioName[0], scenarioDescription[0], Campaign::VideoSequence{ { "EVIL01V.SMK", Video::VideoAction::IGNORE_VIDEO }, { "EVIL01.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, emptyPlayback ); scenarioDatas.emplace_back( scenarioInfo[1], std::vector{ scenarioInfo[2], scenarioInfo[3] }, "CAMPE02.H2C", scenarioName[1], scenarioDescription[1], Campaign::VideoSequence{ { "EVIL02W.SMK", Video::VideoAction::IGNORE_VIDEO }, { "EVIL02.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, Campaign::VideoSequence{ { "EVIL03QW.SMK", Video::VideoAction::IGNORE_VIDEO }, { "EVIL03.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); scenarioDatas.emplace_back( scenarioInfo[2], std::vector{ scenarioInfo[4], scenarioInfo[11] }, "CAMPE03.H2C", scenarioName[2], scenarioDescription[2], emptyPlayback, Campaign::VideoSequence{ { "EVIL05AV.SMK", Video::VideoAction::IGNORE_VIDEO }, { "EVIL05.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END }, { "SBETRAYV.SMK", Video::VideoAction::IGNORE_VIDEO }, { "EVIL05.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); scenarioDatas.emplace_back( scenarioInfo[3], std::vector{ scenarioInfo[4], scenarioInfo[11] }, "CAMPE04.H2C", scenarioName[3], scenarioDescription[3], emptyPlayback, Campaign::VideoSequence{ { "EVIL05BV.SMK", Video::VideoAction::IGNORE_VIDEO }, { "EVIL05.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END }, { "SBETRAYV.SMK", Video::VideoAction::IGNORE_VIDEO }, { "EVIL05.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); scenarioDatas.emplace_back( scenarioInfo[4], std::vector{ scenarioInfo[5] }, "CAMPE05.H2C", scenarioName[4], scenarioDescription[4], emptyPlayback, Campaign::VideoSequence{ { "EVIL06AW.SMK", Video::VideoAction::IGNORE_VIDEO }, { "EVIL06.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); scenarioDatas.emplace_back( scenarioInfo[5], std::vector{ scenarioInfo[6], scenarioInfo[7] }, "CAMPE06.H2C", scenarioName[5], scenarioDescription[5], emptyPlayback, Campaign::VideoSequence{ { "EVIL07W.SMK", Video::VideoAction::IGNORE_VIDEO }, { "EVIL07.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); scenarioDatas.emplace_back( scenarioInfo[6], std::vector{ scenarioInfo[7] }, "CAMPE07.H2C", scenarioName[6], scenarioDescription[6], emptyPlayback, Campaign::VideoSequence{ { "EVIL08.SMK", Video::VideoAction::PLAY_TILL_VIDEO_END } }, Campaign::ScenarioVictoryCondition::CAPTURE_DRAGON_CITY ); scenarioDatas.emplace_back( scenarioInfo[7], std::vector{ scenarioInfo[8], scenarioInfo[9] }, "CAMPE08.H2C", scenarioName[7], scenarioDescription[7], emptyPlayback, Campaign::VideoSequence{ { "EVIL09W.SMK", Video::VideoAction::IGNORE_VIDEO }, { "EVIL09.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); scenarioDatas.emplace_back( scenarioInfo[8], std::vector{ scenarioInfo[10] }, "CAMPE09.H2C", scenarioName[8], scenarioDescription[8], emptyPlayback, emptyPlayback, Campaign::ScenarioVictoryCondition::OBTAIN_ULTIMATE_CROWN ); scenarioDatas.emplace_back( scenarioInfo[9], std::vector{ scenarioInfo[10] }, "CAMPE10.H2C", scenarioName[9], scenarioDescription[9], emptyPlayback, emptyPlayback ); scenarioDatas.emplace_back( scenarioInfo[10], std::vector{}, "CAMPE11.H2C", scenarioName[10], scenarioDescription[10], Campaign::VideoSequence{ { "EVIL11W.SMK", Video::VideoAction::IGNORE_VIDEO }, { "EVIL10.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, Campaign::VideoSequence{ { "PRISON.SMK", Video::VideoAction::PLAY_TILL_VIDEO_END } } ); // At the end of the Betrayal scenario we should start a Roland scenario. scenarioDatas.emplace_back( scenarioInfo[11], std::vector{ Campaign::ScenarioInfoId( Campaign::ROLAND_CAMPAIGN, 5 ) }, "CAMPE05B.H2C", scenarioName[11], scenarioDescription[11], emptyPlayback, Campaign::VideoSequence{ { "GOOD06BV.SMK", Video::VideoAction::IGNORE_VIDEO }, { "GOOD06.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); Campaign::CampaignData campaignData; campaignData.setCampaignID( Campaign::ARCHIBALD_CAMPAIGN ); campaignData.setCampaignScenarios( std::move( scenarioDatas ) ); return campaignData; } Campaign::CampaignData getPriceOfLoyaltyCampaignData() { const int scenarioCount = 8; const std::array scenarioName = { gettext_noop( "Uprising" ), gettext_noop( "Island of Chaos" ), gettext_noop( "Arrow's Flight" ), gettext_noop( "The Abyss" ), gettext_noop( "The Giant's Pass" ), gettext_noop( "Aurora Borealis" ), gettext_noop( "Betrayal's End" ), gettext_noop( "Corruption's Heart" ) }; const std::array scenarioDescription = { gettext_noop( "Subdue the unruly local lords in order to provide the Empire with facilities to operate in this region." ), gettext_noop( "Eliminate all opposition in this area. Then the first piece of the artifact will be yours." ), gettext_noop( "The sorceresses to the northeast are rebelling! For the good of the empire you must quash their feeble uprising on your way to the mountains." ), gettext_noop( "Having prepared for your arrival, Kraeger has arranged for a force of necromancers to thwart your quest. You must capture the castle of Scabsdale before the first day of the third week, or the Necromancers will be too strong for you." ), gettext_noop( "The barbarian despot in this area is, as yet, ignorant of your presence. Quickly, build up your forces before you are discovered and attacked! Secure the region by subduing all enemy forces." ), gettext_noop( "The Empire is weak in this region. You will be unable to completely subdue all forces in this area, so take what you can before reprisal strikes. Remember, your true goal is to claim the Helmet of Anduran." ), gettext_noop( "For the good of the Empire, eliminate Kraeger." ), gettext_noop( "At last, you have the opportunity and the facilities to rid the Empire of the necromancer's evil. Eradicate them completely, and you will be sung as a hero for all time." ) }; std::vector scenarioDatas; scenarioDatas.reserve( 8 ); std::vector scenarioInfo; scenarioInfo.reserve( scenarioCount ); for ( int i = 0; i < scenarioCount; ++i ) { scenarioInfo.emplace_back( Campaign::PRICE_OF_LOYALTY_CAMPAIGN, i ); } scenarioDatas.emplace_back( scenarioInfo[0], std::vector{ scenarioInfo[1] }, "CAMP1_01.HXC", scenarioName[0], scenarioDescription[0], Campaign::VideoSequence{ { "MIXPOL1.SMK", Video::VideoAction::IGNORE_VIDEO }, { "POL1.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, emptyPlayback ); scenarioDatas.emplace_back( scenarioInfo[1], std::vector{ scenarioInfo[2], scenarioInfo[3] }, "CAMP1_02.HXC", scenarioName[1], scenarioDescription[1], Campaign::VideoSequence{ { "MIXPOL2.SMK", Video::VideoAction::IGNORE_VIDEO }, { "POL2.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, Campaign::VideoSequence{ { "MIXPOL3.SMK", Video::VideoAction::IGNORE_VIDEO }, { "POL3.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); scenarioDatas.emplace_back( scenarioInfo[2], std::vector{ scenarioInfo[4] }, "CAMP1_03.HXC", scenarioName[2], scenarioDescription[2], emptyPlayback, emptyPlayback ); scenarioDatas.emplace_back( scenarioInfo[3], std::vector{ scenarioInfo[5] }, "CAMP1_04.HXC", scenarioName[3], scenarioDescription[3], emptyPlayback, emptyPlayback ); scenarioDatas.emplace_back( scenarioInfo[4], std::vector{ scenarioInfo[5] }, "CAMP1_05.HXC", scenarioName[4], scenarioDescription[4], Campaign::VideoSequence{ { "MIXPOL4.SMK", Video::VideoAction::IGNORE_VIDEO }, { "POL4.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, emptyPlayback ); scenarioDatas.emplace_back( scenarioInfo[5], std::vector{ scenarioInfo[6], scenarioInfo[7] }, "CAMP1_06.HXC", scenarioName[5], scenarioDescription[5], Campaign::VideoSequence{ { "MIXPOL5.SMK", Video::VideoAction::IGNORE_VIDEO }, { "POL5.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, Campaign::VideoSequence{ { "MIXPOL6.SMK", Video::VideoAction::IGNORE_VIDEO }, { "POL6.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); scenarioDatas.emplace_back( scenarioInfo[6], std::vector{ scenarioInfo[7] }, "CAMP1_07.HXC", scenarioName[6], scenarioDescription[6], emptyPlayback, Campaign::VideoSequence{ { "MIXPOL7.SMK", Video::VideoAction::IGNORE_VIDEO }, { "POL7.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); scenarioDatas.emplace_back( scenarioInfo[7], std::vector{}, "CAMP1_08.HXC", scenarioName[7], scenarioDescription[7], emptyPlayback, Campaign::VideoSequence{ { "MIXPOL8.SMK", Video::VideoAction::IGNORE_VIDEO }, { "POL8.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); Campaign::CampaignData campaignData; campaignData.setCampaignID( Campaign::PRICE_OF_LOYALTY_CAMPAIGN ); campaignData.setCampaignScenarios( std::move( scenarioDatas ) ); return campaignData; } Campaign::CampaignData getDescendantsCampaignData() { const int scenarioCount = 8; const std::array scenarioName = { gettext_noop( "Conquer and Unify" ), gettext_noop( "Border Towns" ), gettext_noop( "The Wayward Son" ), gettext_noop( "Crazy Uncle Ivan" ), gettext_noop( "The Southern War" ), gettext_noop( "Ivory Gates" ), gettext_noop( "The Elven Lands" ), gettext_noop( "The Epic Battle" ) }; const std::array scenarioDescription = { gettext_noop( "Conquer and unite all the enemy tribes. Don't lose the hero Jarkonas, the forefather of all descendants." ), gettext_noop( "Your rival, the Kingdom of Harondale, is attacking weak towns on your border! Recover from their first strike and crush them completely!" ), gettext_noop( "Find your wayward son Joseph who is rumored to be living in the desolate lands. Do it before the first day of the third month or it will be of no help to your family." ), gettext_noop( "Rescue your crazy uncle Ivan. Find him before the first day of the fourth month or it will be no help to your kingdom." ), gettext_noop( "Destroy the barbarians who are attacking the southern border of your kingdom! Recover your fallen towns, and then invade the jungle kingdom. Leave no enemy standing." ), gettext_noop( "Retake the castle of Ivory Gates, which has fallen due to treachery." ), gettext_noop( "Gain the favor of the elves. They will not allow trees to be chopped down, so they will send you wood every 2 weeks. You must complete your mission before the first day of the seventh month, or the kingdom will surely fall." ), gettext_noop( "This is the final battle against your rival kingdom of Harondale. Eliminate everyone, and don't lose the hero Jarkonas VI." ) }; std::vector scenarioDatas; scenarioDatas.reserve( 8 ); std::vector scenarioInfo; scenarioInfo.reserve( scenarioCount ); for ( int i = 0; i < scenarioCount; ++i ) { scenarioInfo.emplace_back( Campaign::DESCENDANTS_CAMPAIGN, i ); } scenarioDatas.emplace_back( scenarioInfo[0], std::vector{ scenarioInfo[1] }, "CAMP2_01.HXC", scenarioName[0], scenarioDescription[0], Campaign::VideoSequence{ { "MIXDES9.SMK", Video::VideoAction::IGNORE_VIDEO }, { "DES9.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, emptyPlayback ); scenarioDatas.emplace_back( scenarioInfo[1], std::vector{ scenarioInfo[2], scenarioInfo[3] }, "CAMP2_02.HXC", scenarioName[1], scenarioDescription[1], Campaign::VideoSequence{ { "MIXDES10.SMK", Video::VideoAction::IGNORE_VIDEO }, { "DES10.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, Campaign::VideoSequence{ { "MIXDES11.SMK", Video::VideoAction::IGNORE_VIDEO }, { "DES11.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); scenarioDatas.emplace_back( scenarioInfo[2], std::vector{ scenarioInfo[4] }, "CAMP2_03.HXC", scenarioName[2], scenarioDescription[2], emptyPlayback, emptyPlayback ); scenarioDatas.emplace_back( scenarioInfo[3], std::vector{ scenarioInfo[4] }, "CAMP2_04.HXC", scenarioName[3], scenarioDescription[3], emptyPlayback, emptyPlayback ); scenarioDatas.emplace_back( scenarioInfo[4], std::vector{ scenarioInfo[5], scenarioInfo[6] }, "CAMP2_05.HXC", scenarioName[4], scenarioDescription[4], Campaign::VideoSequence{ { "MIXDES12.SMK", Video::VideoAction::IGNORE_VIDEO }, { "DES12.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, Campaign::VideoSequence{ { "MIXDES13.SMK", Video::VideoAction::IGNORE_VIDEO }, { "DES13.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); scenarioDatas.emplace_back( scenarioInfo[5], std::vector{ scenarioInfo[7] }, "CAMP2_06.HXC", scenarioName[5], scenarioDescription[5], emptyPlayback, emptyPlayback ); scenarioDatas.emplace_back( scenarioInfo[6], std::vector{ scenarioInfo[7] }, "CAMP2_07.HXC", scenarioName[6], scenarioDescription[6], emptyPlayback, emptyPlayback ); scenarioDatas.emplace_back( scenarioInfo[7], std::vector{}, "CAMP2_08.HXC", scenarioName[7], scenarioDescription[7], Campaign::VideoSequence{ { "MIXDES14.SMK", Video::VideoAction::IGNORE_VIDEO }, { "DES14.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, Campaign::VideoSequence{ { "MIXDES15.SMK", Video::VideoAction::IGNORE_VIDEO }, { "DES15.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); Campaign::CampaignData campaignData; campaignData.setCampaignID( Campaign::DESCENDANTS_CAMPAIGN ); campaignData.setCampaignScenarios( std::move( scenarioDatas ) ); return campaignData; } Campaign::CampaignData getWizardsIsleCampaignData() { const int scenarioCount = 4; const std::array scenarioName = { gettext_noop( "The Shrouded Isles" ), gettext_noop( "The Eternal Scrolls" ), gettext_noop( "Power's End" ), gettext_noop( "Fount of Wizardry" ) }; const std::array scenarioDescription = { gettext_noop( "Your mission is to vanquish the warring mages in the magical Shrouded Isles. The completion of this task will give you a fighting chance against your rivals." ), gettext_noop( "The location of the great library has been discovered! You must make your way to it, and reclaim the city of Chronos in which it lies." ), gettext_noop( "Find the Orb of negation, which is said to be buried in this land. There are clues inscribed on stone obelisks which will help lead you to your prize. Find the Orb before the first day of the sixth month, or your rivals will surely have gotten to the fount before you." ), gettext_noop( "You must take control of the castle of Magic, where the fount of wizardry lies. Do this and your victory will be supreme." ) }; std::vector scenarioDatas; scenarioDatas.reserve( 4 ); std::vector scenarioInfo; scenarioInfo.reserve( scenarioCount ); for ( int i = 0; i < scenarioCount; ++i ) { scenarioInfo.emplace_back( Campaign::WIZARDS_ISLE_CAMPAIGN, i ); } scenarioDatas.emplace_back( scenarioInfo[0], std::vector{ scenarioInfo[1] }, "CAMP3_01.HXC", scenarioName[0], scenarioDescription[0], Campaign::VideoSequence{ { "MIXWIZ16.SMK", Video::VideoAction::IGNORE_VIDEO }, { "WIZ16.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, emptyPlayback ); scenarioDatas.emplace_back( scenarioInfo[1], std::vector{ scenarioInfo[2], scenarioInfo[3] }, "CAMP3_02.HXC", scenarioName[1], scenarioDescription[1], Campaign::VideoSequence{ { "MIXWIZ17.SMK", Video::VideoAction::IGNORE_VIDEO }, { "WIZ17.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, Campaign::VideoSequence{ { "MIXWIZ18.SMK", Video::VideoAction::IGNORE_VIDEO }, { "WIZ18.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); scenarioDatas.emplace_back( scenarioInfo[2], std::vector{ scenarioInfo[3] }, "CAMP3_03.HXC", scenarioName[2], scenarioDescription[2], emptyPlayback, Campaign::VideoSequence{ { "MIXWIZ19.SMK", Video::VideoAction::IGNORE_VIDEO }, { "WIZ19.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, Campaign::ScenarioVictoryCondition::OBTAIN_SPHERE_NEGATION ); scenarioDatas.emplace_back( scenarioInfo[3], std::vector{}, "CAMP3_04.HXC", scenarioName[3], scenarioDescription[3], emptyPlayback, Campaign::VideoSequence{ { "MIXWIZ20.SMK", Video::VideoAction::IGNORE_VIDEO }, { "WIZ20.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); Campaign::CampaignData campaignData; campaignData.setCampaignID( Campaign::WIZARDS_ISLE_CAMPAIGN ); campaignData.setCampaignScenarios( std::move( scenarioDatas ) ); return campaignData; } Campaign::CampaignData getVoyageHomeCampaignData() { const int scenarioCount = 4; const std::array scenarioName = { gettext_noop( "Stranded" ), gettext_noop( "Pirate Isles" ), gettext_noop( "King and Country" ), gettext_noop( "Blood is Thicker" ) }; const std::array scenarioDescription = { gettext_noop( "Capture the town on the island off the southeast shore in order to construct a boat and travel back towards the mainland. Do not lose the hero Gallavant." ), gettext_noop( "Find and defeat Martine, the pirate leader, who resides in Pirates Cove. Do not lose Gallavant or your quest will be over." ), gettext_noop( "Eliminate all the other forces who oppose the rule of Lord Alberon. Gallavant must not die." ), gettext_noop( "Overthrow the entrenched monarchy of Lord Alberon, and claim all the land in your name. Gallavant must not die." ) }; std::vector scenarioDatas; scenarioDatas.reserve( 4 ); std::vector scenarioInfo; scenarioInfo.reserve( scenarioCount ); for ( int i = 0; i < scenarioCount; ++i ) { scenarioInfo.emplace_back( Campaign::VOYAGE_HOME_CAMPAIGN, i ); } scenarioDatas.emplace_back( scenarioInfo[0], std::vector{ scenarioInfo[1] }, "CAMP4_01.HXC", scenarioName[0], scenarioDescription[0], Campaign::VideoSequence{ { "MIXVOY21.SMK", Video::VideoAction::IGNORE_VIDEO }, { "VOY21.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, emptyPlayback ); scenarioDatas.emplace_back( scenarioInfo[1], std::vector{ scenarioInfo[2], scenarioInfo[3] }, "CAMP4_02.HXC", scenarioName[1], scenarioDescription[1], Campaign::VideoSequence{ { "MIXVOY22.SMK", Video::VideoAction::IGNORE_VIDEO }, { "VOY22.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } }, Campaign::VideoSequence{ { "MIXVOY23.SMK", Video::VideoAction::IGNORE_VIDEO }, { "VOY23.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); scenarioDatas.emplace_back( scenarioInfo[2], std::vector{}, "CAMP4_03.HXC", scenarioName[2], scenarioDescription[2], emptyPlayback, Campaign::VideoSequence{ { "MIXVOY24.SMK", Video::VideoAction::IGNORE_VIDEO }, { "VOY24.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); scenarioDatas.emplace_back( scenarioInfo[3], std::vector{}, "CAMP4_04.HXC", scenarioName[3], scenarioDescription[3], emptyPlayback, Campaign::VideoSequence{ { "MIXVOY25.SMK", Video::VideoAction::IGNORE_VIDEO }, { "VOY25.SMK", Video::VideoAction::PLAY_TILL_AUDIO_END } } ); Campaign::CampaignData campaignData; campaignData.setCampaignID( Campaign::VOYAGE_HOME_CAMPAIGN ); campaignData.setCampaignScenarios( std::move( scenarioDatas ) ); return campaignData; } } namespace Campaign { // this is used to get awards that are not directly obtainable via scenario clear, such as assembling artifacts std::vector CampaignAwardData::getExtraCampaignAwardData( const int campaignID ) { assert( campaignID >= 0 ); switch ( campaignID ) { case PRICE_OF_LOYALTY_CAMPAIGN: { std::vector extraAwards; extraAwards.emplace_back( 10, Campaign::CampaignAwardData::TYPE_GET_ARTIFACT, Artifact::BATTLE_GARB ); return extraAwards; } case ROLAND_CAMPAIGN: case ARCHIBALD_CAMPAIGN: case DESCENDANTS_CAMPAIGN: case WIZARDS_ISLE_CAMPAIGN: case VOYAGE_HOME_CAMPAIGN: default: break; } return std::vector(); } const std::vector & CampaignData::getScenariosAfter( const ScenarioInfoId & scenarioInfo ) { const CampaignData & campaignData = getCampaignData( scenarioInfo.campaignId ); const std::vector & scenarios = campaignData.getAllScenarios(); assert( scenarioInfo.scenarioId >= 0 && static_cast( scenarioInfo.scenarioId ) < scenarios.size() ); return scenarios[scenarioInfo.scenarioId].getNextScenarios(); } std::vector CampaignData::getStartingScenarios() const { std::vector startingScenarios; for ( size_t i = 0; i < _scenarios.size(); ++i ) { const int scenarioID = _scenarios[i].getScenarioID(); if ( isStartingScenario( { _campaignID, scenarioID } ) ) startingScenarios.emplace_back( _campaignID, scenarioID ); } return startingScenarios; } bool CampaignData::isStartingScenario( const ScenarioInfoId & scenarioInfo ) const { // starting scenario = a scenario that is never included as a nextMap for ( size_t i = 0; i < _scenarios.size(); ++i ) { const std::vector & nextMaps = _scenarios[i].getNextScenarios(); if ( std::find( nextMaps.begin(), nextMaps.end(), scenarioInfo ) != nextMaps.end() ) return false; } return true; } bool CampaignData::isAllCampaignMapsPresent() const { for ( size_t i = 0; i < _scenarios.size(); ++i ) { if ( !_scenarios[i].isMapFilePresent() ) return false; } return true; } bool CampaignData::isLastScenario( const Campaign::ScenarioInfoId & scenarioInfoId ) const { assert( !_scenarios.empty() ); for ( const ScenarioData & scenario : _scenarios ) { if ( ( scenario.getScenarioInfoId() == scenarioInfoId ) && scenario.getNextScenarios().empty() ) { return true; } } return false; } void CampaignData::setCampaignScenarios( std::vector && scenarios ) { _scenarios = std::move( scenarios ); } const CampaignData & CampaignData::getCampaignData( const int campaignID ) { switch ( campaignID ) { case ROLAND_CAMPAIGN: { static const Campaign::CampaignData campaign( getRolandCampaignData() ); return campaign; } case ARCHIBALD_CAMPAIGN: { static const Campaign::CampaignData campaign( getArchibaldCampaignData() ); return campaign; } case PRICE_OF_LOYALTY_CAMPAIGN: { static const Campaign::CampaignData campaign( getPriceOfLoyaltyCampaignData() ); return campaign; } case DESCENDANTS_CAMPAIGN: { static const Campaign::CampaignData campaign( getDescendantsCampaignData() ); return campaign; } case VOYAGE_HOME_CAMPAIGN: { static const Campaign::CampaignData campaign( getVoyageHomeCampaignData() ); return campaign; } case WIZARDS_ISLE_CAMPAIGN: { static const Campaign::CampaignData campaign( getWizardsIsleCampaignData() ); return campaign; } default: { // Did you add a new campaign? Add the corresponding case above. assert( 0 ); static const Campaign::CampaignData noCampaign; return noCampaign; } } } void CampaignData::updateScenarioGameplayConditions( const Campaign::ScenarioInfoId & scenarioInfoId, Maps::FileInfo & mapInfo ) { bool allAIPlayersInAlliance = false; switch ( scenarioInfoId.campaignId ) { case ROLAND_CAMPAIGN: if ( scenarioInfoId.scenarioId == 9 ) { allAIPlayersInAlliance = true; } break; case ARCHIBALD_CAMPAIGN: if ( scenarioInfoId.scenarioId == 5 || scenarioInfoId.scenarioId == 10 ) { allAIPlayersInAlliance = true; } break; case PRICE_OF_LOYALTY_CAMPAIGN: case DESCENDANTS_CAMPAIGN: case VOYAGE_HOME_CAMPAIGN: case WIZARDS_ISLE_CAMPAIGN: break; default: // Did you add a new campaign? Add the corresponding case above. assert( 0 ); return; } if ( allAIPlayersInAlliance ) { const Colors humanColors( mapInfo.colorsAvailableForHumans ); // Make sure that this is only one human player on the map. if ( humanColors.size() != 1 ) { // Looks like somebody is modifying the original map. assert( 0 ); return; } const int aiColors = ( mapInfo.kingdomColors & ( ~mapInfo.colorsAvailableForHumans ) ); if ( aiColors == 0 ) { // This is definitely not the map to modify. assert( 0 ); return; } // If this assertion blows up then the whole logic behind colors is going crazy. assert( aiColors < 256 ); const uint8_t humanColor = static_cast( humanColors.front() ); for ( uint8_t & allianceColor : mapInfo.unions ) { if ( allianceColor != humanColor && ( aiColors & allianceColor ) ) { allianceColor = static_cast( aiColors ); } } } } // default amount to 1 for initialized campaign award data CampaignAwardData::CampaignAwardData( const int32_t id, const int32_t type, const int32_t subType ) : CampaignAwardData( id, type, subType, 1, 0 ) { // Do nothing. } CampaignAwardData::CampaignAwardData( const int32_t id, const int32_t type, const int32_t subType, const int32_t amount ) : CampaignAwardData( id, type, subType, amount, 0 ) { // Do nothing. } CampaignAwardData::CampaignAwardData( const int32_t id, const int32_t type, const int32_t subType, std::string customName ) : CampaignAwardData( id, type, subType, 1, 0, std::move( customName ) ) { // Do nothing. } CampaignAwardData::CampaignAwardData( const int32_t id, const int32_t type, const int32_t subType, const int32_t amount, const int32_t startScenarioID, std::string customName ) : _id( id ) , _type( type ) , _subType( subType ) , _amount( amount ) , _startScenarioID( startScenarioID ) , _customName( std::move( customName ) ) { // Do nothing. } std::string CampaignAwardData::getName() const { if ( !_customName.empty() ) return _( _customName ); switch ( _type ) { case CampaignAwardData::TYPE_CREATURE_CURSE: return Monster( _subType ).GetName() + std::string( _( " bane" ) ); case CampaignAwardData::TYPE_CREATURE_ALLIANCE: return Monster( _subType ).GetName() + std::string( _( " alliance" ) ); case CampaignAwardData::TYPE_GET_ARTIFACT: return Artifact( _subType ).GetName(); case CampaignAwardData::TYPE_CARRY_OVER_FORCES: return _( "Carry-over forces" ); case CampaignAwardData::TYPE_RESOURCE_BONUS: return Resource::String( _subType ) + std::string( _( " bonus" ) ); case CampaignAwardData::TYPE_GET_SPELL: return Spell( _subType ).GetName(); case CampaignAwardData::TYPE_HIREABLE_HERO: return Heroes( _subType, 0 ).GetName(); case CampaignAwardData::TYPE_DEFEAT_ENEMY_HERO: return Heroes( _subType, 0 ).GetName() + std::string( _( " defeated" ) ); default: // Did you add a new award? Add the logic above! assert( 0 ); break; } return {}; } std::string CampaignAwardData::getDescription() const { switch ( _type ) { case CampaignAwardData::TYPE_CREATURE_CURSE: { std::vector monsters; monsters.emplace_back( _subType ); std::string description( monsters.back().GetMultiName() ); while ( monsters.back() != monsters.back().GetUpgrade() ) { monsters.emplace_back( monsters.back().GetUpgrade() ); description += ", "; description += monsters.back().GetMultiName(); } description += _( " will always run away from your army." ); return description; } case CampaignAwardData::TYPE_CREATURE_ALLIANCE: { std::vector monsters; monsters.emplace_back( _subType ); std::string description( monsters.back().GetMultiName() ); while ( monsters.back() != monsters.back().GetUpgrade() ) { monsters.emplace_back( monsters.back().GetUpgrade() ); description += ", "; description += monsters.back().GetMultiName(); } description += _( " will be willing to join your army." ); return description; } case CampaignAwardData::TYPE_GET_ARTIFACT: { std::string description( _( "\"%{artifact}\" artifact will be carried over the scenario." ) ); StringReplace( description, "%{artifact}", Artifact( _subType ).GetName() ); return description; } case CampaignAwardData::TYPE_CARRY_OVER_FORCES: { return _( "The army will be carried over the scenario." ); } case CampaignAwardData::TYPE_RESOURCE_BONUS: { std::string description( _( "The kingdom will have +%{count} %{resource} each day." ) ); StringReplace( description, "%{count}", std::to_string( _amount ) ); StringReplace( description, "%{resource}", Resource::String( _subType ) ); return description; } case CampaignAwardData::TYPE_GET_SPELL: { std::string description( _( "\"%{spell}\" spell will be carried over the scenario." ) ); StringReplace( description, "%{spell}", Spell( _subType ).GetName() ); return description; } case CampaignAwardData::TYPE_HIREABLE_HERO: { std::string description( _( "%{hero} can be hired in the scenario." ) ); StringReplace( description, "%{hero}", Heroes( _subType, 0 ).GetName() ); return description; } case CampaignAwardData::TYPE_DEFEAT_ENEMY_HERO: { std::string description( _( "%{hero} has been defeated and will not appear in the subsequent scenarios." ) ); StringReplace( description, "%{hero}", Heroes( _subType, 0 ).GetName() ); return description; } default: // Did you add a new award? Add the logic above! assert( 0 ); break; } return {}; } std::vector CampaignAwardData::getCampaignAwardData( const ScenarioInfoId & scenarioInfo ) { assert( scenarioInfo.campaignId >= 0 && scenarioInfo.scenarioId >= 0 ); switch ( scenarioInfo.campaignId ) { case ROLAND_CAMPAIGN: return getRolandCampaignAwardData( scenarioInfo.scenarioId ); case ARCHIBALD_CAMPAIGN: return getArchibaldCampaignAwardData( scenarioInfo.scenarioId ); case PRICE_OF_LOYALTY_CAMPAIGN: return getPriceOfLoyaltyCampaignAwardData( scenarioInfo.scenarioId ); case DESCENDANTS_CAMPAIGN: return getDescendantsCampaignAwardData( scenarioInfo.scenarioId ); case WIZARDS_ISLE_CAMPAIGN: return getWizardsIsleCampaignAwardData( scenarioInfo.scenarioId ); case VOYAGE_HOME_CAMPAIGN: // No campaign award for voyage home! return {}; default: // Did you add a new campaign? Add the corresponding case above. assert( 0 ); break; } return {}; } const char * CampaignAwardData::getAllianceJoiningMessage( const int monsterId ) { switch ( monsterId ) { case Monster::DWARF: case Monster::BATTLE_DWARF: return _( "The dwarves recognize their allies and gladly join your forces." ); case Monster::OGRE: case Monster::OGRE_LORD: return _( "The ogres recognize you as the Dwarfbane and lumber over to join you." ); case Monster::GREEN_DRAGON: case Monster::RED_DRAGON: case Monster::BLACK_DRAGON: return _( "The dragons, snarling and growling, agree to join forces with you, their 'Ally'." ); case Monster::ELF: case Monster::GRAND_ELF: return _( "As you approach the group of elves, their leader calls them all to attention. He shouts to them, \"Who of you is brave enough to join this fearless ally of ours?\" The group explodes with cheers as they run to join your ranks." ); default: break; } assert( 0 ); // Did you forget to add a new alliance type? return nullptr; } const char * CampaignAwardData::getAllianceFleeingMessage( const int monsterId ) { switch ( monsterId ) { case Monster::DWARF: case Monster::BATTLE_DWARF: return _( "The dwarves hail you, \"Any friend of Roland is a friend of ours. You may pass.\"" ); case Monster::OGRE: case Monster::OGRE_LORD: return _( "The ogres give you a grunt of recognition, \"Archibald's allies may pass.\"" ); case Monster::GREEN_DRAGON: case Monster::RED_DRAGON: case Monster::BLACK_DRAGON: return _( "The dragons see you and call out. \"Our alliance with Archibald compels us to join you. Unfortunately you have no room. A pity!\" They quickly scatter." ); case Monster::ELF: case Monster::GRAND_ELF: return _( "The elves stand at attention as you approach. Their leader calls to you and says, \"Let us not impede your progress, ally! Move on, and may victory be yours.\"" ); default: break; } assert( 0 ); // Did you forget to add a new alliance type? return nullptr; } const char * CampaignAwardData::getBaneFleeingMessage( const int monsterId ) { switch ( monsterId ) { case Monster::DWARF: case Monster::BATTLE_DWARF: return _( "\"The Dwarfbane!!!!, run for your lives.\"" ); default: break; } assert( 0 ); // Did you forget to add a new bane type? return nullptr; } } fheroes2-1.0.12+dfsg/src/fheroes2/campaign/campaign_data.h000066400000000000000000000117511456075706000232520ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2CAMPAIGN_DATA_H #define H2CAMPAIGN_DATA_H #include #include #include #include "campaign_scenariodata.h" namespace Maps { struct FileInfo; } namespace Campaign { class CampaignData { public: int getCampaignID() const { return _campaignID; } const std::vector & getAllScenarios() const { return _scenarios; } static const std::vector & getScenariosAfter( const ScenarioInfoId & scenarioInfo ); std::vector getStartingScenarios() const; bool isAllCampaignMapsPresent() const; bool isLastScenario( const Campaign::ScenarioInfoId & scenarioInfoId ) const; void setCampaignID( const int campaignID ) { _campaignID = campaignID; } void setCampaignScenarios( std::vector && scenarios ); static const CampaignData & getCampaignData( const int campaignID ); // Some scenarios have different gameplay conditions like unions which are not specified within the map file itself. static void updateScenarioGameplayConditions( const Campaign::ScenarioInfoId & scenarioInfoId, Maps::FileInfo & mapInfo ); private: int _campaignID{ 0 }; std::string _campaignName; std::vector _scenarios; bool isStartingScenario( const ScenarioInfoId & scenarioInfo ) const; }; struct CampaignAwardData { public: enum AwardType : int32_t { TYPE_CREATURE_CURSE, // eg: dwarf bane TYPE_CREATURE_ALLIANCE, // eg: dwarf alliance TYPE_GET_ARTIFACT, // eg: ultimate crown TYPE_GET_SPELL, // eg: guardian spell in wizard's isle TYPE_CARRY_OVER_FORCES, // eg: the gauntlet TYPE_HIREABLE_HERO, // eg: sorceress guild TYPE_DEFEAT_ENEMY_HERO, // eg: corlagon defeated TYPE_RESOURCE_BONUS, // eg: wood bonus in price of loyalty }; // NOTE: Carry over forces shouldn't use these other than id, type and startScenarioID // IDs are here so that we just have to store an int instead of the entire award data in a campaign save data // also usable when we have to remove specific awards when completing a mission (PoL campaign) int32_t _id; int32_t _type; int32_t _subType; int32_t _amount; int32_t _startScenarioID; std::string _customName; CampaignAwardData( const int32_t id, const int32_t type, const int32_t subType ); CampaignAwardData( const int32_t id, const int32_t type, const int32_t subType, const int32_t amount ); CampaignAwardData( const int32_t id, const int32_t type, const int32_t subType, std::string customName ); CampaignAwardData( const int32_t id, const int32_t type, const int32_t subType, const int32_t amount, const int32_t startScenarioID, std::string customName = std::string() ); std::string getName() const; std::string getDescription() const; static std::vector getCampaignAwardData( const ScenarioInfoId & scenarioInfo ); static std::vector getExtraCampaignAwardData( const int campaignID ); static const char * getAllianceJoiningMessage( const int monsterId ); static const char * getAllianceFleeingMessage( const int monsterId ); static const char * getBaneFleeingMessage( const int monsterId ); }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/campaign/campaign_savedata.cpp000066400000000000000000000204721456075706000244640ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "campaign_savedata.h" #include #include #include #include #include "army.h" #include "campaign_data.h" #include "serialize.h" namespace Campaign { CampaignSaveData & Campaign::CampaignSaveData::Get() { static CampaignSaveData instance; return instance; } void CampaignSaveData::addCampaignAward( const int awardID ) { _obtainedCampaignAwards.emplace_back( awardID ); } void CampaignSaveData::removeCampaignAward( const int awardID ) { _obtainedCampaignAwards.erase( std::remove( _obtainedCampaignAwards.begin(), _obtainedCampaignAwards.end(), awardID ), _obtainedCampaignAwards.end() ); } void CampaignSaveData::setEnemyDefeatedAward( const int heroId ) { const ScenarioInfoId & currentScenarioInfo = getCurrentScenarioInfoId(); const std::vector obtainableAwards = CampaignAwardData::getCampaignAwardData( currentScenarioInfo ); for ( const auto & obtainableAward : obtainableAwards ) { const int32_t awardType = obtainableAward._type; if ( awardType == CampaignAwardData::AwardType::TYPE_DEFEAT_ENEMY_HERO ) { if ( obtainableAward._subType == heroId ) { addCampaignAward( obtainableAward._id ); } break; } } } void CampaignSaveData::setCurrentScenarioInfo( const ScenarioInfoId & scenarioInfoId, const int32_t bonusId /* = -1 */ ) { assert( scenarioInfoId.campaignId >= 0 && scenarioInfoId.scenarioId >= 0 ); _currentScenarioInfoId = scenarioInfoId; _currentScenarioBonusId = bonusId; } void CampaignSaveData::addCurrentMapToFinished() { // Check for a duplicate if ( std::find( _finishedMaps.begin(), _finishedMaps.end(), _currentScenarioInfoId ) != _finishedMaps.end() ) { return; } _finishedMaps.emplace_back( _currentScenarioInfoId ); _bonusesForFinishedMaps.emplace_back( _currentScenarioBonusId ); assert( _finishedMaps.size() == _bonusesForFinishedMaps.size() ); } void CampaignSaveData::addDaysPassed( const uint32_t days ) { _daysPassed += days; } void CampaignSaveData::reset() { _finishedMaps.clear(); _bonusesForFinishedMaps.clear(); _obtainedCampaignAwards.clear(); _carryOverTroops.clear(); _currentScenarioInfoId = { -1, -1 }; _currentScenarioBonusId = -1; _daysPassed = 0; _difficulty = CampaignDifficulty::Normal; } void CampaignSaveData::setCarryOverTroops( const Troops & troops ) { _carryOverTroops.clear(); for ( size_t i = 0; i < troops.Size(); ++i ) { _carryOverTroops.emplace_back( *troops.GetTroop( i ) ); } } const ScenarioInfoId & CampaignSaveData::getLastCompletedScenarioInfoID() const { assert( !_finishedMaps.empty() ); return _finishedMaps.back(); } uint32_t CampaignSaveData::getCampaignDifficultyPercent() const { switch ( _difficulty ) { case CampaignDifficulty::Easy: return 125; case CampaignDifficulty::Normal: // Original campaign difficulty. return 100; case CampaignDifficulty::Hard: return 75; default: // Did you add a new campaign difficulty? Add the logic above! assert( 0 ); return 100; } } std::vector CampaignSaveData::getObtainedCampaignAwards() const { std::vector obtainedAwards; for ( size_t i = 0; i < _finishedMaps.size(); ++i ) { const std::vector awards = Campaign::CampaignAwardData::getCampaignAwardData( _finishedMaps[i] ); for ( size_t j = 0; j < awards.size(); ++j ) { if ( std::find( _obtainedCampaignAwards.begin(), _obtainedCampaignAwards.end(), awards[j]._id ) != _obtainedCampaignAwards.end() ) obtainedAwards.emplace_back( awards[j] ); } } const std::vector extraAwards = Campaign::CampaignAwardData::getExtraCampaignAwardData( _currentScenarioInfoId.campaignId ); for ( const Campaign::CampaignAwardData & award : extraAwards ) { if ( std::find( _obtainedCampaignAwards.begin(), _obtainedCampaignAwards.end(), award._id ) != _obtainedCampaignAwards.end() ) obtainedAwards.emplace_back( award ); } return obtainedAwards; } StreamBase & operator<<( StreamBase & msg, const CampaignSaveData & data ) { return msg << data._currentScenarioInfoId.campaignId << data._currentScenarioInfoId.scenarioId << data._currentScenarioBonusId << data._finishedMaps << data._bonusesForFinishedMaps << data._daysPassed << data._obtainedCampaignAwards << data._carryOverTroops << data._difficulty; } StreamBase & operator>>( StreamBase & msg, CampaignSaveData & data ) { msg >> data._currentScenarioInfoId.campaignId >> data._currentScenarioInfoId.scenarioId >> data._currentScenarioBonusId >> data._finishedMaps >> data._bonusesForFinishedMaps; // Make sure that the number of elements in the vector of map bonuses matches the number of elements in the vector of finished maps data._bonusesForFinishedMaps.resize( data._finishedMaps.size(), -1 ); return msg >> data._daysPassed >> data._obtainedCampaignAwards >> data._carryOverTroops >> data._difficulty; } ScenarioVictoryCondition getCurrentScenarioVictoryCondition() { const CampaignSaveData & campaignData = CampaignSaveData::Get(); const std::vector & scenarios = CampaignData::getCampaignData( campaignData.getCampaignID() ).getAllScenarios(); const int scenarioId = campaignData.getCurrentScenarioID(); assert( scenarioId >= 0 && static_cast( scenarioId ) < scenarios.size() ); if ( scenarioId >= 0 && static_cast( scenarioId ) < scenarios.size() ) { return scenarios[scenarioId].getVictoryCondition(); } return ScenarioVictoryCondition::STANDARD; } ScenarioLossCondition getCurrentScenarioLossCondition() { const CampaignSaveData & campaignData = CampaignSaveData::Get(); const std::vector & scenarios = CampaignData::getCampaignData( campaignData.getCampaignID() ).getAllScenarios(); const int scenarioId = campaignData.getCurrentScenarioID(); assert( scenarioId >= 0 && static_cast( scenarioId ) < scenarios.size() ); if ( scenarioId >= 0 && static_cast( scenarioId ) < scenarios.size() ) { return scenarios[scenarioId].getLossCondition(); } return ScenarioLossCondition::STANDARD; } } fheroes2-1.0.12+dfsg/src/fheroes2/campaign/campaign_savedata.h000066400000000000000000000113511456075706000241250ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2CAMPAIGN_SAVEDATA_H #define H2CAMPAIGN_SAVEDATA_H #include #include #include "army_troop.h" #include "campaign_scenariodata.h" class StreamBase; class Troops; namespace Campaign { struct CampaignAwardData; enum CampaignDifficulty : int32_t { Easy = -1, Normal = 0, Hard = 1 }; class CampaignSaveData { public: const std::vector & getFinishedMaps() const { return _finishedMaps; } int getCampaignID() const { return _currentScenarioInfoId.campaignId; } int getCurrentScenarioID() const { return _currentScenarioInfoId.scenarioId; } const ScenarioInfoId & getCurrentScenarioInfoId() const { return _currentScenarioInfoId; } int32_t getCurrentScenarioBonusId() const { return _currentScenarioBonusId; } // Make sure that this is not the first scenario in the campaign. Please call isStarting to verify this. const ScenarioInfoId & getLastCompletedScenarioInfoID() const; bool isStarting() const { return _finishedMaps.empty(); } uint32_t getDaysPassed() const { return _daysPassed; } int32_t getDifficulty() const { return _difficulty; } void setDifficulty( const int32_t difficulty ) { _difficulty = difficulty; } // Get the campaign difficulty in percents for rating calculations. uint32_t getCampaignDifficultyPercent() const; const std::vector & getCarryOverTroops() const { return _carryOverTroops; } std::vector getObtainedCampaignAwards() const; void setCurrentScenarioInfo( const ScenarioInfoId & scenarioInfoId, const int32_t bonusId = -1 ); void addCurrentMapToFinished(); void addCampaignAward( const int awardID ); void setCarryOverTroops( const Troops & troops ); void reset(); void addDaysPassed( const uint32_t days ); void removeCampaignAward( const int awardID ); void setEnemyDefeatedAward( const int heroId ); void removeAllAwards() { _obtainedCampaignAwards.clear(); } static CampaignSaveData & Get(); private: friend StreamBase & operator<<( StreamBase & msg, const CampaignSaveData & data ); friend StreamBase & operator>>( StreamBase & msg, CampaignSaveData & data ); CampaignSaveData() = default; std::vector _finishedMaps; std::vector _bonusesForFinishedMaps; std::vector _obtainedCampaignAwards; std::vector _carryOverTroops; ScenarioInfoId _currentScenarioInfoId; int32_t _currentScenarioBonusId{ -1 }; uint32_t _daysPassed{ 0 }; int32_t _difficulty{ CampaignDifficulty::Normal }; }; // Call this function only when playing campaign scenario. ScenarioVictoryCondition getCurrentScenarioVictoryCondition(); // Call this function only when playing campaign scenario. ScenarioLossCondition getCurrentScenarioLossCondition(); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/campaign/campaign_scenariodata.cpp000066400000000000000000001150011456075706000253220ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "campaign_scenariodata.h" #include #include #include #include #include #include #include #include "artifact.h" #include "dir.h" #include "maps_fileinfo.h" #include "monster.h" #include "race.h" #include "resource.h" #include "serialize.h" #include "settings.h" #include "skill.h" #include "spell.h" #include "system.h" #include "tools.h" #include "translations.h" namespace { std::vector getRolandCampaignBonusData( const int scenarioID ) { std::vector bonus; switch ( scenarioID ) { case 0: bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::GOLD, 2000 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::THUNDER_MACE, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::ARMORED_GAUNTLETS, 1 ); break; case 1: case 2: case 3: case 4: bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::WZRD, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::SORC, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::KNGT, 1 ); break; case 5: bonus.emplace_back( Campaign::ScenarioBonusData::SPELL, Spell::MIRRORIMAGE, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::SPELL, Spell::SUMMONEELEMENT, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::SPELL, Spell::RESURRECT, 1 ); break; case 6: bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::BLACK_PEARL, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::DRAGON_SWORD, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::DIVINE_BREASTPLATE, 1 ); break; case 7: bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::WZRD, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::SORC, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::KNGT, 1 ); break; case 8: bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::CRYSTAL, 20 ); bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::GEMS, 20 ); bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::MERCURY, 20 ); break; case 9: bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::TAX_LIEN, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::HIDEOUS_MASK, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::FIZBIN_MISFORTUNE, 1 ); break; case 10: bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::NECR, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::WRLK, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::BARB, 1 ); break; default: assert( 0 ); break; } return bonus; } std::vector getArchibaldCampaignBonusData( const int scenarioID ) { std::vector bonus; switch ( scenarioID ) { case 0: bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::GOLD, 2000 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::MAGE_RING, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::MINOR_SCROLL, 1 ); break; case 1: bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::NECR, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::WRLK, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::BARB, 1 ); break; case 2: bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::GOLD, 2000 ); bonus.emplace_back( Campaign::ScenarioBonusData::SPELL, Spell::MASSCURSE, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::DEFENDER_HELM, 1 ); break; case 3: case 4: case 5: bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::NECR, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::WRLK, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::BARB, 1 ); break; case 6: bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE_AND_ARMY, Race::NECR, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE_AND_ARMY, Race::WRLK, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE_AND_ARMY, Race::BARB, 1 ); break; case 7: bonus.emplace_back( Campaign::ScenarioBonusData::SKILL_SECONDARY, Skill::Secondary::LOGISTICS, Skill::Level::BASIC ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::POWER_AXE, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::WHITE_PEARL, 1 ); break; case 8: bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::NECR, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::WRLK, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::BARB, 1 ); break; case 9: bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::BLACK_PEARL, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::DRAGON_SWORD, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::DIVINE_BREASTPLATE, 1 ); break; case 10: bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::TAX_LIEN, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::HIDEOUS_MASK, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::FIZBIN_MISFORTUNE, 1 ); break; case 11: bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::WZRD, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::SORC, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::STARTING_RACE, Race::KNGT, 1 ); break; default: assert( 0 ); break; } return bonus; } std::vector getPriceOfLoyaltyCampaignBonusData( const int scenarioID ) { std::vector bonus; switch ( scenarioID ) { case 0: bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::MEDAL_VALOR, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::STEALTH_SHIELD, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::MINOR_SCROLL, 1 ); break; case 1: bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::WHITE_PEARL, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::BALLISTA, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::GOLD, 2000 ); break; case 2: bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::MAGE_RING, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::MAJOR_SCROLL, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::SKILL_SECONDARY, Skill::Secondary::LOGISTICS, Skill::Level::ADVANCED ); break; case 3: bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::MEDAL_HONOR, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::CASTER_BRACELET, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::DEFENDER_HELM, 1 ); break; case 4: bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::GOLD, 2000 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::POWER_AXE, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::SKILL_PRIMARY, Skill::Primary::DEFENSE, 2 ); break; case 5: bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::GOLD, 2000 ); bonus.emplace_back( Campaign::ScenarioBonusData::TROOP, Monster::CRUSADER, 3 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::TRAVELER_BOOTS_MOBILITY, 1 ); break; case 6: bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::SULFUR, 10 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::POWER_AXE, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::SPELL_SCROLL, 1, Spell::ANIMATEDEAD ); break; case 7: bonus.emplace_back( Campaign::ScenarioBonusData::SPELL, Spell::VIEWHEROES, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::TROOP, Monster::MAGE, 5 ); bonus.emplace_back( Campaign::ScenarioBonusData::SKILL_SECONDARY, Skill::Secondary::ESTATES, Skill::Level::ADVANCED ); break; default: assert( 0 ); break; } return bonus; } std::vector getDescendantsCampaignBonusData( const int scenarioID ) { std::vector bonus; switch ( scenarioID ) { case 0: bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::GOLD, 1000 ); bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::WOOD, 50 ); bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::CRYSTAL, 10 ); break; case 1: bonus.emplace_back( Campaign::ScenarioBonusData::SKILL_PRIMARY, Skill::Primary::ATTACK, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::SKILL_PRIMARY, Skill::Primary::DEFENSE, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::GOLD, 1000 ); break; case 2: bonus.emplace_back( Campaign::ScenarioBonusData::SKILL_SECONDARY, Skill::Secondary::LOGISTICS, Skill::Level::BASIC ); bonus.emplace_back( Campaign::ScenarioBonusData::SPELL, Spell::STEELSKIN, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::MAGE_RING, 1 ); break; case 3: bonus.emplace_back( Campaign::ScenarioBonusData::SKILL_SECONDARY, Skill::Secondary::LOGISTICS, Skill::Level::BASIC ); bonus.emplace_back( Campaign::ScenarioBonusData::SKILL_SECONDARY, Skill::Secondary::SCOUTING, Skill::Level::BASIC ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::POWER_AXE, 1 ); break; case 4: bonus.emplace_back( Campaign::ScenarioBonusData::SKILL_SECONDARY, Skill::Secondary::LUCK, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::SKILL_SECONDARY, Skill::Secondary::LEADERSHIP, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::SPIKED_SHIELD, 1 ); break; case 5: // These are negative values as they should be. Do NOT change them! bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::GOLD, -1000 ); bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::WOOD, -10 ); bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::MERCURY, -2 ); break; case 6: bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::GOLD, 1000 ); bonus.emplace_back( Campaign::ScenarioBonusData::SKILL_SECONDARY, Skill::Secondary::SCOUTING, Skill::Level::BASIC ); bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::WOOD, 20 ); break; case 7: bonus.emplace_back( Campaign::ScenarioBonusData::SKILL_SECONDARY, Skill::Secondary::LEADERSHIP, Skill::Level::BASIC ); bonus.emplace_back( Campaign::ScenarioBonusData::SKILL_SECONDARY, Skill::Secondary::ARCHERY, Skill::Level::BASIC ); bonus.emplace_back( Campaign::ScenarioBonusData::SKILL_PRIMARY, Skill::Primary::POWER, 1 ); break; default: assert( 0 ); break; } return bonus; } std::vector getWizardsIsleCampaignBonusData( const int scenarioID ) { std::vector bonus; switch ( scenarioID ) { case 0: bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::GOLD, 2000 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::POWER_AXE, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::STEALTH_SHIELD, 1 ); break; case 1: bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::MAGE_RING, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::FOREMOST_SCROLL, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::SPELL, Spell::FIREBLAST, 1 ); break; case 2: bonus.emplace_back( Campaign::ScenarioBonusData::SPELL, Spell::MASSHASTE, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::SPELL, Spell::SUMMONEELEMENT, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::SPELL, Spell::CHAINLIGHTNING, 1 ); break; case 3: bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::WOOD, 5 ); bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::ORE, 5 ); bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::GOLD, 1000 ); break; default: assert( 0 ); break; } return bonus; } std::vector getVoyageHomeCampaignBonusData( const int scenarioID ) { std::vector bonus; switch ( scenarioID ) { case 0: bonus.emplace_back( Campaign::ScenarioBonusData::ARTIFACT, Artifact::NOMAD_BOOTS_MOBILITY, 1 ); bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::GOLD, 500 ); bonus.emplace_back( Campaign::ScenarioBonusData::SKILL_SECONDARY, Skill::Secondary::SCOUTING, Skill::Level::BASIC ); break; case 1: bonus.emplace_back( Campaign::ScenarioBonusData::SKILL_SECONDARY, Skill::Secondary::PATHFINDING, Skill::Level::BASIC ); bonus.emplace_back( Campaign::ScenarioBonusData::SKILL_SECONDARY, Skill::Secondary::NAVIGATION, Skill::Level::BASIC ); bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::SULFUR, 15 ); break; case 2: bonus.emplace_back( Campaign::ScenarioBonusData::SKILL_SECONDARY, Skill::Secondary::ARCHERY, Skill::Level::BASIC ); bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::GOLD, 3000 ); bonus.emplace_back( Campaign::ScenarioBonusData::SKILL_PRIMARY, Skill::Primary::ATTACK, 1 ); break; case 3: bonus.emplace_back( Campaign::ScenarioBonusData::SKILL_SECONDARY, Skill::Secondary::NECROMANCY, Skill::Level::BASIC ); bonus.emplace_back( Campaign::ScenarioBonusData::SKILL_SECONDARY, Skill::Secondary::LOGISTICS, Skill::Level::BASIC ); bonus.emplace_back( Campaign::ScenarioBonusData::RESOURCES, Resource::GOLD, 2000 ); break; default: assert( 0 ); break; } return bonus; } const char * getSpellCampaignName( const int32_t spellId ) { switch ( spellId ) { case Spell::ANIMATEDEAD: return _( "campaignBonus|Animate Dead" ); case Spell::CHAINLIGHTNING: return _( "campaignBonus|Chain Lightning" ); case Spell::FIREBLAST: return _( "campaignBonus|Fireblast" ); case Spell::MASSCURSE: return _( "campaignBonus|Mass Curse" ); case Spell::MASSHASTE: return _( "campaignBonus|Mass Haste" ); case Spell::MIRRORIMAGE: return _( "campaignBonus|Mirror Image" ); case Spell::RESURRECT: return _( "campaignBonus|Resurrect" ); case Spell::STEELSKIN: return _( "campaignBonus|Steelskin" ); case Spell::SUMMONEELEMENT: return _( "campaignBonus|Summon Earth" ); case Spell::VIEWHEROES: return _( "campaignBonus|View Heroes" ); default: return Spell( spellId ).GetName(); } } const char * getArtifactCampaignName( const int32_t artifactId, const int32_t spellId ) { switch ( artifactId ) { case Artifact::BALLISTA: return _( "campaignBonus|Ballista" ); case Artifact::BLACK_PEARL: return _( "campaignBonus|Black Pearl" ); case Artifact::CASTER_BRACELET: return _( "campaignBonus|Caster's Bracelet" ); case Artifact::DEFENDER_HELM: return _( "campaignBonus|Defender Helm" ); case Artifact::DIVINE_BREASTPLATE: return _( "campaignBonus|Breastplate" ); case Artifact::DRAGON_SWORD: return _( "campaignBonus|Dragon Sword" ); case Artifact::FIZBIN_MISFORTUNE: return _( "campaignBonus|Fizbin Medal" ); case Artifact::FOREMOST_SCROLL: return _( "campaignBonus|Foremost Scroll" ); case Artifact::ARMORED_GAUNTLETS: return _( "campaignBonus|Gauntlets" ); case Artifact::HIDEOUS_MASK: return _( "campaignBonus|Hideous Mask" ); case Artifact::MAGE_RING: return _( "campaignBonus|Mage's Ring" ); case Artifact::MAJOR_SCROLL: return _( "campaignBonus|Major Scroll" ); case Artifact::MEDAL_HONOR: return _( "campaignBonus|Medal of Honor" ); case Artifact::MEDAL_VALOR: return _( "campaignBonus|Medal of Valor" ); case Artifact::MINOR_SCROLL: return _( "campaignBonus|Minor Scroll" ); case Artifact::NOMAD_BOOTS_MOBILITY: return _( "campaignBonus|Nomad Boots" ); case Artifact::POWER_AXE: return _( "campaignBonus|Power Axe" ); case Artifact::SPIKED_SHIELD: return _( "campaignBonus|Spiked Shield" ); case Artifact::STEALTH_SHIELD: return _( "campaignBonus|Stealth Shield" ); case Artifact::TAX_LIEN: return _( "campaignBonus|Tax Lien" ); case Artifact::THUNDER_MACE: return _( "campaignBonus|Thunder Mace" ); case Artifact::TRAVELER_BOOTS_MOBILITY: return _( "campaignBonus|Traveler's Boots" ); case Artifact::WHITE_PEARL: return _( "campaignBonus|White Pearl" ); case Artifact::SPELL_SCROLL: return getSpellCampaignName( spellId ); default: return Artifact( artifactId ).GetName(); } } std::string getSecondarySkillCampaignName( const uint32_t secondarySkillId, const uint32_t secondarySkillLevel ) { switch ( secondarySkillId ) { case Skill::Secondary::ARCHERY: switch ( secondarySkillLevel ) { case Skill::Level::BASIC: return _( "campaignBonus|Basic Archery" ); case Skill::Level::ADVANCED: return _( "campaignBonus|Advanced Archery" ); case Skill::Level::EXPERT: return _( "campaignBonus|Expert Archery" ); default: // There cannot be other valid values of the skill level assert( 0 ); return {}; } case Skill::Secondary::BALLISTICS: switch ( secondarySkillLevel ) { case Skill::Level::BASIC: return _( "campaignBonus|Basic Ballistics" ); case Skill::Level::ADVANCED: return _( "campaignBonus|Advanced Ballistics" ); case Skill::Level::EXPERT: return _( "campaignBonus|Expert Ballistics" ); default: // There cannot be other valid values of the skill level assert( 0 ); return {}; } case Skill::Secondary::DIPLOMACY: switch ( secondarySkillLevel ) { case Skill::Level::BASIC: return _( "campaignBonus|Basic Diplomacy" ); case Skill::Level::ADVANCED: return _( "campaignBonus|Advanced Diplomacy" ); case Skill::Level::EXPERT: return _( "campaignBonus|Expert Diplomacy" ); default: // There cannot be other valid values of the skill level assert( 0 ); return {}; } case Skill::Secondary::EAGLE_EYE: switch ( secondarySkillLevel ) { case Skill::Level::BASIC: return _( "campaignBonus|Basic Eagle Eye" ); case Skill::Level::ADVANCED: return _( "campaignBonus|Advanced Eagle Eye" ); case Skill::Level::EXPERT: return _( "campaignBonus|Expert Eagle Eye" ); default: // There cannot be other valid values of the skill level assert( 0 ); return {}; } case Skill::Secondary::ESTATES: switch ( secondarySkillLevel ) { case Skill::Level::BASIC: return _( "campaignBonus|Basic Estates" ); case Skill::Level::ADVANCED: return _( "campaignBonus|Advanced Estates" ); case Skill::Level::EXPERT: return _( "campaignBonus|Expert Estates" ); default: // There cannot be other valid values of the skill level assert( 0 ); return {}; } case Skill::Secondary::LEADERSHIP: switch ( secondarySkillLevel ) { case Skill::Level::BASIC: return _( "campaignBonus|Basic Leadership" ); case Skill::Level::ADVANCED: return _( "campaignBonus|Advanced Leadership" ); case Skill::Level::EXPERT: return _( "campaignBonus|Expert Leadership" ); default: // There cannot be other valid values of the skill level assert( 0 ); return {}; } case Skill::Secondary::LOGISTICS: switch ( secondarySkillLevel ) { case Skill::Level::BASIC: return _( "campaignBonus|Basic Logistics" ); case Skill::Level::ADVANCED: return _( "campaignBonus|Advanced Logistics" ); case Skill::Level::EXPERT: return _( "campaignBonus|Expert Logistics" ); default: // There cannot be other valid values of the skill level assert( 0 ); return {}; } case Skill::Secondary::LUCK: switch ( secondarySkillLevel ) { case Skill::Level::BASIC: return _( "campaignBonus|Basic Luck" ); case Skill::Level::ADVANCED: return _( "campaignBonus|Advanced Luck" ); case Skill::Level::EXPERT: return _( "campaignBonus|Expert Luck" ); default: // There cannot be other valid values of the skill level assert( 0 ); return {}; } case Skill::Secondary::MYSTICISM: switch ( secondarySkillLevel ) { case Skill::Level::BASIC: return _( "campaignBonus|Basic Mysticism" ); case Skill::Level::ADVANCED: return _( "campaignBonus|Advanced Mysticism" ); case Skill::Level::EXPERT: return _( "campaignBonus|Expert Mysticism" ); default: // There cannot be other valid values of the skill level assert( 0 ); return {}; } case Skill::Secondary::NAVIGATION: switch ( secondarySkillLevel ) { case Skill::Level::BASIC: return _( "campaignBonus|Basic Navigation" ); case Skill::Level::ADVANCED: return _( "campaignBonus|Advanced Navigation" ); case Skill::Level::EXPERT: return _( "campaignBonus|Expert Navigation" ); default: // There cannot be other valid values of the skill level assert( 0 ); return {}; } case Skill::Secondary::NECROMANCY: switch ( secondarySkillLevel ) { case Skill::Level::BASIC: return _( "campaignBonus|Basic Necromancy" ); case Skill::Level::ADVANCED: return _( "campaignBonus|Advanced Necromancy" ); case Skill::Level::EXPERT: return _( "campaignBonus|Expert Necromancy" ); default: // There cannot be other valid values of the skill level assert( 0 ); return {}; } case Skill::Secondary::PATHFINDING: switch ( secondarySkillLevel ) { case Skill::Level::BASIC: return _( "campaignBonus|Basic Pathfinding" ); case Skill::Level::ADVANCED: return _( "campaignBonus|Advanced Pathfinding" ); case Skill::Level::EXPERT: return _( "campaignBonus|Expert Pathfinding" ); default: // There cannot be other valid values of the skill level assert( 0 ); return {}; } case Skill::Secondary::SCOUTING: switch ( secondarySkillLevel ) { case Skill::Level::BASIC: return _( "campaignBonus|Basic Scouting" ); case Skill::Level::ADVANCED: return _( "campaignBonus|Advanced Scouting" ); case Skill::Level::EXPERT: return _( "campaignBonus|Expert Scouting" ); default: // There cannot be other valid values of the skill level assert( 0 ); return {}; } case Skill::Secondary::WISDOM: switch ( secondarySkillLevel ) { case Skill::Level::BASIC: return _( "campaignBonus|Basic Wisdom" ); case Skill::Level::ADVANCED: return _( "campaignBonus|Advanced Wisdom" ); case Skill::Level::EXPERT: return _( "campaignBonus|Expert Wisdom" ); default: // There cannot be other valid values of the skill level assert( 0 ); return {}; } default: // There cannot be other valid values of secondary skills assert( 0 ); return {}; } } bool tryGetMatchingFile( const std::string & fileName, std::string & matchingFilePath ) { static const auto fileNameToPath = []() { std::map result; const ListFiles files = Settings::FindFiles( "maps", "", false ); for ( const std::string & file : files ) { result.try_emplace( StringLower( System::GetBasename( file ) ), file ); } return result; }(); const auto result = fileNameToPath.find( fileName ); if ( result != fileNameToPath.end() ) { matchingFilePath = result->second; return true; } return false; } } namespace Campaign { StreamBase & operator<<( StreamBase & msg, const ScenarioInfoId & data ) { return msg << data.campaignId << data.scenarioId; } StreamBase & operator>>( StreamBase & msg, ScenarioInfoId & data ) { return msg >> data.campaignId >> data.scenarioId; } ScenarioBonusData::ScenarioBonusData() : _type( 0 ) , _subType( 0 ) , _amount( 0 ) , _artifactSpellId( Spell::NONE ) {} ScenarioBonusData::ScenarioBonusData( const int32_t type, const int32_t subType, const int32_t amount ) : _type( type ) , _subType( subType ) , _amount( amount ) , _artifactSpellId( Spell::NONE ) {} ScenarioBonusData::ScenarioBonusData( const int32_t type, const int32_t subType, const int32_t amount, const int32_t spellId ) : _type( type ) , _subType( subType ) , _amount( amount ) , _artifactSpellId( spellId ) {} std::string ScenarioBonusData::getName() const { std::string objectName; switch ( _type ) { case ScenarioBonusData::ARTIFACT: objectName = getArtifactCampaignName( _subType, _artifactSpellId ); break; case ScenarioBonusData::RESOURCES: objectName = std::to_string( _amount ) + " " + Resource::String( _subType ); break; case ScenarioBonusData::TROOP: objectName = Monster( _subType ).GetPluralName( _amount ); break; case ScenarioBonusData::SPELL: objectName = getSpellCampaignName( _subType ); break; case ScenarioBonusData::STARTING_RACE: case ScenarioBonusData::STARTING_RACE_AND_ARMY: objectName = Race::String( _subType ); break; case ScenarioBonusData::SKILL_PRIMARY: objectName = Skill::Primary::String( _subType ); break; case ScenarioBonusData::SKILL_SECONDARY: objectName = getSecondarySkillCampaignName( _subType, _amount ); break; default: assert( 0 ); // some new bonus? break; } const std::vector useAmountTypes = { ScenarioBonusData::ARTIFACT, ScenarioBonusData::TROOP, ScenarioBonusData::SKILL_PRIMARY }; const bool useAmount = std::find( useAmountTypes.begin(), useAmountTypes.end(), _type ) != useAmountTypes.end() && _amount > 1; return useAmount ? std::to_string( _amount ) + " " + objectName : objectName; } std::string ScenarioBonusData::getDescription() const { switch ( _type ) { case ScenarioBonusData::ARTIFACT: { std::string description( _( "The main hero will have \"%{artifact}\" artifact at the start of the scenario." ) ); StringReplace( description, "%{artifact}", Artifact( _subType ).GetName() ); return description; } case ScenarioBonusData::RESOURCES: { std::string description( _amount > 0 ? _( "The kingdom will receive %{amount} additional %{resource} at the start of the scenario." ) : _( "The kingdom will have %{amount} less %{resource} at the start of the scenario." ) ); StringReplace( description, "%{amount}", std::to_string( std::abs( _amount ) ) ); StringReplace( description, "%{resource}", Resource::String( _subType ) ); return description; } case ScenarioBonusData::TROOP: { std::string description( _( "The main hero will have %{count} %{monster} at the start of the scenario." ) ); StringReplace( description, "%{count}", std::to_string( _amount ) ); StringReplaceWithLowercase( description, "%{monster}", Monster( _subType ).GetPluralName( _amount ) ); return description; } case ScenarioBonusData::SPELL: { std::string description( _( "The main hero will have \"%{spell}\" spell at the start of the scenario." ) ); StringReplace( description, "%{spell}", Spell( _subType ).GetName() ); return description; } case ScenarioBonusData::STARTING_RACE: case ScenarioBonusData::STARTING_RACE_AND_ARMY: { std::string description( _( "The starting race of the scenario will be %{race}." ) ); StringReplace( description, "%{race}", Race::String( _subType ) ); return description; } case ScenarioBonusData::SKILL_PRIMARY: { std::string description( _( "The main hero will have additional %{count} %{skill} at the start of the scenario." ) ); StringReplace( description, "%{count}", std::to_string( _amount ) ); StringReplace( description, "%{skill}", Skill::Primary::String( _subType ) ); return description; } case ScenarioBonusData::SKILL_SECONDARY: { std::string description( _( "The main hero will have %{skill} at the start of the scenario." ) ); StringReplace( description, "%{skill}", Skill::Secondary( _subType, _amount ).GetName() ); return description; } default: assert( 0 ); // some new bonus? break; } return {}; } std::vector ScenarioBonusData::getCampaignBonusData( const ScenarioInfoId & scenarioInfo ) { assert( scenarioInfo.scenarioId >= 0 ); switch ( scenarioInfo.campaignId ) { case Campaign::ROLAND_CAMPAIGN: return getRolandCampaignBonusData( scenarioInfo.scenarioId ); case Campaign::ARCHIBALD_CAMPAIGN: return getArchibaldCampaignBonusData( scenarioInfo.scenarioId ); case Campaign::PRICE_OF_LOYALTY_CAMPAIGN: return getPriceOfLoyaltyCampaignBonusData( scenarioInfo.scenarioId ); case Campaign::VOYAGE_HOME_CAMPAIGN: return getVoyageHomeCampaignBonusData( scenarioInfo.scenarioId ); case Campaign::WIZARDS_ISLE_CAMPAIGN: return getWizardsIsleCampaignBonusData( scenarioInfo.scenarioId ); case Campaign::DESCENDANTS_CAMPAIGN: return getDescendantsCampaignBonusData( scenarioInfo.scenarioId ); default: // Did you add a new campaign? Add the corresponding case above. assert( 0 ); break; } // shouldn't be here unless we get an unsupported campaign return std::vector(); } ScenarioData::ScenarioData( const ScenarioInfoId & scenarioInfo, std::vector && nextScenarios, const std::string & fileName, const std::string & scenarioName, const std::string & description, const VideoSequence & startScenarioVideoPlayback, const VideoSequence & endScenarioVideoPlayback, const ScenarioVictoryCondition victoryCondition, const ScenarioLossCondition lossCondition ) : _scenarioInfo( scenarioInfo ) , _nextScenarios( std::move( nextScenarios ) ) , _bonuses( ScenarioBonusData::getCampaignBonusData( scenarioInfo ) ) , _fileName( StringLower( fileName ) ) , _scenarioName( scenarioName ) , _description( description ) , _victoryCondition( victoryCondition ) , _lossCondition( lossCondition ) , _startScenarioVideoPlayback( startScenarioVideoPlayback ) , _endScenarioVideoPlayback( endScenarioVideoPlayback ) {} const char * ScenarioData::getScenarioName() const { return _( _scenarioName ); } const char * ScenarioData::getDescription() const { return _( _description ); } bool Campaign::ScenarioData::isMapFilePresent() const { std::string matchingFilePath; return tryGetMatchingFile( _fileName, matchingFilePath ); } Maps::FileInfo Campaign::ScenarioData::loadMap() const { std::string matchingFilePath; if ( tryGetMatchingFile( _fileName, matchingFilePath ) ) { Maps::FileInfo fi; if ( fi.readMP2Map( std::move( matchingFilePath ), false ) ) { return fi; } } return {}; } const char * getCampaignName( const int campaignId ) { switch ( campaignId ) { case Campaign::ROLAND_CAMPAIGN: return _( "Roland" ); case Campaign::ARCHIBALD_CAMPAIGN: return _( "Archibald" ); case Campaign::PRICE_OF_LOYALTY_CAMPAIGN: return _( "Price of Loyalty" ); case Campaign::VOYAGE_HOME_CAMPAIGN: return _( "Voyage Home" ); case Campaign::WIZARDS_ISLE_CAMPAIGN: return _( "Wizard's Isle" ); case Campaign::DESCENDANTS_CAMPAIGN: return _( "Descendants" ); default: // Did you add a new campaign? Add the corresponding case above. assert( 0 ); break; } return "???"; } } fheroes2-1.0.12+dfsg/src/fheroes2/campaign/campaign_scenariodata.h000066400000000000000000000151361456075706000247770ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2CAMPAIGN_SCENARIODATA_H #define H2CAMPAIGN_SCENARIODATA_H #include #include #include #include "game_video_type.h" #include "maps_fileinfo.h" class StreamBase; namespace Campaign { enum CampaignID { ROLAND_CAMPAIGN = 0, ARCHIBALD_CAMPAIGN = 1, PRICE_OF_LOYALTY_CAMPAIGN = 2, DESCENDANTS_CAMPAIGN = 3, WIZARDS_ISLE_CAMPAIGN = 4, VOYAGE_HOME_CAMPAIGN = 5 }; enum class ScenarioVictoryCondition : int { STANDARD = 0, // standard map's defined victory condition CAPTURE_DRAGON_CITY = 1, OBTAIN_ULTIMATE_CROWN = 2, OBTAIN_SPHERE_NEGATION = 3 }; enum class ScenarioLossCondition : int { STANDARD = 0, // standard map's defined loss condition LOSE_ALL_SORCERESS_VILLAGES = 1 }; struct ScenarioInfoId { ScenarioInfoId() = default; ScenarioInfoId( const int campaignId_, const int scenarioId_ ) : campaignId( campaignId_ ) , scenarioId( scenarioId_ ) { // Do nothing. } bool operator==( const ScenarioInfoId & info ) const { return campaignId == info.campaignId && scenarioId == info.scenarioId; } bool operator!=( const ScenarioInfoId & info ) const { return !operator==( info ); } friend StreamBase & operator<<( StreamBase & msg, const ScenarioInfoId & data ); friend StreamBase & operator>>( StreamBase & msg, ScenarioInfoId & data ); int campaignId{ -1 }; int scenarioId{ -1 }; }; struct ScenarioBonusData { public: enum BonusType : int32_t { RESOURCES = 0, ARTIFACT, TROOP, SPELL, STARTING_RACE, SKILL_PRIMARY, SKILL_SECONDARY, STARTING_RACE_AND_ARMY }; int32_t _type; int32_t _subType; int32_t _amount; int32_t _artifactSpellId; // Spell ID of a spell scroll ScenarioBonusData(); ScenarioBonusData( const int32_t type, const int32_t subType, const int32_t amount ); ScenarioBonusData( const int32_t type, const int32_t subType, const int32_t amount, const int32_t spellId ); std::string getName() const; std::string getDescription() const; static std::vector getCampaignBonusData( const ScenarioInfoId & scenarioInfo ); }; struct ScenarioIntroVideoInfo { std::string fileName; Video::VideoAction action; }; using VideoSequence = std::vector; class ScenarioData { public: ScenarioData() = delete; ScenarioData( const ScenarioInfoId & scenarioInfo, std::vector && nextScenarios, const std::string & fileName, const std::string & scenarioName, const std::string & description, const VideoSequence & startScenarioVideoPlayback, const VideoSequence & endScenarioVideoPlayback, const ScenarioVictoryCondition victoryCondition = ScenarioVictoryCondition::STANDARD, const ScenarioLossCondition lossCondition = ScenarioLossCondition::STANDARD ); const std::vector & getNextScenarios() const { return _nextScenarios; } const std::vector & getBonuses() const { return _bonuses; } int getScenarioID() const { return _scenarioInfo.scenarioId; } int getCampaignId() const { return _scenarioInfo.campaignId; } const ScenarioInfoId & getScenarioInfoId() const { return _scenarioInfo; } const char * getScenarioName() const; const char * getDescription() const; ScenarioVictoryCondition getVictoryCondition() const { return _victoryCondition; } ScenarioLossCondition getLossCondition() const { return _lossCondition; } const std::vector & getStartScenarioVideoPlayback() const { return _startScenarioVideoPlayback; } const std::vector & getEndScenarioVideoPlayback() const { return _endScenarioVideoPlayback; } bool isMapFilePresent() const; Maps::FileInfo loadMap() const; private: ScenarioInfoId _scenarioInfo; std::vector _nextScenarios; std::vector _bonuses; std::string _fileName; // Note: There are inconsistencies with the content of the map file in regards to the map name and description, so we'll be getting them from somewhere else std::string _scenarioName; std::string _description; ScenarioVictoryCondition _victoryCondition; ScenarioLossCondition _lossCondition; VideoSequence _startScenarioVideoPlayback; VideoSequence _endScenarioVideoPlayback; }; const char * getCampaignName( const int campaignId ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/castle/000077500000000000000000000000001456075706000200205ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/fheroes2/castle/buildinginfo.cpp000066400000000000000000000720701456075706000232030ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "buildinginfo.h" #include #include #include #include "agg_image.h" #include "army_troop.h" #include "audio_manager.h" #include "cursor.h" #include "dialog.h" #include "game_hotkeys.h" #include "icn.h" #include "localevent.h" #include "m82.h" #include "maps_fileinfo.h" #include "monster.h" #include "pal.h" #include "payment.h" #include "profit.h" #include "race.h" #include "resource.h" #include "screen.h" #include "settings.h" #include "statusbar.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_text.h" namespace { fheroes2::Point GetFlagOffset( int race ) { switch ( race ) { case Race::KNGT: return { 36, 10 }; case Race::BARB: return { 35, 9 }; case Race::SORC: return { 36, 10 }; case Race::WRLK: return { 34, 10 }; case Race::WZRD: return { 35, 9 }; case Race::NECR: return { 35, 10 }; default: // Did you add a new race? assert( 0 ); return {}; } } } struct buildstats_t { uint32_t id2; uint8_t race; cost_t cost; }; const buildstats_t _builds[] = { // id gold wood mercury ore sulfur crystal gems { BUILD_THIEVESGUILD, Race::ALL, { 750, 5, 0, 0, 0, 0, 0 } }, { BUILD_TAVERN, Race::ALL, { 500, 5, 0, 0, 0, 0, 0 } }, { BUILD_SHIPYARD, Race::ALL, { 2000, 20, 0, 0, 0, 0, 0 } }, { BUILD_WELL, Race::ALL, { 500, 0, 0, 0, 0, 0, 0 } }, { BUILD_STATUE, Race::ALL, { 1250, 0, 0, 5, 0, 0, 0 } }, { BUILD_LEFTTURRET, Race::ALL, { 1500, 0, 0, 5, 0, 0, 0 } }, { BUILD_RIGHTTURRET, Race::ALL, { 1500, 0, 0, 5, 0, 0, 0 } }, { BUILD_MARKETPLACE, Race::ALL, { 500, 5, 0, 0, 0, 0, 0 } }, { BUILD_MOAT, Race::ALL, { 750, 0, 0, 0, 0, 0, 0 } }, { BUILD_CASTLE, Race::ALL, { 5000, 20, 0, 20, 0, 0, 0 } }, { BUILD_CAPTAIN, Race::ALL, { 500, 0, 0, 0, 0, 0, 0 } }, { BUILD_MAGEGUILD1, Race::ALL, { 2000, 5, 0, 5, 0, 0, 0 } }, { BUILD_MAGEGUILD2, Race::ALL, { 1000, 5, 4, 5, 4, 4, 4 } }, { BUILD_MAGEGUILD3, Race::ALL, { 1000, 5, 6, 5, 6, 6, 6 } }, { BUILD_MAGEGUILD4, Race::ALL, { 1000, 5, 8, 5, 8, 8, 8 } }, { BUILD_MAGEGUILD5, Race::ALL, { 1000, 5, 10, 5, 10, 10, 10 } }, { BUILD_WEL2, Race::KNGT, { 1000, 0, 0, 0, 0, 0, 0 } }, { BUILD_WEL2, Race::BARB, { 1000, 0, 0, 0, 0, 0, 0 } }, { BUILD_WEL2, Race::SORC, { 1000, 0, 0, 0, 0, 0, 0 } }, { BUILD_WEL2, Race::WRLK, { 1000, 0, 0, 0, 0, 0, 0 } }, { BUILD_WEL2, Race::WZRD, { 1000, 0, 0, 0, 0, 0, 0 } }, { BUILD_WEL2, Race::NECR, { 1000, 0, 0, 0, 0, 0, 0 } }, { BUILD_SPEC, Race::KNGT, { 1500, 5, 0, 15, 0, 0, 0 } }, { BUILD_SPEC, Race::BARB, { 2000, 10, 0, 10, 0, 0, 0 } }, { BUILD_SPEC, Race::SORC, { 1500, 0, 0, 0, 0, 10, 0 } }, { BUILD_SPEC, Race::WRLK, { 3000, 5, 0, 10, 0, 0, 0 } }, { BUILD_SPEC, Race::WZRD, { 1500, 5, 5, 5, 5, 5, 5 } }, { BUILD_SPEC, Race::NECR, { 1000, 0, 10, 0, 10, 0, 0 } }, { BUILD_SHRINE, Race::NECR, { 4000, 10, 0, 0, 0, 10, 0 } }, { DWELLING_MONSTER1, Race::KNGT, { 200, 0, 0, 0, 0, 0, 0 } }, { DWELLING_MONSTER2, Race::KNGT, { 1000, 0, 0, 0, 0, 0, 0 } }, { DWELLING_UPGRADE2, Race::KNGT, { 1500, 5, 0, 0, 0, 0, 0 } }, { DWELLING_MONSTER3, Race::KNGT, { 1000, 0, 0, 5, 0, 0, 0 } }, { DWELLING_UPGRADE3, Race::KNGT, { 1500, 0, 0, 5, 0, 0, 0 } }, { DWELLING_MONSTER4, Race::KNGT, { 2000, 10, 0, 10, 0, 0, 0 } }, { DWELLING_UPGRADE4, Race::KNGT, { 2000, 5, 0, 5, 0, 0, 0 } }, { DWELLING_MONSTER5, Race::KNGT, { 3000, 20, 0, 0, 0, 0, 0 } }, { DWELLING_UPGRADE5, Race::KNGT, { 3000, 10, 0, 0, 0, 0, 0 } }, { DWELLING_MONSTER6, Race::KNGT, { 5000, 20, 0, 0, 0, 20, 0 } }, { DWELLING_UPGRADE6, Race::KNGT, { 5000, 10, 0, 0, 0, 10, 0 } }, { DWELLING_MONSTER1, Race::BARB, { 300, 0, 0, 0, 0, 0, 0 } }, { DWELLING_MONSTER2, Race::BARB, { 800, 5, 0, 0, 0, 0, 0 } }, { DWELLING_UPGRADE2, Race::BARB, { 1200, 5, 0, 0, 0, 0, 0 } }, { DWELLING_MONSTER3, Race::BARB, { 1000, 0, 0, 0, 0, 0, 0 } }, { DWELLING_MONSTER4, Race::BARB, { 2000, 10, 0, 10, 0, 0, 0 } }, { DWELLING_UPGRADE4, Race::BARB, { 3000, 5, 0, 5, 0, 0, 0 } }, { DWELLING_MONSTER5, Race::BARB, { 4000, 0, 0, 20, 0, 0, 0 } }, { DWELLING_UPGRADE5, Race::BARB, { 2000, 0, 0, 10, 0, 0, 0 } }, { DWELLING_MONSTER6, Race::BARB, { 6000, 0, 0, 20, 0, 20, 0 } }, { DWELLING_MONSTER1, Race::SORC, { 500, 5, 0, 0, 0, 0, 0 } }, { DWELLING_MONSTER2, Race::SORC, { 1000, 5, 0, 0, 0, 0, 0 } }, { DWELLING_UPGRADE2, Race::SORC, { 1500, 5, 0, 0, 0, 0, 0 } }, { DWELLING_MONSTER3, Race::SORC, { 1500, 0, 0, 0, 0, 0, 0 } }, { DWELLING_UPGRADE3, Race::SORC, { 1500, 5, 0, 0, 0, 0, 0 } }, { DWELLING_MONSTER4, Race::SORC, { 2500, 0, 0, 10, 0, 0, 0 } }, { DWELLING_UPGRADE4, Race::SORC, { 1500, 0, 5, 0, 0, 0, 0 } }, { DWELLING_MONSTER5, Race::SORC, { 3000, 10, 0, 0, 0, 0, 10 } }, { DWELLING_MONSTER6, Race::SORC, { 10000, 0, 20, 30, 0, 0, 0 } }, { DWELLING_MONSTER1, Race::WRLK, { 500, 0, 0, 0, 0, 0, 0 } }, { DWELLING_MONSTER2, Race::WRLK, { 1000, 0, 0, 10, 0, 0, 0 } }, { DWELLING_MONSTER3, Race::WRLK, { 2000, 0, 0, 0, 0, 0, 0 } }, { DWELLING_MONSTER4, Race::WRLK, { 3000, 0, 0, 0, 0, 0, 10 } }, { DWELLING_UPGRADE4, Race::WRLK, { 2000, 0, 0, 0, 0, 0, 5 } }, { DWELLING_MONSTER5, Race::WRLK, { 4000, 0, 0, 0, 10, 0, 0 } }, { DWELLING_MONSTER6, Race::WRLK, { 15000, 0, 0, 30, 20, 0, 0 } }, { DWELLING_UPGRADE6, Race::WRLK, { 5000, 0, 0, 5, 10, 0, 0 } }, { DWELLING_UPGRADE7, Race::WRLK, { 5000, 0, 0, 5, 10, 0, 0 } }, { DWELLING_MONSTER1, Race::WZRD, { 400, 0, 0, 0, 0, 0, 0 } }, { DWELLING_MONSTER2, Race::WZRD, { 800, 0, 0, 0, 0, 0, 0 } }, { DWELLING_MONSTER3, Race::WZRD, { 1500, 5, 0, 5, 0, 0, 0 } }, { DWELLING_UPGRADE3, Race::WZRD, { 1500, 0, 5, 0, 0, 0, 0 } }, { DWELLING_MONSTER4, Race::WZRD, { 3000, 5, 0, 0, 0, 0, 0 } }, { DWELLING_MONSTER5, Race::WZRD, { 3500, 5, 5, 5, 5, 5, 5 } }, { DWELLING_UPGRADE5, Race::WZRD, { 4000, 5, 0, 5, 0, 0, 0 } }, { DWELLING_MONSTER6, Race::WZRD, { 12500, 5, 0, 5, 0, 0, 20 } }, { DWELLING_UPGRADE6, Race::WZRD, { 12500, 5, 0, 5, 0, 0, 20 } }, { DWELLING_MONSTER1, Race::NECR, { 400, 0, 0, 0, 0, 0, 0 } }, { DWELLING_MONSTER2, Race::NECR, { 1000, 0, 0, 0, 0, 0, 0 } }, { DWELLING_UPGRADE2, Race::NECR, { 1000, 0, 0, 0, 0, 0, 0 } }, { DWELLING_MONSTER3, Race::NECR, { 1500, 0, 0, 10, 0, 0, 0 } }, { DWELLING_UPGRADE3, Race::NECR, { 1500, 0, 0, 5, 0, 0, 0 } }, { DWELLING_MONSTER4, Race::NECR, { 3000, 10, 0, 0, 0, 0, 0 } }, { DWELLING_UPGRADE4, Race::NECR, { 4000, 5, 0, 0, 0, 10, 10 } }, { DWELLING_MONSTER5, Race::NECR, { 4000, 10, 0, 0, 10, 0, 0 } }, { DWELLING_UPGRADE5, Race::NECR, { 3000, 0, 0, 5, 0, 5, 0 } }, { DWELLING_MONSTER6, Race::NECR, { 10000, 10, 5, 10, 5, 5, 5 } }, // end { BUILD_NOTHING, Race::NONE, { 0, 0, 0, 0, 0, 0, 0 } }, }; Funds BuildingInfo::GetCost( uint32_t build, int race ) { Funds payment; const buildstats_t * ptr = &_builds[0]; while ( BUILD_NOTHING != ptr->id2 && !( ptr->id2 == build && ( !race || ( race & ptr->race ) ) ) ) ++ptr; if ( BUILD_NOTHING != ptr->id2 ) { payment.gold = ptr->cost.gold; payment.wood = ptr->cost.wood; payment.mercury = ptr->cost.mercury; payment.ore = ptr->cost.ore; payment.sulfur = ptr->cost.sulfur; payment.crystal = ptr->cost.crystal; payment.gems = ptr->cost.gems; } return payment; } int GetIndexBuildingSprite( uint32_t build ) { switch ( build ) { case DWELLING_MONSTER1: return 19; case DWELLING_MONSTER2: return 20; case DWELLING_MONSTER3: return 21; case DWELLING_MONSTER4: return 22; case DWELLING_MONSTER5: return 23; case DWELLING_MONSTER6: return 24; case DWELLING_UPGRADE2: return 25; case DWELLING_UPGRADE3: return 26; case DWELLING_UPGRADE4: return 27; case DWELLING_UPGRADE5: return 28; case DWELLING_UPGRADE6: return 29; case DWELLING_UPGRADE7: return 30; case BUILD_MAGEGUILD1: case BUILD_MAGEGUILD2: case BUILD_MAGEGUILD3: case BUILD_MAGEGUILD4: case BUILD_MAGEGUILD5: return 0; case BUILD_THIEVESGUILD: return 1; case BUILD_SHRINE: case BUILD_TAVERN: return 2; case BUILD_SHIPYARD: return 3; case BUILD_WELL: return 4; case BUILD_CASTLE: return 6; case BUILD_STATUE: return 7; case BUILD_LEFTTURRET: return 8; case BUILD_RIGHTTURRET: return 9; case BUILD_MARKETPLACE: return 10; case BUILD_WEL2: return 11; case BUILD_MOAT: return 12; case BUILD_SPEC: return 13; case BUILD_CAPTAIN: return 15; default: break; } return 0; } BuildingInfo::BuildingInfo( const Castle & c, const building_t b ) : castle( c ) , building( b ) , area( 0, 0, 135, 70 ) , bcond( ALLOW_BUILD ) { if ( IsDwelling() ) building = castle.GetActualDwelling( b ); building = castle.isBuild( b ) ? castle.GetUpgradeBuilding( b ) : b; if ( BUILD_TAVERN == building && Race::NECR == castle.GetRace() ) { const GameVersion version = Settings::Get().getCurrentMapInfo().version; building = ( version == GameVersion::PRICE_OF_LOYALTY || version == GameVersion::RESURRECTION ) ? BUILD_SHRINE : BUILD_NOTHING; } bcond = castle.CheckBuyBuilding( building ); // generate description if ( BUILD_DISABLE == bcond ) description = GetConditionDescription(); else if ( IsDwelling() ) { description = _( "The %{building} produces %{monster}." ); StringReplace( description, "%{building}", Castle::GetStringBuilding( building, castle.GetRace() ) ); StringReplaceWithLowercase( description, "%{monster}", Monster( castle.GetRace(), building ).GetMultiName() ); } else description = Castle::GetDescriptionBuilding( building, castle.GetRace() ); switch ( building ) { case BUILD_WELL: StringReplace( description, "%{count}", Castle::GetGrownWell() ); break; case BUILD_WEL2: StringReplace( description, "%{count}", Castle::GetGrownWel2() ); break; case BUILD_CASTLE: case BUILD_STATUE: case BUILD_SPEC: { const Funds profit = ProfitConditions::FromBuilding( building, castle.GetRace() ); StringReplace( description, "%{count}", profit.gold ); break; } default: break; } // fix area for captain if ( b == BUILD_CAPTAIN ) { const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::Get4Captain( castle.GetRace() ), ( building & BUILD_CAPTAIN ? 1 : 0 ) ); area.width = sprite.width(); area.height = sprite.height(); } } void BuildingInfo::SetPos( int32_t x, int32_t y ) { area.x = x; area.y = y; } bool BuildingInfo::IsDwelling() const { switch ( building ) { case DWELLING_MONSTER1: case DWELLING_MONSTER2: case DWELLING_MONSTER3: case DWELLING_MONSTER4: case DWELLING_MONSTER5: case DWELLING_MONSTER6: case DWELLING_UPGRADE2: case DWELLING_UPGRADE3: case DWELLING_UPGRADE4: case DWELLING_UPGRADE5: case DWELLING_UPGRADE6: case DWELLING_UPGRADE7: return true; default: break; } return false; } void BuildingInfo::RedrawCaptain() const { fheroes2::Display & display = fheroes2::Display::instance(); if ( bcond == ALREADY_BUILT ) { const fheroes2::Sprite & captainSprite = fheroes2::AGG::GetICN( ICN::Get4Captain( castle.GetRace() ), 1 ); const fheroes2::Sprite & flag = fheroes2::AGG::GetICN( ICN::GetFlagIcnId( castle.GetColor() ), 0 ); fheroes2::Blit( captainSprite, display, area.x, area.y ); const fheroes2::Point flagOffset = GetFlagOffset( castle.GetRace() ); fheroes2::Blit( flag, display, area.x + flagOffset.x, area.y + flagOffset.y ); } else { fheroes2::Blit( fheroes2::AGG::GetICN( ICN::Get4Captain( castle.GetRace() ), 0 ), display, area.x, area.y ); } // indicator if ( bcond == ALREADY_BUILT ) { const fheroes2::Sprite & spriteAllow = fheroes2::AGG::GetICN( ICN::TOWNWIND, 11 ); fheroes2::Blit( spriteAllow, display, area.x + 83 - 4 - spriteAllow.width(), area.y + 79 - 2 - spriteAllow.height() ); } else if ( bcond != ALLOW_BUILD ) { if ( LACK_RESOURCES == bcond ) { const fheroes2::Sprite & spriteMoney = fheroes2::AGG::GetICN( ICN::TOWNWIND, 13 ); fheroes2::Blit( spriteMoney, display, area.x + 83 - 4 + 1 - spriteMoney.width(), area.y + 79 - 3 - spriteMoney.height() ); } else { const fheroes2::Sprite & spriteDeny = fheroes2::AGG::GetICN( ICN::TOWNWIND, 12 ); fheroes2::Blit( spriteDeny, display, area.x + 83 - 4 + 1 - spriteDeny.width(), area.y + 79 - 2 - spriteDeny.height() ); } } } void BuildingInfo::Redraw() const { if ( BUILD_CAPTAIN == building ) { RedrawCaptain(); return; } fheroes2::Display & display = fheroes2::Display::instance(); const int index = GetIndexBuildingSprite( building ); const fheroes2::Sprite & buildingFrame = fheroes2::AGG::GetICN( ICN::BLDGXTRA, 0 ); fheroes2::Blit( buildingFrame, display, area.x, area.y ); if ( BUILD_DISABLE == bcond ) { const fheroes2::Point offset( 6, 59 ); fheroes2::Sprite grayedOut = fheroes2::Crop( buildingFrame, offset.x, offset.y, 125, 12 ); fheroes2::ApplyPalette( grayedOut, PAL::GetPalette( PAL::PaletteType::GRAY ) ); fheroes2::ApplyPalette( grayedOut, PAL::GetPalette( PAL::PaletteType::DARKENING ) ); fheroes2::Copy( grayedOut, 0, 0, display, area.x + offset.x, area.y + offset.y, 125, 12 ); } // build image if ( BUILD_NOTHING == building ) { const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); const fheroes2::Sprite & buildBackground = fheroes2::AGG::GetICN( isEvilInterface ? ICN::CASLXTRA_EVIL : ICN::CASLXTRA, 0 ); fheroes2::Copy( buildBackground, 0, 0, display, area.x, area.y, buildBackground.width(), buildBackground.height() ); return; } fheroes2::Blit( fheroes2::AGG::GetICN( ICN::Get4Building( castle.GetRace() ), index ), display, area.x + 1, area.y + 1 ); // indicator if ( bcond == ALREADY_BUILT ) { const fheroes2::Sprite & spriteAllow = fheroes2::AGG::GetICN( ICN::TOWNWIND, 11 ); fheroes2::Blit( spriteAllow, display, area.x + buildingFrame.width() - 5 - spriteAllow.width(), area.y + 58 - 2 - spriteAllow.height() ); } else if ( bcond == BUILD_DISABLE ) { const fheroes2::Sprite & spriteDeny = fheroes2::AGG::GetICN( ICN::TOWNWIND, 12 ); fheroes2::Sprite disabledSprite( spriteDeny ); fheroes2::ApplyPalette( disabledSprite, PAL::GetPalette( PAL::PaletteType::GRAY ) ); fheroes2::ApplyPalette( disabledSprite, PAL::GetPalette( PAL::PaletteType::DARKENING ) ); fheroes2::Blit( disabledSprite, display, area.x + buildingFrame.width() - 5 + 1 - spriteDeny.width(), area.y + 58 - 2 - spriteDeny.height() ); } else if ( bcond != ALLOW_BUILD ) { if ( LACK_RESOURCES == bcond ) { const fheroes2::Sprite & spriteMoney = fheroes2::AGG::GetICN( ICN::TOWNWIND, 13 ); fheroes2::Blit( spriteMoney, display, area.x + buildingFrame.width() - 5 + 1 - spriteMoney.width(), area.y + 58 - 3 - spriteMoney.height() ); } else { const fheroes2::Sprite & spriteDeny = fheroes2::AGG::GetICN( ICN::TOWNWIND, 12 ); fheroes2::Blit( spriteDeny, display, area.x + buildingFrame.width() - 5 + 1 - spriteDeny.width(), area.y + 58 - 2 - spriteDeny.height() ); } const fheroes2::Sprite & textBackground = fheroes2::AGG::GetICN( ICN::CASLXTRA, 2 ); fheroes2::Copy( textBackground, 0, 0, display, area.x, area.y + 58, textBackground.width(), textBackground.height() ); } else { const fheroes2::Sprite & textBackground = fheroes2::AGG::GetICN( ICN::CASLXTRA, 1 ); fheroes2::Copy( textBackground, 0, 0, display, area.x, area.y + 58, textBackground.width(), textBackground.height() ); } const fheroes2::Text buildingName( Castle::GetStringBuilding( building, castle.GetRace() ), fheroes2::FontType::smallWhite() ); buildingName.draw( area.x + 68 - buildingName.width() / 2, area.y + 61, display ); } const char * BuildingInfo::GetName() const { return Castle::GetStringBuilding( building, castle.GetRace() ); } bool BuildingInfo::QueueEventProcessing( fheroes2::ButtonBase & exitButton ) const { LocalEvent & le = LocalEvent::Get(); if ( le.MouseClickLeft( area ) ) { if ( bcond == LACK_RESOURCES || bcond == ALLOW_BUILD ) { const fheroes2::ButtonRestorer exitRestorer( exitButton ); return DialogBuyBuilding( true ); } } else if ( le.MousePressRight( area ) ) { DialogBuyBuilding( false ); } return false; } bool BuildingInfo::DialogBuyBuilding( bool buttons ) const { if ( building == BUILD_NOTHING ) { return false; } const CursorRestorer cursorRestorer( buttons, Cursor::POINTER ); std::string extendedDescription = description; if ( ALLOW_BUILD != bcond ) { const std::string & ext = GetConditionDescription(); if ( !ext.empty() && ext != description ) { extendedDescription.append( "\n\n" ); extendedDescription.append( ext ); } } const fheroes2::Text descriptionText( std::move( extendedDescription ), fheroes2::FontType::normalWhite() ); // prepare requirement build string std::string requirement; const uint32_t requirementBuildingIds = castle.GetBuildingRequirement( building ); const char sep = '\n'; for ( uint32_t itr = 0x00000001; itr; itr <<= 1 ) if ( requirementBuildingIds & itr ) { requirement.append( Castle::GetStringBuilding( itr, castle.GetRace() ) ); requirement += sep; } // Remove the last separator. if ( !requirement.empty() ) { requirement.pop_back(); } const bool requirementsPresent = !requirement.empty(); const fheroes2::Text requirementTitle( _( "Requires:" ), fheroes2::FontType::normalWhite() ); const fheroes2::Text requirementText( std::move( requirement ), fheroes2::FontType::normalWhite() ); const int elementOffset = 10; int32_t requirementHeight = 0; if ( requirementsPresent ) { requirementHeight = requirementTitle.height() + requirementText.height( BOXAREA_WIDTH ) + elementOffset; } Resource::BoxSprite rbs( PaymentConditions::BuyBuilding( castle.GetRace(), building ), BOXAREA_WIDTH ); const fheroes2::Sprite & buildingFrame = fheroes2::AGG::GetICN( ICN::BLDGXTRA, 0 ); const int32_t totalDialogHeight = elementOffset + buildingFrame.height() + elementOffset + descriptionText.height( BOXAREA_WIDTH ) + elementOffset + requirementHeight + rbs.GetArea().height; const Dialog::FrameBox dialogFrame( totalDialogHeight, buttons ); const fheroes2::Rect & dialogRoi = dialogFrame.GetArea(); const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); const int buttonOkayIcnID = isEvilInterface ? ICN::UNIFORM_EVIL_OKAY_BUTTON : ICN::UNIFORM_GOOD_OKAY_BUTTON; fheroes2::Point pos{ dialogRoi.x, dialogRoi.y + dialogRoi.height - fheroes2::AGG::GetICN( buttonOkayIcnID, 0 ).height() }; fheroes2::Button buttonOkay( pos.x, pos.y, buttonOkayIcnID, 0, 1 ); const int buttonCancelIcnID = isEvilInterface ? ICN::UNIFORM_EVIL_CANCEL_BUTTON : ICN::UNIFORM_GOOD_CANCEL_BUTTON; pos.x = dialogRoi.x + dialogRoi.width - fheroes2::AGG::GetICN( buttonCancelIcnID, 0 ).width(); pos.y = dialogRoi.y + dialogRoi.height - fheroes2::AGG::GetICN( buttonCancelIcnID, 0 ).height(); fheroes2::Button buttonCancel( pos.x, pos.y, buttonCancelIcnID, 0, 1 ); pos.x = dialogRoi.x + ( dialogRoi.width - buildingFrame.width() ) / 2; pos.y = dialogRoi.y + elementOffset; fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::Blit( buildingFrame, display, pos.x, pos.y ); const fheroes2::Sprite & buildingImage = fheroes2::AGG::GetICN( ICN::Get4Building( castle.GetRace() ), GetIndexBuildingSprite( building ) ); pos.x = dialogRoi.x + ( dialogRoi.width - buildingImage.width() ) / 2; pos.y += 1; fheroes2::Blit( buildingImage, display, pos.x, pos.y ); const fheroes2::Text buildingName( GetName(), fheroes2::FontType::smallWhite() ); pos.x = dialogRoi.x + ( dialogRoi.width - buildingName.width() ) / 2; pos.y += 58; buildingName.draw( pos.x, pos.y + 2, display ); pos.x = dialogRoi.x; pos.y = dialogRoi.y + elementOffset + buildingFrame.height() + elementOffset; descriptionText.draw( pos.x, pos.y + 2, BOXAREA_WIDTH, display ); pos.y += descriptionText.height( BOXAREA_WIDTH ) + elementOffset; if ( requirementsPresent ) { pos.x = dialogRoi.x + ( dialogRoi.width - requirementTitle.width() ) / 2; requirementTitle.draw( pos.x, pos.y + 2, display ); pos.x = dialogRoi.x; pos.y += requirementTitle.height(); requirementText.draw( pos.x, pos.y + 2, BOXAREA_WIDTH, display ); pos.y += requirementText.height( BOXAREA_WIDTH ) + elementOffset; } rbs.SetPos( pos.x, pos.y ); rbs.Redraw(); if ( buttons ) { if ( ALLOW_BUILD != castle.CheckBuyBuilding( building ) ) { buttonOkay.disable(); } buttonOkay.draw(); buttonCancel.draw(); } else { buttonOkay.disable(); buttonOkay.hide(); buttonCancel.disable(); buttonCancel.hide(); } display.render(); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { if ( !buttons && !le.MousePressRight() ) { break; } le.MousePressLeft( buttonOkay.area() ) ? buttonOkay.drawOnPress() : buttonOkay.drawOnRelease(); le.MousePressLeft( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); if ( buttonOkay.isEnabled() && ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) || le.MouseClickLeft( buttonOkay.area() ) ) ) { return true; } if ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancel.area() ) ) { break; } if ( buttonOkay.isVisible() && le.MousePressRight( buttonOkay.area() ) ) { fheroes2::showStandardTextMessage( _( "Okay" ), GetConditionDescription(), Dialog::ZERO ); } else if ( buttonCancel.isVisible() && le.MousePressRight( buttonCancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Exit this menu without doing anything." ), Dialog::ZERO ); } } return false; } const char * GetBuildConditionDescription( int bcond ) { switch ( bcond ) { case NOT_TODAY: return _( "Cannot build. You have already built here today." ); case NEED_CASTLE: return _( "For this action it is necessary to build a castle first." ); default: break; } return nullptr; } std::string BuildingInfo::GetConditionDescription() const { std::string res; switch ( bcond ) { case NOT_TODAY: case NEED_CASTLE: return GetBuildConditionDescription( bcond ); case BUILD_DISABLE: if ( building == BUILD_SHIPYARD ) { res = _( "Cannot build %{name} because the castle is too far away from an ocean." ); StringReplace( res, "%{name}", Castle::GetStringBuilding( BUILD_SHIPYARD, castle.GetRace() ) ); } else { res = _( "disable build." ); } break; case LACK_RESOURCES: res = _( "Cannot afford %{name}." ); StringReplace( res, "%{name}", GetName() ); break; case ALREADY_BUILT: res = _( "%{name} is already built." ); StringReplace( res, "%{name}", GetName() ); break; case REQUIRES_BUILD: res = _( "Cannot build %{name}." ); StringReplace( res, "%{name}", GetName() ); break; case ALLOW_BUILD: res = _( "Build %{name}." ); StringReplace( res, "%{name}", GetName() ); break; default: break; } return res; } void BuildingInfo::SetStatusMessage( StatusBar & bar ) const { if ( building == BUILD_NOTHING ) { return; } switch ( bcond ) { case NOT_TODAY: case ALREADY_BUILT: case NEED_CASTLE: case BUILD_DISABLE: case LACK_RESOURCES: case REQUIRES_BUILD: case ALLOW_BUILD: bar.ShowMessage( GetConditionDescription() ); break; default: break; } } DwellingItem::DwellingItem( const Castle & castle, uint32_t dw ) { type = castle.GetActualDwelling( dw ); mons = Monster( castle.GetRace(), type ); } DwellingsBar::DwellingsBar( Castle & cstl, const fheroes2::Size & sz ) : castle( cstl ) , backsf( sz.width, sz.height ) { backsf.reset(); for ( uint32_t dw = DWELLING_MONSTER1; dw <= DWELLING_MONSTER6; dw <<= 1 ) content.emplace_back( castle, dw ); SetContent( content ); fheroes2::DrawBorder( backsf, fheroes2::GetColorId( 0xd0, 0xc0, 0x48 ) ); setSingleItemSize( sz ); } void DwellingsBar::RedrawBackground( const fheroes2::Rect & pos, fheroes2::Image & dstsf ) { fheroes2::Blit( backsf, dstsf, pos.x, pos.y ); } void DwellingsBar::RedrawItem( DwellingItem & dwl, const fheroes2::Rect & pos, fheroes2::Image & dstsf ) { const fheroes2::Sprite & mons32 = fheroes2::AGG::GetICN( ICN::MONS32, dwl.mons.GetSpriteIndex() ); fheroes2::Blit( mons32, dstsf, pos.x + ( pos.width - mons32.width() ) / 2, pos.y + ( pos.height - 3 - mons32.height() ) ); if ( castle.isBuild( dwl.type ) ) { // Available units for hire. fheroes2::Text text( std::to_string( castle.getMonstersInDwelling( dwl.type ) ), fheroes2::FontType::smallWhite() ); text.draw( pos.x + pos.width - text.width() - 3, pos.y + pos.height - text.height() + 1, dstsf ); uint32_t grown = dwl.mons.GetGrown(); if ( castle.isBuild( BUILD_WELL ) ) grown += Castle::GetGrownWell(); if ( castle.isBuild( BUILD_WEL2 ) && DWELLING_MONSTER1 == dwl.type ) grown += Castle::GetGrownWel2(); // Dwelling's growth. text.set( "+" + std::to_string( grown ), fheroes2::FontType::smallYellow() ); text.draw( pos.x + pos.width - text.width() - 3, pos.y + 4, dstsf ); } else { fheroes2::Blit( fheroes2::AGG::GetICN( ICN::CSLMARKER, 0 ), dstsf, pos.x + pos.width - 10, pos.y + 4 ); } } bool DwellingsBar::ActionBarLeftMouseSingleClick( DwellingItem & dwl ) { if ( castle.isBuild( dwl.type ) ) { castle.RecruitMonster( Dialog::RecruitMonster( dwl.mons, castle.getMonstersInDwelling( dwl.type ), true, -60 ) ); } else if ( !castle.isBuild( BUILD_CASTLE ) ) fheroes2::showStandardTextMessage( "", GetBuildConditionDescription( NEED_CASTLE ), Dialog::OK ); else { const BuildingInfo dwelling( castle, static_cast( dwl.type ) ); if ( dwelling.DialogBuyBuilding( true ) ) { AudioManager::PlaySound( M82::BUILDTWN ); castle.BuyBuilding( dwl.type ); } } return true; } bool DwellingsBar::ActionBarRightMouseHold( DwellingItem & dwl ) { Dialog::DwellingInfo( dwl.mons, castle.getMonstersInDwelling( dwl.type ) ); return true; } fheroes2-1.0.12+dfsg/src/fheroes2/castle/buildinginfo.h000066400000000000000000000066141456075706000226510ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2BUILDINGINFO_H #define H2BUILDINGINFO_H #include #include #include #include "castle.h" #include "image.h" #include "interface_itemsbar.h" #include "math_base.h" #include "monster.h" #include "resource.h" namespace fheroes2 { class ButtonBase; } class StatusBar; class BuildingInfo { public: BuildingInfo( const Castle & c, const building_t b ); uint32_t getBuilding() const { return building; } void SetPos( int32_t, int32_t ); const fheroes2::Rect & GetArea() const { return area; } const char * GetName() const; void SetStatusMessage( StatusBar & ) const; bool IsDwelling() const; void Redraw() const; bool QueueEventProcessing( fheroes2::ButtonBase & exitButton ) const; bool DialogBuyBuilding( bool buttons ) const; static Funds GetCost( uint32_t, int ); private: void RedrawCaptain() const; std::string GetConditionDescription() const; const Castle & castle; uint32_t building; std::string description; fheroes2::Rect area; int bcond; }; struct DwellingItem { DwellingItem( const Castle &, uint32_t dw ); uint32_t type; Monster mons; }; class DwellingsBar : public Interface::ItemsBar { public: DwellingsBar( Castle &, const fheroes2::Size & ); void RedrawBackground( const fheroes2::Rect &, fheroes2::Image & ) override; void RedrawItem( DwellingItem &, const fheroes2::Rect &, fheroes2::Image & ) override; bool ActionBarLeftMouseSingleClick( DwellingItem & dwelling ) override; bool ActionBarRightMouseHold( DwellingItem & dwelling ) override; protected: Castle & castle; fheroes2::Image backsf; std::vector content; }; #endif fheroes2-1.0.12+dfsg/src/fheroes2/castle/captain.cpp000066400000000000000000000173201456075706000221460ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include "agg_image.h" #include "artifact.h" #include "artifact_info.h" #include "captain.h" #include "castle.h" #include "icn.h" #include "interface_icons.h" #include "luck.h" #include "math_base.h" #include "morale.h" #include "race.h" #include "spell_book.h" class Army; namespace { int GetPortraitIcnId( int race ) { switch ( race ) { case Race::KNGT: return ICN::PORT0090; case Race::BARB: return ICN::PORT0091; case Race::SORC: return ICN::PORT0092; case Race::WRLK: return ICN::PORT0093; case Race::WZRD: return ICN::PORT0094; case Race::NECR: return ICN::PORT0095; default: return -1; } } fheroes2::Point GetFlagOffset( int race ) { switch ( race ) { case Race::KNGT: return { 43, 9 }; case Race::BARB: return { 42, 8 }; case Race::SORC: return { 43, 9 }; case Race::WRLK: return { 41, 9 }; case Race::WZRD: return { 42, 10 }; case Race::NECR: return { 42, 9 }; default: return {}; } } } Captain::Captain( Castle & cstl ) : HeroBase( HeroBase::CAPTAIN, cstl.GetRace() ) , home( cstl ) { SetCenter( home.GetCenter() ); } bool Captain::isValid() const { return home.isBuild( BUILD_CAPTAIN ); } int Captain::GetAttack() const { return attack + GetAttackModificator( nullptr ); } int Captain::GetDefense() const { return defense + GetDefenseModificator( nullptr ); } int Captain::GetPower() const { const int finalPower = power + GetPowerModificator( nullptr ); return finalPower < 1 ? 1 : ( finalPower > 255 ? 255 : finalPower ); } int Captain::GetKnowledge() const { return knowledge + GetKnowledgeModificator( nullptr ); } int Captain::GetMorale() const { int result = Morale::NORMAL; // global modificator result += GetMoraleModificator( nullptr ); // A special artifact ability presence must be the last check. const Artifact maxMoraleArtifact = bag_artifacts.getFirstArtifactWithBonus( fheroes2::ArtifactBonusType::MAXIMUM_MORALE ); if ( maxMoraleArtifact.isValid() ) { result = Morale::BLOOD; } return Morale::Normalize( result ); } int Captain::GetLuck() const { int result = Luck::NORMAL; // global modificator result += GetLuckModificator( nullptr ); // A special artifact ability presence must be the last check. const Artifact maxLuckArtifact = bag_artifacts.getFirstArtifactWithBonus( fheroes2::ArtifactBonusType::MAXIMUM_LUCK ); if ( maxLuckArtifact.isValid() ) { result = Luck::IRISH; } return Luck::Normalize( result ); } int Captain::GetRace() const { return home.GetRace(); } int Captain::GetColor() const { return home.GetColor(); } const std::string & Captain::GetName() const { return home.GetName(); } int Captain::GetType() const { return HeroBase::CAPTAIN; } const Army & Captain::GetArmy() const { return home.GetArmy(); } Army & Captain::GetArmy() { return home.GetArmy(); } int Captain::GetControl() const { return home.GetControl(); } void Captain::ActionAfterBattle() { SetSpellPoints( GetMaxSpellPoints() ); } void Captain::ActionPreBattle() { spell_book.resetState(); } const Castle * Captain::inCastle() const { return &home; } fheroes2::Sprite Captain::GetPortrait( const PortraitType type ) const { switch ( type ) { case PORT_BIG: { const int portraitIcnId = GetPortraitIcnId( GetRace() ); if ( portraitIcnId < 0 ) return fheroes2::Image(); fheroes2::Sprite portrait = fheroes2::AGG::GetICN( portraitIcnId, 0 ); const fheroes2::Image & flag = fheroes2::AGG::GetICN( ICN::GetFlagIcnId( GetColor() ), 0 ); const fheroes2::Point & offset = GetFlagOffset( GetRace() ); fheroes2::Blit( flag, portrait, offset.x, offset.y ); return portrait; } case PORT_MEDIUM: case PORT_SMALL: switch ( GetRace() ) { case Race::KNGT: return fheroes2::AGG::GetICN( ICN::MINICAPT, 0 ); case Race::BARB: return fheroes2::AGG::GetICN( ICN::MINICAPT, 1 ); case Race::SORC: return fheroes2::AGG::GetICN( ICN::MINICAPT, 2 ); case Race::WRLK: return fheroes2::AGG::GetICN( ICN::MINICAPT, 3 ); case Race::WZRD: return fheroes2::AGG::GetICN( ICN::MINICAPT, 4 ); case Race::NECR: return fheroes2::AGG::GetICN( ICN::MINICAPT, 5 ); default: break; } break; default: break; } // We shouldn't even reach this code! assert( 0 ); return fheroes2::AGG::GetICN( -1, 0 ); } void Captain::PortraitRedraw( const int32_t px, const int32_t py, const PortraitType type, fheroes2::Image & dstsf ) const { if ( !isValid() ) return; const fheroes2::Sprite & port = GetPortrait( type ); if ( PORT_SMALL != type ) { // a normal portrait in a castle or in battle fheroes2::Blit( port, dstsf, px, py ); return; } const fheroes2::Sprite & mana = fheroes2::AGG::GetICN( ICN::MANA, GetManaIndexSprite() ); const int iconWidth = Interface::IconsBar::GetItemWidth(); const int iconHeight = Interface::IconsBar::GetItemHeight(); const int barWidth = 7; // background fheroes2::Fill( dstsf, px, py, iconWidth, iconHeight, 0 ); // mobility is always 0 const uint8_t blueColor = fheroes2::GetColorId( 15, 30, 120 ); fheroes2::Fill( dstsf, px, py, barWidth, iconHeight, blueColor ); // portrait fheroes2::Blit( port, dstsf, px + barWidth + 1, py ); // spell points fheroes2::Fill( dstsf, px + barWidth + port.width() + 2, py, barWidth, iconHeight, blueColor ); fheroes2::Blit( mana, dstsf, px + barWidth + port.width() + 2, py + mana.y() ); } int Captain::GetManaIndexSprite() const { // valid range (0 - 25) const int r = GetMaxSpellPoints() / 5; return 25 >= r ? r : 25; } fheroes2-1.0.12+dfsg/src/fheroes2/castle/captain.h000066400000000000000000000062331456075706000216140ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2CAPTAIN_H #define H2CAPTAIN_H #include #include #include "heroes_base.h" #include "image.h" class Army; class Castle; class Captain final : public HeroBase { public: explicit Captain( Castle & ); Captain( const Captain & ) = delete; ~Captain() override = default; Captain & operator=( const Captain & ) = delete; bool isValid() const override; int GetAttack() const override; int GetDefense() const override; int GetPower() const override; int GetKnowledge() const override; int GetMorale() const override; int GetLuck() const override; int GetRace() const override; int GetColor() const override; int GetType() const override; int GetControl() const override; const std::string & GetName() const override; const Castle * inCastle() const override; int GetLevelSkill( int ) const override { return 0; } uint32_t GetSecondaryValues( int ) const override { return 0; } const Army & GetArmy() const override; Army & GetArmy() override; uint32_t GetMaxSpellPoints() const override { return knowledge * 10; } void ActionPreBattle() override; void ActionAfterBattle() override; void PortraitRedraw( const int32_t px, const int32_t py, const PortraitType type, fheroes2::Image & dstsf ) const override; fheroes2::Sprite GetPortrait( const PortraitType type ) const; private: int GetManaIndexSprite() const; Castle & home; }; #endif fheroes2-1.0.12+dfsg/src/fheroes2/castle/castle.cpp000066400000000000000000002573061456075706000220140ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "castle.h" #include #include #include #include #include #include #include "agg_image.h" #include "ai.h" #include "army_troop.h" #include "artifact.h" #include "audio_manager.h" #include "battle_board.h" #include "battle_tower.h" #include "castle_building_info.h" #include "dialog.h" #include "difficulty.h" #include "direction.h" #include "game.h" #include "game_static.h" #include "ground.h" #include "heroes.h" #include "heroes_base.h" #include "icn.h" #include "image.h" #include "kingdom.h" #include "logging.h" #include "luck.h" #include "m82.h" #include "maps.h" #include "maps_fileinfo.h" #include "maps_tiles.h" #include "morale.h" #include "mp2.h" #include "payment.h" #include "profit.h" #include "race.h" #include "rand.h" #include "resource.h" #include "screen.h" #include "serialize.h" #include "settings.h" #include "skill.h" #include "spell.h" #include "spell_storage.h" #include "tools.h" #include "translations.h" #include "ui_dialog.h" #include "week.h" #include "world.h" namespace { const size_t maximumCastles = 72; const std::array defaultCastleNames = { gettext_noop( "Blackridge" ), gettext_noop( "Pinehurst" ), gettext_noop( "Woodhaven" ), gettext_noop( "Hillstone" ), gettext_noop( "Whiteshield" ), gettext_noop( "Bloodreign" ), gettext_noop( "Dragontooth" ), gettext_noop( "Greywind" ), gettext_noop( "Blackwind" ), gettext_noop( "Portsmith" ), gettext_noop( "Middle Gate" ), gettext_noop( "Tundara" ), gettext_noop( "Vulcania" ), gettext_noop( "Sansobar" ), gettext_noop( "Atlantium" ), gettext_noop( "Baywatch" ), gettext_noop( "Wildabar" ), gettext_noop( "Fountainhead" ), gettext_noop( "Vertigo" ), gettext_noop( "Winterkill" ), gettext_noop( "Nightshadow" ), gettext_noop( "Sandcaster" ), gettext_noop( "Lakeside" ), gettext_noop( "Olympus" ), gettext_noop( "Brindamoor" ), gettext_noop( "Burlock" ), gettext_noop( "Xabran" ), gettext_noop( "Dragadune" ), gettext_noop( "Alamar" ), gettext_noop( "Kalindra" ), gettext_noop( "Blackfang" ), gettext_noop( "Basenji" ), gettext_noop( "Algary" ), gettext_noop( "Sorpigal" ), gettext_noop( "New Dawn" ), gettext_noop( "Erliquin" ), gettext_noop( "Avone" ), gettext_noop( "Big Oak" ), gettext_noop( "Hampshire" ), gettext_noop( "Chandler" ), gettext_noop( "South Mill" ), gettext_noop( "Weed Patch" ), gettext_noop( "Roc Haven" ), gettext_noop( "Avalon" ), gettext_noop( "Antioch" ), gettext_noop( "Brownston" ), gettext_noop( "Weddington" ), gettext_noop( "Whittingham" ), gettext_noop( "Westfork" ), gettext_noop( "Hilltop" ), gettext_noop( "Yorksford" ), gettext_noop( "Sherman" ), gettext_noop( "Roscomon" ), gettext_noop( "Elk's Head" ), gettext_noop( "Cathcart" ), gettext_noop( "Viper's Nest" ), gettext_noop( "Pig's Eye" ), gettext_noop( "Blacksford" ), gettext_noop( "Burton" ), gettext_noop( "Blackburn" ), gettext_noop( "Lankershire" ), gettext_noop( "Lombard" ), gettext_noop( "Timberhill" ), gettext_noop( "Fenton" ), gettext_noop( "Troy" ), gettext_noop( "Forder Oaks" ), gettext_noop( "Meramec" ), gettext_noop( "Quick Silver" ), gettext_noop( "Westmoor" ), gettext_noop( "Willow" ), gettext_noop( "Sheltemburg" ), gettext_noop( "Corackston" ) }; } Castle::Castle() : race( Race::NONE ) , building( 0 ) , captain( *this ) , army( nullptr ) { std::fill( dwelling, dwelling + CASTLEMAXMONSTER, 0 ); army.SetCommander( &captain ); } Castle::Castle( int32_t cx, int32_t cy, int rc ) : MapPosition( fheroes2::Point( cx, cy ) ) , race( rc ) , building( 0 ) , captain( *this ) , army( nullptr ) { std::fill( dwelling, dwelling + CASTLEMAXMONSTER, 0 ); army.SetCommander( &captain ); } void Castle::LoadFromMP2( const std::vector & data ) { assert( data.size() == MP2::MP2_CASTLE_STRUCTURE_SIZE ); // Structure containing information about town or castle. // // - uint8_t (1 byte) // Owner color. Possible values: // 00 - blue // 01 - green // 02 - red // 03 - yellow // 04 - orange // 05 - purple // 255 - none // // - uint8_t (1 byte) // Does the town / castle have custom buildings set by map creator? // // - uint16_t (2 bytes) // Bitfield containing common buildings within the town / castle. // 0000 0000 0000 0010 : Thieves' Guild // 0000 0000 0000 0100 : Tavern // 0000 0000 0000 1000 : Shipyard // 0000 0000 0001 0000 : Well // 0000 0000 1000 0000 : Statue // 0000 0001 0000 0000 : Left Turret // 0000 0010 0000 0000 : Right Turret // 0000 0100 0000 0000 : Marketplace // 0000 1000 0000 0000 : First monster level growth building // 0001 0000 0000 0000 : Moat // 0010 0000 0000 0000 : Special building // // - uint16_t (2 bytes) // Bitfield containing information about built dwellings in the town / castle. // 0000 0000 0000 1000 : level 1 dwelling // 0000 0000 0001 0000 : level 2 dwelling // 0000 0000 0010 0000 : level 3 dwelling // 0000 0000 0100 0000 : level 4 dwelling // 0000 0000 1000 0000 : level 5 dwelling // 0000 0001 0000 0000 : level 6 dwelling // 0000 0010 0000 0000 : upgraded level 2 dwelling // 0000 0100 0000 0000 : upgraded level 3 dwelling // 0000 1000 0000 0000 : upgraded level 4 dwelling // 0001 0000 0000 0000 : upgraded level 5 dwelling // 0010 0000 0000 0000 : upgraded level 6 dwelling // // - uint8_t (1 byte) // Magic Guild level. // // - uint8_t (1 byte) // Does the town / castle have custom set defenders? // // - uint8_t (1 byte) // Custom defender monster type in army slot 1. // // - uint8_t (1 byte) // Custom defender monster type in army slot 2. // // - uint8_t (1 byte) // Custom defender monster type in army slot 3. // // - uint8_t (1 byte) // Custom defender monster type in army slot 4. // // - uint8_t (1 byte) // Custom defender monster type in army slot 5. // // - uint16_t (2 bytes) // The number of custom defender monsters in army slot 1. // // - uint16_t (2 bytes) // The number of custom defender monsters in army slot 2. // // - uint16_t (2 bytes) // The number of custom defender monsters in army slot 3. // // - uint16_t (2 bytes) // The number of custom defender monsters in army slot 4. // // - uint16_t (2 bytes) // The number of custom defender monsters in army slot 5. // // - uint8_t (1 byte) // Does the town / castle have captain? // // - uint8_t (1 byte) // Does the town / castle have a specified name? // // - string of 13 bytes // Null terminated string of custom town / castle name. // // - uint8_t (1 byte) // Town / castle faction type. Possible values // 00 - knight // 01 - barbarian // 02 - sorceress // 03 - warlock // 04 - wizard // 05 - necromancer // 06 - random // // - uint8_t (1 byte) // Is it a castle (is castle building being built)? // // - uint8_t (1 byte) // Is it allowed to build a castle? // // - unused 29 bytes // Always zeros. StreamBuf dataStream( data ); const uint8_t ownerColor = dataStream.get(); switch ( ownerColor ) { case 0: SetColor( Color::BLUE ); break; case 1: SetColor( Color::GREEN ); break; case 2: SetColor( Color::RED ); break; case 3: SetColor( Color::YELLOW ); break; case 4: SetColor( Color::ORANGE ); break; case 5: SetColor( Color::PURPLE ); break; default: SetColor( Color::NONE ); break; } const bool hasCustomBuildings = ( dataStream.get() != 0 ); if ( hasCustomBuildings ) { // Common buildings. const uint16_t commonBuildings = dataStream.getLE16(); if ( 0x0002 & commonBuildings ) building |= BUILD_THIEVESGUILD; if ( 0x0004 & commonBuildings ) building |= BUILD_TAVERN; if ( 0x0008 & commonBuildings ) building |= BUILD_SHIPYARD; if ( 0x0010 & commonBuildings ) building |= BUILD_WELL; if ( 0x0080 & commonBuildings ) building |= BUILD_STATUE; if ( 0x0100 & commonBuildings ) building |= BUILD_LEFTTURRET; if ( 0x0200 & commonBuildings ) building |= BUILD_RIGHTTURRET; if ( 0x0400 & commonBuildings ) building |= BUILD_MARKETPLACE; if ( 0x1000 & commonBuildings ) building |= BUILD_MOAT; if ( 0x0800 & commonBuildings ) building |= BUILD_WEL2; if ( 0x2000 & commonBuildings ) building |= BUILD_SPEC; // Existing dwellings. const uint16_t existingDwellings = dataStream.getLE16(); if ( 0x0008 & existingDwellings ) building |= DWELLING_MONSTER1; if ( 0x0010 & existingDwellings ) building |= DWELLING_MONSTER2; if ( 0x0020 & existingDwellings ) building |= DWELLING_MONSTER3; if ( 0x0040 & existingDwellings ) building |= DWELLING_MONSTER4; if ( 0x0080 & existingDwellings ) building |= DWELLING_MONSTER5; if ( 0x0100 & existingDwellings ) building |= DWELLING_MONSTER6; if ( 0x0200 & existingDwellings ) building |= DWELLING_UPGRADE2 | DWELLING_MONSTER2; if ( 0x0400 & existingDwellings ) building |= DWELLING_UPGRADE3 | DWELLING_MONSTER3; if ( 0x0800 & existingDwellings ) building |= DWELLING_UPGRADE4 | DWELLING_MONSTER4; if ( 0x1000 & existingDwellings ) building |= DWELLING_UPGRADE5 | DWELLING_MONSTER5; if ( 0x2000 & existingDwellings ) building |= DWELLING_UPGRADE6 | DWELLING_MONSTER6; // magic tower const uint8_t magicGuildLevel = dataStream.get(); if ( 0 < magicGuildLevel ) building |= BUILD_MAGEGUILD1; if ( 1 < magicGuildLevel ) building |= BUILD_MAGEGUILD2; if ( 2 < magicGuildLevel ) building |= BUILD_MAGEGUILD3; if ( 3 < magicGuildLevel ) building |= BUILD_MAGEGUILD4; if ( 4 < magicGuildLevel ) building |= BUILD_MAGEGUILD5; } else { // Skip reading 5 bytes corresponding to custom buildings for the town / castle. dataStream.skip( 5 ); // Set default buildings. building |= DWELLING_MONSTER1; uint32_t dwelling2 = 0; switch ( Game::getDifficulty() ) { case Difficulty::EASY: dwelling2 = 75; break; case Difficulty::NORMAL: dwelling2 = 50; break; case Difficulty::HARD: dwelling2 = 25; break; case Difficulty::EXPERT: dwelling2 = 10; break; default: break; } if ( dwelling2 && dwelling2 >= Rand::Get( 1, 100 ) ) building |= DWELLING_MONSTER2; } const bool customDefenders = ( dataStream.get() != 0 ); if ( customDefenders ) { Troop troops[5]; // set monster id for ( Troop & troop : troops ) troop.SetMonster( dataStream.get() + 1 ); // set count for ( Troop & troop : troops ) troop.SetCount( dataStream.getLE16() ); army.Assign( troops, std::end( troops ) ); SetModes( CUSTOMARMY ); } else { // Skip 15 bytes as custom defenders are not set. dataStream.skip( 15 ); } const bool isCaptainAvailable = ( dataStream.get() != 0 ); if ( isCaptainAvailable ) { building |= BUILD_CAPTAIN; } const bool isCustomTownNameSet = ( dataStream.get() != 0 ); if ( isCustomTownNameSet ) { name = dataStream.toString( 13 ); } else { // Skip 13 bytes since the name is not set. dataStream.skip( 13 ); } const uint8_t castleFaction = dataStream.get(); switch ( castleFaction ) { case 0: race = Race::KNGT; break; case 1: race = Race::BARB; break; case 2: race = Race::SORC; break; case 3: race = Race::WRLK; break; case 4: race = Race::WZRD; break; case 5: race = Race::NECR; break; default: { const uint32_t kingdomRace = Players::GetPlayerRace( GetColor() ); race = ( Color::NONE != GetColor() && ( Race::ALL & kingdomRace ) ? kingdomRace : Race::Rand() ); break; } } const bool isCastleBuilt = ( dataStream.get() != 0 ); if ( isCastleBuilt ) { building |= BUILD_CASTLE; } else { building |= BUILD_TENT; } const bool allowToBuildCastle = ( dataStream.get() != 0 ); if ( allowToBuildCastle ) { ResetModes( ALLOWCASTLE ); } else { SetModes( ALLOWCASTLE ); } // Skip the rest of 29 bytes. PostLoad(); } void Castle::PostLoad() { // dwelling pack if ( building & DWELLING_MONSTER1 ) dwelling[0] = Monster( race, DWELLING_MONSTER1 ).GetGrown(); if ( building & DWELLING_MONSTER2 ) dwelling[1] = Monster( race, DWELLING_MONSTER2 ).GetGrown(); if ( building & DWELLING_UPGRADE2 ) dwelling[1] = Monster( race, DWELLING_UPGRADE2 ).GetGrown(); if ( building & DWELLING_MONSTER3 ) dwelling[2] = Monster( race, DWELLING_MONSTER3 ).GetGrown(); if ( building & DWELLING_UPGRADE3 ) dwelling[2] = Monster( race, DWELLING_UPGRADE3 ).GetGrown(); if ( building & DWELLING_MONSTER4 ) dwelling[3] = Monster( race, DWELLING_MONSTER4 ).GetGrown(); if ( building & DWELLING_UPGRADE4 ) dwelling[3] = Monster( race, DWELLING_UPGRADE4 ).GetGrown(); if ( building & DWELLING_MONSTER5 ) dwelling[4] = Monster( race, DWELLING_MONSTER5 ).GetGrown(); if ( building & DWELLING_UPGRADE5 ) dwelling[4] = Monster( race, DWELLING_UPGRADE5 ).GetGrown(); if ( building & DWELLING_MONSTER6 ) dwelling[5] = Monster( race, DWELLING_MONSTER6 ).GetGrown(); if ( building & DWELLING_UPGRADE6 ) dwelling[5] = Monster( race, DWELLING_UPGRADE6 ).GetGrown(); if ( building & DWELLING_UPGRADE7 ) dwelling[5] = Monster( race, DWELLING_UPGRADE7 ).GetGrown(); // fix upgrade dwelling dependent from race switch ( race ) { case Race::BARB: building &= ~( DWELLING_UPGRADE3 | DWELLING_UPGRADE6 ); break; case Race::SORC: building &= ~( DWELLING_UPGRADE5 | DWELLING_UPGRADE6 ); break; case Race::WRLK: building &= ~( DWELLING_UPGRADE2 | DWELLING_UPGRADE3 | DWELLING_UPGRADE5 ); break; case Race::WZRD: building &= ~( DWELLING_UPGRADE2 | DWELLING_UPGRADE4 ); break; case Race::NECR: building &= ~DWELLING_UPGRADE6; break; default: break; } army.SetColor( GetColor() ); // fix captain if ( building & BUILD_CAPTAIN ) { captain.LoadDefaults( HeroBase::CAPTAIN, race ); captain.SetSpellPoints( captain.GetMaxSpellPoints() ); } // MageGuild mageguild.initialize( race, HaveLibraryCapability() ); // educate heroes and captain EducateHeroes(); // AI troops auto pack for gray towns if ( Color::NONE == GetColor() && !Modes( CUSTOMARMY ) ) { // towns get 4 reinforcements at the start of the game for ( int i = 0; i < 4; ++i ) JoinRNDArmy(); } if ( !HasSeaAccess() ) { // Remove shipyard if no sea access. building &= ~BUILD_SHIPYARD; } // remove tavern from necromancer castle if ( Race::NECR == race && ( building & BUILD_TAVERN ) ) { building &= ~BUILD_TAVERN; const GameVersion version = Settings::Get().getCurrentMapInfo().version; if ( version == GameVersion::PRICE_OF_LOYALTY || version == GameVersion::RESURRECTION ) { building |= BUILD_SHRINE; } } SetModes( ALLOWBUILD ); // end DEBUG_LOG( DBG_GAME, DBG_INFO, ( building & BUILD_CASTLE ? "castle" : "town" ) << ": " << name << ", color: " << Color::String( GetColor() ) << ", race: " << Race::String( race ) ) } uint32_t Castle::CountBuildings() const { uint32_t tavern = BUILD_TAVERN; if ( race == Race::NECR ) { const GameVersion version = Settings::Get().getCurrentMapInfo().version; if ( version == GameVersion::PRICE_OF_LOYALTY || version == GameVersion::RESURRECTION ) { tavern = BUILD_SHRINE; } else { tavern = BUILD_NOTHING; } } return CountBits( building & ( BUILD_THIEVESGUILD | tavern | BUILD_SHIPYARD | BUILD_WELL | BUILD_STATUE | BUILD_LEFTTURRET | BUILD_RIGHTTURRET | BUILD_MARKETPLACE | BUILD_WEL2 | BUILD_MOAT | BUILD_SPEC | BUILD_CAPTAIN | BUILD_CASTLE | BUILD_MAGEGUILD1 | DWELLING_MONSTER1 | DWELLING_MONSTER2 | DWELLING_MONSTER3 | DWELLING_MONSTER4 | DWELLING_MONSTER5 | DWELLING_MONSTER6 ) ); } bool Castle::isPosition( const fheroes2::Point & pt ) const { const fheroes2::Point & mp = GetCenter(); /* - --- -+++- ++X++ */ return ( ( pt.x >= mp.x - 1 && pt.x <= mp.x + 1 && ( pt.y == mp.y - 1 || pt.y == mp.y ) ) || ( ( pt.x == mp.x - 2 || pt.x == mp.x + 2 ) && pt.y == mp.y ) ); } void Castle::EducateHeroes() { if ( GetLevelMageGuild() == 0 ) { return; } Heroes * hero = world.GetHero( *this ); if ( hero != nullptr ) { MageGuildEducateHero( *hero ); } if ( captain.isValid() ) { MageGuildEducateHero( captain ); } } int Castle::getBuildingValue() const { int value = CountBuildings(); // Additional value for the most important buildings if ( isBuild( BUILD_CASTLE ) ) value += 5; if ( isBuild( DWELLING_MONSTER6 ) ) value += 6; if ( race == Race::WRLK && isBuild( DWELLING_UPGRADE7 ) ) value += 2; // DWELLING_UPGRADE7 resolves to a negative, can't use <= operator for ( uint32_t upgrade = DWELLING_UPGRADE2; upgrade <= DWELLING_UPGRADE6; upgrade <<= 1 ) { if ( isBuild( upgrade ) ) ++value; } int increase = 1; for ( uint32_t guild = BUILD_MAGEGUILD2; guild <= BUILD_MAGEGUILD5; guild <<= 1 ) { if ( isBuild( guild ) ) value += increase; ++increase; } return value; } Troops Castle::getAvailableArmy( Funds potentialBudget ) const { Troops reinforcement( army.getTroops() ); for ( uint32_t dw = DWELLING_MONSTER6; dw >= DWELLING_MONSTER1; dw >>= 1 ) { if ( isBuild( dw ) ) { const Monster monster( race, GetActualDwelling( dw ) ); const uint32_t available = getMonstersInDwelling( dw ); uint32_t couldRecruit = potentialBudget.getLowestQuotient( monster.GetCost() ); if ( available < couldRecruit ) couldRecruit = available; if ( couldRecruit > 0 ) { potentialBudget -= ( monster.GetCost() * couldRecruit ); reinforcement.PushBack( monster, couldRecruit ); } } } return reinforcement; } double Castle::getArmyRecruitmentValue() const { return getAvailableArmy( GetKingdom().GetFunds() ).GetStrength(); } double Castle::getVisitValue( const Heroes & hero ) const { const Troops & heroArmy = hero.GetArmy(); Troops futureArmy( heroArmy ); const double heroArmyStrength = futureArmy.GetStrength(); Funds potentialFunds = GetKingdom().GetFunds(); double spellValue = 0; const int mageGuildLevel = GetLevelMageGuild(); if ( mageGuildLevel > 0 ) { const int spellPower = hero.GetPower(); const SpellStorage & guildSpells = mageguild.GetSpells( GetLevelMageGuild(), isLibraryBuild() ); for ( const Spell & spell : guildSpells ) { if ( hero.CanLearnSpell( spell ) && !hero.HaveSpell( spell, true ) ) { spellValue += spell.getStrategicValue( heroArmyStrength, hero.GetMaxSpellPoints(), spellPower ); } } if ( !hero.HaveSpellBook() && spellValue > 0 ) { const Funds payment = PaymentConditions::BuySpellBook(); if ( potentialFunds < payment || hero.GetBagArtifacts().isFull() ) { // Since the hero does not have a magic book and cannot buy any then spells are useless. spellValue = 0; } else { // The hero does not have a magic book but it can buy one. Let's make the visit little more valuable. potentialFunds -= payment; spellValue += 50; } } } for ( size_t i = 0; i < futureArmy.Size(); ++i ) { Troop * troop = futureArmy.GetTroop( i ); if ( troop != nullptr && troop->isValid() && troop->isAllowUpgrade() ) { if ( GetRace() != troop->GetRace() ) { continue; } if ( !isBuild( troop->GetUpgrade().GetDwelling() ) ) { continue; } const Funds payment = troop->GetTotalUpgradeCost(); if ( potentialFunds >= payment ) { potentialFunds -= payment; troop->Upgrade(); } } } const double upgradeStrength = futureArmy.GetStrength() - heroArmyStrength; return spellValue + upgradeStrength + futureArmy.getReinforcementValue( getAvailableArmy( potentialFunds ) ); } bool Castle::isExactBuildingBuilt( const uint32_t buildingToCheck ) const { assert( CountBits( buildingToCheck ) == 1 ); // This building is not built at all if ( ( building & buildingToCheck ) == 0 ) { return false; } const auto checkBuilding = [this]( const uint32_t expectedLevels, const uint32_t allPossibleLevels ) { // All expected levels should be built assert( ( building & expectedLevels ) == expectedLevels ); // Only the expected levels of all possible levels should be built return ( building & allPossibleLevels ) == expectedLevels; }; if ( buildingToCheck & BUILD_MAGEGUILD ) { switch ( buildingToCheck ) { case BUILD_MAGEGUILD1: return checkBuilding( BUILD_MAGEGUILD1, BUILD_MAGEGUILD ); case BUILD_MAGEGUILD2: return checkBuilding( BUILD_MAGEGUILD1 | BUILD_MAGEGUILD2, BUILD_MAGEGUILD ); case BUILD_MAGEGUILD3: return checkBuilding( BUILD_MAGEGUILD1 | BUILD_MAGEGUILD2 | BUILD_MAGEGUILD3, BUILD_MAGEGUILD ); case BUILD_MAGEGUILD4: return checkBuilding( BUILD_MAGEGUILD1 | BUILD_MAGEGUILD2 | BUILD_MAGEGUILD3 | BUILD_MAGEGUILD4, BUILD_MAGEGUILD ); case BUILD_MAGEGUILD5: return checkBuilding( BUILD_MAGEGUILD1 | BUILD_MAGEGUILD2 | BUILD_MAGEGUILD3 | BUILD_MAGEGUILD4 | BUILD_MAGEGUILD5, BUILD_MAGEGUILD ); default: assert( 0 ); } } if ( buildingToCheck & ( DWELLING_MONSTERS | DWELLING_UPGRADES ) ) { switch ( buildingToCheck ) { case DWELLING_MONSTER1: // Level 1 dwellings have no upgrades return true; case DWELLING_MONSTER2: return checkBuilding( DWELLING_MONSTER2, DWELLING_MONSTER2 | DWELLING_UPGRADE2 ); case DWELLING_MONSTER3: return checkBuilding( DWELLING_MONSTER3, DWELLING_MONSTER3 | DWELLING_UPGRADE3 ); case DWELLING_MONSTER4: return checkBuilding( DWELLING_MONSTER4, DWELLING_MONSTER4 | DWELLING_UPGRADE4 ); case DWELLING_MONSTER5: return checkBuilding( DWELLING_MONSTER5, DWELLING_MONSTER5 | DWELLING_UPGRADE5 ); case DWELLING_MONSTER6: // Take the Black Dragon upgrade (DWELLING_UPGRADE7) into account return checkBuilding( DWELLING_MONSTER6, DWELLING_MONSTER6 | DWELLING_UPGRADE6 | DWELLING_UPGRADE7 ); case DWELLING_UPGRADE2: return checkBuilding( DWELLING_MONSTER2 | DWELLING_UPGRADE2, DWELLING_MONSTER2 | DWELLING_UPGRADE2 ); case DWELLING_UPGRADE3: return checkBuilding( DWELLING_MONSTER3 | DWELLING_UPGRADE3, DWELLING_MONSTER3 | DWELLING_UPGRADE3 ); case DWELLING_UPGRADE4: return checkBuilding( DWELLING_MONSTER4 | DWELLING_UPGRADE4, DWELLING_MONSTER4 | DWELLING_UPGRADE4 ); case DWELLING_UPGRADE5: return checkBuilding( DWELLING_MONSTER5 | DWELLING_UPGRADE5, DWELLING_MONSTER5 | DWELLING_UPGRADE5 ); case DWELLING_UPGRADE6: // Take the Black Dragon upgrade (DWELLING_UPGRADE7) into account return checkBuilding( DWELLING_MONSTER6 | DWELLING_UPGRADE6, DWELLING_MONSTER6 | DWELLING_UPGRADE6 | DWELLING_UPGRADE7 ); case DWELLING_UPGRADE7: // Black Dragon upgrade return checkBuilding( DWELLING_MONSTER6 | DWELLING_UPGRADE6 | DWELLING_UPGRADE7, DWELLING_MONSTER6 | DWELLING_UPGRADE6 | DWELLING_UPGRADE7 ); default: assert( 0 ); } } return true; } uint32_t * Castle::GetDwelling( uint32_t dw ) { if ( isBuild( dw ) ) switch ( dw ) { case DWELLING_MONSTER1: return &dwelling[0]; case DWELLING_MONSTER2: case DWELLING_UPGRADE2: return &dwelling[1]; case DWELLING_MONSTER3: case DWELLING_UPGRADE3: return &dwelling[2]; case DWELLING_MONSTER4: case DWELLING_UPGRADE4: return &dwelling[3]; case DWELLING_MONSTER5: case DWELLING_UPGRADE5: return &dwelling[4]; case DWELLING_MONSTER6: case DWELLING_UPGRADE6: case DWELLING_UPGRADE7: return &dwelling[5]; default: break; } return nullptr; } void Castle::ActionNewDay() { EducateHeroes(); SetModes( ALLOWBUILD ); } void Castle::ActionNewWeek() { // Skip the first week if ( world.CountWeek() < 2 ) { return; } static const std::array allDwellings = { DWELLING_MONSTER1, DWELLING_MONSTER2, DWELLING_MONSTER3, DWELLING_MONSTER4, DWELLING_MONSTER5, DWELLING_MONSTER6, DWELLING_UPGRADE2, DWELLING_UPGRADE3, DWELLING_UPGRADE4, DWELLING_UPGRADE5, DWELLING_UPGRADE6, DWELLING_UPGRADE7 }; const bool isNeutral = ( GetColor() == Color::NONE ); const bool isPlagueWeek = ( world.GetWeekType().GetType() == WeekName::PLAGUE ); const bool isMonsterWeek = ( world.GetWeekType().GetType() == WeekName::MONSTERS ); if ( !isPlagueWeek ) { static const std::array basicDwellings = { DWELLING_MONSTER1, DWELLING_MONSTER2, DWELLING_MONSTER3, DWELLING_MONSTER4, DWELLING_MONSTER5, DWELLING_MONSTER6 }; // Normal population growth for ( const uint32_t dwellingId : basicDwellings ) { uint32_t * dwellingMonsters = GetDwelling( dwellingId ); if ( dwellingMonsters == nullptr ) { // Such dwelling (or its upgrade) has not been built continue; } uint32_t growth = Monster( race, GetActualDwelling( dwellingId ) ).GetGrown(); if ( building & BUILD_WELL ) { // The well is built. growth += GetGrownWell(); } if ( ( dwellingId == DWELLING_MONSTER1 ) && ( building & BUILD_WEL2 ) ) { growth += GetGrownWel2(); } if ( isNeutral ) { // Neutral towns always have 50% population growth. growth /= 2; } *dwellingMonsters += growth; } if ( isMonsterWeek && !world.BeginMonth() ) { for ( const uint32_t dwellingId : allDwellings ) { // A building of exactly this level should be built (its upgraded versions should not be considered) if ( !isExactBuildingBuilt( dwellingId ) ) { continue; } const Monster mons( race, dwellingId ); if ( !mons.isValid() || mons.GetID() != world.GetWeekType().GetMonster() ) { continue; } uint32_t * dwellingMonsters = GetDwelling( dwellingId ); assert( dwellingMonsters != nullptr ); *dwellingMonsters += GetGrownWeekOf(); break; } } if ( isNeutral ) { // Neutral towns have additional increase in garrison army. JoinRNDArmy(); // The probability that a town will get additional troops is 40%, castle always gets them if ( isCastle() || Rand::Get( 1, 100 ) <= 40 ) { JoinRNDArmy(); } } } // Monthly population growth bonuses should be calculated taking the weekly growth into account if ( world.BeginMonth() ) { assert( world.GetMonth() > 1 ); if ( isPlagueWeek ) { for ( uint32_t & dwellingRef : dwelling ) { dwellingRef /= 2; } } else if ( isMonsterWeek ) { for ( const uint32_t dwellingId : allDwellings ) { // A building of exactly this level should be built (its upgraded versions should not be considered) if ( !isExactBuildingBuilt( dwellingId ) ) { continue; } const Monster mons( race, dwellingId ); if ( !mons.isValid() || mons.GetID() != world.GetWeekType().GetMonster() ) { continue; } uint32_t * dwellingMonsters = GetDwelling( dwellingId ); assert( dwellingMonsters != nullptr ); *dwellingMonsters += *dwellingMonsters * GetGrownMonthOf() / 100; break; } } } } void Castle::ActionNewWeekAIBonuses() { if ( world.GetWeekType().GetType() == WeekName::PLAGUE ) { // No growth bonus can be applied. return; } if ( !isControlAI() ) { // No AI - no perks! return; } if ( GetColor() == Color::NONE ) { // Neutrals aren't considered as AI players. return; } static const std::array basicDwellings = { DWELLING_MONSTER1, DWELLING_MONSTER2, DWELLING_MONSTER3, DWELLING_MONSTER4, DWELLING_MONSTER5, DWELLING_MONSTER6 }; for ( const building_t dwellingId : basicDwellings ) { uint32_t * dwellingMonsters = GetDwelling( dwellingId ); if ( dwellingMonsters == nullptr ) { // Such dwelling (or its upgrade) has not been built. continue; } uint32_t originalGrowth = Monster( race, GetActualDwelling( dwellingId ) ).GetGrown(); if ( building & BUILD_WELL ) { originalGrowth += GetGrownWell(); } if ( ( dwellingId == DWELLING_MONSTER1 ) && ( building & BUILD_WEL2 ) ) { originalGrowth += GetGrownWel2(); } if ( originalGrowth == 0 ) { continue; } const long bonusGrowth = std::lround( originalGrowth * Difficulty::GetUnitGrowthBonusForAI( Game::getDifficulty(), Game::isCampaign(), dwellingId ) ); if ( bonusGrowth >= 0 ) { *dwellingMonsters += bonusGrowth; continue; } // If the original unit growth is non-zero, then the total unit growth after the application of penalties should be at least one unit const uint32_t growthPenalty = std::min( static_cast( -bonusGrowth ), originalGrowth - 1 ); assert( *dwellingMonsters > growthPenalty ); *dwellingMonsters -= growthPenalty; } } void Castle::ActionNewMonth() const { // Do nothing. } void Castle::ChangeColor( int cl ) { SetColor( cl ); army.SetColor( cl ); } int Castle::GetLevelMageGuild() const { if ( building & BUILD_MAGEGUILD5 ) return 5; else if ( building & BUILD_MAGEGUILD4 ) return 4; else if ( building & BUILD_MAGEGUILD3 ) return 3; else if ( building & BUILD_MAGEGUILD2 ) return 2; else if ( building & BUILD_MAGEGUILD1 ) return 1; return 0; } bool Castle::HaveLibraryCapability() const { return race == Race::WZRD; } bool Castle::isLibraryBuild() const { return race == Race::WZRD && isBuild( BUILD_SPEC ); } void Castle::MageGuildEducateHero( HeroBase & hero ) const { mageguild.educateHero( hero, GetLevelMageGuild(), isLibraryBuild() ); } bool Castle::isFortificationBuild() const { return race == Race::KNGT && isBuild( BUILD_SPEC ); } const char * Castle::GetStringBuilding( uint32_t build, int race ) { return fheroes2::getBuildingName( race, static_cast( build ) ); } const char * Castle::GetDescriptionBuilding( uint32_t build, int race ) { return fheroes2::getBuildingDescription( race, static_cast( build ) ); } bool Castle::AllowBuyHero( std::string * msg ) const { const Heroes * hero = world.GetHero( *this ); if ( hero != nullptr ) { if ( msg ) { *msg = _( "Cannot recruit - you already have a Hero in this town." ); } return false; } const Kingdom & myKingdom = GetKingdom(); if ( !myKingdom.AllowRecruitHero( false ) ) { if ( msg ) { *msg = _( "Cannot recruit - you have too many Heroes." ); } return false; } if ( !myKingdom.AllowRecruitHero( true ) ) { if ( msg ) { *msg = _( "Cannot afford a Hero" ); } return false; } return true; } Heroes * Castle::RecruitHero( Heroes * hero ) { if ( !hero || !AllowBuyHero() ) { return nullptr; } if ( world.GetHero( *this ) != nullptr ) { return nullptr; } if ( !hero->Recruit( *this ) ) { return nullptr; } Kingdom & currentKingdom = GetKingdom(); currentKingdom.OddFundsResource( PaymentConditions::RecruitHero() ); if ( GetLevelMageGuild() ) { MageGuildEducateHero( *hero ); } DEBUG_LOG( DBG_GAME, DBG_INFO, name << ", recruit: " << hero->GetName() ) return hero; } bool Castle::RecruitMonster( const Troop & troop, bool showDialog ) { if ( !troop.isValid() ) return false; int dwellingIndex = 0; switch ( troop.GetDwelling() ) { case DWELLING_MONSTER1: dwellingIndex = 0; break; case DWELLING_UPGRADE2: case DWELLING_MONSTER2: dwellingIndex = 1; break; case DWELLING_UPGRADE3: case DWELLING_MONSTER3: dwellingIndex = 2; break; case DWELLING_UPGRADE4: case DWELLING_MONSTER4: dwellingIndex = 3; break; case DWELLING_UPGRADE5: case DWELLING_MONSTER5: dwellingIndex = 4; break; case DWELLING_UPGRADE7: case DWELLING_UPGRADE6: case DWELLING_MONSTER6: dwellingIndex = 5; break; default: return false; } uint32_t count = troop.GetCount(); if ( dwelling[dwellingIndex] < count ) { count = dwelling[dwellingIndex]; } const Funds paymentCosts = troop.GetTotalCost(); Kingdom & kingdom = GetKingdom(); if ( !kingdom.AllowPayment( paymentCosts ) ) { return false; } if ( !GetArmy().JoinTroop( troop ) ) { Heroes * hero = world.GetHero( *this ); if ( hero == nullptr || !hero->GetArmy().JoinTroop( troop ) ) { if ( showDialog ) { fheroes2::showStandardTextMessage( "", _( "There is no room in the garrison for this army." ), Dialog::OK ); } return false; } } kingdom.OddFundsResource( paymentCosts ); dwelling[dwellingIndex] -= count; DEBUG_LOG( DBG_GAME, DBG_TRACE, name << " recruit: " << troop.GetMultiName() << "(" << count << ")" ) return true; } bool Castle::RecruitMonsterFromDwelling( uint32_t dw, uint32_t count, bool force ) { const Monster monster( race, GetActualDwelling( dw ) ); assert( count <= getRecruitLimit( monster, GetKingdom().GetFunds() ) ); const Troop troop( monster, std::min( count, getRecruitLimit( monster, GetKingdom().GetFunds() ) ) ); if ( RecruitMonster( troop, false ) ) { return true; } // TODO: before removing an existing stack of monsters try to upgrade them and also merge some stacks. if ( force ) { Troop * weak = GetArmy().GetWeakestTroop(); if ( weak && weak->GetStrength() < troop.GetStrength() ) { DEBUG_LOG( DBG_GAME, DBG_INFO, name << ": " << troop.GetCount() << " " << troop.GetMultiName() << " replace " << weak->GetCount() << " " << weak->GetMultiName() ) weak->Set( troop ); return true; } } return false; } void Castle::recruitBestAvailable( Funds budget ) { for ( uint32_t dw = DWELLING_MONSTER6; dw >= DWELLING_MONSTER1; dw >>= 1 ) { if ( !isBuild( dw ) ) { continue; } const Monster monster( race, GetActualDwelling( dw ) ); const uint32_t willRecruit = getRecruitLimit( monster, budget ); if ( willRecruit == 0 ) { continue; } if ( RecruitMonsterFromDwelling( dw, willRecruit, true ) ) { budget -= ( monster.GetCost() * willRecruit ); } } } uint32_t Castle::getRecruitLimit( const Monster & monster, const Funds & budget ) const { // validate that monster is from the current castle if ( monster.GetRace() != race ) return 0; const uint32_t available = getMonstersInDwelling( monster.GetDwelling() ); uint32_t willRecruit = budget.getLowestQuotient( monster.GetCost() ); if ( available < willRecruit ) return available; return willRecruit; } /* return current count monster in dwelling */ uint32_t Castle::getMonstersInDwelling( uint32_t dw ) const { switch ( dw ) { case DWELLING_MONSTER1: return dwelling[0]; case DWELLING_MONSTER2: case DWELLING_UPGRADE2: return dwelling[1]; case DWELLING_MONSTER3: case DWELLING_UPGRADE3: return dwelling[2]; case DWELLING_MONSTER4: case DWELLING_UPGRADE4: return dwelling[3]; case DWELLING_MONSTER5: case DWELLING_UPGRADE5: return dwelling[4]; case DWELLING_MONSTER6: case DWELLING_UPGRADE6: case DWELLING_UPGRADE7: return dwelling[5]; default: break; } return 0; } /* return requirement for building */ uint32_t Castle::GetBuildingRequirement( uint32_t build ) const { uint32_t requirement = 0; switch ( build ) { case BUILD_SPEC: switch ( race ) { case Race::WZRD: requirement |= BUILD_MAGEGUILD1; break; default: break; } break; case DWELLING_MONSTER2: switch ( race ) { case Race::KNGT: case Race::BARB: case Race::WZRD: case Race::WRLK: case Race::NECR: requirement |= DWELLING_MONSTER1; break; case Race::SORC: requirement |= DWELLING_MONSTER1; requirement |= BUILD_TAVERN; break; default: break; } break; case DWELLING_MONSTER3: switch ( race ) { case Race::KNGT: requirement |= DWELLING_MONSTER1; requirement |= BUILD_WELL; break; case Race::BARB: case Race::SORC: case Race::WZRD: case Race::WRLK: case Race::NECR: requirement |= DWELLING_MONSTER1; break; default: break; } break; case DWELLING_MONSTER4: switch ( race ) { case Race::KNGT: requirement |= DWELLING_MONSTER1; requirement |= BUILD_TAVERN; break; case Race::BARB: requirement |= DWELLING_MONSTER1; break; case Race::SORC: requirement |= DWELLING_MONSTER3; requirement |= BUILD_MAGEGUILD1; break; case Race::WZRD: case Race::WRLK: requirement |= DWELLING_MONSTER2; break; case Race::NECR: requirement |= DWELLING_MONSTER3; requirement |= BUILD_THIEVESGUILD; break; default: break; } break; case DWELLING_MONSTER5: switch ( race ) { case Race::KNGT: case Race::BARB: requirement |= DWELLING_MONSTER2; requirement |= DWELLING_MONSTER3; requirement |= DWELLING_MONSTER4; break; case Race::SORC: requirement |= DWELLING_MONSTER4; break; case Race::WRLK: requirement |= DWELLING_MONSTER3; break; case Race::WZRD: requirement |= DWELLING_MONSTER3; requirement |= BUILD_MAGEGUILD1; break; case Race::NECR: requirement |= DWELLING_MONSTER2; requirement |= BUILD_MAGEGUILD1; break; default: break; } break; case DWELLING_MONSTER6: switch ( race ) { case Race::KNGT: requirement |= DWELLING_MONSTER2; requirement |= DWELLING_MONSTER3; requirement |= DWELLING_MONSTER4; break; case Race::BARB: case Race::SORC: case Race::NECR: requirement |= DWELLING_MONSTER5; break; case Race::WRLK: case Race::WZRD: requirement |= DWELLING_MONSTER4; requirement |= DWELLING_MONSTER5; break; default: break; } break; case DWELLING_UPGRADE2: switch ( race ) { case Race::KNGT: case Race::BARB: requirement |= DWELLING_MONSTER2; requirement |= DWELLING_MONSTER3; requirement |= DWELLING_MONSTER4; break; case Race::SORC: requirement |= DWELLING_MONSTER2; requirement |= BUILD_WELL; break; case Race::NECR: requirement |= DWELLING_MONSTER2; break; default: break; } break; case DWELLING_UPGRADE3: switch ( race ) { case Race::KNGT: requirement |= DWELLING_MONSTER2; requirement |= DWELLING_MONSTER3; requirement |= DWELLING_MONSTER4; break; case Race::SORC: requirement |= DWELLING_MONSTER3; requirement |= DWELLING_MONSTER4; break; case Race::WZRD: requirement |= DWELLING_MONSTER3; requirement |= BUILD_WELL; break; case Race::NECR: requirement |= DWELLING_MONSTER3; break; default: break; } break; case DWELLING_UPGRADE4: switch ( race ) { case Race::KNGT: case Race::BARB: requirement |= DWELLING_MONSTER2; requirement |= DWELLING_MONSTER3; requirement |= DWELLING_MONSTER4; break; case Race::SORC: case Race::WRLK: case Race::NECR: requirement |= DWELLING_MONSTER4; break; default: break; } break; case DWELLING_UPGRADE5: switch ( race ) { case Race::KNGT: requirement |= DWELLING_MONSTER5; break; case Race::BARB: requirement |= DWELLING_MONSTER5; break; case Race::WZRD: requirement |= BUILD_SPEC; requirement |= DWELLING_MONSTER5; break; case Race::NECR: requirement |= BUILD_MAGEGUILD2; requirement |= DWELLING_MONSTER5; break; default: break; } break; case DWELLING_UPGRADE6: switch ( race ) { case Race::KNGT: requirement |= DWELLING_MONSTER6; break; case Race::WRLK: case Race::WZRD: requirement |= DWELLING_MONSTER6; break; default: break; } break; case DWELLING_UPGRADE7: if ( race == Race::WRLK ) requirement |= DWELLING_UPGRADE6; break; default: break; } return requirement; } int Castle::CheckBuyBuilding( const uint32_t build ) const { if ( build & building ) { return ALREADY_BUILT; } switch ( build ) { case BUILD_CASTLE: if ( !Modes( ALLOWCASTLE ) ) { return BUILD_DISABLE; } break; case BUILD_SHIPYARD: if ( !HasSeaAccess() ) { return BUILD_DISABLE; } break; case BUILD_SHRINE: if ( Race::NECR != GetRace() || ( Settings::Get().getCurrentMapInfo().version == GameVersion::SUCCESSION_WARS ) ) { return BUILD_DISABLE; } break; case BUILD_TAVERN: if ( Race::NECR == GetRace() ) { return BUILD_DISABLE; } break; default: break; } if ( build >= BUILD_MAGEGUILD2 && build <= BUILD_MAGEGUILD5 ) { const uint32_t prevMageGuild = build >> 1; if ( !( building & prevMageGuild ) ) { return BUILD_DISABLE; } } if ( !Modes( ALLOWBUILD ) ) { return NOT_TODAY; } if ( isCastle() ) { if ( build == BUILD_TENT ) { return BUILD_DISABLE; } } else { if ( build != BUILD_CASTLE ) { return NEED_CASTLE; } } switch ( build ) { case DWELLING_UPGRADE2: if ( ( Race::WRLK | Race::WZRD ) & race ) return UNKNOWN_UPGRADE; break; case DWELLING_UPGRADE3: if ( ( Race::BARB | Race::WRLK ) & race ) return UNKNOWN_UPGRADE; break; case DWELLING_UPGRADE4: if ( Race::WZRD & race ) return UNKNOWN_UPGRADE; break; case DWELLING_UPGRADE5: if ( ( Race::SORC | Race::WRLK ) & race ) return UNKNOWN_UPGRADE; break; case DWELLING_UPGRADE6: if ( ( Race::BARB | Race::SORC | Race::NECR ) & race ) return UNKNOWN_UPGRADE; break; case DWELLING_UPGRADE7: if ( Race::WRLK != race ) return UNKNOWN_UPGRADE; break; default: break; } const uint32_t requirement = Castle::GetBuildingRequirement( build ); for ( uint32_t itr = 0x00000001; itr; itr <<= 1 ) { if ( ( requirement & itr ) && !( building & itr ) ) { return REQUIRES_BUILD; } } if ( !GetKingdom().AllowPayment( PaymentConditions::BuyBuilding( race, build ) ) ) { return LACK_RESOURCES; } return ALLOW_BUILD; } int Castle::GetAllBuildingStatus( const Castle & castle ) { if ( !castle.Modes( ALLOWBUILD ) ) return NOT_TODAY; if ( !castle.isCastle() ) return NEED_CASTLE; const uint32_t rest = ~castle.building; for ( uint32_t itr = 0x00000001; itr; itr <<= 1 ) if ( ( rest & itr ) && ( ALLOW_BUILD == castle.CheckBuyBuilding( itr ) ) ) return ALLOW_BUILD; for ( uint32_t itr = 0x00000001; itr; itr <<= 1 ) if ( ( rest & itr ) && ( LACK_RESOURCES == castle.CheckBuyBuilding( itr ) ) ) return LACK_RESOURCES; for ( uint32_t itr = 0x00000001; itr; itr <<= 1 ) if ( ( rest & itr ) && ( REQUIRES_BUILD == castle.CheckBuyBuilding( itr ) ) ) return REQUIRES_BUILD; return UNKNOWN_COND; } bool Castle::AllowBuyBuilding( uint32_t build ) const { return ALLOW_BUILD == CheckBuyBuilding( build ); } bool Castle::BuyBuilding( uint32_t build ) { if ( !AllowBuyBuilding( build ) ) return false; GetKingdom().OddFundsResource( PaymentConditions::BuyBuilding( race, build ) ); // add build building |= build; switch ( build ) { case BUILD_CASTLE: building &= ~BUILD_TENT; Maps::UpdateCastleSprite( GetCenter(), race ); Maps::ClearFog( GetIndex(), GameStatic::getFogDiscoveryDistance( GameStatic::FogDiscoveryType::CASTLE ), GetColor() ); break; case BUILD_MAGEGUILD1: case BUILD_MAGEGUILD2: case BUILD_MAGEGUILD3: case BUILD_MAGEGUILD4: case BUILD_MAGEGUILD5: EducateHeroes(); break; case BUILD_CAPTAIN: captain.LoadDefaults( HeroBase::CAPTAIN, race ); captain.SetSpellPoints( captain.GetMaxSpellPoints() ); if ( GetLevelMageGuild() ) MageGuildEducateHero( captain ); break; case BUILD_SPEC: // build library if ( HaveLibraryCapability() ) EducateHeroes(); break; case DWELLING_MONSTER1: dwelling[0] = Monster( race, DWELLING_MONSTER1 ).GetGrown(); break; case DWELLING_MONSTER2: dwelling[1] = Monster( race, DWELLING_MONSTER2 ).GetGrown(); break; case DWELLING_MONSTER3: dwelling[2] = Monster( race, DWELLING_MONSTER3 ).GetGrown(); break; case DWELLING_MONSTER4: dwelling[3] = Monster( race, DWELLING_MONSTER4 ).GetGrown(); break; case DWELLING_MONSTER5: dwelling[4] = Monster( race, DWELLING_MONSTER5 ).GetGrown(); break; case DWELLING_MONSTER6: dwelling[5] = Monster( race, DWELLING_MONSTER6 ).GetGrown(); break; default: break; } // disable day build ResetModes( ALLOWBUILD ); DEBUG_LOG( DBG_GAME, DBG_INFO, name << " build " << GetStringBuilding( build, race ) ) return true; } /* draw image castle to position */ void Castle::DrawImageCastle( const fheroes2::Point & pt ) const { fheroes2::Display & display = fheroes2::Display::instance(); const Maps::Tiles & tile = world.GetTiles( GetIndex() ); uint32_t index = 0; fheroes2::Point dst_pt; // draw ground switch ( tile.GetGround() ) { case Maps::Ground::GRASS: index = 0; break; case Maps::Ground::SNOW: index = 10; break; case Maps::Ground::SWAMP: index = 20; break; case Maps::Ground::LAVA: index = 30; break; case Maps::Ground::DESERT: index = 40; break; case Maps::Ground::DIRT: index = 50; break; case Maps::Ground::WASTELAND: index = 60; break; case Maps::Ground::BEACH: index = 70; break; default: return; } for ( uint32_t ii = 0; ii < 5; ++ii ) { const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::OBJNTWBA, index + ii ); dst_pt.x = pt.x + ii * 32 + sprite.x(); dst_pt.y = pt.y + 3 * 32 + sprite.y(); fheroes2::Blit( sprite, display, dst_pt.x, dst_pt.y ); } for ( uint32_t ii = 0; ii < 5; ++ii ) { const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::OBJNTWBA, index + 5 + ii ); dst_pt.x = pt.x + ii * 32 + sprite.x(); dst_pt.y = pt.y + 4 * 32 + sprite.y(); fheroes2::Blit( sprite, display, dst_pt.x, dst_pt.y ); } // draw castle switch ( race ) { case Race::KNGT: index = 0; break; case Race::BARB: index = 32; break; case Race::SORC: index = 64; break; case Race::WRLK: index = 96; break; case Race::WZRD: index = 128; break; case Race::NECR: index = 160; break; default: break; } if ( !( BUILD_CASTLE & building ) ) index += 16; const fheroes2::Sprite & sprite2 = fheroes2::AGG::GetICN( ICN::OBJNTOWN, index ); dst_pt.x = pt.x + 2 * 32 + sprite2.x(); dst_pt.y = pt.y + sprite2.y(); fheroes2::Blit( sprite2, display, dst_pt.x, dst_pt.y ); for ( uint32_t ii = 0; ii < 5; ++ii ) { const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::OBJNTOWN, index + 1 + ii ); dst_pt.x = pt.x + ii * 32 + sprite.x(); dst_pt.y = pt.y + 32 + sprite.y(); fheroes2::Blit( sprite, display, dst_pt.x, dst_pt.y ); } for ( uint32_t ii = 0; ii < 5; ++ii ) { const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::OBJNTOWN, index + 6 + ii ); dst_pt.x = pt.x + ii * 32 + sprite.x(); dst_pt.y = pt.y + 2 * 32 + sprite.y(); fheroes2::Blit( sprite, display, dst_pt.x, dst_pt.y ); } for ( uint32_t ii = 0; ii < 5; ++ii ) { const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::OBJNTOWN, index + 11 + ii ); dst_pt.x = pt.x + ii * 32 + sprite.x(); dst_pt.y = pt.y + 3 * 32 + sprite.y(); fheroes2::Blit( sprite, display, dst_pt.x, dst_pt.y ); } } int Castle::GetICNBoat( int race ) { switch ( race ) { case Race::BARB: return ICN::TWNBBOAT; case Race::KNGT: return ICN::TWNKBOAT; case Race::NECR: return ICN::TWNNBOAT; case Race::SORC: return ICN::TWNSBOAT; case Race::WRLK: return ICN::TWNWBOAT; case Race::WZRD: return ICN::TWNZBOAT; default: break; } DEBUG_LOG( DBG_GAME, DBG_WARN, "return unknown" ) return ICN::UNKNOWN; } /* get building name ICN */ int Castle::GetICNBuilding( uint32_t build, int race ) { if ( Race::BARB == race ) { switch ( build ) { case BUILD_CASTLE: return ICN::TWNBCSTL; case BUILD_TENT: return ICN::TWNBTENT; case BUILD_SPEC: return ICN::TWNBSPEC; case BUILD_CAPTAIN: return ICN::TWNBCAPT; case BUILD_WEL2: return ICN::TWNBWEL2; case BUILD_LEFTTURRET: return ICN::TWNBLTUR; case BUILD_RIGHTTURRET: return ICN::TWNBRTUR; case BUILD_MOAT: return ICN::TWNBMOAT; case BUILD_MARKETPLACE: return ICN::TWNBMARK; case BUILD_THIEVESGUILD: return ICN::TWNBTHIE; case BUILD_TAVERN: return ICN::TWNBTVRN; case BUILD_WELL: return ICN::TWNBWELL; case BUILD_STATUE: return ICN::TWNBSTAT; case BUILD_SHIPYARD: return ICN::TWNBDOCK; case BUILD_MAGEGUILD1: case BUILD_MAGEGUILD2: case BUILD_MAGEGUILD3: case BUILD_MAGEGUILD4: case BUILD_MAGEGUILD5: return ICN::TWNBMAGE; case DWELLING_MONSTER1: return ICN::TWNBDW_0; case DWELLING_MONSTER2: return ICN::TWNBDW_1; case DWELLING_UPGRADE2: return ICN::TWNBUP_1; case DWELLING_MONSTER3: return ICN::TWNBDW_2; case DWELLING_MONSTER4: return ICN::TWNBDW_3; case DWELLING_UPGRADE4: return ICN::TWNBUP_3; case DWELLING_MONSTER5: return ICN::TWNBDW_4; case DWELLING_UPGRADE5: return ICN::TWNBUP_4; case DWELLING_MONSTER6: return ICN::TWNBDW_5; default: break; } } else if ( Race::KNGT == race ) { switch ( build ) { case BUILD_CASTLE: return ICN::TWNKCSTL; case BUILD_TENT: return ICN::TWNKTENT; case BUILD_SPEC: return ICN::TWNKSPEC; case BUILD_CAPTAIN: return ICN::TWNKCAPT; case BUILD_WEL2: return ICN::KNIGHT_CASTLE_RIGHT_FARM; case BUILD_LEFTTURRET: return ICN::TWNKLTUR; case BUILD_RIGHTTURRET: return ICN::TWNKRTUR; case BUILD_MOAT: return ICN::TWNKMOAT; case BUILD_MARKETPLACE: return ICN::TWNKMARK; case BUILD_THIEVESGUILD: return ICN::TWNKTHIE; case BUILD_TAVERN: return ICN::TWNKTVRN; case BUILD_WELL: return ICN::TWNKWELL; case BUILD_STATUE: return ICN::TWNKSTAT; case BUILD_SHIPYARD: return ICN::TWNKDOCK; case BUILD_MAGEGUILD1: case BUILD_MAGEGUILD2: case BUILD_MAGEGUILD3: case BUILD_MAGEGUILD4: case BUILD_MAGEGUILD5: return ICN::TWNKMAGE; case DWELLING_MONSTER1: return ICN::TWNKDW_0; case DWELLING_MONSTER2: return ICN::TWNKDW_1; case DWELLING_UPGRADE2: return ICN::TWNKUP_1; case DWELLING_MONSTER3: return ICN::TWNKDW_2; case DWELLING_UPGRADE3: return ICN::TWNKUP_2; case DWELLING_MONSTER4: return ICN::TWNKDW_3; case DWELLING_UPGRADE4: return ICN::TWNKUP_3; case DWELLING_MONSTER5: return ICN::TWNKDW_4; case DWELLING_UPGRADE5: return ICN::TWNKUP_4; case DWELLING_MONSTER6: return ICN::TWNKDW_5; case DWELLING_UPGRADE6: return ICN::TWNKUP_5; default: break; } } else if ( Race::NECR == race ) { switch ( build ) { case BUILD_CASTLE: return ICN::TWNNCSTL; case BUILD_TENT: return ICN::TWNNTENT; case BUILD_SPEC: return ICN::TWNNSPEC; case BUILD_CAPTAIN: return ICN::NECROMANCER_CASTLE_STANDALONE_CAPTAIN_QUARTERS; case BUILD_WEL2: return ICN::TWNNWEL2; case BUILD_LEFTTURRET: return ICN::TWNNLTUR; case BUILD_RIGHTTURRET: return ICN::TWNNRTUR; case BUILD_MOAT: return ICN::TWNNMOAT; case BUILD_MARKETPLACE: return ICN::TWNNMARK; case BUILD_THIEVESGUILD: return ICN::TWNNTHIE; // shrine case BUILD_SHRINE: return ICN::TWNNTVRN; case BUILD_WELL: return ICN::TWNNWELL; case BUILD_STATUE: return ICN::TWNNSTAT; case BUILD_SHIPYARD: return ICN::TWNNDOCK; case BUILD_MAGEGUILD1: case BUILD_MAGEGUILD2: case BUILD_MAGEGUILD3: case BUILD_MAGEGUILD4: case BUILD_MAGEGUILD5: return ICN::TWNNMAGE; case DWELLING_MONSTER1: return ICN::TWNNDW_0; case DWELLING_MONSTER2: return ICN::TWNNDW_1; case DWELLING_UPGRADE2: return ICN::TWNNUP_1; case DWELLING_MONSTER3: return ICN::TWNNDW_2; case DWELLING_UPGRADE3: return ICN::TWNNUP_2; case DWELLING_MONSTER4: return ICN::TWNNDW_3; case DWELLING_UPGRADE4: return ICN::TWNNUP_3; case DWELLING_MONSTER5: return ICN::TWNNDW_4; case DWELLING_UPGRADE5: return ICN::TWNNUP_4; case DWELLING_MONSTER6: return ICN::TWNNDW_5; default: break; } } else if ( Race::SORC == race ) { switch ( build ) { case BUILD_CASTLE: return ICN::TWNSCSTL; case BUILD_TENT: return ICN::TWNSTENT; case BUILD_SPEC: return ICN::TWNSSPEC; case BUILD_CAPTAIN: return ICN::TWNSCAPT; case BUILD_WEL2: return ICN::TWNSWEL2; case BUILD_LEFTTURRET: return ICN::TWNSLTUR; case BUILD_RIGHTTURRET: return ICN::TWNSRTUR; case BUILD_MOAT: return ICN::TWNSMOAT; case BUILD_MARKETPLACE: return ICN::TWNSMARK; case BUILD_THIEVESGUILD: return ICN::TWNSTHIE; case BUILD_TAVERN: return ICN::TWNSTVRN; case BUILD_WELL: return ICN::TWNSWELL; case BUILD_STATUE: return ICN::TWNSSTAT; case BUILD_SHIPYARD: return ICN::TWNSDOCK; case BUILD_MAGEGUILD1: case BUILD_MAGEGUILD2: case BUILD_MAGEGUILD3: case BUILD_MAGEGUILD4: case BUILD_MAGEGUILD5: return ICN::TWNSMAGE; case DWELLING_MONSTER1: return ICN::TWNSDW_0; case DWELLING_MONSTER2: return ICN::TWNSDW_1; case DWELLING_UPGRADE2: return ICN::TWNSUP_1; case DWELLING_MONSTER3: return ICN::TWNSDW_2; case DWELLING_UPGRADE3: return ICN::TWNSUP_2; case DWELLING_MONSTER4: return ICN::TWNSDW_3; case DWELLING_UPGRADE4: return ICN::TWNSUP_3; case DWELLING_MONSTER5: return ICN::TWNSDW_4; case DWELLING_MONSTER6: return ICN::TWNSDW_5; default: break; } } else if ( Race::WRLK == race ) { switch ( build ) { case BUILD_CASTLE: return ICN::TWNWCSTL; case BUILD_TENT: return ICN::TWNWTENT; case BUILD_SPEC: return ICN::TWNWSPEC; case BUILD_CAPTAIN: return ICN::TWNWCAPT; case BUILD_WEL2: return ICN::TWNWWEL2; case BUILD_LEFTTURRET: return ICN::TWNWLTUR; case BUILD_RIGHTTURRET: return ICN::TWNWRTUR; case BUILD_MOAT: return ICN::TWNWMOAT; case BUILD_MARKETPLACE: return ICN::TWNWMARK; case BUILD_THIEVESGUILD: return ICN::TWNWTHIE; case BUILD_TAVERN: return ICN::TWNWTVRN; case BUILD_WELL: return ICN::TWNWWELL; case BUILD_STATUE: return ICN::TWNWSTAT; case BUILD_SHIPYARD: return ICN::TWNWDOCK; case BUILD_MAGEGUILD1: case BUILD_MAGEGUILD2: case BUILD_MAGEGUILD3: case BUILD_MAGEGUILD4: case BUILD_MAGEGUILD5: return ICN::TWNWMAGE; case DWELLING_MONSTER1: return ICN::TWNWDW_0; case DWELLING_MONSTER2: return ICN::TWNWDW_1; case DWELLING_MONSTER3: return ICN::TWNWDW_2; case DWELLING_MONSTER4: return ICN::TWNWDW_3; case DWELLING_UPGRADE4: return ICN::TWNWUP_3; case DWELLING_MONSTER5: return ICN::TWNWDW_4; case DWELLING_MONSTER6: return ICN::TWNWDW_5; case DWELLING_UPGRADE6: return ICN::TWNWUP_5; case DWELLING_UPGRADE7: return ICN::TWNWUP5B; default: break; } } else if ( Race::WZRD == race ) { switch ( build ) { case BUILD_CASTLE: return ICN::TWNZCSTL; case BUILD_TENT: return ICN::TWNZTENT; case BUILD_SPEC: return ICN::TWNZSPEC; case BUILD_CAPTAIN: return ICN::TWNZCAPT; case BUILD_WEL2: return ICN::TWNZWEL2; case BUILD_LEFTTURRET: return ICN::TWNZLTUR; case BUILD_RIGHTTURRET: return ICN::TWNZRTUR; case BUILD_MOAT: return ICN::TWNZMOAT; case BUILD_MARKETPLACE: return ICN::TWNZMARK; case BUILD_THIEVESGUILD: return ICN::TWNZTHIE; case BUILD_TAVERN: return ICN::TWNZTVRN; case BUILD_WELL: return ICN::TWNZWELL; case BUILD_STATUE: return ICN::TWNZSTAT; case BUILD_SHIPYARD: return ICN::TWNZDOCK; case BUILD_MAGEGUILD1: case BUILD_MAGEGUILD2: case BUILD_MAGEGUILD3: case BUILD_MAGEGUILD4: case BUILD_MAGEGUILD5: return ICN::TWNZMAGE; case DWELLING_MONSTER1: return ICN::TWNZDW_0; case DWELLING_MONSTER2: return ICN::TWNZDW_1; case DWELLING_MONSTER3: return ICN::TWNZDW_2; case DWELLING_UPGRADE3: return ICN::TWNZUP_2; case DWELLING_MONSTER4: return ICN::TWNZDW_3; case DWELLING_MONSTER5: return ICN::TWNZDW_4; case DWELLING_UPGRADE5: return ICN::TWNZUP_4; case DWELLING_MONSTER6: return ICN::TWNZDW_5; case DWELLING_UPGRADE6: return ICN::TWNZUP_5; default: break; } } DEBUG_LOG( DBG_GAME, DBG_WARN, "return unknown" << ", race: " << Race::String( race ) << ", build: " << Castle::GetStringBuilding( build, race ) << ", " << build ) return ICN::UNKNOWN; } Heroes * Castle::GetHero() const { return world.GetHero( *this ); } bool Castle::HasSeaAccess() const { const fheroes2::Point possibleSeaTile{ center.x, center.y + 2 }; if ( !Maps::isValidAbsPoint( possibleSeaTile.x, possibleSeaTile.y ) ) { // If a tile below doesn't exist then no reason to check other tiles. return false; } auto doesTileAllowsToPutBoat = []( const Maps::Tiles & tile ) { if ( !tile.isWater() ) { // No water, no boat. return false; } if ( tile.getObjectIcnType() == MP2::OBJ_ICN_TYPE_UNKNOWN ) { // The main addon does not exist on this tile. // This means that all objects on this tile are not primary objects (like shadows or some parts of objects). return true; } // If this is an object's shadow or this is an action object that can be removed then it is possible to put a boat here. const MP2::MapObjectType objectType = tile.GetObject(); return MP2::isPickupObject( objectType ) || objectType == MP2::OBJ_BOAT || tile.isPassabilityTransparent(); }; const int32_t index = Maps::GetIndexFromAbsPoint( possibleSeaTile.x, possibleSeaTile.y ); if ( doesTileAllowsToPutBoat( world.GetTiles( index ) ) ) { return true; } if ( Maps::isValidAbsPoint( possibleSeaTile.x - 1, possibleSeaTile.y ) && doesTileAllowsToPutBoat( world.GetTiles( index - 1 ) ) ) { return true; } if ( Maps::isValidAbsPoint( possibleSeaTile.x + 1, possibleSeaTile.y ) && doesTileAllowsToPutBoat( world.GetTiles( index + 1 ) ) ) { return true; } return false; } bool Castle::HasBoatNearby() const { const fheroes2::Point possibleSeaTile{ center.x, center.y + 2 }; if ( !Maps::isValidAbsPoint( possibleSeaTile.x, possibleSeaTile.y ) ) { // If a tile below doesn't exist then no reason to check other tiles. return false; } auto doesTileHaveBoat = []( const Maps::Tiles & tile ) { if ( !tile.isWater() ) { // No water, no boat. return false; } const MP2::MapObjectType objectType = tile.GetObject(); return ( objectType == MP2::OBJ_BOAT || objectType == MP2::OBJ_HERO ); }; const int32_t index = Maps::GetIndexFromAbsPoint( possibleSeaTile.x, possibleSeaTile.y ); if ( doesTileHaveBoat( world.GetTiles( index ) ) ) { return true; } if ( Maps::isValidAbsPoint( possibleSeaTile.x - 1, possibleSeaTile.y ) && doesTileHaveBoat( world.GetTiles( index - 1 ) ) ) { return true; } if ( Maps::isValidAbsPoint( possibleSeaTile.x + 1, possibleSeaTile.y ) && doesTileHaveBoat( world.GetTiles( index + 1 ) ) ) { return true; } return false; } int32_t Castle::getTileIndexToPlaceBoat() const { const fheroes2::Point possibleSeaTile{ center.x, center.y + 2 }; if ( !Maps::isValidAbsPoint( possibleSeaTile.x, possibleSeaTile.y ) ) { // If a tile below doesn't exist then no reason to check other tiles. return -1; } auto doesTileAllowsToPutBoat = []( const Maps::Tiles & tile ) { if ( !tile.isWater() ) { // No water, no boat. return false; } // Mark the tile as worthy to a place a boat if the main addon does not exist on this tile. // This means that all objects on this tile are not primary objects (like shadows or some parts of objects). return ( tile.getObjectIcnType() == MP2::OBJ_ICN_TYPE_UNKNOWN || tile.isPassabilityTransparent() ); }; const int32_t index = Maps::GetIndexFromAbsPoint( possibleSeaTile.x, possibleSeaTile.y ); if ( doesTileAllowsToPutBoat( world.GetTiles( index ) ) ) { return index; } if ( Maps::isValidAbsPoint( possibleSeaTile.x - 1, possibleSeaTile.y ) && doesTileAllowsToPutBoat( world.GetTiles( index - 1 ) ) ) { return index - 1; } if ( Maps::isValidAbsPoint( possibleSeaTile.x + 1, possibleSeaTile.y ) && doesTileAllowsToPutBoat( world.GetTiles( index + 1 ) ) ) { return index + 1; } return -1; } uint32_t Castle::GetActualDwelling( const uint32_t buildId ) const { switch ( buildId ) { case DWELLING_MONSTER1: case DWELLING_UPGRADE2: case DWELLING_UPGRADE3: case DWELLING_UPGRADE4: case DWELLING_UPGRADE5: case DWELLING_UPGRADE7: return buildId; case DWELLING_MONSTER2: return building & DWELLING_UPGRADE2 ? DWELLING_UPGRADE2 : buildId; case DWELLING_MONSTER3: return building & DWELLING_UPGRADE3 ? DWELLING_UPGRADE3 : buildId; case DWELLING_MONSTER4: return building & DWELLING_UPGRADE4 ? DWELLING_UPGRADE4 : buildId; case DWELLING_MONSTER5: return building & DWELLING_UPGRADE5 ? DWELLING_UPGRADE5 : buildId; case DWELLING_MONSTER6: return building & DWELLING_UPGRADE7 ? DWELLING_UPGRADE7 : ( building & DWELLING_UPGRADE6 ? DWELLING_UPGRADE6 : buildId ); case DWELLING_UPGRADE6: return building & DWELLING_UPGRADE7 ? DWELLING_UPGRADE7 : buildId; default: break; } return BUILD_NOTHING; } uint32_t Castle::GetUpgradeBuilding( uint32_t build ) const { switch ( build ) { case BUILD_TENT: return BUILD_CASTLE; case BUILD_MAGEGUILD1: return BUILD_MAGEGUILD2; case BUILD_MAGEGUILD2: return BUILD_MAGEGUILD3; case BUILD_MAGEGUILD3: return BUILD_MAGEGUILD4; case BUILD_MAGEGUILD4: return BUILD_MAGEGUILD5; default: break; } if ( Race::BARB == race ) { switch ( build ) { case DWELLING_MONSTER2: return DWELLING_UPGRADE2; case DWELLING_MONSTER4: return DWELLING_UPGRADE4; case DWELLING_MONSTER5: return DWELLING_UPGRADE5; default: break; } } else if ( Race::KNGT == race ) { switch ( build ) { case DWELLING_MONSTER2: return DWELLING_UPGRADE2; case DWELLING_MONSTER3: return DWELLING_UPGRADE3; case DWELLING_MONSTER4: return DWELLING_UPGRADE4; case DWELLING_MONSTER5: return DWELLING_UPGRADE5; case DWELLING_MONSTER6: return DWELLING_UPGRADE6; default: break; } } else if ( Race::NECR == race ) { switch ( build ) { case DWELLING_MONSTER2: return DWELLING_UPGRADE2; case DWELLING_MONSTER3: return DWELLING_UPGRADE3; case DWELLING_MONSTER4: return DWELLING_UPGRADE4; case DWELLING_MONSTER5: return DWELLING_UPGRADE5; default: break; } } else if ( Race::SORC == race ) { switch ( build ) { case DWELLING_MONSTER2: return DWELLING_UPGRADE2; case DWELLING_MONSTER3: return DWELLING_UPGRADE3; case DWELLING_MONSTER4: return DWELLING_UPGRADE4; default: break; } } else if ( Race::WRLK == race ) { switch ( build ) { case DWELLING_MONSTER4: return DWELLING_UPGRADE4; case DWELLING_MONSTER6: return isBuild( DWELLING_UPGRADE6 ) ? DWELLING_UPGRADE7 : DWELLING_UPGRADE6; default: break; } } else if ( Race::WZRD == race ) { switch ( build ) { case DWELLING_MONSTER3: return DWELLING_UPGRADE3; case DWELLING_MONSTER5: return DWELLING_UPGRADE5; case DWELLING_MONSTER6: return DWELLING_UPGRADE6; default: break; } } return build; } bool Castle::PredicateIsCastle( const Castle * castle ) { return castle && castle->isCastle(); } bool Castle::PredicateIsTown( const Castle * castle ) { return castle && !castle->isCastle(); } bool Castle::PredicateIsBuildBuilding( const Castle * castle, const uint32_t building ) { return castle && castle->isBuild( building ); } std::string Castle::String() const { std::ostringstream os; const Heroes * hero = GetHero(); os << "name and type : " << name << " (" << Race::String( race ) << ")" << std::endl << "color : " << Color::String( GetColor() ) << std::endl << "dwellings : "; for ( uint32_t level = 0; level < 7; ++level ) { // there is no dwelling 7 if ( level != 6 && isBuild( DWELLING_MONSTER1 << level ) ) os << level + 1; if ( level > 0 && isBuild( DWELLING_UPGRADE2 << ( level - 1 ) ) ) os << "U, "; else os << ", "; } os << std::endl; os << "buildings : " << CountBuildings() << " (mage guild: " << GetLevelMageGuild() << ")" << std::endl << "coast/has boat : " << ( HasSeaAccess() ? "yes" : "no" ) << " / " << ( HasBoatNearby() ? "yes" : "no" ) << std::endl << "is castle : " << ( isCastle() ? "yes" : "no" ) << " (" << getBuildingValue() << ")" << std::endl << "army : " << army.String() << std::endl; if ( hero != nullptr ) { os << "hero army : " << hero->GetArmy().String() << std::endl; } return os.str(); } int Castle::GetAttackModificator( const std::string * ) const { return 0; } int Castle::GetDefenseModificator( const std::string * ) const { return 0; } int Castle::GetPowerModificator( std::string * strs ) const { int result = 0; if ( Race::NECR == race && isBuild( BUILD_SPEC ) ) { const int mod = 2; result += mod; if ( strs ) { strs->append( GetStringBuilding( BUILD_SPEC, race ) ); StringAppendModifiers( *strs, mod ); } } return result; } int Castle::GetKnowledgeModificator( const std::string * ) const { return 0; } int Castle::GetMoraleModificator( std::string * strs ) const { int result = Morale::NORMAL; // and tavern if ( isBuild( BUILD_TAVERN ) ) { const int mod = 1; result += mod; if ( strs ) { strs->append( GetStringBuilding( BUILD_TAVERN, race ) ); StringAppendModifiers( *strs, mod ); strs->append( "\n" ); } } // and barbarian coliseum if ( Race::BARB == race && isBuild( BUILD_SPEC ) ) { const int mod = 2; result += mod; if ( strs ) { strs->append( GetStringBuilding( BUILD_SPEC, race ) ); StringAppendModifiers( *strs, mod ); strs->append( "\n" ); } } return result; } int Castle::GetLuckModificator( std::string * strs ) const { int result = Luck::NORMAL; if ( Race::SORC == race && isBuild( BUILD_SPEC ) ) { const int mod = 2; result += mod; if ( strs ) { strs->append( Castle::GetStringBuilding( BUILD_SPEC, race ) ); StringAppendModifiers( *strs, mod ); strs->append( "\n" ); } } return result; } const Army & Castle::GetArmy() const { return army; } Army & Castle::GetArmy() { return army; } const Army & Castle::GetActualArmy() const { const Heroes * hero = world.GetHero( *this ); return hero ? hero->GetArmy() : army; } Army & Castle::GetActualArmy() { Heroes * hero = world.GetHero( *this ); return hero ? hero->GetArmy() : army; } double Castle::GetGarrisonStrength( const Heroes * attackingHero ) const { double totalStrength = 0; Heroes * hero = world.GetHero( *this ); // If there is a hero in the castle, then some of the garrison troops can join his army if // there is a place for them. Castle bonuses are applied to the resulting combined army. if ( hero ) { Army garrisonArmy; garrisonArmy.Assign( army ); Army combinedArmy( hero ); combinedArmy.Assign( hero->GetArmy() ); combinedArmy.ArrangeForCastleDefense( garrisonArmy ); totalStrength += combinedArmy.GetStrength(); } // Otherwise just use the garrison army strength. Castle bonuses are also applied. else { totalStrength += army.GetStrength(); } // Add castle bonuses if there are any troops defending the castle if ( isCastle() && totalStrength > 0.1 ) { const Battle::Tower tower( *this, Battle::TowerType::TWR_CENTER, 0 ); const double towerStr = tower.GetStrengthWithBonus( tower.GetAttackBonus(), 0 ); totalStrength += towerStr; if ( isBuild( BUILD_LEFTTURRET ) ) { totalStrength += towerStr / 2; } if ( isBuild( BUILD_RIGHTTURRET ) ) { totalStrength += towerStr / 2; } if ( attackingHero && !attackingHero->HasSecondarySkill( Skill::Secondary::BALLISTICS ) && attackingHero->GetArmy().isMeleeDominantArmy() ) { // Heavy penalty if the attacking hero does not have a ballistic skill, and his army is based on melee infantry totalStrength *= isBuild( BUILD_MOAT ) ? 1.45 : 1.25; } else { totalStrength *= isBuild( BUILD_MOAT ) ? 1.2 : 1.15; } } return totalStrength; } bool Castle::AllowBuyBoat( const bool checkPayment ) const { if ( !isBuild( BUILD_SHIPYARD ) ) { return false; } if ( checkPayment && !GetKingdom().AllowPayment( PaymentConditions::BuyBoat() ) ) { return false; } if ( HasBoatNearby() ) { return false; } return getTileIndexToPlaceBoat() >= 0; } bool Castle::BuyBoat() const { if ( !AllowBuyBoat( true ) ) { return false; } // If this assertion blows up then you didn't even check conditions to build a shipyard! assert( HasSeaAccess() ); const int32_t index = getTileIndexToPlaceBoat(); if ( index < 0 ) { return false; } if ( isControlHuman() ) { AudioManager::PlaySound( M82::BUILDTWN ); } Kingdom & kingdom = GetKingdom(); kingdom.OddFundsResource( PaymentConditions::BuyBoat() ); world.GetTiles( index ).setBoat( Direction::RIGHT, kingdom.GetColor() ); return true; } void Castle::setName( const std::set & usedNames ) { assert( name.empty() ); std::vector shuffledCastleNames( defaultCastleNames.begin(), defaultCastleNames.end() ); Rand::Shuffle( shuffledCastleNames ); for ( const char * originalName : shuffledCastleNames ) { const char * translatedCastleName = _( originalName ); if ( usedNames.count( translatedCastleName ) < 1 ) { name = translatedCastleName; return; } } // How is it possible that we're out of castle names? assert( 0 ); } int Castle::GetControl() const { /* gray towns: AI control */ return GetColor() & Color::ALL ? GetKingdom().GetControl() : CONTROL_AI; } bool Castle::isNecromancyShrineBuild() const { return race == Race::NECR && ( BUILD_SHRINE & building ); } uint32_t Castle::GetGrownWell() { return GameStatic::GetCastleGrownWell(); } uint32_t Castle::GetGrownWel2() { return GameStatic::GetCastleGrownWel2(); } uint32_t Castle::GetGrownWeekOf() { return GameStatic::GetCastleGrownWeekOf(); } uint32_t Castle::GetGrownMonthOf() { return GameStatic::GetCastleGrownMonthOf(); } void Castle::Scout() const { Maps::ClearFog( GetIndex(), GameStatic::getFogDiscoveryDistance( GameStatic::FogDiscoveryType::CASTLE ), GetColor() ); } void Castle::JoinRNDArmy() { const uint32_t timeModifier = world.CountDay() / 10; const uint32_t reinforcementQuality = Rand::Get( 1, 15 ) + timeModifier; uint32_t count = timeModifier / 2; uint32_t dwellingType = DWELLING_MONSTER1; if ( reinforcementQuality > 15 ) { dwellingType = DWELLING_MONSTER5; count += 1; } else if ( reinforcementQuality > 13 ) { dwellingType = DWELLING_MONSTER4; count += Rand::Get( 1, 3 ); } else if ( reinforcementQuality > 10 ) { dwellingType = DWELLING_MONSTER3; count += Rand::Get( 3, 5 ); } else if ( reinforcementQuality > 5 ) { dwellingType = DWELLING_MONSTER2; count += Rand::Get( 5, 7 ); } else { count += Rand::Get( 8, 15 ); } army.JoinTroop( Monster( race, dwellingType ), count, false ); } void Castle::ActionPreBattle() { if ( isControlAI() ) { AI::Get().CastlePreBattle( *this ); return; } Heroes * hero = world.GetHero( *this ); if ( hero == nullptr ) { return; } hero->GetArmy().ArrangeForCastleDefense( army ); } void Castle::ActionAfterBattle( bool attacker_wins ) { if ( attacker_wins ) { army.Clean(); ResetModes( CUSTOMARMY ); } if ( isControlAI() ) AI::Get().CastleAfterBattle( *this, attacker_wins ); } Castle * VecCastles::GetFirstCastle() const { const_iterator it = std::find_if( begin(), end(), []( const Castle * castle ) { return castle->isCastle(); } ); return end() != it ? *it : nullptr; } void VecCastles::ChangeColors( int col1, int col2 ) { for ( iterator it = begin(); it != end(); ++it ) if ( ( *it )->GetColor() == col1 ) ( *it )->ChangeColor( col2 ); } AllCastles::AllCastles() { // reserve memory _castles.reserve( maximumCastles ); } AllCastles::~AllCastles() { Clear(); } void AllCastles::Init() { Clear(); } void AllCastles::Clear() { for ( auto it = begin(); it != end(); ++it ) delete *it; _castles.clear(); _castleTiles.clear(); } void AllCastles::AddCastle( Castle * castle ) { _castles.push_back( castle ); /* Register position of all castle elements on the map Castle element positions are: + +++++ +++++ ++X++ ++ ++ where X is the main castle position + are tiles that are considered part of the castle for the Get() method */ const size_t id = _castles.size() - 1; const fheroes2::Point & center = castle->GetCenter(); // Castles are added from top to bottom, from left to right. // Tiles containing castle ID cannot be overwritten. for ( int32_t y = -2; y <= 1; ++y ) { for ( int32_t x = -2; x <= 2; ++x ) { if ( y == 1 && x == 0 ) { // Do not mark a tile below castle's entrance as castle. continue; } const auto [dummy, inserted] = _castleTiles.try_emplace( center + fheroes2::Point( x, y ), id ); if ( !inserted ) { DEBUG_LOG( DBG_GAME, DBG_INFO, "Tile [" << center.x + x << ", " << center.y + y << "] is occupied by another castle" ) } } } const auto [dummy, inserted] = _castleTiles.try_emplace( center + fheroes2::Point( 0, -3 ), id ); if ( !inserted ) { DEBUG_LOG( DBG_GAME, DBG_INFO, "Tile [" << center.x << ", " << center.y - 3 << "] is occupied by another castle" ) } } void AllCastles::Scout( int colors ) const { for ( auto it = begin(); it != end(); ++it ) if ( colors & ( *it )->GetColor() ) ( *it )->Scout(); } /* pack castle */ StreamBase & operator<<( StreamBase & msg, const Castle & castle ) { const ColorBase & color = castle; msg << static_cast( castle ) << castle.modes << castle.race << castle.building << castle.captain << color << castle.name << castle.mageguild << static_cast( CASTLEMAXMONSTER ); for ( uint32_t ii = 0; ii < CASTLEMAXMONSTER; ++ii ) msg << castle.dwelling[ii]; return msg << castle.army; } /* unpack castle */ StreamBase & operator>>( StreamBase & msg, Castle & castle ) { ColorBase & color = castle; uint32_t dwellingcount; msg >> static_cast( castle ) >> castle.modes >> castle.race >> castle.building >> castle.captain >> color >> castle.name >> castle.mageguild; msg >> dwellingcount; for ( uint32_t ii = 0; ii < dwellingcount; ++ii ) msg >> castle.dwelling[ii]; msg >> castle.army; castle.army.SetCommander( &castle.captain ); return msg; } StreamBase & operator<<( StreamBase & msg, const VecCastles & castles ) { msg << static_cast( castles.size() ); for ( auto it = castles.begin(); it != castles.end(); ++it ) msg << ( *it ? ( *it )->GetIndex() : static_cast( -1 ) ); return msg; } StreamBase & operator>>( StreamBase & msg, VecCastles & castles ) { int32_t index; uint32_t size; msg >> size; castles.resize( size, nullptr ); for ( auto it = castles.begin(); it != castles.end(); ++it ) { msg >> index; *it = ( index < 0 ? nullptr : world.getCastleEntrance( Maps::GetPoint( index ) ) ); assert( *it != nullptr ); } return msg; } StreamBase & operator<<( StreamBase & msg, const AllCastles & castles ) { msg << static_cast( castles.Size() ); for ( const Castle * castle : castles ) msg << *castle; return msg; } StreamBase & operator>>( StreamBase & msg, AllCastles & castles ) { uint32_t size; msg >> size; castles.Clear(); for ( uint32_t i = 0; i < size; ++i ) { Castle * castle = new Castle(); msg >> *castle; castles.AddCastle( castle ); } return msg; } std::string Castle::GetStringBuilding( uint32_t build ) const { return GetStringBuilding( build, GetRace() ); } std::string Castle::GetDescriptionBuilding( uint32_t build ) const { std::string res = GetDescriptionBuilding( build, GetRace() ); switch ( build ) { case BUILD_WELL: StringReplace( res, "%{count}", GetGrownWell() ); break; case BUILD_WEL2: StringReplace( res, "%{count}", GetGrownWel2() ); break; case BUILD_CASTLE: { StringReplace( res, "%{count}", ProfitConditions::FromBuilding( BUILD_CASTLE, race ).gold ); if ( isBuild( BUILD_CASTLE ) ) { res.append( "\n\n" ); res.append( Battle::Tower::GetInfo( *this ) ); } if ( isBuild( BUILD_MOAT ) ) { res.append( "\n\n" ); res.append( Battle::Board::GetMoatInfo() ); } break; } case BUILD_SPEC: case BUILD_STATUE: { const Funds profit = ProfitConditions::FromBuilding( build, GetRace() ); StringReplace( res, "%{count}", profit.gold ); break; } default: break; } return res; } fheroes2-1.0.12+dfsg/src/fheroes2/castle/castle.h000066400000000000000000000360141456075706000214500ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2CASTLE_H #define H2CASTLE_H #include #include #include #include #include #include #include #include #include #include "army.h" #include "bitmodes.h" #include "captain.h" #include "color.h" #include "gamedefs.h" #include "mageguild.h" #include "math_base.h" #include "monster.h" #include "players.h" #include "position.h" namespace fheroes2 { class RandomMonsterAnimation; class Image; } class Funds; class HeroBase; class Heroes; class StreamBase; class Troop; enum building_t : uint32_t { BUILD_NOTHING = 0x00000000, BUILD_THIEVESGUILD = 0x00000001, BUILD_TAVERN = 0x00000002, BUILD_SHIPYARD = 0x00000004, BUILD_WELL = 0x00000008, BUILD_STATUE = 0x00000010, BUILD_LEFTTURRET = 0x00000020, BUILD_RIGHTTURRET = 0x00000040, BUILD_MARKETPLACE = 0x00000080, BUILD_WEL2 = 0x00000100, // Farm, Garbage He, Crystal Gar, Waterfall, Orchard, Skull Pile BUILD_MOAT = 0x00000200, BUILD_SPEC = 0x00000400, // Fortification, Coliseum, Rainbow, Dungeon, Library, Storm BUILD_CASTLE = 0x00000800, BUILD_CAPTAIN = 0x00001000, BUILD_SHRINE = 0x00002000, BUILD_MAGEGUILD1 = 0x00004000, BUILD_MAGEGUILD2 = 0x00008000, BUILD_MAGEGUILD3 = 0x00010000, BUILD_MAGEGUILD4 = 0x00020000, BUILD_MAGEGUILD5 = 0x00040000, BUILD_MAGEGUILD = BUILD_MAGEGUILD1 | BUILD_MAGEGUILD2 | BUILD_MAGEGUILD3 | BUILD_MAGEGUILD4 | BUILD_MAGEGUILD5, BUILD_TENT = 0x00080000, // deprecated DWELLING_MONSTER1 = 0x00100000, DWELLING_MONSTER2 = 0x00200000, DWELLING_MONSTER3 = 0x00400000, DWELLING_MONSTER4 = 0x00800000, DWELLING_MONSTER5 = 0x01000000, DWELLING_MONSTER6 = 0x02000000, DWELLING_MONSTERS = DWELLING_MONSTER1 | DWELLING_MONSTER2 | DWELLING_MONSTER3 | DWELLING_MONSTER4 | DWELLING_MONSTER5 | DWELLING_MONSTER6, DWELLING_UPGRADE2 = 0x04000000, DWELLING_UPGRADE3 = 0x08000000, DWELLING_UPGRADE4 = 0x10000000, DWELLING_UPGRADE5 = 0x20000000, DWELLING_UPGRADE6 = 0x40000000, DWELLING_UPGRADE7 = 0x80000000, // black dragon DWELLING_UPGRADES = DWELLING_UPGRADE2 | DWELLING_UPGRADE3 | DWELLING_UPGRADE4 | DWELLING_UPGRADE5 | DWELLING_UPGRADE6 | DWELLING_UPGRADE7 }; enum buildcond_t { NOT_TODAY = -1, ALREADY_BUILT = -2, NEED_CASTLE = -3, BUILD_DISABLE = -4, UNKNOWN_UPGRADE = -5, REQUIRES_BUILD = -6, LACK_RESOURCES = -7, UNKNOWN_COND = 0, ALLOW_BUILD = 1 }; class Castle : public MapPosition, public BitModes, public ColorBase, public Control { public: enum : uint32_t { ALLOWCASTLE = 0x00000002, CUSTOMARMY = 0x00000004, ALLOWBUILD = 0x00000008 }; enum class CastleDialogReturnValue : int { DoNothing, Close, // Close the dialog. NextCastle, // Open main dialog of the next castle. PreviousCastle, // Open main dialog of the previous castle. NextCostructionWindow, // Open construction dialog of the next castle. PreviousCostructionWindow // Open construction dialog of the previous castle. }; Castle(); Castle( int32_t cx, int32_t cy, int rc ); Castle( const Castle & ) = delete; ~Castle() override = default; Castle & operator=( const Castle & ) = delete; void LoadFromMP2( const std::vector & data ); Captain & GetCaptain() { return captain; } const Captain & GetCaptain() const { return captain; } bool isCastle() const { return ( building & BUILD_CASTLE ) != 0; } bool HasSeaAccess() const; bool HasBoatNearby() const; // Returns a tile ID where it is possible to place a boat or -1 if it is not. int32_t getTileIndexToPlaceBoat() const; bool AllowBuyHero( std::string * = nullptr ) const; bool isPosition( const fheroes2::Point & pt ) const override; bool isNecromancyShrineBuild() const; uint32_t CountBuildings() const; Heroes * RecruitHero( Heroes * ); Heroes * GetHero() const; int GetRace() const { return race; } const std::string & GetName() const { return name; } // This method must be called only at the time of map loading and only for castles with empty names. void setName( const std::set & usedNames ); int GetControl() const override; int GetLevelMageGuild() const; const MageGuild & GetMageGuild() const { return mageguild; } bool HaveLibraryCapability() const; bool isLibraryBuild() const; void MageGuildEducateHero( HeroBase & ) const; bool isFortificationBuild() const; const Army & GetArmy() const; Army & GetArmy(); const Army & GetActualArmy() const; Army & GetActualArmy(); uint32_t getMonstersInDwelling( uint32_t ) const; // Returns the garrison strength estimation calculated as if this castle had really been attacked, including // an estimate of the strength of the combined army consisting of the garrison and the guest hero's troops // (if present), castle-specific bonuses from moat, towers and so on, relative to the attacking hero's abilities // (if the 'attackingHero' is not 'nullptr'). See the implementation for details. double GetGarrisonStrength( const Heroes * attackingHero ) const; double GetGarrisonStrength( const Heroes & attackingHero ) const { return GetGarrisonStrength( &attackingHero ); } // Returns the correct dwelling type available in the castle. BUILD_NOTHING is returned if this is not a dwelling. uint32_t GetActualDwelling( const uint32_t buildId ) const; // Returns true in case of successful recruitment. bool RecruitMonster( const Troop & troop, bool showDialog = true ); void recruitBestAvailable( Funds budget ); uint32_t getRecruitLimit( const Monster & monster, const Funds & budget ) const; int getBuildingValue() const; // Used only for AI. double getArmyRecruitmentValue() const; double getVisitValue( const Heroes & hero ) const; void ChangeColor( int ); void ActionNewDay(); void ActionNewWeek(); void ActionNewWeekAIBonuses(); void ActionNewMonth() const; void ActionPreBattle(); void ActionAfterBattle( bool attacker_wins ); void DrawImageCastle( const fheroes2::Point & pt ) const; CastleDialogReturnValue OpenDialog( const bool openConstructionWindow, const bool fade, const bool renderBackgroundDialog ); int GetAttackModificator( const std::string * ) const; int GetDefenseModificator( const std::string * ) const; int GetPowerModificator( std::string * ) const; int GetKnowledgeModificator( const std::string * ) const; int GetMoraleModificator( std::string * ) const; int GetLuckModificator( std::string * ) const; bool AllowBuyBuilding( uint32_t ) const; bool isBuild( uint32_t bd ) const { return ( building & bd ) != 0; } bool BuyBuilding( uint32_t ); uint32_t GetBuildingRequirement( uint32_t ) const; int CheckBuyBuilding( const uint32_t build ) const; static int GetAllBuildingStatus( const Castle & ); bool AllowBuyBoat( const bool checkPayment ) const; bool BuyBoat() const; void Scout() const; std::string GetStringBuilding( uint32_t ) const; std::string GetDescriptionBuilding( uint32_t ) const; static const char * GetStringBuilding( uint32_t, int race ); static const char * GetDescriptionBuilding( uint32_t, int race ); static int GetICNBuilding( uint32_t, int race ); static int GetICNBoat( int race ); uint32_t GetUpgradeBuilding( uint32_t ) const; static bool PredicateIsCastle( const Castle * ); static bool PredicateIsTown( const Castle * ); static bool PredicateIsBuildBuilding( const Castle * castle, const uint32_t building ); static uint32_t GetGrownWell(); static uint32_t GetGrownWel2(); static uint32_t GetGrownWeekOf(); static uint32_t GetGrownMonthOf(); std::string String() const; int DialogBuyHero( const Heroes * ) const; int DialogBuyCastle( bool fixed = true ) const; Troops getAvailableArmy( Funds potentialBudget ) const; private: enum class ConstructionDialogResult : int { DoNothing, NextConstructionWindow, // Open construction dialog for the next castle. PrevConstructionWindow, // Open construction dialog for the previous castle. Build, // Build something. RecruitHero // Recruit a hero. }; // Checks whether this particular building is currently built in the castle (unlike // the isBuild(), upgraded versions of the same building are not taken into account) bool isExactBuildingBuilt( const uint32_t buildingToCheck ) const; uint32_t * GetDwelling( uint32_t dw ); void EducateHeroes(); ConstructionDialogResult openConstructionDialog( uint32_t & dwellingTobuild ); void OpenTavern() const; void OpenWell(); void OpenMageGuild( const Heroes * hero ) const; void JoinRNDArmy(); void PostLoad(); void _wellRedrawAvailableMonsters( const uint32_t dwellingType, const bool restoreBackground, fheroes2::Image & background ) const; void _wellRedrawBackground( fheroes2::Image & background ) const; void _wellRedrawMonsterAnimation( const fheroes2::Rect & roi, std::array & monsterAnimInfo ) const; // Recruit maximum monsters from the castle. Returns 'true' if the recruit was made. bool _recruitCastleMax( const Troops & currentCastleArmy ); bool RecruitMonsterFromDwelling( uint32_t dw, uint32_t count, bool force = false ); friend StreamBase & operator<<( StreamBase &, const Castle & ); friend StreamBase & operator>>( StreamBase &, Castle & ); int race; uint32_t building; Captain captain; std::string name; MageGuild mageguild; uint32_t dwelling[CASTLEMAXMONSTER]; Army army; }; namespace CastleDialog { // Class used for fading animation class FadeBuilding { public: FadeBuilding() : _alpha( 255 ) , _build( BUILD_NOTHING ) {} void StartFadeBuilding( const uint32_t build ); bool UpdateFadeBuilding(); bool IsFadeDone() const { return _alpha == 255; } void StopFadeBuilding(); uint8_t GetAlpha() const { return _alpha; } uint32_t GetBuild() const { return _build; } private: uint8_t _alpha; uint32_t _build; }; struct BuildingRenderInfo { BuildingRenderInfo( building_t b, const fheroes2::Rect & r ) : id( b ) , coord( r ) {} bool operator==( uint32_t b ) const { return b == static_cast( id ); } building_t id; fheroes2::Rect coord; }; struct CacheBuildings : std::vector { CacheBuildings( const Castle &, const fheroes2::Point & ); }; void RedrawAllBuilding( const Castle & castle, const fheroes2::Point & dst_pt, const CacheBuildings & orders, const CastleDialog::FadeBuilding & alphaBuilding, const uint32_t animationIndex ); void CastleRedrawBuilding( const Castle &, const fheroes2::Point &, uint32_t build, uint32_t frame, uint8_t alpha = 255 ); void CastleRedrawBuildingExtended( const Castle &, const fheroes2::Point &, uint32_t build, uint32_t frame, uint8_t alpha = 255 ); } struct VecCastles : public std::vector { Castle * GetFirstCastle() const; void ChangeColors( int, int ); }; class AllCastles { public: AllCastles(); AllCastles( const AllCastles & ) = delete; ~AllCastles(); AllCastles & operator=( const AllCastles & ) = delete; void Init(); void Clear(); void AddCastle( Castle * castle ); Castle * Get( const fheroes2::Point & position ) const { auto iter = _castleTiles.find( position ); if ( iter == _castleTiles.end() ) return nullptr; return _castles[iter->second]; } void Scout( int ) const; void NewDay() { std::for_each( _castles.begin(), _castles.end(), []( Castle * castle ) { castle->ActionNewDay(); } ); } void NewWeek() { std::for_each( _castles.begin(), _castles.end(), []( Castle * castle ) { castle->ActionNewWeek(); } ); } void NewWeekAI() { std::for_each( _castles.begin(), _castles.end(), []( Castle * castle ) { castle->ActionNewWeekAIBonuses(); } ); } void NewMonth() { std::for_each( _castles.begin(), _castles.end(), []( const Castle * castle ) { castle->ActionNewMonth(); } ); } // begin/end methods so we can iterate through the elements std::vector::const_iterator begin() const { return _castles.begin(); } std::vector::const_iterator end() const { return _castles.end(); } size_t Size() const { return _castles.size(); } private: std::vector _castles; std::map _castleTiles; }; StreamBase & operator<<( StreamBase &, const VecCastles & ); StreamBase & operator>>( StreamBase &, VecCastles & ); StreamBase & operator<<( StreamBase &, const AllCastles & ); StreamBase & operator>>( StreamBase &, AllCastles & ); StreamBase & operator<<( StreamBase &, const Castle & ); StreamBase & operator>>( StreamBase &, Castle & ); #endif fheroes2-1.0.12+dfsg/src/fheroes2/castle/castle_building.cpp000066400000000000000000000420041456075706000236540ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include "agg_image.h" #include "castle.h" #include "castle_building_info.h" #include "game_delays.h" #include "icn.h" #include "image.h" #include "maps_fileinfo.h" #include "math_base.h" #include "race.h" #include "screen.h" #include "settings.h" #include "ui_castle.h" namespace { int getTownIcnId( const int race ) { switch ( race ) { case Race::KNGT: return ICN::TOWNBKG0; case Race::BARB: return ICN::TOWNBKG1; case Race::SORC: return ICN::TOWNBKG2; case Race::WRLK: return ICN::TOWNBKG3; case Race::WZRD: return ICN::TOWNBKG4; case Race::NECR: return ICN::TOWNBKG5; default: // Have you added a new race? Add the logic for it! assert( 0 ); break; } return ICN::UNKNOWN; } fheroes2::Rect CastleGetMaxArea( const Castle & castle, const fheroes2::Point & top ) { const fheroes2::Sprite & townbkg = fheroes2::AGG::GetICN( getTownIcnId( castle.GetRace() ), 0 ); return { top.x, top.y, townbkg.width(), townbkg.height() }; } bool isBuildingConnectionNeeded( const Castle & castle, const uint32_t buildId, const bool constructionInProgress ) { const int race = castle.GetRace(); if ( race == Race::BARB ) { if ( buildId & BUILD_MAGEGUILD ) { const int mageGuildLevel = castle.GetLevelMageGuild(); if ( constructionInProgress ) { return mageGuildLevel == 0 || buildId > ( BUILD_MAGEGUILD1 << ( mageGuildLevel - 1 ) ); } assert( mageGuildLevel > 0 ); return buildId == ( BUILD_MAGEGUILD1 << ( mageGuildLevel - 1 ) ); } if ( buildId == BUILD_THIEVESGUILD ) { return true; } } else if ( race == Race::NECR && buildId == BUILD_CAPTAIN ) { return true; } return false; } void redrawBuildingConnection( const Castle & castle, const fheroes2::Point & position, const uint32_t buildId, const uint8_t alpha = 255 ) { const fheroes2::Rect & roi = CastleGetMaxArea( castle, position ); const bool constructionInProgress = alpha < 255; const int race = castle.GetRace(); if ( race == Race::BARB ) { if ( buildId & BUILD_MAGEGUILD || buildId == BUILD_SPEC ) { if ( buildId & BUILD_MAGEGUILD && ( ( !constructionInProgress && !castle.isBuild( buildId ) ) || ( !castle.isBuild( BUILD_SPEC ) ) ) ) { return; } if ( buildId == BUILD_SPEC && ( ( !constructionInProgress && !castle.isBuild( buildId ) ) || ( !castle.isBuild( BUILD_MAGEGUILD1 ) ) ) ) { return; } fheroes2::drawCastleDialogBuilding( ICN::TWNBEXT2, 0, castle, position, roi, alpha ); } if ( buildId == DWELLING_MONSTER3 || buildId == BUILD_THIEVESGUILD ) { if ( buildId == DWELLING_MONSTER3 && ( ( !constructionInProgress && !castle.isBuild( buildId ) ) || ( !castle.isBuild( BUILD_THIEVESGUILD ) ) ) ) { return; } if ( buildId == BUILD_THIEVESGUILD && ( ( !constructionInProgress && !castle.isBuild( buildId ) ) || ( !castle.isBuild( DWELLING_MONSTER3 ) ) ) ) { return; } fheroes2::drawCastleDialogBuilding( ICN::TWNBEXT3, 0, castle, position, roi, alpha ); } } else if ( race == Race::NECR ) { if ( buildId == BUILD_CAPTAIN && ( ( !constructionInProgress && !castle.isBuild( buildId ) ) || ( !castle.isBuild( BUILD_CASTLE ) ) ) ) { return; } if ( buildId == BUILD_CASTLE && ( ( !constructionInProgress && !castle.isBuild( buildId ) ) || ( !castle.isBuild( BUILD_CAPTAIN ) ) ) ) { return; } fheroes2::drawCastleDialogBuilding( ICN::NECROMANCER_CASTLE_CAPTAIN_QUARTERS_BRIDGE, 0, castle, position, roi, alpha ); } } } CastleDialog::CacheBuildings::CacheBuildings( const Castle & castle, const fheroes2::Point & top ) { const std::vector ordersBuildings = fheroes2::getBuildingDrawingPriorities( castle.GetRace(), Settings::Get().getCurrentMapInfo().version ); for ( const building_t buildingId : ordersBuildings ) { emplace_back( buildingId, fheroes2::getCastleBuildingArea( castle.GetRace(), buildingId ) + top ); } } void CastleDialog::FadeBuilding::StartFadeBuilding( const uint32_t build ) { _alpha = 0; _build = build; } bool CastleDialog::FadeBuilding::UpdateFadeBuilding() { if ( _alpha < 255 && Game::validateAnimationDelay( Game::CASTLE_BUILD_DELAY ) ) { if ( _alpha < 255 - 15 ) { _alpha += 15; } else { _alpha = 255; } return true; } return false; } void CastleDialog::FadeBuilding::StopFadeBuilding() { if ( _build != BUILD_NOTHING ) { _build = BUILD_NOTHING; _alpha = 255; } } void CastleRedrawCurrentBuilding( const Castle & castle, const fheroes2::Point & dst_pt, const CastleDialog::CacheBuildings & orders, const CastleDialog::FadeBuilding & fadeBuilding, const uint32_t animationIndex ) { fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::Sprite & townbkg = fheroes2::AGG::GetICN( getTownIcnId( castle.GetRace() ), 0 ); const fheroes2::Rect max( dst_pt.x, dst_pt.y, townbkg.width(), townbkg.height() ); fheroes2::Copy( townbkg, 0, 0, display, dst_pt.x, dst_pt.y, max.width, max.height ); if ( Race::BARB == castle.GetRace() ) { const fheroes2::Sprite & sprite0 = fheroes2::AGG::GetICN( ICN::TWNBEXT1, 1 + animationIndex % 5 ); fheroes2::Blit( sprite0, display, dst_pt.x + sprite0.x(), dst_pt.y + sprite0.y() ); } // Bay animation if ( Race::WZRD == castle.GetRace() || ( !castle.isBuild( BUILD_SHIPYARD ) && castle.HasSeaAccess() ) ) { int bayIcnId = 0; const uint32_t bayExtraIndex = 1 + animationIndex % 5; switch ( castle.GetRace() ) { case Race::KNGT: bayIcnId = ICN::TWNKEXT0; break; case Race::BARB: bayIcnId = ICN::TWNBEXT0; break; case Race::SORC: bayIcnId = ICN::TWNSEXT0; break; case Race::NECR: bayIcnId = ICN::TWNNEXT0; break; case Race::WRLK: bayIcnId = ICN::TWNWEXT0; break; case Race::WZRD: bayIcnId = ICN::TWNZEXT0; break; default: // Did you add a new race? Add the logic for it! assert( 0 ); break; } fheroes2::drawCastleDialogBuilding( bayIcnId, 0, castle, dst_pt, max ); fheroes2::drawCastleDialogBuilding( bayIcnId, bayExtraIndex, castle, dst_pt, max ); } // redraw all builds if ( BUILD_NOTHING == fadeBuilding.GetBuild() ) { for ( const CastleDialog::BuildingRenderInfo & currentBuild : orders ) { if ( castle.isBuild( currentBuild.id ) ) { CastleDialog::CastleRedrawBuilding( castle, dst_pt, currentBuild.id, animationIndex ); CastleDialog::CastleRedrawBuildingExtended( castle, dst_pt, currentBuild.id, animationIndex ); if ( isBuildingConnectionNeeded( castle, currentBuild.id, false ) ) { redrawBuildingConnection( castle, dst_pt, currentBuild.id ); } } } } // redraw build with alpha else if ( orders.cend() != std::find( orders.cbegin(), orders.cend(), fadeBuilding.GetBuild() ) ) { for ( const CastleDialog::BuildingRenderInfo & currentBuild : orders ) { if ( castle.isBuild( currentBuild.id ) ) { CastleDialog::CastleRedrawBuilding( castle, dst_pt, currentBuild.id, animationIndex ); if ( currentBuild.id == BUILD_SHIPYARD && fadeBuilding.GetBuild() == BUILD_SHIPYARD ) { CastleDialog::CastleRedrawBuildingExtended( castle, dst_pt, currentBuild.id, animationIndex, fadeBuilding.GetAlpha() ); } else { CastleDialog::CastleRedrawBuildingExtended( castle, dst_pt, currentBuild.id, animationIndex ); } if ( isBuildingConnectionNeeded( castle, currentBuild.id, false ) ) { redrawBuildingConnection( castle, dst_pt, fadeBuilding.GetBuild(), fadeBuilding.GetAlpha() ); redrawBuildingConnection( castle, dst_pt, currentBuild.id ); } } else if ( currentBuild.id == fadeBuilding.GetBuild() ) { CastleDialog::CastleRedrawBuilding( castle, dst_pt, currentBuild.id, animationIndex, fadeBuilding.GetAlpha() ); CastleDialog::CastleRedrawBuildingExtended( castle, dst_pt, currentBuild.id, animationIndex, fadeBuilding.GetAlpha() ); if ( isBuildingConnectionNeeded( castle, currentBuild.id, true ) ) { redrawBuildingConnection( castle, dst_pt, currentBuild.id, fadeBuilding.GetAlpha() ); } } } } } void CastleDialog::RedrawAllBuilding( const Castle & castle, const fheroes2::Point & dst_pt, const CacheBuildings & orders, const CastleDialog::FadeBuilding & alphaBuilding, const uint32_t animationIndex ) { CastleRedrawCurrentBuilding( castle, dst_pt, orders, alphaBuilding, animationIndex ); fheroes2::drawCastleName( castle, fheroes2::Display::instance(), dst_pt ); } void CastleDialog::CastleRedrawBuilding( const Castle & castle, const fheroes2::Point & dst_pt, uint32_t build, uint32_t frame, uint8_t alpha ) { if ( build == BUILD_TENT ) { // We don't need to draw a tent because it's on the background image. return; } // correct build switch ( build ) { case DWELLING_MONSTER2: case DWELLING_MONSTER3: case DWELLING_MONSTER4: case DWELLING_MONSTER5: case DWELLING_MONSTER6: build = castle.GetActualDwelling( build ); break; default: break; } const int race = castle.GetRace(); uint32_t index = 0; // correct index (mage guild) switch ( build ) { case BUILD_MAGEGUILD1: if ( castle.GetLevelMageGuild() > 1 ) { return; } break; case BUILD_MAGEGUILD2: if ( castle.GetLevelMageGuild() > 2 ) { return; } index = ( Race::NECR == race ) ? 6 : 1; break; case BUILD_MAGEGUILD3: if ( castle.GetLevelMageGuild() > 3 ) { return; } index = ( Race::NECR == race ) ? 12 : 2; break; case BUILD_MAGEGUILD4: if ( castle.GetLevelMageGuild() > 4 ) { return; } index = ( Race::NECR == race ) ? 18 : 3; break; case BUILD_MAGEGUILD5: index = ( Race::NECR == race ) ? 24 : 4; break; default: break; } const int icn = Castle::GetICNBuilding( build, race ); if ( icn == ICN::UNKNOWN ) { // Have you added a new building? Add the logic for it! assert( 0 ); return; } const fheroes2::Rect max = CastleGetMaxArea( castle, dst_pt ); // Building main sprite. fheroes2::drawCastleDialogBuilding( icn, index, castle, dst_pt, max, alpha ); // Special case: Knight castle's flags are overlapped by Right Turret so we need to draw flags after drawing the Turret. const bool knightCastleCase = ( race == Race::KNGT && castle.isBuild( BUILD_RIGHTTURRET ) && castle.isBuild( BUILD_CASTLE ) ); if ( knightCastleCase && build == BUILD_CASTLE ) { // Do not draw flags. return; } // Building animation sprite. if ( const uint32_t index2 = ICN::AnimationFrame( icn, index, frame ) ) { fheroes2::drawCastleDialogBuilding( icn, index2, castle, dst_pt, max, alpha ); } if ( knightCastleCase && build == BUILD_RIGHTTURRET ) { // Draw Castle's flags after the Turret. const int castleIcn = Castle::GetICNBuilding( BUILD_CASTLE, race ); const uint32_t flagAnimFrame = ICN::AnimationFrame( castleIcn, index, frame ); if ( flagAnimFrame > 0 ) { fheroes2::drawCastleDialogBuilding( castleIcn, flagAnimFrame, castle, dst_pt, max, alpha ); } } } void CastleDialog::CastleRedrawBuildingExtended( const Castle & castle, const fheroes2::Point & dst_pt, uint32_t build, uint32_t frame, uint8_t alpha ) { if ( build == BUILD_TENT ) { // We don't need to draw a tent because it's on the background image. return; } const fheroes2::Rect max = CastleGetMaxArea( castle, dst_pt ); const int icn = Castle::GetICNBuilding( build, castle.GetRace() ); if ( build == BUILD_SHIPYARD ) { // boat if ( castle.HasBoatNearby() ) { const int icn2 = Castle::GetICNBoat( castle.GetRace() ); fheroes2::drawCastleDialogBuilding( icn2, 0, castle, dst_pt, max, alpha ); if ( const uint32_t index2 = ICN::AnimationFrame( icn2, 0, frame ) ) { fheroes2::drawCastleDialogBuilding( icn2, index2, castle, dst_pt, max, alpha ); } } else { if ( const uint32_t index2 = ICN::AnimationFrame( icn, 0, frame ) ) { fheroes2::drawCastleDialogBuilding( icn, index2, castle, dst_pt, max, alpha ); } } } else if ( build == BUILD_WEL2 && Race::SORC == castle.GetRace() ) { // sorc and anime wel2 or statue const int icn2 = castle.isBuild( BUILD_STATUE ) ? ICN::TWNSEXT1 : icn; fheroes2::drawCastleDialogBuilding( icn2, 0, castle, dst_pt, max, alpha ); if ( const uint32_t index2 = ICN::AnimationFrame( icn2, 0, frame ) ) { fheroes2::drawCastleDialogBuilding( icn2, index2, castle, dst_pt, max, alpha ); } } else if ( build == BUILD_WEL2 && castle.GetRace() == Race::KNGT && !castle.isBuild( BUILD_CASTLE ) ) { const fheroes2::Sprite & rightFarm = fheroes2::AGG::GetICN( ICN::KNIGHT_CASTLE_RIGHT_FARM, 0 ); const fheroes2::Sprite & leftFarm = fheroes2::AGG::GetICN( ICN::KNIGHT_CASTLE_LEFT_FARM, 0 ); fheroes2::drawCastleDialogBuilding( ICN::KNIGHT_CASTLE_LEFT_FARM, 0, castle, { dst_pt.x + rightFarm.x() - leftFarm.width(), dst_pt.y + rightFarm.y() }, max, alpha ); } else if ( build == BUILD_CAPTAIN && castle.GetRace() == Race::BARB && !castle.isBuild( BUILD_CASTLE ) ) { const fheroes2::Sprite & rightCaptainQuarters = fheroes2::AGG::GetICN( ICN::TWNBCAPT, 0 ); const fheroes2::Sprite & leftCaptainQuarters = fheroes2::AGG::GetICN( ICN::BARBARIAN_CASTLE_CAPTAIN_QUARTERS_LEFT_SIDE, 0 ); fheroes2::drawCastleDialogBuilding( ICN::BARBARIAN_CASTLE_CAPTAIN_QUARTERS_LEFT_SIDE, 0, castle, { dst_pt.x + rightCaptainQuarters.x() - leftCaptainQuarters.width(), dst_pt.y + rightCaptainQuarters.y() }, max, alpha ); } else if ( build == BUILD_CAPTAIN && castle.GetRace() == Race::SORC && !castle.isBuild( BUILD_CASTLE ) ) { fheroes2::drawCastleDialogBuilding( ICN::SORCERESS_CASTLE_CAPTAIN_QUARTERS_LEFT_SIDE, 0, castle, dst_pt, max, alpha ); } } fheroes2-1.0.12+dfsg/src/fheroes2/castle/castle_building_info.cpp000066400000000000000000001174311456075706000246760ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include "castle_building_info.h" #include "maps_fileinfo.h" #include "race.h" #include "translations.h" namespace { fheroes2::Rect getKnightBuildingArea( const building_t buildingId ) { switch ( buildingId ) { case BUILD_THIEVESGUILD: return { 0, 130, 53, 63 }; case BUILD_TAVERN: return { 345, 114, 83, 62 }; case BUILD_SHIPYARD: return { 531, 211, 108, 45 }; case BUILD_WELL: return { 188, 214, 39, 42 }; case BUILD_STATUE: return { 478, 193, 46, 63 }; case BUILD_MARKETPLACE: return { 219, 138, 120, 30 }; case BUILD_WEL2: return { 241, 102, 141, 24 }; case BUILD_MOAT: return { 0, 146, 311, 30 }; case BUILD_SPEC: return { 0, 78, 251, 22 }; case BUILD_CASTLE: return { 0, 49, 286, 116 }; case BUILD_CAPTAIN: return { 293, 107, 59, 35 }; case BUILD_MAGEGUILD1: return { 397, 150, 84, 32 }; case BUILD_MAGEGUILD2: return { 397, 128, 84, 54 }; case BUILD_MAGEGUILD3: return { 397, 105, 84, 77 }; case BUILD_MAGEGUILD4: return { 397, 85, 84, 97 }; case BUILD_MAGEGUILD5: return { 397, 55, 84, 127 }; case BUILD_TENT: return { 69, 108, 67, 55 }; case DWELLING_MONSTER1: return { 192, 163, 69, 52 }; case DWELLING_MONSTER2: case DWELLING_UPGRADE2: return { 135, 149, 73, 32 }; case DWELLING_MONSTER3: case DWELLING_UPGRADE3: return { 240, 166, 91, 66 }; case DWELLING_MONSTER4: case DWELLING_UPGRADE4: return { 323, 174, 132, 73 }; case DWELLING_MONSTER5: case DWELLING_UPGRADE5: return { 0, 176, 152, 79 }; case DWELLING_MONSTER6: case DWELLING_UPGRADE6: case DWELLING_UPGRADE7: return { 445, 50, 194, 157 }; case BUILD_LEFTTURRET: return { 7, 33, 0, 0 }; case BUILD_RIGHTTURRET: return { 134, 37, 0, 0 }; default: break; } // Did you add a new building but forgot to add its area? assert( 0 ); return {}; } fheroes2::Rect getBarbarianBuildingArea( const building_t buildingId ) { switch ( buildingId ) { case BUILD_THIEVESGUILD: return { 466, 94, 87, 47 }; case BUILD_TAVERN: return { 0, 161, 136, 90 }; case BUILD_SHIPYARD: return { 505, 199, 134, 56 }; case BUILD_WELL: return { 268, 189, 50, 58 }; case BUILD_STATUE: return { 463, 154, 38, 81 }; case BUILD_MARKETPLACE: return { 217, 166, 67, 43 }; case BUILD_WEL2: return { 240, 106, 73, 34 }; case BUILD_MOAT: return { 115, 138, 182, 42 }; case BUILD_SPEC: return { 210, 80, 197, 61 }; case BUILD_CASTLE: return { 0, 0, 214, 175 }; case BUILD_CAPTAIN: return { 206, 99, 46, 42 }; case BUILD_MAGEGUILD1: return { 348, 118, 50, 25 }; case BUILD_MAGEGUILD2: return { 348, 94, 50, 49 }; case BUILD_MAGEGUILD3: return { 348, 72, 50, 72 }; case BUILD_MAGEGUILD4: return { 348, 48, 50, 96 }; case BUILD_MAGEGUILD5: return { 348, 20, 50, 124 }; case BUILD_TENT: return { 44, 109, 87, 52 }; case DWELLING_MONSTER1: return { 290, 138, 58, 45 }; case DWELLING_MONSTER2: case DWELLING_UPGRADE2: return { 145, 195, 76, 52 }; case DWELLING_MONSTER3: case DWELLING_UPGRADE3: return { 557, 48, 83, 83 }; case DWELLING_MONSTER4: case DWELLING_UPGRADE4: return { 496, 136, 138, 64 }; case DWELLING_MONSTER5: case DWELLING_UPGRADE5: return { 318, 174, 131, 54 }; case DWELLING_MONSTER6: case DWELLING_UPGRADE6: case DWELLING_UPGRADE7: return { 407, 0, 113, 106 }; case BUILD_LEFTTURRET: return { 5, 50, 0, 0 }; case BUILD_RIGHTTURRET: return { 118, 45, 0, 0 }; default: break; } // Did you add a new building but forgot to add its area? assert( 0 ); return {}; } fheroes2::Rect getSorceressBuildingArea( const building_t buildingId ) { switch ( buildingId ) { case BUILD_THIEVESGUILD: return { 423, 167, 87, 50 }; case BUILD_TAVERN: return { 490, 141, 148, 91 }; case BUILD_SHIPYARD: return { 0, 208, 178, 48 }; case BUILD_WELL: return { 335, 205, 45, 29 }; case BUILD_STATUE: return { 152, 163, 28, 65 }; case BUILD_MARKETPLACE: return { 404, 122, 69, 45 }; case BUILD_WEL2: return { 131, 185, 71, 53 }; case BUILD_MOAT: return { 0, 171, 272, 23 }; case BUILD_SPEC: return { 152, 0, 236, 84 }; case BUILD_CASTLE: return { 0, 0, 201, 179 }; case BUILD_CAPTAIN: return { 223, 122, 37, 52 }; case BUILD_MAGEGUILD1: return { 280, 21, 60, 143 }; case BUILD_MAGEGUILD2: case BUILD_MAGEGUILD3: case BUILD_MAGEGUILD4: case BUILD_MAGEGUILD5: return { 280, 0, 60, 164 }; case BUILD_TENT: return { 104, 130, 59, 42 }; case DWELLING_MONSTER1: return { 472, 59, 111, 92 }; case DWELLING_MONSTER2: case DWELLING_UPGRADE2: return { 338, 146, 93, 61 }; case DWELLING_MONSTER3: case DWELLING_UPGRADE3: return { 51, 164, 106, 40 }; case DWELLING_MONSTER4: case DWELLING_UPGRADE4: return { 198, 178, 143, 71 }; case DWELLING_MONSTER5: case DWELLING_UPGRADE5: return { 263, 226, 296, 30 }; case DWELLING_MONSTER6: case DWELLING_UPGRADE6: case DWELLING_UPGRADE7: return { 179, 0, 84, 119 }; case BUILD_LEFTTURRET: return { 98, 99, 0, 0 }; case BUILD_RIGHTTURRET: return { 151, 98, 0, 0 }; default: break; } // Did you add a new building but forgot to add its area? assert( 0 ); return {}; } fheroes2::Rect getWarlockBuildingArea( const building_t buildingId ) { switch ( buildingId ) { case BUILD_THIEVESGUILD: return { 520, 103, 64, 54 }; case BUILD_TAVERN: return { 476, 96, 82, 55 }; case BUILD_SHIPYARD: return { 517, 200, 122, 56 }; case BUILD_WELL: return { 342, 205, 67, 42 }; case BUILD_STATUE: return { 478, 161, 37, 63 }; case BUILD_MARKETPLACE: return { 386, 171, 71, 40 }; case BUILD_WEL2: return { 60, 32, 63, 186 }; case BUILD_MOAT: return { 211, 166, 301, 21 }; case BUILD_SPEC: return { 0, 160, 59, 95 }; case BUILD_CASTLE: return { 241, 18, 181, 150 }; case BUILD_CAPTAIN: return { 418, 83, 53, 84 }; case BUILD_MAGEGUILD1: return { 590, 135, 49, 35 }; case BUILD_MAGEGUILD2: return { 590, 108, 49, 60 }; case BUILD_MAGEGUILD3: return { 590, 77, 49, 90 }; case BUILD_MAGEGUILD4: return { 590, 45, 49, 125 }; case BUILD_MAGEGUILD5: return { 590, 14, 49, 155 }; case BUILD_TENT: return { 298, 135, 72, 31 }; case DWELLING_MONSTER1: return { 0, 64, 48, 50 }; case DWELLING_MONSTER2: case DWELLING_UPGRADE2: return { 237, 168, 78, 79 }; case DWELLING_MONSTER3: case DWELLING_UPGRADE3: return { 492, 50, 53, 39 }; case DWELLING_MONSTER4: case DWELLING_UPGRADE4: return { 139, 163, 190, 83 }; case DWELLING_MONSTER5: case DWELLING_UPGRADE5: return { 82, 92, 178, 68 }; case DWELLING_MONSTER6: case DWELLING_UPGRADE6: case DWELLING_UPGRADE7: return { 92, 0, 64, 255 }; case BUILD_LEFTTURRET: return { 311, 84, 0, 0 }; case BUILD_RIGHTTURRET: return { 359, 83, 0, 0 }; default: break; } // Did you add a new building but forgot to add its area? assert( 0 ); return {}; } fheroes2::Rect getWizardBuildingArea( const building_t buildingId ) { switch ( buildingId ) { case BUILD_THIEVESGUILD: return { 505, 50, 51, 49 }; case BUILD_TAVERN: return { 0, 149, 118, 76 }; case BUILD_SHIPYARD: return { 0, 206, 206, 49 }; case BUILD_WELL: return { 249, 139, 28, 33 }; case BUILD_STATUE: return { 464, 45, 24, 72 }; case BUILD_MARKETPLACE: return { 255, 163, 108, 53 }; case BUILD_WEL2: return { 237, 208, 137, 39 }; case BUILD_MOAT: return { 0, 90, 223, 14 }; case BUILD_SPEC: return { 297, 95, 109, 78 }; case BUILD_CASTLE: return { 0, 0, 200, 99 }; case BUILD_CAPTAIN: return { 210, 52, 28, 35 }; case BUILD_MAGEGUILD1: return { 585, 73, 54, 48 }; case BUILD_MAGEGUILD2: return { 585, 69, 54, 50 }; case BUILD_MAGEGUILD3: return { 585, 44, 54, 78 }; case BUILD_MAGEGUILD4: return { 585, 20, 54, 102 }; case BUILD_MAGEGUILD5: return { 585, 0, 54, 122 }; case BUILD_TENT: return { 58, 60, 49, 42 }; case DWELLING_MONSTER1: return { 467, 181, 38, 30 }; case DWELLING_MONSTER2: case DWELLING_UPGRADE2: return { 231, 68, 192, 36 }; case DWELLING_MONSTER3: case DWELLING_UPGRADE3: return { 152, 130, 96, 60 }; case DWELLING_MONSTER4: case DWELLING_UPGRADE4: return { 593, 184, 46, 31 }; case DWELLING_MONSTER5: case DWELLING_UPGRADE5: return { 411, 0, 49, 167 }; case DWELLING_MONSTER6: case DWELLING_UPGRADE6: case DWELLING_UPGRADE7: return { 160, 0, 178, 67 }; case BUILD_LEFTTURRET: return { 30, 17, 0, 0 }; case BUILD_RIGHTTURRET: return { 127, 17, 0, 0 }; default: break; } // Did you add a new building but forgot to add its area? assert( 0 ); return {}; } fheroes2::Rect getNecromancerBuildingArea( const building_t buildingId ) { switch ( buildingId ) { case BUILD_THIEVESGUILD: return { 275, 124, 62, 77 }; case BUILD_SHRINE: return { 455, 39, 51, 103 }; case BUILD_SHIPYARD: return { 500, 220, 139, 36 }; case BUILD_WELL: return { 215, 213, 29, 39 }; case BUILD_STATUE: return { 365, 154, 41, 93 }; case BUILD_MARKETPLACE: return { 412, 193, 98, 61 }; case BUILD_WEL2: return { 263, 181, 90, 65 }; case BUILD_MOAT: return { 258, 171, 193, 19 }; case BUILD_SPEC: return { 0, 0, 640, 63 }; case BUILD_CASTLE: return { 289, 10, 134, 164 }; case BUILD_CAPTAIN: return { 441, 77, 22, 99 }; case BUILD_MAGEGUILD1: return { 565, 131, 73, 74 }; case BUILD_MAGEGUILD2: return { 568, 102, 62, 104 }; case BUILD_MAGEGUILD3: return { 570, 79, 56, 130 }; case BUILD_MAGEGUILD4: return { 570, 61, 60, 146 }; case BUILD_MAGEGUILD5: return { 570, 45, 61, 162 }; case BUILD_TENT: return { 333, 115, 47, 70 }; case DWELLING_MONSTER1: return { 396, 177, 71, 35 }; case DWELLING_MONSTER2: case DWELLING_UPGRADE2: return { 110, 174, 141, 45 }; case DWELLING_MONSTER3: case DWELLING_UPGRADE3: return { 0, 28, 241, 142 }; case DWELLING_MONSTER4: case DWELLING_UPGRADE4: return { 0, 107, 140, 129 }; case DWELLING_MONSTER5: case DWELLING_UPGRADE5: return { 221, 127, 66, 84 }; case DWELLING_MONSTER6: case DWELLING_UPGRADE6: case DWELLING_UPGRADE7: return { 464, 72, 105, 124 }; case BUILD_LEFTTURRET: return { 330, 47, 0, 0 }; case BUILD_RIGHTTURRET: return { 360, 46, 0, 0 }; default: break; } // Did you add a new building but forgot to add its area? assert( 0 ); return {}; } const char * getKnightBuildingName( const building_t buildingId ) { switch ( buildingId ) { case BUILD_SPEC: return _( "Fortifications" ); case BUILD_WEL2: return _( "Farm" ); case DWELLING_MONSTER1: return _( "Thatched Hut" ); case DWELLING_MONSTER2: return _( "Archery Range" ); case DWELLING_UPGRADE2: return _( "Upg. Archery Range" ); case DWELLING_MONSTER3: return _( "Blacksmith" ); case DWELLING_UPGRADE3: return _( "Upg. Blacksmith" ); case DWELLING_MONSTER4: return _( "Armory" ); case DWELLING_UPGRADE4: return _( "Upg. Armory" ); case DWELLING_MONSTER5: return _( "Jousting Arena" ); case DWELLING_UPGRADE5: return _( "Upg. Jousting Arena" ); case DWELLING_MONSTER6: return _( "Cathedral" ); case DWELLING_UPGRADE6: return _( "Upg. Cathedral" ); default: break; } // Did you add a new building? assert( 0 ); return nullptr; } const char * getBarbarianBuildingName( const building_t buildingId ) { switch ( buildingId ) { case BUILD_SPEC: return _( "Coliseum" ); case BUILD_WEL2: return _( "Garbage Heap" ); case DWELLING_MONSTER1: return _( "Hut" ); case DWELLING_MONSTER2: return _( "Stick Hut" ); case DWELLING_UPGRADE2: return _( "Upg. Stick Hut" ); case DWELLING_MONSTER3: return _( "Den" ); case DWELLING_MONSTER4: return _( "Adobe" ); case DWELLING_UPGRADE4: return _( "Upg. Adobe" ); case DWELLING_MONSTER5: return _( "Bridge" ); case DWELLING_UPGRADE5: return _( "Upg. Bridge" ); case DWELLING_MONSTER6: return _( "Pyramid" ); default: break; } // Did you add a new building? assert( 0 ); return nullptr; } const char * getSorceressBuildingName( const building_t buildingId ) { switch ( buildingId ) { case BUILD_SPEC: return _( "Rainbow" ); case BUILD_WEL2: return _( "Crystal Garden" ); case DWELLING_MONSTER1: return _( "Treehouse" ); case DWELLING_MONSTER2: return _( "Cottage" ); case DWELLING_UPGRADE2: return _( "Upg. Cottage" ); case DWELLING_MONSTER3: return _( "Archery Range" ); case DWELLING_UPGRADE3: return _( "Upg. Archery Range" ); case DWELLING_MONSTER4: return _( "Stonehenge" ); case DWELLING_UPGRADE4: return _( "Upg. Stonehenge" ); case DWELLING_MONSTER5: return _( "Fenced Meadow" ); case DWELLING_MONSTER6: return _( "sorceress|Red Tower" ); default: break; } // Did you add a new building? assert( 0 ); return nullptr; } const char * getWarlockBuildingName( const building_t buildingId ) { switch ( buildingId ) { case BUILD_SPEC: return _( "Dungeon" ); case BUILD_WEL2: return _( "Waterfall" ); case DWELLING_MONSTER1: return _( "Cave" ); case DWELLING_MONSTER2: return _( "Crypt" ); case DWELLING_MONSTER3: return _( "Nest" ); case DWELLING_MONSTER4: return _( "Maze" ); case DWELLING_UPGRADE4: return _( "Upg. Maze" ); case DWELLING_MONSTER5: return _( "Swamp" ); case DWELLING_MONSTER6: return _( "Green Tower" ); case DWELLING_UPGRADE6: return _( "warlock|Red Tower" ); case DWELLING_UPGRADE7: return _( "Black Tower" ); default: break; } // Did you add a new building? assert( 0 ); return nullptr; } const char * getWizardBuildingName( const building_t buildingId ) { switch ( buildingId ) { case BUILD_SPEC: return _( "Library" ); case BUILD_WEL2: return _( "Orchard" ); case DWELLING_MONSTER1: return _( "Habitat" ); case DWELLING_MONSTER2: return _( "Pen" ); case DWELLING_MONSTER3: return _( "Foundry" ); case DWELLING_UPGRADE3: return _( "Upg. Foundry" ); case DWELLING_MONSTER4: return _( "Cliff Nest" ); case DWELLING_MONSTER5: return _( "Ivory Tower" ); case DWELLING_UPGRADE5: return _( "Upg. Ivory Tower" ); case DWELLING_MONSTER6: return _( "Cloud Castle" ); case DWELLING_UPGRADE6: return _( "Upg. Cloud Castle" ); default: break; } // Did you add a new building? assert( 0 ); return nullptr; } const char * getNecromancerBuildingName( const building_t buildingId ) { switch ( buildingId ) { case BUILD_SPEC: return _( "Storm" ); case BUILD_WEL2: return _( "Skull Pile" ); case DWELLING_MONSTER1: return _( "Excavation" ); case DWELLING_MONSTER2: return _( "Graveyard" ); case DWELLING_UPGRADE2: return _( "Upg. Graveyard" ); case DWELLING_MONSTER3: return _( "Pyramid" ); case DWELLING_UPGRADE3: return _( "Upg. Pyramid" ); case DWELLING_MONSTER4: return _( "Mansion" ); case DWELLING_UPGRADE4: return _( "Upg. Mansion" ); case DWELLING_MONSTER5: return _( "Mausoleum" ); case DWELLING_UPGRADE5: return _( "Upg. Mausoleum" ); case DWELLING_MONSTER6: return _( "Laboratory" ); case BUILD_SHRINE: return _( "Shrine" ); default: break; } // Did you add a new building? assert( 0 ); return nullptr; } const char * getKnightBuildingDescription( const building_t buildingId ) { switch ( buildingId ) { case BUILD_SPEC: return _( "The Fortifications increase the toughness of the walls, increasing the number of turns it takes to knock them down." ); case BUILD_WEL2: return _( "The Farm increases production of Peasants by %{count} per week." ); default: break; } // Did you add a new building? assert( 0 ); return nullptr; } const char * getBarbarianBuildingDescription( const building_t buildingId ) { switch ( buildingId ) { case BUILD_SPEC: return _( "The Coliseum provides inspiring spectacles to defending troops, raising their morale by two during combat." ); case BUILD_WEL2: return _( "The Garbage Heap increases production of Goblins by %{count} per week." ); default: break; } // Did you add a new building? assert( 0 ); return nullptr; } const char * getSorceressBuildingDescription( const building_t buildingId ) { switch ( buildingId ) { case BUILD_SPEC: return _( "The Rainbow increases the luck of the defending units by two." ); case BUILD_WEL2: return _( "The Crystal Garden increases production of Sprites by %{count} per week." ); default: break; } // Did you add a new building? assert( 0 ); return nullptr; } const char * getWarlockBuildingDescription( const building_t buildingId ) { switch ( buildingId ) { case BUILD_SPEC: return _( "The Dungeon increases the income of the town by %{count} gold per day." ); case BUILD_WEL2: return _( "The Waterfall increases production of Centaurs by %{count} per week." ); default: break; } // Did you add a new building? assert( 0 ); return nullptr; } const char * getWizardBuildingDescription( const building_t buildingId ) { switch ( buildingId ) { case BUILD_SPEC: return _( "The Library increases the number of spells in the Guild by one for each level of the guild." ); case BUILD_WEL2: return _( "The Orchard increases production of Halflings by %{count} per week." ); default: break; } // Did you add a new building? assert( 0 ); return nullptr; } const char * getNecromancerBuildingDescription( const building_t buildingId ) { switch ( buildingId ) { case BUILD_SPEC: return _( "The Storm adds +2 to the power of spells of a defending spell caster." ); case BUILD_WEL2: return _( "The Skull Pile increases production of Skeletons by %{count} per week." ); default: break; } // Did you add a new building? assert( 0 ); return nullptr; } } namespace fheroes2 { Rect getCastleBuildingArea( const int race, const building_t buildingId ) { if ( buildingId == BUILD_NOTHING ) { // Special case which we should ignore. return {}; } switch ( race ) { case Race::KNGT: return getKnightBuildingArea( buildingId ); case Race::BARB: return getBarbarianBuildingArea( buildingId ); case Race::SORC: return getSorceressBuildingArea( buildingId ); case Race::WRLK: return getWarlockBuildingArea( buildingId ); case Race::WZRD: return getWizardBuildingArea( buildingId ); case Race::NECR: return getNecromancerBuildingArea( buildingId ); default: break; } // Did you add a new castle? assert( 0 ); return {}; } const char * getBuildingName( const int race, const building_t buildingId ) { if ( buildingId == BUILD_NOTHING ) { // Special case which we should ignore. return ""; } // Check common buildings as they have the same name. switch ( buildingId ) { case BUILD_THIEVESGUILD: return _( "Thieves' Guild" ); case BUILD_TAVERN: return _( "Tavern" ); case BUILD_SHIPYARD: return _( "Shipyard" ); case BUILD_WELL: return _( "Well" ); case BUILD_STATUE: return _( "Statue" ); case BUILD_LEFTTURRET: return _( "Left Turret" ); case BUILD_RIGHTTURRET: return _( "Right Turret" ); case BUILD_MARKETPLACE: return _( "Marketplace" ); case BUILD_MOAT: return _( "Moat" ); case BUILD_CASTLE: return _( "Castle" ); case BUILD_TENT: return _( "Tent" ); case BUILD_CAPTAIN: return _( "Captain's Quarters" ); case BUILD_MAGEGUILD1: return _( "Mage Guild, Level 1" ); case BUILD_MAGEGUILD2: return _( "Mage Guild, Level 2" ); case BUILD_MAGEGUILD3: return _( "Mage Guild, Level 3" ); case BUILD_MAGEGUILD4: return _( "Mage Guild, Level 4" ); case BUILD_MAGEGUILD5: return _( "Mage Guild, Level 5" ); default: break; } switch ( race ) { case Race::KNGT: return getKnightBuildingName( buildingId ); case Race::BARB: return getBarbarianBuildingName( buildingId ); case Race::SORC: return getSorceressBuildingName( buildingId ); case Race::WRLK: return getWarlockBuildingName( buildingId ); case Race::WZRD: return getWizardBuildingName( buildingId ); case Race::NECR: return getNecromancerBuildingName( buildingId ); default: break; } // Did you add a new castle? assert( 0 ); return nullptr; } const char * getBuildingDescription( const int race, const building_t buildingId ) { if ( buildingId == BUILD_NOTHING ) { // Special case which we should ignore. return ""; } switch ( buildingId ) { case BUILD_SHRINE: return _( "The Shrine increases the necromancy skill of all your necromancers by 10 percent." ); case BUILD_THIEVESGUILD: return _( "The Thieves' Guild provides information on enemy players. Thieves' Guilds can also provide scouting information on enemy towns. Additional Guilds provide more information." ); case BUILD_TAVERN: return _( "The Tavern increases morale for troops defending the castle." ); case BUILD_SHIPYARD: return _( "The Shipyard allows ships to be built." ); case BUILD_WELL: return _( "The Well increases the growth rate of all dwellings by %{count} creatures per week." ); case BUILD_STATUE: return _( "The Statue increases the town's income by %{count} gold per day." ); case BUILD_LEFTTURRET: return _( "The Left Turret provides extra firepower during castle combat." ); case BUILD_RIGHTTURRET: return _( "The Right Turret provides extra firepower during castle combat." ); case BUILD_MARKETPLACE: return _( "The Marketplace can be used to convert one type of resource into another. The more marketplaces you control, the better the exchange rate." ); case BUILD_MOAT: return _( "The Moat slows attacking units. Any unit entering the moat must end its turn there and becomes more vulnerable to attack." ); case BUILD_CASTLE: return _( "The Castle improves the town's defense and increases its income to %{count} gold per day." ); case BUILD_TENT: return _( "The Tent provides workers to build a castle, provided the materials and the gold are available." ); case BUILD_CAPTAIN: return _( "The Captain's Quarters provides a captain to assist in the castle's defense when no hero is present." ); case BUILD_MAGEGUILD1: case BUILD_MAGEGUILD2: case BUILD_MAGEGUILD3: case BUILD_MAGEGUILD4: case BUILD_MAGEGUILD5: return _( "The Mage Guild allows heroes to learn spells and replenish their spell points." ); default: break; } switch ( race ) { case Race::KNGT: return getKnightBuildingDescription( buildingId ); case Race::BARB: return getBarbarianBuildingDescription( buildingId ); case Race::SORC: return getSorceressBuildingDescription( buildingId ); case Race::WRLK: return getWarlockBuildingDescription( buildingId ); case Race::WZRD: return getWizardBuildingDescription( buildingId ); case Race::NECR: return getNecromancerBuildingDescription( buildingId ); default: break; } // Did you add a new castle? assert( 0 ); return nullptr; } std::vector getBuildingDrawingPriorities( const int race, const GameVersion version ) { std::vector priorities; priorities.reserve( 32 ); switch ( race ) { case Race::KNGT: priorities.emplace_back( BUILD_TENT ); priorities.emplace_back( BUILD_WEL2 ); priorities.emplace_back( BUILD_CASTLE ); priorities.emplace_back( BUILD_SPEC ); priorities.emplace_back( BUILD_CAPTAIN ); priorities.emplace_back( BUILD_LEFTTURRET ); priorities.emplace_back( BUILD_RIGHTTURRET ); priorities.emplace_back( BUILD_MOAT ); priorities.emplace_back( BUILD_MARKETPLACE ); priorities.emplace_back( DWELLING_MONSTER2 ); priorities.emplace_back( DWELLING_UPGRADE2 ); priorities.emplace_back( BUILD_THIEVESGUILD ); priorities.emplace_back( BUILD_TAVERN ); priorities.emplace_back( BUILD_MAGEGUILD1 ); priorities.emplace_back( BUILD_MAGEGUILD2 ); priorities.emplace_back( BUILD_MAGEGUILD3 ); priorities.emplace_back( BUILD_MAGEGUILD4 ); priorities.emplace_back( BUILD_MAGEGUILD5 ); priorities.emplace_back( DWELLING_MONSTER5 ); priorities.emplace_back( DWELLING_UPGRADE5 ); priorities.emplace_back( DWELLING_MONSTER6 ); priorities.emplace_back( DWELLING_UPGRADE6 ); priorities.emplace_back( DWELLING_MONSTER1 ); priorities.emplace_back( DWELLING_MONSTER3 ); priorities.emplace_back( DWELLING_UPGRADE3 ); priorities.emplace_back( DWELLING_MONSTER4 ); priorities.emplace_back( DWELLING_UPGRADE4 ); priorities.emplace_back( BUILD_WELL ); priorities.emplace_back( BUILD_SHIPYARD ); priorities.emplace_back( BUILD_STATUE ); break; case Race::BARB: priorities.emplace_back( BUILD_SPEC ); priorities.emplace_back( BUILD_WEL2 ); priorities.emplace_back( DWELLING_MONSTER6 ); priorities.emplace_back( BUILD_MAGEGUILD1 ); priorities.emplace_back( BUILD_MAGEGUILD2 ); priorities.emplace_back( BUILD_MAGEGUILD3 ); priorities.emplace_back( BUILD_MAGEGUILD4 ); priorities.emplace_back( BUILD_MAGEGUILD5 ); priorities.emplace_back( BUILD_CAPTAIN ); priorities.emplace_back( BUILD_TENT ); priorities.emplace_back( BUILD_CASTLE ); priorities.emplace_back( BUILD_LEFTTURRET ); priorities.emplace_back( BUILD_RIGHTTURRET ); priorities.emplace_back( BUILD_MOAT ); priorities.emplace_back( DWELLING_MONSTER3 ); priorities.emplace_back( BUILD_THIEVESGUILD ); priorities.emplace_back( DWELLING_MONSTER1 ); priorities.emplace_back( BUILD_MARKETPLACE ); priorities.emplace_back( DWELLING_MONSTER2 ); priorities.emplace_back( DWELLING_UPGRADE2 ); priorities.emplace_back( BUILD_TAVERN ); priorities.emplace_back( DWELLING_MONSTER4 ); priorities.emplace_back( DWELLING_UPGRADE4 ); priorities.emplace_back( DWELLING_MONSTER5 ); priorities.emplace_back( DWELLING_UPGRADE5 ); priorities.emplace_back( BUILD_WELL ); priorities.emplace_back( BUILD_STATUE ); priorities.emplace_back( BUILD_SHIPYARD ); break; case Race::SORC: priorities.emplace_back( BUILD_SPEC ); priorities.emplace_back( DWELLING_MONSTER6 ); priorities.emplace_back( BUILD_MAGEGUILD1 ); priorities.emplace_back( BUILD_MAGEGUILD2 ); priorities.emplace_back( BUILD_MAGEGUILD3 ); priorities.emplace_back( BUILD_MAGEGUILD4 ); priorities.emplace_back( BUILD_MAGEGUILD5 ); priorities.emplace_back( BUILD_CAPTAIN ); priorities.emplace_back( BUILD_TENT ); priorities.emplace_back( BUILD_CASTLE ); priorities.emplace_back( BUILD_LEFTTURRET ); priorities.emplace_back( BUILD_RIGHTTURRET ); priorities.emplace_back( BUILD_MOAT ); priorities.emplace_back( DWELLING_MONSTER3 ); priorities.emplace_back( DWELLING_UPGRADE3 ); priorities.emplace_back( BUILD_SHIPYARD ); priorities.emplace_back( BUILD_MARKETPLACE ); priorities.emplace_back( DWELLING_MONSTER2 ); priorities.emplace_back( DWELLING_UPGRADE2 ); priorities.emplace_back( BUILD_THIEVESGUILD ); priorities.emplace_back( DWELLING_MONSTER1 ); priorities.emplace_back( BUILD_TAVERN ); priorities.emplace_back( BUILD_STATUE ); priorities.emplace_back( BUILD_WEL2 ); priorities.emplace_back( DWELLING_MONSTER4 ); priorities.emplace_back( DWELLING_UPGRADE4 ); priorities.emplace_back( BUILD_WELL ); priorities.emplace_back( DWELLING_MONSTER5 ); break; case Race::WRLK: priorities.emplace_back( DWELLING_MONSTER5 ); priorities.emplace_back( DWELLING_MONSTER3 ); priorities.emplace_back( BUILD_TENT ); priorities.emplace_back( BUILD_CASTLE ); priorities.emplace_back( BUILD_LEFTTURRET ); priorities.emplace_back( BUILD_RIGHTTURRET ); priorities.emplace_back( BUILD_MOAT ); priorities.emplace_back( BUILD_CAPTAIN ); priorities.emplace_back( BUILD_SHIPYARD ); priorities.emplace_back( BUILD_MAGEGUILD1 ); priorities.emplace_back( BUILD_MAGEGUILD2 ); priorities.emplace_back( BUILD_MAGEGUILD3 ); priorities.emplace_back( BUILD_MAGEGUILD4 ); priorities.emplace_back( BUILD_MAGEGUILD5 ); priorities.emplace_back( BUILD_TAVERN ); priorities.emplace_back( BUILD_THIEVESGUILD ); priorities.emplace_back( BUILD_MARKETPLACE ); priorities.emplace_back( BUILD_STATUE ); priorities.emplace_back( DWELLING_MONSTER1 ); priorities.emplace_back( BUILD_WEL2 ); priorities.emplace_back( BUILD_SPEC ); priorities.emplace_back( DWELLING_MONSTER4 ); priorities.emplace_back( DWELLING_UPGRADE4 ); priorities.emplace_back( DWELLING_MONSTER2 ); priorities.emplace_back( DWELLING_MONSTER6 ); priorities.emplace_back( DWELLING_UPGRADE6 ); priorities.emplace_back( DWELLING_UPGRADE7 ); priorities.emplace_back( BUILD_WELL ); break; case Race::WZRD: priorities.emplace_back( DWELLING_MONSTER6 ); priorities.emplace_back( DWELLING_UPGRADE6 ); priorities.emplace_back( BUILD_TENT ); priorities.emplace_back( BUILD_CASTLE ); priorities.emplace_back( BUILD_LEFTTURRET ); priorities.emplace_back( BUILD_RIGHTTURRET ); priorities.emplace_back( BUILD_MOAT ); priorities.emplace_back( BUILD_CAPTAIN ); priorities.emplace_back( DWELLING_MONSTER2 ); priorities.emplace_back( BUILD_THIEVESGUILD ); priorities.emplace_back( BUILD_TAVERN ); priorities.emplace_back( BUILD_SHIPYARD ); priorities.emplace_back( BUILD_WELL ); priorities.emplace_back( DWELLING_MONSTER3 ); priorities.emplace_back( DWELLING_UPGRADE3 ); priorities.emplace_back( DWELLING_MONSTER5 ); priorities.emplace_back( DWELLING_UPGRADE5 ); priorities.emplace_back( BUILD_MAGEGUILD1 ); priorities.emplace_back( BUILD_MAGEGUILD2 ); priorities.emplace_back( BUILD_MAGEGUILD3 ); priorities.emplace_back( BUILD_MAGEGUILD4 ); priorities.emplace_back( BUILD_MAGEGUILD5 ); priorities.emplace_back( BUILD_SPEC ); priorities.emplace_back( BUILD_STATUE ); priorities.emplace_back( DWELLING_MONSTER1 ); priorities.emplace_back( DWELLING_MONSTER4 ); priorities.emplace_back( BUILD_MARKETPLACE ); priorities.emplace_back( BUILD_WEL2 ); break; case Race::NECR: priorities.emplace_back( BUILD_SPEC ); if ( version == GameVersion::PRICE_OF_LOYALTY || version == GameVersion::RESURRECTION ) { priorities.emplace_back( BUILD_SHRINE ); } priorities.emplace_back( BUILD_TENT ); priorities.emplace_back( BUILD_CASTLE ); priorities.emplace_back( BUILD_LEFTTURRET ); priorities.emplace_back( BUILD_RIGHTTURRET ); priorities.emplace_back( BUILD_MOAT ); priorities.emplace_back( BUILD_CAPTAIN ); priorities.emplace_back( DWELLING_MONSTER6 ); priorities.emplace_back( DWELLING_MONSTER1 ); priorities.emplace_back( BUILD_THIEVESGUILD ); priorities.emplace_back( DWELLING_MONSTER3 ); priorities.emplace_back( DWELLING_UPGRADE3 ); priorities.emplace_back( DWELLING_MONSTER5 ); priorities.emplace_back( DWELLING_UPGRADE5 ); priorities.emplace_back( DWELLING_MONSTER2 ); priorities.emplace_back( DWELLING_UPGRADE2 ); priorities.emplace_back( DWELLING_MONSTER4 ); priorities.emplace_back( DWELLING_UPGRADE4 ); priorities.emplace_back( BUILD_MAGEGUILD1 ); priorities.emplace_back( BUILD_MAGEGUILD2 ); priorities.emplace_back( BUILD_MAGEGUILD3 ); priorities.emplace_back( BUILD_MAGEGUILD4 ); priorities.emplace_back( BUILD_MAGEGUILD5 ); priorities.emplace_back( BUILD_SHIPYARD ); priorities.emplace_back( BUILD_WEL2 ); priorities.emplace_back( BUILD_MARKETPLACE ); priorities.emplace_back( BUILD_STATUE ); priorities.emplace_back( BUILD_WELL ); break; default: // Did you add a new castle? assert( 0 ); break; } return priorities; } } fheroes2-1.0.12+dfsg/src/fheroes2/castle/castle_building_info.h000066400000000000000000000036361456075706000243440ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include "castle.h" #include "math_base.h" enum class GameVersion : int; namespace fheroes2 { Rect getCastleBuildingArea( const int race, const building_t buildingId ); const char * getBuildingName( const int race, const building_t buildingId ); const char * getBuildingDescription( const int race, const building_t buildingId ); std::vector getBuildingDrawingPriorities( const int race, const GameVersion version ); } fheroes2-1.0.12+dfsg/src/fheroes2/castle/castle_dialog.cpp000066400000000000000000001000041456075706000233110ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include "agg_image.h" #include "army.h" #include "army_bar.h" #include "army_troop.h" #include "audio.h" #include "audio_manager.h" #include "captain.h" #include "castle.h" #include "castle_building_info.h" #include "color.h" #include "cursor.h" #include "dialog.h" #include "game.h" #include "game_delays.h" #include "game_hotkeys.h" #include "gamedefs.h" #include "heroes.h" #include "heroes_base.h" #include "icn.h" #include "image.h" #include "kingdom.h" #include "localevent.h" #include "m82.h" #include "math_base.h" #include "monster.h" #include "mus.h" #include "screen.h" #include "statusbar.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_castle.h" #include "ui_dialog.h" #include "ui_kingdom.h" #include "ui_tool.h" #include "ui_window.h" #include "world.h" namespace { uint32_t castleAnimationIndex = 0; building_t getPressedBuildingHotkey() { if ( HotKeyPressEvent( Game::HotKeyEvent::TOWN_DWELLING_LEVEL_1 ) ) { return DWELLING_MONSTER1; } if ( HotKeyPressEvent( Game::HotKeyEvent::TOWN_DWELLING_LEVEL_2 ) ) { return DWELLING_MONSTER2; } if ( HotKeyPressEvent( Game::HotKeyEvent::TOWN_DWELLING_LEVEL_3 ) ) { return DWELLING_MONSTER3; } if ( HotKeyPressEvent( Game::HotKeyEvent::TOWN_DWELLING_LEVEL_4 ) ) { return DWELLING_MONSTER4; } if ( HotKeyPressEvent( Game::HotKeyEvent::TOWN_DWELLING_LEVEL_5 ) ) { return DWELLING_MONSTER5; } if ( HotKeyPressEvent( Game::HotKeyEvent::TOWN_DWELLING_LEVEL_6 ) ) { return DWELLING_MONSTER6; } if ( HotKeyPressEvent( Game::HotKeyEvent::TOWN_MARKETPLACE ) ) { return BUILD_MARKETPLACE; } if ( HotKeyPressEvent( Game::HotKeyEvent::TOWN_WELL ) ) { return BUILD_WELL; } if ( HotKeyPressEvent( Game::HotKeyEvent::TOWN_MAGE_GUILD ) ) { return BUILD_MAGEGUILD; } if ( HotKeyPressEvent( Game::HotKeyEvent::TOWN_SHIPYARD ) ) { return BUILD_SHIPYARD; } if ( HotKeyPressEvent( Game::HotKeyEvent::TOWN_THIEVES_GUILD ) ) { return BUILD_THIEVESGUILD; } if ( HotKeyPressEvent( Game::HotKeyEvent::TOWN_TAVERN ) ) { return BUILD_TAVERN; } if ( HotKeyPressEvent( Game::HotKeyEvent::TOWN_CONSTRUCTION ) ) { return BUILD_CASTLE; } return BUILD_NOTHING; } std::string buildingStatusMessage( const int race, const uint32_t buildingId ) { // Check if building is a monster dwelling or its upgraded version if ( ( buildingId & DWELLING_MONSTERS ) == 0 && ( buildingId & DWELLING_UPGRADES ) == 0 ) { return fheroes2::getBuildingName( race, static_cast( buildingId ) ); } const Monster monster( race, buildingId ); std::string msgStatus = _( "Recruit %{name}" ); StringReplaceWithLowercase( msgStatus, "%{name}", monster.GetMultiName() ); return msgStatus; } void RedrawIcons( const Castle & castle, const Heroes * hero, const fheroes2::Point & pt ) { fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::Blit( fheroes2::AGG::GetICN( ICN::STRIP, 0 ), display, pt.x, pt.y + 256 ); if ( castle.isBuild( BUILD_CAPTAIN ) ) { const fheroes2::Sprite & captainImage = castle.GetCaptain().GetPortrait( PORT_BIG ); fheroes2::Copy( captainImage, 0, 0, display, pt.x + 5, pt.y + 262, captainImage.width(), captainImage.height() ); } else { const fheroes2::Sprite & crestImage = fheroes2::AGG::GetICN( ICN::CREST, Color::GetIndex( castle.GetColor() ) ); fheroes2::Copy( crestImage, 0, 0, display, pt.x + 5, pt.y + 262, crestImage.width(), crestImage.height() ); } if ( hero ) { hero->PortraitRedraw( pt.x + 5, pt.y + 361, PORT_BIG, display ); } else { const fheroes2::Sprite & heroImage = fheroes2::AGG::GetICN( ICN::STRIP, 3 ); const fheroes2::Sprite & backgroundImage = fheroes2::AGG::GetICN( ICN::STRIP, 11 ); fheroes2::Copy( heroImage, 0, 0, display, pt.x + 5, pt.y + 361, heroImage.width(), heroImage.height() ); fheroes2::Copy( backgroundImage, 0, 0, display, pt.x + 112, pt.y + 361, backgroundImage.width(), backgroundImage.height() ); } } std::string getDateString() { std::string output( _( "Month: %{month}, Week: %{week}, Day: %{day}" ) ); StringReplace( output, "%{month}", world.GetMonth() ); StringReplace( output, "%{week}", world.GetWeek() ); StringReplace( output, "%{day}", world.GetDay() ); return output; } bool purchaseSpellBookIfNecessary( const Castle & castle, Heroes * hero ) { if ( hero == nullptr || hero->HaveSpellBook() ) { return false; } if ( hero->IsFullBagArtifacts() ) { fheroes2::showStandardTextMessage( hero->GetName(), _( "You must purchase a spell book to use the mage guild, but you currently have no room for a spell book. Try giving one of your artifacts to another hero." ), Dialog::OK ); return false; } return hero->BuySpellBook( &castle ); } void openHeroDialog( ArmyBar & topArmyBar, ArmyBar & bottomArmyBar, Heroes & hero ) { Game::SetUpdateSoundsOnFocusUpdate( false ); Game::OpenHeroesDialog( hero, false, false ); if ( topArmyBar.isValid() && topArmyBar.isSelected() ) { topArmyBar.ResetSelected(); } if ( bottomArmyBar.isValid() && bottomArmyBar.isSelected() ) { bottomArmyBar.ResetSelected(); } } void generateHeroImage( fheroes2::Image & image, Heroes * hero ) { assert( hero != nullptr ); if ( image.empty() ) { image.resize( 552, 105 ); image.reset(); } fheroes2::Blit( fheroes2::AGG::GetICN( ICN::STRIP, 0 ), 0, 100, image, 0, 0, 552, 107 ); const fheroes2::Sprite & port = hero->GetPortrait( PORT_BIG ); fheroes2::Copy( port, 0, 0, image, 5, 5, port.width(), port.height() ); ArmyBar armyBar( &hero->GetArmy(), false, false ); armyBar.setTableSize( { 5, 1 } ); armyBar.setRenderingOffset( { 112, 5 } ); armyBar.setInBetweenItemsOffset( { 6, 0 } ); armyBar.Redraw( image ); } } Castle::CastleDialogReturnValue Castle::OpenDialog( const bool openConstructionWindow, const bool fade, const bool renderBackgroundDialog ) { // Set the cursor image. This dialog does not require a cursor restorer. It is called from other dialogs that have the same cursor // or from the Game Area that will set the appropriate cursor after this dialog is closed. Cursor::Get().SetThemes( Cursor::POINTER ); fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::Rect dialogRoi; fheroes2::Rect dialogWithShadowRoi; std::unique_ptr background; std::unique_ptr restorer; if ( renderBackgroundDialog ) { background = std::make_unique( fheroes2::Display::DEFAULT_WIDTH, fheroes2::Display::DEFAULT_HEIGHT, false ); dialogRoi = background->activeArea(); dialogWithShadowRoi = background->totalArea(); } else { dialogRoi = { ( display.width() - fheroes2::Display::DEFAULT_WIDTH ) / 2, ( display.height() - fheroes2::Display::DEFAULT_HEIGHT ) / 2, fheroes2::Display::DEFAULT_WIDTH, fheroes2::Display::DEFAULT_HEIGHT }; dialogWithShadowRoi = { dialogRoi.x - 2 * BORDERWIDTH, dialogRoi.y - BORDERWIDTH, dialogRoi.width + 3 * BORDERWIDTH, dialogRoi.height + 3 * BORDERWIDTH }; restorer = std::make_unique( display, dialogRoi.x, dialogRoi.y, dialogRoi.width, dialogRoi.height ); } // Fade-out game screen only for 640x480 resolution and if 'renderBackgroundDialog' is false (we are replacing image in already opened dialog). const bool isDefaultScreenSize = display.isDefaultSize(); if ( fade && ( isDefaultScreenSize || !renderBackgroundDialog ) ) { fheroes2::fadeOutDisplay( dialogRoi, !isDefaultScreenSize ); } AudioManager::PlayMusicAsync( MUS::FromRace( race ), Music::PlaybackMode::RESUME_AND_PLAY_INFINITE ); int alphaHero = 255; CastleDialog::FadeBuilding fadeBuilding; Heroes * hero = world.GetHero( *this ); fheroes2::Image surfaceHero; auto constructionDialogHandler = [this, &display, &dialogRoi, &fadeBuilding, &hero, &surfaceHero, &alphaHero]() { switch ( uint32_t build = BUILD_NOTHING; openConstructionDialog( build ) ) { case ConstructionDialogResult::NextConstructionWindow: return CastleDialogReturnValue::NextCostructionWindow; case ConstructionDialogResult::PrevConstructionWindow: return CastleDialogReturnValue::PreviousCostructionWindow; case ConstructionDialogResult::RecruitHero: hero = world.GetHero( *this ); generateHeroImage( surfaceHero, hero ); AudioManager::PlaySound( M82::BUILDTWN ); alphaHero = 0; break; default: if ( build != BUILD_NOTHING ) { AudioManager::PlaySound( M82::BUILDTWN ); fadeBuilding.StartFadeBuilding( build ); } break; } // Set next render ROI to dialog ROI. It is needed to properly update the castle dialog after the construction dialog is closed. display.updateNextRenderRoi( dialogRoi ); return CastleDialogReturnValue::DoNothing; }; if ( openConstructionWindow && isBuild( BUILD_CASTLE ) ) { const CastleDialogReturnValue constructionResult = constructionDialogHandler(); if ( constructionResult != CastleDialogReturnValue::DoNothing ) { return constructionResult; } } const std::string currentDate = getDateString(); // Previous castle button. fheroes2::Point statusBarPosition( dialogRoi.x, dialogRoi.y + 480 - 19 ); fheroes2::Button buttonPrevCastle( statusBarPosition.x, statusBarPosition.y, ICN::SMALLBAR, 1, 2 ); fheroes2::TimedEventValidator timedButtonPrevCastle( [&buttonPrevCastle]() { return buttonPrevCastle.isPressed(); } ); buttonPrevCastle.subscribe( &timedButtonPrevCastle ); statusBarPosition.x += buttonPrevCastle.area().width; // Status bar at the bottom of dialog. const fheroes2::Sprite & bar = fheroes2::AGG::GetICN( ICN::SMALLBAR, 0 ); fheroes2::Copy( bar, 0, 0, display, statusBarPosition.x, statusBarPosition.y, bar.width(), bar.height() ); StatusBar statusBar; // Status bar must be smaller due to extra art on both sides. statusBar.setRoi( { statusBarPosition.x + 16, statusBarPosition.y + 3, bar.width() - 16 * 2, 0 } ); // Next castle button. fheroes2::Button buttonNextCastle( statusBarPosition.x + bar.width(), statusBarPosition.y, ICN::SMALLBAR, 3, 4 ); fheroes2::TimedEventValidator timedButtonNextCastle( [&buttonNextCastle]() { return buttonNextCastle.isPressed(); } ); buttonNextCastle.subscribe( &timedButtonNextCastle ); // Position of the captain/crest and hero portrait to the left of army bars. const fheroes2::Sprite & crest = fheroes2::AGG::GetICN( ICN::CREST, Color::GetIndex( GetColor() ) ); const fheroes2::Rect rectSign1( dialogRoi.x + 5, dialogRoi.y + 262, crest.width(), crest.height() ); const fheroes2::Rect rectSign2( rectSign1.x, dialogRoi.y + 361, 100, 92 ); // For the case when new hero is recruited we need to render bottom army bar // without hero and his army to properly perform hero fade-in. RedrawIcons( *this, ( alphaHero == 0 ) ? nullptr : hero, dialogRoi.getPosition() ); auto setArmyBarParameters = []( ArmyBar & armyBar, const fheroes2::Point & offset ) { armyBar.setTableSize( { 5, 1 } ); armyBar.setRenderingOffset( offset ); armyBar.setInBetweenItemsOffset( { 6, 0 } ); }; // Castle army (top) bar. ArmyBar topArmyBar( &army, false, false ); setArmyBarParameters( topArmyBar, { dialogRoi.x + 112, rectSign1.y } ); topArmyBar.Redraw( display ); // Hero army (bottom) bar. ArmyBar bottomArmyBar( hero ? &hero->GetArmy() : nullptr, false, false ); setArmyBarParameters( bottomArmyBar, { dialogRoi.x + 112, rectSign2.y } ); if ( hero && alphaHero != 0 ) { // Draw bottom bar only if no hero fading animation is going. bottomArmyBar.Redraw( display ); } // Exit button. fheroes2::Button buttonExit( dialogRoi.x + 553, dialogRoi.y + 428, ICN::BUTTON_EXIT_TOWN, 0, 1 ); // Kingdom resources. const fheroes2::Rect & rectResource = fheroes2::drawResourcePanel( GetKingdom().GetFunds(), display, dialogRoi.getPosition() ); const fheroes2::Rect resActiveArea( rectResource.x, rectResource.y, rectResource.width, buttonExit.area().y - rectResource.y - 3 ); // Cache a vector of castle buildings in the order they are rendered. const CastleDialog::CacheBuildings cacheBuildings( *this, dialogRoi.getPosition() ); // Render castle buildings. CastleDialog::RedrawAllBuilding( *this, dialogRoi.getPosition(), cacheBuildings, fadeBuilding, castleAnimationIndex ); if ( GetKingdom().GetCastles().size() < 2 ) { buttonPrevCastle.disable(); buttonNextCastle.disable(); } buttonPrevCastle.draw(); buttonNextCastle.draw(); buttonExit.draw(); // Fade-in castle dialog. if ( fade ) { if ( renderBackgroundDialog && !isDefaultScreenSize ) { // We need to expand the ROI for the next render to properly render window borders and shadow. display.updateNextRenderRoi( dialogWithShadowRoi ); } // Use half fade if game resolution is not 640x480. fheroes2::fadeInDisplay( dialogRoi, !isDefaultScreenSize ); } else { display.render( dialogWithShadowRoi ); } CastleDialogReturnValue result = CastleDialogReturnValue::DoNothing; bool needRedraw = false; bool needFadeIn = false; // This variable must be declared out of the loop for performance reasons. std::string statusMessage; Game::passAnimationDelay( Game::CASTLE_AROUND_DELAY ); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() && result == CastleDialogReturnValue::DoNothing ) { // During hero purchase or building construction skip any interaction with the dialog. if ( alphaHero >= 255 && fadeBuilding.IsFadeDone() ) { if ( buttonPrevCastle.isEnabled() ) { le.MousePressLeft( buttonPrevCastle.area() ) ? buttonPrevCastle.drawOnPress() : buttonPrevCastle.drawOnRelease(); } if ( buttonNextCastle.isEnabled() ) { le.MousePressLeft( buttonNextCastle.area() ) ? buttonNextCastle.drawOnPress() : buttonNextCastle.drawOnRelease(); } le.MousePressLeft( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); // Check buttons for closing this castle's window. if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) { result = CastleDialogReturnValue::Close; // Fade-out castle dialog. fheroes2::fadeOutDisplay( dialogRoi, !isDefaultScreenSize ); break; } if ( buttonPrevCastle.isEnabled() && ( le.MouseClickLeft( buttonPrevCastle.area() ) || HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_LEFT ) || timedButtonPrevCastle.isDelayPassed() ) ) { result = CastleDialogReturnValue::PreviousCastle; break; } if ( buttonNextCastle.isEnabled() && ( le.MouseClickLeft( buttonNextCastle.area() ) || HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_RIGHT ) || timedButtonNextCastle.isDelayPassed() ) ) { result = CastleDialogReturnValue::NextCastle; break; } if ( le.MouseClickLeft( resActiveArea ) ) { const fheroes2::ButtonRestorer exitRestorer( buttonExit ); fheroes2::showKingdomIncome( GetKingdom(), Dialog::OK ); } else if ( le.MousePressRight( resActiveArea ) ) { fheroes2::showKingdomIncome( GetKingdom(), 0 ); } else if ( le.MousePressRight( buttonExit.area() ) ) { fheroes2::showStandardTextMessage( _( "Exit" ), _( "Exit this menu." ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonNextCastle.area() ) ) { fheroes2::showStandardTextMessage( _( "Show next town" ), _( "Click to show next town." ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonPrevCastle.area() ) ) { fheroes2::showStandardTextMessage( _( "Show previous town" ), _( "Click to show previous town." ), Dialog::ZERO ); } else if ( isBuild( BUILD_CAPTAIN ) && le.MousePressRight( rectSign1 ) ) { Dialog::QuickInfo( GetCaptain() ); } else if ( hero && le.MousePressRight( rectSign2 ) ) { Dialog::QuickInfo( *hero ); } // Army bar events processing. if ( ( bottomArmyBar.isValid() && ( ( le.MouseCursor( topArmyBar.GetArea() ) && topArmyBar.QueueEventProcessing( bottomArmyBar, &statusMessage ) ) || ( le.MouseCursor( bottomArmyBar.GetArea() ) && bottomArmyBar.QueueEventProcessing( topArmyBar, &statusMessage ) ) ) ) || ( !bottomArmyBar.isValid() && le.MouseCursor( topArmyBar.GetArea() ) && topArmyBar.QueueEventProcessing( &statusMessage ) ) ) { needRedraw = true; } // Actions with hero army. if ( hero ) { bool isArmyActionPerformed = false; // Preselecting of troop. const ArmyTroop * keep = nullptr; if ( topArmyBar.isSelected() ) { keep = topArmyBar.GetSelectedItem(); } else if ( bottomArmyBar.isSelected() ) { keep = bottomArmyBar.GetSelectedItem(); } if ( HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_DOWN ) ) { hero->GetArmy().MoveTroops( GetArmy(), keep ? keep->GetID() : Monster::UNKNOWN ); isArmyActionPerformed = true; } else if ( HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_UP ) ) { GetArmy().MoveTroops( hero->GetArmy(), keep ? keep->GetID() : Monster::UNKNOWN ); isArmyActionPerformed = true; } // Reset selection and schedule dialog redraw if any action modifying armies has been made. if ( isArmyActionPerformed ) { if ( topArmyBar.isSelected() ) { topArmyBar.ResetSelected(); } if ( bottomArmyBar.isSelected() ) { bottomArmyBar.ResetSelected(); } needRedraw = true; } } if ( hero && le.MouseClickLeft( rectSign2 ) ) { // View hero. openHeroDialog( topArmyBar, bottomArmyBar, *hero ); needFadeIn = true; needRedraw = true; } // Get pressed hotkey. const building_t hotKeyBuilding = getPressedBuildingHotkey(); // Interaction with buildings. // Animation queue starts from the lowest by Z-value buildings which means that they draw first and most likely overlap by the top buildings in the queue. // In this case we must revert the queue and finding the first suitable building. for ( auto it = cacheBuildings.crbegin(); it != cacheBuildings.crend(); ++it ) { if ( !isBuild( it->id ) ) { continue; } const uint32_t monsterDwelling = GetActualDwelling( it->id ); const bool isMonsterDwelling = ( monsterDwelling != BUILD_NOTHING ); if ( le.MousePressRight( it->coord ) ) { // Check mouse right click. if ( isMonsterDwelling ) { Dialog::DwellingInfo( Monster( race, it->id ), getMonstersInDwelling( it->id ) ); } else { fheroes2::showStandardTextMessage( GetStringBuilding( it->id ), GetDescriptionBuilding( it->id ), Dialog::ZERO ); } // Nothing we need to do after. break; } const bool isMagicGuild = ( BUILD_MAGEGUILD & it->id ) != 0; if ( le.MouseClickLeft( it->coord ) || hotKeyBuilding == it->id || ( isMagicGuild && hotKeyBuilding == BUILD_MAGEGUILD ) ) { if ( topArmyBar.isSelected() ) { topArmyBar.ResetSelected(); } if ( bottomArmyBar.isValid() && bottomArmyBar.isSelected() ) { bottomArmyBar.ResetSelected(); } if ( isMagicGuild ) { const fheroes2::ButtonRestorer exitRestorer( buttonExit ); if ( purchaseSpellBookIfNecessary( *this, hero ) ) { // Guest hero purchased the spellbook, redraw the resource panel needRedraw = true; } OpenMageGuild( hero ); } else if ( isMonsterDwelling ) { const fheroes2::ButtonRestorer exitRestorer( buttonExit ); const int32_t recruitMonsterWindowOffsetY = -65; const Troop monsterToRecruit = Dialog::RecruitMonster( Monster( race, monsterDwelling ), getMonstersInDwelling( it->id ), true, recruitMonsterWindowOffsetY ); if ( Castle::RecruitMonster( monsterToRecruit ) ) { needRedraw = true; } } else { switch ( it->id ) { case BUILD_THIEVESGUILD: Dialog::ThievesGuild( false ); break; case BUILD_TAVERN: { const fheroes2::ButtonRestorer exitRestorer( buttonExit ); OpenTavern(); break; } case BUILD_CAPTAIN: case BUILD_STATUE: case BUILD_WEL2: case BUILD_MOAT: case BUILD_SPEC: case BUILD_SHRINE: { const fheroes2::ButtonRestorer exitRestorer( buttonExit ); fheroes2::showStandardTextMessage( GetStringBuilding( it->id ), GetDescriptionBuilding( it->id ), Dialog::OK ); break; } case BUILD_SHIPYARD: { const fheroes2::ButtonRestorer exitRestorer( buttonExit ); if ( Dialog::OK == Dialog::BuyBoat( AllowBuyBoat( true ) ) ) { BuyBoat(); fadeBuilding.StartFadeBuilding( BUILD_SHIPYARD ); } break; } case BUILD_MARKETPLACE: { const fheroes2::ButtonRestorer exitRestorer( buttonExit ); Dialog::Marketplace( GetKingdom(), false ); needRedraw = true; break; } case BUILD_WELL: OpenWell(); needRedraw = true; break; case BUILD_TENT: { const fheroes2::ButtonRestorer exitRestorer( buttonExit ); if ( !Modes( ALLOWCASTLE ) ) { fheroes2::showStandardTextMessage( _( "Town" ), _( "This town may not be upgraded to a castle." ), Dialog::OK ); } else if ( Dialog::OK == DialogBuyCastle( true ) ) { AudioManager::PlaySound( M82::BUILDTWN ); fadeBuilding.StartFadeBuilding( BUILD_CASTLE ); } break; } case BUILD_CASTLE: { const bool noHeroInCastle = ( hero == nullptr ); result = constructionDialogHandler(); if ( noHeroInCastle && hero != nullptr ) { // A hero has been recruited. needRedraw = true; } break; } default: // Some new building? assert( 0 ); break; } } break; } } } if ( result != CastleDialogReturnValue::DoNothing ) { break; } if ( alphaHero < 255 && Game::validateAnimationDelay( Game::CASTLE_BUYHERO_DELAY ) ) { alphaHero += 10; if ( alphaHero >= 255 ) { alphaHero = 255; // Hero fade-in animation is finished, we can set up his army bar. bottomArmyBar.SetArmy( &hero->GetArmy() ); } fheroes2::AlphaBlit( surfaceHero, display, dialogRoi.x, dialogRoi.y + 356, static_cast( alphaHero ) ); if ( !needRedraw ) { display.render( dialogRoi ); } } if ( needRedraw ) { // Redraw the bottom part of the castle dialog (army bars, resources, exit button). topArmyBar.Redraw( display ); if ( bottomArmyBar.isValid() && alphaHero >= 255 ) { bottomArmyBar.Redraw( display ); } fheroes2::drawResourcePanel( GetKingdom().GetFunds(), display, dialogRoi.getPosition() ); if ( buttonExit.isPressed() ) { buttonExit.draw(); } if ( needFadeIn ) { needFadeIn = false; fheroes2::fadeInDisplay( dialogRoi, !isDefaultScreenSize ); } else { display.render( dialogRoi ); } } // Update status bar. It doesn't depend on animation status. // Animation queue starts from the lowest by Z-value buildings which means that they draw first and most likely overlap by the top buildings in the queue. // In this case we must revert the queue and finding the first suitable building. for ( auto it = cacheBuildings.crbegin(); it != cacheBuildings.crend(); ++it ) { if ( isBuild( it->id ) && le.MouseCursor( it->coord ) ) { statusMessage = buildingStatusMessage( race, it->id ); break; } } if ( le.MouseCursor( buttonExit.area() ) ) { statusMessage = isCastle() ? _( "Exit Castle" ) : _( "Exit Town" ); } else if ( le.MouseCursor( resActiveArea ) ) { statusMessage = _( "Show Income" ); } else if ( buttonPrevCastle.isEnabled() && le.MouseCursor( buttonPrevCastle.area() ) ) { statusMessage = _( "Show previous town" ); } else if ( buttonNextCastle.isEnabled() && le.MouseCursor( buttonNextCastle.area() ) ) { statusMessage = _( "Show next town" ); } else if ( hero && le.MouseCursor( rectSign2 ) ) { statusMessage = _( "View Hero" ); } if ( statusMessage.empty() ) { statusBar.ShowMessage( currentDate ); } else { statusBar.ShowMessage( statusMessage ); statusMessage.clear(); } needRedraw = fadeBuilding.UpdateFadeBuilding(); if ( fadeBuilding.IsFadeDone() ) { const uint32_t build = fadeBuilding.GetBuild(); if ( build != BUILD_NOTHING ) { BuyBuilding( build ); if ( BUILD_CAPTAIN == build ) { RedrawIcons( *this, hero, dialogRoi.getPosition() ); fheroes2::drawCastleName( *this, fheroes2::Display::instance(), dialogRoi.getPosition() ); } fheroes2::drawResourcePanel( GetKingdom().GetFunds(), display, dialogRoi.getPosition() ); display.render( dialogRoi ); } fadeBuilding.StopFadeBuilding(); } else if ( fadeBuilding.GetBuild() == BUILD_CAPTAIN ) { // Fade-in the captain image while fading-in his quarters. const fheroes2::Sprite & crestImage = fheroes2::AGG::GetICN( ICN::CREST, Color::GetIndex( GetColor() ) ); fheroes2::Copy( crestImage, 0, 0, display, rectSign1.x, rectSign1.y, crestImage.width(), crestImage.height() ); const fheroes2::Sprite & captainImage = GetCaptain().GetPortrait( PORT_BIG ); fheroes2::AlphaBlit( captainImage, display, rectSign1.x, rectSign1.y, fadeBuilding.GetAlpha() ); } // Castle dialog animation. if ( Game::validateAnimationDelay( Game::CASTLE_AROUND_DELAY ) || needRedraw ) { CastleDialog::RedrawAllBuilding( *this, dialogRoi.getPosition(), cacheBuildings, fadeBuilding, castleAnimationIndex ); display.render( dialogRoi ); if ( needRedraw ) { needRedraw = false; } else { ++castleAnimationIndex; } } } const uint32_t build = fadeBuilding.GetBuild(); if ( build != BUILD_NOTHING ) { BuyBuilding( build ); } Game::SetUpdateSoundsOnFocusUpdate( true ); return result; } fheroes2-1.0.12+dfsg/src/fheroes2/castle/castle_mageguild.cpp000066400000000000000000000242541456075706000240240ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include "agg_image.h" #include "castle.h" #include "cursor.h" #include "dialog.h" #include "game_hotkeys.h" #include "heroes.h" #include "icn.h" #include "image.h" #include "localevent.h" #include "mageguild.h" #include "math_base.h" #include "race.h" #include "screen.h" #include "settings.h" #include "spell.h" #include "spell_storage.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_text.h" namespace { const int32_t bottomBarOffsetY = 461; class RowSpells { public: RowSpells( const fheroes2::Point & pos, const Castle & castle, const int lvl ); void Redraw( fheroes2::Image & output ); bool QueueEventProcessing(); private: std::vector coords; SpellStorage spells; }; } RowSpells::RowSpells( const fheroes2::Point & pos, const Castle & castle, const int lvl ) { const bool hide = castle.GetLevelMageGuild() < lvl; const fheroes2::Sprite & roll_show = fheroes2::AGG::GetICN( ICN::TOWNWIND, 0 ); const fheroes2::Sprite & roll_hide = fheroes2::AGG::GetICN( ICN::TOWNWIND, 1 ); const fheroes2::Sprite & roll = ( hide ? roll_hide : roll_show ); int32_t count = 0; switch ( lvl ) { case 1: case 2: count = 3; break; case 3: case 4: count = 2; break; case 5: count = 1; break; default: break; } for ( int32_t i = 0; i < count; ++i ) coords.emplace_back( pos.x + i * 110 - roll.width() / 2, pos.y, roll.width(), roll.height() ); if ( castle.HaveLibraryCapability() ) { if ( !hide && castle.isLibraryBuild() ) coords.emplace_back( pos.x + count * 110 - roll_show.width() / 2, pos.y, roll_show.width(), roll_show.height() ); else coords.emplace_back( pos.x + count * 110 - roll_hide.width() / 2, pos.y, roll_hide.width(), roll_hide.height() ); } spells.reserve( 6 ); spells = castle.GetMageGuild().GetSpells( castle.GetLevelMageGuild(), castle.isLibraryBuild(), lvl ); spells.resize( coords.size(), Spell::NONE ); } void RowSpells::Redraw( fheroes2::Image & output ) { const fheroes2::Sprite & roll_show = fheroes2::AGG::GetICN( ICN::TOWNWIND, 0 ); for ( std::vector::iterator it = coords.begin(); it != coords.end(); ++it ) { const fheroes2::Rect & dst = ( *it ); const Spell & spell = spells[std::distance( coords.begin(), it )]; // roll hide if ( dst.width < roll_show.width() || spell == Spell::NONE ) { const fheroes2::Sprite & roll_hide = fheroes2::AGG::GetICN( ICN::TOWNWIND, 1 ); fheroes2::Blit( roll_hide, output, dst.x, dst.y ); } // roll show else { fheroes2::Blit( roll_show, output, dst.x, dst.y ); const fheroes2::Sprite & icon = fheroes2::AGG::GetICN( ICN::SPELLS, spell.IndexSprite() ); fheroes2::Blit( icon, output, dst.x + 3 + ( dst.width - icon.width() ) / 2, dst.y + 31 - icon.height() / 2 ); const fheroes2::Text text( spell.GetName(), fheroes2::FontType::smallWhite() ); text.draw( dst.x + 18, dst.y + 57, 78, fheroes2::Display::instance() ); } } } bool RowSpells::QueueEventProcessing() { LocalEvent & le = LocalEvent::Get(); const int32_t index = GetRectIndex( coords, le.GetMouseCursor() ); if ( 0 <= index && ( le.MouseClickLeft() || le.MousePressRight() ) ) { const Spell & spell = spells[index]; if ( spell != Spell::NONE ) { fheroes2::SpellDialogElement( spell, nullptr ).showPopup( le.MousePressRight() ? Dialog::ZERO : Dialog::OK ); } } return 0 <= index; } void Castle::OpenMageGuild( const Heroes * hero ) const { fheroes2::Display & display = fheroes2::Display::instance(); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); const fheroes2::ImageRestorer restorer( display, ( display.width() - fheroes2::Display::DEFAULT_WIDTH ) / 2, ( display.height() - fheroes2::Display::DEFAULT_HEIGHT ) / 2, fheroes2::Display::DEFAULT_WIDTH, fheroes2::Display::DEFAULT_HEIGHT ); const fheroes2::Point cur_pt( restorer.x(), restorer.y() ); fheroes2::Point dst_pt( cur_pt.x, cur_pt.y ); const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); fheroes2::Blit( fheroes2::AGG::GetICN( isEvilInterface ? ICN::STONEBAK_EVIL : ICN::STONEBAK, 0 ), display, cur_pt.x, cur_pt.y ); // status bar const int32_t exitWidth = fheroes2::AGG::GetICN( ICN::BUTTON_GUILDWELL_EXIT, 0 ).width(); dst_pt.x = cur_pt.x; dst_pt.y = cur_pt.y + bottomBarOffsetY; // The original ICN::WELLXTRA image does not have a yellow outer frame. const fheroes2::Sprite & bottomBar = fheroes2::AGG::GetICN( ICN::SMALLBAR, 0 ); const int32_t barHeight = bottomBar.height(); // ICN::SMALLBAR image's first column contains all black pixels. This should not be drawn. fheroes2::Copy( bottomBar, 1, 0, display, dst_pt.x, dst_pt.y, fheroes2::Display::DEFAULT_WIDTH / 2, barHeight ); fheroes2::Copy( bottomBar, bottomBar.width() - fheroes2::Display::DEFAULT_WIDTH / 2 + exitWidth - 1, 0, display, dst_pt.x + fheroes2::Display::DEFAULT_WIDTH / 2, dst_pt.y, fheroes2::Display::DEFAULT_WIDTH / 2 - exitWidth + 1, barHeight ); // text bar const char * textAlternative; if ( hero == nullptr || !hero->HaveSpellBook() ) { textAlternative = _( "The above spells are available here." ); } else { textAlternative = _( "The spells the hero can learn have been added to their book." ); } fheroes2::Text statusText( textAlternative, fheroes2::FontType::normalWhite() ); statusText.draw( cur_pt.x + ( fheroes2::Display::DEFAULT_WIDTH - exitWidth ) / 2 - statusText.width() / 2, cur_pt.y + 464, display ); const int level = GetLevelMageGuild(); // sprite int icn = ICN::UNKNOWN; switch ( race ) { case Race::KNGT: icn = ICN::MAGEGLDK; break; case Race::BARB: icn = ICN::MAGEGLDB; break; case Race::SORC: icn = ICN::MAGEGLDS; break; case Race::WRLK: icn = ICN::MAGEGLDW; break; case Race::WZRD: icn = ICN::MAGEGLDZ; break; case Race::NECR: icn = ICN::MAGEGLDN; break; default: break; } const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( icn, level - 1 ); const fheroes2::Rect area = fheroes2::GetActiveROI( sprite ); fheroes2::Point inPos( 0, 0 ); fheroes2::Point outPos( cur_pt.x + 100 - area.x - area.width / 2, cur_pt.y + 290 - sprite.height() ); fheroes2::Size inSize( sprite.width(), sprite.height() ); if ( fheroes2::FitToRoi( sprite, inPos, display, outPos, inSize, { cur_pt.x, cur_pt.y, 200, fheroes2::Display::DEFAULT_HEIGHT } ) ) { fheroes2::Blit( sprite, inPos, display, outPos, inSize ); } RowSpells spells5( { cur_pt.x + 250, cur_pt.y + 5 }, *this, 5 ); RowSpells spells4( { cur_pt.x + 250, cur_pt.y + 95 }, *this, 4 ); RowSpells spells3( { cur_pt.x + 250, cur_pt.y + 185 }, *this, 3 ); RowSpells spells2( { cur_pt.x + 250, cur_pt.y + 275 }, *this, 2 ); RowSpells spells1( { cur_pt.x + 250, cur_pt.y + 365 }, *this, 1 ); spells1.Redraw( display ); spells2.Redraw( display ); spells3.Redraw( display ); spells4.Redraw( display ); spells5.Redraw( display ); fheroes2::Button buttonExit( cur_pt.x + fheroes2::Display::DEFAULT_WIDTH - exitWidth, cur_pt.y + bottomBarOffsetY, ICN::BUTTON_GUILDWELL_EXIT, 0, 1 ); buttonExit.draw(); display.render(); LocalEvent & le = LocalEvent::Get(); // message loop while ( le.HandleEvents() ) { le.MousePressLeft( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) break; spells1.QueueEventProcessing() || spells2.QueueEventProcessing() || spells3.QueueEventProcessing() || spells4.QueueEventProcessing() || spells5.QueueEventProcessing(); if ( le.MousePressRight( buttonExit.area() ) ) { fheroes2::Text header( _( "Exit" ), fheroes2::FontType::normalYellow() ); fheroes2::Text body( _( "Exit this menu." ), fheroes2::FontType::normalWhite() ); fheroes2::showMessage( header, body, 0 ); } } } fheroes2-1.0.12+dfsg/src/fheroes2/castle/castle_tavern.cpp000066400000000000000000000050741456075706000233640ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include "castle.h" #include "dialog.h" #include "game_delays.h" #include "icn.h" #include "translations.h" #include "ui_dialog.h" #include "ui_text.h" #include "world.h" void Castle::OpenTavern() const { std::string body( _( "A generous tip for the barkeep yields the following rumor:" ) ); body += "\n\n"; body += world.getCurrentRumor(); const fheroes2::AnimationDialogElement imageUI( ICN::TAVWIN, { 0, 1 }, 0, Game::getAnimationDelayValue( Game::CASTLE_TAVERN_DELAY ) ); const fheroes2::TextDialogElement textBodyUI( std::make_shared( std::move( body ), fheroes2::FontType::normalWhite() ) ); fheroes2::showMessage( fheroes2::Text( GetStringBuilding( BUILD_TAVERN ), fheroes2::FontType::normalYellow() ), fheroes2::Text( "", {} ), Dialog::OK, { &imageUI, &textBodyUI } ); } fheroes2-1.0.12+dfsg/src/fheroes2/castle/castle_town.cpp000066400000000000000000001017771456075706000230630ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include "agg_image.h" #include "army.h" #include "artifact.h" #include "buildinginfo.h" #include "captain.h" #include "castle.h" #include "cursor.h" #include "dialog.h" #include "game_hotkeys.h" #include "heroes.h" #include "heroes_base.h" #include "heroes_recruits.h" #include "icn.h" #include "image.h" #include "kingdom.h" #include "localevent.h" #include "maps_fileinfo.h" #include "math_base.h" #include "payment.h" #include "race.h" #include "resource.h" #include "screen.h" #include "settings.h" #include "skill.h" #include "statusbar.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_castle.h" #include "ui_dialog.h" #include "ui_kingdom.h" #include "ui_text.h" #include "ui_tool.h" #include "world.h" int Castle::DialogBuyHero( const Heroes * hero ) const { if ( !hero ) { return Dialog::CANCEL; } fheroes2::Display & display = fheroes2::Display::instance(); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); const int32_t spacer = 10; const fheroes2::Sprite & portrait_frame = fheroes2::AGG::GetICN( ICN::SURRENDR, 4 ); const fheroes2::Text recruitHeroText( _( "Recruit Hero" ), fheroes2::FontType::normalYellow() ); uint32_t count = hero->GetCountArtifacts(); if ( hero->hasArtifact( Artifact::MAGIC_BOOK ) ) { --count; } std::string str = _( "%{name} is a level %{value} %{race} " ); if ( count ) { str += count > 1 ? _( "with %{count} artifacts." ) : _( "with 1 artifact." ); } else { str += _( "without artifacts." ); } StringReplace( str, "%{name}", hero->GetName() ); StringReplace( str, "%{value}", hero->GetLevel() ); StringReplace( str, "%{race}", Race::String( hero->GetRace() ) ); StringReplace( str, "%{count}", count ); const fheroes2::Text heroDescriptionText( std::move( str ), fheroes2::FontType::normalWhite() ); Resource::BoxSprite rbs( PaymentConditions::RecruitHero(), BOXAREA_WIDTH ); const int32_t dialogHeight = recruitHeroText.height( BOXAREA_WIDTH ) + spacer + portrait_frame.height() + spacer + heroDescriptionText.height( BOXAREA_WIDTH ) + spacer + rbs.GetArea().height; const Dialog::FrameBox box( dialogHeight, true ); const fheroes2::Rect & dialogRoi = box.GetArea(); recruitHeroText.draw( dialogRoi.x, dialogRoi.y + 2, BOXAREA_WIDTH, display ); // portrait and frame fheroes2::Point pos{ dialogRoi.x + ( dialogRoi.width - portrait_frame.width() ) / 2, dialogRoi.y + recruitHeroText.height( BOXAREA_WIDTH ) + spacer }; fheroes2::Blit( portrait_frame, display, pos.x, pos.y ); const fheroes2::Rect heroPortraitArea( pos.x, pos.y, portrait_frame.width(), portrait_frame.height() ); pos.x = pos.x + 5; pos.y = pos.y + 6; hero->PortraitRedraw( pos.x, pos.y, PORT_BIG, display ); pos.y += portrait_frame.height() + spacer; heroDescriptionText.draw( dialogRoi.x, pos.y + 2, BOXAREA_WIDTH, display ); rbs.SetPos( dialogRoi.x, pos.y + heroDescriptionText.height( BOXAREA_WIDTH ) + spacer ); rbs.Redraw(); const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); const int okayButtonIcnID = isEvilInterface ? ICN::UNIFORM_EVIL_OKAY_BUTTON : ICN::UNIFORM_GOOD_OKAY_BUTTON; pos.y = dialogRoi.y + dialogRoi.height - fheroes2::AGG::GetICN( okayButtonIcnID, 0 ).height(); fheroes2::Button buttonOkay( dialogRoi.x, pos.y, okayButtonIcnID, 0, 1 ); if ( !AllowBuyHero() ) { buttonOkay.disable(); } const int cancelButtonIcnID = isEvilInterface ? ICN::UNIFORM_EVIL_CANCEL_BUTTON : ICN::UNIFORM_GOOD_CANCEL_BUTTON; pos.x = dialogRoi.x + dialogRoi.width - fheroes2::AGG::GetICN( cancelButtonIcnID, 0 ).width(); pos.y = dialogRoi.y + dialogRoi.height - fheroes2::AGG::GetICN( cancelButtonIcnID, 0 ).height(); fheroes2::Button buttonCancel( pos.x, pos.y, cancelButtonIcnID, 0, 1 ); buttonOkay.draw(); buttonCancel.draw(); display.render(); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { le.MousePressLeft( buttonOkay.area() ) ? buttonOkay.drawOnPress() : buttonOkay.drawOnRelease(); le.MousePressLeft( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); if ( buttonOkay.isEnabled() && ( le.MouseClickLeft( buttonOkay.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) ) ) { return Dialog::OK; } if ( le.MouseClickLeft( buttonCancel.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { break; } if ( le.MousePressRight( heroPortraitArea ) ) { Dialog::QuickInfo( *hero ); } else if ( le.MousePressRight( buttonOkay.area() ) ) { std::string recruitHero = _( "Recruit %{name} the %{race}" ); StringReplace( recruitHero, "%{name}", hero->GetName() ); StringReplace( recruitHero, "%{race}", Race::String( hero->GetRace() ) ); recruitHero += '.'; fheroes2::showStandardTextMessage( _( "Okay" ), std::move( recruitHero ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonCancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Exit this menu without doing anything." ), Dialog::ZERO ); } } return Dialog::CANCEL; } int Castle::DialogBuyCastle( bool buttons ) const { const BuildingInfo info( *this, BUILD_CASTLE ); return info.DialogBuyBuilding( buttons ) ? Dialog::OK : Dialog::CANCEL; } Castle::ConstructionDialogResult Castle::openConstructionDialog( uint32_t & dwellingTobuild ) { if ( !isBuild( BUILD_CASTLE ) ) { // It is not possible to open this dialog without a built castle! assert( 0 ); return ConstructionDialogResult::DoNothing; } dwellingTobuild = BUILD_NOTHING; // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::ImageRestorer restorer( display, ( display.width() - fheroes2::Display::DEFAULT_WIDTH ) / 2, ( display.height() - fheroes2::Display::DEFAULT_HEIGHT ) / 2, fheroes2::Display::DEFAULT_WIDTH, fheroes2::Display::DEFAULT_HEIGHT ); const fheroes2::Point cur_pt( restorer.x(), restorer.y() ); fheroes2::Point dst_pt( cur_pt ); const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); fheroes2::Blit( fheroes2::AGG::GetICN( isEvilInterface ? ICN::CASLWIND_EVIL : ICN::CASLWIND, 0 ), display, dst_pt.x, dst_pt.y ); // hide captain options if ( !( building & BUILD_CAPTAIN ) ) { dst_pt.x = 530; dst_pt.y = 163; const fheroes2::Rect rect( dst_pt.x, dst_pt.y, 110, 84 ); dst_pt.x += cur_pt.x; dst_pt.y += cur_pt.y; const fheroes2::Sprite & backgroundImage = fheroes2::AGG::GetICN( isEvilInterface ? ICN::STONEBAK_EVIL : ICN::STONEBAK, 0 ); fheroes2::Copy( backgroundImage, rect.x, rect.y, display, dst_pt.x, dst_pt.y, rect.width, rect.height ); } // draw castle sprite dst_pt.x = cur_pt.x + 459; dst_pt.y = cur_pt.y + 5; DrawImageCastle( dst_pt ); // castle name const fheroes2::Text castleName( GetName(), fheroes2::FontType::smallWhite() ); castleName.draw( cur_pt.x + 538 - castleName.width() / 2, cur_pt.y + 3, display ); dst_pt.y = cur_pt.y + 2; BuildingInfo dwelling1( *this, DWELLING_MONSTER1 ); dwelling1.SetPos( cur_pt.x + 5, dst_pt.y ); dwelling1.Redraw(); BuildingInfo dwelling2( *this, DWELLING_MONSTER2 ); dwelling2.SetPos( cur_pt.x + 149, dst_pt.y ); dwelling2.Redraw(); BuildingInfo dwelling3( *this, DWELLING_MONSTER3 ); dwelling3.SetPos( cur_pt.x + 293, dst_pt.y ); dwelling3.Redraw(); dst_pt.y = cur_pt.y + 77; BuildingInfo dwelling4( *this, DWELLING_MONSTER4 ); dwelling4.SetPos( cur_pt.x + 5, dst_pt.y ); dwelling4.Redraw(); BuildingInfo dwelling5( *this, DWELLING_MONSTER5 ); dwelling5.SetPos( cur_pt.x + 149, dst_pt.y ); dwelling5.Redraw(); BuildingInfo dwelling6( *this, DWELLING_MONSTER6 ); dwelling6.SetPos( cur_pt.x + 293, dst_pt.y ); dwelling6.Redraw(); // mage guild building_t level = BUILD_NOTHING; switch ( GetLevelMageGuild() ) { case 0: level = BUILD_MAGEGUILD1; break; case 1: level = BUILD_MAGEGUILD2; break; case 2: level = BUILD_MAGEGUILD3; break; case 3: level = BUILD_MAGEGUILD4; break; default: level = BUILD_MAGEGUILD5; break; } dst_pt.y = cur_pt.y + 157; BuildingInfo buildingMageGuild( *this, level ); buildingMageGuild.SetPos( cur_pt.x + 5, dst_pt.y ); buildingMageGuild.Redraw(); // tavern const bool isSkipTavernInteraction = ( Race::NECR == race ) && ( Settings::Get().getCurrentMapInfo().version == GameVersion::SUCCESSION_WARS ); BuildingInfo buildingTavern( *this, BUILD_TAVERN ); buildingTavern.SetPos( cur_pt.x + 149, dst_pt.y ); buildingTavern.Redraw(); // thieves guild BuildingInfo buildingThievesGuild( *this, BUILD_THIEVESGUILD ); buildingThievesGuild.SetPos( cur_pt.x + 293, dst_pt.y ); buildingThievesGuild.Redraw(); dst_pt.y = cur_pt.y + 232; // shipyard BuildingInfo buildingShipyard( *this, BUILD_SHIPYARD ); buildingShipyard.SetPos( cur_pt.x + 5, dst_pt.y ); buildingShipyard.Redraw(); // statue BuildingInfo buildingStatue( *this, BUILD_STATUE ); buildingStatue.SetPos( cur_pt.x + 149, dst_pt.y ); buildingStatue.Redraw(); // marketplace BuildingInfo buildingMarketplace( *this, BUILD_MARKETPLACE ); buildingMarketplace.SetPos( cur_pt.x + 293, dst_pt.y ); buildingMarketplace.Redraw(); dst_pt.y = cur_pt.y + 307; // well BuildingInfo buildingWell( *this, BUILD_WELL ); buildingWell.SetPos( cur_pt.x + 5, dst_pt.y ); buildingWell.Redraw(); // wel2 BuildingInfo buildingWel2( *this, BUILD_WEL2 ); buildingWel2.SetPos( cur_pt.x + 149, dst_pt.y ); buildingWel2.Redraw(); // spec BuildingInfo buildingSpec( *this, BUILD_SPEC ); buildingSpec.SetPos( cur_pt.x + 293, dst_pt.y ); buildingSpec.Redraw(); dst_pt.y = cur_pt.y + 387; // left turret BuildingInfo buildingLTurret( *this, BUILD_LEFTTURRET ); buildingLTurret.SetPos( cur_pt.x + 5, dst_pt.y ); buildingLTurret.Redraw(); // right turret BuildingInfo buildingRTurret( *this, BUILD_RIGHTTURRET ); buildingRTurret.SetPos( cur_pt.x + 149, dst_pt.y ); buildingRTurret.Redraw(); // moat BuildingInfo buildingMoat( *this, BUILD_MOAT ); buildingMoat.SetPos( cur_pt.x + 293, dst_pt.y ); buildingMoat.Redraw(); // captain BuildingInfo buildingCaptain( *this, BUILD_CAPTAIN ); buildingCaptain.SetPos( cur_pt.x + 444, cur_pt.y + 165 ); buildingCaptain.Redraw(); // combat format const fheroes2::Sprite & spriteSpreadArmyFormat = fheroes2::AGG::GetICN( ICN::HSICONS, 9 ); const fheroes2::Sprite & spriteGroupedArmyFormat = fheroes2::AGG::GetICN( ICN::HSICONS, 10 ); const fheroes2::Rect rectSpreadArmyFormat( cur_pt.x + 550, cur_pt.y + 220, spriteSpreadArmyFormat.width(), spriteSpreadArmyFormat.height() ); const fheroes2::Rect rectGroupedArmyFormat( cur_pt.x + 585, cur_pt.y + 220, spriteGroupedArmyFormat.width(), spriteGroupedArmyFormat.height() ); const std::string descriptionSpreadArmyFormat( _( "'Spread' combat formation spreads the castle's units from the top to the bottom of the battlefield, with at least one empty space between each unit." ) ); const std::string descriptionGroupedArmyFormat( _( "'Grouped' combat formation bunches the castle's units together in the center of the castle's side of the battlefield." ) ); const fheroes2::Point pointSpreadArmyFormat( rectSpreadArmyFormat.x - 1, rectSpreadArmyFormat.y - 1 ); const fheroes2::Point pointGroupedArmyFormat( rectGroupedArmyFormat.x - 1, rectGroupedArmyFormat.y - 1 ); const fheroes2::Rect armyFormatRenderRect( pointSpreadArmyFormat, { rectGroupedArmyFormat.x + rectGroupedArmyFormat.width - pointSpreadArmyFormat.x + 1, rectGroupedArmyFormat.y + rectGroupedArmyFormat.height - pointSpreadArmyFormat.y + 1 } ); fheroes2::MovableSprite cursorFormat( fheroes2::AGG::GetICN( ICN::HSICONS, 11 ) ); if ( isBuild( BUILD_CAPTAIN ) ) { const int32_t skillValueOffsetX = 90; const int32_t skillOffsetY = 12; fheroes2::Text text( Skill::Primary::String( Skill::Primary::ATTACK ), fheroes2::FontType::smallWhite() ); dst_pt.x = cur_pt.x + 535; dst_pt.y = cur_pt.y + 170; text.draw( dst_pt.x, dst_pt.y, display ); text.set( std::to_string( captain.GetAttack() ), fheroes2::FontType::smallWhite() ); text.draw( dst_pt.x + skillValueOffsetX, dst_pt.y, display ); dst_pt.y += skillOffsetY; text.set( Skill::Primary::String( Skill::Primary::DEFENSE ), fheroes2::FontType::smallWhite() ); text.draw( dst_pt.x, dst_pt.y, display ); text.set( std::to_string( captain.GetDefense() ), fheroes2::FontType::smallWhite() ); text.draw( dst_pt.x + skillValueOffsetX, dst_pt.y, display ); dst_pt.y += skillOffsetY; text.set( Skill::Primary::String( Skill::Primary::POWER ), fheroes2::FontType::smallWhite() ); text.draw( dst_pt.x, dst_pt.y, display ); text.set( std::to_string( captain.GetPower() ), fheroes2::FontType::smallWhite() ); text.draw( dst_pt.x + skillValueOffsetX, dst_pt.y, display ); dst_pt.y += skillOffsetY; text.set( Skill::Primary::String( Skill::Primary::KNOWLEDGE ), fheroes2::FontType::smallWhite() ); text.draw( dst_pt.x, dst_pt.y, display ); text.set( std::to_string( captain.GetKnowledge() ), fheroes2::FontType::smallWhite() ); text.draw( dst_pt.x + skillValueOffsetX, dst_pt.y, display ); fheroes2::Copy( spriteSpreadArmyFormat, 0, 0, display, rectSpreadArmyFormat.x, rectSpreadArmyFormat.y, rectSpreadArmyFormat.width, rectSpreadArmyFormat.height ); fheroes2::Copy( spriteGroupedArmyFormat, 0, 0, display, rectGroupedArmyFormat.x, rectGroupedArmyFormat.y, rectGroupedArmyFormat.width, rectGroupedArmyFormat.height ); if ( army.isSpreadFormation() ) { cursorFormat.setPosition( pointSpreadArmyFormat.x, pointSpreadArmyFormat.y ); } else { cursorFormat.setPosition( pointGroupedArmyFormat.x, pointGroupedArmyFormat.y ); } } Kingdom & kingdom = GetKingdom(); Heroes * hero1 = kingdom.GetRecruits().GetHero1(); Heroes * hero2 = kingdom.GetRecruits().GetHero2(); std::string not_allow1_msg; std::string not_allow2_msg; const bool allow_buy_hero1 = hero1 ? AllowBuyHero( ¬_allow1_msg ) : false; const bool allow_buy_hero2 = hero2 ? AllowBuyHero( ¬_allow2_msg ) : false; // first hero dst_pt.x = cur_pt.x + 443; dst_pt.y = cur_pt.y + 260; const fheroes2::Rect rectHero1( dst_pt.x, dst_pt.y, 102, 93 ); if ( hero1 ) { hero1->PortraitRedraw( dst_pt.x, dst_pt.y, PORT_BIG, display ); } else { fheroes2::Image noHeroPortrait; noHeroPortrait._disableTransformLayer(); noHeroPortrait.resize( rectHero1.width, rectHero1.height ); noHeroPortrait.fill( 0 ); fheroes2::Copy( noHeroPortrait, 0, 0, display, rectHero1.x, rectHero1.y, rectHero1.width, rectHero1.height ); } // indicator if ( !allow_buy_hero1 ) { const fheroes2::Sprite & spriteDeny = fheroes2::AGG::GetICN( ICN::TOWNWIND, 12 ); fheroes2::Blit( spriteDeny, display, dst_pt.x + 102 - 4 + 1 - spriteDeny.width(), dst_pt.y + 93 - 2 - spriteDeny.height() ); } // second hero dst_pt.x = cur_pt.x + 443; dst_pt.y = cur_pt.y + 363; const fheroes2::Rect rectHero2( dst_pt.x, dst_pt.y, 102, 94 ); if ( hero2 ) { hero2->PortraitRedraw( dst_pt.x, dst_pt.y, PORT_BIG, display ); } else { fheroes2::Image noHeroPortrait; noHeroPortrait._disableTransformLayer(); noHeroPortrait.resize( rectHero2.width, rectHero2.height ); noHeroPortrait.fill( 0 ); fheroes2::Copy( noHeroPortrait, 0, 0, display, rectHero2.x, rectHero2.y, rectHero2.width, rectHero2.height ); } // indicator if ( !allow_buy_hero2 ) { const fheroes2::Sprite & spriteDeny = fheroes2::AGG::GetICN( ICN::TOWNWIND, 12 ); fheroes2::Blit( spriteDeny, display, dst_pt.x + 102 - 4 + 1 - spriteDeny.width(), dst_pt.y + 93 - 2 - spriteDeny.height() ); } dst_pt.y = cur_pt.y + 480 - 19; fheroes2::Button buttonPrevCastle( cur_pt.x, dst_pt.y, ICN::SMALLBAR, 1, 2 ); fheroes2::TimedEventValidator timedButtonPrevCastle( [&buttonPrevCastle]() { return buttonPrevCastle.isPressed(); } ); buttonPrevCastle.subscribe( &timedButtonPrevCastle ); const fheroes2::Rect buttonPrevCastleArea( buttonPrevCastle.area() ); // bottom small bar const fheroes2::Sprite & bar = fheroes2::AGG::GetICN( ICN::SMALLBAR, 0 ); const int32_t statusBarWidth = bar.width(); dst_pt.x = cur_pt.x + buttonPrevCastleArea.width; fheroes2::Copy( bar, 0, 0, display, dst_pt.x, dst_pt.y, statusBarWidth, bar.height() ); StatusBar statusBar; // Status bar must be smaller due to extra art on both sides. statusBar.setRoi( { dst_pt.x + 16, dst_pt.y + 3, statusBarWidth - 16 * 2, 0 } ); // button next castle fheroes2::Button buttonNextCastle( dst_pt.x + statusBarWidth, dst_pt.y, ICN::SMALLBAR, 3, 4 ); fheroes2::TimedEventValidator timedButtonNextCastle( [&buttonNextCastle]() { return buttonNextCastle.isPressed(); } ); buttonNextCastle.subscribe( &timedButtonNextCastle ); const fheroes2::Rect buttonNextCastleArea( buttonNextCastle.area() ); // button exit dst_pt.x = cur_pt.x + 553; dst_pt.y = cur_pt.y + 428; fheroes2::Button buttonExit( dst_pt.x, dst_pt.y, ICN::BUTTON_EXIT_TOWN, 0, 1 ); const fheroes2::Rect buttonExitArea( buttonExit.area() ); if ( GetKingdom().GetCastles().size() < 2 ) { buttonPrevCastle.disable(); buttonNextCastle.disable(); } buttonPrevCastle.draw(); buttonNextCastle.draw(); buttonExit.draw(); // redraw resource panel const fheroes2::Rect & rectResource = fheroes2::drawResourcePanel( GetKingdom().GetFunds(), display, cur_pt ); const fheroes2::Rect resActiveArea( rectResource.x, rectResource.y, rectResource.width, buttonExitArea.y - rectResource.y - 3 ); auto recruitHeroDialog = [this, &buttonExit]( Heroes * hero ) { const fheroes2::ButtonRestorer exitRestorer( buttonExit ); if ( Dialog::OK == DialogBuyHero( hero ) ) { RecruitHero( hero ); return true; } return false; }; display.render( restorer.rect() ); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { le.MousePressLeft( buttonExitArea ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); if ( le.MouseClickLeft( buttonExitArea ) || Game::HotKeyCloseWindow() ) { break; } if ( buttonPrevCastle.isEnabled() ) { le.MousePressLeft( buttonPrevCastleArea ) ? buttonPrevCastle.drawOnPress() : buttonPrevCastle.drawOnRelease(); if ( le.MouseClickLeft( buttonPrevCastleArea ) || HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_LEFT ) || timedButtonPrevCastle.isDelayPassed() ) { return ConstructionDialogResult::PrevConstructionWindow; } } if ( buttonNextCastle.isEnabled() ) { le.MousePressLeft( buttonNextCastleArea ) ? buttonNextCastle.drawOnPress() : buttonNextCastle.drawOnRelease(); if ( le.MouseClickLeft( buttonNextCastleArea ) || HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_RIGHT ) || timedButtonNextCastle.isDelayPassed() ) { return ConstructionDialogResult::NextConstructionWindow; } } if ( le.MouseClickLeft( resActiveArea ) ) { const fheroes2::ButtonRestorer exitRestorer( buttonExit ); fheroes2::showKingdomIncome( world.GetKingdom( GetColor() ), Dialog::OK ); } else if ( le.MousePressRight( resActiveArea ) ) { fheroes2::showKingdomIncome( world.GetKingdom( GetColor() ), 0 ); } else if ( le.MousePressRight( buttonExitArea ) ) { fheroes2::showStandardTextMessage( _( "Exit" ), _( "Exit this menu." ), Dialog::ZERO ); } else if ( le.MouseCursor( dwelling1.GetArea() ) && dwelling1.QueueEventProcessing( buttonExit ) ) { dwellingTobuild = dwelling1.getBuilding(); return ConstructionDialogResult::Build; } if ( le.MouseCursor( dwelling2.GetArea() ) && dwelling2.QueueEventProcessing( buttonExit ) ) { dwellingTobuild = dwelling2.getBuilding(); return ConstructionDialogResult::Build; } if ( le.MouseCursor( dwelling3.GetArea() ) && dwelling3.QueueEventProcessing( buttonExit ) ) { dwellingTobuild = dwelling3.getBuilding(); return ConstructionDialogResult::Build; } if ( le.MouseCursor( dwelling4.GetArea() ) && dwelling4.QueueEventProcessing( buttonExit ) ) { dwellingTobuild = dwelling4.getBuilding(); return ConstructionDialogResult::Build; } if ( le.MouseCursor( dwelling5.GetArea() ) && dwelling5.QueueEventProcessing( buttonExit ) ) { dwellingTobuild = dwelling5.getBuilding(); return ConstructionDialogResult::Build; } if ( le.MouseCursor( dwelling6.GetArea() ) && dwelling6.QueueEventProcessing( buttonExit ) ) { dwellingTobuild = dwelling6.getBuilding(); return ConstructionDialogResult::Build; } if ( le.MouseCursor( buildingMageGuild.GetArea() ) && buildingMageGuild.QueueEventProcessing( buttonExit ) ) { dwellingTobuild = buildingMageGuild.getBuilding(); return ConstructionDialogResult::Build; } if ( !isSkipTavernInteraction && le.MouseCursor( buildingTavern.GetArea() ) && buildingTavern.QueueEventProcessing( buttonExit ) ) { dwellingTobuild = ( Race::NECR == race ? BUILD_SHRINE : BUILD_TAVERN ); return ConstructionDialogResult::Build; } if ( le.MouseCursor( buildingThievesGuild.GetArea() ) && buildingThievesGuild.QueueEventProcessing( buttonExit ) ) { dwellingTobuild = BUILD_THIEVESGUILD; return ConstructionDialogResult::Build; } if ( le.MouseCursor( buildingShipyard.GetArea() ) && buildingShipyard.QueueEventProcessing( buttonExit ) ) { dwellingTobuild = BUILD_SHIPYARD; return ConstructionDialogResult::Build; } if ( le.MouseCursor( buildingStatue.GetArea() ) && buildingStatue.QueueEventProcessing( buttonExit ) ) { dwellingTobuild = BUILD_STATUE; return ConstructionDialogResult::Build; } if ( le.MouseCursor( buildingMarketplace.GetArea() ) && buildingMarketplace.QueueEventProcessing( buttonExit ) ) { dwellingTobuild = BUILD_MARKETPLACE; return ConstructionDialogResult::Build; } if ( le.MouseCursor( buildingWell.GetArea() ) && buildingWell.QueueEventProcessing( buttonExit ) ) { dwellingTobuild = BUILD_WELL; return ConstructionDialogResult::Build; } if ( le.MouseCursor( buildingWel2.GetArea() ) && buildingWel2.QueueEventProcessing( buttonExit ) ) { dwellingTobuild = BUILD_WEL2; return ConstructionDialogResult::Build; } if ( le.MouseCursor( buildingSpec.GetArea() ) && buildingSpec.QueueEventProcessing( buttonExit ) ) { dwellingTobuild = BUILD_SPEC; return ConstructionDialogResult::Build; } if ( le.MouseCursor( buildingLTurret.GetArea() ) && buildingLTurret.QueueEventProcessing( buttonExit ) ) { dwellingTobuild = BUILD_LEFTTURRET; return ConstructionDialogResult::Build; } if ( le.MouseCursor( buildingRTurret.GetArea() ) && buildingRTurret.QueueEventProcessing( buttonExit ) ) { dwellingTobuild = BUILD_RIGHTTURRET; return ConstructionDialogResult::Build; } if ( le.MouseCursor( buildingMoat.GetArea() ) && buildingMoat.QueueEventProcessing( buttonExit ) ) { dwellingTobuild = BUILD_MOAT; return ConstructionDialogResult::Build; } if ( le.MouseCursor( buildingCaptain.GetArea() ) && buildingCaptain.QueueEventProcessing( buttonExit ) ) { dwellingTobuild = BUILD_CAPTAIN; return ConstructionDialogResult::Build; } if ( hero1 && le.MouseClickLeft( rectHero1 ) && recruitHeroDialog( hero1 ) ) { return ConstructionDialogResult::RecruitHero; } if ( hero2 && le.MouseClickLeft( rectHero2 ) && recruitHeroDialog( hero2 ) ) { return ConstructionDialogResult::RecruitHero; } const bool isCaptainBuilt = isBuild( BUILD_CAPTAIN ); if ( isCaptainBuilt ) { if ( le.MouseClickLeft( rectSpreadArmyFormat ) && !army.isSpreadFormation() ) { cursorFormat.setPosition( pointSpreadArmyFormat.x, pointSpreadArmyFormat.y ); display.render( armyFormatRenderRect ); army.SetSpreadFormation( true ); } else if ( le.MouseClickLeft( rectGroupedArmyFormat ) && army.isSpreadFormation() ) { cursorFormat.setPosition( pointGroupedArmyFormat.x, pointGroupedArmyFormat.y ); display.render( armyFormatRenderRect ); army.SetSpreadFormation( false ); } else if ( le.MousePressRight( rectSpreadArmyFormat ) ) { fheroes2::showStandardTextMessage( _( "Spread Formation" ), descriptionSpreadArmyFormat, Dialog::ZERO ); } else if ( le.MousePressRight( rectGroupedArmyFormat ) ) { fheroes2::showStandardTextMessage( _( "Grouped Formation" ), descriptionGroupedArmyFormat, Dialog::ZERO ); } } // Right click if ( hero1 && le.MousePressRight( rectHero1 ) ) { LocalEvent::GetClean(); hero1->OpenDialog( true, true, false, false, false ); // Use half fade if game resolution is not 640x480. fheroes2::fadeInDisplay( restorer.rect(), !display.isDefaultSize() ); } else if ( hero2 && le.MousePressRight( rectHero2 ) ) { LocalEvent::GetClean(); hero2->OpenDialog( true, true, false, false, false ); // Use half fade if game resolution is not 640x480. fheroes2::fadeInDisplay( restorer.rect(), !display.isDefaultSize() ); } else if ( le.MousePressRight( buttonNextCastleArea ) ) { fheroes2::showStandardTextMessage( _( "Show next town" ), _( "Click to show next town." ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonPrevCastleArea ) ) { fheroes2::showStandardTextMessage( _( "Show previous town" ), _( "Click to show previous town." ), Dialog::ZERO ); } // status info if ( le.MouseCursor( dwelling1.GetArea() ) ) dwelling1.SetStatusMessage( statusBar ); else if ( le.MouseCursor( dwelling2.GetArea() ) ) dwelling2.SetStatusMessage( statusBar ); else if ( le.MouseCursor( dwelling3.GetArea() ) ) dwelling3.SetStatusMessage( statusBar ); else if ( le.MouseCursor( dwelling4.GetArea() ) ) dwelling4.SetStatusMessage( statusBar ); else if ( le.MouseCursor( dwelling5.GetArea() ) ) dwelling5.SetStatusMessage( statusBar ); else if ( le.MouseCursor( dwelling6.GetArea() ) ) dwelling6.SetStatusMessage( statusBar ); else if ( le.MouseCursor( buildingMageGuild.GetArea() ) ) buildingMageGuild.SetStatusMessage( statusBar ); else if ( !isSkipTavernInteraction && le.MouseCursor( buildingTavern.GetArea() ) ) buildingTavern.SetStatusMessage( statusBar ); else if ( le.MouseCursor( buildingThievesGuild.GetArea() ) ) buildingThievesGuild.SetStatusMessage( statusBar ); else if ( le.MouseCursor( buildingShipyard.GetArea() ) ) buildingShipyard.SetStatusMessage( statusBar ); else if ( le.MouseCursor( buildingStatue.GetArea() ) ) buildingStatue.SetStatusMessage( statusBar ); else if ( le.MouseCursor( buildingMarketplace.GetArea() ) ) buildingMarketplace.SetStatusMessage( statusBar ); else if ( le.MouseCursor( buildingWell.GetArea() ) ) buildingWell.SetStatusMessage( statusBar ); else if ( le.MouseCursor( buildingWel2.GetArea() ) ) buildingWel2.SetStatusMessage( statusBar ); else if ( le.MouseCursor( buildingSpec.GetArea() ) ) buildingSpec.SetStatusMessage( statusBar ); else if ( le.MouseCursor( buildingLTurret.GetArea() ) ) buildingLTurret.SetStatusMessage( statusBar ); else if ( le.MouseCursor( buildingRTurret.GetArea() ) ) buildingRTurret.SetStatusMessage( statusBar ); else if ( le.MouseCursor( buildingMoat.GetArea() ) ) buildingMoat.SetStatusMessage( statusBar ); else if ( le.MouseCursor( buildingCaptain.GetArea() ) ) buildingCaptain.SetStatusMessage( statusBar ); else if ( hero1 && le.MouseCursor( rectHero1 ) ) { if ( !allow_buy_hero1 ) statusBar.ShowMessage( not_allow1_msg ); else { std::string str = _( "Recruit %{name} the %{race}" ); StringReplace( str, "%{name}", hero1->GetName() ); StringReplace( str, "%{race}", Race::String( hero1->GetRace() ) ); statusBar.ShowMessage( str ); } } else if ( hero2 && le.MouseCursor( rectHero2 ) ) { if ( !allow_buy_hero2 ) statusBar.ShowMessage( not_allow2_msg ); else { std::string str = _( "Recruit %{name} the %{race}" ); StringReplace( str, "%{name}", hero2->GetName() ); StringReplace( str, "%{race}", Race::String( hero2->GetRace() ) ); statusBar.ShowMessage( str ); } } else if ( isCaptainBuilt && le.MouseCursor( rectSpreadArmyFormat ) ) statusBar.ShowMessage( _( "Set garrison combat formation to 'Spread'" ) ); else if ( isCaptainBuilt && le.MouseCursor( rectGroupedArmyFormat ) ) statusBar.ShowMessage( _( "Set garrison combat formation to 'Grouped'" ) ); else if ( le.MouseCursor( buttonExitArea ) ) statusBar.ShowMessage( _( "Exit Castle Options" ) ); else if ( le.MouseCursor( resActiveArea ) ) statusBar.ShowMessage( _( "Show Income" ) ); else if ( buttonPrevCastle.isEnabled() && le.MouseCursor( buttonPrevCastleArea ) ) { statusBar.ShowMessage( _( "Show previous town" ) ); } else if ( buttonNextCastle.isEnabled() && le.MouseCursor( buttonNextCastleArea ) ) { statusBar.ShowMessage( _( "Show next town" ) ); } else { statusBar.ShowMessage( _( "Castle Options" ) ); } } return ConstructionDialogResult::DoNothing; } fheroes2-1.0.12+dfsg/src/fheroes2/castle/castle_well.cpp000066400000000000000000000575521456075706000230400ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include "agg_image.h" #include "army.h" #include "army_troop.h" #include "battle_cell.h" #include "castle.h" #include "cursor.h" #include "dialog.h" #include "game_delays.h" #include "game_hotkeys.h" #include "gamedefs.h" #include "heroes.h" #include "icn.h" #include "image.h" #include "kingdom.h" #include "localevent.h" #include "math_base.h" #include "monster.h" #include "monster_anim.h" #include "resource.h" #include "screen.h" #include "settings.h" #include "speed.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_text.h" namespace { const int32_t bottomBarOffsetY = 461; const std::array castleDwellings = { DWELLING_MONSTER1, DWELLING_MONSTER2, DWELLING_MONSTER3, DWELLING_MONSTER4, DWELLING_MONSTER5, DWELLING_MONSTER6 }; uint32_t howManyRecruitMonster( const Castle & castle, Troops & tempCastleArmy, Troops & tempHeroArmy, const uint32_t dw, const Funds & add, Funds & res ) { const Monster monsters( castle.GetRace(), castle.GetActualDwelling( dw ) ); if ( !tempCastleArmy.CanJoinTroop( monsters ) && !tempHeroArmy.CanJoinTroop( monsters ) ) { return 0; } uint32_t count = castle.getMonstersInDwelling( dw ); Funds payment; const Kingdom & kingdom = castle.GetKingdom(); while ( count ) { payment = monsters.GetCost() * count; res = payment; payment += add; if ( kingdom.AllowPayment( payment ) ) { break; } --count; } if ( count > 0 ) { if ( tempCastleArmy.CanJoinTroop( monsters ) ) { tempCastleArmy.JoinTroop( monsters, count, false ); } else if ( tempHeroArmy.CanJoinTroop( monsters ) ) { tempHeroArmy.JoinTroop( monsters, count, false ); } } return count; } building_t getPressedBuildingHotkey() { if ( HotKeyPressEvent( Game::HotKeyEvent::TOWN_DWELLING_LEVEL_1 ) ) { return DWELLING_MONSTER1; } if ( HotKeyPressEvent( Game::HotKeyEvent::TOWN_DWELLING_LEVEL_2 ) ) { return DWELLING_MONSTER2; } if ( HotKeyPressEvent( Game::HotKeyEvent::TOWN_DWELLING_LEVEL_3 ) ) { return DWELLING_MONSTER3; } if ( HotKeyPressEvent( Game::HotKeyEvent::TOWN_DWELLING_LEVEL_4 ) ) { return DWELLING_MONSTER4; } if ( HotKeyPressEvent( Game::HotKeyEvent::TOWN_DWELLING_LEVEL_5 ) ) { return DWELLING_MONSTER5; } if ( HotKeyPressEvent( Game::HotKeyEvent::TOWN_DWELLING_LEVEL_6 ) ) { return DWELLING_MONSTER6; } return BUILD_NOTHING; } } bool Castle::_recruitCastleMax( const Troops & currentCastleArmy ) { std::vector totalRecruitmentResult; Funds currentMonsterCost; Funds totalMonstersCost; std::string monstersRecruitedText; Troops tempCastleArmy( currentCastleArmy ); Troops tempGuestArmy; const Heroes * hero = GetHero(); if ( hero ) { tempGuestArmy.Insert( hero->GetArmy().getTroops() ); } // In this loop we should go in reverse order - from strongest monsters to weakest in order to purchase most of the best monsters. for ( size_t id = 0; id < CASTLEMAXMONSTER; ++id ) { const uint32_t dwellingType = castleDwellings[CASTLEMAXMONSTER - id - 1]; const uint32_t recruitableNumber = howManyRecruitMonster( *this, tempCastleArmy, tempGuestArmy, dwellingType, totalMonstersCost, currentMonsterCost ); if ( recruitableNumber == 0 ) { continue; } const Monster recruitableMonster( race, GetActualDwelling( dwellingType ) ); totalRecruitmentResult.emplace_back( recruitableMonster, recruitableNumber ); totalMonstersCost += currentMonsterCost; monstersRecruitedText += recruitableMonster.GetPluralName( recruitableNumber ); monstersRecruitedText += " : "; monstersRecruitedText += std::to_string( recruitableNumber ); monstersRecruitedText += '\n'; } const fheroes2::FontType normalWhite = fheroes2::FontType::normalWhite(); if ( monstersRecruitedText.empty() ) { bool isCreaturePresent = false; bool canAffordOneCreature = false; for ( const uint32_t dwellingType : castleDwellings ) { if ( getMonstersInDwelling( dwellingType ) > 0 ) { const Monster monsters( race, dwellingType ); const Funds payment = monsters.GetCost(); if ( GetKingdom().AllowPayment( payment ) ) { canAffordOneCreature = true; } isCreaturePresent = true; break; } } if ( isCreaturePresent ) { if ( !canAffordOneCreature ) { fheroes2::showMessage( fheroes2::Text( "", {} ), fheroes2::Text( _( "Not enough resources to recruit creatures." ), normalWhite ), Dialog::OK ); } else { fheroes2::showMessage( fheroes2::Text( "", {} ), fheroes2::Text( _( "You are unable to recruit at this time, your ranks are full." ), normalWhite ), Dialog::OK ); } } else { fheroes2::showMessage( fheroes2::Text( "", {} ), fheroes2::Text( _( "No creatures available for purchase." ), normalWhite ), Dialog::OK ); } } else if ( fheroes2::showResourceMessage( fheroes2::Text( _( "Recruit Creatures" ), fheroes2::FontType::normalYellow() ), fheroes2::Text( std::move( monstersRecruitedText ), normalWhite ), Dialog::YES | Dialog::NO, totalMonstersCost ) == Dialog::YES ) { for ( const Troop & troop : totalRecruitmentResult ) { RecruitMonster( troop, false ); } return true; } return false; } void Castle::OpenWell() { fheroes2::Display & display = fheroes2::Display::instance(); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); const fheroes2::ImageRestorer restorer( display, ( display.width() - fheroes2::Display::DEFAULT_WIDTH ) / 2, ( display.height() - fheroes2::Display::DEFAULT_HEIGHT ) / 2, fheroes2::Display::DEFAULT_WIDTH, fheroes2::Display::DEFAULT_HEIGHT ); const fheroes2::Rect roi = restorer.rect(); const int32_t buttonOffsetY = roi.y + bottomBarOffsetY; // MAX button. fheroes2::Button buttonMax( roi.x, buttonOffsetY, ICN::BUYMAX, 0, 1 ); // EXIT button. fheroes2::Button buttonExit( roi.x + roi.width - fheroes2::AGG::GetICN( ICN::BUTTON_GUILDWELL_EXIT, 0 ).width(), buttonOffsetY, ICN::BUTTON_GUILDWELL_EXIT, 0, 1 ); const std::array rectMonster = { fheroes2::Rect( roi.x + 20, roi.y + 18, 288, 124 ), fheroes2::Rect( roi.x + 20, roi.y + 168, 288, 124 ), fheroes2::Rect( roi.x + 20, roi.y + 318, 288, 124 ), fheroes2::Rect( roi.x + 334, roi.y + 18, 288, 124 ), fheroes2::Rect( roi.x + 334, roi.y + 168, 288, 124 ), fheroes2::Rect( roi.x + 334, roi.y + 318, 288, 124 ) }; const std::array castleMonster = { Monster( race, GetActualDwelling( castleDwellings[0] ) ), Monster( race, GetActualDwelling( castleDwellings[1] ) ), Monster( race, GetActualDwelling( castleDwellings[2] ) ), Monster( race, GetActualDwelling( castleDwellings[3] ) ), Monster( race, GetActualDwelling( castleDwellings[4] ) ), Monster( race, GetActualDwelling( castleDwellings[5] ) ) }; std::array monsterAnimInfo = { fheroes2::RandomMonsterAnimation( castleMonster[0] ), fheroes2::RandomMonsterAnimation( castleMonster[1] ), fheroes2::RandomMonsterAnimation( castleMonster[2] ), fheroes2::RandomMonsterAnimation( castleMonster[3] ), fheroes2::RandomMonsterAnimation( castleMonster[4] ), fheroes2::RandomMonsterAnimation( castleMonster[5] ) }; fheroes2::Image background; background._disableTransformLayer(); background.resize( roi.width, roi.height ); _wellRedrawBackground( background ); LocalEvent & le = LocalEvent::Get(); Game::passAnimationDelay( Game::CASTLE_UNIT_DELAY ); while ( le.HandleEvents( Game::isDelayNeeded( { Game::CASTLE_UNIT_DELAY } ) ) ) { le.MousePressLeft( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); le.MousePressLeft( buttonMax.area() ) ? buttonMax.drawOnPress() : buttonMax.drawOnRelease(); const building_t pressedHotkeyBuildingID = getPressedBuildingHotkey(); if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) { break; } if ( le.MouseClickLeft( buttonMax.area() ) || HotKeyPressEvent( Game::HotKeyEvent::TOWN_WELL_BUY_ALL ) ) { const Troops & currentArmy = GetArmy(); if ( _recruitCastleMax( currentArmy ) ) { // Update available monster count on background. for ( const uint32_t dwellingType : castleDwellings ) { _wellRedrawAvailableMonsters( dwellingType, true, background ); } } } for ( size_t dwellingId = 0; dwellingId < CASTLEMAXMONSTER; ++dwellingId ) { if ( building & castleDwellings[dwellingId] ) { if ( le.MouseClickLeft( rectMonster[dwellingId] ) || pressedHotkeyBuildingID == castleDwellings[dwellingId] ) { if ( RecruitMonster( Dialog::RecruitMonster( castleMonster[dwellingId], dwelling[dwellingId], true, 2 ) ) ) { // Update available monster count on background. _wellRedrawAvailableMonsters( castleDwellings[dwellingId], true, background ); } break; } if ( le.MousePressRight( rectMonster[dwellingId] ) ) { Dialog::DwellingInfo( castleMonster[dwellingId], dwelling[dwellingId] ); break; } } } if ( Game::validateAnimationDelay( Game::CASTLE_UNIT_DELAY ) ) { fheroes2::Copy( background, 0, 0, display, roi.x, roi.y, roi.width, roi.height ); _wellRedrawMonsterAnimation( roi, monsterAnimInfo ); buttonMax.draw(); buttonExit.draw(); display.render( roi ); } if ( le.MousePressRight( buttonExit.area() ) ) { fheroes2::showMessage( fheroes2::Text( _( "Exit" ), fheroes2::FontType::normalYellow() ), fheroes2::Text( _( "Exit this menu." ), fheroes2::FontType::normalWhite() ), Dialog::ZERO ); } if ( le.MousePressRight( buttonMax.area() ) ) { fheroes2::showMessage( fheroes2::Text( _( "Max" ), fheroes2::FontType::normalYellow() ), fheroes2::Text( _( "Hire all creatures in the town." ), fheroes2::FontType::normalWhite() ), Dialog::ZERO ); } } } void Castle::_wellRedrawAvailableMonsters( const uint32_t dwellingType, const bool restoreBackground, fheroes2::Image & background ) const { if ( !( building & dwellingType ) ) { // This building has not been built. return; } uint32_t population = 0; fheroes2::Point offset{ 20, 121 }; switch ( dwellingType ) { case DWELLING_MONSTER1: population = dwelling[0]; break; case DWELLING_MONSTER2: population = dwelling[1]; offset.y += 150; break; case DWELLING_MONSTER3: population = dwelling[2]; offset.y += 300; break; case DWELLING_MONSTER4: offset.x += 314; population = dwelling[3]; break; case DWELLING_MONSTER5: population = dwelling[4]; offset.x += 314; offset.y += 150; break; case DWELLING_MONSTER6: population = dwelling[5]; offset.x += 314; offset.y += 300; break; default: // Have you added a new dwelling? assert( 0 ); break; } if ( restoreBackground ) { // Restore background under "Available: " text. const fheroes2::Sprite & wellBackground = fheroes2::AGG::GetICN( Settings::Get().isEvilInterfaceEnabled() ? ICN::WELLBKG_EVIL : ICN::WELLBKG, 0 ); fheroes2::Copy( wellBackground, offset.x, offset.y, background, offset.x, offset.y, 137, fheroes2::getFontHeight( fheroes2::FontSize::NORMAL ) ); } std::string textString = _( "Available" ); textString += ": "; fheroes2::Text text( std::move( textString ), fheroes2::FontType::smallWhite() ); text.draw( offset.x + 24, offset.y + 2, background ); text.set( std::to_string( population ), fheroes2::FontType::normalYellow() ); text.draw( offset.x + 109 - text.width() / 2, offset.y, background ); } void Castle::_wellRedrawBackground( fheroes2::Image & background ) const { const fheroes2::Sprite & wellBackground = fheroes2::AGG::GetICN( Settings::Get().isEvilInterfaceEnabled() ? ICN::WELLBKG_EVIL : ICN::WELLBKG, 0 ); const int32_t backgroundWidth = wellBackground.width(); fheroes2::Copy( wellBackground, 0, 0, background, 0, 0, backgroundWidth, bottomBarOffsetY ); // TODO: Make Well bottom bar and buttons for Evil interface. // The original ICN::WELLBKG image has incorrect bottom message bar with no yellow outline. Also the original graphics did not have MAX button. const fheroes2::Sprite & bottomBar = fheroes2::AGG::GetICN( ICN::SMALLBAR, 0 ); const int32_t barHeight = bottomBar.height(); const int32_t exitWidth = fheroes2::AGG::GetICN( ICN::BUTTON_GUILDWELL_EXIT, 0 ).width(); const int32_t buttonMaxWidth = fheroes2::AGG::GetICN( ICN::BUYMAX, 0 ).width(); // ICN::SMALLBAR image's first column contains all black pixels. This should not be drawn. fheroes2::Copy( bottomBar, 1, 0, background, buttonMaxWidth, bottomBarOffsetY, backgroundWidth / 2 - buttonMaxWidth, barHeight ); fheroes2::Copy( bottomBar, bottomBar.width() - backgroundWidth / 2 + exitWidth - 1, 0, background, backgroundWidth / 2, bottomBarOffsetY, backgroundWidth / 2 - exitWidth + 1, barHeight ); // The original assets Well background has a transparent line to the right of EXIT button and it is not covered by any other image. Fill it with the black color. fheroes2::Fill( background, background.width() - 1, bottomBarOffsetY, 1, bottomBar.height(), static_cast( 0 ) ); fheroes2::Text text( _( "Town Population Information and Statistics" ), fheroes2::FontType::normalWhite() ); text.draw( 315 - text.width() / 2, bottomBarOffsetY + 3, background ); const fheroes2::FontType statsFontType = fheroes2::FontType::smallWhite(); for ( const uint32_t dwellingType : castleDwellings ) { // By default the 'icnIndex' and 'offset' values are set for DWELLING_MONSTER1. uint32_t icnIndex = 19; fheroes2::Point offset{ 0, 1 }; switch ( dwellingType ) { case DWELLING_MONSTER1: break; case DWELLING_MONSTER2: offset.y = 151; icnIndex = DWELLING_UPGRADE2 & building ? 25 : 20; break; case DWELLING_MONSTER3: offset.y = 301; icnIndex = DWELLING_UPGRADE3 & building ? 26 : 21; break; case DWELLING_MONSTER4: offset.x = 314; icnIndex = DWELLING_UPGRADE4 & building ? 27 : 22; break; case DWELLING_MONSTER5: offset.x = 314; offset.y = 151; icnIndex = DWELLING_UPGRADE5 & building ? 28 : 23; break; case DWELLING_MONSTER6: offset.x = 314; offset.y = 301; icnIndex = DWELLING_UPGRADE7 & building ? 30 : ( DWELLING_UPGRADE6 & building ? 29 : 24 ); break; default: // Have you added a new dwelling? assert( 0 ); break; } const uint32_t actualDwellindType = GetActualDwelling( dwellingType ); const Monster monster( race, actualDwellindType ); // Dwelling building image. fheroes2::Point renderPoint( offset.x + 21, offset.y + 35 ); const fheroes2::Sprite & dwellingImage = fheroes2::AGG::GetICN( ICN::Get4Building( race ), icnIndex ); fheroes2::Copy( dwellingImage, 0, 0, background, renderPoint.x, renderPoint.y, dwellingImage.width(), dwellingImage.height() ); // Dwelling name. text.set( GetStringBuilding( actualDwellindType, race ), statsFontType ); renderPoint.x = offset.x + 86 - text.width() / 2; renderPoint.y = offset.y + 104; text.draw( renderPoint.x, renderPoint.y, background ); // Creature name. text.set( monster.GetMultiName(), statsFontType ); renderPoint.x = offset.x + 122 - text.width() / 2; renderPoint.y = offset.y + 19; text.draw( renderPoint.x, renderPoint.y, background ); // attack std::string textString; textString = _( "Attack" ); textString += ": "; textString += std::to_string( monster.GetAttack() ); text.set( textString, statsFontType ); // Set initial X and Y offset. renderPoint.x = offset.x + 269; renderPoint.y = offset.y + 22; text.draw( renderPoint.x - text.width() / 2, renderPoint.y, background ); renderPoint.y += text.height( text.width() ); // defense textString = _( "Defense" ); textString += ": "; textString += std::to_string( monster.GetDefense() ); text.set( textString, statsFontType ); text.draw( renderPoint.x - text.width() / 2, renderPoint.y, background ); renderPoint.y += text.height( text.width() ); // damage textString = _( "Damage" ); textString += ": "; const uint32_t monsterMinDamage = monster.GetDamageMin(); const uint32_t monsterMaxDamage = monster.GetDamageMax(); textString += std::to_string( monsterMinDamage ); if ( monsterMinDamage != monsterMaxDamage ) { textString += '-'; textString += std::to_string( monsterMaxDamage ); } text.set( textString, statsFontType ); text.draw( renderPoint.x - text.width() / 2, renderPoint.y, background ); renderPoint.y += text.height( text.width() ); // hp textString = _( "HP" ); textString += ": "; textString += std::to_string( monster.GetHitPoints() ); text.set( textString, statsFontType ); text.draw( renderPoint.x - text.width() / 2, renderPoint.y, background ); renderPoint.y += 2 * ( text.height( text.width() ) ); // skip a line // speed textString = _( "Speed" ); textString += ':'; text.set( textString, statsFontType ); text.draw( renderPoint.x - text.width() / 2, renderPoint.y, background ); renderPoint.y += text.height( text.width() ); text.set( Speed::String( static_cast( monster.GetSpeed() ) ), statsFontType ); text.draw( renderPoint.x - text.width() / 2, renderPoint.y, background ); renderPoint.y += 2 * ( text.height( text.width() ) ); // skip a line // If dwelling is built show growth and available population. if ( building & dwellingType ) { uint32_t monsterGrown = monster.GetGrown(); monsterGrown += building & BUILD_WELL ? GetGrownWell() : 0; if ( DWELLING_MONSTER1 & dwellingType ) { monsterGrown += building & BUILD_WEL2 ? GetGrownWel2() : 0; } text.set( _( "Growth" ), statsFontType ); text.draw( renderPoint.x - text.width() / 2, renderPoint.y, background ); renderPoint.y += text.height( text.width() ); textString = "+ "; textString += std::to_string( monsterGrown ); textString += " / "; textString += _( "week" ); text.set( textString, statsFontType ); text.draw( renderPoint.x - text.width() / 2, renderPoint.y, background ); _wellRedrawAvailableMonsters( dwellingType, false, background ); } } } void Castle::_wellRedrawMonsterAnimation( const fheroes2::Rect & roi, std::array & monsterAnimInfo ) const { fheroes2::Display & display = fheroes2::Display::instance(); for ( size_t monsterId = 0; monsterId < CASTLEMAXMONSTER; ++monsterId ) { fheroes2::Point outPos( roi.x, roi.y + 1 ); switch ( monsterId ) { case 0: break; case 1: outPos.y += 150; break; case 2: outPos.y += 300; break; case 3: outPos.x += 314; break; case 4: outPos.x += 314; outPos.y += 150; break; case 5: outPos.x += 314; outPos.y += 300; break; default: // Have you added a new dwelling? assert( 0 ); break; } const Monster monster( race, GetActualDwelling( castleDwellings[monsterId] ) ); // monster const bool flipMonsterSprite = ( monsterId > 2 ); const fheroes2::Sprite & smonster = fheroes2::AGG::GetICN( monsterAnimInfo[monsterId].icnFile(), monsterAnimInfo[monsterId].frameId() ); if ( flipMonsterSprite ) { outPos.x += 193 - ( smonster.x() + smonster.width() ) + ( monster.isWide() ? CELLW / 2 : 0 ) + monsterAnimInfo[monsterId].offset(); } else { outPos.x += 193 + smonster.x() - ( monster.isWide() ? CELLW / 2 : 0 ) - monsterAnimInfo[monsterId].offset(); } outPos.y += 124 + smonster.y(); fheroes2::Point inPos( 0, 0 ); fheroes2::Size inSize( smonster.width(), smonster.height() ); if ( fheroes2::FitToRoi( smonster, inPos, display, outPos, inSize, roi ) ) { fheroes2::Blit( smonster, inPos, display, outPos, inSize, flipMonsterSprite ); } monsterAnimInfo[monsterId].increment(); } } fheroes2-1.0.12+dfsg/src/fheroes2/castle/mageguild.cpp000066400000000000000000000151201456075706000224610ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "mageguild.h" #include #include #include #include #include #include #include #include "heroes_base.h" #include "race.h" #include "rand.h" #include "serialize.h" #include "spell.h" #include "tools.h" namespace { Spell getGuaranteedDamageSpell() { const uint32_t rand = Rand::Get( 0, 100 ); if ( rand < 20 ) { return Spell::ARROW; } if ( rand < 40 ) { return Spell::LIGHTNINGBOLT; } if ( rand < 60 ) { return Spell::FIREBALL; } if ( rand < 80 ) { return Spell::COLDRAY; } return Spell::COLDRING; } Spell getGuaranteedCancellationSpell() { const uint32_t rand = Rand::Get( 0, 100 ); if ( rand < 25 ) { return Spell::DISPEL; } if ( rand < 50 ) { return Spell::MASSDISPEL; } if ( rand < 75 ) { return Spell::ANTIMAGIC; } return Spell::CURE; } } void MageGuild::initialize( const int race, const bool hasLibrary ) { assert( CountBits( race ) == 1 && ( race & Race::ALL ) ); general.clear(); library.clear(); std::set spellsInUse; struct MageGuildLevelProps { int freeSlots = 0; bool hasAdventureSpell = false; }; std::array mageGuildLevels = { { { 3, false }, { 3, false }, { 2, false }, { 2, false }, { 1, false } } }; if ( hasLibrary ) { for ( auto & [freeSlots, dummy] : mageGuildLevels ) { ++freeSlots; } } const auto addSpell = [this, hasLibrary, &spellsInUse, &mageGuildLevels]( const Spell & spell ) { const size_t spellLevel = fheroes2::checkedCast( spell.Level() ).value(); assert( spellLevel > 0 && spellLevel <= mageGuildLevels.size() ); auto & [freeSlots, hasAdventureSpell] = mageGuildLevels[spellLevel - 1]; assert( freeSlots > 0 ); // Check for possible duplicates const auto [dummy, inserted] = spellsInUse.insert( spell ); if ( !inserted ) { return; } if ( hasLibrary && freeSlots == 1 ) { library.Append( spell ); } else { general.Append( spell ); } --freeSlots; if ( spell.isAdventure() ) { assert( !hasAdventureSpell ); hasAdventureSpell = true; } }; // Mage Guild must always have one of the specific damage spells... addSpell( getGuaranteedDamageSpell() ); // ... as well as one of the specific "spell cancellation" spells addSpell( getGuaranteedCancellationSpell() ); for ( size_t level = 1; level <= mageGuildLevels.size(); ++level ) { const auto & [freeSlots, hasAdventureSpell] = mageGuildLevels[level - 1]; std::vector allSpellsOfLevel = Spell::getAllSpellIdsSuitableForSpellBook( fheroes2::checkedCast( level ).value() ); while ( freeSlots > 0 ) { assert( !allSpellsOfLevel.empty() ); const uint32_t spellIdx = Rand::Get( 0, fheroes2::checkedCast( allSpellsOfLevel.size() - 1 ).value() ); const Spell spell( allSpellsOfLevel[spellIdx] ); // Some spells may occur less frequently in Mage Guilds than others, depending on race if ( Rand::Get( 0, 10 ) > spell.weightForRace( race ) ) { continue; } // There can only be one adventure spell at each level of the Mage Guild if ( !hasAdventureSpell || !spell.isAdventure() ) { addSpell( spell ); } allSpellsOfLevel.erase( allSpellsOfLevel.begin() + spellIdx ); } } } SpellStorage MageGuild::GetSpells( int guildLevel, bool hasLibrary, int spellLevel ) const { SpellStorage result; if ( spellLevel == -1 ) { // Get all available spells for ( int level = 1; level <= guildLevel; ++level ) { result.Append( general.GetSpells( level ) ); if ( hasLibrary ) { result.Append( library.GetSpells( level ) ); } } } else if ( spellLevel <= guildLevel ) { result = general.GetSpells( spellLevel ); if ( hasLibrary ) { result.Append( library.GetSpells( spellLevel ) ); } } return result; } void MageGuild::educateHero( HeroBase & hero, int guildLevel, bool hasLibrary ) const { if ( hero.HaveSpellBook() && guildLevel > 0 ) { // This method will test the hero for compliance with the wisdom requirements hero.AppendSpellsToBook( MageGuild::GetSpells( guildLevel, hasLibrary ) ); } } StreamBase & operator<<( StreamBase & msg, const MageGuild & guild ) { return msg << guild.general << guild.library; } StreamBase & operator>>( StreamBase & msg, MageGuild & guild ) { return msg >> guild.general >> guild.library; } fheroes2-1.0.12+dfsg/src/fheroes2/castle/mageguild.h000066400000000000000000000051261456075706000221330ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2MAGEGUILD_H #define H2MAGEGUILD_H #include "spell_storage.h" class StreamBase; class HeroBase; class MageGuild { public: MageGuild() = default; // Initializes the Mage Guild according to the rules described in // https://handbookhmm.ru/kakim-obrazom-zaklinaniya-popadayut-v-magicheskuyu-gildiyu.html // except for the part related to the hidden AI-only bonuses. void initialize( const int race, const bool hasLibrary ); void educateHero( HeroBase & hero, int guildLevel, bool hasLibrary ) const; SpellStorage GetSpells( int guildLevel, bool hasLibrary, int spellLevel = -1 ) const; private: friend StreamBase & operator<<( StreamBase &, const MageGuild & ); friend StreamBase & operator>>( StreamBase &, MageGuild & ); SpellStorage general; SpellStorage library; }; StreamBase & operator<<( StreamBase &, const MageGuild & ); StreamBase & operator>>( StreamBase &, MageGuild & ); #endif fheroes2-1.0.12+dfsg/src/fheroes2/dialog/000077500000000000000000000000001456075706000200045ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog.h000066400000000000000000000173301456075706000214200ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2DIALOG_H #define H2DIALOG_H #include #include #include #include #include #include #include "game_mode.h" #include "gamedefs.h" #include "image.h" #define SHADOWWIDTH 16 #define BOXAREA_WIDTH 244 class Castle; class Kingdom; class HeroBase; class Heroes; class Monster; class Troop; struct ArtifactSetData; struct CapturedObject; namespace Skill { class Secondary; } namespace Maps { class Tiles; } namespace Dialog { enum { ZERO = 0x0000, YES = 0x0001, OK = 0x0002, NO = 0x0004, CANCEL = 0x0008, DISMISS = 0x0010, UPGRADE = 0x0020, MAX = 0x0040, PREV = 0x0080, NEXT = 0x0100, WORLD = 0x0200, PUZZLE = 0x0400, INFO = 0x0800, DIG = 0x1000, UPGRADE_DISABLE = MAX, BUTTONS = ( YES | OK | NO | CANCEL ) }; int AdventureOptions( const bool enableDig ); fheroes2::GameMode FileOptions(); std::string SelectFileLoad(); std::string SelectFileSave(); // Shows the quick info window for the given tile void QuickInfo( const Maps::Tiles & tile ); // Shows the quick info window for the given castle void QuickInfo( const Castle & castle ); // Shows the quick info window for the given hero or captain. If the 'showFullInfo' parameter is specified, // then whether full or abbreviated information will be displayed is determined according to its value, // otherwise it is determined by the internal logic of this function. See the implementation for details. void QuickInfo( const HeroBase & hero, const std::optional showFullInfo = {} ); // Shows the quick info window for the given castle, and also indicates the location of this castle on the radar. // 'areaToRestore' defines the area whose contents should be restored when the radar is redrawn. void QuickInfoWithIndicationOnRadar( const Castle & castle, const fheroes2::Rect & areaToRestore ); // Shows the quick info window for the given hero, and also indicates the location of this hero on the radar. // 'areaToRestore' defines the area whose contents should be restored when the radar is redrawn. void QuickInfoWithIndicationOnRadar( const HeroBase & hero, const fheroes2::Rect & areaToRestore ); // Shows the quick info window for the given castle at the given position on the screen, and also indicates the // location of this castle on the radar. void QuickInfoAtPosition( const Castle & castle, const fheroes2::Point & position ); // Shows the quick info window for the given hero at the given position on the screen, and also indicates the // location of this hero on the radar. void QuickInfoAtPosition( const HeroBase & hero, const fheroes2::Point & position ); int LevelUpSelectSkill( const std::string & name, const int primarySkillType, const Skill::Secondary & sec1, const Skill::Secondary & sec2, Heroes & hero ); bool SelectGoldOrExp( const std::string &, const std::string &, uint32_t gold, uint32_t expr, const Heroes & ); int SelectSkillFromArena(); bool SelectCount( const std::string & header, uint32_t min, uint32_t max, uint32_t & cur, int step = 1 ); bool InputString( const std::string & header, std::string & result, const std::string & title = std::string(), const size_t charLimit = 0 ); Troop RecruitMonster( const Monster & monster0, const uint32_t available, const bool allowDowngradedMonster, const int32_t windowOffsetY ); void DwellingInfo( const Monster &, const uint32_t available ); int ArmyInfo( const Troop & troop, int flags, bool isReflected = false, const int32_t windowOffsetY = 0 ); int ArmyJoinFree( const Troop & troop ); int ArmyJoinWithCost( const Troop &, const uint32_t join, const uint32_t gold ); int ArmySplitTroop( uint32_t freeSlots, const uint32_t redistributeMax, uint32_t & redistributeCount, bool & useFastSplit ); void Marketplace( Kingdom & kingdom, bool fromTradingPost ); void MakeGiftResource( Kingdom & kingdom ); int BuyBoat( bool enable ); void ThievesGuild( const bool oracle ); void GameInfo(); // Displays a dialog box informing that an artifact set has been assembled void ArtifactSetAssembled( const ArtifactSetData & artifactSetData ); class NonFixedFrameBox { public: explicit NonFixedFrameBox( int height = 0, int startYPos = -1, bool showButtons = false ); virtual ~NonFixedFrameBox(); const fheroes2::Rect & GetArea() const { return area; } void redraw(); protected: std::unique_ptr _restorer; fheroes2::Rect area; private: fheroes2::Point _position; uint32_t _middleFragmentCount; int32_t _middleFragmentHeight; }; class FrameBox : public NonFixedFrameBox { public: FrameBox( int height, bool buttons = false ); ~FrameBox() override = default; }; class FrameBorder { public: explicit FrameBorder( int v = BORDERWIDTH ); FrameBorder( const fheroes2::Size &, const fheroes2::Image & ); int BorderWidth() const { return border; } int BorderHeight() const { return border; } void SetPosition( int32_t posx, int32_t posy, int32_t encw, int32_t ench ); bool isValid() const { return rect.width != 0 && rect.height != 0; } const fheroes2::Rect & GetRect() const { return rect; } const fheroes2::Rect & GetArea() const { return area; } const fheroes2::Rect & GetTop() const { return top; } static void RenderRegular( const fheroes2::Rect & dstrt ); static void RenderOther( const fheroes2::Image &, const fheroes2::Rect & ); protected: fheroes2::ImageRestorer restorer; private: fheroes2::Rect rect; fheroes2::Rect area; fheroes2::Rect top; int border; }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_adventure.cpp000066400000000000000000000150411456075706000240250ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include "cursor.h" #include "dialog.h" #include "game_hotkeys.h" #include "icn.h" #include "localevent.h" #include "screen.h" #include "settings.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_window.h" namespace { int selectAdventureOption( const bool enableDig ) { // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::StandardWindow background( 289, 204, true, display ); fheroes2::Button buttonWorld; fheroes2::Button buttonPuzzle; fheroes2::Button buttonInfo; fheroes2::Button buttonDig; fheroes2::Button buttonCancel; const int32_t largeButtonsXOffset = 30; const int32_t largeButtonsYOffset = 15; const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); const int apanel = isEvilInterface ? ICN::APANELE : ICN::APANEL; background.renderButton( buttonWorld, apanel, 0, 1, { largeButtonsXOffset, largeButtonsYOffset }, fheroes2::StandardWindow::Padding::TOP_LEFT ); background.renderButton( buttonPuzzle, apanel, 2, 3, { largeButtonsXOffset, largeButtonsYOffset }, fheroes2::StandardWindow::Padding::TOP_RIGHT ); background.renderButton( buttonInfo, isEvilInterface ? ICN::BUTTON_INFO_EVIL : ICN::BUTTON_INFO_GOOD, 0, 1, { largeButtonsXOffset, largeButtonsYOffset + 2 }, fheroes2::StandardWindow::Padding::CENTER_LEFT ); background.renderButton( buttonDig, apanel, 6, 7, { largeButtonsXOffset, largeButtonsYOffset + 2 }, fheroes2::StandardWindow::Padding::CENTER_RIGHT ); background.renderButton( buttonCancel, isEvilInterface ? ICN::BUTTON_SMALL_CANCEL_EVIL : ICN::BUTTON_SMALL_CANCEL_GOOD, 0, 1, { 0, 11 }, fheroes2::StandardWindow::Padding::BOTTOM_CENTER ); if ( !enableDig ) { buttonDig.disable(); buttonDig.draw(); } display.render( background.totalArea() ); int result = Dialog::ZERO; LocalEvent & le = LocalEvent::Get(); // dialog menu loop while ( le.HandleEvents() ) { le.MousePressLeft( buttonWorld.area() ) ? buttonWorld.drawOnPress() : buttonWorld.drawOnRelease(); le.MousePressLeft( buttonPuzzle.area() ) ? buttonPuzzle.drawOnPress() : buttonPuzzle.drawOnRelease(); le.MousePressLeft( buttonInfo.area() ) ? buttonInfo.drawOnPress() : buttonInfo.drawOnRelease(); le.MousePressLeft( buttonDig.area() ) ? buttonDig.drawOnPress() : buttonDig.drawOnRelease(); le.MousePressLeft( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); if ( le.MouseClickLeft( buttonWorld.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::WORLD_VIEW_WORLD ) ) { result = Dialog::WORLD; break; } if ( le.MouseClickLeft( buttonPuzzle.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::WORLD_PUZZLE_MAP ) ) { result = Dialog::PUZZLE; break; } if ( le.MouseClickLeft( buttonInfo.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::WORLD_SCENARIO_INFORMATION ) ) { result = Dialog::INFO; break; } if ( enableDig && ( le.MouseClickLeft( buttonDig.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::WORLD_DIG_ARTIFACT ) ) ) { result = Dialog::DIG; break; } if ( le.MouseClickLeft( buttonCancel.area() ) || Game::HotKeyCloseWindow() ) { result = Dialog::CANCEL; break; } // right info if ( le.MousePressRight( buttonWorld.area() ) ) fheroes2::showStandardTextMessage( _( "View World" ), _( "View the entire world." ), Dialog::ZERO ); else if ( le.MousePressRight( buttonPuzzle.area() ) ) fheroes2::showStandardTextMessage( _( "Puzzle" ), _( "View the obelisk puzzle." ), Dialog::ZERO ); else if ( le.MousePressRight( buttonInfo.area() ) ) fheroes2::showStandardTextMessage( _( "Scenario Information" ), _( "View information on the scenario you are currently playing." ), Dialog::ZERO ); else if ( le.MousePressRight( buttonDig.area() ) ) fheroes2::showStandardTextMessage( _( "Digging" ), _( "Dig for the Ultimate Artifact." ), Dialog::ZERO ); else if ( le.MousePressRight( buttonCancel.area() ) ) fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Exit this menu without doing anything." ), Dialog::ZERO ); } return result; } } int Dialog::AdventureOptions( const bool enableDig ) { const int result = selectAdventureOption( enableDig ); return result; } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_arena.cpp000066400000000000000000000211101456075706000231100ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include "agg_image.h" #include "cursor.h" #include "dialog.h" #include "game_hotkeys.h" #include "icn.h" #include "image.h" #include "localevent.h" #include "math_base.h" #include "screen.h" #include "settings.h" #include "skill.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_text.h" namespace { void InfoSkillClear( const fheroes2::Rect & rect1, const fheroes2::Rect & rect2, const fheroes2::Rect & rect3 ) { fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::Blit( fheroes2::AGG::GetICN( ICN::XPRIMARY, 0 ), display, rect1.x, rect1.y ); fheroes2::Blit( fheroes2::AGG::GetICN( ICN::XPRIMARY, 1 ), display, rect2.x, rect2.y ); fheroes2::Blit( fheroes2::AGG::GetICN( ICN::XPRIMARY, 2 ), display, rect3.x, rect3.y ); } void InfoSkillSelect( int skill, const fheroes2::Rect & rect1, const fheroes2::Rect & rect2, const fheroes2::Rect & rect3 ) { fheroes2::Display & display = fheroes2::Display::instance(); switch ( skill ) { case Skill::Primary::ATTACK: fheroes2::Blit( fheroes2::AGG::GetICN( ICN::XPRIMARY, 4 ), display, rect1.x, rect1.y ); break; case Skill::Primary::DEFENSE: fheroes2::Blit( fheroes2::AGG::GetICN( ICN::XPRIMARY, 5 ), display, rect2.x, rect2.y ); break; case Skill::Primary::POWER: fheroes2::Blit( fheroes2::AGG::GetICN( ICN::XPRIMARY, 6 ), display, rect3.x, rect3.y ); break; default: break; } } int InfoSkillNext( int skill ) { switch ( skill ) { case Skill::Primary::ATTACK: return Skill::Primary::DEFENSE; case Skill::Primary::DEFENSE: return Skill::Primary::POWER; default: break; } return Skill::Primary::UNKNOWN; } int InfoSkillPrev( int skill ) { switch ( skill ) { case Skill::Primary::POWER: return Skill::Primary::DEFENSE; case Skill::Primary::DEFENSE: return Skill::Primary::ATTACK; default: break; } return Skill::Primary::UNKNOWN; } } int Dialog::SelectSkillFromArena() { fheroes2::Display & display = fheroes2::Display::instance(); const int system = Settings::Get().isEvilInterfaceEnabled() ? ICN::SYSTEME : ICN::SYSTEM; // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); fheroes2::Text title( _( "Arena" ), fheroes2::FontType::normalYellow() ); fheroes2::Text textbox( _( "You enter the arena and face a pack of vicious lions. You handily defeat them, to the wild cheers of the crowd. Impressed by your skill, the aged trainer of gladiators agrees to train you in a skill of your choice." ), fheroes2::FontType::normalWhite() ); const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::XPRIMARY, 0 ); const int spacer = 10; Dialog::FrameBox box( title.height( BOXAREA_WIDTH ) + textbox.height( BOXAREA_WIDTH ) + 2 * spacer + sprite.height() + 15, true ); const fheroes2::Rect & box_rt = box.GetArea(); fheroes2::Point dst_pt( box_rt.x, box_rt.y ); title.draw( dst_pt.x, dst_pt.y + 2, BOXAREA_WIDTH, display ); dst_pt.y += title.height( BOXAREA_WIDTH ) + spacer; textbox.draw( dst_pt.x, dst_pt.y + 2, BOXAREA_WIDTH, display ); dst_pt.y += textbox.height( BOXAREA_WIDTH ) + spacer; int res = Skill::Primary::ATTACK; const int spacingX = ( box_rt.width - sprite.width() * 3 ) / 4; fheroes2::Rect rect1( dst_pt.x + spacingX, dst_pt.y, sprite.width(), sprite.height() ); fheroes2::Rect rect2( rect1.x + sprite.width() + spacingX, dst_pt.y, sprite.width(), sprite.height() ); fheroes2::Rect rect3( rect2.x + sprite.width() + spacingX, dst_pt.y, sprite.width(), sprite.height() ); InfoSkillClear( rect1, rect2, rect3 ); InfoSkillSelect( res, rect1, rect2, rect3 ); // info texts const int32_t skillTextWidth = 60; fheroes2::Text text( Skill::Primary::String( Skill::Primary::ATTACK ), fheroes2::FontType::smallWhite() ); dst_pt.x = rect1.x + ( rect1.width - skillTextWidth ) / 2; dst_pt.y = rect1.y + rect1.height + 5; text.draw( dst_pt.x, dst_pt.y + 2, skillTextWidth, display ); text.set( Skill::Primary::String( Skill::Primary::DEFENSE ), fheroes2::FontType::smallWhite() ); dst_pt.x = rect2.x + ( rect2.width - skillTextWidth ) / 2; dst_pt.y = rect2.y + rect2.height + 5; text.draw( dst_pt.x, dst_pt.y + 2, skillTextWidth, display ); text.set( Skill::Primary::String( Skill::Primary::POWER ), fheroes2::FontType::smallWhite() ); dst_pt.x = rect3.x + ( rect3.width - skillTextWidth ) / 2; dst_pt.y = rect3.y + rect3.height + 5; text.draw( dst_pt.x, dst_pt.y + 2, skillTextWidth, display ); // buttons dst_pt.x = box_rt.x + ( box_rt.width - fheroes2::AGG::GetICN( system, 1 ).width() ) / 2; dst_pt.y = box_rt.y + box_rt.height - fheroes2::AGG::GetICN( system, 1 ).height(); fheroes2::Button buttonOk( dst_pt.x, dst_pt.y, system, 1, 2 ); LocalEvent & le = LocalEvent::Get(); buttonOk.draw(); display.render(); // message loop while ( le.HandleEvents() ) { bool redraw = false; le.MousePressLeft( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); if ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_LEFT ) && Skill::Primary::UNKNOWN != InfoSkillPrev( res ) ) { res = InfoSkillPrev( res ); redraw = true; } else if ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_RIGHT ) && Skill::Primary::UNKNOWN != InfoSkillNext( res ) ) { res = InfoSkillNext( res ); redraw = true; } else if ( le.MouseClickLeft( rect1 ) ) { res = Skill::Primary::ATTACK; redraw = true; } else if ( le.MouseClickLeft( rect2 ) ) { res = Skill::Primary::DEFENSE; redraw = true; } else if ( le.MouseClickLeft( rect3 ) ) { res = Skill::Primary::POWER; redraw = true; } else if ( le.MousePressRight( rect1 ) ) { fheroes2::PrimarySkillDialogElement( Skill::Primary::ATTACK, "" ).showPopup( Dialog::ZERO ); } else if ( le.MousePressRight( rect2 ) ) { fheroes2::PrimarySkillDialogElement( Skill::Primary::DEFENSE, "" ).showPopup( Dialog::ZERO ); } else if ( le.MousePressRight( rect3 ) ) { fheroes2::PrimarySkillDialogElement( Skill::Primary::POWER, "" ).showPopup( Dialog::ZERO ); } if ( redraw ) { InfoSkillClear( rect1, rect2, rect3 ); InfoSkillSelect( res, rect1, rect2, rect3 ); display.render(); } if ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) || le.MouseClickLeft( buttonOk.area() ) ) break; } return res; } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_armyinfo.cpp000066400000000000000000000737171456075706000236720ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include "agg_image.h" #include "army_troop.h" #include "battle.h" #include "battle_cell.h" #include "cursor.h" #include "dialog.h" #include "game_delays.h" #include "game_hotkeys.h" #include "icn.h" #include "image.h" #include "localevent.h" #include "luck.h" #include "math_base.h" #include "monster.h" #include "monster_anim.h" #include "monster_info.h" #include "morale.h" #include "resource.h" #include "screen.h" #include "settings.h" #include "spell.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_text.h" namespace { const int offsetXAmountBox = 80; const int offsetYAmountBox = 223; const int widthAmountBox = 125; const int heightAmountBox = 23; struct SpellInfo { SpellInfo( const uint32_t mode_, const uint32_t duration_, const int32_t offset_, const int32_t space_ ) : mode( mode_ ) , duration( duration_ ) , offset( offset_ ) , space( space_ ) {} uint32_t mode; uint32_t duration; int32_t offset; int32_t space; Spell spell; }; Spell modeToSpell( const uint32_t modeId ) { switch ( modeId ) { case Battle::SP_BLOODLUST: return Spell::BLOODLUST; case Battle::SP_BLESS: return Spell::BLESS; case Battle::SP_HASTE: return Spell::HASTE; case Battle::SP_SHIELD: return Spell::SHIELD; case Battle::SP_STONESKIN: return Spell::STONESKIN; case Battle::SP_DRAGONSLAYER: return Spell::DRAGONSLAYER; case Battle::SP_STEELSKIN: return Spell::STEELSKIN; case Battle::SP_ANTIMAGIC: return Spell::ANTIMAGIC; case Battle::SP_CURSE: return Spell::CURSE; case Battle::SP_SLOW: return Spell::SLOW; case Battle::SP_BERSERKER: return Spell::BERSERKER; case Battle::SP_HYPNOTIZE: return Spell::HYPNOTIZE; case Battle::SP_BLIND: return Spell::BLIND; case Battle::SP_PARALYZE: return Spell::PARALYZE; case Battle::SP_STONE: return Spell::PETRIFY; default: // Did you add another mode? Please add a corresponding spell. assert( 0 ); break; } return Spell::NONE; } void DrawMonsterStats( const fheroes2::Point & dst, const Troop & troop ) { fheroes2::Display & display = fheroes2::Display::instance(); // attack fheroes2::Text text( std::string( _( "Attack Skill" ) ) + ":", fheroes2::FontType::normalWhite() ); fheroes2::Point dst_pt{ dst.x - text.width(), dst.y }; text.draw( dst_pt.x, dst_pt.y + 2, display ); const int offsetX = 6; const int offsetY = 16; text.set( troop.GetAttackString(), fheroes2::FontType::normalWhite() ); dst_pt.x = dst.x + offsetX; text.draw( dst_pt.x, dst_pt.y + 2, display ); // defense text.set( std::string( _( "Defense Skill" ) ) + ":", fheroes2::FontType::normalWhite() ); dst_pt.x = dst.x - text.width(); dst_pt.y += offsetY; text.draw( dst_pt.x, dst_pt.y + 2, display ); text.set( troop.GetDefenseString(), fheroes2::FontType::normalWhite() ); dst_pt.x = dst.x + offsetX; text.draw( dst_pt.x, dst_pt.y + 2, display ); if ( fheroes2::getMonsterData( troop.GetID() ).battleStats.shots > 0 ) { std::string message = troop.isBattle() ? _( "Shots Left" ) : _( "Shots" ); message += ':'; text.set( std::move( message ), fheroes2::FontType::normalWhite() ); dst_pt.x = dst.x - text.width(); dst_pt.y += offsetY; text.draw( dst_pt.x, dst_pt.y + 2, display ); text.set( troop.GetShotString(), fheroes2::FontType::normalWhite() ); dst_pt.x = dst.x + offsetX; text.draw( dst_pt.x, dst_pt.y + 2, display ); } // damage text.set( std::string( _( "Damage" ) ) + ":", fheroes2::FontType::normalWhite() ); dst_pt.x = dst.x - text.width(); dst_pt.y += offsetY; text.draw( dst_pt.x, dst_pt.y + 2, display ); if ( troop.GetMonster().GetDamageMin() != troop.GetMonster().GetDamageMax() ) { text.set( std::to_string( troop.GetMonster().GetDamageMin() ) + "-" + std::to_string( troop.GetMonster().GetDamageMax() ), fheroes2::FontType::normalWhite() ); } else { text.set( std::to_string( troop.GetMonster().GetDamageMin() ), fheroes2::FontType::normalWhite() ); } dst_pt.x = dst.x + offsetX; text.draw( dst_pt.x, dst_pt.y + 2, display ); // hp text.set( std::string( _( "Hit Points" ) ) + ":", fheroes2::FontType::normalWhite() ); dst_pt.x = dst.x - text.width(); dst_pt.y += offsetY; text.draw( dst_pt.x, dst_pt.y + 2, display ); text.set( std::to_string( troop.GetMonster().GetHitPoints() ), fheroes2::FontType::normalWhite() ); dst_pt.x = dst.x + offsetX; text.draw( dst_pt.x, dst_pt.y + 2, display ); if ( troop.isBattle() && troop.GetCount() != 0 ) { text.set( std::string( _( "Hit Points Left" ) ) + ":", fheroes2::FontType::normalWhite() ); dst_pt.x = dst.x - text.width(); dst_pt.y += offsetY; text.draw( dst_pt.x, dst_pt.y + 2, display ); text.set( std::to_string( troop.GetHitPointsLeft() ), fheroes2::FontType::normalWhite() ); dst_pt.x = dst.x + offsetX; text.draw( dst_pt.x, dst_pt.y + 2, display ); } // speed text.set( std::string( _( "Speed" ) ) + ":", fheroes2::FontType::normalWhite() ); dst_pt.x = dst.x - text.width(); dst_pt.y += offsetY; text.draw( dst_pt.x, dst_pt.y + 2, display ); text.set( troop.GetSpeedString(), fheroes2::FontType::normalWhite() ); dst_pt.x = dst.x + offsetX; text.draw( dst_pt.x, dst_pt.y + 2, display ); // morale text.set( std::string( _( "Morale" ) ) + ":", fheroes2::FontType::normalWhite() ); dst_pt.x = dst.x - text.width(); dst_pt.y += offsetY; text.draw( dst_pt.x, dst_pt.y + 2, display ); text.set( Morale::String( troop.GetMorale() ), fheroes2::FontType::normalWhite() ); dst_pt.x = dst.x + offsetX; text.draw( dst_pt.x, dst_pt.y + 2, display ); // luck text.set( std::string( _( "Luck" ) ) + ":", fheroes2::FontType::normalWhite() ); dst_pt.x = dst.x - text.width(); dst_pt.y += offsetY; text.draw( dst_pt.x, dst_pt.y + 2, display ); text.set( Luck::String( troop.GetLuck() ), fheroes2::FontType::normalWhite() ); dst_pt.x = dst.x + offsetX; text.draw( dst_pt.x, dst_pt.y + 2, display ); } const fheroes2::Sprite & GetModesSprite( const uint32_t mod ) { switch ( mod ) { case Battle::SP_BLOODLUST: return fheroes2::AGG::GetICN( ICN::SPELLINL, 9 ); case Battle::SP_BLESS: return fheroes2::AGG::GetICN( ICN::SPELLINL, 3 ); case Battle::SP_HASTE: return fheroes2::AGG::GetICN( ICN::SPELLINL, 0 ); case Battle::SP_SHIELD: return fheroes2::AGG::GetICN( ICN::SPELLINL, 10 ); case Battle::SP_STONESKIN: return fheroes2::AGG::GetICN( ICN::SPELLINL, 13 ); case Battle::SP_DRAGONSLAYER: return fheroes2::AGG::GetICN( ICN::SPELLINL, 8 ); case Battle::SP_STEELSKIN: return fheroes2::AGG::GetICN( ICN::SPELLINL, 14 ); case Battle::SP_ANTIMAGIC: return fheroes2::AGG::GetICN( ICN::SPELLINL, 12 ); case Battle::SP_CURSE: return fheroes2::AGG::GetICN( ICN::SPELLINL, 4 ); case Battle::SP_SLOW: return fheroes2::AGG::GetICN( ICN::SPELLINL, 1 ); case Battle::SP_BERSERKER: return fheroes2::AGG::GetICN( ICN::SPELLINL, 5 ); case Battle::SP_HYPNOTIZE: return fheroes2::AGG::GetICN( ICN::SPELLINL, 7 ); case Battle::SP_BLIND: return fheroes2::AGG::GetICN( ICN::SPELLINL, 2 ); case Battle::SP_PARALYZE: return fheroes2::AGG::GetICN( ICN::SPELLINL, 6 ); case Battle::SP_STONE: return fheroes2::AGG::GetICN( ICN::SPELLINL, 11 ); default: break; } return fheroes2::AGG::GetICN( ICN::UNKNOWN, 0 ); } std::vector> DrawBattleStats( const fheroes2::Point & dst, const Troop & b ) { std::vector> output; const std::array modes = { Battle::SP_BLOODLUST, Battle::SP_BLESS, Battle::SP_HASTE, Battle::SP_SHIELD, Battle::SP_STONESKIN, Battle::SP_DRAGONSLAYER, Battle::SP_STEELSKIN, Battle::SP_ANTIMAGIC, Battle::SP_CURSE, Battle::SP_SLOW, Battle::SP_BERSERKER, Battle::SP_HYPNOTIZE, Battle::SP_BLIND, Battle::SP_PARALYZE, Battle::SP_STONE }; int32_t ow = 0; int32_t spritesWidth = 0; std::vector spellsInfo; for ( const uint32_t mode : modes ) { if ( !b.isModes( mode ) ) continue; const fheroes2::Sprite & sprite = GetModesSprite( mode ); if ( sprite.empty() ) continue; const uint32_t duration = b.GetAffectedDuration( mode ); int offset = 0; if ( duration > 0 ) { offset = duration >= 10 ? 12 : 7; if ( mode >= Battle::SP_BLESS && mode <= Battle::SP_DRAGONSLAYER ) offset -= 5; } const int space = ( offset == 2 ) ? 10 : 5; spellsInfo.emplace_back( mode, duration, offset, space ); ow += sprite.width() + offset + space; spritesWidth += sprite.width(); } if ( spellsInfo.empty() ) return output; std::sort( spellsInfo.begin(), spellsInfo.end(), []( const SpellInfo & first, const SpellInfo & second ) { return first.duration > 0 && first.duration < second.duration; } ); ow -= spellsInfo.back().space; const int maxSpritesWidth = 212; const int maxSpriteHeight = 32; fheroes2::Display & display = fheroes2::Display::instance(); if ( ow <= maxSpritesWidth ) { ow = dst.x - ow / 2; for ( const auto & spell : spellsInfo ) { const fheroes2::Sprite & sprite = GetModesSprite( spell.mode ); const fheroes2::Point imageOffset( ow, dst.y + maxSpriteHeight - sprite.height() ); fheroes2::Blit( sprite, display, imageOffset.x, imageOffset.y ); output.emplace_back( fheroes2::Rect( imageOffset.x, imageOffset.y, sprite.width(), sprite.height() ), modeToSpell( spell.mode ) ); if ( spell.duration > 0 ) { const fheroes2::Text text( std::to_string( spell.duration ), fheroes2::FontType::smallWhite() ); ow += sprite.width() + spell.offset; text.draw( ow - text.width(), dst.y + maxSpriteHeight - text.height() + 3, display ); } ow += spell.space; } } else { // Too many spells const int widthDiff = maxSpritesWidth - spritesWidth; int space = widthDiff / static_cast( spellsInfo.size() - 1 ); if ( widthDiff > 0 ) { if ( space > 10 ) space = 10; ow = dst.x + ( spritesWidth + space * static_cast( spellsInfo.size() - 1 ) ) / 2; } else { ow = dst.x + maxSpritesWidth / 2; } for ( auto spellIt = spellsInfo.crbegin(); spellIt != spellsInfo.crend(); ++spellIt ) { const fheroes2::Sprite & sprite = GetModesSprite( spellIt->mode ); const fheroes2::Point imageOffset( ow - sprite.width(), dst.y + maxSpriteHeight - sprite.height() ); fheroes2::Blit( sprite, display, imageOffset.x, imageOffset.y ); output.emplace_back( fheroes2::Rect( imageOffset.x, imageOffset.y, sprite.width(), sprite.height() ), modeToSpell( spellIt->mode ) ); if ( spellIt->duration > 0 ) { const fheroes2::Text text( std::to_string( spellIt->duration ), fheroes2::FontType::smallWhite() ); text.draw( ow - text.width(), dst.y + maxSpriteHeight - text.height() + 3, display ); } ow -= sprite.width() + space; } } return output; } void DrawMonsterInfo( const fheroes2::Point & offset, const Troop & troop ) { fheroes2::Display & display = fheroes2::Display::instance(); // name fheroes2::Text text( troop.GetName(), fheroes2::FontType::normalYellow() ); fheroes2::Point pos( offset.x + 29 + ( 227 - text.width() ) / 2, offset.y + 37 ); text.draw( pos.x, pos.y + 2, display ); // Description. const std::vector descriptions = fheroes2::getMonsterPropertiesDescription( troop.GetID() ); if ( !descriptions.empty() ) { const int32_t descriptionWidth = 210; const int32_t maximumRowCount = 3; const int32_t rowHeight = fheroes2::getFontHeight( fheroes2::FontSize::SMALL ); bool asSolidText = true; if ( descriptions.size() <= static_cast( maximumRowCount ) ) { asSolidText = false; for ( const std::string & sentence : descriptions ) { if ( fheroes2::Text( sentence, fheroes2::FontType::smallWhite() ).width() > descriptionWidth ) { asSolidText = true; break; } } } if ( asSolidText ) { std::string description; for ( const std::string & sentence : descriptions ) { if ( !description.empty() ) { description += ' '; } description += sentence; } const fheroes2::Text descriptionText( description, fheroes2::FontType::smallWhite() ); const int32_t rowCount = descriptionText.rows( descriptionWidth ); descriptionText.draw( offset.x + 37, offset.y + 185 + ( maximumRowCount - rowCount ) * rowHeight, descriptionWidth, display ); } else { int32_t sentenceId = maximumRowCount - static_cast( descriptions.size() ); // safe to cast as we check the size before. for ( const std::string & sentence : descriptions ) { const fheroes2::Text descriptionText( sentence, fheroes2::FontType::smallWhite() ); descriptionText.draw( offset.x + 37, offset.y + 185 + sentenceId * rowHeight, descriptionWidth, display ); ++sentenceId; } } } // amount if ( troop.GetCount() != 0 ) { text.set( std::to_string( troop.GetCount() ), fheroes2::FontType::normalWhite() ); pos.x = offset.x + offsetXAmountBox + widthAmountBox / 2 - text.width() / 2; pos.y = offset.y + offsetYAmountBox + heightAmountBox / 2 - text.height() / 2; text.draw( pos.x, pos.y + 2, display ); } } void DrawMonster( fheroes2::RandomMonsterAnimation & monsterAnimation, const Troop & troop, const fheroes2::Point & offset, bool isReflected, bool isAnimated, const fheroes2::Rect & roi ) { const fheroes2::Sprite & monsterSprite = fheroes2::AGG::GetICN( monsterAnimation.icnFile(), monsterAnimation.frameId() ); fheroes2::Point monsterPos( offset.x, offset.y + monsterSprite.y() ); if ( isReflected ) monsterPos.x -= monsterSprite.x() - ( troop.isWide() ? CELLW / 2 : 0 ) - monsterAnimation.offset() + monsterSprite.width(); else monsterPos.x += monsterSprite.x() - ( troop.isWide() ? CELLW / 2 : 0 ) - monsterAnimation.offset(); fheroes2::Point inPos( 0, 0 ); fheroes2::Point outPos( monsterPos.x, monsterPos.y ); fheroes2::Size inSize( monsterSprite.width(), monsterSprite.height() ); fheroes2::Display & display = fheroes2::Display::instance(); if ( fheroes2::FitToRoi( monsterSprite, inPos, display, outPos, inSize, roi ) ) { fheroes2::Blit( monsterSprite, inPos, display, outPos, inSize, isReflected ); } if ( isAnimated ) monsterAnimation.increment(); } } int Dialog::ArmyInfo( const Troop & troop, int flags, bool isReflected, const int32_t windowOffsetY ) { // Unit cannot be dismissed or upgraded during combat assert( !troop.isBattle() || !( flags & BUTTONS ) || !( flags & ( UPGRADE | DISMISS ) ) ); // This function should not be called with the UPGRADE flag for a non-upgradeable unit assert( !( flags & BUTTONS ) || !( flags & UPGRADE ) || troop.isAllowUpgrade() ); // The active size of the window is 520 by 256 pixels fheroes2::Display & display = fheroes2::Display::instance(); const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); const int viewarmy = isEvilInterface ? ICN::VIEWARME : ICN::VIEWARMY; const fheroes2::Sprite & sprite_dialog = fheroes2::AGG::GetICN( viewarmy, 0 ); const fheroes2::Sprite & spriteDialogShadow = fheroes2::AGG::GetICN( viewarmy, 7 ); // setup cursor const CursorRestorer cursorRestorer( ( flags & BUTTONS ) != 0, Cursor::POINTER ); fheroes2::Point dialogOffset( ( display.width() - sprite_dialog.width() ) / 2, ( display.height() - sprite_dialog.height() ) / 2 ); if ( isEvilInterface ) { dialogOffset.y += 3; } dialogOffset.y += windowOffsetY; const fheroes2::Point shadowShift( spriteDialogShadow.x() - sprite_dialog.x(), spriteDialogShadow.y() - sprite_dialog.y() ); const fheroes2::Point shadowOffset( dialogOffset.x + shadowShift.x, dialogOffset.y + shadowShift.y ); const fheroes2::ImageRestorer restorer( display, shadowOffset.x, dialogOffset.y, sprite_dialog.width() - shadowShift.x, sprite_dialog.height() + shadowShift.y ); fheroes2::Blit( spriteDialogShadow, display, dialogOffset.x + shadowShift.x, dialogOffset.y + shadowShift.y ); fheroes2::Blit( sprite_dialog, display, dialogOffset.x, dialogOffset.y ); fheroes2::Rect pos_rt( dialogOffset.x, dialogOffset.y, sprite_dialog.width(), sprite_dialog.height() ); if ( isEvilInterface ) { pos_rt.x += 9; pos_rt.y -= 1; } const fheroes2::Point monsterStatOffset( pos_rt.x + 400, pos_rt.y + 37 ); DrawMonsterStats( monsterStatOffset, troop ); std::vector> spellAreas; const fheroes2::Point battleStatOffset( pos_rt.x + 395, pos_rt.y + 184 ); if ( troop.isBattle() ) { spellAreas = DrawBattleStats( battleStatOffset, troop ); } DrawMonsterInfo( pos_rt.getPosition(), troop ); const bool isAnimated = ( flags & BUTTONS ) != 0; fheroes2::RandomMonsterAnimation monsterAnimation( troop ); const fheroes2::Point monsterOffset( pos_rt.x + 520 / 4 + 16, pos_rt.y + 175 ); if ( !isAnimated ) { monsterAnimation.reset(); } const fheroes2::Rect dialogRoi( pos_rt.x, pos_rt.y + SHADOWWIDTH, sprite_dialog.width(), sprite_dialog.height() - 2 * SHADOWWIDTH ); DrawMonster( monsterAnimation, troop, monsterOffset, isReflected, isAnimated, dialogRoi ); const int upgradeButtonIcnID = isEvilInterface ? ICN::BUTTON_SMALL_UPGRADE_EVIL : ICN::BUTTON_SMALL_UPGRADE_GOOD; fheroes2::Point dst_pt( pos_rt.x + 400, pos_rt.y + 40 ); dst_pt.x = pos_rt.x + 280; dst_pt.y = pos_rt.y + 192; fheroes2::Button buttonUpgrade( dst_pt.x, dst_pt.y, upgradeButtonIcnID, 0, 1 ); const int dismissButtonIcnID = isEvilInterface ? ICN::BUTTON_SMALL_DISMISS_EVIL : ICN::BUTTON_SMALL_DISMISS_GOOD; dst_pt.x = pos_rt.x + 280; dst_pt.y = pos_rt.y + 221; fheroes2::Button buttonDismiss( dst_pt.x, dst_pt.y, dismissButtonIcnID, 0, 1 ); const int exitButtonIcnID = isEvilInterface ? ICN::BUTTON_SMALL_EXIT_EVIL : ICN::BUTTON_SMALL_EXIT_GOOD; dst_pt.x = pos_rt.x + 415; dst_pt.y = pos_rt.y + 221; fheroes2::Button buttonExit( dst_pt.x, dst_pt.y, exitButtonIcnID, 0, 1 ); if ( ( flags & ( BUTTONS | UPGRADE ) ) == ( BUTTONS | UPGRADE ) ) { buttonUpgrade.enable(); buttonUpgrade.draw(); } else { buttonUpgrade.disable(); } if ( ( flags & ( BUTTONS | DISMISS ) ) == ( BUTTONS | DISMISS ) ) { buttonDismiss.enable(); buttonDismiss.draw(); } else { buttonDismiss.disable(); } if ( flags & BUTTONS ) { buttonExit.draw(); } LocalEvent & le = LocalEvent::Get(); int result = Dialog::ZERO; display.render( restorer.rect() ); while ( le.HandleEvents( ( flags & BUTTONS ) ? Game::isDelayNeeded( { Game::CASTLE_UNIT_DELAY } ) : true ) ) { if ( !( flags & BUTTONS ) ) { if ( !le.MousePressRight() ) { break; } continue; } if ( buttonUpgrade.isEnabled() ) { le.MousePressLeft( buttonUpgrade.area() ) ? buttonUpgrade.drawOnPress() : buttonUpgrade.drawOnRelease(); } if ( buttonDismiss.isEnabled() ) { le.MousePressLeft( buttonDismiss.area() ) ? buttonDismiss.drawOnPress() : buttonDismiss.drawOnRelease(); } le.MousePressLeft( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); if ( buttonUpgrade.isEnabled() && ( le.MouseClickLeft( buttonUpgrade.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::ARMY_UPGRADE_TROOP ) ) ) { // If this assertion blows up then you are executing this code for a monster which has no upgrades. assert( troop.isAllowUpgrade() ); if ( UPGRADE_DISABLE & flags ) { const fheroes2::Text description( _( "You can't afford to upgrade your troops!" ), fheroes2::FontType::normalWhite() ); fheroes2::showResourceMessage( fheroes2::Text( "", {} ), description, Dialog::OK, troop.GetTotalUpgradeCost() ); } else { const fheroes2::Text description( _( "Your troops can be upgraded, but it will cost you dearly. Do you wish to upgrade them?" ), fheroes2::FontType::normalWhite() ); if ( fheroes2::showResourceMessage( fheroes2::Text( "", {} ), description, Dialog::YES | Dialog::NO, troop.GetTotalUpgradeCost() ) == Dialog::YES ) { result = Dialog::UPGRADE; break; } } } if ( buttonDismiss.isEnabled() && ( le.MouseClickLeft( buttonDismiss.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::ARMY_DISMISS ) ) && Dialog::YES == fheroes2::showStandardTextMessage( troop.GetPluralName( troop.GetCount() ), _( "Are you sure you want to dismiss this army?" ), Dialog::YES | Dialog::NO ) ) { result = Dialog::DISMISS; break; } if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) { result = Dialog::CANCEL; break; } if ( le.MousePressRight( buttonExit.area() ) ) { fheroes2::showStandardTextMessage( _( "Exit" ), _( "Exit this menu." ), 0 ); } else if ( buttonUpgrade.isEnabled() && le.MousePressRight( buttonUpgrade.area() ) ) { fheroes2::showStandardTextMessage( _( "Upgrade" ), _( "Upgrade your troops." ), 0 ); } else if ( buttonDismiss.isEnabled() && le.MousePressRight( buttonDismiss.area() ) ) { fheroes2::showStandardTextMessage( _( "Dismiss" ), _( "Dismiss this army." ), 0 ); } for ( const auto & spellInfo : spellAreas ) { if ( le.MousePressRight( spellInfo.first ) ) { fheroes2::SpellDialogElement( spellInfo.second, nullptr ).showPopup( Dialog::ZERO ); break; } } if ( Game::validateAnimationDelay( Game::CASTLE_UNIT_DELAY ) ) { fheroes2::Blit( sprite_dialog, display, dialogOffset.x, dialogOffset.y ); DrawMonsterStats( monsterStatOffset, troop ); if ( troop.isBattle() ) { spellAreas = DrawBattleStats( battleStatOffset, troop ); } DrawMonsterInfo( pos_rt.getPosition(), troop ); DrawMonster( monsterAnimation, troop, monsterOffset, isReflected, true, dialogRoi ); if ( buttonUpgrade.isEnabled() ) { buttonUpgrade.draw(); } if ( buttonDismiss.isEnabled() ) { buttonDismiss.draw(); } if ( buttonExit.isEnabled() ) { buttonExit.draw(); } display.render( restorer.rect() ); } } return result; } int Dialog::ArmyJoinFree( const Troop & troop ) { std::string message = _( "A group of %{monster} with a desire for greater glory wish to join you.\nDo you accept?" ); StringReplaceWithLowercase( message, "%{monster}", troop.GetMultiName() ); return fheroes2::showMessage( fheroes2::Text( _( "Followers" ), fheroes2::FontType::normalYellow() ), fheroes2::Text( std::move( message ), fheroes2::FontType::normalWhite() ), Dialog::YES | Dialog::NO ); } int Dialog::ArmyJoinWithCost( const Troop & troop, const uint32_t join, const uint32_t gold ) { fheroes2::Display & display = fheroes2::Display::instance(); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); std::string message; if ( troop.GetCount() == 1 ) { message = _( "The %{monster} is swayed by your diplomatic tongue, and offers to join your army for the sum of %{gold} gold.\nDo you accept?" ); } else { message = _( "The creatures are swayed by your diplomatic\ntongue, and make you an offer:\n\n" ); if ( join != troop.GetCount() ) message += _( "%{offer} of the %{total} %{monster} will join your army, and the rest will leave you alone, for the sum of %{gold} gold.\nDo you accept?" ); else message += _( "All %{offer} of the %{monster} will join your army for the sum of %{gold} gold.\nDo you accept?" ); } StringReplace( message, "%{offer}", join ); StringReplace( message, "%{total}", troop.GetCount() ); StringReplaceWithLowercase( message, "%{monster}", troop.GetPluralName( join ) ); StringReplace( message, "%{gold}", gold ); fheroes2::Text textbox( message, fheroes2::FontType::normalWhite() ); const int buttons = Dialog::YES | Dialog::NO; const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::RESOURCE, 6 ); int posy = 0; message = _( "(Rate: %{percent})" ); StringReplace( message, "%{percent}", troop.GetMonster().GetCost().gold * join * 100 / gold ); fheroes2::Text text( message, fheroes2::FontType::normalWhite() ); FrameBox box( 10 + textbox.height( BOXAREA_WIDTH ) + 10 + text.height() + 40 + sprite.height() + 10, true ); const fheroes2::Rect & pos = box.GetArea(); posy = pos.y + 10; textbox.draw( pos.x, posy + 2, BOXAREA_WIDTH, display ); posy += textbox.height( BOXAREA_WIDTH ) + 10; text.draw( pos.x + ( pos.width - text.width() ) / 2, posy + 2, display ); posy += text.height() + 40; fheroes2::Blit( sprite, display, pos.x + ( pos.width - sprite.width() ) / 2, posy ); const fheroes2::Text goldText( std::to_string( gold ), fheroes2::FontType::smallWhite() ); goldText.draw( pos.x + ( pos.width - goldText.width() ) / 2, posy + sprite.height() + 5, display ); fheroes2::ButtonGroup btnGroup( pos, buttons ); btnGroup.draw(); display.render(); LocalEvent & le = LocalEvent::Get(); // message loop int result = Dialog::ZERO; while ( result == Dialog::ZERO && le.HandleEvents() ) { result = btnGroup.processEvents(); } return result; } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_artifact.cpp000066400000000000000000000041541456075706000236300ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include "artifact.h" #include "audio_manager.h" #include "dialog.h" #include "m82.h" #include "translations.h" #include "ui_dialog.h" #include "ui_text.h" void Dialog::ArtifactSetAssembled( const ArtifactSetData & artifactSetData ) { const Artifact artifact( artifactSetData._assembledArtifactID ); const fheroes2::ArtifactDialogElement artifactUI( artifact ); AudioManager::PlaySound( M82::TREASURE ); fheroes2::showMessage( fheroes2::Text( artifact.GetName(), fheroes2::FontType::normalYellow() ), fheroes2::Text( _( artifactSetData._assembleMessage ), fheroes2::FontType::normalWhite() ), Dialog::OK, { &artifactUI } ); } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_audio.cpp000066400000000000000000000253371456075706000231420ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include "agg_image.h" #include "audio.h" #include "audio_manager.h" #include "cursor.h" #include "dialog_audio.h" #include "game.h" #include "game_hotkeys.h" #include "gamedefs.h" #include "icn.h" #include "image.h" #include "localevent.h" #include "math_base.h" #include "screen.h" #include "settings.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_option_item.h" namespace { void drawDialog( const std::vector & rects ) { assert( rects.size() == 4 ); const Settings & conf = Settings::Get(); // Music volume. const fheroes2::Sprite & musicVolumeIcon = fheroes2::AGG::GetICN( ICN::SPANEL, Audio::isValid() ? 1 : 0 ); std::string value; if ( Audio::isValid() && conf.MusicVolume() ) { value = std::to_string( conf.MusicVolume() ); } else { value = _( "off" ); } fheroes2::drawOption( rects[0], musicVolumeIcon, _( "Music" ), value, fheroes2::UiOptionTextWidth::TWO_ELEMENTS_ROW ); // Sound volume. const fheroes2::Sprite & soundVolumeOption = fheroes2::AGG::GetICN( ICN::SPANEL, Audio::isValid() ? 3 : 2 ); if ( Audio::isValid() && conf.SoundVolume() ) { value = std::to_string( conf.SoundVolume() ); } else { value = _( "off" ); } fheroes2::drawOption( rects[1], soundVolumeOption, _( "Effects" ), value, fheroes2::UiOptionTextWidth::TWO_ELEMENTS_ROW ); // Music Type. const MusicSource musicType = conf.MusicType(); const fheroes2::Sprite & musicTypeIcon = fheroes2::AGG::GetICN( ICN::SPANEL, musicType == MUSIC_EXTERNAL ? 11 : 10 ); if ( musicType == MUSIC_MIDI_ORIGINAL ) { value = _( "MIDI" ); } else if ( musicType == MUSIC_MIDI_EXPANSION ) { value = _( "MIDI Expansion" ); } else if ( musicType == MUSIC_EXTERNAL ) { value = _( "External" ); } fheroes2::drawOption( rects[2], musicTypeIcon, _( "Music Type" ), value, fheroes2::UiOptionTextWidth::TWO_ELEMENTS_ROW ); // 3D Audio. const bool is3DAudioEnabled = conf.is3DAudioEnabled(); const fheroes2::Sprite & interfaceStateIcon = is3DAudioEnabled ? fheroes2::AGG::GetICN( ICN::SPANEL, 11 ) : fheroes2::AGG::GetICN( ICN::SPANEL, 10 ); if ( is3DAudioEnabled ) { value = _( "On" ); } else { value = _( "Off" ); } fheroes2::drawOption( rects[3], interfaceStateIcon, _( "3D Audio" ), value, fheroes2::UiOptionTextWidth::TWO_ELEMENTS_ROW ); } } namespace Dialog { void openAudioSettingsDialog( const bool fromAdventureMap ) { const CursorRestorer cursorRestorer( true, Cursor::POINTER ); Settings & conf = Settings::Get(); const bool isEvilInterface = conf.isEvilInterfaceEnabled(); fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::Sprite & dialog = fheroes2::AGG::GetICN( ( isEvilInterface ? ICN::ESPANBKG_EVIL : ICN::ESPANBKG ), 0 ); const fheroes2::Sprite & dialogShadow = fheroes2::AGG::GetICN( ( isEvilInterface ? ICN::CSPANBKE : ICN::CSPANBKG ), 1 ); const fheroes2::Point dialogOffset( ( display.width() - dialog.width() ) / 2, ( display.height() - dialog.height() ) / 2 ); const fheroes2::Point shadowOffset( dialogOffset.x - BORDERWIDTH, dialogOffset.y ); fheroes2::ImageRestorer back( display, shadowOffset.x, shadowOffset.y, dialog.width() + BORDERWIDTH, dialog.height() + BORDERWIDTH ); const fheroes2::Rect dialogArea( dialogOffset.x, dialogOffset.y, dialog.width(), dialog.height() ); fheroes2::Fill( display, dialogArea.x, dialogArea.y, dialogArea.width, dialogArea.height, 0 ); fheroes2::Blit( dialogShadow, display, dialogArea.x - BORDERWIDTH, dialogArea.y + BORDERWIDTH ); fheroes2::Blit( dialog, display, dialogArea.x, dialogArea.y ); const fheroes2::Sprite & optionSprite = fheroes2::AGG::GetICN( ICN::SPANEL, 0 ); const fheroes2::Point optionOffset( 69 + dialogArea.x, 47 + dialogArea.y ); const fheroes2::Point optionStep( 118, 110 ); std::vector roi; roi.reserve( 4 ); roi.emplace_back( optionOffset.x, optionOffset.y, optionSprite.width(), optionSprite.height() ); roi.emplace_back( optionOffset.x + optionStep.x, optionOffset.y, optionSprite.width(), optionSprite.height() ); roi.emplace_back( optionOffset.x, optionOffset.y + optionStep.y, optionSprite.width(), optionSprite.height() ); roi.emplace_back( optionOffset.x + optionStep.x, optionOffset.y + optionStep.y, optionSprite.width(), optionSprite.height() ); const fheroes2::Rect & musicVolumeRoi = roi[0]; const fheroes2::Rect & soundVolumeRoi = roi[1]; const fheroes2::Rect & musicTypeRoi = roi[2]; const fheroes2::Rect & audio3D = roi[3]; drawDialog( roi ); const fheroes2::Point buttonOffset( 112 + dialogArea.x, 252 + dialogArea.y ); fheroes2::Button buttonOkay( buttonOffset.x, buttonOffset.y, isEvilInterface ? ICN::BUTTON_SMALL_OKAY_EVIL : ICN::BUTTON_SMALL_OKAY_GOOD, 0, 1 ); buttonOkay.draw(); display.render(); bool saveConfig = false; // dialog menu loop LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { le.MousePressLeft( buttonOkay.area() ) ? buttonOkay.drawOnPress() : buttonOkay.drawOnRelease(); if ( le.MouseClickLeft( buttonOkay.area() ) || Game::HotKeyCloseWindow() ) { break; } // set music or sound volume bool saveMusicVolume = false; bool saveSoundVolume = false; if ( Audio::isValid() ) { if ( le.MouseClickLeft( musicVolumeRoi ) ) { conf.SetMusicVolume( ( conf.MusicVolume() + 1 ) % 11 ); saveMusicVolume = true; } else if ( le.MouseWheelUp( musicVolumeRoi ) ) { conf.SetMusicVolume( conf.MusicVolume() + 1 ); saveMusicVolume = true; } else if ( le.MouseWheelDn( musicVolumeRoi ) ) { conf.SetMusicVolume( conf.MusicVolume() - 1 ); saveMusicVolume = true; } if ( saveMusicVolume ) { Music::setVolume( 100 * conf.MusicVolume() / 10 ); } if ( le.MouseClickLeft( soundVolumeRoi ) ) { conf.SetSoundVolume( ( conf.SoundVolume() + 1 ) % 11 ); saveSoundVolume = true; } else if ( le.MouseWheelUp( soundVolumeRoi ) ) { conf.SetSoundVolume( conf.SoundVolume() + 1 ); saveSoundVolume = true; } else if ( le.MouseWheelDn( soundVolumeRoi ) ) { conf.SetSoundVolume( conf.SoundVolume() - 1 ); saveSoundVolume = true; } if ( le.MouseClickLeft( audio3D ) ) { conf.set3DAudio( !conf.is3DAudioEnabled() ); saveSoundVolume = true; } if ( saveSoundVolume && fromAdventureMap ) { Game::EnvironmentSoundMixer(); } } // set music type bool saveMusicType = false; if ( le.MouseClickLeft( musicTypeRoi ) ) { int type = conf.MusicType() + 1; // If there's no expansion files we skip this option if ( type == MUSIC_MIDI_EXPANSION && !conf.isPriceOfLoyaltySupported() ) { ++type; } conf.SetMusicType( type > MUSIC_EXTERNAL ? 0 : type ); AudioManager::PlayCurrentMusic(); saveMusicType = true; } if ( le.MousePressRight( musicVolumeRoi ) ) { fheroes2::showStandardTextMessage( _( "Music" ), _( "Toggle ambient music level." ), 0 ); } else if ( le.MousePressRight( soundVolumeRoi ) ) { fheroes2::showStandardTextMessage( _( "Effects" ), _( "Toggle foreground sounds level." ), 0 ); } else if ( le.MousePressRight( musicTypeRoi ) ) { fheroes2::showStandardTextMessage( _( "Music Type" ), _( "Change the type of music." ), 0 ); } else if ( le.MousePressRight( audio3D ) ) { fheroes2::showStandardTextMessage( _( "3D Audio" ), _( "Toggle 3D effects of foreground sounds." ), 0 ); } else if ( le.MousePressRight( buttonOkay.area() ) ) { fheroes2::showStandardTextMessage( _( "Okay" ), _( "Exit this menu." ), 0 ); } if ( saveMusicVolume || saveSoundVolume || saveMusicType ) { // redraw fheroes2::Blit( dialog, display, dialogArea.x, dialogArea.y ); drawDialog( roi ); buttonOkay.draw(); display.render(); saveConfig = true; } } if ( saveConfig ) { Settings::Get().Save( Settings::configFileName ); } } } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_audio.h000066400000000000000000000030341456075706000225750ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once namespace Dialog { void openAudioSettingsDialog( const bool fromAdventureMap ); } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_box.cpp000066400000000000000000000164741456075706000226330ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include "agg_image.h" #include "dialog.h" #include "icn.h" #include "image.h" #include "math_base.h" #include "screen.h" #include "settings.h" namespace { const int32_t windowWidth = 288; // this is measured value const int32_t buttonHeight = 40; const int32_t activeAreaHeight = 35; int32_t topHeight( const bool isEvilInterface ) { const fheroes2::Sprite & image = fheroes2::AGG::GetICN( isEvilInterface ? ICN::BUYBUILE : ICN::BUYBUILD, 0 ); return image.height(); } int32_t bottomHeight( const bool isEvilInterface ) { const fheroes2::Sprite & image = fheroes2::AGG::GetICN( isEvilInterface ? ICN::BUYBUILE : ICN::BUYBUILD, 2 ); return image.height(); } int32_t leftWidth( const bool isEvilInterface ) { const int icnId = isEvilInterface ? ICN::BUYBUILE : ICN::BUYBUILD; const fheroes2::Sprite & image3 = fheroes2::AGG::GetICN( icnId, 3 ); const fheroes2::Sprite & image4 = fheroes2::AGG::GetICN( icnId, 4 ); const fheroes2::Sprite & image5 = fheroes2::AGG::GetICN( icnId, 5 ); int32_t widthLeft = image3.width(); widthLeft = std::max( widthLeft, image4.width() ); widthLeft = std::max( widthLeft, image5.width() ); return widthLeft; } int32_t rightWidth( const bool isEvilInterface ) { const int icnId = isEvilInterface ? ICN::BUYBUILE : ICN::BUYBUILD; const fheroes2::Sprite & image0 = fheroes2::AGG::GetICN( icnId, 0 ); const fheroes2::Sprite & image1 = fheroes2::AGG::GetICN( icnId, 1 ); const fheroes2::Sprite & image2 = fheroes2::AGG::GetICN( icnId, 2 ); int32_t widthRight = image0.width(); widthRight = std::max( widthRight, image1.width() ); widthRight = std::max( widthRight, image2.width() ); return widthRight; } int32_t overallWidth( const bool isEvilInterface ) { return leftWidth( isEvilInterface ) + rightWidth( isEvilInterface ); } int32_t leftOffset( const bool isEvilInterface ) { return leftWidth( isEvilInterface ) - windowWidth / 2; } } Dialog::NonFixedFrameBox::NonFixedFrameBox( int height, int startYPos, bool showButtons ) : _middleFragmentCount( 0 ) , _middleFragmentHeight( 0 ) { if ( showButtons ) height += buttonHeight; const bool evil = Settings::Get().isEvilInterfaceEnabled(); _middleFragmentCount = ( height <= 2 * activeAreaHeight ? 0 : 1 + ( height - 2 * activeAreaHeight ) / activeAreaHeight ); _middleFragmentHeight = height <= 2 * activeAreaHeight ? 0 : height - 2 * activeAreaHeight; const int32_t height_top_bottom = topHeight( evil ) + bottomHeight( evil ); area.width = BOXAREA_WIDTH; area.height = activeAreaHeight + activeAreaHeight + _middleFragmentHeight; fheroes2::Display & display = fheroes2::Display::instance(); const int32_t leftSideOffset = leftOffset( evil ); _position.x = ( display.width() - windowWidth ) / 2 - leftSideOffset; _position.y = startYPos; if ( startYPos < 0 ) { _position.y = ( ( display.height() - _middleFragmentHeight ) / 2 ) - topHeight( evil ); } _restorer.reset( new fheroes2::ImageRestorer( display, _position.x, _position.y, overallWidth( evil ), height_top_bottom + _middleFragmentHeight ) ); area.x = _position.x + ( windowWidth - BOXAREA_WIDTH ) / 2 + leftSideOffset; area.y = _position.y + ( topHeight( evil ) - activeAreaHeight ); redraw(); } void Dialog::NonFixedFrameBox::redraw() { const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); const int buybuild = isEvilInterface ? ICN::BUYBUILE : ICN::BUYBUILD; const int32_t overallLeftWidth = leftWidth( isEvilInterface ); // right-top const fheroes2::Sprite & part0 = fheroes2::AGG::GetICN( buybuild, 0 ); // left-top const fheroes2::Sprite & part4 = fheroes2::AGG::GetICN( buybuild, 4 ); fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::Blit( part4, display, _position.x + overallLeftWidth - part4.width(), _position.y ); fheroes2::Blit( part0, display, _position.x + overallLeftWidth, _position.y ); _position.y += part4.height(); const int32_t posBeforeMiddle = _position.y; int32_t middleLeftHeight = _middleFragmentHeight; for ( uint32_t i = 0; i < _middleFragmentCount; ++i ) { const int32_t chunkHeight = middleLeftHeight >= activeAreaHeight ? activeAreaHeight : middleLeftHeight; // left-middle const fheroes2::Sprite & sl = fheroes2::AGG::GetICN( buybuild, 5 ); fheroes2::Blit( sl, 0, 10, display, _position.x + overallLeftWidth - sl.width(), _position.y, sl.width(), chunkHeight ); // right-middle const fheroes2::Sprite & sr = fheroes2::AGG::GetICN( buybuild, 1 ); fheroes2::Blit( sr, 0, 10, display, _position.x + overallLeftWidth, _position.y, sr.width(), chunkHeight ); middleLeftHeight -= chunkHeight; _position.y += chunkHeight; } _position.y = posBeforeMiddle + _middleFragmentHeight; // right-bottom const fheroes2::Sprite & part2 = fheroes2::AGG::GetICN( buybuild, 2 ); // left-bottom const fheroes2::Sprite & part6 = fheroes2::AGG::GetICN( buybuild, 6 ); fheroes2::Blit( part6, display, _position.x + overallLeftWidth - part6.width(), _position.y ); fheroes2::Blit( part2, display, _position.x + overallLeftWidth, _position.y ); } Dialog::NonFixedFrameBox::~NonFixedFrameBox() { _restorer->restore(); fheroes2::Display::instance().render( _restorer->rect() ); } Dialog::FrameBox::FrameBox( int height, bool buttons ) : Dialog::NonFixedFrameBox( height, -1, buttons ) {} fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_buyboat.cpp000066400000000000000000000115411456075706000234760ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "agg_image.h" #include "cursor.h" #include "dialog.h" #include "game_hotkeys.h" #include "icn.h" #include "image.h" #include "localevent.h" #include "math_base.h" #include "payment.h" #include "resource.h" #include "screen.h" #include "settings.h" #include "translations.h" #include "ui_button.h" #include "ui_text.h" int Dialog::BuyBoat( bool enable ) { fheroes2::Display & display = fheroes2::Display::instance(); const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); Resource::BoxSprite rbs( PaymentConditions::BuyBoat(), BOXAREA_WIDTH ); const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::BOATWIND, 0 ); fheroes2::Text text{ _( "Build a new ship:" ), fheroes2::FontType::normalWhite() }; const int spacer = 10; Dialog::FrameBox box( text.height() + spacer + sprite.height() + spacer + text.height() + spacer + rbs.GetArea().height - 20, true ); const fheroes2::Rect & box_rt = box.GetArea(); fheroes2::Point dst_pt( box_rt.x + ( box_rt.width - text.width() ) / 2, box_rt.y ); text.draw( dst_pt.x, dst_pt.y + 2, display ); dst_pt.x = box_rt.x + ( box_rt.width - sprite.width() ) / 2; dst_pt.y = box_rt.y + text.height() + spacer; fheroes2::Blit( sprite, display, dst_pt.x, dst_pt.y ); text.set( _( "Resource cost:" ), fheroes2::FontType::normalWhite() ); dst_pt.x = box_rt.x + ( box_rt.width - text.width() ) / 2; dst_pt.y = dst_pt.y + sprite.height() + spacer; text.draw( dst_pt.x, dst_pt.y + 2, display ); rbs.SetPos( box_rt.x, dst_pt.y + spacer ); rbs.Redraw(); // buttons const int buttonOkayICN = isEvilInterface ? ICN::UNIFORM_EVIL_OKAY_BUTTON : ICN::UNIFORM_GOOD_OKAY_BUTTON; dst_pt.x = box_rt.x; dst_pt.y = box_rt.y + box_rt.height - fheroes2::AGG::GetICN( buttonOkayICN, 0 ).height(); fheroes2::Button buttonOkay( dst_pt.x, dst_pt.y, buttonOkayICN, 0, 1 ); const int buttonCancelICN = isEvilInterface ? ICN::UNIFORM_EVIL_CANCEL_BUTTON : ICN::UNIFORM_GOOD_CANCEL_BUTTON; dst_pt.x = box_rt.x + box_rt.width - fheroes2::AGG::GetICN( buttonCancelICN, 0 ).width(); dst_pt.y = box_rt.y + box_rt.height - fheroes2::AGG::GetICN( buttonCancelICN, 0 ).height(); fheroes2::Button buttonCancel( dst_pt.x, dst_pt.y, buttonCancelICN, 0, 1 ); if ( !enable ) { buttonOkay.press(); buttonOkay.disable(); } buttonOkay.draw(); buttonCancel.draw(); display.render(); LocalEvent & le = LocalEvent::Get(); // message loop while ( le.HandleEvents() ) { if ( buttonOkay.isEnabled() ) le.MousePressLeft( buttonOkay.area() ) ? buttonOkay.drawOnPress() : buttonOkay.drawOnRelease(); le.MousePressLeft( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); if ( buttonOkay.isEnabled() && ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) || le.MouseClickLeft( buttonOkay.area() ) ) ) return Dialog::OK; if ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancel.area() ) ) return Dialog::CANCEL; } return Dialog::ZERO; } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_chest.cpp000066400000000000000000000134071456075706000231420ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include "agg_image.h" #include "cursor.h" #include "dialog.h" #include "game_hotkeys.h" #include "heroes.h" #include "icn.h" #include "image.h" #include "kingdom.h" #include "localevent.h" #include "math_base.h" #include "resource.h" #include "screen.h" #include "settings.h" #include "translations.h" #include "ui_button.h" #include "ui_text.h" #include "world.h" bool Dialog::SelectGoldOrExp( const std::string & header, const std::string & message, uint32_t gold, uint32_t expr, const Heroes & hero ) { fheroes2::Display & display = fheroes2::Display::instance(); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); const fheroes2::Sprite & sprite_gold = fheroes2::AGG::GetICN( ICN::RESOURCE, 6 ); const fheroes2::Sprite & sprite_expr = fheroes2::AGG::GetICN( ICN::EXPMRL, 4 ); fheroes2::Text headerText( header, fheroes2::FontType::normalYellow() ); fheroes2::Text messageText( message, fheroes2::FontType::normalWhite() ); fheroes2::Text text{ std::to_string( gold ) + " (" + _( "Total: " ) + std::to_string( world.GetKingdom( hero.GetColor() ).GetFunds().Get( Resource::GOLD ) ) + ")", fheroes2::FontType::smallWhite() }; const int spacer = 10; FrameBox box( headerText.height( BOXAREA_WIDTH ) + spacer + messageText.height( BOXAREA_WIDTH ) + spacer + sprite_expr.height() + 2 + text.height(), true ); fheroes2::Point pt; const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); const int buttonYesIcnID = isEvilInterface ? ICN::BUTTON_SMALL_YES_EVIL : ICN::BUTTON_SMALL_YES_GOOD; const int buttonNoIcnID = isEvilInterface ? ICN::BUTTON_SMALL_NO_EVIL : ICN::BUTTON_SMALL_NO_GOOD; pt.x = box.GetArea().x + box.GetArea().width / 2 - fheroes2::AGG::GetICN( buttonYesIcnID, 0 ).width() - 20; pt.y = box.GetArea().y + box.GetArea().height - fheroes2::AGG::GetICN( buttonYesIcnID, 0 ).height(); fheroes2::Button button_yes( pt.x, pt.y, buttonYesIcnID, 0, 1 ); pt.x = box.GetArea().x + box.GetArea().width / 2 + 20; pt.y = box.GetArea().y + box.GetArea().height - fheroes2::AGG::GetICN( buttonNoIcnID, 0 ).height(); fheroes2::Button button_no( pt.x, pt.y, buttonNoIcnID, 0, 1 ); fheroes2::Rect pos = box.GetArea(); if ( !header.empty() ) { headerText.draw( pos.x, pos.y + 2, BOXAREA_WIDTH, display ); } pos.y += headerText.height( BOXAREA_WIDTH ) + spacer; if ( !message.empty() ) { messageText.draw( pos.x, pos.y + 2, BOXAREA_WIDTH, display ); } pos.y += messageText.height( BOXAREA_WIDTH ) + spacer; pos.y += sprite_expr.height(); // sprite1 pos.x = box.GetArea().x + box.GetArea().width / 2 - sprite_gold.width() - 30; fheroes2::Blit( sprite_gold, display, pos.x, pos.y - sprite_gold.height() ); // text text.draw( pos.x + sprite_gold.width() / 2 - text.width() / 2, pos.y + 4, display ); // sprite2 pos.x = box.GetArea().x + box.GetArea().width / 2 + 30; fheroes2::Blit( sprite_expr, display, pos.x, pos.y - sprite_expr.height() ); // text text.set( std::to_string( expr ) + " (" + _( "Need: " ) + std::to_string( Heroes::GetExperienceFromLevel( hero.GetLevel() ) - hero.GetExperience() ) + ")", fheroes2::FontType::smallWhite() ); text.draw( pos.x + sprite_expr.width() / 2 - text.width() / 2, pos.y + 4, display ); button_yes.draw(); button_no.draw(); display.render(); LocalEvent & le = LocalEvent::Get(); bool result = false; // message loop while ( le.HandleEvents() ) { le.MousePressLeft( button_yes.area() ) ? button_yes.drawOnPress() : button_yes.drawOnRelease(); le.MousePressLeft( button_no.area() ) ? button_no.drawOnPress() : button_no.drawOnRelease(); if ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) || le.MouseClickLeft( button_yes.area() ) ) { result = true; break; } if ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( button_no.area() ) ) { result = false; break; } } return result; } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_file.cpp000066400000000000000000000215501456075706000227510ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include "cursor.h" #include "dialog.h" #include "dir.h" #include "game_hotkeys.h" #include "game_interface.h" #include "game_io.h" #include "game_mode.h" #include "gamedefs.h" #include "icn.h" #include "image.h" #include "localevent.h" #include "screen.h" #include "settings.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_text.h" #include "ui_window.h" namespace { fheroes2::GameMode selectFileOption() { // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); fheroes2::Display & display = fheroes2::Display::instance(); const int32_t totalDialogWidth = 337; const int32_t totalDialogHeight = 252; // Prepare restorer of the adventure map when save feedback dialog is shown. fheroes2::ImageRestorer back( display, ( display.width() - totalDialogWidth - BORDERWIDTH ) / 2, ( display.height() - totalDialogHeight + BORDERWIDTH ) / 2, totalDialogWidth, totalDialogHeight ); fheroes2::StandardWindow background( 289, 204, true, display ); fheroes2::Button buttonNew; fheroes2::Button buttonLoad; fheroes2::Button buttonSave; fheroes2::Button buttonQuit; fheroes2::Button buttonCancel; const int32_t largeButtonsXOffset = 30; const int32_t largeButtonsYOffset = 15; const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); background.renderButton( buttonNew, isEvilInterface ? ICN::BUTTON_NEW_GAME_EVIL : ICN::BUTTON_NEW_GAME_GOOD, 0, 1, { largeButtonsXOffset, largeButtonsYOffset }, fheroes2::StandardWindow::Padding::TOP_LEFT ); background.renderButton( buttonLoad, isEvilInterface ? ICN::BUTTON_LOAD_GAME_EVIL : ICN::BUTTON_LOAD_GAME_GOOD, 0, 1, { largeButtonsXOffset, largeButtonsYOffset }, fheroes2::StandardWindow::Padding::TOP_RIGHT ); background.renderButton( buttonSave, isEvilInterface ? ICN::BUTTON_SAVE_GAME_EVIL : ICN::BUTTON_SAVE_GAME_GOOD, 0, 1, { largeButtonsXOffset, largeButtonsYOffset + 2 }, fheroes2::StandardWindow::Padding::CENTER_LEFT ); background.renderButton( buttonQuit, isEvilInterface ? ICN::BUTTON_QUIT_EVIL : ICN::BUTTON_QUIT_GOOD, 0, 1, { largeButtonsXOffset, largeButtonsYOffset + 2 }, fheroes2::StandardWindow::Padding::CENTER_RIGHT ); background.renderButton( buttonCancel, isEvilInterface ? ICN::BUTTON_SMALL_CANCEL_EVIL : ICN::BUTTON_SMALL_CANCEL_GOOD, 0, 1, { 0, 11 }, fheroes2::StandardWindow::Padding::BOTTOM_CENTER ); display.render( background.totalArea() ); fheroes2::GameMode result = fheroes2::GameMode::QUIT_GAME; LocalEvent & le = LocalEvent::Get(); // dialog menu loop while ( le.HandleEvents() ) { le.MousePressLeft( buttonNew.area() ) ? buttonNew.drawOnPress() : buttonNew.drawOnRelease(); le.MousePressLeft( buttonLoad.area() ) ? buttonLoad.drawOnPress() : buttonLoad.drawOnRelease(); le.MousePressLeft( buttonSave.area() ) ? buttonSave.drawOnPress() : buttonSave.drawOnRelease(); le.MousePressLeft( buttonQuit.area() ) ? buttonQuit.drawOnPress() : buttonQuit.drawOnRelease(); le.MousePressLeft( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); if ( le.MouseClickLeft( buttonNew.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::MAIN_MENU_NEW_GAME ) ) { if ( Interface::AdventureMap::Get().EventNewGame() == fheroes2::GameMode::NEW_GAME ) { result = fheroes2::GameMode::NEW_GAME; break; } } else if ( le.MouseClickLeft( buttonLoad.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::MAIN_MENU_LOAD_GAME ) ) { if ( ListFiles::IsEmpty( Game::GetSaveDir(), Game::GetSaveFileExtension(), false ) ) { fheroes2::showMessage( fheroes2::Text( _( "Load Game" ), fheroes2::FontType::normalYellow() ), fheroes2::Text( _( "No save files to load." ), fheroes2::FontType::normalWhite() ), Dialog::OK ); } else if ( Interface::AdventureMap::Get().EventLoadGame() == fheroes2::GameMode::LOAD_GAME ) { result = fheroes2::GameMode::LOAD_GAME; break; } } else if ( le.MouseClickLeft( buttonSave.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::WORLD_SAVE_GAME ) ) { // Special case: since we show a window about file saving we don't want to display the current dialog anymore. back.restore(); return Interface::AdventureMap::Get().EventSaveGame(); } else if ( le.MouseClickLeft( buttonQuit.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::MAIN_MENU_QUIT ) ) { if ( Interface::AdventureMap::EventExit() == fheroes2::GameMode::QUIT_GAME ) { result = fheroes2::GameMode::QUIT_GAME; break; } } else if ( le.MouseClickLeft( buttonCancel.area() ) || Game::HotKeyCloseWindow() ) { result = fheroes2::GameMode::CANCEL; break; } else if ( le.MousePressRight( buttonNew.area() ) ) { fheroes2::showMessage( fheroes2::Text( _( "New Game" ), fheroes2::FontType::normalYellow() ), fheroes2::Text( _( "Start a single or multi-player game." ), fheroes2::FontType::normalWhite() ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonLoad.area() ) ) { fheroes2::showMessage( fheroes2::Text( _( "Load Game" ), fheroes2::FontType::normalYellow() ), fheroes2::Text( _( "Load a previously saved game." ), fheroes2::FontType::normalWhite() ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonSave.area() ) ) { fheroes2::showMessage( fheroes2::Text( _( "Save Game" ), fheroes2::FontType::normalYellow() ), fheroes2::Text( _( "Save the current game." ), fheroes2::FontType::normalWhite() ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonQuit.area() ) ) { fheroes2::showMessage( fheroes2::Text( _( "Quit" ), fheroes2::FontType::normalYellow() ), fheroes2::Text( _( "Quit out of Heroes of Might and Magic II." ), fheroes2::FontType::normalWhite() ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonCancel.area() ) ) { fheroes2::showMessage( fheroes2::Text( _( "Cancel" ), fheroes2::FontType::normalYellow() ), fheroes2::Text( _( "Exit this menu without doing anything." ), fheroes2::FontType::normalWhite() ), Dialog::ZERO ); } } return result; } } fheroes2::GameMode Dialog::FileOptions() { const fheroes2::GameMode result = selectFileOption(); return result; } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_frameborder.cpp000066400000000000000000000074401456075706000243240ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include "agg_image.h" #include "dialog.h" #include "gamedefs.h" #include "icn.h" #include "image.h" #include "math_base.h" #include "screen.h" #include "settings.h" Dialog::FrameBorder::FrameBorder( int v ) : restorer( fheroes2::Display::instance(), 0, 0, 0, 0 ) , border( v ) {} Dialog::FrameBorder::FrameBorder( const fheroes2::Size & sz, const fheroes2::Image & sf ) : restorer( fheroes2::Display::instance(), 0, 0, 0, 0 ) , border( BORDERWIDTH ) { const fheroes2::Display & display = fheroes2::Display::instance(); SetPosition( ( display.width() - sz.width - border * 2 ) / 2, ( display.height() - sz.height - border * 2 ) / 2, sz.width, sz.height ); RenderOther( sf, rect ); } void Dialog::FrameBorder::SetPosition( int32_t posx, int32_t posy, int32_t encw, int32_t ench ) { restorer.restore(); rect.x = posx; rect.y = posy; if ( encw > 0 && ench > 0 ) { rect.width = encw + 2 * border; rect.height = ench + 2 * border; restorer.update( rect.x, rect.y, rect.width, rect.height ); area.width = encw; area.height = ench; } else { restorer.update( posx, posy, restorer.width(), restorer.height() ); } area.x = posx + border; area.y = posy + border; top = fheroes2::Rect( rect.x, rect.y, rect.width, border ); } void Dialog::FrameBorder::RenderRegular( const fheroes2::Rect & dstrt ) { const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ( Settings::Get().isEvilInterfaceEnabled() ? ICN::SURDRBKE : ICN::SURDRBKG ), 0 ); const fheroes2::Image renderedImage = fheroes2::Stretch( sprite, SHADOWWIDTH, 0, sprite.width() - SHADOWWIDTH, sprite.height() - SHADOWWIDTH, dstrt.width, dstrt.height ); fheroes2::Blit( renderedImage, fheroes2::Display::instance(), dstrt.x, dstrt.y ); } void Dialog::FrameBorder::RenderOther( const fheroes2::Image & srcsf, const fheroes2::Rect & dstrt ) { const fheroes2::Image renderedImage = fheroes2::Stretch( srcsf, 0, 0, srcsf.width(), srcsf.height(), dstrt.width, dstrt.height ); fheroes2::Blit( renderedImage, fheroes2::Display::instance(), dstrt.x, dstrt.y ); } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_game_settings.cpp000066400000000000000000000341151456075706000246640ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include "agg_image.h" #include "dialog.h" #include "dialog_audio.h" #include "dialog_game_settings.h" #include "dialog_graphics_settings.h" #include "dialog_hotkeys.h" #include "dialog_language_selection.h" #include "game_hotkeys.h" #include "game_mainmenu_ui.h" #include "gamedefs.h" #include "icn.h" #include "image.h" #include "localevent.h" #include "math_base.h" #include "screen.h" #include "settings.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_language.h" #include "ui_option_item.h" namespace { enum class SelectedWindow : int { Configuration, Language, Graphics, AudioSettings, HotKeys, CursorType, TextSupportMode, UpdateSettings, Exit }; const fheroes2::Size offsetBetweenOptions{ 92, 110 }; const fheroes2::Point optionOffset{ 36, 47 }; const int32_t optionWindowSize{ 65 }; const fheroes2::Rect languageRoi{ optionOffset.x, optionOffset.y, optionWindowSize, optionWindowSize }; const fheroes2::Rect graphicsRoi{ optionOffset.x + offsetBetweenOptions.width, optionOffset.y, optionWindowSize, optionWindowSize }; const fheroes2::Rect audioRoi{ optionOffset.x + offsetBetweenOptions.width * 2, optionOffset.y, optionWindowSize, optionWindowSize }; const fheroes2::Rect hotKeyRoi{ optionOffset.x, optionOffset.y + offsetBetweenOptions.height, optionWindowSize, optionWindowSize }; const fheroes2::Rect cursorTypeRoi{ optionOffset.x + offsetBetweenOptions.width, optionOffset.y + offsetBetweenOptions.height, optionWindowSize, optionWindowSize }; const fheroes2::Rect textSupportModeRoi{ optionOffset.x + offsetBetweenOptions.width * 2, optionOffset.y + offsetBetweenOptions.height, optionWindowSize, optionWindowSize }; void drawLanguage( const fheroes2::Rect & optionRoi ) { const fheroes2::SupportedLanguage currentLanguage = fheroes2::getLanguageFromAbbreviation( Settings::Get().getGameLanguage() ); fheroes2::LanguageSwitcher languageSwitcher( currentLanguage ); fheroes2::drawOption( optionRoi, fheroes2::AGG::GetICN( ICN::SPANEL, 18 ), _( "Language" ), fheroes2::getLanguageName( currentLanguage ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); } void drawGraphics( const fheroes2::Rect & optionRoi ) { fheroes2::drawOption( optionRoi, fheroes2::AGG::GetICN( ICN::GAME_OPTION_ICON, 1 ), _( "Graphics" ), _( "Settings" ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); } void drawAudioOptions( const fheroes2::Rect & optionRoi ) { fheroes2::drawOption( optionRoi, fheroes2::AGG::GetICN( ICN::SPANEL, 1 ), _( "Audio" ), _( "Settings" ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); } void drawHotKeyOptions( const fheroes2::Rect & optionRoi ) { fheroes2::drawOption( optionRoi, fheroes2::AGG::GetICN( ICN::GAME_OPTION_ICON, 0 ), _( "Hot Keys" ), _( "Configure" ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); } void drawCursorTypeOptions( const fheroes2::Rect & optionRoi ) { if ( Settings::Get().isMonochromeCursorEnabled() ) { fheroes2::drawOption( optionRoi, fheroes2::AGG::GetICN( ICN::SPANEL, 20 ), _( "Mouse Cursor" ), _( "Black & White" ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); } else { fheroes2::drawOption( optionRoi, fheroes2::AGG::GetICN( ICN::SPANEL, 21 ), _( "Mouse Cursor" ), _( "Color" ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); } } void drawTextSupportModeOptions( const fheroes2::Rect & optionRoi ) { if ( Settings::Get().isTextSupportModeEnabled() ) { fheroes2::drawOption( optionRoi, fheroes2::AGG::GetICN( ICN::CSPANEL, 4 ), _( "Text Support" ), _( "On" ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); } else { fheroes2::drawOption( optionRoi, fheroes2::AGG::GetICN( ICN::SPANEL, 9 ), _( "Text Support" ), _( "Off" ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); } } SelectedWindow showConfigurationWindow() { fheroes2::Display & display = fheroes2::Display::instance(); const Settings & conf = Settings::Get(); const bool isEvilInterface = conf.isEvilInterfaceEnabled(); const int dialogIcnId = isEvilInterface ? ICN::CSPANBKE : ICN::CSPANBKG; const fheroes2::Sprite & dialog = fheroes2::AGG::GetICN( dialogIcnId, 0 ); const fheroes2::Sprite & dialogShadow = fheroes2::AGG::GetICN( dialogIcnId, 1 ); const fheroes2::Point dialogOffset( ( display.width() - dialog.width() ) / 2, ( display.height() - dialog.height() ) / 2 ); const fheroes2::Point shadowOffset( dialogOffset.x - BORDERWIDTH, dialogOffset.y ); const fheroes2::Rect windowRoi{ dialogOffset.x, dialogOffset.y, dialog.width(), dialog.height() }; const fheroes2::ImageRestorer restorer( display, shadowOffset.x, shadowOffset.y, dialog.width() + BORDERWIDTH, dialog.height() + BORDERWIDTH ); fheroes2::Blit( dialogShadow, display, windowRoi.x - BORDERWIDTH, windowRoi.y + BORDERWIDTH ); fheroes2::Blit( dialog, display, windowRoi.x, windowRoi.y ); fheroes2::ImageRestorer emptyDialogRestorer( display, windowRoi.x, windowRoi.y, windowRoi.width, windowRoi.height ); const int buttonIcnId = isEvilInterface ? ICN::BUTTON_SMALL_OKAY_EVIL : ICN::BUTTON_SMALL_OKAY_GOOD; const fheroes2::Rect windowLanguageRoi( languageRoi + windowRoi.getPosition() ); const fheroes2::Rect windowGraphicsRoi( graphicsRoi + windowRoi.getPosition() ); const fheroes2::Rect windowAudioRoi( audioRoi + windowRoi.getPosition() ); const fheroes2::Rect windowHotKeyRoi( hotKeyRoi + windowRoi.getPosition() ); const fheroes2::Rect windowCursorTypeRoi( cursorTypeRoi + windowRoi.getPosition() ); const fheroes2::Rect windowTextSupportModeRoi( textSupportModeRoi + windowRoi.getPosition() ); const auto drawOptions = [&windowLanguageRoi, &windowGraphicsRoi, &windowAudioRoi, &windowHotKeyRoi, &windowCursorTypeRoi, &windowTextSupportModeRoi]() { drawLanguage( windowLanguageRoi ); drawGraphics( windowGraphicsRoi ); drawAudioOptions( windowAudioRoi ); drawHotKeyOptions( windowHotKeyRoi ); drawCursorTypeOptions( windowCursorTypeRoi ); drawTextSupportModeOptions( windowTextSupportModeRoi ); }; drawOptions(); fheroes2::ButtonSprite okayButton( windowRoi.x + 112, windowRoi.y + 252, fheroes2::AGG::GetICN( buttonIcnId, 0 ), fheroes2::AGG::GetICN( buttonIcnId, 1 ) ); okayButton.draw(); display.render(); bool isTextSupportModeEnabled = conf.isTextSupportModeEnabled(); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { if ( le.MousePressLeft( okayButton.area() ) ) { okayButton.drawOnPress(); } else { okayButton.drawOnRelease(); } if ( le.MouseClickLeft( okayButton.area() ) || Game::HotKeyCloseWindow() ) { break; } if ( le.MouseClickLeft( windowLanguageRoi ) ) { return SelectedWindow::Language; } if ( le.MouseClickLeft( windowGraphicsRoi ) ) { return SelectedWindow::Graphics; } if ( le.MouseClickLeft( windowAudioRoi ) ) { return SelectedWindow::AudioSettings; } if ( le.MouseClickLeft( windowHotKeyRoi ) ) { return SelectedWindow::HotKeys; } if ( le.MouseClickLeft( windowCursorTypeRoi ) ) { return SelectedWindow::CursorType; } if ( le.MouseClickLeft( windowTextSupportModeRoi ) ) { return SelectedWindow::TextSupportMode; } if ( le.MousePressRight( windowLanguageRoi ) ) { fheroes2::showStandardTextMessage( _( "Select Game Language" ), _( "Change the language of the game." ), 0 ); } else if ( le.MousePressRight( windowGraphicsRoi ) ) { fheroes2::showStandardTextMessage( _( "Graphics" ), _( "Change the graphics settings of the game." ), 0 ); } else if ( le.MousePressRight( windowAudioRoi ) ) { fheroes2::showStandardTextMessage( _( "Audio" ), _( "Change the audio settings of the game." ), 0 ); } else if ( le.MousePressRight( windowHotKeyRoi ) ) { fheroes2::showStandardTextMessage( _( "Hot Keys" ), _( "Check and configure all the hot keys present in the game." ), 0 ); } else if ( le.MousePressRight( windowCursorTypeRoi ) ) { fheroes2::showStandardTextMessage( _( "Mouse Cursor" ), _( "Toggle colored cursor on or off. This is only an aesthetic choice." ), 0 ); } else if ( le.MousePressRight( windowTextSupportModeRoi ) ) { fheroes2::showStandardTextMessage( _( "Text Support" ), _( "Toggle text support mode to output extra information about windows and events in the game." ), 0 ); } else if ( le.MousePressRight( okayButton.area() ) ) { fheroes2::showStandardTextMessage( _( "Okay" ), _( "Exit this menu." ), 0 ); } // Text support mode can be toggled using a global hotkey, we need to properly reflect this change in the UI if ( isTextSupportModeEnabled != conf.isTextSupportModeEnabled() ) { isTextSupportModeEnabled = conf.isTextSupportModeEnabled(); emptyDialogRestorer.restore(); drawOptions(); display.render( emptyDialogRestorer.rect() ); } } return SelectedWindow::Exit; } } namespace fheroes2 { void openGameSettings() { drawMainMenuScreen(); Settings & conf = Settings::Get(); bool saveConfiguration = false; SelectedWindow windowType = SelectedWindow::Configuration; while ( windowType != SelectedWindow::Exit ) { switch ( windowType ) { case SelectedWindow::Configuration: windowType = showConfigurationWindow(); break; case SelectedWindow::Language: { const std::vector supportedLanguages = getSupportedLanguages(); if ( supportedLanguages.size() > 1 ) { selectLanguage( supportedLanguages, getLanguageFromAbbreviation( conf.getGameLanguage() ) ); } else { assert( supportedLanguages.front() == SupportedLanguage::English ); conf.setGameLanguage( getLanguageAbbreviation( SupportedLanguage::English ) ); fheroes2::showStandardTextMessage( "Attention", "Your version of Heroes of Might and Magic II does not support any other languages than English.", Dialog::OK ); } windowType = SelectedWindow::UpdateSettings; break; } case SelectedWindow::Graphics: fheroes2::openGraphicsSettingsDialog( []() { drawMainMenuScreen(); } ); windowType = SelectedWindow::Configuration; break; case SelectedWindow::AudioSettings: Dialog::openAudioSettingsDialog( false ); windowType = SelectedWindow::Configuration; break; case SelectedWindow::HotKeys: fheroes2::openHotkeysDialog(); windowType = SelectedWindow::Configuration; break; case SelectedWindow::CursorType: conf.setMonochromeCursor( !conf.isMonochromeCursorEnabled() ); windowType = SelectedWindow::UpdateSettings; break; case SelectedWindow::TextSupportMode: conf.setTextSupportMode( !conf.isTextSupportModeEnabled() ); windowType = SelectedWindow::UpdateSettings; break; case SelectedWindow::UpdateSettings: saveConfiguration = true; windowType = SelectedWindow::Configuration; break; default: return; } } if ( saveConfiguration ) { conf.Save( Settings::configFileName ); } } } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_game_settings.h000066400000000000000000000027731456075706000243360ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once namespace fheroes2 { void openGameSettings(); } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_gameinfo.cpp000066400000000000000000000215761456075706000236270ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include "agg_image.h" #include "cursor.h" #include "dialog.h" #include "difficulty.h" #include "game.h" #include "game_hotkeys.h" #include "game_over.h" #include "icn.h" #include "image.h" #include "localevent.h" #include "maps.h" #include "maps_fileinfo.h" #include "math_base.h" #include "player_info.h" #include "screen.h" #include "settings.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_text.h" namespace { enum GameInfoCoordinates { SCENARIO_INFO_VALUES_BOX_WIDTH = 80, DIALOG_CONTENT_WIDTH = 420, SCENARIO_INFO_BOX_INNER_MARGIN = 11, SCENARIO_INFO_ROW_OUTER_MARGIN = 16, DESCRIPTION_INNER_MARGIN = 6, SCENARIO_DESCRIPTION_OUTER_MARGIN = 11, // This is the shadow offset from the original ICN::SCENIBKG image. DIALOG_SHADOW_OFFSET_X = 16, DIALOG_SHADOW_OFFSET_Y = 4 + 12, // This ICN has been modified with a 12 px larger shadow. DIALOG_BORDER_WIDTH = 18, // The following values are calculated using the previous values. SCENARIO_INFO_ROW_OFFSET = DIALOG_BORDER_WIDTH + SCENARIO_INFO_ROW_OUTER_MARGIN, SCENARIO_MAP_DIFFICULTY_OFFSET = SCENARIO_INFO_ROW_OFFSET, SCENARIO_GAME_DIFFICULTY_OFFSET = SCENARIO_INFO_ROW_OFFSET + SCENARIO_INFO_VALUES_BOX_WIDTH + SCENARIO_INFO_BOX_INNER_MARGIN, SCENARIO_RATING_OFFSET = SCENARIO_INFO_ROW_OFFSET + SCENARIO_INFO_VALUES_BOX_WIDTH * 2 + SCENARIO_INFO_BOX_INNER_MARGIN * 2, SCENARIO_MAP_SIZE_OFFSET = SCENARIO_INFO_ROW_OFFSET + SCENARIO_INFO_VALUES_BOX_WIDTH * 3 + SCENARIO_INFO_BOX_INNER_MARGIN * 3, SCENARIO_DESCRIPTION_OFFSET = DIALOG_BORDER_WIDTH + SCENARIO_DESCRIPTION_OUTER_MARGIN + DESCRIPTION_INNER_MARGIN, SCENARIO_DESCRIPTION_WIDTH = 350, PLAYER_INFO_ROW_OFFSET = DIALOG_BORDER_WIDTH + 6, CONDITION_LABEL_OFFSET = DIALOG_BORDER_WIDTH, CONDITION_LABEL_WIDTH = 93, CONDITION_DESCRIPTION_OFFSET = CONDITION_LABEL_OFFSET + CONDITION_LABEL_WIDTH + DESCRIPTION_INNER_MARGIN, CONDITION_DESCRIPTION_WIDTH = 266, OK_BUTTON_OFFSET = DIALOG_CONTENT_WIDTH / 2, }; } void Dialog::GameInfo() { fheroes2::Display & display = fheroes2::Display::instance(); Settings & conf = Settings::Get(); const Maps::FileInfo & mapInfo = conf.getCurrentMapInfo(); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); const fheroes2::Sprite & window = fheroes2::AGG::GetICN( ICN::SCENIBKG, 0 ); const fheroes2::Point dialogOffset( ( display.width() - window.width() - DIALOG_SHADOW_OFFSET_X ) / 2, ( display.height() - window.height() ) / 2 ); const fheroes2::Point shadowOffset( dialogOffset.x + DIALOG_SHADOW_OFFSET_X, dialogOffset.y + DIALOG_SHADOW_OFFSET_Y / 2 ); fheroes2::ImageRestorer restorer( display, dialogOffset.x, shadowOffset.y, window.width(), window.height() ); fheroes2::Blit( window, display, dialogOffset.x, shadowOffset.y ); fheroes2::Text text( mapInfo.name, fheroes2::FontType::normalWhite() ); text.draw( shadowOffset.x, shadowOffset.y + 32, DIALOG_CONTENT_WIDTH, display ); text.set( _( "Map\nDifficulty" ), fheroes2::FontType::smallWhite() ); text.draw( shadowOffset.x + SCENARIO_MAP_DIFFICULTY_OFFSET, shadowOffset.y + 56, SCENARIO_INFO_VALUES_BOX_WIDTH, display ); text.set( _( "Game\nDifficulty" ), fheroes2::FontType::smallWhite() ); text.draw( shadowOffset.x + SCENARIO_GAME_DIFFICULTY_OFFSET, shadowOffset.y + 56, SCENARIO_INFO_VALUES_BOX_WIDTH, display ); text.set( _( "Rating" ), fheroes2::FontType::smallWhite() ); text.draw( shadowOffset.x + SCENARIO_RATING_OFFSET, shadowOffset.y + 78 - text.height( SCENARIO_INFO_VALUES_BOX_WIDTH ), SCENARIO_INFO_VALUES_BOX_WIDTH, display ); text.set( _( "Map Size" ), fheroes2::FontType::smallWhite() ); text.draw( shadowOffset.x + SCENARIO_MAP_SIZE_OFFSET, shadowOffset.y + 78 - text.height( SCENARIO_INFO_VALUES_BOX_WIDTH ), SCENARIO_INFO_VALUES_BOX_WIDTH, display ); text.set( Difficulty::String( mapInfo.difficulty ), fheroes2::FontType::smallWhite() ); text.draw( shadowOffset.x + SCENARIO_MAP_DIFFICULTY_OFFSET, shadowOffset.y + 84, SCENARIO_INFO_VALUES_BOX_WIDTH, display ); text.set( Difficulty::String( Game::getDifficulty() ), fheroes2::FontType::smallWhite() ); text.draw( shadowOffset.x + SCENARIO_GAME_DIFFICULTY_OFFSET, shadowOffset.y + 84, SCENARIO_INFO_VALUES_BOX_WIDTH, display ); text.set( std::to_string( Game::GetRating() ) + " %", fheroes2::FontType::smallWhite() ); text.draw( shadowOffset.x + SCENARIO_RATING_OFFSET, shadowOffset.y + 84, SCENARIO_INFO_VALUES_BOX_WIDTH, display ); text.set( Maps::SizeString( mapInfo.width ), fheroes2::FontType::smallWhite() ); text.draw( shadowOffset.x + SCENARIO_MAP_SIZE_OFFSET, shadowOffset.y + 84, SCENARIO_INFO_VALUES_BOX_WIDTH, display ); text.set( mapInfo.description, fheroes2::FontType::smallWhite() ); text.draw( shadowOffset.x + SCENARIO_DESCRIPTION_OFFSET, shadowOffset.y + 107, SCENARIO_DESCRIPTION_WIDTH, display ); text.set( _( "Opponents" ), fheroes2::FontType::smallWhite() ); text.draw( shadowOffset.x, shadowOffset.y + 152, DIALOG_CONTENT_WIDTH, display ); text.set( _( "Class" ), fheroes2::FontType::smallWhite() ); text.draw( shadowOffset.x, shadowOffset.y + 229, DIALOG_CONTENT_WIDTH, display ); Interface::PlayersInfo playersInfo; playersInfo.UpdateInfo( conf.GetPlayers(), fheroes2::Point( shadowOffset.x + PLAYER_INFO_ROW_OFFSET, shadowOffset.y + 165 ), fheroes2::Point( shadowOffset.x + PLAYER_INFO_ROW_OFFSET, shadowOffset.y + 240 ) ); playersInfo.RedrawInfo( true ); text.set( _( "Victory\nConditions" ), fheroes2::FontType::smallWhite() ); text.draw( shadowOffset.x + CONDITION_LABEL_OFFSET, shadowOffset.y + 347, CONDITION_LABEL_WIDTH, display ); text.set( GameOver::GetActualDescription( mapInfo.ConditionWins() ), fheroes2::FontType::smallWhite() ); text.draw( shadowOffset.x + CONDITION_DESCRIPTION_OFFSET, shadowOffset.y + 350, CONDITION_DESCRIPTION_WIDTH, display ); text.set( _( "Loss\nConditions" ), fheroes2::FontType::smallWhite() ); text.draw( shadowOffset.x + CONDITION_LABEL_OFFSET, shadowOffset.y + 392, CONDITION_LABEL_WIDTH, display ); text.set( GameOver::GetActualDescription( mapInfo.ConditionLoss() ), fheroes2::FontType::smallWhite() ); text.draw( shadowOffset.x + CONDITION_DESCRIPTION_OFFSET, shadowOffset.y + 398, CONDITION_DESCRIPTION_WIDTH, display ); const int buttonOkIcnId = ICN::BUTTON_SMALL_OKAY_GOOD; fheroes2::Button buttonOk( shadowOffset.x + OK_BUTTON_OFFSET - fheroes2::AGG::GetICN( buttonOkIcnId, 0 ).width() / 2, shadowOffset.y + 426, buttonOkIcnId, 0, 1 ); buttonOk.draw(); display.render(); LocalEvent & le = LocalEvent::Get(); // message loop while ( le.HandleEvents() ) { le.MousePressLeft( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); if ( le.MouseClickLeft( buttonOk.area() ) || Game::HotKeyCloseWindow() ) break; if ( le.MousePressRight( buttonOk.area() ) ) { fheroes2::showStandardTextMessage( _( "Okay" ), _( "Exit this menu." ), 0 ); } else { playersInfo.readOnlyEventProcessing(); } } } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_giftresources.cpp000066400000000000000000000317661456075706000247300ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2011 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include "agg_image.h" #include "color.h" #include "cursor.h" #include "dialog.h" #include "icn.h" #include "image.h" #include "kingdom.h" #include "localevent.h" #include "math_base.h" #include "players.h" #include "resource.h" #include "screen.h" #include "settings.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_text.h" #include "ui_window.h" #include "world.h" namespace { const fheroes2::Size giftDialogSize( 320, 234 ); } int32_t GetIndexClickRects( const std::vector & rects ) { LocalEvent & le = LocalEvent::Get(); const fheroes2::Point & pos = le.GetMouseCursor(); const fheroes2::Point position( pos.x, pos.y ); for ( size_t i = 0; i < rects.size(); ++i ) { if ( rects[i] & position ) { if ( le.MouseClickLeft() ) return static_cast( i ); else return -1; } } return -1; } struct SelectRecipientsColors { static constexpr int recipientSpacing = 22; const Colors colors; int recipients; std::vector positions; SelectRecipientsColors( const fheroes2::Point & pos, int senderColor ) : colors( Settings::Get().GetPlayers().GetActualColors() & ~senderColor ) , recipients( 0 ) { positions.reserve( colors.size() ); const fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::Rect box( ( display.width() - giftDialogSize.width ) / 2, ( display.height() - giftDialogSize.height ) / 2, giftDialogSize.width, giftDialogSize.height ); const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::CELLWIN, 43 ); const int32_t colorCount = static_cast( colors.size() ); // safe to cast as the number of players <= 8. const int32_t playerContainerWidth = colorCount * sprite.width() + ( colorCount - 1 ) * recipientSpacing; const int32_t startX = box.x + ( giftDialogSize.width - playerContainerWidth ) / 2; for ( int32_t i = 0; i < colorCount; ++i ) { const int32_t posX = startX + i * ( recipientSpacing + sprite.width() ); positions.emplace_back( posX, pos.y, sprite.width(), sprite.height() ); } } int32_t GetIndexClick() const { return GetIndexClickRects( positions ); } void Redraw() const { fheroes2::Display & display = fheroes2::Display::instance(); for ( Colors::const_iterator it = colors.begin(); it != colors.end(); ++it ) { const fheroes2::Rect & pos = positions[std::distance( colors.begin(), it )]; fheroes2::Blit( fheroes2::AGG::GetICN( ICN::CELLWIN, 43 + Color::GetIndex( *it ) ), display, pos.x, pos.y ); if ( recipients & *it ) fheroes2::Blit( fheroes2::AGG::GetICN( ICN::CELLWIN, 2 ), display, pos.x + 2, pos.y + 2 ); } } bool QueueEventProcessing() { const int32_t index = GetIndexClick(); if ( index >= 0 ) { const int cols = colors[index]; if ( recipients & cols ) recipients &= ~cols; else recipients |= cols; return true; } return false; } }; struct ResourceBar { Funds & resource; std::vector positions; ResourceBar( Funds & funds, int32_t posx, int32_t posy ) : resource( funds ) { positions.reserve( 7 ); const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::TRADPOST, 7 ); positions.emplace_back( posx, posy, sprite.width(), sprite.height() ); positions.emplace_back( posx + 40, posy, sprite.width(), sprite.height() ); positions.emplace_back( posx + 80, posy, sprite.width(), sprite.height() ); positions.emplace_back( posx + 120, posy, sprite.width(), sprite.height() ); positions.emplace_back( posx + 160, posy, sprite.width(), sprite.height() ); positions.emplace_back( posx + 200, posy, sprite.width(), sprite.height() ); positions.emplace_back( posx + 240, posy, sprite.width(), sprite.height() ); } static void RedrawResource( int type, int32_t count, int32_t posx, int32_t posy ) { fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::Text text( std::to_string( count ), fheroes2::FontType::smallWhite() ); const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::TRADPOST, 7 + Resource::getIconIcnIndex( type ) ); fheroes2::Blit( sprite, display, posx, posy ); text.draw( posx + ( sprite.width() - text.width() ) / 2, posy + sprite.height() - 10, display ); } void Redraw( const Funds * res = nullptr ) const { if ( !res ) res = &resource; for ( size_t i = 0; i < positions.size(); ++i ) { const int rs = Resource::getResourceTypeFromIconIndex( static_cast( i ) ); RedrawResource( rs, res->Get( rs ), positions[i].x, positions[i].y ); } } int32_t GetIndexClick() const { return GetIndexClickRects( positions ); } bool QueueEventProcessing( Funds & funds, uint32_t mul ) { const int32_t index = GetIndexClick(); if ( index >= 0 ) { int rs = Resource::getResourceTypeFromIconIndex( index ); uint32_t step = rs == Resource::GOLD ? 100 : 1; uint32_t cur = resource.Get( rs ); uint32_t sel = cur; uint32_t max = mul > 1 ? ( funds.Get( rs ) + resource.Get( rs ) ) / mul : funds.Get( rs ) + resource.Get( rs ); if ( 0 == mul ) { fheroes2::showMessage( fheroes2::Text( "", {} ), fheroes2::Text( _( "First select recipients!" ), fheroes2::FontType::normalWhite() ), Dialog::OK ); } else if ( 0 == max ) { std::string msg = _( "You cannot select %{resource}!" ); StringReplace( msg, "%{resource}", Resource::String( rs ) ); fheroes2::showMessage( fheroes2::Text( "", {} ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK ); } else { std::string msg = _( "Select count %{resource}:" ); StringReplace( msg, "%{resource}", Resource::String( rs ) ); if ( Dialog::SelectCount( msg, 0, max, sel, step ) && cur != sel ) { int32_t * from = funds.GetPtr( rs ); int32_t * to = resource.GetPtr( rs ); if ( from && to ) { int32_t count = sel - cur; *from -= mul > 1 ? count * mul : count; *to += count; return true; } } } } return false; } }; void Dialog::MakeGiftResource( Kingdom & kingdom ) { fheroes2::Display & display = fheroes2::Display::instance(); LocalEvent & le = LocalEvent::Get(); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); const fheroes2::StandardWindow frameborder( 320, 234, true ); const fheroes2::Rect box( frameborder.activeArea() ); Funds funds1( kingdom.GetFunds() ); Funds funds2; const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::TRADPOST, 7 ); const int32_t posX = box.x + ( giftDialogSize.width - ( sprite.width() + 240 ) ) / 2; const fheroes2::FontType normalWhite = fheroes2::FontType::normalWhite(); fheroes2::Text text( _( "Select Recipients" ), normalWhite ); text.draw( box.x + ( box.width - text.width() ) / 2, box.y + 7, display ); SelectRecipientsColors selector( fheroes2::Point( box.x + 65, box.y + 28 ), kingdom.GetColor() ); selector.Redraw(); text.set( _( "Your Funds" ), normalWhite ); text.draw( box.x + ( box.width - text.width() ) / 2, box.y + 57, display ); ResourceBar info1( funds1, posX, box.y + 80 ); info1.Redraw(); text.set( _( "Planned Gift" ), normalWhite ); text.draw( box.x + ( box.width - text.width() ) / 2, box.y + 127, display ); ResourceBar info2( funds2, posX, box.y + 150 ); info2.Redraw(); const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); const int okIcnId = isEvilInterface ? ICN::BUTTON_SMALL_OKAY_EVIL : ICN::BUTTON_SMALL_OKAY_GOOD; const int cancelIcnId = isEvilInterface ? ICN::BUTTON_SMALL_CANCEL_EVIL : ICN::BUTTON_SMALL_CANCEL_GOOD; const fheroes2::Sprite & buttonOkSprite = fheroes2::AGG::GetICN( okIcnId, 0 ); const fheroes2::Sprite & buttonCancelSprite = fheroes2::AGG::GetICN( cancelIcnId, 0 ); const int32_t border = 10; fheroes2::ButtonGroup btnGroup; btnGroup.addButton( fheroes2::makeButtonWithShadow( box.x + border, box.y + box.height - border - buttonOkSprite.height(), buttonOkSprite, fheroes2::AGG::GetICN( okIcnId, 1 ), display ), Dialog::OK ); btnGroup.addButton( fheroes2::makeButtonWithShadow( box.x + box.width - border - buttonCancelSprite.width(), box.y + box.height - border - buttonCancelSprite.height(), buttonCancelSprite, fheroes2::AGG::GetICN( cancelIcnId, 1 ), display ), Dialog::CANCEL ); btnGroup.button( 0 ).disable(); btnGroup.draw(); display.render(); uint32_t count = Color::Count( selector.recipients ); // message loop int result = Dialog::ZERO; while ( result == Dialog::ZERO && le.HandleEvents() ) { result = btnGroup.processEvents(); if ( selector.QueueEventProcessing() ) { uint32_t new_count = Color::Count( selector.recipients ); if ( 0 == new_count || 0 == funds2.GetValidItemsCount() ) btnGroup.button( 0 ).disable(); else btnGroup.button( 0 ).enable(); if ( count != new_count ) { funds1 = kingdom.GetFunds(); funds2.Reset(); info1.Redraw(); info2.Redraw(); count = new_count; } btnGroup.draw(); selector.Redraw(); display.render(); } else if ( info2.QueueEventProcessing( funds1, count ) ) { if ( 0 == Color::Count( selector.recipients ) || 0 == funds2.GetValidItemsCount() ) btnGroup.button( 0 ).disable(); else btnGroup.button( 0 ).enable(); info1.Redraw(); info2.Redraw(); btnGroup.draw(); display.render(); } } if ( Dialog::OK == result ) { EventDate event; event.resource = funds2; event.isApplicableForAIPlayers = true; event.firstOccurrenceDay = world.CountDay() + 1; event.repeatPeriodInDays = 0; event.colors = selector.recipients; event.message = _( "Gift from %{name}" ); const Player * player = Players::Get( kingdom.GetColor() ); if ( player ) StringReplace( event.message, "%{name}", player->GetName() ); world.AddEventDate( event ); if ( 1 < count ) funds2 *= count; kingdom.OddFundsResource( funds2 ); } } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_graphics_settings.cpp000066400000000000000000000274401456075706000255560ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "dialog_graphics_settings.h" #include #include #include #include "agg_image.h" #include "dialog_resolution.h" #include "game_hotkeys.h" #include "gamedefs.h" #include "icn.h" #include "image.h" #include "localevent.h" #include "math_base.h" #include "screen.h" #include "settings.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_option_item.h" #include "ui_text.h" namespace { enum class SelectedWindow : int { Configuration, Resolution, Mode, VSync, SystemInfo, Exit }; const fheroes2::Size offsetBetweenOptions{ 118, 110 }; const fheroes2::Point optionOffset{ 69, 47 }; const int32_t optionWindowSize{ 65 }; const fheroes2::Rect resolutionRoi{ optionOffset.x, optionOffset.y, optionWindowSize, optionWindowSize }; const fheroes2::Rect modeRoi{ optionOffset.x + offsetBetweenOptions.width, optionOffset.y, optionWindowSize, optionWindowSize }; const fheroes2::Rect vSyncRoi{ optionOffset.x, optionOffset.y + offsetBetweenOptions.height, optionWindowSize, optionWindowSize }; const fheroes2::Rect systemInfoRoi{ optionOffset.x + offsetBetweenOptions.width, optionOffset.y + offsetBetweenOptions.height, optionWindowSize, optionWindowSize }; void drawResolution( const fheroes2::Rect & optionRoi ) { const fheroes2::Display & display = fheroes2::Display::instance(); std::string resolutionName; if ( display.screenSize().width != display.width() || display.screenSize().height != display.height() ) { const int32_t integer = display.screenSize().width / display.width(); const int32_t fraction = display.screenSize().width * 10 / display.width() - 10 * integer; resolutionName = std::to_string( display.width() ) + 'x' + std::to_string( display.height() ) + " (x" + std::to_string( integer ) + '.' + std::to_string( fraction ) + ')'; } else { resolutionName = std::to_string( display.width() ) + 'x' + std::to_string( display.height() ); } fheroes2::drawOption( optionRoi, fheroes2::AGG::GetICN( ICN::SPANEL, Settings::Get().isEvilInterfaceEnabled() ? 17 : 16 ), _( "Resolution" ), std::move( resolutionName ), fheroes2::UiOptionTextWidth::TWO_ELEMENTS_ROW ); } void drawMode( const fheroes2::Rect & optionRoi ) { const fheroes2::Sprite & originalIcon = fheroes2::AGG::GetICN( ICN::SPANEL, Settings::Get().isEvilInterfaceEnabled() ? 17 : 16 ); if ( fheroes2::engine().isFullScreen() ) { fheroes2::Sprite icon = originalIcon; fheroes2::Resize( originalIcon, 6, 6, 53, 53, icon, 2, 2, 61, 61 ); fheroes2::drawOption( optionRoi, icon, _( "window|Mode" ), _( "Fullscreen" ), fheroes2::UiOptionTextWidth::TWO_ELEMENTS_ROW ); } else { fheroes2::drawOption( optionRoi, originalIcon, _( "window|Mode" ), _( "Windowed" ), fheroes2::UiOptionTextWidth::TWO_ELEMENTS_ROW ); } } void drawVSync( const fheroes2::Rect & optionRoi ) { const bool isVSyncEnabled = Settings::Get().isVSyncEnabled(); fheroes2::drawOption( optionRoi, fheroes2::AGG::GetICN( ICN::SPANEL, isVSyncEnabled ? 18 : 19 ), _( "V-Sync" ), isVSyncEnabled ? _( "on" ) : _( "off" ), fheroes2::UiOptionTextWidth::TWO_ELEMENTS_ROW ); } void drawSystemInfo( const fheroes2::Rect & optionRoi ) { const bool isSystemInfoDisplayed = Settings::Get().isSystemInfoEnabled(); fheroes2::Sprite image = fheroes2::Crop( fheroes2::AGG::GetICN( ICN::ESPANBKG, 0 ), 69, 47, 65, 65 ); fheroes2::Text info; if ( isSystemInfoDisplayed ) { info.set( _( "FPS" ), fheroes2::FontType( fheroes2::FontSize::NORMAL, fheroes2::FontColor::YELLOW ) ); } else { info.set( _( "N/A" ), fheroes2::FontType( fheroes2::FontSize::NORMAL, fheroes2::FontColor::GRAY ) ); } info.draw( ( image.width() - info.width() ) / 2, ( image.height() - info.height() ) / 2, image ); fheroes2::drawOption( optionRoi, image, _( "System Info" ), isSystemInfoDisplayed ? _( "on" ) : _( "off" ), fheroes2::UiOptionTextWidth::TWO_ELEMENTS_ROW ); } SelectedWindow showConfigurationWindow() { fheroes2::Display & display = fheroes2::Display::instance(); const Settings & conf = Settings::Get(); const bool isEvilInterface = conf.isEvilInterfaceEnabled(); const fheroes2::Sprite & dialog = fheroes2::AGG::GetICN( ( isEvilInterface ? ICN::ESPANBKG_EVIL : ICN::ESPANBKG ), 0 ); const fheroes2::Sprite & dialogShadow = fheroes2::AGG::GetICN( ( isEvilInterface ? ICN::CSPANBKE : ICN::CSPANBKG ), 1 ); const fheroes2::Point dialogOffset( ( display.width() - dialog.width() ) / 2, ( display.height() - dialog.height() ) / 2 ); const fheroes2::Point shadowOffset( dialogOffset.x - BORDERWIDTH, dialogOffset.y ); const fheroes2::Rect windowRoi{ dialogOffset.x, dialogOffset.y, dialog.width(), dialog.height() }; const fheroes2::ImageRestorer restorer( display, shadowOffset.x, shadowOffset.y, dialog.width() + BORDERWIDTH, dialog.height() + BORDERWIDTH ); fheroes2::Blit( dialogShadow, display, windowRoi.x - BORDERWIDTH, windowRoi.y + BORDERWIDTH ); fheroes2::Blit( dialog, display, windowRoi.x, windowRoi.y ); fheroes2::ImageRestorer emptyDialogRestorer( display, windowRoi.x, windowRoi.y, windowRoi.width, windowRoi.height ); const fheroes2::Rect windowResolutionRoi( resolutionRoi + windowRoi.getPosition() ); const fheroes2::Rect windowModeRoi( modeRoi + windowRoi.getPosition() ); const fheroes2::Rect windowVSyncRoi( vSyncRoi + windowRoi.getPosition() ); const fheroes2::Rect windowSystemInfoRoi( systemInfoRoi + windowRoi.getPosition() ); const auto drawOptions = [&windowResolutionRoi, &windowModeRoi, &windowVSyncRoi, &windowSystemInfoRoi]() { drawResolution( windowResolutionRoi ); drawMode( windowModeRoi ); drawVSync( windowVSyncRoi ); drawSystemInfo( windowSystemInfoRoi ); }; drawOptions(); const fheroes2::Point buttonOffset( 112 + windowRoi.x, 252 + windowRoi.y ); fheroes2::Button okayButton( buttonOffset.x, buttonOffset.y, isEvilInterface ? ICN::BUTTON_SMALL_OKAY_EVIL : ICN::BUTTON_SMALL_OKAY_GOOD, 0, 1 ); okayButton.draw(); display.render(); bool isFullScreen = fheroes2::engine().isFullScreen(); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { if ( le.MousePressLeft( okayButton.area() ) ) { okayButton.drawOnPress(); } else { okayButton.drawOnRelease(); } if ( le.MouseClickLeft( okayButton.area() ) || Game::HotKeyCloseWindow() ) { break; } if ( le.MouseClickLeft( windowResolutionRoi ) ) { return SelectedWindow::Resolution; } if ( le.MouseClickLeft( windowModeRoi ) ) { return SelectedWindow::Mode; } if ( le.MouseClickLeft( windowVSyncRoi ) ) { return SelectedWindow::VSync; } if ( le.MouseClickLeft( windowSystemInfoRoi ) ) { return SelectedWindow::SystemInfo; } if ( le.MousePressRight( windowResolutionRoi ) ) { fheroes2::showStandardTextMessage( _( "Select Game Resolution" ), _( "Change the resolution of the game." ), 0 ); } else if ( le.MousePressRight( windowModeRoi ) ) { fheroes2::showStandardTextMessage( _( "window|Mode" ), _( "Toggle between fullscreen and windowed modes." ), 0 ); } else if ( le.MousePressRight( windowVSyncRoi ) ) { fheroes2::showStandardTextMessage( _( "V-Sync" ), _( "The V-Sync option can be enabled to resolve flickering issues on some monitors." ), 0 ); } if ( le.MousePressRight( windowSystemInfoRoi ) ) { fheroes2::showStandardTextMessage( _( "System Info" ), _( "Show extra information such as FPS and current time." ), 0 ); } else if ( le.MousePressRight( okayButton.area() ) ) { fheroes2::showStandardTextMessage( _( "Okay" ), _( "Exit this menu." ), 0 ); } // Fullscreen mode can be toggled using a global hotkey, we need to properly reflect this change in the UI if ( isFullScreen != fheroes2::engine().isFullScreen() ) { isFullScreen = fheroes2::engine().isFullScreen(); emptyDialogRestorer.restore(); drawOptions(); display.render( emptyDialogRestorer.rect() ); } } return SelectedWindow::Exit; } } namespace fheroes2 { void openGraphicsSettingsDialog( const std::function & updateUI ) { Settings & conf = Settings::Get(); SelectedWindow windowType = SelectedWindow::Configuration; while ( windowType != SelectedWindow::Exit ) { switch ( windowType ) { case SelectedWindow::Configuration: windowType = showConfigurationWindow(); break; case SelectedWindow::Resolution: if ( Dialog::SelectResolution() ) { conf.Save( Settings::configFileName ); } updateUI(); windowType = SelectedWindow::Configuration; break; case SelectedWindow::Mode: conf.setFullScreen( !conf.FullScreen() ); conf.Save( Settings::configFileName ); windowType = SelectedWindow::Configuration; break; case SelectedWindow::VSync: conf.setVSync( !conf.isVSyncEnabled() ); conf.Save( Settings::configFileName ); windowType = SelectedWindow::Configuration; break; case SelectedWindow::SystemInfo: conf.setSystemInfo( !conf.isSystemInfoEnabled() ); conf.Save( Settings::configFileName ); windowType = SelectedWindow::Configuration; break; default: return; } } } } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_graphics_settings.h000066400000000000000000000031031456075706000252110ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include namespace fheroes2 { void openGraphicsSettingsDialog( const std::function & updateUI ); } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_hotkeys.cpp000066400000000000000000000342511456075706000235220ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "dialog_hotkeys.h" #include #include #include #include #include #include #include "agg_image.h" #include "cursor.h" #include "dialog.h" #include "game_hotkeys.h" #include "icn.h" #include "image.h" #include "interface_list.h" #include "localevent.h" #include "math_base.h" #include "screen.h" #include "settings.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_scrollbar.h" #include "ui_text.h" #include "ui_window.h" namespace { const int32_t keyDescriptionLength = 300; const int32_t hotKeyLength = 120; class HotKeyElement : public fheroes2::DialogElement { public: HotKeyElement( const fheroes2::Key key, fheroes2::Image & output ) : _restorer( output, 0, 0, 0, 0 ) , _key( key ) { // Text always occupies the whole width of the dialog. _area = { BOXAREA_WIDTH, fheroes2::getFontHeight( fheroes2::FontSize::NORMAL ) }; } ~HotKeyElement() override = default; void draw( fheroes2::Image & output, const fheroes2::Point & offset ) const override { _restorer.restore(); fheroes2::MultiFontText text; text.add( fheroes2::Text{ _( "Hotkey: " ), fheroes2::FontType::normalYellow() } ); text.add( fheroes2::Text{ StringUpper( KeySymGetName( _key ) ), fheroes2::FontType::normalWhite() } ); _restorer.update( offset.x, offset.y, BOXAREA_WIDTH, text.height() ); text.draw( offset.x, offset.y, BOXAREA_WIDTH, output ); } void processEvents( const fheroes2::Point & /*offset*/ ) const override { const LocalEvent & le = LocalEvent::Get(); if ( le.KeyPress() ) { _key = le.KeyValue(); _keyChanged = true; } } // Never call this method as a custom image has nothing to popup. void showPopup( const int /*buttons*/ ) const override { assert( 0 ); } bool update( fheroes2::Image & output, const fheroes2::Point & offset ) const override { if ( _keyChanged ) { _keyChanged = false; draw( output, offset ); return true; } return false; } void reset() { _restorer.reset(); } fheroes2::Key getKey() const { return _key; } private: mutable fheroes2::ImageRestorer _restorer; mutable fheroes2::Key _key; mutable bool _keyChanged{ false }; }; class HotKeyList : public Interface::ListBox> { public: using Interface::ListBox>::ListBox; using Interface::ListBox>::ActionListSingleClick; using Interface::ListBox>::ActionListPressRight; using Interface::ListBox>::ActionListDoubleClick; void RedrawItem( const std::pair & hotKeyEvent, int32_t offsetX, int32_t offsetY, bool current ) override { fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::FontType fontType = current ? fheroes2::FontType::normalYellow() : fheroes2::FontType::normalWhite(); offsetY += 2; fheroes2::Text name( _( Game::getHotKeyEventNameByEventId( hotKeyEvent.first ) ), fontType ); name.fitToOneRow( keyDescriptionLength ); name.draw( offsetX + 4, offsetY, display ); fheroes2::Text hotkey( Game::getHotKeyNameByEventId( hotKeyEvent.first ), fontType ); hotkey.fitToOneRow( hotKeyLength ); hotkey.draw( offsetX + keyDescriptionLength + 9, offsetY, hotKeyLength, display ); } void RedrawBackground( const fheroes2::Point & /* unused */ ) override { _listBackground->restore(); } void ActionCurrentUp() override { // Do nothing. } void ActionCurrentDn() override { // Do nothing. } void ActionListSingleClick( std::pair & /*unused*/ ) override { // Do nothing. } void ActionListPressRight( std::pair & hotKeyEvent ) override { fheroes2::MultiFontText title; title.add( fheroes2::Text{ _( "Category: " ), fheroes2::FontType::normalYellow() } ); title.add( fheroes2::Text{ _( Game::getHotKeyCategoryName( hotKeyEvent.second ) ), fheroes2::FontType::normalWhite() } ); title.add( fheroes2::Text{ "\n\n", fheroes2::FontType::normalWhite() } ); title.add( fheroes2::Text{ _( "Event: " ), fheroes2::FontType::normalYellow() } ); title.add( fheroes2::Text{ _( Game::getHotKeyEventNameByEventId( hotKeyEvent.first ) ), fheroes2::FontType::normalWhite() } ); title.add( fheroes2::Text{ "\n\n", fheroes2::FontType::normalWhite() } ); title.add( fheroes2::Text{ _( "Hotkey: " ), fheroes2::FontType::normalYellow() } ); title.add( fheroes2::Text{ Game::getHotKeyNameByEventId( hotKeyEvent.first ), fheroes2::FontType::normalWhite() } ); fheroes2::showMessage( fheroes2::Text{}, title, Dialog::ZERO ); } void ActionListDoubleClick( std::pair & hotKeyEvent ) override { HotKeyElement hotKeyUI( Game::getHotKeyForEvent( hotKeyEvent.first ), fheroes2::Display::instance() ); // Okay and Cancel events are special cases as they are used in dialogs. By default we need to disable these events to allow to be set any key for an event. // Global events (that work on all screens) must be disabled as well. const fheroes2::Key okayEventKey = Game::getHotKeyForEvent( Game::HotKeyEvent::DEFAULT_OKAY ); const fheroes2::Key cancelEventKey = Game::getHotKeyForEvent( Game::HotKeyEvent::DEFAULT_CANCEL ); const fheroes2::Key fullscreenEventKey = Game::getHotKeyForEvent( Game::HotKeyEvent::GLOBAL_TOGGLE_FULLSCREEN ); const fheroes2::Key textSupportModeEventKey = Game::getHotKeyForEvent( Game::HotKeyEvent::GLOBAL_TOGGLE_TEXT_SUPPORT_MODE ); Game::setHotKeyForEvent( Game::HotKeyEvent::DEFAULT_OKAY, fheroes2::Key::NONE ); Game::setHotKeyForEvent( Game::HotKeyEvent::DEFAULT_CANCEL, fheroes2::Key::NONE ); Game::setHotKeyForEvent( Game::HotKeyEvent::GLOBAL_TOGGLE_FULLSCREEN, fheroes2::Key::NONE ); Game::setHotKeyForEvent( Game::HotKeyEvent::GLOBAL_TOGGLE_TEXT_SUPPORT_MODE, fheroes2::Key::NONE ); fheroes2::MultiFontText title; title.add( fheroes2::Text{ _( "Category: " ), fheroes2::FontType::normalYellow() } ); title.add( fheroes2::Text{ _( Game::getHotKeyCategoryName( hotKeyEvent.second ) ), fheroes2::FontType::normalWhite() } ); title.add( fheroes2::Text{ "\n\n", fheroes2::FontType::normalWhite() } ); title.add( fheroes2::Text{ _( "Event: " ), fheroes2::FontType::normalYellow() } ); title.add( fheroes2::Text{ _( Game::getHotKeyEventNameByEventId( hotKeyEvent.first ) ), fheroes2::FontType::normalWhite() } ); const int returnValue = fheroes2::showMessage( fheroes2::Text{}, title, Dialog::OK | Dialog::CANCEL, { &hotKeyUI } ); Game::setHotKeyForEvent( Game::HotKeyEvent::DEFAULT_OKAY, okayEventKey ); Game::setHotKeyForEvent( Game::HotKeyEvent::DEFAULT_CANCEL, cancelEventKey ); Game::setHotKeyForEvent( Game::HotKeyEvent::GLOBAL_TOGGLE_FULLSCREEN, fullscreenEventKey ); Game::setHotKeyForEvent( Game::HotKeyEvent::GLOBAL_TOGGLE_TEXT_SUPPORT_MODE, textSupportModeEventKey ); // To avoid UI issues we need to reset restorer manually. hotKeyUI.reset(); if ( returnValue == Dialog::CANCEL ) { return; } Game::setHotKeyForEvent( hotKeyEvent.first, hotKeyUI.getKey() ); Game::HotKeySave(); } void initListBackgroundRestorer( fheroes2::Rect roi ) { _listBackground = std::make_unique( fheroes2::Display::instance(), roi.x, roi.y, roi.width, roi.height ); } void updateScrollBarImage() { const int32_t scrollBarWidth = _scrollbar.width(); setScrollBarImage( fheroes2::generateScrollbarSlider( _scrollbar, false, _scrollbar.getArea().height, VisibleItemCount(), _size(), { 0, 0, scrollBarWidth, 8 }, { 0, 7, scrollBarWidth, 8 } ) ); _scrollbar.moveToIndex( _topId ); } private: std::unique_ptr _listBackground; }; } namespace fheroes2 { void openHotkeysDialog() { // Setup cursor. const CursorRestorer cursorRestorer( true, ::Cursor::POINTER ); fheroes2::Display & display = fheroes2::Display::instance(); // Dialog height is capped with the current screen height minus 100 pixels. fheroes2::StandardWindow background( keyDescriptionLength + hotKeyLength + 8 + 75, std::min( display.height() - 100, 520 ), true, display ); const fheroes2::Rect roi( background.activeArea() ); const fheroes2::Rect listRoi( roi.x + 24, roi.y + 37, keyDescriptionLength + hotKeyLength + 8, roi.height - 75 ); const fheroes2::Text title( _( "Hot Keys:" ), fheroes2::FontType::normalYellow() ); title.draw( roi.x + ( roi.width - title.width() ) / 2, roi.y + 16, display ); // We divide the list: action description and binded hot-key. background.applyTextBackgroundShading( { listRoi.x, listRoi.y, keyDescriptionLength + 8, listRoi.height } ); background.applyTextBackgroundShading( { listRoi.x + keyDescriptionLength + 8, listRoi.y, hotKeyLength, listRoi.height } ); const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); // Prepare OKAY button and render its shadow. fheroes2::Button buttonOk; const int buttonOkIcn = isEvilInterface ? ICN::BUTTON_SMALL_OKAY_EVIL : ICN::BUTTON_SMALL_OKAY_GOOD; background.renderButton( buttonOk, buttonOkIcn, 0, 1, { 0, 7 }, StandardWindow::Padding::BOTTOM_CENTER ); HotKeyList listbox( roi.getPosition() ); listbox.initListBackgroundRestorer( listRoi ); listbox.SetAreaItems( { listRoi.x, listRoi.y + 3, listRoi.width - 3, listRoi.height - 4 } ); int32_t scrollbarOffsetX = roi.x + roi.width - 35; background.renderScrollbarBackground( { scrollbarOffsetX, listRoi.y, listRoi.width, listRoi.height }, isEvilInterface ); const int32_t topPartHeight = 19; const int listIcnId = isEvilInterface ? ICN::SCROLLE : ICN::SCROLL; ++scrollbarOffsetX; listbox.SetScrollButtonUp( listIcnId, 0, 1, { scrollbarOffsetX, listRoi.y + 1 } ); listbox.SetScrollButtonDn( listIcnId, 2, 3, { scrollbarOffsetX, listRoi.y + listRoi.height - 15 } ); listbox.setScrollBarArea( { scrollbarOffsetX + 2, listRoi.y + topPartHeight, 10, listRoi.height - 2 * topPartHeight } ); listbox.setScrollBarImage( fheroes2::AGG::GetICN( listIcnId, 4 ) ); listbox.SetAreaMaxItems( ( listRoi.height - 7 ) / fheroes2::getFontHeight( fheroes2::FontSize::NORMAL ) ); std::vector> hotKeyEvents = Game::getAllHotKeyEvents(); listbox.SetListContent( hotKeyEvents ); listbox.updateScrollBarImage(); listbox.Redraw(); display.render( background.totalArea() ); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { le.MousePressLeft( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); listbox.QueueEventProcessing(); if ( le.MouseClickLeft( buttonOk.area() ) || Game::HotKeyCloseWindow() ) { return; } if ( le.MousePressRight( buttonOk.area() ) ) { const fheroes2::Text header( _( "Okay" ), fheroes2::FontType::normalYellow() ); const fheroes2::Text body( _( "Exit this menu." ), fheroes2::FontType::normalWhite() ); fheroes2::showMessage( header, body, 0 ); continue; } if ( !listbox.IsNeedRedraw() ) { continue; } listbox.Redraw(); display.render( roi ); } } } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_hotkeys.h000066400000000000000000000027741456075706000231740ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once namespace fheroes2 { void openHotkeysDialog(); } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_interface_settings.cpp000066400000000000000000000325171456075706000257170ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "dialog_interface_settings.h" #include #include #include #include #include "agg_image.h" #include "game_hotkeys.h" #include "gamedefs.h" #include "icn.h" #include "image.h" #include "localevent.h" #include "math_base.h" #include "screen.h" #include "settings.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_option_item.h" namespace { enum class SelectedWindow : int { Configuration, InterfaceType, InterfacePresence, CursorType, Exit }; const fheroes2::Size offsetBetweenOptions{ 118, 110 }; const fheroes2::Point optionOffset{ 69, 47 }; const int32_t optionWindowSize{ 65 }; const fheroes2::Rect interfaceTypeRoi{ optionOffset.x, optionOffset.y, optionWindowSize, optionWindowSize }; const fheroes2::Rect interfacePresenceRoi{ optionOffset.x + offsetBetweenOptions.width, optionOffset.y, optionWindowSize, optionWindowSize }; const fheroes2::Rect cursorTypeRoi{ optionOffset.x, optionOffset.y + offsetBetweenOptions.height, optionWindowSize, optionWindowSize }; const fheroes2::Rect scrollSpeedRoi{ optionOffset.x + offsetBetweenOptions.width, optionOffset.y + offsetBetweenOptions.height, optionWindowSize, optionWindowSize }; void drawInterfaceType( const fheroes2::Rect & optionRoi ) { const Settings & conf = Settings::Get(); const bool isEvilInterface = conf.isEvilInterfaceEnabled(); const fheroes2::Sprite & interfaceThemeIcon = fheroes2::AGG::GetICN( ICN::SPANEL, isEvilInterface ? 17 : 16 ); std::string value; if ( isEvilInterface ) { value = _( "Evil" ); } else { value = _( "Good" ); } fheroes2::drawOption( optionRoi, interfaceThemeIcon, _( "Interface Type" ), std::move( value ), fheroes2::UiOptionTextWidth::TWO_ELEMENTS_ROW ); } void drawInterfacePresence( const fheroes2::Rect & optionRoi ) { // Interface show/hide state. const Settings & conf = Settings::Get(); const bool isHiddenInterface = conf.isHideInterfaceEnabled(); const bool isEvilInterface = conf.isEvilInterfaceEnabled(); const fheroes2::Sprite & interfaceStateIcon = isHiddenInterface ? fheroes2::AGG::GetICN( ICN::ESPANEL, 4 ) : fheroes2::AGG::GetICN( ICN::SPANEL, isEvilInterface ? 17 : 16 ); std::string value; if ( isHiddenInterface ) { value = _( "Hide" ); } else { value = _( "Show" ); } fheroes2::drawOption( optionRoi, interfaceStateIcon, _( "Interface" ), std::move( value ), fheroes2::UiOptionTextWidth::TWO_ELEMENTS_ROW ); } void drawCursorType( const fheroes2::Rect & optionRoi ) { if ( Settings::Get().isMonochromeCursorEnabled() ) { fheroes2::drawOption( optionRoi, fheroes2::AGG::GetICN( ICN::SPANEL, 20 ), _( "Mouse Cursor" ), _( "Black & White" ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); } else { fheroes2::drawOption( optionRoi, fheroes2::AGG::GetICN( ICN::SPANEL, 21 ), _( "Mouse Cursor" ), _( "Color" ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); } } void drawScrollSpeed( const fheroes2::Rect & optionRoi ) { const Settings & conf = Settings::Get(); const int scrollSpeed = conf.ScrollSpeed(); int32_t scrollSpeedIconIcn = ICN::UNKNOWN; uint32_t scrollSpeedIconId = 0; std::string scrollSpeedName; if ( scrollSpeed == SCROLL_SPEED_NONE ) { scrollSpeedName = _( "Off" ); scrollSpeedIconIcn = ICN::SPANEL; scrollSpeedIconId = 9; } else if ( scrollSpeed == SCROLL_SPEED_SLOW ) { scrollSpeedName = _( "Slow" ); scrollSpeedIconIcn = ICN::CSPANEL; scrollSpeedIconId = 0; } else if ( scrollSpeed == SCROLL_SPEED_NORMAL ) { scrollSpeedName = _( "Normal" ); scrollSpeedIconIcn = ICN::CSPANEL; scrollSpeedIconId = 0; } else if ( scrollSpeed == SCROLL_SPEED_FAST ) { scrollSpeedName = _( "Fast" ); scrollSpeedIconIcn = ICN::CSPANEL; scrollSpeedIconId = 1; } else if ( scrollSpeed == SCROLL_SPEED_VERY_FAST ) { scrollSpeedName = _( "Very Fast" ); scrollSpeedIconIcn = ICN::CSPANEL; scrollSpeedIconId = 2; } assert( scrollSpeedIconIcn != ICN::UNKNOWN ); const fheroes2::Sprite & scrollSpeedIcon = fheroes2::AGG::GetICN( scrollSpeedIconIcn, scrollSpeedIconId ); fheroes2::drawOption( optionRoi, scrollSpeedIcon, _( "Scroll Speed" ), std::move( scrollSpeedName ), fheroes2::UiOptionTextWidth::TWO_ELEMENTS_ROW ); } SelectedWindow showConfigurationWindow( bool & saveConfiguration ) { fheroes2::Display & display = fheroes2::Display::instance(); Settings & conf = Settings::Get(); const bool isEvilInterface = conf.isEvilInterfaceEnabled(); const fheroes2::Sprite & dialog = fheroes2::AGG::GetICN( ( isEvilInterface ? ICN::ESPANBKG_EVIL : ICN::ESPANBKG ), 0 ); const fheroes2::Sprite & dialogShadow = fheroes2::AGG::GetICN( ( isEvilInterface ? ICN::CSPANBKE : ICN::CSPANBKG ), 1 ); const fheroes2::Point dialogOffset( ( display.width() - dialog.width() ) / 2, ( display.height() - dialog.height() ) / 2 ); const fheroes2::Point shadowOffset( dialogOffset.x - BORDERWIDTH, dialogOffset.y ); const fheroes2::ImageRestorer restorer( display, shadowOffset.x, shadowOffset.y, dialog.width() + BORDERWIDTH, dialog.height() + BORDERWIDTH ); const fheroes2::Rect windowRoi{ dialogOffset.x, dialogOffset.y, dialog.width(), dialog.height() }; fheroes2::Blit( dialogShadow, display, windowRoi.x - BORDERWIDTH, windowRoi.y + BORDERWIDTH ); fheroes2::Blit( dialog, display, windowRoi.x, windowRoi.y ); fheroes2::ImageRestorer emptyDialogRestorer( display, windowRoi.x, windowRoi.y, windowRoi.width, windowRoi.height ); const fheroes2::Rect windowInterfaceTypeRoi( interfaceTypeRoi + windowRoi.getPosition() ); const fheroes2::Rect windowInterfacePresenceRoi( interfacePresenceRoi + windowRoi.getPosition() ); const fheroes2::Rect windowCursorTypeRoi( cursorTypeRoi + windowRoi.getPosition() ); const fheroes2::Rect windowScrollSpeedRoi( scrollSpeedRoi + windowRoi.getPosition() ); const auto drawOptions = [&windowInterfaceTypeRoi, &windowInterfacePresenceRoi, &windowCursorTypeRoi, &windowScrollSpeedRoi]() { drawInterfaceType( windowInterfaceTypeRoi ); drawInterfacePresence( windowInterfacePresenceRoi ); drawCursorType( windowCursorTypeRoi ); drawScrollSpeed( windowScrollSpeedRoi ); }; drawOptions(); const fheroes2::Point buttonOffset( 112 + windowRoi.x, 252 + windowRoi.y ); fheroes2::Button okayButton( buttonOffset.x, buttonOffset.y, isEvilInterface ? ICN::BUTTON_SMALL_OKAY_EVIL : ICN::BUTTON_SMALL_OKAY_GOOD, 0, 1 ); okayButton.draw(); const auto refreshWindow = [&drawOptions, &emptyDialogRestorer, &okayButton, &display]() { emptyDialogRestorer.restore(); drawOptions(); okayButton.draw(); display.render( emptyDialogRestorer.rect() ); }; display.render(); bool isFullScreen = fheroes2::engine().isFullScreen(); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { if ( le.MousePressLeft( okayButton.area() ) ) { okayButton.drawOnPress(); } else { okayButton.drawOnRelease(); } if ( le.MouseClickLeft( okayButton.area() ) || Game::HotKeyCloseWindow() ) { break; } if ( le.MouseClickLeft( windowInterfaceTypeRoi ) ) { return SelectedWindow::InterfaceType; } if ( le.MouseClickLeft( windowInterfacePresenceRoi ) ) { return SelectedWindow::InterfacePresence; } if ( le.MouseClickLeft( windowCursorTypeRoi ) ) { return SelectedWindow::CursorType; } if ( le.MouseClickLeft( windowScrollSpeedRoi ) ) { saveConfiguration = true; conf.SetScrollSpeed( ( conf.ScrollSpeed() + 1 ) % ( SCROLL_SPEED_VERY_FAST + 1 ) ); refreshWindow(); continue; } if ( le.MouseWheelUp( windowScrollSpeedRoi ) ) { saveConfiguration = true; conf.SetScrollSpeed( conf.ScrollSpeed() + 1 ); refreshWindow(); continue; } if ( le.MouseWheelDn( windowScrollSpeedRoi ) ) { saveConfiguration = true; conf.SetScrollSpeed( conf.ScrollSpeed() - 1 ); refreshWindow(); continue; } if ( le.MousePressRight( windowInterfaceTypeRoi ) ) { fheroes2::showStandardTextMessage( _( "Interface Type" ), _( "Toggle the type of interface you want to use." ), 0 ); } else if ( le.MousePressRight( windowInterfacePresenceRoi ) ) { fheroes2::showStandardTextMessage( _( "Interface" ), _( "Toggle interface visibility." ), 0 ); } else if ( le.MousePressRight( windowCursorTypeRoi ) ) { fheroes2::showStandardTextMessage( _( "Mouse Cursor" ), _( "Toggle colored cursor on or off. This is only an esthetic choice." ), 0 ); } if ( le.MousePressRight( windowScrollSpeedRoi ) ) { fheroes2::showStandardTextMessage( _( "Scroll Speed" ), _( "Sets the speed at which you scroll the window." ), 0 ); } else if ( le.MousePressRight( okayButton.area() ) ) { fheroes2::showStandardTextMessage( _( "Okay" ), _( "Exit this menu." ), 0 ); } // Fullscreen mode can be toggled using a global hotkey, we need to properly reflect this change in the UI if ( isFullScreen != fheroes2::engine().isFullScreen() ) { isFullScreen = fheroes2::engine().isFullScreen(); emptyDialogRestorer.restore(); drawOptions(); display.render( emptyDialogRestorer.rect() ); } } return SelectedWindow::Exit; } } namespace fheroes2 { void openInterfaceSettingsDialog( const std::function & updateUI ) { Settings & conf = Settings::Get(); bool saveConfiguration = false; SelectedWindow windowType = SelectedWindow::Configuration; while ( windowType != SelectedWindow::Exit ) { switch ( windowType ) { case SelectedWindow::Configuration: windowType = showConfigurationWindow( saveConfiguration ); break; case SelectedWindow::InterfaceType: conf.setEvilInterface( !conf.isEvilInterfaceEnabled() ); updateUI(); saveConfiguration = true; windowType = SelectedWindow::Configuration; break; case SelectedWindow::InterfacePresence: conf.setHideInterface( !conf.isHideInterfaceEnabled() ); updateUI(); saveConfiguration = true; windowType = SelectedWindow::Configuration; break; case SelectedWindow::CursorType: conf.setMonochromeCursor( !conf.isMonochromeCursorEnabled() ); saveConfiguration = true; windowType = SelectedWindow::Configuration; break; default: return; } } if ( saveConfiguration ) { conf.Save( Settings::configFileName ); } } } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_interface_settings.h000066400000000000000000000031041456075706000253520ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include namespace fheroes2 { void openInterfaceSettingsDialog( const std::function & updateUI ); } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_language_selection.cpp000066400000000000000000000324441456075706000256660ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "dialog_language_selection.h" #include #include #include #include #include #include #include "agg_image.h" #include "cursor.h" #include "game_hotkeys.h" #include "icn.h" #include "image.h" #include "interface_list.h" #include "localevent.h" #include "math_base.h" #include "screen.h" #include "settings.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_scrollbar.h" #include "ui_text.h" #include "ui_window.h" namespace { const int32_t verticalPaddingAreasHight = 30; const int32_t textAreaWidth = 270; const int32_t scrollBarAreaWidth = 48; const int32_t paddingLeftSide = 24; class LanguageList : public Interface::ListBox { public: using Interface::ListBox::ActionListSingleClick; using Interface::ListBox::ActionListPressRight; using Interface::ListBox::ActionListDoubleClick; explicit LanguageList( const fheroes2::Point & offset ) : Interface::ListBox( offset ) , _isDoubleClicked( false ) { // Do nothing. } void RedrawItem( const fheroes2::SupportedLanguage & language, int32_t offsetX, int32_t offsetY, bool isSelected ) override { fheroes2::LanguageSwitcher languageSwitcher( language ); const fheroes2::Text languageName( fheroes2::getLanguageName( language ), isSelected ? fheroes2::FontType::normalYellow() : fheroes2::FontType::normalWhite() ); languageName.draw( ( textAreaWidth - languageName.width() ) / 2 + offsetX, offsetY, fheroes2::Display::instance() ); } void RedrawBackground( const fheroes2::Point & /* unused */ ) override { _listBackground->restore(); } void ActionCurrentUp() override { // Do nothing. } void ActionCurrentDn() override { // Do nothing. } void ActionListSingleClick( fheroes2::SupportedLanguage & /*language*/ ) override { // Do nothing. } void ActionListPressRight( fheroes2::SupportedLanguage & /*language*/ ) override { // Do nothing. } void ActionListDoubleClick( fheroes2::SupportedLanguage & /*language*/ ) override { _isDoubleClicked = true; } bool isDoubleClicked() const { return _isDoubleClicked; } int getCurrentId() const { return _currentId; } void initListBackgroundRestorer( fheroes2::Rect roi ) { _listBackground = std::make_unique( fheroes2::Display::instance(), roi.x, roi.y, roi.width, roi.height ); } void updateScrollBarImage() { const int32_t scrollBarWidth = _scrollbar.width(); setScrollBarImage( fheroes2::generateScrollbarSlider( _scrollbar, false, _scrollbar.getArea().height, VisibleItemCount(), _size(), { 0, 0, scrollBarWidth, 8 }, { 0, 7, scrollBarWidth, 8 } ) ); _scrollbar.moveToIndex( _topId ); } private: bool _isDoubleClicked; std::unique_ptr _listBackground; }; void redrawDialogInfo( const fheroes2::Rect & listRoi, const fheroes2::SupportedLanguage & language ) { fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::FontType fontType = fheroes2::FontType::normalYellow(); const fheroes2::Text title( _( "Select Game Language:" ), fontType ); title.draw( listRoi.x + ( listRoi.width - title.width() ) / 2, listRoi.y - ( verticalPaddingAreasHight + title.height() + 2 ) / 2, display ); const fheroes2::Text selectedLanguage( fheroes2::getLanguageName( language ), fontType ); selectedLanguage.draw( listRoi.x + ( listRoi.width - selectedLanguage.width() ) / 2, listRoi.y + listRoi.height + 12 + ( 21 - selectedLanguage.height() ) / 2 + 2, display ); } bool getLanguage( const std::vector & languages, fheroes2::SupportedLanguage chosenLanguage ) { // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); const int32_t listHeightDeduction = 112; const int32_t listAreaOffsetY = 3; const int32_t listAreaHeightDeduction = 4; // If we don't have many languagues, we reduce the maximum dialog height, // but not less than enough for 11 elements. // We also limit the maximum list height to 22 lines. const int32_t maxDialogHeight = fheroes2::getFontHeight( fheroes2::FontSize::NORMAL ) * std::clamp( static_cast( languages.size() ), 11, 22 ) + listAreaOffsetY + listAreaHeightDeduction + listHeightDeduction; fheroes2::Display & display = fheroes2::Display::instance(); // Dialog height is also capped with the current screen height. fheroes2::StandardWindow background( paddingLeftSide + textAreaWidth + scrollBarAreaWidth + 3, std::min( display.height() - 100, maxDialogHeight ), true, display ); const fheroes2::Rect roi( background.activeArea() ); const fheroes2::Rect listRoi( roi.x + paddingLeftSide, roi.y + 37, textAreaWidth, roi.height - listHeightDeduction ); // We divide the list: language list and selected language. const fheroes2::Rect selectedLangRoi( listRoi.x, listRoi.y + listRoi.height + 12, listRoi.width, 21 ); background.applyTextBackgroundShading( selectedLangRoi ); background.applyTextBackgroundShading( { listRoi.x, listRoi.y, listRoi.width, listRoi.height } ); fheroes2::ImageRestorer titleBackground( fheroes2::Display::instance(), roi.x, listRoi.y - verticalPaddingAreasHight, roi.width, verticalPaddingAreasHight ); fheroes2::ImageRestorer selectedLangBackground( fheroes2::Display::instance(), selectedLangRoi.x, selectedLangRoi.y, listRoi.width, selectedLangRoi.height ); fheroes2::ImageRestorer buttonsBackground( fheroes2::Display::instance(), roi.x, selectedLangRoi.y + selectedLangRoi.height + 10, roi.width, verticalPaddingAreasHight ); LanguageList listBox( roi.getPosition() ); listBox.initListBackgroundRestorer( listRoi ); const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); // Prepare OKAY and CANCEL buttons and render their shadows. fheroes2::Button buttonOk; fheroes2::Button buttonCancel; background.renderOkayCancelButtons( buttonOk, buttonCancel, isEvilInterface ); listBox.SetAreaItems( { listRoi.x, listRoi.y + 3, listRoi.width - 3, listRoi.height - 4 } ); int32_t scrollbarOffsetX = roi.x + roi.width - 35; background.renderScrollbarBackground( { scrollbarOffsetX, listRoi.y, listRoi.width, listRoi.height }, isEvilInterface ); const int listIcnId = isEvilInterface ? ICN::SCROLLE : ICN::SCROLL; const int32_t topPartHeight = 19; ++scrollbarOffsetX; listBox.SetScrollButtonUp( listIcnId, 0, 1, { scrollbarOffsetX, listRoi.y + 1 } ); listBox.SetScrollButtonDn( listIcnId, 2, 3, { scrollbarOffsetX, listRoi.y + listRoi.height - 15 } ); listBox.setScrollBarArea( { scrollbarOffsetX + 2, listRoi.y + topPartHeight, 10, listRoi.height - 2 * topPartHeight } ); listBox.setScrollBarImage( fheroes2::AGG::GetICN( listIcnId, 4 ) ); listBox.SetAreaMaxItems( ( listRoi.height - 7 ) / fheroes2::getFontHeight( fheroes2::FontSize::NORMAL ) ); std::vector temp = languages; listBox.SetListContent( temp ); listBox.updateScrollBarImage(); for ( size_t i = 0; i < languages.size(); ++i ) { if ( languages[i] == chosenLanguage ) { listBox.SetCurrent( i ); break; } } listBox.Redraw(); redrawDialogInfo( listRoi, chosenLanguage ); display.render( background.totalArea() ); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { le.MousePressLeft( buttonOk.area() ) && buttonOk.isEnabled() ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); le.MousePressLeft( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); if ( le.MousePressRight( listRoi ) ) { continue; } const int listId = listBox.getCurrentId(); listBox.QueueEventProcessing(); const bool needRedraw = listId != listBox.getCurrentId(); if ( ( buttonOk.isEnabled() && le.MouseClickLeft( buttonOk.area() ) ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) || listBox.isDoubleClicked() ) { return true; } if ( le.MouseClickLeft( buttonCancel.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { return false; } if ( le.MousePressRight( buttonCancel.area() ) ) { fheroes2::Text header( _( "Cancel" ), fheroes2::FontType::normalYellow() ); fheroes2::Text body( _( "Exit this menu without doing anything." ), fheroes2::FontType::normalWhite() ); fheroes2::showMessage( header, body, 0 ); } else if ( le.MousePressRight( buttonOk.area() ) ) { fheroes2::Text header( _( "Okay" ), fheroes2::FontType::normalYellow() ); fheroes2::Text body( _( "Click to choose the selected language." ), fheroes2::FontType::normalWhite() ); fheroes2::showMessage( header, body, 0 ); } if ( !listBox.IsNeedRedraw() ) { continue; } if ( needRedraw ) { const fheroes2::SupportedLanguage newChosenLanguage = listBox.GetCurrent(); if ( newChosenLanguage != chosenLanguage ) { chosenLanguage = newChosenLanguage; Settings::Get().setGameLanguage( fheroes2::getLanguageAbbreviation( chosenLanguage ) ); titleBackground.restore(); selectedLangBackground.restore(); redrawDialogInfo( listRoi, chosenLanguage ); buttonsBackground.restore(); background.renderOkayCancelButtons( buttonOk, buttonCancel, isEvilInterface ); } } listBox.Redraw(); display.render( roi ); } return false; } } namespace fheroes2 { void selectLanguage( const std::vector & languages, const SupportedLanguage currentLanguage ) { if ( languages.empty() ) { // Why do you even call this function having 0 languages? assert( 0 ); Settings::Get().setGameLanguage( fheroes2::getLanguageAbbreviation( SupportedLanguage::English ) ); return; } if ( languages.size() == 1 ) { Settings::Get().setGameLanguage( fheroes2::getLanguageAbbreviation( languages.front() ) ); return; } SupportedLanguage chosenLanguage = languages.front(); for ( const SupportedLanguage language : languages ) { if ( currentLanguage == language ) { chosenLanguage = currentLanguage; break; } } if ( !getLanguage( languages, chosenLanguage ) ) { Settings::Get().setGameLanguage( fheroes2::getLanguageAbbreviation( chosenLanguage ) ); } } } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_language_selection.h000066400000000000000000000032601456075706000253250ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include "ui_language.h" namespace fheroes2 { // UI window to select a language by user. void selectLanguage( const std::vector & languages, const SupportedLanguage currentLanguage ); } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_levelup.cpp000066400000000000000000000246601456075706000235130ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include "agg_image.h" #include "cursor.h" #include "dialog.h" #include "game_hotkeys.h" #include "gamedefs.h" #include "heroes.h" #include "icn.h" #include "image.h" #include "localevent.h" #include "math_base.h" #include "screen.h" #include "settings.h" #include "skill.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_text.h" void DialogPrimaryOnly( const std::string & name, const int primarySkillType ) { std::string message = _( "%{name} has gained a level." ); message.append( "\n\n" ); message.append( _( "%{skill} +1" ) ); StringReplace( message, "%{name}", name ); StringReplace( message, "%{skill}", Skill::Primary::String( primarySkillType ) ); const fheroes2::PrimarySkillDialogElement primarySkillUI( primarySkillType, "+1" ); fheroes2::showMessage( fheroes2::Text( "", {} ), fheroes2::Text( message, fheroes2::FontType::normalWhite() ), Dialog::OK, { &primarySkillUI } ); } int DialogOneSecondary( const Heroes & hero, const std::string & name, const int primarySkillType, const Skill::Secondary & sec ) { std::string message = _( "%{name} has gained a level." ); message.append( "\n\n" ); message.append( _( "%{skill} +1" ) ); StringReplace( message, "%{name}", name ); StringReplace( message, "%{skill}", Skill::Primary::String( primarySkillType ) ); message.append( "\n\n" ); message.append( _( "You have learned %{skill}." ) ); StringReplace( message, "%{skill}", sec.GetName() ); const fheroes2::SecondarySkillDialogElement secondarySkillUI( sec, hero ); fheroes2::showMessage( fheroes2::Text( "", {} ), fheroes2::Text( message, fheroes2::FontType::normalWhite() ), Dialog::OK, { &secondarySkillUI } ); return sec.Skill(); } int DialogSelectSecondary( const std::string & name, const int primarySkillType, const Skill::Secondary & sec1, const Skill::Secondary & sec2, Heroes & hero ) { std::string header = _( "%{name} has gained a level.\n\n%{skill} +1" ); StringReplace( header, "%{name}", name ); StringReplace( header, "%{skill}", Skill::Primary::String( primarySkillType ) ); fheroes2::Display & display = fheroes2::Display::instance(); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); const fheroes2::Sprite & sprite_frame = fheroes2::AGG::GetICN( ICN::SECSKILL, 15 ); const fheroes2::Sprite & sprite_skill1 = fheroes2::AGG::GetICN( ICN::SECSKILL, sec1.GetIndexSprite1() ); const fheroes2::Sprite & sprite_skill2 = fheroes2::AGG::GetICN( ICN::SECSKILL, sec2.GetIndexSprite1() ); std::string message = _( "You may learn either:\n%{skill1}\nor\n%{skill2}" ); StringReplace( message, "%{skill1}", sec1.GetName() ); StringReplace( message, "%{skill2}", sec2.GetName() ); fheroes2::Text box1( std::move( header ), fheroes2::FontType::normalWhite() ); fheroes2::Text box2( std::move( message ), fheroes2::FontType::normalWhite() ); const int spacer = 10; Dialog::FrameBox box( box1.height( BOXAREA_WIDTH ) + spacer + box2.height( BOXAREA_WIDTH ) + 10 + sprite_frame.height(), true ); const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); const int buttonLearnIcnID = isEvilInterface ? ICN::BUTTON_SMALL_LEARN_EVIL : ICN::BUTTON_SMALL_LEARN_GOOD; fheroes2::Point pt; pt.x = box.GetArea().x + box.GetArea().width / 2 - fheroes2::AGG::GetICN( buttonLearnIcnID, 0 ).width() - 20; pt.y = box.GetArea().y + box.GetArea().height - fheroes2::AGG::GetICN( buttonLearnIcnID, 0 ).height(); fheroes2::Button button_learn1( pt.x, pt.y, buttonLearnIcnID, 0, 1 ); pt.x = box.GetArea().x + box.GetArea().width / 2 + 20; pt.y = box.GetArea().y + box.GetArea().height - fheroes2::AGG::GetICN( buttonLearnIcnID, 0 ).height(); fheroes2::Button button_learn2( pt.x, pt.y, buttonLearnIcnID, 0, 1 ); const fheroes2::Rect & boxArea = box.GetArea(); fheroes2::Point pos( boxArea.x, boxArea.y ); box1.draw( pos.x, pos.y + 2, BOXAREA_WIDTH, display ); pos.y += box1.height( BOXAREA_WIDTH ) + spacer; box2.draw( pos.x, pos.y + 2, BOXAREA_WIDTH, display ); pos.y += box2.height( BOXAREA_WIDTH ) + spacer; // sprite1 pos.x = box.GetArea().x + box.GetArea().width / 2 - sprite_frame.width() - 20; fheroes2::Blit( sprite_frame, display, pos.x, pos.y ); pos.x += 3; fheroes2::Rect rect_image1( pos.x, pos.y, sprite_skill1.width(), sprite_skill1.height() ); fheroes2::Blit( sprite_skill1, display, pos.x, pos.y + 3 ); fheroes2::Text text{ Skill::Secondary::String( sec1.Skill() ), fheroes2::FontType::smallWhite() }; text.draw( pos.x + ( sprite_skill1.width() - text.width() ) / 2, pos.y + 7, display ); text.set( Skill::Level::String( sec1.Level() ), fheroes2::FontType::smallWhite() ); text.draw( pos.x + ( sprite_skill1.width() - text.width() ) / 2, pos.y + sprite_skill1.height() - 10, display ); // sprite2 pos.x = box.GetArea().x + box.GetArea().width / 2 + 20; fheroes2::Blit( sprite_frame, display, pos.x, pos.y ); pos.x += 3; fheroes2::Rect rect_image2( pos.x, pos.y, sprite_skill2.width(), sprite_skill2.height() ); fheroes2::Blit( sprite_skill2, display, pos.x, pos.y + 3 ); // text fheroes2::Text name_skill2( Skill::Secondary::String( sec2.Skill() ), fheroes2::FontType::smallWhite() ); name_skill2.draw( pos.x + ( sprite_skill2.width() - name_skill2.width() ) / 2, pos.y + 7, display ); fheroes2::Text name_level2( Skill::Level::String( sec2.Level() ), fheroes2::FontType::smallWhite() ); name_level2.draw( pos.x + ( sprite_skill2.width() - name_level2.width() ) / 2, pos.y + sprite_skill2.height() - 10, display ); // hero button pt.x = box.GetArea().x + box.GetArea().width / 2 - 18; pt.y = box.GetArea().y + box.GetArea().height - 35; const int icnHeroes = isEvilInterface ? ICN::EVIL_ARMY_BUTTON : ICN::GOOD_ARMY_BUTTON; fheroes2::ButtonSprite button_hero = fheroes2::makeButtonWithBackground( pt.x, pt.y, fheroes2::AGG::GetICN( icnHeroes, 0 ), fheroes2::AGG::GetICN( icnHeroes, 1 ), display ); text.set( std::to_string( hero.GetSecondarySkills().Count() ) + "/" + std::to_string( HEROESMAXSKILL ), fheroes2::FontType::normalWhite() ); text.draw( box.GetArea().x + ( box.GetArea().width - text.width() ) / 2, pt.y - 13, display ); button_learn1.draw(); button_learn2.draw(); button_hero.draw(); display.render(); LocalEvent & le = LocalEvent::Get(); // message loop while ( le.HandleEvents() ) { le.MousePressLeft( button_learn1.area() ) ? button_learn1.drawOnPress() : button_learn1.drawOnRelease(); le.MousePressLeft( button_learn2.area() ) ? button_learn2.drawOnPress() : button_learn2.drawOnRelease(); le.MousePressLeft( button_hero.area() ) ? button_hero.drawOnPress() : button_hero.drawOnRelease(); if ( le.MouseClickLeft( button_learn1.area() ) ) { return sec1.Skill(); } if ( le.MouseClickLeft( button_learn2.area() ) ) { return sec2.Skill(); } if ( le.MouseClickLeft( button_hero.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) ) { LocalEvent::GetClean(); hero.OpenDialog( false, true, true, true, true ); display.render(); } if ( le.MouseClickLeft( rect_image1 ) ) { fheroes2::SecondarySkillDialogElement( sec1, hero ).showPopup( Dialog::OK ); } else if ( le.MouseClickLeft( rect_image2 ) ) { fheroes2::SecondarySkillDialogElement( sec2, hero ).showPopup( Dialog::OK ); } if ( le.MousePressRight( rect_image1 ) ) { fheroes2::SecondarySkillDialogElement( sec1, hero ).showPopup( Dialog::ZERO ); display.render(); } else if ( le.MousePressRight( rect_image2 ) ) { fheroes2::SecondarySkillDialogElement( sec2, hero ).showPopup( Dialog::ZERO ); display.render(); } else if ( le.MousePressRight( button_hero.area() ) ) { fheroes2::showStandardTextMessage( "", _( "View Hero" ), Dialog::ZERO ); } } return Skill::Secondary::UNKNOWN; } int Dialog::LevelUpSelectSkill( const std::string & name, const int primarySkillType, const Skill::Secondary & sec1, const Skill::Secondary & sec2, Heroes & hero ) { if ( Skill::Secondary::UNKNOWN == sec1.Skill() && Skill::Secondary::UNKNOWN == sec2.Skill() ) { DialogPrimaryOnly( name, primarySkillType ); return Skill::Secondary::UNKNOWN; } if ( Skill::Secondary::UNKNOWN == sec1.Skill() || Skill::Secondary::UNKNOWN == sec2.Skill() ) { return DialogOneSecondary( hero, name, primarySkillType, ( Skill::Secondary::UNKNOWN == sec2.Skill() ? sec1 : sec2 ) ); } return DialogSelectSecondary( name, primarySkillType, sec1, sec2, hero ); } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_marketplace.cpp000066400000000000000000000702221456075706000243220ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include "agg_image.h" #include "cursor.h" #include "dialog.h" #include "game_hotkeys.h" #include "icn.h" #include "image.h" #include "kingdom.h" #include "localevent.h" #include "math_base.h" #include "players.h" #include "resource.h" #include "resource_trading.h" #include "screen.h" #include "settings.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_kingdom.h" #include "ui_scrollbar.h" #include "ui_text.h" #include "ui_tool.h" #include "world.h" void RedrawFromResource( const fheroes2::Point &, const Funds & ); void RedrawToResource( const fheroes2::Point & pt, bool showcost, const Kingdom & kingdom, bool tradingPost, int from_resource = 0 ); std::string GetStringTradeCosts( const Kingdom & kingdom, int rs_from, int rs_to, bool tradingPost ); uint32_t GetTradeCosts( const Kingdom & kingdom, int rs_from, int rs_to, bool tradingPost ); class TradeWindowGUI { public: explicit TradeWindowGUI( const fheroes2::Rect & rt ) : pos_rt( rt ) , back( fheroes2::Display::instance() ) , tradpostIcnId( Settings::Get().isEvilInterfaceEnabled() ? ICN::TRADPOSE : ICN::TRADPOST ) , textSell( fheroes2::Display::instance() ) , textBuy( fheroes2::Display::instance() ) , _singlePlayer( false ) { Settings & conf = Settings::Get(); back.update( rt.x - 5, rt.y + 15, rt.width + 10, 160 ); const bool isEvilInterface = conf.isEvilInterfaceEnabled(); const int tradeButtonIcnID = isEvilInterface ? ICN::BUTTON_SMALL_TRADE_EVIL : ICN::BUTTON_SMALL_TRADE_GOOD; const int giftButtonIcnID = isEvilInterface ? ICN::BTNGIFT_EVIL : ICN::BTNGIFT_GOOD; buttonGift.setICNInfo( giftButtonIcnID, 0, 1 ); buttonTrade.setICNInfo( tradeButtonIcnID, 0, 1 ); buttonLeft.setICNInfo( tradpostIcnId, 3, 4 ); buttonRight.setICNInfo( tradpostIcnId, 5, 6 ); const fheroes2::Sprite & spriteGift = fheroes2::AGG::GetICN( giftButtonIcnID, 0 ); const fheroes2::Sprite & spriteTrade = fheroes2::AGG::GetICN( tradeButtonIcnID, 0 ); buttonGift.setPosition( pos_rt.x - 68 + ( pos_rt.width - spriteGift.width() ) / 2, pos_rt.y + pos_rt.height - spriteGift.height() ); buttonTrade.setPosition( pos_rt.x + ( pos_rt.width - spriteTrade.width() ) / 2, pos_rt.y + 150 ); buttonLeft.setPosition( pos_rt.x + 11, pos_rt.y + 129 ); buttonRight.setPosition( pos_rt.x + 220, pos_rt.y + 129 ); _scrollbar.setImage( fheroes2::AGG::GetICN( tradpostIcnId, 2 ) ); _scrollbar.setArea( { pos_rt.x + ( pos_rt.width - fheroes2::AGG::GetICN( tradpostIcnId, 1 ).width() ) / 2 + 22, pos_rt.y + 131, 187, 11 } ); _scrollbar.hide(); const fheroes2::Text text( _( "Please inspect our fine wares. If you feel like offering a trade, click on the items you wish to trade with and for." ), fheroes2::FontType::normalWhite() ); text.draw( pos_rt.x, pos_rt.y + 32, pos_rt.width, fheroes2::Display::instance() ); const Players & players = conf.GetPlayers(); int playerCount = 0; for ( const Player * player : players ) { if ( player != nullptr ) { const Kingdom & kingdom = world.GetKingdom( player->GetColor() ); if ( kingdom.isPlay() ) ++playerCount; } } _singlePlayer = playerCount == 1; } void RedrawInfoBuySell( uint32_t count_sell, uint32_t count_buy, uint32_t max_sell, uint32_t orig_buy ); void ShowTradeArea( const Kingdom & kingdom, int resourceFrom, int resourceTo, uint32_t max_buy, uint32_t max_sell, uint32_t count_buy, uint32_t count_sell, const bool fromTradingPost, const bool firstExchange ); fheroes2::Rect buttonMax; fheroes2::Rect buttonMin; fheroes2::Button buttonTrade; fheroes2::Button buttonLeft; fheroes2::Button buttonRight; fheroes2::Button buttonGift; fheroes2::Scrollbar _scrollbar; private: fheroes2::Rect pos_rt; fheroes2::ImageRestorer back; const int tradpostIcnId; fheroes2::MovableText textSell; fheroes2::MovableText textBuy; bool _singlePlayer; }; void TradeWindowGUI::ShowTradeArea( const Kingdom & kingdom, int resourceFrom, int resourceTo, uint32_t max_buy, uint32_t max_sell, uint32_t count_buy, uint32_t count_sell, const bool fromTradingPost, const bool firstExchange ) { fheroes2::Display & display = fheroes2::Display::instance(); bool disable = kingdom.GetFunds().Get( resourceFrom ) <= 0; if ( disable || resourceFrom == resourceTo || ( Resource::GOLD != resourceTo && 0 == max_buy ) ) { _scrollbar.hide(); back.restore(); fheroes2::Rect dst_rt( pos_rt.x, pos_rt.y + 30, pos_rt.width, 100 ); std::string message = firstExchange && ( resourceFrom == resourceTo || 0 == max_buy ) ? _( "Please inspect our fine wares. If you feel like offering a trade, click on the items you wish to trade with and for." ) : _( "You have received quite a bargain. I expect to make no profit on the deal. Can I interest you in any of my other wares?" ); const fheroes2::Text displayMessage( std::move( message ), fheroes2::FontType::normalWhite() ); displayMessage.draw( dst_rt.x, dst_rt.y + 2, dst_rt.width, display ); if ( !_singlePlayer ) { buttonGift.enable(); } buttonTrade.disable(); buttonLeft.disable(); buttonRight.disable(); buttonGift.draw(); buttonMax = fheroes2::Rect(); buttonMin = fheroes2::Rect(); } else { back.restore(); const fheroes2::Sprite & bar = fheroes2::AGG::GetICN( tradpostIcnId, 1 ); fheroes2::Point dst_pt( pos_rt.x + ( pos_rt.width - bar.width() ) / 2 - 2, pos_rt.y + 128 ); fheroes2::Blit( bar, display, dst_pt.x, dst_pt.y ); const uint32_t maximumValue = ( Resource::GOLD == resourceTo ) ? max_sell : max_buy; const fheroes2::Sprite & originalSlider = fheroes2::AGG::GetICN( tradpostIcnId, 2 ); const fheroes2::Image scrollbarSlider = fheroes2::generateScrollbarSlider( originalSlider, true, 187, 1, static_cast( maximumValue + 1 ), { 0, 0, 2, originalSlider.height() }, { 2, 0, 8, originalSlider.height() } ); _scrollbar.setImage( scrollbarSlider ); _scrollbar.setRange( 0, maximumValue ); const uint32_t exchange_rate = GetTradeCosts( kingdom, resourceFrom, resourceTo, fromTradingPost ); std::string message; if ( Resource::GOLD == resourceTo ) { message = _( "I can offer you %{count} for 1 unit of %{resfrom}." ); StringReplace( message, "%{count}", exchange_rate ); StringReplace( message, "%{resfrom}", Resource::String( resourceFrom ) ); } else { message = _( "I can offer you 1 unit of %{resto} for %{count} units of %{resfrom}." ); StringReplace( message, "%{resto}", Resource::String( resourceTo ) ); StringReplace( message, "%{resfrom}", Resource::String( resourceFrom ) ); StringReplace( message, "%{count}", exchange_rate ); } const fheroes2::Text displayMessage( std::move( message ), fheroes2::FontType::normalWhite() ); displayMessage.draw( pos_rt.x, pos_rt.y + 32, pos_rt.width, display ); const fheroes2::Sprite & sprite_from = fheroes2::AGG::GetICN( ICN::RESOURCE, Resource::getIconIcnIndex( resourceFrom ) ); dst_pt.x = pos_rt.x + ( pos_rt.width - sprite_from.width() + 1 ) / 2 - 70; dst_pt.y = pos_rt.y + 115 - sprite_from.height(); fheroes2::Blit( sprite_from, display, dst_pt.x, dst_pt.y ); const fheroes2::Sprite & sprite_to = fheroes2::AGG::GetICN( ICN::RESOURCE, Resource::getIconIcnIndex( resourceTo ) ); dst_pt.x = pos_rt.x + ( pos_rt.width - sprite_to.width() + 1 ) / 2 + 70; dst_pt.y = pos_rt.y + 115 - sprite_to.height(); fheroes2::Blit( sprite_to, display, dst_pt.x, dst_pt.y ); const fheroes2::Sprite & sprite_fromto = fheroes2::AGG::GetICN( tradpostIcnId, 0 ); dst_pt.x = pos_rt.x + ( pos_rt.width - sprite_fromto.width() ) / 2; dst_pt.y = pos_rt.y + 90; fheroes2::Blit( sprite_fromto, display, dst_pt.x, dst_pt.y ); fheroes2::Text text( _( "Max" ), fheroes2::FontType::smallYellow() ); dst_pt.x = pos_rt.x + ( pos_rt.width - text.width() ) / 2 - 5; dst_pt.y = pos_rt.y + 80; buttonMax = fheroes2::Rect( dst_pt.x, dst_pt.y, text.width(), text.height() ); text.draw( dst_pt.x, dst_pt.y + 2, display ); text.set( _( "Min" ), fheroes2::FontType::smallYellow() ); dst_pt.x = pos_rt.x + ( pos_rt.width - text.width() ) / 2 - 5; dst_pt.y = pos_rt.y + 103; buttonMin = fheroes2::Rect( dst_pt.x, dst_pt.y, text.width(), text.height() ); text.draw( dst_pt.x, dst_pt.y + 2, display ); text.set( _( "Qty to trade" ), fheroes2::FontType::smallWhite() ); dst_pt.x = pos_rt.x + ( pos_rt.width - text.width() ) / 2; dst_pt.y = pos_rt.y + 115; text.draw( dst_pt.x, dst_pt.y + 2, display ); buttonGift.enable(); buttonTrade.enable(); buttonLeft.enable(); buttonRight.enable(); buttonTrade.draw(); buttonLeft.draw(); buttonRight.draw(); RedrawInfoBuySell( count_sell, count_buy, max_sell, kingdom.GetFunds().Get( resourceTo ) ); _scrollbar.show(); } display.render(); } void TradeWindowGUI::RedrawInfoBuySell( uint32_t count_sell, uint32_t count_buy, uint32_t max_sell, uint32_t orig_buy ) { fheroes2::Point dst_pt; _scrollbar.hide(); auto text = std::make_unique( std::string( "-" ) + std::to_string( count_sell ) + " " + "(" + std::to_string( max_sell - count_sell ) + ")", fheroes2::FontType::smallWhite() ); int32_t textWidth = text->width(); textSell.update( std::move( text ) ); dst_pt.x = pos_rt.x + pos_rt.width / 2 - 70 - ( textWidth + 1 ) / 2; dst_pt.y = pos_rt.y + 116; textSell.draw( dst_pt.x, dst_pt.y ); text = std::make_unique( std::string( "+" ) + std::to_string( count_buy ) + " " + "(" + std::to_string( orig_buy + count_buy ) + ")", fheroes2::FontType::smallWhite() ); textWidth = text->width(); textBuy.update( std::move( text ) ); dst_pt.x = pos_rt.x + pos_rt.width / 2 + 70 - ( textWidth + 1 ) / 2; dst_pt.y = pos_rt.y + 116; textBuy.draw( dst_pt.x, dst_pt.y ); _scrollbar.show(); } void Dialog::Marketplace( Kingdom & kingdom, bool fromTradingPost ) { fheroes2::Display & display = fheroes2::Display::instance(); const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); const int tradpost = isEvilInterface ? ICN::TRADPOSE : ICN::TRADPOST; const std::string & header = fromTradingPost ? _( "Trading Post" ) : _( "Marketplace" ); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); Dialog::FrameBox box( 297, true ); const fheroes2::Rect & pos_rt = box.GetArea(); fheroes2::Point dst_pt( pos_rt.x, pos_rt.y ); // header fheroes2::Text text{ header, fheroes2::FontType::normalYellow() }; dst_pt.x = pos_rt.x + ( pos_rt.width - text.width() ) / 2; dst_pt.y = pos_rt.y; text.draw( dst_pt.x, dst_pt.y + 2, display ); TradeWindowGUI gui( pos_rt ); Funds fundsFrom = kingdom.GetFunds(); int resourceFrom = 0; const fheroes2::Point pt1( pos_rt.x, pos_rt.y + 190 ); std::vector rectsFrom; rectsFrom.reserve( 7 ); rectsFrom.emplace_back( pt1.x, pt1.y, 34, 34 ); // wood rectsFrom.emplace_back( pt1.x + 37, pt1.y, 34, 34 ); // mercury rectsFrom.emplace_back( pt1.x + 74, pt1.y, 34, 34 ); // ore rectsFrom.emplace_back( pt1.x, pt1.y + 37, 34, 34 ); // sulfur rectsFrom.emplace_back( pt1.x + 37, pt1.y + 37, 34, 34 ); // crystal rectsFrom.emplace_back( pt1.x + 74, pt1.y + 37, 34, 34 ); // gems rectsFrom.emplace_back( pt1.x + 37, pt1.y + 74, 34, 34 ); // gold fheroes2::MovableSprite cursorFrom( fheroes2::AGG::GetICN( tradpost, 14 ) ); text.set( _( "Your Resources" ), fheroes2::FontType::smallWhite() ); dst_pt.x = pt1.x + ( 108 - text.width() ) / 2; dst_pt.y = pt1.y - 15; text.draw( dst_pt.x, dst_pt.y + 2, display ); RedrawFromResource( pt1, fundsFrom ); Funds fundsTo; int resourceTo = 0; const fheroes2::Point pt2( 136 + pos_rt.x, pos_rt.y + 190 ); std::vector rectsTo; rectsTo.reserve( 7 ); rectsTo.emplace_back( pt2.x, pt2.y, 34, 34 ); // wood rectsTo.emplace_back( pt2.x + 37, pt2.y, 34, 34 ); // mercury rectsTo.emplace_back( pt2.x + 74, pt2.y, 34, 34 ); // ore rectsTo.emplace_back( pt2.x, pt2.y + 37, 34, 34 ); // sulfur rectsTo.emplace_back( pt2.x + 37, pt2.y + 37, 34, 34 ); // crystal rectsTo.emplace_back( pt2.x + 74, pt2.y + 37, 34, 34 ); // gems rectsTo.emplace_back( pt2.x + 37, pt2.y + 74, 34, 34 ); // gold fheroes2::MovableSprite cursorTo( fheroes2::AGG::GetICN( tradpost, 14 ) ); text.set( _( "Available Trades" ), fheroes2::FontType::smallWhite() ); dst_pt.x = pt2.x + ( 108 - text.width() ) / 2; dst_pt.y = pt2.y - 15; text.draw( dst_pt.x, dst_pt.y + 2, display ); RedrawToResource( pt2, false, kingdom, fromTradingPost ); uint32_t count_sell = 0; uint32_t count_buy = 0; uint32_t max_sell = 0; uint32_t max_buy = 0; const fheroes2::Rect & buttonMax = gui.buttonMax; const fheroes2::Rect & buttonMin = gui.buttonMin; fheroes2::Button & buttonGift = gui.buttonGift; fheroes2::Button & buttonTrade = gui.buttonTrade; fheroes2::Button & buttonLeft = gui.buttonLeft; fheroes2::Button & buttonRight = gui.buttonRight; fheroes2::TimedEventValidator timedButtonLeft( [&buttonLeft]() { return buttonLeft.isPressed(); } ); fheroes2::TimedEventValidator timedButtonRight( [&buttonRight]() { return buttonRight.isPressed(); } ); buttonLeft.subscribe( &timedButtonLeft ); buttonRight.subscribe( &timedButtonRight ); fheroes2::Scrollbar & scrollbar = gui._scrollbar; // button exit const int exitButtonIcnID = isEvilInterface ? ICN::UNIFORM_EVIL_EXIT_BUTTON : ICN::UNIFORM_GOOD_EXIT_BUTTON; const fheroes2::Sprite & spriteExit = fheroes2::AGG::GetICN( exitButtonIcnID, 0 ); dst_pt.x = pos_rt.x + 68 + ( pos_rt.width - spriteExit.width() ) / 2; dst_pt.y = pos_rt.y + pos_rt.height - spriteExit.height(); fheroes2::Button buttonExit( dst_pt.x, dst_pt.y, exitButtonIcnID, 0, 1 ); buttonGift.draw(); buttonExit.draw(); buttonTrade.disable(); display.render(); LocalEvent & le = LocalEvent::Get(); bool firstExchange = true; // message loop while ( le.HandleEvents() ) { if ( buttonGift.isEnabled() ) le.MousePressLeft( buttonGift.area() ) ? buttonGift.drawOnPress() : buttonGift.drawOnRelease(); if ( buttonTrade.isEnabled() ) le.MousePressLeft( buttonTrade.area() ) ? buttonTrade.drawOnPress() : buttonTrade.drawOnRelease(); if ( buttonLeft.isEnabled() ) le.MousePressLeft( buttonLeft.area() ) ? buttonLeft.drawOnPress() : buttonLeft.drawOnRelease(); if ( buttonRight.isEnabled() ) le.MousePressLeft( buttonRight.area() ) ? buttonRight.drawOnPress() : buttonRight.drawOnRelease(); le.MousePressLeft( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) break; // gift resources if ( buttonGift.isEnabled() && le.MouseClickLeft( buttonGift.area() ) ) { Dialog::MakeGiftResource( kingdom ); resourceTo = Resource::UNKNOWN; resourceFrom = Resource::UNKNOWN; gui.ShowTradeArea( kingdom, resourceFrom, resourceTo, 0, 0, 0, 0, fromTradingPost, firstExchange ); cursorTo.hide(); cursorFrom.hide(); fundsFrom = kingdom.GetFunds(); RedrawFromResource( pt1, fundsFrom ); display.render(); } // click from for ( uint32_t ii = 0; ii < rectsFrom.size(); ++ii ) { const fheroes2::Rect & rect_from = rectsFrom[ii]; if ( le.MouseClickLeft( rect_from ) ) { resourceFrom = Resource::getResourceTypeFromIconIndex( ii ); max_sell = fundsFrom.Get( resourceFrom ); const uint32_t tradeCost = GetTradeCosts( kingdom, resourceFrom, resourceTo, fromTradingPost ); if ( tradeCost ) { max_buy = Resource::GOLD == resourceTo ? max_sell * tradeCost : max_sell / tradeCost; } count_sell = 0; count_buy = 0; cursorFrom.show(); cursorFrom.setPosition( rect_from.x - 2, rect_from.y - 2 ); if ( resourceTo ) { cursorTo.hide(); } RedrawToResource( pt2, true, kingdom, fromTradingPost, resourceFrom ); if ( resourceTo ) { cursorTo.show(); gui.ShowTradeArea( kingdom, resourceFrom, resourceTo, max_buy, max_sell, count_buy, count_sell, fromTradingPost, firstExchange ); } display.render(); } else if ( le.MousePressRight( rect_from ) ) { fheroes2::showKingdomIncome( kingdom, 0 ); } } // click to for ( uint32_t ii = 0; ii < rectsTo.size(); ++ii ) { const fheroes2::Rect & rect_to = rectsTo[ii]; if ( le.MouseClickLeft( rect_to ) ) { resourceTo = Resource::getResourceTypeFromIconIndex( ii ); const uint32_t tradeCost = GetTradeCosts( kingdom, resourceFrom, resourceTo, fromTradingPost ); if ( tradeCost ) { max_buy = Resource::GOLD == resourceTo ? max_sell * tradeCost : max_sell / tradeCost; } count_sell = 0; count_buy = 0; cursorTo.show(); cursorTo.setPosition( rect_to.x - 2, rect_to.y - 2 ); if ( resourceFrom ) { cursorTo.hide(); RedrawToResource( pt2, true, kingdom, fromTradingPost, resourceFrom ); cursorTo.show(); gui.ShowTradeArea( kingdom, resourceFrom, resourceTo, max_buy, max_sell, count_buy, count_sell, fromTradingPost, firstExchange ); } display.render(); } } // Scrollbar if ( buttonLeft.isEnabled() && buttonRight.isEnabled() && max_buy && le.MousePressLeft( scrollbar.getArea() ) ) { const fheroes2::Point & mousePos = le.GetMouseCursor(); scrollbar.moveToPos( mousePos ); const int32_t seek = scrollbar.currentIndex(); count_buy = seek * ( Resource::GOLD == resourceTo ? GetTradeCosts( kingdom, resourceFrom, resourceTo, fromTradingPost ) : 1 ); count_sell = seek * ( Resource::GOLD == resourceTo ? 1 : GetTradeCosts( kingdom, resourceFrom, resourceTo, fromTradingPost ) ); gui.RedrawInfoBuySell( count_sell, count_buy, max_sell, fundsFrom.Get( resourceTo ) ); display.render(); } else if ( scrollbar.updatePosition() ) { display.render(); } // click max if ( buttonMax.width && max_buy && le.MouseClickLeft( buttonMax ) ) { const int32_t max = scrollbar.maxIndex(); count_buy = max * ( Resource::GOLD == resourceTo ? GetTradeCosts( kingdom, resourceFrom, resourceTo, fromTradingPost ) : 1 ); count_sell = max * ( Resource::GOLD == resourceTo ? 1 : GetTradeCosts( kingdom, resourceFrom, resourceTo, fromTradingPost ) ); scrollbar.moveToIndex( max ); gui.RedrawInfoBuySell( count_sell, count_buy, max_sell, fundsFrom.Get( resourceTo ) ); display.render(); } // click min if ( buttonMin.width && max_buy && le.MouseClickLeft( buttonMin ) ) { const int32_t min = 1; count_buy = min * ( Resource::GOLD == resourceTo ? GetTradeCosts( kingdom, resourceFrom, resourceTo, fromTradingPost ) : 1 ); count_sell = min * ( Resource::GOLD == resourceTo ? 1 : GetTradeCosts( kingdom, resourceFrom, resourceTo, fromTradingPost ) ); scrollbar.moveToIndex( min ); gui.RedrawInfoBuySell( count_sell, count_buy, max_sell, fundsFrom.Get( resourceTo ) ); display.render(); } // trade if ( buttonTrade.isEnabled() && le.MouseClickLeft( buttonTrade.area() ) && count_sell && count_buy ) { kingdom.OddFundsResource( Funds( resourceFrom, count_sell ) ); kingdom.AddFundsResource( Funds( resourceTo, count_buy ) ); firstExchange = false; resourceTo = Resource::UNKNOWN; resourceFrom = Resource::UNKNOWN; gui.ShowTradeArea( kingdom, resourceFrom, resourceTo, 0, 0, 0, 0, fromTradingPost, firstExchange ); fundsFrom = kingdom.GetFunds(); cursorTo.hide(); cursorFrom.hide(); RedrawFromResource( pt1, fundsFrom ); RedrawToResource( pt2, false, kingdom, fromTradingPost, resourceFrom ); display.render(); } // decrease trade resource if ( count_buy && ( ( buttonLeft.isEnabled() && ( le.MouseClickLeft( gui.buttonLeft.area() ) || timedButtonLeft.isDelayPassed() ) ) || le.MouseWheelDn( scrollbar.getArea() ) ) ) { count_buy -= Resource::GOLD == resourceTo ? GetTradeCosts( kingdom, resourceFrom, resourceTo, fromTradingPost ) : 1; count_sell -= Resource::GOLD == resourceTo ? 1 : GetTradeCosts( kingdom, resourceFrom, resourceTo, fromTradingPost ); scrollbar.backward(); gui.RedrawInfoBuySell( count_sell, count_buy, max_sell, fundsFrom.Get( resourceTo ) ); display.render(); } // increase trade resource if ( count_buy < max_buy && ( ( buttonRight.isEnabled() && ( le.MouseClickLeft( buttonRight.area() ) || timedButtonRight.isDelayPassed() ) ) || le.MouseWheelUp( scrollbar.getArea() ) ) ) { count_buy += Resource::GOLD == resourceTo ? GetTradeCosts( kingdom, resourceFrom, resourceTo, fromTradingPost ) : 1; count_sell += Resource::GOLD == resourceTo ? 1 : GetTradeCosts( kingdom, resourceFrom, resourceTo, fromTradingPost ); scrollbar.forward(); gui.RedrawInfoBuySell( count_sell, count_buy, max_sell, fundsFrom.Get( resourceTo ) ); display.render(); } } } void RedrawResourceSprite( const fheroes2::Image & sf, int32_t px, int32_t py, int32_t value ) { fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::Blit( sf, display, px, py ); const fheroes2::Text text( std::to_string( value ), fheroes2::FontType::smallWhite() ); text.draw( px + ( 34 - text.width() ) / 2, py + 23, display ); } void RedrawFromResource( const fheroes2::Point & pt, const Funds & rs ) { const int tradpost = Settings::Get().isEvilInterfaceEnabled() ? ICN::TRADPOSE : ICN::TRADPOST; // wood RedrawResourceSprite( fheroes2::AGG::GetICN( tradpost, 7 ), pt.x, pt.y, rs.wood ); // mercury RedrawResourceSprite( fheroes2::AGG::GetICN( tradpost, 8 ), pt.x + 37, pt.y, rs.mercury ); // ore RedrawResourceSprite( fheroes2::AGG::GetICN( tradpost, 9 ), pt.x + 74, pt.y, rs.ore ); // sulfur RedrawResourceSprite( fheroes2::AGG::GetICN( tradpost, 10 ), pt.x, pt.y + 37, rs.sulfur ); // crystal RedrawResourceSprite( fheroes2::AGG::GetICN( tradpost, 11 ), pt.x + 37, pt.y + 37, rs.crystal ); // gems RedrawResourceSprite( fheroes2::AGG::GetICN( tradpost, 12 ), pt.x + 74, pt.y + 37, rs.gems ); // gold RedrawResourceSprite( fheroes2::AGG::GetICN( tradpost, 13 ), pt.x + 37, pt.y + 74, rs.gold ); } void RedrawResourceSprite2( const fheroes2::Image & sf, int32_t px, int32_t py, bool show, const Kingdom & kingdom, int from, int res, bool trading ) { fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::Blit( sf, display, px, py ); if ( show ) { const fheroes2::Text text( GetStringTradeCosts( kingdom, from, res, trading ), fheroes2::FontType::smallWhite() ); text.draw( px + ( 34 - text.width() ) / 2, py + 23, display ); } } void RedrawToResource( const fheroes2::Point & pt, bool showcost, const Kingdom & kingdom, bool tradingPost, int from_resource ) { const int tradpost = Settings::Get().isEvilInterfaceEnabled() ? ICN::TRADPOSE : ICN::TRADPOST; // wood RedrawResourceSprite2( fheroes2::AGG::GetICN( tradpost, 7 ), pt.x, pt.y, showcost, kingdom, from_resource, Resource::WOOD, tradingPost ); // mercury RedrawResourceSprite2( fheroes2::AGG::GetICN( tradpost, 8 ), pt.x + 37, pt.y, showcost, kingdom, from_resource, Resource::MERCURY, tradingPost ); // ore RedrawResourceSprite2( fheroes2::AGG::GetICN( tradpost, 9 ), pt.x + 74, pt.y, showcost, kingdom, from_resource, Resource::ORE, tradingPost ); // sulfur RedrawResourceSprite2( fheroes2::AGG::GetICN( tradpost, 10 ), pt.x, pt.y + 37, showcost, kingdom, from_resource, Resource::SULFUR, tradingPost ); // crystal RedrawResourceSprite2( fheroes2::AGG::GetICN( tradpost, 11 ), pt.x + 37, pt.y + 37, showcost, kingdom, from_resource, Resource::CRYSTAL, tradingPost ); // gems RedrawResourceSprite2( fheroes2::AGG::GetICN( tradpost, 12 ), pt.x + 74, pt.y + 37, showcost, kingdom, from_resource, Resource::GEMS, tradingPost ); // gold RedrawResourceSprite2( fheroes2::AGG::GetICN( tradpost, 13 ), pt.x + 37, pt.y + 74, showcost, kingdom, from_resource, Resource::GOLD, tradingPost ); } std::string GetStringTradeCosts( const Kingdom & kingdom, int rs_from, int rs_to, bool tradingPost ) { std::string res; if ( rs_from == rs_to ) { res = _( "n/a" ); } else { res = "1/"; res.append( std::to_string( GetTradeCosts( kingdom, rs_from, rs_to, tradingPost ) ) ); } return res; } uint32_t GetTradeCosts( const Kingdom & kingdom, int rs_from, int rs_to, bool tradingPost ) { return fheroes2::getTradeCost( ( tradingPost ? 3 : kingdom.GetCountMarketplace() ), rs_from, rs_to ); } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_quickinfo.cpp000066400000000000000000001036651456075706000240320ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include "agg_image.h" #include "army.h" #include "army_troop.h" #include "artifact_ultimate.h" #include "captain.h" #include "castle.h" #include "color.h" #include "cursor.h" #include "dialog.h" #include "game.h" #include "game_interface.h" #include "ground.h" #include "heroes.h" #include "heroes_base.h" #include "icn.h" #include "image.h" #include "interface_base.h" #include "interface_gamearea.h" #include "kingdom.h" #include "localevent.h" #include "logging.h" #include "maps.h" #include "maps_tiles.h" #include "maps_tiles_helper.h" #include "math_base.h" #include "mp2.h" #include "profit.h" #include "resource.h" #include "screen.h" #include "settings.h" #include "skill.h" #include "spell.h" #include "tools.h" #include "translations.h" #include "ui_castle.h" #include "ui_map_interface.h" #include "ui_text.h" #include "world.h" namespace { void outputInTextSupportMode( const Maps::Tiles & tile, const std::string & info ) { START_TEXT_SUPPORT_MODE const int tileIndex = tile.GetIndex(); const int mapWidth = world.w(); const int x = tileIndex % mapWidth; const int y = tileIndex / mapWidth; COUT( "[" << x + 1 << ", " << y + 1 << "]" ) COUT( info ) } class RadarUpdater { public: RadarUpdater( const bool performUpdate, const fheroes2::Point & updatedPosition, const fheroes2::Rect & areaToRestore ) : _performUpdate( performUpdate ) , _updatedPosition( updatedPosition ) , _prevPosition( Interface::AdventureMap::Get().getGameArea().getCurrentCenterInPixels() ) , _restorer( fheroes2::Display::instance(), areaToRestore.x, areaToRestore.y, areaToRestore.width, areaToRestore.height ) { if ( !_performUpdate || _updatedPosition == _prevPosition ) { return; } Interface::AdventureMap & iface = Interface::AdventureMap::Get(); iface.getGameArea().SetCenter( updatedPosition ); iface.redraw( Interface::REDRAW_RADAR_CURSOR ); _restorer.restore(); } void restore() { if ( !_performUpdate || _updatedPosition == _prevPosition ) { return; } Interface::AdventureMap & iface = Interface::AdventureMap::Get(); iface.getGameArea().SetCenterInPixels( _prevPosition ); iface.redraw( Interface::REDRAW_RADAR_CURSOR ); _restorer.restore(); } private: const bool _performUpdate; const fheroes2::Point _updatedPosition; const fheroes2::Point _prevPosition; fheroes2::ImageRestorer _restorer; }; std::string getMineIncomeString( const int32_t resourceType ) { const Funds income = ProfitConditions::FromMine( resourceType ); const int32_t value = income.Get( resourceType ); std::string res; if ( value == 0 ) { return res; } res += ' '; res += '('; res += ( value > 0 ? '+' : '-' ); res.append( std::to_string( value ) ); res += ')'; return res; } std::string showMineInfo( const Maps::Tiles & tile, const bool isOwned ) { const int32_t resourceType = getDailyIncomeObjectResources( tile ).getFirstValidResource().first; std::string objectInfo = Maps::GetMineName( resourceType ); if ( isOwned ) { // TODO: we should use the value from funds. objectInfo.append( getMineIncomeString( resourceType ) ); } return objectInfo; } std::string showGuardiansInfo( const Maps::Tiles & tile, const bool isOwned ) { const MP2::MapObjectType objectType = tile.GetObject( false ); std::string str; if ( objectType == MP2::OBJ_MINE ) { str = showMineInfo( tile, isOwned ); } else { str = MP2::StringObject( objectType ); } const Troop & troop = getTroopFromTile( tile ); if ( troop.isValid() ) { str.append( "\n\n" ); if ( isOwned ) { str.append( _( "guarded by %{count} %{monster}" ) ); StringReplaceWithLowercase( str, "%{count}", Game::formatMonsterCount( troop.GetCount(), true ) ); if ( troop.GetCount() == 1 ) { StringReplaceWithLowercase( str, "%{monster}", troop.GetName() ); } else { StringReplaceWithLowercase( str, "%{monster}", troop.GetMultiName() ); } } else { str.append( _( "guarded by " ) ).append( Translation::StringLower( Army::TroopSizeString( troop ) ) ); } } return str; } std::string showMonsterInfo( const Maps::Tiles & tile, const bool isVisibleFromCrystalBall ) { const Troop & troop = getTroopFromTile( tile ); if ( isVisibleFromCrystalBall ) { std::string str = "%{count} %{monster}"; StringReplace( str, "%{count}", Game::formatMonsterCount( troop.GetCount(), true ) ); if ( troop.GetCount() == 1 ) { StringReplaceWithLowercase( str, "%{monster}", troop.GetName() ); } else { StringReplaceWithLowercase( str, "%{monster}", troop.GetMultiName() ); } return str; } return Army::TroopSizeString( troop ); } std::string showDwellingInfo( const Maps::Tiles & tile, const bool isOwned ) { std::string str = MP2::StringObject( tile.GetObject( false ) ); if ( isOwned ) { str += "\n\n"; const Troop & troop = getTroopFromTile( tile ); if ( troop.isValid() ) { str.append( _( "(available: %{count})" ) ); StringReplace( str, "%{count}", Game::formatMonsterCount( troop.GetCount(), true ) ); } else { str.append( _( "(empty)" ) ); } } return str; } std::string showShrineInfo( const Maps::Tiles & tile, const bool isVisited ) { const MP2::MapObjectType objectType = tile.GetObject( false ); std::string str = MP2::StringObject( objectType ); if ( isVisited ) { const Spell & spell = getSpellFromTile( tile ); str.append( "\n\n(" ); str.append( spell.GetName() ); str += ')'; const Heroes * hero = Interface::GetFocusHeroes(); if ( hero && hero->HaveSpell( spell ) ) { str.append( "\n(" ); str.append( _( "already learned" ) ); str += ')'; } } return str; } std::string showTreeOfKnowledgeInfo( const Maps::Tiles & tile, const bool isVisited ) { const MP2::MapObjectType objectType = tile.GetObject( false ); std::string str = MP2::StringObject( objectType ); const Heroes * hero = Interface::GetFocusHeroes(); if ( isVisited ) { const Funds & payment = getTreeOfKnowledgeRequirement( tile ); str.append( "\n\n(" ); if ( payment.GetValidItemsCount() == 0 ) { str.append( _( "treeOfKnowledge|free" ) ); } else { const auto rc = payment.getFirstValidResource(); str.append( std::to_string( rc.second ) ); str += ' '; str.append( Translation::StringLower( Resource::String( rc.first ) ) ); } str += ')'; if ( hero ) { str.append( "\n(" ); str.append( hero->isVisited( tile ) ? _( "already claimed" ) : _( "not claimed" ) ); str += ')'; } } else { if ( hero ) { str.append( "\n\n(" ); str.append( _( "not claimed" ) ); str += ')'; } } return str; } std::string showWitchHutInfo( const Maps::Tiles & tile, const bool isVisited ) { std::string str = MP2::StringObject( tile.GetObject( false ) ); if ( isVisited ) { const Skill::Secondary & skill = getSecondarySkillFromWitchsHut( tile ); str.append( "\n\n(" ); str.append( Skill::Secondary::String( skill.Skill() ) ); str += ')'; const Heroes * hero = Interface::GetFocusHeroes(); if ( hero ) { const char * heroStr = nullptr; if ( hero->HasSecondarySkill( skill.Skill() ) ) { heroStr = ( _( "already knows this skill" ) ); } else if ( hero->HasMaxSecondarySkill() ) { heroStr = ( _( "already has max skills" ) ); } if ( heroStr != nullptr ) { str.append( "\n(" ); str.append( heroStr ); str += ')'; } } } return str; } std::string showLocalVisitTileInfo( const Maps::Tiles & tile ) { std::string str = MP2::StringObject( tile.GetObject( false ) ); const Heroes * hero = Interface::GetFocusHeroes(); if ( hero ) { str.append( "\n\n" ); str.append( hero->isVisited( tile ) ? _( "(already visited)" ) : _( "(not visited)" ) ); } return str; } std::string showLocalVisitObjectInfo( const MP2::MapObjectType objectType ) { std::string str = MP2::StringObject( objectType ); const Heroes * hero = Interface::GetFocusHeroes(); if ( hero ) { str.append( "\n\n" ); str.append( hero->isObjectTypeVisited( objectType ) ? _( "(already visited)" ) : _( "(not visited)" ) ); } return str; } std::string showObjectVisitInfo( const MP2::MapObjectType objectType, const bool isVisited ) { std::string str = MP2::StringObject( objectType ); str.append( "\n\n" ); str.append( isVisited ? _( "(already visited)" ) : _( "(not visited)" ) ); return str; } std::string showBarrierInfo( const Maps::Tiles & tile, const Kingdom & kingdom ) { std::string str = _( "%{color} Barrier" ); const int32_t barrierColor = getColorFromTile( tile ); StringReplace( str, "%{color}", fheroes2::getBarrierColorName( barrierColor ) ); if ( kingdom.IsVisitTravelersTent( barrierColor ) ) { str.append( "\n\n" ); str.append( _( "(tent visited)" ) ); } return str; } std::string showTentInfo( const Maps::Tiles & tile, const Kingdom & kingdom ) { std::string str = _( "%{color} Tent" ); const int32_t tentColor = getColorFromTile( tile ); StringReplace( str, "%{color}", fheroes2::getTentColorName( tentColor ) ); if ( kingdom.IsVisitTravelersTent( tentColor ) ) { str.append( "\n\n" ); str.append( _( "(already visited)" ) ); } return str; } std::string showGroundInfo( const Maps::Tiles & tile ) { const MP2::MapObjectType objectType = tile.GetObject( false ); const bool isRoad = tile.isRoad(); std::string str; if ( objectType == MP2::OBJ_COAST ) { str = MP2::StringObject( objectType ); } else if ( isRoad ) { str = _( "Road" ); } else { str = Maps::Ground::String( tile.GetGround() ); } str.append( "\n\n" ); // Original Editor allows to put an Ultimate Artifact on an invalid tile. So checking tile index solves this issue. if ( tile.GoodForUltimateArtifact() || world.GetUltimateArtifact().getPosition() == tile.GetIndex() ) { str.append( _( "(digging ok)" ) ); } else { str.append( _( "(no digging)" ) ); } const Heroes * hero = Interface::GetFocusHeroes(); if ( hero ) { const uint32_t cost = isRoad ? Maps::Ground::roadPenalty : Maps::Ground::GetPenalty( tile, hero->GetLevelSkill( Skill::Secondary::PATHFINDING ) ); if ( cost > 0 ) { str += '\n'; str.append( _( "penalty: %{cost}" ) ); StringReplace( str, "%{cost}", static_cast( cost ) ); } } return str; } fheroes2::Rect makeRectQuickInfo( const LocalEvent & le, const fheroes2::Sprite & imageBox, const fheroes2::Point & position = fheroes2::Point() ) { if ( position.x > 0 && position.y > 0 ) { return { position.x - imageBox.width(), position.y, imageBox.width(), imageBox.height() }; } return Interface::getPopupWindowPosition( le.GetMouseCursor(), Interface::AdventureMap::Get().getGameArea().GetROI(), { imageBox.width(), imageBox.height() } ); } std::string getQuickInfoText( const Maps::Tiles & tile ) { const int32_t playerColor = Settings::Get().CurrentColor(); const MP2::MapObjectType objectType = tile.GetObject( false ); if ( objectType == MP2::OBJ_ABANDONED_MINE || isCaptureObjectProtected( tile ) ) { return showGuardiansInfo( tile, playerColor == getColorFromTile( tile ) ); } const Kingdom & kingdom = world.GetKingdom( playerColor ); switch ( objectType ) { case MP2::OBJ_MONSTER: return showMonsterInfo( tile, kingdom.IsTileVisibleFromCrystalBall( tile.GetIndex() ) ); case MP2::OBJ_EVENT: case MP2::OBJ_NONE: case MP2::OBJ_COAST: return showGroundInfo( tile ); case MP2::OBJ_DERELICT_SHIP: case MP2::OBJ_SHIPWRECK: case MP2::OBJ_GRAVEYARD: case MP2::OBJ_DAEMON_CAVE: case MP2::OBJ_SPHINX: case MP2::OBJ_PYRAMID: case MP2::OBJ_WAGON: case MP2::OBJ_SKELETON: case MP2::OBJ_LEAN_TO: case MP2::OBJ_WINDMILL: case MP2::OBJ_WATER_WHEEL: case MP2::OBJ_MAGIC_GARDEN: case MP2::OBJ_ARTESIAN_SPRING: case MP2::OBJ_OBELISK: return showObjectVisitInfo( objectType, kingdom.isVisited( tile ) ); case MP2::OBJ_MAGELLANS_MAPS: return showObjectVisitInfo( objectType, kingdom.isVisited( objectType ) ); case MP2::OBJ_RESOURCE: { const Funds funds = getFundsFromTile( tile ); assert( funds.GetValidItemsCount() == 1 ); return Resource::String( funds.getFirstValidResource().first ); } case MP2::OBJ_MINE: return showMineInfo( tile, playerColor == getColorFromTile( tile ) ); case MP2::OBJ_ALCHEMIST_LAB: case MP2::OBJ_SAWMILL: { std::string objectInfo = MP2::StringObject( objectType ); if ( playerColor == getColorFromTile( tile ) ) { const Funds funds = getDailyIncomeObjectResources( tile ); assert( funds.GetValidItemsCount() == 1 ); // TODO: we should use the value from funds. objectInfo.append( getMineIncomeString( funds.getFirstValidResource().first ) ); } return objectInfo; } // join army case MP2::OBJ_WATCH_TOWER: case MP2::OBJ_EXCAVATION: case MP2::OBJ_CAVE: case MP2::OBJ_TREE_HOUSE: case MP2::OBJ_ARCHER_HOUSE: case MP2::OBJ_GOBLIN_HUT: case MP2::OBJ_DWARF_COTTAGE: case MP2::OBJ_HALFLING_HOLE: case MP2::OBJ_PEASANT_HUT: // recruit army case MP2::OBJ_RUINS: case MP2::OBJ_TREE_CITY: case MP2::OBJ_WAGON_CAMP: case MP2::OBJ_DESERT_TENT: // battle and recruit army case MP2::OBJ_DRAGON_CITY: case MP2::OBJ_CITY_OF_DEAD: case MP2::OBJ_TROLL_BRIDGE: case MP2::OBJ_BARROW_MOUNDS: case MP2::OBJ_AIR_ALTAR: case MP2::OBJ_FIRE_ALTAR: case MP2::OBJ_EARTH_ALTAR: case MP2::OBJ_WATER_ALTAR: return showDwellingInfo( tile, kingdom.isVisited( tile ) ); case MP2::OBJ_GAZEBO: case MP2::OBJ_FORT: case MP2::OBJ_XANADU: case MP2::OBJ_MERCENARY_CAMP: case MP2::OBJ_WITCH_DOCTORS_HUT: case MP2::OBJ_STANDING_STONES: return showLocalVisitTileInfo( tile ); case MP2::OBJ_MAGIC_WELL: case MP2::OBJ_FOUNTAIN: case MP2::OBJ_FAERIE_RING: case MP2::OBJ_IDOL: case MP2::OBJ_OASIS: case MP2::OBJ_TEMPLE: case MP2::OBJ_BUOY: case MP2::OBJ_MERMAID: case MP2::OBJ_WATERING_HOLE: case MP2::OBJ_ARENA: case MP2::OBJ_STABLES: case MP2::OBJ_SIRENS: return showLocalVisitObjectInfo( objectType ); case MP2::OBJ_SHRINE_FIRST_CIRCLE: case MP2::OBJ_SHRINE_SECOND_CIRCLE: case MP2::OBJ_SHRINE_THIRD_CIRCLE: return showShrineInfo( tile, kingdom.isVisited( tile ) ); case MP2::OBJ_WITCHS_HUT: return showWitchHutInfo( tile, kingdom.isVisited( tile ) ); case MP2::OBJ_BARRIER: return showBarrierInfo( tile, kingdom ); case MP2::OBJ_TRAVELLER_TENT: return showTentInfo( tile, kingdom ); case MP2::OBJ_TREE_OF_KNOWLEDGE: return showTreeOfKnowledgeInfo( tile, kingdom.isVisited( tile ) ); // These objects does not have extra text for quick info. case MP2::OBJ_CAMPFIRE: case MP2::OBJ_ARTIFACT: default: return MP2::StringObject( objectType ); } } void showQuickInfo( const Castle & castle, const fheroes2::Point & position, const bool showOnRadar, const fheroes2::Rect & areaToRestore ) { const CursorRestorer cursorRestorer( false, Cursor::POINTER ); // Update radar if needed RadarUpdater radarUpdater( showOnRadar, castle.GetCenter(), areaToRestore ); // image box const fheroes2::Sprite & box = fheroes2::AGG::GetICN( ICN::QWIKTOWN, 0 ); LocalEvent & le = LocalEvent::Get(); fheroes2::Rect cur_rt = makeRectQuickInfo( le, box, position ); fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::ImageRestorer back( display, cur_rt.x, cur_rt.y, cur_rt.width, cur_rt.height ); fheroes2::Blit( box, display, cur_rt.x, cur_rt.y ); cur_rt = fheroes2::Rect( cur_rt.x + 22, cur_rt.y + 9, 192, 154 ); fheroes2::Point dst_pt; // castle name fheroes2::Text text( castle.GetName(), fheroes2::FontType::smallWhite() ); dst_pt.x = cur_rt.x + ( cur_rt.width - text.width() ) / 2; dst_pt.y = cur_rt.y + 3; text.draw( dst_pt.x, dst_pt.y, display ); // castle icon const Settings & conf = Settings::Get(); const fheroes2::Sprite & castleIcon = fheroes2::AGG::GetICN( conf.isEvilInterfaceEnabled() ? ICN::LOCATORE : ICN::LOCATORS, 23 ); dst_pt.x = cur_rt.x + ( cur_rt.width - castleIcon.width() ) / 2; dst_pt.y += 10; fheroes2::Blit( castleIcon, display, dst_pt.x, dst_pt.y ); fheroes2::drawCastleIcon( castle, display, fheroes2::Point( dst_pt.x + 4, dst_pt.y + 4 ) ); // color flags uint32_t index = 0; switch ( castle.GetColor() ) { case Color::BLUE: index = 0; break; case Color::GREEN: index = 2; break; case Color::RED: index = 4; break; case Color::YELLOW: index = 6; break; case Color::ORANGE: index = 8; break; case Color::PURPLE: index = 10; break; case Color::NONE: index = 12; break; default: break; } const fheroes2::Point flagOffset( 5, 4 ); const fheroes2::Sprite & l_flag = fheroes2::AGG::GetICN( ICN::FLAG32, index ); fheroes2::Blit( l_flag, display, dst_pt.x - flagOffset.x - l_flag.width(), dst_pt.y + flagOffset.y ); const fheroes2::Sprite & r_flag = fheroes2::AGG::GetICN( ICN::FLAG32, index + 1 ); fheroes2::Blit( r_flag, display, dst_pt.x + flagOffset.x + castleIcon.width(), dst_pt.y + flagOffset.y ); const int currentColor = conf.CurrentColor(); const Kingdom & kingdom = world.GetKingdom( currentColor ); const bool isDetailedView = castle.isFriends( currentColor ) || kingdom.IsTileVisibleFromCrystalBall( castle.GetIndex() ); const uint32_t thievesGuildsCount = kingdom.GetCountThievesGuild(); text.set( _( "Defenders:" ), fheroes2::FontType::smallWhite() ); dst_pt.x = cur_rt.x + ( cur_rt.width - text.width() ) / 2; dst_pt.y += castleIcon.height() + 2; text.draw( dst_pt.x, dst_pt.y, display ); // draw defenders if ( isDetailedView || thievesGuildsCount > 0 ) { const Army & castleArmy = castle.GetArmy(); if ( castleArmy.isValid() ) { dst_pt.x = cur_rt.x - 1; dst_pt.y += 21; Army::drawMultipleMonsterLines( castleArmy, dst_pt.x, dst_pt.y, 192, false, isDetailedView, true, thievesGuildsCount ); } else { text.set( _( "None" ), fheroes2::FontType::smallWhite() ); dst_pt.x = cur_rt.x + ( cur_rt.width - text.width() ) / 2; dst_pt.y += 47; text.draw( dst_pt.x, dst_pt.y, display ); } } else { text.set( _( "Unknown" ), fheroes2::FontType::smallWhite() ); dst_pt.x = cur_rt.x + ( cur_rt.width - text.width() ) / 2; dst_pt.y += 47; text.draw( dst_pt.x, dst_pt.y, display ); } display.render(); // quick info loop while ( le.HandleEvents() && le.MousePressRight() ) ; // restore background back.restore(); // Restore radar view radarUpdater.restore(); display.render(); } void showQuickInfo( const HeroBase & hero, const fheroes2::Point & position, const bool showOnRadar, const fheroes2::Rect & areaToRestore, const std::optional showFullInfo ) { const CursorRestorer cursorRestorer( false, Cursor::POINTER ); // Update radar if needed RadarUpdater radarUpdater( showOnRadar, hero.GetCenter(), areaToRestore ); // image box const fheroes2::Sprite & box = fheroes2::AGG::GetICN( ICN::QWIKHERO, 0 ); LocalEvent & le = LocalEvent::Get(); fheroes2::Rect cur_rt = makeRectQuickInfo( le, box, position ); fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::ImageRestorer restorer( display, cur_rt.x, cur_rt.y, cur_rt.width, cur_rt.height ); fheroes2::Blit( box, display, cur_rt.x, cur_rt.y ); cur_rt = fheroes2::Rect( restorer.x() + 28, restorer.y() + 10, 146, 144 ); fheroes2::Point dst_pt; const Settings & conf = Settings::Get(); const bool isNeutralHero = ( hero.GetColor() == Color::NONE ); const bool isFullInfo = [&hero, showFullInfo, &conf, isNeutralHero]() { if ( showFullInfo ) { return *showFullInfo; } const Kingdom & kingdom = world.GetKingdom( conf.CurrentColor() ); const bool isFriend = ColorBase( hero.GetColor() ).isFriends( conf.CurrentColor() ); const bool isUnderIdentifyHeroSpell = kingdom.Modes( Kingdom::IDENTIFYHERO ); return ( isNeutralHero || isFriend || isUnderIdentifyHeroSpell || kingdom.IsTileVisibleFromCrystalBall( hero.GetIndex() ) ); }(); const Heroes * activeHero = dynamic_cast( &hero ); const Captain * activeCaptain = dynamic_cast( &hero ); assert( activeHero != nullptr || activeCaptain != nullptr ); const bool isActiveHero = ( activeHero != nullptr ); std::string message; // hero's name if ( isFullInfo && isActiveHero ) { message = _( "%{name} (Level %{level})" ); StringReplace( message, "%{name}", hero.GetName() ); StringReplace( message, "%{level}", activeHero->GetLevel() ); } else { message = hero.GetName(); } const fheroes2::FontType smallWhite = fheroes2::FontType::smallWhite(); fheroes2::Text text( message, smallWhite ); dst_pt.x = cur_rt.x + ( cur_rt.width - text.width() ) / 2; dst_pt.y = cur_rt.y + 2; text.draw( dst_pt.x, dst_pt.y, display ); const fheroes2::Sprite & heroPortraitFrame = fheroes2::AGG::GetICN( conf.isEvilInterfaceEnabled() ? ICN::LOCATORE : ICN::LOCATORS, 22 ); // mini port heroes const fheroes2::Sprite & port = isActiveHero ? activeHero->GetPortrait( PORT_SMALL ) : activeCaptain->GetPortrait( PORT_SMALL ); if ( !port.empty() ) { fheroes2::Blit( heroPortraitFrame, display, cur_rt.x + ( cur_rt.width - heroPortraitFrame.width() ) / 2, cur_rt.y + 12 ); fheroes2::Blit( port, display, cur_rt.x + ( cur_rt.width - port.width() ) / 2, cur_rt.y + 16 ); } // luck if ( isFullInfo ) { const int32_t luck = hero.GetLuck(); const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::MINILKMR, ( 0 > luck ? 0 : ( 0 < luck ? 1 : 2 ) ) ); uint32_t count = ( 0 == luck ? 1 : std::abs( luck ) ); dst_pt.x = cur_rt.x + 120; dst_pt.y = cur_rt.y + ( count == 1 ? 20 : 13 ); while ( count-- ) { fheroes2::Blit( sprite, display, dst_pt.x, dst_pt.y ); dst_pt.y += sprite.height() - 1; } } // morale if ( isFullInfo ) { const int32_t morale = hero.GetMorale(); const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::MINILKMR, ( 0 > morale ? 3 : ( 0 < morale ? 4 : 5 ) ) ); uint32_t count = ( 0 == morale ? 1 : std::abs( morale ) ); dst_pt.x = cur_rt.x + 10; dst_pt.y = cur_rt.y + ( count == 1 ? 20 : 13 ); while ( count-- ) { fheroes2::Blit( sprite, display, dst_pt.x, dst_pt.y ); dst_pt.y += sprite.height() - 1; } } dst_pt.y = cur_rt.y + 13; // color flags, except for neutral heroes if ( !isNeutralHero ) { uint32_t index = 0; switch ( hero.GetColor() ) { case Color::BLUE: index = 0; break; case Color::GREEN: index = 2; break; case Color::RED: index = 4; break; case Color::YELLOW: index = 6; break; case Color::ORANGE: index = 8; break; case Color::PURPLE: index = 10; break; default: break; } const fheroes2::Sprite & l_flag = fheroes2::AGG::GetICN( ICN::FLAG32, index ); dst_pt.x = cur_rt.x + ( cur_rt.width - 40 ) / 2 - l_flag.width(); fheroes2::Blit( l_flag, display, dst_pt.x, dst_pt.y ); const fheroes2::Sprite & r_flag = fheroes2::AGG::GetICN( ICN::FLAG32, index + 1 ); dst_pt.x = cur_rt.x + ( cur_rt.width + 40 ) / 2; fheroes2::Blit( r_flag, display, dst_pt.x, dst_pt.y ); } const uint16_t statNumberColumn = 89; const uint16_t statRow = 11; if ( isFullInfo ) { // attack text.set( _( "Attack:" ), smallWhite ); dst_pt.x = cur_rt.x + 10; dst_pt.y += heroPortraitFrame.height(); text.fitToOneRow( statNumberColumn ); text.draw( dst_pt.x, dst_pt.y, display ); text.set( std::to_string( hero.GetAttack() ), smallWhite ); dst_pt.x += statNumberColumn; text.draw( dst_pt.x, dst_pt.y, display ); // defense text.set( _( "Defense:" ), smallWhite ); dst_pt.x = cur_rt.x + 10; dst_pt.y += statRow; text.fitToOneRow( statNumberColumn ); text.draw( dst_pt.x, dst_pt.y, display ); text.set( std::to_string( hero.GetDefense() ), smallWhite ); dst_pt.x += statNumberColumn; text.draw( dst_pt.x, dst_pt.y, display ); // power text.set( _( "Spell Power:" ), smallWhite ); dst_pt.x = cur_rt.x + 10; dst_pt.y += statRow; text.fitToOneRow( statNumberColumn ); text.draw( dst_pt.x, dst_pt.y, display ); text.set( std::to_string( hero.GetPower() ), smallWhite ); dst_pt.x += statNumberColumn; text.draw( dst_pt.x, dst_pt.y, display ); // knowledge text.set( _( "Knowledge:" ), smallWhite ); dst_pt.x = cur_rt.x + 10; dst_pt.y += statRow; text.fitToOneRow( statNumberColumn ); text.draw( dst_pt.x, dst_pt.y, display ); text.set( std::to_string( hero.GetKnowledge() ), smallWhite ); dst_pt.x += statNumberColumn; text.draw( dst_pt.x, dst_pt.y, display ); // spell point text.set( _( "Spell Points:" ), smallWhite ); dst_pt.x = cur_rt.x + 10; dst_pt.y += statRow; text.fitToOneRow( statNumberColumn ); text.draw( dst_pt.x, dst_pt.y, display ); text.set( std::to_string( hero.GetSpellPoints() ) + "/" + std::to_string( hero.GetMaxSpellPoints() ), smallWhite ); dst_pt.x += statNumberColumn; text.draw( dst_pt.x, dst_pt.y, display ); // move point if ( isActiveHero ) { text.set( _( "Move Points:" ), smallWhite ); dst_pt.x = cur_rt.x + 10; dst_pt.y += statRow; text.fitToOneRow( statNumberColumn ); text.draw( dst_pt.x, dst_pt.y, display ); text.set( std::to_string( activeHero->GetMovePoints() ) + "/" + std::to_string( activeHero->GetMaxMovePoints() ), smallWhite ); dst_pt.x += statNumberColumn; text.draw( dst_pt.x, dst_pt.y, display ); } Army::drawSingleDetailedMonsterLine( hero.GetArmy(), cur_rt.x - 7, cur_rt.y + 118, 160 ); } else { // show limited Army::drawMultipleMonsterLines( hero.GetArmy(), cur_rt.x - 6, cur_rt.y + 60, 160, false, false ); } display.render(); // quick info loop while ( le.HandleEvents() && le.MousePressRight() ) ; // restore background restorer.restore(); // Restore radar view radarUpdater.restore(); display.render(); } } void Dialog::QuickInfo( const Maps::Tiles & tile ) { std::string infoString; const int32_t playerColor = Settings::Get().CurrentColor(); if ( ( playerColor != 0 ) && tile.isFog( playerColor ) ) { infoString = _( "Uncharted Territory" ); } else { const int32_t mainTileIndex = Maps::Tiles::getIndexOfMainTile( tile ); if ( mainTileIndex != -1 ) { infoString = getQuickInfoText( world.GetTiles( mainTileIndex ) ); } else { infoString = getQuickInfoText( tile ); } } outputInTextSupportMode( tile, infoString ); Interface::displayStandardPopupWindow( std::move( infoString ), Interface::AdventureMap::Get().getGameArea().GetROI() ); } void Dialog::QuickInfo( const Castle & castle ) { showQuickInfo( castle, {}, false, {} ); } void Dialog::QuickInfo( const HeroBase & hero, const std::optional showFullInfo /* = {} */ ) { showQuickInfo( hero, {}, false, {}, showFullInfo ); } void Dialog::QuickInfoWithIndicationOnRadar( const Castle & castle, const fheroes2::Rect & areaToRestore ) { showQuickInfo( castle, {}, true, areaToRestore ); } void Dialog::QuickInfoWithIndicationOnRadar( const HeroBase & hero, const fheroes2::Rect & areaToRestore ) { showQuickInfo( hero, {}, true, areaToRestore, {} ); } void Dialog::QuickInfoAtPosition( const Castle & castle, const fheroes2::Point & position ) { showQuickInfo( castle, position, true, {} ); } void Dialog::QuickInfoAtPosition( const HeroBase & hero, const fheroes2::Point & position ) { showQuickInfo( hero, position, true, {}, {} ); } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_recruit.cpp000066400000000000000000000660201456075706000235100ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include "agg_image.h" #include "army_troop.h" #include "bin_info.h" #include "cursor.h" #include "dialog.h" #include "game_hotkeys.h" #include "gamedefs.h" #include "icn.h" #include "image.h" #include "kingdom.h" #include "localevent.h" #include "math_base.h" #include "monster.h" #include "pal.h" #include "resource.h" #include "screen.h" #include "settings.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_text.h" #include "ui_tool.h" #include "ui_window.h" #include "world.h" namespace { void drawButtonShadow( fheroes2::Image & output, const int buttonIcn, const uint32_t icnIndex, const fheroes2::Point & offset ) { const fheroes2::Sprite & buttonSprite = fheroes2::AGG::GetICN( buttonIcn, icnIndex ); fheroes2::addGradientShadow( buttonSprite, output, offset, { -5, 5 } ); } void drawCostPerTroopFrame( fheroes2::Image & output, const fheroes2::Point & offset ) { const fheroes2::Sprite & originalBackground = fheroes2::AGG::GetICN( ICN::RECRBKG, 0 ); const fheroes2::Sprite & recruitWindowTitle = fheroes2::AGG::GetICN( ICN::BLDGXTRA, 0 ); fheroes2::Image recruitWindow( 132, 67 ); // Reset the transparent layer. recruitWindow.reset(); // Copy recruit cost title background and borders to make shadows for it and to Blit it to the Recruit dialog. fheroes2::Copy( recruitWindowTitle, 3, 58, recruitWindow, 3, 0, 63, 14 ); fheroes2::Copy( recruitWindowTitle, recruitWindowTitle.width() - 66, 58, recruitWindow, 66, 0, 63, 14 ); fheroes2::Copy( originalBackground, 138, 54, recruitWindow, 0, 0, 3, 63 ); fheroes2::Copy( originalBackground, 267, 54, recruitWindow, 129, 0, 3, 63 ); fheroes2::Copy( originalBackground, 138, 117, recruitWindow, 0, 63, 132, 4 ); const fheroes2::Text recruitWindowText( _( "Cost per troop:" ), fheroes2::FontType::smallWhite() ); recruitWindowText.draw( ( recruitWindow.width() - recruitWindowText.width() ) / 2, 3, recruitWindow ); if ( Settings::Get().isEvilInterfaceEnabled() ) { fheroes2::ApplyPalette( recruitWindow, PAL::GetPalette( PAL::PaletteType::GOOD_TO_EVIL_INTERFACE ) ); } fheroes2::Blit( recruitWindow, output, offset.x, offset.y ); fheroes2::addGradientShadow( recruitWindow, output, { offset.x, offset.y }, { -5, 5 } ); } } void RedrawCurrentInfo( const fheroes2::Point & pos, const uint32_t result, const Funds & paymentMonster, const Funds & paymentCosts, const Funds & funds, const std::string & label, const fheroes2::Image & background = {} ) { fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::Text text( std::to_string( result ), fheroes2::FontType::normalWhite() ); // Restore the background of the text before rendering it. fheroes2::Copy( background, 118, 147, display, pos.x + 118, pos.y + 147, 68, text.height() ); text.draw( pos.x + 151 - text.width() / 2, pos.y + 147, display ); std::string sgold = std::to_string( paymentCosts.gold ) + " " + "(" + std::to_string( funds.gold - paymentCosts.gold ) + ")"; const int rsext = paymentMonster.GetValidItems() & ~Resource::GOLD; text.set( std::move( sgold ), fheroes2::FontType::smallWhite() ); // Restore the background of the text before rendering it. fheroes2::Copy( background, 0, 214, display, pos.x, pos.y + 214, background.width(), text.height() ); if ( rsext ) { text.draw( pos.x + 117 - text.width() / 2, pos.y + 214, display ); text.set( std::to_string( paymentCosts.Get( rsext ) ) + " " + "(" + std::to_string( funds.Get( rsext ) - paymentCosts.Get( rsext ) ) + ")", fheroes2::FontType::smallWhite() ); text.draw( pos.x + 179 - text.width() / 2, pos.y + 214, display ); } else { text.draw( pos.x + 144 - text.width() / 2, pos.y + 214, display ); } // Restore the background of the text before rendering it or to leave it blank if there is no text. fheroes2::Copy( background, 0, 166, display, pos.x, pos.y + 166, background.width(), text.height() ); if ( !label.empty() ) { text.set( label, fheroes2::FontType::smallWhite() ); text.draw( pos.x + 151 - text.width() / 2, pos.y + 166, display ); } } void RedrawResourceInfo( const int resourceIcnIndex, const fheroes2::Point & pos, const int32_t value, const int32_t px1, const int32_t py1, const int32_t px2, const int32_t py2, const bool showTotalSum ) { fheroes2::Display & display = fheroes2::Display::instance(); // In recruit dialog (where the total sum is also shown) the resource info is shifted by 10 pixels to the right. const int32_t offsetX = showTotalSum ? 10 : 0; const fheroes2::Sprite & sres = fheroes2::AGG::GetICN( ICN::RESOURCE, Resource::getIconIcnIndex( resourceIcnIndex ) ); fheroes2::Blit( sres, fheroes2::Display::instance(), pos.x + px1 + offsetX, pos.y + py1 ); const fheroes2::Text text( std::to_string( value ), fheroes2::FontType::smallWhite() ); text.draw( pos.x + px2 - text.width() / 2 + offsetX, pos.y + py2, display ); if ( showTotalSum ) { fheroes2::Blit( sres, display, pos.x + px1 - 45, pos.y + py1 + 125 ); } } void RedrawMonsterInfo( const fheroes2::Rect & pos, const Monster & monster, const uint32_t available, const bool showTotalSum ) { fheroes2::Display & display = fheroes2::Display::instance(); const Funds paymentMonster = monster.GetCost(); const bool needExtraResources = 2 == paymentMonster.GetValidItemsCount(); // Recruit monster text. std::string str = _( "Recruit %{name}" ); StringReplace( str, "%{name}", monster.GetMultiName() ); fheroes2::Text text( str, fheroes2::FontType::normalYellow() ); fheroes2::Point dst_pt( pos.x + ( pos.width - text.width() ) / 2, pos.y + 11 ); text.draw( dst_pt.x, dst_pt.y, display ); // Monster sprite. const int monsterId = monster.GetID(); const Bin_Info::MonsterAnimInfo & monsterInfo = Bin_Info::GetMonsterInfo( monsterId ); assert( !monsterInfo.animationFrames[Bin_Info::MonsterAnimInfo::STATIC].empty() ); const fheroes2::Sprite & smon = fheroes2::AGG::GetICN( monster.GetMonsterSprite(), monsterInfo.animationFrames[Bin_Info::MonsterAnimInfo::STATIC][0] ); dst_pt.x = pos.x + 64 + smon.x() - ( monster.isWide() ? 22 : 0 ); dst_pt.y = pos.y + 119 - smon.height(); if ( monsterId == Monster::CHAMPION ) { ++dst_pt.x; } fheroes2::Blit( smon, display, dst_pt.x, dst_pt.y ); // Resources needed to buy monster. if ( needExtraResources ) { RedrawResourceInfo( Resource::GOLD, pos.getPosition(), paymentMonster.gold, 134, 59, 167, 89, showTotalSum ); if ( paymentMonster.crystal > 0 ) { RedrawResourceInfo( Resource::CRYSTAL, pos.getPosition(), paymentMonster.crystal, 206, 53, 224, 89, showTotalSum ); } else if ( paymentMonster.mercury > 0 ) { RedrawResourceInfo( Resource::MERCURY, pos.getPosition(), paymentMonster.mercury, 209, 56, 224, 89, showTotalSum ); } else if ( paymentMonster.wood > 0 ) { RedrawResourceInfo( Resource::WOOD, pos.getPosition(), paymentMonster.wood, 209, 56, 224, 89, showTotalSum ); } else if ( paymentMonster.ore > 0 ) { RedrawResourceInfo( Resource::ORE, pos.getPosition(), paymentMonster.ore, 209, 56, 224, 89, showTotalSum ); } else if ( paymentMonster.sulfur > 0 ) { RedrawResourceInfo( Resource::SULFUR, pos.getPosition(), paymentMonster.sulfur, 209, 59, 224, 89, showTotalSum ); } else if ( paymentMonster.gems > 0 ) { RedrawResourceInfo( Resource::GEMS, pos.getPosition(), paymentMonster.gems, 209, 59, 224, 89, showTotalSum ); } } else { // Only gold is needed. RedrawResourceInfo( Resource::GOLD, pos.getPosition(), paymentMonster.gold, 159, 59, 189, 89, showTotalSum ); } str = _( "Available: %{count}" ); StringReplace( str, "%{count}", available ); text.set( std::move( str ), fheroes2::FontType::smallWhite() ); text.draw( pos.x + 64 - text.width() / 2, pos.y + 121, display ); if ( showTotalSum ) { text.set( _( "Number to buy:" ), fheroes2::FontType::smallWhite() ); text.draw( pos.x + 107 - text.width(), pos.y + 149, display ); } } const char * SwitchMaxMinButtons( fheroes2::ButtonBase & btnMax, fheroes2::ButtonBase & btnMin, bool max ) { if ( btnMax.isEnabled() || btnMin.isEnabled() ) { if ( max ) { btnMax.disable(); btnMin.enable(); return _( "Max" ); } btnMin.disable(); btnMax.enable(); return _( "Min" ); } return ""; } uint32_t CalculateMax( const Monster & monster, const Kingdom & kingdom, const uint32_t available ) { uint32_t max = 0; while ( kingdom.AllowPayment( monster.GetCost() * ( max + 1 ) ) && ( max + 1 ) <= available ) { ++max; } return max; } Troop Dialog::RecruitMonster( const Monster & monster0, const uint32_t available, const bool allowDowngradedMonster, const int32_t windowOffsetY ) { const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); fheroes2::Display & display = fheroes2::Display::instance(); LocalEvent & le = LocalEvent::Get(); // Set cursor. const CursorRestorer cursorRestorer( true, Cursor::POINTER ); // Calculate max count. Monster monster = monster0; Funds paymentMonster = monster.GetCost(); const Kingdom & kingdom = world.GetKingdom( Settings::Get().CurrentColor() ); uint32_t max = CalculateMax( monster, kingdom, available ); uint32_t result = max; Funds paymentCosts( paymentMonster * result ); const fheroes2::Size windowSize{ 299, 272 }; const fheroes2::Point dialogOffset( ( display.width() - windowSize.width ) / 2, ( display.height() - windowSize.height ) / 2 + windowOffsetY ); const fheroes2::StandardWindow window( dialogOffset.x, dialogOffset.y, windowSize.width, windowSize.height, true, display ); const fheroes2::Rect roi( window.totalArea() ); const fheroes2::Rect windowActiveArea( window.activeArea() ); const fheroes2::Sprite & originalBackground = fheroes2::AGG::GetICN( ICN::RECRBKG, 0 ); // Render the recruit count background from original recruit dialog ICN. fheroes2::Image background( 68, 19 ); fheroes2::Copy( originalBackground, 134, 159, background, 0, 0, background.width(), background.height() ); if ( isEvilInterface ) { fheroes2::ApplyPalette( background, PAL::GetPalette( PAL::PaletteType::GOOD_TO_EVIL_INTERFACE ) ); } fheroes2::Point dst_pt( dialogOffset.x + 118, dialogOffset.y + 143 ); fheroes2::Copy( background, 0, 0, display, dst_pt.x, dst_pt.y, background.width(), background.height() ); fheroes2::addGradientShadow( background, display, dst_pt, { -5, 5 } ); dst_pt.x = dialogOffset.x + 132; dst_pt.y = dialogOffset.y + 38; drawCostPerTroopFrame( display, dst_pt ); // Prepare buttons. const int32_t backgroundMargin = 18; dst_pt.x = dialogOffset.x + backgroundMargin; dst_pt.y = dialogOffset.y + 233; int buttonId = isEvilInterface ? ICN::BUTTON_SMALL_OKAY_EVIL : ICN::BUTTON_SMALL_OKAY_GOOD; fheroes2::Button buttonOk( dst_pt.x, dst_pt.y, buttonId, 0, 1 ); drawButtonShadow( display, buttonId, 0, dst_pt ); buttonId = isEvilInterface ? ICN::BUTTON_SMALL_CANCEL_EVIL : ICN::BUTTON_SMALL_CANCEL_GOOD; const int32_t buttonCancelWidth = fheroes2::AGG ::GetICN( buttonId, 0 ).width(); dst_pt.x = dialogOffset.x + windowSize.width - backgroundMargin - buttonCancelWidth; fheroes2::Button buttonCancel( dst_pt.x, dst_pt.y, buttonId, 0, 1 ); drawButtonShadow( display, buttonId, 0, dst_pt ); dst_pt.x = dialogOffset.x + 218; dst_pt.y = dialogOffset.y + 140; buttonId = isEvilInterface ? ICN::BUTTON_SMALL_MAX_EVIL : ICN::BUTTON_SMALL_MAX_GOOD; fheroes2::Button buttonMax( dst_pt.x, dst_pt.y, buttonId, 0, 1 ); fheroes2::Button buttonMin( dst_pt.x, dst_pt.y, isEvilInterface ? ICN::BUTTON_SMALL_MIN_EVIL : ICN::BUTTON_SMALL_MIN_GOOD, 0, 1 ); drawButtonShadow( display, buttonId, 0, dst_pt ); dst_pt.x = dialogOffset.x + 189; dst_pt.y = dialogOffset.y + 138; fheroes2::Button buttonUp( dst_pt.x, dst_pt.y, ICN::RECRUIT, 0, 1 ); drawButtonShadow( display, ICN::RECRUIT, 0, dst_pt ); dst_pt.y = dialogOffset.y + 153; fheroes2::Button buttonDn( dst_pt.x, dst_pt.y, ICN::RECRUIT, 2, 3 ); drawButtonShadow( display, ICN::RECRUIT, 2, dst_pt ); fheroes2::TimedEventValidator timedButtonUp( [&buttonUp]() { return buttonUp.isPressed(); } ); fheroes2::TimedEventValidator timedButtonDn( [&buttonDn]() { return buttonDn.isPressed(); } ); buttonDn.subscribe( &timedButtonDn ); buttonUp.subscribe( &timedButtonUp ); const fheroes2::Rect rtWheel( dialogOffset.x + 114, dialogOffset.y + 139, 100, 30 ); // Create monster switching arrows fheroes2::ButtonSprite monsterSwitchLeft; fheroes2::ButtonSprite monsterSwitchRight; const bool showDowngradedMonsterSwitchButtons = allowDowngradedMonster && ( monster0.GetDowngrade() != monster0 ); if ( showDowngradedMonsterSwitchButtons ) { const fheroes2::Sprite & leftButtonSprite = fheroes2::AGG::GetICN( ICN::MONSTER_SWITCH_LEFT_ARROW, 0 ); monsterSwitchLeft.setSprite( leftButtonSprite, fheroes2::AGG::GetICN( ICN::MONSTER_SWITCH_LEFT_ARROW, 1 ) ); dst_pt.x = dialogOffset.x + 6; dst_pt.y = dialogOffset.y + 64; monsterSwitchLeft.setPosition( dst_pt.x, dst_pt.y ); fheroes2::addGradientShadow( leftButtonSprite, display, dst_pt, { -5, 5 } ); const fheroes2::Sprite & rightButtonSprite = fheroes2::AGG::GetICN( ICN::MONSTER_SWITCH_RIGHT_ARROW, 0 ); monsterSwitchRight.setSprite( rightButtonSprite, fheroes2::AGG::GetICN( ICN::MONSTER_SWITCH_RIGHT_ARROW, 1 ) ); dst_pt.x = dialogOffset.x + 105; monsterSwitchRight.setPosition( dst_pt.x, dst_pt.y ); fheroes2::addGradientShadow( rightButtonSprite, display, dst_pt, { -5, 5 } ); // Render Left and Right buttons to restore their initial state later. monsterSwitchLeft.draw(); monsterSwitchRight.draw(); } else { monsterSwitchLeft.hide(); monsterSwitchRight.hide(); monsterSwitchLeft.disable(); monsterSwitchRight.disable(); } // Render Up and Down buttons to restore their initial state later. buttonUp.draw(); buttonDn.draw(); // Make a copy of background dialog to restore its parts before updating some dialog elements. background.resize( windowSize.width, windowSize.height ); fheroes2::Copy( display, dialogOffset.x, dialogOffset.y, background, 0, 0, windowSize.width, windowSize.height ); RedrawMonsterInfo( windowActiveArea, monster, available, true ); if ( 0 == result ) { buttonOk.disable(); buttonMax.disable(); buttonMin.disable(); buttonMax.draw(); } const Funds & funds = kingdom.GetFunds(); std::string maxmin = SwitchMaxMinButtons( buttonMax, buttonMin, true ); RedrawCurrentInfo( dialogOffset, result, paymentMonster, paymentCosts, funds, maxmin ); buttonOk.draw(); buttonCancel.draw(); if ( buttonMax.isEnabled() ) { buttonMax.draw(); } if ( buttonMin.isEnabled() ) { buttonMin.draw(); } display.render( roi ); const fheroes2::Rect monsterArea( dialogOffset.x + 24, dialogOffset.y + 19, 75, 95 ); const auto buttonReleaseRestore = [&display, &background, &dialogOffset]( fheroes2::ButtonBase & button ) { if ( button.isReleased() ) { return; } // When the "Up"/"Down" button is pressed it is shifted 1 pixel down so we need to properly restore the background. const fheroes2::Rect buttonRoi = button.area(); button.release(); fheroes2::Copy( background, buttonRoi.x - dialogOffset.x, buttonRoi.y - dialogOffset.y, display, buttonRoi.x, buttonRoi.y, buttonRoi.width, buttonRoi.height ); // A the non-pressed button is already on the "background" copy so we do not render the button. display.render( buttonRoi ); }; std::vector upgrades = { monster0 }; while ( upgrades.back().GetDowngrade() != upgrades.back() ) { upgrades.emplace_back( upgrades.back().GetDowngrade() ); } // str loop while ( le.HandleEvents() ) { bool redraw = false; if ( buttonOk.isEnabled() ) { le.MousePressLeft( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); } le.MousePressLeft( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); if ( le.MousePressLeft( buttonUp.area() ) ) { buttonUp.drawOnPress(); } else { buttonReleaseRestore( buttonUp ); } if ( le.MousePressLeft( buttonDn.area() ) ) { buttonDn.drawOnPress(); } else { buttonReleaseRestore( buttonDn ); } if ( buttonMax.isEnabled() ) { le.MousePressLeft( buttonMax.area() ) ? buttonMax.drawOnPress() : buttonMax.drawOnRelease(); } if ( buttonMin.isEnabled() ) { le.MousePressLeft( buttonMin.area() ) ? buttonMin.drawOnPress() : buttonMin.drawOnRelease(); } bool updateMonsterInfo = false; if ( showDowngradedMonsterSwitchButtons ) { if ( le.MousePressLeft( monsterSwitchLeft.area() ) ) { monsterSwitchLeft.drawOnPress(); } else { buttonReleaseRestore( monsterSwitchLeft ); } if ( le.MousePressLeft( monsterSwitchRight.area() ) ) { monsterSwitchRight.drawOnPress(); } else { buttonReleaseRestore( monsterSwitchRight ); } if ( le.MouseClickLeft( monsterSwitchLeft.area() ) || le.KeyPress( fheroes2::Key::KEY_LEFT ) ) { for ( size_t i = 0; i < upgrades.size(); ++i ) { if ( upgrades[i] == monster ) { if ( i < upgrades.size() - 1 ) { monster = upgrades[i + 1]; } else { monster = upgrades[0]; } break; } } updateMonsterInfo = true; } else if ( le.MouseClickLeft( monsterSwitchRight.area() ) || le.KeyPress( fheroes2::Key::KEY_RIGHT ) ) { for ( size_t i = 0; i < upgrades.size(); ++i ) { if ( upgrades[i] == monster ) { if ( i > 0 ) { monster = upgrades[i - 1]; } else { monster = upgrades.back(); } break; } } updateMonsterInfo = true; } } if ( updateMonsterInfo ) { // Restore the recruit dialog background. fheroes2::Copy( background, 0, 0, display, dialogOffset.x, dialogOffset.y, windowSize.width, windowSize.height ); max = CalculateMax( monster, kingdom, available ); if ( max == 0 ) { buttonMin.disable(); buttonMax.disable(); } else if ( !buttonMax.isEnabled() && !buttonMin.isEnabled() ) { buttonMin.enable(); } result = max; paymentMonster = monster.GetCost(); paymentCosts = paymentMonster * result; redraw = true; maxmin = SwitchMaxMinButtons( buttonMax, buttonMin, true ); RedrawMonsterInfo( windowActiveArea, monster, available, true ); } if ( le.MousePressRight( monsterArea ) ) { ArmyInfo( Troop( monster, available ), ZERO ); // Perform a full rendering to properly restore the parts of the screen outside of this dialog display.render(); continue; } if ( le.MouseClickLeft( monsterArea ) ) { ArmyInfo( Troop( monster, available ), BUTTONS ); // Perform a full rendering to properly restore the parts of the screen outside of this dialog display.render(); continue; } if ( fheroes2::PressIntKey( max, result ) ) { paymentCosts = paymentMonster * result; redraw = true; maxmin.clear(); if ( result == max ) { maxmin = SwitchMaxMinButtons( buttonMax, buttonMin, true ); } else if ( result == 1 ) { maxmin = SwitchMaxMinButtons( buttonMax, buttonMin, false ); } } if ( ( le.MouseWheelUp( rtWheel ) || le.MouseClickLeft( buttonUp.area() ) || le.KeyPress( fheroes2::Key::KEY_UP ) || timedButtonUp.isDelayPassed() ) && result < max ) { ++result; paymentCosts += paymentMonster; redraw = true; maxmin.clear(); if ( result == max ) { maxmin = SwitchMaxMinButtons( buttonMax, buttonMin, true ); } else if ( result == 1 ) { maxmin = SwitchMaxMinButtons( buttonMax, buttonMin, false ); } } else if ( ( le.MouseWheelDn( rtWheel ) || le.MouseClickLeft( buttonDn.area() ) || le.KeyPress( fheroes2::Key::KEY_DOWN ) || timedButtonDn.isDelayPassed() ) && result ) { --result; paymentCosts -= paymentMonster; redraw = true; maxmin.clear(); if ( result == max ) { maxmin = SwitchMaxMinButtons( buttonMax, buttonMin, true ); } else if ( result == 1 ) { maxmin = SwitchMaxMinButtons( buttonMax, buttonMin, false ); } } else if ( buttonMax.isEnabled() && le.MouseClickLeft( buttonMax.area() ) && result != max ) { maxmin = SwitchMaxMinButtons( buttonMax, buttonMin, true ); result = max; paymentCosts = paymentMonster * max; redraw = true; } else if ( buttonMin.isEnabled() && le.MouseClickLeft( buttonMin.area() ) && result != 1 ) { maxmin = SwitchMaxMinButtons( buttonMax, buttonMin, false ); result = 1; paymentCosts = paymentMonster; redraw = true; } else if ( le.MousePressRight( buttonOk.area() ) ) { fheroes2::showStandardTextMessage( _( "Okay" ), _( "Recruit selected monsters." ), 0 ); } else if ( le.MousePressRight( buttonCancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Exit this menu without doing anything." ), 0 ); } else if ( buttonMax.isEnabled() && le.MousePressRight( buttonMax.area() ) ) { fheroes2::showStandardTextMessage( _( "MAX" ), _( "Select maximum monsters to be recruited." ), 0 ); } else if ( buttonMin.isEnabled() && le.MousePressRight( buttonMin.area() ) ) { fheroes2::showStandardTextMessage( _( "MIN" ), _( "Select only 1 monster to be recruited." ), 0 ); } if ( redraw ) { RedrawCurrentInfo( dialogOffset, result, paymentMonster, paymentCosts, funds, maxmin, background ); if ( 0 == result ) { buttonOk.disable(); buttonOk.draw(); } else { buttonOk.enable(); buttonOk.draw(); } buttonCancel.draw(); if ( buttonMax.isEnabled() || max == 0 ) { buttonMax.draw(); } else if ( buttonMin.isEnabled() ) { buttonMin.draw(); } monsterSwitchLeft.draw(); monsterSwitchRight.draw(); display.render( windowActiveArea ); } if ( buttonOk.isEnabled() && ( le.MouseClickLeft( buttonOk.area() ) || ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) ) ) ) { break; } if ( le.MouseClickLeft( buttonCancel.area() ) || ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) ) { result = 0; break; } } display.updateNextRenderRoi( roi ); return { monster, result }; } void Dialog::DwellingInfo( const Monster & monster, const uint32_t available ) { const fheroes2::Size windowSize{ 289, 139 }; fheroes2::Display & display = fheroes2::Display::instance(); // Set cursor. const CursorRestorer cursorRestorer( false, Cursor::POINTER ); const fheroes2::Point dialogOffset( ( display.width() - windowSize.width ) / 2, display.height() / 2 - display.DEFAULT_HEIGHT / 2 + BORDERWIDTH ); const fheroes2::StandardWindow window( dialogOffset.x, dialogOffset.y, windowSize.width, windowSize.height, true, display ); drawCostPerTroopFrame( display, { dialogOffset.x + 122, dialogOffset.y + 38 } ); RedrawMonsterInfo( window.activeArea(), monster, available, false ); display.render( window.totalArea() ); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() && le.MousePressRight() ) { // Do nothing. } } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_resolution.cpp000066400000000000000000000350331456075706000242360ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "dialog_resolution.h" #include #include #include #include #include #include #include #include "agg_image.h" #include "cursor.h" #include "embedded_image.h" #include "game_hotkeys.h" #include "icn.h" #include "image.h" #include "interface_list.h" #include "localevent.h" #include "math_base.h" #include "screen.h" #include "settings.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_scrollbar.h" #include "ui_text.h" #include "ui_window.h" #include "zzlib.h" namespace { const int32_t textAreaWidth = 270; const int32_t scrollBarAreaWidth = 48; const int32_t paddingLeftSide = 24; const int32_t resolutionItemHeight = 19; // TODO: this is a hack over partially incorrect text height calculation. Fix it later together with the Text classes. const int32_t textOffsetYCorrection = 6; const std::string middleText = " x "; std::pair getResolutionStrings( const fheroes2::ResolutionInfo & resolution ) { if ( resolution.screenWidth != resolution.gameWidth && resolution.screenHeight != resolution.gameHeight ) { const int32_t integer = resolution.screenWidth / resolution.gameWidth; const int32_t fraction = resolution.screenWidth * 10 / resolution.gameWidth - 10 * integer; return std::make_pair( std::to_string( resolution.gameWidth ), std::to_string( resolution.gameHeight ) + " (x" + std::to_string( integer ) + "." + std::to_string( fraction ) + ')' ); } return std::make_pair( std::to_string( resolution.gameWidth ), std::to_string( resolution.gameHeight ) ); } class ResolutionList : public Interface::ListBox { public: using Interface::ListBox::ActionListSingleClick; using Interface::ListBox::ActionListPressRight; using Interface::ListBox::ActionListDoubleClick; explicit ResolutionList( const fheroes2::Point & offset ) : Interface::ListBox( offset ) , _isDoubleClicked( false ) { // Do nothing. } void RedrawItem( const fheroes2::ResolutionInfo & resolution, int32_t offsetX, int32_t offsetY, bool current ) override { const fheroes2::FontType fontType = current ? fheroes2::FontType::normalYellow() : fheroes2::FontType::normalWhite(); const auto [leftText, rightText] = getResolutionStrings( resolution ); const int32_t middleTextSize = fheroes2::Text( middleText, fontType ).width(); const int32_t leftTextSize = fheroes2::Text( leftText, fontType ).width(); const fheroes2::Text resolutionText( leftText + middleText + rightText, fontType ); const int32_t textOffsetX = offsetX + textAreaWidth / 2 - leftTextSize - middleTextSize / 2; const int32_t textOffsetY = offsetY + ( resolutionItemHeight - resolutionText.height() + textOffsetYCorrection ) / 2; resolutionText.draw( textOffsetX, textOffsetY, fheroes2::Display::instance() ); } void RedrawBackground( const fheroes2::Point & /* unused */ ) override { _listBackground->restore(); } void ActionCurrentUp() override { // Do nothing. } void ActionCurrentDn() override { // Do nothing. } void ActionListSingleClick( fheroes2::ResolutionInfo & /*unused*/ ) override { // Do nothing. } void ActionListPressRight( fheroes2::ResolutionInfo & /*unused*/ ) override { // Do nothing. } void ActionListDoubleClick( fheroes2::ResolutionInfo & /*unused*/ ) override { _isDoubleClicked = true; } bool isDoubleClicked() const { return _isDoubleClicked; } int getCurrentId() const { return _currentId; } void initListBackgroundRestorer( fheroes2::Rect roi ) { _listBackground = std::make_unique( fheroes2::Display::instance(), roi.x, roi.y, roi.width, roi.height ); } void updateScrollBarImage() { const int32_t scrollBarWidth = _scrollbar.width(); setScrollBarImage( fheroes2::generateScrollbarSlider( _scrollbar, false, _scrollbar.getArea().height, VisibleItemCount(), _size(), { 0, 0, scrollBarWidth, 8 }, { 0, 7, scrollBarWidth, 8 } ) ); _scrollbar.moveToIndex( _topId ); } private: bool _isDoubleClicked; std::unique_ptr _listBackground; }; void RedrawInfo( const fheroes2::Point & dst, const fheroes2::ResolutionInfo & resolution, fheroes2::Image & output ) { if ( resolution.gameWidth > 0 && resolution.gameHeight > 0 ) { const fheroes2::FontType fontType = fheroes2::FontType::normalYellow(); const auto [leftText, rightText] = getResolutionStrings( resolution ); const int32_t middleTextSize = fheroes2::Text( middleText, fontType ).width(); const int32_t leftTextSize = fheroes2::Text( leftText, fontType ).width(); const int32_t textOffsetX = dst.x + textAreaWidth / 2 - leftTextSize - middleTextSize / 2; const fheroes2::Text resolutionText( leftText + middleText + rightText, fontType ); resolutionText.draw( textOffsetX, dst.y + ( resolutionItemHeight - resolutionText.height() + textOffsetYCorrection ) / 2, output ); } } fheroes2::ResolutionInfo getNewResolution() { // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); const int32_t listHeightDeduction = 112; const int32_t listAreaOffsetY = 3; const int32_t listAreaHeightDeduction = 4; std::vector resolutions = fheroes2::engine().getAvailableResolutions(); // If we don't have many resolutions, we reduce the maximum dialog height, // but not less than enough for 11 elements. // We also limit the maximum list height to 22 lines. const int32_t maxDialogHeight = fheroes2::getFontHeight( fheroes2::FontSize::NORMAL ) * std::clamp( static_cast( resolutions.size() ), 11, 22 ) + listAreaOffsetY + listAreaHeightDeduction + listHeightDeduction; fheroes2::Display & display = fheroes2::Display::instance(); // Dialog height is also capped with the current screen height. fheroes2::StandardWindow background( paddingLeftSide + textAreaWidth + scrollBarAreaWidth + 3, std::min( display.height() - 100, maxDialogHeight ), true, display ); const fheroes2::Rect roi( background.activeArea() ); const fheroes2::Rect listRoi( roi.x + paddingLeftSide, roi.y + 37, textAreaWidth, roi.height - listHeightDeduction ); // We divide the list: resolution list and selected resolution. const fheroes2::Rect selectedResRoi( listRoi.x, listRoi.y + listRoi.height + 12, listRoi.width, 21 ); background.applyTextBackgroundShading( selectedResRoi ); background.applyTextBackgroundShading( { listRoi.x, listRoi.y, listRoi.width, listRoi.height } ); fheroes2::ImageRestorer selectedResBackground( fheroes2::Display::instance(), selectedResRoi.x, selectedResRoi.y, listRoi.width, selectedResRoi.height ); const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); // Prepare OKAY and CANCEL buttons and render their shadows. fheroes2::Button buttonOk; fheroes2::Button buttonCancel; background.renderOkayCancelButtons( buttonOk, buttonCancel, isEvilInterface ); ResolutionList listBox( roi.getPosition() ); listBox.initListBackgroundRestorer( listRoi ); listBox.SetAreaItems( { listRoi.x, listRoi.y + 3, listRoi.width - 3, listRoi.height - 4 } ); int32_t scrollbarOffsetX = roi.x + roi.width - 35; background.renderScrollbarBackground( { scrollbarOffsetX, listRoi.y, listRoi.width, listRoi.height }, isEvilInterface ); const int32_t topPartHeight = 19; ++scrollbarOffsetX; const int listIcnId = isEvilInterface ? ICN::SCROLLE : ICN::SCROLL; listBox.SetScrollButtonUp( listIcnId, 0, 1, { scrollbarOffsetX, listRoi.y + 1 } ); listBox.SetScrollButtonDn( listIcnId, 2, 3, { scrollbarOffsetX, listRoi.y + listRoi.height - 15 } ); listBox.setScrollBarArea( { scrollbarOffsetX + 2, listRoi.y + topPartHeight, 10, listRoi.height - 2 * topPartHeight } ); listBox.setScrollBarImage( fheroes2::AGG::GetICN( listIcnId, 4 ) ); listBox.SetAreaMaxItems( ( listRoi.height - 7 ) / fheroes2::getFontHeight( fheroes2::FontSize::NORMAL ) ); listBox.SetListContent( resolutions ); listBox.updateScrollBarImage(); const fheroes2::ResolutionInfo currentResolution{ display.width(), display.height(), display.screenSize().width, display.screenSize().height }; fheroes2::ResolutionInfo selectedResolution; for ( size_t i = 0; i < resolutions.size(); ++i ) { if ( resolutions[i] == currentResolution ) { listBox.SetCurrent( i ); selectedResolution = listBox.GetCurrent(); break; } } listBox.Redraw(); RedrawInfo( selectedResRoi.getPosition(), selectedResolution, display ); const fheroes2::Text title( _( "Select Game Resolution:" ), fheroes2::FontType::normalYellow() ); title.draw( roi.x + ( roi.width - title.width() ) / 2, roi.y + 16, display ); display.render( background.totalArea() ); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { le.MousePressLeft( buttonOk.area() ) && buttonOk.isEnabled() ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); le.MousePressLeft( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); if ( le.MousePressRight( listRoi ) ) { continue; } const int listId = listBox.getCurrentId(); listBox.QueueEventProcessing(); const bool needRedraw = listId != listBox.getCurrentId(); if ( ( buttonOk.isEnabled() && le.MouseClickLeft( buttonOk.area() ) ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) || listBox.isDoubleClicked() ) { if ( listBox.isSelected() ) { break; } } else if ( le.MouseClickLeft( buttonCancel.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { selectedResolution = {}; break; } else if ( le.MousePressRight( buttonCancel.area() ) ) { fheroes2::Text header( _( "Cancel" ), fheroes2::FontType::normalYellow() ); fheroes2::Text body( _( "Exit this menu without doing anything." ), fheroes2::FontType::normalWhite() ); fheroes2::showMessage( header, body, 0 ); } else if ( le.MousePressRight( buttonOk.area() ) ) { fheroes2::Text header( _( "Okay" ), fheroes2::FontType::normalYellow() ); fheroes2::Text body( _( "Click to apply the selected resolution." ), fheroes2::FontType::normalWhite() ); fheroes2::showMessage( header, body, 0 ); } if ( !listBox.IsNeedRedraw() ) { continue; } if ( needRedraw ) { selectedResolution = listBox.GetCurrent(); selectedResBackground.restore(); RedrawInfo( selectedResRoi.getPosition(), selectedResolution, display ); } listBox.Redraw(); display.render( roi ); } return selectedResolution; } } namespace Dialog { bool SelectResolution() { const fheroes2::ResolutionInfo selectedResolution = getNewResolution(); if ( selectedResolution == fheroes2::ResolutionInfo{} ) { return false; } fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::ResolutionInfo currentResolution{ display.width(), display.height(), display.screenSize().width, display.screenSize().height }; if ( selectedResolution.gameWidth > 0 && selectedResolution.gameHeight > 0 && selectedResolution.screenWidth >= selectedResolution.gameWidth && selectedResolution.screenHeight >= selectedResolution.gameHeight && selectedResolution != currentResolution ) { display.setResolution( selectedResolution ); #if !defined( MACOS_APP_BUNDLE ) const fheroes2::Image & appIcon = CreateImageFromZlib( 32, 32, iconImage, sizeof( iconImage ), true ); fheroes2::engine().setIcon( appIcon ); #endif return true; } return false; } } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_resolution.h000066400000000000000000000030441456075706000237000ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once namespace Dialog { bool SelectResolution(); // returns true if a new resolution is set } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_selectcount.cpp000066400000000000000000000473231456075706000243700ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include "agg_image.h" #include "cursor.h" #include "dialog.h" #include "game_delays.h" #include "game_hotkeys.h" #include "icn.h" #include "image.h" #include "localevent.h" #include "math_base.h" #include "screen.h" #include "settings.h" #include "system.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_keyboard.h" #include "ui_text.h" #include "ui_tool.h" namespace { const int32_t textOffset = 24; void SwitchMaxMinButtons( fheroes2::ButtonBase & minButton, fheroes2::ButtonBase & maxButton, uint32_t currentValue, uint32_t minimumValue ) { const bool isMinValue = ( currentValue <= minimumValue ); if ( isMinValue ) { minButton.hide(); maxButton.show(); } else { minButton.show(); maxButton.hide(); } minButton.draw(); maxButton.draw(); } } class SelectValue : public fheroes2::Rect { public: SelectValue( uint32_t min, uint32_t max, uint32_t cur, uint32_t st ) : vmin( min ) , vmax( max ) , vcur( cur ) , step( st ) , timedBtnUp( [this]() { return btnUp.isPressed(); } ) , timedBtnDn( [this]() { return btnDn.isPressed(); } ) { vmin = std::min( vmin, vmax ); if ( vcur > vmax || vcur < vmin ) { vcur = vmin; } btnUp.setICNInfo( ICN::TOWNWIND, 5, 6 ); btnDn.setICNInfo( ICN::TOWNWIND, 7, 8 ); btnUp.subscribe( &timedBtnUp ); btnDn.subscribe( &timedBtnDn ); pos.width = 90; pos.height = 30; } void SetCur( uint32_t v ) { vcur = v; } void SetPos( const fheroes2::Point & pt ) { pos.x = pt.x; pos.y = pt.y; btnUp.setPosition( pt.x + 70, pt.y ); btnDn.setPosition( pt.x + 70, pt.y + 16 ); } uint32_t getCur() const { return vcur; } void Redraw() const { fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::Sprite & sprite_edit = fheroes2::AGG::GetICN( ICN::TOWNWIND, 4 ); fheroes2::Blit( sprite_edit, display, pos.x, pos.y + 4 ); const fheroes2::Text text( std::to_string( vcur ), fheroes2::FontType::normalWhite() ); text.draw( pos.x + ( sprite_edit.width() - text.width() ) / 2, pos.y + 7, display ); btnUp.draw(); btnDn.draw(); } bool QueueEventProcessing() { LocalEvent & le = LocalEvent::Get(); le.MousePressLeft( btnUp.area() ) ? btnUp.drawOnPress() : btnUp.drawOnRelease(); le.MousePressLeft( btnDn.area() ) ? btnDn.drawOnPress() : btnDn.drawOnRelease(); if ( ( le.MouseWheelUp() || le.MouseClickLeft( btnUp.area() ) || timedBtnUp.isDelayPassed() ) && vcur < vmax ) { vcur += ( ( vcur + step ) <= vmax ) ? step : ( vmax - vcur ); return true; } if ( ( le.MouseWheelDn() || le.MouseClickLeft( btnDn.area() ) || timedBtnDn.isDelayPassed() ) && vmin < vcur ) { vcur -= ( ( vmin + vcur ) >= step ) ? step : ( vcur - vmin ); return true; } return false; } protected: uint32_t vmin; uint32_t vmax; uint32_t vcur; uint32_t step; fheroes2::Rect pos; fheroes2::Button btnUp; fheroes2::Button btnDn; fheroes2::TimedEventValidator timedBtnUp; fheroes2::TimedEventValidator timedBtnDn; }; bool Dialog::SelectCount( const std::string & header, uint32_t min, uint32_t max, uint32_t & cur, int step ) { fheroes2::Display & display = fheroes2::Display::instance(); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); fheroes2::Text text( header, fheroes2::FontType::normalWhite() ); const int spacer = 10; const FrameBox box( text.height() + spacer + 30, true ); SelectValue sel( min, max, cur, step ); const fheroes2::Rect & pos = box.GetArea(); text.draw( pos.x + ( pos.width - text.width() ) / 2, pos.y + 2, display ); sel.SetPos( fheroes2::Point( pos.x + 80, pos.y + 30 ) ); sel.Redraw(); fheroes2::ButtonGroup btnGroups( box.GetArea(), Dialog::OK | Dialog::CANCEL ); btnGroups.draw(); text.set( _( "MAX" ), fheroes2::FontType::smallWhite() ); const fheroes2::Rect rectMax( pos.x + 173, pos.y + 38, text.width(), text.height() ); text.draw( rectMax.x, rectMax.y + 2, display ); LocalEvent & le = LocalEvent::Get(); display.render(); // message loop int result = Dialog::ZERO; while ( result == Dialog::ZERO && le.HandleEvents() ) { bool redraw_count = false; if ( fheroes2::PressIntKey( max, cur ) ) { sel.SetCur( cur ); redraw_count = true; } // max if ( le.MouseClickLeft( rectMax ) ) { sel.SetCur( max ); redraw_count = true; } if ( sel.QueueEventProcessing() ) { redraw_count = true; } if ( redraw_count ) { sel.Redraw(); display.render(); } result = btnGroups.processEvents(); } cur = result == Dialog::OK ? sel.getCur() : 0; return result == Dialog::OK; } bool Dialog::InputString( const std::string & header, std::string & res, const std::string & title, const size_t charLimit ) { fheroes2::Display & display = fheroes2::Display::instance(); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); res.clear(); res.reserve( 48 ); size_t charInsertPos = 0; const fheroes2::Text titlebox( title, fheroes2::FontType::normalYellow() ); const fheroes2::Text textbox( header, fheroes2::FontType::normalWhite() ); const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); const fheroes2::Sprite & inputArea = fheroes2::AGG::GetICN( ( isEvilInterface ? ICN::BUYBUILD : ICN::BUYBUILE ), 3 ); const int32_t titleHeight = title.empty() ? 0 : titlebox.height( BOXAREA_WIDTH ) + 10; const int32_t keyBoardButtonExtraHeight = 20; const FrameBox box( 10 + titleHeight + textbox.height( BOXAREA_WIDTH ) + 10 + inputArea.height() + keyBoardButtonExtraHeight, true ); const fheroes2::Rect & box_rt = box.GetArea(); if ( !title.empty() ) { titlebox.draw( box_rt.x + ( box_rt.width - textbox.width() ) / 2, box_rt.y + 12, BOXAREA_WIDTH, display ); } // text fheroes2::Point dst_pt; dst_pt.x = box_rt.x; dst_pt.y = box_rt.y + 10 + titleHeight; textbox.draw( dst_pt.x, dst_pt.y + 2, BOXAREA_WIDTH, display ); dst_pt.y = box_rt.y + 10 + titleHeight + textbox.height( BOXAREA_WIDTH ) + 10; dst_pt.x = box_rt.x + ( box_rt.width - inputArea.width() ) / 2; fheroes2::Blit( inputArea, display, dst_pt.x, dst_pt.y ); const fheroes2::Rect text_rt( dst_pt.x, dst_pt.y, inputArea.width(), inputArea.height() ); fheroes2::Text text( "_", fheroes2::FontType::normalWhite() ); fheroes2::Blit( inputArea, display, text_rt.x, text_rt.y ); text.draw( text_rt.x + ( text_rt.width - text.width() ) / 2, text_rt.y + 3, display ); const int okayButtonICNID = isEvilInterface ? ICN::UNIFORM_EVIL_OKAY_BUTTON : ICN::UNIFORM_GOOD_OKAY_BUTTON; dst_pt.x = box_rt.x; dst_pt.y = box_rt.y + box_rt.height - fheroes2::AGG::GetICN( okayButtonICNID, 0 ).height(); fheroes2::Button buttonOk( dst_pt.x, dst_pt.y, okayButtonICNID, 0, 1 ); const int cancelButtonIcnID = isEvilInterface ? ICN::UNIFORM_EVIL_CANCEL_BUTTON : ICN::UNIFORM_GOOD_CANCEL_BUTTON; dst_pt.x = box_rt.x + box_rt.width - fheroes2::AGG::GetICN( cancelButtonIcnID, 0 ).width(); dst_pt.y = box_rt.y + box_rt.height - fheroes2::AGG::GetICN( cancelButtonIcnID, 0 ).height(); fheroes2::Button buttonCancel( dst_pt.x, dst_pt.y, cancelButtonIcnID, 0, 1 ); // Generate a button to open the Virtual Keyboard window. fheroes2::Sprite releasedVirtualKB; fheroes2::Sprite pressedVirtualKB; const int32_t buttonVirtualKBWidth = 40; makeButtonSprites( releasedVirtualKB, pressedVirtualKB, "...", buttonVirtualKBWidth, isEvilInterface, true ); // To center the button horizontally we have to take into account that actual button sprite is 10 pixels longer then the requested button width. fheroes2::ButtonSprite buttonVirtualKB = makeButtonWithBackground( box_rt.x + ( box_rt.width - buttonVirtualKBWidth - 10 ) / 2, dst_pt.y - 30, releasedVirtualKB, pressedVirtualKB, display ); if ( res.empty() ) { buttonOk.disable(); } else { buttonOk.enable(); } buttonOk.draw(); buttonCancel.draw(); buttonVirtualKB.draw(); display.render(); LocalEvent & le = LocalEvent::Get(); bool isCursorVisible = true; Game::AnimateResetDelay( Game::DelayType::CURSOR_BLINK_DELAY ); const bool isInGameKeyboardRequired = System::isVirtualKeyboardSupported(); while ( le.HandleEvents( Game::isDelayNeeded( { Game::DelayType::CURSOR_BLINK_DELAY } ) ) ) { bool redraw = false; buttonOk.isEnabled() && le.MousePressLeft( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); le.MousePressLeft( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); le.MousePressLeft( buttonVirtualKB.area() ) ? buttonVirtualKB.drawOnPress() : buttonVirtualKB.drawOnRelease(); if ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) || ( buttonOk.isEnabled() && le.MouseClickLeft( buttonOk.area() ) ) ) { break; } if ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancel.area() ) ) { res.clear(); break; } if ( le.MouseClickLeft( buttonVirtualKB.area() ) || ( isInGameKeyboardRequired && le.MouseClickLeft( text_rt ) ) ) { fheroes2::openVirtualKeyboard( res ); if ( charLimit > 0 && res.size() > charLimit ) { res.resize( charLimit ); } charInsertPos = res.size(); redraw = true; } else if ( le.KeyPress() ) { if ( charLimit == 0 || charLimit > res.size() || le.KeyValue() == fheroes2::Key::KEY_BACKSPACE ) { charInsertPos = InsertKeySym( res, charInsertPos, le.KeyValue(), LocalEvent::getCurrentKeyModifiers() ); redraw = true; } } else if ( le.MouseClickLeft( text_rt ) ) { charInsertPos = fheroes2::getTextInputCursorPosition( res, fheroes2::FontType::normalWhite(), charInsertPos, le.GetMouseCursor().x, text_rt.x + ( text_rt.width - text.width() ) / 2 ); redraw = true; } if ( le.MousePressRight( buttonCancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Exit this menu without doing anything." ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonOk.area() ) ) { fheroes2::showStandardTextMessage( _( "Okay" ), _( "Click to apply the entered text." ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonVirtualKB.area() ) ) { fheroes2::showStandardTextMessage( _( "Open Virtual Keyboard" ), _( "Click to open the Virtual Keyboard dialog." ), Dialog::ZERO ); } // Text input cursor blink. if ( Game::validateAnimationDelay( Game::DelayType::CURSOR_BLINK_DELAY ) ) { isCursorVisible = !isCursorVisible; redraw = true; } if ( redraw ) { if ( res.empty() && buttonOk.isEnabled() ) { buttonOk.disable(); buttonOk.draw(); } if ( !res.empty() && !buttonOk.isEnabled() ) { buttonOk.enable(); buttonOk.draw(); } text.set( insertCharToString( res, charInsertPos, isCursorVisible ? '_' : '\x7F' ), fheroes2::FontType::normalWhite() ); text.fitToOneRow( inputArea.width() - textOffset ); fheroes2::Blit( inputArea, display, text_rt.x, text_rt.y ); text.draw( text_rt.x + ( text_rt.width - text.width() ) / 2, text_rt.y + 3, display ); display.render(); } } return !res.empty(); } int Dialog::ArmySplitTroop( uint32_t freeSlots, const uint32_t redistributeMax, uint32_t & redistributeCount, bool & useFastSplit ) { assert( freeSlots > 0 ); fheroes2::Display & display = fheroes2::Display::instance(); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); const uint32_t min = std::min( 1U, redistributeMax ); const int spacer = 10; const int defaultYPosition = 160; const int boxHeight = freeSlots > 1 ? 90 + spacer : 45; const int boxYPosition = defaultYPosition + ( ( display.height() - display.DEFAULT_HEIGHT ) / 2 ) - boxHeight; const NonFixedFrameBox box( boxHeight, boxYPosition, true ); SelectValue sel( min, redistributeMax, redistributeCount, 1 ); const fheroes2::Rect & pos = box.GetArea(); const int center = pos.x + pos.width / 2; fheroes2::Text text( _( "How many troops to move?" ), fheroes2::FontType::normalWhite() ); text.draw( center - text.width() / 2, pos.y + 2, display ); sel.SetPos( fheroes2::Point( pos.x + 70, pos.y + 30 ) ); sel.Redraw(); fheroes2::MovableSprite ssp; std::vector vrts( freeSlots - 1 ); if ( freeSlots > 1 ) { std::vector sprites( freeSlots - 1 ); int spriteIconIdx = 21; const int deltaX = 10; const int deltaXStart = static_cast( freeSlots - 2 ) * -5; for ( uint32_t i = 0; i < freeSlots - 1; ++i ) { sprites[i] = fheroes2::AGG::GetICN( ICN::REQUESTS, spriteIconIdx ); ++spriteIconIdx; const int spriteWidth = sprites[i].width(); const int offset = spriteWidth * ( 2 * static_cast( i ) + 1 - static_cast( freeSlots ) ) / 2; vrts[i] = fheroes2::Rect( center + offset + deltaXStart + i * deltaX, pos.y + 95, spriteWidth, sprites[i].height() ); } text.set( _( "Fast separation into slots:" ), fheroes2::FontType::normalWhite() ); text.draw( center - text.width() / 2, pos.y + 67, display ); for ( uint32_t i = 0; i < freeSlots - 1; ++i ) { fheroes2::Blit( sprites[i], display, vrts[i].x, vrts[i].y ); } ssp.resize( sprites[0].width(), sprites[0].height() ); ssp.reset(); fheroes2::DrawBorder( ssp, 214 ); if ( useFastSplit ) { ssp.setPosition( vrts[0].x, vrts[0].y ); ssp.show(); } } fheroes2::ButtonGroup btnGroups( box.GetArea(), Dialog::OK | Dialog::CANCEL ); btnGroups.draw(); const fheroes2::Point minMaxButtonOffset( pos.x + 165, pos.y + 30 ); const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); fheroes2::Button buttonMax( minMaxButtonOffset.x, minMaxButtonOffset.y, isEvilInterface ? ICN::UNIFORM_EVIL_MAX_BUTTON : ICN::UNIFORM_GOOD_MAX_BUTTON, 0, 1 ); fheroes2::Button buttonMin( minMaxButtonOffset.x, minMaxButtonOffset.y, isEvilInterface ? ICN::UNIFORM_EVIL_MIN_BUTTON : ICN::UNIFORM_GOOD_MIN_BUTTON, 0, 1 ); const fheroes2::Rect buttonArea( 5, 0, 61, 25 ); SwitchMaxMinButtons( buttonMin, buttonMax, redistributeCount, min ); LocalEvent & le = LocalEvent::Get(); display.render(); // message loop int bres = Dialog::ZERO; while ( bres == Dialog::ZERO && le.HandleEvents() ) { bool redraw_count = false; if ( buttonMax.isVisible() ) { le.MousePressLeft( buttonMax.area() ) ? buttonMax.drawOnPress() : buttonMax.drawOnRelease(); } if ( buttonMin.isVisible() ) { le.MousePressLeft( buttonMin.area() ) ? buttonMin.drawOnPress() : buttonMin.drawOnRelease(); } if ( fheroes2::PressIntKey( redistributeMax, redistributeCount ) ) { sel.SetCur( redistributeCount ); redraw_count = true; } else if ( buttonMax.isVisible() && le.MouseClickLeft( buttonMax.area() ) ) { le.MousePressLeft( buttonMax.area() ) ? buttonMax.drawOnPress() : buttonMax.drawOnRelease(); redistributeCount = redistributeMax; sel.SetCur( redistributeMax ); redraw_count = true; } else if ( buttonMin.isVisible() && le.MouseClickLeft( buttonMin.area() ) ) { le.MousePressLeft( buttonMin.area() ) ? buttonMin.drawOnPress() : buttonMin.drawOnRelease(); redistributeCount = min; sel.SetCur( min ); redraw_count = true; } else if ( sel.QueueEventProcessing() ) { redraw_count = true; } if ( !ssp.empty() ) { for ( std::vector::const_iterator it = vrts.begin(); it != vrts.end(); ++it ) { if ( le.MouseClickLeft( *it ) ) { ssp.setPosition( it->x, it->y ); ssp.show(); display.render(); } } } if ( redraw_count ) { SwitchMaxMinButtons( buttonMin, buttonMax, sel.getCur(), min ); if ( !ssp.empty() ) { ssp.hide(); } sel.Redraw(); if ( buttonMax.isVisible() ) { buttonMax.draw(); } if ( buttonMin.isVisible() ) { buttonMin.draw(); } display.render(); } bres = btnGroups.processEvents(); } int result = 0; if ( bres == Dialog::OK ) { redistributeCount = sel.getCur(); if ( !ssp.isHidden() ) { const fheroes2::Rect rt( ssp.x(), ssp.y(), ssp.width(), ssp.height() ); for ( uint32_t i = 0; i < freeSlots - 1; ++i ) { if ( rt == vrts[i] ) { result = i + 2; break; } } useFastSplit = true; } else { result = 2; useFastSplit = false; } } return result; } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_selectfile.cpp000066400000000000000000000606711456075706000241600ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include "agg_image.h" #include "cursor.h" #include "dialog.h" #include "dir.h" #include "game_delays.h" #include "game_hotkeys.h" #include "game_io.h" #include "icn.h" #include "image.h" #include "interface_list.h" #include "localevent.h" #include "maps_fileinfo.h" #include "math_base.h" #include "screen.h" #include "settings.h" #include "system.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_keyboard.h" #include "ui_scrollbar.h" #include "ui_text.h" #include "ui_tool.h" #include "ui_window.h" #include "world.h" namespace { // This constant sets the maximum displayed file name width. This value affects the dialog horizontal size. const int32_t maxFileNameWidth = 260; std::string ResizeToShortName( const std::string & str ) { std::string res = System::GetBasename( str ); const size_t it = res.rfind( '.' ); if ( std::string::npos != it ) { res.resize( it ); } return res; } void redrawDateTime( fheroes2::Image & output, const time_t timestamp, const int32_t dstx, const int32_t dsty, const fheroes2::FontType font ) { const size_t arraySize = 5; char shortMonth[arraySize]; char shortDate[arraySize]; char shortHours[arraySize]; char shortMinutes[arraySize]; const tm tmi = System::GetTM( timestamp ); // TODO: Use system locale date format (e.g. 9 Mar instead of Mar 09). std::strftime( shortMonth, arraySize, "%b", &tmi ); std::strftime( shortDate, arraySize, "%d", &tmi ); std::strftime( shortHours, arraySize, "%H", &tmi ); std::strftime( shortMinutes, arraySize, "%M", &tmi ); fheroes2::Text text( shortMonth, font ); text.draw( dstx + 4, dsty, output ); text.set( shortDate, font ); text.draw( dstx + 56 - text.width() / 2, dsty, output ); text.set( ",", font ); text.draw( dstx + 66, dsty, output ); text.set( shortHours, font ); text.draw( dstx + 82 - text.width() / 2, dsty, output ); text.set( ":", font ); text.draw( dstx + 92, dsty, output ); text.set( shortMinutes, font ); text.draw( dstx + 105 - text.width() / 2, dsty, output ); } bool redrawTextInputField( const std::string & filename, const fheroes2::Rect & field, const bool isEditing ) { if ( filename.empty() ) { return false; } fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::Text currentFilename( filename, isEditing ? fheroes2::FontType::normalWhite() : fheroes2::FontType::normalYellow() ); const int32_t initialTextWidth = currentFilename.width(); currentFilename.fitToOneRow( maxFileNameWidth ); currentFilename.draw( field.x + 4 + ( maxFileNameWidth - currentFilename.width() ) / 2, field.y + 4, display ); return ( initialTextWidth + 10 ) > maxFileNameWidth; } class FileInfoListBox : public Interface::ListBox { public: using Interface::ListBox::ActionListDoubleClick; using Interface::ListBox::ActionListSingleClick; using Interface::ListBox::ActionListPressRight; using ListBox::ListBox; void RedrawItem( const Maps::FileInfo & info, int32_t dstx, int32_t dsty, bool current ) override; void RedrawBackground( const fheroes2::Point & dst ) override; void ActionCurrentUp() override; void ActionCurrentDn() override; void ActionListDoubleClick( Maps::FileInfo & info ) override; void ActionListSingleClick( Maps::FileInfo & info ) override; void ActionListPressRight( Maps::FileInfo & info ) override { // On some OSes like Windows, the path may contain '\' symbols. This symbol doesn't exist in the resources. // To avoid this we have to replace all '\' symbols by '/' symbols. std::string fullPath = info.filename; // TODO: Make '\' symbol in small font to properly show file path in OS familiar style. StringReplace( fullPath, "\\", "/" ); const fheroes2::Text header( ResizeToShortName( info.filename ), fheroes2::FontType::normalYellow() ); fheroes2::MultiFontText body; body.add( { _( "Map: " ), fheroes2::FontType::normalYellow() } ); body.add( { info.name, fheroes2::FontType::normalWhite() } ); if ( info.worldDay > 0 || info.worldWeek > 0 || info.worldMonth > 0 ) { body.add( { _( "\n\nMonth: " ), fheroes2::FontType::normalYellow() } ); body.add( { std::to_string( info.worldMonth ), fheroes2::FontType::normalWhite() } ); body.add( { _( ", Week: " ), fheroes2::FontType::normalYellow() } ); body.add( { std::to_string( info.worldWeek ), fheroes2::FontType::normalWhite() } ); body.add( { _( ", Day: " ), fheroes2::FontType::normalYellow() } ); body.add( { std::to_string( info.worldDay ), fheroes2::FontType::normalWhite() } ); } body.add( { _( "\n\nLocation: " ), fheroes2::FontType::smallYellow() } ); body.add( { fullPath, fheroes2::FontType::smallWhite() } ); fheroes2::showMessage( header, body, Dialog::ZERO ); } int getCurrentId() const { return _currentId; } void initListBackgroundRestorer( fheroes2::Rect roi ) { _listBackground = std::make_unique( fheroes2::Display::instance(), roi.x, roi.y, roi.width, roi.height ); } bool isDoubleClicked() const { return _isDoubleClicked; } void updateScrollBarImage() { const int32_t scrollBarWidth = _scrollbar.width(); setScrollBarImage( fheroes2::generateScrollbarSlider( _scrollbar, false, _scrollbar.getArea().height, VisibleItemCount(), _size(), { 0, 0, scrollBarWidth, 8 }, { 0, 7, scrollBarWidth, 8 } ) ); _scrollbar.moveToIndex( _topId ); } private: bool _isDoubleClicked{ false }; std::unique_ptr _listBackground; }; void FileInfoListBox::RedrawItem( const Maps::FileInfo & info, int32_t dstx, int32_t dsty, bool current ) { std::string savname( System::GetBasename( info.filename ) ); if ( savname.empty() ) { return; } const std::string saveExtension = Game::GetSaveFileExtension(); const size_t dotPos = savname.size() - saveExtension.size(); if ( StringLower( savname.substr( dotPos ) ) == saveExtension ) { savname.erase( dotPos ); } const fheroes2::FontType font = current ? fheroes2::FontType::normalYellow() : fheroes2::FontType::normalWhite(); fheroes2::Display & display = fheroes2::Display::instance(); dsty += 2; fheroes2::Text text{ std::move( savname ), font }; text.fitToOneRow( maxFileNameWidth ); text.draw( dstx + 4 + ( maxFileNameWidth - text.width() ) / 2, dsty, display ); redrawDateTime( display, info.timestamp, dstx + maxFileNameWidth + 9, dsty, font ); } void FileInfoListBox::RedrawBackground( const fheroes2::Point & /* unused */ ) { _listBackground->restore(); } void FileInfoListBox::ActionCurrentUp() { // Do nothing. } void FileInfoListBox::ActionCurrentDn() { // Do nothing. } void FileInfoListBox::ActionListDoubleClick( Maps::FileInfo & /*unused*/ ) { _isDoubleClicked = true; } void FileInfoListBox::ActionListSingleClick( Maps::FileInfo & /*unused*/ ) { // Do nothing. } MapsFileInfoList getSortedMapsFileInfoList() { ListFiles files; files.ReadDir( Game::GetSaveDir(), Game::GetSaveFileExtension(), false ); MapsFileInfoList mapInfos; mapInfos.reserve( files.size() ); for ( std::string & saveFile : files ) { Maps::FileInfo mapInfo; if ( Game::LoadSAV2FileInfo( std::move( saveFile ), mapInfo ) ) { mapInfos.emplace_back( std::move( mapInfo ) ); } } std::sort( mapInfos.begin(), mapInfos.end(), Maps::FileInfo::sortByFileName ); return mapInfos; } std::string selectFileListSimple( const std::string & header, const std::string & lastfile, const bool isEditing ) { // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); MapsFileInfoList lists = getSortedMapsFileInfoList(); const int32_t listHeightDeduction = 112; const int32_t listAreaOffsetY = 3; const int32_t listAreaHeightDeduction = 4; // If we don't have many save files, we reduce the maximum dialog height, // but not less than enough for 11 elements. // We also limit the maximum list height to 22 lines. const int32_t maxDialogHeight = fheroes2::getFontHeight( fheroes2::FontSize::NORMAL ) * std::clamp( static_cast( lists.size() ), 11, 22 ) + listAreaOffsetY + listAreaHeightDeduction + listHeightDeduction; fheroes2::Display & display = fheroes2::Display::instance(); // Dialog height is also capped with the current screen height. fheroes2::StandardWindow background( maxFileNameWidth + 204, std::min( display.height() - 100, maxDialogHeight ), true, display ); const fheroes2::Rect area( background.activeArea() ); const fheroes2::Rect listRoi( area.x + 24, area.y + 37, area.width - 75, area.height - listHeightDeduction ); const fheroes2::Rect textInputRoi( listRoi.x, listRoi.y + listRoi.height + 12, maxFileNameWidth + 8, 21 ); const int32_t dateTimeoffsetX = textInputRoi.x + textInputRoi.width; const int32_t dateTimeWidth = listRoi.width - textInputRoi.width; // We divide the save-files list: file name and file date/time. background.applyTextBackgroundShading( { listRoi.x, listRoi.y, textInputRoi.width, listRoi.height } ); background.applyTextBackgroundShading( { listRoi.x + textInputRoi.width, listRoi.y, dateTimeWidth, listRoi.height } ); background.applyTextBackgroundShading( textInputRoi ); // Make background for the selected file date and time. background.applyTextBackgroundShading( { dateTimeoffsetX, textInputRoi.y, dateTimeWidth, textInputRoi.height } ); fheroes2::ImageRestorer textInputAndDateBackground( fheroes2::Display::instance(), textInputRoi.x, textInputRoi.y, listRoi.width, textInputRoi.height ); const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); // Prepare OKAY and CANCEL buttons and render their shadows. fheroes2::Button buttonOk; if ( !isEditing && lists.empty() ) { buttonOk.disable(); } fheroes2::Button buttonCancel; background.renderOkayCancelButtons( buttonOk, buttonCancel, isEvilInterface ); // Virtual keyboard button is used only in save game mode (when 'isEditing' is true ). std::unique_ptr buttonVirtualKB; FileInfoListBox listbox( area.getPosition() ); // Initialize list background restorer to use it in list method 'listbox.RedrawBackground()'. listbox.initListBackgroundRestorer( listRoi ); listbox.SetAreaItems( { listRoi.x, listRoi.y + 3, listRoi.width - listAreaOffsetY, listRoi.height - listAreaHeightDeduction } ); int32_t scrollbarOffsetX = area.x + area.width - 35; background.renderScrollbarBackground( { scrollbarOffsetX, listRoi.y, listRoi.width, listRoi.height }, isEvilInterface ); const int listIcnId = isEvilInterface ? ICN::SCROLLE : ICN::SCROLL; const int32_t topPartHeight = 19; ++scrollbarOffsetX; listbox.SetScrollButtonUp( listIcnId, 0, 1, { scrollbarOffsetX, listRoi.y + 1 } ); listbox.SetScrollButtonDn( listIcnId, 2, 3, { scrollbarOffsetX, listRoi.y + listRoi.height - 15 } ); listbox.setScrollBarArea( { scrollbarOffsetX + 2, listRoi.y + topPartHeight, 10, listRoi.height - 2 * topPartHeight } ); listbox.setScrollBarImage( fheroes2::AGG::GetICN( listIcnId, 4 ) ); listbox.SetAreaMaxItems( ( listRoi.height - 7 ) / fheroes2::getFontHeight( fheroes2::FontSize::NORMAL ) ); listbox.SetListContent( lists ); listbox.updateScrollBarImage(); std::string filename; size_t charInsertPos = 0; if ( !lastfile.empty() ) { filename = ResizeToShortName( lastfile ); charInsertPos = filename.size(); MapsFileInfoList::iterator it = lists.begin(); for ( ; it != lists.end(); ++it ) { if ( ( *it ).filename == lastfile ) { break; } } if ( it != lists.end() ) { listbox.SetCurrent( std::distance( lists.begin(), it ) ); } else { if ( !isEditing ) { filename.clear(); charInsertPos = 0; } listbox.Unselect(); } } if ( filename.empty() && listbox.isSelected() ) { filename = ResizeToShortName( listbox.GetCurrent().filename ); charInsertPos = filename.size(); } listbox.Redraw(); redrawTextInputField( filename, textInputRoi, isEditing ); const fheroes2::Text title( header, fheroes2::FontType::normalYellow() ); title.draw( area.x + ( area.width - title.width() ) / 2, area.y + 16, display ); if ( isEditing ) { // Render a button to open the Virtual Keyboard window. buttonVirtualKB = std::make_unique(); background.renderButtonSprite( *buttonVirtualKB, "...", 48, { 0, 7 }, isEvilInterface, fheroes2::StandardWindow::Padding::BOTTOM_CENTER ); Game::passAnimationDelay( Game::DelayType::CURSOR_BLINK_DELAY ); } else if ( listbox.isSelected() ) { // Render the saved file date and time. redrawDateTime( display, listbox.GetCurrent().timestamp, dateTimeoffsetX, textInputRoi.y + 4, fheroes2::FontType::normalYellow() ); } display.render( background.totalArea() ); std::string result; bool isTextLimit = false; std::string lastSelectedSaveFileName; const bool isInGameKeyboardRequired = System::isVirtualKeyboardSupported(); bool isCursorVisible = true; LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents( !isEditing || Game::isDelayNeeded( { Game::DelayType::CURSOR_BLINK_DELAY } ) ) && result.empty() ) { le.MousePressLeft( buttonOk.area() ) && buttonOk.isEnabled() ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); le.MousePressLeft( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); if ( isEditing ) { le.MousePressLeft( buttonVirtualKB->area() ) ? buttonVirtualKB->drawOnPress() : buttonVirtualKB->drawOnRelease(); } const int listId = listbox.getCurrentId(); const bool listboxEvent = listbox.QueueEventProcessing(); bool isListboxSelected = listbox.isSelected(); bool needRedraw = listId != listbox.getCurrentId(); if ( ( buttonOk.isEnabled() && le.MouseClickLeft( buttonOk.area() ) ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) || listbox.isDoubleClicked() ) { if ( !filename.empty() ) { result = System::concatPath( Game::GetSaveDir(), filename + Game::GetSaveFileExtension() ); } else if ( isListboxSelected ) { result = listbox.GetCurrent().filename; } } else if ( le.MouseClickLeft( buttonCancel.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { break; } else if ( isEditing ) { if ( le.MouseClickLeft( buttonVirtualKB->area() ) || ( isInGameKeyboardRequired && le.MouseClickLeft( textInputRoi ) ) ) { fheroes2::openVirtualKeyboard( filename ); charInsertPos = filename.size(); listbox.Unselect(); isListboxSelected = false; needRedraw = true; // Set the whole screen to redraw next time to properly restore image under the Virtual Keyboard dialog. display.updateNextRenderRoi( { 0, 0, display.width(), display.height() } ); } else if ( le.MouseClickLeft( textInputRoi ) ) { const fheroes2::Text text( filename, fheroes2::FontType::normalWhite() ); const int32_t textStartOffsetX = isTextLimit ? 8 : ( textInputRoi.width - text.width() ) / 2; charInsertPos = fheroes2::getTextInputCursorPosition( filename, fheroes2::FontType::normalWhite(), charInsertPos, le.GetMouseCursor().x, textInputRoi.x + textStartOffsetX ); if ( filename.empty() ) { buttonOk.disable(); } listbox.Unselect(); isListboxSelected = false; needRedraw = true; } else if ( !listboxEvent && le.KeyPress() && ( !isTextLimit || fheroes2::Key::KEY_BACKSPACE == le.KeyValue() || fheroes2::Key::KEY_DELETE == le.KeyValue() ) ) { charInsertPos = InsertKeySym( filename, charInsertPos, le.KeyValue(), LocalEvent::getCurrentKeyModifiers() ); if ( filename.empty() ) { buttonOk.disable(); } else { buttonOk.enable(); } needRedraw = true; listbox.Unselect(); isListboxSelected = false; } } if ( le.MousePressRight( buttonCancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Exit this menu without doing anything." ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonOk.area() ) ) { if ( isEditing ) { fheroes2::showStandardTextMessage( _( "Okay" ), _( "Click to save the current game." ), Dialog::ZERO ); } else { fheroes2::showStandardTextMessage( _( "Okay" ), _( "Click to load a previously saved game." ), Dialog::ZERO ); } } else if ( isEditing && le.MousePressRight( buttonVirtualKB->area() ) ) { fheroes2::showStandardTextMessage( _( "Open Virtual Keyboard" ), _( "Click to open the Virtual Keyboard dialog." ), Dialog::ZERO ); } if ( !isEditing && le.KeyPress( fheroes2::Key::KEY_DELETE ) && isListboxSelected ) { listbox.SetCurrent( listId ); listbox.Redraw(); std::string msg( _( "Are you sure you want to delete file:" ) ); msg.append( "\n\n" ); msg.append( System::GetBasename( listbox.GetCurrent().filename ) ); if ( Dialog::YES == fheroes2::showStandardTextMessage( _( "Warning!" ), msg, Dialog::YES | Dialog::NO ) ) { System::Unlink( listbox.GetCurrent().filename ); listbox.RemoveSelected(); if ( lists.empty() || filename.empty() ) { buttonOk.disable(); isListboxSelected = false; filename.clear(); } listbox.updateScrollBarImage(); listbox.SetCurrent( std::max( listId - 1, 0 ) ); } needRedraw = true; } // Text input cursor blink. if ( isEditing && Game::validateAnimationDelay( Game::DelayType::CURSOR_BLINK_DELAY ) ) { isCursorVisible = !isCursorVisible; needRedraw = true; } if ( !needRedraw && !listbox.IsNeedRedraw() ) { continue; } if ( listbox.IsNeedRedraw() ) { listbox.Redraw(); } if ( needRedraw ) { const std::string selectedFileName = isListboxSelected ? ResizeToShortName( listbox.GetCurrent().filename ) : ""; if ( isListboxSelected && lastSelectedSaveFileName != selectedFileName ) { lastSelectedSaveFileName = selectedFileName; filename = selectedFileName; charInsertPos = filename.size(); } else if ( isEditing ) { // Empty last selected save file name so that we can replace the input field's name if we select the same save file again. // But when loading (i.e. isEditing == false), this doesn't matter since we cannot write to the input field lastSelectedSaveFileName = ""; } textInputAndDateBackground.restore(); if ( isEditing ) { isTextLimit = redrawTextInputField( insertCharToString( filename, charInsertPos, isCursorVisible ? '_' : '\x7F' ), textInputRoi, true ); redrawDateTime( display, std::time( nullptr ), dateTimeoffsetX, textInputRoi.y + 4, fheroes2::FontType::normalWhite() ); } else { redrawTextInputField( filename, textInputRoi, false ); redrawDateTime( display, listbox.GetCurrent().timestamp, dateTimeoffsetX, textInputRoi.y + 4, fheroes2::FontType::normalYellow() ); } } display.render( area ); } return result; } } namespace Dialog { std::string SelectFileSave() { std::ostringstream os; os << System::concatPath( Game::GetSaveDir(), Game::GetSaveFileBaseName() ) << '_' << std::setw( 4 ) << std::setfill( '0' ) << world.CountDay() << Game::GetSaveFileExtension(); return selectFileListSimple( _( "File to Save:" ), os.str(), true ); } std::string SelectFileLoad() { const std::string & lastfile = Game::GetLastSaveName(); return selectFileListSimple( _( "File to Load:" ), ( !lastfile.empty() ? lastfile : "" ), false ); } } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_selectitems.cpp000066400000000000000000002013201456075706000243460ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2011 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "dialog_selectitems.h" #include #include #include #include #include #include #include #include #include #include "agg_image.h" #include "army_troop.h" #include "castle.h" #include "color.h" #include "cursor.h" #include "dialog.h" #include "game_hotkeys.h" #include "gamedefs.h" #include "ground.h" #include "heroes_base.h" #include "icn.h" #include "image.h" #include "interface_list.h" #include "kingdom.h" #include "localevent.h" #include "map_object_info.h" #include "maps.h" #include "maps_fileinfo.h" #include "math_base.h" #include "mp2.h" #include "pal.h" #include "race.h" #include "resource.h" #include "screen.h" #include "settings.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_castle.h" #include "ui_dialog.h" #include "ui_map_object.h" #include "ui_scrollbar.h" #include "ui_text.h" #include "ui_tool.h" #include "ui_window.h" #include "world.h" namespace { // Returns the town type according the given player color, race and castle/town state. int getPackedTownType( const int townRace, const bool isCastle ) { // NOTICE: This calculation should be consistent with the number of KINGDOM_TOWNS objects. return townRace * 2 + ( isCastle ? 0 : 1 ); } } class SelectEnum : public Interface::ListBox { public: using Interface::ListBox::ActionListDoubleClick; using Interface::ListBox::ActionListSingleClick; using Interface::ListBox::ActionListPressRight; SelectEnum() = delete; explicit SelectEnum( const fheroes2::Size & dialogSize, std::string title, std::string description = {} ) { assert( !title.empty() ); fheroes2::Display & display = fheroes2::Display::instance(); background = std::make_unique( dialogSize.width, dialogSize.height, true, display ); const fheroes2::Rect & area = background->activeArea(); int32_t listOffsetY = 0; fheroes2::Text text( std::move( title ), fheroes2::FontType::normalYellow() ); text.draw( area.x + ( area.width - text.width() ) / 2, area.y + 10, display ); // The additional text under the title. if ( !description.empty() ) { text.set( std::move( description ), fheroes2::FontType::normalWhite() ); text.draw( area.x + ( area.width - text.width() ) / 2, area.y + 30, display ); listOffsetY = text.height() + 3; } const fheroes2::Rect listRoi( area.x + 10, area.y + 30 + listOffsetY, area.width - 40, area.height - 70 - listOffsetY ); background->applyTextBackgroundShading( listRoi ); listBackground = std::make_unique( display, listRoi.x, listRoi.y, listRoi.width, listRoi.height ); SetAreaItems( { listRoi.x + 5, listRoi.y + 5, listRoi.width - 10, listRoi.height - 10 } ); const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); const int32_t scrollbarOffsetX = area.x + area.width - 25; background->renderScrollbarBackground( { scrollbarOffsetX, listRoi.y, listRoi.width, listRoi.height }, isEvilInterface ); const int32_t topPartHeight = 19; const int listIcnId = isEvilInterface ? ICN::SCROLLE : ICN::SCROLL; SetScrollButtonUp( listIcnId, 0, 1, { scrollbarOffsetX + 1, listRoi.y + 1 } ); SetScrollButtonDn( listIcnId, 2, 3, { scrollbarOffsetX + 1, listRoi.y + listRoi.height - 15 } ); setScrollBarArea( { scrollbarOffsetX + 3, listRoi.y + topPartHeight, 10, listRoi.height - 2 * topPartHeight } ); setScrollBarImage( fheroes2::AGG::GetICN( listIcnId, 4 ) ); // Render dialog buttons. background->renderOkayCancelButtons( buttonOk, buttonCancel, isEvilInterface ); } void RedrawBackground( const fheroes2::Point & /* unused */ ) override { listBackground->restore(); } void ActionListDoubleClick( int & /* unused */ ) override { ok = true; } void RedrawItem( const int & /* unused */, int32_t /* ox */, int32_t /* oy */, bool /* current */ ) override { // Do nothing. } void ActionCurrentUp() override { // Do nothing. } void ActionCurrentDn() override { // Do nothing. } void ActionListSingleClick( int & /* unused */ ) override { // Do nothing. } void ActionListPressRight( int & /* unused */ ) override { // Do nothing. } void updateScrollBarImage() { const int32_t scrollBarWidth = _scrollbar.width(); setScrollBarImage( fheroes2::generateScrollbarSlider( _scrollbar, false, _scrollbar.getArea().height, VisibleItemCount(), _size(), { 0, 0, scrollBarWidth, 8 }, { 0, 7, scrollBarWidth, 8 } ) ); _scrollbar.moveToIndex( _topId ); } // An image with text should have offset of 10 pixels from all left and right edges. void renderItem( const fheroes2::Sprite & itemSprite, const std::string & itemText, const fheroes2::Point & destination, const int32_t middleImageOffsetX, const int32_t textOffsetX, const int32_t itemOffsetY, const bool current ) const { fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::Blit( itemSprite, display, destination.x + middleImageOffsetX - ( itemSprite.width() / 2 ), destination.y + itemOffsetY - ( itemSprite.height() / 2 ) ); fheroes2::Text text( itemText, current ? fheroes2::FontType::normalYellow() : fheroes2::FontType::normalWhite() ); text.fitToOneRow( background->activeArea().width - textOffsetX - 55 ); text.draw( destination.x + textOffsetX, destination.y + itemOffsetY - ( text.height() / 2 ) + 2, display ); } int32_t selectItemsEventProcessing() { fheroes2::Display & display = fheroes2::Display::instance(); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); const fheroes2::Rect & roi = background->activeArea(); updateScrollBarImage(); Redraw(); display.render( background->totalArea() ); LocalEvent & le = LocalEvent::Get(); while ( !ok && le.HandleEvents() ) { le.MousePressLeft( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); le.MousePressLeft( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); if ( le.MouseClickLeft( buttonOk.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) ) { return Dialog::OK; } if ( le.MouseClickLeft( buttonCancel.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { return Dialog::CANCEL; } if ( le.MousePressRight( buttonOk.area() ) ) { fheroes2::showStandardTextMessage( _( "Okay" ), _( "Accept the choice made." ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonCancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Exit this menu without doing anything." ), Dialog::ZERO ); } QueueEventProcessing(); if ( !IsNeedRedraw() ) { continue; } Redraw(); display.render( roi ); } return Dialog::ZERO; } bool ok{ false }; std::unique_ptr background; std::unique_ptr listBackground; fheroes2::Button buttonOk; fheroes2::Button buttonCancel; }; class SelectEnumMonster : public SelectEnum { public: explicit SelectEnumMonster( const fheroes2::Size & rt, std::string title ) : SelectEnum( rt, std::move( title ) ) { SetAreaMaxItems( rtAreaItems.height / _offsetY ); } using SelectEnum::ActionListPressRight; void RedrawItem( const int & index, int32_t dstx, int32_t dsty, bool current ) override { const Monster mons( index ); const fheroes2::Sprite & monsterSprite = fheroes2::AGG::GetICN( ICN::MONS32, mons.GetSpriteIndex() ); renderItem( monsterSprite, mons.GetName(), { dstx, dsty }, 45 / 2, 50, _offsetY / 2, current ); } void ActionListPressRight( int & index ) override { const Monster monster( index ); if ( !monster.isValid() ) { fheroes2::showStandardTextMessage( monster.GetName(), "", Dialog::ZERO ); return; } Dialog::ArmyInfo( Troop( monster, 0 ), Dialog::ZERO ); } private: static const int32_t _offsetY{ 43 }; }; class SelectEnumHeroes : public SelectEnum { public: explicit SelectEnumHeroes( const fheroes2::Size & rt, std::string title ) : SelectEnum( rt, std::move( title ) ) { SetAreaMaxItems( rtAreaItems.height / _offsetY ); } using SelectEnum::ActionListPressRight; void RedrawItem( const int & index, int32_t dstx, int32_t dsty, bool current ) override { const fheroes2::Sprite & port = Heroes::GetPortrait( index, PORT_SMALL ); renderItem( port, Heroes::GetName( index ), { dstx, dsty }, 45 / 2, 50, _offsetY / 2, current ); } void ActionListPressRight( int & index ) override { Dialog::QuickInfo( *world.GetHeroes( index ) ); } private: static const int32_t _offsetY{ 35 }; }; class SelectEnumArtifact : public SelectEnum { public: explicit SelectEnumArtifact( const fheroes2::Size & rt, std::string title ) : SelectEnum( rt, std::move( title ) ) { SetAreaMaxItems( rtAreaItems.height / _offsetY ); } using SelectEnum::ActionListPressRight; void RedrawItem( const int & index, int32_t dstx, int32_t dsty, bool current ) override { const Artifact art( index ); const fheroes2::Sprite & artifactSprite = fheroes2::AGG::GetICN( ICN::ARTFX, art.IndexSprite32() ); renderItem( artifactSprite, art.GetName(), { dstx, dsty }, 45 / 2, 50, _offsetY / 2, current ); } void ActionListPressRight( int & index ) override { fheroes2::ArtifactDialogElement( Artifact( index ) ).showPopup( Dialog::ZERO ); } private: static const int32_t _offsetY{ 42 }; }; class SelectEnumSpell : public SelectEnum { public: explicit SelectEnumSpell( const fheroes2::Size & rt, std::string title ) : SelectEnum( rt, std::move( title ) ) { SetAreaMaxItems( rtAreaItems.height / _offsetY ); } using SelectEnum::ActionListPressRight; void RedrawItem( const int & index, int32_t dstx, int32_t dsty, bool current ) override { const Spell spell( index ); const fheroes2::Sprite & spellSprite = fheroes2::AGG::GetICN( ICN::SPELLS, spell.IndexSprite() ); renderItem( spellSprite, spell.GetName(), { dstx, dsty }, 75 / 2, 80, _offsetY / 2, current ); } void ActionListPressRight( int & index ) override { fheroes2::SpellDialogElement( Spell( index ), nullptr ).showPopup( Dialog::ZERO ); } private: static const int32_t _offsetY{ 55 }; }; class SelectEnumSecSkill : public SelectEnum { public: static int getSkillFromListIndex( int index ) { return 1 + index / 3; } static int getLevelFromListIndex( int index ) { return 1 + ( index % 3 ); } explicit SelectEnumSecSkill( const fheroes2::Size & rt, std::string title ) : SelectEnum( rt, std::move( title ) ) { SetAreaMaxItems( rtAreaItems.height / _offsetY ); } using SelectEnum::ActionListPressRight; void RedrawItem( const int & index, int32_t dstx, int32_t dsty, bool current ) override { const Skill::Secondary skill( getSkillFromListIndex( index ), getLevelFromListIndex( index ) ); const fheroes2::Sprite & skillSprite = fheroes2::AGG::GetICN( ICN::MINISS, skill.GetIndexSprite2() ); renderItem( skillSprite, skill.GetName(), { dstx, dsty }, 45 / 2, 50, _offsetY / 2, current ); } void ActionListPressRight( int & index ) override { fheroes2::SecondarySkillDialogElement( Skill::Secondary( getSkillFromListIndex( index ), getLevelFromListIndex( index ) ), Heroes() ).showPopup( Dialog::ZERO ); } private: static const int32_t _offsetY{ 42 }; }; class SelectKingdomCastle : public SelectEnum { public: explicit SelectKingdomCastle( const fheroes2::Size & rt, std::string title, std::string description ) : SelectEnum( rt, std::move( title ), std::move( description ) ) , _townFrameIcnId( Settings::Get().isEvilInterfaceEnabled() ? ICN::LOCATORE : ICN::LOCATORS ) { SetAreaMaxItems( rtAreaItems.height / itemsOffsetY ); } using SelectEnum::ActionListPressRight; void RedrawItem( const int & index, int32_t dstx, int32_t dsty, bool current ) override { const Castle * castle = world.getCastleEntrance( Maps::GetPoint( index ) ); assert( castle != nullptr ); fheroes2::Sprite castleIcon( fheroes2::AGG::GetICN( _townFrameIcnId, 23 ) ); fheroes2::drawCastleIcon( *castle, castleIcon, { 4, 4 } ); renderItem( castleIcon, castle->GetName(), { dstx, dsty }, 35, 75, itemsOffsetY / 2, current ); } void ActionListPressRight( int & index ) override { Dialog::QuickInfoWithIndicationOnRadar( *world.getCastleEntrance( Maps::GetPoint( index ) ), background->totalArea() ); } static const int32_t itemsOffsetY{ 35 }; private: const int _townFrameIcnId; }; namespace { // This is a base class for items used in the Editor and they rely on Maps::ObjectInfo structures. class ObjectTypeSelection : public SelectEnum { public: ObjectTypeSelection( const std::vector & objectInfo, const fheroes2::Size & size, std::string title, const int32_t imageOffsetX, const int32_t textOffsetX, const int32_t offsetY ) : SelectEnum( size, std::move( title ) ) , _objectInfo( objectInfo ) , _imageOffsetX( imageOffsetX ) , _textOffsetX( textOffsetX ) , _offsetY( offsetY ) { SetAreaMaxItems( rtAreaItems.height / _offsetY ); } using SelectEnum::ActionListPressRight; void RedrawItem( const int & objectId, int32_t posX, int32_t posY, bool isSelected ) override { // If this assertion blows up then you are setting different number of items. assert( objectId >= 0 && objectId < static_cast( _objectInfo.size() ) ); const fheroes2::Sprite & image = fheroes2::generateMapObjectImage( _objectInfo[objectId] ); const int32_t imageHeight = image.height(); const int32_t imageWidth = image.width(); if ( imageHeight > TILEWIDTH * 3 || imageWidth > TILEWIDTH * 5 ) { // Reduce the size of very tall images to fit the list. const double ratio = std::max( imageHeight / ( TILEWIDTH * 3. ), imageWidth / ( TILEWIDTH * 5. ) ); fheroes2::Image resized( static_cast( imageWidth / ratio ), static_cast( imageHeight / ratio ) ); fheroes2::Resize( image, resized ); renderItem( resized, getObjectName( _objectInfo[objectId] ), { posX, posY }, _imageOffsetX, _textOffsetX, _offsetY / 2, isSelected ); } else { renderItem( image, getObjectName( _objectInfo[objectId] ), { posX, posY }, _imageOffsetX, _textOffsetX, _offsetY / 2, isSelected ); } } void ActionListPressRight( int & objectId ) override { // If this assertion blows up then you are setting different number of items. assert( objectId >= 0 && objectId < static_cast( _objectInfo.size() ) ); showPopupWindow( _objectInfo[objectId] ); } private: virtual void showPopupWindow( const Maps::ObjectInfo & info ) = 0; virtual std::string getObjectName( const Maps::ObjectInfo & info ) = 0; const std::vector & _objectInfo; const int32_t _imageOffsetX{ 0 }; const int32_t _textOffsetX{ 0 }; const int32_t _offsetY{ 0 }; }; class HeroTypeSelection : public ObjectTypeSelection { public: HeroTypeSelection( const std::vector & objectInfo, const fheroes2::Size & size, std::string title ) : ObjectTypeSelection( objectInfo, size, std::move( title ), 21, 47, fheroes2::AGG::GetICN( ICN::MINIHERO, 0 ).height() + 2 ) { // Do nothing. } private: void showPopupWindow( const Maps::ObjectInfo & info ) override { fheroes2::showStandardTextMessage( getObjectName( info ), "", Dialog::ZERO ); } std::string getObjectName( const Maps::ObjectInfo & info ) override { const int color = static_cast( info.metadata[0] ); const int race = static_cast( info.metadata[1] ); std::string name( _( "%{color} %{race} hero" ) ); StringReplace( name, "%{color}", Color::String( Color::IndexToColor( color ) ) ); StringReplace( name, "%{race}", Race::String( Race::IndexToRace( race ) ) ); return name; } }; class MonsterTypeSelection : public ObjectTypeSelection { public: MonsterTypeSelection( const std::vector & objectInfo, const fheroes2::Size & size, std::string title ) : ObjectTypeSelection( objectInfo, size, std::move( title ), 45 / 2, 50, 43 ) { // Do nothing. } private: void showPopupWindow( const Maps::ObjectInfo & info ) override { const Monster monster( static_cast( info.metadata[0] ) ); if ( !monster.isValid() ) { fheroes2::showStandardTextMessage( monster.GetName(), "", Dialog::ZERO ); return; } Dialog::ArmyInfo( Troop( monster, 0 ), Dialog::ZERO ); } std::string getObjectName( const Maps::ObjectInfo & info ) override { return Monster( static_cast( info.metadata[0] ) ).GetName(); } }; class ArtifactTypeSelection : public ObjectTypeSelection { public: ArtifactTypeSelection( const std::vector & objectInfo, const fheroes2::Size & size, std::string title ) : ObjectTypeSelection( objectInfo, size, std::move( title ), 24, 65, 40 ) { // Do nothing. } private: void showPopupWindow( const Maps::ObjectInfo & info ) override { switch ( info.objectType ) { case MP2::OBJ_ARTIFACT: fheroes2::ArtifactDialogElement( Artifact( static_cast( info.metadata[0] ) ) ).showPopup( Dialog::ZERO ); break; case MP2::OBJ_RANDOM_ARTIFACT: case MP2::OBJ_RANDOM_ARTIFACT_MINOR: case MP2::OBJ_RANDOM_ARTIFACT_MAJOR: case MP2::OBJ_RANDOM_ARTIFACT_TREASURE: case MP2::OBJ_RANDOM_ULTIMATE_ARTIFACT: fheroes2::showStandardTextMessage( MP2::StringObject( info.objectType ), "", Dialog::ZERO ); break; default: // Did you expand the list of artifacts? Add the corresponding logic! assert( 0 ); break; } } std::string getObjectName( const Maps::ObjectInfo & info ) override { switch ( info.objectType ) { case MP2::OBJ_ARTIFACT: return Artifact( static_cast( info.metadata[0] ) ).GetName(); case MP2::OBJ_RANDOM_ARTIFACT: case MP2::OBJ_RANDOM_ARTIFACT_MINOR: case MP2::OBJ_RANDOM_ARTIFACT_MAJOR: case MP2::OBJ_RANDOM_ARTIFACT_TREASURE: case MP2::OBJ_RANDOM_ULTIMATE_ARTIFACT: return MP2::StringObject( info.objectType ); default: // Did you expand the list of treasures? Add the corresponding logic! assert( 0 ); break; } return {}; } }; class TreasureTypeSelection : public ObjectTypeSelection { public: TreasureTypeSelection( const std::vector & objectInfo, const fheroes2::Size & size, std::string title ) : ObjectTypeSelection( objectInfo, size, std::move( title ), 17, 60, 40 ) { // Do nothing. } private: void showPopupWindow( const Maps::ObjectInfo & info ) override { switch ( info.objectType ) { case MP2::OBJ_RESOURCE: fheroes2::showResourceMessage( fheroes2::Text{ getObjectName( info ), fheroes2::FontType::normalYellow() }, fheroes2::Text{ Resource::getDescription(), fheroes2::FontType::normalWhite() }, Dialog::ZERO, Funds{ static_cast( info.metadata[0] ), 0 } ); break; case MP2::OBJ_CAMPFIRE: case MP2::OBJ_GENIE_LAMP: case MP2::OBJ_RANDOM_RESOURCE: case MP2::OBJ_TREASURE_CHEST: fheroes2::showStandardTextMessage( getObjectName( info ), "", Dialog::ZERO ); break; default: // Did you expand the list of treasures? Add the corresponding logic! assert( 0 ); break; } } std::string getObjectName( const Maps::ObjectInfo & info ) override { switch ( info.objectType ) { case MP2::OBJ_RESOURCE: return Resource::String( static_cast( info.metadata[0] ) ); case MP2::OBJ_CAMPFIRE: case MP2::OBJ_GENIE_LAMP: case MP2::OBJ_RANDOM_RESOURCE: case MP2::OBJ_TREASURE_CHEST: return MP2::StringObject( info.objectType ); default: // Did you expand the list of treasures? Add the corresponding logic! assert( 0 ); break; } return {}; } }; class OceanObjectTypeSelection : public ObjectTypeSelection { public: OceanObjectTypeSelection( const std::vector & objectInfo, const fheroes2::Size & size, std::string title ) : ObjectTypeSelection( objectInfo, size, std::move( title ), 3 * 32 / 2, 3 * 32 + 10, 2 * 32 + 24 ) { // Do nothing. } private: void showPopupWindow( const Maps::ObjectInfo & info ) override { fheroes2::showStandardTextMessage( getObjectName( info ), "", Dialog::ZERO ); } std::string getObjectName( const Maps::ObjectInfo & info ) override { if ( info.objectType == MP2::OBJ_NONE ) { return _( "Terrain object" ); } return MP2::StringObject( info.objectType ); } }; class GenericObjectTypeSelection : public ObjectTypeSelection { public: GenericObjectTypeSelection( const std::vector & objectInfo, const fheroes2::Size & size, std::string title ) : ObjectTypeSelection( objectInfo, size, std::move( title ), 6 * 32 / 2, 6 * 32 + 10, 3 * 32 ) { // Do nothing. } private: void showPopupWindow( const Maps::ObjectInfo & info ) override { fheroes2::showStandardTextMessage( getObjectName( info ), "", Dialog::ZERO ); } std::string getObjectName( const Maps::ObjectInfo & info ) override { if ( info.objectType == MP2::OBJ_NONE ) { return _( "Terrain object" ); } return MP2::StringObject( info.objectType ); } }; class PowerUpObjectTypeSelection : public ObjectTypeSelection { public: PowerUpObjectTypeSelection( const std::vector & objectInfo, const fheroes2::Size & size, std::string title ) : ObjectTypeSelection( objectInfo, size, std::move( title ), 4 * 32 / 2, 4 * 32 + 10, 3 * 32 ) { // Do nothing. } private: void showPopupWindow( const Maps::ObjectInfo & info ) override { fheroes2::showStandardTextMessage( getObjectName( info ), "", Dialog::ZERO ); } std::string getObjectName( const Maps::ObjectInfo & info ) override { return MP2::StringObject( info.objectType ); } }; int selectObjectType( const int objectType, const size_t objectCount, ObjectTypeSelection & objectSelection ) { std::vector objects( objectCount, 0 ); std::iota( objects.begin(), objects.end(), 0 ); objectSelection.SetListContent( objects ); objectSelection.SetCurrent( std::max( objectType, 0 ) ); const int32_t result = objectSelection.selectItemsEventProcessing(); return result == Dialog::OK || objectSelection.ok ? objectSelection.GetCurrent() : -1; } class MineTypeList : public Interface::ListBox { public: using Interface::ListBox::ActionListDoubleClick; using Interface::ListBox::ActionListSingleClick; using Interface::ListBox::ActionListPressRight; using ListBox::ListBox; void ActionListPressRight( Maps::ObjectInfo & info ) override { if ( info.objectType == MP2::OBJ_MINE ) { // Mine description is done by its resource type stored in metadata. fheroes2::showStandardTextMessage( Maps::GetMineName( static_cast( info.metadata[0] ) ), "", Dialog::ZERO ); return; } fheroes2::showStandardTextMessage( MP2::StringObject( info.objectType ), "", Dialog::ZERO ); } int getCurrentId() const { return _currentId; } void initListBackgroundRestorer( fheroes2::Rect roi ) { _listBackground = std::make_unique( fheroes2::Display::instance(), roi.x, roi.y, roi.width, roi.height ); } bool isDoubleClicked() const { return _isDoubleClicked; } void updateScrollBarImage() { const int32_t scrollBarWidth = _scrollbar.width(); setScrollBarImage( fheroes2::generateScrollbarSlider( _scrollbar, false, _scrollbar.getArea().height, VisibleItemCount(), _size(), { 0, 0, scrollBarWidth, 8 }, { 0, 7, scrollBarWidth, 8 } ) ); _scrollbar.moveToIndex( _topId ); } void RedrawItem( const Maps::ObjectInfo & info, int32_t dstx, int32_t dsty, bool current ) override { if ( _color < 0 ) { assert( 0 ); return; } const fheroes2::Sprite mineSprite( fheroes2::generateMapObjectImage( info ) ); fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::Blit( mineSprite, display, dstx + TILEWIDTH * 5 + 5 - mineSprite.width(), dsty + 1 ); // Mine type selection mark background. fheroes2::Sprite markBackground = fheroes2::AGG::GetICN( ICN::CELLWIN, 4 ); if ( Settings::Get().isEvilInterfaceEnabled() ) { fheroes2::ApplyPalette( markBackground, PAL::CombinePalettes( PAL::GetPalette( PAL::PaletteType::GRAY ), PAL::GetPalette( PAL::PaletteType::DARKENING ) ) ); } fheroes2::Blit( markBackground, display, dstx + 10, dsty + 24 ); if ( current ) { const fheroes2::Sprite & mark = fheroes2::AGG::GetICN( ICN::CELLWIN, 5 ); fheroes2::Blit( mark, display, dstx + 13, dsty + 27 ); } } void RedrawBackground( const fheroes2::Point & /* unused */ ) override { _listBackground->restore(); } void ActionCurrentUp() override { // Do nothing. } void ActionCurrentDn() override { // Do nothing. } void ActionListDoubleClick( Maps::ObjectInfo & /*unused*/ ) override { _isDoubleClicked = true; } void ActionListSingleClick( Maps::ObjectInfo & /*unused*/ ) override { // Do nothing. } void setOwnerColor( const int32_t color ) { _color = color; } private: bool _isDoubleClicked{ false }; int32_t _color{ 0 }; std::unique_ptr _listBackground; }; } int32_t Dialog::selectKingdomCastle( const Kingdom & kingdom, const bool notOccupiedByHero, std::string title, std::string description /* = {} */, int32_t castlePositionIndex /* = -1 */ ) { std::vector castles; const VecCastles & kingdomCastles = kingdom.GetCastles(); castles.reserve( kingdomCastles.size() ); for ( const Castle * castle : kingdomCastles ) { assert( castle != nullptr ); if ( notOccupiedByHero && castle->GetHero() ) { continue; } castles.push_back( castle->GetIndex() ); } const int32_t maxHeight = std::min( 100 + SelectKingdomCastle::itemsOffsetY * 12, fheroes2::Display::instance().height() - 200 ); const int32_t itemsHeight = std::max( 100 + SelectKingdomCastle::itemsOffsetY * static_cast( castles.size() ), 100 + SelectKingdomCastle::itemsOffsetY * 5 ); const int32_t totalHeight = std::min( itemsHeight, maxHeight ); SelectKingdomCastle listbox( { 350, totalHeight }, std::move( title ), std::move( description ) ); listbox.SetListContent( castles ); if ( castlePositionIndex != -1 ) { listbox.SetCurrent( castlePositionIndex ); } const int32_t result = listbox.selectItemsEventProcessing(); return ( result == Dialog::OK || listbox.ok ) ? listbox.GetCurrent() : -1; } Skill::Secondary Dialog::selectSecondarySkill( const Heroes & hero, const int skillId /* = Skill::Secondary::UNKNOWN */ ) { std::vector skills; skills.reserve( static_cast( MAXSECONDARYSKILL * 3 ) ); for ( int i = 0; i < MAXSECONDARYSKILL * 3; ++i ) { if ( !hero.HasSecondarySkill( SelectEnumSecSkill::getSkillFromListIndex( i ) ) ) { skills.push_back( i ); } } SelectEnumSecSkill listbox( { 350, fheroes2::Display::instance().height() - 200 }, _( "Select Skill:" ) ); listbox.SetListContent( skills ); if ( skillId != Skill::Secondary::UNKNOWN ) { listbox.SetCurrent( skillId ); } const int32_t result = listbox.selectItemsEventProcessing(); if ( result == Dialog::OK || listbox.ok ) { const int skillIndex = listbox.GetCurrent(); return { SelectEnumSecSkill::getSkillFromListIndex( skillIndex ), SelectEnumSecSkill::getLevelFromListIndex( skillIndex ) }; } return {}; } Spell Dialog::selectSpell( const int spellId, const bool includeRandomSpells ) { std::vector spells = Spell::getAllSpellIdsSuitableForSpellBook(); if ( includeRandomSpells ) { // We add random spell items to the end of the list. for ( int randomSpellId = Spell::RANDOM; randomSpellId <= Spell::RANDOM5; ++randomSpellId ) { spells.push_back( randomSpellId ); } } SelectEnumSpell listbox( { 340, fheroes2::Display::instance().height() - 200 }, _( "Select Spell:" ) ); listbox.SetListContent( spells ); if ( spellId != Spell::NONE ) { listbox.SetCurrent( spellId ); } const int32_t result = listbox.selectItemsEventProcessing(); return result == Dialog::OK || listbox.ok ? Spell( listbox.GetCurrent() ) : Spell( Spell::NONE ); } Artifact Dialog::selectArtifact( const int artifactId ) { std::vector artifacts; artifacts.reserve( Artifact::ARTIFACT_COUNT - 1 ); const GameVersion version = Settings::Get().getCurrentMapInfo().version; const bool isPriceofLoyaltyArtifactAllowed = ( version == GameVersion::PRICE_OF_LOYALTY || version == GameVersion::RESURRECTION ); // We show the magic book at the first place. artifacts.emplace_back( Artifact::MAGIC_BOOK ); for ( int id = Artifact::UNKNOWN + 1; id < Artifact::ARTIFACT_COUNT; ++id ) { if ( id != Artifact::MAGIC_BOOK && Artifact( id ).isValid() && ( isPriceofLoyaltyArtifactAllowed || !fheroes2::isPriceOfLoyaltyArtifact( id ) ) ) { artifacts.emplace_back( id ); } } SelectEnumArtifact listbox( { 370, fheroes2::Display::instance().height() - 200 }, _( "Select Artifact:" ) ); listbox.SetListContent( artifacts ); if ( artifactId != Artifact::UNKNOWN ) { listbox.SetCurrent( artifactId ); } const int32_t result = listbox.selectItemsEventProcessing(); return ( result == Dialog::OK || listbox.ok ) ? Artifact( listbox.GetCurrent() ) : Artifact( Artifact::UNKNOWN ); } Monster Dialog::selectMonster( const int monsterId ) { std::vector monsters( Monster::MONSTER_COUNT - 1, Monster::UNKNOWN ); // Skip Monster::UNKNOWN and start from the next one. std::iota( monsters.begin(), monsters.end(), Monster::UNKNOWN + 1 ); monsters.erase( std::remove_if( monsters.begin(), monsters.end(), []( const int id ) { return Monster( id ).isRandomMonster(); } ), monsters.end() ); SelectEnumMonster listbox( { 320, fheroes2::Display::instance().height() - 200 }, _( "Select Monster:" ) ); listbox.SetListContent( monsters ); if ( monsterId != Monster::UNKNOWN ) { listbox.SetCurrent( monsterId ); } const int32_t result = listbox.selectItemsEventProcessing(); return result == Dialog::OK || listbox.ok ? Monster( listbox.GetCurrent() ) : Monster( Monster::UNKNOWN ); } int Dialog::selectHeroes( const int heroId /* = Heroes::UNKNOWN */ ) { const GameVersion version = Settings::Get().getCurrentMapInfo().version; const bool isPoLHeroesAllowed = ( version == GameVersion::PRICE_OF_LOYALTY || version == GameVersion::RESURRECTION ); std::vector heroes( static_cast( isPoLHeroesAllowed ? Heroes::JARKONAS : Heroes::BRAX ), Heroes::UNKNOWN ); std::iota( heroes.begin(), heroes.end(), Heroes::UNKNOWN + 1 ); SelectEnumHeroes listbox( { 300, fheroes2::Display::instance().height() - 200 }, _( "Select Hero:" ) ); listbox.SetListContent( heroes ); if ( heroId != Heroes::UNKNOWN ) { listbox.SetCurrent( heroId ); } const int32_t result = listbox.selectItemsEventProcessing(); return result == Dialog::OK || listbox.ok ? listbox.GetCurrent() : Heroes::UNKNOWN; } int Dialog::selectHeroType( const int heroType ) { const auto & objectInfo = Maps::getObjectsByGroup( Maps::ObjectGroup::KINGDOM_HEROES ); HeroTypeSelection listbox( objectInfo, { 350, fheroes2::Display::instance().height() - 200 }, _( "Select Hero:" ) ); return selectObjectType( heroType, objectInfo.size(), listbox ); } int Dialog::selectMonsterType( const int monsterType ) { const auto & objectInfo = Maps::getObjectsByGroup( Maps::ObjectGroup::MONSTERS ); MonsterTypeSelection listbox( objectInfo, { 350, fheroes2::Display::instance().height() - 200 }, _( "Select Monster:" ) ); return selectObjectType( monsterType, objectInfo.size(), listbox ); } int Dialog::selectArtifactType( const int artifactType ) { const auto & objectInfo = Maps::getObjectsByGroup( Maps::ObjectGroup::ADVENTURE_ARTIFACTS ); ArtifactTypeSelection listbox( objectInfo, { 350, fheroes2::Display::instance().height() - 200 }, _( "Select Artifact:" ) ); return selectObjectType( artifactType, objectInfo.size(), listbox ); } int Dialog::selectTreasureType( const int resourceType ) { const auto & objectInfo = Maps::getObjectsByGroup( Maps::ObjectGroup::ADVENTURE_TREASURES ); TreasureTypeSelection listbox( objectInfo, { 350, fheroes2::Display::instance().height() - 200 }, _( "Select Treasure:" ) ); return selectObjectType( resourceType, objectInfo.size(), listbox ); } int Dialog::selectOceanObjectType( const int objectType ) { const auto & objectInfo = Maps::getObjectsByGroup( Maps::ObjectGroup::ADVENTURE_WATER ); OceanObjectTypeSelection listbox( objectInfo, { 350, fheroes2::Display::instance().height() - 200 }, _( "Select Ocean Object:" ) ); return selectObjectType( objectType, objectInfo.size(), listbox ); } int Dialog::selectLandscapeOceanObjectType( const int objectType ) { const auto & objectInfo = Maps::getObjectsByGroup( Maps::ObjectGroup::LANDSCAPE_WATER ); OceanObjectTypeSelection listbox( objectInfo, { 350, fheroes2::Display::instance().height() - 200 }, _( "Select Ocean Object:" ) ); return selectObjectType( objectType, objectInfo.size(), listbox ); } void Dialog::selectTownType( int & type, int & color ) { fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::StandardWindow background( 520, 370, true, display ); const fheroes2::Rect & area = background.activeArea(); fheroes2::Text text( _( "Castle/town placing" ), fheroes2::FontType::normalYellow() ); text.draw( area.x + ( area.width - text.width() ) / 2, area.y + 10, display ); // Render color and race selection sprites. const int32_t stepX = 70; const int32_t raceOffsetY = 80; fheroes2::Point pos( area.x + 20, area.y + 30 ); const fheroes2::Sprite & colorSpriteBorderSelected = fheroes2::AGG::GetICN( ICN::BRCREST, 6 ); const fheroes2::Sprite & raceShadow = fheroes2::AGG::GetICN( ICN::NGEXTRA, 61 ); fheroes2::Sprite colorSpriteBorder( colorSpriteBorderSelected ); const std::vector darkGrayPalette = PAL::CombinePalettes( PAL::GetPalette( PAL::PaletteType::GRAY ), PAL::GetPalette( PAL::PaletteType::DARKENING ) ); fheroes2::ApplyPalette( colorSpriteBorder, darkGrayPalette ); // Make race selection borders. const fheroes2::Sprite & randomRaceSprite = fheroes2::AGG::GetICN( ICN::NGEXTRA, 58 ); const int32_t raceBorderWidth = randomRaceSprite.width(); const int32_t raceBorderHeight = randomRaceSprite.height(); fheroes2::Image raceSpriteBorder( raceBorderWidth, raceBorderHeight ); raceSpriteBorder.reset(); const int32_t raceImageWidth = raceBorderWidth - 8; const int32_t raceImageHeight = raceBorderHeight - 8; fheroes2::Copy( randomRaceSprite, 0, 0, raceSpriteBorder, 0, 0, raceBorderWidth, 4 ); fheroes2::Copy( randomRaceSprite, 0, 4, raceSpriteBorder, 0, 4, 4, raceImageHeight ); int32_t imageCopyOffset = raceBorderWidth - 4; fheroes2::Copy( randomRaceSprite, imageCopyOffset, 4, raceSpriteBorder, imageCopyOffset, 4, 4, raceImageHeight ); imageCopyOffset = raceBorderHeight - 4; fheroes2::Copy( randomRaceSprite, 0, imageCopyOffset, raceSpriteBorder, 0, imageCopyOffset, raceBorderWidth, 4 ); fheroes2::Image raceSpriteBorderSelected( raceSpriteBorder ); fheroes2::ApplyPalette( raceSpriteBorder, darkGrayPalette ); fheroes2::ApplyPalette( raceSpriteBorderSelected, PAL::GetPalette( PAL::PaletteType::RED ) ); std::array colorRect; std::array raceRect; // Neutral color. fheroes2::addGradientShadow( colorSpriteBorder, display, pos, { -5, 5 } ); const fheroes2::Sprite & neutralColorSprite = fheroes2::AGG::GetICN( ICN::BRCREST, 7 ); colorRect.back() = { pos.x, pos.y, colorSpriteBorder.width(), colorSpriteBorder.height() }; fheroes2::Copy( neutralColorSprite, 0, 0, display, colorRect.back().x + 4, colorRect.back().y + 4, neutralColorSprite.width(), neutralColorSprite.height() ); text.set( _( "doubleLinedRace|Neutral" ), fheroes2::FontType::smallWhite() ); text.draw( colorRect.back().x + ( colorRect.back().width - text.width() ) / 2, colorRect.back().y + colorRect.back().height + 4, display ); // Random race. raceRect.back() = { pos.x - 2, pos.y + raceOffsetY, raceBorderWidth, raceBorderHeight }; fheroes2::Copy( randomRaceSprite, 4, 4, display, raceRect.back().x + 4, raceRect.back().y + 4, raceImageWidth, raceImageHeight ); fheroes2::Blit( raceShadow, display, pos.x - 5, pos.y + raceOffsetY + 2 ); text.set( _( "race|Random" ), fheroes2::FontType::smallWhite() ); text.draw( raceRect.back().x, raceRect.back().y + raceRect.back().height + 4, raceBorderWidth, display ); pos.x += stepX; for ( uint32_t i = 0; i < 6; ++i ) { const fheroes2::Sprite & colorSprite = fheroes2::AGG::GetICN( ICN::BRCREST, i ); fheroes2::addGradientShadow( colorSpriteBorder, display, pos, { -5, 5 } ); colorRect[i] = { pos.x, pos.y, colorSpriteBorder.width(), colorSpriteBorder.height() }; fheroes2::Copy( colorSprite, 0, 0, display, colorRect[i].x + 4, colorRect[i].y + 4, colorSprite.width(), colorSprite.height() ); text.set( Color::String( Color::IndexToColor( static_cast( i ) ) ), fheroes2::FontType::smallWhite() ); text.draw( colorRect[i].x + ( colorRect[i].width - text.width() ) / 2, colorRect[i].y + colorRect[i].height + 4, display ); const fheroes2::Sprite & raceSprite = fheroes2::AGG::GetICN( ICN::NGEXTRA, 51U + i ); raceRect[i] = { pos.x - 2, pos.y + raceOffsetY, raceBorderWidth, raceBorderHeight }; fheroes2::Copy( raceSprite, 4, 4, display, raceRect[i].x + 4, raceRect[i].y + 4, raceImageWidth, raceImageHeight ); fheroes2::Blit( raceShadow, display, pos.x - 5, pos.y + raceOffsetY + 2 ); text.set( Race::DoubleLinedString( Race::IndexToRace( static_cast( i ) ) ), fheroes2::FontType::smallWhite() ); text.draw( raceRect[i].x, raceRect[i].y + raceRect[i].height + 4, raceBorderWidth, display ); pos.x += stepX; } pos.x = area.x + area.width / 2; pos.y += 260; const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); // Render dialog buttons. fheroes2::Button buttonOk; fheroes2::Button buttonCancel; fheroes2::Button buttonTown; fheroes2::Button buttonCastle; background.renderOkayCancelButtons( buttonOk, buttonCancel, isEvilInterface ); background.renderButton( buttonTown, isEvilInterface ? ICN::BUTTON_TOWN_EVIL : ICN::BUTTON_TOWN_GOOD, 0, 1, { -50, 7 }, fheroes2::StandardWindow::Padding::BOTTOM_CENTER ); background.renderButton( buttonCastle, isEvilInterface ? ICN::BUTTON_CASTLE_EVIL : ICN::BUTTON_CASTLE_GOOD, 0, 1, { 50, 7 }, fheroes2::StandardWindow::Padding::BOTTOM_CENTER ); fheroes2::ImageRestorer castleBackground( display, pos.x - 7 * TILEWIDTH + TILEWIDTH / 2, pos.y - 4 * TILEWIDTH + TILEWIDTH / 2, 9 * TILEWIDTH, 5 * TILEWIDTH ); LocalEvent & le = LocalEvent::Get(); bool needRedraw = true; bool isCastle = false; int townRace = 6; int townColor = 6; const int basementGround = isEvilInterface ? Maps::Ground::LAVA : Maps::Ground::DIRT; if ( type > -1 && color > -1 ) { isCastle = ( type % 2 ) == 0; townRace = type / 2; townColor = color; } isCastle ? buttonCastle.drawOnPress() : buttonTown.drawOnPress(); while ( le.HandleEvents() ) { le.MousePressLeft( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); le.MousePressLeft( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); if ( isCastle ) { if ( le.MousePressLeft( buttonTown.area() ) ) { buttonTown.drawOnPress(); } else if ( le.MouseClickLeft( buttonTown.area() ) ) { buttonTown.drawOnPress(); isCastle = false; needRedraw = true; } else { buttonTown.drawOnRelease(); } } else { if ( le.MousePressLeft( buttonCastle.area() ) ) { buttonCastle.drawOnPress(); } else if ( le.MouseClickLeft( buttonCastle.area() ) ) { buttonCastle.drawOnPress(); isCastle = true; needRedraw = true; } else { buttonCastle.drawOnRelease(); } } if ( le.MouseClickLeft( buttonOk.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) ) { type = getPackedTownType( townRace, isCastle ); color = townColor; return; } if ( le.MouseClickLeft( buttonCancel.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { return; } if ( le.MousePressRight( buttonCancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Exit this menu without doing anything." ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonOk.area() ) ) { fheroes2::showStandardTextMessage( _( "Okay" ), _( "Click to start placing the selected castle/town." ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonTown.area() ) ) { fheroes2::showStandardTextMessage( _( "Town" ), _( "Click to select town placing." ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonCastle.area() ) ) { fheroes2::showStandardTextMessage( _( "Castle" ), _( "Click to select castle placing." ), Dialog::ZERO ); } else if ( le.MousePressRight( castleBackground.rect() ) ) { std::string name( _( "%{color} %{race} %{townOrCastle}" ) ); StringReplace( name, "%{color}", ( townColor < 6 ) ? Color::String( Color::IndexToColor( townColor ) ) : _( "race|Neutral" ) ); StringReplace( name, "%{race}", ( townRace < 6 ) ? Race::String( Race::IndexToRace( townRace ) ) : _( "race|Random" ) ); StringReplace( name, "%{townOrCastle}", isCastle ? _( "Castle" ) : _( "Town" ) ); fheroes2::showStandardTextMessage( _( "You will place" ), std::move( name ), Dialog::ZERO ); } else { for ( size_t i = 0; i < 7; ++i ) { if ( le.MouseClickLeft( raceRect[i] ) ) { townRace = static_cast( i ); needRedraw = true; break; } if ( le.MouseClickLeft( colorRect[i] ) ) { townColor = static_cast( i ); needRedraw = true; break; } if ( le.MousePressRight( raceRect[i] ) ) { fheroes2::showStandardTextMessage( Race::String( Race::IndexToRace( static_cast( i == 6 ? 7 : i ) ) ), _( "Click to select this class." ), Dialog::ZERO ); } else if ( le.MousePressRight( colorRect[i] ) ) { fheroes2::showStandardTextMessage( i == 6 ? _( "race|Neutral" ) : Color::String( Color::IndexToColor( static_cast( i ) ) ), _( "Click to select this color." ), Dialog::ZERO ); } } } if ( !needRedraw ) { continue; } castleBackground.restore(); // Update town image. const fheroes2::Sprite townImage = fheroes2::generateTownObjectImage( getPackedTownType( townRace, isCastle ), townColor, basementGround ); fheroes2::Blit( townImage, display, pos.x + townImage.x(), pos.y + townImage.y() ); // Update color and race borders. for ( int i = 0; i < 7; ++i ) { fheroes2::Blit( ( i == townColor ) ? colorSpriteBorderSelected : colorSpriteBorder, display, colorRect[i].x, colorRect[i].y ); fheroes2::Blit( ( i == townRace ) ? raceSpriteBorderSelected : raceSpriteBorder, display, raceRect[i].x, raceRect[i].y ); } display.render( background.totalArea() ); needRedraw = false; } } int Dialog::selectDwellingType( const int dwellingType ) { const auto & objectInfo = Maps::getObjectsByGroup( Maps::ObjectGroup::ADVENTURE_DWELLINGS ); GenericObjectTypeSelection listbox( objectInfo, { 480, fheroes2::Display::instance().height() - 180 }, _( "Select Dwelling:" ) ); return selectObjectType( dwellingType, objectInfo.size(), listbox ); } int Dialog::selectLandscapeMiscellaneousObjectType( const int objectType ) { const auto & objectInfo = Maps::getObjectsByGroup( Maps::ObjectGroup::LANDSCAPE_MISCELLANEOUS ); GenericObjectTypeSelection listbox( objectInfo, { 420, fheroes2::Display::instance().height() - 180 }, _( "Select Landscape Object:" ) ); return selectObjectType( objectType, objectInfo.size(), listbox ); } void Dialog::selectMineType( int32_t & type, int32_t & color ) { fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::StandardWindow background( 380, 395, true, display ); const fheroes2::Rect & area = background.activeArea(); fheroes2::Text text( _( "Mine placing" ), fheroes2::FontType::normalYellow() ); int32_t offsetY = area.y + 10; text.draw( area.x + ( area.width - text.width() ) / 2, offsetY, display ); // There are 7 resource types (WOOD, MERCURY, ORE, SULFUR, CRYSTAL, GEMS, GOLD) and abandoned mine. const uint32_t resourceCount{ 8 }; const int32_t stepY = 35; offsetY += 50; const fheroes2::Rect resourceSelectRoi( area.x + 22, offsetY - 5, 100, stepY * resourceCount + 10 ); background.applyTextBackgroundShading( resourceSelectRoi ); text.set( _( "Resource\ntype:" ), fheroes2::FontType::smallWhite() ); text.draw( resourceSelectRoi.x, resourceSelectRoi.y - text.height( resourceSelectRoi.width ), resourceSelectRoi.width, display ); const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); std::array resourceRoi; // Resource type selection mark background. fheroes2::Sprite markBackground = fheroes2::AGG::GetICN( ICN::CELLWIN, 4 ); if ( isEvilInterface ) { fheroes2::ApplyPalette( markBackground, PAL::CombinePalettes( PAL::GetPalette( PAL::PaletteType::GRAY ), PAL::GetPalette( PAL::PaletteType::DARKENING ) ) ); } // Resource icons. const int32_t iconOffsetX = 35; const std::vector & resourceInfo = Maps::getObjectsByGroup( Maps::ObjectGroup::ADVENTURE_TREASURES ); assert( resourceInfo.size() > resourceCount - 1 ); for ( uint32_t i = 0; i < resourceCount - 1; ++i ) { const fheroes2::Sprite & resourceImage = fheroes2::generateMapObjectImage( resourceInfo[i] ); const fheroes2::Point imagePosition( area.x + iconOffsetX + ( resourceSelectRoi.width - TILEWIDTH * 3 ) / 2, offsetY + ( stepY - TILEWIDTH ) / 2 ); resourceRoi[i] = { resourceSelectRoi.x + 5, imagePosition.y, resourceSelectRoi.width - 10, TILEWIDTH }; fheroes2::Blit( resourceImage, display, imagePosition.x, imagePosition.y - 4 ); fheroes2::Blit( markBackground, display, resourceRoi[i].x + 2, resourceRoi[i].y + ( resourceRoi[i].height - markBackground.height() ) / 2 ); offsetY += stepY; } // Render ghosts to select Abandoned mine type. const fheroes2::Sprite & ghostImage = fheroes2::AGG::GetICN( ICN::MONS32, 59 ); const int32_t ghostsOnIcon = 4; const int32_t ghostsIconOverlap = 12; const int32_t ghostsStepX = ghostImage.width() - ghostsIconOverlap; const int32_t ghostsIconWight = ghostsOnIcon * ghostsStepX + ghostsIconOverlap; const int32_t ghostsIconHeight = ghostImage.height(); resourceRoi[7] = { resourceSelectRoi.x + 5, offsetY + ( stepY - ghostsIconHeight ) / 2, resourceSelectRoi.width - 10, ghostsIconHeight }; for ( int32_t i = 0; i < ghostsOnIcon; ++i ) { fheroes2::Blit( ghostImage, display, area.x + iconOffsetX + ( resourceSelectRoi.width - ghostsIconWight ) / 2 + ghostsStepX * i, resourceRoi[7].y, i < 2 ); } fheroes2::Blit( markBackground, display, resourceRoi[7].x + 2, resourceRoi[7].y + ( resourceRoi[7].height - markBackground.height() ) / 2 ); // Resource type selection mark. const fheroes2::Sprite & mark = fheroes2::AGG::GetICN( ICN::CELLWIN, 5 ); uint32_t selectedResourceType = 0; const std::vector & allObjectInfo = Maps::getObjectsByGroup( Maps::ObjectGroup::ADVENTURE_MINES ); if ( type > 0 ) { assert( static_cast( type ) < allObjectInfo.size() ); if ( allObjectInfo[type].objectType == MP2::OBJ_ABANDONED_MINE ) { selectedResourceType = 7; } else { switch ( allObjectInfo[type].metadata[0] ) { case Resource::ORE: selectedResourceType = 2; break; case Resource::SULFUR: selectedResourceType = 3; break; case Resource::CRYSTAL: selectedResourceType = 4; break; case Resource::GEMS: selectedResourceType = 5; break; case Resource::GOLD: selectedResourceType = 6; break; case Resource::WOOD: selectedResourceType = 0; break; case Resource::MERCURY: selectedResourceType = 1; break; default: // Have you added a new resource type for mines? assert( 0 ); } } } fheroes2::MovableSprite resourceTypeSelection( mark ); auto redrawResourceSelection = [&resourceTypeSelection, &mark]( const fheroes2::Rect & pos ) { resourceTypeSelection.setPosition( pos.x + 5, pos.y + ( pos.height - mark.height() ) / 2 ); resourceTypeSelection.redraw(); }; redrawResourceSelection( resourceRoi[selectedResourceType] ); // Mine appearance type selection list. MineTypeList listbox( area.getPosition() ); const fheroes2::Rect listRoi( area.x + area.width - 5 * TILEWIDTH - 75, resourceSelectRoi.y, 5 * TILEWIDTH + 20, resourceSelectRoi.height ); background.applyTextBackgroundShading( listRoi ); // Initialize list background restorer to use it in list method 'listbox.RedrawBackground()'. listbox.initListBackgroundRestorer( listRoi ); listbox.SetAreaItems( { listRoi.x, listRoi.y + 3, listRoi.width, listRoi.height - 3 } ); int32_t scrollbarOffsetX = listRoi.x + listRoi.width + 15; background.renderScrollbarBackground( { scrollbarOffsetX, listRoi.y, listRoi.width, listRoi.height }, isEvilInterface ); const int listIcnId = isEvilInterface ? ICN::SCROLLE : ICN::SCROLL; const int32_t topPartHeight = 19; ++scrollbarOffsetX; listbox.SetScrollButtonUp( listIcnId, 0, 1, { scrollbarOffsetX, listRoi.y + 1 } ); listbox.SetScrollButtonDn( listIcnId, 2, 3, { scrollbarOffsetX, listRoi.y + listRoi.height - 15 } ); listbox.setScrollBarArea( { scrollbarOffsetX + 2, listRoi.y + topPartHeight, 10, listRoi.height - 2 * topPartHeight } ); listbox.SetAreaMaxItems( 4 ); auto getObjectTypeByResource = []( const uint32_t resourceType ) { switch ( resourceType ) { case 0: return MP2::OBJ_SAWMILL; case 1: return MP2::OBJ_ALCHEMIST_LAB; case 2: case 3: case 4: case 5: case 6: return MP2::OBJ_MINE; case 7: return MP2::OBJ_ABANDONED_MINE; default: // Have you added a new resource type for mines? assert( 0 ); } return MP2::OBJ_NONE; }; std::vector objectInfo; std::vector objectInfoIndexes; auto updateListContent = [&listbox, &objectInfo, &allObjectInfo, &objectInfoIndexes, &listIcnId, &getObjectTypeByResource]( const uint32_t resourceId ) { const MP2::MapObjectType objectType = getObjectTypeByResource( resourceId ); // Mine appearance is the same for different mine resources so we keep the selection. const bool keepSelection = ( objectType == MP2::OBJ_MINE ) && !objectInfo.empty() && ( objectInfo.front().objectType == MP2::OBJ_MINE ); const int selectedId = keepSelection ? listbox.getCurrentId() : 0; const int topId = keepSelection ? listbox.getTopId() : 0; objectInfo.clear(); objectInfoIndexes.clear(); // For the Abandoned Mine there is no metadata set so 'metadata[0] == 0'. const uint32_t resourceType = ( objectType != MP2::OBJ_ABANDONED_MINE ) ? Resource::getResourceTypeFromIconIndex( resourceId ) : 0; for ( size_t i = 0; i < allObjectInfo.size(); ++i ) { if ( allObjectInfo[i].objectType == objectType && allObjectInfo[i].metadata[0] == resourceType ) { objectInfo.push_back( allObjectInfo[i] ); objectInfoIndexes.push_back( static_cast( i ) ); } } listbox.SetListContent( objectInfo ); // We need to reset the initial slider image to be able to reduce the current slider height. listbox.setScrollBarImage( fheroes2::AGG::GetICN( listIcnId, 4 ) ); listbox.updateScrollBarImage(); if ( keepSelection ) { listbox.SetCurrent( selectedId ); listbox.setTopVisibleItem( topId ); } }; updateListContent( selectedResourceType ); // Select the previously used mine type and resource type in the lists. if ( type > -1 ) { listbox.SetCurrent( std::distance( objectInfoIndexes.begin(), std::find( objectInfoIndexes.begin(), objectInfoIndexes.end(), type ) ) ); } fheroes2::ImageRestorer appearanceTextBackground( display, 0, 0, 0, 0 ); // Mine appearance selection text. auto redrawAppearanceText = [selectedResourceType, &appearanceTextBackground, &listRoi, &display]( const Maps::ObjectInfo & info ) { std::string mineText( _( "%{mineName} appearance:" ) ); if ( info.objectType == MP2::OBJ_MINE ) { assert( selectedResourceType < 7 ); #ifdef NDEBUG (void)selectedResourceType; #endif StringReplace( mineText, "%{mineName}", Maps::GetMineName( static_cast( info.metadata[0] ) ) ); } else { StringReplace( mineText, "%{mineName}", MP2::StringObject( info.objectType ) ); } const fheroes2::Text appearanceText( std::move( mineText ), fheroes2::FontType::smallWhite() ); const int32_t textWidth = appearanceText.width( listRoi.width ); const int32_t textHeight = appearanceText.height( listRoi.width ); appearanceTextBackground.restore(); appearanceTextBackground.update( listRoi.x + ( listRoi.width - textWidth ) / 2, listRoi.y - textHeight, textWidth, textHeight ); appearanceText.draw( listRoi.x, listRoi.y - textHeight, listRoi.width, display ); }; redrawAppearanceText( objectInfo[listbox.getCurrentId()] ); // TODO: Implement owner color selection. listbox.setOwnerColor( 0 ); listbox.Redraw(); // Render dialog buttons. fheroes2::Button buttonOk; fheroes2::Button buttonCancel; background.renderOkayCancelButtons( buttonOk, buttonCancel, isEvilInterface ); display.render( background.totalArea() ); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { le.MousePressLeft( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); le.MousePressLeft( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); bool needRedraw = listbox.QueueEventProcessing(); if ( listbox.isDoubleClicked() || le.MouseClickLeft( buttonOk.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) ) { type = objectInfoIndexes[listbox.getCurrentId()]; color = 0; return; } if ( le.MouseClickLeft( buttonCancel.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { return; } if ( le.MousePressRight( buttonCancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Exit this menu without doing anything." ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonOk.area() ) ) { fheroes2::showStandardTextMessage( _( "Okay" ), _( "Click to start placing the selected castle/town." ), Dialog::ZERO ); } else { for ( uint32_t i = 0; i < resourceCount; ++i ) { if ( le.MouseClickLeft( resourceRoi[i] ) ) { if ( i != selectedResourceType ) { selectedResourceType = i; redrawResourceSelection( resourceRoi[selectedResourceType] ); updateListContent( selectedResourceType ); redrawAppearanceText( objectInfo[listbox.getCurrentId()] ); needRedraw = true; } break; } if ( le.MousePressRight( resourceRoi[i] ) ) { const MP2::MapObjectType objectType = getObjectTypeByResource( i ); std::string objectName; if ( objectType == MP2::OBJ_MINE ) { objectName = Maps::GetMineName( Resource::getResourceTypeFromIconIndex( i ) ); } else { objectName = MP2::StringObject( objectType ); } std::string description( _( "Click to select %{object} as the resource generator to be placed." ) ); StringReplace( description, "%{object}", objectName ); fheroes2::showStandardTextMessage( std::move( objectName ), std::move( description ), Dialog::ZERO ); break; } } } if ( !needRedraw ) { continue; } listbox.Redraw(); display.render( area ); } } int Dialog::selectMountainType( const int mountainType ) { const auto & objectInfo = Maps::getObjectsByGroup( Maps::ObjectGroup::LANDSCAPE_MOUNTAINS ); GenericObjectTypeSelection listbox( objectInfo, { 420, fheroes2::Display::instance().height() - 180 }, _( "Select Mountain Object:" ) ); return selectObjectType( mountainType, objectInfo.size(), listbox ); } int Dialog::selectRockType( const int rockType ) { const auto & objectInfo = Maps::getObjectsByGroup( Maps::ObjectGroup::LANDSCAPE_ROCKS ); GenericObjectTypeSelection listbox( objectInfo, { 420, fheroes2::Display::instance().height() - 180 }, _( "Select Rock Object:" ) ); return selectObjectType( rockType, objectInfo.size(), listbox ); } int Dialog::selectTreeType( const int treeType ) { const auto & objectInfo = Maps::getObjectsByGroup( Maps::ObjectGroup::LANDSCAPE_TREES ); GenericObjectTypeSelection listbox( objectInfo, { 420, fheroes2::Display::instance().height() - 180 }, _( "Select Tree Object:" ) ); return selectObjectType( treeType, objectInfo.size(), listbox ); } int Dialog::selectPowerUpObjectType( const int powerUpObjectType ) { const auto & objectInfo = Maps::getObjectsByGroup( Maps::ObjectGroup::ADVENTURE_POWER_UPS ); PowerUpObjectTypeSelection listbox( objectInfo, { 420, fheroes2::Display::instance().height() - 180 }, _( "Select Power Up Object:" ) ); return selectObjectType( powerUpObjectType, objectInfo.size(), listbox ); } int Dialog::selectAdventureMiscellaneousObjectType( const int objectType ) { const auto & objectInfo = Maps::getObjectsByGroup( Maps::ObjectGroup::ADVENTURE_MISCELLANEOUS ); GenericObjectTypeSelection listbox( objectInfo, { 420, fheroes2::Display::instance().height() - 180 }, _( "Select Adventure Object:" ) ); return selectObjectType( objectType, objectInfo.size(), listbox ); } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_selectitems.h000066400000000000000000000066601456075706000240250ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2011 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2EDITOR_DIALOGS_H #define H2EDITOR_DIALOGS_H #include #include #include "artifact.h" #include "heroes.h" #include "monster.h" #include "skill.h" #include "spell.h" class Kingdom; namespace Dialog { Monster selectMonster( const int monsterId ); int selectHeroes( const int heroId = Heroes::UNKNOWN ); Artifact selectArtifact( const int artifactId ); Spell selectSpell( const int spellId, const bool includeRandomSpells ); int32_t selectKingdomCastle( const Kingdom & kingdom, const bool notOccupiedByHero, std::string title, std::string description = {}, int32_t castlePositionIndex = -1 ); Skill::Secondary selectSecondarySkill( const Heroes & hero, const int skillId = Skill::Secondary::UNKNOWN ); // These functions should be called only from the Editor as they rely on Maps::ObjectInfo structures that are not the same as in-game items. int selectHeroType( const int heroType ); int selectMonsterType( const int monsterType ); int selectArtifactType( const int artifactType ); int selectTreasureType( const int resourceType ); int selectOceanObjectType( const int objectType ); int selectLandscapeOceanObjectType( const int objectType ); void selectTownType( int & type, int & color ); int selectDwellingType( const int dwellingType ); int selectLandscapeMiscellaneousObjectType( const int objectType ); void selectMineType( int32_t & type, int32_t & color ); int selectMountainType( const int mountainType ); int selectRockType( const int rockType ); int selectTreeType( const int treeType ); int selectPowerUpObjectType( const int powerUpObjectType ); int selectAdventureMiscellaneousObjectType( const int objectType ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_selectscenario.cpp000066400000000000000000000753101456075706000250400ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "dialog_selectscenario.h" #include #include #include #include #include #include "agg_image.h" #include "color.h" #include "cursor.h" #include "dialog.h" #include "difficulty.h" #include "game_hotkeys.h" #include "icn.h" #include "image.h" #include "localevent.h" #include "screen.h" #include "settings.h" #include "system.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_scrollbar.h" #include "ui_text.h" namespace { enum { // MAP LIST SCENARIO_LIST_COUNT_PLAYERS_OFFSET_X = 48, SCENARIO_LIST_MAP_SIZE_OFFSET_X = 67, SCENARIO_LIST_MAP_TYPE_OFFSET_X = 86, SCENARIO_LIST_MAP_NAME_OFFSET_X = 104, SCENARIO_LIST_MAP_NAME_WIDTH = 160, SCENARIO_LIST_VICTORY_CONDITION_OFFSET_X = 266, SCENARIO_LIST_LOSS_CONDITION_OFFSET_X = 285, SCENARIO_LIST_ROW_OFFSET_Y = 57, SCENARIO_LIST_COLUMN_HEIGHT = 175, MAP_LIST_ROW_SPACING_Y = 4, // SELECTED SELECTED_SCENARIO_COUNT_PLAYERS_OFFSET_X = 45, SELECTED_SCENARIO_MAP_SIZE_OFFSET_X = 64, SELECTED_SCENARIO_MAP_TYPE_OFFSET_X = 83, SELECTED_SCENARIO_MAP_NAME_OFFSET_X = 108, SELECTED_SCENARIO_MAP_NAME_WIDTH = 160, SELECTED_SCENARIO_VICTORY_CONDITION_OFFSET_X = 276, SELECTED_SCENARIO_LOSS_CONDITION_OFFSET_X = 295, SELECTED_SCENARIO_DIFFICULTY_OFFSET_X = 220, SELECTED_SCENARIO_DIFFICULTY_OFFSET_Y = 293, SELECTED_SCENARIO_DIFFICULTY_WIDTH = 114, SELECTED_SCENARIO_DIFFICULTY_HEIGHT = 20, SELECTED_SCENARIO_DESCRIPTION_OFFSET_X = 42, SELECTED_SCENARIO_DESCRIPTION_OFFSET_Y = 318, SELECTED_SCENARIO_DESCRIPTION_WIDTH = 292, SELECTED_SCENARIO_DESCRIPTION_HEIGHT = 90, SELECTED_SCENARIO_GENERAL_OFFSET_Y = 265, // COMMON ICON_SIZE = 18, MAP_SIZE_BUTTON_OFFSET_Y = 23 }; Maps::mapsize_t currentMapFilter = Maps::ZERO; void ShowToolTip( const std::string & header, const std::string & body ) { fheroes2::showStandardTextMessage( header, body, Dialog::ZERO ); } void mapInfo( const Maps::FileInfo & info ) { // On some OSes like Windows, the path may contain '\' symbols. This symbol doesn't exist in the resources. // To avoid this we have to replace all '\' symbols by '/' symbols. std::string fullPath = info.filename; StringReplace( fullPath, "\\", "/" ); fheroes2::Text header( info.name, fheroes2::FontType::normalYellow() ); fheroes2::MultiFontText body; body.add( { _( "Map Type:\n" ), fheroes2::FontType::normalYellow() } ); switch ( info.version ) { case GameVersion::SUCCESSION_WARS: body.add( { _( "The Succession Wars" ), fheroes2::FontType::normalWhite() } ); break; case GameVersion::PRICE_OF_LOYALTY: body.add( { _( "The Price of Loyalty" ), fheroes2::FontType::normalWhite() } ); break; case GameVersion::RESURRECTION: body.add( { _( "Resurrection" ), fheroes2::FontType::normalWhite() } ); break; default: // Did you add a new map version? Add the logic above! assert( 0 ); break; } body.add( { _( "\n\nLocation: " ), fheroes2::FontType::smallYellow() } ); body.add( { fullPath, fheroes2::FontType::smallWhite() } ); fheroes2::showMessage( header, body, Dialog::ZERO ); } void LossConditionInfo( const Maps::FileInfo & info ) { std::string msg; switch ( info.lossConditionType ) { case Maps::FileInfo::LOSS_EVERYTHING: msg = _( "Lose all your heroes and towns." ); break; case Maps::FileInfo::LOSS_TOWN: msg = _( "Lose a specific town." ); break; case Maps::FileInfo::LOSS_HERO: msg = _( "Lose a specific hero." ); break; case Maps::FileInfo::LOSS_OUT_OF_TIME: msg = _( "Run out of time. Fail to win by a certain point." ); break; default: // This is an unknown condition. Add the logic for it above! assert( 0 ); return; } ShowToolTip( _( "Loss Condition" ), msg ); } void VictoryConditionInfo( const Maps::FileInfo & info ) { std::string msg; switch ( info.victoryConditionType ) { case Maps::FileInfo::VICTORY_DEFEAT_EVERYONE: msg = _( "Defeat all enemy heroes and towns." ); break; case Maps::FileInfo::VICTORY_CAPTURE_TOWN: msg = _( "Capture a specific town." ); break; case Maps::FileInfo::VICTORY_KILL_HERO: msg = _( "Defeat a specific hero." ); break; case Maps::FileInfo::VICTORY_OBTAIN_ARTIFACT: msg = _( "Find a specific artifact." ); break; case Maps::FileInfo::VICTORY_DEFEAT_OTHER_SIDE: msg = _( "Your side defeats the opposing side." ); break; case Maps::FileInfo::VICTORY_COLLECT_ENOUGH_GOLD: msg = _( "Accumulate a large amount of gold." ); break; default: // This is an unknown condition. Add the logic for it above! assert( 0 ); return; } ShowToolTip( _( "Victory Condition" ), msg ); } size_t GetInitialMapId( const MapsFileInfoList & lists ) { const Maps::FileInfo & mapInfo = Settings::Get().getCurrentMapInfo(); const std::string & mapFileName = System::GetBasename( mapInfo.filename ); size_t mapId = 0; for ( MapsFileInfoList::const_iterator mapIter = lists.begin(); mapIter != lists.end(); ++mapIter, ++mapId ) { if ( ( mapIter->name == mapInfo.name ) && ( System::GetBasename( mapIter->filename ) == mapFileName ) ) { return mapId; } } return 0; } int32_t GetCenteredTextXCoordinate( const int32_t startCoordX, const int32_t areaWidth, const int32_t textWidth ) { const int32_t centerTransform = areaWidth > textWidth ? ( areaWidth - textWidth ) / 2 : 0; return startCoordX + centerTransform; } fheroes2::ButtonBase * SwitchPressedMapSizeButtons( fheroes2::Button & newlyPressedButton ) { newlyPressedButton.press(); return &newlyPressedButton; } } void ScenarioListBox::RedrawItem( const Maps::FileInfo & info, int32_t /*dstx*/, int32_t dsty, bool current ) { fheroes2::Display & display = fheroes2::Display::instance(); dsty = dsty + MAP_LIST_ROW_SPACING_Y; _renderScenarioListItem( info, display, dsty, current ); } void ScenarioListBox::RedrawBackground( const fheroes2::Point & dst ) { fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::Blit( fheroes2::AGG::GetICN( ICN::REQSBKG, 0 ), display, dst.x, dst.y ); if ( isSelected() ) { _renderSelectedScenarioInfo( display, dst ); } } void ScenarioListBox::_renderScenarioListItem( const Maps::FileInfo & info, fheroes2::Display & display, const int32_t dsty, const bool current ) const { fheroes2::Blit( _getPlayersCountIcon( info.kingdomColors ), display, _offsetX + SCENARIO_LIST_COUNT_PLAYERS_OFFSET_X, dsty ); _renderMapIcon( info.width, display, _offsetX + SCENARIO_LIST_MAP_SIZE_OFFSET_X, dsty ); fheroes2::Blit( _getMapTypeIcon( info.version ), display, _offsetX + SCENARIO_LIST_MAP_TYPE_OFFSET_X, dsty ); _renderMapName( info, current, dsty, display ); fheroes2::Blit( _getWinConditionsIcon( info.victoryConditionType ), display, _offsetX + SCENARIO_LIST_VICTORY_CONDITION_OFFSET_X, dsty ); fheroes2::Blit( _getLossConditionsIcon( info.lossConditionType ), display, _offsetX + SCENARIO_LIST_LOSS_CONDITION_OFFSET_X, dsty ); } void ScenarioListBox::_renderSelectedScenarioInfo( fheroes2::Display & display, const fheroes2::Point & dst ) { const Maps::FileInfo & info = GetCurrent(); fheroes2::Blit( _getPlayersCountIcon( info.kingdomColors ), display, dst.x + SELECTED_SCENARIO_COUNT_PLAYERS_OFFSET_X, dst.y + SELECTED_SCENARIO_GENERAL_OFFSET_Y ); _renderMapIcon( info.width, display, dst.x + SELECTED_SCENARIO_MAP_SIZE_OFFSET_X, dst.y + SELECTED_SCENARIO_GENERAL_OFFSET_Y ); fheroes2::Blit( _getMapTypeIcon( info.version ), display, dst.x + SELECTED_SCENARIO_MAP_TYPE_OFFSET_X, dst.y + SELECTED_SCENARIO_GENERAL_OFFSET_Y ); fheroes2::Text mapNameText( info.name, fheroes2::FontType::normalWhite() ); mapNameText.draw( GetCenteredTextXCoordinate( dst.x + SELECTED_SCENARIO_MAP_NAME_OFFSET_X, SELECTED_SCENARIO_MAP_NAME_WIDTH, mapNameText.width() ), dst.y + SELECTED_SCENARIO_GENERAL_OFFSET_Y + 2, display ); fheroes2::Blit( _getWinConditionsIcon( info.victoryConditionType ), display, dst.x + SELECTED_SCENARIO_VICTORY_CONDITION_OFFSET_X, dst.y + SELECTED_SCENARIO_GENERAL_OFFSET_Y ); fheroes2::Blit( _getLossConditionsIcon( info.lossConditionType ), display, dst.x + SELECTED_SCENARIO_LOSS_CONDITION_OFFSET_X, dst.y + SELECTED_SCENARIO_GENERAL_OFFSET_Y ); fheroes2::Text difficultyLabelText( _( "Map difficulty:" ), fheroes2::FontType::normalWhite() ); difficultyLabelText.draw( dst.x + 210 - difficultyLabelText.width(), dst.y + SELECTED_SCENARIO_DIFFICULTY_OFFSET_Y, display ); fheroes2::Text difficultyText( Difficulty::String( info.difficulty ), fheroes2::FontType::normalWhite() ); difficultyText.draw( GetCenteredTextXCoordinate( dst.x + SELECTED_SCENARIO_DIFFICULTY_OFFSET_X, SELECTED_SCENARIO_DIFFICULTY_WIDTH, difficultyText.width() ), dst.y + SELECTED_SCENARIO_DIFFICULTY_OFFSET_Y, display ); fheroes2::Text descriptionText( info.description, fheroes2::FontType::normalWhite() ); descriptionText.draw( dst.x + SELECTED_SCENARIO_DESCRIPTION_OFFSET_X, dst.y + SELECTED_SCENARIO_DESCRIPTION_OFFSET_Y + 5, SELECTED_SCENARIO_DESCRIPTION_WIDTH - 2, display ); } void ScenarioListBox::_renderMapName( const Maps::FileInfo & info, bool selected, const int32_t & baseYOffset, fheroes2::Display & display ) const { fheroes2::Text mapName( info.name, { fheroes2::FontSize::NORMAL, ( selected ? fheroes2::FontColor::YELLOW : fheroes2::FontColor::WHITE ) } ); const int32_t xCoordinate = GetCenteredTextXCoordinate( _offsetX + SCENARIO_LIST_MAP_NAME_OFFSET_X, SCENARIO_LIST_MAP_NAME_WIDTH, mapName.width() ); const int32_t yCoordinate = baseYOffset + MAP_LIST_ROW_SPACING_Y - 1; mapName.draw( xCoordinate, yCoordinate, display ); } void ScenarioListBox::SelectMapSize( MapsFileInfoList & mapsList, const int selectedSize_ ) { const fheroes2::Sprite & originalSlider = fheroes2::AGG::GetICN( ICN::ESCROLL, 3 ); const fheroes2::Image updatedScrollbarSlider = fheroes2::generateScrollbarSlider( originalSlider, false, 140, 9, static_cast( mapsList.size() ), { 0, 0, originalSlider.width(), 8 }, { 0, 7, originalSlider.width(), 8 } ); setScrollBarImage( updatedScrollbarSlider ); Maps::FileInfo currentScenario; if ( _size() > 0 ) { currentScenario = GetCurrent(); } else { currentScenario = mapsList[0]; } SetListContent( mapsList ); if ( currentScenario.width == selectedSize_ || selectedSize_ == Maps::ZERO ) { SetCurrent( currentScenario ); } selectedSize = selectedSize_; } void ScenarioListBox::_renderMapIcon( const uint16_t size, fheroes2::Display & display, const int32_t coordX, const int32_t coordY ) { int16_t mapIconIndex = -1; switch ( size ) { case Maps::SMALL: mapIconIndex = 26; break; case Maps::MEDIUM: mapIconIndex = 27; break; case Maps::LARGE: mapIconIndex = 28; break; case Maps::XLARGE: mapIconIndex = 29; break; default: break; } if ( mapIconIndex == -1 ) { fheroes2::Image icon( 17, 17 ); icon.reset(); fheroes2::Fill( icon, 1, 1, 15, 15, fheroes2::GetColorId( 0x8D, 0x73, 0xFF ) ); fheroes2::Text text( _( "N" ), fheroes2::FontType::smallWhite() ); text.draw( ( 17 - text.width() ) / 2, ( 17 - text.height() ) / 2, icon ); fheroes2::Blit( icon, display, coordX, coordY ); } else { fheroes2::Blit( fheroes2::AGG::GetICN( ICN::REQUESTS, mapIconIndex ), display, coordX, coordY ); } } const fheroes2::Sprite & ScenarioListBox::_getPlayersCountIcon( const uint8_t colors ) { const uint32_t iconIndex = 19 + Color::Count( colors ); return fheroes2::AGG::GetICN( ICN::REQUESTS, iconIndex ); } const fheroes2::Sprite & ScenarioListBox::_getMapTypeIcon( const GameVersion version ) { switch ( version ) { case GameVersion::SUCCESSION_WARS: return fheroes2::AGG::GetICN( ICN::MAP_TYPE_ICON, 0 ); case GameVersion::PRICE_OF_LOYALTY: return fheroes2::AGG::GetICN( ICN::MAP_TYPE_ICON, 1 ); case GameVersion::RESURRECTION: return fheroes2::AGG::GetICN( ICN::MAP_TYPE_ICON, 2 ); default: // Did you add a new game version? Add the corresponding logic above! assert( 0 ); break; } return fheroes2::AGG::GetICN( ICN::UNKNOWN, 0 ); } const fheroes2::Sprite & ScenarioListBox::_getWinConditionsIcon( const uint8_t condition ) { uint32_t iconIndex = 30 + condition; return fheroes2::AGG::GetICN( ICN::REQUESTS, iconIndex ); } const fheroes2::Sprite & ScenarioListBox::_getLossConditionsIcon( const uint8_t condition ) { uint32_t iconIndex = 36 + condition; return fheroes2::AGG::GetICN( ICN::REQUESTS, iconIndex ); } void ScenarioListBox::ActionListDoubleClick( Maps::FileInfo & ) { selectOk = true; } const Maps::FileInfo * Dialog::SelectScenario( const MapsFileInfoList & allMaps ) { if ( allMaps.empty() ) { return nullptr; } fheroes2::Display & display = fheroes2::Display::instance(); LocalEvent & le = LocalEvent::Get(); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); MapsFileInfoList small; MapsFileInfoList medium; MapsFileInfoList large; MapsFileInfoList xlarge; MapsFileInfoList all( allMaps ); small.reserve( all.size() ); medium.reserve( all.size() ); large.reserve( all.size() ); xlarge.reserve( all.size() ); for ( const Maps::FileInfo & info : all ) { switch ( info.width ) { case Maps::SMALL: small.push_back( info ); break; case Maps::MEDIUM: medium.push_back( info ); break; case Maps::LARGE: large.push_back( info ); break; case Maps::XLARGE: xlarge.push_back( info ); break; default: // Did you add a new map size? Add the corresponding logic! assert( 0 ); continue; } } const fheroes2::Sprite & panel = fheroes2::AGG::GetICN( ICN::REQSBKG, 0 ); const fheroes2::Rect rt( ( display.width() - panel.width() ) / 2, ( display.height() - panel.height() ) / 2, panel.width(), panel.height() ); fheroes2::ImageRestorer background( display, rt.x - SHADOWWIDTH, rt.y, rt.width + SHADOWWIDTH, rt.height + SHADOWWIDTH ); const fheroes2::Sprite & shadow = fheroes2::AGG::GetICN( ICN::REQSBKG, 1 ); fheroes2::Blit( shadow, display, rt.x - SHADOWWIDTH, rt.y + SHADOWWIDTH ); const fheroes2::Rect countPlayers( rt.x + SCENARIO_LIST_COUNT_PLAYERS_OFFSET_X, rt.y + SCENARIO_LIST_ROW_OFFSET_Y, ICON_SIZE, SCENARIO_LIST_COLUMN_HEIGHT ); const fheroes2::Rect sizeMaps( rt.x + SCENARIO_LIST_MAP_SIZE_OFFSET_X, rt.y + SCENARIO_LIST_ROW_OFFSET_Y, ICON_SIZE, SCENARIO_LIST_COLUMN_HEIGHT ); const fheroes2::Rect mapTypes( rt.x + SCENARIO_LIST_MAP_TYPE_OFFSET_X, rt.y + SCENARIO_LIST_ROW_OFFSET_Y, ICON_SIZE, SCENARIO_LIST_COLUMN_HEIGHT ); const fheroes2::Rect mapNames( rt.x + SCENARIO_LIST_MAP_NAME_OFFSET_X, rt.y + SCENARIO_LIST_ROW_OFFSET_Y, SCENARIO_LIST_MAP_NAME_WIDTH, SCENARIO_LIST_COLUMN_HEIGHT ); const fheroes2::Rect victoryConds( rt.x + SCENARIO_LIST_VICTORY_CONDITION_OFFSET_X, rt.y + SCENARIO_LIST_ROW_OFFSET_Y, ICON_SIZE, SCENARIO_LIST_COLUMN_HEIGHT ); const fheroes2::Rect lossConds( rt.x + SCENARIO_LIST_LOSS_CONDITION_OFFSET_X, rt.y + SCENARIO_LIST_ROW_OFFSET_Y, ICON_SIZE, SCENARIO_LIST_COLUMN_HEIGHT ); const fheroes2::Rect curCountPlayer( rt.x + SELECTED_SCENARIO_COUNT_PLAYERS_OFFSET_X, rt.y + SELECTED_SCENARIO_GENERAL_OFFSET_Y, ICON_SIZE, ICON_SIZE ); const fheroes2::Rect curMapSize( rt.x + SELECTED_SCENARIO_MAP_SIZE_OFFSET_X, rt.y + SELECTED_SCENARIO_GENERAL_OFFSET_Y, ICON_SIZE, ICON_SIZE ); const fheroes2::Rect curMapType( rt.x + SELECTED_SCENARIO_MAP_TYPE_OFFSET_X, rt.y + SELECTED_SCENARIO_GENERAL_OFFSET_Y, ICON_SIZE, ICON_SIZE ); const fheroes2::Rect curMapName( rt.x + SELECTED_SCENARIO_MAP_NAME_OFFSET_X, rt.y + SELECTED_SCENARIO_GENERAL_OFFSET_Y, SELECTED_SCENARIO_MAP_NAME_WIDTH, ICON_SIZE ); const fheroes2::Rect curVictoryCond( rt.x + SELECTED_SCENARIO_VICTORY_CONDITION_OFFSET_X, rt.y + SELECTED_SCENARIO_GENERAL_OFFSET_Y, ICON_SIZE, ICON_SIZE ); const fheroes2::Rect curLossCond( rt.x + SELECTED_SCENARIO_LOSS_CONDITION_OFFSET_X, rt.y + SELECTED_SCENARIO_GENERAL_OFFSET_Y, ICON_SIZE, ICON_SIZE ); const fheroes2::Rect curDifficulty( rt.x + SELECTED_SCENARIO_DIFFICULTY_OFFSET_X, rt.y + SELECTED_SCENARIO_DIFFICULTY_OFFSET_Y, SELECTED_SCENARIO_DIFFICULTY_WIDTH, SELECTED_SCENARIO_DIFFICULTY_HEIGHT ); const fheroes2::Rect curDescription( rt.x + SELECTED_SCENARIO_DESCRIPTION_OFFSET_X, rt.y + SELECTED_SCENARIO_DESCRIPTION_OFFSET_Y, SELECTED_SCENARIO_DESCRIPTION_WIDTH, SELECTED_SCENARIO_DESCRIPTION_HEIGHT ); fheroes2::Button buttonOk( rt.x + 140, rt.y + 410, ICN::BUTTON_SMALL_OKAY_GOOD, 0, 1 ); fheroes2::Button buttonSelectSmall( rt.x + 36, rt.y + MAP_SIZE_BUTTON_OFFSET_Y, ICN::BUTTON_MAPSIZE_SMALL, 0, 1 ); fheroes2::Button buttonSelectMedium( rt.x + 98, rt.y + MAP_SIZE_BUTTON_OFFSET_Y, ICN::BUTTON_MAPSIZE_MEDIUM, 0, 1 ); fheroes2::Button buttonSelectLarge( rt.x + 160, rt.y + MAP_SIZE_BUTTON_OFFSET_Y, ICN::BUTTON_MAPSIZE_LARGE, 0, 1 ); fheroes2::Button buttonSelectXLarge( rt.x + 222, rt.y + MAP_SIZE_BUTTON_OFFSET_Y, ICN::BUTTON_MAPSIZE_XLARGE, 0, 1 ); fheroes2::Button buttonSelectAll( rt.x + 284, rt.y + MAP_SIZE_BUTTON_OFFSET_Y, ICN::BUTTON_MAPSIZE_ALL, 0, 1 ); ScenarioListBox listbox( rt.getPosition() ); listbox.SetScrollButtonUp( ICN::REQUESTS, 5, 6, { rt.x + 327, rt.y + 55 } ); listbox.SetScrollButtonDn( ICN::REQUESTS, 7, 8, { rt.x + 327, rt.y + 217 } ); listbox.setScrollBarArea( { rt.x + 328, rt.y + 73, 12, 140 } ); listbox.SetAreaMaxItems( 9 ); // This has impact on displaying selected scenario info listbox.SetAreaItems( { rt.x + 55, rt.y + 55, 270, 175 } ); fheroes2::ButtonBase * currentPressedButton = nullptr; { MapsFileInfoList * currentMapsList = nullptr; const auto resetFilter = [&all, &buttonSelectAll, ¤tPressedButton, ¤tMapsList]() { currentPressedButton = &buttonSelectAll; currentMapsList = &all; currentMapFilter = Maps::ZERO; }; switch ( currentMapFilter ) { case Maps::SMALL: currentPressedButton = &buttonSelectSmall; currentMapsList = &small; break; case Maps::MEDIUM: currentPressedButton = &buttonSelectMedium; currentMapsList = &medium; break; case Maps::LARGE: currentPressedButton = &buttonSelectLarge; currentMapsList = &large; break; case Maps::XLARGE: currentPressedButton = &buttonSelectXLarge; currentMapsList = &xlarge; break; default: resetFilter(); break; } assert( currentMapsList != nullptr ); if ( currentMapsList->empty() ) { resetFilter(); } assert( currentPressedButton != nullptr && currentMapsList != nullptr && !currentMapsList->empty() ); listbox.SelectMapSize( *currentMapsList, currentMapFilter ); listbox.SetCurrent( GetInitialMapId( *currentMapsList ) ); currentPressedButton->press(); } fheroes2::OptionButtonGroup buttonGroup; buttonGroup.addButton( &buttonSelectSmall ); buttonGroup.addButton( &buttonSelectMedium ); buttonGroup.addButton( &buttonSelectLarge ); buttonGroup.addButton( &buttonSelectXLarge ); buttonGroup.addButton( &buttonSelectAll ); listbox.RedrawBackground( rt.getPosition() ); listbox.Redraw(); buttonOk.draw(); buttonSelectSmall.draw(); buttonSelectMedium.draw(); buttonSelectLarge.draw(); buttonSelectXLarge.draw(); buttonSelectAll.draw(); display.render(); while ( le.HandleEvents() ) { le.MousePressLeft( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); if ( le.MousePressLeft( buttonSelectSmall.area() ) ) { buttonSelectSmall.drawOnPress(); } else if ( currentPressedButton != &buttonSelectSmall ) { buttonSelectSmall.drawOnRelease(); } if ( le.MousePressLeft( buttonSelectMedium.area() ) ) { buttonSelectMedium.drawOnPress(); } else if ( currentPressedButton != &buttonSelectMedium ) { buttonSelectMedium.drawOnRelease(); } if ( le.MousePressLeft( buttonSelectLarge.area() ) ) { buttonSelectLarge.drawOnPress(); } else if ( currentPressedButton != &buttonSelectLarge ) { buttonSelectLarge.drawOnRelease(); } if ( le.MousePressLeft( buttonSelectXLarge.area() ) ) { buttonSelectXLarge.drawOnPress(); } else if ( currentPressedButton != &buttonSelectXLarge ) { buttonSelectXLarge.drawOnRelease(); } if ( le.MousePressLeft( buttonSelectAll.area() ) ) { buttonSelectAll.drawOnPress(); } else if ( currentPressedButton != &buttonSelectAll ) { buttonSelectAll.drawOnRelease(); } listbox.QueueEventProcessing(); bool needRedraw = false; if ( le.MouseClickLeft( buttonOk.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) || listbox.selectOk ) { MapsFileInfoList::const_iterator it = std::find( allMaps.begin(), allMaps.end(), listbox.GetCurrent() ); return ( it != allMaps.end() ) ? &( *it ) : nullptr; } if ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { return nullptr; } if ( le.MouseClickLeft( buttonSelectSmall.area() ) || HotKeyPressEvent( Game::HotKeyEvent::MAIN_MENU_MAP_SIZE_SMALL ) ) { if ( small.empty() ) { fheroes2::showStandardTextMessage( "", _( "No maps exist at that size" ), Dialog::OK ); currentPressedButton->drawOnPress(); } else { currentMapFilter = Maps::SMALL; listbox.SelectMapSize( small, Maps::SMALL ); currentPressedButton = SwitchPressedMapSizeButtons( buttonSelectSmall ); } needRedraw = true; } else if ( le.MouseClickLeft( buttonSelectMedium.area() ) || HotKeyPressEvent( Game::HotKeyEvent::MAIN_MENU_MAP_SIZE_MEDIUM ) ) { if ( medium.empty() ) { fheroes2::showStandardTextMessage( "", _( "No maps exist at that size" ), Dialog::OK ); currentPressedButton->drawOnPress(); } else { currentMapFilter = Maps::MEDIUM; listbox.SelectMapSize( medium, Maps::MEDIUM ); currentPressedButton = SwitchPressedMapSizeButtons( buttonSelectMedium ); } needRedraw = true; } else if ( le.MouseClickLeft( buttonSelectLarge.area() ) || HotKeyPressEvent( Game::HotKeyEvent::MAIN_MENU_MAP_SIZE_LARGE ) ) { if ( large.empty() ) { fheroes2::showStandardTextMessage( "", _( "No maps exist at that size" ), Dialog::OK ); currentPressedButton->drawOnPress(); } else { currentMapFilter = Maps::LARGE; listbox.SelectMapSize( large, Maps::LARGE ); currentPressedButton = SwitchPressedMapSizeButtons( buttonSelectLarge ); } needRedraw = true; } else if ( le.MouseClickLeft( buttonSelectXLarge.area() ) || HotKeyPressEvent( Game::HotKeyEvent::MAIN_MENU_MAP_SIZE_EXTRA_LARGE ) ) { if ( xlarge.empty() ) { fheroes2::showStandardTextMessage( "", _( "No maps exist at that size" ), Dialog::OK ); currentPressedButton->drawOnPress(); } else { currentMapFilter = Maps::XLARGE; listbox.SelectMapSize( xlarge, Maps::XLARGE ); currentPressedButton = SwitchPressedMapSizeButtons( buttonSelectXLarge ); } needRedraw = true; } else if ( le.MouseClickLeft( buttonSelectAll.area() ) || HotKeyPressEvent( Game::HotKeyEvent::MAIN_MENU_MAP_SIZE_ALL ) ) { currentMapFilter = Maps::ZERO; listbox.SelectMapSize( all, Maps::ZERO ); currentPressedButton = SwitchPressedMapSizeButtons( buttonSelectAll ); needRedraw = true; } if ( le.MousePressRight( buttonSelectSmall.area() ) ) ShowToolTip( _( "Small Maps" ), _( "View only maps of size small (36 x 36)." ) ); else if ( le.MousePressRight( buttonSelectMedium.area() ) ) ShowToolTip( _( "Medium Maps" ), _( "View only maps of size medium (72 x 72)." ) ); else if ( le.MousePressRight( buttonSelectLarge.area() ) ) ShowToolTip( _( "Large Maps" ), _( "View only maps of size large (108 x 108)." ) ); else if ( le.MousePressRight( buttonSelectXLarge.area() ) ) ShowToolTip( _( "Extra Large Maps" ), _( "View only maps of size extra large (144 x 144)." ) ); else if ( le.MousePressRight( buttonSelectAll.area() ) ) ShowToolTip( _( "All Maps" ), _( "View all maps, regardless of size." ) ); else if ( le.MousePressRight( countPlayers ) || le.MousePressRight( curCountPlayer ) ) ShowToolTip( _( "Players Icon" ), _( "Indicates how many players total are in the scenario. Any positions not occupied by humans will be occupied by computer players." ) ); else if ( le.MousePressRight( sizeMaps ) || le.MousePressRight( curMapSize ) ) ShowToolTip( _( "Size Icon" ), _( "Indicates whether the map\nis small (36 x 36), medium\n(72 x 72), large (108 x 108),\nor extra large (144 x 144)." ) ); else if ( le.MousePressRight( mapTypes ) || le.MousePressRight( curMapType ) ) // TODO: update this tooltip once the Editor is out for public. ShowToolTip( _( "Map Type" ), _( "Indicates whether the map is made for \"The Succession Wars\" or \"The Price of Loyalty\" version of the game." ) ); else if ( le.MousePressRight( mapNames ) ) { const Maps::FileInfo * item = listbox.GetFromPosition( le.GetMouseCursor() ); if ( item ) mapInfo( *item ); } else if ( le.MousePressRight( curMapName ) ) ShowToolTip( _( "Selected Name" ), _( "The name of the currently selected map." ) ); else if ( le.MousePressRight( victoryConds ) ) { const Maps::FileInfo * item = listbox.GetFromPosition( le.GetMouseCursor() ); if ( item ) VictoryConditionInfo( *item ); } else if ( le.MousePressRight( lossConds ) ) { const Maps::FileInfo * item = listbox.GetFromPosition( le.GetMouseCursor() ); if ( item ) LossConditionInfo( *item ); } else if ( le.MousePressRight( curVictoryCond ) ) VictoryConditionInfo( listbox.GetCurrent() ); else if ( le.MousePressRight( curLossCond ) ) LossConditionInfo( listbox.GetCurrent() ); else if ( le.MousePressRight( curDifficulty ) ) ShowToolTip( _( "Selected Map Difficulty" ), _( "The map difficulty of the currently selected map. The map difficulty is determined by the scenario designer. More difficult maps might include more or stronger enemies, fewer resources, or other special conditions making things tougher for the human player." ) ); else if ( le.MousePressRight( curDescription ) ) ShowToolTip( _( "Selected Description" ), _( "The description of the currently selected map." ) ); else if ( le.MousePressRight( buttonOk.area() ) ) ShowToolTip( _( "Okay" ), _( "Accept the choice made." ) ); if ( !needRedraw && !listbox.IsNeedRedraw() ) { continue; } listbox.Redraw(); buttonOk.draw(); buttonSelectSmall.draw(); buttonSelectMedium.draw(); buttonSelectLarge.draw(); buttonSelectXLarge.draw(); buttonSelectAll.draw(); display.render(); } return nullptr; } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_selectscenario.h000066400000000000000000000077741456075706000245160ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2SELECT_SCENARIO_H #define H2SELECT_SCENARIO_H #include #include "interface_list.h" #include "maps.h" #include "maps_fileinfo.h" #include "math_base.h" namespace fheroes2 { class Display; class Sprite; } class ScenarioListBox : public Interface::ListBox { public: using Interface::ListBox::ActionListDoubleClick; using Interface::ListBox::ActionListSingleClick; using Interface::ListBox::ActionListPressRight; explicit ScenarioListBox( const fheroes2::Point & pt ) : Interface::ListBox( pt ) , selectOk( false ) , _offsetX( pt.x ) {} void RedrawItem( const Maps::FileInfo & info, int32_t /*dstx*/, int32_t dsty, bool current ) override; void RedrawBackground( const fheroes2::Point & dst ) override; void SelectMapSize( MapsFileInfoList & mapsList, const int selectedSize ); void ActionCurrentUp() override { // Do nothing. } void ActionCurrentDn() override { // Do nothing. } void ActionListDoubleClick( Maps::FileInfo & ) override; void ActionListSingleClick( Maps::FileInfo & ) override { // Do nothing. } void ActionListPressRight( Maps::FileInfo & /* info */ ) override { // Do nothing. } bool selectOk; private: int selectedSize{ Maps::ZERO }; const int32_t _offsetX; void _renderScenarioListItem( const Maps::FileInfo & info, fheroes2::Display & display, const int32_t dsty, const bool current ) const; void _renderSelectedScenarioInfo( fheroes2::Display & display, const fheroes2::Point & dst ); void _renderMapName( const Maps::FileInfo & info, bool selected, const int32_t & baseYOffset, fheroes2::Display & display ) const; static void _renderMapIcon( const uint16_t size, fheroes2::Display & display, const int32_t coordX, const int32_t coordY ); static const fheroes2::Sprite & _getPlayersCountIcon( const uint8_t colors ); static const fheroes2::Sprite & _getMapTypeIcon( const GameVersion version ); static const fheroes2::Sprite & _getWinConditionsIcon( const uint8_t condition ); static const fheroes2::Sprite & _getLossConditionsIcon( const uint8_t condition ); }; namespace Dialog { const Maps::FileInfo * SelectScenario( const MapsFileInfoList & allMaps ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_system_options.cpp000066400000000000000000000520201456075706000251250ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "dialog_system_options.h" #include #include #include #include #include #include "agg_image.h" #include "cursor.h" #include "dialog.h" #include "dialog_audio.h" #include "dialog_graphics_settings.h" #include "dialog_hotkeys.h" #include "dialog_interface_settings.h" #include "dialog_language_selection.h" #include "game_delays.h" #include "game_hotkeys.h" #include "game_interface.h" #include "gamedefs.h" #include "icn.h" #include "image.h" #include "interface_base.h" #include "localevent.h" #include "math_base.h" #include "screen.h" #include "settings.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_language.h" #include "ui_option_item.h" namespace { enum class DialogAction : int { Configuration, Language, Graphics, AudioSettings, HotKeys, InterfaceSettings, UpdateInterface, Close }; const fheroes2::Size offsetBetweenOptions{ 92, 110 }; const fheroes2::Point optionOffset{ 36, 47 }; const int32_t optionWindowSize{ 65 }; const fheroes2::Rect languageRoi{ optionOffset.x, optionOffset.y, optionWindowSize, optionWindowSize }; const fheroes2::Rect graphicsRoi{ optionOffset.x + offsetBetweenOptions.width, optionOffset.y, optionWindowSize, optionWindowSize }; const fheroes2::Rect audioRoi{ optionOffset.x + offsetBetweenOptions.width * 2, optionOffset.y, optionWindowSize, optionWindowSize }; const fheroes2::Rect heroSpeedRoi{ optionOffset.x, optionOffset.y + offsetBetweenOptions.height, optionWindowSize, optionWindowSize }; const fheroes2::Rect enemySpeedRoi{ optionOffset.x + offsetBetweenOptions.width, optionOffset.y + offsetBetweenOptions.height, optionWindowSize, optionWindowSize }; const fheroes2::Rect hotKeyRoi{ optionOffset.x + offsetBetweenOptions.width * 2, optionOffset.y + offsetBetweenOptions.height, optionWindowSize, optionWindowSize }; const fheroes2::Rect interfaceRoi{ optionOffset.x, optionOffset.y + offsetBetweenOptions.height * 2, optionWindowSize, optionWindowSize }; const fheroes2::Rect textSupportModeRoi{ optionOffset.x + offsetBetweenOptions.width, optionOffset.y + offsetBetweenOptions.height * 2, optionWindowSize, optionWindowSize }; const fheroes2::Rect battlesRoi{ optionOffset.x + offsetBetweenOptions.width * 2, optionOffset.y + offsetBetweenOptions.height * 2, optionWindowSize, optionWindowSize }; void drawLanguage( const fheroes2::Rect & optionRoi ) { const fheroes2::SupportedLanguage currentLanguage = fheroes2::getLanguageFromAbbreviation( Settings::Get().getGameLanguage() ); fheroes2::LanguageSwitcher languageSwitcher( currentLanguage ); fheroes2::drawOption( optionRoi, fheroes2::AGG::GetICN( ICN::SPANEL, 18 ), _( "Language" ), fheroes2::getLanguageName( currentLanguage ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); } void drawGraphics( const fheroes2::Rect & optionRoi ) { fheroes2::drawOption( optionRoi, fheroes2::AGG::GetICN( ICN::GAME_OPTION_ICON, 1 ), _( "Graphics" ), _( "Settings" ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); } void drawAudioOptions( const fheroes2::Rect & optionRoi ) { fheroes2::drawOption( optionRoi, fheroes2::AGG::GetICN( ICN::SPANEL, 1 ), _( "Audio" ), _( "Settings" ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); } void drawHeroSpeed( const fheroes2::Rect & optionRoi ) { const Settings & conf = Settings::Get(); const int heroSpeed = conf.HeroesMoveSpeed(); uint32_t heroSpeedIconId = 9; if ( heroSpeed >= 4 ) { heroSpeedIconId = 3 + heroSpeed / 2; } else if ( heroSpeed > 0 ) { heroSpeedIconId = 4; } const fheroes2::Sprite & heroSpeedIcon = fheroes2::AGG::GetICN( ICN::SPANEL, heroSpeedIconId ); std::string value; if ( heroSpeed == 10 ) { value = _( "Jump" ); } else { value = std::to_string( heroSpeed ); } fheroes2::drawOption( optionRoi, heroSpeedIcon, _( "Hero Speed" ), std::move( value ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); } void drawEnemySpeed( const fheroes2::Rect & optionRoi ) { const Settings & conf = Settings::Get(); const int aiSpeed = conf.AIMoveSpeed(); uint32_t aiSpeedIconId = 9; if ( aiSpeed >= 4 ) { aiSpeedIconId = 3 + aiSpeed / 2; } else if ( aiSpeed > 0 ) { aiSpeedIconId = 4; } const fheroes2::Sprite & aiSpeedIcon = fheroes2::AGG::GetICN( ICN::SPANEL, aiSpeedIconId ); std::string value; if ( aiSpeed == 0 ) { value = _( "Don't Show" ); } else if ( aiSpeed == 10 ) { value = _( "Jump" ); } else { value = std::to_string( aiSpeed ); } fheroes2::drawOption( optionRoi, aiSpeedIcon, _( "Enemy Speed" ), std::move( value ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); } void drawHotKeyOptions( const fheroes2::Rect & optionRoi ) { fheroes2::drawOption( optionRoi, fheroes2::AGG::GetICN( ICN::GAME_OPTION_ICON, 0 ), _( "Hot Keys" ), _( "Configure" ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); } void drawInterfaceSettings( const fheroes2::Rect & optionRoi ) { const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); const fheroes2::Sprite & interfaceThemeIcon = fheroes2::AGG::GetICN( ICN::SPANEL, isEvilInterface ? 17 : 16 ); fheroes2::drawOption( optionRoi, interfaceThemeIcon, _( "Interface" ), _( "Settings" ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); } void drawTextSupportModeOptions( const fheroes2::Rect & optionRoi ) { if ( Settings::Get().isTextSupportModeEnabled() ) { fheroes2::drawOption( optionRoi, fheroes2::AGG::GetICN( ICN::CSPANEL, 4 ), _( "Text Support" ), _( "On" ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); } else { fheroes2::drawOption( optionRoi, fheroes2::AGG::GetICN( ICN::SPANEL, 9 ), _( "Text Support" ), _( "Off" ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); } } void drawBattles( const fheroes2::Rect & optionRoi ) { const Settings & conf = Settings::Get(); if ( conf.BattleAutoResolve() ) { const bool spellcast = conf.BattleAutoSpellcast(); std::string value = spellcast ? _( "Auto Resolve" ) : _( "Auto, No Spells" ); const fheroes2::Sprite & autoBattleIcon = fheroes2::AGG::GetICN( ICN::CSPANEL, spellcast ? 7 : 6 ); fheroes2::drawOption( optionRoi, autoBattleIcon, _( "Battles" ), std::move( value ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); } else { const fheroes2::Sprite & autoBattleIcon = fheroes2::AGG::GetICN( ICN::SPANEL, 18 ); fheroes2::drawOption( optionRoi, autoBattleIcon, _( "Battles" ), _( "autoBattle|Manual" ), fheroes2::UiOptionTextWidth::THREE_ELEMENTS_ROW ); } } DialogAction openSystemOptionsDialog( bool & saveConfiguration ) { const CursorRestorer cursorRestorer( true, Cursor::POINTER ); Settings & conf = Settings::Get(); const bool isEvilInterface = conf.isEvilInterfaceEnabled(); fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::Sprite & dialog = fheroes2::AGG::GetICN( ( isEvilInterface ? ICN::SPANBKGE : ICN::SPANBKG ), 0 ); const fheroes2::Sprite & dialogShadow = fheroes2::AGG::GetICN( ( isEvilInterface ? ICN::SPANBKGE : ICN::SPANBKG ), 1 ); const fheroes2::Point dialogOffset( ( display.width() - dialog.width() ) / 2, ( display.height() - dialog.height() ) / 2 ); const fheroes2::Point shadowOffset( dialogOffset.x - BORDERWIDTH, dialogOffset.y ); fheroes2::ImageRestorer restorer( display, shadowOffset.x, shadowOffset.y, dialog.width() + BORDERWIDTH, dialog.height() + BORDERWIDTH ); const fheroes2::Rect windowRoi{ dialogOffset.x, dialogOffset.y, dialog.width(), dialog.height() }; fheroes2::Blit( dialogShadow, display, windowRoi.x - BORDERWIDTH, windowRoi.y + BORDERWIDTH ); fheroes2::Blit( dialog, display, windowRoi.x, windowRoi.y ); fheroes2::ImageRestorer emptyDialogRestorer( display, windowRoi.x, windowRoi.y, windowRoi.width, windowRoi.height ); const fheroes2::Rect windowLanguageRoi( languageRoi + windowRoi.getPosition() ); const fheroes2::Rect windowGraphicsRoi( graphicsRoi + windowRoi.getPosition() ); const fheroes2::Rect windowAudioRoi( audioRoi + windowRoi.getPosition() ); const fheroes2::Rect windowHeroSpeedRoi( heroSpeedRoi + windowRoi.getPosition() ); const fheroes2::Rect windowEnemySpeedRoi( enemySpeedRoi + windowRoi.getPosition() ); const fheroes2::Rect windowHotKeyRoi( hotKeyRoi + windowRoi.getPosition() ); const fheroes2::Rect windowInterfaceRoi( interfaceRoi + windowRoi.getPosition() ); const fheroes2::Rect windowTextSupportModeRoi( textSupportModeRoi + windowRoi.getPosition() ); const fheroes2::Rect windowBattlesRoi( battlesRoi + windowRoi.getPosition() ); const auto drawOptions = [&windowLanguageRoi, &windowGraphicsRoi, &windowAudioRoi, &windowHeroSpeedRoi, &windowEnemySpeedRoi, &windowHotKeyRoi, &windowInterfaceRoi, &windowTextSupportModeRoi, &windowBattlesRoi]() { drawLanguage( windowLanguageRoi ); drawGraphics( windowGraphicsRoi ); drawAudioOptions( windowAudioRoi ); drawHeroSpeed( windowHeroSpeedRoi ); drawEnemySpeed( windowEnemySpeedRoi ); drawHotKeyOptions( windowHotKeyRoi ); drawInterfaceSettings( windowInterfaceRoi ); drawTextSupportModeOptions( windowTextSupportModeRoi ); drawBattles( windowBattlesRoi ); }; drawOptions(); const auto refreshWindow = [&drawOptions, &emptyDialogRestorer, &display]() { emptyDialogRestorer.restore(); drawOptions(); display.render( emptyDialogRestorer.rect() ); }; const fheroes2::Point buttonOffset( 112 + windowRoi.x, 362 + windowRoi.y ); fheroes2::Button okayButton( buttonOffset.x, buttonOffset.y, isEvilInterface ? ICN::BUTTON_SMALL_OKAY_EVIL : ICN::BUTTON_SMALL_OKAY_GOOD, 0, 1 ); okayButton.draw(); display.render(); bool isTextSupportModeEnabled = conf.isTextSupportModeEnabled(); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { if ( le.MousePressLeft( okayButton.area() ) ) { okayButton.drawOnPress(); } else { okayButton.drawOnRelease(); } if ( le.MouseClickLeft( okayButton.area() ) || Game::HotKeyCloseWindow() ) { break; } if ( le.MouseClickLeft( windowLanguageRoi ) ) { return DialogAction::Language; } if ( le.MouseClickLeft( windowGraphicsRoi ) ) { return DialogAction::Graphics; } if ( le.MouseClickLeft( windowAudioRoi ) ) { return DialogAction::AudioSettings; } if ( le.MouseClickLeft( windowHeroSpeedRoi ) ) { saveConfiguration = true; conf.SetHeroesMoveSpeed( conf.HeroesMoveSpeed() % 10 + 1 ); Game::UpdateGameSpeed(); refreshWindow(); continue; } if ( le.MouseWheelUp( windowHeroSpeedRoi ) ) { saveConfiguration = true; conf.SetHeroesMoveSpeed( conf.HeroesMoveSpeed() + 1 ); Game::UpdateGameSpeed(); refreshWindow(); continue; } if ( le.MouseWheelDn( windowHeroSpeedRoi ) ) { saveConfiguration = true; conf.SetHeroesMoveSpeed( conf.HeroesMoveSpeed() - 1 ); Game::UpdateGameSpeed(); refreshWindow(); continue; } if ( le.MouseClickLeft( windowEnemySpeedRoi ) ) { saveConfiguration = true; conf.SetAIMoveSpeed( ( conf.AIMoveSpeed() + 1 ) % 11 ); Game::UpdateGameSpeed(); refreshWindow(); continue; } if ( le.MouseWheelUp( windowEnemySpeedRoi ) ) { saveConfiguration = true; conf.SetAIMoveSpeed( conf.AIMoveSpeed() + 1 ); Game::UpdateGameSpeed(); refreshWindow(); continue; } if ( le.MouseWheelDn( windowEnemySpeedRoi ) ) { saveConfiguration = true; conf.SetAIMoveSpeed( conf.AIMoveSpeed() - 1 ); Game::UpdateGameSpeed(); refreshWindow(); continue; } if ( le.MouseClickLeft( windowHotKeyRoi ) ) { return DialogAction::HotKeys; } if ( le.MouseClickLeft( windowInterfaceRoi ) ) { return DialogAction::InterfaceSettings; } if ( le.MouseClickLeft( windowTextSupportModeRoi ) ) { saveConfiguration = true; conf.setTextSupportMode( !conf.isTextSupportModeEnabled() ); refreshWindow(); continue; } if ( le.MouseClickLeft( windowBattlesRoi ) ) { saveConfiguration = true; if ( conf.BattleAutoResolve() ) { if ( conf.BattleAutoSpellcast() ) { conf.setBattleAutoSpellcast( false ); } else { conf.setBattleAutoResolve( false ); } } else { conf.setBattleAutoResolve( true ); conf.setBattleAutoSpellcast( true ); } refreshWindow(); continue; } if ( le.MousePressRight( windowLanguageRoi ) ) { fheroes2::showStandardTextMessage( _( "Select Game Language" ), _( "Change the language of the game." ), 0 ); } else if ( le.MousePressRight( windowGraphicsRoi ) ) { fheroes2::showStandardTextMessage( _( "Graphics" ), _( "Change the graphics settings of the game." ), 0 ); } else if ( le.MousePressRight( windowAudioRoi ) ) { fheroes2::showStandardTextMessage( _( "Audio" ), _( "Change the audio settings of the game." ), 0 ); } else if ( le.MousePressRight( windowHeroSpeedRoi ) ) { fheroes2::showStandardTextMessage( _( "Hero Speed" ), _( "Change the speed at which your heroes move on the main screen." ), 0 ); } else if ( le.MousePressRight( windowEnemySpeedRoi ) ) { fheroes2::showStandardTextMessage( _( "Enemy Speed" ), _( "Sets the speed that A.I. heroes move at. You can also elect not to view A.I. movement at all." ), 0 ); } else if ( le.MousePressRight( windowHotKeyRoi ) ) { fheroes2::showStandardTextMessage( _( "Hot Keys" ), _( "Check and configure all the hot keys present in the game." ), 0 ); } else if ( le.MousePressRight( windowInterfaceRoi ) ) { fheroes2::showStandardTextMessage( _( "Interface Settings" ), _( "Change the interface settings of the game." ), 0 ); } else if ( le.MousePressRight( windowTextSupportModeRoi ) ) { fheroes2::showStandardTextMessage( _( "Text Support" ), _( "Toggle text support mode to output extra information about windows and events in the game." ), 0 ); } else if ( le.MousePressRight( windowBattlesRoi ) ) { fheroes2::showStandardTextMessage( _( "Battles" ), _( "Toggle instant battle mode." ), 0 ); } else if ( le.MousePressRight( okayButton.area() ) ) { fheroes2::showStandardTextMessage( _( "Okay" ), _( "Exit this menu." ), 0 ); } // Text support mode can be toggled using a global hotkey, we need to properly reflect this change in the UI if ( isTextSupportModeEnabled != conf.isTextSupportModeEnabled() ) { isTextSupportModeEnabled = conf.isTextSupportModeEnabled(); emptyDialogRestorer.restore(); drawOptions(); display.render( emptyDialogRestorer.rect() ); } } return DialogAction::Close; } } namespace fheroes2 { void showSystemOptionsDialog() { // We should make file writing only once. bool saveConfiguration = false; Settings & conf = Settings::Get(); auto redrawAdventureMap = []() { Interface::AdventureMap & adventureMap = Interface::AdventureMap::Get(); adventureMap.reset(); // Since radar interface has a restorer we must redraw it first to avoid the restorer do some nasty work. adventureMap.redraw( Interface::REDRAW_RADAR ); adventureMap.redraw( Interface::REDRAW_ALL & ( ~Interface::REDRAW_RADAR ) ); }; DialogAction action = DialogAction::Configuration; while ( action != DialogAction::Close ) { switch ( action ) { case DialogAction::Configuration: action = openSystemOptionsDialog( saveConfiguration ); break; case DialogAction::Language: { const std::vector supportedLanguages = getSupportedLanguages(); if ( supportedLanguages.size() > 1 ) { selectLanguage( supportedLanguages, getLanguageFromAbbreviation( conf.getGameLanguage() ) ); } else { assert( supportedLanguages.front() == SupportedLanguage::English ); conf.setGameLanguage( getLanguageAbbreviation( SupportedLanguage::English ) ); fheroes2::showStandardTextMessage( "Attention", "Your version of Heroes of Might and Magic II does not support any other languages than English.", Dialog::OK ); } redrawAdventureMap(); saveConfiguration = true; action = DialogAction::Configuration; break; } case DialogAction::Graphics: fheroes2::openGraphicsSettingsDialog( redrawAdventureMap ); action = DialogAction::Configuration; break; case DialogAction::AudioSettings: Dialog::openAudioSettingsDialog( true ); action = DialogAction::Configuration; break; case DialogAction::HotKeys: fheroes2::openHotkeysDialog(); action = DialogAction::Configuration; break; case DialogAction::InterfaceSettings: fheroes2::openInterfaceSettingsDialog( redrawAdventureMap ); action = DialogAction::Configuration; break; default: break; } } if ( saveConfiguration ) { conf.Save( Settings::configFileName ); } } } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_system_options.h000066400000000000000000000030021456075706000245660ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once namespace fheroes2 { void showSystemOptionsDialog(); } fheroes2-1.0.12+dfsg/src/fheroes2/dialog/dialog_thievesguild.cpp000066400000000000000000000460601456075706000245310ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include "agg_image.h" #include "castle.h" #include "color.h" #include "cursor.h" #include "dialog.h" #include "game.h" #include "game_hotkeys.h" #include "heroes.h" #include "heroes_base.h" #include "icn.h" #include "image.h" #include "kingdom.h" #include "localevent.h" #include "math_base.h" #include "monster.h" #include "mp2.h" #include "players.h" #include "resource.h" #include "screen.h" #include "settings.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_text.h" #include "ui_tool.h" #include "ui_window.h" #include "world.h" struct ValueColors : std::pair { ValueColors( int value, int color ) : std::pair( value, color ) {} static bool SortValueGreat( const ValueColors & v1, const ValueColors & v2 ) { return v1.first > v2.first; } }; void UpdateValuesColors( std::vector & v, int value, int color ) { const auto it = std::find_if( v.begin(), v.end(), [value]( const ValueColors & vc ) { return vc.first == value; } ); if ( it == v.end() ) { v.emplace_back( value, color ); } else { ( *it ).second |= color; } } void getInfo( std::vector & v, const Colors & colors, const std::function & getValue ) { // 'getValue' should contain a callable function. assert( getValue ); v.clear(); for ( const int color : colors ) { UpdateValuesColors( v, getValue( color ), color ); } std::sort( v.begin(), v.end(), ValueColors::SortValueGreat ); } int getWoodOreValue( const int color ) { const Funds & funds = world.GetKingdom( color ).GetFunds(); return funds.Get( Resource::WOOD ) + funds.Get( Resource::ORE ); } int getGemsCrSlfMerValue( const int color ) { const Funds & funds = world.GetKingdom( color ).GetFunds(); return funds.Get( Resource::GEMS ) + funds.Get( Resource::CRYSTAL ) + funds.Get( Resource::SULFUR ) + funds.Get( Resource::MERCURY ); } void drawFlags( const std::vector & v, const fheroes2::Point & pos, const int32_t step, const size_t count, fheroes2::Image & output ) { const size_t flagGroups = std::min( count, v.size() ); if ( flagGroups == 0 ) { return; } const int32_t sptireWidth = fheroes2::AGG::GetICN( ICN::TOWNWIND, 22 ).width(); const int32_t offsetY = pos.y - 4; for ( size_t i = 0; i < flagGroups; ++i ) { const Colors colors( v[i].second ); int32_t offsetX = pos.x + static_cast( i ) * step - ( static_cast( colors.size() ) * sptireWidth ) / 2 + 3; for ( const int color : colors ) { const fheroes2::Sprite & flag = fheroes2::AGG::GetICN( ICN::TOWNWIND, 22 + Color::GetIndex( color ) ); fheroes2::Blit( flag, output, offsetX, offsetY ); offsetX += sptireWidth; } } } void drawHeroStats( const Heroes * hero, const int32_t offsetX, int32_t offsetY, fheroes2::Image & output ) { fheroes2::Text text( _( "Att." ), fheroes2::FontType::smallWhite() ); text.draw( offsetX, offsetY, output ); text.set( std::to_string( hero->GetAttack() ), fheroes2::FontType::smallWhite() ); text.draw( offsetX + 50 - text.width(), offsetY, output ); offsetY += 11; text.set( _( "Def." ), fheroes2::FontType::smallWhite() ); text.draw( offsetX, offsetY, output ); text.set( std::to_string( hero->GetDefense() ), fheroes2::FontType::smallWhite() ); text.draw( offsetX + 50 - text.width(), offsetY, output ); offsetY += 11; text.set( _( "Power" ), fheroes2::FontType::smallWhite() ); text.draw( offsetX, offsetY, output ); text.set( std::to_string( hero->GetPower() ), fheroes2::FontType::smallWhite() ); text.draw( offsetX + 50 - text.width(), offsetY, output ); offsetY += 11; text.set( _( "Knowl" ), fheroes2::FontType::smallWhite() ); text.draw( offsetX, offsetY, output ); text.set( std::to_string( hero->GetKnowledge() ), fheroes2::FontType::smallWhite() ); text.draw( offsetX + 50 - text.width(), offsetY, output ); } void drawHeroIcons( const Colors & colors, const bool drawStats, const fheroes2::Point & pos, const int32_t step, const int frameIcnID, fheroes2::Image & output ) { int32_t offsetX = pos.x + 1; for ( const int color : colors ) { const Heroes * hero = world.GetKingdom( color ).GetBestHero(); if ( hero == nullptr ) { offsetX += step; continue; } const fheroes2::Sprite & window = fheroes2::AGG::GetICN( frameIcnID, 22 ); fheroes2::Blit( window, output, offsetX - window.width() / 2, pos.y - 4 ); const fheroes2::Sprite & icon = hero->GetPortrait( PORT_SMALL ); fheroes2::Copy( icon, 0, 0, output, offsetX - icon.width() / 2, pos.y, icon.width(), icon.height() ); if ( drawStats ) { drawHeroStats( hero, offsetX - 26, pos.y + 34, output ); } offsetX += step; } } void drawPersonality( const Colors & colors, const fheroes2::Point & pos, const int32_t step, fheroes2::Image & output ) { int32_t offsetX = pos.x; for ( const int color : colors ) { const Player * player = Players::Get( color ); const fheroes2::Text text( player->isControlHuman() ? _( "Human" ) : player->GetPersonalityString(), fheroes2::FontType::smallWhite() ); text.draw( offsetX - text.width() / 2, pos.y, output ); offsetX += step; } } void drawBestMonsterIcons( const Colors & colors, const fheroes2::Point & pos, const int32_t step, fheroes2::Image & output ) { int32_t offsetX = pos.x; for ( const int color : colors ) { const Monster monster = world.GetKingdom( color ).GetStrongestMonster(); if ( monster.isValid() ) { const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::MONS32, monster.GetSpriteIndex() ); fheroes2::Blit( sprite, output, offsetX - sprite.width() / 2, pos.y - sprite.height() / 2 ); } offsetX += step; } } const char * getPlayerOrderString( const size_t player ) { switch ( player ) { case 0: return _( "1st" ); case 1: return _( "2nd" ); case 2: return _( "3rd" ); case 3: return _( "4th" ); case 4: return _( "5th" ); case 5: return _( "6th" ); default: // The engine supports up to 6 players. Check your logic! assert( 0 ); } return {}; } void Dialog::ThievesGuild( const bool oracle ) { // Set the cursor image.This dialog does not require a cursor restorer. It is called from other dialogs that have the same cursor // or from the Game Area that will set the appropriate cursor after this dialog is closed. Cursor::Get().SetThemes( Cursor::POINTER ); fheroes2::Rect dialogRoi; fheroes2::Rect dialogWithShadowRoi; std::unique_ptr background; std::unique_ptr restorer; fheroes2::Display & display = fheroes2::Display::instance(); if ( oracle ) { // We open a new dialog window for the Oracle. background = std::make_unique( fheroes2::Display::DEFAULT_WIDTH, fheroes2::Display::DEFAULT_HEIGHT, false ); dialogRoi = background->activeArea(); dialogWithShadowRoi = background->totalArea(); } else { // The Thieves Guild dialog is opened from the castle dialog. We do not need to make a new dialog window. dialogRoi = { ( display.width() - fheroes2::Display::DEFAULT_WIDTH ) / 2, ( display.height() - fheroes2::Display::DEFAULT_HEIGHT ) / 2, fheroes2::Display::DEFAULT_WIDTH, fheroes2::Display::DEFAULT_HEIGHT }; restorer = std::make_unique( display, dialogRoi.x, dialogRoi.y, dialogRoi.width, dialogRoi.height ); } // Fade-out game screen only for 640x480 resolution and if 'renderBackgroundDialog' is false (we are replacing image in already opened dialog). const bool isDefaultScreenSize = display.isDefaultSize(); if ( isDefaultScreenSize || !oracle ) { fheroes2::fadeOutDisplay( dialogRoi, !isDefaultScreenSize ); } const bool isEvilInterfaceTown = Settings::Get().isEvilInterfaceEnabled(); const fheroes2::Sprite & backgroundSprite = fheroes2::AGG::GetICN( isEvilInterfaceTown ? ICN::STONEBAK_EVIL : ICN::STONEBAK, 0 ); fheroes2::Copy( backgroundSprite, 0, 0, display, dialogRoi.x, dialogRoi.y, backgroundSprite.width(), backgroundSprite.height() ); const uint32_t thievesGuildCount = oracle ? 0xFF : world.GetKingdom( Settings::Get().CurrentColor() ).GetCountBuilding( BUILD_THIEVESGUILD ); std::vector valuesForPlayerColors; const Colors colors( Game::GetActualKingdomColors() ); const size_t playersCount = colors.size(); valuesForPlayerColors.reserve( playersCount ); const int32_t textOffsetX = dialogRoi.x + 207; const int32_t startOffsetX = dialogRoi.x + 264; const int32_t stepX = 68; fheroes2::Text text; fheroes2::Point offset( startOffsetX, dialogRoi.y + 3 ); for ( size_t player = 0; player < playersCount; ++player ) { text.set( getPlayerOrderString( player ), fheroes2::FontType::normalWhite() ); text.draw( offset.x - text.width() / 2, offset.y, display ); offset.x += stepX; } // Status bar. const int32_t exitWidth = fheroes2::AGG::GetICN( ICN::BUTTON_GUILDWELL_EXIT, 0 ).width(); const int32_t bottomBarOffsetY = 461; const fheroes2::Sprite & bottomBar = fheroes2::AGG::GetICN( ICN::SMALLBAR, 0 ); const int32_t barHeight = bottomBar.height(); offset.y = dialogRoi.y + bottomBarOffsetY; // ICN::SMALLBAR image's first column contains all black pixels. This should not be drawn. fheroes2::Copy( bottomBar, 1, 0, display, dialogRoi.x, offset.y, fheroes2::Display::DEFAULT_WIDTH / 2, barHeight ); fheroes2::Copy( bottomBar, bottomBar.width() - fheroes2::Display::DEFAULT_WIDTH / 2 + exitWidth - 1, 0, display, dialogRoi.x + fheroes2::Display::DEFAULT_WIDTH / 2, offset.y, fheroes2::Display::DEFAULT_WIDTH / 2 - exitWidth + 1, barHeight ); // Exit button. offset.x = dialogRoi.x + fheroes2::Display::DEFAULT_WIDTH - exitWidth; fheroes2::Button buttonExit( offset.x, offset.y, ICN::BUTTON_GUILDWELL_EXIT, 0, 1 ); // Text on status bar. text.set( oracle ? _( "Oracle: Player Rankings" ) : _( "Thieves' Guild: Player Rankings" ), fheroes2::FontType::normalWhite() ); offset.x = dialogRoi.x + ( fheroes2::Display::DEFAULT_WIDTH - exitWidth - text.width() ) / 2; text.draw( offset.x, dialogRoi.y + 464, display ); text.set( _( "Number of Towns:" ), fheroes2::FontType::normalWhite() ); offset.y = dialogRoi.y + 31; text.draw( textOffsetX - text.width(), offset.y, display ); offset.x = startOffsetX; getInfo( valuesForPlayerColors, colors, []( const int color ) { return static_cast( world.GetKingdom( color ).GetCountTown() ); } ); drawFlags( valuesForPlayerColors, offset, stepX, playersCount, display ); text.set( _( "Number of Castles:" ), fheroes2::FontType::normalWhite() ); offset.y += 24; text.draw( textOffsetX - text.width(), offset.y, display ); getInfo( valuesForPlayerColors, colors, []( const int color ) { return static_cast( world.GetKingdom( color ).GetCountCastle() ); } ); drawFlags( valuesForPlayerColors, offset, stepX, playersCount, display ); text.set( _( "Number of Heroes:" ), fheroes2::FontType::normalWhite() ); offset.y += 24; text.draw( textOffsetX - text.width(), offset.y, display ); getInfo( valuesForPlayerColors, colors, []( const int color ) { return static_cast( world.GetKingdom( color ).GetHeroes().size() ); } ); drawFlags( valuesForPlayerColors, offset, stepX, playersCount, display ); text.set( _( "Gold in Treasury:" ), fheroes2::FontType::normalWhite() ); offset.y += 24; text.draw( textOffsetX - text.width(), offset.y, display ); if ( thievesGuildCount > 1 ) { getInfo( valuesForPlayerColors, colors, []( const int color ) { return world.GetKingdom( color ).GetFunds().Get( Resource::GOLD ); } ); drawFlags( valuesForPlayerColors, offset, stepX, playersCount, display ); } text.set( _( "Wood & Ore:" ), fheroes2::FontType::normalWhite() ); offset.y += 24; text.draw( textOffsetX - text.width(), offset.y, display ); if ( thievesGuildCount > 1 ) { getInfo( valuesForPlayerColors, colors, getWoodOreValue ); drawFlags( valuesForPlayerColors, offset, stepX, playersCount, display ); } text.set( _( "Gems, Cr, Slf & Mer:" ), fheroes2::FontType::normalWhite() ); offset.y += 24; text.draw( textOffsetX - text.width(), offset.y, display ); if ( thievesGuildCount > 2 ) { getInfo( valuesForPlayerColors, colors, getGemsCrSlfMerValue ); drawFlags( valuesForPlayerColors, offset, stepX, playersCount, display ); } text.set( _( "Obelisks Found:" ), fheroes2::FontType::normalWhite() ); offset.y += 24; text.draw( textOffsetX - text.width(), offset.y, display ); if ( thievesGuildCount > 2 ) { getInfo( valuesForPlayerColors, colors, []( const int color ) { return static_cast( world.GetKingdom( color ).CountVisitedObjects( MP2::OBJ_OBELISK ) ); } ); drawFlags( valuesForPlayerColors, offset, stepX, playersCount, display ); } text.set( _( "Artifacts:" ), fheroes2::FontType::normalWhite() ); offset.y += 24; text.draw( textOffsetX - text.width(), offset.y, display ); if ( thievesGuildCount > 3 ) { getInfo( valuesForPlayerColors, colors, []( const int color ) { return static_cast( world.GetKingdom( color ).GetCountArtifacts() ); } ); drawFlags( valuesForPlayerColors, offset, stepX, playersCount, display ); } text.set( _( "Total Army Strength:" ), fheroes2::FontType::normalWhite() ); offset.y += 24; text.draw( textOffsetX - text.width(), offset.y, display ); if ( thievesGuildCount > 3 ) { getInfo( valuesForPlayerColors, colors, []( const int color ) { return static_cast( world.GetKingdom( color ).GetArmiesStrength() ); } ); drawFlags( valuesForPlayerColors, offset, stepX, playersCount, display ); } text.set( _( "Income:" ), fheroes2::FontType::normalWhite() ); offset.y += 24; text.draw( textOffsetX - text.width(), offset.y, display ); if ( thievesGuildCount > 4 ) { getInfo( valuesForPlayerColors, colors, []( const int color ) { return world.GetKingdom( color ).GetIncome().gold; } ); drawFlags( valuesForPlayerColors, offset, stepX, playersCount, display ); } // Render color's names for each player. offset.y += 24; bool shiftVertically = false; for ( const int color : colors ) { text.set( Color::String( color ), fheroes2::FontType::normalWhite() ); text.draw( offset.x - text.width() / 2, shiftVertically ? ( offset.y + 15 ) : offset.y, display ); offset.x += stepX; shiftVertically = !shiftVertically; } text.set( _( "Best Hero:" ), fheroes2::FontType::normalWhite() ); offset.y += 38; text.draw( textOffsetX - text.width(), offset.y, display ); offset.x = startOffsetX; offset.y -= 4; const int frameIcnID = isEvilInterfaceTown ? ICN::LOCATORE : ICN::LOCATORS; drawHeroIcons( colors, thievesGuildCount > 1, offset, stepX, frameIcnID, display ); text.set( _( "Best Hero Stats:" ), fheroes2::FontType::normalWhite() ); offset.y += 47; text.draw( textOffsetX - text.width(), offset.y, display ); text.set( _( "Personality:" ), fheroes2::FontType::normalWhite() ); offset.y += 42; text.draw( textOffsetX - text.width(), offset.y, display ); offset.y += 3; if ( thievesGuildCount > 2 ) { drawPersonality( colors, offset, stepX, display ); } text.set( _( "Best Monster:" ), fheroes2::FontType::normalWhite() ); offset.y += 32; text.draw( textOffsetX - text.width(), offset.y, display ); offset.y += text.height() / 2; if ( thievesGuildCount > 3 ) { drawBestMonsterIcons( colors, offset, stepX, display ); } buttonExit.draw(); // Fade-in thieves guild dialog. if ( oracle && !isDefaultScreenSize ) { // We need to expand the ROI for the next render to properly render window borders and shadow. display.updateNextRenderRoi( dialogWithShadowRoi ); } // Use half fade if game resolution is not 640x480. fheroes2::fadeInDisplay( dialogRoi, !isDefaultScreenSize ); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { le.MousePressLeft( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) { break; } if ( le.MousePressRight( buttonExit.area() ) ) { fheroes2::showStandardTextMessage( _( "Exit" ), _( "Exit this menu." ), Dialog::ZERO ); } } // Fade-out dialog. fheroes2::fadeOutDisplay( dialogRoi, !isDefaultScreenSize ); if ( oracle ) { // Fade-in game screen only for 640x480 resolution. if ( isDefaultScreenSize ) { Game::setDisplayFadeIn(); } else { display.updateNextRenderRoi( dialogWithShadowRoi ); } } else { restorer->restore(); fheroes2::fadeInDisplay( dialogRoi, !isDefaultScreenSize ); } } fheroes2-1.0.12+dfsg/src/fheroes2/editor/000077500000000000000000000000001456075706000200335ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/fheroes2/editor/editor.h000066400000000000000000000031641456075706000214760ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include "game.h" #include "game_mode.h" namespace Editor { fheroes2::GameMode menuMain(); fheroes2::GameMode menuNewMap(); fheroes2::GameMode menuLoadMap(); } fheroes2-1.0.12+dfsg/src/fheroes2/editor/editor_interface.cpp000066400000000000000000001507241456075706000240560ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "editor_interface.h" #include #include #include #include #include #include #include #include "agg_image.h" #include "artifact.h" #include "audio_manager.h" #include "cursor.h" #include "dialog.h" #include "dialog_selectitems.h" #include "editor_object_popup_window.h" #include "game.h" #include "game_delays.h" #include "game_hotkeys.h" #include "game_static.h" #include "gamedefs.h" #include "ground.h" #include "history_manager.h" #include "icn.h" #include "image.h" #include "interface_base.h" #include "interface_border.h" #include "interface_gamearea.h" #include "interface_radar.h" #include "localevent.h" #include "map_format_helper.h" #include "map_object_info.h" #include "maps.h" #include "maps_tiles.h" #include "maps_tiles_helper.h" #include "math_base.h" #include "mp2.h" #include "screen.h" #include "settings.h" #include "spell.h" #include "system.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_map_object.h" #include "ui_text.h" #include "ui_tool.h" #include "view_world.h" #include "world.h" #include "world_object_uid.h" class Castle; class Heroes; namespace { const uint32_t mapUpdateFlags = Interface::REDRAW_GAMEAREA | Interface::REDRAW_RADAR; fheroes2::Point getBrushAreaIndicies( const fheroes2::Rect & brushSize, const int32_t startIndex ) { if ( brushSize.width <= 0 || brushSize.height <= 0 ) { return { startIndex, startIndex }; } const int32_t worldWidth = world.w(); const int32_t worldHeight = world.h(); fheroes2::Point startPos{ ( startIndex % worldWidth ) + brushSize.x, ( startIndex / worldWidth ) + brushSize.y }; fheroes2::Point endPos{ startPos.x + brushSize.width - 1, startPos.y + brushSize.height - 1 }; startPos.x = std::max( startPos.x, 0 ); startPos.y = std::max( startPos.y, 0 ); endPos.x = std::min( endPos.x, worldWidth - 1 ); endPos.y = std::min( endPos.y, worldHeight - 1 ); return { startPos.x + startPos.y * worldWidth, endPos.x + endPos.y * worldWidth }; } const Maps::ObjectInfo & getObjectInfo( const Maps::ObjectGroup group, const int32_t objectType ) { const auto & objectInfo = Maps::getObjectsByGroup( group ); if ( objectType < 0 || objectType >= static_cast( objectInfo.size() ) ) { assert( 0 ); static const Maps::ObjectInfo emptyObjectInfo{ MP2::OBJ_NONE }; return emptyObjectInfo; } return objectInfo[objectType]; } bool isObjectPlacementAllowed( const Maps::ObjectInfo & info, const fheroes2::Point & mainTilePos ) { // Run through all tile offsets and check that all objects parts can be put on the map. for ( const auto & objectPart : info.groundLevelParts ) { if ( objectPart.layerType == Maps::SHADOW_LAYER ) { // Shadow layer parts are ignored. continue; } if ( !Maps::isValidAbsPoint( mainTilePos.x + objectPart.tileOffset.x, mainTilePos.y + objectPart.tileOffset.y ) ) { return false; } } for ( const auto & objectPart : info.topLevelParts ) { if ( !Maps::isValidAbsPoint( mainTilePos.x + objectPart.tileOffset.x, mainTilePos.y + objectPart.tileOffset.y ) ) { return false; } } return true; } bool isConditionValid( const std::vector & offsets, const fheroes2::Point & mainTilePos, const std::function & condition ) { if ( offsets.empty() ) { return true; } assert( condition ); for ( const auto & offset : offsets ) { const fheroes2::Point temp{ mainTilePos.x + offset.x, mainTilePos.y + offset.y }; if ( !Maps::isValidAbsPoint( temp.x, temp.y ) ) { return false; } if ( !condition( world.GetTiles( temp.x, temp.y ) ) ) { return false; } } return true; } bool checkConditionForOccupiedTiles( const Maps::ObjectInfo & info, const fheroes2::Point & mainTilePos, const std::function & condition ) { return isConditionValid( Maps::getGroundLevelOccupiedTileOffset( info ), mainTilePos, condition ); } bool checkConditionForUsedTiles( const Maps::ObjectInfo & info, const fheroes2::Point & mainTilePos, const std::function & condition ) { return isConditionValid( Maps::getGroundLevelUsedTileOffset( info ), mainTilePos, condition ); } } namespace Interface { void EditorInterface::reset() { const fheroes2::Display & display = fheroes2::Display::instance(); const int32_t xOffset = display.width() - BORDERWIDTH - RADARWIDTH; _radar.SetPos( xOffset, BORDERWIDTH ); _editorPanel.setPos( xOffset, _radar.GetArea().y + _radar.GetArea().height + ( ( display.height() > display.DEFAULT_HEIGHT + BORDERWIDTH ) ? BORDERWIDTH : 0 ) ); const fheroes2::Point prevCenter = _gameArea.getCurrentCenterInPixels(); const fheroes2::Rect prevRoi = _gameArea.GetROI(); _gameArea.SetAreaPosition( BORDERWIDTH, BORDERWIDTH, display.width() - RADARWIDTH - 3 * BORDERWIDTH, display.height() - 2 * BORDERWIDTH ); const fheroes2::Rect newRoi = _gameArea.GetROI(); _gameArea.SetCenterInPixels( prevCenter + fheroes2::Point( newRoi.x + newRoi.width / 2, newRoi.y + newRoi.height / 2 ) - fheroes2::Point( prevRoi.x + prevRoi.width / 2, prevRoi.y + prevRoi.height / 2 ) ); _historyManager.reset(); } void EditorInterface::redraw( const uint32_t force ) { fheroes2::Display & display = fheroes2::Display::instance(); const uint32_t combinedRedraw = _redraw | force; if ( combinedRedraw & REDRAW_GAMEAREA ) { // Render all except the fog. _gameArea.Redraw( display, LEVEL_OBJECTS | LEVEL_HEROES | LEVEL_ROUTES ); if ( _warningMessage.isValid() ) { const fheroes2::Rect & roi = _gameArea.GetROI(); fheroes2::Text text{ _warningMessage.message(), fheroes2::FontType::normalWhite() }; // Keep 4 pixels from each edge. text.fitToOneRow( roi.width - 8 ); text.draw( roi.x + 4, roi.y + roi.height - text.height() - 4, display ); } // TODO:: Render horizontal and vertical map tiles scale and highlight with yellow text cursor position. if ( _editorPanel.showAreaSelectRect() && ( _tileUnderCursor > -1 ) ) { const fheroes2::Rect brushSize = _editorPanel.getBrushArea(); if ( brushSize.width > 0 && brushSize.height > 0 ) { const fheroes2::Point indices = getBrushAreaIndicies( brushSize, _tileUnderCursor ); _gameArea.renderTileAreaSelect( display, indices.x, indices.y ); } else if ( _editorPanel.isTerrainEdit() || _editorPanel.isEraseMode() ) { assert( brushSize == fheroes2::Rect() ); // Render area selection from the tile where the left mouse button was pressed till the tile under the cursor. _gameArea.renderTileAreaSelect( display, _selectedTile, _tileUnderCursor ); } } } if ( combinedRedraw & ( REDRAW_RADAR_CURSOR | REDRAW_RADAR ) ) { // Render the mini-map without fog. _radar.redrawForEditor( combinedRedraw & REDRAW_RADAR ); } if ( combinedRedraw & REDRAW_BORDER ) { // Game border for the View World are the same as for the Map Editor. GameBorderRedraw( true ); } if ( combinedRedraw & REDRAW_PANEL ) { _editorPanel._redraw(); } _redraw = 0; } Interface::EditorInterface & Interface::EditorInterface::Get() { static EditorInterface editorInterface; return editorInterface; } fheroes2::GameMode Interface::EditorInterface::startEdit( const bool isNewMap ) { reset(); if ( isNewMap ) { _mapFormat = {}; Maps::saveMapInEditor( _mapFormat ); } // Stop all sounds and music. AudioManager::ResetAudio(); const Settings & conf = Settings::Get(); _radar.Build(); _radar.SetHide( false ); fheroes2::GameMode res = fheroes2::GameMode::CANCEL; _gameArea.SetUpdateCursor(); setRedraw( REDRAW_GAMEAREA | REDRAW_RADAR | REDRAW_PANEL | REDRAW_STATUS | REDRAW_BORDER ); int32_t fastScrollRepeatCount = 0; const int32_t fastScrollStartThreshold = 2; bool isCursorOverGamearea = false; const std::vector delayTypes = { Game::MAPS_DELAY }; LocalEvent & le = LocalEvent::Get(); Cursor & cursor = Cursor::Get(); while ( res == fheroes2::GameMode::CANCEL ) { if ( !le.HandleEvents( Game::isDelayNeeded( delayTypes ), true ) ) { if ( EventExit() == fheroes2::GameMode::QUIT_GAME ) { res = fheroes2::GameMode::QUIT_GAME; break; } continue; } // Process hot-keys. if ( le.KeyPress() ) { // adventure map control if ( HotKeyPressEvent( Game::HotKeyEvent::MAIN_MENU_QUIT ) || HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { res = EventExit(); } else if ( HotKeyPressEvent( Game::HotKeyEvent::EDITOR_NEW_MAP_MENU ) ) { res = eventNewMap(); } else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_SAVE_GAME ) ) { const std::string dataPath = System::GetDataDirectory( "fheroes2" ); if ( dataPath.empty() ) { fheroes2::showStandardTextMessage( _( "Warning!" ), "Unable to locate data directory to save the map.", Dialog::OK ); continue; } const std::string mapDirectory = System::concatPath( dataPath, "maps" ); if ( !System::IsDirectory( mapDirectory ) && !System::MakeDirectory( mapDirectory ) ) { fheroes2::showStandardTextMessage( _( "Warning!" ), "Unable to create a directory to save the map.", Dialog::OK ); continue; } std::string fileName; if ( !Dialog::InputString( _( "Map filename" ), fileName, std::string(), 128 ) ) { continue; } _mapFormat.name = fileName; _mapFormat.description = "Put a real description here."; if ( !Maps::Map_Format::saveMap( System::concatPath( mapDirectory, fileName + ".fh2m" ), _mapFormat ) ) { fheroes2::showStandardTextMessage( _( "Warning!" ), "Failed to save the map.", Dialog::OK ); } } else if ( HotKeyPressEvent( Game::HotKeyEvent::MAIN_MENU_LOAD_GAME ) ) { res = eventLoadMap(); } else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_FILE_OPTIONS ) ) { res = eventFileDialog(); } else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_SCENARIO_INFORMATION ) ) { // TODO: Make the scenario info editor. Dialog::GameInfo(); } else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_VIEW_WORLD ) ) { eventViewWorld(); } // map scrolling control else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_SCROLL_LEFT ) ) { _gameArea.SetScroll( SCROLL_LEFT ); } else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_SCROLL_RIGHT ) ) { _gameArea.SetScroll( SCROLL_RIGHT ); } else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_SCROLL_UP ) ) { _gameArea.SetScroll( SCROLL_TOP ); } else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_SCROLL_DOWN ) ) { _gameArea.SetScroll( SCROLL_BOTTOM ); } // default action else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_DEFAULT_ACTION ) ) { fheroes2::showStandardTextMessage( _( "Warning!" ), "The Map Editor is still in development. No actions are available yet.", Dialog::OK ); } // open focus else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_OPEN_FOCUS ) ) { fheroes2::showStandardTextMessage( _( "Warning!" ), "The Map Editor is still in development. Open focused object dialog is not implemented yet.", Dialog::OK ); } else if ( HotKeyPressEvent( Game::HotKeyEvent::EDITOR_UNDO_LAST_ACTION ) ) { undoAction(); } else if ( HotKeyPressEvent( Game::HotKeyEvent::EDITOR_REDO_LAST_ACTION ) ) { redoAction(); } } if ( res != fheroes2::GameMode::CANCEL ) { break; } if ( fheroes2::cursor().isFocusActive() && !_gameArea.isDragScroll() && !_radar.isDragRadar() && ( conf.ScrollSpeed() != SCROLL_SPEED_NONE ) ) { int scrollPosition = SCROLL_NONE; if ( isScrollLeft( le.GetMouseCursor() ) ) scrollPosition |= SCROLL_LEFT; else if ( isScrollRight( le.GetMouseCursor() ) ) scrollPosition |= SCROLL_RIGHT; if ( isScrollTop( le.GetMouseCursor() ) ) scrollPosition |= SCROLL_TOP; else if ( isScrollBottom( le.GetMouseCursor() ) ) scrollPosition |= SCROLL_BOTTOM; if ( scrollPosition != SCROLL_NONE ) { if ( Game::validateAnimationDelay( Game::SCROLL_START_DELAY ) && ( fastScrollRepeatCount < fastScrollStartThreshold ) ) { ++fastScrollRepeatCount; } if ( fastScrollRepeatCount >= fastScrollStartThreshold ) { _gameArea.SetScroll( scrollPosition ); } } else { fastScrollRepeatCount = 0; } } else { fastScrollRepeatCount = 0; } isCursorOverGamearea = false; // cursor is over the radar if ( le.MouseCursor( _radar.GetRect() ) ) { cursor.SetThemes( Cursor::POINTER ); // TODO: Add checks for object placing/moving, and other Editor functions that uses mouse dragging. if ( !_gameArea.isDragScroll() && ( _editorPanel.getBrushArea().width > 0 || _selectedTile == -1 ) ) { _radar.QueueEventProcessing(); } } // cursor is over the game area else if ( le.MouseCursor( _gameArea.GetROI() ) && !_gameArea.NeedScroll() ) { isCursorOverGamearea = true; } // cursor is over the buttons area else if ( le.MouseCursor( _editorPanel.getRect() ) ) { cursor.SetThemes( Cursor::POINTER ); if ( !_gameArea.NeedScroll() ) { res = _editorPanel.queueEventProcessing(); } } // cursor is somewhere else else if ( !_gameArea.NeedScroll() ) { cursor.SetThemes( Cursor::POINTER ); _gameArea.ResetCursorPosition(); } // gamearea if ( !_gameArea.NeedScroll() ) { if ( !_radar.isDragRadar() ) { _gameArea.QueueEventProcessing( isCursorOverGamearea ); } else if ( !le.MousePressLeft() ) { _radar.QueueEventProcessing(); } } if ( isCursorOverGamearea ) { // Get tile index under the cursor. const int32_t tileIndex = _gameArea.GetValidTileIdFromPoint( le.GetMouseCursor() ); const fheroes2::Rect brushSize = _editorPanel.getBrushArea(); if ( _tileUnderCursor != tileIndex ) { _tileUnderCursor = tileIndex; // Force redraw if cursor position was changed as area rectangle is also changed. if ( _editorPanel.showAreaSelectRect() && ( brushSize.width > 0 || _selectedTile != -1 ) ) { _redraw |= REDRAW_GAMEAREA; } } if ( _selectedTile == -1 && tileIndex != -1 && brushSize.width == 0 && le.MousePressLeft() ) { _selectedTile = tileIndex; _redraw |= REDRAW_GAMEAREA; } } else if ( _tileUnderCursor != -1 ) { _tileUnderCursor = -1; _redraw |= REDRAW_GAMEAREA; } if ( _selectedTile > -1 && le.MouseReleaseLeft() ) { if ( isCursorOverGamearea && _editorPanel.getBrushArea().width == 0 ) { if ( _editorPanel.isTerrainEdit() ) { // Fill the selected area in terrain edit mode. fheroes2::ActionCreator action( _historyManager, _mapFormat ); const int groundId = _editorPanel.selectedGroundType(); Maps::setTerrainOnTiles( _selectedTile, _tileUnderCursor, groundId ); action.commit(); } else if ( _editorPanel.isEraseMode() ) { // Erase objects in the selected area. fheroes2::ActionCreator action( _historyManager, _mapFormat ); Maps::eraseObjectsOnTiles( _selectedTile, _tileUnderCursor, _editorPanel.getEraseTypes() ); action.commit(); } } // Reset the area start tile. _selectedTile = -1; _redraw |= mapUpdateFlags; } // fast scroll if ( ( Game::validateAnimationDelay( Game::SCROLL_DELAY ) && _gameArea.NeedScroll() ) || _gameArea.needDragScrollRedraw() ) { if ( ( isScrollLeft( le.GetMouseCursor() ) || isScrollRight( le.GetMouseCursor() ) || isScrollTop( le.GetMouseCursor() ) || isScrollBottom( le.GetMouseCursor() ) ) && !_gameArea.isDragScroll() ) { cursor.SetThemes( _gameArea.GetScrollCursor() ); } _gameArea.Scroll(); _redraw |= REDRAW_GAMEAREA | REDRAW_RADAR_CURSOR; } if ( res == fheroes2::GameMode::CANCEL ) { // map objects animation if ( Game::validateAnimationDelay( Game::MAPS_DELAY ) ) { Game::updateAdventureMapAnimationIndex(); _redraw |= REDRAW_GAMEAREA; } if ( needRedraw() ) { redraw( 0 ); // If this assertion blows up it means that we are holding a RedrawLocker lock for rendering which should not happen. assert( getRedrawMask() == 0 ); validateFadeInAndRender(); } } } Game::setDisplayFadeIn(); fheroes2::fadeOutDisplay(); return res; } fheroes2::GameMode EditorInterface::eventLoadMap() { return Dialog::YES == fheroes2::showStandardTextMessage( "", _( "Are you sure you want to load a new map? (Any unsaved changes to the current map will be lost.)" ), Dialog::YES | Dialog::NO ) ? fheroes2::GameMode::EDITOR_LOAD_MAP : fheroes2::GameMode::CANCEL; } fheroes2::GameMode EditorInterface::eventNewMap() { return Dialog::YES == fheroes2::showStandardTextMessage( "", _( "Are you sure you want to create a new map? (Any unsaved changes to the current map will be lost.)" ), Dialog::YES | Dialog::NO ) ? fheroes2::GameMode::EDITOR_NEW_MAP : fheroes2::GameMode::CANCEL; } fheroes2::GameMode EditorInterface::eventFileDialog() { const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); const int cpanbkg = isEvilInterface ? ICN::CPANBKGE : ICN::CPANBKG; const fheroes2::Sprite & background = fheroes2::AGG::GetICN( cpanbkg, 0 ); const CursorRestorer cursorRestorer( true, Cursor::POINTER ); fheroes2::Display & display = fheroes2::Display::instance(); // Since the original image contains shadow it is important to remove it from calculation of window's position. const fheroes2::Point rb( ( display.width() - background.width() - BORDERWIDTH ) / 2, ( display.height() - background.height() + BORDERWIDTH ) / 2 ); fheroes2::ImageRestorer back( display, rb.x, rb.y, background.width(), background.height() ); fheroes2::Blit( background, display, rb.x, rb.y ); // TODO: Make Evil interface for New/Load/Save Map buttons. fheroes2::Button buttonNew( rb.x + 62, rb.y + 31, ICN::ECPANEL, 0, 1 ); fheroes2::Button buttonLoad( rb.x + 195, rb.y + 31, ICN::ECPANEL, 2, 3 ); fheroes2::Button buttonSave( rb.x + 62, rb.y + 107, ICN::ECPANEL, 4, 5 ); fheroes2::Button buttonQuit( rb.x + 195, rb.y + 107, isEvilInterface ? ICN::BUTTON_QUIT_EVIL : ICN::BUTTON_QUIT_GOOD, 0, 1 ); fheroes2::Button buttonCancel( rb.x + 128, rb.y + 184, isEvilInterface ? ICN::BUTTON_SMALL_CANCEL_EVIL : ICN::BUTTON_SMALL_CANCEL_GOOD, 0, 1 ); buttonNew.draw(); buttonLoad.draw(); buttonSave.draw(); buttonQuit.draw(); buttonCancel.draw(); display.render( back.rect() ); fheroes2::GameMode result = fheroes2::GameMode::CANCEL; LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { le.MousePressLeft( buttonNew.area() ) ? buttonNew.drawOnPress() : buttonNew.drawOnRelease(); le.MousePressLeft( buttonLoad.area() ) ? buttonLoad.drawOnPress() : buttonLoad.drawOnRelease(); le.MousePressLeft( buttonSave.area() ) ? buttonSave.drawOnPress() : buttonSave.drawOnRelease(); le.MousePressLeft( buttonQuit.area() ) ? buttonQuit.drawOnPress() : buttonQuit.drawOnRelease(); le.MousePressLeft( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); if ( le.MouseClickLeft( buttonNew.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::EDITOR_NEW_MAP_MENU ) ) { if ( eventNewMap() == fheroes2::GameMode::EDITOR_NEW_MAP ) { result = fheroes2::GameMode::EDITOR_NEW_MAP; break; } } else if ( le.MouseClickLeft( buttonLoad.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::MAIN_MENU_LOAD_GAME ) ) { if ( eventLoadMap() == fheroes2::GameMode::EDITOR_LOAD_MAP ) { result = fheroes2::GameMode::EDITOR_LOAD_MAP; break; } } else if ( le.MouseClickLeft( buttonSave.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::WORLD_SAVE_GAME ) ) { back.restore(); fheroes2::showStandardTextMessage( _( "Warning!" ), "The Map Editor is still in development. Save function is not implemented yet.", Dialog::OK ); break; } else if ( le.MouseClickLeft( buttonQuit.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::MAIN_MENU_QUIT ) ) { if ( EventExit() == fheroes2::GameMode::QUIT_GAME ) { result = fheroes2::GameMode::QUIT_GAME; break; } } else if ( le.MouseClickLeft( buttonCancel.area() ) || Game::HotKeyCloseWindow() ) { break; } else if ( le.MousePressRight( buttonNew.area() ) ) { fheroes2::showStandardTextMessage( _( "New Map" ), _( "Create a new map, either from scratch or using the random map generator." ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonLoad.area() ) ) { fheroes2::showStandardTextMessage( _( "Load Map" ), _( "Load an existing map." ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonSave.area() ) ) { fheroes2::showStandardTextMessage( _( "Save Map" ), _( "Save the current map." ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonQuit.area() ) ) { fheroes2::showStandardTextMessage( _( "Quit" ), _( "Quit out of the map editor." ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonCancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Exit this menu without doing anything." ), Dialog::ZERO ); } } // restore background back.restore(); display.render( back.rect() ); return result; } void EditorInterface::eventViewWorld() { // TODO: Make proper borders restoration for low height resolutions, like for hide interface mode. ViewWorld::ViewWorldWindow( 0, ViewWorldMode::ViewAll, *this ); } void EditorInterface::mouseCursorAreaClickLeft( const int32_t tileIndex ) { Maps::Tiles & tile = world.GetTiles( tileIndex ); Heroes * otherHero = tile.getHero(); Castle * otherCastle = world.getCastle( tile.GetCenter() ); if ( otherHero ) { // TODO: Make hero edit dialog: e.g. with functions like in Battle only dialog, but only for one hero. Game::OpenHeroesDialog( *otherHero, true, true ); } else if ( otherCastle ) { // TODO: Make Castle edit dialog: e.g. like original build dialog. Game::OpenCastleDialog( *otherCastle ); } else if ( _editorPanel.isTerrainEdit() ) { const fheroes2::Rect brushSize = _editorPanel.getBrushArea(); assert( brushSize.width == brushSize.height ); const int groundId = _editorPanel.selectedGroundType(); fheroes2::ActionCreator action( _historyManager, _mapFormat ); if ( brushSize.width > 0 ) { const fheroes2::Point indices = getBrushAreaIndicies( brushSize, tileIndex ); Maps::setTerrainOnTiles( indices.x, indices.y, groundId ); } else { assert( brushSize.width == 0 ); // This is a case when area was not selected but a single tile was clicked. Maps::setTerrainOnTiles( tileIndex, tileIndex, groundId ); _selectedTile = -1; } _redraw |= mapUpdateFlags; action.commit(); } else if ( _editorPanel.isRoadDraw() ) { fheroes2::ActionCreator action( _historyManager, _mapFormat ); if ( Maps::updateRoadOnTile( tile, true ) ) { _redraw |= mapUpdateFlags; action.commit(); } } else if ( _editorPanel.isStreamDraw() ) { fheroes2::ActionCreator action( _historyManager, _mapFormat ); if ( Maps::updateStreamOnTile( tile, true ) ) { _redraw |= mapUpdateFlags; action.commit(); } } else if ( _editorPanel.isEraseMode() ) { const fheroes2::Rect brushSize = _editorPanel.getBrushArea(); assert( brushSize.width == brushSize.height ); fheroes2::ActionCreator action( _historyManager, _mapFormat ); if ( brushSize.width > 1 ) { const fheroes2::Point indices = getBrushAreaIndicies( brushSize, tileIndex ); if ( Maps::eraseObjectsOnTiles( indices.x, indices.y, _editorPanel.getEraseTypes() ) ) { _redraw |= mapUpdateFlags; action.commit(); } } else { if ( Maps::eraseOjects( tile, _editorPanel.getEraseTypes() ) ) { _redraw |= mapUpdateFlags; action.commit(); } if ( brushSize.width == 0 ) { // This is a case when area was not selected but a single tile was clicked. _selectedTile = -1; } } } else if ( _editorPanel.isObjectMode() ) { handleObjectMouseLeftClick( tile ); } } void EditorInterface::handleObjectMouseLeftClick( Maps::Tiles & tile ) { assert( _editorPanel.isObjectMode() ); if ( _editorPanel.getSelectedObjectType() < 0 ) { return; } const fheroes2::Point tilePos = tile.GetCenter(); const Maps::ObjectGroup groupType = _editorPanel.getSelectedObjectGroup(); if ( groupType == Maps::ObjectGroup::MONSTERS ) { const auto & objectInfo = getObjectInfo( groupType, _editorPanel.getSelectedObjectType() ); if ( !isObjectPlacementAllowed( objectInfo, tilePos ) ) { _warningMessage.reset( _( "Objects cannot be placed outside the map." ) ); return; } if ( !checkConditionForUsedTiles( objectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return !tileToCheck.isWater(); } ) ) { _warningMessage.reset( _( "Monsters cannot be placed on water." ) ); return; } if ( !checkConditionForOccupiedTiles( objectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return Maps::isClearGround( tileToCheck ); } ) ) { _warningMessage.reset( _( "Choose a tile which does not contain any objects." ) ); return; } setObjectOnTileAsAction( tile, groupType, _editorPanel.getSelectedObjectType() ); } else if ( groupType == Maps::ObjectGroup::ADVENTURE_TREASURES ) { const auto & objectInfo = getObjectInfo( groupType, _editorPanel.getSelectedObjectType() ); if ( !isObjectPlacementAllowed( objectInfo, tilePos ) ) { _warningMessage.reset( _( "Objects cannot be placed outside the map." ) ); return; } if ( !checkConditionForUsedTiles( objectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return !tileToCheck.isWater(); } ) ) { _warningMessage.reset( _( "Treasures cannot be placed on water." ) ); return; } if ( !checkConditionForOccupiedTiles( objectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return Maps::isClearGround( tileToCheck ); } ) ) { _warningMessage.reset( _( "Choose a tile which does not contain any objects." ) ); return; } setObjectOnTileAsAction( tile, groupType, _editorPanel.getSelectedObjectType() ); } else if ( groupType == Maps::ObjectGroup::KINGDOM_HEROES ) { const auto & objectInfo = getObjectInfo( groupType, _editorPanel.getSelectedObjectType() ); if ( !isObjectPlacementAllowed( objectInfo, tilePos ) ) { _warningMessage.reset( _( "Objects cannot be placed outside the map." ) ); return; } if ( !checkConditionForUsedTiles( objectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return !tileToCheck.isWater(); } ) ) { _warningMessage.reset( _( "Heroes cannot be placed on water." ) ); return; } if ( !checkConditionForOccupiedTiles( objectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return Maps::isClearGround( tileToCheck ); } ) ) { _warningMessage.reset( _( "Choose a tile which does not contain any objects." ) ); return; } // Heroes are limited to 8 per color so all attempts to set more than 8 heroes must be prevented. const auto & objects = Maps::getObjectsByGroup( groupType ); const uint32_t color = objectInfo.metadata[0]; size_t heroCount = 0; for ( const auto & mapTile : _mapFormat.tiles ) { for ( const auto & object : mapTile.objects ) { if ( object.group == groupType ) { assert( object.index < objects.size() ); if ( objects[object.index].metadata[0] == color ) { ++heroCount; } } } } if ( heroCount >= GameStatic::GetKingdomMaxHeroes() ) { std::string warning( _( "A maximum of %{count} heroes of the same color can be placed on the map." ) ); StringReplace( warning, "%{count}", GameStatic::GetKingdomMaxHeroes() ); _warningMessage.reset( std::move( warning ) ); return; } setObjectOnTileAsAction( tile, groupType, _editorPanel.getSelectedObjectType() ); } else if ( groupType == Maps::ObjectGroup::ADVENTURE_ARTIFACTS ) { const auto & objectInfo = getObjectInfo( groupType, _editorPanel.getSelectedObjectType() ); if ( !isObjectPlacementAllowed( objectInfo, tilePos ) ) { _warningMessage.reset( _( "Objects cannot be placed outside the map." ) ); return; } if ( !checkConditionForUsedTiles( objectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return !tileToCheck.isWater(); } ) ) { _warningMessage.reset( _( "Artifacts cannot be placed on water." ) ); return; } if ( !checkConditionForOccupiedTiles( objectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return Maps::isClearGround( tileToCheck ); } ) ) { _warningMessage.reset( _( "Choose a tile which does not contain any objects." ) ); return; } const int32_t artifactType = _editorPanel.getSelectedObjectType(); const auto & artifactInfo = Maps::getObjectsByGroup( Maps::ObjectGroup::ADVENTURE_ARTIFACTS ); assert( artifactType >= 0 && artifactType < static_cast( artifactInfo.size() ) ); // For each Spell Scroll artifact we select a spell. if ( artifactInfo[artifactType].objectType == MP2::OBJ_ARTIFACT && artifactInfo[artifactType].metadata[0] == Artifact::SPELL_SCROLL ) { const int spellId = Dialog::selectSpell( Spell::RANDOM, true ).GetID(); if ( spellId == Spell::NONE ) { // We do not place the Spell Scroll artifact if the spell for it was not selected. return; } setObjectOnTileAsAction( tile, groupType, _editorPanel.getSelectedObjectType() ); Maps::setSpellOnTile( tile, spellId ); } else { setObjectOnTileAsAction( tile, groupType, _editorPanel.getSelectedObjectType() ); } } else if ( groupType == Maps::ObjectGroup::LANDSCAPE_MOUNTAINS ) { const auto & objectInfo = getObjectInfo( groupType, _editorPanel.getSelectedObjectType() ); if ( !isObjectPlacementAllowed( objectInfo, tilePos ) ) { _warningMessage.reset( _( "Objects cannot be placed outside the map." ) ); return; } if ( !checkConditionForUsedTiles( objectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return !tileToCheck.isWater(); } ) ) { _warningMessage.reset( _( "Mountains cannot be placed on water." ) ); return; } if ( !checkConditionForOccupiedTiles( objectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return Maps::isClearGround( tileToCheck ); } ) ) { _warningMessage.reset( _( "Choose a tile which does not contain any objects." ) ); return; } setObjectOnTileAsAction( tile, groupType, _editorPanel.getSelectedObjectType() ); } else if ( groupType == Maps::ObjectGroup::LANDSCAPE_ROCKS ) { const auto & objectInfo = getObjectInfo( groupType, _editorPanel.getSelectedObjectType() ); if ( !isObjectPlacementAllowed( objectInfo, tilePos ) ) { _warningMessage.reset( _( "Objects cannot be placed outside the map." ) ); return; } if ( !checkConditionForUsedTiles( objectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return !tileToCheck.isWater(); } ) ) { _warningMessage.reset( _( "Rocks cannot be placed on water." ) ); return; } if ( !checkConditionForOccupiedTiles( objectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return Maps::isClearGround( tileToCheck ); } ) ) { _warningMessage.reset( _( "Choose a tile which does not contain any objects." ) ); return; } setObjectOnTileAsAction( tile, groupType, _editorPanel.getSelectedObjectType() ); } else if ( groupType == Maps::ObjectGroup::LANDSCAPE_TREES ) { const auto & objectInfo = getObjectInfo( groupType, _editorPanel.getSelectedObjectType() ); if ( !isObjectPlacementAllowed( objectInfo, tilePos ) ) { _warningMessage.reset( _( "Objects cannot be placed outside the map." ) ); return; } if ( !checkConditionForUsedTiles( objectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return !tileToCheck.isWater(); } ) ) { _warningMessage.reset( _( "Trees cannot be placed on water." ) ); return; } if ( !checkConditionForOccupiedTiles( objectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return Maps::isClearGround( tileToCheck ); } ) ) { _warningMessage.reset( _( "Choose a tile which does not contain any objects." ) ); return; } setObjectOnTileAsAction( tile, groupType, _editorPanel.getSelectedObjectType() ); } else if ( groupType == Maps::ObjectGroup::ADVENTURE_WATER || groupType == Maps::ObjectGroup::LANDSCAPE_WATER ) { const auto & objectInfo = getObjectInfo( groupType, _editorPanel.getSelectedObjectType() ); if ( !isObjectPlacementAllowed( objectInfo, tilePos ) ) { _warningMessage.reset( _( "Objects cannot be placed outside the map." ) ); return; } if ( !checkConditionForUsedTiles( objectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return tileToCheck.isWater(); } ) ) { _warningMessage.reset( _( "Ocean object must be placed on water." ) ); return; } if ( !checkConditionForOccupiedTiles( objectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return Maps::isClearGround( tileToCheck ); } ) ) { _warningMessage.reset( _( "Choose a tile which does not contain any objects." ) ); return; } setObjectOnTileAsAction( tile, groupType, _editorPanel.getSelectedObjectType() ); } else if ( groupType == Maps::ObjectGroup::LANDSCAPE_MISCELLANEOUS ) { const auto & objectInfo = getObjectInfo( groupType, _editorPanel.getSelectedObjectType() ); if ( !isObjectPlacementAllowed( objectInfo, tilePos ) ) { _warningMessage.reset( _( "Objects cannot be placed outside the map." ) ); return; } if ( !checkConditionForUsedTiles( objectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return !tileToCheck.isWater(); } ) ) { _warningMessage.reset( _( "Landscape objects cannot be placed on water." ) ); return; } if ( !checkConditionForOccupiedTiles( objectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return Maps::isClearGround( tileToCheck ); } ) ) { _warningMessage.reset( _( "Choose a tile which does not contain any objects." ) ); return; } setObjectOnTileAsAction( tile, groupType, _editorPanel.getSelectedObjectType() ); } else if ( groupType == Maps::ObjectGroup::KINGDOM_TOWNS ) { int32_t type = -1; int32_t color = -1; _editorPanel.getTownObjectProperties( type, color ); if ( type < 0 || color < 0 ) { // Check your logic! assert( 0 ); return; } if ( tile.isWater() ) { _warningMessage.reset( _( "Towns cannot be placed on water." ) ); return; } const int groundType = Maps::Ground::getGroundByImageIndex( tile.getTerrainImageIndex() ); const int32_t basementId = fheroes2::getTownBasementId( groundType ); const auto & townObjectInfo = getObjectInfo( groupType, type ); const auto & basementObjectInfo = getObjectInfo( Maps::ObjectGroup::LANDSCAPE_TOWN_BASEMENTS, basementId ); if ( !isObjectPlacementAllowed( townObjectInfo, tilePos ) ) { _warningMessage.reset( _( "Objects cannot be placed outside the map." ) ); return; } if ( !isObjectPlacementAllowed( basementObjectInfo, tilePos ) ) { _warningMessage.reset( _( "Objects cannot be placed outside the map." ) ); return; } if ( !checkConditionForUsedTiles( townObjectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return !tileToCheck.isWater(); } ) ) { _warningMessage.reset( _( "Towns cannot be placed on water." ) ); return; } if ( !checkConditionForUsedTiles( basementObjectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return !tileToCheck.isWater(); } ) ) { _warningMessage.reset( _( "Towns cannot be placed on water." ) ); return; } if ( !checkConditionForOccupiedTiles( townObjectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return Maps::isClearGround( tileToCheck ); } ) ) { _warningMessage.reset( _( "Choose a tile which does not contain any objects." ) ); return; } if ( !checkConditionForOccupiedTiles( basementObjectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return Maps::isClearGround( tileToCheck ); } ) ) { _warningMessage.reset( _( "Choose a tile which does not contain any objects." ) ); return; } fheroes2::ActionCreator action( _historyManager, _mapFormat ); if ( !setObjectOnTile( tile, Maps::ObjectGroup::LANDSCAPE_TOWN_BASEMENTS, basementId ) ) { return; } // Since the whole object consists of multiple "objects" we have to put the same ID for all of them. // Every time an object is being placed on a map the counter is going to be increased by 1. // Therefore, we set the counter by 1 less for each object to match object UID for all of them. assert( Maps::getLastObjectUID() > 0 ); const uint32_t objectId = Maps::getLastObjectUID() - 1; Maps::setLastObjectUID( objectId ); if ( !setObjectOnTile( tile, groupType, type ) ) { return; } // Add flags. assert( tile.GetIndex() > 0 && tile.GetIndex() < world.w() * world.h() - 1 ); Maps::setLastObjectUID( objectId ); if ( !setObjectOnTile( world.GetTiles( tile.GetIndex() - 1 ), Maps::ObjectGroup::LANDSCAPE_FLAGS, color * 2 ) ) { return; } Maps::setLastObjectUID( objectId ); if ( !setObjectOnTile( world.GetTiles( tile.GetIndex() + 1 ), Maps::ObjectGroup::LANDSCAPE_FLAGS, color * 2 + 1 ) ) { return; } action.commit(); } else if ( groupType == Maps::ObjectGroup::ADVENTURE_MINES ) { int32_t type = -1; int32_t color = -1; _editorPanel.getMineObjectProperties( type, color ); if ( type < 0 || color < 0 ) { // Check your logic! assert( 0 ); return; } const auto & objectInfo = getObjectInfo( groupType, type ); if ( !isObjectPlacementAllowed( objectInfo, tilePos ) ) { _warningMessage.reset( _( "Objects cannot be placed outside the map." ) ); return; } if ( !checkConditionForUsedTiles( objectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return !tileToCheck.isWater(); } ) ) { _warningMessage.reset( _( "Mines cannot be placed on water." ) ); return; } if ( !checkConditionForOccupiedTiles( objectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return Maps::isClearGround( tileToCheck ); } ) ) { _warningMessage.reset( _( "Choose a tile which does not contain any objects." ) ); return; } fheroes2::ActionCreator action( _historyManager, _mapFormat ); if ( !setObjectOnTile( tile, groupType, type ) ) { return; } // TODO: Place owner flag according to the color state. action.commit(); } else if ( groupType == Maps::ObjectGroup::ADVENTURE_DWELLINGS ) { const auto & objectInfo = getObjectInfo( groupType, _editorPanel.getSelectedObjectType() ); if ( !isObjectPlacementAllowed( objectInfo, tilePos ) ) { _warningMessage.reset( _( "Objects cannot be placed outside the map." ) ); return; } if ( !checkConditionForUsedTiles( objectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return !tileToCheck.isWater(); } ) ) { _warningMessage.reset( _( "Dwellings cannot be placed on water." ) ); return; } if ( !checkConditionForOccupiedTiles( objectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return Maps::isClearGround( tileToCheck ); } ) ) { _warningMessage.reset( _( "Choose a tile which does not contain any objects." ) ); return; } setObjectOnTileAsAction( tile, groupType, _editorPanel.getSelectedObjectType() ); } else if ( groupType == Maps::ObjectGroup::ADVENTURE_POWER_UPS ) { const auto & objectInfo = getObjectInfo( groupType, _editorPanel.getSelectedObjectType() ); if ( !isObjectPlacementAllowed( objectInfo, tilePos ) ) { _warningMessage.reset( _( "Objects cannot be placed outside the map." ) ); return; } if ( !checkConditionForUsedTiles( objectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return !tileToCheck.isWater(); } ) ) { _warningMessage.reset( _( "Power-ups cannot be placed on water." ) ); return; } if ( !checkConditionForOccupiedTiles( objectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return Maps::isClearGround( tileToCheck ); } ) ) { _warningMessage.reset( _( "Choose a tile which does not contain any objects." ) ); return; } setObjectOnTileAsAction( tile, groupType, _editorPanel.getSelectedObjectType() ); } else if ( groupType == Maps::ObjectGroup::ADVENTURE_MISCELLANEOUS ) { const auto & objectInfo = getObjectInfo( groupType, _editorPanel.getSelectedObjectType() ); if ( !isObjectPlacementAllowed( objectInfo, tilePos ) ) { _warningMessage.reset( _( "Objects cannot be placed outside the map." ) ); return; } if ( !checkConditionForUsedTiles( objectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return !tileToCheck.isWater(); } ) ) { _warningMessage.reset( _( "Adventure objects cannot be placed on water." ) ); return; } if ( !checkConditionForOccupiedTiles( objectInfo, tilePos, []( const Maps::Tiles & tileToCheck ) { return Maps::isClearGround( tileToCheck ); } ) ) { _warningMessage.reset( _( "Choose a tile which does not contain any objects." ) ); return; } setObjectOnTileAsAction( tile, groupType, _editorPanel.getSelectedObjectType() ); } } void EditorInterface::mouseCursorAreaPressRight( const int32_t tileIndex ) const { Editor::showPopupWindow( world.GetTiles( tileIndex ) ); } void EditorInterface::updateCursor( const int32_t tileIndex ) { if ( _cursorUpdater && tileIndex >= 0 ) { _cursorUpdater( tileIndex ); } else { Cursor::Get().SetThemes( Cursor::POINTER ); } } bool EditorInterface::setObjectOnTile( Maps::Tiles & tile, const Maps::ObjectGroup groupType, const int32_t objectIndex ) { const auto & objectInfo = getObjectInfo( groupType, objectIndex ); if ( objectInfo.empty() ) { // Check your logic as you are trying to insert an empty object! assert( 0 ); return false; } _redraw |= mapUpdateFlags; if ( !Maps::setObjectOnTile( tile, objectInfo, true ) ) { return false; } Maps::addObjectToMap( _mapFormat, tile.GetIndex(), groupType, static_cast( objectIndex ) ); return true; } void EditorInterface::setObjectOnTileAsAction( Maps::Tiles & tile, const Maps::ObjectGroup groupType, const int32_t objectIndex ) { fheroes2::ActionCreator action( _historyManager, _mapFormat ); if ( setObjectOnTile( tile, groupType, objectIndex ) ) { action.commit(); } } bool EditorInterface::loadMap( const std::string & filePath ) { if ( !Maps::Map_Format::loadMap( filePath, _mapFormat ) ) { fheroes2::showStandardTextMessage( _( "Warning!" ), "Failed to load the map.", Dialog::OK ); return false; } if ( !Maps::readMapInEditor( _mapFormat ) ) { fheroes2::showStandardTextMessage( _( "Warning!" ), "Failed to read the map.", Dialog::OK ); return false; } return true; } } fheroes2-1.0.12+dfsg/src/fheroes2/editor/editor_interface.h000066400000000000000000000115121456075706000235120ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include #include #include "editor_interface_panel.h" #include "game_mode.h" #include "history_manager.h" #include "interface_base.h" #include "map_format_info.h" #include "timing.h" namespace Maps { class Tiles; enum class ObjectGroup : int32_t; } namespace Interface { class EditorInterface final : public BaseInterface { public: static EditorInterface & Get(); void redraw( const uint32_t force ) override; // Regenerates the game area and updates the panel positions depending on the UI settings void reset() override; // Start Map Editor interface main function. fheroes2::GameMode startEdit( const bool isNewMap ); static fheroes2::GameMode eventLoadMap(); static fheroes2::GameMode eventNewMap(); static fheroes2::GameMode eventFileDialog(); void eventViewWorld(); bool useMouseDragMovement() override { return _editorPanel.useMouseDragMovement(); } void mouseCursorAreaClickLeft( const int32_t tileIndex ) override; void mouseCursorAreaPressRight( const int32_t tileIndex ) const override; void undoAction() { if ( _historyManager.undo() ) { _redraw |= ( REDRAW_GAMEAREA | REDRAW_RADAR ); } } void redoAction() { if ( _historyManager.redo() ) { _redraw |= ( REDRAW_GAMEAREA | REDRAW_RADAR ); } } void updateCursor( const int32_t tileIndex ) override; void setCursorUpdater( const std::function & cursorUpdater ) { _cursorUpdater = cursorUpdater; } bool loadMap( const std::string & filePath ); private: class WarningMessage { public: explicit WarningMessage( EditorInterface & interface ) : _interface( interface ) { // Do nothing. } void reset( std::string info ) { _message = std::move( info ); _interface.setRedraw( REDRAW_GAMEAREA ); _timer.reset(); } bool isValid() const { return _timer.getS() < 5 && !_message.empty(); } std::string message() const { return _message; } private: EditorInterface & _interface; std::string _message; fheroes2::Time _timer; }; EditorInterface() : BaseInterface( true ) , _editorPanel( *this ) , _warningMessage( *this ) { // Do nothing. } bool setObjectOnTile( Maps::Tiles & tile, const Maps::ObjectGroup groupType, const int32_t objectIndex ); void setObjectOnTileAsAction( Maps::Tiles & tile, const Maps::ObjectGroup groupType, const int32_t objectIndex ); void handleObjectMouseLeftClick( Maps::Tiles & tile ); EditorPanel _editorPanel; int32_t _selectedTile{ -1 }; int32_t _tileUnderCursor{ -1 }; std::function _cursorUpdater; fheroes2::HistoryManager _historyManager; Maps::Map_Format::MapFormat _mapFormat; WarningMessage _warningMessage; }; } fheroes2-1.0.12+dfsg/src/fheroes2/editor/editor_interface_panel.cpp000066400000000000000000001714061456075706000252350ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "editor_interface_panel.h" #include #include #include #include #include #include #include #include "agg_image.h" #include "cursor.h" #include "dialog.h" #include "dialog_selectitems.h" #include "dialog_system_options.h" #include "editor_interface.h" #include "gamedefs.h" #include "ground.h" #include "icn.h" #include "image.h" #include "interface_base.h" #include "localevent.h" #include "maps_tiles.h" #include "monster.h" #include "pal.h" #include "screen.h" #include "settings.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_map_object.h" #include "ui_text.h" #include "ui_window.h" #include "world.h" namespace { fheroes2::Sprite getObjectImage( const Maps::ObjectGroup group, const int32_t type ) { if ( type == -1 ) { return {}; } const auto & objectInfo = Maps::getObjectsByGroup( group ); if ( type < 0 || type >= static_cast( objectInfo.size() ) ) { // You are trying to render some unknown stuff! assert( 0 ); return {}; } return fheroes2::generateMapObjectImage( objectInfo[type] ); } void setCustomCursor( const Maps::ObjectGroup group, const int32_t type ) { if ( type == -1 ) { // The object type is not set. We show the POINTER cursor for this case. Cursor::Get().SetThemes( Cursor::POINTER ); return; } const fheroes2::Sprite & image = getObjectImage( group, type ); Cursor::Get().setCustomImage( image, { image.x(), image.y() } ); } fheroes2::Rect getObjectOccupiedArea( const Maps::ObjectGroup group, const int32_t objectType ) { if ( group == Maps::ObjectGroup::KINGDOM_TOWNS ) { // TODO: make occupied area calculation for complex objects. return { -2, -3, 5, 5 }; } const auto & objectInfo = Maps::getObjectsByGroup( group ); if ( objectType < 0 || objectType >= static_cast( objectInfo.size() ) ) { assert( 0 ); return { 0, 0, 1, 1 }; } const auto & offsets = Maps::getGroundLevelOccupiedTileOffset( objectInfo[objectType] ); if ( offsets.size() < 2 ) { return { 0, 0, 1, 1 }; } fheroes2::Point minPos{ offsets.front() }; fheroes2::Point maxPos{ offsets.front() }; for ( const auto & offset : offsets ) { minPos.x = std::min( minPos.x, offset.x ); minPos.y = std::min( minPos.y, offset.y ); maxPos.x = std::max( maxPos.x, offset.x ); maxPos.y = std::max( maxPos.y, offset.y ); } return { minPos.x, minPos.y, maxPos.x - minPos.x + 1, maxPos.y - minPos.y + 1 }; } fheroes2::Image makeInstrumentPanelBackground( const int32_t width, const int32_t height ) { fheroes2::Image background; background._disableTransformLayer(); background.resize( width, height ); fheroes2::StandardWindow::renderBackgroundImage( background, { 0, 0, width, height }, 0, Settings::Get().isEvilInterfaceEnabled() ); // Make background borders: it consists of rectangles with different transform shading. auto applyRectTransform = [width, height]( fheroes2::Image & output, const int32_t offset, const uint8_t transformId ) { const int32_t doubleOffset = 2 * offset; // Top horizontal line. ApplyTransform( output, offset, offset, width - doubleOffset, 1, transformId ); // Left vertical line without pixels that are parts of horizontal lines. ApplyTransform( output, offset, offset + 1, 1, height - doubleOffset - 2, transformId ); // Bottom horizontal line. ApplyTransform( output, offset, height - 1 - offset, width - doubleOffset, 1, transformId ); // Right vertical line without pixels that are parts of horizontal lines. ApplyTransform( output, width - 1 - offset, offset + 1, 1, height - doubleOffset - 2, transformId ); }; applyRectTransform( background, 0, 2 ); applyRectTransform( background, 1, 4 ); applyRectTransform( background, 2, 5 ); applyRectTransform( background, 2, 9 ); applyRectTransform( background, 3, 8 ); applyRectTransform( background, 4, 9 ); return background; } void drawInstrumentName( fheroes2::Image & output, const fheroes2::Point & pos, std::string text ) { const fheroes2::Sprite & originalPanel = fheroes2::AGG::GetICN( ICN::EDITPANL, 0 ); const int32_t nameBackgroundOffsetX{ 7 }; const int32_t nameBackgroundOffsetY{ 104 }; const int32_t nameBackgroundWidth{ 130 }; const int32_t nameBackgroundHeight{ 14 }; if ( Settings::Get().isEvilInterfaceEnabled() ) { fheroes2::ApplyPalette( originalPanel, nameBackgroundOffsetX, nameBackgroundOffsetY, output, pos.x, pos.y, nameBackgroundWidth, nameBackgroundHeight, PAL::GetPalette( PAL::PaletteType::GOOD_TO_EVIL_INTERFACE ) ); } else { fheroes2::Copy( originalPanel, nameBackgroundOffsetX, nameBackgroundOffsetY, output, pos.x, pos.y, nameBackgroundWidth, nameBackgroundHeight ); } const fheroes2::Text terrainText( std::move( text ), fheroes2::FontType::smallWhite() ); terrainText.draw( pos.x + ( nameBackgroundWidth - terrainText.width() ) / 2, pos.y + 3, output ); } void drawObjectTypeSelectionRect( fheroes2::Image & output, const fheroes2::Point & pos ) { const fheroes2::Sprite & selection = fheroes2::AGG::GetICN( ICN::TERRAINS, 9 ); fheroes2::Blit( selection, 0, 0, output, pos.x - 2, pos.y - 2, selection.width(), selection.height() ); } void showObjectTypeInfoText( std::string objectName ) { std::string text = _( "Used to place %{object}." ); StringReplaceWithLowercase( text, "%{object}", objectName ); fheroes2::showStandardTextMessage( std::move( objectName ), std::move( text ), Dialog::ZERO ); } template void updateObjectTypeSelection( const int8_t objectId, const std::array & buttonAreas, const std::function & getObjectTypeName, const fheroes2::Point & ObjectTypeNamePosition, fheroes2::Image & output ) { if ( objectId < 0 ) { drawInstrumentName( output, ObjectTypeNamePosition, _( "Select object type" ) ); } else { drawObjectTypeSelectionRect( output, buttonAreas[objectId].getPosition() ); drawInstrumentName( output, ObjectTypeNamePosition, getObjectTypeName( objectId ) ); } } } namespace Interface { EditorPanel::EditorPanel( EditorInterface & interface_ ) : _interface( interface_ ) { uint32_t icnIndex = 0; const int icnId = Settings::Get().isEvilInterfaceEnabled() ? ICN::EDITBTNS_EVIL : ICN::EDITBTNS; // Editor Instruments go in this order in ICN: TERRAIN, LANDSCAPE_OBJECTS, DETAIL, ADVENTURE_OBJECTS, KINGDOM_OBJECTS, MONSTERS, STREAM, ROAD, ERASE. for ( size_t i = 0; i < Instrument::INSTRUMENTS_COUNT; ++i ) { if ( i == Instrument::ADVENTURE_OBJECTS ) { // Second row buttons ICN index starts from 53. icnIndex = 35; } else if ( i == Instrument::STREAM ) { // Third row buttons ICN index starts from 53. icnIndex = 6; } _instrumentButtons[i].setICNInfo( icnId, icnIndex, icnIndex + 1 ); icnIndex += 2; } _buttonMagnify.setICNInfo( icnId, 12, 13 ); _buttonUndo.setICNInfo( icnId, 14, 15 ); _buttonNew.setICNInfo( icnId, 16, 17 ); _buttonSpecs.setICNInfo( icnId, 18, 19 ); _buttonFile.setICNInfo( icnId, 20, 21 ); _buttonSystem.setICNInfo( icnId, 22, 23 ); // Brush Size buttons go in this order in ICN: SMALL (1x), MEDIUM (2x), LARGE (4x), AREA. icnIndex = 24; for ( fheroes2::Button & button : _brushSizeButtons ) { button.setICNInfo( icnId, icnIndex, icnIndex + 1 ); icnIndex += 2; } _instrumentButtons[_selectedInstrument].press(); _brushSizeButtons[_selectedBrushSize].press(); _selectedLandscapeObjectType.fill( -1 ); _selectedAdventureObjectType.fill( -1 ); _selectedKingdomObjectType.fill( -1 ); } fheroes2::Rect EditorPanel::getBrushArea() const { // Roads and streams are placed using only 1x1 brush. if ( _selectedInstrument == Instrument::STREAM || _selectedInstrument == Instrument::ROAD || _selectedInstrument == Instrument::DETAIL || _selectedInstrument == Instrument::MONSTERS ) { return { 0, 0, 1, 1 }; } if ( _selectedInstrument == Instrument::LANDSCAPE_OBJECTS || _selectedInstrument == Instrument::ADVENTURE_OBJECTS || _selectedInstrument == Instrument::KINGDOM_OBJECTS ) { const int32_t objectType = getSelectedObjectType(); if ( objectType >= 0 ) { const Maps::ObjectGroup objectGroup = getSelectedObjectGroup(); if ( objectGroup == Maps::ObjectGroup::ADVENTURE_MINES ) { // For mine we need to decode the objectType. int32_t type = -1; int32_t color = -1; getMineObjectProperties( type, color ); return getObjectOccupiedArea( objectGroup, type ); } return getObjectOccupiedArea( objectGroup, objectType ); } return {}; } switch ( _selectedBrushSize ) { case BrushSize::SMALL: return { 0, 0, 1, 1 }; case BrushSize::MEDIUM: return { 0, 0, 2, 2 }; case BrushSize::LARGE: return { -1, -1, 4, 4 }; case BrushSize::AREA: return {}; default: // Have you added a new Brush size? Update the logic above! assert( 0 ); break; } return {}; } void EditorPanel::setPos( const int32_t displayX, int32_t displayY ) { int32_t offsetX = displayX; // Editor panel consists of 3 instrument button rows, instrument panel and 2 system button rows. // Each button row is 36 pixels height. const fheroes2::Display & display = fheroes2::Display::instance(); const int32_t instrumentPanelWidth = display.width() - displayX - BORDERWIDTH; const int32_t bottomBorderOffset = ( display.height() > fheroes2::Display::DEFAULT_HEIGHT + BORDERWIDTH ) ? BORDERWIDTH : 0; const int32_t instrumentPanelHeight = display.height() - displayY - fheroes2::AGG::GetICN( ICN::EDITBTNS, 0 ).height() * 5 - bottomBorderOffset; _instrumentPanelBackground = makeInstrumentPanelBackground( instrumentPanelWidth, instrumentPanelHeight ); for ( size_t i = 0; i < _instrumentButtonsRect.size(); ++i ) { _instrumentButtons[i].setPosition( offsetX, displayY ); _instrumentButtonsRect[i] = _instrumentButtons[i].area(); // We have 3 buttons in a row. if ( ( i + 1 ) % 3 == 0 ) { offsetX = displayX; displayY += _instrumentButtonsRect[i].height; } else { offsetX += _instrumentButtonsRect[i].width; } } _rectInstruments = { _instrumentButtonsRect.front().x, _instrumentButtonsRect.front().y, _instrumentButtonsRect.back().x + _instrumentButtonsRect.back().width - _instrumentButtonsRect.front().x, displayY - _instrumentButtonsRect.front().y }; // Instrument panel. _rectInstrumentPanel = { displayX, displayY, instrumentPanelWidth, instrumentPanelHeight }; // Brush size buttons position. Shown on the terrain and erasure instrument panels. offsetX = displayX + 14; int32_t offsetY = displayY + std::min( instrumentPanelHeight - 27, 135 ); for ( size_t i = 0; i < _brushSizeButtonsRect.size(); ++i ) { _brushSizeButtons[i].setPosition( offsetX, offsetY ); _brushSizeButtonsRect[i] = _brushSizeButtons[i].area(); offsetX += 30; } const int32_t buttonStepX{ 36 }; const int32_t buttonStepY{ 33 }; const int32_t buttonWidth{ 27 }; const int32_t buttonHeight{ 27 }; const int32_t buttonHalfStepX = buttonStepX / 2; // Terrain type select buttons position. Shown on the terrain instrument panel. offsetX = displayX + 23; offsetY = displayY + 11; for ( size_t i = 0; i < _terrainButtonsRect.size(); ++i ) { _terrainButtonsRect[i] = { offsetX + static_cast( i % 3 ) * buttonStepX, offsetY + static_cast( i / 3 ) * buttonStepY, buttonWidth, buttonHeight }; } // Landscape objects buttons position. for ( size_t i = 0; i < _landscapeObjectButtonsRect.size(); ++i ) { _landscapeObjectButtonsRect[i] = { offsetX + static_cast( i % 3 ) * buttonStepX + ( i > 2 ? buttonHalfStepX : 0 ), offsetY + static_cast( i / 3 ) * buttonStepY, buttonWidth, buttonHeight }; } // Adventure objects buttons position. for ( size_t i = 0; i < _adventureObjectButtonsRect.size(); ++i ) { _adventureObjectButtonsRect[i] = { offsetX + static_cast( i % 2 ) * buttonStepX + ( i < 4 ? buttonHalfStepX : ( i < 6 ? 0 : buttonStepX * 2 ) ), offsetY + static_cast( i / 2 ) * buttonStepY - ( i < 6 ? 0 : buttonStepY ), buttonWidth, buttonHeight }; } // Kingdom objects buttons position. for ( size_t i = 0; i < _kingdomObjectButtonsRect.size(); ++i ) { // We have only two buttons and have enough space to make a double stepX. _kingdomObjectButtonsRect[i] = { offsetX + static_cast( i ) * buttonStepX + buttonHalfStepX, offsetY, buttonWidth, buttonHeight }; } // Erase tool object type buttons. offsetX -= 28; offsetY += 20; for ( size_t i = 0; i < _eraseButtonsRect.size(); ++i ) { _eraseButtonsRect[i] = { offsetX + static_cast( i % 4 ) * buttonStepX, offsetY + static_cast( i / 4 ) * buttonStepY, buttonWidth, buttonHeight }; } displayY += _rectInstrumentPanel.height; // System buttons top row. _buttonMagnify.setPosition( displayX, displayY ); _rectMagnify = _buttonMagnify.area(); _buttonUndo.setPosition( _rectMagnify.x + _rectMagnify.width, displayY ); _rectUndo = _buttonUndo.area(); _buttonNew.setPosition( _rectUndo.x + _rectUndo.width, displayY ); _rectNew = _buttonNew.area(); // System buttons bottom row. displayY += _rectMagnify.height; _buttonSpecs.setPosition( displayX, displayY ); _rectSpecs = _buttonSpecs.area(); _buttonFile.setPosition( _rectSpecs.x + _rectSpecs.width, displayY ); _rectFile = _buttonFile.area(); _buttonSystem.setPosition( _rectFile.x + _rectFile.width, displayY ); _rectSystem = _buttonSystem.area(); _rectEditorPanel = { _rectInstruments.x, _rectInstruments.y, _rectSystem.x + _rectSystem.width - _rectInstruments.x, _rectSystem.y + _rectSystem.height - _rectInstruments.y }; } void EditorPanel::setRedraw() const { _interface.setRedraw( REDRAW_PANEL ); } void EditorPanel::_redraw() const { for ( const fheroes2::Button & button : _instrumentButtons ) { button.draw(); } fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::Copy( _instrumentPanelBackground, 0, 0, display, _rectInstrumentPanel.x, _rectInstrumentPanel.y, _rectInstrumentPanel.width, _rectInstrumentPanel.height ); if ( _selectedInstrument == Instrument::TERRAIN || _selectedInstrument == Instrument::ERASE ) { for ( const fheroes2::Button & button : _brushSizeButtons ) { button.draw(); } } if ( _selectedInstrument == Instrument::TERRAIN ) { // We use terrain images from the original terrain instrument panel sprite. const fheroes2::Sprite & originalPanel = fheroes2::AGG::GetICN( ICN::EDITPANL, 0 ); for ( size_t i = 0; i < TerrainBrush::TERRAIN_COUNT; ++i ) { const int32_t originalOffsetX = 30 + static_cast( i % 3 ) * 29; const int32_t originalOffsetY = 11 + static_cast( i / 3 ) * 29; fheroes2::Copy( originalPanel, originalOffsetX, originalOffsetY, display, _terrainButtonsRect[i] ); } // Terrain type selection yellow rectangle. drawObjectTypeSelectionRect( display, _terrainButtonsRect[_selectedTerrain].getPosition() ); // On high resolutions we have space to show selected terrain text. if ( _rectInstrumentPanel.height > 160 ) { drawInstrumentName( display, { _rectInstrumentPanel.x + 7, _rectInstrumentPanel.y + 113 }, _getTerrainTypeName( _selectedTerrain ) ); } } else if ( _selectedInstrument == Instrument::LANDSCAPE_OBJECTS ) { // Landscape objects buttons. for ( uint32_t i = 0; i < LandscapeObjectBrush::LANDSCAPE_COUNT; ++i ) { fheroes2::Copy( fheroes2::AGG::GetICN( ICN::EDITPANL, i + 6 ), 0, 0, display, _landscapeObjectButtonsRect[i] ); } updateObjectTypeSelection( _selectedLandscapeObject, _landscapeObjectButtonsRect, _getLandscapeObjectTypeName, { _rectInstrumentPanel.x + 7, _rectInstrumentPanel.y + 80 }, display ); } else if ( _selectedInstrument == Instrument::ADVENTURE_OBJECTS ) { // Adventure objects buttons. const fheroes2::Sprite & originalPanel = fheroes2::AGG::GetICN( ICN::EDITPANL, 1 ); const int32_t originalArtifactsImageOffsetX{ 15 }; const int32_t originalTreasuresImageOffsetX{ 102 }; const int32_t originalImagesOffsetY{ 96 }; fheroes2::Copy( originalPanel, originalArtifactsImageOffsetX, originalImagesOffsetY, display, _adventureObjectButtonsRect[AdventureObjectBrush::ARTIFACTS] ); for ( uint32_t i = AdventureObjectBrush::DWELLINGS; i < AdventureObjectBrush::TREASURES; ++i ) { fheroes2::Copy( fheroes2::AGG::GetICN( ICN::EDITPANL, i + 10 ), 0, 0, display, _adventureObjectButtonsRect[i] ); } fheroes2::Copy( originalPanel, originalTreasuresImageOffsetX, originalImagesOffsetY, display, _adventureObjectButtonsRect[AdventureObjectBrush::TREASURES] ); for ( uint32_t i = AdventureObjectBrush::WATER_ADVENTURE; i < AdventureObjectBrush::ADVENTURE_COUNT; ++i ) { fheroes2::Copy( fheroes2::AGG::GetICN( ICN::EDITPANL, i + 9 ), 0, 0, display, _adventureObjectButtonsRect[i] ); } updateObjectTypeSelection( _selectedAdventureObject, _adventureObjectButtonsRect, _getAdventureObjectTypeName, { _rectInstrumentPanel.x + 7, _rectInstrumentPanel.y + 113 }, display ); } else if ( _selectedInstrument == Instrument::KINGDOM_OBJECTS ) { // Kingdom objects buttons. const fheroes2::Sprite & originalPanel = fheroes2::AGG::GetICN( ICN::EDITPANL, 1 ); const int32_t originalHeroesImageOffsetX{ 102 }; const int32_t originalTownsImageOffsetX{ 44 }; const int32_t originalImagesOffsetY{ 68 }; fheroes2::Copy( originalPanel, originalHeroesImageOffsetX, originalImagesOffsetY, display, _kingdomObjectButtonsRect[KingdomObjectBrush::HEROES] ); fheroes2::Copy( originalPanel, originalTownsImageOffsetX, originalImagesOffsetY, display, _kingdomObjectButtonsRect[KingdomObjectBrush::TOWNS] ); updateObjectTypeSelection( _selectedKingdomObject, _kingdomObjectButtonsRect, _getKingdomObjectTypeName, { _rectInstrumentPanel.x + 7, _rectInstrumentPanel.y + 48 }, display ); } else if ( _selectedInstrument == Instrument::MONSTERS ) { const fheroes2::Text instrumentName( _( "Monsters" ), fheroes2::FontType::normalWhite() ); instrumentName.draw( _rectInstrumentPanel.x + ( _rectInstrumentPanel.width - instrumentName.width() ) / 2, _rectInstrumentPanel.y + 8, display ); if ( _selectedMonsterType < 0 ) { // Show a tip. const fheroes2::Text text( _( "Click here to\nselect a monster." ), fheroes2::FontType::smallWhite() ); text.draw( _rectInstrumentPanel.x + 5, _rectInstrumentPanel.y + 38, _rectInstrumentPanel.width - 10, display ); } else { // Show the selected monster image on the panel. const fheroes2::Sprite & image = getObjectImage( Maps::ObjectGroup::MONSTERS, _selectedMonsterType ); fheroes2::Blit( image, display, _rectInstrumentPanel.x + ( _rectInstrumentPanel.width - image.width() ) / 2, _rectInstrumentPanel.y + 67 - image.height() ); fheroes2::Text text( Monster( _selectedMonsterType + 1 ).GetName(), fheroes2::FontType::smallWhite() ); text.draw( _rectInstrumentPanel.x + 5, _rectInstrumentPanel.y + 70, _rectInstrumentPanel.width - 10, display ); text.set( _( "Click here to\nselect another monster." ), fheroes2::FontType::smallWhite() ); text.draw( _rectInstrumentPanel.x + 5, _rectInstrumentPanel.y + 95, _rectInstrumentPanel.width - 10, display ); } } else if ( _selectedInstrument == Instrument::DETAIL ) { const fheroes2::Text instrumentName( _( "Cell\nDetails" ), fheroes2::FontType::normalWhite() ); instrumentName.draw( _rectInstrumentPanel.x + 5, _rectInstrumentPanel.y + 8, _rectInstrumentPanel.width - 10, display ); } else if ( _selectedInstrument == Instrument::ROAD ) { const fheroes2::Text instrumentName( _( "Roads" ), fheroes2::FontType::normalWhite() ); instrumentName.draw( _rectInstrumentPanel.x + ( _rectInstrumentPanel.width - instrumentName.width() ) / 2, _rectInstrumentPanel.y + 8, display ); } else if ( _selectedInstrument == Instrument::STREAM ) { const fheroes2::Text instrumentName( _( "Streams" ), fheroes2::FontType::normalWhite() ); instrumentName.draw( _rectInstrumentPanel.x + ( _rectInstrumentPanel.width - instrumentName.width() ) / 2, _rectInstrumentPanel.y + 8, display ); } else if ( _selectedInstrument == Instrument::ERASE ) { const fheroes2::Text instrumentName( _( "Erase" ), fheroes2::FontType::normalWhite() ); instrumentName.draw( _rectInstrumentPanel.x + ( _rectInstrumentPanel.width - instrumentName.width() ) / 2, _rectInstrumentPanel.y + 8, display ); // Object type to erase buttons. const fheroes2::Sprite & originalPanel = fheroes2::AGG::GetICN( ICN::EDITPANL, 1 ); const int32_t originalArtifactsImageOffsetX{ 15 }; const int32_t originalTownsImageOffsetX{ 44 }; const int32_t originalMonstersImageOffsetX{ 73 }; const int32_t originalHeroesTreasuresImageOffsetX{ 102 }; const int32_t originalTownsMonstersHeroesOffsetY{ 68 }; const int32_t originalArtifactsTreasresOffsetY{ 96 }; fheroes2::Copy( fheroes2::AGG::GetICN( ICN::EDITPANL, 8 ), 0, 0, display, _eraseButtonsRect[0] ); fheroes2::Copy( originalPanel, originalTownsImageOffsetX, originalTownsMonstersHeroesOffsetY, display, _eraseButtonsRect[1] ); fheroes2::Copy( originalPanel, originalMonstersImageOffsetX, originalTownsMonstersHeroesOffsetY, display, _eraseButtonsRect[2] ); fheroes2::Copy( originalPanel, originalHeroesTreasuresImageOffsetX, originalTownsMonstersHeroesOffsetY, display, _eraseButtonsRect[3] ); fheroes2::Copy( originalPanel, originalArtifactsImageOffsetX, originalArtifactsTreasresOffsetY, display, _eraseButtonsRect[4] ); fheroes2::Copy( fheroes2::AGG::GetICN( ICN::EDITPANL, 16 ), 0, 0, display, _eraseButtonsRect[5] ); fheroes2::Copy( fheroes2::AGG::GetICN( ICN::EDITPANL, 17 ), 0, 0, display, _eraseButtonsRect[6] ); fheroes2::Copy( originalPanel, originalHeroesTreasuresImageOffsetX, originalArtifactsTreasresOffsetY, display, _eraseButtonsRect[7] ); // Object type to erase selection marks. const fheroes2::Sprite & selectionMark = fheroes2::AGG::GetICN( ICN::TOWNWIND, 11 ); for ( size_t i = 0; i < _eraseButtonsRect.size(); ++i ) { if ( _eraseButtonObjectTypes[i] & _eraseTypes ) { fheroes2::Blit( selectionMark, 0, 0, display, _eraseButtonsRect[i].x + 10, _eraseButtonsRect[i].y + 11, selectionMark.width(), selectionMark.height() ); } } } _buttonMagnify.draw(); _buttonUndo.draw(); _buttonNew.draw(); _buttonSpecs.draw(); _buttonFile.draw(); _buttonSystem.draw(); display.render( _rectInstrumentPanel ); } int32_t EditorPanel::getSelectedObjectType() const { switch ( _selectedInstrument ) { case Instrument::MONSTERS: return _selectedMonsterType; case Instrument::LANDSCAPE_OBJECTS: if ( _selectedLandscapeObject < 0 ) { return -1; } return _selectedLandscapeObjectType[_selectedLandscapeObject]; case Instrument::ADVENTURE_OBJECTS: if ( _selectedAdventureObject < 0 ) { return -1; } return _selectedAdventureObjectType[_selectedAdventureObject]; case Instrument::KINGDOM_OBJECTS: if ( _selectedKingdomObject < 0 ) { return -1; } return _selectedKingdomObjectType[_selectedKingdomObject]; default: // Why are you trying to get type for the non-object instrument. Check your logic! assert( 0 ); return -1; } } Maps::ObjectGroup EditorPanel::getSelectedObjectGroup() const { switch ( _selectedInstrument ) { case Instrument::MONSTERS: return Maps::ObjectGroup::MONSTERS; case Instrument::LANDSCAPE_OBJECTS: assert( _selectedLandscapeObject > -1 ); return _selectedLandscapeObjectGroup[_selectedLandscapeObject]; case Instrument::ADVENTURE_OBJECTS: assert( _selectedAdventureObject > -1 ); return _selectedAdventureObjectGroup[_selectedAdventureObject]; case Instrument::KINGDOM_OBJECTS: assert( _selectedKingdomObject > -1 ); return _selectedKingdomObjectGroup[_selectedKingdomObject]; default: // Why are you trying to get object group for the non-object instrument. Check your logic! assert( 0 ); return Maps::ObjectGroup::GROUP_COUNT; } } int EditorPanel::_getGroundId( const uint8_t brushId ) { switch ( brushId ) { case TerrainBrush::WATER: return Maps::Ground::WATER; case TerrainBrush::GRASS: return Maps::Ground::GRASS; case TerrainBrush::SNOW: return Maps::Ground::SNOW; case TerrainBrush::SWAMP: return Maps::Ground::SWAMP; case TerrainBrush::LAVA: return Maps::Ground::LAVA; case TerrainBrush::DESERT: return Maps::Ground::DESERT; case TerrainBrush::DIRT: return Maps::Ground::DIRT; case TerrainBrush::WASTELAND: return Maps::Ground::WASTELAND; case TerrainBrush::BEACH: return Maps::Ground::BEACH; default: // Have you added a new terrain type? Add the logic above! assert( 0 ); break; } return Maps::Ground::UNKNOWN; } const char * EditorPanel::_getLandscapeObjectTypeName( const uint8_t brushId ) { switch ( brushId ) { case LandscapeObjectBrush::MOUNTAINS: return _( "Mountains" ); case LandscapeObjectBrush::ROCKS: return _( "Rocks" ); case LandscapeObjectBrush::TREES: return _( "Trees" ); case LandscapeObjectBrush::WATER_OBJECTS: return _( "Water Objects" ); case LandscapeObjectBrush::LANDSCAPE_MISC: return _( "Miscellaneous" ); default: // Have you added a new object type? Add the logic above! assert( 0 ); break; } return "Unknown object type"; } const char * EditorPanel::_getAdventureObjectTypeName( const uint8_t brushId ) { switch ( brushId ) { case AdventureObjectBrush::ARTIFACTS: return _( "Artifacts" ); case AdventureObjectBrush::DWELLINGS: return _( "Dwellings" ); case AdventureObjectBrush::MINES: return _( "Mines" ); case AdventureObjectBrush::POWER_UPS: return _( "Power-ups" ); case AdventureObjectBrush::TREASURES: return _( "Treasures" ); case AdventureObjectBrush::WATER_ADVENTURE: return _( "Water Objects" ); case AdventureObjectBrush::ADVENTURE_MISC: return _( "Miscellaneous" ); default: // Have you added a new object type? Add the logic above! assert( 0 ); break; } return "Unknown object type"; } const char * EditorPanel::_getKingdomObjectTypeName( const uint8_t brushId ) { switch ( brushId ) { case KingdomObjectBrush::TOWNS: return _( "Towns" ); case KingdomObjectBrush::HEROES: return _( "Heroes" ); default: // Have you added a new object type? Add the logic above! assert( 0 ); break; } return "Unknown object type"; } const char * EditorPanel::_getEraseObjectTypeName( const uint32_t eraseObjectType ) { switch ( eraseObjectType ) { case Maps::ObjectErasureType::TERRAIN_OBJECTS: return _( "editorErasure|Terrain" ); case Maps::ObjectErasureType::CASTLES: return _( "editorErasure|Castles" ); case Maps::ObjectErasureType::MONSTERS: return _( "editorErasure|Monsters" ); case Maps::ObjectErasureType::HEROES: return _( "editorErasure|Heroes" ); case Maps::ObjectErasureType::ARTIFACTS: return _( "editorErasure|Artifacts" ); case Maps::ObjectErasureType::ROADS: return _( "editorErasure|Roads" ); case Maps::ObjectErasureType::STREAMS: return _( "editorErasure|Streams" ); case Maps::ObjectErasureType::TREASURES: return _( "editorErasure|Treasures" ); default: // Have you added a new object type to erase? Add the logic above! assert( 0 ); break; } return "Unknown object type"; } void EditorPanel::_setCursor() { switch ( _selectedInstrument ) { case Instrument::MONSTERS: _interface.setCursorUpdater( [type = getSelectedObjectType(), group = getSelectedObjectGroup()]( const int32_t /*tileIndex*/ ) { setCustomCursor( group, type ); } ); return; case Instrument::LANDSCAPE_OBJECTS: switch ( _selectedLandscapeObject ) { case LandscapeObjectBrush::MOUNTAINS: case LandscapeObjectBrush::ROCKS: case LandscapeObjectBrush::TREES: case LandscapeObjectBrush::WATER_OBJECTS: case LandscapeObjectBrush::LANDSCAPE_MISC: _interface.setCursorUpdater( [type = getSelectedObjectType(), group = getSelectedObjectGroup()]( const int32_t /*tileIndex*/ ) { setCustomCursor( group, type ); } ); return; default: break; } break; case Instrument::ADVENTURE_OBJECTS: switch ( _selectedAdventureObject ) { case AdventureObjectBrush::ARTIFACTS: case AdventureObjectBrush::DWELLINGS: case AdventureObjectBrush::POWER_UPS: case AdventureObjectBrush::TREASURES: case AdventureObjectBrush::WATER_ADVENTURE: case AdventureObjectBrush::ADVENTURE_MISC: _interface.setCursorUpdater( [type = getSelectedObjectType(), group = getSelectedObjectGroup()]( const int32_t /*tileIndex*/ ) { setCustomCursor( group, type ); } ); return; case AdventureObjectBrush::MINES: _interface.setCursorUpdater( [this]( const int32_t tileIndex ) { int32_t type = -1; int32_t color = -1; getMineObjectProperties( type, color ); if ( type == -1 || color == -1 ) { // The object type is not set. We show the POINTER cursor for this case. Cursor::Get().SetThemes( Cursor::POINTER ); return; } if ( world.GetTiles( tileIndex ).GetGround() == Maps::Ground::WATER ) { // Mines cannot be placed on water. const fheroes2::Sprite & image = fheroes2::AGG::GetICN( ICN::SPELLS, 0 ); Cursor::Get().setCustomImage( image, { -image.width() / 2, -image.height() / 2 } ); return; } assert( Maps::getObjectsByGroup( Maps::ObjectGroup::ADVENTURE_MINES ).size() > static_cast( type ) ); // TODO: Implement a function to render also the owner flag after ownership selection is implemented. const fheroes2::Sprite & image = getObjectImage( Maps::ObjectGroup::ADVENTURE_MINES, type ); Cursor::Get().setCustomImage( image, { image.x(), image.y() } ); } ); return; default: break; } break; case Instrument::KINGDOM_OBJECTS: switch ( _selectedKingdomObject ) { case KingdomObjectBrush::HEROES: _interface.setCursorUpdater( [type = getSelectedObjectType()]( const int32_t /*tileIndex*/ ) { if ( type == -1 ) { // The object type is not set. We show the POINTER cursor for this case. Cursor::Get().SetThemes( Cursor::POINTER ); return; } // TODO: render ICN::MINIHERO from the existing hero images. const fheroes2::Sprite & image = fheroes2::AGG::GetICN( ICN::MINIHERO, type ); // Mini-hero images contain a pole with a flag. // This causes a situation that a selected tile does not properly correspond to current position of the cursor. // We need to add a hardcoded correction. const int32_t heroCorrectionY{ 12 }; Cursor::Get().setCustomImage( image, { -image.width() / 2, -image.height() / 2 - heroCorrectionY } ); } ); return; case KingdomObjectBrush::TOWNS: _interface.setCursorUpdater( [this]( const int32_t tileIndex ) { int32_t type = -1; int32_t color = -1; getTownObjectProperties( type, color ); if ( type == -1 || color == -1 ) { // The object type is not set. We show the POINTER cursor for this case. Cursor::Get().SetThemes( Cursor::POINTER ); return; } const fheroes2::Sprite & image = fheroes2::generateTownObjectImage( type, color, world.GetTiles( tileIndex ).GetGround() ); Cursor::Get().setCustomImage( image, { image.x(), image.y() } ); } ); return; default: break; } break; default: break; } _interface.setCursorUpdater( {} ); } fheroes2::GameMode EditorPanel::queueEventProcessing() { LocalEvent & le = LocalEvent::Get(); fheroes2::GameMode res = fheroes2::GameMode::CANCEL; if ( le.MousePressLeft( _rectInstruments ) ) { for ( size_t i = 0; i < _instrumentButtonsRect.size(); ++i ) { if ( le.MousePressLeft( _instrumentButtonsRect[i] ) ) { if ( _instrumentButtons[i].drawOnPress() ) { _selectedInstrument = static_cast( i ); // When opening Monsters placing and no monster was previously selected force open the Select Monster dialog. if ( _selectedInstrument == Instrument::MONSTERS && _selectedMonsterType == -1 ) { // Update panel image and then open the Select Monster dialog. _redraw(); handleObjectMouseClick( Dialog::selectMonsterType ); } // Reset cursor updater since this UI element was clicked. _setCursor(); setRedraw(); return res; } } else { _instrumentButtons[i].drawOnRelease(); } } } if ( _selectedInstrument == Instrument::TERRAIN || _selectedInstrument == Instrument::ERASE ) { for ( size_t i = 0; i < _brushSizeButtonsRect.size(); ++i ) { if ( le.MousePressLeft( _brushSizeButtonsRect[i] ) ) { if ( _brushSizeButtons[i].drawOnPress() ) { _selectedBrushSize = static_cast( i ); } } else if ( i != _selectedBrushSize ) { _brushSizeButtons[i].drawOnRelease(); } } const auto brushSizeText = []( const int brushSize, const bool isFillBrush ) { std::string text = isFillBrush ? _( "Draws terrain in\n%{size} by %{size} square increments." ) : _( "Erases objects in\n%{size} by %{size} square increments." ); StringReplace( text, "%{size}", brushSize ); return text; }; if ( le.MousePressRight( _brushSizeButtonsRect[BrushSize::SMALL] ) ) { fheroes2::showStandardTextMessage( _( "Small Brush" ), brushSizeText( 1, _selectedInstrument == Instrument::TERRAIN ), Dialog::ZERO ); } else if ( le.MousePressRight( _brushSizeButtonsRect[BrushSize::MEDIUM] ) ) { fheroes2::showStandardTextMessage( _( "Medium Brush" ), brushSizeText( 2, _selectedInstrument == Instrument::TERRAIN ), Dialog::ZERO ); } else if ( le.MousePressRight( _brushSizeButtonsRect[BrushSize::LARGE] ) ) { fheroes2::showStandardTextMessage( _( "Large Brush" ), brushSizeText( 4, _selectedInstrument == Instrument::TERRAIN ), Dialog::ZERO ); } else if ( le.MousePressRight( _brushSizeButtonsRect[BrushSize::AREA] ) ) { if ( _selectedInstrument == Instrument::TERRAIN ) { fheroes2::showStandardTextMessage( _( "Area Fill" ), _( "Used to click and drag for filling in large areas." ), Dialog::ZERO ); } else { fheroes2::showStandardTextMessage( _( "Clear Area" ), _( "Used to click and drag for clearing large areas." ), Dialog::ZERO ); } } } if ( _selectedInstrument == Instrument::TERRAIN ) { for ( size_t i = 0; i < _terrainButtonsRect.size(); ++i ) { if ( ( _selectedTerrain != i ) && le.MousePressLeft( _terrainButtonsRect[i] ) ) { _selectedTerrain = static_cast( i ); setRedraw(); // There is no need to continue the loop as only one button can be pressed at one moment. break; } } const auto movePenaltyText = []( const std::string & rate ) { std::string text = _( "Costs %{rate} times normal movement for all heroes. (Pathfinding reduces or eliminates the penalty.)" ); StringReplace( text, "%{rate}", rate ); return text; }; if ( le.MousePressRight( _terrainButtonsRect[TerrainBrush::WATER] ) ) { fheroes2::showStandardTextMessage( _getTerrainTypeName( TerrainBrush::WATER ), _( "Traversable only by boat." ), Dialog::ZERO ); } else if ( le.MousePressRight( _terrainButtonsRect[TerrainBrush::GRASS] ) ) { fheroes2::showStandardTextMessage( _getTerrainTypeName( TerrainBrush::GRASS ), _( "No special modifiers." ), Dialog::ZERO ); } else if ( le.MousePressRight( _terrainButtonsRect[TerrainBrush::SNOW] ) ) { fheroes2::showStandardTextMessage( _getTerrainTypeName( TerrainBrush::SNOW ), movePenaltyText( "1.5" ), Dialog::ZERO ); } else if ( le.MousePressRight( _terrainButtonsRect[TerrainBrush::SWAMP] ) ) { fheroes2::showStandardTextMessage( _getTerrainTypeName( TerrainBrush::SWAMP ), movePenaltyText( "1.75" ), Dialog::ZERO ); } else if ( le.MousePressRight( _terrainButtonsRect[TerrainBrush::LAVA] ) ) { fheroes2::showStandardTextMessage( _getTerrainTypeName( TerrainBrush::LAVA ), _( "No special modifiers." ), Dialog::ZERO ); } else if ( le.MousePressRight( _terrainButtonsRect[TerrainBrush::DESERT] ) ) { fheroes2::showStandardTextMessage( _getTerrainTypeName( TerrainBrush::DESERT ), movePenaltyText( "2" ), Dialog::ZERO ); } else if ( le.MousePressRight( _terrainButtonsRect[TerrainBrush::DIRT] ) ) { fheroes2::showStandardTextMessage( _getTerrainTypeName( TerrainBrush::DIRT ), _( "No special modifiers." ), Dialog::ZERO ); } else if ( le.MousePressRight( _terrainButtonsRect[TerrainBrush::WASTELAND] ) ) { fheroes2::showStandardTextMessage( _getTerrainTypeName( TerrainBrush::WASTELAND ), movePenaltyText( "1.25" ), Dialog::ZERO ); } else if ( le.MousePressRight( _terrainButtonsRect[TerrainBrush::BEACH] ) ) { fheroes2::showStandardTextMessage( _getTerrainTypeName( TerrainBrush::BEACH ), movePenaltyText( "1.25" ), Dialog::ZERO ); } } else if ( _selectedInstrument == Instrument::LANDSCAPE_OBJECTS ) { for ( size_t i = 0; i < _landscapeObjectButtonsRect.size(); ++i ) { if ( ( _selectedLandscapeObject != static_cast( i ) ) && le.MousePressLeft( _landscapeObjectButtonsRect[i] ) ) { _selectedLandscapeObject = static_cast( i ); // Reset cursor updater since this UI element was clicked. _setCursor(); setRedraw(); break; } } for ( uint8_t objectId = LandscapeObjectBrush::MOUNTAINS; objectId < LandscapeObjectBrush::LANDSCAPE_COUNT; ++objectId ) { if ( le.MousePressRight( _landscapeObjectButtonsRect[objectId] ) ) { showObjectTypeInfoText( _getLandscapeObjectTypeName( objectId ) ); break; } } if ( le.MouseClickLeft( _landscapeObjectButtonsRect[LandscapeObjectBrush::MOUNTAINS] ) ) { handleObjectMouseClick( Dialog::selectMountainType ); return res; } if ( le.MouseClickLeft( _landscapeObjectButtonsRect[LandscapeObjectBrush::ROCKS] ) ) { handleObjectMouseClick( Dialog::selectRockType ); return res; } if ( le.MouseClickLeft( _landscapeObjectButtonsRect[LandscapeObjectBrush::TREES] ) ) { handleObjectMouseClick( Dialog::selectTreeType ); return res; } if ( le.MouseClickLeft( _landscapeObjectButtonsRect[LandscapeObjectBrush::WATER_OBJECTS] ) ) { handleObjectMouseClick( Dialog::selectLandscapeOceanObjectType ); return res; } if ( le.MouseClickLeft( _landscapeObjectButtonsRect[LandscapeObjectBrush::LANDSCAPE_MISC] ) ) { handleObjectMouseClick( Dialog::selectLandscapeMiscellaneousObjectType ); return res; } } else if ( _selectedInstrument == Instrument::ADVENTURE_OBJECTS ) { for ( size_t i = 0; i < _adventureObjectButtonsRect.size(); ++i ) { if ( ( _selectedAdventureObject != static_cast( i ) ) && le.MousePressLeft( _adventureObjectButtonsRect[i] ) ) { _selectedAdventureObject = static_cast( i ); // Reset cursor updater since this UI element was clicked. _setCursor(); setRedraw(); break; } } for ( uint8_t objectId = AdventureObjectBrush::ARTIFACTS; objectId < AdventureObjectBrush::ADVENTURE_COUNT; ++objectId ) { if ( le.MousePressRight( _adventureObjectButtonsRect[objectId] ) ) { showObjectTypeInfoText( _getAdventureObjectTypeName( objectId ) ); break; } } if ( le.MouseClickLeft( _adventureObjectButtonsRect[AdventureObjectBrush::ARTIFACTS] ) ) { handleObjectMouseClick( Dialog::selectArtifactType ); return res; } if ( le.MouseClickLeft( _adventureObjectButtonsRect[AdventureObjectBrush::TREASURES] ) ) { handleObjectMouseClick( Dialog::selectTreasureType ); return res; } if ( le.MouseClickLeft( _adventureObjectButtonsRect[AdventureObjectBrush::WATER_ADVENTURE] ) ) { handleObjectMouseClick( Dialog::selectOceanObjectType ); return res; } if ( le.MouseClickLeft( _adventureObjectButtonsRect[AdventureObjectBrush::MINES] ) ) { handleObjectMouseClick( [this]( const int32_t /* type */ ) { int32_t type = -1; int32_t color = -1; getMineObjectProperties( type, color ); Dialog::selectMineType( type, color ); return _generateMineObjectProperties( type, color ); } ); return res; } if ( le.MouseClickLeft( _adventureObjectButtonsRect[AdventureObjectBrush::DWELLINGS] ) ) { handleObjectMouseClick( Dialog::selectDwellingType ); return res; } if ( le.MouseClickLeft( _adventureObjectButtonsRect[AdventureObjectBrush::POWER_UPS] ) ) { handleObjectMouseClick( Dialog::selectPowerUpObjectType ); return res; } if ( le.MouseClickLeft( _adventureObjectButtonsRect[AdventureObjectBrush::ADVENTURE_MISC] ) ) { handleObjectMouseClick( Dialog::selectAdventureMiscellaneousObjectType ); return res; } } else if ( _selectedInstrument == Instrument::KINGDOM_OBJECTS ) { for ( size_t i = 0; i < _kingdomObjectButtonsRect.size(); ++i ) { if ( ( _selectedKingdomObject != static_cast( i ) ) && le.MousePressLeft( _kingdomObjectButtonsRect[i] ) ) { _selectedKingdomObject = static_cast( i ); // Reset cursor updater since this UI element was clicked. _setCursor(); setRedraw(); break; } } for ( uint8_t objectId = KingdomObjectBrush::HEROES; objectId < KingdomObjectBrush::KINGDOM_OBJECTS_COUNT; ++objectId ) { if ( le.MousePressRight( _kingdomObjectButtonsRect[objectId] ) ) { showObjectTypeInfoText( _getKingdomObjectTypeName( objectId ) ); break; } } if ( le.MouseClickLeft( _kingdomObjectButtonsRect[KingdomObjectBrush::HEROES] ) ) { handleObjectMouseClick( Dialog::selectHeroType ); return res; } if ( le.MouseClickLeft( _kingdomObjectButtonsRect[KingdomObjectBrush::TOWNS] ) ) { handleObjectMouseClick( [this]( const int32_t /* type */ ) { int32_t type = -1; int32_t color = -1; getTownObjectProperties( type, color ); Dialog::selectTownType( type, color ); return _generateTownObjectProperties( type, color ); } ); return res; } } else if ( _selectedInstrument == Instrument::MONSTERS && le.MouseClickLeft( _rectInstrumentPanel ) ) { handleObjectMouseClick( Dialog::selectMonsterType ); setRedraw(); return res; } else if ( _selectedInstrument == Instrument::ERASE ) { for ( size_t i = 0; i < _eraseButtonsRect.size(); ++i ) { if ( le.MouseClickLeft( _eraseButtonsRect[i] ) ) { _eraseTypes ^= _eraseButtonObjectTypes[i]; setRedraw(); } else if ( le.MousePressRight( _eraseButtonsRect[i] ) ) { std::string header = _( "Toggle the erasure of %{type} objects." ); StringReplaceWithLowercase( header, "%{type}", _getEraseObjectTypeName( _eraseButtonObjectTypes[i] ) ); fheroes2::showStandardTextMessage( std::move( header ), ( _eraseButtonObjectTypes[i] & _eraseTypes ) ? _( "Objects of this type will be deleted with the Erase tool. Left-click here to deselect this type. Press and hold this button to deselect all other object types." ) : _( "Objects of this type will NOT be deleted with the Erase tool. Left-click here to select this type. Press and hold this button to select all other object types." ), Dialog::ZERO ); } else if ( le.MouseLongPressLeft( _eraseButtonsRect[i] ) ) { _eraseTypes = ( _eraseButtonObjectTypes[i] & _eraseTypes ) ? _eraseButtonObjectTypes[i] : ( Maps::ObjectErasureType::ALL_OBJECTS & ~_eraseButtonObjectTypes[i] ); setRedraw(); } } } le.MousePressLeft( _rectMagnify ) ? _buttonMagnify.drawOnPress() : _buttonMagnify.drawOnRelease(); le.MousePressLeft( _rectUndo ) ? _buttonUndo.drawOnPress() : _buttonUndo.drawOnRelease(); le.MousePressLeft( _rectNew ) ? _buttonNew.drawOnPress() : _buttonNew.drawOnRelease(); le.MousePressLeft( _rectSpecs ) ? _buttonSpecs.drawOnPress() : _buttonSpecs.drawOnRelease(); le.MousePressLeft( _rectFile ) ? _buttonFile.drawOnPress() : _buttonFile.drawOnRelease(); le.MousePressLeft( _rectSystem ) ? _buttonSystem.drawOnPress() : _buttonSystem.drawOnRelease(); if ( le.MouseClickLeft( _rectMagnify ) ) { _interface.eventViewWorld(); } else if ( _buttonUndo.isEnabled() && le.MouseClickLeft( _rectUndo ) ) { _interface.undoAction(); return fheroes2::GameMode::CANCEL; } else if ( le.MouseClickLeft( _rectNew ) ) { res = EditorInterface::eventNewMap(); } else if ( le.MouseClickLeft( _rectSpecs ) ) { // TODO: Make the scenario info editor. Dialog::GameInfo(); } else if ( le.MouseClickLeft( _rectFile ) ) { res = Interface::EditorInterface::eventFileDialog(); } else if ( le.MouseClickLeft( _rectSystem ) ) { // Replace this with Editor options dialog. fheroes2::showSystemOptionsDialog(); } else if ( le.MousePressRight( _instrumentButtonsRect[Instrument::TERRAIN] ) ) { fheroes2::showStandardTextMessage( _( "Terrain Mode" ), _( "Used to draw the underlying grass, dirt, water, etc. on the map." ), Dialog::ZERO ); } else if ( le.MousePressRight( _instrumentButtonsRect[Instrument::LANDSCAPE_OBJECTS] ) ) { fheroes2::showStandardTextMessage( _( "Landscape Objects Mode" ), _( "Used to place landscape objects (mountains, rocks, trees, etc.) on the map." ), Dialog::ZERO ); } else if ( le.MousePressRight( _instrumentButtonsRect[Instrument::DETAIL] ) ) { fheroes2::showStandardTextMessage( _( "Detail Mode" ), _( "Used for special editing of monsters, heroes and towns." ), Dialog::ZERO ); } else if ( le.MousePressRight( _instrumentButtonsRect[Instrument::ADVENTURE_OBJECTS] ) ) { fheroes2::showStandardTextMessage( _( "Adventure Objects Mode" ), _( "Used to place adventure objects (artifacts, dwellings, mines, treasures, etc.) on the map." ), Dialog::ZERO ); } else if ( le.MousePressRight( _instrumentButtonsRect[Instrument::KINGDOM_OBJECTS] ) ) { fheroes2::showStandardTextMessage( _( "Kingdom Objects Mode" ), _( "Used to place kingdom objects (towns, castles and heroes) on the map." ), Dialog::ZERO ); } else if ( le.MousePressRight( _instrumentButtonsRect[Instrument::MONSTERS] ) ) { fheroes2::showStandardTextMessage( _( "Monsters Mode" ), _( "Used to place monsters on the map." ), Dialog::ZERO ); } else if ( le.MousePressRight( _instrumentButtonsRect[Instrument::STREAM] ) ) { fheroes2::showStandardTextMessage( _( "Stream Mode" ), _( "Allows you to draw streams by clicking and dragging." ), Dialog::ZERO ); } else if ( le.MousePressRight( _instrumentButtonsRect[Instrument::ROAD] ) ) { fheroes2::showStandardTextMessage( _( "Road Mode" ), _( "Allows you to draw roads by clicking and dragging." ), Dialog::ZERO ); } else if ( le.MousePressRight( _instrumentButtonsRect[Instrument::ERASE] ) ) { fheroes2::showStandardTextMessage( _( "Erase Mode" ), _( "Used to erase objects from the map." ), Dialog::ZERO ); } else if ( le.MousePressRight( _rectMagnify ) ) { fheroes2::showStandardTextMessage( _( "Magnify" ), _( "Change between zoom and normal view." ), Dialog::ZERO ); } else if ( le.MousePressRight( _rectUndo ) ) { fheroes2::showStandardTextMessage( _( "Undo" ), _( "Undo your last action." ), Dialog::ZERO ); } else if ( le.MousePressRight( _rectNew ) ) { fheroes2::showStandardTextMessage( _( "New Map" ), _( "Create a new map either from scratch or using the random map generator." ), Dialog::ZERO ); } else if ( le.MousePressRight( _rectSpecs ) ) { fheroes2::showStandardTextMessage( _( "Specifications" ), _( "Edit map title, description, and other general information." ), Dialog::ZERO ); } else if ( le.MousePressRight( _rectFile ) ) { fheroes2::showStandardTextMessage( _( "File Options" ), _( "Open the file options menu, where you can save or load maps, or quit out of the editor." ), Dialog::ZERO ); } else if ( le.MousePressRight( _rectSystem ) ) { fheroes2::showStandardTextMessage( _( "System Options" ), _( "View the editor system options, which let you customize the editor." ), Dialog::ZERO ); } return res; } void EditorPanel::handleObjectMouseClick( const std::function & typeSelection ) { const int type = typeSelection( getSelectedObjectType() ); if ( type >= 0 ) { switch ( _selectedInstrument ) { case Instrument::MONSTERS: _selectedMonsterType = type; break; case Instrument::LANDSCAPE_OBJECTS: _selectedLandscapeObjectType[_selectedLandscapeObject] = type; break; case Instrument::ADVENTURE_OBJECTS: _selectedAdventureObjectType[_selectedAdventureObject] = type; break; case Instrument::KINGDOM_OBJECTS: _selectedKingdomObjectType[_selectedKingdomObject] = type; break; default: // Why are you trying to get type for the non-object instrument. Check your logic! assert( 0 ); } } _setCursor(); _interface.updateCursor( 0 ); } void EditorPanel::getTownObjectProperties( int32_t & type, int32_t & color ) const { const auto & townObjects = Maps::getObjectsByGroup( Maps::ObjectGroup::KINGDOM_TOWNS ); if ( townObjects.empty() ) { // How is it even possible? assert( 0 ); type = -1; color = -1; return; } type = _selectedKingdomObjectType[KingdomObjectBrush::TOWNS] % static_cast( townObjects.size() ); color = _selectedKingdomObjectType[KingdomObjectBrush::TOWNS] / static_cast( townObjects.size() ); } int32_t EditorPanel::_generateTownObjectProperties( const int32_t type, const int32_t color ) { const auto & townObjects = Maps::getObjectsByGroup( Maps::ObjectGroup::KINGDOM_TOWNS ); if ( townObjects.empty() ) { // How is it even possible? assert( 0 ); return -1; } return color * static_cast( townObjects.size() ) + type; } void EditorPanel::getMineObjectProperties( int32_t & type, int32_t & color ) const { const auto & mineObjects = Maps::getObjectsByGroup( Maps::ObjectGroup::ADVENTURE_MINES ); if ( mineObjects.empty() ) { // How is it even possible? assert( 0 ); type = -1; color = -1; return; } type = _selectedAdventureObjectType[AdventureObjectBrush::MINES] % static_cast( mineObjects.size() ); color = _selectedAdventureObjectType[AdventureObjectBrush::MINES] / static_cast( mineObjects.size() ); } int32_t EditorPanel::_generateMineObjectProperties( const int32_t type, const int32_t color ) { const auto & mineObjects = Maps::getObjectsByGroup( Maps::ObjectGroup::ADVENTURE_MINES ); if ( mineObjects.empty() ) { // How is it even possible? assert( 0 ); return -1; } const int32_t objectType = ( color * static_cast( mineObjects.size() ) + type ); return ( objectType < 0 ) ? -1 : objectType; } } fheroes2-1.0.12+dfsg/src/fheroes2/editor/editor_interface_panel.h000066400000000000000000000304061456075706000246740ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include #include "game_mode.h" #include "ground.h" #include "image.h" #include "map_object_info.h" #include "maps_tiles_helper.h" #include "math_base.h" #include "ui_button.h" namespace Interface { class EditorInterface; class EditorPanel { public: explicit EditorPanel( EditorInterface & interface_ ); ~EditorPanel() = default; const fheroes2::Rect & getRect() const { return _rectEditorPanel; } fheroes2::Rect getBrushArea() const; int selectedGroundType() const { return _getGroundId( _selectedTerrain ); } bool isTerrainEdit() const { return _selectedInstrument == Instrument::TERRAIN; } bool isRoadDraw() const { return _selectedInstrument == Instrument::ROAD; } bool isStreamDraw() const { return _selectedInstrument == Instrument::STREAM; } bool isEraseMode() const { return _selectedInstrument == Instrument::ERASE; } bool isObjectMode() const { return _selectedInstrument == Instrument::LANDSCAPE_OBJECTS || _selectedInstrument == Instrument::ADVENTURE_OBJECTS || _selectedInstrument == Instrument::KINGDOM_OBJECTS || _selectedInstrument == Instrument::MONSTERS; } uint32_t getEraseTypes() const { return _eraseTypes; } bool showAreaSelectRect() const { return _selectedInstrument == Instrument::TERRAIN || _selectedInstrument == Instrument::STREAM || _selectedInstrument == Instrument::ROAD || _selectedInstrument == Instrument::ERASE || isObjectMode() || _selectedInstrument == Instrument::DETAIL; } bool useMouseDragMovement() const { return !( ( _selectedInstrument == Instrument::TERRAIN || _selectedInstrument == Instrument::ERASE ) && _selectedBrushSize == BrushSize::AREA ); } // Set Editor panel positions on screen. void setPos( const int32_t displayX, int32_t displayY ); // Set flag to redraw Editor buttons panel on the next interface render. void setRedraw() const; fheroes2::GameMode queueEventProcessing(); // Do not call this method directly, use Interface::Editor::redraw() instead. // The name of this method starts from _ on purpose to do not mix with other public methods. void _redraw() const; int32_t getSelectedObjectType() const; Maps::ObjectGroup getSelectedObjectGroup() const; void getTownObjectProperties( int32_t & type, int32_t & color ) const; void getMineObjectProperties( int32_t & type, int32_t & color ) const; private: static int _getGroundId( const uint8_t brushId ); static const char * _getTerrainTypeName( const uint8_t brushId ) { return Maps::Ground::String( _getGroundId( brushId ) ); } static const char * _getLandscapeObjectTypeName( const uint8_t brushId ); static const char * _getAdventureObjectTypeName( const uint8_t brushId ); static const char * _getKingdomObjectTypeName( const uint8_t brushId ); static const char * _getEraseObjectTypeName( const uint32_t eraseObjectType ); static int32_t _generateTownObjectProperties( const int32_t type, const int32_t color ); static int32_t _generateMineObjectProperties( const int32_t type, const int32_t color ); void _setCursor(); void handleObjectMouseClick( const std::function & typeSelection ); enum Instrument : uint8_t { // IMPORTANT. This enumeration corresponds with the order of instruments in original assets. Do not change this order. TERRAIN = 0U, LANDSCAPE_OBJECTS = 1U, DETAIL = 2U, ADVENTURE_OBJECTS = 3U, KINGDOM_OBJECTS = 4U, MONSTERS = 5U, STREAM = 6U, ROAD = 7U, ERASE = 8U, // The last element corresponds to the editor instruments count. INSTRUMENTS_COUNT = 9U }; enum TerrainBrush : uint8_t { // IMPORTANT. This enumeration corresponds with the order of instruments in original assets. Do not change this order. WATER = 0U, GRASS = 1U, SNOW = 2U, SWAMP = 3U, LAVA = 4U, DESERT = 5U, DIRT = 6U, WASTELAND = 7U, BEACH = 8U, // The last element corresponds to the editor terrain types count. TERRAIN_COUNT = 9U, }; enum LandscapeObjectBrush : int8_t { MOUNTAINS = 0, ROCKS = 1, TREES = 2, WATER_OBJECTS = 3, LANDSCAPE_MISC = 4, LANDSCAPE_COUNT = 5, }; enum AdventureObjectBrush : int8_t { ARTIFACTS = 0U, DWELLINGS = 1U, MINES = 2U, POWER_UPS = 3U, TREASURES = 4U, WATER_ADVENTURE = 5U, ADVENTURE_MISC = 6U, ADVENTURE_COUNT = 7U, }; enum KingdomObjectBrush : int8_t { HEROES = 0U, TOWNS = 1U, KINGDOM_OBJECTS_COUNT = 2U, }; enum BrushSize : uint8_t { // IMPORTANT. This enumeration corresponds with the order of brush size in original assets. Do not change this order. SMALL = 0U, MEDIUM = 1U, LARGE = 2U, AREA = 3U, // The last element corresponds to the editor brush size count. BRUSH_SIZE_COUNT = 4U }; // This array represents the order of object-to-erase images on the erase tool panel (from left to right, from top to bottom). const std::array _eraseButtonObjectTypes{ Maps::ObjectErasureType::TERRAIN_OBJECTS, Maps::ObjectErasureType::CASTLES, Maps::ObjectErasureType::MONSTERS, Maps::ObjectErasureType::HEROES, Maps::ObjectErasureType::ARTIFACTS, Maps::ObjectErasureType::ROADS, Maps::ObjectErasureType::STREAMS, Maps::ObjectErasureType::TREASURES }; EditorInterface & _interface; fheroes2::Button _buttonMagnify; fheroes2::Button _buttonUndo; fheroes2::Button _buttonNew; fheroes2::Button _buttonSpecs; fheroes2::Button _buttonFile; fheroes2::Button _buttonSystem; fheroes2::Rect _rectMagnify; fheroes2::Rect _rectUndo; fheroes2::Rect _rectNew; fheroes2::Rect _rectSpecs; fheroes2::Rect _rectFile; fheroes2::Rect _rectSystem; fheroes2::Rect _rectInstruments; fheroes2::Rect _rectInstrumentPanel; fheroes2::Rect _rectEditorPanel; fheroes2::Image _instrumentPanelBackground; std::array _instrumentButtons; std::array _brushSizeButtons; std::array _instrumentButtonsRect; std::array _terrainButtonsRect; std::array _landscapeObjectButtonsRect; std::array _adventureObjectButtonsRect; std::array _kingdomObjectButtonsRect; std::array _brushSizeButtonsRect; std::array _eraseButtonsRect; uint8_t _selectedInstrument{ Instrument::TERRAIN }; // A brand new map is always filled with Water so there is no need to make Water terrain brush as a default terrain selection. uint8_t _selectedTerrain{ TerrainBrush::GRASS }; int8_t _selectedLandscapeObject{ -1 }; int8_t _selectedAdventureObject{ -1 }; int8_t _selectedKingdomObject{ -1 }; uint8_t _selectedBrushSize{ BrushSize::MEDIUM }; uint32_t _eraseTypes{ Maps::ObjectErasureType::ALL_OBJECTS }; std::array _selectedLandscapeObjectType; std::array _selectedAdventureObjectType; std::array _selectedKingdomObjectType; int32_t _selectedMonsterType{ -1 }; const std::array _selectedLandscapeObjectGroup{ Maps::ObjectGroup::LANDSCAPE_MOUNTAINS, Maps::ObjectGroup::LANDSCAPE_ROCKS, Maps::ObjectGroup::LANDSCAPE_TREES, Maps::ObjectGroup::LANDSCAPE_WATER, Maps::ObjectGroup::LANDSCAPE_MISCELLANEOUS }; const std::array _selectedAdventureObjectGroup{ Maps::ObjectGroup::ADVENTURE_ARTIFACTS, Maps::ObjectGroup::ADVENTURE_DWELLINGS, Maps::ObjectGroup::ADVENTURE_MINES, Maps::ObjectGroup::ADVENTURE_POWER_UPS, Maps::ObjectGroup::ADVENTURE_TREASURES, Maps::ObjectGroup::ADVENTURE_WATER, Maps::ObjectGroup::ADVENTURE_MISCELLANEOUS }; const std::array _selectedKingdomObjectGroup{ Maps::ObjectGroup::KINGDOM_HEROES, Maps::ObjectGroup::KINGDOM_TOWNS }; }; } fheroes2-1.0.12+dfsg/src/fheroes2/editor/editor_mainmenu.cpp000066400000000000000000000325531456075706000237260ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include "audio.h" #include "audio_manager.h" #include "cursor.h" #include "dialog.h" #include "dialog_selectscenario.h" #include "editor.h" #include "editor_interface.h" #include "game.h" #include "game_hotkeys.h" #include "game_mainmenu_ui.h" #include "game_mode.h" #include "icn.h" #include "localevent.h" #include "logging.h" #include "maps.h" #include "maps_fileinfo.h" #include "math_base.h" #include "mus.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_tool.h" #include "world.h" #include "world_object_uid.h" namespace { const int32_t buttonYStep = 66; const size_t mapSizeCount = 4; const std::array mapSizeHotkeys = { Game::HotKeyEvent::MAIN_MENU_MAP_SIZE_SMALL, Game::HotKeyEvent::MAIN_MENU_MAP_SIZE_MEDIUM, Game::HotKeyEvent::MAIN_MENU_MAP_SIZE_LARGE, Game::HotKeyEvent::MAIN_MENU_MAP_SIZE_EXTRA_LARGE }; const std::array mapSizes = { Maps::SMALL, Maps::MEDIUM, Maps::LARGE, Maps::XLARGE }; void outputEditorMainMenuInTextSupportMode() { START_TEXT_SUPPORT_MODE COUT( "Map Editor\n" ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::EDITOR_NEW_MAP_MENU ) << " to create a new map either from scratch or using the random map generator." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::EDITOR_LOAD_MAP_MENU ) << " to load an existing map." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::DEFAULT_CANCEL ) << " to go back to Main Menu." ) } void outputEditorNewMapMenuInTextSupportMode() { START_TEXT_SUPPORT_MODE COUT( "New Map\n" ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::EDITOR_FROM_SCRATCH_MAP_MENU ) << " to create a blank map from scratch." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::EDITOR_RANDOM_MAP_MENU ) << " to create a randomly generated map." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::DEFAULT_CANCEL ) << " to go back to Map Editor main menu." ) } void outputEditorMapSizeMenuInTextSupportMode() { START_TEXT_SUPPORT_MODE COUT( "Map Size\n" ) const size_t buttonCount = mapSizeHotkeys.size(); for ( size_t i = 0; i < buttonCount; ++i ) { std::string message = " to create a map that is %{size} squares wide and %{size} squares high."; StringReplace( message, "%{size}", std::to_string( mapSizes[i] ) ); COUT( "Press " << Game::getHotKeyNameByEventId( mapSizeHotkeys[i] ) << message ) } COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::DEFAULT_CANCEL ) << " to go back to New Map menu." ) } void showWIPInfo() { fheroes2::showStandardTextMessage( _( "Warning!" ), "The Map Editor is still in development. This function is not available yet.", Dialog::OK ); } Maps::mapsize_t selectMapSize() { outputEditorMapSizeMenuInTextSupportMode(); const CursorRestorer cursorRestorer( true, Cursor::POINTER ); fheroes2::drawEditorMainMenuScreen(); const fheroes2::Point buttonPos = fheroes2::drawButtonPanel(); std::array buttons; for ( uint32_t i = 0; i < mapSizeCount; ++i ) { buttons[i].setICNInfo( ICN::BTNESIZE, 0 + i * 2, 1 + i * 2 ); buttons[i].setPosition( buttonPos.x, buttonPos.y + buttonYStep * static_cast( i ) ); buttons[i].draw(); } fheroes2::Button cancel( buttonPos.x, buttonPos.y + 5 * buttonYStep, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); cancel.draw(); fheroes2::validateFadeInAndRender(); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { for ( size_t i = 0; i < mapSizeCount; ++i ) { le.MousePressLeft( buttons[i].area() ) ? buttons[i].drawOnPress() : buttons[i].drawOnRelease(); if ( le.MouseClickLeft( buttons[i].area() ) || Game::HotKeyPressEvent( mapSizeHotkeys[i] ) ) { return mapSizes[i]; } if ( le.MousePressRight( buttons[i].area() ) ) { std::string mapSize = std::to_string( mapSizes[i] ); std::string message = _( "Create a map that is %{size} squares wide and %{size} squares high." ); StringReplace( message, "%{size}", mapSize ); mapSize += " x " + mapSize; fheroes2::showStandardTextMessage( mapSize, message, Dialog::ZERO ); } } le.MousePressLeft( cancel.area() ) ? cancel.drawOnPress() : cancel.drawOnRelease(); if ( le.MouseClickLeft( cancel.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { return Maps::ZERO; } if ( le.MousePressRight( cancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the New Map menu." ), Dialog::ZERO ); } } return Maps::ZERO; } } namespace Editor { fheroes2::GameMode menuMain() { outputEditorMainMenuInTextSupportMode(); // Stop all sounds, but not the music AudioManager::stopSounds(); AudioManager::PlayMusicAsync( MUS::MAINMENU, Music::PlaybackMode::RESUME_AND_PLAY_INFINITE ); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); fheroes2::drawEditorMainMenuScreen(); const fheroes2::Point buttonPos = fheroes2::drawButtonPanel(); fheroes2::Button newMap( buttonPos.x, buttonPos.y, ICN::BTNEMAIN, 0, 1 ); fheroes2::Button loadMap( buttonPos.x, buttonPos.y + buttonYStep, ICN::BTNEMAIN, 2, 3 ); fheroes2::Button cancel( buttonPos.x, buttonPos.y + 5 * buttonYStep, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); newMap.draw(); loadMap.draw(); cancel.draw(); fheroes2::validateFadeInAndRender(); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { le.MousePressLeft( newMap.area() ) ? newMap.drawOnPress() : newMap.drawOnRelease(); le.MousePressLeft( loadMap.area() ) ? loadMap.drawOnPress() : loadMap.drawOnRelease(); le.MousePressLeft( cancel.area() ) ? cancel.drawOnPress() : cancel.drawOnRelease(); if ( le.MouseClickLeft( newMap.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::EDITOR_NEW_MAP_MENU ) ) { return fheroes2::GameMode::EDITOR_NEW_MAP; } if ( le.MouseClickLeft( loadMap.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::EDITOR_LOAD_MAP_MENU ) ) { return fheroes2::GameMode::EDITOR_LOAD_MAP; } if ( le.MouseClickLeft( cancel.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { return fheroes2::GameMode::MAIN_MENU; } if ( le.MousePressRight( newMap.area() ) ) { fheroes2::showStandardTextMessage( _( "New Map" ), _( "Create a new map either from scratch or using the random map generator." ), Dialog::ZERO ); } else if ( le.MousePressRight( loadMap.area() ) ) { fheroes2::showStandardTextMessage( _( "Load Map" ), _( "Load an existing map." ), Dialog::ZERO ); } else if ( le.MousePressRight( cancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the main menu." ), Dialog::ZERO ); } } return fheroes2::GameMode::MAIN_MENU; } fheroes2::GameMode menuNewMap() { outputEditorNewMapMenuInTextSupportMode(); const CursorRestorer cursorRestorer( true, Cursor::POINTER ); fheroes2::drawEditorMainMenuScreen(); const fheroes2::Point buttonPos = fheroes2::drawButtonPanel(); fheroes2::Button scratchMap( buttonPos.x, buttonPos.y, ICN::BTNENEW, 0, 1 ); fheroes2::Button randomMap( buttonPos.x, buttonPos.y + buttonYStep, ICN::BTNENEW, 2, 3 ); fheroes2::Button cancel( buttonPos.x, buttonPos.y + 5 * buttonYStep, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); // TODO: enable it back once random map generator is ready. randomMap.disable(); scratchMap.draw(); randomMap.draw(); cancel.draw(); fheroes2::validateFadeInAndRender(); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { le.MousePressLeft( scratchMap.area() ) ? scratchMap.drawOnPress() : scratchMap.drawOnRelease(); if ( randomMap.isEnabled() ) { le.MousePressLeft( randomMap.area() ) ? randomMap.drawOnPress() : randomMap.drawOnRelease(); } le.MousePressLeft( cancel.area() ) ? cancel.drawOnPress() : cancel.drawOnRelease(); if ( le.MouseClickLeft( scratchMap.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::EDITOR_FROM_SCRATCH_MAP_MENU ) ) { const Maps::mapsize_t mapSize = selectMapSize(); if ( mapSize != Maps::ZERO ) { world.generateForEditor( mapSize ); // Reset object UID to keep track of newly added objects. Maps::resetObjectUID(); fheroes2::fadeOutDisplay(); Game::setDisplayFadeIn(); return Interface::EditorInterface::Get().startEdit( true ); } return fheroes2::GameMode::EDITOR_NEW_MAP; } if ( randomMap.isEnabled() && ( le.MouseClickLeft( randomMap.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::EDITOR_RANDOM_MAP_MENU ) ) ) { if ( selectMapSize() != Maps::ZERO ) { showWIPInfo(); } return fheroes2::GameMode::EDITOR_NEW_MAP; } if ( le.MouseClickLeft( cancel.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { return fheroes2::GameMode::EDITOR_MAIN_MENU; } if ( le.MousePressRight( scratchMap.area() ) ) { fheroes2::showStandardTextMessage( _( "From Scratch" ), _( "Start from scratch with a blank map." ), Dialog::ZERO ); } else if ( le.MousePressRight( randomMap.area() ) ) { fheroes2::showStandardTextMessage( _( "Random" ), _( "Create a randomly generated map." ), Dialog::ZERO ); } else if ( le.MousePressRight( cancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the Map Editor main menu." ), Dialog::ZERO ); } } return fheroes2::GameMode::EDITOR_MAIN_MENU; } fheroes2::GameMode menuLoadMap() { const CursorRestorer cursorRestorer( true, Cursor::POINTER ); fheroes2::drawEditorMainMenuScreen(); fheroes2::validateFadeInAndRender(); const MapsFileInfoList lists = Maps::getResurrectionMapFileInfos( true, false ); if ( lists.empty() ) { fheroes2::showStandardTextMessage( _( "Warning" ), _( "No maps available!" ), Dialog::OK ); return fheroes2::GameMode::EDITOR_MAIN_MENU; } const Maps::FileInfo * fileInfo = Dialog::SelectScenario( lists ); if ( fileInfo == nullptr ) { return fheroes2::GameMode::EDITOR_MAIN_MENU; } Interface::EditorInterface & editorInterface = Interface::EditorInterface::Get(); if ( !editorInterface.loadMap( fileInfo->filename ) ) { return fheroes2::GameMode::CANCEL; } fheroes2::fadeOutDisplay(); Game::setDisplayFadeIn(); return Interface::EditorInterface::Get().startEdit( false ); } } fheroes2-1.0.12+dfsg/src/fheroes2/editor/editor_object_popup_window.cpp000066400000000000000000000061101456075706000261630ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "editor_object_popup_window.h" #include #include #include #include #include #include "editor_interface.h" #include "ground.h" #include "interface_gamearea.h" #include "logging.h" #include "maps_tiles.h" #include "maps_tiles_helper.h" #include "mp2.h" #include "resource.h" #include "translations.h" #include "ui_map_interface.h" #include "world.h" namespace { std::string getObjectInfoText( const Maps::Tiles & tile ) { const MP2::MapObjectType type = tile.GetObject(); switch ( type ) { case MP2::OBJ_NONE: if ( tile.isRoad() ) { return _( "Road" ); } return Maps::Ground::String( tile.GetGround() ); case MP2::OBJ_RESOURCE: { const Funds funds = getFundsFromTile( tile ); assert( funds.GetValidItemsCount() == 1 ); return Resource::String( funds.getFirstValidResource().first ); } default: break; } return MP2::StringObject( type ); } } namespace Editor { void showPopupWindow( const Maps::Tiles & tile ) { DEBUG_LOG( DBG_DEVEL, DBG_INFO, '\n' << tile.String() ) std::string infoString; const int32_t mainTileIndex = Maps::Tiles::getIndexOfMainTile( tile ); if ( mainTileIndex != -1 ) { infoString = getObjectInfoText( world.GetTiles( mainTileIndex ) ); } else { infoString = getObjectInfoText( tile ); } Interface::displayStandardPopupWindow( std::move( infoString ), Interface::EditorInterface::Get().getGameArea().GetROI() ); } } fheroes2-1.0.12+dfsg/src/fheroes2/editor/editor_object_popup_window.h000066400000000000000000000030671456075706000256400ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once namespace Maps { class Tiles; } namespace Editor { void showPopupWindow( const Maps::Tiles & tile ); } fheroes2-1.0.12+dfsg/src/fheroes2/editor/history_manager.cpp000066400000000000000000000106211456075706000237320ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "history_manager.h" #include #include #include "map_format_helper.h" #include "map_format_info.h" #include "world_object_uid.h" namespace { // This class holds 2 copies of MapFormat objects: // - one copy before the action // - one copy after the action // Ideally, we need to store only difference between before and after an action // but for simplification purposes we chose this way. // TODO: optimize this class (if possible) to store less data. class MapAction : public fheroes2::Action { public: explicit MapAction( Maps::Map_Format::MapFormat & mapFormat ) : _mapFormat( mapFormat ) , _latestObjectUIDBefore( Maps::getLastObjectUID() ) { if ( !Maps::saveMapInEditor( _mapFormat ) ) { // If this assertion blows up then something is really wrong with the Editor. assert( 0 ); } _beforeMapFormat = _mapFormat; } bool prepare() { if ( !Maps::saveMapInEditor( _mapFormat ) ) { // If this assertion blows up then something is really wrong with the Editor. assert( 0 ); return false; } _afterMapFormat = _mapFormat; return true; } bool redo() override { _mapFormat = _afterMapFormat; if ( !Maps::readMapInEditor( _mapFormat ) ) { // If this assertion blows up then something is really wrong with the Editor. assert( 0 ); return false; } Maps::setLastObjectUID( _latestObjectUIDAfter ); return true; } bool undo() override { _mapFormat = _beforeMapFormat; if ( !Maps::readMapInEditor( _mapFormat ) ) { // If this assertion blows up then something is really wrong with the Editor. assert( 0 ); return false; } Maps::setLastObjectUID( _latestObjectUIDBefore ); return true; } private: Maps::Map_Format::MapFormat & _mapFormat; Maps::Map_Format::MapFormat _beforeMapFormat; Maps::Map_Format::MapFormat _afterMapFormat; const uint32_t _latestObjectUIDBefore{ 0 }; uint32_t _latestObjectUIDAfter{ 0 }; }; } namespace fheroes2 { ActionCreator::ActionCreator( HistoryManager & manager, Maps::Map_Format::MapFormat & mapFormat ) : _manager( manager ) { _action = std::make_unique( mapFormat ); } void ActionCreator::commit() { auto * action = dynamic_cast( _action.get() ); if ( action == nullptr ) { // How is it even possible? Did you call this method twice? assert( 0 ); return; } if ( action->prepare() ) { _manager.add( std::move( _action ) ); } } } fheroes2-1.0.12+dfsg/src/fheroes2/editor/history_manager.h000066400000000000000000000077501456075706000234100ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include #include #include #include namespace Maps::Map_Format { struct MapFormat; } namespace fheroes2 { class HistoryManager; class Action { public: virtual ~Action() = default; virtual bool redo() = 0; virtual bool undo() = 0; }; // Remember the map state and create an action if the map has changed. class ActionCreator { public: explicit ActionCreator( HistoryManager & manager, Maps::Map_Format::MapFormat & mapFormat ); ~ActionCreator() { if ( _action ) { // The action wasn't committed. Undo all the changes. _action->undo(); } } ActionCreator( const ActionCreator & ) = delete; ActionCreator & operator=( const ActionCreator & ) = delete; void commit(); private: HistoryManager & _manager; std::unique_ptr _action; }; class HistoryManager { public: void reset() { _actions.clear(); _lastActionId = 0; } void add( std::unique_ptr action ) { _actions.resize( _lastActionId ); _actions.push_back( std::move( action ) ); ++_lastActionId; if ( _actions.size() > maxActions ) { --_lastActionId; _actions.pop_front(); } assert( _actions.size() <= maxActions ); } bool undo() { if ( _lastActionId == 0 ) { // Nothing to do. return false; } --_lastActionId; return _actions[_lastActionId]->undo(); } bool redo() { if ( _lastActionId == _actions.size() ) { // Nothing to do. return false; } const bool result = _actions[_lastActionId]->redo(); ++_lastActionId; return result; } bool isUndoAvailable() const { return ( _lastActionId > 0 ); } bool isRedoAvailable() const { return ( _lastActionId < _actions.size() ); } private: // We shouldn't store too many actions. It is extremely rare when there is a need to revert so many changes. static const size_t maxActions{ 500 }; std::deque> _actions; size_t _lastActionId{ 0 }; }; } fheroes2-1.0.12+dfsg/src/fheroes2/game/000077500000000000000000000000001456075706000174565ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/fheroes2/game/difficulty.cpp000066400000000000000000000163741456075706000223370ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "difficulty.h" #include #include "translations.h" std::string Difficulty::String( int difficulty ) { switch ( difficulty ) { case Difficulty::EASY: return _( "difficulty|Easy" ); case Difficulty::NORMAL: return _( "difficulty|Normal" ); case Difficulty::HARD: return _( "difficulty|Hard" ); case Difficulty::EXPERT: return _( "difficulty|Expert" ); case Difficulty::IMPOSSIBLE: return _( "difficulty|Impossible" ); default: break; } return "Unknown"; } int Difficulty::GetScoutingBonusForAI( int difficulty ) { switch ( difficulty ) { case Difficulty::NORMAL: return 1; case Difficulty::HARD: return 2; case Difficulty::EXPERT: return 3; case Difficulty::IMPOSSIBLE: return 4; default: break; } return 0; } double Difficulty::getGoldIncomeBonusForAI( const int difficulty ) { switch ( difficulty ) { case Difficulty::EASY: // It is deduction from the income. return -0.25; case Difficulty::HARD: return 0.29; case Difficulty::EXPERT: return 0.45; case Difficulty::IMPOSSIBLE: return 0.6; default: break; } return 0; } double Difficulty::GetUnitGrowthBonusForAI( const int difficulty, const bool /* isCampaign */, const building_t /* dwelling */ ) { // In the original game AI has a cheeky monster growth bonus depending on difficulty: // Easy - 0.0 (no bonus) // Normal - 0.0 (no bonus) // Hard - 0.20 (or 20% extra) // Expert - 0.32 (or 32% extra) // Impossible - 0.44 (or 44% extra) // This bonus was introduced to compensate weak AI in the game. // // However, with introduction of proper AI in this engine AI has become much stronger and some maps are impossible to beat. // Also this bonus can be abused by players while capturing AI castles on a first day of a week. // // Completely removing these bonuses might break some maps and they become unplayable. // Therefore, these bonuses are reduced by 5% which is the value of noise in many processes / systems. switch ( difficulty ) { case Difficulty::EASY: case Difficulty::NORMAL: return 0; case Difficulty::HARD: return 0.14; case Difficulty::EXPERT: return 0.254; case Difficulty::IMPOSSIBLE: return 0.368; default: // Did you add a new difficulty level? Add the logic above! assert( 0 ); break; } return 0; } int Difficulty::GetHeroMovementBonusForAI( int difficulty ) { switch ( difficulty ) { case Difficulty::EXPERT: case Difficulty::IMPOSSIBLE: return 75; default: break; } return 0; } bool Difficulty::allowAIToRetreat( const int /* difficulty */, const bool /* isCampaign */ ) { return true; } bool Difficulty::allowAIToSurrender( const int /* difficulty */, const bool /* isCampaign */ ) { return true; } int Difficulty::getMinHeroLevelForAIRetreat( const int /* difficulty */ ) { return 3; } double Difficulty::getArmyStrengthRatioForAIRetreat( const int difficulty ) { switch ( difficulty ) { case Difficulty::NORMAL: return 100.0 / 7.5; case Difficulty::HARD: case Difficulty::EXPERT: return 100.0 / 8.5; case Difficulty::IMPOSSIBLE: return 100.0 / 10.0; default: break; } return 100.0 / 6.0; } uint32_t Difficulty::getGoldReserveRatioForAISurrender( const int /* difficulty */ ) { return 10; } uint32_t Difficulty::GetDimensionDoorLimitForAI( int difficulty ) { switch ( difficulty ) { case Difficulty::EASY: return 1; case Difficulty::NORMAL: return 2; case Difficulty::HARD: return 3; default: break; } return UINT32_MAX; } bool Difficulty::areAIHeroRolesAllowed( const int difficulty ) { switch ( difficulty ) { case Difficulty::EASY: return false; case Difficulty::NORMAL: case Difficulty::HARD: case Difficulty::EXPERT: case Difficulty::IMPOSSIBLE: return true; default: // Did you add a new difficulty level? Add the logic above! assert( 0 ); break; } return true; } int Difficulty::getMinStatDiffForAIHeroesMeeting( const int difficulty ) { switch ( difficulty ) { case Difficulty::EASY: // Easy difficulty still allows to merge armies but only if the difference in stats is huge. return 10; case Difficulty::NORMAL: case Difficulty::HARD: case Difficulty::EXPERT: case Difficulty::IMPOSSIBLE: return 2; default: // Did you add a new difficulty level? Add the logic above! assert( 0 ); break; } return 2; } bool Difficulty::allowAIToSplitWeakStacks( const int difficulty ) { switch ( difficulty ) { case Difficulty::EASY: case Difficulty::NORMAL: return false; default: break; } return true; } bool Difficulty::allowAIToDevelopCastlesOnDay( const int difficulty, const bool isCampaign, const uint32_t day ) { switch ( difficulty ) { case Difficulty::EASY: return isCampaign || day % 2 == 0; default: break; } return true; } bool Difficulty::allowAIToBuildCastleBuilding( const int difficulty, const bool isCampaign, const building_t building ) { switch ( difficulty ) { case Difficulty::EASY: // Only the construction of the corresponding dwelling is limited, but not its upgrade return isCampaign || building != DWELLING_MONSTER6; default: break; } return true; } fheroes2-1.0.12+dfsg/src/fheroes2/game/difficulty.h000066400000000000000000000100651456075706000217730ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2DIFFICULTY_H #define H2DIFFICULTY_H #include #include #include "castle.h" namespace Difficulty { // !!! IMPORTANT !!! // Do NOT change the order of the items as they are used for the map format. enum DifficultyLevel : int { EASY, NORMAL, HARD, EXPERT, IMPOSSIBLE }; std::string String( int ); int GetScoutingBonusForAI( int difficulty ); // Returns an extra gold bonus modifier for AI based on difficulty level. double getGoldIncomeBonusForAI( const int difficulty ); // Returns an extra growth bonus modifier for AI based on difficulty level. double GetUnitGrowthBonusForAI( const int difficulty, const bool isCampaign, const building_t dwelling ); int GetHeroMovementBonusForAI( int difficulty ); bool allowAIToRetreat( const int difficulty, const bool isCampaign ); bool allowAIToSurrender( const int difficulty, const bool isCampaign ); // Returns the minimum hero level at which the AI can consider the possibility of surrender or retreat from the battlefield for this hero int getMinHeroLevelForAIRetreat( const int difficulty ); // Returns the ratio of the strength of the enemy army to the strength of the AI army, above which the AI decides to surrender or retreat from the battlefield double getArmyStrengthRatioForAIRetreat( const int difficulty ); // Returns the minimum ratio of the AI kingdom's gold reserve to the cost of surrender, at which the AI will prefer surrender to retreat from the battlefield uint32_t getGoldReserveRatioForAISurrender( const int difficulty ); uint32_t GetDimensionDoorLimitForAI( int difficulty ); bool areAIHeroRolesAllowed( const int difficulty ); // Returns the minimum advantage in stats (i.e. the sum of the levels of primary and secondary skills) that a hero must have in order to allow another hero with the // same role to meet on his own initiative with this hero to exchange armies and artifacts int getMinStatDiffForAIHeroesMeeting( const int difficulty ); // Returns true if AI should avoid having free slots in the army bool allowAIToSplitWeakStacks( const int difficulty ); bool allowAIToDevelopCastlesOnDay( const int difficulty, const bool isCampaign, const uint32_t day ); bool allowAIToBuildCastleBuilding( const int difficulty, const bool isCampaign, const building_t building ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/game/fheroes2.cpp000066400000000000000000000304501456075706000217010ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include // IWYU pragma: keep #include #if defined( _WIN32 ) #include #endif #include "agg.h" #include "agg_image.h" #include "audio_manager.h" #include "bin_info.h" #include "core.h" #include "cursor.h" #include "dir.h" #include "embedded_image.h" #include "exception.h" #include "game.h" #include "game_logo.h" #include "game_video.h" #include "game_video_type.h" #include "h2d.h" #include "icn.h" #include "image.h" #include "image_palette.h" #include "localevent.h" #include "logging.h" #include "render_processor.h" #include "screen.h" #include "settings.h" #include "system.h" #include "timing.h" #include "ui_tool.h" #include "zzlib.h" namespace { std::string GetCaption() { return std::string( "fheroes2 engine, version: " + Settings::GetVersion() ); } void ReadConfigs() { const std::string configurationFileName( Settings::configFileName ); const std::string confFile = Settings::GetLastFile( "", configurationFileName ); Settings & conf = Settings::Get(); if ( System::IsFile( confFile ) && conf.Read( confFile ) ) { LocalEvent::Get().SetControllerPointerSpeed( conf.controllerPointerSpeed() ); } else { conf.Save( configurationFileName ); // Fullscreen mode can be enabled by default for some devices, we need to forcibly // synchronize reality with the default config if config file was not read conf.setFullScreen( conf.FullScreen() ); } } void InitConfigDir() { const std::string configDir = System::GetConfigDirectory( "fheroes2" ); if ( !System::IsDirectory( configDir ) ) { System::MakeDirectory( configDir ); } } void InitDataDir() { const std::string dataDir = System::GetDataDirectory( "fheroes2" ); if ( dataDir.empty() ) return; const std::string dataFiles = System::concatPath( dataDir, "files" ); const std::string dataFilesSave = System::concatPath( dataFiles, "save" ); if ( !System::IsDirectory( dataDir ) ) System::MakeDirectory( dataDir ); if ( System::IsDirectory( dataDir, true ) && !System::IsDirectory( dataFiles ) ) System::MakeDirectory( dataFiles ); if ( System::IsDirectory( dataFiles, true ) && !System::IsDirectory( dataFilesSave ) ) System::MakeDirectory( dataFilesSave ); } void displayMissingResourceWindow() { fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::Image & image = CreateImageFromZlib( 290, 190, errorMessage, sizeof( errorMessage ), false ); display.fill( 0 ); fheroes2::Resize( image, display ); display.render(); LocalEvent & le = LocalEvent::Get(); // Display the message for 5 seconds so that the user sees it enough and not immediately closes without reading properly. const fheroes2::Time timer; bool closeWindow = false; while ( le.HandleEvents( true, true ) ) { if ( closeWindow && timer.getS() >= 5 ) { break; } if ( le.KeyPress() || le.MouseClickLeft() ) { closeWindow = true; } } } class DisplayInitializer { public: DisplayInitializer() { const Settings & conf = Settings::Get(); fheroes2::Display & display = fheroes2::Display::instance(); if ( conf.isFirstGameRun() && System::isHandheldDevice() ) { // We do not show resolution dialog for first run on handheld devices. In this case it is wise to set 'widest' resolution by default. const std::vector resolutions = fheroes2::engine().getAvailableResolutions(); fheroes2::ResolutionInfo bestResolution{ conf.currentResolutionInfo() }; for ( const fheroes2::ResolutionInfo & info : resolutions ) { if ( info.gameWidth > bestResolution.gameWidth && info.gameHeight == bestResolution.gameHeight ) { bestResolution = info; } } display.setResolution( bestResolution ); } else { display.setResolution( conf.currentResolutionInfo() ); } display.fill( 0 ); // start from a black screen fheroes2::engine().setTitle( GetCaption() ); SDL_ShowCursor( SDL_DISABLE ); // hide system cursor fheroes2::RenderProcessor & renderProcessor = fheroes2::RenderProcessor::instance(); display.subscribe( [&renderProcessor]( std::vector & palette ) { return renderProcessor.preRenderAction( palette ); }, [&renderProcessor]() { renderProcessor.postRenderAction(); } ); // Initialize system info renderer. _systemInfoRenderer = std::make_unique(); renderProcessor.registerRenderers( [sysInfoRenderer = _systemInfoRenderer.get()]() { sysInfoRenderer->preRender(); }, [sysInfoRenderer = _systemInfoRenderer.get()]() { sysInfoRenderer->postRender(); } ); renderProcessor.startColorCycling(); // Update mouse cursor when switching between software emulation and OS mouse modes. fheroes2::cursor().registerUpdater( Cursor::Refresh ); #if !defined( MACOS_APP_BUNDLE ) const fheroes2::Image & appIcon = CreateImageFromZlib( 32, 32, iconImage, sizeof( iconImage ), true ); fheroes2::engine().setIcon( appIcon ); #endif } DisplayInitializer( const DisplayInitializer & ) = delete; DisplayInitializer & operator=( const DisplayInitializer & ) = delete; ~DisplayInitializer() { fheroes2::RenderProcessor::instance().unregisterRenderers(); fheroes2::Display & display = fheroes2::Display::instance(); display.subscribe( {}, {} ); display.release(); } private: // This member must not be initialized before Display. std::unique_ptr _systemInfoRenderer; }; class DataInitializer { public: DataInitializer() { const fheroes2::ScreenPaletteRestorer screenRestorer; try { _aggInitializer.reset( new AGG::AGGInitializer ); _h2dInitializer.reset( new fheroes2::h2d::H2DInitializer ); // Verify that the font is present and it is not corrupted. fheroes2::AGG::GetICN( ICN::FONT, 0 ); } catch ( ... ) { displayMissingResourceWindow(); throw; } } DataInitializer( const DataInitializer & ) = delete; DataInitializer & operator=( const DataInitializer & ) = delete; ~DataInitializer() = default; const std::string & getOriginalAGGFilePath() const { return _aggInitializer->getOriginalAGGFilePath(); } const std::string & getExpansionAGGFilePath() const { return _aggInitializer->getExpansionAGGFilePath(); } private: std::unique_ptr _aggInitializer; std::unique_ptr _h2dInitializer; }; } int main( int argc, char ** argv ) { // SDL2main.lib converts argv to UTF-8, but this application expects ANSI, use the original argv #if defined( _WIN32 ) assert( argc == __argc ); argv = __argv; #else (void)argc; #endif try { const fheroes2::HardwareInitializer hardwareInitializer; Logging::InitLog(); COUT( GetCaption() ) Settings & conf = Settings::Get(); conf.SetProgramPath( argv[0] ); InitConfigDir(); InitDataDir(); ReadConfigs(); std::set coreComponents{ fheroes2::SystemInitializationComponent::Audio, fheroes2::SystemInitializationComponent::Video }; #if defined( TARGET_PS_VITA ) || defined( TARGET_NINTENDO_SWITCH ) coreComponents.emplace( fheroes2::SystemInitializationComponent::GameController ); #endif const fheroes2::CoreInitializer coreInitializer( coreComponents ); DEBUG_LOG( DBG_GAME, DBG_INFO, conf.String() ) const DisplayInitializer displayInitializer; const DataInitializer dataInitializer; ListFiles midiSoundFonts; midiSoundFonts.Append( Settings::FindFiles( System::concatPath( "files", "soundfonts" ), ".sf2", false ) ); midiSoundFonts.Append( Settings::FindFiles( System::concatPath( "files", "soundfonts" ), ".sf3", false ) ); #ifdef WITH_DEBUG for ( const std::string & file : midiSoundFonts ) { DEBUG_LOG( DBG_GAME, DBG_INFO, "MIDI SoundFont to load: " << file ) } #endif const AudioManager::AudioInitializer audioInitializer( dataInitializer.getOriginalAGGFilePath(), dataInitializer.getExpansionAGGFilePath(), midiSoundFonts ); // Load palette. fheroes2::setGamePalette( AGG::getDataFromAggFile( "KB.PAL" ) ); fheroes2::Display::instance().changePalette( nullptr, true ); // load BIN data Bin_Info::InitBinInfo(); // init game data Game::Init(); conf.setGameLanguage( conf.getGameLanguage() ); if ( conf.isShowIntro() ) { fheroes2::showTeamInfo(); Video::ShowVideo( "NWCLOGO.SMK", Video::VideoAction::PLAY_TILL_VIDEO_END ); Video::ShowVideo( "CYLOGO.SMK", Video::VideoAction::PLAY_TILL_VIDEO_END ); Video::ShowVideo( "H2XINTRO.SMK", Video::VideoAction::PLAY_TILL_VIDEO_END ); } try { const CursorRestorer cursorRestorer( true, Cursor::POINTER ); Game::mainGameLoop( conf.isFirstGameRun() ); } catch ( const fheroes2::InvalidDataResources & ex ) { ERROR_LOG( ex.what() ) displayMissingResourceWindow(); return EXIT_FAILURE; } } catch ( const std::exception & ex ) { ERROR_LOG( "Exception '" << ex.what() << "' occurred during application runtime." ) return EXIT_FAILURE; } catch ( ... ) { ERROR_LOG( "An unknown exception occurred during application runtime." ) return EXIT_FAILURE; } return EXIT_SUCCESS; } fheroes2-1.0.12+dfsg/src/fheroes2/game/game.cpp000066400000000000000000000345031456075706000211000ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "game.h" #include #include #include #include #include #include #include "army.h" #include "audio.h" #include "audio_manager.h" #include "campaign_savedata.h" #include "castle.h" #include "color.h" #include "cursor.h" #include "difficulty.h" #include "game_credits.h" #include "game_hotkeys.h" #include "game_interface.h" #include "game_static.h" #include "heroes.h" #include "localevent.h" #include "m82.h" #include "maps.h" #include "maps_fileinfo.h" #include "maps_tiles.h" #include "math_base.h" #include "mus.h" #include "players.h" #include "rand.h" #include "settings.h" #include "tools.h" #include "world.h" namespace { std::string lastMapFileName; std::vector savedPlayers; bool updateSoundsOnFocusUpdate = true; bool needFadeIn{ true }; uint32_t maps_animation_frame = 0; } namespace Game { void AnimateDelaysInitialize(); } bool Game::isCampaign() { return Settings::Get().isCampaignGameType(); } int Game::getDifficulty() { const Settings & configuration = Settings::Get(); // Difficulty of non-campaign games depends only on the difficulty settings set by the player if ( !configuration.isCampaignGameType() ) { return configuration.GameDifficulty(); } // Difficulty of campaign games depends on both the difficulty of a particular campaign map and the difficulty settings set by the player int difficulty = configuration.getCurrentMapInfo().difficulty; const int difficultyAdjustment = Campaign::CampaignSaveData::Get().getDifficulty(); difficulty += difficultyAdjustment; return std::clamp( difficulty, static_cast( Difficulty::EASY ), static_cast( Difficulty::IMPOSSIBLE ) ); } void Game::LoadPlayers( const std::string & mapFileName, Players & players ) { if ( lastMapFileName != mapFileName || savedPlayers.size() != players.size() ) { return; } const auto newHumanCount = std::count_if( players.begin(), players.end(), []( const Player * player ) { return player->GetControl() == CONTROL_HUMAN; } ); const auto savedHumanCount = std::count_if( savedPlayers.begin(), savedPlayers.end(), []( const Player & player ) { return player.GetControl() == CONTROL_HUMAN; } ); if ( newHumanCount != savedHumanCount ) { return; } players.clear(); for ( const Player & p : savedPlayers ) { Player * player = new Player( p.GetColor() ); player->SetRace( p.GetRace() ); player->SetControl( p.GetControl() ); player->SetFriends( p.GetFriends() ); player->SetName( p.GetName() ); player->setHandicapStatus( p.getHandicapStatus() ); players.push_back( player ); Players::Set( Color::GetIndex( p.GetColor() ), player ); } } void Game::saveDifficulty( const int difficulty ) { Settings::Get().SetGameDifficulty( difficulty ); } void Game::SavePlayers( const std::string & mapFileName, const Players & players ) { lastMapFileName = mapFileName; savedPlayers.clear(); for ( const Player * p : players ) { assert( p != nullptr ); Player player( p->GetColor() ); player.SetRace( p->GetRace() ); player.SetControl( p->GetControl() ); player.SetFriends( p->GetFriends() ); player.SetName( p->GetName() ); player.setHandicapStatus( p->getHandicapStatus() ); savedPlayers.push_back( player ); } } fheroes2::GameMode Game::Credits() { ShowCredits( true ); return fheroes2::GameMode::MAIN_MENU; } bool Game::UpdateSoundsOnFocusUpdate() { return updateSoundsOnFocusUpdate; } void Game::SetUpdateSoundsOnFocusUpdate( const bool update ) { updateSoundsOnFocusUpdate = update; } bool Game::isFadeInNeeded() { return needFadeIn; } void Game::setDisplayFadeIn() { needFadeIn = true; } bool Game::validateDisplayFadeIn() { if ( needFadeIn ) { needFadeIn = false; return true; } return false; } void Game::Init() { // set global events LocalEvent & le = LocalEvent::Get(); le.setGlobalMouseMotionEventHook( Cursor::updateCursorPosition ); le.setGlobalKeyDownEventHook( Game::globalKeyDownEvent ); Game::AnimateDelaysInitialize(); Game::HotKeysLoad( Settings::GetLastFile( "", "fheroes2.key" ) ); } uint32_t Game::getAdventureMapAnimationIndex() { return maps_animation_frame; } void Game::updateAdventureMapAnimationIndex() { ++maps_animation_frame; } // play environment sounds from the game area in focus void Game::EnvironmentSoundMixer() { int availableChannels = Mixer::getChannelCount(); if ( availableChannels <= 2 ) { // 2 channels are left for hero's movement. return; } availableChannels -= 2; fheroes2::Point center; fheroes2::Point tilePixelOffset; Player * player = Settings::Get().GetPlayers().GetCurrent(); if ( player != nullptr ) { Focus & focus = player->GetFocus(); const Heroes * hero = focus.GetHeroes(); if ( hero != nullptr ) { center = hero->GetCenter(); tilePixelOffset = hero->getCurrentPixelOffset(); } else if ( focus.GetCastle() ) { center = focus.GetCastle()->GetCenter(); } else { center = { world.w() / 2, world.h() / 2 }; } } else { center = { world.w() / 2, world.h() / 2 }; } std::map> soundEffects; const int32_t maxOffset = 3; // Usual area of getting object sounds around a center is 7 x 7 pixel. However, in case of a moving hero we need to expand the area to make sound transition smooth. int32_t scanningOffset = maxOffset; if ( tilePixelOffset != fheroes2::Point() ) { ++scanningOffset; } std::vector positions; positions.reserve( 2 * 2 * scanningOffset * scanningOffset ); for ( int32_t y = -scanningOffset; y <= scanningOffset; ++y ) { const int32_t posY = y + center.y; for ( int32_t x = -scanningOffset; x <= scanningOffset; ++x ) { if ( Maps::isValidAbsPoint( x + center.x, posY ) ) { positions.emplace_back( x, y ); } } } // Sort positions by distance to the center. std::stable_sort( positions.begin(), positions.end(), []( const fheroes2::Point & p1, const fheroes2::Point & p2 ) { return p1.x * p1.x + p1.y * p1.y < p2.x * p2.x + p2.y * p2.y; } ); const double maxDistance = std::sqrt( ( maxOffset * maxOffset + maxOffset * maxOffset ) * TILEWIDTH * TILEWIDTH ); const bool is3DAudioEnabled = Settings::Get().is3DAudioEnabled(); for ( const fheroes2::Point & pos : positions ) { const M82::SoundType soundType = M82::getAdventureMapTileSound( world.GetTiles( pos.x + center.x, pos.y + center.y ) ); if ( soundType == M82::UNKNOWN ) { continue; } fheroes2::Point actualPosition = pos; actualPosition.x *= TILEWIDTH; actualPosition.y *= TILEWIDTH; actualPosition -= tilePixelOffset; const double distance = std::sqrt( actualPosition.x * actualPosition.x + actualPosition.y * actualPosition.y ); if ( distance >= maxDistance ) { continue; } const uint8_t volumePercentage = static_cast( ( maxDistance - distance ) * 100 / maxDistance ); assert( volumePercentage <= 100 ); if ( volumePercentage == 0 ) { continue; } int16_t angle = 0; if ( is3DAudioEnabled ) { // This is a schema how the direction of sound looks like: // | 0 | // | 270 90 | // | 180 | // so the direction to an object on the top is 0 degrees, on the right side - 90, bottom - 180 and left side - 270 degrees. // We need to swap X and Y axes and invert Y axis as on screen Y axis goes from top to bottom. angle = static_cast( std::atan2( actualPosition.x, -actualPosition.y ) * 180 / M_PI ); // It is extremely important to normalize the angle. if ( angle < 0 ) { angle = 360 + angle; } } std::vector & effects = soundEffects[soundType]; bool doesEffectExist = false; for ( AudioManager::AudioLoopEffectInfo & info : effects ) { if ( info.angle == angle ) { info.volumePercentage = std::max( volumePercentage, info.volumePercentage ); doesEffectExist = true; break; } } if ( doesEffectExist ) { continue; } effects.emplace_back( angle, volumePercentage ); --availableChannels; if ( availableChannels == 0 ) { break; } } AudioManager::playLoopSoundsAsync( std::move( soundEffects ) ); } void Game::restoreSoundsForCurrentFocus() { AudioManager::ResetAudio(); switch ( Interface::GetFocusType() ) { case GameFocus::HEROES: { const Heroes * focusedHero = Interface::GetFocusHeroes(); assert( focusedHero != nullptr ); const int heroIndexPos = focusedHero->GetIndex(); if ( heroIndexPos >= 0 ) { Game::EnvironmentSoundMixer(); AudioManager::PlayMusicAsync( MUS::FromGround( world.GetTiles( heroIndexPos ).GetGround() ), Music::PlaybackMode::RESUME_AND_PLAY_INFINITE ); } break; } case GameFocus::CASTLE: { const Castle * focusedCastle = Interface::GetFocusCastle(); assert( focusedCastle != nullptr ); Game::EnvironmentSoundMixer(); AudioManager::PlayMusicAsync( MUS::FromGround( world.GetTiles( focusedCastle->GetIndex() ).GetGround() ), Music::PlaybackMode::RESUME_AND_PLAY_INFINITE ); break; } default: break; } } uint32_t Game::GetRating() { uint32_t rating = 50; switch ( Settings::Get().getCurrentMapInfo().difficulty ) { case Difficulty::NORMAL: rating += 20; break; case Difficulty::HARD: rating += 40; break; case Difficulty::EXPERT: case Difficulty::IMPOSSIBLE: rating += 80; break; default: break; } switch ( Game::getDifficulty() ) { case Difficulty::NORMAL: rating += 30; break; case Difficulty::HARD: rating += 50; break; case Difficulty::EXPERT: rating += 70; break; case Difficulty::IMPOSSIBLE: rating += 90; break; default: break; } return rating; } uint32_t Game::getGameOverScoreFactor() { uint32_t mapSizeFactor = 0; switch ( Settings::Get().getCurrentMapInfo().width ) { case Maps::SMALL: mapSizeFactor = 140; break; case Maps::MEDIUM: mapSizeFactor = 100; break; case Maps::LARGE: mapSizeFactor = 80; break; case Maps::XLARGE: mapSizeFactor = 60; break; default: break; } const uint32_t daysFactor = world.CountDay() * mapSizeFactor / 100; uint32_t daysScore = 0; if ( daysFactor <= 60 ) { daysScore = daysFactor; } else if ( daysFactor <= 120 ) { daysScore = daysFactor / 2 + 30; } else if ( daysFactor <= 360 ) { daysScore = daysFactor / 4 + 60; } else if ( daysFactor <= 600 ) { daysScore = daysFactor / 8 + 105; } else { daysScore = 180; } return ( 200 - daysScore ); } uint32_t Game::GetLostTownDays() { return GameStatic::GetGameOverLostDays(); } uint32_t Game::GetWhirlpoolPercent() { return GameStatic::GetLostOnWhirlpoolPercent(); } int Game::GetKingdomColors() { return Settings::Get().GetPlayers().GetColors(); } int Game::GetActualKingdomColors() { return Settings::Get().GetPlayers().GetActualColors(); } std::string Game::formatMonsterCount( const uint32_t count, const bool isDetailedView, const bool abbreviateNumber /* = false */ ) { if ( isDetailedView ) { return ( abbreviateNumber ? fheroes2::abbreviateNumber( count ) : std::to_string( count ) ); } return Army::SizeString( count ); } void Game::PlayPickupSound() { int wav = M82::UNKNOWN; switch ( Rand::Get( 1, 7 ) ) { case 1: wav = M82::PICKUP01; break; case 2: wav = M82::PICKUP02; break; case 3: wav = M82::PICKUP03; break; case 4: wav = M82::PICKUP04; break; case 5: wav = M82::PICKUP05; break; case 6: wav = M82::PICKUP06; break; case 7: wav = M82::PICKUP07; break; default: return; } AudioManager::PlaySound( wav ); } fheroes2-1.0.12+dfsg/src/fheroes2/game/game.h000066400000000000000000000127331456075706000205460ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2GAME_H #define H2GAME_H #include #include #include "game_mode.h" class Players; class Heroes; class Castle; namespace Game { void Init(); // type_t enum { TYPE_MENU = 0, TYPE_STANDARD = 0x01, TYPE_CAMPAIGN = 0x02, TYPE_HOTSEAT = 0x04, TYPE_NETWORK = 0x08, TYPE_BATTLEONLY = 0x10, // TYPE_LOADFILE used in the Settings::LoadedGameVersion, if you change that value, // change in that function as well. TYPE_LOADFILE = 0x80, TYPE_MULTI = TYPE_HOTSEAT }; void mainGameLoop( bool isFirstGameRun ); fheroes2::GameMode MainMenu( bool isFirstGameRun ); fheroes2::GameMode NewGame(); fheroes2::GameMode LoadGame(); fheroes2::GameMode Credits(); fheroes2::GameMode NewStandard(); fheroes2::GameMode CampaignSelection(); fheroes2::GameMode NewSuccessionWarsCampaign(); fheroes2::GameMode NewPriceOfLoyaltyCampaign(); fheroes2::GameMode NewMulti(); fheroes2::GameMode NewHotSeat(); fheroes2::GameMode NewBattleOnly(); fheroes2::GameMode NewNetwork(); // To be utilized in future. fheroes2::GameMode LoadStandard(); fheroes2::GameMode LoadCampaign(); fheroes2::GameMode LoadMulti(); fheroes2::GameMode LoadHotseat(); fheroes2::GameMode ScenarioInfo(); fheroes2::GameMode SelectCampaignScenario( const fheroes2::GameMode prevMode, const bool allowToRestart ); fheroes2::GameMode SelectScenario(); fheroes2::GameMode StartGame(); fheroes2::GameMode StartBattleOnly(); fheroes2::GameMode DisplayLoadGameDialog(); fheroes2::GameMode CompleteCampaignScenario( const bool isLoadingSaveFile ); fheroes2::GameMode DisplayHighScores( const bool isCampaign ); bool isSuccessionWarsCampaignPresent(); bool isPriceOfLoyaltyCampaignPresent(); void EnvironmentSoundMixer(); void restoreSoundsForCurrentFocus(); bool UpdateSoundsOnFocusUpdate(); void SetUpdateSoundsOnFocusUpdate( const bool update ); bool isFadeInNeeded(); // Set display fade-in state to true. void setDisplayFadeIn(); // If display fade-in state is set reset it to false and return true. Otherwise return false. bool validateDisplayFadeIn(); int GetKingdomColors(); int GetActualKingdomColors(); void DialogPlayers( int color, std::string title, std::string message ); uint32_t getAdventureMapAnimationIndex(); void updateAdventureMapAnimationIndex(); uint32_t GetRating(); uint32_t getGameOverScoreFactor(); uint32_t GetLostTownDays(); uint32_t GetWhirlpoolPercent(); uint32_t SelectCountPlayers(); void PlayPickupSound(); void OpenHeroesDialog( Heroes & hero, bool updateFocus, const bool renderBackgroundDialog, const bool disableDismiss = false ); void OpenCastleDialog( Castle & castle, bool updateFocus = true, const bool renderBackgroundDialog = true ); void LoadPlayers( const std::string & mapFileName, Players & players ); void SavePlayers( const std::string & mapFileName, const Players & players ); // Returns true if the currently active game is a campaign, otherwise returns false bool isCampaign(); // Returns the difficulty level of the currently active game, depending on its type (see the implementation for details) int getDifficulty(); void saveDifficulty( const int difficulty ); int32_t GetStep4Player( const int32_t currentId, const int32_t width, const int32_t totalCount ); // Returns the string representation of the monster count. If a detailed view is requested, the exact number is returned // (unless the abbreviated number is requested), otherwise, a qualitative estimate is returned (Few, Several, etc). std::string formatMonsterCount( const uint32_t count, const bool isDetailedView, const bool abbreviateNumber = false ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/game/game_campaign.cpp000066400000000000000000002311121456075706000227320ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include "agg_image.h" #include "army.h" #include "army_troop.h" #include "artifact.h" #include "audio.h" #include "audio_manager.h" #include "battle.h" #include "campaign_data.h" #include "campaign_savedata.h" #include "campaign_scenariodata.h" #include "cursor.h" #include "dialog.h" #include "game.h" #include "game_credits.h" #include "game_hotkeys.h" #include "game_io.h" #include "game_mode.h" #include "game_over.h" #include "game_video.h" #include "game_video_type.h" #include "heroes.h" #include "highscores.h" #include "icn.h" #include "image.h" #include "kingdom.h" #include "localevent.h" #include "logging.h" #include "maps_fileinfo.h" #include "math_base.h" #include "monster.h" #include "mus.h" #include "pal.h" #include "players.h" #include "race.h" #include "resource.h" #include "screen.h" #include "settings.h" #include "skill.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_campaign.h" #include "ui_dialog.h" #include "ui_text.h" #include "ui_tool.h" #include "ui_window.h" #include "world.h" namespace { enum ScenarioIcon : uint32_t { SCENARIO_ICON_CLEARED = 0, SCENARIO_ICON_AVAILABLE = 1, SCENARIO_ICON_UNAVAILABLE = 2, }; const int betrayalScenarioId = 4; std::vector getCampaignIconOffsets( const int campaignId, const bool archibaldLowerBetrayalBranch = false ) { switch ( campaignId ) { case Campaign::ROLAND_CAMPAIGN: return { { 0, 1 }, { 2, 1 }, { 3, 0 }, { 4, 1 }, { 6, 1 }, { 8, 1 }, { 10, 2 }, { 10, 0 }, { 12, 1 }, { 14, 1 }, { 6, 2 } }; case Campaign::ARCHIBALD_CAMPAIGN: if ( archibaldLowerBetrayalBranch ) { return { { 0, 1 }, { 2, 1 }, { 4, 0 }, { 4, 2 }, { 6, 1 }, { 8, 1 }, { 9, 0 }, { 10, 1 }, { 12, 0 }, { 12, 2 }, { 14, 1 }, { 6, 2 } }; } else { return { { 0, 1 }, { 2, 1 }, { 4, 0 }, { 4, 2 }, { 6, 1 }, { 8, 1 }, { 9, 0 }, { 10, 1 }, { 12, 0 }, { 12, 2 }, { 14, 1 }, { 6, 0 } }; } case Campaign::PRICE_OF_LOYALTY_CAMPAIGN: return { { 0, 0 }, { 2, 0 }, { 4, 1 }, { 4, 0 }, { 6, 1 }, { 7, 0 }, { 9, 1 }, { 10, 0 } }; case Campaign::DESCENDANTS_CAMPAIGN: return { { 0, 1 }, { 2, 1 }, { 4, 0 }, { 4, 2 }, { 6, 1 }, { 8, 2 }, { 8, 0 }, { 10, 1 } }; case Campaign::WIZARDS_ISLE_CAMPAIGN: return { { 0, 0 }, { 2, 0 }, { 4, 1 }, { 6, 0 } }; case Campaign::VOYAGE_HOME_CAMPAIGN: return { { 0, 0 }, { 2, 0 }, { 4, 0 }, { 4, 1 } }; default: // Implementing a new campaign? Add a new case! assert( 0 ); return {}; } } bool isBetrayalScenario( const Campaign::ScenarioInfoId & scenarioInfoId ) { // Betrayal scenario is a special scenario of switching between campaigns. Next scenarios after this must have different campaign ID. for ( const Campaign::ScenarioInfoId & nextScenario : Campaign::CampaignData::getScenariosAfter( scenarioInfoId ) ) { if ( nextScenario.campaignId != scenarioInfoId.campaignId ) { return true; } } return false; } void getScenarioIconId( const Campaign::ScenarioInfoId & scenarioInfoId, int & iconsId, uint32_t & iconStatusOffset, uint32_t & selectedIconIdx ) { iconsId = ICN::UNKNOWN; iconStatusOffset = 0; selectedIconIdx = 0; switch ( scenarioInfoId.campaignId ) { case Campaign::ROLAND_CAMPAIGN: iconsId = ICN::CAMPXTRG; iconStatusOffset = 10; selectedIconIdx = isBetrayalScenario( scenarioInfoId ) ? 17 : 14; return; case Campaign::ARCHIBALD_CAMPAIGN: iconsId = ICN::CAMPXTRE; iconStatusOffset = 10; selectedIconIdx = isBetrayalScenario( scenarioInfoId ) ? 14 : 17; return; case Campaign::PRICE_OF_LOYALTY_CAMPAIGN: iconsId = ICN::X_CMPEXT; iconStatusOffset = 0; selectedIconIdx = 4; return; case Campaign::DESCENDANTS_CAMPAIGN: iconsId = ICN::X_CMPEXT; iconStatusOffset = 0; selectedIconIdx = 7; return; case Campaign::WIZARDS_ISLE_CAMPAIGN: iconsId = ICN::X_CMPEXT; iconStatusOffset = 0; selectedIconIdx = 10; return; case Campaign::VOYAGE_HOME_CAMPAIGN: iconsId = ICN::X_CMPEXT; iconStatusOffset = 0; selectedIconIdx = 13; return; default: // Implementing a new campaign? Add a new case! assert( 0 ); return; } } void DrawCampaignScenarioIcon( const int icnId, const uint32_t iconIdx, const fheroes2::Point & offset, const int posX, const int posY ) { const fheroes2::Sprite & icon = fheroes2::AGG::GetICN( icnId, iconIdx ); fheroes2::Blit( icon, fheroes2::Display::instance(), offset.x + posX, offset.y + posY ); } void addScenarioButton( fheroes2::ButtonGroup & buttonGroup, const int buttonId, const Campaign::ScenarioInfoId & scenarioInfo, const std::vector & availableMaps, const std::vector & clearedMaps, const fheroes2::Point & trackOffset, const bool archibaldLowerBetrayalBranch ) { const int deltaY = 42; const int deltaX = 37; const std::vector & iconOffsets = getCampaignIconOffsets( scenarioInfo.campaignId, archibaldLowerBetrayalBranch ); assert( scenarioInfo.scenarioId >= 0 && static_cast( scenarioInfo.scenarioId ) < iconOffsets.size() ); if ( scenarioInfo.scenarioId < 0 || static_cast( scenarioInfo.scenarioId ) >= iconOffsets.size() ) { return; } fheroes2::Point offset = iconOffsets[scenarioInfo.scenarioId]; offset.x *= deltaX; offset.y *= deltaY; // Price of loyalty's track doesn't respect the standard offset between icons. if ( scenarioInfo.campaignId == Campaign::CampaignID::PRICE_OF_LOYALTY_CAMPAIGN && scenarioInfo.scenarioId >= 5 ) { switch ( scenarioInfo.scenarioId ) { case 5: offset.x -= 2; break; case 6: offset.x -= 1; break; case 7: offset.x -= 4; break; default: break; } } offset.x -= 2; offset.y -= 2; int iconsId = -1; uint32_t iconStatusOffset = 0; uint32_t selectedIconIdx = 0; getScenarioIconId( scenarioInfo, iconsId, iconStatusOffset, selectedIconIdx ); // available scenario (one of which should be selected) if ( std::find( availableMaps.begin(), availableMaps.end(), scenarioInfo ) != availableMaps.end() ) { const fheroes2::Sprite & availableIcon = fheroes2::AGG::GetICN( iconsId, iconStatusOffset + SCENARIO_ICON_AVAILABLE ); const fheroes2::Sprite & selectedIcon = fheroes2::AGG::GetICN( iconsId, selectedIconIdx ); buttonGroup.createButton( trackOffset.x + offset.x, trackOffset.y + offset.y, availableIcon, selectedIcon, buttonId ); } // cleared scenario else if ( std::find( clearedMaps.begin(), clearedMaps.end(), scenarioInfo ) != clearedMaps.end() ) { if ( isBetrayalScenario( scenarioInfo ) ) { assert( static_cast( betrayalScenarioId ) < iconOffsets.size() ); offset = iconOffsets[betrayalScenarioId]; offset.x *= deltaX; offset.y *= deltaY; offset.x -= 2; offset.y -= 2; } DrawCampaignScenarioIcon( iconsId, iconStatusOffset + SCENARIO_ICON_CLEARED, trackOffset, offset.x, offset.y ); } else if ( !isBetrayalScenario( scenarioInfo ) ) { DrawCampaignScenarioIcon( iconsId, iconStatusOffset + SCENARIO_ICON_UNAVAILABLE, trackOffset, offset.x, offset.y ); } } void DrawCampaignScenarioIcons( fheroes2::ButtonGroup & buttonGroup, const Campaign::CampaignData & campaignData, const fheroes2::Point & top, const int chosenScenarioId ) { std::vector prevScenarioNextMaps; const Campaign::CampaignSaveData & saveData = Campaign::CampaignSaveData::Get(); const std::vector & clearedMaps = saveData.getFinishedMaps(); std::vector availableMaps; if ( chosenScenarioId >= 0 ) { availableMaps.emplace_back( saveData.getCampaignID(), chosenScenarioId ); } else { availableMaps = saveData.isStarting() ? campaignData.getStartingScenarios() : Campaign::CampaignData::getScenariosAfter( saveData.getLastCompletedScenarioInfoID() ); } bool isBetrayalScenarioNext = false; for ( const Campaign::ScenarioInfoId & scenarioInfo : availableMaps ) { if ( isBetrayalScenario( scenarioInfo ) ) { isBetrayalScenarioNext = true; break; } } bool isBetrayalScenarioCompleted = false; if ( !isBetrayalScenarioNext ) { for ( const Campaign::ScenarioInfoId & scenarioInfo : clearedMaps ) { if ( isBetrayalScenario( scenarioInfo ) ) { isBetrayalScenarioCompleted = true; break; } } } int campaignTrack = ICN::UNKNOWN; bool archibaldLowerBetrayalBranch = false; switch ( campaignData.getCampaignID() ) { case Campaign::ROLAND_CAMPAIGN: if ( isBetrayalScenarioNext ) { campaignTrack = ICN::CTRACK04; } else if ( isBetrayalScenarioCompleted ) { campaignTrack = ICN::CTRACK02; } else { campaignTrack = ICN::CTRACK00; } break; case Campaign::ARCHIBALD_CAMPAIGN: if ( isBetrayalScenarioNext ) { // Archibald campaign is unique in terms of scenario branching so this is the only way to make it work. assert( !clearedMaps.empty() ); if ( clearedMaps.back().scenarioId == 2 ) { campaignTrack = ICN::CTRACK05; } else { assert( clearedMaps.back().scenarioId == 3 ); campaignTrack = ICN::CTRACK06; archibaldLowerBetrayalBranch = true; } } else if ( isBetrayalScenarioCompleted ) { campaignTrack = ICN::CTRACK01; } else { campaignTrack = ICN::CTRACK03; } break; case Campaign::PRICE_OF_LOYALTY_CAMPAIGN: campaignTrack = ICN::X_TRACK1; break; case Campaign::DESCENDANTS_CAMPAIGN: campaignTrack = ICN::X_TRACK2; break; case Campaign::WIZARDS_ISLE_CAMPAIGN: campaignTrack = ICN::X_TRACK3; break; case Campaign::VOYAGE_HOME_CAMPAIGN: campaignTrack = ICN::X_TRACK4; break; default: // Implementing a new campaign? Add a new case! assert( 0 ); return; } const fheroes2::Sprite & track = fheroes2::AGG::GetICN( campaignTrack, 0 ); const fheroes2::Point trackOffset( top.x + track.x(), top.y + track.y() ); fheroes2::Blit( track, fheroes2::Display::instance(), trackOffset.x, trackOffset.y ); if ( isBetrayalScenarioCompleted ) { assert( campaignData.getCampaignID() == Campaign::ROLAND_CAMPAIGN || campaignData.getCampaignID() == Campaign::ARCHIBALD_CAMPAIGN ); const bool isRolandCurrentCampaign = ( campaignData.getCampaignID() == Campaign::ROLAND_CAMPAIGN ); const Campaign::CampaignData & beforeCampaignData = Campaign::CampaignData::getCampaignData( isRolandCurrentCampaign ? Campaign::ARCHIBALD_CAMPAIGN : Campaign::ROLAND_CAMPAIGN ); const Campaign::CampaignData & currentCampaignData = Campaign::CampaignData::getCampaignData( isRolandCurrentCampaign ? Campaign::ROLAND_CAMPAIGN : Campaign::ARCHIBALD_CAMPAIGN ); int scenarioCounter = 0; for ( const Campaign::ScenarioData & scenarioData : beforeCampaignData.getAllScenarios() ) { if ( scenarioData.getScenarioID() <= betrayalScenarioId || isBetrayalScenario( scenarioData.getScenarioInfoId() ) ) { const Campaign::ScenarioInfoId scenarioInfo{ scenarioData.getCampaignId(), scenarioData.getScenarioID() }; addScenarioButton( buttonGroup, scenarioCounter, scenarioInfo, availableMaps, clearedMaps, trackOffset, archibaldLowerBetrayalBranch ); ++scenarioCounter; } } for ( const Campaign::ScenarioData & scenarioData : currentCampaignData.getAllScenarios() ) { if ( scenarioData.getScenarioID() > betrayalScenarioId ) { const Campaign::ScenarioInfoId scenarioInfo{ scenarioData.getCampaignId(), scenarioData.getScenarioID() }; addScenarioButton( buttonGroup, scenarioCounter, scenarioInfo, availableMaps, clearedMaps, trackOffset, archibaldLowerBetrayalBranch ); ++scenarioCounter; } } } else { const std::vector & scenarios = campaignData.getAllScenarios(); for ( size_t i = 0; i < scenarios.size(); ++i ) { const Campaign::ScenarioInfoId scenarioInfo{ scenarios[i].getCampaignId(), scenarios[i].getScenarioID() }; addScenarioButton( buttonGroup, static_cast( i ), scenarioInfo, availableMaps, clearedMaps, trackOffset, archibaldLowerBetrayalBranch ); } } } void DrawCampaignScenarioDescription( const Campaign::ScenarioData & scenario, const fheroes2::Point & top ) { fheroes2::Display & display = fheroes2::Display::instance(); const std::vector & bonuses = scenario.getBonuses(); fheroes2::Text mapName( scenario.getScenarioName(), fheroes2::FontType::normalWhite() ); mapName.fitToOneRow( 200 ); mapName.draw( top.x + 197 + ( 200 - mapName.width() ) / 2, top.y + 99 - mapName.height() / 2, display ); int scenarioId = scenario.getScenarioID() + 1; if ( isBetrayalScenario( scenario.getScenarioInfoId() ) ) { assert( scenario.getCampaignId() == Campaign::ARCHIBALD_CAMPAIGN || scenario.getCampaignId() == Campaign::ROLAND_CAMPAIGN ); scenarioId = betrayalScenarioId + 1; } const fheroes2::Text campaignMapId( std::to_string( scenarioId ), fheroes2::FontType::normalWhite() ); campaignMapId.draw( top.x + 172 - campaignMapId.width() / 2, top.y + 99 - campaignMapId.height() / 2, display ); fheroes2::Text mapDescription( scenario.getDescription(), fheroes2::FontType::normalWhite() ); mapDescription.draw( top.x + 34, top.y + 134, 356, display ); const int textChoiceWidth = 160; const fheroes2::Point initialOffset{ top.x + 425, top.y + 211 }; for ( size_t i = 0; i < bonuses.size(); ++i ) { fheroes2::Text choice( bonuses[i].getName(), fheroes2::FontType::normalWhite() ); choice.fitToOneRow( textChoiceWidth ); choice.draw( initialOffset.x, initialOffset.y + 22 * static_cast( i ) - choice.height() / 2, display ); } } bool displayScenarioBonusPopupWindow( const Campaign::ScenarioData & scenario, const fheroes2::Point & top ) { const std::vector & bonuses = scenario.getBonuses(); if ( bonuses.empty() ) { // Nothing to process. return false; } const LocalEvent & le = LocalEvent::Get(); for ( size_t i = 0; i < bonuses.size(); ++i ) { if ( le.MousePressRight( { top.x + 414, top.y + 198 + 22 * static_cast( i ), 200, 22 } ) ) { fheroes2::showScenarioBonusDataPopupWindow( bonuses[i] ); return true; } } return false; } void drawObtainedCampaignAwards( const Campaign::CampaignSaveData & campaignSaveData, const fheroes2::Point & top ) { if ( isBetrayalScenario( campaignSaveData.getCurrentScenarioInfoId() ) ) { return; } const int textAwardWidth = 180; // if there are more than 3 awards, we need to reduce the offset between text so that it doesn't overflow out of the text box const std::vector obtainedAwards = campaignSaveData.getObtainedCampaignAwards(); const size_t awardCount = obtainedAwards.size(); const size_t indexEnd = awardCount <= 4 ? awardCount : 4; const int yOffset = awardCount > 3 ? 16 : 22; const int initialOffsetY = 102; fheroes2::Display & display = fheroes2::Display::instance(); if ( awardCount == 0 ) { const fheroes2::Text text( _( "None" ), fheroes2::FontType::normalWhite() ); text.draw( top.x + 425, top.y + initialOffsetY - text.height() / 2, textAwardWidth, display ); return; } fheroes2::Text award; for ( size_t i = 0; i < indexEnd; ++i ) { if ( i < 3 ) { award.set( obtainedAwards[i].getName(), fheroes2::FontType::normalWhite() ); } else { // if we have exactly 4 obtained awards, display the fourth award, otherwise show "and more..." award.set( awardCount == 4 ? obtainedAwards[i].getName() : std::string( _( "and more..." ) ), fheroes2::FontType::normalWhite() ); } award.fitToOneRow( textAwardWidth ); award.draw( top.x + 425 + ( textAwardWidth - award.width() ) / 2, top.y + initialOffsetY + yOffset * static_cast( i ) - award.height() / 2, display ); } } bool displayScenarioAwardsPopupWindow( const Campaign::CampaignSaveData & campaignSaveData, const fheroes2::Point & top ) { if ( isBetrayalScenario( campaignSaveData.getCurrentScenarioInfoId() ) ) { return false; } const std::vector obtainedAwards = campaignSaveData.getObtainedCampaignAwards(); if ( obtainedAwards.empty() ) { // Nothing to process. return false; } const size_t awardCount = obtainedAwards.size(); const size_t indexEnd = awardCount <= 4 ? awardCount : 4; const int yOffset = awardCount > 3 ? 16 : 22; const LocalEvent & le = LocalEvent::Get(); for ( size_t i = 0; i < indexEnd; ++i ) { if ( le.MousePressRight( { top.x + 414, top.y + 100 - yOffset / 2 + yOffset * static_cast( i ), 200, yOffset } ) ) { fheroes2::showAwardDataPopupWindow( obtainedAwards[i] ); return true; } } return false; } void replaceArmy( Army & army, const std::vector & troops ) { army.Clean(); for ( size_t i = 0; i < troops.size(); ++i ) army.GetTroop( i )->Set( troops[i] ); } void setHeroAndArmyBonus( Heroes * hero, const Campaign::ScenarioInfoId & scenarioInfoId ) { switch ( scenarioInfoId.campaignId ) { case Campaign::ARCHIBALD_CAMPAIGN: { if ( scenarioInfoId.scenarioId != 6 ) { assert( 0 ); // no other scenario has this bonus return; } switch ( hero->GetRace() ) { case Race::NECR: replaceArmy( hero->GetArmy(), { { Monster::SKELETON, 50 }, { Monster::ROYAL_MUMMY, 18 }, { Monster::VAMPIRE_LORD, 8 } } ); break; case Race::WRLK: replaceArmy( hero->GetArmy(), { { Monster::CENTAUR, 40 }, { Monster::GARGOYLE, 24 }, { Monster::GRIFFIN, 18 } } ); break; case Race::BARB: replaceArmy( hero->GetArmy(), { { Monster::ORC_CHIEF, 12 }, { Monster::OGRE, 18 }, { Monster::GOBLIN, 40 } } ); break; default: assert( 0 ); // bonus changed? } const uint32_t exp = hero->GetExperience(); if ( exp < 5000 ) { hero->IncreaseExperience( 5000 - exp, true ); } break; } default: assert( 0 ); // some new campaign that uses this bonus? } } Heroes * getHeroToApplyBonusOrAwards( const Campaign::ScenarioInfoId & scenarioInfoId, const Kingdom & kingdom ) { { static const std::map, int> targetHeroes = { // Final Justice { { Campaign::ROLAND_CAMPAIGN, 9 }, Heroes::ROLAND }, // Apocalypse { { Campaign::ARCHIBALD_CAMPAIGN, 10 }, Heroes::ARCHIBALD }, // Blood is Thicker { { Campaign::VOYAGE_HOME_CAMPAIGN, 3 }, Heroes::GALLAVANT } }; const auto iter = targetHeroes.find( { scenarioInfoId.campaignId, scenarioInfoId.scenarioId } ); if ( iter != targetHeroes.end() ) { // The "special" kingdom heroes may have the ID of another hero, but a custom name and portrait, // so the search should be performed by the portrait ID for ( Heroes * hero : kingdom.GetHeroes() ) { assert( hero != nullptr ); if ( hero->getPortraitId() == iter->second ) { return hero; } } DEBUG_LOG( DBG_GAME, DBG_WARN, "the hero to whom bonuses or awards should be applied has not been found" << ", campaign id: " << scenarioInfoId.campaignId << ", scenario id: " << scenarioInfoId.scenarioId ) } } { static const std::map, int> targetRaces = { // Defender { { Campaign::ROLAND_CAMPAIGN, 5 }, Race::SORC }, // The Wayward Son { { Campaign::DESCENDANTS_CAMPAIGN, 2 }, Race::SORC }, // The Epic Battle { { Campaign::DESCENDANTS_CAMPAIGN, 7 }, Race::SORC } }; const auto iter = targetRaces.find( { scenarioInfoId.campaignId, scenarioInfoId.scenarioId } ); if ( iter != targetRaces.end() ) { for ( Heroes * hero : kingdom.GetHeroes() ) { assert( hero != nullptr ); if ( hero->GetRace() == iter->second ) { return hero; } } DEBUG_LOG( DBG_GAME, DBG_WARN, "the hero to whom bonuses or awards should be applied has not been found" << ", campaign id: " << scenarioInfoId.campaignId << ", scenario id: " << scenarioInfoId.scenarioId ) } } // By default, bonuses and awards are applied to the best hero of the kingdom return kingdom.GetBestHero(); } void SetScenarioBonus( const Campaign::ScenarioInfoId & scenarioInfoId, const Campaign::ScenarioBonusData & scenarioBonus ) { const Players & sortedPlayers = Settings::Get().GetPlayers(); for ( const Player * player : sortedPlayers ) { if ( player == nullptr ) { continue; } if ( !player->isControlHuman() ) { continue; } Kingdom & kingdom = world.GetKingdom( player->GetColor() ); switch ( scenarioBonus._type ) { case Campaign::ScenarioBonusData::RESOURCES: kingdom.AddFundsResource( Funds( scenarioBonus._subType, scenarioBonus._amount ) ); break; case Campaign::ScenarioBonusData::ARTIFACT: { Heroes * hero = getHeroToApplyBonusOrAwards( scenarioInfoId, kingdom ); assert( hero != nullptr ); if ( hero != nullptr ) { Artifact artifact( scenarioBonus._subType ); if ( artifact == Artifact::SPELL_SCROLL ) { artifact.SetSpell( scenarioBonus._artifactSpellId ); } hero->PickupArtifact( artifact ); } break; } case Campaign::ScenarioBonusData::TROOP: { Heroes * hero = getHeroToApplyBonusOrAwards( scenarioInfoId, kingdom ); assert( hero != nullptr ); if ( hero != nullptr ) { hero->GetArmy().JoinTroop( Troop( Monster( scenarioBonus._subType ), scenarioBonus._amount ) ); } break; } case Campaign::ScenarioBonusData::SPELL: { Heroes * hero = getHeroToApplyBonusOrAwards( scenarioInfoId, kingdom ); assert( hero != nullptr ); if ( hero != nullptr ) { hero->AppendSpellToBook( scenarioBonus._subType, true ); } break; } case Campaign::ScenarioBonusData::STARTING_RACE: Players::SetPlayerRace( player->GetColor(), scenarioBonus._subType ); break; case Campaign::ScenarioBonusData::STARTING_RACE_AND_ARMY: { Heroes * hero = getHeroToApplyBonusOrAwards( scenarioInfoId, kingdom ); assert( hero != nullptr ); if ( hero != nullptr ) { setHeroAndArmyBonus( hero, scenarioInfoId ); } break; } case Campaign::ScenarioBonusData::SKILL_PRIMARY: { Heroes * hero = getHeroToApplyBonusOrAwards( scenarioInfoId, kingdom ); assert( hero != nullptr ); if ( hero != nullptr ) { for ( int32_t i = 0; i < scenarioBonus._amount; ++i ) { hero->IncreasePrimarySkill( scenarioBonus._subType ); } } break; } case Campaign::ScenarioBonusData::SKILL_SECONDARY: { Heroes * hero = getHeroToApplyBonusOrAwards( scenarioInfoId, kingdom ); assert( hero != nullptr ); if ( hero != nullptr ) { hero->LearnSkill( Skill::Secondary( scenarioBonus._subType, scenarioBonus._amount ) ); } break; } default: assert( 0 ); } } } // apply only the ones that are applied at the start (artifact, spell, carry-over troops) // the rest will be applied based on the situation required void applyObtainedCampaignAwards( const Campaign::ScenarioInfoId & currentScenarioInfoId, const std::vector & awards ) { const Players & sortedPlayers = Settings::Get().GetPlayers(); const Kingdom & humanKingdom = world.GetKingdom( Players::HumanColors() ); for ( size_t i = 0; i < awards.size(); ++i ) { if ( currentScenarioInfoId.scenarioId < awards[i]._startScenarioID ) { continue; } switch ( awards[i]._type ) { case Campaign::CampaignAwardData::TYPE_GET_ARTIFACT: { Heroes * hero = getHeroToApplyBonusOrAwards( currentScenarioInfoId, humanKingdom ); assert( hero != nullptr ); if ( hero != nullptr ) { hero->PickupArtifact( Artifact( awards[i]._subType ) ); // Some artifacts increase the Spell Power of the hero we have to set spell points to maximum. hero->SetSpellPoints( std::max( hero->GetSpellPoints(), hero->GetMaxSpellPoints() ) ); } break; } case Campaign::CampaignAwardData::TYPE_GET_SPELL: { Heroes * hero = getHeroToApplyBonusOrAwards( currentScenarioInfoId, humanKingdom ); assert( hero != nullptr ); if ( hero != nullptr ) { hero->AppendSpellToBook( awards[i]._subType, true ); } break; } case Campaign::CampaignAwardData::TYPE_DEFEAT_ENEMY_HERO: for ( const Player * player : sortedPlayers ) { const Kingdom & kingdom = world.GetKingdom( player->GetColor() ); const VecHeroes & heroes = kingdom.GetHeroes(); for ( size_t j = 0; j < heroes.size(); ++j ) { assert( heroes[j] != nullptr ); if ( heroes[j]->GetID() == awards[i]._subType ) { heroes[j]->Dismiss( Battle::RESULT_LOSS ); break; } } } break; case Campaign::CampaignAwardData::TYPE_CARRY_OVER_FORCES: { Heroes * hero = getHeroToApplyBonusOrAwards( currentScenarioInfoId, humanKingdom ); assert( hero != nullptr ); if ( hero != nullptr ) { replaceArmy( hero->GetArmy(), Campaign::CampaignSaveData::Get().getCarryOverTroops() ); } break; } default: break; } } } void playPreviousScenarioVideo() { const Campaign::CampaignSaveData & saveData = Campaign::CampaignSaveData::Get(); if ( saveData.isStarting() ) { return; } const Campaign::ScenarioInfoId & lastCompletedScenarioInfoId = saveData.getLastCompletedScenarioInfoID(); const Campaign::CampaignData & campaignData = Campaign::CampaignData::getCampaignData( lastCompletedScenarioInfoId.campaignId ); const std::vector & scenarios = campaignData.getAllScenarios(); assert( lastCompletedScenarioInfoId.scenarioId >= 0 && static_cast( lastCompletedScenarioInfoId.scenarioId ) < scenarios.size() ); const Campaign::ScenarioData & completedScenario = scenarios[lastCompletedScenarioInfoId.scenarioId]; if ( !completedScenario.getEndScenarioVideoPlayback().empty() ) { AudioManager::ResetAudio(); for ( const Campaign::ScenarioIntroVideoInfo & videoInfo : completedScenario.getEndScenarioVideoPlayback() ) { Video::ShowVideo( videoInfo.fileName, videoInfo.action ); } AudioManager::ResetAudio(); } } void playCurrentScenarioVideo() { const Campaign::CampaignSaveData & saveData = Campaign::CampaignSaveData::Get(); const Campaign::ScenarioInfoId & currentScenarioInfoId = saveData.getCurrentScenarioInfoId(); const Campaign::CampaignData & campaignData = Campaign::CampaignData::getCampaignData( currentScenarioInfoId.campaignId ); const std::vector & scenarios = campaignData.getAllScenarios(); assert( currentScenarioInfoId.scenarioId >= 0 && static_cast( currentScenarioInfoId.scenarioId ) < scenarios.size() ); const Campaign::ScenarioData & scenario = scenarios[currentScenarioInfoId.scenarioId]; if ( !scenario.getStartScenarioVideoPlayback().empty() ) { AudioManager::ResetAudio(); for ( const Campaign::ScenarioIntroVideoInfo & videoInfo : scenario.getStartScenarioVideoPlayback() ) { Video::ShowVideo( videoInfo.fileName, videoInfo.action ); } AudioManager::ResetAudio(); } } int getCampaignButtonId( const int campaignId ) { switch ( campaignId ) { case Campaign::ROLAND_CAMPAIGN: return ICN::GOOD_CAMPAIGN_BUTTONS; case Campaign::ARCHIBALD_CAMPAIGN: return ICN::EVIL_CAMPAIGN_BUTTONS; case Campaign::PRICE_OF_LOYALTY_CAMPAIGN: case Campaign::DESCENDANTS_CAMPAIGN: case Campaign::WIZARDS_ISLE_CAMPAIGN: case Campaign::VOYAGE_HOME_CAMPAIGN: return ICN::POL_CAMPAIGN_BUTTONS; default: // Implementing a new campaign? Add a new case! assert( 0 ); return ICN::UNKNOWN; } } void drawCampaignNameHeader( const int campaignId, fheroes2::Image & output, const fheroes2::Point & offset ) { // Add extra image header if supported uint32_t campaignNameHeader = ICN::UNKNOWN; switch ( campaignId ) { case Campaign::PRICE_OF_LOYALTY_CAMPAIGN: campaignNameHeader = 15; break; case Campaign::DESCENDANTS_CAMPAIGN: campaignNameHeader = 16; break; case Campaign::WIZARDS_ISLE_CAMPAIGN: campaignNameHeader = 17; break; case Campaign::VOYAGE_HOME_CAMPAIGN: campaignNameHeader = 18; break; default: return; } const fheroes2::Sprite & header = fheroes2::AGG::GetICN( ICN::X_CMPEXT, campaignNameHeader ); fheroes2::Blit( header, output, offset.x + 24, offset.y + 25 ); } void playCampaignMusic( const int campaignId ) { switch ( campaignId ) { case Campaign::ROLAND_CAMPAIGN: case Campaign::PRICE_OF_LOYALTY_CAMPAIGN: case Campaign::DESCENDANTS_CAMPAIGN: case Campaign::WIZARDS_ISLE_CAMPAIGN: case Campaign::VOYAGE_HOME_CAMPAIGN: AudioManager::PlayMusicAsync( MUS::ROLAND_CAMPAIGN_SCREEN, Music::PlaybackMode::REWIND_AND_PLAY_INFINITE ); break; case Campaign::ARCHIBALD_CAMPAIGN: AudioManager::PlayMusicAsync( MUS::ARCHIBALD_CAMPAIGN_SCREEN, Music::PlaybackMode::REWIND_AND_PLAY_INFINITE ); break; default: // Implementing a new campaign? Add a new case! assert( 0 ); break; } } std::string getCampaignDifficultyText( const int32_t difficulty ) { switch ( difficulty ) { case Campaign::CampaignDifficulty::Easy: return { _( "Easy" ) }; case Campaign::CampaignDifficulty::Normal: // Original campaign difficulty. return { _( "Normal" ) }; case Campaign::CampaignDifficulty::Hard: return { _( "Hard" ) }; default: // Did you add a new campaign difficulty? Add the logic above! assert( 0 ); return {}; } } int32_t setCampaignDifficulty( int32_t currentDifficulty, const int32_t maximumAllowedDifficulty ) { // It's better to have frame border width value divisible to 2 and 3 w/o remainder. const fheroes2::StandardWindow frameborder( 300, 284, true ); const fheroes2::Rect & windowRoi = frameborder.activeArea(); const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); const int buttonIcnId = isEvilInterface ? ICN::BUTTON_SMALL_OKAY_EVIL : ICN::BUTTON_SMALL_OKAY_GOOD; const fheroes2::Sprite & buttonSprite = fheroes2::AGG::GetICN( buttonIcnId, 0 ); fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::Rect buttonMaxRoi( windowRoi.x + 5, windowRoi.y, windowRoi.width - 10, windowRoi.height - 5 ); fheroes2::ButtonSprite buttonOk = fheroes2::makeButtonWithShadow( buttonMaxRoi.x + ( buttonMaxRoi.width - buttonSprite.width() ) / 2, buttonMaxRoi.y + buttonMaxRoi.height - buttonSprite.height(), fheroes2::AGG::GetICN( buttonIcnId, 0 ), fheroes2::AGG::GetICN( buttonIcnId, 1 ), display ); buttonOk.draw(); const fheroes2::Text caption( _( "Campaign Difficulty" ), fheroes2::FontType::normalYellow() ); caption.draw( windowRoi.x + ( windowRoi.width - caption.width() ) / 2, windowRoi.y + 10, display ); const int32_t iconSize = 65; const int32_t iconShadowSize = 4; const int32_t fullIconSize = iconSize + iconShadowSize; const int32_t pawnIconOffsetX = windowRoi.x - iconShadowSize + ( windowRoi.width - iconSize ) / 2 - windowRoi.width / 3; const int32_t horseIconOffsetX = windowRoi.x - iconShadowSize + ( windowRoi.width - iconSize ) / 2; const int32_t rookIconOffsetX = windowRoi.x - iconShadowSize + ( windowRoi.width - iconSize ) / 2 + windowRoi.width / 3; const std::array copyFromArea{ fheroes2::Rect{ 20, 94, fullIconSize, fullIconSize }, fheroes2::Rect{ 97, 94, fullIconSize, fullIconSize }, fheroes2::Rect{ 173, 94, fullIconSize, fullIconSize } }; const std::array copyToOffset{ fheroes2::Point{ pawnIconOffsetX, windowRoi.y + 40 }, fheroes2::Point{ horseIconOffsetX, windowRoi.y + 40 }, fheroes2::Point{ rookIconOffsetX, windowRoi.y + 40 } }; const fheroes2::Sprite & chessIcon = fheroes2::AGG::GetICN( ICN::NGHSBKG, 0 ); for ( size_t i = 0; i < copyToOffset.size(); ++i ) { fheroes2::Copy( chessIcon, copyFromArea[i].x, copyFromArea[i].y, display, copyToOffset[i].x, copyToOffset[i].y, copyFromArea[i].width, copyFromArea[i].height ); } if ( isEvilInterface ) { const std::vector & goodToEvilPalette = PAL::GetPalette( PAL::PaletteType::GOOD_TO_EVIL_INTERFACE ); for ( size_t i = 0; i < copyToOffset.size(); ++i ) { fheroes2::ApplyPalette( display, copyToOffset[i].x, copyToOffset[i].y, display, copyToOffset[i].x, copyToOffset[i].y, copyFromArea[i].width, copyFromArea[i].height, goodToEvilPalette ); } } const fheroes2::Sprite & selectionImage = fheroes2::AGG::GetICN( ICN::NGEXTRA, 62 ); fheroes2::MovableSprite selection( selectionImage ); const char * easyDescription = _( "Choose this difficulty if you want to prefer game story over challenge. AI is weaker in comparison with normal difficulty." ); const char * normalDescription = _( "Choose this difficulty to enjoy the campaign as per the original design." ); const char * hardDescription = _( "Choose this difficulty if you want challenge. AI is stronger in comparison with normal difficulty." ); const std::array difficultyArea{ fheroes2::Rect( copyToOffset[0].x + 1, windowRoi.y + 37, selectionImage.width(), selectionImage.height() ), fheroes2::Rect( copyToOffset[1].x + 1, windowRoi.y + 37, selectionImage.width(), selectionImage.height() ), fheroes2::Rect( copyToOffset[2].x + 1, windowRoi.y + 37, selectionImage.width(), selectionImage.height() ) }; const std::array iconArea{ fheroes2::Rect( difficultyArea[0].x + iconShadowSize, difficultyArea[0].y + iconShadowSize, iconSize - 1, iconSize - 1 ), fheroes2::Rect( difficultyArea[1].x + iconShadowSize, difficultyArea[1].y + iconShadowSize, iconSize - 1, iconSize - 1 ), fheroes2::Rect( difficultyArea[2].x + iconShadowSize, difficultyArea[2].y + iconShadowSize, iconSize - 1, iconSize - 1 ) }; const char * currentDescription = nullptr; switch ( currentDifficulty ) { case Campaign::CampaignDifficulty::Easy: currentDescription = easyDescription; selection.setPosition( difficultyArea[0].x, difficultyArea[0].y ); break; case Campaign::CampaignDifficulty::Normal: currentDescription = normalDescription; selection.setPosition( difficultyArea[1].x, difficultyArea[1].y ); break; case Campaign::CampaignDifficulty::Hard: currentDescription = hardDescription; selection.setPosition( difficultyArea[2].x, difficultyArea[2].y ); break; default: // Did you add a new difficulty level for campaigns? Add the logic above! assert( 0 ); break; } const std::array allowedSelection{ ( maximumAllowedDifficulty >= Campaign::CampaignDifficulty::Easy ), ( maximumAllowedDifficulty >= Campaign::CampaignDifficulty::Normal ), ( maximumAllowedDifficulty >= Campaign::CampaignDifficulty::Hard ) }; for ( size_t i = 0; i < allowedSelection.size(); ++i ) { if ( !allowedSelection[i] ) { fheroes2::ApplyPalette( display, iconArea[i].x, iconArea[i].y, display, iconArea[i].x, iconArea[i].y, iconArea[i].width, iconArea[i].height, PAL::GetPalette( PAL::PaletteType::GRAY ) ); } } const int32_t textWidth = windowRoi.width - 16; const fheroes2::Point textOffset{ windowRoi.x + 8, windowRoi.y + 140 }; fheroes2::Text description( currentDescription, fheroes2::FontType::normalWhite() ); fheroes2::ImageRestorer restorer( display, textOffset.x, textOffset.y, textWidth, description.height( textWidth ) ); description.draw( textOffset.x, textOffset.y, textWidth, display ); const fheroes2::Text easyName( getCampaignDifficultyText( Campaign::CampaignDifficulty::Easy ), fheroes2::FontType::normalWhite() ); const fheroes2::Text normalName( getCampaignDifficultyText( Campaign::CampaignDifficulty::Normal ), fheroes2::FontType::normalWhite() ); const fheroes2::Text hardName( getCampaignDifficultyText( Campaign::CampaignDifficulty::Hard ), fheroes2::FontType::normalWhite() ); easyName.draw( difficultyArea[0].x + ( difficultyArea[0].width - easyName.width() ) / 2, difficultyArea[0].y + difficultyArea[0].height + 5, display ); normalName.draw( difficultyArea[1].x + ( difficultyArea[1].width - normalName.width() ) / 2, difficultyArea[1].y + difficultyArea[1].height + 5, display ); hardName.draw( difficultyArea[2].x + ( difficultyArea[2].width - hardName.width() ) / 2, difficultyArea[2].y + difficultyArea[2].height + 5, display ); display.render(); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { le.MousePressLeft( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); if ( le.MouseClickLeft( buttonOk.area() ) || Game::HotKeyCloseWindow() ) { break; } if ( le.MousePressRight( buttonOk.area() ) ) { const fheroes2::Text header( _( "Okay" ), fheroes2::FontType::normalYellow() ); const fheroes2::Text body( _( "Exit this menu." ), fheroes2::FontType::normalWhite() ); fheroes2::showMessage( header, body, 0 ); } else if ( le.MousePressRight( difficultyArea[0] ) ) { const fheroes2::Text header( getCampaignDifficultyText( Campaign::CampaignDifficulty::Easy ), fheroes2::FontType::normalYellow() ); const fheroes2::Text body( easyDescription, fheroes2::FontType::normalWhite() ); fheroes2::showMessage( header, body, 0 ); } else if ( le.MousePressRight( difficultyArea[1] ) ) { const fheroes2::Text header( getCampaignDifficultyText( Campaign::CampaignDifficulty::Normal ), fheroes2::FontType::normalYellow() ); const fheroes2::Text body( normalDescription, fheroes2::FontType::normalWhite() ); fheroes2::showMessage( header, body, 0 ); } else if ( le.MousePressRight( difficultyArea[2] ) ) { const fheroes2::Text header( getCampaignDifficultyText( Campaign::CampaignDifficulty::Hard ), fheroes2::FontType::normalYellow() ); const fheroes2::Text body( hardDescription, fheroes2::FontType::normalWhite() ); fheroes2::showMessage( header, body, 0 ); } bool updateInfo = false; if ( allowedSelection[0] && le.MouseClickLeft( difficultyArea[0] ) ) { currentDescription = easyDescription; selection.setPosition( difficultyArea[0].x, difficultyArea[0].y ); currentDifficulty = Campaign::CampaignDifficulty::Easy; updateInfo = true; } else if ( allowedSelection[1] && le.MouseClickLeft( difficultyArea[1] ) ) { currentDescription = normalDescription; selection.setPosition( difficultyArea[1].x, difficultyArea[1].y ); currentDifficulty = Campaign::CampaignDifficulty::Normal; updateInfo = true; } else if ( allowedSelection[2] && le.MouseClickLeft( difficultyArea[2] ) ) { currentDescription = hardDescription; selection.setPosition( difficultyArea[2].x, difficultyArea[2].y ); currentDifficulty = Campaign::CampaignDifficulty::Hard; updateInfo = true; } if ( updateInfo ) { restorer.restore(); description.set( currentDescription, fheroes2::FontType::normalWhite() ); restorer.reset(); restorer.update( textOffset.x, textOffset.y, textWidth, description.height( textWidth ) ); description.draw( textOffset.x, textOffset.y, textWidth, display ); display.render(); } } return currentDifficulty; } void outputCampaignScenarioInfoInTextSupportMode( const bool allowToRestart ) { START_TEXT_SUPPORT_MODE const Campaign::CampaignSaveData & campaignSaveData = Campaign::CampaignSaveData::Get(); const int chosenCampaignID = campaignSaveData.getCampaignID(); const Campaign::CampaignData & campaignData = Campaign::CampaignData::getCampaignData( chosenCampaignID ); const int scenarioId = campaignSaveData.getCurrentScenarioID(); const std::vector & scenarios = campaignData.getAllScenarios(); const Campaign::ScenarioData & scenario = scenarios[scenarioId]; COUT( "Scenario Information\n" ) COUT( "'" << Campaign::getCampaignName( chosenCampaignID ) << "' campaign, scenario " << scenarioId + 1 << ": " << scenario.getScenarioName() ) COUT( "Description: " << scenario.getDescription() << '\n' ) const std::vector obtainedAwards = campaignSaveData.getObtainedCampaignAwards(); if ( obtainedAwards.empty() ) { COUT( "Awards: None" ) } else { COUT( "Awards:" ) for ( const Campaign::CampaignAwardData & award : obtainedAwards ) { COUT( "- " << award.getName() << " : " << award.getDescription() ) } } COUT( "Bonuses:" ) const std::vector & bonusChoices = scenario.getBonuses(); for ( const Campaign::ScenarioBonusData & bonus : bonusChoices ) { COUT( "- " << bonus.getName() << " : " << bonus.getDescription() ) } if ( !bonusChoices.empty() ) { COUT( "- Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::CAMPAIGN_SELECT_FIRST_BONUS ) << " to select the first bonus." ) } if ( bonusChoices.size() > 1 ) { COUT( "- Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::CAMPAIGN_SELECT_SECOND_BONUS ) << " to select the second bonus." ) } if ( bonusChoices.size() > 2 ) { COUT( "- Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::CAMPAIGN_SELECT_THIRD_BONUS ) << " to select the third bonus." ) } if ( allowToRestart ) { COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::CAMPAIGN_RESTART_SCENARIO ) << " to Restart scenario." ) } else { COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::DEFAULT_OKAY ) << " to Start scenario." ) } COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::CAMPAIGN_VIEW_INTRO ) << " to View Intro Video." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::CAMPAIGN_SELECT_DIFFICULTY ) << " to Select / View Campaign Difficulty." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::DEFAULT_CANCEL ) << " to Exit this dialog." ) } } bool Game::isSuccessionWarsCampaignPresent() { return Campaign::CampaignData::getCampaignData( Campaign::ROLAND_CAMPAIGN ).isAllCampaignMapsPresent() && Campaign::CampaignData::getCampaignData( Campaign::ARCHIBALD_CAMPAIGN ).isAllCampaignMapsPresent(); } bool Game::isPriceOfLoyaltyCampaignPresent() { // We need to check game resources as well. if ( fheroes2::AGG::GetICN( ICN::X_LOADCM, 0 ).empty() || fheroes2::AGG::GetICN( ICN::X_IVY, 0 ).empty() ) { return false; } return Campaign::CampaignData::getCampaignData( Campaign::PRICE_OF_LOYALTY_CAMPAIGN ).isAllCampaignMapsPresent() && Campaign::CampaignData::getCampaignData( Campaign::VOYAGE_HOME_CAMPAIGN ).isAllCampaignMapsPresent() && Campaign::CampaignData::getCampaignData( Campaign::WIZARDS_ISLE_CAMPAIGN ).isAllCampaignMapsPresent() && Campaign::CampaignData::getCampaignData( Campaign::DESCENDANTS_CAMPAIGN ).isAllCampaignMapsPresent(); } fheroes2::GameMode Game::CompleteCampaignScenario( const bool isLoadingSaveFile ) { Campaign::CampaignSaveData & saveData = Campaign::CampaignSaveData::Get(); const Campaign::CampaignData & campaignData = Campaign::CampaignData::getCampaignData( saveData.getCampaignID() ); if ( !isLoadingSaveFile ) { saveData.addCurrentMapToFinished(); saveData.addDaysPassed( world.CountDay() ); if ( !campaignData.isLastScenario( saveData.getLastCompletedScenarioInfoID() ) ) { Game::SaveCompletedCampaignScenario(); } } const Campaign::ScenarioInfoId & lastCompletedScenarioInfo = saveData.getLastCompletedScenarioInfoID(); const std::vector obtainableAwards = Campaign::CampaignAwardData::getCampaignAwardData( lastCompletedScenarioInfo ); // TODO: Check for awards that have to be obtained with 'freak' conditions for ( const auto & obtainableAward : obtainableAwards ) { const int32_t awardType = obtainableAward._type; if ( awardType == Campaign::CampaignAwardData::AwardType::TYPE_DEFEAT_ENEMY_HERO ) { // This award must be granted only after defeating a hero in a battle. continue; } if ( awardType == Campaign::CampaignAwardData::AwardType::TYPE_CARRY_OVER_FORCES ) { const Kingdom & humanKingdom = world.GetKingdom( Players::HumanColors() ); const Heroes * lastBattleWinHero = humanKingdom.GetLastBattleWinHero(); if ( lastBattleWinHero ) saveData.setCarryOverTroops( lastBattleWinHero->GetArmy() ); } saveData.addCampaignAward( obtainableAward._id ); // after adding an artifact award, check whether the artifacts can be assembled into something else if ( awardType == Campaign::CampaignAwardData::AwardType::TYPE_GET_ARTIFACT ) { const std::vector obtainedAwards = saveData.getObtainedCampaignAwards(); std::map artifactAwardIDs; BagArtifacts bagArtifacts; for ( const Campaign::CampaignAwardData & awardData : obtainedAwards ) { if ( awardData._type != Campaign::CampaignAwardData::AwardType::TYPE_GET_ARTIFACT ) continue; artifactAwardIDs.emplace( awardData._subType, awardData._id ); bagArtifacts.PushArtifact( awardData._subType ); saveData.removeCampaignAward( awardData._id ); } // add the assembled artifact's campaign award to artifactAwards for ( const Campaign::CampaignAwardData & awardData : Campaign::CampaignAwardData::getExtraCampaignAwardData( saveData.getCampaignID() ) ) { if ( awardData._type != Campaign::CampaignAwardData::AwardType::TYPE_GET_ARTIFACT ) continue; artifactAwardIDs.emplace( awardData._subType, awardData._id ); } bagArtifacts.assembleArtifactSetIfPossible(); for ( const Artifact & artifact : bagArtifacts ) { if ( !artifact.isValid() ) continue; const auto foundArtifact = artifactAwardIDs.find( artifact.GetID() ); if ( foundArtifact != artifactAwardIDs.end() ) saveData.addCampaignAward( foundArtifact->second ); } } } playPreviousScenarioVideo(); if ( campaignData.isLastScenario( lastCompletedScenarioInfo ) ) { Game::ShowCredits( false ); // Get data for ratings text. const Campaign::CampaignSaveData & campaignSaveData = Campaign::CampaignSaveData::Get(); const int32_t daysPassed = static_cast( campaignSaveData.getDaysPassed() ); // Rating is calculated based on difficulty of campaign. const int32_t score = daysPassed * static_cast( campaignSaveData.getCampaignDifficultyPercent() ) / 100; // Make ratings text as a subtitle for WIN.SMK. fheroes2::MultiFontText ratingText; std::string textBody = _( "Congratulations!\n\nDays: %{days}\n" ); StringReplace( textBody, "%{days}", daysPassed ); ratingText.add( { textBody, fheroes2::FontType::normalWhite() } ); textBody = _( "\nDifficulty: %{difficulty}\n\n" ); StringReplace( textBody, "%{difficulty}", getCampaignDifficultyText( campaignSaveData.getDifficulty() ) ); ratingText.add( { textBody, fheroes2::FontType::smallWhite() } ); textBody = _( "Score: %{score}\n\nRating:\n%{rating}" ); StringReplace( textBody, "%{score}", score ); StringReplace( textBody, "%{rating}", fheroes2::HighScoreDataContainer::getMonsterByDay( score ).GetName() ); ratingText.add( { textBody, fheroes2::FontType::normalWhite() } ); // Show results from the 5th second until end (forever) and set maximum width to 140 to fit the black area. // Set subtitles top-center position (475,110) to render results over the black rectangle of burned picture in WIN.SMK video. Video::Subtitle ratingSubtitle( ratingText, 5000, UINT32_MAX, { 475, 110 }, 140 ); AudioManager::ResetAudio(); Video::ShowVideo( "WIN.SMK", Video::VideoAction::WAIT_FOR_USER_INPUT, { std::move( ratingSubtitle ) }, true ); // fheroes2::PlayMusic is run here in order to start playing before displaying the high score. AudioManager::PlayMusicAsync( MUS::VICTORY, Music::PlaybackMode::REWIND_AND_PLAY_INFINITE ); return fheroes2::GameMode::HIGHSCORES_CAMPAIGN; } const Campaign::ScenarioInfoId firstNextMap = Campaign::CampaignData::getScenariosAfter( lastCompletedScenarioInfo ).front(); saveData.setCurrentScenarioInfo( firstNextMap ); return fheroes2::GameMode::SELECT_CAMPAIGN_SCENARIO; } fheroes2::GameMode Game::SelectCampaignScenario( const fheroes2::GameMode prevMode, const bool allowToRestart ) { fheroes2::Display & display = fheroes2::Display::instance(); display.fill( 0 ); Settings & conf = Settings::Get(); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); Campaign::CampaignSaveData & campaignSaveData = Campaign::CampaignSaveData::Get(); const int chosenCampaignID = campaignSaveData.getCampaignID(); const Campaign::CampaignData & campaignData = Campaign::CampaignData::getCampaignData( chosenCampaignID ); const Campaign::ScenarioInfoId & currentScenarioInfoId = campaignSaveData.getCurrentScenarioInfoId(); const std::vector & scenarios = campaignData.getAllScenarios(); const Campaign::ScenarioData & scenario = scenarios[currentScenarioInfoId.scenarioId]; const fheroes2::GameInterfaceTypeRestorer gameInterfaceRestorer( chosenCampaignID != Campaign::ROLAND_CAMPAIGN ); if ( !allowToRestart ) { playCurrentScenarioVideo(); } outputCampaignScenarioInfoInTextSupportMode( allowToRestart ); playCampaignMusic( chosenCampaignID ); int backgroundIconID = ICN::UNKNOWN; switch ( chosenCampaignID ) { case Campaign::ROLAND_CAMPAIGN: backgroundIconID = ICN::CAMPBKGG; break; case Campaign::ARCHIBALD_CAMPAIGN: backgroundIconID = ICN::CAMPBKGE; break; // PoL campaigns use the same background, but different headers. TODO: Implement the headers case Campaign::PRICE_OF_LOYALTY_CAMPAIGN: case Campaign::DESCENDANTS_CAMPAIGN: case Campaign::WIZARDS_ISLE_CAMPAIGN: case Campaign::VOYAGE_HOME_CAMPAIGN: backgroundIconID = ICN::X_CMPBKG; break; default: // Implementing a new campaign? Add a new case! assert( 0 ); break; } const fheroes2::Sprite & backgroundImage = fheroes2::AGG::GetICN( backgroundIconID, 0 ); const int32_t backgroundImageWidth = backgroundImage.width(); const fheroes2::Point top( ( display.width() - backgroundImageWidth ) / 2, ( display.height() - backgroundImage.height() ) / 2 ); fheroes2::Blit( backgroundImage, display, top.x, top.y ); drawCampaignNameHeader( chosenCampaignID, display, top ); const int buttonIconID = getCampaignButtonId( chosenCampaignID ); // find the placements for the buttons by taking into account their widths. The space between VIEW INTRO and CANCEL is divided by 3 // for the 3 interbutton spaces. The OKAY and RESTART buttons never appear together const int32_t backgroundMargin = 30; const int32_t viewIntroPlacement = top.x + backgroundMargin; const int32_t endOfViewIntroPlacement = viewIntroPlacement + fheroes2::AGG::GetICN( buttonIconID, 0 ).width(); const int32_t cancelPlacement = top.x + backgroundImageWidth - backgroundMargin - fheroes2::AGG::GetICN( buttonIconID, 6 ).width(); const int32_t spaceBetweenViewIntroAndCancel = cancelPlacement - endOfViewIntroPlacement; const int32_t difficultyButtonWidth = fheroes2::AGG::GetICN( buttonIconID, 8 ).width(); const uint32_t okayRestartIndex = allowToRestart ? 2 : 4; const int32_t middleButtonsMargin = ( spaceBetweenViewIntroAndCancel - difficultyButtonWidth - ( fheroes2::AGG::GetICN( buttonIconID, okayRestartIndex ).width() ) ) / 3; const int32_t difficultyPlacement = endOfViewIntroPlacement + middleButtonsMargin; const int32_t okRestartPlacement = difficultyPlacement + difficultyButtonWidth + middleButtonsMargin; const int32_t buttonOffsetY = top.y + 431; fheroes2::Button buttonViewIntro( viewIntroPlacement, buttonOffsetY, buttonIconID, 0, 1 ); fheroes2::Button buttonRestart( okRestartPlacement, buttonOffsetY, buttonIconID, 2, 3 ); fheroes2::Button buttonOk( okRestartPlacement, buttonOffsetY, buttonIconID, 4, 5 ); fheroes2::Button buttonCancel( cancelPlacement, buttonOffsetY, buttonIconID, 6, 7 ); fheroes2::Button buttonDifficulty( difficultyPlacement, buttonOffsetY, buttonIconID, 8, 9 ); fheroes2::addGradientShadow( fheroes2::AGG::GetICN( buttonIconID, 0 ), display, { viewIntroPlacement, buttonOffsetY }, { -5, 5 } ); fheroes2::addGradientShadow( fheroes2::AGG::GetICN( buttonIconID, okayRestartIndex ), display, { okRestartPlacement, buttonOffsetY }, { -5, 5 } ); fheroes2::addGradientShadow( fheroes2::AGG::GetICN( buttonIconID, 6 ), display, { cancelPlacement, buttonOffsetY }, { -5, 5 } ); fheroes2::addGradientShadow( fheroes2::AGG::GetICN( buttonIconID, 8 ), display, { difficultyPlacement, buttonOffsetY }, { -5, 5 } ); // create scenario bonus choice buttons fheroes2::ButtonGroup buttonChoices; fheroes2::OptionButtonGroup optionButtonGroup; std::optional scenarioBonusId; const std::vector & bonusChoices = scenario.getBonuses(); const fheroes2::Point optionButtonOffset( 590, 199 ); const int32_t optionButtonStep = 22; const fheroes2::Sprite & pressedButton = fheroes2::AGG::GetICN( ICN::CAMPXTRG, 8 ); fheroes2::Sprite releaseButton( pressedButton.width(), pressedButton.height(), pressedButton.x(), pressedButton.y() ); fheroes2::Copy( backgroundImage, optionButtonOffset.x + pressedButton.x(), optionButtonOffset.y + pressedButton.y(), releaseButton, 0, 0, releaseButton.width(), releaseButton.height() ); const uint32_t bonusChoiceCount = static_cast( bonusChoices.size() ); { const int32_t saveDataBonusId = campaignSaveData.getCurrentScenarioBonusId(); for ( uint32_t i = 0; i < bonusChoiceCount; ++i ) { buttonChoices.createButton( optionButtonOffset.x + top.x, optionButtonOffset.y + optionButtonStep * i + top.y, releaseButton, pressedButton, i ); optionButtonGroup.addButton( &buttonChoices.button( i ) ); if ( allowToRestart && saveDataBonusId >= 0 && static_cast( saveDataBonusId ) == i ) { scenarioBonusId = saveDataBonusId; buttonChoices.button( i ).press(); } } } if ( bonusChoiceCount > 0 ) { if ( allowToRestart ) { // If the campaign scenario is already in progress, then one of the bonuses should be selected assert( scenarioBonusId.has_value() ); } else { // If this is the beginning of a new campaign scenario, then just select the first bonus scenarioBonusId = 0; buttonChoices.button( 0 ).press(); } } optionButtonGroup.draw(); buttonViewIntro.draw(); buttonDifficulty.draw(); const bool isMapPresent = scenario.isMapFilePresent(); if ( allowToRestart ) { buttonOk.disable(); buttonOk.hide(); if ( !isMapPresent ) { buttonRestart.disable(); } buttonRestart.draw(); } else { buttonRestart.disable(); buttonRestart.hide(); if ( !isMapPresent ) { buttonOk.disable(); } buttonOk.draw(); } // Only one button can be enabled at the time. assert( buttonRestart.isHidden() != buttonOk.isHidden() ); buttonCancel.draw(); const fheroes2::Text textDaysSpent( std::to_string( campaignSaveData.getDaysPassed() ), fheroes2::FontType::normalWhite() ); textDaysSpent.draw( top.x + 582 - textDaysSpent.width() / 2, top.y + 33, display ); DrawCampaignScenarioDescription( scenario, top ); drawObtainedCampaignAwards( campaignSaveData, top ); std::vector selectableScenarios; if ( allowToRestart ) { selectableScenarios.emplace_back( currentScenarioInfoId ); } else { selectableScenarios = campaignSaveData.isStarting() ? campaignData.getStartingScenarios() : Campaign::CampaignData::getScenariosAfter( campaignSaveData.getLastCompletedScenarioInfoID() ); } const uint32_t selectableScenariosCount = static_cast( selectableScenarios.size() ); fheroes2::ButtonGroup selectableScenarioButtons; const int highlightedScenarioId = allowToRestart ? currentScenarioInfoId.scenarioId : -1; DrawCampaignScenarioIcons( selectableScenarioButtons, campaignData, top, highlightedScenarioId ); for ( uint32_t i = 0; i < selectableScenariosCount; ++i ) { if ( currentScenarioInfoId == selectableScenarios[i] ) selectableScenarioButtons.button( i ).press(); selectableScenarioButtons.button( i ).draw(); } LocalEvent & le = LocalEvent::Get(); // Fade-in campaign scenario info. if ( validateDisplayFadeIn() ) { fheroes2::fadeInDisplay( { top.x, top.y, backgroundImage.width(), backgroundImage.height() }, false ); } std::vector choiceArea( bonusChoiceCount ); for ( uint32_t i = 0; i < bonusChoiceCount; ++i ) { choiceArea[i] = buttonChoices.button( i ).area(); choiceArea[i].x -= 170; choiceArea[i].width += 170; } const fheroes2::Rect areaDaysSpent{ top.x + 413, top.y + 27, 201, 25 }; const std::array hotKeyBonusChoice{ Game::HotKeyEvent::CAMPAIGN_SELECT_FIRST_BONUS, Game::HotKeyEvent::CAMPAIGN_SELECT_SECOND_BONUS, Game::HotKeyEvent::CAMPAIGN_SELECT_THIRD_BONUS }; int32_t currentDifficulty = campaignSaveData.getDifficulty(); while ( le.HandleEvents() ) { le.MousePressLeft( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); le.MousePressLeft( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); le.MousePressLeft( buttonViewIntro.area() ) ? buttonViewIntro.drawOnPress() : buttonViewIntro.drawOnRelease(); le.MousePressLeft( buttonDifficulty.area() ) ? buttonDifficulty.drawOnPress() : buttonDifficulty.drawOnRelease(); le.MousePressLeft( buttonRestart.area() ) ? buttonRestart.drawOnPress() : buttonRestart.drawOnRelease(); for ( uint32_t i = 0; i < bonusChoiceCount; ++i ) { if ( le.MousePressLeft( choiceArea[i] ) || ( i < hotKeyBonusChoice.size() && HotKeyPressEvent( hotKeyBonusChoice[i] ) ) ) { scenarioBonusId = fheroes2::checkedCast( i ); buttonChoices.button( i ).press(); optionButtonGroup.draw(); display.render(); break; } } for ( uint32_t i = 0; i < selectableScenariosCount; ++i ) { if ( currentScenarioInfoId != selectableScenarios[i] && le.MouseClickLeft( selectableScenarioButtons.button( i ).area() ) ) { campaignSaveData.setCurrentScenarioInfo( selectableScenarios[i] ); return fheroes2::GameMode::SELECT_CAMPAIGN_SCENARIO; } } if ( le.MouseClickLeft( buttonCancel.area() ) || HotKeyPressEvent( HotKeyEvent::DEFAULT_CANCEL ) ) { if ( !allowToRestart ) { // Make sure to reset a state of the game if a user does not want to load it. GameOver::Result::Get().Reset(); } fheroes2::fadeOutDisplay( { top.x, top.y, backgroundImage.width(), backgroundImage.height() }, false ); if ( prevMode == fheroes2::GameMode::LOAD_CAMPAIGN || prevMode == fheroes2::GameMode::MAIN_MENU ) { // We are going back to main menu. setDisplayFadeIn(); } return prevMode; } displayScenarioAwardsPopupWindow( campaignSaveData, top ) || displayScenarioBonusPopupWindow( scenario, top ); const bool restartButtonClicked = ( buttonRestart.isEnabled() && ( le.MouseClickLeft( buttonRestart.area() ) || HotKeyPressEvent( HotKeyEvent::CAMPAIGN_RESTART_SCENARIO ) ) ); if ( le.MousePressRight( buttonCancel.area() ) ) { fheroes2::showMessage( fheroes2::Text( _( "Cancel" ), fheroes2::FontType::normalYellow() ), fheroes2::Text( _( "Exit this menu without doing anything." ), fheroes2::FontType::normalWhite() ), Dialog::ZERO ); } else if ( buttonOk.isVisible() && le.MousePressRight( buttonOk.area() ) ) { fheroes2::showMessage( fheroes2::Text( _( "Okay" ), fheroes2::FontType::normalYellow() ), fheroes2::Text( _( "Start the selected scenario." ), fheroes2::FontType::normalWhite() ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonViewIntro.area() ) ) { fheroes2::showMessage( fheroes2::Text( _( "View Intro" ), fheroes2::FontType::normalYellow() ), fheroes2::Text( _( "View Intro videos for the current state of the campaign." ), fheroes2::FontType::normalWhite() ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonDifficulty.area() ) ) { fheroes2::showMessage( fheroes2::Text( _( "Campaign Difficulty" ), fheroes2::FontType::normalYellow() ), fheroes2::Text( _( "Select the campaign difficulty. This can be lowered at any point during the campaign." ), fheroes2::FontType::normalWhite() ), Dialog::ZERO ); } else if ( buttonRestart.isVisible() && le.MousePressRight( buttonRestart.area() ) ) { fheroes2::showMessage( fheroes2::Text( _( "Restart" ), fheroes2::FontType::normalYellow() ), fheroes2::Text( _( "Restart the current scenario." ), fheroes2::FontType::normalWhite() ), Dialog::ZERO ); } else if ( ( buttonOk.isEnabled() && ( le.MouseClickLeft( buttonOk.area() ) || HotKeyPressEvent( HotKeyEvent::DEFAULT_OKAY ) ) ) || restartButtonClicked ) { if ( ( !campaignSaveData.isStarting() || allowToRestart ) && currentDifficulty != campaignSaveData.getDifficulty() && fheroes2::showStandardTextMessage( _( "Difficulty" ), _( "You have changed to a lower difficulty for the campaign. You will not be able to revert this after this " "point. The high score will be calculated based solely on the new difficulty. Do you want to proceed?" ), Dialog::YES | Dialog::NO ) == Dialog::NO ) { continue; } if ( restartButtonClicked && fheroes2::showStandardTextMessage( _( "Restart" ), _( "Are you sure you want to restart this scenario?" ), Dialog::YES | Dialog::NO ) == Dialog::NO ) { continue; } Maps::FileInfo mapInfo = scenario.loadMap(); Campaign::CampaignData::updateScenarioGameplayConditions( currentScenarioInfoId, mapInfo ); conf.SetCurrentFileInfo( mapInfo ); assert( !scenarioBonusId || ( scenarioBonusId >= 0 && static_cast( *scenarioBonusId ) < bonusChoices.size() ) ); const Campaign::ScenarioBonusData scenarioBonus = [scenarioBonusId, &bonusChoices = std::as_const( bonusChoices )]() -> Campaign::ScenarioBonusData { if ( !scenarioBonusId ) { return {}; } if ( scenarioBonusId < 0 || static_cast( *scenarioBonusId ) >= bonusChoices.size() ) { return {}; } return bonusChoices[*scenarioBonusId]; }(); // starting faction scenario bonus has to be called before players.SetStartGame() if ( scenarioBonus._type == Campaign::ScenarioBonusData::STARTING_RACE || scenarioBonus._type == Campaign::ScenarioBonusData::STARTING_RACE_AND_ARMY ) { // but the army has to be set after starting the game, so first only set the race SetScenarioBonus( currentScenarioInfoId, { Campaign::ScenarioBonusData::STARTING_RACE, scenarioBonus._subType, scenarioBonus._amount } ); } // Betrayal scenario eliminates all obtained awards. if ( isBetrayalScenario( currentScenarioInfoId ) ) { campaignSaveData.removeAllAwards(); } Players & players = conf.GetPlayers(); players.SetStartGame(); conf.SetGameType( Game::TYPE_CAMPAIGN ); const bool isSWCampaign = ( chosenCampaignID == Campaign::ROLAND_CAMPAIGN ) || ( chosenCampaignID == Campaign::ARCHIBALD_CAMPAIGN ); if ( !world.LoadMapMP2( mapInfo.filename, isSWCampaign ) ) { fheroes2::showStandardTextMessage( _( "Campaign Scenario loading failure" ), _( "Please make sure that campaign files are correct and present." ), Dialog::OK ); // TODO: find a way to restore world for the current game after a failure. conf.SetCurrentFileInfo( {} ); continue; } // Fade-out screen before loading a scenario. fheroes2::fadeOutDisplay(); // meanwhile, the others should be called after players.SetStartGame() if ( scenarioBonus._type != Campaign::ScenarioBonusData::STARTING_RACE ) { SetScenarioBonus( currentScenarioInfoId, scenarioBonus ); } applyObtainedCampaignAwards( currentScenarioInfoId, campaignSaveData.getObtainedCampaignAwards() ); campaignSaveData.setCurrentScenarioInfo( currentScenarioInfoId, scenarioBonusId.value_or( -1 ) ); campaignSaveData.setDifficulty( currentDifficulty ); return fheroes2::GameMode::START_GAME; } else if ( le.MouseClickLeft( buttonViewIntro.area() ) || HotKeyPressEvent( HotKeyEvent::CAMPAIGN_VIEW_INTRO ) ) { AudioManager::ResetAudio(); fheroes2::ImageRestorer restorer( display, top.x, top.y, backgroundImage.width(), backgroundImage.height() ); fheroes2::fadeOutDisplay( restorer.rect(), false ); playPreviousScenarioVideo(); playCurrentScenarioVideo(); playCampaignMusic( chosenCampaignID ); restorer.restore(); fheroes2::fadeInDisplay( restorer.rect(), false ); } else if ( le.MousePressRight( areaDaysSpent ) ) { fheroes2::showMessage( fheroes2::Text( _( "Days spent" ), fheroes2::FontType::normalYellow() ), fheroes2::Text( _( "The number of days spent on this campaign." ), fheroes2::FontType::normalWhite() ), Dialog::ZERO ); } else if ( le.MouseClickLeft( buttonDifficulty.area() ) || HotKeyPressEvent( HotKeyEvent::CAMPAIGN_SELECT_DIFFICULTY ) ) { if ( campaignSaveData.isStarting() && !allowToRestart ) { currentDifficulty = setCampaignDifficulty( currentDifficulty, Campaign::CampaignDifficulty::Hard ); } else { currentDifficulty = setCampaignDifficulty( currentDifficulty, campaignSaveData.getDifficulty() ); } display.render(); } } return prevMode; } fheroes2-1.0.12+dfsg/src/fheroes2/game/game_credits.cpp000066400000000000000000001046131456075706000226150ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "game_credits.h" #include #include #include #include #include #include #include #include "agg_image.h" #include "audio.h" #include "audio_manager.h" #include "cursor.h" #include "game_delays.h" #include "icn.h" #include "image.h" #include "localevent.h" #include "math_base.h" #include "mus.h" #include "pal.h" #include "screen.h" #include "settings.h" #include "tools.h" #include "translations.h" #include "ui_text.h" #include "ui_tool.h" namespace { void transformToBlack( fheroes2::Image & out ) { if ( out.empty() ) { return; } assert( !out.singleLayer() ); uint8_t * image = out.image(); const uint8_t * transform = out.transform(); const uint8_t * imageEnd = image + out.width() * out.height(); for ( ; image != imageEnd; ++image, ++transform ) { if ( *transform == 0 ) { *image = 0; } } } enum class AnimationState : int { FADING_IN, NO_ACTION, FADING_OUT }; class AnimationSequence { public: AnimationSequence() = delete; explicit AnimationSequence( const int32_t imageCount ) : _imageCount( imageCount ) {} int32_t pageId() const { return _pageId; } uint8_t alpha() const { return static_cast( _alphaValue ); } AnimationState state() const { return _animationState; } void increment() { switch ( _animationState ) { case AnimationState::FADING_IN: _alphaValue += ALPHA_VALUE_STEP; if ( _alphaValue > 255 ) { _alphaValue = 255; _animationState = AnimationState::NO_ACTION; _noActionCounter = 0; } break; case AnimationState::NO_ACTION: ++_noActionCounter; if ( _noActionCounter > NO_ACTION_COUNTER_LIMIT ) { _animationState = AnimationState::FADING_OUT; } break; case AnimationState::FADING_OUT: _alphaValue -= ALPHA_VALUE_STEP; if ( _alphaValue < 0 ) { _alphaValue = 0; _animationState = AnimationState::FADING_IN; ++_pageId; if ( _pageId >= _imageCount ) { _pageId = 0; } } break; default: assert( 0 ); break; } } private: const int32_t _imageCount; int32_t _alphaValue = 0; int32_t _noActionCounter = 0; int32_t _pageId = 0; AnimationState _animationState = AnimationState::FADING_IN; enum : int32_t { ALPHA_VALUE_STEP = 5, NO_ACTION_COUNTER_LIMIT = 100 }; }; fheroes2::Sprite generateHeader() { const fheroes2::Sprite & background = fheroes2::AGG::GetICN( ICN::CBKGLAVA, 0 ); assert( background.height() < fheroes2::Display::DEFAULT_HEIGHT ); fheroes2::Sprite output; output._disableTransformLayer(); output.resize( fheroes2::Display::DEFAULT_WIDTH, fheroes2::Display::DEFAULT_HEIGHT - background.height() ); output.fill( 0 ); const fheroes2::Text caption( "fheroes2 engine (" + Settings::GetVersion() + ")", fheroes2::FontType::normalYellow() ); caption.draw( output.width() / 2 - caption.width() / 2, 17, output ); return output; } int32_t renderText( fheroes2::Image & output, const int32_t offsetX, const int32_t offsetY, const int32_t textWidth, const char * titleText, const char * bodyText ) { const fheroes2::Text title( titleText, fheroes2::FontType::normalYellow() ); const fheroes2::Text name( bodyText, fheroes2::FontType::normalWhite() ); title.draw( offsetX, offsetY, textWidth, output ); const int32_t titleHeight = title.height( textWidth ); name.draw( offsetX, offsetY + titleHeight, textWidth, output ); return name.height( textWidth ) + titleHeight; } fheroes2::Sprite generateResurrectionCreditsFirstPage() { fheroes2::Sprite output = fheroes2::AGG::GetICN( ICN::CBKGLAVA, 0 ); output._disableTransformLayer(); const int32_t columnStep = 210; const int32_t textInitialOffsetY = 42; const int32_t textWidth = 200; int32_t offsetY = textInitialOffsetY; int32_t offsetX = 0; offsetY += renderText( output, offsetX, offsetY, textWidth, _( "Project Coordination and Core Development" ), "Ihar Hubchyk" ); offsetY += 10; const fheroes2::Sprite & blackDragon = fheroes2::AGG::GetICN( ICN::DRAGBLAK, 5 ); fheroes2::Blit( blackDragon, output, ( columnStep - blackDragon.width() ) / 2, offsetY ); offsetY += blackDragon.height() + 50; const int32_t secondAuthorLayerY = offsetY; offsetY += renderText( output, offsetX, offsetY, textWidth, _( "Development" ), "Sergei Ivanov" ); offsetY += 10; const fheroes2::Sprite & minotaur = fheroes2::AGG::GetICN( ICN::MINOTAUR, 14 ); fheroes2::Blit( minotaur, output, ( columnStep - minotaur.width() ) / 2, offsetY ); offsetY += minotaur.height(); offsetY += 40; const fheroes2::Text websiteInto( _( "Visit us at " ), fheroes2::FontType::normalWhite() ); const fheroes2::Text website( "https://github.com/ihhub/fheroes2", fheroes2::FontType::normalYellow() ); const int32_t websiteIntoWidth = websiteInto.width(); const int32_t websiteWidth = website.width(); const int32_t websiteHeight = website.height(); const int32_t websiteOffsetX = ( output.width() - websiteIntoWidth - websiteWidth ) / 2; websiteInto.draw( websiteOffsetX, offsetY, output ); website.draw( websiteOffsetX + websiteIntoWidth, offsetY, output ); const fheroes2::Sprite & missile = fheroes2::AGG::GetICN( ICN::ARCH_MSL, 4 ); fheroes2::Blit( missile, output, websiteOffsetX - 10 - missile.width(), offsetY + websiteHeight / 2 - missile.height() / 2 ); fheroes2::Blit( missile, output, websiteOffsetX + websiteIntoWidth + websiteWidth + 10, offsetY + websiteHeight / 2 - missile.height() / 2, true ); offsetY = textInitialOffsetY; offsetX += columnStep; offsetY += renderText( output, offsetX, offsetY, textWidth, _( "QA and Support" ), "Igor Tsivilko" ); offsetY += 10; const fheroes2::Sprite & cyclop = fheroes2::AGG::GetICN( ICN::CYCLOPS, 38 ); fheroes2::Blit( cyclop, output, offsetX + ( columnStep - cyclop.width() ) / 2, offsetY ); offsetY = secondAuthorLayerY; offsetY += renderText( output, offsetX, offsetY, textWidth, _( "Development" ), "Ivan Shibanov" ); offsetY += 10; const fheroes2::Sprite & crusader = fheroes2::AGG::GetICN( ICN::PALADIN2, 23 ); fheroes2::Blit( crusader, output, offsetX + ( columnStep - crusader.width() ) / 2, offsetY ); offsetY = textInitialOffsetY; offsetX += columnStep; offsetY += renderText( output, offsetX, offsetY, textWidth, _( "Development" ), "Oleg Derevenetz" ); offsetY += 10; const fheroes2::Sprite & mage = fheroes2::AGG::GetICN( ICN::MAGE1, 24 ); fheroes2::Blit( mage, output, offsetX + ( columnStep - mage.width() ) / 2, offsetY ); offsetY = secondAuthorLayerY; offsetY += renderText( output, offsetX, offsetY, textWidth, _( "Dev and Support" ), "Zense" ); offsetY += 10; const fheroes2::Sprite & phoenix = fheroes2::AGG::GetICN( ICN::PHOENIX, 4 ); fheroes2::Blit( phoenix, output, offsetX + ( columnStep - phoenix.width() ) / 2, offsetY - 10 ); const fheroes2::Sprite & goblin = fheroes2::AGG::GetICN( ICN::GOBLIN, 27 ); fheroes2::Blit( goblin, output, output.width() - goblin.width() * 2, output.height() - goblin.height() - 10, true ); return output; } fheroes2::Sprite generateResurrectionCreditsSecondPage() { fheroes2::Sprite output = fheroes2::AGG::GetICN( ICN::CBKGLAVA, 0 ); output._disableTransformLayer(); const int32_t textInitialOffsetX = output.width() / 2; const int32_t textInitialOffsetY = 60; const int32_t textWidth = 300; const int32_t textOffsetX = ( textInitialOffsetX - textWidth ) / 2; int32_t offsetY = textInitialOffsetY; const fheroes2::Text title( _( "Special Thanks to" ), fheroes2::FontType::normalYellow() ); title.draw( textInitialOffsetX - title.width() / 2, offsetY, output ); offsetY += title.height() + 5; std::string contributors( "LeHerosInconnu\n" "undef21\n" "shprotru\n" "Arkadiy Illarionov\n" "a1exsh\n" "vincent-grosbois\n" "eos428\n" "Arthusppp\n" "felix642\n" "Vasilenko Alexey\n" "Andrii Kurdiumov\n" "Mr-Bajs\n" "dimag0g\n" "Effektus\n" "Laserlicht\n" "Stisen1\n" "Mauri Mustonen\n" "tau3\n" ); fheroes2::Text name( std::move( contributors ), fheroes2::FontType::normalWhite() ); const int32_t constributorsHeight = name.height( textWidth ); const int32_t contributorCount = name.rows( textWidth ); name.draw( textOffsetX, offsetY, textWidth, output ); std::string supporters( "Aimi Lindschouw\n" "Aleksei Mazur\n" "Andrew Szucs\n" "Benjamin Hughes\n" "Brandon Wright\n" "Connor Townsend\n" "Grigoris Papadourakis\n" "Hajler\n" "Kiril Lipatov\n" "Kresimir Condic\n" "Kuza\n" "Matt Taylor\n" "slvclw\n" "TechnoCore\n" "William Hoskinson\n" ); name.set( std::move( supporters ), fheroes2::FontType::normalWhite() ); const int32_t supportersHeight = name.height( textWidth ); const int32_t supporterCount = name.rows( textWidth ); const int32_t countDifference = ( contributorCount - supporterCount ); const int32_t supportersOffset = ( countDifference / 2 ) * ( supportersHeight / supporterCount ); name.draw( textInitialOffsetX + textOffsetX, offsetY + supportersOffset, textWidth, output ); offsetY += constributorsHeight; name.set( _( "and many other contributors!" ), fheroes2::FontType::normalWhite() ); name.draw( 10, offsetY, textWidth, output ); name.set( _( "and many-many other supporters!" ), fheroes2::FontType::normalWhite() ); name.draw( textInitialOffsetX + textOffsetX, offsetY, textWidth, output ); const fheroes2::Sprite & hydra = fheroes2::AGG::GetICN( ICN::HYDRA, 11 ); offsetY = output.height() - hydra.height() - 40; fheroes2::Blit( hydra, output, textInitialOffsetX - hydra.width() / 2, offsetY ); return output; } fheroes2::Sprite generateResurrectionCreditsThirdPage() { fheroes2::Sprite output = fheroes2::AGG::GetICN( ICN::CBKGSWMP, 0 ); output._disableTransformLayer(); const int32_t textInitialOffsetX = output.width() / 2; const int32_t textInitialOffsetY = 80; const int32_t textWidth = 300; int32_t offsetX = ( textInitialOffsetX - textWidth ) / 2; int32_t offsetY = textInitialOffsetY; offsetY += renderText( output, offsetX, offsetY, textWidth, _( "Support us at" ), _( "local-donation-platform|https://www.patreon.com/fheroes2" ) ); offsetY += 30; const fheroes2::Sprite & wizard = fheroes2::AGG::GetICN( ICN::CMBTCAPZ, 4 ); fheroes2::Blit( wizard, output, ( textInitialOffsetX - wizard.width() ) / 2, offsetY ); offsetY += wizard.height() + 20; offsetY += renderText( output, offsetX, offsetY, textWidth - 10, _( "Connect with us at" ), _( "local-social-network|https://www.facebook.com/groups/fheroes2" ) ); offsetY += 20; const fheroes2::Sprite & vampireLord = fheroes2::AGG::GetICN( ICN::VAMPIRE2, 22 ); fheroes2::Blit( vampireLord, output, ( textInitialOffsetX - vampireLord.width() ) / 2, offsetY ); offsetY = textInitialOffsetY; offsetX += textInitialOffsetX; offsetY += renderText( output, offsetX, offsetY, textWidth - 10, _( "Need help with the game?" ), "https://discord.gg/xF85vbZ" ); offsetY += 10; fheroes2::Sprite labyrinth = fheroes2::AGG::GetICN( ICN::TWNWUP_3, 0 ); fheroes2::ApplyPalette( labyrinth, 2 ); fheroes2::Blit( labyrinth, output, textInitialOffsetX + ( textInitialOffsetX - labyrinth.width() ) / 2, offsetY ); offsetY += labyrinth.height() + 50; const int32_t miniMonsterXOffset = textInitialOffsetX * 3 / 2; fheroes2::Text name( _( "Original project before 0.7" ), fheroes2::FontType::smallYellow() ); name.draw( offsetX, offsetY, textWidth - 10, output ); offsetY += name.height( textWidth - 10 ); name.set( "Andrey Afletdinov\nhttps://sourceforge.net/\nprojects/fheroes2/", fheroes2::FontType::smallWhite() ); name.draw( offsetX, offsetY, textWidth - 10, output ); fheroes2::Sprite creature = fheroes2::AGG::GetICN( ICN::MAGE2, 4 ); transformToBlack( creature ); const int32_t creatureOffsetY = output.height() - 95; fheroes2::Blit( creature, 0, 0, output, miniMonsterXOffset - creature.width() / 2, creatureOffsetY, creature.width(), creature.height() ); name.set( "?", fheroes2::FontType::normalYellow() ); name.draw( miniMonsterXOffset - name.width() / 2, creatureOffsetY + creature.height() / 2 - 5, output ); return output; } fheroes2::Sprite generateSuccessionWarsCreditsFirstPage() { fheroes2::Sprite output = fheroes2::AGG::GetICN( ICN::CBKGWATR, 0 ); fheroes2::ApplyPalette( output, PAL::GetPalette( PAL::PaletteType::DARKENING ) ); output._disableTransformLayer(); const fheroes2::FontType nameFontType = fheroes2::FontType::normalWhite(); const fheroes2::Text title( _( "Heroes of Might and Magic II: The Succession Wars team" ), nameFontType ); title.draw( ( output.width() - title.width() ) / 2, 10, output ); const int32_t textInitialOffsetY = 35; const int32_t textWidth = 320; const int32_t titleOffsetY = 7; int32_t offsetY = textInitialOffsetY; int32_t offsetX = 0; offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Designed and Directed" ), "Jon Van Caneghem" ); offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Programming and Design" ), "Phil Steinmeyer" ); offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Executive Producer" ), "Mark Caldwell" ); offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Producer" ), "Walt Hochbrueckner" ); offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Additional Design" ), "Paul Rattner\n" "Debbie Van Caneghem" ); offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Additional Programming" ), "George Ruof\n" "Todd Hendrix\n" "Mark Caldwell" ); offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Musical Production" ), "Rob King" ); renderText( output, offsetX, offsetY, textWidth, _( "Music and Sound Design" ), "Rob King\n" "Steve Baca\n" "Paul Romero" ); offsetY = textInitialOffsetY; offsetX += textWidth; offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Vocalists" ), "Grant Youngblood\n" "Kareen Meshagan" ); offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Art Director" ), "Julia Ulano" ); offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Assistant Art Director" ), "Bonita Long-Hemsath" ); renderText( output, offsetX, offsetY, textWidth, _( "Artists" ), "Julie Bateman\n" "Rebecca Christel\n" "Shelly Garcia\n" "Sam Hasson\n" "Louis Henderson\n" "Tracy Iwata\n" "Steve Jasper\n" "April Lee\n" "Lieu Pham\n" "Phelan Sykes\n" "Steve Wasaff\n" "Scott White" ); return output; } fheroes2::Sprite generateSuccessionWarsCreditsSecondPage() { fheroes2::Sprite output = fheroes2::AGG::GetICN( ICN::CBKGWATR, 0 ); fheroes2::ApplyPalette( output, PAL::GetPalette( PAL::PaletteType::DARKENING ) ); output._disableTransformLayer(); const fheroes2::FontType nameFontType = fheroes2::FontType::normalWhite(); const fheroes2::Text title( _( "Heroes of Might and Magic II: The Succession Wars team" ), nameFontType ); title.draw( ( output.width() - title.width() ) / 2, 10, output ); const int32_t textInitialOffsetY = 35; const int32_t textWidth = 320; const int32_t titleOffsetY = 7; int32_t offsetY = textInitialOffsetY; int32_t offsetX = 0; offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "QA Manager" ), "Peter Ryu" ); offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "QA" ), "David Botan\n" "David Fernandez\n" "Bill Nesemeier\n" "Walter Johnson\n" "Kate McClelland\n" "Timothy Lang\n" "Bryan Farina" ); offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Writing" ), "Paul Rattner" ); renderText( output, offsetX, offsetY, textWidth, _( "Manual and Helpfile" ), "Bryan Farina\n" "Rozita Tolouey\n" "Bruce Schlickbernd" ); offsetY = textInitialOffsetY; offsetX += textWidth; offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Scenarios" ), "Jon Van Caneghem\n" "Debbie Van Caneghem\n" "Clayton Retzer\n" "Christian Vanover\n" "Paul Rattner\n" "Benjamin Bent\n" "Bryan Farina\n" "Eric Heffron\n" "Mark Palczynski\n" "Walt Hochbrueckner\n" "Bruce Schlickbernd\n" "Craig Konas" ); renderText( output, offsetX, offsetY, textWidth, _( "Special Thanks to" ), "Scott McDaniel\n" "Dean Rettig\n" "Ted Chapman\n" "Dean Frost" ); return output; } fheroes2::Sprite generatePriceOfLoyaltyCreditsFirstPage() { fheroes2::Sprite output = fheroes2::AGG::GetICN( ICN::CBKGGRAV, 0 ); fheroes2::ApplyPalette( output, PAL::GetPalette( PAL::PaletteType::DARKENING ) ); output._disableTransformLayer(); const fheroes2::FontType titleFontType = fheroes2::FontType::normalYellow(); const fheroes2::FontType nameFontType = fheroes2::FontType::normalWhite(); fheroes2::Text title( _( "Heroes of Might and Magic II: The Price of Loyalty team" ), nameFontType ); title.draw( ( output.width() - title.width() ) / 2, 10, output ); title.set( "Cyberlore Studios", titleFontType ); title.draw( ( output.width() - title.width() ) / 2, 10 + title.height() * 2, output ); const int32_t textInitialOffsetY = 35 + title.height() * 4; const int32_t textWidth = 320; const int32_t titleOffsetY = 7; int32_t offsetY = textInitialOffsetY; int32_t offsetX = 0; offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Executive Producer" ), "Lester Humphreys" ); offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Producer" ), "Joe Minton" ); offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Design Lead" ), "Jim DuBois" ); offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Designers" ), "Jesse King\n" "Kris Greenia" ); offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Programming Lead" ), "Mike White" ); renderText( output, offsetX, offsetY, textWidth, _( "Art Director" ), "Seth Spaulding" ); offsetY = textInitialOffsetY; offsetX += textWidth; offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Art Lead" ), "Thomas Gale" ); offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Artists" ), "Michael Clarke\n" "Michael Baker\n" "Julie Airoldi" ); renderText( output, offsetX, offsetY, textWidth, _( "Playtesters" ), "Bart Simon\n" "Fred Fredette\n" "Rendall Koski\n" "T.J. Andrzejczyk\n" "Joanne Delphia" ); return output; } fheroes2::Sprite generatePriceOfLoyaltyCreditsSecondPage() { fheroes2::Sprite output = fheroes2::AGG::GetICN( ICN::CBKGGRAV, 0 ); fheroes2::ApplyPalette( output, PAL::GetPalette( PAL::PaletteType::DARKENING ) ); output._disableTransformLayer(); const fheroes2::FontType titleFontType = fheroes2::FontType::normalYellow(); const fheroes2::FontType nameFontType = fheroes2::FontType::normalWhite(); fheroes2::Text title( _( "Heroes of Might and Magic II: The Price of Loyalty team" ), nameFontType ); title.draw( ( output.width() - title.width() ) / 2, 10, output ); title.set( "New World Computing", titleFontType ); title.draw( ( output.width() - title.width() ) / 2, 10 + title.height() * 2, output ); const int32_t textInitialOffsetY = 35 + title.height() * 4; const int32_t textWidth = 320; const int32_t titleOffsetY = 7; int32_t offsetY = textInitialOffsetY; int32_t offsetX = 0; offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Designer" ), "Jon Van Caneghem" ); offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Executive Producer" ), "Mark Caldwell" ); offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Producers" ), "Peter Ryu\n" "Deane Rettig" ); offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Musical Production" ), "Rob King" ); offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "QA Managers" ), "Brian Gilmer\n" "Peter Ryu" ); renderText( output, offsetX, offsetY, textWidth, _( "Music" ), "Rob King\n" "Paul Romero\n" "Steve Baca" ); offsetY = textInitialOffsetY; offsetX += textWidth; offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Sound Design" ), "Rob King\n" "Steve Baca" ); offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Town Themes" ), "Paul Romero" ); offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Alto Sax" ), "Brock \"Saxman\" Summers" ); offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Harpsichord and Piano" ), "Paul Romero" ); offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Basso Vocal" ), "Reid Bruton" ); offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Soprano Vocal" ), "Karin Meshagin" ); std::string recordedString = _( "Recorded at %{recordingStudio}" ); StringReplace( recordedString, "%{recordingStudio}", "Green Street Studios" ); title.set( recordedString, titleFontType ); title.draw( offsetX, offsetY, textWidth, output ); return output; } fheroes2::Sprite generatePriceOfLoyaltyCreditsThirdPage() { fheroes2::Sprite output = fheroes2::AGG::GetICN( ICN::CBKGGRAV, 0 ); fheroes2::ApplyPalette( output, PAL::GetPalette( PAL::PaletteType::DARKENING ) ); output._disableTransformLayer(); const fheroes2::FontType titleFontType = fheroes2::FontType::normalYellow(); const fheroes2::FontType nameFontType = fheroes2::FontType::normalWhite(); fheroes2::Text title( _( "Heroes of Might and Magic II: The Price of Loyalty team" ), nameFontType ); title.draw( ( output.width() - title.width() ) / 2, 10, output ); title.set( "New World Computing", titleFontType ); title.draw( ( output.width() - title.width() ) / 2, 10 + title.height() * 2, output ); const int32_t textInitialOffsetY = 35 + title.height() * 4; const int32_t textWidth = 320; const int32_t titleOffsetY = 7; int32_t offsetY = textInitialOffsetY; int32_t offsetX = 0; offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "credits|Manual" ), "Bryan Farina" ); offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "German Consultant" ), "Dr. Brock H. Summers" ); renderText( output, offsetX, offsetY, textWidth, _( "Map Designers" ), "Christian Vanover\n" "Ben Bent\n" "Tracy Iwata\n" "Clay Ratzner\n" "Walter Hochbrueckner\n" "Paul Ratner" ); offsetY = textInitialOffsetY; offsetX += textWidth; offsetY += titleOffsetY + renderText( output, offsetX, offsetY, textWidth, _( "Package Design" ), "Rozita Tolouey" ); renderText( output, offsetX, offsetY, textWidth, _( "Playtesters" ), "Mikeael Herauf\n" "Walter Johnson\n" "David Botan\n" "David Fernandez\n" "Kate McClelland\n" "William Nesemeier\n" "Tim Lang\n" "Pavel Vesely\n" "John Lencioni\n" "Jason Wildblood" ); return output; } } void Game::ShowCredits( const bool keepMainMenuBorders ) { // Credits are shown in the place of Main Menu background which is correctly resized. // We get the Main Menu background ROI to use it for credits ROI and leave borders unchanged. const fheroes2::Sprite & mainMenuBackground = fheroes2::AGG::GetICN( ICN::HEROES, 0 ); const fheroes2::Rect creditsRoi( mainMenuBackground.x(), mainMenuBackground.y(), mainMenuBackground.width(), mainMenuBackground.height() ); // Hide mouse cursor. const CursorRestorer cursorRestorer( false, Cursor::POINTER ); std::unique_ptr restorer; fheroes2::Display & display = fheroes2::Display::instance(); if ( keepMainMenuBorders ) { // Make a copy of background image to restore it during fade after the credits. restorer = std::make_unique( display, creditsRoi.x, creditsRoi.y, creditsRoi.width, creditsRoi.height ); fheroes2::fadeOutDisplay( creditsRoi, false ); } AudioManager::PlayMusicAsync( MUS::VICTORY, Music::PlaybackMode::REWIND_AND_PLAY_INFINITE ); const uint64_t animationDelay = 50; std::vector pages; pages.emplace_back( generateResurrectionCreditsFirstPage() ); pages.emplace_back( generateResurrectionCreditsSecondPage() ); pages.emplace_back( generateResurrectionCreditsThirdPage() ); if ( Settings::Get().isPriceOfLoyaltySupported() ) { pages.emplace_back( generatePriceOfLoyaltyCreditsFirstPage() ); pages.emplace_back( generatePriceOfLoyaltyCreditsSecondPage() ); pages.emplace_back( generatePriceOfLoyaltyCreditsThirdPage() ); } pages.emplace_back( generateSuccessionWarsCreditsFirstPage() ); pages.emplace_back( generateSuccessionWarsCreditsSecondPage() ); // Resize the credits pages. 'creditsRoi' is made using Main Menu background parameters that were already properly calculated for the current resolution. const int32_t resizedPageHeight = creditsRoi.width * pages.front().height() / pages.front().width(); for ( fheroes2::Sprite & page : pages ) { fheroes2::Sprite resizedPage; resizedPage._disableTransformLayer(); resizedPage.resize( creditsRoi.width, resizedPageHeight ); fheroes2::Resize( page, resizedPage ); page = std::move( resizedPage ); } fheroes2::Sprite header; header._disableTransformLayer(); header.resize( creditsRoi.width, creditsRoi.height - resizedPageHeight ); fheroes2::Resize( generateHeader(), header ); AnimationSequence sequence( static_cast( pages.size() ) ); bool fadeInHeader = true; // Immediately indicate that the delay has passed to render first frame immediately. Game::passCustomAnimationDelay( animationDelay ); // Make sure that the first run is passed immediately. assert( !Game::isCustomDelayNeeded( animationDelay ) ); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents( Game::isCustomDelayNeeded( animationDelay ) ) ) { if ( le.KeyPress() || le.MouseClickLeft() || le.MouseClickMiddle() || le.MouseClickRight() ) { break; } if ( Game::validateCustomAnimationDelay( animationDelay ) ) { if ( sequence.state() == AnimationState::NO_ACTION ) { if ( fadeInHeader ) { fadeInHeader = false; } sequence.increment(); continue; } const uint8_t alpha = sequence.alpha(); if ( fadeInHeader ) { fheroes2::ApplyAlpha( header, 0, 0, display, creditsRoi.x, creditsRoi.y, header.width(), header.height(), alpha ); } const fheroes2::Image & page = pages[sequence.pageId()]; if ( alpha == 255 ) { // This alpha is for fully bright image so there is no need to apply alpha. Copy( page, 0, 0, display, creditsRoi.x, creditsRoi.y + header.height(), page.width(), page.height() ); } else if ( alpha == 0 ) { // This alpha is for fully dark image so fill it with the black color. Fill( display, creditsRoi.x, creditsRoi.y + header.height(), page.width(), page.height(), 0 ); } else { fheroes2::ApplyAlpha( page, 0, 0, display, creditsRoi.x, creditsRoi.y + header.height(), page.width(), page.height(), alpha ); } display.render(); sequence.increment(); } } if ( keepMainMenuBorders ) { fheroes2::fadeOutDisplay( creditsRoi, false ); // Restore a copy of background image to fade it in. restorer->restore(); fheroes2::fadeInDisplay( creditsRoi, false ); } else { fheroes2::fadeOutDisplay(); } } fheroes2-1.0.12+dfsg/src/fheroes2/game/game_credits.h000066400000000000000000000032531456075706000222600ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once namespace Game { // Setting keepMainMenuBorders to 'true' will keep screen borders without fade // and restore the main menu image during fade after the credits. void ShowCredits( const bool keepMainMenuBorders ); } fheroes2-1.0.12+dfsg/src/fheroes2/game/game_delays.cpp000066400000000000000000000220251456075706000224350ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "game_delays.h" #include #include #include "gamedefs.h" #include "settings.h" #include "timing.h" namespace { std::vector delays( Game::LAST_DELAY + 1, fheroes2::TimeDelay( 0 ) ); static_assert( ( DEFAULT_BATTLE_SPEED >= 0 ) && ( DEFAULT_BATTLE_SPEED < 10 ) ); constexpr double battleSpeedAdjustment = 1.0 / static_cast( 10 - DEFAULT_BATTLE_SPEED ); int humanHeroMultiplier = 1; int aiHeroMultiplier = 1; void SetupHeroMovement( const int speed, fheroes2::TimeDelay & delay, int & multiplier ) { switch ( speed ) { case 1: delay.setDelay( 18 ); multiplier = 1; break; case 2: delay.setDelay( 16 ); multiplier = 1; break; case 3: delay.setDelay( 14 ); multiplier = 1; break; case 4: delay.setDelay( 12 ); multiplier = 1; break; case 5: delay.setDelay( 10 ); multiplier = 1; break; case 6: delay.setDelay( 16 ); multiplier = 2; break; case 7: delay.setDelay( 12 ); multiplier = 2; break; case 8: delay.setDelay( 16 ); multiplier = 4; break; case 9: delay.setDelay( 8 ); multiplier = 4; break; default: delay.setDelay( 1 ); multiplier = 4; } } } namespace Game { void AnimateDelaysInitialize(); } void Game::AnimateDelaysInitialize() { delays[SCROLL_DELAY].setDelay( 20 ); delays[SCROLL_START_DELAY].setDelay( 20 ); delays[CURSOR_BLINK_DELAY].setDelay( 440 ); delays[MAIN_MENU_DELAY].setDelay( 250 ); delays[MAPS_DELAY].setDelay( 250 ); delays[CASTLE_TAVERN_DELAY].setDelay( 75 ); delays[CASTLE_AROUND_DELAY].setDelay( 200 ); delays[CASTLE_BUYHERO_DELAY].setDelay( 130 ); delays[CASTLE_BUILD_DELAY].setDelay( 130 ); delays[CASTLE_UNIT_DELAY].setDelay( 150 ); delays[HEROES_FADE_DELAY].setDelay( 32 ); delays[HEROES_PICKUP_DELAY].setDelay( 40 ); delays[PUZZLE_FADE_DELAY].setDelay( 50 ); delays[BATTLE_DIALOG_DELAY].setDelay( 75 ); delays[BATTLE_FRAME_DELAY].setDelay( 120 ); delays[BATTLE_MISSILE_DELAY].setDelay( 40 ); delays[BATTLE_SPELL_DELAY].setDelay( 90 ); delays[BATTLE_DISRUPTING_DELAY].setDelay( 20 ); delays[BATTLE_CATAPULT_DELAY].setDelay( 90 ); delays[BATTLE_CATAPULT_BOULDER_DELAY].setDelay( 40 ); delays[BATTLE_CATAPULT_CLOUD_DELAY].setDelay( 40 ); delays[BATTLE_BRIDGE_DELAY].setDelay( 90 ); delays[BATTLE_IDLE_DELAY].setDelay( 150 ); delays[BATTLE_OPPONENTS_DELAY].setDelay( 75 ); delays[BATTLE_FLAGS_DELAY].setDelay( 250 ); delays[BATTLE_POPUP_DELAY].setDelay( 800 ); delays[BATTLE_COLOR_CYCLE_DELAY].setDelay( 220 ); delays[BATTLE_SELECTED_UNIT_DELAY].setDelay( 160 ); delays[CUSTOM_BATTLE_UNIT_MOVEMENT_DELAY].setDelay( 10 ); delays[CURRENT_HERO_DELAY].setDelay( 10 ); delays[CURRENT_AI_DELAY].setDelay( 10 ); for ( fheroes2::TimeDelay & delay : delays ) { delay.reset(); } UpdateGameSpeed(); } void Game::AnimateResetDelay( const DelayType delayType ) { delays[delayType].reset(); } bool Game::validateCustomAnimationDelay( const uint64_t delayMs ) { if ( delays[Game::DelayType::CUSTOM_DELAY].isPassed( delayMs ) ) { delays[Game::DelayType::CUSTOM_DELAY].reset(); return true; } return false; } bool Game::validateAnimationDelay( const DelayType delayType ) { assert( delayType != Game::DelayType::CUSTOM_DELAY ); if ( delays[delayType].isPassed() ) { delays[delayType].reset(); return true; } return false; } void Game::passAnimationDelay( const DelayType delayType ) { assert( delayType != Game::DelayType::CUSTOM_DELAY ); delays[delayType].pass(); } void Game::passCustomAnimationDelay( const uint64_t delayMs ) { delays[Game::DelayType::CUSTOM_DELAY].setDelay( delayMs ); delays[Game::DelayType::CUSTOM_DELAY].pass(); } void Game::setCustomUnitMovementDelay( const uint64_t delayMs ) { delays[Game::DelayType::CUSTOM_BATTLE_UNIT_MOVEMENT_DELAY].setDelay( delayMs > 0 ? delayMs : 1 ); } void Game::UpdateGameSpeed() { const Settings & conf = Settings::Get(); SetupHeroMovement( conf.HeroesMoveSpeed(), delays[CURRENT_HERO_DELAY], humanHeroMultiplier ); SetupHeroMovement( conf.AIMoveSpeed(), delays[CURRENT_AI_DELAY], aiHeroMultiplier ); const int32_t battleSpeed = conf.BattleSpeed(); // For the battle speed = 10 avoid the zero delay and set animation speed to the 1/3 of battleSpeedAdjustment step. const double adjustedBattleSpeed = ( battleSpeed < 10 ) ? ( ( 10 - battleSpeed ) * battleSpeedAdjustment ) : ( battleSpeedAdjustment / 3 ); // Reduce the Idle animation adjustment interval to: 1.2 for speed 1 ... 0.8 for speed 10. const double adjustedIdleAnimationSpeed = ( 28 - battleSpeed ) / 22.5; delays[BATTLE_FRAME_DELAY].setDelay( static_cast( 120 * adjustedBattleSpeed ) ); delays[BATTLE_MISSILE_DELAY].setDelay( static_cast( 40 * adjustedBattleSpeed ) ); delays[BATTLE_SPELL_DELAY].setDelay( static_cast( 75 * adjustedBattleSpeed ) ); delays[BATTLE_DISRUPTING_DELAY].setDelay( static_cast( 25 * adjustedBattleSpeed ) ); delays[BATTLE_CATAPULT_DELAY].setDelay( static_cast( 90 * adjustedBattleSpeed ) ); delays[BATTLE_CATAPULT_BOULDER_DELAY].setDelay( static_cast( 40 * adjustedBattleSpeed ) ); delays[BATTLE_CATAPULT_CLOUD_DELAY].setDelay( static_cast( 40 * adjustedBattleSpeed ) ); delays[BATTLE_BRIDGE_DELAY].setDelay( static_cast( 90 * adjustedBattleSpeed ) ); delays[BATTLE_IDLE_DELAY].setDelay( static_cast( 150 * adjustedIdleAnimationSpeed ) ); delays[BATTLE_OPPONENTS_DELAY].setDelay( static_cast( 75 * adjustedIdleAnimationSpeed ) ); delays[BATTLE_FLAGS_DELAY].setDelay( static_cast( 250 * adjustedIdleAnimationSpeed ) ); } int Game::HumanHeroAnimSpeedMultiplier() { assert( humanHeroMultiplier > 0 ); return humanHeroMultiplier; } int Game::AIHeroAnimSpeedMultiplier() { assert( aiHeroMultiplier > 0 ); return aiHeroMultiplier; } uint32_t Game::ApplyBattleSpeed( uint32_t delay ) { const uint32_t battleSpeed = static_cast( battleSpeedAdjustment * ( 10 - Settings::Get().BattleSpeed() ) * delay ); return battleSpeed == 0 ? 1 : battleSpeed; } bool Game::hasEveryDelayPassed( const std::vector & delayTypes ) { for ( const Game::DelayType type : delayTypes ) { if ( !delays[type].isPassed() ) { return false; } } return true; } bool Game::isDelayNeeded( const std::vector & delayTypes ) { for ( const Game::DelayType type : delayTypes ) { assert( type != Game::DelayType::CUSTOM_DELAY ); if ( delays[type].isPassed() ) { return false; } } return true; } bool Game::isCustomDelayNeeded( const uint64_t delayMs ) { return !delays[Game::DelayType::CUSTOM_DELAY].isPassed( delayMs ); } uint64_t Game::getAnimationDelayValue( const DelayType delayType ) { assert( delayType != Game::DelayType::CUSTOM_DELAY ); return delays[delayType].getDelay(); } fheroes2-1.0.12+dfsg/src/fheroes2/game/game_delays.h000066400000000000000000000104251456075706000221030ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2GAME_DELAYS_H #define H2GAME_DELAYS_H #include #include namespace Game { enum DelayType : int { SCROLL_DELAY = 0, SCROLL_START_DELAY, CURSOR_BLINK_DELAY, MAIN_MENU_DELAY, MAPS_DELAY, CASTLE_TAVERN_DELAY, CASTLE_AROUND_DELAY, CASTLE_BUYHERO_DELAY, CASTLE_BUILD_DELAY, CASTLE_UNIT_DELAY, HEROES_FADE_DELAY, HEROES_PICKUP_DELAY, PUZZLE_FADE_DELAY, BATTLE_DIALOG_DELAY, BATTLE_FRAME_DELAY, BATTLE_MISSILE_DELAY, BATTLE_SPELL_DELAY, BATTLE_DISRUPTING_DELAY, BATTLE_CATAPULT_DELAY, BATTLE_CATAPULT_BOULDER_DELAY, BATTLE_CATAPULT_CLOUD_DELAY, BATTLE_BRIDGE_DELAY, BATTLE_IDLE_DELAY, BATTLE_OPPONENTS_DELAY, BATTLE_FLAGS_DELAY, BATTLE_POPUP_DELAY, BATTLE_COLOR_CYCLE_DELAY, BATTLE_SELECTED_UNIT_DELAY, CUSTOM_BATTLE_UNIT_MOVEMENT_DELAY, CURRENT_HERO_DELAY, CURRENT_AI_DELAY, CUSTOM_DELAY, // IMPORTANT!!! All new entries must be put before this entry! LAST_DELAY }; // If delay is passed reset it and return true. Otherwise return false. DelayType::CUSTOM_DELAY must not be passed! bool validateAnimationDelay( const DelayType delayType ); // If custom delay (DelayType::CUSTOM_DELAY) is passed reset it and return true. Otherwise return false. bool validateCustomAnimationDelay( const uint64_t delayMs ); // Explicitly pass delay. Useful for loops when first iterator must pass. DelayType::CUSTOM_DELAY must not be passed! void passAnimationDelay( const DelayType delayType ); // Explicitly pass custom delay. Useful for loops when first iterator must pass. void passCustomAnimationDelay( const uint64_t delayMs ); // Set the delay between frames of unit movement animation sequence on the battlefield. void setCustomUnitMovementDelay( const uint64_t delayMs ); void AnimateResetDelay( const DelayType delayType ); void UpdateGameSpeed(); uint32_t ApplyBattleSpeed( uint32_t delay ); // Returns the animation speed multiplier for a human-controlled hero. int HumanHeroAnimSpeedMultiplier(); // Returns the animation speed multiplier for an AI-controlled hero. int AIHeroAnimSpeedMultiplier(); // Returns true if every of delay type has passed. bool hasEveryDelayPassed( const std::vector & delayTypes ); // Returns true if every of delay type is not passed yet. DelayType::CUSTOM_DELAY must not be added in this function! bool isDelayNeeded( const std::vector & delayTypes ); bool isCustomDelayNeeded( const uint64_t delayMs ); // Custom delay must never be called in this function. uint64_t getAnimationDelayValue( const DelayType delayType ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/game/game_highscores.cpp000066400000000000000000000452761456075706000233270ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include "agg_image.h" #include "campaign_savedata.h" #include "campaign_scenariodata.h" #include "cursor.h" #include "dialog.h" #include "game.h" #include "game_delays.h" #include "game_hotkeys.h" #include "game_io.h" #include "game_mode.h" #include "game_over.h" #include "highscores.h" #include "icn.h" #include "image.h" #include "localevent.h" #include "maps_fileinfo.h" #include "math_base.h" #include "monster.h" #include "monster_anim.h" #include "screen.h" #include "settings.h" #include "system.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_language.h" #include "ui_text.h" #include "ui_tool.h" #include "ui_window.h" #include "world.h" #ifdef WITH_DEBUG #include "logging.h" #endif namespace { const std::string highScoreFileName = "fheroes2.hgs"; fheroes2::HighScoreDataContainer highScoreDataContainer; const int32_t initialHighScoreEntryOffsetY = 72; const int32_t highScoreEntryStepY = 40; const int32_t playerNameOffset = 88; const int32_t scenarioNameOffset = 244; const int32_t dayCountOffset = 403; const int32_t ratingOffset = 484; const int32_t monsterOffsetX = 554; const int32_t monsterOffsetY = 91; // Calculate the mini monster sprites ROI. fheroes2::Rect getAnimationRoi( const fheroes2::Point & position, const bool isCampaign ) { fheroes2::Rect roi; std::vector animationIndex; if ( isCampaign ) { const std::vector & highScoreData = highScoreDataContainer.getHighScoresCampaign(); animationIndex.reserve( highScoreData.size() ); for ( const fheroes2::HighscoreData & data : highScoreData ) { animationIndex.push_back( fheroes2::HighScoreDataContainer::getMonsterByDay( data.rating ).GetSpriteIndex() * 9 ); } } else { const std::vector & highScoreData = highScoreDataContainer.getHighScoresStandard(); animationIndex.reserve( highScoreData.size() ); for ( const fheroes2::HighscoreData & data : highScoreData ) { animationIndex.push_back( fheroes2::HighScoreDataContainer::getMonsterByRating( data.rating ).GetSpriteIndex() * 9 ); } } // Animation frames: 0 - static part, 1-6 - animation, 7 and 8 - attacking sprite. We analyze and render here only 0-6 frames. for ( uint32_t i = 0; i < 7; ++i ) { const fheroes2::Sprite & topMonsterSprite = fheroes2::AGG::GetICN( ICN::MINIMON, animationIndex.front() + i ); const fheroes2::Sprite & bottomMonsterSprite = fheroes2::AGG::GetICN( ICN::MINIMON, animationIndex.back() + i ); // We search for the most top sprite offset of the top monster sprite, the most bottom offset of the bottom monster. int32_t offset = topMonsterSprite.y(); if ( offset < roi.y ) { roi.y = offset; } // The offset of the sprite right border from the render point. offset = bottomMonsterSprite.y() + bottomMonsterSprite.height(); if ( offset > roi.height ) { roi.height = offset; } for ( const uint32_t index : animationIndex ) { const fheroes2::Sprite & monsterSprite = fheroes2::AGG::GetICN( ICN::MINIMON, index + i ); offset = monsterSprite.x(); if ( offset < roi.x ) { roi.x = offset; } offset += monsterSprite.width(); if ( offset > roi.width ) { roi.width = offset; } } } // We have 10 high score records and should take into account 9 intervals between each record. roi.height += highScoreEntryStepY * 9 - roi.y; roi.width -= roi.x; // We take into account the High Scores windows position and first monster sprite offset. roi.x += position.x + monsterOffsetX; roi.y += position.y + monsterOffsetY; return roi; } void redrawMonstersAnimation( const fheroes2::Point & position, uint32_t & monsterAnimationFrameId, const std::vector & highScores, const std::function & getMonster ) { ++monsterAnimationFrameId; fheroes2::Display & display = fheroes2::Display::instance(); const std::array & monsterAnimationSequence = fheroes2::getMonsterAnimationSequence(); int32_t offsetY = position.y; for ( const fheroes2::HighscoreData & data : highScores ) { // Animation frame of a creature is based on its position on screen and common animation frame ID. const uint32_t monsterAnimationId = monsterAnimationSequence[( position.x + offsetY + data.dayCount + monsterAnimationFrameId ) % monsterAnimationSequence.size()]; const uint32_t secondaryMonsterAnimationIndex = getMonster( data.rating ).GetSpriteIndex() * 9 + 1 + monsterAnimationId; const fheroes2::Sprite & secondaryMonsterSprite = fheroes2::AGG::GetICN( ICN::MINIMON, secondaryMonsterAnimationIndex ); fheroes2::Blit( secondaryMonsterSprite, display, secondaryMonsterSprite.x() + position.x + monsterOffsetX, secondaryMonsterSprite.y() + offsetY + monsterOffsetY ); offsetY += highScoreEntryStepY; } } void redrawHighScoreAnimation( const fheroes2::Point & position, uint32_t & monsterAnimationFrameId, const bool isCampaign ) { if ( isCampaign ) { redrawMonstersAnimation( position, monsterAnimationFrameId, highScoreDataContainer.getHighScoresCampaign(), fheroes2::HighScoreDataContainer::getMonsterByDay ); } else { redrawMonstersAnimation( position, monsterAnimationFrameId, highScoreDataContainer.getHighScoresStandard(), fheroes2::HighScoreDataContainer::getMonsterByRating ); } } void redrawHighScoreScreen( const fheroes2::Point & position, const int32_t selectedScoreIndex, const std::vector & highScores, const uint32_t titleImageIndex, const std::function & getMonster ) { fheroes2::Display & display = fheroes2::Display::instance(); // Draw background. const fheroes2::Sprite & background = fheroes2::AGG::GetICN( ICN::HSBKG, 0 ); fheroes2::Copy( background, 0, 0, display, position.x, position.y, background.width(), background.height() ); fheroes2::Blit( fheroes2::AGG::GetICN( ICN::HISCORE, titleImageIndex ), display, position.x + 50, position.y + 31 ); fheroes2::Text text( "", fheroes2::FontType::normalWhite() ); int32_t scoreIndex = 0; int32_t offsetY = position.y; for ( const fheroes2::HighscoreData & data : highScores ) { const fheroes2::FontType font = ( scoreIndex == selectedScoreIndex ) ? fheroes2::FontType::normalYellow() : fheroes2::FontType::normalWhite(); const fheroes2::LanguageSwitcher languageSwitcher( fheroes2::getLanguageFromAbbreviation( data.languageAbbreviation ) ); text.set( data.playerName, font ); text.draw( position.x + playerNameOffset, offsetY + initialHighScoreEntryOffsetY, display ); text.set( data.scenarioName, font ); text.draw( position.x + scenarioNameOffset, offsetY + initialHighScoreEntryOffsetY, display ); text.set( std::to_string( data.dayCount ), font ); text.draw( position.x + dayCountOffset, offsetY + initialHighScoreEntryOffsetY, display ); text.set( std::to_string( data.rating ), font ); text.draw( position.x + ratingOffset, offsetY + initialHighScoreEntryOffsetY, display ); // Render static part of monster animation. const Monster monster = getMonster( data.rating ); const uint32_t baseMonsterAnimationIndex = monster.GetSpriteIndex() * 9; const fheroes2::Sprite & baseMonsterSprite = fheroes2::AGG::GetICN( ICN::MINIMON, baseMonsterAnimationIndex ); fheroes2::Blit( baseMonsterSprite, display, baseMonsterSprite.x() + position.x + 554, baseMonsterSprite.y() + offsetY + 91 ); offsetY += highScoreEntryStepY; ++scoreIndex; } } } fheroes2::GameMode Game::DisplayHighScores( const bool isCampaign ) { GameOver::Result & gameResult = GameOver::Result::Get(); #ifdef WITH_DEBUG if ( IS_DEVEL() && ( gameResult.GetResult() & GameOver::WINS ) ) { std::string msg = "Developer mode is active, the result will not be saved! \n\n Your result: "; if ( isCampaign ) { msg += std::to_string( Campaign::CampaignSaveData::Get().getDaysPassed() ); } else { msg += std::to_string( GetRating() * getGameOverScoreFactor() / 100 ); } fheroes2::showMessage( fheroes2::Text( "High Scores", fheroes2::FontType::normalYellow() ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK ); gameResult.ResetResult(); return fheroes2::GameMode::MAIN_MENU; } #endif const std::string highScoreDataPath = System::concatPath( GetSaveDir(), highScoreFileName ); if ( !highScoreDataContainer.load( highScoreDataPath ) ) { // Unable to load the file. Let's populate with the default values. highScoreDataContainer.clear(); highScoreDataContainer.populateStandardDefaultHighScores(); highScoreDataContainer.populateCampaignDefaultHighScores(); } int32_t selectedEntryIndex = -1; const bool isAfterGameCompletion = ( ( gameResult.GetResult() & GameOver::WINS ) != 0 ); fheroes2::Display & display = fheroes2::Display::instance(); const bool isDefaultScreenSize = display.isDefaultSize(); if ( isAfterGameCompletion ) { const auto inputPlayerName = []( std::string & playerName ) { Dialog::InputString( _( "Your Name" ), playerName, std::string(), 15 ); if ( playerName.empty() ) { playerName = _( "Unknown Hero" ); } }; const uint32_t completionTime = fheroes2::HighscoreData::generateCompletionTime(); std::string lang = fheroes2::getLanguageAbbreviation( fheroes2::getCurrentLanguage() ); // Check whether the game result is good enough to be put on high score board. If not then just skip showing the player name dialog. if ( isCampaign ) { const Campaign::CampaignSaveData & campaignSaveData = Campaign::CampaignSaveData::Get(); const uint32_t daysPassed = campaignSaveData.getDaysPassed(); // Rating is calculated based on difficulty of campaign. const uint32_t rating = daysPassed * campaignSaveData.getCampaignDifficultyPercent() / 100; const auto & campaignHighscoreData = highScoreDataContainer.getHighScoresCampaign(); assert( !campaignHighscoreData.empty() ); if ( campaignHighscoreData.back().rating < rating ) { gameResult.ResetResult(); return fheroes2::GameMode::MAIN_MENU; } std::string playerName; inputPlayerName( playerName ); selectedEntryIndex = highScoreDataContainer.registerScoreCampaign( { std::move( lang ), playerName, Campaign::getCampaignName( campaignSaveData.getCampaignID() ), completionTime, daysPassed, rating, world.GetMapSeed() } ); } else { const uint32_t rating = GetRating() * getGameOverScoreFactor() / 100; const auto & standardHighscoreData = highScoreDataContainer.getHighScoresStandard(); assert( !standardHighscoreData.empty() ); if ( standardHighscoreData.back().rating > rating ) { gameResult.ResetResult(); return fheroes2::GameMode::MAIN_MENU; } const uint32_t daysPassed = world.CountDay(); std::string playerName; inputPlayerName( playerName ); selectedEntryIndex = highScoreDataContainer.registerScoreStandard( { std::move( lang ), playerName, Settings::Get().getCurrentMapInfo().name, completionTime, daysPassed, rating, world.GetMapSeed() } ); } highScoreDataContainer.save( highScoreDataPath ); gameResult.ResetResult(); // Fade-out game screen. fheroes2::fadeOutDisplay(); } else if ( isDefaultScreenSize ) { fheroes2::fadeOutDisplay(); } // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); const fheroes2::Sprite & back = fheroes2::AGG::GetICN( ICN::HSBKG, 0 ); const fheroes2::Point top{ ( display.width() - back.width() ) / 2, ( display.height() - back.height() ) / 2 }; const fheroes2::StandardWindow border( display.DEFAULT_WIDTH, display.DEFAULT_HEIGHT, false ); uint32_t monsterAnimationFrameId = 0; if ( isCampaign ) { redrawHighScoreScreen( top, selectedEntryIndex, highScoreDataContainer.getHighScoresCampaign(), 7, fheroes2::HighScoreDataContainer::getMonsterByDay ); } else { redrawHighScoreScreen( top, selectedEntryIndex, highScoreDataContainer.getHighScoresStandard(), 6, fheroes2::HighScoreDataContainer::getMonsterByRating ); } const fheroes2::Rect animationRoi = getAnimationRoi( top, isCampaign ); // Make a copy of static monsters animation part with background to restore it during animations. fheroes2::ImageRestorer backgroundCopy( display, animationRoi.x, animationRoi.y, animationRoi.width, animationRoi.height ); // Render first animation frame. redrawHighScoreAnimation( top, monsterAnimationFrameId, isCampaign ); fheroes2::Button buttonOtherHighScore( top.x + 8, top.y + 315, isCampaign ? ICN::BUTTON_HSCORES_VERTICAL_CAMPAIGN : ICN::BUTTON_HSCORES_VERTICAL_STANDARD, 0, 1 ); fheroes2::Button buttonExit( top.x + back.width() - 36, top.y + 315, ICN::BUTTON_HSCORES_VERTICAL_EXIT, 0, 1 ); buttonOtherHighScore.draw(); buttonExit.draw(); // Fade-in High Scores screen. if ( isAfterGameCompletion ) { fheroes2::fadeInDisplay(); } else { if ( !isDefaultScreenSize ) { // We need to expand the ROI for the next render to properly render window borders and shadow. display.updateNextRenderRoi( border.totalArea() ); } fheroes2::fadeInDisplay( border.activeArea(), !isDefaultScreenSize ); } LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents( Game::isDelayNeeded( { Game::MAPS_DELAY } ) ) ) { le.MousePressLeft( buttonOtherHighScore.area() ) ? buttonOtherHighScore.drawOnPress() : buttonOtherHighScore.drawOnRelease(); le.MousePressLeft( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); if ( le.MouseClickLeft( buttonExit.area() ) || HotKeyCloseWindow() ) { if ( isAfterGameCompletion || isDefaultScreenSize ) { fheroes2::fadeOutDisplay(); Game::setDisplayFadeIn(); } else { fheroes2::fadeOutDisplay( border.activeArea(), true ); } return fheroes2::GameMode::MAIN_MENU; } if ( le.MouseClickLeft( buttonOtherHighScore.area() ) ) { return isCampaign ? fheroes2::GameMode::HIGHSCORES_STANDARD : fheroes2::GameMode::HIGHSCORES_CAMPAIGN; } if ( le.MousePressRight( buttonExit.area() ) ) { fheroes2::showMessage( fheroes2::Text( _( "Exit" ), fheroes2::FontType::normalYellow() ), fheroes2::Text( _( "Exit this menu." ), fheroes2::FontType::normalWhite() ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonOtherHighScore.area() ) ) { if ( isCampaign ) { fheroes2::showMessage( fheroes2::Text( _( "Standard" ), fheroes2::FontType::normalYellow() ), fheroes2::Text( _( "View High Scores for Standard Maps." ), fheroes2::FontType::normalWhite() ), Dialog::ZERO ); } else { fheroes2::showMessage( fheroes2::Text( _( "Campaign" ), fheroes2::FontType::normalYellow() ), fheroes2::Text( _( "View High Scores for Campaigns." ), fheroes2::FontType::normalWhite() ), Dialog::ZERO ); } } if ( Game::validateAnimationDelay( Game::MAPS_DELAY ) ) { // Restore background with static monster animation part. backgroundCopy.restore(); redrawHighScoreAnimation( top, monsterAnimationFrameId, isCampaign ); buttonOtherHighScore.draw(); buttonExit.draw(); display.render( animationRoi ); } } return fheroes2::GameMode::QUIT_GAME; } fheroes2-1.0.12+dfsg/src/fheroes2/game/game_hotkeys.cpp000066400000000000000000001012571456075706000226470ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "game_hotkeys.h" #include #include #include #include #include #include #include #include #include #include "battle_arena.h" #include "dialog.h" #include "localevent.h" #include "logging.h" #include "players.h" #include "settings.h" #include "system.h" #include "tinyconfig.h" #include "tools.h" #include "translations.h" #include "ui_dialog.h" #include "ui_language.h" namespace { struct HotKeyEventInfo { HotKeyEventInfo() = default; HotKeyEventInfo( const Game::HotKeyCategory category_, const char * name_, const fheroes2::Key key_ ) : category( category_ ) , name( name_ ) , key( key_ ) { // Do nothing. } HotKeyEventInfo( const HotKeyEventInfo & ) = default; HotKeyEventInfo( HotKeyEventInfo && ) = default; ~HotKeyEventInfo() = default; HotKeyEventInfo & operator=( const HotKeyEventInfo & ) = default; HotKeyEventInfo & operator=( HotKeyEventInfo && ) = default; Game::HotKeyCategory category = Game::HotKeyCategory::DEFAULT; const char * name = ""; fheroes2::Key key = fheroes2::Key::NONE; }; constexpr typename std::underlying_type::type hotKeyEventToInt( Game::HotKeyEvent value ) { return static_cast::type>( value ); } std::array hotKeyEventInfo; void initializeHotKeyEvents() { // Make sure that event name is unique! hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::DEFAULT_OKAY )] = { Game::HotKeyCategory::DEFAULT, gettext_noop( "hotkey|default okay event" ), fheroes2::Key::KEY_ENTER }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::DEFAULT_CANCEL )] = { Game::HotKeyCategory::DEFAULT, gettext_noop( "hotkey|default cancel event" ), fheroes2::Key::KEY_ESCAPE }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::DEFAULT_LEFT )] = { Game::HotKeyCategory::DEFAULT, gettext_noop( "hotkey|default left" ), fheroes2::Key::KEY_LEFT }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::DEFAULT_RIGHT )] = { Game::HotKeyCategory::DEFAULT, gettext_noop( "hotkey|default right" ), fheroes2::Key::KEY_RIGHT }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::DEFAULT_UP )] = { Game::HotKeyCategory::DEFAULT, gettext_noop( "hotkey|default up" ), fheroes2::Key::KEY_UP }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::DEFAULT_DOWN )] = { Game::HotKeyCategory::DEFAULT, gettext_noop( "hotkey|default down" ), fheroes2::Key::KEY_DOWN }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::GLOBAL_TOGGLE_FULLSCREEN )] = { Game::HotKeyCategory::GLOBAL, gettext_noop( "hotkey|toggle fullscreen" ), fheroes2::Key::KEY_F4 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::GLOBAL_TOGGLE_TEXT_SUPPORT_MODE )] = { Game::HotKeyCategory::GLOBAL, gettext_noop( "hotkey|toggle text support mode" ), fheroes2::Key::KEY_F10 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::MAIN_MENU_NEW_GAME )] = { Game::HotKeyCategory::MAIN_MENU, gettext_noop( "hotkey|new game" ), fheroes2::Key::KEY_N }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::MAIN_MENU_LOAD_GAME )] = { Game::HotKeyCategory::MAIN_MENU, gettext_noop( "hotkey|load game" ), fheroes2::Key::KEY_L }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::MAIN_MENU_HIGHSCORES )] = { Game::HotKeyCategory::MAIN_MENU, gettext_noop( "hotkey|highscores" ), fheroes2::Key::KEY_H }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::MAIN_MENU_CREDITS )] = { Game::HotKeyCategory::MAIN_MENU, gettext_noop( "hotkey|credits" ), fheroes2::Key::KEY_C }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::MAIN_MENU_STANDARD )] = { Game::HotKeyCategory::MAIN_MENU, gettext_noop( "hotkey|standard game" ), fheroes2::Key::KEY_S }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::MAIN_MENU_CAMPAIGN )] = { Game::HotKeyCategory::MAIN_MENU, gettext_noop( "hotkey|campaign game" ), fheroes2::Key::KEY_C }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::MAIN_MENU_MULTI )] = { Game::HotKeyCategory::MAIN_MENU, gettext_noop( "hotkey|multi-player game" ), fheroes2::Key::KEY_M }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::MAIN_MENU_SETTINGS )] = { Game::HotKeyCategory::MAIN_MENU, gettext_noop( "hotkey|settings" ), fheroes2::Key::KEY_T }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::MAIN_MENU_QUIT )] = { Game::HotKeyCategory::MAIN_MENU, gettext_noop( "hotkey|quit" ), fheroes2::Key::KEY_Q }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::MAIN_MENU_SELECT_MAP )] = { Game::HotKeyCategory::MAIN_MENU, gettext_noop( "hotkey|select map" ), fheroes2::Key::KEY_S }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::MAIN_MENU_MAP_SIZE_SMALL )] = { Game::HotKeyCategory::MAIN_MENU, gettext_noop( "hotkey|select small map size" ), fheroes2::Key::KEY_S }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::MAIN_MENU_MAP_SIZE_MEDIUM )] = { Game::HotKeyCategory::MAIN_MENU, gettext_noop( "hotkey|select medium map size" ), fheroes2::Key::KEY_M }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::MAIN_MENU_MAP_SIZE_LARGE )] = { Game::HotKeyCategory::MAIN_MENU, gettext_noop( "hotkey|select large map size" ), fheroes2::Key::KEY_L }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::MAIN_MENU_MAP_SIZE_EXTRA_LARGE )] = { Game::HotKeyCategory::MAIN_MENU, gettext_noop( "hotkey|select extra large map size" ), fheroes2::Key::KEY_X }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::MAIN_MENU_MAP_SIZE_ALL )] = { Game::HotKeyCategory::MAIN_MENU, gettext_noop( "hotkey|select all map sizes" ), fheroes2::Key::KEY_A }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::MAIN_MENU_HOTSEAT )] = { Game::HotKeyCategory::MAIN_MENU, gettext_noop( "hotkey|hotseat game" ), fheroes2::Key::KEY_H }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::MAIN_MENU_BATTLEONLY )] = { Game::HotKeyCategory::MAIN_MENU, gettext_noop( "hotkey|battle only game" ), fheroes2::Key::KEY_B }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::MAIN_MENU_NEW_ORIGINAL_CAMPAIGN )] = { Game::HotKeyCategory::MAIN_MENU, gettext_noop( "hotkey|choose the original campaign" ), fheroes2::Key::KEY_O }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::MAIN_MENU_NEW_EXPANSION_CAMPAIGN )] = { Game::HotKeyCategory::MAIN_MENU, gettext_noop( "hotkey|choose the expansion campaign" ), fheroes2::Key::KEY_E }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::EDITOR_MAIN_MENU )] = { Game::HotKeyCategory::EDITOR, gettext_noop( "hotkey|map editor main menu" ), fheroes2::Key::KEY_E }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::EDITOR_NEW_MAP_MENU )] = { Game::HotKeyCategory::EDITOR, gettext_noop( "hotkey|new map menu" ), fheroes2::Key::KEY_N }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::EDITOR_LOAD_MAP_MENU )] = { Game::HotKeyCategory::EDITOR, gettext_noop( "hotkey|load map menu" ), fheroes2::Key::KEY_L }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::EDITOR_FROM_SCRATCH_MAP_MENU )] = { Game::HotKeyCategory::EDITOR, gettext_noop( "hotkey|new map from scratch" ), fheroes2::Key::KEY_S }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::EDITOR_RANDOM_MAP_MENU )] = { Game::HotKeyCategory::EDITOR, gettext_noop( "hotkey|new random map" ), fheroes2::Key::KEY_R }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::EDITOR_UNDO_LAST_ACTION )] = { Game::HotKeyCategory::EDITOR, gettext_noop( "hotkey|undo last action" ), fheroes2::Key::KEY_U }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::EDITOR_REDO_LAST_ACTION )] = { Game::HotKeyCategory::EDITOR, gettext_noop( "hotkey|redo last action" ), fheroes2::Key::KEY_R }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::CAMPAIGN_ROLAND )] = { Game::HotKeyCategory::CAMPAIGN, gettext_noop( "hotkey|roland campaign" ), fheroes2::Key::KEY_1 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::CAMPAIGN_ARCHIBALD )] = { Game::HotKeyCategory::CAMPAIGN, gettext_noop( "hotkey|archibald campaign" ), fheroes2::Key::KEY_2 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::CAMPAIGN_PRICE_OF_LOYALTY )] = { Game::HotKeyCategory::CAMPAIGN, gettext_noop( "hotkey|the price of loyalty campaign" ), fheroes2::Key::KEY_1 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::CAMPAIGN_VOYAGE_HOME )] = { Game::HotKeyCategory::CAMPAIGN, gettext_noop( "hotkey|voyage home campaign" ), fheroes2::Key::KEY_2 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::CAMPAIGN_WIZARDS_ISLE )] = { Game::HotKeyCategory::CAMPAIGN, gettext_noop( "hotkey|wizard's isle campaign" ), fheroes2::Key::KEY_3 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::CAMPAIGN_DESCENDANTS )] = { Game::HotKeyCategory::CAMPAIGN, gettext_noop( "hotkey|descendants campaign" ), fheroes2::Key::KEY_4 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::CAMPAIGN_SELECT_FIRST_BONUS )] = { Game::HotKeyCategory::CAMPAIGN, gettext_noop( "hotkey|select first campaign bonus" ), fheroes2::Key::KEY_1 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::CAMPAIGN_SELECT_SECOND_BONUS )] = { Game::HotKeyCategory::CAMPAIGN, gettext_noop( "hotkey|select second campaign bonus" ), fheroes2::Key::KEY_2 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::CAMPAIGN_SELECT_THIRD_BONUS )] = { Game::HotKeyCategory::CAMPAIGN, gettext_noop( "hotkey|select third campaign bonus" ), fheroes2::Key::KEY_3 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::CAMPAIGN_VIEW_INTRO )] = { Game::HotKeyCategory::CAMPAIGN, gettext_noop( "hotkey|view campaign intro" ), fheroes2::Key::KEY_V }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::CAMPAIGN_SELECT_DIFFICULTY )] = { Game::HotKeyCategory::CAMPAIGN, gettext_noop( "hotkey|select campaign difficulty" ), fheroes2::Key::KEY_D }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::CAMPAIGN_RESTART_SCENARIO )] = { Game::HotKeyCategory::CAMPAIGN, gettext_noop( "hotkey|restart campaign scenario" ), fheroes2::Key::KEY_R }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_LEFT )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|world map left" ), fheroes2::Key::KEY_KP_4 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_RIGHT )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|world map right" ), fheroes2::Key::KEY_KP_6 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_UP )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|world map up" ), fheroes2::Key::KEY_KP_8 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_DOWN )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|world map down" ), fheroes2::Key::KEY_KP_2 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_UP_LEFT )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|world map up left" ), fheroes2::Key::KEY_KP_7 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_UP_RIGHT )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|world map up right" ), fheroes2::Key::KEY_KP_9 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_DOWN_LEFT )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|world map down left" ), fheroes2::Key::KEY_KP_1 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_DOWN_RIGHT )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|world map down right" ), fheroes2::Key::KEY_KP_3 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_SAVE_GAME )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|save game" ), fheroes2::Key::KEY_S }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_NEXT_HERO )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|next hero" ), fheroes2::Key::KEY_H }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_START_HERO_MOVEMENT )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|start hero movement" ), fheroes2::Key::KEY_M }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_CAST_SPELL )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|cast adventure spell" ), fheroes2::Key::KEY_C }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_SLEEP_HERO )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|put hero to sleep" ), fheroes2::Key::KEY_Z }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_NEXT_TOWN )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|next town" ), fheroes2::Key::KEY_T }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_END_TURN )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|end turn" ), fheroes2::Key::KEY_E }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_FILE_OPTIONS )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|file options" ), fheroes2::Key::KEY_F }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_ADVENTURE_OPTIONS )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|adventure options" ), fheroes2::Key::KEY_A }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_PUZZLE_MAP )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|puzzle map" ), fheroes2::Key::KEY_P }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_SCENARIO_INFORMATION )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|scenario information" ), fheroes2::Key::KEY_I }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_DIG_ARTIFACT )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|dig for artifact" ), fheroes2::Key::KEY_D }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_VIEW_WORLD )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|view world" ), fheroes2::Key::KEY_V }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_KINGDOM_SUMMARY )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|kingdom summary" ), fheroes2::Key::KEY_K }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_DEFAULT_ACTION )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|default action" ), fheroes2::Key::KEY_SPACE }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_OPEN_FOCUS )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|open focus" ), fheroes2::Key::KEY_ENTER }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_SYSTEM_OPTIONS )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|system options" ), fheroes2::Key::KEY_O }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_SCROLL_LEFT )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|scroll left" ), fheroes2::Key::KEY_LEFT }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_SCROLL_RIGHT )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|scroll right" ), fheroes2::Key::KEY_RIGHT }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_SCROLL_UP )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|scroll up" ), fheroes2::Key::KEY_UP }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_SCROLL_DOWN )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|scroll down" ), fheroes2::Key::KEY_DOWN }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_TOGGLE_CONTROL_PANEL )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|toggle control panel" ), fheroes2::Key::KEY_1 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_TOGGLE_RADAR )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|toggle radar" ), fheroes2::Key::KEY_2 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_TOGGLE_BUTTONS )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|toggle buttons" ), fheroes2::Key::KEY_3 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_TOGGLE_STATUS )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|toggle status" ), fheroes2::Key::KEY_4 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_TOGGLE_ICONS )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|toggle icons" ), fheroes2::Key::KEY_5 }; #if defined( WITH_DEBUG ) hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::WORLD_TRANSFER_CONTROL_TO_AI )] = { Game::HotKeyCategory::WORLD_MAP, gettext_noop( "hotkey|transfer control to ai" ), fheroes2::Key::KEY_F8 }; #endif hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::BATTLE_RETREAT )] = { Game::HotKeyCategory::BATTLE, gettext_noop( "hotkey|retreat from battle" ), fheroes2::Key::KEY_R }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::BATTLE_SURRENDER )] = { Game::HotKeyCategory::BATTLE, gettext_noop( "hotkey|surrender during battle" ), fheroes2::Key::KEY_S }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::BATTLE_AUTO_SWITCH )] = { Game::HotKeyCategory::BATTLE, gettext_noop( "hotkey|toggle battle auto mode" ), fheroes2::Key::KEY_A }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::BATTLE_AUTO_FINISH )] = { Game::HotKeyCategory::BATTLE, gettext_noop( "hotkey|finish the battle in auto mode" ), fheroes2::Key::KEY_Q }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::BATTLE_OPTIONS )] = { Game::HotKeyCategory::BATTLE, gettext_noop( "hotkey|battle options" ), fheroes2::Key::KEY_O }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::BATTLE_SKIP )] = { Game::HotKeyCategory::BATTLE, gettext_noop( "hotkey|skip turn in battle" ), fheroes2::Key::KEY_SPACE }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::BATTLE_CAST_SPELL )] = { Game::HotKeyCategory::BATTLE, gettext_noop( "hotkey|cast battle spell" ), fheroes2::Key::KEY_C }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::TOWN_DWELLING_LEVEL_1 )] = { Game::HotKeyCategory::TOWN, gettext_noop( "hotkey|dwelling level 1" ), fheroes2::Key::KEY_1 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::TOWN_DWELLING_LEVEL_2 )] = { Game::HotKeyCategory::TOWN, gettext_noop( "hotkey|dwelling level 2" ), fheroes2::Key::KEY_2 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::TOWN_DWELLING_LEVEL_3 )] = { Game::HotKeyCategory::TOWN, gettext_noop( "hotkey|dwelling level 3" ), fheroes2::Key::KEY_3 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::TOWN_DWELLING_LEVEL_4 )] = { Game::HotKeyCategory::TOWN, gettext_noop( "hotkey|dwelling level 4" ), fheroes2::Key::KEY_4 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::TOWN_DWELLING_LEVEL_5 )] = { Game::HotKeyCategory::TOWN, gettext_noop( "hotkey|dwelling level 5" ), fheroes2::Key::KEY_5 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::TOWN_DWELLING_LEVEL_6 )] = { Game::HotKeyCategory::TOWN, gettext_noop( "hotkey|dwelling level 6" ), fheroes2::Key::KEY_6 }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::TOWN_WELL )] = { Game::HotKeyCategory::TOWN, gettext_noop( "hotkey|well" ), fheroes2::Key::KEY_W }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::TOWN_MARKETPLACE )] = { Game::HotKeyCategory::TOWN, gettext_noop( "hotkey|marketplace" ), fheroes2::Key::KEY_M }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::TOWN_MAGE_GUILD )] = { Game::HotKeyCategory::TOWN, gettext_noop( "hotkey|mage guild" ), fheroes2::Key::KEY_S }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::TOWN_SHIPYARD )] = { Game::HotKeyCategory::TOWN, gettext_noop( "hotkey|shipyard" ), fheroes2::Key::KEY_N }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::TOWN_THIEVES_GUILD )] = { Game::HotKeyCategory::TOWN, gettext_noop( "hotkey|thieves guild" ), fheroes2::Key::KEY_T }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::TOWN_TAVERN )] = { Game::HotKeyCategory::TOWN, gettext_noop( "hotkey|tavern" ), fheroes2::Key::KEY_R }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::TOWN_CONSTRUCTION )] = { Game::HotKeyCategory::TOWN, gettext_noop( "hotkey|construction screen" ), fheroes2::Key::KEY_B }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::TOWN_WELL_BUY_ALL )] = { Game::HotKeyCategory::TOWN, gettext_noop( "hotkey|buy all monsters in well" ), fheroes2::Key::KEY_M }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::ARMY_SPLIT_STACK_BY_HALF )] = { Game::HotKeyCategory::ARMY, gettext_noop( "hotkey|split stack by half" ), fheroes2::Key::KEY_LEFT_SHIFT }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::ARMY_SPLIT_STACK_BY_ONE )] = { Game::HotKeyCategory::ARMY, gettext_noop( "hotkey|split stack by one" ), fheroes2::Key::KEY_LEFT_CONTROL }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::ARMY_JOIN_STACKS )] = { Game::HotKeyCategory::ARMY, gettext_noop( "hotkey|join stacks" ), fheroes2::Key::KEY_LEFT_ALT }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::ARMY_UPGRADE_TROOP )] = { Game::HotKeyCategory::ARMY, gettext_noop( "hotkey|upgrade troop" ), fheroes2::Key::KEY_U }; hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::ARMY_DISMISS )] = { Game::HotKeyCategory::ARMY, gettext_noop( "hotkey|dismiss hero or troop" ), fheroes2::Key::KEY_D }; } std::string getHotKeyFileContent() { std::ostringstream os; os << "# fheroes2 hotkey file (saved by version " << Settings::GetVersion() << ")" << std::endl; os << std::endl; Game::HotKeyCategory currentCategory = hotKeyEventInfo[hotKeyEventToInt( Game::HotKeyEvent::NONE ) + 1].category; os << "# " << getHotKeyCategoryName( currentCategory ) << ':' << std::endl; #if defined( WITH_DEBUG ) std::set duplicationStringVerifier; #endif const fheroes2::LanguageSwitcher languageSwitcher( fheroes2::SupportedLanguage::English ); for ( int32_t eventId = hotKeyEventToInt( Game::HotKeyEvent::NONE ) + 1; eventId < hotKeyEventToInt( Game::HotKeyEvent::NO_EVENT ); ++eventId ) { if ( currentCategory != hotKeyEventInfo[eventId].category ) { currentCategory = hotKeyEventInfo[eventId].category; os << std::endl; os << "# " << getHotKeyCategoryName( currentCategory ) << ':' << std::endl; } const char * eventName = _( hotKeyEventInfo[eventId].name ); assert( strlen( eventName ) > 0 ); #if defined( WITH_DEBUG ) const bool isUnique = duplicationStringVerifier.emplace( eventName ).second; assert( isUnique ); #endif os << eventName << " = " << StringUpper( KeySymGetName( hotKeyEventInfo[eventId].key ) ) << std::endl; } return os.str(); } } bool Game::HotKeyPressEvent( const HotKeyEvent eventID ) { const LocalEvent & le = LocalEvent::Get(); return le.KeyPress() && le.KeyValue() == hotKeyEventInfo[hotKeyEventToInt( eventID )].key; } bool Game::HotKeyHoldEvent( const HotKeyEvent eventID ) { const LocalEvent & le = LocalEvent::Get(); return le.KeyHold() && le.KeyValue() == hotKeyEventInfo[hotKeyEventToInt( eventID )].key; } fheroes2::Key Game::getHotKeyForEvent( const HotKeyEvent eventID ) { return hotKeyEventInfo[hotKeyEventToInt( eventID )].key; } void Game::setHotKeyForEvent( const HotKeyEvent eventID, const fheroes2::Key key ) { hotKeyEventInfo[hotKeyEventToInt( eventID )].key = key; } std::string Game::getHotKeyNameByEventId( const HotKeyEvent eventID ) { return StringUpper( KeySymGetName( hotKeyEventInfo[hotKeyEventToInt( eventID )].key ) ); } const char * Game::getHotKeyEventNameByEventId( const HotKeyEvent eventID ) { return hotKeyEventInfo[hotKeyEventToInt( eventID )].name; } std::vector> Game::getAllHotKeyEvents() { std::vector> events; events.reserve( hotKeyEventInfo.size() - 1 ); for ( size_t i = 1; i < hotKeyEventInfo.size(); ++i ) { events.emplace_back( static_cast( i ), hotKeyEventInfo[i].category ); } return events; } void Game::HotKeysLoad( const std::string & filename ) { initializeHotKeyEvents(); bool isFilePresent = System::IsFile( filename ); if ( isFilePresent ) { TinyConfig config( '=', '#' ); isFilePresent = config.Load( filename ); if ( isFilePresent ) { std::map nameToKey; for ( int32_t i = static_cast( fheroes2::Key::NONE ); i < static_cast( fheroes2::Key::LAST_KEY ); ++i ) { const fheroes2::Key key = static_cast( i ); nameToKey.try_emplace( StringUpper( KeySymGetName( key ) ), key ); } const fheroes2::LanguageSwitcher languageSwitcher( fheroes2::SupportedLanguage::English ); for ( int eventId = hotKeyEventToInt( HotKeyEvent::NONE ) + 1; eventId < hotKeyEventToInt( HotKeyEvent::NO_EVENT ); ++eventId ) { const char * eventName = _( hotKeyEventInfo[eventId].name ); std::string value = config.StrParams( eventName ); if ( value.empty() ) { continue; } value = StringUpper( value ); auto foundKey = nameToKey.find( value ); if ( foundKey == nameToKey.end() ) { continue; } hotKeyEventInfo[eventId].key = foundKey->second; DEBUG_LOG( DBG_GAME, DBG_INFO, "Event '" << eventName << "' has key '" << value << "'" ) } } } HotKeySave(); } void Game::HotKeySave() { // Save the latest information into the file. const std::string filename = System::concatPath( System::GetConfigDirectory( "fheroes2" ), "fheroes2.key" ); std::fstream file( filename.data(), std::fstream::out | std::fstream::trunc ); if ( !file ) { ERROR_LOG( "Unable to open hotkey settings file " << filename ) return; } const std::string & data = getHotKeyFileContent(); file.write( data.data(), data.size() ); } void Game::globalKeyDownEvent( const fheroes2::Key key, const int32_t modifier ) { if ( ( modifier & fheroes2::KeyModifier::KEY_MODIFIER_ALT ) || ( modifier & fheroes2::KeyModifier::KEY_MODIFIER_CTRL ) ) { return; } Settings & conf = Settings::Get(); if ( key == hotKeyEventInfo[hotKeyEventToInt( HotKeyEvent::GLOBAL_TOGGLE_FULLSCREEN )].key ) { conf.setFullScreen( !conf.FullScreen() ); conf.Save( Settings::configFileName ); } else if ( key == hotKeyEventInfo[hotKeyEventToInt( HotKeyEvent::GLOBAL_TOGGLE_TEXT_SUPPORT_MODE )].key ) { conf.setTextSupportMode( !conf.isTextSupportModeEnabled() ); conf.Save( Settings::configFileName ); } #if defined( WITH_DEBUG ) else if ( key == hotKeyEventInfo[hotKeyEventToInt( HotKeyEvent::WORLD_TRANSFER_CONTROL_TO_AI )].key ) { static bool recursiveCall = false; if ( !recursiveCall ) { class RecursionGuard { public: explicit RecursionGuard( bool & rc ) : _recursiveCall( rc ) { _recursiveCall = true; } RecursionGuard( const RecursionGuard & ) = delete; ~RecursionGuard() { _recursiveCall = false; } RecursionGuard & operator=( const RecursionGuard & ) = delete; private: bool & _recursiveCall; }; const RecursionGuard recursionGuard( recursiveCall ); Player * player = Settings::Get().GetPlayers().GetCurrent(); // Do not allow to transfer control to/from AI during battle if ( player && ( player->isControlHuman() || player->isAIAutoControlMode() ) && Battle::GetArena() == nullptr ) { if ( player->isAIAutoControlMode() ) { if ( fheroes2::showStandardTextMessage( _( "Warning" ), _( "Do you want to regain control from AI? The effect will take place only on the next turn." ), Dialog::YES | Dialog::NO ) == Dialog::YES ) { player->setAIAutoControlMode( false ); } } else { if ( fheroes2::showStandardTextMessage( _( "Warning" ), _( "Do you want to transfer control from you to the AI? The effect will take place only on the next turn." ), Dialog::YES | Dialog::NO ) == Dialog::YES ) { player->setAIAutoControlMode( true ); } } } } } #endif } const char * Game::getHotKeyCategoryName( const HotKeyCategory category ) { switch ( category ) { case HotKeyCategory::DEFAULT: return gettext_noop( "Default Actions" ); case HotKeyCategory::GLOBAL: return gettext_noop( "Global Actions" ); case HotKeyCategory::MAIN_MENU: return gettext_noop( "Main Menu" ); case HotKeyCategory::CAMPAIGN: return gettext_noop( "Campaign" ); case HotKeyCategory::WORLD_MAP: return gettext_noop( "World Map" ); case HotKeyCategory::BATTLE: return gettext_noop( "Battle Screen" ); case HotKeyCategory::TOWN: return gettext_noop( "Town Screen" ); case HotKeyCategory::ARMY: return gettext_noop( "Army Actions" ); case HotKeyCategory::EDITOR: return gettext_noop( "Editor" ); default: // Did you add a new category? Add the logic above! assert( 0 ); break; } return ""; } fheroes2-1.0.12+dfsg/src/fheroes2/game/game_hotkeys.h000066400000000000000000000142071456075706000223120ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include #include namespace fheroes2 { enum class Key : int32_t; } namespace Game { enum class HotKeyEvent : int32_t { NONE, DEFAULT_OKAY, DEFAULT_CANCEL, DEFAULT_LEFT, DEFAULT_RIGHT, DEFAULT_UP, DEFAULT_DOWN, GLOBAL_TOGGLE_FULLSCREEN, GLOBAL_TOGGLE_TEXT_SUPPORT_MODE, MAIN_MENU_NEW_GAME, MAIN_MENU_LOAD_GAME, MAIN_MENU_HIGHSCORES, MAIN_MENU_CREDITS, MAIN_MENU_STANDARD, MAIN_MENU_CAMPAIGN, MAIN_MENU_MULTI, MAIN_MENU_SETTINGS, MAIN_MENU_QUIT, MAIN_MENU_SELECT_MAP, MAIN_MENU_MAP_SIZE_SMALL, MAIN_MENU_MAP_SIZE_MEDIUM, MAIN_MENU_MAP_SIZE_LARGE, MAIN_MENU_MAP_SIZE_EXTRA_LARGE, MAIN_MENU_MAP_SIZE_ALL, MAIN_MENU_HOTSEAT, MAIN_MENU_BATTLEONLY, MAIN_MENU_NEW_ORIGINAL_CAMPAIGN, MAIN_MENU_NEW_EXPANSION_CAMPAIGN, EDITOR_MAIN_MENU, EDITOR_NEW_MAP_MENU, EDITOR_LOAD_MAP_MENU, EDITOR_FROM_SCRATCH_MAP_MENU, EDITOR_RANDOM_MAP_MENU, EDITOR_UNDO_LAST_ACTION, EDITOR_REDO_LAST_ACTION, CAMPAIGN_ROLAND, CAMPAIGN_ARCHIBALD, CAMPAIGN_PRICE_OF_LOYALTY, CAMPAIGN_VOYAGE_HOME, CAMPAIGN_WIZARDS_ISLE, CAMPAIGN_DESCENDANTS, CAMPAIGN_SELECT_FIRST_BONUS, CAMPAIGN_SELECT_SECOND_BONUS, CAMPAIGN_SELECT_THIRD_BONUS, CAMPAIGN_VIEW_INTRO, CAMPAIGN_SELECT_DIFFICULTY, CAMPAIGN_RESTART_SCENARIO, // Move the hero if there is a selected one, otherwise scroll the adventure map. WORLD_LEFT, WORLD_RIGHT, WORLD_UP, WORLD_DOWN, WORLD_UP_LEFT, WORLD_UP_RIGHT, WORLD_DOWN_LEFT, WORLD_DOWN_RIGHT, // WORLD_SAVE_GAME, WORLD_NEXT_HERO, WORLD_START_HERO_MOVEMENT, WORLD_CAST_SPELL, WORLD_SLEEP_HERO, WORLD_NEXT_TOWN, WORLD_END_TURN, WORLD_FILE_OPTIONS, WORLD_ADVENTURE_OPTIONS, WORLD_PUZZLE_MAP, WORLD_SCENARIO_INFORMATION, WORLD_DIG_ARTIFACT, WORLD_VIEW_WORLD, WORLD_KINGDOM_SUMMARY, WORLD_DEFAULT_ACTION, WORLD_OPEN_FOCUS, WORLD_SYSTEM_OPTIONS, // Scroll the adventure map regardless of the selected object. WORLD_SCROLL_LEFT, WORLD_SCROLL_RIGHT, WORLD_SCROLL_UP, WORLD_SCROLL_DOWN, // WORLD_TOGGLE_CONTROL_PANEL, WORLD_TOGGLE_RADAR, WORLD_TOGGLE_BUTTONS, WORLD_TOGGLE_STATUS, WORLD_TOGGLE_ICONS, #if defined( WITH_DEBUG ) // This hotkey is only for debug mode as of now. WORLD_TRANSFER_CONTROL_TO_AI, #endif BATTLE_RETREAT, BATTLE_SURRENDER, BATTLE_AUTO_SWITCH, BATTLE_AUTO_FINISH, BATTLE_OPTIONS, BATTLE_SKIP, BATTLE_CAST_SPELL, TOWN_DWELLING_LEVEL_1, TOWN_DWELLING_LEVEL_2, TOWN_DWELLING_LEVEL_3, TOWN_DWELLING_LEVEL_4, TOWN_DWELLING_LEVEL_5, TOWN_DWELLING_LEVEL_6, TOWN_WELL, TOWN_MARKETPLACE, TOWN_MAGE_GUILD, TOWN_SHIPYARD, TOWN_THIEVES_GUILD, TOWN_TAVERN, // Also used to build a castle in the town. TOWN_CONSTRUCTION, TOWN_WELL_BUY_ALL, ARMY_SPLIT_STACK_BY_HALF, ARMY_SPLIT_STACK_BY_ONE, ARMY_JOIN_STACKS, ARMY_UPGRADE_TROOP, ARMY_DISMISS, // WARNING! Put all new event only above this line. No adding in between. NO_EVENT, }; enum class HotKeyCategory : uint8_t { DEFAULT, GLOBAL, MAIN_MENU, CAMPAIGN, WORLD_MAP, BATTLE, TOWN, ARMY, EDITOR, }; bool HotKeyPressEvent( const HotKeyEvent eventID ); bool HotKeyHoldEvent( const HotKeyEvent eventID ); fheroes2::Key getHotKeyForEvent( const HotKeyEvent eventID ); void setHotKeyForEvent( const HotKeyEvent eventID, const fheroes2::Key key ); inline bool HotKeyCloseWindow() { return HotKeyPressEvent( HotKeyEvent::DEFAULT_CANCEL ) || HotKeyPressEvent( HotKeyEvent::DEFAULT_OKAY ); } std::string getHotKeyNameByEventId( const HotKeyEvent eventID ); const char * getHotKeyEventNameByEventId( const HotKeyEvent eventID ); std::vector> getAllHotKeyEvents(); void globalKeyDownEvent( const fheroes2::Key key, const int32_t modifier ); void HotKeysLoad( const std::string & filename ); void HotKeySave(); const char * getHotKeyCategoryName( const HotKeyCategory category ); } fheroes2-1.0.12+dfsg/src/fheroes2/game/game_interface.cpp000066400000000000000000000272411456075706000231210ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "game_interface.h" #include #include "agg_image.h" #include "cursor.h" #include "dialog.h" #include "game.h" #include "game_delays.h" #include "game_hotkeys.h" #include "gamedefs.h" #include "icn.h" #include "image.h" #include "interface_border.h" #include "interface_gamearea.h" #include "interface_radar.h" #include "interface_status.h" #include "localevent.h" #include "maps.h" #include "math_base.h" #include "screen.h" #include "settings.h" #include "ui_button.h" #include "ui_tool.h" #include "world.h" Interface::AdventureMap::AdventureMap() : BaseInterface( false ) , iconsPanel( *this ) , buttonsArea( *this ) , controlPanel( *this ) , _statusWindow( *this ) , _lockRedraw( false ) { AdventureMap::reset(); } void Interface::AdventureMap::reset() { const fheroes2::Display & display = fheroes2::Display::instance(); Settings & conf = Settings::Get(); const bool isHideInterface = conf.isHideInterfaceEnabled(); if ( isHideInterface ) { conf.SetShowControlPanel( true ); controlPanel.SetPos( display.width() - controlPanel.GetArea().width - BORDERWIDTH, 0 ); fheroes2::Point radrPos = conf.PosRadar(); fheroes2::Point bttnPos = conf.PosButtons(); fheroes2::Point iconPos = conf.PosIcons(); fheroes2::Point statPos = conf.PosStatus(); const auto isPosValid = []( const fheroes2::Point & pos ) { return pos.x >= 0 && pos.y >= 0; }; if ( isPosValid( radrPos ) && isPosValid( bttnPos ) && isPosValid( iconPos ) && isPosValid( statPos ) ) { _radar.SetPos( radrPos.x, radrPos.y ); iconsPanel.SetPos( iconPos.x, iconPos.y ); buttonsArea.SetPos( bttnPos.x, bttnPos.y ); _statusWindow.SetPos( statPos.x, statPos.y ); } else { _radar.SetPos( 0, 0 ); // It's OK to use display.width() for the X coordinate here, panel will be docked to the right edge iconsPanel.SetPos( display.width(), _radar.GetRect().y + _radar.GetRect().height ); buttonsArea.SetPos( display.width(), iconsPanel.GetRect().y + iconsPanel.GetRect().height ); _statusWindow.SetPos( display.width(), buttonsArea.GetRect().y + buttonsArea.GetRect().height ); } } else { const int32_t px = display.width() - BORDERWIDTH - RADARWIDTH; _radar.SetPos( px, BORDERWIDTH ); iconsPanel.SetPos( px, _radar.GetArea().y + _radar.GetArea().height + BORDERWIDTH ); buttonsArea.SetPos( px, iconsPanel.GetArea().y + iconsPanel.GetArea().height + BORDERWIDTH ); _statusWindow.SetPos( px, buttonsArea.GetArea().y + buttonsArea.GetArea().height ); } const fheroes2::Point prevCenter = _gameArea.getCurrentCenterInPixels(); const fheroes2::Rect prevRoi = _gameArea.GetROI(); _gameArea.generate( { display.width(), display.height() }, isHideInterface ); const fheroes2::Rect newRoi = _gameArea.GetROI(); _gameArea.SetCenterInPixels( prevCenter + fheroes2::Point( newRoi.x + newRoi.width / 2, newRoi.y + newRoi.height / 2 ) - fheroes2::Point( prevRoi.x + prevRoi.width / 2, prevRoi.y + prevRoi.height / 2 ) ); } Interface::AdventureMap & Interface::AdventureMap::Get() { static AdventureMap basic; return basic; } void Interface::AdventureMap::redraw( const uint32_t force ) { if ( _lockRedraw ) { setRedraw( force ); return; } const Settings & conf = Settings::Get(); const uint32_t combinedRedraw = _redraw | force; const bool hideInterface = conf.isHideInterfaceEnabled(); if ( combinedRedraw & REDRAW_GAMEAREA ) { _gameArea.Redraw( fheroes2::Display::instance(), LEVEL_ALL ); if ( hideInterface && conf.ShowControlPanel() ) { controlPanel._redraw(); } } if ( ( hideInterface && conf.ShowRadar() ) || ( combinedRedraw & ( REDRAW_RADAR_CURSOR | REDRAW_RADAR ) ) ) { // Redraw radar map only if `REDRAW_RADAR` is set. _radar._redraw( combinedRedraw & REDRAW_RADAR ); } if ( ( hideInterface && conf.ShowIcons() ) || ( combinedRedraw & REDRAW_ICONS ) ) { iconsPanel._redraw(); } else if ( combinedRedraw & REDRAW_HEROES ) { iconsPanel._redrawIcons( ICON_HEROES ); } else if ( combinedRedraw & REDRAW_CASTLES ) { iconsPanel._redrawIcons( ICON_CASTLES ); } if ( ( hideInterface && conf.ShowButtons() ) || ( combinedRedraw & REDRAW_BUTTONS ) ) { buttonsArea._redraw(); } if ( ( hideInterface && conf.ShowStatus() ) || ( combinedRedraw & REDRAW_STATUS ) ) { _statusWindow._redraw(); } if ( combinedRedraw & REDRAW_BORDER ) { GameBorderRedraw( false ); } _redraw = 0; } int32_t Interface::AdventureMap::GetDimensionDoorDestination( const int32_t from, const int32_t distance, const bool water ) { fheroes2::Display & display = fheroes2::Display::instance(); Settings & conf = Settings::Get(); const bool isEvilInterface = conf.isEvilInterfaceEnabled(); const bool isHideInterface = conf.isHideInterfaceEnabled(); const bool isIconsVisible = conf.ShowIcons(); const bool isButtonsVisible = conf.ShowButtons(); const bool isStatusVisible = conf.ShowStatus(); const bool isControlPanelVisible = conf.ShowControlPanel(); const fheroes2::Rect & radarRect = _radar.GetRect(); const fheroes2::Rect & radarArea = _radar.GetArea(); fheroes2::Button buttonExit( radarArea.x + 32, radarArea.y + radarArea.height - 37, ( isEvilInterface ? ICN::BUTTON_EXIT_PUZZLE_DIM_DOOR_EVIL : ICN::BUTTON_EXIT_PUZZLE_DIM_DOOR_GOOD ), 0, 1 ); const auto drawControlPanel = [&display, isEvilInterface, isHideInterface, &radarRect, &radarArea, &buttonExit]() { if ( isHideInterface ) { Dialog::FrameBorder::RenderRegular( radarRect ); } fheroes2::Blit( fheroes2::AGG::GetICN( ( isEvilInterface ? ICN::EVIWDDOR : ICN::VIEWDDOR ), 0 ), display, radarArea.x, radarArea.y ); buttonExit.draw(); }; const fheroes2::Rect & gameAreaROI = _gameArea.GetROI(); const bool isFadingEnabled = ( gameAreaROI.width > TILEWIDTH * distance ) || ( gameAreaROI.height > TILEWIDTH * distance ); const fheroes2::Rect spellROI = [this, from, distance, isHideInterface, &gameAreaROI]() -> fheroes2::Rect { const fheroes2::Point heroPos = _gameArea.GetRelativeTilePosition( Maps::GetPoint( from ) ); const int32_t x = heroPos.x - TILEWIDTH * ( distance / 2 ); const int32_t y = heroPos.y - TILEWIDTH * ( distance / 2 ); // We need to add an extra cell since the hero stands exactly in the middle of a cell const int32_t w = std::min( TILEWIDTH * ( distance + 1 ), gameAreaROI.width ); const int32_t h = std::min( TILEWIDTH * ( distance + 1 ), gameAreaROI.height ); return { isHideInterface ? x : std::max( x, BORDERWIDTH ), isHideInterface ? y : std::max( y, BORDERWIDTH ), w, h }; }(); if ( isHideInterface ) { // There is no need to hide the radar because it will be replaced by the Dimension Door control panel conf.SetShowIcons( false ); conf.SetShowButtons( false ); conf.SetShowStatus( false ); conf.SetShowControlPanel( false ); redraw( REDRAW_GAMEAREA ); } if ( isFadingEnabled ) { if ( isHideInterface ) { InvertedShadow( display, gameAreaROI, spellROI, 5, 9 ); drawControlPanel(); } else { drawControlPanel(); fheroes2::InvertedFadeWithPalette( display, gameAreaROI, spellROI, 5, 300, 9 ); } } else { drawControlPanel(); } display.render(); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); Cursor & cursor = Cursor::Get(); LocalEvent & le = LocalEvent::Get(); int32_t returnValue = -1; while ( le.HandleEvents( Game::isDelayNeeded( { Game::MAPS_DELAY } ) ) ) { const fheroes2::Point & mp = le.GetMouseCursor(); if ( radarRect & mp ) { cursor.SetThemes( Cursor::POINTER ); le.MousePressLeft( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) { break; } } else if ( gameAreaROI & mp ) { const int32_t dst = _gameArea.GetValidTileIdFromPoint( mp ); bool valid = ( dst >= 0 ); if ( valid ) { valid = ( spellROI & mp ) && Maps::isValidForDimensionDoor( dst, water ); } cursor.SetThemes( valid ? ( water ? static_cast( Cursor::CURSOR_HERO_BOAT ) : static_cast( Cursor::CURSOR_HERO_MOVE ) ) : static_cast( Cursor::WAR_NONE ) ); if ( dst >= 0 && le.MousePressRight() ) { Dialog::QuickInfo( world.GetTiles( dst ) ); } else if ( le.MouseClickLeft() && valid ) { returnValue = dst; break; } } else { cursor.SetThemes( Cursor::POINTER ); } if ( Game::validateAnimationDelay( Game::MAPS_DELAY ) ) { Game::updateAdventureMapAnimationIndex(); redraw( REDRAW_GAMEAREA ); if ( isFadingEnabled ) { InvertedShadow( display, gameAreaROI, spellROI, 5, 9 ); if ( isHideInterface ) { drawControlPanel(); } } display.render(); } } if ( isFadingEnabled ) { setRedraw( REDRAW_GAMEAREA ); } if ( isHideInterface ) { conf.SetShowIcons( isIconsVisible ); conf.SetShowButtons( isButtonsVisible ); conf.SetShowStatus( isStatusVisible ); conf.SetShowControlPanel( isControlPanelVisible ); setRedraw( REDRAW_ICONS | REDRAW_BUTTONS | REDRAW_STATUS | REDRAW_GAMEAREA ); } redraw( REDRAW_RADAR_CURSOR ); display.render(); return returnValue; } fheroes2-1.0.12+dfsg/src/fheroes2/game/game_interface.h000066400000000000000000000143571456075706000225720ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2GAMEINTERFACE_H #define H2GAMEINTERFACE_H #include #include "game_mode.h" #include "interface_base.h" #include "interface_buttons.h" #include "interface_cpanel.h" #include "interface_icons.h" #include "interface_status.h" #include "players.h" class Castle; class Heroes; namespace Maps { class Tiles; } namespace GameFocus { enum { UNSEL = FOCUS_UNSEL, HEROES = FOCUS_HEROES, CASTLE = FOCUS_CASTLE, FIRSTHERO }; } namespace Interface { Castle * GetFocusCastle(); Heroes * GetFocusHeroes(); int GetFocusType(); class AdventureMap final : public BaseInterface { public: // This class is used to lock rendering of Basic class. This is useful when we have to generate only a single frame. // Use this class ONLY when you are going to call rendering after all other operations. class RedrawLocker { public: explicit RedrawLocker( AdventureMap & interface_ ) : _interface( interface_ ) { _interface._lockRedraw = true; } RedrawLocker( const RedrawLocker & ) = delete; RedrawLocker( RedrawLocker && ) = delete; RedrawLocker & operator=( const RedrawLocker & ) = delete; RedrawLocker & operator=( RedrawLocker && ) = delete; ~RedrawLocker() { _interface._lockRedraw = false; } private: AdventureMap & _interface; }; static AdventureMap & Get(); void redraw( const uint32_t force ) override; int32_t GetDimensionDoorDestination( const int32_t from, const int32_t distance, const bool water ); IconsPanel & GetIconsPanel() { return iconsPanel; } ControlPanel & getControlPanel() { return controlPanel; } StatusWindow & getStatusWindow() { return _statusWindow; } void SetFocus( Heroes *, const bool retainScrollBarPosition ); void SetFocus( Castle * ); void ResetFocus( const int priority, const bool retainScrollBarPosition ); void RedrawFocus(); void updateFocus(); void EventSwitchHeroSleeping(); fheroes2::GameMode EventDefaultAction(); void EventOpenFocus() const; fheroes2::GameMode EventSaveGame() const; void EventPuzzleMaps() const; static fheroes2::GameMode EventScenarioInformation(); void EventSystemDialog() const; void EventNextHero(); void EventNextTown(); fheroes2::GameMode EventHeroMovement(); void EventResetHeroPath(); void EventKingdomInfo() const; void EventCastSpell(); void EventSwitchShowRadar() const; void EventSwitchShowStatus() const; void EventSwitchShowButtons() const; void EventSwitchShowIcons() const; void EventSwitchShowControlPanel() const; fheroes2::GameMode EventNewGame() const; fheroes2::GameMode EventLoadGame() const; fheroes2::GameMode EventAdventureDialog(); void EventViewWorld(); fheroes2::GameMode EventFileDialog() const; fheroes2::GameMode EventEndTurn() const; fheroes2::GameMode EventDigArtifact(); void EventKeyArrowPress( int direct ); fheroes2::GameMode StartGame(); void mouseCursorAreaClickLeft( const int32_t tileIndex ) override; void mouseCursorAreaPressRight( const int32_t tileIndex ) const override; void updateCursor( const int32_t tileIndex ) override; // Regenerates the game area and updates the panel positions depending on the UI settings void reset() override; private: AdventureMap(); static int GetCursorFocusCastle( const Castle & castle, const Maps::Tiles & tile ); static int GetCursorFocusHeroes( const Heroes & hero, const Maps::Tiles & tile ); static int GetCursorFocusShipmaster( const Heroes & hero, const Maps::Tiles & tile ); static int _getCursorNoFocus( const Maps::Tiles & tile ); static int GetCursorTileIndex( int32_t dstIndex ); void ShowPathOrStartMoveHero( Heroes * hero, const int32_t destinationIdx ); void MoveHeroFromArrowKeys( Heroes & hero, const int direction ); void _startHeroMove( Heroes & hero ); fheroes2::GameMode HumanTurn( const bool isload ); IconsPanel iconsPanel; ButtonsArea buttonsArea; ControlPanel controlPanel; StatusWindow _statusWindow; bool _lockRedraw; }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/game/game_io.cpp000066400000000000000000000311261456075706000215650ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "game_io.h" #include #include #include #include #include #include #include "campaign_savedata.h" #include "campaign_scenariodata.h" #include "dialog.h" #include "game.h" #include "game_over.h" #include "logging.h" #include "maps_fileinfo.h" #include "save_format_version.h" #include "serialize.h" #include "settings.h" #include "system.h" #include "translations.h" #include "ui_dialog.h" #include "ui_language.h" #include "world.h" #include "zzlib.h" namespace { const std::string autoSaveName{ "AUTOSAVE" }; const uint16_t SAV2ID3 = 0xFF03; uint16_t versionOfCurrentSaveFile = CURRENT_FORMAT_VERSION; std::string lastSaveName; struct HeaderSAV { enum { REQUIRES_POL_RESOURCES = 0x4000 }; HeaderSAV() : status( 0 ) , gameType( 0 ) {} HeaderSAV( const Maps::FileInfo & fi, const int type, const uint32_t worldDay, const uint32_t worldWeek, const uint32_t worldMonth ) : status( 0 ) , info( fi ) , gameType( type ) { time_t rawtime; std::time( &rawtime ); info.timestamp = static_cast( rawtime ); info.worldDay = worldDay; info.worldWeek = worldWeek; info.worldMonth = worldMonth; if ( fi.version != GameVersion::SUCCESSION_WARS ) { status |= REQUIRES_POL_RESOURCES; } } uint16_t status; Maps::FileInfo info; int gameType; }; StreamBase & operator<<( StreamBase & msg, const HeaderSAV & hdr ) { return msg << hdr.status << hdr.info << hdr.gameType; } StreamBase & operator>>( StreamBase & msg, HeaderSAV & hdr ) { return msg >> hdr.status >> hdr.info >> hdr.gameType; } } bool Game::AutoSave() { return Game::Save( System::concatPath( GetSaveDir(), autoSaveName + GetSaveFileExtension() ), true ); } bool Game::Save( const std::string & filePath, const bool autoSave /* = false */ ) { DEBUG_LOG( DBG_GAME, DBG_INFO, filePath ) const Settings & conf = Settings::Get(); StreamFile fs; fs.setbigendian( true ); if ( !fs.open( filePath, "wb" ) ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Error opening the file " << filePath ) return false; } // Always use the latest version of the file save format SetVersionOfCurrentSaveFile( CURRENT_FORMAT_VERSION ); uint16_t saveFileVersion = CURRENT_FORMAT_VERSION; // Header fs << SAV2ID3 << std::to_string( saveFileVersion ) << saveFileVersion << HeaderSAV( conf.getCurrentMapInfo(), conf.GameType(), world.GetDay(), world.GetWeek(), world.GetMonth() ); fs.close(); ZStreamBuf zb; zb.setbigendian( true ); // Game data in ZIP format zb << World::Get() << Settings::Get() << GameOver::Result::Get(); if ( conf.isCampaignGameType() ) { zb << Campaign::CampaignSaveData::Get(); } // End-of-data marker zb << SAV2ID3; if ( zb.fail() || !zb.write( filePath, true ) ) { return false; } if ( !autoSave ) { Game::SetLastSaveName( filePath ); } return true; } fheroes2::GameMode Game::Load( const std::string & filePath ) { DEBUG_LOG( DBG_GAME, DBG_INFO, filePath ) const auto showGenericErrorMessage = []() { fheroes2::showStandardTextMessage( _( "Error" ), _( "The save file is corrupted." ), Dialog::OK ); }; StreamFile fs; fs.setbigendian( true ); if ( !fs.open( filePath, "rb" ) ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Error opening the file " << filePath ) showGenericErrorMessage(); return fheroes2::GameMode::CANCEL; } uint16_t savId = 0; fs >> savId; if ( savId != SAV2ID3 ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Invalid file identifier in the file " << filePath ) showGenericErrorMessage(); return fheroes2::GameMode::CANCEL; } std::string saveFileVersionStr; uint16_t saveFileVersion = 0; fs >> saveFileVersionStr >> saveFileVersion; DEBUG_LOG( DBG_GAME, DBG_TRACE, "Version of the file " << filePath << ": " << saveFileVersion ) if ( saveFileVersion > CURRENT_FORMAT_VERSION || saveFileVersion < LAST_SUPPORTED_FORMAT_VERSION ) { std::string errorMessage( _( "Unsupported save format: " ) ); errorMessage += std::to_string( saveFileVersion ); errorMessage += ".\n"; errorMessage += _( "Current game version: " ); errorMessage += std::to_string( CURRENT_FORMAT_VERSION ); errorMessage += ".\n"; errorMessage += _( "Last supported version: " ); errorMessage += std::to_string( LAST_SUPPORTED_FORMAT_VERSION ); errorMessage += ".\n"; fheroes2::showStandardTextMessage( _( "Error" ), errorMessage, Dialog::OK ); return fheroes2::GameMode::CANCEL; } SetVersionOfCurrentSaveFile( saveFileVersion ); HeaderSAV header; fs >> header; size_t offset = fs.tell(); fs.close(); Settings & conf = Settings::Get(); if ( ( conf.GameType() & header.gameType ) == 0 ) { fheroes2::showStandardTextMessage( _( "Error" ), _( "This file contains a save with an invalid game type." ), Dialog::OK ); return fheroes2::GameMode::CANCEL; } ZStreamBuf zb; zb.setbigendian( true ); if ( !zb.read( filePath, offset ) ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Error uncompressing the file " << filePath ) showGenericErrorMessage(); return fheroes2::GameMode::CANCEL; } if ( ( header.status & HeaderSAV::REQUIRES_POL_RESOURCES ) && !conf.isPriceOfLoyaltySupported() ) { fheroes2::showStandardTextMessage( _( "Error" ), _( "This save file requires \"The Price of Loyalty\" game assets, but they have not been provided to the engine." ), Dialog::OK ); return fheroes2::GameMode::CANCEL; } zb >> World::Get() >> conf >> GameOver::Result::Get(); fheroes2::GameMode returnValue = fheroes2::GameMode::START_GAME; if ( conf.isCampaignGameType() ) { Campaign::CampaignSaveData & saveData = Campaign::CampaignSaveData::Get(); zb >> saveData; if ( !saveData.isStarting() && saveData.getCurrentScenarioInfoId() == saveData.getLastCompletedScenarioInfoID() ) { // This is the end of the current scenario. We should show next scenario selection. returnValue = fheroes2::GameMode::COMPLETE_CAMPAIGN_SCENARIO_FROM_LOAD_FILE; } } uint16_t endOfDataMarker = 0; zb >> endOfDataMarker; if ( zb.fail() || endOfDataMarker != SAV2ID3 ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "File " << filePath << " is corrupted" ) showGenericErrorMessage(); return fheroes2::GameMode::CANCEL; } // Settings should contain the full path to the current map file, if this map is available conf.getCurrentMapInfo().filename = Settings::GetLastFile( "maps", System::GetBasename( conf.getCurrentMapInfo().filename ) ); if ( !conf.loadedFileLanguage().empty() && conf.loadedFileLanguage() != "en" && conf.loadedFileLanguage() != conf.getGameLanguage() ) { std::string warningMessage( _( "This saved game is localized to '" ) ); warningMessage.append( fheroes2::getLanguageName( fheroes2::getLanguageFromAbbreviation( conf.loadedFileLanguage() ) ) ); warningMessage.append( _( "' language, but the current language of the game is '" ) ); warningMessage.append( fheroes2::getLanguageName( fheroes2::getLanguageFromAbbreviation( conf.getGameLanguage() ) ) ); warningMessage += "'."; fheroes2::showStandardTextMessage( _( "Warning" ), warningMessage, Dialog::OK ); } Game::SetLastSaveName( filePath ); conf.SetGameType( conf.GameType() | Game::TYPE_LOADFILE ); return returnValue; } bool Game::LoadSAV2FileInfo( std::string filePath, Maps::FileInfo & fileInfo ) { DEBUG_LOG( DBG_GAME, DBG_INFO, filePath ) StreamFile fs; fs.setbigendian( true ); if ( !fs.open( filePath, "rb" ) ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Error opening the file " << filePath ) return false; } uint16_t savId = 0; fs >> savId; if ( savId != SAV2ID3 ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Invalid file identifier in the file " << filePath ) return false; } std::string saveFileVersionStr; uint16_t saveFileVersion = 0; fs >> saveFileVersionStr >> saveFileVersion; DEBUG_LOG( DBG_GAME, DBG_TRACE, "Version of the file " << filePath << ": " << saveFileVersion ) if ( saveFileVersion > CURRENT_FORMAT_VERSION || saveFileVersion < LAST_SUPPORTED_FORMAT_VERSION ) { return false; } SetVersionOfCurrentSaveFile( saveFileVersion ); HeaderSAV header; fs >> header; if ( ( Settings::Get().GameType() & header.gameType ) == 0 ) { return false; } fileInfo = std::move( header.info ); fileInfo.filename = std::move( filePath ); return true; } void Game::SetVersionOfCurrentSaveFile( const uint16_t version ) { versionOfCurrentSaveFile = version; } uint16_t Game::GetVersionOfCurrentSaveFile() { return versionOfCurrentSaveFile; } const std::string & Game::GetLastSaveName() { return lastSaveName; } void Game::SetLastSaveName( const std::string & name ) { lastSaveName = name; } std::string Game::GetSaveDir() { return System::concatPath( System::concatPath( System::GetDataDirectory( "fheroes2" ), "files" ), "save" ); } std::string Game::GetSaveFileBaseName() { std::string baseName = Settings::Get().getCurrentMapInfo().name; // Replace all non-ASCII characters by exclamation marks std::replace_if( baseName.begin(), baseName.end(), []( const unsigned char c ) { return ( c > 127 ); }, '!' ); // Remove all non-printable characters baseName.erase( std::remove_if( baseName.begin(), baseName.end(), []( const unsigned char c ) { return !std::isprint( c ); } ), baseName.end() ); // Replace all remaining non-alphanumeric characters (excluding exclamation marks) by underscores std::replace_if( baseName.begin(), baseName.end(), []( const unsigned char c ) { return ( c != '!' && !std::isalnum( c ) ); }, '_' ); // If in the end there are no characters left, set the base name to "newgame" if ( baseName.empty() ) { baseName = "newgame"; } return baseName; } std::string Game::GetSaveFileExtension() { return GetSaveFileExtension( Settings::Get().GameType() ); } std::string Game::GetSaveFileExtension( const int gameType ) { if ( gameType & Game::TYPE_STANDARD ) return ".sav"; else if ( gameType & Game::TYPE_CAMPAIGN ) return ".savc"; else if ( gameType & Game::TYPE_HOTSEAT ) return ".savh"; return ".savm"; } bool Game::SaveCompletedCampaignScenario() { return Save( System::concatPath( GetSaveDir(), GetSaveFileBaseName() ) + "_Complete" + GetSaveFileExtension() ); } fheroes2-1.0.12+dfsg/src/fheroes2/game/game_io.h000066400000000000000000000050171456075706000212320ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2GAMEIO_H #define H2GAMEIO_H #include #include #include "game_mode.h" namespace Maps { struct FileInfo; } namespace Game { const std::string & GetLastSaveName(); void SetLastSaveName( const std::string & name ); uint16_t GetVersionOfCurrentSaveFile(); void SetVersionOfCurrentSaveFile( const uint16_t version ); std::string GetSaveDir(); std::string GetSaveFileBaseName(); std::string GetSaveFileExtension(); std::string GetSaveFileExtension( const int gameType ); bool AutoSave(); bool Save( const std::string & filePath, const bool autoSave = false ); // Returns GameMode::CANCEL in case of failure. fheroes2::GameMode Load( const std::string & filePath ); bool LoadSAV2FileInfo( std::string filePath, Maps::FileInfo & fileInfo ); bool SaveCompletedCampaignScenario(); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/game/game_loadgame.cpp000066400000000000000000000250371456075706000227330ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "game.h" #include #include #include #include #include #include "agg_image.h" #include "audio.h" #include "audio_manager.h" #include "cursor.h" #include "dialog.h" #include "dir.h" #include "game_hotkeys.h" #include "game_io.h" #include "game_mainmenu_ui.h" #include "game_mode.h" #include "icn.h" #include "localevent.h" #include "logging.h" #include "math_base.h" #include "mus.h" #include "screen.h" #include "settings.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_tool.h" namespace { const int32_t buttonYStep = 66; void outputLoadGameInTextSupportMode() { START_TEXT_SUPPORT_MODE COUT( "Load Game\n" ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::MAIN_MENU_STANDARD ) << " to choose Standard Game." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::MAIN_MENU_CAMPAIGN ) << " to choose Campaign Game." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::MAIN_MENU_MULTI ) << " to show Multi-Player Game." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::DEFAULT_CANCEL ) << " to go back to Main Menu." ) } } fheroes2::GameMode Game::LoadCampaign() { Settings::Get().SetGameType( Game::TYPE_CAMPAIGN ); return DisplayLoadGameDialog(); } fheroes2::GameMode Game::LoadHotseat() { Settings::Get().SetGameType( Game::TYPE_HOTSEAT ); return DisplayLoadGameDialog(); } fheroes2::GameMode Game::LoadMulti() { fheroes2::Display & display = fheroes2::Display::instance(); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); // image background fheroes2::drawMainMenuScreen(); const fheroes2::Point buttonPos = fheroes2::drawButtonPanel(); fheroes2::Button buttonHotSeat( buttonPos.x, buttonPos.y, ICN::BUTTON_HOT_SEAT, 0, 1 ); fheroes2::Button buttonNetwork( buttonPos.x, buttonPos.y + buttonYStep * 1, ICN::BTNMP, 2, 3 ); fheroes2::Button buttonCancelGame( buttonPos.x, buttonPos.y + buttonYStep * 5, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); buttonHotSeat.draw(); buttonCancelGame.draw(); buttonNetwork.disable(); display.render(); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { le.MousePressLeft( buttonHotSeat.area() ) ? buttonHotSeat.drawOnPress() : buttonHotSeat.drawOnRelease(); le.MousePressLeft( buttonCancelGame.area() ) ? buttonCancelGame.drawOnPress() : buttonCancelGame.drawOnRelease(); if ( le.MouseClickLeft( buttonHotSeat.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_HOTSEAT ) ) { if ( ListFiles::IsEmpty( GetSaveDir(), GetSaveFileExtension( Game::TYPE_HOTSEAT ), false ) ) { fheroes2::showStandardTextMessage( _( "Load Game" ), _( "No save files to load." ), Dialog::OK ); } else { return fheroes2::GameMode::LOAD_HOT_SEAT; } } else if ( HotKeyPressEvent( HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancelGame.area() ) ) { return fheroes2::GameMode::LOAD_GAME; } // right info else if ( le.MousePressRight( buttonHotSeat.area() ) ) { fheroes2::showStandardTextMessage( _( "Hot Seat" ), _( "Play a Hot Seat game, where 2 to 4 players play around the same computer, switching into the 'Hot Seat' when it is their turn." ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonCancelGame.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the main menu." ), Dialog::ZERO ); } } return fheroes2::GameMode::LOAD_GAME; } fheroes2::GameMode Game::LoadGame() { outputLoadGameInTextSupportMode(); // Stop all sounds, but not the music AudioManager::stopSounds(); AudioManager::PlayMusicAsync( MUS::MAINMENU, Music::PlaybackMode::RESUME_AND_PLAY_INFINITE ); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); fheroes2::drawMainMenuScreen(); const fheroes2::Point buttonPos = fheroes2::drawButtonPanel(); fheroes2::Button buttonStandardGame( 0, 0, ICN::BUTTON_STANDARD_GAME, 0, 1 ); fheroes2::ButtonSprite buttonCampaignGame( buttonPos.x, buttonPos.y + buttonYStep * 1, fheroes2::AGG::GetICN( ICN::BUTTON_CAMPAIGN_GAME, 0 ), fheroes2::AGG::GetICN( ICN::BUTTON_CAMPAIGN_GAME, 1 ), fheroes2::AGG::GetICN( ICN::NEW_CAMPAIGN_DISABLED_BUTTON, 0 ) ); fheroes2::Button buttonMultiplayerGame( 0, 0, ICN::BUTTON_MULTIPLAYER_GAME, 0, 1 ); fheroes2::Button buttonCancel( 0, 0, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); std::array buttons{ &buttonStandardGame, &buttonCampaignGame, &buttonMultiplayerGame, &buttonCancel }; if ( !isSuccessionWarsCampaignPresent() ) { buttonCampaignGame.disable(); } static_assert( buttons.size() > 1, "The number of buttons in this dialog cannot be less than 2!" ); for ( size_t i = 0; i < buttons.size() - 1; ++i ) { buttons[i]->setPosition( buttonPos.x, buttonPos.y + buttonYStep * static_cast( i ) ); buttons[i]->draw(); } // following the cancel button in newgame buttonCancel.setPosition( buttonPos.x, buttonPos.y + buttonYStep * 5 ); buttonCancel.draw(); fheroes2::validateFadeInAndRender(); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { for ( fheroes2::ButtonBase * button : buttons ) { le.MousePressLeft( button->area() ) ? button->drawOnPress() : button->drawOnRelease(); } if ( le.MouseClickLeft( buttonStandardGame.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_STANDARD ) ) { if ( ListFiles::IsEmpty( GetSaveDir(), GetSaveFileExtension( Game::TYPE_STANDARD ), false ) ) { fheroes2::showStandardTextMessage( _( "Load Game" ), _( "No save files to load." ), Dialog::OK ); } else { return fheroes2::GameMode::LOAD_STANDARD; } } else if ( buttonCampaignGame.isEnabled() && ( le.MouseClickLeft( buttonCampaignGame.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_CAMPAIGN ) ) ) { if ( ListFiles::IsEmpty( GetSaveDir(), GetSaveFileExtension( Game::TYPE_CAMPAIGN ), false ) ) { fheroes2::showStandardTextMessage( _( "Load Game" ), _( "No save files to load." ), Dialog::OK ); } else { return fheroes2::GameMode::LOAD_CAMPAIGN; } } else if ( le.MouseClickLeft( buttonMultiplayerGame.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_MULTI ) ) { return fheroes2::GameMode::LOAD_MULTI; } else if ( le.MouseClickLeft( buttonCancel.area() ) || HotKeyPressEvent( HotKeyEvent::DEFAULT_CANCEL ) ) { return fheroes2::GameMode::MAIN_MENU; } else if ( le.MousePressRight( buttonStandardGame.area() ) ) { fheroes2::showStandardTextMessage( _( "Standard Game" ), _( "A single player game playing out a single map." ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonCampaignGame.area() ) ) { fheroes2::showStandardTextMessage( _( "Campaign Game" ), _( "A single player game playing through a series of maps." ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonMultiplayerGame.area() ) ) { fheroes2::showStandardTextMessage( _( "Multi-Player Game" ), _( "A multi-player game, with several human players completing against each other on a single map." ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonCancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the main menu." ), Dialog::ZERO ); } } return fheroes2::GameMode::MAIN_MENU; } fheroes2::GameMode Game::LoadStandard() { Settings::Get().SetGameType( Game::TYPE_STANDARD ); return DisplayLoadGameDialog(); } fheroes2::GameMode Game::DisplayLoadGameDialog() { // Stop all sounds, but not the music AudioManager::stopSounds(); AudioManager::PlayMusicAsync( MUS::MAINMENU, Music::PlaybackMode::RESUME_AND_PLAY_INFINITE ); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); // image background fheroes2::drawMainMenuScreen(); fheroes2::validateFadeInAndRender(); const std::string file = Dialog::SelectFileLoad(); if ( file.empty() ) { return fheroes2::GameMode::LOAD_GAME; } const fheroes2::GameMode returnValue = Game::Load( file ); if ( returnValue == fheroes2::GameMode::CANCEL ) { return fheroes2::GameMode::LOAD_GAME; } // We are loading a game so fade-out main menu screen. fheroes2::fadeOutDisplay(); setDisplayFadeIn(); return returnValue; } fheroes2-1.0.12+dfsg/src/fheroes2/game/game_logo.cpp000066400000000000000000000066231456075706000221220ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "game_logo.h" #include #include #include "game_delays.h" #include "image.h" #include "localevent.h" #include "math_base.h" #include "screen.h" #include "translations.h" #include "ui_text.h" #include "ui_tool.h" void fheroes2::showTeamInfo() { const ScreenPaletteRestorer restorer; Display & display = Display::instance(); Text text( _( "fheroes2 Resurrection Team presents" ), FontType{ FontSize::LARGE, FontColor::WHITE } ); const int32_t correctedTextWidth = text.width( 500 ); const Rect roi{ ( display.width() - correctedTextWidth ) / 2, ( display.height() - text.height( correctedTextWidth ) ) / 2, text.width(), text.height( correctedTextWidth ) }; Image textImage( roi.width, roi.height ); textImage.fill( 0 ); text.draw( 0, 0, correctedTextWidth, textImage ); // First frame must be fully rendered. display.fill( 0 ); Copy( textImage, 0, 0, display, roi.x, roi.y, roi.width, roi.height ); display.render(); uint8_t alpha = 250; const uint64_t animationDelay = 40; // Immediately indicate that the delay has passed to render first frame immediately. Game::passCustomAnimationDelay( animationDelay ); // Make sure that the first run is passed immediately. assert( !Game::isCustomDelayNeeded( animationDelay ) ); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents( Game::isCustomDelayNeeded( animationDelay ) ) && alpha > 20 ) { if ( le.KeyPress() || le.MouseClickLeft() || le.MouseClickMiddle() || le.MouseClickRight() ) break; // Subsequent frames must update only the area within the text. if ( Game::validateCustomAnimationDelay( animationDelay ) ) { Copy( textImage, 0, 0, display, roi.x, roi.y, roi.width, roi.height ); ApplyAlpha( display, roi.x, roi.y, display, roi.x, roi.y, roi.width, roi.height, alpha ); display.render( roi ); alpha -= 5; } } } fheroes2-1.0.12+dfsg/src/fheroes2/game/game_logo.h000066400000000000000000000027671456075706000215740ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once namespace fheroes2 { void showTeamInfo(); } fheroes2-1.0.12+dfsg/src/fheroes2/game/game_mainmenu.cpp000066400000000000000000000431101456075706000227630ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include "agg_image.h" #include "audio.h" #include "audio_manager.h" #include "cursor.h" #include "dialog.h" #include "dialog_game_settings.h" #include "dialog_language_selection.h" #include "dialog_resolution.h" #include "editor.h" #include "game.h" #include "game_delays.h" #include "game_hotkeys.h" #include "game_interface.h" #include "game_mainmenu_ui.h" #include "game_mode.h" #include "icn.h" #include "image.h" #include "localevent.h" #include "logging.h" #include "math_base.h" #include "mus.h" #include "screen.h" #include "settings.h" #include "system.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_language.h" #include "ui_text.h" #include "ui_tool.h" namespace { struct ButtonInfo { uint32_t frame; fheroes2::Button & button; bool isOver; bool wasOver; }; enum { NEWGAME_DEFAULT = 1, LOADGAME_DEFAULT = 5, HIGHSCORES_DEFAULT = 9, CREDITS_DEFAULT = 13, QUIT_DEFAULT = 17 }; void outputMainMenuInTextSupportMode() { START_TEXT_SUPPORT_MODE COUT( "Main Menu\n" ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::MAIN_MENU_NEW_GAME ) << " to choose New Game." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::MAIN_MENU_LOAD_GAME ) << " to choose Load previously saved game." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::MAIN_MENU_HIGHSCORES ) << " to show High Scores." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::MAIN_MENU_CREDITS ) << " to show Credits." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::MAIN_MENU_SETTINGS ) << " to open Game Settings." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::MAIN_MENU_QUIT ) << " or " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::DEFAULT_CANCEL ) << " to Quit the game." ) } } void Game::mainGameLoop( bool isFirstGameRun ) { fheroes2::GameMode result = fheroes2::GameMode::MAIN_MENU; bool exit = false; while ( !exit ) { switch ( result ) { case fheroes2::GameMode::QUIT_GAME: exit = true; break; case fheroes2::GameMode::MAIN_MENU: result = Game::MainMenu( isFirstGameRun ); isFirstGameRun = false; break; case fheroes2::GameMode::NEW_GAME: result = Game::NewGame(); break; case fheroes2::GameMode::LOAD_GAME: result = Game::LoadGame(); break; case fheroes2::GameMode::HIGHSCORES_STANDARD: result = Game::DisplayHighScores( false ); break; case fheroes2::GameMode::HIGHSCORES_CAMPAIGN: result = Game::DisplayHighScores( true ); break; case fheroes2::GameMode::CREDITS: result = Game::Credits(); break; case fheroes2::GameMode::NEW_STANDARD: result = Game::NewStandard(); break; case fheroes2::GameMode::NEW_CAMPAIGN_SELECTION: result = Game::CampaignSelection(); break; case fheroes2::GameMode::NEW_SUCCESSION_WARS_CAMPAIGN: result = Game::NewSuccessionWarsCampaign(); break; case fheroes2::GameMode::NEW_PRICE_OF_LOYALTY_CAMPAIGN: result = Game::NewPriceOfLoyaltyCampaign(); break; case fheroes2::GameMode::NEW_MULTI: result = Game::NewMulti(); break; case fheroes2::GameMode::NEW_HOT_SEAT: result = Game::NewHotSeat(); break; case fheroes2::GameMode::NEW_BATTLE_ONLY: result = Game::NewBattleOnly(); break; case fheroes2::GameMode::LOAD_STANDARD: result = Game::LoadStandard(); break; case fheroes2::GameMode::LOAD_CAMPAIGN: result = Game::LoadCampaign(); break; case fheroes2::GameMode::LOAD_MULTI: result = Game::LoadMulti(); break; case fheroes2::GameMode::LOAD_HOT_SEAT: result = Game::LoadHotseat(); break; case fheroes2::GameMode::SCENARIO_INFO: result = Game::ScenarioInfo(); break; case fheroes2::GameMode::SELECT_SCENARIO: result = Game::SelectScenario(); break; case fheroes2::GameMode::START_GAME: result = Game::StartGame(); break; case fheroes2::GameMode::SELECT_CAMPAIGN_SCENARIO: result = Game::SelectCampaignScenario( fheroes2::GameMode::MAIN_MENU, false ); break; case fheroes2::GameMode::COMPLETE_CAMPAIGN_SCENARIO: result = Game::CompleteCampaignScenario( false ); break; case fheroes2::GameMode::COMPLETE_CAMPAIGN_SCENARIO_FROM_LOAD_FILE: result = Game::CompleteCampaignScenario( true ); while ( result == fheroes2::GameMode::SELECT_CAMPAIGN_SCENARIO ) { result = Game::SelectCampaignScenario( fheroes2::GameMode::LOAD_CAMPAIGN, false ); } break; case fheroes2::GameMode::EDITOR_MAIN_MENU: result = Editor::menuMain(); break; case fheroes2::GameMode::EDITOR_NEW_MAP: result = Editor::menuNewMap(); break; case fheroes2::GameMode::EDITOR_LOAD_MAP: result = Editor::menuLoadMap(); break; default: // If this assertion blows up then you are entering an infinite loop! // Add the logic for the newly added entry. assert( 0 ); exit = true; break; } } // We are quitting the game, so fade-out the screen. fheroes2::fadeOutDisplay(); } fheroes2::GameMode Game::MainMenu( bool isFirstGameRun ) { // Stop all sounds, but not the music AudioManager::stopSounds(); AudioManager::PlayMusicAsync( MUS::MAINMENU, Music::PlaybackMode::RESUME_AND_PLAY_INFINITE ); Settings & conf = Settings::Get(); conf.SetGameType( TYPE_MENU ); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); fheroes2::Display & display = fheroes2::Display::instance(); // image background fheroes2::drawMainMenuScreen(); if ( isFirstGameRun ) { // Fade in Main Menu image before showing messages. This also resets the "need fade" state to have no fade-in after these messages. fheroes2::validateFadeInAndRender(); fheroes2::selectLanguage( fheroes2::getSupportedLanguages(), fheroes2::getLanguageFromAbbreviation( conf.getGameLanguage() ) ); if ( System::isHandheldDevice() ) { // Handheld devices should use the minimal game's resolution. Users on handheld devices aren't asked to choose resolution. fheroes2::showStandardTextMessage( _( "Greetings!" ), _( "Welcome to Heroes of Might and Magic II powered by fheroes2 engine!" ), Dialog::OK ); } else { fheroes2::showStandardTextMessage( _( "Greetings!" ), _( "Welcome to Heroes of Might and Magic II powered by the fheroes2 engine!\nBefore starting the game please select a game resolution." ), Dialog::OK ); const bool isResolutionChanged = Dialog::SelectResolution(); if ( isResolutionChanged ) { fheroes2::drawMainMenuScreen(); } } fheroes2::Text header( _( "Please Remember" ), fheroes2::FontType::normalYellow() ); fheroes2::MultiFontText body; body.add( { _( "You can always change the language, resolution and settings of the game by clicking on the " ), fheroes2::FontType::normalWhite() } ); body.add( { _( "door" ), fheroes2::FontType::normalYellow() } ); body.add( { _( " on the left side of the Main Menu, or with the " ), fheroes2::FontType::normalWhite() } ); body.add( { _( "CONFIG" ), fheroes2::FontType::normalYellow() } ); body.add( { _( " button from the " ), fheroes2::FontType::normalWhite() } ); body.add( { _( "NEW GAME" ), fheroes2::FontType::normalYellow() } ); body.add( { _( " menu. \n\nEnjoy the game!" ), fheroes2::FontType::normalWhite() } ); fheroes2::showMessage( header, body, Dialog::OK ); conf.resetFirstGameRun(); conf.Save( Settings::configFileName ); } outputMainMenuInTextSupportMode(); LocalEvent & le = LocalEvent::Get(); fheroes2::Button buttonNewGame( 0, 0, ICN::BTNSHNGL, NEWGAME_DEFAULT, NEWGAME_DEFAULT + 2 ); fheroes2::Button buttonLoadGame( 0, 0, ICN::BTNSHNGL, LOADGAME_DEFAULT, LOADGAME_DEFAULT + 2 ); fheroes2::Button buttonHighScores( 0, 0, ICN::BTNSHNGL, HIGHSCORES_DEFAULT, HIGHSCORES_DEFAULT + 2 ); fheroes2::Button buttonCredits( 0, 0, ICN::BTNSHNGL, CREDITS_DEFAULT, CREDITS_DEFAULT + 2 ); fheroes2::Button buttonQuit( 0, 0, ICN::BTNSHNGL, QUIT_DEFAULT, QUIT_DEFAULT + 2 ); const fheroes2::Sprite & lantern10 = fheroes2::AGG::GetICN( ICN::SHNGANIM, 0 ); fheroes2::Blit( lantern10, display, lantern10.x(), lantern10.y() ); const fheroes2::Sprite & lantern11 = fheroes2::AGG::GetICN( ICN::SHNGANIM, ICN::AnimationFrame( ICN::SHNGANIM, 0, 0 ) ); fheroes2::Blit( lantern11, display, lantern11.x(), lantern11.y() ); buttonNewGame.draw(); buttonLoadGame.draw(); buttonHighScores.draw(); buttonCredits.draw(); buttonQuit.draw(); fheroes2::validateFadeInAndRender(); const double scaleX = static_cast( display.width() ) / fheroes2::Display::DEFAULT_WIDTH; const double scaleY = static_cast( display.height() ) / fheroes2::Display::DEFAULT_HEIGHT; const double scale = std::min( scaleX, scaleY ); const int32_t offsetX = static_cast( std::lround( display.width() - fheroes2::Display::DEFAULT_WIDTH * scale ) ) / 2; const int32_t offsetY = static_cast( std::lround( display.height() - fheroes2::Display::DEFAULT_HEIGHT * scale ) ) / 2; const fheroes2::Rect settingsArea( static_cast( 63 * scale ) + offsetX, static_cast( 202 * scale ) + offsetY, static_cast( 108 * scale ), static_cast( 160 * scale ) ); uint32_t lantern_frame = 0; std::vector buttons{ { NEWGAME_DEFAULT, buttonNewGame, false, false }, { LOADGAME_DEFAULT, buttonLoadGame, false, false }, { HIGHSCORES_DEFAULT, buttonHighScores, false, false }, { CREDITS_DEFAULT, buttonCredits, false, false }, { QUIT_DEFAULT, buttonQuit, false, false } }; for ( size_t i = 0; le.MouseMotion() && i < buttons.size(); ++i ) { const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::BTNSHNGL, buttons[i].frame ); fheroes2::Blit( sprite, display, sprite.x(), sprite.y() ); } fheroes2::Sprite highlightDoor = fheroes2::AGG::GetICN( ICN::SHNGANIM, 18 ); fheroes2::ApplyPalette( highlightDoor, 8 ); while ( true ) { if ( !le.HandleEvents( true, true ) ) { if ( Interface::AdventureMap::EventExit() == fheroes2::GameMode::QUIT_GAME ) { break; } else { continue; } } bool redrawScreen = false; for ( size_t i = 0; i < buttons.size(); ++i ) { buttons[i].wasOver = buttons[i].isOver; if ( le.MousePressLeft( buttons[i].button.area() ) ) { buttons[i].button.drawOnPress(); } else { buttons[i].button.drawOnRelease(); } buttons[i].isOver = le.MouseCursor( buttons[i].button.area() ); if ( buttons[i].isOver != buttons[i].wasOver ) { uint32_t frame = buttons[i].frame; if ( buttons[i].isOver && !buttons[i].wasOver ) ++frame; if ( !redrawScreen ) { redrawScreen = true; } const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::BTNSHNGL, frame ); fheroes2::Blit( sprite, display, sprite.x(), sprite.y() ); } } if ( redrawScreen ) { display.render(); } if ( HotKeyPressEvent( HotKeyEvent::MAIN_MENU_NEW_GAME ) || le.MouseClickLeft( buttonNewGame.area() ) ) { return fheroes2::GameMode::NEW_GAME; } if ( HotKeyPressEvent( HotKeyEvent::MAIN_MENU_LOAD_GAME ) || le.MouseClickLeft( buttonLoadGame.area() ) ) { return fheroes2::GameMode::LOAD_GAME; } if ( HotKeyPressEvent( HotKeyEvent::MAIN_MENU_HIGHSCORES ) || le.MouseClickLeft( buttonHighScores.area() ) ) { return fheroes2::GameMode::HIGHSCORES_STANDARD; } if ( HotKeyPressEvent( HotKeyEvent::MAIN_MENU_CREDITS ) || le.MouseClickLeft( buttonCredits.area() ) ) { return fheroes2::GameMode::CREDITS; } if ( HotKeyPressEvent( HotKeyEvent::MAIN_MENU_QUIT ) || HotKeyPressEvent( HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonQuit.area() ) ) { if ( Interface::AdventureMap::EventExit() == fheroes2::GameMode::QUIT_GAME ) { return fheroes2::GameMode::QUIT_GAME; } } else if ( HotKeyPressEvent( HotKeyEvent::MAIN_MENU_SETTINGS ) || le.MouseClickLeft( settingsArea ) ) { fheroes2::openGameSettings(); return fheroes2::GameMode::MAIN_MENU; } else if ( conf.isEditorEnabled() && HotKeyPressEvent( HotKeyEvent::EDITOR_MAIN_MENU ) ) { if ( Game::isPriceOfLoyaltyCampaignPresent() ) { return fheroes2::GameMode::EDITOR_MAIN_MENU; } fheroes2::showStandardTextMessage( _( "Editor" ), _( "The Editor requires \"The Price of Loyalty\" expansion files to work." ), Dialog::OK ); } // right info if ( le.MousePressRight( buttonQuit.area() ) ) fheroes2::showStandardTextMessage( _( "Quit" ), _( "Quit Heroes of Might and Magic II and return to the operating system." ), Dialog::ZERO ); else if ( le.MousePressRight( buttonLoadGame.area() ) ) fheroes2::showStandardTextMessage( _( "Load Game" ), _( "Load a previously saved game." ), Dialog::ZERO ); else if ( le.MousePressRight( buttonCredits.area() ) ) fheroes2::showStandardTextMessage( _( "Credits" ), _( "View the credits screen." ), Dialog::ZERO ); else if ( le.MousePressRight( buttonHighScores.area() ) ) fheroes2::showStandardTextMessage( _( "High Scores" ), _( "View the high scores screen." ), Dialog::ZERO ); else if ( le.MousePressRight( buttonNewGame.area() ) ) fheroes2::showStandardTextMessage( _( "New Game" ), _( "Start a single or multi-player game." ), Dialog::ZERO ); else if ( le.MousePressRight( settingsArea ) ) fheroes2::showStandardTextMessage( _( "Game Settings" ), _( "Change language, resolution and settings of the game." ), Dialog::ZERO ); if ( validateAnimationDelay( MAIN_MENU_DELAY ) ) { const fheroes2::Sprite & lantern12 = fheroes2::AGG::GetICN( ICN::SHNGANIM, ICN::AnimationFrame( ICN::SHNGANIM, 0, lantern_frame ) ); ++lantern_frame; fheroes2::Blit( lantern12, display, lantern12.x(), lantern12.y() ); if ( le.MouseCursor( settingsArea ) ) { const int32_t doorOffsetY = static_cast( 55 * scale ) + offsetY; fheroes2::Blit( highlightDoor, 0, doorOffsetY, display, highlightDoor.x(), highlightDoor.y() + doorOffsetY, highlightDoor.width(), highlightDoor.height() ); } display.render(); } } return fheroes2::GameMode::QUIT_GAME; } fheroes2-1.0.12+dfsg/src/fheroes2/game/game_mainmenu_ui.cpp000066400000000000000000000141221456075706000234610ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "game_mainmenu_ui.h" #include #include #include #include "agg_image.h" #include "dialog.h" #include "game.h" #include "icn.h" #include "image.h" #include "math_base.h" #include "screen.h" #include "settings.h" #include "ui_tool.h" namespace { void drawSprite( fheroes2::Image & output, const int icnId, const uint32_t index ) { const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( icnId, index ); fheroes2::Blit( sprite, 0, 0, output, sprite.x(), sprite.y(), sprite.width(), sprite.height() ); } void renderWindowBackground( fheroes2::Image & output, const fheroes2::Rect & roi ) { if ( roi.width == 0 || roi.height == 0 ) { // Nothing to render. return; } const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); const fheroes2::Sprite & background = fheroes2::AGG::GetICN( isEvilInterface ? ICN::STONEBAK_EVIL : ICN::STONEBAK, 0 ); const int32_t stepX = ( roi.width + background.width() ) / background.width(); const int32_t stepY = ( roi.height + background.height() ) / background.height(); fheroes2::Point offset{ roi.x, roi.y }; for ( int y = 0; y < stepY; ++y ) { offset.x = roi.x; const int32_t height = std::min( background.height(), roi.height - y * background.height() ); for ( int x = 0; x < stepX; ++x ) { fheroes2::Copy( background, 0, 0, output, offset.x, offset.y, std::min( background.width(), roi.width - x * background.width() ), height ); offset.x += background.width(); } offset.y += background.height(); } } void fillScreenBorders( fheroes2::Image & output, const fheroes2::Rect & backgroundRoi ) { if ( backgroundRoi.y == 0 ) { renderWindowBackground( output, { 0, 0, backgroundRoi.x, output.height() } ); renderWindowBackground( output, { backgroundRoi.x + backgroundRoi.width, 0, output.width() - backgroundRoi.x - backgroundRoi.width, output.height() } ); } else { assert( backgroundRoi.x == 0 ); renderWindowBackground( output, { 0, 0, output.width(), backgroundRoi.y } ); renderWindowBackground( output, { 0, backgroundRoi.y + backgroundRoi.height, output.width(), output.height() - backgroundRoi.y - backgroundRoi.height } ); } } } namespace fheroes2 { void drawMainMenuScreen() { Display & display = Display::instance(); const Sprite & mainMenuBackground = AGG::GetICN( ICN::HEROES, 0 ); Copy( mainMenuBackground, 0, 0, display, mainMenuBackground.x(), mainMenuBackground.y(), mainMenuBackground.width(), mainMenuBackground.height() ); drawSprite( display, ICN::BTNSHNGL, 1 ); drawSprite( display, ICN::BTNSHNGL, 5 ); drawSprite( display, ICN::BTNSHNGL, 9 ); drawSprite( display, ICN::BTNSHNGL, 13 ); drawSprite( display, ICN::BTNSHNGL, 17 ); fillScreenBorders( display, { mainMenuBackground.x(), mainMenuBackground.y(), mainMenuBackground.width(), mainMenuBackground.height() } ); } void drawEditorMainMenuScreen() { Display & display = Display::instance(); const Sprite & background = AGG::GetICN( ICN::EDITOR, 0 ); Copy( background, 0, 0, display, background.x(), background.y(), background.width(), background.height() ); fillScreenBorders( display, { background.x(), background.y(), background.width(), background.height() } ); } Point drawButtonPanel() { const fheroes2::Sprite & back = fheroes2::AGG::GetICN( ICN::HEROES, 0 ); const fheroes2::Sprite & panel = fheroes2::AGG::GetICN( ICN::REDBACK, 0 ); const uint32_t panelOffset = fheroes2::Display::DEFAULT_HEIGHT - panel.height(); const uint32_t panelXPos = back.width() + back.x() - ( panel.width() + panelOffset ); fheroes2::Blit( panel, fheroes2::Display::instance(), panelXPos, panelOffset + back.y() ); const int32_t buttonMiddlePos = panelXPos + SHADOWWIDTH + ( panel.width() - SHADOWWIDTH ) / 2; const fheroes2::Sprite & buttonSample = fheroes2::AGG::GetICN( ICN::BTNNEWGM, 0 ); const int32_t buttonWidth = buttonSample.width(); const int32_t buttonXPos = buttonMiddlePos - buttonWidth / 2 - 3; // 3 is button shadow const int32_t buttonYPos = 46 + back.y(); return { buttonXPos, buttonYPos }; } void validateFadeInAndRender() { if ( Game::validateDisplayFadeIn() ) { fheroes2::fadeInDisplay(); } else { fheroes2::Display::instance().render(); } } } fheroes2-1.0.12+dfsg/src/fheroes2/game/game_mainmenu_ui.h000066400000000000000000000034171456075706000231330ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include "math_base.h" namespace fheroes2 { void drawMainMenuScreen(); void drawEditorMainMenuScreen(); Point drawButtonPanel(); // If display fade-in state is set reset it to false and fade-in the full display image. Otherwise render full display image without fade-in. void validateFadeInAndRender(); } fheroes2-1.0.12+dfsg/src/fheroes2/game/game_mode.h000066400000000000000000000043751456075706000215550ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once namespace fheroes2 { enum class GameMode : int { CANCEL = 0, QUIT_GAME, MAIN_MENU, NEW_GAME, LOAD_GAME, HIGHSCORES_STANDARD, HIGHSCORES_CAMPAIGN, CREDITS, NEW_STANDARD, NEW_CAMPAIGN_SELECTION, NEW_SUCCESSION_WARS_CAMPAIGN, NEW_PRICE_OF_LOYALTY_CAMPAIGN, NEW_MULTI, NEW_HOT_SEAT, NEW_BATTLE_ONLY, LOAD_STANDARD, LOAD_CAMPAIGN, LOAD_MULTI, LOAD_HOT_SEAT, SCENARIO_INFO, SELECT_SCENARIO, START_GAME, SAVE_GAME, END_TURN, SELECT_CAMPAIGN_SCENARIO, COMPLETE_CAMPAIGN_SCENARIO, COMPLETE_CAMPAIGN_SCENARIO_FROM_LOAD_FILE, EDITOR_MAIN_MENU, EDITOR_NEW_MAP, EDITOR_LOAD_MAP }; } fheroes2-1.0.12+dfsg/src/fheroes2/game/game_newgame.cpp000066400000000000000000001020151456075706000225750ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "game.h" #include #include #include #include #include #include #include #include #include #include "agg_image.h" #include "audio.h" #include "audio_manager.h" #include "campaign_savedata.h" #include "campaign_scenariodata.h" #include "cursor.h" #include "dialog.h" #include "dialog_game_settings.h" #include "game_delays.h" #include "game_hotkeys.h" #include "game_io.h" #include "game_mainmenu_ui.h" #include "game_mode.h" #include "game_video.h" #include "game_video_type.h" #include "icn.h" #include "image.h" #include "localevent.h" #include "logging.h" #include "math_base.h" #include "mus.h" #include "screen.h" #include "settings.h" #include "smk_decoder.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_text.h" #include "ui_tool.h" namespace { const int32_t buttonYStep = 66; std::unique_ptr getVideo( const std::string & fileName ) { std::string videoPath; if ( !Video::getVideoFilePath( fileName, videoPath ) ) { return nullptr; } std::unique_ptr video = std::make_unique( videoPath ); if ( video->frameCount() < 1 ) { return nullptr; } return video; } void outputNewMenuInTextSupportMode() { START_TEXT_SUPPORT_MODE COUT( "New Game\n" ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::MAIN_MENU_STANDARD ) << " to choose Standard Game." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::MAIN_MENU_CAMPAIGN ) << " to choose Campaign Game." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::MAIN_MENU_MULTI ) << " to choose Multiplayer Game." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::MAIN_MENU_SETTINGS ) << " to open Game Settings." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::DEFAULT_CANCEL ) << " to come back to Main Menu." ) } void outputNewCampaignSelectionInTextSupportMode() { START_TEXT_SUPPORT_MODE COUT( "New Campaign\n" ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::MAIN_MENU_NEW_ORIGINAL_CAMPAIGN ) << " to choose The Original Campaign." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::MAIN_MENU_NEW_EXPANSION_CAMPAIGN ) << " to choose The Expansion Campaign." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::DEFAULT_CANCEL ) << " to come back to Main Menu." ) } void outputNewSuccessionWarsCampaignInTextSupportMode() { START_TEXT_SUPPORT_MODE COUT( "Choose your Lord:\n" ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::CAMPAIGN_ROLAND ) << " to choose Roland Campaign." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::CAMPAIGN_ARCHIBALD ) << " to choose Archibald Campaign." ) } void outputPriceOfLoyaltyCampaignInTextSupportMode() { START_TEXT_SUPPORT_MODE COUT( "Select your campaign\n" ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::CAMPAIGN_PRICE_OF_LOYALTY ) << " to choose The Price of Loyalty Campaign." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::CAMPAIGN_VOYAGE_HOME ) << " to choose Voyage Home Campaign." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::CAMPAIGN_WIZARDS_ISLE ) << " to choose Wizard's Isle Campaign." ) COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::CAMPAIGN_DESCENDANTS ) << " to choose Descendants Campaign." ) } void showMissingVideoFilesWindow() { fheroes2::showMessage( fheroes2::Text{ _( "Warning!" ), fheroes2::FontType::normalYellow() }, fheroes2::Text{ _( "Required video files for campaign selection window are missing. " "Please make sure that all necessary files are present in the system." ), fheroes2::FontType::normalWhite() }, Dialog::OK ); } } fheroes2::GameMode Game::NewStandard() { Settings & conf = Settings::Get(); if ( conf.isCampaignGameType() ) conf.SetCurrentFileInfo( {} ); conf.SetGameType( Game::TYPE_STANDARD ); conf.SetPreferablyCountPlayers( 0 ); return fheroes2::GameMode::SELECT_SCENARIO; } fheroes2::GameMode Game::NewBattleOnly() { Settings & conf = Settings::Get(); conf.SetGameType( Game::TYPE_BATTLEONLY ); return fheroes2::GameMode::NEW_MULTI; } fheroes2::GameMode Game::NewHotSeat() { Settings & conf = Settings::Get(); if ( conf.isCampaignGameType() ) conf.SetCurrentFileInfo( {} ); if ( conf.IsGameType( Game::TYPE_BATTLEONLY ) ) { conf.SetPreferablyCountPlayers( 2 ); // Redraw the main menu screen without multiplayer sub-menu to show it after the battle using screen restorer. fheroes2::drawMainMenuScreen(); return StartBattleOnly(); } else { conf.SetGameType( Game::TYPE_HOTSEAT ); const uint32_t select = SelectCountPlayers(); if ( select > 0 ) { conf.SetPreferablyCountPlayers( select ); return fheroes2::GameMode::SELECT_SCENARIO; } } return fheroes2::GameMode::MAIN_MENU; } fheroes2::GameMode Game::CampaignSelection() { if ( !isPriceOfLoyaltyCampaignPresent() ) { return fheroes2::GameMode::NEW_SUCCESSION_WARS_CAMPAIGN; } outputNewCampaignSelectionInTextSupportMode(); fheroes2::drawMainMenuScreen(); const fheroes2::Point buttonPos = fheroes2::drawButtonPanel(); fheroes2::Button buttonSuccessionWars( buttonPos.x, buttonPos.y, ICN::BUTTON_ORIGINAL_CAMPAIGN, 0, 1 ); fheroes2::Button buttonPriceOfLoyalty( buttonPos.x, buttonPos.y + buttonYStep * 1, ICN::BUTTON_EXPANSION_CAMPAIGN, 0, 1 ); fheroes2::Button buttonCancelGame( buttonPos.x, buttonPos.y + buttonYStep * 5, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); buttonSuccessionWars.draw(); buttonPriceOfLoyalty.draw(); buttonCancelGame.draw(); fheroes2::Display::instance().render(); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { le.MousePressLeft( buttonSuccessionWars.area() ) ? buttonSuccessionWars.drawOnPress() : buttonSuccessionWars.drawOnRelease(); le.MousePressLeft( buttonPriceOfLoyalty.area() ) ? buttonPriceOfLoyalty.drawOnPress() : buttonPriceOfLoyalty.drawOnRelease(); le.MousePressLeft( buttonCancelGame.area() ) ? buttonCancelGame.drawOnPress() : buttonCancelGame.drawOnRelease(); if ( le.MouseClickLeft( buttonSuccessionWars.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_NEW_ORIGINAL_CAMPAIGN ) ) { return fheroes2::GameMode::NEW_SUCCESSION_WARS_CAMPAIGN; } if ( le.MouseClickLeft( buttonPriceOfLoyalty.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_NEW_EXPANSION_CAMPAIGN ) ) { return fheroes2::GameMode::NEW_PRICE_OF_LOYALTY_CAMPAIGN; } if ( HotKeyPressEvent( HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancelGame.area() ) ) { return fheroes2::GameMode::MAIN_MENU; } if ( le.MousePressRight( buttonSuccessionWars.area() ) ) { fheroes2::showStandardTextMessage( _( "Original Campaign" ), _( "Either Roland's or Archibald's campaign from the original Heroes of Might and Magic II." ), Dialog::ZERO ); } if ( le.MousePressRight( buttonPriceOfLoyalty.area() ) ) { fheroes2::showStandardTextMessage( _( "Expansion Campaign" ), _( "One of the four new campaigns from the Price of Loyalty expansion set." ), Dialog::ZERO ); } if ( le.MousePressRight( buttonCancelGame.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the main menu." ), Dialog::ZERO ); } } return fheroes2::GameMode::QUIT_GAME; } fheroes2::GameMode Game::NewSuccessionWarsCampaign() { Settings::Get().SetGameType( Game::TYPE_CAMPAIGN ); // Reset all sound and music before playing videos AudioManager::ResetAudio(); fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::Point roiOffset( ( display.width() - display.DEFAULT_WIDTH ) / 2, ( display.height() - display.DEFAULT_HEIGHT ) / 2 ); // Fade-out screen before playing video. fheroes2::fadeOutDisplay(); const fheroes2::Text loadingScreen( _( "Loading video. Please wait..." ), fheroes2::FontType::normalWhite() ); loadingScreen.draw( display.width() / 2 - loadingScreen.width() / 2, display.height() / 2 - loadingScreen.height() / 2 + 2, display ); display.render(); Video::ShowVideo( "INTRO.SMK", Video::VideoAction::PLAY_TILL_VIDEO_END ); Campaign::CampaignSaveData & campaignSaveData = Campaign::CampaignSaveData::Get(); campaignSaveData.reset(); std::unique_ptr video = getVideo( "CHOOSE.SMK" ); if ( !video ) { showMissingVideoFilesWindow(); campaignSaveData.setCurrentScenarioInfo( { Campaign::ROLAND_CAMPAIGN, 0 } ); return fheroes2::GameMode::SELECT_CAMPAIGN_SCENARIO; } const std::array campaignRoi{ fheroes2::Rect( 382 + roiOffset.x, 58 + roiOffset.y, 222, 298 ), fheroes2::Rect( 30 + roiOffset.x, 59 + roiOffset.y, 224, 297 ) }; const uint64_t customDelay = static_cast( std::lround( 1000.0 / video->fps() ) ); outputNewSuccessionWarsCampaignInTextSupportMode(); AudioManager::ResetAudio(); Video::ShowVideo( "CHOOSEW.SMK", Video::VideoAction::IGNORE_VIDEO ); const fheroes2::ScreenPaletteRestorer screenRestorer; std::vector palette = video->getCurrentPalette(); screenRestorer.changePalette( palette.data() ); const CursorRestorer cursorRestorer( true, Cursor::POINTER ); Cursor::Get().setVideoPlaybackCursor(); // Immediately indicate that the delay has passed to render first frame immediately. Game::passCustomAnimationDelay( customDelay ); // Make sure that the first run is passed immediately. assert( !Game::isCustomDelayNeeded( customDelay ) ); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents( Game::isCustomDelayNeeded( customDelay ) ) ) { if ( le.MouseClickLeft( campaignRoi[0] ) || HotKeyPressEvent( HotKeyEvent::CAMPAIGN_ROLAND ) ) { campaignSaveData.setCurrentScenarioInfo( { Campaign::ROLAND_CAMPAIGN, 0 } ); break; } if ( le.MouseClickLeft( campaignRoi[1] ) || HotKeyPressEvent( HotKeyEvent::CAMPAIGN_ARCHIBALD ) ) { campaignSaveData.setCurrentScenarioInfo( { Campaign::ARCHIBALD_CAMPAIGN, 0 } ); break; } size_t highlightCampaignId = campaignRoi.size(); for ( size_t i = 0; i < campaignRoi.size(); ++i ) { if ( le.MouseCursor( campaignRoi[i] ) ) { highlightCampaignId = i; break; } } if ( Game::validateCustomAnimationDelay( customDelay ) ) { fheroes2::Rect frameRoi( roiOffset.x, roiOffset.y, 0, 0 ); video->getNextFrame( display, frameRoi.x, frameRoi.y, frameRoi.width, frameRoi.height, palette ); if ( highlightCampaignId < campaignRoi.size() ) { const fheroes2::Rect & roi = campaignRoi[highlightCampaignId]; fheroes2::DrawRect( display, roi, 51 ); fheroes2::DrawRect( display, { roi.x - 1, roi.y - 1, roi.width + 2, roi.height + 2 }, 51 ); } display.render( frameRoi ); if ( video->frameCount() <= video->getCurrentFrame() ) { video->resetFrame(); } } } screenRestorer.changePalette( nullptr ); // Update the frame but do not render it. display.fill( 0 ); display.updateNextRenderRoi( { 0, 0, display.width(), display.height() } ); // Set the fade-in for the Campaign scenario info. setDisplayFadeIn(); return fheroes2::GameMode::SELECT_CAMPAIGN_SCENARIO; } fheroes2::GameMode Game::NewPriceOfLoyaltyCampaign() { // TODO: Properly choose the campaign instead of this hackish way Campaign::CampaignSaveData & campaignSaveData = Campaign::CampaignSaveData::Get(); campaignSaveData.reset(); campaignSaveData.setCurrentScenarioInfo( { Campaign::PRICE_OF_LOYALTY_CAMPAIGN, 0 } ); std::array, 4> videos{ getVideo( "IVYPOL.SMK" ), getVideo( "IVYVOY.SMK" ), getVideo( "IVYWIZ.SMK" ), getVideo( "IVYDES.SMK" ) }; if ( !videos[0] ) { // File doesn't exist. Fallback to PoL campaign. showMissingVideoFilesWindow(); return fheroes2::GameMode::SELECT_CAMPAIGN_SCENARIO; } // Fade-out display before playing video. fheroes2::fadeOutDisplay(); outputPriceOfLoyaltyCampaignInTextSupportMode(); const fheroes2::ScreenPaletteRestorer screenRestorer; const CursorRestorer cursorRestorer( true, Cursor::POINTER ); std::vector palette = videos[0]->getCurrentPalette(); screenRestorer.changePalette( palette.data() ); Cursor::Get().setVideoPlaybackCursor(); fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::Point roiOffset( ( display.width() - display.DEFAULT_WIDTH ) / 2, ( display.height() - display.DEFAULT_HEIGHT ) / 2 ); const fheroes2::Sprite & background = fheroes2::AGG::GetICN( ICN::X_IVY, 1 ); fheroes2::Blit( background, 0, 0, display, roiOffset.x, roiOffset.y, background.width(), background.height() ); const fheroes2::Sprite & campaignChoice = fheroes2::AGG::GetICN( ICN::X_IVY, 0 ); fheroes2::Blit( campaignChoice, 0, 0, display, roiOffset.x + campaignChoice.x(), roiOffset.y + campaignChoice.y(), campaignChoice.width(), campaignChoice.height() ); display.render(); const std::array activeCampaignArea{ fheroes2::Rect( roiOffset.x + 192, roiOffset.y + 23, 248, 163 ), fheroes2::Rect( roiOffset.x + 19, roiOffset.y + 120, 166, 193 ), fheroes2::Rect( roiOffset.x + 450, roiOffset.y + 120, 166, 193 ), fheroes2::Rect( roiOffset.x + 192, roiOffset.y + 240, 248, 163 ) }; const std::array renderCampaignArea{ fheroes2::Rect( roiOffset.x + 214, roiOffset.y + 47, 248, 163 ), fheroes2::Rect( roiOffset.x + 41, roiOffset.y + 140, 166, 193 ), fheroes2::Rect( roiOffset.x + 472, roiOffset.y + 131, 166, 193 ), fheroes2::Rect( roiOffset.x + 214, roiOffset.y + 273, 248, 163 ) }; size_t highlightCampaignId = videos.size(); fheroes2::GameMode gameChoice = fheroes2::GameMode::NEW_CAMPAIGN_SELECTION; uint64_t customDelay = 0; // Immediately indicate that the delay has passed to render first frame immediately. Game::passCustomAnimationDelay( customDelay ); // Make sure that the first run is passed immediately. assert( !Game::isCustomDelayNeeded( customDelay ) ); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents( highlightCampaignId < videos.size() ? Game::isCustomDelayNeeded( customDelay ) : true ) ) { if ( le.MouseClickLeft( activeCampaignArea[0] ) || HotKeyPressEvent( HotKeyEvent::CAMPAIGN_PRICE_OF_LOYALTY ) ) { campaignSaveData.setCurrentScenarioInfo( { Campaign::PRICE_OF_LOYALTY_CAMPAIGN, 0 } ); gameChoice = fheroes2::GameMode::SELECT_CAMPAIGN_SCENARIO; break; } if ( le.MouseClickLeft( activeCampaignArea[1] ) || HotKeyPressEvent( HotKeyEvent::CAMPAIGN_VOYAGE_HOME ) ) { campaignSaveData.setCurrentScenarioInfo( { Campaign::VOYAGE_HOME_CAMPAIGN, 0 } ); gameChoice = fheroes2::GameMode::SELECT_CAMPAIGN_SCENARIO; break; } if ( le.MouseClickLeft( activeCampaignArea[2] ) || HotKeyPressEvent( HotKeyEvent::CAMPAIGN_WIZARDS_ISLE ) ) { campaignSaveData.setCurrentScenarioInfo( { Campaign::WIZARDS_ISLE_CAMPAIGN, 0 } ); gameChoice = fheroes2::GameMode::SELECT_CAMPAIGN_SCENARIO; break; } if ( le.MouseClickLeft( activeCampaignArea[3] ) || HotKeyPressEvent( HotKeyEvent::CAMPAIGN_DESCENDANTS ) ) { campaignSaveData.setCurrentScenarioInfo( { Campaign::DESCENDANTS_CAMPAIGN, 0 } ); gameChoice = fheroes2::GameMode::SELECT_CAMPAIGN_SCENARIO; break; } const size_t beforeCampaignId = highlightCampaignId; highlightCampaignId = videos.size(); for ( size_t i = 0; i < activeCampaignArea.size(); ++i ) { if ( le.MouseCursor( activeCampaignArea[i] ) && videos[i] ) { highlightCampaignId = i; customDelay = static_cast( std::lround( 1000.0 / videos[highlightCampaignId]->fps() ) ); break; } } if ( highlightCampaignId != beforeCampaignId ) { fheroes2::Blit( background, 0, 0, display, roiOffset.x, roiOffset.y, background.width(), background.height() ); fheroes2::Blit( campaignChoice, 0, 0, display, roiOffset.x + campaignChoice.x(), roiOffset.y + campaignChoice.y(), campaignChoice.width(), campaignChoice.height() ); if ( highlightCampaignId >= videos.size() ) { display.render(); } } if ( highlightCampaignId < videos.size() && Game::validateCustomAnimationDelay( customDelay ) ) { fheroes2::Rect frameRoi( renderCampaignArea[highlightCampaignId].x, renderCampaignArea[highlightCampaignId].y, 0, 0 ); videos[highlightCampaignId]->getNextFrame( display, frameRoi.x, frameRoi.y, frameRoi.width, frameRoi.height, palette ); fheroes2::Blit( background, frameRoi.x - roiOffset.x, frameRoi.y - roiOffset.y, display, frameRoi.x, frameRoi.y, frameRoi.width, frameRoi.height ); display.render( frameRoi ); if ( videos[highlightCampaignId]->frameCount() <= videos[highlightCampaignId]->getCurrentFrame() ) { videos[highlightCampaignId]->resetFrame(); } } } // Update the frame but do not render it. display.fill( 0 ); display.updateNextRenderRoi( { 0, 0, display.width(), display.height() } ); // Set the fade-in for the Campaign scenario info. setDisplayFadeIn(); return gameChoice; } fheroes2::GameMode Game::NewNetwork() { Settings & conf = Settings::Get(); conf.SetGameType( conf.GameType() | Game::TYPE_NETWORK ); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); fheroes2::drawMainMenuScreen(); const fheroes2::Point buttonPos = fheroes2::drawButtonPanel(); fheroes2::Button buttonHost( buttonPos.x, buttonPos.y, ICN::BTNNET, 0, 1 ); fheroes2::Button buttonGuest( buttonPos.x, buttonPos.y + buttonYStep, ICN::BTNNET, 2, 3 ); fheroes2::Button buttonCancelGame( buttonPos.x, buttonPos.y + buttonYStep * 2, ICN::BTNMP, 8, 9 ); buttonHost.draw(); buttonGuest.draw(); buttonCancelGame.draw(); fheroes2::Display::instance().render(); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { le.MousePressLeft( buttonHost.area() ) ? buttonHost.drawOnPress() : buttonHost.drawOnRelease(); le.MousePressLeft( buttonGuest.area() ) ? buttonGuest.drawOnPress() : buttonGuest.drawOnRelease(); le.MousePressLeft( buttonCancelGame.area() ) ? buttonCancelGame.drawOnPress() : buttonCancelGame.drawOnRelease(); if ( HotKeyPressEvent( HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancelGame.area() ) ) return fheroes2::GameMode::MAIN_MENU; // right info if ( le.MousePressRight( buttonHost.area() ) ) fheroes2::showStandardTextMessage( _( "Host" ), _( "The host sets up the game options. There can only be one host per network game." ), Dialog::ZERO ); if ( le.MousePressRight( buttonGuest.area() ) ) fheroes2::showStandardTextMessage( _( "Guest" ), _( "The guest waits for the host to set up the game, then is automatically added in. There can be multiple guests for TCP/IP games." ), Dialog::ZERO ); if ( le.MousePressRight( buttonCancelGame.area() ) ) fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the main menu." ), Dialog::ZERO ); } return fheroes2::GameMode::MAIN_MENU; } fheroes2::GameMode Game::NewGame() { outputNewMenuInTextSupportMode(); // Stop all sounds, but not the music AudioManager::stopSounds(); AudioManager::PlayMusicAsync( MUS::MAINMENU, Music::PlaybackMode::RESUME_AND_PLAY_INFINITE ); // reset last save name Game::SetLastSaveName( "" ); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); fheroes2::drawMainMenuScreen(); const fheroes2::Point buttonPos = fheroes2::drawButtonPanel(); fheroes2::Button buttonStandardGame( buttonPos.x, buttonPos.y, ICN::BUTTON_STANDARD_GAME, 0, 1 ); fheroes2::ButtonSprite buttonCampaignGame( buttonPos.x, buttonPos.y + buttonYStep * 1, fheroes2::AGG::GetICN( ICN::BUTTON_CAMPAIGN_GAME, 0 ), fheroes2::AGG::GetICN( ICN::BUTTON_CAMPAIGN_GAME, 1 ), fheroes2::AGG::GetICN( ICN::NEW_CAMPAIGN_DISABLED_BUTTON, 0 ) ); fheroes2::Button buttonMultiGame( buttonPos.x, buttonPos.y + buttonYStep * 2, ICN::BUTTON_MULTIPLAYER_GAME, 0, 1 ); fheroes2::Button buttonBattleGame( buttonPos.x, buttonPos.y + buttonYStep * 3, ICN::BTNBATTLEONLY, 0, 1 ); fheroes2::Button buttonSettings( buttonPos.x, buttonPos.y + buttonYStep * 4, ICN::BUTTON_LARGE_CONFIG, 0, 1 ); fheroes2::Button buttonCancelGame( buttonPos.x, buttonPos.y + buttonYStep * 5, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); if ( !isSuccessionWarsCampaignPresent() ) { buttonCampaignGame.disable(); } buttonStandardGame.draw(); buttonCampaignGame.draw(); buttonMultiGame.draw(); buttonBattleGame.draw(); buttonSettings.draw(); buttonCancelGame.draw(); fheroes2::validateFadeInAndRender(); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { le.MousePressLeft( buttonStandardGame.area() ) ? buttonStandardGame.drawOnPress() : buttonStandardGame.drawOnRelease(); if ( buttonCampaignGame.isEnabled() ) { le.MousePressLeft( buttonCampaignGame.area() ) ? buttonCampaignGame.drawOnPress() : buttonCampaignGame.drawOnRelease(); } le.MousePressLeft( buttonMultiGame.area() ) ? buttonMultiGame.drawOnPress() : buttonMultiGame.drawOnRelease(); le.MousePressLeft( buttonBattleGame.area() ) ? buttonBattleGame.drawOnPress() : buttonBattleGame.drawOnRelease(); le.MousePressLeft( buttonSettings.area() ) ? buttonSettings.drawOnPress() : buttonSettings.drawOnRelease(); le.MousePressLeft( buttonCancelGame.area() ) ? buttonCancelGame.drawOnPress() : buttonCancelGame.drawOnRelease(); if ( HotKeyPressEvent( HotKeyEvent::MAIN_MENU_STANDARD ) || le.MouseClickLeft( buttonStandardGame.area() ) ) return fheroes2::GameMode::NEW_STANDARD; if ( buttonCampaignGame.isEnabled() && ( HotKeyPressEvent( HotKeyEvent::MAIN_MENU_CAMPAIGN ) || le.MouseClickLeft( buttonCampaignGame.area() ) ) ) return fheroes2::GameMode::NEW_CAMPAIGN_SELECTION; if ( HotKeyPressEvent( HotKeyEvent::MAIN_MENU_MULTI ) || le.MouseClickLeft( buttonMultiGame.area() ) ) return fheroes2::GameMode::NEW_MULTI; if ( HotKeyPressEvent( HotKeyEvent::MAIN_MENU_SETTINGS ) || le.MouseClickLeft( buttonSettings.area() ) ) { fheroes2::openGameSettings(); return fheroes2::GameMode::MAIN_MENU; } if ( HotKeyPressEvent( HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancelGame.area() ) ) return fheroes2::GameMode::MAIN_MENU; if ( HotKeyPressEvent( HotKeyEvent::MAIN_MENU_BATTLEONLY ) || le.MouseClickLeft( buttonBattleGame.area() ) ) return fheroes2::GameMode::NEW_BATTLE_ONLY; if ( le.MousePressRight( buttonStandardGame.area() ) ) fheroes2::showStandardTextMessage( _( "Standard Game" ), _( "A single player game playing out a single map." ), Dialog::ZERO ); else if ( le.MousePressRight( buttonCampaignGame.area() ) ) fheroes2::showStandardTextMessage( _( "Campaign Game" ), _( "A single player game playing through a series of maps." ), Dialog::ZERO ); else if ( le.MousePressRight( buttonMultiGame.area() ) ) fheroes2::showStandardTextMessage( _( "Multi-Player Game" ), _( "A multi-player game, with several human players completing against each other on a single map." ), Dialog::ZERO ); else if ( le.MousePressRight( buttonBattleGame.area() ) ) fheroes2::showStandardTextMessage( _( "Battle Only" ), _( "Setup and play a battle without loading any map." ), Dialog::ZERO ); else if ( le.MousePressRight( buttonSettings.area() ) ) fheroes2::showStandardTextMessage( _( "Game Settings" ), _( "Change language, resolution and settings of the game." ), Dialog::ZERO ); else if ( le.MousePressRight( buttonCancelGame.area() ) ) fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the main menu." ), Dialog::ZERO ); } return fheroes2::GameMode::QUIT_GAME; } fheroes2::GameMode Game::NewMulti() { Settings & conf = Settings::Get(); if ( !( conf.IsGameType( Game::TYPE_BATTLEONLY ) ) ) conf.SetGameType( Game::TYPE_STANDARD ); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); fheroes2::drawMainMenuScreen(); const fheroes2::Point buttonPos = fheroes2::drawButtonPanel(); fheroes2::Button buttonHotSeat( buttonPos.x, buttonPos.y, ICN::BUTTON_HOT_SEAT, 0, 1 ); fheroes2::Button buttonNetwork( buttonPos.x, buttonPos.y + buttonYStep * 1, ICN::BTNMP, 2, 3 ); fheroes2::Button buttonCancelGame( buttonPos.x, buttonPos.y + buttonYStep * 5, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); buttonHotSeat.draw(); buttonCancelGame.draw(); buttonNetwork.disable(); fheroes2::Display::instance().render(); LocalEvent & le = LocalEvent::Get(); // newgame loop while ( le.HandleEvents() ) { le.MousePressLeft( buttonHotSeat.area() ) ? buttonHotSeat.drawOnPress() : buttonHotSeat.drawOnRelease(); le.MousePressLeft( buttonCancelGame.area() ) ? buttonCancelGame.drawOnPress() : buttonCancelGame.drawOnRelease(); if ( le.MouseClickLeft( buttonHotSeat.area() ) || HotKeyPressEvent( HotKeyEvent::MAIN_MENU_HOTSEAT ) ) return fheroes2::GameMode::NEW_HOT_SEAT; if ( HotKeyPressEvent( HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancelGame.area() ) ) return fheroes2::GameMode::MAIN_MENU; // right info if ( le.MousePressRight( buttonHotSeat.area() ) ) fheroes2::showStandardTextMessage( _( "Hot Seat" ), _( "Play a Hot Seat game, where 2 to 4 players play around the same computer, switching into the 'Hot Seat' when it is their turn." ), Dialog::ZERO ); if ( le.MousePressRight( buttonCancelGame.area() ) ) fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the main menu." ), Dialog::ZERO ); } return fheroes2::GameMode::QUIT_GAME; } uint32_t Game::SelectCountPlayers() { // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); fheroes2::drawMainMenuScreen(); const fheroes2::Point buttonPos = fheroes2::drawButtonPanel(); fheroes2::Button button2Players( buttonPos.x, buttonPos.y, ICN::BUTTON_2_PLAYERS, 0, 1 ); fheroes2::Button button3Players( buttonPos.x, buttonPos.y + buttonYStep * 1, ICN::BUTTON_3_PLAYERS, 0, 1 ); fheroes2::Button button4Players( buttonPos.x, buttonPos.y + buttonYStep * 2, ICN::BUTTON_4_PLAYERS, 0, 1 ); fheroes2::Button button5Players( buttonPos.x, buttonPos.y + buttonYStep * 3, ICN::BUTTON_5_PLAYERS, 0, 1 ); fheroes2::Button button6Players( buttonPos.x, buttonPos.y + buttonYStep * 4, ICN::BUTTON_6_PLAYERS, 0, 1 ); fheroes2::Button buttonCancel( buttonPos.x, buttonPos.y + buttonYStep * 5, ICN::BUTTON_LARGE_CANCEL, 0, 1 ); button2Players.draw(); button3Players.draw(); button4Players.draw(); button5Players.draw(); button6Players.draw(); buttonCancel.draw(); fheroes2::Display::instance().render(); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { le.MousePressLeft( button2Players.area() ) ? button2Players.drawOnPress() : button2Players.drawOnRelease(); le.MousePressLeft( button3Players.area() ) ? button3Players.drawOnPress() : button3Players.drawOnRelease(); le.MousePressLeft( button4Players.area() ) ? button4Players.drawOnPress() : button4Players.drawOnRelease(); le.MousePressLeft( button5Players.area() ) ? button5Players.drawOnPress() : button5Players.drawOnRelease(); le.MousePressLeft( button6Players.area() ) ? button6Players.drawOnPress() : button6Players.drawOnRelease(); le.MousePressLeft( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); if ( le.MouseClickLeft( button2Players.area() ) || le.KeyPress( fheroes2::Key::KEY_2 ) ) return 2; if ( le.MouseClickLeft( button3Players.area() ) || le.KeyPress( fheroes2::Key::KEY_3 ) ) return 3; if ( le.MouseClickLeft( button4Players.area() ) || le.KeyPress( fheroes2::Key::KEY_4 ) ) return 4; if ( le.MouseClickLeft( button5Players.area() ) || le.KeyPress( fheroes2::Key::KEY_5 ) ) return 5; if ( le.MouseClickLeft( button6Players.area() ) || le.KeyPress( fheroes2::Key::KEY_6 ) ) return 6; if ( HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancel.area() ) ) return 0; // right info if ( le.MousePressRight( button2Players.area() ) ) fheroes2::showStandardTextMessage( _( "2 Players" ), _( "Play with 2 human players, and optionally, up to 4 additional computer players." ), Dialog::ZERO ); if ( le.MousePressRight( button3Players.area() ) ) fheroes2::showStandardTextMessage( _( "3 Players" ), _( "Play with 3 human players, and optionally, up to 3 additional computer players." ), Dialog::ZERO ); if ( le.MousePressRight( button4Players.area() ) ) fheroes2::showStandardTextMessage( _( "4 Players" ), _( "Play with 4 human players, and optionally, up to 2 additional computer players." ), Dialog::ZERO ); if ( le.MousePressRight( button5Players.area() ) ) fheroes2::showStandardTextMessage( _( "5 Players" ), _( "Play with 5 human players, and optionally, up to 1 additional computer player." ), Dialog::ZERO ); if ( le.MousePressRight( button6Players.area() ) ) fheroes2::showStandardTextMessage( _( "6 Players" ), _( "Play with 6 human players." ), Dialog::ZERO ); if ( le.MousePressRight( buttonCancel.area() ) ) fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Cancel back to the main menu." ), Dialog::ZERO ); } return 0; } fheroes2-1.0.12+dfsg/src/fheroes2/game/game_over.cpp000066400000000000000000000547541456075706000221450ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "game_over.h" #include #include #include #include #include "artifact.h" #include "audio.h" #include "audio_manager.h" #include "campaign_savedata.h" #include "campaign_scenariodata.h" #include "castle.h" #include "color.h" #include "dialog.h" #include "game.h" #include "game_video.h" #include "game_video_type.h" #include "gamedefs.h" #include "heroes.h" #include "highscores.h" #include "kingdom.h" #include "maps_fileinfo.h" #include "monster.h" #include "mus.h" #include "players.h" #include "serialize.h" #include "settings.h" #include "tools.h" #include "translations.h" #include "ui_dialog.h" #include "ui_text.h" #include "world.h" namespace { void DialogWins( uint32_t cond ) { std::string body; const Settings & conf = Settings::Get(); if ( conf.isCampaignGameType() ) { const Campaign::ScenarioVictoryCondition victoryCondition = Campaign::getCurrentScenarioVictoryCondition(); if ( victoryCondition == Campaign::ScenarioVictoryCondition::CAPTURE_DRAGON_CITY ) { body = _( "Dragon city has fallen! You are now the Master of the Dragons." ); } } if ( body.empty() ) { switch ( cond ) { case GameOver::WINS_ALL: break; case GameOver::WINS_TOWN: { body = _( "You captured %{name}!\nYou are victorious." ); const Castle * town = world.getCastleEntrance( conf.getCurrentMapInfo().WinsMapsPositionObject() ); assert( town != nullptr ); if ( town ) { StringReplace( body, "%{name}", town->GetName() ); } break; } case GameOver::WINS_HERO: { body = _( "You have captured the enemy hero %{name}!\nYour quest is complete." ); const Heroes * hero = world.GetHeroesCondWins(); assert( hero != nullptr ); if ( hero ) { StringReplace( body, "%{name}", hero->GetName() ); } break; } case GameOver::WINS_ARTIFACT: body = _( "You have found the %{name}.\nYour quest is complete." ); if ( conf.getCurrentMapInfo().WinsFindUltimateArtifact() ) { StringReplace( body, "%{name}", _( "Ultimate Artifact" ) ); } else { const Artifact art = conf.getCurrentMapInfo().WinsFindArtifactID(); StringReplace( body, "%{name}", art.GetName() ); } break; case GameOver::WINS_SIDE: body = _( "The enemy is beaten.\nYour side has triumphed!" ); break; case GameOver::WINS_GOLD: body = _( "You have built up over %{count} gold in your treasury.\nAll enemies bow before your wealth and power." ); StringReplace( body, "%{count}", conf.getCurrentMapInfo().getWinningGoldAccumulationValue() ); break; default: break; } } if ( !body.empty() ) { AudioManager::PlayMusic( MUS::VICTORY, Music::PlaybackMode::PLAY_ONCE ); fheroes2::showStandardTextMessage( _( "Victory!" ), body, Dialog::OK ); } } void DialogLoss( uint32_t cond ) { const Settings & conf = Settings::Get(); std::string body; switch ( cond ) { case GameOver::LOSS_ENEMY_WINS_TOWN: { body = _( "The enemy has captured %{name}!\nThey are triumphant." ); const Castle * town = world.getCastleEntrance( conf.getCurrentMapInfo().WinsMapsPositionObject() ); assert( town != nullptr ); if ( town ) { StringReplace( body, "%{name}", town->GetName() ); } break; } case GameOver::LOSS_ENEMY_WINS_GOLD: body = _( "The enemy has built up over %{count} gold in his treasury.\nYou must bow done in defeat before his wealth and power." ); StringReplace( body, "%{count}", conf.getCurrentMapInfo().getWinningGoldAccumulationValue() ); break; case GameOver::LOSS_ALL: body = _( "You have been eliminated from the game!!!" ); break; case GameOver::LOSS_TOWN: { body = _( "The enemy has captured %{name}!\nThey are triumphant." ); const Castle * town = world.getCastleEntrance( conf.getCurrentMapInfo().LossMapsPositionObject() ); assert( town != nullptr ); if ( town ) { StringReplace( body, "%{name}", town->GetName() ); } break; } case GameOver::LOSS_HERO: { body = _( "You have lost the hero %{name}.\nYour quest is over." ); const Heroes * hero = world.GetHeroesCondLoss(); assert( hero != nullptr ); if ( hero ) { StringReplace( body, "%{name}", hero->GetName() ); } break; } case GameOver::LOSS_TIME: body = _( "You have failed to complete your quest in time.\nAll is lost." ); break; default: break; } if ( !body.empty() ) { AudioManager::PlayMusic( MUS::LOSTGAME, Music::PlaybackMode::PLAY_ONCE ); fheroes2::showStandardTextMessage( _( "Defeat!" ), body, Dialog::OK ); } } Video::Subtitle standardGameResults() { // Get data for ratings text. const int32_t difficulty = static_cast( Game::GetRating() ); const int32_t baseScore = static_cast( Game::getGameOverScoreFactor() ); const int32_t score = difficulty * baseScore / 100; // Make ratings text as a subtitle for WIN.SMK. fheroes2::MultiFontText ratingText; std::string textBody = _( "Congratulations!\n\nDays: %{days}\n" ); StringReplace( textBody, "%{days}", static_cast( world.CountDay() ) ); ratingText.add( { textBody, fheroes2::FontType::normalWhite() } ); textBody = _( "Base score: %{score}\nDifficulty: %{difficulty}\n\n" ); StringReplace( textBody, "%{score}", baseScore ); StringReplace( textBody, "%{difficulty}", difficulty ); ratingText.add( { textBody, fheroes2::FontType::smallWhite() } ); textBody = _( "Score: %{score}\n\nRating:\n%{rating}" ); StringReplace( textBody, "%{score}", score ); StringReplace( textBody, "%{rating}", fheroes2::HighScoreDataContainer::getMonsterByRating( score ).GetName() ); ratingText.add( { textBody, fheroes2::FontType::normalWhite() } ); // Show results from the 5th second until end (forever) and set maximum width to 140 to fit the black area. // Set subtitles top-center position (475,110) to render results over the black rectangle of burned picture in WIN.SMK video. return { ratingText, 5000, UINT32_MAX, { 475, 110 }, 140 }; } } const char * GameOver::GetString( uint32_t cond ) { switch ( cond ) { case WINS_ALL: return _( "Defeat all enemy heroes and capture all enemy towns and castles." ); case WINS_TOWN: return _( "Capture a specific town." ); case WINS_HERO: return _( "Defeat a specific hero." ); case WINS_ARTIFACT: return _( "Find a specific artifact." ); case WINS_SIDE: return _( "Your side defeats the opposing side." ); case WINS_GOLD: return _( "Accumulate a large amount of gold." ); case LOSS_ALL: return _( "Lose all your heroes and towns." ); case LOSS_TOWN: return _( "Lose a specific town." ); case LOSS_HERO: return _( "Lose a specific hero." ); case LOSS_TIME: return _( "Run out of time. (Fail to win by a certain point.)" ); default: break; } return "None"; } std::string GameOver::GetActualDescription( uint32_t cond ) { const Settings & conf = Settings::Get(); std::string msg; if ( WINS_ALL == cond ) { msg = GetString( WINS_ALL ); } else if ( cond == WINS_SIDE ) { const Player * currentPlayer = Settings::Get().GetPlayers().GetCurrent(); assert( currentPlayer != nullptr ); const int currentColor = currentPlayer->GetColor(); const int friendColors = currentPlayer->GetFriends(); const auto makeListOfPlayers = []( const int colors ) { std::pair result{ {}, 0 }; for ( const int col : Colors( colors ) ) { const Player * player = Players::Get( col ); assert( player != nullptr ); ++result.second; if ( result.second > 1 ) { result.first += ", "; } result.first += player->GetName(); } return result; }; const auto [alliesList, alliesCount] = makeListOfPlayers( friendColors & ~currentColor ); const auto [enemiesList, enemiesCount] = makeListOfPlayers( Game::GetKingdomColors() & ~friendColors ); assert( enemiesCount > 0 ); if ( alliesCount == 0 ) { msg = _n( "You must defeat the enemy %{enemies}.", "You must defeat the enemy alliance of %{enemies}.", enemiesCount ); StringReplace( msg, "%{enemies}", enemiesList ); } else { msg = _n( "The alliance consisting of %{allies} and you must defeat the enemy %{enemies}.", "The alliance consisting of %{allies} and you must defeat the enemy alliance of %{enemies}.", enemiesCount ); StringReplace( msg, "%{allies}", alliesList ); StringReplace( msg, "%{enemies}", enemiesList ); } } else if ( WINS_TOWN & cond ) { const Castle * town = world.getCastleEntrance( conf.getCurrentMapInfo().WinsMapsPositionObject() ); assert( town != nullptr ); if ( town ) { msg = town->isCastle() ? _( "Capture the castle '%{name}'." ) : _( "Capture the town '%{name}'." ); StringReplace( msg, "%{name}", town->GetName() ); } } else if ( WINS_HERO & cond ) { const Heroes * hero = world.GetHeroesCondWins(); assert( hero != nullptr ); if ( hero ) { msg = _( "Defeat the hero '%{name}'." ); StringReplace( msg, "%{name}", hero->GetName() ); } } else if ( WINS_ARTIFACT & cond ) { if ( conf.getCurrentMapInfo().WinsFindUltimateArtifact() ) { msg = _( "Find the ultimate artifact." ); } else { const Artifact art = conf.getCurrentMapInfo().WinsFindArtifactID(); msg = _( "Find the '%{name}' artifact." ); StringReplace( msg, "%{name}", art.GetName() ); } } else if ( WINS_GOLD & cond ) { msg = _( "Accumulate %{count} gold." ); StringReplace( msg, "%{count}", conf.getCurrentMapInfo().getWinningGoldAccumulationValue() ); } if ( WINS_ALL != cond && ( WINS_ALL & cond ) ) { msg.append( _( ", or you may win by defeating all enemy heroes and capturing all enemy towns and castles." ) ); } if ( LOSS_ALL == cond ) { msg = GetString( LOSS_ALL ); } else if ( LOSS_TOWN & cond ) { const Castle * town = world.getCastleEntrance( conf.getCurrentMapInfo().LossMapsPositionObject() ); assert( town != nullptr ); if ( town ) { msg = town->isCastle() ? _( "Lose the castle '%{name}'." ) : _( "Lose the town '%{name}'." ); StringReplace( msg, "%{name}", town->GetName() ); } } else if ( LOSS_HERO & cond ) { const Heroes * hero = world.GetHeroesCondLoss(); assert( hero != nullptr ); if ( hero ) { msg = _( "Lose the hero: %{name}." ); StringReplace( msg, "%{name}", hero->GetName() ); } } else if ( LOSS_TIME & cond ) { const uint32_t dayCount = conf.getCurrentMapInfo().LossCountDays() - 1; const uint32_t month = dayCount / ( DAYOFWEEK * WEEKOFMONTH ); const uint32_t week = ( dayCount - month * ( DAYOFWEEK * WEEKOFMONTH ) ) / DAYOFWEEK; const uint32_t day = dayCount % DAYOFWEEK; msg = _( "Fail to win by the end of month %{month}, week %{week}, day %{day}." ); StringReplace( msg, "%{day}", day + 1 ); StringReplace( msg, "%{week}", week + 1 ); StringReplace( msg, "%{month}", month + 1 ); } return msg; } GameOver::Result & GameOver::Result::Get() { static Result gresult; return gresult; } GameOver::Result::Result() : colors( 0 ) , result( 0 ) {} void GameOver::Result::Reset() { colors = Game::GetKingdomColors(); result = GameOver::COND_NONE; } fheroes2::GameMode GameOver::Result::checkGameOver() { fheroes2::GameMode res = fheroes2::GameMode::CANCEL; const Settings & conf = Settings::Get(); const int humanColors = Players::HumanColors(); const int currentColor = conf.CurrentColor(); const bool isSinglePlayer = ( Color::Count( humanColors ) == 1 ); // Remembers whether the current player was considered active at the time of calling this function const bool isCurrentPlayerWasActive = ( currentColor & colors ); int activeHumanColors = 0; for ( const int color : Colors( colors ) ) { if ( !world.GetKingdom( color ).isPlay() ) { // This notification should always be displayed for the AI players. For human players, this should only be displayed in a multiplayer game for a // human player who is not currently active - in all other cases, the "you have been eliminated" dialog should be displayed. if ( !( color & humanColors ) || ( !isSinglePlayer && color != currentColor ) ) { Game::DialogPlayers( color, _( "Major Event!" ), _( "%{color} player has been vanquished!" ) ); } colors &= ( ~color ); } else if ( color & humanColors ) { ++activeHumanColors; } } if ( isSinglePlayer ) { assert( activeHumanColors <= 1 ); const Kingdom & kingdom = world.GetKingdom( humanColors ); #if defined( WITH_DEBUG ) const Player * player = Players::Get( humanColors ); assert( player != nullptr ); const bool isAIAutoControlMode = player->isAIAutoControlMode(); #else const bool isAIAutoControlMode = false; #endif if ( kingdom.isControlHuman() || isAIAutoControlMode ) { result = world.CheckKingdomWins( kingdom ); if ( result != GameOver::COND_NONE ) { DialogWins( result ); if ( conf.isCampaignGameType() ) { res = fheroes2::GameMode::COMPLETE_CAMPAIGN_SCENARIO; } else { AudioManager::ResetAudio(); Video::ShowVideo( "WIN.SMK", Video::VideoAction::WAIT_FOR_USER_INPUT, { standardGameResults() }, true ); // AudioManager::PlayMusic is run here in order to start playing before displaying the high score. AudioManager::PlayMusicAsync( MUS::VICTORY, Music::PlaybackMode::REWIND_AND_PLAY_INFINITE ); res = fheroes2::GameMode::HIGHSCORES_STANDARD; } } else { // If the player's kingdom has been vanquished, he loses regardless of other conditions if ( !kingdom.isPlay() ) { result = GameOver::LOSS_ALL; } else { result = world.CheckKingdomLoss( kingdom ); } if ( result != GameOver::COND_NONE ) { // Don't show the loss dialog if player's kingdom has been vanquished due to the expired countdown of days since the loss of the last town. // This case was already handled at the end of the Interface::AdventureMap::HumanTurn(). if ( !( result == GameOver::LOSS_ALL && kingdom.GetCastles().empty() && kingdom.GetLostTownDays() == 0 ) ) { DialogLoss( result ); } AudioManager::ResetAudio(); Video::ShowVideo( "LOSE.SMK", Video::VideoAction::LOOP_VIDEO ); res = fheroes2::GameMode::MAIN_MENU; } } } } else { result = [currentColor, activeHumanColors]() -> uint32_t { // If all human-controlled players have been vanquished, then the game is over if ( activeHumanColors == 0 ) { return GameOver::LOSS_ALL; } // Do not perform other checks if there is no active kingdom at the moment if ( !( currentColor & Color::ALL ) ) { return GameOver::COND_NONE; } const Kingdom & kingdom = world.GetKingdom( currentColor ); #if defined( WITH_DEBUG ) const Player * player = Players::Get( currentColor ); assert( player != nullptr ); const bool isAIAutoControlMode = player->isAIAutoControlMode(); #else const bool isAIAutoControlMode = false; #endif // Check the win/loss conditions for human-controlled players only if ( !kingdom.isControlHuman() && !isAIAutoControlMode ) { return GameOver::COND_NONE; } // If the player's kingdom has been vanquished, this particular player loses regardless of other conditions if ( !kingdom.isPlay() ) { return GameOver::LOSS_ALL; } uint32_t condition = world.CheckKingdomWins( kingdom ); if ( condition != GameOver::COND_NONE ) { return condition; } condition = world.CheckKingdomLoss( kingdom ); // LOSS_TOWN is currently not supported in multiplayer if ( condition == GameOver::LOSS_HERO || condition == GameOver::LOSS_TIME || ( condition & GameOver::LOSS_ENEMY_WINS ) ) { return condition; } return GameOver::COND_NONE; }(); if ( result & GameOver::WINS ) { DialogWins( result ); AudioManager::ResetAudio(); Video::ShowVideo( "WIN.SMK", Video::VideoAction::WAIT_FOR_USER_INPUT, { standardGameResults() }, true ); res = fheroes2::GameMode::HIGHSCORES_STANDARD; } else if ( result & GameOver::LOSS ) { const bool showLossDialog = [currentColor, isCurrentPlayerWasActive, this]() { // We shouldn't show the loss notification dialog if there is no active kingdom at the moment if ( !( currentColor & Color::ALL ) ) { return false; } const Kingdom & kingdom = world.GetKingdom( currentColor ); #if defined( WITH_DEBUG ) const Player * player = Players::Get( currentColor ); assert( player != nullptr ); const bool isAIAutoControlMode = player->isAIAutoControlMode(); #else const bool isAIAutoControlMode = false; #endif // We shouldn't show the loss notification dialog to AI players if ( !kingdom.isControlHuman() && !isAIAutoControlMode ) { return false; } // Don't show the loss dialog if player's kingdom has been vanquished due to the expired countdown of days since the loss of the last town. // This case was already handled at the end of the Interface::AdventureMap::HumanTurn(). if ( result == GameOver::LOSS_ALL && kingdom.GetCastles().empty() && kingdom.GetLostTownDays() == 0 ) { return false; } // LOSS_ALL fulfillment by itself is not a reason to end the multiplayer game, and in this case, this function can be called multiple times. // Show the loss notification dialog just once. if ( !isCurrentPlayerWasActive ) { return false; } return true; }(); // LOSS_ALL fulfillment by itself is not a reason to end the multiplayer game, unless all human-controlled players are vanquished const bool endGame = ( result != GameOver::LOSS_ALL || activeHumanColors == 0 ); if ( showLossDialog ) { DialogLoss( result ); } if ( endGame ) { AudioManager::ResetAudio(); Video::ShowVideo( "LOSE.SMK", Video::VideoAction::LOOP_VIDEO ); res = fheroes2::GameMode::MAIN_MENU; } } } return res; } StreamBase & GameOver::operator<<( StreamBase & msg, const Result & res ) { return msg << res.colors << res.result; } StreamBase & GameOver::operator>>( StreamBase & msg, Result & res ) { return msg >> res.colors >> res.result; } fheroes2-1.0.12+dfsg/src/fheroes2/game/game_over.h000066400000000000000000000067071456075706000216050ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2GAMEOVER_H #define H2GAMEOVER_H #include #include #include "game_mode.h" class StreamBase; namespace GameOver { enum conditions_t : uint32_t { COND_NONE = 0x00000000, WINS_ALL = 0x00000001, WINS_TOWN = 0x00000002, WINS_HERO = 0x00000004, WINS_ARTIFACT = 0x00000008, WINS_SIDE = 0x00000010, WINS_GOLD = 0x00000020, WINS = WINS_ALL | WINS_TOWN | WINS_HERO | WINS_ARTIFACT | WINS_SIDE | WINS_GOLD, LOSS_ALL = 0x00000100, LOSS_TOWN = 0x00000200, LOSS_HERO = 0x00000400, LOSS_TIME = 0x00000800, // These loss conditions apply if the enemy player won because of the corresponding win condition LOSS_ENEMY_WINS_TOWN = 0x00010000, LOSS_ENEMY_WINS_GOLD = 0x00020000, LOSS = LOSS_ALL | LOSS_TOWN | LOSS_HERO | LOSS_TIME | LOSS_ENEMY_WINS_TOWN | LOSS_ENEMY_WINS_GOLD, LOSS_ENEMY_WINS = LOSS_ENEMY_WINS_TOWN | LOSS_ENEMY_WINS_GOLD }; const char * GetString( uint32_t cond ); std::string GetActualDescription( uint32_t cond ); class Result { public: static Result & Get(); void Reset(); // Resets everything // Reset game result to COND_NONE. void ResetResult() { result = GameOver::COND_NONE; } uint32_t GetResult() const { return result; } fheroes2::GameMode checkGameOver(); private: friend StreamBase & operator<<( StreamBase &, const Result & ); friend StreamBase & operator>>( StreamBase &, Result & ); Result(); int colors; uint32_t result; }; StreamBase & operator<<( StreamBase &, const Result & ); StreamBase & operator>>( StreamBase &, Result & ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/game/game_scenarioinfo.cpp000066400000000000000000000510571456075706000236420ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include "agg_image.h" #include "audio.h" #include "audio_manager.h" #include "cursor.h" #include "dialog.h" #include "dialog_selectscenario.h" #include "difficulty.h" #include "game.h" #include "game_hotkeys.h" #include "game_interface.h" #include "game_mainmenu_ui.h" #include "game_mode.h" #include "gamedefs.h" #include "icn.h" #include "image.h" #include "localevent.h" #include "logging.h" #include "maps_fileinfo.h" #include "math_base.h" #include "mus.h" #include "player_info.h" #include "players.h" #include "screen.h" #include "settings.h" #include "system.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_text.h" #include "ui_tool.h" #include "ui_window.h" #include "world.h" namespace { void updatePlayers( Players & players, const int humanPlayerCount ) { if ( humanPlayerCount < 2 ) return; int foundHumans = 0; for ( size_t i = 0; i < players.size(); ++i ) { if ( players[i]->isControlHuman() ) { ++foundHumans; if ( players[i]->isControlAI() ) players[i]->SetControl( CONTROL_HUMAN ); } if ( foundHumans == humanPlayerCount ) break; } } void DrawScenarioStaticInfo( const fheroes2::Rect & rt ) { fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::FontType normalWhiteFont = fheroes2::FontType::normalWhite(); // text scenario fheroes2::Text text( _( "Scenario:" ), normalWhiteFont ); text.draw( rt.x, rt.y + 9, rt.width, display ); // text game difficulty text.set( _( "Game Difficulty:" ), normalWhiteFont ); text.draw( rt.x, rt.y + 59, rt.width, display ); // text opponents text.set( _( "Opponents:" ), normalWhiteFont ); text.draw( rt.x, rt.y + 164, rt.width, display ); // text class text.set( _( "Class:" ), normalWhiteFont ); text.draw( rt.x, rt.y + 248, rt.width, display ); } void RedrawMapTitle( const fheroes2::Rect & roi ) { const fheroes2::Text text( Settings::Get().getCurrentMapInfo().name, fheroes2::FontType::normalWhite() ); text.draw( roi.x, roi.y + 8, roi.width, fheroes2::Display::instance() ); } void RedrawDifficultyInfo( const fheroes2::Point & dst ) { const int32_t width = 77; const int32_t height = 69; for ( int32_t current = Difficulty::EASY; current <= Difficulty::IMPOSSIBLE; ++current ) { const int32_t offset = width * current; int32_t normalSpecificOffset = 0; // Add offset shift because the original difficulty icons have irregular spacing. if ( current == Difficulty::NORMAL ) { normalSpecificOffset = 1; } fheroes2::Text text( Difficulty::String( current ), fheroes2::FontType::smallWhite() ); text.draw( dst.x + 31 + offset + normalSpecificOffset - ( text.width() / 2 ), dst.y + height, fheroes2::Display::instance() ); } } fheroes2::Rect RedrawRatingInfo( const fheroes2::Point & offset, int32_t width_ ) { std::string str( _( "Rating %{rating}%" ) ); StringReplace( str, "%{rating}", Game::GetRating() ); const fheroes2::Text text( str, fheroes2::FontType::normalWhite() ); const int32_t y = offset.y + 372; text.draw( offset.x, y, width_, fheroes2::Display::instance() ); const int32_t textX = ( width_ > text.width() ) ? offset.x + ( width_ - text.width() ) / 2 : 0; return { textX, y, text.width(), text.height() }; } fheroes2::GameMode ChooseNewMap( const MapsFileInfoList & lists ) { // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); fheroes2::Display & display = fheroes2::Display::instance(); Settings & conf = Settings::Get(); const bool isEvilInterface = conf.isEvilInterfaceEnabled(); fheroes2::drawMainMenuScreen(); fheroes2::StandardWindow background( 388, 397, true, display ); const fheroes2::Rect roi( background.activeArea() ); const fheroes2::Point pointDifficultyInfo( roi.x + 8, roi.y + 79 ); const fheroes2::Point pointOpponentInfo( roi.x + 8, roi.y + 181 ); const fheroes2::Point pointClassInfo( roi.x + 8, roi.y + 265 ); const fheroes2::Sprite & scenarioBox = fheroes2::AGG::GetICN( isEvilInterface ? ICN::METALLIC_BORDERED_TEXTBOX_EVIL : ICN::METALLIC_BORDERED_TEXTBOX_GOOD, 0 ); const fheroes2::Rect scenarioBoxRoi( roi.x + ( roi.width - scenarioBox.width() ) / 2, roi.y + 24, scenarioBox.width(), scenarioBox.height() ); fheroes2::Copy( scenarioBox, 0, 0, display, scenarioBoxRoi ); fheroes2::addGradientShadow( scenarioBox, display, scenarioBoxRoi.getPosition(), { -5, 5 } ); const fheroes2::Sprite & difficultyCursor = fheroes2::AGG::GetICN( ICN::NGEXTRA, 62 ); const int32_t difficultyCursorWidth = difficultyCursor.width(); const int32_t difficultyCursorHeight = difficultyCursor.height(); // vector coord difficulty std::vector coordDifficulty; coordDifficulty.reserve( 5 ); coordDifficulty.emplace_back( roi.x + 8, roi.y + 78, difficultyCursorWidth, difficultyCursorHeight ); coordDifficulty.emplace_back( roi.x + 85, roi.y + 78, difficultyCursorWidth, difficultyCursorHeight ); coordDifficulty.emplace_back( roi.x + 161, roi.y + 78, difficultyCursorWidth, difficultyCursorHeight ); coordDifficulty.emplace_back( roi.x + 238, roi.y + 78, difficultyCursorWidth, difficultyCursorHeight ); coordDifficulty.emplace_back( roi.x + 315, roi.y + 78, difficultyCursorWidth, difficultyCursorHeight ); const int32_t buttonSelectWidth = fheroes2::AGG::GetICN( ICN::BUTTON_MAP_SELECT_GOOD, 0 ).width(); fheroes2::Button buttonSelectMaps( scenarioBoxRoi.x + scenarioBoxRoi.width - 6 - buttonSelectWidth, scenarioBoxRoi.y + 5, isEvilInterface ? ICN::BUTTON_MAP_SELECT_EVIL : ICN::BUTTON_MAP_SELECT_GOOD, 0, 1 ); buttonSelectMaps.draw(); fheroes2::Button buttonOk; fheroes2::Button buttonCancel; const fheroes2::Point buttonOffset( 20, 6 ); const int buttonOkIcn = isEvilInterface ? ICN::BUTTON_SMALL_OKAY_EVIL : ICN::BUTTON_SMALL_OKAY_GOOD; background.renderButton( buttonOk, buttonOkIcn, 0, 1, buttonOffset, fheroes2::StandardWindow::Padding::BOTTOM_LEFT ); const int buttonCancelIcn = isEvilInterface ? ICN::BUTTON_SMALL_CANCEL_EVIL : ICN::BUTTON_SMALL_CANCEL_GOOD; background.renderButton( buttonCancel, buttonCancelIcn, 0, 1, buttonOffset, fheroes2::StandardWindow::Padding::BOTTOM_RIGHT ); bool resetStartingSettings = conf.getCurrentMapInfo().filename.empty(); Players & players = conf.GetPlayers(); Interface::PlayersInfo playersInfo; const int humanPlayerCount = Settings::Get().PreferablyCountPlayers(); if ( !resetStartingSettings ) { // verify that current map really exists in map's list resetStartingSettings = true; const std::string & mapName = conf.getCurrentMapInfo().name; const std::string & mapFileName = System::GetBasename( conf.getCurrentMapInfo().filename ); for ( const Maps::FileInfo & mapInfo : lists ) { if ( ( mapInfo.name == mapName ) && ( System::GetBasename( mapInfo.filename ) == mapFileName ) ) { if ( mapInfo.filename == conf.getCurrentMapInfo().filename ) { conf.SetCurrentFileInfo( mapInfo ); updatePlayers( players, humanPlayerCount ); Game::LoadPlayers( mapInfo.filename, players ); resetStartingSettings = false; break; } } } } // set first map's settings if ( resetStartingSettings ) { conf.SetCurrentFileInfo( lists.front() ); updatePlayers( players, humanPlayerCount ); Game::LoadPlayers( lists.front().filename, players ); } playersInfo.UpdateInfo( players, pointOpponentInfo, pointClassInfo ); DrawScenarioStaticInfo( roi ); RedrawDifficultyInfo( pointDifficultyInfo ); const int icnIndex = isEvilInterface ? 1 : 0; // Draw difficulty icons. for ( int i = 0; i < 5; ++i ) { const fheroes2::Sprite & icon = fheroes2::AGG::GetICN( ICN::DIFFICULTY_ICON_EASY + i, icnIndex ); fheroes2::Copy( icon, 0, 0, display, coordDifficulty[i] ); fheroes2::addGradientShadow( icon, display, { coordDifficulty[i].x, coordDifficulty[i].y }, { -5, 5 } ); } // Set up restorers. fheroes2::ImageRestorer mapTitleArea( display, scenarioBoxRoi.x + 113, scenarioBoxRoi.y + 5, 141, scenarioBoxRoi.height ); fheroes2::ImageRestorer opponentsArea( display, roi.x, pointOpponentInfo.y, roi.width, 65 ); fheroes2::ImageRestorer classArea( display, roi.x, pointClassInfo.y, roi.width, 69 ); fheroes2::ImageRestorer handicapArea( display, roi.x, pointClassInfo.y + 69, roi.width, 31 ); fheroes2::ImageRestorer ratingArea( display, buttonOk.area().x + buttonOk.area().width, buttonOk.area().y, roi.width - buttonOk.area().width - buttonCancel.area().width - 20 * 2, buttonOk.area().height ); // Map name RedrawMapTitle( scenarioBoxRoi ); playersInfo.RedrawInfo( false ); fheroes2::Rect ratingRoi = RedrawRatingInfo( roi.getPosition(), roi.width ); fheroes2::MovableSprite levelCursor( difficultyCursor ); const int32_t levelCursorOffset = 3; switch ( Game::getDifficulty() ) { case Difficulty::EASY: levelCursor.setPosition( coordDifficulty[0].x - levelCursorOffset, coordDifficulty[0].y - levelCursorOffset ); break; case Difficulty::NORMAL: levelCursor.setPosition( coordDifficulty[1].x - levelCursorOffset, coordDifficulty[1].y - levelCursorOffset ); break; case Difficulty::HARD: levelCursor.setPosition( coordDifficulty[2].x - levelCursorOffset, coordDifficulty[2].y - levelCursorOffset ); break; case Difficulty::EXPERT: levelCursor.setPosition( coordDifficulty[3].x - levelCursorOffset, coordDifficulty[3].y - levelCursorOffset ); break; case Difficulty::IMPOSSIBLE: levelCursor.setPosition( coordDifficulty[4].x - levelCursorOffset, coordDifficulty[4].y - levelCursorOffset ); break; default: // Did you add a new difficulty mode? Add the corresponding case above! assert( 0 ); break; } levelCursor.redraw(); display.render(); fheroes2::GameMode result = fheroes2::GameMode::QUIT_GAME; LocalEvent & le = LocalEvent::Get(); while ( true ) { if ( !le.HandleEvents( true, true ) ) { if ( Interface::AdventureMap::EventExit() == fheroes2::GameMode::QUIT_GAME ) { fheroes2::fadeOutDisplay(); return fheroes2::GameMode::QUIT_GAME; } continue; } // press button le.MousePressLeft( buttonSelectMaps.area() ) ? buttonSelectMaps.drawOnPress() : buttonSelectMaps.drawOnRelease(); le.MousePressLeft( buttonOk.area() ) ? buttonOk.drawOnPress() : buttonOk.drawOnRelease(); le.MousePressLeft( buttonCancel.area() ) ? buttonCancel.drawOnPress() : buttonCancel.drawOnRelease(); // click select if ( HotKeyPressEvent( Game::HotKeyEvent::MAIN_MENU_SELECT_MAP ) || le.MouseClickLeft( buttonSelectMaps.area() ) ) { const Maps::FileInfo * fi = Dialog::SelectScenario( lists ); const std::string currentMapName = conf.getCurrentMapInfo().filename; if ( fi && fi->filename != currentMapName ) { Game::SavePlayers( currentMapName, conf.GetPlayers() ); conf.SetCurrentFileInfo( *fi ); mapTitleArea.restore(); RedrawMapTitle( scenarioBoxRoi ); Game::LoadPlayers( fi->filename, players ); opponentsArea.restore(); classArea.restore(); handicapArea.restore(); ratingArea.restore(); updatePlayers( players, humanPlayerCount ); playersInfo.UpdateInfo( players, pointOpponentInfo, pointClassInfo ); playersInfo.resetSelection(); playersInfo.RedrawInfo( false ); ratingRoi = RedrawRatingInfo( roi.getPosition(), roi.width ); levelCursor.setPosition( coordDifficulty[Game::getDifficulty()].x - levelCursorOffset, coordDifficulty[Game::getDifficulty()].y - levelCursorOffset ); // From 0 to 4, see: Difficulty enum } display.render(); } else if ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) || le.MouseClickLeft( buttonCancel.area() ) ) { result = fheroes2::GameMode::MAIN_MENU; break; } else if ( Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) || le.MouseClickLeft( buttonOk.area() ) ) { DEBUG_LOG( DBG_GAME, DBG_INFO, "select maps: " << conf.getCurrentMapInfo().filename << ", difficulty: " << Difficulty::String( Game::getDifficulty() ) ) result = fheroes2::GameMode::START_GAME; // Fade-out screen before starting a scenario. fheroes2::fadeOutDisplay(); break; } else if ( le.MouseClickLeft( roi ) ) { const int32_t index = GetRectIndex( coordDifficulty, le.GetMouseCursor() ); // select difficulty if ( 0 <= index ) { levelCursor.setPosition( coordDifficulty[index].x - levelCursorOffset, coordDifficulty[index].y - levelCursorOffset ); levelCursor.redraw(); Game::saveDifficulty( index ); ratingArea.restore(); ratingRoi = RedrawRatingInfo( roi.getPosition(), roi.width ); display.render( roi ); } // playersInfo else if ( playersInfo.QueueEventProcessing() ) { opponentsArea.restore(); classArea.restore(); handicapArea.restore(); playersInfo.RedrawInfo( false ); display.render( roi ); } } else if ( ( le.MouseWheelUp() || le.MouseWheelDn() ) && playersInfo.QueueEventProcessing() ) { playersInfo.resetSelection(); opponentsArea.restore(); classArea.restore(); handicapArea.restore(); playersInfo.RedrawInfo( false ); display.render( roi ); } if ( le.MousePressRight( roi ) ) { if ( le.MousePressRight( buttonSelectMaps.area() ) ) { fheroes2::showStandardTextMessage( _( "Scenario" ), _( "Click here to select which scenario to play." ), Dialog::ZERO ); } else if ( 0 <= GetRectIndex( coordDifficulty, le.GetMouseCursor() ) ) { fheroes2::showStandardTextMessage( _( "Game Difficulty" ), _( "This lets you change the starting difficulty at which you will play. Higher difficulty levels start you of with fewer resources, and at the higher settings, give extra resources to the computer." ), Dialog::ZERO ); } else if ( le.MousePressRight( ratingRoi ) ) { fheroes2::showStandardTextMessage( _( "Difficulty Rating" ), _( "The difficulty rating reflects a combination of various settings for your game. This number will be applied to your final score." ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonOk.area() ) ) { fheroes2::showStandardTextMessage( _( "Okay" ), _( "Click to accept these settings and start a new game." ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonCancel.area() ) ) { fheroes2::showStandardTextMessage( _( "Cancel" ), _( "Click to return to the main menu." ), Dialog::ZERO ); } else { playersInfo.QueueEventProcessing(); } } } Game::SavePlayers( conf.getCurrentMapInfo().filename, conf.GetPlayers() ); return result; } fheroes2::GameMode LoadNewMap() { Settings & conf = Settings::Get(); conf.GetPlayers().SetStartGame(); const Maps::FileInfo & mapInfo = conf.getCurrentMapInfo(); if ( mapInfo.version == GameVersion::SUCCESSION_WARS || mapInfo.version == GameVersion::PRICE_OF_LOYALTY ) { if ( world.LoadMapMP2( mapInfo.filename, ( mapInfo.version == GameVersion::SUCCESSION_WARS ) ) ) { return fheroes2::GameMode::START_GAME; } return fheroes2::GameMode::MAIN_MENU; } assert( mapInfo.version == GameVersion::RESURRECTION ); if ( world.loadResurrectionMap( mapInfo.filename ) ) { return fheroes2::GameMode::START_GAME; } return fheroes2::GameMode::MAIN_MENU; } } fheroes2::GameMode Game::SelectScenario() { return fheroes2::GameMode::SCENARIO_INFO; } fheroes2::GameMode Game::ScenarioInfo() { AudioManager::PlayMusicAsync( MUS::MAINMENU, Music::PlaybackMode::RESUME_AND_PLAY_INFINITE ); const MapsFileInfoList maps = Maps::getAllMapFileInfos( false, Settings::Get().IsGameType( Game::TYPE_MULTI ) ); if ( maps.empty() ) { fheroes2::showStandardTextMessage( _( "Warning" ), _( "No maps available!" ), Dialog::OK ); return fheroes2::GameMode::MAIN_MENU; } // We must release UI resources for this window before loading a new map. That's why all UI logic is in a separate function. const fheroes2::GameMode result = ChooseNewMap( maps ); if ( result != fheroes2::GameMode::START_GAME ) { return result; } return LoadNewMap(); } int32_t Game::GetStep4Player( const int32_t currentId, const int32_t width, const int32_t totalCount ) { return currentId * width * KINGDOMMAX / totalCount + ( width * ( KINGDOMMAX - totalCount ) / ( 2 * totalCount ) ); } fheroes2-1.0.12+dfsg/src/fheroes2/game/game_startgame.cpp000066400000000000000000001627641456075706000231620ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "game.h" #include #include #include #include #include #include #include #include #include #include "agg_image.h" #include "ai.h" #include "army.h" #include "audio.h" #include "audio_manager.h" #include "battle_only.h" #include "castle.h" #include "color.h" #include "cursor.h" #include "dialog.h" #include "direction.h" #include "game_delays.h" #include "game_hotkeys.h" #include "game_interface.h" #include "game_io.h" #include "game_mode.h" #include "game_over.h" #include "heroes.h" #include "icn.h" #include "image.h" #include "interface_base.h" #include "interface_buttons.h" #include "interface_cpanel.h" #include "interface_gamearea.h" #include "interface_icons.h" #include "interface_radar.h" #include "interface_status.h" #include "kingdom.h" #include "localevent.h" #include "logging.h" #include "m82.h" #include "maps.h" #include "maps_tiles.h" #include "maps_tiles_helper.h" #include "math_base.h" #include "monster.h" #include "mp2.h" #include "mus.h" #include "players.h" #include "resource.h" #include "screen.h" #include "settings.h" #include "tools.h" #include "translations.h" #include "ui_dialog.h" #include "ui_text.h" #include "ui_tool.h" #include "week.h" #include "world.h" namespace { bool SortPlayers( const Player * player1, const Player * player2 ) { return ( player1->isControlHuman() && !player2->isControlHuman() ) || ( ( player1->isControlHuman() == player2->isControlHuman() ) && ( player1->GetColor() < player2->GetColor() ) ); } // Get colors value of players to use in fog directions update. // For human allied AI returns colors of this alliance, for hostile AI - colors of all human players and their allies. int32_t hotSeatAIFogColors( const Player * player ) { assert( player != nullptr ); // This function should be called when AI makes a move. assert( world.GetKingdom( player->GetColor() ).GetControl() == CONTROL_AI ); const int32_t humanColors = Players::HumanColors(); // Check if the current AI player is a friend of any of human players to fully show his move and revealed map, // otherwise his revealed map will not be shown - instead of it we will show the revealed map by all human players. const bool isFriendlyAI = Players::isFriends( player->GetColor(), humanColors ); #if defined( WITH_DEBUG ) if ( isFriendlyAI || player->isAIAutoControlMode() ) { #else if ( isFriendlyAI ) { #endif // Fully update fog directions for allied AI players in Hot Seat mode as the previous move could be done by opposing player. return player->GetFriends(); } // If AI is hostile for all human players then fully update fog directions for all human players to see enemy AI hero move on tiles with // discovered fog. int32_t friendColors = 0; for ( const int32_t color : Colors( humanColors ) ) { const Player * humanPlayer = Players::Get( color ); if ( humanPlayer ) { friendColors |= humanPlayer->GetFriends(); } } return friendColors; } } fheroes2::GameMode Game::StartBattleOnly() { static Battle::Only battleOnlySetup; world.generateBattleOnlyMap(); bool reset = false; bool allowBackup = true; while ( battleOnlySetup.setup( allowBackup, reset ) ) { allowBackup = false; if ( reset ) { world.generateBattleOnlyMap(); battleOnlySetup.reset(); reset = false; continue; } battleOnlySetup.StartBattle(); break; } return fheroes2::GameMode::MAIN_MENU; } fheroes2::GameMode Game::StartGame() { AI::Get().Reset(); const Settings & conf = Settings::Get(); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); if ( !conf.LoadedGameVersion() ) GameOver::Result::Get().Reset(); return Interface::AdventureMap::Get().StartGame(); } void Game::DialogPlayers( int color, std::string title, std::string message ) { const Player * player = Players::Get( color ); StringReplace( message, "%{color}", ( player ? player->GetName() : Color::String( color ) ) ); const fheroes2::Sprite & border = fheroes2::AGG::GetICN( ICN::BRCREST, 6 ); fheroes2::Sprite sign = border; switch ( color ) { case Color::BLUE: fheroes2::Blit( fheroes2::AGG::GetICN( ICN::BRCREST, 0 ), sign, 4, 4 ); break; case Color::GREEN: fheroes2::Blit( fheroes2::AGG::GetICN( ICN::BRCREST, 1 ), sign, 4, 4 ); break; case Color::RED: fheroes2::Blit( fheroes2::AGG::GetICN( ICN::BRCREST, 2 ), sign, 4, 4 ); break; case Color::YELLOW: fheroes2::Blit( fheroes2::AGG::GetICN( ICN::BRCREST, 3 ), sign, 4, 4 ); break; case Color::ORANGE: fheroes2::Blit( fheroes2::AGG::GetICN( ICN::BRCREST, 4 ), sign, 4, 4 ); break; case Color::PURPLE: fheroes2::Blit( fheroes2::AGG::GetICN( ICN::BRCREST, 5 ), sign, 4, 4 ); break; default: // Did you add a new color? Add the logic for it! assert( 0 ); break; } const fheroes2::CustomImageDialogElement imageUI( std::move( sign ) ); fheroes2::showMessage( fheroes2::Text( std::move( title ), fheroes2::FontType::normalYellow() ), fheroes2::Text( std::move( message ), fheroes2::FontType::normalWhite() ), Dialog::OK, { &imageUI } ); } void Game::OpenCastleDialog( Castle & castle, bool updateFocus /* = true */, const bool renderBackgroundDialog /* = true */ ) { // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); // Stop all sounds, but not the music - it will be replaced by the music of the castle AudioManager::stopSounds(); const Settings & conf = Settings::Get(); Kingdom & myKingdom = world.GetKingdom( conf.CurrentColor() ); const VecCastles & myCastles = myKingdom.GetCastles(); VecCastles::const_iterator it = std::find( myCastles.begin(), myCastles.end(), &castle ); const size_t heroCountBefore = myKingdom.GetHeroes().size(); assert( it != myCastles.end() ); Castle::CastleDialogReturnValue result = ( *it )->OpenDialog( false, true, renderBackgroundDialog ); while ( result != Castle::CastleDialogReturnValue::Close ) { if ( result == Castle::CastleDialogReturnValue::PreviousCastle || result == Castle::CastleDialogReturnValue::PreviousCostructionWindow ) { if ( it == myCastles.begin() ) { it = myCastles.end(); } --it; } else if ( result == Castle::CastleDialogReturnValue::NextCastle || result == Castle::CastleDialogReturnValue::NextCostructionWindow ) { ++it; if ( it == myCastles.end() ) { it = myCastles.begin(); } } assert( it != myCastles.end() ); const bool openConstructionWindow = ( result == Castle::CastleDialogReturnValue::PreviousCostructionWindow ) || ( result == Castle::CastleDialogReturnValue::NextCostructionWindow ); result = ( *it )->OpenDialog( openConstructionWindow, false, renderBackgroundDialog ); } // If Castle dialog background was not rendered than we have opened it from other dialog (Kingdom Overview) // and there is no need update Adventure map interface at this time. if ( renderBackgroundDialog ) { Interface::AdventureMap & adventureMapInterface = Interface::AdventureMap::Get(); if ( updateFocus ) { if ( heroCountBefore < myKingdom.GetHeroes().size() ) { adventureMapInterface.SetFocus( myKingdom.GetHeroes()[heroCountBefore], false ); } else if ( it != myCastles.end() ) { Heroes * heroInCastle = world.GetTiles( ( *it )->GetIndex() ).getHero(); if ( heroInCastle == nullptr ) { adventureMapInterface.SetFocus( *it ); } else { adventureMapInterface.SetFocus( heroInCastle, false ); } } else { adventureMapInterface.ResetFocus( GameFocus::HEROES, false ); } } else { // If we don't update focus, we still have to restore environment sounds and terrain music theme restoreSoundsForCurrentFocus(); } // The castle garrison can change adventureMapInterface.RedrawFocus(); adventureMapInterface.ResetFocus( Interface::GetFocusType(), false ); // Fade-in game screen only for 640x480 resolution. if ( fheroes2::Display::instance().isDefaultSize() ) { setDisplayFadeIn(); } } else { // If we opened the castle dialog from other dialog, we have to restore environment sounds and terrain music theme instead of the castle's music theme restoreSoundsForCurrentFocus(); } } void Game::OpenHeroesDialog( Heroes & hero, bool updateFocus, const bool renderBackgroundDialog, const bool disableDismiss /* = false */ ) { // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); Interface::AdventureMap & adventureMapInterface = Interface::AdventureMap::Get(); const VecHeroes & myHeroes = hero.GetKingdom().GetHeroes(); VecHeroes::const_iterator it = std::find( myHeroes.begin(), myHeroes.end(), &hero ); const bool isDefaultScreenSize = fheroes2::Display::instance().isDefaultSize(); bool needFade = true; int result = Dialog::ZERO; while ( it != myHeroes.end() && result != Dialog::CANCEL ) { result = ( *it )->OpenDialog( false, needFade, disableDismiss, false, renderBackgroundDialog ); if ( needFade ) { needFade = false; } switch ( result ) { case Dialog::PREV: if ( it == myHeroes.begin() ) { it = myHeroes.end(); } --it; break; case Dialog::NEXT: ++it; if ( it == myHeroes.end() ) { it = myHeroes.begin(); } break; case Dialog::DISMISS: AudioManager::PlaySound( M82::KILLFADE ); ( *it )->ShowPath( false ); // Check if this dialog is not opened from the other dialog and we will be exiting to the Adventure map. if ( renderBackgroundDialog ) { // Redraw Adventure map with hidden hero path. adventureMapInterface.redraw( Interface::REDRAW_GAMEAREA ); // Fade-in game screen only for 640x480 resolution. if ( isDefaultScreenSize ) { fheroes2::fadeInDisplay(); } ( *it )->FadeOut(); updateFocus = true; } ( *it )->Dismiss( 0 ); it = myHeroes.end(); result = Dialog::CANCEL; break; case Dialog::CANCEL: needFade = true; break; default: break; } } // If Hero dialog background was not rendered than we have opened it from other dialog (Kingdom Overview or Castle dialog) // and there is no need update Adventure map interface at this time. if ( renderBackgroundDialog ) { if ( updateFocus ) { if ( it != myHeroes.end() ) { adventureMapInterface.SetFocus( *it, false ); } else { adventureMapInterface.ResetFocus( GameFocus::HEROES, false ); } } // The hero's army can change adventureMapInterface.RedrawFocus(); // Fade-in game screen only for 640x480 resolution. if ( needFade && renderBackgroundDialog && isDefaultScreenSize ) { setDisplayFadeIn(); } } } void ShowNewWeekDialog() { // restore the original music on exit const AudioManager::MusicRestorer musicRestorer; const bool isNewMonth = world.BeginMonth(); AudioManager::PlayMusic( isNewMonth ? MUS::NEW_MONTH : MUS::NEW_WEEK, Music::PlaybackMode::PLAY_ONCE ); const Week & week = world.GetWeekType(); // head std::string message = isNewMonth ? _( "Astrologers proclaim the Month of the %{name}." ) : _( "Astrologers proclaim the Week of the %{name}." ); StringReplace( message, "%{name}", week.GetName() ); message += "\n\n"; if ( week.GetType() == WeekName::MONSTERS ) { const Monster monster( week.GetMonster() ); const uint32_t count = isNewMonth ? Castle::GetGrownMonthOf() : Castle::GetGrownWeekOf(); if ( monster.isValid() && count ) { if ( isNewMonth ) message += 100 == Castle::GetGrownMonthOf() ? _( "After regular growth, the population of %{monster} is doubled!" ) : _n( "After regular growth, the population of %{monster} increases by %{count} percent!", "After regular growth, the population of %{monster} increases by %{count} percent!", count ); else message += _( "%{monster} growth +%{count}." ); StringReplaceWithLowercase( message, "%{monster}", monster.GetMultiName() ); StringReplace( message, "%{count}", count ); message += "\n\n"; } } if ( week.GetType() == WeekName::PLAGUE ) message += _( " All populations are halved." ); else message += _( " All dwellings increase population." ); fheroes2::showStandardTextMessage( isNewMonth ? _( "New Month!" ) : _( "New Week!" ), message, Dialog::OK ); } void ShowWarningLostTownsDialog() { const Kingdom & myKingdom = world.GetKingdom( Settings::Get().CurrentColor() ); const uint32_t lostTownDays = myKingdom.GetLostTownDays(); if ( lostTownDays == 1 ) { Game::DialogPlayers( myKingdom.GetColor(), _( "Beware!" ), _( "%{color} player, this is your last day to capture a town, or you will be banished from this land." ) ); } else if ( lostTownDays > 0 && lostTownDays <= Game::GetLostTownDays() ) { std::string str = _( "%{color} player, you only have %{day} days left to capture a town, or you will be banished from this land." ); StringReplace( str, "%{day}", lostTownDays ); Game::DialogPlayers( myKingdom.GetColor(), _( "Beware!" ), str ); } } int Interface::AdventureMap::GetCursorFocusCastle( const Castle & castle, const Maps::Tiles & tile ) { switch ( tile.GetObject() ) { case MP2::OBJ_NON_ACTION_CASTLE: case MP2::OBJ_CASTLE: { const Castle * otherCastle = world.getCastle( tile.GetCenter() ); if ( otherCastle ) { return otherCastle->GetColor() == castle.GetColor() ? Cursor::CASTLE : Cursor::POINTER; } break; } case MP2::OBJ_HERO: { const Heroes * hero = tile.getHero(); if ( hero ) { return hero->GetColor() == castle.GetColor() ? Cursor::HEROES : Cursor::POINTER; } break; } default: break; } return Cursor::POINTER; } int Interface::AdventureMap::GetCursorFocusShipmaster( const Heroes & hero, const Maps::Tiles & tile ) { const bool isWater = tile.isWater(); switch ( tile.GetObject() ) { case MP2::OBJ_NON_ACTION_CASTLE: case MP2::OBJ_CASTLE: { const Castle * castle = world.getCastle( tile.GetCenter() ); if ( castle ) { if ( tile.GetObject() == MP2::OBJ_NON_ACTION_CASTLE && isWater && tile.isPassableFrom( Direction::CENTER, true, false, hero.GetColor() ) ) { return Cursor::DistanceThemes( Cursor::CURSOR_HERO_BOAT, hero.getNumOfTravelDays( tile.GetIndex() ) ); } return hero.GetColor() == castle->GetColor() ? Cursor::CASTLE : Cursor::POINTER; } break; } case MP2::OBJ_HERO: { const Heroes * otherHero = tile.getHero(); if ( otherHero ) { if ( !otherHero->isShipMaster() ) { return hero.GetColor() == otherHero->GetColor() ? Cursor::HEROES : Cursor::POINTER; } if ( otherHero->GetCenter() == hero.GetCenter() ) { return Cursor::HEROES; } if ( hero.GetColor() == otherHero->GetColor() ) { return Cursor::DistanceThemes( Cursor::CURSOR_HERO_MEET, hero.getNumOfTravelDays( tile.GetIndex() ) ); } if ( hero.isFriends( otherHero->GetColor() ) ) { return Cursor::POINTER; } return Cursor::DistanceThemes( Cursor::CURSOR_HERO_FIGHT, hero.getNumOfTravelDays( tile.GetIndex() ) ); } break; } // Some map editors allow to place monsters on water tiles case MP2::OBJ_MONSTER: return isWater ? Cursor::DistanceThemes( Cursor::CURSOR_HERO_FIGHT, hero.getNumOfTravelDays( tile.GetIndex() ) ) : Cursor::POINTER; case MP2::OBJ_COAST: return Cursor::DistanceThemes( Cursor::CURSOR_HERO_ANCHOR, hero.getNumOfTravelDays( tile.GetIndex() ) ); default: if ( isWater ) { if ( MP2::isWaterActionObject( tile.GetObject() ) ) { return Cursor::DistanceThemes( Cursor::CURSOR_HERO_BOAT_ACTION, hero.getNumOfTravelDays( tile.GetIndex() ) ); } if ( tile.isPassableFrom( Direction::CENTER, true, false, hero.GetColor() ) ) { return Cursor::DistanceThemes( Cursor::CURSOR_HERO_BOAT, hero.getNumOfTravelDays( tile.GetIndex() ) ); } } break; } return Cursor::POINTER; } int Interface::AdventureMap::_getCursorNoFocus( const Maps::Tiles & tile ) { switch ( tile.GetObject() ) { case MP2::OBJ_NON_ACTION_CASTLE: case MP2::OBJ_CASTLE: { const Castle * castle = world.getCastle( tile.GetCenter() ); if ( castle && castle->GetColor() == Settings::Get().CurrentColor() ) { return Cursor::CASTLE; } break; } case MP2::OBJ_HERO: { const Heroes * hero = tile.getHero(); if ( hero && hero->GetColor() == Settings::Get().CurrentColor() ) { return Cursor::HEROES; } break; } default: break; } return Cursor::POINTER; } int Interface::AdventureMap::GetCursorFocusHeroes( const Heroes & hero, const Maps::Tiles & tile ) { if ( hero.Modes( Heroes::ENABLEMOVE ) ) { return Cursor::Get().Themes(); } if ( hero.isShipMaster() ) { return GetCursorFocusShipmaster( hero, tile ); } switch ( tile.GetObject() ) { case MP2::OBJ_NON_ACTION_CASTLE: case MP2::OBJ_CASTLE: { const Castle * castle = world.getCastle( tile.GetCenter() ); if ( castle ) { if ( tile.GetObject() == MP2::OBJ_NON_ACTION_CASTLE ) { if ( tile.GetPassable() == 0 ) { return ( hero.GetColor() == castle->GetColor() ) ? Cursor::CASTLE : Cursor::POINTER; } return Cursor::DistanceThemes( Maps::isTileUnderProtection( tile.GetIndex() ) ? Cursor::CURSOR_HERO_FIGHT : Cursor::CURSOR_HERO_MOVE, hero.getNumOfTravelDays( tile.GetIndex() ) ); } if ( hero.GetIndex() == castle->GetIndex() ) { return hero.GetColor() == castle->GetColor() ? Cursor::CASTLE : Cursor::POINTER; } if ( hero.GetColor() == castle->GetColor() ) { return Cursor::DistanceThemes( Cursor::CURSOR_HERO_ACTION, hero.getNumOfTravelDays( castle->GetIndex() ) ); } if ( hero.isFriends( castle->GetColor() ) ) { return Cursor::POINTER; } if ( castle->GetActualArmy().isValid() ) { return Cursor::DistanceThemes( Cursor::CURSOR_HERO_FIGHT, hero.getNumOfTravelDays( castle->GetIndex() ) ); } return Cursor::DistanceThemes( Cursor::CURSOR_HERO_ACTION, hero.getNumOfTravelDays( castle->GetIndex() ) ); } break; } case MP2::OBJ_HERO: { const Heroes * otherHero = tile.getHero(); if ( otherHero ) { if ( otherHero->GetCenter() == hero.GetCenter() ) { return Cursor::HEROES; } if ( hero.GetColor() == otherHero->GetColor() ) { const int cursor = Cursor::DistanceThemes( Cursor::CURSOR_HERO_MEET, hero.getNumOfTravelDays( tile.GetIndex() ) ); return cursor != Cursor::POINTER ? cursor : Cursor::HEROES; } if ( hero.isFriends( otherHero->GetColor() ) ) { return Cursor::POINTER; } return Cursor::DistanceThemes( Cursor::CURSOR_HERO_FIGHT, hero.getNumOfTravelDays( tile.GetIndex() ) ); } break; } case MP2::OBJ_BOAT: return Cursor::DistanceThemes( Cursor::CURSOR_HERO_BOAT, hero.getNumOfTravelDays( tile.GetIndex() ) ); default: if ( MP2::isActionObject( tile.GetObject() ) ) { const bool isProtected = ( Maps::isTileUnderProtection( tile.GetIndex() ) || ( !hero.isFriends( getColorFromTile( tile ) ) && isCaptureObjectProtected( tile ) ) ); return Cursor::DistanceThemes( isProtected ? Cursor::CURSOR_HERO_FIGHT : Cursor::CURSOR_HERO_ACTION, hero.getNumOfTravelDays( tile.GetIndex() ) ); } if ( tile.isPassableFrom( Direction::CENTER, hero.isShipMaster(), false, hero.GetColor() ) ) { return Cursor::DistanceThemes( Maps::isTileUnderProtection( tile.GetIndex() ) ? Cursor::CURSOR_HERO_FIGHT : Cursor::CURSOR_HERO_MOVE, hero.getNumOfTravelDays( tile.GetIndex() ) ); } break; } return Cursor::POINTER; } void Interface::AdventureMap::updateCursor( const int32_t tileIndex ) { Cursor::Get().SetThemes( GetCursorTileIndex( tileIndex ) ); } int Interface::AdventureMap::GetCursorTileIndex( int32_t dstIndex ) { if ( !Maps::isValidAbsIndex( dstIndex ) ) { return Cursor::POINTER; } const Maps::Tiles & tile = world.GetTiles( dstIndex ); if ( tile.isFog( Settings::Get().CurrentColor() ) ) { return Cursor::POINTER; } switch ( GetFocusType() ) { case GameFocus::HEROES: return GetCursorFocusHeroes( *GetFocusHeroes(), tile ); case GameFocus::CASTLE: return GetCursorFocusCastle( *GetFocusCastle(), tile ); case GameFocus::UNSEL: return _getCursorNoFocus( tile ); default: break; } return Cursor::POINTER; } fheroes2::GameMode Interface::AdventureMap::StartGame() { Settings & conf = Settings::Get(); reset(); _radar.Build(); _radar.SetHide( true ); iconsPanel.HideIcons( ICON_ANY ); _statusWindow.Reset(); // Prepare for render the whole game interface with adventure map filled with fog as it was not uncovered by 'updateMapFogDirections()'. redraw( REDRAW_GAMEAREA | REDRAW_RADAR | REDRAW_ICONS | REDRAW_BUTTONS | REDRAW_STATUS | REDRAW_BORDER ); bool loadedFromSave = conf.LoadedGameVersion(); bool skipTurns = loadedFromSave; // Set need of fade-in of game screen. Game::setDisplayFadeIn(); GameOver::Result & gameResult = GameOver::Result::Get(); fheroes2::GameMode res = fheroes2::GameMode::END_TURN; std::vector sortedPlayers = conf.GetPlayers().getVector(); std::sort( sortedPlayers.begin(), sortedPlayers.end(), SortPlayers ); if ( !loadedFromSave || world.CountDay() == 1 ) { // Clear fog around heroes, castles and mines for all players when starting a new map or if the save was done at the first day. for ( const Player * player : sortedPlayers ) { world.ClearFog( player->GetColor() ); } } const bool isHotSeatGame = conf.IsGameType( Game::TYPE_HOTSEAT ); if ( !isHotSeatGame ) { // It is not a Hot Seat (multiplayer) game so we set current color to the only human player. conf.SetCurrentColor( Players::HumanColors() ); // Fully update fog directions if there will be only one human player. Interface::GameArea::updateMapFogDirections(); } while ( res == fheroes2::GameMode::END_TURN ) { if ( !loadedFromSave ) { world.NewDay(); } // check if the game is over at the beginning of a new day res = gameResult.checkGameOver(); if ( res != fheroes2::GameMode::CANCEL ) { break; } res = fheroes2::GameMode::END_TURN; // All bonuses for AI must be applied on the first AI player turn, not the first player in general. // This prevents human players from abusing AI bonuses. bool applyAIBonuses = true; for ( const Player * player : sortedPlayers ) { assert( player != nullptr ); if ( skipTurns ) { // Game saves can only be performed during a human player's turn (including when it is under temporary AI control // in the case of a debug build), and human players always go first in the turn queue. If we skipped all the human // players and still haven't found the current player, then something is clearly wrong here. if ( player->GetControl() == CONTROL_AI ) { break; } if ( !player->isColor( conf.CurrentColor() ) ) { continue; } } // player with conf.CurrentColor() was found, there is no need for further skips skipTurns = false; const int playerColor = player->GetColor(); Kingdom & kingdom = world.GetKingdom( playerColor ); if ( kingdom.isPlay() ) { DEBUG_LOG( DBG_GAME, DBG_INFO, world.DateString() << ", color: " << Color::String( playerColor ) << ", resource: " << kingdom.GetFunds().String() ) _radar.SetHide( true ); _radar.SetRedraw( REDRAW_RADAR_CURSOR ); switch ( kingdom.GetControl() ) { case CONTROL_HUMAN: // Reset environment sounds and music theme at the beginning of the human turn AudioManager::ResetAudio(); if ( isHotSeatGame ) { iconsPanel.HideIcons( ICON_ANY ); _statusWindow.Reset(); // Fully update fog directions in Hot Seat mode to cover the map with fog on player change. // TODO: Cover the Adventure map area with fog sprites without rendering the "Game Area" for player change. Maps::updateFogDirectionsInArea( { 0, 0 }, { world.w(), world.h() }, Color::NONE ); redraw( REDRAW_GAMEAREA | REDRAW_ICONS | REDRAW_BUTTONS | REDRAW_STATUS ); validateFadeInAndRender(); // reset the music after closing the dialog const AudioManager::MusicRestorer musicRestorer; AudioManager::PlayMusic( MUS::NEW_MONTH, Music::PlaybackMode::PLAY_ONCE ); Game::DialogPlayers( playerColor, "", _( "%{color} player's turn." ) ); } conf.SetCurrentColor( playerColor ); kingdom.ActionBeforeTurn(); iconsPanel.ShowIcons( ICON_ANY ); iconsPanel.SetRedraw(); res = HumanTurn( loadedFromSave ); // Skip resetting Audio after winning scenario because MUS::VICTORY should continue playing. if ( res == fheroes2::GameMode::HIGHSCORES_STANDARD ) { break; } // Reset environment sounds and music theme at the end of the human turn. AudioManager::ResetAudio(); break; case CONTROL_AI: // TODO: remove this temporary assertion assert( res == fheroes2::GameMode::END_TURN ); if ( applyAIBonuses ) { world.NewDayAI(); applyAIBonuses = false; } Cursor::Get().SetThemes( Cursor::WAIT ); conf.SetCurrentColor( playerColor ); _statusWindow.Reset(); _statusWindow.SetState( StatusType::STATUS_AITURN ); #if defined( WITH_DEBUG ) if ( player->isAIAutoControlMode() ) { // If player gave control to AI we show the radar image and update it fully at the start of player's turn. _radar.SetHide( false ); _radar.SetRedraw( REDRAW_RADAR ); } #endif redraw( 0 ); validateFadeInAndRender(); // In Hot Seat mode there could be different alliances so we have to update fog directions for some cases. if ( isHotSeatGame ) { Maps::updateFogDirectionsInArea( { 0, 0 }, { world.w(), world.h() }, hotSeatAIFogColors( player ) ); } if ( !loadedFromSave ) { kingdom.ActionNewDayResourceUpdate( nullptr ); } kingdom.ActionBeforeTurn(); #if defined( WITH_DEBUG ) if ( !loadedFromSave && player->isAIAutoControlMode() && conf.isAutoSaveAtBeginningOfTurnEnabled() ) { // This is a human player which gave control to AI so we need to do autosave here. Game::AutoSave(); } #endif AI::Get().KingdomTurn( kingdom ); #if defined( WITH_DEBUG ) if ( !loadedFromSave && player->isAIAutoControlMode() && !conf.isAutoSaveAtBeginningOfTurnEnabled() ) { // This is a human player which gave control to AI so we need to do autosave here. Game::AutoSave(); } #endif break; default: // So far no other player type is supported so this should not happen. assert( 0 ); break; } if ( res != fheroes2::GameMode::END_TURN ) { break; } // check if the game is over after each player's turn res = gameResult.checkGameOver(); if ( res != fheroes2::GameMode::CANCEL ) { break; } res = fheroes2::GameMode::END_TURN; } // reset this after potential HumanTurn() call, but regardless of whether current kingdom // is vanquished - next alive kingdom should start a new day from scratch loadedFromSave = false; } // we went through all the players, but the current player from the save file is still not found, // something is clearly wrong here if ( skipTurns ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "the current player from the save file was not found" << ", player color: " << Color::String( conf.CurrentColor() ) ) res = fheroes2::GameMode::MAIN_MENU; } // Don't carry the current player color to the next turn. conf.SetCurrentColor( Color::NONE ); } // if we are here, the res value should never be fheroes2::GameMode::END_TURN assert( res != fheroes2::GameMode::END_TURN ); Game::setDisplayFadeIn(); // Do not use fade-out effect when exiting to Highscores screen as in this case name input dialog will be rendered next. if ( res != fheroes2::GameMode::HIGHSCORES_STANDARD ) { fheroes2::fadeOutDisplay(); } return res; } fheroes2::GameMode Interface::AdventureMap::HumanTurn( const bool isload ) { if ( isload ) { updateFocus(); } else { ResetFocus( GameFocus::FIRSTHERO, false ); } _radar.SetHide( false ); _statusWindow.Reset(); _gameArea.SetUpdateCursor(); const Settings & conf = Settings::Get(); if ( conf.IsGameType( Game::TYPE_HOTSEAT ) ) { // TODO: Cache fog directions for all Human players in array to not perform full update at every turn start. // Fully update fog directions at the start of player's move in Hot Seat mode as the previous move could be done by opposing player. Interface::GameArea::updateMapFogDirections(); } redraw( REDRAW_GAMEAREA | REDRAW_RADAR | REDRAW_ICONS | REDRAW_BUTTONS | REDRAW_STATUS | REDRAW_BORDER ); validateFadeInAndRender(); Kingdom & myKingdom = world.GetKingdom( conf.CurrentColor() ); if ( !isload ) { if ( 1 < world.CountWeek() && world.BeginWeek() ) { ShowNewWeekDialog(); } myKingdom.ActionNewDayResourceUpdate( []( const EventDate & event, const Funds & funds ) { if ( funds.GetValidItemsCount() ) { fheroes2::showResourceMessage( fheroes2::Text( event.title, fheroes2::FontType::normalYellow() ), fheroes2::Text( event.message, fheroes2::FontType::normalWhite() ), Dialog::OK, funds ); } else if ( !event.message.empty() ) { fheroes2::showStandardTextMessage( event.title, event.message, Dialog::OK ); } } ); // The amount of the kingdom resources has changed, the status window needs to be updated redraw( REDRAW_STATUS ); fheroes2::Display::instance().render(); if ( conf.isAutoSaveAtBeginningOfTurnEnabled() ) { Game::AutoSave(); } } GameOver::Result & gameResult = GameOver::Result::Get(); // check if the game is over at the beginning of each human-controlled player's turn fheroes2::GameMode res = gameResult.checkGameOver(); const VecCastles & myCastles = myKingdom.GetCastles(); if ( res == fheroes2::GameMode::CANCEL && myCastles.empty() ) { ShowWarningLostTownsDialog(); } int fastScrollRepeatCount = 0; const int fastScrollStartThreshold = 2; bool isMovingHero = false; bool stopHero = false; int heroAnimationFrameCount = 0; fheroes2::Point heroAnimationOffset; int heroAnimationSpriteId = 0; bool isCursorOverButtons = false; bool isCursorOverGamearea = false; const std::vector delayTypes = { Game::CURRENT_HERO_DELAY, Game::MAPS_DELAY }; LocalEvent & le = LocalEvent::Get(); Cursor & cursor = Cursor::Get(); while ( res == fheroes2::GameMode::CANCEL ) { if ( !le.HandleEvents( Game::isDelayNeeded( delayTypes ), true ) ) { if ( EventExit() == fheroes2::GameMode::QUIT_GAME ) { res = fheroes2::GameMode::QUIT_GAME; break; } continue; } #if defined( WITH_DEBUG ) { const Player * player = Players::Get( myKingdom.GetColor() ); assert( player != nullptr ); // Control has just been transferred to AI, end the turn immediately if ( player->isAIAutoControlMode() ) { return fheroes2::GameMode::END_TURN; } } #endif // pending timer events _statusWindow.TimerEventProcessing(); // hotkeys if ( le.KeyPress() ) { // if the hero is currently moving, pressing any key should stop him if ( isMovingHero ) { stopHero = true; } // adventure map control else if ( HotKeyPressEvent( Game::HotKeyEvent::MAIN_MENU_QUIT ) || HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) res = EventExit(); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_END_TURN ) ) res = EventEndTurn(); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_NEXT_HERO ) ) EventNextHero(); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_NEXT_TOWN ) ) EventNextTown(); else if ( HotKeyPressEvent( Game::HotKeyEvent::MAIN_MENU_NEW_GAME ) ) res = EventNewGame(); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_SAVE_GAME ) ) EventSaveGame(); else if ( HotKeyPressEvent( Game::HotKeyEvent::MAIN_MENU_LOAD_GAME ) ) res = EventLoadGame(); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_FILE_OPTIONS ) ) res = EventFileDialog(); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_ADVENTURE_OPTIONS ) ) res = EventAdventureDialog(); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_SYSTEM_OPTIONS ) ) EventSystemDialog(); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_PUZZLE_MAP ) ) EventPuzzleMaps(); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_SCENARIO_INFORMATION ) ) res = EventScenarioInformation(); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_CAST_SPELL ) ) EventCastSpell(); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_KINGDOM_SUMMARY ) ) EventKingdomInfo(); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_VIEW_WORLD ) ) EventViewWorld(); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_TOGGLE_CONTROL_PANEL ) ) EventSwitchShowControlPanel(); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_TOGGLE_RADAR ) ) EventSwitchShowRadar(); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_TOGGLE_BUTTONS ) ) EventSwitchShowButtons(); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_TOGGLE_STATUS ) ) EventSwitchShowStatus(); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_TOGGLE_ICONS ) ) EventSwitchShowIcons(); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_START_HERO_MOVEMENT ) ) res = EventHeroMovement(); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_DIG_ARTIFACT ) ) res = EventDigArtifact(); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_SLEEP_HERO ) ) EventSwitchHeroSleeping(); // hero movement control else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_LEFT ) ) EventKeyArrowPress( Direction::LEFT ); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_RIGHT ) ) EventKeyArrowPress( Direction::RIGHT ); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_UP ) ) EventKeyArrowPress( Direction::TOP ); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_DOWN ) ) EventKeyArrowPress( Direction::BOTTOM ); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_UP_LEFT ) ) EventKeyArrowPress( Direction::TOP_LEFT ); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_UP_RIGHT ) ) EventKeyArrowPress( Direction::TOP_RIGHT ); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_DOWN_LEFT ) ) EventKeyArrowPress( Direction::BOTTOM_LEFT ); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_DOWN_RIGHT ) ) EventKeyArrowPress( Direction::BOTTOM_RIGHT ); // map scrolling control else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_SCROLL_LEFT ) ) _gameArea.SetScroll( SCROLL_LEFT ); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_SCROLL_RIGHT ) ) _gameArea.SetScroll( SCROLL_RIGHT ); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_SCROLL_UP ) ) _gameArea.SetScroll( SCROLL_TOP ); else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_SCROLL_DOWN ) ) _gameArea.SetScroll( SCROLL_BOTTOM ); // default action else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_DEFAULT_ACTION ) ) res = EventDefaultAction(); // open focus else if ( HotKeyPressEvent( Game::HotKeyEvent::WORLD_OPEN_FOCUS ) ) EventOpenFocus(); } if ( res != fheroes2::GameMode::CANCEL ) { break; } if ( fheroes2::cursor().isFocusActive() && !_gameArea.isDragScroll() && !_radar.isDragRadar() && ( conf.ScrollSpeed() != SCROLL_SPEED_NONE ) ) { int scrollPosition = SCROLL_NONE; if ( isScrollLeft( le.GetMouseCursor() ) ) scrollPosition |= SCROLL_LEFT; else if ( isScrollRight( le.GetMouseCursor() ) ) scrollPosition |= SCROLL_RIGHT; if ( isScrollTop( le.GetMouseCursor() ) ) scrollPosition |= SCROLL_TOP; else if ( isScrollBottom( le.GetMouseCursor() ) ) scrollPosition |= SCROLL_BOTTOM; if ( scrollPosition != SCROLL_NONE ) { if ( Game::validateAnimationDelay( Game::SCROLL_START_DELAY ) ) { if ( fastScrollRepeatCount < fastScrollStartThreshold ) { ++fastScrollRepeatCount; } } if ( fastScrollRepeatCount >= fastScrollStartThreshold ) { _gameArea.SetScroll( scrollPosition ); } } else { fastScrollRepeatCount = 0; } } else { fastScrollRepeatCount = 0; } const bool isHiddenInterface = conf.isHideInterfaceEnabled(); const bool prevIsCursorOverButtons = isCursorOverButtons; isCursorOverButtons = false; isCursorOverGamearea = false; if ( isMovingHero ) { // hero is moving, set the appropriate cursor cursor.SetThemes( Cursor::WAIT ); // if the hero is currently moving, pressing any mouse button should stop him if ( le.MouseClickLeft() || le.MousePressRight() ) { stopHero = true; } } // cursor is over the status window else if ( ( !isHiddenInterface || conf.ShowStatus() ) && le.MouseCursor( _statusWindow.GetRect() ) ) { cursor.SetThemes( Cursor::POINTER ); _statusWindow.QueueEventProcessing(); } // cursor is over the buttons area else if ( ( !isHiddenInterface || conf.ShowButtons() ) && le.MouseCursor( buttonsArea.GetRect() ) ) { cursor.SetThemes( Cursor::POINTER ); res = buttonsArea.QueueEventProcessing(); isCursorOverButtons = true; } // cursor is over the icons panel else if ( ( !isHiddenInterface || conf.ShowIcons() ) && le.MouseCursor( iconsPanel.GetRect() ) ) { cursor.SetThemes( Cursor::POINTER ); iconsPanel.QueueEventProcessing(); } // cursor is over the radar else if ( ( !isHiddenInterface || conf.ShowRadar() ) && le.MouseCursor( _radar.GetRect() ) ) { cursor.SetThemes( Cursor::POINTER ); if ( !_gameArea.isDragScroll() ) _radar.QueueEventProcessing(); } // cursor is over the control panel else if ( isHiddenInterface && conf.ShowControlPanel() && le.MouseCursor( controlPanel.GetArea() ) ) { cursor.SetThemes( Cursor::POINTER ); res = controlPanel.QueueEventProcessing(); } // cursor is over the game area else if ( le.MouseCursor( _gameArea.GetROI() ) && !_gameArea.NeedScroll() ) { isCursorOverGamearea = true; } // cursor is somewhere else else if ( !_gameArea.NeedScroll() ) { cursor.SetThemes( Cursor::POINTER ); _gameArea.ResetCursorPosition(); } // gamearea if ( !_gameArea.NeedScroll() && !isMovingHero ) { if ( !_radar.isDragRadar() ) _gameArea.QueueEventProcessing( isCursorOverGamearea ); else if ( !le.MousePressLeft() ) _radar.QueueEventProcessing(); } if ( prevIsCursorOverButtons && !isCursorOverButtons ) { buttonsArea.ResetButtons(); } if ( res != fheroes2::GameMode::CANCEL ) { break; } // animation of the hero's movement if ( Game::validateAnimationDelay( Game::CURRENT_HERO_DELAY ) ) { Heroes * hero = GetFocusHeroes(); if ( hero ) { bool resetHeroSprite = false; if ( heroAnimationFrameCount > 0 ) { const int32_t heroMovementSkipValue = Game::HumanHeroAnimSpeedMultiplier(); _gameArea.ShiftCenter( { heroAnimationOffset.x * heroMovementSkipValue, heroAnimationOffset.y * heroMovementSkipValue } ); _gameArea.SetRedraw(); if ( heroAnimationOffset != fheroes2::Point() ) { Game::EnvironmentSoundMixer(); } heroAnimationFrameCount -= heroMovementSkipValue; if ( ( heroAnimationFrameCount & 0x3 ) == 0 ) { // % 4 hero->SetSpriteIndex( heroAnimationSpriteId ); if ( heroAnimationFrameCount == 0 ) resetHeroSprite = true; else ++heroAnimationSpriteId; } const int offsetStep = ( ( 4 - ( heroAnimationFrameCount & 0x3 ) ) & 0x3 ); // % 4 hero->SetOffset( { heroAnimationOffset.x * offsetStep, heroAnimationOffset.y * offsetStep } ); } if ( heroAnimationFrameCount == 0 ) { if ( resetHeroSprite ) { hero->SetSpriteIndex( heroAnimationSpriteId - 1 ); } if ( hero->isMoveEnabled() ) { if ( hero->Move( 10 == conf.HeroesMoveSpeed() ) ) { // Do not generate a frame as we are going to do it later. Interface::AdventureMap::RedrawLocker redrawLocker( Interface::AdventureMap::Get() ); _gameArea.SetCenter( hero->GetCenter() ); if ( stopHero ) { stopHero = false; hero->SetMove( false ); } } else { const fheroes2::Point movement( hero->MovementDirection() ); if ( movement != fheroes2::Point() ) { // don't waste resources for no movement // Do not generate a frame as we are going to do it later. Interface::AdventureMap::RedrawLocker redrawLocker( Interface::AdventureMap::Get() ); const int32_t heroMovementSkipValue = Game::HumanHeroAnimSpeedMultiplier(); heroAnimationOffset = movement; _gameArea.ShiftCenter( movement ); heroAnimationFrameCount = 32 - heroMovementSkipValue; heroAnimationSpriteId = hero->GetSpriteIndex(); if ( heroMovementSkipValue < 4 ) { hero->SetSpriteIndex( heroAnimationSpriteId - 1 ); hero->SetOffset( { heroAnimationOffset.x * heroMovementSkipValue, heroAnimationOffset.y * heroMovementSkipValue } ); } else { ++heroAnimationSpriteId; } } _gameArea.SetRedraw(); } // Update the hero's move status. isMovingHero = hero->isMoveEnabled(); if ( hero->isAction() ) { // The action can not be performed while moving, only after the move is ended. assert( !isMovingHero ); // Check if the game is over after the hero's action. res = gameResult.checkGameOver(); hero->ResetAction(); } if ( !isMovingHero ) { // Reset the 'ENABLEMOVE' state on this loop to properly update the cursor in this frame and not in the next. hero->SetMove( false ); // During the action and/or movement the adventure map and/or cursor position may have changed, so we should update the cursor image. if ( Game::isFadeInNeeded() ) { // Do not change cursor right now because fade-in is scheduled. _gameArea.SetUpdateCursor(); } else { if ( le.MouseCursor( _gameArea.GetROI() ) ) { // We do not use '_gameArea.SetUpdateCursor()' here because we need to update the cursor before rendering the current frame // and '_gameArea.QueueEventProcessing()' was called earlier in this loop and will only be able to update the cursor in the // next loop for the next frame. cursor.SetThemes( GetCursorTileIndex( _gameArea.GetValidTileIdFromPoint( le.GetMouseCursor() ) ) ); } else { // When the cursor is not over the game area we use the Pointer cursor. cursor.SetThemes( Cursor::POINTER ); } } } } else { isMovingHero = false; stopHero = false; hero->SetMove( false ); _gameArea.SetUpdateCursor(); } } } else { isMovingHero = false; stopHero = false; } } // fast scroll if ( ( _gameArea.NeedScroll() && !isMovingHero ) || _gameArea.needDragScrollRedraw() ) { if ( Game::validateAnimationDelay( Game::SCROLL_DELAY ) ) { if ( ( isScrollLeft( le.GetMouseCursor() ) || isScrollRight( le.GetMouseCursor() ) || isScrollTop( le.GetMouseCursor() ) || isScrollBottom( le.GetMouseCursor() ) ) && !_gameArea.isDragScroll() ) { cursor.SetThemes( _gameArea.GetScrollCursor() ); } _gameArea.Scroll(); _gameArea.SetRedraw(); _radar.SetRedraw( REDRAW_RADAR_CURSOR ); } } // Check that the kingdom is not vanquished yet (has at least one hero or castle). if ( res == fheroes2::GameMode::CANCEL && !myKingdom.isPlay() ) { res = fheroes2::GameMode::END_TURN; } // Render map only if the turn is not over. if ( res == fheroes2::GameMode::CANCEL ) { // map objects animation if ( Game::validateAnimationDelay( Game::MAPS_DELAY ) ) { Game::updateAdventureMapAnimationIndex(); _gameArea.SetRedraw(); } if ( needRedraw() ) { redraw( 0 ); // If this assertion blows up it means that we are holding a RedrawLocker lock for rendering which should not happen. assert( getRedrawMask() == 0 ); validateFadeInAndRender(); } } } if ( res == fheroes2::GameMode::END_TURN ) { if ( GetFocusHeroes() ) { GetFocusHeroes()->ShowPath( false ); setRedraw( REDRAW_GAMEAREA ); } if ( myKingdom.isPlay() ) { // these warnings should be shown at the end of the turn if ( myCastles.empty() ) { const uint32_t lostTownDays = myKingdom.GetLostTownDays(); if ( lostTownDays > Game::GetLostTownDays() ) { Game::DialogPlayers( conf.CurrentColor(), _( "Beware!" ), _( "%{color} player, you have lost your last town. If you do not conquer another town in the next week, you will be eliminated." ) ); } else if ( lostTownDays == 1 ) { Game::DialogPlayers( conf.CurrentColor(), _( "Defeat!" ), _( "%{color} player, your heroes abandon you, and you are banished from this land." ) ); } } if ( !conf.isAutoSaveAtBeginningOfTurnEnabled() ) { Game::AutoSave(); } } } return res; } void Interface::AdventureMap::mouseCursorAreaClickLeft( const int32_t tileIndex ) { Heroes * focusedHero = GetFocusHeroes(); assert( focusedHero == nullptr || !focusedHero->Modes( Heroes::ENABLEMOVE ) ); const Maps::Tiles & tile = world.GetTiles( tileIndex ); switch ( Cursor::WithoutDistanceThemes( Cursor::Get().Themes() ) ) { case Cursor::HEROES: { Heroes * otherHero = tile.getHero(); if ( otherHero == nullptr ) { break; } if ( focusedHero == nullptr || focusedHero != otherHero ) { SetFocus( otherHero, false ); RedrawFocus(); } else { Game::OpenHeroesDialog( *otherHero, true, true ); } break; } case Cursor::CASTLE: { const MP2::MapObjectType objectType = tile.GetObject(); if ( MP2::OBJ_NON_ACTION_CASTLE != objectType && MP2::OBJ_CASTLE != objectType ) { break; } Castle * otherCastle = world.getCastle( tile.GetCenter() ); if ( otherCastle == nullptr ) { break; } const Castle * focusedCastle = GetFocusCastle(); if ( focusedCastle == nullptr || focusedCastle != otherCastle ) { SetFocus( otherCastle ); RedrawFocus(); } else { Game::OpenCastleDialog( *otherCastle ); } break; } case Cursor::CURSOR_HERO_FIGHT: case Cursor::CURSOR_HERO_MOVE: case Cursor::CURSOR_HERO_BOAT: case Cursor::CURSOR_HERO_ANCHOR: case Cursor::CURSOR_HERO_MEET: case Cursor::CURSOR_HERO_ACTION: case Cursor::CURSOR_HERO_BOAT_ACTION: { if ( focusedHero == nullptr ) { break; } ShowPathOrStartMoveHero( focusedHero, tileIndex ); break; } default: break; } } void Interface::AdventureMap::mouseCursorAreaPressRight( const int32_t tileIndex ) const { #ifndef NDEBUG const Heroes * focusedHero = GetFocusHeroes(); #endif assert( focusedHero == nullptr || !focusedHero->Modes( Heroes::ENABLEMOVE ) ); const Settings & conf = Settings::Get(); const Maps::Tiles & tile = world.GetTiles( tileIndex ); DEBUG_LOG( DBG_DEVEL, DBG_INFO, '\n' << tile.String() ) if ( !IS_DEVEL() && tile.isFog( conf.CurrentColor() ) ) { Dialog::QuickInfo( tile ); } else { switch ( tile.GetObject() ) { case MP2::OBJ_NON_ACTION_CASTLE: case MP2::OBJ_CASTLE: { const Castle * castle = world.getCastle( tile.GetCenter() ); if ( castle ) { Dialog::QuickInfo( *castle ); } else { Dialog::QuickInfo( tile ); } break; } case MP2::OBJ_HERO: { const Heroes * heroes = tile.getHero(); if ( heroes ) { Dialog::QuickInfo( *heroes ); } break; } default: Dialog::QuickInfo( tile ); break; } } } fheroes2-1.0.12+dfsg/src/fheroes2/game/game_static.cpp000066400000000000000000000233721456075706000224510ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2011 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "game_static.h" #include "heroes.h" #include "mp2.h" #include "race.h" #include "skill.h" #include "skill_static.h" namespace Skill { stats_t _stats[] = { { "knight", { 1, 1, 1, 1 }, { 2, 2, 1, 1 }, 0, 0, { 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, 10, { 35, 45, 10, 10 }, { 25, 25, 25, 25 }, { 2, 4, 3, 1, 3, 5, 3, 1, 1, 2, 0, 3, 2, 2 } }, { "barbarian", { 1, 1, 1, 1 }, { 3, 1, 1, 1 }, 0, 0, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0 }, 10, { 55, 35, 5, 5 }, { 30, 30, 20, 20 }, { 3, 3, 2, 1, 2, 3, 3, 2, 1, 3, 0, 4, 4, 1 } }, { "sorceress", { 0, 0, 2, 2 }, { 0, 0, 2, 3 }, 1, 15, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 1 }, 10, { 10, 10, 30, 50 }, { 20, 20, 30, 30 }, { 3, 3, 2, 2, 2, 1, 2, 3, 3, 4, 0, 2, 1, 4 } }, { "warlock", { 0, 0, 2, 2 }, { 0, 0, 3, 2 }, 1, 19, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1 }, 10, { 10, 10, 50, 30 }, { 20, 20, 30, 30 }, { 1, 3, 2, 3, 2, 1, 2, 1, 3, 2, 1, 2, 4, 5 } }, { "wizard", { 0, 0, 2, 2 }, { 0, 1, 2, 2 }, 1, 17, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 }, 10, { 10, 10, 40, 40 }, { 20, 20, 30, 30 }, { 1, 3, 2, 3, 2, 2, 2, 2, 4, 2, 0, 2, 2, 5 } }, { "necromancer", { 0, 0, 2, 2 }, { 1, 0, 2, 2 }, 1, 10, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1 }, 10, { 15, 15, 35, 35 }, { 25, 25, 25, 25 }, { 1, 3, 2, 3, 2, 0, 2, 1, 3, 2, 5, 3, 1, 4 } }, { nullptr, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, 0, 0, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, 10, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } } }; values_t _values[] = { { "pathfinding", { 25, 50, 100 } }, { "archery", { 10, 25, 50 } }, { "logistics", { 10, 20, 30 } }, { "scouting", { 1, 2, 3 } }, { "diplomacy", { 25, 50, 100 } }, { "navigation", { 33, 66, 100 } }, { "leadership", { 1, 2, 3 } }, { "wisdom", { 3, 4, 5 } }, { "mysticism", { 1, 2, 3 } }, { "luck", { 1, 2, 3 } }, { "ballistics", { 0, 0, 0 } }, { "eagleeye", { 20, 30, 40 } }, { "necromancy", { 10, 20, 30 } }, { "estates", { 100, 250, 500 } }, { nullptr, { 0, 0, 0 } }, }; secondary_t _from_witchs_hut = { /* archery */ 1, /* ballistics */ 1, /* diplomacy */ 1, /* eagleeye */ 1, /* estates */ 1, /* leadership */ 0, /* logistics */ 1, /* luck */ 1, /* mysticism */ 1, /* navigation */ 1, /* necromancy*/ 0, /* pathfinding */ 1, /* scouting */ 1, /* wisdom */ 1 }; } uint32_t GameStatic::GetLostOnWhirlpoolPercent() { return 50; } uint32_t GameStatic::getFogDiscoveryDistance( const FogDiscoveryType type ) { switch ( type ) { case FogDiscoveryType::CASTLE: return 5; case FogDiscoveryType::HEROES: return 4; case FogDiscoveryType::OBSERVATION_TOWER: return 20; case FogDiscoveryType::MAGI_EYES: return 9; default: break; } return 0; } uint32_t GameStatic::GetGameOverLostDays() { return 7; } uint32_t GameStatic::GetHeroesRestoreSpellPointsPerDay() { return 1; } uint32_t GameStatic::GetKingdomMaxHeroes() { return 8; } uint32_t GameStatic::GetCastleGrownWell() { return 2; } uint32_t GameStatic::GetCastleGrownWel2() { return 8; } uint32_t GameStatic::GetCastleGrownWeekOf() { return 5; } uint32_t GameStatic::GetCastleGrownMonthOf() { return 100; } int32_t GameStatic::ObjectVisitedModifiers( const MP2::MapObjectType objectType ) { switch ( objectType ) { case MP2::OBJ_BUOY: case MP2::OBJ_OASIS: case MP2::OBJ_WATERING_HOLE: case MP2::OBJ_MERMAID: case MP2::OBJ_FAERIE_RING: case MP2::OBJ_FOUNTAIN: case MP2::OBJ_IDOL: return 1; case MP2::OBJ_TEMPLE: return 2; case MP2::OBJ_GRAVEYARD: case MP2::OBJ_DERELICT_SHIP: case MP2::OBJ_SHIPWRECK: return -1; case MP2::OBJ_PYRAMID: return -2; default: break; } return 0; } const Skill::stats_t * GameStatic::GetSkillStats( int race ) { switch ( race ) { case Race::KNGT: return &Skill::_stats[0]; case Race::BARB: return &Skill::_stats[1]; case Race::SORC: return &Skill::_stats[2]; case Race::WRLK: return &Skill::_stats[3]; case Race::WZRD: return &Skill::_stats[4]; case Race::NECR: return &Skill::_stats[5]; default: break; } return nullptr; } const Skill::values_t * GameStatic::GetSkillValues( int type ) { switch ( type ) { case Skill::Secondary::PATHFINDING: return &Skill::_values[0]; case Skill::Secondary::ARCHERY: return &Skill::_values[1]; case Skill::Secondary::LOGISTICS: return &Skill::_values[2]; case Skill::Secondary::SCOUTING: return &Skill::_values[3]; case Skill::Secondary::DIPLOMACY: return &Skill::_values[4]; case Skill::Secondary::NAVIGATION: return &Skill::_values[5]; case Skill::Secondary::LEADERSHIP: return &Skill::_values[6]; case Skill::Secondary::WISDOM: return &Skill::_values[7]; case Skill::Secondary::MYSTICISM: return &Skill::_values[8]; case Skill::Secondary::LUCK: return &Skill::_values[9]; case Skill::Secondary::BALLISTICS: return &Skill::_values[10]; case Skill::Secondary::EAGLE_EYE: return &Skill::_values[11]; case Skill::Secondary::NECROMANCY: return &Skill::_values[12]; case Skill::Secondary::ESTATES: return &Skill::_values[13]; default: break; } return nullptr; } const Skill::secondary_t * GameStatic::GetSkillForWitchsHut() { return &Skill::_from_witchs_hut; } int GameStatic::GetBattleMoatReduceDefense() { return 3; } uint32_t GameStatic::getMovementPointBonus( const MP2::MapObjectType objectType ) { switch ( objectType ) { case MP2::OBJ_OASIS: return 800; case MP2::OBJ_STABLES: case MP2::OBJ_WATERING_HOLE: return 400; default: break; } return 0; } bool GameStatic::isHeroWorthyToVisitXanadu( const Heroes & hero ) { const uint32_t heroLevel = hero.GetLevel(); if ( heroLevel > 9 ) { return true; } const uint32_t diplomacyLevel = hero.GetLevelSkill( Skill::Secondary::DIPLOMACY ); return ( diplomacyLevel == Skill::Level::BASIC && heroLevel > 7 ) || ( diplomacyLevel == Skill::Level::ADVANCED && heroLevel > 5 ) || ( diplomacyLevel == Skill::Level::EXPERT && heroLevel > 3 ); } fheroes2-1.0.12+dfsg/src/fheroes2/game/game_static.h000066400000000000000000000054371456075706000221200ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2011 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2GAMESTATIC_H #define H2GAMESTATIC_H #include #include "mp2.h" namespace Skill { struct stats_t; struct values_t; struct secondary_t; } class Heroes; namespace GameStatic { enum class FogDiscoveryType : int32_t { CASTLE, HEROES, OBSERVATION_TOWER, MAGI_EYES }; uint32_t GetLostOnWhirlpoolPercent(); uint32_t GetGameOverLostDays(); uint32_t getFogDiscoveryDistance( const FogDiscoveryType type ); uint32_t GetKingdomMaxHeroes(); uint32_t GetCastleGrownWell(); uint32_t GetCastleGrownWel2(); uint32_t GetCastleGrownWeekOf(); uint32_t GetCastleGrownMonthOf(); uint32_t GetHeroesRestoreSpellPointsPerDay(); int32_t ObjectVisitedModifiers( const MP2::MapObjectType objectType ); int GetBattleMoatReduceDefense(); const Skill::stats_t * GetSkillStats( int race ); const Skill::values_t * GetSkillValues( int skill ); const Skill::secondary_t * GetSkillForWitchsHut(); uint32_t getMovementPointBonus( const MP2::MapObjectType objectType ); bool isHeroWorthyToVisitXanadu( const Heroes & hero ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/game/game_video.cpp000066400000000000000000000242651456075706000222720ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "game_video.h" #include #include #include #include #include #include #include #include "audio.h" #include "cursor.h" #include "dir.h" #include "game_delays.h" #include "localevent.h" #include "logging.h" #include "screen.h" #include "settings.h" #include "smk_decoder.h" #include "system.h" #include "ui_text.h" #include "ui_tool.h" namespace { // Anim2 directory is used in Russian Buka version of the game. std::array videoDir = { "anim", "anim2", System::concatPath( "heroes2", "anim" ), "data" }; void playAudio( const std::vector> & audioChannels ) { Mixer::setVolume( -1, 100 * Settings::Get().SoundVolume() / 10 ); for ( const std::vector & audio : audioChannels ) { if ( !audio.empty() ) { Mixer::Play( audio.data(), static_cast( audio.size() ), -1, false ); } } } } namespace Video { bool getVideoFilePath( const std::string & fileName, std::string & path ) { for ( const std::string & rootDir : Settings::GetRootDirs() ) { for ( size_t dirIdx = 0; dirIdx < videoDir.size(); ++dirIdx ) { const std::string fullDirPath = System::concatPath( rootDir, videoDir[dirIdx] ); if ( System::IsDirectory( fullDirPath ) ) { ListFiles videoFiles; videoFiles.FindFileInDir( fullDirPath, fileName, false ); if ( videoFiles.empty() ) { continue; } std::swap( videoFiles.front(), path ); if ( dirIdx > 0 ) { // Put the current directory at the first place to increase cache hit chance. std::swap( videoDir[0], videoDir[dirIdx] ); } return true; } } } return false; } bool ShowVideo( const std::string & fileName, const VideoAction action, const std::vector & subtitles /* = {} */, const bool fadeColorsOnEnd /* = false */ ) { // Stop any cycling animation. const fheroes2::ScreenPaletteRestorer screenRestorer; std::string videoPath; if ( !getVideoFilePath( fileName, videoPath ) ) { // File doesn't exist, so no need to even try to load it. DEBUG_LOG( DBG_GAME, DBG_INFO, fileName << " video file does not exist." ) return false; } SMKVideoSequence video( videoPath ); const uint32_t frameCount = video.frameCount(); if ( frameCount < 1 ) { // Nothing to show. return false; } const std::vector> & audioChannels = video.getAudioChannels(); const bool hasAudio = Audio::isValid() && !audioChannels.empty(); if ( action == VideoAction::IGNORE_VIDEO ) { // Since no video is rendered play audio if available. if ( hasAudio ) { playAudio( audioChannels ); } return true; } const bool isLooped = ( action == VideoAction::LOOP_VIDEO || action == VideoAction::PLAY_TILL_AUDIO_END ); // Hide mouse cursor. const CursorRestorer cursorRestorer( false, Cursor::Get().Themes() ); fheroes2::Display & display = fheroes2::Display::instance(); display.fill( 0 ); display.updateNextRenderRoi( { 0, 0, display.width(), display.height() } ); uint32_t currentFrame = 0; fheroes2::Rect frameRoi( ( display.width() - video.width() ) / 2, ( display.height() - video.height() ) / 2, 0, 0 ); const uint32_t delay = static_cast( 1000.0 / video.fps() + 0.5 ); // This might be not very accurate but it's the best we can have now std::vector palette; std::vector prevPalette; // Prepare the first frame. video.resetFrame(); video.getNextFrame( display, frameRoi.x, frameRoi.y, frameRoi.width, frameRoi.height, prevPalette ); screenRestorer.changePalette( prevPalette.data() ); // Render subtitles on the first frame for ( const Subtitle & subtitle : subtitles ) { if ( subtitle.needRender( 0 ) ) { subtitle.render( display, frameRoi ); } } LocalEvent & le = LocalEvent::Get(); Game::passCustomAnimationDelay( delay ); // Make sure that the first run is passed immediately. assert( !Game::isCustomDelayNeeded( delay ) ); // Play audio just before rendering the frame. This is important to minimize synchronization issues between audio and video. if ( hasAudio ) { playAudio( audioChannels ); } while ( le.HandleEvents( Game::isCustomDelayNeeded( delay ) ) ) { if ( ( action == VideoAction::PLAY_TILL_AUDIO_END ) && !Mixer::isPlaying( -1 ) ) { break; } if ( le.KeyPress() || le.MouseClickLeft() || le.MouseClickMiddle() || le.MouseClickRight() ) { Mixer::Stop(); break; } if ( Game::validateCustomAnimationDelay( delay ) ) { if ( currentFrame < frameCount ) { // Render the prepared frame. display.render( frameRoi ); ++currentFrame; if ( ( currentFrame == frameCount ) && isLooped ) { currentFrame = 0; video.resetFrame(); if ( hasAudio ) { playAudio( audioChannels ); } } // Prepare the next frame for render. video.getNextFrame( display, frameRoi.x, frameRoi.y, frameRoi.width, frameRoi.height, palette ); if ( prevPalette != palette ) { screenRestorer.changePalette( palette.data() ); std::swap( prevPalette, palette ); } // Render subtitles on the prepared next frame for ( const Subtitle & subtitle : subtitles ) { if ( subtitle.needRender( currentFrame * delay ) ) { subtitle.render( display, frameRoi ); } } } else if ( action != VideoAction::WAIT_FOR_USER_INPUT ) { break; } } } if ( fadeColorsOnEnd ) { // Do color fade for 1 second with 15 FPS. fheroes2::colorFade( palette, frameRoi, 1000, 15.0 ); } else { display.fill( 0 ); } display.updateNextRenderRoi( { 0, 0, display.width(), display.height() } ); return true; } Subtitle::Subtitle( const fheroes2::TextBase & subtitleText, const uint32_t startTimeMS, const uint32_t durationMS, const fheroes2::Point & position /* = { -1, -1 } */, const int32_t maxWidth /* = fheroes2::Display::DEFAULT_WIDTH */ ) : _position( position ) , _startTimeMS( startTimeMS ) { assert( maxWidth > 0 ); const int32_t textWidth = subtitleText.width( maxWidth ); // We add extra 1 to have space for contour. _subtitleImage.resize( textWidth + 1, subtitleText.height( textWidth ) + 1 ); // Draw text and remove all shadow data if it could not be properly applied to video palette. // We use the black color with id = 36 so no shadow will be applied to it. const uint8_t blackColor = 36; _subtitleImage.fill( blackColor ); // At the left and bottom there is space for contour left by original font shadows, we leave 1 extra pixel from the right and top. subtitleText.draw( 0, 1, textWidth, _subtitleImage ); fheroes2::ReplaceColorIdByTransformId( _subtitleImage, blackColor, 1 ); // Add black contour to the text. fheroes2::Blit( fheroes2::CreateContour( _subtitleImage, blackColor ), _subtitleImage ); // This is made to avoid overflow when calculating the end frame. _endTimeMS = _startTimeMS + std::min( durationMS, UINT32_MAX - _startTimeMS ); // If position has negative value: position subtitles at the bottom center by using default screen size (it is currently equal to video size). if ( ( _position.x < 0 ) || ( _position.y < 0 ) ) { _position.x = ( fheroes2::Display::DEFAULT_WIDTH - _subtitleImage.width() ) / 2; _position.y = fheroes2::Display::DEFAULT_HEIGHT - _subtitleImage.height(); } else { _position.x -= _subtitleImage.width() / 2; } } } fheroes2-1.0.12+dfsg/src/fheroes2/game/game_video.h000066400000000000000000000063141456075706000217320ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include #include "game_video_type.h" #include "image.h" #include "math_base.h" #include "screen.h" namespace fheroes2 { class TextBase; } namespace Video { class Subtitle { public: // Generate the image from subtitles text and store it in Subtitle class. // The 'position' represents the top-center of subtitles image. All subtitles are center aligned. Subtitle( const fheroes2::TextBase & subtitleText, const uint32_t startTimeMS, const uint32_t durationMS, const fheroes2::Point & position = { -1, -1 }, const int32_t maxWidth = fheroes2::Display::DEFAULT_WIDTH ); // Check if subtitles need to be rendered at the current time (in milliseconds). bool needRender( const uint32_t currentTimeMS ) const { return ( ( currentTimeMS >= _startTimeMS ) && ( currentTimeMS < _endTimeMS ) ); } // Render subtitles image to the output image. void render( fheroes2::Image & output, const fheroes2::Rect & frameRoi ) const { fheroes2::Blit( _subtitleImage, 0, 0, output, frameRoi.x + _position.x, frameRoi.y + _position.y, _subtitleImage.width(), _subtitleImage.height() ); } private: fheroes2::Point _position{ -1, -1 }; fheroes2::Image _subtitleImage; const uint32_t _startTimeMS{ 0 }; uint32_t _endTimeMS{ 0 }; }; // Returns true if the file exists. bool getVideoFilePath( const std::string & fileName, std::string & path ); // Returns false if the video is not present or it is corrupted. bool ShowVideo( const std::string & fileName, const VideoAction action, const std::vector & subtitles = {}, const bool fadeColorsOnEnd = false ); } fheroes2-1.0.12+dfsg/src/fheroes2/game/game_video_type.h000066400000000000000000000032111456075706000227640ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once namespace Video { enum class VideoAction : int { PLAY_TILL_VIDEO_END, LOOP_VIDEO, WAIT_FOR_USER_INPUT, PLAY_TILL_AUDIO_END, IGNORE_VIDEO }; } fheroes2-1.0.12+dfsg/src/fheroes2/game/highscores.cpp000066400000000000000000000407221456075706000223250ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "highscores.h" #include #include #include #include #include "campaign_scenariodata.h" #include "serialize.h" #include "translations.h" #include "ui_language.h" #include "zzlib.h" namespace { const uint32_t highscoreFileMagicValueV1 = 0xBADC0DE; const uint32_t highscoreFileMagicValueV2 = 0xBADC1DE; const size_t highscoreMaximumEntries = 10; const std::array monstersInRanking = { Monster::PEASANT, Monster::GOBLIN, Monster::SPRITE, Monster::HALFLING, Monster::CENTAUR, Monster::ROGUE, Monster::SKELETON, Monster::ORC, Monster::ZOMBIE, Monster::ARCHER, Monster::RANGER, Monster::BOAR, Monster::DWARF, Monster::MUTANT_ZOMBIE, Monster::ORC_CHIEF, Monster::ELF, Monster::GARGOYLE, Monster::PIKEMAN, Monster::GRAND_ELF, Monster::BATTLE_DWARF, Monster::NOMAD, Monster::VETERAN_PIKEMAN, Monster::WOLF, Monster::MUMMY, Monster::IRON_GOLEM, Monster::ROYAL_MUMMY, Monster::OGRE, Monster::GRIFFIN, Monster::SWORDSMAN, Monster::DRUID, Monster::STEEL_GOLEM, Monster::MASTER_SWORDSMAN, Monster::AIR_ELEMENT, Monster::GREATER_DRUID, Monster::FIRE_ELEMENT, Monster::GHOST, Monster::VAMPIRE, Monster::WATER_ELEMENT, Monster::EARTH_ELEMENT, Monster::ROC, Monster::MINOTAUR, Monster::CAVALRY, Monster::TROLL, Monster::MAGE, Monster::MEDUSA, Monster::LICH, Monster::OGRE_LORD, Monster::MINOTAUR_KING, Monster::CHAMPION, Monster::WAR_TROLL, Monster::VAMPIRE_LORD, Monster::ARCHMAGE, Monster::POWER_LICH, Monster::UNICORN, Monster::HYDRA, Monster::PALADIN, Monster::GENIE, Monster::CRUSADER, Monster::CYCLOPS, Monster::GIANT, Monster::PHOENIX, Monster::BONE_DRAGON, Monster::GREEN_DRAGON, Monster::RED_DRAGON, Monster::TITAN, Monster::BLACK_DRAGON }; int32_t saveHighscoreEntry( fheroes2::HighscoreData && data, std::vector & entries, const bool isCampaign ) { auto iter = std::find( entries.begin(), entries.end(), data ); if ( iter != entries.end() ) { // This is the same game completion. Just replace the entry. *iter = std::move( data ); return static_cast( iter - entries.begin() ); } entries.emplace_back( data ); std::sort( entries.begin(), entries.end(), [isCampaign]( const fheroes2::HighscoreData & first, const fheroes2::HighscoreData & second ) { if ( isCampaign ) { return first.rating < second.rating; } return first.rating > second.rating; } ); if ( entries.size() > highscoreMaximumEntries ) { entries.resize( highscoreMaximumEntries ); } iter = std::find( entries.begin(), entries.end(), data ); if ( iter != entries.end() ) { return static_cast( iter - entries.begin() ); } return -1; } } namespace fheroes2 { uint32_t HighscoreData::generateCompletionTime() { return static_cast( std::time( nullptr ) ); } void HighscoreData::loadV1( StreamBase & msg ) { languageAbbreviation = fheroes2::getLanguageAbbreviation( fheroes2::SupportedLanguage::English ); msg >> playerName >> scenarioName >> completionTime >> dayCount >> rating >> mapSeed; } StreamBase & operator<<( StreamBase & msg, const HighscoreData & data ) { return msg << data.languageAbbreviation << data.playerName << data.scenarioName << data.completionTime << data.dayCount << data.rating << data.mapSeed; } StreamBase & operator>>( StreamBase & msg, HighscoreData & data ) { return msg >> data.languageAbbreviation >> data.playerName >> data.scenarioName >> data.completionTime >> data.dayCount >> data.rating >> data.mapSeed; } bool HighScoreDataContainer::load( const std::string & fileName ) { ZStreamBuf hdata; if ( !hdata.read( fileName ) ) { return false; } hdata.setbigendian( true ); uint32_t magicValue = 0; hdata >> magicValue; if ( magicValue != highscoreFileMagicValueV1 && magicValue != highscoreFileMagicValueV2 ) { // It is not a highscore file. return false; } if ( magicValue == highscoreFileMagicValueV1 ) { uint32_t size = hdata.get32(); _highScoresStandard.resize( size ); for ( HighscoreData & data : _highScoresStandard ) { data.loadV1( hdata ); } size = hdata.get32(); _highScoresCampaign.resize( size ); for ( HighscoreData & data : _highScoresCampaign ) { data.loadV1( hdata ); } } else { hdata >> _highScoresStandard >> _highScoresCampaign; } if ( hdata.fail() ) { return false; } // Since the introduction of campaign difficulty we need to calculate rating of a campaign completion. // Before the change rating for campaigns was always 0. We need to set it to the number of days. for ( fheroes2::HighscoreData & data : _highScoresCampaign ) { if ( data.rating == 0 ) { data.rating = data.dayCount; } } if ( _highScoresStandard.size() < highscoreMaximumEntries ) { populateStandardDefaultHighScores(); } else if ( _highScoresStandard.size() > highscoreMaximumEntries ) { _highScoresStandard.resize( highscoreMaximumEntries ); } if ( _highScoresCampaign.size() < highscoreMaximumEntries ) { populateCampaignDefaultHighScores(); } else if ( _highScoresCampaign.size() > highscoreMaximumEntries ) { _highScoresCampaign.resize( highscoreMaximumEntries ); } return true; } bool HighScoreDataContainer::save( const std::string & fileName ) const { ZStreamBuf hdata; hdata.setbigendian( true ); hdata << highscoreFileMagicValueV2 << _highScoresStandard << _highScoresCampaign; return !hdata.fail() && hdata.write( fileName ); } int32_t HighScoreDataContainer::registerScoreStandard( HighscoreData && data ) { return saveHighscoreEntry( std::move( data ), _highScoresStandard, false ); } int32_t HighScoreDataContainer::registerScoreCampaign( HighscoreData && data ) { return saveHighscoreEntry( std::move( data ), _highScoresCampaign, true ); } Monster HighScoreDataContainer::getMonsterByRating( const size_t rating ) { static std::vector> monsterRatings; if ( monsterRatings.empty() ) { uint32_t ratingSoFar = 0; uint32_t ratingIncrementCount = 0; for ( size_t i = 0; i < monstersInRanking.size(); ++i ) { const Monster::monster_t monster = monstersInRanking[i]; // 0 to 3 if ( monster == Monster::PEASANT ) { ratingIncrementCount = 3; } // 4 to 131 else if ( monster == Monster::GOBLIN ) { ratingIncrementCount = 4; } // 132 to 227 else if ( monster == Monster::GREATER_DRUID ) { ratingIncrementCount = 3; } // >= 228 else if ( monster == Monster::BLACK_DRAGON ) { ratingIncrementCount = 1; } ratingSoFar += ratingIncrementCount; monsterRatings.emplace_back( ratingSoFar, monstersInRanking[i] ); } } const std::pair & lastData = monsterRatings.back(); if ( rating >= lastData.first ) return Monster( lastData.second ); Monster::monster_t monster = Monster::PEASANT; for ( size_t i = 0; i < monsterRatings.size(); ++i ) { if ( rating <= monsterRatings[i].first ) { monster = monsterRatings[i].second; break; } } return Monster( monster ); } Monster HighScoreDataContainer::getMonsterByDay( const size_t dayCount ) { static std::vector> monsterDays; if ( monsterDays.empty() ) { uint32_t daySoFar = 0; uint32_t dayIncrementCount = 0; // need int for reverse-for loop const int monstersInRankingCount = static_cast( monstersInRanking.size() ); for ( int i = monstersInRankingCount - 1; i >= 0; --i ) { const Monster::monster_t monster = monstersInRanking[i]; // 0 to 300 if ( monster == Monster::BLACK_DRAGON ) { dayIncrementCount = 300; } // 301 to 1000 else if ( monster == Monster::TITAN ) { dayIncrementCount = 20; } // 1001 to 2000 else if ( monster == Monster::DRUID ) { dayIncrementCount = 100; } // 2001 to 5800 else if ( monster == Monster::BATTLE_DWARF ) { dayIncrementCount = 200; } // >= 5801 else if ( monster == Monster::PEASANT ) { dayIncrementCount = 1; } daySoFar += dayIncrementCount; monsterDays.emplace_back( daySoFar, monstersInRanking[i] ); } } std::pair lastData = monsterDays.back(); if ( dayCount >= lastData.first ) return Monster( lastData.second ); Monster::monster_t monster = Monster::PEASANT; for ( size_t i = 0; i < monsterDays.size(); ++i ) { if ( dayCount <= monsterDays[i].first ) { monster = monsterDays[i].second; break; } } return Monster( monster ); } void HighScoreDataContainer::populateStandardDefaultHighScores() { const uint32_t currentTime = HighscoreData::generateCompletionTime(); const std::string lang = fheroes2::getLanguageAbbreviation( fheroes2::getCurrentLanguage() ); registerScoreStandard( { lang, _( "Lord Kilburn" ), "Beltway", currentTime, 70, 150, 0 } ); registerScoreStandard( { lang, _( "Tsabu" ), "Deathgate", currentTime, 80, 140, 0 } ); registerScoreStandard( { lang, _( "Sir Galant" ), "Enroth", currentTime, 90, 130, 0 } ); registerScoreStandard( { lang, _( "Thundax" ), "Lost Continent", currentTime, 100, 120, 0 } ); registerScoreStandard( { lang, _( "Lord Haart" ), "Mountain King", currentTime, 120, 110, 0 } ); registerScoreStandard( { lang, _( "Ariel" ), "Pandemonium", currentTime, 140, 100, 0 } ); registerScoreStandard( { lang, _( "Rebecca" ), "Terra Firma", currentTime, 160, 90, 0 } ); registerScoreStandard( { lang, _( "Sandro" ), "The Clearing", currentTime, 180, 80, 0 } ); registerScoreStandard( { lang, _( "Crodo" ), "Vikings!", currentTime, 200, 70, 0 } ); registerScoreStandard( { lang, _( "Barock" ), "Wastelands", currentTime, 240, 60, 0 } ); } void HighScoreDataContainer::populateCampaignDefaultHighScores() { const uint32_t currentTime = HighscoreData::generateCompletionTime(); const std::string lang = fheroes2::getLanguageAbbreviation( fheroes2::getCurrentLanguage() ); registerScoreCampaign( { lang, _( "Antoine" ), Campaign::getCampaignName( Campaign::ROLAND_CAMPAIGN ), currentTime, 600, 600, 0 } ); registerScoreCampaign( { lang, _( "Astra" ), Campaign::getCampaignName( Campaign::ARCHIBALD_CAMPAIGN ), currentTime, 650, 650, 0 } ); registerScoreCampaign( { lang, _( "Agar" ), Campaign::getCampaignName( Campaign::ROLAND_CAMPAIGN ), currentTime, 700, 700, 0 } ); registerScoreCampaign( { lang, _( "Vatawna" ), Campaign::getCampaignName( Campaign::ARCHIBALD_CAMPAIGN ), currentTime, 750, 750, 0 } ); registerScoreCampaign( { lang, _( "Vesper" ), Campaign::getCampaignName( Campaign::ROLAND_CAMPAIGN ), currentTime, 800, 800, 0 } ); registerScoreCampaign( { lang, _( "Ambrose" ), Campaign::getCampaignName( Campaign::ARCHIBALD_CAMPAIGN ), currentTime, 850, 850, 0 } ); registerScoreCampaign( { lang, _( "Troyan" ), Campaign::getCampaignName( Campaign::ROLAND_CAMPAIGN ), currentTime, 900, 900, 0 } ); registerScoreCampaign( { lang, _( "Jojosh" ), Campaign::getCampaignName( Campaign::ARCHIBALD_CAMPAIGN ), currentTime, 1000, 1000, 0 } ); registerScoreCampaign( { lang, _( "Wrathmont" ), Campaign::getCampaignName( Campaign::ROLAND_CAMPAIGN ), currentTime, 2000, 2000, 0 } ); registerScoreCampaign( { lang, _( "Maximus" ), Campaign::getCampaignName( Campaign::ARCHIBALD_CAMPAIGN ), currentTime, 3000, 3000, 0 } ); } } fheroes2-1.0.12+dfsg/src/fheroes2/game/highscores.h000066400000000000000000000113651456075706000217730ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2HIGHSCORES_H #define H2HIGHSCORES_H #include #include #include #include #include #include "monster.h" class StreamBase; namespace fheroes2 { struct HighscoreData { public: HighscoreData() = default; HighscoreData( std::string languageAbbreviation_, std::string playerName_, std::string scenarioName_, const uint32_t completionTime_, const uint32_t dayCount_, const uint32_t rating_, const uint32_t mapSeed_ ) : languageAbbreviation( std::move( languageAbbreviation_ ) ) , playerName( std::move( playerName_ ) ) , scenarioName( std::move( scenarioName_ ) ) , completionTime( completionTime_ ) , dayCount( dayCount_ ) , rating( rating_ ) , mapSeed( mapSeed_ ) { // Do nothing. } HighscoreData( const HighscoreData & ) = default; HighscoreData( HighscoreData && ) = default; ~HighscoreData() = default; HighscoreData & operator=( const HighscoreData & data ) = default; HighscoreData & operator=( HighscoreData && data ) noexcept = default; bool operator==( const HighscoreData & other ) const { // Ignore player name and completion time. return scenarioName == other.scenarioName && dayCount == other.dayCount && rating == other.rating && mapSeed == other.mapSeed; } std::string languageAbbreviation; std::string playerName; std::string scenarioName; uint32_t completionTime{ 0 }; uint32_t dayCount{ 0 }; // Rating is used only for standalone scenarios. Campaigns do not use this member. uint32_t rating{ 0 }; // Map seed is used to identify the same game completion to avoid duplicates in highscores. uint32_t mapSeed{ 0 }; static uint32_t generateCompletionTime(); void loadV1( StreamBase & msg ); private: friend StreamBase & operator<<( StreamBase &, const HighscoreData & ); friend StreamBase & operator>>( StreamBase &, HighscoreData & ); }; class HighScoreDataContainer { public: bool load( const std::string & fileName ); bool save( const std::string & fileName ) const; int32_t registerScoreStandard( HighscoreData && data ); int32_t registerScoreCampaign( HighscoreData && data ); void populateStandardDefaultHighScores(); void populateCampaignDefaultHighScores(); const std::vector & getHighScoresStandard() const { return _highScoresStandard; } const std::vector & getHighScoresCampaign() const { return _highScoresCampaign; } static Monster getMonsterByRating( const size_t rating ); static Monster getMonsterByDay( const size_t dayCount ); void clear() { _highScoresStandard.clear(); _highScoresCampaign.clear(); } private: std::vector _highScoresStandard; std::vector _highScoresCampaign; }; StreamBase & operator<<( StreamBase & msg, const HighscoreData & data ); StreamBase & operator>>( StreamBase & msg, HighscoreData & data ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/gui/000077500000000000000000000000001456075706000173315ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/fheroes2/gui/cursor.cpp000066400000000000000000000171411456075706000213560ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "cursor.h" #include #include "agg_image.h" #include "icn.h" #include "image.h" #include "localevent.h" #include "screen.h" Cursor::Cursor() : theme( NONE ) , _monochromeCursorThemes( false ) { // Do nothing. } Cursor & Cursor::Get() { static Cursor _cursor; return _cursor; } int Cursor::Themes() const { assert( theme <= CURSOR_HERO_BOAT_ACTION_8 ); return theme; } bool Cursor::SetThemes( int name, bool force ) { if ( force || theme != name ) { theme = name; // Video pointer cannot be properly rendered in black-white so we have to force to use color cursor. int icnID = ( _monochromeCursorThemes && ( name != Cursor::POINTER_VIDEO ) ) ? ICN::MONO_CURSOR_ADVMBW : ICN::ADVMCO; switch ( 0xF000 & name ) { case 0x3000: icnID = _monochromeCursorThemes ? ICN::MONO_CURSOR_SPELBW : ICN::SPELCO; break; case 0x2000: icnID = _monochromeCursorThemes ? ICN::MONO_CURSOR_CMSSBW : ICN::CMSECO; break; case 0x4000: icnID = _monochromeCursorThemes ? ICN::MONO_CURSOR_ADVENTURE_MAP : ICN::COLOR_CURSOR_ADVENTURE_MAP; break; default: break; } const fheroes2::Sprite & spr = fheroes2::AGG::GetICN( icnID, 0xFF & name ); SetOffset( name, { ( spr.width() - spr.x() ) / 2, ( spr.height() - spr.y() ) / 2 } ); fheroes2::cursor().update( spr, -_offset.x, -_offset.y ); // Apply new offset. const fheroes2::Point & currentPos = LocalEvent::Get().GetMouseCursor(); Move( currentPos.x, currentPos.y ); return true; } return false; } void Cursor::setCustomImage( const fheroes2::Image & image, const fheroes2::Point & offset ) { theme = NONE; fheroes2::cursor().update( image, -offset.x, -offset.y ); // Immediately apply new mouse offset. const fheroes2::Point & currentPos = LocalEvent::Get().GetMouseCursor(); _offset = offset; Move( currentPos.x, currentPos.y ); } fheroes2::Rect Cursor::updateCursorPosition( const int32_t x, const int32_t y ) { if ( fheroes2::cursor().isSoftwareEmulation() ) { Cursor::Get().Move( x, y ); if ( fheroes2::cursor().isVisible() ) { return { x, y, 1, 1 }; } } return {}; } void Cursor::Move( int32_t x, int32_t y ) const { fheroes2::cursor().setPosition( x + _offset.x, y + _offset.y ); } void Cursor::SetOffset( int name, const fheroes2::Point & defaultOffset ) { switch ( name ) { case Cursor::POINTER: case Cursor::POINTER_VIDEO: case Cursor::WAR_POINTER: _offset = { 0, 0 }; break; case Cursor::CURSOR_HERO_FIGHT: case Cursor::CURSOR_HERO_FIGHT_2: case Cursor::CURSOR_HERO_FIGHT_3: case Cursor::CURSOR_HERO_FIGHT_4: case Cursor::CURSOR_HERO_FIGHT_5: case Cursor::CURSOR_HERO_FIGHT_6: case Cursor::CURSOR_HERO_FIGHT_7: case Cursor::CURSOR_HERO_FIGHT_8: _offset = { -10, -11 }; break; case Cursor::SCROLL_TOPRIGHT: case Cursor::SCROLL_RIGHT: _offset = { -15, 0 }; break; case Cursor::SCROLL_BOTTOM: case Cursor::SCROLL_BOTTOMLEFT: _offset = { 0, -15 }; break; case Cursor::SCROLL_BOTTOMRIGHT: case Cursor::SWORD_BOTTOMRIGHT: _offset = { -20, -20 }; break; case Cursor::SWORD_BOTTOMLEFT: _offset = { -5, -20 }; break; case Cursor::SWORD_TOPLEFT: _offset = { -5, -5 }; break; case Cursor::SWORD_TOPRIGHT: _offset = { -20, -5 }; break; case Cursor::SWORD_LEFT: _offset = { -5, -7 }; break; case Cursor::SWORD_RIGHT: _offset = { -25, -7 }; break; default: _offset = { -defaultOffset.x, -defaultOffset.y }; break; } } void Cursor::setVideoPlaybackCursor() { if ( fheroes2::cursor().isSoftwareEmulation() ) { SetThemes( Cursor::POINTER_VIDEO ); } } void Cursor::Refresh() { Get().SetThemes( Get().Themes(), true ); } int Cursor::DistanceThemes( const int theme, uint32_t distance ) { if ( distance == 0 ) { return POINTER; } if ( distance > 8 ) { distance = 8; } switch ( theme ) { case CURSOR_HERO_MOVE: case CURSOR_HERO_FIGHT: case CURSOR_HERO_BOAT: case CURSOR_HERO_ANCHOR: case CURSOR_HERO_MEET: case CURSOR_HERO_ACTION: case CURSOR_HERO_BOAT_ACTION: return theme + distance - 1; default: break; } return theme; } int Cursor::WithoutDistanceThemes( const int theme ) { if ( theme > CURSOR_HERO_MOVE && theme <= CURSOR_HERO_MOVE_8 ) { return CURSOR_HERO_MOVE; } if ( theme > CURSOR_HERO_FIGHT && theme <= CURSOR_HERO_FIGHT_8 ) { return CURSOR_HERO_FIGHT; } if ( theme > CURSOR_HERO_BOAT && theme <= CURSOR_HERO_BOAT_8 ) { return CURSOR_HERO_BOAT; } if ( theme > CURSOR_HERO_ANCHOR && theme <= CURSOR_HERO_ANCHOR_8 ) { return CURSOR_HERO_ANCHOR; } if ( theme > CURSOR_HERO_MEET && theme <= CURSOR_HERO_MEET_8 ) { return CURSOR_HERO_MEET; } if ( theme > CURSOR_HERO_ACTION && theme <= CURSOR_HERO_ACTION_8 ) { return CURSOR_HERO_ACTION; } if ( theme > CURSOR_HERO_BOAT_ACTION && theme <= CURSOR_HERO_BOAT_ACTION_8 ) { return CURSOR_HERO_BOAT_ACTION; } return theme; } CursorRestorer::CursorRestorer() : _theme( Cursor::Get().Themes() ) , _visible( fheroes2::cursor().isVisible() ) {} CursorRestorer::CursorRestorer( const bool visible, const int theme ) : CursorRestorer() { Cursor::Get().SetThemes( theme ); fheroes2::cursor().show( visible ); } CursorRestorer::~CursorRestorer() { fheroes2::Cursor & cursorRenderer = fheroes2::cursor(); Cursor & cursorIcon = Cursor::Get(); if ( cursorRenderer.isVisible() != _visible || cursorIcon.Themes() != _theme ) { cursorIcon.SetThemes( _theme ); cursorRenderer.show( _visible ); } } fheroes2-1.0.12+dfsg/src/fheroes2/gui/cursor.h000066400000000000000000000162341456075706000210250ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2CURSOR_H #define H2CURSOR_H #include #include "math_base.h" namespace fheroes2 { class Image; } class Cursor { public: enum CursorType : int { NONE = 0x0000, // ADVMCO.ICN POINTER = 0x1000, WAIT = 0x1001, HEROES = 0x1002, CASTLE = 0x1003, // 0x1004 to 0x101F are not in use anymore SCROLL_TOP = 0x1020, SCROLL_TOPRIGHT = 0x1021, SCROLL_RIGHT = 0x1022, SCROLL_BOTTOMRIGHT = 0x1023, SCROLL_BOTTOM = 0x1024, SCROLL_BOTTOMLEFT = 0x1025, SCROLL_LEFT = 0x1026, SCROLL_TOPLEFT = 0x1027, POINTER_VIDEO = 0x1028, // this cursor is used only for video playback // CMSECO.ICN WAR_NONE = 0x2000, WAR_MOVE = 0x2001, WAR_FLY = 0x2002, WAR_ARROW = 0x2003, WAR_HERO = 0x2004, WAR_INFO = 0x2005, WAR_POINTER = 0x2006, SWORD_TOPRIGHT = 0x2007, SWORD_RIGHT = 0x2008, SWORD_BOTTOMRIGHT = 0x2009, SWORD_BOTTOMLEFT = 0x200A, SWORD_LEFT = 0x200B, SWORD_TOPLEFT = 0x200C, SWORD_TOP = 0x200D, SWORD_BOTTOM = 0x200E, WAR_BROKENARROW = 0x200F, // SPELCO.ICN SP_NONE = WAR_NONE, SP_SLOW = 0x3001, SP_UNKNOWN = 0x3002, SP_CURSE = 0x3003, SP_LIGHTNINGBOLT = 0x3004, SP_CHAINLIGHTNING = 0x3005, SP_CURE = 0x3006, SP_BLESS = 0x3007, SP_FIREBALL = 0x3008, SP_FIREBLAST = 0x3009, SP_TELEPORT = 0x300A, SP_ELEMENTALSTORM = 0x300B, SP_RESURRECTTRUE = 0x300C, SP_RESURRECT = 0x300D, SP_HASTE = 0x300E, SP_SHIELD = 0x300F, SP_ARMAGEDDON = 0x3010, SP_ANTIMAGIC = 0x3011, SP_DISPEL = 0x3012, SP_BERSERKER = 0x3013, SP_PARALYZE = 0x3014, SP_BLIND = 0x3015, SP_HOLYWORD = 0x3016, SP_HOLYSHOUT = 0x3017, SP_METEORSHOWER = 0x3018, SP_ANIMATEDEAD = 0x3019, SP_MIRRORIMAGE = 0x301A, SP_BLOODLUST = 0x301B, SP_DEATHRIPPLE = 0x301C, SP_DEATHWAVE = 0x301D, SP_STEELSKIN = 0x301E, SP_STONESKIN = 0x301F, SP_DRAGONSLAYER = 0x3020, SP_EARTHQUAKE = 0x3021, SP_DISRUPTINGRAY = 0x3022, SP_COLDRING = 0x3023, SP_COLDRAY = 0x3024, SP_HYPNOTIZE = 0x3025, SP_ARROW = 0x3026 }; enum MapHeroCursor : int { CURSOR_HERO_MOVE = 0x4000, CURSOR_HERO_MOVE_2, CURSOR_HERO_MOVE_3, CURSOR_HERO_MOVE_4, CURSOR_HERO_MOVE_5, CURSOR_HERO_MOVE_6, CURSOR_HERO_MOVE_7, CURSOR_HERO_MOVE_8, CURSOR_HERO_FIGHT, CURSOR_HERO_FIGHT_2, CURSOR_HERO_FIGHT_3, CURSOR_HERO_FIGHT_4, CURSOR_HERO_FIGHT_5, CURSOR_HERO_FIGHT_6, CURSOR_HERO_FIGHT_7, CURSOR_HERO_FIGHT_8, CURSOR_HERO_BOAT, CURSOR_HERO_BOAT_2, CURSOR_HERO_BOAT_3, CURSOR_HERO_BOAT_4, CURSOR_HERO_BOAT_5, CURSOR_HERO_BOAT_6, CURSOR_HERO_BOAT_7, CURSOR_HERO_BOAT_8, CURSOR_HERO_ANCHOR, CURSOR_HERO_ANCHOR_2, CURSOR_HERO_ANCHOR_3, CURSOR_HERO_ANCHOR_4, CURSOR_HERO_ANCHOR_5, CURSOR_HERO_ANCHOR_6, CURSOR_HERO_ANCHOR_7, CURSOR_HERO_ANCHOR_8, CURSOR_HERO_MEET, CURSOR_HERO_MEET_2, CURSOR_HERO_MEET_3, CURSOR_HERO_MEET_4, CURSOR_HERO_MEET_5, CURSOR_HERO_MEET_6, CURSOR_HERO_MEET_7, CURSOR_HERO_MEET_8, CURSOR_HERO_ACTION, CURSOR_HERO_ACTION_2, CURSOR_HERO_ACTION_3, CURSOR_HERO_ACTION_4, CURSOR_HERO_ACTION_5, CURSOR_HERO_ACTION_6, CURSOR_HERO_ACTION_7, CURSOR_HERO_ACTION_8, CURSOR_HERO_BOAT_ACTION, CURSOR_HERO_BOAT_ACTION_2, CURSOR_HERO_BOAT_ACTION_3, CURSOR_HERO_BOAT_ACTION_4, CURSOR_HERO_BOAT_ACTION_5, CURSOR_HERO_BOAT_ACTION_6, CURSOR_HERO_BOAT_ACTION_7, CURSOR_HERO_BOAT_ACTION_8 }; Cursor( const Cursor & ) = delete; Cursor & operator=( const Cursor & ) = delete; static Cursor & Get(); // Returns a non-empty area if it should be updated while rendering. static fheroes2::Rect updateCursorPosition( const int32_t x, const int32_t y ); static int DistanceThemes( const int theme, uint32_t distance ); static int WithoutDistanceThemes( const int theme ); static void Refresh(); int Themes() const; bool SetThemes( int name, bool force = false ); void setCustomImage( const fheroes2::Image & image, const fheroes2::Point & offset ); // Only for software emulation. void setVideoPlaybackCursor(); // Do not call this method directly anywhere except Settings. void setMonochromeCursor( const bool enable ) { _monochromeCursorThemes = enable; } private: Cursor(); ~Cursor() = default; void SetOffset( int name, const fheroes2::Point & defaultOffset ); void Move( int32_t x, int32_t y ) const; int theme; fheroes2::Point _offset; bool _monochromeCursorThemes; }; class CursorRestorer { public: CursorRestorer(); CursorRestorer( const bool visible, const int theme ); // For convenience, also sets visibility and theme of the cursor CursorRestorer( const CursorRestorer & ) = delete; ~CursorRestorer(); CursorRestorer & operator=( const CursorRestorer & ) = delete; private: const int _theme; const bool _visible; }; #endif fheroes2-1.0.12+dfsg/src/fheroes2/gui/interface_base.cpp000066400000000000000000000034701456075706000227730ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "interface_base.h" #include "game.h" #include "ui_tool.h" namespace Interface { void Interface::BaseInterface::validateFadeInAndRender() { if ( Game::validateDisplayFadeIn() ) { fheroes2::fadeInDisplay(); setRedraw( REDRAW_GAMEAREA ); } else { fheroes2::Display::instance().render(); } } } fheroes2-1.0.12+dfsg/src/fheroes2/gui/interface_base.h000066400000000000000000000113711456075706000224370ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include "game_mode.h" #include "gamedefs.h" #include "interface_gamearea.h" #include "interface_radar.h" #include "math_base.h" #include "screen.h" namespace Interface { enum Redraw : uint32_t { // The next values are base for game/editor. // To render the cursor over the previously generated radar map image. REDRAW_RADAR_CURSOR = 0x01, // To render radar map fully or in ROI and then the cursor over it. REDRAW_RADAR = 0x02, REDRAW_BORDER = 0x04, REDRAW_GAMEAREA = 0x08, REDRAW_STATUS = 0x10, // The next values are base for game (Adventure Map) interface. REDRAW_HEROES = 0x20, REDRAW_CASTLES = 0x40, REDRAW_BUTTONS = 0x80, REDRAW_ICONS = REDRAW_HEROES | REDRAW_CASTLES, // The next value is base for Map Editor interface. REDRAW_PANEL = 0x100, REDRAW_ALL = 0x1FF }; class BaseInterface { public: explicit BaseInterface( const bool isEditor_ ) : _gameArea( *this ) , _radar( *this ) , _isEditor( isEditor_ ) { // Do nothing } virtual ~BaseInterface() = default; virtual void redraw( const uint32_t force ) = 0; bool needRedraw() const { return _redraw != 0; } void setRedraw( const uint32_t r ) { _redraw |= r; } uint32_t getRedrawMask() const { return _redraw; } static bool isScrollLeft( const fheroes2::Point & cursorPos ) { return cursorPos.x < BORDERWIDTH; } static bool isScrollRight( const fheroes2::Point & cursorPos ) { const fheroes2::Display & display = fheroes2::Display::instance(); return cursorPos.x >= display.width() - BORDERWIDTH; } static bool isScrollTop( const fheroes2::Point & cursorPos ) { return cursorPos.y < BORDERWIDTH; } static bool isScrollBottom( const fheroes2::Point & cursorPos ) { const fheroes2::Display & display = fheroes2::Display::instance(); return cursorPos.y >= display.height() - BORDERWIDTH; } GameArea & getGameArea() { return _gameArea; } Radar & getRadar() { return _radar; } static fheroes2::GameMode EventExit(); virtual bool useMouseDragMovement() { return true; } virtual void mouseCursorAreaClickLeft( const int32_t tileIndex ) = 0; virtual void mouseCursorAreaPressRight( const int32_t tileIndex ) const = 0; // Regenerates the game area and updates the panel positions depending on the UI settings virtual void reset() = 0; virtual void updateCursor( const int32_t tileIndex ) = 0; bool isEditor() const { return _isEditor; } protected: // If display fade-in state is set reset it to false and fade-in the full display image. Otherwise render full display image without fade-in. void validateFadeInAndRender(); GameArea _gameArea; Radar _radar; uint32_t _redraw{ 0 }; const bool _isEditor{ false }; }; } fheroes2-1.0.12+dfsg/src/fheroes2/gui/interface_border.cpp000066400000000000000000000502671456075706000233440ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include "agg_image.h" #include "gamedefs.h" #include "icn.h" #include "image.h" #include "interface_border.h" #include "localevent.h" #include "maps.h" #include "screen.h" #include "settings.h" #include "ui_tool.h" namespace { void repeatPattern( const fheroes2::Image & in, int32_t inX, int32_t inY, int32_t inWidth, int32_t inHeight, fheroes2::Image & out, int32_t outX, int32_t outY, int32_t width, int32_t height ) { if ( inX < 0 || inY < 0 || outX < 0 || outY < 0 || inWidth <= 0 || inHeight <= 0 || width <= 0 || height <= 0 ) return; const int32_t countX = width / inWidth; const int32_t countY = height / inHeight; const int32_t restWidth = width % inWidth; const int32_t restHeight = height % inHeight; for ( int32_t y = 0; y < countY; ++y ) { for ( int32_t x = 0; x < countX; ++x ) { fheroes2::Blit( in, inX, inY, out, outX + x * inWidth, outY + y * inHeight, inWidth, inHeight ); } if ( restWidth != 0 ) { fheroes2::Blit( in, inX, inY, out, outX + width - restWidth, outY + y * inHeight, restWidth, inHeight ); } } if ( restHeight != 0 ) { for ( int32_t x = 0; x < countX; ++x ) { fheroes2::Blit( in, inX, inY, out, outX + x * inWidth, outY + height - restHeight, inWidth, restHeight ); } if ( restWidth != 0 ) { fheroes2::Blit( in, inX, inY, out, outX + width - restWidth, outY + height - restHeight, restWidth, restHeight ); } } } } void Interface::GameBorderRedraw( const bool viewWorldMode ) { const Settings & conf = Settings::Get(); if ( conf.isHideInterfaceEnabled() && !viewWorldMode ) return; const bool isEvilInterface = conf.isEvilInterfaceEnabled(); fheroes2::Display & display = fheroes2::Display::instance(); const int32_t displayWidth = display.width(); const int32_t displayHeight = display.height(); const int32_t extraDisplayWidth = displayWidth - fheroes2::Display::DEFAULT_WIDTH; const int32_t extraDisplayHeight = displayHeight - fheroes2::Display::DEFAULT_HEIGHT; const int32_t topRepeatCount = extraDisplayWidth > 0 ? extraDisplayWidth / TILEWIDTH : 0; const int32_t topRepeatWidth = ( topRepeatCount + 1 ) * TILEWIDTH; const int32_t vertRepeatCount = extraDisplayHeight > 0 ? extraDisplayHeight / TILEWIDTH : 0; const int32_t iconsCount = vertRepeatCount > 3 ? 8 : ( vertRepeatCount < 3 ? 4 : 7 ); const int32_t vertRepeatHeight = ( vertRepeatCount + 1 ) * TILEWIDTH; const int32_t vertRepeatHeightTop = ( iconsCount - 3 ) * TILEWIDTH; const int32_t vertRepeatHeightBottom = vertRepeatHeight - vertRepeatHeightTop; const int32_t topPadWidth = extraDisplayWidth % TILEWIDTH; // top and bottom padding is split in two halves around the repeated "tiles" const int32_t topPadWidthLeft = topPadWidth / 2; const int32_t topPadWidthRight = topPadWidth - topPadWidthLeft; int32_t bottomTileWidth; int32_t bottomRepeatCount; if ( isEvilInterface ) { bottomTileWidth = 7; // width of a single bone piece bottomRepeatCount = extraDisplayWidth > 0 ? extraDisplayWidth / bottomTileWidth : 0; } else { bottomTileWidth = TILEWIDTH; bottomRepeatCount = topRepeatCount; } const int32_t bottomRepeatWidth = ( bottomRepeatCount + 1 ) * bottomTileWidth; const int32_t bottomPadWidth = extraDisplayWidth % bottomTileWidth; const int32_t bottomPadWidthLeft = bottomPadWidth / 2; const int32_t bottomPadWidthRight = bottomPadWidth - bottomPadWidthLeft; const int32_t vertPadHeight = extraDisplayHeight % TILEWIDTH; fheroes2::Rect srcrt; fheroes2::Point dstpt; const fheroes2::Sprite & icnadv = fheroes2::AGG::GetICN( isEvilInterface ? ICN::ADVBORDE : ICN::ADVBORD, 0 ); // TOP BORDER srcrt.x = 0; srcrt.y = 0; srcrt.width = isEvilInterface ? 153 : 193; srcrt.height = BORDERWIDTH; dstpt.x = srcrt.x; dstpt.y = srcrt.y; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); srcrt.x += srcrt.width; srcrt.width = 6; dstpt.x = srcrt.x; repeatPattern( icnadv, srcrt.x, srcrt.y, srcrt.width, srcrt.height, display, dstpt.x, dstpt.y, srcrt.width + topPadWidthLeft, BORDERWIDTH ); dstpt.x += srcrt.width + topPadWidthLeft; srcrt.x += srcrt.width; srcrt.width = isEvilInterface ? 64 : 24; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); dstpt.x += srcrt.width; srcrt.x += srcrt.width; srcrt.width = TILEWIDTH; repeatPattern( icnadv, srcrt.x, srcrt.y, srcrt.width, srcrt.height, display, dstpt.x, dstpt.y, topRepeatWidth, BORDERWIDTH ); dstpt.x += topRepeatWidth; srcrt.x += TILEWIDTH; srcrt.width = isEvilInterface ? 65 : 25; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); dstpt.x += srcrt.width; srcrt.x += srcrt.width; srcrt.width = 6; repeatPattern( icnadv, srcrt.x, srcrt.y, srcrt.width, srcrt.height, display, dstpt.x, dstpt.y, srcrt.width + topPadWidthRight, BORDERWIDTH ); dstpt.x += srcrt.width + topPadWidthRight; srcrt.x += srcrt.width; srcrt.width = icnadv.width() - srcrt.x; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); // LEFT BORDER srcrt.x = 0; srcrt.y = BORDERWIDTH; srcrt.width = BORDERWIDTH; srcrt.height = 255 - BORDERWIDTH; dstpt.x = srcrt.x; dstpt.y = srcrt.y; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); dstpt.y += srcrt.height; srcrt.y += srcrt.height; if ( isEvilInterface ) { srcrt.height = TILEWIDTH; repeatPattern( icnadv, srcrt.x, srcrt.y, srcrt.width, srcrt.height, display, dstpt.x, dstpt.y, BORDERWIDTH, vertRepeatHeightTop ); dstpt.y += vertRepeatHeightTop; srcrt.y += TILEWIDTH; srcrt.width = BORDERWIDTH; srcrt.height = 35; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); dstpt.y += srcrt.height; srcrt.y += srcrt.height; srcrt.height = 6; repeatPattern( icnadv, srcrt.x, srcrt.y, srcrt.width, srcrt.height, display, dstpt.x, dstpt.y, BORDERWIDTH, srcrt.height + vertPadHeight ); dstpt.y += srcrt.height + vertPadHeight; srcrt.y += srcrt.height; srcrt.height = 103; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); dstpt.y += srcrt.height; srcrt.y += srcrt.height; srcrt.height = TILEWIDTH; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); dstpt.y += TILEWIDTH; repeatPattern( icnadv, srcrt.x, srcrt.y, srcrt.width, srcrt.height, display, dstpt.x, dstpt.y, BORDERWIDTH, vertRepeatHeightBottom ); dstpt.y += vertRepeatHeightBottom; srcrt.y += TILEWIDTH; } else { srcrt.height = TILEWIDTH; repeatPattern( icnadv, srcrt.x, srcrt.y, srcrt.width, srcrt.height, display, dstpt.x, dstpt.y, BORDERWIDTH, vertRepeatHeight ); dstpt.y += vertRepeatHeight; srcrt.y += TILEWIDTH; srcrt.height = 125; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); dstpt.y += srcrt.height; srcrt.y += srcrt.height; srcrt.height = 4; repeatPattern( icnadv, srcrt.x, srcrt.y, srcrt.width, srcrt.height, display, dstpt.x, dstpt.y, BORDERWIDTH, srcrt.height + vertPadHeight ); dstpt.y += srcrt.height + vertPadHeight; srcrt.y += srcrt.height; } srcrt.height = icnadv.height() - BORDERWIDTH - srcrt.y; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); // MIDDLE BORDER srcrt.x = icnadv.width() - RADARWIDTH - 2 * BORDERWIDTH; srcrt.y = BORDERWIDTH; srcrt.width = BORDERWIDTH; srcrt.height = 255 - BORDERWIDTH; dstpt.x = displayWidth - RADARWIDTH - 2 * BORDERWIDTH; dstpt.y = srcrt.y; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); dstpt.y += srcrt.height; srcrt.y += srcrt.height; srcrt.height = TILEWIDTH; repeatPattern( icnadv, srcrt.x, srcrt.y, srcrt.width, srcrt.height, display, dstpt.x, dstpt.y, BORDERWIDTH, vertRepeatHeightTop ); dstpt.y += vertRepeatHeightTop; srcrt.y += TILEWIDTH; srcrt.height = isEvilInterface ? 35 : 50; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); // hide embranchment if ( viewWorldMode ) { fheroes2::Rect fixrt( 478, isEvilInterface ? 137 : 345, 3, isEvilInterface ? 15 : 20 ); fheroes2::Point fixpt( dstpt.x + 14, dstpt.y + 18 ); fheroes2::Blit( icnadv, fixrt.x, fixrt.y, display, fixpt.x, fixpt.y, fixrt.width, fixrt.height ); } dstpt.y += srcrt.height; srcrt.y += srcrt.height; if ( isEvilInterface ) { srcrt.height = 6; repeatPattern( icnadv, srcrt.x, srcrt.y, srcrt.width, srcrt.height, display, dstpt.x, dstpt.y, BORDERWIDTH, srcrt.height + vertPadHeight ); dstpt.y += srcrt.height + vertPadHeight; srcrt.y += srcrt.height; srcrt.height = 103; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); dstpt.y += srcrt.height; srcrt.y += srcrt.height; srcrt.height = TILEWIDTH; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); dstpt.y += TILEWIDTH; repeatPattern( icnadv, srcrt.x, srcrt.y, srcrt.width, srcrt.height, display, dstpt.x, dstpt.y, BORDERWIDTH, vertRepeatHeightBottom ); dstpt.y += vertRepeatHeightBottom; srcrt.y += TILEWIDTH; } else { srcrt.height = TILEWIDTH; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); dstpt.y += TILEWIDTH; repeatPattern( icnadv, srcrt.x, srcrt.y, srcrt.width, srcrt.height, display, dstpt.x, dstpt.y, BORDERWIDTH, vertRepeatHeightBottom ); dstpt.y += vertRepeatHeightBottom; srcrt.y += TILEWIDTH; srcrt.height = 43; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); dstpt.y += srcrt.height; srcrt.y += srcrt.height; srcrt.height = 8; // middle border is special on good interface due to all the green leaves repeatPattern( icnadv, srcrt.x, srcrt.y, srcrt.width, srcrt.height, display, dstpt.x, dstpt.y, BORDERWIDTH, srcrt.height + vertPadHeight ); dstpt.y += srcrt.height + vertPadHeight; srcrt.y += srcrt.height; } srcrt.height = icnadv.height() - BORDERWIDTH - srcrt.y; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); // RIGHT BORDER srcrt.x = icnadv.width() - BORDERWIDTH; srcrt.y = BORDERWIDTH; srcrt.width = BORDERWIDTH; srcrt.height = 255 - BORDERWIDTH; dstpt.x = displayWidth - BORDERWIDTH; dstpt.y = srcrt.y; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); dstpt.y += srcrt.height; srcrt.y += srcrt.height; srcrt.height = TILEWIDTH; repeatPattern( icnadv, srcrt.x, srcrt.y, srcrt.width, srcrt.height, display, dstpt.x, dstpt.y, BORDERWIDTH, vertRepeatHeightTop ); dstpt.y += vertRepeatHeightTop; srcrt.y += TILEWIDTH; srcrt.height = isEvilInterface ? 35 : 50; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); // hide embranchment if ( viewWorldMode ) { fheroes2::Rect fixrt( 624, isEvilInterface ? 139 : 345, 3, isEvilInterface ? 15 : 20 ); fheroes2::Point fixpt( dstpt.x, dstpt.y + 18 ); fheroes2::Blit( icnadv, fixrt.x, fixrt.y, display, fixpt.x, fixpt.y, fixrt.width, fixrt.height ); } dstpt.y += srcrt.height; srcrt.y += srcrt.height; if ( isEvilInterface ) { srcrt.height = 6; repeatPattern( icnadv, srcrt.x, srcrt.y, srcrt.width, srcrt.height, display, dstpt.x, dstpt.y, BORDERWIDTH, srcrt.height + vertPadHeight ); dstpt.y += srcrt.height + vertPadHeight; srcrt.y += srcrt.height; srcrt.height = 103; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); dstpt.y += srcrt.height; srcrt.y += srcrt.height; srcrt.height = TILEWIDTH; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); dstpt.y += TILEWIDTH; repeatPattern( icnadv, srcrt.x, srcrt.y, srcrt.width, srcrt.height, display, dstpt.x, dstpt.y, BORDERWIDTH, vertRepeatHeightBottom ); dstpt.y += vertRepeatHeightBottom; srcrt.y += TILEWIDTH; } else { srcrt.height = TILEWIDTH; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); dstpt.y += TILEWIDTH; repeatPattern( icnadv, srcrt.x, srcrt.y, srcrt.width, srcrt.height, display, dstpt.x, dstpt.y, BORDERWIDTH, vertRepeatHeightBottom ); dstpt.y += vertRepeatHeightBottom; srcrt.y += TILEWIDTH; srcrt.height = 43; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); dstpt.y += srcrt.height; srcrt.y += srcrt.height; srcrt.height = 4; repeatPattern( icnadv, srcrt.x, srcrt.y, srcrt.width, srcrt.height, display, dstpt.x, dstpt.y, BORDERWIDTH, srcrt.height + vertPadHeight ); dstpt.y += srcrt.height + vertPadHeight; srcrt.y += srcrt.height; } srcrt.height = icnadv.height() - BORDERWIDTH - srcrt.y; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); // BOTTOM BORDER srcrt.x = 0; srcrt.y = icnadv.height() - BORDERWIDTH; srcrt.width = isEvilInterface ? 129 : 193; srcrt.height = BORDERWIDTH; dstpt.x = srcrt.x; dstpt.y = displayHeight - BORDERWIDTH; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); dstpt.x += srcrt.width; srcrt.x += srcrt.width; srcrt.width = 6; repeatPattern( icnadv, srcrt.x, srcrt.y, srcrt.width, srcrt.height, display, dstpt.x, dstpt.y, srcrt.width + bottomPadWidthLeft, BORDERWIDTH ); dstpt.x += srcrt.width + bottomPadWidthLeft; srcrt.x += srcrt.width; srcrt.width = isEvilInterface ? 90 : 24; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); dstpt.x += srcrt.width; srcrt.x += srcrt.width; srcrt.width = bottomTileWidth; repeatPattern( icnadv, srcrt.x, srcrt.y, srcrt.width, srcrt.height, display, dstpt.x, dstpt.y, bottomRepeatWidth, BORDERWIDTH ); dstpt.x += bottomRepeatWidth; srcrt.x += srcrt.width; srcrt.width = isEvilInterface ? 86 : 25; // evil bottom border is asymmetric fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); dstpt.x += srcrt.width; srcrt.x += srcrt.width; srcrt.width = 6; repeatPattern( icnadv, srcrt.x, srcrt.y, srcrt.width, srcrt.height, display, dstpt.x, dstpt.y, srcrt.width + bottomPadWidthRight, BORDERWIDTH ); dstpt.x += srcrt.width + bottomPadWidthRight; srcrt.x += srcrt.width; srcrt.width = icnadv.width() - srcrt.x; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); // ICON BORDER srcrt.x = icnadv.width() - RADARWIDTH - BORDERWIDTH; srcrt.y = RADARWIDTH + BORDERWIDTH; srcrt.width = RADARWIDTH; srcrt.height = BORDERWIDTH; dstpt.x = displayWidth - RADARWIDTH - BORDERWIDTH; dstpt.y = srcrt.y; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); if ( !viewWorldMode ) { dstpt.y = srcrt.y + BORDERWIDTH + iconsCount * 32; srcrt.y = srcrt.y + BORDERWIDTH + 4 * 32; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); } } Interface::BorderWindow::BorderWindow( const fheroes2::Rect & rt ) : area( rt ) {} const fheroes2::Rect & Interface::BorderWindow::GetRect() const { return Settings::Get().isHideInterfaceEnabled() && border.isValid() ? border.GetRect() : GetArea(); } void Interface::BorderWindow::Redraw() const { Dialog::FrameBorder::RenderRegular( border.GetRect() ); } void Interface::BorderWindow::SetPosition( int32_t px, int32_t py, uint32_t pw, uint32_t ph ) { area.width = pw; area.height = ph; SetPosition( px, py ); } void Interface::BorderWindow::SetPosition( int32_t px, int32_t py ) { if ( Settings::Get().isHideInterfaceEnabled() ) { const fheroes2::Display & display = fheroes2::Display::instance(); px = std::max( 0, std::min( px, display.width() - ( area.width + border.BorderWidth() * 2 ) ) ); py = std::max( 0, std::min( py, display.height() - ( area.height + border.BorderHeight() * 2 ) ) ); area.x = px + border.BorderWidth(); area.y = py + border.BorderHeight(); border.SetPosition( px, py, area.width, area.height ); SavePosition(); } else { area.x = px; area.y = py; } } bool Interface::BorderWindow::QueueEventProcessing() { const Settings & conf = Settings::Get(); LocalEvent & le = LocalEvent::Get(); if ( conf.isHideInterfaceEnabled() && le.MousePressLeft( border.GetTop() ) ) { fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::Point & mp = le.GetMouseCursor(); const fheroes2::Rect & pos = GetRect(); fheroes2::MovableSprite moveIndicator( pos.width, pos.height, pos.x, pos.y ); moveIndicator.reset(); fheroes2::DrawBorder( moveIndicator, fheroes2::GetColorId( 0xD0, 0xC0, 0x48 ), 6 ); const int32_t ox = mp.x - pos.x; const int32_t oy = mp.y - pos.y; moveIndicator.setPosition( pos.x, pos.y ); moveIndicator.redraw(); display.render(); while ( le.HandleEvents() && le.MousePressLeft() ) { if ( le.MouseMotion() ) { moveIndicator.setPosition( mp.x - ox, mp.y - oy ); display.render(); } } SetPos( mp.x - ox, mp.y - oy ); return true; } return false; } fheroes2-1.0.12+dfsg/src/fheroes2/gui/interface_border.h000066400000000000000000000051651456075706000230060ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2INTERFACE_BORDER_H #define H2INTERFACE_BORDER_H #include #include "dialog.h" #include "math_base.h" namespace Interface { void GameBorderRedraw( const bool viewWorldMode ); class BorderWindow { public: explicit BorderWindow( const fheroes2::Rect & ); BorderWindow( const BorderWindow & ) = delete; virtual ~BorderWindow() = default; BorderWindow & operator=( const BorderWindow & ) = delete; virtual void SetPos( int32_t, int32_t ) = 0; virtual void SavePosition() = 0; bool QueueEventProcessing(); const fheroes2::Rect & GetRect() const; const fheroes2::Rect & GetArea() const { return area; } protected: void Redraw() const; void SetPosition( int32_t, int32_t, uint32_t, uint32_t ); void SetPosition( int32_t, int32_t ); fheroes2::Rect area; Dialog::FrameBorder border; }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/gui/interface_buttons.cpp000066400000000000000000000234641456075706000235640ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "interface_buttons.h" #include #include #include "dialog.h" #include "game_interface.h" #include "heroes.h" #include "icn.h" #include "interface_base.h" #include "kingdom.h" #include "localevent.h" #include "mp2.h" #include "route.h" #include "settings.h" #include "translations.h" #include "ui_dialog.h" #include "world.h" Interface::ButtonsArea::ButtonsArea( AdventureMap & basic ) : BorderWindow( { 0, 0, 144, 72 } ) , interface( basic ) {} void Interface::ButtonsArea::SavePosition() { Settings & conf = Settings::Get(); conf.SetPosButtons( GetRect().getPosition() ); conf.Save( Settings::configFileName ); } void Interface::ButtonsArea::SetRedraw() const { interface.setRedraw( REDRAW_BUTTONS ); } void Interface::ButtonsArea::SetPos( int32_t ox, int32_t oy ) { BorderWindow::SetPosition( ox, oy ); const int icnbtn = Settings::Get().isEvilInterfaceEnabled() ? ICN::ADVEBTNS : ICN::ADVBTNS; buttonNextHero.setICNInfo( icnbtn, 0, 1 ); buttonHeroMovement.setICNInfo( icnbtn, 2, 3 ); buttonKingdom.setICNInfo( icnbtn, 4, 5 ); buttonSpell.setICNInfo( icnbtn, 6, 7 ); buttonEndTurn.setICNInfo( icnbtn, 8, 9 ); buttonAdventure.setICNInfo( icnbtn, 10, 11 ); buttonFile.setICNInfo( icnbtn, 12, 13 ); buttonSystem.setICNInfo( icnbtn, 14, 15 ); SetButtonStatus(); ox = GetArea().x; oy = GetArea().y; // Top row buttonNextHero.setPosition( ox, oy ); nextHeroRect = buttonNextHero.area(); buttonHeroMovement.setPosition( nextHeroRect.x + nextHeroRect.width, oy ); heroMovementRect = buttonHeroMovement.area(); buttonKingdom.setPosition( heroMovementRect.x + heroMovementRect.width, oy ); kingdomRect = buttonKingdom.area(); buttonSpell.setPosition( kingdomRect.x + kingdomRect.width, oy ); spellRect = buttonSpell.area(); // Bottom row oy = nextHeroRect.y + nextHeroRect.height; buttonEndTurn.setPosition( ox, oy ); endTurnRect = buttonEndTurn.area(); buttonAdventure.setPosition( endTurnRect.x + endTurnRect.width, oy ); adventureRect = buttonAdventure.area(); buttonFile.setPosition( adventureRect.x + adventureRect.width, oy ); fileRect = buttonFile.area(); buttonSystem.setPosition( fileRect.x + fileRect.width, oy ); systemRect = buttonSystem.area(); } void Interface::ButtonsArea::_redraw() { const Settings & conf = Settings::Get(); if ( !conf.isHideInterfaceEnabled() || conf.ShowButtons() ) { if ( conf.isHideInterfaceEnabled() ) BorderWindow::Redraw(); SetButtonStatus(); buttonNextHero.draw(); buttonHeroMovement.draw(); buttonKingdom.draw(); buttonSpell.draw(); buttonEndTurn.draw(); buttonAdventure.draw(); buttonFile.draw(); buttonSystem.draw(); } } void Interface::ButtonsArea::ResetButtons() { buttonNextHero.drawOnRelease(); buttonHeroMovement.drawOnRelease(); buttonKingdom.drawOnRelease(); buttonSpell.drawOnRelease(); buttonEndTurn.drawOnRelease(); buttonAdventure.drawOnRelease(); buttonFile.drawOnRelease(); buttonSystem.drawOnRelease(); } fheroes2::GameMode Interface::ButtonsArea::QueueEventProcessing() { LocalEvent & le = LocalEvent::Get(); le.MousePressLeft( nextHeroRect ) ? buttonNextHero.drawOnPress() : buttonNextHero.drawOnRelease(); le.MousePressLeft( heroMovementRect ) ? buttonHeroMovement.drawOnPress() : buttonHeroMovement.drawOnRelease(); le.MousePressLeft( kingdomRect ) ? buttonKingdom.drawOnPress() : buttonKingdom.drawOnRelease(); le.MousePressLeft( spellRect ) ? buttonSpell.drawOnPress() : buttonSpell.drawOnRelease(); le.MousePressLeft( endTurnRect ) ? buttonEndTurn.drawOnPress() : buttonEndTurn.drawOnRelease(); le.MousePressLeft( adventureRect ) ? buttonAdventure.drawOnPress() : buttonAdventure.drawOnRelease(); le.MousePressLeft( fileRect ) ? buttonFile.drawOnPress() : buttonFile.drawOnRelease(); le.MousePressLeft( systemRect ) ? buttonSystem.drawOnPress() : buttonSystem.drawOnRelease(); fheroes2::GameMode res = fheroes2::GameMode::CANCEL; // Move border window if ( Settings::Get().ShowButtons() && BorderWindow::QueueEventProcessing() ) { SetRedraw(); } else if ( buttonNextHero.isEnabled() && le.MouseClickLeft( nextHeroRect ) ) { interface.EventNextHero(); } else if ( buttonHeroMovement.isEnabled() && le.MouseClickLeft( heroMovementRect ) ) { res = interface.EventHeroMovement(); } else if ( buttonHeroMovement.isEnabled() && le.MouseLongPressLeft( heroMovementRect ) ) { interface.EventResetHeroPath(); } else if ( le.MouseClickLeft( kingdomRect ) ) { interface.EventKingdomInfo(); } else if ( buttonSpell.isEnabled() && le.MouseClickLeft( spellRect ) ) { interface.EventCastSpell(); } else if ( le.MouseClickLeft( endTurnRect ) ) { res = interface.EventEndTurn(); } else if ( le.MouseClickLeft( adventureRect ) ) { res = interface.EventAdventureDialog(); } else if ( le.MouseClickLeft( fileRect ) ) { res = interface.EventFileDialog(); } else if ( le.MouseClickLeft( systemRect ) ) { interface.EventSystemDialog(); } if ( le.MousePressRight( nextHeroRect ) ) { fheroes2::showStandardTextMessage( _( "Next Hero" ), _( "Select the next Hero." ), Dialog::ZERO ); } else if ( le.MousePressRight( heroMovementRect ) ) { fheroes2::showStandardTextMessage( _( "Hero Movement" ), _( "Start the Hero's movement along the current path or re-visit the object occupied by the Hero. Press and hold this button to reset the Hero's path." ), Dialog::ZERO ); } else if ( le.MousePressRight( kingdomRect ) ) { fheroes2::showStandardTextMessage( _( "Kingdom Summary" ), _( "View a Summary of your Kingdom." ), Dialog::ZERO ); } else if ( le.MousePressRight( spellRect ) ) { fheroes2::showStandardTextMessage( _( "Cast Spell" ), _( "Cast an adventure spell." ), Dialog::ZERO ); } else if ( le.MousePressRight( endTurnRect ) ) { fheroes2::showStandardTextMessage( _( "End Turn" ), _( "End your turn and left the computer take its turn." ), Dialog::ZERO ); } else if ( le.MousePressRight( adventureRect ) ) { fheroes2::showStandardTextMessage( _( "Adventure Options" ), _( "Bring up the adventure options menu." ), Dialog::ZERO ); } else if ( le.MousePressRight( fileRect ) ) { fheroes2::showStandardTextMessage( _( "File Options" ), _( "Bring up the file options menu, allowing you to load, save, start a new game or quit." ), Dialog::ZERO ); } else if ( le.MousePressRight( systemRect ) ) { fheroes2::showStandardTextMessage( _( "System Options" ), _( "Bring up the system options menu, allowing you to customize your game." ), Dialog::ZERO ); } return res; } void Interface::ButtonsArea::SetButtonStatus() { Heroes * currentHero = GetFocusHeroes(); if ( currentHero && currentHero->GetPath().isValidForMovement() && currentHero->MayStillMove( false, true ) ) { buttonHeroMovement.setICNIndexes( 2, 3 ); buttonHeroMovement.enable(); } else if ( currentHero && MP2::isActionObject( currentHero->getObjectTypeUnderHero(), currentHero->isShipMaster() ) ) { buttonHeroMovement.setICNIndexes( 16, 17 ); buttonHeroMovement.enable(); } else { buttonHeroMovement.setICNIndexes( 2, 3 ); buttonHeroMovement.disable(); } if ( currentHero && currentHero->HaveSpellBook() && currentHero->MayCastAdventureSpells() ) { buttonSpell.enable(); } else { buttonSpell.disable(); } const Kingdom & kingdom = world.GetKingdom( Settings::Get().CurrentColor() ); const VecHeroes & heroes = kingdom.GetHeroes(); const bool isMovableHeroPresent = std::any_of( heroes.begin(), heroes.end(), []( const Heroes * hero ) { assert( hero != nullptr ); return hero->MayStillMove( false, false ); } ); if ( isMovableHeroPresent ) { buttonNextHero.enable(); } else { buttonNextHero.disable(); } } fheroes2-1.0.12+dfsg/src/fheroes2/gui/interface_buttons.h000066400000000000000000000064701456075706000232270ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2INTERFACE_BUTTONS_H #define H2INTERFACE_BUTTONS_H #include #include "game_mode.h" #include "interface_border.h" #include "math_base.h" #include "ui_button.h" namespace Interface { class AdventureMap; class ButtonsArea final : public BorderWindow { public: explicit ButtonsArea( AdventureMap & ); ButtonsArea( const ButtonsArea & ) = delete; ~ButtonsArea() override = default; ButtonsArea & operator=( const ButtonsArea & ) = delete; void SetPos( int32_t ox, int32_t oy ) override; void SavePosition() override; void SetRedraw() const; fheroes2::GameMode QueueEventProcessing(); void ResetButtons(); // Do not call this method directly, use Interface::AdventureMap::redraw() instead to avoid issues in the "no interface" mode. // The name of this method starts from _ on purpose to do not mix with other public methods. void _redraw(); private: void SetButtonStatus(); AdventureMap & interface; fheroes2::Button buttonNextHero; fheroes2::Button buttonHeroMovement; fheroes2::Button buttonKingdom; fheroes2::Button buttonSpell; fheroes2::Button buttonEndTurn; fheroes2::Button buttonAdventure; fheroes2::Button buttonFile; fheroes2::Button buttonSystem; fheroes2::Rect nextHeroRect; fheroes2::Rect heroMovementRect; fheroes2::Rect kingdomRect; fheroes2::Rect spellRect; fheroes2::Rect endTurnRect; fheroes2::Rect adventureRect; fheroes2::Rect fileRect; fheroes2::Rect systemRect; }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/gui/interface_cpanel.cpp000066400000000000000000000102711456075706000233200ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "interface_cpanel.h" #include #include "agg_image.h" #include "game_interface.h" #include "icn.h" #include "image.h" #include "localevent.h" #include "screen.h" #include "settings.h" Interface::ControlPanel::ControlPanel( AdventureMap & basic ) : interface( basic ) { width = 180; height = 36; rt_radar.width = 36; rt_radar.height = 36; rt_icons.width = 36; rt_icons.height = 36; rt_buttons.width = 36; rt_buttons.height = 36; rt_status.width = 36; rt_status.height = 36; rt_end.width = 36; rt_end.height = 36; ResetTheme(); } void Interface::ControlPanel::ResetTheme() { const int icn = Settings::Get().isEvilInterfaceEnabled() ? ICN::ADVEBTNS : ICN::ADVBTNS; _buttons.reset( new Buttons( fheroes2::AGG::GetICN( icn, 4 ), fheroes2::AGG::GetICN( icn, 0 ), fheroes2::AGG::GetICN( icn, 12 ), fheroes2::AGG::GetICN( icn, 10 ), fheroes2::AGG::GetICN( icn, 8 ) ) ); } const fheroes2::Rect & Interface::ControlPanel::GetArea() const { return *this; } void Interface::ControlPanel::SetPos( int32_t ox, int32_t oy ) { x = ox; y = oy; rt_radar.x = x; rt_radar.y = y; rt_icons.x = x + 36; rt_icons.y = y; rt_buttons.x = x + 72; rt_buttons.y = y; rt_status.x = x + 108; rt_status.y = y; rt_end.x = x + 144; rt_end.y = y; } void Interface::ControlPanel::_redraw() const { assert( _buttons ); fheroes2::Display & display = fheroes2::Display::instance(); const uint8_t alpha = 128; fheroes2::AlphaBlit( _buttons->radar, display, rt_radar.x, rt_radar.y, alpha ); fheroes2::AlphaBlit( _buttons->icons, display, rt_icons.x, rt_icons.y, alpha ); fheroes2::AlphaBlit( _buttons->buttons, display, rt_buttons.x, rt_buttons.y, alpha ); fheroes2::AlphaBlit( _buttons->status, display, rt_status.x, rt_status.y, alpha ); fheroes2::AlphaBlit( _buttons->end, display, rt_end.x, rt_end.y, alpha ); } fheroes2::GameMode Interface::ControlPanel::QueueEventProcessing() const { LocalEvent & le = LocalEvent::Get(); if ( le.MouseClickLeft( rt_radar ) ) interface.EventSwitchShowRadar(); else if ( le.MouseClickLeft( rt_icons ) ) interface.EventSwitchShowIcons(); else if ( le.MouseClickLeft( rt_buttons ) ) interface.EventSwitchShowButtons(); else if ( le.MouseClickLeft( rt_status ) ) interface.EventSwitchShowStatus(); else if ( le.MouseClickLeft( rt_end ) ) return interface.EventEndTurn(); return fheroes2::GameMode::CANCEL; } fheroes2-1.0.12+dfsg/src/fheroes2/gui/interface_cpanel.h000066400000000000000000000071411456075706000227670ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2INTERFACE_CPANEL_H #define H2INTERFACE_CPANEL_H #include #include #include "game_mode.h" #include "math_base.h" namespace fheroes2 { class Sprite; } namespace Interface { class AdventureMap; class ControlPanel final : protected fheroes2::Rect { public: explicit ControlPanel( AdventureMap & ); ControlPanel( const ControlPanel & ) = delete; ~ControlPanel() = default; ControlPanel & operator=( const ControlPanel & ) = delete; void SetPos( int32_t, int32_t ); void ResetTheme(); fheroes2::GameMode QueueEventProcessing() const; const fheroes2::Rect & GetArea() const; // Do not call this method directly, use Interface::AdventureMap::redraw() instead to avoid issues in the "no interface" mode. // The name of this method starts from _ on purpose to do not mix with other public methods. void _redraw() const; private: AdventureMap & interface; // We do not want to make a copy of images but to store just references to them. struct Buttons { Buttons( const fheroes2::Sprite & radar_, const fheroes2::Sprite & icons_, const fheroes2::Sprite & buttons_, const fheroes2::Sprite & status_, const fheroes2::Sprite & end_ ) : radar( radar_ ) , icons( icons_ ) , buttons( buttons_ ) , status( status_ ) , end( end_ ) {} const fheroes2::Sprite & radar; const fheroes2::Sprite & icons; const fheroes2::Sprite & buttons; const fheroes2::Sprite & status; const fheroes2::Sprite & end; }; std::unique_ptr _buttons; fheroes2::Rect rt_radar; fheroes2::Rect rt_icons; fheroes2::Rect rt_buttons; fheroes2::Rect rt_status; fheroes2::Rect rt_end; }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/gui/interface_events.cpp000066400000000000000000000472241456075706000233720ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2012 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include "artifact.h" #include "artifact_ultimate.h" #include "audio.h" #include "audio_manager.h" #include "castle.h" #include "dialog.h" #include "dialog_system_options.h" #include "direction.h" #include "game.h" #include "game_delays.h" #include "game_interface.h" #include "game_io.h" #include "game_mode.h" #include "game_over.h" #include "heroes.h" #include "image.h" #include "interface_base.h" #include "interface_buttons.h" #include "interface_gamearea.h" #include "interface_icons.h" #include "interface_radar.h" #include "interface_status.h" #include "kingdom.h" #include "logging.h" #include "m82.h" #include "maps.h" #include "maps_tiles.h" #include "mp2.h" #include "mus.h" #include "puzzle.h" #include "route.h" #include "screen.h" #include "settings.h" #include "spell.h" #include "spell_book.h" #include "system.h" #include "tools.h" #include "translations.h" #include "ui_dialog.h" #include "ui_text.h" #include "ui_tool.h" #include "view_world.h" #include "world.h" void Interface::AdventureMap::ShowPathOrStartMoveHero( Heroes * hero, const int32_t destinationIdx ) { if ( hero == nullptr || !Maps::isValidAbsIndex( destinationIdx ) ) { return; } assert( !hero->Modes( Heroes::ENABLEMOVE ) ); const Route::Path & path = hero->GetPath(); // Calculate and show the hero's path if ( path.GetDestinationIndex() != destinationIdx ) { hero->calculatePath( destinationIdx ); DEBUG_LOG( DBG_GAME, DBG_TRACE, hero->GetName() << ", distance: " << world.getDistance( *hero, destinationIdx ) << ", route: " << path.String() ) _gameArea.SetRedraw(); buttonsArea.SetRedraw(); } // Start the hero's movement else if ( path.isValidForMovement() && hero->MayStillMove( false, true ) ) { _startHeroMove( *hero ); } } void Interface::AdventureMap::MoveHeroFromArrowKeys( Heroes & hero, const int direction ) { const int32_t heroIndex = hero.GetIndex(); if ( !Maps::isValidDirection( heroIndex, direction ) ) { return; } const int32_t dstIndex = Maps::GetDirectionIndex( heroIndex, direction ); const Maps::Tiles & tile = world.GetTiles( dstIndex ); if ( !tile.isPassableFrom( Direction::CENTER, hero.isShipMaster(), false, hero.GetColor() ) ) { return; } ShowPathOrStartMoveHero( &hero, dstIndex ); } void Interface::AdventureMap::_startHeroMove( Heroes & hero ) { SetFocus( &hero, true ); RedrawFocus(); hero.SetMove( true ); // We pass this delay to start hero moving immediately and set all the variables needed to handle game events correctly // and to stop handling mouse click events until hero stops. Otherwise there could be a rare case // when double click is faster than this delay and the second click will also be handled which should not happen. Game::passAnimationDelay( Game::DelayType::CURRENT_HERO_DELAY ); } void Interface::AdventureMap::EventNextHero() { const Kingdom & myKingdom = world.GetKingdom( Settings::Get().CurrentColor() ); const VecHeroes & myHeroes = myKingdom.GetHeroes(); if ( myHeroes.empty() ) { return; } if ( GetFocusHeroes() ) { VecHeroes::const_iterator it = std::find( myHeroes.begin(), myHeroes.end(), GetFocusHeroes() ); VecHeroes::const_iterator currentHero = it; do { ++it; if ( it == myHeroes.end() ) { it = myHeroes.begin(); } if ( ( *it )->MayStillMove( true, false ) ) { SetFocus( *it, false ); break; } } while ( it != currentHero ); } else { for ( Heroes * hero : myHeroes ) { if ( hero->MayStillMove( true, false ) ) { SetFocus( hero, false ); break; } } } RedrawFocus(); } fheroes2::GameMode Interface::AdventureMap::EventHeroMovement() { Heroes * hero = GetFocusHeroes(); if ( hero ) { if ( hero->GetPath().isValidForMovement() && hero->MayStillMove( false, true ) ) { _startHeroMove( *hero ); } else if ( MP2::isActionObject( hero->getObjectTypeUnderHero(), hero->isShipMaster() ) ) { return EventDefaultAction(); } } return fheroes2::GameMode::CANCEL; } void Interface::AdventureMap::EventResetHeroPath() { Heroes * hero = GetFocusHeroes(); if ( hero == nullptr ) { return; } hero->GetPath().Reset(); setRedraw( REDRAW_GAMEAREA | REDRAW_BUTTONS ); } void Interface::AdventureMap::EventKingdomInfo() const { Kingdom & myKingdom = world.GetKingdom( Settings::Get().CurrentColor() ); myKingdom.openOverviewDialog(); iconsPanel.SetRedraw(); } void Interface::AdventureMap::EventCastSpell() { Heroes * hero = GetFocusHeroes(); if ( hero == nullptr ) { return; } // Center on the hero before opening the spell book _gameArea.SetCenter( hero->GetCenter() ); redraw( REDRAW_GAMEAREA | REDRAW_RADAR_CURSOR ); const Spell spell = hero->OpenSpellBook( SpellBook::Filter::ADVN, true, false, nullptr ); if ( spell.isValid() ) { hero->ActionSpellCast( spell ); // The spell will consume the hero's spell points (and perhaps also movement points) and can move the // hero to another location, so we may have to update the terrain music theme and environment sounds ResetFocus( GameFocus::HEROES, true ); RedrawFocus(); } } fheroes2::GameMode Interface::AdventureMap::EventEndTurn() const { #ifndef NDEBUG const Heroes * focusedHero = GetFocusHeroes(); #endif assert( focusedHero == nullptr || !focusedHero->Modes( Heroes::ENABLEMOVE ) ); const Kingdom & myKingdom = world.GetKingdom( Settings::Get().CurrentColor() ); if ( !myKingdom.HeroesMayStillMove() || Dialog::YES == fheroes2::showStandardTextMessage( _( "End Turn" ), _( "One or more heroes may still move, are you sure you want to end your turn?" ), Dialog::YES | Dialog::NO ) ) { return fheroes2::GameMode::END_TURN; } return fheroes2::GameMode::CANCEL; } fheroes2::GameMode Interface::AdventureMap::EventAdventureDialog() { switch ( Dialog::AdventureOptions( GameFocus::HEROES == GetFocusType() ) ) { case Dialog::WORLD: EventViewWorld(); break; case Dialog::PUZZLE: EventPuzzleMaps(); break; case Dialog::INFO: return EventScenarioInformation(); case Dialog::DIG: return EventDigArtifact(); default: break; } return fheroes2::GameMode::CANCEL; } void Interface::AdventureMap::EventViewWorld() { ViewWorld::ViewWorldWindow( Settings::Get().CurrentColor(), ViewWorldMode::OnlyVisible, *this ); } fheroes2::GameMode Interface::AdventureMap::EventFileDialog() const { return Dialog::FileOptions(); } void Interface::AdventureMap::EventSystemDialog() const { fheroes2::showSystemOptionsDialog(); } fheroes2::GameMode Interface::BaseInterface::EventExit() { if ( Dialog::YES & fheroes2::showStandardTextMessage( _( "Quit" ), _( "Are you sure you want to quit?" ), Dialog::YES | Dialog::NO ) ) return fheroes2::GameMode::QUIT_GAME; return fheroes2::GameMode::CANCEL; } void Interface::AdventureMap::EventNextTown() { Kingdom & myKingdom = world.GetKingdom( Settings::Get().CurrentColor() ); VecCastles & myCastles = myKingdom.GetCastles(); if ( !myCastles.empty() ) { if ( GetFocusCastle() ) { VecCastles::const_iterator it = std::find( myCastles.begin(), myCastles.end(), GetFocusCastle() ); ++it; if ( it == myCastles.end() ) it = myCastles.begin(); SetFocus( *it ); } else ResetFocus( GameFocus::CASTLE, false ); RedrawFocus(); } } fheroes2::GameMode Interface::AdventureMap::EventNewGame() const { return Dialog::YES == fheroes2::showStandardTextMessage( "", _( "Are you sure you want to restart? (Your current game will be lost.)" ), Dialog::YES | Dialog::NO ) ? fheroes2::GameMode::NEW_GAME : fheroes2::GameMode::CANCEL; } fheroes2::GameMode Interface::AdventureMap::EventSaveGame() const { while ( true ) { const std::string filename = Dialog::SelectFileSave(); if ( filename.empty() ) { return fheroes2::GameMode::CANCEL; } if ( System::IsFile( filename ) && Dialog::NO == fheroes2::showStandardTextMessage( "", _( "Are you sure you want to overwrite the save with this name?" ), Dialog::YES | Dialog::NO ) ) { continue; } if ( Game::Save( filename ) ) { fheroes2::showStandardTextMessage( "", _( "Game saved successfully." ), Dialog::OK ); } else { fheroes2::showStandardTextMessage( "", _( "There was an issue during saving." ), Dialog::OK ); } return fheroes2::GameMode::CANCEL; } } fheroes2::GameMode Interface::AdventureMap::EventLoadGame() const { return Dialog::YES == fheroes2::showStandardTextMessage( "", _( "Are you sure you want to load a new game? (Your current game will be lost.)" ), Dialog::YES | Dialog::NO ) ? fheroes2::GameMode::LOAD_GAME : fheroes2::GameMode::CANCEL; } void Interface::AdventureMap::EventPuzzleMaps() const { world.GetKingdom( Settings::Get().CurrentColor() ).PuzzleMaps().ShowMapsDialog(); } fheroes2::GameMode Interface::AdventureMap::EventScenarioInformation() { if ( Settings::Get().isCampaignGameType() ) { fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::ImageRestorer saver( display, 0, 0, display.width(), display.height() ); // We are opening campaign scenario info. It is a full screen image change. So do fade-out and set the fade-in. fheroes2::fadeOutDisplay(); Game::setDisplayFadeIn(); AudioManager::ResetAudio(); const fheroes2::GameMode returnMode = Game::SelectCampaignScenario( fheroes2::GameMode::CANCEL, true ); if ( returnMode == fheroes2::GameMode::CANCEL ) { // We are going back to the Adventure map with fade-in. Game::setDisplayFadeIn(); saver.restore(); Game::restoreSoundsForCurrentFocus(); } else { saver.reset(); } return returnMode; } Dialog::GameInfo(); return fheroes2::GameMode::CANCEL; } void Interface::AdventureMap::EventSwitchHeroSleeping() { Heroes * hero = GetFocusHeroes(); if ( hero ) { hero->Modes( Heroes::SLEEPER ) ? hero->ResetModes( Heroes::SLEEPER ) : hero->SetModes( Heroes::SLEEPER ); setRedraw( REDRAW_HEROES ); buttonsArea.SetRedraw(); } } fheroes2::GameMode Interface::AdventureMap::EventDigArtifact() { Heroes * hero = GetFocusHeroes(); if ( hero == nullptr ) { // TODO: verify how this method can be called if a hero is not selected. return fheroes2::GameMode::CANCEL; } if ( hero->isShipMaster() ) { fheroes2::showStandardTextMessage( "", _( "Try looking on land!!!" ), Dialog::OK ); return fheroes2::GameMode::CANCEL; } if ( hero->GetBagArtifacts().isFull() ) { fheroes2::showStandardTextMessage( "", _( "Searching for the Ultimate Artifact is fruitless. Your hero could not carry it even if he found it - all his artifact slots are full." ), Dialog::OK ); return fheroes2::GameMode::CANCEL; } if ( hero->GetMaxMovePoints() > hero->GetMovePoints() ) { fheroes2::showStandardTextMessage( "", _( "Digging for artifacts requires a whole day, try again tomorrow." ), Dialog::OK ); return fheroes2::GameMode::CANCEL; } // Original Editor allows to put an Ultimate Artifact on an invalid tile. So checking tile index solves this issue. const UltimateArtifact & ultimateArtifact = world.GetUltimateArtifact(); if ( world.GetTiles( hero->GetIndex() ).GoodForUltimateArtifact() || ( ultimateArtifact.getPosition() == hero->GetIndex() && !ultimateArtifact.isFound() ) ) { AudioManager::PlaySound( M82::DIGSOUND ); hero->ResetMovePoints(); hero->GetPath().Reset(); if ( world.DiggingForUltimateArtifact( hero->GetCenter() ) ) { const AudioManager::MusicRestorer musicRestorer; if ( Settings::Get().MusicMIDI() ) { AudioManager::PlaySound( M82::TREASURE ); } else { AudioManager::PlayMusic( MUS::ULTIMATE_ARTIFACT, Music::PlaybackMode::PLAY_ONCE ); } const Artifact & ultimate = ultimateArtifact.GetArtifact(); if ( !hero->PickupArtifact( ultimate ) ) { assert( 0 ); } std::string msg( _( "After spending many hours digging here, you have uncovered the %{artifact}." ) ); StringReplace( msg, "%{artifact}", ultimate.GetName() ); const fheroes2::ArtifactDialogElement artifactUI( ultimate.GetID() ); fheroes2::showMessage( fheroes2::Text( _( "Congratulations!" ), fheroes2::FontType::normalYellow() ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK, { &artifactUI } ); } else { fheroes2::showStandardTextMessage( "", _( "Nothing here. Where could it be?" ), Dialog::OK ); } redraw( REDRAW_HEROES | REDRAW_BUTTONS ); fheroes2::Display::instance().render(); // check if the game is over due to conditions related to the ultimate artifact return GameOver::Result::Get().checkGameOver(); } fheroes2::showStandardTextMessage( "", _( "Try searching on clear ground." ), Dialog::OK ); return fheroes2::GameMode::CANCEL; } fheroes2::GameMode Interface::AdventureMap::EventDefaultAction() { Heroes * hero = GetFocusHeroes(); if ( hero ) { if ( MP2::isActionObject( hero->getObjectTypeUnderHero(), hero->isShipMaster() ) ) { hero->Action( hero->GetIndex() ); // The action object can alter the status of the hero (e.g. Stables or Well) or // move it to another location (e.g. Stone Liths or Whirlpool) ResetFocus( GameFocus::HEROES, true ); RedrawFocus(); // If the hero has performed an action, we have to check the completion condition // of the scenario if ( hero->isAction() ) { hero->ResetAction(); return GameOver::Result::Get().checkGameOver(); } } } else if ( GetFocusCastle() ) { Game::OpenCastleDialog( *GetFocusCastle() ); } return fheroes2::GameMode::CANCEL; } void Interface::AdventureMap::EventOpenFocus() const { if ( GetFocusHeroes() ) Game::OpenHeroesDialog( *GetFocusHeroes(), true, true ); else if ( GetFocusCastle() ) Game::OpenCastleDialog( *GetFocusCastle() ); } void Interface::AdventureMap::EventSwitchShowRadar() const { Settings & conf = Settings::Get(); if ( conf.isHideInterfaceEnabled() ) { if ( conf.ShowRadar() ) { conf.SetShowRadar( false ); _gameArea.SetRedraw(); } else { conf.SetShowRadar( true ); _radar.SetRedraw( REDRAW_RADAR ); } } } void Interface::AdventureMap::EventSwitchShowButtons() const { Settings & conf = Settings::Get(); if ( conf.isHideInterfaceEnabled() ) { if ( conf.ShowButtons() ) { conf.SetShowButtons( false ); _gameArea.SetRedraw(); } else { conf.SetShowButtons( true ); buttonsArea.SetRedraw(); } } } void Interface::AdventureMap::EventSwitchShowStatus() const { Settings & conf = Settings::Get(); if ( conf.isHideInterfaceEnabled() ) { if ( conf.ShowStatus() ) { conf.SetShowStatus( false ); _gameArea.SetRedraw(); } else { conf.SetShowStatus( true ); _statusWindow.SetRedraw(); } } } void Interface::AdventureMap::EventSwitchShowIcons() const { Settings & conf = Settings::Get(); if ( conf.isHideInterfaceEnabled() ) { if ( conf.ShowIcons() ) { conf.SetShowIcons( false ); _gameArea.SetRedraw(); } else { conf.SetShowIcons( true ); iconsPanel.SetRedraw(); } } } void Interface::AdventureMap::EventSwitchShowControlPanel() const { Settings & conf = Settings::Get(); if ( conf.isHideInterfaceEnabled() ) { conf.SetShowControlPanel( !conf.ShowControlPanel() ); _gameArea.SetRedraw(); } } void Interface::AdventureMap::EventKeyArrowPress( int dir ) { Heroes * hero = GetFocusHeroes(); // move hero if ( hero ) MoveHeroFromArrowKeys( *hero, dir ); else // scroll map switch ( dir ) { case Direction::TOP_LEFT: _gameArea.SetScroll( SCROLL_TOP ); _gameArea.SetScroll( SCROLL_LEFT ); break; case Direction::TOP: _gameArea.SetScroll( SCROLL_TOP ); break; case Direction::TOP_RIGHT: _gameArea.SetScroll( SCROLL_TOP ); _gameArea.SetScroll( SCROLL_RIGHT ); break; case Direction::RIGHT: _gameArea.SetScroll( SCROLL_RIGHT ); break; case Direction::BOTTOM_RIGHT: _gameArea.SetScroll( SCROLL_BOTTOM ); _gameArea.SetScroll( SCROLL_RIGHT ); break; case Direction::BOTTOM: _gameArea.SetScroll( SCROLL_BOTTOM ); break; case Direction::BOTTOM_LEFT: _gameArea.SetScroll( SCROLL_BOTTOM ); _gameArea.SetScroll( SCROLL_LEFT ); break; case Direction::LEFT: _gameArea.SetScroll( SCROLL_LEFT ); break; default: break; } } fheroes2-1.0.12+dfsg/src/fheroes2/gui/interface_focus.cpp000066400000000000000000000235521456075706000232030ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2012 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include "audio.h" #include "audio_manager.h" #include "castle.h" #include "game.h" #include "game_interface.h" #include "heroes.h" #include "interface_base.h" #include "interface_gamearea.h" #include "interface_icons.h" #include "interface_status.h" #include "kingdom.h" #include "maps_tiles.h" #include "mus.h" #include "players.h" #include "settings.h" #include "world.h" void Interface::AdventureMap::SetFocus( Heroes * hero, const bool retainScrollBarPosition ) { assert( hero != nullptr ); Player * player = Settings::Get().GetPlayers().GetCurrent(); if ( player == nullptr ) { return; } #ifndef NDEBUG #if defined( WITH_DEBUG ) const bool isAIAutoControlMode = player->isAIAutoControlMode(); #else const bool isAIAutoControlMode = false; #endif // WITH_DEBUG #endif // !NDEBUG assert( player->GetColor() == hero->GetColor() && ( player->isControlHuman() || ( player->isControlAI() && isAIAutoControlMode ) ) ); Focus & focus = player->GetFocus(); Heroes * focusedHero = focus.GetHeroes(); if ( focusedHero && focusedHero != hero ) { // Focus has been changed to another hero. Hide the path of the previous hero. focusedHero->ShowPath( false ); } // Heroes::calculatePath() uses PlayerWorldPathfinder and should not be used for an AI-controlled hero if ( !hero->isMoveEnabled() && hero->isControlHuman() ) { hero->calculatePath( -1 ); } hero->ShowPath( true ); focus.Set( hero ); setRedraw( REDRAW_BUTTONS ); if ( !retainScrollBarPosition ) { iconsPanel.Select( hero ); } _gameArea.SetCenter( hero->GetCenter() ); _statusWindow.SetState( StatusType::STATUS_ARMY ); const int heroIndex = hero->GetIndex(); if ( Game::UpdateSoundsOnFocusUpdate() && heroIndex >= 0 ) { Game::EnvironmentSoundMixer(); AudioManager::PlayMusicAsync( MUS::FromGround( world.GetTiles( heroIndex ).GetGround() ), Music::PlaybackMode::RESUME_AND_PLAY_INFINITE ); } } void Interface::AdventureMap::SetFocus( Castle * castle ) { assert( castle != nullptr ); Player * player = Settings::Get().GetPlayers().GetCurrent(); if ( player == nullptr ) { return; } #ifndef NDEBUG #if defined( WITH_DEBUG ) const bool isAIAutoControlMode = player->isAIAutoControlMode(); #else const bool isAIAutoControlMode = false; #endif // WITH_DEBUG #endif // !NDEBUG assert( player->GetColor() == castle->GetColor() && ( player->isControlHuman() || ( player->isControlAI() && isAIAutoControlMode ) ) ); Focus & focus = player->GetFocus(); Heroes * focusedHero = focus.GetHeroes(); if ( focusedHero ) { focusedHero->ShowPath( false ); } focus.Set( castle ); setRedraw( REDRAW_BUTTONS ); iconsPanel.Select( castle ); _gameArea.SetCenter( castle->GetCenter() ); _statusWindow.SetState( StatusType::STATUS_FUNDS ); if ( Game::UpdateSoundsOnFocusUpdate() ) { Game::EnvironmentSoundMixer(); AudioManager::PlayMusicAsync( MUS::FromGround( world.GetTiles( castle->GetIndex() ).GetGround() ), Music::PlaybackMode::RESUME_AND_PLAY_INFINITE ); } } void Interface::AdventureMap::updateFocus() { Player * player = Settings::Get().GetPlayers().GetCurrent(); if ( player == nullptr ) { return; } const Focus & focus = player->GetFocus(); if ( focus.first == FOCUS_CASTLE ) { Castle * castle = GetFocusCastle(); if ( castle != nullptr ) { SetFocus( castle ); } } else if ( focus.first == FOCUS_HEROES ) { Heroes * hero = GetFocusHeroes(); if ( hero != nullptr ) { SetFocus( hero, false ); } } } void Interface::AdventureMap::ResetFocus( const int priority, const bool retainScrollBarPosition ) { Player * player = Settings::Get().GetPlayers().GetCurrent(); if ( player == nullptr ) { return; } Focus & focus = player->GetFocus(); Kingdom & myKingdom = world.GetKingdom( player->GetColor() ); if ( !retainScrollBarPosition ) { iconsPanel.ResetIcons( ICON_ANY ); } switch ( priority ) { case GameFocus::FIRSTHERO: { const VecHeroes & heroes = myKingdom.GetHeroes(); // Find first hero excluding sleeping ones. const VecHeroes::const_iterator it = std::find_if( heroes.begin(), heroes.end(), []( const Heroes * hero ) { return !hero->Modes( Heroes::SLEEPER ); } ); if ( it != heroes.end() ) { SetFocus( *it, false ); } else { // There are no non-sleeping heroes. Focus on the castle instead. ResetFocus( GameFocus::CASTLE, retainScrollBarPosition ); } break; } case GameFocus::HEROES: if ( Heroes * hero = focus.GetHeroes(); ( hero != nullptr ) && ( hero->GetColor() == player->GetColor() ) ) { // Set focus on the previously focused hero. SetFocus( hero, retainScrollBarPosition ); } else if ( !myKingdom.GetHeroes().empty() ) { // Reset scrollbar here because the current focused hero might have // lost a battle and is not in the heroes list anymore. iconsPanel.ResetIcons( ICON_HEROES ); SetFocus( myKingdom.GetHeroes().front(), false ); } else if ( !myKingdom.GetCastles().empty() ) { // There are no heroes left in the kingdom. Reset the heroes scrollbar and focus on the first castle. iconsPanel.SetRedraw( ICON_HEROES ); SetFocus( myKingdom.GetCastles().front() ); } else { focus.Reset(); } break; case GameFocus::CASTLE: if ( Castle * castle = focus.GetCastle(); ( castle != nullptr ) && ( castle->GetColor() == player->GetColor() ) ) { // Focus on the previously focused castle. SetFocus( castle ); } else if ( !myKingdom.GetCastles().empty() ) { // The previously focused castle is lost, so we update the castles scrollbar. iconsPanel.ResetIcons( ICON_CASTLES ); SetFocus( myKingdom.GetCastles().front() ); } else if ( !myKingdom.GetHeroes().empty() ) { // There are no castles left in the kingdom. Reset the castles scrollbar and focus on the first hero. iconsPanel.SetRedraw( ICON_CASTLES ); SetFocus( myKingdom.GetHeroes().front(), false ); } else { focus.Reset(); } break; default: focus.Reset(); break; } } int Interface::GetFocusType() { Player * player = Settings::Get().GetPlayers().GetCurrent(); if ( player ) { Focus & focus = player->GetFocus(); if ( focus.GetHeroes() ) { return GameFocus::HEROES; } if ( focus.GetCastle() ) { return GameFocus::CASTLE; } } return GameFocus::UNSEL; } Castle * Interface::GetFocusCastle() { Player * player = Settings::Get().GetPlayers().GetCurrent(); return player ? player->GetFocus().GetCastle() : nullptr; } Heroes * Interface::GetFocusHeroes() { Player * player = Settings::Get().GetPlayers().GetCurrent(); return player ? player->GetFocus().GetHeroes() : nullptr; } void Interface::AdventureMap::RedrawFocus() { const int type = GetFocusType(); if ( type != FOCUS_HEROES && iconsPanel.IsSelected( ICON_HEROES ) ) { iconsPanel.ResetIcons( ICON_HEROES ); iconsPanel.SetRedraw(); } else if ( type == FOCUS_HEROES && !iconsPanel.IsSelected( ICON_HEROES ) ) { iconsPanel.Select( GetFocusHeroes() ); iconsPanel.SetRedraw(); } if ( type != FOCUS_CASTLE && iconsPanel.IsSelected( ICON_CASTLES ) ) { iconsPanel.ResetIcons( ICON_CASTLES ); iconsPanel.SetRedraw(); } else if ( type == FOCUS_CASTLE && !iconsPanel.IsSelected( ICON_CASTLES ) ) { iconsPanel.Select( GetFocusCastle() ); iconsPanel.SetRedraw(); } setRedraw( REDRAW_GAMEAREA | REDRAW_RADAR_CURSOR ); if ( type == FOCUS_HEROES ) { iconsPanel.SetRedraw( ICON_HEROES ); } else if ( type == FOCUS_CASTLE ) { iconsPanel.SetRedraw( ICON_CASTLES ); } _statusWindow.SetRedraw(); } fheroes2-1.0.12+dfsg/src/fheroes2/gui/interface_gamearea.cpp000066400000000000000000001324131456075706000236230ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "interface_gamearea.h" #include #include #include #include #include #include #include #include #include "agg_image.h" #include "castle.h" #include "cursor.h" #include "direction.h" #include "game_delays.h" #include "game_interface.h" #include "gamedefs.h" #include "ground.h" #include "heroes.h" #include "icn.h" #include "interface_base.h" #include "interface_cpanel.h" #include "localevent.h" #include "logging.h" #include "maps.h" #include "maps_tiles.h" #include "maps_tiles_helper.h" #include "maps_tiles_render.h" #include "pal.h" #include "players.h" #include "route.h" #include "screen.h" #include "settings.h" #include "skill.h" #include "ui_object_rendering.h" #include "world.h" namespace { const int32_t minimalRequiredDraggingMovement = 10; static_assert( std::is_trivially_copyable::value, "This class is not trivially copyable anymore. Add std::move where required." ); struct TileUnfitRenderObjectInfo { std::map> bottomImages; std::map> bottomBackgroundImages; std::map> topImages; std::map> lowPriorityBottomImages; std::map> highPriorityBottomImages; std::map> heroBackgroundImages; std::map> shadowImages; }; void populateStaticTileUnfitObjectInfo( TileUnfitRenderObjectInfo & tileUnfit, std::vector & imageInfo, std::vector & shadowInfo, const fheroes2::Point & offset, const uint8_t alphaValue ) { for ( auto & objectInfo : imageInfo ) { const fheroes2::Point imagePos = objectInfo.tileOffset; objectInfo.alphaValue = alphaValue; if ( imagePos.y > 0 ) { if ( imagePos.x < 0 ) { tileUnfit.bottomBackgroundImages[imagePos + offset].emplace_front( objectInfo ); } else { tileUnfit.bottomBackgroundImages[imagePos + offset].emplace_back( objectInfo ); } } else if ( imagePos.y == 0 ) { if ( imagePos.x < 0 ) { tileUnfit.bottomImages[imagePos + offset].emplace_front( objectInfo ); } else { tileUnfit.bottomImages[imagePos + offset].emplace_back( objectInfo ); } } else { if ( imagePos.x < 0 ) { tileUnfit.topImages[imagePos + offset].emplace_front( objectInfo ); } else { tileUnfit.topImages[imagePos + offset].emplace_back( objectInfo ); } } } // Static object's shadows are always on the same layer. for ( auto & objectInfo : shadowInfo ) { const fheroes2::Point imagePos = objectInfo.tileOffset + offset; // Shadows outside the game area should not be rendered (objects cast shadows in top-right direction). if ( imagePos.x < 0 || imagePos.y < 0 ) { continue; } objectInfo.alphaValue = alphaValue; tileUnfit.shadowImages[imagePos].emplace_back( objectInfo ); } } void populateStaticTileUnfitBackgroundObjectInfo( TileUnfitRenderObjectInfo & tileUnfit, std::vector & imageInfo, const fheroes2::Point & offset, const uint8_t alphaValue ) { for ( auto & objectInfo : imageInfo ) { const fheroes2::Point imagePos = objectInfo.tileOffset; objectInfo.alphaValue = alphaValue; if ( imagePos.y > 0 ) { tileUnfit.bottomBackgroundImages[imagePos + offset].emplace_front( objectInfo ); } else if ( imagePos.y == 0 ) { tileUnfit.bottomImages[imagePos + offset].emplace_front( objectInfo ); } else { tileUnfit.topImages[imagePos + offset].emplace_front( objectInfo ); } } } void populateHeroObjectInfo( TileUnfitRenderObjectInfo & tileUnfit, const Heroes * hero ) { assert( hero != nullptr ); const fheroes2::Point & heroPos = hero->GetCenter(); fheroes2::Point nextHeroPos = heroPos; const bool movingHero = hero->isMoveEnabled(); if ( movingHero ) { const Route::Path & path = hero->GetPath(); assert( !path.empty() ); nextHeroPos = Maps::GetPoint( Maps::GetDirectionIndex( hero->GetIndex(), path.GetFrontDirection() ) ); } // A castle's road south from a castle should actually be level 3 but it is level 2 causing a hero's horse legs to be truncated. // In order to render the legs properly we need to make the bottom part of the hero's sprite to be rendered after castle's road. // This happens only when a hero stands in a castle. const Castle * castle = world.getCastleEntrance( heroPos ); const bool isHeroInCastle = ( castle != nullptr && castle->GetCenter() == heroPos ); const uint8_t heroAlphaValue = hero->getAlphaValue(); const int32_t worldHeight = world.h(); auto spriteInfo = Maps::getHeroSpritesPerTile( *hero ); auto spriteShadowInfo = Maps::getHeroShadowSpritesPerTile( *hero ); for ( auto & objectInfo : spriteInfo ) { const fheroes2::Point imagePos = objectInfo.tileOffset; objectInfo.alphaValue = heroAlphaValue; if ( movingHero && imagePos.y == 0 ) { if ( nextHeroPos.y > heroPos.y && nextHeroPos.x > heroPos.x && imagePos.x > 0 ) { // The hero moves south-east. We need to render it over everything. tileUnfit.highPriorityBottomImages[imagePos + heroPos].emplace_back( objectInfo ); continue; } if ( nextHeroPos.y > heroPos.y && nextHeroPos.x < heroPos.x && imagePos.x < 0 ) { // The hero moves south-west. We need to render it over everything. tileUnfit.highPriorityBottomImages[imagePos + heroPos].emplace_back( objectInfo ); continue; } if ( nextHeroPos.y < heroPos.y && nextHeroPos.x < heroPos.x && imagePos.x < 0 ) { // The hero moves north-west. We need to render it under all other objects. tileUnfit.lowPriorityBottomImages[imagePos + heroPos].emplace_back( objectInfo ); continue; } if ( nextHeroPos.y < heroPos.y && nextHeroPos.x > heroPos.x && imagePos.x > 0 ) { // The hero moves north-east. We need to render it under all other objects. tileUnfit.lowPriorityBottomImages[imagePos + heroPos].emplace_back( objectInfo ); continue; } } if ( movingHero && imagePos.y == 1 ) { if ( nextHeroPos.y > heroPos.y && nextHeroPos.x > heroPos.x && imagePos.x > 0 ) { // The hero moves south-east. We need to render it over everything. tileUnfit.bottomImages[imagePos + heroPos].emplace_back( objectInfo ); continue; } if ( nextHeroPos.y > heroPos.y && nextHeroPos.x < heroPos.x && imagePos.x < 0 ) { // The hero moves south-west. We need to render it over everything. tileUnfit.bottomImages[imagePos + heroPos].emplace_back( objectInfo ); continue; } } if ( movingHero && imagePos.y == -1 ) { if ( nextHeroPos.y < heroPos.y && nextHeroPos.x < heroPos.x && imagePos.x < 0 ) { // The hero moves north-west. We need to render it under all other objects. tileUnfit.bottomImages[imagePos + heroPos].emplace_back( objectInfo ); continue; } if ( nextHeroPos.y < heroPos.y && nextHeroPos.x > heroPos.x && imagePos.x > 0 ) { // The hero moves north-east. We need to render it under all other objects. tileUnfit.bottomImages[imagePos + heroPos].emplace_back( objectInfo ); continue; } } if ( imagePos.y > 0 && !isHeroInCastle ) { // Hero horse or boat should not be rendered over the bottom map border. if ( ( heroPos.y + imagePos.y ) >= worldHeight ) { continue; } // The very bottom part of hero (or hero on boat) image should not be rendered before it's shadow so we place it in the extra deque. if ( imagePos.x < 0 ) { tileUnfit.heroBackgroundImages[imagePos + heroPos].emplace_front( objectInfo ); } else { tileUnfit.heroBackgroundImages[imagePos + heroPos].emplace_back( objectInfo ); } } else if ( imagePos.y == 0 || ( isHeroInCastle && imagePos.y > 0 ) ) { if ( imagePos.x < 0 ) { tileUnfit.bottomImages[imagePos + heroPos].emplace_front( objectInfo ); } else { tileUnfit.bottomImages[imagePos + heroPos].emplace_back( objectInfo ); } } else { if ( imagePos.x < 0 ) { tileUnfit.topImages[imagePos + heroPos].emplace_front( objectInfo ); } else { tileUnfit.topImages[imagePos + heroPos].emplace_back( objectInfo ); } } } for ( auto & objectInfo : spriteShadowInfo ) { const fheroes2::Point imagePos = objectInfo.tileOffset + heroPos; // Shadows outside the game area should not be rendered. if ( imagePos.x < 0 || imagePos.y < 0 || imagePos.y >= worldHeight ) { continue; } objectInfo.alphaValue = heroAlphaValue; tileUnfit.shadowImages[imagePos].emplace_back( objectInfo ); } } void renderImagesOnTiles( fheroes2::Image & output, const std::map> & images, const Interface::GameArea & area ) { for ( const auto & [offset, imgInfo] : images ) { for ( const auto & info : imgInfo ) { area.BlitOnTile( output, fheroes2::AGG::GetICN( info.icnId, info.icnIndex ), info.area, info.imageOffset.x, info.imageOffset.y, offset, info.isFlipped, info.alphaValue ); } } } bool isTallTopLayerObject( const int32_t x, const int32_t y, const uint32_t uid ) { if ( y + 1 >= world.h() ) { // There is nothing below so it's not a tall object. return false; } // There is a tile below the current. const Maps::Tiles & tileBelow = world.GetTiles( x, y + 1 ); for ( const Maps::TilesAddon & lowerAddon : tileBelow.getTopLayerAddons() ) { if ( lowerAddon._uid == uid ) { // This is a tall object. return true; } } return false; } } Interface::GameArea::GameArea( BaseInterface & interface ) : _interface( interface ) , _minLeftOffset( 0 ) , _maxLeftOffset( 0 ) , _minTopOffset( 0 ) , _maxTopOffset( 0 ) , _prevIndexPos( 0 ) , scrollDirection( 0 ) , updateCursor( false ) , _mouseDraggingInitiated( false ) , _mouseDraggingMovement( false ) , _needRedrawByMouseDragging( false ) { // Do nothing. } void Interface::GameArea::generate( const fheroes2::Size & screenSize, const bool withoutBorders ) { if ( withoutBorders ) SetAreaPosition( 0, 0, screenSize.width, screenSize.height ); else SetAreaPosition( BORDERWIDTH, BORDERWIDTH, screenSize.width - RADARWIDTH - 3 * BORDERWIDTH, screenSize.height - 2 * BORDERWIDTH ); } void Interface::GameArea::SetAreaPosition( int32_t x, int32_t y, int32_t w, int32_t h ) { _windowROI = { x, y, w, h }; const fheroes2::Size worldSize( world.w() * TILEWIDTH, world.h() * TILEWIDTH ); if ( worldSize.width > w ) { _minLeftOffset = -( w / 2 ) - TILEWIDTH / 2; _maxLeftOffset = worldSize.width - w / 2; } else { _minLeftOffset = -( w - worldSize.width ) / 2; _maxLeftOffset = _minLeftOffset; } if ( worldSize.height > h ) { _minTopOffset = -( h / 2 ) - TILEWIDTH / 2; _maxTopOffset = worldSize.height - h / 2; } else { _minTopOffset = -( h - worldSize.height ) / 2; _maxTopOffset = _minTopOffset; } // adding 1 extra tile for both axes in case of drawing tiles partially near sides _visibleTileCount = { ( w + TILEWIDTH - 1 ) / TILEWIDTH + 1, ( h + TILEWIDTH - 1 ) / TILEWIDTH + 1 }; _setCenterToTile( fheroes2::Point( world.w() / 2, world.h() / 2 ) ); } void Interface::GameArea::BlitOnTile( fheroes2::Image & dst, const fheroes2::Image & src, int32_t ox, int32_t oy, const fheroes2::Point & mp, bool flip, uint8_t alpha ) const { const fheroes2::Point tileOffset = GetRelativeTilePosition( mp ); const fheroes2::Rect imageRoi{ tileOffset.x + ox, tileOffset.y + oy, src.width(), src.height() }; const fheroes2::Rect overlappedRoi = _windowROI ^ imageRoi; fheroes2::AlphaBlit( src, overlappedRoi.x - imageRoi.x, overlappedRoi.y - imageRoi.y, dst, overlappedRoi.x, overlappedRoi.y, overlappedRoi.width, overlappedRoi.height, alpha, flip ); } void Interface::GameArea::BlitOnTile( fheroes2::Image & dst, const fheroes2::Image & src, const fheroes2::Rect & srcRoi, int32_t ox, int32_t oy, const fheroes2::Point & mp, bool flip, uint8_t alpha ) const { const fheroes2::Point tileOffset = GetRelativeTilePosition( mp ); const fheroes2::Rect imageRoi{ tileOffset.x + ox, tileOffset.y + oy, srcRoi.width, srcRoi.height }; const fheroes2::Rect overlappedRoi = _windowROI ^ imageRoi; fheroes2::AlphaBlit( src, srcRoi.x + overlappedRoi.x - imageRoi.x, srcRoi.y + overlappedRoi.y - imageRoi.y, dst, overlappedRoi.x, overlappedRoi.y, overlappedRoi.width, overlappedRoi.height, alpha, flip ); } void Interface::GameArea::DrawTile( fheroes2::Image & dst, const fheroes2::Image & src, const fheroes2::Point & mp ) const { const fheroes2::Point tileOffset = GetRelativeTilePosition( mp ); const fheroes2::Rect imageRoi{ tileOffset.x, tileOffset.y, src.width(), src.height() }; const fheroes2::Rect overlappedRoi = _windowROI ^ imageRoi; fheroes2::Copy( src, overlappedRoi.x - imageRoi.x, overlappedRoi.y - imageRoi.y, dst, overlappedRoi.x, overlappedRoi.y, overlappedRoi.width, overlappedRoi.height ); } void Interface::GameArea::Redraw( fheroes2::Image & dst, int flag, bool isPuzzleDraw ) const { const fheroes2::Rect & tileROI = GetVisibleTileROI(); int32_t minX = tileROI.x; int32_t minY = tileROI.y; int32_t maxX = tileROI.x + tileROI.width; int32_t maxY = tileROI.y + tileROI.height; #ifdef WITH_DEBUG const bool renderFog = ( ( flag & LEVEL_FOG ) == LEVEL_FOG ) && !IS_DEVEL(); #else const bool renderFog = ( flag & LEVEL_FOG ) == LEVEL_FOG; #endif // Render terrain. for ( int32_t y = 0; y < tileROI.height; ++y ) { fheroes2::Point offset( tileROI.x, tileROI.y + y ); if ( offset.y < 0 || offset.y >= world.h() ) { for ( ; offset.x < maxX; ++offset.x ) { Maps::redrawEmptyTile( dst, offset, *this ); } } else { for ( ; offset.x < maxX; ++offset.x ) { if ( offset.x < 0 || offset.x >= world.w() ) { Maps::redrawEmptyTile( dst, offset, *this ); } else { const Maps::Tiles & tile = world.GetTiles( offset.x, offset.y ); // Do not render terrain on the tiles fully covered with the fog. if ( tile.getFogDirection() != DIRECTION_ALL || !renderFog ) { DrawTile( dst, getTileSurface( tile ), offset ); } } } } } minX = std::max( minX, 0 ); minY = std::max( minY, 0 ); maxX = std::min( maxX, world.w() ); maxY = std::min( maxY, world.h() ); if ( minX >= maxX || minY >= maxY ) { // This can't be true! Please check your code changes as we shouldn't have an empty area. assert( 0 ); return; } // Each tile can contain multiple object parts or sprites. Each object part has its own level or in other words layer of rendering. // We need to use a correct order of levels to render objects on tiles. The levels are: // 0 - main and action objects like mines, forest, castle and etc. // 1 - background objects like lakes or bushes. // 2 - shadows and some special objects like castle's entrance road. // 3 - roads, water flaws and cracks. Essentially everything what is a part of terrain. // The correct order of levels is 3 --> 1 --> 2 --> 0. // // There are also two groups of objects: ground objects (bottom layer) and high objects (top layer). High objects are the parts of the objects which are taller than // 1 tile. For example, a castle. All ground objects are drawn first. // // However, there are some objects which appear to be more than 1 tile (32 x 32 pixels) size such as heroes, monsters and boats. // To render all these 'special' objects we need to create a copy of object sprite stacks for each tile, add temporary extra sprites and render them. // Let's call these objects as tile-unfit objects, the rest of objects will be called tile-fit objects. // Fading animation can be applied as for tile-fit and tile-unfit objects. // In case of tile-fit objects we need to pass UID of the object and alpha values. // In case of tile-unfit objects we need to pass tile ID and set alpha value while creating RenderObjectInfo instances. const bool drawHeroes = ( flag & LEVEL_HEROES ) == LEVEL_HEROES; TileUnfitRenderObjectInfo tileUnfit; const Heroes * currentHero = drawHeroes ? GetFocusHeroes() : nullptr; // TODO: Dragon City with Object ICN Type OBJ_ICN_TYPE_OBJNMUL2 and object index 46 is a bottom layer sprite. // TODO: When a hero standing besides this turns a part of the hero is visible. This can be fixed only by some hack. // Run through all visible tiles and find all tile-unfit objects. // Also cover extra tiles from right and bottom sides because these objects are usually bigger than 1x1 tiles. const int32_t roiToRenderMinX = std::max( minX - 1, 0 ); const int32_t roiToRenderMinY = std::max( minY - 1, 0 ); const int32_t roiToRenderMaxX = std::min( maxX + 2, world.w() ); const int32_t roiToRenderMaxY = std::min( maxY + 2, world.h() ); for ( int32_t posY = roiToRenderMinY; posY < roiToRenderMaxY; ++posY ) { for ( int32_t posX = roiToRenderMinX; posX < roiToRenderMaxX; ++posX ) { const Maps::Tiles & tile = world.GetTiles( posX, posY ); MP2::MapObjectType objectType = tile.GetObject(); // We will skip objects which are fully under the fog. const bool isTileUnderFog = ( tile.getFogDirection() == DIRECTION_ALL ) && renderFog; switch ( objectType ) { case MP2::OBJ_HERO: { if ( _interface.isEditor() ) { const uint8_t alphaValue = getObjectAlphaValue( tile.GetIndex(), MP2::OBJ_HERO ); auto spriteInfo = getEditorHeroSpritesPerTile( tile ); std::vector temp; populateStaticTileUnfitObjectInfo( tileUnfit, spriteInfo, temp, tile.GetCenter(), alphaValue ); continue; } if ( !drawHeroes ) { continue; } const bool isUpperTileUnderFog = ( posY > 0 ) ? ( world.GetTiles( posX, posY - 1 ).getFogDirection() == DIRECTION_ALL ) : true; const Heroes * hero = tile.getHero(); // Boats are 2 tiles high so for hero on the boat we have to populate info for boat one tile lower than the fog. if ( isTileUnderFog && ( isUpperTileUnderFog || !hero->isShipMaster() ) ) { // AI heroes can go out of fog so we have to render them one step earlier than getting out of fog. if ( hero->isControlAI() ) { const Route::Path & path = hero->GetPath(); // Check if the next AI hero path point will not be seen on map to skip it. if ( path.isValidForMovement() && ( world.GetTiles( path.GetFrontIndex() ).getFogDirection() == DIRECTION_ALL ) ) { continue; } } else { continue; } } populateHeroObjectInfo( tileUnfit, hero ); // Update object type as it could be an object under the hero. objectType = tile.GetObject( false ); break; } case MP2::OBJ_MONSTER: { if ( isPuzzleDraw || isTileUnderFog ) { continue; } const uint8_t alphaValue = getObjectAlphaValue( tile.GetIndex(), MP2::OBJ_MONSTER ); auto spriteInfo = getMonsterSpritesPerTile( tile, _interface.isEditor() ); auto spriteShadowInfo = getMonsterShadowSpritesPerTile( tile, _interface.isEditor() ); populateStaticTileUnfitObjectInfo( tileUnfit, spriteInfo, spriteShadowInfo, tile.GetCenter(), alphaValue ); continue; } case MP2::OBJ_BOAT: { // Boats are 2 tiles high so we have to populate info for boat one tile lower than the fog. const bool isUpperTileUnderFog = ( posY > 0 ) ? ( world.GetTiles( posX, posY - 1 ).getFogDirection() == DIRECTION_ALL ) : true; if ( !drawHeroes || ( isTileUnderFog && isUpperTileUnderFog ) ) { // Boats can be occupied by heroes so they are considered as the same objects. continue; } const uint8_t alphaValue = getObjectAlphaValue( tile.GetIndex(), MP2::OBJ_BOAT ); auto spriteInfo = getBoatSpritesPerTile( tile ); auto spriteShadowInfo = getBoatShadowSpritesPerTile( tile ); populateStaticTileUnfitObjectInfo( tileUnfit, spriteInfo, spriteShadowInfo, tile.GetCenter(), alphaValue ); continue; } default: break; } // These are parts of original action objects which must be rendered under heroes. if ( objectType == MP2::OBJ_MINE ) { auto spriteInfo = getMineGuardianSpritesPerTile( tile ); if ( !spriteInfo.empty() ) { const uint8_t alphaValue = getObjectAlphaValue( tile.GetObjectUID() ); populateStaticTileUnfitBackgroundObjectInfo( tileUnfit, spriteInfo, tile.GetCenter(), alphaValue ); } } } } // Render all terrain and background layer object. for ( int32_t y = minY; y < maxY; ++y ) { for ( int32_t x = minX; x < maxX; ++x ) { const Maps::Tiles & tile = world.GetTiles( x, y ); if ( tile.getFogDirection() == DIRECTION_ALL && renderFog ) { continue; } // Draw roads, rivers and cracks. redrawBottomLayerObjects( tile, dst, isPuzzleDraw, *this, Maps::TERRAIN_LAYER ); redrawBottomLayerObjects( tile, dst, isPuzzleDraw, *this, Maps::BACKGROUND_LAYER ); } } // Draw the lower part of tile-unfit object's sprite. renderImagesOnTiles( dst, tileUnfit.bottomBackgroundImages, *this ); for ( int32_t y = minY; y < maxY; ++y ) { for ( int32_t x = minX; x < maxX; ++x ) { const Maps::Tiles & tile = world.GetTiles( x, y ); if ( tile.getFogDirection() == DIRECTION_ALL && renderFog ) { continue; } redrawBottomLayerObjects( tile, dst, isPuzzleDraw, *this, Maps::SHADOW_LAYER ); } } // Draw all shadows from tile-unfit objects. renderImagesOnTiles( dst, tileUnfit.shadowImages, *this ); // Draw the lower part of hero's sprite including boat sprite when it is controlled by hero. renderImagesOnTiles( dst, tileUnfit.heroBackgroundImages, *this ); // Low priority images are drawn before any other object on this tile. renderImagesOnTiles( dst, tileUnfit.lowPriorityBottomImages, *this ); for ( int32_t y = minY; y < maxY; ++y ) { for ( int32_t x = minX; x < maxX; ++x ) { const Maps::Tiles & tile = world.GetTiles( x, y ); if ( tile.getFogDirection() == DIRECTION_ALL && renderFog ) { continue; } // TODO: some action objects have tiles above which are still on bottom layer. These images must be drawn last. redrawBottomLayerObjects( tile, dst, isPuzzleDraw, *this, Maps::OBJECT_LAYER ); } } // Draw middle part of tile-unfit sprites. renderImagesOnTiles( dst, tileUnfit.bottomImages, *this ); // High priority images are drawn after any other object on this tile. renderImagesOnTiles( dst, tileUnfit.highPriorityBottomImages, *this ); std::vector topLayerTallObjects; // Expand ROI to properly render very tall objects (1 tile - left and right; 2 tiles - bottom): Abandoned mine Ghosts, Flag on the Alchemist lab, and others. const int32_t roiExtraObjectsMaxX = std::min( maxX + 1, world.w() ); for ( int32_t y = minY; y < roiToRenderMaxY; ++y ) { for ( int32_t x = roiToRenderMinX; x < roiExtraObjectsMaxX; ++x ) { const Maps::Tiles & tile = world.GetTiles( x, y ); const bool isTileUnderFog = ( tile.getFogDirection() == DIRECTION_ALL ) && renderFog; if ( isTileUnderFog ) { // To correctly render tall extra objects (ghosts over abandoned mine) it is needed to analyze one tile to the bottom direction under fog. const bool isUpperTileUnderFog = ( y > 0 ) ? ( world.GetTiles( x, y - 1 ).getFogDirection() == DIRECTION_ALL ) : true; if ( isUpperTileUnderFog ) { // If current tile and the bottom one are both under the fog continue; } redrawTopLayerExtraObjects( tile, dst, isPuzzleDraw, *this ); continue; } // Since some objects are taller than 2 tiles their top layer sprites must be drawn at the very end. // For now what we need to do is to run through all level 2 objects and verify that the tile below doesn't have // any other level 2 objects with the same UID. topLayerTallObjects.clear(); for ( const Maps::TilesAddon & addon : tile.getTopLayerAddons() ) { if ( isTallTopLayerObject( x, y, addon._uid ) ) { topLayerTallObjects.emplace_back( &addon ); } else { redrawTopLayerObject( tile, dst, isPuzzleDraw, *this, addon ); } } redrawTopLayerExtraObjects( tile, dst, isPuzzleDraw, *this ); for ( const Maps::TilesAddon * addon : topLayerTallObjects ) { redrawTopLayerObject( tile, dst, isPuzzleDraw, *this, *addon ); } } } // Draw upper part of tile-unfit sprites. renderImagesOnTiles( dst, tileUnfit.topImages, *this ); // Draw hero's route. It should be drawn on top of everything. const bool drawRoutes = ( flag & LEVEL_ROUTES ) != 0; if ( drawRoutes && ( currentHero != nullptr ) && currentHero->GetPath().isShow() ) { const Route::Path & path = currentHero->GetPath(); int32_t greenColorSteps = path.GetAllowedSteps(); const int32_t pathfinding = currentHero->GetLevelSkill( Skill::Secondary::PATHFINDING ); Route::Path::const_iterator currentStep = path.begin(); Route::Path::const_iterator nextStep = currentStep; if ( currentHero->isMoveEnabled() && ( currentHero->GetDirection() == path.GetFrontDirection() ) ) { // Do not draw the first path mark when hero / boat is moving in the direction of the path. ++currentStep; ++nextStep; --greenColorSteps; } // Not all arrows and their shadows fit in 1 tile. We need to consider an area of 1 tile bigger to properly render everything. const fheroes2::Rect extendedVisibleRoi{ tileROI.x - 1, tileROI.y - 1, tileROI.width + 2, tileROI.height + 2 }; for ( ; currentStep != path.end(); ++currentStep ) { const int32_t tileIndex = currentStep->GetIndex(); const fheroes2::Point & mp = Maps::GetPoint( tileIndex ); ++nextStep; --greenColorSteps; if ( !( extendedVisibleRoi & mp ) ) { // The mark is on a tile outside the drawing area. Just skip it. continue; } uint32_t routeSpriteIndex = 0; if ( nextStep != path.end() ) { const Maps::Tiles & tile = world.GetTiles( tileIndex ); const uint32_t cost = tile.isRoad() ? Maps::Ground::roadPenalty : Maps::Ground::GetPenalty( tile, pathfinding ); routeSpriteIndex = Route::Path::GetIndexSprite( currentStep->GetDirection(), nextStep->GetDirection(), cost ); } const fheroes2::Sprite & routeSprite = fheroes2::AGG::GetICN( ( ( greenColorSteps < 0 ) ? ICN::ROUTERED : ICN::ROUTE ), routeSpriteIndex ); BlitOnTile( dst, routeSprite, routeSprite.x() - 12, routeSprite.y() + 2, mp, false, 255 ); } } const bool drawTowns = ( flag & LEVEL_TOWNS ); #ifdef WITH_DEBUG if ( IS_DEVEL() ) { // redraw grid if ( flag & LEVEL_ALL ) { const int32_t friendColors = Players::FriendColors(); for ( int32_t y = minY; y < maxY; ++y ) { for ( int32_t x = minX; x < maxX; ++x ) { redrawPassable( world.GetTiles( x, y ), dst, friendColors, *this ); } } } } else { #endif // redraw fog if ( renderFog ) { for ( int32_t y = minY; y < maxY; ++y ) { for ( int32_t x = minX; x < maxX; ++x ) { const Maps::Tiles & tile = world.GetTiles( x, y ); if ( tile.getFogDirection() != Direction::UNKNOWN ) { drawFog( tile, dst, *this ); if ( drawTowns ) { drawByObjectIcnType( tile, dst, *this, MP2::OBJ_ICN_TYPE_OBJNTWBA ); const MP2::MapObjectType objectType = tile.GetObject( false ); if ( objectType == MP2::OBJ_CASTLE || objectType == MP2::OBJ_NON_ACTION_CASTLE ) { drawByObjectIcnType( tile, dst, *this, MP2::OBJ_ICN_TYPE_OBJNTOWN ); } } } } } } #ifdef WITH_DEBUG } #endif updateObjectAnimationInfo(); } void Interface::GameArea::renderTileAreaSelect( fheroes2::Image & dst, const int32_t startTile, const int32_t endTile ) const { if ( startTile < 0 || endTile < 0 ) { return; } const fheroes2::Point startTileOffset = GetRelativeTilePosition( Maps::GetPoint( startTile ) ); const fheroes2::Point endTileOffset = GetRelativeTilePosition( Maps::GetPoint( endTile ) ); const int32_t startX = std::min( startTileOffset.x, endTileOffset.x ); const int32_t startY = std::min( startTileOffset.y, endTileOffset.y ); const int32_t sizeX = TILEWIDTH + std::abs( startTileOffset.x - endTileOffset.x ); const int32_t sizeY = TILEWIDTH + std::abs( startTileOffset.y - endTileOffset.y ); const fheroes2::Rect imageRoi{ startX, startY, sizeX, sizeY }; const fheroes2::Rect overlappedRoi = _windowROI ^ imageRoi; const int32_t limitedLineWidth = std::min( 2, overlappedRoi.width ); const int32_t limitedLineHeight = std::min( 2, overlappedRoi.height ); fheroes2::Fill( dst, overlappedRoi.x, overlappedRoi.y, overlappedRoi.width, limitedLineHeight, 181 ); fheroes2::Fill( dst, overlappedRoi.x, overlappedRoi.y + 2, limitedLineWidth, overlappedRoi.height - 4, 181 ); fheroes2::Fill( dst, overlappedRoi.x, overlappedRoi.y + overlappedRoi.height - limitedLineHeight, overlappedRoi.width, limitedLineHeight, 181 ); fheroes2::Fill( dst, overlappedRoi.x + overlappedRoi.width - limitedLineWidth, overlappedRoi.y + 2, limitedLineWidth, overlappedRoi.height - 4, 181 ); } void Interface::GameArea::updateMapFogDirections() { const int32_t friendColors = Players::FriendColors(); Maps::updateFogDirectionsInArea( { 0, 0 }, { world.w(), world.h() }, friendColors ); } void Interface::GameArea::Scroll() { const int32_t scrollSpeed = Settings::Get().ScrollSpeed(); if ( scrollSpeed == SCROLL_SPEED_NONE ) { // No scrolling. scrollDirection = SCROLL_NONE; return; } const int32_t shift = 2 << scrollSpeed; fheroes2::Point offset; if ( scrollDirection & SCROLL_LEFT ) { offset.x = -shift; } else if ( scrollDirection & SCROLL_RIGHT ) { offset.x = shift; } if ( scrollDirection & SCROLL_TOP ) { offset.y = -shift; } else if ( scrollDirection & SCROLL_BOTTOM ) { offset.y = shift; } ShiftCenter( offset ); scrollDirection = SCROLL_NONE; } void Interface::GameArea::SetRedraw() const { _interface.setRedraw( REDRAW_GAMEAREA ); } fheroes2::Image Interface::GameArea::GenerateUltimateArtifactAreaSurface( const int32_t index, const fheroes2::Point & offset ) { if ( !Maps::isValidAbsIndex( index ) ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Ultimate artifact is not found on index " << index ) return fheroes2::Image(); } fheroes2::Image result; result._disableTransformLayer(); result.resize( 448, 448 ); // Make a temporary copy GameArea gamearea = AdventureMap::Get().getGameArea(); gamearea.SetAreaPosition( 0, 0, result.width(), result.height() ); const fheroes2::Point pt = Maps::GetPoint( index ); gamearea.SetCenter( pt + offset ); gamearea.Redraw( result, LEVEL_OBJECTS, true ); const fheroes2::Sprite & marker = fheroes2::AGG::GetICN( ICN::ROUTE, 0 ); const fheroes2::Point markerPos( gamearea.GetRelativeTilePosition( pt ) - gamearea._middlePoint() - fheroes2::Point( gamearea._windowROI.x, gamearea._windowROI.y ) + fheroes2::Point( result.width() / 2, result.height() / 2 ) ); fheroes2::Blit( marker, result, markerPos.x, markerPos.y + 8 ); fheroes2::ApplyPalette( result, PAL::GetPalette( PAL::PaletteType::TAN ) ); return result; } int Interface::GameArea::GetScrollCursor() const { switch ( scrollDirection ) { case SCROLL_LEFT | SCROLL_TOP: return Cursor::SCROLL_TOPLEFT; case SCROLL_LEFT | SCROLL_BOTTOM: return Cursor::SCROLL_BOTTOMLEFT; case SCROLL_RIGHT | SCROLL_TOP: return Cursor::SCROLL_TOPRIGHT; case SCROLL_RIGHT | SCROLL_BOTTOM: return Cursor::SCROLL_BOTTOMRIGHT; case SCROLL_TOP: return Cursor::SCROLL_TOP; case SCROLL_BOTTOM: return Cursor::SCROLL_BOTTOM; case SCROLL_RIGHT: return Cursor::SCROLL_RIGHT; case SCROLL_LEFT: return Cursor::SCROLL_LEFT; default: break; } return Cursor::NONE; } void Interface::GameArea::SetScroll( int direct ) { if ( ( direct & SCROLL_LEFT ) == SCROLL_LEFT ) { if ( _topLeftTileOffset.x > _minLeftOffset ) { scrollDirection |= direct; updateCursor = true; } } else if ( ( direct & SCROLL_RIGHT ) == SCROLL_RIGHT ) { if ( _topLeftTileOffset.x < _maxLeftOffset ) { scrollDirection |= direct; updateCursor = true; } } if ( ( direct & SCROLL_TOP ) == SCROLL_TOP ) { if ( _topLeftTileOffset.y > _minTopOffset ) { scrollDirection |= direct; updateCursor = true; } } else if ( ( direct & SCROLL_BOTTOM ) == SCROLL_BOTTOM ) { if ( _topLeftTileOffset.y < _maxTopOffset ) { scrollDirection |= direct; updateCursor = true; } } scrollTime.reset(); } void Interface::GameArea::QueueEventProcessing( bool isCursorOverGamearea ) { LocalEvent & le = LocalEvent::Get(); const fheroes2::Point & mousePosition = le.GetMouseCursor(); if ( !le.MousePressLeft() ) { _mouseDraggingInitiated = false; _mouseDraggingMovement = false; _needRedrawByMouseDragging = false; } else if ( !_mouseDraggingInitiated ) { _mouseDraggingInitiated = true; _lastMouseDragPosition = mousePosition; } else if ( isCursorOverGamearea && _interface.useMouseDragMovement() && ( std::abs( _lastMouseDragPosition.x - mousePosition.x ) > minimalRequiredDraggingMovement || std::abs( _lastMouseDragPosition.y - mousePosition.y ) > minimalRequiredDraggingMovement ) ) { _mouseDraggingMovement = true; } if ( _mouseDraggingMovement ) { if ( _lastMouseDragPosition == mousePosition ) { _needRedrawByMouseDragging = false; } else { // Update the center coordinates and redraw the adventure map only if the mouse was moved. _needRedrawByMouseDragging = true; SetCenterInPixels( getCurrentCenterInPixels() + _lastMouseDragPosition - mousePosition ); _lastMouseDragPosition = mousePosition; } return; } if ( !isCursorOverGamearea ) { // In this case the cursor image changes in 'Interface::AdventureMap::HumanTurn()' so there is nothing more to do here. return; } int32_t index = GetValidTileIdFromPoint( mousePosition ); if ( !Maps::isValidAbsIndex( index ) ) { // Change the cursor image when it gets out of the map boundaries or by 'updateCursor' flag. if ( updateCursor || index != _prevIndexPos ) { _interface.updateCursor( index ); _prevIndexPos = index; updateCursor = false; } return; } const Settings & conf = Settings::Get(); if ( conf.isHideInterfaceEnabled() && conf.ShowControlPanel() && le.MouseCursor( Interface::AdventureMap::Get().getControlPanel().GetArea() ) ) { return; } const fheroes2::Point tileOffset = _topLeftTileOffset + mousePosition - _windowROI.getPosition(); const fheroes2::Point tilePos( ( tileOffset.x / TILEWIDTH ) * TILEWIDTH - _topLeftTileOffset.x + _windowROI.x, ( tileOffset.y / TILEWIDTH ) * TILEWIDTH - _topLeftTileOffset.y + _windowROI.x ); const fheroes2::Rect tileROI( tilePos.x, tilePos.y, TILEWIDTH, TILEWIDTH ); if ( le.MouseClickLeft( tileROI ) ) { _interface.mouseCursorAreaClickLeft( index ); } else if ( le.MousePressRight( tileROI ) ) { _interface.mouseCursorAreaPressRight( index ); } // The cursor may have moved after mouse click events. index = GetValidTileIdFromPoint( le.GetMouseCursor() ); // Change the cursor image if needed. if ( updateCursor || index != _prevIndexPos ) { _interface.updateCursor( index ); _prevIndexPos = index; updateCursor = false; } } fheroes2::Point Interface::GameArea::_getStartTileId() const { const int32_t x = ( _topLeftTileOffset.x < 0 ? ( _topLeftTileOffset.x - TILEWIDTH - 1 ) / TILEWIDTH : _topLeftTileOffset.x / TILEWIDTH ); const int32_t y = ( _topLeftTileOffset.y < 0 ? ( _topLeftTileOffset.y - TILEWIDTH - 1 ) / TILEWIDTH : _topLeftTileOffset.y / TILEWIDTH ); return { x, y }; } void Interface::GameArea::_setCenterToTile( const fheroes2::Point & tile ) { SetCenterInPixels( { tile.x * TILEWIDTH + TILEWIDTH / 2, tile.y * TILEWIDTH + TILEWIDTH / 2 } ); } void Interface::GameArea::SetCenterInPixels( const fheroes2::Point & point ) { const fheroes2::Point & middlePos = _middlePoint(); int32_t offsetX = point.x - middlePos.x; int32_t offsetY = point.y - middlePos.y; if ( offsetX < _minLeftOffset ) offsetX = _minLeftOffset; else if ( offsetX > _maxLeftOffset ) offsetX = _maxLeftOffset; if ( offsetY < _minTopOffset ) offsetY = _minTopOffset; else if ( offsetY > _maxTopOffset ) offsetY = _maxTopOffset; _topLeftTileOffset = fheroes2::Point( offsetX, offsetY ); } int32_t Interface::GameArea::GetValidTileIdFromPoint( const fheroes2::Point & point ) const { const fheroes2::Point offset = _topLeftTileOffset + point - _windowROI.getPosition(); if ( offset.x < 0 || offset.y < 0 ) return -1; const int32_t x = offset.x / TILEWIDTH; const int32_t y = offset.y / TILEWIDTH; if ( x >= world.w() || y >= world.h() ) return -1; return y * world.w() + x; } fheroes2::Point Interface::GameArea::GetRelativeTilePosition( const fheroes2::Point & tileId ) const { return { tileId.x * TILEWIDTH - _topLeftTileOffset.x + _windowROI.x, tileId.y * TILEWIDTH - _topLeftTileOffset.y + _windowROI.y }; } void Interface::GameArea::updateObjectAnimationInfo() const { for ( auto iter = _animationInfo.begin(); iter != _animationInfo.end(); ) { if ( ( *iter )->update() ) { iter = _animationInfo.erase( iter ); } else { ++iter; } } } uint8_t Interface::GameArea::getObjectAlphaValue( const int32_t tileId, const MP2::MapObjectType type ) const { for ( const auto & info : _animationInfo ) { if ( info->tileId == tileId && type == info->type ) { return info->alphaValue; } } return 255; } uint8_t Interface::GameArea::getObjectAlphaValue( const uint32_t uid ) const { for ( const auto & info : _animationInfo ) { if ( uid == info->uid ) { return info->alphaValue; } } return 255; } void Interface::GameArea::runSingleObjectAnimation( const std::shared_ptr & info ) { if ( !info ) { assert( 0 ); return; } addObjectAnimationInfo( info ); LocalEvent & le = LocalEvent::Get(); fheroes2::Display & display = fheroes2::Display::instance(); Interface::AdventureMap & adventureMapInterface = Interface::AdventureMap::Get(); while ( le.HandleEvents( Game::isDelayNeeded( { Game::HEROES_PICKUP_DELAY } ) ) && !info->isAnimationCompleted() ) { if ( Game::validateAnimationDelay( Game::HEROES_PICKUP_DELAY ) ) { adventureMapInterface.redraw( Interface::REDRAW_GAMEAREA ); display.render(); } } } Interface::ObjectFadingOutInfo::~ObjectFadingOutInfo() { Maps::Tiles & tile = world.GetTiles( tileId ); if ( tile.GetObject() == type ) { removeObjectSprite( tile ); tile.setAsEmpty(); } } fheroes2-1.0.12+dfsg/src/fheroes2/gui/interface_gamearea.h000066400000000000000000000240611456075706000232670ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2INTERFACE_GAMEAREA_H #define H2INTERFACE_GAMEAREA_H #include #include #include #include #include #include "image.h" #include "math_base.h" #include "mp2.h" #include "timing.h" namespace Interface { class BaseInterface; enum ScrollingType { SCROLL_NONE = 0x00, SCROLL_LEFT = 0x01, SCROLL_RIGHT = 0x02, SCROLL_TOP = 0x04, SCROLL_BOTTOM = 0x08 }; enum RedrawLevelType { LEVEL_OBJECTS = 0x00, // we always render objects for each type of view. LEVEL_HEROES = 0x01, LEVEL_FOG = 0x02, LEVEL_ROUTES = 0x04, LEVEL_TOWNS = 0x08, // this level is used only for View All / View Towns spells. LEVEL_ALL = LEVEL_OBJECTS | LEVEL_HEROES | LEVEL_FOG | LEVEL_ROUTES }; struct BaseObjectAnimationInfo { BaseObjectAnimationInfo() = default; BaseObjectAnimationInfo( const uint32_t uid_, const int32_t tileId_, const MP2::MapObjectType type_ ) : uid( uid_ ) , tileId( tileId_ ) , type( type_ ) { // Do nothing. } BaseObjectAnimationInfo( const BaseObjectAnimationInfo & ) = delete; BaseObjectAnimationInfo & operator=( const BaseObjectAnimationInfo & ) = delete; virtual ~BaseObjectAnimationInfo() = default; uint32_t uid{ 0 }; int32_t tileId{ -1 }; MP2::MapObjectType type{ MP2::OBJ_NONE }; uint8_t alphaValue{ 255 }; virtual bool update() = 0; virtual bool isAnimationCompleted() const = 0; }; struct ObjectFadingOutInfo : public BaseObjectAnimationInfo { using BaseObjectAnimationInfo::BaseObjectAnimationInfo; ~ObjectFadingOutInfo() override; bool update() override { if ( alphaValue >= alphaStep ) { alphaValue -= alphaStep; } else { alphaValue = 0; } return ( alphaValue == 0 ); } bool isAnimationCompleted() const override { return ( alphaValue == 0 ); } static const uint8_t alphaStep{ 20 }; }; struct ObjectFadingInInfo : public BaseObjectAnimationInfo { ObjectFadingInInfo() = delete; ObjectFadingInInfo( const uint32_t uid_, const int32_t tileId_, const MP2::MapObjectType type_ ) : BaseObjectAnimationInfo( uid_, tileId_, type_ ) { alphaValue = 0; } ~ObjectFadingInInfo() override = default; bool update() override { if ( 255 - alphaValue >= alphaStep ) { alphaValue += alphaStep; } else { alphaValue = 255; } return ( alphaValue == 255 ); } bool isAnimationCompleted() const override { return ( alphaValue == 255 ); } static const uint8_t alphaStep{ 20 }; }; class GameArea { public: explicit GameArea( BaseInterface & interface ); GameArea( const GameArea & ) = default; GameArea( GameArea && ) = delete; ~GameArea() = default; GameArea & operator=( const GameArea & ) = delete; GameArea & operator=( GameArea && ) = delete; void generate( const fheroes2::Size & screenSize, const bool withoutBorders ); const fheroes2::Rect & GetROI() const // returns visible Game Area ROI in pixels { return _windowROI; } // Do NOT use this method directly in heavy computation loops fheroes2::Rect GetVisibleTileROI() const { return { _getStartTileId(), _visibleTileCount }; } // Shift center in pixels. void ShiftCenter( const fheroes2::Point & offset ) { SetCenterInPixels( _topLeftTileOffset + _middlePoint() + offset ); } int GetScrollCursor() const; bool NeedScroll() const { return scrollDirection != 0; } void Scroll(); void SetScroll( int ); void SetCenter( const fheroes2::Point & point ) { _setCenterToTile( point ); scrollDirection = 0; } // Do not call this method unless it's needed for manual setup of the position void SetCenterInPixels( const fheroes2::Point & point ); void SetRedraw() const; // Do not call this method directly if the rendering takes place on the screen, use // Interface::BaseInterface::Redraw() instead to avoid issues in the "no interface" mode void Redraw( fheroes2::Image & dst, int flag, bool isPuzzleDraw = false ) const; void renderTileAreaSelect( fheroes2::Image & dst, const int32_t startTile, const int32_t endTile ) const; void BlitOnTile( fheroes2::Image & dst, const fheroes2::Image & src, int32_t ox, int32_t oy, const fheroes2::Point & mp, bool flip, uint8_t alpha ) const; void BlitOnTile( fheroes2::Image & dst, const fheroes2::Image & src, const fheroes2::Rect & srcRoi, int32_t ox, int32_t oy, const fheroes2::Point & mp, bool flip, uint8_t alpha ) const; // Use this method to draw TIL images void DrawTile( fheroes2::Image & src, const fheroes2::Image & dst, const fheroes2::Point & mp ) const; void SetUpdateCursor() { updateCursor = true; } // Update fog directions data for entire map tiles by checking fog data for current player and its allies. static void updateMapFogDirections(); void QueueEventProcessing( bool isCursorOverGamearea ); static fheroes2::Image GenerateUltimateArtifactAreaSurface( const int32_t index, const fheroes2::Point & offset ); int32_t GetValidTileIdFromPoint( const fheroes2::Point & point ) const; // returns -1 in case of invalid index (out of World Map) fheroes2::Point GetRelativeTilePosition( const fheroes2::Point & tileId ) const; // in relation to screen void ResetCursorPosition() { _prevIndexPos = -1; } void SetAreaPosition( int32_t x, int32_t y, int32_t w, int32_t h ); fheroes2::Point getCurrentCenterInPixels() const { return _topLeftTileOffset + _middlePoint(); } void addObjectAnimationInfo( std::shared_ptr info ) { _animationInfo.emplace_back( std::move( info ) ); } uint8_t getObjectAlphaValue( const int32_t tileId, const MP2::MapObjectType type ) const; uint8_t getObjectAlphaValue( const uint32_t uid ) const; // Make sure you do not have a copy of this object after the execution of the method to avoid incorrect object removal in some cases. void runSingleObjectAnimation( const std::shared_ptr & info ); bool isDragScroll() const { return _mouseDraggingMovement; } bool needDragScrollRedraw() const { return _needRedrawByMouseDragging; } private: BaseInterface & _interface; fheroes2::Rect _windowROI; // visible to draw area of World Map in pixels fheroes2::Point _topLeftTileOffset; // offset of tiles to be drawn (from here we can find any tile ID) // boundaries for World Map int32_t _minLeftOffset; int32_t _maxLeftOffset; int32_t _minTopOffset; int32_t _maxTopOffset; fheroes2::Size _visibleTileCount; // number of tiles to be drawn on screen int32_t _prevIndexPos; int scrollDirection; bool updateCursor; fheroes2::Time scrollTime; // This member needs to be mutable because it is modified during rendering. mutable std::vector> _animationInfo; fheroes2::Point _lastMouseDragPosition; bool _mouseDraggingInitiated; bool _mouseDraggingMovement; bool _needRedrawByMouseDragging; // Returns middle point of window ROI. fheroes2::Point _middlePoint() const { return { _windowROI.width / 2, _windowROI.height / 2 }; } fheroes2::Point _getStartTileId() const; void _setCenterToTile( const fheroes2::Point & tile ); // set center to the middle of tile (input is tile ID) void updateObjectAnimationInfo() const; }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/gui/interface_icons.cpp000066400000000000000000000365511456075706000232020ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "interface_icons.h" #include "agg_image.h" #include "castle.h" #include "dialog.h" #include "game.h" #include "game_interface.h" #include "gamedefs.h" #include "heroes.h" #include "heroes_base.h" #include "icn.h" #include "interface_base.h" #include "kingdom.h" #include "maps.h" #include "screen.h" #include "settings.h" #include "ui_castle.h" #include "ui_scrollbar.h" #include "world.h" namespace { const int32_t iconsWidth = 46; const int32_t iconsHeight = 22; const int32_t iconsCursorWidth = 56; const int32_t iconsCursorHeight = 32; } bool Interface::IconsBar::IsVisible() { const Settings & conf = Settings::Get(); return !conf.isHideInterfaceEnabled() || conf.ShowIcons(); } int32_t Interface::IconsBar::GetItemWidth() { return iconsWidth; } int32_t Interface::IconsBar::GetItemHeight() { return iconsHeight; } void Interface::RedrawCastleIcon( const Castle & castle, int32_t sx, int32_t sy ) { fheroes2::drawCastleIcon( castle, fheroes2::Display::instance(), { sx, sy } ); } void Interface::RedrawHeroesIcon( const Heroes & hero, int32_t sx, int32_t sy ) { hero.PortraitRedraw( sx, sy, PORT_SMALL, fheroes2::Display::instance() ); } void Interface::IconsBar::redrawBackground( fheroes2::Image & output, const fheroes2::Point & offset, const int32_t validItemCount ) const { const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); const fheroes2::Sprite & icnadv = fheroes2::AGG::GetICN( isEvilInterface ? ICN::ADVBORDE : ICN::ADVBORD, 0 ); fheroes2::Rect srcrt( icnadv.width() - RADARWIDTH - BORDERWIDTH, RADARWIDTH + 2 * BORDERWIDTH, RADARWIDTH / 2, 32 ); fheroes2::Blit( icnadv, srcrt.x, srcrt.y, output, offset.x, offset.y, srcrt.width, srcrt.height ); srcrt.y = srcrt.y + srcrt.height; int32_t internalOffsetY = offset.y + srcrt.height; srcrt.height = 32; if ( iconsCount > 2 ) { for ( int32_t i = 0; i < iconsCount - 2; ++i ) { fheroes2::Blit( icnadv, srcrt.x, srcrt.y, output, offset.x, internalOffsetY, srcrt.width, srcrt.height ); internalOffsetY += srcrt.height; } } srcrt.y = srcrt.y + 64; srcrt.height = 32; fheroes2::Blit( icnadv, srcrt.x, srcrt.y, output, offset.x, internalOffsetY, srcrt.width, srcrt.height ); for ( int32_t i = validItemCount; i < iconsCount; ++i ) { const fheroes2::Sprite & background = fheroes2::AGG::GetICN( isEvilInterface ? ICN::LOCATORE : ICN::LOCATORS, 1 + i % 8 ); fheroes2::Blit( background, output, offset.x + 5, offset.y + 5 + i * ( IconsBar::GetItemHeight() + 10 ) ); } } void Interface::CastleIcons::RedrawItem( const CASTLE & item, int32_t ox, int32_t oy, bool current ) { if ( item && show ) { RedrawCastleIcon( *item, ox + 5, oy + 5 ); if ( current ) fheroes2::Blit( marker, fheroes2::Display::instance(), ox, oy ); } } void Interface::CastleIcons::RedrawBackground( const fheroes2::Point & pos ) { redrawBackground( fheroes2::Display::instance(), pos, show ? _size() : 0 ); } void Interface::CastleIcons::ActionCurrentUp() { Interface::AdventureMap::Get().SetFocus( GetCurrent() ); } void Interface::CastleIcons::ActionCurrentDn() { Interface::AdventureMap::Get().SetFocus( GetCurrent() ); } void Interface::CastleIcons::ActionListDoubleClick( CASTLE & item ) { if ( item ) { Game::OpenCastleDialog( *item ); } } void Interface::CastleIcons::ActionListSingleClick( CASTLE & item ) { if ( item ) { Interface::AdventureMap & I = Interface::AdventureMap::Get(); I.SetFocus( item ); I.RedrawFocus(); } } void Interface::CastleIcons::ActionListPressRight( CASTLE & item ) { if ( item ) { Dialog::QuickInfoAtPosition( *item, { _topLeftCorner.x - 1, _topLeftCorner.y } ); } } void Interface::CastleIcons::SetShow( bool f ) { IconsBar::SetShow( f ); if ( IconsBar::IsVisible() ) { if ( f ) GetScrollbar().show(); else GetScrollbar().hide(); } } void Interface::CastleIcons::SetPos( int32_t px, int32_t py ) { Castle * selectedCastle = isSelected() ? GetCurrent() : nullptr; const int icnscroll = Settings::Get().isEvilInterfaceEnabled() ? ICN::SCROLLE : ICN::SCROLL; _topLeftCorner = fheroes2::Point( px, py ); SetTopLeft( _topLeftCorner ); setScrollBarArea( { px + iconsCursorWidth + 3, py + 19, 10, iconsCursorHeight * iconsCount - 38 } ); VecCastles & castles = world.GetKingdom( Settings::Get().CurrentColor() ).GetCastles(); const fheroes2::Sprite & originalSlider = fheroes2::AGG::GetICN( icnscroll, 4 ); const fheroes2::Image scrollbarSlider = fheroes2::generateScrollbarSlider( originalSlider, false, iconsCursorHeight * iconsCount - 38, iconsCount, static_cast( castles.size() ), { 0, 0, originalSlider.width(), 8 }, { 0, 7, originalSlider.width(), 8 } ); setScrollBarImage( scrollbarSlider ); SetScrollButtonUp( icnscroll, 0, 1, { px + iconsCursorWidth + 1, py + 1 } ); SetScrollButtonDn( icnscroll, 2, 3, { px + iconsCursorWidth + 1, py + iconsCount * iconsCursorHeight - 15 } ); SetAreaMaxItems( iconsCount ); SetAreaItems( { px, py, iconsCursorWidth, iconsCount * iconsCursorHeight } ); DisableHotkeys( true ); SetListContent( castles ); Reset(); if ( selectedCastle ) { SetCurrent( selectedCastle ); } } void Interface::HeroesIcons::RedrawItem( const HEROES & item, int32_t ox, int32_t oy, bool current ) { if ( item && show ) { RedrawHeroesIcon( *item, ox + 5, oy + 5 ); if ( current ) fheroes2::Blit( marker, fheroes2::Display::instance(), ox, oy ); } } void Interface::HeroesIcons::RedrawBackground( const fheroes2::Point & pos ) { redrawBackground( fheroes2::Display::instance(), pos, show ? _size() : 0 ); } void Interface::HeroesIcons::ActionCurrentUp() { Interface::AdventureMap::Get().SetFocus( GetCurrent(), false ); } void Interface::HeroesIcons::ActionCurrentDn() { Interface::AdventureMap::Get().SetFocus( GetCurrent(), false ); } void Interface::HeroesIcons::ActionListDoubleClick( HEROES & item ) { if ( item ) { Game::OpenHeroesDialog( *item, false, true ); } } void Interface::HeroesIcons::ActionListSingleClick( HEROES & item ) { if ( item ) { Interface::AdventureMap & I = Interface::AdventureMap::Get(); I.SetFocus( item, false ); I.RedrawFocus(); } } void Interface::HeroesIcons::ActionListPressRight( HEROES & item ) { if ( item ) { Dialog::QuickInfoAtPosition( *item, { _topLeftCorner.x - 1, _topLeftCorner.y } ); } } void Interface::HeroesIcons::SetShow( bool f ) { IconsBar::SetShow( f ); if ( IconsBar::IsVisible() ) { if ( f ) GetScrollbar().show(); else GetScrollbar().hide(); } } void Interface::HeroesIcons::SetPos( int32_t px, int32_t py ) { Heroes * selectedHero = isSelected() ? GetCurrent() : nullptr; const int icnscroll = Settings::Get().isEvilInterfaceEnabled() ? ICN::SCROLLE : ICN::SCROLL; _topLeftCorner = fheroes2::Point( px, py ); SetTopLeft( _topLeftCorner ); setScrollBarArea( { px + iconsCursorWidth + 3, py + 19, 10, iconsCursorHeight * iconsCount - 38 } ); VecHeroes & heroes = world.GetKingdom( Settings::Get().CurrentColor() ).GetHeroes(); const fheroes2::Sprite & originalSlider = fheroes2::AGG::GetICN( icnscroll, 4 ); const fheroes2::Image scrollbarSlider = fheroes2::generateScrollbarSlider( originalSlider, false, iconsCursorHeight * iconsCount - 38, iconsCount, static_cast( heroes.size() ), { 0, 0, originalSlider.width(), 8 }, { 0, 7, originalSlider.width(), 8 } ); setScrollBarImage( scrollbarSlider ); SetScrollButtonUp( icnscroll, 0, 1, { px + iconsCursorWidth + 1, py + 1 } ); SetScrollButtonDn( icnscroll, 2, 3, { px + iconsCursorWidth + 1, py + iconsCount * iconsCursorHeight - 15 } ); SetAreaMaxItems( iconsCount ); SetAreaItems( { px, py, iconsCursorWidth, iconsCount * iconsCursorHeight } ); DisableHotkeys( true ); SetListContent( heroes ); Reset(); if ( selectedHero ) { SetCurrent( selectedHero ); } } Interface::IconsPanel::IconsPanel( AdventureMap & basic ) : BorderWindow( { 0, 0, 144, 128 } ) , interface( basic ) , castleIcons( 4, sfMarker ) , heroesIcons( 4, sfMarker ) { sfMarker.resize( iconsCursorWidth, iconsCursorHeight ); sfMarker.reset(); fheroes2::DrawBorder( sfMarker, fheroes2::GetColorId( 0xA0, 0xE0, 0xE0 ) ); } void Interface::IconsPanel::SavePosition() { Settings & conf = Settings::Get(); conf.SetPosIcons( GetRect().getPosition() ); conf.Save( Settings::configFileName ); } void Interface::IconsPanel::SetRedraw( const icons_t type ) const { if ( !IconsBar::IsVisible() ) { return; } switch ( type ) { case ICON_HEROES: interface.setRedraw( REDRAW_HEROES ); break; case ICON_CASTLES: interface.setRedraw( REDRAW_CASTLES ); break; case ICON_ANY: interface.setRedraw( REDRAW_ICONS ); break; default: break; } } void Interface::IconsPanel::SetRedraw() const { SetRedraw( ICON_ANY ); } void Interface::IconsPanel::SetPos( int32_t ox, int32_t oy ) { int32_t iconsCount = 0; if ( Settings::Get().isHideInterfaceEnabled() ) { iconsCount = 2; } else { const int32_t count_h = ( fheroes2::Display::instance().height() - fheroes2::Display::DEFAULT_HEIGHT ) / TILEWIDTH; iconsCount = count_h > 3 ? 8 : ( count_h < 3 ? 4 : 7 ); } BorderWindow::SetPosition( ox, oy, 144, iconsCount * iconsCursorHeight ); heroesIcons.SetIconsCount( iconsCount ); castleIcons.SetIconsCount( iconsCount ); const fheroes2::Rect & rect = GetArea(); heroesIcons.SetPos( rect.x, rect.y ); castleIcons.SetPos( rect.x + 72, rect.y ); } void Interface::IconsPanel::_redraw() { if ( !IconsBar::IsVisible() ) { return; } if ( Settings::Get().isHideInterfaceEnabled() ) { BorderWindow::Redraw(); } heroesIcons.Redraw(); castleIcons.Redraw(); } void Interface::IconsPanel::QueueEventProcessing() { // Move border window if ( Settings::Get().ShowIcons() && BorderWindow::QueueEventProcessing() ) { SetRedraw(); } else if ( heroesIcons.QueueEventProcessing() ) { if ( heroesIcons.isSelected() ) { castleIcons.Unselect(); } SetRedraw(); } else if ( castleIcons.QueueEventProcessing() ) { if ( castleIcons.isSelected() ) { heroesIcons.Unselect(); } SetRedraw(); } } void Interface::IconsPanel::Select( Heroes * const hr ) { castleIcons.Unselect(); heroesIcons.SetCurrent( hr ); } void Interface::IconsPanel::Select( Castle * const cs ) { heroesIcons.Unselect(); castleIcons.SetCurrent( cs ); } void Interface::IconsPanel::ResetIcons( const icons_t type ) { Kingdom & kingdom = world.GetKingdom( Settings::Get().CurrentColor() ); if ( !kingdom.isControlAI() ) { const int icnscroll = Settings::Get().isEvilInterfaceEnabled() ? ICN::SCROLLE : ICN::SCROLL; const fheroes2::Sprite & originalSlider = fheroes2::AGG::GetICN( icnscroll, 4 ); if ( type & ICON_HEROES ) { const fheroes2::Image scrollbarSlider = fheroes2::generateScrollbarSlider( originalSlider, false, iconsCursorHeight * heroesIcons.getIconsCount() - 38, heroesIcons.getIconsCount(), static_cast( kingdom.GetHeroes().size() ), { 0, 0, originalSlider.width(), 8 }, { 0, 7, originalSlider.width(), 8 } ); heroesIcons.setScrollBarImage( scrollbarSlider ); heroesIcons.SetListContent( kingdom.GetHeroes() ); // SetListContent() selects the first item (hero) in the list. We reset it by unselecting. heroesIcons.Unselect(); } if ( type & ICON_CASTLES ) { const fheroes2::Image scrollbarSlider = fheroes2::generateScrollbarSlider( originalSlider, false, iconsCursorHeight * castleIcons.getIconsCount() - 38, castleIcons.getIconsCount(), static_cast( kingdom.GetCastles().size() ), { 0, 0, originalSlider.width(), 8 }, { 0, 7, originalSlider.width(), 8 } ); castleIcons.setScrollBarImage( scrollbarSlider ); castleIcons.SetListContent( kingdom.GetCastles() ); // SetListContent() selects the first item (castle/town) in the list. We reset it by unselecting. castleIcons.Unselect(); } } } void Interface::IconsPanel::HideIcons( const icons_t type ) { if ( type & ICON_HEROES ) { heroesIcons.SetShow( false ); } if ( type & ICON_CASTLES ) { castleIcons.SetShow( false ); } } void Interface::IconsPanel::ShowIcons( const icons_t type ) { if ( type & ICON_HEROES ) { heroesIcons.SetShow( true ); } if ( type & ICON_CASTLES ) { castleIcons.SetShow( true ); } } void Interface::IconsPanel::_redrawIcons( const icons_t type ) { if ( type & ICON_HEROES ) { heroesIcons.Redraw(); } if ( type & ICON_CASTLES ) { castleIcons.Redraw(); } } bool Interface::IconsPanel::IsSelected( const icons_t type ) const { if ( type & ICON_HEROES ) { return heroesIcons.isSelected(); } if ( type & ICON_CASTLES ) { return castleIcons.isSelected(); } return false; } fheroes2-1.0.12+dfsg/src/fheroes2/gui/interface_icons.h000066400000000000000000000153501456075706000226410ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2INTERFACE_ICONS_H #define H2INTERFACE_ICONS_H #include #include #include "image.h" #include "interface_border.h" #include "interface_list.h" #include "math_base.h" class Castle; class Heroes; enum icons_t : uint8_t { ICON_HEROES = 0x01, ICON_CASTLES = 0x02, ICON_ANY = ICON_HEROES | ICON_CASTLES }; namespace Interface { class AdventureMap; using HEROES = Heroes *; using CASTLE = Castle *; class IconsBar { public: IconsBar( const int32_t count, const fheroes2::Image & sf ) : marker( sf ) , iconsCount( count ) , show( true ) { assert( count >= 0 ); } IconsBar( const IconsBar & ) = delete; virtual ~IconsBar() = default; IconsBar & operator=( const IconsBar & ) = delete; void SetShow( bool f ) { show = f; } void redrawBackground( fheroes2::Image & output, const fheroes2::Point & offset, const int32_t validItemCount ) const; void SetIconsCount( const int32_t c ) { iconsCount = c; } int32_t getIconsCount() const { return iconsCount; } static int32_t GetItemWidth(); static int32_t GetItemHeight(); static bool IsVisible(); protected: const fheroes2::Image & marker; int32_t iconsCount; bool show; }; void RedrawHeroesIcon( const Heroes &, int32_t, int32_t ); void RedrawCastleIcon( const Castle &, int32_t, int32_t ); class HeroesIcons final : public Interface::ListBox, public IconsBar { public: HeroesIcons( const int32_t count, const fheroes2::Image & sf ) : IconsBar( count, sf ) {} HeroesIcons( const HeroesIcons & ) = delete; ~HeroesIcons() override = default; HeroesIcons & operator=( const HeroesIcons & ) = delete; void SetPos( int32_t px, int32_t py ); void SetShow( bool ); private: using Interface::ListBox::ActionListDoubleClick; using Interface::ListBox::ActionListSingleClick; using Interface::ListBox::ActionListPressRight; void ActionCurrentUp() override; void ActionCurrentDn() override; void ActionListDoubleClick( HEROES & ) override; void ActionListSingleClick( HEROES & ) override; void ActionListPressRight( HEROES & ) override; void RedrawItem( const HEROES & item, int32_t ox, int32_t oy, bool current ) override; void RedrawBackground( const fheroes2::Point & ) override; fheroes2::Point _topLeftCorner; }; class CastleIcons final : public Interface::ListBox, public IconsBar { public: CastleIcons( const int32_t count, const fheroes2::Image & sf ) : IconsBar( count, sf ) {} CastleIcons( const CastleIcons & ) = delete; ~CastleIcons() override = default; CastleIcons & operator=( const CastleIcons & ) = delete; void SetPos( int32_t px, int32_t py ); void SetShow( bool ); private: using Interface::ListBox::ActionListDoubleClick; using Interface::ListBox::ActionListSingleClick; using Interface::ListBox::ActionListPressRight; void ActionCurrentUp() override; void ActionCurrentDn() override; void ActionListDoubleClick( CASTLE & ) override; void ActionListSingleClick( CASTLE & ) override; void ActionListPressRight( CASTLE & ) override; void RedrawItem( const CASTLE & item, int32_t ox, int32_t oy, bool current ) override; void RedrawBackground( const fheroes2::Point & ) override; fheroes2::Point _topLeftCorner; }; class IconsPanel final : public BorderWindow { public: explicit IconsPanel( AdventureMap & basic ); IconsPanel( const IconsPanel & ) = delete; ~IconsPanel() override = default; IconsPanel & operator=( const IconsPanel & ) = delete; void SetPos( int32_t ox, int32_t oy ) override; void SavePosition() override; void SetRedraw() const; void SetRedraw( const icons_t type ) const; void QueueEventProcessing(); void Select( Heroes * const ); void Select( Castle * const ); bool IsSelected( const icons_t type ) const; void ResetIcons( const icons_t type ); void HideIcons( const icons_t type ); void ShowIcons( const icons_t type ); // Do not call this method directly, use Interface::AdventureMap::redraw() instead to avoid issues in the "no interface" mode. // The name of this method starts from _ on purpose to do not mix with other public methods. void _redraw(); // The name of this method starts from _ on purpose to do not mix with other public methods. void _redrawIcons( const icons_t type ); private: AdventureMap & interface; fheroes2::Image sfMarker; CastleIcons castleIcons; HeroesIcons heroesIcons; }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/gui/interface_itemsbar.h000066400000000000000000000412571456075706000233410ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2012 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2INTERFACE_ITEMSBAR_H #define H2INTERFACE_ITEMSBAR_H #include #include #include #include "gamedefs.h" #include "image.h" #include "localevent.h" namespace Interface { template class ItemsBar { public: ItemsBar() = default; virtual ~ItemsBar() = default; virtual void RedrawBackground( const fheroes2::Rect &, fheroes2::Image & ) = 0; virtual void RedrawItem( Item &, const fheroes2::Rect &, fheroes2::Image & ) = 0; void setTableSize( const fheroes2::Size & size ) { assert( size.width > 0 && size.height > 0 ); tableSize = size; calculateRoi(); } void SetContent( std::vector & content ) { items.clear(); items.reserve( content.size() ); for ( Item & item : content ) { items.push_back( &item ); } SetContentItems(); } void setRenderingOffset( const fheroes2::Point & offset ) { renderingRoi.x = offset.x; renderingRoi.y = offset.y; } void setSingleItemSize( const fheroes2::Size & size ) { assert( size.width > 0 && size.height > 0 ); singleItemRoi = size; calculateRoi(); } void setInBetweenItemsOffset( const fheroes2::Size & offset ) { offsetBetweenItems = offset; calculateRoi(); } Item * GetItem( const fheroes2::Point & pt ) { ItemsIterator posItem = GetItemIter( pt ); return posItem != items.end() ? *posItem : nullptr; } const fheroes2::Rect & GetArea() const { return renderingRoi; } void Redraw( fheroes2::Image & output ) { fheroes2::Point itemOffset{ renderingRoi.x, renderingRoi.y }; for ( int32_t y = 0; y < tableSize.height; ++y ) { for ( int32_t x = 0; x < tableSize.width; ++x ) { RedrawBackground( { itemOffset.x, itemOffset.y, singleItemRoi.width, singleItemRoi.height }, output ); itemOffset.x += offsetBetweenItems.width + singleItemRoi.width; } itemOffset.x = renderingRoi.x; itemOffset.y += offsetBetweenItems.height + singleItemRoi.height; } itemOffset = { renderingRoi.x, renderingRoi.y }; ItemsIterator itemIter = GetTopItemIter(); for ( int32_t y = 0; y < tableSize.height; ++y ) { for ( int32_t x = 0; x < tableSize.width; ++x ) { if ( itemIter == items.end() ) { return; } RedrawItemIter( itemIter, { itemOffset.x, itemOffset.y, singleItemRoi.width, singleItemRoi.height }, output ); ++itemIter; itemOffset.x += offsetBetweenItems.width + singleItemRoi.width; } itemOffset.x = renderingRoi.x; itemOffset.y += offsetBetweenItems.height + singleItemRoi.height; } } virtual bool ActionBarLeftMouseSingleClick( Item & ) { return false; } virtual bool ActionBarRightMouseHold( Item & ) { return false; } virtual bool ActionBarCursor( Item & ) { return false; } bool QueueEventProcessing() { if ( isItemsEmpty() ) { return false; } const fheroes2::Point & cursor = LocalEvent::Get().GetMouseCursor(); return ActionCursorItemIter( cursor, GetItemIterPos( cursor ) ); } protected: // Since we store pointers and the number of elements do not change in a container, vector is the most efficient container to be used. using ItemsIterator = typename std::vector::iterator; using ItemIterPos = std::pair; std::vector items; virtual void SetContentItems() { // Do nothing. } ItemsIterator GetBeginItemIter() { return items.begin(); } ItemsIterator GetEndItemIter() { return items.end(); } virtual ItemsIterator GetTopItemIter() { return items.begin(); } virtual ItemsIterator GetCurItemIter() { return items.end(); } virtual void RedrawItemIter( ItemsIterator it, const fheroes2::Rect & pos, fheroes2::Image & output ) { RedrawItem( **it, pos, output ); } virtual bool ActionCursorItemIter( const fheroes2::Point &, ItemIterPos iterPos ) { if ( iterPos.first == GetEndItemIter() ) { return false; } if ( ActionBarCursor( **iterPos.first ) ) { return true; } LocalEvent & le = LocalEvent::Get(); if ( le.MouseClickLeft( iterPos.second ) ) { return ActionBarLeftMouseSingleClick( **iterPos.first ); } if ( le.MousePressRight( iterPos.second ) ) { return ActionBarRightMouseHold( **iterPos.first ); } return false; } bool isItemsEmpty() { return items.empty(); } ItemsIterator GetItemIter( const fheroes2::Point & pt ) { return GetItemIterPos( pt ).first; } ItemIterPos GetItemIterPos( const fheroes2::Point & position ) { fheroes2::Rect dstrt( renderingRoi.x, renderingRoi.y, singleItemRoi.width, singleItemRoi.height ); ItemsIterator posItem = GetTopItemIter(); for ( int32_t y = 0; y < tableSize.height; ++y ) { for ( int32_t x = 0; x < tableSize.width; ++x ) { if ( posItem != items.end() ) { if ( dstrt & position ) return ItemIterPos( posItem, dstrt ); ++posItem; } dstrt.x += offsetBetweenItems.width + singleItemRoi.width; } dstrt.x = renderingRoi.x; dstrt.y += offsetBetweenItems.height + singleItemRoi.height; } return { items.end(), {} }; } private: void calculateRoi() { if ( tableSize.width > 0 ) { renderingRoi.width = tableSize.width * singleItemRoi.width + ( tableSize.width - 1 ) * offsetBetweenItems.width; } else { renderingRoi.width = 0; } if ( tableSize.height > 0 ) { renderingRoi.height = tableSize.height * singleItemRoi.height + ( tableSize.height - 1 ) * offsetBetweenItems.height; } else { renderingRoi.height = 0; } } fheroes2::Rect renderingRoi; fheroes2::Size singleItemRoi; fheroes2::Size tableSize; fheroes2::Size offsetBetweenItems; }; template class ItemsActionBar : public ItemsBar { public: ItemsActionBar() { ResetSelected(); } ~ItemsActionBar() override = default; void RedrawItem( Item &, const fheroes2::Rect &, fheroes2::Image & ) override { // Do nothing. } virtual void RedrawItem( Item &, const fheroes2::Rect &, bool, fheroes2::Image & ) { // Do nothing. } bool ActionBarCursor( Item & ) override { return false; } virtual bool ActionBarCursor( Item &, Item & ) { return false; } virtual bool ActionBarLeftMouseSingleClick( Item &, Item & ) { return false; } bool ActionBarLeftMouseSingleClick( Item & ) override { return false; } virtual bool ActionBarLeftMouseDoubleClick( Item & item ) { return ActionBarLeftMouseSingleClick( item ); } virtual bool ActionBarLeftMouseRelease( Item & ) { return false; } virtual bool ActionBarLeftMouseRelease( Item &, Item & ) { return false; } virtual bool ActionBarRightMouseHold( Item &, Item & ) { return false; } virtual bool ActionBarLeftMouseHold( Item &, const fheroes2::Rect & ) { return false; } virtual bool ActionBarLeftMouseHold( Item &, Item & ) { return false; } bool ActionBarRightMouseHold( Item & ) override { return false; } virtual bool ActionBarRightMouseSingleClick( Item & ) { return false; } virtual bool ActionBarRightMouseSingleClick( Item &, Item & ) { return false; } Item * GetSelectedItem() { return *GetCurItemIter(); } fheroes2::Rect * GetSelectedPos() { return &curItemPos.second; } int32_t GetSelectedIndex() { return std::distance( ItemsBar::GetBeginItemIter(), GetCurItemIter() ); } bool isSelected() { return GetCurItemIter() != ItemsBar::GetEndItemIter(); } void ResetSelected() { topItem = ItemsBar::GetBeginItemIter(); curItemPos = ItemIterPos( ItemsBar::items.end(), fheroes2::Rect() ); } bool QueueEventProcessing() { return ItemsBar::QueueEventProcessing(); } bool QueueEventProcessing( ItemsActionBar & other ) { const LocalEvent & le = LocalEvent::Get(); const fheroes2::Point & cursor = le.GetMouseCursor(); if ( ItemsBar::isItemsEmpty() && other.isItemsEmpty() ) { return false; } if ( other.GetItem( le.GetMousePressLeft() ) && ActionCrossItemBarDrag( cursor, other ) ) { return true; } return other.isSelected() ? ActionCursorItemIter( cursor, other ) : ActionCursorItemIter( cursor, ItemsBar::GetItemIterPos( cursor ) ); } protected: using ItemsIterator = typename ItemsBar::ItemsIterator; using ItemIterPos = typename ItemsBar::ItemIterPos; ItemsIterator topItem; ItemIterPos curItemPos; ItemsIterator GetTopItemIter() override { return topItem; } ItemsIterator GetCurItemIter() override { return curItemPos.first; } void SetContentItems() override { ResetSelected(); } void RedrawItemIter( ItemsIterator it, const fheroes2::Rect & pos, fheroes2::Image & output ) override { RedrawItem( **it, pos, GetCurItemIter() == it, output ); } bool ActionCrossItemBarDrag( const fheroes2::Point & cursor, ItemsActionBar & other ) { const LocalEvent & le = LocalEvent::Get(); Item * otherItemPress = other.GetItem( le.GetMousePressLeft() ); assert( otherItemPress != nullptr ); ItemIterPos iterPos1 = ItemsBar::GetItemIterPos( cursor ); if ( iterPos1.first == ItemsBar::GetEndItemIter() ) return false; if ( le.MousePressLeft( iterPos1.second ) ) { return ActionBarLeftMouseHold( **iterPos1.first, *otherItemPress ); } if ( le.MouseReleaseLeft( iterPos1.second ) ) { if ( ActionBarLeftMouseRelease( **iterPos1.first, *otherItemPress ) ) { other.ResetSelected(); } return true; } return false; } bool ActionCursorItemIter( const fheroes2::Point &, ItemIterPos iterPos ) override { if ( iterPos.first == ItemsBar::GetEndItemIter() ) { return false; } if ( ActionBarCursor( **iterPos.first ) ) { return true; } LocalEvent & le = LocalEvent::Get(); if ( le.MouseClickLeft( iterPos.second ) ) { if ( iterPos.first == GetCurItemIter() ) { return ActionBarLeftMouseDoubleClick( **iterPos.first ); } if ( ActionBarLeftMouseSingleClick( **iterPos.first ) ) { curItemPos = iterPos; } else { ResetSelected(); } return true; } if ( le.MousePressLeft( iterPos.second ) ) { return ActionBarLeftMouseHold( **iterPos.first, iterPos.second ); } if ( le.MouseReleaseLeft( iterPos.second ) ) { return ActionBarLeftMouseRelease( **iterPos.first ); } if ( le.MouseClickRight( iterPos.second ) ) { return ActionBarRightMouseSingleClick( **iterPos.first ); } if ( le.MousePressRight( iterPos.second ) ) { return ActionBarRightMouseHold( **iterPos.first ); } return false; } bool ActionCursorItemIter( const fheroes2::Point & cursor, ItemsActionBar & other ) { ItemIterPos iterPos1 = ItemsBar::GetItemIterPos( cursor ); if ( iterPos1.first == ItemsBar::GetEndItemIter() ) { return false; } ItemIterPos iterPos2 = other.curItemPos; if ( ActionBarCursor( **iterPos1.first, **iterPos2.first ) ) { return true; } LocalEvent & le = LocalEvent::Get(); if ( le.MouseClickLeft( iterPos1.second ) ) { if ( ActionBarLeftMouseSingleClick( **iterPos1.first, **iterPos2.first ) ) curItemPos = iterPos1; else ResetSelected(); other.ResetSelected(); return true; } if ( le.MousePressLeft( iterPos1.second ) ) { return ActionBarLeftMouseHold( **iterPos1.first, **iterPos2.first ); } // Let the ActionCrossItemBarDrag() handle the case of MouseReleaseLeft() if ( le.MouseClickRight( iterPos1.second ) ) { ActionBarRightMouseSingleClick( **iterPos1.first, **iterPos2.first ); other.ResetSelected(); // has to return true to display selection reset return true; } if ( le.MousePressRight( iterPos1.second ) ) { return ActionBarRightMouseHold( **iterPos1.first, **iterPos2.first ); } return false; } }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/gui/interface_list.h000066400000000000000000000436651456075706000225130ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2INTERFACE_LIST_H #define H2INTERFACE_LIST_H #include #include "localevent.h" #include "ui_button.h" #include "ui_scrollbar.h" namespace Interface { class ListBasic { public: virtual ~ListBasic() = default; virtual bool IsNeedRedraw() const = 0; virtual void Redraw() = 0; virtual bool QueueEventProcessing() = 0; int getTopId() const { return _topId; } protected: int _currentId{ -1 }; int _topId{ -1 }; }; template class ListBox : public ListBasic { public: explicit ListBox( const fheroes2::Point & pt = fheroes2::Point() ) : ptRedraw( pt ) , _timedButtonPgUp( [this]() { return buttonPgUp.isPressed(); } ) , _timedButtonPgDn( [this]() { return buttonPgDn.isPressed(); } ) { buttonPgUp.subscribe( &_timedButtonPgUp ); buttonPgDn.subscribe( &_timedButtonPgDn ); } ~ListBox() override = default; virtual void RedrawItem( const Item &, int32_t ox, int32_t oy, bool current ) = 0; virtual void RedrawBackground( const fheroes2::Point & ) = 0; virtual void ActionCurrentUp() = 0; virtual void ActionCurrentDn() = 0; virtual void ActionListDoubleClick( Item & ) = 0; virtual void ActionListSingleClick( Item & ) = 0; virtual void ActionListPressRight( Item & ) = 0; virtual void ActionListDoubleClick( Item & item, const fheroes2::Point & /*mousePos*/, int32_t /*itemOffsetX*/, int32_t /*itemOffsetY*/ ) { ActionListDoubleClick( item ); } virtual void ActionListSingleClick( Item & item, const fheroes2::Point & /*mousePos*/, int32_t /*itemOffsetX*/, int32_t /*itemOffsetY*/ ) { ActionListSingleClick( item ); } virtual void ActionListPressRight( Item & item, const fheroes2::Point & /*mousePos*/, int32_t /*itemOffsetX*/, int32_t /*itemOffsetY*/ ) { ActionListPressRight( item ); } virtual bool ActionListCursor( Item & /* item */, const fheroes2::Point & /* cursor */ ) { return false; } void SetTopLeft( const fheroes2::Point & tl ) { ptRedraw = tl; } void SetScrollButtonUp( int icn, uint32_t index1, uint32_t index2, const fheroes2::Point & pos ) { buttonPgUp.setICNInfo( icn, index1, index2 ); buttonPgUp.setPosition( pos.x, pos.y ); } void SetScrollButtonDn( int icn, uint32_t index1, uint32_t index2, const fheroes2::Point & pos ) { buttonPgDn.setICNInfo( icn, index1, index2 ); buttonPgDn.setPosition( pos.x, pos.y ); } void setScrollBarArea( const fheroes2::Rect & area ) { _scrollbar.setArea( area ); } void setScrollBarImage( const fheroes2::Image & image ) { _scrollbar.setImage( image ); } fheroes2::Scrollbar & GetScrollbar() { return _scrollbar; } void SetAreaMaxItems( int maxValue ) { maxItems = maxValue; Reset(); } void SetAreaItems( const fheroes2::Rect & rt ) { rtAreaItems = rt; } void SetListContent( std::vector & list ) { content = &list; Reset(); if ( IsValid() ) { _currentId = 0; } } void Reset() { if ( content == nullptr || content->empty() ) { // Empty content. It is non-initialized array, initialize it. _currentId = -1; _topId = -1; _scrollbar.setRange( 0, 0 ); } else { _currentId = -1; // no selection _topId = 0; const int newMaxValue = std::max( _size() - maxItems, 0 ); _scrollbar.setRange( 0, newMaxValue ); } } void DisableHotkeys( bool f ) { useHotkeys = !f; } void Redraw() override { needRedraw = false; RedrawBackground( ptRedraw ); buttonPgUp.draw(); buttonPgDn.draw(); _scrollbar.redraw(); Verify(); // reset values if they are wrong if ( IsValid() ) { // we have something to display int id = _topId; const int end = ( _topId + maxItems ) > _size() ? _size() - _topId : _topId + maxItems; for ( ; id < end; ++id ) { RedrawItem( ( *content )[id], rtAreaItems.x, rtAreaItems.y + ( id - _topId ) * rtAreaItems.height / maxItems, id == _currentId ); } } } bool IsValid() const { return content != nullptr && !content->empty() && _topId >= 0 && _topId < _size() && _currentId < _size() && maxItems > 0; } bool IsNeedRedraw() const override { return needRedraw; } Item & GetCurrent() // always call this function only after IsValid()! { return ( *content )[_currentId]; } Item * GetFromPosition( const fheroes2::Point & mp ) { Verify(); if ( !IsValid() ) { return nullptr; } if ( mp.y < rtAreaItems.y || mp.y >= rtAreaItems.y + rtAreaItems.height ) { // Out of boundaries. return nullptr; } if ( mp.x < rtAreaItems.x || mp.x >= rtAreaItems.x + rtAreaItems.width ) { // Out of boundaries. return nullptr; } const int id = ( mp.y - rtAreaItems.y ) * maxItems / rtAreaItems.height; if ( _topId + id >= _size() ) { // Out of items. return nullptr; } return &( *content )[_topId + id]; } void SetCurrent( size_t posId ) { if ( posId < content->size() ) { _currentId = static_cast( posId ); } SetCurrentVisible(); } void SetCurrentVisible() { Verify(); if ( !IsValid() ) { Reset(); return; } if ( _currentId >= 0 && ( _topId > _currentId || _topId + maxItems <= _currentId ) ) { // out of view if ( _topId > _currentId ) { // Scroll up, put current id on top. _topId = _currentId; } else if ( _topId + maxItems <= _currentId ) { // Scroll down, put current id at bottom. _topId = _currentId + 1 - maxItems; } } UpdateScrollbarRange(); _scrollbar.moveToIndex( _topId ); } // Move visible area to the position with given element ID being on the top of the list. void setTopVisibleItem( const int topId ) { Verify(); if ( !IsValid() ) { Reset(); return; } _topId = std::max( 0, std::min( topId, _size() - maxItems ) ); UpdateScrollbarRange(); _scrollbar.moveToIndex( _topId ); } void SetCurrent( const Item & item ) { const typename std::vector::iterator pos = std::find( content->begin(), content->end(), item ); if ( pos == content->end() ) { Reset(); } else { _currentId = static_cast( pos - content->begin() ); } SetCurrentVisible(); } void RemoveSelected() { if ( content != nullptr && _currentId >= 0 && _currentId < _size() ) { content->erase( content->begin() + _currentId ); // List item is removed, so we need to redraw the list. needRedraw = true; } } bool isSelected() const { return IsValid() && _currentId >= 0; } void Unselect() { Verify(); if ( IsValid() ) { _currentId = -1; // List item is unselected, so we need to redraw the list with no selected elements. needRedraw = true; } } bool QueueEventProcessing() override { LocalEvent & le = LocalEvent::Get(); le.MousePressLeft( buttonPgUp.area() ) ? buttonPgUp.drawOnPress() : buttonPgUp.drawOnRelease(); le.MousePressLeft( buttonPgDn.area() ) ? buttonPgDn.drawOnPress() : buttonPgDn.drawOnRelease(); if ( !IsValid() ) { return false; } if ( useHotkeys && le.KeyPress( fheroes2::Key::KEY_PAGE_UP ) && ( _topId > 0 ) ) { needRedraw = true; if ( _topId > maxItems ) { _topId -= maxItems; } else { _topId = 0; } UpdateScrollbarRange(); _scrollbar.moveToIndex( _topId ); return true; } if ( useHotkeys && le.KeyPress( fheroes2::Key::KEY_PAGE_DOWN ) && ( _topId + maxItems < _size() ) ) { needRedraw = true; _topId += maxItems; if ( _topId + maxItems >= _size() ) { _topId = _size() - maxItems; } UpdateScrollbarRange(); _scrollbar.moveToIndex( _topId ); return true; } if ( useHotkeys && le.KeyPress( fheroes2::Key::KEY_UP ) && ( _currentId > 0 ) ) { needRedraw = true; --_currentId; SetCurrentVisible(); ActionCurrentUp(); return true; } if ( useHotkeys && le.KeyPress( fheroes2::Key::KEY_DOWN ) && ( _currentId + 1 < _size() ) ) { needRedraw = true; ++_currentId; SetCurrentVisible(); ActionCurrentDn(); return true; } if ( useHotkeys && le.KeyPress( fheroes2::Key::KEY_HOME ) && ( _topId > 0 ) ) { needRedraw = true; _topId = 0; UpdateScrollbarRange(); _scrollbar.moveToIndex( _topId ); return true; } if ( useHotkeys && le.KeyPress( fheroes2::Key::KEY_END ) && ( _topId + maxItems < _size() ) ) { needRedraw = true; _topId = _size() - maxItems; UpdateScrollbarRange(); _scrollbar.moveToIndex( _topId ); return true; } if ( ( le.MouseClickLeft( buttonPgUp.area() ) || le.MouseWheelUp( rtAreaItems ) || le.MouseWheelUp( _scrollbar.getArea() ) || _timedButtonPgUp.isDelayPassed() ) && ( _topId > 0 ) ) { needRedraw = true; --_topId; _scrollbar.backward(); return true; } if ( ( le.MouseClickLeft( buttonPgDn.area() ) || le.MouseWheelDn( rtAreaItems ) || le.MouseWheelDn( _scrollbar.getArea() ) || _timedButtonPgDn.isDelayPassed() ) && ( _topId + maxItems < _size() ) ) { needRedraw = true; ++_topId; _scrollbar.forward(); return true; } if ( le.MousePressLeft( _scrollbar.getArea() ) && ( _size() > maxItems ) ) { const fheroes2::Point mousePosition = le.GetMouseCursor(); const int32_t prevScrollbarX = _scrollbar.x(); const int32_t prevScrollbarY = _scrollbar.y(); UpdateScrollbarRange(); _scrollbar.moveToPos( mousePosition ); // We don't need to render the scrollbar if it's position is not changed. if ( ( _scrollbar.x() == prevScrollbarX ) && ( _scrollbar.y() == prevScrollbarY ) ) { return false; } _topId = _scrollbar.currentIndex(); needRedraw = true; _updateScrollbar = true; return true; } if ( _updateScrollbar ) { _updateScrollbar = false; if ( _scrollbar.updatePosition() ) { needRedraw = true; return true; } } const fheroes2::Point & mousePos = le.GetMouseCursor(); if ( rtAreaItems & mousePos ) { // within our rectangle needRedraw = true; const int id = ( mousePos.y - rtAreaItems.y ) * maxItems / rtAreaItems.height + _topId; if ( id < _size() ) { Item & item = ( *content )[static_cast( id )]; // id is always >= 0 const int32_t offsetY = ( id - _topId ) * rtAreaItems.height / maxItems; if ( ActionListCursor( item, mousePos ) ) return true; if ( le.MouseClickLeft( rtAreaItems ) ) { if ( id == _currentId ) { ActionListDoubleClick( item, mousePos, rtAreaItems.x, rtAreaItems.y + offsetY ); } else { _currentId = id; ActionListSingleClick( item, mousePos, rtAreaItems.x, rtAreaItems.y + offsetY ); } return true; } if ( le.MousePressRight( rtAreaItems ) ) { ActionListPressRight( item, mousePos, rtAreaItems.x, rtAreaItems.y + offsetY ); return true; } } needRedraw = false; } return false; } protected: bool needRedraw{ false }; fheroes2::Rect rtAreaItems; fheroes2::Button buttonPgUp; fheroes2::Button buttonPgDn; fheroes2::Scrollbar _scrollbar; int VisibleItemCount() const { return maxItems; } int _size() const { return content == nullptr ? 0 : static_cast( content->size() ); } private: std::vector * content{ nullptr }; int maxItems{ 0 }; fheroes2::Point ptRedraw; bool useHotkeys{ true }; bool _updateScrollbar{ false }; fheroes2::TimedEventValidator _timedButtonPgUp; fheroes2::TimedEventValidator _timedButtonPgDn; void Verify() { if ( content == nullptr || content->empty() ) { _currentId = -1; _topId = -1; } else { const int listSize = _size(); const int maxTopId = std::max( listSize - maxItems, 0 ); if ( _currentId >= listSize ) { _currentId = -1; } if ( _topId < 0 || _topId > maxTopId ) { // Top position is out of list boundaries. if ( _currentId == -1 ) { // There is no item selected. Show the list from its very top. _topId = 0; } else { // Set top position to show the selected item. _topId = std::min( _currentId - maxItems / 2, maxTopId ); } } // Update scrollbar range if needed. if ( _scrollbar.maxIndex() != maxTopId ) { _scrollbar.setRange( 0, maxTopId ); _scrollbar.moveToIndex( _topId ); } } } void UpdateScrollbarRange() { const int newMaxValue = _size() - maxItems; const int maxValue = ( content != nullptr && newMaxValue > 0 ) ? newMaxValue : 0; if ( _scrollbar.maxIndex() != maxValue ) { _scrollbar.setRange( 0, maxValue ); } } }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/gui/interface_radar.cpp000066400000000000000000000470721456075706000231600ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "interface_radar.h" #include #include #include "agg_image.h" #include "castle.h" #include "color.h" #include "dialog.h" #include "gamedefs.h" #include "ground.h" #include "heroes.h" #include "icn.h" #include "interface_base.h" #include "interface_gamearea.h" #include "localevent.h" #include "maps.h" #include "maps_tiles.h" #include "mp2.h" #include "players.h" #include "screen.h" #include "settings.h" #include "translations.h" #include "ui_dialog.h" #include "world.h" #ifdef WITH_DEBUG #include "logging.h" #endif namespace { enum : uint8_t { RADARCOLOR = 0xB5, // index palette COLOR_DESERT = 0x76, COLOR_SNOW = 0x0D, COLOR_SWAMP = 0x68, COLOR_WASTELAND = 0xCE, COLOR_BEACH = 0x29, COLOR_LAVA = 0x20, COLOR_DIRT = 0x36, COLOR_GRASS = 0x62, COLOR_WATER = 0x4D, COLOR_ROAD = 0x7A, COLOR_BLUE = 0x47, COLOR_GREEN = 0x5D, COLOR_RED = 0xbd, COLOR_YELLOW = 0x70, COLOR_ORANGE = 0xCA, COLOR_PURPLE = 0x87, COLOR_GRAY = 0x10, COLOR_WHITE = 0x0a, COLOR_BLACK = 0x00 }; uint8_t GetPaletteIndexFromGround( int ground ) { switch ( ground ) { case Maps::Ground::DESERT: return COLOR_DESERT; case Maps::Ground::SNOW: return COLOR_SNOW; case Maps::Ground::SWAMP: return COLOR_SWAMP; case Maps::Ground::WASTELAND: return COLOR_WASTELAND; case Maps::Ground::BEACH: return COLOR_BEACH; case Maps::Ground::LAVA: return COLOR_LAVA; case Maps::Ground::DIRT: return COLOR_DIRT; case Maps::Ground::GRASS: return COLOR_GRASS; case Maps::Ground::WATER: return COLOR_WATER; default: break; } return COLOR_BLACK; } uint8_t GetPaletteIndexFromColor( int color ) { switch ( color ) { case Color::BLUE: return COLOR_BLUE; case Color::GREEN: return COLOR_GREEN; case Color::RED: return COLOR_RED; case Color::YELLOW: return COLOR_YELLOW; case Color::ORANGE: return COLOR_ORANGE; case Color::PURPLE: return COLOR_PURPLE; default: break; } return COLOR_WHITE; } bool getCastleColor( uint8_t & fillColor, const fheroes2::Point & position ) { const Castle * castle = world.getCastle( position ); if ( castle != nullptr ) { fillColor = GetPaletteIndexFromColor( castle->GetColor() ); return true; } return false; } } Interface::Radar::Radar( BaseInterface & interface ) : BorderWindow( { 0, 0, RADARWIDTH, RADARWIDTH } ) , _radarType( RadarType::WorldMap ) , _interface( interface ) { // Initialize radar image (_map) as a single-layer image. _map._disableTransformLayer(); _map.resize( RADARWIDTH, RADARWIDTH ); } Interface::Radar::Radar( const Radar & radar, const fheroes2::Display & display ) : BorderWindow( { display.width() - BORDERWIDTH - RADARWIDTH, BORDERWIDTH, RADARWIDTH, RADARWIDTH } ) , _radarType( RadarType::ViewWorld ) , _interface( radar._interface ) , _roi( 0, 0, world.w(), world.h() ) , _zoom( radar._zoom ) , _hide( false ) { // Initialize radar image (_map) as a single-layer image. _map._disableTransformLayer(); _map.resize( RADARWIDTH, RADARWIDTH ); } void Interface::Radar::SavePosition() { Settings & conf = Settings::Get(); conf.SetPosRadar( GetRect().getPosition() ); conf.Save( Settings::configFileName ); } void Interface::Radar::SetPos( int32_t ox, int32_t oy ) { BorderWindow::SetPosition( ox, oy ); } void Interface::Radar::Build() { SetZoom(); _roi = { 0, 0, world.w(), world.h() }; } void Interface::Radar::SetZoom() { const int32_t worldWidth = world.w(); // Currently we have and support only square size maps. assert( worldWidth == world.h() ); _zoom = static_cast( area.width ) / worldWidth; // Currently we have and support only maps with 36 - 144 tiles width and height. assert( ( _zoom >= 1.0 ) && ( _zoom <= 4.0 ) ); } void Interface::Radar::SetRedraw( const uint32_t redrawMode ) const { // Only radar redraws are allowed here. assert( ( redrawMode & ~( REDRAW_RADAR_CURSOR | REDRAW_RADAR ) ) == 0 ); _interface.setRedraw( redrawMode ); } void Interface::Radar::SetRenderArea( const fheroes2::Rect & roi ) { const Settings & conf = Settings::Get(); // We set ROI only if radar is visible as there will be no render of radar map image if it is hidden. if ( !conf.isHideInterfaceEnabled() || conf.ShowRadar() ) { // "_roi" should not be outside the "world". _roi = roi ^ fheroes2::Rect( 0, 0, world.w(), world.h() ); } } void Interface::Radar::_redraw( const bool redrawMapObjects ) { const Settings & conf = Settings::Get(); if ( conf.isHideInterfaceEnabled() ) { if ( conf.ShowRadar() ) { BorderWindow::Redraw(); } else { // We are in "Hide Interface" mode and radar is turned off so we have nothing to render. // Force set radar ROI for the whole world to be prepared to fully update radar when it will be shown. _roi = { 0, 0, world.w(), world.h() }; return; } } fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::Rect & rect = GetArea(); if ( _hide ) { fheroes2::Blit( fheroes2::AGG::GetICN( ( conf.isEvilInterfaceEnabled() ? ICN::HEROLOGE : ICN::HEROLOGO ), 0 ), display, rect.x, rect.y ); // Force set radar ROI for the whole world to be prepared to fully update radar when it will be shown. _roi = { 0, 0, world.w(), world.h() }; } else { _cursorArea.hide(); if ( redrawMapObjects ) { RedrawObjects( Players::FriendColors(), ViewWorldMode::OnlyVisible ); } fheroes2::Copy( _map, 0, 0, display, rect.x, rect.y, _map.width(), _map.height() ); _cursorArea.show(); RedrawCursor(); } } void Interface::Radar::RedrawForViewWorld( const ViewWorld::ZoomROIs & roi, const ViewWorldMode mode, const bool renderMapObjects ) { _cursorArea.hide(); if ( renderMapObjects ) { RedrawObjects( Players::FriendColors(), mode ); fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::Rect & rect = GetArea(); fheroes2::Copy( _map, 0, 0, display, rect.x, rect.y, _map.width(), _map.height() ); } const fheroes2::Rect roiInTiles = roi.GetROIinTiles(); _cursorArea.show(); RedrawCursor( &roiInTiles ); } void Interface::Radar::redrawForEditor( const bool renderMapObjects ) { _cursorArea.hide(); if ( renderMapObjects ) { RedrawObjects( 0, ViewWorldMode::ViewAll ); const fheroes2::Rect & rect = GetArea(); fheroes2::Copy( _map, 0, 0, fheroes2::Display::instance(), rect.x, rect.y, _map.width(), _map.height() ); } _cursorArea.show(); RedrawCursor(); } void Interface::Radar::RedrawObjects( const int32_t playerColor, const ViewWorldMode flags ) { #ifdef WITH_DEBUG const bool revealAll = ( flags == ViewWorldMode::ViewAll ) || IS_DEVEL(); #else const bool revealAll = flags == ViewWorldMode::ViewAll; #endif uint8_t * radarImage = _map.image(); assert( _roi.x >= 0 && _roi.y >= 0 && ( _roi.width + _roi.x ) <= world.w() && ( _roi.height + _roi.y ) <= world.h() ); // Fill the radar map with black color ( 0 ) only if we are redrawing the entire map. if ( _roi.x == 0 && _roi.y == 0 && _roi.width == world.w() && _roi.height == world.h() ) { std::memset( radarImage, COLOR_BLACK, static_cast( area.width ) * area.height ); } const bool revealMines = revealAll || ( flags == ViewWorldMode::ViewMines ); const bool revealHeroes = revealAll || ( flags == ViewWorldMode::ViewHeroes ); const bool revealTowns = revealAll || ( flags == ViewWorldMode::ViewTowns ); const bool revealArtifacts = revealAll || ( flags == ViewWorldMode::ViewArtifacts ); const bool revealResources = revealAll || ( flags == ViewWorldMode::ViewResources ); const bool revealOnlyVisible = revealAll || ( flags == ViewWorldMode::OnlyVisible ); const int32_t radarWidth = _map.width(); const bool isZoomIn = _zoom > 1.0; const int32_t maxRoiX = _roi.width + _roi.x; const int32_t maxRoiY = _roi.height + _roi.y; const uint8_t * radarYEnd = nullptr; for ( int32_t y = _roi.y; y < maxRoiY; ++y ) { uint8_t * radarY = radarImage + static_cast( y * _zoom ) * radarWidth; if ( isZoomIn ) { radarYEnd = radarImage + static_cast( ( y + 1 ) * _zoom ) * radarWidth; } for ( int32_t x = _roi.x; x < maxRoiX; ++x ) { const Maps::Tiles & tile = world.GetTiles( x, y ); const bool visibleTile = revealAll || !tile.isFog( playerColor ); uint8_t fillColor = 0; const MP2::MapObjectType objectType = tile.GetObject( revealOnlyVisible || revealHeroes ); switch ( objectType ) { case MP2::OBJ_HERO: { if ( visibleTile || revealHeroes ) { const Heroes * hero = world.GetHeroes( { x, y } ); if ( hero ) { fillColor = GetPaletteIndexFromColor( hero->GetColor() ); break; } } continue; } case MP2::OBJ_LIGHTHOUSE: case MP2::OBJ_ALCHEMIST_LAB: case MP2::OBJ_MINE: case MP2::OBJ_SAWMILL: // TODO: Why Lighthouse is in this category? Verify the logic! if ( visibleTile || revealMines ) { fillColor = GetPaletteIndexFromColor( world.ColorCapturedObject( tile.GetIndex() ) ); break; } continue; case MP2::OBJ_NON_ACTION_LIGHTHOUSE: case MP2::OBJ_NON_ACTION_ALCHEMIST_LAB: case MP2::OBJ_NON_ACTION_MINE: case MP2::OBJ_NON_ACTION_SAWMILL: // TODO: Why Lighthouse is in this category? Verify the logic! if ( visibleTile || revealMines ) { const int32_t mainTileIndex = Maps::Tiles::getIndexOfMainTile( tile ); if ( mainTileIndex >= 0 ) { fillColor = GetPaletteIndexFromColor( world.ColorCapturedObject( mainTileIndex ) ); break; } } continue; case MP2::OBJ_ARTIFACT: if ( visibleTile || revealArtifacts ) { fillColor = COLOR_GRAY; break; } continue; case MP2::OBJ_RESOURCE: if ( visibleTile || revealResources ) { fillColor = COLOR_GRAY; break; } continue; default: if ( visibleTile ) { // Castles and Towns can be partially covered by other non-action objects so we need to rely on special storage of castle's tiles. if ( !getCastleColor( fillColor, { x, y } ) ) { // This is a visible tile and not covered by other objects, so fill it with the ground tile data. if ( tile.isRoad() ) { fillColor = COLOR_ROAD; } else { fillColor = GetPaletteIndexFromGround( tile.GetGround() ); if ( objectType == MP2::OBJ_MOUNTAINS || objectType == MP2::OBJ_TREES ) { fillColor += 3; } } } } else if ( revealTowns ) { getCastleColor( fillColor, { x, y } ); } else { // Non visible tile, we have already black radar so skip the render of this tile. continue; } } const size_t offsetX = static_cast( x * _zoom ); uint8_t * radarX = radarY + offsetX; if ( isZoomIn ) { const uint8_t * radarXEnd = radarYEnd + offsetX; const size_t radarXStep = static_cast( ( x + 1 ) * _zoom ) - offsetX; for ( ; radarX != radarXEnd; radarX += radarWidth ) { std::memset( radarX, fillColor, radarXStep ); } } else { *radarX = fillColor; } } } // Reset ROI to full radar image to be able to redraw the mini-map without calling 'SetMapRedraw()'. _roi = { 0, 0, world.w(), world.h() }; } // Redraw radar cursor. RoiRectangle is a rectangle in tile unit of the current radar view. void Interface::Radar::RedrawCursor( const fheroes2::Rect * roiRectangle /* = nullptr */ ) { const Settings & conf = Settings::Get(); if ( conf.isHideInterfaceEnabled() && !conf.ShowRadar() && _radarType != RadarType::ViewWorld ) { return; } const fheroes2::Rect worldSize{ 0, 0, world.w(), world.h() }; if ( worldSize.width < 1 || worldSize.height < 1 ) { return; } const fheroes2::Rect & viewableWorldArea = ( roiRectangle == nullptr ) ? _interface.getGameArea().GetVisibleTileROI() : *roiRectangle; if ( ( viewableWorldArea.width > worldSize.width ) && ( viewableWorldArea.height > worldSize.height ) ) { // We hide the cursor if the whole map is displayed. _cursorArea.resize( 0, 0 ); _cursorArea.reset(); _cursorArea.setPosition( 0, 0 ); } else { const fheroes2::Rect radarWorldArea = worldSize ^ viewableWorldArea; const fheroes2::Rect & totalRenderingArea = GetArea(); const fheroes2::Size actualRenderingArea{ totalRenderingArea.width, totalRenderingArea.height }; const fheroes2::Size cursorSize{ ( radarWorldArea.width * actualRenderingArea.width ) / worldSize.width, ( radarWorldArea.height * actualRenderingArea.height ) / worldSize.height }; if ( _cursorArea.width() != cursorSize.width || _cursorArea.height() != cursorSize.height ) { _cursorArea.resize( cursorSize.width, cursorSize.height ); _cursorArea.reset(); fheroes2::DrawBorder( _cursorArea, RADARCOLOR, 6 ); } _cursorArea.setPosition( totalRenderingArea.x + ( radarWorldArea.x * actualRenderingArea.width ) / worldSize.width, totalRenderingArea.y + ( radarWorldArea.y * actualRenderingArea.height ) / worldSize.height ); } } void Interface::Radar::QueueEventProcessing() { const Settings & conf = Settings::Get(); LocalEvent & le = LocalEvent::Get(); const fheroes2::Rect & rect = GetArea(); const fheroes2::Rect & borderArea = GetRect(); if ( !le.MouseCursor( borderArea ) || le.MouseCursor( rect ) ) { _mouseDraggingMovement = false; } // Move border window if ( conf.ShowRadar() && BorderWindow::QueueEventProcessing() ) { _cursorArea.hide(); _interface.setRedraw( REDRAW_RADAR_CURSOR ); } else if ( le.MouseCursor( rect ) ) { // move cursor if ( le.MouseClickLeft() || le.MousePressLeft() ) { _mouseDraggingMovement = true; const fheroes2::Point & pt = le.GetMouseCursor(); if ( rect & pt ) { GameArea & gamearea = _interface.getGameArea(); fheroes2::Rect visibleROI( gamearea.GetVisibleTileROI() ); const fheroes2::Point prev( visibleROI.x, visibleROI.y ); gamearea.SetCenter( { ( pt.x - rect.x ) * world.w() / rect.width, ( pt.y - rect.y ) * world.h() / rect.height } ); visibleROI = gamearea.GetVisibleTileROI(); if ( prev.x != visibleROI.x || prev.y != visibleROI.y ) { _interface.setRedraw( REDRAW_RADAR_CURSOR ); gamearea.SetRedraw(); } } } else if ( le.MousePressRight( GetRect() ) ) { fheroes2::showStandardTextMessage( _( "World Map" ), _( "A miniature view of the known world. Left click to move viewing area." ), Dialog::ZERO ); } } } bool Interface::Radar::QueueEventProcessingForWorldView( ViewWorld::ZoomROIs & roi ) const { LocalEvent & le = LocalEvent::Get(); const fheroes2::Rect & rect = GetArea(); // move cursor if ( le.MouseCursor( rect ) ) { if ( le.MouseClickLeft() || le.MousePressLeft() ) { const fheroes2::Point & pt = le.GetMouseCursor(); if ( rect & pt ) { const fheroes2::Rect & initROI = roi.GetROIinPixels(); const fheroes2::Point prevCoordsTopLeft( initROI.x, initROI.y ); const fheroes2::Point newCoordsCenter( ( pt.x - rect.x ) * world.w() / rect.width, ( pt.y - rect.y ) * world.h() / rect.height ); const fheroes2::Point newCoordsTopLeft( newCoordsCenter.x - initROI.width / 2, newCoordsCenter.y - initROI.height / 2 ); if ( prevCoordsTopLeft != newCoordsTopLeft ) { return roi.ChangeCenter( { newCoordsCenter.x * TILEWIDTH - TILEWIDTH / 2, newCoordsCenter.y * TILEWIDTH - TILEWIDTH / 2 } ); } } } else if ( le.MousePressRight( GetRect() ) ) { fheroes2::showStandardTextMessage( _( "World Map" ), _( "A miniature view of the known world. Left click to move viewing area." ), Dialog::ZERO ); } else if ( le.MouseWheelUp() ) { return roi.zoomIn( false ); } else if ( le.MouseWheelDn() ) { return roi.zoomOut( false ); } } return false; } fheroes2-1.0.12+dfsg/src/fheroes2/gui/interface_radar.h000066400000000000000000000105341456075706000226160ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2INTERFACE_RADAR_H #define H2INTERFACE_RADAR_H #include #include "image.h" #include "interface_border.h" #include "math_base.h" #include "ui_tool.h" #include "view_world.h" namespace fheroes2 { class Display; } namespace Interface { class BaseInterface; class Radar final : public BorderWindow { public: explicit Radar( BaseInterface & interface ); // Creates a radar with a fixed position at the top right of the screen, // based on an existing radar and suitable for the View World window Radar( const Radar & radar, const fheroes2::Display & display ); Radar( const Radar & ) = delete; ~Radar() override = default; Radar & operator=( const Radar & ) = delete; void SetPos( int32_t ox, int32_t oy ) override; // Set the render redraw flag from Interface::Redraw enumeration: // - 'REDRAW_RADAR' - to redraw the radar map image fully or in ROI and render the cursor over it. // - 'REDRAW_RADAR_CURSOR' - to render the previously generated radar map image and the cursor over it. void SetRedraw( const uint32_t redrawMode ) const; // Set the "need" of render the radar map only in the given 'roi' on next radar Redraw call. void SetRenderArea( const fheroes2::Rect & roi ); void Build(); void RedrawForViewWorld( const ViewWorld::ZoomROIs & roi, ViewWorldMode mode, const bool renderMapObjects ); void redrawForEditor( const bool renderMapObjects ); void SetHide( bool f ) { _hide = f; } void QueueEventProcessing(); bool QueueEventProcessingForWorldView( ViewWorld::ZoomROIs & roi ) const; bool isDragRadar() const { return _mouseDraggingMovement; } // Do not call this method directly, use Interface::AdventureMap::redraw() instead to avoid issues in the "no interface" mode. // The name of this method starts from _ on purpose to do not mix with other public methods. void _redraw( const bool redrawMapObjects ); private: enum class RadarType : char { WorldMap, ViewWorld }; void SavePosition() override; void SetZoom(); void RedrawObjects( const int32_t playerColor, const ViewWorldMode flags ); void RedrawCursor( const fheroes2::Rect * roiRectangle = nullptr ); RadarType _radarType; BaseInterface & _interface; fheroes2::Image _map; fheroes2::MovableSprite _cursorArea; fheroes2::Rect _roi; double _zoom{ 1.0 }; bool _hide{ true }; bool _mouseDraggingMovement{ false }; }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/gui/interface_status.cpp000066400000000000000000000430261456075706000234050ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "interface_status.h" #include #include #include #include "agg_image.h" #include "army.h" #include "castle.h" #include "color.h" #include "dialog.h" #include "game.h" #include "game_delays.h" #include "game_interface.h" #include "gamedefs.h" #include "heroes.h" #include "icn.h" #include "image.h" #include "interface_base.h" #include "kingdom.h" #include "localevent.h" #include "math_base.h" #include "players.h" #include "resource.h" #include "screen.h" #include "settings.h" #include "tools.h" #include "translations.h" #include "ui_dialog.h" #include "ui_text.h" #include "world.h" namespace { const uint32_t resourceWindowExpireTime = 2500; } Interface::StatusWindow::StatusWindow( BaseInterface & interface ) : BorderWindow( { 0, 0, 144, 72 } ) , _interface( interface ) , _state( StatusType::STATUS_UNKNOWN ) , lastResource( Resource::UNKNOWN ) , countLastResource( 0 ) , turn_progress( 0 ) , showLastResourceDelay( resourceWindowExpireTime ) {} void Interface::StatusWindow::Reset() { _state = StatusType::STATUS_DAY; lastResource = Resource::UNKNOWN; countLastResource = 0; } void Interface::StatusWindow::SavePosition() { Settings & conf = Settings::Get(); conf.SetPosStatus( GetRect().getPosition() ); conf.Save( Settings::configFileName ); } void Interface::StatusWindow::SetRedraw() const { _interface.setRedraw( REDRAW_STATUS ); } void Interface::StatusWindow::SetPos( int32_t ox, int32_t oy ) { uint32_t ow = 144; uint32_t oh = 72; if ( !Settings::Get().isHideInterfaceEnabled() ) { oh = fheroes2::Display::instance().height() - oy - BORDERWIDTH; } BorderWindow::SetPosition( ox, oy, ow, oh ); } void Interface::StatusWindow::SetState( const StatusType status ) { // SetResource() should be used to set this status assert( status != StatusType::STATUS_RESOURCE ); if ( _state != StatusType::STATUS_RESOURCE ) { _state = status; } } void Interface::StatusWindow::_redraw() const { const Settings & conf = Settings::Get(); if ( conf.isHideInterfaceEnabled() && !conf.ShowStatus() ) { // The window is hidden. return; } const fheroes2::Rect & pos = GetArea(); if ( conf.isHideInterfaceEnabled() ) { fheroes2::Fill( fheroes2::Display::instance(), pos.x, pos.y, pos.width, pos.height, fheroes2::GetColorId( 0x51, 0x31, 0x18 ) ); BorderWindow::Redraw(); } else { DrawBackground(); } // Do not draw anything if the game hasn't really started yet if ( world.CountDay() == 0 ) { return; } // draw info: Day and Funds and Army const fheroes2::Sprite & ston = fheroes2::AGG::GetICN( conf.isEvilInterfaceEnabled() ? ICN::STONBAKE : ICN::STONBACK, 0 ); const int32_t stonHeight = ston.height(); if ( StatusType::STATUS_AITURN == _state ) { DrawAITurns(); } else if ( StatusType::STATUS_UNKNOWN != _state && pos.height >= ( stonHeight * 3 + 15 ) ) { DrawDayInfo(); if ( conf.CurrentColor() & Players::HumanColors() ) { DrawKingdomInfo( stonHeight + 5 ); if ( _state != StatusType::STATUS_RESOURCE ) DrawArmyInfo( 2 * stonHeight + 10 ); else DrawResourceInfo( 2 * stonHeight + 10 ); } } else if ( StatusType::STATUS_UNKNOWN != _state && pos.height >= ( stonHeight * 2 + 15 ) ) { DrawDayInfo(); switch ( _state ) { case StatusType::STATUS_FUNDS: DrawKingdomInfo( stonHeight + 5 ); break; case StatusType::STATUS_DAY: case StatusType::STATUS_ARMY: DrawArmyInfo( stonHeight + 5 ); break; case StatusType::STATUS_RESOURCE: DrawResourceInfo( stonHeight + 5 ); break; case StatusType::STATUS_UNKNOWN: case StatusType::STATUS_AITURN: assert( 0 ); // we shouldn't even reach this code break; default: break; } } else { switch ( _state ) { case StatusType::STATUS_DAY: DrawDayInfo(); break; case StatusType::STATUS_FUNDS: DrawKingdomInfo(); break; case StatusType::STATUS_ARMY: DrawArmyInfo(); break; case StatusType::STATUS_RESOURCE: DrawResourceInfo(); break; case StatusType::STATUS_UNKNOWN: case StatusType::STATUS_AITURN: assert( 0 ); // we shouldn't even reach this code break; default: break; } } } void Interface::StatusWindow::NextState() { const int32_t areaHeight = GetArea().height; const fheroes2::Sprite & ston = fheroes2::AGG::GetICN( Settings::Get().isEvilInterfaceEnabled() ? ICN::STONBAKE : ICN::STONBACK, 0 ); const int32_t stonHeight = ston.height(); const bool skipDayStatus = areaHeight >= ( stonHeight * 2 + 15 ) && areaHeight < ( stonHeight * 3 + 15 ); if ( StatusType::STATUS_DAY == _state ) _state = StatusType::STATUS_FUNDS; else if ( StatusType::STATUS_FUNDS == _state ) _state = ( GameFocus::UNSEL == GetFocusType() ? StatusType::STATUS_DAY : StatusType::STATUS_ARMY ); else if ( StatusType::STATUS_ARMY == _state ) { if ( skipDayStatus ) { _state = StatusType::STATUS_FUNDS; } else { _state = StatusType::STATUS_DAY; } } else if ( StatusType::STATUS_RESOURCE == _state ) _state = StatusType::STATUS_ARMY; if ( _state == StatusType::STATUS_ARMY ) { const Castle * castle = GetFocusCastle(); // skip empty army for castle if ( castle && !castle->GetArmy().isValid() ) NextState(); } } void Interface::StatusWindow::DrawKingdomInfo( int oh ) const { const fheroes2::Rect & pos = GetArea(); const Kingdom & myKingdom = world.GetKingdom( Settings::Get().CurrentColor() ); // sprite all resource fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::Blit( fheroes2::AGG::GetICN( ICN::RESSMALL, 0 ), display, pos.x + 6, pos.y + 3 + oh ); // count castle fheroes2::Text text( std::to_string( myKingdom.GetCountCastle() ), fheroes2::FontType::smallWhite() ); text.draw( pos.x + 26 - text.width() / 2, pos.y + 30 + oh, display ); // count town text.set( std::to_string( myKingdom.GetCountTown() ), fheroes2::FontType::smallWhite() ); text.draw( pos.x + 78 - text.width() / 2, pos.y + 30 + oh, display ); // count gold text.set( std::to_string( myKingdom.GetFunds().Get( Resource::GOLD ) ), fheroes2::FontType::smallWhite() ); text.draw( pos.x + 122 - text.width() / 2, pos.y + 30 + oh, display ); // count wood text.set( fheroes2::abbreviateNumber( myKingdom.GetFunds().Get( Resource::WOOD ) ), fheroes2::FontType::smallWhite() ); text.draw( pos.x + 15 - text.width() / 2, pos.y + 60 + oh, display ); // count mercury text.set( fheroes2::abbreviateNumber( myKingdom.GetFunds().Get( Resource::MERCURY ) ), fheroes2::FontType::smallWhite() ); text.draw( pos.x + 37 - text.width() / 2, pos.y + 60 + oh, display ); // count ore text.set( fheroes2::abbreviateNumber( myKingdom.GetFunds().Get( Resource::ORE ) ), fheroes2::FontType::smallWhite() ); text.draw( pos.x + 60 - text.width() / 2, pos.y + 60 + oh, display ); // count sulfur text.set( fheroes2::abbreviateNumber( myKingdom.GetFunds().Get( Resource::SULFUR ) ), fheroes2::FontType::smallWhite() ); text.draw( pos.x + 84 - text.width() / 2, pos.y + 60 + oh, display ); // count crystal text.set( fheroes2::abbreviateNumber( myKingdom.GetFunds().Get( Resource::CRYSTAL ) ), fheroes2::FontType::smallWhite() ); text.draw( pos.x + 108 - text.width() / 2, pos.y + 60 + oh, display ); // count gems text.set( fheroes2::abbreviateNumber( myKingdom.GetFunds().Get( Resource::GEMS ) ), fheroes2::FontType::smallWhite() ); text.draw( pos.x + 130 - text.width() / 2, pos.y + 60 + oh, display ); } void Interface::StatusWindow::DrawDayInfo( int oh ) const { const fheroes2::Rect & pos = GetArea(); const int dayOfWeek = world.GetDay(); const int weekOfMonth = world.GetWeek(); const uint32_t month = world.GetMonth(); const int icnType = Settings::Get().isEvilInterfaceEnabled() ? ICN::SUNMOONE : ICN::SUNMOON; uint32_t icnId = dayOfWeek > 1 ? 0 : ( ( weekOfMonth - 1 ) % 4 ) + 1; // Special case if ( dayOfWeek == 1 && weekOfMonth == 1 && month == 1 ) { icnId = 0; } fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::Blit( fheroes2::AGG::GetICN( icnType, icnId ), display, pos.x, pos.y + 1 + oh ); std::string message = _( "Month: %{month} Week: %{week}" ); StringReplace( message, "%{month}", month ); StringReplace( message, "%{week}", weekOfMonth ); fheroes2::Text text( message, fheroes2::FontType::smallWhite() ); text.draw( pos.x + ( pos.width - text.width() ) / 2, pos.y + 32 + oh, display ); message = _( "Day: %{day}" ); StringReplace( message, "%{day}", dayOfWeek ); text.set( message, fheroes2::FontType::normalWhite() ); text.draw( pos.x + ( pos.width - text.width() ) / 2, pos.y + 48 + oh, display ); } void Interface::StatusWindow::SetResource( int res, uint32_t count ) { lastResource = res; countLastResource = count; _state = StatusType::STATUS_RESOURCE; showLastResourceDelay.reset(); } void Interface::StatusWindow::DrawResourceInfo( int oh ) const { const fheroes2::Rect & pos = GetArea(); fheroes2::Display & display = fheroes2::Display::instance(); std::string message = _( "You find a small\nquantity of %{resource}." ); StringReplace( message, "%{resource}", Resource::String( lastResource ) ); fheroes2::Text text{ std::move( message ), fheroes2::FontType::smallWhite() }; text.draw( pos.x, pos.y + 6 + oh, pos.width, display ); const fheroes2::Sprite & spr = fheroes2::AGG::GetICN( ICN::RESOURCE, Resource::getIconIcnIndex( lastResource ) ); fheroes2::Blit( spr, display, pos.x + ( pos.width - spr.width() ) / 2, pos.y + 6 + oh + text.height( pos.width ) ); text.set( std::to_string( countLastResource ), fheroes2::FontType::smallWhite() ); text.draw( pos.x + ( pos.width - text.width() ) / 2, pos.y + oh + text.height() * 2 + spr.height() + 10, display ); } void Interface::StatusWindow::DrawArmyInfo( int oh ) const { const Army * armies = nullptr; if ( GetFocusHeroes() ) armies = &GetFocusHeroes()->GetArmy(); else if ( GetFocusCastle() ) armies = &GetFocusCastle()->GetArmy(); if ( armies ) { const fheroes2::Rect & pos = GetArea(); Army::drawMultipleMonsterLines( *armies, pos.x + 4, pos.y + 1 + oh, 138, true, true ); } } void Interface::StatusWindow::DrawAITurns() const { // restore background DrawBackground(); fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::Sprite & glass = fheroes2::AGG::GetICN( ICN::HOURGLAS, 0 ); const fheroes2::Rect & pos = GetArea(); int32_t dst_x = pos.x + ( pos.width - glass.width() ) / 2; int32_t dst_y = pos.y + ( pos.height - glass.height() ) / 2; fheroes2::Blit( glass, display, dst_x, dst_y ); int color_index = 0; const Settings & conf = Settings::Get(); switch ( conf.CurrentColor() ) { case Color::BLUE: color_index = 0; break; case Color::GREEN: color_index = 1; break; case Color::RED: color_index = 2; break; case Color::YELLOW: color_index = 3; break; case Color::ORANGE: color_index = 4; break; case Color::PURPLE: color_index = 5; break; default: return; } const fheroes2::Sprite & crest = fheroes2::AGG::GetICN( ICN::BRCREST, color_index ); dst_x += 2; dst_y += 2; fheroes2::Blit( crest, display, dst_x, dst_y ); const fheroes2::Sprite & sand = fheroes2::AGG::GetICN( ICN::HOURGLAS, 1 + ( turn_progress % 10 ) ); dst_x += ( glass.width() - sand.width() - sand.x() - 3 ); dst_y += sand.y(); fheroes2::Blit( sand, display, dst_x, dst_y ); } void Interface::StatusWindow::DrawBackground() const { fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::Sprite & icnston = fheroes2::AGG::GetICN( Settings::Get().isEvilInterfaceEnabled() ? ICN::STONBAKE : ICN::STONBACK, 0 ); const fheroes2::Rect & pos = GetArea(); if ( !Settings::Get().isHideInterfaceEnabled() && display.height() - BORDERWIDTH - icnston.height() > pos.y ) { // top const int32_t startY = 11; const int32_t copyHeight = 46; fheroes2::Rect srcrt( 0, 0, icnston.width(), startY ); fheroes2::Point dstpt( pos.x, pos.y ); fheroes2::Blit( icnston, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); // middle srcrt = fheroes2::Rect( 0, startY, icnston.width(), copyHeight ); const int32_t count = ( pos.height - ( icnston.height() - copyHeight ) ) / copyHeight; for ( int32_t i = 0; i < count; ++i ) { dstpt = fheroes2::Point( pos.x, pos.y + copyHeight * i + startY ); fheroes2::Blit( icnston, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); } // bottom srcrt = fheroes2::Rect( 0, startY, icnston.width(), icnston.height() - startY ); dstpt = fheroes2::Point( pos.x, pos.y + pos.height - ( icnston.height() - startY ) ); fheroes2::Blit( icnston, srcrt.x, srcrt.y, display, dstpt.x, dstpt.y, srcrt.width, srcrt.height ); } else { fheroes2::Blit( icnston, display, pos.x, pos.y ); } } void Interface::StatusWindow::QueueEventProcessing() { // Move border window if ( Settings::Get().ShowStatus() && BorderWindow::QueueEventProcessing() ) { SetRedraw(); return; } LocalEvent & le = LocalEvent::Get(); const fheroes2::Rect & drawnArea = GetArea(); if ( le.MouseClickLeft( drawnArea ) ) { NextState(); SetRedraw(); } if ( le.MousePressRight( GetRect() ) ) { const fheroes2::Sprite & ston = fheroes2::AGG::GetICN( Settings::Get().isEvilInterfaceEnabled() ? ICN::STONBAKE : ICN::STONBACK, 0 ); const fheroes2::Rect & pos = GetArea(); const bool isFullInfo = StatusType::STATUS_UNKNOWN != _state && pos.height >= ( ston.height() * 3 + 15 ); if ( isFullInfo ) { fheroes2::showStandardTextMessage( _( "Status Window" ), _( "This window provides information on the status of your hero or kingdom, and shows the date." ), Dialog::ZERO ); } else { fheroes2::showStandardTextMessage( _( "Status Window" ), _( "This window provides information on the status of your hero or kingdom, and shows the date. Left click here to cycle through these windows." ), Dialog::ZERO ); } } } void Interface::StatusWindow::TimerEventProcessing() { if ( _state != StatusType::STATUS_RESOURCE || !showLastResourceDelay.isPassed() ) { return; } switch ( GetFocusType() ) { case GameFocus::HEROES: _state = StatusType::STATUS_ARMY; break; case GameFocus::CASTLE: _state = StatusType::STATUS_FUNDS; break; default: _state = StatusType::STATUS_DAY; break; } SetRedraw(); } void Interface::StatusWindow::DrawAITurnProgress( const uint32_t progressValue ) { // Process events if any before rendering a frame. For instance, updating a mouse cursor position. LocalEvent::Get().HandleEvents( false ); turn_progress = progressValue; _interface.setRedraw( REDRAW_STATUS ); if ( Game::validateAnimationDelay( Game::MAPS_DELAY ) ) { Game::updateAdventureMapAnimationIndex(); _interface.redraw( REDRAW_GAMEAREA ); fheroes2::Display::instance().render(); } } fheroes2-1.0.12+dfsg/src/fheroes2/gui/interface_status.h000066400000000000000000000067171456075706000230600ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2INTERFACE_STATUS_H #define H2INTERFACE_STATUS_H #include #include "interface_border.h" #include "timing.h" namespace Interface { class BaseInterface; enum class StatusType : int { STATUS_UNKNOWN, STATUS_DAY, STATUS_FUNDS, STATUS_ARMY, STATUS_RESOURCE, STATUS_AITURN }; class StatusWindow final : public BorderWindow { public: explicit StatusWindow( BaseInterface & interface ); StatusWindow( const StatusWindow & ) = delete; ~StatusWindow() override = default; StatusWindow & operator=( const StatusWindow & ) = delete; void SetPos( int32_t ox, int32_t oy ) override; void SavePosition() override; void SetRedraw() const; void Reset(); void NextState(); void SetState( const StatusType status ); void SetResource( int, uint32_t ); void DrawAITurnProgress( const uint32_t progressValue ); void QueueEventProcessing(); void TimerEventProcessing(); // Do not call this method directly, use Interface::AdventureMap::redraw() instead to avoid issues in the "no interface" mode. // The name of this method starts from _ on purpose to do not mix with other public methods. void _redraw() const; private: void DrawKingdomInfo( int oh = 0 ) const; void DrawDayInfo( int oh = 0 ) const; void DrawArmyInfo( int oh = 0 ) const; void DrawResourceInfo( int oh = 0 ) const; void DrawBackground() const; void DrawAITurns() const; BaseInterface & _interface; StatusType _state; int lastResource; uint32_t countLastResource; uint32_t turn_progress; fheroes2::TimeDelay showLastResourceDelay; }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/gui/player_info.cpp000066400000000000000000000522211456075706000223460ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "player_info.h" #include #include #include #include #include "agg_image.h" #include "color.h" #include "dialog.h" #include "game.h" #include "icn.h" #include "image.h" #include "localevent.h" #include "maps_fileinfo.h" #include "players.h" #include "race.h" #include "screen.h" #include "settings.h" #include "tools.h" #include "translations.h" #include "ui_dialog.h" #include "ui_text.h" namespace { void changeRaceToNext( Player & player ) { switch ( player.GetRace() ) { case Race::KNGT: player.SetRace( Race::BARB ); break; case Race::BARB: player.SetRace( Race::SORC ); break; case Race::SORC: player.SetRace( Race::WRLK ); break; case Race::WRLK: player.SetRace( Race::WZRD ); break; case Race::WZRD: player.SetRace( Race::NECR ); break; case Race::NECR: player.SetRace( Race::RAND ); break; case Race::RAND: player.SetRace( Race::KNGT ); break; default: // Did you add a new race? Add the logic above assert( 0 ); break; } } void changeRaceToPrev( Player & player ) { switch ( player.GetRace() ) { case Race::KNGT: player.SetRace( Race::RAND ); break; case Race::BARB: player.SetRace( Race::KNGT ); break; case Race::SORC: player.SetRace( Race::BARB ); break; case Race::WRLK: player.SetRace( Race::SORC ); break; case Race::WZRD: player.SetRace( Race::WRLK ); break; case Race::NECR: player.SetRace( Race::WZRD ); break; case Race::RAND: player.SetRace( Race::NECR ); break; default: // Did you add a new race? Add the logic above assert( 0 ); break; } } } void Interface::PlayersInfo::UpdateInfo( Players & players, const fheroes2::Point & playerTypeOffset, const fheroes2::Point & classOffset ) { clear(); const fheroes2::Sprite & playerTypeImage = fheroes2::AGG::GetICN( ICN::NGEXTRA, 3 ); const fheroes2::Sprite & classImage = fheroes2::AGG::GetICN( ICN::NGEXTRA, 51 ); const fheroes2::Sprite & handicapImage = fheroes2::AGG::GetICN( ICN::NGEXTRA, 0 ); const int32_t playerCount = static_cast( players.size() ); // safe to cast as the number of players <= 8. reserve( playerCount ); for ( int32_t i = 0; i < playerCount; ++i ) { emplace_back(); PlayerInfo & info = back(); info.player = players[i]; assert( info.player != nullptr ); info.playerTypeRoi = { playerTypeOffset.x + Game::GetStep4Player( i, playerTypeImage.width(), playerCount ), playerTypeOffset.y, playerTypeImage.width(), playerTypeImage.height() }; info.nameRoi = { info.playerTypeRoi.x, info.playerTypeRoi.y + info.playerTypeRoi.height, info.playerTypeRoi.width, 10 }; info.classRoi = { classOffset.x + Game::GetStep4Player( i, classImage.width(), playerCount ), classOffset.y, classImage.width(), classImage.height() }; info.handicapRoi = { classOffset.x + Game::GetStep4Player( i, handicapImage.width(), playerCount ), classOffset.y + 69, handicapImage.width(), handicapImage.height() }; } } bool Interface::PlayersInfo::SwapPlayers( Player & player1, Player & player2 ) const { const Settings & conf = Settings::Get(); const Maps::FileInfo & mapInfo = conf.getCurrentMapInfo(); const int player1Color = player1.GetColor(); const int player2Color = player2.GetColor(); bool swap = false; if ( player1.isControlAI() == player2.isControlAI() ) { swap = true; } else if ( ( player1Color & mapInfo.AllowCompHumanColors() ) && ( player2Color & mapInfo.AllowCompHumanColors() ) ) { const int humans = conf.GetPlayers().GetColors( CONTROL_HUMAN, true ); if ( humans & player1Color ) { Players::SetPlayerControl( player1Color, CONTROL_AI | CONTROL_HUMAN ); Players::SetPlayerControl( player2Color, CONTROL_HUMAN ); } else { Players::SetPlayerControl( player2Color, CONTROL_AI | CONTROL_HUMAN ); Players::SetPlayerControl( player1Color, CONTROL_HUMAN ); } swap = true; } if ( swap ) { const int player1Race = player1.GetRace(); const int player2Race = player2.GetRace(); const Player::HandicapStatus player1HandicapStatus = player1.getHandicapStatus(); const Player::HandicapStatus player2HandicapStatus = player2.getHandicapStatus(); player2.setHandicapStatus( player1HandicapStatus ); player1.setHandicapStatus( player2HandicapStatus ); if ( player1Race != player2Race && mapInfo.AllowChangeRace( player1Color ) && mapInfo.AllowChangeRace( player2Color ) ) { player1.SetRace( player2Race ); player2.SetRace( player1Race ); } const std::string player1Name = player1.GetName(); const std::string player2Name = player2.GetName(); const std::string player1DefaultName = player1.GetDefaultName(); const std::string player2DefaultName = player2.GetDefaultName(); if ( player2Name == player2DefaultName ) { player1.SetName( player1DefaultName ); } else { player1.SetName( player2Name ); } if ( player1Name == player1DefaultName ) { player2.SetName( player2DefaultName ); } else { player2.SetName( player1Name ); } } return swap; } Player * Interface::PlayersInfo::GetFromOpponentClick( const fheroes2::Point & pt ) { for ( const PlayerInfo & info : *this ) { if ( info.playerTypeRoi & pt ) { return info.player; } } return nullptr; } Player * Interface::PlayersInfo::GetFromOpponentNameClick( const fheroes2::Point & pt ) { for ( const PlayerInfo & info : *this ) { if ( info.nameRoi & pt ) { return info.player; } } return nullptr; } Player * Interface::PlayersInfo::GetFromClassClick( const fheroes2::Point & pt ) { for ( const PlayerInfo & info : *this ) { if ( info.classRoi & pt ) { return info.player; } } return nullptr; } Player * Interface::PlayersInfo::getPlayerFromHandicapRoi( const fheroes2::Point & point ) { for ( const PlayerInfo & info : *this ) { if ( info.handicapRoi & point ) { return info.player; } } return nullptr; } void Interface::PlayersInfo::RedrawInfo( const bool displayInGameInfo ) const { const Settings & conf = Settings::Get(); fheroes2::Display & display = fheroes2::Display::instance(); const Maps::FileInfo & mapInfo = conf.getCurrentMapInfo(); const int32_t playerCount = static_cast( conf.GetPlayers().size() ); const uint32_t humanColors = conf.GetPlayers().GetColors( CONTROL_HUMAN, true ); // We need to render icon shadows and since shadows are drawn on left side from images we have to render images from right to left. for ( auto iter = crbegin(); iter != crend(); ++iter ) { const PlayerInfo & info = *iter; uint32_t playerTypeIcnIndex = 0; if ( humanColors & info.player->GetColor() ) { // Current human. playerTypeIcnIndex = 9 + Color::GetIndex( info.player->GetColor() ); } else if ( mapInfo.ComputerOnlyColors() & info.player->GetColor() ) { // Computer only. playerTypeIcnIndex = 15 + Color::GetIndex( info.player->GetColor() ); } else { // Computer or human. playerTypeIcnIndex = 3 + Color::GetIndex( info.player->GetColor() ); } // wide sprite offset playerTypeIcnIndex += 24; const fheroes2::Sprite & playerIconShadow = fheroes2::AGG::GetICN( ICN::NGEXTRA, 60 ); const fheroes2::Sprite & playerIcon = fheroes2::AGG::GetICN( ICN::NGEXTRA, playerTypeIcnIndex ); fheroes2::Blit( playerIconShadow, display, info.playerTypeRoi.x - 5, info.playerTypeRoi.y + 3 ); fheroes2::Blit( playerIcon, display, info.playerTypeRoi.x, info.playerTypeRoi.y ); if ( currentSelectedPlayer != nullptr && info.player == currentSelectedPlayer ) { // TODO: add an overloaded DrawBorder() function to draw a border inside an image. fheroes2::Image selection( playerIcon.width(), playerIcon.height() ); selection.reset(); fheroes2::DrawBorder( selection, 214 ); fheroes2::Blit( selection, display, info.playerTypeRoi.x, info.playerTypeRoi.y ); } // draw player name fheroes2::Text name( info.player->GetName(), fheroes2::FontType::smallWhite() ); const int32_t maximumTextWidth = playerIcon.width() - 4; name.fitToOneRow( maximumTextWidth ); name.draw( info.playerTypeRoi.x + 2 + ( maximumTextWidth - name.width() ) / 2, info.playerTypeRoi.y + info.playerTypeRoi.height + 1, display ); // 2. redraw class const bool isActivePlayer = displayInGameInfo ? info.player->isPlay() : mapInfo.AllowChangeRace( info.player->GetColor() ); uint32_t classIcnIndex = 0; switch ( info.player->GetRace() ) { case Race::KNGT: classIcnIndex = isActivePlayer ? 51 : 70; break; case Race::BARB: classIcnIndex = isActivePlayer ? 52 : 71; break; case Race::SORC: classIcnIndex = isActivePlayer ? 53 : 72; break; case Race::WRLK: classIcnIndex = isActivePlayer ? 54 : 73; break; case Race::WZRD: classIcnIndex = isActivePlayer ? 55 : 74; break; case Race::NECR: classIcnIndex = isActivePlayer ? 56 : 75; break; case Race::MULT: classIcnIndex = isActivePlayer ? 57 : 76; break; case Race::RAND: assert( !displayInGameInfo ); classIcnIndex = 58; break; default: // Did you add a new race? Add the logic above! assert( 0 ); continue; } const fheroes2::Sprite & classIcon = fheroes2::AGG::GetICN( ICN::NGEXTRA, classIcnIndex ); const fheroes2::Sprite & classIconShadow = fheroes2::AGG::GetICN( ICN::NGEXTRA, 61 ); fheroes2::Blit( classIconShadow, display, info.classRoi.x - 5, info.classRoi.y + 3 ); fheroes2::Blit( classIcon, display, info.classRoi.x, info.classRoi.y ); const char * raceName; if ( playerCount > 4 ) { raceName = Race::DoubleLinedString( info.player->GetRace() ); } else { raceName = Race::String( info.player->GetRace() ); } const int32_t maxClassNameTextWidth = classIcon.width() + 60; const fheroes2::Text text( raceName, fheroes2::FontType::smallWhite() ); text.draw( info.classRoi.x - 31, info.classRoi.y + info.classRoi.height + 4, maxClassNameTextWidth, display ); // Display a handicap icon. uint32_t handicapIcnIndex = 0; if ( humanColors & info.player->GetColor() ) { switch ( info.player->getHandicapStatus() ) { case Player::HandicapStatus::NONE: handicapIcnIndex = 0; break; case Player::HandicapStatus::MILD: handicapIcnIndex = 1; break; case Player::HandicapStatus::SEVERE: handicapIcnIndex = 2; break; default: // Did you add a new handicap status? Add the logic above! assert( 0 ); break; } } else { assert( info.player->getHandicapStatus() == Player::HandicapStatus::NONE ); handicapIcnIndex = 78; } const fheroes2::Sprite & handicapIcon = fheroes2::AGG::GetICN( ICN::NGEXTRA, handicapIcnIndex ); const fheroes2::Sprite & handicapIconShadow = fheroes2::AGG::GetICN( ICN::NGEXTRA, 59 ); fheroes2::Blit( handicapIconShadow, display, info.handicapRoi.x - 5, info.handicapRoi.y + 3 ); fheroes2::Blit( handicapIcon, display, info.handicapRoi.x, info.handicapRoi.y ); } } bool Interface::PlayersInfo::QueueEventProcessing() { Settings & conf = Settings::Get(); const LocalEvent & le = LocalEvent::Get(); if ( le.MousePressRight() ) { const Player * player = GetFromOpponentClick( le.GetMouseCursor() ); if ( player != nullptr ) { fheroes2::showStandardTextMessage( _( "Opponents" ), _( "This lets you change player starting positions and colors. A particular color will always start in a particular location. Some positions may only be played by a computer player or only by a human player." ), Dialog::ZERO ); return true; } player = GetFromClassClick( le.GetMouseCursor() ); if ( player != nullptr ) { fheroes2::showStandardTextMessage( _( "Class" ), _( "This lets you change the class of a player. Classes are not always changeable. Depending on the scenario, a player may receive additional towns and/or heroes not of their primary alignment." ), Dialog::ZERO ); return true; } player = getPlayerFromHandicapRoi( le.GetMouseCursor() ); if ( player != nullptr ) { fheroes2::showMessage( fheroes2::Text( _( "Handicap" ), fheroes2::FontType::normalYellow() ), fheroes2::Text( _( "This lets you change the handicap of a particular player. Only humans may be handicapped. Handicapped players " "start with fewer resources and earn 15 or 30% fewer resources per turn for mild and severe handicaps, " "respectively." ), fheroes2::FontType::normalWhite() ), Dialog::ZERO ); return true; } return false; } if ( le.MouseWheelUp() ) { Player * player = GetFromClassClick( le.GetMouseCursor() ); if ( player != nullptr && conf.getCurrentMapInfo().AllowChangeRace( player->GetColor() ) ) { changeRaceToPrev( *player ); return true; } return false; } if ( le.MouseWheelDn() ) { Player * player = GetFromClassClick( le.GetMouseCursor() ); if ( player != nullptr && conf.getCurrentMapInfo().AllowChangeRace( player->GetColor() ) ) { changeRaceToNext( *player ); return true; } return false; } Player * player = GetFromOpponentClick( le.GetMouseCursor() ); if ( player != nullptr ) { const Maps::FileInfo & fi = conf.getCurrentMapInfo(); if ( conf.IsGameType( Game::TYPE_MULTI ) ) { if ( currentSelectedPlayer == nullptr ) { currentSelectedPlayer = player; } else if ( currentSelectedPlayer == player ) { currentSelectedPlayer = nullptr; } else if ( SwapPlayers( *player, *currentSelectedPlayer ) ) { currentSelectedPlayer = nullptr; } } else { const int playerColor = player->GetColor(); if ( playerColor & fi.colorsAvailableForHumans ) { const int human = conf.GetPlayers().GetColors( CONTROL_HUMAN, true ); if ( playerColor != human ) { Player * currentPlayer = Players::Get( human ); Player * nextPlayer = Players::Get( playerColor ); assert( currentPlayer != nullptr && nextPlayer != nullptr ); const Player::HandicapStatus currentHandicapStatus = currentPlayer->getHandicapStatus(); Players::SetPlayerControl( human, CONTROL_AI | CONTROL_HUMAN ); Players::SetPlayerControl( playerColor, CONTROL_HUMAN ); nextPlayer->setHandicapStatus( currentHandicapStatus ); currentPlayer->setHandicapStatus( Player::HandicapStatus::NONE ); } } } return true; } player = GetFromOpponentNameClick( le.GetMouseCursor() ); if ( player != nullptr ) { std::string str = _( "%{color} player" ); StringReplace( str, "%{color}", Color::String( player->GetColor() ) ); std::string res; if ( Dialog::InputString( str, res ) && !res.empty() ) { player->SetName( res ); } return true; } player = GetFromClassClick( le.GetMouseCursor() ); if ( player != nullptr && conf.getCurrentMapInfo().AllowChangeRace( player->GetColor() ) ) { changeRaceToNext( *player ); return true; } player = getPlayerFromHandicapRoi( le.GetMouseCursor() ); if ( player != nullptr ) { if ( !( player->GetControl() & CONTROL_AI ) ) { switch ( player->getHandicapStatus() ) { case Player::HandicapStatus::NONE: player->setHandicapStatus( Player::HandicapStatus::MILD ); break; case Player::HandicapStatus::MILD: player->setHandicapStatus( Player::HandicapStatus::SEVERE ); break; case Player::HandicapStatus::SEVERE: player->setHandicapStatus( Player::HandicapStatus::NONE ); break; default: // Did you add a new handicap status? Add the logic above! assert( 0 ); break; } } return true; } return false; } bool Interface::PlayersInfo::readOnlyEventProcessing() { const LocalEvent & le = LocalEvent::Get(); if ( !le.MousePressRight() ) { // Read only mode works only for right click events. return false; } const Player * player = getPlayerFromHandicapRoi( le.GetMouseCursor() ); if ( player != nullptr ) { switch ( player->getHandicapStatus() ) { case Player::HandicapStatus::NONE: fheroes2::showMessage( fheroes2::Text( _( "No Handicap" ), fheroes2::FontType::normalYellow() ), fheroes2::Text( _( "No special restrictions on start resources and earning them per turn." ), fheroes2::FontType::normalWhite() ), Dialog::ZERO ); break; case Player::HandicapStatus::MILD: fheroes2::showMessage( fheroes2::Text( _( "Mild Handicap" ), fheroes2::FontType::normalYellow() ), fheroes2::Text( _( "Mild handicapped players start with fewer resources and earn 15% fewer resources per turn." ), fheroes2::FontType::normalWhite() ), Dialog::ZERO ); break; case Player::HandicapStatus::SEVERE: fheroes2::showMessage( fheroes2::Text( _( "Severe Handicap" ), fheroes2::FontType::normalYellow() ), fheroes2::Text( _( "Severe handicapped players start with fewer resources and earn 30% fewer resources per turn." ), fheroes2::FontType::normalWhite() ), Dialog::ZERO ); break; default: // Did you add a new handicap status? Add the logic above! assert( 0 ); break; } return true; } return false; } fheroes2-1.0.12+dfsg/src/fheroes2/gui/player_info.h000066400000000000000000000053241456075706000220150ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include "math_base.h" class Player; class Players; namespace Interface { struct PlayerInfo { Player * player{ nullptr }; fheroes2::Rect playerTypeRoi; fheroes2::Rect classRoi; fheroes2::Rect nameRoi; fheroes2::Rect handicapRoi; }; struct PlayersInfo : std::vector { void UpdateInfo( Players &, const fheroes2::Point & playerTypeOffset, const fheroes2::Point & classOffset ); bool SwapPlayers( Player & player1, Player & player2 ) const; Player * GetFromOpponentClick( const fheroes2::Point & pt ); Player * GetFromOpponentNameClick( const fheroes2::Point & pt ); Player * GetFromClassClick( const fheroes2::Point & pt ); Player * getPlayerFromHandicapRoi( const fheroes2::Point & point ); // displayInGameInfo: use colored and grayscale race icons to distinguish between players who are still in the game and vanquished players respectively void RedrawInfo( const bool displayInGameInfo ) const; void resetSelection() { currentSelectedPlayer = nullptr; } bool QueueEventProcessing(); bool readOnlyEventProcessing(); Player * currentSelectedPlayer{ nullptr }; }; } fheroes2-1.0.12+dfsg/src/fheroes2/gui/skill_bar.cpp000066400000000000000000000276171456075706000220140ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "skill_bar.h" #include #include #include "agg_image.h" #include "dialog.h" #include "dialog_selectitems.h" #include "heroes.h" #include "icn.h" #include "screen.h" #include "skill.h" #include "tools.h" #include "translations.h" #include "ui_dialog.h" #include "ui_text.h" namespace { fheroes2::Image GetBarBackgroundSprite() { fheroes2::Image icon( 34, 34 ); icon.reset(); fheroes2::DrawBorder( icon, fheroes2::GetColorId( 0xD0, 0xC0, 0x48 ) ); fheroes2::Blit( fheroes2::AGG::GetICN( ICN::HSICONS, 0 ), 26, 21, icon, 1, 1, 32, 32 ); return icon; } } PrimarySkillsBar::PrimarySkillsBar( const Heroes * hero, bool mini ) : _hero( hero ) , useSmallSize( mini ) , content{ Skill::Primary::ATTACK, Skill::Primary::DEFENSE, Skill::Primary::POWER, Skill::Primary::KNOWLEDGE } , toff( 0, 0 ) { if ( useSmallSize ) { backsf = GetBarBackgroundSprite(); setSingleItemSize( { backsf.width(), backsf.height() } ); } else { const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::PRIMSKIL, 0 ); setSingleItemSize( { sprite.width(), sprite.height() } ); } SetContent( content ); } void PrimarySkillsBar::SetTextOff( int32_t ox, int32_t oy ) { toff = fheroes2::Point( ox, oy ); } void PrimarySkillsBar::RedrawBackground( const fheroes2::Rect & pos, fheroes2::Image & dstsf ) { if ( useSmallSize ) fheroes2::Blit( backsf, dstsf, pos.x, pos.y ); } void PrimarySkillsBar::RedrawItem( int & skill, const fheroes2::Rect & pos, fheroes2::Image & dstsf ) { if ( skill == Skill::Primary::UNKNOWN ) { // Why is the unknown skill here? assert( 0 ); return; } if ( useSmallSize ) { const fheroes2::Sprite & backSprite = fheroes2::AGG::GetICN( ICN::SWAPWIN, 0 ); const int ww = 32; const fheroes2::Point dstpt( pos.x + ( pos.width - ww ) / 2, pos.y + ( pos.height - ww ) / 2 ); std::string skillText; switch ( skill ) { case Skill::Primary::ATTACK: fheroes2::Blit( backSprite, 217, 52, dstsf, dstpt.x, dstpt.y, ww, ww ); if ( _hero ) { skillText = std::to_string( _hero->GetAttack() ); } break; case Skill::Primary::DEFENSE: fheroes2::Blit( backSprite, 217, 85, dstsf, dstpt.x, dstpt.y, ww, ww ); if ( _hero ) { skillText = std::to_string( _hero->GetDefense() ); } break; case Skill::Primary::POWER: fheroes2::Blit( backSprite, 217, 118, dstsf, dstpt.x, dstpt.y, ww, ww ); if ( _hero ) { skillText = std::to_string( _hero->GetPower() ); } break; case Skill::Primary::KNOWLEDGE: fheroes2::Blit( backSprite, 217, 151, dstsf, dstpt.x, dstpt.y, ww, ww ); if ( _hero ) { skillText = std::to_string( _hero->GetKnowledge() ); } break; default: // Your primary skill is different. Make sure that the logic is correct! assert( 0 ); break; } if ( _hero != nullptr && !skillText.empty() ) { // Render text only if there is something to render. const fheroes2::Text text{ std::move( skillText ), fheroes2::FontType::smallWhite() }; text.draw( pos.x + ( pos.width + toff.x - text.width() ) / 2, pos.y + pos.height + toff.y + 2, dstsf ); } } else { const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::PRIMSKIL, skill - 1 ); fheroes2::Blit( sprite, dstsf, pos.x + ( pos.width - sprite.width() ) / 2, pos.y + ( pos.height - sprite.height() ) / 2 ); fheroes2::Text text{ Skill::Primary::String( skill ), fheroes2::FontType::smallWhite() }; text.draw( pos.x + ( pos.width - text.width() ) / 2, pos.y + 6, dstsf ); if ( _hero == nullptr ) { // Nothing more to render. return; } switch ( skill ) { case Skill::Primary::ATTACK: text.set( std::to_string( _hero->GetAttack() ), fheroes2::FontType::normalWhite() ); break; case Skill::Primary::DEFENSE: text.set( std::to_string( _hero->GetDefense() ), fheroes2::FontType::normalWhite() ); break; case Skill::Primary::POWER: text.set( std::to_string( _hero->GetPower() ), fheroes2::FontType::normalWhite() ); break; case Skill::Primary::KNOWLEDGE: text.set( std::to_string( _hero->GetKnowledge() ), fheroes2::FontType::normalWhite() ); break; default: // Your primary skill is different. Make sure that the logic is correct! assert( 0 ); return; } text.draw( pos.x + ( pos.width - text.width() ) / 2, pos.y + pos.height - text.height(), dstsf ); } } bool PrimarySkillsBar::ActionBarLeftMouseSingleClick( int & skill ) { if ( Skill::Primary::UNKNOWN != skill ) { fheroes2::showStandardTextMessage( Skill::Primary::String( skill ), Skill::Primary::StringDescription( skill, _hero ), Dialog::OK ); return true; } return false; } bool PrimarySkillsBar::ActionBarRightMouseHold( int & skill ) { if ( Skill::Primary::UNKNOWN != skill ) { fheroes2::showStandardTextMessage( Skill::Primary::String( skill ), Skill::Primary::StringDescription( skill, _hero ), Dialog::ZERO ); return true; } return false; } bool PrimarySkillsBar::ActionBarCursor( int & skill ) { if ( Skill::Primary::UNKNOWN != skill ) { msg = _( "View %{skill} Info" ); StringReplace( msg, "%{skill}", Skill::Primary::String( skill ) ); } return false; } bool PrimarySkillsBar::QueueEventProcessing( std::string * str ) { msg.clear(); bool res = Interface::ItemsBar::QueueEventProcessing(); if ( str ) *str = msg; return res; } SecondarySkillsBar::SecondarySkillsBar( const Heroes & hero, bool mini /* true */, bool change /* false */ ) : use_mini_sprite( mini ) , can_change( change ) , _hero( hero ) { if ( use_mini_sprite ) { backsf = GetBarBackgroundSprite(); setSingleItemSize( { backsf.width(), backsf.height() } ); } else { const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::SECSKILL, 0 ); setSingleItemSize( { sprite.width(), sprite.height() } ); } } void SecondarySkillsBar::RedrawBackground( const fheroes2::Rect & pos, fheroes2::Image & dstsf ) { if ( use_mini_sprite ) fheroes2::Blit( backsf, dstsf, pos.x, pos.y ); else fheroes2::Blit( fheroes2::AGG::GetICN( ICN::SECSKILL, 0 ), dstsf, pos.x, pos.y ); } void SecondarySkillsBar::RedrawItem( Skill::Secondary & skill, const fheroes2::Rect & pos, fheroes2::Image & dstsf ) { if ( !skill.isValid() ) { // If the skill is invalid nothing we need to do. return; } const fheroes2::Sprite & sprite = use_mini_sprite ? fheroes2::AGG::GetICN( ICN::MINISS, skill.GetIndexSprite2() ) : fheroes2::AGG::GetICN( ICN::SECSKILL, skill.GetIndexSprite1() ); fheroes2::Blit( sprite, dstsf, pos.x + ( pos.width - sprite.width() ) / 2, pos.y + ( pos.height - sprite.height() ) / 2 ); if ( use_mini_sprite ) { const fheroes2::Text text{ std::to_string( skill.Level() ), fheroes2::FontType::smallWhite() }; text.draw( pos.x + ( pos.width - text.width() ) - 3, pos.y + pos.height - 10, dstsf ); } else { fheroes2::Text text{ Skill::Secondary::String( skill.Skill() ), fheroes2::FontType::smallWhite() }; if ( text.width() > sprite.width() + 1 ) { text.fitToOneRow( pos.width ); } const int skillNamePaddingX = ( pos.width - text.width() ) / 2 - 1; text.drawInRoi( pos.x + skillNamePaddingX, pos.y + 5, dstsf, pos ); text.set( Skill::Level::StringWithBonus( _hero, skill ), fheroes2::FontType::smallWhite() ); if ( text.width() > sprite.width() + 1 ) { text.fitToOneRow( pos.width ); } const int skillLevelPaddingX = ( pos.width - text.width() ) / 2 - 1; text.drawInRoi( pos.x + skillLevelPaddingX, pos.y + 53, dstsf, pos ); } } bool SecondarySkillsBar::ActionBarLeftMouseSingleClick( Skill::Secondary & skill ) { if ( skill.isValid() ) { fheroes2::SecondarySkillDialogElement( skill, _hero ).showPopup( Dialog::OK ); return true; } else if ( can_change ) { Skill::Secondary alt = Dialog::selectSecondarySkill( _hero ); if ( alt.isValid() ) { skill = alt; return true; } } return false; } bool SecondarySkillsBar::ActionBarRightMouseHold( Skill::Secondary & skill ) { if ( skill.isValid() ) { if ( can_change ) { skill.Reset(); } else { fheroes2::SecondarySkillDialogElement( skill, _hero ).showPopup( Dialog::ZERO ); } return true; } return false; } bool SecondarySkillsBar::ActionBarCursor( Skill::Secondary & skill ) { if ( skill.isValid() ) { msg = _( "View %{skill} Info" ); StringReplace( msg, "%{skill}", skill.GetName() ); } return false; } bool SecondarySkillsBar::QueueEventProcessing( std::string * str ) { msg.clear(); bool res = Interface::ItemsBar::QueueEventProcessing(); if ( str ) *str = msg; return res; } namespace fheroes2 { void RedrawPrimarySkillInfo( const fheroes2::Point & pos, PrimarySkillsBar * bar1, PrimarySkillsBar * bar2 ) { Display & display = Display::instance(); // attack skill Text text( Skill::Primary::String( Skill::Primary::ATTACK ), FontType::smallWhite() ); text.draw( pos.x + 320 - text.width() / 2, pos.y + 62, display ); // defense skill text.set( Skill::Primary::String( Skill::Primary::DEFENSE ), FontType::smallWhite() ); text.draw( pos.x + 320 - text.width() / 2, pos.y + 95, display ); // spell power text.set( Skill::Primary::String( Skill::Primary::POWER ), FontType::smallWhite() ); text.draw( pos.x + 320 - text.width() / 2, pos.y + 128, display ); // knowledge text.set( Skill::Primary::String( Skill::Primary::KNOWLEDGE ), FontType::smallWhite() ); text.draw( pos.x + 320 - text.width() / 2, pos.y + 161, display ); if ( bar1 ) { bar1->Redraw( display ); } if ( bar2 ) { bar2->Redraw( display ); } } } fheroes2-1.0.12+dfsg/src/fheroes2/gui/skill_bar.h000066400000000000000000000063451456075706000214540ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include #include "image.h" #include "interface_itemsbar.h" #include "math_base.h" class Heroes; namespace Skill { class Secondary; } class PrimarySkillsBar : public Interface::ItemsBar { public: PrimarySkillsBar( const Heroes * hero, bool mini ); void SetTextOff( int32_t, int32_t ); void RedrawBackground( const fheroes2::Rect &, fheroes2::Image & ) override; void RedrawItem( int &, const fheroes2::Rect &, fheroes2::Image & ) override; bool ActionBarLeftMouseSingleClick( int & skill ) override; bool ActionBarRightMouseHold( int & skill ) override; bool ActionBarCursor( int & skill ) override; bool QueueEventProcessing( std::string * = nullptr ); private: const Heroes * _hero; fheroes2::Image backsf; const bool useSmallSize; std::vector content; fheroes2::Point toff; std::string msg; }; class SecondarySkillsBar : public Interface::ItemsBar { public: SecondarySkillsBar( const Heroes & hero, bool mini = true, bool change = false ); void RedrawBackground( const fheroes2::Rect &, fheroes2::Image & ) override; void RedrawItem( Skill::Secondary &, const fheroes2::Rect &, fheroes2::Image & ) override; bool ActionBarLeftMouseSingleClick( Skill::Secondary & skill ) override; bool ActionBarRightMouseHold( Skill::Secondary & skill ) override; bool ActionBarCursor( Skill::Secondary & skill ) override; bool QueueEventProcessing( std::string * = nullptr ); private: fheroes2::Image backsf; const bool use_mini_sprite; const bool can_change; std::string msg; const Heroes & _hero; }; namespace fheroes2 { void RedrawPrimarySkillInfo( const fheroes2::Point & pos, PrimarySkillsBar * bar1, PrimarySkillsBar * bar2 ); } fheroes2-1.0.12+dfsg/src/fheroes2/gui/statusbar.cpp000066400000000000000000000050561456075706000220530ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "statusbar.h" #include #include #include #include "screen.h" #include "tools.h" #include "ui_text.h" StatusBar::StatusBar() : MovableText( fheroes2::Display::instance() ) { // Do nothing. } void StatusBar::ShowMessage( std::string msg ) { if ( msg == _prevMessage ) { // No updates. return; } _prevMessage = msg; auto text = std::make_unique( std::move( msg ), fheroes2::FontType::normalWhite() ); text->fitToOneRow( _roi.width ); const int32_t textWidth = text->width(); const fheroes2::Rect messageRoi{ _roi.x + ( _roi.width - textWidth ) / 2, _roi.y, textWidth, text->height() }; update( std::move( text ) ); draw( messageRoi.x, messageRoi.y ); fheroes2::Display::instance().render( fheroes2::getBoundaryRect( _prevMessageRoi, messageRoi ) ); _prevMessageRoi = messageRoi; } fheroes2-1.0.12+dfsg/src/fheroes2/gui/statusbar.h000066400000000000000000000041011456075706000215060ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2STATUSBAR_H #define H2STATUSBAR_H #include #include "math_base.h" #include "ui_tool.h" class StatusBar : public fheroes2::MovableText { public: StatusBar(); void setRoi( const fheroes2::Rect & roi ) { _roi = roi; } void ShowMessage( std::string msg ); private: fheroes2::Rect _roi; std::string _prevMessage; fheroes2::Rect _prevMessageRoi; }; #endif fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_base.cpp000066400000000000000000000031201456075706000214400ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "ui_base.h" namespace fheroes2 { void ActionObject::senderUpdate( const ActionObject * /*unused*/ ) { // Do nothing. } } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_base.h000066400000000000000000000042261456075706000211150ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once namespace fheroes2 { // Action-event class to communicate between sender and receiver (only for user actions) class ActionObject { public: ActionObject() = default; virtual ~ActionObject() = default; void subscribe( ActionObject * receiver ) { _receiver = receiver; } void unsubscribe() { _receiver = nullptr; } protected: void notifySubscriber() { if ( _receiver != nullptr ) { _receiver->senderUpdate( this ); } } virtual void senderUpdate( const ActionObject * ); private: ActionObject * _receiver{ nullptr }; }; } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_button.cpp000066400000000000000000000661631456075706000220610ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "ui_button.h" #include #include #include #include "agg_image.h" #include "dialog.h" #include "game_hotkeys.h" #include "icn.h" #include "localevent.h" #include "pal.h" #include "settings.h" #include "translations.h" #include "ui_text.h" namespace { fheroes2::Image resizeButton( const fheroes2::Image & original, const int32_t width ) { const int32_t height = original.height(); assert( height > 0 ); fheroes2::Image output; output.resize( width, height ); output.reset(); const int32_t originalWidth = original.width(); if ( originalWidth >= width ) { fheroes2::Copy( original, 0, 0, output, 0, 0, width / 2, height ); const int32_t secondHalf = width - width / 2; fheroes2::Copy( original, originalWidth - secondHalf, 0, output, width - secondHalf, 0, secondHalf, height ); } else { const int32_t middleWidth = originalWidth / 3; const int32_t overallMiddleWidth = width - middleWidth * 2; const int32_t middleWidthCount = overallMiddleWidth / middleWidth; const int32_t middleLeftOver = overallMiddleWidth - middleWidthCount * middleWidth; fheroes2::Copy( original, 0, 0, output, 0, 0, middleWidth, height ); int32_t offsetX = middleWidth; for ( int32_t i = 0; i < middleWidthCount; ++i ) { fheroes2::Copy( original, middleWidth, 0, output, offsetX, 0, middleWidth, height ); offsetX += middleWidth; } if ( middleLeftOver > 0 ) { fheroes2::Copy( original, middleWidth, 0, output, offsetX, 0, middleLeftOver, height ); offsetX += middleLeftOver; } const int32_t rightPartWidth = originalWidth - middleWidth * 2; assert( offsetX + rightPartWidth == width ); fheroes2::Copy( original, originalWidth - rightPartWidth, 0, output, offsetX, 0, rightPartWidth, height ); } return output; } void getButtonSpecificValues( const int emptyButtonIcnID, fheroes2::FontColor & font, int32_t & textMargin, int32_t & minimumTextAreaWidth, int32_t & backgroundBorders, fheroes2::Point & releasedOffset, fheroes2::Point & pressedOffset ) { switch ( emptyButtonIcnID ) { case ICN::EMPTY_GOOD_BUTTON: font = fheroes2::FontColor::WHITE; textMargin = 4 + 4; // The minimum text area width for campaign buttons is 86 judging from the shared widths of the // original OKAY and the CANCEL buttons even though OKAY is a shorter word minimumTextAreaWidth = 86; backgroundBorders = 6 + 4; releasedOffset = { 5, 5 }; pressedOffset = { 4, 6 }; break; case ICN::EMPTY_EVIL_BUTTON: font = fheroes2::FontColor::GRAY; textMargin = 4 + 4; minimumTextAreaWidth = 87; backgroundBorders = 6 + 3; releasedOffset = { 6, 5 }; pressedOffset = { 5, 6 }; break; case ICN::EMPTY_POL_BUTTON: font = fheroes2::FontColor::GRAY; textMargin = 4 + 4; minimumTextAreaWidth = 87; backgroundBorders = 4 + 3; releasedOffset = { 4, 5 }; pressedOffset = { 3, 6 }; break; case ICN::EMPTY_GUILDWELL_BUTTON: font = fheroes2::FontColor::WHITE; textMargin = 2 + 2; minimumTextAreaWidth = 53; backgroundBorders = 5 + 3; releasedOffset = { 4, 2 }; pressedOffset = { 3, 3 }; break; case ICN::EMPTY_VERTICAL_GOOD_BUTTON: font = fheroes2::FontColor::WHITE; textMargin = 0 + 0; minimumTextAreaWidth = 19; backgroundBorders = 5 + 4; // TODO: The center of the button will change according to the height of it when we can resize it. // The height offsets will need to be adjusted when this is possible, now they point to the // center of the empty button. releasedOffset = { 5, 52 }; pressedOffset = { 4, 53 }; break; case ICN::EMPTY_MAP_SELECT_BUTTON: font = fheroes2::FontColor::WHITE; textMargin = 2 + 2; minimumTextAreaWidth = 60; backgroundBorders = 6 + 3; releasedOffset = { 6, 3 }; pressedOffset = { 5, 4 }; break; default: // Was a new empty button template added? assert( 0 ); break; } } } namespace fheroes2 { ButtonBase::ButtonBase( const int32_t offsetX, const int32_t offsetY ) : _offsetX( offsetX ) , _offsetY( offsetY ) , _isPressed( false ) , _isEnabled( true ) , _isVisible( true ) , _releasedSprite( nullptr ) , _disabledSprite() {} bool ButtonBase::press() { if ( !isEnabled() ) { return false; } _isPressed = true; notifySubscriber(); return true; } bool ButtonBase::release() { if ( !isEnabled() ) { return false; } _isPressed = false; notifySubscriber(); return true; } void ButtonBase::enable() { _isEnabled = true; notifySubscriber(); } void ButtonBase::disable() { _isEnabled = false; _isPressed = false; // button can't be disabled and pressed notifySubscriber(); } void ButtonBase::show() { _isVisible = true; notifySubscriber(); } void ButtonBase::hide() { _isVisible = false; notifySubscriber(); } bool ButtonBase::draw( Image & output ) const { if ( !isVisible() ) { return false; } if ( isPressed() ) { // button can't be disabled and pressed const Sprite & sprite = _getPressed(); Blit( sprite, output, _offsetX + sprite.x(), _offsetY + sprite.y() ); } else { const Sprite & sprite = isEnabled() ? _getReleased() : _getDisabled(); Blit( sprite, output, _offsetX + sprite.x(), _offsetY + sprite.y() ); } return true; } bool ButtonBase::drawOnPress( Display & output ) { if ( isPressed() ) { return false; } if ( !press() ) { return false; } if ( isVisible() ) { draw( output ); output.render( area() ); } return true; } bool ButtonBase::drawOnRelease( Display & output ) { if ( !isPressed() ) { return false; } if ( !release() ) { return false; } if ( isVisible() ) { draw( output ); output.render( area() ); } return true; } Rect ButtonBase::area() const { const Sprite & sprite = isPressed() ? _getPressed() : _getReleased(); return Rect( _offsetX + sprite.x(), _offsetY + sprite.y(), sprite.width(), sprite.height() ); } const Sprite & ButtonBase::_getDisabled() const { const Sprite & sprite = _getReleased(); if ( !_disabledSprite || ( _releasedSprite != &sprite ) ) { _releasedSprite = &sprite; _disabledSprite.reset( new Sprite( sprite ) ); ApplyPalette( *_disabledSprite, PAL::GetPalette( PAL::PaletteType::DARKENING ) ); } return *_disabledSprite.get(); } Button::Button( int32_t offsetX, int32_t offsetY ) : ButtonBase( offsetX, offsetY ) , _icnId( -1 ) , _releasedIndex( 0 ) , _pressedIndex( 0 ) {} Button::Button( int32_t offsetX, int32_t offsetY, int icnId, uint32_t releasedIndex, uint32_t pressedIndex ) : ButtonBase( offsetX, offsetY ) , _icnId( icnId ) , _releasedIndex( releasedIndex ) , _pressedIndex( pressedIndex ) {} void Button::setICNInfo( int icnId, uint32_t releasedIndex, uint32_t pressedIndex ) { _icnId = icnId; _releasedIndex = releasedIndex; _pressedIndex = pressedIndex; } void Button::setICNIndexes( const uint32_t releasedIndex, const uint32_t pressedIndex ) { _releasedIndex = releasedIndex; _pressedIndex = pressedIndex; } const Sprite & Button::_getPressed() const { return AGG::GetICN( _icnId, _pressedIndex ); } const Sprite & Button::_getReleased() const { return AGG::GetICN( _icnId, _releasedIndex ); } ButtonSprite::ButtonSprite( int32_t offsetX, int32_t offsetY ) : ButtonBase( offsetX, offsetY ) {} ButtonSprite::ButtonSprite( int32_t offsetX, int32_t offsetY, Sprite released, Sprite pressed, Sprite disabled ) : ButtonBase( offsetX, offsetY ) , _released( std::move( released ) ) , _pressed( std::move( pressed ) ) , _disabled( std::move( disabled ) ) {} void ButtonSprite::setSprite( const Sprite & released, const Sprite & pressed, const Sprite & disabled ) { _released = released; _pressed = pressed; _disabled = disabled; } const Sprite & ButtonSprite::_getPressed() const { return _pressed; } const Sprite & ButtonSprite::_getReleased() const { return _released; } const Sprite & ButtonSprite::_getDisabled() const { if ( _disabled.empty() ) { return ButtonBase::_getDisabled(); } return _disabled; } ButtonGroup::ButtonGroup( const Rect & area, int buttonTypes ) { const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); const int buttonYesIcnID = isEvilInterface ? ICN::BUTTON_SMALL_YES_EVIL : ICN::BUTTON_SMALL_YES_GOOD; const int buttonNoIcnID = isEvilInterface ? ICN::BUTTON_SMALL_NO_EVIL : ICN::BUTTON_SMALL_NO_GOOD; const int buttonOkayIcnID = isEvilInterface ? ICN::UNIFORM_EVIL_OKAY_BUTTON : ICN::UNIFORM_GOOD_OKAY_BUTTON; const int buttonCancelIcnID = isEvilInterface ? ICN::UNIFORM_EVIL_CANCEL_BUTTON : ICN::UNIFORM_GOOD_CANCEL_BUTTON; Point offset; switch ( buttonTypes ) { case Dialog::YES | Dialog::NO: offset.x = area.x; offset.y = area.y + area.height - AGG::GetICN( buttonYesIcnID, 0 ).height(); createButton( offset.x, offset.y, buttonYesIcnID, 0, 1, Dialog::YES ); offset.x = area.x + area.width - AGG::GetICN( buttonNoIcnID, 0 ).width(); offset.y = area.y + area.height - AGG::GetICN( buttonNoIcnID, 0 ).height(); createButton( offset.x, offset.y, buttonNoIcnID, 0, 1, Dialog::NO ); break; case Dialog::OK | Dialog::CANCEL: offset.x = area.x; offset.y = area.y + area.height - AGG::GetICN( buttonOkayIcnID, 0 ).height(); createButton( offset.x, offset.y, buttonOkayIcnID, 0, 1, Dialog::OK ); offset.x = area.x + area.width - AGG::GetICN( buttonCancelIcnID, 0 ).width(); offset.y = area.y + area.height - AGG::GetICN( buttonCancelIcnID, 0 ).height(); createButton( offset.x, offset.y, buttonCancelIcnID, 0, 1, Dialog::CANCEL ); break; case Dialog::OK: offset.x = area.x + ( area.width - AGG::GetICN( buttonOkayIcnID, 0 ).width() ) / 2; offset.y = area.y + area.height - AGG::GetICN( buttonOkayIcnID, 0 ).height(); createButton( offset.x, offset.y, buttonOkayIcnID, 0, 1, Dialog::OK ); break; case Dialog::CANCEL: offset.x = area.x + ( area.width - AGG::GetICN( buttonCancelIcnID, 0 ).width() ) / 2; offset.y = area.y + area.height - AGG::GetICN( buttonCancelIcnID, 0 ).height(); createButton( offset.x, offset.y, buttonCancelIcnID, 0, 1, Dialog::CANCEL ); break; default: break; } } ButtonGroup::~ButtonGroup() { for ( size_t i = 0; i < _button.size(); ++i ) { delete _button[i]; } _button.clear(); _value.clear(); } void ButtonGroup::createButton( int32_t offsetX, int32_t offsetY, int icnId, uint32_t releasedIndex, uint32_t pressedIndex, int returnValue ) { _button.push_back( new Button( offsetX, offsetY, icnId, releasedIndex, pressedIndex ) ); _value.emplace_back( returnValue ); } void ButtonGroup::createButton( int32_t offsetX, int32_t offsetY, const Sprite & released, const Sprite & pressed, int returnValue ) { _button.push_back( new ButtonSprite( offsetX, offsetY, released, pressed ) ); _value.emplace_back( returnValue ); } void ButtonGroup::addButton( ButtonSprite && button, int returnValue ) { _button.push_back( new ButtonSprite( std::move( button ) ) ); _value.emplace_back( returnValue ); } void ButtonGroup::draw( Image & area ) const { for ( size_t i = 0; i < _button.size(); ++i ) { _button[i]->draw( area ); } } ButtonBase & ButtonGroup::button( size_t id ) { assert( id < _button.size() ); return *_button[id]; } const ButtonBase & ButtonGroup::button( size_t id ) const { assert( id < _button.size() ); return *_button[id]; } int ButtonGroup::processEvents() { LocalEvent & le = LocalEvent::Get(); for ( size_t i = 0; i < _button.size(); ++i ) { if ( _button[i]->isEnabled() ) { le.MousePressLeft( _button[i]->area() ) ? _button[i]->drawOnPress() : _button[i]->drawOnRelease(); } } for ( size_t i = 0; i < _button.size(); ++i ) { if ( _button[i]->isEnabled() && le.MouseClickLeft( _button[i]->area() ) ) { return _value[i]; } } for ( size_t i = 0; i < _button.size(); ++i ) { if ( _button[i]->isEnabled() ) { if ( ( _value[i] == Dialog::YES || _value[i] == Dialog::OK ) && Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_OKAY ) ) { return _value[i]; } if ( ( _value[i] == Dialog::CANCEL || _value[i] == Dialog::NO ) && Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) { return _value[i]; } } } return Dialog::ZERO; } ButtonRestorer::ButtonRestorer( ButtonBase & button ) : _button( button ) , _isEnabled( button.isEnabled() ) { if ( _isEnabled ) { Display & display = Display::instance(); _button.disable(); _button.draw( display ); display.render( _button.area() ); } } ButtonRestorer::~ButtonRestorer() { if ( _isEnabled ) { Display & display = Display::instance(); _button.enable(); _button.draw( display ); display.render( _button.area() ); } } void OptionButtonGroup::addButton( ButtonBase * button ) { if ( button == nullptr ) return; _button.push_back( button ); button->subscribe( this ); } void OptionButtonGroup::draw( Image & area ) const { for ( size_t i = 0; i < _button.size(); ++i ) { _button[i]->draw( area ); } } void OptionButtonGroup::senderUpdate( const ActionObject * sender ) { if ( sender == nullptr ) // how is it even possible? return; for ( size_t i = 0; i < _button.size(); ++i ) { if ( sender == _button[i] ) { const ButtonBase * button = _button[i]; if ( button->isPressed() ) { unsubscribeAll(); for ( size_t buttonId = 0; buttonId < _button.size(); ++buttonId ) { if ( i != buttonId ) { _button[buttonId]->release(); } } subscribeAll(); } } } } void OptionButtonGroup::subscribeAll() { for ( size_t i = 0; i < _button.size(); ++i ) { _button[i]->subscribe( this ); } } void OptionButtonGroup::unsubscribeAll() { for ( size_t i = 0; i < _button.size(); ++i ) { _button[i]->unsubscribe(); } } void makeTransparentBackground( const Sprite & released, Sprite & pressed, const int backgroundIcnID ) { // We need to copy the background image to pressed button only where it does not overlay the image of released button. const fheroes2::Sprite & background = fheroes2::AGG::GetICN( backgroundIcnID, 0 ); // you are trying to apply transform on an image that is single-layered assert( !pressed.singleLayer() && !released.singleLayer() ); const uint8_t * releasedTransform = released.transform(); uint8_t * pressedTransform = pressed.transform(); uint8_t * pressedImage = pressed.image(); const uint8_t * backgroundImage = background.image() + ( background.width() - pressed.width() ) / 2 + ( background.height() - pressed.height() ) * background.width() / 2; const int32_t pressedArea = pressed.width() * pressed.height(); for ( int32_t x = 0; x < pressedArea; ++x ) { if ( ( *( pressedTransform + x ) == 1 ) && ( *( releasedTransform + x ) == 0 ) ) { *( pressedImage + x ) = *( backgroundImage + ( x % pressed.width() ) + static_cast( x / pressed.width() ) * background.width() ); *( pressedTransform + x ) = 0; } } } ButtonSprite makeButtonWithBackground( int32_t offsetX, int32_t offsetY, const Sprite & released, const Sprite & pressed, const Image & background ) { const Sprite croppedBackground = Crop( background, offsetX, offsetY, released.width(), released.height() ); Sprite releasedWithBackground( croppedBackground.width(), croppedBackground.height(), 0, 0 ); Copy( croppedBackground, releasedWithBackground ); Blit( released, releasedWithBackground, released.x(), released.y() ); Sprite pressedWithBackground( croppedBackground.width(), croppedBackground.height(), 0, 0 ); Copy( croppedBackground, pressedWithBackground ); Blit( pressed, pressedWithBackground, pressed.x(), pressed.y() ); Sprite disabled( released ); ApplyPalette( disabled, PAL::GetPalette( PAL::PaletteType::DARKENING ) ); Sprite disabledWithBackground( croppedBackground.width(), croppedBackground.height(), 0, 0 ); Copy( croppedBackground, disabledWithBackground ); disabledWithBackground.setPosition( 0, 0 ); Blit( disabled, disabledWithBackground, disabled.x(), disabled.y() ); return { offsetX, offsetY, std::move( releasedWithBackground ), std::move( pressedWithBackground ), std::move( disabledWithBackground ) }; } ButtonSprite makeButtonWithShadow( int32_t offsetX, int32_t offsetY, const Sprite & released, const Sprite & pressed, const Image & background, const Point & shadowOffset ) { const Sprite & shadow = fheroes2::makeShadow( released, shadowOffset, 3 ); Sprite croppedBackground = Crop( background, offsetX + shadow.x(), offsetY + shadow.y(), shadow.width(), shadow.height() ); Blit( shadow, croppedBackground ); Sprite releasedWithBackground( croppedBackground.width(), croppedBackground.height(), 0, 0 ); Copy( croppedBackground, releasedWithBackground ); Blit( released, releasedWithBackground, released.x() - shadow.x(), released.y() - shadow.y() ); Sprite pressedWithBackground( croppedBackground.width(), croppedBackground.height(), 0, 0 ); Copy( croppedBackground, pressedWithBackground ); Blit( pressed, pressedWithBackground, pressed.x() - shadow.x(), pressed.y() - shadow.y() ); Sprite disabled( released ); ApplyPalette( disabled, PAL::GetPalette( PAL::PaletteType::DARKENING ) ); Sprite disabledWithBackground( croppedBackground.width(), croppedBackground.height(), 0, 0 ); Copy( croppedBackground, disabledWithBackground ); disabledWithBackground.setPosition( 0, 0 ); Blit( disabled, disabledWithBackground, disabled.x() - shadow.x(), disabled.y() - shadow.y() ); return { offsetX + shadow.x(), offsetY + shadow.y(), std::move( releasedWithBackground ), std::move( pressedWithBackground ), std::move( disabledWithBackground ) }; } void getCustomNormalButton( Sprite & released, Sprite & pressed, const bool isEvilInterface, int32_t width, Point & releasedOffset, Point & pressedOffset, const bool isTransparentBackground /* = false */ ) { assert( width > 0 ); releasedOffset = { 7, 5 }; pressedOffset = { 6, 6 }; // The actual button sprite is 10 pixels longer. width += 10; const int32_t icnId = isEvilInterface ? ICN::EMPTY_EVIL_BUTTON : ICN::EMPTY_GOOD_BUTTON; const int32_t minimumButtonSize = 16; const int32_t maximumButtonSize = 200; // Why is such a wide button needed? width = std::clamp( width, minimumButtonSize, maximumButtonSize ); const Sprite & originalReleased = AGG::GetICN( icnId, 0 ); const Sprite & originalPressed = AGG::GetICN( icnId, 1 ); released = resizeButton( originalReleased, width ); pressed = resizeButton( originalPressed, width ); if ( !isTransparentBackground ) { const int backgroundIcnId = isEvilInterface ? ICN::STONEBAK_EVIL : ICN::STONEBAK; makeTransparentBackground( released, pressed, backgroundIcnId ); } } void getTextAdaptedButton( Sprite & released, Sprite & pressed, const char * text, const int emptyButtonIcnID, const int buttonBackgroundIcnID ) { fheroes2::FontColor buttonFont = fheroes2::FontColor::WHITE; int32_t textAreaBorder = 0; int32_t minimumTextAreaWidth = 0; int32_t backgroundBorders = 0; fheroes2::Point releasedOffset = {}; fheroes2::Point pressedOffset = {}; getButtonSpecificValues( emptyButtonIcnID, buttonFont, textAreaBorder, minimumTextAreaWidth, backgroundBorders, releasedOffset, pressedOffset ); const fheroes2::FontType releasedButtonFont{ fheroes2::FontSize::BUTTON_RELEASED, buttonFont }; const char * translatedText = _( text ); const char * supportedText = fheroes2::isFontAvailable( translatedText, releasedButtonFont ) ? translatedText : text; const fheroes2::Text releasedText( supportedText, releasedButtonFont ); const fheroes2::Text pressedText( supportedText, { fheroes2::FontSize::BUTTON_PRESSED, buttonFont } ); const int32_t maximumTextAreaWidth = 200; // Why is such a wide button needed? // We need to pass an argument to width() so that it correctly accounts for multi-lined texts. // TODO: Remove the need for the argument once width() has been improved to handle this. const int32_t textWidth = releasedText.width( maximumTextAreaWidth ); assert( textWidth > 0 ); const int32_t borderedTextWidth = textWidth + textAreaBorder; const int32_t textAreaWidth = std::clamp( borderedTextWidth, minimumTextAreaWidth, maximumTextAreaWidth ); assert( textAreaWidth + backgroundBorders > 0 ); // TODO: Make resizeButton() scale in vertical direction too, for vertical and larger buttons. released = resizeButton( AGG::GetICN( emptyButtonIcnID, 0 ), textAreaWidth + backgroundBorders ); pressed = resizeButton( AGG::GetICN( emptyButtonIcnID, 1 ), textAreaWidth + backgroundBorders ); if ( buttonBackgroundIcnID != ICN::UNKNOWN ) { makeTransparentBackground( released, pressed, buttonBackgroundIcnID ); } const fheroes2::Size releasedTextSize( releasedText.width( textAreaWidth ), releasedText.height( textAreaWidth ) ); const fheroes2::Size pressedTextSize( pressedText.width( textAreaWidth ), pressedText.height( textAreaWidth ) ); // The button font letters are all shifted 1 pixel to the left due to shadows, so we have to add 1 to the x position when drawing // to properly center-align releasedText.draw( releasedOffset.x + 1, releasedOffset.y + ( releasedText.height() - releasedTextSize.height ) / 2, textAreaWidth, released ); pressedText.draw( pressedOffset.x + 1, pressedOffset.y + ( pressedText.height() - pressedTextSize.height ) / 2, textAreaWidth, pressed ); } void makeButtonSprites( Sprite & released, Sprite & pressed, const std::string & text, const int32_t buttonWidth, const bool isEvilInterface, const bool isTransparentBackground ) { fheroes2::Point releasedOffset; fheroes2::Point pressedOffset; fheroes2::getCustomNormalButton( released, pressed, isEvilInterface, buttonWidth, releasedOffset, pressedOffset, isTransparentBackground ); const fheroes2::FontColor fontColor = isEvilInterface ? fheroes2::FontColor::GRAY : fheroes2::FontColor::WHITE; const fheroes2::Text releasedText( text, { fheroes2::FontSize::BUTTON_RELEASED, fontColor } ); const fheroes2::Text pressedText( text, { fheroes2::FontSize::BUTTON_PRESSED, fontColor } ); const fheroes2::Point textOffset{ ( buttonWidth - releasedText.width() ) / 2, ( 16 - fheroes2::getFontHeight( fheroes2::FontSize::NORMAL ) ) / 2 }; releasedText.draw( releasedOffset.x + textOffset.x, releasedOffset.y + textOffset.y, released ); pressedText.draw( pressedOffset.x + textOffset.x, pressedOffset.y + textOffset.y, pressed ); } } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_button.h000066400000000000000000000230501456075706000215120ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include #include #include #include "image.h" #include "math_base.h" #include "screen.h" #include "ui_base.h" namespace fheroes2 { // An abstract class for button usage class ButtonBase : public ActionObject { public: ButtonBase() = default; ButtonBase( const int32_t offsetX, const int32_t offsetY ); ButtonBase( const ButtonBase & ) = delete; ButtonBase( ButtonBase && ) noexcept = default; ~ButtonBase() override = default; ButtonBase & operator=( const ButtonBase & button ) = delete; ButtonBase & operator=( ButtonBase && ) noexcept = default; bool isEnabled() const { return _isEnabled; } bool isDisabled() const { return !_isEnabled; } bool isPressed() const { return _isPressed; } bool isReleased() const { return !_isPressed; } bool isVisible() const { return _isVisible; } bool isHidden() const { return !_isVisible; } bool press(); bool release(); void enable(); void disable(); // button becomes disabled and released void show(); // this method doesn't call draw void hide(); // this method doesn't call draw void setPosition( const int32_t offsetX_, const int32_t offsetY_ ) { _offsetX = offsetX_; _offsetY = offsetY_; } bool draw( Image & output = Display::instance() ) const; // will draw on screen by default // Will draw on screen by default. Returns true in case of state change. This method calls render() internally. bool drawOnPress( Display & output = Display::instance() ); // Will draw on screen by default. Returns true in case of state change. This method calls render() internally. bool drawOnRelease( Display & output = Display::instance() ); Rect area() const; protected: virtual const Sprite & _getPressed() const = 0; virtual const Sprite & _getReleased() const = 0; virtual const Sprite & _getDisabled() const; private: int32_t _offsetX{ 0 }; int32_t _offsetY{ 0 }; bool _isPressed{ false }; bool _isEnabled{ true }; bool _isVisible{ true }; mutable const Sprite * _releasedSprite = nullptr; mutable std::unique_ptr _disabledSprite; }; class Button : public ButtonBase { public: Button( int32_t offsetX = 0, int32_t offsetY = 0 ); Button( int32_t offsetX, int32_t offsetY, int icnId, uint32_t releasedIndex, uint32_t pressedIndex ); ~Button() override = default; void setICNInfo( int icnId, uint32_t releasedIndex, uint32_t pressedIndex ); void setICNIndexes( const uint32_t releasedIndex, const uint32_t pressedIndex ); protected: const Sprite & _getPressed() const override; const Sprite & _getReleased() const override; private: int _icnId; uint32_t _releasedIndex; uint32_t _pressedIndex; }; // This button class is used for custom Sprites class ButtonSprite : public ButtonBase { public: ButtonSprite( int32_t offsetX = 0, int32_t offsetY = 0 ); ButtonSprite( int32_t offsetX, int32_t offsetY, Sprite released, Sprite pressed, Sprite disabled = Sprite() ); ButtonSprite( const ButtonSprite & ) = delete; ButtonSprite( ButtonSprite && ) noexcept = default; ~ButtonSprite() override = default; ButtonSprite & operator=( const ButtonSprite & ) = delete; ButtonSprite & operator=( ButtonSprite && ) noexcept = default; void setSprite( const Sprite & released, const Sprite & pressed, const Sprite & disabled = Sprite() ); protected: const Sprite & _getPressed() const override; const Sprite & _getReleased() const override; const Sprite & _getDisabled() const override; private: Sprite _released; Sprite _pressed; Sprite _disabled; }; class ButtonGroup { public: // Please refer to dialog.h enumeration for states ButtonGroup( const Rect & area = Rect(), int buttonTypes = 0 ); ButtonGroup( const ButtonGroup & ) = delete; ~ButtonGroup(); ButtonGroup & operator=( const ButtonGroup & ) = delete; void createButton( int32_t offsetX, int32_t offsetY, int icnId, uint32_t releasedIndex, uint32_t pressedIndex, int returnValue ); void createButton( int32_t offsetX, int32_t offsetY, const Sprite & released, const Sprite & pressed, int returnValue ); void addButton( ButtonSprite && button, int returnValue ); void draw( Image & area = Display::instance() ) const; // will draw on screen by default // Make sure that id is less than size! ButtonBase & button( size_t id ); const ButtonBase & button( size_t id ) const; size_t size() const { return _button.size(); } int processEvents(); private: std::vector _button; std::vector _value; }; // This class is used for a situations when we need to disable a button for certain action // and restore it within the scope of code. The changed button is immediately rendered on display. class ButtonRestorer { public: explicit ButtonRestorer( ButtonBase & button ); ButtonRestorer( const ButtonRestorer & ) = delete; ~ButtonRestorer(); ButtonRestorer & operator=( const ButtonRestorer & ) = delete; private: ButtonBase & _button; bool _isEnabled; }; class OptionButtonGroup : public ActionObject { public: void addButton( ButtonBase * button ); void draw( Image & area = Display::instance() ) const; // will draw on screen by default protected: void senderUpdate( const ActionObject * sender ) override; private: std::vector _button; void subscribeAll(); void unsubscribeAll(); }; // Make transparent edges around buttons making the pressed state appear without parts of the released state void makeTransparentBackground( const Sprite & released, Sprite & pressed, const int backgroundIcnID ); // Makes a button with the background (usually from display): it can be used when original button sprites do not contain pieces of background in the pressed state ButtonSprite makeButtonWithBackground( int32_t offsetX, int32_t offsetY, const Sprite & released, const Sprite & pressed, const Image & background ); // Makes a button with the shadow: for that it needs to capture the background from the display at construct time ButtonSprite makeButtonWithShadow( int32_t offsetX, int32_t offsetY, const Sprite & released, const Sprite & pressed, const Image & background, const Point & shadowOffset = Point( -4, 6 ) ); // The height of text area is only 16 pixels. If 'isTransparentBackground' is set to false the button sprite will have a default background pattern from // STONEBAK or STONEBAK_EVIL (for Evil interface). The pattern is the same for all buttons. void getCustomNormalButton( Sprite & released, Sprite & pressed, const bool isEvilInterface, int32_t width, Point & releasedOffset, Point & pressedOffset, const bool isTransparentBackground = false ); // Makes a button that has the width necessary to fit a provided text using an empty button template void getTextAdaptedButton( Sprite & released, Sprite & pressed, const char * text, const int icnId, const int buttonBackgroundIcnID ); // Generate released and pressed button sprites with the text on it over a transparent or a default (STONEBAK/STONEBAK_EVIL) background. void makeButtonSprites( Sprite & released, Sprite & pressed, const std::string & text, const int32_t buttonWidth, const bool isEvilInterface, const bool isTransparentBackground ); } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_campaign.cpp000066400000000000000000000264131456075706000223170ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "ui_campaign.h" #include #include #include #include #include #include #include #include #include "agg_image.h" #include "army_troop.h" #include "artifact.h" #include "campaign_data.h" #include "campaign_savedata.h" #include "campaign_scenariodata.h" #include "dialog.h" #include "heroes.h" #include "icn.h" #include "image.h" #include "math_base.h" #include "monster.h" #include "skill.h" #include "spell.h" #include "spell_info.h" #include "ui_dialog.h" #include "ui_monster.h" #include "ui_text.h" namespace { fheroes2::Sprite getMonsterFrame( const Monster & monster, const int32_t count ) { const fheroes2::Point frameOffset = { 6, 6 }; fheroes2::Sprite output = fheroes2::AGG::GetICN( ICN::STRIP, 12 ); renderMonsterFrame( monster, output, frameOffset ); if ( count > 0 ) { const fheroes2::Text monsterCountText( "+" + std::to_string( count ), fheroes2::FontType::normalWhite() ); monsterCountText.draw( output.width() - frameOffset.x - monsterCountText.width() - 3, output.height() - frameOffset.y - monsterCountText.height() + 2, output ); } return output; } } namespace fheroes2 { void showScenarioBonusDataPopupWindow( const Campaign::ScenarioBonusData & bonusData ) { switch ( bonusData._type ) { case Campaign::ScenarioBonusData::ARTIFACT: { Artifact artifact( bonusData._subType ); if ( artifact == Artifact::SPELL_SCROLL ) { artifact.SetSpell( bonusData._artifactSpellId ); } const ArtifactDialogElement artifactUI( artifact ); const TextDialogElement artifactDescriptionUI( std::make_shared( artifact.GetDescription(), FontType::normalWhite() ) ); showMessage( Text( bonusData.getName(), FontType::normalYellow() ), Text( bonusData.getDescription(), FontType::normalWhite() ), Dialog::ZERO, { &artifactUI, &artifactDescriptionUI } ); break; } case Campaign::ScenarioBonusData::RESOURCES: { const std::string resourceAmount = std::to_string( bonusData._amount ); const ResourceDialogElement resourceUI( bonusData._subType, ( bonusData._amount > 0 ? "+" + resourceAmount : resourceAmount ) ); showMessage( Text( bonusData.getName(), FontType::normalYellow() ), Text( bonusData.getDescription(), FontType::normalWhite() ), Dialog::ZERO, { &resourceUI } ); break; } case Campaign::ScenarioBonusData::TROOP: { const Monster monster( bonusData._subType ); const CustomImageDialogElement monsterUI( getMonsterFrame( monster, bonusData._amount ) ); showMessage( Text( bonusData.getName(), FontType::normalYellow() ), Text( bonusData.getDescription(), FontType::normalWhite() ), Dialog::ZERO, { &monsterUI } ); break; } case Campaign::ScenarioBonusData::SPELL: { const Spell spell( bonusData._subType ); const SpellDialogElement spellUI( spell, nullptr ); const TextDialogElement spellDescriptionUI( std::make_shared( getSpellDescription( spell, nullptr ), FontType::normalWhite() ) ); showMessage( Text( bonusData.getName(), FontType::normalYellow() ), Text( bonusData.getDescription(), FontType::normalWhite() ), Dialog::ZERO, { &spellUI, &spellDescriptionUI } ); break; } case Campaign::ScenarioBonusData::STARTING_RACE: case Campaign::ScenarioBonusData::STARTING_RACE_AND_ARMY: { const CustomImageDialogElement raceUI( AGG::GetICN( ICN::Get4Captain( bonusData._subType ), 1 ) ); showMessage( Text( bonusData.getName(), FontType::normalYellow() ), Text( bonusData.getDescription(), FontType::normalWhite() ), Dialog::ZERO, { &raceUI } ); break; } case Campaign::ScenarioBonusData::SKILL_PRIMARY: { const PrimarySkillDialogElement primarySkillUI( bonusData._subType, "+" + std::to_string( bonusData._amount ) ); const TextDialogElement skillDescriptionUI( std::make_shared( Skill::Primary::StringDescription( bonusData._subType, nullptr ), FontType::normalWhite() ) ); showMessage( Text( bonusData.getName(), FontType::normalYellow() ), Text( bonusData.getDescription(), FontType::normalWhite() ), Dialog::ZERO, { &primarySkillUI, &skillDescriptionUI } ); break; } case Campaign::ScenarioBonusData::SKILL_SECONDARY: { Heroes fakeHero; Skill::Secondary skill( bonusData._subType, bonusData._amount ); const SecondarySkillDialogElement secondarySkillUI( skill, fakeHero ); const TextDialogElement skillDescriptionUI( std::make_shared( skill.GetDescription( fakeHero ), FontType::normalWhite() ) ); showMessage( Text( bonusData.getName(), FontType::normalYellow() ), Text( bonusData.getDescription(), FontType::normalWhite() ), Dialog::ZERO, { &secondarySkillUI, &skillDescriptionUI } ); break; } default: assert( 0 ); // some new bonus? break; } } void showAwardDataPopupWindow( const Campaign::CampaignAwardData & awardData ) { switch ( awardData._type ) { case Campaign::CampaignAwardData::TYPE_CREATURE_CURSE: case Campaign::CampaignAwardData::TYPE_CREATURE_ALLIANCE: { std::vector monsters; std::vector uiElements; std::vector> monsterUI; monsters.emplace_back( awardData._subType ); monsterUI.emplace_back( new CustomImageDialogElement( getMonsterFrame( monsters.back(), 0 ) ) ); uiElements.emplace_back( monsterUI.back().get() ); while ( monsters.back() != monsters.back().GetUpgrade() ) { monsters.emplace_back( monsters.back().GetUpgrade() ); monsterUI.emplace_back( new CustomImageDialogElement( getMonsterFrame( monsters.back(), 0 ) ) ); uiElements.emplace_back( monsterUI.back().get() ); } showMessage( Text( awardData.getName(), FontType::normalYellow() ), Text( awardData.getDescription(), FontType::normalWhite() ), Dialog::ZERO, uiElements ); break; } case Campaign::CampaignAwardData::TYPE_GET_ARTIFACT: { const Artifact artifact( awardData._subType ); const ArtifactDialogElement artifactUI( artifact ); const TextDialogElement artifactDescriptionUI( std::make_shared( artifact.GetDescription(), FontType::normalWhite() ) ); showMessage( Text( awardData.getName(), FontType::normalYellow() ), Text( awardData.getDescription(), FontType::normalWhite() ), Dialog::ZERO, { &artifactUI, &artifactDescriptionUI } ); break; } case Campaign::CampaignAwardData::TYPE_CARRY_OVER_FORCES: { const std::vector & troops = Campaign::CampaignSaveData::Get().getCarryOverTroops(); std::vector uiElements; std::vector> monsterUI; for ( const Troop & troop : troops ) { if ( !troop.isValid() ) { continue; } monsterUI.emplace_back( new CustomImageDialogElement( getMonsterFrame( troop, troop.GetCount() ) ) ); uiElements.emplace_back( monsterUI.back().get() ); } showMessage( Text( awardData.getName(), FontType::normalYellow() ), Text( awardData.getDescription(), FontType::normalWhite() ), Dialog::ZERO, uiElements ); break; } case Campaign::CampaignAwardData::TYPE_RESOURCE_BONUS: { const int32_t resourceType = awardData._subType; const std::string resourceAmount = std::to_string( awardData._amount ); const ResourceDialogElement resourceUI( resourceType, ( awardData._amount > 0 ? "+" + resourceAmount : resourceAmount ) ); showMessage( Text( awardData.getName(), FontType::normalYellow() ), Text( awardData.getDescription(), FontType::normalWhite() ), Dialog::ZERO, { &resourceUI } ); break; } case Campaign::CampaignAwardData::TYPE_GET_SPELL: { const Spell spell( awardData._subType ); const SpellDialogElement spellUI( spell, nullptr ); const TextDialogElement spellDescriptionUI( std::make_shared( getSpellDescription( spell, nullptr ), FontType::normalWhite() ) ); showMessage( Text( awardData.getName(), FontType::normalYellow() ), Text( awardData.getDescription(), FontType::normalWhite() ), Dialog::ZERO, { &spellUI, &spellDescriptionUI } ); break; } case Campaign::CampaignAwardData::TYPE_HIREABLE_HERO: case Campaign::CampaignAwardData::TYPE_DEFEAT_ENEMY_HERO: { Sprite output = Crop( AGG::GetICN( ICN::HEROBKG, 0 ), 47, 29, 105, 98 ); const Sprite & heroPortrait = AGG::GetICN( ICN::PORTxxxx( awardData._subType ), 0 ); Blit( heroPortrait, 0, 0, output, 2, 2, heroPortrait.width(), heroPortrait.height() ); const CustomImageDialogElement heroUI( std::move( output ) ); showMessage( Text( awardData.getName(), FontType::normalYellow() ), Text( awardData.getDescription(), FontType::normalWhite() ), Dialog::ZERO, { &heroUI } ); break; } default: // Did you add a new award? Add the logic above! assert( 0 ); break; } } } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_campaign.h000066400000000000000000000033431456075706000217610ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once namespace Campaign { struct ScenarioBonusData; struct CampaignAwardData; } namespace fheroes2 { void showScenarioBonusDataPopupWindow( const Campaign::ScenarioBonusData & bonusData ); void showAwardDataPopupWindow( const Campaign::CampaignAwardData & awardData ); } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_castle.cpp000066400000000000000000000320301456075706000220030ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include "agg_image.h" #include "castle.h" #include "color.h" #include "icn.h" #include "image.h" #include "logging.h" #include "race.h" #include "resource.h" #include "screen.h" #include "settings.h" #include "ui_castle.h" #include "ui_text.h" namespace { const int32_t originalCastleFlagStartColorId = 152; const int32_t originalCastleFlagColorLength = 16 + 7; std::vector getModifiedPalette( const uint8_t inputStartId, const uint8_t inputLength, const uint8_t outputStartId, const uint8_t outputLength ) { assert( inputLength > 0 && outputLength > 0 ); std::vector output( 256 ); for ( int32_t i = 0; i < 256; ++i ) { output[i] = static_cast( i ); } for ( uint8_t i = 0; i < inputLength; ++i ) { output[i + inputStartId] = outputStartId + i * outputLength / inputLength; } return output; } std::vector getModifiedPaletteByPlayerColor( const uint8_t inputStartId, const uint8_t inputLength, const int playerColor ) { switch ( playerColor ) { case Color::BLUE: return getModifiedPalette( inputStartId, inputLength, 63, 16 + 6 ); case Color::GREEN: return getModifiedPalette( inputStartId, inputLength, 85, 16 + 7 ); case Color::RED: return getModifiedPalette( inputStartId, inputLength, 175, 16 + 7 ); case Color::YELLOW: return getModifiedPalette( inputStartId, inputLength, 108, 16 + 7 ); case Color::ORANGE: return getModifiedPalette( inputStartId, inputLength, 199, 16 ); case Color::PURPLE: return getModifiedPalette( inputStartId, inputLength, 132, 16 + 5 ); default: // Did you add a new color? Please add the logic above. assert( 0 ); break; } return getModifiedPalette( inputStartId, inputLength, inputStartId, inputLength ); } std::vector getColorEffectiveAreas( const int32_t icnId, const uint32_t icnIndex ) { switch ( icnId ) { case ICN::TWNZCSTL: { if ( icnIndex == 0 ) { return { fheroes2::Rect( 76, 7, 3, 3 ), fheroes2::Rect( 96, 7, 3, 3 ), fheroes2::Rect( 178, 11, 3, 3 ) }; } const fheroes2::Sprite & image = fheroes2::AGG::GetICN( icnId, icnIndex ); return { fheroes2::Rect( 0, 0, image.width(), image.height() ) }; } case ICN::TWNKCSTL: { if ( icnIndex == 0 ) { return { fheroes2::Rect( 127, 36, 3, 3 ), fheroes2::Rect( 287, 6, 3, 3 ) }; } const fheroes2::Sprite & image = fheroes2::AGG::GetICN( icnId, icnIndex ); return { fheroes2::Rect( 0, 0, image.width(), image.height() ) }; } case ICN::TWNKDW_4: case ICN::TWNKUP_4: if ( icnIndex == 0 ) { return { fheroes2::Rect( 61, 3, 1, 1 ) }; } return { fheroes2::Rect( 59, 0, 6, 5 ) }; case ICN::TWNKLTUR: { if ( icnIndex == 0 ) { return { fheroes2::Rect( 5, 6, 3, 3 ) }; } const fheroes2::Sprite & image = fheroes2::AGG::GetICN( icnId, icnIndex ); return { fheroes2::Rect( 0, 0, image.width(), image.height() ) }; } case ICN::TWNKRTUR: { if ( icnIndex == 0 ) { return { fheroes2::Rect( 55, 6, 3, 3 ) }; } const fheroes2::Sprite & image = fheroes2::AGG::GetICN( icnId, icnIndex ); return { fheroes2::Rect( 0, 0, image.width(), image.height() ) }; } default: // You are calling this function for unsupported image ID. Verify your logic! assert( 0 ); break; } return {}; } fheroes2::Sprite getModifiedByColorImage( const int32_t icnId, const uint32_t icnIndex, const int32_t colorId ) { const std::vector regions = getColorEffectiveAreas( icnId, icnIndex ); const std::vector palette = getModifiedPaletteByPlayerColor( originalCastleFlagStartColorId, originalCastleFlagColorLength, colorId ); fheroes2::Sprite temp = fheroes2::AGG::GetICN( icnId, icnIndex ); for ( const fheroes2::Rect & roi : regions ) { fheroes2::ApplyPalette( temp, roi.x, roi.y, temp, roi.x, roi.y, roi.width, roi.height, palette ); } return temp; } bool isImagePlayerColorDependent( const int32_t icnId ) { switch ( icnId ) { case ICN::TWNZCSTL: case ICN::TWNKCSTL: case ICN::TWNKDW_4: case ICN::TWNKUP_4: case ICN::TWNKLTUR: case ICN::TWNKRTUR: return true; default: break; } return false; } } namespace fheroes2 { void drawCastleIcon( const Castle & castle, Image & output, const Point & offset ) { uint32_t icnIndex = 1; switch ( castle.GetRace() ) { case Race::KNGT: icnIndex = castle.isCastle() ? 9 : 15; break; case Race::BARB: icnIndex = castle.isCastle() ? 10 : 16; break; case Race::SORC: icnIndex = castle.isCastle() ? 11 : 17; break; case Race::WRLK: icnIndex = castle.isCastle() ? 12 : 18; break; case Race::WZRD: icnIndex = castle.isCastle() ? 13 : 19; break; case Race::NECR: icnIndex = castle.isCastle() ? 14 : 20; break; default: assert( 0 ); DEBUG_LOG( DBG_GAME, DBG_WARN, "unknown race" ) } const Sprite & castleImage = fheroes2::AGG::GetICN( Settings::Get().isEvilInterfaceEnabled() ? ICN::LOCATORE : ICN::LOCATORS, icnIndex ); fheroes2::Blit( castleImage, output, offset.x, offset.y ); // Draw castle's marker. switch ( Castle::GetAllBuildingStatus( castle ) ) { case NOT_TODAY: fheroes2::Blit( fheroes2::AGG::GetICN( ICN::CSLMARKER, 0 ), output, offset.x + 40, offset.y ); break; case REQUIRES_BUILD: fheroes2::Blit( fheroes2::AGG::GetICN( ICN::CSLMARKER, 1 ), output, offset.x + 40, offset.y ); break; default: break; } } Rect drawResourcePanel( const Funds & kingdomTreasures, Image & output, const Point & offset ) { const fheroes2::Rect roi( offset.x + 552, offset.y + 262, 82, 192 ); fheroes2::Fill( output, roi.x, roi.y, roi.width, roi.height, 0 ); // Maximum width is 39 pixels (except gold), maximum height is 32 pixels const int32_t maxWidth = 39; const int32_t maxHeight = 32; const int32_t leftColumnOffset = roi.x + 1; const int32_t rightColumnOffset = roi.x + 1 + maxWidth + 2; const fheroes2::FontType fontType( fheroes2::FontSize::SMALL, fheroes2::FontColor::WHITE ); const int32_t fontHeight = fheroes2::Text( std::string(), fontType ).height(); const std::array offsetY = { 0, maxHeight + fontHeight + 2, ( maxHeight + fontHeight ) * 2 - 1, ( maxHeight + fontHeight ) * 3 + 1 }; const fheroes2::Sprite & woodImage = fheroes2::AGG::GetICN( ICN::RESOURCE, 0 ); const fheroes2::Sprite & mercuryImage = fheroes2::AGG::GetICN( ICN::RESOURCE, 1 ); const fheroes2::Sprite & oreImage = fheroes2::AGG::GetICN( ICN::RESOURCE, 2 ); const fheroes2::Sprite & sulfurImage = fheroes2::AGG::GetICN( ICN::RESOURCE, 3 ); const fheroes2::Sprite & crystalImage = fheroes2::AGG::GetICN( ICN::RESOURCE, 4 ); const fheroes2::Sprite & gemsImage = fheroes2::AGG::GetICN( ICN::RESOURCE, 5 ); const fheroes2::Sprite & goldImage = fheroes2::AGG::GetICN( ICN::RESOURCE, 6 ); fheroes2::Blit( woodImage, output, leftColumnOffset + ( maxWidth - woodImage.width() ) / 2, roi.y + offsetY[0] + maxHeight - woodImage.height() ); fheroes2::Blit( sulfurImage, output, rightColumnOffset + ( maxWidth - sulfurImage.width() ) / 2, roi.y + offsetY[0] + maxHeight - sulfurImage.height() ); fheroes2::Blit( crystalImage, output, leftColumnOffset + ( maxWidth - crystalImage.width() ) / 2, roi.y + offsetY[1] + maxHeight - crystalImage.height() ); fheroes2::Blit( mercuryImage, output, rightColumnOffset + ( maxWidth - mercuryImage.width() ) / 2, roi.y + offsetY[1] + maxHeight - mercuryImage.height() ); fheroes2::Blit( oreImage, output, leftColumnOffset + ( maxWidth - oreImage.width() ) / 2, roi.y + offsetY[2] + maxHeight - oreImage.height() ); fheroes2::Blit( gemsImage, output, rightColumnOffset + ( maxWidth - gemsImage.width() ) / 2, roi.y + offsetY[2] + maxHeight - gemsImage.height() ); fheroes2::Blit( goldImage, output, roi.x + ( roi.width - goldImage.width() ) / 2, roi.y + offsetY[3] ); fheroes2::Text text( std::to_string( kingdomTreasures.wood ), fontType ); text.draw( leftColumnOffset + ( maxWidth - text.width() ) / 2, roi.y + offsetY[0] + maxHeight + 1, output ); text.set( std::to_string( kingdomTreasures.sulfur ), fontType ); text.draw( rightColumnOffset + ( maxWidth - text.width() ) / 2, roi.y + offsetY[0] + maxHeight + 1, output ); text.set( std::to_string( kingdomTreasures.crystal ), fontType ); text.draw( leftColumnOffset + ( maxWidth - text.width() ) / 2, roi.y + offsetY[1] + maxHeight + 1, output ); text.set( std::to_string( kingdomTreasures.mercury ), fontType ); text.draw( rightColumnOffset + ( maxWidth - text.width() ) / 2, roi.y + offsetY[1] + maxHeight + 1, output ); text.set( std::to_string( kingdomTreasures.ore ), fontType ); text.draw( leftColumnOffset + ( maxWidth - text.width() ) / 2, roi.y + offsetY[2] + maxHeight + 1, output ); text.set( std::to_string( kingdomTreasures.gems ), fontType ); text.draw( rightColumnOffset + ( maxWidth - text.width() ) / 2, roi.y + offsetY[2] + maxHeight + 1, output ); text.set( std::to_string( kingdomTreasures.gold ), fontType ); text.draw( roi.x + ( roi.width - text.width() ) / 2, roi.y + offsetY[3] + goldImage.height() + 1, output ); fheroes2::Blit( fheroes2::AGG::GetICN( ICN::BUTTON_EXIT_TOWN, 0 ), output, roi.x + 1, roi.y + 166 ); return roi; } void drawCastleName( const Castle & castle, Image & output, const Point & offset ) { const fheroes2::Sprite & background = fheroes2::AGG::GetICN( ICN::TOWNNAME, 0 ); fheroes2::Blit( background, fheroes2::Display::instance(), offset.x + 320 - background.width() / 2, offset.y + 248 ); const fheroes2::Text text( castle.GetName(), fheroes2::FontType::smallWhite() ); text.draw( offset.x + 320 - text.width() / 2, offset.y + 250, output ); } void drawCastleDialogBuilding( const int32_t icnId, const uint32_t icnIndex, const Castle & castle, const Point & offset, const Rect & renderArea, const uint8_t alpha ) { const fheroes2::Sprite & image = isImagePlayerColorDependent( icnId ) ? getModifiedByColorImage( icnId, icnIndex, castle.GetColor() ) : AGG::GetICN( icnId, icnIndex ); const fheroes2::Rect imageRoi{ offset.x + image.x(), offset.y + image.y(), image.width(), image.height() }; const fheroes2::Rect overlappedRoi = renderArea ^ imageRoi; fheroes2::AlphaBlit( image, overlappedRoi.x - imageRoi.x, overlappedRoi.y - imageRoi.y, fheroes2::Display::instance(), overlappedRoi.x, overlappedRoi.y, overlappedRoi.width, overlappedRoi.height, alpha ); } } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_castle.h000066400000000000000000000040451456075706000214550ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include "math_base.h" class Castle; class Funds; namespace fheroes2 { class Image; void drawCastleIcon( const Castle & castle, Image & output, const Point & offset ); Rect drawResourcePanel( const Funds & kingdomTreasures, Image & output, const Point & offset ); void drawCastleName( const Castle & castle, Image & output, const Point & offset ); void drawCastleDialogBuilding( const int32_t icnId, const uint32_t icnIndex, const Castle & castle, const Point & offset, const Rect & renderArea, const uint8_t alpha = 255 ); } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_dialog.cpp000066400000000000000000001005641456075706000217770ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "ui_dialog.h" #include #include #include #include #include #include #include #include "agg_image.h" #include "cursor.h" #include "dialog.h" #include "experience.h" #include "game_delays.h" #include "game_hotkeys.h" #include "heroes_indicator.h" #include "icn.h" #include "localevent.h" #include "logging.h" #include "luck.h" #include "morale.h" #include "resource.h" #include "screen.h" #include "spell_info.h" #include "ui_button.h" #include "ui_text.h" class HeroBase; namespace { const int32_t textOffsetY = 10; const int32_t elementOffsetX = 10; const int32_t textOffsetFromElement = 2; const int32_t defaultElementPopupButtons = Dialog::ZERO; void outputInTextSupportMode( const fheroes2::TextBase & header, const fheroes2::TextBase & body, const int buttonTypes ) { START_TEXT_SUPPORT_MODE COUT( header.text() ) COUT( '\n' ) COUT( body.text() ) if ( buttonTypes & Dialog::YES ) { COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::DEFAULT_OKAY ) << " to choose YES." ) } if ( buttonTypes & Dialog::NO ) { COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::DEFAULT_CANCEL ) << " to choose NO." ) } if ( buttonTypes & Dialog::OK ) { COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::DEFAULT_OKAY ) << " to choose OK." ) } if ( buttonTypes & Dialog::CANCEL ) { COUT( "Press " << Game::getHotKeyNameByEventId( Game::HotKeyEvent::DEFAULT_CANCEL ) << " to choose CANCEL." ) } } } namespace fheroes2 { int showMessage( const TextBase & header, const TextBase & body, const int buttons, const std::vector & elements ) { outputInTextSupportMode( header, body, buttons ); const bool isProperDialog = ( buttons != 0 ); // setup cursor const CursorRestorer cursorRestorer( isProperDialog, ::Cursor::POINTER ); const int32_t headerHeight = header.empty() ? 0 : header.height( BOXAREA_WIDTH ) + textOffsetY; int overallTextHeight = headerHeight; const int32_t bodyTextHeight = body.height( BOXAREA_WIDTH ); if ( bodyTextHeight > 0 ) { overallTextHeight += bodyTextHeight + textOffsetY; } std::vector rowElementIndex; std::vector rowHeight; std::vector rowId; std::vector rowMaxElementWidth; std::vector rowElementCount; int32_t elementHeight = 0; size_t elementId = 0; for ( const DialogElement * element : elements ) { assert( element != nullptr ); const int32_t currentElementWidth = element->area().width; if ( rowHeight.empty() ) { rowElementIndex.emplace_back( 0 ); rowHeight.emplace_back( element->area().height ); rowId.emplace_back( elementId ); rowMaxElementWidth.emplace_back( currentElementWidth ); rowElementCount.emplace_back( 1 ); ++elementId; } else if ( ( std::max( rowMaxElementWidth.back(), currentElementWidth ) + elementOffsetX ) * ( rowElementCount.back() + 1 ) <= BOXAREA_WIDTH ) { rowElementIndex.emplace_back( rowElementIndex.back() + 1 ); rowHeight.back() = std::max( rowHeight.back(), element->area().height ); // We cannot use back() to insert it into the same container as it will be resized upon insertion. const size_t lastRoiId = rowId.back(); rowId.emplace_back( lastRoiId ); rowMaxElementWidth.back() = std::max( rowMaxElementWidth.back(), currentElementWidth ); ++rowElementCount.back(); } else { elementHeight += textOffsetY; elementHeight += rowHeight.back(); rowElementIndex.emplace_back( 0 ); rowHeight.emplace_back( element->area().height ); rowId.emplace_back( elementId ); rowMaxElementWidth.emplace_back( currentElementWidth ); rowElementCount.emplace_back( 1 ); ++elementId; } } if ( !rowHeight.empty() ) { // UI elements are offset from the dialog body. if ( bodyTextHeight > 0 ) { elementHeight += textOffsetY; } elementHeight += textOffsetY; elementHeight += rowHeight.back(); } Dialog::FrameBox box( overallTextHeight + elementHeight, isProperDialog ); const Rect & pos = box.GetArea(); Display & display = Display::instance(); header.draw( pos.x, pos.y + textOffsetY, BOXAREA_WIDTH, display ); body.draw( pos.x, pos.y + textOffsetY + headerHeight, BOXAREA_WIDTH, display ); elementHeight = overallTextHeight + textOffsetY; if ( bodyTextHeight > 0 ) { elementHeight += textOffsetY; } elementId = 0; size_t prevRowId = 0; int32_t currentRowHeight = 0; std::vector elementOffsets; for ( const DialogElement * element : elements ) { const size_t currentRowId = rowId[elementId]; if ( prevRowId != currentRowId ) { prevRowId = currentRowId; elementHeight += textOffsetY; elementHeight += currentRowHeight; } currentRowHeight = rowHeight[currentRowId]; const int32_t currentRowElementIndex = rowElementIndex[elementId]; const int32_t currentRowElementCount = rowElementCount[currentRowId]; const int32_t currentRowMaxElementWidth = rowMaxElementWidth[currentRowId]; const int32_t emptyWidth = BOXAREA_WIDTH - currentRowElementCount * currentRowMaxElementWidth; const int32_t offsetBetweenElements = emptyWidth / ( currentRowElementCount + 1 ); const int32_t widthOffset = offsetBetweenElements + currentRowElementIndex * ( currentRowMaxElementWidth + offsetBetweenElements ); elementOffsets.emplace_back( pos.x + widthOffset + ( currentRowMaxElementWidth - element->area().width ) / 2, pos.y + elementHeight + currentRowHeight - element->area().height ); element->draw( display, elementOffsets.back() ); ++elementId; } ButtonGroup group( pos, buttons ); group.draw(); display.render(); int result = Dialog::ZERO; LocalEvent & le = LocalEvent::Get(); bool delayInEventHandling = true; while ( result == Dialog::ZERO && le.HandleEvents( delayInEventHandling ) ) { if ( !buttons && !le.MousePressRight() ) { break; } if ( isProperDialog ) { elementId = 0; for ( const DialogElement * element : elements ) { element->processEvents( elementOffsets[elementId] ); ++elementId; } } result = group.processEvents(); delayInEventHandling = true; elementId = 0; for ( const DialogElement * element : elements ) { if ( element->update( display, elementOffsets[elementId] ) ) { delayInEventHandling = false; } ++elementId; } if ( !delayInEventHandling ) { display.render(); } } return result; } int showStandardTextMessage( std::string headerText, std::string messageBody, const int buttons ) { fheroes2::Text header( std::move( headerText ), fheroes2::FontType::normalYellow() ); fheroes2::Text body( std::move( messageBody ), fheroes2::FontType::normalWhite() ); return fheroes2::showMessage( header, body, buttons ); } TextDialogElement::TextDialogElement( const std::shared_ptr & text ) : _text( text ) { // Text always occupies the whole width of the dialog. _area = { BOXAREA_WIDTH, _text->height( BOXAREA_WIDTH ) }; } void TextDialogElement::draw( Image & output, const Point & offset ) const { _text->draw( offset.x, offset.y, BOXAREA_WIDTH, output ); } void TextDialogElement::processEvents( const Point & /* offset */ ) const { // No events processed here. } void TextDialogElement::showPopup( const int /* buttons */ ) const { assert( 0 ); } CustomImageDialogElement::CustomImageDialogElement( const Image & image ) : _image( image ) { _area = { _image.width(), _image.height() }; } CustomImageDialogElement::CustomImageDialogElement( Image && image ) : _image( std::move( image ) ) { _area = { _image.width(), _image.height() }; } void CustomImageDialogElement::draw( Image & output, const Point & offset ) const { Blit( _image, 0, 0, output, offset.x, offset.y, _image.width(), _image.height() ); } void CustomImageDialogElement::processEvents( const Point & /* offset */ ) const { // No events processed here. } void CustomImageDialogElement::showPopup( const int /* buttons */ ) const { assert( 0 ); } ArtifactDialogElement::ArtifactDialogElement( const Artifact & artifact ) : _artifact( artifact ) { assert( artifact.isValid() ); const Sprite & frame = AGG::GetICN( ICN::RESOURCE, 7 ); _area = { frame.width(), frame.height() }; } void ArtifactDialogElement::draw( Image & output, const Point & offset ) const { const Sprite & frame = AGG::GetICN( ICN::RESOURCE, 7 ); Blit( frame, 0, 0, output, offset.x, offset.y, frame.width(), frame.height() ); const Sprite & artifact = AGG::GetICN( ICN::ARTIFACT, _artifact.IndexSprite64() ); Blit( artifact, output, offset.x + 6, offset.y + 6 ); } void ArtifactDialogElement::processEvents( const Point & offset ) const { if ( LocalEvent::Get().MousePressRight( { offset.x, offset.y, _area.width, _area.height } ) ) { // Make sure you never pass any buttons here to avoid call stack overflow! showPopup( defaultElementPopupButtons ); } } void ArtifactDialogElement::showPopup( const int buttons ) const { const Text header( _artifact.GetName(), FontType::normalYellow() ); const Text description( _artifact.GetDescription(), FontType::normalWhite() ); showMessage( header, description, buttons, { this } ); } ResourceDialogElement::ResourceDialogElement( const int32_t resourceType, std::string text ) : _resourceType( resourceType ) , _icnIndex( Resource::getIconIcnIndex( resourceType ) ) , _text( std::move( text ) ) { const Text quantityText( _text, FontType::smallWhite() ); const Sprite & icn = AGG::GetICN( ICN::RESOURCE, _icnIndex ); _area = { std::max( icn.width(), quantityText.width() ), icn.height() + textOffsetFromElement + quantityText.height() }; } void ResourceDialogElement::draw( Image & output, const Point & offset ) const { const Sprite & icn = AGG::GetICN( ICN::RESOURCE, _icnIndex ); const Text quantityText( _text, FontType::smallWhite() ); const int32_t maxWidth = std::max( icn.width(), quantityText.width() ); Blit( icn, 0, 0, output, offset.x + ( maxWidth - icn.width() ) / 2, offset.y, icn.width(), icn.height() ); quantityText.draw( offset.x + ( maxWidth - quantityText.width() ) / 2, offset.y + icn.height() + textOffsetFromElement, output ); } void ResourceDialogElement::processEvents( const Point & offset ) const { if ( LocalEvent::Get().MousePressRight( { offset.x, offset.y, _area.width, _area.height } ) ) { // Make sure you never pass any buttons here to avoid call stack overflow! showPopup( defaultElementPopupButtons ); } } void ResourceDialogElement::showPopup( const int buttons ) const { const Text header( Resource::String( _resourceType ), FontType::normalYellow() ); const Text description( Resource::getDescription(), FontType::normalWhite() ); showMessage( header, description, buttons ); } std::vector getResourceDialogElements( const Funds & funds ) { std::vector elements; if ( funds.wood != 0 ) { elements.emplace_back( Resource::WOOD, std::to_string( funds.wood ) ); } if ( funds.mercury != 0 ) { elements.emplace_back( Resource::MERCURY, std::to_string( funds.mercury ) ); } if ( funds.ore != 0 ) { elements.emplace_back( Resource::ORE, std::to_string( funds.ore ) ); } if ( funds.sulfur != 0 ) { elements.emplace_back( Resource::SULFUR, std::to_string( funds.sulfur ) ); } if ( funds.crystal != 0 ) { elements.emplace_back( Resource::CRYSTAL, std::to_string( funds.crystal ) ); } if ( funds.gems != 0 ) { elements.emplace_back( Resource::GEMS, std::to_string( funds.gems ) ); } if ( funds.gold != 0 ) { elements.emplace_back( Resource::GOLD, std::to_string( funds.gold ) ); } return elements; } int showResourceMessage( const TextBase & header, const TextBase & body, const int buttons, const Funds & funds ) { const std::vector elements = getResourceDialogElements( funds ); std::vector uiElements; uiElements.reserve( elements.size() ); for ( const fheroes2::ResourceDialogElement & element : elements ) { uiElements.emplace_back( &element ); } return showMessage( header, body, buttons, uiElements ); } SpellDialogElement::SpellDialogElement( const Spell & spell, const HeroBase * hero ) : _spell( spell ) , _hero( hero ) { assert( spell.isValid() ); std::string spellText( _spell.GetName() ); const uint32_t spellPoints = _spell.spellPoints( nullptr ); if ( spellPoints > 0 ) { spellText += " [" + std::to_string( spellPoints ) + ']'; } const Text spellNameText( std::move( spellText ), FontType::smallWhite() ); const Sprite & icn = AGG::GetICN( ICN::SPELLS, _spell.IndexSprite() ); _area = { std::max( icn.width(), spellNameText.width() ), icn.height() + textOffsetFromElement + spellNameText.height() }; } void SpellDialogElement::draw( Image & output, const Point & offset ) const { std::string spellText( _spell.GetName() ); const uint32_t spellPoints = _spell.spellPoints( nullptr ); if ( spellPoints > 0 ) { spellText += " [" + std::to_string( spellPoints ) + ']'; } const Text spellNameText( std::move( spellText ), FontType::smallWhite() ); const Sprite & icn = AGG::GetICN( ICN::SPELLS, _spell.IndexSprite() ); const int32_t maxWidth = std::max( icn.width(), spellNameText.width() ); Blit( icn, 0, 0, output, offset.x + ( maxWidth - icn.width() ) / 2, offset.y, icn.width(), icn.height() ); spellNameText.draw( offset.x + ( maxWidth - spellNameText.width() ) / 2, offset.y + icn.height() + textOffsetFromElement, output ); } void SpellDialogElement::processEvents( const Point & offset ) const { if ( LocalEvent::Get().MousePressRight( { offset.x, offset.y, _area.width, _area.height } ) ) { // Make sure you never pass any buttons here to avoid call stack overflow! showPopup( defaultElementPopupButtons ); } } void SpellDialogElement::showPopup( const int buttons ) const { const Text header( _spell.GetName(), FontType::normalYellow() ); const Text description( getSpellDescription( _spell, _hero ), FontType::normalWhite() ); showMessage( header, description, buttons, { this } ); } LuckDialogElement::LuckDialogElement( const bool goodLuck ) : _goodLuck( goodLuck ) { const fheroes2::Sprite & icn = fheroes2::AGG::GetICN( ICN::EXPMRL, ( _goodLuck ? 0 : 1 ) ); _area = { icn.width(), icn.height() }; } void LuckDialogElement::draw( Image & output, const Point & offset ) const { const fheroes2::Sprite & icn = fheroes2::AGG::GetICN( ICN::EXPMRL, ( _goodLuck ? 0 : 1 ) ); Blit( icn, 0, 0, output, offset.x, offset.y, icn.width(), icn.height() ); } void LuckDialogElement::processEvents( const Point & offset ) const { if ( LocalEvent::Get().MousePressRight( { offset.x, offset.y, _area.width, _area.height } ) ) { // Make sure you never pass any buttons here to avoid call stack overflow! showPopup( defaultElementPopupButtons ); } } void LuckDialogElement::showPopup( const int buttons ) const { const int luckType = _goodLuck ? Luck::GOOD : Luck::BAD; const Text header( LuckString( luckType ), FontType::normalYellow() ); const Text description( Luck::Description( luckType ), FontType::normalWhite() ); showMessage( header, description, buttons, { this } ); } MoraleDialogElement::MoraleDialogElement( const bool goodMorale ) : _goodMorale( goodMorale ) { const fheroes2::Sprite & icn = fheroes2::AGG::GetICN( ICN::EXPMRL, ( _goodMorale ? 2 : 3 ) ); _area = { icn.width(), icn.height() }; } void MoraleDialogElement::draw( Image & output, const Point & offset ) const { const fheroes2::Sprite & icn = fheroes2::AGG::GetICN( ICN::EXPMRL, ( _goodMorale ? 2 : 3 ) ); Blit( icn, 0, 0, output, offset.x, offset.y, icn.width(), icn.height() ); } void MoraleDialogElement::processEvents( const Point & offset ) const { if ( LocalEvent::Get().MousePressRight( { offset.x, offset.y, _area.width, _area.height } ) ) { // Make sure you never pass any buttons here to avoid call stack overflow! showPopup( defaultElementPopupButtons ); } } void MoraleDialogElement::showPopup( const int buttons ) const { const int moraleType = _goodMorale ? Morale::GOOD : Morale::POOR; const Text header( MoraleString( moraleType ), FontType::normalYellow() ); const Text description( Morale::Description( moraleType ), FontType::normalWhite() ); showMessage( header, description, buttons, { this } ); } ExperienceDialogElement::ExperienceDialogElement( const int32_t experience ) : _experience( experience ) { const Sprite & icn = AGG::GetICN( ICN::EXPMRL, 4 ); if ( experience != 0 ) { const Text experienceText( std::to_string( _experience ), FontType::smallWhite() ); _area = { std::max( icn.width(), experienceText.width() ), icn.height() + textOffsetFromElement + experienceText.height() }; } else { _area = { icn.width(), icn.height() }; } } void ExperienceDialogElement::draw( Image & output, const Point & offset ) const { const Sprite & icn = AGG::GetICN( ICN::EXPMRL, 4 ); if ( _experience != 0 ) { const Text experienceText( std::to_string( _experience ), FontType::smallWhite() ); const int32_t maxWidth = std::max( icn.width(), experienceText.width() ); Blit( icn, 0, 0, output, offset.x + ( maxWidth - icn.width() ) / 2, offset.y, icn.width(), icn.height() ); experienceText.draw( offset.x + ( maxWidth - experienceText.width() ) / 2, offset.y + icn.height() + textOffsetFromElement, output ); } else { Blit( icn, 0, 0, output, offset.x, offset.y, icn.width(), icn.height() ); } } void ExperienceDialogElement::processEvents( const Point & offset ) const { if ( LocalEvent::Get().MousePressRight( { offset.x, offset.y, _area.width, _area.height } ) ) { // Make sure you never pass any buttons here to avoid call stack overflow! showPopup( defaultElementPopupButtons ); } } void ExperienceDialogElement::showPopup( const int buttons ) const { const Text header( getExperienceName(), FontType::normalYellow() ); const Text description( getExperienceDescription(), FontType::normalWhite() ); const ExperienceDialogElement experienceUI( 0 ); showMessage( header, description, buttons, { &experienceUI } ); } PrimarySkillDialogElement::PrimarySkillDialogElement( const int32_t skillType, std::string text ) : _skillType( skillType ) , _text( std::move( text ) ) { assert( _skillType >= Skill::Primary::ATTACK && _skillType <= Skill::Primary::KNOWLEDGE ); const Sprite & background = AGG::GetICN( ICN::PRIMSKIL, 4 ); _area = { background.width(), background.height() }; } void PrimarySkillDialogElement::draw( Image & output, const Point & offset ) const { const Sprite & background = AGG::GetICN( ICN::PRIMSKIL, 4 ); Blit( background, 0, 0, output, offset.x, offset.y, background.width(), background.height() ); uint32_t icnId = 0; switch ( _skillType ) { case Skill::Primary::ATTACK: icnId = 0; break; case Skill::Primary::DEFENSE: icnId = 1; break; case Skill::Primary::POWER: icnId = 2; break; case Skill::Primary::KNOWLEDGE: icnId = 3; break; default: // Are you sure you are passing the correct Primary Skill type? assert( 0 ); break; } const Sprite & icn = AGG::GetICN( ICN::PRIMSKIL, icnId ); Blit( icn, 0, 0, output, offset.x + ( background.width() - icn.width() ) / 2, offset.y + ( background.height() - icn.height() ) / 2, icn.width(), icn.height() ); const Text skillName( Skill::Primary::String( _skillType ), FontType::smallWhite() ); skillName.draw( offset.x + ( background.width() - skillName.width() ) / 2, offset.y + 10, output ); if ( !_text.empty() ) { const Text descriptionText( _text, FontType::normalWhite() ); descriptionText.draw( offset.x + ( background.width() - descriptionText.width() ) / 2, offset.y + 82, output ); } } void PrimarySkillDialogElement::processEvents( const Point & offset ) const { if ( LocalEvent::Get().MousePressRight( { offset.x, offset.y, _area.width, _area.height } ) ) { // Make sure you never pass any buttons here to avoid call stack overflow! showPopup( defaultElementPopupButtons ); } } void PrimarySkillDialogElement::showPopup( const int buttons ) const { const Text header( Skill::Primary::String( _skillType ), FontType::normalYellow() ); const Text description( Skill::Primary::StringDescription( _skillType, nullptr ), FontType::normalWhite() ); const PrimarySkillDialogElement elementUI( _skillType, std::string() ); showMessage( header, description, buttons, { &elementUI } ); } SmallPrimarySkillDialogElement::SmallPrimarySkillDialogElement( const int32_t skillType, std::string text ) : PrimarySkillDialogElement( skillType, std::move( text ) ) { _area = _iconSize; if ( !_text.empty() ) { _area.height += Text{ _text, FontType::smallWhite() }.height( _iconSize.width ) + 2; } } void SmallPrimarySkillDialogElement::draw( Image & output, const Point & offset ) const { const Sprite & originalImage = AGG::GetICN( ICN::SWAPWIN, 0 ); switch ( _skillType ) { case Skill::Primary::ATTACK: Copy( originalImage, 216, 51, output, offset.x, offset.y, _iconSize.width, _iconSize.height ); break; case Skill::Primary::DEFENSE: Copy( originalImage, 216, 84, output, offset.x, offset.y, _iconSize.width, _iconSize.height ); break; case Skill::Primary::POWER: Copy( originalImage, 216, 117, output, offset.x, offset.y, _iconSize.width, _iconSize.height ); break; case Skill::Primary::KNOWLEDGE: Copy( originalImage, 216, 150, output, offset.x, offset.y, _iconSize.width, _iconSize.height ); break; default: // Are you sure you are passing the correct Primary Skill type? assert( 0 ); break; } if ( !_text.empty() ) { const Text descriptionText( _text, FontType::smallWhite() ); descriptionText.draw( offset.x, offset.y + _iconSize.height + 2, _iconSize.width, output ); } } SecondarySkillDialogElement::SecondarySkillDialogElement( const Skill::Secondary & skill, const Heroes & hero ) : _skill( skill ) , _hero( hero ) { const Sprite & background = AGG::GetICN( ICN::SECSKILL, 15 ); _area = { background.width(), background.height() }; } void SecondarySkillDialogElement::draw( Image & output, const Point & offset ) const { const Sprite & background = AGG::GetICN( ICN::SECSKILL, 15 ); Blit( background, 0, 0, output, offset.x, offset.y, background.width(), background.height() ); const Sprite & icn = AGG::GetICN( ICN::SECSKILL, _skill.GetIndexSprite1() ); const fheroes2::Rect icnRect( offset.x + ( background.width() - icn.width() ) / 2, offset.y + ( background.height() - icn.height() ) / 2, icn.width(), icn.height() ); Copy( icn, 0, 0, output, icnRect ); Text skillName( Skill::Secondary::String( _skill.Skill() ), FontType::smallWhite() ); skillName.fitToOneRow( icnRect.width ); skillName.drawInRoi( offset.x + ( background.width() - skillName.width() ) / 2 - 1, offset.y + 8, output, icnRect ); Text skillDescription( Skill::Level::StringWithBonus( _hero, _skill ), FontType::smallWhite() ); skillDescription.fitToOneRow( icnRect.width ); skillDescription.drawInRoi( offset.x + ( background.width() - skillDescription.width() ) / 2 - 1, offset.y + 56, output, icnRect ); } void SecondarySkillDialogElement::processEvents( const Point & offset ) const { if ( LocalEvent::Get().MousePressRight( { offset.x, offset.y, _area.width, _area.height } ) ) { // Make sure you never pass any buttons here to avoid call stack overflow! showPopup( defaultElementPopupButtons ); } } void SecondarySkillDialogElement::showPopup( const int buttons ) const { const Text header( _skill.GetNameWithBonus( _hero ), FontType::normalYellow() ); const Text description( _skill.GetDescription( _hero ), FontType::normalWhite() ); showMessage( header, description, buttons, { this } ); } AnimationDialogElement::AnimationDialogElement( const int icnId, std::vector backgroundIndices, const uint32_t animationIndexOffset, const uint64_t delay ) : _icnId( icnId ) , _backgroundIndices( std::move( backgroundIndices ) ) , _animationIndexOffset( animationIndexOffset ) , _delay( delay ) , _currentIndex( 0 ) { assert( !_backgroundIndices.empty() && _delay > 0 ); for ( const uint32_t index : _backgroundIndices ) { const Sprite & image = AGG::GetICN( _icnId, index ); _area.width = std::max( _area.width, image.width() ); _area.height = std::max( _area.height, image.height() ); _internalOffset = { ( _area.width - image.width() ) / 2, ( _area.height - image.height() ) / 2 }; } } void AnimationDialogElement::draw( Image & output, const Point & offset ) const { if ( _currentIndex == 0 ) { // Since this is the first time to draw we have to draw the background. for ( const uint32_t index : _backgroundIndices ) { const Sprite & image = AGG::GetICN( _icnId, index ); Blit( image, 0, 0, output, offset.x + ( _area.width - image.width() ) / 2, offset.y + ( _area.height - image.height() ) / 2, image.width(), image.height() ); } } const uint32_t animationFrameId = ICN::AnimationFrame( _icnId, _animationIndexOffset, _currentIndex ); ++_currentIndex; const Sprite & animationImage = AGG::GetICN( _icnId, animationFrameId ); Blit( animationImage, 0, 0, output, offset.x + _internalOffset.x + animationImage.x(), offset.y + _internalOffset.y + animationImage.y(), animationImage.width(), animationImage.height() ); } void AnimationDialogElement::processEvents( const Point & /* offset */ ) const { // No events processed here. } void AnimationDialogElement::showPopup( const int /* buttons */ ) const { assert( 0 ); } bool AnimationDialogElement::update( Image & output, const Point & offset ) const { if ( Game::validateCustomAnimationDelay( _delay ) ) { draw( output, offset ); return true; } return false; } CustomAnimationDialogElement::CustomAnimationDialogElement( const int icnId, Image staticImage, const Point animationPositionOffset, const uint32_t animationIndexOffset, const uint64_t delay ) : _icnId( icnId ) , _staticImage( std::move( staticImage ) ) , _animationPosition( animationPositionOffset ) , _animationIndexOffset( animationIndexOffset ) , _delay( delay ) , _currentIndex( 0 ) { assert( delay > 0 ); _area = { _staticImage.width(), _staticImage.height() }; } void CustomAnimationDialogElement::draw( Image & output, const Point & offset ) const { if ( _currentIndex == 0 ) { // Since this is the first time to draw we have to draw the background. Blit( _staticImage, 0, 0, output, offset.x, offset.y, _staticImage.width(), _staticImage.height() ); } const uint32_t animationFrameId = ICN::AnimationFrame( _icnId, _animationIndexOffset, _currentIndex ); ++_currentIndex; const Sprite & animationImage = AGG::GetICN( _icnId, animationFrameId ); Blit( animationImage, 0, 0, output, offset.x + _animationPosition.x, offset.y + _animationPosition.y, animationImage.width(), animationImage.height() ); } void CustomAnimationDialogElement::processEvents( const Point & /* offset */ ) const { // No events processed here. } void CustomAnimationDialogElement::showPopup( const int /* buttons */ ) const { assert( 0 ); } bool CustomAnimationDialogElement::update( Image & output, const Point & offset ) const { if ( Game::validateCustomAnimationDelay( _delay ) ) { draw( output, offset ); return true; } return false; } } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_dialog.h000066400000000000000000000252471456075706000214500ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include #include #include "artifact.h" #include "image.h" #include "math_base.h" #include "skill.h" #include "spell.h" class Funds; class HeroBase; class Heroes; namespace fheroes2 { class DialogElement; class TextBase; int showMessage( const TextBase & header, const TextBase & body, const int buttons, const std::vector & elements = {} ); // This is a simplified version of UI window which is used to display a window with a text. // Header text has yellow normal font style and body text - white normal font style. int showStandardTextMessage( std::string headerText, std::string messageBody, const int buttons ); // An interactive UI element within a dialog. class DialogElement { public: virtual ~DialogElement() = default; // Draw the element on a given image. virtual void draw( Image & output, const Point & offset ) const = 0; // Process events internally. In most cases it is a right mouse click event only. virtual void processEvents( const Point & offset ) const = 0; // Return the size of the element. const Size & area() const { return _area; } // Display a popup window with no buttons and standard description of the element. It is usually used for a right mouse click event. virtual void showPopup( const int buttons ) const = 0; // Update the content of UI elements. By default it does nothing. virtual bool update( Image & /*output*/, const Point & /*offset*/ ) const { return false; } protected: // This element must be cached to avoid heavy calculations. Size _area; }; // IMPORTANT! // It is essential to store members by values rather than by references. // This leads to more memory consumption but at the same time prevents any memory related issues. class TextDialogElement : public DialogElement { public: explicit TextDialogElement( const std::shared_ptr & text ); ~TextDialogElement() override = default; void draw( Image & output, const Point & offset ) const override; void processEvents( const Point & offset ) const override; // Never call this method as a custom image has nothing to popup. void showPopup( const int buttons ) const override; private: const std::shared_ptr _text; }; class CustomImageDialogElement : public DialogElement { public: explicit CustomImageDialogElement( const Image & image ); explicit CustomImageDialogElement( Image && image ); ~CustomImageDialogElement() override = default; void draw( Image & output, const Point & offset ) const override; void processEvents( const Point & offset ) const override; // Never call this method as a custom image has nothing to popup. void showPopup( const int buttons ) const override; private: const Image _image; }; class ArtifactDialogElement : public DialogElement { public: explicit ArtifactDialogElement( const Artifact & artifact ); ~ArtifactDialogElement() override = default; void draw( Image & output, const Point & offset ) const override; void processEvents( const Point & offset ) const override; void showPopup( const int buttons ) const override; private: const Artifact _artifact; }; class ResourceDialogElement : public DialogElement { public: ResourceDialogElement( const int32_t resourceType, std::string text ); ~ResourceDialogElement() override = default; void draw( Image & output, const Point & offset ) const override; void processEvents( const Point & offset ) const override; void showPopup( const int buttons ) const override; private: const int32_t _resourceType = 0; const uint32_t _icnIndex = 0; const std::string _text; }; std::vector getResourceDialogElements( const Funds & funds ); int showResourceMessage( const TextBase & header, const TextBase & body, const int buttons, const Funds & funds ); class SpellDialogElement : public DialogElement { public: SpellDialogElement( const Spell & spell, const HeroBase * hero ); ~SpellDialogElement() override = default; void draw( Image & output, const Point & offset ) const override; void processEvents( const Point & offset ) const override; void showPopup( const int buttons ) const override; private: const Spell _spell; const HeroBase * _hero; }; class LuckDialogElement : public DialogElement { public: explicit LuckDialogElement( const bool goodLuck ); ~LuckDialogElement() override = default; void draw( Image & output, const Point & offset ) const override; void processEvents( const Point & offset ) const override; void showPopup( const int buttons ) const override; private: const bool _goodLuck; }; class MoraleDialogElement : public DialogElement { public: explicit MoraleDialogElement( const bool goodMorale ); ~MoraleDialogElement() override = default; void draw( Image & output, const Point & offset ) const override; void processEvents( const Point & offset ) const override; void showPopup( const int buttons ) const override; private: const bool _goodMorale; }; class ExperienceDialogElement : public DialogElement { public: explicit ExperienceDialogElement( const int32_t experience ); ~ExperienceDialogElement() override = default; void draw( Image & output, const Point & offset ) const override; void processEvents( const Point & offset ) const override; void showPopup( const int buttons ) const override; private: const int32_t _experience; }; class PrimarySkillDialogElement : public DialogElement { public: PrimarySkillDialogElement( const int32_t skillType, std::string text ); ~PrimarySkillDialogElement() override = default; void draw( Image & output, const Point & offset ) const override; void processEvents( const Point & offset ) const override; void showPopup( const int buttons ) const override; protected: const int32_t _skillType; const std::string _text; }; class SmallPrimarySkillDialogElement : public PrimarySkillDialogElement { public: SmallPrimarySkillDialogElement( const int32_t skillType, std::string text ); ~SmallPrimarySkillDialogElement() override = default; void draw( Image & output, const Point & offset ) const override; private: const Size _iconSize{ 34, 34 }; }; class SecondarySkillDialogElement : public DialogElement { public: SecondarySkillDialogElement( const Skill::Secondary & skill, const Heroes & hero ); ~SecondarySkillDialogElement() override = default; void draw( Image & output, const Point & offset ) const override; void processEvents( const Point & offset ) const override; void showPopup( const int buttons ) const override; private: const Skill::Secondary _skill; const Heroes & _hero; }; class AnimationDialogElement : public DialogElement { public: explicit AnimationDialogElement( const int icnId, std::vector backgroundIndices, const uint32_t animationIndexOffset, const uint64_t delay ); ~AnimationDialogElement() override = default; void draw( Image & output, const Point & offset ) const override; void processEvents( const Point & offset ) const override; // Never call this method as a dynamic image has nothing to popup. void showPopup( const int buttons ) const override; bool update( Image & output, const Point & offset ) const override; private: const int _icnId; const std::vector _backgroundIndices; const uint32_t _animationIndexOffset; const uint64_t _delay; mutable uint32_t _currentIndex; Point _internalOffset; }; class CustomAnimationDialogElement : public DialogElement { public: explicit CustomAnimationDialogElement( const int icnId, Image staticImage, const Point animationPositionOffset, const uint32_t animationIndexOffset, const uint64_t delay ); ~CustomAnimationDialogElement() override = default; void draw( Image & output, const Point & offset ) const override; void processEvents( const Point & offset ) const override; // Never call this method as a dynamic image has nothing to popup. void showPopup( const int buttons ) const override; bool update( Image & output, const Point & offset ) const override; private: const int _icnId; const Image _staticImage; const Point _animationPosition; const uint32_t _animationIndexOffset; const uint64_t _delay; mutable uint32_t _currentIndex; }; } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_font.cpp000066400000000000000000013413561456075706000215150ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "ui_font.h" #include #include #include #include #include #include "icn.h" #include "image.h" #include "math_base.h" namespace { const size_t baseFontSize = 96; const uint8_t buttonGoodReleasedColor = 56; const uint8_t buttonGoodPressedColor = 62; const uint8_t buttonEvilReleasedColor = 30; const uint8_t buttonEvilPressedColor = 36; const uint8_t buttonContourColor = 10; const fheroes2::Point buttonFontOffset{ -1, 0 }; void updateNormalFontLetterShadow( fheroes2::Image & letter ) { fheroes2::updateShadow( letter, { -1, 2 }, 2, false ); } void updateSmallFontLetterShadow( fheroes2::Image & letter ) { fheroes2::updateShadow( letter, { -1, 1 }, 2, true ); } fheroes2::Sprite addContour( fheroes2::Sprite & input, const fheroes2::Point & contourOffset, const uint8_t colorId ) { if ( input.empty() || input.singleLayer() || contourOffset.x > 0 || contourOffset.y < 0 || ( -contourOffset.x >= input.width() ) || ( contourOffset.y >= input.height() ) ) { return input; } fheroes2::Sprite output = input; const int32_t imageWidth = output.width(); const int32_t width = imageWidth + contourOffset.x; const int32_t height = output.height() - contourOffset.y; const int32_t offsetY = imageWidth * contourOffset.y; uint8_t * imageOutY = output.image() + offsetY; const uint8_t * transformInY = input.transform() - contourOffset.x; uint8_t * transformOutY = output.transform() + offsetY; const uint8_t * transformOutYEnd = transformOutY + imageWidth * height; for ( ; transformOutY != transformOutYEnd; transformInY += imageWidth, transformOutY += imageWidth, imageOutY += imageWidth ) { uint8_t * imageOutX = imageOutY; const uint8_t * transformInX = transformInY; uint8_t * transformOutX = transformOutY; const uint8_t * transformOutXEnd = transformOutX + width; for ( ; transformOutX != transformOutXEnd; ++transformInX, ++transformOutX, ++imageOutX ) { if ( *transformOutX == 1 && ( *transformInX == 0 || ( *( transformInX + contourOffset.x ) == 0 && *( transformInX + offsetY ) == 0 ) ) ) { // When there are two pixels adjacent diagonally we create a contour pixel in the corner that is closer to the image. // Doing so there will be no "empty" pixels between the image and its shadow (for 1 pixel offset case). *imageOutX = colorId; *transformOutX = 0; } } } return output; } void applyGoodButtonReleasedLetterEffects( fheroes2::Sprite & letter ) { updateShadow( letter, { 1, -1 }, 2, true ); updateShadow( letter, { 2, -2 }, 4, true ); letter = addContour( letter, { -1, 1 }, buttonContourColor ); updateShadow( letter, { -1, 1 }, 7, true ); } void applyGoodButtonPressedLetterEffects( fheroes2::Sprite & letter ) { ReplaceColorId( letter, buttonGoodReleasedColor, buttonGoodPressedColor ); fheroes2::updateShadow( letter, { 1, -1 }, 2, true ); fheroes2::updateShadow( letter, { -1, 1 }, 7, true ); fheroes2::updateShadow( letter, { -2, 2 }, 8, true ); } void applyEvilButtonReleasedLetterEffects( fheroes2::Sprite & letter ) { ReplaceColorId( letter, buttonGoodReleasedColor, buttonEvilReleasedColor ); } void applyEvilButtonPressedLetterEffects( fheroes2::Sprite & letter ) { ReplaceColorId( letter, buttonGoodPressedColor, buttonEvilPressedColor ); } void updateButtonFont( std::vector & goodReleased, std::vector & goodPressed, std::vector & evilReleased, std::vector & evilPressed ) { goodPressed.resize( goodReleased.size() ); evilReleased.resize( goodReleased.size() ); evilPressed.resize( goodReleased.size() ); for ( size_t i = 0; i < goodReleased.size(); ++i ) { goodPressed[i] = goodReleased[i]; // Apply special effects on good interface letters first. applyGoodButtonReleasedLetterEffects( goodReleased[i] ); applyGoodButtonPressedLetterEffects( goodPressed[i] ); evilReleased[i] = goodReleased[i]; evilPressed[i] = goodPressed[i]; applyEvilButtonReleasedLetterEffects( evilReleased[i] ); applyEvilButtonPressedLetterEffects( evilPressed[i] ); } } void generateCP1250Alphabet( std::vector> & icnVsSprite ) { // Resize fonts. for ( const int icnId : { ICN::FONT, ICN::SMALFONT } ) { icnVsSprite[icnId].resize( baseFontSize ); const fheroes2::Sprite firstSprite{ icnVsSprite[icnId][0] }; icnVsSprite[icnId].insert( icnVsSprite[icnId].end(), 160, firstSprite ); } // Normal font. { std::vector & font = icnVsSprite[ICN::FONT]; // Uppercase S with caron. Generate accent for further use. font[138 - 32].resize( font[83 - 32].width(), font[83 - 32].height() + 3 ); font[138 - 32].reset(); fheroes2::Copy( font[83 - 32], 0, 0, font[138 - 32], 0, 3, font[83 - 32].width(), font[83 - 32].height() ); fheroes2::Copy( font[65], 1, 1, font[138 - 32], 4, 0, 1, 1 ); fheroes2::Copy( font[65], 1, 1, font[138 - 32], 6, 0, 1, 1 ); fheroes2::Copy( font[65], 1, 0, font[138 - 32], 4, 1, 1, 1 ); fheroes2::Copy( font[65], 7, 1, font[138 - 32], 5, 1, 1, 1 ); fheroes2::Copy( font[65], 1, 0, font[138 - 32], 6, 1, 1, 1 ); font[138 - 32].setPosition( font[83 - 32].x(), font[83 - 32].y() - 3 ); updateNormalFontLetterShadow( font[138 - 32] ); // Uppercase S with acute. Generate accent for further use. font[140 - 32].resize( font[83 - 32].width(), font[83 - 32].height() + 3 ); font[140 - 32].reset(); fheroes2::Copy( font[83 - 32], 0, 0, font[140 - 32], 0, 3, font[83 - 32].width(), font[83 - 32].height() ); fheroes2::Copy( font[111 - 32], 2, 0, font[140 - 32], 4, 0, 3, 2 ); fheroes2::FillTransform( font[140 - 32], 4, 0, 1, 1, 1 ); fheroes2::FillTransform( font[140 - 32], 6, 1, 1, 1, 1 ); font[140 - 32].setPosition( font[83 - 32].x(), font[83 - 32].y() - 3 ); updateNormalFontLetterShadow( font[140 - 32] ); // Uppercase T with caron font[141 - 32].resize( font[84 - 32].width(), font[84 - 32].height() + 3 ); font[141 - 32].reset(); fheroes2::Copy( font[84 - 32], 0, 0, font[141 - 32], 0, 3, font[84 - 32].width(), font[84 - 32].height() ); fheroes2::Copy( font[138 - 32], 4, 0, font[141 - 32], 5, 0, 3, 2 ); font[141 - 32].setPosition( font[84 - 32].x(), font[84 - 32].y() - 3 ); updateNormalFontLetterShadow( font[141 - 32] ); // Uppercase Z with caron font[142 - 32].resize( font[90 - 32].width(), font[90 - 32].height() + 3 ); font[142 - 32].reset(); fheroes2::Copy( font[90 - 32], 0, 0, font[142 - 32], 0, 3, font[90 - 32].width(), font[90 - 32].height() ); fheroes2::Copy( font[138 - 32], 4, 0, font[142 - 32], 5, 0, 3, 2 ); font[142 - 32].setPosition( font[90 - 32].x(), font[90 - 32].y() - 3 ); updateNormalFontLetterShadow( font[142 - 32] ); // Uppercase Z with acute font[143 - 32].resize( font[90 - 32].width(), font[90 - 32].height() + 3 ); font[143 - 32].reset(); fheroes2::Copy( font[90 - 32], 0, 0, font[143 - 32], 0, 3, font[90 - 32].width(), font[90 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[143 - 32], 4, 0, 3, 2 ); font[143 - 32].setPosition( font[90 - 32].x(), font[90 - 32].y() - 3 ); updateNormalFontLetterShadow( font[143 - 32] ); // Lowercase s with caron font[154 - 32].resize( font[115 - 32].width(), font[115 - 32].height() + 3 ); font[154 - 32].reset(); fheroes2::Copy( font[115 - 32], 0, 0, font[154 - 32], 0, 3, font[115 - 32].width(), font[115 - 32].height() ); fheroes2::Copy( font[138 - 32], 4, 0, font[154 - 32], 3, 0, 3, 2 ); font[154 - 32].setPosition( font[115 - 32].x(), font[115 - 32].y() - 3 ); updateNormalFontLetterShadow( font[154 - 32] ); // Lowercase s with acute font[156 - 32].resize( font[115 - 32].width(), font[115 - 32].height() + 3 ); font[156 - 32].reset(); fheroes2::Copy( font[115 - 32], 0, 0, font[156 - 32], 0, 3, font[115 - 32].width(), font[115 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[156 - 32], 4, 0, 3, 2 ); font[156 - 32].setPosition( font[115 - 32].x(), font[115 - 32].y() - 3 ); updateNormalFontLetterShadow( font[156 - 32] ); // Lowercase t with caron font[157 - 32].resize( font[116 - 32].width(), font[116 - 32].height() + 1 ); font[157 - 32].reset(); fheroes2::Copy( font[116 - 32], 0, 0, font[157 - 32], 0, 1, font[116 - 32].width(), font[116 - 32].height() ); fheroes2::Copy( font[65], 1, 1, font[157 - 32], 5, 1, 1, 1 ); fheroes2::Copy( font[65], 1, 1, font[157 - 32], 6, 0, 1, 1 ); fheroes2::Copy( font[65], 1, 0, font[157 - 32], 4, 1, 1, 1 ); fheroes2::Copy( font[65], 1, 0, font[157 - 32], 5, 0, 1, 1 ); font[157 - 32].setPosition( font[116 - 32].x(), font[116 - 32].y() - 1 ); updateNormalFontLetterShadow( font[157 - 32] ); // Lowercase z with caron font[158 - 32].resize( font[122 - 32].width(), font[122 - 32].height() + 3 ); font[158 - 32].reset(); fheroes2::Copy( font[122 - 32], 0, 0, font[158 - 32], 0, 3, font[122 - 32].width(), font[122 - 32].height() ); fheroes2::Copy( font[138 - 32], 4, 0, font[158 - 32], 4, 0, 3, 2 ); font[158 - 32].setPosition( font[122 - 32].x(), font[122 - 32].y() - 3 ); updateNormalFontLetterShadow( font[158 - 32] ); // Lowercase z with acute font[159 - 32].resize( font[122 - 32].width(), font[122 - 32].height() + 3 ); font[159 - 32].reset(); fheroes2::Copy( font[122 - 32], 0, 0, font[159 - 32], 0, 3, font[122 - 32].width(), font[122 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[159 - 32], 4, 0, 3, 2 ); font[159 - 32].setPosition( font[122 - 32].x(), font[122 - 32].y() - 3 ); updateNormalFontLetterShadow( font[159 - 32] ); // Uppercase L with stroke font[163 - 32].resize( font[76 - 32].width(), font[76 - 32].height() + 3 ); font[163 - 32].reset(); fheroes2::Copy( font[76 - 32], 0, 0, font[163 - 32], 0, 0, font[76 - 32].width(), font[76 - 32].height() ); // Stroke diacritic. fheroes2::Copy( font[76 - 32], 1, 1, font[163 - 32], 6, 5, 1, 1 ); fheroes2::Copy( font[76 - 32], 1, 1, font[163 - 32], 7, 4, 1, 1 ); fheroes2::Copy( font[76 - 32], 1, 1, font[163 - 32], 8, 3, 1, 1 ); fheroes2::Copy( font[76 - 32], 2, 1, font[163 - 32], 6, 6, 1, 1 ); fheroes2::Copy( font[76 - 32], 2, 1, font[163 - 32], 7, 5, 1, 1 ); fheroes2::Copy( font[76 - 32], 2, 1, font[163 - 32], 8, 4, 1, 1 ); font[163 - 32].setPosition( font[76 - 32].x(), font[76 - 32].y() ); updateNormalFontLetterShadow( font[163 - 32] ); // Uppercase A with ogonek. Generate ogonek for further use. font[165 - 32].resize( font[65 - 32].width(), font[65 - 32].height() + 3 ); font[165 - 32].reset(); fheroes2::Copy( font[65 - 32], 0, 0, font[165 - 32], 0, 0, font[65 - 32].width(), font[65 - 32].height() ); // Ogonek diacritic. fheroes2::Copy( font[97 - 32], 6, 5, font[165 - 32], 12, 11, 1, 1 ); fheroes2::Copy( font[97 - 32], 6, 5, font[165 - 32], 11, 12, 1, 1 ); fheroes2::Copy( font[97 - 32], 6, 5, font[165 - 32], 12, 13, 2, 1 ); fheroes2::Copy( font[97 - 32], 6, 5, font[165 - 32], 14, 12, 1, 1 ); fheroes2::Copy( font[97 - 32], 1, 3, font[165 - 32], 11, 11, 1, 1 ); fheroes2::Copy( font[97 - 32], 1, 3, font[165 - 32], 12, 12, 1, 1 ); fheroes2::Copy( font[97 - 32], 1, 3, font[165 - 32], 11, 13, 1, 1 ); fheroes2::Copy( font[97 - 32], 1, 3, font[165 - 32], 14, 13, 1, 1 ); font[165 - 32].setPosition( font[65 - 32].x(), font[65 - 32].y() ); updateNormalFontLetterShadow( font[165 - 32] ); // Remove unnecessary shadows fheroes2::FillTransform( font[165 - 32], 10, 13, 1, 1, 1 ); fheroes2::FillTransform( font[165 - 32], 11, 14, 1, 1, 1 ); fheroes2::FillTransform( font[165 - 32], 10, 15, 1, 1, 1 ); fheroes2::FillTransform( font[165 - 32], 13, 15, 1, 1, 1 ); // Uppercase Z with dot above. Generate dot for further use. font[175 - 32].resize( font[90 - 32].width(), font[90 - 32].height() + 3 ); font[175 - 32].reset(); fheroes2::Copy( font[90 - 32], 0, 0, font[175 - 32], 0, 3, font[90 - 32].width(), font[90 - 32].height() ); fheroes2::Copy( font[122 - 32], 7, 5, font[175 - 32], 5, 0, 2, 1 ); fheroes2::Copy( font[122 - 32], 6, 1, font[175 - 32], 5, 1, 2, 1 ); font[175 - 32].setPosition( font[90 - 32].x(), font[90 - 32].y() - 3 ); updateNormalFontLetterShadow( font[175 - 32] ); // Lowercase l with stroke font[179 - 32].resize( font[108 - 32].width(), font[108 - 32].height() + 3 ); font[179 - 32].reset(); fheroes2::Copy( font[108 - 32], 0, 0, font[179 - 32], 0, 0, font[108 - 32].width(), font[108 - 32].height() ); // Stroke diacritic. fheroes2::Copy( font[108 - 32], 3, 1, font[179 - 32], 4, 3, 1, 1 ); fheroes2::Copy( font[108 - 32], 2, 1, font[179 - 32], 4, 4, 1, 1 ); fheroes2::Copy( font[108 - 32], 3, 1, font[179 - 32], 2, 6, 1, 1 ); fheroes2::Copy( font[108 - 32], 2, 1, font[179 - 32], 1, 6, 1, 1 ); fheroes2::Copy( font[108 - 32], 3, 0, font[179 - 32], 2, 7, 1, 1 ); fheroes2::Copy( font[108 - 32], 1, 1, font[179 - 32], 1, 7, 1, 1 ); font[179 - 32].setPosition( font[108 - 32].x(), font[108 - 32].y() ); updateNormalFontLetterShadow( font[179 - 32] ); // Lowercase a with ogonek font[185 - 32].resize( font[97 - 32].width(), font[97 - 32].height() + 3 ); font[185 - 32].reset(); fheroes2::Copy( font[97 - 32], 0, 0, font[185 - 32], 0, 0, font[97 - 32].width(), font[97 - 32].height() ); font[185 - 32].setPosition( font[97 - 32].x(), font[97 - 32].y() ); updateNormalFontLetterShadow( font[185 - 32] ); // Shadows are already made for the ogonek. fheroes2::Copy( font[165 - 32], 10, 11, font[185 - 32], 5, 7, 5, 5 ); // Uppercase L with caron (NOT an uppercase Y with diaeresis) font[188 - 32].resize( font[76 - 32].width(), font[76 - 32].height() ); font[188 - 32].reset(); fheroes2::Copy( font[76 - 32], 0, 0, font[188 - 32], 0, 0, font[76 - 32].width(), font[76 - 32].height() ); fheroes2::Copy( font[65], 1, 1, font[188 - 32], 9, 0, 1, 1 ); fheroes2::Copy( font[65], 1, 1, font[188 - 32], 9, 1, 1, 1 ); fheroes2::Copy( font[65], 1, 1, font[188 - 32], 10, 0, 1, 1 ); fheroes2::Copy( font[65], 1, 0, font[188 - 32], 8, 0, 1, 1 ); fheroes2::Copy( font[65], 1, 0, font[188 - 32], 8, 1, 1, 1 ); font[188 - 32].setPosition( font[76 - 32].x(), font[76 - 32].y() ); updateNormalFontLetterShadow( font[188 - 32] ); // Lowercase l with caron (NOT an uppercase L with caron) font[190 - 32].resize( font[108 - 32].width() + 2, font[108 - 32].height() ); font[190 - 32].reset(); fheroes2::Copy( font[108 - 32], 0, 0, font[190 - 32], 0, 0, font[108 - 32].width(), font[108 - 32].height() ); fheroes2::Copy( font[188 - 32], 8, 0, font[190 - 32], 4, 0, 3, 2 ); font[190 - 32].setPosition( font[108 - 32].x(), font[108 - 32].y() ); updateNormalFontLetterShadow( font[190 - 32] ); // Lowercase z with dot above font[191 - 32].resize( font[122 - 32].width(), font[122 - 32].height() + 3 ); font[191 - 32].reset(); fheroes2::Copy( font[122 - 32], 0, 0, font[191 - 32], 0, 3, font[122 - 32].width(), font[122 - 32].height() ); fheroes2::Copy( font[175 - 32], 5, 0, font[191 - 32], 4, 0, 2, 2 ); font[191 - 32].setPosition( font[122 - 32].x(), font[122 - 32].y() - 3 ); updateNormalFontLetterShadow( font[191 - 32] ); // Uppercase R with acute font[192 - 32].resize( font[82 - 32].width(), font[82 - 32].height() + 3 ); font[192 - 32].reset(); fheroes2::Copy( font[82 - 32], 0, 0, font[192 - 32], 0, 3, font[82 - 32].width(), font[82 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[192 - 32], 7, 0, 3, 2 ); font[192 - 32].setPosition( font[82 - 32].x(), font[82 - 32].y() - 3 ); updateNormalFontLetterShadow( font[192 - 32] ); // Uppercase A with acute font[193 - 32].resize( font[65 - 32].width(), font[65 - 32].height() + 3 ); font[193 - 32].reset(); fheroes2::Copy( font[65 - 32], 0, 0, font[193 - 32], 0, 3, font[65 - 32].width(), font[65 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[193 - 32], 7, 0, 3, 2 ); font[193 - 32].setPosition( font[65 - 32].x(), font[65 - 32].y() - 3 ); updateNormalFontLetterShadow( font[193 - 32] ); // Uppercase A with circumflex font[194 - 32].resize( font[65 - 32].width(), font[65 - 32].height() + 3 ); font[194 - 32].reset(); fheroes2::Copy( font[65 - 32], 0, 0, font[194 - 32], 0, 3, font[65 - 32].width(), font[65 - 32].height() ); fheroes2::Copy( font[97 - 32], 7, 1, font[194 - 32], 7, 1, 1, 1 ); fheroes2::Copy( font[97 - 32], 7, 1, font[194 - 32], 8, 0, 1, 1 ); fheroes2::Copy( font[97 - 32], 7, 1, font[194 - 32], 9, 1, 1, 1 ); fheroes2::Copy( font[97 - 32], 1, 0, font[194 - 32], 7, 0, 1, 1 ); fheroes2::Copy( font[97 - 32], 1, 0, font[194 - 32], 9, 0, 1, 1 ); font[194 - 32].setPosition( font[65 - 32].x(), font[65 - 32].y() - 3 ); updateNormalFontLetterShadow( font[194 - 32] ); // Uppercase A with diaeresis font[196 - 32].resize( font[65 - 32].width(), font[65 - 32].height() + 3 ); font[196 - 32].reset(); fheroes2::Copy( font[65 - 32], 0, 0, font[196 - 32], 0, 3, font[65 - 32].width(), font[65 - 32].height() ); fheroes2::Copy( font[175 - 32], 5, 0, font[196 - 32], 5, 0, 2, 2 ); fheroes2::Copy( font[175 - 32], 5, 0, font[196 - 32], 10, 0, 2, 2 ); font[196 - 32].setPosition( font[65 - 32].x(), font[65 - 32].y() - 3 ); updateNormalFontLetterShadow( font[196 - 32] ); // Uppercase L with acute font[197 - 32].resize( font[76 - 32].width(), font[76 - 32].height() + 3 ); font[197 - 32].reset(); fheroes2::Copy( font[76 - 32], 0, 0, font[197 - 32], 0, 3, font[76 - 32].width(), font[76 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[197 - 32], 8, 0, 3, 2 ); font[197 - 32].setPosition( font[76 - 32].x(), font[76 - 32].y() - 3 ); updateNormalFontLetterShadow( font[197 - 32] ); // Uppercase C with acute font[198 - 32].resize( font[67 - 32].width(), font[67 - 32].height() + 3 ); font[198 - 32].reset(); fheroes2::Copy( font[67 - 32], 0, 0, font[198 - 32], 0, 3, font[67 - 32].width(), font[67 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[198 - 32], 8, 0, 3, 2 ); font[198 - 32].setPosition( font[67 - 32].x(), font[67 - 32].y() - 3 ); updateNormalFontLetterShadow( font[198 - 32] ); // Uppercase C with caron font[200 - 32].resize( font[67 - 32].width(), font[67 - 32].height() + 3 ); font[200 - 32].reset(); fheroes2::Copy( font[67 - 32], 0, 0, font[200 - 32], 0, 3, font[67 - 32].width(), font[67 - 32].height() ); fheroes2::Copy( font[138 - 32], 4, 0, font[200 - 32], 7, 0, 3, 2 ); font[200 - 32].setPosition( font[67 - 32].x(), font[67 - 32].y() - 3 ); updateNormalFontLetterShadow( font[200 - 32] ); // Uppercase E with acute font[201 - 32].resize( font[69 - 32].width(), font[69 - 32].height() + 3 ); font[201 - 32].reset(); fheroes2::Copy( font[69 - 32], 0, 0, font[201 - 32], 0, 3, font[69 - 32].width(), font[69 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[201 - 32], 6, 0, 3, 2 ); font[201 - 32].setPosition( font[69 - 32].x(), font[69 - 32].y() - 3 ); updateNormalFontLetterShadow( font[201 - 32] ); // Uppercase E with ogonek font[202 - 32].resize( font[69 - 32].width(), font[69 - 32].height() + 3 ); font[202 - 32].reset(); fheroes2::Copy( font[69 - 32], 0, 0, font[202 - 32], 0, 0, font[69 - 32].width(), font[69 - 32].height() ); font[202 - 32].setPosition( font[69 - 32].x(), font[69 - 32].y() ); updateNormalFontLetterShadow( font[202 - 32] ); // Shadows are already made for the ogonek. fheroes2::Copy( font[165 - 32], 10, 11, font[202 - 32], 5, 11, 5, 5 ); // Uppercase E with caron font[204 - 32].resize( font[69 - 32].width(), font[69 - 32].height() + 3 ); font[204 - 32].reset(); fheroes2::Copy( font[69 - 32], 0, 0, font[204 - 32], 0, 3, font[69 - 32].width(), font[69 - 32].height() ); fheroes2::Copy( font[138 - 32], 4, 0, font[204 - 32], 5, 0, 3, 2 ); font[204 - 32].setPosition( font[69 - 32].x(), font[69 - 32].y() - 3 ); updateNormalFontLetterShadow( font[204 - 32] ); // Uppercase I with acute font[205 - 32].resize( font[73 - 32].width(), font[73 - 32].height() + 3 ); font[205 - 32].reset(); fheroes2::Copy( font[73 - 32], 0, 0, font[205 - 32], 0, 3, font[73 - 32].width(), font[73 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[205 - 32], 4, 0, 3, 2 ); font[205 - 32].setPosition( font[73 - 32].x(), font[73 - 32].y() - 3 ); updateNormalFontLetterShadow( font[205 - 32] ); // Uppercase D with caron font[207 - 32].resize( font[68 - 32].width(), font[68 - 32].height() + 3 ); font[207 - 32].reset(); fheroes2::Copy( font[68 - 32], 0, 0, font[207 - 32], 0, 3, font[68 - 32].width(), font[68 - 32].height() ); fheroes2::Copy( font[138 - 32], 4, 0, font[207 - 32], 5, 0, 3, 2 ); font[207 - 32].setPosition( font[68 - 32].x(), font[68 - 32].y() - 3 ); updateNormalFontLetterShadow( font[207 - 32] ); // Uppercase N with acute font[209 - 32].resize( font[78 - 32].width(), font[78 - 32].height() + 3 ); font[209 - 32].reset(); fheroes2::Copy( font[78 - 32], 0, 0, font[209 - 32], 0, 3, font[78 - 32].width(), font[78 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[209 - 32], 8, 0, 3, 2 ); font[209 - 32].setPosition( font[78 - 32].x(), font[78 - 32].y() - 3 ); updateNormalFontLetterShadow( font[209 - 32] ); // Uppercase N with caron font[210 - 32].resize( font[78 - 32].width(), font[78 - 32].height() + 3 ); font[210 - 32].reset(); fheroes2::Copy( font[78 - 32], 0, 0, font[210 - 32], 0, 3, font[78 - 32].width(), font[78 - 32].height() ); fheroes2::Copy( font[138 - 32], 4, 0, font[210 - 32], 7, 0, 3, 2 ); font[210 - 32].setPosition( font[78 - 32].x(), font[78 - 32].y() - 3 ); updateNormalFontLetterShadow( font[210 - 32] ); // Uppercase O with acute font[211 - 32].resize( font[79 - 32].width(), font[79 - 32].height() + 3 ); font[211 - 32].reset(); fheroes2::Copy( font[79 - 32], 0, 0, font[211 - 32], 0, 3, font[79 - 32].width(), font[79 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[211 - 32], 8, 0, 3, 2 ); font[211 - 32].setPosition( font[79 - 32].x(), font[79 - 32].y() - 3 ); updateNormalFontLetterShadow( font[211 - 32] ); // Uppercase O with circumflex font[212 - 32].resize( font[79 - 32].width(), font[79 - 32].height() + 3 ); font[212 - 32].reset(); fheroes2::Copy( font[79 - 32], 0, 0, font[212 - 32], 0, 3, font[79 - 32].width(), font[79 - 32].height() ); fheroes2::Copy( font[65], 1, 1, font[212 - 32], 6, 1, 1, 1 ); fheroes2::Copy( font[65], 1, 1, font[212 - 32], 7, 0, 1, 1 ); fheroes2::Copy( font[65], 1, 1, font[212 - 32], 8, 0, 1, 1 ); fheroes2::Copy( font[65], 1, 1, font[212 - 32], 9, 0, 1, 1 ); fheroes2::Copy( font[65], 1, 1, font[212 - 32], 10, 1, 1, 1 ); fheroes2::Copy( font[65], 1, 0, font[212 - 32], 6, 0, 1, 1 ); fheroes2::Copy( font[65], 1, 0, font[212 - 32], 10, 0, 1, 1 ); font[212 - 32].setPosition( font[79 - 32].x(), font[79 - 32].y() - 3 ); updateNormalFontLetterShadow( font[212 - 32] ); // Uppercase O with double acute font[213 - 32].resize( font[79 - 32].width(), font[79 - 32].height() + 3 ); font[213 - 32].reset(); fheroes2::Copy( font[79 - 32], 0, 0, font[213 - 32], 0, 3, font[79 - 32].width(), font[79 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[213 - 32], 5, 0, 3, 2 ); fheroes2::Copy( font[140 - 32], 4, 0, font[213 - 32], 9, 0, 3, 2 ); font[213 - 32].setPosition( font[79 - 32].x(), font[79 - 32].y() - 3 ); updateNormalFontLetterShadow( font[213 - 32] ); // Uppercase O with diaeresis font[214 - 32].resize( font[79 - 32].width(), font[79 - 32].height() + 3 ); font[214 - 32].reset(); fheroes2::Copy( font[79 - 32], 0, 0, font[214 - 32], 0, 3, font[79 - 32].width(), font[79 - 32].height() ); fheroes2::Copy( font[175 - 32], 5, 0, font[214 - 32], 5, 0, 2, 2 ); fheroes2::Copy( font[175 - 32], 5, 0, font[214 - 32], 10, 0, 2, 2 ); font[214 - 32].setPosition( font[79 - 32].x(), font[79 - 32].y() - 3 ); updateNormalFontLetterShadow( font[214 - 32] ); // Uppercase R with caron font[216 - 32].resize( font[82 - 32].width(), font[82 - 32].height() + 3 ); font[216 - 32].reset(); fheroes2::Copy( font[82 - 32], 0, 0, font[216 - 32], 0, 3, font[82 - 32].width(), font[82 - 32].height() ); fheroes2::Copy( font[138 - 32], 4, 0, font[216 - 32], 5, 0, 3, 2 ); font[216 - 32].setPosition( font[82 - 32].x(), font[82 - 32].y() - 3 ); updateNormalFontLetterShadow( font[216 - 32] ); // Uppercase U with ring above font[217 - 32].resize( font[85 - 32].width(), font[85 - 32].height() + 4 ); font[217 - 32].reset(); fheroes2::Copy( font[85 - 32], 0, 0, font[217 - 32], 0, 4, font[85 - 32].width(), font[85 - 32].height() ); fheroes2::Copy( font[80], 5, 6, font[217 - 32], 5, 0, 4, 1 ); fheroes2::Copy( font[80], 5, 6, font[217 - 32], 5, 2, 4, 1 ); fheroes2::Copy( font[84], 1, 0, font[217 - 32], 5, 1, 1, 1 ); fheroes2::Copy( font[84], 1, 0, font[217 - 32], 8, 1, 1, 1 ); font[217 - 32].setPosition( font[85 - 32].x(), font[85 - 32].y() - 4 ); updateNormalFontLetterShadow( font[217 - 32] ); // Uppercase U with acute font[218 - 32].resize( font[85 - 32].width(), font[85 - 32].height() + 3 ); font[218 - 32].reset(); fheroes2::Copy( font[85 - 32], 0, 0, font[218 - 32], 0, 3, font[85 - 32].width(), font[85 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[218 - 32], 6, 0, 3, 2 ); font[218 - 32].setPosition( font[85 - 32].x(), font[85 - 32].y() - 3 ); updateNormalFontLetterShadow( font[218 - 32] ); // Uppercase U with double acute font[219 - 32].resize( font[85 - 32].width(), font[85 - 32].height() + 3 ); font[219 - 32].reset(); fheroes2::Copy( font[85 - 32], 0, 0, font[219 - 32], 0, 3, font[85 - 32].width(), font[85 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[219 - 32], 4, 0, 3, 2 ); fheroes2::Copy( font[140 - 32], 4, 0, font[219 - 32], 8, 0, 3, 2 ); font[219 - 32].setPosition( font[85 - 32].x(), font[85 - 32].y() - 3 ); updateNormalFontLetterShadow( font[219 - 32] ); // Uppercase U with diaeresis font[220 - 32].resize( font[85 - 32].width(), font[85 - 32].height() + 3 ); font[220 - 32].reset(); fheroes2::Copy( font[85 - 32], 0, 0, font[220 - 32], 0, 3, font[85 - 32].width(), font[85 - 32].height() ); fheroes2::Copy( font[175 - 32], 5, 0, font[220 - 32], 4, 0, 2, 2 ); fheroes2::Copy( font[175 - 32], 5, 0, font[220 - 32], 9, 0, 2, 2 ); font[220 - 32].setPosition( font[85 - 32].x(), font[85 - 32].y() - 3 ); updateNormalFontLetterShadow( font[220 - 32] ); // Uppercase Y with acute font[221 - 32].resize( font[89 - 32].width(), font[89 - 32].height() + 3 ); font[221 - 32].reset(); fheroes2::Copy( font[89 - 32], 0, 0, font[221 - 32], 0, 3, font[89 - 32].width(), font[89 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[221 - 32], 7, 0, 3, 2 ); font[221 - 32].setPosition( font[89 - 32].x(), font[89 - 32].y() - 3 ); updateNormalFontLetterShadow( font[221 - 32] ); // Lowercase r with acute font[224 - 32].resize( font[114 - 32].width(), font[114 - 32].height() + 3 ); font[224 - 32].reset(); fheroes2::Copy( font[114 - 32], 0, 0, font[224 - 32], 0, 3, font[114 - 32].width(), font[114 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[224 - 32], 4, 0, 3, 2 ); font[224 - 32].setPosition( font[114 - 32].x(), font[114 - 32].y() - 3 ); updateNormalFontLetterShadow( font[224 - 32] ); // Lowercase a with acute font[225 - 32].resize( font[97 - 32].width(), font[97 - 32].height() + 3 ); font[225 - 32].reset(); fheroes2::Copy( font[97 - 32], 0, 0, font[225 - 32], 0, 3, font[97 - 32].width(), font[97 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[225 - 32], 3, 0, 3, 2 ); font[225 - 32].setPosition( font[97 - 32].x(), font[97 - 32].y() - 3 ); updateNormalFontLetterShadow( font[225 - 32] ); // Lowercase a with circumflex font[226 - 32].resize( font[97 - 32].width(), font[97 - 32].height() + 3 ); font[226 - 32].reset(); fheroes2::Copy( font[97 - 32], 0, 0, font[226 - 32], 0, 3, font[97 - 32].width(), font[97 - 32].height() ); fheroes2::Copy( font[194 - 32], 7, 0, font[226 - 32], 3, 0, 3, 2 ); font[226 - 32].setPosition( font[97 - 32].x(), font[97 - 32].y() - 3 ); updateNormalFontLetterShadow( font[226 - 32] ); // Lowercase a with diaeresis font[228 - 32].resize( font[97 - 32].width(), font[97 - 32].height() + 3 ); font[228 - 32].reset(); fheroes2::Copy( font[97 - 32], 0, 0, font[228 - 32], 0, 3, font[97 - 32].width(), font[97 - 32].height() ); fheroes2::Copy( font[175 - 32], 5, 0, font[228 - 32], 2, 0, 2, 2 ); fheroes2::Copy( font[175 - 32], 5, 0, font[228 - 32], 6, 0, 2, 2 ); font[228 - 32].setPosition( font[97 - 32].x(), font[97 - 32].y() - 3 ); updateNormalFontLetterShadow( font[228 - 32] ); // Lowercase l with acute font[229 - 32].resize( font[108 - 32].width(), font[108 - 32].height() + 3 ); font[229 - 32].reset(); fheroes2::Copy( font[108 - 32], 0, 0, font[229 - 32], 0, 3, font[108 - 32].width(), font[108 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[229 - 32], 2, 0, 3, 2 ); font[229 - 32].setPosition( font[108 - 32].x(), font[108 - 32].y() - 3 ); updateNormalFontLetterShadow( font[229 - 32] ); // Lowercase c with acute font[230 - 32].resize( font[99 - 32].width(), font[99 - 32].height() + 3 ); font[230 - 32].reset(); fheroes2::Copy( font[99 - 32], 0, 0, font[230 - 32], 0, 3, font[99 - 32].width(), font[99 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[230 - 32], 4, 0, 3, 2 ); font[230 - 32].setPosition( font[99 - 32].x(), font[99 - 32].y() - 3 ); updateNormalFontLetterShadow( font[230 - 32] ); // Lowercase c with caron font[232 - 32].resize( font[99 - 32].width(), font[99 - 32].height() + 3 ); font[232 - 32].reset(); fheroes2::Copy( font[99 - 32], 0, 0, font[232 - 32], 0, 3, font[99 - 32].width(), font[99 - 32].height() ); fheroes2::Copy( font[138 - 32], 4, 0, font[232 - 32], 4, 0, 3, 2 ); font[232 - 32].setPosition( font[99 - 32].x(), font[99 - 32].y() - 3 ); updateNormalFontLetterShadow( font[232 - 32] ); // Lowercase e with acute font[233 - 32].resize( font[101 - 32].width(), font[101 - 32].height() + 3 ); font[233 - 32].reset(); fheroes2::Copy( font[101 - 32], 0, 0, font[233 - 32], 0, 3, font[101 - 32].width(), font[101 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[233 - 32], 4, 0, 3, 2 ); font[233 - 32].setPosition( font[101 - 32].x(), font[101 - 32].y() - 3 ); updateNormalFontLetterShadow( font[233 - 32] ); // Lowercase e with ogonek font[234 - 32].resize( font[101 - 32].width(), font[101 - 32].height() + 3 ); font[234 - 32].reset(); fheroes2::Copy( font[101 - 32], 0, 0, font[234 - 32], 0, 0, font[101 - 32].width(), font[101 - 32].height() ); font[234 - 32].setPosition( font[101 - 32].x(), font[101 - 32].y() ); updateNormalFontLetterShadow( font[234 - 32] ); // Shadows are already made for the ogonek. fheroes2::Copy( font[165 - 32], 10, 11, font[234 - 32], 3, 7, 5, 5 ); // Lowercase e with caron font[236 - 32].resize( font[101 - 32].width(), font[101 - 32].height() + 3 ); font[236 - 32].reset(); fheroes2::Copy( font[101 - 32], 0, 0, font[236 - 32], 0, 3, font[101 - 32].width(), font[101 - 32].height() ); fheroes2::Copy( font[138 - 32], 4, 0, font[236 - 32], 4, 0, 3, 2 ); font[236 - 32].setPosition( font[101 - 32].x(), font[101 - 32].y() - 3 ); updateNormalFontLetterShadow( font[236 - 32] ); // Lowercase i with acute font[237 - 32].resize( font[105 - 32].width(), font[105 - 32].height() ); font[237 - 32].reset(); fheroes2::Copy( font[105 - 32], 0, 3, font[237 - 32], 0, 3, font[105 - 32].width(), font[105 - 32].height() ); // Remove old dot shadow fheroes2::FillTransform( font[237 - 32], 0, 3, 1, 1, 1 ); // Add acute accent fheroes2::Copy( font[140 - 32], 4, 0, font[237 - 32], 2, 0, 3, 2 ); font[237 - 32].setPosition( font[105 - 32].x(), font[105 - 32].y() ); updateNormalFontLetterShadow( font[237 - 32] ); // Lowercase d with caron. Requires acute accent. font[239 - 32].resize( font[100 - 32].width() + 3, font[100 - 32].height() ); font[239 - 32].reset(); fheroes2::Copy( font[100 - 32], 0, 0, font[239 - 32], 0, 0, font[100 - 32].width(), font[100 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[239 - 32], 10, 0, 3, 2 ); font[239 - 32].setPosition( font[100 - 32].x(), font[100 - 32].y() ); updateNormalFontLetterShadow( font[239 - 32] ); // Lowercase n with acute font[241 - 32].resize( font[110 - 32].width(), font[110 - 32].height() + 3 ); font[241 - 32].reset(); fheroes2::Copy( font[110 - 32], 0, 0, font[241 - 32], 0, 3, font[110 - 32].width(), font[110 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[241 - 32], 4, 0, 3, 2 ); font[241 - 32].setPosition( font[110 - 32].x(), font[110 - 32].y() - 3 ); updateNormalFontLetterShadow( font[241 - 32] ); // Lowercase n with caron font[242 - 32].resize( font[110 - 32].width(), font[110 - 32].height() + 3 ); font[242 - 32].reset(); fheroes2::Copy( font[110 - 32], 0, 0, font[242 - 32], 0, 3, font[110 - 32].width(), font[110 - 32].height() ); fheroes2::Copy( font[138 - 32], 4, 0, font[242 - 32], 4, 0, 3, 2 ); font[242 - 32].setPosition( font[110 - 32].x(), font[110 - 32].y() - 3 ); updateNormalFontLetterShadow( font[242 - 32] ); // Lowercase o with acute font[243 - 32].resize( font[111 - 32].width(), font[111 - 32].height() + 3 ); font[243 - 32].reset(); fheroes2::Copy( font[111 - 32], 0, 0, font[243 - 32], 0, 3, font[111 - 32].width(), font[111 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[243 - 32], 4, 0, 3, 2 ); font[243 - 32].setPosition( font[111 - 32].x(), font[111 - 32].y() - 3 ); updateNormalFontLetterShadow( font[243 - 32] ); // Lowercase o with circumflex font[244 - 32].resize( font[111 - 32].width(), font[111 - 32].height() + 3 ); font[244 - 32].reset(); fheroes2::Copy( font[111 - 32], 0, 0, font[244 - 32], 0, 3, font[111 - 32].width(), font[111 - 32].height() ); fheroes2::Copy( font[65], 1, 1, font[244 - 32], 3, 1, 1, 1 ); fheroes2::Copy( font[65], 1, 1, font[244 - 32], 4, 0, 1, 1 ); fheroes2::Copy( font[65], 1, 1, font[244 - 32], 5, 0, 1, 1 ); fheroes2::Copy( font[65], 1, 1, font[244 - 32], 6, 1, 1, 1 ); fheroes2::Copy( font[65], 1, 0, font[244 - 32], 3, 0, 1, 1 ); fheroes2::Copy( font[65], 1, 0, font[244 - 32], 6, 0, 1, 1 ); font[244 - 32].setPosition( font[111 - 32].x(), font[111 - 32].y() - 3 ); updateNormalFontLetterShadow( font[244 - 32] ); // Lowercase o with double acute font[245 - 32].resize( font[111 - 32].width(), font[111 - 32].height() + 3 ); font[245 - 32].reset(); fheroes2::Copy( font[111 - 32], 0, 0, font[245 - 32], 0, 3, font[111 - 32].width(), font[111 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[245 - 32], 2, 0, 3, 2 ); fheroes2::Copy( font[140 - 32], 4, 0, font[245 - 32], 6, 0, 3, 2 ); font[245 - 32].setPosition( font[111 - 32].x(), font[111 - 32].y() - 3 ); updateNormalFontLetterShadow( font[245 - 32] ); // Lowercase o with diaeresis font[246 - 32].resize( font[111 - 32].width(), font[111 - 32].height() + 3 ); font[246 - 32].reset(); fheroes2::Copy( font[111 - 32], 0, 0, font[246 - 32], 0, 3, font[111 - 32].width(), font[111 - 32].height() ); fheroes2::Copy( font[175 - 32], 5, 0, font[246 - 32], 2, 0, 2, 2 ); fheroes2::Copy( font[175 - 32], 5, 0, font[246 - 32], 6, 0, 2, 2 ); font[246 - 32].setPosition( font[111 - 32].x(), font[111 - 32].y() - 3 ); updateNormalFontLetterShadow( font[246 - 32] ); // Lowercase r with caron font[248 - 32].resize( font[114 - 32].width(), font[114 - 32].height() + 3 ); font[248 - 32].reset(); fheroes2::Copy( font[114 - 32], 0, 0, font[248 - 32], 0, 3, font[114 - 32].width(), font[114 - 32].height() ); fheroes2::Copy( font[138 - 32], 4, 0, font[248 - 32], 4, 0, 3, 2 ); font[248 - 32].setPosition( font[114 - 32].x(), font[114 - 32].y() - 3 ); updateNormalFontLetterShadow( font[248 - 32] ); // Lowercase u with ring above font[249 - 32].resize( font[117 - 32].width(), font[117 - 32].height() + 4 ); font[249 - 32].reset(); fheroes2::Copy( font[117 - 32], 0, 0, font[249 - 32], 0, 4, font[117 - 32].width(), font[117 - 32].height() ); fheroes2::Copy( font[217 - 32], 5, 0, font[249 - 32], 3, 0, 1, 3 ); fheroes2::Copy( font[217 - 32], 8, 0, font[249 - 32], 7, 0, 1, 3 ); fheroes2::Copy( font[65], 2, 0, font[249 - 32], 4, 0, 3, 1 ); fheroes2::Copy( font[65], 2, 0, font[249 - 32], 4, 2, 3, 1 ); fheroes2::Copy( font[69], 3, 2, font[249 - 32], 4, 1, 3, 1 ); font[249 - 32].setPosition( font[117 - 32].x(), font[117 - 32].y() - 4 ); updateNormalFontLetterShadow( font[249 - 32] ); // Lowercase u with acute font[250 - 32].resize( font[117 - 32].width(), font[117 - 32].height() + 3 ); font[250 - 32].reset(); fheroes2::Copy( font[117 - 32], 0, 0, font[250 - 32], 0, 3, font[117 - 32].width(), font[117 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[250 - 32], 4, 0, 3, 2 ); font[250 - 32].setPosition( font[117 - 32].x(), font[117 - 32].y() - 3 ); updateNormalFontLetterShadow( font[250 - 32] ); // Lowercase u with double acute font[251 - 32].resize( font[117 - 32].width(), font[117 - 32].height() + 3 ); font[251 - 32].reset(); fheroes2::Copy( font[117 - 32], 0, 0, font[251 - 32], 0, 3, font[117 - 32].width(), font[117 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[251 - 32], 2, 0, 3, 2 ); fheroes2::Copy( font[140 - 32], 4, 0, font[251 - 32], 6, 0, 3, 2 ); font[251 - 32].setPosition( font[117 - 32].x(), font[117 - 32].y() - 3 ); updateNormalFontLetterShadow( font[251 - 32] ); // Lowercase u with diaeresis font[252 - 32].resize( font[117 - 32].width(), font[117 - 32].height() + 3 ); font[252 - 32].reset(); fheroes2::Copy( font[117 - 32], 0, 0, font[252 - 32], 0, 3, font[117 - 32].width(), font[117 - 32].height() ); fheroes2::Copy( font[175 - 32], 5, 0, font[252 - 32], 2, 0, 2, 2 ); fheroes2::Copy( font[175 - 32], 5, 0, font[252 - 32], 6, 0, 2, 2 ); font[252 - 32].setPosition( font[117 - 32].x(), font[117 - 32].y() - 3 ); updateNormalFontLetterShadow( font[252 - 32] ); // Lowercase y with acute font[253 - 32].resize( font[121 - 32].width(), font[121 - 32].height() + 3 ); font[253 - 32].reset(); fheroes2::Copy( font[121 - 32], 0, 0, font[253 - 32], 0, 3, font[121 - 32].width(), font[121 - 32].height() ); fheroes2::Copy( font[140 - 32], 4, 0, font[253 - 32], 5, 0, 3, 2 ); font[253 - 32].setPosition( font[121 - 32].x(), font[121 - 32].y() - 3 ); updateNormalFontLetterShadow( font[253 - 32] ); } // Small font. { std::vector & font = icnVsSprite[ICN::SMALFONT]; // Uppercase S with caron font[138 - 32].resize( font[83 - 32].width(), font[83 - 32].height() + 3 ); font[138 - 32].reset(); fheroes2::Copy( font[83 - 32], 0, 0, font[138 - 32], 0, 3, font[83 - 32].width(), font[83 - 32].height() ); fheroes2::Copy( font[116 - 32], 2, 5, font[138 - 32], 3, 0, 3, 2 ); font[138 - 32].setPosition( font[83 - 32].x(), font[83 - 32].y() - 3 ); updateSmallFontLetterShadow( font[138 - 32] ); // Uppercase S with acute font[140 - 32].resize( font[83 - 32].width(), font[83 - 32].height() + 3 ); font[140 - 32].reset(); fheroes2::Copy( font[83 - 32], 0, 0, font[140 - 32], 0, 3, font[83 - 32].width(), font[83 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[140 - 32], 4, 0, 2, 2 ); font[140 - 32].setPosition( font[83 - 32].x(), font[83 - 32].y() - 3 ); updateSmallFontLetterShadow( font[140 - 32] ); // Uppercase T with caron font[141 - 32].resize( font[84 - 32].width(), font[84 - 32].height() + 3 ); font[141 - 32].reset(); fheroes2::Copy( font[84 - 32], 0, 0, font[141 - 32], 0, 3, font[84 - 32].width(), font[84 - 32].height() ); fheroes2::Copy( font[116 - 32], 2, 5, font[141 - 32], 3, 0, 3, 2 ); font[141 - 32].setPosition( font[84 - 32].x(), font[84 - 32].y() - 3 ); updateSmallFontLetterShadow( font[141 - 32] ); // Uppercase Z with caron font[142 - 32].resize( font[90 - 32].width(), font[90 - 32].height() + 3 ); font[142 - 32].reset(); fheroes2::Copy( font[90 - 32], 0, 0, font[142 - 32], 0, 3, font[90 - 32].width(), font[90 - 32].height() ); fheroes2::Copy( font[116 - 32], 2, 5, font[142 - 32], 3, 0, 3, 2 ); font[142 - 32].setPosition( font[90 - 32].x(), font[90 - 32].y() - 3 ); updateSmallFontLetterShadow( font[142 - 32] ); // Uppercase Z with acute font[143 - 32].resize( font[90 - 32].width(), font[90 - 32].height() + 3 ); font[143 - 32].reset(); fheroes2::Copy( font[90 - 32], 0, 0, font[143 - 32], 0, 3, font[90 - 32].width(), font[90 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[143 - 32], 4, 0, 2, 2 ); font[143 - 32].setPosition( font[90 - 32].x(), font[90 - 32].y() - 3 ); updateSmallFontLetterShadow( font[143 - 32] ); // Lowercase s with caron font[154 - 32].resize( font[115 - 32].width(), font[115 - 32].height() + 3 ); font[154 - 32].reset(); fheroes2::Copy( font[115 - 32], 0, 0, font[154 - 32], 0, 3, font[115 - 32].width(), font[115 - 32].height() ); fheroes2::Copy( font[116 - 32], 2, 5, font[154 - 32], 2, 0, 3, 2 ); font[154 - 32].setPosition( font[115 - 32].x(), font[115 - 32].y() - 3 ); updateSmallFontLetterShadow( font[154 - 32] ); // Lowercase s with acute font[156 - 32].resize( font[115 - 32].width(), font[115 - 32].height() + 3 ); font[156 - 32].reset(); fheroes2::Copy( font[115 - 32], 0, 0, font[156 - 32], 0, 3, font[115 - 32].width(), font[115 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[156 - 32], 3, 0, 2, 2 ); font[156 - 32].setPosition( font[115 - 32].x(), font[115 - 32].y() - 3 ); updateSmallFontLetterShadow( font[156 - 32] ); // Lowercase t with caron font[157 - 32].resize( font[116 - 32].width(), font[116 - 32].height() + 1 ); font[157 - 32].reset(); fheroes2::Copy( font[116 - 32], 0, 0, font[157 - 32], 0, 1, font[116 - 32].width(), font[116 - 32].height() ); fheroes2::Copy( font[97 - 32], 2, 0, font[157 - 32], 4, 0, 1, 1 ); fheroes2::Copy( font[97 - 32], 2, 0, font[157 - 32], 4, 1, 1, 1 ); font[157 - 32].setPosition( font[116 - 32].x(), font[116 - 32].y() - 1 ); updateSmallFontLetterShadow( font[157 - 32] ); // Lowercase z with caron font[158 - 32].resize( font[122 - 32].width(), font[122 - 32].height() + 3 ); font[158 - 32].reset(); fheroes2::Copy( font[122 - 32], 0, 0, font[158 - 32], 0, 3, font[122 - 32].width(), font[122 - 32].height() ); fheroes2::Copy( font[116 - 32], 2, 5, font[158 - 32], 2, 0, 3, 2 ); font[158 - 32].setPosition( font[122 - 32].x(), font[122 - 32].y() - 3 ); updateSmallFontLetterShadow( font[158 - 32] ); // Lowercase z with acute font[159 - 32].resize( font[122 - 32].width(), font[122 - 32].height() + 3 ); font[159 - 32].reset(); fheroes2::Copy( font[122 - 32], 0, 0, font[159 - 32], 0, 3, font[122 - 32].width(), font[122 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[159 - 32], 3, 0, 2, 2 ); font[159 - 32].setPosition( font[122 - 32].x(), font[122 - 32].y() - 3 ); updateSmallFontLetterShadow( font[159 - 32] ); // Uppercase L with stroke font[163 - 32].resize( font[76 - 32].width(), font[76 - 32].height() ); font[163 - 32].reset(); fheroes2::Copy( font[76 - 32], 0, 0, font[163 - 32], 0, 0, font[76 - 32].width(), font[76 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[163 - 32], 4, 2, 2, 2 ); font[163 - 32].setPosition( font[76 - 32].x(), font[76 - 32].y() ); updateSmallFontLetterShadow( font[163 - 32] ); // Uppercase A with ogonek font[165 - 32].resize( font[65 - 32].width(), font[65 - 32].height() + 2 ); font[165 - 32].reset(); fheroes2::Copy( font[65 - 32], 0, 0, font[165 - 32], 0, 0, font[65 - 32].width(), font[65 - 32].height() ); fheroes2::Copy( font[65 - 32], 7, 5, font[165 - 32], 7, 7, 2, 2 ); font[165 - 32].setPosition( font[65 - 32].x(), font[65 - 32].y() ); updateSmallFontLetterShadow( font[165 - 32] ); // Uppercase Z with dot above font[175 - 32].resize( font[90 - 32].width(), font[90 - 32].height() + 2 ); font[175 - 32].reset(); fheroes2::Copy( font[90 - 32], 0, 0, font[175 - 32], 0, 2, font[90 - 32].width(), font[90 - 32].height() ); fheroes2::Copy( font[90 - 32], 2, 0, font[175 - 32], 3, 0, 2, 1 ); font[175 - 32].setPosition( font[90 - 32].x(), font[90 - 32].y() - 2 ); updateSmallFontLetterShadow( font[175 - 32] ); // Lowercase l with stroke font[179 - 32].resize( font[108 - 32].width(), font[108 - 32].height() ); font[179 - 32].reset(); fheroes2::Copy( font[108 - 32], 0, 0, font[179 - 32], 0, 0, font[108 - 32].width(), font[108 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 3, font[179 - 32], 3, 2, 1, 1 ); fheroes2::Copy( font[122 - 32], 2, 3, font[179 - 32], 1, 4, 1, 1 ); font[179 - 32].setPosition( font[108 - 32].x(), font[108 - 32].y() ); updateSmallFontLetterShadow( font[179 - 32] ); // Lowercase a with ogonek font[185 - 32].resize( font[97 - 32].width(), font[97 - 32].height() + 2 ); font[185 - 32].reset(); fheroes2::Copy( font[97 - 32], 0, 0, font[185 - 32], 0, 0, font[97 - 32].width(), font[97 - 32].height() ); fheroes2::Copy( font[65 - 32], 7, 5, font[185 - 32], 5, 5, 2, 2 ); font[185 - 32].setPosition( font[97 - 32].x(), font[97 - 32].y() ); updateSmallFontLetterShadow( font[185 - 32] ); // Uppercase L with caron (NOT an uppercase Y with diaeresis) font[188 - 32].resize( font[76 - 32].width(), font[76 - 32].height() ); font[188 - 32].reset(); fheroes2::Copy( font[76 - 32], 0, 0, font[188 - 32], 0, 0, font[76 - 32].width(), font[76 - 32].height() ); fheroes2::Copy( font[97 - 32], 2, 0, font[188 - 32], 6, 0, 1, 1 ); fheroes2::Copy( font[97 - 32], 2, 0, font[188 - 32], 7, 0, 1, 1 ); fheroes2::Copy( font[97 - 32], 2, 0, font[188 - 32], 6, 1, 1, 1 ); font[188 - 32].setPosition( font[76 - 32].x(), font[76 - 32].y() ); updateSmallFontLetterShadow( font[188 - 32] ); // Lowercase l with caron (NOT an uppercase L with caron) font[190 - 32].resize( font[108 - 32].width() + 1, font[108 - 32].height() ); font[190 - 32].reset(); fheroes2::Copy( font[108 - 32], 0, 0, font[190 - 32], 0, 0, font[108 - 32].width(), font[108 - 32].height() ); fheroes2::Copy( font[97 - 32], 2, 0, font[190 - 32], 4, 0, 1, 1 ); fheroes2::Copy( font[97 - 32], 2, 0, font[190 - 32], 4, 1, 1, 1 ); font[190 - 32].setPosition( font[108 - 32].x(), font[108 - 32].y() ); updateSmallFontLetterShadow( font[190 - 32] ); // Lowercase z with dot above font[191 - 32].resize( font[122 - 32].width(), font[122 - 32].height() + 2 ); font[191 - 32].reset(); fheroes2::Copy( font[122 - 32], 0, 0, font[191 - 32], 0, 2, font[122 - 32].width(), font[122 - 32].height() ); fheroes2::Copy( font[90 - 32], 2, 0, font[191 - 32], 3, 0, 2, 1 ); font[191 - 32].setPosition( font[122 - 32].x(), font[122 - 32].y() - 2 ); updateSmallFontLetterShadow( font[191 - 32] ); // Uppercase R with acute font[192 - 32].resize( font[82 - 32].width(), font[82 - 32].height() + 3 ); font[192 - 32].reset(); fheroes2::Copy( font[82 - 32], 0, 0, font[192 - 32], 0, 3, font[82 - 32].width(), font[82 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[192 - 32], 5, 0, 2, 2 ); font[192 - 32].setPosition( font[82 - 32].x(), font[82 - 32].y() - 3 ); updateSmallFontLetterShadow( font[192 - 32] ); // Uppercase A with acute font[193 - 32].resize( font[65 - 32].width(), font[65 - 32].height() + 3 ); font[193 - 32].reset(); fheroes2::Copy( font[65 - 32], 0, 0, font[193 - 32], 0, 3, font[65 - 32].width(), font[65 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[193 - 32], 5, 0, 2, 2 ); font[193 - 32].setPosition( font[65 - 32].x(), font[65 - 32].y() - 3 ); updateSmallFontLetterShadow( font[193 - 32] ); // Uppercase A with circumflex font[194 - 32].resize( font[33].width(), font[33].height() + 3 ); font[194 - 32].reset(); fheroes2::Copy( font[33], 0, 0, font[194 - 32], 0, 3, font[33].width(), font[33].height() ); fheroes2::Copy( font[33], 4, 0, font[194 - 32], 4, 1, 1, 1 ); fheroes2::Copy( font[33], 4, 0, font[194 - 32], 5, 0, 1, 1 ); fheroes2::Copy( font[33], 4, 0, font[194 - 32], 6, 1, 1, 1 ); font[194 - 32].setPosition( font[33].x(), font[33].y() - 3 ); updateSmallFontLetterShadow( font[194 - 32] ); // Uppercase A with diaeresis font[196 - 32].resize( font[65 - 32].width(), font[65 - 32].height() + 2 ); font[196 - 32].reset(); fheroes2::Copy( font[65 - 32], 0, 0, font[196 - 32], 0, 2, font[65 - 32].width(), font[65 - 32].height() ); fheroes2::Copy( font[97 - 32], 2, 0, font[196 - 32], 3, 0, 1, 1 ); fheroes2::Copy( font[97 - 32], 2, 0, font[196 - 32], 6, 0, 1, 1 ); font[196 - 32].setPosition( font[65 - 32].x(), font[65 - 32].y() - 2 ); updateSmallFontLetterShadow( font[196 - 32] ); // Uppercase L with acute font[197 - 32].resize( font[76 - 32].width(), font[76 - 32].height() + 3 ); font[197 - 32].reset(); fheroes2::Copy( font[76 - 32], 0, 0, font[197 - 32], 0, 3, font[76 - 32].width(), font[76 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[197 - 32], 5, 0, 2, 2 ); font[197 - 32].setPosition( font[76 - 32].x(), font[76 - 32].y() - 3 ); updateSmallFontLetterShadow( font[197 - 32] ); // Uppercase C with acute font[198 - 32].resize( font[67 - 32].width(), font[67 - 32].height() + 3 ); font[198 - 32].reset(); fheroes2::Copy( font[67 - 32], 0, 0, font[198 - 32], 0, 3, font[67 - 32].width(), font[67 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[198 - 32], 4, 0, 2, 2 ); font[198 - 32].setPosition( font[67 - 32].x(), font[67 - 32].y() - 3 ); updateSmallFontLetterShadow( font[198 - 32] ); // Uppercase C with caron font[200 - 32].resize( font[67 - 32].width(), font[67 - 32].height() + 3 ); font[200 - 32].reset(); fheroes2::Copy( font[67 - 32], 0, 0, font[200 - 32], 0, 3, font[67 - 32].width(), font[67 - 32].height() ); fheroes2::Copy( font[116 - 32], 2, 5, font[200 - 32], 3, 0, 3, 2 ); font[200 - 32].setPosition( font[67 - 32].x(), font[67 - 32].y() - 3 ); updateSmallFontLetterShadow( font[200 - 32] ); // Uppercase E with acute font[201 - 32].resize( font[69 - 32].width(), font[69 - 32].height() + 3 ); font[201 - 32].reset(); fheroes2::Copy( font[69 - 32], 0, 0, font[201 - 32], 0, 3, font[69 - 32].width(), font[69 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[201 - 32], 4, 0, 2, 2 ); font[201 - 32].setPosition( font[69 - 32].x(), font[69 - 32].y() - 3 ); updateSmallFontLetterShadow( font[201 - 32] ); // Uppercase E with ogonek font[202 - 32].resize( font[69 - 32].width(), font[69 - 32].height() + 2 ); font[202 - 32].reset(); fheroes2::Copy( font[69 - 32], 0, 0, font[202 - 32], 0, 0, font[69 - 32].width(), font[69 - 32].height() ); fheroes2::Copy( font[65 - 32], 7, 5, font[202 - 32], 5, 7, 2, 2 ); font[202 - 32].setPosition( font[69 - 32].x(), font[69 - 32].y() ); updateSmallFontLetterShadow( font[202 - 32] ); // Uppercase E with caron font[204 - 32].resize( font[69 - 32].width(), font[69 - 32].height() + 3 ); font[204 - 32].reset(); fheroes2::Copy( font[69 - 32], 0, 0, font[204 - 32], 0, 3, font[69 - 32].width(), font[69 - 32].height() ); fheroes2::Copy( font[116 - 32], 2, 5, font[204 - 32], 3, 0, 3, 2 ); font[204 - 32].setPosition( font[69 - 32].x(), font[69 - 32].y() - 3 ); updateSmallFontLetterShadow( font[204 - 32] ); // Uppercase I with acute font[205 - 32].resize( font[73 - 32].width(), font[73 - 32].height() + 3 ); font[205 - 32].reset(); fheroes2::Copy( font[73 - 32], 0, 0, font[205 - 32], 0, 3, font[73 - 32].width(), font[73 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[205 - 32], 2, 0, 2, 2 ); font[205 - 32].setPosition( font[73 - 32].x(), font[73 - 32].y() - 3 ); updateSmallFontLetterShadow( font[205 - 32] ); // Uppercase D with caron font[207 - 32].resize( font[68 - 32].width(), font[68 - 32].height() + 3 ); font[207 - 32].reset(); fheroes2::Copy( font[68 - 32], 0, 0, font[207 - 32], 0, 3, font[68 - 32].width(), font[68 - 32].height() ); fheroes2::Copy( font[116 - 32], 2, 5, font[207 - 32], 3, 0, 3, 2 ); font[207 - 32].setPosition( font[68 - 32].x(), font[68 - 32].y() - 3 ); updateSmallFontLetterShadow( font[207 - 32] ); // Uppercase N with acute font[209 - 32].resize( font[78 - 32].width(), font[78 - 32].height() + 3 ); font[209 - 32].reset(); fheroes2::Copy( font[78 - 32], 0, 0, font[209 - 32], 0, 3, font[78 - 32].width(), font[78 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[209 - 32], 5, 0, 2, 2 ); font[209 - 32].setPosition( font[78 - 32].x(), font[78 - 32].y() - 3 ); updateSmallFontLetterShadow( font[209 - 32] ); // Uppercase N with caron font[210 - 32].resize( font[78 - 32].width(), font[78 - 32].height() + 3 ); font[210 - 32].reset(); fheroes2::Copy( font[78 - 32], 0, 0, font[210 - 32], 0, 3, font[78 - 32].width(), font[78 - 32].height() ); fheroes2::Copy( font[116 - 32], 2, 5, font[210 - 32], 5, 0, 3, 2 ); font[210 - 32].setPosition( font[78 - 32].x(), font[78 - 32].y() - 3 ); updateSmallFontLetterShadow( font[210 - 32] ); // Uppercase O with acute font[211 - 32].resize( font[79 - 32].width(), font[79 - 32].height() + 3 ); font[211 - 32].reset(); fheroes2::Copy( font[79 - 32], 0, 0, font[211 - 32], 0, 3, font[79 - 32].width(), font[79 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[211 - 32], 4, 0, 2, 2 ); font[211 - 32].setPosition( font[79 - 32].x(), font[79 - 32].y() - 3 ); updateSmallFontLetterShadow( font[211 - 32] ); // Uppercase O with double circumflex font[212 - 32].resize( font[79 - 32].width(), font[79 - 32].height() + 3 ); font[212 - 32].reset(); fheroes2::Copy( font[79 - 32], 0, 0, font[212 - 32], 0, 3, font[79 - 32].width(), font[79 - 32].height() ); fheroes2::Copy( font[97 - 32], 2, 0, font[212 - 32], 2, 1, 1, 1 ); fheroes2::Copy( font[97 - 32], 2, 0, font[212 - 32], 3, 0, 1, 1 ); fheroes2::Copy( font[97 - 32], 2, 0, font[212 - 32], 4, 0, 1, 1 ); fheroes2::Copy( font[97 - 32], 2, 0, font[212 - 32], 5, 0, 1, 1 ); fheroes2::Copy( font[97 - 32], 2, 0, font[212 - 32], 6, 1, 1, 1 ); font[212 - 32].setPosition( font[79 - 32].x(), font[79 - 32].y() - 3 ); updateSmallFontLetterShadow( font[212 - 32] ); // Uppercase O with double acute font[213 - 32].resize( font[79 - 32].width(), font[79 - 32].height() + 3 ); font[213 - 32].reset(); fheroes2::Copy( font[79 - 32], 0, 0, font[213 - 32], 0, 3, font[79 - 32].width(), font[79 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[213 - 32], 2, 0, 2, 2 ); fheroes2::Copy( font[122 - 32], 2, 2, font[213 - 32], 5, 0, 2, 2 ); font[213 - 32].setPosition( font[79 - 32].x(), font[79 - 32].y() - 3 ); updateSmallFontLetterShadow( font[213 - 32] ); // Uppercase O with diaeresis font[214 - 32].resize( font[79 - 32].width(), font[79 - 32].height() + 2 ); font[214 - 32].reset(); fheroes2::Copy( font[79 - 32], 0, 0, font[214 - 32], 0, 2, font[79 - 32].width(), font[79 - 32].height() ); fheroes2::Copy( font[122 - 32], 3, 2, font[214 - 32], 2, 0, 1, 1 ); fheroes2::Copy( font[122 - 32], 3, 2, font[214 - 32], 6, 0, 1, 1 ); font[214 - 32].setPosition( font[79 - 32].x(), font[79 - 32].y() - 2 ); updateSmallFontLetterShadow( font[214 - 32] ); // Uppercase R with caron font[216 - 32].resize( font[82 - 32].width(), font[82 - 32].height() + 3 ); font[216 - 32].reset(); fheroes2::Copy( font[82 - 32], 0, 0, font[216 - 32], 0, 3, font[82 - 32].width(), font[82 - 32].height() ); fheroes2::Copy( font[116 - 32], 2, 5, font[216 - 32], 4, 0, 3, 2 ); font[216 - 32].setPosition( font[82 - 32].x(), font[82 - 32].y() - 3 ); updateSmallFontLetterShadow( font[216 - 32] ); // Uppercase U with ring above font[217 - 32].resize( font[85 - 32].width(), font[85 - 32].height() + 3 ); font[217 - 32].reset(); fheroes2::Copy( font[85 - 32], 0, 0, font[217 - 32], 0, 3, font[85 - 32].width(), font[85 - 32].height() ); fheroes2::Copy( font[116 - 32], 2, 5, font[217 - 32], 5, 0, 1, 1 ); fheroes2::Copy( font[116 - 32], 2, 5, font[217 - 32], 4, 1, 3, 2 ); font[217 - 32].setPosition( font[85 - 32].x(), font[85 - 32].y() - 3 ); updateSmallFontLetterShadow( font[217 - 32] ); // Uppercase U with acute font[218 - 32].resize( font[85 - 32].width(), font[85 - 32].height() + 3 ); font[218 - 32].reset(); fheroes2::Copy( font[85 - 32], 0, 0, font[218 - 32], 0, 3, font[85 - 32].width(), font[85 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[218 - 32], 5, 0, 2, 2 ); font[218 - 32].setPosition( font[85 - 32].x(), font[85 - 32].y() - 3 ); updateSmallFontLetterShadow( font[218 - 32] ); // Uppercase U with double acute font[219 - 32].resize( font[85 - 32].width(), font[85 - 32].height() + 3 ); font[219 - 32].reset(); fheroes2::Copy( font[85 - 32], 0, 0, font[219 - 32], 0, 3, font[85 - 32].width(), font[85 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[219 - 32], 3, 0, 2, 2 ); fheroes2::Copy( font[122 - 32], 2, 2, font[219 - 32], 7, 0, 2, 2 ); font[219 - 32].setPosition( font[85 - 32].x(), font[85 - 32].y() - 3 ); updateSmallFontLetterShadow( font[219 - 32] ); // Uppercase U with diaeresis font[220 - 32].resize( font[85 - 32].width(), font[85 - 32].height() + 2 ); font[220 - 32].reset(); fheroes2::Copy( font[85 - 32], 0, 0, font[220 - 32], 0, 2, font[85 - 32].width(), font[85 - 32].height() ); fheroes2::Copy( font[122 - 32], 3, 2, font[220 - 32], 3, 0, 1, 1 ); fheroes2::Copy( font[122 - 32], 3, 2, font[220 - 32], 7, 0, 1, 1 ); font[220 - 32].setPosition( font[85 - 32].x(), font[85 - 32].y() - 2 ); updateSmallFontLetterShadow( font[220 - 32] ); // Uppercase Y with acute font[221 - 32].resize( font[89 - 32].width(), font[89 - 32].height() + 3 ); font[221 - 32].reset(); fheroes2::Copy( font[89 - 32], 0, 0, font[221 - 32], 0, 3, font[89 - 32].width(), font[89 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[221 - 32], 5, 0, 2, 2 ); font[221 - 32].setPosition( font[89 - 32].x(), font[89 - 32].y() - 3 ); updateSmallFontLetterShadow( font[221 - 32] ); // Lowercase r with acute font[224 - 32].resize( font[114 - 32].width(), font[114 - 32].height() + 3 ); font[224 - 32].reset(); fheroes2::Copy( font[114 - 32], 0, 0, font[224 - 32], 0, 3, font[114 - 32].width(), font[114 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[224 - 32], 3, 0, 2, 2 ); font[224 - 32].setPosition( font[114 - 32].x(), font[114 - 32].y() - 3 ); updateSmallFontLetterShadow( font[224 - 32] ); // Lowercase a with acute font[225 - 32].resize( font[97 - 32].width(), font[97 - 32].height() + 3 ); font[225 - 32].reset(); fheroes2::Copy( font[97 - 32], 0, 0, font[225 - 32], 0, 3, font[97 - 32].width(), font[97 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[225 - 32], 3, 0, 2, 2 ); font[225 - 32].setPosition( font[97 - 32].x(), font[97 - 32].y() - 3 ); updateSmallFontLetterShadow( font[225 - 32] ); // Lowercase a with circumflex font[226 - 32].resize( font[97 - 32].width(), font[97 - 32].height() + 3 ); font[226 - 32].reset(); fheroes2::Copy( font[97 - 32], 0, 0, font[226 - 32], 0, 3, font[97 - 32].width(), font[97 - 32].height() ); fheroes2::Copy( font[97 - 32], 2, 0, font[226 - 32], 2, 1, 1, 1 ); fheroes2::Copy( font[97 - 32], 2, 0, font[226 - 32], 3, 0, 1, 1 ); fheroes2::Copy( font[97 - 32], 2, 0, font[226 - 32], 4, 0, 1, 1 ); fheroes2::Copy( font[97 - 32], 2, 0, font[226 - 32], 5, 1, 1, 1 ); font[226 - 32].setPosition( font[97 - 32].x(), font[97 - 32].y() - 3 ); updateSmallFontLetterShadow( font[226 - 32] ); // Lowercase a with diaeresis font[228 - 32].resize( font[97 - 32].width(), font[97 - 32].height() + 2 ); font[228 - 32].reset(); fheroes2::Copy( font[97 - 32], 0, 0, font[228 - 32], 0, 2, font[97 - 32].width(), font[97 - 32].height() ); fheroes2::Copy( font[97 - 32], 2, 0, font[228 - 32], 2, 0, 1, 1 ); fheroes2::Copy( font[97 - 32], 2, 0, font[228 - 32], 5, 0, 1, 1 ); font[228 - 32].setPosition( font[97 - 32].x(), font[97 - 32].y() - 2 ); updateSmallFontLetterShadow( font[228 - 32] ); // Lowercase l with acute font[229 - 32].resize( font[108 - 32].width(), font[108 - 32].height() + 3 ); font[229 - 32].reset(); fheroes2::Copy( font[108 - 32], 0, 0, font[229 - 32], 0, 3, font[108 - 32].width(), font[108 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[229 - 32], 2, 0, 2, 2 ); font[229 - 32].setPosition( font[108 - 32].x(), font[108 - 32].y() - 3 ); updateSmallFontLetterShadow( font[229 - 32] ); // Lowercase c with acute font[230 - 32].resize( font[99 - 32].width(), font[99 - 32].height() + 3 ); font[230 - 32].reset(); fheroes2::Copy( font[99 - 32], 0, 0, font[230 - 32], 0, 3, font[99 - 32].width(), font[99 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[230 - 32], 3, 0, 2, 2 ); font[230 - 32].setPosition( font[99 - 32].x(), font[99 - 32].y() - 3 ); updateSmallFontLetterShadow( font[230 - 32] ); // Lowercase c with caron font[232 - 32].resize( font[99 - 32].width(), font[99 - 32].height() + 3 ); font[232 - 32].reset(); fheroes2::Copy( font[99 - 32], 0, 0, font[232 - 32], 0, 3, font[99 - 32].width(), font[99 - 32].height() ); fheroes2::Copy( font[116 - 32], 2, 5, font[232 - 32], 2, 0, 3, 2 ); font[232 - 32].setPosition( font[99 - 32].x(), font[99 - 32].y() - 3 ); updateSmallFontLetterShadow( font[232 - 32] ); // Lowercase e with acute font[233 - 32].resize( font[101 - 32].width(), font[101 - 32].height() + 3 ); font[233 - 32].reset(); fheroes2::Copy( font[101 - 32], 0, 0, font[233 - 32], 0, 3, font[101 - 32].width(), font[101 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[233 - 32], 3, 0, 2, 2 ); font[233 - 32].setPosition( font[101 - 32].x(), font[101 - 32].y() - 3 ); updateSmallFontLetterShadow( font[233 - 32] ); // Lowercase e with ogonek font[234 - 32].resize( font[101 - 32].width(), font[101 - 32].height() + 2 ); font[234 - 32].reset(); fheroes2::Copy( font[101 - 32], 0, 0, font[234 - 32], 0, 0, font[101 - 32].width(), font[101 - 32].height() ); fheroes2::Copy( font[65 - 32], 7, 5, font[234 - 32], 3, 5, 2, 2 ); font[234 - 32].setPosition( font[101 - 32].x(), font[101 - 32].y() ); updateSmallFontLetterShadow( font[234 - 32] ); // Lowercase e with caron font[236 - 32].resize( font[101 - 32].width(), font[101 - 32].height() + 3 ); font[236 - 32].reset(); fheroes2::Copy( font[101 - 32], 0, 0, font[236 - 32], 0, 3, font[101 - 32].width(), font[101 - 32].height() ); fheroes2::Copy( font[116 - 32], 2, 5, font[236 - 32], 2, 0, 3, 2 ); font[236 - 32].setPosition( font[101 - 32].x(), font[101 - 32].y() - 3 ); updateSmallFontLetterShadow( font[236 - 32] ); // Lowercase i with acute font[237 - 32].resize( font[105 - 32].width(), font[105 - 32].height() + 1 ); font[237 - 32].reset(); fheroes2::Copy( font[105 - 32], 0, 0, font[237 - 32], 0, 1, font[105 - 32].width(), font[105 - 32].height() ); fheroes2::Copy( font[122 - 32], 3, 2, font[237 - 32], 3, 0, 1, 1 ); font[237 - 32].setPosition( font[105 - 32].x(), font[105 - 32].y() - 1 ); updateSmallFontLetterShadow( font[237 - 32] ); // Lowercase d with caron font[239 - 32].resize( font[100 - 32].width() + 2, font[100 - 32].height() ); font[239 - 32].reset(); fheroes2::Copy( font[100 - 32], 0, 0, font[239 - 32], 0, 0, font[100 - 32].width(), font[100 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[239 - 32], 7, 0, 2, 2 ); font[239 - 32].setPosition( font[100 - 32].x(), font[100 - 32].y() ); updateSmallFontLetterShadow( font[239 - 32] ); // Lowercase n with acute font[241 - 32].resize( font[110 - 32].width(), font[110 - 32].height() + 3 ); font[241 - 32].reset(); fheroes2::Copy( font[110 - 32], 0, 0, font[241 - 32], 0, 3, font[110 - 32].width(), font[110 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[241 - 32], 4, 0, 2, 2 ); font[241 - 32].setPosition( font[110 - 32].x(), font[110 - 32].y() - 3 ); updateSmallFontLetterShadow( font[241 - 32] ); // Lowercase n with caron font[242 - 32].resize( font[110 - 32].width(), font[110 - 32].height() + 3 ); font[242 - 32].reset(); fheroes2::Copy( font[110 - 32], 0, 0, font[242 - 32], 0, 3, font[110 - 32].width(), font[110 - 32].height() ); fheroes2::Copy( font[116 - 32], 2, 5, font[242 - 32], 3, 0, 3, 2 ); font[242 - 32].setPosition( font[110 - 32].x(), font[110 - 32].y() - 3 ); updateSmallFontLetterShadow( font[242 - 32] ); // Lowercase o with acute font[243 - 32].resize( font[111 - 32].width(), font[111 - 32].height() + 3 ); font[243 - 32].reset(); fheroes2::Copy( font[111 - 32], 0, 0, font[243 - 32], 0, 3, font[111 - 32].width(), font[111 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[243 - 32], 3, 0, 2, 2 ); font[243 - 32].setPosition( font[111 - 32].x(), font[111 - 32].y() - 3 ); updateSmallFontLetterShadow( font[243 - 32] ); // Lowercase o with circumflex font[244 - 32].resize( font[111 - 32].width() + 1, font[111 - 32].height() + 3 ); font[244 - 32].reset(); fheroes2::Copy( font[111 - 32], 0, 0, font[244 - 32], 0, 3, font[111 - 32].width(), font[111 - 32].height() ); fheroes2::Copy( font[97 - 32], 2, 0, font[244 - 32], 2, 1, 1, 1 ); fheroes2::Copy( font[97 - 32], 2, 0, font[244 - 32], 3, 0, 1, 1 ); fheroes2::Copy( font[97 - 32], 2, 0, font[244 - 32], 4, 1, 1, 1 ); font[244 - 32].setPosition( font[111 - 32].x(), font[111 - 32].y() - 3 ); updateSmallFontLetterShadow( font[244 - 32] ); // Lowercase o with double acute font[245 - 32].resize( font[111 - 32].width() + 1, font[111 - 32].height() + 3 ); font[245 - 32].reset(); fheroes2::Copy( font[111 - 32], 0, 0, font[245 - 32], 0, 3, font[111 - 32].width(), font[111 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[245 - 32], 2, 0, 2, 2 ); fheroes2::Copy( font[122 - 32], 2, 2, font[245 - 32], 5, 0, 2, 2 ); font[245 - 32].setPosition( font[111 - 32].x(), font[111 - 32].y() - 3 ); updateSmallFontLetterShadow( font[245 - 32] ); // Lowercase o with diaeresis font[246 - 32].resize( font[111 - 32].width(), font[111 - 32].height() + 2 ); font[246 - 32].reset(); fheroes2::Copy( font[111 - 32], 0, 0, font[246 - 32], 0, 2, font[111 - 32].width(), font[111 - 32].height() ); fheroes2::Copy( font[122 - 32], 3, 2, font[246 - 32], 2, 0, 1, 1 ); fheroes2::Copy( font[122 - 32], 3, 2, font[246 - 32], 4, 0, 1, 1 ); font[246 - 32].setPosition( font[111 - 32].x(), font[111 - 32].y() - 2 ); updateSmallFontLetterShadow( font[246 - 32] ); // Lowercase r with caron font[248 - 32].resize( font[114 - 32].width(), font[114 - 32].height() + 3 ); font[248 - 32].reset(); fheroes2::Copy( font[114 - 32], 0, 0, font[248 - 32], 0, 3, font[114 - 32].width(), font[114 - 32].height() ); fheroes2::Copy( font[116 - 32], 2, 5, font[248 - 32], 3, 0, 3, 2 ); font[248 - 32].setPosition( font[114 - 32].x(), font[114 - 32].y() - 3 ); updateSmallFontLetterShadow( font[248 - 32] ); // Lowercase u with ring above font[249 - 32].resize( font[117 - 32].width(), font[117 - 32].height() + 4 ); font[249 - 32].reset(); fheroes2::Copy( font[117 - 32], 0, 0, font[249 - 32], 0, 4, font[117 - 32].width(), font[117 - 32].height() ); fheroes2::Copy( font[116 - 32], 2, 5, font[249 - 32], 4, 0, 1, 1 ); fheroes2::Copy( font[116 - 32], 2, 5, font[249 - 32], 3, 1, 3, 2 ); font[249 - 32].setPosition( font[117 - 32].x(), font[117 - 32].y() - 4 ); updateSmallFontLetterShadow( font[249 - 32] ); // Lowercase u with acute font[250 - 32].resize( font[117 - 32].width(), font[117 - 32].height() + 3 ); font[250 - 32].reset(); fheroes2::Copy( font[117 - 32], 0, 0, font[250 - 32], 0, 3, font[117 - 32].width(), font[117 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[250 - 32], 3, 0, 2, 2 ); font[250 - 32].setPosition( font[117 - 32].x(), font[117 - 32].y() - 3 ); updateSmallFontLetterShadow( font[250 - 32] ); // Lowercase u with double acute font[251 - 32].resize( font[117 - 32].width(), font[117 - 32].height() + 3 ); font[251 - 32].reset(); fheroes2::Copy( font[117 - 32], 0, 0, font[251 - 32], 0, 3, font[117 - 32].width(), font[117 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[251 - 32], 2, 0, 2, 2 ); fheroes2::Copy( font[122 - 32], 2, 2, font[251 - 32], 5, 0, 2, 2 ); font[251 - 32].setPosition( font[117 - 32].x(), font[117 - 32].y() - 3 ); updateSmallFontLetterShadow( font[251 - 32] ); // Lowercase u with diaeresis font[252 - 32].resize( font[117 - 32].width(), font[117 - 32].height() + 2 ); font[252 - 32].reset(); fheroes2::Copy( font[117 - 32], 0, 0, font[252 - 32], 0, 2, font[117 - 32].width(), font[117 - 32].height() ); fheroes2::Copy( font[122 - 32], 3, 2, font[252 - 32], 2, 0, 1, 1 ); fheroes2::Copy( font[122 - 32], 3, 2, font[252 - 32], 6, 0, 1, 1 ); font[252 - 32].setPosition( font[117 - 32].x(), font[117 - 32].y() - 2 ); updateSmallFontLetterShadow( font[252 - 32] ); // Lowercase y with acute font[253 - 32].resize( font[121 - 32].width(), font[121 - 32].height() + 3 ); font[253 - 32].reset(); fheroes2::Copy( font[121 - 32], 0, 0, font[253 - 32], 0, 3, font[121 - 32].width(), font[121 - 32].height() ); fheroes2::Copy( font[122 - 32], 2, 2, font[253 - 32], 4, 0, 2, 2 ); font[253 - 32].setPosition( font[121 - 32].x(), font[121 - 32].y() - 3 ); updateSmallFontLetterShadow( font[253 - 32] ); } } void generateFrenchAlphabet( std::vector> & icnVsSprite ) { // Normal font. { std::vector & font = icnVsSprite[ICN::FONT]; // Lowercase o with circumflex font[35 - 32] = font[244 - 32]; // Lowercase u with circumflex // Lowercase u with circumflex font[36 - 32] = font[251 - 32]; // Lowercase u with grave accent font[38 - 32] = font[249 - 32]; // Lowercase a with circumflex font[42 - 32] = font[226 - 32]; // Lowercase i with diaeresis font[60 - 32] = font[239 - 32]; // Lowercase i with circumflex <- Confirmed used in the OG Succession wars. font[62 - 32] = font[238 - 32]; // Lowercase a with grave accent font[64 - 32] = font[224 - 32]; // Lowercase c with cedilla font[94 - 32] = font[231 - 32]; // Lowercase e with grave accent font[96 - 32] = font[232 - 32]; // Lowercase i with diaeresis font[123 - 32] = font[239 - 32]; // Lowercase e with circumflex font[124 - 32] = font[234 - 32]; // Lowercase i with circumflex font[125 - 32] = font[239 - 32]; // Lowercase e with acute font[126 - 32] = font[233 - 32]; // Lowercase i with circumflex font[127 - 32] = font[239 - 32]; } // Small font. { std::vector & font = icnVsSprite[ICN::SMALFONT]; // Lowercase o with circumflex font[35 - 32] = font[244 - 32]; // Lowercase u with circumflex font[36 - 32] = font[251 - 32]; // Lowercase u with grave accent font[38 - 32] = font[249 - 32]; // Lowercase a with circumflex font[42 - 32] = font[226 - 32]; // Lowercase i with diaeresis font[60 - 32] = font[239 - 32]; // Lowercase i with circumflex font[62 - 32] = font[238 - 32]; // Lowercase a with grave accent font[64 - 32] = font[224 - 32]; // Lowercase c with cedilla font[94 - 32] = font[231 - 32]; // Lowercase e with grave accent font[96 - 32] = font[232 - 32]; // Lowercase i with diaeresis font[123 - 32] = font[239 - 32]; // Lowercase e with circumflex font[124 - 32] = font[234 - 32]; // Lowercase i with circumflex font[125 - 32] = font[239 - 32]; // Lowercase e with acute font[126 - 32] = font[233 - 32]; // Lowercase i with circumflex font[127 - 32] = font[239 - 32]; } } // CP-1251 supports Russian, Ukrainian, Belarussian, Bulgarian, Serbian Cyrillic, Macedonian and English. void generateCP1251Alphabet( std::vector> & icnVsSprite ) { // TODO: add support for Serbian Cyrillic and Macedonian languages by generating missing letters. // Resize fonts. for ( const int icnId : { ICN::FONT, ICN::SMALFONT } ) { std::vector & original = icnVsSprite[icnId]; original.resize( baseFontSize ); const fheroes2::Sprite firstSprite{ original[0] }; original.insert( original.end(), 128, firstSprite ); } // Normal font. { std::vector & font = icnVsSprite[ICN::FONT]; size_t offset = 0; // E with 2 dots on top. font[168 - 32].resize( font[37 + offset].width(), font[37 + offset].height() + 3 ); font[168 - 32].reset(); fheroes2::Copy( font[37 + offset], 0, 0, font[168 - 32], 0, 3, font[37 + offset].width(), font[37 + offset].height() ); fheroes2::Copy( font[168 - 32], 5, 5, font[168 - 32], 4, 0, 1, 1 ); fheroes2::Copy( font[168 - 32], 5, 5, font[168 - 32], 7, 0, 1, 1 ); fheroes2::Copy( font[168 - 32], 4, 5, font[168 - 32], 4, 1, 1, 1 ); fheroes2::Copy( font[168 - 32], 4, 5, font[168 - 32], 7, 1, 1, 1 ); font[168 - 32].setPosition( font[37 + offset].x(), font[37 + offset].y() - 3 ); updateNormalFontLetterShadow( font[168 - 32] ); font[161 - 32].resize( font[57 + offset].width(), font[57 + offset].height() + 3 ); font[161 - 32].reset(); fheroes2::Copy( font[57 + offset], 0, 0, font[161 - 32], 0, 3, font[57 + offset].width(), font[57 + offset].height() ); fheroes2::Copy( font[168 - 32], 3, 0, font[161 - 32], 7, 0, 2, 3 ); font[161 - 32].setPosition( font[57 + offset].x(), font[57 + offset].y() - 3 ); updateNormalFontLetterShadow( font[161 - 32] ); font[162 - 32].resize( font[89 + offset].width(), font[89 + offset].height() + 3 ); font[162 - 32].reset(); fheroes2::Copy( font[89 + offset], 0, 0, font[162 - 32], 0, 3, font[89 + offset].width(), font[89 + offset].height() ); fheroes2::Copy( font[89 + offset], 4, 1, font[162 - 32], 6, 0, 1, 1 ); fheroes2::Copy( font[89 + offset], 4, 0, font[162 - 32], 6, 1, 1, 1 ); font[162 - 32].setPosition( font[89 + offset].x(), font[89 + offset].y() - 3 ); updateNormalFontLetterShadow( font[162 - 32] ); // C with a horizontal line in the middle. font[170 - 32] = font[67 - 32]; fheroes2::Copy( font[170 - 32], 7, 0, font[170 - 32], 6, 5, 4, 2 ); updateNormalFontLetterShadow( font[170 - 32] ); font[186 - 32] = font[99 - 32]; fheroes2::Copy( font[186 - 32], 4, 0, font[186 - 32], 3, 3, 3, 1 ); updateNormalFontLetterShadow( font[186 - 32] ); // I and i with 2 dots. font[175 - 32].resize( font[73 - 32].width(), font[73 - 32].height() + 3 ); font[175 - 32].reset(); fheroes2::Copy( font[73 - 32], 0, 0, font[175 - 32], 0, 3, font[73 - 32].width(), font[73 - 32].height() ); fheroes2::Copy( font[168 - 32], 3, 0, font[175 - 32], 2, 0, 5, 3 ); font[175 - 32].setPosition( font[73 - 32].x(), font[73 - 32].y() - 3 ); font[191 - 32] = font[105 - 32]; fheroes2::FillTransform( font[191 - 32], 2, 0, 1, 3, 1 ); // J and j. font[163 - 32] = font[74 - 32]; font[188 - 32] = font[106 - 32]; // S and s. font[189 - 32] = font[83 - 32]; font[190 - 32] = font[115 - 32]; // I and i. font[178 - 32] = font[73 - 32]; font[179 - 32] = font[105 - 32]; // A font[192 - 32] = font[33]; font[193 - 32] = font[34 + offset]; fheroes2::FillTransform( font[193 - 32], 9, 4, 2, 1, 1 ); fheroes2::Copy( font[38], 6, 0, font[193 - 32], 6, 0, 5, 4 ); fheroes2::Copy( font[193 - 32], 9, 5, font[193 - 32], 8, 4, 1, 1 ); updateNormalFontLetterShadow( font[193 - 32] ); font[194 - 32] = font[34 + offset]; font[195 - 32] = font[38]; fheroes2::FillTransform( font[195 - 32], 6, 4, 3, 4, 1 ); // The same letter as above but with a vertical line at the top. font[165 - 32].resize( font[195 - 32].width(), font[195 - 32].height() + 1 ); font[165 - 32].reset(); fheroes2::Copy( font[195 - 32], 0, 0, font[165 - 32], 0, 1, font[195 - 32].width(), font[195 - 32].height() ); fheroes2::Copy( font[195 - 32], 9, 1, font[165 - 32], 9, 0, 2, 1 ); fheroes2::Copy( font[195 - 32], 9, 1, font[165 - 32], 10, 1, 1, 1 ); fheroes2::Copy( font[195 - 32], 10, 0, font[165 - 32], 10, 2, 1, 1 ); fheroes2::Copy( font[195 - 32], 8, 1, font[165 - 32], 9, 2, 1, 1 ); font[165 - 32].setPosition( font[195 - 32].x(), font[195 - 32].y() - 1 ); font[196 - 32] = font[36 + offset]; font[197 - 32] = font[37 + offset]; // x with | in the middle. font[198 - 32].resize( font[56].width() + 1, font[56].height() ); font[198 - 32].reset(); fheroes2::Copy( font[56], 1, 0, font[198 - 32], 1, 0, 8, 11 ); fheroes2::Copy( font[56], 9, 0, font[198 - 32], 10, 0, 6, 11 ); fheroes2::Fill( font[198 - 32], 9, 1, 1, 9, font[198 - 32].image()[1 + font[198 - 32].width()] ); font[198 - 32].setPosition( font[56].x(), font[56].y() ); updateNormalFontLetterShadow( font[198 - 32] ); font[199 - 32].resize( font[19].width() + 1, font[19].height() ); font[199 - 32].reset(); fheroes2::Copy( font[19], 1, 0, font[199 - 32], 1, 0, 5, 3 ); fheroes2::Copy( font[19], 5, 0, font[199 - 32], 6, 0, 3, 4 ); fheroes2::Copy( font[19], 3, 5, font[199 - 32], 4, 4, 5, 4 ); fheroes2::Copy( font[19], 1, 8, font[199 - 32], 1, 8, 5, 3 ); fheroes2::Copy( font[19], 5, 8, font[199 - 32], 6, 8, 3, 3 ); fheroes2::FillTransform( font[199 - 32], 2, 6, 5, 3, 1 ); font[199 - 32].setPosition( font[19].x(), font[19].y() ); updateNormalFontLetterShadow( font[199 - 32] ); // Reverted N. font[200 - 32] = font[46]; fheroes2::FillTransform( font[200 - 32], 6, 1, 5, 11, 1 ); fheroes2::Copy( font[46], 6, 2, font[200 - 32], 6, 6, 1, 3 ); fheroes2::Copy( font[46], 7, 3, font[200 - 32], 7, 5, 1, 3 ); fheroes2::Copy( font[46], 8, 4, font[200 - 32], 8, 4, 1, 3 ); fheroes2::Copy( font[46], 8, 4, font[200 - 32], 9, 3, 1, 3 ); fheroes2::Copy( font[46], 8, 4, font[200 - 32], 10, 2, 1, 3 ); fheroes2::Copy( font[46], 8, 4, font[200 - 32], 11, 1, 1, 3 ); fheroes2::Copy( font[46], 11, 7, font[200 - 32], 11, 8, 1, 1 ); fheroes2::Copy( font[46], 13, 9, font[200 - 32], 11, 9, 1, 1 ); updateNormalFontLetterShadow( font[200 - 32] ); font[201 - 32].resize( font[200 - 32].width(), font[200 - 32].height() + 3 ); font[201 - 32].reset(); fheroes2::Copy( font[200 - 32], 0, 0, font[201 - 32], 0, 3, font[200 - 32].width(), font[200 - 32].height() ); font[201 - 32].setPosition( font[200 - 32].x(), font[200 - 32].y() - 3 ); fheroes2::Copy( font[201 - 32], 12, 4, font[201 - 32], 8, 0, 1, 1 ); fheroes2::Copy( font[201 - 32], 11, 10, font[201 - 32], 8, 1, 1, 1 ); updateNormalFontLetterShadow( font[201 - 32] ); font[202 - 32] = font[43 + offset]; font[204 - 32] = font[45 + offset]; font[205 - 32] = font[40 + offset]; font[206 - 32] = font[47 + offset]; font[207 - 32] = font[195 - 32]; fheroes2::Copy( font[207 - 32], 4, 1, font[207 - 32], 8, 1, 2, 9 ); fheroes2::Copy( font[207 - 32], 4, 9, font[207 - 32], 8, 10, 2, 1 ); fheroes2::Copy( font[207 - 32], 6, 0, font[207 - 32], 10, 0, 1, 2 ); updateNormalFontLetterShadow( font[207 - 32] ); font[203 - 32].resize( font[207 - 32].width() - 1, font[207 - 32].height() ); font[203 - 32].reset(); fheroes2::Copy( font[207 - 32], 0, 0, font[203 - 32], 0, 0, font[207 - 32].width() - 1, font[207 - 32].height() ); fheroes2::FillTransform( font[203 - 32], 0, 0, 4, 6, 1 ); fheroes2::FillTransform( font[203 - 32], 4, 0, 3, 2, 1 ); fheroes2::Copy( font[203 - 32], 4, 2, font[203 - 32], 5, 1, 2, 1 ); fheroes2::Copy( font[203 - 32], 1, 10, font[203 - 32], 5, 0, 2, 1 ); font[203 - 32].setPosition( font[207 - 32].x(), font[207 - 32].y() ); updateNormalFontLetterShadow( font[203 - 32] ); font[208 - 32] = font[48 + offset]; font[209 - 32] = font[35 + offset]; font[210 - 32].resize( font[207 - 32].width() + 4, font[207 - 32].height() ); font[210 - 32].reset(); fheroes2::Copy( font[207 - 32], 0, 0, font[210 - 32], 0, 0, font[207 - 32].width(), font[207 - 32].height() ); fheroes2::Copy( font[210 - 32], 7, 0, font[210 - 32], 11, 0, 4, font[207 - 32].height() ); font[210 - 32].setPosition( font[207 - 32].x(), font[207 - 32].y() ); font[211 - 32] = font[57 + offset]; font[212 - 32].resize( font[48].width() + 1, font[48].height() ); font[212 - 32].reset(); fheroes2::Copy( font[48], 0, 0, font[212 - 32], 1, 0, font[48].width(), font[48].height() ); fheroes2::Flip( font[48], 6, 0, font[212 - 32], 1, 0, 5, 6, true, false ); font[212 - 32].setPosition( font[48].x(), font[48].y() ); updateNormalFontLetterShadow( font[212 - 32] ); font[213 - 32] = font[56 + offset]; font[214 - 32].resize( font[53].width() + 2, font[53].height() + 1 ); font[214 - 32].reset(); fheroes2::Copy( font[53], 0, 0, font[214 - 32], 0, 0, font[52].width(), font[52].height() ); fheroes2::Copy( font[214 - 32], 9, 1, font[214 - 32], 11, 9, 1, 1 ); fheroes2::Copy( font[214 - 32], 9, 1, font[214 - 32], 12, 8, 1, 1 ); fheroes2::Copy( font[214 - 32], 9, 1, font[214 - 32], 12, 10, 1, 2 ); fheroes2::Copy( font[214 - 32], 10, 1, font[214 - 32], 12, 9, 1, 1 ); fheroes2::Copy( font[214 - 32], 10, 1, font[214 - 32], 13, 8, 1, 4 ); font[214 - 32].setPosition( font[53].x(), font[53].y() ); updateNormalFontLetterShadow( font[214 - 32] ); font[216 - 32].resize( font[53].width() + 2, font[53].height() ); font[216 - 32].reset(); fheroes2::Copy( font[53], 0, 0, font[216 - 32], 0, 0, 6, 11 ); fheroes2::Copy( font[53], 8, 0, font[216 - 32], 7, 0, 3, 11 ); fheroes2::Copy( font[53], 8, 0, font[216 - 32], 11, 0, 3, 11 ); fheroes2::Copy( font[204 - 32], 10, 0, font[216 - 32], 6, 5, 3, 5 ); fheroes2::Copy( font[204 - 32], 10, 0, font[216 - 32], 10, 5, 3, 5 ); fheroes2::FillTransform( font[216 - 32], 7, 10, 1, 1, 1 ); fheroes2::FillTransform( font[216 - 32], 11, 10, 1, 1, 1 ); font[216 - 32].setPosition( font[53].x(), font[53].y() ); updateNormalFontLetterShadow( font[216 - 32] ); font[215 - 32] = font[53]; fheroes2::FillTransform( font[215 - 32], 3, 6, 6, 7, 1 ); fheroes2::Copy( font[216 - 32], 4, 5, font[215 - 32], 4, 3, 4, 6 ); fheroes2::Copy( font[215 - 32], 6, 5, font[215 - 32], 8, 3, 1, 3 ); fheroes2::Copy( font[215 - 32], 7, 4, font[215 - 32], 9, 2, 1, 2 ); fheroes2::Copy( font[215 - 32], 9, 8, font[215 - 32], 9, 9, 1, 1 ); updateNormalFontLetterShadow( font[215 - 32] ); font[217 - 32].resize( font[216 - 32].width() + 2, font[216 - 32].height() + 1 ); font[217 - 32].reset(); fheroes2::Copy( font[216 - 32], 0, 0, font[217 - 32], 0, 0, font[216 - 32].width(), font[216 - 32].height() ); fheroes2::Copy( font[214 - 32], 11, 8, font[217 - 32], 14, 8, 3, 4 ); font[217 - 32].setPosition( font[216 - 32].x(), font[216 - 32].y() ); updateNormalFontLetterShadow( font[217 - 32] ); font[218 - 32].resize( font[193 - 32].width() + 1, font[193 - 32].height() ); font[218 - 32].reset(); fheroes2::Copy( font[193 - 32], 0, 0, font[218 - 32], 1, 0, font[193 - 32].width(), font[193 - 32].height() ); fheroes2::Copy( font[193 - 32], 1, 0, font[218 - 32], 1, 0, 3, 4 ); fheroes2::FillTransform( font[218 - 32], 7, 0, 5, 4, 1 ); font[218 - 32].setPosition( font[193 - 32].x(), font[193 - 32].y() ); updateNormalFontLetterShadow( font[218 - 32] ); font[220 - 32] = font[193 - 32]; fheroes2::FillTransform( font[220 - 32], 0, 0, 4, 6, 1 ); fheroes2::FillTransform( font[220 - 32], 6, 0, 5, 4, 1 ); fheroes2::Copy( font[53], 8, 0, font[220 - 32], 3, 0, 3, 1 ); updateNormalFontLetterShadow( font[220 - 32] ); font[219 - 32].resize( font[220 - 32].width() + 3, font[220 - 32].height() ); font[219 - 32].reset(); fheroes2::Copy( font[220 - 32], 0, 0, font[219 - 32], 0, 0, font[220 - 32].width(), font[220 - 32].height() ); fheroes2::Copy( font[219 - 32], 3, 0, font[219 - 32], 11, 0, 3, 9 ); fheroes2::Copy( font[207 - 32], 8, 9, font[219 - 32], 12, 9, 2, 2 ); font[219 - 32].setPosition( font[220 - 32].x(), font[220 - 32].y() ); updateNormalFontLetterShadow( font[219 - 32] ); font[221 - 32].resize( font[47].width() - 3, font[47].height() ); font[221 - 32].reset(); fheroes2::Copy( font[47], 4, 0, font[221 - 32], 1, 0, 9, 11 ); fheroes2::FillTransform( font[221 - 32], 0, 3, 3, 5, 1 ); fheroes2::Copy( font[221 - 32], 3, 0, font[221 - 32], 4, 5, 5, 1 ); font[221 - 32].setPosition( font[47].x(), font[47].y() ); updateNormalFontLetterShadow( font[221 - 32] ); font[222 - 32].resize( font[47].width() + 1, font[47].height() ); font[222 - 32].reset(); fheroes2::Copy( font[193 - 32], 0, 0, font[222 - 32], 0, 0, 6, 13 ); fheroes2::Copy( font[47], 4, 1, font[222 - 32], 7, 1, 4, 8 ); fheroes2::Copy( font[47], 10, 1, font[222 - 32], 11, 1, 3, 8 ); fheroes2::Copy( font[47], 5, 0, font[222 - 32], 8, 0, 3, 1 ); fheroes2::Copy( font[47], 10, 0, font[222 - 32], 11, 0, 2, 1 ); fheroes2::Copy( font[47], 4, 9, font[222 - 32], 7, 9, 4, 2 ); fheroes2::Copy( font[47], 10, 9, font[222 - 32], 11, 9, 3, 2 ); fheroes2::Copy( font[222 - 32], 2, 0, font[222 - 32], 6, 5, 2, 1 ); font[222 - 32].setPosition( font[193 - 32].x(), font[193 - 32].y() ); updateNormalFontLetterShadow( font[222 - 32] ); font[223 - 32].resize( font[203 - 32].width() - 1, font[203 - 32].height() ); font[223 - 32].reset(); fheroes2::Copy( font[33], 0, 5, font[223 - 32], 0, 5, 7, 6 ); fheroes2::Copy( font[212 - 32], 0, 0, font[223 - 32], 1, 0, 7, 6 ); fheroes2::Copy( font[203 - 32], 8, 0, font[223 - 32], 7, 0, 2, 11 ); fheroes2::Copy( font[223 - 32], 6, 5, font[223 - 32], 7, 5, 1, 1 ); font[223 - 32].setPosition( font[203 - 32].x(), font[203 - 32].y() ); updateNormalFontLetterShadow( font[223 - 32] ); offset = 32; // e with 2 dots on top. font[184 - 32].resize( font[69].width(), font[69].height() + 3 ); font[184 - 32].reset(); fheroes2::Copy( font[69], 0, 0, font[184 - 32], 0, 3, font[69].width(), font[69].height() ); fheroes2::Copy( font[168 - 32], 3, 0, font[184 - 32], 3, 0, 2, 4 ); fheroes2::Copy( font[168 - 32], 3, 0, font[184 - 32], 5, 0, 2, 4 ); font[184 - 32].setPosition( font[69].x(), font[69].y() - 3 ); // Not shadow needs to be updated here. font[224 - 32] = font[33 + offset]; font[225 - 32].resize( font[69].width(), font[69].height() + 3 ); font[225 - 32].reset(); fheroes2::Copy( font[69], 1, 5, font[225 - 32], 1, 8, 8, 2 ); fheroes2::Copy( font[69], 1, 0, font[225 - 32], 1, 6, 8, 2 ); fheroes2::Copy( font[67], 1, 0, font[225 - 32], 1, 0, 8, 2 ); fheroes2::Copy( font[45], 7, 3, font[225 - 32], 1, 2, 3, 1 ); fheroes2::Copy( font[45], 7, 3, font[225 - 32], 2, 3, 3, 1 ); fheroes2::Copy( font[45], 7, 3, font[225 - 32], 3, 4, 3, 1 ); fheroes2::Copy( font[45], 8, 3, font[225 - 32], 6, 5, 2, 1 ); fheroes2::Copy( font[45], 7, 3, font[225 - 32], 4, 5, 2, 1 ); font[225 - 32].setPosition( font[69].x(), font[69].y() - 3 ); updateNormalFontLetterShadow( font[225 - 32] ); font[227 - 32] = font[82]; fheroes2::Copy( font[227 - 32], 1, 0, font[227 - 32], 3, 0, 2, 1 ); fheroes2::Copy( font[227 - 32], 4, 2, font[227 - 32], 4, 1, 1, 1 ); fheroes2::SetTransformPixel( font[227 - 32], 4, 2, 1 ); updateNormalFontLetterShadow( font[227 - 32] ); // The same letter as above but with a vertical line at the top. font[180 - 32].resize( font[227 - 32].width(), font[227 - 32].height() + 1 ); font[180 - 32].reset(); fheroes2::Copy( font[227 - 32], 0, 0, font[180 - 32], 0, 1, font[227 - 32].width(), font[227 - 32].height() ); fheroes2::Copy( font[227 - 32], 6, 1, font[180 - 32], 6, 0, 3, 1 ); fheroes2::FillTransform( font[180 - 32], 7, 2, 2, 1, 1 ); fheroes2::FillTransform( font[180 - 32], 6, 4, 2, 1, 1 ); font[180 - 32].setPosition( font[227 - 32].x(), font[227 - 32].y() - 1 ); font[228 - 32] = font[71]; font[229 - 32] = font[37 + offset]; // x with | in the middle. font[230 - 32].resize( font[88].width() + 2, font[88].height() ); font[230 - 32].reset(); fheroes2::Copy( font[88], 0, 0, font[230 - 32], 0, 0, 6, 7 ); fheroes2::Copy( font[88], 5, 0, font[230 - 32], 7, 0, 5, 7 ); fheroes2::Fill( font[230 - 32], 6, 1, 1, 5, font[230 - 32].image()[3 + font[230 - 32].width()] ); font[230 - 32].setPosition( font[88].x(), font[88].y() ); updateNormalFontLetterShadow( font[230 - 32] ); // letter 3 (z) font[231 - 32].resize( font[19].width(), font[19].height() - 4 ); font[231 - 32].reset(); fheroes2::Copy( font[19], 0, 0, font[231 - 32], 0, 0, font[19].width(), 3 ); fheroes2::Copy( font[19], 0, 5, font[231 - 32], 0, 3, font[19].width(), 1 ); fheroes2::Copy( font[19], 0, 8, font[231 - 32], 0, 4, font[19].width(), 4 ); fheroes2::FillTransform( font[231 - 32], 0, 2, 3, 3, 1 ); font[231 - 32].setPosition( font[19].x(), font[19].y() + 4 ); updateNormalFontLetterShadow( font[231 - 32] ); // letter B (v) font[226 - 32].resize( font[231 - 32].width() + 1, font[231 - 32].height() ); font[226 - 32].reset(); fheroes2::Copy( font[231 - 32], 0, 0, font[226 - 32], 1, 0, font[231 - 32].width(), font[231 - 32].height() ); fheroes2::Copy( font[77], 1, 0, font[226 - 32], 1, 0, 3, 7 ); fheroes2::Copy( font[226 - 32], 7, 1, font[226 - 32], 3, 0, 1, 1 ); fheroes2::Copy( font[226 - 32], 7, 1, font[226 - 32], 3, 6, 1, 1 ); fheroes2::Copy( font[226 - 32], 3, 4, font[226 - 32], 3, 5, 1, 1 ); font[226 - 32].setPosition( font[231 - 32].x(), font[231 - 32].y() ); updateNormalFontLetterShadow( font[226 - 32] ); font[232 - 32] = font[85]; font[233 - 32].resize( font[232 - 32].width(), font[232 - 32].height() + 3 ); font[233 - 32].reset(); fheroes2::Copy( font[232 - 32], 0, 0, font[233 - 32], 0, 3, font[232 - 32].width(), font[232 - 32].height() ); fheroes2::Copy( font[233 - 32], 8, 3, font[233 - 32], 5, 0, 1, 1 ); fheroes2::Copy( font[233 - 32], 7, 3, font[233 - 32], 5, 1, 1, 1 ); font[233 - 32].setPosition( font[232 - 32].x(), font[232 - 32].y() - 3 ); updateNormalFontLetterShadow( font[233 - 32] ); // Shorter k. font[234 - 32].resize( font[75].width() - 1, font[75].height() - 4 ); font[234 - 32].reset(); fheroes2::Copy( font[75], 0, 0, font[234 - 32], 0, 0, 4, 6 ); fheroes2::Copy( font[75], 4, 4, font[234 - 32], 4, 0, 5, 6 ); fheroes2::Copy( font[75], 0, 10, font[234 - 32], 0, 6, 4, 1 ); fheroes2::Copy( font[75], 7, 10, font[234 - 32], 6, 6, 3, 1 ); font[234 - 32].setPosition( font[75].x(), font[75].y() + 4 ); updateNormalFontLetterShadow( font[234 - 32] ); font[235 - 32] = font[78]; fheroes2::Copy( font[235 - 32], 3, 0, font[235 - 32], 2, 1, 1, 1 ); fheroes2::FillTransform( font[235 - 32], 0, 0, 2, 3, 1 ); fheroes2::FillTransform( font[235 - 32], 2, 0, 1, 1, 1 ); updateNormalFontLetterShadow( font[235 - 32] ); font[236 - 32] = font[45 + offset]; fheroes2::Copy( font[87], 9, 0, font[236 - 32], 3, 0, 4, 7 ); fheroes2::Copy( font[87], 9, 0, font[236 - 32], 9, 0, 4, 7 ); fheroes2::FillTransform( font[236 - 32], 0, 0, 3, 6, 1 ); updateNormalFontLetterShadow( font[236 - 32] ); font[237 - 32] = font[78]; fheroes2::FillTransform( font[237 - 32], 4, 0, 3, 8, 1 ); fheroes2::Copy( font[78], 4, 1, font[237 - 32], 4, 3, 1, 2 ); fheroes2::Copy( font[78], 4, 1, font[237 - 32], 5, 3, 1, 2 ); fheroes2::Copy( font[78], 4, 1, font[237 - 32], 6, 3, 1, 2 ); fheroes2::Copy( font[78], 4, 1, font[237 - 32], 7, 3, 1, 1 ); updateNormalFontLetterShadow( font[237 - 32] ); font[238 - 32] = font[47 + offset]; font[239 - 32] = font[78]; font[240 - 32] = font[48 + offset]; font[241 - 32] = font[35 + offset]; font[242 - 32] = font[77]; font[243 - 32] = font[57 + offset]; font[244 - 32].resize( font[81].width(), font[81].height() ); font[244 - 32].reset(); fheroes2::Copy( font[80], 1, 0, font[244 - 32], 3, 0, 4, 10 ); fheroes2::Copy( font[81], 0, 0, font[244 - 32], 0, 0, 5, 7 ); fheroes2::Copy( font[80], 6, 0, font[244 - 32], 7, 0, 4, 7 ); font[244 - 32].setPosition( font[81].x(), font[81].y() ); updateNormalFontLetterShadow( font[244 - 32] ); font[245 - 32] = font[56 + offset]; font[246 - 32].resize( font[85].width() + 2, font[85].height() + 1 ); font[246 - 32].reset(); fheroes2::Copy( font[85], 0, 0, font[246 - 32], 0, 0, font[85].width(), font[85].height() ); fheroes2::Copy( font[246 - 32], 7, 4, font[246 - 32], 9, 5, 1, 1 ); fheroes2::Copy( font[246 - 32], 7, 4, font[246 - 32], 10, 4, 1, 1 ); fheroes2::Copy( font[246 - 32], 8, 1, font[246 - 32], 11, 4, 1, 4 ); fheroes2::Copy( font[246 - 32], 8, 1, font[246 - 32], 10, 5, 1, 1 ); fheroes2::Copy( font[246 - 32], 9, 5, font[246 - 32], 10, 6, 1, 1 ); fheroes2::Copy( font[246 - 32], 9, 5, font[246 - 32], 10, 7, 1, 1 ); font[246 - 32].setPosition( font[85].x(), font[85].y() ); updateNormalFontLetterShadow( font[246 - 32] ); font[247 - 32] = font[85]; fheroes2::Copy( font[247 - 32], 2, 5, font[247 - 32], 2, 3, 6, 2 ); fheroes2::Copy( font[247 - 32], 8, 0, font[247 - 32], 7, 4, 1, 1 ); fheroes2::Copy( font[247 - 32], 8, 0, font[247 - 32], 7, 5, 1, 1 ); fheroes2::Copy( font[247 - 32], 8, 0, font[247 - 32], 7, 6, 1, 1 ); fheroes2::FillTransform( font[247 - 32], 1, 5, 6, 4, 1 ); updateNormalFontLetterShadow( font[247 - 32] ); font[248 - 32].resize( font[85].width() + 3, font[85].height() ); font[248 - 32].reset(); fheroes2::Copy( font[85], 0, 0, font[248 - 32], 0, 0, 4, 7 ); fheroes2::Copy( font[85], 1, 0, font[248 - 32], 5, 0, 4, 7 ); fheroes2::Copy( font[85], 6, 0, font[248 - 32], 9, 0, 4, 7 ); fheroes2::Copy( font[248 - 32], 8, 5, font[248 - 32], 4, 5, 4, 2 ); font[248 - 32].setPosition( font[85].x(), font[85].y() ); updateNormalFontLetterShadow( font[248 - 32] ); font[249 - 32].resize( font[248 - 32].width() + 2, font[248 - 32].height() ); font[249 - 32].reset(); fheroes2::Copy( font[248 - 32], 0, 0, font[249 - 32], 0, 0, 12, 7 ); fheroes2::Copy( font[246 - 32], 9, 4, font[249 - 32], 12, 4, 3, 4 ); font[249 - 32].setPosition( font[248 - 32].x(), font[248 - 32].y() ); updateNormalFontLetterShadow( font[249 - 32] ); font[252 - 32] = font[226 - 32]; fheroes2::FillTransform( font[252 - 32], 4, 0, 5, 3, 1 ); font[250 - 32].resize( font[252 - 32].width() + 1, font[252 - 32].height() ); font[250 - 32].reset(); fheroes2::Copy( font[252 - 32], 0, 0, font[250 - 32], 1, 0, font[252 - 32].width(), font[252 - 32].height() ); fheroes2::Copy( font[252 - 32], 1, 0, font[250 - 32], 1, 0, 1, 2 ); font[250 - 32].setPosition( font[252 - 32].x(), font[252 - 32].y() ); updateNormalFontLetterShadow( font[250 - 32] ); font[251 - 32].resize( font[252 - 32].width() + 3, font[252 - 32].height() ); font[251 - 32].reset(); fheroes2::Copy( font[252 - 32], 0, 0, font[251 - 32], 0, 0, font[252 - 32].width(), font[252 - 32].height() ); fheroes2::Copy( font[252 - 32], 2, 0, font[251 - 32], 10, 0, 2, 7 ); font[251 - 32].setPosition( font[252 - 32].x(), font[252 - 32].y() ); updateNormalFontLetterShadow( font[251 - 32] ); font[253 - 32] = font[79]; fheroes2::FillTransform( font[253 - 32], 0, 2, 3, 3, 1 ); fheroes2::Copy( font[253 - 32], 8, 3, font[253 - 32], 7, 3, 1, 1 ); fheroes2::Copy( font[253 - 32], 8, 3, font[253 - 32], 6, 3, 1, 1 ); fheroes2::Copy( font[253 - 32], 8, 3, font[253 - 32], 5, 3, 1, 1 ); updateNormalFontLetterShadow( font[253 - 32] ); font[254 - 32].resize( font[79].width() + 1, font[79].height() ); font[254 - 32].reset(); fheroes2::Copy( font[251 - 32], 1, 0, font[254 - 32], 1, 0, 3, 7 ); fheroes2::Copy( font[79], 2, 1, font[254 - 32], 6, 1, 1, 5 ); fheroes2::Copy( font[79], 3, 0, font[254 - 32], 6, 0, 3, 2 ); fheroes2::Copy( font[79], 7, 0, font[254 - 32], 9, 0, 1, 2 ); fheroes2::Copy( font[79], 8, 2, font[254 - 32], 9, 2, 1, 3 ); fheroes2::Copy( font[79], 7, 5, font[254 - 32], 9, 5, 1, 2 ); fheroes2::Copy( font[79], 3, 6, font[254 - 32], 6, 6, 3, 1 ); fheroes2::Copy( font[254 - 32], 1, 0, font[254 - 32], 4, 3, 2, 1 ); font[254 - 32].setPosition( font[251 - 32].x(), font[251 - 32].y() ); updateNormalFontLetterShadow( font[254 - 32] ); font[255 - 32] = font[65]; fheroes2::FillTransform( font[255 - 32], 0, 2, 6, 3, 1 ); fheroes2::Copy( font[69], 2, 5, font[255 - 32], 1, 2, 6, 2 ); fheroes2::Copy( font[255 - 32], 6, 4, font[255 - 32], 6, 3, 1, 1 ); updateNormalFontLetterShadow( font[255 - 32] ); } // Small font. { std::vector & font = icnVsSprite[ICN::SMALFONT]; size_t offset = 0; // E with 2 dots on top. font[168 - 32].resize( font[37].width(), font[37].height() + 2 ); font[168 - 32].reset(); fheroes2::Copy( font[37], 0, 0, font[168 - 32], 0, 2, font[37].width(), font[37].height() ); fheroes2::Copy( font[37], 3, 0, font[168 - 32], 3, 0, 1, 1 ); fheroes2::Copy( font[37], 3, 0, font[168 - 32], 5, 0, 1, 1 ); font[168 - 32].setPosition( font[37].x(), font[37].y() ); updateSmallFontLetterShadow( font[168 - 32] ); font[161 - 32].resize( font[57 + offset].width(), font[57 + offset].height() + 2 ); font[161 - 32].reset(); fheroes2::Copy( font[57 + offset], 0, 0, font[161 - 32], 0, 2, font[57 + offset].width(), font[57 + offset].height() ); fheroes2::Copy( font[57 + offset], 2, 0, font[161 - 32], 5, 0, 2, 1 ); font[161 - 32].setPosition( font[57 + offset].x(), font[57 + offset].y() - 2 ); updateSmallFontLetterShadow( font[161 - 32] ); font[162 - 32].resize( font[89 + offset].width(), font[89 + offset].height() + 2 ); font[162 - 32].reset(); fheroes2::Copy( font[89 + offset], 0, 0, font[162 - 32], 0, 2, font[89 + offset].width(), font[89 + offset].height() ); fheroes2::Copy( font[89 + offset], 1, 0, font[162 - 32], 4, 0, 2, 1 ); font[162 - 32].setPosition( font[89 + offset].x(), font[89 + offset].y() - 2 ); updateSmallFontLetterShadow( font[162 - 32] ); // C with a horizontal line in the middle. font[170 - 32] = font[67 - 32]; fheroes2::Copy( font[170 - 32], 3, 0, font[170 - 32], 2, 3, 3, 1 ); updateSmallFontLetterShadow( font[170 - 32] ); font[186 - 32] = font[99 - 32]; fheroes2::Copy( font[186 - 32], 2, 0, font[186 - 32], 2, 2, 2, 1 ); updateSmallFontLetterShadow( font[186 - 32] ); // I and i with 2 dots. font[175 - 32] = font[73 - 32]; font[191 - 32] = font[105 - 32]; fheroes2::Copy( font[105 - 32], 1, 0, font[191 - 32], 0, 0, 2, 2 ); fheroes2::Copy( font[105 - 32], 1, 0, font[191 - 32], 2, 0, 2, 2 ); // J and j. font[163 - 32] = font[74 - 32]; font[188 - 32] = font[106 - 32]; // S and s. font[189 - 32] = font[83 - 32]; font[190 - 32] = font[115 - 32]; // I and i. font[178 - 32] = font[73 - 32]; font[179 - 32] = font[105 - 32]; // A. font[192 - 32] = font[33 + offset]; font[193 - 32] = font[34 + offset]; fheroes2::FillTransform( font[193 - 32], 5, 1, 2, 2, 1 ); fheroes2::Copy( font[193 - 32], 5, 0, font[193 - 32], 6, 0, 1, 1 ); updateSmallFontLetterShadow( font[193 - 32] ); font[194 - 32] = font[34 + offset]; font[195 - 32].resize( font[193 - 32].width() + 1, font[193 - 32].height() ); font[195 - 32].reset(); fheroes2::Copy( font[193 - 32], 0, 0, font[195 - 32], 0, 0, 4, 8 ); fheroes2::Copy( font[193 - 32], 3, 0, font[195 - 32], 4, 0, 4, 1 ); font[195 - 32].setPosition( font[193 - 32].x(), font[193 - 32].y() ); updateSmallFontLetterShadow( font[195 - 32] ); // The same letter as above but with a vertical line of top. font[165 - 32].resize( font[195 - 32].width() - 1, font[195 - 32].height() + 1 ); font[165 - 32].reset(); fheroes2::Copy( font[195 - 32], 0, 0, font[165 - 32], 0, 1, font[195 - 32].width() - 1, font[195 - 32].height() ); fheroes2::Copy( font[195 - 32], 2, 0, font[165 - 32], 6, 0, 1, 1 ); fheroes2::FillTransform( font[165 - 32], 6, 2, 1, 1, 1 ); font[165 - 32].setPosition( font[195 - 32].x(), font[195 - 32].y() - 1 ); updateSmallFontLetterShadow( font[165 - 32] ); font[196 - 32] = font[36 + offset]; font[197 - 32] = font[37 + offset]; font[198 - 32].resize( font[56].width() + 1, font[56].height() ); font[198 - 32].reset(); fheroes2::Copy( font[56], 1, 0, font[198 - 32], 1, 0, 3, 7 ); fheroes2::Copy( font[56], 7, 0, font[198 - 32], 7, 0, 2, 7 ); fheroes2::Copy( font[56], 4, 2, font[198 - 32], 3, 2, 1, 3 ); fheroes2::Copy( font[56], 6, 2, font[198 - 32], 7, 2, 1, 3 ); fheroes2::Copy( font[37], 4, 5, font[198 - 32], 4, 2, 3, 3 ); fheroes2::Copy( font[37], 3, 0, font[198 - 32], 5, 0, 1, 7 ); fheroes2::Copy( font[56], 8, 0, font[198 - 32], 9, 0, 1, 7 ); font[198 - 32].setPosition( font[56].x(), font[56].y() ); updateSmallFontLetterShadow( font[198 - 32] ); font[199 - 32].resize( font[19].width() + 2, font[19].height() ); font[199 - 32].reset(); fheroes2::Copy( font[19], 1, 0, font[199 - 32], 1, 0, 3, 2 ); fheroes2::Copy( font[19], 2, 0, font[199 - 32], 4, 0, 3, 2 ); fheroes2::Copy( font[19], 2, 2, font[199 - 32], 3, 2, 3, 3 ); fheroes2::Copy( font[19], 2, 5, font[199 - 32], 4, 5, 3, 2 ); fheroes2::Copy( font[19], 1, 5, font[199 - 32], 1, 5, 3, 2 ); fheroes2::FillTransform( font[199 - 32], 2, 4, 3, 2, 1 ); fheroes2::FillTransform( font[199 - 32], 5, 5, 1, 1, 1 ); fheroes2::FillTransform( font[199 - 32], 4, 2, 1, 1, 1 ); font[199 - 32].setPosition( font[19].x(), font[19].y() ); updateSmallFontLetterShadow( font[199 - 32] ); font[200 - 32] = font[40]; fheroes2::FillTransform( font[200 - 32], 4, 2, 3, 4, 1 ); fheroes2::Copy( font[40], 3, 0, font[200 - 32], 4, 4, 1, 1 ); fheroes2::Copy( font[40], 3, 0, font[200 - 32], 5, 3, 1, 1 ); fheroes2::Copy( font[40], 3, 0, font[200 - 32], 6, 2, 1, 1 ); updateSmallFontLetterShadow( font[200 - 32] ); font[201 - 32].resize( font[200 - 32].width(), font[200 - 32].height() + 2 ); font[201 - 32].reset(); fheroes2::Copy( font[200 - 32], 1, 0, font[201 - 32], 1, 2, 8, 7 ); fheroes2::Copy( font[200 - 32], 2, 0, font[201 - 32], 5, 0, 2, 1 ); font[201 - 32].setPosition( font[200 - 32].x(), font[200 - 32].y() - 2 ); updateSmallFontLetterShadow( font[201 - 32] ); font[202 - 32] = font[43 + offset]; font[203 - 32].resize( font[34].width(), font[34].height() ); font[203 - 32].reset(); fheroes2::Copy( font[34], 1, 0, font[203 - 32], 1, 0, 3, 7 ); fheroes2::Copy( font[34], 3, 0, font[203 - 32], 6, 0, 1, 7 ); fheroes2::Copy( font[34], 3, 0, font[203 - 32], 4, 0, 2, 1 ); fheroes2::FillTransform( font[203 - 32], 1, 0, 2, 2, 1 ); fheroes2::FillTransform( font[203 - 32], 3, 0, 1, 1, 1 ); font[203 - 32].setPosition( font[34].x(), font[34].y() ); updateSmallFontLetterShadow( font[203 - 32] ); font[204 - 32] = font[45 + offset]; font[205 - 32] = font[40 + offset]; font[206 - 32] = font[47 + offset]; font[207 - 32] = font[195 - 32]; fheroes2::Copy( font[207 - 32], 3, 0, font[207 - 32], 6, 0, 1, 7 ); updateSmallFontLetterShadow( font[207 - 32] ); font[208 - 32] = font[48 + offset]; font[209 - 32] = font[35 + offset]; font[210 - 32].resize( font[207 - 32].width() + 2, font[207 - 32].height() ); font[210 - 32].reset(); fheroes2::Copy( font[207 - 32], 0, 0, font[210 - 32], 0, 0, font[207 - 32].width(), font[207 - 32].height() ); fheroes2::Copy( font[210 - 32], 5, 0, font[210 - 32], 8, 0, 2, 8 ); font[210 - 32].setPosition( font[207 - 32].x(), font[207 - 32].y() ); font[211 - 32] = font[57 + offset]; font[213 - 32] = font[56 + offset]; font[214 - 32].resize( font[53].width(), font[53].height() + 1 ); font[214 - 32].reset(); fheroes2::Copy( font[53], 1, 0, font[214 - 32], 1, 0, 8, 7 ); fheroes2::Copy( font[214 - 32], 3, 0, font[214 - 32], 9, 5, 1, 3 ); font[214 - 32].setPosition( font[53].x(), font[53].y() ); updateSmallFontLetterShadow( font[214 - 32] ); font[215 - 32] = font[53]; fheroes2::Copy( font[53], 3, 5, font[215 - 32], 3, 2, 4, 2 ); fheroes2::FillTransform( font[215 - 32], 2, 4, 5, 4, 1 ); updateSmallFontLetterShadow( font[215 - 32] ); font[216 - 32].resize( font[53].width(), font[53].height() ); font[216 - 32].reset(); fheroes2::Copy( font[53], 1, 0, font[216 - 32], 1, 0, 4, 7 ); fheroes2::Copy( font[53], 7, 1, font[216 - 32], 6, 1, 2, 6 ); fheroes2::Copy( font[44], 3, 0, font[216 - 32], 9, 0, 1, 8 ); fheroes2::Copy( font[53], 7, 1, font[216 - 32], 5, 5, 1, 1 ); fheroes2::Copy( font[53], 7, 1, font[216 - 32], 8, 5, 1, 1 ); fheroes2::Copy( font[53], 7, 1, font[216 - 32], 6, 0, 1, 1 ); font[216 - 32].setPosition( font[53].x(), font[53].y() ); updateSmallFontLetterShadow( font[216 - 32] ); font[217 - 32].resize( font[216 - 32].width() + 2, font[216 - 32].height() + 1 ); font[217 - 32].reset(); fheroes2::Copy( font[216 - 32], 1, 0, font[217 - 32], 1, 0, 9, 7 ); fheroes2::Copy( font[216 - 32], 3, 0, font[217 - 32], 10, 6, 1, 1 ); fheroes2::Copy( font[216 - 32], 3, 0, font[217 - 32], 11, 5, 1, 3 ); font[217 - 32].setPosition( font[216 - 32].x(), font[216 - 32].y() ); updateSmallFontLetterShadow( font[217 - 32] ); font[220 - 32].resize( font[34].width(), font[34].height() ); font[220 - 32].reset(); fheroes2::Copy( font[34], 2, 0, font[220 - 32], 1, 0, 2, 7 ); fheroes2::Copy( font[34], 4, 3, font[220 - 32], 3, 3, 1, 4 ); fheroes2::Copy( font[34], 4, 3, font[220 - 32], 4, 3, 3, 4 ); fheroes2::FillTransform( font[220 - 32], 1, 0, 1, 1, 1 ); font[220 - 32].setPosition( font[34].x(), font[34].y() ); updateSmallFontLetterShadow( font[220 - 32] ); font[219 - 32].resize( font[220 - 32].width() + 2, font[220 - 32].height() ); font[219 - 32].reset(); fheroes2::Copy( font[220 - 32], 1, 0, font[219 - 32], 1, 0, 6, 7 ); fheroes2::Copy( font[220 - 32], 2, 0, font[219 - 32], 8, 0, 1, 7 ); font[219 - 32].setPosition( font[220 - 32].x(), font[220 - 32].y() ); updateSmallFontLetterShadow( font[219 - 32] ); font[218 - 32].resize( font[220 - 32].width() + 2, font[220 - 32].height() ); font[218 - 32].reset(); fheroes2::Copy( font[220 - 32], 1, 0, font[218 - 32], 3, 0, 6, 7 ); fheroes2::Copy( font[220 - 32], 2, 3, font[218 - 32], 1, 0, 3, 1 ); fheroes2::Copy( font[220 - 32], 2, 3, font[218 - 32], 1, 1, 1, 1 ); font[218 - 32].setPosition( font[220 - 32].x(), font[220 - 32].y() ); updateSmallFontLetterShadow( font[218 - 32] ); font[221 - 32].resize( font[47].width() - 1, font[47].height() ); font[221 - 32].reset(); fheroes2::Copy( font[47], 2, 0, font[221 - 32], 1, 0, 6, 7 ); fheroes2::Copy( font[47], 3, 0, font[221 - 32], 4, 3, 2, 1 ); font[221 - 32].setPosition( font[47].x(), font[47].y() ); updateSmallFontLetterShadow( font[221 - 32] ); font[222 - 32].resize( font[47].width() + 1, font[47].height() ); font[222 - 32].reset(); fheroes2::Copy( font[44], 2, 0, font[222 - 32], 1, 0, 2, 7 ); fheroes2::Copy( font[47], 2, 0, font[222 - 32], 4, 0, 5, 2 ); fheroes2::Copy( font[47], 2, 5, font[222 - 32], 4, 5, 5, 2 ); fheroes2::Copy( font[222 - 32], 1, 0, font[222 - 32], 3, 3, 1, 1 ); fheroes2::Copy( font[222 - 32], 2, 0, font[222 - 32], 4, 2, 1, 3 ); fheroes2::Copy( font[222 - 32], 2, 0, font[222 - 32], 8, 2, 1, 3 ); font[222 - 32].setPosition( font[47].x(), font[47].y() ); updateSmallFontLetterShadow( font[222 - 32] ); font[223 - 32].resize( font[48].width() - 1, font[48].height() ); font[223 - 32].reset(); fheroes2::Copy( font[48], 7, 1, font[223 - 32], 2, 1, 1, 2 ); fheroes2::Copy( font[48], 2, 0, font[223 - 32], 3, 0, 3, 1 ); fheroes2::Copy( font[48], 2, 0, font[223 - 32], 3, 3, 3, 1 ); fheroes2::Copy( font[44], 3, 0, font[223 - 32], 6, 0, 1, 7 ); fheroes2::Copy( font[33], 1, 6, font[223 - 32], 1, 6, 2, 1 ); fheroes2::Copy( font[33], 1, 6, font[223 - 32], 3, 5, 1, 1 ); fheroes2::Copy( font[33], 1, 6, font[223 - 32], 4, 4, 1, 1 ); font[223 - 32].setPosition( font[48].x(), font[48].y() ); updateSmallFontLetterShadow( font[223 - 32] ); offset = 32; // e with 2 dots on top. font[184 - 32].resize( font[69].width(), font[69].height() + 2 ); font[184 - 32].reset(); fheroes2::Copy( font[69], 0, 0, font[184 - 32], 0, 2, font[69].width(), font[69].height() ); fheroes2::Copy( font[69], 2, 0, font[184 - 32], 2, 0, 1, 1 ); fheroes2::Copy( font[69], 2, 0, font[184 - 32], 4, 0, 1, 1 ); font[184 - 32].setPosition( font[69].x(), font[69].y() - 2 ); updateSmallFontLetterShadow( font[184 - 32] ); font[224 - 32] = font[33 + offset]; font[225 - 32].resize( font[34].width(), font[34].height() ); font[225 - 32].reset(); fheroes2::Copy( font[34], 4, 3, font[225 - 32], 4, 3, 3, 4 ); fheroes2::Copy( font[65], 1, 2, font[225 - 32], 2, 4, 2, 3 ); fheroes2::FillTransform( font[225 - 32], 3, 5, 1, 1, 1 ); fheroes2::Copy( font[225 - 32], 2, 5, font[225 - 32], 2, 1, 2, 2 ); fheroes2::Copy( font[37], 2, 0, font[225 - 32], 2, 0, 5, 1 ); font[225 - 32].setPosition( font[34].x(), font[34].y() ); updateSmallFontLetterShadow( font[225 - 32] ); font[226 - 32].resize( font[82].width() - 1, font[82].height() ); font[226 - 32].reset(); fheroes2::Copy( font[82], 1, 0, font[226 - 32], 1, 0, 2, 5 ); fheroes2::Copy( font[82], 1, 0, font[226 - 32], 3, 0, 2, 1 ); fheroes2::Copy( font[82], 1, 0, font[226 - 32], 3, 2, 2, 1 ); fheroes2::Copy( font[82], 1, 0, font[226 - 32], 3, 4, 2, 1 ); fheroes2::Copy( font[82], 1, 0, font[226 - 32], 5, 1, 1, 1 ); fheroes2::Copy( font[82], 1, 0, font[226 - 32], 5, 3, 1, 1 ); font[226 - 32].setPosition( font[82].x(), font[82].y() ); updateSmallFontLetterShadow( font[226 - 32] ); font[227 - 32] = font[82]; fheroes2::Copy( font[227 - 32], 3, 1, font[227 - 32], 3, 0, 1, 1 ); fheroes2::FillTransform( font[227 - 32], 3, 1, 1, 1, 1 ); updateSmallFontLetterShadow( font[227 - 32] ); // The same letter as above but with a vertical line of top. font[180 - 32].resize( font[227 - 32].width() - 1, font[227 - 32].height() + 1 ); font[180 - 32].reset(); fheroes2::Copy( font[227 - 32], 0, 0, font[180 - 32], 0, 1, font[227 - 32].width() - 1, font[227 - 32].height() ); fheroes2::Copy( font[227 - 32], 5, 0, font[180 - 32], 5, 0, 1, 1 ); fheroes2::FillTransform( font[180 - 32], 5, 2, 1, 1, 1 ); font[180 - 32].setPosition( font[227 - 32].x(), font[227 - 32].y() - 1 ); updateSmallFontLetterShadow( font[180 - 32] ); font[228 - 32] = font[71]; font[229 - 32] = font[37 + offset]; font[230 - 32].resize( font[88].width() + 1, font[88].height() ); font[230 - 32].reset(); fheroes2::Copy( font[88], 0, 0, font[230 - 32], 0, 0, 4, 5 ); fheroes2::Copy( font[88], 4, 0, font[230 - 32], 5, 0, 4, 5 ); fheroes2::Copy( font[85], 2, 0, font[230 - 32], 4, 0, 1, 4 ); fheroes2::Copy( font[85], 2, 0, font[230 - 32], 4, 4, 1, 1 ); font[230 - 32].setPosition( font[88].x(), font[88].y() ); updateSmallFontLetterShadow( font[230 - 32] ); font[232 - 32] = font[85]; font[233 - 32].resize( font[232 - 32].width(), font[232 - 32].height() + 2 ); font[233 - 32].reset(); fheroes2::Copy( font[232 - 32], 1, 0, font[233 - 32], 1, 2, 7, 5 ); fheroes2::Copy( font[232 - 32], 1, 0, font[233 - 32], 3, 0, 2, 1 ); font[233 - 32].setPosition( font[232 - 32].x(), font[232 - 32].y() - 2 ); updateSmallFontLetterShadow( font[233 - 32] ); font[234 - 32].resize( font[75].width(), font[75].height() - 2 ); font[234 - 32].reset(); fheroes2::Copy( font[75], 1, 0, font[234 - 32], 1, 0, 2, 5 ); fheroes2::Copy( font[75], 3, 2, font[234 - 32], 3, 0, 3, 5 ); font[234 - 32].setPosition( font[75].x(), font[75].y() + 2 ); updateSmallFontLetterShadow( font[234 - 32] ); font[235 - 32].resize( font[65].width() - 2, font[65].height() ); font[235 - 32].reset(); fheroes2::Copy( font[203 - 32], 2, 3, font[235 - 32], 1, 1, 2, 4 ); fheroes2::Copy( font[203 - 32], 5, 0, font[235 - 32], 3, 0, 2, 5 ); fheroes2::FillTransform( font[235 - 32], 1, 1, 1, 1, 1 ); font[235 - 32].setPosition( font[65].x(), font[65].y() ); updateSmallFontLetterShadow( font[235 - 32] ); font[236 - 32].resize( font[235 - 32].width() + 3, font[235 - 32].height() ); font[236 - 32].reset(); fheroes2::Copy( font[235 - 32], 4, 0, font[236 - 32], 4, 0, 1, 5 ); fheroes2::Copy( font[235 - 32], 1, 0, font[236 - 32], 1, 1, 3, 3 ); fheroes2::Copy( font[236 - 32], 2, 0, font[236 - 32], 5, 0, 3, 5 ); fheroes2::Copy( font[236 - 32], 4, 0, font[236 - 32], 1, 4, 1, 1 ); font[236 - 32].setPosition( font[235 - 32].x(), font[235 - 32].y() ); updateSmallFontLetterShadow( font[236 - 32] ); font[237 - 32].resize( font[72].width() - 2, font[72].height() - 2 ); font[237 - 32].reset(); fheroes2::Copy( font[72], 1, 0, font[237 - 32], 1, 0, 2, 5 ); fheroes2::Copy( font[72], 2, 0, font[237 - 32], 5, 0, 1, 5 ); fheroes2::Copy( font[72], 3, 2, font[237 - 32], 3, 2, 2, 1 ); font[237 - 32].setPosition( font[72].x(), font[72].y() + 2 ); updateSmallFontLetterShadow( font[237 - 32] ); font[238 - 32] = font[47 + offset]; font[239 - 32] = font[78]; font[240 - 32] = font[48 + offset]; font[241 - 32] = font[35 + offset]; font[242 - 32].resize( font[77].width() - 4, font[77].height() ); font[242 - 32].reset(); fheroes2::Copy( font[77], 1, 0, font[242 - 32], 1, 0, 2, 5 ); fheroes2::Copy( font[77], 6, 1, font[242 - 32], 4, 1, 1, 4 ); fheroes2::Copy( font[77], 10, 1, font[242 - 32], 6, 1, 2, 4 ); fheroes2::Copy( font[77], 3, 0, font[242 - 32], 2, 0, 3, 1 ); fheroes2::Copy( font[77], 3, 0, font[242 - 32], 5, 0, 1, 1 ); font[242 - 32].setPosition( font[77].x(), font[77].y() ); updateSmallFontLetterShadow( font[242 - 32] ); font[243 - 32] = font[57 + offset]; font[244 - 32].resize( font[81].width() + 3, font[81].height() + 1 ); font[244 - 32].reset(); fheroes2::Copy( font[81], 1, 0, font[244 - 32], 1, 0, 5, 7 ); fheroes2::Copy( font[81], 2, 0, font[244 - 32], 6, 0, 4, 4 ); fheroes2::Copy( font[81], 2, 4, font[244 - 32], 6, 4, 3, 1 ); fheroes2::Copy( font[81], 2, 4, font[244 - 32], 5, 7, 1, 1 ); font[244 - 32].setPosition( font[81].x(), font[81].y() ); updateSmallFontLetterShadow( font[244 - 32] ); // Bigger letter font[212 - 32] = font[244 - 32]; fheroes2::Copy( font[212 - 32], 5, 1, font[212 - 32], 5, 0, 1, 1 ); fheroes2::Copy( font[212 - 32], 5, 1, font[212 - 32], 4, 7, 1, 1 ); font[212 - 32].setPosition( font[48].x(), font[48].y() ); // copy from a big better updateSmallFontLetterShadow( font[212 - 32] ); font[245 - 32] = font[56 + offset]; font[246 - 32].resize( font[85].width() + 1, font[85].height() + 1 ); font[246 - 32].reset(); fheroes2::Copy( font[85], 0, 0, font[246 - 32], 0, 0, font[85].width(), font[85].height() ); fheroes2::Copy( font[246 - 32], 2, 0, font[246 - 32], 8, 3, 1, 3 ); font[246 - 32].setPosition( font[85].x(), font[85].y() ); updateSmallFontLetterShadow( font[246 - 32] ); font[247 - 32] = font[85]; fheroes2::Copy( font[85], 2, 4, font[247 - 32], 2, 2, 4, 1 ); fheroes2::Copy( font[85], 1, 0, font[247 - 32], 6, 4, 1, 1 ); fheroes2::FillTransform( font[247 - 32], 1, 3, 5, 3, 1 ); updateSmallFontLetterShadow( font[247 - 32] ); font[248 - 32].resize( font[85].width() + 2, font[85].height() ); font[248 - 32].reset(); fheroes2::Copy( font[85], 1, 0, font[248 - 32], 1, 0, 3, 5 ); fheroes2::Copy( font[85], 6, 0, font[248 - 32], 5, 0, 2, 5 ); fheroes2::Copy( font[85], 6, 0, font[248 - 32], 8, 0, 2, 5 ); fheroes2::Copy( font[85], 1, 0, font[248 - 32], 4, 4, 1, 1 ); fheroes2::Copy( font[85], 1, 0, font[248 - 32], 7, 4, 1, 1 ); font[248 - 32].setPosition( font[85].x(), font[85].y() ); updateSmallFontLetterShadow( font[248 - 32] ); font[249 - 32].resize( font[248 - 32].width() + 1, font[248 - 32].height() ); font[249 - 32].reset(); fheroes2::Copy( font[248 - 32], 1, 0, font[249 - 32], 1, 0, 9, 5 ); fheroes2::Copy( font[248 - 32], 2, 0, font[249 - 32], 10, 3, 1, 3 ); font[249 - 32].setPosition( font[248 - 32].x(), font[248 - 32].y() ); updateSmallFontLetterShadow( font[249 - 32] ); font[252 - 32] = font[226 - 32]; fheroes2::Copy( font[252 - 32], 1, 0, font[252 - 32], 5, 4, 1, 1 ); fheroes2::FillTransform( font[252 - 32], 0, 0, 2, 2, 1 ); fheroes2::FillTransform( font[252 - 32], 3, 0, 3, 2, 1 ); font[250 - 32].resize( font[252 - 32].width() + 1, font[252 - 32].height() ); font[250 - 32].reset(); fheroes2::Copy( font[252 - 32], 1, 0, font[250 - 32], 2, 0, 5, 5 ); fheroes2::Copy( font[252 - 32], 2, 2, font[250 - 32], 1, 0, 2, 1 ); fheroes2::Copy( font[252 - 32], 2, 2, font[250 - 32], 1, 1, 1, 1 ); font[250 - 32].setPosition( font[252 - 32].x(), font[252 - 32].y() ); updateSmallFontLetterShadow( font[250 - 32] ); font[251 - 32].resize( font[252 - 32].width() + 2, font[252 - 32].height() ); font[251 - 32].reset(); fheroes2::Copy( font[252 - 32], 1, 0, font[251 - 32], 1, 0, 5, 5 ); fheroes2::Copy( font[252 - 32], 2, 0, font[251 - 32], 7, 0, 1, 5 ); font[251 - 32].setPosition( font[252 - 32].x(), font[252 - 32].y() ); updateSmallFontLetterShadow( font[251 - 32] ); font[253 - 32].resize( font[79].width() - 1, font[79].height() ); font[253 - 32].reset(); fheroes2::Copy( font[79], 2, 0, font[253 - 32], 1, 0, 4, 5 ); fheroes2::Copy( font[79], 2, 0, font[253 - 32], 2, 2, 2, 1 ); font[253 - 32].setPosition( font[79].x(), font[79].y() ); updateSmallFontLetterShadow( font[253 - 32] ); font[231 - 32] = font[253 - 32]; fheroes2::FillTransform( font[231 - 32], 0, 1, 3, 3, 1 ); fheroes2::FillTransform( font[231 - 32], 4, 2, 1, 1, 1 ); fheroes2::FillTransform( font[231 - 32], 1, 0, 1, 1, 1 ); fheroes2::Copy( font[253 - 32], 1, 0, font[231 - 32], 1, 1, 1, 1 ); updateSmallFontLetterShadow( font[231 - 32] ); font[254 - 32].resize( font[79].width() + 2, font[79].height() ); font[254 - 32].reset(); fheroes2::Copy( font[82], 1, 0, font[254 - 32], 1, 0, 2, 5 ); fheroes2::Copy( font[79], 1, 0, font[254 - 32], 4, 0, 3, 5 ); fheroes2::Copy( font[79], 5, 1, font[254 - 32], 7, 1, 1, 3 ); fheroes2::Copy( font[79], 5, 1, font[254 - 32], 3, 2, 1, 1 ); font[254 - 32].setPosition( font[79].x(), font[79].y() ); updateSmallFontLetterShadow( font[254 - 32] ); font[255 - 32].resize( font[65].width() - 1, font[65].height() ); font[255 - 32].reset(); fheroes2::Copy( font[65], 1, 2, font[255 - 32], 2, 0, 3, 3 ); fheroes2::Copy( font[235 - 32], 4, 0, font[255 - 32], 5, 0, 1, 5 ); fheroes2::Copy( font[33], 1, 5, font[255 - 32], 1, 3, 3, 2 ); font[255 - 32].setPosition( font[65].x(), font[65].y() ); updateSmallFontLetterShadow( font[255 - 32] ); } } // CP1252 supports German, Italian, Spanish, Norwegian, Swedish and Danish (and French but OG has custom encoding) void generateCP1252Alphabet( std::vector> & icnVsSprite ) { // Resize fonts. for ( const int icnId : { ICN::FONT, ICN::SMALFONT } ) { icnVsSprite[icnId].resize( baseFontSize ); const fheroes2::Sprite firstSprite{ icnVsSprite[icnId][0] }; icnVsSprite[icnId].insert( icnVsSprite[icnId].end(), 160, firstSprite ); } // Normal font. { std::vector & font = icnVsSprite[ICN::FONT]; // Inverted exclamation mark !. font[161 - 32].resize( font[33 - 32].width() + 1, font[33 - 32].height() + 3 ); font[161 - 32].reset(); fheroes2::Copy( font[33 - 32], 1, 0, font[161 - 32], 1, 3, font[33 - 32].width(), font[33 - 32].height() ); { fheroes2::Sprite temp = fheroes2::Flip( font[161 - 32], false, true ); fheroes2::Copy( temp, 0, 2, font[161 - 32], 1, 2, temp.width(), temp.height() ); } font[161 - 32].setPosition( font[33 - 32].x(), font[33 - 32].y() + 2 ); updateNormalFontLetterShadow( font[161 - 32] ); // Left-pointing double angle quotation mark <<. font[171 - 32].resize( 8, 9 ); font[171 - 32].reset(); fheroes2::Copy( font[47 - 32], 5, 0, font[171 - 32], 1, 0, 3, 3 ); fheroes2::Flip( font[47 - 32], 5, 0, font[171 - 32], 1, 4, 3, 3, false, true ); fheroes2::Flip( font[47 - 32], 5, 2, font[171 - 32], 1, 3, 2, 1, true, false ); fheroes2::Copy( font[47 - 32], 5, 2, font[171 - 32], 1, 1, 1, 1 ); fheroes2::Copy( font[47 - 32], 5, 2, font[171 - 32], 1, 5, 1, 1 ); // Second mark. fheroes2::Copy( font[171 - 32], 1, 0, font[171 - 32], 5, 0, 3, 7 ); font[171 - 32].setPosition( font[33].x(), font[33].y() + 4 ); updateNormalFontLetterShadow( font[171 - 32] ); // Right-pointing double angle quotation mark >>. font[187 - 32].resize( 8, 9 ); font[187 - 32].reset(); fheroes2::Flip( font[171 - 32], 1, 0, font[187 - 32], 1, 0, 7, 7, true, false ); // Remove old shadows fheroes2::FillTransform( font[187 - 32], 3, 6, 1, 1, 1 ); fheroes2::FillTransform( font[187 - 32], 7, 6, 1, 1, 1 ); fheroes2::FillTransform( font[187 - 32], 4, 5, 1, 1, 1 ); font[187 - 32].setPosition( font[33].x(), font[33].y() + 4 ); updateNormalFontLetterShadow( font[187 - 32] ); // Inverted question mark ?. font[191 - 32].resize( font[63 - 32].width() + 1, font[63 - 32].height() ); font[191 - 32].reset(); fheroes2::Copy( font[63 - 32], 1, 0, font[191 - 32], 0, 0, font[63 - 32].width(), 11 ); { fheroes2::Sprite temp = fheroes2::Flip( font[191 - 32], true, true ); fheroes2::Copy( temp, 1, 2, font[191 - 32], 0, 0, temp.width(), temp.height() ); } // Remove old shadows fheroes2::FillTransform( font[191 - 32], 6, 1, 1, 2, 1 ); fheroes2::FillTransform( font[191 - 32], 5, 2, 1, 1, 1 ); fheroes2::FillTransform( font[191 - 32], 2, 4, 1, 1, 1 ); fheroes2::FillTransform( font[191 - 32], 3, 8, 5, 1, 1 ); fheroes2::FillTransform( font[191 - 32], 4, 7, 3, 1, 1 ); fheroes2::FillTransform( font[191 - 32], 8, 5, 1, 1, 1 ); font[191 - 32].setPosition( font[63 - 32].x(), font[63 - 32].y() + 2 ); updateNormalFontLetterShadow( font[191 - 32] ); // Improve generated shadows fheroes2::FillTransform( font[191 - 32], 0, 8, 1, 1, 1 ); fheroes2::FillTransform( font[191 - 32], 0, 12, 1, 1, 1 ); fheroes2::FillTransform( font[191 - 32], 7, 12, 1, 1, 1 ); // A with grave accent ` and generate the grave accent for further use. font[192 - 32].resize( font[33].width(), font[33].height() + 3 ); font[192 - 32].reset(); fheroes2::Copy( font[33], 0, 0, font[192 - 32], 0, 3, font[33].width(), font[33].height() ); font[192 - 32].setPosition( font[33].x(), font[33].y() - 3 ); fheroes2::Copy( font[192 - 32], 3, 4, font[192 - 32], 7, 0, 1, 1 ); fheroes2::Copy( font[192 - 32], 4, 4, font[192 - 32], 8, 0, 1, 1 ); fheroes2::Copy( font[192 - 32], 3, 4, font[192 - 32], 8, 1, 1, 1 ); fheroes2::Copy( font[192 - 32], 4, 4, font[192 - 32], 9, 1, 1, 1 ); fheroes2::Copy( font[192 - 32], 3, 3, font[192 - 32], 10, 1, 1, 1 ); updateNormalFontLetterShadow( font[192 - 32] ); // A with acute accent. Generate the accent for further use. font[193 - 32].resize( font[33].width(), font[33].height() + 3 ); font[193 - 32].reset(); fheroes2::Copy( font[33], 0, 0, font[193 - 32], 0, 3, font[33].width(), font[33].height() ); fheroes2::Copy( font[193 - 32], 3, 4, font[193 - 32], 10, 0, 1, 1 ); fheroes2::Copy( font[193 - 32], 4, 4, font[193 - 32], 9, 0, 1, 1 ); fheroes2::Copy( font[193 - 32], 3, 4, font[193 - 32], 9, 1, 1, 1 ); fheroes2::Copy( font[193 - 32], 4, 4, font[193 - 32], 8, 1, 1, 1 ); fheroes2::Copy( font[193 - 32], 3, 3, font[193 - 32], 7, 1, 1, 1 ); font[193 - 32].setPosition( font[33].x(), font[33].y() - 3 ); updateNormalFontLetterShadow( font[193 - 32] ); // A with circumflex accent. Generation of accent for further use. font[194 - 32].resize( font[65 - 32].width(), font[65 - 32].height() + 3 ); font[194 - 32].reset(); fheroes2::Copy( font[65 - 32], 0, 0, font[194 - 32], 0, 3, font[65 - 32].width(), font[65 - 32].height() ); fheroes2::Copy( font[97 - 32], 7, 1, font[194 - 32], 7, 1, 1, 1 ); fheroes2::Copy( font[97 - 32], 7, 1, font[194 - 32], 8, 0, 1, 1 ); fheroes2::Copy( font[97 - 32], 7, 1, font[194 - 32], 9, 1, 1, 1 ); fheroes2::Copy( font[97 - 32], 1, 0, font[194 - 32], 7, 0, 1, 1 ); fheroes2::Copy( font[97 - 32], 1, 0, font[194 - 32], 9, 0, 1, 1 ); font[194 - 32].setPosition( font[65 - 32].x(), font[65 - 32].y() - 3 ); updateNormalFontLetterShadow( font[194 - 32] ); // A with tilde accent ~. Generate accent for further use. font[195 - 32].resize( font[65 - 32].width(), font[65 - 32].height() + 4 ); font[195 - 32].reset(); fheroes2::Copy( font[65 - 32], 0, 0, font[195 - 32], 0, 4, font[65 - 32].width(), font[65 - 32].height() ); fheroes2::Copy( font[37 - 32], 7, 5, font[195 - 32], 4, 0, 5, 2 ); fheroes2::Copy( font[37 - 32], 8, 8, font[195 - 32], 8, 2, 3, 1 ); fheroes2::Copy( font[37 - 32], 10, 7, font[195 - 32], 10, 1, 2, 1 ); font[195 - 32].setPosition( font[65 - 32].x(), font[65 - 32].y() - 4 ); updateNormalFontLetterShadow( font[195 - 32] ); // A with 2 dots on top. font[196 - 32].resize( font[33].width(), font[33].height() + 3 ); font[196 - 32].reset(); fheroes2::Copy( font[33], 0, 0, font[196 - 32], 0, 3, font[33].width(), font[33].height() ); fheroes2::Copy( font[196 - 32], 3, 1 + 3, font[196 - 32], 4, 0, 1, 1 ); fheroes2::Copy( font[196 - 32], 4, 1 + 3, font[196 - 32], 5, 0, 1, 1 ); fheroes2::Copy( font[196 - 32], 4, 0, font[196 - 32], 9, 0, 2, 1 ); font[196 - 32].setPosition( font[33].x(), font[33].y() - 3 ); updateNormalFontLetterShadow( font[196 - 32] ); // A with circle on top. font[197 - 32].resize( font[33].width(), font[33].height() + 2 ); font[197 - 32].reset(); fheroes2::Copy( font[33], 0, 3, font[197 - 32], 0, 5, font[33].width(), font[33].height() ); fheroes2::FillTransform( font[197 - 32], 2, 5, 3, 3, 1 ); fheroes2::Copy( font[80], 5, 6, font[197 - 32], 7, 4, 4, 1 ); fheroes2::Copy( font[80], 5, 6, font[197 - 32], 7, 0, 4, 1 ); fheroes2::Copy( font[80], 5, 6, font[197 - 32], 7, 2, 4, 1 ); fheroes2::Copy( font[84], 1, 0, font[197 - 32], 7, 1, 1, 1 ); fheroes2::Copy( font[84], 1, 0, font[197 - 32], 10, 1, 1, 1 ); font[197 - 32].setPosition( font[33].x(), font[33].y() - 2 ); updateNormalFontLetterShadow( font[197 - 32] ); // A attached to E. font[198 - 32].resize( font[33].width() + 3, font[33].height() ); font[198 - 32].reset(); fheroes2::Copy( font[33], 0, 0, font[198 - 32], 0, 0, font[33].width(), font[33].height() ); fheroes2::Copy( font[37], 6, 0, font[198 - 32], 12, 0, 6, 4 ); fheroes2::Copy( font[37], 5, 0, font[198 - 32], 10, 0, 2, 2 ); fheroes2::Copy( font[37], 6, 4, font[198 - 32], 12, 4, 3, 2 ); fheroes2::Copy( font[37], 6, 4, font[198 - 32], 15, 4, 1, 2 ); fheroes2::Copy( font[37], 8, 9, font[198 - 32], 14, 9, 3, 2 ); font[198 - 32].setPosition( font[33].x(), font[33].y() ); updateNormalFontLetterShadow( font[198 - 32] ); // C with cedilla. font[199 - 32].resize( font[35].width(), font[35].height() + 3 ); font[199 - 32].reset(); fheroes2::Copy( font[35], 0, 0, font[199 - 32], 0, 0, font[35].width(), font[35].height() ); fheroes2::Copy( font[67], 2, 1, font[199 - 32], 7, 11, 1, 1 ); fheroes2::Copy( font[67], 5, 6, font[199 - 32], 8, 11, 1, 1 ); fheroes2::Copy( font[67], 2, 6, font[199 - 32], 9, 11, 1, 1 ); fheroes2::Copy( font[67], 2, 1, font[199 - 32], 8, 12, 1, 1 ); fheroes2::Copy( font[67], 5, 6, font[199 - 32], 9, 12, 1, 1 ); fheroes2::Copy( font[67], 2, 1, font[199 - 32], 7, 13, 1, 1 ); fheroes2::Copy( font[67], 5, 6, font[199 - 32], 8, 13, 1, 1 ); fheroes2::Copy( font[67], 3, 0, font[199 - 32], 9, 13, 1, 1 ); font[199 - 32].setPosition( font[35].x(), font[35].y() ); updateNormalFontLetterShadow( font[199 - 32] ); // E with grave accent `. font[200 - 32].resize( font[37].width(), font[37].height() + 3 ); font[200 - 32].reset(); fheroes2::Copy( font[37], 0, 0, font[200 - 32], 0, 3, font[37].width(), font[37].height() ); font[200 - 32].setPosition( font[37].x(), font[37].y() - 3 ); fheroes2::Copy( font[192 - 32], 7, 0, font[200 - 32], 4, 0, 4, 2 ); updateNormalFontLetterShadow( font[200 - 32] ); // E with acute accent. font[201 - 32].resize( font[37].width(), font[37].height() + 3 ); font[201 - 32].reset(); fheroes2::Copy( font[37], 0, 0, font[201 - 32], 0, 3, font[37].width(), font[37].height() ); fheroes2::Copy( font[193 - 32], 7, 0, font[201 - 32], 5, 0, 4, 2 ); font[201 - 32].setPosition( font[37].x(), font[37].y() - 3 ); updateNormalFontLetterShadow( font[201 - 32] ); // E with circumflex accent. font[202 - 32].resize( font[69 - 32].width(), font[69 - 32].height() + 3 ); font[202 - 32].reset(); fheroes2::Copy( font[69 - 32], 0, 0, font[202 - 32], 0, 3, font[69 - 32].width(), font[69 - 32].height() ); fheroes2::Copy( font[194 - 32], 7, 0, font[202 - 32], 5, 0, 3, 2 ); font[202 - 32].setPosition( font[69 - 32].x(), font[69 - 32].y() - 3 ); updateNormalFontLetterShadow( font[202 - 32] ); // E with diaeresis. font[203 - 32].resize( font[37].width(), font[37].height() + 3 ); font[203 - 32].reset(); fheroes2::Copy( font[37], 0, 0, font[203 - 32], 0, 3, font[37].width(), font[37].height() ); fheroes2::Copy( font[193 - 32], 7, 0, font[203 - 32], 5, 0, 4, 2 ); font[203 - 32].setPosition( font[37].x(), font[37].y() - 3 ); updateNormalFontLetterShadow( font[203 - 32] ); // I with grave accent `. font[204 - 32].resize( font[41].width(), font[41].height() + 3 ); font[204 - 32].reset(); fheroes2::Copy( font[41], 0, 0, font[204 - 32], 0, 3, font[41].width(), font[41].height() ); font[204 - 32].setPosition( font[41].x(), font[41].y() - 3 ); fheroes2::Copy( font[192 - 32], 7, 0, font[204 - 32], 3, 0, 4, 2 ); updateNormalFontLetterShadow( font[204 - 32] ); // I with accute accent. font[205 - 32].resize( font[41].width(), font[41].height() + 3 ); font[205 - 32].reset(); fheroes2::Copy( font[41], 0, 0, font[205 - 32], 0, 3, font[41].width(), font[41].height() ); fheroes2::Copy( font[193 - 32], 7, 0, font[205 - 32], 3, 0, 4, 2 ); font[205 - 32].setPosition( font[41].x(), font[41].y() - 3 ); updateNormalFontLetterShadow( font[205 - 32] ); // I with circumflex. font[206 - 32].resize( font[41].width(), font[41].height() + 3 ); font[206 - 32].reset(); fheroes2::Copy( font[41], 0, 0, font[206 - 32], 0, 3, font[41].width(), font[41].height() ); fheroes2::Copy( font[194 - 32], 7, 0, font[206 - 32], 3, 0, 3, 2 ); font[206 - 32].setPosition( font[41].x(), font[41].y() - 3 ); updateNormalFontLetterShadow( font[206 - 32] ); // I with diaeresis. font[207 - 32].resize( font[41].width(), font[41].height() + 3 ); font[207 - 32].reset(); fheroes2::Copy( font[41], 0, 0, font[207 - 32], 0, 3, font[41].width(), font[41].height() ); fheroes2::Copy( font[193 - 32], 7, 0, font[207 - 32], 3, 0, 4, 2 ); font[207 - 32].setPosition( font[41].x(), font[41].y() - 3 ); updateNormalFontLetterShadow( font[207 - 32] ); // N with tilde ~. font[209 - 32].resize( font[46].width(), font[46].height() + 3 ); font[209 - 32].reset(); fheroes2::Copy( font[46], 0, 0, font[209 - 32], 0, 3, font[46].width(), font[46].height() ); fheroes2::Copy( font[195 - 32], 4, 0, font[209 - 32], 4, 0, 8, 3 ); font[209 - 32].setPosition( font[46].x(), font[46].y() - 3 ); updateNormalFontLetterShadow( font[209 - 32] ); // O with grave accent `. font[210 - 32].resize( font[47].width(), font[47].height() + 3 ); font[210 - 32].reset(); fheroes2::Copy( font[47], 0, 0, font[210 - 32], 0, 3, font[47].width(), font[47].height() ); font[210 - 32].setPosition( font[47].x(), font[47].y() - 3 ); fheroes2::Copy( font[192 - 32], 7, 0, font[210 - 32], 6, 0, 4, 2 ); updateNormalFontLetterShadow( font[210 - 32] ); // O with acute accent. font[211 - 32].resize( font[47].width(), font[47].height() + 3 ); font[211 - 32].reset(); fheroes2::Copy( font[47], 0, 0, font[211 - 32], 0, 3, font[47].width(), font[47].height() ); fheroes2::Copy( font[193 - 32], 7, 0, font[211 - 32], 7, 0, 4, 2 ); font[211 - 32].setPosition( font[47].x(), font[47].y() - 3 ); updateNormalFontLetterShadow( font[211 - 32] ); // O with circumflex accent. font[212 - 32].resize( font[79 - 32].width(), font[79 - 32].height() + 3 ); font[212 - 32].reset(); fheroes2::Copy( font[79 - 32], 0, 0, font[212 - 32], 0, 3, font[79 - 32].width(), font[79 - 32].height() ); fheroes2::Copy( font[194 - 32], 7, 0, font[212 - 32], 7, 0, 3, 2 ); font[212 - 32].setPosition( font[79 - 32].x(), font[79 - 32].y() - 3 ); updateNormalFontLetterShadow( font[212 - 32] ); // O with tilde accent ~. font[213 - 32].resize( font[79 - 32].width(), font[79 - 32].height() + 4 ); font[213 - 32].reset(); fheroes2::Copy( font[79 - 32], 0, 0, font[213 - 32], 0, 4, font[79 - 32].width(), font[79 - 32].height() ); fheroes2::Copy( font[195 - 32], 4, 0, font[213 - 32], 4, 0, 8, 3 ); font[213 - 32].setPosition( font[79 - 32].x(), font[79 - 32].y() - 4 ); updateNormalFontLetterShadow( font[213 - 32] ); // O with 2 dots on top. TODO replace with previous accent generation in A. font[214 - 32].resize( font[47].width(), font[47].height() + 3 ); font[214 - 32].reset(); fheroes2::Copy( font[47], 0, 0, font[214 - 32], 0, 3, font[47].width(), font[47].height() ); fheroes2::Copy( font[214 - 32], 1, 2 + 3, font[214 - 32], 5, 0, 1, 1 ); fheroes2::Copy( font[214 - 32], 2, 2 + 3, font[214 - 32], 6, 0, 1, 1 ); fheroes2::Copy( font[214 - 32], 5, 0, font[214 - 32], 10, 0, 2, 1 ); font[214 - 32].setPosition( font[47].x(), font[47].y() - 3 ); updateNormalFontLetterShadow( font[214 - 32] ); // O with / inside. font[216 - 32].resize( font[47].width() + 2, font[47].height() ); font[216 - 32].reset(); fheroes2::Copy( font[47], 0, 0, font[216 - 32], 0, 0, font[47].width(), font[47].height() ); fheroes2::Copy( font[56], 10, 0, font[216 - 32], 6, 3, 5, 5 ); fheroes2::Copy( font[56], 13, 0, font[216 - 32], font[47].width() - 1, 0, 3, 2 ); fheroes2::Copy( font[47], font[47].width() - 3, 1, font[216 - 32], font[47].width() - 3, 2, 2, 1 ); fheroes2::Copy( font[33], 1, 7, font[216 - 32], 1, 7, 2, 4 ); fheroes2::Copy( font[56], 10, 3, font[216 - 32], 3, font[47].height() - 4, 2, 2 ); font[216 - 32].setPosition( font[47].x(), font[47].y() ); updateNormalFontLetterShadow( font[216 - 32] ); // U with grave accent `. font[217 - 32].resize( font[53].width(), font[53].height() + 3 ); font[217 - 32].reset(); fheroes2::Copy( font[53], 0, 0, font[217 - 32], 0, 3, font[53].width(), font[53].height() ); font[217 - 32].setPosition( font[53].x(), font[53].y() - 3 ); fheroes2::Copy( font[192 - 32], 7, 0, font[217 - 32], 5, 0, 4, 2 ); updateNormalFontLetterShadow( font[217 - 32] ); // U with acute accent. font[218 - 32].resize( font[53].width(), font[53].height() + 3 ); font[218 - 32].reset(); fheroes2::Copy( font[53], 0, 0, font[218 - 32], 0, 3, font[53].width(), font[53].height() ); fheroes2::Copy( font[193 - 32], 7, 0, font[218 - 32], 5, 0, 4, 2 ); font[218 - 32].setPosition( font[53].x(), font[53].y() - 3 ); updateNormalFontLetterShadow( font[218 - 32] ); // U with circumflex. font[219 - 32].resize( font[53].width(), font[53].height() + 3 ); font[219 - 32].reset(); fheroes2::Copy( font[53], 0, 0, font[219 - 32], 0, 3, font[53].width(), font[53].height() ); fheroes2::Copy( font[194 - 32], 7, 0, font[219 - 32], 6, 0, 3, 2 ); font[219 - 32].setPosition( font[53].x(), font[53].y() - 3 ); updateNormalFontLetterShadow( font[219 - 32] ); // U with 2 dots on top. font[220 - 32].resize( font[53].width(), font[53].height() + 3 ); font[220 - 32].reset(); fheroes2::Copy( font[53], 0, 0, font[220 - 32], 0, 3, font[53].width(), font[53].height() ); fheroes2::Copy( font[220 - 32], 1, 1 + 3, font[220 - 32], 4, 0, 1, 1 ); fheroes2::Copy( font[220 - 32], 2, 1 + 3, font[220 - 32], 5, 0, 1, 1 ); fheroes2::Copy( font[220 - 32], 4, 0, font[220 - 32], 9, 0, 2, 1 ); font[220 - 32].setPosition( font[53].x(), font[53].y() - 3 ); updateNormalFontLetterShadow( font[220 - 32] ); // Eszett. font[223 - 32].resize( font[34].width(), font[34].height() + 3 ); font[223 - 32].reset(); fheroes2::Copy( font[34], 1, 0, font[223 - 32], 0, 3, font[34].width() - 1, font[34].height() ); fheroes2::FillTransform( font[223 - 32], 0, 0 + 3, 3, 10, 1 ); fheroes2::Copy( font[223 - 32], 0, 0 + 3, font[223 - 32], 3, 0 + 3, 1, 1 ); fheroes2::Copy( font[223 - 32], 3, 1 + 3, font[223 - 32], 4, 0 + 3, 1, 1 ); fheroes2::Copy( font[223 - 32], 4, 3 + 3, font[223 - 32], 3, 7 + 3, 1, 3 ); fheroes2::Copy( font[223 - 32], 4, 3 + 3, font[223 - 32], 2, 10 + 3, 1, 1 ); fheroes2::Copy( font[223 - 32], 3, 3 + 3, font[223 - 32], 2, 7 + 3, 1, 3 ); fheroes2::Copy( font[223 - 32], 3, 6 + 3, font[223 - 32], 1, 10 + 3, 1, 1 ); fheroes2::Copy( font[223 - 32], 7, 4 + 3, font[223 - 32], 4, 8 + 3, 1, 1 ); fheroes2::Copy( font[223 - 32], 7, 4 + 3, font[223 - 32], 3, 10 + 3, 1, 1 ); fheroes2::Copy( font[223 - 32], 8, 5 + 3, font[223 - 32], 4, 9 + 3, 1, 1 ); font[223 - 32].setPosition( font[34].x(), font[34].y() - 3 ); updateNormalFontLetterShadow( font[223 - 32] ); // a with grave accent `. font[224 - 32].resize( font[65].width(), font[65].height() + 3 ); font[224 - 32].reset(); fheroes2::Copy( font[65], 0, 0, font[224 - 32], 0, 3, font[65].width(), font[65].height() ); font[224 - 32].setPosition( font[65].x(), font[65].y() - 3 ); fheroes2::Copy( font[192 - 32], 7, 0, font[224 - 32], 3, 0, 4, 2 ); updateNormalFontLetterShadow( font[224 - 32] ); // a with acute accent. font[225 - 32].resize( font[65].width(), font[65].height() + 3 ); font[225 - 32].reset(); fheroes2::Copy( font[65], 0, 0, font[225 - 32], 0, 3, font[65].width(), font[65].height() ); fheroes2::Copy( font[193 - 32], 7, 0, font[225 - 32], 3, 0, 4, 2 ); font[225 - 32].setPosition( font[65].x(), font[65].y() - 3 ); updateNormalFontLetterShadow( font[225 - 32] ); // a with circumflex accent. font[226 - 32].resize( font[97 - 32].width(), font[97 - 32].height() + 3 ); font[226 - 32].reset(); fheroes2::Copy( font[97 - 32], 0, 0, font[226 - 32], 0, 3, font[97 - 32].width(), font[97 - 32].height() ); fheroes2::Copy( font[194 - 32], 7, 0, font[226 - 32], 3, 0, 3, 2 ); font[226 - 32].setPosition( font[97 - 32].x(), font[97 - 32].y() - 3 ); updateNormalFontLetterShadow( font[226 - 32] ); // a with tilde accent ~. font[227 - 32].resize( font[97 - 32].width(), font[97 - 32].height() + 4 ); font[227 - 32].reset(); fheroes2::Copy( font[97 - 32], 0, 0, font[227 - 32], 0, 4, font[97 - 32].width(), font[97 - 32].height() ); fheroes2::Copy( font[195 - 32], 4, 0, font[227 - 32], 1, 0, 8, 3 ); font[227 - 32].setPosition( font[97 - 32].x(), font[97 - 32].y() - 4 ); updateNormalFontLetterShadow( font[227 - 32] ); // a with 2 dots on top. font[228 - 32].resize( font[65].width(), font[65].height() + 3 ); font[228 - 32].reset(); fheroes2::Copy( font[65], 0, 0, font[228 - 32], 0, 3, font[65].width(), font[65].height() ); fheroes2::Copy( font[228 - 32], 3, 0 + 3, font[228 - 32], 3, 0, 1, 1 ); fheroes2::Copy( font[228 - 32], 2, 1 + 3, font[228 - 32], 3, 1, 1, 1 ); fheroes2::Copy( font[228 - 32], 2, 1 + 3, font[228 - 32], 2, 0, 1, 1 ); fheroes2::Copy( font[228 - 32], 1, 0 + 3, font[228 - 32], 2, 1, 1, 1 ); fheroes2::Copy( font[228 - 32], 2, 0, font[228 - 32], 5, 0, 2, 2 ); font[228 - 32].setPosition( font[65].x(), font[65].y() - 3 ); updateNormalFontLetterShadow( font[228 - 32] ); // a with circle on top. font[229 - 32].resize( font[65].width(), font[65].height() + 5 ); font[229 - 32].reset(); fheroes2::Copy( font[65], 0, 0, font[229 - 32], 0, 4, font[65].width(), font[65].height() ); fheroes2::Copy( font[197 - 32], 7, 0, font[229 - 32], 2, 0, 1, 3 ); fheroes2::Copy( font[197 - 32], 10, 0, font[229 - 32], 6, 0, 1, 3 ); fheroes2::Copy( font[65], 2, 0, font[229 - 32], 3, 0, 3, 1 ); fheroes2::Copy( font[65], 2, 0, font[229 - 32], 3, 2, 3, 1 ); fheroes2::Copy( font[69], 3, 2, font[229 - 32], 3, 1, 3, 1 ); font[229 - 32].setPosition( font[65].x(), font[65].y() - 4 ); updateNormalFontLetterShadow( font[229 - 32] ); // a attached to e. font[230 - 32].resize( font[65].width() + 4, font[65].height() ); font[230 - 32].reset(); fheroes2::Copy( font[65], 0, 0, font[230 - 32], 0, 0, font[65].width(), font[65].height() ); fheroes2::Copy( font[69], 3, 0, font[230 - 32], 8, 0, 6, 8 ); font[230 - 32].setPosition( font[65].x(), font[65].y() ); updateNormalFontLetterShadow( font[230 - 32] ); // c with cedilla. font[231 - 32].resize( font[67].width(), font[67].height() + 3 ); font[231 - 32].reset(); fheroes2::Copy( font[67], 0, 0, font[231 - 32], 0, 0, font[67].width(), font[67].height() ); fheroes2::Copy( font[199 - 32], 7, 11, font[231 - 32], 4, 7, 3, 3 ); font[231 - 32].setPosition( font[67].x(), font[67].y() ); updateNormalFontLetterShadow( font[231 - 32] ); // e with grave accent `. font[232 - 32].resize( font[69].width(), font[69].height() + 3 ); font[232 - 32].reset(); fheroes2::Copy( font[69], 0, 0, font[232 - 32], 0, 3, font[69].width(), font[69].height() ); font[232 - 32].setPosition( font[69].x(), font[69].y() - 3 ); fheroes2::Copy( font[192 - 32], 7, 0, font[232 - 32], 3, 0, 4, 2 ); updateNormalFontLetterShadow( font[232 - 32] ); // e with acute accent. font[233 - 32].resize( font[69].width(), font[69].height() + 3 ); font[233 - 32].reset(); fheroes2::Copy( font[69], 0, 0, font[233 - 32], 0, 3, font[69].width(), font[69].height() ); fheroes2::Copy( font[193 - 32], 7, 0, font[233 - 32], 3, 0, 4, 2 ); font[233 - 32].setPosition( font[69].x(), font[69].y() - 3 ); updateNormalFontLetterShadow( font[233 - 32] ); // e with circumflex accent. font[234 - 32].resize( font[101 - 32].width(), font[101 - 32].height() + 3 ); font[234 - 32].reset(); fheroes2::Copy( font[101 - 32], 0, 0, font[234 - 32], 0, 3, font[101 - 32].width(), font[101 - 32].height() ); fheroes2::Copy( font[194 - 32], 7, 0, font[234 - 32], 4, 0, 3, 2 ); font[234 - 32].setPosition( font[101 - 32].x(), font[101 - 32].y() - 3 ); updateNormalFontLetterShadow( font[234 - 32] ); // e with diaeresis. font[235 - 32].resize( font[69].width(), font[69].height() + 3 ); font[235 - 32].reset(); fheroes2::Copy( font[69], 0, 0, font[235 - 32], 0, 3, font[69].width(), font[69].height() ); font[235 - 32].setPosition( font[69].x(), font[69].y() - 3 ); updateNormalFontLetterShadow( font[235 - 32] ); // i with grave accent `. font[236 - 32] = font[73]; fheroes2::FillTransform( font[236 - 32], 0, 0, font[236 - 32].width(), 3, 1 ); fheroes2::Copy( font[192 - 32], 7, 0, font[236 - 32], 1, 0, 4, 2 ); updateNormalFontLetterShadow( font[236 - 32] ); // i with acute accent. font[237 - 32] = font[73]; fheroes2::FillTransform( font[237 - 32], 0, 0, font[237 - 32].width(), 3, 1 ); fheroes2::Copy( font[193 - 32], 7, 0, font[237 - 32], 1, 0, 4, 2 ); updateNormalFontLetterShadow( font[237 - 32] ); // i with circumflex. font[238 - 32] = font[73]; fheroes2::FillTransform( font[238 - 32], 0, 0, font[238 - 32].width(), 3, 1 ); fheroes2::Copy( font[194 - 32], 7, 0, font[238 - 32], 1, 0, 3, 2 ); updateNormalFontLetterShadow( font[238 - 32] ); // i with diaeresis. font[239 - 32] = font[73]; fheroes2::FillTransform( font[239 - 32], 0, 0, font[239 - 32].width(), 3, 1 ); fheroes2::Copy( font[193 - 32], 7, 0, font[239 - 32], 1, 0, 4, 2 ); updateNormalFontLetterShadow( font[239 - 32] ); // n with tilde ~. font[241 - 32].resize( font[110 - 32].width(), font[110 - 32].height() + 4 ); font[241 - 32].reset(); fheroes2::Copy( font[110 - 32], 0, 0, font[241 - 32], 0, 4, font[110 - 32].width(), font[110 - 32].height() ); fheroes2::Copy( font[195 - 32], 4, 0, font[241 - 32], 1, 0, 8, 3 ); font[241 - 32].setPosition( font[110 - 32].x(), font[110 - 32].y() - 4 ); updateNormalFontLetterShadow( font[241 - 32] ); // o with grave accent `. font[242 - 32].resize( font[79].width(), font[79].height() + 3 ); font[242 - 32].reset(); fheroes2::Copy( font[79], 0, 0, font[242 - 32], 0, 3, font[79].width(), font[79].height() ); font[242 - 32].setPosition( font[79].x(), font[79].y() - 3 ); fheroes2::Copy( font[192 - 32], 7, 0, font[242 - 32], 3, 0, 4, 2 ); updateNormalFontLetterShadow( font[242 - 32] ); // o with acute accent. font[243 - 32].resize( font[79].width(), font[79].height() + 3 ); font[243 - 32].reset(); fheroes2::Copy( font[79], 0, 0, font[243 - 32], 0, 3, font[79].width(), font[79].height() ); fheroes2::Copy( font[193 - 32], 7, 0, font[243 - 32], 3, 0, 4, 2 ); font[243 - 32].setPosition( font[79].x(), font[79].y() - 3 ); updateNormalFontLetterShadow( font[243 - 32] ); // o with circumflex accent. font[244 - 32].resize( font[111 - 32].width(), font[111 - 32].height() + 3 ); font[244 - 32].reset(); fheroes2::Copy( font[111 - 32], 0, 0, font[244 - 32], 0, 3, font[111 - 32].width(), font[111 - 32].height() ); fheroes2::Copy( font[194 - 32], 7, 0, font[244 - 32], 4, 0, 3, 2 ); font[244 - 32].setPosition( font[111 - 32].x(), font[111 - 32].y() - 3 ); updateNormalFontLetterShadow( font[244 - 32] ); // o with tilde accent ~. font[245 - 32].resize( font[111 - 32].width(), font[111 - 32].height() + 4 ); font[245 - 32].reset(); fheroes2::Copy( font[111 - 32], 0, 0, font[245 - 32], 0, 4, font[111 - 32].width(), font[111 - 32].height() ); fheroes2::Copy( font[195 - 32], 4, 0, font[245 - 32], 1, 0, 8, 3 ); font[245 - 32].setPosition( font[111 - 32].x(), font[111 - 32].y() - 4 ); updateNormalFontLetterShadow( font[245 - 32] ); // o with 2 dots on top. font[246 - 32].resize( font[79].width(), font[79].height() + 3 ); font[246 - 32].reset(); fheroes2::Copy( font[79], 0, 0, font[246 - 32], 0, 3, font[79].width(), font[79].height() ); fheroes2::Copy( font[246 - 32], 4, 0 + 3, font[246 - 32], 3, 0, 1, 1 ); fheroes2::Copy( font[246 - 32], 6, 1 + 3, font[246 - 32], 3, 1, 1, 1 ); fheroes2::Copy( font[246 - 32], 6, 1 + 3, font[246 - 32], 2, 0, 1, 1 ); fheroes2::Copy( font[246 - 32], 4, 1 + 3, font[246 - 32], 2, 1, 1, 1 ); fheroes2::Copy( font[246 - 32], 2, 0, font[246 - 32], 6, 0, 2, 2 ); font[246 - 32].setPosition( font[79].x(), font[79].y() - 3 ); updateNormalFontLetterShadow( font[246 - 32] ); // o with / inside. font[248 - 32].resize( font[79].width(), font[79].height() ); font[248 - 32].reset(); fheroes2::Copy( font[79], 0, 0, font[248 - 32], 0, 0, font[79].width(), font[79].height() ); fheroes2::Copy( font[88], 5, 0, font[248 - 32], 3, 2, 4, 4 ); fheroes2::Copy( font[79], 6, 5, font[248 - 32], 6, 5, 1, 1 ); fheroes2::Copy( font[88], 1, 6, font[248 - 32], 1, 6, 2, 1 ); fheroes2::Copy( font[88], 7, 0, font[248 - 32], 7, 0, 2, 1 ); font[248 - 32].setPosition( font[79].x(), font[79].y() ); updateNormalFontLetterShadow( font[248 - 32] ); // u with grave accent `. font[249 - 32].resize( font[85].width(), font[85].height() + 3 ); font[249 - 32].reset(); fheroes2::Copy( font[85], 0, 0, font[249 - 32], 0, 3, font[85].width(), font[85].height() ); font[249 - 32].setPosition( font[85].x(), font[85].y() - 3 ); fheroes2::Copy( font[192 - 32], 7, 0, font[249 - 32], 3, 0, 4, 2 ); updateNormalFontLetterShadow( font[249 - 32] ); // u with acute accent. font[250 - 32].resize( font[85].width(), font[85].height() + 3 ); font[250 - 32].reset(); fheroes2::Copy( font[85], 0, 0, font[250 - 32], 0, 3, font[85].width(), font[85].height() ); fheroes2::Copy( font[193 - 32], 7, 0, font[250 - 32], 3, 0, 4, 2 ); font[250 - 32].setPosition( font[85].x(), font[85].y() - 3 ); updateNormalFontLetterShadow( font[250 - 32] ); // u with circumflex. font[251 - 32].resize( font[85].width(), font[85].height() + 3 ); font[251 - 32].reset(); fheroes2::Copy( font[85], 0, 0, font[251 - 32], 0, 3, font[85].width(), font[85].height() ); fheroes2::Copy( font[194 - 32], 7, 0, font[251 - 32], 4, 0, 3, 2 ); font[251 - 32].setPosition( font[85].x(), font[85].y() - 3 ); updateNormalFontLetterShadow( font[251 - 32] ); // u with 2 dots on top. font[252 - 32].resize( font[85].width(), font[85].height() + 3 ); font[252 - 32].reset(); fheroes2::Copy( font[85], 0, 0, font[252 - 32], 0, 3, font[85].width(), font[85].height() ); fheroes2::Copy( font[252 - 32], 2, 0 + 3, font[252 - 32], 3, 0, 1, 1 ); fheroes2::Copy( font[252 - 32], 2, 1 + 3, font[252 - 32], 3, 1, 1, 1 ); fheroes2::Copy( font[252 - 32], 2, 1 + 3, font[252 - 32], 2, 0, 1, 1 ); fheroes2::Copy( font[252 - 32], 3, 6 + 3, font[252 - 32], 2, 1, 1, 1 ); fheroes2::Copy( font[252 - 32], 2, 0, font[252 - 32], 6, 0, 2, 2 ); font[252 - 32].setPosition( font[85].x(), font[85].y() - 3 ); updateNormalFontLetterShadow( font[252 - 32] ); } // Small font. { std::vector & font = icnVsSprite[ICN::SMALFONT]; // Inverted exclamation mark !. font[161 - 32].resize( font[33 - 32].width(), font[33 - 32].height() ); font[161 - 32].reset(); fheroes2::Copy( font[33 - 32], 1, 0, font[161 - 32], 1, 0, font[33 - 32].width(), 7 ); { fheroes2::Sprite temp = fheroes2::Flip( font[161 - 32], false, true ); fheroes2::Copy( temp, 0, 1, font[161 - 32], 0, 0, temp.width(), temp.height() ); } font[161 - 32].setPosition( font[33 - 32].x(), font[33 - 32].y() + 2 ); updateSmallFontLetterShadow( font[161 - 32] ); // Inverted question mark ?. font[191 - 32].resize( font[63 - 32].width(), font[63 - 32].height() ); font[191 - 32].reset(); fheroes2::Copy( font[63 - 32], 1, 0, font[191 - 32], 0, 0, font[63 - 32].width(), 7 ); { fheroes2::Sprite temp = fheroes2::Flip( font[191 - 32], true, true ); fheroes2::Copy( temp, 0, 1, font[191 - 32], 0, 0, temp.width(), temp.height() ); } // Remove old shadows fheroes2::FillTransform( font[191 - 32], 4, 1, 1, 1, 1 ); fheroes2::FillTransform( font[191 - 32], 3, 5, 2, 1, 1 ); fheroes2::FillTransform( font[191 - 32], 2, 4, 1, 1, 1 ); font[191 - 32].setPosition( font[63 - 32].x(), font[63 - 32].y() + 2 ); updateSmallFontLetterShadow( font[191 - 32] ); // A with grave accent `. Generate grave accent for further use. font[192 - 32].resize( font[33].width(), font[33].height() + 3 ); font[192 - 32].reset(); fheroes2::Copy( font[33], 0, 0, font[192 - 32], 0, 4, font[33].width(), font[33].height() ); fheroes2::Copy( font[192 - 32], 4, 4, font[192 - 32], 4, 0, 1, 1 ); fheroes2::Copy( font[192 - 32], 4, 4, font[192 - 32], 5, 1, 1, 1 ); fheroes2::Copy( font[192 - 32], 4, 4, font[192 - 32], 6, 2, 1, 1 ); font[192 - 32].setPosition( font[33].x(), font[33].y() - 4 ); updateSmallFontLetterShadow( font[192 - 32] ); // A with acute accent. Generate acute accent for further use font[193 - 32].resize( font[33].width(), font[33].height() + 4 ); font[193 - 32].reset(); fheroes2::Copy( font[33], 0, 0, font[193 - 32], 0, 4, font[33].width(), font[33].height() ); fheroes2::Copy( font[33], 4, 0, font[193 - 32], 4, 2, 1, 1 ); fheroes2::Copy( font[33], 4, 0, font[193 - 32], 5, 1, 1, 1 ); fheroes2::Copy( font[33], 4, 0, font[193 - 32], 6, 0, 1, 1 ); font[193 - 32].setPosition( font[33].x(), font[33].y() - 4 ); updateSmallFontLetterShadow( font[193 - 32] ); // A with circumflex accent. Generate accent for further use. font[194 - 32].resize( font[33].width(), font[33].height() + 3 ); font[194 - 32].reset(); fheroes2::Copy( font[33], 0, 0, font[194 - 32], 0, 3, font[33].width(), font[33].height() ); fheroes2::Copy( font[33], 4, 0, font[194 - 32], 4, 1, 1, 1 ); fheroes2::Copy( font[33], 4, 0, font[194 - 32], 5, 0, 1, 1 ); fheroes2::Copy( font[33], 4, 0, font[194 - 32], 6, 1, 1, 1 ); font[194 - 32].setPosition( font[33].x(), font[33].y() - 3 ); updateSmallFontLetterShadow( font[194 - 32] ); // A with tilde accent ~. Generate for further use. font[195 - 32].resize( font[65 - 32].width(), font[65 - 32].height() + 3 ); font[195 - 32].reset(); fheroes2::Copy( font[65 - 32], 0, 0, font[195 - 32], 0, 3, font[65 - 32].width(), font[65 - 32].height() ); fheroes2::Copy( font[37 - 32], 5, 4, font[195 - 32], 3, 0, 3, 2 ); fheroes2::Copy( font[37 - 32], 5, 4, font[195 - 32], 6, 1, 2, 2 ); font[195 - 32].setPosition( font[65 - 32].x(), font[65 - 32].y() - 3 ); updateSmallFontLetterShadow( font[195 - 32] ); // A with 2 dots on top. font[196 - 32].resize( font[33].width(), font[33].height() + 2 ); font[196 - 32].reset(); fheroes2::Copy( font[33], 0, 0, font[196 - 32], 0, 2, font[33].width(), font[33].height() ); fheroes2::Copy( font[196 - 32], 3, 0 + 2, font[196 - 32], 4, 0, 1, 1 ); fheroes2::Copy( font[196 - 32], 3, 0 + 2, font[196 - 32], 6, 0, 1, 1 ); font[196 - 32].setPosition( font[33].x(), font[33].y() - 2 ); updateSmallFontLetterShadow( font[196 - 32] ); // A with circle on top. font[197 - 32].resize( font[33].width(), font[33].height() + 4 ); font[197 - 32].reset(); fheroes2::Copy( font[33], 0, 0, font[197 - 32], 0, 2, font[33].width(), font[33].height() ); fheroes2::FillTransform( font[197 - 32], 3, 2, 3, 1, 1 ); fheroes2::FillTransform( font[197 - 32], 1, 2, 3, 3, 1 ); // Generate circle for further use. fheroes2::Copy( font[69], 2, 0, font[197 - 32], 5, 0, 1, 1 ); fheroes2::Copy( font[69], 2, 0, font[197 - 32], 6, 1, 1, 1 ); fheroes2::Copy( font[69], 2, 0, font[197 - 32], 5, 2, 1, 1 ); fheroes2::Copy( font[69], 2, 0, font[197 - 32], 4, 1, 1, 1 ); font[197 - 32].setPosition( font[33].x(), font[33].y() - 2 ); updateSmallFontLetterShadow( font[197 - 32] ); // A attached to E. font[198 - 32].resize( font[33].width() + 3, font[33].height() ); font[198 - 32].reset(); fheroes2::Copy( font[33], 0, 0, font[198 - 32], 0, 0, font[33].width(), font[33].height() ); fheroes2::Copy( font[37], 3, 0, font[198 - 32], 6, 0, 5, 4 ); fheroes2::Copy( font[37], 1, 0, font[198 - 32], 9, 5, 2, 2 ); font[198 - 32].setPosition( font[33].x(), font[33].y() ); updateSmallFontLetterShadow( font[198 - 32] ); // C with cedilla. font[199 - 32].resize( font[35].width(), font[35].height() + 3 ); font[199 - 32].reset(); fheroes2::Copy( font[35], 0, 0, font[199 - 32], 0, 0, font[35].width(), font[35].height() ); fheroes2::Copy( font[35], 1, 1, font[199 - 32], 3, 7, 2, 2 ); font[199 - 32].setPosition( font[35].x(), font[35].y() ); updateSmallFontLetterShadow( font[199 - 32] ); // E with grave accent `. font[200 - 32].resize( font[37].width(), font[37].height() + 4 ); font[200 - 32].reset(); fheroes2::Copy( font[37], 0, 0, font[200 - 32], 0, 4, font[37].width(), font[37].height() ); fheroes2::Copy( font[192 - 32], 4, 0, font[200 - 32], 4, 0, 3, 3 ); font[200 - 32].setPosition( font[37].x(), font[37].y() - 4 ); updateSmallFontLetterShadow( font[200 - 32] ); // E with acute accent. font[201 - 32].resize( font[37].width(), font[37].height() + 4 ); font[201 - 32].reset(); fheroes2::Copy( font[37], 0, 0, font[201 - 32], 0, 4, font[37].width(), font[37].height() ); fheroes2::Copy( font[193 - 32], 4, 0, font[201 - 32], 3, 0, 3, 3 ); font[201 - 32].setPosition( font[37].x(), font[37].y() - 4 ); updateSmallFontLetterShadow( font[201 - 32] ); // E with circumflex accent. font[202 - 32].resize( font[69 - 32].width(), font[69 - 32].height() + 3 ); font[202 - 32].reset(); fheroes2::Copy( font[69 - 32], 0, 0, font[202 - 32], 0, 3, font[69 - 32].width(), font[69 - 32].height() ); fheroes2::Copy( font[194 - 32], 4, 0, font[202 - 32], 3, 0, 3, 2 ); font[202 - 32].setPosition( font[69 - 32].x(), font[69 - 32].y() - 3 ); updateSmallFontLetterShadow( font[202 - 32] ); // I with grave accent `. font[204 - 32].resize( font[41].width(), font[41].height() + 4 ); font[204 - 32].reset(); fheroes2::Copy( font[41], 0, 0, font[204 - 32], 0, 4, font[41].width(), font[41].height() ); fheroes2::Copy( font[192 - 32], 4, 0, font[204 - 32], 1, 0, 3, 3 ); font[204 - 32].setPosition( font[41].x(), font[41].y() - 4 ); updateSmallFontLetterShadow( font[204 - 32] ); // I with acute accent. font[205 - 32].resize( font[41].width(), font[41].height() + 4 ); font[205 - 32].reset(); fheroes2::Copy( font[41], 0, 0, font[205 - 32], 0, 4, font[41].width(), font[41].height() ); fheroes2::Copy( font[193 - 32], 4, 0, font[205 - 32], 1, 0, 3, 3 ); font[205 - 32].setPosition( font[41].x(), font[41].y() - 4 ); updateSmallFontLetterShadow( font[205 - 32] ); // I with circumflex font[206 - 32].resize( font[73 - 32].width() + 1, font[73 - 32].height() + 3 ); font[206 - 32].reset(); fheroes2::Copy( font[73 - 32], 0, 0, font[206 - 32], 0, 3, font[73 - 32].width(), font[73 - 32].height() ); fheroes2::Copy( font[194 - 32], 4, 0, font[206 - 32], 2, 0, 3, 2 ); font[206 - 32].setPosition( font[73 - 32].x(), font[73 - 32].y() - 3 ); updateSmallFontLetterShadow( font[206 - 32] ); // N with tilde ~. font[209 - 32].resize( font[46].width(), font[46].height() + 3 ); font[209 - 32].reset(); fheroes2::Copy( font[46], 0, 0, font[209 - 32], 0, 4, font[46].width(), font[46].height() ); fheroes2::Copy( font[195 - 32], 3, 0, font[209 - 32], 4, 0, 5, 3 ); font[209 - 32].setPosition( font[46].x(), font[46].y() - 4 ); updateSmallFontLetterShadow( font[209 - 32] ); // O with grave accent `. font[210 - 32].resize( font[47].width(), font[47].height() + 4 ); font[210 - 32].reset(); fheroes2::Copy( font[47], 0, 0, font[210 - 32], 0, 4, font[47].width(), font[47].height() ); fheroes2::Copy( font[192 - 32], 4, 0, font[210 - 32], 4, 0, 3, 3 ); font[210 - 32].setPosition( font[47].x(), font[47].y() - 4 ); updateSmallFontLetterShadow( font[210 - 32] ); // O with acute accent. font[211 - 32].resize( font[47].width(), font[47].height() + 4 ); font[211 - 32].reset(); fheroes2::Copy( font[47], 0, 0, font[211 - 32], 0, 4, font[47].width(), font[47].height() ); fheroes2::Copy( font[193 - 32], 4, 0, font[211 - 32], 3, 0, 3, 3 ); font[211 - 32].setPosition( font[47].x(), font[47].y() - 4 ); updateSmallFontLetterShadow( font[211 - 32] ); // O with circumflex accent. font[212 - 32].resize( font[79 - 32].width(), font[79 - 32].height() + 3 ); font[212 - 32].reset(); fheroes2::Copy( font[79 - 32], 0, 0, font[212 - 32], 0, 3, font[79 - 32].width(), font[79 - 32].height() ); fheroes2::Copy( font[194 - 32], 4, 0, font[212 - 32], 3, 0, 3, 2 ); font[212 - 32].setPosition( font[79 - 32].x(), font[79 - 32].y() - 3 ); updateSmallFontLetterShadow( font[212 - 32] ); // O with tilde accent ~. font[213 - 32].resize( font[79 - 32].width(), font[79 - 32].height() + 3 ); font[213 - 32].reset(); fheroes2::Copy( font[79 - 32], 0, 0, font[213 - 32], 0, 3, font[79 - 32].width(), font[79 - 32].height() ); fheroes2::Copy( font[195 - 32], 3, 0, font[213 - 32], 2, 0, 5, 3 ); font[213 - 32].setPosition( font[79 - 32].x(), font[79 - 32].y() - 3 ); updateSmallFontLetterShadow( font[213 - 32] ); // O with 2 dots on top. font[214 - 32].resize( font[47].width(), font[47].height() + 2 ); font[214 - 32].reset(); fheroes2::Copy( font[47], 0, 0, font[214 - 32], 0, 2, font[47].width(), font[47].height() ); fheroes2::Copy( font[214 - 32], 3, 0 + 2, font[214 - 32], 3, 0, 1, 1 ); fheroes2::Copy( font[214 - 32], 3, 0 + 2, font[214 - 32], 5, 0, 1, 1 ); font[214 - 32].setPosition( font[47].x(), font[47].y() - 2 ); updateSmallFontLetterShadow( font[214 - 32] ); // O with / inside. font[216 - 32].resize( font[47].width(), font[47].height() ); font[216 - 32].reset(); fheroes2::Copy( font[47], 0, 0, font[216 - 32], 0, 0, font[47].width(), font[47].height() ); fheroes2::Copy( font[56], 6, 0, font[216 - 32], 3, 2, 3, 3 ); fheroes2::Copy( font[56], 1, 0, font[216 - 32], font[47].width() - 1, 0, 1, 1 ); fheroes2::Copy( font[56], 1, 0, font[216 - 32], 1, font[47].height() - 2, 1, 1 ); font[216 - 32].setPosition( font[47].x(), font[47].y() ); updateSmallFontLetterShadow( font[216 - 32] ); // U with grave accent `. font[217 - 32].resize( font[53].width(), font[53].height() + 4 ); font[217 - 32].reset(); fheroes2::Copy( font[53], 0, 0, font[217 - 32], 0, 4, font[53].width(), font[53].height() ); fheroes2::Copy( font[192 - 32], 4, 0, font[217 - 32], 4, 0, 3, 3 ); font[217 - 32].setPosition( font[53].x(), font[53].y() - 4 ); updateSmallFontLetterShadow( font[217 - 32] ); // U with acute accent. font[218 - 32].resize( font[53].width(), font[53].height() + 4 ); font[218 - 32].reset(); fheroes2::Copy( font[53], 0, 0, font[218 - 32], 0, 4, font[53].width(), font[53].height() ); fheroes2::Copy( font[193 - 32], 4, 0, font[218 - 32], 4, 0, 3, 3 ); font[218 - 32].setPosition( font[53].x(), font[53].y() - 4 ); updateSmallFontLetterShadow( font[218 - 32] ); // U with circumflex. font[219 - 32].resize( font[85 - 32].width(), font[85 - 32].height() + 3 ); font[219 - 32].reset(); fheroes2::Copy( font[85 - 32], 0, 0, font[219 - 32], 0, 3, font[85 - 32].width(), font[85 - 32].height() ); fheroes2::Copy( font[194 - 32], 4, 0, font[219 - 32], 4, 0, 3, 2 ); font[219 - 32].setPosition( font[85 - 32].x(), font[85 - 32].y() - 3 ); updateSmallFontLetterShadow( font[219 - 32] ); // U with 2 dots on top. font[220 - 32].resize( font[53].width(), font[53].height() + 2 ); font[220 - 32].reset(); fheroes2::Copy( font[53], 0, 0, font[220 - 32], 0, 2, font[53].width(), font[53].height() ); fheroes2::Copy( font[220 - 32], 3, 0 + 2, font[220 - 32], 4, 0, 1, 1 ); fheroes2::Copy( font[220 - 32], 3, 0 + 2, font[220 - 32], 6, 0, 1, 1 ); font[220 - 32].setPosition( font[53].x(), font[53].y() - 2 ); updateSmallFontLetterShadow( font[220 - 32] ); // Eszett. font[223 - 32].resize( font[34].width(), font[34].height() + 2 ); font[223 - 32].reset(); fheroes2::Copy( font[34], 0, 0, font[223 - 32], 0, 2, font[34].width(), font[34].height() ); fheroes2::FillTransform( font[223 - 32], 0, 0 + 2, 4, 9, 1 ); fheroes2::Copy( font[223 - 32], 6, 1 + 2, font[223 - 32], 2, 1 + 2, 1, 5 ); fheroes2::Copy( font[223 - 32], 4, 3 + 2, font[223 - 32], 2, 3 + 2, 2, 1 ); fheroes2::Copy( font[223 - 32], 5, 0 + 2, font[223 - 32], 1, 6 + 2, 1, 1 ); fheroes2::Copy( font[223 - 32], 5, 0 + 2, font[223 - 32], 3, 0 + 2, 1, 1 ); fheroes2::Copy( font[223 - 32], 5, 0 + 2, font[223 - 32], 3, 6 + 2, 1, 1 ); font[223 - 32].setPosition( font[34].x(), font[34].y() - 2 ); updateSmallFontLetterShadow( font[223 - 32] ); // a with grave accent `. font[224 - 32].resize( font[65].width(), font[65].height() + 3 ); font[224 - 32].reset(); fheroes2::Copy( font[65], 0, 0, font[224 - 32], 0, 3, font[65].width(), font[65].height() ); fheroes2::Copy( font[192 - 32], 4, 0, font[224 - 32], 3, 0, 3, 2 ); font[224 - 32].setPosition( font[65].x(), font[65].y() - 3 ); updateSmallFontLetterShadow( font[224 - 32] ); // a with acute accent. font[225 - 32].resize( font[65].width(), font[65].height() + 3 ); font[225 - 32].reset(); fheroes2::Copy( font[65], 0, 0, font[225 - 32], 0, 3, font[65].width(), font[65].height() ); fheroes2::Copy( font[193 - 32], 5, 0, font[225 - 32], 3, 0, 2, 2 ); font[225 - 32].setPosition( font[65].x(), font[65].y() - 3 ); updateSmallFontLetterShadow( font[225 - 32] ); // a with circumflex accent. font[226 - 32].resize( font[97 - 32].width(), font[97 - 32].height() + 3 ); font[226 - 32].reset(); fheroes2::Copy( font[97 - 32], 0, 0, font[226 - 32], 0, 3, font[97 - 32].width(), font[97 - 32].height() ); fheroes2::Copy( font[194 - 32], 4, 0, font[226 - 32], 2, 0, 3, 2 ); font[226 - 32].setPosition( font[97 - 32].x(), font[97 - 32].y() - 3 ); updateSmallFontLetterShadow( font[226 - 32] ); // a with tilde accent ~. font[227 - 32].resize( font[97 - 32].width(), font[97 - 32].height() + 4 ); font[227 - 32].reset(); fheroes2::Copy( font[97 - 32], 0, 0, font[227 - 32], 0, 4, font[97 - 32].width(), font[97 - 32].height() ); fheroes2::Copy( font[195 - 32], 3, 0, font[227 - 32], 1, 0, 5, 3 ); font[227 - 32].setPosition( font[97 - 32].x(), font[97 - 32].y() - 4 ); updateSmallFontLetterShadow( font[227 - 32] ); // a with 2 dots on top. font[228 - 32].resize( font[65].width(), font[65].height() + 2 ); font[228 - 32].reset(); fheroes2::Copy( font[65], 0, 0, font[228 - 32], 0, 2, font[65].width(), font[65].height() ); fheroes2::Copy( font[228 - 32], 3, 0 + 2, font[228 - 32], 2, 0, 1, 1 ); fheroes2::Copy( font[228 - 32], 3, 0 + 2, font[228 - 32], 5, 0, 1, 1 ); font[228 - 32].setPosition( font[65].x(), font[65].y() - 2 ); updateSmallFontLetterShadow( font[228 - 32] ); // a with circle on top. font[229 - 32].resize( font[65].width(), font[65].height() + 3 ); font[229 - 32].reset(); fheroes2::Copy( font[65], 0, 0, font[229 - 32], 0, 3, font[65].width(), font[65].height() ); fheroes2::Copy( font[197 - 32], 4, 0, font[229 - 32], 2, 0, 3, 3 ); font[229 - 32].setPosition( font[65].x(), font[65].y() - 3 ); updateSmallFontLetterShadow( font[229 - 32] ); // a attached to e. font[230 - 32].resize( font[65].width() + 3, font[65].height() ); font[230 - 32].reset(); fheroes2::Copy( font[65], 0, 0, font[230 - 32], 0, 0, font[65].width(), font[65].height() ); fheroes2::Copy( font[69], 2, 0, font[230 - 32], 6, 0, font[69].width() - 2, font[69].height() ); font[230 - 32].setPosition( font[65].x(), font[65].y() ); updateSmallFontLetterShadow( font[230 - 32] ); // c with cedilla. font[231 - 32].resize( font[67].width(), font[67].height() + 3 ); font[231 - 32].reset(); fheroes2::Copy( font[67], 0, 0, font[231 - 32], 0, 0, font[67].width(), font[67].height() ); fheroes2::Copy( font[199 - 32], 1, 1, font[231 - 32], 2, 5, 2, 2 ); font[231 - 32].setPosition( font[67].x(), font[67].y() ); updateSmallFontLetterShadow( font[231 - 32] ); // e with grave accent `. font[232 - 32].resize( font[69].width(), font[69].height() + 3 ); font[232 - 32].reset(); fheroes2::Copy( font[69], 0, 0, font[232 - 32], 0, 3, font[69].width(), font[69].height() ); fheroes2::Copy( font[192 - 32], 4, 0, font[232 - 32], 3, 0, 3, 2 ); font[232 - 32].setPosition( font[69].x(), font[69].y() - 3 ); updateSmallFontLetterShadow( font[232 - 32] ); // e with acute accent. font[233 - 32].resize( font[69].width(), font[69].height() + 3 ); font[233 - 32].reset(); fheroes2::Copy( font[69], 0, 0, font[233 - 32], 0, 3, font[69].width(), font[69].height() ); fheroes2::Copy( font[193 - 32], 5, 0, font[233 - 32], 3, 0, 2, 2 ); font[233 - 32].setPosition( font[69].x(), font[69].y() - 3 ); updateSmallFontLetterShadow( font[233 - 32] ); // e with circumflex accent. font[234 - 32].resize( font[101 - 32].width(), font[101 - 32].height() + 3 ); font[234 - 32].reset(); fheroes2::Copy( font[101 - 32], 0, 0, font[234 - 32], 0, 3, font[101 - 32].width(), font[101 - 32].height() ); fheroes2::Copy( font[194 - 32], 4, 0, font[234 - 32], 2, 0, 3, 2 ); font[234 - 32].setPosition( font[101 - 32].x(), font[101 - 32].y() - 3 ); updateSmallFontLetterShadow( font[234 - 32] ); // i with grave accent `. font[236 - 32].resize( font[73].width(), font[73].height() + 1 ); font[236 - 32].reset(); fheroes2::Copy( font[73], 0, 2, font[236 - 32], 0, 3, font[73].width(), 6 ); fheroes2::Copy( font[192 - 32], 4, 0, font[236 - 32], 2, 0, 2, 2 ); font[236 - 32].setPosition( font[73].x(), font[73].y() - 1 ); updateSmallFontLetterShadow( font[236 - 32] ); // i with acute accent. font[237 - 32].resize( font[73].width(), font[73].height() + 1 ); font[237 - 32].reset(); fheroes2::Copy( font[73], 0, 2, font[237 - 32], 0, 3, font[73].width(), 6 ); fheroes2::Copy( font[193 - 32], 5, 0, font[237 - 32], 1, 0, 2, 2 ); font[237 - 32].setPosition( font[73].x(), font[73].y() - 1 ); updateSmallFontLetterShadow( font[237 - 32] ); // i with circumflex. font[238 - 32].resize( font[105 - 32].width(), font[105 - 32].height() + 1 ); font[238 - 32].reset(); fheroes2::Copy( font[105 - 32], 0, 2, font[238 - 32], 0, 3, font[105 - 32].width(), font[105 - 32].height() ); fheroes2::Copy( font[194 - 32], 4, 0, font[238 - 32], 1, 0, 3, 2 ); font[238 - 32].setPosition( font[105 - 32].x(), font[105 - 32].y() - 1 ); updateSmallFontLetterShadow( font[238 - 32] ); // n with tilde ~. font[241 - 32].resize( font[110 - 32].width(), font[110 - 32].height() + 4 ); font[241 - 32].reset(); fheroes2::Copy( font[110 - 32], 0, 0, font[241 - 32], 0, 4, font[110 - 32].width(), font[110 - 32].height() ); fheroes2::Copy( font[195 - 32], 3, 0, font[241 - 32], 2, 0, 5, 3 ); font[241 - 32].setPosition( font[110 - 32].x(), font[110 - 32].y() - 4 ); updateSmallFontLetterShadow( font[241 - 32] ); // o with grave accent `. font[242 - 32].resize( font[79].width(), font[79].height() + 3 ); font[242 - 32].reset(); fheroes2::Copy( font[79], 0, 0, font[242 - 32], 0, 3, font[79].width(), font[79].height() ); fheroes2::Copy( font[192 - 32], 4, 0, font[242 - 32], 3, 0, 3, 2 ); font[242 - 32].setPosition( font[79].x(), font[79].y() - 3 ); updateSmallFontLetterShadow( font[242 - 32] ); // o with acute accent. font[243 - 32].resize( font[79].width(), font[79].height() + 3 ); font[243 - 32].reset(); fheroes2::Copy( font[79], 0, 0, font[243 - 32], 0, 3, font[79].width(), font[79].height() ); fheroes2::Copy( font[193 - 32], 5, 0, font[243 - 32], 3, 0, 2, 2 ); font[243 - 32].setPosition( font[79].x(), font[79].y() - 3 ); updateSmallFontLetterShadow( font[243 - 32] ); // o with circumflex accent. font[244 - 32].resize( font[111 - 32].width(), font[111 - 32].height() + 3 ); font[244 - 32].reset(); fheroes2::Copy( font[111 - 32], 0, 0, font[244 - 32], 0, 3, font[111 - 32].width(), font[111 - 32].height() ); fheroes2::Copy( font[194 - 32], 4, 0, font[244 - 32], 2, 0, 3, 2 ); font[244 - 32].setPosition( font[111 - 32].x(), font[111 - 32].y() - 3 ); updateSmallFontLetterShadow( font[244 - 32] ); // o with tilde accent ~. font[245 - 32].resize( font[111 - 32].width(), font[111 - 32].height() + 4 ); font[245 - 32].reset(); fheroes2::Copy( font[111 - 32], 0, 0, font[245 - 32], 0, 4, font[111 - 32].width(), font[111 - 32].height() ); fheroes2::Copy( font[195 - 32], 3, 0, font[245 - 32], 1, 0, 5, 3 ); font[245 - 32].setPosition( font[111 - 32].x(), font[111 - 32].y() - 4 ); updateSmallFontLetterShadow( font[245 - 32] ); // o with 2 dots on top. font[246 - 32].resize( font[79].width(), font[79].height() + 2 ); font[246 - 32].reset(); fheroes2::Copy( font[79], 0, 0, font[246 - 32], 0, 2, font[79].width(), font[79].height() ); fheroes2::Copy( font[246 - 32], 3, 0 + 2, font[246 - 32], 2, 0, 1, 1 ); fheroes2::Copy( font[246 - 32], 3, 0 + 2, font[246 - 32], 4, 0, 1, 1 ); font[246 - 32].setPosition( font[79].x(), font[79].y() - 2 ); updateSmallFontLetterShadow( font[246 - 32] ); // o with / inside. font[248 - 32].resize( font[79].width(), font[79].height() ); font[248 - 32].reset(); fheroes2::Copy( font[79], 0, 0, font[248 - 32], 0, 0, font[79].width(), font[79].height() ); fheroes2::Copy( font[56], 6, 0, font[248 - 32], 2, 1, 3, 3 ); fheroes2::Copy( font[56], 1, 0, font[248 - 32], font[79].width() - 1, 0, 1, 1 ); fheroes2::Copy( font[56], 1, 0, font[248 - 32], 1, font[79].height() - 2, 1, 1 ); font[248 - 32].setPosition( font[79].x(), font[79].y() ); updateSmallFontLetterShadow( font[248 - 32] ); // u with grave accent `. font[249 - 32].resize( font[85].width(), font[85].height() + 3 ); font[249 - 32].reset(); fheroes2::Copy( font[85], 0, 0, font[249 - 32], 0, 3, font[85].width(), font[85].height() ); fheroes2::Copy( font[192 - 32], 4, 0, font[249 - 32], 3, 0, 3, 2 ); font[249 - 32].setPosition( font[85].x(), font[85].y() - 3 ); updateSmallFontLetterShadow( font[249 - 32] ); // u with acute accent. font[250 - 32] = font[85]; font[250 - 32].resize( font[85].width(), font[85].height() + 3 ); font[250 - 32].reset(); fheroes2::Copy( font[85], 0, 0, font[250 - 32], 0, 3, font[85].width(), font[85].height() ); fheroes2::Copy( font[193 - 32], 5, 0, font[250 - 32], 3, 0, 2, 2 ); font[250 - 32].setPosition( font[85].x(), font[85].y() - 3 ); updateSmallFontLetterShadow( font[250 - 32] ); // u with circumflex accent. font[251 - 32].resize( font[117 - 32].width(), font[117 - 32].height() + 3 ); font[251 - 32].reset(); fheroes2::Copy( font[117 - 32], 0, 0, font[251 - 32], 0, 3, font[117 - 32].width(), font[117 - 32].height() ); fheroes2::Copy( font[194 - 32], 4, 0, font[251 - 32], 3, 0, 3, 2 ); font[251 - 32].setPosition( font[117 - 32].x(), font[117 - 32].y() - 3 ); updateSmallFontLetterShadow( font[251 - 32] ); // u with 2 dots on top. font[252 - 32].resize( font[85].width(), font[85].height() + 2 ); font[252 - 32].reset(); fheroes2::Copy( font[85], 0, 0, font[252 - 32], 0, 2, font[85].width(), font[85].height() ); fheroes2::Copy( font[252 - 32], 2, 0 + 2, font[252 - 32], 2, 0, 1, 1 ); fheroes2::Copy( font[252 - 32], 2, 0 + 2, font[252 - 32], 6, 0, 1, 1 ); font[252 - 32].setPosition( font[85].x(), font[85].y() - 2 ); updateSmallFontLetterShadow( font[252 - 32] ); } } void generateCP1254Alphabet( std::vector> & icnVsSprite ) { // Resize fonts. for ( const int icnId : { ICN::FONT, ICN::SMALFONT } ) { icnVsSprite[icnId].resize( baseFontSize ); const fheroes2::Sprite firstSprite{ icnVsSprite[icnId][0] }; icnVsSprite[icnId].insert( icnVsSprite[icnId].end(), 160, firstSprite ); } // Normal font. { std::vector & font = icnVsSprite[ICN::FONT]; // C with cedilla. font[199 - 32].resize( font[35].width(), font[35].height() + 3 ); font[199 - 32].reset(); fheroes2::Copy( font[35], 0, 0, font[199 - 32], 0, 0, font[35].width(), font[35].height() ); fheroes2::Copy( font[67], 2, 1, font[199 - 32], 7, 11, 1, 1 ); fheroes2::Copy( font[67], 5, 6, font[199 - 32], 8, 11, 1, 1 ); fheroes2::Copy( font[67], 2, 6, font[199 - 32], 9, 11, 1, 1 ); fheroes2::Copy( font[67], 2, 1, font[199 - 32], 8, 12, 1, 1 ); fheroes2::Copy( font[67], 5, 6, font[199 - 32], 9, 12, 1, 1 ); fheroes2::Copy( font[67], 2, 1, font[199 - 32], 7, 13, 1, 1 ); fheroes2::Copy( font[67], 5, 6, font[199 - 32], 8, 13, 1, 1 ); fheroes2::Copy( font[67], 3, 0, font[199 - 32], 9, 13, 1, 1 ); font[199 - 32].setPosition( font[35].x(), font[35].y() ); updateNormalFontLetterShadow( font[199 - 32] ); // G with breve. font[208 - 32].resize( font[39].width(), font[39].height() + 3 ); font[208 - 32].reset(); fheroes2::Copy( font[39], 0, 0, font[208 - 32], 0, 3, font[39].width(), font[39].height() ); fheroes2::Copy( font[39], 5, 9, font[208 - 32], 5, 0, 7, 2 ); fheroes2::FillTransform( font[208 - 32], 7, 0, 3, 1, 1 ); font[208 - 32].setPosition( font[39].x(), font[39].y() - 3 ); updateNormalFontLetterShadow( font[208 - 32] ); // O with diaeresis, two dots above. font[214 - 32].resize( font[47].width(), font[47].height() + 3 ); font[214 - 32].reset(); fheroes2::Copy( font[47], 0, 0, font[214 - 32], 0, 3, font[47].width(), font[47].height() ); fheroes2::Copy( font[214 - 32], 1, 2 + 3, font[214 - 32], 5, 0, 1, 1 ); fheroes2::Copy( font[214 - 32], 2, 2 + 3, font[214 - 32], 6, 0, 1, 1 ); fheroes2::Copy( font[214 - 32], 5, 0, font[214 - 32], 10, 0, 2, 1 ); font[214 - 32].setPosition( font[47].x(), font[47].y() - 3 ); updateNormalFontLetterShadow( font[214 - 32] ); // U with diaeresis. font[220 - 32].resize( font[53].width(), font[53].height() + 3 ); font[220 - 32].reset(); fheroes2::Copy( font[53], 0, 0, font[220 - 32], 0, 3, font[53].width(), font[53].height() ); fheroes2::Copy( font[220 - 32], 1, 1 + 3, font[220 - 32], 4, 0, 1, 1 ); fheroes2::Copy( font[220 - 32], 2, 1 + 3, font[220 - 32], 5, 0, 1, 1 ); fheroes2::Copy( font[220 - 32], 4, 0, font[220 - 32], 9, 0, 2, 1 ); font[220 - 32].setPosition( font[53].x(), font[53].y() - 3 ); updateNormalFontLetterShadow( font[220 - 32] ); // I with dot above. font[221 - 32].resize( font[41].width(), font[41].height() + 3 ); font[221 - 32].reset(); fheroes2::Copy( font[41], 0, 0, font[221 - 32], 0, 3, font[41].width(), font[41].height() ); fheroes2::Copy( font[73], 1, 0, font[221 - 32], 3, 0, 3, 2 ); font[221 - 32].setPosition( font[41].x(), font[41].y() - 3 ); updateNormalFontLetterShadow( font[221 - 32] ); // S with cedilla. font[222 - 32].resize( font[51].width(), font[51].height() + 3 ); font[222 - 32].reset(); fheroes2::Copy( font[51], 0, 0, font[222 - 32], 0, 0, font[51].width(), font[51].height() ); fheroes2::Copy( font[199 - 32], 7, 11, font[222 - 32], 5, 11, 3, 3 ); font[222 - 32].setPosition( font[51].x(), font[51].y() ); updateNormalFontLetterShadow( font[222 - 32] ); // c with cedilla. font[231 - 32].resize( font[67].width(), font[67].height() + 3 ); font[231 - 32].reset(); fheroes2::Copy( font[67], 0, 0, font[231 - 32], 0, 0, font[67].width(), font[67].height() ); fheroes2::Copy( font[199 - 32], 7, 11, font[231 - 32], 4, 7, 3, 3 ); font[231 - 32].setPosition( font[67].x(), font[67].y() ); updateNormalFontLetterShadow( font[231 - 32] ); // g with breve. font[240 - 32].resize( font[71].width(), font[71].height() + 3 ); font[240 - 32].reset(); fheroes2::Copy( font[71], 0, 0, font[240 - 32], 0, 3, font[71].width(), font[71].height() ); fheroes2::Copy( font[79], 2, 5, font[240 - 32], 2, 0, 6, 2 ); fheroes2::FillTransform( font[240 - 32], 4, 0, 2, 1, 1 ); font[240 - 32].setPosition( font[71].x(), font[71].y() - 3 ); updateNormalFontLetterShadow( font[240 - 32] ); // o with diaeresis, two dots above. font[246 - 32].resize( font[79].width(), font[79].height() + 3 ); font[246 - 32].reset(); fheroes2::Copy( font[79], 0, 0, font[246 - 32], 0, 3, font[79].width(), font[79].height() ); fheroes2::Copy( font[246 - 32], 4, 0 + 3, font[246 - 32], 3, 0, 1, 1 ); fheroes2::Copy( font[246 - 32], 6, 1 + 3, font[246 - 32], 3, 1, 1, 1 ); fheroes2::Copy( font[246 - 32], 6, 1 + 3, font[246 - 32], 2, 0, 1, 1 ); fheroes2::Copy( font[246 - 32], 4, 1 + 3, font[246 - 32], 2, 1, 1, 1 ); fheroes2::Copy( font[246 - 32], 2, 0, font[246 - 32], 6, 0, 2, 2 ); font[246 - 32].setPosition( font[79].x(), font[79].y() - 3 ); updateNormalFontLetterShadow( font[246 - 32] ); // u with diaeresis. font[252 - 32].resize( font[85].width(), font[85].height() + 3 ); font[252 - 32].reset(); fheroes2::Copy( font[85], 0, 0, font[252 - 32], 0, 3, font[85].width(), font[85].height() ); fheroes2::Copy( font[252 - 32], 2, 0 + 3, font[252 - 32], 3, 0, 1, 1 ); fheroes2::Copy( font[252 - 32], 2, 1 + 3, font[252 - 32], 3, 1, 1, 1 ); fheroes2::Copy( font[252 - 32], 2, 1 + 3, font[252 - 32], 2, 0, 1, 1 ); fheroes2::Copy( font[252 - 32], 3, 6 + 3, font[252 - 32], 2, 1, 1, 1 ); fheroes2::Copy( font[252 - 32], 2, 0, font[252 - 32], 6, 0, 2, 2 ); font[252 - 32].setPosition( font[85].x(), font[85].y() - 3 ); updateNormalFontLetterShadow( font[252 - 32] ); // i without dot above. font[253 - 32] = font[73]; fheroes2::FillTransform( font[253 - 32], 0, 0, font[253 - 32].width(), 3, 1 ); fheroes2::Copy( font[83], 2, 0, font[253 - 32], 0, 3, 1, 1 ); updateNormalFontLetterShadow( font[253 - 32] ); // s with cedilla. font[254 - 32].resize( font[83].width(), font[83].height() + 3 ); font[254 - 32].reset(); fheroes2::Copy( font[83], 0, 0, font[254 - 32], 0, 0, font[83].width(), font[83].height() ); fheroes2::Copy( font[199 - 32], 7, 11, font[254 - 32], 4, 7, 3, 3 ); font[254 - 32].setPosition( font[83].x(), font[83].y() ); updateNormalFontLetterShadow( font[254 - 32] ); } // Small font. { std::vector & font = icnVsSprite[ICN::SMALFONT]; // C with cedilla. font[199 - 32].resize( font[35].width(), font[35].height() + 3 ); font[199 - 32].reset(); fheroes2::Copy( font[35], 0, 0, font[199 - 32], 0, 0, font[35].width(), font[35].height() ); fheroes2::Copy( font[35], 1, 1, font[199 - 32], 3, 7, 2, 2 ); font[199 - 32].setPosition( font[35].x(), font[35].y() ); updateSmallFontLetterShadow( font[199 - 32] ); // G with breve. font[208 - 32].resize( font[39].width(), font[39].height() + 3 ); font[208 - 32].reset(); fheroes2::Copy( font[39], 0, 0, font[208 - 32], 0, 3, font[39].width(), font[39].height() ); fheroes2::Copy( font[35], 2, 5, font[208 - 32], 3, 0, 1, 1 ); fheroes2::Copy( font[35], 2, 5, font[208 - 32], 6, 0, 1, 1 ); fheroes2::Copy( font[35], 3, 6, font[208 - 32], 4, 1, 2, 1 ); font[208 - 32].setPosition( font[39].x(), font[39].y() - 3 ); updateSmallFontLetterShadow( font[208 - 32] ); // O with diaeresis, two dots above. font[214 - 32].resize( font[47].width(), font[47].height() + 2 ); font[214 - 32].reset(); fheroes2::Copy( font[47], 0, 0, font[214 - 32], 0, 2, font[47].width(), font[47].height() ); fheroes2::Copy( font[214 - 32], 3, 0 + 2, font[214 - 32], 3, 0, 1, 1 ); fheroes2::Copy( font[214 - 32], 3, 0 + 2, font[214 - 32], 5, 0, 1, 1 ); font[214 - 32].setPosition( font[47].x(), font[47].y() - 2 ); updateSmallFontLetterShadow( font[214 - 32] ); // U with diaeresis. font[220 - 32].resize( font[53].width(), font[53].height() + 2 ); font[220 - 32].reset(); fheroes2::Copy( font[53], 0, 0, font[220 - 32], 0, 2, font[53].width(), font[53].height() ); fheroes2::Copy( font[220 - 32], 3, 0 + 2, font[220 - 32], 4, 0, 1, 1 ); fheroes2::Copy( font[220 - 32], 3, 0 + 2, font[220 - 32], 6, 0, 1, 1 ); font[220 - 32].setPosition( font[53].x(), font[53].y() - 2 ); updateSmallFontLetterShadow( font[220 - 32] ); // I with dot above. font[221 - 32].resize( font[41].width(), font[41].height() + 2 ); font[221 - 32].reset(); fheroes2::Copy( font[41], 0, 0, font[221 - 32], 0, 2, font[41].width(), font[41].height() ); fheroes2::Copy( font[41], 2, 0, font[221 - 32], 2, 0, 2, 1 ); font[221 - 32].setPosition( font[41].x(), font[41].y() - 2 ); updateSmallFontLetterShadow( font[221 - 32] ); // S with cedilla. font[222 - 32].resize( font[51].width(), font[51].height() + 3 ); font[222 - 32].reset(); fheroes2::Copy( font[51], 0, 0, font[222 - 32], 0, 0, font[51].width(), font[51].height() ); fheroes2::Copy( font[35], 1, 1, font[222 - 32], 3, 7, 2, 2 ); font[222 - 32].setPosition( font[51].x(), font[51].y() ); updateSmallFontLetterShadow( font[222 - 32] ); // c with cedilla. font[231 - 32].resize( font[67].width(), font[67].height() + 3 ); font[231 - 32].reset(); fheroes2::Copy( font[67], 0, 0, font[231 - 32], 0, 0, font[67].width(), font[67].height() ); fheroes2::Copy( font[199 - 32], 1, 1, font[231 - 32], 2, 5, 2, 2 ); font[231 - 32].setPosition( font[67].x(), font[67].y() ); updateSmallFontLetterShadow( font[231 - 32] ); // g with breve. font[240 - 32].resize( font[71].width(), font[71].height() + 3 ); font[240 - 32].reset(); fheroes2::Copy( font[71], 0, 0, font[240 - 32], 0, 3, font[71].width(), font[71].height() ); fheroes2::Copy( font[35], 2, 5, font[240 - 32], 2, 0, 1, 1 ); fheroes2::Copy( font[35], 2, 5, font[240 - 32], 4, 0, 1, 1 ); fheroes2::Copy( font[35], 3, 6, font[240 - 32], 3, 1, 1, 1 ); font[240 - 32].setPosition( font[71].x(), font[71].y() - 3 ); updateSmallFontLetterShadow( font[240 - 32] ); // o with diaeresis, two dots above. font[246 - 32].resize( font[79].width(), font[79].height() + 2 ); font[246 - 32].reset(); fheroes2::Copy( font[79], 0, 0, font[246 - 32], 0, 2, font[79].width(), font[79].height() ); fheroes2::Copy( font[246 - 32], 3, 0 + 2, font[246 - 32], 2, 0, 1, 1 ); fheroes2::Copy( font[246 - 32], 3, 0 + 2, font[246 - 32], 4, 0, 1, 1 ); font[246 - 32].setPosition( font[79].x(), font[79].y() - 2 ); updateSmallFontLetterShadow( font[246 - 32] ); // u with diaeresis. font[252 - 32].resize( font[85].width(), font[85].height() + 2 ); font[252 - 32].reset(); fheroes2::Copy( font[85], 0, 0, font[252 - 32], 0, 2, font[85].width(), font[85].height() ); fheroes2::Copy( font[252 - 32], 2, 0 + 2, font[252 - 32], 2, 0, 1, 1 ); fheroes2::Copy( font[252 - 32], 2, 0 + 2, font[252 - 32], 6, 0, 1, 1 ); font[252 - 32].setPosition( font[85].x(), font[85].y() - 2 ); updateSmallFontLetterShadow( font[252 - 32] ); // i without dot above. font[253 - 32].resize( font[73].width(), font[73].height() + 1 ); font[253 - 32].reset(); fheroes2::Copy( font[73], 0, 2, font[253 - 32], 0, 3, font[73].width(), 6 ); font[253 - 32].setPosition( font[73].x(), font[73].y() - 1 ); updateSmallFontLetterShadow( font[253 - 32] ); // s with cedilla. font[254 - 32].resize( font[83].width(), font[83].height() + 3 ); font[254 - 32].reset(); fheroes2::Copy( font[83], 0, 0, font[254 - 32], 0, 0, font[83].width(), font[83].height() ); fheroes2::Copy( font[35], 1, 1, font[254 - 32], 2, 5, 2, 2 ); font[254 - 32].setPosition( font[83].x(), font[83].y() ); updateSmallFontLetterShadow( font[254 - 32] ); } } void generateCP1258Alphabet( std::vector> & icnVsSprite ) { // Resize fonts. for ( const int icnId : { ICN::FONT, ICN::SMALFONT } ) { icnVsSprite[icnId].resize( baseFontSize ); const fheroes2::Sprite firstSprite{ icnVsSprite[icnId][0] }; icnVsSprite[icnId].insert( icnVsSprite[icnId].end(), 160, firstSprite ); } // Normal font. { std::vector & font = icnVsSprite[ICN::FONT]; // A with grave accent ` and generate the grave accent for further use. font[192 - 32].resize( font[33].width(), font[33].height() + 3 ); font[192 - 32].reset(); fheroes2::Copy( font[33], 0, 0, font[192 - 32], 0, 3, font[33].width(), font[33].height() ); font[192 - 32].setPosition( font[33].x(), font[33].y() - 3 ); fheroes2::Copy( font[192 - 32], 3, 4, font[192 - 32], 7, 0, 1, 1 ); fheroes2::Copy( font[192 - 32], 4, 4, font[192 - 32], 8, 0, 1, 1 ); fheroes2::Copy( font[192 - 32], 3, 4, font[192 - 32], 8, 1, 1, 1 ); fheroes2::Copy( font[192 - 32], 4, 4, font[192 - 32], 9, 1, 1, 1 ); fheroes2::Copy( font[192 - 32], 3, 3, font[192 - 32], 10, 1, 1, 1 ); updateNormalFontLetterShadow( font[192 - 32] ); // A with acute accent. Generate the accent for further use. font[193 - 32].resize( font[33].width(), font[33].height() + 3 ); font[193 - 32].reset(); fheroes2::Copy( font[33], 0, 0, font[193 - 32], 0, 3, font[33].width(), font[33].height() ); fheroes2::Copy( font[193 - 32], 3, 4, font[193 - 32], 10, 0, 1, 1 ); fheroes2::Copy( font[193 - 32], 4, 4, font[193 - 32], 9, 0, 1, 1 ); fheroes2::Copy( font[193 - 32], 3, 4, font[193 - 32], 9, 1, 1, 1 ); fheroes2::Copy( font[193 - 32], 4, 4, font[193 - 32], 8, 1, 1, 1 ); fheroes2::Copy( font[193 - 32], 3, 3, font[193 - 32], 7, 1, 1, 1 ); font[193 - 32].setPosition( font[33].x(), font[33].y() - 3 ); updateNormalFontLetterShadow( font[193 - 32] ); // A with circumflex accent. Generation of accent for further use. font[194 - 32].resize( font[65 - 32].width(), font[65 - 32].height() + 3 ); font[194 - 32].reset(); fheroes2::Copy( font[65 - 32], 0, 0, font[194 - 32], 0, 3, font[65 - 32].width(), font[65 - 32].height() ); fheroes2::Copy( font[97 - 32], 7, 1, font[194 - 32], 7, 1, 1, 1 ); fheroes2::Copy( font[97 - 32], 7, 1, font[194 - 32], 8, 0, 1, 1 ); fheroes2::Copy( font[97 - 32], 7, 1, font[194 - 32], 9, 1, 1, 1 ); fheroes2::Copy( font[97 - 32], 1, 0, font[194 - 32], 7, 0, 1, 1 ); fheroes2::Copy( font[97 - 32], 1, 0, font[194 - 32], 9, 0, 1, 1 ); font[194 - 32].setPosition( font[65 - 32].x(), font[65 - 32].y() - 3 ); updateNormalFontLetterShadow( font[194 - 32] ); // A with breve and generate the accent for further use. font[195 - 32].resize( font[33].width(), font[33].height() + 3 ); font[195 - 32].reset(); fheroes2::Copy( font[33], 0, 0, font[195 - 32], 0, 3, font[33].width(), font[33].height() ); fheroes2::Copy( font[39], 5, 9, font[195 - 32], 5, 0, 7, 2 ); fheroes2::FillTransform( font[195 - 32], 7, 0, 3, 1, 1 ); font[195 - 32].setPosition( font[33].x(), font[33].y() - 3 ); updateNormalFontLetterShadow( font[195 - 32] ); // E with grave accent `. font[200 - 32].resize( font[37].width(), font[37].height() + 3 ); font[200 - 32].reset(); fheroes2::Copy( font[37], 0, 0, font[200 - 32], 0, 3, font[37].width(), font[37].height() ); font[200 - 32].setPosition( font[37].x(), font[37].y() - 3 ); fheroes2::Copy( font[192 - 32], 7, 0, font[200 - 32], 4, 0, 4, 2 ); updateNormalFontLetterShadow( font[200 - 32] ); // E with acute accent. font[201 - 32].resize( font[37].width(), font[37].height() + 3 ); font[201 - 32].reset(); fheroes2::Copy( font[37], 0, 0, font[201 - 32], 0, 3, font[37].width(), font[37].height() ); fheroes2::Copy( font[193 - 32], 7, 0, font[201 - 32], 5, 0, 4, 2 ); font[201 - 32].setPosition( font[37].x(), font[37].y() - 3 ); updateNormalFontLetterShadow( font[201 - 32] ); // E with circumflex accent. font[202 - 32].resize( font[69 - 32].width(), font[69 - 32].height() + 3 ); font[202 - 32].reset(); fheroes2::Copy( font[69 - 32], 0, 0, font[202 - 32], 0, 3, font[69 - 32].width(), font[69 - 32].height() ); fheroes2::Copy( font[194 - 32], 7, 0, font[202 - 32], 5, 0, 3, 2 ); font[202 - 32].setPosition( font[69 - 32].x(), font[69 - 32].y() - 3 ); updateNormalFontLetterShadow( font[202 - 32] ); // [204 - 32] COMBINING GRAVE TONE MARK `. // I with accute accent. font[205 - 32].resize( font[41].width(), font[41].height() + 3 ); font[205 - 32].reset(); fheroes2::Copy( font[41], 0, 0, font[205 - 32], 0, 3, font[41].width(), font[41].height() ); fheroes2::Copy( font[193 - 32], 7, 0, font[205 - 32], 3, 0, 4, 2 ); font[205 - 32].setPosition( font[41].x(), font[41].y() - 3 ); updateNormalFontLetterShadow( font[205 - 32] ); // D with stroke. Needs stroke generated. font[208 - 32] = font[36]; // [210 - 32] COMBINING HOOK ABOVE. // O with acute accent. font[211 - 32].resize( font[47].width(), font[47].height() + 3 ); font[211 - 32].reset(); fheroes2::Copy( font[47], 0, 0, font[211 - 32], 0, 3, font[47].width(), font[47].height() ); fheroes2::Copy( font[193 - 32], 7, 0, font[211 - 32], 7, 0, 4, 2 ); font[211 - 32].setPosition( font[47].x(), font[47].y() - 3 ); updateNormalFontLetterShadow( font[211 - 32] ); // O with circumflex accent. font[212 - 32].resize( font[79 - 32].width(), font[79 - 32].height() + 3 ); font[212 - 32].reset(); fheroes2::Copy( font[79 - 32], 0, 0, font[212 - 32], 0, 3, font[79 - 32].width(), font[79 - 32].height() ); fheroes2::Copy( font[194 - 32], 7, 0, font[212 - 32], 7, 0, 3, 2 ); font[212 - 32].setPosition( font[79 - 32].x(), font[79 - 32].y() - 3 ); updateNormalFontLetterShadow( font[212 - 32] ); // O with horn. Needs accent generation. font[213 - 32] = font[79 - 32]; // U with grave accent `. font[217 - 32].resize( font[53].width(), font[53].height() + 3 ); font[217 - 32].reset(); fheroes2::Copy( font[53], 0, 0, font[217 - 32], 0, 3, font[53].width(), font[53].height() ); font[217 - 32].setPosition( font[53].x(), font[53].y() - 3 ); fheroes2::Copy( font[192 - 32], 7, 0, font[217 - 32], 5, 0, 4, 2 ); updateNormalFontLetterShadow( font[217 - 32] ); // U with acute accent. font[218 - 32].resize( font[53].width(), font[53].height() + 3 ); font[218 - 32].reset(); fheroes2::Copy( font[53], 0, 0, font[218 - 32], 0, 3, font[53].width(), font[53].height() ); fheroes2::Copy( font[193 - 32], 7, 0, font[218 - 32], 5, 0, 4, 2 ); font[218 - 32].setPosition( font[53].x(), font[53].y() - 3 ); updateNormalFontLetterShadow( font[218 - 32] ); // U with horn. Needs accent generated. font[221 - 32] = font[53]; // [222 - 32] COMBINING TILDE ~. // a with grave accent `. font[224 - 32].resize( font[65].width(), font[65].height() + 3 ); font[224 - 32].reset(); fheroes2::Copy( font[65], 0, 0, font[224 - 32], 0, 3, font[65].width(), font[65].height() ); font[224 - 32].setPosition( font[65].x(), font[65].y() - 3 ); fheroes2::Copy( font[192 - 32], 7, 0, font[224 - 32], 3, 0, 4, 2 ); updateNormalFontLetterShadow( font[224 - 32] ); // a with acute accent. font[225 - 32].resize( font[65].width(), font[65].height() + 3 ); font[225 - 32].reset(); fheroes2::Copy( font[65], 0, 0, font[225 - 32], 0, 3, font[65].width(), font[65].height() ); fheroes2::Copy( font[193 - 32], 7, 0, font[225 - 32], 3, 0, 4, 2 ); font[225 - 32].setPosition( font[65].x(), font[65].y() - 3 ); updateNormalFontLetterShadow( font[225 - 32] ); // a with circumflex accent. font[226 - 32].resize( font[97 - 32].width(), font[97 - 32].height() + 3 ); font[226 - 32].reset(); fheroes2::Copy( font[97 - 32], 0, 0, font[226 - 32], 0, 3, font[97 - 32].width(), font[97 - 32].height() ); fheroes2::Copy( font[194 - 32], 7, 0, font[226 - 32], 3, 0, 3, 2 ); font[226 - 32].setPosition( font[97 - 32].x(), font[97 - 32].y() - 3 ); updateNormalFontLetterShadow( font[226 - 32] ); // a with breve. font[227 - 32].resize( font[65].width(), font[65].height() + 3 ); font[227 - 32].reset(); fheroes2::Copy( font[65], 0, 0, font[227 - 32], 0, 3, font[65].width(), font[65].height() ); fheroes2::Copy( font[65], 2, 0, font[227 - 32], 3, 0, 1, 1 ); fheroes2::Copy( font[65], 2, 0, font[227 - 32], 6, 0, 1, 1 ); fheroes2::Copy( font[65], 1, 0, font[227 - 32], 3, 1, 1, 1 ); fheroes2::Copy( font[65], 2, 0, font[227 - 32], 4, 1, 2, 1 ); fheroes2::Copy( font[65], 1, 0, font[227 - 32], 6, 1, 1, 1 ); font[227 - 32].setPosition( font[65].x(), font[65].y() - 3 ); updateNormalFontLetterShadow( font[227 - 32] ); // e with grave accent `. font[232 - 32].resize( font[69].width(), font[69].height() + 3 ); font[232 - 32].reset(); fheroes2::Copy( font[69], 0, 0, font[232 - 32], 0, 3, font[69].width(), font[69].height() ); font[232 - 32].setPosition( font[69].x(), font[69].y() - 3 ); fheroes2::Copy( font[192 - 32], 7, 0, font[232 - 32], 3, 0, 4, 2 ); updateNormalFontLetterShadow( font[232 - 32] ); // e with acute accent. font[233 - 32].resize( font[69].width(), font[69].height() + 3 ); font[233 - 32].reset(); fheroes2::Copy( font[69], 0, 0, font[233 - 32], 0, 3, font[69].width(), font[69].height() ); fheroes2::Copy( font[193 - 32], 7, 0, font[233 - 32], 3, 0, 4, 2 ); font[233 - 32].setPosition( font[69].x(), font[69].y() - 3 ); updateNormalFontLetterShadow( font[233 - 32] ); // e with circumflex accent. font[234 - 32].resize( font[101 - 32].width(), font[101 - 32].height() + 3 ); font[234 - 32].reset(); fheroes2::Copy( font[101 - 32], 0, 0, font[234 - 32], 0, 3, font[101 - 32].width(), font[101 - 32].height() ); fheroes2::Copy( font[194 - 32], 7, 0, font[234 - 32], 4, 0, 3, 2 ); font[234 - 32].setPosition( font[101 - 32].x(), font[101 - 32].y() - 3 ); updateNormalFontLetterShadow( font[234 - 32] ); // [236 - 32] COMBINING ACUTE TONE MARK. // i with acute accent. font[237 - 32] = font[73]; fheroes2::FillTransform( font[237 - 32], 0, 0, font[237 - 32].width(), 3, 1 ); fheroes2::Copy( font[193 - 32], 7, 0, font[237 - 32], 1, 0, 4, 2 ); updateNormalFontLetterShadow( font[237 - 32] ); // d with stroke. Needs stroke generated. font[240 - 32] = font[68]; // [242 - 32] COMBINING DOT BELOW. // o with acute accent. font[243 - 32].resize( font[79].width(), font[79].height() + 3 ); font[243 - 32].reset(); fheroes2::Copy( font[79], 0, 0, font[243 - 32], 0, 3, font[79].width(), font[79].height() ); fheroes2::Copy( font[193 - 32], 7, 0, font[243 - 32], 3, 0, 4, 2 ); font[243 - 32].setPosition( font[79].x(), font[79].y() - 3 ); updateNormalFontLetterShadow( font[243 - 32] ); // o with circumflex accent. font[244 - 32].resize( font[111 - 32].width(), font[111 - 32].height() + 3 ); font[244 - 32].reset(); fheroes2::Copy( font[111 - 32], 0, 0, font[244 - 32], 0, 3, font[111 - 32].width(), font[111 - 32].height() ); fheroes2::Copy( font[194 - 32], 7, 0, font[244 - 32], 4, 0, 3, 2 ); font[244 - 32].setPosition( font[111 - 32].x(), font[111 - 32].y() - 3 ); updateNormalFontLetterShadow( font[244 - 32] ); // o with horn. Needs accent generated. font[245 - 32] = font[111 - 32]; // u with grave accent `. font[249 - 32].resize( font[85].width(), font[85].height() + 3 ); font[249 - 32].reset(); fheroes2::Copy( font[85], 0, 0, font[249 - 32], 0, 3, font[85].width(), font[85].height() ); font[249 - 32].setPosition( font[85].x(), font[85].y() - 3 ); fheroes2::Copy( font[192 - 32], 7, 0, font[249 - 32], 3, 0, 4, 2 ); updateNormalFontLetterShadow( font[249 - 32] ); // u with acute accent. font[250 - 32].resize( font[85].width(), font[85].height() + 3 ); font[250 - 32].reset(); fheroes2::Copy( font[85], 0, 0, font[250 - 32], 0, 3, font[85].width(), font[85].height() ); fheroes2::Copy( font[193 - 32], 7, 0, font[250 - 32], 3, 0, 4, 2 ); font[250 - 32].setPosition( font[85].x(), font[85].y() - 3 ); updateNormalFontLetterShadow( font[250 - 32] ); // u with horn. Needs accent generated font[253 - 32] = font[85]; } // Small font. { std::vector & font = icnVsSprite[ICN::SMALFONT]; // A with grave accent `. Generate grave accent for further use. font[192 - 32].resize( font[33].width(), font[33].height() + 3 ); font[192 - 32].reset(); fheroes2::Copy( font[33], 0, 0, font[192 - 32], 0, 4, font[33].width(), font[33].height() ); fheroes2::Copy( font[192 - 32], 4, 4, font[192 - 32], 4, 0, 1, 1 ); fheroes2::Copy( font[192 - 32], 4, 4, font[192 - 32], 5, 1, 1, 1 ); fheroes2::Copy( font[192 - 32], 4, 4, font[192 - 32], 6, 2, 1, 1 ); font[192 - 32].setPosition( font[33].x(), font[33].y() - 4 ); updateSmallFontLetterShadow( font[192 - 32] ); // A with acute accent. Generate acute accent for further use font[193 - 32].resize( font[33].width(), font[33].height() + 4 ); font[193 - 32].reset(); fheroes2::Copy( font[33], 0, 0, font[193 - 32], 0, 4, font[33].width(), font[33].height() ); fheroes2::Copy( font[33], 4, 0, font[193 - 32], 4, 2, 1, 1 ); fheroes2::Copy( font[33], 4, 0, font[193 - 32], 5, 1, 1, 1 ); fheroes2::Copy( font[33], 4, 0, font[193 - 32], 6, 0, 1, 1 ); font[193 - 32].setPosition( font[33].x(), font[33].y() - 4 ); updateSmallFontLetterShadow( font[193 - 32] ); // A with circumflex accent. Generate accent for further use. font[194 - 32].resize( font[33].width(), font[33].height() + 3 ); font[194 - 32].reset(); fheroes2::Copy( font[33], 0, 0, font[194 - 32], 0, 3, font[33].width(), font[33].height() ); fheroes2::Copy( font[33], 4, 0, font[194 - 32], 4, 1, 1, 1 ); fheroes2::Copy( font[33], 4, 0, font[194 - 32], 5, 0, 1, 1 ); fheroes2::Copy( font[33], 4, 0, font[194 - 32], 6, 1, 1, 1 ); font[194 - 32].setPosition( font[33].x(), font[33].y() - 3 ); updateSmallFontLetterShadow( font[194 - 32] ); // A with breve. Generate accent for further use. font[195 - 32].resize( font[33].width(), font[33].height() + 3 ); font[195 - 32].reset(); fheroes2::Copy( font[33], 0, 0, font[195 - 32], 0, 3, font[33].width(), font[33].height() ); fheroes2::Copy( font[33], 3, 0, font[195 - 32], 3, 0, 1, 1 ); fheroes2::Copy( font[33], 3, 0, font[195 - 32], 4, 1, 2, 1 ); fheroes2::Copy( font[33], 3, 0, font[195 - 32], 6, 0, 1, 1 ); font[195 - 32].setPosition( font[33].x(), font[33].y() - 3 ); updateSmallFontLetterShadow( font[195 - 32] ); // E with grave accent `. font[200 - 32].resize( font[37].width(), font[37].height() + 4 ); font[200 - 32].reset(); fheroes2::Copy( font[37], 0, 0, font[200 - 32], 0, 4, font[37].width(), font[37].height() ); fheroes2::Copy( font[192 - 32], 4, 0, font[200 - 32], 4, 0, 3, 3 ); font[200 - 32].setPosition( font[37].x(), font[37].y() - 4 ); updateSmallFontLetterShadow( font[200 - 32] ); // E with acute accent. font[201 - 32].resize( font[37].width(), font[37].height() + 4 ); font[201 - 32].reset(); fheroes2::Copy( font[37], 0, 0, font[201 - 32], 0, 4, font[37].width(), font[37].height() ); fheroes2::Copy( font[193 - 32], 4, 0, font[201 - 32], 3, 0, 3, 3 ); font[201 - 32].setPosition( font[37].x(), font[37].y() - 4 ); updateSmallFontLetterShadow( font[201 - 32] ); // E with circumflex accent. font[202 - 32].resize( font[69 - 32].width(), font[69 - 32].height() + 3 ); font[202 - 32].reset(); fheroes2::Copy( font[69 - 32], 0, 0, font[202 - 32], 0, 3, font[69 - 32].width(), font[69 - 32].height() ); fheroes2::Copy( font[194 - 32], 4, 0, font[202 - 32], 3, 0, 3, 2 ); font[202 - 32].setPosition( font[69 - 32].x(), font[69 - 32].y() - 3 ); updateSmallFontLetterShadow( font[202 - 32] ); // [204 - 32] COMBINING GRAVE TONE MARK `. // I with acute accent. font[205 - 32].resize( font[41].width(), font[41].height() + 4 ); font[205 - 32].reset(); fheroes2::Copy( font[41], 0, 0, font[205 - 32], 0, 4, font[41].width(), font[41].height() ); fheroes2::Copy( font[193 - 32], 4, 0, font[205 - 32], 1, 0, 3, 3 ); font[205 - 32].setPosition( font[41].x(), font[41].y() - 4 ); updateSmallFontLetterShadow( font[205 - 32] ); // D with stroke. Needs stroke generated. font[208 - 32] = font[36]; // [210 - 32] COMBINING HOOK ABOVE. // O with acute accent. font[211 - 32].resize( font[47].width(), font[47].height() + 4 ); font[211 - 32].reset(); fheroes2::Copy( font[47], 0, 0, font[211 - 32], 0, 4, font[47].width(), font[47].height() ); fheroes2::Copy( font[193 - 32], 4, 0, font[211 - 32], 3, 0, 3, 3 ); font[211 - 32].setPosition( font[47].x(), font[47].y() - 4 ); updateSmallFontLetterShadow( font[211 - 32] ); // O with circumflex accent. font[212 - 32].resize( font[79 - 32].width(), font[79 - 32].height() + 3 ); font[212 - 32].reset(); fheroes2::Copy( font[79 - 32], 0, 0, font[212 - 32], 0, 3, font[79 - 32].width(), font[79 - 32].height() ); fheroes2::Copy( font[194 - 32], 4, 0, font[212 - 32], 3, 0, 3, 2 ); font[212 - 32].setPosition( font[79 - 32].x(), font[79 - 32].y() - 3 ); updateSmallFontLetterShadow( font[212 - 32] ); // O with horn. Needs accent. font[213 - 32] = font[79 - 32]; // U with grave accent `. font[217 - 32].resize( font[53].width(), font[53].height() + 4 ); font[217 - 32].reset(); fheroes2::Copy( font[53], 0, 0, font[217 - 32], 0, 4, font[53].width(), font[53].height() ); fheroes2::Copy( font[192 - 32], 4, 0, font[217 - 32], 4, 0, 3, 3 ); font[217 - 32].setPosition( font[53].x(), font[53].y() - 4 ); updateSmallFontLetterShadow( font[217 - 32] ); // U with acute accent. font[218 - 32].resize( font[53].width(), font[53].height() + 4 ); font[218 - 32].reset(); fheroes2::Copy( font[53], 0, 0, font[218 - 32], 0, 4, font[53].width(), font[53].height() ); fheroes2::Copy( font[193 - 32], 4, 0, font[218 - 32], 4, 0, 3, 3 ); font[218 - 32].setPosition( font[53].x(), font[53].y() - 4 ); updateSmallFontLetterShadow( font[218 - 32] ); // U with horn. Needs accent generated. font[221 - 32] = font[53]; // [222 - 32] COMBINING TILDE ~. // a with grave accent `. font[224 - 32].resize( font[65].width(), font[65].height() + 3 ); font[224 - 32].reset(); fheroes2::Copy( font[65], 0, 0, font[224 - 32], 0, 3, font[65].width(), font[65].height() ); fheroes2::Copy( font[192 - 32], 4, 0, font[224 - 32], 3, 0, 3, 2 ); font[224 - 32].setPosition( font[65].x(), font[65].y() - 3 ); updateSmallFontLetterShadow( font[224 - 32] ); // a with acute accent. font[225 - 32].resize( font[65].width(), font[65].height() + 3 ); font[225 - 32].reset(); fheroes2::Copy( font[65], 0, 0, font[225 - 32], 0, 3, font[65].width(), font[65].height() ); fheroes2::Copy( font[193 - 32], 5, 0, font[225 - 32], 3, 0, 2, 2 ); font[225 - 32].setPosition( font[65].x(), font[65].y() - 3 ); updateSmallFontLetterShadow( font[225 - 32] ); // a with circumflex accent. font[226 - 32].resize( font[97 - 32].width(), font[97 - 32].height() + 3 ); font[226 - 32].reset(); fheroes2::Copy( font[97 - 32], 0, 0, font[226 - 32], 0, 3, font[97 - 32].width(), font[97 - 32].height() ); fheroes2::Copy( font[194 - 32], 4, 0, font[226 - 32], 2, 0, 3, 2 ); font[226 - 32].setPosition( font[97 - 32].x(), font[97 - 32].y() - 3 ); updateSmallFontLetterShadow( font[226 - 32] ); // a with breve. font[227 - 32].resize( font[65].width(), font[65].height() + 3 ); font[227 - 32].reset(); fheroes2::Copy( font[65], 0, 0, font[227 - 32], 0, 3, font[65].width(), font[65].height() ); fheroes2::Copy( font[195 - 32], 3, 0, font[227 - 32], 2, 0, 4, 2 ); font[227 - 32].setPosition( font[65].x(), font[65].y() - 3 ); updateSmallFontLetterShadow( font[227 - 32] ); // e with grave accent `. font[232 - 32].resize( font[69].width(), font[69].height() + 3 ); font[232 - 32].reset(); fheroes2::Copy( font[69], 0, 0, font[232 - 32], 0, 3, font[69].width(), font[69].height() ); fheroes2::Copy( font[192 - 32], 4, 0, font[232 - 32], 3, 0, 3, 2 ); font[232 - 32].setPosition( font[69].x(), font[69].y() - 3 ); updateSmallFontLetterShadow( font[232 - 32] ); // e with acute accent. font[233 - 32].resize( font[69].width(), font[69].height() + 3 ); font[233 - 32].reset(); fheroes2::Copy( font[69], 0, 0, font[233 - 32], 0, 3, font[69].width(), font[69].height() ); fheroes2::Copy( font[193 - 32], 5, 0, font[233 - 32], 3, 0, 2, 2 ); font[233 - 32].setPosition( font[69].x(), font[69].y() - 3 ); updateSmallFontLetterShadow( font[233 - 32] ); // e with circumflex accent. font[234 - 32].resize( font[101 - 32].width(), font[101 - 32].height() + 3 ); font[234 - 32].reset(); fheroes2::Copy( font[101 - 32], 0, 0, font[234 - 32], 0, 3, font[101 - 32].width(), font[101 - 32].height() ); fheroes2::Copy( font[194 - 32], 4, 0, font[234 - 32], 2, 0, 3, 2 ); font[234 - 32].setPosition( font[101 - 32].x(), font[101 - 32].y() - 3 ); updateSmallFontLetterShadow( font[234 - 32] ); // [236 - 32] COMBINING ACUTE TONE MARK. // i with acute accent. font[237 - 32].resize( font[73].width(), font[73].height() + 1 ); font[237 - 32].reset(); fheroes2::Copy( font[73], 0, 2, font[237 - 32], 0, 3, font[73].width(), 6 ); fheroes2::Copy( font[193 - 32], 5, 0, font[237 - 32], 1, 0, 2, 2 ); font[237 - 32].setPosition( font[73].x(), font[73].y() - 1 ); updateSmallFontLetterShadow( font[237 - 32] ); // d with stroke. Needs stroke generated. font[240 - 32] = font[68]; // [242 - 32] COMBINING DOT BELOW. // o with acute accent. font[243 - 32].resize( font[79].width(), font[79].height() + 3 ); font[243 - 32].reset(); fheroes2::Copy( font[79], 0, 0, font[243 - 32], 0, 3, font[79].width(), font[79].height() ); fheroes2::Copy( font[193 - 32], 5, 0, font[243 - 32], 3, 0, 2, 2 ); font[243 - 32].setPosition( font[79].x(), font[79].y() - 3 ); updateSmallFontLetterShadow( font[243 - 32] ); // o with circumflex accent. font[244 - 32].resize( font[111 - 32].width(), font[111 - 32].height() + 3 ); font[244 - 32].reset(); fheroes2::Copy( font[111 - 32], 0, 0, font[244 - 32], 0, 3, font[111 - 32].width(), font[111 - 32].height() ); fheroes2::Copy( font[194 - 32], 4, 0, font[244 - 32], 2, 0, 3, 2 ); font[244 - 32].setPosition( font[111 - 32].x(), font[111 - 32].y() - 3 ); updateSmallFontLetterShadow( font[244 - 32] ); // o with horn. Needs accent to be generated. font[245 - 32] = font[111 - 32]; // u with grave accent `. font[249 - 32].resize( font[85].width(), font[85].height() + 3 ); font[249 - 32].reset(); fheroes2::Copy( font[85], 0, 0, font[249 - 32], 0, 3, font[85].width(), font[85].height() ); fheroes2::Copy( font[192 - 32], 4, 0, font[249 - 32], 3, 0, 3, 2 ); font[249 - 32].setPosition( font[85].x(), font[85].y() - 3 ); updateSmallFontLetterShadow( font[249 - 32] ); // u with acute accent. font[250 - 32].resize( font[85].width(), font[85].height() + 3 ); font[250 - 32].reset(); fheroes2::Copy( font[85], 0, 0, font[250 - 32], 0, 3, font[85].width(), font[85].height() ); fheroes2::Copy( font[193 - 32], 5, 0, font[250 - 32], 3, 0, 2, 2 ); font[250 - 32].setPosition( font[85].x(), font[85].y() - 3 ); updateSmallFontLetterShadow( font[250 - 32] ); // u with horn. font[253 - 32] = font[85]; } } void generateISO8859_16Alphabet( std::vector> & icnVsSprite ) { // Resize fonts. for ( const int icnId : { ICN::FONT, ICN::SMALFONT } ) { icnVsSprite[icnId].resize( baseFontSize ); const fheroes2::Sprite firstSprite{ icnVsSprite[icnId][0] }; icnVsSprite[icnId].insert( icnVsSprite[icnId].end(), 160, firstSprite ); } // Normal font. { std::vector & font = icnVsSprite[ICN::FONT]; // S with comma. font[170 - 32].resize( font[51].width(), font[51].height() + 4 ); font[170 - 32].reset(); fheroes2::Copy( font[51], 0, 0, font[170 - 32], 0, 0, font[51].width(), font[51].height() ); fheroes2::Copy( font[12], 0, 0, font[170 - 32], 3, 12, font[12].width(), font[12].height() ); font[170 - 32].setPosition( font[51].x(), font[51].y() ); updateNormalFontLetterShadow( font[170 - 32] ); // A with circonflex and generate the accent for further use. font[194 - 32].resize( font[33].width(), font[33].height() + 3 ); font[194 - 32].reset(); fheroes2::Copy( font[33], 0, 0, font[194 - 32], 0, 3, font[33].width(), font[33].height() ); fheroes2::Copy( font[33], 3, 0, font[194 - 32], 7, 0, 1, 1 ); fheroes2::Copy( font[33], 3, 0, font[194 - 32], 6, 1, 1, 1 ); fheroes2::Copy( font[33], 3, 0, font[194 - 32], 10, 0, 1, 1 ); fheroes2::Copy( font[33], 3, 0, font[194 - 32], 11, 1, 1, 1 ); fheroes2::Copy( font[33], 4, 0, font[194 - 32], 8, 0, 1, 1 ); fheroes2::Copy( font[33], 4, 0, font[194 - 32], 7, 1, 1, 1 ); fheroes2::Copy( font[33], 4, 0, font[194 - 32], 10, 1, 1, 1 ); fheroes2::Copy( font[33], 4, 0, font[194 - 32], 9, 0, 1, 1 ); font[194 - 32].setPosition( font[33].x(), font[33].y() - 3 ); updateNormalFontLetterShadow( font[194 - 32] ); // A with breve and generate the accent for further use. font[195 - 32].resize( font[33].width(), font[33].height() + 3 ); font[195 - 32].reset(); fheroes2::Copy( font[33], 0, 0, font[195 - 32], 0, 3, font[33].width(), font[33].height() ); fheroes2::Copy( font[39], 5, 9, font[195 - 32], 5, 0, 7, 2 ); fheroes2::FillTransform( font[195 - 32], 7, 0, 3, 1, 1 ); font[195 - 32].setPosition( font[33].x(), font[33].y() - 3 ); updateNormalFontLetterShadow( font[195 - 32] ); // T with comma. font[222 - 32].resize( font[52].width(), font[52].height() + 4 ); font[222 - 32].reset(); fheroes2::Copy( font[52], 0, 0, font[222 - 32], 0, 0, font[52].width(), font[52].height() ); fheroes2::Copy( font[12], 0, 0, font[222 - 32], 3, 12, font[12].width(), font[12].height() ); font[222 - 32].setPosition( font[52].x(), font[52].y() ); updateNormalFontLetterShadow( font[222 - 32] ); // s with comma. font[186 - 32].resize( font[83].width(), font[83].height() + 4 ); font[186 - 32].reset(); fheroes2::Copy( font[83], 0, 0, font[186 - 32], 0, 0, font[83].width(), font[83].height() ); fheroes2::Copy( font[12], 0, 0, font[186 - 32], 2, 8, font[12].width(), font[12].height() ); font[186 - 32].setPosition( font[83].x(), font[83].y() ); updateNormalFontLetterShadow( font[186 - 32] ); // a with circonflex. font[226 - 32].resize( font[65].width(), font[65].height() + 4 ); font[226 - 32].reset(); fheroes2::Copy( font[65], 0, 0, font[226 - 32], 0, 3, font[65].width(), font[65].height() ); fheroes2::Copy( font[194 - 32], 6, 0, font[226 - 32], 2, 0, 3, 2 ); fheroes2::Copy( font[194 - 32], 10, 0, font[226 - 32], 5, 0, 2, 2 ); font[226 - 32].setPosition( font[65].x(), font[65].y() - 3 ); updateNormalFontLetterShadow( font[226 - 32] ); // a with breve. font[227 - 32].resize( font[65].width(), font[65].height() + 3 ); font[227 - 32].reset(); fheroes2::Copy( font[65], 0, 0, font[227 - 32], 0, 3, font[65].width(), font[65].height() ); fheroes2::Copy( font[65], 2, 0, font[227 - 32], 3, 0, 1, 1 ); fheroes2::Copy( font[65], 2, 0, font[227 - 32], 6, 0, 1, 1 ); fheroes2::Copy( font[65], 1, 0, font[227 - 32], 3, 1, 1, 1 ); fheroes2::Copy( font[65], 2, 0, font[227 - 32], 4, 1, 2, 1 ); fheroes2::Copy( font[65], 1, 0, font[227 - 32], 6, 1, 1, 1 ); font[227 - 32].setPosition( font[65].x(), font[65].y() - 3 ); updateNormalFontLetterShadow( font[227 - 32] ); // t with comma. font[254 - 32].resize( font[84].width(), font[84].height() + 4 ); font[254 - 32].reset(); fheroes2::Copy( font[84], 0, 0, font[254 - 32], 0, 0, font[84].width(), font[84].height() ); fheroes2::Copy( font[12], 0, 0, font[254 - 32], 1, 12, font[12].width(), font[12].height() ); font[254 - 32].setPosition( font[84].x(), font[84].y() ); updateNormalFontLetterShadow( font[254 - 32] ); } // Small font. { std::vector & font = icnVsSprite[ICN::SMALFONT]; // S with comma. font[170 - 32].resize( font[51].width(), font[51].height() + 4 ); font[170 - 32].reset(); fheroes2::Copy( font[51], 0, 0, font[170 - 32], 0, 0, font[51].width(), font[51].height() ); fheroes2::Copy( font[12], 0, 0, font[170 - 32], 2, 8, font[12].width(), font[12].height() ); font[170 - 32].setPosition( font[51].x(), font[51].y() ); updateSmallFontLetterShadow( font[170 - 32] ); // A with circonflex and generate the accent for further use. font[194 - 32].resize( font[33].width(), font[33].height() + 3 ); font[194 - 32].reset(); fheroes2::Copy( font[33], 0, 0, font[194 - 32], 0, 3, font[33].width(), font[33].height() ); fheroes2::Copy( font[33], 3, 0, font[194 - 32], 5, 0, 1, 1 ); fheroes2::Copy( font[33], 3, 0, font[194 - 32], 4, 1, 1, 1 ); fheroes2::Copy( font[33], 3, 0, font[194 - 32], 6, 1, 1, 1 ); font[194 - 32].setPosition( font[33].x(), font[33].y() - 3 ); updateSmallFontLetterShadow( font[194 - 32] ); // A with breve and generate the accent for further use. font[195 - 32].resize( font[33].width(), font[33].height() + 3 ); font[195 - 32].reset(); fheroes2::Copy( font[33], 0, 0, font[195 - 32], 0, 3, font[33].width(), font[33].height() ); fheroes2::Copy( font[33], 3, 0, font[195 - 32], 3, 0, 1, 1 ); fheroes2::Copy( font[33], 3, 0, font[195 - 32], 4, 1, 2, 1 ); fheroes2::Copy( font[33], 3, 0, font[195 - 32], 6, 0, 1, 1 ); font[195 - 32].setPosition( font[33].x(), font[33].y() - 3 ); updateSmallFontLetterShadow( font[195 - 32] ); // T with comma. font[222 - 32].resize( font[52].width(), font[52].height() + 4 ); font[222 - 32].reset(); fheroes2::Copy( font[52], 0, 0, font[222 - 32], 0, 0, font[52].width(), font[52].height() ); fheroes2::Copy( font[12], 0, 0, font[222 - 32], 2, 8, font[12].width(), font[12].height() ); font[222 - 32].setPosition( font[52].x(), font[52].y() ); updateSmallFontLetterShadow( font[222 - 32] ); // s with comma. font[186 - 32].resize( font[83].width(), font[83].height() + 4 ); font[186 - 32].reset(); fheroes2::Copy( font[83], 0, 0, font[186 - 32], 0, 0, font[83].width(), font[83].height() ); fheroes2::Copy( font[12], 0, 0, font[186 - 32], 1, 6, font[12].width(), font[12].height() ); font[186 - 32].setPosition( font[83].x(), font[83].y() ); updateSmallFontLetterShadow( font[186 - 32] ); // a with circonflex. font[226 - 32].resize( font[65].width(), font[65].height() + 4 ); font[226 - 32].reset(); fheroes2::Copy( font[65], 0, 0, font[226 - 32], 0, 3, font[65].width(), font[65].height() ); fheroes2::Copy( font[194 - 32], 4, 0, font[226 - 32], 2, 0, 3, 2 ); font[226 - 32].setPosition( font[65].x(), font[65].y() - 3 ); updateSmallFontLetterShadow( font[226 - 32] ); // a with breve. font[227 - 32].resize( font[65].width(), font[65].height() + 3 ); font[227 - 32].reset(); fheroes2::Copy( font[65], 0, 0, font[227 - 32], 0, 3, font[65].width(), font[65].height() ); fheroes2::Copy( font[195 - 32], 3, 0, font[227 - 32], 2, 0, 4, 2 ); font[227 - 32].setPosition( font[65].x(), font[65].y() - 3 ); updateSmallFontLetterShadow( font[227 - 32] ); // t with comma. font[254 - 32].resize( font[84].width(), font[84].height() + 4 ); font[254 - 32].reset(); fheroes2::Copy( font[84], 0, 0, font[254 - 32], 0, 0, font[84].width(), font[84].height() ); fheroes2::Copy( font[12], 0, 0, font[254 - 32], 1, 8, font[12].width(), font[12].height() ); font[254 - 32].setPosition( font[84].x(), font[84].y() ); updateSmallFontLetterShadow( font[254 - 32] ); } } void generateGoodButtonFontBaseShape( std::vector & released ) { // Button font does not exist in the original game assets but we can regenerate it from scratch. // All letters in buttons have some variations in colors but overall shapes are the same. // We want to standardize the font and to use one approach to generate letters. // The shape of the letter is defined only by one color (in general). The rest of information is generated from transformations and contours. // // Another essential difference from normal fonts is that button font has only uppercase letters. // This means that we need to generate only 26 letter of English alphabet, 10 digits and few special characters, totalling in about 50 symbols. // The downside of this font is that code is necessary for the generation of released and pressed states of each letter. released.resize( baseFontSize ); // We need 2 pixels from all sides of a letter to add extra effects. const int32_t offset = 2; // Since all symbols have -1 shift by X axis to avoid any issues with alignment we need to makes all images at least 1 pixel in size. // These images are completely transparent. for ( fheroes2::Sprite & letter : released ) { letter.resize( 1, 1 ); letter.reset(); letter.setPosition( buttonFontOffset.x, buttonFontOffset.y ); } // Address symbols that should have even less space to neighboring symbols. released[1].setPosition( buttonFontOffset.x - 1, buttonFontOffset.y ); released[13].setPosition( buttonFontOffset.x - 2, buttonFontOffset.y ); released[14].setPosition( buttonFontOffset.x - 1, buttonFontOffset.y ); released[26].setPosition( buttonFontOffset.x - 2, buttonFontOffset.y ); released[27].setPosition( buttonFontOffset.x - 1, buttonFontOffset.y ); released[31].setPosition( buttonFontOffset.x - 1, buttonFontOffset.y ); released[33].setPosition( buttonFontOffset.x - 1, buttonFontOffset.y ); released[54].setPosition( buttonFontOffset.x - 1, buttonFontOffset.y ); released[57].setPosition( buttonFontOffset.x - 1, buttonFontOffset.y ); // ! released[1].resize( 2 + offset * 2, 10 + offset * 2 ); released[1].reset(); fheroes2::DrawLine( released[1], { offset + 0, offset + 0 }, { offset + 0, offset + 5 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[1], { offset + 1, offset + 0 }, { offset + 1, offset + 5 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[1], { offset + 0, offset + 8 }, { offset + 0, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[1], { offset + 1, offset + 8 }, { offset + 1, offset + 9 }, buttonGoodReleasedColor ); // " released[2].resize( 6 + offset * 2, 10 + offset * 2 ); released[2].reset(); fheroes2::DrawLine( released[2], { offset + 1, offset + 0 }, { offset + 1, offset + 1 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[2], { offset + 5, offset + 0 }, { offset + 5, offset + 1 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[2], offset + 0, offset + 0, buttonGoodReleasedColor ); fheroes2::SetPixel( released[2], offset + 4, offset + 0, buttonGoodReleasedColor ); // # released[3].resize( 10 + offset * 2, 10 + offset * 2 ); released[3].reset(); fheroes2::DrawLine( released[3], { offset + 1, offset + 3 }, { offset + 9, offset + 3 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[3], { offset + 0, offset + 6 }, { offset + 8, offset + 6 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[3], { offset + 4, offset + 0 }, { offset + 2, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[3], { offset + 7, offset + 0 }, { offset + 5, offset + 9 }, buttonGoodReleasedColor ); // % released[5].resize( 9 + offset * 2, 10 + offset * 2 ); released[5].reset(); fheroes2::DrawLine( released[5], { offset + 6, offset + 0 }, { offset + 2, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[5], { offset + 1, offset + 0 }, { offset + 2, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[5], { offset + 1, offset + 3 }, { offset + 2, offset + 3 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[5], { offset + 0, offset + 1 }, { offset + 0, offset + 2 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[5], { offset + 3, offset + 1 }, { offset + 3, offset + 2 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[5], { offset + 7, offset + 9 }, { offset + 6, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[5], { offset + 7, offset + 6 }, { offset + 6, offset + 6 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[5], { offset + 8, offset + 8 }, { offset + 8, offset + 7 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[5], { offset + 5, offset + 8 }, { offset + 5, offset + 7 }, buttonGoodReleasedColor ); // & released[6].resize( 8 + offset * 2, 10 + offset * 2 ); released[6].reset(); fheroes2::DrawLine( released[6], { offset + 2, offset + 0 }, { offset + 3, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[6], { offset + 1, offset + 1 }, { offset + 1, offset + 2 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[6], { offset + 4, offset + 1 }, { offset + 4, offset + 2 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[6], { offset + 2, offset + 3 }, { offset + 7, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[6], { offset + 3, offset + 3 }, { offset + 1, offset + 5 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[6], { offset + 0, offset + 6 }, { offset + 0, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[6], { offset + 1, offset + 9 }, { offset + 4, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[6], { offset + 5, offset + 8 }, { offset + 7, offset + 5 }, buttonGoodReleasedColor ); // ' released[7].resize( 2 + offset * 2, 10 + offset * 2 ); released[7].reset(); fheroes2::DrawLine( released[7], { offset + 1, offset + 0 }, { offset + 1, offset + 1 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[7], offset + 0, offset + 0, buttonGoodReleasedColor ); // ( released[8].resize( 3 + offset * 2, 10 + offset * 2 ); released[8].reset(); fheroes2::DrawLine( released[8], { offset + 0, offset + 3 }, { offset + 0, offset + 6 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[8], { offset + 1, offset + 1 }, { offset + 1, offset + 2 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[8], { offset + 1, offset + 7 }, { offset + 1, offset + 8 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[8], offset + 2, offset + 0, buttonGoodReleasedColor ); fheroes2::SetPixel( released[8], offset + 2, offset + 9, buttonGoodReleasedColor ); // ) released[9].resize( 3 + offset * 2, 10 + offset * 2 ); released[9].reset(); fheroes2::DrawLine( released[9], { offset + 2, offset + 3 }, { offset + 2, offset + 6 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[9], { offset + 1, offset + 1 }, { offset + 1, offset + 2 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[9], { offset + 1, offset + 7 }, { offset + 1, offset + 8 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[9], offset + 0, offset + 0, buttonGoodReleasedColor ); fheroes2::SetPixel( released[9], offset + 0, offset + 9, buttonGoodReleasedColor ); //* released[10].resize( 5 + offset * 2, 10 + offset * 2 ); released[10].reset(); fheroes2::DrawLine( released[10], { offset + 2, offset + 0 }, { offset + 2, offset + 5 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[10], { offset + 0, offset + 1 }, { offset + 4, offset + 4 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[10], { offset + 0, offset + 4 }, { offset + 4, offset + 1 }, buttonGoodReleasedColor ); // + released[11].resize( 5 + offset * 2, 10 + offset * 2 ); released[11].reset(); fheroes2::DrawLine( released[11], { offset + 0, offset + 5 }, { offset + 4, offset + 5 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[11], { offset + 2, offset + 3 }, { offset + 2, offset + 7 }, buttonGoodReleasedColor ); // , released[12].resize( 3 + offset * 2, 11 + offset * 2 ); released[12].reset(); fheroes2::DrawLine( released[12], { offset + 1, offset + 8 }, { offset + 2, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[12], { offset + 1, offset + 9 }, { offset + 2, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[12], { offset + 0, offset + 10 }, { offset + 1, offset + 10 }, buttonGoodReleasedColor ); // - released[13].resize( 6 + offset * 2, 6 + offset * 2 ); released[13].reset(); fheroes2::DrawLine( released[13], { offset + 0, offset + 5 }, { offset + 5, offset + 5 }, buttonGoodReleasedColor ); // . released[14].resize( 2 + offset * 2, 10 + offset * 2 ); released[14].reset(); fheroes2::DrawLine( released[14], { offset + 0, offset + 8 }, { offset + 1, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[14], { offset + 0, offset + 9 }, { offset + 1, offset + 9 }, buttonGoodReleasedColor ); // / released[15].resize( 4 + offset * 2, 10 + offset * 2 ); released[15].reset(); fheroes2::DrawLine( released[15], { offset + 3, offset + 0 }, { offset + 0, offset + 9 }, buttonGoodReleasedColor ); // 0 released[16].resize( 9 + offset * 2, 10 + offset * 2 ); released[16].reset(); fheroes2::DrawLine( released[16], { offset + 2, offset + 0 }, { offset + 6, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[16], { offset + 0, offset + 2 }, { offset + 0, offset + 7 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[16], { offset + 2, offset + 9 }, { offset + 6, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[16], { offset + 8, offset + 2 }, { offset + 8, offset + 7 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[16], offset + 1, offset + 1, buttonGoodReleasedColor ); fheroes2::SetPixel( released[16], offset + 7, offset + 1, buttonGoodReleasedColor ); fheroes2::SetPixel( released[16], offset + 1, offset + 8, buttonGoodReleasedColor ); fheroes2::SetPixel( released[16], offset + 7, offset + 8, buttonGoodReleasedColor ); // 1 released[17].resize( 5 + offset * 2, 10 + offset * 2 ); released[17].reset(); fheroes2::DrawLine( released[17], { offset + 2, offset + 0 }, { offset + 2, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[17], { offset + 0, offset + 9 }, { offset + 4, offset + 9 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[17], offset + 1, offset + 1, buttonGoodReleasedColor ); fheroes2::SetPixel( released[17], offset + 0, offset + 2, buttonGoodReleasedColor ); // 2 released[18].resize( 7 + offset * 2, 10 + offset * 2 ); released[18].reset(); fheroes2::DrawLine( released[18], { offset + 1, offset + 0 }, { offset + 5, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[18], { offset + 6, offset + 1 }, { offset + 6, offset + 3 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[18], { offset + 5, offset + 4 }, { offset + 0, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[18], { offset + 1, offset + 9 }, { offset + 6, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[18], { offset + 0, offset + 1 }, { offset + 0, offset + 2 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[18], offset + 6, offset + 8, buttonGoodReleasedColor ); // 3 released[19].resize( 7 + offset * 2, 10 + offset * 2 ); released[19].reset(); fheroes2::DrawLine( released[19], { offset + 1, offset + 0 }, { offset + 5, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[19], { offset + 6, offset + 1 }, { offset + 6, offset + 3 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[19], { offset + 2, offset + 4 }, { offset + 5, offset + 4 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[19], { offset + 6, offset + 5 }, { offset + 6, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[19], { offset + 1, offset + 9 }, { offset + 5, offset + 9 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[19], offset + 0, offset + 1, buttonGoodReleasedColor ); fheroes2::SetPixel( released[19], offset + 0, offset + 8, buttonGoodReleasedColor ); // 4 released[20].resize( 8 + offset * 2, 10 + offset * 2 ); released[20].reset(); fheroes2::DrawLine( released[20], { offset + 0, offset + 4 }, { offset + 4, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[20], { offset + 5, offset + 0 }, { offset + 5, offset + 4 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[20], { offset + 5, offset + 6 }, { offset + 5, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[20], { offset + 0, offset + 5 }, { offset + 7, offset + 5 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[20], { offset + 3, offset + 9 }, { offset + 7, offset + 9 }, buttonGoodReleasedColor ); // 5 released[21].resize( 7 + offset * 2, 10 + offset * 2 ); released[21].reset(); fheroes2::DrawLine( released[21], { offset + 0, offset + 0 }, { offset + 6, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[21], { offset + 0, offset + 1 }, { offset + 0, offset + 3 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[21], { offset + 1, offset + 4 }, { offset + 5, offset + 4 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[21], { offset + 6, offset + 5 }, { offset + 6, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[21], { offset + 1, offset + 9 }, { offset + 5, offset + 9 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[21], offset + 0, offset + 8, buttonGoodReleasedColor ); // 6 released[22].resize( 7 + offset * 2, 10 + offset * 2 ); released[22].reset(); fheroes2::DrawLine( released[22], { offset + 1, offset + 0 }, { offset + 5, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[22], { offset + 0, offset + 1 }, { offset + 0, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[22], { offset + 1, offset + 4 }, { offset + 5, offset + 4 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[22], { offset + 6, offset + 5 }, { offset + 6, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[22], { offset + 1, offset + 9 }, { offset + 5, offset + 9 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[22], offset + 6, offset + 1, buttonGoodReleasedColor ); // 7 released[23].resize( 7 + offset * 2, 10 + offset * 2 ); released[23].reset(); fheroes2::DrawLine( released[23], { offset + 0, offset + 0 }, { offset + 5, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[23], { offset + 6, offset + 0 }, { offset + 2, offset + 9 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[23], offset + 0, offset + 1, buttonGoodReleasedColor ); fheroes2::SetPixel( released[23], offset + 1, offset + 9, buttonGoodReleasedColor ); fheroes2::SetPixel( released[23], offset + 3, offset + 9, buttonGoodReleasedColor ); // 8 released[24].resize( 7 + offset * 2, 10 + offset * 2 ); released[24].reset(); fheroes2::DrawLine( released[24], { offset + 1, offset + 0 }, { offset + 5, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[24], { offset + 0, offset + 1 }, { offset + 0, offset + 3 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[24], { offset + 6, offset + 1 }, { offset + 6, offset + 3 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[24], { offset + 1, offset + 4 }, { offset + 5, offset + 4 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[24], { offset + 0, offset + 5 }, { offset + 0, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[24], { offset + 6, offset + 5 }, { offset + 6, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[24], { offset + 1, offset + 9 }, { offset + 5, offset + 9 }, buttonGoodReleasedColor ); // 9 released[25].resize( 7 + offset * 2, 10 + offset * 2 ); released[25].reset(); fheroes2::DrawLine( released[25], { offset + 1, offset + 0 }, { offset + 5, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[25], { offset + 0, offset + 1 }, { offset + 0, offset + 4 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[25], { offset + 6, offset + 1 }, { offset + 6, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[25], { offset + 1, offset + 5 }, { offset + 5, offset + 5 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[25], { offset + 1, offset + 9 }, { offset + 5, offset + 9 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[25], offset + 0, offset + 8, buttonGoodReleasedColor ); // : released[26].resize( 2 + offset * 2, 10 + offset * 2 ); released[26].reset(); fheroes2::DrawLine( released[26], { offset + 0, offset + 3 }, { offset + 1, offset + 3 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[26], { offset + 0, offset + 4 }, { offset + 1, offset + 4 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[26], { offset + 0, offset + 8 }, { offset + 1, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[26], { offset + 0, offset + 9 }, { offset + 1, offset + 9 }, buttonGoodReleasedColor ); // ; released[27].resize( 3 + offset * 2, 11 + offset * 2 ); released[27].reset(); fheroes2::DrawLine( released[27], { offset + 1, offset + 3 }, { offset + 2, offset + 3 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[27], { offset + 1, offset + 4 }, { offset + 2, offset + 4 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[27], { offset + 1, offset + 8 }, { offset + 2, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[27], { offset + 1, offset + 9 }, { offset + 2, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[27], { offset + 0, offset + 10 }, { offset + 1, offset + 10 }, buttonGoodReleasedColor ); // < released[28].resize( 4 + offset * 2, 10 + offset * 2 ); released[28].reset(); fheroes2::DrawLine( released[28], { offset + 3, offset + 2 }, { offset + 0, offset + 5 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[28], { offset + 3, offset + 8 }, { offset + 1, offset + 6 }, buttonGoodReleasedColor ); // = released[29].resize( 6 + offset * 2, 8 + offset * 2 ); released[29].reset(); fheroes2::DrawLine( released[29], { offset + 0, offset + 3 }, { offset + 5, offset + 3 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[29], { offset + 0, offset + 7 }, { offset + 5, offset + 7 }, buttonGoodReleasedColor ); // > released[30].resize( 4 + offset * 2, 10 + offset * 2 ); released[30].reset(); fheroes2::DrawLine( released[30], { offset + 0, offset + 2 }, { offset + 3, offset + 5 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[30], { offset + 0, offset + 8 }, { offset + 2, offset + 6 }, buttonGoodReleasedColor ); // ? released[31].resize( 6 + offset * 2, 10 + offset * 2 ); released[31].reset(); fheroes2::DrawLine( released[31], { offset + 0, offset + 1 }, { offset + 0, offset + 2 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[31], { offset + 1, offset + 0 }, { offset + 4, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[31], { offset + 5, offset + 1 }, { offset + 5, offset + 2 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[31], { offset + 4, offset + 3 }, { offset + 3, offset + 5 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[31], { offset + 2, offset + 8 }, { offset + 3, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[31], { offset + 2, offset + 9 }, { offset + 3, offset + 9 }, buttonGoodReleasedColor ); // A released[33].resize( 13 + offset * 2, 10 + offset * 2 ); released[33].reset(); fheroes2::DrawLine( released[33], { offset + 0, offset + 9 }, { offset + 4, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[33], { offset + 8, offset + 9 }, { offset + 12, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[33], { offset + 5, offset + 5 }, { offset + 8, offset + 5 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[33], { offset + 2, offset + 8 }, { offset + 4, offset + 5 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[33], { offset + 7, offset + 1 }, { offset + 10, offset + 8 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[33], offset + 4, offset + 4, buttonGoodReleasedColor ); fheroes2::SetPixel( released[33], offset + 5, offset + 3, buttonGoodReleasedColor ); fheroes2::SetPixel( released[33], offset + 5, offset + 2, buttonGoodReleasedColor ); fheroes2::SetPixel( released[33], offset + 6, offset + 1, buttonGoodReleasedColor ); fheroes2::SetPixel( released[33], offset + 6, offset + 0, buttonGoodReleasedColor ); // B released[34].resize( 11 + offset * 2, 10 + offset * 2 ); released[34].reset(); fheroes2::DrawLine( released[34], { offset + 0, offset + 0 }, { offset + 8, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[34], { offset + 0, offset + 9 }, { offset + 8, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[34], { offset + 3, offset + 5 }, { offset + 9, offset + 5 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[34], { offset + 2, offset + 1 }, { offset + 2, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[34], { offset + 10, offset + 2 }, { offset + 10, offset + 4 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[34], { offset + 10, offset + 6 }, { offset + 10, offset + 7 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[34], offset + 9, offset + 8, buttonGoodReleasedColor ); fheroes2::SetPixel( released[34], offset + 9, offset + 1, buttonGoodReleasedColor ); // C released[35].resize( 10 + offset * 2, 10 + offset * 2 ); released[35].reset(); fheroes2::DrawLine( released[35], { offset + 2, offset + 0 }, { offset + 7, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[35], { offset + 0, offset + 2 }, { offset + 0, offset + 7 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[35], { offset + 2, offset + 9 }, { offset + 7, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[35], { offset + 9, offset + 0 }, { offset + 9, offset + 2 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[35], offset + 1, offset + 1, buttonGoodReleasedColor ); fheroes2::SetPixel( released[35], offset + 1, offset + 8, buttonGoodReleasedColor ); fheroes2::SetPixel( released[35], offset + 8, offset + 1, buttonGoodReleasedColor ); fheroes2::SetPixel( released[35], offset + 8, offset + 8, buttonGoodReleasedColor ); fheroes2::SetPixel( released[35], offset + 9, offset + 7, buttonGoodReleasedColor ); // D released[36].resize( 11 + offset * 2, 10 + offset * 2 ); released[36].reset(); fheroes2::DrawLine( released[36], { offset + 0, offset + 0 }, { offset + 8, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[36], { offset + 0, offset + 9 }, { offset + 8, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[36], { offset + 2, offset + 1 }, { offset + 2, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[36], { offset + 10, offset + 2 }, { offset + 10, offset + 7 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[36], offset + 9, offset + 1, buttonGoodReleasedColor ); fheroes2::SetPixel( released[36], offset + 9, offset + 8, buttonGoodReleasedColor ); // E released[37].resize( 9 + offset * 2, 10 + offset * 2 ); released[37].reset(); fheroes2::DrawLine( released[37], { offset + 0, offset + 0 }, { offset + 8, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[37], { offset + 0, offset + 9 }, { offset + 8, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[37], { offset + 2, offset + 1 }, { offset + 2, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[37], { offset + 3, offset + 4 }, { offset + 6, offset + 4 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[37], offset + 8, offset + 1, buttonGoodReleasedColor ); fheroes2::SetPixel( released[37], offset + 8, offset + 8, buttonGoodReleasedColor ); fheroes2::SetPixel( released[37], offset + 6, offset + 3, buttonGoodReleasedColor ); fheroes2::SetPixel( released[37], offset + 6, offset + 5, buttonGoodReleasedColor ); // F released[38].resize( 9 + offset * 2, 10 + offset * 2 ); released[38].reset(); fheroes2::DrawLine( released[38], { offset + 0, offset + 0 }, { offset + 8, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[38], { offset + 0, offset + 9 }, { offset + 3, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[38], { offset + 2, offset + 1 }, { offset + 2, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[38], { offset + 3, offset + 4 }, { offset + 6, offset + 4 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[38], offset + 8, offset + 1, buttonGoodReleasedColor ); fheroes2::SetPixel( released[38], offset + 6, offset + 3, buttonGoodReleasedColor ); fheroes2::SetPixel( released[38], offset + 6, offset + 5, buttonGoodReleasedColor ); // G released[39].resize( 11 + offset * 2, 10 + offset * 2 ); released[39].reset(); fheroes2::DrawLine( released[39], { offset + 2, offset + 0 }, { offset + 7, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[39], { offset + 2, offset + 9 }, { offset + 7, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[39], { offset + 0, offset + 2 }, { offset + 0, offset + 7 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[39], { offset + 7, offset + 5 }, { offset + 10, offset + 5 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[39], { offset + 9, offset + 0 }, { offset + 9, offset + 2 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[39], { offset + 9, offset + 6 }, { offset + 9, offset + 7 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[39], offset + 1, offset + 1, buttonGoodReleasedColor ); fheroes2::SetPixel( released[39], offset + 1, offset + 8, buttonGoodReleasedColor ); fheroes2::SetPixel( released[39], offset + 8, offset + 1, buttonGoodReleasedColor ); fheroes2::SetPixel( released[39], offset + 8, offset + 8, buttonGoodReleasedColor ); // H released[40].resize( 13 + offset * 2, 10 + offset * 2 ); released[40].reset(); fheroes2::DrawLine( released[40], { offset + 0, offset + 0 }, { offset + 4, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[40], { offset + 0, offset + 9 }, { offset + 4, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[40], { offset + 2, offset + 1 }, { offset + 2, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[40], { offset + 8, offset + 0 }, { offset + 12, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[40], { offset + 8, offset + 9 }, { offset + 12, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[40], { offset + 10, offset + 1 }, { offset + 10, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[40], { offset + 3, offset + 5 }, { offset + 9, offset + 5 }, buttonGoodReleasedColor ); // I released[41].resize( 5 + offset * 2, 10 + offset * 2 ); released[41].reset(); fheroes2::DrawLine( released[41], { offset + 0, offset + 0 }, { offset + 4, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[41], { offset + 0, offset + 9 }, { offset + 4, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[41], { offset + 2, offset + 1 }, { offset + 2, offset + 8 }, buttonGoodReleasedColor ); // J released[42].resize( 8 + offset * 2, 10 + offset * 2 ); released[42].reset(); fheroes2::DrawLine( released[42], { offset + 3, offset + 0 }, { offset + 7, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[42], { offset + 1, offset + 9 }, { offset + 4, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[42], { offset + 5, offset + 1 }, { offset + 5, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[42], { offset + 0, offset + 7 }, { offset + 0, offset + 8 }, buttonGoodReleasedColor ); // K released[43].resize( 12 + offset * 2, 10 + offset * 2 ); released[43].reset(); fheroes2::DrawLine( released[43], { offset + 0, offset + 0 }, { offset + 4, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[43], { offset + 0, offset + 9 }, { offset + 4, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[43], { offset + 2, offset + 1 }, { offset + 2, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[43], { offset + 3, offset + 4 }, { offset + 5, offset + 4 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[43], { offset + 6, offset + 3 }, { offset + 8, offset + 1 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[43], { offset + 6, offset + 5 }, { offset + 9, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[43], { offset + 7, offset + 0 }, { offset + 10, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[43], { offset + 8, offset + 9 }, { offset + 11, offset + 9 }, buttonGoodReleasedColor ); // L released[44].resize( 9 + offset * 2, 10 + offset * 2 ); released[44].reset(); fheroes2::DrawLine( released[44], { offset + 0, offset + 0 }, { offset + 4, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[44], { offset + 0, offset + 9 }, { offset + 8, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[44], { offset + 2, offset + 1 }, { offset + 2, offset + 8 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[44], offset + 8, offset + 8, buttonGoodReleasedColor ); // M released[45].resize( 15 + offset * 2, 10 + offset * 2 ); released[45].reset(); fheroes2::DrawLine( released[45], { offset + 0, offset + 0 }, { offset + 2, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[45], { offset + 2, offset + 0 }, { offset + 2, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[45], { offset + 0, offset + 9 }, { offset + 4, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[45], { offset + 3, offset + 1 }, { offset + 7, offset + 5 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[45], { offset + 8, offset + 4 }, { offset + 11, offset + 1 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[45], { offset + 12, offset + 1 }, { offset + 12, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[45], { offset + 12, offset + 0 }, { offset + 14, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[45], { offset + 10, offset + 9 }, { offset + 14, offset + 9 }, buttonGoodReleasedColor ); // N released[46].resize( 14 + offset * 2, 10 + offset * 2 ); released[46].reset(); fheroes2::DrawLine( released[46], { offset + 0, offset + 0 }, { offset + 1, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[46], { offset + 2, offset + 0 }, { offset + 2, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[46], { offset + 0, offset + 9 }, { offset + 4, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[46], { offset + 3, offset + 1 }, { offset + 10, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[46], { offset + 9, offset + 0 }, { offset + 13, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[46], { offset + 11, offset + 0 }, { offset + 11, offset + 9 }, buttonGoodReleasedColor ); // O released[47].resize( 10 + offset * 2, 10 + offset * 2 ); released[47].reset(); fheroes2::DrawLine( released[47], { offset + 2, offset + 0 }, { offset + 7, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[47], { offset + 0, offset + 2 }, { offset + 0, offset + 7 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[47], { offset + 2, offset + 9 }, { offset + 7, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[47], { offset + 9, offset + 2 }, { offset + 9, offset + 7 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[47], offset + 1, offset + 1, buttonGoodReleasedColor ); fheroes2::SetPixel( released[47], offset + 8, offset + 1, buttonGoodReleasedColor ); fheroes2::SetPixel( released[47], offset + 1, offset + 8, buttonGoodReleasedColor ); fheroes2::SetPixel( released[47], offset + 8, offset + 8, buttonGoodReleasedColor ); // P released[48].resize( 11 + offset * 2, 10 + offset * 2 ); released[48].reset(); fheroes2::DrawLine( released[48], { offset + 0, offset + 0 }, { offset + 9, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[48], { offset + 0, offset + 9 }, { offset + 4, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[48], { offset + 3, offset + 5 }, { offset + 9, offset + 5 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[48], { offset + 2, offset + 1 }, { offset + 2, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[48], { offset + 10, offset + 1 }, { offset + 10, offset + 4 }, buttonGoodReleasedColor ); // Q released[49].resize( 11 + offset * 2, 11 + offset * 2 ); released[49].reset(); fheroes2::DrawLine( released[49], { offset + 2, offset + 0 }, { offset + 7, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[49], { offset + 0, offset + 2 }, { offset + 0, offset + 7 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[49], { offset + 2, offset + 9 }, { offset + 7, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[49], { offset + 9, offset + 2 }, { offset + 9, offset + 7 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[49], { offset + 4, offset + 7 }, { offset + 5, offset + 7 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[49], { offset + 6, offset + 7 }, { offset + 9, offset + 10 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[49], { offset + 10, offset + 10 }, { offset + 11, offset + 10 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[49], offset + 1, offset + 1, buttonGoodReleasedColor ); fheroes2::SetPixel( released[49], offset + 8, offset + 1, buttonGoodReleasedColor ); fheroes2::SetPixel( released[49], offset + 1, offset + 8, buttonGoodReleasedColor ); fheroes2::SetPixel( released[49], offset + 8, offset + 8, buttonGoodReleasedColor ); fheroes2::SetPixel( released[49], offset + 12, offset + 9, buttonGoodReleasedColor ); // R released[50].resize( 12 + offset * 2, 10 + offset * 2 ); released[50].reset(); fheroes2::DrawLine( released[50], { offset + 0, offset + 0 }, { offset + 9, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[50], { offset + 0, offset + 9 }, { offset + 4, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[50], { offset + 8, offset + 9 }, { offset + 11, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[50], { offset + 3, offset + 5 }, { offset + 9, offset + 5 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[50], { offset + 2, offset + 1 }, { offset + 2, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[50], { offset + 10, offset + 1 }, { offset + 10, offset + 4 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[50], { offset + 7, offset + 6 }, { offset + 9, offset + 8 }, buttonGoodReleasedColor ); // S released[51].resize( 9 + offset * 2, 10 + offset * 2 ); released[51].reset(); fheroes2::DrawLine( released[51], { offset + 1, offset + 0 }, { offset + 7, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[51], { offset + 0, offset + 1 }, { offset + 0, offset + 3 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[51], { offset + 1, offset + 4 }, { offset + 7, offset + 4 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[51], { offset + 8, offset + 5 }, { offset + 8, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[51], { offset + 1, offset + 9 }, { offset + 7, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[51], { offset + 0, offset + 8 }, { offset + 1, offset + 8 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[51], offset + 8, offset + 1, buttonGoodReleasedColor ); // T released[52].resize( 11 + offset * 2, 10 + offset * 2 ); released[52].reset(); fheroes2::DrawLine( released[52], { offset + 0, offset + 0 }, { offset + 10, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[52], { offset + 5, offset + 1 }, { offset + 5, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[52], { offset + 0, offset + 1 }, { offset + 0, offset + 2 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[52], { offset + 10, offset + 1 }, { offset + 10, offset + 2 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[52], { offset + 4, offset + 9 }, { offset + 6, offset + 9 }, buttonGoodReleasedColor ); // U released[53].resize( 13 + offset * 2, 10 + offset * 2 ); released[53].reset(); fheroes2::DrawLine( released[53], { offset + 0, offset + 0 }, { offset + 4, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[53], { offset + 8, offset + 0 }, { offset + 12, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[53], { offset + 2, offset + 1 }, { offset + 2, offset + 7 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[53], { offset + 10, offset + 1 }, { offset + 10, offset + 7 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[53], { offset + 4, offset + 9 }, { offset + 8, offset + 9 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[53], offset + 3, offset + 8, buttonGoodReleasedColor ); fheroes2::SetPixel( released[53], offset + 9, offset + 8, buttonGoodReleasedColor ); // V released[54].resize( 11 + offset * 2, 10 + offset * 2 ); released[54].reset(); fheroes2::DrawLine( released[54], { offset + 0, offset + 0 }, { offset + 4, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[54], { offset + 6, offset + 0 }, { offset + 10, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[54], { offset + 2, offset + 1 }, { offset + 5, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[54], { offset + 8, offset + 1 }, { offset + 6, offset + 7 }, buttonGoodReleasedColor ); // W released[55].resize( 17 + offset * 2, 10 + offset * 2 ); released[55].reset(); fheroes2::DrawLine( released[55], { offset + 0, offset + 0 }, { offset + 4, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[55], { offset + 7, offset + 0 }, { offset + 9, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[55], { offset + 12, offset + 0 }, { offset + 16, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[55], { offset + 2, offset + 1 }, { offset + 5, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[55], { offset + 8, offset + 1 }, { offset + 6, offset + 7 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[55], { offset + 9, offset + 3 }, { offset + 10, offset + 7 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[55], { offset + 14, offset + 1 }, { offset + 11, offset + 9 }, buttonGoodReleasedColor ); // X released[56].resize( 12 + offset * 2, 10 + offset * 2 ); released[56].reset(); fheroes2::DrawLine( released[56], { offset + 0, offset + 0 }, { offset + 3, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[56], { offset + 8, offset + 0 }, { offset + 11, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[56], { offset + 0, offset + 9 }, { offset + 3, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[56], { offset + 8, offset + 9 }, { offset + 11, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[56], { offset + 2, offset + 1 }, { offset + 9, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[56], { offset + 2, offset + 8 }, { offset + 9, offset + 1 }, buttonGoodReleasedColor ); // Y released[57].resize( 11 + offset * 2, 10 + offset * 2 ); released[57].reset(); fheroes2::DrawLine( released[57], { offset + 0, offset + 0 }, { offset + 3, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[57], { offset + 7, offset + 0 }, { offset + 10, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[57], { offset + 2, offset + 1 }, { offset + 4, offset + 3 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[57], { offset + 6, offset + 3 }, { offset + 8, offset + 1 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[57], { offset + 5, offset + 4 }, { offset + 5, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[57], { offset + 3, offset + 9 }, { offset + 7, offset + 9 }, buttonGoodReleasedColor ); // Z released[58].resize( 9 + offset * 2, 10 + offset * 2 ); released[58].reset(); fheroes2::DrawLine( released[58], { offset + 0, offset + 0 }, { offset + 8, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[58], { offset + 0, offset + 9 }, { offset + 8, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[58], { offset + 7, offset + 1 }, { offset + 0, offset + 8 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[58], offset + 0, offset + 1, buttonGoodReleasedColor ); fheroes2::SetPixel( released[58], offset + 8, offset + 8, buttonGoodReleasedColor ); // [ released[59].resize( 4 + offset * 2, 10 + offset * 2 ); released[59].reset(); fheroes2::DrawLine( released[59], { offset + 0, offset + 0 }, { offset + 0, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[59], { offset + 1, offset + 0 }, { offset + 3, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[59], { offset + 1, offset + 9 }, { offset + 3, offset + 9 }, buttonGoodReleasedColor ); /* \ */ released[60].resize( 4 + offset * 2, 10 + offset * 2 ); released[60].reset(); fheroes2::DrawLine( released[60], { offset + 0, offset + 0 }, { offset + 3, offset + 9 }, buttonGoodReleasedColor ); // ] released[61].resize( 4 + offset * 2, 10 + offset * 2 ); released[61].reset(); fheroes2::DrawLine( released[61], { offset + 3, offset + 0 }, { offset + 3, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[61], { offset + 0, offset + 0 }, { offset + 2, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[61], { offset + 0, offset + 9 }, { offset + 2, offset + 9 }, buttonGoodReleasedColor ); // ^ released[62].resize( 5 + offset * 2, 3 + offset * 2 ); released[62].reset(); fheroes2::DrawLine( released[62], { offset + 0, offset + 2 }, { offset + 2, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[62], { offset + 3, offset + 1 }, { offset + 4, offset + 2 }, buttonGoodReleasedColor ); // _ released[63].resize( 8 + offset * 2, 11 + offset * 2 ); released[63].reset(); fheroes2::DrawLine( released[63], { offset + 0, offset + 10 }, { offset + 7, offset + 10 }, buttonGoodReleasedColor ); // | - replaced with Caps Lock symbol for virtual keyboard // TODO: put the Caps Lock symbol to a special font to not replace any other ASCII character. released[92].resize( 11 + offset * 2, 11 + offset * 2 ); released[92].reset(); fheroes2::SetPixel( released[92], offset + 5, offset + 0, buttonGoodReleasedColor ); fheroes2::DrawLine( released[92], { offset + 4, offset + 1 }, { offset + 6, offset + 1 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[92], { offset + 3, offset + 2 }, { offset + 7, offset + 2 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[92], { offset + 2, offset + 3 }, { offset + 8, offset + 3 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[92], { offset + 1, offset + 4 }, { offset + 9, offset + 4 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[92], { offset + 0, offset + 5 }, { offset + 10, offset + 5 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[92], { offset + 3, offset + 6 }, { offset + 7, offset + 6 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[92], { offset + 3, offset + 7 }, { offset + 7, offset + 7 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[92], { offset + 3, offset + 9 }, { offset + 7, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[92], { offset + 3, offset + 10 }, { offset + 7, offset + 10 }, buttonGoodReleasedColor ); // ~ - replaced with Backspace symbol ( & released ) { // Increase size to fit full CP1252 set of characters. Fill with 1px transparent images. const fheroes2::Sprite firstSprite{ released[0] }; released.insert( released.end(), 160, firstSprite ); // We need 2 pixels from all sides of a letter to add extra effects. const int32_t offset = 2; // Offset letters with diacritics above them. for ( const int & charCode : { 138, 140, 141, 142, 143, 175, 192, 193, 194, 195, 196, 197, 198, 200, 201, 203, 204, 205, 206, 207, 209, 210, 211, 212, 213, 214, 216, 218, 219, 220, 221 } ) { released[charCode - 32].setPosition( buttonFontOffset.x, buttonFontOffset.y - 3 ); } released[217 - 32].setPosition( buttonFontOffset.x, buttonFontOffset.y - 4 ); // S with caron. released[138 - 32].resize( released[83 - 32].width(), released[83 - 32].height() + 3 ); released[138 - 32].reset(); fheroes2::Copy( released[83 - 32], 0, 0, released[138 - 32], 0, 3, released[83 - 32].width(), released[83 - 32].height() ); fheroes2::SetPixel( released[138 - 32], offset + 3, offset + 0, buttonGoodReleasedColor ); fheroes2::DrawLine( released[138 - 32], { offset + 4, offset + 1 }, { offset + 5, offset + 0 }, buttonGoodReleasedColor ); // S with acute accent. released[140 - 32].resize( released[83 - 32].width(), released[83 - 32].height() + 3 ); released[140 - 32].reset(); fheroes2::Copy( released[83 - 32], 0, 0, released[140 - 32], 0, 3, released[83 - 32].width(), released[83 - 32].height() ); fheroes2::DrawLine( released[140 - 32], { offset + 4, offset + 1 }, { offset + 5, offset + 0 }, buttonGoodReleasedColor ); // T with caron. released[141 - 32].resize( released[84 - 32].width(), released[84 - 32].height() + 3 ); released[141 - 32].reset(); fheroes2::Copy( released[84 - 32], 0, 0, released[141 - 32], 0, 3, released[84 - 32].width(), released[84 - 32].height() ); fheroes2::SetPixel( released[141 - 32], offset + 4, offset + 0, buttonGoodReleasedColor ); fheroes2::DrawLine( released[141 - 32], { offset + 5, offset + 1 }, { offset + 6, offset + 0 }, buttonGoodReleasedColor ); // Z with caron. released[142 - 32].resize( released[90 - 32].width(), released[90 - 32].height() + 3 ); released[142 - 32].reset(); fheroes2::Copy( released[90 - 32], 0, 0, released[142 - 32], 0, 3, released[90 - 32].width(), released[90 - 32].height() ); fheroes2::SetPixel( released[142 - 32], offset + 3, offset + 0, buttonGoodReleasedColor ); fheroes2::DrawLine( released[142 - 32], { offset + 4, offset + 1 }, { offset + 5, offset + 0 }, buttonGoodReleasedColor ); // Z with acute. released[143 - 32].resize( released[90 - 32].width(), released[90 - 32].height() + 3 ); released[143 - 32].reset(); fheroes2::Copy( released[90 - 32], 0, 0, released[143 - 32], 0, 3, released[90 - 32].width(), released[90 - 32].height() ); fheroes2::DrawLine( released[143 - 32], { offset + 4, offset + 1 }, { offset + 5, offset + 0 }, buttonGoodReleasedColor ); // L with stroke. released[163 - 32] = released[76 - 32]; fheroes2::DrawLine( released[131], { offset + 1, offset + 6 }, { offset + 5, offset + 2 }, buttonGoodReleasedColor ); // A with ogonek. released[165 - 32].resize( released[65 - 32].width(), released[65 - 32].height() + 3 ); released[165 - 32].reset(); fheroes2::Copy( released[65 - 32], 0, 0, released[165 - 32], 0, 0, released[65 - 32].width(), released[65 - 32].height() ); fheroes2::DrawLine( released[165 - 32], { offset + 9, offset + 11 }, { offset + 10, offset + 10 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[165 - 32], { offset + 10, offset + 12 }, { offset + 11, offset + 12 }, buttonGoodReleasedColor ); // S with cedilla. released[170 - 32].resize( released[83 - 32].width(), released[83 - 32].height() + 3 ); released[170 - 32].reset(); fheroes2::Copy( released[83 - 32], 0, 0, released[170 - 32], 0, 0, released[83 - 32].width(), released[83 - 32].height() ); fheroes2::DrawLine( released[170 - 32], { offset + 4, offset + 10 }, { offset + 5, offset + 11 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[170 - 32], { offset + 3, offset + 12 }, { offset + 4, offset + 12 }, buttonGoodReleasedColor ); // Z with dot above. released[175 - 32].resize( released[90 - 32].width(), released[90 - 32].height() + 3 ); released[175 - 32].reset(); fheroes2::Copy( released[90 - 32], 0, 0, released[175 - 32], 0, 3, released[90 - 32].width(), released[90 - 32].height() ); fheroes2::DrawLine( released[175 - 32], { offset + 4, offset + 1 }, { offset + 5, offset + 1 }, buttonGoodReleasedColor ); // L with caron. released[188 - 32] = released[76 - 32]; fheroes2::DrawLine( released[188 - 32], { offset + 6, offset + 0 }, { offset + 7, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[188 - 32], { offset + 6, offset + 2 }, { offset + 7, offset + 1 }, buttonGoodReleasedColor ); // R with acute. released[192 - 32].resize( released[82 - 32].width(), released[82 - 32].height() + 3 ); released[192 - 32].reset(); fheroes2::Copy( released[82 - 32], 0, 0, released[192 - 32], 0, 3, released[82 - 32].width(), released[82 - 32].height() ); fheroes2::DrawLine( released[192 - 32], { offset + 5, offset + 1 }, { offset + 6, offset + 0 }, buttonGoodReleasedColor ); // A with acute. released[193 - 32].resize( released[65 - 32].width(), released[65 - 32].height() + 3 ); released[193 - 32].reset(); fheroes2::Copy( released[65 - 32], 0, 0, released[193 - 32], 0, 3, released[65 - 32].width(), released[65 - 32].height() ); fheroes2::DrawLine( released[193 - 32], { offset + 6, offset + 1 }, { offset + 7, offset + 0 }, buttonGoodReleasedColor ); // A with circumflex. released[194 - 32].resize( released[65 - 32].width(), released[65 - 32].height() + 3 ); released[194 - 32].reset(); fheroes2::Copy( released[65 - 32], 0, 0, released[194 - 32], 0, 3, released[65 - 32].width(), released[65 - 32].height() ); fheroes2::SetPixel( released[194 - 32], offset + 5, offset + 1, buttonGoodReleasedColor ); fheroes2::DrawLine( released[194 - 32], { offset + 6, offset + 0 }, { offset + 7, offset + 1 }, buttonGoodReleasedColor ); // A with breve. released[195 - 32].resize( released[65 - 32].width(), released[65 - 32].height() + 3 ); released[195 - 32].reset(); fheroes2::Copy( released[65 - 32], 0, 0, released[195 - 32], 0, 3, released[65 - 32].width(), released[65 - 32].height() ); fheroes2::DrawLine( released[195 - 32], { offset + 5, offset + 0 }, { offset + 6, offset + 1 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[195 - 32], { offset + 7, offset + 1 }, { offset + 8, offset + 0 }, buttonGoodReleasedColor ); // A with diaeresis. released[196 - 32].resize( released[65 - 32].width(), released[65 - 32].height() + 3 ); released[196 - 32].reset(); fheroes2::Copy( released[65 - 32], 0, 0, released[196 - 32], 0, 3, released[65 - 32].width(), released[65 - 32].height() ); fheroes2::DrawLine( released[196 - 32], { offset + 4, offset + 1 }, { offset + 5, offset + 1 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[196 - 32], { offset + 8, offset + 1 }, { offset + 9, offset + 1 }, buttonGoodReleasedColor ); // L with acute. released[197 - 32].resize( released[76 - 32].width(), released[76 - 32].height() + 3 ); released[197 - 32].reset(); fheroes2::Copy( released[76 - 32], 0, 0, released[197 - 32], 0, 3, released[76 - 32].width(), released[76 - 32].height() ); fheroes2::DrawLine( released[197 - 32], { offset + 3, offset + 1 }, { offset + 4, offset + 0 }, buttonGoodReleasedColor ); // C with acute accent. released[198 - 32].resize( released[67 - 32].width(), released[67 - 32].height() + 3 ); released[198 - 32].reset(); fheroes2::Copy( released[67 - 32], 0, 0, released[198 - 32], 0, 3, released[67 - 32].width(), released[67 - 32].height() ); fheroes2::DrawLine( released[198 - 32], { offset + 5, offset + 1 }, { offset + 6, offset + 0 }, buttonGoodReleasedColor ); // C with cedilla. released[199 - 32].resize( released[67 - 32].width(), released[67 - 32].height() + 3 ); released[199 - 32].reset(); fheroes2::Copy( released[67 - 32], 0, 0, released[199 - 32], 0, 0, released[67 - 32].width(), released[67 - 32].height() ); fheroes2::DrawLine( released[199 - 32], { offset + 5, offset + 10 }, { offset + 6, offset + 11 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[199 - 32], { offset + 4, offset + 12 }, { offset + 5, offset + 12 }, buttonGoodReleasedColor ); // C with caron. released[200 - 32].resize( released[67 - 32].width(), released[67 - 32].height() + 3 ); released[200 - 32].reset(); fheroes2::Copy( released[67 - 32], 0, 0, released[200 - 32], 0, 3, released[67 - 32].width(), released[67 - 32].height() ); fheroes2::SetPixel( released[200 - 32], offset + 4, offset + 0, buttonGoodReleasedColor ); fheroes2::DrawLine( released[200 - 32], { offset + 5, offset + 1 }, { offset + 6, offset + 0 }, buttonGoodReleasedColor ); // E with acute. released[201 - 32].resize( released[69 - 32].width(), released[69 - 32].height() + 3 ); released[201 - 32].reset(); fheroes2::Copy( released[69 - 32], 0, 0, released[201 - 32], 0, 3, released[69 - 32].width(), released[69 - 32].height() ); fheroes2::DrawLine( released[201 - 32], { offset + 5, offset + 1 }, { offset + 6, offset + 0 }, buttonGoodReleasedColor ); // E with ogonek. released[202 - 32].resize( released[69 - 32].width(), released[69 - 32].height() + 3 ); released[202 - 32].reset(); fheroes2::Copy( released[69 - 32], 0, 0, released[202 - 32], 0, 0, released[69 - 32].width(), released[69 - 32].height() ); fheroes2::DrawLine( released[202 - 32], { offset + 6, offset + 11 }, { offset + 7, offset + 10 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[202 - 32], { offset + 7, offset + 12 }, { offset + 8, offset + 12 }, buttonGoodReleasedColor ); // E with diaeresis. released[203 - 32].resize( released[69 - 32].width(), released[69 - 32].height() + 3 ); released[203 - 32].reset(); fheroes2::Copy( released[69 - 32], 0, 0, released[203 - 32], 0, 3, released[69 - 32].width(), released[69 - 32].height() ); fheroes2::DrawLine( released[203 - 32], { offset + 2, offset + 1 }, { offset + 3, offset + 1 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[203 - 32], { offset + 6, offset + 1 }, { offset + 7, offset + 1 }, buttonGoodReleasedColor ); // E with caron. released[204 - 32].resize( released[69 - 32].width(), released[69 - 32].height() + 3 ); released[204 - 32].reset(); fheroes2::Copy( released[69 - 32], 0, 0, released[204 - 32], 0, 3, released[69 - 32].width(), released[69 - 32].height() ); fheroes2::SetPixel( released[204 - 32], offset + 4, offset + 0, buttonGoodReleasedColor ); fheroes2::DrawLine( released[204 - 32], { offset + 5, offset + 1 }, { offset + 6, offset + 0 }, buttonGoodReleasedColor ); // I with acute. released[205 - 32].resize( released[73 - 32].width(), released[73 - 32].height() + 3 ); released[205 - 32].reset(); fheroes2::Copy( released[73 - 32], 0, 0, released[205 - 32], 0, 3, released[73 - 32].width(), released[73 - 32].height() ); fheroes2::DrawLine( released[205 - 32], { offset + 2, offset + 1 }, { offset + 3, offset + 0 }, buttonGoodReleasedColor ); // I with circumflex. released[206 - 32].resize( released[73 - 32].width(), released[73 - 32].height() + 3 ); released[206 - 32].reset(); fheroes2::Copy( released[73 - 32], 0, 0, released[206 - 32], 0, 3, released[73 - 32].width(), released[73 - 32].height() ); fheroes2::SetPixel( released[206 - 32], offset + 1, offset + 1, buttonGoodReleasedColor ); fheroes2::DrawLine( released[206 - 32], { offset + 2, offset + 0 }, { offset + 3, offset + 1 }, buttonGoodReleasedColor ); // D with caron. released[207 - 32].resize( released[68 - 32].width(), released[68 - 32].height() + 3 ); released[207 - 32].reset(); fheroes2::Copy( released[68 - 32], 0, 0, released[207 - 32], 0, 3, released[68 - 32].width(), released[68 - 32].height() ); fheroes2::SetPixel( released[207 - 32], offset + 4, offset + 0, buttonGoodReleasedColor ); fheroes2::DrawLine( released[207 - 32], { offset + 5, offset + 1 }, { offset + 6, offset + 0 }, buttonGoodReleasedColor ); // D with stroke. released[208 - 32] = released[68 - 32]; fheroes2::DrawLine( released[208 - 32], { offset + 1, offset + 4 }, { offset + 5, offset + 4 }, buttonGoodReleasedColor ); // N with acute. released[209 - 32].resize( released[78 - 32].width(), released[78 - 32].height() + 3 ); released[209 - 32].reset(); fheroes2::Copy( released[78 - 32], 0, 0, released[209 - 32], 0, 3, released[78 - 32].width(), released[78 - 32].height() ); fheroes2::DrawLine( released[209 - 32], { offset + 7, offset + 1 }, { offset + 8, offset + 0 }, buttonGoodReleasedColor ); // N with caron. released[210 - 32].resize( released[78 - 32].width(), released[78 - 32].height() + 3 ); released[210 - 32].reset(); fheroes2::Copy( released[78 - 32], 0, 0, released[210 - 32], 0, 3, released[78 - 32].width(), released[78 - 32].height() ); fheroes2::SetPixel( released[210 - 32], offset + 6, offset + 0, buttonGoodReleasedColor ); fheroes2::DrawLine( released[210 - 32], { offset + 7, offset + 1 }, { offset + 8, offset + 0 }, buttonGoodReleasedColor ); // O with acute. released[211 - 32].resize( released[79 - 32].width(), released[79 - 32].height() + 3 ); released[211 - 32].reset(); fheroes2::Copy( released[79 - 32], 0, 0, released[211 - 32], 0, 3, released[79 - 32].width(), released[79 - 32].height() ); fheroes2::DrawLine( released[211 - 32], { offset + 5, offset + 1 }, { offset + 6, offset + 0 }, buttonGoodReleasedColor ); // O with circumflex. released[212 - 32].resize( released[79 - 32].width(), released[79 - 32].height() + 3 ); released[212 - 32].reset(); fheroes2::Copy( released[79 - 32], 0, 0, released[212 - 32], 0, 3, released[79 - 32].width(), released[79 - 32].height() ); fheroes2::SetPixel( released[212 - 32], offset + 4, offset + 1, buttonGoodReleasedColor ); fheroes2::DrawLine( released[212 - 32], { offset + 5, offset + 0 }, { offset + 6, offset + 1 }, buttonGoodReleasedColor ); // O with double acute. released[213 - 32].resize( released[79 - 32].width(), released[79 - 32].height() + 3 ); released[213 - 32].reset(); fheroes2::Copy( released[79 - 32], 0, 0, released[213 - 32], 0, 3, released[79 - 32].width(), released[79 - 32].height() ); fheroes2::DrawLine( released[213 - 32], { offset + 3, offset + 1 }, { offset + 4, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[213 - 32], { offset + 6, offset + 1 }, { offset + 7, offset + 0 }, buttonGoodReleasedColor ); // O with diaeresis. released[214 - 32].resize( released[79 - 32].width(), released[79 - 32].height() + 3 ); released[214 - 32].reset(); fheroes2::Copy( released[79 - 32], 0, 0, released[214 - 32], 0, 3, released[79 - 32].width(), released[79 - 32].height() ); fheroes2::DrawLine( released[214 - 32], { offset + 2, offset + 1 }, { offset + 3, offset + 1 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[214 - 32], { offset + 6, offset + 1 }, { offset + 7, offset + 1 }, buttonGoodReleasedColor ); // R with caron. released[216 - 32].resize( released[82 - 32].width(), released[82 - 32].height() + 3 ); released[216 - 32].reset(); fheroes2::Copy( released[82 - 32], 0, 0, released[216 - 32], 0, 3, released[82 - 32].width(), released[82 - 32].height() ); fheroes2::SetPixel( released[216 - 32], offset + 5, offset + 0, buttonGoodReleasedColor ); fheroes2::DrawLine( released[216 - 32], { offset + 6, offset + 1 }, { offset + 7, offset + 0 }, buttonGoodReleasedColor ); // U with ring above. released[217 - 32].resize( released[85 - 32].width(), released[85 - 32].height() + 4 ); released[217 - 32].reset(); fheroes2::Copy( released[85 - 32], 0, 0, released[217 - 32], 0, 4, released[85 - 32].width(), released[85 - 32].height() ); fheroes2::DrawLine( released[217 - 32], { offset + 5, offset + 1 }, { offset + 6, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[217 - 32], { offset + 6, offset + 2 }, { offset + 7, offset + 1 }, buttonGoodReleasedColor ); // U with acute. released[218 - 32].resize( released[85 - 32].width(), released[85 - 32].height() + 3 ); released[218 - 32].reset(); fheroes2::Copy( released[85 - 32], 0, 0, released[218 - 32], 0, 3, released[85 - 32].width(), released[85 - 32].height() ); fheroes2::DrawLine( released[218 - 32], { offset + 6, offset + 1 }, { offset + 7, offset + 0 }, buttonGoodReleasedColor ); // U with double acute. released[219 - 32].resize( released[85 - 32].width(), released[85 - 32].height() + 3 ); released[219 - 32].reset(); fheroes2::Copy( released[85 - 32], 0, 0, released[219 - 32], 0, 3, released[85 - 32].width(), released[85 - 32].height() ); fheroes2::DrawLine( released[219 - 32], { offset + 5, offset + 1 }, { offset + 6, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[219 - 32], { offset + 8, offset + 1 }, { offset + 9, offset + 0 }, buttonGoodReleasedColor ); // U with diaeresis. released[220 - 32].resize( released[85 - 32].width(), released[85 - 32].height() + 3 ); released[220 - 32].reset(); fheroes2::Copy( released[85 - 32], 0, 0, released[220 - 32], 0, 3, released[85 - 32].width(), released[85 - 32].height() ); fheroes2::DrawLine( released[220 - 32], { offset + 4, offset + 1 }, { offset + 5, offset + 1 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[220 - 32], { offset + 8, offset + 1 }, { offset + 9, offset + 1 }, buttonGoodReleasedColor ); // Y with acute. released[221 - 32].resize( released[89 - 32].width(), released[89 - 32].height() + 3 ); released[221 - 32].reset(); fheroes2::Copy( released[89 - 32], 0, 0, released[221 - 32], 0, 3, released[89 - 32].width(), released[89 - 32].height() ); fheroes2::DrawLine( released[221 - 32], { offset + 5, offset + 1 }, { offset + 6, offset + 0 }, buttonGoodReleasedColor ); // T with cedilla. Only copied from T. released[222 - 32].resize( released[84 - 32].width(), released[84 - 32].height() + 3 ); released[222 - 32].reset(); fheroes2::Copy( released[84 - 32], 0, 0, released[222 - 32], 0, 0, released[84 - 32].width(), released[84 - 32].height() ); fheroes2::DrawLine( released[222 - 32], { offset + 5, offset + 10 }, { offset + 6, offset + 11 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[222 - 32], { offset + 4, offset + 12 }, { offset + 5, offset + 12 }, buttonGoodReleasedColor ); } void generateCP1251GoodButtonFont( std::vector & released ) { // Increase size to fit full CP1252 set of characters. Fill with 1px transparent images. const fheroes2::Sprite firstSprite{ released[0] }; released.insert( released.end(), 160, firstSprite ); // We need 2 pixels from all sides of a letter to add extra effects. const int32_t offset = 2; // Offset symbols that either have diacritics or need less space to neighboring symbols. released[109].setPosition( buttonFontOffset.x, buttonFontOffset.y - 3 ); released[129].setPosition( buttonFontOffset.x, buttonFontOffset.y - 3 ); released[136].setPosition( buttonFontOffset.x, buttonFontOffset.y - 3 ); released[143].setPosition( buttonFontOffset.x, buttonFontOffset.y - 3 ); released[160].setPosition( buttonFontOffset.x - 1, buttonFontOffset.y ); released[169].setPosition( buttonFontOffset.x, buttonFontOffset.y - 3 ); released[186].setPosition( buttonFontOffset.x - 1, buttonFontOffset.y ); // K with acute, Cyrillic KJE. Needs to have upper right arm adjusted. released[109].resize( released[43].width(), released[43].height() + 4 ); released[109].reset(); fheroes2::Copy( released[43], offset + 2, offset + 1, released[109], offset + 1, offset + 4, 7, released[43].height() - offset * 2 - 2 ); fheroes2::DrawLine( released[109], { offset + 0, offset + 3 }, { offset + 2, offset + 3 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[109], { offset + 0, offset + 12 }, { offset + 2, offset + 12 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[109], { offset + 6, offset + 3 }, { offset + 8, offset + 3 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[109], { offset + 6, offset + 12 }, { offset + 8, offset + 12 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[109], offset + 7, offset + 11, buttonGoodReleasedColor ); fheroes2::DrawLine( released[109], { offset + 5, offset + 1 }, { offset + 6, offset + 0 }, buttonGoodReleasedColor ); // ' released[114].resize( 3 + offset * 2, 4 + offset * 2 ); released[114].reset(); fheroes2::DrawLine( released[114], { offset + 1, offset + 0 }, { offset + 1, offset + 2 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[114], { offset + 2, offset + 0 }, { offset + 2, offset + 1 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[114], offset + 0, offset + 3, buttonGoodReleasedColor ); // y with breve. released[129].resize( 9 + offset * 2, 13 + offset * 2 ); released[129].reset(); fheroes2::DrawLine( released[129], { offset + 0, offset + 3 }, { offset + 2, offset + 3 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[129], { offset + 6, offset + 3 }, { offset + 8, offset + 3 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[129], { offset + 3, offset + 8 }, { offset + 1, offset + 4 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[129], { offset + 5, offset + 8 }, { offset + 7, offset + 4 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[129], { offset + 4, offset + 8 }, { offset + 3, offset + 11 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[129], { offset + 0, offset + 12 }, { offset + 2, offset + 12 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[129], offset + 0, offset + 11, buttonGoodReleasedColor ); fheroes2::DrawLine( released[129], { offset + 3, offset + 1 }, { offset + 5, offset + 1 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[129], offset + 2, offset + 0, buttonGoodReleasedColor ); fheroes2::SetPixel( released[129], offset + 6, offset + 0, buttonGoodReleasedColor ); // J released[131] = released[42]; // E with two dots above. released[136].resize( released[37].width() - 1, released[37].height() + 3 ); released[136].reset(); fheroes2::Copy( released[37], offset + 1, offset + 0, released[136], offset + 0, offset + 3, released[37].width() - 1 - offset * 2, released[37].height() - offset * 2 ); fheroes2::SetPixel( released[136], offset + 3, offset + 1, buttonGoodReleasedColor ); fheroes2::SetPixel( released[136], offset + 6, offset + 1, buttonGoodReleasedColor ); // I with two dots above, Cyrillic YI released[143].resize( released[41].width(), released[41].height() + 3 ); released[143].reset(); fheroes2::Copy( released[41], 0, 0, released[143], 0, 3, released[41].width(), released[41].height() ); fheroes2::SetPixel( released[143], offset + 1, offset + 1, buttonGoodReleasedColor ); fheroes2::SetPixel( released[143], offset + 4, offset + 1, buttonGoodReleasedColor ); // I, Belarusian-Ukrainian I released[146] = released[41]; // S released[157].resize( released[51].width() - 1, released[51].height() ); fheroes2::Copy( released[51], 0, 0, released[157], 0, 0, 8, released[51].height() ); fheroes2::Copy( released[51], 9, 0, released[157], 8, 0, released[51].width() - 9, released[51].height() ); // A released[160].resize( 10 + offset * 2, 10 + offset * 2 ); released[160].reset(); fheroes2::DrawLine( released[160], { offset + 1, offset + 9 }, { offset + 4, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[160], { offset + 5, offset + 0 }, { offset + 8, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[160], { offset + 3, offset + 0 }, { offset + 5, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[160], { offset + 0, offset + 9 }, { offset + 2, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[160], { offset + 7, offset + 9 }, { offset + 9, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[160], { offset + 2, offset + 6 }, { offset + 7, offset + 6 }, buttonGoodReleasedColor ); // 6, Cyrillic BE released[161].resize( 8 + offset * 2, 10 + offset * 2 ); released[161].reset(); fheroes2::DrawLine( released[161], { offset + 0, offset + 0 }, { offset + 7, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[161], { offset + 0, offset + 9 }, { offset + 6, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[161], { offset + 1, offset + 1 }, { offset + 1, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[161], { offset + 2, offset + 4 }, { offset + 6, offset + 4 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[161], { offset + 7, offset + 5 }, { offset + 7, offset + 8 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[161], offset + 7, offset + 1, buttonGoodReleasedColor ); // B released[162].resize( 8 + offset * 2, 10 + offset * 2 ); released[162].reset(); fheroes2::DrawLine( released[162], { offset + 0, offset + 0 }, { offset + 5, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[162], { offset + 0, offset + 9 }, { offset + 6, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[162], { offset + 1, offset + 1 }, { offset + 1, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[162], { offset + 2, offset + 4 }, { offset + 6, offset + 4 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[162], { offset + 6, offset + 1 }, { offset + 6, offset + 3 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[162], { offset + 7, offset + 5 }, { offset + 7, offset + 8 }, buttonGoodReleasedColor ); // r, Cyrillic GHE released[163].resize( 8 + offset * 2, 10 + offset * 2 ); released[163].reset(); fheroes2::DrawLine( released[163], { offset + 0, offset + 0 }, { offset + 7, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[163], { offset + 7, offset + 1 }, { offset + 7, offset + 2 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[163], { offset + 0, offset + 9 }, { offset + 2, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[163], { offset + 1, offset + 1 }, { offset + 1, offset + 8 }, buttonGoodReleasedColor ); // Cyrillic DE released[164].resize( 10 + offset * 2, 13 + offset * 2 ); released[164].reset(); fheroes2::DrawLine( released[164], { offset + 2, offset + 0 }, { offset + 8, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[164], { offset + 8, offset + 1 }, { offset + 8, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[164], { offset + 3, offset + 1 }, { offset + 3, offset + 7 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[164], { offset + 0, offset + 9 }, { offset + 9, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[164], { offset + 0, offset + 10 }, { offset + 0, offset + 11 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[164], { offset + 9, offset + 10 }, { offset + 9, offset + 11 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[164], offset + 2, offset + 8, buttonGoodReleasedColor ); // E released[165].resize( released[37].width() - 1, released[37].height() ); fheroes2::Copy( released[37], 1, 0, released[165], 0, 0, released[37].width() - 1, released[37].height() ); fheroes2::SetTransformPixel( released[165], 1, offset + 0, 1 ); fheroes2::SetTransformPixel( released[165], 1, offset + 9, 1 ); // X with vertical stroke through it, Cyrillic ZHE. Needs to have upper right and left arms adjusted. released[166].resize( released[56].width() - 1, released[56].height() ); released[166].reset(); fheroes2::Copy( released[56], offset + 1, offset + 0, released[166], offset + 0, offset + 0, 5, released[56].height() - offset * 2 ); fheroes2::Copy( released[56], offset + 6, offset + 0, released[166], offset + 6, offset + 0, 5, released[56].height() - offset * 2 ); fheroes2::DrawLine( released[166], { offset + 5, offset + 1 }, { offset + 5, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[166], { offset + 4, offset + 0 }, { offset + 6, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[166], { offset + 4, offset + 9 }, { offset + 6, offset + 9 }, buttonGoodReleasedColor ); // 3, Cyrillic ZE released[167].resize( 7 + offset * 2, 10 + offset * 2 ); released[167].reset(); fheroes2::DrawLine( released[167], { offset + 2, offset + 0 }, { offset + 5, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[167], { offset + 0, offset + 0 }, { offset + 0, offset + 2 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[167], { offset + 6, offset + 1 }, { offset + 6, offset + 3 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[167], { offset + 2, offset + 4 }, { offset + 5, offset + 4 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[167], { offset + 6, offset + 5 }, { offset + 6, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[167], { offset + 1, offset + 9 }, { offset + 5, offset + 9 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[167], offset + 1, offset + 1, buttonGoodReleasedColor ); fheroes2::SetPixel( released[167], offset + 0, offset + 8, buttonGoodReleasedColor ); // Mirrored N, Cyrillic I released[168].resize( 9 + offset * 2, 10 + offset * 2 ); released[168].reset(); fheroes2::DrawLine( released[168], { offset + 1, offset + 1 }, { offset + 1, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[168], { offset + 0, offset + 0 }, { offset + 2, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[168], { offset + 2, offset + 8 }, { offset + 6, offset + 1 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[168], { offset + 7, offset + 0 }, { offset + 7, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[168], { offset + 6, offset + 9 }, { offset + 8, offset + 9 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[168], offset + 0, offset + 9, buttonGoodReleasedColor ); fheroes2::SetPixel( released[168], offset + 8, offset + 0, buttonGoodReleasedColor ); // Mirrored N with breve, Cyrillic Short I released[169].resize( released[168].width(), released[168].height() + 3 ); released[169].reset(); fheroes2::Copy( released[168], 0, 0, released[169], 0, 3, released[168].width(), released[168].height() ); fheroes2::DrawLine( released[169], { offset + 3, offset + 1 }, { offset + 5, offset + 1 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[169], offset + 2, offset + 0, buttonGoodReleasedColor ); fheroes2::SetPixel( released[169], offset + 6, offset + 0, buttonGoodReleasedColor ); // K. released[170].resize( released[43].width() - 3, released[43].height() ); released[170].reset(); fheroes2::Copy( released[43], offset + 2, offset + 1, released[170], offset + 1, offset + 1, 7, released[43].height() - offset * 2 - 2 ); fheroes2::DrawLine( released[170], { offset + 0, offset + 0 }, { offset + 2, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[170], { offset + 0, offset + 9 }, { offset + 2, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[170], { offset + 6, offset + 0 }, { offset + 8, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[170], { offset + 6, offset + 9 }, { offset + 8, offset + 9 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[170], offset + 7, offset + 8, buttonGoodReleasedColor ); // /\, Cyrillic EL released[171].resize( 10 + offset * 2, 10 + offset * 2 ); released[171].reset(); fheroes2::DrawLine( released[171], { offset + 1, offset + 9 }, { offset + 4, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[171], { offset + 5, offset + 0 }, { offset + 8, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[171], { offset + 3, offset + 0 }, { offset + 5, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[171], { offset + 0, offset + 9 }, { offset + 2, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[171], { offset + 7, offset + 9 }, { offset + 9, offset + 9 }, buttonGoodReleasedColor ); // M released[172].resize( 9 + offset * 2, 10 + offset * 2 ); released[172].reset(); fheroes2::DrawLine( released[172], { offset + 1, offset + 0 }, { offset + 1, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[172], { offset + 0, offset + 9 }, { offset + 2, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[172], { offset + 2, offset + 1 }, { offset + 4, offset + 5 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[172], { offset + 5, offset + 4 }, { offset + 6, offset + 1 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[172], { offset + 7, offset + 0 }, { offset + 7, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[172], { offset + 6, offset + 9 }, { offset + 8, offset + 9 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[172], offset + 0, offset + 0, buttonGoodReleasedColor ); fheroes2::SetPixel( released[172], offset + 8, offset + 0, buttonGoodReleasedColor ); fheroes2::SetPixel( released[172], offset + 4, offset + 4, buttonGoodReleasedColor ); // H released[173].resize( 9 + offset * 2, 10 + offset * 2 ); released[173].reset(); fheroes2::DrawLine( released[173], { offset + 1, offset + 1 }, { offset + 1, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[173], { offset + 0, offset + 0 }, { offset + 2, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[173], { offset + 0, offset + 9 }, { offset + 2, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[173], { offset + 2, offset + 5 }, { offset + 7, offset + 5 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[173], { offset + 7, offset + 1 }, { offset + 7, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[173], { offset + 6, offset + 0 }, { offset + 8, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[173], { offset + 6, offset + 9 }, { offset + 8, offset + 9 }, buttonGoodReleasedColor ); // O released[174].resize( released[47].width() - 2, released[47].height() ); fheroes2::Copy( released[47], 0, 0, released[174], 0, 0, 7, released[47].height() ); fheroes2::Copy( released[47], 9, 0, released[174], 7, 0, released[47].width() - 9, released[47].height() ); // Cyrillic PE released[175].resize( 9 + offset * 2, 10 + offset * 2 ); released[175].reset(); fheroes2::DrawLine( released[175], { offset + 0, offset + 0 }, { offset + 8, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[175], { offset + 1, offset + 1 }, { offset + 1, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[175], { offset + 7, offset + 1 }, { offset + 7, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[175], { offset + 0, offset + 9 }, { offset + 2, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[175], { offset + 6, offset + 9 }, { offset + 8, offset + 9 }, buttonGoodReleasedColor ); // P released[176].resize( 8 + offset * 2, 10 + offset * 2 ); released[176].reset(); fheroes2::DrawLine( released[176], { offset + 1, offset + 1 }, { offset + 1, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[176], { offset + 0, offset + 0 }, { offset + 6, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[176], { offset + 2, offset + 5 }, { offset + 6, offset + 5 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[176], { offset + 0, offset + 9 }, { offset + 2, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[176], { offset + 7, offset + 1 }, { offset + 7, offset + 4 }, buttonGoodReleasedColor ); // C released[177].resize( released[35].width() - 2, released[35].height() ); fheroes2::Copy( released[35], 0, 0, released[177], 0, 0, 7, released[35].height() ); fheroes2::Copy( released[35], 9, 0, released[177], 7, 0, released[35].width() - 9, released[47].height() ); // T released[178].resize( released[52].width() - 2, released[52].height() ); fheroes2::Copy( released[52], 0, 0, released[178], 0, 0, 5, released[52].height() ); fheroes2::Copy( released[52], 6, 0, released[178], 5, 0, 3, released[52].height() ); fheroes2::Copy( released[52], 10, 0, released[178], 8, 0, 5, released[52].height() ); // y, Cyrillic U released[179].resize( 9 + offset * 2, 10 + offset * 2 ); released[179].reset(); fheroes2::DrawLine( released[179], { offset + 0, offset + 0 }, { offset + 2, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[179], { offset + 6, offset + 0 }, { offset + 8, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[179], { offset + 3, offset + 5 }, { offset + 1, offset + 1 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[179], { offset + 5, offset + 5 }, { offset + 7, offset + 1 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[179], { offset + 4, offset + 5 }, { offset + 3, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[179], { offset + 0, offset + 9 }, { offset + 2, offset + 9 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[179], offset + 0, offset + 8, buttonGoodReleasedColor ); // O with vertical bar, Cyrillic EF released[180].resize( 10 + offset * 2, 10 + offset * 2 ); released[180].reset(); fheroes2::DrawLine( released[180], { offset + 1, offset + 2 }, { offset + 7, offset + 2 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[180], { offset + 0, offset + 3 }, { offset + 0, offset + 6 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[180], { offset + 1, offset + 7 }, { offset + 7, offset + 7 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[180], { offset + 8, offset + 3 }, { offset + 8, offset + 6 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[180], { offset + 4, offset + 1 }, { offset + 4, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[180], { offset + 3, offset + 0 }, { offset + 5, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[180], { offset + 3, offset + 9 }, { offset + 5, offset + 9 }, buttonGoodReleasedColor ); // X released[181].resize( released[56].width() - 3, released[56].height() ); released[181].reset(); fheroes2::Copy( released[56], offset + 1, offset + 0, released[181], offset + 0, offset + 0, 5, released[56].height() - offset * 2 ); fheroes2::Copy( released[56], offset + 7, offset + 0, released[181], offset + 5, offset + 0, 4, released[56].height() - offset * 2 ); // Cyrillic TSE released[182].resize( 9 + offset * 2, 12 + offset * 2 ); released[182].reset(); fheroes2::DrawLine( released[182], { offset + 0, offset + 9 }, { offset + 8, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[182], { offset + 1, offset + 1 }, { offset + 1, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[182], { offset + 7, offset + 1 }, { offset + 7, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[182], { offset + 0, offset + 0 }, { offset + 2, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[182], { offset + 6, offset + 0 }, { offset + 8, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[182], { offset + 8, offset + 10 }, { offset + 8, offset + 11 }, buttonGoodReleasedColor ); // Cyrillic CHE released[183].resize( 9 + offset * 2, 10 + offset * 2 ); released[183].reset(); fheroes2::DrawLine( released[183], { offset + 0, offset + 0 }, { offset + 2, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[183], { offset + 6, offset + 0 }, { offset + 8, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[183], { offset + 1, offset + 1 }, { offset + 1, offset + 4 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[183], { offset + 7, offset + 1 }, { offset + 7, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[183], { offset + 2, offset + 5 }, { offset + 6, offset + 5 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[183], { offset + 6, offset + 9 }, { offset + 8, offset + 9 }, buttonGoodReleasedColor ); // Cyrillic SHA released[184].resize( 11 + offset * 2, 10 + offset * 2 ); released[184].reset(); fheroes2::DrawLine( released[184], { offset + 1, offset + 1 }, { offset + 1, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[184], { offset + 5, offset + 1 }, { offset + 5, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[184], { offset + 9, offset + 1 }, { offset + 9, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[184], { offset + 0, offset + 9 }, { offset + 10, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[184], { offset + 0, offset + 0 }, { offset + 2, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[184], { offset + 4, offset + 0 }, { offset + 6, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[184], { offset + 8, offset + 0 }, { offset + 10, offset + 0 }, buttonGoodReleasedColor ); // Cyrillic SHCHA released[185].resize( 11 + offset * 2, 12 + offset * 2 ); released[185].reset(); fheroes2::Copy( released[184], 0, 0, released[185], 0, 0, released[184].width(), released[184].height() ); fheroes2::DrawLine( released[185], { offset + 10, offset + 10 }, { offset + 10, offset + 11 }, buttonGoodReleasedColor ); // b, Cyrillic hard sign released[186].resize( 10 + offset * 2, 10 + offset * 2 ); released[186].reset(); fheroes2::DrawLine( released[186], { offset + 0, offset + 0 }, { offset + 3, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[186], { offset + 2, offset + 9 }, { offset + 8, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[186], { offset + 3, offset + 1 }, { offset + 3, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[186], { offset + 4, offset + 4 }, { offset + 8, offset + 4 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[186], { offset + 9, offset + 5 }, { offset + 9, offset + 8 }, buttonGoodReleasedColor ); // bI, Cyrillic YERU released[187].resize( 10 + offset * 2, 10 + offset * 2 ); released[187].reset(); fheroes2::DrawLine( released[187], { offset + 0, offset + 0 }, { offset + 2, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[187], { offset + 0, offset + 9 }, { offset + 4, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[187], { offset + 1, offset + 1 }, { offset + 1, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[187], { offset + 2, offset + 4 }, { offset + 4, offset + 4 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[187], { offset + 5, offset + 5 }, { offset + 5, offset + 8 }, buttonGoodReleasedColor ); fheroes2::Copy( released[41], offset + 1, 0, released[187], offset + 7, 0, released[41].width() - 2 - offset * 2, released[41].height() ); // b, Cyrillic soft sign released[188].resize( 8 + offset * 2, 10 + offset * 2 ); released[188].reset(); fheroes2::DrawLine( released[188], { offset + 0, offset + 0 }, { offset + 2, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[188], { offset + 0, offset + 9 }, { offset + 6, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[188], { offset + 1, offset + 1 }, { offset + 1, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[188], { offset + 2, offset + 4 }, { offset + 6, offset + 4 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[188], { offset + 7, offset + 5 }, { offset + 7, offset + 8 }, buttonGoodReleasedColor ); // Flipped C with line inside, Cyrillic E released[189].resize( 8 + offset * 2, 10 + offset * 2 ); released[189].reset(); fheroes2::DrawLine( released[189], { offset + 2, offset + 0 }, { offset + 5, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[189], { offset + 7, offset + 2 }, { offset + 7, offset + 7 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[189], { offset + 2, offset + 9 }, { offset + 5, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[189], { offset + 0, offset + 0 }, { offset + 0, offset + 2 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[189], { offset + 2, offset + 4 }, { offset + 6, offset + 4 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[189], offset + 0, offset + 7, buttonGoodReleasedColor ); fheroes2::SetPixel( released[189], offset + 1, offset + 1, buttonGoodReleasedColor ); fheroes2::SetPixel( released[189], offset + 1, offset + 8, buttonGoodReleasedColor ); fheroes2::SetPixel( released[189], offset + 6, offset + 1, buttonGoodReleasedColor ); fheroes2::SetPixel( released[189], offset + 6, offset + 8, buttonGoodReleasedColor ); // IO, Cyrillic YU released[190].resize( 11 + offset * 2, 10 + offset * 2 ); released[190].reset(); fheroes2::Copy( released[41], offset + 1, 0, released[190], offset + 0, 0, released[41].width() - 2 - offset * 2, released[41].height() ); fheroes2::Copy( released[47], offset, 0, released[190], offset + 4, 0, 3, released[47].height() ); fheroes2::Copy( released[47], offset + 6, 0, released[190], offset + 7, 0, 4, released[47].height() ); fheroes2::DrawLine( released[190], { offset + 2, offset + 4 }, { offset + 3, offset + 4 }, buttonGoodReleasedColor ); // Mirrored R, Cyrillic YA released[191].resize( released[176].width(), released[176].height() ); fheroes2::Flip( released[176], 0, 0, released[191], 0, 0, released[176].width(), released[176].height(), true, false ); fheroes2::DrawLine( released[191], { offset + 0, offset + 9 }, { offset + 2, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[191], { offset + 3, offset + 6 }, { offset + 1, offset + 8 }, buttonGoodReleasedColor ); } void generateCP1252GoodButtonFont( std::vector & released ) { // Increase size to fit full CP1252 set of characters. Fill with 1px transparent images. const fheroes2::Sprite firstSprite{ released[0] }; released.insert( released.end(), 160, firstSprite ); // We need 2 pixels from all sides of a letter to add extra effects. const int32_t offset = 2; // Offset letters with diacritics above them. for ( const int & charCode : { 192, 193, 194, 195, 200, 201, 202, 205, 209, 211, 212, 213, 218 } ) { released[charCode - 32].setPosition( buttonFontOffset.x, buttonFontOffset.y - 3 ); } for ( const int & charCode : { 196, 197, 214 } ) { released[charCode - 32].setPosition( buttonFontOffset.x, buttonFontOffset.y - 2 ); } // Offset A-related letters to have less space to neighboring letters. Keep the y-offset change from earlier. for ( const int & charCode : { 65, 192, 193, 194, 195, 196, 197, 198 } ) { released[charCode - 32].setPosition( buttonFontOffset.x - 2, released[charCode - 32].y() ); } // A with grave. released[192 - 32].resize( released[65 - 32].width(), released[65 - 32].height() + 3 ); released[192 - 32].reset(); fheroes2::Copy( released[65 - 32], 0, 0, released[192 - 32], 0, 3, released[65 - 32].width(), released[65 - 32].height() ); fheroes2::DrawLine( released[192 - 32], { offset + 6, offset + 0 }, { offset + 7, offset + 1 }, buttonGoodReleasedColor ); // A with acute. released[193 - 32].resize( released[65 - 32].width(), released[65 - 32].height() + 3 ); released[193 - 32].reset(); fheroes2::Copy( released[65 - 32], 0, 0, released[193 - 32], 0, 3, released[65 - 32].width(), released[65 - 32].height() ); fheroes2::DrawLine( released[193 - 32], { offset + 6, offset + 1 }, { offset + 7, offset + 0 }, buttonGoodReleasedColor ); // A with circumflex released[194 - 32].resize( released[65 - 32].width(), released[65 - 32].height() + 3 ); released[194 - 32].reset(); fheroes2::Copy( released[65 - 32], 0, 0, released[194 - 32], 0, 3, released[65 - 32].width(), released[65 - 32].height() ); fheroes2::DrawLine( released[194 - 32], { offset + 5, offset + 1 }, { offset + 6, offset + 0 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[194 - 32], offset + 7, offset + 1, buttonGoodReleasedColor ); // A with tilde released[195 - 32].resize( released[65 - 32].width(), released[65 - 32].height() + 3 ); released[195 - 32].reset(); fheroes2::Copy( released[65 - 32], 0, 0, released[195 - 32], 0, 3, released[65 - 32].width(), released[65 - 32].height() ); fheroes2::DrawLine( released[195 - 32], { offset + 4, offset + 1 }, { offset + 5, offset + 0 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[195 - 32], offset + 6, offset + 0, buttonGoodReleasedColor ); fheroes2::SetPixel( released[195 - 32], offset + 7, offset + 1, buttonGoodReleasedColor ); fheroes2::DrawLine( released[195 - 32], { offset + 8, offset + 1 }, { offset + 9, offset + 0 }, buttonGoodReleasedColor ); // A with diaeresis released[196 - 32].resize( released[65 - 32].width(), released[65 - 32].height() + 2 ); released[196 - 32].reset(); fheroes2::Copy( released[65 - 32], 0, 0, released[196 - 32], 0, 2, released[65 - 32].width(), released[65 - 32].height() ); fheroes2::SetPixel( released[196 - 32], offset + 5, offset + 0, buttonGoodReleasedColor ); fheroes2::SetPixel( released[196 - 32], offset + 7, offset + 0, buttonGoodReleasedColor ); // A with circle on top released[197 - 32].resize( 13 + offset * 2, 12 + offset * 2 ); released[197 - 32].reset(); fheroes2::DrawLine( released[197 - 32], { offset + 0, offset + 11 }, { offset + 4, offset + 11 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[197 - 32], { offset + 8, offset + 11 }, { offset + 12, offset + 11 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[197 - 32], { offset + 5, offset + 7 }, { offset + 8, offset + 7 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[197 - 32], { offset + 2, offset + 10 }, { offset + 4, offset + 7 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[197 - 32], { offset + 7, offset + 3 }, { offset + 10, offset + 10 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[197 - 32], { offset + 5, offset + 1 }, { offset + 5, offset + 2 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[197 - 32], { offset + 8, offset + 1 }, { offset + 8, offset + 2 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[197 - 32], { offset + 6, offset + 0 }, { offset + 7, offset + 0 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[197 - 32], offset + 4, offset + 6, buttonGoodReleasedColor ); fheroes2::SetPixel( released[197 - 32], offset + 5, offset + 5, buttonGoodReleasedColor ); fheroes2::SetPixel( released[197 - 32], offset + 5, offset + 4, buttonGoodReleasedColor ); fheroes2::SetPixel( released[197 - 32], offset + 6, offset + 3, buttonGoodReleasedColor ); // A attached to E. released[198 - 32].resize( 15 + offset * 2, 10 + offset * 2 ); released[198 - 32].reset(); fheroes2::DrawLine( released[198 - 32], { offset + 0, offset + 9 }, { offset + 4, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[198 - 32], { offset + 8, offset + 9 }, { offset + 12, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[198 - 32], { offset + 5, offset + 5 }, { offset + 8, offset + 5 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[198 - 32], { offset + 2, offset + 8 }, { offset + 4, offset + 5 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[198 - 32], { offset + 7, offset + 1 }, { offset + 10, offset + 8 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[198 - 32], { offset + 7, offset + 0 }, { offset + 14, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[198 - 32], { offset + 13, offset + 9 }, { offset + 14, offset + 9 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[198 - 32], { offset + 9, offset + 4 }, { offset + 12, offset + 4 }, buttonGoodReleasedColor ); fheroes2::SetPixel( released[198 - 32], offset + 4, offset + 4, buttonGoodReleasedColor ); fheroes2::SetPixel( released[198 - 32], offset + 5, offset + 3, buttonGoodReleasedColor ); fheroes2::SetPixel( released[198 - 32], offset + 5, offset + 2, buttonGoodReleasedColor ); fheroes2::SetPixel( released[198 - 32], offset + 6, offset + 1, buttonGoodReleasedColor ); fheroes2::SetPixel( released[198 - 32], offset + 6, offset + 0, buttonGoodReleasedColor ); fheroes2::SetPixel( released[198 - 32], offset + 14, offset + 1, buttonGoodReleasedColor ); fheroes2::SetPixel( released[198 - 32], offset + 14, offset + 8, buttonGoodReleasedColor ); fheroes2::SetPixel( released[198 - 32], offset + 12, offset + 3, buttonGoodReleasedColor ); fheroes2::SetPixel( released[198 - 32], offset + 12, offset + 5, buttonGoodReleasedColor ); // C with cedilla. released[199 - 32].resize( released[67 - 32].width(), released[67 - 32].height() + 3 ); released[199 - 32].reset(); fheroes2::Copy( released[67 - 32], 0, 0, released[199 - 32], 0, 0, released[67 - 32].width(), released[67 - 32].height() ); fheroes2::DrawLine( released[199 - 32], { offset + 5, offset + 10 }, { offset + 6, offset + 11 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[199 - 32], { offset + 4, offset + 12 }, { offset + 5, offset + 12 }, buttonGoodReleasedColor ); // E with grave. released[200 - 32].resize( released[69 - 32].width(), released[69 - 32].height() + 3 ); released[200 - 32].reset(); fheroes2::Copy( released[69 - 32], 0, 0, released[200 - 32], 0, 3, released[69 - 32].width(), released[69 - 32].height() ); fheroes2::DrawLine( released[200 - 32], { offset + 4, offset + 0 }, { offset + 5, offset + 1 }, buttonGoodReleasedColor ); // E with acute. released[201 - 32].resize( released[69 - 32].width(), released[69 - 32].height() + 3 ); released[201 - 32].reset(); fheroes2::Copy( released[69 - 32], 0, 0, released[201 - 32], 0, 3, released[69 - 32].width(), released[69 - 32].height() ); fheroes2::DrawLine( released[201 - 32], { offset + 4, offset + 1 }, { offset + 5, offset + 0 }, buttonGoodReleasedColor ); // E with circumflex. released[202 - 32].resize( released[69 - 32].width(), released[69 - 32].height() + 3 ); released[202 - 32].reset(); fheroes2::Copy( released[69 - 32], 0, 0, released[202 - 32], 0, 3, released[69 - 32].width(), released[69 - 32].height() ); fheroes2::Copy( released[194 - 32], offset + 5, offset, released[202 - 32], offset + 3, offset, 3, 2 ); // I with acute. released[205 - 32].resize( released[73 - 32].width(), released[73 - 32].height() + 3 ); released[205 - 32].reset(); fheroes2::Copy( released[73 - 32], 0, 0, released[205 - 32], 0, 3, released[73 - 32].width(), released[73 - 32].height() ); fheroes2::DrawLine( released[205 - 32], { offset + 2, offset + 1 }, { offset + 3, offset + 0 }, buttonGoodReleasedColor ); // N with tilde. released[209 - 32].resize( released[78 - 32].width(), released[78 - 32].height() + 3 ); released[209 - 32].reset(); fheroes2::Copy( released[78 - 32], 0, 0, released[209 - 32], 0, 3, released[78 - 32].width(), released[78 - 32].height() ); fheroes2::Copy( released[195 - 32], offset + 4, offset, released[209 - 32], offset + 4, offset, 6, 2 ); // O with acute. released[211 - 32].resize( released[79 - 32].width(), released[79 - 32].height() + 3 ); released[211 - 32].reset(); fheroes2::Copy( released[79 - 32], 0, 0, released[211 - 32], 0, 3, released[79 - 32].width(), released[79 - 32].height() ); fheroes2::DrawLine( released[211 - 32], { offset + 4, offset + 1 }, { offset + 5, offset + 0 }, buttonGoodReleasedColor ); // O with circumflex. released[212 - 32].resize( released[79 - 32].width(), released[79 - 32].height() + 3 ); released[212 - 32].reset(); fheroes2::Copy( released[79 - 32], 0, 0, released[212 - 32], 0, 3, released[79 - 32].width(), released[79 - 32].height() ); fheroes2::DrawLine( released[212 - 32], { offset + 3, offset + 1 }, { offset + 4, offset + 0 }, buttonGoodReleasedColor ); fheroes2::DrawLine( released[212 - 32], { offset + 5, offset + 0 }, { offset + 6, offset + 1 }, buttonGoodReleasedColor ); // O with tilde. released[213 - 32].resize( released[79 - 32].width(), released[79 - 32].height() + 3 ); released[213 - 32].reset(); fheroes2::Copy( released[79 - 32], 0, 0, released[213 - 32], 0, 3, released[79 - 32].width(), released[79 - 32].height() ); fheroes2::Copy( released[195 - 32], offset + 4, offset, released[213 - 32], offset + 2, offset, 6, 2 ); // O with diaeresis. released[214 - 32].resize( released[79 - 32].width(), released[79 - 32].height() + 2 ); released[214 - 32].reset(); fheroes2::Copy( released[79 - 32], 0, 0, released[214 - 32], 0, 2, released[79 - 32].width(), released[79 - 32].height() ); fheroes2::SetPixel( released[214 - 32], offset + 3, offset + 0, buttonGoodReleasedColor ); fheroes2::SetPixel( released[214 - 32], offset + 6, offset + 0, buttonGoodReleasedColor ); // U with acute. released[218 - 32].resize( released[85 - 32].width(), released[85 - 32].height() + 3 ); released[218 - 32].reset(); fheroes2::Copy( released[85 - 32], 0, 0, released[218 - 32], 0, 3, released[85 - 32].width(), released[85 - 32].height() ); fheroes2::DrawLine( released[218 - 32], { offset + 6, offset + 1 }, { offset + 7, offset + 0 }, buttonGoodReleasedColor ); } } namespace fheroes2 { void generateAlphabet( const SupportedLanguage language, std::vector> & icnVsSprite ) { switch ( language ) { case SupportedLanguage::Czech: case SupportedLanguage::Hungarian: case SupportedLanguage::Polish: case SupportedLanguage::Slovak: generateCP1250Alphabet( icnVsSprite ); break; case SupportedLanguage::Belarusian: case SupportedLanguage::Bulgarian: case SupportedLanguage::Russian: case SupportedLanguage::Ukrainian: generateCP1251Alphabet( icnVsSprite ); break; case SupportedLanguage::Danish: case SupportedLanguage::Dutch: case SupportedLanguage::German: case SupportedLanguage::Italian: case SupportedLanguage::Norwegian: case SupportedLanguage::Portuguese: case SupportedLanguage::Spanish: case SupportedLanguage::Swedish: generateCP1252Alphabet( icnVsSprite ); break; case SupportedLanguage::French: generateCP1252Alphabet( icnVsSprite ); // This serves to make the font compatible with the original French custom encoding. generateFrenchAlphabet( icnVsSprite ); break; case SupportedLanguage::Turkish: generateCP1254Alphabet( icnVsSprite ); break; case SupportedLanguage::Vietnamese: generateCP1258Alphabet( icnVsSprite ); break; case SupportedLanguage::Romanian: generateISO8859_16Alphabet( icnVsSprite ); break; default: // Add new language generation code! assert( 0 ); break; } icnVsSprite[ICN::YELLOW_FONT].clear(); icnVsSprite[ICN::YELLOW_SMALLFONT].clear(); icnVsSprite[ICN::GRAY_FONT].clear(); icnVsSprite[ICN::GRAY_SMALL_FONT].clear(); icnVsSprite[ICN::WHITE_LARGE_FONT].clear(); } bool isAlphabetSupported( const SupportedLanguage language ) { switch ( language ) { case SupportedLanguage::Polish: case SupportedLanguage::German: case SupportedLanguage::French: case SupportedLanguage::Italian: case SupportedLanguage::Norwegian: case SupportedLanguage::Russian: case SupportedLanguage::Belarusian: case SupportedLanguage::Bulgarian: case SupportedLanguage::Portuguese: case SupportedLanguage::Romanian: case SupportedLanguage::Spanish: case SupportedLanguage::Swedish: case SupportedLanguage::Turkish: case SupportedLanguage::Ukrainian: case SupportedLanguage::Dutch: case SupportedLanguage::Hungarian: case SupportedLanguage::Czech: case SupportedLanguage::Danish: case SupportedLanguage::Slovak: case SupportedLanguage::Vietnamese: return true; default: break; } return false; } void generateBaseButtonFont( std::vector & goodReleased, std::vector & goodPressed, std::vector & evilReleased, std::vector & evilPressed ) { generateGoodButtonFontBaseShape( goodReleased ); updateButtonFont( goodReleased, goodPressed, evilReleased, evilPressed ); } void generateButtonAlphabet( const SupportedLanguage language, std::vector> & icnVsSprite ) { generateGoodButtonFontBaseShape( icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED] ); switch ( language ) { case SupportedLanguage::English: generateBaseButtonFont( icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED], icnVsSprite[ICN::BUTTON_GOOD_FONT_PRESSED], icnVsSprite[ICN::BUTTON_EVIL_FONT_RELEASED], icnVsSprite[ICN::BUTTON_EVIL_FONT_PRESSED] ); return; case SupportedLanguage::Czech: case SupportedLanguage::Hungarian: case SupportedLanguage::Polish: case SupportedLanguage::Slovak: generateCP1250GoodButtonFont( icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED] ); break; case SupportedLanguage::Belarusian: case SupportedLanguage::Bulgarian: case SupportedLanguage::Russian: case SupportedLanguage::Ukrainian: generateCP1251GoodButtonFont( icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED] ); break; case SupportedLanguage::Danish: case SupportedLanguage::Dutch: case SupportedLanguage::French: case SupportedLanguage::German: case SupportedLanguage::Italian: case SupportedLanguage::Norwegian: case SupportedLanguage::Portuguese: case SupportedLanguage::Spanish: case SupportedLanguage::Swedish: generateCP1252GoodButtonFont( icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED] ); break; case SupportedLanguage::Turkish: // generateGoodCP1254ButtonFont( icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED] ); break; case SupportedLanguage::Vietnamese: // generateGoodCP1258ButtonFont( icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED] ); break; case SupportedLanguage::Romanian: // generateGoodISO8859_16ButtonFont( icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED] ); break; default: // Add new language generation code! assert( 0 ); break; } updateButtonFont( icnVsSprite[ICN::BUTTON_GOOD_FONT_RELEASED], icnVsSprite[ICN::BUTTON_GOOD_FONT_PRESSED], icnVsSprite[ICN::BUTTON_EVIL_FONT_RELEASED], icnVsSprite[ICN::BUTTON_EVIL_FONT_PRESSED] ); } void modifyBaseNormalFont( std::vector & icnVsSprite ) { if ( icnVsSprite.size() < 96 ) { return; } // Remove white line from % symbol fheroes2::FillTransform( icnVsSprite[5], 5, 0, 5, 1, 1 ); fheroes2::FillTransform( icnVsSprite[5], 6, 2, 2, 1, 1 ); updateNormalFontLetterShadow( icnVsSprite[5] ); // Move "-" further down icnVsSprite[13].setPosition( icnVsSprite[13].x(), icnVsSprite[13].y() + 1 ); updateNormalFontLetterShadow( icnVsSprite[13] ); // Proper lowercase k. fheroes2::FillTransform( icnVsSprite[75], 4, 1, 5, 8, 1 ); fheroes2::Copy( icnVsSprite[43], 6, 5, icnVsSprite[75], 4, 7, 3, 1 ); fheroes2::Copy( icnVsSprite[43], 6, 4, icnVsSprite[75], 4, 6, 4, 1 ); fheroes2::Copy( icnVsSprite[43], 7, 4, icnVsSprite[75], 6, 5, 3, 1 ); fheroes2::Copy( icnVsSprite[43], 7, 4, icnVsSprite[75], 7, 4, 2, 1 ); fheroes2::Copy( icnVsSprite[43], 6, 6, icnVsSprite[75], 4, 8, 4, 1 ); icnVsSprite[75].setPosition( icnVsSprite[75].x(), icnVsSprite[75].y() ); updateNormalFontLetterShadow( icnVsSprite[75] ); // System call 'DELETE' (0x7F) is never used as a text character in phrases. // To make the blinking text cursor we have to make a transparent character with the width of the cursor '_'. icnVsSprite[127 - 32].resize( icnVsSprite[95 - 32].width(), 1 ); icnVsSprite[127 - 32].reset(); } void modifyBaseSmallFont( std::vector & icnVsSprite ) { if ( icnVsSprite.size() < 96 ) { return; } // Remove white line from % symbol fheroes2::FillTransform( icnVsSprite[5], 3, 0, 4, 1, 1 ); fheroes2::FillTransform( icnVsSprite[5], 4, 1, 2, 1, 1 ); updateNormalFontLetterShadow( icnVsSprite[5] ); // Proper lowercase k. icnVsSprite[75].resize( 6, 8 ); icnVsSprite[75].reset(); fheroes2::Copy( icnVsSprite[76], 1, 0, icnVsSprite[75], 1, 0, 2, 7 ); fheroes2::Copy( icnVsSprite[76], 1, 0, icnVsSprite[75], 1, 6, 1, 1 ); fheroes2::Copy( icnVsSprite[56], 6, 0, icnVsSprite[75], 3, 2, 3, 3 ); fheroes2::Copy( icnVsSprite[65], 2, icnVsSprite[65].height() - 2, icnVsSprite[75], 5, 6, 2, 1 ); fheroes2::Copy( icnVsSprite[65], 2, 0, icnVsSprite[75], 4, 5, 1, 1 ); icnVsSprite[75].setPosition( icnVsSprite[75].x(), icnVsSprite[75].y() ); updateSmallFontLetterShadow( icnVsSprite[75] ); } } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_font.h000066400000000000000000000042661456075706000211550ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include "ui_language.h" namespace fheroes2 { class Sprite; void generateAlphabet( const SupportedLanguage language, std::vector> & icnVsSprite ); bool isAlphabetSupported( const SupportedLanguage language ); void generateBaseButtonFont( std::vector & goodReleased, std::vector & goodPressed, std::vector & evilReleased, std::vector & evilPressed ); void generateButtonAlphabet( const SupportedLanguage language, std::vector> & icnVsSprite ); void modifyBaseNormalFont( std::vector & icnVsSprite ); void modifyBaseSmallFont( std::vector & icnVsSprite ); } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_keyboard.cpp000066400000000000000000000743321456075706000223430ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "ui_keyboard.h" #include #include #include #include #include #include #include #include #include "agg_image.h" #include "game_delays.h" #include "game_hotkeys.h" #include "icn.h" #include "image.h" #include "localevent.h" #include "math_base.h" #include "pal.h" #include "screen.h" #include "settings.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_language.h" #include "ui_text.h" #include "ui_tool.h" #include "ui_window.h" namespace { const int32_t buttonOffset{ 8 }; const int32_t defaultButtonHeight{ 25 }; const int32_t defaultSpecialButtonWidth{ 54 }; const int32_t spacebarButtonWidth{ 175 }; const int32_t defaultWindowWidth{ 520 }; const int32_t defaultWindowHeight{ 250 }; const int32_t defaultLetterRows{ 3 }; const fheroes2::Point buttonShadowOffset{ -5, 5 }; const fheroes2::Point offsetFromWindowBorders{ 25, 50 }; const fheroes2::Size inputAreaSize{ 268, 21 }; const int32_t inputAreaBorders{ 2 }; const int32_t inputAreaOffsetFromWindowTop{ 20 }; fheroes2::SupportedLanguage lastSelectedLanguage{ fheroes2::SupportedLanguage::English }; bool isSupportedForLanguageSwitching( const fheroes2::SupportedLanguage language ) { switch ( language ) { case fheroes2::SupportedLanguage::English: // English is a default language so it is not considered as an extra language. return false; case fheroes2::SupportedLanguage::Polish: case fheroes2::SupportedLanguage::Russian: return true; default: break; } return false; } enum class DialogAction : int { DoNothing, AddLetter, UpperCase, LowerCase, Numeric, ChangeLanguage, Backspace, Close }; enum class LayoutType : int { LowerCase, UpperCase, Numeric }; class KeyboardRenderer { public: KeyboardRenderer( fheroes2::Display & output, std::string & info, const bool evilInterface ) : _output( output ) , _info( info ) , _isEvilInterface( evilInterface ) , _cursorPosition( info.size() ) { // Do nothing. } fheroes2::Rect getWindowRoi() const { if ( !_window ) { // You are calling this method for an empty renderer! assert( 0 ); return {}; } return _window->activeArea(); } fheroes2::Rect getTextRoi() const { const fheroes2::Rect windowRoi{ getWindowRoi() }; return { windowRoi.x + ( windowRoi.width - inputAreaSize.width ) / 2 + inputAreaBorders, windowRoi.y + inputAreaOffsetFromWindowTop + inputAreaBorders, inputAreaSize.width - inputAreaBorders * 2, inputAreaSize.height - inputAreaBorders * 2 }; } bool isEvilInterface() const { return _isEvilInterface; } // Returns true if keyboard dialog resize was made. bool resize( const fheroes2::Size size ) { if ( _window && size.width == _window->activeArea().width && size.height == _window->activeArea().height ) { // This is the same window size. Nothing to do. return false; } assert( size.width > 0 && size.height > 0 ); const fheroes2::Point defaultOffset{ ( _output.width() - defaultWindowWidth ) / 2, ( _output.height() - defaultWindowHeight ) / 2 }; const fheroes2::Point offset{ defaultOffset.x - ( size.width - defaultWindowWidth ), defaultOffset.y - ( size.height - defaultWindowHeight ) }; // It is important to destroy the previous window to avoid rendering issues. _window.reset(); _window = std::make_unique( offset.x, offset.y, size.width, size.height, true, _output ); _window->render(); renderInputArea(); return true; } void insertCharacter( const char character ) { if ( _info.size() >= 255 ) { // Do not add more characters as the string is already long enough. return; } _info.insert( _cursorPosition, 1, character ); ++_cursorPosition; _output.render( renderInputArea() ); } void removeCharacter() { if ( _info.empty() || _cursorPosition == 0 ) { return; } if ( _cursorPosition >= _info.size() ) { _info.pop_back(); } else { _info.erase( _cursorPosition - 1, 1 ); } --_cursorPosition; _output.render( renderInputArea() ); } void changeCursorState() { _isCursorVisible = !_isCursorVisible; renderInputArea(); } void setCursorPosition( const int32_t clickXPosition, const int32_t startXPosition ) { _cursorPosition = fheroes2::getTextInputCursorPosition( _info, fheroes2::FontType::normalWhite(), _cursorPosition, clickXPosition, startXPosition ); renderInputArea(); } private: fheroes2::Display & _output; std::string & _info; std::unique_ptr _window; const bool _isEvilInterface{ false }; bool _isCursorVisible{ true }; size_t _cursorPosition{ 0 }; fheroes2::Rect renderInputArea() { const fheroes2::Rect & windowRoi = _window->activeArea(); const fheroes2::Rect outputRoi{ windowRoi.x + ( windowRoi.width - inputAreaSize.width ) / 2, windowRoi.y + inputAreaOffsetFromWindowTop, inputAreaSize.width, inputAreaSize.height }; const fheroes2::Sprite & initialWindow = fheroes2::AGG::GetICN( ICN::REQBKG, 0 ); fheroes2::Copy( initialWindow, 40, 286, _output, outputRoi.x, outputRoi.y, outputRoi.width, outputRoi.height ); if ( _isEvilInterface ) { fheroes2::ApplyPalette( _output, outputRoi.x, outputRoi.y, _output, outputRoi.x, outputRoi.y, outputRoi.width, outputRoi.height, PAL::GetPalette( PAL::PaletteType::GOOD_TO_EVIL_INTERFACE ) ); } fheroes2::Text textUI( insertCharToString( _info, _cursorPosition, _isCursorVisible ? '_' : '\x7F' ), fheroes2::FontType::normalWhite() ); textUI.fitToOneRow( inputAreaSize.width - inputAreaBorders * 2 ); textUI.draw( windowRoi.x + ( windowRoi.width - inputAreaSize.width ) / 2 + inputAreaBorders, windowRoi.y + inputAreaSize.height + ( inputAreaSize.height - textUI.height() ) / 2 + inputAreaBorders, _output ); return outputRoi; } }; struct KeyboardButton { KeyboardButton() = default; KeyboardButton( std::string input, const int32_t buttonWidth, const bool isEvilInterface, std::function actionEvent ) : text( std::move( input ) ) , action( std::move( actionEvent ) ) { fheroes2::Sprite released; fheroes2::Sprite pressed; makeButtonSprites( released, pressed, text, buttonWidth, isEvilInterface, false ); button.setSprite( released, pressed ); // Make Image with shadow for button to Blit it during render. buttonShadow.resize( released.width() + std::abs( buttonShadowOffset.x ), released.height() + std::abs( buttonShadowOffset.y ) ); buttonShadow.reset(); fheroes2::addGradientShadow( released, buttonShadow, { -std::min( 0, buttonShadowOffset.x ), -std::min( 0, buttonShadowOffset.y ) }, buttonShadowOffset ); } KeyboardButton( const KeyboardButton & ) = delete; KeyboardButton( KeyboardButton && ) noexcept = default; ~KeyboardButton() = default; KeyboardButton & operator=( const KeyboardButton & ) = delete; KeyboardButton & operator=( KeyboardButton && ) = default; std::string text; std::function action; fheroes2::ButtonSprite button; fheroes2::Image buttonShadow; // This is used only for buttons which should have pressed state for some layouts. bool isInvertedRenderingLogic{ false }; }; std::vector getNumericCharacterLayout( const fheroes2::SupportedLanguage language ) { // Numeric layout can be used for special letters as well. switch ( language ) { case fheroes2::SupportedLanguage::English: case fheroes2::SupportedLanguage::Polish: case fheroes2::SupportedLanguage::Russian: return { "1234567890", "-:;()_+=", "[].,!'" }; default: // Did you add a new layout type? Add the logic above! assert( 0 ); break; } return {}; } std::vector getCapitalCharacterLayout( const fheroes2::SupportedLanguage language ) { switch ( language ) { case fheroes2::SupportedLanguage::English: return { "QWERTYUIOP", "ASDFGHJKL", "ZXCVBNM" }; case fheroes2::SupportedLanguage::Polish: return { "\x8C\x8F\xA3\xA5\xAF\xC6\xCA\xD1\xD3", "QWERTYUIOP", "ASDFGHJKL", "ZXCVBNM" }; case fheroes2::SupportedLanguage::Russian: return { "\xC9\xD6\xD3\xCA\xC5\xCD\xC3\xD8\xD9\xC7\xD5\xDA", "\xD4\xDB\xC2\xC0\xCF\xD0\xCE\xCB\xC4\xC6\xDD", "\xDF\xD7\xD1\xCC\xC8\xD2\xDC\xC1\xDE\xA8" }; default: // Did you add a new layout type? Add the logic above! assert( 0 ); break; } return {}; } std::vector getNonCapitalCharacterLayout( const fheroes2::SupportedLanguage language ) { switch ( language ) { case fheroes2::SupportedLanguage::English: return { "qwertyuiop", "asdfghjkl", "zxcvbnm" }; case fheroes2::SupportedLanguage::Polish: return { "\x9C\x9F\xB3\xB9\xBF\xE6\xEA\xF1\xF3", "qwertyuiop", "asdfghjkl", "zxcvbnm" }; case fheroes2::SupportedLanguage::Russian: return { "\xE9\xF6\xF3\xEA\xE5\xED\xE3\xF8\xF9\xE7\xF5\xFA", "\xF4\xFB\xE2\xE0\xEF\xF0\xEE\xEB\xE4\xE6\xFD", "\xFF\xF7\xF1\xEC\xE8\xF2\xFC\xE1\xFE\xB8" }; default: // Did you add a new layout type? Add the logic above! assert( 0 ); break; } return {}; } void getCharacterLayout( const LayoutType layoutType, const fheroes2::SupportedLanguage language, std::vector & buttonLetters, std::vector & returnLetters ) { switch ( layoutType ) { case LayoutType::LowerCase: buttonLetters = getCapitalCharacterLayout( language ); returnLetters = getNonCapitalCharacterLayout( language ); break; case LayoutType::UpperCase: buttonLetters = getCapitalCharacterLayout( language ); returnLetters = buttonLetters; break; case LayoutType::Numeric: buttonLetters = getNumericCharacterLayout( language ); returnLetters = buttonLetters; break; default: // Did you add a new layout type? Add the logic above! assert( 0 ); break; } } int32_t getDefaultButtonWidth( const fheroes2::SupportedLanguage language ) { // Different languages have different number of letters per row. // We cannot expand the virtual keyboard window beyond 640 pixels but we can change the size of buttons. switch ( language ) { case fheroes2::SupportedLanguage::English: case fheroes2::SupportedLanguage::Polish: return 30; case fheroes2::SupportedLanguage::Russian: return 24; default: // Did you add a new supported language? Add the value above! assert( 0 ); break; } return {}; } std::vector> generateButtons( const std::vector & buttonLetters, const std::vector & returnLetters, const LayoutType layoutType, const fheroes2::SupportedLanguage language, const bool isEvilInterface ) { assert( buttonLetters.size() == returnLetters.size() ); // This is required in order to render proper text on buttons but do not change Okay button in the window. const fheroes2::LanguageSwitcher switcher( language ); std::vector> buttons; buttons.resize( buttonLetters.size() ); const int32_t buttonWidth = ( layoutType == LayoutType::Numeric ) ? getDefaultButtonWidth( fheroes2::SupportedLanguage::English ) : getDefaultButtonWidth( language ); for ( size_t i = 0; i < buttonLetters.size(); ++i ) { assert( buttonLetters[i].size() == returnLetters[i].size() ); for ( size_t buttonId = 0; buttonId < buttonLetters[i].size(); ++buttonId ) { buttons[i].emplace_back( std::string( 1, buttonLetters[i][buttonId] ), buttonWidth, isEvilInterface, [letter = returnLetters[i][buttonId]]( KeyboardRenderer & renderer ) { renderer.insertCharacter( letter ); return DialogAction::AddLetter; } ); } } return buttons; } void addExtraStandardButtons( std::vector> & buttons, const LayoutType layoutType, const bool isEvilInterface, const bool isExtraLanguageSupported, const fheroes2::SupportedLanguage /* unused */ ) { auto & lastButtonRow = buttons.emplace_back(); switch ( layoutType ) { case LayoutType::LowerCase: lastButtonRow.emplace_back( "|", defaultSpecialButtonWidth, isEvilInterface, []( const KeyboardRenderer & ) { return DialogAction::UpperCase; } ); lastButtonRow.emplace_back( _( "Keyboard|123" ), defaultSpecialButtonWidth, isEvilInterface, []( const KeyboardRenderer & ) { return DialogAction::Numeric; } ); lastButtonRow.emplace_back( _( "Keyboard|SPACE" ), spacebarButtonWidth, isEvilInterface, []( KeyboardRenderer & renderer ) { renderer.insertCharacter( ' ' ); return DialogAction::AddLetter; } ); lastButtonRow.emplace_back( "\x7F", defaultSpecialButtonWidth, isEvilInterface, []( const KeyboardRenderer & ) { return DialogAction::ChangeLanguage; } ); if ( !isExtraLanguageSupported ) { lastButtonRow.back().button.hide(); } lastButtonRow.emplace_back( "~", defaultSpecialButtonWidth, isEvilInterface, []( KeyboardRenderer & renderer ) { renderer.removeCharacter(); return DialogAction::Backspace; } ); break; case LayoutType::UpperCase: lastButtonRow.emplace_back( "|", defaultSpecialButtonWidth, isEvilInterface, []( const KeyboardRenderer & ) { return DialogAction::LowerCase; } ); lastButtonRow.back().isInvertedRenderingLogic = true; lastButtonRow.emplace_back( _( "Keyboard|123" ), defaultSpecialButtonWidth, isEvilInterface, []( const KeyboardRenderer & ) { return DialogAction::Numeric; } ); lastButtonRow.emplace_back( _( "Keyboard|SPACE" ), spacebarButtonWidth, isEvilInterface, []( KeyboardRenderer & renderer ) { renderer.insertCharacter( ' ' ); return DialogAction::AddLetter; } ); lastButtonRow.emplace_back( "\x7F", defaultSpecialButtonWidth, isEvilInterface, []( const KeyboardRenderer & ) { return DialogAction::ChangeLanguage; } ); if ( !isExtraLanguageSupported ) { lastButtonRow.back().button.hide(); } lastButtonRow.emplace_back( "~", defaultSpecialButtonWidth, isEvilInterface, []( KeyboardRenderer & renderer ) { renderer.removeCharacter(); return DialogAction::Backspace; } ); break; case LayoutType::Numeric: lastButtonRow.emplace_back( "|", defaultSpecialButtonWidth, isEvilInterface, []( const KeyboardRenderer & ) { return DialogAction::DoNothing; } ); lastButtonRow.back().button.hide(); lastButtonRow.emplace_back( _( "Keyboard|ABC" ), defaultSpecialButtonWidth, isEvilInterface, []( const KeyboardRenderer & ) { return DialogAction::LowerCase; } ); lastButtonRow.emplace_back( _( "Keyboard|SPACE" ), spacebarButtonWidth, isEvilInterface, []( KeyboardRenderer & renderer ) { renderer.insertCharacter( ' ' ); return DialogAction::AddLetter; } ); lastButtonRow.emplace_back( "\x7F", defaultSpecialButtonWidth, isEvilInterface, []( const KeyboardRenderer & ) { return DialogAction::ChangeLanguage; } ); lastButtonRow.back().button.hide(); lastButtonRow.emplace_back( "~", defaultSpecialButtonWidth, isEvilInterface, []( KeyboardRenderer & renderer ) { renderer.removeCharacter(); return DialogAction::Backspace; } ); break; default: // Did you add a new layout type? Add the logic above! assert( 0 ); break; } } void addExtraButtons( std::vector> & buttons, const LayoutType layoutType, const fheroes2::SupportedLanguage language, const bool isEvilInterface, const bool isExtraLanguageSupported ) { switch ( language ) { case fheroes2::SupportedLanguage::English: case fheroes2::SupportedLanguage::Polish: case fheroes2::SupportedLanguage::Russian: addExtraStandardButtons( buttons, layoutType, isEvilInterface, isExtraLanguageSupported, language ); break; default: assert( 0 ); break; } } fheroes2::Rect getButtonsRoi( const std::vector> & buttonLayout, const fheroes2::Point offset ) { std::vector offsets; int32_t maximumLength = 0; for ( const auto & buttonRow : buttonLayout ) { int32_t length = 0; for ( const auto & buttonInfo : buttonRow ) { length += buttonInfo.button.area().width; } length += ( static_cast( buttonRow.size() ) - 1 ) * buttonOffset; offsets.push_back( length ); maximumLength = std::max( maximumLength, length ); } for ( int32_t & rowOffset : offsets ) { rowOffset = ( defaultWindowWidth - 2 * offsetFromWindowBorders.x - rowOffset ) / 2; } fheroes2::Rect roi{ offset.x + offsets.front(), offset.y, 1, 1 }; const size_t buttonRows = buttonLayout.size(); for ( size_t i = 0; i < buttonRows; ++i ) { int32_t xOffset = offset.x + offsets[i]; const int32_t newX = std::min( xOffset, roi.x ); roi.width = ( roi.x - newX ) + roi.width; roi.x = newX; for ( const auto & buttonInfo : buttonLayout[i] ) { xOffset += buttonInfo.button.area().width + buttonOffset; } roi.width = std::max( xOffset - buttonOffset - roi.x, roi.width ); } const int32_t yOffset = offset.y + static_cast( buttonRows * defaultButtonHeight + ( buttonRows - 1 ) * buttonOffset * 2 ); // Take button shadow offset into account. roi.x += std::min( 0, buttonShadowOffset.x ); roi.y += std::min( 0, buttonShadowOffset.y ); roi.width += std::abs( buttonShadowOffset.x ); roi.height = yOffset - roi.y + std::abs( buttonShadowOffset.y ); return roi; } void renderButtons( std::vector> & buttonLayout, const fheroes2::Point offset, fheroes2::Image & output ) { std::vector offsets; int32_t maximumLength = 0; for ( const auto & buttonRow : buttonLayout ) { int32_t length = 0; for ( const auto & buttonInfo : buttonRow ) { length += buttonInfo.button.area().width; } length += ( static_cast( buttonRow.size() ) - 1 ) * buttonOffset; offsets.push_back( length ); maximumLength = std::max( maximumLength, length ); } for ( int32_t & rowOffset : offsets ) { rowOffset = ( defaultWindowWidth - 2 * offsetFromWindowBorders.x - rowOffset ) / 2; } int32_t yOffset = offset.y; for ( size_t i = 0; i < buttonLayout.size(); ++i ) { int32_t xOffset = offset.x + offsets[i]; for ( auto & buttonInfo : buttonLayout[i] ) { buttonInfo.button.setPosition( xOffset, yOffset ); if ( buttonInfo.isInvertedRenderingLogic ) { buttonInfo.button.press(); } if ( buttonInfo.button.draw( output ) ) { fheroes2::Blit( buttonInfo.buttonShadow, output, xOffset + buttonShadowOffset.x, yOffset ); } xOffset += buttonInfo.button.area().width + buttonOffset; } yOffset += defaultButtonHeight + buttonOffset * 2; } } DialogAction handleButtonEvents( const std::vector> & buttonLayout, LocalEvent & le, KeyboardRenderer & renderer ) { for ( const auto & buttonRow : buttonLayout ) { for ( const auto & buttonInfo : buttonRow ) { if ( buttonInfo.button.isVisible() && le.MouseClickLeft( buttonInfo.button.area() ) ) { assert( buttonInfo.action ); return buttonInfo.action( renderer ); } } } return DialogAction::DoNothing; } void updateButtonStates( std::vector> & buttonLayout, const LocalEvent & le ) { for ( auto & buttonRow : buttonLayout ) { for ( auto & buttonInfo : buttonRow ) { if ( buttonInfo.isInvertedRenderingLogic ) { le.MousePressLeft( buttonInfo.button.area() ) ? buttonInfo.button.drawOnRelease() : buttonInfo.button.drawOnPress(); } else { le.MousePressLeft( buttonInfo.button.area() ) ? buttonInfo.button.drawOnPress() : buttonInfo.button.drawOnRelease(); } } } } DialogAction processVirtualKeyboardEvent( const LayoutType layoutType, const fheroes2::SupportedLanguage language, const bool isExtraLanguageSupported, KeyboardRenderer & renderer ) { std::vector buttonLetters; std::vector returnLetters; getCharacterLayout( layoutType, language, buttonLetters, returnLetters ); const bool isResized = renderer.resize( { defaultWindowWidth, defaultWindowHeight + ( static_cast( buttonLetters.size() ) - defaultLetterRows ) * ( defaultButtonHeight + buttonOffset * 2 ) } ); const bool isEvilInterface = renderer.isEvilInterface(); auto buttons = generateButtons( buttonLetters, returnLetters, layoutType, language, isEvilInterface ); addExtraButtons( buttons, layoutType, language, isEvilInterface, isExtraLanguageSupported ); const fheroes2::Rect windowRoi{ renderer.getWindowRoi() }; const fheroes2::Rect buttonsRoi = getButtonsRoi( buttons, windowRoi.getPosition() + offsetFromWindowBorders ); const fheroes2::Rect textRoi{ renderer.getTextRoi() }; fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::ImageRestorer restorer( display, buttonsRoi.x, buttonsRoi.y, buttonsRoi.width, buttonsRoi.height ); renderButtons( buttons, windowRoi.getPosition() + offsetFromWindowBorders, display ); const int buttonIcnId = isEvilInterface ? ICN::BUTTON_SMALL_OKAY_EVIL : ICN::BUTTON_SMALL_OKAY_GOOD; const fheroes2::Sprite & okayButtonReleasedImage = fheroes2::AGG::GetICN( buttonIcnId, 0 ); const fheroes2::Sprite & okayButtonPressedImage = fheroes2::AGG::GetICN( buttonIcnId, 1 ); const fheroes2::Point okayButtonPosition{ windowRoi.x + ( windowRoi.width - okayButtonReleasedImage.width() ) / 2, windowRoi.y + windowRoi.height - 35 }; fheroes2::ButtonSprite okayButton( okayButtonPosition.x, okayButtonPosition.y, okayButtonReleasedImage, okayButtonPressedImage ); // Render OKAY button and its shadow only if the keyboard dialog was resized. if ( isResized ) { okayButton.draw(); fheroes2::addGradientShadow( okayButtonReleasedImage, display, okayButtonPosition, buttonShadowOffset ); } display.render(); DialogAction action = DialogAction::DoNothing; LocalEvent & le = LocalEvent::Get(); Game::AnimateResetDelay( Game::DelayType::CURSOR_BLINK_DELAY ); while ( le.HandleEvents( Game::isDelayNeeded( { Game::DelayType::CURSOR_BLINK_DELAY } ) ) ) { if ( le.MouseClickLeft( okayButton.area() ) || Game::HotKeyCloseWindow() ) { break; } action = handleButtonEvents( buttons, le, renderer ); switch ( action ) { case DialogAction::DoNothing: case DialogAction::AddLetter: case DialogAction::Backspace: // Do nothing. break; default: return action; } updateButtonStates( buttons, le ); if ( le.MousePressLeft( okayButton.area() ) ) { okayButton.drawOnPress(); } else { okayButton.drawOnRelease(); } if ( le.MouseClickLeft( textRoi ) ) { renderer.setCursorPosition( le.GetMouseCursor().x, textRoi.x ); } // Text input cursor blink. if ( Game::validateAnimationDelay( Game::DelayType::CURSOR_BLINK_DELAY ) ) { renderer.changeCursorState(); } } return DialogAction::Close; } } namespace fheroes2 { void openVirtualKeyboard( std::string & output ) { const std::vector supportedLanguages = getSupportedLanguages(); SupportedLanguage language = SupportedLanguage::English; DialogAction action = DialogAction::AddLetter; LayoutType layoutType = LayoutType::LowerCase; const SupportedLanguage currentGameLanguage = getCurrentLanguage(); if ( currentGameLanguage == lastSelectedLanguage ) { language = lastSelectedLanguage; } KeyboardRenderer renderer( fheroes2::Display::instance(), output, Settings::Get().isEvilInterfaceEnabled() ); while ( action != DialogAction::Close ) { action = processVirtualKeyboardEvent( layoutType, language, isSupportedForLanguageSwitching( currentGameLanguage ), renderer ); switch ( action ) { case DialogAction::DoNothing: case DialogAction::AddLetter: case DialogAction::Backspace: // These actions must not be processed here! assert( 0 ); break; case DialogAction::LowerCase: layoutType = LayoutType::LowerCase; break; case DialogAction::UpperCase: layoutType = LayoutType::UpperCase; break; case DialogAction::Numeric: layoutType = LayoutType::Numeric; break; case DialogAction::ChangeLanguage: assert( isSupportedForLanguageSwitching( currentGameLanguage ) ); if ( currentGameLanguage != SupportedLanguage::English ) { if ( language == SupportedLanguage::English ) { language = currentGameLanguage; } else { language = SupportedLanguage::English; } lastSelectedLanguage = language; } break; case DialogAction::Close: return; default: // Did you add a new state? Add the logic above! assert( 0 ); break; } } } } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_keyboard.h000066400000000000000000000035061456075706000220030ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include namespace fheroes2 { // fheroes2 does not support UTF-8 so on mobile devices with virtual keyboard it might be a big problem. // As a solution we should utilize an in-game virtual keyboard which supports all code pages available by the engine. // The default language in the keyboard is English. void openVirtualKeyboard( std::string & output ); } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_kingdom.cpp000066400000000000000000000117051456075706000221660ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "ui_kingdom.h" #include #include #include #include #include "agg_image.h" #include "game_delays.h" #include "icn.h" #include "image.h" #include "kingdom.h" #include "maps.h" #include "mp2.h" #include "translations.h" #include "ui_dialog.h" #include "ui_text.h" #include "world.h" namespace fheroes2 { void showKingdomIncome( const Kingdom & kingdom, const int buttons ) { const Text header( _( "Kingdom Income" ), FontType::normalYellow() ); const Text body( _( "Kingdom Income per day." ), FontType::normalWhite() ); showResourceMessage( header, body, buttons, kingdom.GetIncome( Kingdom::INCOME_ALL ) ); } void showLighthouseInfo( const Kingdom & kingdom, const int buttons ) { const uint32_t lighthouseCount = world.CountCapturedObject( MP2::OBJ_LIGHTHOUSE, kingdom.GetColor() ); const std::string body( _( "For every lighthouse controlled, your ships will move further each day." ) ); const Sprite & shadowTop = AGG::GetICN( ICN::OBJNMUL2, 60 ); const Sprite & shadowMiddle = AGG::GetICN( ICN::OBJNMUL2, 71 ); const Sprite & shadowBottom = AGG::GetICN( ICN::OBJNMUL2, 72 ); const Sprite & lighthouseTop = AGG::GetICN( ICN::OBJNMUL2, 59 ); const Sprite & lighthouseMiddle = AGG::GetICN( ICN::OBJNMUL2, 61 ); const Sprite & lighthouseBottom = AGG::GetICN( ICN::OBJNMUL2, 73 ); const Sprite & lighthouseLight = AGG::GetICN( ICN::OBJNMUL2, 62 ); const int32_t topOffset = TILEWIDTH - lighthouseTop.height(); Image combined( 5 * TILEWIDTH, 2 * TILEWIDTH + lighthouseTop.height() ); combined.reset(); Copy( shadowTop, 0, 0, combined, shadowTop.x(), TILEWIDTH + shadowTop.y() - topOffset, TILEWIDTH, TILEWIDTH ); Copy( shadowMiddle, 0, 0, combined, shadowMiddle.x(), TILEWIDTH * 2 + shadowMiddle.y() - topOffset, TILEWIDTH, TILEWIDTH ); Copy( shadowBottom, 0, 0, combined, TILEWIDTH + shadowBottom.x(), TILEWIDTH * 2 + shadowBottom.y() - topOffset, TILEWIDTH, TILEWIDTH ); Copy( lighthouseTop, 0, 0, combined, TILEWIDTH * 2 + lighthouseTop.x(), lighthouseTop.y() - topOffset, TILEWIDTH, TILEWIDTH ); Copy( lighthouseMiddle, 0, 0, combined, TILEWIDTH * 2 + lighthouseMiddle.x(), TILEWIDTH + lighthouseMiddle.y() - topOffset, TILEWIDTH, TILEWIDTH ); Copy( lighthouseBottom, 0, 0, combined, TILEWIDTH * 2 + lighthouseBottom.x(), TILEWIDTH * 2 + lighthouseBottom.y() - topOffset, TILEWIDTH, TILEWIDTH ); const TextDialogElement lighthouseControlledElement( std::make_shared( std::to_string( lighthouseCount ), FontType::normalWhite() ) ); // Use MAPS_DELAY for animation delay since the lighthouse is a map object // 61 (0x3D) references the icn offset for the lighthouse animation in icn.cpp const CustomAnimationDialogElement lighthouseCustomDynamicImageElement( ICN::OBJNMUL2, combined, { TILEWIDTH * 2 + lighthouseLight.x(), TILEWIDTH + lighthouseLight.y() - topOffset }, 61, getAnimationDelayValue( Game::MAPS_DELAY ) ); // StringObject on OBJ_LIGHTHOUSE with count 2 for the plural of lighthouse showMessage( Text( StringObject( MP2::OBJ_LIGHTHOUSE, 2 ), FontType::normalYellow() ), Text( body, FontType::normalWhite() ), buttons, { &lighthouseCustomDynamicImageElement, &lighthouseControlledElement } ); } } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_kingdom.h000066400000000000000000000032031456075706000216250ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once class Kingdom; namespace fheroes2 { void showKingdomIncome( const Kingdom & kingdom, const int buttons ); void showLighthouseInfo( const Kingdom & kingdom, const int buttons ); } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_language.cpp000066400000000000000000000315771456075706000223320ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "ui_language.h" #include #include #include #include #include #include #include "agg.h" #include "agg_image.h" #include "icn.h" #include "settings.h" #include "tools.h" #include "translations.h" #include "ui_font.h" namespace { const std::map languageCRC32 = { { 0x406967B9, fheroes2::SupportedLanguage::French }, // GoG version { 0x04745D1D, fheroes2::SupportedLanguage::German }, // GoG version { 0x88774771, fheroes2::SupportedLanguage::Polish }, // GoG version { 0xDB10FFD8, fheroes2::SupportedLanguage::Russian }, // XXI Vek version { 0xD5CF8AF3, fheroes2::SupportedLanguage::Russian }, // Buka version { 0x219B3124, fheroes2::SupportedLanguage::Italian }, // ??? { 0x1CEBD099, fheroes2::SupportedLanguage::Czech } }; // CD Projekt // Strings in this map must in lower case and non translatable. const std::map languageName = { { "pl", fheroes2::SupportedLanguage::Polish }, { "polish", fheroes2::SupportedLanguage::Polish }, { "de", fheroes2::SupportedLanguage::German }, { "german", fheroes2::SupportedLanguage::German }, { "fr", fheroes2::SupportedLanguage::French }, { "french", fheroes2::SupportedLanguage::French }, { "ru", fheroes2::SupportedLanguage::Russian }, { "russian", fheroes2::SupportedLanguage::Russian }, { "it", fheroes2::SupportedLanguage::Italian }, { "italian", fheroes2::SupportedLanguage::Italian }, { "cs", fheroes2::SupportedLanguage::Czech }, { "czech", fheroes2::SupportedLanguage::Czech }, { "nb", fheroes2::SupportedLanguage::Norwegian }, { "norwegian", fheroes2::SupportedLanguage::Norwegian }, { "be", fheroes2::SupportedLanguage::Belarusian }, { "belarusian", fheroes2::SupportedLanguage::Belarusian }, { "uk", fheroes2::SupportedLanguage::Ukrainian }, { "ukrainian", fheroes2::SupportedLanguage::Ukrainian }, { "bg", fheroes2::SupportedLanguage::Bulgarian }, { "bulgarian", fheroes2::SupportedLanguage::Bulgarian }, { "es", fheroes2::SupportedLanguage::Spanish }, { "spanish", fheroes2::SupportedLanguage::Spanish }, { "pt", fheroes2::SupportedLanguage::Portuguese }, { "portuguese", fheroes2::SupportedLanguage::Portuguese }, { "sv", fheroes2::SupportedLanguage::Swedish }, { "swedish", fheroes2::SupportedLanguage::Swedish }, { "tr", fheroes2::SupportedLanguage::Turkish }, { "turkish", fheroes2::SupportedLanguage::Turkish }, { "ro", fheroes2::SupportedLanguage::Romanian }, { "romanian", fheroes2::SupportedLanguage::Romanian }, { "nl", fheroes2::SupportedLanguage::Dutch }, { "dutch", fheroes2::SupportedLanguage::Dutch }, { "hu", fheroes2::SupportedLanguage::Hungarian }, { "hungarian", fheroes2::SupportedLanguage::Hungarian }, { "dk", fheroes2::SupportedLanguage::Danish }, { "danish", fheroes2::SupportedLanguage::Danish }, { "sk", fheroes2::SupportedLanguage::Slovak }, { "slovak", fheroes2::SupportedLanguage::Slovak }, { "vi", fheroes2::SupportedLanguage::Vietnamese }, { "vietnamese", fheroes2::SupportedLanguage::Vietnamese } }; } namespace fheroes2 { LanguageSwitcher::LanguageSwitcher( const SupportedLanguage language ) : _currentLanguage( Settings::Get().getGameLanguage() ) { Settings::Get().setGameLanguage( getLanguageAbbreviation( language ) ); } LanguageSwitcher::~LanguageSwitcher() { Settings::Get().setGameLanguage( _currentLanguage ); } SupportedLanguage getResourceLanguage() { const std::vector & data = ::AGG::getDataFromAggFile( ICN::GetString( ICN::FONT ) ); if ( data.empty() ) { // How is it possible to run the game without a font? assert( 0 ); return SupportedLanguage::English; } const uint32_t crc32 = calculateCRC32( data.data(), data.size() ); auto iter = languageCRC32.find( crc32 ); if ( iter == languageCRC32.end() ) { return SupportedLanguage::English; } return iter->second; } std::vector getSupportedLanguages() { std::vector languages; const SupportedLanguage resourceLanguage = getResourceLanguage(); if ( resourceLanguage != SupportedLanguage::English ) { languages.emplace_back( resourceLanguage ); } const std::set possibleLanguages{ SupportedLanguage::French, SupportedLanguage::Polish, SupportedLanguage::German, SupportedLanguage::Russian, SupportedLanguage::Italian, SupportedLanguage::Norwegian, SupportedLanguage::Belarusian, SupportedLanguage::Bulgarian, SupportedLanguage::Ukrainian, SupportedLanguage::Romanian, SupportedLanguage::Spanish, SupportedLanguage::Portuguese, SupportedLanguage::Swedish, SupportedLanguage::Turkish, SupportedLanguage::Dutch, SupportedLanguage::Hungarian, SupportedLanguage::Czech, SupportedLanguage::Danish, SupportedLanguage::Slovak, SupportedLanguage::Vietnamese }; for ( const SupportedLanguage language : possibleLanguages ) { if ( language != resourceLanguage && isAlphabetSupported( language ) ) { languages.emplace_back( language ); } } Settings & conf = Settings::Get(); fheroes2::SupportedLanguage currentLanguage = fheroes2::getLanguageFromAbbreviation( conf.getGameLanguage() ); std::vector validSupportedLanguages{ fheroes2::SupportedLanguage::English }; for ( fheroes2::SupportedLanguage language : languages ) { if ( conf.setGameLanguage( fheroes2::getLanguageAbbreviation( language ) ) ) { validSupportedLanguages.emplace_back( language ); } } conf.setGameLanguage( fheroes2::getLanguageAbbreviation( currentLanguage ) ); assert( !validSupportedLanguages.empty() ); return validSupportedLanguages; } const char * getLanguageName( const SupportedLanguage language ) { switch ( language ) { case SupportedLanguage::English: return _( "English" ); case SupportedLanguage::French: return _( "French" ); case SupportedLanguage::Polish: return _( "Polish" ); case SupportedLanguage::German: return _( "German" ); case SupportedLanguage::Russian: return _( "Russian" ); case SupportedLanguage::Italian: return _( "Italian" ); case SupportedLanguage::Czech: return _( "Czech" ); case SupportedLanguage::Norwegian: return _( "Norwegian" ); case SupportedLanguage::Belarusian: return _( "Belarusian" ); case SupportedLanguage::Bulgarian: return _( "Bulgarian" ); case SupportedLanguage::Ukrainian: return _( "Ukrainian" ); case SupportedLanguage::Romanian: return _( "Romanian" ); case SupportedLanguage::Spanish: return _( "Spanish" ); case SupportedLanguage::Swedish: return _( "Swedish" ); case SupportedLanguage::Portuguese: return _( "Portuguese" ); case SupportedLanguage::Turkish: return _( "Turkish" ); case SupportedLanguage::Dutch: return _( "Dutch" ); case SupportedLanguage::Hungarian: return _( "Hungarian" ); case SupportedLanguage::Danish: return _( "Danish" ); case SupportedLanguage::Slovak: return _( "Slovak" ); case SupportedLanguage::Vietnamese: return _( "Vietnamese" ); default: // Did you add a new language? Please add the code to handle it. assert( 0 ); return nullptr; } } const char * getLanguageAbbreviation( const SupportedLanguage language ) { switch ( language ) { case SupportedLanguage::English: return ""; // English is a special case. It always returns an empty string as it's a default language. case SupportedLanguage::French: return "fr"; case SupportedLanguage::Polish: return "pl"; case SupportedLanguage::German: return "de"; case SupportedLanguage::Russian: return "ru"; case SupportedLanguage::Italian: return "it"; case SupportedLanguage::Czech: return "cs"; case SupportedLanguage::Norwegian: return "nb"; case SupportedLanguage::Belarusian: return "be"; case SupportedLanguage::Bulgarian: return "bg"; case SupportedLanguage::Ukrainian: return "uk"; case SupportedLanguage::Romanian: return "ro"; case SupportedLanguage::Spanish: return "es"; case SupportedLanguage::Swedish: return "sv"; case SupportedLanguage::Portuguese: return "pt"; case SupportedLanguage::Turkish: return "tr"; case SupportedLanguage::Dutch: return "nl"; case SupportedLanguage::Hungarian: return "hu"; case SupportedLanguage::Danish: return "dk"; case SupportedLanguage::Slovak: return "sk"; case SupportedLanguage::Vietnamese: return "vi"; default: // Did you add a new language? Please add the code to handle it. assert( 0 ); return nullptr; } } SupportedLanguage getLanguageFromAbbreviation( const std::string & abbreviation ) { if ( abbreviation.empty() ) { return SupportedLanguage::English; } const std::string name( StringLower( abbreviation ) ); auto iter = languageName.find( name ); if ( iter == languageName.end() ) { // Unsupported language. Fallback to English. return SupportedLanguage::English; } return iter->second; } void updateAlphabet( const std::string & abbreviation ) { const SupportedLanguage language = getLanguageFromAbbreviation( abbreviation ); const bool isOriginalResourceLanguage = ( language == SupportedLanguage::English ) || ( language == getResourceLanguage() ); AGG::updateLanguageDependentResources( language, isOriginalResourceLanguage ); } SupportedLanguage getCurrentLanguage() { return fheroes2::getLanguageFromAbbreviation( Settings::Get().getGameLanguage() ); } } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_language.h000066400000000000000000000065601456075706000217710ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include namespace fheroes2 { enum class SupportedLanguage : uint8_t { English = 0, // default language for all versions of the game. French, // GOG version Polish, // GOG version German, // GOG version Russian, // Buka and XXI Vek versions Italian, // Rare version? Czech, // Local release occurred in 2002 by CD Projekt Spanish, // Published by Proein. Only Succession Wars // All languages listed below are original to fheroes2. Belarusian, Bulgarian, Danish, Dutch, Hungarian, Norwegian, Portuguese, Romanian, Slovak, Swedish, Turkish, Ukrainian, Vietnamese }; class LanguageSwitcher { public: explicit LanguageSwitcher( const SupportedLanguage language ); LanguageSwitcher( const LanguageSwitcher & ) = delete; LanguageSwitcher( LanguageSwitcher && ) = delete; LanguageSwitcher & operator=( const LanguageSwitcher & ) = delete; LanguageSwitcher & operator=( LanguageSwitcher && ) = delete; ~LanguageSwitcher(); private: const std::string _currentLanguage; }; SupportedLanguage getResourceLanguage(); // This function returns an array of supported languages. If the array contains only one language it must be English. std::vector getSupportedLanguages(); // Return name of the language. Call this function only within the scope of LanguageSwitcher object. const char * getLanguageName( const SupportedLanguage language ); const char * getLanguageAbbreviation( const SupportedLanguage language ); SupportedLanguage getLanguageFromAbbreviation( const std::string & abbreviation ); void updateAlphabet( const std::string & abbreviation ); SupportedLanguage getCurrentLanguage(); } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_map_interface.cpp000066400000000000000000000100611456075706000233250ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "ui_map_interface.h" #include #include #include #include #include "agg_image.h" #include "cursor.h" #include "gamedefs.h" #include "icn.h" #include "image.h" #include "localevent.h" #include "maps.h" #include "screen.h" #include "ui_text.h" namespace Interface { fheroes2::Rect getPopupWindowPosition( const fheroes2::Point & mousePos, const fheroes2::Rect & interfaceArea, const fheroes2::Size & windowSize ) { // If this assertion blows up then you are trying to render a window bigger than the interface area. assert( interfaceArea.width >= windowSize.width && interfaceArea.height >= windowSize.height ); fheroes2::Point windowsPos{ ( ( mousePos.x - BORDERWIDTH ) / TILEWIDTH ) * TILEWIDTH + TILEWIDTH - ( windowSize.width / 2 ), ( ( mousePos.y - BORDERWIDTH ) / TILEWIDTH ) * TILEWIDTH + TILEWIDTH - ( windowSize.height / 2 ) }; // Clamp area to the edges of the area. windowsPos.x = std::clamp( windowsPos.x, BORDERWIDTH, ( interfaceArea.width - windowSize.width ) + BORDERWIDTH ); windowsPos.y = std::clamp( windowsPos.y, BORDERWIDTH, ( interfaceArea.height - windowSize.height ) + BORDERWIDTH ); return { windowsPos.x, windowsPos.y, windowSize.width, windowSize.height }; } void displayStandardPopupWindow( std::string text, const fheroes2::Rect & interfaceArea ) { const CursorRestorer cursorRestorer( false, Cursor::POINTER ); const fheroes2::Sprite & windowImage = fheroes2::AGG::GetICN( ICN::QWIKINFO, 0 ); LocalEvent & le = LocalEvent::Get(); const fheroes2::Rect windowRoi = Interface::getPopupWindowPosition( le.GetMouseCursor(), interfaceArea, { windowImage.width(), windowImage.height() } ); fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::ImageRestorer restorer( display, windowRoi.x, windowRoi.y, windowRoi.width, windowRoi.height ); fheroes2::Blit( windowImage, display, windowRoi.x, windowRoi.y ); const int32_t objectTextBorderedWidth = windowRoi.width - 2 * BORDERWIDTH; const fheroes2::Text textUi( std::move( text ), fheroes2::FontType::smallWhite() ); textUi.draw( windowRoi.x + 22, windowRoi.y - 6 + ( ( windowRoi.height - textUi.height( objectTextBorderedWidth ) ) / 2 ), objectTextBorderedWidth, display ); display.render( restorer.rect() ); while ( le.HandleEvents() && le.MousePressRight() ) { // Do nothing and wait till the user releases the button. } restorer.restore(); display.render( restorer.rect() ); } } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_map_interface.h000066400000000000000000000034021456075706000227730ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include "math_base.h" namespace Interface { fheroes2::Rect getPopupWindowPosition( const fheroes2::Point & mousePos, const fheroes2::Rect & interfaceArea, const fheroes2::Size & windowSize ); void displayStandardPopupWindow( std::string text, const fheroes2::Rect & interfaceArea ); } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_map_object.cpp000066400000000000000000000272571456075706000226520ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "ui_map_object.h" #if defined( WITH_DEBUG ) #include #include #endif #include #include #include #include #include #include #include "agg_image.h" #include "ground.h" #include "icn.h" #include "map_object_info.h" #include "maps_tiles.h" #include "math_base.h" #include "mp2.h" namespace { const int32_t tileSize{ 32 }; void renderObjectPart( fheroes2::Sprite & image, const Maps::ObjectPartInfo & objectPart, const fheroes2::Point & minOffset ) { const int icnId = MP2::getIcnIdFromObjectIcnType( objectPart.icnType ); const fheroes2::Sprite & imagePart = fheroes2::AGG::GetICN( icnId, objectPart.icnIndex ); Blit( imagePart, 0, 0, image, ( objectPart.tileOffset.x - minOffset.x ) * tileSize + imagePart.x(), ( objectPart.tileOffset.y - minOffset.y ) * tileSize + imagePart.y(), imagePart.width(), imagePart.height() ); if ( objectPart.animationFrames > 0 ) { const fheroes2::Sprite & animationFrame = fheroes2::AGG::GetICN( icnId, objectPart.icnIndex + 1 ); Blit( animationFrame, 0, 0, image, ( objectPart.tileOffset.x - minOffset.x ) * tileSize + animationFrame.x(), ( objectPart.tileOffset.y - minOffset.y ) * tileSize + animationFrame.y(), animationFrame.width(), animationFrame.height() ); } } void renderObject( fheroes2::Sprite & image, const Maps::ObjectInfo & object, const fheroes2::Point & minOffset ) { for ( const auto & objectPart : object.groundLevelParts ) { renderObjectPart( image, objectPart, minOffset ); } for ( const auto & objectPart : object.topLevelParts ) { renderObjectPart( image, objectPart, minOffset ); } } void getMinMaxOffsets( const Maps::ObjectInfo & object, fheroes2::Point & minOffset, fheroes2::Point & maxOffset ) { for ( const auto & objectPart : object.groundLevelParts ) { minOffset.x = std::min( minOffset.x, objectPart.tileOffset.x ); minOffset.y = std::min( minOffset.y, objectPart.tileOffset.y ); maxOffset.x = std::max( maxOffset.x, objectPart.tileOffset.x ); maxOffset.y = std::max( maxOffset.y, objectPart.tileOffset.y ); } for ( const auto & objectPart : object.topLevelParts ) { minOffset.x = std::min( minOffset.x, objectPart.tileOffset.x ); minOffset.y = std::min( minOffset.y, objectPart.tileOffset.y ); maxOffset.x = std::max( maxOffset.x, objectPart.tileOffset.x ); maxOffset.y = std::max( maxOffset.y, objectPart.tileOffset.y ); } } } namespace fheroes2 { Sprite generateMapObjectImage( const Maps::ObjectInfo & object ) { if ( object.groundLevelParts.empty() ) { // Why are you passing an empty object? Check your logic! assert( 0 ); return {}; } // The first object part must always have no offset. assert( object.groundLevelParts.front().tileOffset == Point() ); if ( object.groundLevelParts.size() == 1 && object.topLevelParts.empty() ) { const Maps::ObjectPartInfo & objectPart = object.groundLevelParts.front(); Sprite image; if ( objectPart.animationFrames > 0 ) { image.resize( tileSize, tileSize ); image.reset(); renderObjectPart( image, objectPart, { 0, 0 } ); } else { image = AGG::GetICN( MP2::getIcnIdFromObjectIcnType( objectPart.icnType ), objectPart.icnIndex ); } // If it is a one tile image make sure that the offset is in the middle of the image. image.setPosition( -image.width() / 2, -image.height() / 2 ); return image; } #if defined( WITH_DEBUG ) // Verify that all offsets are unique. std::map> uniqueOffsets; for ( const auto & objectPart : object.groundLevelParts ) { const auto [dummy, inserted] = uniqueOffsets[objectPart.icnType].emplace( objectPart.tileOffset ); if ( !inserted ) { // The object hasn't formed properly! assert( 0 ); } } for ( const auto & objectPart : object.topLevelParts ) { const auto [dummy, inserted] = uniqueOffsets[objectPart.icnType].emplace( objectPart.tileOffset ); if ( !inserted ) { // The object hasn't formed properly! assert( 0 ); } } #endif // Calculate the required size of the object in tiles. Point minOffset; Point maxOffset; getMinMaxOffsets( object, minOffset, maxOffset ); // We can use an approximate size of the object based on tiles it needs // but make sure that the offset corresponds to { 0, 0 } tile offset's center. // In other words, the main tile should be the center of the image. const int32_t width{ ( maxOffset.x - minOffset.x + 1 ) * tileSize }; const int32_t height{ ( maxOffset.y - minOffset.y + 1 ) * tileSize }; const Point imageOffset{ ( minOffset.x * tileSize ) - tileSize / 2, ( minOffset.y * tileSize ) - tileSize / 2 }; Sprite image{ width, height, imageOffset.x, imageOffset.y }; // Since we don't generate a pixel precise image make it transparent at first. image.reset(); renderObject( image, object, minOffset ); return image; } Sprite generateTownObjectImage( const int townType, const int color, const int groundId ) { if ( townType < 0 || color < 0 ) { assert( 0 ); return {}; } if ( groundId == Maps::Ground::WATER ) { // Towns can not be placed on water. Sprite image = AGG::GetICN( ICN::SPELLS, 0 ); image.setPosition( -image.width() / 2, -image.height() / 2 ); return image; } const int32_t basementOffset = getTownBasementId( groundId ); // NOTICE: This calculations should be consistent with objects position in KINGDOM_TOWNS and LANDSCAPE_FLAGS vectors. assert( Maps::getObjectsByGroup( Maps::ObjectGroup::LANDSCAPE_TOWN_BASEMENTS ).size() > static_cast( basementOffset ) ); assert( Maps::getObjectsByGroup( Maps::ObjectGroup::KINGDOM_TOWNS ).size() > static_cast( townType ) ); assert( Maps::getObjectsByGroup( Maps::ObjectGroup::LANDSCAPE_FLAGS ).size() > static_cast( color ) * 2 + 1 ); const auto & basementObject = Maps::getObjectsByGroup( Maps::ObjectGroup::LANDSCAPE_TOWN_BASEMENTS )[basementOffset]; const auto & combinedTownObject = Maps::getObjectsByGroup( Maps::ObjectGroup::KINGDOM_TOWNS )[townType]; const auto & leftFlagObject = Maps::getObjectsByGroup( Maps::ObjectGroup::LANDSCAPE_FLAGS )[static_cast( color ) * 2]; const auto & rightFlagObject = Maps::getObjectsByGroup( Maps::ObjectGroup::LANDSCAPE_FLAGS )[static_cast( color ) * 2 + 1]; Maps::ObjectInfo townObject{ combinedTownObject.objectType }; Maps::ObjectInfo shadowObject{ MP2::OBJ_NONE }; for ( const auto & partInfo : combinedTownObject.groundLevelParts ) { if ( partInfo.layerType == Maps::SHADOW_LAYER ) { shadowObject.groundLevelParts.push_back( partInfo ); } else { townObject.groundLevelParts.push_back( partInfo ); } } townObject.topLevelParts = combinedTownObject.topLevelParts; fheroes2::Point maxOffset; fheroes2::Point basementMinOffset; fheroes2::Point townMinOffset; fheroes2::Point shadowMinOffset; const fheroes2::Point leftFlagMinOffset{ 1, 0 }; const fheroes2::Point rightFlagMinOffset{ -1, 0 }; getMinMaxOffsets( basementObject, basementMinOffset, maxOffset ); getMinMaxOffsets( townObject, townMinOffset, maxOffset ); getMinMaxOffsets( shadowObject, shadowMinOffset, maxOffset ); assert( leftFlagObject.groundLevelParts.size() == 1 && leftFlagObject.topLevelParts.empty() ); assert( rightFlagObject.groundLevelParts.size() == 1 && rightFlagObject.topLevelParts.empty() ); fheroes2::Point minOffset = basementMinOffset; minOffset.x = std::min( minOffset.x, townMinOffset.x ); minOffset.x = std::min( minOffset.x, shadowMinOffset.x ); minOffset.y = std::min( minOffset.y, townMinOffset.y ); minOffset.y = std::min( minOffset.y, shadowMinOffset.y ); const int32_t width{ ( maxOffset.x - minOffset.x + 1 ) * tileSize }; const int32_t height{ ( maxOffset.y - minOffset.y + 1 ) * tileSize }; const Point imageOffset{ ( minOffset.x * tileSize ) - tileSize / 2, ( minOffset.y * tileSize ) - tileSize / 2 }; fheroes2::Sprite outputImage( width, height, imageOffset.x, imageOffset.y ); outputImage.reset(); renderObject( outputImage, shadowObject, minOffset ); renderObject( outputImage, basementObject, minOffset ); renderObject( outputImage, townObject, minOffset ); renderObject( outputImage, leftFlagObject, minOffset + leftFlagMinOffset ); renderObject( outputImage, rightFlagObject, minOffset + rightFlagMinOffset ); return outputImage; } int32_t getTownBasementId( const int groundType ) { // NOTICE: 'basementOffset' should be consistent with basement objects position in LANDSCAPE_TOWN_BASEMENTS vector. switch ( groundType ) { case Maps::Ground::WATER: // Logically Water is not allowed but let's do this. assert( 0 ); return 0; case Maps::Ground::GRASS: return 0; case Maps::Ground::SNOW: return 1; case Maps::Ground::SWAMP: return 2; case Maps::Ground::LAVA: return 3; case Maps::Ground::DESERT: return 4; case Maps::Ground::DIRT: return 5; case Maps::Ground::WASTELAND: return 6; case Maps::Ground::BEACH: return 7; default: // Have you added a new ground? Add the logic above! assert( 0 ); break; } return 0; } } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_map_object.h000066400000000000000000000034161456075706000223060ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include "image.h" namespace Maps { struct ObjectInfo; } namespace fheroes2 { Sprite generateMapObjectImage( const Maps::ObjectInfo & object ); Sprite generateTownObjectImage( const int townType, const int color, const int groundId ); int32_t getTownBasementId( const int groundType ); } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_monster.cpp000066400000000000000000000053771456075706000222350ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "ui_monster.h" #include "agg_image.h" #include "icn.h" #include "image.h" #include "monster.h" #include "race.h" namespace fheroes2 { void renderMonsterFrame( const Monster & monster, Image & output, const Point & offset ) { switch ( monster.GetRace() ) { case Race::KNGT: Blit( AGG::GetICN( ICN::STRIP, 4 ), output, offset.x, offset.y ); break; case Race::BARB: Blit( AGG::GetICN( ICN::STRIP, 5 ), output, offset.x, offset.y ); break; case Race::SORC: Blit( AGG::GetICN( ICN::STRIP, 6 ), output, offset.x, offset.y ); break; case Race::WRLK: Blit( AGG::GetICN( ICN::STRIP, 7 ), output, offset.x, offset.y ); break; case Race::WZRD: Blit( AGG::GetICN( ICN::STRIP, 8 ), output, offset.x, offset.y ); break; case Race::NECR: Blit( AGG::GetICN( ICN::STRIP, 9 ), output, offset.x, offset.y ); break; default: Blit( AGG::GetICN( ICN::STRIP, 10 ), output, offset.x, offset.y ); break; } const fheroes2::Sprite & monsterImage = fheroes2::AGG::GetICN( monster.ICNMonh(), 0 ); fheroes2::Blit( monsterImage, output, offset.x + monsterImage.x(), offset.y + monsterImage.y() ); } } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_monster.h000066400000000000000000000031661456075706000216740ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include "math_base.h" class Monster; namespace fheroes2 { class Image; void renderMonsterFrame( const Monster & monster, Image & output, const Point & offset ); } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_object_rendering.h000066400000000000000000000051761456075706000235130ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include "math_base.h" namespace fheroes2 { struct ObjectRenderingInfo { Point tileOffset; Point imageOffset; Rect area; int icnId{ -1 }; uint32_t icnIndex{ 0 }; bool isFlipped{ false }; uint8_t alphaValue{ 255 }; ObjectRenderingInfo() = default; ObjectRenderingInfo( const Point & tileOffset_, const Point & imageOffset_, const Rect & area_, const int icnId_, const uint32_t icnIndex_, const bool isFlipped_, const uint8_t alphaValue_ ) : tileOffset( tileOffset_ ) , imageOffset( imageOffset_ ) , area( area_ ) , icnId( icnId_ ) , icnIndex( icnIndex_ ) , isFlipped( isFlipped_ ) , alphaValue( alphaValue_ ) { // Do nothing. } ObjectRenderingInfo( const ObjectRenderingInfo & ) = default; ObjectRenderingInfo( ObjectRenderingInfo && ) = default; ~ObjectRenderingInfo() = default; ObjectRenderingInfo & operator=( const ObjectRenderingInfo & ) = default; ObjectRenderingInfo & operator=( ObjectRenderingInfo && ) = default; }; } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_option_item.cpp000066400000000000000000000051161456075706000230630ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "ui_option_item.h" #include #include #include "image.h" #include "screen.h" #include "ui_text.h" namespace { const int32_t textVerticalOffset = 12; const int32_t nameVerticalOffset = 6; } namespace fheroes2 { void drawOption( const Rect & optionRoi, const Sprite & icon, std::string titleText, std::string valueText, const int32_t textMaxWidth ) { Display & display = Display::instance(); const Text title( std::move( titleText ), FontType::smallWhite() ); const Text name( std::move( valueText ), FontType::smallWhite() ); // Calculate the text field left border position to horizontally align the text to the icon center. const int32_t textHorizontalOffset = optionRoi.x + ( icon.width() - textMaxWidth ) / 2; title.draw( textHorizontalOffset, optionRoi.y - textVerticalOffset + title.height() - title.height( textMaxWidth ), textMaxWidth, display ); name.draw( textHorizontalOffset, optionRoi.y + optionRoi.height + nameVerticalOffset, textMaxWidth, display ); Blit( icon, 0, 0, display, optionRoi.x, optionRoi.y, icon.width(), icon.height() ); } } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_option_item.h000066400000000000000000000034601456075706000225300ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include "math_base.h" namespace fheroes2 { enum UiOptionTextWidth : int32_t { TWO_ELEMENTS_ROW = 113, THREE_ELEMENTS_ROW = 87 }; class Sprite; void drawOption( const Rect & optionRoi, const Sprite & icon, std::string titleText, std::string valueText, const int32_t textMaxWidth ); } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_scrollbar.cpp000066400000000000000000000237051456075706000225240ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "ui_scrollbar.h" #include #include #include namespace fheroes2 { Scrollbar::Scrollbar() : _minIndex( 0 ) , _maxIndex( 0 ) , _currentIndex( 0 ) { // Do nothing. } void Scrollbar::setImage( const Image & image ) { Copy( image, *this ); } void Scrollbar::setArea( const Rect & area ) { _area = area; } void Scrollbar::setRange( const int minIndex, const int maxIndex ) { assert( maxIndex >= minIndex ); _minIndex = minIndex; _maxIndex = maxIndex; _currentIndex = _minIndex; if ( _maxIndex == _minIndex ) { setPosition( _area.x + ( _area.width - width() ) / 2, _area.y + ( _area.height - height() ) / 2 ); } else { if ( _isVertical() ) { setPosition( _area.x + ( _area.width - width() ) / 2, _area.y ); } else { setPosition( _area.x, _area.y + ( _area.height - height() ) / 2 ); } } } void Scrollbar::forward() { if ( _currentIndex < _maxIndex ) { moveToIndex( _currentIndex + 1 ); } } void Scrollbar::backward() { if ( _currentIndex > _minIndex ) { moveToIndex( _currentIndex - 1 ); } } bool Scrollbar::moveToIndex( const int indexId ) { const int32_t roiWidth = _area.width - width(); const int32_t roiHeight = _area.height - height(); if ( _maxIndex == _minIndex ) { // There are less elements than the list height. We set scrollbar slider to the center of scrollbar. setPosition( _area.x + roiWidth / 2, _area.y + roiHeight / 2 ); return false; } if ( indexId < _minIndex ) { _currentIndex = _minIndex; } else if ( indexId > _maxIndex ) { _currentIndex = _maxIndex; } else { _currentIndex = indexId; } Point newPosition; if ( _isVertical() ) { newPosition.x = _area.x + roiWidth / 2; newPosition.y = _area.y + ( _currentIndex - _minIndex ) * roiHeight / ( _maxIndex - _minIndex ); } else { newPosition.x = _area.x + ( _currentIndex - _minIndex ) * roiWidth / ( _maxIndex - _minIndex ); newPosition.y = _area.y + roiHeight / 2; } if ( newPosition.x != x() || newPosition.y != y() ) { // Update only on the change. setPosition( newPosition.x, newPosition.y ); return true; } return false; } void Scrollbar::moveToPos( const Point & position ) { if ( _maxIndex == _minIndex ) return; const int roiWidth = _area.width - width(); const int roiHeight = _area.height - height(); if ( _isVertical() ) { const int32_t scrollbarImageMiddle = height() / 2; const int32_t minYPos = _area.y + scrollbarImageMiddle; const int32_t maxYPos = _area.y + roiHeight + scrollbarImageMiddle; int32_t posY = position.y; if ( posY < minYPos ) posY = minYPos; else if ( posY > maxYPos ) posY = maxYPos; const double tempPos = static_cast( posY - minYPos ) * ( _maxIndex - _minIndex ) / roiHeight; _currentIndex = static_cast( std::lround( tempPos ) ) + _minIndex; setPosition( _area.x + roiWidth / 2, posY - scrollbarImageMiddle ); } else { const int32_t scrollbarImageMiddle = width() / 2; const int32_t minXPos = _area.x + scrollbarImageMiddle; const int32_t maxXPos = _area.x + roiWidth + scrollbarImageMiddle; int32_t posX = position.x; if ( posX < minXPos ) posX = minXPos; else if ( posX > maxXPos ) posX = maxXPos; const double tempPos = static_cast( posX - minXPos ) * ( _maxIndex - _minIndex ) / roiWidth; _currentIndex = static_cast( std::lround( tempPos ) ) + _minIndex; setPosition( posX - scrollbarImageMiddle, _area.y + roiHeight / 2 ); } } Image generateScrollbarSlider( const Image & originalSlider, const bool horizontalSlider, const int32_t sliderAreaLength, const int32_t elementCountPerView, const int32_t totalElementCount, const Rect & startSliderArea, const Rect & middleSliderArea ) { if ( originalSlider.empty() ) { // Why do you pass an empty image? assert( 0 ); return originalSlider; } assert( sliderAreaLength > 0 && elementCountPerView > 0 ); if ( horizontalSlider ) { if ( middleSliderArea.width < 1 ) { // Middle area cannot be empty! assert( 0 ); return originalSlider; } if ( sliderAreaLength < originalSlider.width() ) { // The slider is bigger than the area! assert( 0 ); return originalSlider; } } else { if ( middleSliderArea.height < 1 ) { // Middle area cannot be empty! assert( 0 ); return originalSlider; } if ( sliderAreaLength < originalSlider.height() ) { // The slider is bigger than the area! assert( 0 ); return originalSlider; } } const int32_t currentSliderLength = horizontalSlider ? originalSlider.width() : originalSlider.height(); if ( sliderAreaLength * elementCountPerView < currentSliderLength * totalElementCount ) { // Slider is too big. return originalSlider; } const int32_t step = horizontalSlider ? middleSliderArea.width : middleSliderArea.height; const int32_t middleLength = ( sliderAreaLength * elementCountPerView / std::max( elementCountPerView, totalElementCount ) ) - currentSliderLength; int32_t width = originalSlider.width(); int32_t height = originalSlider.height(); if ( horizontalSlider ) { width += middleLength; } else { height += middleLength; } Image output( width, height ); output.reset(); Copy( originalSlider, startSliderArea.x, startSliderArea.y, output, startSliderArea.x, startSliderArea.y, startSliderArea.width, startSliderArea.height ); int32_t offset = 0; if ( horizontalSlider ) { offset = startSliderArea.x + startSliderArea.width; } else { offset = startSliderArea.y + startSliderArea.height; } const int32_t middleChunkCount = middleLength / step; for ( int32_t i = 0; i < middleChunkCount; ++i ) { if ( horizontalSlider ) { Copy( originalSlider, middleSliderArea.x, middleSliderArea.y, output, offset, startSliderArea.y, middleSliderArea.width, middleSliderArea.height ); offset += middleSliderArea.width; } else { Copy( originalSlider, middleSliderArea.x, middleSliderArea.y, output, startSliderArea.x, offset, middleSliderArea.width, middleSliderArea.height ); offset += middleSliderArea.height; } } // Draw leftovers. const int32_t leftover = middleLength - middleChunkCount * step; if ( leftover > 0 ) { if ( horizontalSlider ) { Copy( originalSlider, middleSliderArea.x, middleSliderArea.y, output, offset, startSliderArea.y, leftover, middleSliderArea.height ); offset += leftover; } else { Copy( originalSlider, middleSliderArea.x, middleSliderArea.y, output, startSliderArea.x, offset, middleSliderArea.width, leftover ); offset += leftover; } } if ( horizontalSlider ) { Copy( originalSlider, startSliderArea.x + startSliderArea.width, startSliderArea.y, output, offset, startSliderArea.y, originalSlider.width() - startSliderArea.width, startSliderArea.height ); } else { Copy( originalSlider, startSliderArea.x, startSliderArea.y + startSliderArea.height, output, startSliderArea.x, offset, startSliderArea.width, originalSlider.height() - startSliderArea.height ); } return output; } } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_scrollbar.h000066400000000000000000000074251456075706000221720ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include "image.h" #include "math_base.h" #include "ui_tool.h" namespace fheroes2 { class Scrollbar : public fheroes2::MovableSprite { public: Scrollbar(); Scrollbar( const Scrollbar & ) = delete; Scrollbar & operator=( const Scrollbar & ) = delete; ~Scrollbar() override { // We need to reset the ImageRestorer for the scroll bar to avoid its restoration by destructor and possible race with the dialog window restorer. _resetRestorer(); } // The original resources do not support proper scrollbar slider scaling. Use generateScrollbarSlider() function to generate needed image. void setImage( const Image & image ); void setArea( const Rect & area ); void setRange( const int minIndex, const int maxIndex ); void forward(); void backward(); // Returns true if the position and/or index is updated. bool moveToIndex( const int indexId ); void moveToPos( const Point & position ); // Update position of the scrollbar based on the index. Useful for mouse movement and release. bool updatePosition() { return moveToIndex( _currentIndex ); } int currentIndex() const { return _currentIndex; } int minIndex() const { return _minIndex; } int maxIndex() const { return _maxIndex; } const Rect & getArea() const { return _area; } private: Rect _area; int _minIndex; int _maxIndex; int _currentIndex; bool _isVertical() const { return _area.width < _area.height; } }; // The original scrollbar slider has fixed size. This is a not user-friendly solution as on big screens it might look extremely tiny. // In the most modern applications the slider size depends on the number of elements. The lesser the number the bigger the slider. Image generateScrollbarSlider( const Image & originalSlider, const bool horizontalSlider, const int32_t sliderAreaLength, const int32_t elementCountPerView, const int32_t totalElementCount, const Rect & startSliderArea, const Rect & middleSliderArea ); } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_text.cpp000066400000000000000000001037561456075706000215320ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "ui_text.h" #include #include #include #include #include #include #include "agg_image.h" namespace { const uint8_t lineSeparator = '\n'; const uint8_t hyphenChar{ '-' }; const uint8_t invalidChar = '?'; class CharValidator { public: explicit CharValidator( const fheroes2::FontSize fontSize ) : _charLimit( fheroes2::AGG::getCharacterLimit( fontSize ) ) {} bool isValid( const uint8_t character ) const { return character >= 0x21 && character <= _charLimit; } private: const uint32_t _charLimit; }; bool isSpaceChar( const uint8_t character ) { return ( character == 0x20 ); } int32_t getSpaceCharWidth( const fheroes2::FontSize fontSize ) { switch ( fontSize ) { case fheroes2::FontSize::SMALL: return 4; case fheroes2::FontSize::NORMAL: return 6; case fheroes2::FontSize::LARGE: case fheroes2::FontSize::BUTTON_RELEASED: case fheroes2::FontSize::BUTTON_PRESSED: return 8; default: assert( 0 ); // Did you add a new font size? Please add implementation. } return 0; } int32_t getCharWidth( const uint8_t character, const fheroes2::FontType & fontType ) { const fheroes2::Sprite & image = fheroes2::AGG::getChar( character, fontType ); assert( ( fontType.size != fheroes2::FontSize::BUTTON_RELEASED && fontType.size != fheroes2::FontSize::BUTTON_PRESSED && image.x() >= 0 ) || image.x() < 0 ); return image.x() + image.width(); } int32_t getLineWidth( const uint8_t * data, const int32_t size, const fheroes2::FontType & fontType ) { assert( data != nullptr && size > 0 ); const CharValidator validator( fontType.size ); int32_t width = 0; const int32_t spaceCharWidth = getSpaceCharWidth( fontType.size ); const uint8_t * dataEnd = data + size; while ( data != dataEnd ) { if ( validator.isValid( *data ) ) { width += getCharWidth( *data, fontType ); } else if ( isSpaceChar( *data ) ) { width += spaceCharWidth; } else { width += getCharWidth( invalidChar, fontType ); } ++data; } return width; } int32_t getMaxCharacterCount( const uint8_t * data, const int32_t size, const fheroes2::FontType & fontType, const int32_t maxWidth ) { assert( data != nullptr && size > 0 && maxWidth > 0 ); const CharValidator validator( fontType.size ); int characterCount = 0; int32_t width = 0; const int32_t spaceCharWidth = getSpaceCharWidth( fontType.size ); const uint8_t * dataEnd = data + size; while ( data != dataEnd ) { if ( validator.isValid( *data ) ) { width += getCharWidth( *data, fontType ); } else if ( isSpaceChar( *data ) ) { width += spaceCharWidth; } else { width += getCharWidth( invalidChar, fontType ); } if ( width > maxWidth ) { return characterCount; } ++data; ++characterCount; } return characterCount; } // Ignore spaces at the end of the line. This function must be used only at the time of final rendering. int32_t getTruncatedLineWidth( const uint8_t * data, const int32_t size, const fheroes2::FontType & fontType ) { assert( data != nullptr && size > 0 ); const CharValidator validator( fontType.size ); int32_t width = 0; int32_t spaceWidth = 0; const int32_t spaceCharWidth = getSpaceCharWidth( fontType.size ); const uint8_t * dataEnd = data + size; while ( data != dataEnd ) { if ( isSpaceChar( *data ) ) { spaceWidth += spaceCharWidth; } else { width += spaceWidth; spaceWidth = 0; if ( validator.isValid( *data ) ) { width += getCharWidth( *data, fontType ); } else { width += getCharWidth( invalidChar, fontType ); } } ++data; } return width; } // Returns text lines parameters (in pixels) in 'offsets': x - represents the line widths, y - represents vertical line shift. void getMultiRowInfo( const uint8_t * data, const int32_t size, const int32_t maxWidth, const fheroes2::FontType & fontType, const int32_t rowHeight, std::deque & offsets ) { assert( data != nullptr && size > 0 && maxWidth > 0 ); if ( offsets.empty() ) { offsets.emplace_back(); } const CharValidator validator( fontType.size ); // We need to cut sentences not in the middle of a word but by a space or invalid characters. const uint8_t * dataEnd = data + size; const int32_t spaceCharWidth = getSpaceCharWidth( fontType.size ); int32_t lineLength = 0; int32_t lastWordLength = 0; int32_t lineWidth = 0; fheroes2::Point * offset = &offsets.back(); while ( data != dataEnd ) { if ( *data == lineSeparator ) { if ( lineLength > 0 ) { offset->x += lineWidth; lineLength = 0; lastWordLength = 0; lineWidth = 0; } offsets.emplace_back( 0, offset->y + rowHeight ); offset = &offsets.back(); ++data; } else { // This is another character in the line. Get its width. int32_t charWidth{ 0 }; const bool isSpace = isSpaceChar( *data ); if ( isSpace ) { charWidth = spaceCharWidth; } else { charWidth = getCharWidth( validator.isValid( *data ) ? *data : invalidChar, fontType ); } if ( offset->x + lineWidth + charWidth > maxWidth ) { // Current character has exceeded the maximum line width. if ( lineLength == lastWordLength ) { // This is the only word in the line. // Search for '-' symbol to avoid truncating the word in the middle. const uint8_t * hyphenPos = data - lineLength; for ( ; hyphenPos != data; ++hyphenPos ) { if ( *hyphenPos == hyphenChar ) { break; } } if ( hyphenPos != data ) { // The '-' symbol has been found. In this case we consider everything after it as a separate word. offset->x += getLineWidth( data - lineLength, static_cast( hyphenPos + lineLength - data ) + 1, fontType ); data = hyphenPos; ++data; } else { offset->x += lineWidth; } } else { if ( isSpace ) { // Current character could be a space character then current line is over. offset->x += lineWidth; // We skip this space character. ++data; } else { // Exclude last word from this line. offset->x += getLineWidth( data - lineLength, lineLength - lastWordLength, fontType ); // Go back to the start of the word. data -= lastWordLength; } } lineLength = 0; lastWordLength = 0; lineWidth = 0; offsets.emplace_back( 0, offset->y + rowHeight ); offset = &offsets.back(); } else { ++data; ++lineLength; lineWidth += charWidth; lastWordLength = isSpace ? 0 : ( lastWordLength + 1 ); } } } offset->x += lineWidth; } int32_t render( const uint8_t * data, const int32_t size, const int32_t x, const int32_t y, fheroes2::Image & output, const fheroes2::Rect & imageRoi, const fheroes2::FontType & fontType ) { assert( data != nullptr && size > 0 && !output.empty() ); const CharValidator validator( fontType.size ); int32_t offsetX = x; const int32_t spaceCharWidth = getSpaceCharWidth( fontType.size ); const uint8_t * dataEnd = data + size; for ( ; data != dataEnd; ++data ) { if ( isSpaceChar( *data ) ) { offsetX += spaceCharWidth; continue; } // TODO: remove this hack or expand it to cover more cases. if ( *data == lineSeparator ) { // This should never happen as line cannot contain line separator in the middle. // But due to some limitations in UI we have to deal with it. // The only way is to just ignore it here. continue; } const fheroes2::Sprite & charSprite = fheroes2::AGG::getChar( validator.isValid( *data ) ? *data : invalidChar, fontType ); assert( !charSprite.empty() ); const fheroes2::Rect charRoi{ offsetX + charSprite.x(), y + charSprite.y(), charSprite.width(), charSprite.height() }; const fheroes2::Rect overlappedRoi = imageRoi ^ charRoi; fheroes2::Blit( charSprite, overlappedRoi.x - charRoi.x, overlappedRoi.y - charRoi.y, output, overlappedRoi.x, overlappedRoi.y, overlappedRoi.width, overlappedRoi.height ); offsetX += charSprite.width() + charSprite.x(); } return offsetX; } void renderLine( const uint8_t * data, const int32_t size, const int32_t x, const int32_t y, const int32_t maxWidth, fheroes2::Image & output, const fheroes2::Rect & imageRoi, const fheroes2::FontType & fontType, const bool align ) { if ( align ) { const int32_t correctedLineWidth = getTruncatedLineWidth( data, size, fontType ); assert( correctedLineWidth <= maxWidth ); // For button font single letters in a row we add 1 extra pixel to the width to more properly center odd-width letters. const int32_t extraOffsetX = ( size == 1 && ( maxWidth % 2 == 0 ) && ( fontType.size == fheroes2::FontSize::BUTTON_RELEASED || fontType.size == fheroes2::FontSize::BUTTON_PRESSED ) ) ? 1 : 0; render( data, size, x + ( maxWidth - correctedLineWidth + extraOffsetX ) / 2, y, output, imageRoi, fontType ); } else { render( data, size, x, y, output, imageRoi, fontType ); } } void render( const uint8_t * data, const int32_t size, const int32_t x, const int32_t y, const int32_t maxWidth, fheroes2::Image & output, const fheroes2::Rect & imageRoi, const fheroes2::FontType & fontType, const int32_t rowHeight, const bool align, std::deque & offsets ) { assert( data != nullptr && size > 0 && !output.empty() && maxWidth > 0 ); const CharValidator validator( fontType.size ); // We need to cut sentences not in the middle of a word but by a space or invalid characters. const uint8_t * dataEnd = data + size; int32_t lineLength = 0; int32_t lastWordLength = 0; int32_t lineWidth = 0; fheroes2::Point staticOffset; fheroes2::Point * offset; const bool hasInputOffsets = !offsets.empty(); if ( hasInputOffsets ) { offset = &offsets.front(); } else { offset = &staticOffset; } const int32_t fontHeight = fheroes2::getFontHeight( fontType.size ); const int32_t yPos = y + ( rowHeight - fontHeight ) / 2; const int32_t spaceCharWidth = getSpaceCharWidth( fontType.size ); while ( data != dataEnd ) { if ( *data == lineSeparator ) { // End of line. Render the current line. if ( lineLength > 0 ) { renderLine( data - lineLength, lineLength, x + offset->x, yPos + offset->y, maxWidth, output, imageRoi, fontType, align ); lineLength = 0; lastWordLength = 0; lineWidth = 0; } if ( hasInputOffsets ) { offsets.pop_front(); assert( !offsets.empty() ); offset = &offsets.front(); } else { offset->x = 0; offset->y += rowHeight; } ++data; } else { int32_t charWidth{ 0 }; const bool isSpace = isSpaceChar( *data ); if ( isSpace ) { charWidth = spaceCharWidth; } else { charWidth = getCharWidth( validator.isValid( *data ) ? *data : invalidChar, fontType ); } if ( offset->x + lineWidth + charWidth > maxWidth ) { // Current character has exceeded the maximum line width. const uint8_t * line = data - lineLength; if ( lineLength == 0 && lastWordLength == 0 ) { // No new characters can be added. // This can happen when a new text in a multi-font text being appended just at the end of the line. assert( lineWidth == 0 ); } else if ( lineLength == lastWordLength ) { // This is the only word in the line. // Search for '-' symbol to avoid truncating the word in the middle. const uint8_t * hyphenPos = data - lineLength; for ( ; hyphenPos != data; ++hyphenPos ) { if ( *hyphenPos == hyphenChar ) { break; } } if ( hyphenPos != data ) { renderLine( line, static_cast( hyphenPos + lineLength - data ) + 1, x + offset->x, yPos + offset->y, maxWidth, output, imageRoi, fontType, align ); data = hyphenPos; ++data; } else { renderLine( line, lineLength, x + offset->x, yPos + offset->y, maxWidth, output, imageRoi, fontType, align ); } } else { if ( isSpace ) { // Current character could be a space character then current line is over. renderLine( line, lineLength, x + offset->x, yPos + offset->y, maxWidth, output, imageRoi, fontType, align ); // We skip this space character. ++data; } else { // Exclude last word from this line. renderLine( line, lineLength - lastWordLength, x + offset->x, yPos + offset->y, maxWidth, output, imageRoi, fontType, align ); // Go back to the start of the word. data -= lastWordLength; } } lineLength = 0; lastWordLength = 0; lineWidth = 0; if ( hasInputOffsets ) { offsets.pop_front(); assert( !offsets.empty() ); offset = &offsets.front(); } else { offset->x = 0; offset->y += rowHeight; } // This is a new line. getMultiRowInfo() function does estimations while ignoring whitespace characters at the start and end of lines. // If the next line starts from a whitespace character it is important to skip it. while ( data != dataEnd ) { if ( !isSpaceChar( *data ) ) { break; } ++data; } } else { ++data; ++lineLength; lineWidth += charWidth; lastWordLength = isSpace ? 0 : ( lastWordLength + 1 ); } } } if ( lineLength > 0 ) { renderLine( data - lineLength, lineLength, x + offset->x, yPos + offset->y, maxWidth, output, imageRoi, fontType, align ); offset->x += lineWidth; } } int32_t getMaxWordWidth( const uint8_t * data, const int32_t size, const fheroes2::FontType & fontType ) { assert( data != nullptr && size > 0 ); int32_t maxWidth = 1; const CharValidator validator( fontType.size ); int32_t width = 0; const uint8_t * dataEnd = data + size; while ( data != dataEnd ) { if ( *data == lineSeparator || isSpaceChar( *data ) ) { // If it is the end of line ("\n") or a space (" "), then the word has ended. if ( maxWidth < width ) { maxWidth = width; } width = 0; } else if ( validator.isValid( *data ) ) { width += getCharWidth( *data, fontType ); } else { width += getCharWidth( invalidChar, fontType ); } ++data; } if ( maxWidth < width ) { maxWidth = width; } return maxWidth; } } namespace fheroes2 { int32_t getFontHeight( const fheroes2::FontSize fontSize ) { switch ( fontSize ) { case fheroes2::FontSize::SMALL: return 8 + 2 + 1; case fheroes2::FontSize::NORMAL: return 13 + 3 + 1; case fheroes2::FontSize::LARGE: return 26 + 6 + 1; case fheroes2::FontSize::BUTTON_RELEASED: case fheroes2::FontSize::BUTTON_PRESSED: return 15; default: assert( 0 ); // Did you add a new font size? Please add implementation. } return 0; } TextBase::~TextBase() = default; Text::Text( std::string text, const FontType fontType ) : _text( std::move( text ) ) , _fontType( fontType ) { // Do nothing. } Text::~Text() = default; // TODO: Properly handle strings with many text lines ('\n'). Now their widths are counted as if they're one line. int32_t Text::width() const { return getLineWidth( reinterpret_cast( _text.data() ), static_cast( _text.size() ), _fontType ); } // TODO: Properly handle strings with many text lines ('\n'). Now their heights are counted as if they're one line. int32_t Text::height() const { return getFontHeight( _fontType.size ); } int32_t Text::width( const int32_t maxWidth ) const { if ( _text.empty() ) { return 0; } const int32_t fontHeight = getFontHeight( _fontType.size ); std::deque offsets; getMultiRowInfo( reinterpret_cast( _text.data() ), static_cast( _text.size() ), maxWidth, _fontType, fontHeight, offsets ); int32_t maxRowWidth = offsets.front().x; for ( const Point & point : offsets ) { maxRowWidth = std::max( maxRowWidth, point.x ); } return maxRowWidth; } int32_t Text::height( const int32_t maxWidth ) const { if ( _text.empty() ) { return 0; } const int32_t fontHeight = getFontHeight( _fontType.size ); std::deque offsets; getMultiRowInfo( reinterpret_cast( _text.data() ), static_cast( _text.size() ), maxWidth, _fontType, fontHeight, offsets ); return offsets.back().y + fontHeight; } int32_t Text::rows( const int32_t maxWidth ) const { if ( _text.empty() ) { return 0; } const int32_t fontHeight = getFontHeight( _fontType.size ); std::deque offsets; getMultiRowInfo( reinterpret_cast( _text.data() ), static_cast( _text.size() ), maxWidth, _fontType, fontHeight, offsets ); return offsets.back().y / fontHeight + 1; } void Text::drawInRoi( const int32_t x, const int32_t y, Image & output, const Rect & imageRoi ) const { if ( output.empty() || _text.empty() ) { // No use to render something on an empty image or if something is empty. return; } render( reinterpret_cast( _text.data() ), static_cast( _text.size() ), x, y, output, imageRoi, _fontType ); } void Text::drawInRoi( const int32_t x, const int32_t y, const int32_t maxWidth, Image & output, const Rect & imageRoi ) const { if ( output.empty() || _text.empty() ) { // No use to render something on an empty image or if something is empty. return; } assert( maxWidth > 0 ); // Why is the limit less than 1? if ( maxWidth <= 0 ) { drawInRoi( x, y, output, imageRoi ); return; } const int32_t fontHeight = getFontHeight( _fontType.size ); std::deque offsets; getMultiRowInfo( reinterpret_cast( _text.data() ), static_cast( _text.size() ), maxWidth, _fontType, fontHeight, offsets ); int32_t xOffset = 0; int32_t correctedWidth = maxWidth; if ( offsets.size() > 1 ) { // This is a multi-line message. Optimize it to fit the text evenly. int32_t startWidth = getMaxWordWidth( reinterpret_cast( _text.data() ), static_cast( _text.size() ), _fontType ); int32_t endWidth = maxWidth; while ( startWidth + 1 < endWidth ) { const int32_t currentWidth = ( endWidth + startWidth ) / 2; std::deque tempOffsets; getMultiRowInfo( reinterpret_cast( _text.data() ), static_cast( _text.size() ), currentWidth, _fontType, fontHeight, tempOffsets ); if ( tempOffsets.size() > offsets.size() ) { startWidth = currentWidth; continue; } correctedWidth = currentWidth; endWidth = currentWidth; } } else { // This is a single-line message. Find its length. correctedWidth = width(); } assert( correctedWidth <= maxWidth ); // Center text according to the maximum width. xOffset = ( maxWidth - correctedWidth ) / 2; offsets.clear(); render( reinterpret_cast( _text.data() ), static_cast( _text.size() ), x + xOffset, y, correctedWidth, output, imageRoi, _fontType, fontHeight, true, offsets ); } bool Text::empty() const { return _text.empty(); } void Text::set( std::string text, const FontType fontType ) { _text = std::move( text ); _fontType = fontType; } void Text::fitToOneRow( const int32_t maxWidth ) { assert( maxWidth > 0 ); // Why is the limit less than 1? if ( maxWidth <= 0 ) { return; } if ( _text.empty() ) { // Nothing needs to be done. return; } const int32_t originalTextWidth = getTruncatedLineWidth( reinterpret_cast( _text.data() ), static_cast( _text.size() ), _fontType ); if ( originalTextWidth <= maxWidth ) { // Nothing to do. The text is not longer than the provided maximum width. return; } const std::string truncatedEnding( "..." ); const int32_t truncationSymbolWidth = getLineWidth( reinterpret_cast( truncatedEnding.data() ), static_cast( truncatedEnding.size() ), _fontType ); const int32_t maxCharacterCount = getMaxCharacterCount( reinterpret_cast( _text.data() ), static_cast( _text.size() ), _fontType, maxWidth - truncationSymbolWidth ); _text.resize( maxCharacterCount ); _text += truncatedEnding; } std::string Text::text() const { return _text; } MultiFontText::~MultiFontText() = default; void MultiFontText::add( Text text ) { if ( !text._text.empty() ) { _texts.emplace_back( std::move( text ) ); } } int32_t MultiFontText::width() const { int32_t totalWidth = 0; for ( const Text & text : _texts ) { totalWidth += text.width(); } return totalWidth; } int32_t MultiFontText::height() const { int32_t maxHeight = 0; for ( const Text & text : _texts ) { const int32_t height = text.height(); if ( maxHeight < height ) { maxHeight = height; } } return maxHeight; } int32_t MultiFontText::width( const int32_t maxWidth ) const { const int32_t maxFontHeight = height(); std::deque offsets; for ( const Text & text : _texts ) { getMultiRowInfo( reinterpret_cast( text._text.data() ), static_cast( text._text.size() ), maxWidth, text._fontType, maxFontHeight, offsets ); } int32_t maxRowWidth = offsets.front().x; for ( const Point & point : offsets ) { maxRowWidth = std::max( maxRowWidth, point.x ); } return maxRowWidth; } int32_t MultiFontText::height( const int32_t maxWidth ) const { const int32_t maxFontHeight = height(); std::deque offsets; for ( const Text & text : _texts ) { getMultiRowInfo( reinterpret_cast( text._text.data() ), static_cast( text._text.size() ), maxWidth, text._fontType, maxFontHeight, offsets ); } return offsets.back().y + maxFontHeight; } int32_t MultiFontText::rows( const int32_t maxWidth ) const { if ( _texts.empty() ) { return 0; } const int32_t maxFontHeight = height(); std::deque offsets; for ( const Text & text : _texts ) { if ( text._text.empty() ) { continue; } getMultiRowInfo( reinterpret_cast( text._text.data() ), static_cast( text._text.size() ), maxWidth, text._fontType, maxFontHeight, offsets ); } if ( offsets.empty() ) { return 0; } return offsets.back().y / maxFontHeight + 1; } void MultiFontText::drawInRoi( const int32_t x, const int32_t y, Image & output, const Rect & imageRoi ) const { if ( output.empty() || _texts.empty() ) { // No use to render something on an empty image or if something is empty. return; } const int32_t maxFontHeight = height(); int32_t offsetX = x; for ( const Text & text : _texts ) { const int32_t fontHeight = getFontHeight( text._fontType.size ); offsetX = render( reinterpret_cast( text._text.data() ), static_cast( text._text.size() ), offsetX, y + ( maxFontHeight - fontHeight ) / 2, output, imageRoi, text._fontType ); } } void MultiFontText::drawInRoi( const int32_t x, const int32_t y, const int32_t maxWidth, Image & output, const Rect & imageRoi ) const { if ( output.empty() || _texts.empty() ) { // No use to render something on an empty image or if something is empty. return; } assert( maxWidth > 0 ); // Why is the limit less than 1? if ( maxWidth <= 0 ) { drawInRoi( x, y, output, imageRoi ); return; } const int32_t maxFontHeight = height(); std::deque offsets; for ( const Text & text : _texts ) { getMultiRowInfo( reinterpret_cast( text._text.data() ), static_cast( text._text.size() ), maxWidth, text._fontType, maxFontHeight, offsets ); } int32_t xOffset = 0; int32_t correctedWidth = maxWidth; if ( offsets.size() > 1 ) { // This is a multi-line message. Optimize it to fit the text evenly. int32_t startWidth = 1; for ( const Text & text : _texts ) { const int32_t maxWordWidth = getMaxWordWidth( reinterpret_cast( text._text.data() ), static_cast( text._text.size() ), text._fontType ); if ( startWidth < maxWordWidth ) { startWidth = maxWordWidth; } } int32_t endWidth = maxWidth; while ( startWidth + 1 < endWidth ) { const int32_t currentWidth = ( endWidth + startWidth ) / 2; std::deque tempOffsets; for ( const Text & text : _texts ) { getMultiRowInfo( reinterpret_cast( text._text.data() ), static_cast( text._text.size() ), currentWidth, text._fontType, maxFontHeight, tempOffsets ); } if ( tempOffsets.size() > offsets.size() ) { startWidth = currentWidth; continue; } correctedWidth = currentWidth; endWidth = currentWidth; std::swap( offsets, tempOffsets ); } xOffset = ( maxWidth - correctedWidth ) / 2; } else { // This is a single-line message. Find its length and center it according to the maximum width. correctedWidth = width(); assert( correctedWidth <= maxWidth ); xOffset = ( maxWidth - correctedWidth ) / 2; } for ( Point & point : offsets ) { point.x = ( correctedWidth - point.x ) / 2; } for ( size_t i = 0; i < _texts.size(); ++i ) { render( reinterpret_cast( _texts[i]._text.data() ), static_cast( _texts[i]._text.size() ), x + xOffset, y, correctedWidth, output, imageRoi, _texts[i]._fontType, maxFontHeight, false, offsets ); } } bool MultiFontText::empty() const { return _texts.empty(); } std::string MultiFontText::text() const { std::string output; for ( const Text & singleText : _texts ) { output += singleText.text(); } return output; } bool isFontAvailable( const std::string & text, const FontType fontType ) { if ( text.empty() ) { return true; } const CharValidator validator( fontType.size ); for ( const char letter : text ) { const uint8_t character = static_cast( letter ); if ( character == lineSeparator ) { continue; } if ( isSpaceChar( character ) ) { continue; } if ( !validator.isValid( character ) ) { return false; } } return true; } } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_text.h000066400000000000000000000162541456075706000211730ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include #include "image.h" #include "math_base.h" namespace fheroes2 { enum class FontSize : uint8_t { SMALL, NORMAL, LARGE, // These are special fonts designed only for buttons. Use WHITE font color for Good Interface and GRAY for Evil Interface. BUTTON_RELEASED, BUTTON_PRESSED, }; enum class FontColor : uint8_t { WHITE, GRAY, YELLOW }; struct FontType { FontType() = default; FontType( const FontSize size_, const FontColor color_ ) : size( size_ ) , color( color_ ) {} FontSize size = FontSize::NORMAL; FontColor color = FontColor::WHITE; static FontType normalWhite() { return { FontSize::NORMAL, FontColor::WHITE }; } static FontType normalYellow() { return { FontSize::NORMAL, FontColor::YELLOW }; } static FontType smallWhite() { return { FontSize::SMALL, FontColor::WHITE }; } static FontType smallYellow() { return { FontSize::SMALL, FontColor::YELLOW }; } static FontType largeWhite() { return { FontSize::LARGE, FontColor::WHITE }; } }; int32_t getFontHeight( const FontSize fontSize ); class TextBase { public: TextBase() = default; virtual ~TextBase(); // Returns width of a text as a single-line text only. virtual int32_t width() const = 0; // Returns height of a text as a single-line text only. virtual int32_t height() const = 0; // Returns width of a text as a multi-line text limited by maximum width of a line. virtual int32_t width( const int32_t maxWidth ) const = 0; // Returns height of a text as a multi-line text limited by width of a line. virtual int32_t height( const int32_t maxWidth ) const = 0; // Returns number of multi-line text rows limited by width of a line. It can be 0 if the text is empty. virtual int32_t rows( const int32_t maxWidth ) const = 0; // Draw text as a single line text. void draw( const int32_t x, const int32_t y, Image & output ) const { drawInRoi( x, y, output, { 0, 0, output.width(), output.height() } ); } // Draw text as a multi-line limited by width of a line. The text will be centered according to the provided maximum width. void draw( const int32_t x, const int32_t y, const int32_t maxWidth, Image & output ) const { drawInRoi( x, y, maxWidth, output, { 0, 0, output.width(), output.height() } ); } // Draw text as a single line text within a given image ROI. virtual void drawInRoi( const int32_t x, const int32_t y, Image & output, const Rect & imageRoi ) const = 0; // Draw text as a multi-line within a given image ROI and limited by width of a line. The text will be centered according to the provided maximum width. virtual void drawInRoi( const int32_t x, const int32_t y, const int32_t maxWidth, Image & output, const Rect & imageRoi ) const = 0; // Returns true if nothing to draw. virtual bool empty() const = 0; // Returns full text. Multi-text class cannot return by reference hence returning by value . virtual std::string text() const = 0; }; class Text : public TextBase { public: friend class MultiFontText; Text() = default; Text( std::string text, const FontType fontType ); Text( const Text & text ) = default; Text( Text && text ) = default; Text & operator=( const Text & text ) = default; Text & operator=( Text && text ) = default; ~Text() override; int32_t width() const override; int32_t height() const override; int32_t width( const int32_t maxWidth ) const override; int32_t height( const int32_t maxWidth ) const override; int32_t rows( const int32_t maxWidth ) const override; void drawInRoi( const int32_t x, const int32_t y, Image & output, const Rect & imageRoi ) const override; void drawInRoi( const int32_t x, const int32_t y, const int32_t maxWidth, Image & output, const Rect & imageRoi ) const override; bool empty() const override; void set( std::string text, const FontType fontType ); // This method modifies the underlying text and ends it with '...' if it is longer than the provided width. void fitToOneRow( const int32_t maxWidth ); std::string text() const override; private: std::string _text; FontType _fontType; }; class MultiFontText : public TextBase { public: MultiFontText() = default; ~MultiFontText() override; void add( Text text ); int32_t width() const override; int32_t height() const override; int32_t width( const int32_t maxWidth ) const override; int32_t height( const int32_t maxWidth ) const override; int32_t rows( const int32_t maxWidth ) const override; void drawInRoi( const int32_t x, const int32_t y, Image & output, const Rect & imageRoi ) const override; void drawInRoi( const int32_t x, const int32_t y, const int32_t maxWidth, Image & output, const Rect & imageRoi ) const override; bool empty() const override; std::string text() const override; private: std::vector _texts; }; // This function is usually useful for text generation on buttons as button font is a separate set of sprites. bool isFontAvailable( const std::string & text, const FontType fontType ); } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_tool.cpp000066400000000000000000000701621456075706000215150ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "ui_tool.h" #include #include #include #include #include #include #include #include #include #include "agg_image.h" #include "cursor.h" #include "game_delays.h" #include "image_palette.h" #include "localevent.h" #include "render_processor.h" #include "screen.h" #include "settings.h" #include "system.h" #include "translations.h" namespace { // The parameters of display fade effect. Full dark and full bright alpha values. const uint8_t fullDarkAlpha = 0; const uint8_t fullBrightAlpha = 255; // Fade-in and fade-out effects are made in separate functions. Here we set the duration of a single function. const uint32_t screenFadeTimeMs = 100; // For 100 ms duration 6 frames will result in 60 FPS. const uint32_t screenFadeFrameCount = 6; const uint8_t screenFadeStep = ( fullBrightAlpha - fullDarkAlpha ) / screenFadeFrameCount; void fadeDisplay( const uint8_t startAlpha, const uint8_t endAlpha, const fheroes2::Rect & roi, const uint32_t fadeTimeMs, const uint32_t frameCount ) { if ( frameCount < 2 || roi.height <= 0 || roi.width <= 0 ) { return; } Cursor::Get().SetThemes( Cursor::POINTER ); fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::Rect fadeRoi( roi ^ fheroes2::Rect( 0, 0, display.width(), display.height() ) ); fheroes2::Image temp{ fadeRoi.width, fadeRoi.height }; Copy( display, fadeRoi.x, fadeRoi.y, temp, 0, 0, fadeRoi.width, fadeRoi.height ); double alpha = startAlpha; const uint32_t delay = fadeTimeMs / frameCount; const double alphaStep = ( alpha - endAlpha ) / static_cast( frameCount - 1 ); uint32_t frameNumber = 0; LocalEvent & le = LocalEvent::Get(); Game::passCustomAnimationDelay( delay ); while ( le.HandleEvents( Game::isCustomDelayNeeded( delay ) ) ) { if ( Game::validateCustomAnimationDelay( delay ) ) { if ( frameNumber == frameCount ) { break; } assert( alpha >= 0 && alpha <= 255 ); const uint8_t fadeAlpha = static_cast( std::round( alpha ) ); if ( fadeAlpha == 255 ) { // This alpha is for fully bright image so there is no need to apply alpha. Copy( temp, 0, 0, display, fadeRoi.x, fadeRoi.y, fadeRoi.width, fadeRoi.height ); } else if ( fadeAlpha == 0 ) { // This alpha is for fully dark image so fill it with the black color. // Color index '0' in all game palettes (including videos) corresponds to the black color. Fill( display, fadeRoi.x, fadeRoi.y, fadeRoi.width, fadeRoi.height, 0 ); } else { ApplyAlpha( temp, 0, 0, display, fadeRoi.x, fadeRoi.y, fadeRoi.width, fadeRoi.height, fadeAlpha ); } display.render( fadeRoi ); alpha -= alphaStep; ++frameNumber; } } } } namespace fheroes2 { MovableSprite::MovableSprite() : _restorer( Display::instance(), 0, 0, 0, 0 ) , _isHidden( true ) {} MovableSprite::MovableSprite( int32_t width_, int32_t height_, int32_t x_, int32_t y_ ) : Sprite( width_, height_, x_, y_ ) , _restorer( Display::instance(), x_, y_, width_, height_ ) , _isHidden( height_ == 0 && width_ == 0 ) {} MovableSprite::MovableSprite( const Sprite & sprite ) : Sprite( sprite ) , _restorer( Display::instance(), 0, 0, 0, 0 ) , _isHidden( false ) {} MovableSprite::~MovableSprite() { if ( _isHidden ) { _restorer.reset(); } } MovableSprite & MovableSprite::operator=( const Sprite & sprite ) { if ( this == &sprite ) { return *this; } Sprite::operator=( sprite ); _restorer.update( x(), y(), width(), height() ); return *this; } void MovableSprite::setPosition( int32_t x_, int32_t y_ ) { if ( _isHidden ) { Sprite::setPosition( x_, y_ ); return; } hide(); Sprite::setPosition( x_, y_ ); show(); } void MovableSprite::show() { if ( _isHidden ) { _restorer.update( x(), y(), width(), height() ); fheroes2::Blit( *this, Display::instance(), x(), y() ); _isHidden = false; } } void MovableSprite::hide() { if ( !_isHidden ) { _restorer.restore(); _isHidden = true; } } MovableText::MovableText( Image & output ) : _output( output ) , _restorer( output, 0, 0, 0, 0 ) , _isHidden( false ) { // Do nothing. } void MovableText::update( std::unique_ptr text ) { _text = std::move( text ); } void MovableText::draw( const int32_t x, const int32_t y ) { hide(); _restorer.update( x, y, _text->width(), _text->height() ); _text->draw( x, y, _output ); _isHidden = false; } void MovableText::hide() { if ( !_isHidden ) { _restorer.restore(); _isHidden = true; } } SystemInfoRenderer::SystemInfoRenderer() : _startTime( std::chrono::steady_clock::now() ) , _text( fheroes2::Display::instance() ) {} void SystemInfoRenderer::preRender() { const int32_t offsetX = 26; const int32_t offsetY = fheroes2::Display::instance().height() - 30; const tm tmi = System::GetTM( std::time( nullptr ) ); std::array mbstr{ 0 }; std::strftime( mbstr.data(), mbstr.size(), "%H:%M:%S", &tmi ); std::string info( mbstr.data() ); const std::chrono::time_point endTime = std::chrono::steady_clock::now(); const std::chrono::duration time = endTime - _startTime; _startTime = endTime; const double totalTime = time.count() * 1000.0; const double fps = totalTime < 1 ? 0 : 1000 / totalTime; _fps.push_front( fps ); while ( _fps.size() > 10 ) { _fps.pop_back(); } double averageFps = 0; for ( const double value : _fps ) { averageFps += value; } averageFps /= static_cast( _fps.size() ); const int32_t currentFps = static_cast( averageFps ); info += _( ", FPS: " ); info += std::to_string( currentFps ); if ( averageFps < 10 ) { info += '.'; info += std::to_string( static_cast( ( averageFps - currentFps ) * 10 ) ); } _text.update( std::make_unique( std::move( info ), fheroes2::FontType::normalWhite() ) ); _text.draw( offsetX, offsetY ); } TimedEventValidator::TimedEventValidator( std::function verification, const uint64_t delayBeforeFirstUpdateMs, const uint64_t delayBetweenUpdateMs ) : _verification( std::move( verification ) ) , _delayBetweenUpdateMs( delayBetweenUpdateMs ) , _delayBeforeFirstUpdateMs( delayBeforeFirstUpdateMs ) {} bool TimedEventValidator::isDelayPassed() { if ( _delayBeforeFirstUpdateMs.isPassed() && _delayBetweenUpdateMs.isPassed() && _verification() ) { _delayBetweenUpdateMs.reset(); return true; } return false; } void TimedEventValidator::senderUpdate( const ActionObject * sender ) { if ( sender == nullptr ) return; _delayBeforeFirstUpdateMs.reset(); _delayBetweenUpdateMs.reset(); } ScreenPaletteRestorer::ScreenPaletteRestorer() { RenderProcessor::instance().stopColorCycling(); } ScreenPaletteRestorer::~ScreenPaletteRestorer() { Display::instance().changePalette( nullptr ); RenderProcessor::instance().startColorCycling(); } void ScreenPaletteRestorer::changePalette( const uint8_t * palette ) const { Display::instance().changePalette( palette ); } GameInterfaceTypeRestorer::GameInterfaceTypeRestorer( const bool isEvilInterface_ ) : isEvilInterface( isEvilInterface_ ) , isOriginalEvilInterface( Settings::Get().isEvilInterfaceEnabled() ) { if ( isEvilInterface != isOriginalEvilInterface ) { Settings::Get().setEvilInterface( isEvilInterface ); } } GameInterfaceTypeRestorer::~GameInterfaceTypeRestorer() { if ( isEvilInterface != isOriginalEvilInterface ) { Settings::Get().setEvilInterface( isOriginalEvilInterface ); } } void colorFade( const std::vector & palette, const fheroes2::Rect & frameRoi, const uint32_t durationMs, const double fps ) { assert( fps > 0 ); // Game palette has 256 values for red, green and blue, so its size is: 256 * 3 = 768. const int32_t paletteSize = 768; // Do a color fade only for valid palette. if ( palette.size() != paletteSize ) { return; } // The biggest problem here is that the palette could be different from the one in the game. // Since we want to do color fading to grayscale colors we take the original palette and // find the nearest colors in video's palette to grayscale colors of the original palette. // Then we gradually change the current palette to be only grayscale and after reaching the // last frame change all colors of the frame to be only grayscale colors of the original palette. const uint8_t * originalPalette = fheroes2::getGamePalette(); // Yes, these values are hardcoded. There are ways to do it programmatically. const int32_t startGrayScaleColorId = 10; const int32_t endGrayScaleColorId = 36; // The game's palette has 256 color indexes. const int32_t paletteIndexes = 256; std::vector assignedValue( paletteIndexes ); for ( size_t id = 0; id < paletteIndexes; ++id ) { int32_t nearestDistance = INT32_MAX; for ( uint8_t colorId = startGrayScaleColorId; colorId <= endGrayScaleColorId; ++colorId ) { const int32_t redDiff = static_cast( palette[id * 3] ) - static_cast( originalPalette[static_cast( colorId ) * 3] ) * 4; const int32_t greenDiff = static_cast( palette[id * 3 + 1] ) - static_cast( originalPalette[static_cast( colorId ) * 3 + 1] ) * 4; const int32_t blueDiff = static_cast( palette[id * 3 + 2] ) - static_cast( originalPalette[static_cast( colorId ) * 3 + 2] ) * 4; const int32_t distance = redDiff * redDiff + greenDiff * greenDiff + blueDiff * blueDiff; if ( nearestDistance > distance ) { nearestDistance = distance; assignedValue[id] = colorId; } } } std::array endPalette{ 0 }; for ( size_t i = 0; i < paletteIndexes; ++i ) { const uint8_t valuePosition = assignedValue[i] * 3; // Red color. endPalette[i * 3] = originalPalette[valuePosition] * 4; // Green color. endPalette[i * 3 + 1] = originalPalette[valuePosition + 1] * 4; // Blue color. endPalette[i * 3 + 2] = originalPalette[valuePosition + 2] * 4; } const uint32_t delay = static_cast( std::round( 1000.0 / fps ) ); // Gradually fade the palette. const uint32_t gradingSteps = durationMs / delay; uint32_t gradingId = 1; std::vector gradingPalette( paletteSize ); std::vector prevPalette( paletteSize ); const fheroes2::ScreenPaletteRestorer screenRestorer; fheroes2::Display & display = fheroes2::Display::instance(); LocalEvent & le = LocalEvent::Get(); Game::passCustomAnimationDelay( delay ); while ( le.HandleEvents( Game::isCustomDelayNeeded( delay ) ) ) { if ( Game::validateCustomAnimationDelay( delay ) ) { if ( gradingId == gradingSteps ) { break; } for ( int32_t i = 0; i < paletteSize; ++i ) { gradingPalette[i] = static_cast( ( palette[i] * ( gradingSteps - gradingId ) + endPalette[i] * gradingId ) / gradingSteps ); } screenRestorer.changePalette( gradingPalette.data() ); // We need to swap the palettes so the next call of 'changePalette()' will think that the palette is new. std::swap( prevPalette, gradingPalette ); display.render( frameRoi ); ++gradingId; } } // Convert all video frame colors to original game palette colors. fheroes2::ApplyPalette( display, frameRoi.x, frameRoi.y, display, frameRoi.x, frameRoi.y, frameRoi.width, frameRoi.height, assignedValue ); screenRestorer.changePalette( originalPalette ); } void CreateDeathWaveEffect( Image & out, const Image & in, const int32_t x, const std::vector & deathWaveCurve ) { if ( in.empty() ) { return; } const int32_t inWidth = in.width(); const int32_t waveLength = static_cast( deathWaveCurve.size() ); // If the death wave curve is outside of the battlefield - return. if ( x < 0 || ( x - waveLength ) >= inWidth || deathWaveCurve.empty() ) { return; } const int32_t inHeight = in.height(); const int32_t outWaveWidth = x > waveLength ? ( x > inWidth ? ( waveLength - x + inWidth ) : waveLength ) : x; // If the out image is small for the Death Wave spell effect, resize it. if ( out.width() < outWaveWidth || out.height() < inHeight ) { out.resize( outWaveWidth, inHeight ); } const int32_t outWidth = out.width(); // Set the input image horizontal offset from where to draw the wave. const int32_t offsetX = x < waveLength ? 0 : x - waveLength; const uint8_t * inImageX = in.image() + offsetX; uint8_t * outImageX = out.image(); // Set pointers to the start and the end of the death wave curve. std::vector::const_iterator pntX = deathWaveCurve.begin() + ( x < waveLength ? waveLength - x : 0 ); const std::vector::const_iterator endX = deathWaveCurve.end() - ( x > inWidth ? x - inWidth : 0 ); for ( ; pntX != endX; ++pntX, ++outImageX, ++inImageX ) { // The death curve should have only negative values and should not be higher, than the height of 'in' image. if ( ( *pntX >= 0 ) || ( *pntX <= -inHeight ) ) { assert( 0 ); continue; } const uint8_t * outImageYEnd = outImageX + static_cast( inHeight + *pntX ) * outWidth; const uint8_t * inImageY = inImageX - static_cast( *pntX + 1 ) * inWidth; // A loop to shift all horizontal pixels vertically. uint8_t * outImageY = outImageX; for ( ; outImageY != outImageYEnd; outImageY += outWidth ) { inImageY += inWidth; *outImageY = *inImageY; } // Flip the image under the death wave to create a distortion effect. for ( int32_t i = 0; i > *pntX; --i ) { *outImageY = *inImageY; outImageY += outWidth; inImageY -= inWidth; } } } Image CreateHolyShoutEffect( const Image & in, const int32_t blurRadius, const uint8_t darkredStrength ) { if ( in.empty() ) { return {}; } if ( blurRadius < 1 ) { return in; } const int32_t width = in.width(); const int32_t height = in.height(); // To properly call 'GetColorId' we need to multiply the RGB color values by a coefficient equal to 4. Also the Holy Word/Shout spell effect // should have some dark + red effect. So we reduce all these coefficients for all colors, for green and blue we make a stronger reduction. const double redCoeff = 4.0 - darkredStrength / 280.0; const double greenBlueCoeff = 4.0 - darkredStrength / 80.0; Image out; out._disableTransformLayer(); out.resize( width, height ); uint8_t * imageOutX = out.image(); const uint8_t * imageIn = in.image(); const uint8_t * gamePalette = getGamePalette(); // The spell effect represents as a blurry image. The blur algorithm should blur only horizontally and vertically from current pixel. // So the color data is averaged in a "cross" around the current pixel (not a square or circle like other blur algorithms). for ( int32_t y = 0; y < height; ++y ) { const int32_t startY = std::max( y - blurRadius, 0 ); const int32_t rangeY = std::min( y + blurRadius + 1, height ) - startY; const uint8_t * imageInXStart = imageIn + static_cast( y ) * width; const uint8_t * imageInYStart = imageIn + static_cast( startY ) * width; for ( int32_t x = 0; x < width; ++x, ++imageOutX ) { const int32_t startX = std::max( x - blurRadius, 0 ); const int32_t rangeX = std::min( x + blurRadius + 1, width ) - startX; uint32_t sumRed = 0; uint32_t sumGreen = 0; uint32_t sumBlue = 0; const uint8_t * imageInX = imageInXStart + startX; const uint8_t * imageInXEnd = imageInX + rangeX; for ( ; imageInX != imageInXEnd; ++imageInX ) { const uint8_t * palette = gamePalette + static_cast( *imageInX ) * 3; sumRed += *palette; sumGreen += *( palette + 1 ); sumBlue += *( palette + 2 ); } const uint8_t * imageInY = imageInYStart + x; const uint8_t * imageInYEnd = imageInY + static_cast( rangeY ) * width; const uint8_t * currentPixel = imageInXStart + x; for ( ; imageInY != imageInYEnd; imageInY += width ) { if ( imageInY != currentPixel ) { const uint8_t * palette = gamePalette + static_cast( *imageInY ) * 3; sumRed += *palette; sumGreen += *( palette + 1 ); sumBlue += *( palette + 2 ); } } const uint32_t roiSize = static_cast( rangeX + rangeY - 1 ); *imageOutX = GetColorId( static_cast( redCoeff * sumRed / roiSize ), static_cast( greenBlueCoeff * sumGreen / roiSize ), static_cast( greenBlueCoeff * sumBlue / roiSize ) ); } } return out; } Image CreateRippleEffect( const Image & in, const int32_t frameId, const double scaleX /* = 0.05 */, const double waveFrequency /* = 20.0 */ ) { if ( in.empty() || in.singleLayer() ) { return {}; } const int32_t widthIn = in.width(); const int32_t height = in.height(); // convert frames to -10...10 range with a period of 40 const int32_t linearWave = std::abs( 20 - ( frameId + 10 ) % 40 ) - 10; const int32_t progress = 7 - frameId / 10; const double rippleXModifier = ( progress * scaleX + 0.3 ) * linearWave; const int32_t offsetX = static_cast( std::abs( rippleXModifier ) ); const int32_t limitY = static_cast( waveFrequency * M_PI ); Image out( widthIn + offsetX * 2, height ); out.reset(); const int32_t widthOut = out.width(); uint8_t * outImageY = out.image(); uint8_t * outTransformY = out.transform(); const uint8_t * inImageY = in.image(); const uint8_t * inTransformY = in.transform(); for ( int32_t y = 0; y < height; ++y, inImageY += widthIn, inTransformY += widthIn, outImageY += widthOut, outTransformY += widthOut ) { // Take top half the sin wave starting at 0 with period set by waveFrequency, result is -1...1 const double sinYEffect = sin( ( y % limitY ) / waveFrequency ) * 2.0 - 1; const int32_t offset = static_cast( rippleXModifier * sinYEffect ) + offsetX; memcpy( outImageY + offset, inImageY, widthIn ); memcpy( outTransformY + offset, inTransformY, widthIn ); } return out; } void fadeOutDisplay() { const Display & display = Display::instance(); fadeOutDisplay( { 0, 0, display.width(), display.height() }, false ); } void fadeOutDisplay( const Rect & roi, const bool halfFade ) { static_assert( screenFadeFrameCount != 0 ); // As we are doing fade-out we already have the full bright picture so we skip it and calculate the startAlpha. const uint8_t startAlpha = fullBrightAlpha - screenFadeStep; if ( halfFade ) { fadeDisplay( startAlpha, ( fullBrightAlpha - fullDarkAlpha ) / 2, roi, screenFadeTimeMs / 2, screenFadeFrameCount / 2 ); } else { fadeDisplay( startAlpha, fullDarkAlpha, roi, screenFadeTimeMs, screenFadeFrameCount ); } } void fadeInDisplay() { const Display & display = Display::instance(); fadeInDisplay( { 0, 0, display.width(), display.height() }, false ); } void fadeInDisplay( const Rect & roi, const bool halfFade ) { static_assert( screenFadeFrameCount != 0 ); // As we are doing fade-in we already have the full dark picture from the previous fade-out so we skip it and calculate the startAlpha. const uint8_t startAlpha = fullDarkAlpha + screenFadeStep; if ( halfFade ) { // We add an extra frame to have a smoother fade effect taking into account that the last frame is fully bright to render the image. fadeDisplay( ( fullBrightAlpha - startAlpha ) / 2, fullBrightAlpha, roi, screenFadeTimeMs * 3 / 4, screenFadeFrameCount / 2 + 1 ); } else { fadeDisplay( startAlpha, fullBrightAlpha, roi, screenFadeTimeMs, screenFadeFrameCount ); } } void FadeDisplayWithPalette( const Image & top, const Point & pos, const uint8_t paletteId, const int32_t fadeTimeMs, const int32_t frameCount ) { Display & display = Display::instance(); const int32_t stepDelay = fadeTimeMs / frameCount; Image shadow = top; const fheroes2::Rect roi{ pos.x, pos.y, shadow.width(), shadow.height() }; LocalEvent & le = LocalEvent::Get(); int32_t frameNumber = 0; Game::passCustomAnimationDelay( stepDelay ); while ( le.HandleEvents( Game::isCustomDelayNeeded( stepDelay ) ) ) { if ( Game::validateCustomAnimationDelay( stepDelay ) ) { if ( frameNumber == frameCount ) { break; } ApplyPalette( shadow, paletteId ); Copy( shadow, 0, 0, display, roi.x, roi.y, roi.width, roi.height ); display.render( roi ); ++frameNumber; } } } size_t getTextInputCursorPosition( const std::string & text, const FontType & fontType, const size_t currentTextCursorPosition, const int32_t pointerCursorXOffset, const int32_t textStartXOffset ) { if ( text.empty() || pointerCursorXOffset <= textStartXOffset ) { // The text is empty or mouse cursor position is to the left of input field. return 0; } const int32_t maxOffset = pointerCursorXOffset - textStartXOffset; const size_t textSize = text.size(); int32_t positionOffset = 0; for ( size_t i = 0; i < textSize; ++i ) { positionOffset += AGG::getChar( static_cast( text[i] ), fontType ).width(); if ( positionOffset > maxOffset ) { return i; } // If the mouse cursor is to the right of the current text cursor position we take its width into account if ( i == currentTextCursorPosition ) { positionOffset += AGG::getChar( '_', fontType ).width(); } } return textSize; } void InvertedFadeWithPalette( Image & image, const Rect & roi, const Rect & excludedRoi, const uint8_t paletteId, const int32_t fadeTimeMs, const int32_t frameCount ) { Display & display = Display::instance(); const int32_t stepDelay = fadeTimeMs / frameCount; LocalEvent & le = LocalEvent::Get(); int32_t frameNumber = 0; Game::passCustomAnimationDelay( stepDelay ); while ( le.HandleEvents( Game::isCustomDelayNeeded( stepDelay ) ) ) { if ( Game::validateCustomAnimationDelay( stepDelay ) ) { if ( frameNumber == frameCount ) { break; } InvertedShadow( image, roi, excludedRoi, paletteId, 1 ); display.render( roi ); ++frameNumber; } } } void InvertedShadow( Image & image, const Rect & roi, const Rect & excludedRoi, const uint8_t paletteId, const int32_t paletteCount ) { // Identify 4 areas around excluded ROI to be used for palette application. const Rect topRoi( roi.x, roi.y, roi.width, excludedRoi.y - roi.y ); const Rect bottomRoi( roi.x, excludedRoi.y + excludedRoi.height, roi.width, roi.y + roi.height - excludedRoi.y - excludedRoi.height ); const Rect leftRoi( roi.x, excludedRoi.y, excludedRoi.x - roi.x, excludedRoi.height ); const Rect rightRoi( excludedRoi.x + excludedRoi.width, excludedRoi.y, roi.x + roi.width - excludedRoi.x - excludedRoi.width, excludedRoi.height ); for ( int i = 0; i < paletteCount; ++i ) { ApplyPalette( image, topRoi.x, topRoi.y, image, topRoi.x, topRoi.y, topRoi.width, topRoi.height, paletteId ); ApplyPalette( image, bottomRoi.x, bottomRoi.y, image, bottomRoi.x, bottomRoi.y, bottomRoi.width, bottomRoi.height, paletteId ); ApplyPalette( image, leftRoi.x, leftRoi.y, image, leftRoi.x, leftRoi.y, leftRoi.width, leftRoi.height, paletteId ); ApplyPalette( image, rightRoi.x, rightRoi.y, image, rightRoi.x, rightRoi.y, rightRoi.width, rightRoi.height, paletteId ); } } } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_tool.h000066400000000000000000000165101456075706000211570ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include #include #include #include #include #include #include "image.h" #include "math_base.h" #include "timing.h" #include "ui_base.h" #include "ui_text.h" namespace fheroes2 { class MovableSprite : public Sprite { public: MovableSprite(); MovableSprite( int32_t width_, int32_t height_, int32_t x_, int32_t y_ ); explicit MovableSprite( const Sprite & sprite ); MovableSprite( const MovableSprite & ) = delete; ~MovableSprite() override; MovableSprite & operator=( const MovableSprite & ) = delete; MovableSprite & operator=( const Sprite & sprite ); void show(); void hide(); // In case if Display has changed. void redraw() { hide(); show(); } bool isHidden() const { return _isHidden; } void setPosition( int32_t x_, int32_t y_ ) override; protected: void _resetRestorer() { _restorer.reset(); } private: ImageRestorer _restorer; bool _isHidden; }; class MovableText { public: explicit MovableText( Image & output ); MovableText( const MovableText & ) = delete; ~MovableText() = default; MovableText & operator=( const MovableText & ) = delete; void update( std::unique_ptr text ); void draw( const int32_t x, const int32_t y ); void hide(); private: Image & _output; ImageRestorer _restorer; std::unique_ptr _text; bool _isHidden; }; // Renderer of current time and FPS on screen class SystemInfoRenderer { public: SystemInfoRenderer(); SystemInfoRenderer( const SystemInfoRenderer & ) = delete; ~SystemInfoRenderer() = default; SystemInfoRenderer & operator=( const SystemInfoRenderer & ) = delete; void preRender(); void postRender() { _text.hide(); } private: std::chrono::time_point _startTime; fheroes2::MovableText _text; std::deque _fps; }; class TimedEventValidator : public ActionObject { public: explicit TimedEventValidator( std::function verification, const uint64_t delayBeforeFirstUpdateMs = 500, const uint64_t delayBetweenUpdateMs = 100 ); TimedEventValidator( const TimedEventValidator & ) = delete; ~TimedEventValidator() override = default; TimedEventValidator & operator=( const TimedEventValidator & ) = delete; bool isDelayPassed(); protected: void senderUpdate( const ActionObject * sender ) override; private: std::function _verification; fheroes2::TimeDelay _delayBetweenUpdateMs; fheroes2::TimeDelay _delayBeforeFirstUpdateMs; }; // This class is useful for cases of playing videos only class ScreenPaletteRestorer { public: ScreenPaletteRestorer(); ScreenPaletteRestorer( const ScreenPaletteRestorer & ) = delete; ~ScreenPaletteRestorer(); ScreenPaletteRestorer & operator=( const ScreenPaletteRestorer & ) = delete; void changePalette( const uint8_t * palette ) const; }; struct GameInterfaceTypeRestorer { GameInterfaceTypeRestorer() = delete; explicit GameInterfaceTypeRestorer( const bool isEvilInterface_ ); GameInterfaceTypeRestorer( const GameInterfaceTypeRestorer & ) = delete; ~GameInterfaceTypeRestorer(); GameInterfaceTypeRestorer & operator=( const GameInterfaceTypeRestorer & ) = delete; const bool isEvilInterface; const bool isOriginalEvilInterface; }; // Fade display image colors to grayscale part of default game palette. void colorFade( const std::vector & palette, const fheroes2::Rect & frameRoi, const uint32_t durationMs, const double fps ); void CreateDeathWaveEffect( Image & out, const Image & in, const int32_t x, const std::vector & deathWaveCurve ); Image CreateHolyShoutEffect( const Image & in, const int32_t blurRadius, const uint8_t darkredStrength ); Image CreateRippleEffect( const Image & in, const int32_t frameId, const double scaleX = 0.05, const double waveFrequency = 20.0 ); // Fade-out the whole screen. void fadeOutDisplay(); // Fade-out the display image in ROI. The 'halfFade' parameter sets to do only half of fade-out: till half-darkened image. void fadeOutDisplay( const Rect & roi, const bool halfFade ); // Fade-in the prepared image in display instance on the whole screen. The last frame is fully bright so it is a copy of original image. void fadeInDisplay(); // Fade-in the prepared display image in ROI. The 'halfFade' parameter sets to do only half of fade-in: from the half-darkened image. // The last frame is fully bright so it is a copy of original image. void fadeInDisplay( const Rect & roi, const bool halfFade ); void FadeDisplayWithPalette( const Image & top, const Point & pos, const uint8_t paletteId, const int32_t fadeTimeMs, const int32_t frameCount ); // Returns the character position number in the 'text' string. size_t getTextInputCursorPosition( const std::string & text, const FontType & fontType, const size_t currentTextCursorPosition, const int32_t pointerCursorXOffset, const int32_t textStartXOffset ); void InvertedFadeWithPalette( Image & image, const Rect & roi, const Rect & excludedRoi, const uint8_t paletteId, const int32_t fadeTimeMs, const int32_t frameCount ); void InvertedShadow( Image & image, const Rect & roi, const Rect & excludedRoi, const uint8_t paletteId, const int32_t paletteCount ); } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_window.cpp000066400000000000000000000733671456075706000220610ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "ui_window.h" #include #include #include "agg_image.h" #include "gamedefs.h" #include "icn.h" #include "settings.h" #include "ui_button.h" namespace { const int32_t borderSize{ BORDERWIDTH }; // Offset from border edges (size of evil interface corners is 43 pixels) - these edges (corners) will not be copied to fill the border. const int32_t borderEdgeOffset{ 43 }; // Size in pixels of dithered transition from one image to another. const int32_t transitionSize{ 10 }; // Offset from window edges to background copy area. const int32_t backgroundOffset{ 22 }; } namespace fheroes2 { StandardWindow::StandardWindow( const int32_t width, const int32_t height, const bool renderBackground, Image & output ) : _output( output ) , _activeArea( ( output.width() - width ) / 2, ( output.height() - height ) / 2, width, height ) , _windowArea( _activeArea.x - borderSize, _activeArea.y - borderSize, _activeArea.width + 2 * borderSize, _activeArea.height + 2 * borderSize ) , _totalArea( _windowArea.x - borderSize, _windowArea.y, _windowArea.width + borderSize, _windowArea.height + borderSize ) , _restorer( output, _windowArea.x - borderSize, _windowArea.y, _windowArea.width + borderSize, _windowArea.height + borderSize ) , _hasBackground{ renderBackground } { render(); } StandardWindow::StandardWindow( const int32_t x, const int32_t y, const int32_t width, const int32_t height, const bool renderBackground, Image & output ) : _output( output ) , _activeArea( x, y, width, height ) , _windowArea( _activeArea.x - borderSize, _activeArea.y - borderSize, _activeArea.width + 2 * borderSize, _activeArea.height + 2 * borderSize ) , _totalArea( _windowArea.x - borderSize, _windowArea.y, _windowArea.width + borderSize, _windowArea.height + borderSize ) , _restorer( output, _windowArea.x - borderSize, _windowArea.y, _windowArea.width + borderSize, _windowArea.height + borderSize ) , _hasBackground{ renderBackground } { render(); } void StandardWindow::render() { const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); // Notice: ICN::SURDRBKE and ICN::SURDRBKG has 16 (equals to BORDERWIDTH) pixels shadow from the left and the bottom sides. const Sprite & horizontalSprite = AGG::GetICN( ( isEvilInterface ? ICN::SURDRBKE : ICN::SURDRBKG ), 0 ); const Sprite & verticalSprite = AGG::GetICN( ( isEvilInterface ? ICN::WINLOSEE : ICN::WINLOSE ), 0 ); // Offset from window edges to background copy area and also the size of corners to render. const int32_t cornerSize = _hasBackground ? backgroundOffset : borderSize; const int32_t horizontalSpriteWidth = horizontalSprite.width() - borderSize; const int32_t horizontalSpriteHeight = horizontalSprite.height() - borderSize; const int32_t verticalSpriteHeight = verticalSprite.height(); const int32_t verticalSpriteWidth = verticalSprite.width(); // Render window corners. The corners are the same in used original images, so we use only 'verticalSprite'. const int32_t rightCornerOffsetX = _windowArea.x + _windowArea.width - cornerSize; const int32_t bottomCornerOffsetY = _windowArea.y + _windowArea.height - cornerSize; const int32_t rightCornerSpriteOffsetX = verticalSpriteWidth - cornerSize; const int32_t bottomCornerSpriteOffsetY = verticalSpriteHeight - cornerSize; Blit( verticalSprite, 0, 0, _output, _windowArea.x, _windowArea.y, cornerSize, cornerSize ); Blit( verticalSprite, rightCornerSpriteOffsetX, 0, _output, rightCornerOffsetX, _windowArea.y, cornerSize, cornerSize ); Blit( verticalSprite, 0, bottomCornerSpriteOffsetY, _output, _windowArea.x, bottomCornerOffsetY, cornerSize, cornerSize ); Blit( verticalSprite, rightCornerSpriteOffsetX, bottomCornerSpriteOffsetY, _output, rightCornerOffsetX, bottomCornerOffsetY, cornerSize, cornerSize ); // Render additional part of border corners. This part will not be repeated to fill the border length. const int32_t extraCornerSize = borderEdgeOffset - cornerSize; const Point cornerOffset( _windowArea.x + cornerSize, _windowArea.y + cornerSize ); const int32_t rightBorderEdgeOffset = verticalSpriteWidth - borderEdgeOffset; const int32_t bottomBorderEdgeOffset = verticalSpriteHeight - borderEdgeOffset; Blit( verticalSprite, cornerSize, 0, _output, cornerOffset.x, _windowArea.y, extraCornerSize, cornerSize ); Blit( verticalSprite, 0, cornerSize, _output, _windowArea.x, cornerOffset.y, cornerSize, extraCornerSize ); Blit( verticalSprite, rightBorderEdgeOffset, 0, _output, rightCornerOffsetX - extraCornerSize, _windowArea.y, extraCornerSize, cornerSize ); Blit( verticalSprite, rightCornerSpriteOffsetX, cornerSize, _output, rightCornerOffsetX, cornerOffset.y, cornerSize, extraCornerSize ); Blit( verticalSprite, cornerSize, bottomCornerSpriteOffsetY, _output, cornerOffset.x, bottomCornerOffsetY, extraCornerSize, cornerSize ); Blit( verticalSprite, 0, bottomBorderEdgeOffset, _output, _windowArea.x, bottomCornerOffsetY - extraCornerSize, cornerSize, extraCornerSize ); Blit( verticalSprite, rightBorderEdgeOffset, bottomCornerSpriteOffsetY, _output, rightCornerOffsetX - extraCornerSize, bottomCornerOffsetY, extraCornerSize, cornerSize ); Blit( verticalSprite, rightCornerSpriteOffsetX, bottomBorderEdgeOffset, _output, rightCornerOffsetX, bottomCornerOffsetY - extraCornerSize, cornerSize, extraCornerSize ); if ( _hasBackground ) { // Render the background image. renderBackgroundImage( _output, _windowArea, backgroundOffset, isEvilInterface ); // Make a transition from borders to the background in the corners. CreateDitheringTransition( verticalSprite, cornerSize, cornerSize, _output, cornerOffset.x, cornerOffset.y, extraCornerSize, transitionSize, false, true ); CreateDitheringTransition( verticalSprite, cornerSize, cornerSize, _output, cornerOffset.x, cornerOffset.y, transitionSize, extraCornerSize, true, true ); CreateDitheringTransition( verticalSprite, rightBorderEdgeOffset, cornerSize, _output, rightCornerOffsetX - extraCornerSize, cornerOffset.y, extraCornerSize, transitionSize, false, true ); CreateDitheringTransition( verticalSprite, rightCornerSpriteOffsetX - transitionSize, cornerSize, _output, rightCornerOffsetX - transitionSize, cornerOffset.y, transitionSize, extraCornerSize, true, false ); CreateDitheringTransition( verticalSprite, cornerSize, bottomCornerSpriteOffsetY - transitionSize, _output, cornerOffset.x, bottomCornerOffsetY - transitionSize, extraCornerSize, transitionSize, false, false ); CreateDitheringTransition( verticalSprite, cornerSize, bottomBorderEdgeOffset, _output, cornerOffset.x, bottomCornerOffsetY - extraCornerSize, transitionSize, extraCornerSize, true, true ); CreateDitheringTransition( verticalSprite, rightBorderEdgeOffset, bottomCornerSpriteOffsetY - transitionSize, _output, rightCornerOffsetX - extraCornerSize, bottomCornerOffsetY - transitionSize, extraCornerSize, transitionSize, false, false ); CreateDitheringTransition( verticalSprite, rightCornerSpriteOffsetX - transitionSize, bottomBorderEdgeOffset, _output, rightCornerOffsetX - transitionSize, bottomCornerOffsetY - extraCornerSize, transitionSize, extraCornerSize, true, false ); } // Render vertical borders. const int32_t doubleBorderEdgeOffset = borderEdgeOffset * 2; const int32_t verticalSpriteCopyHeight = std::min( _windowArea.height, verticalSpriteHeight ) - doubleBorderEdgeOffset; const int32_t verticalSpriteCopies = ( _windowArea.height - doubleBorderEdgeOffset - 1 - transitionSize ) / ( bottomBorderEdgeOffset - borderEdgeOffset - transitionSize ); const int32_t topBorderEdgeOffset = _windowArea.y + borderEdgeOffset; Blit( verticalSprite, 0, borderEdgeOffset, _output, _windowArea.x, topBorderEdgeOffset, cornerSize, verticalSpriteCopyHeight ); Blit( verticalSprite, rightCornerSpriteOffsetX, borderEdgeOffset, _output, rightCornerOffsetX, topBorderEdgeOffset, cornerSize, verticalSpriteCopyHeight ); // Render a transition to the background. if ( _hasBackground ) { CreateDitheringTransition( verticalSprite, cornerSize, borderEdgeOffset, _output, cornerOffset.x, topBorderEdgeOffset, transitionSize, verticalSpriteCopyHeight, true, true ); CreateDitheringTransition( verticalSprite, rightCornerSpriteOffsetX - transitionSize, borderEdgeOffset, _output, rightCornerOffsetX - transitionSize, topBorderEdgeOffset, transitionSize, verticalSpriteCopyHeight, true, false ); } // If we need more copies to fill vertical borders we make a transition and copy the central part of the border. if ( verticalSpriteCopies > 0 ) { int32_t toOffsetY = borderEdgeOffset + verticalSpriteCopyHeight; const int32_t outputY = _windowArea.y + toOffsetY - transitionSize; CreateDitheringTransition( verticalSprite, 0, borderEdgeOffset, _output, _windowArea.x, outputY, cornerSize, transitionSize, false, false ); CreateDitheringTransition( verticalSprite, rightCornerSpriteOffsetX, borderEdgeOffset, _output, rightCornerOffsetX, outputY, cornerSize, transitionSize, false, false ); const int32_t stepY = verticalSpriteCopyHeight - transitionSize; const int32_t fromOffsetY = borderEdgeOffset + transitionSize; for ( int32_t i = 0; i < verticalSpriteCopies; ++i ) { const int32_t copyHeight = std::min( verticalSpriteCopyHeight, _windowArea.height - borderEdgeOffset - toOffsetY ); const int32_t toY = _windowArea.y + toOffsetY; Blit( verticalSprite, 0, fromOffsetY, _output, _windowArea.x, toY, cornerSize, copyHeight ); Blit( verticalSprite, rightCornerSpriteOffsetX, fromOffsetY, _output, rightCornerOffsetX, toY, cornerSize, copyHeight ); // Render a transition to the background. if ( _hasBackground ) { CreateDitheringTransition( verticalSprite, cornerSize, fromOffsetY, _output, cornerOffset.x, toY, transitionSize, copyHeight, true, true ); CreateDitheringTransition( verticalSprite, rightCornerSpriteOffsetX - transitionSize, fromOffsetY, _output, rightCornerOffsetX - transitionSize, toY, transitionSize, copyHeight, true, false ); } toOffsetY += stepY; } } // Make a transition to the bottom corners. const int32_t verticalSpriteBottomCornerEdgeY = bottomBorderEdgeOffset - transitionSize; const int32_t optputBottomCornerEdgeY = _windowArea.y + _windowArea.height - borderEdgeOffset - transitionSize; CreateDitheringTransition( verticalSprite, 0, verticalSpriteBottomCornerEdgeY, _output, _windowArea.x, optputBottomCornerEdgeY, cornerSize, transitionSize, false, false ); CreateDitheringTransition( verticalSprite, rightCornerSpriteOffsetX, verticalSpriteBottomCornerEdgeY, _output, rightCornerOffsetX, optputBottomCornerEdgeY, cornerSize, transitionSize, false, false ); // Render horizontal borders. We have to remember that 'verticalSprite' has 16 (equals to BORDERWIDTH) pixels of shadow at the left and bottom sides. const int32_t horizontalSpriteCopyWidth = std::min( _windowArea.width, horizontalSpriteWidth ) - doubleBorderEdgeOffset; const int32_t horizontalSpriteCopies = ( _windowArea.width - doubleBorderEdgeOffset - 1 - transitionSize ) / ( horizontalSpriteWidth - doubleBorderEdgeOffset - transitionSize ); const int32_t bottomBorderSpriteOffsetY = horizontalSpriteHeight - cornerSize; const int32_t horizontalSpriteCopyStartX = borderEdgeOffset + borderSize; const int32_t leftBorderEdgeOffset = _windowArea.x + borderEdgeOffset; Blit( horizontalSprite, horizontalSpriteCopyStartX, 0, _output, leftBorderEdgeOffset, _windowArea.y, horizontalSpriteCopyWidth, cornerSize ); Blit( horizontalSprite, horizontalSpriteCopyStartX, bottomBorderSpriteOffsetY, _output, leftBorderEdgeOffset, bottomCornerOffsetY, horizontalSpriteCopyWidth, cornerSize ); // Render a transition to the background. if ( _hasBackground ) { CreateDitheringTransition( horizontalSprite, horizontalSpriteCopyStartX, cornerSize, _output, leftBorderEdgeOffset, cornerOffset.y, horizontalSpriteCopyWidth, transitionSize, false, true ); CreateDitheringTransition( horizontalSprite, horizontalSpriteCopyStartX, bottomBorderSpriteOffsetY - transitionSize, _output, leftBorderEdgeOffset, bottomCornerOffsetY - transitionSize, horizontalSpriteCopyWidth, transitionSize, false, false ); } // If we need more copies to fill horizontal borders we make a transition and copy the central part of the border. if ( horizontalSpriteCopies > 0 ) { int32_t toOffsetX = borderEdgeOffset + horizontalSpriteCopyWidth; const int32_t outputX = _windowArea.x + toOffsetX - transitionSize; CreateDitheringTransition( horizontalSprite, horizontalSpriteCopyStartX, 0, _output, outputX, _windowArea.y, transitionSize, cornerSize, true, false ); CreateDitheringTransition( horizontalSprite, horizontalSpriteCopyStartX, bottomBorderSpriteOffsetY, _output, outputX, bottomCornerOffsetY, transitionSize, cornerSize, true, false ); const int32_t stepX = horizontalSpriteCopyWidth - transitionSize; const int32_t fromOffsetX = horizontalSpriteCopyStartX + transitionSize; for ( int32_t i = 0; i < horizontalSpriteCopies; ++i ) { const int32_t copyWidth = std::min( horizontalSpriteCopyWidth, _windowArea.width - borderEdgeOffset - toOffsetX ); const int32_t toX = _windowArea.x + toOffsetX; Blit( horizontalSprite, fromOffsetX, 0, _output, toX, _windowArea.y, copyWidth, cornerSize ); Blit( horizontalSprite, fromOffsetX, bottomBorderSpriteOffsetY, _output, toX, bottomCornerOffsetY, copyWidth, cornerSize ); // Render a transition to the background. if ( _hasBackground ) { CreateDitheringTransition( horizontalSprite, fromOffsetX, cornerSize, _output, toX, cornerOffset.y, copyWidth, transitionSize, false, true ); CreateDitheringTransition( horizontalSprite, fromOffsetX, bottomBorderSpriteOffsetY - transitionSize, _output, toX, bottomCornerOffsetY - transitionSize, copyWidth, transitionSize, false, false ); } toOffsetX += stepX; } } // Make a transition to the right corners. const int32_t horizontalSpriteRightCornerEdgeX = horizontalSprite.width() - borderEdgeOffset - transitionSize; const int32_t optputRightCornerEdgeX = _windowArea.x + _windowArea.width - borderEdgeOffset - transitionSize; CreateDitheringTransition( horizontalSprite, horizontalSpriteRightCornerEdgeX, 0, _output, optputRightCornerEdgeX, _windowArea.y, transitionSize, cornerSize, true, false ); CreateDitheringTransition( horizontalSprite, horizontalSpriteRightCornerEdgeX, bottomBorderSpriteOffsetY, _output, optputRightCornerEdgeX, bottomCornerOffsetY, transitionSize, cornerSize, true, false ); // Render shadow at the left side of the window. int32_t offsetY = _windowArea.y + borderSize; ApplyTransform( _output, _totalArea.x, offsetY, borderSize, 1, 5 ); ++offsetY; ApplyTransform( _output, _totalArea.x, offsetY, 1, _windowArea.height - 2, 5 ); ApplyTransform( _output, _totalArea.x + 1, offsetY, borderSize - 1, 1, 4 ); ++offsetY; ApplyTransform( _output, _totalArea.x + 1, offsetY, 1, _windowArea.height - 4, 4 ); ApplyTransform( _output, _totalArea.x + 2, offsetY, borderSize - 2, 1, 3 ); ++offsetY; ApplyTransform( _output, _totalArea.x + 2, offsetY, 1, _windowArea.height - 6, 3 ); ApplyTransform( _output, _totalArea.x + 3, offsetY, borderSize - 3, _windowArea.height - borderSize - 3, 2 ); // Render shadow at the bottom side of the window. offsetY = _windowArea.y + _windowArea.height; const int32_t shadowBottomEdge = _windowArea.y + _totalArea.height; ApplyTransform( _output, _totalArea.x + 3, offsetY, _windowArea.width - 6, borderSize - 3, 2 ); ApplyTransform( _output, _totalArea.x + 2, shadowBottomEdge - 3, _windowArea.width - 4, 1, 3 ); ApplyTransform( _output, _totalArea.x + _windowArea.width - 3, offsetY, 1, borderSize - 3, 3 ); ApplyTransform( _output, _totalArea.x + 1, shadowBottomEdge - 2, _windowArea.width - 2, 1, 4 ); ApplyTransform( _output, _totalArea.x + _windowArea.width - 2, offsetY, 1, borderSize - 2, 4 ); ApplyTransform( _output, _totalArea.x, shadowBottomEdge - 1, _windowArea.width, 1, 5 ); ApplyTransform( _output, _totalArea.x + _windowArea.width - 1, offsetY, 1, borderSize - 1, 5 ); } void StandardWindow::applyTextBackgroundShading( const Rect & roi ) { const Rect shadingRoi = roi ^ _activeArea; // The text background is darker than original background. The shadow strength 2 is too much so we do two shading transforms: 3 and 5. ApplyTransform( _output, shadingRoi.x + 2, shadingRoi.y + 2, shadingRoi.width - 4, shadingRoi.height - 4, 3 ); ApplyTransform( _output, shadingRoi.x + 2, shadingRoi.y + 2, shadingRoi.width - 4, shadingRoi.height - 4, 5 ); // Make text background borders: it consists of rectangles with different transform shading. auto applyRectTransform = [&shadingRoi]( Image & output, const int32_t offset, const int32_t size, const uint8_t transformId ) { // Top horizontal line. ApplyTransform( output, shadingRoi.x + offset, shadingRoi.y + offset, shadingRoi.width - 2 * offset, size, transformId ); // Left vertical line without pixels that are parts of horizontal lines. ApplyTransform( output, shadingRoi.x + offset, shadingRoi.y + offset + size, size, shadingRoi.height - 2 * ( offset + size ), transformId ); // Bottom horizontal line. ApplyTransform( output, shadingRoi.x + offset, shadingRoi.y + shadingRoi.height - 1 - offset - size + 1, shadingRoi.width - 2 * offset, size, transformId ); // Right vertical line without pixels that are parts of horizontal lines. ApplyTransform( output, shadingRoi.x + shadingRoi.width - 1 - offset - size + 1, shadingRoi.y + offset + size, size, shadingRoi.height - 2 * ( offset + size ), transformId ); }; // Outer rectangle is slightly bright. applyRectTransform( _output, 0, 1, 9 ); // Next shaded rectangles have these shadow strengths: 4, 3, 2, 2, 2, 3, 4, 5. applyRectTransform( _output, 1, 1, 4 ); applyRectTransform( _output, 2, 1, 3 ); applyRectTransform( _output, 3, 3, 2 ); applyRectTransform( _output, 6, 1, 3 ); applyRectTransform( _output, 7, 1, 4 ); applyRectTransform( _output, 8, 1, 5 ); } void StandardWindow::renderScrollbarBackground( const Rect & roi, const bool isEvilInterface ) { const Sprite & scrollBar = AGG::GetICN( isEvilInterface ? ICN::ADVBORDE : ICN::ADVBORD, 0 ); const int32_t topPartHeight = 19; const int32_t scrollBarWidth = 16; const int32_t middlePartHeight = 88; const int32_t icnOffsetX = 536; const int32_t middleAndBottomPartsHeight = roi.height - topPartHeight; // Top part of scrollbar background. Copy( scrollBar, icnOffsetX, 176, _output, roi.x, roi.y, scrollBarWidth, topPartHeight ); // Middle part of scrollbar background. const int32_t middlePartCount = ( roi.height - 2 * topPartHeight + middlePartHeight - 1 ) / middlePartHeight; int32_t offsetY = topPartHeight; for ( int32_t i = 0; i < middlePartCount; ++i ) { Copy( scrollBar, icnOffsetX, 196, _output, roi.x, roi.y + offsetY, scrollBarWidth, std::min( middlePartHeight, middleAndBottomPartsHeight - offsetY ) ); offsetY += middlePartHeight; } // Bottom part of scrollbar background. Copy( scrollBar, icnOffsetX, 285, _output, roi.x, roi.y + middleAndBottomPartsHeight, scrollBarWidth, topPartHeight ); // Make scrollbar shadow. for ( uint8_t i = 0; i < 4; ++i ) { const uint8_t transformId = i + 1; ApplyTransform( _output, roi.x - transformId, roi.y + transformId, 1, roi.height - transformId, transformId ); ApplyTransform( _output, roi.x - transformId, roi.y + roi.height + i, scrollBarWidth, 1, transformId ); } } void StandardWindow::renderButtonSprite( ButtonSprite & button, const std::string & buttonText, const int32_t buttonWidth, const Point & offset, const bool isEvilInterface, const Padding padding ) { Sprite released; Sprite pressed; makeButtonSprites( released, pressed, buttonText, buttonWidth, isEvilInterface, false ); const Point pos = _getRenderPos( offset, { released.width(), released.height() }, padding ); button.setSprite( released, pressed ); button.setPosition( pos.x, pos.y ); addGradientShadow( released, _output, button.area().getPosition(), { -5, 5 } ); button.draw(); } void StandardWindow::renderButton( Button & button, const int icnId, const uint32_t releasedIndex, const uint32_t pressedIndex, const Point & offset, const Padding padding ) { const Sprite & buttonSprite = AGG::GetICN( icnId, 0 ); const Point pos = _getRenderPos( offset, { buttonSprite.width(), buttonSprite.height() }, padding ); button.setICNInfo( icnId, releasedIndex, pressedIndex ); button.setPosition( pos.x, pos.y ); addGradientShadow( buttonSprite, _output, button.area().getPosition(), { -5, 5 } ); button.draw(); } void StandardWindow::renderOkayCancelButtons( Button & buttonOk, Button & buttonCancel, const bool isEvilInterface ) { const Point buttonOffset( 20, 7 ); const int buttonOkIcn = isEvilInterface ? ICN::BUTTON_SMALL_OKAY_EVIL : ICN::BUTTON_SMALL_OKAY_GOOD; renderButton( buttonOk, buttonOkIcn, 0, 1, buttonOffset, Padding::BOTTOM_LEFT ); const int buttonCancelIcn = isEvilInterface ? ICN::BUTTON_SMALL_CANCEL_EVIL : ICN::BUTTON_SMALL_CANCEL_GOOD; renderButton( buttonCancel, buttonCancelIcn, 0, 1, buttonOffset, Padding::BOTTOM_RIGHT ); } Point StandardWindow::_getRenderPos( const Point & offset, const Size & itemSize, const Padding padding ) const { Point pos( _activeArea.x, _activeArea.y ); // Apply horizontal padding. switch ( padding ) { case Padding::TOP_LEFT: case Padding::CENTER_LEFT: case Padding::BOTTOM_LEFT: pos.x += offset.x; break; case Padding::TOP_CENTER: case Padding::CENTER_CENTER: case Padding::BOTTOM_CENTER: pos.x += ( _activeArea.width - itemSize.width ) / 2 + offset.x; break; case Padding::TOP_RIGHT: case Padding::CENTER_RIGHT: case Padding::BOTTOM_RIGHT: pos.x += _activeArea.width - itemSize.width - offset.x; break; default: // Have you added a new padding? Add a logic for it. assert( 0 ); break; } // Apply vertical padding. switch ( padding ) { case Padding::TOP_LEFT: case Padding::TOP_CENTER: case Padding::TOP_RIGHT: pos.y += offset.y; break; case Padding::CENTER_LEFT: case Padding::CENTER_CENTER: case Padding::CENTER_RIGHT: pos.y += ( _activeArea.height - itemSize.height ) / 2 + offset.y; break; case Padding::BOTTOM_LEFT: case Padding::BOTTOM_CENTER: case Padding::BOTTOM_RIGHT: pos.y += _activeArea.height - itemSize.height - offset.y; break; default: // Have you added a new padding? Add a logic for it. assert( 0 ); break; } return pos; } void StandardWindow::renderBackgroundImage( Image & output, const Rect & roi, const int32_t borderOffset, const bool isEvilInterface ) { const Sprite & backgroundSprite = AGG::GetICN( ( isEvilInterface ? ICN::STONEBAK_EVIL : ICN::STONEBAK ), 0 ); const int32_t backgroundSpriteWidth{ backgroundSprite.width() }; const int32_t backgroundSpriteHeight{ backgroundSprite.height() }; const int32_t backgroundWidth = roi.width - borderOffset * 2; const int32_t backgroundHeight = roi.height - borderOffset * 2; const int32_t backgroundHorizontalCopies = ( backgroundWidth - 1 - transitionSize ) / ( backgroundSpriteWidth - transitionSize ); const int32_t backgroundVerticalCopies = ( backgroundHeight - 1 - transitionSize ) / ( backgroundSpriteHeight - transitionSize ); const int32_t backgroundCopyWidth = std::min( backgroundSpriteWidth, backgroundWidth ); const int32_t backgroundCopyHeight = std::min( backgroundSpriteHeight, backgroundHeight ); const int32_t backgroundOffsetX = roi.x + borderOffset; const int32_t backgroundOffsetY = roi.y + borderOffset; // We do a copy as the background image does not have transparent pixels. Copy( backgroundSprite, 0, 0, output, backgroundOffsetX, backgroundOffsetY, backgroundCopyWidth, backgroundCopyHeight ); // If we need more copies to fill background horizontally we make a transition and copy existing image. if ( backgroundHorizontalCopies > 0 ) { int32_t toOffsetX = borderOffset + backgroundSpriteWidth; CreateDitheringTransition( backgroundSprite, 0, 0, output, roi.x + toOffsetX - transitionSize, backgroundOffsetY, transitionSize, backgroundCopyHeight, true, false ); const int32_t stepX = backgroundSpriteWidth - transitionSize; const int32_t fromOffsetX = borderOffset + transitionSize; for ( int32_t i = 0; i < backgroundHorizontalCopies; ++i ) { Copy( output, roi.x + fromOffsetX, backgroundOffsetY, output, roi.x + toOffsetX, backgroundOffsetY, std::min( backgroundSpriteWidth, roi.width - borderOffset - toOffsetX ), backgroundCopyHeight ); toOffsetX += stepX; } } // If we need more copies to fill background vertically we make a transition and copy existing image in full background width. if ( backgroundVerticalCopies > 0 ) { int32_t toOffsetY = borderOffset + backgroundSpriteHeight; CreateDitheringTransition( output, backgroundOffsetX, backgroundOffsetY, output, backgroundOffsetX, roi.y + toOffsetY - transitionSize, backgroundWidth, transitionSize, false, false ); const int32_t stepY = backgroundSpriteHeight - transitionSize; const int32_t fromOffsetY = borderOffset + transitionSize; for ( int32_t i = 0; i < backgroundVerticalCopies; ++i ) { Copy( output, backgroundOffsetX, roi.y + fromOffsetY, output, backgroundOffsetX, roi.y + toOffsetY, backgroundWidth, std::min( backgroundSpriteHeight, roi.height - borderOffset - toOffsetY ) ); toOffsetY += stepY; } } } } fheroes2-1.0.12+dfsg/src/fheroes2/gui/ui_window.h000066400000000000000000000105131456075706000215060ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include "image.h" #include "math_base.h" #include "screen.h" namespace fheroes2 { class Button; class ButtonSprite; // Standard window with shadow. class StandardWindow { public: enum class Padding : uint8_t { TOP_LEFT, TOP_CENTER, TOP_RIGHT, CENTER_LEFT, CENTER_CENTER, CENTER_RIGHT, BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT }; StandardWindow() = delete; StandardWindow( const StandardWindow & ) = delete; StandardWindow & operator=( const StandardWindow & ) = delete; StandardWindow( const int32_t width, const int32_t height, const bool renderBackground, Image & output = Display::instance() ); StandardWindow( const int32_t x, const int32_t y, const int32_t width, const int32_t height, const bool renderBackground, Image & output = Display::instance() ); ~StandardWindow() { Display & display = Display::instance(); if ( &_output == &display ) { // The screen area of the closed window should be updated during the next '.render()' call. display.updateNextRenderRoi( _totalArea ); } } // Returns the window background ROI. const Rect & activeArea() const { return _activeArea; } // Returns ROI that includes window background and window borders. const Rect & windowArea() const { return _windowArea; } // Returns ROI that includes window background, borders and window shadow. const Rect & totalArea() const { return _totalArea; } void render(); void renderScrollbarBackground( const Rect & roi, const bool isEvilInterface ); void renderButtonSprite( ButtonSprite & button, const std::string & buttonText, const int32_t buttonWidth, const Point & offset, const bool isEvilInterface, const Padding padding ); void renderButton( Button & button, const int icnId, const uint32_t releasedIndex, const uint32_t pressedIndex, const Point & offset, const Padding padding ); void renderOkayCancelButtons( Button & buttonOk, Button & buttonCancel, const bool isEvilInterface ); void applyTextBackgroundShading( const Rect & roi ); static void renderBackgroundImage( fheroes2::Image & output, const Rect & roi, const int32_t borderOffset, const bool isEvilInterface ); private: Image & _output; const Rect _activeArea; const Rect _windowArea; const Rect _totalArea; ImageRestorer _restorer; const bool _hasBackground{ true }; Point _getRenderPos( const Point & offset, const Size & itemSize, const Padding padding ) const; }; } fheroes2-1.0.12+dfsg/src/fheroes2/h2d/000077500000000000000000000000001456075706000172225ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/fheroes2/h2d/h2d.cpp000066400000000000000000000053111456075706000204030ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "h2d.h" #include #include #include "h2d_file.h" #include "logging.h" #include "settings.h" #include "system.h" namespace { fheroes2::H2DReader reader; bool getH2DFilePath( const std::string & fileName, std::string & path ) { #if defined( MACOS_APP_BUNDLE ) return Settings::findFile( "h2d", fileName, path ); #else return Settings::findFile( System::concatPath( "files", "data" ), fileName, path ); #endif } } namespace fheroes2 { namespace h2d { H2DInitializer::H2DInitializer() { const std::string fileName{ "resurrection.h2d" }; std::string filePath; if ( !getH2DFilePath( fileName, filePath ) ) { VERBOSE_LOG( "'" << fileName << "' file cannot be found in the system." ) throw std::logic_error( fileName + " is not found." ); } if ( !reader.open( filePath ) ) { VERBOSE_LOG( "Failed to open '" << filePath << "' file." ) throw std::logic_error( std::string( "Cannot open file: " ) + filePath ); } } bool readImage( const std::string & name, Sprite & image ) { return readImageFromH2D( reader, name, image ); } } } fheroes2-1.0.12+dfsg/src/fheroes2/h2d/h2d.h000066400000000000000000000035731456075706000200600ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include namespace fheroes2 { class Sprite; namespace h2d { class H2DInitializer { public: H2DInitializer(); H2DInitializer( const H2DInitializer & ) = delete; H2DInitializer & operator=( const H2DInitializer & ) = delete; ~H2DInitializer() = default; }; bool readImage( const std::string & name, Sprite & image ); } } fheroes2-1.0.12+dfsg/src/fheroes2/heroes/000077500000000000000000000000001456075706000200325ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/fheroes2/heroes/direction.cpp000066400000000000000000000122241456075706000225170ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "direction.h" std::string Direction::String( int direct ) { std::string temp; if ( direct & CENTER ) { temp += "center, "; } if ( direct & TOP ) { temp += "top, "; } if ( direct & TOP_RIGHT ) { temp += "top-right, "; } if ( direct & RIGHT ) { temp += "right, "; } if ( direct & BOTTOM_RIGHT ) { temp += "bottom-right, "; } if ( direct & BOTTOM ) { temp += "bottom, "; } if ( direct & BOTTOM_LEFT ) { temp += "bottom-left, "; } if ( direct & LEFT ) { temp += "left, "; } if ( direct & TOP_LEFT ) { temp += "top-left, "; } if ( !temp.empty() && temp.back() == ' ' ) { temp.pop_back(); } if ( !temp.empty() && temp.back() == ',' ) { temp.pop_back(); } return temp.empty() ? "unknown" : temp; } bool Direction::isDiagonal( int direction ) { return ( direction & DIRECTION_ALL_CORNERS ) != 0; } bool Direction::ShortDistanceClockWise( int from, int to ) { switch ( from ) { case TOP: switch ( to ) { case TOP_RIGHT: case RIGHT: case BOTTOM_RIGHT: case BOTTOM: return true; default: break; } break; case TOP_RIGHT: switch ( to ) { case RIGHT: case BOTTOM_RIGHT: case BOTTOM: case BOTTOM_LEFT: return true; default: break; } break; case RIGHT: switch ( to ) { case BOTTOM_RIGHT: case BOTTOM: case BOTTOM_LEFT: case LEFT: return true; default: break; } break; case BOTTOM_RIGHT: switch ( to ) { case BOTTOM: case BOTTOM_LEFT: case LEFT: case TOP_LEFT: return true; default: break; } break; case BOTTOM: switch ( to ) { case BOTTOM_LEFT: case LEFT: case TOP_LEFT: return true; default: break; } break; case BOTTOM_LEFT: switch ( to ) { case TOP: case TOP_RIGHT: case LEFT: case TOP_LEFT: return true; default: break; } break; case LEFT: switch ( to ) { case TOP: case TOP_RIGHT: case RIGHT: case TOP_LEFT: return true; default: break; } break; case TOP_LEFT: switch ( to ) { case TOP: case TOP_RIGHT: case RIGHT: case BOTTOM_RIGHT: return true; default: break; } break; default: break; } return false; } int Direction::Reflect( int direct ) { switch ( direct ) { case TOP_LEFT: return BOTTOM_RIGHT; case TOP: return BOTTOM; case TOP_RIGHT: return BOTTOM_LEFT; case RIGHT: return LEFT; case BOTTOM_RIGHT: return TOP_LEFT; case BOTTOM: return TOP; case BOTTOM_LEFT: return TOP_RIGHT; case LEFT: return RIGHT; case CENTER: return CENTER; default: break; } return UNKNOWN; } const Directions & Direction::All() { static const Directions allDirections = Directions( { TOP_LEFT, TOP, TOP_RIGHT, RIGHT, BOTTOM_RIGHT, BOTTOM, BOTTOM_LEFT, LEFT } ); return allDirections; } fheroes2-1.0.12+dfsg/src/fheroes2/heroes/direction.h000066400000000000000000000070261456075706000221700ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2DIRECTION_H #define H2DIRECTION_H #include #include using Directions = std::vector; namespace Direction { enum { UNKNOWN = 0x0000, TOP_LEFT = 0x0001, TOP = 0x0002, TOP_RIGHT = 0x0004, RIGHT = 0x0008, BOTTOM_RIGHT = 0x0010, BOTTOM = 0x0020, BOTTOM_LEFT = 0x0040, LEFT = 0x0080, CENTER = 0x0100 }; std::string String( int ); bool isDiagonal( int direction ); int Reflect( int direct ); bool ShortDistanceClockWise( int direct1, int direct2 ); const Directions & All(); } #define DIRECTION_TOP_ROW ( Direction::TOP_LEFT | Direction::TOP | Direction::TOP_RIGHT ) #define DIRECTION_BOTTOM_ROW ( Direction::BOTTOM_LEFT | Direction::BOTTOM | Direction::BOTTOM_RIGHT ) #define DIRECTION_CENTER_ROW ( Direction::LEFT | Direction::CENTER | Direction::RIGHT ) #define DIRECTION_LEFT_COL ( Direction::TOP_LEFT | Direction::LEFT | Direction::BOTTOM_LEFT ) #define DIRECTION_CENTER_COL ( Direction::TOP | Direction::CENTER | Direction::BOTTOM ) #define DIRECTION_RIGHT_COL ( Direction::TOP_RIGHT | Direction::RIGHT | Direction::BOTTOM_RIGHT ) #define DIRECTION_ALL ( DIRECTION_TOP_ROW | DIRECTION_BOTTOM_ROW | DIRECTION_CENTER_ROW ) #define DIRECTION_AROUND ( DIRECTION_TOP_ROW | DIRECTION_BOTTOM_ROW | Direction::LEFT | Direction::RIGHT ) #define DIRECTION_TOP_RIGHT_CORNER ( Direction::TOP | Direction::TOP_RIGHT | Direction::RIGHT ) #define DIRECTION_TOP_LEFT_CORNER ( Direction::TOP | Direction::TOP_LEFT | Direction::LEFT ) #define DIRECTION_BOTTOM_RIGHT_CORNER ( Direction::BOTTOM | Direction::BOTTOM_RIGHT | Direction::RIGHT ) #define DIRECTION_BOTTOM_LEFT_CORNER ( Direction::BOTTOM | Direction::BOTTOM_LEFT | Direction::LEFT ) #define DIRECTION_ALL_CORNERS ( Direction::TOP_RIGHT | Direction::BOTTOM_RIGHT | Direction::BOTTOM_LEFT | Direction::TOP_LEFT ) #endif fheroes2-1.0.12+dfsg/src/fheroes2/heroes/heroes.cpp000066400000000000000000002171531456075706000220340ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "heroes.h" #include #include #include #include #include #include #include #include #include #include "agg_image.h" #include "ai.h" #include "army_troop.h" #include "artifact.h" #include "artifact_info.h" #include "audio_manager.h" #include "battle.h" #include "castle.h" #include "dialog.h" #include "difficulty.h" #include "direction.h" #include "game.h" #include "game_io.h" #include "game_static.h" #include "gamedefs.h" #include "ground.h" #include "icn.h" #include "image.h" #include "kingdom.h" #include "logging.h" #include "luck.h" #include "m82.h" #include "maps.h" #include "maps_fileinfo.h" #include "maps_tiles.h" #include "monster.h" #include "morale.h" #include "mp2.h" #include "payment.h" #include "players.h" #include "race.h" #include "rand.h" #include "resource.h" #include "save_format_version.h" #include "serialize.h" #include "settings.h" #include "speed.h" #include "spell_book.h" #include "tools.h" #include "translations.h" #include "ui_dialog.h" #include "ui_text.h" #include "world.h" namespace { std::pair getHeroIdRangeForRace( const int race ) { switch ( race ) { case Race::KNGT: return { Heroes::LORDKILBURN, Heroes::DIMITRY }; case Race::BARB: return { Heroes::THUNDAX, Heroes::ATLAS }; case Race::SORC: return { Heroes::ASTRA, Heroes::LUNA }; case Race::WRLK: return { Heroes::ARIE, Heroes::WRATHMONT }; case Race::WZRD: return { Heroes::MYRA, Heroes::MANDIGAL }; case Race::NECR: return { Heroes::ZOM, Heroes::CELIA }; default: break; } return { Heroes::LORDKILBURN, Heroes::CELIA }; } int ObjectVisitedModifiersResult( const std::vector & objectTypes, const Heroes & hero, std::string * strs ) { int result = 0; for ( const MP2::MapObjectType objectType : objectTypes ) { if ( hero.isObjectTypeVisited( objectType ) ) { result += GameStatic::ObjectVisitedModifiers( objectType ); if ( strs ) { switch ( objectType ) { case MP2::OBJ_GRAVEYARD: case MP2::OBJ_NON_ACTION_GRAVEYARD: case MP2::OBJ_SHIPWRECK: case MP2::OBJ_NON_ACTION_SHIPWRECK: case MP2::OBJ_DERELICT_SHIP: case MP2::OBJ_NON_ACTION_DERELICT_SHIP: { std::string modRobber = _( "%{object} robber" ); StringReplace( modRobber, "%{object}", MP2::StringObject( objectType ) ); strs->append( modRobber ); break; } case MP2::OBJ_PYRAMID: case MP2::OBJ_NON_ACTION_PYRAMID: { std::string modRaided = _( "%{object} raided" ); StringReplace( modRaided, "%{object}", MP2::StringObject( objectType ) ); strs->append( modRaided ); break; } default: strs->append( MP2::StringObject( objectType ) ); break; } StringAppendModifiers( *strs, GameStatic::ObjectVisitedModifiers( objectType ) ); strs->append( "\n" ); } } } return result; } std::string GetHeroRoleString( const Heroes & hero ) { switch ( hero.getAIRole() ) { case Heroes::Role::SCOUT: return "Scout"; case Heroes::Role::COURIER: return "Courier"; case Heroes::Role::HUNTER: return "Hunter"; case Heroes::Role::FIGHTER: return "Fighter"; case Heroes::Role::CHAMPION: return "Champion"; default: // Did you add a new AI hero role? Add the appropriate logic for it! assert( 0 ); break; } return "Unknown"; } } const char * Heroes::GetName( int heroid ) { assert( heroid >= UNKNOWN && heroid < HEROES_COUNT ); const std::array names = { // Unknown / uninitialized hero. "Unknown", // Knight heroes from The Succession Wars. gettext_noop( "Lord Kilburn" ), gettext_noop( "Sir Gallant" ), gettext_noop( "Ector" ), gettext_noop( "Gwenneth" ), gettext_noop( "Tyro" ), gettext_noop( "Ambrose" ), gettext_noop( "Ruby" ), gettext_noop( "Maximus" ), gettext_noop( "Dimitry" ), // Barbarian heroes from The Succession Wars. gettext_noop( "Thundax" ), gettext_noop( "Fineous" ), gettext_noop( "Jojosh" ), gettext_noop( "Crag Hack" ), gettext_noop( "Jezebel" ), gettext_noop( "Jaclyn" ), gettext_noop( "Ergon" ), gettext_noop( "Tsabu" ), gettext_noop( "Atlas" ), // Sorceress heroes from The Succession Wars. gettext_noop( "Astra" ), gettext_noop( "Natasha" ), gettext_noop( "Troyan" ), gettext_noop( "Vatawna" ), gettext_noop( "Rebecca" ), gettext_noop( "Gem" ), gettext_noop( "Ariel" ), gettext_noop( "Carlawn" ), gettext_noop( "Luna" ), // Warlock heroes from The Succession Wars. gettext_noop( "Arie" ), gettext_noop( "Alamar" ), gettext_noop( "Vesper" ), gettext_noop( "Crodo" ), gettext_noop( "Barok" ), gettext_noop( "Kastore" ), gettext_noop( "Agar" ), gettext_noop( "Falagar" ), gettext_noop( "Wrathmont" ), // Wizard heroes from The Succession Wars. gettext_noop( "Myra" ), gettext_noop( "Flint" ), gettext_noop( "Dawn" ), gettext_noop( "Halon" ), gettext_noop( "Myrini" ), gettext_noop( "Wilfrey" ), gettext_noop( "Sarakin" ), gettext_noop( "Kalindra" ), gettext_noop( "Mandigal" ), // Necromancer heroes from The Succession Wars. gettext_noop( "Zom" ), gettext_noop( "Darlana" ), gettext_noop( "Zam" ), gettext_noop( "Ranloo" ), gettext_noop( "Charity" ), gettext_noop( "Rialdo" ), gettext_noop( "Roxana" ), gettext_noop( "Sandro" ), gettext_noop( "Celia" ), // The Succession Wars campaign heroes. gettext_noop( "Roland" ), gettext_noop( "Lord Corlagon" ), gettext_noop( "Sister Eliza" ), gettext_noop( "Archibald" ), gettext_noop( "Lord Halton" ), gettext_noop( "Brother Brax" ), // The Price of Loyalty expansion heroes. gettext_noop( "Solmyr" ), gettext_noop( "Dainwin" ), gettext_noop( "Mog" ), gettext_noop( "Uncle Ivan" ), gettext_noop( "Joseph" ), gettext_noop( "Gallavant" ), _( "Elderian" ), gettext_noop( "Ceallach" ), gettext_noop( "Drakonia" ), gettext_noop( "Martine" ), gettext_noop( "Jarkonas" ), // Debug hero. Should not be used anywhere outside the development! "Debug Hero" }; return _( names[heroid] ); } Heroes::Heroes() : experience( 0 ) , army( this ) , _id( UNKNOWN ) , portrait( UNKNOWN ) , _race( Race::NONE ) , _objectTypeUnderHero( MP2::OBJ_NONE ) , path( *this ) , direction( Direction::RIGHT ) , sprite_index( 18 ) , _patrolDistance( 0 ) , _alphaValue( 255 ) , _attackedMonsterTileIndex( -1 ) , _aiRole( Role::HUNTER ) { // Do nothing. } Heroes::Heroes( const int heroID, const int race, const uint32_t additionalExperience ) : Heroes( heroID, race ) { IncreaseExperience( additionalExperience, true ); } Heroes::Heroes( int heroid, int rc ) : HeroBase( HeroBase::HEROES, rc ) , ColorBase( Color::NONE ) , experience( GetStartingXp() ) , secondary_skills( rc ) , army( this ) , _id( heroid ) , portrait( heroid ) , _race( rc ) , _objectTypeUnderHero( MP2::OBJ_NONE ) , path( *this ) , direction( Direction::RIGHT ) , sprite_index( 18 ) , _patrolDistance( 0 ) , _alphaValue( 255 ) , _attackedMonsterTileIndex( -1 ) , _aiRole( Role::HUNTER ) { name = _( Heroes::GetName( heroid ) ); army.Reset( true ); // Add to debug hero a lot of stuff. if ( _id == DEBUG_HERO ) { army.Clean(); army.JoinTroop( Monster::BLACK_DRAGON, 2, false ); army.JoinTroop( Monster::RED_DRAGON, 3, false ); secondary_skills = Skill::SecSkills(); secondary_skills.AddSkill( Skill::Secondary( Skill::Secondary::PATHFINDING, Skill::Level::ADVANCED ) ); secondary_skills.AddSkill( Skill::Secondary( Skill::Secondary::LOGISTICS, Skill::Level::ADVANCED ) ); secondary_skills.AddSkill( Skill::Secondary( Skill::Secondary::SCOUTING, Skill::Level::BASIC ) ); secondary_skills.AddSkill( Skill::Secondary( Skill::Secondary::MYSTICISM, Skill::Level::BASIC ) ); PickupArtifact( Artifact::STEALTH_SHIELD ); PickupArtifact( Artifact::DRAGON_SWORD ); PickupArtifact( Artifact::NOMAD_BOOTS_MOBILITY ); PickupArtifact( Artifact::TRAVELER_BOOTS_MOBILITY ); PickupArtifact( Artifact::TRUE_COMPASS_MOBILITY ); experience = 777; magic_point = 120; // This hero has all the spells in his spell book for ( const int spellId : Spell::getAllSpellIdsSuitableForSpellBook() ) { AppendSpellToBook( Spell( spellId ), true ); } } if ( !magic_point ) { SetSpellPoints( GetMaxSpellPoints() ); } move_point = GetMaxMovePoints(); } void Heroes::LoadFromMP2( const int32_t mapIndex, const int colorType, const int raceType, const bool isInJail, const std::vector & data ) { assert( data.size() == MP2::MP2_HEROES_STRUCTURE_SIZE ); // Structure containing information about a hero. // // - uint8_t (1 byte) // Unknown / unused. TODO: find out what this byte is for. // // - uint8_t (1 byte) // Does the hero have custom army set by map creator? // // - uint8_t (1 byte) // Custom monster type in army slot 1. // // - uint8_t (1 byte) // Custom monster type in army slot 2. // // - uint8_t (1 byte) // Custom monster type in army slot 3. // // - uint8_t (1 byte) // Custom monster type in army slot 4. // // - uint8_t (1 byte) // Custom monster type in army slot 5. // // - uint16_t (2 bytes) // The number of custom monsters in army slot 1. // // - uint16_t (2 bytes) // The number of custom monsters in army slot 2. // // - uint16_t (2 bytes) // The number of custom monsters in army slot 3. // // - uint16_t (2 bytes) // The number of custom monsters in army slot 4. // // - uint16_t (2 bytes) // The number of custom monsters in army slot 5. // // - uint8_t (1 byte) // Does the hero have a custom portrait? // // - uint8_t (1 byte) // Custom portrait ID. // // - uint8_t (1 byte) // Custom first artifact ID. // // - uint8_t (1 byte) // Custom second artifact ID. // // - uint8_t (1 byte) // Custom third artifact ID. // // - uint8_t (1 byte) // Unknown / unused. TODO: find out what this byte is for. // // - uint32_t (4 bytes) // Experience. // // - uint8_t (1 byte) // Does the hero have custom secondary skills? // // - uint8_t (1 byte) // Custom secondary skill type at slot 1. // // - uint8_t (1 byte) // Custom secondary skill type at slot 2. // // - uint8_t (1 byte) // Custom secondary skill type at slot 3. // // - uint8_t (1 byte) // Custom secondary skill type at slot 4. // // - uint8_t (1 byte) // Custom secondary skill type at slot 5. // // - uint8_t (1 byte) // Custom secondary skill type at slot 6. // // - uint8_t (1 byte) // Custom secondary skill type at slot 7. // // - uint8_t (1 byte) // Custom secondary skill type at slot 8. // // - uint8_t (1 byte) // Custom secondary skill level at slot 1. // // - uint8_t (1 byte) // Custom secondary skill level at slot 2. // // - uint8_t (1 byte) // Custom secondary skill level at slot 3. // // - uint8_t (1 byte) // Custom secondary skill level at slot 4. // // - uint8_t (1 byte) // Custom secondary skill level at slot 5. // // - uint8_t (1 byte) // Custom secondary skill level at slot 6. // // - uint8_t (1 byte) // Custom secondary skill level at slot 7. // // - uint8_t (1 byte) // Custom secondary skill level at slot 8. // // - uint8_t (1 byte) // Unknown / unused. TODO: find out what this byte is for. // // - uint8_t (1 byte) // Does the hero have a custom name? // // - string of 13 bytes // Null terminated string of custom hero name. // // - uint8_t (1 byte) // Is AI hero on patrol? // // - uint8_t (1 byte) // Patrol distance of this hero, if this is an AI hero placed on the map, or the race of this hero, if this hero is in Jail. // // - unused 15 bytes // Always zeros. modes = 0; if ( isInJail ) { SetModes( JAIL ); } SetIndex( mapIndex ); SetColor( colorType ); // The hero's race can be changed if the portrait that was specified using the map editor does not match the desired race of this hero, or if there are no free heroes // of the desired race if ( _race != raceType ) { SetModes( CUSTOM ); _race = raceType; } // // Campaign heroes have a non-standard amount of experience by default, so if they are used on the map, then we have to reset their properties to the default values // corresponding to their race. The same must be done if the hero's race has been changed. // // Clear the initial spell SpellBookDeactivate(); // Reset primary skills and initial spell to defaults HeroBase::LoadDefaults( HeroBase::HEROES, _race ); // Reset secondary skills to defaults secondary_skills = Skill::SecSkills( _race ); // Reset the army to default army.Reset( true ); StreamBuf dataStream( data ); // Skip first unused byte. dataStream.skip( 1 ); const bool doesHeroHaveCustomArmy = ( dataStream.get() != 0 ); if ( doesHeroHaveCustomArmy ) { Troop troops[5]; // Set monster types. for ( Troop & troop : troops ) { // Monster IDs in the MP2 format start from 0, while in the engine they start from 1 due to presence of UNKNOWN monster type. troop.SetMonster( dataStream.get() + 1 ); } // Set monster count. for ( Troop & troop : troops ) { troop.SetCount( dataStream.getLE16() ); } army.Assign( troops, std::end( troops ) ); // On some maps, customized heroes don't have an army, give them a minimal army if ( !army.isValid() ) { army.Reset( false ); } } else { dataStream.skip( 15 ); } const bool doesHeroHaveCustomPortrait = ( dataStream.get() != 0 ); if ( doesHeroHaveCustomPortrait ) { SetModes( CUSTOM ); // Portrait sprite index. In should be increased by 1 as in the original game hero IDs start from 0. portrait = dataStream.get() + 1; if ( !isValidId( portrait ) ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Invalid MP2 file format: incorrect custom portrait ID: " << portrait ) portrait = _id; } } else { dataStream.skip( 1 ); } const auto addInitialArtifact = [this]( const Artifact & art ) { // Perhaps the hero already has a spell book because of his race if ( art == Artifact::MAGIC_BOOK && HaveSpellBook() ) { return; } PickupArtifact( art ); }; // 3 artifacts. Artifact IDs are by value 1 bigger than in the original game. addInitialArtifact( Artifact( dataStream.get() + 1 ) ); addInitialArtifact( Artifact( dataStream.get() + 1 ) ); addInitialArtifact( Artifact( dataStream.get() + 1 ) ); // Skip unused byte. dataStream.skip( 1 ); // Get hero's experience. experience = dataStream.getLE32(); const bool doesHeroHaveCustomSecondarySkills = ( dataStream.get() != 0 ); if ( doesHeroHaveCustomSecondarySkills ) { SetModes( CUSTOM ); std::vector secs( 8 ); for ( Skill::Secondary & skill : secs ) { // Secondary Skill IDs in the MP2 format start from 0, while in the engine they start from 1 due to presence of UNKNOWN skill type. skill.SetSkill( dataStream.get() + 1 ); } for ( Skill::Secondary & skill : secs ) { skill.SetLevel( dataStream.get() ); } secondary_skills = Skill::SecSkills(); for ( const Skill::Secondary & skill : secs ) { if ( skill.isValid() ) { // The original map editor does not check presence of similar skills even those which have different levels. // We need to check whether the existing skill has a lower level before updating it. const auto * existingSkill = secondary_skills.FindSkill( skill.Skill() ); if ( existingSkill == nullptr || ( existingSkill->Level() < skill.Level() ) ) { secondary_skills.AddSkill( skill ); } } } } else { dataStream.skip( 16 ); } // Skip unused byte. dataStream.skip( 1 ); const bool doesHeroHaveCustomName = ( dataStream.get() != 0 ); if ( doesHeroHaveCustomName ) { // An empty name can be set in the original Editor which is wrong. std::string temp = dataStream.toString( 13 ); if ( !temp.empty() ) { SetModes( CUSTOM ); name = std::move( temp ); } } else { dataStream.skip( 13 ); } const bool doesAIHeroSetOnPatrol = ( dataStream.get() != 0 ); if ( doesAIHeroSetOnPatrol ) { SetModes( PATROL ); _patrolCenter = GetCenter(); _patrolDistance = dataStream.get(); } else { dataStream.skip( 1 ); } // TODO: remove this temporary assertion assert( _objectTypeUnderHero == MP2::OBJ_NONE ); // Level up if needed const int level = GetLevel(); if ( level > 1 ) { SetModes( CUSTOM ); for ( int i = 1; i < level; ++i ) { LevelUp( doesHeroHaveCustomSecondarySkills, true ); } } SetSpellPoints( GetMaxSpellPoints() ); move_point = GetMaxMovePoints(); if ( isControlAI() ) { AI::Get().HeroesPostLoad( *this ); } DEBUG_LOG( DBG_GAME, DBG_INFO, name << ", color: " << Color::String( GetColor() ) << ", race: " << Race::String( _race ) ) } int Heroes::GetRace() const { return _race; } const std::string & Heroes::GetName() const { return name; } int Heroes::GetColor() const { return ColorBase::GetColor(); } int Heroes::GetType() const { return HeroBase::HEROES; } const Army & Heroes::GetArmy() const { return army; } Army & Heroes::GetArmy() { return army; } int Heroes::GetMobilityIndexSprite() const { // valid range (0 - 25) int index = CanMove() ? ( move_point + 50 ) / 100 : 0; return 25 >= index ? index : 25; } int Heroes::GetManaIndexSprite() const { // Add 2 to round values. const int value = ( GetSpellPoints() + 2 ) / 5; return value >= 25 ? 25 : value; } int Heroes::getStatsValue() const { // experience and artifacts don't matter here, only natural stats return attack + defense + power + knowledge + secondary_skills.GetTotalLevel(); } double Heroes::getRecruitValue() const { return army.GetStrength() + ( ( bag_artifacts.getArtifactValue() * 10.0 + getStatsValue() ) * SKILL_VALUE ); } double Heroes::getMeetingValue( const Heroes & receivingHero ) const { // TODO: add logic to check artifacts with curses and those which are invaluable for a hero. // Magic Book is not transferable. const uint32_t artCount = bag_artifacts.CountArtifacts() - bag_artifacts.Count( Artifact::MAGIC_BOOK ); const uint32_t canFit = HEROESMAXARTIFACT - receivingHero.bag_artifacts.CountArtifacts(); double artifactValue = bag_artifacts.getArtifactValue() * 5.0; if ( artCount > canFit ) { artifactValue = canFit * ( artifactValue / artCount ); } // TODO: leaving only one monster in an army is very risky. Add logic to find out which part of the army would be useful to get. return receivingHero.army.getReinforcementValue( army ) + artifactValue * SKILL_VALUE; } int Heroes::GetAttack() const { return GetAttack( nullptr ); } int Heroes::GetAttack( std::string * strs ) const { int result = attack + GetAttackModificator( strs ); return result < 0 ? 0 : ( result > 255 ? 255 : result ); } int Heroes::GetDefense() const { return GetDefense( nullptr ); } int Heroes::GetDefense( std::string * strs ) const { int result = defense + GetDefenseModificator( strs ); return result < 0 ? 0 : ( result > 255 ? 255 : result ); } int Heroes::GetPower() const { return GetPower( nullptr ); } int Heroes::GetPower( std::string * strs ) const { const int result = power + GetPowerModificator( strs ); return result < 1 ? 1 : ( result > 255 ? 255 : result ); } int Heroes::GetKnowledge() const { return GetKnowledge( nullptr ); } int Heroes::GetKnowledge( std::string * strs ) const { int result = knowledge + GetKnowledgeModificator( strs ); return result < 0 ? 0 : ( result > 255 ? 255 : result ); } void Heroes::IncreasePrimarySkill( int skill ) { switch ( skill ) { case Skill::Primary::ATTACK: ++attack; break; case Skill::Primary::DEFENSE: ++defense; break; case Skill::Primary::POWER: ++power; break; case Skill::Primary::KNOWLEDGE: ++knowledge; break; default: break; } } uint32_t Heroes::GetMaxSpellPoints() const { return 10 * GetKnowledge(); } uint32_t Heroes::GetMaxMovePoints() const { return GetMaxMovePoints( isShipMaster() ); } uint32_t Heroes::GetMaxMovePoints( const bool onWater ) const { uint32_t result = 0; if ( onWater ) { // Initial mobility on water does not depend on the composition of the army result = 1500; // Influence of Navigation skill result = UpdateMovementPoints( result, Skill::Secondary::NAVIGATION ); // Artifact bonuses result += GetBagArtifacts().getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::SEA_MOBILITY ); // Bonuses from captured lighthouses result += 500 * world.CountCapturedObject( MP2::OBJ_LIGHTHOUSE, GetColor() ); } else { // Initial mobility on land depends on the speed of the slowest army unit const Troop * troop = army.GetSlowestTroop(); if ( troop ) { switch ( troop->GetSpeed() ) { case Speed::VERYSLOW: result = 1000; break; case Speed::SLOW: result = 1100; break; case Speed::AVERAGE: result = 1200; break; case Speed::FAST: result = 1300; break; case Speed::VERYFAST: result = 1400; break; case Speed::ULTRAFAST: result = 1500; break; default: assert( 0 ); break; } } // Influence of Logistics skill result = UpdateMovementPoints( result, Skill::Secondary::LOGISTICS ); // Artifact bonuses result += GetBagArtifacts().getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::LAND_MOBILITY ); // Bonuses from visited objects if ( isObjectTypeVisited( MP2::OBJ_STABLES ) ) { result += GameStatic::getMovementPointBonus( MP2::OBJ_STABLES ); } } // AI-controlled heroes receive additional movement bonus depending on the game difficulty if ( isControlAI() ) { result += Difficulty::GetHeroMovementBonusForAI( Game::getDifficulty() ); } return result; } int Heroes::GetMorale() const { return GetMoraleWithModificators( nullptr ); } int Heroes::GetMoraleWithModificators( std::string * strs ) const { int result = Morale::NORMAL; // bonus leadership result += Skill::GetLeadershipModifiers( GetLevelSkill( Skill::Secondary::LEADERSHIP ), strs ); // object visited const std::vector objectTypes{ MP2::OBJ_BUOY, MP2::OBJ_OASIS, MP2::OBJ_WATERING_HOLE, MP2::OBJ_TEMPLE, MP2::OBJ_GRAVEYARD, MP2::OBJ_DERELICT_SHIP, MP2::OBJ_SHIPWRECK }; result += ObjectVisitedModifiersResult( objectTypes, *this, strs ); // bonus artifact result += GetMoraleModificator( strs ); // A special artifact ability presence must be the last check. const Artifact maxMoraleArtifact = bag_artifacts.getFirstArtifactWithBonus( fheroes2::ArtifactBonusType::MAXIMUM_MORALE ); if ( maxMoraleArtifact.isValid() ) { if ( strs != nullptr ) { *strs += maxMoraleArtifact.GetName(); *strs += _( " gives you maximum morale" ); } result = Morale::BLOOD; } else if ( strs != nullptr && !strs->empty() && strs->back() == '\n' ) { // Remove the possible empty line at the end of the string. strs->pop_back(); } return Morale::Normalize( result ); } int Heroes::GetLuck() const { return GetLuckWithModificators( nullptr ); } int Heroes::GetLuckWithModificators( std::string * strs ) const { int result = Luck::NORMAL; // bonus luck result += Skill::GetLuckModifiers( GetLevelSkill( Skill::Secondary::LUCK ), strs ); // object visited const std::vector objectTypes{ MP2::OBJ_MERMAID, MP2::OBJ_FAERIE_RING, MP2::OBJ_FOUNTAIN, MP2::OBJ_IDOL, MP2::OBJ_PYRAMID }; result += ObjectVisitedModifiersResult( objectTypes, *this, strs ); // bonus artifact result += GetLuckModificator( strs ); const Artifact maxLuckArtifact = bag_artifacts.getFirstArtifactWithBonus( fheroes2::ArtifactBonusType::MAXIMUM_LUCK ); if ( maxLuckArtifact.isValid() ) { if ( strs != nullptr ) { *strs += maxLuckArtifact.GetName(); *strs += _( " gives you maximum luck" ); } result = Luck::IRISH; } else if ( strs != nullptr && !strs->empty() && strs->back() == '\n' ) { // Remove the possible empty line at the end of the string. strs->pop_back(); } return Luck::Normalize( result ); } bool Heroes::Recruit( const int col, const fheroes2::Point & pt ) { if ( GetColor() != Color::NONE ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "hero has already been hired by some kingdom" ) return false; } Kingdom & kingdom = world.GetKingdom( col ); if ( !kingdom.AllowRecruitHero( false ) ) { return false; } ResetModes( RECRUIT ); ResetModes( JAIL ); SetColor( col ); SetCenter( pt ); setDirection( Direction::RIGHT ); if ( !Modes( SAVEMP ) ) { move_point = GetMaxMovePoints(); } if ( !army.isValid() ) { army.Reset( false ); } world.GetTiles( pt.x, pt.y ).setHero( this ); kingdom.AddHero( this ); // Update the set of recruits in the kingdom kingdom.GetRecruits(); // After recruiting a hero we reveal map in hero scout area. Scout( GetIndex() ); if ( isControlHuman() ) { // And the radar image map for human player. ScoutRadar(); } return true; } bool Heroes::Recruit( const Castle & castle ) { if ( !Recruit( castle.GetColor(), castle.GetCenter() ) ) { return false; } if ( castle.GetLevelMageGuild() ) { castle.MageGuildEducateHero( *this ); } SetVisited( GetIndex() ); return true; } void Heroes::ActionNewDay() { move_point = GetMaxMovePoints(); if ( world.CountDay() > 1 ) { ReplenishSpellPoints(); } visit_object.remove_if( Visit::isDayLife ); ResetModes( SAVEMP ); } void Heroes::ActionNewWeek() { visit_object.remove_if( Visit::isWeekLife ); } void Heroes::ActionNewMonth() { visit_object.remove_if( Visit::isMonthLife ); } void Heroes::ActionAfterBattle() { visit_object.remove_if( Visit::isBattleLife ); SetModes( ACTION ); } uint32_t Heroes::getDailyRestoredSpellPoints() const { uint32_t points = GameStatic::GetHeroesRestoreSpellPointsPerDay(); // Spell points from artifacts. points += static_cast( GetBagArtifacts().getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::SPELL_POINTS_DAILY_GENERATION ) ); points += GetSecondaryValues( Skill::Secondary::MYSTICISM ); return points; } void Heroes::ReplenishSpellPoints() { const uint32_t maxp = GetMaxSpellPoints(); uint32_t curr = GetSpellPoints(); // spell points may be doubled in artesian spring, leave as is if ( curr >= maxp ) { return; } const Castle * castle = inCastle(); // in castle? if ( castle && castle->GetLevelMageGuild() ) { SetSpellPoints( maxp ); } else { curr += getDailyRestoredSpellPoints(); SetSpellPoints( std::min( curr, maxp ) ); } } void Heroes::calculatePath( int32_t dstIdx ) { if ( dstIdx < 0 ) { // Recalculating an existing path dstIdx = path.GetDestinationIndex(); } if ( !path.isValidForMovement() ) { path.Reset(); } if ( dstIdx < 0 ) { return; } path.setPath( world.getPath( *this, dstIdx ) ); if ( !path.isValidForMovement() ) { path.Reset(); } } const Castle * Heroes::inCastle() const { return inCastleMutable(); } Castle * Heroes::inCastleMutable() const { if ( GetColor() == Color::NONE ) { return nullptr; } Castle * castle = world.getCastleEntrance( GetCenter() ); return castle && castle->GetHero() == this ? castle : nullptr; } bool Heroes::isVisited( const Maps::Tiles & tile, Visit::type_t type ) const { const int32_t index = tile.GetIndex(); const MP2::MapObjectType objectType = tile.GetObject( false ); if ( Visit::GLOBAL == type ) { return GetKingdom().isVisited( index, objectType ); } return visit_object.end() != std::find( visit_object.begin(), visit_object.end(), IndexObject( index, objectType ) ); } bool Heroes::isObjectTypeVisited( const MP2::MapObjectType objectType, Visit::type_t type ) const { if ( Visit::GLOBAL == type ) { return GetKingdom().isVisited( objectType ); } return std::any_of( visit_object.begin(), visit_object.end(), [objectType]( const IndexObject & v ) { return v.isObject( objectType ); } ); } void Heroes::SetVisited( int32_t index, Visit::type_t type ) { const Maps::Tiles & tile = world.GetTiles( index ); const MP2::MapObjectType objectType = tile.GetObject( false ); if ( Visit::GLOBAL == type ) { GetKingdom().SetVisited( index, objectType ); } else if ( !isVisited( tile ) && MP2::OBJ_NONE != objectType ) { visit_object.emplace_front( index, objectType ); } } void Heroes::setVisitedForAllies( const int32_t tileIndex ) const { const Maps::Tiles & tile = world.GetTiles( tileIndex ); const MP2::MapObjectType objectType = tile.GetObject( false ); // Set visited to all allies as well. const Colors friendColors( Players::GetPlayerFriends( GetColor() ) ); for ( const int friendColor : friendColors ) { world.GetKingdom( friendColor ).SetVisited( tileIndex, objectType ); } } void Heroes::SetVisitedWideTile( int32_t index, const MP2::MapObjectType objectType, Visit::type_t type ) { const Maps::Tiles & tile = world.GetTiles( index ); const uint32_t uid = tile.GetObjectUID(); int wide = 0; switch ( objectType ) { case MP2::OBJ_SKELETON: case MP2::OBJ_OASIS: case MP2::OBJ_STANDING_STONES: case MP2::OBJ_ARTESIAN_SPRING: wide = 2; break; case MP2::OBJ_WATERING_HOLE: wide = 4; break; default: break; } if ( tile.GetObject( false ) == objectType && wide ) { for ( int32_t ii = tile.GetIndex() - ( wide - 1 ); ii <= tile.GetIndex() + ( wide - 1 ); ++ii ) if ( Maps::isValidAbsIndex( ii ) && world.GetTiles( ii ).GetObjectUID() == uid ) SetVisited( ii, type ); } } void Heroes::markHeroMeeting( int heroID ) { if ( isValidId( heroID ) && !hasMetWithHero( heroID ) ) { visit_object.emplace_front( heroID, MP2::OBJ_HERO ); } } void Heroes::unmarkHeroMeeting() { const VecHeroes & heroes = GetKingdom().GetHeroes(); for ( Heroes * hero : heroes ) { if ( hero == nullptr || hero == this ) { continue; } hero->visit_object.remove( IndexObject( _id, MP2::OBJ_HERO ) ); visit_object.remove( IndexObject( hero->_id, MP2::OBJ_HERO ) ); } } bool Heroes::hasMetWithHero( int heroID ) const { return visit_object.end() != std::find( visit_object.begin(), visit_object.end(), IndexObject( heroID, MP2::OBJ_HERO ) ); } bool Heroes::isLosingGame() const { return GetKingdom().isLosingGame(); } bool Heroes::isAction() const { return Modes( ACTION ); } void Heroes::ResetAction() { ResetModes( ACTION ); } bool Heroes::PickupArtifact( const Artifact & art ) { if ( !art.isValid() ) { return false; } if ( !bag_artifacts.PushArtifact( art ) ) { if ( isControlHuman() ) { art.GetID() == Artifact::MAGIC_BOOK ? fheroes2::showStandardTextMessage( GetName(), _( "You must purchase a spell book to use the mage guild, but you currently have no room for a spell book. Try giving one of your artifacts to another hero." ), Dialog::OK ) : fheroes2::showStandardTextMessage( art.GetName(), _( "You cannot pick up this artifact, you already have a full load!" ), Dialog::OK ); } return false; } const auto assembledArtifacts = bag_artifacts.assembleArtifactSetIfPossible(); if ( isControlHuman() ) { std::for_each( assembledArtifacts.begin(), assembledArtifacts.end(), Dialog::ArtifactSetAssembled ); } // If the hero is in jail and gets an artifact assigned using the map editor, then there is no need to scout the area if ( Modes( JAIL ) ) { return true; } const auto scout = [this]( const int32_t artifactID ) { const std::vector & bonuses = fheroes2::getArtifactData( artifactID ).bonuses; if ( std::find( bonuses.begin(), bonuses.end(), fheroes2::ArtifactBonus( fheroes2::ArtifactBonusType::AREA_REVEAL_DISTANCE ) ) == bonuses.end() ) { return false; } Scout( GetIndex() ); if ( isControlHuman() ) { ScoutRadar(); } return true; }; // Check the picked up artifact for a bonus to the scouting area. if ( scout( art.GetID() ) ) { return true; } // If there were artifacts assembled, check them for a bonus to the scouting area. for ( const ArtifactSetData & assembledArtifact : assembledArtifacts ) { if ( scout( assembledArtifact._assembledArtifactID ) ) { return true; } } return true; } void Heroes::IncreaseExperience( const uint32_t amount, const bool autoselect /* = false */ ) { int oldLevel = GetLevelFromExperience( experience ); int newLevel = GetLevelFromExperience( experience + amount ); const uint32_t updatedExperience = experience + amount; for ( int level = oldLevel; level < newLevel - 1; ++level ) { experience = GetExperienceFromLevel( level ); LevelUp( false, autoselect ); } experience = updatedExperience; if ( newLevel > oldLevel ) { LevelUp( false, autoselect ); } } int Heroes::GetLevelFromExperience( uint32_t exp ) { for ( int lvl = 1; lvl < 255; ++lvl ) if ( exp < GetExperienceFromLevel( lvl ) ) return lvl; return 0; } uint32_t Heroes::GetExperienceFromLevel( int lvl ) { switch ( lvl ) { case 0: return 0; case 1: return 1000; case 2: return 2000; case 3: return 3200; case 4: return 4500; case 5: return 6000; case 6: return 7700; case 7: return 9000; case 8: return 11000; case 9: return 13200; case 10: return 15500; case 11: return 18500; case 12: return 22100; case 13: return 26400; case 14: return 31600; case 15: return 37800; case 16: return 45300; case 17: return 54200; case 18: return 65000; case 19: return 78000; case 20: return 93600; case 21: return 112300; case 22: return 134700; case 23: return 161600; case 24: return 193900; case 25: return 232700; case 26: return 279300; case 27: return 335200; case 28: return 402300; case 29: return 482800; case 30: return 579400; case 31: return 695300; case 32: return 834400; case 33: return 1001300; case 34: return 1201600; case 35: return 1442000; case 36: return 1730500; case 37: return 2076700; case 38: return 2492100; case 39: return 2990600; default: break; } const uint32_t l1 = GetExperienceFromLevel( lvl - 1 ); return ( l1 + static_cast( round( ( l1 - GetExperienceFromLevel( lvl - 2 ) ) * 1.2 / 100 ) * 100 ) ); } bool Heroes::BuySpellBook( const Castle * castle ) { if ( HaveSpellBook() || Color::NONE == GetColor() ) { return false; } const Funds payment = PaymentConditions::BuySpellBook(); Kingdom & kingdom = GetKingdom(); std::string header = _( "To cast spells, you must first buy a spell book for %{gold} gold." ); StringReplace( header, "%{gold}", payment.gold ); if ( !kingdom.AllowPayment( payment ) ) { if ( isControlHuman() ) { header.append( " " ); header.append( _( "Unfortunately, you seem to be a little short of cash at the moment." ) ); const fheroes2::ArtifactDialogElement artifactUI( Artifact::MAGIC_BOOK ); fheroes2::showMessage( fheroes2::Text( GetName(), fheroes2::FontType::normalYellow() ), fheroes2::Text( header, fheroes2::FontType::normalWhite() ), Dialog::OK, { &artifactUI } ); } return false; } if ( isControlHuman() ) { header.append( " " ); header.append( _( "Do you wish to buy one?" ) ); const fheroes2::ArtifactDialogElement artifactUI( Artifact::MAGIC_BOOK ); if ( fheroes2::showMessage( fheroes2::Text( GetName(), fheroes2::FontType::normalYellow() ), fheroes2::Text( header, fheroes2::FontType::normalWhite() ), Dialog::YES | Dialog::NO, { &artifactUI } ) == Dialog::NO ) { return false; } } if ( SpellBookActivate() ) { kingdom.OddFundsResource( payment ); if ( castle ) { castle->MageGuildEducateHero( *this ); } return true; } return false; } bool Heroes::isMoveEnabled() const { return Modes( ENABLEMOVE ) && path.isValidForMovement() && path.hasAllowedSteps(); } bool Heroes::CanMove() const { const Maps::Tiles & tile = world.GetTiles( GetIndex() ); return move_point >= ( tile.isRoad() ? Maps::Ground::roadPenalty : Maps::Ground::GetPenalty( tile, GetLevelSkill( Skill::Secondary::PATHFINDING ) ) ); } void Heroes::SetMove( const bool enable ) { if ( enable ) { if ( Modes( ENABLEMOVE ) ) { return; } ResetModes( SLEEPER ); if ( isControlAI() ) { AI::Get().HeroesBeginMovement( *this ); } SetModes( ENABLEMOVE ); } else { if ( !Modes( ENABLEMOVE ) ) { return; } ResetModes( ENABLEMOVE ); if ( isControlAI() ) { AI::Get().HeroesFinishMovement( *this ); } // Reset the hero sprite switch ( direction ) { case Direction::TOP: sprite_index = 0; break; case Direction::BOTTOM: sprite_index = 36; break; case Direction::TOP_RIGHT: case Direction::TOP_LEFT: sprite_index = 9; break; case Direction::BOTTOM_RIGHT: case Direction::BOTTOM_LEFT: sprite_index = 27; break; case Direction::RIGHT: case Direction::LEFT: sprite_index = 18; break; default: break; } } } bool Heroes::isShipMaster() const { return Modes( SHIPMASTER ); } void Heroes::SetShipMaster( bool f ) { f ? SetModes( SHIPMASTER ) : ResetModes( SHIPMASTER ); } bool Heroes::HasSecondarySkill( int skill ) const { return Skill::Level::NONE != secondary_skills.GetLevel( skill ); } uint32_t Heroes::GetSecondaryValues( int skill ) const { return secondary_skills.GetValues( skill ); } bool Heroes::HasMaxSecondarySkill() const { return HEROESMAXSKILL <= secondary_skills.Count(); } int Heroes::GetLevelSkill( int skill ) const { return secondary_skills.GetLevel( skill ); } void Heroes::LearnSkill( const Skill::Secondary & skill ) { if ( skill.isValid() ) secondary_skills.AddSkill( skill ); } void Heroes::Scout( const int tileIndex ) const { // We should not scout for the NONE color player. assert( GetColor() != Color::NONE ); Maps::ClearFog( tileIndex, GetScoutingDistance(), GetColor() ); #if defined( WITH_DEBUG ) const Player * player = Players::Get( GetColor() ); assert( player != nullptr ); // If player gave control to AI we need to update the radar image after every 'ClearFog()' call as in this mode we don't // do any optimizations. if ( player->isAIAutoControlMode() ) { // We redraw the radar map fully as there is no need to make a code for rendering optimizations for AI debug tracking. // As AI don't waste time for thinking between hero moves we don't need to force radar update in other places. ScoutRadar(); } #endif } int Heroes::GetScoutingDistance() const { return static_cast( GetBagArtifacts().getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::AREA_REVEAL_DISTANCE ) + GameStatic::getFogDiscoveryDistance( GameStatic::FogDiscoveryType::HEROES ) + GetSecondaryValues( Skill::Secondary::SCOUTING ) ); } fheroes2::Rect Heroes::GetScoutRoi() const { const int32_t scoutRange = GetScoutingDistance(); const fheroes2::Point heroPosition = GetCenter(); return { heroPosition.x - scoutRange, heroPosition.y - scoutRange, 2 * scoutRange + 1, 2 * scoutRange + 1 }; } uint32_t Heroes::UpdateMovementPoints( const uint32_t movePoints, const int skill ) const { const int level = GetLevelSkill( skill ); if ( level == Skill::Level::NONE ) return movePoints; const uint32_t skillValue = GetSecondaryValues( skill ); if ( skillValue == 33 ) { return movePoints * 4 / 3; } else if ( skillValue == 66 ) { return movePoints * 5 / 3; } return movePoints + skillValue * movePoints / 100; } uint32_t Heroes::GetVisionsDistance() const { return 8; } int Heroes::getNumOfTravelDays( int32_t dstIdx ) const { assert( Maps::isValidAbsIndex( dstIdx ) ); const uint32_t maxMovePoints = GetMaxMovePoints(); const std::list routePath = world.getPath( *this, dstIdx ); if ( routePath.empty() ) { return 0; } uint32_t movePoints = GetMovePoints(); int days = 1; for ( const Route::Step & step : routePath ) { const uint32_t stepPenalty = step.GetPenalty(); if ( movePoints >= stepPenalty ) { // This movement takes place on the same day movePoints -= stepPenalty; } else { // This movement takes place at the beginning of a new day: start with max // movement points, don't carry leftovers from the previous day assert( maxMovePoints >= stepPenalty ); movePoints = maxMovePoints - stepPenalty; ++days; // Stop at 8 days if ( days >= 8 ) { break; } } } // Return no more than 8 days assert( days <= 8 ); return days; } void Heroes::LevelUp( bool skipsecondary, bool autoselect ) { const HeroSeedsForLevelUp seeds = GetSeedsForLevelUp(); // level up primary skill const int primarySkill = Skill::Primary::LevelUp( _race, GetLevel(), seeds.seedPrimarySkill ); DEBUG_LOG( DBG_GAME, DBG_INFO, "for " << GetName() << ", up " << Skill::Primary::String( primarySkill ) ) if ( !skipsecondary ) LevelUpSecondarySkill( seeds, primarySkill, ( autoselect || isControlAI() ) ); if ( isControlAI() ) AI::Get().HeroesLevelUp( *this ); } void Heroes::LevelUpSecondarySkill( const HeroSeedsForLevelUp & seeds, int primary, bool autoselect ) { const auto [sec1, sec2] = secondary_skills.FindSkillsForLevelUp( _race, seeds.seedSecondarySkill1, seeds.seedSecondarySkill2 ); if ( sec1.isValid() && sec2.isValid() ) { DEBUG_LOG( DBG_GAME, DBG_INFO, GetName() << " select " << Skill::Secondary::String( sec1.Skill() ) << " or " << Skill::Secondary::String( sec2.Skill() ) ) } else if ( sec1.isValid() ) { DEBUG_LOG( DBG_GAME, DBG_INFO, GetName() << " select " << Skill::Secondary::String( sec1.Skill() ) ) } else if ( sec2.isValid() ) { DEBUG_LOG( DBG_GAME, DBG_INFO, GetName() << " select " << Skill::Secondary::String( sec2.Skill() ) ) } Skill::Secondary selected; if ( autoselect ) { if ( sec1.isValid() && sec2.isValid() ) { selected = Rand::GetWithSeed( 0, 1, seeds.seedSecondarySkillRandomChoose ) ? sec1 : sec2; } else { selected = sec1.isValid() ? sec1 : sec2; } } else { AudioManager::PlaySound( M82::NWHEROLV ); const int result = Dialog::LevelUpSelectSkill( name, primary, sec1, sec2, *this ); if ( Skill::Secondary::UNKNOWN != result ) { selected = ( result == sec2.Skill() ) ? sec2 : sec1; } } if ( selected.isValid() ) { DEBUG_LOG( DBG_GAME, DBG_INFO, GetName() << ", selected: " << Skill::Secondary::String( selected.Skill() ) ) Skill::Secondary * secs = secondary_skills.FindSkill( selected.Skill() ); if ( secs ) { secs->NextLevel(); } else { secondary_skills.AddSkill( Skill::Secondary( selected.Skill(), Skill::Level::BASIC ) ); } // Campaign-only heroes get additional experience immediately upon their creation, even while still neutral. // We should not try to scout the area around such heroes. if ( selected.Skill() == Skill::Secondary::SCOUTING && GetColor() != Color::NONE ) { Scout( GetIndex() ); if ( isControlHuman() ) { ScoutRadar(); } } } } void Heroes::ApplyPenaltyMovement( uint32_t penalty ) { if ( move_point >= penalty ) move_point -= penalty; else move_point = 0; } bool Heroes::MayStillMove( const bool ignorePath, const bool ignoreSleeper ) const { if ( !isActive() ) { return false; } if ( !ignoreSleeper && Modes( SLEEPER ) ) { return false; } if ( path.isValidForMovement() && !ignorePath ) { return path.hasAllowedSteps(); } return CanMove(); } bool Heroes::MayCastAdventureSpells() const { return isValid() && GetColor() != Color::NONE; } bool Heroes::isValid() const { return isValidId( _id ); } bool Heroes::isActive() const { return isValid() && ( GetColor() & Color::ALL ) && !Modes( JAIL ); } bool Heroes::isAvailableForHire() const { return isValid() && GetColor() == Color::NONE && !Modes( JAIL ); } void Heroes::Dismiss( int reason ) { if ( isAvailableForHire() ) { return; } // if not surrendering, reset army if ( ( reason & Battle::RESULT_SURRENDER ) == 0 ) { army.Reset( true ); } const int heroColor = GetColor(); Kingdom & kingdom = GetKingdom(); if ( heroColor != Color::NONE ) { kingdom.RemoveHero( this ); } SetColor( Color::NONE ); world.GetTiles( GetIndex() ).setHero( nullptr ); SetIndex( -1 ); modes = 0; path.Hide(); path.Reset(); SetMove( false ); SetModes( ACTION ); if ( ( Battle::RESULT_RETREAT | Battle::RESULT_SURRENDER ) & reason ) { SetModes( SAVEMP ); if ( heroColor != Color::NONE ) { kingdom.appendSurrenderedHero( *this ); } } } int Heroes::GetControl() const { return GetKingdom().GetControl(); } uint32_t Heroes::GetStartingXp() { return Rand::Get( 40, 90 ); } void Heroes::ActionPreBattle() { spell_book.resetState(); } void Heroes::ActionNewPosition( const bool allowMonsterAttack ) { if ( allowMonsterAttack ) { // scan for monsters around const MapsIndexes targets = Maps::getMonstersProtectingTile( GetIndex(), false ); if ( !targets.empty() ) { SetMove( false ); ShowPath( false ); // first fight the monsters on the destination tile (if any) MapsIndexes::const_iterator it = std::find( targets.begin(), targets.end(), GetPath().GetDestinationIndex() ); if ( it != targets.end() ) { Action( *it ); } // otherwise fight the monsters on the first adjacent tile else { Action( targets.front() ); } } } if ( isControlAI() ) AI::Get().HeroesActionNewPosition( *this ); ResetModes( VISIONS ); } void Heroes::Move2Dest( const int32_t dstIndex ) { const int32_t currentIndex = GetIndex(); if ( dstIndex == currentIndex ) { return; } world.GetTiles( currentIndex ).setHero( nullptr ); SetIndex( dstIndex ); world.GetTiles( dstIndex ).setHero( this ); } const fheroes2::Sprite & Heroes::GetPortrait( int id, int type ) { if ( isValidId( id ) ) switch ( type ) { case PORT_BIG: return fheroes2::AGG::GetICN( ICN::PORTxxxx( id ), 0 ); case PORT_MEDIUM: { // Original ICN::PORTMEDI sprites are badly rendered. Instead of them we're getting high quality ICN:PORT00xx file and resize it to a smaller image. // TODO: find a better way to store these images, ideally in agg_image.cpp file. static std::map mediumSizePortrait; auto iter = mediumSizePortrait.find( id ); if ( iter != mediumSizePortrait.end() ) { return iter->second; } const fheroes2::Sprite & original = fheroes2::AGG::GetICN( ICN::PORTxxxx( id ), 0 ); fheroes2::Sprite output( 50, 47 ); fheroes2::Resize( original, output ); return mediumSizePortrait.try_emplace( id, std::move( output ) ).first->second; } case PORT_SMALL: if ( id == Heroes::DEBUG_HERO ) { return fheroes2::AGG::GetICN( ICN::MINIPORT, BRAX - 1 ); } // Since hero IDs start from 1 we have to deduct 1 from the ID. return fheroes2::AGG::GetICN( ICN::MINIPORT, id - 1 ); default: break; } return fheroes2::AGG::GetICN( ICN::UNKNOWN, 0 ); } void Heroes::PortraitRedraw( const int32_t px, const int32_t py, const PortraitType type, fheroes2::Image & dstsf ) const { const fheroes2::Sprite & port = GetPortrait( portrait, type ); fheroes2::Point mp; if ( !port.empty() ) { if ( PORT_BIG == type ) { fheroes2::Blit( port, dstsf, px, py ); mp.y = 2; mp.x = port.width() - 12; } else if ( PORT_MEDIUM == type ) { fheroes2::Blit( port, dstsf, px, py ); mp.x = port.width() - 10; } else if ( PORT_SMALL == type ) { const fheroes2::Sprite & background = fheroes2::AGG::GetICN( ICN::PORTXTRA, 0 ); const fheroes2::Sprite & mobility = fheroes2::AGG::GetICN( ICN::MOBILITY, GetMobilityIndexSprite() ); const fheroes2::Sprite & mana = fheroes2::AGG::GetICN( ICN::MANA, GetManaIndexSprite() ); const int barw = 7; // Draw background. fheroes2::Blit( background, dstsf, px, py ); // Draw mobility. fheroes2::Blit( mobility, dstsf, px, py + mobility.y() ); // Draw hero's portrait. fheroes2::Blit( port, dstsf, px + barw + 1, py ); // Draw mana. fheroes2::Blit( mana, dstsf, px + barw + port.width() + 2, py + mana.y() ); mp.x = 35; } } if ( isControlAI() ) { // AI heroes should not have any UI indicators for their statuses. return; } if ( Modes( Heroes::SLEEPER ) ) { const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::MISC4, 14 ); fheroes2::Image sleeperBG( sprite.width() - 4, sprite.height() - 4 ); sleeperBG.fill( 0 ); fheroes2::Blit( sleeperBG, dstsf, px + mp.x + 3, py + mp.y - 1 ); fheroes2::Blit( sprite, dstsf, px + mp.x + 1, py + mp.y - 3 ); } } std::string Heroes::String() const { std::ostringstream os; os << "name : " << name << " (" << Race::String( _race ) << ")" << std::endl << "color : " << Color::String( GetColor() ) << std::endl << "experience : " << experience << std::endl << "level : " << GetLevel() << std::endl << "magic points : " << GetSpellPoints() << " / " << GetMaxSpellPoints() << std::endl << "position x, y : " << GetCenter().x << ", " << GetCenter().y << std::endl << "move points : " << move_point << " / " << GetMaxMovePoints() << std::endl << "direction : " << Direction::String( direction ) << std::endl << "index sprite : " << sprite_index << std::endl << "in castle : " << ( inCastle() ? "true" : "false" ) << std::endl << "save object : " << MP2::StringObject( world.GetTiles( GetIndex() ).GetObject( false ) ) << std::endl << "flags : " << ( Modes( SHIPMASTER ) ? "SHIPMASTER," : "" ) << ( Modes( CUSTOM ) ? "CUSTOM," : "" ) << ( Modes( PATROL ) ? "PATROL" : "" ) << std::endl; if ( Modes( PATROL ) ) { os << "patrol zone : center: (" << _patrolCenter.x << ", " << _patrolCenter.y << "), distance " << _patrolDistance << std::endl; } if ( !visit_object.empty() ) { os << "visit objects : "; for ( std::list::const_iterator it = visit_object.begin(); it != visit_object.end(); ++it ) os << MP2::StringObject( static_cast( ( *it ).second ) ) << "(" << ( *it ).first << "), "; os << std::endl; } if ( isControlAI() ) { os << "skills : " << secondary_skills.String() << std::endl << "artifacts : " << bag_artifacts.String() << std::endl << "spell book : " << ( HaveSpellBook() ? spell_book.String() : "disabled" ) << std::endl << "army dump : " << army.String() << std::endl << "ai role : " << GetHeroRoleString( *this ) << std::endl; os << AI::Get().HeroesString( *this ); } return os.str(); } Heroes::HeroSeedsForLevelUp Heroes::GetSeedsForLevelUp() const { /* We generate seeds based on the hero and global world map seed * The idea is that, we want the skill selection to be randomized at each map restart, * but deterministic for a given hero. * We also want the available skills to change depending on current skills/stats of the hero, * to avoid giving out the same skills/stats at each level up. We can't use the level field for this, as it * doesn't change when we level up several levels at once. * We also need to generate different seeds for each possible call to the random number generator, * in order to avoid always drawing the same random number at level-up: otherwise this * would mean that for all possible games, the 2nd secondary * skill would always be the same once the 1st one is selected. * */ uint32_t hash = world.GetMapSeed(); fheroes2::hashCombine( hash, _id ); fheroes2::hashCombine( hash, _race ); fheroes2::hashCombine( hash, attack ); fheroes2::hashCombine( hash, defense ); fheroes2::hashCombine( hash, power ); fheroes2::hashCombine( hash, knowledge ); for ( int skillId = Skill::Secondary::PATHFINDING; skillId <= Skill::Secondary::ESTATES; ++skillId ) { fheroes2::hashCombine( hash, GetLevelSkill( skillId ) ); } HeroSeedsForLevelUp seeds; seeds.seedPrimarySkill = hash; seeds.seedSecondarySkill1 = hash + 1; seeds.seedSecondarySkill2 = hash + 2; seeds.seedSecondarySkillRandomChoose = hash + 3; return seeds; } double Heroes::getAIMinimumJoiningArmyStrength() const { assert( isControlAI() ); double strengthThreshold = 0.05; switch ( getAIRole() ) { case Heroes::Role::SCOUT: strengthThreshold = 0.01; break; case Heroes::Role::COURIER: strengthThreshold = 0.015; break; case Heroes::Role::HUNTER: strengthThreshold = 0.02; break; case Heroes::Role::FIGHTER: strengthThreshold = 0.025; break; case Heroes::Role::CHAMPION: strengthThreshold = 0.03; break; default: // Did you add a new AI hero role? Add the logic above! assert( 0 ); break; } return strengthThreshold * Troops( GetArmy().getTroops() ).GetStrength(); } AllHeroes::~AllHeroes() { clear(); } void AllHeroes::Init() { clear(); reserve( Heroes::HEROES_COUNT ); push_back( new Heroes( Heroes::UNKNOWN, Race::KNGT ) ); for ( const int race : std::array{ Race::KNGT, Race::BARB, Race::SORC, Race::WRLK, Race::WZRD, Race::NECR } ) { const auto [minHeroId, maxHeroId] = getHeroIdRangeForRace( race ); assert( minHeroId <= maxHeroId ); for ( int hid = minHeroId; hid <= maxHeroId; ++hid ) { push_back( new Heroes( hid, race ) ); } } // SW campaign push_back( new Heroes( Heroes::ROLAND, Race::WZRD, 5000 ) ); push_back( new Heroes( Heroes::CORLAGON, Race::KNGT, 5000 ) ); push_back( new Heroes( Heroes::ELIZA, Race::SORC, 5000 ) ); push_back( new Heroes( Heroes::ARCHIBALD, Race::WRLK, 5000 ) ); push_back( new Heroes( Heroes::HALTON, Race::KNGT, 5000 ) ); push_back( new Heroes( Heroes::BRAX, Race::NECR, 5000 ) ); // PoL const GameVersion version = Settings::Get().getCurrentMapInfo().version; if ( version == GameVersion::PRICE_OF_LOYALTY || version == GameVersion::RESURRECTION ) { push_back( new Heroes( Heroes::SOLMYR, Race::WZRD, 5000 ) ); push_back( new Heroes( Heroes::DAINWIN, Race::WRLK, 5000 ) ); push_back( new Heroes( Heroes::MOG, Race::NECR, 5000 ) ); push_back( new Heroes( Heroes::UNCLEIVAN, Race::BARB, 5000 ) ); push_back( new Heroes( Heroes::JOSEPH, Race::WZRD, 5000 ) ); push_back( new Heroes( Heroes::GALLAVANT, Race::KNGT, 5000 ) ); push_back( new Heroes( Heroes::ELDERIAN, Race::WRLK, 5000 ) ); push_back( new Heroes( Heroes::CEALLACH, Race::KNGT, 5000 ) ); push_back( new Heroes( Heroes::DRAKONIA, Race::WZRD, 5000 ) ); push_back( new Heroes( Heroes::MARTINE, Race::SORC, 5000 ) ); push_back( new Heroes( Heroes::JARKONAS, Race::BARB, 5000 ) ); } else { // for non-PoL maps, just add unknown heroes instead in place of the PoL-specific ones for ( int i = Heroes::SOLMYR; i <= Heroes::JARKONAS; ++i ) { push_back( new Heroes( Heroes::UNKNOWN, Race::KNGT ) ); } } if ( IS_DEVEL() ) { push_back( new Heroes( Heroes::DEBUG_HERO, Race::WRLK ) ); } else { push_back( new Heroes( Heroes::UNKNOWN, Race::KNGT ) ); } assert( size() == Heroes::HEROES_COUNT ); } void AllHeroes::clear() { std::for_each( begin(), end(), []( Heroes * hero ) { assert( hero != nullptr ); delete hero; } ); std::vector::clear(); } Heroes * VecHeroes::Get( int hid ) const { if ( !Heroes::isValidId( hid ) ) { return nullptr; } return ( *this )[hid]; } Heroes * VecHeroes::Get( const fheroes2::Point & center ) const { for ( Heroes * hero : *this ) { assert( hero != nullptr ); if ( hero->isPosition( center ) ) { return hero; } } return nullptr; } Heroes * AllHeroes::GetHeroForHire( const int race, const int heroIDToIgnore ) const { const std::set customHeroesPortraits = [this]() { std::set result; for ( const Heroes * hero : *this ) { assert( hero != nullptr ); if ( !hero->Modes( Heroes::CUSTOM ) ) { continue; } result.insert( hero->getPortraitId() ); } return result; }(); std::vector heroesForHire; heroesForHire.reserve( Heroes::HEROES_COUNT - 2 ); const auto fillHeroesForHire = [this, heroIDToIgnore, &customHeroesPortraits, &heroesForHire]( const int raceFilter, const bool avoidCustomHeroes ) { const auto [minHeroId, maxHeroId] = getHeroIdRangeForRace( Race::NONE ); for ( const Heroes * hero : *this ) { assert( hero != nullptr ); // Only regular (non-campaign) heroes are available for hire if ( hero->GetID() > maxHeroId ) { continue; } if ( hero->GetID() == heroIDToIgnore ) { continue; } if ( raceFilter != Race::NONE && hero->GetRace() != raceFilter ) { continue; } if ( !hero->isAvailableForHire() ) { continue; } if ( avoidCustomHeroes && customHeroesPortraits.find( hero->getPortraitId() ) != customHeroesPortraits.end() ) { continue; } heroesForHire.push_back( hero->GetID() ); } }; // First, try to find a free hero of the specified race (avoiding customized heroes, as well as heroes with non-unique portraits) fillHeroesForHire( race, true ); // If no suitable heroes were found, then try to find a free hero of any race (avoiding customized heroes, as well as heroes with non-unique portraits) if ( heroesForHire.empty() && race != Race::NONE ) { fillHeroesForHire( Race::NONE, true ); } // No suitable heroes were found, any free hero will do if ( heroesForHire.empty() ) { fillHeroesForHire( Race::NONE, false ); } // All the heroes are busy if ( heroesForHire.empty() ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "no hero found for hire, all the heroes are busy." ) return nullptr; } // Try to avoid heroes who are already available for recruitment in any kingdom std::vector heroesForHireNotRecruits = heroesForHire; heroesForHireNotRecruits.erase( std::remove_if( heroesForHireNotRecruits.begin(), heroesForHireNotRecruits.end(), [this]( const int heroID ) { return at( heroID )->Modes( Heroes::RECRUIT ); } ), heroesForHireNotRecruits.end() ); if ( !heroesForHireNotRecruits.empty() ) { return at( Rand::Get( heroesForHireNotRecruits ) ); } // There are no heroes who are not yet available for recruitment, allow heroes to be available for recruitment in several kingdoms at the same time return at( Rand::Get( heroesForHire ) ); } void AllHeroes::Scout( int colors ) const { for ( const Heroes * hero : *this ) { assert( hero != nullptr ); if ( colors & hero->GetColor() ) { hero->Scout( hero->GetIndex() ); } } } Heroes * AllHeroes::FromJail( int32_t index ) const { for ( Heroes * hero : *this ) { assert( hero != nullptr ); if ( hero->Modes( Heroes::JAIL ) && index == hero->GetIndex() ) { return hero; } } return nullptr; } StreamBase & operator<<( StreamBase & msg, const VecHeroes & heroes ) { msg << static_cast( heroes.size() ); for ( const Heroes * hero : heroes ) { assert( hero != nullptr ); msg << hero->GetID(); } return msg; } StreamBase & operator>>( StreamBase & msg, VecHeroes & heroes ) { uint32_t size; msg >> size; heroes.clear(); for ( uint32_t i = 0; i < size; ++i ) { int32_t hid; msg >> hid; static_assert( LAST_SUPPORTED_FORMAT_VERSION < FORMAT_VERSION_1010_RELEASE, "Remove the logic below." ); if ( Game::GetVersionOfCurrentSaveFile() < FORMAT_VERSION_1010_RELEASE ) { // UNKNOWN was 72 before FORMAT_VERSION_1010_RELEASE. UNKNOWN hero shouldn't exist! if ( hid == 72 || !Heroes::isValidId( hid + 1 ) ) { continue; } Heroes * hero = world.GetHeroes( hid + 1 ); if ( hero == nullptr ) { // Most likely save file is corrupted. continue; } heroes.emplace_back( hero ); } else { if ( !Heroes::isValidId( hid ) ) { continue; } Heroes * hero = world.GetHeroes( hid ); if ( hero == nullptr ) { // Most likely save file is corrupted. continue; } heroes.emplace_back( hero ); } } return msg; } StreamBase & operator<<( StreamBase & msg, const Heroes & hero ) { const HeroBase & base = hero; const ColorBase & col = hero; // HeroBase msg << base; // Heroes using ObjectTypeUnderHeroType = std::underlying_type_t; return msg << hero.name << col << hero.experience << hero.secondary_skills << hero.army << hero._id << hero.portrait << hero._race << static_cast( hero._objectTypeUnderHero ) << hero.path << hero.direction << hero.sprite_index << hero._patrolCenter << hero._patrolDistance << hero.visit_object << hero._lastGroundRegion; } StreamBase & operator>>( StreamBase & msg, Heroes & hero ) { HeroBase & base = hero; ColorBase & col = hero; // HeroBase msg >> base; // Heroes msg >> hero.name >> col >> hero.experience >> hero.secondary_skills >> hero.army >> hero._id >> hero.portrait >> hero._race; static_assert( LAST_SUPPORTED_FORMAT_VERSION < FORMAT_VERSION_1100_RELEASE, "Remove the logic below." ); if ( Game::GetVersionOfCurrentSaveFile() < FORMAT_VERSION_1100_RELEASE ) { // Before FORMAT_VERSION_1100_RELEASE we did not check that a custom hero name is empty set inside the original map. // This leads to assertion rise while rendering text. Also, it is incorrect to have a hero with no name. if ( hero.name.empty() ) { hero.name = Heroes::GetName( hero._id ); } } static_assert( LAST_SUPPORTED_FORMAT_VERSION < FORMAT_VERSION_1010_RELEASE, "Remove the logic below." ); if ( Game::GetVersionOfCurrentSaveFile() < FORMAT_VERSION_1010_RELEASE ) { // Before FORMAT_VERSION_1010_RELEASE Heroes::UNKNOWN was 72. if ( hero._id == 72 ) { hero._id = Heroes::UNKNOWN; hero.portrait = Heroes::UNKNOWN; } else { ++hero._id; ++hero.portrait; } } using ObjectTypeUnderHeroType = std::underlying_type_t; static_assert( std::is_same_v, "Type of _objectTypeUnderHero has been changed, check the logic below." ); static_assert( LAST_SUPPORTED_FORMAT_VERSION < FORMAT_VERSION_PRE1_1009_RELEASE, "Remove the logic below." ); if ( Game::GetVersionOfCurrentSaveFile() < FORMAT_VERSION_PRE1_1009_RELEASE ) { int temp = 0; msg >> temp; hero._objectTypeUnderHero = static_cast( temp ); } else { ObjectTypeUnderHeroType temp = 0; msg >> temp; hero._objectTypeUnderHero = static_cast( temp ); } msg >> hero.path >> hero.direction >> hero.sprite_index; static_assert( LAST_SUPPORTED_FORMAT_VERSION < FORMAT_VERSION_1010_RELEASE, "Remove the logic below." ); if ( Game::GetVersionOfCurrentSaveFile() < FORMAT_VERSION_1010_RELEASE ) { int16_t patrolX = 0; int16_t patrolY = 0; msg >> patrolX >> patrolY; hero._patrolCenter = fheroes2::Point( patrolX, patrolY ); } else { msg >> hero._patrolCenter; } msg >> hero._patrolDistance >> hero.visit_object >> hero._lastGroundRegion; hero.army.SetCommander( &hero ); return msg; } StreamBase & operator<<( StreamBase & msg, const AllHeroes & heroes ) { msg << static_cast( heroes.size() ); for ( Heroes * const & hero : heroes ) { msg << *hero; } return msg; } StreamBase & operator>>( StreamBase & msg, AllHeroes & heroes ) { uint32_t size; msg >> size; heroes.clear(); heroes.resize( size, nullptr ); static_assert( LAST_SUPPORTED_FORMAT_VERSION < FORMAT_VERSION_1010_RELEASE, "Remove the logic below." ); if ( Game::GetVersionOfCurrentSaveFile() < FORMAT_VERSION_1010_RELEASE ) { // Before FORMAT_VERSION_1010_RELEASE UNKNOWN hero was last while now it is first. // In order to preserve the original order of heroes we have to do the below trick. for ( size_t i = 1; i < heroes.size(); ++i ) { heroes[i] = new Heroes(); msg >> *heroes[i]; } heroes[0] = new Heroes(); msg >> *heroes[0]; } else { for ( Heroes *& hero : heroes ) { hero = new Heroes(); msg >> *hero; } } return msg; } fheroes2-1.0.12+dfsg/src/fheroes2/heroes/heroes.h000066400000000000000000000542701456075706000215000ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2HEROES_H #define H2HEROES_H #include #include #include #include #include #include #include #include #include "army.h" #include "artifact.h" #include "color.h" #include "direction.h" #include "heroes_base.h" #include "math_base.h" #include "mp2.h" #include "pairs.h" #include "route.h" #include "skill.h" #include "spell.h" #include "visit.h" class Castle; class StreamBase; namespace Battle { class Only; } namespace Maps { class Tiles; } namespace fheroes2 { class Image; class Sprite; } class Heroes final : public HeroBase, public ColorBase { public: friend class Battle::Only; enum : int32_t { // Unknown / undefined hero. UNKNOWN, // Knight heroes from The Succession Wars. LORDKILBURN, SIRGALLANTH, ECTOR, GVENNETH, TYRO, AMBROSE, RUBY, MAXIMUS, DIMITRY, // Barbarian heroes from The Succession Wars. THUNDAX, FINEOUS, JOJOSH, CRAGHACK, JEZEBEL, JACLYN, ERGON, TSABU, ATLAS, // Sorceress heroes from The Succession Wars. ASTRA, NATASHA, TROYAN, VATAWNA, REBECCA, GEM, ARIEL, CARLAWN, LUNA, // Warlock heroes from The Succession Wars. ARIE, ALAMAR, VESPER, CRODO, BAROK, KASTORE, AGAR, FALAGAR, WRATHMONT, // Wizard heroes from The Succession Wars. MYRA, FLINT, DAWN, HALON, MYRINI, WILFREY, SARAKIN, KALINDRA, MANDIGAL, // Necromancer heroes from The Succession Wars. ZOM, DARLANA, ZAM, RANLOO, CHARITY, RIALDO, ROXANA, SANDRO, CELIA, // The Succession Wars campaign heroes. ROLAND, CORLAGON, ELIZA, ARCHIBALD, HALTON, BRAX, // The Price of Loyalty expansion heroes. SOLMYR, DAINWIN, MOG, UNCLEIVAN, JOSEPH, GALLAVANT, ELDERIAN, CEALLACH, DRAKONIA, MARTINE, JARKONAS, // Debug hero. Should not be used anywhere outside the development! DEBUG_HERO, // Resurrection expansion heroes. // IMPORTANT! Put all new heroes just above this line. HEROES_COUNT }; enum : uint32_t { SHIPMASTER = 0x00000001, // UNUSED = 0x00000002, SPELLCASTED = 0x00000004, ENABLEMOVE = 0x00000008, // UNUSED = 0x00000010, // UNUSED = 0x00000020, // Hero is available for recruitment in any kingdom RECRUIT = 0x00000040, JAIL = 0x00000080, ACTION = 0x00000100, // Hero must retain his movement points if he retreated or surrendered and was then rehired on the same day SAVEMP = 0x00000200, SLEEPER = 0x00000400, // UNUSED = 0x00000800, // Hero has non-standard properties that were set using the map editor CUSTOM = 0x00001000, NOTDISMISS = 0x00002000, VISIONS = 0x00004000, PATROL = 0x00008000 }; // Types of hero roles. They are only for AI as humans are smart enough to manage heroes by themselves. // The order of roles is important as it is used to identify more valuable heroes among others. enum class Role : int { // The main goal for Scout is to discover new areas so he should run towards the fog of war to expand the visible territory. // These heroes usually appear when either no tasks exist on the map or when AI has too many heroes. SCOUT, // Courier's life is to deliver things from one place to another. Usually they bring an army for Fighters or Champions from // dwellings, castles or from one hero to another. COURIER, // The most ordinary hero's role with no any specialization. This type does everything what a hero can do: // collecting resources, fighting (mostly weak) monsters, claiming towns and mines and expanding the visible territory. HUNTER, // The type of hero with a skew towards fights. His main priority is to kill monsters and enemies, capture castles and guarded mines. // This type still can capture valuable mines or dwellings if they're on the way to something better. FIGHTER, // The mightiest hero among others. The main purpose of this type is to run over the enemy's territory and defeat all heroes there while // capturing all castles and towns. This type of hero is set when one (or few) heroes are too strong in comparison to others. // A hero to be defeated as a winning condition for human must be marked as this type of role. CHAMPION }; // This class is used to update a flag for an AI hero to make him available to meet other heroes. // Such cases happen after battles, reinforcements or collecting artifacts. class AIHeroMeetingUpdater { public: explicit AIHeroMeetingUpdater( Heroes & hero ) : _hero( hero ) , _initialArmyStrength( hero.GetArmy().GetStrength() ) { // Do nothing. } AIHeroMeetingUpdater( const AIHeroMeetingUpdater & ) = delete; AIHeroMeetingUpdater( AIHeroMeetingUpdater && ) = delete; AIHeroMeetingUpdater & operator=( const AIHeroMeetingUpdater & ) = delete; AIHeroMeetingUpdater & operator=( AIHeroMeetingUpdater && ) = delete; ~AIHeroMeetingUpdater() { double currentArmyStrength = 0; try { // Army::GetStrength() could potentially throw an exception, and SonarQube complains about a potentially uncaught exception // in the destructor. This is not a problem per se, because calling std::terminate() is OK, so let's just do this ourselves. currentArmyStrength = _hero.GetArmy().GetStrength(); } catch ( ... ) { // This should never happen assert( 0 ); std::terminate(); } if ( std::fabs( _initialArmyStrength - currentArmyStrength ) > 0.001 ) { _hero.unmarkHeroMeeting(); } } private: Heroes & _hero; const double _initialArmyStrength; }; static const int heroFrameCountPerTile{ 9 }; Heroes(); Heroes( int heroid, int rc ); Heroes( const int heroID, const int race, const uint32_t additionalExperience ); Heroes( const Heroes & ) = delete; ~Heroes() override = default; Heroes & operator=( const Heroes & ) = delete; bool isValid() const override; // Returns true if the hero is active on the adventure map (i.e. has a valid ID, is not imprisoned, and is hired by // some kingdom), otherwise returns false bool isActive() const; // Returns true if the hero is available for hire (i.e. has a valid ID, is not imprisoned, and is not hired by any // kingdom), otherwise returns false bool isAvailableForHire() const; // Dismisses the hero (makes him available for hire) because of a 'reason'. See the implementation for details. void Dismiss( int reason ); bool isLosingGame() const; const Castle * inCastle() const override; Castle * inCastleMutable() const; void LoadFromMP2( const int32_t mapIndex, const int colorType, const int raceType, const bool isInJail, const std::vector & data ); int GetRace() const override; const std::string & GetName() const override; int GetColor() const override; int GetType() const override; int GetControl() const override; const Army & GetArmy() const override; Army & GetArmy() override; int GetID() const { return _id; } double getMeetingValue( const Heroes & otherHero ) const; double getRecruitValue() const; int getStatsValue() const; int GetAttack() const override; int GetDefense() const override; int GetPower() const override; int GetKnowledge() const override; int GetAttack( std::string * ) const; int GetDefense( std::string * ) const; int GetPower( std::string * ) const; int GetKnowledge( std::string * ) const; void IncreasePrimarySkill( int skill ); int GetMorale() const override; int GetLuck() const override; int GetMoraleWithModificators( std::string * str = nullptr ) const; int GetLuckWithModificators( std::string * str = nullptr ) const; int GetLevel() const { return GetLevelFromExperience( experience ); } MP2::MapObjectType getObjectTypeUnderHero() const { return _objectTypeUnderHero; } void setObjectTypeUnderHero( const MP2::MapObjectType objectType ) { _objectTypeUnderHero = ( ( objectType != MP2::OBJ_HERO ) ? objectType : MP2::OBJ_NONE ); } const fheroes2::Point & GetPatrolCenter() const { return _patrolCenter; } void SetPatrolCenter( const fheroes2::Point & pos ) { _patrolCenter = pos; } uint32_t GetPatrolDistance() const { return _patrolDistance; } uint32_t GetMaxSpellPoints() const override; // Returns the maximum number of hero movement points, depending on the surface type on which the hero is currently located uint32_t GetMaxMovePoints() const; // Returns the maximum number of hero movement points, depending on the specified surface type (water or land) uint32_t GetMaxMovePoints( const bool onWater ) const; uint32_t GetMovePoints() const { return move_point; } void IncreaseMovePoints( const uint32_t point ) { move_point += point; } bool MayStillMove( const bool ignorePath, const bool ignoreSleeper ) const; void ResetMovePoints() { move_point = 0; } bool HasSecondarySkill( int ) const; bool HasMaxSecondarySkill() const; int GetLevelSkill( int ) const override; uint32_t GetSecondaryValues( int skill ) const override; void LearnSkill( const Skill::Secondary & ); Skill::SecSkills & GetSecondarySkills() { return secondary_skills; } bool PickupArtifact( const Artifact & art ); bool HasUltimateArtifact() const { return bag_artifacts.ContainUltimateArtifact(); } uint32_t GetCountArtifacts() const { return bag_artifacts.CountArtifacts(); } bool IsFullBagArtifacts() const { return bag_artifacts.isFull(); } int GetMobilityIndexSprite() const; // Returns the relative height of mana column near hero's portrait in heroes panel. Returned value will be in range [0; 25]. int GetManaIndexSprite() const; int OpenDialog( const bool readonly, const bool fade, const bool disableDismiss, const bool disableSwitch, const bool renderBackgroundDialog ); void MeetingDialog( Heroes & ); bool Recruit( const int col, const fheroes2::Point & pt ); bool Recruit( const Castle & castle ); void ActionNewDay(); void ActionNewWeek(); void ActionNewMonth(); void ActionAfterBattle() override; void ActionPreBattle() override; bool BuySpellBook( const Castle * castle ); const Route::Path & GetPath() const { return path; } Route::Path & GetPath() { return path; } // Returns the number of travel days to the tile with the dstIdx index using the pathfinder from the World global // object, or zero if the destination tile is unreachable. The number of days returned is limited, see the source // of this method. int getNumOfTravelDays( int32_t dstIdx ) const; void ShowPath( const bool show ) { show ? path.Show() : path.Hide(); } // Calculates the hero's path to the tile with the dstIdx index using the pathfinder from the World global object. // Recalculates the existing path if dstIdx is negative. Not applicable if you want to use a pathfinder other than // PlayerWorldPathfinder. void calculatePath( int32_t dstIdx ); int GetDirection() const { return direction; } void setDirection( const int directionToSet ) { if ( directionToSet != Direction::UNKNOWN ) { direction = directionToSet; } } // set visited cell void SetVisited( int32_t, Visit::type_t = Visit::LOCAL ); // Set global visited state for itself and for allies. void setVisitedForAllies( const int32_t tileIndex ) const; void SetVisitedWideTile( int32_t, const MP2::MapObjectType objectType, Visit::type_t = Visit::LOCAL ); bool isObjectTypeVisited( const MP2::MapObjectType object, Visit::type_t = Visit::LOCAL ) const; bool isVisited( const Maps::Tiles &, Visit::type_t = Visit::LOCAL ) const; // These methods are used only for AI. bool hasMetWithHero( int heroID ) const; void markHeroMeeting( int heroID ); // Do not call this method directly. It is used by AIHeroMeetingUpdater class. void unmarkHeroMeeting(); bool Move( const bool jumpToNextTile = false ); void Move2Dest( const int32_t destination ); bool isMoveEnabled() const; bool CanMove() const; void SetMove( const bool enable ); bool isAction() const; void ResetAction(); void Action( int tileIndex ); void ActionNewPosition( const bool allowMonsterAttack ); void ApplyPenaltyMovement( uint32_t penalty ); void ActionSpellCast( const Spell & spell ); // Update map in the scout area around the Hero on radar (mini-map). void ScoutRadar() const; bool MayCastAdventureSpells() const; void PortraitRedraw( const int32_t px, const int32_t py, const PortraitType type, fheroes2::Image & dstsf ) const override; int GetSpriteIndex() const { return sprite_index; } // These 2 methods must be used only for hero's animation. Please never use them anywhere else! void SetSpriteIndex( const int index ) { sprite_index = index; } void SetOffset( const fheroes2::Point & offset ) { _offset = offset; } fheroes2::Point getCurrentPixelOffset() const; // Performs a hero fade-out animation with the given speed multiplier and an optional offset void FadeOut( const int animSpeedMultiplier, const fheroes2::Point & offset = fheroes2::Point() ) const; // Performs a hero fade-in animation with the given speed multiplier and an optional offset void FadeIn( const int animSpeedMultiplier, const fheroes2::Point & offset = fheroes2::Point() ) const; // Performs a hero fade-out animation with an optional offset at the lowest possible speed void FadeOut( const fheroes2::Point & offset = fheroes2::Point() ) const { FadeOut( 1, offset ); } // Performs a hero fade-in animation with an optional offset at the lowest possible speed void FadeIn( const fheroes2::Point & offset = fheroes2::Point() ) const { FadeIn( 1, offset ); } void Scout( const int tileIndex ) const; int GetScoutingDistance() const; // Returns the area in map tiles around hero's position in his scout range. fheroes2::Rect GetScoutRoi() const; uint32_t GetVisionsDistance() const; bool isShipMaster() const; void SetShipMaster( bool ); void setLastGroundRegion( const uint32_t regionID ) { _lastGroundRegion = regionID; } uint32_t GetExperience() const { return experience; } void IncreaseExperience( const uint32_t amount, const bool autoselect = false ); std::string String() const; const fheroes2::Sprite & GetPortrait( const int type ) const { return Heroes::GetPortrait( portrait, type ); } int getPortraitId() const { return portrait; } bool isPoLPortrait() const { return ( portrait >= SOLMYR && portrait <= JARKONAS ); } fheroes2::Point MovementDirection() const; int GetAttackedMonsterTileIndex() const { return _attackedMonsterTileIndex; } void SetAttackedMonsterTileIndex( const int idx ) { _attackedMonsterTileIndex = idx; } void setAIRole( const Role role ) { _aiRole = role; } Role getAIRole() const { return _aiRole; } void setDimensionDoorUsage( const uint32_t newUsage ) { _dimensionDoorsUsed = newUsage; } uint32_t getDimensionDoorUses() const { return _dimensionDoorsUsed; } uint8_t getAlphaValue() const { return static_cast( _alphaValue ); } double getAIMinimumJoiningArmyStrength() const; uint32_t getDailyRestoredSpellPoints() const; bool isInDeepOcean() const; static int GetLevelFromExperience( uint32_t exp ); static uint32_t GetExperienceFromLevel( int lvl ); static const fheroes2::Sprite & GetPortrait( int heroid, int type ); static const char * GetName( int heroid ); static bool isValidId( const int32_t id ) { return id > UNKNOWN && id < HEROES_COUNT; } private: friend StreamBase & operator<<( StreamBase &, const Heroes & ); friend StreamBase & operator>>( StreamBase &, Heroes & ); enum { SKILL_VALUE = 100 }; struct HeroSeedsForLevelUp { uint32_t seedPrimarySkill{ 0 }; uint32_t seedSecondarySkill1{ 0 }; uint32_t seedSecondarySkill2{ 0 }; uint32_t seedSecondarySkillRandomChoose{ 0 }; }; HeroSeedsForLevelUp GetSeedsForLevelUp() const; void LevelUp( bool skipsecondary, bool autoselect = false ); void LevelUpSecondarySkill( const HeroSeedsForLevelUp & seeds, int primary, bool autoselect = false ); void AngleStep( int ); bool MoveStep( const bool jumpToNextTile ); bool isInVisibleMapArea() const; // This function is useful only in a situation when AI hero moves out of the fog // we don't update his direction during movement under the fog so there is a situation // when initial hero's sprite is set incorrectly. This function fixes it void SetValidDirectionSprite(); uint32_t UpdateMovementPoints( const uint32_t movePoints, const int skill ) const; // Daily replenishment of spell points void ReplenishSpellPoints(); static uint32_t GetStartingXp(); std::string name; uint32_t experience; Skill::SecSkills secondary_skills; Army army; // Hero ID int _id; // Corresponds to the ID of the hero whose portrait is applied. Usually equal to the // ID of this hero, unless a custom portrait is applied. int portrait; int _race; MP2::MapObjectType _objectTypeUnderHero; Route::Path path; int direction; int sprite_index; fheroes2::Point _offset; // used only during hero's movement fheroes2::Point _patrolCenter; uint32_t _patrolDistance; std::list visit_object; uint32_t _lastGroundRegion = 0; // Tracking how many spells this hero used this turn uint32_t _dimensionDoorsUsed = 0; mutable int _alphaValue; int _attackedMonsterTileIndex; // used only when hero attacks a group of wandering monsters // This value should NOT be saved in save file as it's dynamically set during AI turn. Role _aiRole; }; struct VecHeroes : public std::vector { Heroes * Get( int hid ) const; Heroes * Get( const fheroes2::Point & center ) const; }; struct AllHeroes : public VecHeroes { AllHeroes() = default; AllHeroes( const AllHeroes & ) = delete; ~AllHeroes(); AllHeroes & operator=( const AllHeroes & ) = delete; void Init(); void clear(); void Scout( int colors ) const; void ResetModes( const uint32_t modes ) const { std::for_each( begin(), end(), [modes]( Heroes * hero ) { hero->ResetModes( modes ); } ); } void NewDay() { std::for_each( begin(), end(), []( Heroes * hero ) { hero->ActionNewDay(); } ); } void NewWeek() { std::for_each( begin(), end(), []( Heroes * hero ) { hero->ActionNewWeek(); } ); } void NewMonth() { std::for_each( begin(), end(), []( Heroes * hero ) { hero->ActionNewMonth(); } ); } Heroes * GetHeroForHire( const int race, const int heroIDToIgnore ) const; Heroes * FromJail( int32_t index ) const; }; StreamBase & operator<<( StreamBase &, const VecHeroes & ); StreamBase & operator>>( StreamBase &, VecHeroes & ); StreamBase & operator<<( StreamBase &, const Heroes & ); StreamBase & operator>>( StreamBase &, Heroes & ); StreamBase & operator<<( StreamBase &, const AllHeroes & ); StreamBase & operator>>( StreamBase &, AllHeroes & ); #endif fheroes2-1.0.12+dfsg/src/fheroes2/heroes/heroes_action.cpp000066400000000000000000005073631456075706000233760ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include "agg_image.h" #include "ai.h" #include "army.h" #include "army_troop.h" #include "artifact.h" #include "audio.h" #include "audio_manager.h" #include "battle.h" #include "castle.h" #include "color.h" #include "dialog.h" #include "game.h" #include "game_delays.h" #include "game_interface.h" #include "game_static.h" #include "heroes.h" #include "icn.h" #include "image.h" #include "interface_base.h" #include "interface_gamearea.h" #include "interface_icons.h" #include "interface_radar.h" #include "interface_status.h" #include "kingdom.h" #include "localevent.h" #include "logging.h" #include "m82.h" #include "maps.h" #include "maps_objects.h" #include "maps_tiles.h" #include "maps_tiles_helper.h" #include "math_base.h" #include "monster.h" #include "mp2.h" #include "mus.h" #include "payment.h" #include "players.h" #include "profit.h" #include "puzzle.h" #include "rand.h" #include "resource.h" #include "route.h" #include "screen.h" #include "settings.h" #include "skill.h" #include "spell.h" #include "tools.h" #include "translations.h" #include "ui_dialog.h" #include "ui_monster.h" #include "ui_text.h" #include "visit.h" #include "world.h" namespace { class MusicalEffectPlayer { public: MusicalEffectPlayer() = default; explicit MusicalEffectPlayer( const int trackId ) { play( trackId ); } MusicalEffectPlayer( const MusicalEffectPlayer & ) = delete; ~MusicalEffectPlayer() { Music::setVolume( 100 * Settings::Get().MusicVolume() / 10 ); } MusicalEffectPlayer & operator=( const MusicalEffectPlayer & ) = delete; static bool canBePlayed( const int trackId ) { if ( trackId == MUS::UNUSED || trackId == MUS::UNKNOWN ) { return false; } // There are no "musical" effects in MIDI format if ( Settings::Get().MusicMIDI() ) { return false; } return AudioManager::isExternalMusicFileAvailable( trackId ); } static void play( const int trackId ) { if ( !canBePlayed( trackId ) ) { return; } // "Musical" sound effects should use the volume of the sound effects instead of the volume of the music const int soundVolume = Settings::Get().SoundVolume(); // Play the sound effect only if the volume of the sound effect is not set to 0 if ( soundVolume <= 0 ) { return; } Music::setVolume( 100 * soundVolume / 10 ); AudioManager::PlayMusic( trackId, Music::PlaybackMode::PLAY_ONCE ); } private: const AudioManager::MusicRestorer _musicRestorer; }; void DialogCaptureResourceObject( const std::string & hdr, const std::string & str, const int32_t resourceType ) { const Funds info = ProfitConditions::FromMine( resourceType ); int32_t resourceCount = 0; switch ( resourceType ) { case Resource::MERCURY: resourceCount = info.mercury; break; case Resource::WOOD: resourceCount = info.wood; break; case Resource::ORE: resourceCount = info.ore; break; case Resource::SULFUR: resourceCount = info.sulfur; break; case Resource::CRYSTAL: resourceCount = info.crystal; break; case Resource::GEMS: resourceCount = info.gems; break; case Resource::GOLD: resourceCount = info.gold; break; default: // You're passing an invalid resource type. Check your logic! assert( 0 ); break; } std::string perday = _( "%{count} / day" ); StringReplace( perday, "%{count}", resourceCount ); std::string msg = str; switch ( resourceCount ) { case 1: StringReplace( msg, "%{count}", _( "one" ) ); break; case 2: StringReplace( msg, "%{count}", _( "two" ) ); break; default: StringReplace( msg, "%{count}", resourceCount ); break; } fheroes2::ResourceDialogElement resourceUI( resourceType, std::move( perday ) ); fheroes2::showMessage( fheroes2::Text( hdr, fheroes2::FontType::normalYellow() ), fheroes2::Text( std::move( msg ), fheroes2::FontType::normalWhite() ), Dialog::OK, { &resourceUI } ); } void BattleLose( Heroes & hero, const Battle::Result & res, bool attacker ) { const uint32_t reason = attacker ? res.AttackerResult() : res.DefenderResult(); AudioManager::PlaySound( M82::KILLFADE ); hero.FadeOut(); hero.Dismiss( reason ); Interface::AdventureMap & I = Interface::AdventureMap::Get(); if ( !hero.GetKingdom().isLoss() ) { // If the enemy is not vanquished we update only position of defeated hero on radar to remove hero mark. const fheroes2::Point heroPosition = hero.GetCenter(); I.getRadar().SetRenderArea( { heroPosition.x, heroPosition.y, 1, 1 } ); } // If the enemy is vanquished we do not set radar ROI and fully redraw the radar map image as there might be color reset of enemy's objects. I.setRedraw( Interface::REDRAW_RADAR ); } void RecruitMonsterFromTile( Heroes & hero, Maps::Tiles & tile, const std::string & msg, const Troop & troop, bool remove ) { if ( !hero.GetArmy().CanJoinTroop( troop ) ) fheroes2::showStandardTextMessage( msg, _( "You are unable to recruit at this time, your ranks are full." ), Dialog::OK ); else { const uint32_t recruit = Dialog::RecruitMonster( troop.GetMonster(), troop.GetCount(), false, 0 ).GetCount(); if ( recruit ) { if ( remove && recruit == troop.GetCount() ) { Game::PlayPickupSound(); setMonsterCountOnTile( tile, 0 ); Interface::AdventureMap::Get().getGameArea().runSingleObjectAnimation( std::make_shared( tile.GetObjectUID(), tile.GetIndex(), tile.GetObject() ) ); } else { setMonsterCountOnTile( tile, troop.GetCount() - recruit ); } const Funds paymentCosts = troop.GetMonster().GetCost() * recruit; hero.GetKingdom().OddFundsResource( paymentCosts ); hero.GetArmy().JoinTroop( troop.GetMonster(), recruit, false ); Interface::AdventureMap::Get().setRedraw( Interface::REDRAW_STATUS ); } } } void WhirlpoolTroopLoseEffect( Heroes & hero ) { const Army & heroArmy = hero.GetArmy(); Troop * weakestTroop = heroArmy.GetWeakestTroop(); assert( weakestTroop != nullptr ); if ( weakestTroop == nullptr ) { return; } // Whirlpool effect affects heroes only with more than one creature in more than one slot if ( heroArmy.GetOccupiedSlotCount() == 1 && weakestTroop->GetCount() == 1 ) { return; } if ( 1 == Rand::Get( 1, 3 ) ) { fheroes2::showStandardTextMessage( MP2::StringObject( MP2::MapObjectType::OBJ_WHIRLPOOL ), _( "A whirlpool engulfs your ship. Some of your army has fallen overboard." ), Dialog::OK ); if ( weakestTroop->GetCount() == 1 ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() << " lost " << weakestTroop->GetCount() << " " << weakestTroop->GetName() << " in the whirlpool" ) weakestTroop->Reset(); } else { const uint32_t newCount = Monster::GetCountFromHitPoints( weakestTroop->GetID(), weakestTroop->GetHitPoints() - weakestTroop->GetHitPoints() * Game::GetWhirlpoolPercent() / 100 ); DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() << " lost " << weakestTroop->GetCount() - newCount << " " << weakestTroop->GetName() << " in the whirlpool" ) weakestTroop->SetCount( newCount ); } Interface::AdventureMap::Get().setRedraw( Interface::REDRAW_STATUS ); } } void ActionToMonster( Heroes & hero, int32_t dst_index ) { Maps::Tiles & tile = world.GetTiles( dst_index ); Troop troop = getTroopFromTile( tile ); Interface::AdventureMap & I = Interface::AdventureMap::Get(); bool destroy = false; const NeutralMonsterJoiningCondition join = Army::GetJoinSolution( hero, tile, troop ); if ( join.reason == NeutralMonsterJoiningCondition::Reason::Alliance ) { if ( hero.GetArmy().CanJoinTroop( troop ) ) { DEBUG_LOG( DBG_GAME, DBG_INFO, troop.GetName() << " join " << hero.GetName() << " as a part of alliance" ) assert( join.joiningMessage != nullptr ); fheroes2::showStandardTextMessage( "", join.joiningMessage, Dialog::OK ); hero.GetArmy().JoinTroop( troop ); } else { DEBUG_LOG( DBG_GAME, DBG_INFO, troop.GetName() << " unblock way for " << hero.GetName() << " as a part of alliance" ) assert( join.fleeingMessage != nullptr ); fheroes2::showStandardTextMessage( "", join.fleeingMessage, Dialog::OK ); } destroy = true; } else if ( join.reason == NeutralMonsterJoiningCondition::Reason::Bane ) { DEBUG_LOG( DBG_GAME, DBG_INFO, troop.GetName() << " run away from " << hero.GetName() << " as a part of bane" ) assert( join.fleeingMessage != nullptr ); fheroes2::showStandardTextMessage( "", join.fleeingMessage, Dialog::OK ); destroy = true; } else if ( join.reason == NeutralMonsterJoiningCondition::Reason::Free ) { DEBUG_LOG( DBG_GAME, DBG_INFO, troop.GetName() << " want to join " << hero.GetName() ) // This condition must already be met if a group of monsters wants to join assert( hero.GetArmy().CanJoinTroop( troop ) ); if ( Dialog::YES == Dialog::ArmyJoinFree( troop ) ) { hero.GetArmy().JoinTroop( troop ); I.setRedraw( Interface::REDRAW_STATUS ); destroy = true; } else { fheroes2::showStandardTextMessage( "", _( "Insulted by your refusal of their offer, the monsters attack!" ), Dialog::OK ); } } else if ( join.reason == NeutralMonsterJoiningCondition::Reason::ForMoney ) { const int32_t joiningCost = troop.GetTotalCost().gold; DEBUG_LOG( DBG_GAME, DBG_INFO, join.monsterCount << " " << troop.GetName() << " want to join " << hero.GetName() << " for " << joiningCost << " gold" ) // These conditions must already be met if a group of monsters wants to join assert( hero.GetArmy().CanJoinTroop( troop ) && hero.GetKingdom().AllowPayment( Funds( Resource::GOLD, joiningCost ) ) ); if ( Dialog::YES == Dialog::ArmyJoinWithCost( troop, join.monsterCount, joiningCost ) ) { hero.GetArmy().JoinTroop( troop.GetMonster(), join.monsterCount, false ); hero.GetKingdom().OddFundsResource( Funds( Resource::GOLD, joiningCost ) ); I.setRedraw( Interface::REDRAW_STATUS ); destroy = true; } else { fheroes2::showStandardTextMessage( "", _( "Insulted by your refusal of their offer, the monsters attack!" ), Dialog::OK ); } } else if ( join.reason == NeutralMonsterJoiningCondition::Reason::RunAway ) { DEBUG_LOG( DBG_GAME, DBG_INFO, troop.GetName() << " run away from " << hero.GetName() ) std::string message = _( "The %{monster}, awed by the power of your forces, begin to scatter.\nDo you wish to pursue and engage them?" ); StringReplaceWithLowercase( message, "%{monster}", troop.GetMultiName() ); if ( fheroes2::showStandardTextMessage( "", message, Dialog::YES | Dialog::NO ) == Dialog::NO ) { destroy = true; } } // Fight if ( !destroy ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() << " attacks monster " << troop.GetName() ) // Set the hero's attacked monster tile index and immediately redraw game area to show an attacking sprite for this monster hero.SetAttackedMonsterTileIndex( dst_index ); I.redraw( Interface::REDRAW_GAMEAREA ); Army army( tile ); Battle::Result res = Battle::Loader( hero.GetArmy(), army, dst_index ); if ( res.AttackerWins() ) { hero.IncreaseExperience( res.GetExperienceAttacker() ); destroy = true; } else { BattleLose( hero, res, true ); // The army can include both the original monsters and their upgraded version const uint32_t monstersLeft = army.getTotalCount(); #ifndef NDEBUG const Monster originalMonster = troop.GetMonster(); const Monster upgradedMonster = originalMonster.GetUpgrade(); if ( upgradedMonster == originalMonster ) { assert( monstersLeft == army.GetCountMonsters( originalMonster ) ); } else { assert( monstersLeft == army.GetCountMonsters( originalMonster ) + army.GetCountMonsters( upgradedMonster ) ); } #endif if ( monstersLeft > 0 ) { setMonsterCountOnTile( tile, monstersLeft ); if ( Maps::isMonsterOnTileJoinConditionFree( tile ) ) { Maps::setMonsterOnTileJoinCondition( tile, Monster::JOIN_CONDITION_MONEY ); } } else { // TODO: do fading animation for the hero and for the monsters at the same time. destroy = true; } } } if ( destroy ) { AudioManager::PlaySound( M82::KILLFADE ); setMonsterCountOnTile( tile, 0 ); Interface::AdventureMap::Get().getGameArea().runSingleObjectAnimation( std::make_shared( tile.GetObjectUID(), tile.GetIndex(), tile.GetObject() ) ); } // Clear the hero's attacked monster tile index hero.SetAttackedMonsterTileIndex( -1 ); } void ActionToCastle( Heroes & hero, const int32_t dstIndex ) { Castle * castle = world.getCastleEntrance( Maps::GetPoint( dstIndex ) ); if ( castle == nullptr ) { // This should never happen assert( 0 ); DEBUG_LOG( DBG_GAME, DBG_WARN, hero.GetName() << " is trying to visit the castle on tile " << dstIndex << ", but there is no entrance to the castle on this tile" ) return; } if ( hero.GetColor() == castle->GetColor() ) { assert( hero.GetIndex() == dstIndex ); DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() << " visits " << castle->GetName() ) castle->MageGuildEducateHero( hero ); Game::OpenCastleDialog( *castle ); return; } if ( hero.isFriends( castle->GetColor() ) ) { // This should never happen - hero should not be able to visit an allied castle assert( 0 ); DEBUG_LOG( DBG_GAME, DBG_WARN, hero.GetName() << " is not allowed to visit the allied castle " << castle->GetName() ) return; } const auto captureCastle = [&hero, dstIndex, castle]() { Kingdom & enemyKingdom = castle->GetKingdom(); enemyKingdom.RemoveCastle( castle ); hero.GetKingdom().AddCastle( castle ); world.CaptureObject( dstIndex, hero.GetColor() ); castle->Scout(); Interface::AdventureMap & I = Interface::AdventureMap::Get(); // If the enemy is not vanquished we update only the area around the castle on radar. if ( !enemyKingdom.isLoss() ) { const int32_t scoutRange = static_cast( GameStatic::getFogDiscoveryDistance( GameStatic::FogDiscoveryType::CASTLE ) ); const fheroes2::Point castlePosition = Maps::GetPoint( dstIndex ); I.getRadar().SetRenderArea( { castlePosition.x - scoutRange, castlePosition.y - scoutRange, 2 * scoutRange + 1, 2 * scoutRange + 1 } ); } // Otherwise we fully redraw the radar map image as there might be color reset of enemy's objects. I.setRedraw( Interface::REDRAW_CASTLES | Interface::REDRAW_RADAR ); }; Army & army = castle->GetActualArmy(); // Hero is standing in front of the castle, which means that there must be an enemy army in the castle if ( hero.GetIndex() != dstIndex ) { assert( army.isValid() && army.GetColor() != hero.GetColor() ); DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() << " attacks the enemy castle " << castle->GetName() ) Heroes * defender = castle->GetHero(); castle->ActionPreBattle(); const Battle::Result res = Battle::Loader( hero.GetArmy(), army, dstIndex ); castle->ActionAfterBattle( res.AttackerWins() ); // The defender was defeated if ( !res.DefenderWins() && defender ) { BattleLose( *defender, res, false ); } // The attacker was defeated if ( !res.AttackerWins() ) { BattleLose( hero, res, true ); } // The attacker won if ( res.AttackerWins() ) { captureCastle(); hero.IncreaseExperience( res.GetExperienceAttacker() ); } // The defender won else if ( res.DefenderWins() && defender ) { defender->IncreaseExperience( res.GetExperienceDefender() ); } return; } // If hero is already in the castle, then there must be his own army in the castle assert( army.isValid() && army.GetColor() == hero.GetColor() ); DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() << " captures the enemy castle " << castle->GetName() ) captureCastle(); castle->MageGuildEducateHero( hero ); Game::OpenCastleDialog( *castle ); } void ActionToHeroes( Heroes & hero, const int32_t dstIndex ) { Heroes * otherHero = world.GetTiles( dstIndex ).getHero(); if ( otherHero == nullptr ) { // This should never happen assert( 0 ); DEBUG_LOG( DBG_GAME, DBG_WARN, hero.GetName() << " is trying to meet the hero on tile " << dstIndex << ", but there is no hero on this tile" ) return; } if ( hero.GetColor() == otherHero->GetColor() ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() << " meets " << otherHero->GetName() ) hero.MeetingDialog( *otherHero ); return; } if ( hero.isFriends( otherHero->GetColor() ) ) { // This should never happen - hero should not be able to meet an allied hero assert( 0 ); DEBUG_LOG( DBG_GAME, DBG_WARN, hero.GetName() << " is not allowed to meet the allied hero " << otherHero->GetName() ) return; } if ( otherHero->inCastle() ) { ActionToCastle( hero, dstIndex ); return; } DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() << " attacks " << otherHero->GetName() ) const Battle::Result res = Battle::Loader( hero.GetArmy(), otherHero->GetArmy(), dstIndex ); // TODO: make fading animation of both heroes together. // The defender was defeated if ( !res.DefenderWins() ) { BattleLose( *otherHero, res, false ); } // The attacker was defeated if ( !res.AttackerWins() ) { BattleLose( hero, res, true ); } // The attacker won if ( res.AttackerWins() ) { hero.IncreaseExperience( res.GetExperienceAttacker() ); } // The defender won else if ( res.DefenderWins() ) { otherHero->IncreaseExperience( res.GetExperienceDefender() ); } } void ActionToBoat( Heroes & hero, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) if ( hero.isShipMaster() ) { return; } hero.setLastGroundRegion( world.GetTiles( hero.GetIndex() ).GetRegion() ); const fheroes2::Point offset( Maps::GetPoint( dst_index ) - hero.GetCenter() ); // Get the direction of the boat so that the direction of the hero can be set to it after boarding const int boatDirection = world.GetTiles( dst_index ).getBoatDirection(); AudioManager::PlaySound( M82::KILLFADE ); hero.ShowPath( false ); hero.FadeOut( offset ); hero.Scout( dst_index ); hero.Move2Dest( dst_index ); hero.ResetMovePoints(); hero.GetPath().Reset(); // Update the radar map image before changing the direction of the hero. Interface::AdventureMap & I = Interface::AdventureMap::Get(); I.getRadar().SetRenderArea( hero.GetScoutRoi() ); I.redraw( Interface::REDRAW_RADAR ); // Set the direction of the hero to the one of the boat as the boat does not move when boarding it hero.setDirection( boatDirection ); hero.setObjectTypeUnderHero( MP2::OBJ_NONE ); world.GetTiles( dst_index ).resetObjectSprite(); hero.SetShipMaster( true ); hero.ShowPath( true ); // Boat is no longer empty so we reset color to default world.GetTiles( dst_index ).resetBoatOwnerColor(); } void ActionToCoast( Heroes & hero, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) if ( !hero.isShipMaster() ) { return; } const int fromIndex = hero.GetIndex(); Maps::Tiles & from = world.GetTiles( fromIndex ); // Calculate the offset before making the action. const fheroes2::Point offset( Maps::GetPoint( dst_index ) - hero.GetCenter() ); hero.ShowPath( false ); hero.Scout( dst_index ); hero.Move2Dest( dst_index ); hero.ResetMovePoints(); hero.GetPath().Reset(); from.setBoat( Maps::GetDirection( fromIndex, dst_index ), hero.GetColor() ); hero.SetShipMaster( false ); AudioManager::PlaySound( M82::KILLFADE ); hero.FadeIn( offset ); hero.ShowPath( true ); // Clear hero position marker from the boat and scout the area on radar after disembarking. Interface::AdventureMap & I = Interface::AdventureMap::Get(); I.getRadar().SetRenderArea( hero.GetScoutRoi() ); I.setRedraw( Interface::REDRAW_RADAR ); hero.ActionNewPosition( true ); } void ActionToPickupResource( const Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() << ", object: " << MP2::StringObject( objectType ) ) Maps::Tiles & tile = world.GetTiles( dst_index ); Interface::AdventureMap & I = Interface::AdventureMap::Get(); if ( objectType == MP2::OBJ_BOTTLE ) { const MapSign * sign = dynamic_cast( world.GetMapObject( dst_index ) ); fheroes2::showStandardTextMessage( MP2::StringObject( objectType ), ( sign ? sign->message : "No message provided" ), Dialog::OK ); } else { const Funds funds = getFundsFromTile( tile ); if ( objectType == MP2::OBJ_CAMPFIRE ) { const fheroes2::Text header( MP2::StringObject( objectType ), fheroes2::FontType::normalYellow() ); const fheroes2::Text body( _( "Ransacking an enemy camp, you discover a hidden cache of treasures." ), fheroes2::FontType::normalWhite() ); fheroes2::showResourceMessage( header, body, Dialog::OK, funds ); } else { const auto resource = funds.getFirstValidResource(); I.getStatusWindow().SetResource( resource.first, resource.second ); I.setRedraw( Interface::REDRAW_STATUS ); } hero.GetKingdom().AddFundsResource( funds ); } Game::PlayPickupSound(); I.getGameArea().runSingleObjectAnimation( std::make_shared( tile.GetObjectUID(), tile.GetIndex(), tile.GetObject() ) ); resetObjectInfoOnTile( tile ); if ( objectType == MP2::OBJ_RESOURCE ) { // Update the position of picked up resource on radar to remove its mark. const fheroes2::Point resourcePosition = Maps::GetPoint( dst_index ); I.getRadar().SetRenderArea( { resourcePosition.x, resourcePosition.y, 1, 1 } ); I.setRedraw( Interface::REDRAW_RADAR ); } } void ActionToObjectResource( const Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() << ", object: " << MP2::StringObject( objectType ) ) Maps::Tiles & tile = world.GetTiles( dst_index ); Funds funds = getFundsFromTile( tile ); std::string msg; const std::string & caption = MP2::StringObject( objectType ); switch ( objectType ) { case MP2::OBJ_WINDMILL: msg = funds.GetValidItemsCount() > 0 ? _( "The keeper of the mill announces:\n\"Milord, I have been working very hard to provide you with these resources, come back next week for more.\"" ) : _( "The keeper of the mill announces:\n\"Milord, I am sorry, there are no resources currently available. Please try again next week.\"" ); break; case MP2::OBJ_WATER_WHEEL: msg = funds.GetValidItemsCount() > 0 ? _( "The keeper of the mill announces:\n\"Milord, I have been working very hard to provide you with this gold, come back next week for more.\"" ) : _( "The keeper of the mill announces:\n\"Milord, I am sorry, there is no gold currently available. Please try again next week.\"" ); break; case MP2::OBJ_LEAN_TO: msg = funds.GetValidItemsCount() > 0 ? _( "You've found an abandoned lean-to.\nPoking about, you discover some resources hidden nearby." ) : _( "The lean-to is long abandoned. There is nothing of value here." ); break; case MP2::OBJ_MAGIC_GARDEN: msg = funds.GetValidItemsCount() > 0 ? _( "You catch a leprechaun foolishly sleeping amidst a cluster of magic mushrooms.\nIn exchange for his freedom, he guides you to a small pot filled with precious things." ) : _( "You've found a magic garden, the kind of place that leprechauns and faeries like to cavort in, but there is no one here today.\nPerhaps you should try again next week." ); break; default: break; } if ( funds.GetValidItemsCount() > 0 ) { { const MusicalEffectPlayer musicalEffectPlayer; // The Magic Garden has a special sound if ( objectType == MP2::OBJ_MAGIC_GARDEN && MusicalEffectPlayer::canBePlayed( MUS::TREEHOUSE ) ) { MusicalEffectPlayer::play( MUS::TREEHOUSE ); } // The Lean-To has a special sound else if ( objectType == MP2::OBJ_LEAN_TO ) { AudioManager::PlaySound( M82::EXPERNCE ); } else { AudioManager::PlaySound( M82::TREASURE ); } fheroes2::showResourceMessage( fheroes2::Text( caption, fheroes2::FontType::normalYellow() ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK, funds ); } hero.GetKingdom().AddFundsResource( funds ); } else { fheroes2::showStandardTextMessage( caption, msg, Dialog::OK ); } resetObjectInfoOnTile( tile ); hero.setVisitedForAllies( dst_index ); } void ActionToSkeleton( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) Maps::Tiles & tile = world.GetTiles( dst_index ); std::string message( _( "You come upon the remains of an unfortunate adventurer." ) ); const std::string title( MP2::StringObject( objectType ) ); // artifact if ( doesTileContainValuableItems( tile ) ) { if ( hero.IsFullBagArtifacts() ) { uint32_t gold = GoldInsteadArtifact( objectType ); const Funds funds( Resource::GOLD, gold ); AudioManager::PlaySound( M82::EXPERNCE ); fheroes2::showResourceMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( _( "Treasure" ), fheroes2::FontType::normalWhite() ), Dialog::OK, funds ); hero.GetKingdom().AddFundsResource( funds ); } else { const Artifact & art = getArtifactFromTile( tile ); message += '\n'; message.append( _( "Searching through the tattered clothing, you find the %{artifact}." ) ); StringReplace( message, "%{artifact}", art.GetName() ); AudioManager::PlaySound( M82::TREASURE ); const fheroes2::ArtifactDialogElement artifactUI( art ); fheroes2::showMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( message, fheroes2::FontType::normalWhite() ), Dialog::OK, { &artifactUI } ); hero.PickupArtifact( art ); } resetObjectInfoOnTile( tile ); } else { message += '\n'; message.append( _( "Searching through the tattered clothing, you find nothing." ) ); fheroes2::showStandardTextMessage( title, message, Dialog::OK ); } hero.SetVisitedWideTile( dst_index, objectType, Visit::GLOBAL ); } void ActionToWagon( Heroes & hero, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) Maps::Tiles & tile = world.GetTiles( dst_index ); std::string message( _( "You come across an old wagon left by a trader who didn't quite make it to safe terrain." ) ); const std::string title( MP2::StringObject( MP2::OBJ_WAGON ) ); if ( doesTileContainValuableItems( tile ) ) { const Artifact & art = getArtifactFromTile( tile ); if ( art.isValid() ) { if ( hero.IsFullBagArtifacts() ) { message += '\n'; message.append( _( "Unfortunately, others have found it first, and the wagon is empty." ) ); fheroes2::showStandardTextMessage( title, message, Dialog::OK ); } else { message += '\n'; message.append( _( "Searching inside, you find the %{artifact}." ) ); StringReplace( message, "%{artifact}", art.GetName() ); AudioManager::PlaySound( M82::TREASURE ); const fheroes2::ArtifactDialogElement artifactUI( art ); fheroes2::showMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( message, fheroes2::FontType::normalWhite() ), Dialog::OK, { &artifactUI } ); hero.PickupArtifact( art ); } } else { const Funds & funds = getFundsFromTile( tile ); AudioManager::PlaySound( M82::EXPERNCE ); message += '\n'; message.append( _( "Inside, you find some of the wagon's cargo still intact." ) ); fheroes2::showResourceMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( message, fheroes2::FontType::normalWhite() ), Dialog::OK, funds ); hero.GetKingdom().AddFundsResource( funds ); } resetObjectInfoOnTile( tile ); } else { message += '\n'; message.append( _( "Unfortunately, others have found it first, and the wagon is empty." ) ); fheroes2::showStandardTextMessage( title, message, Dialog::OK ); } hero.SetVisited( dst_index, Visit::GLOBAL ); } void ActionToFlotSam( const Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) Maps::Tiles & tile = world.GetTiles( dst_index ); std::string msg; const std::string title( MP2::StringObject( objectType ) ); const Funds & funds = getFundsFromTile( tile ); if ( funds.GetValidItemsCount() > 0 ) { msg = funds.wood && funds.gold ? _( "You search through the flotsam, and find some wood and some gold." ) : _( "You search through the flotsam, and find some wood." ); fheroes2::showResourceMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK, funds ); hero.GetKingdom().AddFundsResource( funds ); } else { msg = _( "You search through the flotsam, but find nothing." ); fheroes2::showStandardTextMessage( title, msg, Dialog::OK ); } Game::PlayPickupSound(); Interface::AdventureMap::Get().getGameArea().runSingleObjectAnimation( std::make_shared( tile.GetObjectUID(), tile.GetIndex(), tile.GetObject() ) ); resetObjectInfoOnTile( tile ); } void ActionToShrine( Heroes & hero, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) const Spell & spell = getSpellFromTile( world.GetTiles( dst_index ) ); assert( spell.isValid() ); const int spellLevel = spell.Level(); std::string head; std::string body; switch ( spellLevel ) { case 1: head = _( "Shrine of the 1st Circle" ); body = _( "You come across a small shrine attended by a group of novice acolytes.\nIn exchange for your protection, they agree to teach you a simple spell - '%{spell}'." ); break; case 2: head = _( "Shrine of the 2nd Circle" ); body = _( "You come across an ornate shrine attended by a group of rotund friars.\nIn exchange for your protection, they agree to teach you a spell - '%{spell}'." ); break; case 3: head = _( "Shrine of the 3rd Circle" ); body = _( "You come across a lavish shrine attended by a group of high priests.\nIn exchange for your protection, they agree to teach you a sophisticated spell - '%{spell}'." ); break; default: // Did you add a new level shrine? Add the logic above. assert( 0 ); return; } StringReplace( body, "%{spell}", spell.GetName() ); if ( hero.HaveSpellBook() ) { // check valid level spell and wisdom skill if ( 3 == spellLevel && Skill::Level::NONE == hero.GetLevelSkill( Skill::Secondary::WISDOM ) ) { body += _( "\nUnfortunately, you do not have the wisdom to understand the spell, and you are unable to learn it." ); fheroes2::showStandardTextMessage( head, body, Dialog::OK ); } // already know (skip bag artifacts) else if ( hero.HaveSpell( spell.GetID(), true ) ) { body += _( "\nUnfortunately, you already have knowledge of this spell, so there is nothing more for them to teach you." ); fheroes2::showStandardTextMessage( head, body, Dialog::OK ); } else { AudioManager::PlaySound( M82::TREASURE ); hero.AppendSpellToBook( spell.GetID() ); const fheroes2::SpellDialogElement spellUI( spell, &hero ); fheroes2::showMessage( fheroes2::Text( head, fheroes2::FontType::normalYellow() ), fheroes2::Text( body, fheroes2::FontType::normalWhite() ), Dialog::OK, { &spellUI } ); } } else { body += _( "\nUnfortunately, you have no Magic Book to record the spell with." ); fheroes2::showStandardTextMessage( head, body, Dialog::OK ); } hero.SetVisited( dst_index, Visit::GLOBAL ); } void ActionToWitchsHut( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) const Skill::Secondary & skill = getSecondarySkillFromWitchsHut( world.GetTiles( dst_index ) ); if ( skill.isValid() ) { std::string msg = _( "You approach the hut and observe a witch inside studying an ancient tome on %{skill}.\n\n" ); const std::string & skill_name = Skill::Secondary::String( skill.Skill() ); StringReplace( msg, "%{skill}", skill_name ); const std::string title( MP2::StringObject( objectType ) ); // No room for a new skill if ( hero.HasMaxSecondarySkill() ) { msg.append( _( "As you approach, she turns and focuses her one glass eye on you.\n\"You already know everything you deserve to learn!\" the witch screeches. \"NOW GET OUT OF MY HOUSE!\"" ) ); fheroes2::showStandardTextMessage( title, msg, Dialog::OK ); } // Skill has already been learned else if ( hero.HasSecondarySkill( skill.Skill() ) ) { msg.append( _( "As you approach, she turns and speaks.\n\"You already know that which I would teach you. I can help you no further.\"" ) ); fheroes2::showStandardTextMessage( title, msg, Dialog::OK ); } else { hero.LearnSkill( skill ); // When Scouting skill is learned we reveal the fog and redraw the radar map image in a new scout area of the hero. if ( skill.Skill() == Skill::Secondary::SCOUTING ) { hero.Scout( hero.GetIndex() ); hero.ScoutRadar(); } msg.append( _( "An ancient and immortal witch living in a hut with bird's legs for stilts teaches you %{skill} for her own inscrutable purposes." ) ); StringReplace( msg, "%{skill}", skill_name ); { const MusicalEffectPlayer musicalEffectPlayer( MUS::EXPERIENCE ); const fheroes2::SecondarySkillDialogElement secondarySkillUI( skill, hero ); fheroes2::showMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK, { &secondarySkillUI } ); } } } else { // A broken object? assert( 0 ); } hero.SetVisited( dst_index, Visit::GLOBAL ); } void ActionToGoodLuckObject( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() << ", object: " << MP2::StringObject( objectType ) ) bool visited = hero.isObjectTypeVisited( objectType ); std::string msg; switch ( objectType ) { case MP2::OBJ_FOUNTAIN: msg = visited ? _( "You drink from the enchanted fountain, but nothing happens." ) : _( "As you drink the sweet water, you gain luck for your next battle." ); break; case MP2::OBJ_FAERIE_RING: msg = visited ? _( "You enter the faerie ring, but nothing happens." ) : _( "Upon entering the mystical faerie ring, your army gains luck for its next battle." ); break; case MP2::OBJ_IDOL: msg = visited ? _( "You've found an ancient and weathered stone idol.\nIt is supposed to grant luck to visitors, but since the stars are already smiling upon you, it does nothing." ) : _( "You've found an ancient and weathered stone idol.\nKissing it is supposed to be lucky, so you do. The stone is very cold to the touch." ); break; case MP2::OBJ_MERMAID: msg = visited ? _( "The mermaids silently entice you to return later and be blessed again." ) : _( "The magical, soothing beauty of the Mermaids reaches you and your crew.\nJust for a moment, you forget your worries and bask in the beauty of the moment.\nThe mermaids charms bless you with increased luck for your next combat." ); break; default: break; } const std::string title( MP2::StringObject( objectType ) ); // check already visited if ( visited ) { fheroes2::showStandardTextMessage( title, msg, Dialog::OK ); } else { // modify luck hero.SetVisited( dst_index ); AudioManager::PlaySound( M82::GOODLUCK ); const fheroes2::LuckDialogElement luckUI( true ); fheroes2::showMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK, { &luckUI } ); } } void ActionToPyramid( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) Maps::Tiles & tile = world.GetTiles( dst_index ); const Spell & spell = getSpellFromTile( tile ); const std::string ask = _( "You come upon the pyramid of a great and ancient king.\nYou are tempted to search it for treasure, but all the old stories warn of fearful curses and undead " "guardians.\nWill you search?" ); const std::string title( MP2::StringObject( objectType ) ); bool enter = false; { const MusicalEffectPlayer musicalEffectPlayer( MUS::DUNGEON ); enter = ( fheroes2::showStandardTextMessage( title, ask, Dialog::YES | Dialog::NO ) == Dialog::YES ); } if ( enter ) { if ( spell.isValid() ) { // battle Army army( tile ); const Battle::Result res = Battle::Loader( hero.GetArmy(), army, dst_index ); if ( res.AttackerWins() ) { hero.IncreaseExperience( res.GetExperienceAttacker() ); bool valid = false; std::string msg = _( "Upon defeating the monsters, you decipher an ancient glyph on the wall, telling the secret of the spell - '" ); msg += spell.GetName(); msg += "'."; // Check Magic Book. if ( !hero.HaveSpellBook() ) { msg += '\n'; msg += _( "Unfortunately, you have no Magic Book to record the spell with." ); } else if ( Skill::Level::EXPERT > hero.GetLevelSkill( Skill::Secondary::WISDOM ) ) { // check skill level for wisdom msg += '\n'; msg += _( "Unfortunately, you do not have the wisdom to understand the spell, and you are unable to learn it." ); } else { valid = true; } if ( valid ) { const fheroes2::SpellDialogElement spellUI( spell, &hero ); fheroes2::showMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK, { &spellUI } ); hero.AppendSpellToBook( spell ); } else { fheroes2::showStandardTextMessage( title, msg, Dialog::OK ); } resetObjectInfoOnTile( tile ); hero.SetVisited( dst_index, Visit::GLOBAL ); } else { BattleLose( hero, res, true ); } } else { // Modify luck AudioManager::PlaySound( M82::BADLUCK ); const std::string msg = _( "You come upon the pyramid of a great and ancient king.\nRoutine exploration reveals that the pyramid is completely empty." ); const fheroes2::LuckDialogElement luckUI( false ); fheroes2::showMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK, { &luckUI, &luckUI } ); hero.SetVisited( dst_index, Visit::LOCAL ); hero.SetVisited( dst_index, Visit::GLOBAL ); } } } void ActionToSign( const Heroes & hero, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) #ifndef WITH_DEBUG (void)hero; #endif const MapSign * sign = dynamic_cast( world.GetMapObject( dst_index ) ); fheroes2::showStandardTextMessage( MP2::StringObject( MP2::OBJ_SIGN ), ( sign ? sign->message : "" ), Dialog::OK ); } void ActionToMagicWell( Heroes & hero, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) const uint32_t max = hero.GetMaxSpellPoints(); const std::string title( MP2::StringObject( MP2::OBJ_MAGIC_WELL ) ); if ( hero.GetSpellPoints() >= max ) { fheroes2::showStandardTextMessage( title, _( "A drink at the well is supposed to restore your spell points, but you are already at maximum." ), Dialog::OK ); } else { if ( hero.isObjectTypeVisited( MP2::OBJ_MAGIC_WELL ) ) { const MusicalEffectPlayer musicalEffectPlayer( MUS::WATERSPRING ); fheroes2::showStandardTextMessage( title, _( "A second drink at the well in one day will not help you." ), Dialog::OK ); } else { hero.SetSpellPoints( max ); { const MusicalEffectPlayer musicalEffectPlayer( MUS::WATERSPRING ); fheroes2::showStandardTextMessage( title, _( "A drink from the well has restored your spell points to maximum." ), Dialog::OK ); } hero.SetVisited( dst_index ); } } } void ActionToTradingPost( const Heroes & hero ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) Dialog::Marketplace( hero.GetKingdom(), true ); } void ActionToPrimarySkillObject( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() << ", object: " << MP2::StringObject( objectType ) ) const Maps::Tiles & tile = world.GetTiles( dst_index ); std::string msg; int skill = Skill::Primary::ATTACK; bool visited = hero.isVisited( tile ); switch ( objectType ) { case MP2::OBJ_FORT: skill = Skill::Primary::DEFENSE; msg = visited ? _( "\"I'm sorry sir,\" The leader of the soldiers says, \"but you already know everything we have to teach.\"" ) : _( "The soldiers living in the fort teach you a few new defensive tricks." ); break; case MP2::OBJ_MERCENARY_CAMP: skill = Skill::Primary::ATTACK; msg = visited ? _( "You've come upon a mercenary camp practicing their tactics. \"You're too advanced for us,\" the mercenary captain says. \"We can teach nothing more.\"" ) : _( "You've come upon a mercenary camp practicing their tactics. The mercenaries welcome you and your troops and invite you to train with them." ); break; case MP2::OBJ_WITCH_DOCTORS_HUT: skill = Skill::Primary::KNOWLEDGE; msg = visited ? _( "\"Go 'way!\", the witch doctor barks, \"you know all I know.\"" ) : _( "An Orcish witch doctor living in the hut deepens your knowledge of magic by showing you how to cast stones, read portents, and decipher the intricacies of chicken entrails." ); break; case MP2::OBJ_STANDING_STONES: skill = Skill::Primary::POWER; msg = visited ? _( "You've found a group of Druids worshipping at one of their strange stone edifices. Silently, the Druids turn you away, indicating they have nothing new to teach you." ) : _( "You've found a group of Druids worshipping at one of their strange stone edifices. Silently, they teach you new ways to cast spells." ); break; default: return; } const std::string title( MP2::StringObject( objectType ) ); if ( visited ) { fheroes2::showStandardTextMessage( title, msg, Dialog::OK ); } else { hero.IncreasePrimarySkill( skill ); { const MusicalEffectPlayer musicalEffectPlayer( MUS::SKILL ); const fheroes2::PrimarySkillDialogElement primarySkillUI( skill, "+1" ); fheroes2::showMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK, { &primarySkillUI } ); } hero.SetVisited( dst_index ); hero.SetVisitedWideTile( dst_index, objectType ); } } void ActionToPoorMoraleObject( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() << ", object: " << MP2::StringObject( objectType ) ) Maps::Tiles & tile = world.GetTiles( dst_index ); const Funds funds = getFundsFromTile( tile ); assert( funds.GetValidItemsCount() == 0 || ( funds.GetValidItemsCount() == 1 && funds.gold > 0 ) ); uint32_t gold = funds.gold; std::string ask; std::string msg; std::string win; switch ( objectType ) { case MP2::OBJ_GRAVEYARD: ask = _( "You tentatively approach the burial ground of ancient warriors. Do you want to search the graves?" ); msg = _( "You spend several hours searching the graves and find nothing. Such a despicable act reduces your army's morale." ); win = _( "Upon defeating the Zombies you search the graves and find something!" ); break; case MP2::OBJ_SHIPWRECK: ask = _( "The rotting hulk of a great pirate ship creaks eerily as it is pushed against the rocks. Do you wish to search the shipwreck?" ); msg = _( "You spend several hours sifting through the debris and find nothing. Such a despicable act reduces your army's morale." ); win = _( "Upon defeating the Ghosts you sift through the debris and find something!" ); break; case MP2::OBJ_DERELICT_SHIP: ask = _( "The rotting hulk of a great pirate ship creaks eerily as it is pushed against the rocks. Do you wish to search the ship?" ); msg = _( "You spend several hours sifting through the debris and find nothing. Such a despicable act reduces your army's morale." ); win = _( "Upon defeating the Skeletons you sift through the debris and find something!" ); break; default: break; } const std::string title( MP2::StringObject( objectType ) ); { const MusicalEffectPlayer musicalEffectPlayer( MUS::WATCHTOWER ); if ( fheroes2::showStandardTextMessage( title, ask, Dialog::YES | Dialog::NO ) != Dialog::YES ) { return; } } bool complete = false; if ( gold ) { Army army( tile ); Battle::Result res = Battle::Loader( hero.GetArmy(), army, dst_index ); if ( res.AttackerWins() ) { hero.IncreaseExperience( res.GetExperienceAttacker() ); complete = true; Artifact art; if ( MP2::isArtifactObject( objectType ) ) { art = getArtifactFromTile( tile ); } if ( art.isValid() ) { if ( hero.IsFullBagArtifacts() ) { gold = GoldInsteadArtifact( objectType ); const fheroes2::ResourceDialogElement goldUI( Resource::GOLD, std::to_string( gold ) ); fheroes2::showMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( win, fheroes2::FontType::normalWhite() ), Dialog::OK, { &goldUI } ); } else { const fheroes2::ResourceDialogElement goldUI( Resource::GOLD, std::to_string( gold ) ); const fheroes2::ArtifactDialogElement artifactUI( art ); fheroes2::showMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( win, fheroes2::FontType::normalWhite() ), Dialog::OK, { &artifactUI, &goldUI } ); hero.PickupArtifact( art ); } } else { const fheroes2::ResourceDialogElement goldUI( Resource::GOLD, std::to_string( gold ) ); fheroes2::showMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( win, fheroes2::FontType::normalWhite() ), Dialog::OK, { &goldUI } ); } hero.GetKingdom().AddFundsResource( Funds( Resource::GOLD, gold ) ); } else { BattleLose( hero, res, true ); } } if ( complete ) { resetObjectInfoOnTile( tile ); hero.SetVisited( dst_index, Visit::GLOBAL ); } else if ( 0 == gold ) { // Modify morale hero.SetVisited( dst_index, Visit::LOCAL ); hero.SetVisited( dst_index, Visit::GLOBAL ); AudioManager::PlaySound( M82::BADMRLE ); const fheroes2::MoraleDialogElement moraleUI( false ); fheroes2::showMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK, { &moraleUI } ); } } void ActionToGoodMoraleObject( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() << ", object: " << MP2::StringObject( objectType ) ) std::string msg; uint32_t move = 0; bool visited = hero.isObjectTypeVisited( objectType ); switch ( objectType ) { case MP2::OBJ_BUOY: msg = visited ? _( "Your men spot a navigational buoy, confirming that you are on course." ) : _( "Your men spot a navigational buoy, confirming that you are on course and increasing their morale." ); break; case MP2::OBJ_OASIS: msg = visited ? _( "The drink at the oasis is refreshing, but offers no further benefit. The oasis might help again if you fought a battle first." ) : _( "A drink at the oasis fills your troops with strength and lifts their spirits. You can travel a bit further today." ); move = GameStatic::getMovementPointBonus( MP2::OBJ_OASIS ); break; case MP2::OBJ_WATERING_HOLE: msg = visited ? _( "The drink at the watering hole is refreshing, but offers no further benefit. The watering hole might help again if you fought a battle first." ) : _( "A drink at the watering hole fills your troops with strength and lifts their spirits. You can travel a bit further today." ); move = GameStatic::getMovementPointBonus( MP2::OBJ_WATERING_HOLE ); break; case MP2::OBJ_TEMPLE: msg = visited ? _( "It doesn't help to pray twice before a battle. Come back after you've fought." ) : _( "A visit and a prayer at the temple raises the morale of your troops." ); break; default: return; } const std::string title( MP2::StringObject( objectType ) ); // check already visited if ( visited ) { fheroes2::showStandardTextMessage( title, msg, Dialog::OK ); } else { // modify morale hero.SetVisited( dst_index ); AudioManager::PlaySound( M82::GOODMRLE ); const fheroes2::MoraleDialogElement moraleUI( true ); std::vector elementUI{ &moraleUI }; if ( objectType == MP2::OBJ_TEMPLE ) { elementUI.emplace_back( &moraleUI ); } fheroes2::showMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK, elementUI ); hero.IncreaseMovePoints( move ); // fix double action tile hero.SetVisitedWideTile( dst_index, objectType ); } } void ActionToExperienceObject( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() << ", object: " << MP2::StringObject( objectType ) ) const Maps::Tiles & tile = world.GetTiles( dst_index ); bool visited = hero.isVisited( tile ); std::string msg; int32_t exp = 0; switch ( objectType ) { case MP2::OBJ_GAZEBO: msg = visited ? _( "An old Knight appears on the steps of the gazebo. \"I am sorry, my liege, I have taught you all I can.\"" ) : _( "An old Knight appears on the steps of the gazebo. \"My liege, I will teach you all that I know to aid you in your travels.\"" ); exp = 1000; break; default: // Are you calling this function for a new object? Add the logic above! assert( 0 ); return; } const std::string title( MP2::StringObject( objectType ) ); if ( visited ) { fheroes2::showStandardTextMessage( title, msg, Dialog::OK ); } else { { const MusicalEffectPlayer musicalEffectPlayer; if ( MusicalEffectPlayer::canBePlayed( MUS::EXPERIENCE ) ) { MusicalEffectPlayer::play( MUS::EXPERIENCE ); } else { AudioManager::PlaySound( M82::EXPERNCE ); } const fheroes2::ExperienceDialogElement experienceUI( exp ); fheroes2::showMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK, { &experienceUI } ); } hero.IncreaseExperience( exp ); hero.SetVisited( dst_index ); } } void ActionToShipwreckSurvivor( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) Maps::Tiles & tile = world.GetTiles( dst_index ); const std::string title( MP2::StringObject( objectType ) ); if ( hero.IsFullBagArtifacts() ) { const uint32_t gold = GoldInsteadArtifact( objectType ); const fheroes2::ResourceDialogElement goldUI( Resource::GOLD, std::to_string( gold ) ); fheroes2::showMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( _( "You've pulled a shipwreck survivor from certain death in an unforgiving ocean. Grateful, he says, " "\"I would give you an artifact as a reward, but you're all full.\"" ), fheroes2::FontType::normalWhite() ), Dialog::OK, { &goldUI } ); hero.GetKingdom().AddFundsResource( Funds( Resource::GOLD, gold ) ); } else { const Artifact & art = getArtifactFromTile( tile ); std::string str = _( "You've pulled a shipwreck survivor from certain death in an unforgiving ocean. Grateful, he rewards you for your act of kindness by giving you the %{art}." ); StringReplace( str, "%{art}", art.GetName() ); AudioManager::PlaySound( M82::TREASURE ); const fheroes2::ArtifactDialogElement artifactUI( art ); fheroes2::showMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( str, fheroes2::FontType::normalWhite() ), Dialog::OK, { &artifactUI } ); hero.PickupArtifact( art ); } Game::PlayPickupSound(); Interface::AdventureMap::Get().getGameArea().runSingleObjectAnimation( std::make_shared( tile.GetObjectUID(), tile.GetIndex(), tile.GetObject() ) ); resetObjectInfoOnTile( tile ); } void ActionToArtifact( Heroes & hero, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) Maps::Tiles & tile = world.GetTiles( dst_index ); const std::string title( MP2::StringObject( MP2::OBJ_ARTIFACT ) ); if ( hero.IsFullBagArtifacts() ) fheroes2::showStandardTextMessage( title, _( "You cannot pick up this artifact, you already have a full load!" ), Dialog::OK ); else { const Maps::ArtifactCaptureCondition condition = getArtifactCaptureCondition( tile ); const Artifact art = getArtifactFromTile( tile ); bool result = false; std::string msg; if ( condition == Maps::ArtifactCaptureCondition::PAY_2000_GOLD || condition == Maps::ArtifactCaptureCondition::PAY_2500_GOLD_AND_3_RESOURCES || condition == Maps::ArtifactCaptureCondition::PAY_3000_GOLD_AND_5_RESOURCES ) { const Funds payment = getArtifactResourceRequirement( tile ); if ( condition == Maps::ArtifactCaptureCondition::PAY_2000_GOLD ) { msg = _( "A leprechaun offers you the %{art} for the small price of %{gold} Gold." ); StringReplace( msg, "%{gold}", payment.gold ); } else { msg = _( "A leprechaun offers you the %{art} for the small price of %{gold} Gold and %{count} %{res}." ); StringReplace( msg, "%{gold}", payment.gold ); for ( const int res : { Resource::WOOD, Resource::MERCURY, Resource::ORE, Resource::SULFUR, Resource::CRYSTAL, Resource::GEMS } ) { const uint32_t count = payment.Get( res ); if ( count > 0 ) { StringReplace( msg, "%{res}", Resource::String( res ) ); StringReplace( msg, "%{count}", static_cast( count ) ); break; } } } StringReplace( msg, "%{art}", art.GetName() ); msg += '\n'; msg.append( _( "Do you wish to buy this artifact?" ) ); AudioManager::PlaySound( M82::EXPERNCE ); const fheroes2::ArtifactDialogElement artifactUI( art ); const fheroes2::Text titleText( title, fheroes2::FontType::normalYellow() ); const fheroes2::Text bodyText( msg, fheroes2::FontType::normalWhite() ); if ( Dialog::YES == fheroes2::showMessage( titleText, bodyText, Dialog::YES | Dialog::NO, { &artifactUI } ) ) { if ( hero.GetKingdom().AllowPayment( payment ) ) { result = true; hero.GetKingdom().OddFundsResource( payment ); } else { fheroes2::showStandardTextMessage( title, _( "You try to pay the leprechaun, but realize that you can't afford it. The leprechaun stamps his foot and ignores you." ), Dialog::OK ); } } else { fheroes2::showStandardTextMessage( title, _( "Insulted by your refusal of his generous offer, the leprechaun stamps his foot and ignores you." ), Dialog::OK ); } } else if ( condition == Maps::ArtifactCaptureCondition::HAVE_WISDOM_SKILL || condition == Maps::ArtifactCaptureCondition::HAVE_LEADERSHIP_SKILL ) { const Skill::Secondary & skill = getArtifactSecondarySkillRequirement( tile ); if ( hero.HasSecondarySkill( skill.Skill() ) ) { const char * artifactDiscoveryDescription = Artifact::getDiscoveryDescription( art ); if ( artifactDiscoveryDescription != nullptr ) { msg = artifactDiscoveryDescription; } else { msg = _( "You've found the artifact: " ); msg += '\n'; msg.append( art.GetName() ); } AudioManager::PlaySound( M82::TREASURE ); const fheroes2::ArtifactDialogElement artifactUI( art ); fheroes2::showMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK, { &artifactUI } ); result = true; } else { if ( skill.Skill() == Skill::Secondary::WISDOM ) { msg = _( "You've found the humble dwelling of a withered hermit. The hermit tells you that he is willing to give the %{art} to the first wise person he meets." ); } else if ( skill.Skill() == Skill::Secondary::LEADERSHIP ) { msg = _( "You've come across the spartan quarters of a retired soldier. The soldier tells you that he is willing to pass on the %{art} to the first true leader he meets." ); } else { // Did you add a new condition? If yes add a proper if-else branch. assert( 0 ); msg = _( "You've encountered a strange person with a hat and an owl on it. He tells you that he is willing to give %{art} if you have %{skill}." ); StringReplace( msg, "%{skill}", skill.GetName() ); } StringReplace( msg, "%{art}", art.GetName() ); fheroes2::showStandardTextMessage( title, msg, Dialog::OK ); } } else if ( condition >= Maps::ArtifactCaptureCondition::FIGHT_50_ROGUES && condition <= Maps::ArtifactCaptureCondition::FIGHT_1_BONE_DRAGON ) { bool battle = true; Army army( tile ); const Troop * troop = army.GetFirstValid(); if ( troop ) { if ( Monster::ROGUE == troop->GetID() ) fheroes2::showStandardTextMessage( title, _( "You come upon an ancient artifact. As you reach for it, a pack of Rogues leap out of the brush to guard their stolen loot." ), Dialog::OK ); else { msg = _( "Through a clearing you observe an ancient artifact. Unfortunately, it's guarded by a nearby %{monster}. Do you want to fight the %{monster} for the artifact?" ); StringReplaceWithLowercase( msg, "%{monster}", troop->GetName() ); battle = ( Dialog::YES == fheroes2::showStandardTextMessage( title, msg, Dialog::YES | Dialog::NO ) ); } } if ( battle ) { Battle::Result res = Battle::Loader( hero.GetArmy(), army, dst_index ); if ( res.AttackerWins() ) { hero.IncreaseExperience( res.GetExperienceAttacker() ); result = true; msg = _( "Victorious, you take your prize, the %{art}." ); StringReplace( msg, "%{art}", art.GetName() ); AudioManager::PlaySound( M82::TREASURE ); const fheroes2::ArtifactDialogElement artifactUI( art ); fheroes2::showMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK, { &artifactUI } ); } else { BattleLose( hero, res, true ); } } else { fheroes2::showStandardTextMessage( title, _( "Discretion is the better part of valor, and you decide to avoid this fight for today." ), Dialog::OK ); } } else { const char * artifactDiscoveryDescription = Artifact::getDiscoveryDescription( art ); if ( artifactDiscoveryDescription != nullptr ) { msg = artifactDiscoveryDescription; } else { msg = _( "You've found the artifact: " ); msg += '\n'; msg.append( art.GetName() ); } AudioManager::PlaySound( M82::TREASURE ); const fheroes2::ArtifactDialogElement artifactUI( art ); fheroes2::showMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK, { &artifactUI } ); result = true; } if ( result && hero.PickupArtifact( art ) ) { Game::PlayPickupSound(); Interface::AdventureMap & I = Interface::AdventureMap::Get(); I.getGameArea().runSingleObjectAnimation( std::make_shared( tile.GetObjectUID(), tile.GetIndex(), tile.GetObject() ) ); resetObjectInfoOnTile( tile ); const fheroes2::Point artifactPosition = Maps::GetPoint( dst_index ); // Update the position of picked up artifact on radar to remove its mark. I.getRadar().SetRenderArea( { artifactPosition.x, artifactPosition.y, 1, 1 } ); I.setRedraw( Interface::REDRAW_RADAR ); } } } void ActionToTreasureChest( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) Maps::Tiles & tile = world.GetTiles( dst_index ); const std::string & hdr = MP2::StringObject( objectType ); std::string msg; const Funds funds = getFundsFromTile( tile ); assert( funds.gold > 0 || funds.GetValidItemsCount() == 0 ); uint32_t gold = funds.gold; // dialog if ( tile.isWater() ) { if ( gold ) { const Artifact & art = getArtifactFromTile( tile ); if ( art.isValid() ) { if ( hero.IsFullBagArtifacts() ) { gold = GoldInsteadArtifact( objectType ); msg = _( "After spending hours trying to fish the chest out of the sea, you open it and find %{gold} gold pieces." ); StringReplace( msg, "%{gold}", gold ); const fheroes2::ResourceDialogElement goldUI( Resource::GOLD, std::to_string( gold ) ); fheroes2::showMessage( fheroes2::Text( hdr, fheroes2::FontType::normalYellow() ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK, { &goldUI } ); } else { msg = _( "After spending hours trying to fish the chest out of the sea, you open it and find %{gold} gold and the %{art}." ); StringReplace( msg, "%{gold}", gold ); StringReplace( msg, "%{art}", art.GetName() ); const fheroes2::ResourceDialogElement goldUI( Resource::GOLD, std::to_string( gold ) ); const fheroes2::ArtifactDialogElement artifactUI( art ); fheroes2::showMessage( fheroes2::Text( hdr, fheroes2::FontType::normalYellow() ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK, { &artifactUI, &goldUI } ); hero.PickupArtifact( art ); } } else { msg = _( "After spending hours trying to fish the chest out of the sea, you open it and find %{gold} gold pieces." ); StringReplace( msg, "%{gold}", gold ); const fheroes2::ResourceDialogElement goldUI( Resource::GOLD, std::to_string( gold ) ); fheroes2::showMessage( fheroes2::Text( hdr, fheroes2::FontType::normalYellow() ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK, { &goldUI } ); } } else { fheroes2::showStandardTextMessage( hdr, _( "After spending hours trying to fish the chest out of the sea, you open it, only to find it empty." ), Dialog::OK ); } } else { const Artifact & art = getArtifactFromTile( tile ); if ( gold ) { const uint32_t expr = gold > 500 ? gold - 500 : 500; msg = _( "After scouring the area, you fall upon a hidden treasure cache. You may take the gold or distribute the gold to the peasants for experience. Do you wish to keep the gold?" ); if ( !Dialog::SelectGoldOrExp( hdr, msg, gold, expr, hero ) ) { gold = 0; hero.IncreaseExperience( expr ); } } else if ( art.isValid() ) { if ( hero.IsFullBagArtifacts() ) { gold = GoldInsteadArtifact( objectType ); msg = _( "After scouring the area, you fall upon a hidden chest, containing the %{gold} gold pieces." ); StringReplace( msg, "%{gold}", gold ); const fheroes2::ResourceDialogElement goldUI( Resource::GOLD, std::to_string( gold ) ); fheroes2::showMessage( fheroes2::Text( hdr, fheroes2::FontType::normalYellow() ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK, { &goldUI } ); } else { msg = _( "After scouring the area, you fall upon a hidden chest, containing the ancient artifact %{art}." ); StringReplace( msg, "%{art}", art.GetName() ); AudioManager::PlaySound( M82::TREASURE ); const fheroes2::ArtifactDialogElement artifactUI( art ); fheroes2::showMessage( fheroes2::Text( hdr, fheroes2::FontType::normalYellow() ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK, { &artifactUI } ); hero.PickupArtifact( art ); } } } if ( gold ) { hero.GetKingdom().AddFundsResource( Funds( Resource::GOLD, gold ) ); } Game::PlayPickupSound(); Interface::AdventureMap::Get().getGameArea().runSingleObjectAnimation( std::make_shared( tile.GetObjectUID(), tile.GetIndex(), tile.GetObject() ) ); resetObjectInfoOnTile( tile ); } void ActionToGenieLamp( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) Maps::Tiles & tile = world.GetTiles( dst_index ); const Troop & troop = getTroopFromTile( tile ); if ( !troop.isValid() ) { return; } const std::string title( MP2::StringObject( objectType ) ); bool recruit = false; { const MusicalEffectPlayer musicalEffectPlayer( MUS::ARABIAN ); recruit = ( fheroes2::showStandardTextMessage( title, _( "You stumble upon a dented and tarnished lamp lodged deep in the earth. Do you wish to rub the lamp?" ), Dialog::YES | Dialog::NO ) == Dialog::YES ); } if ( recruit ) { RecruitMonsterFromTile( hero, tile, title, troop, true ); } } void ActionToTeleports( Heroes & hero, int32_t index_from ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) const int32_t index_to = world.NextTeleport( index_from ); if ( index_from == index_to ) { DEBUG_LOG( DBG_GAME, DBG_WARN, hero.GetName() << " has nowhere to go through stone liths" ) AudioManager::PlaySound( M82::RSBRYFZL ); return; } assert( world.GetTiles( index_to ).GetObject() != MP2::OBJ_HERO ); AudioManager::PlaySound( M82::KILLFADE ); hero.ShowPath( false ); hero.FadeOut(); const fheroes2::Point fromPoint = Maps::GetPoint( index_from ); hero.Scout( index_to ); hero.Move2Dest( index_to ); hero.GetPath().Reset(); // Clear the previous hero position Interface::AdventureMap & I = Interface::AdventureMap::Get(); I.getRadar().SetRenderArea( { fromPoint.x, fromPoint.y, 1, 1 } ); I.redraw( Interface::REDRAW_RADAR ); I.getGameArea().SetCenter( hero.GetCenter() ); I.getRadar().SetRenderArea( hero.GetScoutRoi() ); I.setRedraw( Interface::REDRAW_GAMEAREA | Interface::REDRAW_RADAR ); AudioManager::PlaySound( M82::KILLFADE ); hero.FadeIn(); hero.ShowPath( true ); hero.ActionNewPosition( false ); } void ActionToWhirlpools( Heroes & hero, int32_t index_from ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) const int32_t index_to = world.NextWhirlpool( index_from ); if ( index_from == index_to ) { DEBUG_LOG( DBG_GAME, DBG_WARN, hero.GetName() << " has nowhere to go through the whirlpool" ) AudioManager::PlaySound( M82::RSBRYFZL ); return; } AudioManager::PlaySound( M82::KILLFADE ); hero.ShowPath( false ); hero.FadeOut(); const fheroes2::Point fromPoint = Maps::GetPoint( index_from ); hero.Scout( index_to ); hero.Move2Dest( index_to ); hero.GetPath().Reset(); WhirlpoolTroopLoseEffect( hero ); // Clear the previous hero position Interface::AdventureMap & I = Interface::AdventureMap::Get(); I.getRadar().SetRenderArea( { fromPoint.x, fromPoint.y, 1, 1 } ); I.redraw( Interface::REDRAW_RADAR ); I.getGameArea().SetCenter( hero.GetCenter() ); I.getRadar().SetRenderArea( hero.GetScoutRoi() ); I.setRedraw( Interface::REDRAW_GAMEAREA | Interface::REDRAW_RADAR ); AudioManager::PlaySound( M82::KILLFADE ); hero.FadeIn(); hero.ShowPath( true ); hero.ActionNewPosition( false ); } void ActionToCaptureObject( Heroes & hero, const MP2::MapObjectType objectType, const int32_t dstIndex ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() << " object: " << MP2::StringObject( objectType ) ) Maps::Tiles & tile = world.GetTiles( dstIndex ); if ( !hero.isFriends( getColorFromTile( tile ) ) ) { const auto updateRadar = [objectType, dstIndex]() { // TODO: make a function that will automatically get the object size in tiles and return a ROI for radar update. // Set the radar update ROI according to captured object size and position. fheroes2::Rect radarRoi( Maps::GetPoint( dstIndex ), { 1, 1 } ); switch ( objectType ) { case MP2::OBJ_SAWMILL: radarRoi.x -= 2; --radarRoi.y; radarRoi.width = 4; radarRoi.height = 2; break; case MP2::OBJ_ALCHEMIST_LAB: case MP2::OBJ_MINE: --radarRoi.x; --radarRoi.y; radarRoi.width = 3; radarRoi.height = 2; break; case MP2::OBJ_LIGHTHOUSE: radarRoi.y -= 2; radarRoi.height = 3; break; default: break; } Interface::AdventureMap & I = Interface::AdventureMap::Get(); // Update the object on radar. I.getRadar().SetRenderArea( radarRoi ); I.redraw( Interface::REDRAW_GAMEAREA | Interface::REDRAW_RADAR ); }; const auto removeObjectProtection = [&tile]() { // Clear any metadata related to spells if ( tile.GetObject( false ) == MP2::OBJ_MINE ) { removeMineSpellFromTile( tile ); } }; const auto captureObject = [&hero, objectType, &tile, &updateRadar, &removeObjectProtection]() { removeObjectProtection(); setColorOnTile( tile, hero.GetColor() ); updateRadar(); std::string header; std::string body; int32_t resource = Resource::UNKNOWN; switch ( objectType ) { case MP2::OBJ_ALCHEMIST_LAB: resource = Resource::MERCURY; header = MP2::StringObject( objectType ); body = _( "You have taken control of the local Alchemist shop. It will provide you with %{count} unit of Mercury per day." ); break; case MP2::OBJ_SAWMILL: resource = Resource::WOOD; header = MP2::StringObject( objectType ); body = _( "You gain control of a sawmill. It will provide you with %{count} units of wood per day." ); break; case MP2::OBJ_MINE: { resource = getDailyIncomeObjectResources( tile ).getFirstValidResource().first; header = Maps::GetMineName( resource ); switch ( resource ) { case Resource::ORE: body = _( "You gain control of an ore mine. It will provide you with %{count} units of ore per day." ); break; case Resource::SULFUR: body = _( "You gain control of a sulfur mine. It will provide you with %{count} unit of sulfur per day." ); break; case Resource::CRYSTAL: body = _( "You gain control of a crystal mine. It will provide you with %{count} unit of crystal per day." ); break; case Resource::GEMS: body = _( "You gain control of a gem mine. It will provide you with %{count} unit of gems per day." ); break; case Resource::GOLD: body = _( "You gain control of a gold mine. It will provide you with %{count} gold per day." ); break; default: break; } break; } case MP2::OBJ_LIGHTHOUSE: header = MP2::StringObject( objectType ); body = _( "The lighthouse is now under your control, and all of your ships will now move further each day." ); break; default: body = _( "You gain control of a %{name}." ); header = MP2::StringObject( objectType ); StringReplace( body, "%{name}", MP2::StringObject( objectType ) ); break; } { const MusicalEffectPlayer musicalEffectPlayer; // The Lighthouse has a special sound if ( objectType == MP2::OBJ_LIGHTHOUSE ) { MusicalEffectPlayer::play( MUS::XANADU ); } if ( resource == Resource::UNKNOWN ) { fheroes2::showStandardTextMessage( header, body, Dialog::OK ); } else { DialogCaptureResourceObject( header, body, resource ); } } }; if ( isCaptureObjectProtected( tile ) ) { Army army( tile ); Battle::Result result = Battle::Loader( hero.GetArmy(), army, dstIndex ); if ( result.AttackerWins() ) { hero.IncreaseExperience( result.GetExperienceAttacker() ); captureObject(); } else { // The army should include only the original monsters const uint32_t monstersLeft = army.getTotalCount(); assert( monstersLeft == army.GetCountMonsters( getMonsterFromTile( tile ) ) ); Troop & troop = world.GetCapturedObject( dstIndex ).GetTroop(); troop.SetCount( monstersLeft ); // If all the guards are defeated, but the hero has lost the battle, // just remove the protection from the object if ( monstersLeft == 0 ) { removeObjectProtection(); } BattleLose( hero, result, true ); } } else { captureObject(); } } } void ActionToAbandonedMine( Heroes & hero, const MP2::MapObjectType objectType, const int32_t dstIndex ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) bool enter = false; { const MusicalEffectPlayer musicalEffectPlayer( MUS::WATCHTOWER ); enter = ( fheroes2::showStandardTextMessage( MP2::StringObject( objectType ), _( "You come upon an abandoned gold mine. The mine appears to be haunted. Do you wish to enter?" ), Dialog::YES | Dialog::NO ) == Dialog::YES ); } if ( enter ) { Maps::Tiles & tile = world.GetTiles( dstIndex ); Army army( tile ); Battle::Result result = Battle::Loader( hero.GetArmy(), army, dstIndex ); if ( result.AttackerWins() ) { hero.IncreaseExperience( result.GetExperienceAttacker() ); Maps::restoreAbandonedMine( tile, Resource::GOLD ); hero.setObjectTypeUnderHero( MP2::OBJ_MINE ); setColorOnTile( tile, hero.GetColor() ); // TODO: make a function that will automatically get the object size in tiles and return a ROI for radar update. // Set the radar update ROI according to captured object size and position. const fheroes2::Point tilePoint = Maps::GetPoint( dstIndex ); const fheroes2::Rect radarRoi( tilePoint.x - 1, tilePoint.y - 1, 3, 2 ); Interface::AdventureMap & I = Interface::AdventureMap::Get(); // Update the object on radar. I.getRadar().SetRenderArea( radarRoi ); I.redraw( Interface::REDRAW_GAMEAREA | Interface::REDRAW_RADAR ); fheroes2::showStandardTextMessage( MP2::StringObject( objectType ), _( "You beat the Ghosts and are able to restore the mine to production." ), Dialog::OK ); } else { BattleLose( hero, result, true ); } } } void ActionToDwellingJoinMonster( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() << ", object: " << MP2::StringObject( objectType ) ) Maps::Tiles & tile = world.GetTiles( dst_index ); const Troop & troop = getTroopFromTile( tile ); const std::string title( MP2::StringObject( objectType ) ); if ( troop.isValid() ) { std::string message = _( "A group of %{monster} with a desire for greater glory wish to join you. Do you accept?" ); StringReplaceWithLowercase( message, "%{monster}", troop.GetMultiName() ); bool recruit = false; { const MusicalEffectPlayer musicalEffectPlayer; // The Tree House has a special sound if ( objectType == MP2::OBJ_TREE_HOUSE && MusicalEffectPlayer::canBePlayed( MUS::TREEHOUSE ) ) { MusicalEffectPlayer::play( MUS::TREEHOUSE ); } else { AudioManager::PlaySound( M82::EXPERNCE ); } recruit = ( fheroes2::showStandardTextMessage( title, message, Dialog::YES | Dialog::NO ) == Dialog::YES ); } if ( recruit ) { if ( !hero.GetArmy().CanJoinTroop( troop ) ) { fheroes2::showStandardTextMessage( troop.GetName(), _( "You are unable to recruit at this time, your ranks are full." ), Dialog::OK ); } else { setMonsterCountOnTile( tile, 0 ); hero.GetArmy().JoinTroop( troop ); Interface::AdventureMap::Get().setRedraw( Interface::REDRAW_STATUS ); } } } else { fheroes2::showStandardTextMessage( title, _( "As you approach the dwelling, you notice that there is no one here." ), Dialog::OK ); } hero.SetVisited( dst_index, Visit::GLOBAL ); } void ActionToDwellingRecruitMonster( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() << ", object: " << MP2::StringObject( objectType ) ) Maps::Tiles & tile = world.GetTiles( dst_index ); std::string msg_full; std::string msg_void; switch ( objectType ) { case MP2::OBJ_RUINS: msg_void = _( "You search the ruins, but the Medusas that used to live here are gone. Perhaps there will be more next week." ); msg_full = _( "You've found some Medusas living in the ruins. They are willing to join your army for a price. Do you want to recruit Medusas?" ); break; case MP2::OBJ_TREE_CITY: msg_void = _( "You've found a Sprite Tree City. Unfortunately, none of the Sprites living there wish to join an army. Maybe next week." ); msg_full = _( "Some of the Sprites living in the tree city are willing to join your army for a price. Do you want to recruit Sprites?" ); break; case MP2::OBJ_WAGON_CAMP: msg_void = _( "A colorful Rogues' wagon stands empty here. Perhaps more Rogues will be here later." ); msg_full = _( "Distant sounds of music and laughter draw you to a colorful wagon housing Rogues. Do you wish to have any Rogues join your army?" ); break; case MP2::OBJ_DESERT_TENT: msg_void = _( "A group of tattered tents, billowing in the sandy wind, beckons you. The tents are unoccupied. Perhaps more Nomads will be here later." ); msg_full = _( "A group of tattered tents, billowing in the sandy wind, beckons you. Do you wish to have any Nomads join you during your travels?" ); break; case MP2::OBJ_EARTH_ALTAR: msg_void = _( "The pit of mud bubbles for a minute and then lies still." ); msg_full = _( "As you approach the bubbling pit of mud, creatures begin to climb out and position themselves around it. In unison they say: \"Mother Earth would like to offer you a few of her troops. Do you want to recruit Earth Elementals?\"" ); break; case MP2::OBJ_AIR_ALTAR: msg_void = _( "You enter the structure of white stone pillars, and find nothing." ); msg_full = _( "White stone pillars support a roof that rises up to the sky. As you enter the structure, the dead air of the outside gives way to a whirling gust that almost pushes you back out. The air current materializes into a barely visible form. The creature asks, in what can only be described as a loud whisper: \"Why have you come? Are you here to call upon the forces of the air?\"" ); break; case MP2::OBJ_FIRE_ALTAR: msg_void = _( "No Fire Elementals approach you from the lava pool." ); msg_full = _( "Beneath a structure that serves to hold in heat, Fire Elementals move about in a fiery pool of molten lava. A group of them approach you and offer their services. Would you like to recruit Fire Elementals?" ); break; case MP2::OBJ_WATER_ALTAR: msg_void = _( "A face forms in the water for a moment, and then is gone." ); msg_full = _( "Crystalline structures cast shadows over a small reflective pool of water. You peer into the pool, and a face that is not your own peers back. It asks: \"Would you like to call upon the powers of water?\"" ); break; case MP2::OBJ_BARROW_MOUNDS: msg_void = _( "This burial site is deathly still." ); msg_full = _( "Restless spirits of long dead warriors seeking their final resting place offer to join you in hopes of finding peace. Do you wish to recruit ghosts?" ); break; default: return; } const Troop & troop = getTroopFromTile( tile ); const std::string title( MP2::StringObject( objectType ) ); if ( !troop.isValid() ) { fheroes2::showStandardTextMessage( title, msg_void, Dialog::OK ); } else { bool recruit = false; { const MusicalEffectPlayer musicalEffectPlayer; // The Tree City and the Wagon Camp have a special sound if ( ( objectType == MP2::OBJ_TREE_CITY || objectType == MP2::OBJ_WAGON_CAMP ) && MusicalEffectPlayer::canBePlayed( MUS::TREEHOUSE ) ) { MusicalEffectPlayer::play( MUS::TREEHOUSE ); } // Changed OG selection to something more appropriate else if ( objectType == MP2::OBJ_DESERT_TENT && MusicalEffectPlayer::canBePlayed( MUS::ARABIAN ) ) { MusicalEffectPlayer::play( MUS::ARABIAN ); } else { AudioManager::PlaySound( M82::EXPERNCE ); } recruit = ( fheroes2::showStandardTextMessage( title, msg_full, Dialog::YES | Dialog::NO ) == Dialog::YES ); } if ( recruit ) { RecruitMonsterFromTile( hero, tile, title, troop, false ); } } hero.SetVisited( dst_index, Visit::GLOBAL ); } void ActionToDwellingBattleMonster( Heroes & hero, const MP2::MapObjectType objectType, const int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() << ", object: " << MP2::StringObject( objectType ) ) const std::string title( MP2::StringObject( objectType ) ); const char * objectIsEmptyMsg = nullptr; const char * recruitmentAvailableMsg = nullptr; const char * warningMsg = nullptr; const char * victoryMsg = nullptr; switch ( objectType ) { case MP2::OBJ_CITY_OF_DEAD: objectIsEmptyMsg = _( "The City of the Dead is empty of life, and empty of unlife as well. Perhaps some undead will move in next week." ); recruitmentAvailableMsg = _( "Some Liches living here are willing to join your army for a price. Do you want to recruit Liches?" ); warningMsg = _( "You've found the ruins of an ancient city, now inhabited solely by the undead. Will you search?" ); victoryMsg = _( "Some of the surviving Liches are impressed by your victory over their fellows, and offer to join you for a price. Do you want to recruit Liches?" ); break; case MP2::OBJ_TROLL_BRIDGE: objectIsEmptyMsg = _( "You've found one of those bridges that Trolls are so fond of living under, but there are none here. Perhaps there will be some next week." ); recruitmentAvailableMsg = _( "Some Trolls living under a bridge are willing to join your army, but for a price. Do you want to recruit Trolls?" ); warningMsg = _( "Trolls living under the bridge challenge you. Will you fight them?" ); victoryMsg = _( "A few Trolls remain, cowering under the bridge. They approach you and offer to join your forces as mercenaries. Do you want to buy any Trolls?" ); break; case MP2::OBJ_DRAGON_CITY: objectIsEmptyMsg = _( "The Dragon city has no Dragons willing to join you this week. Perhaps a Dragon will become available next week." ); recruitmentAvailableMsg = _( "The Dragon city is willing to offer some Dragons for your army for a price. Do you wish to recruit Dragons?" ); warningMsg = _( "You stand before the Dragon City, a place off-limits to mere humans. Do you wish to violate this rule and challenge the Dragons to a fight?" ); victoryMsg = _( "Having defeated the Dragon champions, the city's leaders agree to supply some Dragons to your army for a price. Do you wish to recruit Dragons?" ); break; default: assert( 0 ); return; } enum class Outcome { Invalid, Empty, IgnoreRecruit, IgnoreFight, Recruit, Fight }; const Outcome outcome = [dst_index, &title, objectIsEmptyMsg, recruitmentAvailableMsg, warningMsg]() { const Maps::Tiles & tile = world.GetTiles( dst_index ); if ( getColorFromTile( tile ) != Color::NONE ) { const Troop troop = getTroopFromTile( tile ); if ( !troop.isValid() ) { fheroes2::showStandardTextMessage( title, objectIsEmptyMsg, Dialog::OK ); return Outcome::Empty; } const MusicalEffectPlayer musicalEffectPlayer( MUS::DUNGEON ); if ( fheroes2::showStandardTextMessage( title, recruitmentAvailableMsg, Dialog::YES | Dialog::NO ) == Dialog::YES ) { return Outcome::Recruit; } return Outcome::IgnoreRecruit; } // In the original game, this dialog window has no sound, this is an improvement specific to fheroes2 const MusicalEffectPlayer musicalEffectPlayer( MUS::DUNGEON ); if ( fheroes2::showStandardTextMessage( title, warningMsg, Dialog::YES | Dialog::NO ) == Dialog::YES ) { return Outcome::Fight; } return Outcome::IgnoreFight; }(); Maps::Tiles & tile = world.GetTiles( dst_index ); switch ( outcome ) { case Outcome::Empty: case Outcome::IgnoreRecruit: hero.SetVisited( dst_index, Visit::GLOBAL ); break; case Outcome::IgnoreFight: break; case Outcome::Recruit: { const Troop troop = getTroopFromTile( tile ); assert( troop.isValid() ); RecruitMonsterFromTile( hero, tile, title, troop, false ); hero.SetVisited( dst_index, Visit::GLOBAL ); break; } case Outcome::Fight: { Army army( tile ); const Battle::Result res = Battle::Loader( hero.GetArmy(), army, dst_index ); if ( res.AttackerWins() ) { hero.IncreaseExperience( res.GetExperienceAttacker() ); // Set ownership of the dwelling to a Neutral (gray) player so that any player can recruit troops without a fight. setColorOnTile( tile, Color::UNUSED ); tile.SetObjectPassable( true ); if ( fheroes2::showStandardTextMessage( title, victoryMsg, Dialog::YES | Dialog::NO ) == Dialog::YES ) { const Troop troop = getTroopFromTile( tile ); assert( troop.isValid() ); RecruitMonsterFromTile( hero, tile, title, troop, false ); } hero.SetVisited( dst_index, Visit::GLOBAL ); } else { BattleLose( hero, res, true ); } break; } default: assert( 0 ); break; } } void ActionToObservationTower( const Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) { const MusicalEffectPlayer musicalEffectPlayer( MUS::WATCHTOWER ); fheroes2::showStandardTextMessage( MP2::StringObject( objectType ), _( "From the observation tower, you are able to see distant lands." ), Dialog::OK ); } const int32_t scoutRange = static_cast( GameStatic::getFogDiscoveryDistance( GameStatic::FogDiscoveryType::OBSERVATION_TOWER ) ); Maps::ClearFog( dst_index, scoutRange, hero.GetColor() ); Interface::AdventureMap & I = Interface::AdventureMap::Get(); const fheroes2::Point towerPosition = Maps::GetPoint( dst_index ); const fheroes2::Rect towerRoi( towerPosition.x - scoutRange, towerPosition.y - scoutRange, 2 * scoutRange + 1, 2 * scoutRange + 1 ); I.getRadar().SetRenderArea( towerRoi ); I.setRedraw( Interface::REDRAW_RADAR ); } void ActionToArtesianSpring( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) const std::string title( MP2::StringObject( MP2::OBJ_ARTESIAN_SPRING ) ); if ( world.isAnyKingdomVisited( objectType, dst_index ) ) { fheroes2::showStandardTextMessage( title, _( "The spring only refills once a week, and someone's already been here this week." ), Dialog::OK ); } else { const uint32_t max = hero.GetMaxSpellPoints(); if ( hero.GetSpellPoints() >= max * 2 ) { const MusicalEffectPlayer musicalEffectPlayer( MUS::WATERSPRING ); fheroes2:: showStandardTextMessage( title, _( "A drink at the spring is supposed to give you twice your normal spell points, but you are already at that level." ), Dialog::OK ); } else { hero.SetSpellPoints( max * 2 ); { const MusicalEffectPlayer musicalEffectPlayer( MUS::WATERSPRING ); fheroes2::showStandardTextMessage( title, _( "A drink from the spring fills your blood with magic! You have twice your normal spell points in reserve." ), Dialog::OK ); } } } hero.SetVisitedWideTile( dst_index, objectType, Visit::GLOBAL ); } void ActionToXanadu( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) const Maps::Tiles & tile = world.GetTiles( dst_index ); const std::string title( MP2::StringObject( objectType ) ); if ( hero.isVisited( tile ) ) { fheroes2::showStandardTextMessage( title, _( "Recognizing you, the butler refuses to admit you. \"The master,\" he says, \"will not see the same student twice.\"" ), Dialog::OK ); } else { if ( GameStatic::isHeroWorthyToVisitXanadu( hero ) ) { { const MusicalEffectPlayer musicalEffectPlayer( MUS::XANADU ); const fheroes2::SmallPrimarySkillDialogElement attackUI( Skill::Primary::ATTACK, "+1" ); const fheroes2::SmallPrimarySkillDialogElement defenseUI( Skill::Primary::DEFENSE, "+1" ); const fheroes2::SmallPrimarySkillDialogElement powerUI( Skill::Primary::POWER, "+1" ); const fheroes2::SmallPrimarySkillDialogElement knowledgeUI( Skill::Primary::KNOWLEDGE, "+1" ); fheroes2:: showMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( _( "The butler admits you to see the master of the house. He trains you in the four skills a hero should know." ), fheroes2::FontType::normalWhite() ), Dialog::OK, { &attackUI, &defenseUI, &powerUI, &knowledgeUI } ); } hero.IncreasePrimarySkill( Skill::Primary::ATTACK ); hero.IncreasePrimarySkill( Skill::Primary::DEFENSE ); hero.IncreasePrimarySkill( Skill::Primary::KNOWLEDGE ); hero.IncreasePrimarySkill( Skill::Primary::POWER ); hero.SetVisited( dst_index ); } else { fheroes2::showStandardTextMessage( title, _( "The butler opens the door and looks you up and down. \"You are neither famous nor diplomatic enough to be admitted to see my master,\" he sniffs. \"Come back when you think yourself worthy.\"" ), Dialog::OK ); } } } bool ActionToUpgradeArmy( Army & army, const Monster & mons, std::string & str1, std::string & str2, const bool combineWithAnd ) { const std::string combTypeAnd = _( " and " ); const std::string combTypeComma = ", "; if ( army.HasMonster( mons ) ) { army.UpgradeMonsters( mons ); if ( !str1.empty() ) str1 += combineWithAnd ? combTypeAnd : combTypeComma; str1 += mons.GetMultiName(); if ( !str2.empty() ) str2 += combineWithAnd ? combTypeAnd : combTypeComma; str2 += mons.GetUpgrade().GetMultiName(); return true; } return false; } void ActionToUpgradeArmyObject( Heroes & hero, const MP2::MapObjectType objectType, const std::string & defaultMessage ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() << ", object: " << MP2::StringObject( objectType ) ) std::string monsters; std::string monsters_upgrade; std::string msg1; std::string msg2; std::vector mons; std::vector monsToUpgrade; switch ( objectType ) { case MP2::OBJ_HILL_FORT: { monsToUpgrade = { Monster( Monster::OGRE ), Monster( Monster::ORC ), Monster( Monster::DWARF ) }; msg1 = _( "All of the %{monsters} you have in your army have been trained by the battle masters of the fort. Your army now contains %{monsters2}." ); msg2 = _( "An unusual alliance of Ogres, Orcs, and Dwarves offer to train (upgrade) any such troops brought to them. Unfortunately, you have none with you." ); break; } case MP2::OBJ_FREEMANS_FOUNDRY: { monsToUpgrade = { Monster( Monster::SWORDSMAN ), Monster( Monster::PIKEMAN ), Monster( Monster::IRON_GOLEM ) }; msg1 = _( "All of your %{monsters} have been upgraded into %{monsters2}." ); msg2 = _( "A blacksmith working at the foundry offers to convert all Pikemen and Swordsmen's weapons brought to him from iron to steel. He also says that he knows a process that will convert Iron Golems into Steel Golems. Unfortunately, you have none of these troops in your army, so he can't help you." ); break; } case MP2::OBJ_STABLES: { assert( !defaultMessage.empty() ); msg1 = defaultMessage; msg2 = defaultMessage; monsToUpgrade = { Monster( Monster::CAVALRY ) }; break; } default: ERROR_LOG( "Incorrect object type passed to ActionToUpgradeArmyObject" ) assert( 0 ); return; } if ( monsToUpgrade.empty() ) { ERROR_LOG( "monsToUpgrade mustn't be empty." ) assert( 0 ); return; } Army & heroArmy = hero.GetArmy(); mons.reserve( monsToUpgrade.size() ); for ( size_t i = 0; i < monsToUpgrade.size(); ++i ) { if ( !heroArmy.HasMonster( monsToUpgrade[i] ) ) continue; const bool combineWithAnd = i == ( monsToUpgrade.size() - 1 ) && !mons.empty(); if ( ActionToUpgradeArmy( heroArmy, monsToUpgrade[i], monsters, monsters_upgrade, combineWithAnd ) ) mons.emplace_back( &monsToUpgrade[i] ); } const std::string title( MP2::StringObject( objectType ) ); if ( !mons.empty() ) { // composite sprite int32_t offsetX = 0; int32_t offsetY = 0; const fheroes2::Sprite & border = fheroes2::AGG::GetICN( ICN::STRIP, 12 ); const int32_t monsterCount = static_cast( mons.size() ); // safe to do as the count is no more than 3 const int32_t monsterPerX = monsterCount > 1 ? 2 : 1; const int32_t monsterPerY = monsterCount == 1 ? 1 : ( monsterCount + ( monsterCount - 1 ) ) / 2; fheroes2::Image surface( border.width() * monsterPerX + ( monsterPerX - 1 ) * 4, border.height() * monsterPerY + ( monsterPerY - 1 ) * 4 ); surface.reset(); StringReplace( msg1, "%{monsters}", monsters ); StringReplace( msg1, "%{monsters2}", monsters_upgrade ); for ( size_t i = 0; i < mons.size(); ++i ) { if ( i > 0 && ( i & 1 ) == 0 ) { if ( i == mons.size() - 1 ) { offsetX = border.width() / 2 + 2; } else { offsetX = 0; } offsetY += border.height() + 4; } // border fheroes2::Blit( border, surface, offsetX, offsetY ); fheroes2::renderMonsterFrame( mons[i]->GetUpgrade(), surface, { 6 + offsetX, 6 + offsetY } ); offsetX += border.width() + 4; } { const MusicalEffectPlayer musicalEffectPlayer; // The Hill Fort has a special sound if ( objectType == MP2::OBJ_HILL_FORT ) { MusicalEffectPlayer::play( MUS::HILLFORT ); } const fheroes2::CustomImageDialogElement imageUI( std::move( surface ) ); fheroes2::showMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( msg1, fheroes2::FontType::normalWhite() ), Dialog::OK, { &imageUI } ); } } else { fheroes2::showStandardTextMessage( title, msg2, Dialog::OK ); } } void ActionToMagellanMaps( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) const Funds payment( Resource::GOLD, 1000 ); Kingdom & kingdom = hero.GetKingdom(); const std::string title( MP2::StringObject( objectType ) ); if ( hero.isObjectTypeVisited( objectType, Visit::GLOBAL ) ) { fheroes2::showStandardTextMessage( title, _( "The captain looks at you with surprise and says:\n\"You already have all the maps I know about. Let me fish in peace now.\"" ), Dialog::OK ); } else { if ( kingdom.AllowPayment( payment ) ) { bool buy = false; { const MusicalEffectPlayer musicalEffectPlayer( MUS::WATCHTOWER ); buy = ( fheroes2::showStandardTextMessage( title, _( "A retired captain living on this refurbished fishing platform offers to sell you maps of the sea he made in his younger days for 1,000 gold. Do you wish to buy the maps?" ), Dialog::YES | Dialog::NO ) == Dialog::YES ); } if ( buy ) { world.ActionForMagellanMaps( hero.GetColor() ); kingdom.OddFundsResource( payment ); hero.SetVisited( dst_index, Visit::GLOBAL ); hero.setVisitedForAllies( dst_index ); // Fully update fog directions and redraw radar and game area. Interface::GameArea::updateMapFogDirections(); Interface::AdventureMap & I = Interface::AdventureMap::Get(); I.setRedraw( Interface::REDRAW_GAMEAREA | Interface::REDRAW_RADAR ); } } else { const MusicalEffectPlayer musicalEffectPlayer( MUS::WATCHTOWER ); fheroes2::showStandardTextMessage( title, _( "The captain sighs. \"You don't have enough money, eh? You can't expect me to give my maps away for free!\"" ), Dialog::OK ); } } } void ActionToEvent( Heroes & hero, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) MapEvent * event_maps = world.GetMapEvent( Maps::GetPoint( dst_index ) ); if ( event_maps && event_maps->isAllow( hero.GetColor() ) ) { hero.SetMove( false ); const Funds fundsToUpdate = Resource::CalculateEventResourceUpdate( hero.GetKingdom().GetFunds(), event_maps->resources ); std::vector resourceUI = fheroes2::getResourceDialogElements( fundsToUpdate ); std::unique_ptr artifactUI; if ( event_maps->resources.GetValidItemsCount() ) { hero.GetKingdom().AddFundsResource( event_maps->resources ); } const Artifact & art = event_maps->artifact; if ( art.isValid() && hero.PickupArtifact( art ) ) { artifactUI = std::make_unique( art ); AudioManager::PlaySound( M82::TREASURE ); } std::vector elementUI; elementUI.reserve( resourceUI.size() ); for ( const fheroes2::ResourceDialogElement & element : resourceUI ) { elementUI.emplace_back( &element ); } if ( artifactUI ) { elementUI.emplace_back( artifactUI.get() ); } fheroes2::showMessage( fheroes2::Text( "", {} ), fheroes2::Text( event_maps->message, fheroes2::FontType::normalWhite() ), Dialog::OK, elementUI ); event_maps->SetVisited( hero.GetColor() ); if ( event_maps->cancel ) { hero.setObjectTypeUnderHero( MP2::OBJ_NONE ); world.RemoveMapObject( event_maps ); } } } void ActionToObelisk( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) Kingdom & kingdom = hero.GetKingdom(); const std::string title( MP2::StringObject( objectType ) ); if ( !hero.isVisited( world.GetTiles( dst_index ), Visit::GLOBAL ) ) { hero.SetVisited( dst_index, Visit::GLOBAL ); kingdom.PuzzleMaps().Update( kingdom.CountVisitedObjects( MP2::OBJ_OBELISK ), world.CountObeliskOnMaps() ); AudioManager::PlaySound( M82::EXPERNCE ); fheroes2::showStandardTextMessage( title, _( "You come upon an obelisk made from a type of stone you have never seen before. Staring at it intensely, the smooth surface suddenly changes to an inscription. The inscription is a piece of a lost ancient map. Quickly you copy down the piece and the inscription vanishes as abruptly as it appeared." ), Dialog::OK ); kingdom.PuzzleMaps().ShowMapsDialog(); } else { fheroes2::showStandardTextMessage( title, _( "You have already been to this obelisk." ), Dialog::OK ); } } void ActionToTreeKnowledge( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) const Maps::Tiles & tile = world.GetTiles( dst_index ); const std::string title( MP2::StringObject( objectType ) ); if ( hero.isVisited( tile ) ) { fheroes2::showStandardTextMessage( title, _( "Upon your approach, the tree opens its eyes in delight. \"It is good to see you, my student. I hope my teachings have helped you.\"" ), Dialog::OK ); } else { const Funds & payment = getTreeOfKnowledgeRequirement( tile ); bool increaseExperience = ( payment.GetValidItemsCount() == 0 ); const int level = hero.GetLevel(); assert( level > 0 ); const uint32_t possibleExperience = Heroes::GetExperienceFromLevel( level ) - Heroes::GetExperienceFromLevel( level - 1 ); { const MusicalEffectPlayer musicalEffectPlayer( MUS::EXPERIENCE ); // Free training if ( increaseExperience ) { const std::string msg = _( "Upon your approach, the tree opens its eyes in delight. \"Ahh, an adventurer! Allow me to teach you a little of what I have learned over the ages.\"" ); const fheroes2::ExperienceDialogElement experienceUI( static_cast( possibleExperience ) ); const fheroes2::Text titleUI( title, fheroes2::FontType::normalYellow() ); const fheroes2::Text messageUI( msg, fheroes2::FontType::normalWhite() ); // In the original game, there was no way to refuse to level up for free, this is an improvement specific to fheroes2 increaseExperience = ( fheroes2::showMessage( titleUI, messageUI, Dialog::YES | Dialog::NO, { &experienceUI } ) == Dialog::YES ); } else { const auto rc = payment.getFirstValidResource(); if ( hero.GetKingdom().AllowPayment( payment ) ) { std::string msg = _( "Upon your approach, the tree opens its eyes in delight." ); msg += '\n'; msg.append( _( "\"Ahh, an adventurer! I will be happy to teach you a little of what I have learned over the ages for a mere %{count} %{res}.\"" ) ); msg += '\n'; msg.append( _( "(Just bury it around my roots.)" ) ); StringReplace( msg, "%{res}", Resource::String( rc.first ) ); StringReplace( msg, "%{count}", std::to_string( rc.second ) ); const fheroes2::ExperienceDialogElement experienceUI( static_cast( possibleExperience ) ); const fheroes2::Text titleUI( title, fheroes2::FontType::normalYellow() ); const fheroes2::Text messageUI( msg, fheroes2::FontType::normalWhite() ); increaseExperience = ( fheroes2::showMessage( titleUI, messageUI, Dialog::YES | Dialog::NO, { &experienceUI } ) == Dialog::YES ); } else { std::string msg = _( "Tears brim in the eyes of the tree." ); msg += '\n'; msg.append( _( "\"I need %{count} %{res}.\"" ) ); msg += '\n'; msg.append( _( "it whispers. (sniff) \"Well, come back when you can pay me.\"" ) ); StringReplace( msg, "%{res}", Resource::String( rc.first ) ); StringReplace( msg, "%{count}", std::to_string( rc.second ) ); fheroes2::showStandardTextMessage( title, msg, Dialog::OK ); } } hero.SetVisited( dst_index, Visit::GLOBAL ); } if ( increaseExperience ) { hero.GetKingdom().OddFundsResource( payment ); hero.SetVisited( dst_index ); hero.IncreaseExperience( possibleExperience ); } } } void ActionToOracle( const Heroes & hero, const MP2::MapObjectType objectType ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) #ifndef WITH_DEBUG (void)hero; #endif { const MusicalEffectPlayer musicalEffectPlayer( MUS::WATERSPRING ); fheroes2::showStandardTextMessage( MP2::StringObject( objectType ), _( "Nestled among the trees sits a blind seer. After you explain the intent of your journey, the seer activates his crystal ball, allowing you to see the strengths and weaknesses of your opponents." ), Dialog::OK ); } Dialog::ThievesGuild( true ); } void ActionToDaemonCave( Heroes & hero, const MP2::MapObjectType objectType, const int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) const std::string title = MP2::StringObject( objectType ); enum class Outcome { Invalid, Ignore, Empty, BattleWithServants, Experience, ExperienceAndGold, ExperienceAndArtifact, PayOff, Death }; const uint32_t demonSlayingExperience = 1000; // Implicitly capture everything by reference because different compilers disagree on whether there is a need to capture the 'demonSlayingExperience' or not const Outcome outcome = [&]() { const MusicalEffectPlayer musicalEffectPlayer( MUS::DEMONCAVE ); const Maps::Tiles & tile = world.GetTiles( dst_index ); if ( fheroes2::showStandardTextMessage( title, _( "The entrance to the cave is dark, and a foul, sulfurous smell issues from the cave mouth. Will you enter?" ), Dialog::YES | Dialog::NO ) != Dialog::YES ) { return Outcome::Ignore; } if ( !doesTileContainValuableItems( tile ) ) { fheroes2::showStandardTextMessage( title, _( "Except for evidence of a terrible battle, the cave is empty." ), Dialog::OK ); return Outcome::Empty; } if ( fheroes2::showStandardTextMessage( title, _( "You find a powerful and grotesque Demon in the cave. \"Today,\" it rasps, \"you will fight and surely die. But I will give you a choice of deaths. You may fight me, or you may fight my servants. Do you prefer to fight my servants?\"" ), Dialog::YES | Dialog::NO ) == Dialog::YES ) { return Outcome::BattleWithServants; } const Maps::DaemonCaveCaptureBonus originalDaemonBonus = getDaemonCaveBonusType( tile ); const Maps::DaemonCaveCaptureBonus bonus = ( originalDaemonBonus == Maps::DaemonCaveCaptureBonus::GET_1000_EXPERIENCE_AND_ARTIFACT && hero.IsFullBagArtifacts() ) ? Maps::DaemonCaveCaptureBonus::GET_1000_EXPERIENCE_AND_2500_GOLD : originalDaemonBonus; switch ( bonus ) { case Maps::DaemonCaveCaptureBonus::GET_1000_EXPERIENCE: { std::string msg = _( "The Demon screams its challenge and attacks! After a short, desperate battle, you slay the monster and receive %{exp} experience points." ); StringReplace( msg, "%{exp}", std::to_string( demonSlayingExperience ) ); const fheroes2::ExperienceDialogElement experienceUI( demonSlayingExperience ); fheroes2::showMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK, { &experienceUI } ); return Outcome::Experience; } case Maps::DaemonCaveCaptureBonus::GET_1000_EXPERIENCE_AND_2500_GOLD: { const Funds funds = getFundsFromTile( tile ); assert( funds.gold > 0 || funds.GetValidItemsCount() == 0 ); const uint32_t gold = funds.gold; std::string msg = _( "The Demon screams its challenge and attacks! After a short, desperate battle, you slay the monster and receive %{exp} experience points and %{count} gold." ); StringReplace( msg, "%{exp}", std::to_string( demonSlayingExperience ) ); StringReplace( msg, "%{count}", std::to_string( gold ) ); const fheroes2::ExperienceDialogElement experienceUI( demonSlayingExperience ); const fheroes2::ResourceDialogElement goldUI( Resource::GOLD, std::to_string( gold ) ); fheroes2::showMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK, { &experienceUI, &goldUI } ); return Outcome::ExperienceAndGold; } case Maps::DaemonCaveCaptureBonus::GET_1000_EXPERIENCE_AND_ARTIFACT: { const Artifact art = getArtifactFromTile( tile ); if ( !art.isValid() ) { return Outcome::Invalid; } std::string msg = _( "The Demon screams its challenge and attacks! After a short, desperate battle, you slay the monster and find the %{art} in the back of the cave." ); StringReplace( msg, "%{art}", art.GetName() ); const fheroes2::ExperienceDialogElement experienceUI( demonSlayingExperience ); const fheroes2::ArtifactDialogElement artifactUI( art ); fheroes2::showMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK, { &experienceUI, &artifactUI } ); return Outcome::ExperienceAndArtifact; } case Maps::DaemonCaveCaptureBonus::PAY_2500_GOLD: { const Kingdom & kingdom = hero.GetKingdom(); const Funds payment( getDaemonPaymentCondition( tile ) ); if ( !kingdom.AllowPayment( payment ) ) { std::string msg = _( "Seeing that you do not have %{count} gold, the demon slashes you with its claws, and the last thing you see is a red haze." ); StringReplace( msg, "%{count}", std::to_string( payment.gold ) ); fheroes2::showStandardTextMessage( title, msg, Dialog::OK ); return Outcome::Death; } std::string msg = _( "The Demon leaps upon you and has its claws at your throat before you can even draw your sword. \"Your life is mine,\" it says. \"I will sell it back to you for %{count} gold.\"" ); StringReplace( msg, "%{count}", std::to_string( payment.gold ) ); if ( fheroes2::showStandardTextMessage( title, msg, Dialog::YES | Dialog::NO ) == Dialog::YES ) { return Outcome::PayOff; } return Outcome::Death; } case Maps::DaemonCaveCaptureBonus::EMPTY: break; default: // This condition should never happen. assert( 0 ); break; } return Outcome::Invalid; }(); if ( outcome != Outcome::Ignore ) { Kingdom & kingdom = hero.GetKingdom(); if ( outcome != Outcome::Empty ) { Maps::Tiles & tile = world.GetTiles( dst_index ); assert( doesTileContainValuableItems( tile ) ); switch ( outcome ) { case Outcome::BattleWithServants: { Army army( tile ); Battle::Result res = Battle::Loader( hero.GetArmy(), army, dst_index ); if ( res.AttackerWins() ) { hero.IncreaseExperience( res.GetExperienceAttacker() ); // Daemon Cave always gives 2500 Gold after a battle. const uint32_t gold = 2500; std::string msg = _( "Upon defeating the daemon's servants, you find a hidden cache with %{count} gold." ); StringReplace( msg, "%{count}", gold ); const fheroes2::ResourceDialogElement goldUI( Resource::GOLD, std::to_string( gold ) ); fheroes2::showMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK, { &goldUI } ); kingdom.AddFundsResource( Funds( Resource::GOLD, gold ) ); } else { BattleLose( hero, res, true ); } break; } case Outcome::Experience: hero.IncreaseExperience( demonSlayingExperience ); break; case Outcome::ExperienceAndGold: { hero.IncreaseExperience( demonSlayingExperience ); kingdom.AddFundsResource( getFundsFromTile( tile ) ); break; } case Outcome::ExperienceAndArtifact: { const Artifact art = getArtifactFromTile( tile ); hero.IncreaseExperience( demonSlayingExperience ); hero.PickupArtifact( art ); break; } case Outcome::PayOff: { kingdom.OddFundsResource( getDaemonPaymentCondition( tile ) ); break; } case Outcome::Death: { Battle::Result res; res.army1 = Battle::RESULT_LOSS; BattleLose( hero, res, true ); break; } default: assert( 0 ); break; } resetObjectInfoOnTile( tile ); } // Even if the hero has been defeated by a demon (and no longer belongs to any // valid kingdom), this tile should be marked as visited for his former kingdom. kingdom.SetVisited( dst_index, objectType ); } } void ActionToAlchemistTower( Heroes & hero ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) BagArtifacts & bag = hero.GetBagArtifacts(); const uint32_t cursed = static_cast( std::count_if( bag.begin(), bag.end(), []( const Artifact & art ) { return art.containsCurses(); } ) ); const char * title = MP2::StringObject( MP2::OBJ_ALCHEMIST_TOWER ); if ( cursed ) { const Funds payment = PaymentConditions::ForAlchemist(); std::string msg = _( "As you enter the Alchemist's Tower, a hobbled, graying man in a brown cloak makes his way towards you." ); msg += '\n'; msg.append( _n( "He checks your pack, and sees that you have 1 cursed item.", "He checks your pack, and sees that you have %{count} cursed items.", cursed ) ); StringReplace( msg, "%{count}", cursed ); msg += '\n'; msg.append( _n( "For %{gold} gold, the alchemist will remove it for you. Do you pay?", "For %{gold} gold, the alchemist will remove them for you. Do you pay?", cursed ) ); StringReplace( msg, "%{gold}", payment.gold ); AudioManager::PlaySound( M82::EXPERNCE ); if ( Dialog::YES == fheroes2::showStandardTextMessage( title, msg, Dialog::YES | Dialog::NO ) ) { if ( hero.GetKingdom().AllowPayment( payment ) ) { hero.GetKingdom().OddFundsResource( payment ); for ( Artifact & artifact : bag ) { if ( artifact.containsCurses() ) { artifact = Artifact::UNKNOWN; } } msg = _n( "After you consent to pay the requested amount of gold, the alchemist grabs the cursed artifact and throws it into his magical cauldron.", "After you consent to pay the requested amount of gold, the alchemist grabs all cursed artifacts and throws them into his magical cauldron.", cursed ); AudioManager::PlaySound( M82::GOODLUCK ); fheroes2::showStandardTextMessage( title, msg, Dialog::OK ); } else { fheroes2::showStandardTextMessage( title, _( "You hear a voice from behind the locked door, \"You don't have enough gold to pay for my services.\"" ), Dialog::OK ); } } } else { fheroes2::showStandardTextMessage( title, _( "You hear a voice from high above in the tower, \"Go away! I can't help you!\"" ), Dialog::OK ); } } void ActionToStables( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) const bool isCavalryPresent = hero.GetArmy().HasMonster( Monster::CAVALRY ); const bool visited = hero.isObjectTypeVisited( objectType ); std::string body; if ( isCavalryPresent && visited ) { body = _( "The head groom speaks to you, \"That is a fine looking horse you have. I am afraid we can give you no better, but the horses your cavalry are riding look to be of poor breeding stock. We have many trained war horses which would aid your riders greatly. I insist you take them.\"" ); } else if ( isCavalryPresent && !visited ) { body = _( "As you approach the stables, the head groom appears, leading a fine looking war horse. \"This steed will help speed you in your travels. Alas, he will grow tired in a week. You must also let me give better horses to your mounted soldiers, their horses look shoddy and weak.\"" ); } else if ( !isCavalryPresent && visited ) { body = _( "The head groom approaches you and speaks, \"You already have a fine horse, and have no inexperienced cavalry which might make use of our trained war horses.\"" ); } else { body = _( "As you approach the stables, the head groom appears, leading a fine looking war horse. \"This steed will help speed you in your travels. Alas, his endurance will wane with a lot of heavy riding, and you must return for a fresh mount in a week. We also have many fine war horses which could benefit mounted soldiers, but you have none we can help.\"" ); } if ( !visited ) { hero.SetVisited( dst_index ); AudioManager::PlaySound( M82::EXPERNCE ); hero.IncreaseMovePoints( GameStatic::getMovementPointBonus( MP2::OBJ_STABLES ) ); } if ( isCavalryPresent ) { ActionToUpgradeArmyObject( hero, objectType, body ); } else { fheroes2::showStandardTextMessage( MP2::StringObject( objectType ), body, Dialog::OK ); } } void ActionToArena( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) if ( hero.isObjectTypeVisited( objectType ) ) { fheroes2::showStandardTextMessage( MP2::StringObject( objectType ), _( "The Arena guards turn you away." ), Dialog::OK ); } else { hero.SetVisited( dst_index ); AudioManager::PlaySound( M82::EXPERNCE ); hero.IncreasePrimarySkill( Dialog::SelectSkillFromArena() ); } } void ActionToSirens( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) const std::string title( MP2::StringObject( objectType ) ); if ( hero.isObjectTypeVisited( objectType ) ) { fheroes2::showStandardTextMessage( title, _( "You have your crew stop up their ears with wax before the sirens' eerie song has any chance of luring them to a watery grave." ), Dialog::OK ); } else { const uint32_t experience = hero.GetArmy().ActionToSirens(); if ( experience == 0 ) { fheroes2::showStandardTextMessage( title, _( "As the sirens sing their eerie song, your small, determined army manages to overcome the urge to dive headlong into the sea." ), Dialog::OK ); } else { const fheroes2::ExperienceDialogElement experienceUI( static_cast( experience ) ); std::string str = _( "An eerie wailing song emanates from the sirens perched upon the rocks. Many of your crew fall under its spell, and dive into the water where they drown. You are now wiser for the visit, and gain %{exp} experience." ); StringReplace( str, "%{exp}", experience ); AudioManager::PlaySound( M82::EXPERNCE ); fheroes2::showMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( str, fheroes2::FontType::normalWhite() ), Dialog::OK, { &experienceUI } ); hero.IncreaseExperience( experience ); } hero.SetVisited( dst_index ); } } void ActionToJail( const Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) const Kingdom & kingdom = hero.GetKingdom(); const std::string title( MP2::StringObject( objectType ) ); if ( kingdom.AllowRecruitHero( false ) ) { const Maps::Tiles & tile = world.GetTiles( dst_index ); AudioManager::PlaySound( M82::EXPERNCE ); fheroes2::showStandardTextMessage( title, _( "In a dazzling display of daring, you break into the local jail and free the hero imprisoned there, who, in return, pledges loyalty to your cause." ), Dialog::OK ); Interface::AdventureMap & adventureMapInterface = Interface::AdventureMap::Get(); adventureMapInterface.getGameArea().runSingleObjectAnimation( std::make_shared( tile.GetObjectUID(), tile.GetIndex(), tile.GetObject() ) ); // TODO: add hero fading in animation together with jail animation. Heroes * prisoner = world.FromJailHeroes( dst_index ); if ( prisoner ) { prisoner->Recruit( hero.GetColor(), Maps::GetPoint( dst_index ) ); // Update the kingdom heroes list including the scrollbar. adventureMapInterface.GetIconsPanel().ResetIcons( ICON_HEROES ); } } else { std::string str = _( "You already have %{count} heroes, and regretfully must leave the prisoner in this jail to languish in agony for untold days." ); StringReplace( str, "%{count}", Kingdom::GetMaxHeroes() ); fheroes2::showStandardTextMessage( title, str, Dialog::OK ); } } void ActionToHutMagi( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) AudioManager::PlaySound( M82::EXPERNCE ); fheroes2::showStandardTextMessage( MP2::StringObject( objectType ), _( "You enter a rickety hut and talk to the magician who lives there. He tells you of places near and far which may aid you in your journeys." ), Dialog::OK ); if ( !hero.isObjectTypeVisited( objectType, Visit::GLOBAL ) ) { hero.SetVisited( dst_index, Visit::GLOBAL ); const MapsIndexes eyeMagiIndexes = Maps::GetObjectPositions( MP2::OBJ_EYE_OF_MAGI ); if ( !eyeMagiIndexes.empty() ) { Interface::AdventureMap & I = Interface::AdventureMap::Get(); fheroes2::Display & display = fheroes2::Display::instance(); const size_t maxDelay = 7; const int32_t scoutRange = static_cast( GameStatic::getFogDiscoveryDistance( GameStatic::FogDiscoveryType::MAGI_EYES ) ); bool skipAnimation = false; fheroes2::Rect radarRenderArea; for ( const int32_t eyeIndex : eyeMagiIndexes ) { Maps::ClearFog( eyeIndex, scoutRange, hero.GetColor() ); const fheroes2::Point eyePosition = Maps::GetPoint( eyeIndex ); const fheroes2::Rect eyeRoi( eyePosition.x - scoutRange, eyePosition.y - scoutRange, 2 * scoutRange + 1, 2 * scoutRange + 1 ); if ( skipAnimation ) { radarRenderArea = fheroes2::getBoundaryRect( radarRenderArea, eyeRoi ); continue; } I.getGameArea().SetCenter( eyePosition ); I.getRadar().SetRenderArea( eyeRoi ); I.redraw( Interface::REDRAW_GAMEAREA | Interface::REDRAW_RADAR ); display.render(); LocalEvent & le = LocalEvent::Get(); size_t delay = 0; while ( delay < maxDelay && le.HandleEvents( Game::isDelayNeeded( { Game::MAPS_DELAY } ) ) ) { if ( le.KeyPress() || le.MouseClickLeft() || le.MouseClickMiddle() || le.MouseClickRight() ) { skipAnimation = true; break; } if ( Game::validateAnimationDelay( Game::MAPS_DELAY ) ) { ++delay; Game::updateAdventureMapAnimationIndex(); I.redraw( Interface::REDRAW_GAMEAREA ); display.render(); } } } if ( skipAnimation ) { I.getRadar().SetRenderArea( radarRenderArea ); I.setRedraw( Interface::REDRAW_RADAR ); } I.getGameArea().SetCenter( hero.GetCenter() ); I.setRedraw( Interface::REDRAW_GAMEAREA | Interface::REDRAW_RADAR_CURSOR ); display.render(); } } } void ActionToEyeMagi( const Heroes & hero, const MP2::MapObjectType objectType ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) #ifndef WITH_DEBUG (void)hero; #endif fheroes2::showStandardTextMessage( MP2::StringObject( objectType ), _( "This eye seems to be intently studying its surroundings." ), Dialog::OK ); } void ActionToSphinx( Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) enum class Outcome { Invalid, Empty, Ignore, CorrectAnswer, IncorrectAnswer }; MapSphinx * riddle = dynamic_cast( world.GetMapObject( dst_index ) ); const Outcome outcome = [objectType, riddle]() { const std::string title = MP2::StringObject( objectType ); const MusicalEffectPlayer musicalEffectPlayer( MUS::ARABIAN ); if ( riddle == nullptr || !riddle->valid ) { fheroes2::showStandardTextMessage( title, _( "You come across a giant Sphinx. The Sphinx remains strangely quiet." ), Dialog::OK ); return Outcome::Empty; } if ( fheroes2::showStandardTextMessage( title, _( "\"I have a riddle for you,\" the Sphinx says. \"Answer correctly, and you shall be rewarded. Answer incorrectly, and you shall be eaten. Do you accept the challenge?\"" ), Dialog::YES | Dialog::NO ) != Dialog::YES ) { return Outcome::Ignore; } std::string question( _( "The Sphinx asks you the following riddle:\n\n'%{riddle}'\n\nYour answer?" ) ); StringReplace( question, "%{riddle}", riddle->message ); std::string answer; Dialog::InputString( question, answer, title ); if ( !riddle->AnswerCorrect( answer ) ) { fheroes2::showStandardTextMessage( title, _( "\"You guessed incorrectly,\" the Sphinx says, smiling. The Sphinx swipes at you with a paw, knocking you to the ground. Another blow makes the world go black, and you know no more." ), Dialog::OK ); return Outcome::IncorrectAnswer; } const Funds & res = riddle->resources; const Artifact & art = riddle->artifact; const uint32_t count = res.GetValidItemsCount(); const std::string msg = _( "Looking somewhat disappointed, the Sphinx sighs. \"You've answered my riddle so here's your reward. Now begone.\"" ); if ( count || art.isValid() ) { const std::vector resourceUiElements = fheroes2::getResourceDialogElements( res ); std::vector uiElements; uiElements.reserve( resourceUiElements.size() + 1 ); for ( const fheroes2::ResourceDialogElement & element : resourceUiElements ) { uiElements.emplace_back( &element ); } std::unique_ptr artifactUI; if ( art.isValid() ) { artifactUI = std::make_unique( art ); uiElements.emplace_back( artifactUI.get() ); } fheroes2::showMessage( fheroes2::Text( title, fheroes2::FontType::normalYellow() ), fheroes2::Text( msg, fheroes2::FontType::normalWhite() ), Dialog::OK, uiElements ); return Outcome::CorrectAnswer; } return Outcome::Invalid; }(); switch ( outcome ) { case Outcome::Empty: hero.SetVisited( dst_index, Visit::GLOBAL ); break; case Outcome::Ignore: break; case Outcome::CorrectAnswer: { assert( riddle && riddle->valid ); const Funds & res = riddle->resources; const Artifact & art = riddle->artifact; const uint32_t count = res.GetValidItemsCount(); if ( count ) { hero.GetKingdom().AddFundsResource( res ); } if ( art.isValid() ) { hero.PickupArtifact( art ); } riddle->SetQuiet(); hero.SetVisited( dst_index, Visit::GLOBAL ); break; } case Outcome::IncorrectAnswer: { Battle::Result result; result.army1 = Battle::RESULT_LOSS; BattleLose( hero, result, true ); break; } default: assert( 0 ); break; } } void ActionToBarrier( const Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) // A hero cannot stand on a barrier. He must stand in front of the barrier. Something wrong with logic! assert( hero.GetIndex() != dst_index ); const Maps::Tiles & tile = world.GetTiles( dst_index ); const Kingdom & kingdom = hero.GetKingdom(); const std::string title = MP2::StringObject( objectType ); if ( kingdom.IsVisitTravelersTent( getColorFromTile( tile ) ) ) { AudioManager::PlaySound( M82::EXPERNCE ); fheroes2::showStandardTextMessage( title, _( "A magical barrier stands tall before you, blocking your way. Runes on the arch read,\n\"Speak the key and you may pass.\"\nAs you speak the magic word, the glowing barrier dissolves into nothingness." ), Dialog::OK ); AudioManager::PlaySound( M82::KILLFADE ); Interface::AdventureMap::Get().getGameArea().runSingleObjectAnimation( std::make_shared( tile.GetObjectUID(), tile.GetIndex(), tile.GetObject() ) ); } else { fheroes2::showStandardTextMessage( title, _( "A magical barrier stands tall before you, blocking your way. Runes on the arch read,\n\"Speak the key and you may pass.\"\nYou speak, and nothing happens." ), Dialog::OK ); } } void ActionToTravellersTent( const Heroes & hero, const MP2::MapObjectType objectType, int32_t dst_index ) { DEBUG_LOG( DBG_GAME, DBG_INFO, hero.GetName() ) AudioManager::PlaySound( M82::EXPERNCE ); fheroes2::showStandardTextMessage( MP2::StringObject( objectType ), _( "You enter the tent and see an old woman gazing into a magic gem. She looks up and says,\n\"In my travels, I have learned much in the way of arcane magic. A great oracle taught me his skill. I have the answer you seek.\"" ), Dialog::OK ); const Maps::Tiles & tile = world.GetTiles( dst_index ); Kingdom & kingdom = hero.GetKingdom(); kingdom.SetVisitTravelersTent( getColorFromTile( tile ) ); } } void Heroes::ScoutRadar() const { Interface::AdventureMap & I = Interface::AdventureMap::Get(); #if defined( WITH_DEBUG ) if ( GetColor() != Color::NONE ) { const Player * player = Players::Get( GetColor() ); assert( player != nullptr ); // If player gave control to AI we need to fully update the radar image as there is no need to make a code for rendering optimizations so we // don't call 'SetRenderArea()'. if ( !player->isAIAutoControlMode() ) { #endif I.getRadar().SetRenderArea( GetScoutRoi() ); #if defined( WITH_DEBUG ) } } #endif I.setRedraw( Interface::REDRAW_RADAR ); } void Heroes::Action( int tileIndex ) { // Hero may be lost while performing the action, reset the focus after completing the action (and update environment sounds and music if necessary) struct FocusUpdater { FocusUpdater() = default; FocusUpdater( const FocusUpdater & ) = delete; ~FocusUpdater() { Interface::AdventureMap & I = Interface::AdventureMap::Get(); I.ResetFocus( GameFocus::HEROES, true ); I.RedrawFocus(); } FocusUpdater & operator=( const FocusUpdater & ) = delete; }; std::unique_ptr focusUpdater; #if defined( WITH_DEBUG ) const Player * player = Players::Get( GetKingdom().GetColor() ); assert( player != nullptr ); const bool isAIAutoControlMode = player->isAIAutoControlMode(); #else const bool isAIAutoControlMode = false; #endif if ( !GetKingdom().isControlAI() || isAIAutoControlMode ) { focusUpdater = std::make_unique(); if ( isAIAutoControlMode ) { Interface::AdventureMap::Get().SetFocus( this, false ); } } if ( GetKingdom().isControlAI() ) { // Restore the original music after the action is completed. const AudioManager::MusicRestorer musicRestorer; return AI::HeroesAction( *this, tileIndex ); } const int32_t heroPosIndex = GetIndex(); assert( heroPosIndex >= 0 ); // Update environment sounds and music before performing the action if ( Game::UpdateSoundsOnFocusUpdate() ) { Game::EnvironmentSoundMixer(); AudioManager::PlayMusicAsync( MUS::FromGround( world.GetTiles( heroPosIndex ).GetGround() ), Music::PlaybackMode::RESUME_AND_PLAY_INFINITE ); } const MP2::MapObjectType objectType = world.GetTiles( tileIndex ).GetObject( tileIndex != heroPosIndex ); if ( MP2::isActionObject( objectType, isShipMaster() ) ) { SetModes( ACTION ); } // Most likely there will be some action or event, immediately center the map on the hero to avoid subsequent minor screen movements if ( Modes( ACTION ) || objectType == MP2::OBJ_EVENT ) { Interface::AdventureMap & I = Interface::AdventureMap::Get(); I.getGameArea().SetCenter( GetCenter() ); I.redraw( Interface::REDRAW_GAMEAREA | Interface::REDRAW_RADAR_CURSOR | Interface::REDRAW_HEROES ); } switch ( objectType ) { case MP2::OBJ_MONSTER: ActionToMonster( *this, tileIndex ); break; case MP2::OBJ_CASTLE: ActionToCastle( *this, tileIndex ); break; case MP2::OBJ_HERO: ActionToHeroes( *this, tileIndex ); break; case MP2::OBJ_BOAT: ActionToBoat( *this, tileIndex ); break; case MP2::OBJ_COAST: ActionToCoast( *this, tileIndex ); break; // resource object case MP2::OBJ_WINDMILL: case MP2::OBJ_WATER_WHEEL: case MP2::OBJ_MAGIC_GARDEN: case MP2::OBJ_LEAN_TO: ActionToObjectResource( *this, objectType, tileIndex ); break; case MP2::OBJ_WAGON: ActionToWagon( *this, tileIndex ); break; case MP2::OBJ_SKELETON: ActionToSkeleton( *this, objectType, tileIndex ); break; case MP2::OBJ_BOTTLE: case MP2::OBJ_CAMPFIRE: case MP2::OBJ_RESOURCE: ActionToPickupResource( *this, objectType, tileIndex ); break; case MP2::OBJ_SEA_CHEST: case MP2::OBJ_TREASURE_CHEST: ActionToTreasureChest( *this, objectType, tileIndex ); break; case MP2::OBJ_GENIE_LAMP: ActionToGenieLamp( *this, objectType, tileIndex ); break; case MP2::OBJ_FLOTSAM: ActionToFlotSam( *this, objectType, tileIndex ); break; case MP2::OBJ_SHIPWRECK_SURVIVOR: ActionToShipwreckSurvivor( *this, objectType, tileIndex ); break; case MP2::OBJ_ARTIFACT: ActionToArtifact( *this, tileIndex ); break; // shrine circle case MP2::OBJ_SHRINE_FIRST_CIRCLE: case MP2::OBJ_SHRINE_SECOND_CIRCLE: case MP2::OBJ_SHRINE_THIRD_CIRCLE: ActionToShrine( *this, tileIndex ); break; case MP2::OBJ_WITCHS_HUT: ActionToWitchsHut( *this, objectType, tileIndex ); break; // info message case MP2::OBJ_SIGN: ActionToSign( *this, tileIndex ); break; // luck modification case MP2::OBJ_FOUNTAIN: case MP2::OBJ_FAERIE_RING: case MP2::OBJ_IDOL: ActionToGoodLuckObject( *this, objectType, tileIndex ); break; case MP2::OBJ_PYRAMID: ActionToPyramid( *this, objectType, tileIndex ); break; case MP2::OBJ_MAGIC_WELL: ActionToMagicWell( *this, tileIndex ); break; case MP2::OBJ_TRADING_POST: ActionToTradingPost( *this ); break; // primary skill modification case MP2::OBJ_FORT: case MP2::OBJ_MERCENARY_CAMP: case MP2::OBJ_WITCH_DOCTORS_HUT: case MP2::OBJ_STANDING_STONES: ActionToPrimarySkillObject( *this, objectType, tileIndex ); break; // morale modification case MP2::OBJ_OASIS: case MP2::OBJ_TEMPLE: case MP2::OBJ_WATERING_HOLE: case MP2::OBJ_BUOY: ActionToGoodMoraleObject( *this, objectType, tileIndex ); break; case MP2::OBJ_SHIPWRECK: case MP2::OBJ_GRAVEYARD: case MP2::OBJ_DERELICT_SHIP: ActionToPoorMoraleObject( *this, objectType, tileIndex ); break; // experience modification case MP2::OBJ_GAZEBO: ActionToExperienceObject( *this, objectType, tileIndex ); break; case MP2::OBJ_DAEMON_CAVE: ActionToDaemonCave( *this, objectType, tileIndex ); break; // teleports case MP2::OBJ_STONE_LITHS: ActionToTeleports( *this, tileIndex ); break; case MP2::OBJ_WHIRLPOOL: ActionToWhirlpools( *this, tileIndex ); break; case MP2::OBJ_OBSERVATION_TOWER: ActionToObservationTower( *this, objectType, tileIndex ); break; case MP2::OBJ_MAGELLANS_MAPS: ActionToMagellanMaps( *this, objectType, tileIndex ); break; // capture color object case MP2::OBJ_ALCHEMIST_LAB: case MP2::OBJ_MINE: case MP2::OBJ_SAWMILL: case MP2::OBJ_LIGHTHOUSE: ActionToCaptureObject( *this, objectType, tileIndex ); break; case MP2::OBJ_ABANDONED_MINE: ActionToAbandonedMine( *this, objectType, tileIndex ); break; // accept army case MP2::OBJ_WATCH_TOWER: case MP2::OBJ_EXCAVATION: case MP2::OBJ_CAVE: case MP2::OBJ_TREE_HOUSE: case MP2::OBJ_ARCHER_HOUSE: case MP2::OBJ_GOBLIN_HUT: case MP2::OBJ_DWARF_COTTAGE: case MP2::OBJ_HALFLING_HOLE: case MP2::OBJ_PEASANT_HUT: ActionToDwellingJoinMonster( *this, objectType, tileIndex ); break; // recruit army case MP2::OBJ_RUINS: case MP2::OBJ_TREE_CITY: case MP2::OBJ_WAGON_CAMP: case MP2::OBJ_DESERT_TENT: ActionToDwellingRecruitMonster( *this, objectType, tileIndex ); break; // battle and recruit army case MP2::OBJ_DRAGON_CITY: case MP2::OBJ_CITY_OF_DEAD: case MP2::OBJ_TROLL_BRIDGE: ActionToDwellingBattleMonster( *this, objectType, tileIndex ); break; case MP2::OBJ_ARTESIAN_SPRING: ActionToArtesianSpring( *this, objectType, tileIndex ); break; case MP2::OBJ_XANADU: ActionToXanadu( *this, objectType, tileIndex ); break; case MP2::OBJ_HILL_FORT: case MP2::OBJ_FREEMANS_FOUNDRY: ActionToUpgradeArmyObject( *this, objectType, "" ); break; case MP2::OBJ_EVENT: ActionToEvent( *this, tileIndex ); break; case MP2::OBJ_OBELISK: ActionToObelisk( *this, objectType, tileIndex ); break; case MP2::OBJ_TREE_OF_KNOWLEDGE: ActionToTreeKnowledge( *this, objectType, tileIndex ); break; case MP2::OBJ_ORACLE: ActionToOracle( *this, objectType ); break; case MP2::OBJ_SPHINX: ActionToSphinx( *this, objectType, tileIndex ); break; // loyalty version case MP2::OBJ_WATER_ALTAR: case MP2::OBJ_AIR_ALTAR: case MP2::OBJ_FIRE_ALTAR: case MP2::OBJ_EARTH_ALTAR: case MP2::OBJ_BARROW_MOUNDS: ActionToDwellingRecruitMonster( *this, objectType, tileIndex ); break; case MP2::OBJ_ALCHEMIST_TOWER: ActionToAlchemistTower( *this ); break; case MP2::OBJ_STABLES: ActionToStables( *this, objectType, tileIndex ); break; case MP2::OBJ_ARENA: ActionToArena( *this, objectType, tileIndex ); break; case MP2::OBJ_MERMAID: ActionToGoodLuckObject( *this, objectType, tileIndex ); break; case MP2::OBJ_SIRENS: ActionToSirens( *this, objectType, tileIndex ); break; case MP2::OBJ_JAIL: ActionToJail( *this, objectType, tileIndex ); break; case MP2::OBJ_HUT_OF_MAGI: ActionToHutMagi( *this, objectType, tileIndex ); break; case MP2::OBJ_EYE_OF_MAGI: ActionToEyeMagi( *this, objectType ); break; case MP2::OBJ_BARRIER: ActionToBarrier( *this, objectType, tileIndex ); break; case MP2::OBJ_TRAVELLER_TENT: ActionToTravellersTent( *this, objectType, tileIndex ); break; // other object default: break; } } fheroes2-1.0.12+dfsg/src/fheroes2/heroes/heroes_base.cpp000066400000000000000000000523561456075706000230300ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "heroes_base.h" #include #include #include #include "army.h" #include "army_troop.h" #include "artifact_info.h" #include "castle.h" #include "gamedefs.h" #include "heroes.h" #include "kingdom.h" #include "maps.h" #include "maps_tiles.h" #include "monster.h" #include "mp2.h" #include "race.h" #include "serialize.h" #include "spell_info.h" #include "tools.h" #include "translations.h" #include "world.h" HeroBase::HeroBase( const int type, const int race ) : magic_point( 0 ) , move_point( 0 ) { bag_artifacts.assign( HEROESMAXARTIFACT, Artifact::UNKNOWN ); LoadDefaults( type, race ); } void HeroBase::LoadDefaults( const int type, const int race ) { if ( Race::ALL & race ) { // fixed default primary skills Skill::Primary::LoadDefaults( type, race ); // fixed default spell switch ( type ) { case HeroBase::CAPTAIN: { // force add spell book SpellBookActivate(); const Spell spell = Skill::Primary::GetInitialSpell( race ); if ( spell.isValid() ) spell_book.Append( spell ); break; } case HeroBase::HEROES: { const Spell spell = Skill::Primary::GetInitialSpell( race ); if ( spell.isValid() ) { SpellBookActivate(); spell_book.Append( spell ); } break; } default: break; } } } HeroBase::HeroBase() : magic_point( 0 ) , move_point( 0 ) {} bool HeroBase::isCaptain() const { return GetType() == CAPTAIN; } bool HeroBase::isHeroes() const { return GetType() == HEROES; } uint32_t HeroBase::GetSpellPoints() const { return magic_point; } void HeroBase::SetSpellPoints( const uint32_t points ) { magic_point = points; } bool HeroBase::isPotentSpellcaster() const { // With knowledge 5 or less there isn't enough spell points to make a difference if ( knowledge <= 5 ) return false; for ( const Spell & spell : spell_book ) { // This list is based on spells AI can use efficiently - should be updated later on switch ( spell.GetID() ) { case Spell::BLIND: case Spell::PARALYZE: case Spell::DIMENSIONDOOR: case Spell::SUMMONAELEMENT: case Spell::SUMMONEELEMENT: case Spell::SUMMONFELEMENT: case Spell::SUMMONWELEMENT: case Spell::MIRRORIMAGE: return true; case Spell::COLDRAY: case Spell::LIGHTNINGBOLT: case Spell::CHAINLIGHTNING: case Spell::METEORSHOWER: case Spell::ARMAGEDDON: if ( power > 5 ) return true; break; case Spell::RESURRECT: case Spell::RESURRECTTRUE: if ( !GetArmy().AllTroopsAreUndead() ) return true; break; case Spell::ANIMATEDEAD: if ( GetArmy().AllTroopsAreUndead() ) return true; break; default: break; } } return false; } bool HeroBase::HaveSpellPoints( const Spell & spell ) const { return magic_point >= spell.spellPoints( this ); } bool HeroBase::haveMovePoints( const Spell & spell ) const { return move_point >= spell.minMovePoints(); } void HeroBase::EditSpellBook() { spell_book.Edit( *this ); } Spell HeroBase::OpenSpellBook( const SpellBook::Filter filter, const bool canCastSpell, const bool restorePreviousState, const std::function * statusCallback ) const { return spell_book.Open( *this, filter, canCastSpell, restorePreviousState, statusCallback ); } SpellStorage HeroBase::getAllSpells() const { // If the hero doesn't have a spell book, then spell scrolls are useless if ( !HaveSpellBook() ) { return {}; } SpellStorage storage; storage.Append( spell_book ); storage.Append( bag_artifacts ); return storage; } bool HeroBase::HaveSpell( const Spell & spell, const bool skip_bag ) const { return HaveSpellBook() && ( spell_book.isPresentSpell( spell ) || ( !skip_bag && bag_artifacts.ContainSpell( spell.GetID() ) ) ); } void HeroBase::AppendSpellToBook( const Spell & spell, const bool without_wisdom ) { if ( without_wisdom || CanLearnSpell( spell ) ) spell_book.Append( spell ); } void HeroBase::AppendSpellsToBook( const SpellStorage & spells, const bool without_wisdom ) { for ( const auto & spell : spells ) AppendSpellToBook( spell, without_wisdom ); } bool HeroBase::SpellBookActivate() { return !HaveSpellBook() && bag_artifacts.PushArtifact( Artifact::MAGIC_BOOK ); } void HeroBase::SpellBookDeactivate() { bag_artifacts.RemoveArtifact( Artifact::MAGIC_BOOK ); // Hero should not have more than one spell book assert( !HaveSpellBook() ); spell_book.clear(); } bool HeroBase::hasArtifact( const Artifact & art ) const { return bag_artifacts.isPresentArtifact( art ); } int HeroBase::GetAttackModificator( std::string * strs ) const { int result = 0; if ( strs == nullptr ) { result += bag_artifacts.getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::ATTACK_SKILL ); } else { result += bag_artifacts.getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::ATTACK_SKILL, *strs ); } // check castle modificator const Castle * castle = inCastle(); if ( castle ) result += castle->GetAttackModificator( strs ); return result; } int HeroBase::GetDefenseModificator( std::string * strs ) const { int result = 0; if ( strs == nullptr ) { result += bag_artifacts.getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::DEFENCE_SKILL ); } else { result += bag_artifacts.getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::DEFENCE_SKILL, *strs ); } // check castle modificator const Castle * castle = inCastle(); if ( castle ) result += castle->GetDefenseModificator( strs ); return result; } int HeroBase::GetPowerModificator( std::string * strs ) const { int result = 0; if ( strs == nullptr ) { result += bag_artifacts.getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::SPELL_POWER_SKILL ); result -= bag_artifacts.getTotalArtifactEffectValue( fheroes2::ArtifactCurseType::SPELL_POWER_SKILL ); } else { result += bag_artifacts.getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::SPELL_POWER_SKILL, *strs ); result -= bag_artifacts.getTotalArtifactEffectValue( fheroes2::ArtifactCurseType::SPELL_POWER_SKILL, *strs ); } // check castle modificator const Castle * castle = inCastle(); if ( castle ) result += castle->GetPowerModificator( strs ); return result; } int HeroBase::GetKnowledgeModificator( std::string * strs ) const { int result = 0; if ( strs == nullptr ) { result += bag_artifacts.getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::KNOWLEDGE_SKILL ); } else { result += bag_artifacts.getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::KNOWLEDGE_SKILL, *strs ); } // check castle modificator const Castle * castle = inCastle(); if ( castle ) result += castle->GetKnowledgeModificator( strs ); return result; } int HeroBase::GetMoraleModificator( std::string * strs ) const { int result = 0; // army modificator (including the castle modificator) result += GetArmy().GetMoraleModificator( strs ); if ( strs == nullptr ) { result += bag_artifacts.getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::MORALE ); if ( Modes( Heroes::SHIPMASTER ) ) { result += bag_artifacts.getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::SEA_BATTLE_MORALE_BOOST ); } result -= bag_artifacts.getTotalArtifactEffectValue( fheroes2::ArtifactCurseType::MORALE ); } else { result += bag_artifacts.getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::MORALE, *strs ); if ( Modes( Heroes::SHIPMASTER ) ) { result += bag_artifacts.getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::SEA_BATTLE_MORALE_BOOST, *strs ); } result -= bag_artifacts.getTotalArtifactEffectValue( fheroes2::ArtifactCurseType::MORALE, *strs ); } return result; } int HeroBase::GetLuckModificator( std::string * strs ) const { int result = 0; // army modificator (including the castle modificator) result += GetArmy().GetLuckModificator( strs ); if ( strs == nullptr ) { result += bag_artifacts.getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::LUCK ); if ( Modes( Heroes::SHIPMASTER ) ) { result += bag_artifacts.getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::SEA_BATTLE_LUCK_BOOST ); } } else { result += bag_artifacts.getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::LUCK, *strs ); if ( Modes( Heroes::SHIPMASTER ) ) { result += bag_artifacts.getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::SEA_BATTLE_LUCK_BOOST, *strs ); } } return result; } double HeroBase::GetMagicStrategicValue( const double armyStrength ) const { const SpellStorage spells = getAllSpells(); const uint32_t currentSpellPoints = GetSpellPoints(); const int spellPower = GetPower(); double bestValue = 0; for ( const Spell & spell : spells ) { if ( spell.isCombat() ) { bestValue = std::max( bestValue, spell.getStrategicValue( armyStrength, currentSpellPoints, spellPower ) ); } } return bestValue; } bool HeroBase::CanCastSpell( const Spell & spell, std::string * res /* = nullptr */ ) const { if ( !HaveSpellBook() ) { if ( res ) { // This should not happen for a human-controlled hero (for which this method is usually called with the non-null res) assert( 0 ); *res = _( "No spell book is present." ); } return false; } if ( !HaveSpellPoints( spell ) ) { if ( GetSpellPoints() == 0 && res != nullptr ) { *res = _( "This spell costs %{mana} spell points. You have no spell points, so you cannot cast it." ); StringReplace( *res, "%{mana}", spell.spellPoints( this ) ); } else if ( res != nullptr ) { *res = _( "This spell costs %{mana} spell points. You only have %{point} spell points, so you cannot cast it." ); StringReplace( *res, "%{mana}", spell.spellPoints( this ) ); StringReplace( *res, "%{point}", GetSpellPoints() ); } return false; } if ( !HaveSpell( spell ) ) { if ( res ) { // This should not happen for a human-controlled hero (for which this method is usually called with the non-null res) assert( 0 ); *res = _( "The spell was not found." ); } return false; } if ( spell.isAdventure() ) { const Heroes * hero = dynamic_cast( this ); if ( hero == nullptr ) { // How is it possible that a captain can access this spell? assert( 0 ); if ( res != nullptr ) { *res = _( "Only heroes can cast this spell." ); } return false; } if ( !hero->MayCastAdventureSpells() ) { // This should never happen assert( 0 ); if ( res ) { *res = _( "This hero is not able to cast adventure spells." ); } return false; } if ( !haveMovePoints( spell ) ) { if ( res ) { *res = _( "Your hero is too tired to cast this spell today. Try again tomorrow." ); } return false; } if ( ( spell == Spell::SUMMONBOAT || spell == Spell::TOWNGATE || spell == Spell::TOWNPORTAL ) && hero->isShipMaster() ) { if ( res != nullptr ) { *res = _( "This spell cannot be cast on a boat." ); } return false; } if ( spell == Spell::SUMMONBOAT ) { if ( !fheroes2::isHeroNearWater( *hero ) ) { if ( res != nullptr ) { *res = _( "This spell can only be cast near an ocean." ); } return false; } const int32_t boatSource = fheroes2::getSummonableBoat( *hero ); const int32_t boatDestination = fheroes2::getPossibleBoatPosition( *hero ); const bool validBoatDestination = Maps::isValidAbsIndex( boatDestination ); if ( boatSource == -1 && !validBoatDestination ) { if ( res != nullptr ) { *res = _( "There are no boats available and no ocean adjacent to the hero where this spell will work." ); } return false; } if ( boatSource == -1 ) { if ( res != nullptr ) { *res = _( "There are no boats available for this spell." ); } return false; } if ( !validBoatDestination ) { if ( res != nullptr ) { *res = _( "There is no ocean adjacent to the hero where this spell will work." ); } return false; } } if ( spell == Spell::TOWNGATE || spell == Spell::TOWNPORTAL ) { const VecCastles & castles = hero->GetKingdom().GetCastles(); bool hasCastles = std::any_of( castles.begin(), castles.end(), []( const Castle * castle ) { return castle && castle->GetHero() == nullptr; } ); if ( !hasCastles ) { if ( res != nullptr ) { *res = _( "You do not own any town or castle that is not currently occupied by a hero. This spell will have no effect." ); } return false; } } if ( spell == Spell::TOWNGATE ) { const Castle * castle = fheroes2::getNearestCastleTownGate( *hero ); assert( castle != nullptr ); if ( castle->GetIndex() == hero->GetIndex() ) { if ( res != nullptr ) { *res = _( "This hero is already in a town, so this spell will have no effect." ); } return false; } const Heroes * townHero = castle->GetHero(); if ( townHero != nullptr ) { if ( res != nullptr ) { *res = _( "The nearest town is %{town}.\n\nThis town is occupied by your hero %{hero}." ); StringReplace( *res, "%{town}", castle->GetName() ); StringReplace( *res, "%{hero}", townHero->GetName() ); } return false; } } if ( spell == Spell::IDENTIFYHERO ) { const Kingdom & kingdom = hero->GetKingdom(); if ( kingdom.Modes( Kingdom::IDENTIFYHERO ) ) { if ( res != nullptr ) { *res = _( "This spell is already in effect." ); } return false; } const bool opponentsHaveHeroes = kingdom.opponentsHaveHeroes(); const bool opponentsCanRecruitHeroes = kingdom.opponentsCanRecruitMoreHeroes(); // This text is shown in two cases. First when there are no opponents // left in the game. Second when opponent doesn't have heroes left // and cannot recruit more. This will happen when all opponent // heroes are defeated and the opponent has a town that cannot be // upgraded to a castle to recruit more heroes. Because having a // town opponent is not defeated yet. if ( !opponentsHaveHeroes && !opponentsCanRecruitHeroes ) { if ( res != nullptr ) { *res = _( "No opponent neither has nor can have any hero under their command anymore. Casting this spell will have no effect." ); } return false; } // This is shown when opponent exists, but doesn't have heroes at // the moment and can recruit more. if ( !opponentsHaveHeroes && opponentsCanRecruitHeroes ) { if ( res != nullptr ) { *res = _( "No opponent has a hero under their command at this time. Casting this spell will have no effect." ); } return false; } } if ( spell == Spell::VISIONS ) { const MapsIndexes monsters = Maps::getVisibleMonstersAroundHero( *hero ); if ( monsters.empty() ) { if ( res != nullptr ) { const uint32_t dist = hero->GetVisionsDistance(); *res = _( "You must be within %{count} spaces of a monster for the Visions spell to work." ); StringReplace( *res, "%{count}", dist ); } return false; } } if ( spell == Spell::HAUNT || spell == Spell::SETAGUARDIAN || spell == Spell::SETEGUARDIAN || spell == Spell::SETFGUARDIAN || spell == Spell::SETWGUARDIAN ) { const Maps::Tiles & tile = world.GetTiles( hero->GetIndex() ); const MP2::MapObjectType object = tile.GetObject( false ); if ( MP2::OBJ_MINE != object ) { if ( res != nullptr ) { *res = _( "You must be standing on the entrance to a mine (sawmills and alchemist labs do not count) to cast this spell." ); } return false; } const Troop & troop = world.GetCapturedObject( tile.GetIndex() ).GetTroop(); const int monsterType = troop.GetMonster().GetID(); if ( monsterType == Monster::GHOST ) { if ( res != nullptr ) { *res = _( "You must first defeat the ghosts guarding the mine to cast this spell." ); } return false; } if ( spell != Spell::HAUNT ) { const uint32_t newCount = fheroes2::getGuardianMonsterCount( spell, hero->GetPower(), hero ); const uint32_t currentCount = troop.GetCount(); if ( newCount <= currentCount ) { if ( res != nullptr ) { *res = _( "There are already at least as many elementals guarding the mine as this hero can generate. Casting this spell will have no effect." ); } return false; } } } } return true; } void HeroBase::SpellCasted( const Spell & spell ) { magic_point -= std::min( spell.spellPoints( this ), magic_point ); move_point -= std::min( spell.movePoints(), move_point ); } bool HeroBase::CanLearnSpell( const Spell & spell ) const { const int wisdom = GetLevelSkill( Skill::Secondary::WISDOM ); return ( ( 4 < spell.Level() && Skill::Level::EXPERT == wisdom ) || ( 4 == spell.Level() && Skill::Level::ADVANCED <= wisdom ) || ( 3 == spell.Level() && Skill::Level::BASIC <= wisdom ) || 3 > spell.Level() ); } StreamBase & operator<<( StreamBase & msg, const HeroBase & hero ) { return msg << static_cast( hero ) << static_cast( hero ) << hero.modes << hero.magic_point << hero.move_point << hero.spell_book << hero.bag_artifacts; } StreamBase & operator>>( StreamBase & msg, HeroBase & hero ) { msg >> static_cast( hero ) >> static_cast( hero ) >> hero.modes >> hero.magic_point >> hero.move_point >> hero.spell_book >> hero.bag_artifacts; return msg; } fheroes2-1.0.12+dfsg/src/fheroes2/heroes/heroes_base.h000066400000000000000000000140341456075706000224640ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Josh Matthews * * Copyright (C) 2008 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2HEROESBASE_H #define H2HEROESBASE_H #include #include #include #include "artifact.h" #include "bitmodes.h" #include "players.h" #include "position.h" #include "skill.h" #include "spell.h" #include "spell_book.h" #include "spell_storage.h" class StreamBase; namespace fheroes2 { class Image; } class Army; class Castle; enum { MDF_NONE, MDF_ATTACK, MDF_DEFENSE, MDF_POWER, MDF_KNOWLEDGE, MDF_MORALE, MDF_LUCK }; enum PortraitType { PORT_BIG = 1, PORT_MEDIUM = 2, PORT_SMALL = 3 }; class HeroBase : public Skill::Primary, public MapPosition, public BitModes, public Control { public: HeroBase( const int type, const int race ); HeroBase(); ~HeroBase() override = default; enum { UNDEFINED, CAPTAIN, HEROES }; virtual const std::string & GetName() const = 0; virtual int GetColor() const = 0; int GetControl() const override = 0; virtual bool isValid() const = 0; virtual const Army & GetArmy() const = 0; virtual Army & GetArmy() = 0; virtual uint32_t GetMaxSpellPoints() const = 0; virtual int GetLevelSkill( int skill ) const = 0; virtual uint32_t GetSecondaryValues( int skill ) const = 0; virtual void ActionAfterBattle() = 0; virtual void ActionPreBattle() = 0; virtual const Castle * inCastle() const = 0; virtual void PortraitRedraw( const int32_t px, const int32_t py, const PortraitType type, fheroes2::Image & dstsf ) const = 0; virtual int GetType() const = 0; virtual bool isCaptain() const; virtual bool isHeroes() const; int GetAttackModificator( std::string * = nullptr ) const; int GetDefenseModificator( std::string * = nullptr ) const; int GetPowerModificator( std::string * = nullptr ) const; int GetKnowledgeModificator( std::string * = nullptr ) const; int GetMoraleModificator( std::string * = nullptr ) const; int GetLuckModificator( std::string * = nullptr ) const; double GetMagicStrategicValue( const double armyStrength ) const; uint32_t GetSpellPoints() const; bool HaveSpellPoints( const Spell & spell ) const; bool haveMovePoints( const Spell & spell ) const; bool CanCastSpell( const Spell & spell, std::string * res = nullptr ) const; bool CanLearnSpell( const Spell & spell ) const; void SpellCasted( const Spell & spell ); void SetSpellPoints( const uint32_t points ); bool isPotentSpellcaster() const; // Returns all spells that the hero can cast (including spells from the spell book and spell scrolls) SpellStorage getAllSpells() const; const SpellStorage & getMagicBookSpells() const { return spell_book; } void EditSpellBook(); Spell OpenSpellBook( const SpellBook::Filter filter, const bool canCastSpell, const bool restorePreviousState, const std::function * statusCallback ) const; bool HaveSpellBook() const { return hasArtifact( Artifact::MAGIC_BOOK ); } bool HaveSpell( const Spell & spell, const bool skip_bag = false ) const; void AppendSpellToBook( const Spell &, const bool without_wisdom = false ); void AppendSpellsToBook( const SpellStorage &, const bool without_wisdom = false ); // Adds the spell book to the artifact bag if it is not already there. Returns true if the spell book was actually added to the artifact bag, otherwise returns false. bool SpellBookActivate(); // Removes the spell book artifact from the artifact bag, if it is there, and removes all spells from the hero's spell book. void SpellBookDeactivate(); BagArtifacts & GetBagArtifacts() { return bag_artifacts; } const BagArtifacts & GetBagArtifacts() const { return bag_artifacts; } bool hasArtifact( const Artifact & art ) const; void LoadDefaults( const int type, const int race ); protected: friend StreamBase & operator<<( StreamBase & msg, const HeroBase & hero ); friend StreamBase & operator>>( StreamBase & msg, HeroBase & hero ); uint32_t magic_point; uint32_t move_point; SpellBook spell_book; BagArtifacts bag_artifacts; }; StreamBase & operator<<( StreamBase & msg, const HeroBase & hero ); StreamBase & operator>>( StreamBase & msg, HeroBase & hero ); #endif fheroes2-1.0.12+dfsg/src/fheroes2/heroes/heroes_dialog.cpp000066400000000000000000000453171456075706000233540ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include "agg_image.h" #include "army.h" #include "army_bar.h" #include "artifact.h" #include "color.h" #include "cursor.h" #include "dialog.h" #include "game_hotkeys.h" #include "gamedefs.h" #include "heroes.h" #include "heroes_base.h" #include "heroes_indicator.h" #include "icn.h" #include "image.h" #include "kingdom.h" #include "localevent.h" #include "math_base.h" #include "race.h" #include "screen.h" #include "settings.h" #include "skill.h" #include "skill_bar.h" #include "statusbar.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_text.h" #include "ui_tool.h" #include "ui_window.h" int Heroes::OpenDialog( const bool readonly, const bool fade, const bool disableDismiss, const bool disableSwitch, const bool renderBackgroundDialog ) { // Set the cursor image.This dialog does not require a cursor restorer. It is called from other dialogs that have the same cursor // or from the Game Area that will set the appropriate cursor after this dialog is closed. Cursor::Get().SetThemes( Cursor::POINTER ); fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::Rect dialogRoi; fheroes2::Rect dialogWithShadowRoi; std::unique_ptr background; std::unique_ptr restorer; if ( renderBackgroundDialog ) { background = std::make_unique( fheroes2::Display::DEFAULT_WIDTH, fheroes2::Display::DEFAULT_HEIGHT, false ); dialogRoi = background->activeArea(); dialogWithShadowRoi = background->totalArea(); } else { dialogRoi = { ( display.width() - fheroes2::Display::DEFAULT_WIDTH ) / 2, ( display.height() - fheroes2::Display::DEFAULT_HEIGHT ) / 2, fheroes2::Display::DEFAULT_WIDTH, fheroes2::Display::DEFAULT_HEIGHT }; dialogWithShadowRoi = { dialogRoi.x - 2 * BORDERWIDTH, dialogRoi.y - BORDERWIDTH, dialogRoi.width + 3 * BORDERWIDTH, dialogRoi.height + 3 * BORDERWIDTH }; restorer = std::make_unique( display, dialogRoi.x, dialogRoi.y, dialogRoi.width, dialogRoi.height ); } // Fade-out game screen only for 640x480 resolution and if 'renderBackgroundDialog' is false (we are replacing image in already opened dialog). const bool isDefaultScreenSize = display.isDefaultSize(); if ( fade && ( isDefaultScreenSize || !renderBackgroundDialog ) ) { fheroes2::fadeOutDisplay( dialogRoi, !isDefaultScreenSize ); } fheroes2::Point cur_pt = { dialogRoi.x, dialogRoi.y }; fheroes2::Point dst_pt( cur_pt ); fheroes2::Blit( fheroes2::AGG::GetICN( ICN::HEROBKG, 0 ), display, dst_pt.x, dst_pt.y ); fheroes2::Blit( fheroes2::AGG::GetICN( Settings::Get().isEvilInterfaceEnabled() ? ICN::HEROEXTE : ICN::HEROEXTG, 0 ), display, dst_pt.x, dst_pt.y ); // portrait dst_pt.x = cur_pt.x + 49; dst_pt.y = cur_pt.y + 31; const fheroes2::Rect portPos( dst_pt.x, dst_pt.y, 101, 93 ); PortraitRedraw( dst_pt.x, dst_pt.y, PORT_BIG, display ); // name std::string message = _( "%{name} the %{race} (Level %{level})" ); StringReplace( message, "%{name}", name ); StringReplace( message, "%{race}", Race::String( _race ) ); StringReplace( message, "%{level}", GetLevel() ); const fheroes2::Text title( message, fheroes2::FontType::normalWhite() ); title.draw( cur_pt.x + 320 - title.width() / 2, cur_pt.y + 3, display ); PrimarySkillsBar primskill_bar( this, false ); primskill_bar.setTableSize( { 4, 1 } ); primskill_bar.setInBetweenItemsOffset( { 6, 0 } ); primskill_bar.setRenderingOffset( { cur_pt.x + 156, cur_pt.y + 31 } ); primskill_bar.Redraw( display ); // morale dst_pt.x = cur_pt.x + 514; dst_pt.y = cur_pt.y + 34; MoraleIndicator moraleIndicator( this ); moraleIndicator.SetPos( dst_pt ); moraleIndicator.Redraw(); // luck dst_pt.x = cur_pt.x + 550; dst_pt.y = cur_pt.y + 34; LuckIndicator luckIndicator( this ); luckIndicator.SetPos( dst_pt ); luckIndicator.Redraw(); // army format spread dst_pt.x = cur_pt.x + 516; dst_pt.y = cur_pt.y + 63; const fheroes2::Sprite & sprite1 = fheroes2::AGG::GetICN( ICN::HSICONS, 9 ); fheroes2::Blit( sprite1, display, dst_pt.x, dst_pt.y ); const fheroes2::Rect rectSpreadArmyFormat( dst_pt.x, dst_pt.y, sprite1.width(), sprite1.height() ); const std::string descriptionSpreadArmyFormat = _( "'Spread' combat formation spreads the hero's units from the top to the bottom of the battlefield, with at least one empty space between each unit." ); const fheroes2::Point army1_pt( dst_pt.x - 1, dst_pt.y - 1 ); // army format grouped dst_pt.x = cur_pt.x + 552; dst_pt.y = cur_pt.y + 63; const fheroes2::Sprite & sprite2 = fheroes2::AGG::GetICN( ICN::HSICONS, 10 ); fheroes2::Blit( sprite2, display, dst_pt.x, dst_pt.y ); const fheroes2::Rect rectGroupedArmyFormat( dst_pt.x, dst_pt.y, sprite2.width(), sprite2.height() ); const std::string descriptionGroupedArmyFormat = _( "'Grouped' combat formation bunches the hero's army together in the center of their side of the battlefield." ); const fheroes2::Point army2_pt( dst_pt.x - 1, dst_pt.y - 1 ); // cursor format fheroes2::MovableSprite cursorFormat( fheroes2::AGG::GetICN( ICN::HSICONS, 11 ) ); const fheroes2::Point cursorFormatPos = army.isSpreadFormation() ? army1_pt : army2_pt; cursorFormat.setPosition( cursorFormatPos.x, cursorFormatPos.y ); // experience ExperienceIndicator experienceInfo( this ); experienceInfo.SetPos( fheroes2::Point( cur_pt.x + 512, cur_pt.y + 86 ) ); experienceInfo.Redraw(); // spell points SpellPointsIndicator spellPointsInfo( this ); spellPointsInfo.SetPos( fheroes2::Point( cur_pt.x + 550, cur_pt.y + 88 ) ); spellPointsInfo.Redraw(); // crest dst_pt.x = cur_pt.x + 49; dst_pt.y = cur_pt.y + 130; fheroes2::Blit( fheroes2::AGG::GetICN( ICN::CREST, Color::NONE == GetColor() ? Color::GetIndex( Settings::Get().CurrentColor() ) : Color::GetIndex( GetColor() ) ), display, dst_pt.x, dst_pt.y ); // monster dst_pt.x = cur_pt.x + 156; dst_pt.y = cur_pt.y + 130; ArmyBar selectArmy( &army, false, readonly ); selectArmy.setTableSize( { 5, 1 } ); selectArmy.setRenderingOffset( dst_pt ); selectArmy.setInBetweenItemsOffset( { 6, 0 } ); selectArmy.Redraw( display ); // secskill SecondarySkillsBar secskill_bar( *this, false ); secskill_bar.setTableSize( { 8, 1 } ); secskill_bar.setInBetweenItemsOffset( { 5, 0 } ); secskill_bar.SetContent( secondary_skills.ToVector() ); secskill_bar.setRenderingOffset( { cur_pt.x + 3, cur_pt.y + 233 } ); secskill_bar.Redraw( display ); // bottom small bar dst_pt.x = cur_pt.x + 22; dst_pt.y = cur_pt.y + 460; const fheroes2::Sprite & bar = fheroes2::AGG::GetICN( ICN::HSBTNS, 8 ); fheroes2::Blit( bar, display, dst_pt.x, dst_pt.y ); StatusBar statusBar; // Status bar must be smaller due to extra art on both sides. statusBar.setRoi( { dst_pt.x + 16, dst_pt.y + 3, bar.width() - 16 * 2, 0 } ); // artifact bar dst_pt.x = cur_pt.x + 51; dst_pt.y = cur_pt.y + 308; ArtifactsBar selectArtifacts( this, false, readonly, false, true, &statusBar ); selectArtifacts.setTableSize( { 7, 2 } ); selectArtifacts.setInBetweenItemsOffset( { 15, 15 } ); selectArtifacts.SetContent( GetBagArtifacts() ); selectArtifacts.setRenderingOffset( dst_pt ); selectArtifacts.Redraw( display ); // button prev dst_pt.x = cur_pt.x; dst_pt.y = cur_pt.y + fheroes2::Display::DEFAULT_HEIGHT - 20; fheroes2::Button buttonPrevHero( dst_pt.x, dst_pt.y, ICN::HSBTNS, 4, 5 ); fheroes2::TimedEventValidator timedButtonPrevHero( [&buttonPrevHero]() { return buttonPrevHero.isPressed(); } ); buttonPrevHero.subscribe( &timedButtonPrevHero ); // button next dst_pt.x = cur_pt.x + fheroes2::Display::DEFAULT_WIDTH - 22; dst_pt.y = cur_pt.y + fheroes2::Display::DEFAULT_HEIGHT - 20; fheroes2::Button buttonNextHero( dst_pt.x, dst_pt.y, ICN::HSBTNS, 6, 7 ); fheroes2::TimedEventValidator timedButtonNextHero( [&buttonNextHero]() { return buttonNextHero.isPressed(); } ); buttonNextHero.subscribe( &timedButtonNextHero ); // button dismiss dst_pt.x = cur_pt.x + 9; dst_pt.y = cur_pt.y + 318; fheroes2::ButtonSprite buttonDismiss( dst_pt.x, dst_pt.y, fheroes2::AGG::GetICN( ICN::BUTTON_VERTICAL_DISMISS, 0 ), fheroes2::AGG::GetICN( ICN::BUTTON_VERTICAL_DISMISS, 1 ), fheroes2::AGG::GetICN( ICN::DISMISS_HERO_DISABLED_BUTTON, 0 ) ); fheroes2::addGradientShadow( fheroes2::AGG::GetICN( ICN::BUTTON_VERTICAL_DISMISS, 0 ), display, { cur_pt.x + 9, cur_pt.y + 318 }, { -3, 5 } ); // button exit dst_pt.x = cur_pt.x + 602; dst_pt.y = cur_pt.y + 318; fheroes2::Button buttonExit( dst_pt.x, dst_pt.y, ICN::BUTTON_VERTICAL_EXIT, 0, 1 ); LocalEvent & le = LocalEvent::Get(); if ( inCastle() || readonly || disableDismiss || Modes( NOTDISMISS ) ) { buttonDismiss.disable(); if ( readonly || disableDismiss ) { buttonDismiss.hide(); } } if ( readonly || disableSwitch || 2 > GetKingdom().GetHeroes().size() ) { buttonNextHero.disable(); buttonPrevHero.disable(); } buttonPrevHero.draw(); buttonNextHero.draw(); buttonDismiss.draw(); buttonExit.draw(); // Fade-in hero dialog. if ( fade ) { if ( renderBackgroundDialog && !isDefaultScreenSize ) { // We need to expand the ROI for the next render to properly render window borders and shadow. display.updateNextRenderRoi( dialogWithShadowRoi ); } // Use half fade if game resolution is not 640x480. fheroes2::fadeInDisplay( dialogRoi, !isDefaultScreenSize ); } else { display.render( dialogWithShadowRoi ); } bool redrawMorale = false; bool redrawLuck = false; message.clear(); // dialog menu loop while ( le.HandleEvents() ) { if ( redrawMorale ) { moraleIndicator.Redraw(); display.render( dialogRoi ); redrawMorale = false; } if ( redrawLuck ) { luckIndicator.Redraw(); display.render( dialogRoi ); redrawLuck = false; } // exit if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) { // Fade-out hero dialog. fheroes2::fadeOutDisplay( dialogRoi, !isDefaultScreenSize ); return Dialog::CANCEL; } // heroes troops if ( le.MouseCursor( selectArmy.GetArea() ) && selectArmy.QueueEventProcessing( &message ) ) { if ( selectArtifacts.isSelected() ) selectArtifacts.ResetSelected(); selectArmy.Redraw( display ); redrawMorale = true; redrawLuck = true; } if ( le.MouseCursor( selectArtifacts.GetArea() ) && selectArtifacts.QueueEventProcessing( &message ) ) { if ( selectArmy.isSelected() ) selectArmy.ResetSelected(); selectArtifacts.Redraw( display ); spellPointsInfo.Redraw(); redrawMorale = true; redrawLuck = true; } // button click or hotkey press le.MousePressLeft( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); if ( buttonDismiss.isEnabled() ) { if ( le.MousePressLeft( buttonDismiss.area() ) || HotKeyPressEvent( Game::HotKeyEvent::ARMY_DISMISS ) ) { buttonDismiss.drawOnPress(); } else { buttonDismiss.drawOnRelease(); } } if ( buttonPrevHero.isEnabled() ) le.MousePressLeft( buttonPrevHero.area() ) ? buttonPrevHero.drawOnPress() : buttonPrevHero.drawOnRelease(); if ( buttonNextHero.isEnabled() ) le.MousePressLeft( buttonNextHero.area() ) ? buttonNextHero.drawOnPress() : buttonNextHero.drawOnRelease(); // prev hero if ( buttonPrevHero.isEnabled() && ( le.MouseClickLeft( buttonPrevHero.area() ) || HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_LEFT ) || timedButtonPrevHero.isDelayPassed() ) ) { return Dialog::PREV; } // next hero if ( buttonNextHero.isEnabled() && ( le.MouseClickLeft( buttonNextHero.area() ) || HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_RIGHT ) || timedButtonNextHero.isDelayPassed() ) ) { return Dialog::NEXT; } // dismiss if ( buttonDismiss.isEnabled() && buttonDismiss.isVisible() && ( le.MouseClickLeft( buttonDismiss.area() ) || Game::HotKeyPressEvent( Game::HotKeyEvent::ARMY_DISMISS ) ) && Dialog::YES == fheroes2::showStandardTextMessage( GetName(), _( "Are you sure you want to dismiss this Hero?" ), Dialog::YES | Dialog::NO ) ) { // Fade-out hero dialog. fheroes2::fadeOutDisplay( dialogRoi, !isDefaultScreenSize ); return Dialog::DISMISS; } if ( le.MouseCursor( moraleIndicator.GetArea() ) ) MoraleIndicator::QueueEventProcessing( moraleIndicator ); else if ( le.MouseCursor( luckIndicator.GetArea() ) ) LuckIndicator::QueueEventProcessing( luckIndicator ); else if ( le.MouseCursor( experienceInfo.GetArea() ) ) experienceInfo.QueueEventProcessing(); else if ( le.MouseCursor( spellPointsInfo.GetArea() ) ) spellPointsInfo.QueueEventProcessing(); // left click info if ( !readonly && le.MouseClickLeft( rectSpreadArmyFormat ) && !army.isSpreadFormation() ) { cursorFormat.setPosition( army1_pt.x, army1_pt.y ); display.render( dialogRoi ); army.SetSpreadFormation( true ); } else if ( !readonly && le.MouseClickLeft( rectGroupedArmyFormat ) && army.isSpreadFormation() ) { cursorFormat.setPosition( army2_pt.x, army2_pt.y ); display.render( dialogRoi ); army.SetSpreadFormation( false ); } else if ( le.MouseCursor( secskill_bar.GetArea() ) && secskill_bar.QueueEventProcessing( &message ) ) { display.render( dialogRoi ); } else if ( le.MouseCursor( primskill_bar.GetArea() ) && primskill_bar.QueueEventProcessing( &message ) ) { display.render( dialogRoi ); } // right info if ( le.MousePressRight( portPos ) ) { Dialog::QuickInfo( *this, true ); } else if ( le.MousePressRight( rectSpreadArmyFormat ) ) { fheroes2::showStandardTextMessage( _( "Spread Formation" ), descriptionSpreadArmyFormat, Dialog::ZERO ); } else if ( le.MousePressRight( rectGroupedArmyFormat ) ) { fheroes2::showStandardTextMessage( _( "Grouped Formation" ), descriptionGroupedArmyFormat, Dialog::ZERO ); } // status message if ( le.MouseCursor( moraleIndicator.GetArea() ) ) { message = fheroes2::MoraleString( army.GetMorale() ); } else if ( le.MouseCursor( luckIndicator.GetArea() ) ) { message = fheroes2::LuckString( army.GetLuck() ); } else if ( le.MouseCursor( experienceInfo.GetArea() ) ) message = _( "View Experience Info" ); else if ( le.MouseCursor( spellPointsInfo.GetArea() ) ) message = _( "View Spell Points Info" ); else if ( le.MouseCursor( rectSpreadArmyFormat ) ) message = _( "Set army combat formation to 'Spread'" ); else if ( le.MouseCursor( rectGroupedArmyFormat ) ) message = _( "Set army combat formation to 'Grouped'" ); else if ( le.MouseCursor( buttonExit.area() ) ) message = _( "Exit Hero Screen" ); else if ( buttonDismiss.isVisible() && le.MouseCursor( buttonDismiss.area() ) ) { if ( inCastle() ) { message = _( "You cannot dismiss a hero in a castle" ); } else if ( Modes( NOTDISMISS ) ) { message = _( "Dismissal of %{name} the %{race} is prohibited by scenario" ); StringReplace( message, "%{name}", name ); StringReplace( message, "%{race}", Race::String( _race ) ); } else if ( buttonDismiss.isEnabled() ) { message = _( "Dismiss %{name} the %{race}" ); StringReplace( message, "%{name}", name ); StringReplace( message, "%{race}", Race::String( _race ) ); } } else if ( buttonPrevHero.isEnabled() && le.MouseCursor( buttonPrevHero.area() ) ) message = _( "Show previous hero" ); else if ( buttonNextHero.isEnabled() && le.MouseCursor( buttonNextHero.area() ) ) message = _( "Show next hero" ); if ( message.empty() ) statusBar.ShowMessage( _( "Hero Screen" ) ); else { statusBar.ShowMessage( message ); message.clear(); } } return Dialog::CANCEL; } fheroes2-1.0.12+dfsg/src/fheroes2/heroes/heroes_indicator.cpp000066400000000000000000000215031456075706000240600ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "heroes_indicator.h" #include #include #include #include #include "agg_image.h" #include "army.h" #include "dialog.h" #include "heroes.h" #include "icn.h" #include "localevent.h" #include "screen.h" #include "tools.h" #include "translations.h" #include "ui_dialog.h" #include "ui_text.h" namespace fheroes2 { std::string MoraleString( const int morale ) { if ( morale == Morale::BLOOD ) { return _( "Blood Morale" ); } std::string str = _( "%{morale} Morale" ); StringReplace( str, "%{morale}", Morale::String( morale ) ); return str; } std::string LuckString( const int luck ) { std::string str = _( "%{luck} Luck" ); StringReplace( str, "%{luck}", Luck::String( luck ) ); return str; } } HeroesIndicator::HeroesIndicator( const Heroes * hero ) : _hero( hero ) , _back( fheroes2::Display::instance() ) { assert( _hero != nullptr ); } const fheroes2::Rect & HeroesIndicator::GetArea() const { return _area; } void HeroesIndicator::SetPos( const fheroes2::Point & pt ) { _area.x = pt.x; _area.y = pt.y; _back.update( _area.x, _area.y, _area.width, _area.height ); } void LuckIndicator::Redraw() { std::string modificators; _luck = _hero->GetLuckWithModificators( &modificators ); _description.clear(); _description.append( Luck::Description( _luck ) ); _description.append( "\n\n" ); _description.append( _( "Current Luck Modifiers:" ) ); _description.append( "\n\n" ); const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::HSICONS, ( 0 > _luck ? 3 : ( 0 < _luck ? 2 : 6 ) ) ); const int32_t inter = 6; int32_t count = ( 0 == _luck ? 1 : std::abs( _luck ) ); int32_t cx = _area.x + ( _area.width - ( sprite.width() + inter * ( count - 1 ) ) ) / 2; const int32_t cy = _area.y + ( _area.height - sprite.height() ) / 2; if ( modificators.empty() ) { _description.append( _( "None" ) ); } else { _description.append( modificators ); } _back.restore(); fheroes2::Display & display = fheroes2::Display::instance(); while ( count-- ) { fheroes2::Blit( sprite, display, cx, cy ); cx += inter; } } void LuckIndicator::QueueEventProcessing( const LuckIndicator & indicator ) { LocalEvent & le = LocalEvent::Get(); if ( le.MouseClickLeft( indicator._area ) ) { fheroes2::showStandardTextMessage( fheroes2::LuckString( indicator._luck ), indicator._description, Dialog::OK ); } else if ( le.MousePressRight( indicator._area ) ) { fheroes2::showStandardTextMessage( fheroes2::LuckString( indicator._luck ), indicator._description, Dialog::ZERO ); } } void MoraleIndicator::Redraw() { std::string modificators; _morale = _hero->GetMoraleWithModificators( &modificators ); _description.clear(); _description.append( Morale::Description( _morale ) ); _description.append( "\n\n" ); _description.append( _( "Current Morale Modifiers:" ) ); _description.append( "\n\n" ); if ( modificators.empty() ) { _description.append( _( "None" ) ); } else { _description.append( modificators ); } if ( _hero->GetArmy().AllTroopsAreUndead() ) { _description.append( "\n\n" ); _description.append( _( "Entire army is undead, so morale does not apply." ) ); } const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::HSICONS, ( 0 > _morale ? 5 : ( 0 < _morale ? 4 : 7 ) ) ); const int32_t inter = 6; int32_t count = ( 0 == _morale ? 1 : std::abs( _morale ) ); int32_t cx = _area.x + ( _area.width - ( sprite.width() + inter * ( count - 1 ) ) ) / 2; const int32_t cy = _area.y + ( _area.height - sprite.height() ) / 2; _back.restore(); fheroes2::Display & display = fheroes2::Display::instance(); while ( count-- ) { fheroes2::Blit( sprite, display, cx, cy ); cx += inter; } } void MoraleIndicator::QueueEventProcessing( const MoraleIndicator & indicator ) { LocalEvent & le = LocalEvent::Get(); if ( le.MouseClickLeft( indicator._area ) ) { fheroes2::showStandardTextMessage( fheroes2::MoraleString( indicator._morale ), indicator._description, Dialog::OK ); } else if ( le.MousePressRight( indicator._area ) ) { fheroes2::showStandardTextMessage( fheroes2::MoraleString( indicator._morale ), indicator._description, Dialog::ZERO ); } } ExperienceIndicator::ExperienceIndicator( const Heroes * hero ) : HeroesIndicator( hero ) { _area.width = 35; _area.height = 36; _description = _( "Current experience %{exp1}.\n Next level %{exp2}." ); const uint32_t experience = _hero->GetExperience(); StringReplace( _description, "%{exp1}", experience ); StringReplace( _description, "%{exp2}", Heroes::GetExperienceFromLevel( Heroes::GetLevelFromExperience( experience ) ) ); } void ExperienceIndicator::Redraw() const { fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::HSICONS, 1 ); fheroes2::Blit( sprite, display, _area.x, _area.y ); const fheroes2::Text text( std::to_string( _hero->GetExperience() ), fheroes2::FontType::smallWhite() ); text.draw( _area.x + 17 - text.width() / 2, _area.y + 25, display ); } void ExperienceIndicator::QueueEventProcessing() const { LocalEvent & le = LocalEvent::Get(); if ( le.MouseClickLeft( _area ) || le.MousePressRight( _area ) ) { std::string message = _( "Level %{level}" ); StringReplace( message, "%{level}", _hero->GetLevel() ); fheroes2::showStandardTextMessage( std::move( message ), _description, ( le.MousePressRight() ? Dialog::ZERO : Dialog::OK ) ); } } SpellPointsIndicator::SpellPointsIndicator( const Heroes * hero ) : HeroesIndicator( hero ) { _area.width = 35; _area.height = 36; _description = _( "%{name} currently has %{point} spell points out of a maximum of %{max}. The maximum number of spell points is 10 times the hero's knowledge. It is occasionally possible for the hero to have more than their maximum spell points via special events." ); StringReplace( _description, "%{name}", _hero->GetName() ); StringReplace( _description, "%{point}", _hero->GetSpellPoints() ); StringReplace( _description, "%{max}", _hero->GetMaxSpellPoints() ); } void SpellPointsIndicator::Redraw() const { fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::HSICONS, 8 ); fheroes2::Blit( sprite, display, _area.x, _area.y ); const fheroes2::Text text( std::to_string( _hero->GetSpellPoints() ) + "/" + std::to_string( _hero->GetMaxSpellPoints() ), fheroes2::FontType::smallWhite() ); text.draw( _area.x + sprite.width() / 2 - text.width() / 2, _area.y + 23, display ); } void SpellPointsIndicator::QueueEventProcessing() const { LocalEvent & le = LocalEvent::Get(); if ( le.MouseClickLeft( _area ) || le.MousePressRight( _area ) ) { fheroes2::showStandardTextMessage( _( "Spell Points" ), _description, ( le.MousePressRight() ? Dialog::ZERO : Dialog::OK ) ); } } fheroes2-1.0.12+dfsg/src/fheroes2/heroes/heroes_indicator.h000066400000000000000000000071361456075706000235330ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2HEROESIND_H #define H2HEROESIND_H #include #include "image.h" #include "luck.h" #include "math_base.h" #include "morale.h" namespace fheroes2 { std::string LuckString( const int luck ); std::string MoraleString( const int morale ); } class Heroes; class HeroesIndicator { public: explicit HeroesIndicator( const Heroes * hero ); const fheroes2::Rect & GetArea() const; void SetPos( const fheroes2::Point & pt ); protected: const Heroes * _hero; fheroes2::Rect _area; fheroes2::ImageRestorer _back; std::string _description; }; class LuckIndicator : public HeroesIndicator { public: explicit LuckIndicator( const Heroes * hero ) : HeroesIndicator( hero ) { _area.width = 35; _area.height = 26; } void Redraw(); // Redraws only the background under this indicator, but not the indicator itself void redrawOnlyBackground() { _back.restore(); } static void QueueEventProcessing( const LuckIndicator & indicator ); private: int _luck{ Luck::NORMAL }; }; class MoraleIndicator : public HeroesIndicator { public: explicit MoraleIndicator( const Heroes * hero ) : HeroesIndicator( hero ) { _area.width = 35; _area.height = 26; } void Redraw(); // Redraws only the background under this indicator, but not the indicator itself void redrawOnlyBackground() { _back.restore(); } static void QueueEventProcessing( const MoraleIndicator & indicator ); private: int _morale{ Morale::NORMAL }; }; class ExperienceIndicator : public HeroesIndicator { public: explicit ExperienceIndicator( const Heroes * hero ); void Redraw() const; void QueueEventProcessing() const; }; class SpellPointsIndicator : public HeroesIndicator { public: explicit SpellPointsIndicator( const Heroes * hero ); void Redraw() const; void QueueEventProcessing() const; }; #endif fheroes2-1.0.12+dfsg/src/fheroes2/heroes/heroes_meeting.cpp000066400000000000000000000663311456075706000235440ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include "agg_image.h" #include "army.h" #include "army_bar.h" #include "army_troop.h" #include "artifact.h" #include "artifact_info.h" #include "cursor.h" #include "dialog.h" #include "game.h" #include "game_hotkeys.h" #include "gamedefs.h" #include "heroes.h" #include "heroes_base.h" #include "heroes_indicator.h" #include "icn.h" #include "image.h" #include "localevent.h" #include "math_base.h" #include "monster.h" #include "screen.h" #include "skill.h" #include "skill_bar.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_text.h" #include "ui_tool.h" namespace { fheroes2::ButtonSprite createMoveButton( const int32_t icnId, const int32_t offsetX, const int32_t offsetY, const fheroes2::Image & display ) { const fheroes2::Sprite & originalReleasedImage = fheroes2::AGG::GetICN( icnId, 0 ); const fheroes2::Sprite & originalPressedImage = fheroes2::AGG::GetICN( icnId, 1 ); const int32_t minX = std::min( originalReleasedImage.x(), originalPressedImage.x() ); const int32_t minY = std::min( originalReleasedImage.y(), originalPressedImage.y() ); return fheroes2::makeButtonWithShadow( offsetX + minX, offsetY + minY, originalReleasedImage, originalPressedImage, display, fheroes2::Point( -3, 3 ) ); } void moveArtifacts( BagArtifacts & bagFrom, BagArtifacts & bagTo ) { size_t toIdx = 0; for ( size_t fromIdx = 0; fromIdx < bagFrom.size(); ++fromIdx ) { if ( bagFrom[fromIdx].isValid() && bagFrom[fromIdx].GetID() != Artifact::MAGIC_BOOK ) { while ( toIdx < bagTo.size() ) { if ( !bagTo[toIdx].isValid() ) break; ++toIdx; } if ( toIdx == bagTo.size() ) break; std::swap( bagFrom[fromIdx], bagTo[toIdx] ); } } } } class MeetingArmyBar : public ArmyBar { public: using ArmyBar::RedrawItem; explicit MeetingArmyBar( Army * army ) : ArmyBar( army, true, false, false ) {} void RedrawBackground( const fheroes2::Rect & roi, fheroes2::Image & image ) override { if ( _cachedBackground.empty() ) { _cachedBackground.resize( roi.width, roi.height ); fheroes2::Copy( image, roi.x, roi.y, _cachedBackground, 0, 0, roi.width, roi.height ); } fheroes2::Blit( _cachedBackground, 0, 0, image, roi.x, roi.y, roi.width, roi.height ); } void RedrawItem( ArmyTroop & troop, const fheroes2::Rect & roi, bool isSelected, fheroes2::Image & image ) override { if ( !troop.isValid() ) return; const fheroes2::Text text( std::to_string( troop.GetCount() ), fheroes2::FontType::smallWhite() ); const fheroes2::Sprite & mons32 = fheroes2::AGG::GetICN( ICN::MONS32, troop.GetSpriteIndex() ); fheroes2::Rect srcrt( 0, 0, mons32.width(), mons32.height() ); if ( mons32.width() > roi.width ) { srcrt.x = ( mons32.width() - roi.width ) / 2; srcrt.width = roi.width; } if ( mons32.height() > roi.height ) { srcrt.y = ( mons32.height() - roi.height ) / 2; srcrt.height = roi.height; } int32_t offsetX = ( roi.width - mons32.width() ) / 2; int32_t offsetY = roi.height - mons32.height() - 3; if ( offsetX < 1 ) offsetX = 1; if ( offsetY < 1 ) offsetY = 1; fheroes2::Blit( mons32, srcrt.x, srcrt.y, image, roi.x + offsetX, roi.y + offsetY, srcrt.width, srcrt.height ); text.draw( roi.x + ( roi.width - text.width() ) / 2, roi.y + roi.height + 1, image ); if ( isSelected ) { spcursor.setPosition( roi.x, roi.y ); spcursor.show(); } } private: fheroes2::Image _cachedBackground; }; class MeetingArtifactBar : public ArtifactsBar { public: using ArtifactsBar::RedrawItem; explicit MeetingArtifactBar( const Heroes * hero ) : ArtifactsBar( hero, true, false, false, false, nullptr ) {} void RedrawBackground( const fheroes2::Rect & roi, fheroes2::Image & image ) override { if ( _cachedBackground.empty() ) { _cachedBackground.resize( roi.width, roi.height ); fheroes2::Copy( image, roi.x, roi.y, _cachedBackground, 0, 0, roi.width, roi.height ); } fheroes2::Blit( _cachedBackground, 0, 0, image, roi.x, roi.y, roi.width, roi.height ); } void RedrawItem( Artifact & arifact, const fheroes2::Rect & roi, bool isSelected, fheroes2::Image & image ) override { if ( !arifact.isValid() ) return; const fheroes2::Sprite & artifactSprite = fheroes2::AGG::GetICN( ICN::ARTFX, arifact.IndexSprite32() ); fheroes2::Blit( artifactSprite, image, roi.x + 1, roi.y + 1 ); if ( isSelected ) { spcursor.setPosition( roi.x, roi.y ); spcursor.show(); } } private: fheroes2::Image _cachedBackground; }; class MeetingPrimarySkillsBar : public PrimarySkillsBar { public: explicit MeetingPrimarySkillsBar( const Heroes * hero ) : PrimarySkillsBar( hero, true ) {} void RedrawBackground( const fheroes2::Rect &, fheroes2::Image & ) override { // Just do nothing } }; class MeetingSecondarySkillsBar : public SecondarySkillsBar { public: explicit MeetingSecondarySkillsBar( const Heroes & hero ) : SecondarySkillsBar( hero ) {} void RedrawBackground( const fheroes2::Rect & roi, fheroes2::Image & image ) override { if ( _cachedBackground.empty() ) { _cachedBackground.resize( roi.width, roi.height ); fheroes2::Copy( image, roi.x, roi.y, _cachedBackground, 0, 0, roi.width, roi.height ); } fheroes2::Blit( _cachedBackground, 0, 0, image, roi.x, roi.y, roi.width, roi.height ); } void RedrawItem( Skill::Secondary & skill, const fheroes2::Rect & roi, fheroes2::Image & image ) override { if ( !skill.isValid() ) return; const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::MINISS, skill.GetIndexSprite2() ); fheroes2::Blit( sprite, image, roi.x + ( roi.width - sprite.width() ) / 2, roi.y + ( roi.height - sprite.height() ) / 2 ); const fheroes2::Text text( std::to_string( skill.Level() ), fheroes2::FontType::smallWhite() ); text.draw( roi.x + ( roi.width - text.width() ) - 3, roi.y + roi.height - text.height() + 2, image ); } private: fheroes2::Image _cachedBackground; }; void Heroes::MeetingDialog( Heroes & otherHero ) { fheroes2::Display & display = fheroes2::Display::instance(); // Set the cursor image. This dialog is called after hero's move and does not require a cursor restorer: // Game Area event processor will change it to the appropriate one after this dialog is closed. Cursor::Get().SetThemes( Cursor::POINTER ); const fheroes2::Sprite & backSprite = fheroes2::AGG::GetICN( ICN::SWAPWIN, 0 ); const fheroes2::Point cur_pt( ( display.width() - backSprite.width() ) / 2, ( display.height() - backSprite.height() ) / 2 ); fheroes2::ImageRestorer restorer( display, cur_pt.x, cur_pt.y, backSprite.width(), backSprite.height() ); const fheroes2::Rect src_rt( 0, 0, fheroes2::Display::DEFAULT_WIDTH, fheroes2::Display::DEFAULT_HEIGHT ); // Fade-out game screen only for 640x480 resolution. const fheroes2::Rect fadeRoi( src_rt + cur_pt ); const bool isDefaultScreenSize = display.isDefaultSize(); if ( isDefaultScreenSize ) { fheroes2::fadeOutDisplay(); } // background fheroes2::Point dst_pt( cur_pt ); fheroes2::Blit( backSprite, src_rt.x, src_rt.y, display, dst_pt.x, dst_pt.y, src_rt.width, src_rt.height ); // header std::string message( _( "%{name1} meets %{name2}" ) ); StringReplace( message, "%{name1}", GetName() ); StringReplace( message, "%{name2}", otherHero.GetName() ); const fheroes2::Text text( message, fheroes2::FontType::normalWhite() ); text.draw( cur_pt.x + 320 - text.width() / 2, cur_pt.y + 29, display ); const int iconsH1XOffset = 34; const int iconsH2XOffset = 571; const int portraitYOffset = 72; // portrait dst_pt.x = cur_pt.x + 93; dst_pt.y = cur_pt.y + portraitYOffset; const fheroes2::Sprite & portrait1 = GetPortrait( PORT_BIG ); const fheroes2::Rect hero1Area( dst_pt.x, dst_pt.y, portrait1.width(), portrait1.height() ); PortraitRedraw( dst_pt.x, dst_pt.y, PORT_BIG, display ); dst_pt.x = cur_pt.x + 445; dst_pt.y = cur_pt.y + portraitYOffset; const fheroes2::Sprite & portrait2 = otherHero.GetPortrait( PORT_BIG ); const fheroes2::Rect hero2Area( dst_pt.x, dst_pt.y, portrait2.width(), portrait2.height() ); otherHero.PortraitRedraw( dst_pt.x, dst_pt.y, PORT_BIG, display ); MoraleIndicator moraleIndicator1( this ); dst_pt.x = cur_pt.x + iconsH1XOffset; dst_pt.y = cur_pt.y + portraitYOffset + moraleIndicator1.GetArea().height / 3; moraleIndicator1.SetPos( dst_pt ); moraleIndicator1.Redraw(); LuckIndicator luckIndicator1( this ); dst_pt.x = cur_pt.x + iconsH1XOffset; dst_pt.y = cur_pt.y + portraitYOffset + portrait1.height() - luckIndicator1.GetArea().height * 4 / 3; luckIndicator1.SetPos( dst_pt ); luckIndicator1.Redraw(); MoraleIndicator moraleIndicator2( &otherHero ); dst_pt.x = cur_pt.x + iconsH2XOffset; dst_pt.y = cur_pt.y + portraitYOffset + moraleIndicator2.GetArea().height / 3; moraleIndicator2.SetPos( dst_pt ); moraleIndicator2.Redraw(); LuckIndicator luckIndicator2( &otherHero ); dst_pt.x = cur_pt.x + iconsH2XOffset; dst_pt.y = cur_pt.y + portraitYOffset + portrait2.height() - luckIndicator2.GetArea().height * 4 / 3; luckIndicator2.SetPos( dst_pt ); luckIndicator2.Redraw(); // primary skill fheroes2::ImageRestorer backPrimary( display, cur_pt.x + 255, cur_pt.y + 50, 130, 135 ); MeetingPrimarySkillsBar primskill_bar1( this ); primskill_bar1.setTableSize( { 1, 4 } ); primskill_bar1.setInBetweenItemsOffset( { 0, -1 } ); primskill_bar1.SetTextOff( 70, -25 ); primskill_bar1.setRenderingOffset( { cur_pt.x + 216, cur_pt.y + 51 } ); MeetingPrimarySkillsBar primskill_bar2( &otherHero ); primskill_bar2.setTableSize( { 1, 4 } ); primskill_bar2.setInBetweenItemsOffset( { 0, -1 } ); primskill_bar2.SetTextOff( -70, -25 ); primskill_bar2.setRenderingOffset( { cur_pt.x + 389, cur_pt.y + 51 } ); fheroes2::RedrawPrimarySkillInfo( cur_pt, &primskill_bar1, &primskill_bar2 ); // secondary skill MeetingSecondarySkillsBar secskill_bar1( *this ); secskill_bar1.setTableSize( { 8, 1 } ); secskill_bar1.setInBetweenItemsOffset( { -1, 0 } ); secskill_bar1.SetContent( secondary_skills.ToVector() ); secskill_bar1.setRenderingOffset( { cur_pt.x + 22, cur_pt.y + 199 } ); secskill_bar1.Redraw( display ); MeetingSecondarySkillsBar secskill_bar2( otherHero ); secskill_bar2.setTableSize( { 8, 1 } ); secskill_bar2.setInBetweenItemsOffset( { -1, 0 } ); secskill_bar2.SetContent( otherHero.GetSecondarySkills().ToVector() ); secskill_bar2.setRenderingOffset( { cur_pt.x + 353, cur_pt.y + 199 } ); secskill_bar2.Redraw( display ); const fheroes2::Sprite & moveButtonBackground = fheroes2::AGG::GetICN( ICN::STONEBAK, 0 ); fheroes2::Blit( moveButtonBackground, 292, 270, display, cur_pt.x + 292, cur_pt.y + 270, 48, 44 ); // The original resources do not have such animated buttons so we have to create those. fheroes2::ButtonSprite moveArmyToHero2 = createMoveButton( ICN::SWAP_ARROW_LEFT_TO_RIGHT, cur_pt.x + 298, cur_pt.y + 267, display ); fheroes2::ButtonSprite moveArmyToHero1 = createMoveButton( ICN::SWAP_ARROW_RIGHT_TO_LEFT, cur_pt.x + 298, cur_pt.y + 290, display ); fheroes2::ImageRestorer armyCountBackgroundRestorerLeft( display, cur_pt.x + 36, cur_pt.y + 310, 223, 20 ); fheroes2::ImageRestorer armyCountBackgroundRestorerRight( display, cur_pt.x + 381, cur_pt.y + 310, 223, 20 ); // army dst_pt.x = cur_pt.x + 36; dst_pt.y = cur_pt.y + 267; MeetingArmyBar selectArmy1( &GetArmy() ); selectArmy1.setTableSize( { 5, 1 } ); selectArmy1.setRenderingOffset( dst_pt ); selectArmy1.setInBetweenItemsOffset( { 2, 0 } ); selectArmy1.Redraw( display ); dst_pt.x = cur_pt.x + 381; dst_pt.y = cur_pt.y + 267; MeetingArmyBar selectArmy2( &otherHero.GetArmy() ); selectArmy2.setTableSize( { 5, 1 } ); selectArmy2.setRenderingOffset( dst_pt ); selectArmy2.setInBetweenItemsOffset( { 2, 0 } ); selectArmy2.Redraw( display ); // artifact dst_pt.x = cur_pt.x + 23; dst_pt.y = cur_pt.y + 347; MeetingArtifactBar selectArtifacts1( this ); selectArtifacts1.setTableSize( { 7, 2 } ); selectArtifacts1.setInBetweenItemsOffset( { 2, 2 } ); selectArtifacts1.SetContent( GetBagArtifacts() ); selectArtifacts1.setRenderingOffset( dst_pt ); selectArtifacts1.Redraw( display ); dst_pt.x = cur_pt.x + 367; dst_pt.y = cur_pt.y + 347; MeetingArtifactBar selectArtifacts2( &otherHero ); selectArtifacts2.setTableSize( { 7, 2 } ); selectArtifacts2.setInBetweenItemsOffset( { 2, 2 } ); selectArtifacts2.SetContent( otherHero.GetBagArtifacts() ); selectArtifacts2.setRenderingOffset( dst_pt ); selectArtifacts2.Redraw( display ); fheroes2::Blit( moveButtonBackground, 292, 363, display, cur_pt.x + 292, cur_pt.y + 363, 48, 44 ); fheroes2::ButtonSprite moveArtifactsToHero2 = createMoveButton( ICN::SWAP_ARROW_LEFT_TO_RIGHT, cur_pt.x + 298, cur_pt.y + 361, display ); fheroes2::ButtonSprite moveArtifactsToHero1 = createMoveButton( ICN::SWAP_ARROW_RIGHT_TO_LEFT, cur_pt.x + 298, cur_pt.y + 384, display ); // button exit dst_pt.x = cur_pt.x + 280; dst_pt.y = cur_pt.y + 428; fheroes2::Button buttonExit( dst_pt.x, dst_pt.y, ICN::BUTTON_EXIT_HEROES_MEETING, 0, 1 ); moveArmyToHero2.draw(); moveArmyToHero1.draw(); moveArtifactsToHero2.draw(); moveArtifactsToHero1.draw(); buttonExit.draw(); // Fade-in heroes meeting dialog. Use half fade if game resolution is not 640x480. // This dialog currently does not have borders so its ROI is the same as fade ROI. fheroes2::fadeInDisplay( fadeRoi, !isDefaultScreenSize ); const int32_t hero1ScoutAreaBonus = bag_artifacts.getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::AREA_REVEAL_DISTANCE ); const int32_t hero2ScoutAreaBonus = otherHero.GetBagArtifacts().getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::AREA_REVEAL_DISTANCE ); LocalEvent & le = LocalEvent::Get(); // message loop while ( le.HandleEvents() ) { le.MousePressLeft( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); if ( le.MousePressLeft( moveArmyToHero2.area() ) || HotKeyHoldEvent( Game::HotKeyEvent::DEFAULT_RIGHT ) ) { moveArmyToHero2.drawOnPress(); moveArmyToHero1.drawOnRelease(); } else if ( le.MousePressLeft( moveArmyToHero1.area() ) || HotKeyHoldEvent( Game::HotKeyEvent::DEFAULT_LEFT ) ) { moveArmyToHero1.drawOnPress(); moveArmyToHero2.drawOnRelease(); } else { moveArmyToHero1.drawOnRelease(); moveArmyToHero2.drawOnRelease(); } if ( le.MousePressLeft( moveArtifactsToHero2.area() ) ) { moveArtifactsToHero2.drawOnPress(); moveArtifactsToHero1.drawOnRelease(); } else if ( le.MousePressLeft( moveArtifactsToHero1.area() ) ) { moveArtifactsToHero1.drawOnPress(); moveArtifactsToHero2.drawOnRelease(); } else { moveArtifactsToHero1.drawOnRelease(); moveArtifactsToHero2.drawOnRelease(); } if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) break; // selector troops event if ( ( le.MouseCursor( selectArmy1.GetArea() ) && selectArmy1.QueueEventProcessing( selectArmy2 ) ) || ( le.MouseCursor( selectArmy2.GetArea() ) && selectArmy2.QueueEventProcessing( selectArmy1 ) ) ) { if ( selectArtifacts1.isSelected() ) selectArtifacts1.ResetSelected(); else if ( selectArtifacts2.isSelected() ) selectArtifacts2.ResetSelected(); armyCountBackgroundRestorerLeft.restore(); armyCountBackgroundRestorerRight.restore(); selectArmy1.Redraw( display ); selectArmy2.Redraw( display ); moraleIndicator1.Redraw(); moraleIndicator2.Redraw(); luckIndicator1.Redraw(); luckIndicator2.Redraw(); display.render(); } // selector artifacts event if ( ( le.MouseCursor( selectArtifacts1.GetArea() ) && selectArtifacts1.QueueEventProcessing( selectArtifacts2 ) ) || ( le.MouseCursor( selectArtifacts2.GetArea() ) && selectArtifacts2.QueueEventProcessing( selectArtifacts1 ) ) ) { if ( selectArmy1.isSelected() ) selectArmy1.ResetSelected(); else if ( selectArmy2.isSelected() ) selectArmy2.ResetSelected(); std::set assembledArtifacts = bag_artifacts.assembleArtifactSetIfPossible(); std::set otherHeroAssembledArtifacts = otherHero.bag_artifacts.assembleArtifactSetIfPossible(); // MSVC 2017 fails to use the std::set<...>::merge( std::set<...> && ) overload here, so we have to use a temporary variable assembledArtifacts.merge( otherHeroAssembledArtifacts ); std::for_each( assembledArtifacts.begin(), assembledArtifacts.end(), Dialog::ArtifactSetAssembled ); selectArtifacts1.Redraw( display ); selectArtifacts2.Redraw( display ); backPrimary.restore(); fheroes2::RedrawPrimarySkillInfo( cur_pt, &primskill_bar1, &primskill_bar2 ); moraleIndicator1.Redraw(); moraleIndicator2.Redraw(); luckIndicator1.Redraw(); luckIndicator2.Redraw(); display.render(); } if ( ( le.MouseCursor( primskill_bar1.GetArea() ) && primskill_bar1.QueueEventProcessing() ) || ( le.MouseCursor( primskill_bar2.GetArea() ) && primskill_bar2.QueueEventProcessing() ) || ( le.MouseCursor( secskill_bar1.GetArea() ) && secskill_bar1.QueueEventProcessing() ) || ( le.MouseCursor( secskill_bar2.GetArea() ) && secskill_bar2.QueueEventProcessing() ) ) { display.render(); } if ( le.MouseCursor( moraleIndicator1.GetArea() ) ) { MoraleIndicator::QueueEventProcessing( moraleIndicator1 ); } else if ( le.MouseCursor( moraleIndicator2.GetArea() ) ) { MoraleIndicator::QueueEventProcessing( moraleIndicator2 ); } else if ( le.MouseCursor( luckIndicator1.GetArea() ) ) { LuckIndicator::QueueEventProcessing( luckIndicator1 ); } else if ( le.MouseCursor( luckIndicator2.GetArea() ) ) { LuckIndicator::QueueEventProcessing( luckIndicator2 ); } const bool isHero1LeftClicked = le.MouseClickLeft( hero1Area ); if ( isHero1LeftClicked || le.MouseClickLeft( hero2Area ) ) { // Currently we are calling hero dialog with borders from meeting dialog without borders // so the engine thinks that we are opening there was now window before to fade-out. // We also have to cache the display image to properly restore it after closing hero dialog. const fheroes2::Rect restorerRoi( cur_pt.x - 2 * BORDERWIDTH, cur_pt.y - BORDERWIDTH, src_rt.width + 3 * BORDERWIDTH, src_rt.height + 3 * BORDERWIDTH ); fheroes2::ImageRestorer dialogRestorer( display, restorerRoi.x, restorerRoi.y, restorerRoi.width, restorerRoi.height ); // If game display resolution is 640x480 then all fade effects are done in 'OpenHeroesDialog()' except fade-in after dialog close. if ( !isDefaultScreenSize ) { fheroes2::fadeOutDisplay( fadeRoi, true ); } Game::OpenHeroesDialog( isHero1LeftClicked ? *this : otherHero, false, true, true ); if ( !isDefaultScreenSize ) { dialogRestorer.restore(); } else { dialogRestorer.reset(); } armyCountBackgroundRestorerLeft.restore(); armyCountBackgroundRestorerRight.restore(); selectArtifacts1.ResetSelected(); selectArtifacts2.ResetSelected(); selectArtifacts1.Redraw( display ); selectArtifacts2.Redraw( display ); selectArmy1.ResetSelected(); selectArmy2.ResetSelected(); selectArmy1.Redraw( display ); selectArmy2.Redraw( display ); moraleIndicator1.Redraw(); moraleIndicator2.Redraw(); luckIndicator1.Redraw(); luckIndicator2.Redraw(); display.updateNextRenderRoi( restorerRoi ); fheroes2::fadeInDisplay( fadeRoi, !isDefaultScreenSize ); } else if ( le.MouseClickLeft( moveArmyToHero2.area() ) || HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_RIGHT ) ) { const ArmyTroop * keep = nullptr; if ( selectArmy1.isSelected() ) { keep = selectArmy1.GetSelectedItem(); } else if ( selectArmy2.isSelected() ) { keep = selectArmy2.GetSelectedItem(); } otherHero.GetArmy().MoveTroops( GetArmy(), keep ? keep->GetID() : Monster::UNKNOWN ); armyCountBackgroundRestorerLeft.restore(); armyCountBackgroundRestorerRight.restore(); selectArmy1.ResetSelected(); selectArmy2.ResetSelected(); selectArmy1.Redraw( display ); selectArmy2.Redraw( display ); moraleIndicator1.Redraw(); moraleIndicator2.Redraw(); display.render(); } else if ( le.MouseClickLeft( moveArmyToHero1.area() ) || HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_LEFT ) ) { const ArmyTroop * keep = nullptr; if ( selectArmy1.isSelected() ) { keep = selectArmy1.GetSelectedItem(); } else if ( selectArmy2.isSelected() ) { keep = selectArmy2.GetSelectedItem(); } GetArmy().MoveTroops( otherHero.GetArmy(), keep ? keep->GetID() : Monster::UNKNOWN ); armyCountBackgroundRestorerLeft.restore(); armyCountBackgroundRestorerRight.restore(); selectArmy1.ResetSelected(); selectArmy2.ResetSelected(); selectArmy1.Redraw( display ); selectArmy2.Redraw( display ); moraleIndicator1.Redraw(); moraleIndicator2.Redraw(); display.render(); } else if ( le.MouseClickLeft( moveArtifactsToHero2.area() ) ) { moveArtifacts( GetBagArtifacts(), otherHero.GetBagArtifacts() ); selectArtifacts1.ResetSelected(); selectArtifacts2.ResetSelected(); selectArtifacts1.Redraw( display ); selectArtifacts2.Redraw( display ); backPrimary.restore(); fheroes2::RedrawPrimarySkillInfo( cur_pt, &primskill_bar1, &primskill_bar2 ); moraleIndicator1.Redraw(); moraleIndicator2.Redraw(); luckIndicator1.Redraw(); luckIndicator2.Redraw(); display.render(); } else if ( le.MouseClickLeft( moveArtifactsToHero1.area() ) ) { moveArtifacts( otherHero.GetBagArtifacts(), GetBagArtifacts() ); selectArtifacts1.ResetSelected(); selectArtifacts2.ResetSelected(); selectArtifacts1.Redraw( display ); selectArtifacts2.Redraw( display ); backPrimary.restore(); fheroes2::RedrawPrimarySkillInfo( cur_pt, &primskill_bar1, &primskill_bar2 ); moraleIndicator1.Redraw(); moraleIndicator2.Redraw(); luckIndicator1.Redraw(); luckIndicator2.Redraw(); display.render(); } if ( le.MousePressRight( hero1Area ) ) { Dialog::QuickInfo( *this ); } else if ( le.MousePressRight( hero2Area ) ) { Dialog::QuickInfo( otherHero ); } } selectArmy1.ResetSelected(); selectArmy2.ResetSelected(); selectArtifacts1.ResetSelected(); selectArtifacts2.ResetSelected(); backPrimary.reset(); armyCountBackgroundRestorerLeft.reset(); armyCountBackgroundRestorerRight.reset(); // Fade-out heroes meeting dialog. fheroes2::fadeOutDisplay( fadeRoi, !isDefaultScreenSize ); restorer.restore(); // If the scout area bonus is increased with the new artifact we reveal the fog and update the radar. if ( hero1ScoutAreaBonus < bag_artifacts.getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::AREA_REVEAL_DISTANCE ) ) { Scout( GetIndex() ); ScoutRadar(); } if ( hero2ScoutAreaBonus < otherHero.GetBagArtifacts().getTotalArtifactEffectValue( fheroes2::ArtifactBonusType::AREA_REVEAL_DISTANCE ) ) { otherHero.Scout( otherHero.GetIndex() ); otherHero.ScoutRadar(); } // Set fade-in game screen only for 640x480 resolution. if ( isDefaultScreenSize ) { Game::setDisplayFadeIn(); } else { // Heroes meeting dialog currently does not have borders so its ROI is the same as fade ROI. display.render( fadeRoi ); } } fheroes2-1.0.12+dfsg/src/fheroes2/heroes/heroes_move.cpp000066400000000000000000000500461456075706000230560ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include "army.h" #include "audio_manager.h" #include "castle.h" #include "direction.h" #include "game_delays.h" #include "game_interface.h" #include "ground.h" #include "heroes.h" #include "interface_base.h" #include "interface_gamearea.h" #include "kingdom.h" #include "localevent.h" #include "m82.h" #include "maps.h" #include "maps_tiles.h" #include "math_base.h" #include "mp2.h" #include "route.h" #include "screen.h" #include "settings.h" #include "world.h" namespace { const int32_t heroMoveStep{ 4 }; // in pixels void playHeroWalkingSound( const int groundType ) { const int heroMovementSpeed = Settings::Get().HeroesMoveSpeed(); int speed = 1; // Speed 10 is actually Jump so essentially we have speed range from 1 to 9. if ( heroMovementSpeed >= 7 ) { speed = 3; } else if ( heroMovementSpeed >= 4 ) { speed = 2; } int soundId = M82::UNKNOWN; switch ( groundType ) { case Maps::Ground::WATER: if ( speed == 1 ) { soundId = M82::WSND00; } else if ( speed == 2 ) { soundId = M82::WSND10; } else { soundId = M82::WSND20; } break; case Maps::Ground::GRASS: if ( speed == 1 ) { soundId = M82::WSND01; } else if ( speed == 2 ) { soundId = M82::WSND11; } else { soundId = M82::WSND21; } break; case Maps::Ground::WASTELAND: if ( speed == 1 ) { soundId = M82::WSND02; } else if ( speed == 2 ) { soundId = M82::WSND12; } else { soundId = M82::WSND22; } break; case Maps::Ground::SWAMP: case Maps::Ground::BEACH: if ( speed == 1 ) { soundId = M82::WSND03; } else if ( speed == 2 ) { soundId = M82::WSND13; } else { soundId = M82::WSND23; } break; case Maps::Ground::LAVA: if ( speed == 1 ) { soundId = M82::WSND04; } else if ( speed == 2 ) { soundId = M82::WSND14; } else { soundId = M82::WSND24; } break; case Maps::Ground::DESERT: case Maps::Ground::SNOW: if ( speed == 1 ) { soundId = M82::WSND05; } else if ( speed == 2 ) { soundId = M82::WSND15; } else { soundId = M82::WSND25; } break; case Maps::Ground::DIRT: if ( speed == 1 ) { soundId = M82::WSND06; } else if ( speed == 2 ) { soundId = M82::WSND16; } else { soundId = M82::WSND26; } break; default: // Did you add a new terrain type? Add the corresponding logic above! assert( 0 ); return; } assert( soundId != M82::UNKNOWN ); AudioManager::PlaySoundAsync( soundId ); } bool isNeedStayFrontObject( const Heroes & hero, const Maps::Tiles & next ) { if ( next.GetObject() == MP2::OBJ_CASTLE ) { const Castle * castle = world.getCastleEntrance( next.GetCenter() ); if ( castle == nullptr ) { return false; } // If this is an allied castle, then we shouldn't be here at all assert( hero.GetColor() == castle->GetColor() || !hero.isFriends( castle->GetColor() ) ); return !hero.isFriends( castle->GetColor() ) && castle->GetActualArmy().isValid(); } if ( hero.isShipMaster() && next.GetObject() == MP2::OBJ_COAST ) { return true; } if ( !hero.isShipMaster() && next.GetObject() == MP2::OBJ_SHIPWRECK ) { return true; } return MP2::isNeedStayFront( next.GetObject() ); } } bool Heroes::isInVisibleMapArea() const { // TODO: this is not entirely correct. Consider a hero being outside the visible area but his shadow is still inside. The visible tile ROI should be extended by // TODO: at least 1 tile in each direction. return Interface::AdventureMap::Get().getGameArea().GetVisibleTileROI() & GetCenter(); } bool Heroes::isInDeepOcean() const { // Maximum number of hero's steps per cell is 9 so we check if the hero moved more than half of them. const bool isHeroMovedHalfOfCell = ( sprite_index < 45 && ( sprite_index % heroFrameCountPerTile ) > 4 ); const int32_t tileIndex = ( isHeroMovedHalfOfCell && Maps::isValidDirection( GetIndex(), direction ) ) ? Maps::GetDirectionIndex( GetIndex(), direction ) : GetIndex(); for ( const int32_t nearbyIndex : Maps::getAroundIndexes( tileIndex ) ) { if ( !world.GetTiles( nearbyIndex ).isWater() ) { return false; } } return true; } bool Heroes::MoveStep( const bool jumpToNextTile ) { const int32_t heroIndex = GetIndex(); const int32_t nextStepIndex = Maps::GetDirectionIndex( heroIndex, path.GetFrontDirection() ); const auto makeStep = [this, nextStepIndex]( const bool performMovement ) { ApplyPenaltyMovement( path.GetFrontPenalty() ); if ( !performMovement ) { // If we are accessing an object located on a tile that we cannot step on, then this should be the last step of the path assert( nextStepIndex == path.GetDestinationIndex() ); path.PopFront(); assert( path.empty() ); Action( nextStepIndex ); return; } Move2Dest( nextStepIndex ); if ( isControlHuman() ) { // Update the radar map image in the area that is visible to the hero after his movement. ScoutRadar(); } ActionNewPosition( true ); path.PopFront(); // It is possible that the hero in the new position will be attacked and lose the battle before he can perform the action if ( isActive() ) { Action( nextStepIndex ); } }; if ( jumpToNextTile ) { if ( isNeedStayFrontObject( *this, world.GetTiles( nextStepIndex ) ) ) { makeStep( false ); } else { // Unveil fog before moving the hero. Scout( nextStepIndex ); makeStep( true ); } return true; } const int currentHeroFrameIndex = ( sprite_index % heroFrameCountPerTile ); if ( currentHeroFrameIndex == 0 ) { if ( isNeedStayFrontObject( *this, world.GetTiles( nextStepIndex ) ) ) { makeStep( false ); return true; } if ( GetKingdom().isControlHuman() ) { const fheroes2::Point & mp = GetCenter(); playHeroWalkingSound( world.GetTiles( mp.x, mp.y ).GetGround() ); } } else if ( currentHeroFrameIndex == 1 ) { // This is a start of hero's movement. We should clear fog around him. Scout( nextStepIndex ); } else if ( currentHeroFrameIndex == 8 ) { sprite_index -= 8; makeStep( true ); // if we continue to move into the same direction we must skip first frame as it's for stand position only if ( isMoveEnabled() && GetDirection() == path.GetFrontDirection() && !isNeedStayFrontObject( *this, world.GetTiles( path.GetFrontIndex() ) ) ) { if ( GetKingdom().isControlHuman() ) { playHeroWalkingSound( world.GetTiles( heroIndex ).GetGround() ); } ++sprite_index; } return true; } ++sprite_index; return false; } void Heroes::AngleStep( int to_direct ) { bool clockwise = Direction::ShortDistanceClockWise( direction, to_direct ); // start index if ( 45 > sprite_index && ( sprite_index % heroFrameCountPerTile ) == 0 ) { switch ( direction ) { case Direction::TOP: sprite_index = 45; break; case Direction::TOP_RIGHT: sprite_index = clockwise ? 47 : 46; break; case Direction::TOP_LEFT: sprite_index = clockwise ? 46 : 47; break; case Direction::RIGHT: sprite_index = clockwise ? 49 : 48; break; case Direction::LEFT: sprite_index = clockwise ? 48 : 49; break; case Direction::BOTTOM_RIGHT: sprite_index = clockwise ? 51 : 50; break; case Direction::BOTTOM_LEFT: sprite_index = clockwise ? 50 : 51; break; case Direction::BOTTOM: sprite_index = clockwise ? 52 : 53; break; default: break; } } // animation process else { switch ( direction ) { case Direction::TOP_RIGHT: case Direction::RIGHT: case Direction::BOTTOM_RIGHT: clockwise ? ++sprite_index : --sprite_index; break; case Direction::TOP: ++sprite_index; break; case Direction::TOP_LEFT: case Direction::LEFT: case Direction::BOTTOM_LEFT: clockwise ? --sprite_index : ++sprite_index; break; case Direction::BOTTOM: --sprite_index; break; default: break; } bool end = false; int next = Direction::UNKNOWN; switch ( direction ) { case Direction::TOP: next = clockwise ? Direction::TOP_RIGHT : Direction::TOP_LEFT; break; case Direction::TOP_RIGHT: next = clockwise ? Direction::RIGHT : Direction::TOP; break; case Direction::TOP_LEFT: next = clockwise ? Direction::TOP : Direction::LEFT; break; case Direction::RIGHT: next = clockwise ? Direction::BOTTOM_RIGHT : Direction::TOP_RIGHT; break; case Direction::LEFT: next = clockwise ? Direction::TOP_LEFT : Direction::BOTTOM_LEFT; break; case Direction::BOTTOM_RIGHT: next = clockwise ? Direction::BOTTOM : Direction::RIGHT; break; case Direction::BOTTOM_LEFT: next = clockwise ? Direction::LEFT : Direction::BOTTOM; break; case Direction::BOTTOM: next = clockwise ? Direction::BOTTOM_LEFT : Direction::BOTTOM_RIGHT; break; default: break; } switch ( next ) { case Direction::TOP: end = ( sprite_index == 44 ); break; case Direction::TOP_RIGHT: end = ( sprite_index == ( clockwise ? 47 : 46 ) ); break; case Direction::TOP_LEFT: end = ( sprite_index == ( clockwise ? 46 : 47 ) ); break; case Direction::RIGHT: end = ( sprite_index == ( clockwise ? 49 : 48 ) ); break; case Direction::LEFT: end = ( sprite_index == ( clockwise ? 48 : 49 ) ); break; case Direction::BOTTOM_RIGHT: end = ( sprite_index == ( clockwise ? 51 : 50 ) ); break; case Direction::BOTTOM_LEFT: end = ( sprite_index == ( clockwise ? 50 : 51 ) ); break; case Direction::BOTTOM: end = ( sprite_index == 53 ); break; default: break; } if ( end ) { switch ( next ) { case Direction::TOP: sprite_index = 0; break; case Direction::BOTTOM: sprite_index = 36; break; case Direction::TOP_RIGHT: case Direction::TOP_LEFT: sprite_index = 9; break; case Direction::BOTTOM_RIGHT: case Direction::BOTTOM_LEFT: sprite_index = 27; break; case Direction::RIGHT: case Direction::LEFT: sprite_index = 18; break; default: break; } direction = next; } } } fheroes2::Point Heroes::getCurrentPixelOffset() const { if ( sprite_index >= 45 ) { return {}; } int frame = ( sprite_index % heroFrameCountPerTile ); if ( frame > 0 ) --frame; if ( frame == 0 ) { return _offset; } fheroes2::Point realOffset{ _offset }; if ( direction & DIRECTION_LEFT_COL ) { realOffset.x -= heroMoveStep * frame; } else if ( direction & DIRECTION_RIGHT_COL ) { realOffset.x += heroMoveStep * frame; } if ( direction & DIRECTION_TOP_ROW ) { realOffset.y -= heroMoveStep * frame; } else if ( direction & DIRECTION_BOTTOM_ROW ) { realOffset.y += heroMoveStep * frame; } return realOffset; } void Heroes::FadeOut( const int animSpeedMultiplier, const fheroes2::Point & offset /* = fheroes2::Point() */ ) const { assert( animSpeedMultiplier > 0 ); if ( !isInVisibleMapArea() ) { return; } Interface::AdventureMap & iface = Interface::AdventureMap::Get(); Interface::GameArea & gamearea = iface.getGameArea(); fheroes2::Display & display = fheroes2::Display::instance(); LocalEvent & le = LocalEvent::Get(); _alphaValue = 255; while ( le.HandleEvents( Game::isDelayNeeded( { Game::HEROES_FADE_DELAY } ) ) && _alphaValue > 0 ) { if ( !Game::validateAnimationDelay( Game::HEROES_FADE_DELAY ) ) { continue; } if ( offset.x != 0 || offset.y != 0 ) { gamearea.ShiftCenter( offset ); } _alphaValue = std::max( 0, _alphaValue - 8 * animSpeedMultiplier ); iface.redraw( Interface::REDRAW_GAMEAREA ); display.render(); } _alphaValue = 255; } void Heroes::FadeIn( const int animSpeedMultiplier, const fheroes2::Point & offset /* = fheroes2::Point() */ ) const { assert( animSpeedMultiplier > 0 ); if ( !isInVisibleMapArea() ) { return; } Interface::AdventureMap & iface = Interface::AdventureMap::Get(); Interface::GameArea & gamearea = iface.getGameArea(); fheroes2::Display & display = fheroes2::Display::instance(); LocalEvent & le = LocalEvent::Get(); _alphaValue = 0; while ( le.HandleEvents( Game::isDelayNeeded( { Game::HEROES_FADE_DELAY } ) ) && _alphaValue < 255 ) { if ( !Game::validateAnimationDelay( Game::HEROES_FADE_DELAY ) ) { continue; } if ( offset.x != 0 || offset.y != 0 ) { gamearea.ShiftCenter( offset ); } _alphaValue = std::min( _alphaValue + 8 * animSpeedMultiplier, 255 ); iface.redraw( Interface::REDRAW_GAMEAREA ); display.render(); } _alphaValue = 255; } bool Heroes::Move( const bool jumpToNextTile /* = false */ ) { ResetModes( ACTION ); if ( path.isValidForMovement() && ( isMoveEnabled() || ( GetSpriteIndex() < 45 && ( GetSpriteIndex() % heroFrameCountPerTile ) > 0 ) || GetSpriteIndex() >= 45 ) ) { // Jump to the next position. if ( jumpToNextTile ) { direction = path.GetFrontDirection(); MoveStep( jumpToNextTile ); // TODO: why don't we check isActive() like it is done for a normal movement? return true; } // The hero is changing the direction of movement. const int frontDirection = path.GetFrontDirection(); if ( GetDirection() != frontDirection ) { AngleStep( frontDirection ); } else { // Set valid direction sprite in case of AI hero appearing from fog. SetValidDirectionSprite(); if ( MoveStep( jumpToNextTile ) ) { return isActive(); } } } else { SetMove( false ); } return false; } fheroes2::Point Heroes::MovementDirection() const { const int32_t from = GetIndex(); if ( from == -1 ) { return {}; } const int32_t to = Maps::GetDirectionIndex( from, path.GetFrontDirection() ); if ( to == -1 ) { return {}; } if ( direction == Direction::TOP ) { if ( sprite_index > 1 && sprite_index < 9 ) { return { 0, -1 }; } } else if ( direction == Direction::TOP_RIGHT || direction == Direction::TOP_LEFT ) { if ( sprite_index > 9 + 1 && sprite_index < 18 ) { return { direction == Direction::TOP_RIGHT ? 1 : -1, -1 }; } } else if ( direction == Direction::RIGHT || direction == Direction::LEFT ) { if ( sprite_index > 18 + 1 && sprite_index < 27 ) { return { direction == Direction::RIGHT ? 1 : -1, 0 }; } } else if ( direction == Direction::BOTTOM_RIGHT || direction == Direction::BOTTOM_LEFT ) { if ( sprite_index > 27 + 1 && sprite_index < 36 ) { return { direction == Direction::BOTTOM_RIGHT ? 1 : -1, 1 }; } } else if ( direction == Direction::BOTTOM ) { if ( sprite_index > 36 + 1 && sprite_index < 45 ) { return { 0, 1 }; } } return {}; } void Heroes::SetValidDirectionSprite() { const int32_t from = GetIndex(); const int32_t to = Maps::GetDirectionIndex( from, path.GetFrontDirection() ); if ( from == -1 || to == -1 ) return; if ( direction == Direction::TOP ) { if ( sprite_index < 0 || sprite_index >= 9 ) { sprite_index = 0; } } else if ( direction == Direction::TOP_RIGHT || direction == Direction::TOP_LEFT ) { if ( sprite_index < 9 || sprite_index >= 18 ) { sprite_index = 9; } } else if ( direction == Direction::RIGHT || direction == Direction::LEFT ) { if ( sprite_index < 18 || sprite_index >= 27 ) { sprite_index = 18; } } else if ( direction == Direction::BOTTOM_RIGHT || direction == Direction::BOTTOM_LEFT ) { if ( sprite_index < 27 || sprite_index >= 36 ) { sprite_index = 27; } } else if ( direction == Direction::BOTTOM ) { if ( sprite_index < 36 || sprite_index >= 45 ) { sprite_index = 36; } } } fheroes2-1.0.12+dfsg/src/fheroes2/heroes/heroes_recruits.cpp000066400000000000000000000077401456075706000237530ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "heroes_recruits.h" #include #include "game_io.h" #include "heroes.h" #include "save_format_version.h" #include "serialize.h" #include "world.h" Recruit::Recruit() : _id( Heroes::UNKNOWN ) , _surrenderDay( 0 ) {} Recruit::Recruit( const Heroes & hero, const uint32_t surrenderDay ) : _id( hero.GetID() ) , _surrenderDay( surrenderDay ) {} Recruit::Recruit( const Heroes & hero ) : Recruit( hero, 0 ) {} Recruits::Recruits() : std::pair() {} void Recruits::Reset() { first = {}; second = {}; } int Recruits::GetID1() const { return first.getID(); } int Recruits::GetID2() const { return second.getID(); } Heroes * Recruits::GetHero1() const { return world.GetHeroes( first.getID() ); } Heroes * Recruits::GetHero2() const { return world.GetHeroes( second.getID() ); } uint32_t Recruits::getSurrenderDayOfHero1() const { return first.getSurrenderDay(); } uint32_t Recruits::getSurrenderDayOfHero2() const { return second.getSurrenderDay(); } void Recruits::SetHero1( Heroes * hero ) { if ( hero ) { hero->SetModes( Heroes::RECRUIT ); first = Recruit( *hero ); } else { first = {}; } } void Recruits::SetHero2( Heroes * hero ) { if ( hero ) { hero->SetModes( Heroes::RECRUIT ); second = Recruit( *hero ); } else { second = {}; } } void Recruits::appendSurrenderedHero( Heroes & hero, const uint32_t heroSurrenderDay ) { assert( heroSurrenderDay > 0 ); hero.SetModes( Heroes::RECRUIT ); // The original game offers to hire only the hero who retreated or surrendered last. fheroes2 offers to hire up to the last two heroes of this kind. Recruit & recruit = ( first.getSurrenderDay() > second.getSurrenderDay() ? second : first ); recruit = Recruit( hero, heroSurrenderDay ); } StreamBase & operator<<( StreamBase & msg, const Recruit & recruit ) { return msg << recruit._id << recruit._surrenderDay; } StreamBase & operator>>( StreamBase & msg, Recruit & recruit ) { msg >> recruit._id >> recruit._surrenderDay; static_assert( LAST_SUPPORTED_FORMAT_VERSION < FORMAT_VERSION_1010_RELEASE, "Remove the logic below." ); if ( Game::GetVersionOfCurrentSaveFile() < FORMAT_VERSION_1010_RELEASE ) { ++recruit._id; } return msg; } fheroes2-1.0.12+dfsg/src/fheroes2/heroes/heroes_recruits.h000066400000000000000000000060101456075706000234050ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2RECRUITS_H #define H2RECRUITS_H #include #include class Heroes; class StreamBase; class Recruit { public: Recruit(); Recruit( const Heroes & hero, const uint32_t surrenderDay ); explicit Recruit( const Heroes & hero ); Recruit( const Recruit & ) = delete; ~Recruit() = default; Recruit & operator=( const Recruit & ) = delete; Recruit & operator=( Recruit && ) = default; int getID() const { return _id; } uint32_t getSurrenderDay() const { return _surrenderDay; } private: int _id; uint32_t _surrenderDay; friend StreamBase & operator<<( StreamBase & msg, const Recruit & recruit ); friend StreamBase & operator>>( StreamBase & msg, Recruit & recruit ); }; class Recruits : public std::pair { public: Recruits(); void Reset(); int GetID1() const; int GetID2() const; Heroes * GetHero1() const; Heroes * GetHero2() const; uint32_t getSurrenderDayOfHero1() const; uint32_t getSurrenderDayOfHero2() const; void SetHero1( Heroes * hero ); void SetHero2( Heroes * hero ); void appendSurrenderedHero( Heroes & hero, const uint32_t heroSurrenderDay ); }; StreamBase & operator<<( StreamBase & msg, const Recruit & recruit ); StreamBase & operator>>( StreamBase & msg, Recruit & recruit ); #endif fheroes2-1.0.12+dfsg/src/fheroes2/heroes/heroes_spell.cpp000066400000000000000000000341671456075706000232350ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include "army.h" #include "army_troop.h" #include "audio_manager.h" #include "castle.h" #include "color.h" #include "dialog.h" #include "dialog_selectitems.h" #include "direction.h" #include "game_interface.h" #include "heroes.h" #include "interface_base.h" #include "interface_gamearea.h" #include "interface_radar.h" #include "kingdom.h" #include "logging.h" #include "m82.h" #include "maps.h" #include "maps_tiles.h" #include "maps_tiles_helper.h" #include "math_base.h" #include "monster.h" #include "mp2.h" #include "resource.h" #include "route.h" #include "settings.h" #include "spell.h" #include "spell_info.h" #include "tools.h" #include "translations.h" #include "ui_dialog.h" #include "view_world.h" #include "world.h" namespace { void HeroesTownGate( Heroes & hero, const Castle * castle ) { assert( castle && castle->GetHero() == nullptr ); Interface::AdventureMap & I = Interface::AdventureMap::Get(); const fheroes2::Point fromPosition = hero.GetCenter(); // Position of Hero on radar before casting the spell to clear it after casting. const fheroes2::Rect fromRoi( fromPosition.x, fromPosition.y, 1, 1 ); // Before casting the spell, make sure that the game area is centered on the hero I.getGameArea().SetCenter( fromPosition ); I.redraw( Interface::REDRAW_GAMEAREA | Interface::REDRAW_RADAR_CURSOR ); const int32_t dst = castle->GetIndex(); assert( Maps::isValidAbsIndex( dst ) ); AudioManager::PlaySound( M82::KILLFADE ); hero.ShowPath( false ); hero.FadeOut(); hero.Scout( dst ); hero.Move2Dest( dst ); hero.GetPath().Reset(); // Clear previous hero position on radar. I.getRadar().SetRenderArea( fromRoi ); I.redraw( Interface::REDRAW_RADAR ); I.getGameArea().SetCenter( hero.GetCenter() ); // Update radar image in scout area around Hero after teleport. I.getRadar().SetRenderArea( hero.GetScoutRoi() ); I.setRedraw( Interface::REDRAW_GAMEAREA | Interface::REDRAW_RADAR ); AudioManager::PlaySound( M82::KILLFADE ); hero.FadeIn(); hero.ShowPath( true ); castle->MageGuildEducateHero( hero ); } bool ActionSpellViewMines() { ViewWorld::ViewWorldWindow( Settings::Get().CurrentColor(), ViewWorldMode::ViewMines, Interface::AdventureMap::Get() ); return true; } bool ActionSpellViewResources() { ViewWorld::ViewWorldWindow( Settings::Get().CurrentColor(), ViewWorldMode::ViewResources, Interface::AdventureMap::Get() ); return true; } bool ActionSpellViewArtifacts() { ViewWorld::ViewWorldWindow( Settings::Get().CurrentColor(), ViewWorldMode::ViewArtifacts, Interface::AdventureMap::Get() ); return true; } bool ActionSpellViewTowns() { ViewWorld::ViewWorldWindow( Settings::Get().CurrentColor(), ViewWorldMode::ViewTowns, Interface::AdventureMap::Get() ); return true; } bool ActionSpellViewHeroes() { ViewWorld::ViewWorldWindow( Settings::Get().CurrentColor(), ViewWorldMode::ViewHeroes, Interface::AdventureMap::Get() ); return true; } bool ActionSpellViewAll() { ViewWorld::ViewWorldWindow( Settings::Get().CurrentColor(), ViewWorldMode::ViewAll, Interface::AdventureMap::Get() ); return true; } bool ActionSpellIdentifyHero( const Heroes & hero ) { assert( !hero.GetKingdom().Modes( Kingdom::IDENTIFYHERO ) ); hero.GetKingdom().SetModes( Kingdom::IDENTIFYHERO ); fheroes2::showStandardTextMessage( _( "Identify Hero" ), _( "Enemy heroes are now fully identifiable." ), Dialog::OK ); return true; } bool ActionSpellSummonBoat( const Heroes & hero ) { assert( !hero.isShipMaster() ); const int32_t boatDestination = fheroes2::getPossibleBoatPosition( hero ); assert( Maps::isValidAbsIndex( boatDestination ) ); const int32_t boatSource = fheroes2::getSummonableBoat( hero ); // Player should have a summonable boat before calling this function. assert( Maps::isValidAbsIndex( boatSource ) ); const int heroColor = hero.GetColor(); Interface::GameArea & gameArea = Interface::AdventureMap::Get().getGameArea(); Maps::Tiles & tileSource = world.GetTiles( boatSource ); gameArea.runSingleObjectAnimation( std::make_shared( tileSource.GetObjectUID(), boatSource, MP2::OBJ_BOAT ) ); Maps::Tiles & tileDest = world.GetTiles( boatDestination ); tileDest.setBoat( Direction::RIGHT, heroColor ); tileSource.resetBoatOwnerColor(); gameArea.runSingleObjectAnimation( std::make_shared( tileDest.GetObjectUID(), boatDestination, MP2::OBJ_BOAT ) ); return true; } bool ActionSpellDimensionDoor( Heroes & hero ) { Interface::AdventureMap & I = Interface::AdventureMap::Get(); const fheroes2::Point fromPosition = hero.GetCenter(); // Position of Hero on radar before casting the spell to clear it after casting. const fheroes2::Rect fromRoi( fromPosition.x, fromPosition.y, 1, 1 ); // Before casting the spell, make sure that the game area is centered on the hero I.getGameArea().SetCenter( hero.GetCenter() ); I.redraw( Interface::REDRAW_GAMEAREA | Interface::REDRAW_RADAR_CURSOR ); const int32_t src = hero.GetIndex(); assert( Maps::isValidAbsIndex( src ) ); const int32_t dst = I.GetDimensionDoorDestination( src, Spell::CalculateDimensionDoorDistance(), hero.isShipMaster() ); if ( !Maps::isValidAbsIndex( dst ) ) { return false; } AudioManager::PlaySound( M82::KILLFADE ); hero.ShowPath( false ); hero.FadeOut(); hero.Scout( dst ); hero.Move2Dest( dst ); hero.SpellCasted( Spell::DIMENSIONDOOR ); hero.GetPath().Reset(); // Clear previous hero position on radar. I.getRadar().SetRenderArea( fromRoi ); I.redraw( Interface::REDRAW_RADAR ); I.getGameArea().SetCenter( hero.GetCenter() ); // Update radar image in scout area around Hero after teleport. I.getRadar().SetRenderArea( hero.GetScoutRoi() ); I.setRedraw( Interface::REDRAW_GAMEAREA | Interface::REDRAW_RADAR ); AudioManager::PlaySound( M82::KILLFADE ); hero.FadeIn(); hero.ShowPath( true ); hero.ActionNewPosition( false ); // SpellCasted() has already been called, we should not call it once again return false; } bool ActionSpellTownGate( Heroes & hero ) { assert( !hero.isShipMaster() ); const Castle * castle = fheroes2::getNearestCastleTownGate( hero ); if ( !castle ) { // A hero must be able to have a destination castle. Something is wrong with the logic! assert( 0 ); return false; } if ( castle->GetHero() ) { // The nearest town occupation must be checked before casting this spell. Something is wrong with the logic! assert( 0 ); return false; } HeroesTownGate( hero, castle ); return true; } bool ActionSpellTownPortal( Heroes & hero ) { assert( !hero.isShipMaster() ); const int32_t townPositionIndex = Dialog::selectKingdomCastle( hero.GetKingdom(), true, _( "Town Portal" ), _( "Select town to port to." ) ); if ( townPositionIndex == -1 ) { return false; } HeroesTownGate( hero, world.getCastleEntrance( Maps::GetPoint( townPositionIndex ) ) ); return true; } bool ActionSpellVisions( Heroes & hero ) { const MapsIndexes monsters = Maps::getVisibleMonstersAroundHero( hero ); assert( !monsters.empty() ); for ( const int32_t monsterIndex : monsters ) { const Maps::Tiles & tile = world.GetTiles( monsterIndex ); const Troop troop = getTroopFromTile( tile ); const NeutralMonsterJoiningCondition join = Army::GetJoinSolution( hero, tile, troop ); std::string hdr; std::string msg; hdr = std::string( "%{count} " ) + troop.GetPluralName( join.monsterCount ); StringReplace( hdr, "%{count}", troop.GetCount() ); switch ( join.reason ) { case NeutralMonsterJoiningCondition::Reason::Free: case NeutralMonsterJoiningCondition::Reason::Alliance: msg = _( "The creatures are willing to join us!" ); break; case NeutralMonsterJoiningCondition::Reason::ForMoney: if ( join.monsterCount == troop.GetCount() ) msg = _( "All the creatures will join us..." ); else { msg = _n( "The creature will join us...", "%{count} of the creatures will join us...", join.monsterCount ); StringReplace( msg, "%{count}", join.monsterCount ); } msg += '\n'; msg.append( _( "\n for a fee of %{gold} gold." ) ); StringReplace( msg, "%{gold}", troop.GetTotalCost().gold ); break; case NeutralMonsterJoiningCondition::Reason::RunAway: case NeutralMonsterJoiningCondition::Reason::Bane: msg = _( "These weak creatures will surely flee before us." ); break; default: msg = _( "I fear these creatures are in the mood for a fight." ); break; } fheroes2::showStandardTextMessage( hdr, msg, Dialog::OK ); } hero.SetModes( Heroes::VISIONS ); return true; } bool ActionSpellSetGuardian( const Heroes & hero, const Spell & spell ) { Maps::Tiles & tile = world.GetTiles( hero.GetIndex() ); assert( MP2::OBJ_MINE == tile.GetObject( false ) ); const uint32_t count = fheroes2::getGuardianMonsterCount( spell, hero.GetPower(), &hero ); if ( count == 0 ) { return false; } Maps::setMineSpellOnTile( tile, spell.GetID() ); if ( spell == Spell::HAUNT ) { world.CaptureObject( tile.GetIndex(), Color::NONE ); tile.removeOwnershipFlag( MP2::OBJ_MINE ); // Update the color of haunted mine on radar. Interface::AdventureMap & I = Interface::AdventureMap::Get(); const fheroes2::Point heroPosition = hero.GetCenter(); I.getRadar().SetRenderArea( { heroPosition.x - 1, heroPosition.y - 1, 3, 2 } ); I.setRedraw( Interface::REDRAW_RADAR ); } world.GetCapturedObject( tile.GetIndex() ).GetTroop().Set( Monster( spell ), count ); return true; } } void Heroes::ActionSpellCast( const Spell & spell ) { assert( spell.isValid() && !spell.isCombat() && CanCastSpell( spell ) ); bool apply = false; switch ( spell.GetID() ) { case Spell::VIEWMINES: apply = ActionSpellViewMines(); break; case Spell::VIEWRESOURCES: apply = ActionSpellViewResources(); break; case Spell::VIEWARTIFACTS: apply = ActionSpellViewArtifacts(); break; case Spell::VIEWTOWNS: apply = ActionSpellViewTowns(); break; case Spell::VIEWHEROES: apply = ActionSpellViewHeroes(); break; case Spell::VIEWALL: apply = ActionSpellViewAll(); break; case Spell::IDENTIFYHERO: apply = ActionSpellIdentifyHero( *this ); break; case Spell::SUMMONBOAT: apply = ActionSpellSummonBoat( *this ); break; case Spell::DIMENSIONDOOR: apply = ActionSpellDimensionDoor( *this ); break; case Spell::TOWNGATE: apply = ActionSpellTownGate( *this ); break; case Spell::TOWNPORTAL: apply = ActionSpellTownPortal( *this ); break; case Spell::VISIONS: apply = ActionSpellVisions( *this ); break; case Spell::HAUNT: case Spell::SETEGUARDIAN: case Spell::SETAGUARDIAN: case Spell::SETFGUARDIAN: case Spell::SETWGUARDIAN: apply = ActionSpellSetGuardian( *this, spell ); break; default: break; } if ( !apply ) { return; } DEBUG_LOG( DBG_GAME, DBG_INFO, GetName() << " cast spell: " << spell.GetName() ) SpellCasted( spell ); } fheroes2-1.0.12+dfsg/src/fheroes2/heroes/route.cpp000066400000000000000000000251631456075706000217030ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "route.h" #include #include #include #include "game_io.h" #include "heroes.h" #include "maps.h" #include "maps_tiles.h" #include "mp2.h" #include "save_format_version.h" #include "serialize.h" #include "world.h" Route::Path::Path( const Heroes & hero ) : _hero( &hero ) , _hide( true ) {} bool Route::Path::isValidForMovement() const { return !empty() && front().GetDirection() != Direction::UNKNOWN && Maps::isValidAbsIndex( front().GetIndex() ); } bool Route::Path::isValidForTeleportation() const { return !empty() && front().GetDirection() == Direction::UNKNOWN && Maps::isValidAbsIndex( front().GetIndex() ); } int Route::Path::GetIndexSprite( const int from, const int to, const int cost ) { // ICN::ROUTE // start index 1, 25, 49, 73, 97, 121 (path arrow size) int index = 1; switch ( cost ) { case 200: index = 121; break; case 175: index = 97; break; case 150: index = 73; break; case 125: index = 49; break; case 100: index = 25; break; default: break; } switch ( from ) { case Direction::TOP: switch ( to ) { case Direction::TOP: index += 8; break; case Direction::TOP_RIGHT: index += 17; break; case Direction::RIGHT: index += 18; break; case Direction::LEFT: index += 6; break; case Direction::TOP_LEFT: index += 7; break; case Direction::BOTTOM_LEFT: index += 5; break; case Direction::BOTTOM_RIGHT: index += 19; break; default: index = 0; break; } break; case Direction::TOP_RIGHT: switch ( to ) { case Direction::TOP: index += 0; break; case Direction::TOP_RIGHT: index += 9; break; case Direction::RIGHT: index += 18; break; case Direction::BOTTOM_RIGHT: index += 19; break; case Direction::TOP_LEFT: index += 7; break; case Direction::BOTTOM: index += 20; break; case Direction::LEFT: index += 6; break; default: index = 0; break; } break; case Direction::RIGHT: switch ( to ) { case Direction::TOP: index += 0; break; case Direction::BOTTOM: index += 20; break; case Direction::BOTTOM_RIGHT: index += 19; break; case Direction::RIGHT: index += 10; break; case Direction::TOP_RIGHT: index += 1; break; case Direction::TOP_LEFT: index += 7; break; case Direction::BOTTOM_LEFT: index += 21; break; default: index = 0; break; } break; case Direction::BOTTOM_RIGHT: switch ( to ) { case Direction::TOP_RIGHT: index += 1; break; case Direction::RIGHT: index += 2; break; case Direction::BOTTOM_RIGHT: index += 11; break; case Direction::BOTTOM: index += 20; break; case Direction::BOTTOM_LEFT: index += 21; break; case Direction::TOP: index += 0; break; case Direction::LEFT: index += 22; break; default: index = 0; break; } break; case Direction::BOTTOM: switch ( to ) { case Direction::RIGHT: index += 2; break; case Direction::BOTTOM_RIGHT: index += 3; break; case Direction::BOTTOM: index += 12; break; case Direction::BOTTOM_LEFT: index += 21; break; case Direction::LEFT: index += 22; break; case Direction::TOP_LEFT: index += 16; break; case Direction::TOP_RIGHT: index += 1; break; default: index = 0; break; } break; case Direction::BOTTOM_LEFT: switch ( to ) { case Direction::BOTTOM_RIGHT: index += 3; break; case Direction::BOTTOM: index += 4; break; case Direction::BOTTOM_LEFT: index += 13; break; case Direction::LEFT: index += 22; break; case Direction::TOP_LEFT: index += 23; break; case Direction::TOP: index += 16; break; case Direction::RIGHT: index += 2; break; default: index = 0; break; } break; case Direction::LEFT: switch ( to ) { case Direction::TOP: index += 16; break; case Direction::BOTTOM: index += 4; break; case Direction::BOTTOM_LEFT: index += 5; break; case Direction::LEFT: index += 14; break; case Direction::TOP_LEFT: index += 23; break; case Direction::TOP_RIGHT: index += 17; break; case Direction::BOTTOM_RIGHT: index += 3; break; default: index = 0; break; } break; case Direction::TOP_LEFT: switch ( to ) { case Direction::TOP: index += 16; break; case Direction::TOP_RIGHT: index += 17; break; case Direction::BOTTOM_LEFT: index += 5; break; case Direction::LEFT: index += 6; break; case Direction::TOP_LEFT: index += 15; break; case Direction::BOTTOM: index += 4; break; case Direction::RIGHT: index += 18; break; default: index = 0; break; } break; default: index = 0; break; } return index; } bool Route::Path::hasAllowedSteps() const { if ( !isValidForMovement() ) { return false; } assert( _hero != nullptr ); return _hero->GetMovePoints() >= front().GetPenalty(); } int Route::Path::GetAllowedSteps() const { assert( _hero != nullptr ); int green = 0; uint32_t movePoints = _hero->GetMovePoints(); for ( const_iterator it = begin(); it != end() && movePoints > 0; ++it ) { uint32_t penalty = it->GetPenalty(); if ( movePoints >= penalty ) { movePoints -= penalty; ++green; } else { movePoints = 0; } } return green; } std::string Route::Path::String() const { assert( _hero != nullptr ); const int32_t dstIndex = GetDestinationIndex(); std::string output( "from: " ); output += std::to_string( _hero->GetIndex() ); output += ", to: "; output += std::to_string( dstIndex ); output += ", obj: "; output += MP2::StringObject( Maps::isValidAbsIndex( dstIndex ) ? world.GetTiles( dstIndex ).GetObject() : MP2::OBJ_NONE ); output += ", dump: "; for ( const Step & step : *this ) { output += Direction::String( step.GetDirection() ); output += '('; output += std::to_string( step.GetPenalty() ); output += ')'; } output += "end"; return output; } StreamBase & Route::operator<<( StreamBase & msg, const Step & step ) { return msg << step.from << step.direction << step.penalty; } StreamBase & Route::operator<<( StreamBase & msg, const Path & path ) { return msg << path._hide << static_cast &>( path ); } StreamBase & Route::operator>>( StreamBase & msg, Step & step ) { msg >> step.from >> step.direction >> step.penalty; step.currentIndex = Maps::GetDirectionIndex( step.from, step.direction ); return msg; } StreamBase & Route::operator>>( StreamBase & msg, Path & path ) { std::list & base = path; static_assert( LAST_SUPPORTED_FORMAT_VERSION < FORMAT_VERSION_1007_RELEASE, "Remove the logic below." ); if ( Game::GetVersionOfCurrentSaveFile() < FORMAT_VERSION_1007_RELEASE ) { int32_t dummy; msg >> dummy; } return msg >> path._hide >> base; } uint32_t Route::calculatePathPenalty( const std::list & path ) { return std::accumulate( path.begin(), path.end(), static_cast( 0 ), []( const uint32_t total, const Step & step ) { return total + step.GetPenalty(); } ); } fheroes2-1.0.12+dfsg/src/fheroes2/heroes/route.h000066400000000000000000000147151456075706000213510ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2ROUTE_H #define H2ROUTE_H #include #include #include #include #include "direction.h" class Heroes; class StreamBase; namespace Route { class Step { public: Step() = default; Step( int32_t index, int32_t fromIndex, int dir, uint32_t cost ) : currentIndex( index ) , from( fromIndex ) , direction( dir ) , penalty( cost ) {} int32_t GetIndex() const { return currentIndex; } uint32_t GetPenalty() const { return penalty; } int32_t GetFrom() const { return from; } int GetDirection() const { return direction; } protected: friend StreamBase & operator<<( StreamBase &, const Step & ); friend StreamBase & operator>>( StreamBase &, Step & ); int32_t currentIndex = -1; int32_t from = -1; int direction = Direction::CENTER; uint32_t penalty = 0; }; class Path : public std::list { public: explicit Path( const Heroes & hero ); Path( const Path & ) = delete; Path & operator=( const Path & ) = delete; // Returns the index of the last step of the path. If the path is empty, then returns -1. int32_t GetDestinationIndex() const { return empty() ? -1 : back().GetIndex(); } // Returns the target index of the current step (the first in the queue). If the queue is empty, then returns -1. int32_t GetFrontIndex() const { return empty() ? -1 : front().GetIndex(); } // Returns the source index of the current step (the first in the queue). If the queue is empty, then returns -1. int32_t GetFrontFrom() const { return empty() ? -1 : front().GetFrom(); } // Returns the direction of the current step (the first in the queue). If the queue is empty, then returns // 'Direction::UNKNOWN'. int GetFrontDirection() const { return empty() ? Direction::UNKNOWN : front().GetDirection(); } // Returns the penalty of the current step (the first in the queue). If the queue is empty, then returns 0. uint32_t GetFrontPenalty() const { return empty() ? 0 : front().GetPenalty(); } // Returns the direction of the next step (the second in the queue). If the size of the queue is less than 2 elements, // then returns 'Direction::UNKNOWN'. int GetNextStepDirection() const { if ( size() < 2 ) { return Direction::UNKNOWN; } auto iter = cbegin(); return ( ++iter )->GetDirection(); } void setPath( const std::list & path ) { assign( path.begin(), path.end() ); } void Show() { _hide = false; } void Hide() { _hide = true; } void Reset() { clear(); } // Truncates the path after the current step (the first in the queue). If the queue is empty, then does nothing. void Truncate() { if ( empty() ) { return; } auto iter = cbegin(); erase( ++iter, cend() ); } void PopFront() { if ( empty() ) { return; } pop_front(); } // Returns true if this path is valid for normal movement on the map (the current step is performed to the tile // adjacent to the hero), otherwise returns false bool isValidForMovement() const; // Returns true if this path is valid to move through teleportation (the current step is performed NOT to the tile // adjacent to the hero, but to some distant valid tile), otherwise returns false bool isValidForTeleportation() const; bool isShow() const { return !_hide; } bool hasAllowedSteps() const; int GetAllowedSteps() const; std::string String() const; static int GetIndexSprite( const int from, const int to, const int cost ); private: friend StreamBase & operator<<( StreamBase &, const Path & ); friend StreamBase & operator>>( StreamBase &, Path & ); const Heroes * _hero; bool _hide; }; StreamBase & operator<<( StreamBase &, const Step & ); StreamBase & operator<<( StreamBase &, const Path & ); StreamBase & operator>>( StreamBase &, Step & ); StreamBase & operator>>( StreamBase &, Path & ); uint32_t calculatePathPenalty( const std::list & path ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/heroes/skill.cpp000066400000000000000000000731011456075706000216560ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "skill.h" #include #include #include #include #include "artifact.h" #include "artifact_info.h" #include "game_static.h" #include "gamedefs.h" #include "heroes.h" #include "heroes_base.h" #include "kingdom.h" #include "race.h" #include "rand.h" #include "serialize.h" #include "skill_static.h" #include "tools.h" #include "translations.h" #include "world.h" namespace { constexpr std::array allSecondarySkills{ Skill::Secondary::PATHFINDING, Skill::Secondary::ARCHERY, Skill::Secondary::LOGISTICS, Skill::Secondary::SCOUTING, Skill::Secondary::DIPLOMACY, Skill::Secondary::NAVIGATION, Skill::Secondary::LEADERSHIP, Skill::Secondary::WISDOM, Skill::Secondary::MYSTICISM, Skill::Secondary::LUCK, Skill::Secondary::BALLISTICS, Skill::Secondary::EAGLE_EYE, Skill::Secondary::NECROMANCY, Skill::Secondary::ESTATES }; static_assert( !allSecondarySkills.empty() && allSecondarySkills.back() != 0, "All existing secondary skills must be present in this array" ); int SecondaryGetWeightSkillFromRace( const int race, const int skill ) { const Skill::stats_t * ptr = GameStatic::GetSkillStats( race ); if ( ptr == nullptr ) { return 0; } switch ( skill ) { case Skill::Secondary::PATHFINDING: return ptr->mature_secondary.pathfinding; case Skill::Secondary::ARCHERY: return ptr->mature_secondary.archery; case Skill::Secondary::LOGISTICS: return ptr->mature_secondary.logistics; case Skill::Secondary::SCOUTING: return ptr->mature_secondary.scouting; case Skill::Secondary::DIPLOMACY: return ptr->mature_secondary.diplomacy; case Skill::Secondary::NAVIGATION: return ptr->mature_secondary.navigation; case Skill::Secondary::LEADERSHIP: return ptr->mature_secondary.leadership; case Skill::Secondary::WISDOM: return ptr->mature_secondary.wisdom; case Skill::Secondary::MYSTICISM: return ptr->mature_secondary.mysticism; case Skill::Secondary::LUCK: return ptr->mature_secondary.luck; case Skill::Secondary::BALLISTICS: return ptr->mature_secondary.ballistics; case Skill::Secondary::EAGLE_EYE: return ptr->mature_secondary.eagleeye; case Skill::Secondary::NECROMANCY: return ptr->mature_secondary.necromancy; case Skill::Secondary::ESTATES: return ptr->mature_secondary.estates; default: assert( 0 ); break; } return 0; } int SecondaryPriorityFromRace( const int race, const std::unordered_set & blacklist, const uint32_t seed ) { Rand::Queue parts( MAXSECONDARYSKILL ); for ( auto skill : allSecondarySkills ) { if ( blacklist.find( skill ) != blacklist.end() ) { continue; } parts.Push( skill, SecondaryGetWeightSkillFromRace( race, skill ) ); } if ( parts.Size() == 0 ) { return Skill::Secondary::UNKNOWN; } return parts.GetWithSeed( seed ); } } uint32_t Skill::Secondary::GetValues() const { const values_t * val = GameStatic::GetSkillValues( Skill() ); if ( val ) switch ( Level() ) { case Level::BASIC: return val->values.basic; case Level::ADVANCED: return val->values.advanced; case Level::EXPERT: return val->values.expert; default: break; } return 0; } Skill::Primary::Primary() : attack( 0 ) , defense( 0 ) , power( 0 ) , knowledge( 0 ) {} void Skill::Primary::LoadDefaults( int type, int race ) { const stats_t * ptr = GameStatic::GetSkillStats( race ); if ( ptr ) switch ( type ) { case HeroBase::CAPTAIN: attack = ptr->captain_primary.attack; defense = ptr->captain_primary.defense; power = ptr->captain_primary.power; knowledge = ptr->captain_primary.knowledge; break; case HeroBase::HEROES: attack = ptr->initial_primary.attack; defense = ptr->initial_primary.defense; power = ptr->initial_primary.power; knowledge = ptr->initial_primary.knowledge; break; default: break; } } int Skill::Primary::GetInitialSpell( int race ) { const stats_t * ptr = GameStatic::GetSkillStats( race ); return ptr ? ptr->initial_spell : 0; } int Skill::Primary::LevelUp( int race, int level, uint32_t seed ) { Rand::Queue percents( MAXPRIMARYSKILL ); const stats_t * ptr = GameStatic::GetSkillStats( race ); if ( ptr ) { if ( ptr->over_level > level ) { percents.Push( ATTACK, ptr->mature_primary_under.attack ); percents.Push( DEFENSE, ptr->mature_primary_under.defense ); percents.Push( POWER, ptr->mature_primary_under.power ); percents.Push( KNOWLEDGE, ptr->mature_primary_under.knowledge ); } else { percents.Push( ATTACK, ptr->mature_primary_over.attack ); percents.Push( DEFENSE, ptr->mature_primary_over.defense ); percents.Push( POWER, ptr->mature_primary_over.power ); percents.Push( KNOWLEDGE, ptr->mature_primary_over.knowledge ); } } int result = percents.Size() ? percents.GetWithSeed( seed ) : UNKNOWN; switch ( result ) { case ATTACK: ++attack; break; case DEFENSE: ++defense; break; case POWER: ++power; break; case KNOWLEDGE: ++knowledge; break; default: break; } return result; } const char * Skill::Primary::String( const int skillType ) { switch ( skillType ) { case ATTACK: return _( "Attack Skill" ); case DEFENSE: return _( "Defense Skill" ); case POWER: return _( "Spell Power" ); case KNOWLEDGE: return _( "Knowledge" ); default: // Are you sure that you are passing the correct skill type? assert( 0 ); break; } return "Unknown"; } std::string Skill::Primary::StringDescription( int skill, const Heroes * hero ) { std::string res; std::string ext; switch ( skill ) { case ATTACK: res = _( "The hero's attack skill is a bonus added to each unit's attack skill." ); if ( hero ) hero->GetAttack( &ext ); break; case DEFENSE: res = _( "The hero's defense skill is a bonus added to each unit's defense skill." ); if ( hero ) hero->GetDefense( &ext ); break; case POWER: res = _( "The hero's spell power determines the duration or power of a spell." ); if ( hero ) hero->GetPower( &ext ); break; case KNOWLEDGE: res = _( " The hero's knowledge determines how many spell points the hero may have. Under normal circumstances, a hero is limited to 10 spell points per level of knowledge." ); if ( hero ) hero->GetKnowledge( &ext ); break; default: // Are you sure that you are passing the correct skill type? assert( 0 ); break; } if ( !ext.empty() ) { res.append( "\n\n" ); res.append( _( "Current Modifiers:" ) ); res.append( "\n\n" ); res.append( ext ); } return res; } const char * Skill::Level::String( int level ) { switch ( level ) { case BASIC: return _( "skill|Basic" ); case ADVANCED: return _( "skill|Advanced" ); case EXPERT: return _( "skill|Expert" ); default: // Are you sure that you are passing the correct skill level? assert( 0 ); break; } return "None"; } std::string Skill::Level::StringWithBonus( const Heroes & hero, const Secondary & skill ) { const std::string levelStr = String( skill.Level() ); if ( skill.Skill() == Skill::Secondary::NECROMANCY && Skill::GetNecromancyBonus( hero ) > 0 ) { return levelStr + "+" + std::to_string( Skill::GetNecromancyBonus( hero ) ); } return levelStr; } Skill::Secondary::Secondary() : std::pair( UNKNOWN, Level::NONE ) {} Skill::Secondary::Secondary( int skill, int level ) : std::pair( UNKNOWN, Level::NONE ) { SetSkill( skill ); SetLevel( level ); } void Skill::Secondary::Reset() { first = UNKNOWN; second = Level::NONE; } void Skill::Secondary::Set( const Secondary & skill ) { first = skill.first; second = skill.second; } void Skill::Secondary::SetSkill( int skill ) { first = ( skill >= UNKNOWN && skill <= ESTATES ) ? skill : UNKNOWN; } void Skill::Secondary::SetLevel( int level ) { second = ( level >= Level::NONE && level <= Level::EXPERT ) ? level : Level::NONE; } void Skill::Secondary::NextLevel() { switch ( second ) { case Level::NONE: second = Level::BASIC; break; case Level::BASIC: second = Level::ADVANCED; break; case Level::ADVANCED: second = Level::EXPERT; break; default: break; } } bool Skill::Secondary::isValid() const { return Skill() != UNKNOWN && Level() != Level::NONE; } int Skill::Secondary::RandForWitchsHut() { const Skill::secondary_t * sec = GameStatic::GetSkillForWitchsHut(); std::vector v; if ( sec ) { v.reserve( 14 ); if ( sec->archery ) v.push_back( ARCHERY ); if ( sec->ballistics ) v.push_back( BALLISTICS ); if ( sec->diplomacy ) v.push_back( DIPLOMACY ); if ( sec->eagleeye ) v.push_back( EAGLE_EYE ); if ( sec->estates ) v.push_back( ESTATES ); if ( sec->leadership ) v.push_back( LEADERSHIP ); if ( sec->logistics ) v.push_back( LOGISTICS ); if ( sec->luck ) v.push_back( LUCK ); if ( sec->mysticism ) v.push_back( MYSTICISM ); if ( sec->navigation ) v.push_back( NAVIGATION ); if ( sec->necromancy ) v.push_back( NECROMANCY ); if ( sec->pathfinding ) v.push_back( PATHFINDING ); if ( sec->scouting ) v.push_back( SCOUTING ); if ( sec->wisdom ) v.push_back( WISDOM ); } return v.empty() ? UNKNOWN : Rand::Get( v ); } int Skill::Secondary::GetIndexSprite1() const { return ( Skill() > UNKNOWN && Skill() <= ESTATES ) ? Skill() : 0; } int Skill::Secondary::GetIndexSprite2() const { return ( Skill() > UNKNOWN && Skill() <= ESTATES ) ? Skill() - 1 : 0xFF; } const char * Skill::Secondary::String( int skill ) { switch ( skill ) { case PATHFINDING: return _( "Pathfinding" ); case ARCHERY: return _( "Archery" ); case LOGISTICS: return _( "Logistics" ); case SCOUTING: return _( "Scouting" ); case DIPLOMACY: return _( "Diplomacy" ); case NAVIGATION: return _( "Navigation" ); case LEADERSHIP: return _( "Leadership" ); case WISDOM: return _( "Wisdom" ); case MYSTICISM: return _( "Mysticism" ); case LUCK: return _( "Luck" ); case BALLISTICS: return _( "Ballistics" ); case EAGLE_EYE: return _( "Eagle Eye" ); case NECROMANCY: return _( "Necromancy" ); case ESTATES: return _( "Estates" ); default: // Are you sure that you are passing the correct secondary skill type? assert( 0 ); break; } return "Unknown"; } std::string Skill::Secondary::GetName() const { const char * name_skill[] = { _( "Basic Pathfinding" ), _( "Advanced Pathfinding" ), _( "Expert Pathfinding" ), _( "Basic Archery" ), _( "Advanced Archery" ), _( "Expert Archery" ), _( "Basic Logistics" ), _( "Advanced Logistics" ), _( "Expert Logistics" ), _( "Basic Scouting" ), _( "Advanced Scouting" ), _( "Expert Scouting" ), _( "Basic Diplomacy" ), _( "Advanced Diplomacy" ), _( "Expert Diplomacy" ), _( "Basic Navigation" ), _( "Advanced Navigation" ), _( "Expert Navigation" ), _( "Basic Leadership" ), _( "Advanced Leadership" ), _( "Expert Leadership" ), _( "Basic Wisdom" ), _( "Advanced Wisdom" ), _( "Expert Wisdom" ), _( "Basic Mysticism" ), _( "Advanced Mysticism" ), _( "Expert Mysticism" ), _( "Basic Luck" ), _( "Advanced Luck" ), _( "Expert Luck" ), _( "Basic Ballistics" ), _( "Advanced Ballistics" ), _( "Expert Ballistics" ), _( "Basic Eagle Eye" ), _( "Advanced Eagle Eye" ), _( "Expert Eagle Eye" ), _( "Basic Necromancy" ), _( "Advanced Necromancy" ), _( "Expert Necromancy" ), _( "Basic Estates" ), _( "Advanced Estates" ), _( "Expert Estates" ) }; return isValid() ? name_skill[( Level() - 1 ) + ( Skill() - 1 ) * 3] : "unknown"; } std::string Skill::Secondary::GetNameWithBonus( const Heroes & hero ) const { if ( Skill() == NECROMANCY && Skill::GetNecromancyBonus( hero ) > 0 ) { return GetName() + " (+" + std::to_string( Skill::GetNecromancyBonus( hero ) ) + ')'; } return GetName(); } std::string Skill::Secondary::GetDescription( const Heroes & hero ) const { uint32_t count = GetValues(); std::string name = GetName(); std::string str = "unknown"; switch ( Skill() ) { case PATHFINDING: switch ( Level() ) { case Level::BASIC: case Level::ADVANCED: str = _( "%{skill} reduces the movement penalty for rough terrain by %{count} percent." ); break; case Level::EXPERT: str = _( "%{skill} eliminates the movement penalty for rough terrain." ); break; default: break; } break; case ARCHERY: { str = _( "%{skill} increases the damage done by the hero's range attacking creatures by %{count} percent." ); break; } case LOGISTICS: { str = _( "%{skill} increases the hero's movement points by %{count} percent." ); break; } case SCOUTING: { str = _n( "%{skill} increases the hero's viewable area by one square.", "%{skill} increases the hero's viewable area by %{count} squares.", count ); break; } case DIPLOMACY: { str = _( "%{skill} allows the hero to negotiate with monsters who are weaker than their army, and reduces the cost of surrender." ); str += "\n\n"; switch ( Level() ) { case Level::BASIC: case Level::ADVANCED: str.append( _( "Approximately %{count} percent of the creatures may offer to join the hero." ) ); break; case Level::EXPERT: str.append( _( "All of the creatures may offer to join the hero." ) ); break; default: break; } str += "\n\n"; str.append( _( "The cost of surrender is reduced to %{percent} percent of the total cost of troops in the army." ) ); StringReplace( str, "%{percent}", GetDiplomacySurrenderCostDiscount( Level() ) ); break; } case NAVIGATION: { str = _( "%{skill} increases the hero's movement points over water by %{count} percent." ); break; } case LEADERSHIP: { str = _( "%{skill} increases the hero's troops morale by %{count}." ); break; } case WISDOM: { switch ( Level() ) { case Level::BASIC: str = _( "%{skill} allows the hero to learn third level spells." ); break; case Level::ADVANCED: str = _( "%{skill} allows the hero to learn fourth level spells." ); break; case Level::EXPERT: str = _( "%{skill} allows the hero to learn fifth level spells." ); break; default: break; } break; } case MYSTICISM: { str = _n( "%{skill} regenerates one additional spell point per day to the hero.", "%{skill} regenerates %{count} additional spell points per day to the hero.", count ); break; } case LUCK: { str = _( "%{skill} increases the hero's luck by %{count}." ); break; } case BALLISTICS: { switch ( Level() ) { case Level::BASIC: str = _( "%{skill} gives the hero's catapult shots a greater chance to hit and do damage to castle walls." ); break; case Level::ADVANCED: str = _( "%{skill} gives the hero's catapult an extra shot, and each shot has a greater chance to hit and do damage to castle walls." ); break; case Level::EXPERT: str = _( "%{skill} gives the hero's catapult an extra shot, and each shot automatically destroys any wall, except a fortified wall in a Knight castle." ); break; default: break; } break; } case EAGLE_EYE: { switch ( Level() ) { case Level::BASIC: str = _( "%{skill} gives the hero a %{count} percent chance to learn any given 1st or 2nd level spell that was cast by an enemy during combat." ); break; case Level::ADVANCED: str = _( "%{skill} gives the hero a %{count} percent chance to learn any given 3rd level spell (or below) that was cast by an enemy during combat." ); break; case Level::EXPERT: str = _( "%{skill} gives the hero a %{count} percent chance to learn any given 4th level spell (or below) that was cast by an enemy during combat." ); break; default: break; } break; } case NECROMANCY: { count += Skill::GetNecromancyPercent( hero ) - hero.GetSecondaryValues( Skill::Secondary::NECROMANCY ); name = GetNameWithBonus( hero ); str = _( "%{skill} allows %{count} percent of the creatures killed in combat to be brought back from the dead as Skeletons." ); break; } case ESTATES: { str = _( "The hero produces %{count} gold pieces per day as tax revenue from estates." ); break; } default: // Are you sure that you are passing the correct secondary skill type? assert( 0 ); break; } StringReplace( str, "%{skill}", name ); StringReplace( str, "%{count}", count ); return str; } Skill::SecSkills::SecSkills() { reserve( HEROESMAXSKILL ); } Skill::SecSkills::SecSkills( int race ) { reserve( HEROESMAXSKILL ); if ( race & Race::ALL ) { const stats_t * ptr = GameStatic::GetSkillStats( race ); if ( ptr ) { if ( ptr->initial_secondary.archery ) AddSkill( Secondary( Secondary::ARCHERY, ptr->initial_secondary.archery ) ); if ( ptr->initial_secondary.diplomacy ) AddSkill( Secondary( Secondary::DIPLOMACY, ptr->initial_secondary.diplomacy ) ); if ( ptr->initial_secondary.eagleeye ) AddSkill( Secondary( Secondary::EAGLE_EYE, ptr->initial_secondary.eagleeye ) ); if ( ptr->initial_secondary.estates ) AddSkill( Secondary( Secondary::ESTATES, ptr->initial_secondary.estates ) ); if ( ptr->initial_secondary.logistics ) AddSkill( Secondary( Secondary::LOGISTICS, ptr->initial_secondary.logistics ) ); if ( ptr->initial_secondary.luck ) AddSkill( Secondary( Secondary::LUCK, ptr->initial_secondary.luck ) ); if ( ptr->initial_secondary.mysticism ) AddSkill( Secondary( Secondary::MYSTICISM, ptr->initial_secondary.mysticism ) ); if ( ptr->initial_secondary.pathfinding ) AddSkill( Secondary( Secondary::PATHFINDING, ptr->initial_secondary.pathfinding ) ); if ( ptr->initial_secondary.leadership ) AddSkill( Secondary( Secondary::LEADERSHIP, ptr->initial_secondary.leadership ) ); if ( ptr->initial_secondary.ballistics ) AddSkill( Secondary( Secondary::BALLISTICS, ptr->initial_secondary.ballistics ) ); if ( ptr->initial_secondary.navigation ) AddSkill( Secondary( Secondary::NAVIGATION, ptr->initial_secondary.navigation ) ); if ( ptr->initial_secondary.scouting ) AddSkill( Secondary( Secondary::SCOUTING, ptr->initial_secondary.scouting ) ); if ( ptr->initial_secondary.necromancy ) AddSkill( Secondary( Secondary::NECROMANCY, ptr->initial_secondary.necromancy ) ); if ( ptr->initial_secondary.wisdom ) AddSkill( Secondary( Secondary::WISDOM, ptr->initial_secondary.wisdom ) ); } } } int Skill::SecSkills::GetLevel( int skill ) const { const_iterator it = std::find_if( begin(), end(), [skill]( const Secondary & v ) { return v.isSkill( skill ); } ); return it == end() ? Level::NONE : ( *it ).Level(); } uint32_t Skill::SecSkills::GetValues( int skill ) const { const_iterator it = std::find_if( begin(), end(), [skill]( const Secondary & v ) { return v.isSkill( skill ); } ); return it == end() ? 0 : ( *it ).GetValues(); } int Skill::SecSkills::Count() const { return static_cast( std::count_if( begin(), end(), []( const Secondary & v ) { return v.isValid(); } ) ); // it's safe to cast as number is small } int Skill::SecSkills::GetTotalLevel() const { int result = 0; for ( const Skill::Secondary & skill : *this ) { if ( skill.isValid() ) { result += skill.Level(); } } return result; } void Skill::SecSkills::AddSkill( const Skill::Secondary & skill ) { if ( skill.isValid() ) { const int skillValue = skill.Skill(); iterator it = std::find_if( begin(), end(), [skillValue]( const Secondary & v ) { return v.isSkill( skillValue ); } ); if ( it != end() ) ( *it ).SetLevel( skill.Level() ); else { it = std::find_if( begin(), end(), []( const Secondary & v ) { return !v.isValid(); } ); if ( it != end() ) ( *it ).Set( skill ); else if ( size() < HEROESMAXSKILL ) push_back( skill ); } } } Skill::Secondary * Skill::SecSkills::FindSkill( int skill ) { iterator it = std::find_if( begin(), end(), [skill]( const Secondary & v ) { return v.isSkill( skill ); } ); return it != end() ? &( *it ) : nullptr; } std::vector & Skill::SecSkills::ToVector() { std::vector & v = *this; return v; } const std::vector & Skill::SecSkills::ToVector() const { const std::vector & v = *this; return v; } std::string Skill::SecSkills::String() const { std::string output; for ( const_iterator it = begin(); it != end(); ++it ) { output += it->GetName(); output += ", "; } return output; } void Skill::SecSkills::FillMax( const Skill::Secondary & skill ) { if ( size() < HEROESMAXSKILL ) resize( HEROESMAXSKILL, skill ); } std::pair Skill::SecSkills::FindSkillsForLevelUp( const int race, const uint32_t firstSkillSeed, uint32_t const secondSkillSeed ) const { std::unordered_set blacklist; blacklist.reserve( MAXSECONDARYSKILL + HEROESMAXSKILL ); for ( const Secondary & skill : *this ) { if ( skill.Level() != Level::EXPERT ) { continue; } blacklist.insert( skill.Skill() ); } if ( Count() >= HEROESMAXSKILL ) { for ( const int skill : allSecondarySkills ) { if ( GetLevel( skill ) != Level::NONE ) { continue; } blacklist.insert( skill ); } } // Wisdom should be offered to the heroes of "magic" classes on a mandatory basis at least once every three level-ups, regardless of its probability in accordance // with the class parameters const bool isWisdomPriority = [this, race, &blacklist = std::as_const( blacklist )]() { if ( !Race::isMagicalRace( race ) ) { return false; } if ( GetTotalLevel() % 3 != 0 ) { return false; } if ( blacklist.find( Skill::Secondary::WISDOM ) != blacklist.end() ) { return false; } return true; }(); const auto levelUpSingleSkill = [this]( const int skill ) -> Secondary { if ( skill == Secondary::UNKNOWN ) { return {}; } Secondary result{ skill, GetLevel( skill ) }; assert( result.Level() != Level::EXPERT ); result.NextLevel(); return result; }; std::pair result; result.first = levelUpSingleSkill( isWisdomPriority ? Skill::Secondary::WISDOM : SecondaryPriorityFromRace( race, blacklist, firstSkillSeed ) ); if ( result.first.Skill() == Secondary::UNKNOWN ) { return result; } blacklist.insert( result.first.Skill() ); result.second = levelUpSingleSkill( SecondaryPriorityFromRace( race, blacklist, secondSkillSeed ) ); assert( result.first.Skill() != result.second.Skill() ); return result; } void StringAppendModifiers( std::string & str, int value ) { if ( value < 0 ) str.append( " " ); // '-' present else if ( value > 0 ) str.append( " +" ); str.append( std::to_string( value ) ); } int Skill::GetLeadershipModifiers( int level, std::string * strs = nullptr ) { Secondary skill( Secondary::LEADERSHIP, level ); if ( skill.GetValues() && strs ) { strs->append( skill.GetName() ); StringAppendModifiers( *strs, skill.GetValues() ); strs->append( "\n" ); } return skill.GetValues(); } int Skill::GetLuckModifiers( int level, std::string * strs = nullptr ) { Secondary skill( Secondary::LUCK, level ); if ( skill.GetValues() && strs ) { strs->append( skill.GetName() ); StringAppendModifiers( *strs, skill.GetValues() ); strs->append( "\n" ); } return skill.GetValues(); } uint32_t Skill::GetNecromancyBonus( const HeroBase & hero ) { const uint32_t shrineCount = world.GetKingdom( hero.GetColor() ).GetCountNecromancyShrineBuild(); const uint32_t artifactEffect = hero.GetBagArtifacts().isArtifactBonusPresent( fheroes2::ArtifactBonusType::NECROMANCY_SKILL ) ? 1 : 0; // cap bonus at 7 return std::min( 7u, shrineCount + artifactEffect ); } uint32_t Skill::GetNecromancyPercent( const HeroBase & hero ) { uint32_t percent = hero.GetSecondaryValues( Skill::Secondary::NECROMANCY ); percent += 10 * GetNecromancyBonus( hero ); // cap at 100% bonus return std::min( percent, 100u ); } uint32_t Skill::GetDiplomacySurrenderCostDiscount( const int level ) { switch ( level ) { case Level::BASIC: return 40; case Level::ADVANCED: return 30; case Level::EXPERT: return 20; default: return 0; } } StreamBase & Skill::operator<<( StreamBase & msg, const Primary & skill ) { return msg << skill.attack << skill.defense << skill.knowledge << skill.power; } StreamBase & Skill::operator>>( StreamBase & msg, Primary & skill ) { return msg >> skill.attack >> skill.defense >> skill.knowledge >> skill.power; } StreamBase & Skill::operator>>( StreamBase & sb, Secondary & st ) { return sb >> st.first >> st.second; } StreamBase & Skill::operator<<( StreamBase & sb, const SecSkills & ss ) { const std::vector & v = ss; return sb << v; } StreamBase & Skill::operator>>( StreamBase & sb, SecSkills & ss ) { std::vector & v = ss; sb >> v; if ( v.size() > HEROESMAXSKILL ) v.resize( HEROESMAXSKILL ); return sb; } fheroes2-1.0.12+dfsg/src/fheroes2/heroes/skill.h000066400000000000000000000145431456075706000213300ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2SKILL_H #define H2SKILL_H #include #include #include #include void StringAppendModifiers( std::string &, int ); class Heroes; class HeroBase; class StreamBase; namespace Skill { class Secondary; int GetLeadershipModifiers( int level, std::string * strs ); int GetLuckModifiers( int level, std::string * strs ); uint32_t GetNecromancyBonus( const HeroBase & hero ); uint32_t GetNecromancyPercent( const HeroBase & hero ); uint32_t GetDiplomacySurrenderCostDiscount( const int level ); namespace Level { enum { NONE = 0, BASIC = 1, ADVANCED = 2, EXPERT = 3 }; const char * String( int level ); std::string StringWithBonus( const Heroes & hero, const Secondary & skill ); } class Secondary final : public std::pair { public: enum { UNKNOWN = 0, PATHFINDING = 1, ARCHERY = 2, LOGISTICS = 3, SCOUTING = 4, DIPLOMACY = 5, NAVIGATION = 6, LEADERSHIP = 7, WISDOM = 8, MYSTICISM = 9, LUCK = 10, BALLISTICS = 11, EAGLE_EYE = 12, NECROMANCY = 13, ESTATES = 14 }; Secondary(); Secondary( int skill, int level ); void Reset(); void Set( const Secondary & ); void SetSkill( int ); void SetLevel( int ); void NextLevel(); int Level() const { return second; } int Skill() const { return first; } bool isSkill( int skill ) const { return skill == first; } bool isValid() const; std::string GetName() const; std::string GetNameWithBonus( const Heroes & hero ) const; std::string GetDescription( const Heroes & hero ) const; uint32_t GetValues() const; // Returns the sprite index from SECSKILL int GetIndexSprite1() const; // Returns the sprite index from MINISS int GetIndexSprite2() const; static int RandForWitchsHut(); static const char * String( int ); }; StreamBase & operator>>( StreamBase &, Secondary & ); class SecSkills final : protected std::vector { public: SecSkills(); explicit SecSkills( int race ); SecSkills( const SecSkills & ) = delete; ~SecSkills() = default; SecSkills & operator=( const SecSkills & ) = delete; SecSkills & operator=( SecSkills && ) = default; int Count() const; int GetLevel( int skill ) const; int GetTotalLevel() const; uint32_t GetValues( int skill ) const; Secondary * FindSkill( int ); void AddSkill( const Skill::Secondary & ); void FillMax( const Skill::Secondary & ); std::pair FindSkillsForLevelUp( const int race, const uint32_t firstSkillSeed, uint32_t const secondSkillSeed ) const; std::string String() const; std::vector & ToVector(); const std::vector & ToVector() const; protected: friend StreamBase & operator<<( StreamBase &, const SecSkills & ); friend StreamBase & operator>>( StreamBase &, SecSkills & ); }; StreamBase & operator<<( StreamBase &, const SecSkills & ); StreamBase & operator>>( StreamBase &, SecSkills & ); class Primary { public: Primary(); virtual ~Primary() = default; enum { UNKNOWN = 0, ATTACK = 1, DEFENSE = 2, POWER = 3, KNOWLEDGE = 4 }; virtual int GetAttack() const = 0; virtual int GetDefense() const = 0; virtual int GetPower() const = 0; virtual int GetKnowledge() const = 0; virtual int GetMorale() const = 0; virtual int GetLuck() const = 0; virtual int GetRace() const = 0; int LevelUp( int race, int level, uint32_t seed ); static const char * String( const int skillType ); static std::string StringDescription( int, const Heroes * ); static int GetInitialSpell( int race ); protected: void LoadDefaults( int type, int race ); friend StreamBase & operator<<( StreamBase &, const Primary & ); friend StreamBase & operator>>( StreamBase &, Primary & ); int attack; int defense; int power; int knowledge; }; StreamBase & operator<<( StreamBase &, const Primary & ); StreamBase & operator>>( StreamBase &, Primary & ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/heroes/skill_static.h000066400000000000000000000055271456075706000227010ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2012 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2SKILL_STATIC_H #define H2SKILL_STATIC_H #include "gamedefs.h" namespace Skill { struct level_t { uint16_t basic; uint16_t advanced; uint16_t expert; }; struct primary_t { uint8_t attack; uint8_t defense; uint8_t power; uint8_t knowledge; }; struct secondary_t { uint8_t archery; uint8_t ballistics; uint8_t diplomacy; uint8_t eagleeye; uint8_t estates; uint8_t leadership; uint8_t logistics; uint8_t luck; uint8_t mysticism; uint8_t navigation; uint8_t necromancy; uint8_t pathfinding; uint8_t scouting; uint8_t wisdom; }; struct stats_t { const char * id; primary_t captain_primary; primary_t initial_primary; uint8_t initial_book; uint8_t initial_spell; secondary_t initial_secondary; uint8_t over_level; primary_t mature_primary_under; primary_t mature_primary_over; secondary_t mature_secondary; }; struct values_t { const char * id; level_t values; }; } #endif fheroes2-1.0.12+dfsg/src/fheroes2/image/000077500000000000000000000000001456075706000176275ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/fheroes2/image/embedded_image.h000066400000000000000000002111751456075706000227020ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include // If ANY of these arrays are updated please update palette in image_palette.cpp file as some colors might be missing. const uint8_t errorMessage[] = { 0x78, 0x9c, 0xed, 0x9d, 0x0d, 0x7a, 0xa4, 0x38, 0x92, 0x86, 0x4f, 0xb2, 0xdd, 0x4e, 0x5f, 0x61, 0x0b, 0x04, 0x02, 0xdf, 0xa9, 0x0d, 0xcb, 0x8f, 0x40, 0xc2, 0xd3, 0x73, 0xc4, 0xee, 0x0b, 0x6d, 0x7c, 0x11, 0xe2, 0x37, 0x81, 0x04, 0xd2, 0xe5, 0x76, 0xcd, 0xae, 0x9e, 0xe9, 0xa9, 0x74, 0x42, 0x82, 0xf4, 0x12, 0x0a, 0x85, 0x14, 0xa1, 0xe0, 0xbf, 0x7e, 0xef, 0xcb, 0x6f, 0xdb, 0x85, 0x8e, 0xfd, 0xfe, 0xb8, 0x3c, 0x3a, 0xe9, 0xb7, 0xe9, 0x09, 0x0f, 0xaf, 0x38, 0xaf, 0xc0, 0x3f, 0x51, 0x70, 0x63, 0xf5, 0xdb, 0x6f, 0xb7, 0x2f, 0x2f, 0x6f, 0xb7, 0xd7, 0xc7, 0x65, 0xf1, 0x9b, 0xc5, 0xdf, 0x07, 0x2e, 0xf0, 0x19, 0xe5, 0x96, 0x26, 0xc9, 0xed, 0xf6, 0x42, 0x55, 0xee, 0x4b, 0xba, 0x55, 0x0e, 0x37, 0xfe, 0x4d, 0x10, 0x4c, 0xbf, 0x19, 0xca, 0xfe, 0x4f, 0xf7, 0x09, 0xf1, 0xf1, 0x2f, 0x46, 0x44, 0xb7, 0x49, 0x84, 0x10, 0xc8, 0x24, 0xeb, 0x45, 0x6b, 0xf9, 0x37, 0x7d, 0xd0, 0xbc, 0xbe, 0x6c, 0x22, 0x46, 0x39, 0xc2, 0x69, 0x8d, 0xc6, 0x80, 0xe4, 0x6b, 0x01, 0xd1, 0xed, 0x58, 0x84, 0x5e, 0x89, 0x50, 0x9a, 0xe8, 0x7f, 0xff, 0x3b, 0xf6, 0xe5, 0xcf, 0xbb, 0x42, 0x5f, 0x26, 0xc7, 0xa4, 0x88, 0x2e, 0x74, 0x5f, 0xa6, 0xc4, 0x19, 0xd4, 0x11, 0x40, 0x2b, 0x32, 0xb4, 0x3c, 0xe3, 0x0b, 0x08, 0x25, 0x68, 0x37, 0x08, 0x25, 0x5a, 0xb8, 0xfc, 0x6b, 0xa3, 0xfc, 0xf9, 0xa7, 0x4e, 0x8e, 0x11, 0xd2, 0xf1, 0x4a, 0x99, 0xc3, 0x3a, 0xde, 0x65, 0xef, 0xb8, 0x7d, 0x35, 0xa0, 0x1b, 0x03, 0x62, 0x42, 0xff, 0x9e, 0xd2, 0xf9, 0x58, 0x14, 0x20, 0x8a, 0xcf, 0x10, 0x8a, 0x50, 0x36, 0x40, 0x25, 0xc9, 0x41, 0x71, 0xfc, 0xe7, 0x09, 0x41, 0x86, 0xf8, 0x56, 0x42, 0x68, 0x93, 0x0f, 0x23, 0x3a, 0x45, 0x28, 0xf2, 0x65, 0x15, 0xd1, 0x13, 0x84, 0xa6, 0xac, 0xbe, 0x88, 0xd0, 0xed, 0x9e, 0xd0, 0x0a, 0x1f, 0x16, 0xa2, 0x13, 0x84, 0xa2, 0xb1, 0xac, 0x23, 0x3a, 0xaa, 0xf5, 0xbf, 0x05, 0xa1, 0x57, 0x26, 0x94, 0x26, 0xf1, 0x9f, 0x3b, 0x80, 0x80, 0xe8, 0x1a, 0xa1, 0x25, 0xa2, 0x4f, 0x22, 0xf4, 0x45, 0x80, 0x86, 0x32, 0x21, 0xb4, 0x24, 0xd3, 0x7d, 0x74, 0xdd, 0x67, 0x12, 0x8a, 0x3f, 0x85, 0xd0, 0x57, 0x03, 0x9a, 0x10, 0x5a, 0xf2, 0x91, 0x72, 0x86, 0x50, 0xb2, 0x20, 0xb4, 0x22, 0x44, 0xbf, 0x2a, 0xa1, 0x6d, 0x40, 0x40, 0x74, 0x9c, 0xd0, 0x02, 0xd0, 0x12, 0xd1, 0x67, 0x10, 0xfa, 0x72, 0x40, 0x03, 0xa1, 0xbb, 0x2e, 0xd6, 0x4d, 0x84, 0xe8, 0x1c, 0x21, 0xa5, 0xb6, 0x11, 0xfd, 0xe7, 0x10, 0xea, 0xae, 0x13, 0x52, 0x6a, 0x82, 0xe8, 0x3f, 0x87, 0xd0, 0x26, 0xa0, 0xcf, 0x27, 0xf4, 0x8b, 0x01, 0xf2, 0x84, 0xb6, 0xf0, 0x78, 0x45, 0x14, 0xeb, 0xe3, 0x84, 0xd4, 0x7f, 0x3e, 0xa1, 0x29, 0x1d, 0x27, 0x84, 0xa2, 0xa3, 0x84, 0xbc, 0x08, 0x6d, 0x21, 0x7a, 0x9a, 0xd0, 0x3f, 0x00, 0xe8, 0x9e, 0xd0, 0x04, 0x0f, 0x15, 0x26, 0xa4, 0xfe, 0xcf, 0x13, 0xfa, 0xf3, 0x5f, 0x2b, 0x80, 0x9c, 0x94, 0x8b, 0x84, 0xd6, 0x11, 0x3d, 0x4b, 0xe8, 0x6a, 0x23, 0x9f, 0x82, 0xbb, 0x20, 0x34, 0xf4, 0x2e, 0xd7, 0x17, 0xa8, 0xea, 0x0b, 0x84, 0xd4, 0x37, 0x22, 0xf4, 0x14, 0xdd, 0x2d, 0x42, 0x6e, 0x41, 0xe8, 0xe5, 0x3b, 0x10, 0x7a, 0x06, 0xd0, 0x75, 0x44, 0x44, 0x48, 0x8f, 0x84, 0xee, 0xf8, 0x48, 0x37, 0x3b, 0x41, 0x48, 0xfd, 0x7c, 0x42, 0x67, 0x1b, 0x4c, 0x27, 0xa7, 0xcf, 0x10, 0x7a, 0x9b, 0x10, 0x5a, 0x01, 0x74, 0x82, 0x50, 0xba, 0x4a, 0xe8, 0x7e, 0xfe, 0x3a, 0x2e, 0xca, 0x5e, 0x01, 0xc4, 0xcb, 0xc7, 0xbc, 0x54, 0x79, 0x98, 0x0f, 0x16, 0xd0, 0x4f, 0x52, 0x9d, 0x12, 0x9a, 0xc8, 0xd0, 0x1a, 0x20, 0x21, 0x14, 0x7f, 0x02, 0xa1, 0x61, 0x94, 0x1b, 0xd7, 0x65, 0x8f, 0x19, 0xa2, 0x0b, 0x42, 0x89, 0xd6, 0x90, 0xc6, 0xf4, 0x58, 0x7b, 0x3d, 0x20, 0x41, 0x74, 0x9d, 0xd0, 0x8e, 0x08, 0xb1, 0x22, 0x52, 0x17, 0x08, 0x2d, 0xbb, 0x59, 0xa4, 0xc2, 0xb0, 0xfb, 0xdb, 0xd9, 0xd6, 0xb6, 0xad, 0xa5, 0xff, 0xda, 0xc6, 0x46, 0xf1, 0x29, 0x29, 0x92, 0x16, 0xa7, 0x5a, 0x35, 0x65, 0xd5, 0x46, 0xfa, 0xed, 0x28, 0x20, 0xa6, 0x23, 0x8c, 0x2e, 0x11, 0x7a, 0x1b, 0x08, 0xad, 0x03, 0xfa, 0x2c, 0x42, 0x2a, 0x74, 0x65, 0x9e, 0xa1, 0xbc, 0xfb, 0x92, 0x99, 0x83, 0xb3, 0x99, 0x09, 0x20, 0x88, 0x90, 0x79, 0xff, 0xe3, 0x3d, 0x0b, 0xf5, 0x51, 0xcf, 0x64, 0xff, 0x14, 0xae, 0x22, 0x5a, 0x12, 0xba, 0x03, 0xf4, 0x79, 0x84, 0x5a, 0x62, 0xf3, 0x07, 0x15, 0xe1, 0x43, 0xff, 0x96, 0x07, 0x67, 0x33, 0x73, 0x42, 0x71, 0x49, 0xbf, 0xcd, 0x0e, 0xc9, 0xd0, 0x6d, 0x02, 0xe8, 0x32, 0xa2, 0x91, 0xd0, 0x86, 0x08, 0x3d, 0x4b, 0x68, 0x50, 0x43, 0x24, 0x43, 0x65, 0x99, 0x11, 0x98, 0xbc, 0xae, 0xa8, 0xe4, 0xef, 0xef, 0xf5, 0x19, 0x19, 0xea, 0x1b, 0xcd, 0x84, 0x08, 0x6e, 0xf2, 0xb0, 0xb1, 0xb3, 0xdf, 0xde, 0x06, 0x75, 0x7d, 0x96, 0xd2, 0x82, 0xd0, 0x3d, 0xa0, 0xe7, 0x65, 0x48, 0x28, 0x41, 0x0f, 0x85, 0x7f, 0x83, 0x50, 0xc3, 0x5a, 0xba, 0x7d, 0x7f, 0x37, 0x0b, 0x55, 0xcd, 0xcd, 0xe2, 0xff, 0x36, 0xf0, 0x8c, 0x84, 0x6a, 0xfd, 0x90, 0x90, 0xaf, 0x54, 0xff, 0xeb, 0xdb, 0x55, 0x85, 0x3d, 0x10, 0xda, 0x12, 0x21, 0x0c, 0x66, 0xcf, 0xc9, 0x90, 0x97, 0x23, 0x1c, 0x72, 0x42, 0x08, 0x0e, 0x6f, 0x22, 0xd4, 0xea, 0x5d, 0x4d, 0x3d, 0xf5, 0x61, 0xbf, 0xf5, 0x5f, 0x79, 0x42, 0xcd, 0xe3, 0xb1, 0x8c, 0x91, 0xdc, 0xd2, 0xd1, 0x4a, 0xb8, 0xf5, 0x41, 0x08, 0xdf, 0x83, 0xd0, 0x44, 0x0b, 0x4d, 0xc6, 0x7b, 0x4f, 0x88, 0x2a, 0xae, 0x6d, 0x96, 0x39, 0x2d, 0xb1, 0x01, 0xf0, 0xff, 0xd2, 0x3f, 0x69, 0x42, 0x7f, 0xc3, 0xda, 0xd1, 0xb0, 0x94, 0xc4, 0xc5, 0x9d, 0xc8, 0x38, 0xc4, 0xc6, 0xd3, 0x1b, 0xff, 0x23, 0x84, 0xec, 0x21, 0x42, 0xf7, 0x32, 0xb4, 0x90, 0xc8, 0x53, 0x84, 0x3e, 0x49, 0x53, 0x47, 0x77, 0x80, 0x26, 0x45, 0x08, 0xb1, 0xe8, 0xe8, 0xe8, 0x23, 0x8c, 0xb4, 0xd6, 0x62, 0x6a, 0x6b, 0xfa, 0xa5, 0x66, 0x96, 0xf4, 0x19, 0xbf, 0x02, 0xb2, 0x38, 0x92, 0x4f, 0x89, 0x16, 0x0b, 0x28, 0xc1, 0x21, 0xfa, 0x48, 0x84, 0xb2, 0xee, 0x80, 0x3d, 0x34, 0x10, 0xea, 0x43, 0x25, 0x9e, 0xd0, 0x43, 0xff, 0x5a, 0x2e, 0x0c, 0xcd, 0x45, 0xe8, 0x14, 0xa1, 0x48, 0xf8, 0xac, 0x30, 0xa2, 0x26, 0x7b, 0x42, 0xbe, 0xc4, 0x71, 0xd8, 0xd6, 0x65, 0xd5, 0xc5, 0xb1, 0xab, 0xf2, 0xd2, 0xa9, 0xba, 0x28, 0x3b, 0xb2, 0x76, 0x8a, 0xdc, 0x28, 0xa2, 0x65, 0xab, 0x22, 0x2f, 0x8d, 0xa2, 0xb3, 0x94, 0x35, 0x65, 0xe9, 0x62, 0xdd, 0xd5, 0x79, 0xde, 0xc6, 0x4c, 0x48, 0x1d, 0x36, 0x87, 0x04, 0xd1, 0xed, 0xf6, 0xdc, 0x58, 0x76, 0x37, 0x73, 0x9d, 0xad, 0x31, 0x9e, 0xea, 0x65, 0x93, 0x12, 0xcf, 0x44, 0x88, 0x05, 0xa5, 0x23, 0x42, 0x99, 0xf5, 0xce, 0xeb, 0x38, 0xaa, 0x31, 0xfe, 0xbf, 0x17, 0x71, 0x93, 0xc1, 0xc4, 0xa9, 0xa1, 0x5f, 0x54, 0x0e, 0x3b, 0xa0, 0xd6, 0x1f, 0x25, 0x9b, 0x06, 0xef, 0xb9, 0xd3, 0x86, 0x2d, 0xa8, 0x3c, 0xb2, 0x19, 0x86, 0x79, 0xa7, 0xab, 0xf7, 0x3f, 0xf2, 0xe3, 0x06, 0x63, 0xfa, 0x09, 0xf6, 0xd0, 0x03, 0x6f, 0xd0, 0x59, 0x19, 0x5a, 0xe9, 0x60, 0xbd, 0xcc, 0x48, 0x2f, 0x2b, 0x4a, 0x94, 0xff, 0x31, 0xb1, 0x22, 0x0b, 0x12, 0x8d, 0x36, 0x19, 0xcc, 0xa4, 0x2c, 0x07, 0xa1, 0x82, 0x4d, 0xa6, 0xd2, 0x11, 0x95, 0xff, 0x29, 0x04, 0x49, 0x29, 0x67, 0xb5, 0x7c, 0x16, 0x69, 0x31, 0x22, 0x74, 0x64, 0xb0, 0x5f, 0x20, 0xba, 0xa0, 0xa3, 0x27, 0x84, 0x76, 0x42, 0x3f, 0xb8, 0x10, 0xa1, 0xfb, 0xc2, 0xf1, 0x6b, 0x77, 0x84, 0x96, 0xd3, 0xd4, 0x69, 0x10, 0x91, 0xe6, 0x5e, 0xd6, 0x97, 0x3c, 0x8a, 0xa9, 0x37, 0xd3, 0x17, 0x79, 0x46, 0x02, 0x94, 0x67, 0x05, 0xb5, 0xfc, 0x9d, 0x28, 0xd1, 0x47, 0x30, 0x7b, 0xaf, 0xa2, 0xa8, 0x83, 0x40, 0xe5, 0x1f, 0xee, 0x03, 0xa2, 0x97, 0xf3, 0x59, 0xd9, 0x87, 0xae, 0xdf, 0x8f, 0x0c, 0xf6, 0x53, 0x44, 0xe9, 0x33, 0x13, 0x33, 0x8e, 0xb0, 0x9a, 0xb6, 0x69, 0x90, 0x01, 0x35, 0x19, 0xb8, 0x59, 0x61, 0xce, 0xc2, 0xd2, 0x66, 0x45, 0xaa, 0x90, 0x24, 0x77, 0x4c, 0x66, 0xc7, 0x53, 0x21, 0x94, 0xc9, 0xdc, 0x23, 0xb7, 0x38, 0xb9, 0x03, 0x9d, 0xf7, 0xf7, 0x4a, 0xa9, 0x36, 0xa4, 0x5e, 0x96, 0x65, 0xd4, 0x09, 0x23, 0x67, 0x2b, 0x56, 0xc6, 0x5a, 0x73, 0xaf, 0x74, 0x49, 0x84, 0x7f, 0xde, 0xdf, 0xcb, 0x30, 0xb2, 0x4e, 0x83, 0x90, 0x11, 0x42, 0x07, 0xda, 0x3c, 0xca, 0xcf, 0x45, 0x40, 0xaf, 0x88, 0x63, 0x4c, 0xfb, 0xc6, 0x0f, 0x1a, 0xf4, 0xae, 0xac, 0x45, 0x04, 0xdd, 0xc9, 0xca, 0x9c, 0x47, 0xfa, 0xb2, 0x8c, 0x83, 0x4c, 0x62, 0x26, 0xe4, 0x42, 0x92, 0x9d, 0x8e, 0xb0, 0x13, 0x52, 0x21, 0x94, 0x2b, 0xe8, 0x71, 0x60, 0x79, 0x67, 0x3d, 0x8e, 0x6f, 0xa9, 0x23, 0xd1, 0x80, 0x5f, 0x51, 0xbf, 0x32, 0x5a, 0xf1, 0x59, 0x34, 0x17, 0xe3, 0x62, 0xc4, 0x92, 0x3a, 0xba, 0xb8, 0x3a, 0x76, 0xb4, 0x4b, 0x80, 0x5e, 0x27, 0x3d, 0x65, 0x68, 0xd9, 0x5d, 0x89, 0xa7, 0x02, 0x35, 0x2b, 0x33, 0xb5, 0x93, 0xa4, 0x63, 0x0f, 0x5c, 0x55, 0x54, 0x9a, 0x09, 0x75, 0x83, 0x9a, 0xd2, 0x40, 0x46, 0x5a, 0xb8, 0x8d, 0x69, 0x28, 0x8f, 0x30, 0x46, 0x91, 0x42, 0xa6, 0x8f, 0xcd, 0x3b, 0xb8, 0xc0, 0x6a, 0x6a, 0xd8, 0x7e, 0x16, 0x42, 0x64, 0x69, 0xb2, 0x24, 0x12, 0x21, 0x3b, 0x10, 0x3a, 0x88, 0xe8, 0x32, 0x9f, 0x19, 0xa1, 0xcd, 0xf2, 0x92, 0x44, 0xe1, 0xa0, 0xbd, 0x97, 0x2a, 0x6a, 0x24, 0x15, 0x27, 0x0f, 0x2e, 0x36, 0x10, 0xd2, 0x62, 0x01, 0x51, 0xb1, 0x20, 0x54, 0x28, 0x82, 0xcb, 0x84, 0x04, 0x16, 0xa4, 0xc9, 0xdb, 0x95, 0x2d, 0xb3, 0x0a, 0x71, 0x16, 0x8d, 0xf0, 0x6c, 0x3e, 0x12, 0xb5, 0x77, 0x07, 0x42, 0xb7, 0x53, 0x84, 0xae, 0x02, 0x3a, 0x46, 0x48, 0x13, 0xa1, 0x3b, 0x3f, 0xe3, 0x9c, 0xd2, 0x31, 0x42, 0x16, 0x84, 0x42, 0x56, 0x6a, 0xb1, 0x6a, 0x6b, 0x2b, 0x84, 0x4c, 0x14, 0x43, 0x49, 0x95, 0xd4, 0xa3, 0x32, 0x82, 0xa5, 0x87, 0x79, 0x85, 0xc8, 0x90, 0xd5, 0x1f, 0x38, 0x4b, 0xc6, 0x2f, 0xb1, 0xc6, 0x79, 0xed, 0xe3, 0x28, 0xa1, 0xa7, 0x57, 0xf2, 0x8f, 0x11, 0xf2, 0x86, 0xe4, 0x16, 0x27, 0x5e, 0x3b, 0x7c, 0x4c, 0xa8, 0xc5, 0xd8, 0x14, 0xca, 0x4c, 0x36, 0xcc, 0xa9, 0x4b, 0xe1, 0x8b, 0x77, 0x1b, 0x6b, 0xb2, 0xca, 0xd8, 0x12, 0xaa, 0x63, 0x9d, 0xea, 0x7e, 0xf6, 0xfe, 0x46, 0xd2, 0x84, 0x31, 0x4f, 0x0f, 0x93, 0x15, 0x26, 0x14, 0x7f, 0x84, 0xf1, 0x20, 0x43, 0x47, 0xda, 0xf8, 0x14, 0xa0, 0xa3, 0x84, 0x3e, 0x66, 0x86, 0xf6, 0x3d, 0x24, 0x1e, 0xf0, 0x1e, 0x13, 0x22, 0x91, 0xf8, 0xa3, 0xf0, 0xd1, 0x21, 0x24, 0x3e, 0x65, 0x84, 0x2f, 0xb2, 0x8e, 0xa4, 0x23, 0x25, 0xeb, 0x88, 0xed, 0xed, 0x44, 0xd6, 0xc8, 0x30, 0x96, 0x25, 0xdc, 0xbd, 0x68, 0xdc, 0x92, 0xce, 0x99, 0xc0, 0x48, 0x24, 0x59, 0x6b, 0x4b, 0x32, 0xbd, 0xfb, 0x55, 0x81, 0xeb, 0x2d, 0xff, 0xa9, 0x84, 0xe6, 0x90, 0x7a, 0x21, 0x7a, 0x48, 0x48, 0x96, 0x07, 0xf3, 0xd6, 0x98, 0xa6, 0x69, 0x1b, 0x2c, 0x10, 0x81, 0x10, 0x49, 0x52, 0x4c, 0x1d, 0x2a, 0x0e, 0x79, 0x9c, 0x43, 0xdf, 0xc2, 0x08, 0x47, 0x42, 0xa4, 0x62, 0x45, 0xc2, 0x94, 0x2b, 0x9d, 0x58, 0x5e, 0x33, 0xf3, 0x9d, 0x8c, 0x2c, 0x47, 0xe8, 0xf3, 0xb7, 0xef, 0x47, 0xe8, 0x6e, 0xca, 0x3f, 0x43, 0x74, 0x8c, 0x10, 0x26, 0x16, 0x43, 0x21, 0x33, 0x39, 0x02, 0xb2, 0x2a, 0x1a, 0xb0, 0xf0, 0x8c, 0xf4, 0x4d, 0xe3, 0x6b, 0xd2, 0x49, 0x25, 0xd9, 0xd2, 0xbc, 0x00, 0xd0, 0x72, 0xaf, 0xe3, 0x4e, 0xc6, 0x6a, 0xfc, 0x0f, 0xb2, 0x1b, 0xd3, 0x6f, 0x46, 0x28, 0x5c, 0x59, 0xe1, 0xbf, 0x40, 0xa8, 0x1c, 0xe8, 0xf0, 0x9c, 0x2b, 0x8c, 0x78, 0xd4, 0x22, 0x35, 0x74, 0x13, 0xd3, 0x88, 0x04, 0xe6, 0x0d, 0x76, 0x93, 0xaa, 0x32, 0x39, 0x05, 0x80, 0xd2, 0x44, 0xc6, 0x7c, 0xaf, 0x86, 0x44, 0xec, 0xf4, 0x33, 0x26, 0xce, 0xd7, 0x10, 0xba, 0x22, 0x43, 0x71, 0x93, 0x97, 0xb5, 0x31, 0xa6, 0x2e, 0x0b, 0xb2, 0xaa, 0x4b, 0x17, 0x45, 0xf4, 0x45, 0xd5, 0x51, 0xff, 0x41, 0x2f, 0x2b, 0x8b, 0xb2, 0x15, 0xff, 0x37, 0x99, 0x02, 0x2d, 0xce, 0xc8, 0x4d, 0x08, 0x1b, 0x48, 0x5b, 0x9c, 0xd5, 0x13, 0x52, 0x75, 0x96, 0xbb, 0x7e, 0x23, 0xc5, 0x37, 0x26, 0x74, 0xa7, 0x87, 0x0e, 0x8d, 0x65, 0x7e, 0x41, 0x96, 0x74, 0x35, 0x5d, 0x10, 0x93, 0x19, 0xd6, 0xd9, 0x90, 0x07, 0xea, 0x5a, 0x38, 0xe8, 0x45, 0x03, 0x4b, 0x41, 0x21, 0x2f, 0x20, 0xa5, 0x2c, 0x53, 0xbc, 0x3c, 0x24, 0xab, 0xb4, 0xa9, 0xac, 0x12, 0x25, 0x5f, 0x05, 0xe8, 0x4b, 0x09, 0xdd, 0x78, 0x15, 0x51, 0xf7, 0x7b, 0x1b, 0x62, 0x98, 0x3e, 0x71, 0x4f, 0x25, 0x1d, 0x96, 0x14, 0xfd, 0x99, 0xfd, 0xdf, 0xaf, 0x98, 0x16, 0x8d, 0x33, 0x73, 0x76, 0xb9, 0x7e, 0x9d, 0x04, 0x7d, 0x31, 0xa1, 0xbe, 0xf9, 0x68, 0xa4, 0x1e, 0x26, 0x70, 0xfd, 0x6a, 0xf4, 0xdb, 0xdb, 0x6c, 0x6b, 0xda, 0x72, 0x5f, 0x5b, 0x5f, 0xe3, 0xdb, 0xd5, 0xc5, 0xc2, 0x7f, 0x8c, 0xd0, 0xc7, 0x05, 0x42, 0xb3, 0x4d, 0x67, 0x8f, 0x7f, 0x30, 0x81, 0xb3, 0xf6, 0xf5, 0x77, 0x20, 0x14, 0x87, 0x0b, 0x7b, 0x68, 0xc5, 0x64, 0xbc, 0x42, 0xe8, 0xf0, 0xa6, 0xbc, 0xd5, 0xf2, 0x8b, 0x10, 0x3a, 0x61, 0x53, 0xaf, 0x13, 0xfa, 0xe6, 0x80, 0x2e, 0x12, 0xba, 0x9b, 0x99, 0x5d, 0x23, 0xf4, 0x2b, 0x00, 0x3a, 0x4a, 0x48, 0x6d, 0xcd, 0xcb, 0xfe, 0x9f, 0xd0, 0x03, 0x42, 0x1f, 0xcf, 0x10, 0x7a, 0x46, 0x0b, 0x7d, 0x15, 0x9f, 0x83, 0x84, 0x5e, 0x62, 0x35, 0x9d, 0x98, 0xdd, 0x8b, 0xd0, 0xb1, 0xd5, 0x8f, 0x35, 0x42, 0xdf, 0x0b, 0xd0, 0xda, 0x1d, 0x2e, 0x10, 0x5a, 0x01, 0xf4, 0x71, 0x68, 0x05, 0xad, 0x2f, 0xc7, 0x00, 0xdd, 0xef, 0x97, 0xfe, 0xb9, 0x80, 0x86, 0xda, 0x2d, 0x6e, 0x72, 0x9e, 0x50, 0xb7, 0x21, 0x42, 0xc7, 0x09, 0xbd, 0x1d, 0xd9, 0x7f, 0xbf, 0x78, 0xa4, 0x5f, 0xc2, 0x27, 0x65, 0x8b, 0x7f, 0x34, 0xee, 0xcf, 0x12, 0xea, 0x36, 0x01, 0x9d, 0x25, 0xf4, 0xe6, 0x09, 0xdd, 0x76, 0x64, 0x68, 0xfe, 0x60, 0xbf, 0x80, 0x4f, 0x9a, 0xcc, 0x9c, 0x3c, 0xc3, 0xcc, 0xef, 0x19, 0x42, 0x1f, 0x33, 0x42, 0xf1, 0x09, 0x42, 0x6f, 0xbe, 0x8f, 0xed, 0x8b, 0xd0, 0x0a, 0xa3, 0x9f, 0x81, 0x47, 0x26, 0x32, 0xe3, 0x4e, 0xf8, 0xc1, 0x77, 0xc3, 0x13, 0xc6, 0xd3, 0x84, 0x36, 0x00, 0x61, 0x28, 0xd3, 0x27, 0x08, 0x3d, 0xcc, 0x05, 0xb2, 0x7c, 0xc2, 0x3f, 0x0f, 0xcf, 0x08, 0x68, 0x16, 0x75, 0xc0, 0xab, 0x10, 0xc9, 0x49, 0x42, 0xdd, 0x46, 0x1f, 0xbb, 0x4c, 0x68, 0xe7, 0x9c, 0x9f, 0x45, 0x63, 0x1d, 0xd0, 0xdd, 0x0e, 0xdd, 0x09, 0xa2, 0xc3, 0x84, 0xee, 0xc3, 0x42, 0x66, 0x84, 0xa2, 0x0b, 0x84, 0xf6, 0xce, 0xf9, 0x52, 0x42, 0xf3, 0x4d, 0xde, 0x33, 0x67, 0x72, 0xf2, 0x04, 0xa1, 0xa5, 0xbf, 0x8c, 0xba, 0xed, 0x2f, 0x49, 0x68, 0x06, 0x48, 0xa8, 0xc8, 0xf2, 0x8c, 0xd7, 0xd8, 0x97, 0x09, 0xcd, 0x01, 0x09, 0xa1, 0x63, 0x80, 0x78, 0x21, 0xe8, 0xdb, 0x74, 0xb2, 0x29, 0xa0, 0x78, 0x9e, 0x26, 0x47, 0x46, 0xb7, 0xab, 0x84, 0x3e, 0x9e, 0x21, 0xc4, 0xa9, 0x9c, 0x9e, 0x06, 0x74, 0x05, 0xe7, 0xdd, 0x0f, 0x6e, 0x58, 0x15, 0x1f, 0x00, 0x25, 0xe3, 0x71, 0x0f, 0x49, 0x1f, 0x22, 0x74, 0xbb, 0x27, 0xb4, 0x04, 0x74, 0x9a, 0xd0, 0xfe, 0x74, 0xe3, 0x0c, 0x9f, 0x13, 0x88, 0x56, 0x7e, 0x30, 0xc9, 0x12, 0xe0, 0x47, 0xf7, 0xc5, 0xe9, 0xc7, 0x54, 0xc7, 0x1d, 0xa1, 0x7b, 0x09, 0x1a, 0x57, 0xe1, 0xd7, 0x0a, 0x3a, 0xf6, 0x18, 0x0b, 0xf6, 0x78, 0x42, 0x76, 0xb0, 0xb1, 0x67, 0x23, 0xa7, 0xee, 0x6f, 0x32, 0x03, 0x74, 0x7f, 0x25, 0x6e, 0xfc, 0x11, 0x40, 0x4b, 0x42, 0x4b, 0x01, 0x12, 0x83, 0x71, 0x93, 0x90, 0x5f, 0x96, 0x06, 0xa4, 0x44, 0xb6, 0x3e, 0x9d, 0x4b, 0x8a, 0x36, 0x54, 0x77, 0x5e, 0xf5, 0x74, 0xb2, 0x07, 0xeb, 0x30, 0xa0, 0x49, 0x38, 0xd3, 0x8d, 0xe7, 0x7e, 0x7d, 0x1f, 0x8b, 0xe3, 0xf5, 0x00, 0x91, 0x43, 0x84, 0x6e, 0x77, 0x36, 0xf5, 0x0c, 0x8f, 0x4f, 0xd2, 0x10, 0x6f, 0x6d, 0x86, 0x22, 0x3c, 0xaa, 0x35, 0x8d, 0xe3, 0x09, 0x8f, 0x6b, 0x9c, 0x7e, 0xb8, 0xb1, 0x6c, 0xef, 0xf1, 0x4f, 0x00, 0xc5, 0xae, 0x31, 0xad, 0x4a, 0x0e, 0x07, 0x07, 0xa1, 0x2e, 0x71, 0x3b, 0x8f, 0x73, 0x4f, 0x1e, 0x00, 0x3a, 0x36, 0xeb, 0x20, 0x42, 0xe1, 0x62, 0x79, 0x68, 0x82, 0x67, 0xb4, 0x23, 0xd6, 0x09, 0x25, 0x3a, 0x32, 0x79, 0x55, 0x17, 0x59, 0x89, 0x18, 0xe0, 0x22, 0xab, 0x1e, 0x2a, 0xac, 0xad, 0xe7, 0x3f, 0x23, 0x94, 0x24, 0x5d, 0x59, 0xd4, 0x75, 0x96, 0xb5, 0xc9, 0xd1, 0xe8, 0x20, 0x50, 0xb5, 0x59, 0x16, 0x25, 0x93, 0xc8, 0x38, 0x2f, 0x42, 0x0c, 0xe8, 0x09, 0x42, 0xf0, 0x75, 0x38, 0x87, 0x4d, 0x61, 0xd8, 0x1b, 0xe6, 0x24, 0x4e, 0x7f, 0x16, 0x83, 0x26, 0x46, 0xf5, 0xda, 0xc5, 0x5e, 0xb4, 0x2a, 0x32, 0x17, 0xc5, 0x1d, 0xb5, 0x25, 0x8e, 0xc2, 0x2c, 0x33, 0xd1, 0x05, 0x42, 0xf8, 0x7a, 0xf4, 0xd4, 0x33, 0x77, 0x97, 0xd5, 0x2a, 0xd6, 0x6d, 0x56, 0x9c, 0x20, 0x94, 0x68, 0x93, 0x95, 0x90, 0xf6, 0xd8, 0x39, 0x66, 0x92, 0xf6, 0x22, 0x94, 0x6c, 0x5e, 0xe3, 0xa0, 0x37, 0xc8, 0x55, 0xd8, 0x1c, 0x56, 0x14, 0x05, 0xfe, 0xbf, 0x6e, 0x5d, 0x17, 0xf6, 0x74, 0x64, 0xc2, 0xa7, 0x42, 0x6b, 0xd5, 0xda, 0x86, 0x3a, 0x1a, 0x2e, 0x4d, 0xd6, 0x90, 0xa4, 0x75, 0x45, 0xa5, 0x10, 0x50, 0x9d, 0xb5, 0x8f, 0x08, 0xad, 0x37, 0xee, 0x96, 0x2a, 0xab, 0xd2, 0x9e, 0x50, 0xaa, 0x49, 0x1a, 0x43, 0xba, 0xbb, 0x33, 0xdd, 0x89, 0x5e, 0xa6, 0xa3, 0x3a, 0xab, 0xa1, 0x32, 0xdb, 0x2c, 0x73, 0x10, 0xf9, 0xa4, 0x17, 0xa1, 0xed, 0x6b, 0x30, 0xa1, 0x74, 0xa5, 0xbc, 0x8c, 0xf1, 0x8c, 0x08, 0x15, 0x6f, 0x9a, 0x32, 0xcb, 0x21, 0x3f, 0xd6, 0x14, 0x59, 0x56, 0x13, 0xa1, 0xc1, 0x3a, 0x87, 0xa0, 0xaa, 0x36, 0xcb, 0xd5, 0x5a, 0x56, 0xc2, 0x24, 0x86, 0x08, 0xf1, 0xc9, 0x5a, 0x63, 0x63, 0x47, 0x66, 0x1f, 0xca, 0xd0, 0x84, 0xd4, 0xe4, 0x42, 0xba, 0xca, 0x8c, 0xf7, 0xbc, 0xe2, 0x2f, 0x9b, 0x55, 0xac, 0xfb, 0x96, 0x8e, 0xc7, 0xad, 0x4b, 0x71, 0x3b, 0xe3, 0xa8, 0x24, 0x19, 0xa6, 0xca, 0x34, 0x88, 0x63, 0x4b, 0xc7, 0x4e, 0xb6, 0x2d, 0x42, 0xaf, 0x9c, 0xb1, 0x72, 0x8c, 0x83, 0xdd, 0x08, 0x75, 0x25, 0x19, 0x09, 0x4c, 0x56, 0xd8, 0x2e, 0xa4, 0xd2, 0x19, 0x42, 0x04, 0x42, 0x7e, 0x57, 0x46, 0x92, 0xd0, 0xf1, 0x3a, 0xab, 0x94, 0x8a, 0xa7, 0xf1, 0xc1, 0x5c, 0xa5, 0x04, 0x51, 0xe6, 0x59, 0x17, 0xca, 0xe6, 0xd6, 0x48, 0x61, 0xbf, 0x4b, 0x74, 0x6e, 0x7b, 0xab, 0x94, 0x54, 0x2b, 0x82, 0xdb, 0x0f, 0x06, 0x24, 0x42, 0x26, 0x33, 0x2a, 0x42, 0x60, 0xc8, 0x89, 0x04, 0x86, 0x54, 0x81, 0x32, 0x6b, 0xb0, 0x7d, 0x84, 0x1a, 0xa3, 0x84, 0x50, 0x2f, 0x42, 0x9b, 0x92, 0x27, 0xf9, 0x18, 0xc7, 0xa0, 0xe0, 0xd5, 0x02, 0x44, 0x41, 0x93, 0x15, 0x3f, 0x02, 0x85, 0x0e, 0x15, 0x90, 0x7e, 0xb4, 0x13, 0x42, 0x74, 0x9b, 0x90, 0x9e, 0x4d, 0x2f, 0x56, 0x3c, 0xf2, 0x8b, 0xcf, 0x99, 0xb4, 0x74, 0x48, 0x9a, 0x42, 0x24, 0x0e, 0x84, 0x48, 0xd4, 0x42, 0x0e, 0xf5, 0x48, 0xa7, 0x71, 0xd8, 0x63, 0x1b, 0x27, 0x42, 0xdc, 0x7f, 0x21, 0xa7, 0x69, 0x74, 0xd0, 0xae, 0x4f, 0x8d, 0x40, 0xe0, 0xa9, 0xa9, 0x9e, 0x90, 0xdf, 0x3a, 0x3d, 0x1f, 0xc7, 0xef, 0x2e, 0x24, 0x8f, 0x2b, 0x2c, 0x48, 0x1b, 0x52, 0x15, 0x59, 0x1d, 0x8d, 0x84, 0xf4, 0x1e, 0xa1, 0x31, 0x26, 0x5f, 0xd1, 0xff, 0x86, 0x32, 0x37, 0x98, 0x11, 0x76, 0x48, 0x17, 0x0d, 0x10, 0x9c, 0x4f, 0xb7, 0xf9, 0x91, 0x41, 0x62, 0x22, 0xbf, 0x67, 0x07, 0x71, 0xad, 0x41, 0x9e, 0xb5, 0x61, 0x3f, 0xf6, 0xfb, 0x99, 0x3e, 0xcf, 0xfe, 0x14, 0x7e, 0xd7, 0xf7, 0xc9, 0x48, 0x11, 0xe6, 0x30, 0xe2, 0x99, 0xe1, 0x18, 0x98, 0x8e, 0x35, 0xcf, 0x45, 0x44, 0xc3, 0xb0, 0x0e, 0x9a, 0xf2, 0x17, 0xbc, 0x51, 0xc8, 0xe2, 0xa7, 0x3d, 0xa1, 0x88, 0x9a, 0xca, 0x84, 0x92, 0xe9, 0x95, 0x06, 0x52, 0x6c, 0x7a, 0x0d, 0xb1, 0xef, 0xfe, 0x4a, 0x44, 0x59, 0x51, 0x3d, 0x9d, 0x10, 0x92, 0x01, 0xc3, 0x13, 0xda, 0xd3, 0xf5, 0x4c, 0x28, 0xda, 0x4b, 0xa5, 0xa7, 0x98, 0x45, 0xa4, 0xa8, 0xa5, 0x61, 0xf4, 0xfb, 0x0b, 0x23, 0x2a, 0xb3, 0x22, 0x08, 0xa9, 0xbd, 0x55, 0x59, 0x56, 0x0e, 0xfb, 0xc6, 0xa8, 0x5f, 0x9b, 0xa6, 0xb1, 0x4a, 0x85, 0x6d, 0x55, 0x16, 0x65, 0xa3, 0x04, 0x11, 0xa9, 0xf8, 0x96, 0xce, 0x6d, 0x10, 0x95, 0x67, 0x5c, 0xac, 0x70, 0x11, 0x26, 0x1b, 0xd1, 0x69, 0xa5, 0xe9, 0x48, 0x22, 0xf1, 0xa1, 0xb2, 0xa2, 0x9a, 0x12, 0x67, 0xaa, 0xca, 0x45, 0x91, 0xad, 0x10, 0x2f, 0x04, 0xf9, 0xd0, 0x96, 0x6f, 0x11, 0x45, 0xa1, 0xad, 0xb3, 0x82, 0x86, 0xd2, 0x88, 0x09, 0xc5, 0x96, 0x6e, 0x58, 0xd3, 0x9f, 0x4a, 0x87, 0xa6, 0x2c, 0x71, 0x3f, 0x65, 0x4d, 0x55, 0x47, 0xc0, 0x64, 0xab, 0x56, 0xe3, 0xaf, 0xaa, 0xe3, 0x2b, 0x19, 0xa5, 0x99, 0x17, 0xd7, 0x96, 0xa4, 0xbf, 0xc3, 0xb3, 0x32, 0x24, 0x4a, 0xa0, 0xed, 0xb7, 0x68, 0xed, 0xa9, 0x7a, 0xec, 0x0d, 0x5a, 0xcb, 0xf2, 0x35, 0x43, 0x14, 0x7b, 0x42, 0x2a, 0x7a, 0x79, 0xc1, 0xf0, 0x1d, 0xd6, 0x59, 0xfe, 0x23, 0x74, 0x45, 0xd5, 0xb6, 0x55, 0x96, 0x05, 0x2a, 0xa8, 0x4b, 0x92, 0x2a, 0x54, 0xd4, 0x16, 0x84, 0x83, 0xbe, 0xf3, 0xcf, 0x57, 0x7f, 0x54, 0x55, 0x91, 0xe5, 0xd8, 0xe9, 0x82, 0x41, 0x0c, 0x84, 0x20, 0x7c, 0x71, 0x87, 0xd3, 0xf2, 0x2c, 0x0f, 0xba, 0x22, 0x27, 0x78, 0x74, 0x8c, 0x09, 0x45, 0x35, 0xa9, 0x38, 0x43, 0x5d, 0x11, 0xd1, 0x55, 0x7c, 0xd7, 0x9a, 0xc0, 0xf3, 0xe5, 0x5a, 0xba, 0x4e, 0x41, 0xb7, 0x08, 0x99, 0x10, 0x61, 0xa5, 0x31, 0x95, 0x1e, 0x45, 0x67, 0xb3, 0xb2, 0x31, 0x39, 0x3d, 0x3c, 0x5b, 0xa1, 0xef, 0x43, 0xaa, 0xa9, 0xc3, 0xc7, 0x16, 0x96, 0x52, 0x43, 0xf5, 0xa4, 0x62, 0x78, 0x5f, 0x12, 0x49, 0x6f, 0x63, 0x5b, 0xfa, 0x22, 0x8c, 0xf9, 0x2f, 0xc7, 0xe6, 0x9b, 0x10, 0xda, 0xeb, 0x64, 0x9e, 0x50, 0x0f, 0xe3, 0x6e, 0x95, 0xac, 0x9b, 0x12, 0xaa, 0x26, 0x84, 0x48, 0x27, 0xd1, 0x18, 0x15, 0x04, 0x01, 0xf4, 0x41, 0x08, 0x65, 0x13, 0x84, 0x21, 0x84, 0xcb, 0x38, 0x6b, 0x4b, 0x82, 0xa9, 0x58, 0x41, 0xb1, 0xd2, 0xaa, 0xc9, 0x80, 0xc2, 0x20, 0xa6, 0x42, 0x52, 0xe8, 0x35, 0x11, 0xea, 0x70, 0x36, 0xf5, 0xb8, 0xac, 0xab, 0xa9, 0x73, 0x06, 0xf4, 0x2b, 0x2b, 0x32, 0xa4, 0x31, 0xd6, 0xd8, 0xcc, 0x74, 0x18, 0x8d, 0xa9, 0xee, 0x35, 0xa9, 0x1e, 0x85, 0x13, 0xe9, 0x4e, 0x35, 0x63, 0x97, 0x5e, 0x26, 0x66, 0x43, 0x18, 0xd0, 0x30, 0x50, 0xd3, 0x69, 0x54, 0x95, 0x2a, 0x0c, 0xa9, 0x07, 0x59, 0x6a, 0x70, 0x98, 0x93, 0x75, 0x41, 0xc2, 0x49, 0x1a, 0x87, 0x7e, 0xd2, 0x41, 0x27, 0xd3, 0x9f, 0x86, 0x6e, 0x1c, 0xa1, 0x9e, 0x39, 0x08, 0xd1, 0x68, 0x1f, 0xea, 0xa3, 0x84, 0x6e, 0x53, 0x42, 0x2b, 0x94, 0x80, 0xe8, 0x8e, 0x10, 0x21, 0x08, 0x02, 0x93, 0x37, 0xc1, 0x0f, 0x54, 0x9c, 0xc6, 0xb7, 0x86, 0xbe, 0x20, 0x40, 0x21, 0x59, 0xcf, 0xd6, 0xfe, 0x55, 0xd3, 0x33, 0x55, 0x32, 0xc8, 0x85, 0x5d, 0xc5, 0x84, 0x30, 0x88, 0x11, 0x42, 0x1a, 0xb1, 0xe9, 0xff, 0xcb, 0xac, 0x0d, 0xd0, 0x4f, 0x8c, 0x42, 0xd7, 0x71, 0x81, 0xeb, 0xc4, 0x1e, 0x47, 0xa8, 0x5e, 0xd6, 0x14, 0x55, 0x18, 0x10, 0x50, 0xa7, 0x68, 0xe4, 0x2b, 0xa1, 0xd5, 0x48, 0xe7, 0x18, 0x3c, 0x8a, 0x56, 0xf5, 0xc3, 0x20, 0xf6, 0xd0, 0xd0, 0x35, 0xc0, 0xb6, 0xc3, 0x19, 0x06, 0x0c, 0x1d, 0x53, 0xc5, 0x70, 0xd9, 0xd0, 0x57, 0x44, 0xcd, 0x00, 0x22, 0x5f, 0x03, 0x4a, 0x0c, 0x17, 0xa2, 0x8b, 0x60, 0x03, 0x40, 0x42, 0x84, 0x24, 0xc2, 0xff, 0x20, 0xa1, 0x68, 0x6f, 0x07, 0xde, 0x40, 0x08, 0x8f, 0x3f, 0xe2, 0x5d, 0x1e, 0xd0, 0x77, 0x0d, 0x89, 0x0f, 0xb5, 0xd2, 0xd0, 0x6c, 0xa2, 0x65, 0x42, 0x15, 0x44, 0x88, 0xe6, 0x26, 0xad, 0xa9, 0x6b, 0x3c, 0x52, 0x26, 0x04, 0x63, 0x9c, 0xa4, 0x82, 0x2e, 0x23, 0x84, 0x78, 0xc8, 0x0b, 0xe9, 0x33, 0xfa, 0x9d, 0x71, 0xc8, 0x52, 0x40, 0xbd, 0x85, 0x3e, 0x88, 0x70, 0x10, 0x21, 0x9a, 0x9c, 0x14, 0xd4, 0x72, 0x3e, 0x1d, 0x3c, 0x31, 0x78, 0x12, 0xd1, 0x5a, 0x08, 0xc5, 0x53, 0x42, 0x36, 0x0c, 0x1d, 0x89, 0x04, 0xc6, 0x06, 0x12, 0x39, 0x0b, 0x42, 0x1d, 0x3d, 0x24, 0x4c, 0x44, 0x88, 0x46, 0x97, 0x93, 0x69, 0x4b, 0xf2, 0xd9, 0x72, 0xcd, 0x2a, 0x96, 0x74, 0xfa, 0x03, 0x73, 0x1f, 0x44, 0xd7, 0x16, 0x62, 0xbc, 0xf1, 0x96, 0xb8, 0x3d, 0x6b, 0x48, 0x7a, 0x59, 0xb4, 0xd3, 0xcb, 0xba, 0xa1, 0x97, 0x55, 0xe8, 0x20, 0x1a, 0x80, 0x14, 0xfa, 0x14, 0xf1, 0x29, 0x8b, 0x0a, 0x8a, 0xc3, 0x52, 0x3d, 0x48, 0x88, 0xd9, 0x54, 0xb2, 0x25, 0x62, 0x39, 0x73, 0x32, 0x29, 0x59, 0x11, 0xd1, 0xcf, 0x42, 0xaa, 0x5c, 0xd8, 0x51, 0x93, 0x73, 0x9a, 0xab, 0x40, 0x44, 0x82, 0x00, 0x7a, 0xc7, 0x76, 0xdc, 0xf6, 0x30, 0x68, 0x10, 0xd2, 0xe9, 0x67, 0x22, 0x4c, 0x88, 0xda, 0x17, 0xc2, 0x2a, 0xe8, 0x02, 0x88, 0x01, 0x0f, 0x8e, 0xfc, 0x2b, 0xea, 0x8a, 0x03, 0xa1, 0x04, 0x84, 0x1c, 0x4e, 0x2b, 0x42, 0x5c, 0x87, 0x5a, 0xde, 0x91, 0xb4, 0xd0, 0x6f, 0x42, 0x97, 0xe7, 0x42, 0x28, 0xc3, 0x18, 0x8a, 0xbe, 0x4c, 0x9c, 0xe8, 0x4a, 0xc4, 0x93, 0xe4, 0xaa, 0xc4, 0x70, 0x83, 0x09, 0x74, 0xad, 0x3d, 0xa1, 0xf8, 0x21, 0xa1, 0xb9, 0x0c, 0xad, 0xad, 0xd7, 0x7b, 0x42, 0xbd, 0xc5, 0x13, 0xf9, 0xe7, 0x87, 0xe7, 0xf6, 0x03, 0xad, 0xb5, 0xa4, 0x81, 0x0c, 0x9e, 0x14, 0x7f, 0xd7, 0xd8, 0xbf, 0xa0, 0x6e, 0xd9, 0x48, 0x64, 0x45, 0x54, 0x42, 0xc8, 0x14, 0x13, 0x42, 0x33, 0xa9, 0xbf, 0xb4, 0xdc, 0x43, 0x78, 0xd6, 0x42, 0xec, 0xba, 0xb6, 0x9f, 0x7e, 0x52, 0x55, 0x42, 0x28, 0x5d, 0x3a, 0x22, 0xcf, 0xa0, 0x80, 0x5e, 0xa7, 0x42, 0x1a, 0x2f, 0x44, 0x23, 0x87, 0x5e, 0x86, 0x7d, 0x58, 0xe8, 0x5f, 0x96, 0xff, 0x5f, 0x45, 0x84, 0x81, 0xe6, 0x3d, 0xe8, 0xfa, 0x21, 0x86, 0x06, 0xea, 0xf7, 0x20, 0x64, 0x44, 0x01, 0x99, 0x30, 0xe8, 0x72, 0x96, 0x74, 0x92, 0x5b, 0x10, 0x62, 0x43, 0x4e, 0xfb, 0x7e, 0x7d, 0x9a, 0xd0, 0x8a, 0x1e, 0x62, 0x42, 0x6c, 0xa2, 0x31, 0x21, 0x28, 0x4e, 0x1a, 0xd6, 0x15, 0x74, 0xd1, 0x8f, 0x9a, 0x26, 0x6a, 0x96, 0x9f, 0x14, 0x34, 0x35, 0xf5, 0xa8, 0xc2, 0xda, 0xba, 0x28, 0x04, 0xa6, 0x66, 0x19, 0x2a, 0xa1, 0x51, 0x14, 0x3f, 0xf3, 0x8e, 0x7b, 0x19, 0x0d, 0x60, 0x34, 0x81, 0xa1, 0x49, 0x95, 0x35, 0xa4, 0xb2, 0x49, 0xc3, 0x52, 0xc7, 0x68, 0xc4, 0x68, 0xd1, 0x34, 0xb3, 0x6d, 0x30, 0x1a, 0x43, 0x38, 0xd0, 0x77, 0x49, 0xb3, 0x28, 0xc2, 0xd5, 0xb2, 0x20, 0x99, 0x61, 0x85, 0x25, 0x89, 0xd9, 0x3a, 0x82, 0x3a, 0x86, 0xe6, 0x57, 0x05, 0xb4, 0x0c, 0x3d, 0x04, 0xeb, 0xaa, 0xa2, 0xe3, 0xef, 0x78, 0x7e, 0x23, 0xe3, 0xba, 0x51, 0x60, 0x5d, 0x76, 0xae, 0x2c, 0x61, 0xf6, 0x12, 0x36, 0xb2, 0xc8, 0xab, 0xb0, 0x9f, 0xdb, 0x1f, 0x19, 0xcb, 0x8e, 0x11, 0xa2, 0xa7, 0xdb, 0xb4, 0x6d, 0x53, 0x63, 0xca, 0x11, 0xf3, 0xc3, 0xc9, 0x00, 0x84, 0xb4, 0x72, 0x6b, 0x9c, 0xc3, 0x84, 0xb6, 0xaa, 0xe8, 0xbb, 0x22, 0xcf, 0x5b, 0xfa, 0x87, 0xcc, 0x1f, 0xcd, 0x84, 0xe8, 0xf1, 0x75, 0x3c, 0x7c, 0x65, 0x65, 0x55, 0x51, 0x97, 0xcb, 0xc8, 0x4e, 0xa1, 0x47, 0x9f, 0x55, 0x04, 0xd7, 0x12, 0x90, 0xa2, 0x69, 0x61, 0x47, 0xf8, 0x96, 0xd3, 0x17, 0xd8, 0x9b, 0x88, 0xfd, 0x3f, 0x55, 0xd5, 0x50, 0xcf, 0xce, 0x0c, 0x46, 0x24, 0x6a, 0x07, 0xa6, 0x52, 0x95, 0x29, 0x5b, 0xdf, 0xae, 0x96, 0x8c, 0x62, 0x7a, 0x5c, 0x74, 0xdf, 0xb2, 0x69, 0x08, 0x10, 0xb1, 0x08, 0x79, 0xef, 0xa3, 0x85, 0xda, 0x36, 0x5c, 0x23, 0xde, 0xb1, 0x07, 0xc3, 0xa9, 0x44, 0x8d, 0x32, 0xf4, 0xc8, 0x16, 0xd7, 0xb5, 0x09, 0x1d, 0x6c, 0xde, 0x3c, 0xeb, 0xf8, 0x91, 0x3d, 0x74, 0x37, 0x96, 0x21, 0xa3, 0xf0, 0xdd, 0x68, 0x4f, 0xd3, 0x7b, 0x63, 0xc8, 0x16, 0x2c, 0xc8, 0x06, 0x0a, 0x65, 0xe3, 0xb3, 0xa2, 0x8a, 0x99, 0x80, 0x46, 0x73, 0x32, 0x10, 0xc3, 0x88, 0xfe, 0x20, 0xd5, 0xdb, 0x19, 0x3a, 0x81, 0x86, 0x76, 0x43, 0x7f, 0x28, 0x91, 0xa1, 0x8e, 0xc7, 0x12, 0x02, 0x5a, 0x54, 0x4d, 0xa7, 0x2c, 0x14, 0x74, 0x14, 0xd1, 0x79, 0x79, 0xd1, 0x28, 0x1d, 0x93, 0xbd, 0x57, 0x20, 0xd0, 0xdc, 0xcf, 0x79, 0x53, 0x55, 0x1b, 0x36, 0xb9, 0xa3, 0xba, 0xa8, 0x2d, 0x7a, 0x1b, 0x9d, 0x57, 0x83, 0x35, 0x59, 0x7c, 0x24, 0x9b, 0xb5, 0x4d, 0x3c, 0xa1, 0x06, 0x66, 0x0e, 0x99, 0xcd, 0x1d, 0xf6, 0x9c, 0x37, 0x11, 0xdb, 0xef, 0x2d, 0xdd, 0x9c, 0xac, 0x83, 0x90, 0x2a, 0xd1, 0xa8, 0xae, 0x6a, 0xd8, 0xad, 0xa3, 0x64, 0x48, 0xa0, 0xef, 0x58, 0xeb, 0x57, 0x65, 0xed, 0x68, 0xae, 0x6d, 0x2a, 0x59, 0x27, 0xe0, 0xb9, 0x6b, 0x1c, 0xef, 0x6d, 0xde, 0x5f, 0x21, 0x34, 0x93, 0x23, 0x21, 0x94, 0xd0, 0x04, 0x5f, 0xa6, 0xc1, 0x3c, 0xcb, 0x90, 0x09, 0x7f, 0xcc, 0x1b, 0xc6, 0xe9, 0x3f, 0xee, 0xd7, 0x11, 0x4f, 0xd9, 0x7c, 0xe9, 0xd7, 0x65, 0x63, 0xc8, 0xbe, 0x41, 0x87, 0xe3, 0x47, 0x25, 0xde, 0x70, 0xe0, 0x45, 0xec, 0xb9, 0xcc, 0x17, 0x90, 0x8b, 0x60, 0x4c, 0x67, 0xe5, 0x27, 0x52, 0xf0, 0xc3, 0xf8, 0xa9, 0x34, 0x1b, 0xe7, 0x29, 0x4f, 0x3f, 0xa2, 0xd8, 0x4f, 0x4f, 0x48, 0x7b, 0xd4, 0x98, 0xc6, 0xf2, 0xd4, 0xa2, 0xff, 0x3d, 0x7e, 0x13, 0xf9, 0x1f, 0xc9, 0xb4, 0x25, 0x1d, 0xa6, 0x31, 0x3c, 0x5b, 0xf4, 0xc7, 0xa6, 0x33, 0x6b, 0x59, 0xa7, 0x8e, 0x77, 0x15, 0xd1, 0x61, 0x42, 0x60, 0x24, 0x45, 0xf6, 0x49, 0x8f, 0x7f, 0x6b, 0x7f, 0x30, 0x9e, 0x6d, 0xf6, 0xf4, 0x13, 0x5e, 0xd2, 0x50, 0x2d, 0x37, 0x52, 0x8f, 0x7b, 0x84, 0xfb, 0xb7, 0x5f, 0xc8, 0x0c, 0x6a, 0x8c, 0x1f, 0xbf, 0x4d, 0x4a, 0xda, 0xcf, 0x6a, 0x87, 0xc3, 0xe9, 0xf0, 0x1b, 0xaa, 0x71, 0x54, 0xf4, 0x5b, 0x16, 0x27, 0xaf, 0x27, 0x48, 0xfb, 0xe3, 0x23, 0x02, 0xbe, 0x50, 0x92, 0x4c, 0x7e, 0xcb, 0x8f, 0x60, 0x70, 0x00, 0x30, 0xed, 0x78, 0x7f, 0xe6, 0xba, 0x46, 0x68, 0x06, 0x48, 0x08, 0x31, 0x93, 0xd9, 0x2e, 0xd6, 0xbb, 0xdd, 0xb0, 0xc9, 0x62, 0xcf, 0xb5, 0x00, 0x2b, 0xb3, 0x30, 0xd2, 0xc9, 0x64, 0x2e, 0x7f, 0xdb, 0x28, 0x9b, 0xe1, 0x42, 0x4b, 0x70, 0x58, 0x8b, 0x70, 0xa4, 0x9b, 0xae, 0x2c, 0xa2, 0x2c, 0x6f, 0xd9, 0x0b, 0xd1, 0x4e, 0x37, 0x5b, 0x23, 0x34, 0xc5, 0xc3, 0x33, 0xd7, 0x83, 0x71, 0x2f, 0xe9, 0x6c, 0xbf, 0xf5, 0x47, 0xdd, 0xb2, 0xb5, 0x6b, 0xd9, 0xcd, 0x0b, 0x34, 0x8f, 0x63, 0x3d, 0xb6, 0x8f, 0x0e, 0xf7, 0xb0, 0x35, 0xd4, 0x57, 0x56, 0x2e, 0x96, 0xeb, 0x0e, 0xf1, 0x5d, 0xb9, 0xe7, 0x4d, 0x34, 0xd1, 0x8e, 0x10, 0xad, 0x64, 0xce, 0xfd, 0x98, 0xef, 0xf6, 0x3d, 0x4e, 0x48, 0xdc, 0x3e, 0xbc, 0x10, 0xa1, 0x31, 0x60, 0x63, 0xec, 0xc9, 0x2c, 0x2f, 0x04, 0x6e, 0x07, 0x0b, 0x6d, 0xab, 0xc8, 0xfb, 0x82, 0x5b, 0x94, 0x59, 0x6e, 0xc8, 0xfa, 0x8a, 0x8e, 0xbb, 0x38, 0x1e, 0x5d, 0x33, 0x15, 0x21, 0xda, 0x5e, 0x85, 0xbd, 0x27, 0xf4, 0xf1, 0x0c, 0x21, 0xf1, 0x8c, 0x41, 0x6b, 0xd2, 0x74, 0xbc, 0x34, 0x5d, 0xcc, 0x2b, 0xa5, 0xdb, 0x3e, 0xc4, 0xb3, 0x84, 0x14, 0x8d, 0x7e, 0x95, 0xd5, 0x9f, 0x05, 0xa8, 0xef, 0x67, 0xc9, 0xe6, 0xe5, 0x8e, 0x10, 0x3a, 0x1c, 0x7f, 0x37, 0x79, 0xff, 0x10, 0x0f, 0x1f, 0xb2, 0x3c, 0xcd, 0x80, 0x36, 0xbb, 0xd8, 0xb9, 0xc6, 0x40, 0x13, 0x8d, 0xab, 0x62, 0x9f, 0x01, 0xa8, 0x47, 0xb4, 0xe3, 0x0d, 0x5a, 0xcb, 0xe0, 0xfd, 0x3c, 0x21, 0x89, 0x59, 0xe2, 0x7d, 0x61, 0xa9, 0x10, 0xda, 0xfe, 0xd9, 0x49, 0x44, 0x9f, 0xbd, 0x79, 0xea, 0xc6, 0x88, 0x96, 0x4e, 0xfb, 0x9f, 0x4f, 0xc8, 0x6b, 0xed, 0x24, 0x79, 0x1c, 0xd4, 0x79, 0x21, 0x34, 0xe1, 0x13, 0x01, 0x49, 0xe7, 0xdd, 0xee, 0x68, 0x07, 0x08, 0x1d, 0x8f, 0x72, 0xbd, 0x7b, 0xcb, 0x57, 0x1f, 0x38, 0xbd, 0x1f, 0x0b, 0x73, 0x89, 0xd1, 0x45, 0x14, 0xeb, 0xb7, 0xdd, 0x43, 0xf4, 0x90, 0x10, 0x26, 0x86, 0xb1, 0xde, 0xcc, 0xe5, 0xb1, 0x49, 0x68, 0xf4, 0x35, 0x1c, 0x7b, 0x71, 0xd9, 0x95, 0x16, 0x5f, 0xe2, 0xb3, 0xb6, 0xaf, 0xef, 0x26, 0x71, 0x56, 0x7a, 0xad, 0x1e, 0x47, 0x7a, 0x19, 0x3b, 0x9c, 0x8f, 0x30, 0x4a, 0xd7, 0x00, 0x9d, 0x78, 0xb5, 0xdb, 0x3a, 0xb6, 0xe1, 0xf3, 0xca, 0xc1, 0x25, 0xdf, 0xed, 0xab, 0x78, 0x35, 0x3f, 0x18, 0xe9, 0xde, 0x78, 0x9d, 0xfc, 0x2e, 0x59, 0x9f, 0x7d, 0x30, 0xa1, 0x68, 0xef, 0x85, 0x26, 0x3c, 0x85, 0x08, 0xc3, 0xf8, 0x2a, 0xa1, 0x47, 0x78, 0x16, 0x5e, 0xad, 0x65, 0xe3, 0x1e, 0xde, 0x74, 0x82, 0xe1, 0xd1, 0x31, 0xa4, 0x48, 0x0a, 0x3b, 0x25, 0x3e, 0x24, 0xa5, 0xa6, 0x0e, 0x7f, 0x7f, 0x78, 0xc5, 0xb6, 0xf6, 0x84, 0xd4, 0x1d, 0xa0, 0x31, 0xcc, 0x03, 0xcb, 0x51, 0x79, 0xd6, 0x1e, 0xd0, 0x45, 0xe9, 0x56, 0x1f, 0xdb, 0xfd, 0xc9, 0xd4, 0x3f, 0x76, 0x1b, 0xde, 0x58, 0x38, 0x77, 0x0f, 0xde, 0xdd, 0x21, 0x9d, 0xce, 0x75, 0x3d, 0x85, 0xe9, 0x0f, 0xe6, 0xe7, 0xbd, 0xfa, 0x70, 0x91, 0xc8, 0x20, 0xf2, 0x80, 0xac, 0x72, 0xac, 0xe5, 0x67, 0x6a, 0x66, 0x26, 0x4e, 0x68, 0xae, 0x10, 0x1a, 0x5d, 0x88, 0x73, 0x40, 0x3e, 0x5c, 0x08, 0xcb, 0xc6, 0xf6, 0xc0, 0x3c, 0x68, 0x83, 0xd0, 0xde, 0x74, 0x63, 0xd8, 0x2b, 0x40, 0x95, 0x96, 0x59, 0xe7, 0xe0, 0x51, 0x1c, 0xe6, 0x9e, 0xd3, 0xd9, 0xab, 0x77, 0x62, 0xf6, 0x9d, 0xc5, 0x7b, 0x1c, 0xa5, 0xbf, 0xf0, 0x09, 0x32, 0x6d, 0x9d, 0xb8, 0x13, 0xc7, 0x63, 0x3a, 0xcc, 0x0b, 0x17, 0x86, 0x45, 0x56, 0x69, 0xac, 0xac, 0x65, 0x6a, 0xd9, 0xa9, 0xf6, 0x09, 0x4d, 0x19, 0x75, 0xb6, 0xa9, 0xeb, 0x52, 0x4a, 0x03, 0x4c, 0x4d, 0x96, 0xfd, 0x38, 0x90, 0xe6, 0x76, 0x22, 0x00, 0x07, 0x95, 0x90, 0x82, 0x33, 0xa0, 0xc2, 0x7d, 0x6a, 0x4c, 0x4f, 0xb4, 0x6b, 0x1a, 0x63, 0x38, 0x6f, 0x6c, 0x55, 0x75, 0x9e, 0x8a, 0x56, 0xa6, 0xfa, 0x60, 0x08, 0xaa, 0x6d, 0x4c, 0xc5, 0x67, 0x57, 0xc6, 0x62, 0x5d, 0x2f, 0x89, 0x2d, 0x9d, 0x6d, 0xc2, 0x61, 0x75, 0x56, 0x63, 0x59, 0x48, 0x77, 0xed, 0x78, 0x95, 0x26, 0xf1, 0x84, 0x90, 0x9d, 0x06, 0xe1, 0x22, 0x70, 0x38, 0x91, 0xa9, 0x56, 0x65, 0x65, 0xb4, 0xbb, 0xb6, 0xb8, 0x4b, 0xe8, 0xa3, 0x29, 0xd9, 0x07, 0x61, 0x4c, 0x95, 0x65, 0xa5, 0xb5, 0xce, 0x64, 0x79, 0x70, 0xe0, 0x75, 0x02, 0x2b, 0x84, 0x26, 0xd6, 0xe2, 0x1a, 0x29, 0x0b, 0xd7, 0x20, 0xdd, 0x01, 0x9e, 0xc9, 0x8a, 0xaa, 0x5f, 0x57, 0x19, 0x2f, 0x46, 0x56, 0x75, 0x99, 0x35, 0xb2, 0xc0, 0xa3, 0x63, 0x38, 0x07, 0x59, 0x82, 0xd9, 0x91, 0x58, 0x22, 0xb7, 0x35, 0x4e, 0xc3, 0xba, 0x53, 0x58, 0xe1, 0x53, 0xad, 0x22, 0xf1, 0xb7, 0x21, 0xd0, 0xa4, 0x88, 0xe2, 0xa6, 0x2a, 0xa8, 0xfa, 0x0c, 0x28, 0xcb, 0x64, 0xca, 0x25, 0xe1, 0x22, 0x35, 0x8c, 0xd8, 0xae, 0xa3, 0x29, 0x11, 0xfc, 0x27, 0x4c, 0xc8, 0x57, 0xe4, 0x18, 0xa1, 0x81, 0x11, 0xc2, 0xa9, 0xca, 0xac, 0xfc, 0xcb, 0x5a, 0x78, 0xb5, 0x2a, 0x10, 0x1a, 0x7d, 0xe6, 0x7b, 0x84, 0xfa, 0x89, 0xeb, 0x80, 0xa8, 0x7f, 0xff, 0x66, 0xff, 0xb2, 0xd8, 0x25, 0x23, 0x0e, 0x51, 0x30, 0xb2, 0x18, 0xcf, 0x8e, 0x40, 0xfc, 0x6b, 0x79, 0xa5, 0xad, 0x68, 0x99, 0x10, 0x1a, 0x46, 0xfd, 0x82, 0x9d, 0x21, 0x4c, 0x80, 0x17, 0xe4, 0x34, 0x56, 0xcb, 0x4b, 0x48, 0x04, 0x56, 0x6a, 0x33, 0xa7, 0x34, 0xc3, 0xc4, 0xaf, 0x2b, 0x2c, 0x4d, 0x39, 0x76, 0xfb, 0x52, 0x87, 0x34, 0xb9, 0xe6, 0xac, 0x06, 0x38, 0x58, 0x7b, 0x57, 0x30, 0x16, 0xe3, 0x70, 0xa1, 0x79, 0x8b, 0x0e, 0x10, 0x9a, 0xc8, 0x91, 0xf8, 0x4d, 0x5b, 0x22, 0xd4, 0x62, 0xd5, 0x1d, 0x8b, 0xd1, 0xe2, 0x2b, 0xbb, 0x4f, 0x39, 0x28, 0x0b, 0x45, 0xfd, 0x7a, 0x9e, 0xdf, 0x78, 0xd4, 0x2f, 0x64, 0x0d, 0x1a, 0xc5, 0xeb, 0x8b, 0xc9, 0x22, 0x96, 0x27, 0x24, 0x51, 0x23, 0x21, 0x5c, 0x5f, 0xec, 0x3e, 0x15, 0x3f, 0x01, 0x96, 0xdf, 0x4a, 0x27, 0x22, 0xa4, 0xf2, 0xda, 0xc0, 0x53, 0x9a, 0xf4, 0x67, 0x8b, 0xe3, 0x3f, 0xb2, 0x19, 0xaf, 0xd8, 0x87, 0x2c, 0x4e, 0xdc, 0xcf, 0x12, 0xac, 0x86, 0x43, 0xa0, 0x38, 0x4e, 0x09, 0x7e, 0x87, 0x48, 0x37, 0xa5, 0x57, 0x61, 0x1c, 0x0c, 0xd1, 0xf6, 0xc1, 0x10, 0x31, 0x07, 0x81, 0x68, 0x2f, 0xf9, 0xbb, 0x93, 0x98, 0x05, 0x21, 0x4f, 0xa9, 0x1b, 0x64, 0x88, 0xfe, 0x2d, 0x5a, 0xcb, 0xde, 0x44, 0xb2, 0x89, 0xfa, 0x71, 0xa7, 0x5f, 0x60, 0xf4, 0x4b, 0x67, 0x43, 0xc2, 0x45, 0x1f, 0xf2, 0xe6, 0xd7, 0x11, 0x07, 0x1d, 0x9c, 0x0c, 0x9f, 0x47, 0x73, 0xa0, 0x27, 0xa4, 0x46, 0x42, 0x4d, 0x4f, 0x08, 0xc1, 0x65, 0x31, 0x87, 0xb4, 0xd1, 0x34, 0xd5, 0x64, 0x8e, 0xe5, 0x6a, 0x24, 0x84, 0xf5, 0x26, 0x76, 0x4f, 0x91, 0xec, 0xc0, 0xc5, 0x03, 0xaf, 0x0b, 0x96, 0xe9, 0x74, 0x54, 0x15, 0xa5, 0xf8, 0x80, 0x3c, 0x21, 0x24, 0x16, 0xf1, 0x0f, 0x83, 0xc3, 0x6b, 0xac, 0x38, 0x3a, 0x41, 0x08, 0xae, 0x10, 0x64, 0xf5, 0x49, 0x06, 0x0b, 0x69, 0x93, 0xd0, 0xdb, 0x3c, 0x69, 0x32, 0x33, 0x12, 0x42, 0x00, 0x64, 0x73, 0xf4, 0xb2, 0xda, 0x3f, 0x39, 0xad, 0x9c, 0xed, 0x14, 0xb7, 0x3a, 0x74, 0x96, 0x64, 0x1b, 0x8b, 0xe9, 0x74, 0x6f, 0xfe, 0x08, 0x3e, 0xf4, 0xad, 0x0d, 0x25, 0x34, 0x0c, 0x50, 0xf0, 0xe7, 0x87, 0xb0, 0xe1, 0xf3, 0x79, 0xae, 0x1f, 0xf3, 0xe9, 0x82, 0x28, 0x8d, 0x3d, 0x21, 0x78, 0x21, 0x42, 0x4c, 0x76, 0x85, 0x50, 0xec, 0xa7, 0xef, 0xa4, 0x7a, 0x33, 0x13, 0xc0, 0xd1, 0x8c, 0x2e, 0xc1, 0x67, 0x37, 0xdc, 0xe1, 0x52, 0xf6, 0x4a, 0x35, 0x70, 0x34, 0x97, 0xfc, 0x13, 0x4c, 0x8e, 0x6c, 0xd6, 0x4a, 0x8c, 0x99, 0x27, 0x14, 0x8f, 0x99, 0x44, 0x52, 0xef, 0xa6, 0x65, 0x2f, 0x9d, 0x66, 0x5c, 0x4e, 0x82, 0x2f, 0xa8, 0x49, 0x61, 0xbc, 0x83, 0x48, 0x22, 0xac, 0x86, 0x58, 0xaa, 0x1e, 0x91, 0x73, 0x4c, 0xc8, 0x41, 0x03, 0xb5, 0xd6, 0xc1, 0xa9, 0x0b, 0xbd, 0x68, 0xca, 0x92, 0x54, 0x21, 0xf5, 0x02, 0x47, 0x03, 0x10, 0x3e, 0xc5, 0xf0, 0x9f, 0x95, 0xf8, 0x16, 0xc2, 0xdd, 0xf9, 0xe3, 0x1c, 0xf9, 0xa4, 0xe5, 0x48, 0xce, 0x0f, 0x35, 0x6c, 0xfc, 0x2f, 0x95, 0x42, 0xbc, 0x0b, 0x02, 0x32, 0x58, 0x3d, 0xa4, 0xac, 0x59, 0x82, 0xc0, 0x35, 0x45, 0xe1, 0xd8, 0xd1, 0x8f, 0xe6, 0x22, 0xde, 0xa8, 0xd7, 0x2c, 0x65, 0x11, 0x70, 0x65, 0x14, 0x27, 0xba, 0xa2, 0xb3, 0x1b, 0xe9, 0x1c, 0x5a, 0xba, 0x59, 0x18, 0x14, 0x25, 0x1d, 0x86, 0xe7, 0x59, 0x85, 0x45, 0x89, 0x40, 0x0b, 0xac, 0x8f, 0x83, 0x10, 0x5f, 0xa5, 0x8f, 0xad, 0x4c, 0x9d, 0x61, 0x4f, 0x54, 0x5d, 0x57, 0x79, 0x15, 0xc7, 0x12, 0xaf, 0x45, 0x04, 0xbb, 0x1a, 0xb5, 0xa9, 0xd4, 0xde, 0xfa, 0xd0, 0xdb, 0xca, 0xe6, 0x2a, 0x12, 0xa2, 0x12, 0x8e, 0x28, 0x78, 0xe0, 0x31, 0xda, 0x97, 0xde, 0xd1, 0x5b, 0xfc, 0x77, 0xc0, 0xc1, 0x01, 0x8e, 0xb4, 0x64, 0x80, 0xb8, 0x3d, 0xe2, 0x56, 0xc1, 0x81, 0xca, 0xa1, 0x1d, 0x41, 0x51, 0x58, 0xd7, 0xd5, 0x34, 0x9a, 0x08, 0xa1, 0x3a, 0xb3, 0x41, 0xc7, 0xce, 0xd1, 0xae, 0x80, 0x4f, 0x1a, 0x2e, 0x61, 0x44, 0x42, 0x04, 0x70, 0x15, 0x76, 0x9e, 0x10, 0xc2, 0x16, 0xca, 0x22, 0x2f, 0x5b, 0xdf, 0x79, 0x9a, 0xcc, 0x17, 0xe5, 0xc3, 0x44, 0x2d, 0xdf, 0x89, 0xda, 0xe3, 0x09, 0x79, 0xf5, 0x91, 0x30, 0x21, 0xb8, 0x53, 0x4b, 0x38, 0x71, 0x6d, 0x08, 0x8f, 0xbd, 0x73, 0x05, 0x1c, 0x07, 0x71, 0x6c, 0xfb, 0xab, 0xb4, 0xfd, 0x20, 0xd2, 0xc1, 0x64, 0xa1, 0x3e, 0x41, 0x4f, 0xbb, 0x66, 0xbf, 0x31, 0x3c, 0xfe, 0x71, 0x9b, 0xd5, 0x01, 0x3a, 0xb8, 0x79, 0x24, 0x43, 0xcb, 0xb0, 0x45, 0x22, 0x44, 0x6c, 0x10, 0x04, 0x65, 0xd9, 0x64, 0x2c, 0xb9, 0x06, 0xf4, 0xff, 0x01, 0x24, 0xde, 0x90, 0x05, 0x59, 0xfc, 0xf8, 0xf1, 0xa3, 0x45, 0xec, 0x10, 0x9e, 0xaa, 0xfb, 0xe1, 0x4c, 0x8b, 0xf8, 0x15, 0x1b, 0xa0, 0x7b, 0xc2, 0x93, 0x85, 0xf1, 0x02, 0x47, 0x48, 0x0a, 0x5b, 0xfc, 0xbe, 0x0d, 0x3a, 0x0b, 0x69, 0x61, 0x37, 0x7a, 0x57, 0x11, 0x45, 0xef, 0xd7, 0x41, 0x30, 0x19, 0xdc, 0xd2, 0x21, 0x0b, 0x09, 0xe2, 0x13, 0xd1, 0x8e, 0xd6, 0x76, 0xe8, 0xa8, 0x88, 0x31, 0x86, 0x43, 0x0d, 0xde, 0xfc, 0x08, 0xd9, 0xbb, 0x59, 0xc1, 0x26, 0x03, 0xa1, 0x06, 0xcf, 0xa6, 0x0e, 0x1c, 0xc7, 0xa2, 0xb8, 0xcc, 0x4c, 0x09, 0x35, 0x7c, 0x15, 0x3d, 0xf6, 0xb2, 0x96, 0x4c, 0x96, 0x20, 0x44, 0x1c, 0x41, 0xc4, 0xc1, 0x20, 0x21, 0x1c, 0xb3, 0x45, 0xc8, 0x71, 0x04, 0x6e, 0x4f, 0x86, 0xa6, 0x2f, 0xcc, 0xee, 0x11, 0xa9, 0x90, 0x6e, 0x55, 0x73, 0x4c, 0x30, 0x09, 0x6f, 0x17, 0x78, 0x36, 0x39, 0xb2, 0x4f, 0x95, 0x2d, 0x5d, 0xd4, 0xb6, 0xce, 0xb6, 0x06, 0x11, 0x8a, 0x21, 0xfc, 0xac, 0x86, 0x0e, 0x3b, 0xb0, 0xa3, 0xe3, 0x05, 0x4b, 0x83, 0x1f, 0x7a, 0x4b, 0x1c, 0xc1, 0x33, 0xa6, 0x23, 0x79, 0xd9, 0x74, 0x8e, 0x6c, 0x7e, 0x53, 0xc1, 0xbb, 0xa7, 0x7b, 0x42, 0x88, 0x23, 0x61, 0xa5, 0x8b, 0x8c, 0x7a, 0x20, 0x54, 0x04, 0x2a, 0x96, 0x00, 0x34, 0x8e, 0xa6, 0x6b, 0x10, 0xc1, 0x46, 0xda, 0x10, 0xd3, 0x29, 0x9c, 0x6d, 0x99, 0x90, 0x5c, 0x9e, 0x6b, 0x65, 0x10, 0x85, 0x4a, 0x8f, 0xc0, 0x14, 0xa4, 0x3e, 0x73, 0x26, 0x04, 0x7a, 0x96, 0x7d, 0x2c, 0x13, 0x3d, 0x64, 0xd9, 0x73, 0x4e, 0x84, 0xa8, 0x3f, 0x44, 0xec, 0xf6, 0x26, 0x4b, 0xa1, 0x34, 0x75, 0x91, 0xd7, 0x43, 0x6e, 0xbe, 0x75, 0x42, 0x3e, 0xf2, 0x6f, 0xba, 0xf6, 0x15, 0x85, 0xf0, 0x7e, 0x07, 0x12, 0x7e, 0x18, 0x21, 0xec, 0x86, 0x54, 0x52, 0x93, 0x15, 0xb5, 0x69, 0x48, 0x98, 0x82, 0x2e, 0xb0, 0x6c, 0x72, 0x67, 0x65, 0x47, 0xed, 0xe7, 0xe8, 0x8d, 0xda, 0xca, 0xf1, 0xd6, 0x85, 0x92, 0x94, 0x0a, 0xa3, 0x0d, 0xf7, 0x98, 0xda, 0x52, 0x03, 0x72, 0x3e, 0x12, 0x82, 0x15, 0x3e, 0xa9, 0x7e, 0xa3, 0x52, 0x82, 0x41, 0xa5, 0x09, 0xa8, 0x61, 0x25, 0x0f, 0xc4, 0x31, 0x22, 0x8a, 0x82, 0x21, 0x74, 0xb6, 0xe3, 0x90, 0xe0, 0x18, 0x21, 0x5d, 0x19, 0x99, 0xac, 0x71, 0x14, 0xf4, 0xd1, 0xc6, 0x09, 0x5b, 0xc5, 0x54, 0x93, 0x16, 0xea, 0x1a, 0xa6, 0x82, 0x23, 0x48, 0xce, 0x21, 0xf0, 0x83, 0x34, 0x75, 0xeb, 0xf5, 0xfd, 0xf8, 0x5e, 0x19, 0xc8, 0x50, 0x19, 0xb2, 0x7b, 0x4a, 0x82, 0x4b, 0x48, 0xea, 0x68, 0xba, 0x59, 0xa2, 0x45, 0xd1, 0xb8, 0xaf, 0x6c, 0x9d, 0xd0, 0xbd, 0xe5, 0x17, 0x63, 0xae, 0x6a, 0x42, 0x04, 0x23, 0xf5, 0x11, 0xa4, 0x30, 0x1b, 0xa1, 0xbb, 0xc1, 0xa4, 0xe3, 0x90, 0x37, 0x1a, 0xe1, 0x4a, 0x87, 0xbf, 0x1d, 0x4c, 0xdc, 0xc2, 0x22, 0x14, 0x94, 0xc4, 0x58, 0x49, 0xf8, 0x01, 0x8c, 0x88, 0xd0, 0x35, 0x30, 0x82, 0xa9, 0xfb, 0x15, 0x2e, 0xe0, 0x08, 0x2c, 0x0e, 0xf4, 0xe1, 0xa8, 0x87, 0x74, 0x4a, 0x28, 0x60, 0xad, 0xcb, 0x2e, 0x48, 0xb4, 0x5b, 0x1e, 0x4c, 0x6b, 0xe9, 0x0f, 0x17, 0xf9, 0x18, 0x5a, 0x92, 0x8d, 0x08, 0xc8, 0x58, 0xd8, 0x90, 0x0e, 0x99, 0x63, 0x25, 0x10, 0x15, 0x81, 0x06, 0xc3, 0xf6, 0xa7, 0x3e, 0x14, 0x08, 0x21, 0x1e, 0x11, 0x1d, 0x67, 0xc6, 0xa4, 0xa1, 0xaa, 0x0f, 0x61, 0x68, 0xd9, 0x98, 0xf4, 0x84, 0x1a, 0xe9, 0x97, 0x10, 0x40, 0xc5, 0x59, 0xc3, 0x36, 0x57, 0x76, 0xd7, 0x27, 0xec, 0x1a, 0x0f, 0xab, 0x91, 0xd7, 0x05, 0xbd, 0xbc, 0x44, 0x42, 0x08, 0x41, 0x63, 0x44, 0x08, 0xc3, 0x36, 0xb5, 0xf3, 0x2f, 0x04, 0xc1, 0x94, 0xac, 0x67, 0xdc, 0x5f, 0x0e, 0x61, 0x31, 0xf8, 0x0f, 0x80, 0xbc, 0x09, 0x92, 0xa8, 0x06, 0x4a, 0x96, 0xc4, 0x03, 0x86, 0x39, 0x8e, 0x20, 0xcf, 0x53, 0xc6, 0xc1, 0x6a, 0x34, 0xf8, 0xf7, 0xa3, 0x7d, 0xd7, 0x07, 0x0c, 0x21, 0x60, 0x82, 0x67, 0x03, 0x95, 0x04, 0x22, 0x2b, 0xd2, 0x24, 0x1c, 0x31, 0x06, 0x37, 0x3e, 0x22, 0xaf, 0xbc, 0x25, 0x88, 0x70, 0x2c, 0xea, 0x80, 0xc4, 0xc2, 0xc2, 0x0f, 0x5e, 0x70, 0x38, 0x1f, 0x34, 0x73, 0x83, 0xe1, 0x2c, 0x67, 0x92, 0x11, 0xa6, 0x49, 0x8a, 0x05, 0xb1, 0xa9, 0x46, 0x42, 0x06, 0x87, 0xb8, 0x97, 0x81, 0x10, 0x6b, 0x56, 0x9a, 0xca, 0xa2, 0x36, 0x24, 0x9f, 0x9d, 0x5a, 0xd7, 0xd6, 0xbf, 0xaf, 0x12, 0x7a, 0xe1, 0x30, 0x15, 0xed, 0x6d, 0x66, 0x58, 0x21, 0x75, 0x63, 0x2c, 0x86, 0xc5, 0xb6, 0xad, 0x49, 0x7c, 0xe0, 0x0a, 0x23, 0x65, 0x42, 0x8d, 0x73, 0x88, 0xf3, 0xb0, 0x44, 0x88, 0xa4, 0xc4, 0x70, 0x67, 0x33, 0xb9, 0x95, 0xc6, 0x43, 0x17, 0x74, 0x1d, 0x47, 0xf5, 0x54, 0x38, 0x62, 0x4d, 0xd1, 0xb2, 0x6e, 0x6a, 0x5d, 0x5b, 0x54, 0x9e, 0x90, 0xee, 0x7c, 0x14, 0x0b, 0xb7, 0xd1, 0xb6, 0x40, 0xd1, 0x98, 0x86, 0xe6, 0x5e, 0xf4, 0xac, 0xe9, 0xfa, 0xd2, 0x4b, 0x12, 0xf6, 0xbe, 0xd9, 0xb6, 0x63, 0xfd, 0x42, 0xc2, 0xd8, 0x62, 0x5c, 0x84, 0xe2, 0x74, 0x6c, 0x46, 0xc5, 0xd4, 0x6e, 0x84, 0xbd, 0xc1, 0x5b, 0x0d, 0x33, 0x8e, 0x26, 0x18, 0x79, 0xc3, 0x97, 0x31, 0x3e, 0x88, 0xf4, 0xa6, 0x39, 0xfe, 0x95, 0x84, 0x8a, 0xce, 0xb4, 0x0d, 0x1e, 0x69, 0xdb, 0xb4, 0x8e, 0x6f, 0x6a, 0xab, 0x42, 0xd1, 0xd7, 0x18, 0x3d, 0x8f, 0x13, 0x42, 0x98, 0x8a, 0xf5, 0x2b, 0x42, 0x2f, 0x3c, 0xc0, 0x94, 0x34, 0x50, 0x70, 0x68, 0x69, 0x49, 0xcd, 0x46, 0xb0, 0x09, 0x19, 0x92, 0x32, 0xbd, 0xcd, 0xb2, 0xbc, 0xc8, 0x2b, 0x84, 0xeb, 0x73, 0x0c, 0x4a, 0xd9, 0xcf, 0xc9, 0x63, 0x7f, 0x24, 0x44, 0x38, 0x86, 0xfc, 0x32, 0x8e, 0x24, 0x8d, 0x60, 0xd1, 0xf4, 0x53, 0xa2, 0x04, 0x86, 0x09, 0x5b, 0xb9, 0xb8, 0x1a, 0xe9, 0xcc, 0x22, 0x97, 0xdd, 0x23, 0x50, 0xee, 0x34, 0xfd, 0xf6, 0xc1, 0xd0, 0x5c, 0x05, 0xc4, 0x00, 0xd1, 0x05, 0xa9, 0x64, 0x79, 0xe5, 0x44, 0x9b, 0x37, 0xd8, 0xa7, 0x41, 0xa4, 0x2c, 0xd9, 0x14, 0x12, 0xa0, 0x88, 0x47, 0x10, 0x57, 0xd8, 0x83, 0x52, 0x70, 0x48, 0x8c, 0x1f, 0x34, 0x61, 0x7c, 0x60, 0x03, 0x57, 0xd2, 0xa2, 0xd2, 0x21, 0xe6, 0x27, 0x95, 0x8b, 0x70, 0x53, 0x84, 0xc0, 0xf1, 0x96, 0x98, 0xf5, 0xcd, 0x1d, 0x1b, 0x84, 0x10, 0x8b, 0x37, 0xac, 0x2a, 0xbe, 0xc4, 0x21, 0x7a, 0x17, 0x56, 0x8d, 0x5a, 0xe8, 0x5b, 0x44, 0xe3, 0xb5, 0xbc, 0x72, 0x44, 0x1d, 0x0e, 0x7f, 0x58, 0xdb, 0x49, 0x2c, 0x83, 0x72, 0x2e, 0xd4, 0x63, 0x9c, 0x01, 0x1b, 0xdb, 0x12, 0xba, 0x1f, 0xd2, 0x91, 0x58, 0xc2, 0x1f, 0x3a, 0xd7, 0xc5, 0xe3, 0x02, 0x98, 0x96, 0xed, 0x20, 0x48, 0x22, 0xd8, 0x51, 0x23, 0x23, 0xb1, 0x7a, 0x11, 0x12, 0x49, 0x7f, 0x47, 0xe3, 0x60, 0x8d, 0xc5, 0x41, 0x0e, 0x95, 0x40, 0x3d, 0xc2, 0xde, 0xe6, 0x4e, 0x95, 0xdf, 0x3e, 0xa1, 0x42, 0xce, 0x9d, 0x9a, 0x68, 0x9c, 0xa6, 0x63, 0xbf, 0x2f, 0x07, 0x57, 0xf1, 0x8c, 0xb1, 0xa5, 0x03, 0xd9, 0x1d, 0x49, 0xf3, 0xa1, 0x4f, 0x91, 0xa6, 0x55, 0x7d, 0x6d, 0x22, 0x59, 0x73, 0xdc, 0x70, 0x99, 0xad, 0x13, 0xba, 0xcd, 0x12, 0x71, 0x23, 0xff, 0xd0, 0x10, 0xf8, 0xc2, 0xa1, 0x2f, 0xfd, 0x7f, 0x63, 0x9c, 0x47, 0xd4, 0xe7, 0x7f, 0x9f, 0x44, 0xba, 0x8c, 0x4b, 0x21, 0x43, 0xdc, 0xfc, 0x30, 0x83, 0xe5, 0x33, 0xc6, 0x97, 0x44, 0x40, 0x9a, 0xfa, 0x81, 0x74, 0x30, 0x3d, 0x74, 0x32, 0x5d, 0x1a, 0x1b, 0x0e, 0xcf, 0x17, 0x19, 0x87, 0xf0, 0x17, 0x6f, 0x3e, 0xcc, 0x2f, 0x33, 0xac, 0x4a, 0x43, 0xaf, 0x15, 0x32, 0xc8, 0xce, 0xc6, 0x6d, 0x3e, 0x65, 0x32, 0x9b, 0x3e, 0x4c, 0x88, 0x19, 0x0d, 0x9f, 0xd3, 0xf9, 0xdc, 0x76, 0xf1, 0xde, 0xdf, 0x68, 0xb2, 0x91, 0x63, 0xbe, 0xe2, 0x3c, 0xf9, 0x33, 0x1d, 0x1a, 0x35, 0xf9, 0x72, 0xb1, 0xb0, 0xcc, 0x07, 0xa6, 0x4b, 0x4b, 0xfd, 0xf0, 0xd2, 0x1f, 0xee, 0x7f, 0xbd, 0x58, 0xd5, 0x4e, 0x27, 0x83, 0xfa, 0x40, 0x7c, 0x76, 0x1d, 0x09, 0x17, 0x59, 0xbe, 0xa7, 0x68, 0xbc, 0xe9, 0x58, 0x4e, 0x10, 0x9a, 0x5d, 0x65, 0xfe, 0xd6, 0xf1, 0xbb, 0xad, 0x43, 0x43, 0x3c, 0xd3, 0xb9, 0xf2, 0x7a, 0x68, 0x9d, 0xfe, 0x75, 0xf3, 0xb4, 0xe1, 0xeb, 0xfd, 0x17, 0x0e, 0xd2, 0xf3, 0x45, 0xfd, 0xcb, 0x4c, 0x3d, 0x7e, 0x2f, 0xe1, 0x3d, 0xa0, 0x63, 0x84, 0x7c, 0x66, 0x95, 0xc5, 0xfb, 0x04, 0x46, 0x39, 0x4e, 0x26, 0x11, 0x4d, 0x27, 0xca, 0xb2, 0x2e, 0xe3, 0x57, 0x6b, 0x67, 0xdc, 0x66, 0x48, 0x67, 0x07, 0xd2, 0x64, 0xf6, 0xd3, 0xd7, 0xf9, 0x55, 0x62, 0xb2, 0xda, 0x69, 0x9e, 0x59, 0x84, 0x6b, 0x13, 0xf8, 0x2d, 0x2e, 0x17, 0x09, 0x2d, 0x05, 0x67, 0x06, 0xe8, 0x34, 0xa1, 0xdd, 0x7a, 0x9d, 0x29, 0xb7, 0x9d, 0x3c, 0xf8, 0x74, 0x9f, 0x04, 0xa6, 0x7d, 0x5e, 0x5f, 0x8d, 0xa7, 0x79, 0x92, 0x50, 0xf2, 0x3d, 0x08, 0x6d, 0x5f, 0x0d, 0x95, 0xa7, 0xc9, 0x1f, 0xc6, 0xab, 0x8b, 0xe1, 0x22, 0x97, 0x09, 0xe9, 0x67, 0x09, 0x3d, 0x05, 0x65, 0x49, 0x61, 0xf3, 0x6a, 0x5c, 0xfb, 0x99, 0xe3, 0xec, 0xdc, 0xa5, 0x5f, 0x6f, 0x17, 0x09, 0xe9, 0x39, 0xa0, 0x64, 0x65, 0x91, 0xfe, 0xeb, 0x08, 0xed, 0xb7, 0x91, 0xeb, 0xbf, 0x39, 0x2f, 0x7d, 0xfc, 0xe3, 0xa7, 0x08, 0x4d, 0x01, 0x9d, 0x25, 0xf4, 0x33, 0x68, 0x6c, 0xb6, 0xf2, 0xea, 0x3d, 0x69, 0x1c, 0x3c, 0x4a, 0x68, 0xb9, 0xca, 0xf6, 0x0b, 0x11, 0x7a, 0xbd, 0xfe, 0x3a, 0x0a, 0x6c, 0x64, 0xd3, 0xd7, 0x08, 0xcd, 0xf9, 0x24, 0xab, 0xae, 0xb0, 0x8d, 0x2b, 0x5d, 0x15, 0xf8, 0x7f, 0xa0, 0x50, 0x6d, 0xc9, 0xfa, 0x5b, 0x12, 0x5a, 0x5f, 0x0d, 0x59, 0x66, 0xe2, 0xbb, 0x4c, 0x68, 0xe2, 0x65, 0x7f, 0x54, 0xbb, 0xa7, 0x30, 0xce, 0x6e, 0x7a, 0xf9, 0x12, 0x2f, 0xf1, 0x8c, 0xd0, 0xc2, 0x4f, 0xb8, 0x24, 0xa4, 0x1f, 0x01, 0x3a, 0x42, 0x08, 0x73, 0xf4, 0xd2, 0x1e, 0x20, 0xf4, 0x5c, 0xeb, 0xee, 0xee, 0x7b, 0xed, 0x1a, 0x9a, 0x67, 0x10, 0x03, 0x21, 0x16, 0x93, 0xc1, 0x61, 0xb8, 0x47, 0x68, 0x15, 0xd0, 0x51, 0x42, 0xd8, 0xf6, 0xf5, 0x38, 0x51, 0xc7, 0x53, 0x6d, 0xdb, 0x0e, 0xc6, 0x3b, 0x77, 0x11, 0xde, 0x79, 0x96, 0x26, 0x3d, 0x21, 0x5e, 0x6c, 0xed, 0x9c, 0xb5, 0x58, 0xbc, 0xd4, 0x4b, 0x44, 0x53, 0x42, 0xeb, 0x80, 0x8e, 0x11, 0xe2, 0x9d, 0x73, 0xed, 0xfe, 0x8e, 0x37, 0xdf, 0xc6, 0x27, 0xde, 0x39, 0x36, 0xfe, 0x1a, 0xc6, 0xf6, 0x35, 0x4b, 0x9a, 0x25, 0x08, 0x0b, 0x46, 0xc3, 0x58, 0x16, 0x63, 0xc7, 0x6a, 0xc6, 0xbe, 0xc1, 0xa2, 0x09, 0x97, 0x59, 0x60, 0xd2, 0x64, 0x70, 0x84, 0x2c, 0x2d, 0xa1, 0xe5, 0xfb, 0x00, 0x1e, 0x10, 0xb2, 0xde, 0xc1, 0xfc, 0xb0, 0x89, 0x89, 0xb3, 0xbb, 0x89, 0x02, 0x76, 0x7f, 0x9e, 0x26, 0x61, 0x1b, 0x62, 0xa1, 0x00, 0xe9, 0x86, 0xce, 0x83, 0xe6, 0x26, 0x0b, 0xa0, 0xd1, 0xa6, 0xd6, 0x70, 0x93, 0xf0, 0x66, 0x5d, 0xde, 0x2d, 0xbb, 0x40, 0x34, 0x02, 0x9a, 0x40, 0xd2, 0x53, 0x40, 0xc7, 0x6c, 0xea, 0x04, 0xab, 0xa9, 0xdd, 0x81, 0xd4, 0x28, 0x9c, 0xe6, 0x43, 0xdd, 0xbf, 0x54, 0xf9, 0xf5, 0x58, 0xd8, 0x5e, 0x12, 0x17, 0x59, 0x4b, 0x75, 0x7b, 0xa3, 0xdb, 0x7d, 0x1c, 0x36, 0xf6, 0x27, 0x80, 0x78, 0x87, 0xb0, 0x84, 0xec, 0xf5, 0x84, 0xa8, 0x93, 0x19, 0x21, 0x04, 0xcf, 0x8b, 0x8b, 0xf5, 0x6c, 0xd7, 0x8f, 0xc7, 0xb3, 0x5c, 0xfb, 0xe8, 0x57, 0x9f, 0x7a, 0x69, 0x3a, 0x50, 0x73, 0x4e, 0x6b, 0xf1, 0x78, 0xad, 0x04, 0xdd, 0x51, 0x32, 0x73, 0x2c, 0x5a, 0x70, 0xa8, 0xa9, 0xf0, 0xf6, 0xa3, 0x0d, 0x49, 0x12, 0x56, 0xf5, 0x5a, 0x46, 0x9b, 0x8d, 0xd2, 0x13, 0x4a, 0xa4, 0xa1, 0xb7, 0x19, 0xa1, 0x34, 0xe6, 0xb7, 0xcd, 0xc0, 0x63, 0xd3, 0x88, 0x3f, 0x81, 0xa3, 0x16, 0x92, 0x44, 0x00, 0x45, 0x3e, 0xa1, 0xc7, 0xb8, 0x74, 0xa6, 0x26, 0x41, 0x1e, 0xfe, 0xcd, 0x24, 0xc3, 0xe6, 0xae, 0x49, 0x19, 0xf5, 0x01, 0x0a, 0xf6, 0x16, 0x96, 0xca, 0x87, 0xea, 0xf3, 0xf7, 0x63, 0x5f, 0x9d, 0x9e, 0xc7, 0xfe, 0xc2, 0x5a, 0x45, 0xc9, 0xb0, 0x26, 0xe8, 0x67, 0x55, 0x72, 0x74, 0x58, 0x8e, 0x9b, 0x6c, 0xca, 0x92, 0xb5, 0x4c, 0xf9, 0x8b, 0x7d, 0xfa, 0xc1, 0x24, 0xb9, 0xcd, 0xf4, 0xe0, 0xca, 0x0a, 0xdc, 0xe4, 0x06, 0xf2, 0x14, 0x38, 0x92, 0x41, 0x2f, 0x08, 0x61, 0xad, 0x3c, 0xef, 0x3c, 0xa1, 0x1f, 0xaa, 0xcf, 0x19, 0xc3, 0xb7, 0x88, 0xb0, 0xe4, 0xca, 0xeb, 0xaf, 0xb2, 0x08, 0xcb, 0x9e, 0xd8, 0x50, 0x5e, 0xd1, 0x99, 0xf6, 0x7b, 0x38, 0x7c, 0xaa, 0x0f, 0xbf, 0xc9, 0x2c, 0x92, 0x08, 0x11, 0x60, 0xeb, 0xdf, 0x0b, 0x0d, 0x42, 0x91, 0x4f, 0x6b, 0x11, 0x29, 0x89, 0x0f, 0x49, 0xb4, 0x17, 0x4b, 0x16, 0x2b, 0x2c, 0x06, 0x7a, 0xdf, 0x86, 0x84, 0x43, 0x09, 0x3d, 0xde, 0x05, 0xc9, 0xdf, 0xf7, 0xeb, 0xb1, 0xbe, 0xd9, 0xfd, 0x31, 0x3c, 0x4c, 0x11, 0xea, 0x58, 0x76, 0x68, 0x47, 0x8d, 0xec, 0x89, 0xee, 0x77, 0x4e, 0x68, 0x7f, 0x70, 0x9a, 0x8a, 0xa5, 0x4f, 0x5a, 0x23, 0x95, 0x98, 0x5a, 0x69, 0x22, 0x31, 0x4b, 0x19, 0xe2, 0xca, 0x23, 0x0d, 0x0e, 0x3c, 0x50, 0x95, 0x43, 0x16, 0x91, 0x4e, 0xb9, 0x32, 0xaf, 0xe8, 0x9b, 0xa6, 0x2a, 0x8a, 0x0a, 0x0e, 0xfc, 0x06, 0x1b, 0x5d, 0x9b, 0x0e, 0x29, 0x88, 0x64, 0x7b, 0xaa, 0x38, 0xe8, 0xe3, 0x18, 0x69, 0x39, 0xf2, 0xa2, 0x89, 0xe8, 0x83, 0xc1, 0x0f, 0x95, 0xa5, 0x5f, 0xf2, 0xf9, 0x8d, 0x24, 0x26, 0xa8, 0xd8, 0x91, 0x20, 0x39, 0xa4, 0xf0, 0x73, 0xba, 0x0c, 0xa7, 0xdb, 0x69, 0xf9, 0xd2, 0x6d, 0xcd, 0x2f, 0x6d, 0xa3, 0xd2, 0xe4, 0x0d, 0x07, 0xc5, 0x74, 0x6d, 0x91, 0xd5, 0x4d, 0xdb, 0x52, 0x9b, 0x42, 0xec, 0x89, 0x35, 0x6a, 0x48, 0x3d, 0x94, 0x24, 0x1f, 0x75, 0x81, 0xe4, 0x2b, 0x70, 0x17, 0x9a, 0x02, 0x8e, 0x8b, 0x08, 0x6f, 0x5f, 0x2e, 0x5d, 0xa4, 0x5a, 0xba, 0xad, 0xc2, 0x6b, 0x96, 0x39, 0x2e, 0xa3, 0x55, 0x71, 0x5b, 0x1a, 0xbc, 0x8e, 0xb0, 0x44, 0x33, 0x6c, 0x59, 0xc0, 0x25, 0xe7, 0x4c, 0x55, 0xb4, 0xb8, 0x97, 0xaa, 0x6b, 0x09, 0x8f, 0x41, 0xb3, 0xea, 0x70, 0x11, 0x3b, 0x4c, 0x3d, 0x4d, 0x06, 0x94, 0x09, 0xa1, 0x9a, 0x08, 0x05, 0xc8, 0xa2, 0x50, 0xd2, 0x0d, 0x91, 0x66, 0x84, 0x33, 0x94, 0x38, 0x47, 0xad, 0x81, 0x0b, 0xc7, 0xba, 0x02, 0x4e, 0x74, 0xec, 0x14, 0xb7, 0x54, 0x41, 0x6c, 0x3f, 0xee, 0x3c, 0xa1, 0x8e, 0x68, 0x61, 0xd7, 0xbc, 0x8d, 0x1d, 0x52, 0x14, 0xb4, 0xbd, 0xf3, 0x07, 0x01, 0x29, 0x7e, 0x4b, 0x77, 0xc5, 0xcf, 0x88, 0x50, 0x65, 0x06, 0xae, 0xe9, 0x1c, 0x0a, 0x3b, 0x0a, 0x89, 0x60, 0x5b, 0xc2, 0x51, 0x9d, 0xcb, 0xd6, 0x67, 0x52, 0xcf, 0xbc, 0xcb, 0x3b, 0xe6, 0x50, 0x96, 0xb2, 0xac, 0xe1, 0x60, 0xaf, 0x70, 0xab, 0xa2, 0xeb, 0x57, 0xea, 0xa9, 0xa3, 0xd6, 0x16, 0x8e, 0x7c, 0xec, 0xca, 0xe7, 0xb4, 0x28, 0xb9, 0xe3, 0x44, 0x28, 0x86, 0xc3, 0xd1, 0x10, 0xc9, 0x86, 0xa8, 0xc8, 0xb2, 0x22, 0x2c, 0x51, 0x99, 0x55, 0x52, 0x27, 0x2b, 0x59, 0x52, 0xaa, 0x48, 0x19, 0xa2, 0x5a, 0x42, 0xae, 0x2c, 0x42, 0x1e, 0x63, 0xa9, 0x7c, 0x29, 0x5e, 0xa3, 0xe9, 0x70, 0x16, 0x8b, 0x43, 0x75, 0x4e, 0xa8, 0x6c, 0x48, 0x46, 0x6a, 0x24, 0xcf, 0x80, 0xfb, 0xb7, 0x41, 0x3a, 0x0a, 0xc7, 0x0e, 0x6e, 0x38, 0x0b, 0x2d, 0xd5, 0xcc, 0xfd, 0xe5, 0x4c, 0xd1, 0x76, 0x65, 0xd6, 0xfc, 0x6d, 0x5d, 0x55, 0x86, 0x4c, 0x28, 0x8e, 0x2b, 0xc4, 0x5f, 0x04, 0xb5, 0x64, 0x58, 0x28, 0x33, 0x5b, 0x67, 0x2d, 0x02, 0x2a, 0x3a, 0x0e, 0x1d, 0xa0, 0xee, 0xe8, 0xe8, 0xfe, 0x2c, 0x02, 0x1a, 0x3e, 0xe1, 0x36, 0x33, 0x2e, 0x28, 0x72, 0x24, 0x0d, 0xc0, 0xa6, 0x7a, 0xce, 0x2a, 0xc0, 0xd1, 0x35, 0x8a, 0xaa, 0xe0, 0x3c, 0xc9, 0x00, 0x71, 0x25, 0xec, 0x5e, 0x36, 0x54, 0x1d, 0xec, 0x97, 0x8f, 0x7c, 0xff, 0xab, 0x90, 0xff, 0x44, 0x63, 0x1b, 0xbd, 0x1c, 0x93, 0x54, 0x3f, 0x74, 0x46, 0x83, 0xf8, 0x0b, 0xce, 0xe2, 0xa0, 0x24, 0x20, 0x8f, 0x3d, 0xb8, 0xf0, 0xb3, 0xa2, 0x4e, 0x38, 0x68, 0x38, 0x24, 0x86, 0xd3, 0x33, 0xb1, 0x5f, 0xb6, 0xe4, 0x6d, 0xee, 0x95, 0x0c, 0xe0, 0x4b, 0x42, 0x98, 0x93, 0xe1, 0x96, 0x13, 0x3d, 0x44, 0xad, 0x71, 0x56, 0x5e, 0x25, 0x1a, 0x21, 0x56, 0xa0, 0xc4, 0x4f, 0x11, 0xb2, 0x64, 0x9b, 0x0a, 0x09, 0x4c, 0xe0, 0xf6, 0x6c, 0x38, 0x93, 0x1e, 0x76, 0xb4, 0xb7, 0xd6, 0x75, 0x92, 0x44, 0x0a, 0xaf, 0x11, 0x37, 0x1d, 0x82, 0xb1, 0x0c, 0xd4, 0x13, 0x55, 0x0a, 0x31, 0x3f, 0x92, 0x4d, 0xc6, 0x22, 0x28, 0x03, 0x49, 0x09, 0xc4, 0x97, 0xcf, 0xd1, 0x2d, 0x45, 0x13, 0xb8, 0x1a, 0xae, 0x53, 0x42, 0x6a, 0x1b, 0x1a, 0x1f, 0x2c, 0x67, 0xeb, 0xa8, 0x11, 0xa9, 0x67, 0xb4, 0x24, 0x2c, 0x89, 0x1c, 0x42, 0x40, 0x14, 0x72, 0xef, 0x71, 0xea, 0x13, 0xf0, 0x96, 0x90, 0x0f, 0xcb, 0xee, 0x69, 0x32, 0x6f, 0xf1, 0x10, 0xf9, 0x98, 0x24, 0xfd, 0x20, 0xd9, 0xce, 0xad, 0x4f, 0x84, 0x82, 0xc4, 0x24, 0x42, 0xa8, 0x40, 0x28, 0x0c, 0xa7, 0x56, 0x41, 0x9d, 0x1a, 0xc6, 0x27, 0x09, 0x6c, 0xf0, 0x65, 0xc9, 0xe1, 0x1f, 0x2d, 0x2a, 0x51, 0x74, 0x77, 0x26, 0x48, 0x22, 0x2a, 0x72, 0x4e, 0x88, 0xc3, 0x12, 0x38, 0xf9, 0x09, 0x08, 0x39, 0x8e, 0xb0, 0xc9, 0x10, 0xbd, 0xdc, 0x22, 0x06, 0x1d, 0x72, 0x5a, 0x18, 0xc4, 0xa1, 0xe3, 0x53, 0xd9, 0x8c, 0xb9, 0x3d, 0x3a, 0xe4, 0x59, 0x42, 0x56, 0x29, 0xc4, 0x84, 0x41, 0x33, 0x20, 0x88, 0x0a, 0x8e, 0x71, 0x82, 0x53, 0x11, 0x2c, 0x13, 0xf7, 0x79, 0x3d, 0xa8, 0x3b, 0x51, 0xef, 0x2b, 0x4a, 0x02, 0x84, 0xc8, 0x0b, 0x04, 0x46, 0x73, 0x44, 0x2b, 0x52, 0xa1, 0x04, 0x55, 0xe6, 0xc3, 0x54, 0x10, 0x1c, 0x84, 0x40, 0x0d, 0x09, 0xf3, 0x89, 0x24, 0x7d, 0x85, 0xe4, 0xbe, 0xe3, 0xe6, 0xf5, 0x19, 0xc3, 0x02, 0x7f, 0xcc, 0x22, 0xa4, 0xa1, 0x68, 0x58, 0x18, 0x2a, 0x82, 0x17, 0x48, 0x1a, 0x32, 0x09, 0xee, 0xe4, 0x3c, 0x32, 0xb9, 0x61, 0x9f, 0x70, 0x29, 0x9b, 0x93, 0x8d, 0x04, 0x59, 0x48, 0x70, 0x43, 0x1f, 0x9d, 0x7d, 0x67, 0x15, 0xa4, 0xf1, 0x74, 0x5e, 0x86, 0x78, 0x38, 0x54, 0x8a, 0x5b, 0xf2, 0xa2, 0x71, 0xdb, 0x0a, 0x12, 0x89, 0x59, 0xc2, 0x0f, 0x24, 0xac, 0x00, 0xa2, 0x96, 0xed, 0x6e, 0x88, 0x11, 0x07, 0x34, 0x57, 0x3d, 0xa1, 0x36, 0xcf, 0x0d, 0x74, 0x15, 0xf2, 0xb3, 0x80, 0x50, 0x10, 0x72, 0x2e, 0xa6, 0x50, 0x1e, 0x3e, 0x69, 0x36, 0xd5, 0x67, 0xcf, 0x09, 0xe0, 0x28, 0xe7, 0xd8, 0x31, 0x89, 0xed, 0x0c, 0x38, 0xfe, 0x50, 0xe2, 0x9b, 0x1b, 0x4b, 0x24, 0x13, 0x1f, 0x18, 0x2d, 0x84, 0x38, 0x07, 0x8b, 0xa4, 0x3e, 0xc9, 0xd9, 0x7e, 0x4c, 0xa8, 0x96, 0x9e, 0x90, 0x44, 0x2f, 0x08, 0xdc, 0x0e, 0x84, 0xf2, 0x8e, 0xef, 0x57, 0xaf, 0x10, 0xca, 0xa5, 0x4e, 0x35, 0x1f, 0x44, 0x4c, 0x57, 0x18, 0x04, 0xf4, 0x44, 0x4b, 0x0e, 0x3a, 0xb1, 0xbe, 0x12, 0xb3, 0x28, 0x70, 0xef, 0xec, 0x5f, 0x12, 0xaa, 0xfa, 0xd4, 0x23, 0x12, 0xe0, 0x85, 0xaa, 0x63, 0xa6, 0x19, 0x74, 0x88, 0x4c, 0x82, 0x0b, 0xda, 0x5a, 0xd2, 0xd4, 0x75, 0x27, 0x59, 0x07, 0x39, 0x9a, 0x14, 0x63, 0x3d, 0xee, 0xe2, 0xfe, 0x66, 0x15, 0xcd, 0x84, 0x38, 0x1a, 0x55, 0x72, 0x31, 0xa1, 0x2b, 0xd4, 0x7d, 0xe8, 0xa1, 0x44, 0x7a, 0x20, 0x58, 0x86, 0xcf, 0xc4, 0xa1, 0x00, 0xb1, 0x42, 0x89, 0xc4, 0x2f, 0x56, 0xa5, 0x64, 0x00, 0xbc, 0x49, 0x10, 0x35, 0xba, 0x00, 0x8b, 0x16, 0x4c, 0x30, 0xe3, 0xdd, 0x81, 0x50, 0x43, 0x60, 0xa6, 0xb5, 0x44, 0x3c, 0xaa, 0x3e, 0x2d, 0x8a, 0xf3, 0x81, 0xd4, 0xe8, 0xea, 0x3d, 0xa1, 0xa8, 0x27, 0x44, 0xd2, 0xda, 0x28, 0x0e, 0xfb, 0x40, 0xe8, 0x36, 0xe2, 0x43, 0x02, 0x1a, 0x21, 0x8d, 0x27, 0xd4, 0x04, 0x81, 0x84, 0xb1, 0x4e, 0xcd, 0x38, 0x6f, 0x6f, 0x45, 0xe3, 0x0a, 0x1a, 0x13, 0xaa, 0xa3, 0x21, 0xac, 0xc7, 0x8a, 0x51, 0x8a, 0x6f, 0x71, 0x3d, 0x1a, 0xdd, 0xba, 0x32, 0xb7, 0xbc, 0x81, 0xa1, 0x26, 0xd1, 0x27, 0x31, 0xa2, 0x3e, 0xe1, 0x84, 0x10, 0x54, 0x09, 0xf5, 0x1c, 0x04, 0xc8, 0xa9, 0x90, 0x83, 0x4c, 0x63, 0xd6, 0xde, 0xa8, 0x3e, 0x3d, 0x37, 0x89, 0xd2, 0x94, 0xa8, 0xef, 0x2e, 0x93, 0x44, 0x39, 0x48, 0x68, 0x25, 0xa4, 0x48, 0x57, 0x9a, 0x44, 0x6c, 0x3c, 0x22, 0x99, 0xdc, 0xc6, 0x13, 0x09, 0x0d, 0x3a, 0x01, 0x44, 0xb9, 0xf3, 0xc9, 0x01, 0xe5, 0x8d, 0xc0, 0x35, 0x51, 0x55, 0x35, 0x92, 0x86, 0x94, 0x7c, 0x2c, 0x0f, 0xf9, 0x49, 0xf0, 0xcb, 0xce, 0x99, 0x50, 0x18, 0x72, 0xc4, 0xbe, 0x0a, 0x39, 0x9f, 0x9c, 0x68, 0x2c, 0xa9, 0x53, 0x7f, 0x90, 0x54, 0x69, 0xcb, 0xd9, 0x5c, 0x02, 0xa4, 0x99, 0xa4, 0xab, 0x58, 0xd2, 0x80, 0x4d, 0x61, 0xb5, 0x56, 0x55, 0x45, 0xb8, 0x5a, 0x59, 0xa3, 0xa1, 0x01, 0x2d, 0x1d, 0xc6, 0xb2, 0x24, 0xce, 0x47, 0xb7, 0x6d, 0x9a, 0xb4, 0xec, 0xe5, 0x26, 0x73, 0x1a, 0x43, 0x68, 0x4e, 0xf6, 0x07, 0x67, 0x1c, 0x29, 0xeb, 0x32, 0xaf, 0x11, 0x05, 0x9a, 0x63, 0x5b, 0x81, 0xe1, 0x34, 0x13, 0x5a, 0xc2, 0x2a, 0x4b, 0x27, 0x59, 0x47, 0xa8, 0xc2, 0xdc, 0xf7, 0x1a, 0xec, 0x1b, 0xe8, 0x26, 0x19, 0x18, 0xd9, 0xc8, 0x90, 0xa4, 0x38, 0xb8, 0x26, 0xb2, 0x85, 0x18, 0xb9, 0x74, 0x87, 0x88, 0xc5, 0xd0, 0xc7, 0xbb, 0xfa, 0x13, 0x63, 0x3e, 0xa5, 0x42, 0x3c, 0x25, 0x29, 0x8a, 0xbc, 0x8f, 0xea, 0x93, 0x99, 0x1d, 0x02, 0x3b, 0x48, 0x28, 0xfc, 0x31, 0x6c, 0xc0, 0xc7, 0x9a, 0x01, 0xc2, 0xf8, 0x00, 0xb0, 0xa8, 0x1c, 0xc2, 0x4e, 0x0a, 0x24, 0xb1, 0xc3, 0xb0, 0x6f, 0x30, 0x68, 0x72, 0x9d, 0x0c, 0x1f, 0xe4, 0x30, 0x12, 0x7a, 0x44, 0x1d, 0xe9, 0x83, 0xd2, 0xe0, 0x39, 0x51, 0x25, 0xaa, 0x50, 0xc7, 0x54, 0x7f, 0x0e, 0x0d, 0xe9, 0x74, 0x44, 0x1f, 0x7d, 0xc8, 0xc8, 0xb8, 0x4e, 0x4d, 0x4f, 0xc7, 0x0e, 0x6e, 0x5b, 0xcc, 0x8d, 0x45, 0xb9, 0x93, 0x14, 0x91, 0x59, 0x43, 0x9a, 0x0c, 0x2a, 0xd1, 0x54, 0xf8, 0xa4, 0xf8, 0x53, 0xdd, 0x20, 0xda, 0x16, 0xc6, 0x3c, 0x8c, 0xb0, 0x1a, 0x5f, 0x3b, 0x63, 0x60, 0xb5, 0x75, 0x6c, 0x2d, 0x47, 0x48, 0xad, 0xa7, 0x91, 0x15, 0xa7, 0x8a, 0xfa, 0xad, 0x15, 0xb7, 0x54, 0xb5, 0x8e, 0x2d, 0xee, 0xd6, 0xb4, 0x50, 0xd0, 0xf4, 0x43, 0x43, 0x53, 0x78, 0x5c, 0x24, 0x42, 0xf2, 0x36, 0xbf, 0x49, 0x50, 0xa6, 0x1d, 0x75, 0x03, 0x61, 0x24, 0x7b, 0xaf, 0x6e, 0xc2, 0x49, 0x5c, 0xb4, 0x86, 0xb5, 0xd6, 0x2a, 0x31, 0x54, 0xe9, 0x98, 0x98, 0x9a, 0x74, 0x61, 0xa9, 0x4c, 0x4b, 0xb7, 0xc5, 0x0d, 0xe8, 0xc2, 0x9c, 0x79, 0x05, 0xd5, 0xa4, 0x3a, 0xf1, 0xb4, 0x83, 0xeb, 0x24, 0x07, 0x91, 0xfe, 0x26, 0x44, 0x6a, 0x62, 0x24, 0xf9, 0xa9, 0x0d, 0x22, 0x95, 0x93, 0xae, 0x41, 0x38, 0x48, 0x4b, 0x82, 0x44, 0x1f, 0xa3, 0x5e, 0x6f, 0x8f, 0x6b, 0x8c, 0xb3, 0xf9, 0x0a, 0xbf, 0x9d, 0xfe, 0x45, 0x54, 0xc2, 0xf2, 0x5d, 0x1f, 0xe3, 0xec, 0x15, 0xb3, 0xe7, 0x37, 0xbf, 0x42, 0x2b, 0x53, 0x87, 0xa4, 0xdf, 0xf5, 0x24, 0x5b, 0x16, 0x38, 0x2c, 0x57, 0xf6, 0x5d, 0x89, 0xa5, 0x9a, 0xbe, 0x8d, 0x53, 0xb7, 0x61, 0x85, 0x00, 0x33, 0xa9, 0x58, 0x15, 0x92, 0x74, 0xd8, 0x9b, 0xb4, 0xa9, 0xdf, 0xb8, 0xdf, 0x2f, 0x22, 0xc8, 0xb4, 0xe9, 0xd6, 0xc7, 0x7a, 0x24, 0xc3, 0x16, 0xa9, 0xc4, 0xef, 0xa2, 0xba, 0xf5, 0xb7, 0x95, 0x6b, 0xf7, 0xdb, 0xfe, 0xe5, 0x3d, 0x37, 0xfe, 0x24, 0x9f, 0xff, 0xc3, 0xdf, 0x57, 0xf6, 0x0a, 0x0c, 0x37, 0xc0, 0x0a, 0x0e, 0x26, 0x22, 0x6f, 0x3c, 0x47, 0x7a, 0xf3, 0x37, 0x9c, 0x7a, 0x14, 0x17, 0x84, 0xfa, 0x3f, 0xfc, 0xbc, 0x4b, 0xf7, 0xeb, 0x1f, 0x43, 0xe8, 0x0a, 0x9a, 0xf6, 0x26, 0xe7, 0xf6, 0xfb, 0x5d, 0xa6, 0x89, 0x3d, 0x52, 0xbf, 0x89, 0xa1, 0x94, 0x98, 0x94, 0x45, 0x18, 0xc3, 0x10, 0x94, 0xe1, 0x9b, 0x8a, 0x18, 0xb4, 0xae, 0x27, 0xf9, 0x3a, 0xa9, 0x42, 0x3a, 0xc9, 0xf9, 0xd9, 0x4f, 0x99, 0x86, 0x89, 0xee, 0x3c, 0x1f, 0xe8, 0x78, 0xe5, 0x49, 0xe4, 0xc7, 0x90, 0x7f, 0x5a, 0x26, 0x88, 0x93, 0x83, 0xb3, 0x4a, 0x6c, 0x4c, 0xff, 0xa7, 0xf3, 0x32, 0xbf, 0x79, 0x67, 0x85, 0xd0, 0x32, 0x41, 0xe9, 0xd1, 0x05, 0x21, 0x9e, 0x11, 0x92, 0xd6, 0xb0, 0xc3, 0x0b, 0x8e, 0xb7, 0x4e, 0xe4, 0xbc, 0x38, 0x65, 0xa5, 0xc7, 0x99, 0xd1, 0x66, 0x75, 0x4f, 0x66, 0xf7, 0x48, 0xfd, 0x76, 0x03, 0x35, 0x40, 0x3a, 0x55, 0x5e, 0x7b, 0x42, 0x6f, 0xe9, 0xa3, 0x72, 0xee, 0xc2, 0x7d, 0xf5, 0x54, 0x07, 0x03, 0x6e, 0x1a, 0x88, 0xbb, 0x7e, 0x62, 0xd2, 0x21, 0x27, 0xa1, 0x9a, 0xe5, 0x04, 0x58, 0xaf, 0xed, 0xc6, 0x25, 0x06, 0xd7, 0xc8, 0xf2, 0xc2, 0xf2, 0x76, 0xdd, 0x31, 0x0a, 0xec, 0xf0, 0xcb, 0x0e, 0x26, 0x77, 0xfc, 0x9d, 0x01, 0x25, 0x5b, 0xe5, 0x29, 0x42, 0x1c, 0xed, 0x38, 0x59, 0x50, 0xdc, 0x5a, 0xf1, 0xe4, 0xf4, 0x5c, 0x83, 0xb6, 0xfa, 0x9c, 0xb2, 0xe0, 0xd3, 0x53, 0xda, 0x4b, 0xf0, 0xb1, 0x51, 0x98, 0xd0, 0x26, 0xa0, 0x1e, 0xd2, 0x35, 0x42, 0x51, 0x4b, 0x03, 0xcd, 0x03, 0x40, 0x62, 0x03, 0x34, 0x34, 0x36, 0x25, 0xfd, 0x39, 0x9f, 0x80, 0xe9, 0xd6, 0x77, 0x30, 0xb5, 0x2c, 0x91, 0x9f, 0xb1, 0x7f, 0x32, 0xa1, 0x4b, 0x80, 0x6e, 0x4b, 0x1d, 0xb8, 0xdd, 0x18, 0x56, 0x70, 0x03, 0xa0, 0xed, 0x73, 0x4f, 0x00, 0x62, 0x01, 0xba, 0xe3, 0xc3, 0x8c, 0x4e, 0x8a, 0x91, 0x27, 0xa4, 0xe7, 0xe5, 0x53, 0x08, 0xcd, 0x47, 0xc7, 0x9d, 0x00, 0x9f, 0xe9, 0x8e, 0xf0, 0x67, 0x9d, 0xad, 0x53, 0x40, 0xeb, 0x84, 0x54, 0x74, 0x6c, 0x07, 0xf0, 0x9c, 0x90, 0xbe, 0x2b, 0x33, 0x44, 0x17, 0x09, 0xcd, 0xcb, 0x4e, 0x7b, 0xa6, 0x67, 0xdc, 0xf6, 0x22, 0xca, 0x8e, 0x12, 0x9a, 0xc6, 0x5d, 0xfa, 0x8f, 0x33, 0x44, 0x0f, 0x1d, 0x76, 0xff, 0x04, 0xa1, 0xbd, 0x06, 0x2d, 0x00, 0x5d, 0xf3, 0x01, 0xae, 0x00, 0xf2, 0xeb, 0xdf, 0x89, 0x98, 0x66, 0x53, 0x44, 0x7b, 0xe9, 0x05, 0x17, 0xe5, 0x65, 0xb5, 0x97, 0xcd, 0x01, 0x9d, 0x0e, 0x9e, 0x3a, 0x47, 0x68, 0xde, 0xba, 0x21, 0x3c, 0xe4, 0x22, 0x1f, 0x9f, 0x07, 0x26, 0x8a, 0xe6, 0xef, 0x91, 0xba, 0x89, 0xc3, 0x66, 0xd0, 0x45, 0xc7, 0xaf, 0xef, 0x09, 0x4d, 0x10, 0x25, 0x33, 0x2d, 0xf4, 0xa5, 0x84, 0x3c, 0x1f, 0xaf, 0x96, 0x9e, 0x01, 0x14, 0x0f, 0xef, 0x91, 0x9a, 0x18, 0x9a, 0x93, 0x2d, 0xcf, 0xd1, 0xf1, 0x9e, 0xfc, 0x13, 0xc7, 0xb2, 0xab, 0x80, 0x06, 0xcd, 0x7d, 0x91, 0x90, 0xb8, 0x7e, 0xee, 0xee, 0x8a, 0xbf, 0xf5, 0x80, 0xe8, 0xb8, 0x10, 0x3d, 0x24, 0x74, 0xd9, 0x1c, 0xba, 0x44, 0x68, 0x92, 0x67, 0xf2, 0x22, 0x22, 0x11, 0xa1, 0x64, 0xe5, 0x9e, 0x33, 0x44, 0xc7, 0x85, 0xe8, 0x8b, 0x08, 0x1d, 0x68, 0xd9, 0x6d, 0x02, 0xe8, 0xba, 0x32, 0x12, 0x11, 0x5a, 0x03, 0xf4, 0xea, 0xdd, 0x60, 0x1e, 0xd1, 0xe1, 0x11, 0xff, 0x11, 0xa1, 0xeb, 0x93, 0x8e, 0x53, 0x84, 0x06, 0x2e, 0xb7, 0x61, 0x9d, 0x24, 0x3d, 0xf8, 0xd3, 0xc5, 0x85, 0x76, 0x32, 0x9a, 0xcd, 0x10, 0x1d, 0xee, 0x66, 0x98, 0xb9, 0x3e, 0x22, 0xf4, 0xbc, 0xa2, 0x7e, 0xdc, 0xb0, 0xdb, 0x5c, 0xfb, 0x70, 0xa5, 0x2e, 0x88, 0x11, 0xd4, 0xf1, 0xde, 0x04, 0xef, 0x06, 0x9f, 0x5d, 0xdf, 0xcd, 0x8e, 0x5d, 0xf2, 0x01, 0xa1, 0xf4, 0xeb, 0x08, 0x79, 0x69, 0x1d, 0xfe, 0x96, 0xdb, 0x9f, 0x23, 0xc4, 0xd3, 0xb1, 0xfd, 0x49, 0x05, 0x4d, 0x17, 0x85, 0xd0, 0x51, 0xab, 0x51, 0xf6, 0x4a, 0xef, 0xe0, 0xf9, 0x0c, 0x42, 0x47, 0x9a, 0x76, 0xbb, 0x4d, 0x6d, 0x45, 0x5e, 0x10, 0x3c, 0xfa, 0xe3, 0xe9, 0x65, 0x92, 0xfd, 0x64, 0xd3, 0xec, 0x6e, 0x3e, 0xa7, 0x88, 0xb6, 0x09, 0x8d, 0xab, 0x43, 0x5f, 0x40, 0xa8, 0x47, 0xe4, 0xcf, 0xbe, 0x8d, 0x7a, 0xe8, 0x50, 0x2b, 0xc6, 0xab, 0xe8, 0x87, 0x21, 0x80, 0x3d, 0xa2, 0xa3, 0x8a, 0xe8, 0xf7, 0x75, 0x42, 0xd3, 0xe5, 0xb3, 0x8b, 0x79, 0xf0, 0x4e, 0x01, 0x92, 0xaa, 0xdf, 0x86, 0xbc, 0xda, 0x22, 0x42, 0x07, 0x7f, 0x38, 0xb9, 0x04, 0x75, 0xb2, 0x47, 0xbf, 0xeb, 0x35, 0xd1, 0x09, 0x42, 0x77, 0x2b, 0x68, 0x8b, 0x05, 0xc6, 0xaf, 0x22, 0x34, 0xb3, 0x87, 0x92, 0x2b, 0xeb, 0x69, 0xac, 0xa7, 0x1f, 0x47, 0x91, 0x8a, 0x26, 0x3a, 0x43, 0x68, 0x82, 0x28, 0x5d, 0xe2, 0x91, 0x54, 0x6f, 0x5f, 0x02, 0xa8, 0x57, 0xd7, 0x02, 0xe8, 0x92, 0x08, 0x11, 0xa1, 0xc7, 0xbf, 0xbb, 0xdd, 0x4e, 0x11, 0x7a, 0x19, 0x16, 0xf1, 0xb7, 0xca, 0x97, 0x89, 0xd0, 0x38, 0xe6, 0xef, 0x66, 0x4c, 0xda, 0xfd, 0x7d, 0x7a, 0x40, 0xff, 0x7a, 0x45, 0x74, 0x58, 0x53, 0xbf, 0x3d, 0x20, 0xf4, 0x75, 0x22, 0xf4, 0x7a, 0xbf, 0x9c, 0x76, 0xb6, 0x1c, 0xba, 0xa1, 0x27, 0x74, 0x78, 0xb4, 0xff, 0x56, 0x84, 0xbc, 0x33, 0xf1, 0x2a, 0xa0, 0xa3, 0x84, 0xb4, 0xd8, 0x43, 0xc7, 0xae, 0xf9, 0x88, 0xd0, 0xe5, 0x94, 0xae, 0xd7, 0x00, 0x71, 0x4f, 0x79, 0x66, 0x8d, 0xf1, 0x90, 0x75, 0xaa, 0xcf, 0xd8, 0xd4, 0x2f, 0x6f, 0xfb, 0xfe, 0xb2, 0x7f, 0x80, 0xd0, 0xd3, 0xab, 0xb0, 0x0f, 0xef, 0xa0, 0xcf, 0xcc, 0xcb, 0x5e, 0xf0, 0xa6, 0xf0, 0x7d, 0x40, 0x5f, 0x4e, 0xe8, 0xe7, 0x02, 0xe2, 0x2d, 0x1b, 0xc7, 0x27, 0x1d, 0xfc, 0xb6, 0xd2, 0x4d, 0x42, 0x5f, 0xcf, 0xe7, 0xf5, 0xfa, 0x86, 0xc2, 0x13, 0x37, 0xd0, 0xd1, 0x89, 0x65, 0x58, 0xc8, 0xd0, 0xea, 0x1b, 0x6f, 0x7d, 0x46, 0xd7, 0xe7, 0x08, 0xfd, 0xd4, 0x96, 0x5e, 0x2d, 0xbc, 0x2f, 0xea, 0xb8, 0xb7, 0x03, 0x32, 0x74, 0xd7, 0xcd, 0xde, 0x26, 0xe5, 0x31, 0x82, 0x5f, 0x8e, 0xd0, 0x4b, 0x14, 0x45, 0x27, 0x56, 0xf2, 0x19, 0xc3, 0x0c, 0xcf, 0x0c, 0xd0, 0x3e, 0x21, 0xbe, 0xe1, 0xaf, 0x06, 0x08, 0x9d, 0xec, 0x84, 0x4f, 0x71, 0x46, 0x08, 0x9f, 0xde, 0xde, 0x8e, 0x03, 0xda, 0x0e, 0xe7, 0xf8, 0xce, 0x84, 0xd2, 0x33, 0x5a, 0xc8, 0xdb, 0x43, 0x1b, 0xe5, 0xc8, 0x0b, 0x12, 0x7e, 0x3d, 0x40, 0x10, 0xa1, 0x33, 0x3e, 0x57, 0xb6, 0x87, 0x56, 0xf1, 0x1c, 0x6a, 0xf5, 0x23, 0x80, 0xdf, 0xaf, 0x9c, 0x15, 0x21, 0x19, 0xcb, 0x04, 0xd2, 0x6d, 0x25, 0x5d, 0xfb, 0xdd, 0x17, 0xf3, 0xbb, 0xfd, 0x72, 0x7c, 0x58, 0x84, 0x76, 0x5e, 0x66, 0xbf, 0x52, 0x5e, 0x56, 0x53, 0x8f, 0xf7, 0xcc, 0xf6, 0xda, 0xbd, 0x8b, 0xe7, 0x1b, 0x13, 0xda, 0x7b, 0xcf, 0xd4, 0x5a, 0x79, 0xb9, 0x4d, 0xd4, 0xce, 0x21, 0xfd, 0x3c, 0xdc, 0x6a, 0xf9, 0xfd, 0xaf, 0x00, 0xe8, 0x95, 0xd7, 0x47, 0x4e, 0x55, 0xee, 0xd8, 0xeb, 0x6e, 0x0e, 0x94, 0x05, 0xb3, 0x9f, 0xd5, 0xc2, 0x67, 0xcb, 0xf9, 0xca, 0xfd, 0x2f, 0x76, 0xd7, 0x43, 0xee }; #if !defined( MACOS_APP_BUNDLE ) const uint8_t iconImage[] = { 0x78, 0x9c, 0xc5, 0xd3, 0xeb, 0x6e, 0xda, 0x30, 0x18, 0x06, 0x60, 0xe0, 0x02, 0xf6, 0x77, 0x5b, 0x0f, 0xe4, 0xa4, 0x14, 0x82, 0x63, 0x27, 0x71, 0x1c, 0x1f, 0xe2, 0x24, 0x57, 0x54, 0xf3, 0x77, 0xac, 0xa5, 0x54, 0xed, 0xc5, 0x6d, 0xab, 0xba, 0xeb, 0xe8, 0x75, 0xec, 0x8b, 0x03, 0x34, 0x14, 0x0a, 0x93, 0x3a, 0x69, 0x16, 0x0a, 0x72, 0x9e, 0x37, 0x3e, 0xe5, 0xcb, 0x60, 0x70, 0xa4, 0x05, 0x01, 0x5c, 0xea, 0xfe, 0x9d, 0x4a, 0x37, 0xaf, 0x1d, 0x7d, 0xd3, 0x9a, 0x54, 0x9b, 0x5b, 0x41, 0xc5, 0xb3, 0x94, 0xf2, 0xae, 0x1f, 0xa8, 0x25, 0xc1, 0x15, 0xfc, 0x0b, 0x51, 0x94, 0x4d, 0x30, 0x08, 0x1a, 0x5d, 0x64, 0x28, 0x19, 0xb8, 0x34, 0x49, 0x33, 0xc6, 0x68, 0x82, 0x09, 0xef, 0x3c, 0x68, 0x38, 0x2b, 0x38, 0xcb, 0x29, 0xd7, 0x86, 0x06, 0x81, 0xef, 0x3b, 0x17, 0x67, 0x19, 0x65, 0x65, 0x5d, 0xb7, 0x3e, 0x10, 0xed, 0x22, 0xfc, 0xa6, 0xac, 0xea, 0xfa, 0xd1, 0xdc, 0x7a, 0x5d, 0xd3, 0xa5, 0xd6, 0x55, 0x15, 0xb7, 0xce, 0x03, 0x3b, 0x79, 0x5d, 0x69, 0xc5, 0x4d, 0xe2, 0x5c, 0x8e, 0xc7, 0xf1, 0x7c, 0x9e, 0x49, 0xa9, 0x4a, 0xdd, 0x79, 0xb7, 0x3c, 0xad, 0xa4, 0xe0, 0x91, 0xb9, 0x0a, 0xaf, 0xcd, 0x84, 0x66, 0xd1, 0xd4, 0x4c, 0xb0, 0x88, 0x5b, 0x12, 0xeb, 0xe5, 0xc2, 0x3a, 0x59, 0x16, 0x22, 0x4c, 0x08, 0x77, 0x3d, 0x7e, 0x7b, 0xb3, 0x98, 0x4f, 0xac, 0x2b, 0x55, 0x57, 0xa5, 0x92, 0x30, 0xa9, 0xeb, 0x3a, 0x26, 0xa6, 0x94, 0xe6, 0x8c, 0x41, 0xcf, 0x7f, 0x99, 0xe1, 0x76, 0xea, 0x52, 0xd6, 0xfa, 0x51, 0x0a, 0xcb, 0x0e, 0x31, 0x99, 0x0d, 0xb4, 0xee, 0x5f, 0x60, 0x7b, 0x64, 0xbc, 0x6a, 0xe7, 0x06, 0x77, 0x1c, 0xe7, 0x32, 0x4c, 0x6c, 0xc0, 0x6d, 0xfd, 0x3c, 0x69, 0xbd, 0x61, 0x30, 0xbc, 0x10, 0xf6, 0x71, 0x27, 0x09, 0x71, 0x96, 0x75, 0xee, 0xf9, 0x5f, 0xb3, 0x8d, 0x3f, 0xac, 0x3d, 0xb9, 0x0e, 0x3f, 0x65, 0x6d, 0xc0, 0xfa, 0xe7, 0x3b, 0xeb, 0xf9, 0xab, 0x4f, 0x30, 0x0a, 0xc9, 0xd6, 0xe3, 0x95, 0xdd, 0x19, 0x2b, 0x37, 0xe3, 0x33, 0xc3, 0xd2, 0x30, 0xda, 0xb8, 0x87, 0x5e, 0xec, 0xc9, 0x70, 0x07, 0xce, 0xdc, 0xe3, 0x45, 0x91, 0xcf, 0x7e, 0x52, 0x07, 0x47, 0xb0, 0x0c, 0xb7, 0xe0, 0x5c, 0x9e, 0xc7, 0xdd, 0xc9, 0x6a, 0x66, 0x9d, 0xb1, 0x24, 0x8c, 0xa9, 0x33, 0xc3, 0xc0, 0x2e, 0x63, 0x5c, 0x90, 0xb4, 0x7b, 0xed, 0xb5, 0x7d, 0x69, 0xf0, 0x02, 0xa7, 0xb0, 0x79, 0x27, 0x46, 0xc0, 0x1e, 0xbc, 0x50, 0x11, 0xdf, 0xad, 0xcb, 0x22, 0x77, 0xc1, 0x45, 0xf1, 0xeb, 0x47, 0x02, 0x13, 0x8f, 0xcd, 0x18, 0x26, 0x07, 0x2e, 0x90, 0x5e, 0xd7, 0x8d, 0x6c, 0x07, 0x10, 0x4b, 0xb3, 0x60, 0x2c, 0x77, 0xdd, 0x39, 0x87, 0xa5, 0x71, 0xa1, 0xc8, 0x62, 0x53, 0x67, 0x4d, 0xe6, 0x81, 0x3f, 0x1b, 0x38, 0x77, 0x38, 0xd8, 0x68, 0x36, 0xa3, 0x9e, 0x50, 0x12, 0xad, 0xb6, 0x85, 0xc8, 0x61, 0x00, 0xf9, 0xf4, 0xc4, 0x0b, 0x56, 0x78, 0x1e, 0x36, 0xb1, 0x99, 0x11, 0x4a, 0x48, 0xb0, 0xf5, 0x26, 0x15, 0x95, 0x9f, 0x3c, 0xb5, 0x7b, 0x96, 0x32, 0x8f, 0xd4, 0xf2, 0xd9, 0x5c, 0xa1, 0xfb, 0x5e, 0x59, 0x4b, 0xa2, 0xea, 0xe9, 0x6f, 0x0f, 0xea, 0x5d, 0x89, 0x14, 0x09, 0xf9, 0xe0, 0xcc, 0x6e, 0x76, 0x3e, 0x04, 0x46, 0xd4, 0x95, 0x99, 0xa7, 0x1a, 0x7c, 0x39, 0x5d, 0x29, 0x85, 0x17, 0xfe, 0xee, 0x97, 0x92, 0x13, 0xa9, 0x69, 0x64, 0xa0, 0xee, 0x04, 0x8a, 0x45, 0x4c, 0x76, 0xbe, 0x1a, 0x1b, 0xc0, 0x0e, 0x5c, 0xcc, 0xb7, 0x07, 0x75, 0x37, 0x45, 0x8b, 0xb7, 0x0c, 0x81, 0x15, 0xfe, 0xe2, 0x0f, 0x98, 0xb9, 0x97, 0x04, 0x7d, 0x6f, 0xf6, 0x18, 0x9a, 0x4a, 0xe3, 0xb3, 0x3a, 0x22, 0x88, 0xac, 0x0e, 0xa9, 0x1d, 0x82, 0x20, 0x84, 0x97, 0x07, 0x1f, 0x5e, 0x27, 0xee, 0x57, 0x6f, 0x74, 0x08, 0xbf, 0x77, 0x1b, 0xd8, 0x68, 0xd0, 0x0f, 0x74, 0x8f, 0xf4, 0x3a, 0xd6, 0x37, 0xb7, 0x7a, 0xa3, 0x6e, 0x3a, 0x6b, 0x7f, 0xb7, 0xfd, 0x7f, 0x3f, 0x1e, 0xf8, 0xa0, 0x8f, 0x4e, 0xf8, 0xf0, 0xc3, 0x7e, 0x7c, 0x83, 0xff, 0xc2, 0x8f, 0x04, 0x86, 0x7f, 0xe1, 0xc7, 0x26, 0x18, 0x9e, 0x18, 0xe0, 0x84, 0xaf, 0xeb, 0xf0, 0xa4, 0xbf, 0x13, 0x18, 0x6d, 0xfd, 0x70, 0x60, 0x5b, 0xf5, 0x87, 0x7d, 0x34, 0x3c, 0x1a, 0xe8, 0xf1, 0xa1, 0x19, 0xfa, 0x7c, 0x20, 0xb0, 0xcb, 0x7b, 0x81, 0xb7, 0x6c, 0x03, 0xdb, 0xc4, 0x68, 0x9f, 0x7b, 0x89, 0xd1, 0x0e, 0xff, 0x01, 0x68, 0x73, 0x94, 0x0b }; #endif fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/000077500000000000000000000000001456075706000201755ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/color.cpp000066400000000000000000000140671456075706000220270ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "color.h" #include "players.h" #include "serialize.h" #include "tools.h" #include "translations.h" #include "world.h" namespace { enum class BarrierColor : int { NONE = 0, AQUA = 1, BLUE = 2, BROWN = 3, GOLD = 4, GREEN = 5, ORANGE = 6, PURPLE = 7, RED = 8 }; } std::string Color::String( int color ) { switch ( color ) { case Color::BLUE: return _( "Blue" ); case Color::GREEN: return _( "Green" ); case Color::RED: return _( "Red" ); case Color::YELLOW: return _( "Yellow" ); case Color::ORANGE: return _( "Orange" ); case Color::PURPLE: return _( "Purple" ); case Color::UNUSED: return "Unknown"; default: break; } return "None"; } int Color::GetIndex( int color ) { switch ( color ) { case BLUE: return 0; case GREEN: return 1; case RED: return 2; case YELLOW: return 3; case ORANGE: return 4; case PURPLE: return 5; default: break; } // NONE return 6; } int Color::Count( int colors ) { return CountBits( colors & ALL ); } int Color::FromInt( int col ) { switch ( col ) { case BLUE: case GREEN: case RED: case YELLOW: case ORANGE: case PURPLE: return col; default: break; } return NONE; } int Color::GetFirst( int colors ) { if ( colors & BLUE ) return BLUE; else if ( colors & GREEN ) return GREEN; else if ( colors & RED ) return RED; else if ( colors & YELLOW ) return YELLOW; else if ( colors & ORANGE ) return ORANGE; else if ( colors & PURPLE ) return PURPLE; return NONE; } uint8_t Color::IndexToColor( const int index ) { switch ( index ) { case 0: return BLUE; case 1: return GREEN; case 2: return RED; case 3: return YELLOW; case 4: return ORANGE; case 5: return PURPLE; default: break; } return Color::NONE; } const char * fheroes2::getBarrierColorName( const int color ) { switch ( static_cast( color ) ) { case BarrierColor::AQUA: return _( "barrier|Aqua" ); case BarrierColor::BLUE: return _( "barrier|Blue" ); case BarrierColor::BROWN: return _( "barrier|Brown" ); case BarrierColor::GOLD: return _( "barrier|Gold" ); case BarrierColor::GREEN: return _( "barrier|Green" ); case BarrierColor::ORANGE: return _( "barrier|Orange" ); case BarrierColor::PURPLE: return _( "barrier|Purple" ); case BarrierColor::RED: return _( "barrier|Red" ); default: break; } return "None"; } const char * fheroes2::getTentColorName( const int color ) { switch ( static_cast( color ) ) { case BarrierColor::AQUA: return _( "tent|Aqua" ); case BarrierColor::BLUE: return _( "tent|Blue" ); case BarrierColor::BROWN: return _( "tent|Brown" ); case BarrierColor::GOLD: return _( "tent|Gold" ); case BarrierColor::GREEN: return _( "tent|Green" ); case BarrierColor::ORANGE: return _( "tent|Orange" ); case BarrierColor::PURPLE: return _( "tent|Purple" ); case BarrierColor::RED: return _( "tent|Red" ); default: break; } return "None"; } Colors::Colors( int colors ) { reserve( 6 ); if ( colors & Color::BLUE ) push_back( Color::BLUE ); if ( colors & Color::GREEN ) push_back( Color::GREEN ); if ( colors & Color::RED ) push_back( Color::RED ); if ( colors & Color::YELLOW ) push_back( Color::YELLOW ); if ( colors & Color::ORANGE ) push_back( Color::ORANGE ); if ( colors & Color::PURPLE ) push_back( Color::PURPLE ); } bool ColorBase::isFriends( int col ) const { return ( col & Color::ALL ) && ( color == col || Players::isFriends( color, col ) ); } void ColorBase::SetColor( int col ) { color = Color::FromInt( col ); } Kingdom & ColorBase::GetKingdom() const { return world.GetKingdom( color ); } StreamBase & operator<<( StreamBase & msg, const ColorBase & col ) { return msg << col.color; } StreamBase & operator>>( StreamBase & msg, ColorBase & col ) { return msg >> col.color; } fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/color.h000066400000000000000000000061741456075706000214740ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2COLOR_H #define H2COLOR_H #include #include #include class StreamBase; namespace fheroes2 { const char * getBarrierColorName( const int color ); const char * getTentColorName( const int color ); } namespace Color { // !!! IMPORTANT !!! // Do NOT change the order of the items as they are used for the map format. enum : uint8_t { NONE = 0x00, BLUE = 0x01, GREEN = 0x02, RED = 0x04, YELLOW = 0x08, ORANGE = 0x10, PURPLE = 0x20, UNUSED = 0x80, ALL = BLUE | GREEN | RED | YELLOW | ORANGE | PURPLE }; std::string String( int color ); int Count( int colors ); int GetIndex( int color ); int GetFirst( int colors ); int FromInt( int col ); uint8_t IndexToColor( const int index ); } class Colors : public std::vector { public: explicit Colors( int colors = Color::ALL ); }; class Kingdom; class ColorBase { int color; friend StreamBase & operator<<( StreamBase &, const ColorBase & ); friend StreamBase & operator>>( StreamBase &, ColorBase & ); public: explicit ColorBase( int col = Color::NONE ) : color( col ) {} bool isFriends( int ) const; void SetColor( int ); Kingdom & GetKingdom() const; int GetColor() const { return color; } }; StreamBase & operator<<( StreamBase &, const ColorBase & ); StreamBase & operator>>( StreamBase &, ColorBase & ); #endif fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/experience.cpp000066400000000000000000000034041456075706000230310ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "experience.h" #include "translations.h" namespace fheroes2 { const char * getExperienceName() { return _( "Experience" ); } const char * getExperienceDescription() { return _( "Experience allows your heroes to go up levels, increasing their primary and secondary skills." ); } } fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/experience.h000066400000000000000000000030621456075706000224760ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once namespace fheroes2 { const char * getExperienceName(); const char * getExperienceDescription(); } fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/kingdom.cpp000066400000000000000000001010121456075706000223240ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "kingdom.h" #include #include #include #include #include #include #include #include "ai.h" #include "army.h" #include "artifact.h" #include "artifact_info.h" #include "battle.h" #include "campaign_data.h" #include "campaign_savedata.h" #include "color.h" #include "difficulty.h" #include "game.h" #include "game_interface.h" #include "game_io.h" #include "game_static.h" #include "interface_icons.h" #include "logging.h" #include "maps.h" #include "maps_fileinfo.h" #include "maps_tiles.h" #include "maps_tiles_helper.h" #include "math_base.h" #include "payment.h" #include "players.h" #include "profit.h" #include "race.h" #include "route.h" #include "save_format_version.h" #include "serialize.h" #include "settings.h" #include "skill.h" #include "visit.h" #include "world.h" namespace { int32_t getHandicapIncomePercentage( const Player::HandicapStatus handicapStatus ) { switch ( handicapStatus ) { case Player::HandicapStatus::NONE: return 100; case Player::HandicapStatus::MILD: return 85; case Player::HandicapStatus::SEVERE: return 70; default: // Did you add a new handicap status? Add the logic above! break; } return 100; } Funds getHandicapDependentIncome( const Funds & original, const Player::HandicapStatus handicapStatus ) { const int32_t handicapPercentage = getHandicapIncomePercentage( handicapStatus ); Funds corrected( original ); corrected.wood = std::min( corrected.wood, ( corrected.wood * handicapPercentage + 99 ) / 100 ); corrected.mercury = std::min( corrected.mercury, ( corrected.mercury * handicapPercentage + 99 ) / 100 ); corrected.ore = std::min( corrected.ore, ( corrected.ore * handicapPercentage + 99 ) / 100 ); corrected.sulfur = std::min( corrected.sulfur, ( corrected.sulfur * handicapPercentage + 99 ) / 100 ); corrected.crystal = std::min( corrected.crystal, ( corrected.crystal * handicapPercentage + 99 ) / 100 ); corrected.gems = std::min( corrected.gems, ( corrected.gems * handicapPercentage + 99 ) / 100 ); corrected.gold = std::min( corrected.gold, ( corrected.gold * handicapPercentage + 99 ) / 100 ); return corrected; } } bool HeroesStrongestArmy( const Heroes * h1, const Heroes * h2 ) { return h1 && h2 && h2->GetArmy().isStrongerThan( h1->GetArmy() ); } Kingdom::Kingdom() : color( Color::NONE ) , _lastBattleWinHeroID( 0 ) , lost_town_days( 0 ) , visited_tents_colors( 0 ) , _topCastleInKingdomView( -1 ) , _topHeroInKingdomView( -1 ) { // Do nothing. } void Kingdom::Init( int clr ) { clear(); color = clr; if ( Color::ALL & color ) { // Difficulty calculation is different for campaigns. Difficulty affects only on starting resources for human players. const Settings & configuration = Settings::Get(); const int difficultyLevel = ( configuration.isCampaignGameType() ? configuration.getCurrentMapInfo().difficulty : configuration.GameDifficulty() ); resource = _getKingdomStartingResources( difficultyLevel ); // Some human players can have handicap for resources. const Player * player = Players::Get( color ); assert( player != nullptr ); resource = getHandicapDependentIncome( resource, player->getHandicapStatus() ); } else { DEBUG_LOG( DBG_GAME, DBG_WARN, "Kingdom: unknown player: " << Color::String( color ) << "(" << static_cast( color ) << ")" ) } } void Kingdom::clear() { modes = 0; color = Color::NONE; visited_tents_colors = 0; lost_town_days = Game::GetLostTownDays() + 1; heroes.clear(); castles.clear(); visit_object.clear(); recruits.Reset(); puzzle_maps.reset(); } int Kingdom::GetControl() const { return Players::GetPlayerControl( color ); } int Kingdom::GetColor() const { return color; } int Kingdom::GetRace() const { return Players::GetPlayerRace( GetColor() ); } bool Kingdom::isLoss() const { return castles.empty() && heroes.empty(); } bool Kingdom::isPlay() const { return Players::GetPlayerInGame( color ); } void Kingdom::LossPostActions() { if ( isPlay() ) { Players::SetPlayerInGame( color, false ); // Heroes::Dismiss() calls Kingdom::RemoveHero(), which eventually calls heroes.erase() while ( !heroes.empty() ) { Heroes * hero = heroes.back(); assert( hero->GetColor() == GetColor() ); hero->Dismiss( static_cast( Battle::RESULT_LOSS ) ); } if ( !castles.empty() ) { castles.ChangeColors( GetColor(), Color::NONE ); castles.clear(); } world.ResetCapturedObjects( GetColor() ); } } void Kingdom::ActionBeforeTurn() { if ( isControlHuman() ) { // Recalculate the existing paths of heroes if the kingdom is controlled by a human std::for_each( heroes.begin(), heroes.end(), []( Heroes * hero ) { hero->calculatePath( -1 ); } ); } else { // Reset the paths of heroes if the kingdom is controlled by AI, because it uses a // special pathfinder implementation and revises its goals every turn std::for_each( heroes.begin(), heroes.end(), []( Heroes * hero ) { hero->GetPath().Reset(); } ); } } void Kingdom::ActionNewDay() { // Clear the visited objects with a lifetime of one day, even if this kingdom has already been vanquished visit_object.remove_if( Visit::isDayLife ); if ( !isPlay() ) { return; } // Countdown of days since the loss of the last town, first day isn't counted if ( world.CountDay() > 1 && castles.empty() && lost_town_days > 0 ) { --lost_town_days; } // Check the basic conditions of losing the game if ( isLoss() || 0 == lost_town_days ) { LossPostActions(); return; } // Reset the effect of the "Identify Hero" spell ResetModes( IDENTIFYHERO ); } void Kingdom::ActionNewDayResourceUpdate( const std::function & displayEventDialog ) { // Skip the income for the first day if ( world.CountDay() > 1 ) { AddFundsResource( GetIncome() ); // Resource bonuses from campaign awards if ( isControlHuman() && Settings::Get().isCampaignGameType() ) { const std::vector campaignAwards = Campaign::CampaignSaveData::Get().getObtainedCampaignAwards(); for ( size_t i = 0; i < campaignAwards.size(); ++i ) { if ( campaignAwards[i]._type != Campaign::CampaignAwardData::TYPE_RESOURCE_BONUS ) { continue; } AddFundsResource( Funds( campaignAwards[i]._subType, campaignAwards[i]._amount ) ); } } } const bool isAIPlayer = ( GetControl() == CONTROL_AI ); // Resources from events const EventsDate events = world.GetEventsDate( GetColor() ); for ( const EventDate & event : events ) { if ( isAIPlayer && !event.isApplicableForAIPlayers ) { continue; } const Funds fundsUpdate = Resource::CalculateEventResourceUpdate( GetFunds(), event.resource ); AddFundsResource( fundsUpdate ); if ( displayEventDialog ) displayEventDialog( event, fundsUpdate ); } } void Kingdom::ActionNewWeek() { // Clear the visited objects with a lifetime of one week, even if this kingdom has already been vanquished visit_object.remove_if( Visit::isWeekLife ); if ( !isPlay() ) { return; } // Skip the first week if ( world.CountWeek() > 1 ) { // Additional gift in debug mode if ( IS_DEVEL() && isControlHuman() ) { Funds gift( 20, 20, 10, 10, 10, 10, 5000 ); DEBUG_LOG( DBG_GAME, DBG_INFO, "debug gift: " << gift.String() ) resource += gift; } } // Settle a new set of recruits GetRecruits(); } void Kingdom::ActionNewMonth() { // Clear the visited objects with a lifetime of one month, even if this kingdom has already been vanquished visit_object.remove_if( Visit::isMonthLife ); } void Kingdom::AddHero( Heroes * hero ) { if ( hero == nullptr ) { // Why are you adding an empty hero? assert( 0 ); return; } if ( heroes.end() == std::find( heroes.begin(), heroes.end(), hero ) ) { heroes.push_back( hero ); } AI::Get().HeroesAdd( *hero ); } void Kingdom::RemoveHero( const Heroes * hero ) { if ( hero != nullptr ) { if ( !heroes.empty() ) { auto it = std::find( heroes.begin(), heroes.end(), hero ); assert( it != heroes.end() ); if ( it != heroes.end() ) { heroes.erase( it ); } } Player * player = Players::Get( GetColor() ); if ( player && player->GetFocus().GetHeroes() == hero ) { player->GetFocus().Reset(); } assert( hero != nullptr ); AI::Get().HeroesRemove( *hero ); } else { // Why are trying to delete a non existing hero? assert( 0 ); } if ( isLoss() ) LossPostActions(); } void Kingdom::AddCastle( const Castle * castle ) { if ( castle ) { if ( castles.end() == std::find( castles.begin(), castles.end(), castle ) ) castles.push_back( const_cast( castle ) ); const Player * player = Settings::Get().GetPlayers().GetCurrent(); if ( player && player->isColor( GetColor() ) ) Interface::AdventureMap::Get().GetIconsPanel().ResetIcons( ICON_CASTLES ); AI::Get().CastleAdd( *castle ); } lost_town_days = Game::GetLostTownDays() + 1; } void Kingdom::RemoveCastle( const Castle * castle ) { if ( castle ) { if ( !castles.empty() ) { auto it = std::find( castles.begin(), castles.end(), castle ); assert( it != castles.end() ); if ( it != castles.end() ) { castles.erase( it ); } } Player * player = Players::Get( GetColor() ); if ( player && player->GetFocus().GetCastle() == castle ) { player->GetFocus().Reset(); } assert( castle != nullptr ); AI::Get().CastleRemove( *castle ); } if ( isLoss() ) LossPostActions(); } uint32_t Kingdom::GetCountCastle() const { return static_cast( std::count_if( castles.begin(), castles.end(), Castle::PredicateIsCastle ) ); } uint32_t Kingdom::GetCountTown() const { return static_cast( std::count_if( castles.begin(), castles.end(), Castle::PredicateIsTown ) ); } uint32_t Kingdom::GetCountMarketplace() const { return static_cast( std::count_if( castles.begin(), castles.end(), []( const Castle * castle ) { return Castle::PredicateIsBuildBuilding( castle, BUILD_MARKETPLACE ); } ) ); } uint32_t Kingdom::GetCountNecromancyShrineBuild() const { return static_cast( std::count_if( castles.begin(), castles.end(), []( const Castle * castle ) { return castle->isNecromancyShrineBuild(); } ) ); } uint32_t Kingdom::GetCountBuilding( uint32_t build ) const { return static_cast( std::count_if( castles.begin(), castles.end(), [build]( const Castle * castle ) { return castle->isBuild( build ); } ) ); } uint32_t Kingdom::GetCountThievesGuild() const { return static_cast( std::count_if( castles.begin(), castles.end(), []( const Castle * castle ) { return Castle::PredicateIsBuildBuilding( castle, BUILD_THIEVESGUILD ); } ) ); } uint32_t Kingdom::GetCountArtifacts() const { uint32_t result = 0; for ( const Heroes * hero : heroes ) result += hero->GetCountArtifacts(); return result; } bool Kingdom::AllowPayment( const Funds & funds ) const { return ( resource.wood >= funds.wood || 0 == funds.wood ) && ( resource.mercury >= funds.mercury || 0 == funds.mercury ) && ( resource.ore >= funds.ore || 0 == funds.ore ) && ( resource.sulfur >= funds.sulfur || 0 == funds.sulfur ) && ( resource.crystal >= funds.crystal || 0 == funds.crystal ) && ( resource.gems >= funds.gems || 0 == funds.gems ) && ( resource.gold >= funds.gold || 0 == funds.gold ); } bool Kingdom::isVisited( const Maps::Tiles & tile ) const { return isVisited( tile.GetIndex(), tile.GetObject( false ) ); } bool Kingdom::isVisited( int32_t index, const MP2::MapObjectType objectType ) const { std::list::const_iterator it = std::find_if( visit_object.begin(), visit_object.end(), [index]( const IndexObject & v ) { return v.isIndex( index ); } ); return visit_object.end() != it && ( *it ).isObject( objectType ); } bool Kingdom::isVisited( const MP2::MapObjectType objectType ) const { return std::any_of( visit_object.begin(), visit_object.end(), [objectType]( const IndexObject & v ) { return v.isObject( objectType ); } ); } uint32_t Kingdom::CountVisitedObjects( const MP2::MapObjectType objectType ) const { // Safe to downcast as we don't deal with gigantic amount of data. return static_cast( std::count_if( visit_object.begin(), visit_object.end(), [objectType]( const IndexObject & v ) { return v.isObject( objectType ); } ) ); } void Kingdom::SetVisited( int32_t index, const MP2::MapObjectType objectType ) { if ( !isVisited( index, objectType ) && objectType != MP2::OBJ_NONE ) visit_object.emplace_front( index, objectType ); } bool Kingdom::isValidKingdomObject( const Maps::Tiles & tile, const MP2::MapObjectType objectType ) const { if ( !MP2::isActionObject( objectType ) ) return false; if ( isVisited( tile.GetIndex(), objectType ) ) return false; // Check castle first to ignore guest hero (tile with both Castle and Hero) if ( tile.GetObject( false ) == MP2::OBJ_CASTLE ) { const int tileColor = getColorFromTile( tile ); // Castle can only be visited if it either belongs to this kingdom or is an enemy castle (in the latter case, an attack may occur) return color == tileColor || !Players::isFriends( color, tileColor ); } // Hero object can overlay other objects when standing on top of it: force check with GetObject( true ) if ( objectType == MP2::OBJ_HERO ) { const Heroes * hero = tile.getHero(); // Hero can only be met if he either belongs to this kingdom or is an enemy hero (in the latter case, an attack will occur) return hero && ( color == hero->GetColor() || !Players::isFriends( color, hero->GetColor() ) ); } if ( MP2::isCaptureObject( objectType ) ) return !Players::isFriends( color, getColorFromTile( tile ) ); if ( MP2::isValuableResourceObject( objectType ) ) return doesTileContainValuableItems( tile ); return true; } bool Kingdom::opponentsCanRecruitMoreHeroes() const { for ( int opponentColor : Players::getInPlayOpponents( GetColor() ) ) { if ( world.GetKingdom( opponentColor ).canRecruitHeroes() ) return true; } return false; } bool Kingdom::opponentsHaveHeroes() const { for ( int opponentColor : Players::getInPlayOpponents( GetColor() ) ) { if ( world.GetKingdom( opponentColor ).hasHeroes() ) return true; } return false; } bool Kingdom::HeroesMayStillMove() const { return std::any_of( heroes.begin(), heroes.end(), []( const Heroes * hero ) { return hero->MayStillMove( false, false ); } ); } void Kingdom::AddFundsResource( const Funds & funds ) { resource = resource + funds; resource.Trim(); } void Kingdom::OddFundsResource( const Funds & funds ) { resource = resource - funds; resource.Trim(); } uint32_t Kingdom::GetLostTownDays() const { return lost_town_days; } const Recruits & Kingdom::GetRecruits() { // At the beginning of a new week, in case there are no heroes who retreated or surrendered on the last day of the previous week and should be available for hire // (this mechanic is an extension of fheroes2 and is missing from the original game), it is necessary to offer one native hero const bool offerNativeHero = ( recruits.GetID1() == Heroes::UNKNOWN && recruits.GetID2() == Heroes::UNKNOWN ); // Special hero given as a campaign award Heroes * specialHireableHero = nullptr; // If there is a hero received as a campaign award, then this hero should be offered instead of a native hero at the beginning of the first week if ( isControlHuman() && Settings::Get().isCampaignGameType() && offerNativeHero && world.CountWeek() < 2 ) { const std::vector obtainedAwards = Campaign::CampaignSaveData::Get().getObtainedCampaignAwards(); for ( const auto & obtainedAward : obtainedAwards ) { if ( obtainedAward._type != Campaign::CampaignAwardData::TYPE_HIREABLE_HERO ) { continue; } Heroes * hero = world.GetHeroes( obtainedAward._subType ); if ( hero && hero->isAvailableForHire() ) { specialHireableHero = hero; } } } if ( recruits.GetID1() == Heroes::UNKNOWN || ( recruits.GetHero1() && !recruits.GetHero1()->isAvailableForHire() ) ) { if ( specialHireableHero ) { recruits.SetHero1( specialHireableHero ); } else { recruits.SetHero1( world.GetHeroForHire( offerNativeHero ? GetRace() : Race::NONE, recruits.GetID2() ) ); } } if ( recruits.GetID2() == Heroes::UNKNOWN || ( recruits.GetHero2() && !recruits.GetHero2()->isAvailableForHire() ) ) { recruits.SetHero2( world.GetHeroForHire( Race::NONE, recruits.GetID1() ) ); } assert( recruits.GetID1() != recruits.GetID2() && recruits.GetID1() != Heroes::UNKNOWN && recruits.GetID2() != Heroes::UNKNOWN ); return recruits; } Recruits & Kingdom::GetCurrentRecruits() { return recruits; } Puzzle & Kingdom::PuzzleMaps() { return puzzle_maps; } void Kingdom::SetVisitTravelersTent( int col ) { // visited_tents_color is a bitfield visited_tents_colors |= ( 1 << col ); } bool Kingdom::IsVisitTravelersTent( int col ) const { // visited_tents_color is a bitfield return ( visited_tents_colors & ( 1 << col ) ) != 0; } bool Kingdom::AllowRecruitHero( bool check_payment ) const { return ( heroes.size() < GetMaxHeroes() ) && ( !check_payment || AllowPayment( PaymentConditions::RecruitHero() ) ); } void Kingdom::ApplyPlayWithStartingHero() { if ( !isPlay() || castles.empty() ) return; bool foundHeroes = false; for ( const Castle * castle : castles ) { if ( castle == nullptr ) continue; // check manual set hero (castle position + point(0, 1))? const fheroes2::Point & cp = castle->GetCenter(); Heroes * hero = world.GetTiles( cp.x, cp.y + 1 ).getHero(); // and move manual set hero to castle if ( hero && hero->GetColor() == GetColor() ) { const bool patrol = hero->Modes( Heroes::PATROL ); if ( hero->isValid() ) { hero->Move2Dest( Maps::GetIndexFromAbsPoint( cp ) ); } else { hero->Dismiss( 0 ); hero->Recruit( *castle ); } if ( patrol ) { hero->SetModes( Heroes::PATROL ); hero->SetPatrolCenter( cp ); } foundHeroes = true; } } if ( !foundHeroes && Settings::Get().getCurrentMapInfo().startWithHeroInEachCastle ) { // get first castle const Castle * first = castles.GetFirstCastle(); if ( nullptr == first ) first = castles.front(); Heroes * hero = world.GetHeroForHire( first->GetRace() ); if ( hero && AllowRecruitHero( false ) ) hero->Recruit( *first ); } } uint32_t Kingdom::GetMaxHeroes() { return GameStatic::GetKingdomMaxHeroes(); } Funds Kingdom::GetIncome( int type /* INCOME_ALL */ ) const { Funds totalIncome; if ( INCOME_CAPTURED & type ) { // captured object const int resources[8] = { Resource::WOOD, Resource::ORE, Resource::MERCURY, Resource::SULFUR, Resource::CRYSTAL, Resource::GEMS, Resource::GOLD, Resource::UNKNOWN }; for ( uint32_t index = 0; resources[index] != Resource::UNKNOWN; ++index ) totalIncome += ProfitConditions::FromMine( resources[index] ) * world.CountCapturedMines( resources[index], GetColor() ); } if ( INCOME_CASTLES & type ) { // castles for ( const Castle * castle : castles ) { assert( castle != nullptr ); // castle or town profit totalIncome += ProfitConditions::FromBuilding( ( castle->isCastle() ? BUILD_CASTLE : BUILD_TENT ), 0 ); // statue if ( castle->isBuild( BUILD_STATUE ) ) totalIncome += ProfitConditions::FromBuilding( BUILD_STATUE, 0 ); // dungeon for warlock if ( castle->isBuild( BUILD_SPEC ) && Race::WRLK == castle->GetRace() ) totalIncome += ProfitConditions::FromBuilding( BUILD_SPEC, Race::WRLK ); } } if ( INCOME_ARTIFACTS & type ) { for ( const Heroes * hero : heroes ) { const BagArtifacts & bag = hero->GetBagArtifacts(); for ( const Artifact & artifact : bag ) { totalIncome += ProfitConditions::FromArtifact( artifact.GetID() ); } } } if ( INCOME_HERO_SKILLS & type ) { // estates skill bonus for ( const Heroes * hero : heroes ) { assert( hero != nullptr ); totalIncome.gold += hero->GetSecondaryValues( Skill::Secondary::ESTATES ); } } if ( ( type & INCOME_CAMPAIGN_BONUS ) && Settings::Get().isCampaignGameType() ) { const std::vector awards = Campaign::CampaignSaveData::Get().getObtainedCampaignAwards(); for ( const Campaign::CampaignAwardData & award : awards ) { if ( award._type != Campaign::CampaignAwardData::TYPE_RESOURCE_BONUS ) { continue; } totalIncome += Funds( award._subType, award._amount ); } } if ( isControlAI() && totalIncome.gold > 0 ) { const int32_t bonusGold = static_cast( totalIncome.gold * Difficulty::getGoldIncomeBonusForAI( Game::getDifficulty() ) ); totalIncome.gold += bonusGold; } // Some human players can have handicap for resources. const Player * player = Players::Get( color ); assert( player != nullptr ); return getHandicapDependentIncome( totalIncome, player->getHandicapStatus() ); } Heroes * Kingdom::GetBestHero() const { return !heroes.empty() ? *std::max_element( heroes.begin(), heroes.end(), HeroesStrongestArmy ) : nullptr; } Monster Kingdom::GetStrongestMonster() const { Monster monster( Monster::UNKNOWN ); for ( const Heroes * hero : heroes ) { const Monster currentMonster = hero->GetArmy().GetStrongestMonster(); if ( currentMonster.GetMonsterStrength() > monster.GetMonsterStrength() ) { monster = currentMonster; } } for ( const Castle * castle : castles ) { const Monster currentMonster = castle->GetArmy().GetStrongestMonster(); if ( currentMonster.GetMonsterStrength() > monster.GetMonsterStrength() ) { monster = currentMonster; } } return monster; } double Kingdom::GetArmiesStrength() const { double res = 0; for ( const Heroes * hero : heroes ) { assert( hero != nullptr ); res += hero->GetArmy().GetStrength(); } for ( const Castle * castle : castles ) { assert( castle != nullptr ); res += castle->GetArmy().GetStrength(); } return res; } void Kingdoms::Init() { const Colors colors( Settings::Get().GetPlayers().GetColors() ); clear(); for ( Colors::const_iterator it = colors.begin(); it != colors.end(); ++it ) GetKingdom( *it ).Init( *it ); } void Kingdoms::clear() { for ( Kingdom & kingdom : kingdoms ) kingdom.clear(); } void Kingdoms::ApplyPlayWithStartingHero() { for ( Kingdom & kingdom : kingdoms ) if ( kingdom.isPlay() ) kingdom.ApplyPlayWithStartingHero(); } const Kingdom & Kingdoms::GetKingdom( int color ) const { switch ( color ) { case Color::BLUE: return kingdoms[0]; case Color::GREEN: return kingdoms[1]; case Color::RED: return kingdoms[2]; case Color::YELLOW: return kingdoms[3]; case Color::ORANGE: return kingdoms[4]; case Color::PURPLE: return kingdoms[5]; default: break; } return kingdoms[6]; } Kingdom & Kingdoms::GetKingdom( int color ) { switch ( color ) { case Color::BLUE: return kingdoms[0]; case Color::GREEN: return kingdoms[1]; case Color::RED: return kingdoms[2]; case Color::YELLOW: return kingdoms[3]; case Color::ORANGE: return kingdoms[4]; case Color::PURPLE: return kingdoms[5]; default: break; } return kingdoms[6]; } void Kingdom::SetLastBattleWinHero( const Heroes & hero ) { _lastBattleWinHeroID = hero.GetID(); } Heroes * Kingdom::GetLastBattleWinHero() const { return Heroes::UNKNOWN != _lastBattleWinHeroID ? world.GetHeroes( _lastBattleWinHeroID ) : nullptr; } void Kingdom::appendSurrenderedHero( Heroes & hero ) { recruits.appendSurrenderedHero( hero, world.CountDay() ); } void Kingdoms::NewDay() { for ( Kingdom & kingdom : kingdoms ) { kingdom.ActionNewDay(); } } void Kingdoms::NewWeek() { for ( Kingdom & kingdom : kingdoms ) { kingdom.ActionNewWeek(); } } void Kingdoms::NewMonth() { for ( Kingdom & kingdom : kingdoms ) { kingdom.ActionNewMonth(); } } int Kingdoms::GetNotLossColors() const { int result = 0; for ( const Kingdom & kingdom : kingdoms ) if ( kingdom.GetColor() && !kingdom.isLoss() ) result |= kingdom.GetColor(); return result; } int Kingdoms::FindWins( int cond ) const { for ( const Kingdom & kingdom : kingdoms ) if ( kingdom.GetColor() && world.KingdomIsWins( kingdom, cond ) ) return kingdom.GetColor(); return 0; } void Kingdoms::AddHeroes( const AllHeroes & heroes ) { for ( Heroes * hero : heroes ) { assert( hero != nullptr ); if ( hero->GetColor() != Color::NONE ) { GetKingdom( hero->GetColor() ).AddHero( hero ); } } } void Kingdoms::AddCastles( const AllCastles & castles ) { for ( const Castle * castle : castles ) { // skip gray color if ( castle->GetColor() ) GetKingdom( castle->GetColor() ).AddCastle( castle ); } } std::set Kingdoms::resetRecruits() { std::set remainingRecruits; for ( Kingdom & kingdom : kingdoms ) { Recruits & recruits = kingdom.GetCurrentRecruits(); // Heroes who retreated or surrendered on the last day of the previous week should still be available for recruitment next week in the same kingdom, provided that // this kingdom is still playable. This mechanic is an extension of fheroes2 and is missing from the original game. if ( !kingdom.isPlay() || world.CountDay() - recruits.getSurrenderDayOfHero1() > 1 ) { recruits.SetHero1( nullptr ); } else { Heroes * hero = recruits.GetHero1(); if ( hero ) { remainingRecruits.insert( hero ); } } if ( !kingdom.isPlay() || world.CountDay() - recruits.getSurrenderDayOfHero2() > 1 ) { recruits.SetHero2( nullptr ); } else { Heroes * hero = recruits.GetHero2(); if ( hero ) { remainingRecruits.insert( hero ); } } } return remainingRecruits; } bool Kingdom::IsTileVisibleFromCrystalBall( const int32_t dest ) const { for ( const Heroes * hero : heroes ) { assert( hero != nullptr ); if ( hero->GetBagArtifacts().isArtifactBonusPresent( fheroes2::ArtifactBonusType::VIEW_MONSTER_INFORMATION ) ) { const uint32_t crystalBallDistance = hero->GetVisionsDistance(); if ( Maps::GetStraightLineDistance( hero->GetIndex(), dest ) <= crystalBallDistance ) { return true; } } } return false; } cost_t Kingdom::_getKingdomStartingResources( const int difficulty ) const { if ( isControlAI() ) { switch ( difficulty ) { case Difficulty::EASY: case Difficulty::NORMAL: return { 7500, 20, 5, 20, 5, 5, 5 }; case Difficulty::HARD: case Difficulty::EXPERT: case Difficulty::IMPOSSIBLE: return { 10000, 30, 10, 30, 10, 10, 10 }; default: // Did you add a new difficulty level? assert( 0 ); break; } return { 10000, 30, 10, 30, 10, 10, 10 }; } switch ( difficulty ) { case Difficulty::EASY: return { 10000, 30, 10, 30, 10, 10, 10 }; case Difficulty::NORMAL: return { 7500, 20, 5, 20, 5, 5, 5 }; case Difficulty::HARD: return { 5000, 10, 2, 10, 2, 2, 2 }; case Difficulty::EXPERT: return { 2500, 5, 0, 5, 0, 0, 0 }; case Difficulty::IMPOSSIBLE: return { 0, 0, 0, 0, 0, 0, 0 }; default: // Did you add a new difficulty level? assert( 0 ); break; } return { 7500, 20, 5, 20, 5, 5, 5 }; } StreamBase & operator<<( StreamBase & msg, const Kingdom & kingdom ) { return msg << kingdom.modes << kingdom.color << kingdom.resource << kingdom.lost_town_days << kingdom.castles << kingdom.heroes << kingdom.recruits << kingdom.visit_object << kingdom.puzzle_maps << kingdom.visited_tents_colors << kingdom._lastBattleWinHeroID << kingdom._topCastleInKingdomView << kingdom._topHeroInKingdomView; } StreamBase & operator>>( StreamBase & msg, Kingdom & kingdom ) { msg >> kingdom.modes >> kingdom.color >> kingdom.resource >> kingdom.lost_town_days >> kingdom.castles >> kingdom.heroes >> kingdom.recruits >> kingdom.visit_object >> kingdom.puzzle_maps >> kingdom.visited_tents_colors >> kingdom._lastBattleWinHeroID; static_assert( LAST_SUPPORTED_FORMAT_VERSION < FORMAT_VERSION_1010_RELEASE, "Remove the logic below." ); if ( Game::GetVersionOfCurrentSaveFile() < FORMAT_VERSION_1010_RELEASE ) { ++kingdom._lastBattleWinHeroID; } return msg >> kingdom._topCastleInKingdomView >> kingdom._topHeroInKingdomView; } StreamBase & operator<<( StreamBase & msg, const Kingdoms & obj ) { msg << Kingdoms::_size; for ( const Kingdom & kingdom : obj.kingdoms ) msg << kingdom; return msg; } StreamBase & operator>>( StreamBase & msg, Kingdoms & obj ) { uint32_t kingdomscount = 0; msg >> kingdomscount; if ( kingdomscount <= Kingdoms::_size ) { for ( uint32_t i = 0; i < kingdomscount; ++i ) msg >> obj.kingdoms[i]; } return msg; } fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/kingdom.h000066400000000000000000000176231456075706000220070ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2KINGDOM_H #define H2KINGDOM_H #include #include #include #include #include #include "bitmodes.h" #include "castle.h" #include "gamedefs.h" #include "heroes.h" #include "heroes_recruits.h" #include "monster.h" #include "mp2.h" #include "pairs.h" #include "players.h" #include "puzzle.h" #include "resource.h" class StreamBase; struct EventDate; namespace Maps { class Tiles; } class Kingdom : public BitModes, public Control { public: enum { // UNDEF = 0x0001, IDENTIFYHERO = 0x0002, // UNUSED = 0x0004, KINGDOM_OVERVIEW_CASTLE_SELECTION = 0x0008 }; enum { INCOME_CAPTURED = 0x01, INCOME_CASTLES = 0x02, INCOME_ARTIFACTS = 0x04, INCOME_HERO_SKILLS = 0x08, INCOME_CAMPAIGN_BONUS = 0x10, INCOME_ALL = 0xFF }; Kingdom(); ~Kingdom() override = default; void Init( int color ); void clear(); void openOverviewDialog(); bool isPlay() const; bool isLoss() const; bool AllowPayment( const Funds & ) const; bool AllowRecruitHero( bool check_payment ) const; // Return true if this kingdom can recruit heroes, false otherwise. For // example this function will return false when kingdom has one town that // cannot be upgraded to a castle. bool canRecruitHeroes() const { return std::any_of( castles.begin(), castles.end(), []( const Castle * castle ) { return ( castle->isCastle() || castle->Modes( Castle::ALLOWCASTLE ) ); } ); } // Return true if this kingdom has any heroes, false otherwise. bool hasHeroes() const { return !heroes.empty(); } void SetLastBattleWinHero( const Heroes & hero ); Heroes * GetLastBattleWinHero() const; void appendSurrenderedHero( Heroes & hero ); Heroes * GetBestHero() const; Monster GetStrongestMonster() const; int GetControl() const override; int GetColor() const; int GetRace() const; const Funds & GetFunds() const { return resource; } Funds GetIncome( int type = INCOME_ALL ) const; double GetArmiesStrength() const; void AddFundsResource( const Funds & ); void OddFundsResource( const Funds & ); bool isLosingGame() const { return castles.empty(); } uint32_t GetCountCastle() const; uint32_t GetCountTown() const; uint32_t GetCountMarketplace() const; uint32_t GetLostTownDays() const; uint32_t GetCountNecromancyShrineBuild() const; uint32_t GetCountBuilding( uint32_t ) const; uint32_t GetCountThievesGuild() const; uint32_t GetCountArtifacts() const; // Returns a reference to the pair of heroes available for recruitment, // updating it on the fly if necessary const Recruits & GetRecruits(); // Returns a reference to the pair of heroes available for recruitment // without making any changes in it Recruits & GetCurrentRecruits(); const VecHeroes & GetHeroes() const { return heroes; } const VecCastles & GetCastles() const { return castles; } VecHeroes & GetHeroes() { return heroes; } VecCastles & GetCastles() { return castles; } void AddHero( Heroes * hero ); void RemoveHero( const Heroes * hero ); void ApplyPlayWithStartingHero(); void AddCastle( const Castle * ); void RemoveCastle( const Castle * ); void ActionBeforeTurn(); void ActionNewDay(); void ActionNewWeek(); void ActionNewMonth(); void ActionNewDayResourceUpdate( const std::function & displayEventDialog ); void SetVisited( int32_t index, const MP2::MapObjectType objectType ); uint32_t CountVisitedObjects( const MP2::MapObjectType objectType ) const; bool isVisited( const MP2::MapObjectType objectType ) const; bool isVisited( const Maps::Tiles & ) const; bool isVisited( int32_t, const MP2::MapObjectType objectType ) const; bool isValidKingdomObject( const Maps::Tiles & tile, const MP2::MapObjectType objectType ) const; bool opponentsCanRecruitMoreHeroes() const; bool opponentsHaveHeroes() const; bool HeroesMayStillMove() const; Puzzle & PuzzleMaps(); void SetVisitTravelersTent( int color ); bool IsVisitTravelersTent( int ) const; void LossPostActions(); // Checks whether this tile is visible to any hero who has an artifact with the VIEW_MONSTER_INFORMATION // bonus (for example, a Crystal Ball) bool IsTileVisibleFromCrystalBall( const int32_t dest ) const; static uint32_t GetMaxHeroes(); private: cost_t _getKingdomStartingResources( const int difficulty ) const; friend StreamBase & operator<<( StreamBase &, const Kingdom & ); friend StreamBase & operator>>( StreamBase &, Kingdom & ); int color; int _lastBattleWinHeroID; Funds resource; uint32_t lost_town_days; VecCastles castles; VecHeroes heroes; Recruits recruits; std::list visit_object; Puzzle puzzle_maps; uint32_t visited_tents_colors; // Used to remember which item was selected in Kingdom View dialog. int _topCastleInKingdomView; int _topHeroInKingdomView; }; class Kingdoms { public: Kingdoms() = default; void Init(); void clear(); void ApplyPlayWithStartingHero(); void NewDay(); void NewWeek(); void NewMonth(); Kingdom & GetKingdom( int color ); const Kingdom & GetKingdom( int color ) const; int GetNotLossColors() const; int FindWins( int ) const; void AddHeroes( const AllHeroes & ); void AddCastles( const AllCastles & ); // Resets recruits in all kingdoms and returns a set of heroes that are still available for recruitment // in the kingdoms std::set resetRecruits(); private: friend StreamBase & operator<<( StreamBase &, const Kingdoms & ); friend StreamBase & operator>>( StreamBase &, Kingdoms & ); static constexpr uint32_t _size = KINGDOMMAX + 1; Kingdom kingdoms[_size]; }; StreamBase & operator<<( StreamBase &, const Kingdom & ); StreamBase & operator>>( StreamBase &, Kingdom & ); StreamBase & operator<<( StreamBase &, const Kingdoms & ); StreamBase & operator>>( StreamBase &, Kingdoms & ); #endif fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/kingdom_overview.cpp000066400000000000000000001073361456075706000242710ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2012 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include "agg_image.h" #include "army_bar.h" #include "artifact.h" #include "buildinginfo.h" #include "captain.h" #include "castle.h" #include "cursor.h" #include "dialog.h" #include "game.h" #include "game_hotkeys.h" #include "game_interface.h" #include "heroes.h" #include "heroes_base.h" #include "icn.h" #include "image.h" #include "interface_base.h" #include "interface_icons.h" #include "interface_list.h" #include "kingdom.h" #include "localevent.h" #include "math_base.h" #include "mp2.h" #include "resource.h" #include "screen.h" #include "skill.h" #include "skill_bar.h" #include "tools.h" #include "translations.h" #include "ui_button.h" #include "ui_dialog.h" #include "ui_kingdom.h" #include "ui_scrollbar.h" #include "ui_text.h" #include "ui_tool.h" #include "ui_window.h" #include "world.h" namespace { const int32_t scrollbarOffset = 626; bool needFadeIn{ false }; std::string CapturedExtInfoString( const int resource, const int color, const Funds & funds ) { std::string output = std::to_string( world.CountCapturedMines( resource, color ) ); const int32_t vals = funds.Get( resource ); if ( vals != 0 ) { output += " ("; if ( vals > 0 ) { output += '+'; } output += std::to_string( vals ); output += ')'; } return output; } void redrawCommonBackground( const fheroes2::Point & dst, const int visibleItems, fheroes2::Image & output ) { // Scrollbar background. const fheroes2::Sprite & scrollbar = fheroes2::AGG::GetICN( ICN::OVERVIEW, 13 ); fheroes2::Copy( scrollbar, 0, 0, output, dst.x + scrollbarOffset + 1, dst.y + 17, scrollbar.width(), scrollbar.height() ); // Items background. const fheroes2::Sprite & itemsBack = fheroes2::AGG::GetICN( ICN::OVERVIEW, 8 ); const fheroes2::Sprite & overback = fheroes2::AGG::GetICN( ICN::OVERBACK, 0 ); const int32_t itemsBackWidth = itemsBack.width(); const int32_t itemsBackHeight = itemsBack.height(); const int32_t offsetX = dst.x + 30; int32_t offsetY = dst.y + 17; const int32_t stepY = itemsBackHeight + 4; const int32_t overbackOffsetX = dst.x + 29; int32_t overbackOffsetY = dst.y + 13; for ( int i = 0; i < visibleItems; ++i, offsetY += stepY, overbackOffsetY += stepY ) { fheroes2::Copy( itemsBack, 0, 0, output, offsetX, offsetY, itemsBackWidth, itemsBackHeight ); // Horizontal yellow "grid" lines. fheroes2::Copy( overback, 29, 13, output, overbackOffsetX, overbackOffsetY, 595, 4 ); } // Copy one vertical line in case of previous army selection. fheroes2::Copy( overback, 29, 12, output, overbackOffsetX, dst.y + 12, 1, 357 ); } } struct HeroRow { Heroes * hero{ nullptr }; std::unique_ptr armyBar; std::unique_ptr artifactsBar; std::unique_ptr secSkillsBar; std::unique_ptr primSkillsBar; explicit HeroRow( Heroes * ptr = nullptr ) { assert( ptr != nullptr ); Init( ptr ); } HeroRow( const HeroRow & ) = delete; HeroRow & operator=( const HeroRow & ) = delete; HeroRow( HeroRow && ) = default; HeroRow & operator=( HeroRow && ) = default; ~HeroRow() = default; void Init( Heroes * ptr ) { hero = ptr; armyBar = std::make_unique( &hero->GetArmy(), true, false ); armyBar->SetBackground( { 41, 53 }, fheroes2::GetColorId( 72, 28, 0 ) ); armyBar->setTableSize( { 5, 1 } ); armyBar->setInBetweenItemsOffset( { -1, 0 } ); armyBar->setTroopWindowOffsetY( -60 ); artifactsBar = std::make_unique( hero, true, false, false, true, nullptr ); artifactsBar->setTableSize( { 7, 2 } ); artifactsBar->setInBetweenItemsOffset( { 1, 8 } ); artifactsBar->SetContent( hero->GetBagArtifacts() ); secSkillsBar = std::make_unique( *hero ); secSkillsBar->setTableSize( { 4, 2 } ); secSkillsBar->setInBetweenItemsOffset( { -1, 8 } ); secSkillsBar->SetContent( hero->GetSecondarySkills().ToVector() ); primSkillsBar = std::make_unique( hero, true ); primSkillsBar->setTableSize( { 4, 1 } ); primSkillsBar->setInBetweenItemsOffset( { 2, 0 } ); primSkillsBar->SetTextOff( 20, -13 ); } }; class StatsHeroesList : public Interface::ListBox { public: StatsHeroesList( const fheroes2::Rect & windowArea, const fheroes2::Point & offset, const VecHeroes & heroes ); bool Refresh( VecHeroes & heroes ); void RedrawItem( const HeroRow & row, int32_t dstx, int32_t dsty, bool current ) override; void RedrawBackground( const fheroes2::Point & dst ) override; void ActionCurrentUp() override { // Do nothing. } void ActionCurrentDn() override { // Do nothing. } void ActionListSingleClick( HeroRow & /* unused */ ) override { // Do nothing. } void ActionListDoubleClick( HeroRow & /* unused */ ) override { // Do nothing. } void ActionListPressRight( HeroRow & /* unused */ ) override { // Do nothing. } void ActionListSingleClick( HeroRow & row, const fheroes2::Point & cursor, int32_t ox, int32_t oy ) override; void ActionListDoubleClick( HeroRow & row, const fheroes2::Point & cursor, int32_t ox, int32_t oy ) override; void ActionListPressRight( HeroRow & row, const fheroes2::Point & cursor, int32_t ox, int32_t oy ) override; bool ActionListCursor( HeroRow & row, const fheroes2::Point & cursor ) override; private: void SetContent( const VecHeroes & heroes ); std::vector content; const fheroes2::Rect _windowArea; }; StatsHeroesList::StatsHeroesList( const fheroes2::Rect & windowArea, const fheroes2::Point & offset, const VecHeroes & heroes ) : Interface::ListBox( offset ) , _windowArea( windowArea ) { const fheroes2::Sprite & back = fheroes2::AGG::GetICN( ICN::OVERVIEW, 13 ); const int32_t backHeight = back.height(); SetTopLeft( offset ); const int32_t offsetX = offset.x + scrollbarOffset; setScrollBarArea( { offsetX + 2, offset.y + 18, back.width(), backHeight - 2 } ); const fheroes2::Sprite & originalSlider = fheroes2::AGG::GetICN( ICN::SCROLL, 4 ); const fheroes2::Image scrollbarSlider = fheroes2::generateScrollbarSlider( originalSlider, false, backHeight - 2, 4, static_cast( heroes.size() ), { 0, 0, originalSlider.width(), 8 }, { 0, 7, originalSlider.width(), 8 } ); setScrollBarImage( scrollbarSlider ); SetScrollButtonUp( ICN::SCROLL, 0, 1, { offsetX, offset.y } ); SetScrollButtonDn( ICN::SCROLL, 2, 3, { offsetX, offset.y + 20 + backHeight } ); SetAreaMaxItems( 4 ); SetAreaItems( { offset.x + 30, offset.y + 17, 594, 344 } ); SetContent( heroes ); } void StatsHeroesList::SetContent( const VecHeroes & heroes ) { content.clear(); content.reserve( heroes.size() ); for ( Heroes * hero : heroes ) { content.emplace_back( hero ); } SetListContent( content ); } // Updates the UI list according to current list of kingdom heroes. // Returns true if we updated something bool StatsHeroesList::Refresh( VecHeroes & heroes ) { const size_t contentSize = content.size(); if ( heroes.size() != contentSize ) { const fheroes2::Sprite & back = fheroes2::AGG::GetICN( ICN::OVERVIEW, 13 ); const fheroes2::Sprite & originalSlider = fheroes2::AGG::GetICN( ICN::SCROLL, 4 ); const fheroes2::Image scrollbarSlider = fheroes2::generateScrollbarSlider( originalSlider, false, back.height() - 2, 4, static_cast( heroes.size() ), { 0, 0, originalSlider.width(), 8 }, { 0, 7, originalSlider.width(), 8 } ); setScrollBarImage( scrollbarSlider ); SetContent( heroes ); return true; } for ( size_t i = 0; i < contentSize; ++i ) { if ( heroes[i] != content[i].hero ) { SetContent( heroes ); return true; } } return false; } void StatsHeroesList::ActionListDoubleClick( HeroRow & row, const fheroes2::Point & cursor, int32_t ox, int32_t oy ) { ActionListSingleClick( row, cursor, ox, oy ); } void StatsHeroesList::ActionListSingleClick( HeroRow & row, const fheroes2::Point & cursor, int32_t ox, int32_t oy ) { if ( row.hero && ( fheroes2::Rect( ox + 5, oy + 4, Interface::IconsBar::GetItemWidth(), Interface::IconsBar::GetItemHeight() ) & cursor ) ) { Game::OpenHeroesDialog( *row.hero, false, false ); needFadeIn = true; } } void StatsHeroesList::ActionListPressRight( HeroRow & row, const fheroes2::Point & cursor, int32_t ox, int32_t oy ) { if ( row.hero && ( fheroes2::Rect( ox + 5, oy + 4, Interface::IconsBar::GetItemWidth(), Interface::IconsBar::GetItemHeight() ) & cursor ) ) { Dialog::QuickInfoWithIndicationOnRadar( *row.hero, _windowArea ); } } bool StatsHeroesList::ActionListCursor( HeroRow & row, const fheroes2::Point & cursor ) { const fheroes2::Point cursorPos( cursor.x, cursor.y ); if ( ( row.armyBar->GetArea() & cursorPos ) && row.armyBar->QueueEventProcessing() ) { if ( row.artifactsBar->isSelected() ) { row.artifactsBar->ResetSelected(); } return true; } if ( ( row.artifactsBar->GetArea() & cursorPos ) && row.artifactsBar->QueueEventProcessing() ) { if ( row.armyBar->isSelected() ) { row.armyBar->ResetSelected(); } return true; } if ( ( row.primSkillsBar->GetArea() & cursorPos ) && row.primSkillsBar->QueueEventProcessing() ) { return true; } if ( ( row.secSkillsBar->GetArea() & cursorPos ) && row.secSkillsBar->QueueEventProcessing() ) { return true; } return false; } void StatsHeroesList::RedrawItem( const HeroRow & row, int32_t dstx, int32_t dsty, bool current ) { (void)current; if ( row.hero == nullptr ) { // No hero to draw. return; } fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::Blit( fheroes2::AGG::GetICN( ICN::OVERVIEW, 10 ), display, dstx, dsty ); // base info Interface::RedrawHeroesIcon( *row.hero, dstx + 5, dsty + 4 ); int32_t offsetX = dstx + 90; const int32_t offsetY = dsty + 22; fheroes2::Text text( std::to_string( row.hero->GetAttack() ), fheroes2::FontType::smallWhite() ); text.draw( offsetX - text.width(), offsetY, display ); offsetX += 35; text.set( std::to_string( row.hero->GetDefense() ), fheroes2::FontType::smallWhite() ); text.draw( offsetX - text.width(), offsetY, display ); offsetX += 35; text.set( std::to_string( row.hero->GetPower() ), fheroes2::FontType::smallWhite() ); text.draw( offsetX - text.width(), offsetY, display ); offsetX += 35; text.set( std::to_string( row.hero->GetKnowledge() ), fheroes2::FontType::smallWhite() ); text.draw( offsetX - text.width(), offsetY, display ); // primary skills info row.primSkillsBar->setRenderingOffset( { dstx + 56, dsty - 3 } ); row.primSkillsBar->Redraw( display ); // secondary skills info row.secSkillsBar->setRenderingOffset( { dstx + 206, dsty + 3 } ); row.secSkillsBar->Redraw( display ); // artifacts info row.artifactsBar->setRenderingOffset( { dstx + 348, dsty + 3 } ); row.artifactsBar->Redraw( display ); // army info row.armyBar->setRenderingOffset( { dstx - 1, dsty + 30 } ); row.armyBar->Redraw( display ); } void StatsHeroesList::RedrawBackground( const fheroes2::Point & dst ) { fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::Sprite & header = fheroes2::AGG::GetICN( ICN::OVERVIEW, 6 ); fheroes2::Copy( header, 0, 0, display, dst.x + 30, dst.y, header.width(), header.height() ); int32_t offsetY = dst.y + 3; fheroes2::Text text( _( "Hero/Stats" ), fheroes2::FontType::smallWhite() ); text.draw( dst.x + 130 - text.width() / 2, offsetY, display ); text.set( _( "Skills" ), fheroes2::FontType::smallWhite() ); text.draw( dst.x + 300 - text.width() / 2, offsetY, display ); text.set( _( "Artifacts" ), fheroes2::FontType::smallWhite() ); text.draw( dst.x + 500 - text.width() / 2, offsetY, display ); redrawCommonBackground( dst, VisibleItemCount(), display ); } struct CstlRow { Castle * castle{ nullptr }; std::unique_ptr garrisonArmyBar; std::unique_ptr heroArmyBar; std::unique_ptr dwellingsBar; explicit CstlRow( Castle * ptr = nullptr ) { assert( ptr != nullptr ); Init( ptr ); } CstlRow( const CstlRow & ) = delete; CstlRow & operator=( const CstlRow & ) = delete; CstlRow( CstlRow && ) = default; CstlRow & operator=( CstlRow && ) = default; ~CstlRow() = default; void Init( Castle * ptr ) { castle = ptr; garrisonArmyBar = std::make_unique( &castle->GetArmy(), true, false ); garrisonArmyBar->SetBackground( { 41, 41 }, fheroes2::GetColorId( 40, 12, 0 ) ); garrisonArmyBar->setTableSize( { 5, 1 } ); garrisonArmyBar->setInBetweenItemsOffset( { -1, 0 } ); garrisonArmyBar->setTroopWindowOffsetY( -60 ); updateHeroArmyBar(); dwellingsBar = std::make_unique( *castle, fheroes2::Size{ 39, 52 } ); dwellingsBar->setTableSize( { 6, 1 } ); dwellingsBar->setInBetweenItemsOffset( { 2, 0 } ); } void updateHeroArmyBar() { Heroes * hero = world.GetHero( *castle ); if ( hero ) { heroArmyBar = std::make_unique( &hero->GetArmy(), true, false ); heroArmyBar->SetBackground( { 41, 41 }, fheroes2::GetColorId( 40, 12, 0 ) ); heroArmyBar->setTableSize( { 5, 1 } ); heroArmyBar->setInBetweenItemsOffset( { -1, 0 } ); } else { heroArmyBar.reset(); } } }; class StatsCastlesList : public Interface::ListBox { public: StatsCastlesList( const fheroes2::Rect & windowArea, const fheroes2::Point & offset, const VecCastles & castles ); void RedrawItem( const CstlRow & row, int32_t dstx, int32_t dsty, bool current ) override; void RedrawBackground( const fheroes2::Point & dst ) override; void ActionCurrentUp() override { // Do nothing. } void ActionCurrentDn() override { // Do nothing. } void ActionListDoubleClick( CstlRow & /* unused */ ) override { // Do nothing. } void ActionListSingleClick( CstlRow & /* unused */ ) override { // Do nothing. } void ActionListPressRight( CstlRow & /* unused */ ) override { // Do nothing. } void ActionListSingleClick( CstlRow & row, const fheroes2::Point & cursor, int32_t ox, int32_t oy ) override; void ActionListDoubleClick( CstlRow & row, const fheroes2::Point & cursor, int32_t ox, int32_t oy ) override; void ActionListPressRight( CstlRow & row, const fheroes2::Point & cursor, int32_t ox, int32_t oy ) override; bool ActionListCursor( CstlRow & row, const fheroes2::Point & cursor ) override; void updateHeroArmyBars() { for ( CstlRow & row : content ) { row.updateHeroArmyBar(); } } private: std::vector content; const fheroes2::Rect _windowArea; }; StatsCastlesList::StatsCastlesList( const fheroes2::Rect & windowArea, const fheroes2::Point & offset, const VecCastles & castles ) : Interface::ListBox( offset ) , _windowArea( windowArea ) { const fheroes2::Sprite & back = fheroes2::AGG::GetICN( ICN::OVERVIEW, 13 ); const int32_t backHeight = back.height(); SetTopLeft( offset ); const int32_t offsetX = offset.x + scrollbarOffset; setScrollBarArea( { offsetX + 2, offset.y + 18, back.width(), backHeight - 2 } ); const fheroes2::Sprite & originalSlider = fheroes2::AGG::GetICN( ICN::SCROLL, 4 ); const fheroes2::Image scrollbarSlider = fheroes2::generateScrollbarSlider( originalSlider, false, back.height() - 2, 4, static_cast( castles.size() ), { 0, 0, originalSlider.width(), 8 }, { 0, 7, originalSlider.width(), 8 } ); setScrollBarImage( scrollbarSlider ); SetScrollButtonUp( ICN::SCROLL, 0, 1, { offsetX, offset.y } ); SetScrollButtonDn( ICN::SCROLL, 2, 3, { offsetX, offset.y + 20 + backHeight } ); SetAreaMaxItems( 4 ); SetAreaItems( { offset.x + 30, offset.y + 17, 594, 344 } ); content.reserve( castles.size() ); for ( Castle * castle : castles ) { content.emplace_back( castle ); } SetListContent( content ); } void StatsCastlesList::ActionListDoubleClick( CstlRow & row, const fheroes2::Point & cursor, int32_t ox, int32_t oy ) { ActionListSingleClick( row, cursor, ox, oy ); } void StatsCastlesList::ActionListSingleClick( CstlRow & row, const fheroes2::Point & cursor, int32_t ox, int32_t oy ) { if ( row.castle ) { // click castle icon if ( fheroes2::Rect( ox + 17, oy + 19, Interface::IconsBar::GetItemWidth(), Interface::IconsBar::GetItemHeight() ) & cursor ) { Game::OpenCastleDialog( *row.castle, false, false ); } // click hero icon else if ( fheroes2::Rect( ox + 82, oy + 19, Interface::IconsBar::GetItemWidth(), Interface::IconsBar::GetItemHeight() ) & cursor ) { Heroes * hero = row.castle->GetHero(); if ( !hero ) { return; } Game::OpenHeroesDialog( *hero, false, false ); } else { return; } needFadeIn = true; } } void StatsCastlesList::ActionListPressRight( CstlRow & row, const fheroes2::Point & cursor, int32_t ox, int32_t oy ) { if ( row.castle ) { if ( fheroes2::Rect( ox + 17, oy + 19, Interface::IconsBar::GetItemWidth(), Interface::IconsBar::GetItemHeight() ) & cursor ) { Dialog::QuickInfoWithIndicationOnRadar( *row.castle, _windowArea ); } else if ( fheroes2::Rect( ox + 82, oy + 19, Interface::IconsBar::GetItemWidth(), Interface::IconsBar::GetItemHeight() ) & cursor ) { const Heroes * hero = row.castle->GetHero(); if ( hero ) { Dialog::QuickInfoWithIndicationOnRadar( *hero, _windowArea ); } else if ( row.castle->isBuild( BUILD_CAPTAIN ) ) { Dialog::QuickInfoWithIndicationOnRadar( row.castle->GetCaptain(), _windowArea ); } } } } bool StatsCastlesList::ActionListCursor( CstlRow & row, const fheroes2::Point & cursor ) { const fheroes2::Point cursorPos( cursor.x, cursor.y ); if ( row.garrisonArmyBar && ( row.garrisonArmyBar->GetArea() & cursorPos ) && ( row.heroArmyBar ? row.garrisonArmyBar->QueueEventProcessing( *row.heroArmyBar ) : row.garrisonArmyBar->QueueEventProcessing() ) ) { if ( row.heroArmyBar && row.heroArmyBar->isSelected() ) { row.heroArmyBar->ResetSelected(); } return true; } if ( row.heroArmyBar && ( row.heroArmyBar->GetArea() & cursorPos ) && ( row.garrisonArmyBar ? row.heroArmyBar->QueueEventProcessing( *row.garrisonArmyBar ) : row.heroArmyBar->QueueEventProcessing() ) ) { if ( row.garrisonArmyBar && row.garrisonArmyBar->isSelected() ) { row.garrisonArmyBar->ResetSelected(); } return true; } if ( row.dwellingsBar && ( row.dwellingsBar->GetArea() & cursorPos ) && row.dwellingsBar->QueueEventProcessing() ) { if ( row.heroArmyBar && row.heroArmyBar->isSelected() ) { row.heroArmyBar->ResetSelected(); } if ( row.garrisonArmyBar && row.garrisonArmyBar->isSelected() ) { row.garrisonArmyBar->ResetSelected(); } return true; } return false; } void StatsCastlesList::RedrawItem( const CstlRow & row, int32_t dstx, int32_t dsty, bool current ) { (void)current; if ( row.castle == nullptr ) { // No castle to draw. return; } fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::Blit( fheroes2::AGG::GetICN( ICN::OVERVIEW, 11 ), display, dstx, dsty ); // base info Interface::RedrawCastleIcon( *row.castle, dstx + 17, dsty + 19 ); const Heroes * hero = row.castle->GetHero(); if ( hero ) { Interface::RedrawHeroesIcon( *hero, dstx + 82, dsty + 19 ); const std::string sep = "-"; const fheroes2::Text text( std::to_string( hero->GetAttack() ) + sep + std::to_string( hero->GetDefense() ) + sep + std::to_string( hero->GetPower() ) + sep + std::to_string( hero->GetKnowledge() ), fheroes2::FontType::smallWhite() ); text.draw( dstx + 104 - text.width() / 2, dsty + 45, display ); } else if ( row.castle->GetCaptain().isValid() ) { const Captain & captain = row.castle->GetCaptain(); captain.PortraitRedraw( dstx + 82, dsty + 19, PORT_SMALL, display ); const std::string sep = "-"; const fheroes2::Text text( std::to_string( captain.GetAttack() ) + sep + std::to_string( captain.GetDefense() ) + sep + std::to_string( captain.GetPower() ) + sep + std::to_string( captain.GetKnowledge() ), fheroes2::FontType::smallWhite() ); text.draw( dstx + 104 - text.width() / 2, dsty + 45, display ); } const fheroes2::Text text( row.castle->GetName(), fheroes2::FontType::smallWhite() ); text.draw( dstx + 72 - text.width() / 2, dsty + 63, display ); // army info if ( row.garrisonArmyBar ) { row.garrisonArmyBar->setRenderingOffset( { dstx + 146, row.heroArmyBar ? dsty : dsty + 20 } ); row.garrisonArmyBar->Redraw( display ); } if ( row.heroArmyBar ) { row.heroArmyBar->setRenderingOffset( { dstx + 146, row.garrisonArmyBar ? dsty + 41 : dsty + 20 } ); row.heroArmyBar->Redraw( display ); } row.dwellingsBar->setRenderingOffset( { dstx + 349, dsty + 15 } ); row.dwellingsBar->Redraw( display ); } void StatsCastlesList::RedrawBackground( const fheroes2::Point & dst ) { fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::Sprite & header = fheroes2::AGG::GetICN( ICN::OVERVIEW, 7 ); fheroes2::Copy( header, 0, 0, display, dst.x + 30, dst.y, header.width(), header.height() ); int32_t offsetY = dst.y + 3; fheroes2::Text text( _( "Town/Castle" ), fheroes2::FontType::smallWhite() ); text.draw( dst.x + 105 - text.width() / 2, offsetY, display ); text.set( _( "Garrison" ), fheroes2::FontType::smallWhite() ); text.draw( dst.x + 275 - text.width() / 2, offsetY, display ); text.set( _( "Available" ), fheroes2::FontType::smallWhite() ); text.draw( dst.x + 500 - text.width() / 2, offsetY, display ); redrawCommonBackground( dst, VisibleItemCount(), display ); } void RedrawIncomeInfo( const fheroes2::Point & pt, const Kingdom & myKingdom ) { const Funds income = myKingdom.GetIncome( Kingdom::INCOME_ARTIFACTS | Kingdom::INCOME_HERO_SKILLS | Kingdom::INCOME_CAMPAIGN_BONUS ); const int32_t offsetY = pt.y + 410; fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::Text text( CapturedExtInfoString( Resource::WOOD, myKingdom.GetColor(), income ), fheroes2::FontType::smallWhite() ); text.draw( pt.x + 54 - text.width() / 2, offsetY, display ); text.set( CapturedExtInfoString( Resource::MERCURY, myKingdom.GetColor(), income ), fheroes2::FontType::smallWhite() ); text.draw( pt.x + 146 - text.width() / 2, offsetY, display ); text.set( CapturedExtInfoString( Resource::ORE, myKingdom.GetColor(), income ), fheroes2::FontType::smallWhite() ); text.draw( pt.x + 228 - text.width() / 2, offsetY, display ); text.set( CapturedExtInfoString( Resource::SULFUR, myKingdom.GetColor(), income ), fheroes2::FontType::smallWhite() ); text.draw( pt.x + 294 - text.width() / 2, offsetY, display ); text.set( CapturedExtInfoString( Resource::CRYSTAL, myKingdom.GetColor(), income ), fheroes2::FontType::smallWhite() ); text.draw( pt.x + 360 - text.width() / 2, offsetY, display ); text.set( CapturedExtInfoString( Resource::GEMS, myKingdom.GetColor(), income ), fheroes2::FontType::smallWhite() ); text.draw( pt.x + 428 - text.width() / 2, offsetY, display ); text.set( CapturedExtInfoString( Resource::GOLD, myKingdom.GetColor(), income ), fheroes2::FontType::smallWhite() ); text.draw( pt.x + 494 - text.width() / 2, offsetY, display ); } void RedrawFundsInfo( const fheroes2::Point & pt, const Kingdom & myKingdom ) { const Funds & funds = myKingdom.GetFunds(); int32_t offsetY = pt.y + 450; fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::Blit( fheroes2::AGG::GetICN( ICN::OVERBACK, 0 ), 4, 422, display, pt.x + 4, pt.y + 422, 530, 56 ); fheroes2::Text text( std::to_string( funds.wood ), fheroes2::FontType::smallWhite() ); text.draw( pt.x + 56 - text.width() / 2, offsetY, display ); text.set( std::to_string( funds.mercury ), fheroes2::FontType::smallWhite() ); text.draw( pt.x + 146 - text.width() / 2, offsetY, display ); text.set( std::to_string( funds.ore ), fheroes2::FontType::smallWhite() ); text.draw( pt.x + 226 - text.width() / 2, offsetY, display ); text.set( std::to_string( funds.sulfur ), fheroes2::FontType::smallWhite() ); text.draw( pt.x + 294 - text.width() / 2, offsetY, display ); text.set( std::to_string( funds.crystal ), fheroes2::FontType::smallWhite() ); text.draw( pt.x + 362 - text.width() / 2, offsetY, display ); text.set( std::to_string( funds.gems ), fheroes2::FontType::smallWhite() ); text.draw( pt.x + 428 - text.width() / 2, offsetY, display ); text.set( std::to_string( funds.gold ), fheroes2::FontType::smallWhite() ); text.draw( pt.x + 496 - text.width() / 2, offsetY, display ); offsetY += 14; text.set( _( "Gold Per Day:" ) + std::string( " " ) + std::to_string( myKingdom.GetIncome().Get( Resource::GOLD ) ), fheroes2::FontType::smallWhite() ); text.draw( pt.x + 180, offsetY, display ); std::string msg = _( "Day: %{day}" ); StringReplace( msg, "%{day}", world.GetDay() ); text.set( msg, fheroes2::FontType::smallWhite() ); text.draw( pt.x + 360, offsetY, display ); // Show Lighthouse count const uint32_t lighthouseCount = world.CountCapturedObject( MP2::OBJ_LIGHTHOUSE, myKingdom.GetColor() ); text.set( std::to_string( lighthouseCount ), fheroes2::FontType::smallWhite() ); text.draw( pt.x + 105, offsetY, display ); const fheroes2::Sprite & lighthouse = fheroes2::AGG::GetICN( ICN::OVERVIEW, 14 ); fheroes2::Blit( lighthouse, 0, 0, display, pt.x + 100 - lighthouse.width(), pt.y + 459, lighthouse.width(), lighthouse.height() ); } void Kingdom::openOverviewDialog() { Game::SetUpdateSoundsOnFocusUpdate( false ); fheroes2::Display & display = fheroes2::Display::instance(); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); const fheroes2::StandardWindow background( display.DEFAULT_WIDTH, display.DEFAULT_HEIGHT, false ); // Fade-out game screen only for 640x480 resolution. const bool isDefaultScreenSize = display.isDefaultSize(); if ( isDefaultScreenSize ) { fheroes2::fadeOutDisplay(); } const fheroes2::Point cur_pt( background.activeArea().x, background.activeArea().y ); fheroes2::Point dst_pt( cur_pt ); fheroes2::Blit( fheroes2::AGG::GetICN( ICN::OVERBACK, 0 ), display, dst_pt.x, dst_pt.y ); RedrawIncomeInfo( cur_pt, *this ); RedrawFundsInfo( cur_pt, *this ); StatsHeroesList listHeroes( background.windowArea(), dst_pt, heroes ); StatsCastlesList listCastles( background.windowArea(), dst_pt, castles ); // buttons dst_pt.x = cur_pt.x + 540; dst_pt.y = cur_pt.y + 360; fheroes2::Button buttonHeroes( dst_pt.x, dst_pt.y, ICN::BUTTON_KINGDOM_HEROES, 0, 1 ); // We need to additionally render the background between HEROES and TOWNS/CASTLES buttons. dst_pt.y += 42; fheroes2::Copy( fheroes2::AGG::GetICN( ICN::OVERBACK, 0 ), 540, 444, display, dst_pt.x, dst_pt.y, 99, 5 ); dst_pt.y += 3; fheroes2::Button buttonCastle( dst_pt.x, dst_pt.y, ICN::BUTTON_KINGDOM_TOWNS, 0, 1 ); dst_pt.y += 48; fheroes2::Button buttonExit( dst_pt.x, dst_pt.y, ICN::BUTTON_KINGDOM_EXIT, 0, 1 ); const fheroes2::Rect rectIncome( cur_pt.x + 1, cur_pt.y + 360, 535, 60 ); const fheroes2::Rect rectGoldPerDay( cur_pt.x + 124, cur_pt.y + 459, 289, 16 ); const fheroes2::Sprite & lighthouse = fheroes2::AGG::GetICN( ICN::OVERVIEW, 14 ); const fheroes2::Rect rectLighthouse( cur_pt.x + 100 - lighthouse.width(), cur_pt.y + 459, lighthouse.width() + 10, lighthouse.height() ); Interface::ListBasic * listStats = nullptr; if ( Modes( KINGDOM_OVERVIEW_CASTLE_SELECTION ) ) { // set state view: castles buttonCastle.press(); buttonHeroes.release(); listStats = &listCastles; } else { // set state view: heroes buttonHeroes.press(); buttonCastle.release(); listStats = &listHeroes; } listCastles.setTopVisibleItem( _topCastleInKingdomView ); listHeroes.setTopVisibleItem( _topHeroInKingdomView ); listStats->Redraw(); buttonHeroes.draw(); buttonCastle.draw(); buttonExit.draw(); // Fade-in Kingdom overview dialog. if ( !isDefaultScreenSize ) { // We need to expand the ROI for the next render to properly render window borders and shadow. display.updateNextRenderRoi( background.totalArea() ); } fheroes2::fadeInDisplay( background.activeArea(), !isDefaultScreenSize ); LocalEvent & le = LocalEvent::Get(); bool redraw = true; uint32_t worldMapRedrawMask = 0; // dialog menu loop while ( le.HandleEvents() ) { le.MousePressLeft( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); if ( le.MousePressRight( buttonHeroes.area() ) ) { fheroes2::showStandardTextMessage( _( "Heroes" ), _( "View Heroes." ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonCastle.area() ) ) { fheroes2::showStandardTextMessage( _( "Towns/Castles" ), _( "View Towns and Castles." ), Dialog::ZERO ); } else if ( le.MousePressRight( buttonExit.area() ) ) { fheroes2::showStandardTextMessage( _( "Exit" ), _( "Exit this menu." ), Dialog::ZERO ); } else if ( le.MousePressRight( rectIncome ) || le.MousePressRight( rectGoldPerDay ) ) { fheroes2::showKingdomIncome( *this, Dialog::ZERO ); } else if ( le.MousePressRight( rectLighthouse ) ) { fheroes2::showLighthouseInfo( *this, Dialog::ZERO ); } // Exit this dialog. if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) { // Fade-out Kingdom overview dialog. fheroes2::fadeOutDisplay( background.activeArea(), !isDefaultScreenSize ); if ( isDefaultScreenSize ) { Game::setDisplayFadeIn(); } break; } // switch view: heroes/castle if ( buttonHeroes.isReleased() && le.MouseClickLeft( buttonHeroes.area() ) ) { buttonHeroes.drawOnPress(); buttonCastle.drawOnRelease(); listStats = &listHeroes; ResetModes( KINGDOM_OVERVIEW_CASTLE_SELECTION ); redraw = true; } else if ( buttonCastle.isReleased() && le.MouseClickLeft( buttonCastle.area() ) ) { buttonCastle.drawOnPress(); buttonHeroes.drawOnRelease(); listStats = &listCastles; SetModes( KINGDOM_OVERVIEW_CASTLE_SELECTION ); redraw = true; } redraw |= listStats->QueueEventProcessing(); if ( le.MouseClickLeft( rectIncome ) || le.MouseClickLeft( rectGoldPerDay ) ) { fheroes2::showKingdomIncome( *this, Dialog::OK ); } if ( le.MouseClickLeft( rectLighthouse ) ) { fheroes2::showLighthouseInfo( *this, Dialog::OK ); } if ( !listStats->IsNeedRedraw() && !redraw ) { continue; } // Check if graphics in main world map window should change, // this can happen if hero was hired or dismissed: hero icon list is updated. // So, it's equivalent to check if hero list changed. if ( listHeroes.Refresh( heroes ) ) { worldMapRedrawMask |= Interface::AdventureMap::Get().getRedrawMask(); worldMapRedrawMask |= Interface::REDRAW_HEROES; // Update army bars in Castles. listCastles.updateHeroArmyBars(); } listStats->Redraw(); RedrawIncomeInfo( cur_pt, *this ); if ( Modes( KINGDOM_OVERVIEW_CASTLE_SELECTION ) ) { // Funds could be changed only after an action in the castle. RedrawFundsInfo( cur_pt, *this ); } if ( needFadeIn ) { needFadeIn = false; fheroes2::fadeInDisplay( background.activeArea(), !isDefaultScreenSize ); } else { display.render(); } redraw = false; } _topCastleInKingdomView = listCastles.getTopId(); _topHeroInKingdomView = listHeroes.getTopId(); Game::SetUpdateSoundsOnFocusUpdate( true ); Interface::AdventureMap & adventureMapInterface = Interface::AdventureMap::Get(); if ( worldMapRedrawMask != 0 ) { // Force redraw of all UI elements that changed, that were masked by Kingdom window adventureMapInterface.setRedraw( worldMapRedrawMask ); // Update focus because there were some changes made in the Kingdom overview dialog. adventureMapInterface.ResetFocus( Interface::GetFocusType(), false ); } // The army of the selected hero / castle may have been changed. adventureMapInterface.RedrawFocus(); } fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/luck.cpp000066400000000000000000000057431456075706000216500ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include "luck.h" #include "translations.h" std::string Luck::String( int luck ) { switch ( luck ) { case Luck::CURSED: return _( "luck|Cursed" ); case Luck::AWFUL: return _( "luck|Awful" ); case Luck::BAD: return _( "luck|Bad" ); case Luck::NORMAL: return _( "luck|Normal" ); case Luck::GOOD: return _( "luck|Good" ); case Luck::GREAT: return _( "luck|Great" ); case Luck::IRISH: return _( "luck|Irish" ); default: break; } return "Unknown"; } std::string Luck::Description( int luck ) { switch ( luck ) { case Luck::CURSED: case Luck::AWFUL: case Luck::BAD: return _( "Bad luck sometimes falls on the hero's units in combat, causing their attacks to only do half damage." ); case Luck::NORMAL: return _( "Neutral luck means the hero's units will never get lucky or unlucky attacks on the enemy." ); case Luck::GOOD: case Luck::GREAT: case Luck::IRISH: return _( "Good luck sometimes lets the hero's units get lucky attacks (double strength) in combat." ); default: break; } return "Unknown"; } int Luck::Normalize( const int luck ) { return std::clamp( luck, static_cast( Luck::CURSED ), static_cast( Luck::IRISH ) ); } fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/luck.h000066400000000000000000000040131456075706000213020ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2LUCK_H #define H2LUCK_H #include namespace Luck { enum { UNKNOWN = -4, CURSED = -3, AWFUL = -2, BAD = -1, NORMAL = 0, GOOD = 1, GREAT = 2, IRISH = 3 }; std::string String( int ); std::string Description( int ); int Normalize( const int luck ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/morale.cpp000066400000000000000000000057621456075706000221720ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include "morale.h" #include "translations.h" std::string Morale::String( int morale ) { switch ( morale ) { case Morale::TREASON: return _( "morale|Treason" ); case Morale::AWFUL: return _( "morale|Awful" ); case Morale::POOR: return _( "morale|Poor" ); case Morale::NORMAL: return _( "morale|Normal" ); case Morale::GOOD: return _( "morale|Good" ); case Morale::GREAT: return _( "morale|Great" ); case Morale::BLOOD: return _( "morale|Blood!" ); default: break; } return "Unknown"; } std::string Morale::Description( int morale ) { switch ( morale ) { case Morale::TREASON: case Morale::AWFUL: case Morale::POOR: return _( "Bad morale may cause the hero's units to freeze in combat." ); case Morale::NORMAL: return _( "Neutral morale means the hero's units will never be blessed with extra attacks or freeze in combat." ); case Morale::GOOD: case Morale::GREAT: case Morale::BLOOD: return _( "Good morale may give the hero's units extra attacks in combat." ); default: break; } return "Unknown"; } int Morale::Normalize( const int morale ) { return std::clamp( morale, static_cast( Morale::TREASON ), static_cast( Morale::BLOOD ) ); } fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/morale.h000066400000000000000000000040251456075706000216260ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2MORALE_H #define H2MORALE_H #include namespace Morale { enum { UNKNOWN = -4, TREASON = -3, AWFUL = -2, POOR = -1, NORMAL = 0, GOOD = 1, GREAT = 2, BLOOD = 3 }; std::string String( int ); std::string Description( int ); int Normalize( const int morale ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/payment.cpp000066400000000000000000000043131456075706000223570ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "payment.h" #include "buildinginfo.h" Funds PaymentConditions::BuyBuilding( int race, uint32_t build ) { return BuildingInfo::GetCost( build, race ); } Funds PaymentConditions::BuyBoat() { return Funds( cost_t{ 1000, 10, 0, 0, 0, 0, 0 } ); } Funds PaymentConditions::BuySpellBook() { return Funds( cost_t{ 500, 0, 0, 0, 0, 0, 0 } ); } Funds PaymentConditions::RecruitHero() { return Funds( cost_t{ 2500, 0, 0, 0, 0, 0, 0 } ); } Funds PaymentConditions::ForAlchemist() { return Funds( cost_t{ 750, 0, 0, 0, 0, 0, 0 } ); } fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/payment.h000066400000000000000000000036611456075706000220310ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2PAYMENT_H #define H2PAYMENT_H #include #include "resource.h" namespace PaymentConditions { Funds BuyBuilding( int race, uint32_t build ); Funds BuyBoat(); Funds BuySpellBook(); Funds RecruitHero(); Funds ForAlchemist(); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/profit.cpp000066400000000000000000000105671456075706000222150ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include "artifact_info.h" #include "castle.h" #include "profit.h" #include "race.h" #include "resource.h" Funds ProfitConditions::FromBuilding( uint32_t building, int race ) { switch ( building ) { case BUILD_CASTLE: return Funds( cost_t{ 1000, 0, 0, 0, 0, 0, 0 } ); case BUILD_TENT: case BUILD_STATUE: return Funds( cost_t{ 250, 0, 0, 0, 0, 0, 0 } ); case BUILD_SPEC: if ( race == Race::WRLK ) return Funds( cost_t{ 500, 0, 0, 0, 0, 0, 0 } ); break; default: break; } return {}; } Funds ProfitConditions::FromArtifact( int artifact ) { const fheroes2::ArtifactData & data = fheroes2::getArtifactData( artifact ); Funds cost; for ( const fheroes2::ArtifactBonus & bonus : data.bonuses ) { switch ( bonus.type ) { case fheroes2::ArtifactBonusType::GOLD_INCOME: cost.gold += bonus.value; break; case fheroes2::ArtifactBonusType::WOOD_INCOME: cost.wood += bonus.value; break; case fheroes2::ArtifactBonusType::ORE_INCOME: cost.ore += bonus.value; break; case fheroes2::ArtifactBonusType::MERCURY_INCOME: cost.mercury += bonus.value; break; case fheroes2::ArtifactBonusType::GEMS_INCOME: cost.gems += bonus.value; break; case fheroes2::ArtifactBonusType::SULFUR_INCOME: cost.sulfur += bonus.value; break; case fheroes2::ArtifactBonusType::CRYSTAL_INCOME: cost.crystal += bonus.value; break; default: break; } } for ( const fheroes2::ArtifactCurse & curse : data.curses ) { switch ( curse.type ) { case fheroes2::ArtifactCurseType::GOLD_PENALTY: cost.gold -= curse.value; break; default: break; } } return cost; } Funds ProfitConditions::FromMine( int type ) { switch ( type ) { case Resource::ORE: return Funds( cost_t{ 0, 0, 0, 2, 0, 0, 0 } ); case Resource::WOOD: return Funds( cost_t{ 0, 2, 0, 0, 0, 0, 0 } ); case Resource::MERCURY: return Funds( cost_t{ 0, 0, 1, 0, 0, 0, 0 } ); case Resource::SULFUR: return Funds( cost_t{ 0, 0, 0, 0, 1, 0, 0 } ); case Resource::CRYSTAL: return Funds( cost_t{ 0, 0, 0, 0, 0, 1, 0 } ); case Resource::GEMS: return Funds( cost_t{ 0, 0, 0, 0, 0, 0, 1 } ); case Resource::GOLD: return Funds( cost_t{ 1000, 0, 0, 0, 0, 0, 0 } ); default: // Did you add a new type of resources? Add the logic here! assert( 0 ); break; } return {}; } fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/profit.h000066400000000000000000000036121456075706000216530ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2PROFIT_H #define H2PROFIT_H #include #include "resource.h" namespace ProfitConditions { Funds FromBuilding( uint32_t building, int race ); Funds FromArtifact( int ); Funds FromMine( int ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/puzzle.cpp000066400000000000000000000345301456075706000222370ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "puzzle.h" #include #include #include #include #include #include #include "agg_image.h" #include "artifact_ultimate.h" #include "audio.h" #include "audio_manager.h" #include "cursor.h" #include "dialog.h" #include "game_delays.h" #include "game_hotkeys.h" #include "game_interface.h" #include "gamedefs.h" #include "icn.h" #include "image.h" #include "interface_gamearea.h" #include "interface_radar.h" #include "localevent.h" #include "logging.h" #include "math_base.h" #include "mus.h" #include "rand.h" #include "screen.h" #include "serialize.h" #include "settings.h" #include "ui_button.h" #include "ui_tool.h" #include "ui_window.h" #include "world.h" namespace { bool ClosedTilesExists( const Puzzle & pzl, const std::vector & zone ) { return std::any_of( zone.begin(), zone.end(), [&pzl]( const uint8_t tile ) { return !pzl.test( tile ); } ); } void ZoneOpenFirstTiles( Puzzle & pzl, size_t & opens, const std::vector & zone ) { while ( opens ) { std::vector::const_iterator it = zone.begin(); while ( it != zone.end() && pzl.test( *it ) ) ++it; if ( it != zone.end() ) { pzl.set( *it ); --opens; } else break; } } void drawPuzzle( const Puzzle & pzl, const fheroes2::Image & sf, int32_t dstx, int32_t dsty ) { fheroes2::Display & display = fheroes2::Display::instance(); // Immediately reveal the entire puzzle in developer mode if ( IS_DEVEL() ) { assert( sf.singleLayer() ); fheroes2::Copy( sf, 0, 0, display, dstx, dsty, sf.width(), sf.height() ); return; } for ( size_t i = 0; i < pzl.size(); ++i ) { const fheroes2::Sprite & piece = fheroes2::AGG::GetICN( ICN::PUZZLE, static_cast( i ) ); fheroes2::Blit( piece, display, dstx + piece.x() - BORDERWIDTH, dsty + piece.y() - BORDERWIDTH ); } } bool revealPuzzle( const Puzzle & pzl, const fheroes2::Image & sf, int32_t dstx, int32_t dsty, fheroes2::Button & buttonExit, const std::function * drawControlPanel = nullptr ) { // In developer mode, the entire puzzle should already be revealed if ( IS_DEVEL() ) { return false; } // The game area puzzle image should be single-layer. assert( sf.singleLayer() ); fheroes2::Display & display = fheroes2::Display::instance(); LocalEvent & le = LocalEvent::Get(); const std::vector delayTypes = { Game::PUZZLE_FADE_DELAY }; Game::passAnimationDelay( Game::PUZZLE_FADE_DELAY ); int alpha = 250; while ( alpha >= 0 && le.HandleEvents( Game::isDelayNeeded( delayTypes ) ) ) { le.MousePressLeft( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); // If exit button was pressed before reveal animation is finished, return true to indicate early exit. if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) { return true; } if ( Game::validateAnimationDelay( Game::PUZZLE_FADE_DELAY ) ) { fheroes2::Copy( sf, 0, 0, display, dstx, dsty, sf.width(), sf.height() ); for ( size_t i = 0; i < pzl.size(); ++i ) { const fheroes2::Sprite & piece = fheroes2::AGG::GetICN( ICN::PUZZLE, static_cast( i ) ); uint8_t pieceAlpha = 255; if ( pzl.test( i ) ) pieceAlpha = static_cast( alpha ); fheroes2::AlphaBlit( piece, display, dstx + piece.x() - BORDERWIDTH, dsty + piece.y() - BORDERWIDTH, pieceAlpha ); } if ( drawControlPanel ) { display.render( ( *drawControlPanel )() ); } display.render( { dstx, dsty, sf.width(), sf.height() } ); if ( alpha <= 0 ) { break; } alpha -= 10; assert( alpha >= 0 ); } } return false; } void ShowStandardDialog( const Puzzle & pzl, const fheroes2::Image & sf ) { fheroes2::Display & display = fheroes2::Display::instance(); const bool isEvilInterface = Settings::Get().isEvilInterfaceEnabled(); // Puzzle map is called only for the Adventure Map, not Editor. Interface::AdventureMap & adventureMapInterface = Interface::AdventureMap::Get(); const fheroes2::Rect & radarArea = adventureMapInterface.getRadar().GetArea(); fheroes2::ImageRestorer back( display, BORDERWIDTH, BORDERWIDTH, sf.width(), sf.height() ); fheroes2::ImageRestorer radarRestorer( display, radarArea.x, radarArea.y, radarArea.width, radarArea.height ); fheroes2::fadeOutDisplay( back.rect(), false ); fheroes2::Copy( fheroes2::AGG::GetICN( ( isEvilInterface ? ICN::EVIWPUZL : ICN::VIEWPUZL ), 0 ), 0, 0, display, radarArea ); display.updateNextRenderRoi( radarArea ); fheroes2::Button buttonExit( radarArea.x + 32, radarArea.y + radarArea.height - 37, ( isEvilInterface ? ICN::BUTTON_EXIT_PUZZLE_DIM_DOOR_EVIL : ICN::BUTTON_EXIT_PUZZLE_DIM_DOOR_GOOD ), 0, 1 ); buttonExit.draw(); drawPuzzle( pzl, sf, BORDERWIDTH, BORDERWIDTH ); display.updateNextRenderRoi( radarArea ); fheroes2::fadeInDisplay( back.rect(), false ); const bool earlyExit = revealPuzzle( pzl, sf, BORDERWIDTH, BORDERWIDTH, buttonExit ); LocalEvent & le = LocalEvent::Get(); while ( !earlyExit && le.HandleEvents() ) { le.MousePressLeft( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) { break; } } // Fade from puzzle map to adventure map. fheroes2::fadeOutDisplay( back.rect(), false ); back.restore(); radarRestorer.restore(); display.updateNextRenderRoi( radarArea ); fheroes2::fadeInDisplay( back.rect(), false ); adventureMapInterface.getGameArea().SetUpdateCursor(); } void ShowExtendedDialog( const Puzzle & pzl, const fheroes2::Image & sf ) { fheroes2::Display & display = fheroes2::Display::instance(); // Puzzle map is called only for the Adventure Map, not Editor. Interface::AdventureMap & adventureMapInterface = Interface::AdventureMap::Get(); Interface::GameArea & gameArea = adventureMapInterface.getGameArea(); const fheroes2::Rect & gameAreaRoi = gameArea.GetROI(); const Interface::Radar & radar = adventureMapInterface.getRadar(); const fheroes2::Rect & radarArea = radar.GetArea(); const fheroes2::ImageRestorer radarRestorer( display, radarArea.x, radarArea.y, radarArea.width, radarArea.height ); const fheroes2::StandardWindow border( gameAreaRoi.x + ( gameAreaRoi.width - sf.width() ) / 2, gameAreaRoi.y + ( gameAreaRoi.height - sf.height() ) / 2, sf.width(), sf.height(), false ); const fheroes2::Rect & puzzleArea = border.activeArea(); fheroes2::Image background( puzzleArea.width, puzzleArea.height ); const Settings & conf = Settings::Get(); const bool isEvilInterface = conf.isEvilInterfaceEnabled(); const bool isHideInterface = conf.isHideInterfaceEnabled(); if ( isEvilInterface ) { background.fill( fheroes2::GetColorId( 80, 80, 80 ) ); } else { background.fill( fheroes2::GetColorId( 128, 64, 32 ) ); } fheroes2::Copy( background, 0, 0, display, puzzleArea ); fheroes2::Button buttonExit( radarArea.x + 32, radarArea.y + radarArea.height - 37, ( isEvilInterface ? ICN::BUTTON_EXIT_PUZZLE_DIM_DOOR_EVIL : ICN::BUTTON_EXIT_PUZZLE_DIM_DOOR_GOOD ), 0, 1 ); const fheroes2::Rect & radarRect = radar.GetRect(); const std::function drawControlPanel = [&display, isEvilInterface, isHideInterface, &radarRect, &radarArea, &buttonExit]() { if ( isHideInterface ) { Dialog::FrameBorder::RenderRegular( radarRect ); } fheroes2::Copy( fheroes2::AGG::GetICN( ( isEvilInterface ? ICN::EVIWPUZL : ICN::VIEWPUZL ), 0 ), 0, 0, display, radarArea ); display.updateNextRenderRoi( radarArea ); buttonExit.draw(); return radarRect; }; drawPuzzle( pzl, sf, puzzleArea.x, puzzleArea.y ); drawControlPanel(); display.updateNextRenderRoi( border.totalArea() ); fheroes2::fadeInDisplay( border.activeArea(), true ); const bool earlyExit = revealPuzzle( pzl, sf, puzzleArea.x, puzzleArea.y, buttonExit, isHideInterface ? &drawControlPanel : nullptr ); LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() && !earlyExit ) { le.MousePressLeft( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) { break; } } fheroes2::fadeOutDisplay( border.activeArea(), true ); gameArea.SetUpdateCursor(); } } Puzzle::Puzzle() { Rand::Shuffle( zone1_order ); Rand::Shuffle( zone2_order ); Rand::Shuffle( zone3_order ); Rand::Shuffle( zone4_order ); } Puzzle & Puzzle::operator=( const char * str ) { while ( str && *str ) { *this <<= 1; if ( *str == 0x31 ) set( 0 ); ++str; } return *this; } void Puzzle::Update( uint32_t open_obelisk, uint32_t total_obelisk ) { const uint32_t open_puzzle = open_obelisk * PUZZLETILES / total_obelisk; size_t need_puzzle = open_puzzle > count() ? open_puzzle - count() : 0; if ( need_puzzle && ClosedTilesExists( *this, zone1_order ) ) ZoneOpenFirstTiles( *this, need_puzzle, zone1_order ); if ( need_puzzle && ClosedTilesExists( *this, zone2_order ) ) ZoneOpenFirstTiles( *this, need_puzzle, zone2_order ); if ( need_puzzle && ClosedTilesExists( *this, zone3_order ) ) ZoneOpenFirstTiles( *this, need_puzzle, zone3_order ); if ( need_puzzle && ClosedTilesExists( *this, zone4_order ) ) ZoneOpenFirstTiles( *this, need_puzzle, zone4_order ); } void Puzzle::ShowMapsDialog() const { const fheroes2::Image & sf = world.GetUltimateArtifact().GetPuzzleMapSurface(); if ( sf.empty() ) return; const fheroes2::Display & display = fheroes2::Display::instance(); // Set the cursor image. After this dialog the Game Area will be shown, so it does not require a cursor restorer. Cursor::Get().SetThemes( Cursor::POINTER ); // restore the original music on exit const AudioManager::MusicRestorer musicRestorer; AudioManager::PlayMusic( MUS::PUZZLE, Music::PlaybackMode::PLAY_ONCE ); if ( display.isDefaultSize() && !Settings::Get().isHideInterfaceEnabled() ) { ShowStandardDialog( *this, sf ); } else { ShowExtendedDialog( *this, sf ); } } StreamBase & operator<<( StreamBase & msg, const Puzzle & pzl ) { msg << pzl.to_string, std::allocator>(); // orders msg << static_cast( pzl.zone1_order.size() ); for ( const uint8_t tile : pzl.zone1_order ) msg << tile; msg << static_cast( pzl.zone2_order.size() ); for ( const uint8_t tile : pzl.zone2_order ) msg << tile; msg << static_cast( pzl.zone3_order.size() ); for ( const uint8_t tile : pzl.zone3_order ) msg << tile; msg << static_cast( pzl.zone4_order.size() ); for ( const uint8_t tile : pzl.zone4_order ) msg << tile; return msg; } StreamBase & operator>>( StreamBase & msg, Puzzle & pzl ) { std::string str; msg >> str; pzl = str.c_str(); uint8_t size; msg >> size; pzl.zone1_order.resize( size ); for ( uint8_t ii = 0; ii < size; ++ii ) msg >> pzl.zone1_order[ii]; msg >> size; pzl.zone2_order.resize( size ); for ( uint8_t ii = 0; ii < size; ++ii ) msg >> pzl.zone2_order[ii]; msg >> size; pzl.zone3_order.resize( size ); for ( uint8_t ii = 0; ii < size; ++ii ) msg >> pzl.zone3_order[ii]; msg >> size; pzl.zone4_order.resize( size ); for ( uint8_t ii = 0; ii < size; ++ii ) msg >> pzl.zone4_order[ii]; return msg; } fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/puzzle.h000066400000000000000000000047011456075706000217010ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2PUZZLE_H #define H2PUZZLE_H #include #include #include #define PUZZLETILES 48 class StreamBase; class Puzzle : public std::bitset { public: Puzzle(); Puzzle & operator=( const char * str ); void Update( uint32_t open, uint32_t total ); void ShowMapsDialog() const; std::vector zone1_order{ 0, 1, 2, 3, 4, 5, 6, 11, 12, 17, 18, 23, 24, 29, 30, 35, 36, 41, 42, 43, 44, 45, 46, 47 }; std::vector zone2_order{ 7, 8, 9, 10, 13, 16, 19, 22, 25, 28, 31, 34, 37, 38, 39, 40 }; std::vector zone3_order{ 14, 15, 32, 33 }; std::vector zone4_order{ 20, 21, 26, 27 }; }; StreamBase & operator<<( StreamBase &, const Puzzle & ); StreamBase & operator>>( StreamBase &, Puzzle & ); #endif fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/race.cpp000066400000000000000000000103431456075706000216140ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "race.h" #include #include "rand.h" #include "translations.h" const char * Race::String( int race ) { switch ( race ) { case Race::KNGT: return _( "Knight" ); case Race::BARB: return _( "Barbarian" ); case Race::SORC: return _( "Sorceress" ); case Race::WRLK: return _( "Warlock" ); case Race::WZRD: return _( "Wizard" ); case Race::NECR: return _( "Necromancer" ); case Race::MULT: return _( "Multi" ); case Race::RAND: return _( "race|Random" ); case Race::NONE: return _( "race|Neutral" ); default: // Did you add a new race? Add the logic above! assert( 0 ); break; } return _( "race|Neutral" ); } const char * Race::DoubleLinedString( int race ) { switch ( race ) { case Race::KNGT: return _( "doubleLined|Knight" ); case Race::BARB: return _( "doubleLined|Barbarian" ); case Race::SORC: return _( "doubleLined|Sorceress" ); case Race::WRLK: return _( "doubleLined|Warlock" ); case Race::WZRD: return _( "doubleLined|Wizard" ); case Race::NECR: return _( "doubleLined|Necro-\nmancer" ); case Race::MULT: return _( "doubleLinedRace|Multi" ); case Race::RAND: return _( "doubleLinedRace|Random" ); case Race::NONE: return _( "doubleLinedRace|Neutral" ); default: // Did you add a new race? Add the logic above! assert( 0 ); break; } return _( "doubleLinedRace|Neutral" ); } int Race::Rand() { switch ( Rand::Get( 1, 6 ) ) { case 1: return Race::KNGT; case 2: return Race::BARB; case 3: return Race::SORC; case 4: return Race::WRLK; case 5: return Race::WZRD; default: break; } return Race::NECR; } bool Race::isMagicalRace( const int race ) { switch ( race ) { case KNGT: case BARB: return false; case SORC: case WRLK: case WZRD: case NECR: return true; default: assert( 0 ); break; } return false; } uint8_t Race::IndexToRace( const int index ) { switch ( index ) { case 0: return Race::KNGT; case 1: return Race::BARB; case 2: return Race::SORC; case 3: return Race::WRLK; case 4: return Race::WZRD; case 5: return Race::NECR; case 6: return Race::MULT; case 7: return Race::RAND; default: break; } return Race::NONE; } fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/race.h000066400000000000000000000042701456075706000212630ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2RACE_H #define H2RACE_H #include namespace Race { enum { NONE = 0x00, KNGT = 0x01, BARB = 0x02, SORC = 0x04, WRLK = 0x08, WZRD = 0x10, NECR = 0x20, MULT = 0x40, RAND = 0x80, ALL = KNGT | BARB | SORC | WRLK | WZRD | NECR }; const char * String( int race ); const char * DoubleLinedString( int race ); int Rand(); uint8_t IndexToRace( const int index ); bool isMagicalRace( const int race ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/resource_trading.cpp000066400000000000000000000124501456075706000242420ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "resource_trading.h" #include #include #include #include "resource.h" namespace { // Resources for trade are divided into 3 categories: // - common such as Ore and Wood // - rare such as Mercury, Sulfur, Crystal and Gems // - Gold // // So we could have the following 8 combinations: // common --> common // common --> rare // common --> gold // rare --> common // rare --> rare // rare --> gold // gold --> common // gold --> rare // // However, common --> common and rare --> rare should have the same conversion ratio as these are within category. // So are left with 7 combinations. const size_t maxMarketplaces{ 9 }; const std::array resourceToResource{ 10, 7, 5, 4, 4, 3, 3, 3, 2 }; const std::array commonToRare{ 20, 14, 10, 8, 7, 6, 5, 5, 4 }; const std::array rareToCommon{ 5, 4, 3, 2, 2, 2, 2, 2, 1 }; const std::array commonToGold{ 25, 37, 50, 62, 74, 87, 100, 112, 124 }; const std::array rareToGold{ 50, 74, 100, 124, 149, 175, 200, 224, 249 }; const std::array goldToCommon{ 2500, 1667, 1250, 1000, 834, 715, 625, 556, 500 }; const std::array goldToRare{ 5000, 3334, 2500, 2000, 1667, 1429, 1250, 1112, 1000 }; } namespace fheroes2 { uint32_t getTradeCost( uint32_t marketplaceCount, const int resourceFrom, const int resourceTo ) { if ( marketplaceCount == 0 ) { assert( 0 ); return 0; } if ( resourceFrom == resourceTo || resourceFrom == Resource::UNKNOWN || resourceTo == Resource::UNKNOWN ) { // The resource is not selected (Resource::UNKNOWN) or resources are the same. // What are we trying to achieve? return 0; } if ( marketplaceCount > 9 ) { marketplaceCount = 9; } --marketplaceCount; switch ( resourceFrom ) { case Resource::GOLD: { switch ( resourceTo ) { case Resource::WOOD: case Resource::ORE: return goldToCommon[marketplaceCount]; case Resource::MERCURY: case Resource::SULFUR: case Resource::CRYSTAL: case Resource::GEMS: return goldToRare[marketplaceCount]; default: assert( 0 ); } return 0; } case Resource::WOOD: case Resource::ORE: { switch ( resourceTo ) { case Resource::WOOD: case Resource::ORE: return resourceToResource[marketplaceCount]; case Resource::GOLD: return commonToGold[marketplaceCount]; case Resource::MERCURY: case Resource::SULFUR: case Resource::CRYSTAL: case Resource::GEMS: return commonToRare[marketplaceCount]; default: assert( 0 ); } return 0; } case Resource::MERCURY: case Resource::SULFUR: case Resource::CRYSTAL: case Resource::GEMS: { switch ( resourceTo ) { case Resource::WOOD: case Resource::ORE: return rareToCommon[marketplaceCount]; case Resource::GOLD: return rareToGold[marketplaceCount]; case Resource::MERCURY: case Resource::SULFUR: case Resource::CRYSTAL: case Resource::GEMS: return resourceToResource[marketplaceCount]; default: assert( 0 ); } return 0; } default: assert( 0 ); return 0; } } } fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/resource_trading.h000066400000000000000000000031301456075706000237020ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include namespace fheroes2 { uint32_t getTradeCost( uint32_t marketplaceCount, const int resourceFrom, const int resourceTo ); } fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/speed.cpp000066400000000000000000000071601456075706000220050ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "speed.h" #include "spell.h" #include "translations.h" std::string Speed::String( int speed ) { switch ( speed ) { case STANDING: return _( "speed|Standing" ); case CRAWLING: return _( "speed|Crawling" ); case VERYSLOW: return _( "speed|Very Slow" ); case SLOW: return _( "speed|Slow" ); case AVERAGE: return _( "speed|Average" ); case FAST: return _( "speed|Fast" ); case VERYFAST: return _( "speed|Very Fast" ); case ULTRAFAST: return _( "speed|Ultra Fast" ); case BLAZING: return _( "speed|Blazing" ); case INSTANT: return _( "speed|Instant" ); default: break; } return "Unknown"; } int Speed::GetOriginalSlow( int speed ) { switch ( speed ) { case CRAWLING: case VERYSLOW: return CRAWLING; case SLOW: case AVERAGE: return VERYSLOW; case FAST: case VERYFAST: return SLOW; case ULTRAFAST: case BLAZING: return AVERAGE; case INSTANT: return FAST; default: break; } return STANDING; } int Speed::GetOriginalFast( int speed ) { switch ( speed ) { case CRAWLING: return SLOW; case VERYSLOW: return AVERAGE; case SLOW: return FAST; case AVERAGE: return VERYFAST; case FAST: return ULTRAFAST; case VERYFAST: return BLAZING; case ULTRAFAST: case BLAZING: case INSTANT: return INSTANT; default: break; } return STANDING; } int Speed::GetSlowSpeedFromSpell( const int currentSpeed ) { Spell spell = Spell::SLOW; return spell.ExtraValue() ? currentSpeed - spell.ExtraValue() : Speed::GetOriginalSlow( currentSpeed ); } int Speed::GetHasteSpeedFromSpell( const int currentSpeed ) { Spell spell = Spell::HASTE; return spell.ExtraValue() ? currentSpeed + spell.ExtraValue() : Speed::GetOriginalFast( currentSpeed ); } fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/speed.h000066400000000000000000000043221456075706000214470ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2SPEED_H #define H2SPEED_H #include namespace Speed { enum { STANDING = 0, CRAWLING = 1, VERYSLOW = 2, SLOW = 3, AVERAGE = 4, FAST = 5, VERYFAST = 6, ULTRAFAST = 7, BLAZING = 8, INSTANT = 9 }; std::string String( const int speed ); int GetOriginalSlow( const int speed ); int GetOriginalFast( const int speed ); int GetSlowSpeedFromSpell( const int currentSpeed ); int GetHasteSpeedFromSpell( const int currentSpeed ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/view_world.cpp000066400000000000000000000764751456075706000231050ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "view_world.h" #include #include #include #include #include #include #include #include "agg_image.h" #include "castle.h" #include "color.h" #include "cursor.h" #include "game_hotkeys.h" #include "gamedefs.h" #include "heroes.h" #include "icn.h" #include "image.h" #include "interface_base.h" #include "interface_border.h" #include "interface_gamearea.h" #include "interface_radar.h" #include "localevent.h" #include "maps.h" #include "maps_tiles.h" #include "maps_tiles_helper.h" #include "mp2.h" #include "render_processor.h" #include "resource.h" #include "screen.h" #include "settings.h" #include "ui_button.h" #include "ui_tool.h" #include "world.h" // #define VIEWWORLD_DEBUG_ZOOM_LEVEL // Activate this when you want to debug this window. It will provide an extra zoom level at 1:1 scale #if defined( VIEWWORLD_DEBUG_ZOOM_LEVEL ) #define SAVE_WORLD_MAP #include "image_tool.h" namespace { const std::string saveFilePrefix = "_old"; } #endif namespace { // This constant is used to mark the unknown color or resource index. const uint32_t unknownIndex = UINT32_MAX; const std::array tileSizePerZoomLevel{ 4, 6, 12, 32 }; const std::array icnPerZoomLevel{ ICN::MISC4, ICN::MISC6, ICN::MISC12, ICN::MISC12 }; const std::array icnLetterPerZoomLevel{ ICN::LETTER4, ICN::LETTER6, ICN::LETTER12, ICN::LETTER12 }; const std::array icnPerZoomLevelFlags{ ICN::VWFLAG4, ICN::VWFLAG6, ICN::VWFLAG12, ICN::VWFLAG12 }; #ifdef VIEWWORLD_DEBUG_ZOOM_LEVEL const int32_t zoomLevels = 4; #else const int32_t zoomLevels = 3; #endif // Compute a rectangle that defines which world pixels we can see in the "view world" window, // based on given zoom level and initial center fheroes2::Rect computeROI( const fheroes2::Point & centerInPixel, const ZoomLevel zoomLevel, const fheroes2::Rect & visibleROI ) { // how many pixels from "world map" we can see in "view world" window, given current zoom const int32_t pixelsW = visibleROI.width * TILEWIDTH / tileSizePerZoomLevel[static_cast( zoomLevel )]; const int32_t pixelsH = visibleROI.height * TILEWIDTH / tileSizePerZoomLevel[static_cast( zoomLevel )]; const int32_t x = centerInPixel.x - pixelsW / 2; const int32_t y = centerInPixel.y - pixelsH / 2; return { x, y, pixelsW, pixelsH }; } ZoomLevel GetNextZoomLevel( const ZoomLevel level, const bool cycle ) { switch ( level ) { case ZoomLevel::ZoomLevel0: return ZoomLevel::ZoomLevel1; case ZoomLevel::ZoomLevel1: return ZoomLevel::ZoomLevel2; #ifdef VIEWWORLD_DEBUG_ZOOM_LEVEL case ZoomLevel::ZoomLevel2: return ZoomLevel::ZoomLevel3; default: return cycle ? ZoomLevel::ZoomLevel0 : ZoomLevel::ZoomLevel3; #else default: return cycle ? ZoomLevel::ZoomLevel0 : ZoomLevel::ZoomLevel2; #endif } } ZoomLevel GetPreviousZoomLevel( const ZoomLevel level, const bool cycle ) { switch ( level ) { #ifdef VIEWWORLD_DEBUG_ZOOM_LEVEL case ZoomLevel::ZoomLevel0: return cycle ? ZoomLevel::ZoomLevel3 : ZoomLevel::ZoomLevel0; #else case ZoomLevel::ZoomLevel0: return cycle ? ZoomLevel::ZoomLevel2 : ZoomLevel::ZoomLevel0; #endif case ZoomLevel::ZoomLevel1: return ZoomLevel::ZoomLevel0; case ZoomLevel::ZoomLevel2: return ZoomLevel::ZoomLevel1; default: return ZoomLevel::ZoomLevel2; } } // Convert the color to 'ICN::VWFLAG*' or 'ICN::MISC*' index, returns 'unknownIndex' for unknown color. uint32_t colorToOffsetICN( const int32_t color ) { switch ( color ) { case Color::BLUE: return 0; case Color::GREEN: return 1; case Color::RED: return 2; case Color::YELLOW: return 3; case Color::ORANGE: return 4; case Color::PURPLE: return 5; case Color::NONE: case Color::UNUSED: return 6; default: return unknownIndex; } } // Convert the resource type to 'ICN::LETTER*' index, returns 'unknownIndex' for unknown resource. uint32_t resourceToOffsetICN( const uint32_t resource ) { switch ( resource ) { case Resource::WOOD: return 0; case Resource::MERCURY: return 1; case Resource::ORE: return 2; case Resource::SULFUR: return 3; case Resource::CRYSTAL: return 4; case Resource::GEMS: return 5; case Resource::GOLD: return 6; default: return unknownIndex; } } struct CacheForMapWithResources { std::array cachedImages; // One image per zoom Level CacheForMapWithResources() = delete; // Compute complete world map, and save it for all zoom levels explicit CacheForMapWithResources( const ViewWorldMode viewMode, Interface::GameArea & gameArea ) { for ( int32_t i = 0; i < zoomLevels; ++i ) { cachedImages[i]._disableTransformLayer(); cachedImages[i].resize( world.w() * tileSizePerZoomLevel[i], world.h() * tileSizePerZoomLevel[i] ); } const int32_t blockSizeX = 18; const int32_t blockSizeY = 18; const int32_t worldWidth = world.w(); const int32_t worldHeight = world.h(); // Assert will fail in case we add non-standard map sizes, otherwise standard map sizes are multiples of 18 tiles assert( worldWidth % blockSizeX == 0 ); assert( worldHeight % blockSizeY == 0 ); const int32_t redrawAreaWidth = blockSizeX * TILEWIDTH; const int32_t redrawAreaHeight = blockSizeY * TILEWIDTH; const int32_t redrawAreaCenterX = blockSizeX * TILEWIDTH / 2; const int32_t redrawAreaCenterY = blockSizeY * TILEWIDTH / 2; // Create temporary image where we will draw blocks of the main map on fheroes2::Image temporaryImg; temporaryImg._disableTransformLayer(); temporaryImg.resize( redrawAreaWidth, redrawAreaHeight ); // Remember the original game area ROI and center of the view. const fheroes2::Rect gameAreaRoi( gameArea.GetROI() ); const fheroes2::Point gameAreaCenter( gameArea.getCurrentCenterInPixels() ); gameArea.SetAreaPosition( 0, 0, redrawAreaWidth, redrawAreaHeight ); int32_t drawingFlags = Interface::RedrawLevelType::LEVEL_ALL & ~Interface::RedrawLevelType::LEVEL_ROUTES; if ( viewMode == ViewWorldMode::ViewAll ) { drawingFlags &= ~Interface::RedrawLevelType::LEVEL_FOG; } else if ( viewMode == ViewWorldMode::ViewTowns ) { drawingFlags |= Interface::RedrawLevelType::LEVEL_TOWNS; } #if !defined( SAVE_WORLD_MAP ) drawingFlags ^= Interface::RedrawLevelType::LEVEL_HEROES; #endif // Draw sub-blocks of the main map, and resize them to draw them on lower-res cached versions: for ( int32_t x = 0; x < worldWidth; x += blockSizeX ) { for ( int32_t y = 0; y < worldHeight; y += blockSizeY ) { gameArea.SetCenterInPixels( { x * TILEWIDTH + redrawAreaCenterX, y * TILEWIDTH + redrawAreaCenterY } ); gameArea.Redraw( temporaryImg, drawingFlags ); for ( int32_t i = 0; i < zoomLevels; ++i ) { fheroes2::Resize( temporaryImg, 0, 0, temporaryImg.width(), temporaryImg.height(), cachedImages[i], x * tileSizePerZoomLevel[i], y * tileSizePerZoomLevel[i], blockSizeX * tileSizePerZoomLevel[i], blockSizeY * tileSizePerZoomLevel[i] ); } } } // Restore the original game area ROI and center of the view. gameArea.SetAreaPosition( gameAreaRoi.x, gameAreaRoi.y, gameAreaRoi.width, gameAreaRoi.height ); gameArea.SetCenterInPixels( gameAreaCenter ); #if defined( SAVE_WORLD_MAP ) fheroes2::Save( cachedImages[3], Settings::Get().getCurrentMapInfo().name + saveFilePrefix + ".bmp" ); #endif } }; void DrawWorld( const ViewWorld::ZoomROIs & ROI, CacheForMapWithResources & cache, const fheroes2::Rect & roiScreen ) { fheroes2::Display & display = fheroes2::Display::instance(); const uint8_t zoomLevelId = static_cast( ROI.getZoomLevel() ); const fheroes2::Image & image = cache.cachedImages[zoomLevelId]; const int32_t offsetPixelsX = tileSizePerZoomLevel[zoomLevelId] * ROI.GetROIinPixels().x / TILEWIDTH; const int32_t offsetPixelsY = tileSizePerZoomLevel[zoomLevelId] * ROI.GetROIinPixels().y / TILEWIDTH; const fheroes2::Point inPos( offsetPixelsX < 0 ? 0 : offsetPixelsX, offsetPixelsY < 0 ? 0 : offsetPixelsY ); const fheroes2::Point outPos( BORDERWIDTH + ( offsetPixelsX < 0 ? -offsetPixelsX : 0 ), BORDERWIDTH + ( offsetPixelsY < 0 ? -offsetPixelsY : 0 ) ); fheroes2::Copy( image, inPos.x, inPos.y, display, outPos.x, outPos.y, roiScreen.width, roiScreen.height ); // Fill black pixels outside of the main view. const auto fillBlack = [&display]( const int32_t x, const int32_t y, const int32_t width, const int32_t height ) { const int32_t displayWidth = display.width(); assert( ( width > 0 ) && ( height > 0 ) && ( x >= 0 ) && ( y >= 0 ) && ( ( x + width ) < displayWidth ) && ( ( y + height ) < display.height() ) ); uint8_t * imageY = display.image() + static_cast( y ) * displayWidth + x; const uint8_t * imageYEnd = imageY + static_cast( height ) * displayWidth; for ( ; imageY != imageYEnd; imageY += displayWidth ) { memset( imageY, static_cast( 0 ), width ); } }; if ( image.width() < roiScreen.width ) { // Left black bar. fillBlack( BORDERWIDTH, BORDERWIDTH, outPos.x - BORDERWIDTH, roiScreen.height ); // Right black bar. fillBlack( BORDERWIDTH - offsetPixelsX + image.width(), BORDERWIDTH, display.width() - 3 * BORDERWIDTH - RADARWIDTH + offsetPixelsX - image.width(), roiScreen.height ); } if ( image.height() < roiScreen.height ) { // Top black bar. fillBlack( BORDERWIDTH, BORDERWIDTH, display.width() - 3 * BORDERWIDTH - RADARWIDTH, outPos.y - BORDERWIDTH ); // Bottom black bar. fillBlack( BORDERWIDTH, BORDERWIDTH - offsetPixelsY + image.height(), roiScreen.width, display.height() - 2 * BORDERWIDTH + offsetPixelsY - image.height() ); } } void DrawObjectsIcons( const int32_t color, const ViewWorldMode mode, CacheForMapWithResources & cache ) { const bool revealAll = mode == ViewWorldMode::ViewAll; const bool revealMines = revealAll || ( mode == ViewWorldMode::ViewMines ); const bool revealHeroes = revealAll || ( mode == ViewWorldMode::ViewHeroes ); const bool revealTowns = revealAll || ( mode == ViewWorldMode::ViewTowns ); const bool revealArtifacts = revealAll || ( mode == ViewWorldMode::ViewArtifacts ); const bool revealResources = revealAll || ( mode == ViewWorldMode::ViewResources ); const int32_t worldWidth = world.w(); const int32_t worldHeight = world.h(); assert( worldWidth >= 0 && worldHeight >= 0 ); // Render two flags to the left and to the right of Castle/Town entrance. const auto renderCastleFlags = [&cache]( const uint32_t icnIndex, const int32_t posX, const int32_t posY ) { for ( int32_t zoomLevelId = 0; zoomLevelId < zoomLevels; ++zoomLevelId ) { const int32_t tileSize = tileSizePerZoomLevel[zoomLevelId]; const int32_t icnFlagsBase = icnPerZoomLevelFlags[zoomLevelId]; const uint32_t flagIndex = ( icnFlagsBase == ICN::FLAG32 ) ? ( 2 * icnIndex + 1 ) : icnIndex; const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( icnFlagsBase, flagIndex ); const int32_t dstx = posX * tileSize + ( tileSize - sprite.width() ) / 2; const int32_t dsty = posY * tileSize + ( tileSize - sprite.height() ) / 2 + 1; fheroes2::Blit( sprite, cache.cachedImages[zoomLevelId], dstx + tileSize, dsty, false ); // We place a second flag, flipped horizontally. fheroes2::Blit( sprite, cache.cachedImages[zoomLevelId], dstx - tileSize, dsty, true ); } }; // Render hero/artifact icon. const auto renderIcon = [&cache]( const uint32_t icnIndex, const int32_t posX, const int32_t posY ) { for ( int32_t zoomLevelId = 0; zoomLevelId < zoomLevels; ++zoomLevelId ) { const int32_t tileSize = tileSizePerZoomLevel[zoomLevelId]; const int32_t dstx = posX * tileSize + tileSize / 2; const int32_t dsty = posY * tileSize + tileSize / 2; const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( icnPerZoomLevel[zoomLevelId], icnIndex ); fheroes2::Blit( sprite, cache.cachedImages[zoomLevelId], dstx - sprite.width() / 2, dsty - sprite.height() / 2 ); } }; // Render resource/mine icon with letter inside. const auto renderResourceIcon = [&cache]( const uint32_t icnIndex, const uint32_t resource, const int32_t posX, const int32_t posY ) { const uint32_t letterIndex = resourceToOffsetICN( resource ); if ( letterIndex == unknownIndex ) { // This is an unknown resource. return; } for ( int32_t zoomLevelId = 0; zoomLevelId < zoomLevels; ++zoomLevelId ) { const int32_t tileSize = tileSizePerZoomLevel[zoomLevelId]; const int32_t dstx = posX * tileSize + tileSize / 2; const int32_t dsty = posY * tileSize + tileSize / 2; const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( icnPerZoomLevel[zoomLevelId], icnIndex ); fheroes2::Blit( sprite, cache.cachedImages[zoomLevelId], dstx - sprite.width() / 2, dsty - sprite.height() / 2 ); const fheroes2::Sprite & letter = fheroes2::AGG::GetICN( icnLetterPerZoomLevel[zoomLevelId], letterIndex ); fheroes2::Blit( letter, cache.cachedImages[zoomLevelId], dstx - letter.width() / 2, dsty - letter.height() / 2 ); } }; // There could be maximum 2 objects on the tile to analyze (in example: a Hero and a Castle). std::array objectTypes{}; uint32_t objectCount{ 0 }; for ( int32_t posY = 0; posY < worldWidth; ++posY ) { for ( int32_t posX = 0; posX < worldHeight; ++posX ) { const Maps::Tiles & tile = world.GetTiles( posX, posY ); objectTypes[0] = tile.GetObject( false ); objectTypes[1] = tile.GetObject( true ); objectCount = ( objectTypes[0] == objectTypes[1] ) ? 1 : 2; for ( uint32_t i = 0; i < objectCount; ++i ) { switch ( objectTypes[i] ) { case MP2::OBJ_HERO: { if ( revealHeroes || !tile.isFog( color ) ) { const Heroes * hero = world.GetHeroes( tile.GetCenter() ); if ( hero ) { const uint32_t colorOffset = colorToOffsetICN( hero->GetColor() ); // Do not render an unknown color. if ( colorOffset != unknownIndex ) { renderIcon( 7 + colorOffset, posX, posY ); } } } break; } case MP2::OBJ_CASTLE: { const bool isFog = tile.isFog( color ); if ( revealTowns || !isFog ) { const Castle * castle = world.getCastleEntrance( tile.GetCenter() ); if ( castle ) { const uint32_t colorOffset = colorToOffsetICN( castle->GetColor() ); // Do not render an unknown color. if ( colorOffset != unknownIndex ) { renderCastleFlags( colorOffset, posX, posY ); } } } break; } case MP2::OBJ_ALCHEMIST_LAB: case MP2::OBJ_MINE: case MP2::OBJ_SAWMILL: if ( revealMines || !tile.isFog( color ) ) { const uint32_t colorOffset = colorToOffsetICN( getColorFromTile( tile ) ); // Do not render an unknown color. if ( colorOffset != unknownIndex ) { const Funds funds = getDailyIncomeObjectResources( tile ); assert( funds.GetValidItemsCount() == 1 ); renderResourceIcon( colorOffset, funds.getFirstValidResource().first, posX, posY ); } } break; case MP2::OBJ_ARTIFACT: if ( revealArtifacts || !tile.isFog( color ) ) { renderIcon( 14, posX, posY ); } break; case MP2::OBJ_RESOURCE: if ( revealResources || !tile.isFog( color ) ) { const Funds funds = getFundsFromTile( tile ); assert( funds.GetValidItemsCount() == 1 ); renderResourceIcon( 13, funds.getFirstValidResource().first, posX, posY ); } break; default: break; } } } } } int32_t GetSpriteResource( const ViewWorldMode mode, const bool evil ) { switch ( mode ) { case ViewWorldMode::ViewAll: return evil ? ICN::EVIW_ALL : ICN::VIEW_ALL; case ViewWorldMode::ViewArtifacts: return evil ? ICN::EVIWRTFX : ICN::VIEWRTFX; case ViewWorldMode::ViewMines: return evil ? ICN::EVIWMINE : ICN::VIEWMINE; case ViewWorldMode::ViewResources: return evil ? ICN::EVIWRSRC : ICN::VIEWRSRC; case ViewWorldMode::ViewHeroes: return evil ? ICN::EVIWHROS : ICN::VIEWHROS; case ViewWorldMode::ViewTowns: return evil ? ICN::EVIWTWNS : ICN::VIEWTWNS; default: // "View World" return evil ? ICN::EVIWWRLD : ICN::VIEWWRLD; } } void drawViewWorldSprite( const fheroes2::Sprite & viewWorldSprite, fheroes2::Display & display, const bool isEvilInterface ) { const int32_t dstX = display.width() - viewWorldSprite.width() - BORDERWIDTH; int32_t dstY = 2 * BORDERWIDTH + RADARWIDTH; const int32_t cutHeight = 275; fheroes2::Copy( viewWorldSprite, 0, 0, display, dstX, dstY, viewWorldSprite.width(), cutHeight ); dstY += cutHeight; if ( display.height() > fheroes2::Display::DEFAULT_HEIGHT ) { const fheroes2::Sprite & icnston = fheroes2::AGG::GetICN( isEvilInterface ? ICN::STONBAKE : ICN::STONBACK, 0 ); const int32_t startY = 11; const int32_t copyHeight = 46; const int32_t repeatHeight = display.height() - BORDERWIDTH - dstY - ( viewWorldSprite.height() - cutHeight ); const int32_t repeatCount = repeatHeight / copyHeight; for ( int32_t i = 0; i < repeatCount; ++i ) { fheroes2::Copy( icnston, 0, startY, display, dstX, dstY, icnston.width(), copyHeight ); dstY += copyHeight; } fheroes2::Copy( icnston, 0, startY, display, dstX, dstY, icnston.width(), repeatHeight % copyHeight ); dstY += repeatHeight % copyHeight; } fheroes2::Copy( viewWorldSprite, 0, cutHeight, display, dstX, dstY, viewWorldSprite.width(), viewWorldSprite.height() - cutHeight ); } } ViewWorld::ZoomROIs::ZoomROIs( const ZoomLevel zoomLevel, const fheroes2::Point & centerInPixels, const fheroes2::Rect & visibleScreenInPixels ) : _zoomLevel( zoomLevel ) , _center( centerInPixels ) , _visibleROI( visibleScreenInPixels ) { _updateZoomLevels(); _updateCenter(); } void ViewWorld::ZoomROIs::_updateZoomLevels() { for ( int32_t i = 0; i < zoomLevels; ++i ) { _roiForZoomLevels[i] = computeROI( _center, static_cast( i ), _visibleROI ); } } bool ViewWorld::ZoomROIs::_updateCenter() { return ChangeCenter( _center ); } bool ViewWorld::ZoomROIs::ChangeCenter( const fheroes2::Point & centerInPixels ) { const fheroes2::Rect & currentRect = GetROIinPixels(); const fheroes2::Size worldSize( world.w() * TILEWIDTH, world.h() * TILEWIDTH ); fheroes2::Point newCenter; if ( worldSize.width <= currentRect.width ) { newCenter.x = ( worldSize.width - 1 ) / 2; } else { newCenter.x = std::clamp( centerInPixels.x, currentRect.width / 2, worldSize.width - currentRect.width / 2 ); } if ( worldSize.height <= currentRect.height ) { newCenter.y = ( worldSize.height - 1 ) / 2; } else { newCenter.y = std::clamp( centerInPixels.y, currentRect.height / 2, worldSize.height - currentRect.height / 2 ); } if ( newCenter == _center ) { return false; } _center = newCenter; _updateZoomLevels(); return true; } bool ViewWorld::ZoomROIs::_changeZoom( const ZoomLevel newLevel ) { const bool changed = ( newLevel != _zoomLevel ); _zoomLevel = newLevel; _updateCenter(); return changed; } bool ViewWorld::ZoomROIs::zoomIn( const bool cycle ) { const ZoomLevel newLevel = GetNextZoomLevel( _zoomLevel, cycle ); return _changeZoom( newLevel ); } bool ViewWorld::ZoomROIs::zoomOut( const bool cycle ) { const ZoomLevel newLevel = GetPreviousZoomLevel( _zoomLevel, cycle ); return _changeZoom( newLevel ); } fheroes2::Rect ViewWorld::ZoomROIs::GetROIinTiles() const { fheroes2::Rect result = GetROIinPixels(); result.x = ( result.x + TILEWIDTH / 2 ) / TILEWIDTH; result.y = ( result.y + TILEWIDTH / 2 ) / TILEWIDTH; result.width = ( result.width + TILEWIDTH / 2 ) / TILEWIDTH; result.height = ( result.height + TILEWIDTH / 2 ) / TILEWIDTH; return result; } void ViewWorld::ViewWorldWindow( const int32_t color, const ViewWorldMode mode, Interface::BaseInterface & interface ) { fheroes2::Display & display = fheroes2::Display::instance(); fheroes2::ImageRestorer restorer( display ); Settings & conf = Settings::Get(); const bool isEvilInterface = conf.isEvilInterfaceEnabled(); const bool isHideInterface = conf.isHideInterfaceEnabled(); const ZoomLevel zoomLevel = conf.ViewWorldZoomLevel(); fheroes2::Rect fadeRoi( 0, 0, display.width(), display.height() ); if ( !isHideInterface ) { // If interface is on there is no need to fade the whole screen, just only map area. fadeRoi.x += BORDERWIDTH; fadeRoi.y += BORDERWIDTH; fadeRoi.width -= 3 * BORDERWIDTH + RADARWIDTH; fadeRoi.height -= 2 * BORDERWIDTH; } // Fade-out Adventure map screen. fheroes2::fadeOutDisplay( fadeRoi, false ); // If the interface is currently hidden, we have to temporarily bring it back, because // the map generation in the World View mode heavily depends on the existing game area if ( isHideInterface ) { conf.setHideInterface( false ); interface.reset(); } // Set the cursor image. After this dialog the Game Area will be shown, so it does not require a cursor restorer. Cursor::Get().SetThemes( Cursor::POINTER ); fheroes2::RenderProcessor & renderProcessor = fheroes2::RenderProcessor::instance(); renderProcessor.stopColorCycling(); // Creates fixed radar on top-right, suitable for the View World window Interface::Radar radar( interface.getRadar(), fheroes2::Display::instance() ); Interface::GameArea & gameArea = interface.getGameArea(); const fheroes2::Rect & worldMapROI = gameArea.GetVisibleTileROI(); const fheroes2::Rect & visibleScreenInPixels = gameArea.GetROI(); // Initial view is centered on where the player is centered fheroes2::Point viewCenterInPixels( worldMapROI.x * TILEWIDTH + ( visibleScreenInPixels.width + TILEWIDTH ) / 2, worldMapROI.y * TILEWIDTH + ( visibleScreenInPixels.height + TILEWIDTH ) / 2 ); // Special case: full map picture can be contained within the window -> center view on center of the map if ( world.w() * tileSizePerZoomLevel[static_cast( zoomLevel )] <= visibleScreenInPixels.width && world.h() * tileSizePerZoomLevel[static_cast( zoomLevel )] <= visibleScreenInPixels.height ) { viewCenterInPixels.x = world.w() * TILEWIDTH / 2; viewCenterInPixels.y = world.h() * TILEWIDTH / 2; } ZoomROIs currentROI( zoomLevel, viewCenterInPixels, visibleScreenInPixels ); CacheForMapWithResources cache( mode, gameArea ); DrawObjectsIcons( color, mode, cache ); // We need to draw interface borders only if game interface is turned off on Adventure map. if ( isHideInterface ) { Interface::GameBorderRedraw( true ); } // Draw radar radar.RedrawForViewWorld( currentROI, mode, true ); // "View world" sprite const fheroes2::Sprite & viewWorldSprite = fheroes2::AGG::GetICN( GetSpriteResource( mode, isEvilInterface ), 0 ); drawViewWorldSprite( viewWorldSprite, display, isEvilInterface ); // Zoom button const fheroes2::Point buttonZoomPosition( display.width() - RADARWIDTH + 16, 2 * BORDERWIDTH + RADARWIDTH + 128 ); fheroes2::Button buttonZoom( buttonZoomPosition.x, buttonZoomPosition.y, ( isEvilInterface ? ICN::LGNDXTRE : ICN::LGNDXTRA ), 0, 1 ); buttonZoom.draw(); // Exit button const fheroes2::Point buttonExitPosition( display.width() - RADARWIDTH + 16, 2 * BORDERWIDTH + RADARWIDTH + 236 ); fheroes2::Button buttonExit( buttonExitPosition.x, buttonExitPosition.y, ( isEvilInterface ? ICN::BUTTON_VIEWWORLD_EXIT_EVIL : ICN::BUTTON_VIEWWORLD_EXIT_GOOD ), 0, 1 ); buttonExit.draw(); // Fade-in View World screen. if ( !isHideInterface ) { display.render( { display.width() - RADARWIDTH + BORDERWIDTH, BORDERWIDTH, display.height() - 2 * BORDERWIDTH, RADARWIDTH } ); } // Render the View World map image. DrawWorld( currentROI, cache, visibleScreenInPixels ); fheroes2::fadeInDisplay( fadeRoi, false ); // Use for dragging the map from main window bool isDrag = false; fheroes2::Point initMousePos; fheroes2::Point initRoiCenter; // message loop LocalEvent & le = LocalEvent::Get(); while ( le.HandleEvents() ) { le.MousePressLeft( buttonExit.area() ) ? buttonExit.drawOnPress() : buttonExit.drawOnRelease(); le.MousePressLeft( buttonZoom.area() ) ? buttonZoom.drawOnPress() : buttonZoom.drawOnRelease(); bool changed = false; if ( le.MouseClickLeft( buttonExit.area() ) || Game::HotKeyCloseWindow() ) { break; } if ( le.MouseClickLeft( buttonZoom.area() ) ) { changed = currentROI.zoomOut( true ); } else if ( le.MouseCursor( radar.GetRect() ) ) { changed = radar.QueueEventProcessingForWorldView( currentROI ); } else if ( le.MousePressLeft( visibleScreenInPixels ) ) { if ( isDrag ) { const fheroes2::Point & newMousePos = le.GetMouseCursor(); const int32_t tileSize = tileSizePerZoomLevel[static_cast( currentROI.getZoomLevel() )]; const fheroes2::Point newRoiCenter( initRoiCenter.x - ( newMousePos.x - initMousePos.x ) * TILEWIDTH / tileSize, initRoiCenter.y - ( newMousePos.y - initMousePos.y ) * TILEWIDTH / tileSize ); changed = currentROI.ChangeCenter( newRoiCenter ); } else { isDrag = true; initMousePos = le.GetMouseCursor(); initRoiCenter = currentROI.getCenter(); } } else if ( le.MouseWheelUp() ) { changed = currentROI.zoomIn( false ); } else if ( le.MouseWheelDn() ) { changed = currentROI.zoomOut( false ); } if ( !le.MousePressLeft( visibleScreenInPixels ) || !le.MouseCursor( visibleScreenInPixels ) ) { isDrag = false; } if ( changed ) { DrawWorld( currentROI, cache, visibleScreenInPixels ); radar.RedrawForViewWorld( currentROI, mode, false ); display.render(); } } // Memorize the last zoom level value. conf.SetViewWorldZoomLevel( currentROI.getZoomLevel() ); renderProcessor.startColorCycling(); // Fade-out View World screen and fade-in the Adventure map screen. fheroes2::fadeOutDisplay( fadeRoi, false ); restorer.restore(); display.updateNextRenderRoi( restorer.rect() ); fheroes2::fadeInDisplay( fadeRoi, false ); gameArea.SetUpdateCursor(); // Don't forget to reset the interface settings back if necessary if ( isHideInterface ) { conf.setHideInterface( true ); interface.reset(); } else { radar.SetRedraw( Interface::REDRAW_RADAR_CURSOR ); } } fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/view_world.h000066400000000000000000000057751456075706000225450ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2VIEWWORLD_H #define H2VIEWWORLD_H #include #include #include "math_base.h" #include "settings.h" namespace Interface { class BaseInterface; } enum class ViewWorldMode : int32_t { OnlyVisible = 0, // Only show what is currently not under fog of war ViewArtifacts = 1, ViewMines = 2, ViewResources = 3, ViewHeroes = 4, ViewTowns = 5, ViewAll = 6, }; class ViewWorld { public: static void ViewWorldWindow( const int32_t color, const ViewWorldMode mode, Interface::BaseInterface & interface ); class ZoomROIs { public: ZoomROIs( const ZoomLevel zoomLevel, const fheroes2::Point & centerInPixels, const fheroes2::Rect & visibleScreenInPixels ); bool zoomIn( const bool cycle ); bool zoomOut( const bool cycle ); bool ChangeCenter( const fheroes2::Point & centerInPixels ); const fheroes2::Rect & GetROIinPixels() const { return _roiForZoomLevels[static_cast( _zoomLevel )]; } fheroes2::Rect GetROIinTiles() const; ZoomLevel getZoomLevel() const { return _zoomLevel; } const fheroes2::Point & getCenter() const { return _center; } private: void _updateZoomLevels(); bool _updateCenter(); bool _changeZoom( const ZoomLevel newLevel ); ZoomLevel _zoomLevel{ ZoomLevel::ZoomLevel1 }; fheroes2::Point _center; std::array _roiForZoomLevels; fheroes2::Rect _visibleROI; }; }; #endif fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/week.cpp000066400000000000000000000150261456075706000216400ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include "rand.h" #include "tools.h" #include "translations.h" #include "week.h" namespace { WeekName WeekRand( const uint32_t seed ) { uint32_t weekTypeSeed = seed; fheroes2::hashCombine( weekTypeSeed, 367245 ); // Salt uint32_t weekType = Rand::GetWithSeed( 0, 3, weekTypeSeed ); // A regular week, probability 75% if ( weekType < 3 ) { uint32_t weekSeed = seed; fheroes2::hashCombine( weekSeed, 1946256 ); // Salt return Rand::GetWithSeed( WeekName::SQUIRREL, WeekName::CONDOR, weekSeed ); } // The Week of a monster, probability 25% return WeekName::MONSTERS; } WeekName MonthRand( const uint32_t seed ) { uint32_t monthTypeSeed = seed; fheroes2::hashCombine( monthTypeSeed, 9536582 ); // Salt uint32_t monthType = Rand::GetWithSeed( 0, 9, monthTypeSeed ); // A regular month, probability 50% if ( monthType < 5 ) { uint32_t monthSeed = seed; fheroes2::hashCombine( monthSeed, 5544783 ); // Salt return Rand::GetWithSeed( WeekName::ANT, WeekName::BEETLE, monthSeed ); } // The Month of a monster, probability 40% if ( monthType < 9 ) { return WeekName::MONSTERS; } // The Month of the Plague, probability 10% return WeekName::PLAGUE; } Monster::monster_t RandomMonsterWeekOf( const uint32_t seed ) { uint32_t monsterSeed = seed; fheroes2::hashCombine( monsterSeed, 886473 ); // Salt return Rand::GetWithSeed( Monster::PEASANT, Monster::BONE_DRAGON, monsterSeed ); } Monster::monster_t RandomMonsterMonthOf( const uint32_t seed ) { uint32_t monsterSeed = seed; fheroes2::hashCombine( monsterSeed, 1130906 ); // Salt switch ( Rand::GetWithSeed( 1, 12, monsterSeed ) ) { case 1: return Monster::PEASANT; case 2: return Monster::WOLF; case 3: return Monster::OGRE; case 4: return Monster::TROLL; case 5: return Monster::DWARF; case 6: return Monster::DRUID; case 7: return Monster::UNICORN; case 8: return Monster::CENTAUR; case 9: return Monster::GARGOYLE; case 10: return Monster::ROC; case 11: return Monster::VAMPIRE; case 12: return Monster::LICH; default: assert( 0 ); } return Monster::UNKNOWN; } } const char * Week::GetName() const { switch ( _week ) { case WeekName::UNNAMED: break; case WeekName::SQUIRREL: return _( "week|Squirrel" ); case WeekName::RABBIT: return _( "week|Rabbit" ); case WeekName::GOPHER: return _( "week|Gopher" ); case WeekName::BADGER: return _( "week|Badger" ); case WeekName::RAT: return _( "week|Rat" ); case WeekName::EAGLE: return _( "week|Eagle" ); case WeekName::WEASEL: return _( "week|Weasel" ); case WeekName::RAVEN: return _( "week|Raven" ); case WeekName::MONGOOSE: return _( "week|Mongoose" ); case WeekName::DOG: return _( "week|Dog" ); case WeekName::AARDVARK: return _( "week|Aardvark" ); case WeekName::LIZARD: return _( "week|Lizard" ); case WeekName::TORTOISE: return _( "week|Tortoise" ); case WeekName::HEDGEHOG: return _( "week|Hedgehog" ); case WeekName::CONDOR: return _( "week|Condor" ); case WeekName::ANT: return _( "week|Ant" ); case WeekName::GRASSHOPPER: return _( "week|Grasshopper" ); case WeekName::DRAGONFLY: return _( "week|Dragonfly" ); case WeekName::SPIDER: return _( "week|Spider" ); case WeekName::BUTTERFLY: return _( "week|Butterfly" ); case WeekName::BUMBLEBEE: return _( "week|Bumblebee" ); case WeekName::LOCUST: return _( "week|Locust" ); case WeekName::EARTHWORM: return _( "week|Earthworm" ); case WeekName::HORNET: return _( "week|Hornet" ); case WeekName::BEETLE: return _( "week|Beetle" ); case WeekName::MONSTERS: return Monster( _monster ).GetName(); case WeekName::PLAGUE: return _( "week|PLAGUE" ); default: assert( 0 ); } return _( "week|Unnamed" ); } Week Week::RandomWeek( const bool isNewMonth, const uint32_t weekSeed ) { const WeekName weekName = isNewMonth ? MonthRand( weekSeed ) : WeekRand( weekSeed ); if ( weekName == WeekName::MONSTERS ) { if ( isNewMonth ) { return { weekName, RandomMonsterMonthOf( weekSeed ) }; } return { weekName, RandomMonsterWeekOf( weekSeed ) }; } return { weekName, Monster::UNKNOWN }; } fheroes2-1.0.12+dfsg/src/fheroes2/kingdom/week.h000066400000000000000000000055111456075706000213030ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2WEEK_H #define H2WEEK_H #include #include "monster.h" enum class WeekName : int { UNNAMED, // A regular week (Week Of ...) SQUIRREL, RABBIT, GOPHER, BADGER, RAT, EAGLE, WEASEL, RAVEN, MONGOOSE, DOG, AARDVARK, LIZARD, TORTOISE, HEDGEHOG, CONDOR, // A regular first week of the month (Month Of ...) ANT, GRASSHOPPER, DRAGONFLY, SPIDER, BUTTERFLY, BUMBLEBEE, LOCUST, EARTHWORM, HORNET, BEETLE, // The Week of a monster (the Month of a monster, if it's the first week of the month) MONSTERS, // The Month of the Plague PLAGUE }; struct Week { Week( const WeekName type = WeekName::UNNAMED, const Monster::monster_t monster = Monster::UNKNOWN ) : _week( type ) , _monster( monster ) {} WeekName GetType() const { return _week; } Monster::monster_t GetMonster() const { return _monster; } const char * GetName() const; static Week RandomWeek( const bool isNewMonth, const uint32_t weekSeed ); private: WeekName _week; Monster::monster_t _monster; }; #endif fheroes2-1.0.12+dfsg/src/fheroes2/maps/000077500000000000000000000000001456075706000175055ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/fheroes2/maps/ground.cpp000066400000000000000000000212531456075706000215120ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "ground.h" #include #include "maps_tiles.h" #include "rand.h" #include "skill.h" #include "translations.h" uint16_t Maps::Ground::getTerrainStartImageIndex( const int groundId ) { switch ( groundId ) { case WATER: return 0U; case GRASS: return 30U; case SNOW: return 92U; case SWAMP: return 146U; case LAVA: return 208U; case DESERT: return 262U; case DIRT: return 321U; case WASTELAND: return 361U; case BEACH: return 415U; default: // Have you added a new ground? Add the logic above! assert( 0 ); return 0; } } int Maps::Ground::getGroundByImageIndex( const uint16_t terrainImageIndex ) { if ( 30U > terrainImageIndex ) { return Maps::Ground::WATER; } if ( 92U > terrainImageIndex ) { return Maps::Ground::GRASS; } if ( 146U > terrainImageIndex ) { return Maps::Ground::SNOW; } if ( 208U > terrainImageIndex ) { return Maps::Ground::SWAMP; } if ( 262U > terrainImageIndex ) { return Maps::Ground::LAVA; } if ( 321U > terrainImageIndex ) { return Maps::Ground::DESERT; } if ( 361U > terrainImageIndex ) { return Maps::Ground::DIRT; } if ( 415U > terrainImageIndex ) { return Maps::Ground::WASTELAND; } if ( 432U > terrainImageIndex ) { return Maps::Ground::BEACH; } // Have you added a new ground? Add the logic above! assert( 0 ); return Maps::Ground::UNKNOWN; } bool Maps::Ground::isTerrainTransitionImage( const uint16_t terrainImageIndex ) { const int groundId = getGroundByImageIndex( terrainImageIndex ); switch ( groundId ) { case WATER: case DIRT: return terrainImageIndex < 16U + getTerrainStartImageIndex( groundId ); case GRASS: case SNOW: case SWAMP: case LAVA: case DESERT: case WASTELAND: return terrainImageIndex < 38U + getTerrainStartImageIndex( groundId ); case BEACH: return false; default: // Have you added a new ground? Add the logic above! assert( 0 ); return false; } } bool Maps::Ground::doesTerrainImageIndexContainEmbeddedObjects( const uint16_t terrainImageIndex ) { const int groundId = getGroundByImageIndex( terrainImageIndex ); switch ( groundId ) { case WATER: return false; case GRASS: case SWAMP: case SNOW: case WASTELAND: case LAVA: case DESERT: return terrainImageIndex > 45U + getTerrainStartImageIndex( groundId ); case DIRT: return terrainImageIndex > 23U + getTerrainStartImageIndex( groundId ); case BEACH: return terrainImageIndex > 7U + getTerrainStartImageIndex( groundId ); default: // Have you added a new ground? Add the logic above! assert( 0 ); return false; } } const char * Maps::Ground::String( int groundId ) { switch ( groundId ) { case DESERT: return _( "Desert" ); case SNOW: return _( "Snow" ); case SWAMP: return _( "Swamp" ); case WASTELAND: return _( "Wasteland" ); case BEACH: return _( "Beach" ); case LAVA: return _( "Lava" ); case DIRT: return _( "Dirt" ); case GRASS: return _( "Grass" ); case WATER: return _( "Ocean" ); default: return "Unknown"; } } uint32_t Maps::Ground::GetPenalty( const Maps::Tiles & tile, uint32_t level ) { // none basc advd expr // Desert 2.00 1.75 1.50 1.00 // Swamp 1.75 1.50 1.25 1.00 // Snow 1.50 1.25 1.00 1.00 // Wasteland 1.25 1.00 1.00 1.00 // Beach 1.25 1.00 1.00 1.00 // Lava 1.00 1.00 1.00 1.00 // Dirt 1.00 1.00 1.00 1.00 // Grass 1.00 1.00 1.00 1.00 // Water 1.00 1.00 1.00 1.00 // Road 0.75 0.75 0.75 0.75 uint32_t result = defaultGroundPenalty; switch ( tile.GetGround() ) { case DESERT: switch ( level ) { case Skill::Level::EXPERT: break; case Skill::Level::ADVANCED: result += 50; break; case Skill::Level::BASIC: result += 75; break; default: result += 100; break; } break; case SWAMP: switch ( level ) { case Skill::Level::EXPERT: break; case Skill::Level::ADVANCED: result += 25; break; case Skill::Level::BASIC: result += 50; break; default: result += 75; break; } break; case SNOW: switch ( level ) { case Skill::Level::EXPERT: case Skill::Level::ADVANCED: break; case Skill::Level::BASIC: result += 25; break; default: result += 50; break; } break; case WASTELAND: case BEACH: result += ( Skill::Level::NONE == level ? 25 : 0 ); break; default: break; } return result; } uint16_t Maps::Ground::getRandomTerrainImageIndex( const int groundId, const bool allowEmbeddedObjectsAppearOnTerrain ) { if ( groundId == WATER ) { return static_cast( Rand::Get( 3 ) ) + 16U + getTerrainStartImageIndex( groundId ); } // Terrain images, except Water, can contain extra objects that are a part of the image. if ( allowEmbeddedObjectsAppearOnTerrain && Rand::Get( 6 ) == 0 ) { switch ( groundId ) { case GRASS: case SWAMP: return static_cast( Rand::Get( 15 ) ) + 46U + getTerrainStartImageIndex( groundId ); case SNOW: case WASTELAND: case LAVA: return static_cast( Rand::Get( 7 ) ) + 46U + getTerrainStartImageIndex( groundId ); case DESERT: return static_cast( Rand::Get( 12 ) ) + 46U + getTerrainStartImageIndex( groundId ); case DIRT: return static_cast( Rand::Get( 15 ) ) + 24U + getTerrainStartImageIndex( groundId ); case BEACH: return static_cast( Rand::Get( 8 ) ) + 8U + getTerrainStartImageIndex( groundId ); default: // Have you added a new ground? Add the logic above! assert( 0 ); return 0; } } const uint16_t indexOffset = static_cast( Rand::Get( 7 ) ); switch ( groundId ) { case GRASS: case SNOW: case SWAMP: case LAVA: case DESERT: case WASTELAND: return indexOffset + 38U + getTerrainStartImageIndex( groundId ); case DIRT: return indexOffset + 16U + getTerrainStartImageIndex( groundId ); case BEACH: return indexOffset + getTerrainStartImageIndex( groundId ); default: // Have you added a new ground? Add the logic above! assert( 0 ); return 0; } } fheroes2-1.0.12+dfsg/src/fheroes2/maps/ground.h000066400000000000000000000063261456075706000211630ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2MAPSGROUND_H #define H2MAPSGROUND_H #include namespace Maps { class Tiles; namespace Ground { enum { UNKNOWN = 0x0000, DESERT = 0x0001, SNOW = 0x0002, SWAMP = 0x0004, WASTELAND = 0x0008, BEACH = 0x0010, LAVA = 0x0020, DIRT = 0x0040, GRASS = 0x0080, WATER = 0x0100, ALL = DESERT | SNOW | SWAMP | WASTELAND | BEACH | LAVA | DIRT | GRASS }; // Returns index of first ground image in GROUND32.TIL uint16_t getTerrainStartImageIndex( const int groundId ); // Returns ground index by ground image index in GROUND32.TIL int getGroundByImageIndex( const uint16_t terrainImageIndex ); // Returns true if ground image index corresponds to image with transition to other ground. bool isTerrainTransitionImage( const uint16_t terrainImageIndex ); bool doesTerrainImageIndexContainEmbeddedObjects( const uint16_t terrainImageIndex ); const uint32_t roadPenalty = 75; const uint32_t defaultGroundPenalty = 100; const uint32_t slowestMovePenalty = 200; const char * String( int groundId ); uint32_t GetPenalty( const Maps::Tiles & tile, uint32_t pathfindingLevel ); // Returns the random ground image index (used in GROUND32.TIL) for main (without transition) terrain layout. uint16_t getRandomTerrainImageIndex( const int groundId, const bool allowEmbeddedObjectsAppearOnTerrain ); } } #endif fheroes2-1.0.12+dfsg/src/fheroes2/maps/map_format_helper.cpp000066400000000000000000000164621456075706000237060ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "map_format_helper.h" #include #include #include #include #include #include #include #include #include "map_format_info.h" #include "map_object_info.h" #include "maps_tiles.h" #include "maps_tiles_helper.h" #include "mp2.h" #include "world.h" #include "world_object_uid.h" namespace { void addObjectToTile( Maps::Map_Format::TileInfo & info, const Maps::ObjectGroup group, const uint32_t index, const uint32_t uid ) { auto & object = info.objects.emplace_back(); object.id = uid; object.group = group; object.index = index; } struct IndexedObjectInfo { int32_t tileIndex{ -1 }; const Maps::Map_Format::ObjectInfo * info{ nullptr }; }; } namespace Maps { bool readMapInEditor( const Map_Format::MapFormat & map ) { world.generateForEditor( map.size ); if ( !readAllTiles( map ) ) { return false; } world.updatePassabilities(); return true; } bool readAllTiles( const Map_Format::MapFormat & map ) { assert( static_cast( world.w() ) * world.h() == map.tiles.size() ); // We must clear all tiles before writing something on them. for ( size_t i = 0; i < map.tiles.size(); ++i ) { auto & tile = world.GetTiles( static_cast( i ) ); tile = {}; tile.setIndex( static_cast( i ) ); } for ( size_t i = 0; i < map.tiles.size(); ++i ) { readTileTerrain( world.GetTiles( static_cast( i ) ), map.tiles[i] ); } // Read objects from all tiles and place them based on their IDs. auto sortObjects = []( const IndexedObjectInfo & left, const IndexedObjectInfo & right ) { return left.info->id < right.info->id; }; std::multiset sortedObjects( sortObjects ); for ( size_t i = 0; i < map.tiles.size(); ++i ) { for ( const auto & object : map.tiles[i].objects ) { IndexedObjectInfo info; info.tileIndex = static_cast( i ); info.info = &object; sortedObjects.emplace( info ); } } for ( const auto & info : sortedObjects ) { assert( info.info != nullptr ); if ( !readTileObject( world.GetTiles( info.tileIndex ), *info.info ) ) { return false; } } return true; } bool saveMapInEditor( Map_Format::MapFormat & map ) { assert( world.w() > 0 && world.w() == world.h() ); map.size = world.w(); const size_t size = static_cast( map.size ) * map.size; map.tiles.resize( size ); for ( size_t i = 0; i < size; ++i ) { writeTile( world.GetTiles( static_cast( i ) ), map.tiles[i] ); } return true; } void readTileTerrain( Tiles & tile, const Map_Format::TileInfo & info ) { tile.setTerrain( info.terrainIndex, info.terrainFlag & 2, info.terrainFlag & 1 ); } bool readTileObject( Tiles & tile, const Map_Format::ObjectInfo & object ) { const auto & objectInfos = getObjectsByGroup( object.group ); if ( object.index >= objectInfos.size() ) { // This is a bad map format! assert( 0 ); return false; } // Object UID is set through global object UID counter. Therefore, we need to update it before running the operation. if ( object.id == 0 ) { // This object UID is not set! assert( 0 ); return false; } setLastObjectUID( object.id - 1 ); // We don't update map passabilities as it is a very expensive process. // Let's do it once everything is being loaded. return setObjectOnTile( tile, objectInfos[object.index], false ); } void writeTile( const Tiles & tile, Map_Format::TileInfo & info ) { // Roads and streams are the only objects that are needed to be saved separately. // This is because modification on one tile affects all neighboring tiles as well. // Check all existing objects and delete all roads and streams. info.objects.erase( std::remove_if( info.objects.begin(), info.objects.end(), []( const Maps::Map_Format::ObjectInfo & object ) { return object.group == ObjectGroup::ROADS || object.group == ObjectGroup::STREAMS; } ), info.objects.end() ); for ( const auto & addon : tile.getBottomLayerAddons() ) { if ( addon._objectIcnType == MP2::OBJ_ICN_TYPE_ROAD || addon._objectIcnType == MP2::OBJ_ICN_TYPE_STREAM ) { const ObjectGroup group = ( addon._objectIcnType == MP2::OBJ_ICN_TYPE_ROAD ) ? ObjectGroup::ROADS : ObjectGroup::STREAMS; const auto & objectInfos = getObjectsByGroup( group ); if ( addon._imageIndex < objectInfos.size() ) { // This is the correct object. addObjectToTile( info, group, addon._imageIndex, addon._uid ); } } } info.terrainIndex = tile.getTerrainImageIndex(); info.terrainFlag = tile.getTerrainFlags(); } void addObjectToMap( Map_Format::MapFormat & map, const int32_t tileId, const ObjectGroup group, const uint32_t index ) { assert( tileId >= 0 && map.tiles.size() > static_cast( tileId ) ); // At this time it is assumed that object was added into world object to be rendered using Maps::setObjectOnTile() function. const uint32_t uid = getLastObjectUID(); assert( uid > 0 ); addObjectToTile( map.tiles[tileId], group, index, uid ); } } fheroes2-1.0.12+dfsg/src/fheroes2/maps/map_format_helper.h000066400000000000000000000042641456075706000233500ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include namespace Maps { class Tiles; namespace Map_Format { struct MapFormat; struct TileInfo; struct ObjectInfo; } enum class ObjectGroup : int32_t; bool readMapInEditor( const Map_Format::MapFormat & map ); bool readAllTiles( const Map_Format::MapFormat & map ); bool saveMapInEditor( Map_Format::MapFormat & map ); void readTileTerrain( Tiles & tile, const Map_Format::TileInfo & info ); bool readTileObject( Tiles & tile, const Map_Format::ObjectInfo & object ); void writeTile( const Tiles & tile, Map_Format::TileInfo & info ); void addObjectToMap( Map_Format::MapFormat & map, const int32_t tileId, const ObjectGroup group, const uint32_t index ); } fheroes2-1.0.12+dfsg/src/fheroes2/maps/map_format_info.cpp000066400000000000000000000202621456075706000233530ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "map_format_info.h" #include #include #include #include "serialize.h" namespace { const std::array magicWord{ 'h', '2', 'm', 'a', 'p', '\0' }; // This value is set to avoid any corrupted files to be processed. const size_t minFileSize{ 128 }; } namespace Maps::Map_Format { StreamBase & operator<<( StreamBase & msg, const ObjectInfo & object ) { using GroupUnderlyingType = std::underlying_type_t; return msg << object.id << static_cast( object.group ) << object.index; } StreamBase & operator>>( StreamBase & msg, ObjectInfo & object ) { msg >> object.id; using GroupUnderlyingType = std::underlying_type_t; GroupUnderlyingType group; msg >> group; object.group = static_cast( group ); return msg >> object.index; } StreamBase & operator<<( StreamBase & msg, const TileInfo & tile ) { return msg << tile.terrainIndex << tile.terrainFlag << tile.objects; } StreamBase & operator>>( StreamBase & msg, TileInfo & tile ) { return msg >> tile.terrainIndex >> tile.terrainFlag >> tile.objects; } StreamBase & operator<<( StreamBase & msg, const StandardObjectMetadata & metadata ) { return msg << metadata.metadata; } StreamBase & operator>>( StreamBase & msg, StandardObjectMetadata & metadata ) { return msg >> metadata.metadata; } StreamBase & operator<<( StreamBase & msg, const CastleMetadata & metadata ) { return msg << metadata.customName << metadata.defenderMonsterType << metadata.defenderMonsterCount << metadata.isCaptainAvailable << metadata.builtBuildings << metadata.bannedBuildings << metadata.mustHaveSpells << metadata.bannedSpells << metadata.availableToHireMonsterCount; } StreamBase & operator>>( StreamBase & msg, CastleMetadata & metadata ) { return msg >> metadata.customName >> metadata.defenderMonsterType >> metadata.defenderMonsterCount >> metadata.isCaptainAvailable >> metadata.builtBuildings >> metadata.bannedBuildings >> metadata.mustHaveSpells >> metadata.bannedSpells >> metadata.availableToHireMonsterCount; } StreamBase & operator<<( StreamBase & msg, const HeroMetadata & metadata ) { return msg << metadata.customName << metadata.customPortrait << metadata.armyMonsterType << metadata.armyMonsterCount << metadata.artifact << metadata.artifactMetadata << metadata.availableSpells << metadata.isOnPatrol << metadata.patrolRadius << metadata.secondarySkill << metadata.secondarySkillLevel << metadata.customLevel << metadata.customExperience << metadata.customAttack << metadata.customDefence << metadata.customKnowledge << metadata.customSpellPower << metadata.magicPoints; } StreamBase & operator>>( StreamBase & msg, HeroMetadata & metadata ) { return msg >> metadata.customName >> metadata.customPortrait >> metadata.armyMonsterType >> metadata.armyMonsterCount >> metadata.artifact >> metadata.artifactMetadata >> metadata.availableSpells >> metadata.isOnPatrol >> metadata.patrolRadius >> metadata.secondarySkill >> metadata.secondarySkillLevel >> metadata.customLevel >> metadata.customExperience >> metadata.customAttack >> metadata.customDefence >> metadata.customKnowledge >> metadata.customSpellPower >> metadata.magicPoints; } StreamBase & operator<<( StreamBase & msg, const BaseMapFormat & map ) { return msg << map.version << map.isCampaign << map.difficulty << map.availablePlayerColors << map.humanPlayerColors << map.computerPlayerColors << map.alliances << map.victoryConditionType << map.isVictoryConditionApplicableForAI << map.allowNormalVictory << map.victoryConditionMetadata << map.lossCondition << map.lossConditionMetadata << map.size << map.name << map.description; } StreamBase & operator>>( StreamBase & msg, BaseMapFormat & map ) { return msg >> map.version >> map.isCampaign >> map.difficulty >> map.availablePlayerColors >> map.humanPlayerColors >> map.computerPlayerColors >> map.alliances >> map.victoryConditionType >> map.isVictoryConditionApplicableForAI >> map.allowNormalVictory >> map.victoryConditionMetadata >> map.lossCondition >> map.lossConditionMetadata >> map.size >> map.name >> map.description; } StreamBase & operator<<( StreamBase & msg, const MapFormat & map ) { return msg << static_cast( map ) << map.additionalInfo << map.tiles << map.standardMetadata << map.castleMetadata << map.heroMetadata; } StreamBase & operator>>( StreamBase & msg, MapFormat & map ) { // TODO: verify the correctness of metadata. return msg >> static_cast( map ) >> map.additionalInfo >> map.tiles >> map.standardMetadata >> map.castleMetadata >> map.heroMetadata; } bool loadBaseMap( const std::string & path, BaseMapFormat & map ) { if ( path.empty() ) { return false; } StreamFile fileStream; fileStream.setbigendian( true ); if ( !fileStream.open( path, "rb" ) ) { return false; } const size_t fileSize = fileStream.size(); if ( fileSize < minFileSize ) { return false; } for ( const uint8_t value : magicWord ) { if ( fileStream.get() != value ) { return false; } } fileStream >> map; return true; } bool loadMap( const std::string & path, MapFormat & map ) { if ( path.empty() ) { return false; } StreamFile fileStream; fileStream.setbigendian( true ); if ( !fileStream.open( path, "rb" ) ) { return false; } const size_t fileSize = fileStream.size(); if ( fileSize < minFileSize ) { return false; } for ( const uint8_t value : magicWord ) { if ( fileStream.get() != value ) { return false; } } fileStream >> map; return true; } bool saveMap( const std::string & path, const MapFormat & map ) { if ( path.empty() ) { return false; } StreamFile fileStream; fileStream.setbigendian( true ); if ( !fileStream.open( path, "wb" ) ) { return false; } for ( const uint8_t value : magicWord ) { fileStream.put( value ); } fileStream << map; return true; } } fheroes2-1.0.12+dfsg/src/fheroes2/maps/map_format_info.h000066400000000000000000000171331456075706000230230ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include #include #include #include "map_object_info.h" class StreamBase; namespace Maps::Map_Format { struct ObjectInfo { uint32_t id{ 0 }; ObjectGroup group{ ObjectGroup::LANDSCAPE_MOUNTAINS }; uint32_t index{ 0 }; }; struct TileInfo { uint16_t terrainIndex{ 0 }; uint8_t terrainFlag{ 0 }; std::vector objects; }; // This structure should be used for any object that require simple data to be saved into map. struct StandardObjectMetadata { std::array metadata{ 0 }; }; struct CastleMetadata { // Color, type and whether it is castle or town must come from ObjectInfo to make sure // that these values correspond to graphical representation of the castle. // If the name is empty a random name is going to be set by the engine. std::string customName; // Defending monsters that are set in the castle. Type 0 means not set. std::array defenderMonsterType{ 0 }; std::array defenderMonsterCount{ 0 }; // Whether the captain is being hired in the town / castle. bool isCaptainAvailable{ false }; // A list of built buildings. std::vector builtBuildings; // A list of buildings that cannot be built. std::vector bannedBuildings; // Spells that must appear in the Magic Guild. std::vector mustHaveSpells; // Spells that must NOT appear the Magic Guild. std::vector bannedSpells; // The number of monsters available to hire in dwellings. A negative value means that no change will be applied. std::array availableToHireMonsterCount{ -1 }; }; struct HeroMetadata { // Hero's color and type must come from ObjectInfo to make sure // that these values correspond to graphical representation of the hero. // If the name is empty a random name is going to be set by the engine. std::string customName; // Custom portrait. A negative value means no customization. int32_t customPortrait{ -1 }; // Custom hero army. Type 0 means not set. std::array armyMonsterType{ 0 }; std::array armyMonsterCount{ 0 }; // Artifacts with metadata. Type 0 means not set. std::array artifact{ 0 }; std::array artifactMetadata{ 0 }; // Custom spells available in a Spell Book. If no spells are set then defaults spells will be set for the hero. std::vector availableSpells; // Patrol state of a hero. It is only applicable for AI heroes. bool isOnPatrol{ false }; uint8_t patrolRadius{ 0 }; // Secondary skills. Type 0 means not set. std::array secondarySkill{ 0 }; std::array secondarySkillLevel{ 0 }; // Mutually exclusive settings: either a hero has a custom level or custom experience. // If none of them is set default values are applied. int16_t customLevel{ -1 }; int32_t customExperience{ -1 }; // Primary Skill bonuses. By default they are to 0. They can be positive or negative. // These values are applied after hero's basic primary skills' values and after level him up, if required. int16_t customAttack{ 0 }; int16_t customDefence{ 0 }; int16_t customKnowledge{ 0 }; int16_t customSpellPower{ 0 }; // The amount of magic points (mana). Negative value means it is not set. int16_t magicPoints{ -1 }; }; struct BaseMapFormat { // TODO: change it only once the Editor is released to public and there is a need to expand map format functionality. uint16_t version{ 1 }; bool isCampaign{ false }; uint8_t difficulty{ 0 }; uint8_t availablePlayerColors{ 0 }; uint8_t humanPlayerColors{ 0 }; uint8_t computerPlayerColors{ 0 }; std::vector alliances; uint8_t victoryConditionType{ 0 }; bool isVictoryConditionApplicableForAI{ false }; bool allowNormalVictory{ false }; std::vector victoryConditionMetadata; uint8_t lossCondition{ 0 }; std::vector lossConditionMetadata; int32_t size{ 0 }; std::string name; std::string description; }; struct MapFormat : public BaseMapFormat { // This is used only for campaign maps. std::vector additionalInfo; std::vector tiles; // These are matadata maps in relation to object UID. std::map standardMetadata; std::map castleMetadata; std::map heroMetadata; }; bool loadBaseMap( const std::string & path, BaseMapFormat & map ); bool loadMap( const std::string & path, MapFormat & map ); bool saveMap( const std::string & path, const MapFormat & map ); StreamBase & operator<<( StreamBase & msg, const ObjectInfo & object ); StreamBase & operator>>( StreamBase & msg, ObjectInfo & object ); StreamBase & operator<<( StreamBase & msg, const TileInfo & tile ); StreamBase & operator>>( StreamBase & msg, TileInfo & tile ); StreamBase & operator<<( StreamBase & msg, const StandardObjectMetadata & metadata ); StreamBase & operator>>( StreamBase & msg, StandardObjectMetadata & metadata ); StreamBase & operator<<( StreamBase & msg, const CastleMetadata & metadata ); StreamBase & operator>>( StreamBase & msg, CastleMetadata & metadata ); StreamBase & operator<<( StreamBase & msg, const HeroMetadata & metadata ); StreamBase & operator>>( StreamBase & msg, HeroMetadata & metadata ); StreamBase & operator<<( StreamBase & msg, const BaseMapFormat & map ); StreamBase & operator>>( StreamBase & msg, BaseMapFormat & map ); StreamBase & operator<<( StreamBase & msg, const MapFormat & map ); StreamBase & operator>>( StreamBase & msg, MapFormat & map ); } fheroes2-1.0.12+dfsg/src/fheroes2/maps/map_object_info.cpp000066400000000000000000011350231456075706000233340ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "map_object_info.h" #include #include #include #include #include #include #include #include "artifact.h" #include "monster.h" #include "resource.h" namespace { // This is the main container that holds information about all Adventure Map objects in the game. // // All object information is based on The Price of Loyalty expansion of the original game since // the fheroes2 Editor requires to have resources from the expansion. std::array, static_cast( Maps::ObjectGroup::GROUP_COUNT )> objectData; void populateRoads( std::vector & objects ) { assert( objects.empty() ); for ( uint32_t i = 0; i < 32; ++i ) { Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_ROAD, i, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } } void populateStreams( std::vector & objects ) { assert( objects.empty() ); for ( uint32_t i = 0; i < 13; ++i ) { Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_STREAM, i, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } } void populateLandscapeMountains( std::vector & objects ) { assert( objects.empty() ); // Grouped mountains have different appearances: Generic, Grass, Snow, Swamp, Lava, Desert, Dirt, Wasteland. for ( const MP2::ObjectIcnType type : { MP2::OBJ_ICN_TYPE_MTNMULT, MP2::OBJ_ICN_TYPE_MTNGRAS, MP2::OBJ_ICN_TYPE_MTNSNOW, MP2::OBJ_ICN_TYPE_MTNSWMP, MP2::OBJ_ICN_TYPE_MTNLAVA, MP2::OBJ_ICN_TYPE_MTNDSRT, MP2::OBJ_ICN_TYPE_MTNDIRT, MP2::OBJ_ICN_TYPE_MTNCRCK } ) { // Big mountain from top-left to bottom-right. { Maps::ObjectInfo object{ MP2::OBJ_MOUNTAINS }; object.groundLevelParts.emplace_back( type, 14U, fheroes2::Point{ 0, 0 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 6U, fheroes2::Point{ -2, -1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 7U, fheroes2::Point{ -1, -1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 8U, fheroes2::Point{ 0, -1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 9U, fheroes2::Point{ 1, -1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 12U, fheroes2::Point{ -2, 0 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 13U, fheroes2::Point{ -1, 0 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 15U, fheroes2::Point{ 1, 0 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 16U, fheroes2::Point{ 2, 0 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 18U, fheroes2::Point{ 0, 1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 19U, fheroes2::Point{ 1, 1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 20U, fheroes2::Point{ 2, 1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 0U, fheroes2::Point{ -3, -2 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, 5U, fheroes2::Point{ -3, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, 11U, fheroes2::Point{ -3, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, 17U, fheroes2::Point{ -1, 1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( type, 1U, fheroes2::Point{ -2, -2 }, MP2::OBJ_MOUNTAINS ); object.topLevelParts.emplace_back( type, 2U, fheroes2::Point{ -1, -2 }, MP2::OBJ_MOUNTAINS ); object.topLevelParts.emplace_back( type, 3U, fheroes2::Point{ 0, -2 }, MP2::OBJ_MOUNTAINS ); object.topLevelParts.emplace_back( type, 4U, fheroes2::Point{ 1, -2 }, MP2::OBJ_MOUNTAINS ); object.topLevelParts.emplace_back( type, 10U, fheroes2::Point{ 2, -1 }, MP2::OBJ_MOUNTAINS ); objects.emplace_back( std::move( object ) ); } uint32_t icnOffset = 21U; // Big mountain from top-right to bottom-left. { Maps::ObjectInfo object{ MP2::OBJ_MOUNTAINS }; object.groundLevelParts.emplace_back( type, icnOffset + 14U, fheroes2::Point{ 0, 0 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 7U, fheroes2::Point{ -1, -1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 8U, fheroes2::Point{ 0, -1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 9U, fheroes2::Point{ 1, -1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 10U, fheroes2::Point{ 2, -1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 12U, fheroes2::Point{ -2, 0 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 13U, fheroes2::Point{ -1, 0 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 15U, fheroes2::Point{ 1, 0 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 16U, fheroes2::Point{ 2, 0 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 18U, fheroes2::Point{ -2, 1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 19U, fheroes2::Point{ -1, 1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 20U, fheroes2::Point{ 0, 1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 0U, fheroes2::Point{ -2, -2 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 5U, fheroes2::Point{ -3, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 11U, fheroes2::Point{ -3, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 17U, fheroes2::Point{ -3, 1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( type, icnOffset + 1U, fheroes2::Point{ -1, -2 }, MP2::OBJ_MOUNTAINS ); object.topLevelParts.emplace_back( type, icnOffset + 2U, fheroes2::Point{ 0, -2 }, MP2::OBJ_MOUNTAINS ); object.topLevelParts.emplace_back( type, icnOffset + 3U, fheroes2::Point{ 1, -2 }, MP2::OBJ_MOUNTAINS ); object.topLevelParts.emplace_back( type, icnOffset + 4U, fheroes2::Point{ 2, -2 }, MP2::OBJ_MOUNTAINS ); object.topLevelParts.emplace_back( type, icnOffset + 6U, fheroes2::Point{ -2, -1 }, MP2::OBJ_MOUNTAINS ); objects.emplace_back( std::move( object ) ); } icnOffset += 21; // Dirt and Wasteland has extra mountains placed in ICN between big and small mountains if ( type == MP2::OBJ_ICN_TYPE_MTNDIRT || type == MP2::OBJ_ICN_TYPE_MTNCRCK ) { // Extra mountain from top-left to bottom-right. { Maps::ObjectInfo object{ MP2::OBJ_MOUNTAINS }; object.groundLevelParts.emplace_back( type, icnOffset + 8U, fheroes2::Point{ 0, 0 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 6U, fheroes2::Point{ -2, 0 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 7U, fheroes2::Point{ -1, 0 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 12U, fheroes2::Point{ 0, 1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 13U, fheroes2::Point{ 1, 1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 14U, fheroes2::Point{ 2, 1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 0U, fheroes2::Point{ -3, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 4U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 5U, fheroes2::Point{ -3, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 11U, fheroes2::Point{ -1, 1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( type, icnOffset + 1U, fheroes2::Point{ -2, -1 }, MP2::OBJ_MOUNTAINS ); object.topLevelParts.emplace_back( type, icnOffset + 2U, fheroes2::Point{ -1, -1 }, MP2::OBJ_MOUNTAINS ); object.topLevelParts.emplace_back( type, icnOffset + 3U, fheroes2::Point{ 0, -1 }, MP2::OBJ_MOUNTAINS ); object.topLevelParts.emplace_back( type, icnOffset + 9U, fheroes2::Point{ 1, 0 }, MP2::OBJ_MOUNTAINS ); object.topLevelParts.emplace_back( type, icnOffset + 10U, fheroes2::Point{ 2, 0 }, MP2::OBJ_MOUNTAINS ); objects.emplace_back( std::move( object ) ); } icnOffset += 15; // Extra mountain from top-right to bottom-left. { Maps::ObjectInfo object{ MP2::OBJ_MOUNTAINS }; object.groundLevelParts.emplace_back( type, icnOffset + 8U, fheroes2::Point{ 0, 0 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 9U, fheroes2::Point{ 1, 0 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 10U, fheroes2::Point{ 2, 0 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 12U, fheroes2::Point{ -2, 1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 13U, fheroes2::Point{ -1, 1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 14U, fheroes2::Point{ 0, 1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 0U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 1U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 5U, fheroes2::Point{ -3, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 11U, fheroes2::Point{ -3, 1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( type, icnOffset + 2U, fheroes2::Point{ 0, -1 }, MP2::OBJ_MOUNTAINS ); object.topLevelParts.emplace_back( type, icnOffset + 3U, fheroes2::Point{ 1, -1 }, MP2::OBJ_MOUNTAINS ); object.topLevelParts.emplace_back( type, icnOffset + 4U, fheroes2::Point{ 2, -1 }, MP2::OBJ_MOUNTAINS ); object.topLevelParts.emplace_back( type, icnOffset + 6U, fheroes2::Point{ -2, 0 }, MP2::OBJ_MOUNTAINS ); object.topLevelParts.emplace_back( type, icnOffset + 7U, fheroes2::Point{ -1, 0 }, MP2::OBJ_MOUNTAINS ); objects.emplace_back( std::move( object ) ); } icnOffset += 15; } // Medium mountain from top-left to bottom-right. { Maps::ObjectInfo object{ MP2::OBJ_MOUNTAINS }; object.groundLevelParts.emplace_back( type, icnOffset + 5U, fheroes2::Point{ 0, 0 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 4U, fheroes2::Point{ -1, 0 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 6U, fheroes2::Point{ 1, 0 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 8U, fheroes2::Point{ 0, 1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 9U, fheroes2::Point{ 1, 1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 0U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 3U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 7U, fheroes2::Point{ -1, 1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); // Grassy medium mountain should not be passable in the upper row (according to its image) if ( type == MP2::OBJ_ICN_TYPE_MTNGRAS ) { object.groundLevelParts.emplace_back( type, icnOffset + 1U, fheroes2::Point{ -1, -1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 2U, fheroes2::Point{ 0, -1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); } else { object.topLevelParts.emplace_back( type, icnOffset + 1U, fheroes2::Point{ -1, -1 }, MP2::OBJ_MOUNTAINS ); object.topLevelParts.emplace_back( type, icnOffset + 2U, fheroes2::Point{ 0, -1 }, MP2::OBJ_MOUNTAINS ); } objects.emplace_back( std::move( object ) ); } icnOffset += 10; // Medium mountain from top-left to bottom-right. { Maps::ObjectInfo object{ MP2::OBJ_MOUNTAINS }; object.groundLevelParts.emplace_back( type, icnOffset + 5U, fheroes2::Point{ 0, 0 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 4U, fheroes2::Point{ -1, 0 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 6U, fheroes2::Point{ 1, 0 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 8U, fheroes2::Point{ -1, 1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 9U, fheroes2::Point{ 0, 1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 0U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 3U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 7U, fheroes2::Point{ -2, 1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); // Grassy medium mountain should not be passable in the upper row (according to its image) if ( type == MP2::OBJ_ICN_TYPE_MTNGRAS ) { object.groundLevelParts.emplace_back( type, icnOffset + 1U, fheroes2::Point{ 0, -1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 2U, fheroes2::Point{ 1, -1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); } else { object.topLevelParts.emplace_back( type, icnOffset + 1U, fheroes2::Point{ 0, -1 }, MP2::OBJ_MOUNTAINS ); object.topLevelParts.emplace_back( type, icnOffset + 2U, fheroes2::Point{ 1, -1 }, MP2::OBJ_MOUNTAINS ); } objects.emplace_back( std::move( object ) ); } icnOffset += 10; // Small mountain from top-left to bottom-right. { Maps::ObjectInfo object{ MP2::OBJ_MOUNTAINS }; object.groundLevelParts.emplace_back( type, icnOffset + 4U, fheroes2::Point{ 0, 0 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 1U, fheroes2::Point{ -1, -1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 2U, fheroes2::Point{ 0, -1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 5U, fheroes2::Point{ 1, 0 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 0U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 3U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } icnOffset += 6; // Small mountain from top-right to bottom-left. { Maps::ObjectInfo object{ MP2::OBJ_MOUNTAINS }; object.groundLevelParts.emplace_back( type, icnOffset + 5U, fheroes2::Point{ 0, 0 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 1U, fheroes2::Point{ 0, -1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 2U, fheroes2::Point{ 1, -1 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 4U, fheroes2::Point{ -1, 0 }, MP2::OBJ_MOUNTAINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 0U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, icnOffset + 3U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } } // Single mountains. // Grass medium mound. { Maps::ObjectInfo object{ MP2::OBJ_MOUND }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 77U, fheroes2::Point{ 0, 0 }, MP2::OBJ_MOUND, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 78U, fheroes2::Point{ 1, 0 }, MP2::OBJ_MOUND, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 76U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Grass small mound. { Maps::ObjectInfo object{ MP2::OBJ_MOUND }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 149U, fheroes2::Point{ 0, 0 }, MP2::OBJ_MOUND, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 150U, fheroes2::Point{ 1, 0 }, MP2::OBJ_MOUND, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Big volcano (Lava terrain). { Maps::ObjectInfo object{ MP2::OBJ_VOLCANO }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV3, 245U, fheroes2::Point{ 0, 0 }, MP2::OBJ_VOLCANO, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV3, 244U, fheroes2::Point{ -1, 0 }, MP2::OBJ_VOLCANO, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV3, 246U, fheroes2::Point{ 1, 0 }, MP2::OBJ_VOLCANO, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV3, 120U, fheroes2::Point{ -4, -2 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 14; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV3, 180U, fheroes2::Point{ -5, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 14; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV3, 195U, fheroes2::Point{ -4, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 14; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV3, 210U, fheroes2::Point{ -3, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 14; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV3, 225U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 14; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV3, 243U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV3, 0U, fheroes2::Point{ -1, -4 }, MP2::OBJ_VOLCANO ); object.topLevelParts.back().animationFrames = 14; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV3, 15U, fheroes2::Point{ 0, -4 }, MP2::OBJ_VOLCANO ); object.topLevelParts.back().animationFrames = 14; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV3, 30U, fheroes2::Point{ 1, -4 }, MP2::OBJ_VOLCANO ); object.topLevelParts.back().animationFrames = 14; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV3, 45U, fheroes2::Point{ -2, -3 }, MP2::OBJ_VOLCANO ); object.topLevelParts.back().animationFrames = 14; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV3, 60U, fheroes2::Point{ -1, -3 }, MP2::OBJ_VOLCANO ); object.topLevelParts.back().animationFrames = 14; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV3, 75U, fheroes2::Point{ 0, -3 }, MP2::OBJ_VOLCANO ); object.topLevelParts.back().animationFrames = 14; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV3, 90U, fheroes2::Point{ 1, -3 }, MP2::OBJ_VOLCANO ); object.topLevelParts.back().animationFrames = 14; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV3, 105U, fheroes2::Point{ 2, -3 }, MP2::OBJ_VOLCANO ); object.topLevelParts.back().animationFrames = 14; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV3, 135U, fheroes2::Point{ -1, -2 }, MP2::OBJ_VOLCANO ); object.topLevelParts.back().animationFrames = 14; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV3, 150U, fheroes2::Point{ 0, -2 }, MP2::OBJ_VOLCANO ); object.topLevelParts.back().animationFrames = 14; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV3, 165U, fheroes2::Point{ 1, -2 }, MP2::OBJ_VOLCANO ); object.topLevelParts.back().animationFrames = 14; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV3, 240U, fheroes2::Point{ -1, -1 }, MP2::OBJ_VOLCANO ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV3, 241U, fheroes2::Point{ 0, -1 }, MP2::OBJ_VOLCANO ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV3, 242U, fheroes2::Point{ 1, -1 }, MP2::OBJ_VOLCANO ); objects.emplace_back( std::move( object ) ); } // Middle volcano (Lava terrain). { Maps::ObjectInfo object{ MP2::OBJ_VOLCANO }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV2, 31U, fheroes2::Point{ 0, 0 }, MP2::OBJ_VOLCANO, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV2, 30U, fheroes2::Point{ -1, 0 }, MP2::OBJ_VOLCANO, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV2, 32U, fheroes2::Point{ 1, 0 }, MP2::OBJ_VOLCANO, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV2, 7U, fheroes2::Point{ -3, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV2, 14U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV2, 28U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV2, 29U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV2, 0U, fheroes2::Point{ 0, -2 }, MP2::OBJ_VOLCANO ); object.topLevelParts.back().animationFrames = 6; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV2, 21U, fheroes2::Point{ 0, -1 }, MP2::OBJ_VOLCANO ); object.topLevelParts.back().animationFrames = 6; objects.emplace_back( std::move( object ) ); } // Small volcano (Lava terrain). { Maps::ObjectInfo object{ MP2::OBJ_VOLCANO }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV2, 80U, fheroes2::Point{ 0, 0 }, MP2::OBJ_VOLCANO, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV2, 79U, fheroes2::Point{ -1, 0 }, MP2::OBJ_VOLCANO, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV2, 81U, fheroes2::Point{ 1, 0 }, MP2::OBJ_VOLCANO, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV2, 44U, fheroes2::Point{ -3, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 10; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV2, 55U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 10; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV2, 78U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV2, 33U, fheroes2::Point{ 0, -2 }, MP2::OBJ_VOLCANO ); object.topLevelParts.back().animationFrames = 10; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV2, 66U, fheroes2::Point{ -1, -1 }, MP2::OBJ_VOLCANO ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAV2, 67U, fheroes2::Point{ 0, -1 }, MP2::OBJ_VOLCANO ); object.topLevelParts.back().animationFrames = 10; objects.emplace_back( std::move( object ) ); } // Smallest volcano (Lava terrain). { Maps::ObjectInfo object{ MP2::OBJ_VOLCANO }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 76U, fheroes2::Point{ 0, 0 }, MP2::OBJ_VOLCANO, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 77U, fheroes2::Point{ 1, 0 }, MP2::OBJ_VOLCANO, Maps::OBJECT_LAYER ); // TODO: Fit the lack of shadow tile to the left. object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 74U, fheroes2::Point{ 0, -1 }, MP2::OBJ_VOLCANO ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 75U, fheroes2::Point{ 1, -1 }, MP2::OBJ_VOLCANO ); objects.emplace_back( std::move( object ) ); } // Desert dune. { Maps::ObjectInfo object{ MP2::OBJ_DUNE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 14U, fheroes2::Point{ 0, 0 }, MP2::OBJ_DUNE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 15U, fheroes2::Point{ 1, 0 }, MP2::OBJ_DUNE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 13U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Desert mound. { Maps::ObjectInfo object{ MP2::OBJ_DUNE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 17U, fheroes2::Point{ 0, 0 }, MP2::OBJ_DUNE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 18U, fheroes2::Point{ 1, 0 }, MP2::OBJ_DUNE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 19U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Desert dune. { Maps::ObjectInfo object{ MP2::OBJ_DUNE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 21U, fheroes2::Point{ 0, 0 }, MP2::OBJ_DUNE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 20U, fheroes2::Point{ -1, 0 }, MP2::OBJ_DUNE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 22U, fheroes2::Point{ 1, 0 }, MP2::OBJ_DUNE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 23U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Dirt mound. { Maps::ObjectInfo object{ MP2::OBJ_MOUND }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 12U, fheroes2::Point{ 0, 0 }, MP2::OBJ_MOUND, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 13U, fheroes2::Point{ 1, 0 }, MP2::OBJ_MOUND, Maps::OBJECT_LAYER ); // TODO: Fit the lack of shadow tile to the left. objects.emplace_back( std::move( object ) ); } // Dirt mound. { Maps::ObjectInfo object{ MP2::OBJ_MOUND }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 15U, fheroes2::Point{ 0, 0 }, MP2::OBJ_MOUND, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 16U, fheroes2::Point{ 1, 0 }, MP2::OBJ_MOUND, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 14U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } } void populateLandscapeRocks( std::vector & objects ) { assert( objects.empty() ); // Small and medium rocks, grass terrain, variant 1. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 33U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 34U, fheroes2::Point{ 1, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 32U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium and very small rocks, grass terrain, variant 2. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 37U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 38U, fheroes2::Point{ 1, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 36U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 35U, fheroes2::Point{ 0, -1 }, MP2::OBJ_ROCK ); objects.emplace_back( std::move( object ) ); } // Single medium rock, grass terrain. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 40U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 39U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Tiny rocks, grass terrain. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 41U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Small 1-tile rocks, grass terrain. 4 variants. for ( uint32_t count = 0; count < 4; ++count ) { const uint32_t offset = 42U + count * 2U; Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, offset + 1U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, offset + 0U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Small rocks, snow terrain. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 22U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 21U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium rocks, snow terrain. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 26U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 27U, fheroes2::Point{ 1, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 25U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 23U, fheroes2::Point{ 0, -1 }, MP2::OBJ_ROCK ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 24U, fheroes2::Point{ 1, -1 }, MP2::OBJ_ROCK ); objects.emplace_back( std::move( object ) ); } // Tiny rocks, snow terrain. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 28U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Small 1-tile rocks, grass terrain. 2 variants. for ( uint32_t count = 0; count < 2; ++count ) { const uint32_t offset = 29U + count * 2U; Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, offset + 1U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, offset + 0U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Wide small rocks, snow terrain. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 34U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 35U, fheroes2::Point{ 1, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 33U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium rock, snow terrain. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 37U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 36U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium rock, snow terrain. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 39U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 38U, fheroes2::Point{ -1, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Wide and low mossy rock, swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_MOSSY_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 139U, fheroes2::Point{ 0, 0 }, MP2::OBJ_MOSSY_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 138U, fheroes2::Point{ -1, 0 }, MP2::OBJ_MOSSY_ROCK, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium mossy rock, swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_MOSSY_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 203U, fheroes2::Point{ 0, 0 }, MP2::OBJ_MOSSY_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 202U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium rock, swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 205U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 204U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Big mossy rock, swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_MOSSY_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 209U, fheroes2::Point{ 0, 0 }, MP2::OBJ_MOSSY_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 208U, fheroes2::Point{ -1, 0 }, MP2::OBJ_MOSSY_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 207U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 206U, fheroes2::Point{ 0, -1 }, MP2::OBJ_MOSSY_ROCK ); objects.emplace_back( std::move( object ) ); } // Very-very small rocks (1-tile), swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 210U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Wide medium rocks, dirt terrain. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 92U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 93U, fheroes2::Point{ 1, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 91U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Big and three medium rocks, dirt terrain. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 98U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 99U, fheroes2::Point{ 1, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 97U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 94U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 95U, fheroes2::Point{ 0, -1 }, MP2::OBJ_ROCK ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 96U, fheroes2::Point{ 1, -1 }, MP2::OBJ_ROCK ); objects.emplace_back( std::move( object ) ); } // Medium and small rocks, dirt terrain. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 101U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 102U, fheroes2::Point{ 1, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 100U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium rock, dirt terrain. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 104U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 103U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Small rocks, dirt terrain. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 105U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Wide medium rock with a tree on it, wasteland terrain. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 10U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 11U, fheroes2::Point{ 1, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 9U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 7U, fheroes2::Point{ 0, -1 }, MP2::OBJ_ROCK ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 8U, fheroes2::Point{ 1, -1 }, MP2::OBJ_ROCK ); objects.emplace_back( std::move( object ) ); } // Small rock, wasteland terrain. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 18U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Big rock and two medium around, wasteland terrain. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 21U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 22U, fheroes2::Point{ 1, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 20U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 19U, fheroes2::Point{ 0, -1 }, MP2::OBJ_ROCK ); objects.emplace_back( std::move( object ) ); } // Wide medium rock, wasteland terrain. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 24U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 25U, fheroes2::Point{ 1, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 23U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Wide big rock, wasteland terrain. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 30U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 29U, fheroes2::Point{ -1, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 28U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 26U, fheroes2::Point{ -1, -1 }, MP2::OBJ_ROCK ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 27U, fheroes2::Point{ 0, -1 }, MP2::OBJ_ROCK ); objects.emplace_back( std::move( object ) ); } // Wide low rock, wasteland terrain. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 31U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 32U, fheroes2::Point{ 1, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Wide small rock and a small rock, wasteland terrain. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 38U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 37U, fheroes2::Point{ -1, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 36U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Wide small rock, wasteland terrain. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 40U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 41U, fheroes2::Point{ 1, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 39U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Small and medium rocks, wasteland terrain. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 43U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 42U, fheroes2::Point{ -1, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Tall rocks, wasteland terrain. 4 variants. for ( uint32_t count = 0; count < 4; ++count ) { const uint32_t offset = 44U + count * 3U; Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, offset + 2U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, offset + 1U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, offset + 0U, fheroes2::Point{ 0, -1 }, MP2::OBJ_ROCK ); objects.emplace_back( std::move( object ) ); } } void populateLandscapeTrees( std::vector & objects ) { assert( objects.empty() ); // Forest have different appearances: Deciduous, Evil (dead), Autumn, Fir trees, Jungle, Snowy fir trees. for ( const MP2::ObjectIcnType type : { MP2::OBJ_ICN_TYPE_TREDECI, MP2::OBJ_ICN_TYPE_TREEVIL, MP2::OBJ_ICN_TYPE_TREFALL, MP2::OBJ_ICN_TYPE_TREFIR, MP2::OBJ_ICN_TYPE_TREJNGL, MP2::OBJ_ICN_TYPE_TRESNOW } ) { // Big forest from top-left to bottom-right. { Maps::ObjectInfo object{ MP2::OBJ_TREES }; object.groundLevelParts.emplace_back( type, 5U, fheroes2::Point{ 0, 0 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 4U, fheroes2::Point{ -1, 0 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 8U, fheroes2::Point{ 0, 1 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 9U, fheroes2::Point{ 1, 1 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 0U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, 3U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, 7U, fheroes2::Point{ -1, 1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( type, 1U, fheroes2::Point{ -1, -1 }, MP2::OBJ_TREES ); object.topLevelParts.emplace_back( type, 2U, fheroes2::Point{ 0, -1 }, MP2::OBJ_TREES ); object.topLevelParts.emplace_back( type, 6U, fheroes2::Point{ 1, 0 }, MP2::OBJ_TREES ); objects.emplace_back( std::move( object ) ); } // Big forest from top-right to bottom-left. { Maps::ObjectInfo object{ MP2::OBJ_TREES }; object.groundLevelParts.emplace_back( type, 15U, fheroes2::Point{ 0, 0 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 16U, fheroes2::Point{ 1, 0 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 18U, fheroes2::Point{ -1, 1 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 19U, fheroes2::Point{ 0, 1 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 10U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, 13U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, 17U, fheroes2::Point{ -2, 1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( type, 11U, fheroes2::Point{ 0, -1 }, MP2::OBJ_TREES ); object.topLevelParts.emplace_back( type, 12U, fheroes2::Point{ 1, -1 }, MP2::OBJ_TREES ); object.topLevelParts.emplace_back( type, 14U, fheroes2::Point{ -1, 0 }, MP2::OBJ_TREES ); objects.emplace_back( std::move( object ) ); } // Medium forest from top-left to bottom-right. { Maps::ObjectInfo object{ MP2::OBJ_TREES }; object.groundLevelParts.emplace_back( type, 24U, fheroes2::Point{ 0, 0 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 21U, fheroes2::Point{ -1, -1 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 22U, fheroes2::Point{ 0, -1 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 25U, fheroes2::Point{ 1, 0 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 20U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, 23U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium forest from top-right to bottom-left. { Maps::ObjectInfo object{ MP2::OBJ_TREES }; object.groundLevelParts.emplace_back( type, 31U, fheroes2::Point{ 0, 0 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 27U, fheroes2::Point{ 0, -1 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 28U, fheroes2::Point{ 1, -1 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 30U, fheroes2::Point{ -1, 0 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, 26U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, 29U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Small forest (2 variants). for ( uint32_t count = 0; count < 2; ++count ) { const uint32_t offset = count * 2U + 32U; Maps::ObjectInfo object{ MP2::OBJ_TREES }; object.groundLevelParts.emplace_back( type, offset + 1U, fheroes2::Point{ 0, 0 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, offset + 0U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } } // Three trees, grass terrain, variant 1. { Maps::ObjectInfo object{ MP2::OBJ_TREES }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 84U, fheroes2::Point{ 0, 0 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 83U, fheroes2::Point{ -1, 0 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 85U, fheroes2::Point{ 1, 0 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 82U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 79U, fheroes2::Point{ -1, -1 }, MP2::OBJ_TREES ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 80U, fheroes2::Point{ 0, -1 }, MP2::OBJ_TREES ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 81U, fheroes2::Point{ 1, -1 }, MP2::OBJ_TREES ); objects.emplace_back( std::move( object ) ); } // Three trees, grass terrain, variant 2. { Maps::ObjectInfo object{ MP2::OBJ_TREES }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 89U, fheroes2::Point{ 0, 0 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 90U, fheroes2::Point{ 1, 0 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 88U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 86U, fheroes2::Point{ 0, -1 }, MP2::OBJ_TREES ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 87U, fheroes2::Point{ 1, -1 }, MP2::OBJ_TREES ); objects.emplace_back( std::move( object ) ); } // Single tree, grass terrain. { Maps::ObjectInfo object{ MP2::OBJ_TREES }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 93U, fheroes2::Point{ 0, 0 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 92U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 91U, fheroes2::Point{ 0, -1 }, MP2::OBJ_TREES ); objects.emplace_back( std::move( object ) ); } // Two stumps, snow terrain. { Maps::ObjectInfo object{ MP2::OBJ_STUMP }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 41U, fheroes2::Point{ 0, 0 }, MP2::OBJ_STUMP, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 40U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Single stump, snow terrain. { Maps::ObjectInfo object{ MP2::OBJ_STUMP }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 42U, fheroes2::Point{ 0, 0 }, MP2::OBJ_STUMP, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Dead tree (medium, wide), snow terrain. { Maps::ObjectInfo object{ MP2::OBJ_DEAD_TREE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 49U, fheroes2::Point{ 0, 0 }, MP2::OBJ_DEAD_TREE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 50U, fheroes2::Point{ 1, 0 }, MP2::OBJ_DEAD_TREE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 48U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 46U, fheroes2::Point{ 0, -1 }, MP2::OBJ_DEAD_TREE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 47U, fheroes2::Point{ 1, -1 }, MP2::OBJ_DEAD_TREE ); objects.emplace_back( std::move( object ) ); } // Dead tree (tall, wide), snow terrain. { Maps::ObjectInfo object{ MP2::OBJ_DEAD_TREE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 56U, fheroes2::Point{ 0, 0 }, MP2::OBJ_DEAD_TREE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 55U, fheroes2::Point{ -1, 0 }, MP2::OBJ_DEAD_TREE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 57U, fheroes2::Point{ 1, 0 }, MP2::OBJ_DEAD_TREE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 54U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 51U, fheroes2::Point{ -1, -1 }, MP2::OBJ_DEAD_TREE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 52U, fheroes2::Point{ 0, -1 }, MP2::OBJ_DEAD_TREE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 53U, fheroes2::Point{ 1, -1 }, MP2::OBJ_DEAD_TREE ); objects.emplace_back( std::move( object ) ); } // Dead tree (medium, 1-tile), snow terrain. { Maps::ObjectInfo object{ MP2::OBJ_DEAD_TREE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 60U, fheroes2::Point{ 0, 0 }, MP2::OBJ_DEAD_TREE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 59U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 58U, fheroes2::Point{ 0, -1 }, MP2::OBJ_DEAD_TREE ); objects.emplace_back( std::move( object ) ); } // Dead tree (medium, thinned, wide), snow terrain. { Maps::ObjectInfo object{ MP2::OBJ_DEAD_TREE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 64U, fheroes2::Point{ 0, 0 }, MP2::OBJ_DEAD_TREE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 65U, fheroes2::Point{ 1, 0 }, MP2::OBJ_DEAD_TREE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 63U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 61U, fheroes2::Point{ 0, -1 }, MP2::OBJ_DEAD_TREE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 62U, fheroes2::Point{ 1, -1 }, MP2::OBJ_DEAD_TREE ); objects.emplace_back( std::move( object ) ); } // Dead trees (1-tile), snow terrain. 5 variants. for ( uint32_t count = 0; count < 5; ++count ) { const uint32_t offset = 66U + count * 3U; Maps::ObjectInfo object{ MP2::OBJ_DEAD_TREE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, offset + 2U, fheroes2::Point{ 0, 0 }, MP2::OBJ_DEAD_TREE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, offset + 1U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, offset + 0U, fheroes2::Point{ 0, -1 }, MP2::OBJ_DEAD_TREE ); objects.emplace_back( std::move( object ) ); } // Dead tree (tall in the pool), swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_DEAD_TREE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 167U, fheroes2::Point{ 0, 0 }, MP2::OBJ_DEAD_TREE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 166U, fheroes2::Point{ -1, 0 }, MP2::OBJ_DEAD_TREE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 163U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 161U, fheroes2::Point{ -1, -2 }, MP2::OBJ_DEAD_TREE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 162U, fheroes2::Point{ 0, -2 }, MP2::OBJ_DEAD_TREE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 164U, fheroes2::Point{ -1, -1 }, MP2::OBJ_DEAD_TREE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 165U, fheroes2::Point{ 0, -1 }, MP2::OBJ_DEAD_TREE ); objects.emplace_back( std::move( object ) ); } // Dead trees (2-tile), swamp terrain. 2 variants. for ( uint32_t count = 0; count < 2; ++count ) { const uint32_t offset = 168U + count * 5U; Maps::ObjectInfo object{ MP2::OBJ_DEAD_TREE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, offset + 3U, fheroes2::Point{ 0, 0 }, MP2::OBJ_DEAD_TREE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, offset + 4U, fheroes2::Point{ 1, 0 }, MP2::OBJ_DEAD_TREE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, offset + 2U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, offset + 0U, fheroes2::Point{ 0, -1 }, MP2::OBJ_DEAD_TREE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, offset + 1U, fheroes2::Point{ 1, -1 }, MP2::OBJ_DEAD_TREE ); objects.emplace_back( std::move( object ) ); } // Two tall palms, desert terrain. { Maps::ObjectInfo object{ MP2::OBJ_TREES }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 3U, fheroes2::Point{ 0, 0 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); // The next tile contents a shadow and the upper part of the tree. How to render it properly? What layer is better to use? object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 2U, fheroes2::Point{ -1, 0 }, MP2::OBJ_TREES ); objects.emplace_back( std::move( object ) ); } // Tall palms, desert terrain. 3 variants. for ( uint32_t count = 0; count < 3; ++count ) { const uint32_t offset = 4U + count * 3U; Maps::ObjectInfo object{ MP2::OBJ_TREES }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, offset + 2U, fheroes2::Point{ 0, 0 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); // The next tile contents a shadow and the upper part of the tree. How to render it properly? What layer is better to use? object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, offset + 1U, fheroes2::Point{ -1, 0 }, MP2::OBJ_TREES ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, offset + 0U, fheroes2::Point{ 0, -1 }, MP2::OBJ_TREES ); objects.emplace_back( std::move( object ) ); } // Small palms, desert terrain. 3 variants. for ( uint32_t count = 0; count < 3; ++count ) { const uint32_t offset = 23U + count * 2U; Maps::ObjectInfo object{ MP2::OBJ_TREES }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, offset + 1U, fheroes2::Point{ 0, 0 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, offset + 0U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Tall single tree, dirt terrain. { Maps::ObjectInfo object{ MP2::OBJ_TREES }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 118U, fheroes2::Point{ 0, 0 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 117U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 116U, fheroes2::Point{ 1, -1 }, MP2::OBJ_TREES ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 115U, fheroes2::Point{ 0, -1 }, MP2::OBJ_TREES ); // The next tile contents the upper part of the tree. It is passable but should be rendered before the hero on this tile. // How to render it properly? What layer is better to use? object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 119U, fheroes2::Point{ 1, 0 }, MP2::OBJ_TREES ); objects.emplace_back( std::move( object ) ); } // Two tall trees, dirt terrain. { Maps::ObjectInfo object{ MP2::OBJ_TREES }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 123U, fheroes2::Point{ 0, 0 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); // The next tile contents a shadow and the upper part of the tree. How to render it properly? What layer is better to use? object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 122U, fheroes2::Point{ -1, 0 }, MP2::OBJ_TREES ); // The next tile contents the upper part of the tree. It is passable but should be rendered before the hero on this tile. object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 124U, fheroes2::Point{ 1, 0 }, MP2::OBJ_TREES ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 120U, fheroes2::Point{ 0, -1 }, MP2::OBJ_TREES ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 121U, fheroes2::Point{ 1, -1 }, MP2::OBJ_TREES ); objects.emplace_back( std::move( object ) ); } // Two medium trees, dirt terrain. { Maps::ObjectInfo object{ MP2::OBJ_TREES }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 127U, fheroes2::Point{ 0, 0 }, MP2::OBJ_TREES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 126U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 125U, fheroes2::Point{ 0, -1 }, MP2::OBJ_TREES ); objects.emplace_back( std::move( object ) ); } // Dead tree, generic. { Maps::ObjectInfo object{ MP2::OBJ_DEAD_TREE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 2U, fheroes2::Point{ 0, 0 }, MP2::OBJ_DEAD_TREE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 1U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 0U, fheroes2::Point{ 0, -1 }, MP2::OBJ_DEAD_TREE ); objects.emplace_back( std::move( object ) ); } // Log (dead tree), generic. { // We use MP2::OBJ_DEAD_TREE instead of MP2::OBJ_NOTHING_SPECIAL used by the original editor. Maps::ObjectInfo object{ MP2::OBJ_DEAD_TREE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 4U, fheroes2::Point{ 0, 0 }, MP2::OBJ_DEAD_TREE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 3U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Three stumps, generic. { // We use MP2::OBJ_STUMP instead of MP2::OBJ_DEAD_TREE used by the original editor. Maps::ObjectInfo object{ MP2::OBJ_STUMP }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 16U, fheroes2::Point{ 0, 0 }, MP2::OBJ_STUMP, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Two stumps, generic. { // We use MP2::OBJ_STUMP instead of MP2::OBJ_DEAD_TREE used by the original editor. Maps::ObjectInfo object{ MP2::OBJ_STUMP }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 18U, fheroes2::Point{ 0, 0 }, MP2::OBJ_STUMP, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 17U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Single stump, generic. { // We use MP2::OBJ_STUMP instead of MP2::OBJ_DEAD_TREE used by the original editor. Maps::ObjectInfo object{ MP2::OBJ_STUMP }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 19U, fheroes2::Point{ 0, 0 }, MP2::OBJ_STUMP, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } } void populateLandscapeWater( std::vector & objects ) { assert( objects.empty() ); // Rock with seagulls. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 182, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 183, fheroes2::Point{ 1, 0 }, MP2::OBJ_ROCK, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 166, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 118, fheroes2::Point{ -1, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 15; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 150, fheroes2::Point{ 1, -1 }, MP2::OBJ_ROCK ); object.topLevelParts.back().animationFrames = 15; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 134, fheroes2::Point{ 0, -1 }, MP2::OBJ_ROCK ); object.topLevelParts.back().animationFrames = 15; objects.emplace_back( std::move( object ) ); } // Rock. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 185, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::BACKGROUND_LAYER ); objects.emplace_back( std::move( object ) ); } // Rock. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 186, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 187, fheroes2::Point{ 1, 0 }, MP2::OBJ_ROCK, Maps::BACKGROUND_LAYER ); objects.emplace_back( std::move( object ) ); } // Rock. { Maps::ObjectInfo object{ MP2::OBJ_ROCK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWAT2, 2, fheroes2::Point{ 0, 0 }, MP2::OBJ_ROCK, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWAT2, 1, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWAT2, 0, fheroes2::Point{ 0, -1 }, MP2::OBJ_ROCK ); objects.emplace_back( std::move( object ) ); } // Aquatic plants. Terrain object. { Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 83, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 76, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 90, fheroes2::Point{ 1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.back().animationFrames = 6; objects.emplace_back( std::move( object ) ); } // Aquatic plants. Terrain object. { Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 97, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 104, fheroes2::Point{ 1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.back().animationFrames = 6; objects.emplace_back( std::move( object ) ); } // Reefs. { Maps::ObjectInfo object{ MP2::OBJ_REEFS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 113, fheroes2::Point{ 0, 0 }, MP2::OBJ_REEFS, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 114, fheroes2::Point{ 1, 0 }, MP2::OBJ_REEFS, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 115, fheroes2::Point{ 2, 0 }, MP2::OBJ_REEFS, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 111, fheroes2::Point{ 1, -1 }, MP2::OBJ_REEFS, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 112, fheroes2::Point{ 2, -1 }, MP2::OBJ_REEFS, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 116, fheroes2::Point{ 0, 1 }, MP2::OBJ_REEFS, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 117, fheroes2::Point{ 1, 1 }, MP2::OBJ_REEFS, Maps::BACKGROUND_LAYER ); objects.emplace_back( std::move( object ) ); } { Maps::ObjectInfo object{ MP2::OBJ_REEFS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 120, fheroes2::Point{ 0, 0 }, MP2::OBJ_REEFS, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 121, fheroes2::Point{ 1, 0 }, MP2::OBJ_REEFS, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 122, fheroes2::Point{ 2, 0 }, MP2::OBJ_REEFS, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 118, fheroes2::Point{ 0, -1 }, MP2::OBJ_REEFS, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 119, fheroes2::Point{ 1, -1 }, MP2::OBJ_REEFS, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 123, fheroes2::Point{ 1, 1 }, MP2::OBJ_REEFS, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 124, fheroes2::Point{ 2, 1 }, MP2::OBJ_REEFS, Maps::BACKGROUND_LAYER ); objects.emplace_back( std::move( object ) ); } { Maps::ObjectInfo object{ MP2::OBJ_REEFS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 125, fheroes2::Point{ 0, 0 }, MP2::OBJ_REEFS, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 126, fheroes2::Point{ 1, 0 }, MP2::OBJ_REEFS, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 127, fheroes2::Point{ 0, 1 }, MP2::OBJ_REEFS, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 128, fheroes2::Point{ 1, 1 }, MP2::OBJ_REEFS, Maps::BACKGROUND_LAYER ); objects.emplace_back( std::move( object ) ); } { Maps::ObjectInfo object{ MP2::OBJ_REEFS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 129, fheroes2::Point{ 0, 0 }, MP2::OBJ_REEFS, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 130, fheroes2::Point{ -1, 1 }, MP2::OBJ_REEFS, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 131, fheroes2::Point{ 0, 1 }, MP2::OBJ_REEFS, Maps::BACKGROUND_LAYER ); objects.emplace_back( std::move( object ) ); } { Maps::ObjectInfo object{ MP2::OBJ_REEFS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 132, fheroes2::Point{ 0, 0 }, MP2::OBJ_REEFS, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 133, fheroes2::Point{ 1, 0 }, MP2::OBJ_REEFS, Maps::BACKGROUND_LAYER ); objects.emplace_back( std::move( object ) ); } { Maps::ObjectInfo object{ MP2::OBJ_REEFS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 134, fheroes2::Point{ 0, 0 }, MP2::OBJ_REEFS, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 135, fheroes2::Point{ 0, 1 }, MP2::OBJ_REEFS, Maps::BACKGROUND_LAYER ); objects.emplace_back( std::move( object ) ); } } void populateLandscapeMiscellaneous( std::vector & objects ) { assert( objects.empty() ); // Crack, grass terrain. { Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 9U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 8U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Crack, grass terrain. { Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 15U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 10U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 11U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 12U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 13U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 14U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 16U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Crack, grass terrain. { Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 21U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 17U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 18U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 19U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 20U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 22U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 23U, fheroes2::Point{ -1, 1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 24U, fheroes2::Point{ 0, 1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 25U, fheroes2::Point{ 1, 1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Small crack, grass terrain. { Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 28U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 26U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 27U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Big water lake, grass terrain. { Maps::ObjectInfo object{ MP2::OBJ_WATER_LAKE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 61U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 54U, fheroes2::Point{ -2, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 55U, fheroes2::Point{ -1, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 56U, fheroes2::Point{ 0, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 57U, fheroes2::Point{ 1, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 58U, fheroes2::Point{ 2, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 59U, fheroes2::Point{ -2, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 60U, fheroes2::Point{ -1, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 62U, fheroes2::Point{ 1, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 63U, fheroes2::Point{ 2, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 50U, fheroes2::Point{ -1, -2 }, MP2::OBJ_WATER_LAKE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 51U, fheroes2::Point{ 0, -2 }, MP2::OBJ_WATER_LAKE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 52U, fheroes2::Point{ 1, -2 }, MP2::OBJ_WATER_LAKE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 53U, fheroes2::Point{ 2, -2 }, MP2::OBJ_WATER_LAKE ); objects.emplace_back( std::move( object ) ); } // Pink flowers, grass terrain. { Maps::ObjectInfo object{ MP2::OBJ_FLOWERS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 64U, fheroes2::Point{ 0, 0 }, MP2::OBJ_FLOWERS, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium water lake, grass terrain. { Maps::ObjectInfo object{ MP2::OBJ_WATER_LAKE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 71U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 65U, fheroes2::Point{ -2, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 66U, fheroes2::Point{ -1, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 67U, fheroes2::Point{ 0, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 68U, fheroes2::Point{ 1, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 69U, fheroes2::Point{ -2, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 70U, fheroes2::Point{ -1, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 72U, fheroes2::Point{ 1, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Small water lake, grass terrain. { Maps::ObjectInfo object{ MP2::OBJ_WATER_LAKE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 74U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 73U, fheroes2::Point{ -1, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 75U, fheroes2::Point{ 1, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Wide shrub, grass terrain. { Maps::ObjectInfo object{ MP2::OBJ_SHRUB }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 96U, fheroes2::Point{ 0, 0 }, MP2::OBJ_SHRUB, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 95U, fheroes2::Point{ -1, 0 }, MP2::OBJ_SHRUB, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 97U, fheroes2::Point{ 1, 0 }, MP2::OBJ_SHRUB, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 94U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Wide shrub, grass terrain. { Maps::ObjectInfo object{ MP2::OBJ_SHRUB }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 100U, fheroes2::Point{ 0, 0 }, MP2::OBJ_SHRUB, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 99U, fheroes2::Point{ -1, 0 }, MP2::OBJ_SHRUB, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 101U, fheroes2::Point{ 1, 0 }, MP2::OBJ_SHRUB, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium wide shrub, grass terrain. 3 variants. for ( uint32_t count = 0; count < 3; ++count ) { const uint32_t offset = 102U + count * 3U; Maps::ObjectInfo object{ MP2::OBJ_SHRUB }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, offset + 1U, fheroes2::Point{ 0, 0 }, MP2::OBJ_SHRUB, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, offset + 2U, fheroes2::Point{ 1, 0 }, MP2::OBJ_SHRUB, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, offset + 0U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Small shrub, grass terrain. { Maps::ObjectInfo object{ MP2::OBJ_SHRUB }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 112U, fheroes2::Point{ 0, 0 }, MP2::OBJ_SHRUB, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 111U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Wide flowers, grass terrain. { Maps::ObjectInfo object{ MP2::OBJ_FLOWERS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 115U, fheroes2::Point{ 0, 0 }, MP2::OBJ_FLOWERS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 114U, fheroes2::Point{ -1, 0 }, MP2::OBJ_FLOWERS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 116U, fheroes2::Point{ 1, 0 }, MP2::OBJ_FLOWERS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 113U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Wide flowers (taller), grass terrain. { Maps::ObjectInfo object{ MP2::OBJ_FLOWERS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 122U, fheroes2::Point{ 0, 0 }, MP2::OBJ_FLOWERS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 121U, fheroes2::Point{ -1, 0 }, MP2::OBJ_FLOWERS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 123U, fheroes2::Point{ 1, 0 }, MP2::OBJ_FLOWERS, Maps::OBJECT_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 117U, fheroes2::Point{ -1, -1 }, MP2::OBJ_FLOWERS ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 118U, fheroes2::Point{ 0, -1 }, MP2::OBJ_FLOWERS ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 119U, fheroes2::Point{ 1, -1 }, MP2::OBJ_FLOWERS ); objects.emplace_back( std::move( object ) ); } // Wide flowers, grass terrain. 2 variants. for ( uint32_t count = 0; count < 2; ++count ) { const uint32_t offset = 124U + count * 4U; Maps::ObjectInfo object{ MP2::OBJ_FLOWERS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, offset + 2U, fheroes2::Point{ 0, 0 }, MP2::OBJ_FLOWERS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, offset + 3U, fheroes2::Point{ 1, 0 }, MP2::OBJ_FLOWERS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, offset + 1U, fheroes2::Point{ -1, 0 }, MP2::OBJ_FLOWERS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, offset + 0U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium wide flowers (taller), grass terrain. { Maps::ObjectInfo object{ MP2::OBJ_FLOWERS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 136U, fheroes2::Point{ 0, 0 }, MP2::OBJ_FLOWERS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 135U, fheroes2::Point{ -1, 0 }, MP2::OBJ_FLOWERS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 134U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 132U, fheroes2::Point{ -1, -1 }, MP2::OBJ_FLOWERS ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 133U, fheroes2::Point{ 0, -1 }, MP2::OBJ_FLOWERS ); objects.emplace_back( std::move( object ) ); } // Very small flowers, grass terrain. { Maps::ObjectInfo object{ MP2::OBJ_FLOWERS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 137U, fheroes2::Point{ 0, 0 }, MP2::OBJ_FLOWERS, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Low medium-wide flowers, grass terrain. { Maps::ObjectInfo object{ MP2::OBJ_FLOWERS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 140U, fheroes2::Point{ 0, 0 }, MP2::OBJ_FLOWERS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 139U, fheroes2::Point{ -1, 0 }, MP2::OBJ_FLOWERS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 138U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Small (1 tile) flowers, grass terrain. 4 variants. for ( uint32_t count = 0; count < 4; ++count ) { const uint32_t offset = 141U + count * 2U; Maps::ObjectInfo object{ MP2::OBJ_FLOWERS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, offset + 1U, fheroes2::Point{ 0, 0 }, MP2::OBJ_FLOWERS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, offset + 0U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Dug hole, grass terrain. { Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 9U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Small cliff, grass terrain. 3 variants. for ( uint32_t count = 0; count < 3; ++count ) { const uint32_t offset = 10U + count * 2U + ( count == 2 ? 1 : 0 ); Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, offset + 1U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, offset + 0U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Crack, snow terrain. { Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 1U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 0U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Dug hole, snow terrain. { Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 11U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Small cliff, snow terrain. 2 variants. for ( uint32_t count = 0; count < 2; ++count ) { const uint32_t offset = 14U + count * 2U; Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, offset + 1U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, offset + 0U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Wide small cliff, snow terrain. { Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 19U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 18U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 20U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Very small shrub, snow terrain. 3 variants. for ( uint32_t count = 0; count < 3; ++count ) { const uint32_t offset = 43U + count * 1U; Maps::ObjectInfo object{ MP2::OBJ_SHRUB }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, offset + 0U, fheroes2::Point{ 0, 0 }, MP2::OBJ_SHRUB, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Big frozen water lake, snow terrain. { Maps::ObjectInfo object{ MP2::OBJ_WATER_LAKE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 87U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 81U, fheroes2::Point{ -2, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 82U, fheroes2::Point{ -1, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 83U, fheroes2::Point{ 0, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 84U, fheroes2::Point{ 1, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 85U, fheroes2::Point{ -2, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 86U, fheroes2::Point{ -1, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 88U, fheroes2::Point{ 1, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 89U, fheroes2::Point{ 2, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium frozen water lake, snow terrain. { Maps::ObjectInfo object{ MP2::OBJ_WATER_LAKE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 91U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 90U, fheroes2::Point{ -1, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 92U, fheroes2::Point{ 1, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Small frozen water lake, snow terrain. { Maps::ObjectInfo object{ MP2::OBJ_WATER_LAKE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 94U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 95U, fheroes2::Point{ 1, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 93U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Crack, swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 30U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 29U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Shrub, swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_SHRUB }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 33U, fheroes2::Point{ 0, 0 }, MP2::OBJ_SHRUB, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 32U, fheroes2::Point{ -1, 0 }, MP2::OBJ_SHRUB, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 31U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Dug hole, swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 86U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Big water lake, swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_WATER_LAKE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 96U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 88U, fheroes2::Point{ -2, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 89U, fheroes2::Point{ -1, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 90U, fheroes2::Point{ 0, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 91U, fheroes2::Point{ 1, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 94U, fheroes2::Point{ -2, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 95U, fheroes2::Point{ -1, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 97U, fheroes2::Point{ 1, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 98U, fheroes2::Point{ 2, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 101U, fheroes2::Point{ -2, 1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 102U, fheroes2::Point{ -1, 1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 103U, fheroes2::Point{ 0, 1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 104U, fheroes2::Point{ 1, 1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 105U, fheroes2::Point{ 2, 1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 87U, fheroes2::Point{ -3, -1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 92U, fheroes2::Point{ 2, -1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 93U, fheroes2::Point{ -3, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 99U, fheroes2::Point{ 3, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 100U, fheroes2::Point{ -3, 1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 106U, fheroes2::Point{ 3, 1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium (wide) water lake, swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_WATER_LAKE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 113U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 108U, fheroes2::Point{ -1, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 110U, fheroes2::Point{ 1, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 109U, fheroes2::Point{ 0, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 112U, fheroes2::Point{ -1, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 107U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 111U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 114U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium (tall) water lake, swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_WATER_LAKE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 119U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 115U, fheroes2::Point{ -1, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 116U, fheroes2::Point{ 0, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 118U, fheroes2::Point{ -1, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 122U, fheroes2::Point{ -1, 1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 123U, fheroes2::Point{ 0, 1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 117U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 120U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 121U, fheroes2::Point{ -2, 1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 124U, fheroes2::Point{ 1, 1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Single mandrake, swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_MANDRAKE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 126U, fheroes2::Point{ 0, 0 }, MP2::OBJ_MANDRAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 125U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium-wide mandrake, swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_MANDRAKE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 129U, fheroes2::Point{ 0, 0 }, MP2::OBJ_MANDRAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 128U, fheroes2::Point{ -1, 0 }, MP2::OBJ_MANDRAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 127U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Single mandrake, swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_MANDRAKE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 131U, fheroes2::Point{ 0, 0 }, MP2::OBJ_MANDRAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 130U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Wide mandrakes, swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_MANDRAKE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 134U, fheroes2::Point{ 0, 0 }, MP2::OBJ_MANDRAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 133U, fheroes2::Point{ -1, 0 }, MP2::OBJ_MANDRAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 135U, fheroes2::Point{ 1, 0 }, MP2::OBJ_MANDRAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 132U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Single small mandrake, swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_MANDRAKE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 137U, fheroes2::Point{ 0, 0 }, MP2::OBJ_MANDRAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 136U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Wide swampy water lake, swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_WATER_LAKE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 147U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 142U, fheroes2::Point{ 0, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 143U, fheroes2::Point{ 1, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 144U, fheroes2::Point{ 2, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 145U, fheroes2::Point{ -2, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 146U, fheroes2::Point{ -1, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 148U, fheroes2::Point{ 1, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 149U, fheroes2::Point{ 2, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Small wide swampy water lake, swamp terrain. 3 variants. In original editor is called "Nothing Special" and "Shrub". for ( uint32_t count = 0; count < 3; ++count ) { const uint32_t offset = 150U + count * 3U; Maps::ObjectInfo object{ MP2::OBJ_WATER_LAKE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, offset + 1U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, offset + 0U, fheroes2::Point{ -1, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, offset + 2U, fheroes2::Point{ 1, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Small swampy water lake, swamp terrain. In original editor is called "Nothing Special". { Maps::ObjectInfo object{ MP2::OBJ_WATER_LAKE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 160U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 159U, fheroes2::Point{ -1, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Wide reed, swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_NOTHING_SPECIAL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 181U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NOTHING_SPECIAL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 179U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NOTHING_SPECIAL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 180U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NOTHING_SPECIAL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 182U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NOTHING_SPECIAL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 183U, fheroes2::Point{ 2, 0 }, MP2::OBJ_NOTHING_SPECIAL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 178U, fheroes2::Point{ -3, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium-wide reed, swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_NOTHING_SPECIAL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 185U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NOTHING_SPECIAL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 184U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NOTHING_SPECIAL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 186U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NOTHING_SPECIAL, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Small reed, swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_NOTHING_SPECIAL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 187U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NOTHING_SPECIAL, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Rotten roots, swamp terrain. 2 variants. In original editor is called "Nothing Special". for ( uint32_t count = 0; count < 2; ++count ) { const uint32_t offset = 188U + count * 2U; Maps::ObjectInfo object{ MP2::OBJ_NOTHING_SPECIAL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, offset + 1U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NOTHING_SPECIAL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, offset + 0U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NOTHING_SPECIAL, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Small rotten roots, swamp terrain. 2 variants. In original editor is called "Nothing Special". { Maps::ObjectInfo object{ MP2::OBJ_NOTHING_SPECIAL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 192U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NOTHING_SPECIAL, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium-wide shrub, swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_SHRUB }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 194U, fheroes2::Point{ 0, 0 }, MP2::OBJ_SHRUB, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 193U, fheroes2::Point{ -1, 0 }, MP2::OBJ_SHRUB, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Small shrub, swamp terrain. 2variants. for ( uint32_t count = 0; count < 2; ++count ) { const uint32_t offset = 195U + count * 2U; Maps::ObjectInfo object{ MP2::OBJ_SHRUB }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, offset + 1U, fheroes2::Point{ 0, 0 }, MP2::OBJ_SHRUB, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, offset + 0U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium-wide shrub, swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_SHRUB }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 200U, fheroes2::Point{ 0, 0 }, MP2::OBJ_SHRUB, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 199U, fheroes2::Point{ -1, 0 }, MP2::OBJ_SHRUB, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Small shrub, swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_SHRUB }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 201U, fheroes2::Point{ 0, 0 }, MP2::OBJ_SHRUB, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Mossy roots, swamp terrain. In original editor is called "Nothing Special". { Maps::ObjectInfo object{ MP2::OBJ_NOTHING_SPECIAL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 213U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NOTHING_SPECIAL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 212U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NOTHING_SPECIAL, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Crack, lava terrain. { Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 1U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 0U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium crater (wide), lava terrain. { Maps::ObjectInfo object{ MP2::OBJ_CRATER }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 8U, fheroes2::Point{ 0, 0 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 2U, fheroes2::Point{ -2, -1 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 3U, fheroes2::Point{ -1, -1 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 4U, fheroes2::Point{ 0, -1 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 5U, fheroes2::Point{ 1, -1 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 6U, fheroes2::Point{ -2, 0 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 7U, fheroes2::Point{ -1, 0 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 9U, fheroes2::Point{ 1, 0 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium crater (high), lava terrain. { Maps::ObjectInfo object{ MP2::OBJ_CRATER }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 15U, fheroes2::Point{ 0, 0 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 12U, fheroes2::Point{ -1, -1 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 13U, fheroes2::Point{ 0, -1 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 14U, fheroes2::Point{ -1, 0 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 16U, fheroes2::Point{ -1, 1 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 17U, fheroes2::Point{ 0, 1 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Small lava pool, lava terrain. { Maps::ObjectInfo object{ MP2::OBJ_LAVAPOOL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 20U, fheroes2::Point{ 0, 0 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 18U, fheroes2::Point{ 0, -1 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 19U, fheroes2::Point{ -1, 0 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Passable lava, lava terrain. { Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 24U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 21U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 22U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 23U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 25U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Dug hole, lava terrain. { Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 26U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Big lava pool, lava terrain. { Maps::ObjectInfo object{ MP2::OBJ_LAVAPOOL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 36U, fheroes2::Point{ 0, 0 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 27U, fheroes2::Point{ -3, -1 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 28U, fheroes2::Point{ -2, -1 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 29U, fheroes2::Point{ -1, -1 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 30U, fheroes2::Point{ 0, -1 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 31U, fheroes2::Point{ 1, -1 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 32U, fheroes2::Point{ 2, -1 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 33U, fheroes2::Point{ -3, 0 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 34U, fheroes2::Point{ -2, 0 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 35U, fheroes2::Point{ -1, 0 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 37U, fheroes2::Point{ 1, 0 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 38U, fheroes2::Point{ 2, 0 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium lava pool, lava terrain. { Maps::ObjectInfo object{ MP2::OBJ_LAVAPOOL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 43U, fheroes2::Point{ 0, 0 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 39U, fheroes2::Point{ -1, -1 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 40U, fheroes2::Point{ 0, -1 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 41U, fheroes2::Point{ 1, -1 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 42U, fheroes2::Point{ -1, 0 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 44U, fheroes2::Point{ 1, 0 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium (little lifted) lava pool, lava terrain. { Maps::ObjectInfo object{ MP2::OBJ_LAVAPOOL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 51U, fheroes2::Point{ 0, 0 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 46U, fheroes2::Point{ -1, -1 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 47U, fheroes2::Point{ 0, -1 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 48U, fheroes2::Point{ 1, -1 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 50U, fheroes2::Point{ -1, 0 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 52U, fheroes2::Point{ 1, 0 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 49U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Small lava streams, lava terrain. { Maps::ObjectInfo object{ MP2::OBJ_LAVAPOOL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 56U, fheroes2::Point{ 0, 0 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 53U, fheroes2::Point{ -1, -1 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 54U, fheroes2::Point{ 0, -1 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 55U, fheroes2::Point{ -1, 0 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Small lava streams, lava terrain. { Maps::ObjectInfo object{ MP2::OBJ_LAVAPOOL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 59U, fheroes2::Point{ 0, 0 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 57U, fheroes2::Point{ 0, -1 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 58U, fheroes2::Point{ -1, 0 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Small lava streams, lava terrain. { Maps::ObjectInfo object{ MP2::OBJ_LAVAPOOL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 63U, fheroes2::Point{ 0, 0 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 60U, fheroes2::Point{ -1, -1 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 61U, fheroes2::Point{ 0, -1 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 62U, fheroes2::Point{ -1, 0 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium lava streams, lava terrain. { Maps::ObjectInfo object{ MP2::OBJ_LAVAPOOL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 67U, fheroes2::Point{ 0, 0 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 65U, fheroes2::Point{ 1, -1 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 66U, fheroes2::Point{ -1, 0 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 68U, fheroes2::Point{ 1, 0 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 64U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium lava streams, lava terrain. { Maps::ObjectInfo object{ MP2::OBJ_LAVAPOOL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 72U, fheroes2::Point{ 0, 0 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 69U, fheroes2::Point{ -1, -1 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 70U, fheroes2::Point{ 0, -1 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 71U, fheroes2::Point{ -1, 0 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 73U, fheroes2::Point{ 1, 0 }, MP2::OBJ_LAVAPOOL, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Smoking volcano crater (without the mountain), lava terrain. { Maps::ObjectInfo object{ MP2::OBJ_VOLCANO }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 98U, fheroes2::Point{ 0, 0 }, MP2::OBJ_VOLCANO, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 9; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 88U, fheroes2::Point{ -1, 0 }, MP2::OBJ_VOLCANO, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 9; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 78U, fheroes2::Point{ 0, -1 }, MP2::OBJ_VOLCANO ); object.topLevelParts.back().animationFrames = 9; objects.emplace_back( std::move( object ) ); } // Crack, desert terrain. { Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 1U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 0U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Tiny cactus, desert terrain. 3 variants. for ( uint32_t count = 0; count < 3; ++count ) { const uint32_t offset = 30U + count * 1U; Maps::ObjectInfo object{ MP2::OBJ_CACTUS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, offset + 0U, fheroes2::Point{ 0, 0 }, MP2::OBJ_CACTUS, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium cactus, desert terrain. 2 variants. for ( uint32_t count = 0; count < 2; ++count ) { const uint32_t offset = 33U + count * 2U; Maps::ObjectInfo object{ MP2::OBJ_CACTUS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, offset + 1U, fheroes2::Point{ 0, 0 }, MP2::OBJ_CACTUS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, offset + 0U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium cactus, desert terrain. { Maps::ObjectInfo object{ MP2::OBJ_CACTUS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 39U, fheroes2::Point{ 0, 0 }, MP2::OBJ_CACTUS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 38U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 37U, fheroes2::Point{ 0, -1 }, MP2::OBJ_CACTUS ); objects.emplace_back( std::move( object ) ); } // Small cactus, desert terrain. { Maps::ObjectInfo object{ MP2::OBJ_CACTUS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 40U, fheroes2::Point{ 0, 0 }, MP2::OBJ_CACTUS, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium cactus, desert terrain. { Maps::ObjectInfo object{ MP2::OBJ_CACTUS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 42U, fheroes2::Point{ 0, 0 }, MP2::OBJ_CACTUS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 41U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium cactus, desert terrain. { Maps::ObjectInfo object{ MP2::OBJ_CACTUS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 45U, fheroes2::Point{ 0, 0 }, MP2::OBJ_CACTUS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 44U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 43U, fheroes2::Point{ 0, -1 }, MP2::OBJ_CACTUS ); objects.emplace_back( std::move( object ) ); } // Medium cactus, desert terrain. { Maps::ObjectInfo object{ MP2::OBJ_CACTUS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 48U, fheroes2::Point{ 0, 0 }, MP2::OBJ_CACTUS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 47U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Small cactus, desert terrain. { Maps::ObjectInfo object{ MP2::OBJ_CACTUS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 49U, fheroes2::Point{ 0, 0 }, MP2::OBJ_CACTUS, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium cactus, desert terrain. 2 variants. for ( uint32_t count = 0; count < 2; ++count ) { const uint32_t offset = 50U + count * 2U; Maps::ObjectInfo object{ MP2::OBJ_CACTUS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, offset + 1U, fheroes2::Point{ 0, 0 }, MP2::OBJ_CACTUS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, offset + 0U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Dug hole, desert terrain. { Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 68U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Big hole, desert terrain. In original editor is called "Nothing Special". { Maps::ObjectInfo object{ MP2::OBJ_NOTHING_SPECIAL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 111U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NOTHING_SPECIAL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 110U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NOTHING_SPECIAL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 112U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NOTHING_SPECIAL, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Crack, dirt terrain. { Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 11U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 10U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Crack, dirt terrain. 3 variants. for ( uint32_t count = 0; count < 3; ++count ) { const uint32_t offset = 17U + count * 2U; Maps::ObjectInfo object{ MP2::OBJ_CRATER }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, offset + 1U, fheroes2::Point{ 0, 0 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, offset + 0U, fheroes2::Point{ -1, 0 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Big water lake, dirt terrain. { Maps::ObjectInfo object{ MP2::OBJ_WATER_LAKE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 35U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 23U, fheroes2::Point{ -4, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 24U, fheroes2::Point{ -3, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 25U, fheroes2::Point{ -2, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 26U, fheroes2::Point{ -1, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 27U, fheroes2::Point{ 0, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 28U, fheroes2::Point{ 1, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 29U, fheroes2::Point{ 2, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 30U, fheroes2::Point{ 3, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 31U, fheroes2::Point{ -4, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 32U, fheroes2::Point{ -3, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 33U, fheroes2::Point{ -2, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 34U, fheroes2::Point{ -1, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 36U, fheroes2::Point{ 1, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 37U, fheroes2::Point{ 2, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 38U, fheroes2::Point{ 3, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 40U, fheroes2::Point{ -3, 1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 41U, fheroes2::Point{ -2, 1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 42U, fheroes2::Point{ -1, 1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 43U, fheroes2::Point{ 0, 1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 44U, fheroes2::Point{ 1, 1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 45U, fheroes2::Point{ 2, 1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 39U, fheroes2::Point{ -4, 1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 46U, fheroes2::Point{ 3, 1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Medium water lake, dirt terrain. { Maps::ObjectInfo object{ MP2::OBJ_WATER_LAKE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 55U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 48U, fheroes2::Point{ -2, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 49U, fheroes2::Point{ -1, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 50U, fheroes2::Point{ 0, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 51U, fheroes2::Point{ 1, -1 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 53U, fheroes2::Point{ -2, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 54U, fheroes2::Point{ -1, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 56U, fheroes2::Point{ 1, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Small water lake, dirt terrain. { Maps::ObjectInfo object{ MP2::OBJ_WATER_LAKE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 58U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 57U, fheroes2::Point{ -1, 0 }, MP2::OBJ_WATER_LAKE, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Wide shrub, dirt terrain. 3 variants. for ( uint32_t count = 0; count < 3; ++count ) { const uint32_t offset = 59U + count * 3U; Maps::ObjectInfo object{ MP2::OBJ_SHRUB }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, offset + 2U, fheroes2::Point{ 0, 0 }, MP2::OBJ_SHRUB, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, offset + 1U, fheroes2::Point{ -1, 0 }, MP2::OBJ_SHRUB, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, offset + 0U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Shrub, dirt terrain. 2 variants. for ( uint32_t count = 0; count < 2; ++count ) { const uint32_t offset = 68U + count * 2U; Maps::ObjectInfo object{ MP2::OBJ_SHRUB }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, offset + 1U, fheroes2::Point{ 0, 0 }, MP2::OBJ_SHRUB, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, offset + 0U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Wide flowers, dirt terrain. 4 variants. for ( uint32_t count = 0; count < 4; ++count ) { const uint32_t offset = 72U + count * 3U; Maps::ObjectInfo object{ MP2::OBJ_FLOWERS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, offset + 2U, fheroes2::Point{ 0, 0 }, MP2::OBJ_FLOWERS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, offset + 1U, fheroes2::Point{ -1, 0 }, MP2::OBJ_FLOWERS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, offset + 0U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Small flowers, dirt terrain. { Maps::ObjectInfo object{ MP2::OBJ_FLOWERS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 85U, fheroes2::Point{ 0, 0 }, MP2::OBJ_FLOWERS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 84U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Tiny flowers, dirt terrain. { Maps::ObjectInfo object{ MP2::OBJ_FLOWERS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 86U, fheroes2::Point{ 0, 0 }, MP2::OBJ_FLOWERS, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Small flowers, dirt terrain. { Maps::ObjectInfo object{ MP2::OBJ_FLOWERS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 88U, fheroes2::Point{ 0, 0 }, MP2::OBJ_FLOWERS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 87U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Tiny flowers, dirt terrain. 2 variants. for ( uint32_t count = 0; count < 2; ++count ) { const uint32_t offset = 89U + count * 1U; Maps::ObjectInfo object{ MP2::OBJ_FLOWERS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, offset + 0U, fheroes2::Point{ 0, 0 }, MP2::OBJ_FLOWERS, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Tiny meadow, dirt terrain. In original editor is called "Nothing Special". { Maps::ObjectInfo object{ MP2::OBJ_NOTHING_SPECIAL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 106U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NOTHING_SPECIAL, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Wide meadow, dirt terrain. 2 variants. In original editor is called "Nothing Special". for ( uint32_t count = 0; count < 2; ++count ) { const uint32_t offset = 107U + count * 2U; Maps::ObjectInfo object{ MP2::OBJ_NOTHING_SPECIAL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, offset + 1U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NOTHING_SPECIAL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, offset + 0U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NOTHING_SPECIAL, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Grass (high), dirt terrain. In original editor is called "Nothing Special". { Maps::ObjectInfo object{ MP2::OBJ_NOTHING_SPECIAL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 112U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NOTHING_SPECIAL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 111U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Grass, dirt terrain. In original editor is called "Nothing Special". { Maps::ObjectInfo object{ MP2::OBJ_NOTHING_SPECIAL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 113U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NOTHING_SPECIAL, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Dug hole, dirt terrain. { Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 140U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Small cliff, dirt terrain. 3 variants. for ( uint32_t count = 0; count < 2; ++count ) { const uint32_t offset = 141U + count * 2U; Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, offset + 1U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, offset + 0U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Crack, wasteland terrain. { Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 6U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 5U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Cactus, wasteland terrain. { Maps::ObjectInfo object{ MP2::OBJ_CACTUS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 14U, fheroes2::Point{ 0, 0 }, MP2::OBJ_CACTUS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 13U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 12U, fheroes2::Point{ 0, -1 }, MP2::OBJ_CACTUS ); objects.emplace_back( std::move( object ) ); } // Cactus, wasteland terrain. { Maps::ObjectInfo object{ MP2::OBJ_CACTUS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 16U, fheroes2::Point{ 0, 0 }, MP2::OBJ_CACTUS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 15U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Cow scull, wasteland terrain. { Maps::ObjectInfo object{ MP2::OBJ_NOTHING_SPECIAL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 17U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NOTHING_SPECIAL, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Shrub, wasteland terrain. { Maps::ObjectInfo object{ MP2::OBJ_SHRUB }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 57U, fheroes2::Point{ 0, 0 }, MP2::OBJ_SHRUB, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 56U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Big crack, wasteland terrain. { Maps::ObjectInfo object{ MP2::OBJ_CRATER }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 64U, fheroes2::Point{ 0, 0 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 58U, fheroes2::Point{ -2, -1 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 59U, fheroes2::Point{ -1, -1 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 62U, fheroes2::Point{ -2, 0 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 63U, fheroes2::Point{ -1, 0 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 65U, fheroes2::Point{ 1, 0 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 68U, fheroes2::Point{ 0, 1 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 69U, fheroes2::Point{ 1, 1 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 60U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 61U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 66U, fheroes2::Point{ -2, 1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 67U, fheroes2::Point{ -1, 1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Dug hole, wasteland terrain. { Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 70U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Shrub, wasteland terrain. 2 variants. for ( uint32_t count = 0; count < 2; ++count ) { const uint32_t offset = 71U + count * 1U; Maps::ObjectInfo object{ MP2::OBJ_SHRUB }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, offset + 0U, fheroes2::Point{ 0, 0 }, MP2::OBJ_SHRUB, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Tar pit, wasteland terrain. { Maps::ObjectInfo object{ MP2::OBJ_TAR_PIT }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 113U, fheroes2::Point{ 0, 0 }, MP2::OBJ_TAR_PIT, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 10; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 76U, fheroes2::Point{ -2, -1 }, MP2::OBJ_TAR_PIT, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 77U, fheroes2::Point{ -1, -1 }, MP2::OBJ_TAR_PIT, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 78U, fheroes2::Point{ 0, -1 }, MP2::OBJ_TAR_PIT, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 80U, fheroes2::Point{ -3, 0 }, MP2::OBJ_TAR_PIT, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 10; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 91U, fheroes2::Point{ -2, 0 }, MP2::OBJ_TAR_PIT, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 10; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 102U, fheroes2::Point{ -1, 0 }, MP2::OBJ_TAR_PIT, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 10; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 124U, fheroes2::Point{ 1, 0 }, MP2::OBJ_TAR_PIT, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 10; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 135U, fheroes2::Point{ 2, 0 }, MP2::OBJ_TAR_PIT, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 136U, fheroes2::Point{ -3, 1 }, MP2::OBJ_TAR_PIT, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 137U, fheroes2::Point{ -2, 1 }, MP2::OBJ_TAR_PIT, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 10; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 148U, fheroes2::Point{ -1, 1 }, MP2::OBJ_TAR_PIT, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 10; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 159U, fheroes2::Point{ 0, 1 }, MP2::OBJ_TAR_PIT, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 10; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 170U, fheroes2::Point{ 1, 1 }, MP2::OBJ_TAR_PIT, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 10; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 181U, fheroes2::Point{ 2, 1 }, MP2::OBJ_TAR_PIT, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Crack, wasteland terrain. { Maps::ObjectInfo object{ MP2::OBJ_CRATER }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 225U, fheroes2::Point{ 0, 0 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 221U, fheroes2::Point{ -1, -1 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 222U, fheroes2::Point{ 0, -1 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 223U, fheroes2::Point{ 1, -1 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 224U, fheroes2::Point{ -1, 0 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 226U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Crack (vertical), wasteland terrain. { Maps::ObjectInfo object{ MP2::OBJ_CRATER }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 230U, fheroes2::Point{ 0, 0 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 227U, fheroes2::Point{ -1, -1 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 228U, fheroes2::Point{ 0, -1 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 229U, fheroes2::Point{ -1, 0 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 231U, fheroes2::Point{ -1, 1 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 232U, fheroes2::Point{ 0, 1 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Crack (horizontal), wasteland terrain. { Maps::ObjectInfo object{ MP2::OBJ_CRATER }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 234U, fheroes2::Point{ 0, 0 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 233U, fheroes2::Point{ -1, 0 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 235U, fheroes2::Point{ 1, 0 }, MP2::OBJ_CRATER, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Shrub, generic terrain. { Maps::ObjectInfo object{ MP2::OBJ_SHRUB }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 64U, fheroes2::Point{ 0, 0 }, MP2::OBJ_SHRUB, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 63U, fheroes2::Point{ -1, 0 }, MP2::OBJ_SHRUB, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 65U, fheroes2::Point{ 1, 0 }, MP2::OBJ_SHRUB, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // River delta (ocean on bottom), generic terrain. { Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 2U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 0U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 1U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 3U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 4U, fheroes2::Point{ -1, 1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 5U, fheroes2::Point{ 0, 1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 6U, fheroes2::Point{ 1, 1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // River delta (ocean on top), generic terrain. { Maps::ObjectInfo object{ MP2::OBJ_NONE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 11U, fheroes2::Point{ 0, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 7U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 8U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 9U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 10U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 12U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 13U, fheroes2::Point{ 0, 1 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } } void populateLandscapeTownBasements( std::vector & objects ) { assert( objects.empty() ); // Castle/town basement in the next order: grass, snow, swamp, lava, desert, dirt, wasteland, beach. for ( uint8_t basement = 0; basement < 8; ++basement ) { const uint8_t icnOffset = basement * 10; Maps::ObjectInfo object{ MP2::OBJ_NON_ACTION_CASTLE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNTWBA, icnOffset + 2, fheroes2::Point{ 0, 0 }, MP2::OBJ_NON_ACTION_CASTLE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNTWBA, icnOffset + 0, fheroes2::Point{ -2, 0 }, MP2::OBJ_NON_ACTION_CASTLE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNTWBA, icnOffset + 1, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_CASTLE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNTWBA, icnOffset + 3, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_CASTLE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNTWBA, icnOffset + 4, fheroes2::Point{ 2, 0 }, MP2::OBJ_NON_ACTION_CASTLE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNTWBA, icnOffset + 5, fheroes2::Point{ -2, 1 }, MP2::OBJ_NON_ACTION_CASTLE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNTWBA, icnOffset + 6, fheroes2::Point{ -1, 1 }, MP2::OBJ_NON_ACTION_CASTLE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNTWBA, icnOffset + 7, fheroes2::Point{ 0, 1 }, MP2::OBJ_NON_ACTION_CASTLE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNTWBA, icnOffset + 8, fheroes2::Point{ 1, 1 }, MP2::OBJ_NON_ACTION_CASTLE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNTWBA, icnOffset + 9, fheroes2::Point{ 2, 1 }, MP2::OBJ_NON_ACTION_CASTLE, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } } void populateLandscapeFlags( std::vector & objects ) { assert( objects.empty() ); // Castle flags located one tile to the left and right from the castle entrance: blue, green, red, yellow, orange, purple, gray (neutral). for ( uint8_t color = 0; color < 7; ++color ) { const uint8_t icnOffset = color * 2; { // Left flag. Maps::ObjectInfo object{ MP2::OBJ_NON_ACTION_CASTLE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_FLAG32, icnOffset, fheroes2::Point{ 0, 0 }, MP2::OBJ_NON_ACTION_CASTLE, Maps::OBJECT_LAYER ); object.metadata[0] = color; objects.emplace_back( std::move( object ) ); } { // Right flag. Maps::ObjectInfo object{ MP2::OBJ_NON_ACTION_CASTLE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_FLAG32, icnOffset + 1, fheroes2::Point{ 0, 0 }, MP2::OBJ_NON_ACTION_CASTLE, Maps::OBJECT_LAYER ); object.metadata[0] = color; objects.emplace_back( std::move( object ) ); } } // TODO: Add flags for other capture-able objects. } void populateAdventureArtifacts( std::vector & objects ) { assert( objects.empty() ); // All artifacts before Magic Book have their own images. // Magic Book is not present in the ICN resources but it is present in artifact IDs. auto addArtifactObject = [&objects]( const uint32_t artifactId, const MP2::MapObjectType ObjectType, const uint32_t mainIcnIndex ) { Maps::ObjectInfo object{ ObjectType }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNARTI, mainIcnIndex, fheroes2::Point{ 0, 0 }, ObjectType, Maps::OBJECT_LAYER ); object.metadata[0] = artifactId; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNARTI, mainIcnIndex - 1U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); }; for ( uint32_t artifactId = Artifact::ARCANE_NECKLACE; artifactId < Artifact::MAGIC_BOOK; ++artifactId ) { addArtifactObject( artifactId, MP2::OBJ_ARTIFACT, artifactId * 2U - 1U ); } // Assign temporary sprites for the Magic Book. addArtifactObject( Artifact::MAGIC_BOOK, MP2::OBJ_ARTIFACT, 207U ); for ( uint32_t artifactId = Artifact::SPELL_SCROLL; artifactId < Artifact::ARTIFACT_COUNT; ++artifactId ) { addArtifactObject( artifactId, MP2::OBJ_ARTIFACT, artifactId * 2U - 1U ); } // Random artifacts. addArtifactObject( Artifact::UNKNOWN, MP2::OBJ_RANDOM_ARTIFACT, 163U ); addArtifactObject( Artifact::UNKNOWN, MP2::OBJ_RANDOM_ARTIFACT_MINOR, 167U ); addArtifactObject( Artifact::UNKNOWN, MP2::OBJ_RANDOM_ARTIFACT_MAJOR, 169U ); addArtifactObject( Artifact::UNKNOWN, MP2::OBJ_RANDOM_ARTIFACT_TREASURE, 171U ); // The random Ultimate artifact does not have a shadow in original assets. // We temporary use an empty shadow image from the Spell Scroll artifact for this case. { Maps::ObjectInfo object{ MP2::OBJ_RANDOM_ULTIMATE_ARTIFACT }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNARTI, 164U, fheroes2::Point{ 0, 0 }, MP2::OBJ_RANDOM_ULTIMATE_ARTIFACT, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNARTI, 172U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } } void populateAdventureDwellings( std::vector & objects ) { assert( objects.empty() ); // Peasant Hut. { Maps::ObjectInfo object{ MP2::OBJ_PEASANT_HUT }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 35U, fheroes2::Point{ 0, 0 }, MP2::OBJ_PEASANT_HUT, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 25U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 9; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 15U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 9; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 5U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_PEASANT_HUT ); object.topLevelParts.back().animationFrames = 9; objects.emplace_back( std::move( object ) ); } // Ruins (for Medusa). { Maps::ObjectInfo object{ MP2::OBJ_RUINS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 73U, fheroes2::Point{ 0, 0 }, MP2::OBJ_RUINS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 74U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_RUINS, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Tree House (for Sprites). { Maps::ObjectInfo object{ MP2::OBJ_TREE_HOUSE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 114U, fheroes2::Point{ 0, 0 }, MP2::OBJ_TREE_HOUSE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 113U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 112U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_TREE_HOUSE ); objects.emplace_back( std::move( object ) ); } // Watch Tower (for Orcs). { Maps::ObjectInfo object{ MP2::OBJ_WATCH_TOWER }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 116U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WATCH_TOWER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 115U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Watch Tower (for Orcs). { Maps::ObjectInfo object{ MP2::OBJ_WATCH_TOWER }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 138U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WATCH_TOWER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 137U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 136U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_WATCH_TOWER ); objects.emplace_back( std::move( object ) ); } // Halfling Hole. { Maps::ObjectInfo object{ MP2::OBJ_HALFLING_HOLE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 138U, fheroes2::Point{ 0, 0 }, MP2::OBJ_HALFLING_HOLE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 139U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_HALFLING_HOLE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 137U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_HALFLING_HOLE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 136U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Halfling Hole. { Maps::ObjectInfo object{ MP2::OBJ_HALFLING_HOLE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 7U, fheroes2::Point{ 0, 0 }, MP2::OBJ_HALFLING_HOLE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 8U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_HALFLING_HOLE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 6U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_HALFLING_HOLE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 5U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Tree City (for Sprites). { Maps::ObjectInfo object{ MP2::OBJ_TREE_CITY }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 151U, fheroes2::Point{ 0, 0 }, MP2::OBJ_TREE_CITY, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 152U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_TREE_CITY, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 150U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 148U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_TREE_CITY ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 147U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_TREE_CITY ); objects.emplace_back( std::move( object ) ); } // Tree City (for Sprites). { Maps::ObjectInfo object{ MP2::OBJ_TREE_CITY }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 21U, fheroes2::Point{ 0, 0 }, MP2::OBJ_TREE_CITY, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 22U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_TREE_CITY, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 20U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 18U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_TREE_CITY ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 17U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_TREE_CITY ); objects.emplace_back( std::move( object ) ); } // Desert Tent (for Nomads). { Maps::ObjectInfo object{ MP2::OBJ_DESERT_TENT }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 73U, fheroes2::Point{ 0, 0 }, MP2::OBJ_DESERT_TENT, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 72U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_DESERT_TENT, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 71U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 70U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_DESERT_TENT ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 69U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_DESERT_TENT ); objects.emplace_back( std::move( object ) ); } // City of the Dead (for Liches). { Maps::ObjectInfo object{ MP2::OBJ_CITY_OF_DEAD }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 96U, fheroes2::Point{ 0, 0 }, MP2::OBJ_CITY_OF_DEAD, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 94U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NON_ACTION_CITY_OF_DEAD, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 95U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_CITY_OF_DEAD, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 97U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_CITY_OF_DEAD, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 98U, fheroes2::Point{ 2, 0 }, MP2::OBJ_NON_ACTION_CITY_OF_DEAD, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 89U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NON_ACTION_CITY_OF_DEAD, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 90U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_CITY_OF_DEAD, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 91U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_CITY_OF_DEAD, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 92U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_CITY_OF_DEAD, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 93U, fheroes2::Point{ 2, -1 }, MP2::OBJ_NON_ACTION_CITY_OF_DEAD, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Excavation (for Skeletons). { Maps::ObjectInfo object{ MP2::OBJ_EXCAVATION }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 101U, fheroes2::Point{ 0, 0 }, MP2::OBJ_EXCAVATION, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 100U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_EXCAVATION, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 99U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NON_ACTION_EXCAVATION, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Troll's Bridge. { // This is the only action object who's parts should be on background layer. Maps::ObjectInfo object{ MP2::OBJ_TROLL_BRIDGE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 189U, fheroes2::Point{ 0, 0 }, MP2::OBJ_TROLL_BRIDGE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 188U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_TROLL_BRIDGE, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 187U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NON_ACTION_TROLL_BRIDGE, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 186U, fheroes2::Point{ -3, 0 }, MP2::OBJ_NON_ACTION_TROLL_BRIDGE, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 185U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_TROLL_BRIDGE, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 184U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_TROLL_BRIDGE, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 183U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NON_ACTION_TROLL_BRIDGE, Maps::BACKGROUND_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 182U, fheroes2::Point{ -3, -1 }, MP2::OBJ_NON_ACTION_TROLL_BRIDGE, Maps::BACKGROUND_LAYER ); objects.emplace_back( std::move( object ) ); } // Archer's House. { Maps::ObjectInfo object{ MP2::OBJ_ARCHER_HOUSE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 84U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ARCHER_HOUSE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 77U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 70U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 63U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_ARCHER_HOUSE ); objects.emplace_back( std::move( object ) ); } // Goblin's Hut. { Maps::ObjectInfo object{ MP2::OBJ_GOBLIN_HUT }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 92U, fheroes2::Point{ 0, 0 }, MP2::OBJ_GOBLIN_HUT, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 91U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Dwarf's Cottage. { Maps::ObjectInfo object{ MP2::OBJ_DWARF_COTTAGE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 114U, fheroes2::Point{ 0, 0 }, MP2::OBJ_DWARF_COTTAGE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 107U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 100U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 93U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_DWARF_COTTAGE ); object.topLevelParts.back().animationFrames = 6; objects.emplace_back( std::move( object ) ); } // Dragon City. { Maps::ObjectInfo object{ MP2::OBJ_DRAGON_CITY }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 54U, fheroes2::Point{ 0, 0 }, MP2::OBJ_DRAGON_CITY, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 51U, fheroes2::Point{ -3, 0 }, MP2::OBJ_NON_ACTION_DRAGON_CITY, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 52U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NON_ACTION_DRAGON_CITY, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 53U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_DRAGON_CITY, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 55U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_DRAGON_CITY, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 46U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_DRAGON_CITY, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 49U, fheroes2::Point{ -5, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 50U, fheroes2::Point{ -4, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 42U, fheroes2::Point{ -5, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 43U, fheroes2::Point{ -4, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 44U, fheroes2::Point{ -3, -1 }, MP2::OBJ_NON_ACTION_DRAGON_CITY ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 45U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NON_ACTION_DRAGON_CITY ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 47U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_DRAGON_CITY ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 48U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_DRAGON_CITY ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 37U, fheroes2::Point{ -3, -2 }, MP2::OBJ_NON_ACTION_DRAGON_CITY ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 38U, fheroes2::Point{ -2, -2 }, MP2::OBJ_NON_ACTION_DRAGON_CITY ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 39U, fheroes2::Point{ -1, -2 }, MP2::OBJ_NON_ACTION_DRAGON_CITY ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 40U, fheroes2::Point{ 0, -2 }, MP2::OBJ_NON_ACTION_DRAGON_CITY ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 41U, fheroes2::Point{ 1, -2 }, MP2::OBJ_NON_ACTION_DRAGON_CITY ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 34U, fheroes2::Point{ -2, -3 }, MP2::OBJ_NONE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 35U, fheroes2::Point{ -1, -3 }, MP2::OBJ_NON_ACTION_DRAGON_CITY ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 36U, fheroes2::Point{ 0, -3 }, MP2::OBJ_NONE ); objects.emplace_back( std::move( object ) ); } // Graveyard (for Zombies). { Maps::ObjectInfo object{ MP2::OBJ_GRAVEYARD }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 58U, fheroes2::Point{ 0, 0 }, MP2::OBJ_GRAVEYARD, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 57U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_GRAVEYARD, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 56U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Graveyard (for Zombies). { Maps::ObjectInfo object{ MP2::OBJ_GRAVEYARD }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 160U, fheroes2::Point{ 0, 0 }, MP2::OBJ_GRAVEYARD, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 159U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_GRAVEYARD, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 158U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Graveyard (for Zombies). { Maps::ObjectInfo object{ MP2::OBJ_GRAVEYARD }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 122U, fheroes2::Point{ 0, 0 }, MP2::OBJ_GRAVEYARD, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 121U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_GRAVEYARD, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 120U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Graveyard (for Zombies). { Maps::ObjectInfo object{ MP2::OBJ_GRAVEYARD }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 208U, fheroes2::Point{ 0, 0 }, MP2::OBJ_GRAVEYARD, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 209U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_GRAVEYARD, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 207U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_GRAVEYARD, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 206U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 205U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_GRAVEYARD ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 204U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_GRAVEYARD ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 203U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_GRAVEYARD ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 202U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE ); objects.emplace_back( std::move( object ) ); } // Graveyard (for Zombies). { Maps::ObjectInfo object{ MP2::OBJ_GRAVEYARD }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 209U, fheroes2::Point{ 0, 0 }, MP2::OBJ_GRAVEYARD, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 210U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_GRAVEYARD, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 208U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_GRAVEYARD, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 207U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 206U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_GRAVEYARD ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 205U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_GRAVEYARD ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 204U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_GRAVEYARD ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 203U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE ); objects.emplace_back( std::move( object ) ); } // Wagon Camp (for Rogues). { Maps::ObjectInfo object{ MP2::OBJ_WAGON_CAMP }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 129U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WAGON_CAMP, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 136U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_WAGON_CAMP, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 128U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_WAGON_CAMP, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 127U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 126U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_WAGON_CAMP ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 125U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_WAGON_CAMP ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 124U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_WAGON_CAMP ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 123U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE ); objects.emplace_back( std::move( object ) ); } // Cave (for Centaurs). { Maps::ObjectInfo object{ MP2::OBJ_CAVE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 3U, fheroes2::Point{ 0, 0 }, MP2::OBJ_CAVE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 2U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_CAVE, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Barrow Mounds (for Ghosts). { Maps::ObjectInfo object{ MP2::OBJ_BARROW_MOUNDS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 77U, fheroes2::Point{ 0, 0 }, MP2::OBJ_BARROW_MOUNDS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 76U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_BARROW_MOUNDS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 75U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NON_ACTION_BARROW_MOUNDS, Maps::OBJECT_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 74U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_BARROW_MOUNDS ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 73U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_BARROW_MOUNDS ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 72U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE ); objects.emplace_back( std::move( object ) ); } // Earth Summoning Altar (for Earth Elementals). { Maps::ObjectInfo object{ MP2::OBJ_EARTH_ALTAR }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 94U, fheroes2::Point{ 0, 0 }, MP2::OBJ_EARTH_ALTAR, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 8; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 103U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_EARTH_ALTAR, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 8; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 85U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_EARTH_ALTAR, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 8; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 84U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 83U, fheroes2::Point{ -3, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 82U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_EARTH_ALTAR ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 81U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_EARTH_ALTAR ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 80U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_EARTH_ALTAR ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 79U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 78U, fheroes2::Point{ -3, -1 }, MP2::OBJ_NONE ); objects.emplace_back( std::move( object ) ); } // Air Summoning Altar (for Air Elementals). { Maps::ObjectInfo object{ MP2::OBJ_AIR_ALTAR }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 118U, fheroes2::Point{ 0, 0 }, MP2::OBJ_AIR_ALTAR, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 119U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_AIR_ALTAR, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 117U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_AIR_ALTAR, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 116U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 115U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_AIR_ALTAR ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 114U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_AIR_ALTAR ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 113U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NONE ); objects.emplace_back( std::move( object ) ); } // Fire Summoning Altar (for Fire Elementals). { Maps::ObjectInfo object{ MP2::OBJ_FIRE_ALTAR }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 127U, fheroes2::Point{ 0, 0 }, MP2::OBJ_FIRE_ALTAR, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 128U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_FIRE_ALTAR, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 126U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_FIRE_ALTAR, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 125U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 124U, fheroes2::Point{ -3, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 123U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_FIRE_ALTAR ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 122U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_FIRE_ALTAR ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 121U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_FIRE_ALTAR ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 120U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE ); objects.emplace_back( std::move( object ) ); } // Water Summoning Altar (for Water Elementals). { Maps::ObjectInfo object{ MP2::OBJ_WATER_ALTAR }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 135U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WATER_ALTAR, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 136U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_WATER_ALTAR, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 134U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_WATER_ALTAR, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 133U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 132U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_WATER_ALTAR ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 131U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_WATER_ALTAR ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 130U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_WATER_ALTAR ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 129U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE ); objects.emplace_back( std::move( object ) ); } } void populateAdventureMines( std::vector & objects ) { assert( objects.empty() ); // Mines provide 5 resources: Ore, Sulfur, Crystal, Gems, Gold. const uint8_t mineResources = 5; // Mines have different appearances: Generic, Grass, Snow, Swamp, Lava, Desert, Dirt, Wasteland. for ( const auto & [type, offset] : { std::make_pair( MP2::OBJ_ICN_TYPE_MTNMULT, 74U ), std::make_pair( MP2::OBJ_ICN_TYPE_MTNGRAS, 74U ), std::make_pair( MP2::OBJ_ICN_TYPE_MTNSNOW, 74U ), std::make_pair( MP2::OBJ_ICN_TYPE_MTNSWMP, 74U ), std::make_pair( MP2::OBJ_ICN_TYPE_MTNLAVA, 74U ), std::make_pair( MP2::OBJ_ICN_TYPE_MTNDSRT, 74U ), std::make_pair( MP2::OBJ_ICN_TYPE_MTNDIRT, 104U ), std::make_pair( MP2::OBJ_ICN_TYPE_MTNCRCK, 104U ) } ) { Maps::ObjectInfo object{ MP2::OBJ_MINE }; object.groundLevelParts.emplace_back( type, offset + 8U, fheroes2::Point{ 0, 0 }, MP2::OBJ_MINE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, offset + 4U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_MINE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, offset + 7U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_MINE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, offset + 9U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_MINE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, offset + 0U, fheroes2::Point{ -3, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, offset + 1U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, offset + 5U, fheroes2::Point{ -3, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( type, offset + 6U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( type, offset + 2U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_MINE ); object.topLevelParts.emplace_back( type, offset + 3U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_MINE ); // The carts with these resources are placed above the main mine tile. for ( uint8_t resource = 0; resource < mineResources; ++resource ) { object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_EXTRAOVR, resource, fheroes2::Point{ 0, 0 }, MP2::OBJ_MINE, Maps::OBJECT_LAYER ); // Set the metadata: 0 - resource type, 1 - income per day. switch ( resource ) { case 0: object.metadata[0] = Resource::ORE; object.metadata[1] = 2; break; case 1: object.metadata[0] = Resource::SULFUR; object.metadata[1] = 1; break; case 2: object.metadata[0] = Resource::CRYSTAL; object.metadata[1] = 1; break; case 3: object.metadata[0] = Resource::GEMS; object.metadata[1] = 1; break; case 4: object.metadata[0] = Resource::GOLD; object.metadata[1] = 1000; break; default: // Have you added a new mine resource?! Update the logic above! assert( 0 ); break; } if ( resource < mineResources - 1 ) { objects.emplace_back( object ); // Remove the resource from the mine to add a new one in the next loop. object.groundLevelParts.pop_back(); } } // The last resource with the current mine appearance type. objects.emplace_back( std::move( object ) ); } // Abandoned mines are only for Grass and Dirt and they have different tiles number. { Maps::ObjectInfo object{ MP2::OBJ_ABANDONED_MINE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 6U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ABANDONED_MINE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 3U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_ABANDONED_MINE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 5U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_ABANDONED_MINE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 7U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_ABANDONED_MINE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 0U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 4U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 1U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_ABANDONED_MINE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 2U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_ABANDONED_MINE ); objects.emplace_back( std::move( object ) ); } { Maps::ObjectInfo object{ MP2::OBJ_ABANDONED_MINE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 8U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ABANDONED_MINE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 4U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_ABANDONED_MINE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 7U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_ABANDONED_MINE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 9U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_ABANDONED_MINE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 0U, fheroes2::Point{ -3, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 1U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 5U, fheroes2::Point{ -3, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 6U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 2U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_ABANDONED_MINE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 3U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_ABANDONED_MINE ); objects.emplace_back( std::move( object ) ); } // Sawmills for different terrains: Grass/Swamp, Snow, Lava, Desert, Dirt, Wasteland. for ( const auto & [type, offset] : { std::make_pair( MP2::OBJ_ICN_TYPE_OBJNMUL2, 210U ), std::make_pair( MP2::OBJ_ICN_TYPE_OBJNSNOW, 195U ), std::make_pair( MP2::OBJ_ICN_TYPE_OBJNLAVA, 118U ), std::make_pair( MP2::OBJ_ICN_TYPE_OBJNDSRT, 123U ), std::make_pair( MP2::OBJ_ICN_TYPE_OBJNMUL2, 74U ), std::make_pair( MP2::OBJ_ICN_TYPE_OBJNCRCK, 239U ) } ) { Maps::ObjectInfo object{ MP2::OBJ_SAWMILL }; object.groundLevelParts.emplace_back( type, offset + 6U, fheroes2::Point{ 0, 0 }, MP2::OBJ_SAWMILL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, offset + 2U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_SAWMILL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, offset + 3U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_SAWMILL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, offset + 4U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NON_ACTION_SAWMILL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, offset + 5U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_SAWMILL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( type, offset + 7U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_SAWMILL, Maps::OBJECT_LAYER ); object.topLevelParts.emplace_back( type, offset + 0U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NON_ACTION_SAWMILL ); object.topLevelParts.emplace_back( type, offset + 1U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_SAWMILL ); // Set resource type and income per day. object.metadata[0] = Resource::WOOD; object.metadata[1] = 2; objects.emplace_back( std::move( object ) ); } // Alchemist Lab (one for all terrains). { Maps::ObjectInfo object{ MP2::OBJ_ALCHEMIST_LAB }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 26U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ALCHEMIST_LAB, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 25U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_ALCHEMIST_LAB, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 27U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_ALCHEMIST_LAB, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 24U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 20U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 21U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_ALCHEMIST_LAB ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 22U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_ALCHEMIST_LAB ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 23U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_ALCHEMIST_LAB ); // Set resource type and income per day. object.metadata[0] = Resource::MERCURY; object.metadata[1] = 1; objects.emplace_back( std::move( object ) ); } } void populateAdventurePowerUps( std::vector & objects ) { assert( objects.empty() ); // Artesian Spring. { Maps::ObjectInfo object{ MP2::OBJ_ARTESIAN_SPRING }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 3U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ARTESIAN_SPRING, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 4U, fheroes2::Point{ 1, 0 }, MP2::OBJ_ARTESIAN_SPRING, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 2U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 1U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NONE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 0U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NONE ); objects.emplace_back( std::move( object ) ); } // Watering Hole. { Maps::ObjectInfo object{ MP2::OBJ_WATERING_HOLE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 218U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WATERING_HOLE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 217U, fheroes2::Point{ -1, 0 }, MP2::OBJ_WATERING_HOLE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 219U, fheroes2::Point{ 1, 0 }, MP2::OBJ_WATERING_HOLE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 220U, fheroes2::Point{ 2, 0 }, MP2::OBJ_WATERING_HOLE, Maps::OBJECT_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 216U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NONE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 215U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NONE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 214U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NONE ); objects.emplace_back( std::move( object ) ); } // Faerie Ring. { Maps::ObjectInfo object{ MP2::OBJ_FAERIE_RING }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 129U, fheroes2::Point{ 0, 0 }, MP2::OBJ_FAERIE_RING, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 130U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_FAERIE_RING, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 128U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Faerie Ring. { Maps::ObjectInfo object{ MP2::OBJ_FAERIE_RING }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 30U, fheroes2::Point{ 0, 0 }, MP2::OBJ_FAERIE_RING, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 31U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_FAERIE_RING, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRAS, 29U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Oasis. { Maps::ObjectInfo object{ MP2::OBJ_OASIS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 108U, fheroes2::Point{ 0, 0 }, MP2::OBJ_OASIS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 109U, fheroes2::Point{ 1, 0 }, MP2::OBJ_OASIS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 107U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 106U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_OASIS ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 105U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_OASIS ); objects.emplace_back( std::move( object ) ); } // Fountain. { Maps::ObjectInfo object{ MP2::OBJ_FOUNTAIN }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 15U, fheroes2::Point{ 0, 0 }, MP2::OBJ_FOUNTAIN, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 14U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Magic Well. { Maps::ObjectInfo object{ MP2::OBJ_MAGIC_WELL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 162U, fheroes2::Point{ 0, 0 }, MP2::OBJ_MAGIC_WELL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 161U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Magic Well. { Maps::ObjectInfo object{ MP2::OBJ_MAGIC_WELL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 165U, fheroes2::Point{ 0, 0 }, MP2::OBJ_MAGIC_WELL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 164U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Magic Well. { Maps::ObjectInfo object{ MP2::OBJ_MAGIC_WELL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 194U, fheroes2::Point{ 0, 0 }, MP2::OBJ_MAGIC_WELL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 193U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 192U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NONE ); objects.emplace_back( std::move( object ) ); } // Fort. { Maps::ObjectInfo object{ MP2::OBJ_FORT }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 59U, fheroes2::Point{ 0, 0 }, MP2::OBJ_FORT, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 58U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_FORT, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 57U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 56U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_FORT ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 55U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_FORT ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 54U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 45U, fheroes2::Point{ -3, -1 }, MP2::OBJ_NONE ); object.topLevelParts.back().animationFrames = 8; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 36U, fheroes2::Point{ -1, -2 }, MP2::OBJ_NONE ); object.topLevelParts.back().animationFrames = 8; objects.emplace_back( std::move( object ) ); } // Gazebo. { Maps::ObjectInfo object{ MP2::OBJ_GAZEBO }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 62U, fheroes2::Point{ 0, 0 }, MP2::OBJ_GAZEBO, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 61U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 60U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_FORT ); objects.emplace_back( std::move( object ) ); } // Witch Doctor's Hut. { Maps::ObjectInfo object{ MP2::OBJ_WITCH_DOCTORS_HUT }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 69U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WITCH_DOCTORS_HUT, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 68U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 67U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 66U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_FORT ); objects.emplace_back( std::move( object ) ); } // Mercenary Camp. { Maps::ObjectInfo object{ MP2::OBJ_MERCENARY_CAMP }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 71U, fheroes2::Point{ 0, 0 }, MP2::OBJ_MERCENARY_CAMP, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 72U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_MERCENARY_CAMP, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 70U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_MERCENARY_CAMP, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Shrine of the First Circle. { Maps::ObjectInfo object{ MP2::OBJ_SHRINE_FIRST_CIRCLE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 80U, fheroes2::Point{ 0, 0 }, MP2::OBJ_SHRINE_FIRST_CIRCLE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 79U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Shrine of the Second Circle. { Maps::ObjectInfo object{ MP2::OBJ_SHRINE_SECOND_CIRCLE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 76U, fheroes2::Point{ 0, 0 }, MP2::OBJ_SHRINE_SECOND_CIRCLE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 75U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Shrine of the Third Circle. { Maps::ObjectInfo object{ MP2::OBJ_SHRINE_THIRD_CIRCLE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 78U, fheroes2::Point{ 0, 0 }, MP2::OBJ_SHRINE_THIRD_CIRCLE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 77U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Idol. { Maps::ObjectInfo object{ MP2::OBJ_IDOL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 82U, fheroes2::Point{ 0, 0 }, MP2::OBJ_IDOL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 81U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Standing Stones. { Maps::ObjectInfo object{ MP2::OBJ_STANDING_STONES }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 84U, fheroes2::Point{ 0, 0 }, MP2::OBJ_STANDING_STONES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 85U, fheroes2::Point{ 1, 0 }, MP2::OBJ_STANDING_STONES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 83U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Temple. { Maps::ObjectInfo object{ MP2::OBJ_TEMPLE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 88U, fheroes2::Point{ 0, 0 }, MP2::OBJ_TEMPLE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 89U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_TEMPLE, Maps::OBJECT_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 87U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_FORT ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 86U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_FORT ); objects.emplace_back( std::move( object ) ); } // Tree of Knowledge. { Maps::ObjectInfo object{ MP2::OBJ_TREE_OF_KNOWLEDGE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 123U, fheroes2::Point{ 0, 0 }, MP2::OBJ_TREE_OF_KNOWLEDGE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 122U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 121U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 120U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_TREE_OF_KNOWLEDGE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 119U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_TREE_OF_KNOWLEDGE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 118U, fheroes2::Point{ 0, -2 }, MP2::OBJ_NON_ACTION_TREE_OF_KNOWLEDGE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 117U, fheroes2::Point{ -1, -2 }, MP2::OBJ_NONE ); objects.emplace_back( std::move( object ) ); } // Xanadu. { Maps::ObjectInfo object{ MP2::OBJ_XANADU }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 81U, fheroes2::Point{ 0, 0 }, MP2::OBJ_XANADU, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 82U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_XANADU, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 74U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_XANADU, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 67U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NON_ACTION_XANADU, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 66U, fheroes2::Point{ -3, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 65U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_XANADU ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 58U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_XANADU ); object.topLevelParts.back().animationFrames = 6; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 51U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_XANADU ); object.topLevelParts.back().animationFrames = 6; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 50U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NON_ACTION_XANADU ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 43U, fheroes2::Point{ -3, -1 }, MP2::OBJ_NON_ACTION_XANADU ); object.topLevelParts.back().animationFrames = 6; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 42U, fheroes2::Point{ 1, -2 }, MP2::OBJ_NONE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 41U, fheroes2::Point{ 0, -2 }, MP2::OBJ_NONE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 34U, fheroes2::Point{ -1, -2 }, MP2::OBJ_NON_ACTION_XANADU ); object.topLevelParts.back().animationFrames = 6; objects.emplace_back( std::move( object ) ); } // Arena. { Maps::ObjectInfo object{ MP2::OBJ_ARENA }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 70U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ARENA, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 71U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_ARENA, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 69U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_ARENA, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 68U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 59U, fheroes2::Point{ -3, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 8; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 50U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_ARENA, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 8; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 49U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_ARENA, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 40U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_ARENA, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 8; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 31U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 8; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 22U, fheroes2::Point{ 1, -2 }, MP2::OBJ_NON_ACTION_ARENA ); object.topLevelParts.back().animationFrames = 8; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 13U, fheroes2::Point{ 0, -2 }, MP2::OBJ_NON_ACTION_ARENA ); object.topLevelParts.back().animationFrames = 8; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 4U, fheroes2::Point{ -1, -2 }, MP2::OBJ_NONE ); object.topLevelParts.back().animationFrames = 8; objects.emplace_back( std::move( object ) ); } } void populateAdventureTreasures( std::vector & objects ) { assert( objects.empty() ); // Normal resources. int32_t resourceImageIndex = 1; for ( const uint32_t resource : { Resource::WOOD, Resource::MERCURY, Resource::ORE, Resource::SULFUR, Resource::CRYSTAL, Resource::GEMS, Resource::GOLD } ) { Maps::ObjectInfo object{ MP2::OBJ_RESOURCE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNRSRC, resourceImageIndex, fheroes2::Point{ 0, 0 }, MP2::OBJ_RESOURCE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNRSRC, resourceImageIndex - 1, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.metadata[0] = resource; objects.emplace_back( std::move( object ) ); resourceImageIndex += 2; } // Genie's Lamp. { Maps::ObjectInfo object{ MP2::OBJ_GENIE_LAMP }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNRSRC, 15, fheroes2::Point{ 0, 0 }, MP2::OBJ_GENIE_LAMP, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNRSRC, 14, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Random resource. { Maps::ObjectInfo object{ MP2::OBJ_RANDOM_RESOURCE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNRSRC, 17, fheroes2::Point{ 0, 0 }, MP2::OBJ_RANDOM_RESOURCE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNRSRC, 16, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Treasure chest. { Maps::ObjectInfo object{ MP2::OBJ_TREASURE_CHEST }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNRSRC, 19, fheroes2::Point{ 0, 0 }, MP2::OBJ_TREASURE_CHEST, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNRSRC, 18, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Campfire. { Maps::ObjectInfo object{ MP2::OBJ_CAMPFIRE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 131, fheroes2::Point{ 0, 0 }, MP2::OBJ_CAMPFIRE, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 124, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 6; objects.emplace_back( std::move( object ) ); } // Campfire on show. { Maps::ObjectInfo object{ MP2::OBJ_CAMPFIRE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 4, fheroes2::Point{ 0, 0 }, MP2::OBJ_CAMPFIRE, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 6; // The original resources do not have a part of shadow for this object so we use the same shadow from another campfire. object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 54, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 6; objects.emplace_back( std::move( object ) ); } // Campfire in desert. { Maps::ObjectInfo object{ MP2::OBJ_CAMPFIRE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 61, fheroes2::Point{ 0, 0 }, MP2::OBJ_CAMPFIRE, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 54, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 6; objects.emplace_back( std::move( object ) ); } } void populateAdventureWater( std::vector & objects ) { assert( objects.empty() ); // Bottle. { Maps::ObjectInfo object{ MP2::OBJ_BOTTLE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 0, fheroes2::Point{ 0, 0 }, MP2::OBJ_BOTTLE, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 11; objects.emplace_back( std::move( object ) ); } // Sea chest. { Maps::ObjectInfo object{ MP2::OBJ_SEA_CHEST }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 19, fheroes2::Point{ 0, 0 }, MP2::OBJ_SEA_CHEST, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 12, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 6; objects.emplace_back( std::move( object ) ); } // Flotsam. { Maps::ObjectInfo object{ MP2::OBJ_FLOTSAM }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 45, fheroes2::Point{ 0, 0 }, MP2::OBJ_FLOTSAM, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 38, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 6; objects.emplace_back( std::move( object ) ); } // Buoy. { Maps::ObjectInfo object{ MP2::OBJ_BUOY }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 195, fheroes2::Point{ 0, 0 }, MP2::OBJ_BUOY, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 188, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 6; objects.emplace_back( std::move( object ) ); } // Boat. { Maps::ObjectInfo object{ MP2::OBJ_BOAT }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_BOAT32, 18, fheroes2::Point{ 0, 0 }, MP2::OBJ_BOAT, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Shipwreck survivor. { Maps::ObjectInfo object{ MP2::OBJ_SHIPWRECK_SURVIVOR }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 111, fheroes2::Point{ 0, 0 }, MP2::OBJ_SHIPWRECK_SURVIVOR, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 6; objects.emplace_back( std::move( object ) ); } // Magellan's Maps. { Maps::ObjectInfo object{ MP2::OBJ_MAGELLANS_MAPS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 62, fheroes2::Point{ 0, 0 }, MP2::OBJ_MAGELLANS_MAPS, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 69, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_MAGELLANS_MAPS, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 55, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 54, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_MAGELLANS_MAPS ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 53, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_MAGELLANS_MAPS ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 52, fheroes2::Point{ -1, -1 }, MP2::OBJ_NONE ); objects.emplace_back( std::move( object ) ); } // Whirlpool. The center point is middle lower tile. { Maps::ObjectInfo object{ MP2::OBJ_WHIRLPOOL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 218, fheroes2::Point{ 0, 0 }, MP2::OBJ_WHIRLPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 3; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 222, fheroes2::Point{ 1, 0 }, MP2::OBJ_WHIRLPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 3; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 214, fheroes2::Point{ -1, 0 }, MP2::OBJ_WHIRLPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 3; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 206, fheroes2::Point{ 0, -1 }, MP2::OBJ_WHIRLPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 3; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 210, fheroes2::Point{ 1, -1 }, MP2::OBJ_WHIRLPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 3; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 202, fheroes2::Point{ -1, -1 }, MP2::OBJ_WHIRLPOOL, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 3; objects.emplace_back( std::move( object ) ); } // Shipwreck. { Maps::ObjectInfo object{ MP2::OBJ_SHIPWRECK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 241, fheroes2::Point{ 0, 0 }, MP2::OBJ_SHIPWRECK, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 248, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_SHIPWRECK, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 240, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 233, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_SHIPWRECK ); object.topLevelParts.back().animationFrames = 6; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWATR, 226, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_SHIPWRECK ); object.topLevelParts.back().animationFrames = 6; objects.emplace_back( std::move( object ) ); } // Derelict Ship. { Maps::ObjectInfo object{ MP2::OBJ_DERELICT_SHIP }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWAT2, 21, fheroes2::Point{ 0, 0 }, MP2::OBJ_DERELICT_SHIP, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWAT2, 20, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_DERELICT_SHIP, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWAT2, 22, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_DERELICT_SHIP, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWAT2, 19, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_DERELICT_SHIP, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWAT2, 12, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_DERELICT_SHIP, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWAT2, 10, fheroes2::Point{ 1, -2 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWAT2, 11, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_DERELICT_SHIP ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNWAT2, 3, fheroes2::Point{ 0, -2 }, MP2::OBJ_NON_ACTION_DERELICT_SHIP ); objects.emplace_back( std::move( object ) ); } // Mermaid. { Maps::ObjectInfo object{ MP2::OBJ_MERMAID }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 37, fheroes2::Point{ 0, 0 }, MP2::OBJ_MERMAID, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 8; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 46, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_MERMAID, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 28, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_MERMAID, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 8; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 10, fheroes2::Point{ -1, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 8; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 19, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_MERMAID ); object.topLevelParts.back().animationFrames = 8; objects.emplace_back( std::move( object ) ); } // Sirens. { Maps::ObjectInfo object{ MP2::OBJ_SIRENS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 101, fheroes2::Point{ 0, 0 }, MP2::OBJ_SIRENS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 102, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_SIRENS, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 8; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 92, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_SIRENS, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 8; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 83, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 8; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 47, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 8; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 56, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_SIRENS ); object.topLevelParts.back().animationFrames = 8; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 65, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_SIRENS ); object.topLevelParts.back().animationFrames = 8; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 74, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_SIRENS ); object.topLevelParts.back().animationFrames = 8; objects.emplace_back( std::move( object ) ); } } void populateAdventureMiscellaneous( std::vector & objects ) { assert( objects.empty() ); // Hill Fort, grass terrain. { Maps::ObjectInfo object{ MP2::OBJ_HILL_FORT }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 4U, fheroes2::Point{ 0, 0 }, MP2::OBJ_HILL_FORT, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 3U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_HILL_FORT, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 2U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NON_ACTION_HILL_FORT, Maps::OBJECT_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 0U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_HILL_FORT ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 1U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_HILL_FORT ); objects.emplace_back( std::move( object ) ); } // Hill Fort, dirt terrain. { Maps::ObjectInfo object{ MP2::OBJ_HILL_FORT }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 135U, fheroes2::Point{ 0, 0 }, MP2::OBJ_HILL_FORT, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 134U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_HILL_FORT, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 133U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NON_ACTION_HILL_FORT, Maps::OBJECT_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 131U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_HILL_FORT ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 132U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_HILL_FORT ); objects.emplace_back( std::move( object ) ); } // Windmill, grass terrain. { Maps::ObjectInfo object{ MP2::OBJ_WINDMILL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 55U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WINDMILL, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 3; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 59U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_WINDMILL, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 3; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 31U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 3; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 35U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 3; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 47U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 3; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 51U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 3; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 23U, fheroes2::Point{ 0, -2 }, MP2::OBJ_NON_ACTION_WINDMILL ); object.topLevelParts.back().animationFrames = 3; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 27U, fheroes2::Point{ 1, -2 }, MP2::OBJ_NON_ACTION_WINDMILL ); object.topLevelParts.back().animationFrames = 3; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 39U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_WINDMILL ); object.topLevelParts.back().animationFrames = 3; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 43U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_WINDMILL ); object.topLevelParts.back().animationFrames = 3; objects.emplace_back( std::move( object ) ); } // Windmill, snow terrain. { Maps::ObjectInfo object{ MP2::OBJ_WINDMILL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 128U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WINDMILL, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 3; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 132U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_WINDMILL, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 3; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 104U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 3; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 108U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 3; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 120U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 3; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 124U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 3; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 96U, fheroes2::Point{ 0, -2 }, MP2::OBJ_NON_ACTION_WINDMILL ); object.topLevelParts.back().animationFrames = 3; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 100U, fheroes2::Point{ 1, -2 }, MP2::OBJ_NON_ACTION_WINDMILL ); object.topLevelParts.back().animationFrames = 3; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 112U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_WINDMILL ); object.topLevelParts.back().animationFrames = 3; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 116U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_WINDMILL ); object.topLevelParts.back().animationFrames = 3; objects.emplace_back( std::move( object ) ); } // Windmill, dirt terrain. { Maps::ObjectInfo object{ MP2::OBJ_WINDMILL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 185U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WINDMILL, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 3; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 189U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_WINDMILL, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 3; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 161U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 3; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 165U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 3; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 177U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 3; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 181U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 3; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 153U, fheroes2::Point{ 0, -2 }, MP2::OBJ_NON_ACTION_WINDMILL ); object.topLevelParts.back().animationFrames = 3; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 157U, fheroes2::Point{ 1, -2 }, MP2::OBJ_NON_ACTION_WINDMILL ); object.topLevelParts.back().animationFrames = 3; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 169U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_WINDMILL ); object.topLevelParts.back().animationFrames = 3; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 173U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_WINDMILL ); object.topLevelParts.back().animationFrames = 3; objects.emplace_back( std::move( object ) ); } // Oracle, grass terrain. { Maps::ObjectInfo object{ MP2::OBJ_ORACLE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 126U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ORACLE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 125U, fheroes2::Point{ -1, 0 }, MP2::OBJ_ORACLE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 124U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 122U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_ORACLE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 123U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_ORACLE ); objects.emplace_back( std::move( object ) ); } // Oracle, dirt terrain. { Maps::ObjectInfo object{ MP2::OBJ_ORACLE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 198U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ORACLE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 197U, fheroes2::Point{ -1, 0 }, MP2::OBJ_ORACLE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 196U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 194U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_ORACLE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 195U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_ORACLE ); objects.emplace_back( std::move( object ) ); } // Obelisk, grass terrain. { Maps::ObjectInfo object{ MP2::OBJ_OBELISK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 129U, fheroes2::Point{ 0, 0 }, MP2::OBJ_OBELISK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 128U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNGRA2, 127U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_OBELISK ); objects.emplace_back( std::move( object ) ); } // Obelisk, snow terrain. { Maps::ObjectInfo object{ MP2::OBJ_OBELISK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 141U, fheroes2::Point{ 0, 0 }, MP2::OBJ_OBELISK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 140U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 139U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_OBELISK ); objects.emplace_back( std::move( object ) ); } // Obelisk, swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_OBELISK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 216U, fheroes2::Point{ 0, 0 }, MP2::OBJ_OBELISK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 215U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 214U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_OBELISK ); objects.emplace_back( std::move( object ) ); } // Obelisk, lava terrain. { Maps::ObjectInfo object{ MP2::OBJ_OBELISK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 110U, fheroes2::Point{ 0, 0 }, MP2::OBJ_OBELISK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 109U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 108U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_OBELISK ); objects.emplace_back( std::move( object ) ); } // Obelisk, desert terrain. { Maps::ObjectInfo object{ MP2::OBJ_OBELISK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 104U, fheroes2::Point{ 0, 0 }, MP2::OBJ_OBELISK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 103U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 102U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_OBELISK ); objects.emplace_back( std::move( object ) ); } // Obelisk, wasteland terrain. { Maps::ObjectInfo object{ MP2::OBJ_OBELISK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 238U, fheroes2::Point{ 0, 0 }, MP2::OBJ_OBELISK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 237U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 236U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_OBELISK ); objects.emplace_back( std::move( object ) ); } // Obelisk, dirt terrain. { Maps::ObjectInfo object{ MP2::OBJ_OBELISK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 201U, fheroes2::Point{ 0, 0 }, MP2::OBJ_OBELISK, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 200U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDIRT, 199U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_OBELISK ); objects.emplace_back( std::move( object ) ); } // Lean-to, snow terrain. { Maps::ObjectInfo object{ MP2::OBJ_LEAN_TO }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 13U, fheroes2::Point{ 0, 0 }, MP2::OBJ_LEAN_TO, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 12U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); objects.emplace_back( std::move( object ) ); } // Sign, snow terrain. { Maps::ObjectInfo object{ MP2::OBJ_SIGN }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 143U, fheroes2::Point{ 0, 0 }, MP2::OBJ_SIGN, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 142U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Sign, swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_SIGN }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 140U, fheroes2::Point{ 0, 0 }, MP2::OBJ_SIGN, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Sign, lava terrain. { Maps::ObjectInfo object{ MP2::OBJ_SIGN }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 117U, fheroes2::Point{ 0, 0 }, MP2::OBJ_SIGN, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 116U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Sign, desert terrain. { Maps::ObjectInfo object{ MP2::OBJ_SIGN }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 119U, fheroes2::Point{ 0, 0 }, MP2::OBJ_SIGN, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 118U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Sign, generic terrain. { Maps::ObjectInfo object{ MP2::OBJ_SIGN }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 114U, fheroes2::Point{ 0, 0 }, MP2::OBJ_SIGN, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 113U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Water wheel, snow terrain. { Maps::ObjectInfo object{ MP2::OBJ_WATER_WHEEL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 191U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WATER_WHEEL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 177U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NON_ACTION_WATER_WHEEL, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 184U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_WATER_WHEEL, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 161U, fheroes2::Point{ -1, -2 }, MP2::OBJ_NON_ACTION_WATER_WHEEL ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 162U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NON_ACTION_WATER_WHEEL ); object.topLevelParts.back().animationFrames = 6; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 169U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_WATER_WHEEL ); object.topLevelParts.back().animationFrames = 6; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSNOW, 176U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_WATER_WHEEL ); objects.emplace_back( std::move( object ) ); } // Water wheel, generic terrain. { Maps::ObjectInfo object{ MP2::OBJ_WATER_WHEEL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 112U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WATER_WHEEL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 98U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NON_ACTION_WATER_WHEEL, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 105U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_WATER_WHEEL, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 82U, fheroes2::Point{ -1, -2 }, MP2::OBJ_NON_ACTION_WATER_WHEEL ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 83U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NON_ACTION_WATER_WHEEL ); object.topLevelParts.back().animationFrames = 6; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 90U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_WATER_WHEEL ); object.topLevelParts.back().animationFrames = 6; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 97U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_WATER_WHEEL ); objects.emplace_back( std::move( object ) ); } // Witch's Hut, swamp terrain. { Maps::ObjectInfo object{ MP2::OBJ_WITCHS_HUT }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 22U, fheroes2::Point{ 0, 0 }, MP2::OBJ_WITCHS_HUT, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 14U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 21U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 0U, fheroes2::Point{ 0, -2 }, MP2::OBJ_NON_ACTION_WITCHS_HUT ); object.topLevelParts.back().animationFrames = 6; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNSWMP, 7U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_WITCHS_HUT ); object.topLevelParts.back().animationFrames = 6; objects.emplace_back( std::move( object ) ); } // Daemon Cave, lava terrain. { Maps::ObjectInfo object{ MP2::OBJ_DAEMON_CAVE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 115U, fheroes2::Point{ 0, 0 }, MP2::OBJ_DAEMON_CAVE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 114U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_DAEMON_CAVE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 113U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 111U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_DAEMON_CAVE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNLAVA, 112U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_DAEMON_CAVE ); objects.emplace_back( std::move( object ) ); } // Daemon Cave, desert terrain. { Maps::ObjectInfo object{ MP2::OBJ_DAEMON_CAVE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 117U, fheroes2::Point{ 0, 0 }, MP2::OBJ_DAEMON_CAVE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 116U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_DAEMON_CAVE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 115U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 113U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_DAEMON_CAVE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 114U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_DAEMON_CAVE ); objects.emplace_back( std::move( object ) ); } // Pyramid, desert terrain. { Maps::ObjectInfo object{ MP2::OBJ_PYRAMID }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 82U, fheroes2::Point{ 0, 0 }, MP2::OBJ_PYRAMID, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 81U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_PYRAMID, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 80U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 78U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_PYRAMID ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 79U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_PYRAMID ); objects.emplace_back( std::move( object ) ); } // Skeleton, desert terrain. { Maps::ObjectInfo object{ MP2::OBJ_SKELETON }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 84U, fheroes2::Point{ 0, 0 }, MP2::OBJ_SKELETON, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 83U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_SKELETON, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Sphinx, desert terrain. { Maps::ObjectInfo object{ MP2::OBJ_SPHINX }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 87U, fheroes2::Point{ 0, 0 }, MP2::OBJ_SPHINX, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 88U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NON_ACTION_SPHINX, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 86U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNDSRT, 85U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_SPHINX ); objects.emplace_back( std::move( object ) ); } // Trading Post, wasteland terrain. { Maps::ObjectInfo object{ MP2::OBJ_TRADING_POST }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 213U, fheroes2::Point{ 0, 0 }, MP2::OBJ_TRADING_POST, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 202U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_TRADING_POST, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 10; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 201U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNCRCK, 190U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_TRADING_POST ); object.topLevelParts.back().animationFrames = 10; objects.emplace_back( std::move( object ) ); } // Trading Post, generic terrain. { Maps::ObjectInfo object{ MP2::OBJ_TRADING_POST }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 111U, fheroes2::Point{ 0, 0 }, MP2::OBJ_TRADING_POST, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 104U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_TRADING_POST, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 97U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMULT, 90U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_TRADING_POST ); object.topLevelParts.back().animationFrames = 6; objects.emplace_back( std::move( object ) ); } // Lighthouse, generic terrain. { Maps::ObjectInfo object{ MP2::OBJ_LIGHTHOUSE }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 73U, fheroes2::Point{ 0, 0 }, MP2::OBJ_LIGHTHOUSE, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 60U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 71U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 72U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 59U, fheroes2::Point{ 0, -2 }, MP2::OBJ_NON_ACTION_LIGHTHOUSE ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 61U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_LIGHTHOUSE ); object.topLevelParts.back().animationFrames = 9; objects.emplace_back( std::move( object ) ); } // Stone Liths, generic terrain. { Maps::ObjectInfo object{ MP2::OBJ_STONE_LITHS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 116U, fheroes2::Point{ 0, 0 }, MP2::OBJ_STONE_LITHS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 115U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Stone Liths, generic terrain. 2 variants. for ( uint32_t count = 0; count < 2; ++count ) { const uint32_t offset = 117U + count * 3U; Maps::ObjectInfo object{ MP2::OBJ_STONE_LITHS }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, offset + 2U, fheroes2::Point{ 0, 0 }, MP2::OBJ_STONE_LITHS, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, offset + 1U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, offset + 0U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_STONE_LITHS ); objects.emplace_back( std::move( object ) ); } // Event, generic terrain. { Maps::ObjectInfo object{ MP2::OBJ_EVENT }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 163U, fheroes2::Point{ 0, 0 }, MP2::OBJ_EVENT, Maps::OBJECT_LAYER ); objects.emplace_back( std::move( object ) ); } // Freeman's Foundry, generic terrain. { Maps::ObjectInfo object{ MP2::OBJ_FREEMANS_FOUNDRY }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 188U, fheroes2::Point{ 0, 0 }, MP2::OBJ_FREEMANS_FOUNDRY, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 187U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_FREEMANS_FOUNDRY, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 180U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 166U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_FREEMANS_FOUNDRY ); object.topLevelParts.back().animationFrames = 6; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 173U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_FREEMANS_FOUNDRY ); object.topLevelParts.back().animationFrames = 6; objects.emplace_back( std::move( object ) ); } // Magic Garden, generic terrain. { Maps::ObjectInfo object{ MP2::OBJ_MAGIC_GARDEN }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 190U, fheroes2::Point{ 0, 0 }, MP2::OBJ_MAGIC_GARDEN, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 6; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 189U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Observation Tower, generic terrain. { Maps::ObjectInfo object{ MP2::OBJ_OBSERVATION_TOWER }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 201U, fheroes2::Point{ 0, 0 }, MP2::OBJ_OBSERVATION_TOWER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 199U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 200U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_OBJNMUL2, 198U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_OBSERVATION_TOWER ); objects.emplace_back( std::move( object ) ); } // Alchemist's Tower, PoL object. { Maps::ObjectInfo object{ MP2::OBJ_ALCHEMIST_TOWER }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 3U, fheroes2::Point{ 0, 0 }, MP2::OBJ_ALCHEMIST_TOWER, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 1U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 2U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC1, 0U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_ALCHEMIST_TOWER ); objects.emplace_back( std::move( object ) ); } // Stables, PoL object. { Maps::ObjectInfo object{ MP2::OBJ_STABLES }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 4U, fheroes2::Point{ 0, 0 }, MP2::OBJ_STABLES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 3U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NON_ACTION_STABLES, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 2U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 0U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_STABLES ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 1U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_STABLES ); objects.emplace_back( std::move( object ) ); } // Jail, PoL object. { Maps::ObjectInfo object{ MP2::OBJ_JAIL }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 9U, fheroes2::Point{ 0, 0 }, MP2::OBJ_JAIL, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 7U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 8U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 5U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NON_ACTION_JAIL ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC2, 6U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_JAIL ); objects.emplace_back( std::move( object ) ); } // Hut of the Magi, PoL object. { Maps::ObjectInfo object{ MP2::OBJ_HUT_OF_MAGI }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC3, 30U, fheroes2::Point{ 0, 0 }, MP2::OBJ_HUT_OF_MAGI, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC3, 0U, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 8; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC3, 9U, fheroes2::Point{ -1, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC3, 20U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC3, 29U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC3, 31U, fheroes2::Point{ 1, 0 }, MP2::OBJ_NONE, Maps::TERRAIN_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC3, 10U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_HUT_OF_MAGI ); object.topLevelParts.back().animationFrames = 8; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC3, 19U, fheroes2::Point{ 1, -1 }, MP2::OBJ_NON_ACTION_HUT_OF_MAGI ); objects.emplace_back( std::move( object ) ); } // Eye of the Magi, PoL object. { Maps::ObjectInfo object{ MP2::OBJ_EYE_OF_MAGI }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC3, 50U, fheroes2::Point{ 0, 0 }, MP2::OBJ_EYE_OF_MAGI, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 8; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC3, 41U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.back().animationFrames = 8; object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC3, 32U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_EYE_OF_MAGI ); object.topLevelParts.back().animationFrames = 8; objects.emplace_back( std::move( object ) ); } // Barrier, PoL object. 8 variants: Aqua, Blue, Brown, Gold, Green, Orange, Purple, Red for ( uint32_t count = 0; count < 8; ++count ) { const uint32_t offset = 59U + count * 6U; Maps::ObjectInfo object{ MP2::OBJ_BARRIER }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC3, offset + 1U, fheroes2::Point{ 0, 0 }, MP2::OBJ_BARRIER, Maps::OBJECT_LAYER ); object.groundLevelParts.back().animationFrames = 4; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC3, offset + 0U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); objects.emplace_back( std::move( object ) ); } // Traveller's Tent, PoL object. 8 variants: Aqua, Blue, Brown, Gold, Green, Orange, Purple, Red for ( uint32_t count = 0; count < 8; ++count ) { const uint32_t offset = 107U + count * 4U; Maps::ObjectInfo object{ MP2::OBJ_TRAVELLER_TENT }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC3, offset + 3U, fheroes2::Point{ 0, 0 }, MP2::OBJ_TRAVELLER_TENT, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC3, offset + 1U, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC3, offset + 2U, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.topLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_X_LOC3, offset + 0U, fheroes2::Point{ 0, -1 }, MP2::OBJ_NON_ACTION_TRAVELLER_TENT ); objects.emplace_back( std::move( object ) ); } } void populateKingdomHeroes( std::vector & objects ) { assert( objects.empty() ); // Only 7 races and 6 colors exist in the game. for ( int32_t color = 0; color < 6; ++color ) { for ( int32_t race = 0; race < 7; ++race ) { Maps::ObjectInfo object{ MP2::OBJ_HERO }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_MINIHERO, color * 7 + race, fheroes2::Point{ 0, 0 }, MP2::OBJ_HERO, Maps::OBJECT_LAYER ); object.metadata[0] = color; if ( race == 6 ) { // We need to set as a random race, not multi. ++race; } object.metadata[1] = race; objects.emplace_back( std::move( object ) ); } } } void populateKingdomTowns( std::vector & objects ) { assert( objects.empty() ); auto addTown = [&objects]( const MP2::MapObjectType mainObjectType, const uint8_t townIcnOffset, const uint8_t shadowIcnOffset, const MP2::ObjectIcnType townIcnType, const MP2::ObjectIcnType shadowIcnType ) { assert( MP2::isActionObject( mainObjectType ) ); const MP2::MapObjectType secondaryObjectType( static_cast( mainObjectType - MP2::OBJ_ACTION_OBJECT_TYPE ) ); Maps::ObjectInfo object{ mainObjectType }; object.groundLevelParts.emplace_back( townIcnType, townIcnOffset + 13, fheroes2::Point{ 0, 0 }, mainObjectType, Maps::OBJECT_LAYER ); // Castle/town shadow. object.groundLevelParts.emplace_back( shadowIcnType, shadowIcnOffset + 13, fheroes2::Point{ -3, 1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( shadowIcnType, shadowIcnOffset + 14, fheroes2::Point{ -2, 1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( shadowIcnType, shadowIcnOffset + 15, fheroes2::Point{ -1, 1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( shadowIcnType, shadowIcnOffset + 9, fheroes2::Point{ -4, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( shadowIcnType, shadowIcnOffset + 10, fheroes2::Point{ -3, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( shadowIcnType, shadowIcnOffset + 11, fheroes2::Point{ -2, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( shadowIcnType, shadowIcnOffset + 12, fheroes2::Point{ -1, 0 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( shadowIcnType, shadowIcnOffset + 4, fheroes2::Point{ -5, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( shadowIcnType, shadowIcnOffset + 5, fheroes2::Point{ -4, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( shadowIcnType, shadowIcnOffset + 6, fheroes2::Point{ -3, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( shadowIcnType, shadowIcnOffset + 7, fheroes2::Point{ -2, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( shadowIcnType, shadowIcnOffset + 8, fheroes2::Point{ -1, -1 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( shadowIcnType, shadowIcnOffset + 0, fheroes2::Point{ -4, -2 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( shadowIcnType, shadowIcnOffset + 1, fheroes2::Point{ -3, -2 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( shadowIcnType, shadowIcnOffset + 2, fheroes2::Point{ -2, -2 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); object.groundLevelParts.emplace_back( shadowIcnType, shadowIcnOffset + 3, fheroes2::Point{ -1, -2 }, MP2::OBJ_NONE, Maps::SHADOW_LAYER ); // Castle/town main sprite. object.groundLevelParts.emplace_back( townIcnType, townIcnOffset + 11, fheroes2::Point{ -2, 0 }, secondaryObjectType, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( townIcnType, townIcnOffset + 12, fheroes2::Point{ -1, 0 }, secondaryObjectType, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( townIcnType, townIcnOffset + 14, fheroes2::Point{ 1, 0 }, secondaryObjectType, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( townIcnType, townIcnOffset + 15, fheroes2::Point{ 2, 0 }, secondaryObjectType, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( townIcnType, townIcnOffset + 7, fheroes2::Point{ -1, -1 }, secondaryObjectType, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( townIcnType, townIcnOffset + 8, fheroes2::Point{ 0, -1 }, secondaryObjectType, Maps::OBJECT_LAYER ); object.groundLevelParts.emplace_back( townIcnType, townIcnOffset + 9, fheroes2::Point{ 1, -1 }, secondaryObjectType, Maps::OBJECT_LAYER ); object.topLevelParts.emplace_back( townIcnType, townIcnOffset + 6, fheroes2::Point{ -2, -1 }, secondaryObjectType ); object.topLevelParts.emplace_back( townIcnType, townIcnOffset + 10, fheroes2::Point{ 2, -1 }, secondaryObjectType ); object.topLevelParts.emplace_back( townIcnType, townIcnOffset + 1, fheroes2::Point{ -2, -2 }, secondaryObjectType ); object.topLevelParts.emplace_back( townIcnType, townIcnOffset + 2, fheroes2::Point{ -1, -2 }, secondaryObjectType ); object.topLevelParts.emplace_back( townIcnType, townIcnOffset + 3, fheroes2::Point{ 0, -2 }, secondaryObjectType ); object.topLevelParts.emplace_back( townIcnType, townIcnOffset + 4, fheroes2::Point{ 1, -2 }, secondaryObjectType ); object.topLevelParts.emplace_back( townIcnType, townIcnOffset + 5, fheroes2::Point{ 2, -2 }, secondaryObjectType ); object.topLevelParts.emplace_back( townIcnType, townIcnOffset + 0, fheroes2::Point{ 0, -3 }, secondaryObjectType ); objects.emplace_back( std::move( object ) ); }; // Castle and town sprites: Knight, Barbarian, Sorceress, Warlock, Wizard, Necromancer. // First goes castle, then town, then the next race. for ( uint8_t race = 0; race < 6 * 2; ++race ) { const uint8_t icnOffset = race * 16; addTown( MP2::OBJ_CASTLE, icnOffset, icnOffset, MP2::OBJ_ICN_TYPE_OBJNTOWN, MP2::OBJ_ICN_TYPE_OBJNTWSH ); } // Random castle/town for ( uint8_t i = 0; i < 2; ++i ) { const uint8_t icnOffset = i * 16; const MP2::MapObjectType mainObjectType = ( i == 0 ) ? MP2::OBJ_RANDOM_CASTLE : MP2::OBJ_RANDOM_TOWN; addTown( mainObjectType, icnOffset, icnOffset + 32, MP2::OBJ_ICN_TYPE_OBJNTWRD, MP2::OBJ_ICN_TYPE_OBJNTWRD ); } } void populateMonsters( std::vector & objects ) { assert( objects.empty() ); // Monsters are "unique" objects in terms of their ICN resources. // The Editor uses ICN::MON32 while the Adventure Map renderer uses modified ICN::MINIMON resources. for ( int32_t monsterId = Monster::PEASANT; monsterId <= Monster::WATER_ELEMENT; ++monsterId ) { Maps::ObjectInfo object{ MP2::OBJ_MONSTER }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_MONS32, monsterId - 1, fheroes2::Point{ 0, 0 }, MP2::OBJ_MONSTER, Maps::OBJECT_LAYER ); object.metadata[0] = monsterId; objects.emplace_back( std::move( object ) ); } // Random monsters. { Maps::ObjectInfo object{ MP2::OBJ_RANDOM_MONSTER }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_MONS32, Monster::RANDOM_MONSTER - 1, fheroes2::Point{ 0, 0 }, MP2::OBJ_RANDOM_MONSTER, Maps::OBJECT_LAYER ); object.metadata[0] = Monster::RANDOM_MONSTER; objects.emplace_back( std::move( object ) ); } { Maps::ObjectInfo object{ MP2::OBJ_RANDOM_MONSTER_WEAK }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_MONS32, Monster::RANDOM_MONSTER_LEVEL_1 - 1, fheroes2::Point{ 0, 0 }, MP2::OBJ_RANDOM_MONSTER_WEAK, Maps::OBJECT_LAYER ); object.metadata[0] = Monster::RANDOM_MONSTER_LEVEL_1; objects.emplace_back( std::move( object ) ); } { Maps::ObjectInfo object{ MP2::OBJ_RANDOM_MONSTER_MEDIUM }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_MONS32, Monster::RANDOM_MONSTER_LEVEL_2 - 1, fheroes2::Point{ 0, 0 }, MP2::OBJ_RANDOM_MONSTER_MEDIUM, Maps::OBJECT_LAYER ); object.metadata[0] = Monster::RANDOM_MONSTER_LEVEL_2; objects.emplace_back( std::move( object ) ); } { Maps::ObjectInfo object{ MP2::OBJ_RANDOM_MONSTER_STRONG }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_MONS32, Monster::RANDOM_MONSTER_LEVEL_3 - 1, fheroes2::Point{ 0, 0 }, MP2::OBJ_RANDOM_MONSTER_STRONG, Maps::OBJECT_LAYER ); object.metadata[0] = Monster::RANDOM_MONSTER_LEVEL_3; objects.emplace_back( std::move( object ) ); } { Maps::ObjectInfo object{ MP2::OBJ_RANDOM_MONSTER_VERY_STRONG }; object.groundLevelParts.emplace_back( MP2::OBJ_ICN_TYPE_MONS32, Monster::RANDOM_MONSTER_LEVEL_4 - 1, fheroes2::Point{ 0, 0 }, MP2::OBJ_RANDOM_MONSTER_VERY_STRONG, Maps::OBJECT_LAYER ); object.metadata[0] = Monster::RANDOM_MONSTER_LEVEL_4; objects.emplace_back( std::move( object ) ); } } void populateObjectData() { static bool isPopulated = false; if ( isPopulated ) { // The object container has been populated. No need to do anything else. return; } // IMPORTANT!!! // The order of objects must be preserved. If you want to add a new object, add it to the end of the corresponding container. populateRoads( objectData[static_cast( Maps::ObjectGroup::ROADS )] ); populateStreams( objectData[static_cast( Maps::ObjectGroup::STREAMS )] ); populateLandscapeMountains( objectData[static_cast( Maps::ObjectGroup::LANDSCAPE_MOUNTAINS )] ); populateLandscapeRocks( objectData[static_cast( Maps::ObjectGroup::LANDSCAPE_ROCKS )] ); populateLandscapeTrees( objectData[static_cast( Maps::ObjectGroup::LANDSCAPE_TREES )] ); populateLandscapeWater( objectData[static_cast( Maps::ObjectGroup::LANDSCAPE_WATER )] ); populateLandscapeMiscellaneous( objectData[static_cast( Maps::ObjectGroup::LANDSCAPE_MISCELLANEOUS )] ); // These are the extra objects used with the others and never used alone. populateLandscapeTownBasements( objectData[static_cast( Maps::ObjectGroup::LANDSCAPE_TOWN_BASEMENTS )] ); populateLandscapeFlags( objectData[static_cast( Maps::ObjectGroup::LANDSCAPE_FLAGS )] ); populateAdventureArtifacts( objectData[static_cast( Maps::ObjectGroup::ADVENTURE_ARTIFACTS )] ); populateAdventureDwellings( objectData[static_cast( Maps::ObjectGroup::ADVENTURE_DWELLINGS )] ); populateAdventureMines( objectData[static_cast( Maps::ObjectGroup::ADVENTURE_MINES )] ); populateAdventurePowerUps( objectData[static_cast( Maps::ObjectGroup::ADVENTURE_POWER_UPS )] ); populateAdventureTreasures( objectData[static_cast( Maps::ObjectGroup::ADVENTURE_TREASURES )] ); populateAdventureWater( objectData[static_cast( Maps::ObjectGroup::ADVENTURE_WATER )] ); populateAdventureMiscellaneous( objectData[static_cast( Maps::ObjectGroup::ADVENTURE_MISCELLANEOUS )] ); populateKingdomHeroes( objectData[static_cast( Maps::ObjectGroup::KINGDOM_HEROES )] ); populateKingdomTowns( objectData[static_cast( Maps::ObjectGroup::KINGDOM_TOWNS )] ); populateMonsters( objectData[static_cast( Maps::ObjectGroup::MONSTERS )] ); #if defined( WITH_DEBUG ) // It is important to check that all data is accurately generated. for ( const auto & objects : objectData ) { for ( const auto & objectInfo : objects ) { // An object must have at least one ground level part. assert( !objectInfo.groundLevelParts.empty() ); // And this part must have the same type as the object. assert( objectInfo.groundLevelParts.front().objectType == objectInfo.objectType ); const MP2::MapObjectType type = objectInfo.objectType; MP2::MapObjectType secondaryType = type; if ( ( secondaryType & MP2::OBJ_ACTION_OBJECT_TYPE ) == MP2::OBJ_ACTION_OBJECT_TYPE ) { secondaryType = static_cast( secondaryType & ~MP2::OBJ_ACTION_OBJECT_TYPE ); } for ( const auto & info : objectInfo.groundLevelParts ) { if ( info.layerType == Maps::SHADOW_LAYER || info.layerType == Maps::TERRAIN_LAYER ) { // Shadow layer never has an object on it. assert( info.objectType == MP2::OBJ_NONE ); } else { // All parts should belong to the object. assert( info.objectType == type || info.objectType == secondaryType ); } } } } // Check that all landscape objects are non-action objects. for ( size_t groupType = static_cast( Maps::ObjectGroup::ROADS ); groupType <= static_cast( Maps::ObjectGroup::LANDSCAPE_FLAGS ); ++groupType ) { const auto & objects = objectData[groupType]; for ( const auto & objectInfo : objects ) { assert( !MP2::isActionObject( objectInfo.objectType ) ); } } // Check that all other objects are action objects. for ( size_t groupType = static_cast( Maps::ObjectGroup::ADVENTURE_ARTIFACTS ); groupType < static_cast( Maps::ObjectGroup::GROUP_COUNT ); ++groupType ) { const auto & objects = objectData[groupType]; for ( const auto & objectInfo : objects ) { assert( MP2::isActionObject( objectInfo.objectType ) || objectInfo.objectType == MP2::OBJ_EVENT ); } } #endif isPopulated = true; } } namespace Maps { const std::vector & getObjectsByGroup( const ObjectGroup group ) { assert( group != ObjectGroup::GROUP_COUNT ); populateObjectData(); return objectData[static_cast( group )]; } MP2::MapObjectType getObjectTypeByIcn( const MP2::ObjectIcnType icnType, const uint32_t icnIndex ) { populateObjectData(); for ( const auto & group : objectData ) { for ( const auto & object : group ) { assert( !object.groundLevelParts.empty() ); const auto & info = object.groundLevelParts.front(); if ( info.icnType == icnType && info.icnIndex == icnIndex ) { return info.objectType; } } } return MP2::OBJ_NONE; } std::vector getGroundLevelOccupiedTileOffset( const ObjectInfo & info ) { // If this assertion blows up then the object is not formed properly. assert( !info.groundLevelParts.empty() ); std::vector offsets; for ( const auto & objectPart : info.groundLevelParts ) { if ( objectPart.layerType == OBJECT_LAYER || objectPart.layerType == BACKGROUND_LAYER ) { offsets.push_back( objectPart.tileOffset ); } } return offsets; } std::vector getGroundLevelUsedTileOffset( const ObjectInfo & info ) { // If this assertion blows up then the object is not formed properly. assert( !info.groundLevelParts.empty() ); std::vector offsets; for ( const auto & objectPart : info.groundLevelParts ) { if ( objectPart.layerType != SHADOW_LAYER ) { offsets.push_back( objectPart.tileOffset ); } } return offsets; } } fheroes2-1.0.12+dfsg/src/fheroes2/maps/map_object_info.h000066400000000000000000000156431456075706000230050ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include #include "maps_tiles.h" #include "math_base.h" #include "mp2.h" namespace Maps { // An object usually contains of multiple parts / tiles. Each part has its own features like object layer type or image index. // An object always contains a main object part (addon). // All object's parts shares images from the same ICN source (MP2::ObjectIcnType). struct ObjectPartInfo { ObjectPartInfo( const MP2::ObjectIcnType icn, const uint32_t index, const fheroes2::Point offset, const MP2::MapObjectType type ) : tileOffset( offset ) , icnIndex( index ) , icnType( icn ) , objectType( type ) { // Do nothing. } // A tile offset from the main object tile. fheroes2::Point tileOffset; // Image index from an ICN resource to render this object part. uint32_t icnIndex{ 0 }; // ICN type associated to this object part. Some objects like towns can have parts coming from different ICN resources. MP2::ObjectIcnType icnType{ MP2::OBJ_ICN_TYPE_UNKNOWN }; // Object type associated with this object part. Not every object part has a type. For example, shadows don't have types. MP2::MapObjectType objectType{ MP2::OBJ_NONE }; // The number of following by index images is used to animate this object part. // In most cases this value is 0 as the majority of object parts do not have animations. uint8_t animationFrames{ 0 }; }; struct LayeredObjectPartInfo : public ObjectPartInfo { LayeredObjectPartInfo( const MP2::ObjectIcnType icn, const uint32_t index, const fheroes2::Point offset, const MP2::MapObjectType type, const ObjectLayerType layer ) : ObjectPartInfo( icn, index, offset, type ) , layerType( layer ) { // Do nothing. } // A layer where this object part / addon sits on. // The layer is used for passability calculations as well as an order of rendering objects. ObjectLayerType layerType{ OBJECT_LAYER }; }; struct ObjectInfo { explicit ObjectInfo( const MP2::MapObjectType type ) : objectType( type ) { // Do nothing. } // A collection of object parts. // IMPORTANT!!! // - must not be empty // - the main object part must come first // IMPORTANT!!! std::vector groundLevelParts; // Top level parts. Can be empty and should never contain objects with Y value more or equal 0. // It does not matter what layer is set here as it is going to be ignored. For consistency put OBJECT_LAYER to detect any bad logic. std::vector topLevelParts; // Object type. Some objects don't have it like cracks. MP2::MapObjectType objectType{ MP2::OBJ_NONE }; // Some action objects require additional information to be stored. // Information stored in this member should be interpret based on a group. std::array metadata{ 0 }; bool empty() const { return groundLevelParts.empty(); } }; // Some objects use the same ICN resources and belong to the same object type // but the majority of them have different object types while using the same ICN // or use multiple ICN resource among the same object type. // Plus the fheroes2 Editor requires object type and index in order to save it, // therefore, such enumeration exist. // // These groups do not correlate with the original Editor. // // !!! IMPORTANT !!! // Do NOT change the order of the items as they are used for the map format. enum class ObjectGroup : int32_t { // These groups are not being used by the Editor directly but they are still a part of a tile. ROADS, STREAMS, // Landscape objects. LANDSCAPE_MOUNTAINS, LANDSCAPE_ROCKS, LANDSCAPE_TREES, LANDSCAPE_WATER, LANDSCAPE_MISCELLANEOUS, // Extra landscape objects placed together with other objects. LANDSCAPE_TOWN_BASEMENTS, LANDSCAPE_FLAGS, // Adventure objects. ADVENTURE_ARTIFACTS, ADVENTURE_DWELLINGS, ADVENTURE_MINES, ADVENTURE_POWER_UPS, ADVENTURE_TREASURES, ADVENTURE_WATER, ADVENTURE_MISCELLANEOUS, // Kingdom objects. KINGDOM_HEROES, KINGDOM_TOWNS, // Monsters. MONSTERS, // IMPORTANT!!! // Put all new entries just above this entry. GROUP_COUNT }; const std::vector & getObjectsByGroup( const ObjectGroup group ); MP2::MapObjectType getObjectTypeByIcn( const MP2::ObjectIcnType icnType, const uint32_t icnIndex ); // The function returns tile offsets only for ground level objects located on OBJECT_LAYER and BACKGROUND_LAYER layers. // Objects on other layers do not affect passabilities of tiles so they do not 'occupy' these tiles. std::vector getGroundLevelOccupiedTileOffset( const ObjectInfo & info ); // The function returns tile offsets only for ground level objects located on OBJECT_LAYER, BACKGROUND_LAYER and TERRAIN_LAYER layers. // SHADOW_LAYER is excluded as it does not "use" any tile. std::vector getGroundLevelUsedTileOffset( const ObjectInfo & info ); } fheroes2-1.0.12+dfsg/src/fheroes2/maps/maps.cpp000066400000000000000000000625641456075706000211660ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "maps.h" #include #include #include #include #include #include "ai.h" #include "difficulty.h" #include "direction.h" #include "game.h" #include "heroes.h" #include "kingdom.h" #include "logging.h" #include "maps_tiles.h" #include "maps_tiles_helper.h" #include "players.h" #include "race.h" #include "resource.h" #include "translations.h" #include "world.h" namespace { Maps::Indexes MapsIndexesFilteredObject( const Maps::Indexes & indexes, const MP2::MapObjectType objectType, const bool ignoreHeroes = true ) { Maps::Indexes result; for ( size_t idx = 0; idx < indexes.size(); ++idx ) { if ( world.GetTiles( indexes[idx] ).GetObject( !ignoreHeroes ) == objectType ) { result.push_back( indexes[idx] ); } } return result; } Maps::Indexes MapsIndexesObject( const MP2::MapObjectType objectType, const bool ignoreHeroes ) { Maps::Indexes result; const int32_t size = static_cast( world.getSize() ); for ( int32_t idx = 0; idx < size; ++idx ) { if ( world.GetTiles( idx ).GetObject( !ignoreHeroes ) == objectType ) { result.push_back( idx ); } } return result; } } struct ComparisonDistance { explicit ComparisonDistance( const int32_t index ) : centerPoint( Maps::GetPoint( index ) ) {} ComparisonDistance() = delete; bool operator()( const int32_t index1, const int32_t index2 ) const { const fheroes2::Point point1( Maps::GetPoint( index1 ) ); const fheroes2::Point point2( Maps::GetPoint( index2 ) ); const int32_t diffX1 = std::abs( centerPoint.x - point1.x ); const int32_t diffY1 = std::abs( centerPoint.y - point1.y ); const int32_t diffX2 = std::abs( centerPoint.x - point2.x ); const int32_t diffY2 = std::abs( centerPoint.y - point2.y ); return ( diffX1 * diffX1 + diffY1 * diffY1 ) < ( diffX2 * diffX2 + diffY2 * diffY2 ); } const fheroes2::Point centerPoint; }; const char * Maps::SizeString( int s ) { switch ( s ) { case SMALL: return _( "maps|Small" ); case MEDIUM: return _( "maps|Medium" ); case LARGE: return _( "maps|Large" ); case XLARGE: return _( "maps|Extra Large" ); default: break; } return _( "maps|Custom Size" ); } const char * Maps::GetMineName( const int resourceType ) { switch ( resourceType ) { case Resource::ORE: return _( "Ore Mine" ); case Resource::SULFUR: return _( "Sulfur Mine" ); case Resource::CRYSTAL: return _( "Crystal Mine" ); case Resource::GEMS: return _( "Gems Mine" ); case Resource::GOLD: return _( "Gold Mine" ); default: break; } return _( "Mine" ); } int Maps::GetDirection( int from, int to ) { if ( from == to ) return Direction::CENTER; const int diff = to - from; const int width = world.w(); if ( diff == ( -width - 1 ) ) { return Direction::TOP_LEFT; } else if ( diff == -width ) { return Direction::TOP; } else if ( diff == ( -width + 1 ) ) { return Direction::TOP_RIGHT; } else if ( diff == -1 ) { return Direction::LEFT; } else if ( diff == 1 ) { return Direction::RIGHT; } else if ( diff == width - 1 ) { return Direction::BOTTOM_LEFT; } else if ( diff == width ) { return Direction::BOTTOM; } else if ( diff == width + 1 ) { return Direction::BOTTOM_RIGHT; } return Direction::UNKNOWN; } int32_t Maps::GetDirectionIndex( const int32_t from, const int direction ) { switch ( direction ) { case Direction::TOP: return from - world.w(); case Direction::TOP_RIGHT: return from - world.w() + 1; case Direction::RIGHT: return from + 1; case Direction::BOTTOM_RIGHT: return from + world.w() + 1; case Direction::BOTTOM: return from + world.w(); case Direction::BOTTOM_LEFT: return from + world.w() - 1; case Direction::LEFT: return from - 1; case Direction::TOP_LEFT: return from - world.w() - 1; default: break; } return -1; } fheroes2::Point Maps::getDirectionPoint( const fheroes2::Point & from, const int direction ) { switch ( direction ) { case Direction::TOP: return { from.x, from.y - 1 }; case Direction::TOP_RIGHT: return { from.x + 1, from.y - 1 }; case Direction::RIGHT: return { from.x + 1, from.y }; case Direction::BOTTOM_RIGHT: return { from.x + 1, from.y + 1 }; case Direction::BOTTOM: return { from.x, from.y + 1 }; case Direction::BOTTOM_LEFT: return { from.x - 1, from.y + 1 }; case Direction::LEFT: return { from.x - 1, from.y }; case Direction::TOP_LEFT: return { from.x - 1, from.y - 1 }; case Direction::CENTER: return from; default: // This is not a valid direction. assert( 0 ); break; } // This is equal to index = -1. return { -1, 0 }; } bool Maps::isValidDirection( int32_t from, int vector ) { const int32_t width = world.w(); switch ( vector ) { case Direction::TOP: return ( from >= width ); case Direction::RIGHT: return ( ( from % width ) < ( width - 1 ) ); case Direction::BOTTOM: return ( from < width * ( world.h() - 1 ) ); case Direction::LEFT: return ( from % width ) != 0; case Direction::TOP_RIGHT: return ( from >= width ) && ( ( from % width ) < ( width - 1 ) ); case Direction::BOTTOM_RIGHT: return ( from < width * ( world.h() - 1 ) ) && ( ( from % width ) < ( width - 1 ) ); case Direction::BOTTOM_LEFT: return ( from < width * ( world.h() - 1 ) ) && ( from % width ); case Direction::TOP_LEFT: return ( from >= width ) && ( from % width ); default: break; } return false; } fheroes2::Point Maps::GetPoint( const int32_t index ) { return fheroes2::Point( index % world.w(), index / world.w() ); } bool Maps::isValidAbsIndex( const int32_t index ) { return 0 <= index && index < world.w() * world.h(); } bool Maps::isValidAbsPoint( const int32_t x, const int32_t y ) { return 0 <= x && world.w() > x && 0 <= y && world.h() > y; } int32_t Maps::GetIndexFromAbsPoint( const fheroes2::Point & mp ) { if ( mp.x < 0 || mp.y < 0 ) { return -1; } return mp.y * world.w() + mp.x; } int32_t Maps::GetIndexFromAbsPoint( const int32_t x, const int32_t y ) { if ( x < 0 || y < 0 ) { return -1; } return y * world.w() + x; } Maps::Indexes Maps::getAroundIndexes( const int32_t tileIndex, const int32_t maxDistanceFromTile /* = 1 */ ) { Indexes results; if ( !isValidAbsIndex( tileIndex ) || maxDistanceFromTile <= 0 ) { return results; } const size_t areaSideSize = maxDistanceFromTile * 2 + 1; results.reserve( areaSideSize * areaSideSize - 1 ); const int32_t worldWidth = world.w(); const int32_t worldHeight = world.h(); assert( worldWidth > 0 && worldHeight > 0 ); const int32_t centerX = tileIndex % worldWidth; const int32_t centerY = tileIndex / worldWidth; // We avoid getting out of map boundaries. const int32_t minTileX = std::max( centerX - maxDistanceFromTile, 0 ); const int32_t minTileY = std::max( centerY - maxDistanceFromTile, 0 ); const int32_t maxTileX = std::min( centerX + maxDistanceFromTile + 1, worldWidth ); const int32_t maxTileY = std::min( centerY + maxDistanceFromTile + 1, worldHeight ); for ( int32_t tileY = minTileY; tileY < maxTileY; ++tileY ) { const int32_t indexOffsetY = tileY * worldWidth; const bool isCenterY = ( tileY == centerY ); for ( int32_t tileX = minTileX; tileX < maxTileX; ++tileX ) { // Skip the center tile. if ( isCenterY && tileX == centerX ) { continue; } results.push_back( indexOffsetY + tileX ); } } return results; } MapsIndexes Maps::getVisibleMonstersAroundHero( const Heroes & hero ) { const uint32_t dist = hero.GetVisionsDistance(); MapsIndexes monsters = Maps::ScanAroundObjectWithDistance( hero.GetIndex(), dist, MP2::OBJ_MONSTER ); const int32_t heroColor = hero.GetColor(); monsters.erase( std::remove_if( monsters.begin(), monsters.end(), [heroColor]( const int32_t index ) { return world.GetTiles( index ).isFog( heroColor ); } ), monsters.end() ); return monsters; } void Maps::ClearFog( const int32_t tileIndex, int scoutingDistance, const int playerColor ) { if ( scoutingDistance <= 0 || !Maps::isValidAbsIndex( tileIndex ) ) { // Nothing to uncover. return; } const fheroes2::Point center = Maps::GetPoint( tileIndex ); // AI is cheating! const Kingdom & kingdom = world.GetKingdom( playerColor ); const bool isAIPlayer = kingdom.isControlAI(); if ( isAIPlayer ) { scoutingDistance += Difficulty::GetScoutingBonusForAI( Game::getDifficulty() ); } const int alliedColors = Players::GetPlayerFriends( playerColor ); const bool isHumanOrHumanFriend = !isAIPlayer || Players::isFriends( playerColor, Players::HumanColors() ); const int revealRadiusSquared = scoutingDistance * scoutingDistance + 4; // constant factor for "backwards compatibility" const int32_t minY = std::max( center.y - scoutingDistance, 0 ); const int32_t maxY = std::min( center.y + scoutingDistance, world.h() - 1 ); assert( minY < maxY ); const int32_t minX = std::max( center.x - scoutingDistance, 0 ); const int32_t maxX = std::min( center.x + scoutingDistance, world.w() - 1 ); assert( minX < maxX ); fheroes2::Point fogRevealMinPos( world.h(), world.w() ); fheroes2::Point fogRevealMaxPos( 0, 0 ); for ( int32_t y = minY; y <= maxY; ++y ) { const int32_t dy = y - center.y; for ( int32_t x = minX; x <= maxX; ++x ) { const int32_t dx = x - center.x; if ( revealRadiusSquared >= dx * dx + dy * dy ) { Maps::Tiles & tile = world.GetTiles( x, y ); if ( isAIPlayer && tile.isFog( playerColor ) ) { AI::Get().revealFog( tile, kingdom ); } if ( tile.isFog( alliedColors ) ) { // Clear fog only if it is not already cleared. tile.ClearFog( alliedColors ); if ( isHumanOrHumanFriend ) { // Update fog reveal area points only for human player and his allies. fogRevealMinPos.x = std::min( fogRevealMinPos.x, x ); fogRevealMinPos.y = std::min( fogRevealMinPos.y, y ); fogRevealMaxPos.x = std::max( fogRevealMaxPos.x, x ); fogRevealMaxPos.y = std::max( fogRevealMaxPos.y, y ); } } } } } // Update fog directions only for human player and his allies and only if fog has to be cleared. if ( isHumanOrHumanFriend && ( fogRevealMaxPos.x >= fogRevealMinPos.x ) && ( fogRevealMaxPos.y >= fogRevealMinPos.y ) ) { // Fog directions should be updated 1 tile outside of the cleared fog. fogRevealMinPos -= { 1, 1 }; fogRevealMaxPos += { 1, 1 }; Maps::updateFogDirectionsInArea( fogRevealMinPos, fogRevealMaxPos, alliedColors ); } } int32_t Maps::getFogTileCountToBeRevealed( const int32_t tileIndex, int scoutingDistance, const int playerColor ) { if ( scoutingDistance <= 0 || !Maps::isValidAbsIndex( tileIndex ) ) { return 0; } const fheroes2::Point center = Maps::GetPoint( tileIndex ); // AI is cheating! const bool isAIPlayer = world.GetKingdom( playerColor ).isControlAI(); if ( isAIPlayer ) { scoutingDistance += Difficulty::GetScoutingBonusForAI( Game::getDifficulty() ); } const int revealRadiusSquared = scoutingDistance * scoutingDistance + 4; // constant factor for "backwards compatibility" const int32_t minY = std::max( center.y - scoutingDistance, 0 ); const int32_t maxY = std::min( center.y + scoutingDistance, world.h() - 1 ); assert( minY < maxY ); const int32_t minX = std::max( center.x - scoutingDistance, 0 ); const int32_t maxX = std::min( center.x + scoutingDistance, world.w() - 1 ); assert( minX < maxX ); int32_t tileCount = 0; for ( int32_t y = minY; y <= maxY; ++y ) { const int32_t dy = y - center.y; for ( int32_t x = minX; x <= maxX; ++x ) { const int32_t dx = x - center.x; if ( revealRadiusSquared >= dx * dx + dy * dy ) { const Maps::Tiles & tile = world.GetTiles( x, y ); if ( tile.isFog( playerColor ) ) { ++tileCount; } } } } return tileCount; } Maps::Indexes Maps::ScanAroundObject( const int32_t center, const MP2::MapObjectType objectType, const bool ignoreHeroes ) { Indexes results = getAroundIndexes( center ); return MapsIndexesFilteredObject( results, objectType, ignoreHeroes ); } Maps::Indexes Maps::GetFreeIndexesAroundTile( const int32_t center ) { Indexes results = getAroundIndexes( center ); results.erase( std::remove_if( results.begin(), results.end(), []( const int32_t tile ) { return !isClearGround( world.GetTiles( tile ) ); } ), results.end() ); return results; } bool Maps::isValidForDimensionDoor( int32_t targetIndex, bool isWater ) { const Maps::Tiles & tile = world.GetTiles( targetIndex ); return ( tile.GetPassable() & Direction::CENTER ) != 0 && isWater == tile.isWater() && !MP2::isActionObject( tile.GetObject( true ) ); } Maps::Indexes Maps::ScanAroundObject( const int32_t center, const MP2::MapObjectType objectType ) { Indexes results = getAroundIndexes( center ); return MapsIndexesFilteredObject( results, objectType ); } Maps::Indexes Maps::ScanAroundObjectWithDistance( const int32_t center, const uint32_t dist, const MP2::MapObjectType objectType ) { Indexes results = getAroundIndexes( center, dist ); std::sort( results.begin(), results.end(), ComparisonDistance( center ) ); return MapsIndexesFilteredObject( results, objectType ); } bool Maps::doesObjectExistOnMap( const MP2::MapObjectType objectType ) { const int32_t size = static_cast( world.getSize() ); for ( int32_t idx = 0; idx < size; ++idx ) { if ( world.GetTiles( idx ).GetObject( false ) == objectType ) { return true; } } return false; } Maps::Indexes Maps::GetObjectPositions( const MP2::MapObjectType objectType ) { return MapsIndexesObject( objectType, true ); } Maps::Indexes Maps::GetObjectPositions( int32_t center, const MP2::MapObjectType objectType, bool ignoreHeroes ) { Indexes results = MapsIndexesObject( objectType, ignoreHeroes ); std::sort( results.begin(), results.end(), ComparisonDistance( center ) ); return results; } bool Maps::isTileUnderProtection( const int32_t tileIndex ) { return world.GetTiles( tileIndex ).GetObject() == MP2::OBJ_MONSTER ? true : !getMonstersProtectingTile( tileIndex ).empty(); } Maps::Indexes Maps::getMonstersProtectingTile( const int32_t tileIndex, const bool checkObjectOnTile /* = true */ ) { if ( !isValidAbsIndex( tileIndex ) ) { return {}; } Indexes result; const Maps::Tiles & tile = world.GetTiles( tileIndex ); // If a tile contains an object that you can interact with without visiting this tile, then this interaction doesn't trigger a monster attack... if ( checkObjectOnTile && MP2::isNeedStayFront( tile.GetObject() ) ) { // ... unless the tile itself contains a monster if ( tile.GetObject() == MP2::OBJ_MONSTER ) { result.push_back( tileIndex ); } return result; } result.reserve( 9 ); const int width = world.w(); const int x = tileIndex % width; const int y = tileIndex / width; const auto isProtectedBy = [tileIndex, &tile]( const int32_t monsterTileIndex ) { const Maps::Tiles & monsterTile = world.GetTiles( monsterTileIndex ); if ( monsterTile.GetObject() != MP2::OBJ_MONSTER || tile.isWater() != monsterTile.isWater() ) { return false; } const int directionToMonster = Maps::GetDirection( tileIndex, monsterTileIndex ); const int directionFromMonster = Direction::Reflect( directionToMonster ); // The tile is directly accessible to the monster if ( ( tile.GetPassable() & directionToMonster ) && ( monsterTile.GetPassable() & directionFromMonster ) ) { return true; } // The tile is not directly accessible to the monster, but he can still attack in the diagonal direction if, when the hero moves away from the tile // in question in the vertical direction and the monster moves away from his tile in the horizontal direction, they would have to meet if ( directionFromMonster == Direction::TOP_LEFT && ( tile.GetPassable() & Direction::BOTTOM ) && ( monsterTile.GetPassable() & Direction::LEFT ) ) { return true; } if ( directionFromMonster == Direction::TOP_RIGHT && ( tile.GetPassable() & Direction::BOTTOM ) && ( monsterTile.GetPassable() & Direction::RIGHT ) ) { return true; } if ( directionFromMonster == Direction::BOTTOM_RIGHT && ( tile.GetPassable() & Direction::TOP ) && ( monsterTile.GetPassable() & Direction::RIGHT ) ) { return true; } if ( directionFromMonster == Direction::BOTTOM_LEFT && ( tile.GetPassable() & Direction::TOP ) && ( monsterTile.GetPassable() & Direction::LEFT ) ) { return true; } return false; }; const auto validateAndAdd = [&result, &isProtectedBy]( const int monsterTileIndex ) { if ( isProtectedBy( monsterTileIndex ) ) { result.push_back( monsterTileIndex ); } }; if ( y > 0 ) { if ( x > 0 ) { validateAndAdd( tileIndex - width - 1 ); } validateAndAdd( tileIndex - width ); if ( x < width - 1 ) { validateAndAdd( tileIndex - width + 1 ); } } if ( x > 0 ) { validateAndAdd( tileIndex - 1 ); } if ( tile.GetObject() == MP2::OBJ_MONSTER ) { result.push_back( tileIndex ); } if ( x < width - 1 ) { validateAndAdd( tileIndex + 1 ); } if ( y < world.h() - 1 ) { if ( x > 0 ) { validateAndAdd( tileIndex + width - 1 ); } validateAndAdd( tileIndex + width ); if ( x < width - 1 ) { validateAndAdd( tileIndex + width + 1 ); } } return result; } uint32_t Maps::GetApproximateDistance( const int32_t pos1, const int32_t pos2 ) { const fheroes2::Point point1( GetPoint( pos1 ) ); const fheroes2::Point point2( GetPoint( pos2 ) ); const uint32_t diffX = std::abs( point1.x - point2.x ); const uint32_t diffY = std::abs( point1.y - point2.y ); assert( diffX < Maps::XLARGE && diffY < Maps::XLARGE ); return std::max( diffX, diffY ) + std::min( diffX, diffY ) / 2; } uint32_t Maps::GetStraightLineDistance( const int32_t pos1, const int32_t pos2 ) { const fheroes2::Point point1( GetPoint( pos1 ) ); const fheroes2::Point point2( GetPoint( pos2 ) ); const uint32_t diffX = std::abs( point1.x - point2.x ); const uint32_t diffY = std::abs( point1.y - point2.y ); assert( diffX < Maps::XLARGE && diffY < Maps::XLARGE ); return static_cast( std::hypot( diffX, diffY ) ); } void Maps::ReplaceRandomCastleObjectId( const fheroes2::Point & center ) { // Reset castle ID for ( int32_t y = -3; y < 2; ++y ) { for ( int32_t x = -2; x < 3; ++x ) { Maps::Tiles & tile = world.GetTiles( center.x + x, center.y + y ); if ( MP2::OBJ_NON_ACTION_RANDOM_CASTLE == tile.GetObject() || MP2::OBJ_NON_ACTION_RANDOM_TOWN == tile.GetObject() ) { tile.SetObject( MP2::OBJ_NON_ACTION_CASTLE ); } } } // restore center ID world.GetTiles( center.x, center.y ).SetObject( MP2::OBJ_CASTLE ); } void Maps::UpdateCastleSprite( const fheroes2::Point & center, int race, bool isCastle, bool isRandom ) { /* Castle/Town object image consists of 42 tile sprites: 10 base tiles (OBJNTWBA) with 16 shadow tiles on left side (OBJNTWSH) overlaid by 16 town tiles (OBJNTOWN) Shadows (OBJNTWSH) Castle (OBJNTOWN) 0 32 33 34 35 1 2 3 4 5 36 37 38 39 40 6 7 8 9 10 41 42 43 44 11 12 13 14 15 45 46 47 */ // correct only RND town and castle const Maps::Tiles & entranceTile = world.GetTiles( center.x, center.y ); const MP2::MapObjectType objectType = entranceTile.GetObject(); const uint32_t castleID = entranceTile.GetObjectUID(); if ( isRandom && ( objectType != MP2::OBJ_RANDOM_CASTLE && objectType != MP2::OBJ_RANDOM_TOWN ) ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "incorrect object" << ", index: " << GetIndexFromAbsPoint( center.x, center.y ) ) return; } uint8_t raceIndex = 0; // Race::KNIGHT switch ( race ) { case Race::BARB: raceIndex = 1; break; case Race::SORC: raceIndex = 2; break; case Race::WRLK: raceIndex = 3; break; case Race::WZRD: raceIndex = 4; break; case Race::NECR: raceIndex = 5; break; default: break; } const int castleCoordinates[16][2] = { { 0, -3 }, { -2, -2 }, { -1, -2 }, { 0, -2 }, { 1, -2 }, { 2, -2 }, { -2, -1 }, { -1, -1 }, { 0, -1 }, { 1, -1 }, { 2, -1 }, { -2, 0 }, { -1, 0 }, { 0, 0 }, { 1, 0 }, { 2, 0 } }; const int shadowCoordinates[16][2] = { { -4, -2 }, { -3, -2 }, { -2, -2 }, { -1, -2 }, { -5, -1 }, { -4, -1 }, { -3, -1 }, { -2, -1 }, { -1, -1 }, { -4, 0 }, { -3, 0 }, { -2, 0 }, { -1, 0 }, { -3, 1 }, { -2, 1 }, { -1, 1 } }; for ( uint8_t index = 0; index < 16; ++index ) { const uint8_t fullTownIndex = index + ( isCastle ? 0 : 16 ) + raceIndex * 32; const uint8_t lookupID = isRandom ? index + ( isCastle ? 0 : 16 ) : fullTownIndex; const int castleTile = GetIndexFromAbsPoint( center.x + castleCoordinates[index][0], center.y + castleCoordinates[index][1] ); if ( isValidAbsIndex( castleTile ) ) { Tiles & tile = world.GetTiles( castleTile ); if ( isRandom ) tile.replaceObject( castleID, MP2::OBJ_ICN_TYPE_OBJNTWRD, MP2::OBJ_ICN_TYPE_OBJNTOWN, lookupID, fullTownIndex ); // OBJNTWRD to OBJNTOWN else tile.updateObjectImageIndex( castleID, MP2::OBJ_ICN_TYPE_OBJNTOWN, -16 ); if ( index == 0 ) { TilesAddon * addon = tile.getTopLayerAddon( castleID ); if ( addon && addon->_objectIcnType == MP2::OBJ_ICN_TYPE_OBJNTWRD ) { addon->_objectIcnType = MP2::OBJ_ICN_TYPE_OBJNTOWN; addon->_imageIndex = fullTownIndex - 16; } } } const int shadowTileId = GetIndexFromAbsPoint( center.x + shadowCoordinates[index][0], center.y + shadowCoordinates[index][1] ); if ( isValidAbsIndex( shadowTileId ) ) { Maps::Tiles & shadowTile = world.GetTiles( shadowTileId ); if ( isRandom ) shadowTile.replaceObject( castleID, MP2::OBJ_ICN_TYPE_OBJNTWRD, MP2::OBJ_ICN_TYPE_OBJNTWSH, lookupID + 32, fullTownIndex ); else shadowTile.updateObjectImageIndex( castleID, MP2::OBJ_ICN_TYPE_OBJNTWSH, -16 ); } } } fheroes2-1.0.12+dfsg/src/fheroes2/maps/maps.h000066400000000000000000000147331456075706000206260ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2MAPS_H #define H2MAPS_H #include #include #include #include "math_base.h" #include "mp2.h" #define TILEWIDTH 32 class Heroes; using MapsIndexes = std::vector; namespace Maps { enum mapsize_t : int { ZERO = 0, SMALL = 36, MEDIUM = 72, LARGE = 108, XLARGE = 144 }; using Indexes = MapsIndexes; const char * SizeString( int size ); const char * GetMineName( const int resourceType ); int GetDirection( int from, int to ); int32_t GetDirectionIndex( const int32_t from, const int direction ); // Returns the nearest point on the map to the current tile, located in the given direction vector. fheroes2::Point getDirectionPoint( const fheroes2::Point & from, const int direction ); bool isValidDirection( int32_t from, int vector ); bool isValidAbsIndex( const int32_t index ); bool isValidAbsPoint( const int32_t x, const int32_t y ); fheroes2::Point GetPoint( const int32_t index ); // Convert maps point to index maps. Returns -1 if x or y is negative. int32_t GetIndexFromAbsPoint( const fheroes2::Point & mp ); int32_t GetIndexFromAbsPoint( const int32_t x, const int32_t y ); Indexes getAroundIndexes( const int32_t tileIndex, const int32_t maxDistanceFromTile = 1 ); MapsIndexes getVisibleMonstersAroundHero( const Heroes & hero ); Indexes ScanAroundObject( const int32_t center, const MP2::MapObjectType objectType ); Indexes ScanAroundObjectWithDistance( const int32_t center, const uint32_t dist, const MP2::MapObjectType objectType ); Indexes ScanAroundObject( const int32_t center, const MP2::MapObjectType objectType, const bool ignoreHeroes ); Indexes GetFreeIndexesAroundTile( const int32_t center ); bool isValidForDimensionDoor( int32_t targetIndex, bool isWater ); // Checks if the tile is guarded by a monster bool isTileUnderProtection( const int32_t tileIndex ); // Returns a list of indexes of tiles with monsters guarding the specified tile. If the 'checkObjectOnTile' parameter // is set to true, then an additional check is performed to see if it is possible to interact with an object on this // tile without triggering a monster attack. Indexes getMonstersProtectingTile( const int32_t tileIndex, const bool checkObjectOnTile = true ); // This function always ignores heroes. bool doesObjectExistOnMap( const MP2::MapObjectType objectType ); // This function always ignores heroes. Indexes GetObjectPositions( const MP2::MapObjectType objectType ); Indexes GetObjectPositions( int32_t center, const MP2::MapObjectType objectType, bool ignoreHeroes ); void ClearFog( const int32_t tileIndex, int scoutingDistance, const int playerColor ); int32_t getFogTileCountToBeRevealed( const int32_t tileIndex, int scoutingDistance, const int playerColor ); // Returns the approximate distance between two tiles with given indexes. This distance is calculated as the number of // tiles (truncated to the nearest smaller integer value) that would need to be traversed in a straight direction to // overcome the distance that corresponds to the distance that the hero would have to cover when moving between tiles // with given indexes using the usual rules and penalties. // // For example, in order to navigate to a tile B, which is located seven tiles below and five to the right of tile A, // the hero should move two tiles down and five tiles right down diagonally (diagonal movement costs 50% more), which // corresponds to 2 + 5 * 1.5 = 9.5 (9 after truncation) tiles traversed in the straight direction. // // This function should be avoided unless high precision is not important. uint32_t GetApproximateDistance( const int32_t pos1, const int32_t pos2 ); // Returns the straight line distance between two tiles with given indexes. This distance is calculated as the number // of tiles (truncated to the nearest smaller integer value) that would need to be traversed in a straight direction // to overcome the distance that corresponds to the straight line distance between the tiles with given indexes. // // For example, in order to navigate to a tile B, which is located seven tiles below and five to the right of tile A, // a straight line distance should be covered, which corresponds to sqrt( 5^2 + 7^2 ) = 8.6 (8 after truncation) tiles // traversed in the straight direction. // // This function is not suitable for accurate calculation of the distance expressed in movement points. uint32_t GetStraightLineDistance( const int32_t pos1, const int32_t pos2 ); void UpdateCastleSprite( const fheroes2::Point & center, int race, bool isCastle = false, bool isRandom = false ); void ReplaceRandomCastleObjectId( const fheroes2::Point & ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/maps/maps_fileinfo.cpp000066400000000000000000000533631456075706000230360ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "maps_fileinfo.h" #include #include #include #include #include #include #include #include #include #include #include "color.h" #include "difficulty.h" #include "dir.h" #include "game_over.h" #include "logging.h" #include "map_format_info.h" #include "maps_tiles.h" #include "maps_tiles_helper.h" #include "mp2.h" #include "mp2_helper.h" #include "race.h" #include "serialize.h" #include "settings.h" #include "system.h" #include "tools.h" namespace { const size_t mapNameLength = 16; const size_t mapDescriptionLength = 200; template bool CaseInsensitiveCompare( const std::basic_string & lhs, const std::basic_string & rhs ) { typename std::basic_string::const_iterator li = lhs.begin(); typename std::basic_string::const_iterator ri = rhs.begin(); while ( li != lhs.end() && ri != rhs.end() ) { const CharType lc = std::tolower( *li, std::locale() ); const CharType rc = std::tolower( *ri, std::locale() ); ++li; ++ri; if ( lc < rc ) { return true; } if ( lc > rc ) { return false; } // the chars are "equal", so proceed to check the next pair } // we came to the end of either (or both) strings, left is "smaller" if it was shorter: return li == lhs.end() && ri != rhs.end(); } // This function returns an unsorted array. It is a caller responsibility to take care of sorting if needed. MapsFileInfoList getValidMaps( const ListFiles & mapFiles, const bool isMultiplayer, const bool isForEditor, const bool isOriginalMapFormat ) { // create a list of unique maps (based on the map file name) and filter it by the preferred number of players std::map uniqueMaps; const Settings & conf = Settings::Get(); const int prefNumOfPlayers = conf.PreferablyCountPlayers(); for ( const std::string & mapFile : mapFiles ) { Maps::FileInfo fi; if ( isOriginalMapFormat ) { if ( !fi.readMP2Map( mapFile, isForEditor ) ) { continue; } } else { if ( !fi.readResurrectionMap( mapFile, isForEditor ) ) { continue; } } if ( !isForEditor ) { if ( isMultiplayer ) { assert( prefNumOfPlayers > 1 ); if ( !fi.isAllowCountPlayers( prefNumOfPlayers ) ) { continue; } } else { const int humanOnlyColorsCount = Color::Count( fi.HumanOnlyColors() ); // Map has more than one human-only color, it is not suitable for single player mode if ( humanOnlyColorsCount > 1 ) { continue; } // Map has the human-only color, only this color can be selected by a human player if ( humanOnlyColorsCount == 1 ) { fi.removeHumanColors( fi.AllowCompHumanColors() ); } } } uniqueMaps.try_emplace( System::GetBasename( mapFile ), std::move( fi ) ); } MapsFileInfoList result; result.reserve( uniqueMaps.size() ); for ( auto & [name, info] : uniqueMaps ) { result.emplace_back( std::move( info ) ); } return result; } } namespace Editor { enum { Wins = 0x1000, CaptureTown = 0x1001, DefeatHero = 0x1002, FindArtifact = 0x1003, SideWins = 0x1004, AccumulateGold = 0x1005, CompAlsoWins = 0x0100, AllowNormalVictory = 0x0200, Loss = 0x2000, LoseTown = 0x2001, LoseHero = 0x2002, OutTime = 0x2003 }; } void Maps::FileInfo::Reset() { filename.clear(); name.clear(); description.clear(); width = 0; height = 0; difficulty = Difficulty::NORMAL; static_assert( std::is_same_v>, "Type of races has been changed, check the logic below" ); static_assert( std::is_same_v>, "Type of unions has been changed, check the logic below" ); for ( int i = 0; i < KINGDOMMAX; ++i ) { races[i] = Race::NONE; unions[i] = Color::IndexToColor( i ); } kingdomColors = 0; colorsAvailableForHumans = 0; colorsAvailableForComp = 0; colorsOfRandomRaces = 0; victoryConditionType = VICTORY_DEFEAT_EVERYONE; compAlsoWins = false; allowNormalVictory = false; victoryConditionParams.fill( 0 ); lossConditionType = LOSS_EVERYTHING; lossConditionParams.fill( 0 ); timestamp = 0; startWithHeroInEachCastle = false; version = GameVersion::SUCCESSION_WARS; worldDay = 0; worldWeek = 0; worldMonth = 0; } bool Maps::FileInfo::readMP2Map( std::string filePath, const bool isForEditor ) { Reset(); StreamFile fs; if ( !fs.open( filePath, "rb" ) ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Error opening the file " << filePath ) return false; } // magic byte if ( fs.getBE32() != 0x5C000000 ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "File " << filePath << " is not a valid map file" ) return false; } filename = std::move( filePath ); // Difficulty level switch ( fs.getLE16() ) { case 0x00: difficulty = Difficulty::EASY; break; case 0x01: difficulty = Difficulty::NORMAL; break; case 0x02: difficulty = Difficulty::HARD; break; case 0x03: difficulty = Difficulty::EXPERT; break; default: difficulty = Difficulty::NORMAL; break; } // Width & height of the map in tiles width = fs.get(); height = fs.get(); const Colors colors( Color::ALL ); // Colors used by kingdoms: blue, green, red, yellow, orange, purple for ( const int color : colors ) { if ( fs.get() != 0 ) { kingdomColors |= color; } } // Colors available for human players: blue, green, red, yellow, orange, purple for ( const int color : colors ) { if ( fs.get() != 0 ) { colorsAvailableForHumans |= color; } } // Colors available for computer players: blue, green, red, yellow, orange, purple for ( const int color : colors ) { if ( fs.get() != 0 ) { colorsAvailableForComp |= color; } } if ( !isForEditor && colorsAvailableForHumans == 0 ) { // This is not a valid map since no human players exist so it cannot be played. DEBUG_LOG( DBG_GAME, DBG_WARN, "Map " << filename << " does not contain any human players." ) return false; } // TODO: Number of active kingdoms (unused) // fs.seekg(0x1A, std::ios_base::beg); // fs.get(); fs.seek( 29 ); // Victory condition type. victoryConditionType = fs.get(); // Do the victory conditions apply to AI too? compAlsoWins = ( fs.get() != 0 ); // Is "normal victory" (defeating all other players) applicable here? allowNormalVictory = ( fs.get() != 0 ); // Parameter of victory condition. victoryConditionParams[0] = fs.getLE16(); // Loss condition type. lossConditionType = fs.get(); // Parameter of loss condition. lossConditionParams[0] = fs.getLE16(); // Does the game start with heroes in castles automatically? startWithHeroInEachCastle = ( 0 == fs.get() ); static_assert( std::is_same_v>, "Type of races has been changed, check the logic below" ); // Initial races. for ( const int color : colors ) { const uint8_t race = Race::IndexToRace( fs.get() ); const int idx = Color::GetIndex( color ); assert( idx < KINGDOMMAX ); races[idx] = race; if ( Race::RAND == race ) { colorsOfRandomRaces |= color; } } // Additional parameter of victory condition. victoryConditionParams[1] = fs.getLE16(); // Additional parameter of loss condition. lossConditionParams[1] = fs.getLE16(); bool skipUnionSetup = false; // If loss conditions are LOSS_HERO and victory conditions are VICTORY_DEFEAT_EVERYONE then we have to verify the color to which this object belongs to. // If the color is under computer control only we have to make it as an ally for human player. if ( lossConditionType == LOSS_HERO && victoryConditionType == VICTORY_DEFEAT_EVERYONE && Colors( colorsAvailableForHumans ).size() == 1 ) { // Each tile needs 16 + 8 + 8 + 8 + 8 + 8 + 8 + 8 + 8 + 16 + 32 + 32 = 160 bits or 20 bytes. fs.seek( MP2::MP2_MAP_INFO_SIZE + ( lossConditionParams[0] + lossConditionParams[1] * width ) * 20 ); MP2::mp2tile_t mp2tile; MP2::loadTile( fs, mp2tile ); Maps::Tiles tile; tile.Init( 0, mp2tile ); std::pair colorRace = getColorRaceFromHeroSprite( tile.GetObjectSpriteIndex() ); if ( ( colorRace.first & colorsAvailableForHumans ) == 0 ) { const int side1 = colorRace.first | colorsAvailableForHumans; const int side2 = colorsAvailableForComp ^ colorRace.first; FillUnions( side1, side2 ); victoryConditionType = VICTORY_DEFEAT_OTHER_SIDE; skipUnionSetup = true; } } // Map name fs.seek( 58 ); name = fs.toString( mapNameLength ); // Map description fs.seek( 118 ); description = fs.toString( mapDescriptionLength ); // Alliances of kingdoms if ( victoryConditionType == VICTORY_DEFEAT_OTHER_SIDE && !skipUnionSetup ) { int side1 = 0; int side2 = 0; const Colors availableColors( kingdomColors ); if ( availableColors.empty() ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "File " << filename << ": invalid list of kingdom colors during map load" ) return false; } const int numPlayersSide1 = victoryConditionParams[0]; if ( ( numPlayersSide1 <= 0 ) || ( numPlayersSide1 >= static_cast( availableColors.size() ) ) ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "File " << filename << ": invalid victory condition param during map load" ) return false; } int playerIdx = 0; for ( const int color : availableColors ) { if ( playerIdx < numPlayersSide1 ) { side1 |= color; } else { side2 |= color; } ++playerIdx; } FillUnions( side1, side2 ); } // Determine the type of the map const size_t pos = filename.rfind( '.' ); if ( pos != std::string::npos ) { const std::string fileExtension = StringLower( filename.substr( pos + 1 ) ); version = ( fileExtension == "mx2" || fileExtension == "hxc" ) ? GameVersion::PRICE_OF_LOYALTY : GameVersion::SUCCESSION_WARS; } return true; } bool Maps::FileInfo::readResurrectionMap( std::string filePath, const bool isForEditor ) { Reset(); Maps::Map_Format::MapFormat map; if ( !Maps::Map_Format::loadBaseMap( filePath, map ) ) { return false; } filename = std::move( filePath ); difficulty = map.difficulty; width = static_cast( map.size ); height = static_cast( map.size ); name = std::move( map.name ); description = std::move( map.description ); kingdomColors = map.availablePlayerColors; colorsAvailableForHumans = map.humanPlayerColors; colorsAvailableForComp = map.computerPlayerColors; version = GameVersion::RESURRECTION; if ( !isForEditor && colorsAvailableForHumans == 0 ) { // This is not a valid map since no human players exist so it cannot be played. DEBUG_LOG( DBG_GAME, DBG_WARN, "Map " << filename << " does not contain any human players." ) return false; } return true; } void Maps::FileInfo::FillUnions( const int side1Colors, const int side2Colors ) { static_assert( std::is_same_v>, "Type of unions has been changed, check the logic below" ); using UnionsItemType = decltype( unions )::value_type; for ( int i = 0; i < KINGDOMMAX; ++i ) { const uint8_t color = Color::IndexToColor( i ); if ( side1Colors & color ) { assert( side1Colors >= std::numeric_limits::min() && side1Colors <= std::numeric_limits::max() ); unions[i] = static_cast( side1Colors ); } else if ( side2Colors & color ) { assert( side2Colors >= std::numeric_limits::min() && side2Colors <= std::numeric_limits::max() ); unions[i] = static_cast( side2Colors ); } else { unions[i] = color; } } } bool Maps::FileInfo::sortByFileName( const FileInfo & lhs, const FileInfo & rhs ) { return CaseInsensitiveCompare( lhs.filename, rhs.filename ); } bool Maps::FileInfo::sortByMapName( const FileInfo & lhs, const FileInfo & rhs ) { return CaseInsensitiveCompare( lhs.name, rhs.name ); } int Maps::FileInfo::KingdomRace( int color ) const { switch ( color ) { case Color::BLUE: return races[0]; case Color::GREEN: return races[1]; case Color::RED: return races[2]; case Color::YELLOW: return races[3]; case Color::ORANGE: return races[4]; case Color::PURPLE: return races[5]; default: break; } return 0; } uint32_t Maps::FileInfo::ConditionWins() const { switch ( victoryConditionType ) { case VICTORY_DEFEAT_EVERYONE: return GameOver::WINS_ALL; case VICTORY_CAPTURE_TOWN: return allowNormalVictory ? ( GameOver::WINS_TOWN | GameOver::WINS_ALL ) : GameOver::WINS_TOWN; case VICTORY_KILL_HERO: return allowNormalVictory ? ( GameOver::WINS_HERO | GameOver::WINS_ALL ) : GameOver::WINS_HERO; case VICTORY_OBTAIN_ARTIFACT: return allowNormalVictory ? ( GameOver::WINS_ARTIFACT | GameOver::WINS_ALL ) : GameOver::WINS_ARTIFACT; case VICTORY_DEFEAT_OTHER_SIDE: return GameOver::WINS_SIDE; case VICTORY_COLLECT_ENOUGH_GOLD: return allowNormalVictory ? ( GameOver::WINS_GOLD | GameOver::WINS_ALL ) : GameOver::WINS_GOLD; default: // This is an unsupported winning condition! Please add the logic to handle it. assert( 0 ); break; } return GameOver::COND_NONE; } uint32_t Maps::FileInfo::ConditionLoss() const { switch ( lossConditionType ) { case LOSS_EVERYTHING: return GameOver::LOSS_ALL; case LOSS_TOWN: return GameOver::LOSS_TOWN; case LOSS_HERO: return GameOver::LOSS_HERO; case LOSS_OUT_OF_TIME: return GameOver::LOSS_TIME; default: // This is an unsupported loss condition! Please add the logic to handle it. assert( 0 ); break; } return GameOver::COND_NONE; } bool Maps::FileInfo::WinsCompAlsoWins() const { return compAlsoWins && ( ( GameOver::WINS_TOWN | GameOver::WINS_GOLD ) & ConditionWins() ); } bool Maps::FileInfo::isAllowCountPlayers( int playerCount ) const { const int humanOnly = Color::Count( HumanOnlyColors() ); const int compHuman = Color::Count( AllowCompHumanColors() ); return humanOnly <= playerCount && playerCount <= humanOnly + compHuman; } StreamBase & Maps::operator<<( StreamBase & msg, const FileInfo & fi ) { using VersionUnderlyingType = std::underlying_type_t; // Only the basename of map filename (fi.file) is saved msg << System::GetBasename( fi.filename ) << fi.name << fi.description << fi.width << fi.height << fi.difficulty << static_cast( KINGDOMMAX ); static_assert( std::is_same_v>, "Type of races has been changed, check the logic below" ); static_assert( std::is_same_v>, "Type of unions has been changed, check the logic below" ); for ( size_t i = 0; i < KINGDOMMAX; ++i ) { msg << fi.races[i] << fi.unions[i]; } return msg << fi.kingdomColors << fi.colorsAvailableForHumans << fi.colorsAvailableForComp << fi.colorsOfRandomRaces << fi.victoryConditionType << fi.compAlsoWins << fi.allowNormalVictory << fi.victoryConditionParams[0] << fi.victoryConditionParams[1] << fi.lossConditionType << fi.lossConditionParams[0] << fi.lossConditionParams[1] << fi.timestamp << fi.startWithHeroInEachCastle << static_cast( fi.version ) << fi.worldDay << fi.worldWeek << fi.worldMonth; } StreamBase & Maps::operator>>( StreamBase & msg, FileInfo & fi ) { uint8_t kingdommax = 0; // Only the basename of map filename (fi.file) is loaded msg >> fi.filename >> fi.name >> fi.description >> fi.width >> fi.height >> fi.difficulty >> kingdommax; static_assert( std::is_same_v>, "Type of races has been changed, check the logic below" ); static_assert( std::is_same_v>, "Type of unions has been changed, check the logic below" ); using RacesItemType = decltype( fi.races )::value_type; using UnionsItemType = decltype( fi.unions )::value_type; for ( size_t i = 0; i < kingdommax; ++i ) { RacesItemType racesItem = 0; UnionsItemType unionsItem = 0; msg >> racesItem >> unionsItem; if ( i < KINGDOMMAX ) { fi.races[i] = racesItem; fi.unions[i] = unionsItem; } } msg >> fi.kingdomColors >> fi.colorsAvailableForHumans >> fi.colorsAvailableForComp >> fi.colorsOfRandomRaces >> fi.victoryConditionType >> fi.compAlsoWins >> fi.allowNormalVictory >> fi.victoryConditionParams[0] >> fi.victoryConditionParams[1] >> fi.lossConditionType >> fi.lossConditionParams[0] >> fi.lossConditionParams[1] >> fi.timestamp >> fi.startWithHeroInEachCastle; using VersionUnderlyingType = std::underlying_type_t; static_assert( std::is_same_v, "Type of version has been changed, check the logic below" ); VersionUnderlyingType version = 0; msg >> version; fi.version = static_cast( version ); return msg >> fi.worldDay >> fi.worldWeek >> fi.worldMonth; } MapsFileInfoList Maps::getAllMapFileInfos( const bool isForEditor, const bool isMultiplayer ) { ListFiles maps = Settings::FindFiles( "maps", ".mp2", false ); const bool isPOLSupported = Settings::Get().isPriceOfLoyaltySupported(); if ( isPOLSupported ) { maps.Append( Settings::FindFiles( "maps", ".mx2", false ) ); } MapsFileInfoList validMaps = getValidMaps( maps, isMultiplayer, isForEditor, true ); if ( isPOLSupported ) { const ListFiles resurrectionMaps = Settings::FindFiles( "maps", ".fh2m", false ); MapsFileInfoList validResurrectionMaps = getValidMaps( resurrectionMaps, isMultiplayer, isForEditor, false ); validMaps.reserve( maps.size() + resurrectionMaps.size() ); for ( auto & map : validResurrectionMaps ) { validMaps.emplace_back( std::move( map ) ); } } std::sort( validMaps.begin(), validMaps.end(), Maps::FileInfo::sortByMapName ); return validMaps; } MapsFileInfoList Maps::getResurrectionMapFileInfos( const bool isForEditor, const bool isMultiplayer ) { if ( !Settings::Get().isPriceOfLoyaltySupported() ) { // Resurrection maps require POL resources presence. return {}; } const ListFiles maps = Settings::FindFiles( "maps", ".fh2m", false ); MapsFileInfoList validMaps = getValidMaps( maps, isMultiplayer, isForEditor, false ); std::sort( validMaps.begin(), validMaps.end(), Maps::FileInfo::sortByMapName ); return validMaps; } fheroes2-1.0.12+dfsg/src/fheroes2/maps/maps_fileinfo.h000066400000000000000000000151271456075706000224770ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2MAPSFILEINFO_H #define H2MAPSFILEINFO_H #include #include #include #include #include #include "gamedefs.h" #include "math_base.h" class StreamBase; enum class GameVersion : int { SUCCESSION_WARS = 0, PRICE_OF_LOYALTY = 1, RESURRECTION = 2 }; namespace Maps { struct FileInfo { public: FileInfo() { Reset(); } FileInfo( const FileInfo & ) = default; FileInfo( FileInfo && ) = default; ~FileInfo() = default; FileInfo & operator=( const FileInfo & ) = default; FileInfo & operator=( FileInfo && ) = default; bool operator==( const FileInfo & fi ) const { return filename == fi.filename; } bool readMP2Map( std::string filePath, const bool isForEditor ); bool readResurrectionMap( std::string filePath, const bool isForEditor ); bool isAllowCountPlayers( int playerCount ) const; int AllowCompHumanColors() const { return colorsAvailableForHumans & colorsAvailableForComp; } int HumanOnlyColors() const { return colorsAvailableForHumans & ~colorsAvailableForComp; } int ComputerOnlyColors() const { return colorsAvailableForComp & ~colorsAvailableForHumans; } int KingdomRace( int color ) const; uint32_t ConditionWins() const; uint32_t ConditionLoss() const; bool WinsCompAlsoWins() const; int WinsFindArtifactID() const { // In the original game artifact IDs start from 0 but for the victory condition it starts from 1 which aligns with fheroes2 artifact enumeration. return victoryConditionParams[0]; } bool WinsFindUltimateArtifact() const { return victoryConditionParams[0] == 0; } uint32_t getWinningGoldAccumulationValue() const { return victoryConditionParams[0] * 1000; } fheroes2::Point WinsMapsPositionObject() const { return { victoryConditionParams[0], victoryConditionParams[1] }; } fheroes2::Point LossMapsPositionObject() const { return { lossConditionParams[0], lossConditionParams[1] }; } uint32_t LossCountDays() const { return lossConditionParams[0]; } void removeHumanColors( const int colors ) { colorsAvailableForHumans &= ~colors; } bool AllowChangeRace( const int color ) const { return ( colorsOfRandomRaces & color ) != 0; } void Reset(); static bool sortByFileName( const FileInfo & lhs, const FileInfo & rhs ); static bool sortByMapName( const FileInfo & lhs, const FileInfo & rhs ); enum VictoryCondition : uint8_t { VICTORY_DEFEAT_EVERYONE = 0, VICTORY_CAPTURE_TOWN = 1, VICTORY_KILL_HERO = 2, VICTORY_OBTAIN_ARTIFACT = 3, VICTORY_DEFEAT_OTHER_SIDE = 4, VICTORY_COLLECT_ENOUGH_GOLD = 5 }; enum LossCondition : uint8_t { LOSS_EVERYTHING = 0, LOSS_TOWN = 1, LOSS_HERO = 2, LOSS_OUT_OF_TIME = 3 }; std::string filename; std::string name; std::string description; uint16_t width; uint16_t height; uint8_t difficulty; std::array races; std::array unions; uint8_t kingdomColors; uint8_t colorsAvailableForHumans; uint8_t colorsAvailableForComp; uint8_t colorsOfRandomRaces; // Refer to the VictoryCondition uint8_t victoryConditionType; bool compAlsoWins; bool allowNormalVictory; std::array victoryConditionParams; // Refer to the LossCondition uint8_t lossConditionType; std::array lossConditionParams; // Timestamp of the save file, only relevant for save files uint32_t timestamp; bool startWithHeroInEachCastle; GameVersion version; // World date at the moment the save file was created, only relevant for save files uint32_t worldDay; uint32_t worldWeek; uint32_t worldMonth; private: void FillUnions( const int side1Colors, const int side2Colors ); }; StreamBase & operator<<( StreamBase &, const FileInfo & ); StreamBase & operator>>( StreamBase &, FileInfo & ); } using MapsFileInfoList = std::vector; namespace Maps { // For all map files. MapsFileInfoList getAllMapFileInfos( const bool isForEditor, const bool isMultiplayer ); // Only for RESURRECTION map files. MapsFileInfoList getResurrectionMapFileInfos( const bool isForEditor, const bool isMultiplayer ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/maps/maps_objects.cpp000066400000000000000000000276421456075706000226750ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2013 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include "color.h" #include "logging.h" #include "maps_objects.h" #include "mp2.h" #include "rand.h" #include "serialize.h" #include "tools.h" #include "translations.h" StreamBase & operator<<( StreamBase & msg, const MapObjectSimple & obj ) { return msg << obj.type << obj.uid << static_cast( obj ); } StreamBase & operator>>( StreamBase & msg, MapObjectSimple & obj ) { return msg >> obj.type >> obj.uid >> static_cast( obj ); } MapEvent::MapEvent() : MapObjectSimple( MP2::OBJ_EVENT ) , computer( false ) , cancel( true ) , colors( 0 ) {} void MapEvent::LoadFromMP2( const int32_t index, const std::vector & data ) { assert( data.size() >= MP2::MP2_EVENT_STRUCTURE_MIN_SIZE ); assert( data[0] == 1 ); // Structure containing information about a ground event. // // - uint8_t (1 byte) // Always 1 as an indicator that this indeed a ground event. // // - int32_t (4 bytes) // The amount of Wood to be given. Can be negative. // // - int32_t (4 bytes) // The amount of Mercury to be given. Can be negative. // // - int32_t (4 bytes) // The amount of Ore to be given. Can be negative. // // - int32_t (4 bytes) // The amount of Sulfur to be given. Can be negative. // // - int32_t (4 bytes) // The amount of Crystals to be given. Can be negative. // // - int32_t (4 bytes) // The amount of Gems to be given. Can be negative. // // - int32_t (4 bytes) // The amount of Gold to be given. Can be negative. // // - uint16_t (2 bytes) // An artifact to be given. // // - uint8_t (1 byte) // A flag whether the event is applicable for AI players as well. // // - uint8_t (1 bytes) // Does event occur only once? // // - unused 10 bytes // Always 0. // // - uint8_t (1 byte) // A flag to determine whether Blue player receives the event. // // - uint8_t (1 byte) // A flag to determine whether Green player receives the event. // // - uint8_t (1 byte) // A flag to determine whether Red player receives the event. // // - uint8_t (1 byte) // A flag to determine whether Yellow player receives the event. // // - uint8_t (1 byte) // A flag to determine whether Orange player receives the event. // // - uint8_t (1 byte) // A flag to determine whether Purple player receives the event. // // - string // Null terminated string containing the event text. SetIndex( index ); SetUID( index ); StreamBuf dataStream( data ); dataStream.skip( 1 ); // Get the amount of resources. resources.wood = static_cast( dataStream.getLE32() ); resources.mercury = static_cast( dataStream.getLE32() ); resources.ore = static_cast( dataStream.getLE32() ); resources.sulfur = static_cast( dataStream.getLE32() ); resources.crystal = static_cast( dataStream.getLE32() ); resources.gems = static_cast( dataStream.getLE32() ); resources.gold = static_cast( dataStream.getLE32() ); // An artifact to be given. artifact = dataStream.getLE16() + 1; // The event applies to AI players as well. computer = ( dataStream.get() != 0 ); // Does event occur only once? cancel = ( dataStream.get() != 0 ); dataStream.skip( 10 ); colors = 0; if ( dataStream.get() ) { colors |= Color::BLUE; } if ( dataStream.get() ) { colors |= Color::GREEN; } if ( dataStream.get() ) { colors |= Color::RED; } if ( dataStream.get() ) { colors |= Color::YELLOW; } if ( dataStream.get() ) { colors |= Color::ORANGE; } if ( dataStream.get() ) { colors |= Color::PURPLE; } message = dataStream.toString(); DEBUG_LOG( DBG_GAME, DBG_INFO, "Ground event at tile " << index << " has event message: " << message ) } void MapEvent::SetVisited( int color ) { if ( cancel ) colors = 0; else colors &= ~color; } bool MapEvent::isAllow( int col ) const { return ( col & colors ) != 0; } MapSphinx::MapSphinx() : MapObjectSimple( MP2::OBJ_SPHINX ) , valid( false ) {} void MapSphinx::LoadFromMP2( const int32_t tileIndex, const std::vector & data ) { assert( data.size() >= MP2::MP2_RIDDLE_STRUCTURE_MIN_SIZE ); // Structure containing information about a Sphinx object. // // - uint8_t (1 byte) // Always 0 as an indicator that this indeed a Sphinx object. // // - uint32_t (4 bytes) // The amount of Wood to get upon a successful answer. // // - uint32_t (4 bytes) // The amount of Mercury to get upon a successful answer. // // - uint32_t (4 bytes) // The amount of Ore to get upon a successful answer. // // - uint32_t (4 bytes) // The amount of Sulfur to get upon a successful answer. // // - uint32_t (4 bytes) // The amount of Crystals to get upon a successful answer. // // - uint32_t (4 bytes) // The amount of Gems to get upon a successful answer. // // - uint32_t (4 bytes) // The amount of Gold to get upon a successful answer. // // - uint16_t (2 bytes) // An artifact to get upon a successful answer. // // - uint8_t (1 byte) // A number of correct answers. // // - string of 13 bytes // Null terminated string of first answer. // // - string of 13 bytes // Null terminated string of second answer. // // - string of 13 bytes // Null terminated string of third answer. // // - string of 13 bytes // Null terminated string of forth answer. // // - string of 13 bytes // Null terminated string of fifth answer. // // - string of 13 bytes // Null terminated string of sixth answer. // // - string of 13 bytes // Null terminated string of seventh answer. // // - string of 13 bytes // Null terminated string of eighth answer. // // - string // Question itself. StreamBuf dataStream( data ); const uint8_t magicNumber = dataStream.get(); if ( magicNumber != 0 ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Sphinx data magic number " << static_cast( magicNumber ) << " is incorrect." ) return; } // Retrieve the amount of resources to be given while answering the Sphinx riddle correctly. resources.wood = dataStream.getLE32(); resources.mercury = dataStream.getLE32(); resources.ore = dataStream.getLE32(); resources.sulfur = dataStream.getLE32(); resources.crystal = dataStream.getLE32(); resources.gems = dataStream.getLE32(); resources.gold = dataStream.getLE32(); // Retrieve an artifact to be given while answering the Sphinx riddle correctly. artifact = dataStream.getLE16() + 1; uint8_t answerCount = dataStream.get(); // Get all possible answers. for ( uint32_t i = 0; i < 8; ++i ) { std::string answer = dataStream.toString( 13 ); if ( answerCount > 0 ) { --answerCount; if ( !answer.empty() ) { answers.push_back( StringLower( answer ) ); } } } message = dataStream.toString(); if ( message.empty() ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Sphinx at tile index " << tileIndex << " does not have questions. Marking it as visited." ) return; } DEBUG_LOG( DBG_GAME, DBG_INFO, "Sphinx question is '" << message << "'." ) valid = true; SetIndex( tileIndex ); SetUID( tileIndex ); } bool MapSphinx::AnswerCorrect( const std::string & answer ) { const std::string ans = StringLower( answer ).substr( 0, 4 ); const auto checkAnswer = [&ans]( const std::string & str ) { return StringLower( str ).substr( 0, 4 ) == ans; }; return std::any_of( answers.begin(), answers.end(), checkAnswer ); } void MapSphinx::SetQuiet() { valid = false; artifact = Artifact::UNKNOWN; resources.Reset(); } StreamBase & operator<<( StreamBase & msg, const MapEvent & obj ) { return msg << static_cast( obj ) << obj.resources << obj.artifact << obj.computer << obj.cancel << obj.colors << obj.message; } StreamBase & operator>>( StreamBase & msg, MapEvent & obj ) { return msg >> static_cast( obj ) >> obj.resources >> obj.artifact >> obj.computer >> obj.cancel >> obj.colors >> obj.message; } StreamBase & operator<<( StreamBase & msg, const MapSphinx & obj ) { return msg << static_cast( obj ) << obj.resources << obj.artifact << obj.answers << obj.message << obj.valid; } StreamBase & operator>>( StreamBase & msg, MapSphinx & obj ) { return msg >> static_cast( obj ) >> obj.resources >> obj.artifact >> obj.answers >> obj.message >> obj.valid; } MapSign::MapSign() : MapObjectSimple( MP2::OBJ_SIGN ) {} void MapSign::LoadFromMP2( const int32_t mapIndex, const std::vector & data ) { assert( data.size() >= MP2::MP2_SIGN_STRUCTURE_MIN_SIZE ); assert( data[0] == 0x1 ); // Structure containing information about a sign or bottle. // // - uint8_t (1 byte) // Always equal to 1. // // - unused 8 bytes // Unknown / unused. TODO: find out what these bytes used for. // // - string // Null terminated string. StreamBuf dataStream( data ); dataStream.skip( 9 ); message = dataStream.toString(); if ( message.empty() ) { const std::vector randomMessage{ _( "Next sign 50 miles." ), _( "Burma shave." ), _( "See Rock City." ), _( "This space for rent." ) }; message = Rand::Get( randomMessage ); } SetIndex( mapIndex ); SetUID( mapIndex ); DEBUG_LOG( DBG_GAME, DBG_INFO, "Sign at location " << mapIndex << " has a message: " << message ) } StreamBase & operator<<( StreamBase & msg, const MapSign & obj ) { return msg << static_cast( obj ) << obj.message; } StreamBase & operator>>( StreamBase & msg, MapSign & obj ) { return msg >> static_cast( obj ) >> obj.message; } fheroes2-1.0.12+dfsg/src/fheroes2/maps/maps_objects.h000066400000000000000000000073701456075706000223360ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2013 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2MAPS_OBJECTS_H #define H2MAPS_OBJECTS_H #include #include #include #include #include "artifact.h" #include "position.h" #include "resource.h" class StreamBase; class MapObjectSimple : public MapPosition { public: explicit MapObjectSimple( int v = 0 ) : uid( 0 ) , type( v ) {} ~MapObjectSimple() override = default; int GetType() const { return type; } uint32_t GetUID() const { return uid; } void SetUID( uint32_t v ) { uid = v; } protected: friend StreamBase & operator<<( StreamBase &, const MapObjectSimple & ); friend StreamBase & operator>>( StreamBase &, MapObjectSimple & ); uint32_t uid; int type; }; StreamBase & operator<<( StreamBase &, const MapObjectSimple & ); StreamBase & operator>>( StreamBase &, MapObjectSimple & ); struct MapEvent : public MapObjectSimple { MapEvent(); void LoadFromMP2( const int32_t index, const std::vector & data ); bool isAllow( int color ) const; void SetVisited( int color ); Funds resources; Artifact artifact; bool computer; bool cancel; int colors; std::string message; }; StreamBase & operator<<( StreamBase &, const MapEvent & ); StreamBase & operator>>( StreamBase &, MapEvent & ); using RiddleAnswers = std::list; struct MapSphinx : public MapObjectSimple { MapSphinx(); void LoadFromMP2( const int32_t tileIndex, const std::vector & data ); bool AnswerCorrect( const std::string & answer ); void SetQuiet(); Funds resources; Artifact artifact; RiddleAnswers answers; std::string message; bool valid; }; StreamBase & operator<<( StreamBase &, const MapSphinx & ); StreamBase & operator>>( StreamBase &, MapSphinx & ); struct MapSign : public MapObjectSimple { MapSign(); void LoadFromMP2( const int32_t mapIndex, const std::vector & data ); std::string message; }; StreamBase & operator<<( StreamBase &, const MapSign & ); StreamBase & operator>>( StreamBase &, MapSign & ); #endif fheroes2-1.0.12+dfsg/src/fheroes2/maps/maps_tiles.cpp000066400000000000000000002121021456075706000223470ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "maps_tiles.h" #include #include #include #include #include #include #include #include #include #include #include "army_troop.h" #include "castle.h" #include "game_io.h" #include "heroes.h" #include "icn.h" #include "logging.h" #include "maps.h" #include "maps_tiles_helper.h" // TODO: This file should not be included #include "mounts.h" #include "mp2.h" #include "objcrck.h" #include "objdirt.h" #include "objdsrt.h" #include "objgras.h" #include "objlava.h" #include "objmult.h" #include "objsnow.h" #include "objswmp.h" #include "objtown.h" #include "objwatr.h" #include "objxloc.h" #include "pairs.h" #include "save_format_version.h" #include "serialize.h" #include "trees.h" #include "world.h" #include "world_object_uid.h" namespace { bool isValidShadowSprite( const int icn, const uint8_t icnIndex ) { if ( icn == 0 ) { // Special case when no objects exist. return false; } switch ( icn ) { case ICN::MTNDSRT: case ICN::MTNGRAS: case ICN::MTNLAVA: case ICN::MTNMULT: case ICN::MTNSNOW: case ICN::MTNSWMP: return ObjMnts1::isShadow( icnIndex ); case ICN::MTNCRCK: case ICN::MTNDIRT: return ObjMnts2::isShadow( icnIndex ); case ICN::TREDECI: case ICN::TREEVIL: case ICN::TREFALL: case ICN::TREFIR: case ICN::TREJNGL: case ICN::TRESNOW: return ObjTree::isShadow( icnIndex ); case ICN::OBJNCRCK: return ObjCrck::isShadow( icnIndex ); case ICN::OBJNDIRT: return ObjDirt::isShadow( icnIndex ); case ICN::OBJNDSRT: return ObjDsrt::isShadow( icnIndex ); case ICN::OBJNGRA2: return ObjGra2::isShadow( icnIndex ); case ICN::OBJNGRAS: return ObjGras::isShadow( icnIndex ); case ICN::OBJNMUL2: return ObjMul2::isShadow( icnIndex ); case ICN::OBJNMULT: return ObjMult::isShadow( icnIndex ); case ICN::OBJNSNOW: return ObjSnow::isShadow( icnIndex ); case ICN::OBJNSWMP: return ObjSwmp::isShadow( icnIndex ); case ICN::OBJNWAT2: return ObjWat2::isShadow( icnIndex ); case ICN::OBJNWATR: return ObjWatr::isShadow( icnIndex ); case ICN::OBJNARTI: case ICN::OBJNRSRC: return 0 == ( icnIndex % 2 ); case ICN::OBJNTWRD: return icnIndex > 31; case ICN::X_LOC1: return ObjXlc1::isShadow( icnIndex ); case ICN::X_LOC2: return ObjXlc2::isShadow( icnIndex ); case ICN::X_LOC3: return ObjXlc3::isShadow( icnIndex ); case ICN::OBJNTOWN: return ObjTown::isShadow( icnIndex ); case ICN::OBJNLAVA: return ObjLava::isShadow( icnIndex ); case ICN::OBJNLAV2: return ObjLav2::isShadow( icnIndex ); case ICN::OBJNLAV3: return ObjLav3::isShadow( icnIndex ); case ICN::OBJNTWSH: return true; case ICN::STREAM: case ICN::OBJNTWBA: case ICN::OBJNXTRA: case ICN::ROAD: case ICN::EXTRAOVR: case ICN::MONS32: case ICN::BOAT32: case ICN::FLAG32: case ICN::MINIHERO: return false; default: break; } // Did you add a new type of objects into the game? assert( 0 ); return false; } bool isShadowSprite( const MP2::ObjectIcnType objectIcnType, const uint8_t icnIndex ) { return isValidShadowSprite( MP2::getIcnIdFromObjectIcnType( objectIcnType ), icnIndex ); } bool isValidReefsSprite( const MP2::ObjectIcnType objectIcnType, const uint8_t icnIndex ) { return objectIcnType == MP2::OBJ_ICN_TYPE_X_LOC2 && ObjXlc2::isReefs( icnIndex ); } #if defined( VERIFY_SHADOW_SPRITES ) // Define VERIFY_SHADOW_SPRITES macro to be able to use these functions. bool isShadowImage( const fheroes2::Image & image ) { // The image can't be empty and must have transform layer. assert( !image.empty() && !image.singleLayer() ); if ( image.empty() ) return false; const uint8_t * data = image.transform(); const uint8_t * dataEnd = data + image.width() * image.height(); size_t transformCounter = 0; for ( ; data != dataEnd; ++data ) { if ( *data == 0 ) { return false; } else if ( *data != 1 ) { ++transformCounter; } } if ( transformCounter == 0 ) { assert( image.width() == 1 && image.height() == 1 ); return true; } return true; } // Use this function to verify the correctness of data being returned by isValidShadowSprite function. void findAllShadowImages() { static bool completed = false; if ( completed ) { return; } const std::vector icnIds = { ICN::MTNDSRT, ICN::MTNGRAS, ICN::MTNLAVA, ICN::MTNMULT, ICN::MTNSNOW, ICN::MTNSWMP, ICN::MTNCRCK, ICN::MTNDIRT, ICN::TREDECI, ICN::TREEVIL, ICN::TREFALL, ICN::TREFIR, ICN::TREJNGL, ICN::TRESNOW, ICN::OBJNCRCK, ICN::OBJNDIRT, ICN::OBJNDSRT, ICN::OBJNGRA2, ICN::OBJNGRAS, ICN::OBJNMUL2, ICN::OBJNMULT, ICN::OBJNSNOW, ICN::OBJNSWMP, ICN::OBJNWAT2, ICN::OBJNWATR, ICN::OBJNARTI, ICN::OBJNRSRC, ICN::OBJNTWRD, ICN::OBJNTWSH, ICN::STREAM, ICN::OBJNTWBA, ICN::ROAD, ICN::EXTRAOVR, ICN::X_LOC1, ICN::X_LOC2, ICN::X_LOC3, ICN::OBJNTOWN, ICN::OBJNLAVA, ICN::OBJNLAV2, ICN::OBJNLAV3, ICN::MONS32 }; for ( const int32_t icnId : icnIds ) { const uint32_t maxIndex = fheroes2::AGG::GetICNCount( icnId ); assert( maxIndex != 0 ); std::string output; for ( uint32_t i = 0; i < maxIndex; i++ ) { const uint32_t startIndex = ICN::AnimationFrame( icnId, i, 0, true ); const bool hasAnimation = startIndex != 0; bool isImageShadow = isShadowImage( fheroes2::AGG::GetICN( icnId, i ) ); if ( isImageShadow && hasAnimation ) { for ( uint32_t indexOffset = 1;; ++indexOffset ) { const uint32_t animationIndex = ICN::AnimationFrame( icnId, i, indexOffset, true ); if ( startIndex == animationIndex ) { break; } if ( !isShadowImage( fheroes2::AGG::GetICN( icnId, animationIndex ) ) ) { isImageShadow = false; break; } } } if ( isValidShadowSprite( icnId, i ) != isImageShadow ) { output += std::to_string( i ); output += ", "; } } if ( output.empty() ) { continue; } VERBOSE_LOG( ICN::GetString( icnId ) << ": " << output ) } completed = true; } #endif bool isShortObject( const MP2::MapObjectType objectType ) { // Some objects allow middle moves even being attached to the bottom. // These object actually don't have any sprites on tiles above them within addon 2 level objects. // TODO: find a better way to do not hardcode values here. switch ( objectType ) { case MP2::OBJ_HALFLING_HOLE: case MP2::OBJ_NON_ACTION_HALFLING_HOLE: case MP2::OBJ_LEAN_TO: case MP2::OBJ_WATER_LAKE: case MP2::OBJ_TAR_PIT: case MP2::OBJ_MERCENARY_CAMP: case MP2::OBJ_NON_ACTION_MERCENARY_CAMP: case MP2::OBJ_STANDING_STONES: case MP2::OBJ_SHRINE_FIRST_CIRCLE: case MP2::OBJ_SHRINE_SECOND_CIRCLE: case MP2::OBJ_SHRINE_THIRD_CIRCLE: case MP2::OBJ_MAGIC_GARDEN: case MP2::OBJ_RUINS: case MP2::OBJ_NON_ACTION_RUINS: case MP2::OBJ_SIGN: case MP2::OBJ_IDOL: case MP2::OBJ_STONE_LITHS: case MP2::OBJ_NON_ACTION_STONE_LITHS: case MP2::OBJ_WAGON: case MP2::OBJ_WAGON_CAMP: case MP2::OBJ_NON_ACTION_WAGON_CAMP: case MP2::OBJ_GOBLIN_HUT: case MP2::OBJ_FAERIE_RING: case MP2::OBJ_NON_ACTION_FAERIE_RING: case MP2::OBJ_BARRIER: case MP2::OBJ_MAGIC_WELL: case MP2::OBJ_NOTHING_SPECIAL: return true; default: break; } return false; } bool isDetachedObjectType( const MP2::MapObjectType objectType ) { // Some objects do not take into account other objects below them. switch ( objectType ) { case MP2::OBJ_CASTLE: case MP2::OBJ_WAGON_CAMP: case MP2::OBJ_FAERIE_RING: case MP2::OBJ_MINE: case MP2::OBJ_SAWMILL: case MP2::OBJ_WATER_ALTAR: case MP2::OBJ_AIR_ALTAR: case MP2::OBJ_FIRE_ALTAR: case MP2::OBJ_EARTH_ALTAR: return true; default: break; } return false; } bool isCombinedObject( const MP2::MapObjectType objectType ) { // Trees allow bottom and top movements but they don't allow the same for other trees. switch ( objectType ) { case MP2::OBJ_TREES: case MP2::OBJ_CRATER: return true; default: break; } return false; } const char * getObjectLayerName( const uint8_t level ) { switch ( level ) { case Maps::OBJECT_LAYER: return "Object layer"; case Maps::BACKGROUND_LAYER: return "Background layer"; case Maps::SHADOW_LAYER: return "Shadow layer"; case Maps::TERRAIN_LAYER: return "Terrain layer"; default: assert( 0 ); break; } return "Unknown layer"; } MP2::MapObjectType getLoyaltyObject( const MP2::ObjectIcnType objectIcnType, const uint8_t icnIndex ) { switch ( objectIcnType ) { case MP2::OBJ_ICN_TYPE_X_LOC1: if ( icnIndex == 3 ) return MP2::OBJ_ALCHEMIST_TOWER; else if ( icnIndex < 3 ) return MP2::OBJ_NON_ACTION_ALCHEMIST_TOWER; else if ( 70 == icnIndex ) return MP2::OBJ_ARENA; else if ( 3 < icnIndex && icnIndex < 72 ) return MP2::OBJ_NON_ACTION_ARENA; else if ( 77 == icnIndex ) return MP2::OBJ_BARROW_MOUNDS; else if ( 71 < icnIndex && icnIndex < 78 ) return MP2::OBJ_NON_ACTION_BARROW_MOUNDS; else if ( 94 == icnIndex ) return MP2::OBJ_EARTH_ALTAR; else if ( 77 < icnIndex && icnIndex < 112 ) return MP2::OBJ_NON_ACTION_EARTH_ALTAR; else if ( 118 == icnIndex ) return MP2::OBJ_AIR_ALTAR; else if ( 111 < icnIndex && icnIndex < 120 ) return MP2::OBJ_NON_ACTION_AIR_ALTAR; else if ( 127 == icnIndex ) return MP2::OBJ_FIRE_ALTAR; else if ( 119 < icnIndex && icnIndex < 129 ) return MP2::OBJ_NON_ACTION_FIRE_ALTAR; else if ( 135 == icnIndex ) return MP2::OBJ_WATER_ALTAR; else if ( 128 < icnIndex && icnIndex < 137 ) return MP2::OBJ_NON_ACTION_WATER_ALTAR; break; case MP2::OBJ_ICN_TYPE_X_LOC2: if ( icnIndex == 4 ) return MP2::OBJ_STABLES; else if ( icnIndex < 4 ) return MP2::OBJ_NON_ACTION_STABLES; else if ( icnIndex == 9 ) return MP2::OBJ_JAIL; else if ( 4 < icnIndex && icnIndex < 10 ) return MP2::OBJ_NON_ACTION_JAIL; else if ( icnIndex == 37 ) return MP2::OBJ_MERMAID; else if ( 9 < icnIndex && icnIndex < 47 ) return MP2::OBJ_NON_ACTION_MERMAID; else if ( icnIndex == 101 ) return MP2::OBJ_SIRENS; else if ( 46 < icnIndex && icnIndex < 111 ) return MP2::OBJ_NON_ACTION_SIRENS; else if ( ObjXlc2::isReefs( icnIndex ) ) return MP2::OBJ_REEFS; break; case MP2::OBJ_ICN_TYPE_X_LOC3: if ( icnIndex == 30 ) return MP2::OBJ_HUT_OF_MAGI; else if ( icnIndex < 32 ) return MP2::OBJ_NON_ACTION_HUT_OF_MAGI; else if ( icnIndex == 50 ) return MP2::OBJ_EYE_OF_MAGI; else if ( 31 < icnIndex && icnIndex < 59 ) return MP2::OBJ_NON_ACTION_EYE_OF_MAGI; break; default: break; } return MP2::OBJ_NONE; } bool isSpriteRoad( const MP2::ObjectIcnType objectIcnType, const uint8_t imageIndex ) { switch ( objectIcnType ) { case MP2::OBJ_ICN_TYPE_ROAD: { static const std::set allowedIndecies{ 0, 2, 3, 4, 5, 6, 7, 9, 12, 13, 14, 16, 17, 18, 19, 20, 21, 26, 28, 29, 30, 31 }; return ( allowedIndecies.count( imageIndex ) == 1 ); } case MP2::OBJ_ICN_TYPE_OBJNTOWN: { static const std::set allowedIndecies{ 13, 29, 45, 61, 77, 93, 109, 125, 141, 157, 173, 189 }; return ( allowedIndecies.count( imageIndex ) == 1 ); } case MP2::OBJ_ICN_TYPE_OBJNTWRD: { static const std::set allowedIndecies{ 13, 29 }; return ( allowedIndecies.count( imageIndex ) == 1 ); } default: break; } return false; } bool isAddonShadow( const Maps::TilesAddon & ta ) { return isShadowSprite( ta._objectIcnType, ta._imageIndex ); } bool isAddonResource( const Maps::TilesAddon & ta ) { return ( MP2::OBJ_ICN_TYPE_OBJNRSRC == ta._objectIcnType ) && ( ta._imageIndex % 2 ); } bool isAddonArtifact( const Maps::TilesAddon & ta ) { // OBJNARTI (skip ultimate) return ( MP2::OBJ_ICN_TYPE_OBJNARTI == ta._objectIcnType ) && ( ta._imageIndex > 0x10 ) && ( ta._imageIndex % 2 ); } void getAddonInfo( const Maps::TilesAddon & addon, std::ostringstream & os ) { os << "UID : " << addon._uid << std::endl << "ICN object type : " << static_cast( addon._objectIcnType ) << " (" << ICN::GetString( MP2::getIcnIdFromObjectIcnType( addon._objectIcnType ) ) << ")" << std::endl << "image index : " << static_cast( addon._imageIndex ) << std::endl << "layer type : " << static_cast( addon._layerType ) << " - " << getObjectLayerName( addon._layerType ) << std::endl << "is shadow : " << ( isAddonShadow( addon ) ? "yes" : "no" ) << std::endl; } std::string getAddonInfo( const Maps::TilesAddon & addon, const int lvl ) { std::ostringstream os; os << "--------- Level " << lvl << " --------" << std::endl; getAddonInfo( addon, os ); return os.str(); } } void Maps::Tiles::Init( int32_t index, const MP2::mp2tile_t & mp2 ) { _tilePassabilityDirections = DIRECTION_ALL; _metadata[0] = ( ( ( mp2.quantity2 << 8 ) + mp2.quantity1 ) >> 3 ); _fogColors = Color::ALL; _terrainImageIndex = mp2.terrainImageIndex; _terrainFlags = mp2.terrainFlags; _boatOwnerColor = Color::NONE; _index = index; SetObject( static_cast( mp2.mapObjectType ) ); if ( !MP2::doesObjectContainMetadata( _mainObjectType ) && ( _metadata[0] != 0 ) ) { // No metadata should exist for non-action objects. // Some maps have invalid format. Even if this metadata is set here, it will later be reset during world map loading. DEBUG_LOG( DBG_GAME, DBG_WARN, "Metadata present for non action object " << MP2::StringObject( _mainObjectType ) << " at tile " << _index << ". Metadata value " << _metadata[0] ) } _addonBottomLayer.clear(); _addonTopLayer.clear(); const MP2::ObjectIcnType bottomObjectIcnType = static_cast( mp2.objectName1 >> 2 ); const uint8_t layerType = ( mp2.quantity1 & 0x03 ); // In the original Editor the road bit is set even if no road exist. // It is important to verify the existence of a road without relying on this bit. if ( isSpriteRoad( bottomObjectIcnType, mp2.bottomIcnImageIndex ) ) { _isTileMarkedAsRoad = true; } if ( mp2.mapObjectType == MP2::OBJ_NONE && ( layerType == Maps::ObjectLayerType::SHADOW_LAYER || layerType == Maps::ObjectLayerType::TERRAIN_LAYER ) ) { // If an object sits on shadow or terrain layer then we should put it as a bottom layer add-on. if ( bottomObjectIcnType != MP2::ObjectIcnType::OBJ_ICN_TYPE_UNKNOWN ) { _addonBottomLayer.emplace_back( layerType, mp2.level1ObjectUID, bottomObjectIcnType, mp2.bottomIcnImageIndex ); } } else { _mainAddon._layerType = layerType; _mainAddon._uid = mp2.level1ObjectUID; _mainAddon._objectIcnType = bottomObjectIcnType; _mainAddon._imageIndex = mp2.bottomIcnImageIndex; } const MP2::ObjectIcnType topObjectIcnType = static_cast( mp2.objectName2 >> 2 ); if ( topObjectIcnType != MP2::ObjectIcnType::OBJ_ICN_TYPE_UNKNOWN ) { // Top layer objects do not have any internal structure (layers) so all of them should have the same internal layer. // TODO: remove layer type for top layer objects. _addonTopLayer.emplace_back( OBJECT_LAYER, mp2.level2ObjectUID, topObjectIcnType, mp2.topIcnImageIndex ); } } void Maps::Tiles::setTerrain( const uint16_t terrainImageIndex, const bool horizontalFlip, const bool verticalFlip ) { _terrainFlags = ( verticalFlip ? 1 : 0 ) + ( horizontalFlip ? 2 : 0 ); const int newGround = Ground::getGroundByImageIndex( terrainImageIndex ); const bool isNewGroundWater = ( newGround == Ground::WATER ); const int oldGround = GetGround(); if ( ( isNewGroundWater || oldGround == Ground::WATER ) && oldGround != newGround ) { // When changing water to land and vice-versa we should remove all objects from the tile. eraseOjects( *this, ObjectErasureType::ALL_OBJECTS ); } if ( ( _isTileMarkedAsRoad || isStream() ) && !isNewGroundWater && Maps::Ground::doesTerrainImageIndexContainEmbeddedObjects( terrainImageIndex ) ) { // There cannot be extra objects under the roads and streams. _terrainImageIndex = Ground::getRandomTerrainImageIndex( Ground::getGroundByImageIndex( terrainImageIndex ), false ); return; } _terrainImageIndex = terrainImageIndex; } Heroes * Maps::Tiles::getHero() const { return MP2::OBJ_HERO == _mainObjectType && Heroes::isValidId( _occupantHeroId ) ? world.GetHeroes( _occupantHeroId ) : nullptr; } void Maps::Tiles::setHero( Heroes * hero ) { if ( hero ) { using HeroIDType = decltype( _occupantHeroId ); static_assert( std::is_same_v, "Type of heroID has been changed, check the logic below" ); hero->setObjectTypeUnderHero( _mainObjectType ); assert( hero->GetID() >= std::numeric_limits::min() && hero->GetID() < std::numeric_limits::max() ); _occupantHeroId = static_cast( hero->GetID() ); SetObject( MP2::OBJ_HERO ); } else { hero = getHero(); if ( hero ) { SetObject( hero->getObjectTypeUnderHero() ); hero->setObjectTypeUnderHero( MP2::OBJ_NONE ); } else { setAsEmpty(); } _occupantHeroId = Heroes::UNKNOWN; } } fheroes2::Point Maps::Tiles::GetCenter() const { return Maps::GetPoint( _index ); } MP2::MapObjectType Maps::Tiles::GetObject( bool ignoreObjectUnderHero /* true */ ) const { if ( !ignoreObjectUnderHero && MP2::OBJ_HERO == _mainObjectType ) { const Heroes * hero = getHero(); return hero ? hero->getObjectTypeUnderHero() : MP2::OBJ_NONE; } return _mainObjectType; } void Maps::Tiles::SetObject( const MP2::MapObjectType objectType ) { _mainObjectType = objectType; world.resetPathfinder(); } void Maps::Tiles::setBoat( const int direction, const int color ) { if ( _mainAddon._objectIcnType != MP2::OBJ_ICN_TYPE_UNKNOWN ) { // It is important to preserve the order of objects for rendering purposes. Therefore, the main object should go to the front of objects. _addonBottomLayer.emplace_front( _mainAddon ); } // If this assertion blows up then you are trying to put a boat on land! assert( isWater() ); SetObject( MP2::OBJ_BOAT ); _mainAddon._objectIcnType = MP2::OBJ_ICN_TYPE_BOAT32; switch ( direction ) { case Direction::TOP: _mainAddon._imageIndex = 0; break; case Direction::TOP_RIGHT: _mainAddon._imageIndex = 9; break; case Direction::RIGHT: _mainAddon._imageIndex = 18; break; case Direction::BOTTOM_RIGHT: _mainAddon._imageIndex = 27; break; case Direction::BOTTOM: _mainAddon._imageIndex = 36; break; // Left-side sprites have to be flipped, add 128 to index. case Direction::BOTTOM_LEFT: _mainAddon._imageIndex = 27 + 128; break; case Direction::LEFT: _mainAddon._imageIndex = 18 + 128; break; case Direction::TOP_LEFT: _mainAddon._imageIndex = 9 + 128; break; default: _mainAddon._imageIndex = 18; break; } _mainAddon._uid = getNewObjectUID(); using BoatOwnerColorType = decltype( _boatOwnerColor ); static_assert( std::is_same_v, "Type of _boatOwnerColor has been changed, check the logic below" ); assert( color >= std::numeric_limits::min() && color <= std::numeric_limits::max() ); _boatOwnerColor = static_cast( color ); } int Maps::Tiles::getBoatDirection() const { // Check if it really is a boat if ( _mainAddon._objectIcnType != MP2::OBJ_ICN_TYPE_BOAT32 ) return Direction::UNKNOWN; // Left-side sprites have to flipped, add 128 to index switch ( _mainAddon._imageIndex ) { case 0: return Direction::TOP; case 9: return Direction::TOP_RIGHT; case 18: return Direction::RIGHT; case 27: return Direction::BOTTOM_RIGHT; case 36: return Direction::BOTTOM; case 27 + 128: return Direction::BOTTOM_LEFT; case 18 + 128: return Direction::LEFT; case 9 + 128: return Direction::TOP_LEFT; default: break; } return Direction::UNKNOWN; } int Maps::Tiles::getOriginalPassability() const { const MP2::MapObjectType objectType = GetObject( false ); if ( MP2::isActionObject( objectType ) ) { return MP2::getActionObjectDirection( objectType ); } if ( _mainAddon._objectIcnType == MP2::OBJ_ICN_TYPE_UNKNOWN || _mainAddon.isPassabilityTransparent() || isShadow() ) { // No object exists. Make it fully passable. return DIRECTION_ALL; } if ( isValidReefsSprite( _mainAddon._objectIcnType, _mainAddon._imageIndex ) ) { return 0; } for ( const TilesAddon & addon : _addonBottomLayer ) { if ( isValidReefsSprite( addon._objectIcnType, addon._imageIndex ) ) { return 0; } } // Objects have fixed passability. return DIRECTION_CENTER_ROW | DIRECTION_BOTTOM_ROW; } void Maps::Tiles::setInitialPassability() { using TilePassableType = decltype( _tilePassabilityDirections ); static_assert( std::is_same_v, "Type of tilePassable has been changed, check the logic below" ); const int passability = getOriginalPassability(); assert( passability >= std::numeric_limits::min() && passability <= std::numeric_limits::max() ); _tilePassabilityDirections = static_cast( passability ); } void Maps::Tiles::updatePassability() { // Get object type but ignore heroes as they are "temporary" objects. const MP2::MapObjectType objectType = GetObject( false ); if ( !MP2::isActionObject( objectType ) && ( _mainAddon._objectIcnType != MP2::OBJ_ICN_TYPE_UNKNOWN ) && !_mainAddon.isPassabilityTransparent() && !isShadow() ) { // This is a non-action object. if ( !Maps::isValidDirection( _index, Direction::BOTTOM ) ) { // This object "touches" the bottom part of the map. Mark is as inaccessible. _tilePassabilityDirections = 0; return; } const Tiles & bottomTile = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::BOTTOM ) ); // If an object locates on land and the bottom tile is water mark the current tile as impassable. It's done for cases that a hero won't be able to // disembark on the tile. if ( !isWater() && bottomTile.isWater() ) { _tilePassabilityDirections = 0; return; } // If a bottom tile has the same object ID then this tile must be marked as inaccessible because it is a continuation of the same object. std::vector tileUIDs; // If this assertion blows up then the object is not set properly. An object must have a valid UID! assert( _mainAddon._uid != 0 ); tileUIDs.emplace_back( _mainAddon._uid ); for ( const TilesAddon & addon : _addonBottomLayer ) { if ( !addon.isPassabilityTransparent() ) { // If this assertion blows up then the object is not set properly. An object must have a valid UID! assert( addon._uid != 0 ); tileUIDs.emplace_back( addon._uid ); } } for ( const uint32_t objectId : tileUIDs ) { if ( bottomTile.doesObjectExist( objectId ) ) { _tilePassabilityDirections = 0; return; } } // Count how many objects are there excluding shadows, roads and river streams. const std::ptrdiff_t validBottomLayerObjects = std::count_if( _addonBottomLayer.begin(), _addonBottomLayer.end(), []( const TilesAddon & addon ) { if ( isAddonShadow( addon ) ) { return false; } return addon._objectIcnType != MP2::OBJ_ICN_TYPE_ROAD && addon._objectIcnType != MP2::OBJ_ICN_TYPE_STREAM; } ); const bool singleObjectTile = ( validBottomLayerObjects == 0 ) && _addonTopLayer.empty() && ( bottomTile._mainAddon._objectIcnType != _mainAddon._objectIcnType ); // TODO: we might need to simplify the logic below as singleObjectTile might cover most of it. if ( !singleObjectTile && !isDetachedObject() && !bottomTile._mainAddon.isPassabilityTransparent() && ( bottomTile._mainAddon._objectIcnType != MP2::OBJ_ICN_TYPE_UNKNOWN ) ) { const MP2::MapObjectType bottomTileObjectType = bottomTile.GetObject( false ); const MP2::MapObjectType correctedObjectType = MP2::getBaseActionObjectType( bottomTileObjectType ); if ( MP2::isActionObject( bottomTileObjectType ) ) { if ( ( MP2::getActionObjectDirection( bottomTileObjectType ) & Direction::TOP ) == 0 ) { if ( isShortObject( bottomTileObjectType ) ) { _tilePassabilityDirections &= ~Direction::BOTTOM; } else { _tilePassabilityDirections = 0; return; } } } else if ( bottomTile._mainObjectType != MP2::OBJ_NONE && correctedObjectType != bottomTileObjectType && MP2::isActionObject( correctedObjectType ) && isShortObject( correctedObjectType ) && ( bottomTile.getOriginalPassability() & Direction::TOP ) == 0 ) { _tilePassabilityDirections &= ~Direction::BOTTOM; } else if ( isShortObject( bottomTileObjectType ) || ( !bottomTile.containsAnyObjectIcnType( getValidObjectIcnTypes() ) && ( isCombinedObject( objectType ) || isCombinedObject( bottomTileObjectType ) ) ) ) { _tilePassabilityDirections &= ~Direction::BOTTOM; } else { _tilePassabilityDirections = 0; return; } } } // Left side. if ( ( _tilePassabilityDirections & Direction::TOP_LEFT ) && Maps::isValidDirection( _index, Direction::LEFT ) ) { const Tiles & leftTile = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::LEFT ) ); const bool leftTileTallObject = leftTile.isTallObject(); if ( leftTileTallObject && ( leftTile.getOriginalPassability() & Direction::TOP ) == 0 ) { _tilePassabilityDirections &= ~Direction::TOP_LEFT; } } // Right side. if ( ( _tilePassabilityDirections & Direction::TOP_RIGHT ) && Maps::isValidDirection( _index, Direction::RIGHT ) ) { const Tiles & rightTile = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::RIGHT ) ); const bool rightTileTallObject = rightTile.isTallObject(); if ( rightTileTallObject && ( rightTile.getOriginalPassability() & Direction::TOP ) == 0 ) { _tilePassabilityDirections &= ~Direction::TOP_RIGHT; } } } bool Maps::Tiles::doesObjectExist( const uint32_t uid ) const { if ( _mainAddon._uid == uid && !_mainAddon.isPassabilityTransparent() ) { return true; } return std::any_of( _addonBottomLayer.cbegin(), _addonBottomLayer.cend(), [uid]( const TilesAddon & addon ) { return addon._uid == uid && !addon.isPassabilityTransparent(); } ); } void Maps::Tiles::UpdateRegion( uint32_t newRegionID ) { if ( _tilePassabilityDirections ) { _region = newRegionID; } else { _region = REGION_NODE_BLOCKED; } } void Maps::Tiles::pushBottomLayerAddon( const MP2::mp2addon_t & ma ) { const MP2::ObjectIcnType objectIcnType = static_cast( ma.objectNameN1 >> 2 ); if ( objectIcnType == MP2::ObjectIcnType::OBJ_ICN_TYPE_UNKNOWN ) { // No object exist. return; } // In the original Editor the road bit is set even if no road exist. // It is important to verify the existence of a road without relying on this bit. if ( isSpriteRoad( objectIcnType, ma.bottomIcnImageIndex ) ) { _isTileMarkedAsRoad = true; } _addonBottomLayer.emplace_back( static_cast( ma.quantityN & 0x03 ), ma.level1ObjectUID, objectIcnType, ma.bottomIcnImageIndex ); } void Maps::Tiles::pushTopLayerAddon( const MP2::mp2addon_t & ma ) { const MP2::ObjectIcnType objectIcnType = static_cast( ma.objectNameN2 >> 2 ); if ( objectIcnType == MP2::ObjectIcnType::OBJ_ICN_TYPE_UNKNOWN ) { // No object exist. return; } // Top layer objects do not have any internal structure (layers) so all of them should have the same internal layer. // TODO: remove layer type for top layer objects. _addonTopLayer.emplace_back( OBJECT_LAYER, ma.level2ObjectUID, objectIcnType, ma.topIcnImageIndex ); } void Maps::Tiles::pushBottomLayerAddon( TilesAddon ta ) { if ( isSpriteRoad( ta._objectIcnType, ta._imageIndex ) ) { _isTileMarkedAsRoad = true; } _addonBottomLayer.emplace_back( ta ); } void Maps::Tiles::AddonsSort() { if ( _addonBottomLayer.empty() ) { // Nothing to sort. return; } // Push everything to the container and sort it by level. if ( _mainAddon._objectIcnType != MP2::OBJ_ICN_TYPE_UNKNOWN ) { _addonBottomLayer.emplace_front( _mainAddon ); } // Sort by internal layers. _addonBottomLayer.sort( []( const auto & left, const auto & right ) { return ( left._layerType > right._layerType ); } ); if ( !_addonBottomLayer.empty() ) { TilesAddon & highestPriorityAddon = _addonBottomLayer.back(); std::swap( highestPriorityAddon, _mainAddon ); // If this assertion blows up then you are not storing correct values for layer type! assert( _mainAddon._layerType <= TERRAIN_LAYER ); _addonBottomLayer.pop_back(); } // Top layer objects don't have any rendering priorities so they should be rendered first in queue first to render. } Maps::TilesAddon * Maps::Tiles::getBottomLayerAddon( const uint32_t uid ) { auto it = std::find_if( _addonBottomLayer.begin(), _addonBottomLayer.end(), [uid]( const TilesAddon & v ) { return v._uid == uid; } ); return it != _addonBottomLayer.end() ? &( *it ) : nullptr; } Maps::TilesAddon * Maps::Tiles::getTopLayerAddon( const uint32_t uid ) { auto it = std::find_if( _addonTopLayer.begin(), _addonTopLayer.end(), [uid]( const TilesAddon & v ) { return v._uid == uid; } ); return it != _addonTopLayer.end() ? &( *it ) : nullptr; } std::string Maps::Tiles::String() const { std::ostringstream os; const MP2::MapObjectType objectType = GetObject(); os << "******* Tile info *******" << std::endl << "Tile index : " << _index << ", " << "point: (" << GetCenter().x << ", " << GetCenter().y << ")" << std::endl << "MP2 object type : " << static_cast( objectType ) << " (" << MP2::StringObject( objectType ) << ")" << std::endl; getAddonInfo( _mainAddon, os ); os << "region : " << _region << std::endl << "ground : " << Ground::String( GetGround() ) << " (isRoad: " << _isTileMarkedAsRoad << ")" << std::endl << "ground img index: " << _terrainImageIndex << ", image flags: " << static_cast( _terrainFlags ) << std::endl << "passable from : " << ( _tilePassabilityDirections ? Direction::String( _tilePassabilityDirections ) : "nowhere" ) << std::endl << "metadata value 1: " << _metadata[0] << std::endl << "metadata value 2: " << _metadata[1] << std::endl << "metadata value 3: " << _metadata[2] << std::endl; if ( objectType == MP2::OBJ_BOAT ) os << "boat owner color: " << Color::String( _boatOwnerColor ) << std::endl; for ( const TilesAddon & addon : _addonBottomLayer ) { os << getAddonInfo( addon, 1 ); } for ( const TilesAddon & addon : _addonTopLayer ) { os << getAddonInfo( addon, 2 ); } os << "--- Extra information ---" << std::endl; switch ( objectType ) { case MP2::OBJ_RUINS: case MP2::OBJ_TREE_CITY: case MP2::OBJ_WAGON_CAMP: case MP2::OBJ_DESERT_TENT: case MP2::OBJ_TROLL_BRIDGE: case MP2::OBJ_DRAGON_CITY: case MP2::OBJ_CITY_OF_DEAD: case MP2::OBJ_WATCH_TOWER: case MP2::OBJ_EXCAVATION: case MP2::OBJ_CAVE: case MP2::OBJ_TREE_HOUSE: case MP2::OBJ_ARCHER_HOUSE: case MP2::OBJ_GOBLIN_HUT: case MP2::OBJ_DWARF_COTTAGE: case MP2::OBJ_HALFLING_HOLE: case MP2::OBJ_PEASANT_HUT: case MP2::OBJ_MONSTER: os << "monster count : " << getMonsterCountFromTile( *this ) << std::endl; break; case MP2::OBJ_HERO: { const Heroes * hero = getHero(); if ( hero ) os << hero->String(); break; } case MP2::OBJ_NON_ACTION_CASTLE: case MP2::OBJ_CASTLE: { const Castle * castle = world.getCastle( GetCenter() ); if ( castle ) os << castle->String(); break; } default: { const MapsIndexes & v = Maps::getMonstersProtectingTile( _index ); if ( !v.empty() ) { os << "protection : "; for ( const int32_t index : v ) { os << index << ", "; } os << std::endl; } break; } } if ( MP2::isCaptureObject( GetObject( false ) ) ) { const CapturedObject & co = world.GetCapturedObject( _index ); os << "capture color : " << Color::String( co.objcol.second ) << std::endl; if ( co.guardians.isValid() ) { os << "capture guard : " << co.guardians.GetName() << std::endl << "capture count : " << co.guardians.GetCount() << std::endl; } } os << "*************************" << std::endl; return os.str(); } void Maps::Tiles::FixObject() { if ( MP2::OBJ_NONE == _mainObjectType ) { if ( std::any_of( _addonBottomLayer.begin(), _addonBottomLayer.end(), isAddonArtifact ) ) SetObject( MP2::OBJ_ARTIFACT ); else if ( std::any_of( _addonBottomLayer.begin(), _addonBottomLayer.end(), isAddonResource ) ) SetObject( MP2::OBJ_RESOURCE ); } } bool Maps::Tiles::GoodForUltimateArtifact() const { if ( isWater() || !isPassableFrom( Direction::CENTER, false, true, 0 ) ) { return false; } if ( _mainAddon._objectIcnType != MP2::OBJ_ICN_TYPE_UNKNOWN && !isAddonShadow( _mainAddon ) ) { return false; } if ( static_cast( std::count_if( _addonBottomLayer.begin(), _addonBottomLayer.end(), isAddonShadow ) ) != _addonBottomLayer.size() ) { return false; } if ( static_cast( std::count_if( _addonTopLayer.begin(), _addonTopLayer.end(), isAddonShadow ) ) != _addonTopLayer.size() ) { return false; } return true; } bool Maps::Tiles::isPassabilityTransparent() const { for ( const TilesAddon & addon : _addonBottomLayer ) { if ( !addon.isPassabilityTransparent() ) { return false; } } return _mainAddon.isPassabilityTransparent(); } bool Maps::Tiles::isPassableFrom( const int direction, const bool fromWater, const bool skipFog, const int heroColor ) const { if ( !skipFog && isFog( heroColor ) ) { return false; } const bool tileIsWater = isWater(); // From the water we can get either to the coast tile or to the water tile (provided there is no boat on this tile). if ( fromWater && _mainObjectType != MP2::OBJ_COAST && ( !tileIsWater || _mainObjectType == MP2::OBJ_BOAT ) ) { return false; } // From the ground we can get to the water tile only if this tile contains a certain object. if ( !fromWater && tileIsWater && _mainObjectType != MP2::OBJ_SHIPWRECK && _mainObjectType != MP2::OBJ_HERO && _mainObjectType != MP2::OBJ_BOAT ) { return false; } // Tiles on which allied heroes are located are inaccessible if ( _mainObjectType == MP2::OBJ_HERO ) { const Heroes * hero = getHero(); assert( hero != nullptr ); if ( hero->GetColor() != heroColor && hero->isFriends( heroColor ) ) { return false; } } // Tiles on which the entrances to the allied castles are located are inaccessible if ( _mainObjectType == MP2::OBJ_CASTLE ) { const Castle * castle = world.getCastleEntrance( GetCenter() ); if ( castle && castle->GetColor() != heroColor && castle->isFriends( heroColor ) ) { return false; } } return ( direction & _tilePassabilityDirections ) != 0; } void Maps::Tiles::SetObjectPassable( bool pass ) { if ( GetObject( false ) == MP2::OBJ_TROLL_BRIDGE ) { if ( pass ) { _tilePassabilityDirections |= Direction::TOP_LEFT; } else { _tilePassabilityDirections &= ~Direction::TOP_LEFT; } } } bool Maps::Tiles::isStream() const { for ( const TilesAddon & addon : _addonBottomLayer ) { if ( addon._objectIcnType == MP2::OBJ_ICN_TYPE_STREAM || ( addon._objectIcnType == MP2::OBJ_ICN_TYPE_OBJNMUL2 && addon._imageIndex < 14 ) ) { return true; } } return _mainAddon._objectIcnType == MP2::OBJ_ICN_TYPE_STREAM || ( _mainAddon._objectIcnType == MP2::OBJ_ICN_TYPE_OBJNMUL2 && _mainAddon._imageIndex < 14 ); } bool Maps::Tiles::isShadow() const { return isAddonShadow( _mainAddon ) && _addonBottomLayer.size() == static_cast( std::count_if( _addonBottomLayer.begin(), _addonBottomLayer.end(), isAddonShadow ) ); } Maps::TilesAddon * Maps::Tiles::getAddonWithFlag( const uint32_t uid ) { const auto isFlag = [uid]( const TilesAddon & addon ) { return addon._uid == uid && addon._objectIcnType == MP2::OBJ_ICN_TYPE_FLAG32; }; auto iter = std::find_if( _addonBottomLayer.begin(), _addonBottomLayer.end(), isFlag ); if ( iter != _addonBottomLayer.end() ) { return &( *iter ); } iter = std::find_if( _addonTopLayer.begin(), _addonTopLayer.end(), isFlag ); if ( iter != _addonTopLayer.end() ) { return &( *iter ); } return nullptr; } void Maps::Tiles::setOwnershipFlag( const MP2::MapObjectType objectType, const int color ) { // All flags in FLAG32.ICN are actually the same except the fact of having different offset. // Set the default value for the UNUSED color. uint8_t objectSpriteIndex = 6; switch ( color ) { case Color::NONE: // No flag. Just ignore it. break; case Color::BLUE: objectSpriteIndex = 0; break; case Color::GREEN: objectSpriteIndex = 1; break; case Color::RED: objectSpriteIndex = 2; break; case Color::YELLOW: objectSpriteIndex = 3; break; case Color::ORANGE: objectSpriteIndex = 4; break; case Color::PURPLE: objectSpriteIndex = 5; break; case Color::UNUSED: // Neutral (gray) flag. Index '6' is already set. break; default: // Did you add a new color type? Add logic above! assert( 0 ); break; } switch ( objectType ) { case MP2::OBJ_MAGIC_GARDEN: objectSpriteIndex += 128 + 14; updateFlag( color, objectSpriteIndex, _mainAddon._uid, false ); objectSpriteIndex += 7; if ( Maps::isValidDirection( _index, Direction::RIGHT ) ) { Maps::Tiles & tile = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::RIGHT ) ); tile.updateFlag( color, objectSpriteIndex, _mainAddon._uid, false ); } break; case MP2::OBJ_WATER_WHEEL: case MP2::OBJ_MINE: objectSpriteIndex += 128 + 14; if ( Maps::isValidDirection( _index, Direction::TOP ) ) { Maps::Tiles & tile = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::TOP ) ); tile.updateFlag( color, objectSpriteIndex, _mainAddon._uid, true ); } objectSpriteIndex += 7; if ( Maps::isValidDirection( _index, Direction::TOP_RIGHT ) ) { Maps::Tiles & tile = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::TOP_RIGHT ) ); tile.updateFlag( color, objectSpriteIndex, _mainAddon._uid, true ); } break; case MP2::OBJ_WINDMILL: case MP2::OBJ_LIGHTHOUSE: objectSpriteIndex += 128 + 42; if ( Maps::isValidDirection( _index, Direction::LEFT ) ) { Maps::Tiles & tile = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::LEFT ) ); tile.updateFlag( color, objectSpriteIndex, _mainAddon._uid, false ); } objectSpriteIndex += 7; updateFlag( color, objectSpriteIndex, _mainAddon._uid, false ); break; case MP2::OBJ_ALCHEMIST_LAB: objectSpriteIndex += 21; if ( Maps::isValidDirection( _index, Direction::TOP ) ) { Maps::Tiles & tile = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::TOP ) ); tile.updateFlag( color, objectSpriteIndex, _mainAddon._uid, true ); } break; case MP2::OBJ_SAWMILL: objectSpriteIndex += 28; if ( Maps::isValidDirection( _index, Direction::TOP_RIGHT ) ) { Maps::Tiles & tile = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::TOP_RIGHT ) ); tile.updateFlag( color, objectSpriteIndex, _mainAddon._uid, true ); } break; case MP2::OBJ_CASTLE: objectSpriteIndex *= 2; if ( Maps::isValidDirection( _index, Direction::LEFT ) ) { Maps::Tiles & tile = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::LEFT ) ); tile.updateFlag( color, objectSpriteIndex, _mainAddon._uid, true ); } objectSpriteIndex += 1; if ( Maps::isValidDirection( _index, Direction::RIGHT ) ) { Maps::Tiles & tile = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::RIGHT ) ); tile.updateFlag( color, objectSpriteIndex, _mainAddon._uid, true ); } break; default: break; } } void Maps::Tiles::removeOwnershipFlag( const MP2::MapObjectType objectType ) { setOwnershipFlag( objectType, Color::NONE ); } void Maps::Tiles::updateFlag( const int color, const uint8_t objectSpriteIndex, const uint32_t uid, const bool setOnUpperLayer ) { // Flag deletion or installation must be done in relation to object UID as flag is attached to the object. if ( color == Color::NONE ) { const auto isFlag = [uid]( const TilesAddon & addon ) { return addon._uid == uid && addon._objectIcnType == MP2::OBJ_ICN_TYPE_FLAG32; }; _addonBottomLayer.remove_if( isFlag ); _addonTopLayer.remove_if( isFlag ); return; } TilesAddon * addon = getAddonWithFlag( uid ); if ( addon != nullptr ) { // Replace an existing flag. addon->_imageIndex = objectSpriteIndex; } else if ( setOnUpperLayer ) { _addonTopLayer.emplace_back( OBJECT_LAYER, uid, MP2::OBJ_ICN_TYPE_FLAG32, objectSpriteIndex ); } else { _addonBottomLayer.emplace_back( OBJECT_LAYER, uid, MP2::OBJ_ICN_TYPE_FLAG32, objectSpriteIndex ); } } void Maps::Tiles::_updateRoadFlag() { _isTileMarkedAsRoad = isSpriteRoad( _mainAddon._objectIcnType, _mainAddon._imageIndex ); if ( _isTileMarkedAsRoad ) { return; } for ( const TilesAddon & addon : _addonBottomLayer ) { if ( isSpriteRoad( addon._objectIcnType, addon._imageIndex ) ) { _isTileMarkedAsRoad = true; return; } } } void Maps::Tiles::fixTileObjectType( Tiles & tile ) { const MP2::MapObjectType originalObjectType = tile.GetObject( false ); // Left tile of a skeleton on Desert should be marked as non-action tile. if ( originalObjectType == MP2::OBJ_SKELETON && tile._mainAddon._objectIcnType == MP2::OBJ_ICN_TYPE_OBJNDSRT && tile._mainAddon._imageIndex == 83 ) { tile.SetObject( MP2::OBJ_NON_ACTION_SKELETON ); // There is no need to check the rest of things as we fixed this object. return; } // Oasis object has 2 top tiles being marked as part of bottom object layer while in reality they should be at the top level. if ( originalObjectType == MP2::OBJ_NON_ACTION_OASIS && tile._mainAddon._objectIcnType == MP2::OBJ_ICN_TYPE_OBJNDSRT && ( tile._mainAddon._imageIndex == 105 || tile._mainAddon._imageIndex == 106 ) ) { tile._addonTopLayer.emplace_back(); std::swap( tile._addonTopLayer.back(), tile._mainAddon ); return; } // Original Editor marks Reefs as Stones. We're fixing this issue by changing the type of the object without changing the content of a tile. // This is also required in order to properly calculate Reefs' passability. if ( originalObjectType == MP2::OBJ_ROCK && isValidReefsSprite( tile._mainAddon._objectIcnType, tile._mainAddon._imageIndex ) ) { tile.SetObject( MP2::OBJ_REEFS ); // There is no need to check the rest of things as we fixed this object. return; } // Some maps have water tiles with OBJ_COAST, it shouldn't be, replace OBJ_COAST with OBJ_NONE if ( originalObjectType == MP2::OBJ_COAST && tile.isWater() ) { Heroes * hero = tile.getHero(); if ( hero ) { hero->setObjectTypeUnderHero( MP2::OBJ_NONE ); } else { tile.SetObject( MP2::OBJ_NONE ); } // There is no need to check the rest of things as we fixed this object. return; } // On some maps (apparently created by some non-standard editors), the object type on tiles with random monsters does not match the index // of the monster placeholder sprite. While this engine looks at the object type when placing an actual monster on a tile, the original // HoMM2 apparently looks at the placeholder sprite, so we need to keep them in sync. if ( tile._mainAddon._objectIcnType == MP2::OBJ_ICN_TYPE_MONS32 ) { MP2::MapObjectType monsterObjectType = originalObjectType; const uint8_t originalObjectSpriteIndex = tile.GetObjectSpriteIndex(); switch ( originalObjectSpriteIndex ) { // Random monster placeholder "MON" case 66: monsterObjectType = MP2::OBJ_RANDOM_MONSTER; break; // Random monster placeholder "MON 1" case 67: monsterObjectType = MP2::OBJ_RANDOM_MONSTER_WEAK; break; // Random monster placeholder "MON 2" case 68: monsterObjectType = MP2::OBJ_RANDOM_MONSTER_MEDIUM; break; // Random monster placeholder "MON 3" case 69: monsterObjectType = MP2::OBJ_RANDOM_MONSTER_STRONG; break; // Random monster placeholder "MON 4" case 70: monsterObjectType = MP2::OBJ_RANDOM_MONSTER_VERY_STRONG; break; default: break; } if ( monsterObjectType != originalObjectType ) { tile.SetObject( monsterObjectType ); DEBUG_LOG( DBG_GAME, DBG_WARN, "Invalid object type index " << tile._index << ": type " << MP2::StringObject( originalObjectType ) << ", object sprite index " << static_cast( originalObjectSpriteIndex ) << ", corrected type " << MP2::StringObject( monsterObjectType ) ) // There is no need to check the rest of things as we fixed this object. return; } } // Fix The Price of Loyalty objects even if the map is The Succession Wars type. switch ( originalObjectType ) { case MP2::OBJ_NON_ACTION_EXPANSION_DWELLING: case MP2::OBJ_NON_ACTION_EXPANSION_OBJECT: case MP2::OBJ_EXPANSION_DWELLING: case MP2::OBJ_EXPANSION_OBJECT: { // The type of expansion action object or dwelling is stored in object metadata. // However, we just ignore it. MP2::MapObjectType objectType = getLoyaltyObject( tile._mainAddon._objectIcnType, tile._mainAddon._imageIndex ); if ( objectType != MP2::OBJ_NONE ) { tile.SetObject( objectType ); break; } // Add-ons of level 1 shouldn't even exist if no top object is present. However, let's play safe and verify it as well. for ( const TilesAddon & addon : tile._addonBottomLayer ) { objectType = getLoyaltyObject( addon._objectIcnType, addon._imageIndex ); if ( objectType != MP2::OBJ_NONE ) break; } if ( objectType != MP2::OBJ_NONE ) { tile.SetObject( objectType ); break; } for ( const TilesAddon & addon : tile._addonTopLayer ) { objectType = getLoyaltyObject( addon._objectIcnType, addon._imageIndex ); if ( objectType != MP2::OBJ_NONE ) break; } if ( objectType != MP2::OBJ_NONE ) { tile.SetObject( objectType ); break; } DEBUG_LOG( DBG_GAME, DBG_WARN, "Invalid object type index " << tile._index << ": type " << MP2::StringObject( originalObjectType ) << ", icn ID " << static_cast( tile._mainAddon._imageIndex ) ) break; } default: break; } } void Maps::Tiles::Remove( uint32_t uniqID ) { // TODO: this method must update the type of the main object _addonBottomLayer.remove_if( [uniqID]( const Maps::TilesAddon & v ) { return v._uid == uniqID; } ); _addonTopLayer.remove_if( [uniqID]( const Maps::TilesAddon & v ) { return v._uid == uniqID; } ); if ( _mainAddon._uid == uniqID ) { resetObjectSprite(); _mainAddon._uid = 0; } } void Maps::Tiles::removeObjects( const MP2::ObjectIcnType objectIcnType ) { _addonBottomLayer.remove_if( [objectIcnType]( const Maps::TilesAddon & addon ) { return addon._objectIcnType == objectIcnType; } ); _addonTopLayer.remove_if( [objectIcnType]( const Maps::TilesAddon & addon ) { return addon._objectIcnType == objectIcnType; } ); if ( _mainAddon._objectIcnType == objectIcnType ) { resetObjectSprite(); _mainAddon._uid = 0; } _updateRoadFlag(); } void Maps::Tiles::replaceObject( const uint32_t objectUid, const MP2::ObjectIcnType originalObjectIcnType, const MP2::ObjectIcnType newObjectIcnType, const uint8_t originalImageIndex, const uint8_t newImageIndex ) { // We can immediately return from the function as only one object per tile can have the same UID. for ( TilesAddon & addon : _addonBottomLayer ) { if ( addon._uid == objectUid && addon._objectIcnType == originalObjectIcnType && addon._imageIndex == originalImageIndex ) { addon._objectIcnType = newObjectIcnType; addon._imageIndex = newImageIndex; return; } } for ( TilesAddon & addon : _addonTopLayer ) { if ( addon._uid == objectUid && addon._objectIcnType == originalObjectIcnType && addon._imageIndex == originalImageIndex ) { addon._objectIcnType = newObjectIcnType; addon._imageIndex = newImageIndex; return; } } if ( _mainAddon._uid == objectUid && _mainAddon._objectIcnType == originalObjectIcnType && _mainAddon._imageIndex == originalImageIndex ) { _mainAddon._objectIcnType = newObjectIcnType; _mainAddon._imageIndex = newImageIndex; } } void Maps::Tiles::updateObjectImageIndex( const uint32_t objectUid, const MP2::ObjectIcnType objectIcnType, const int imageIndexOffset ) { // We can immediately return from the function as only one object per tile can have the same UID. for ( TilesAddon & addon : _addonBottomLayer ) { if ( addon._uid == objectUid && addon._objectIcnType == objectIcnType ) { assert( addon._imageIndex + imageIndexOffset >= 0 && addon._imageIndex + imageIndexOffset < 255 ); addon._imageIndex = static_cast( addon._imageIndex + imageIndexOffset ); return; } } for ( TilesAddon & addon : _addonTopLayer ) { if ( addon._uid == objectUid && addon._objectIcnType == objectIcnType ) { assert( addon._imageIndex + imageIndexOffset >= 0 && addon._imageIndex + imageIndexOffset < 255 ); addon._imageIndex = static_cast( addon._imageIndex + imageIndexOffset ); return; } } if ( _mainAddon._uid == objectUid && _mainAddon._objectIcnType == objectIcnType ) { assert( _mainAddon._imageIndex + imageIndexOffset >= 0 && _mainAddon._imageIndex + imageIndexOffset < 255 ); _mainAddon._imageIndex = static_cast( _mainAddon._imageIndex + imageIndexOffset ); } } void Maps::Tiles::ClearFog( const int colors ) { _fogColors &= ~colors; // The fog might be cleared even without the hero's movement - for example, the hero can gain a new level of Scouting // skill by picking up a Treasure Chest from a nearby tile or buying a map in a Magellan's Maps object using the space // bar button. Reset the pathfinder(s) to make the newly discovered tiles immediately available for this hero. world.resetPathfinder(); } void Maps::Tiles::updateTileObjectIcnIndex( Maps::Tiles & tile, const uint32_t uid, const uint8_t newIndex ) { Maps::TilesAddon * addon = tile.getBottomLayerAddon( uid ); if ( addon != nullptr ) { addon->_imageIndex = newIndex; } else if ( tile._mainAddon._uid == uid ) { tile._mainAddon._imageIndex = newIndex; } tile._updateRoadFlag(); } void Maps::Tiles::updateEmpty() { if ( _mainObjectType == MP2::OBJ_NONE ) { setAsEmpty(); } } void Maps::Tiles::setAsEmpty() { // If an object is removed we should validate if this tile a potential candidate to be a coast. // Check if this tile is not water and it has neighbouring water tiles. if ( isWater() ) { SetObject( MP2::OBJ_NONE ); return; } bool isCoast = false; const Indexes tileIndices = Maps::getAroundIndexes( _index, 1 ); for ( const int tileIndex : tileIndices ) { if ( tileIndex < 0 ) { // Invalid tile index. continue; } if ( world.GetTiles( tileIndex ).isWater() ) { isCoast = true; break; } } SetObject( isCoast ? MP2::OBJ_COAST : MP2::OBJ_NONE ); } uint32_t Maps::Tiles::getObjectIdByObjectIcnType( const MP2::ObjectIcnType objectIcnType ) const { if ( _mainAddon._objectIcnType == objectIcnType ) { return _mainAddon._uid; } for ( const TilesAddon & addon : _addonBottomLayer ) { if ( addon._objectIcnType == objectIcnType ) { return addon._uid; } } for ( const TilesAddon & addon : _addonTopLayer ) { if ( addon._objectIcnType == objectIcnType ) { return addon._uid; } } return 0; } std::vector Maps::Tiles::getValidObjectIcnTypes() const { std::vector objectIcnTypes; if ( _mainAddon._objectIcnType != MP2::OBJ_ICN_TYPE_UNKNOWN ) { objectIcnTypes.emplace_back( _mainAddon._objectIcnType ); } for ( const TilesAddon & addon : _addonBottomLayer ) { // If this assertion blows up then you put an empty object into an addon which makes no sense! assert( addon._objectIcnType != MP2::OBJ_ICN_TYPE_UNKNOWN ); objectIcnTypes.emplace_back( addon._objectIcnType ); } for ( const TilesAddon & addon : _addonTopLayer ) { // If this assertion blows up then you put an empty object into an addon which makes no sense! assert( addon._objectIcnType != MP2::OBJ_ICN_TYPE_UNKNOWN ); objectIcnTypes.emplace_back( addon._objectIcnType ); } return objectIcnTypes; } bool Maps::Tiles::containsAnyObjectIcnType( const std::vector & objectIcnTypes ) const { for ( const MP2::ObjectIcnType objectIcnType : objectIcnTypes ) { if ( _mainAddon._objectIcnType == objectIcnType ) { return true; } for ( const TilesAddon & addon : _addonBottomLayer ) { if ( addon._objectIcnType == objectIcnType ) { return true; } } for ( const TilesAddon & addon : _addonTopLayer ) { if ( addon._objectIcnType == objectIcnType ) { return true; } } } return false; } bool Maps::Tiles::containsSprite( const MP2::ObjectIcnType objectIcnType, const uint32_t imageIdx ) const { if ( _mainAddon._objectIcnType == objectIcnType && imageIdx == _mainAddon._imageIndex ) { return true; } if ( std::any_of( _addonBottomLayer.cbegin(), _addonBottomLayer.cend(), [objectIcnType, imageIdx]( const TilesAddon & addon ) { return addon._objectIcnType == objectIcnType && imageIdx == addon._imageIndex; } ) ) { return true; } return std::any_of( _addonTopLayer.cbegin(), _addonTopLayer.cend(), [objectIcnType, imageIdx]( const TilesAddon & addon ) { return addon._objectIcnType == objectIcnType && imageIdx == addon._imageIndex; } ); } bool Maps::Tiles::isTallObject() const { // TODO: possibly cache the output of the method as right now it's in average twice. if ( !Maps::isValidDirection( _index, Direction::TOP ) ) { // Nothing above so this object can't be tall. return false; } std::vector tileUIDs; if ( _mainAddon._objectIcnType != MP2::OBJ_ICN_TYPE_UNKNOWN && _mainAddon._uid != 0 && !_mainAddon.isPassabilityTransparent() ) { tileUIDs.emplace_back( _mainAddon._uid ); } for ( const TilesAddon & addon : _addonBottomLayer ) { if ( addon._uid != 0 && !addon.isPassabilityTransparent() ) { tileUIDs.emplace_back( addon._uid ); } } for ( const TilesAddon & addon : _addonTopLayer ) { if ( addon._uid != 0 && !addon.isPassabilityTransparent() ) { tileUIDs.emplace_back( addon._uid ); } } const Tiles & topTile = world.GetTiles( Maps::GetDirectionIndex( _index, Direction::TOP ) ); for ( const uint32_t tileUID : tileUIDs ) { if ( topTile._mainAddon._uid == tileUID && !isAddonShadow( topTile._mainAddon ) ) { return true; } for ( const TilesAddon & addon : topTile._addonBottomLayer ) { if ( addon._uid == tileUID && !isAddonShadow( addon ) ) { return true; } } for ( const TilesAddon & addon : topTile._addonTopLayer ) { if ( addon._uid == tileUID && !isAddonShadow( addon ) ) { return true; } } } return false; } int32_t Maps::Tiles::getIndexOfMainTile( const Maps::Tiles & tile ) { const MP2::MapObjectType objectType = tile.GetObject( false ); const MP2::MapObjectType correctedObjectType = MP2::getBaseActionObjectType( objectType ); if ( correctedObjectType == objectType ) { // Nothing to do. return tile._index; } assert( correctedObjectType > objectType ); // It's unknown whether object type belongs to bottom layer or ground. Create a list of UIDs starting from bottom layer. std::set uids; uids.insert( tile.GetObjectUID() ); for ( const TilesAddon & addon : tile.getBottomLayerAddons() ) { uids.insert( addon._uid ); } for ( const TilesAddon & addon : tile.getTopLayerAddons() ) { uids.insert( addon._uid ); } const int32_t tileIndex = tile.GetIndex(); const int32_t mapWidth = world.w(); // This is non-main tile of an action object. We have to find the main tile. // Since we don't want to care about the size of every object in the game we should find tiles in a certain radius. const int32_t radiusOfSearch = 3; // Main tile is usually at the bottom of the object so let's start from there. Also there are no objects having tiles below more than 1 row. for ( int32_t y = radiusOfSearch; y >= -1; --y ) { const int32_t offsetX = tileIndex + y * mapWidth; for ( int32_t x = -radiusOfSearch; x <= radiusOfSearch; ++x ) { const int32_t index = offsetX + x; if ( Maps::isValidAbsIndex( index ) ) { const Maps::Tiles & foundTile = world.GetTiles( index ); if ( foundTile.GetObject( false ) != correctedObjectType ) { continue; } if ( foundTile.GetObjectUID() != 0 && uids.count( foundTile.GetObjectUID() ) > 0 ) { return foundTile._index; } } } } // Most likely we have a broken object put by an editor. DEBUG_LOG( DBG_GAME, DBG_TRACE, "Tile " << tileIndex << " of type " << MP2::StringObject( objectType ) << " has no parent tile." ) return -1; } bool Maps::Tiles::isDetachedObject() const { const MP2::MapObjectType objectType = GetObject( false ); if ( isDetachedObjectType( objectType ) ) { return true; } const MP2::MapObjectType correctedObjectType = MP2::getBaseActionObjectType( objectType ); if ( !isDetachedObjectType( correctedObjectType ) ) { return false; } const int32_t mainTileIndex = Maps::Tiles::getIndexOfMainTile( *this ); if ( mainTileIndex == -1 ) { return false; } const uint32_t objectUID = world.GetTiles( mainTileIndex ).GetObjectUID(); if ( _mainAddon._uid == objectUID ) { return !_mainAddon.isPassabilityTransparent(); } for ( const TilesAddon & addon : _addonBottomLayer ) { if ( addon._uid == objectUID ) { return !addon.isPassabilityTransparent(); } } return false; } StreamBase & Maps::operator<<( StreamBase & msg, const TilesAddon & ta ) { using ObjectIcnTypeUnderlyingType = std::underlying_type_t; return msg << ta._layerType << ta._uid << static_cast( ta._objectIcnType ) << ta._imageIndex; } StreamBase & Maps::operator>>( StreamBase & msg, TilesAddon & ta ) { msg >> ta._layerType; static_assert( LAST_SUPPORTED_FORMAT_VERSION < FORMAT_VERSION_PRE2_1009_RELEASE, "Remove the logic below." ); if ( Game::GetVersionOfCurrentSaveFile() < FORMAT_VERSION_PRE2_1009_RELEASE ) { ta._layerType = ( ta._layerType & 0x03 ); } msg >> ta._uid; using ObjectIcnTypeUnderlyingType = std::underlying_type_t; static_assert( std::is_same_v, "Type of _objectIcnType has been changed, check the logic below" ); ObjectIcnTypeUnderlyingType objectIcnType = MP2::OBJ_ICN_TYPE_UNKNOWN; msg >> objectIcnType; ta._objectIcnType = static_cast( objectIcnType ); static_assert( LAST_SUPPORTED_FORMAT_VERSION < FORMAT_VERSION_PRE2_1009_RELEASE, "Remove the logic below." ); if ( Game::GetVersionOfCurrentSaveFile() < FORMAT_VERSION_PRE2_1009_RELEASE ) { bool temp; msg >> temp >> temp; } msg >> ta._imageIndex; return msg; } StreamBase & Maps::operator<<( StreamBase & msg, const Tiles & tile ) { using ObjectIcnTypeUnderlyingType = std::underlying_type_t; using MainObjectTypeUnderlyingType = std::underlying_type_t; // TODO: use operator<<() for _mainAddon. return msg << tile._index << tile._terrainImageIndex << tile._terrainFlags << tile._tilePassabilityDirections << tile._mainAddon._uid << static_cast( tile._mainAddon._objectIcnType ) << tile._mainAddon._imageIndex << static_cast( tile._mainObjectType ) << tile._fogColors << tile._metadata << tile._occupantHeroId << tile._isTileMarkedAsRoad << tile._addonBottomLayer << tile._addonTopLayer << tile._mainAddon._layerType << tile._boatOwnerColor; } StreamBase & Maps::operator>>( StreamBase & msg, Tiles & tile ) { msg >> tile._index >> tile._terrainImageIndex >> tile._terrainFlags >> tile._tilePassabilityDirections >> tile._mainAddon._uid; using ObjectIcnTypeUnderlyingType = std::underlying_type_t; static_assert( std::is_same_v, "Type of _objectIcnType has been changed, check the logic below" ); ObjectIcnTypeUnderlyingType objectIcnType = MP2::OBJ_ICN_TYPE_UNKNOWN; msg >> objectIcnType; tile._mainAddon._objectIcnType = static_cast( objectIcnType ); static_assert( LAST_SUPPORTED_FORMAT_VERSION < FORMAT_VERSION_PRE2_1009_RELEASE, "Remove the logic below." ); if ( Game::GetVersionOfCurrentSaveFile() < FORMAT_VERSION_PRE2_1009_RELEASE ) { bool temp; msg >> temp >> temp; } msg >> tile._mainAddon._imageIndex; using MainObjectTypeUnderlyingType = std::underlying_type_t; static_assert( std::is_same_v, "Type of _mainObjectType has been changed, check the logic below" ); MainObjectTypeUnderlyingType mainObjectType = MP2::OBJ_NONE; msg >> mainObjectType; tile._mainObjectType = static_cast( mainObjectType ); return msg >> tile._fogColors >> tile._metadata >> tile._occupantHeroId >> tile._isTileMarkedAsRoad >> tile._addonBottomLayer >> tile._addonTopLayer >> tile._mainAddon._layerType >> tile._boatOwnerColor; } fheroes2-1.0.12+dfsg/src/fheroes2/maps/maps_tiles.h000066400000000000000000000350231456075706000220210ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2TILES_H #define H2TILES_H #include #include #include #include #include #include #include "color.h" #include "direction.h" #include "ground.h" #include "heroes.h" #include "math_base.h" #include "mp2.h" #include "world_regions.h" class StreamBase; namespace Maps { enum ObjectLayerType : uint8_t { OBJECT_LAYER = 0, // main and action objects like mines, forest, mountains, castles and etc. BACKGROUND_LAYER = 1, // background objects like lakes or bushes. SHADOW_LAYER = 2, // shadows and some special objects like castle's entrance road. TERRAIN_LAYER = 3 // roads, water flaws and cracks. Essentially everything what is a part of terrain. }; struct TilesAddon { TilesAddon() = default; TilesAddon( const uint8_t layerType, const uint32_t uid, const MP2::ObjectIcnType objectIcnType, const uint8_t imageIndex ) : _uid( uid ) , _layerType( layerType ) , _objectIcnType( objectIcnType ) , _imageIndex( imageIndex ) { // Do nothing. } TilesAddon( const TilesAddon & ) = default; ~TilesAddon() = default; // Returns true if it can be passed be hero/boat: addon's layer type is SHADOW or TERRAIN. bool isPassabilityTransparent() const { return _layerType == SHADOW_LAYER || _layerType == TERRAIN_LAYER; } bool operator==( const TilesAddon & addon ) const { return ( _uid == addon._uid ) && ( _layerType == addon._layerType ) && ( _objectIcnType == addon._objectIcnType ) && ( _imageIndex == addon._imageIndex ); } // Unique identifier of an object. UID can be shared among multiple object parts if an object is bigger than 1 tile. uint32_t _uid{ 0 }; // Layer type shows how the object is rendered on Adventure Map. See ObjectLayerType enumeration. uint8_t _layerType{ OBJECT_LAYER }; // The type of object which correlates to ICN id. See MP2::getIcnIdFromObjectIcnType() function for more details. MP2::ObjectIcnType _objectIcnType{ MP2::OBJ_ICN_TYPE_UNKNOWN }; // Image index to define which part of the object is. This index corresponds to an index in ICN objects storing multiple sprites (images). uint8_t _imageIndex{ 255 }; }; class Tiles { public: Tiles() = default; bool operator==( const Tiles & tile ) const { return ( _addonBottomLayer == tile._addonBottomLayer ) && ( _addonTopLayer == tile._addonTopLayer ) && ( _index == tile._index ) && ( _terrainImageIndex == tile._terrainImageIndex ) && ( _terrainFlags == tile._terrainFlags ) && ( _mainAddon == tile._mainAddon ) && ( _mainObjectType == tile._mainObjectType ) && ( _metadata == tile._metadata ) && ( _tilePassabilityDirections == tile._tilePassabilityDirections ) && ( _isTileMarkedAsRoad == tile._isTileMarkedAsRoad ) && ( _occupantHeroId == tile._occupantHeroId ); } bool operator!=( const Tiles & tile ) const { return !operator==( tile ); } void Init( int32_t index, const MP2::mp2tile_t & mp2 ); void setIndex( const int32_t index ) { _index = index; } int32_t GetIndex() const { return _index; } fheroes2::Point GetCenter() const; MP2::MapObjectType GetObject( bool ignoreObjectUnderHero = true ) const; MP2::ObjectIcnType getObjectIcnType() const { return _mainAddon._objectIcnType; } void setObjectIcnType( const MP2::ObjectIcnType type ) { _mainAddon._objectIcnType = type; } uint8_t GetObjectSpriteIndex() const { return _mainAddon._imageIndex; } void setObjectSpriteIndex( const uint8_t index ) { _mainAddon._imageIndex = index; } uint32_t GetObjectUID() const { return _mainAddon._uid; } void setObjectUID( const uint32_t uid ) { _mainAddon._uid = uid; } uint8_t getLayerType() const { return _mainAddon._layerType; } uint16_t GetPassable() const { return _tilePassabilityDirections; } void resetPassability() { _tilePassabilityDirections = DIRECTION_ALL; } int GetGround() const { return Ground::getGroundByImageIndex( _terrainImageIndex ); } bool isWater() const { return Ground::getGroundByImageIndex( _terrainImageIndex ) == Ground::WATER; } bool isSameMainObject( const MP2::MapObjectType objectType ) const { return objectType == _mainObjectType; } // Returns true if tile's main and bottom layer addons do not contain any objects: layer type is SHADOW or TERRAIN. bool isPassabilityTransparent() const; // Checks whether it is possible to move into this tile from the specified direction bool isPassableFrom( const int direction ) const { return ( direction & _tilePassabilityDirections ) != 0; } // Checks whether it is possible to move into this tile from the specified direction under the specified conditions bool isPassableFrom( const int direction, const bool fromWater, const bool skipFog, const int heroColor ) const; // Checks whether it is possible to exit this tile in the specified direction bool isPassableTo( const int direction ) const { return ( direction & _tilePassabilityDirections ) != 0; } bool isRoad() const { return _isTileMarkedAsRoad || _mainObjectType == MP2::OBJ_CASTLE; } bool isStream() const; bool isShadow() const; bool GoodForUltimateArtifact() const; TilesAddon * getBottomLayerAddon( const uint32_t uid ); TilesAddon * getTopLayerAddon( const uint32_t uid ); void SetObject( const MP2::MapObjectType objectType ); void resetBoatOwnerColor() { _boatOwnerColor = Color::NONE; } int getBoatOwnerColor() const { return _boatOwnerColor; } void setBoat( const int direction, const int color ); int getBoatDirection() const; void resetObjectSprite() { _mainAddon._objectIcnType = MP2::OBJ_ICN_TYPE_UNKNOWN; _mainAddon._imageIndex = 255; } void FixObject(); uint32_t GetRegion() const { return _region; } void UpdateRegion( uint32_t newRegionID ); // Set initial passability based on information read from mp2 and addon structures. void setInitialPassability(); // Update passability based on neighbours around. void updatePassability(); void setOwnershipFlag( const MP2::MapObjectType objectType, const int color ); void removeOwnershipFlag( const MP2::MapObjectType objectType ); // Return fog direction of tile. A tile without fog returns "Direction::UNKNOWN". uint16_t getFogDirection() const { return _fogDirection; } void pushBottomLayerAddon( const MP2::mp2addon_t & ma ); void pushBottomLayerAddon( TilesAddon ta ); void pushTopLayerAddon( const MP2::mp2addon_t & ma ); void pushTopLayerAddon( TilesAddon ta ) { _addonTopLayer.emplace_back( ta ); } const std::list & getBottomLayerAddons() const { return _addonBottomLayer; } std::list & getBottomLayerAddons() { return _addonBottomLayer; } const std::list & getTopLayerAddons() const { return _addonTopLayer; } void moveMainAddonToBottomLayer() { if ( _mainAddon._objectIcnType != MP2::OBJ_ICN_TYPE_UNKNOWN ) { _addonBottomLayer.emplace_back( _mainAddon ); } } void AddonsSort(); void Remove( uint32_t uniqID ); // Use to remove object by ICN type only from this tile. Should be used only for 1 tile size objects and roads or streams. void removeObjects( const MP2::ObjectIcnType objectIcnType ); void updateObjectImageIndex( const uint32_t objectUid, const MP2::ObjectIcnType objectIcnType, const int imageIndexOffset ); void replaceObject( const uint32_t objectUid, const MP2::ObjectIcnType originalObjectIcnType, const MP2::ObjectIcnType newObjectIcnType, const uint8_t originalImageIndex, const uint8_t newImageIndex ); std::string String() const; bool isFog( const int colors ) const { // colors may be the union friends return ( _fogColors & colors ) == colors; } void ClearFog( const int colors ); void SetObjectPassable( bool pass ); const std::array & metadata() const { return _metadata; } std::array & metadata() { return _metadata; } uint8_t getTerrainFlags() const { return _terrainFlags; } uint16_t getTerrainImageIndex() const { return _terrainImageIndex; } void setTerrain( const uint16_t terrainImageIndex, const bool horizontalFlip, const bool verticalFlip ); Heroes * getHero() const; void setHero( Heroes * hero ); // If tile is empty (MP2::OBJ_NONE) then verify whether it is a coast and update the tile if needed. void updateEmpty(); // Set tile to coast MP2::OBJ_COAST) if it's near water or to empty (MP2::OBJ_NONE) void setAsEmpty(); uint32_t getObjectIdByObjectIcnType( const MP2::ObjectIcnType objectIcnType ) const; std::vector getValidObjectIcnTypes() const; bool containsAnyObjectIcnType( const std::vector & objectIcnTypes ) const; bool containsSprite( const MP2::ObjectIcnType objectIcnType, const uint32_t imageIdx ) const; // Do NOT call this method directly!!! void setFogDirection( const uint16_t fogDirection ) { _fogDirection = fogDirection; } // Some tiles have incorrect object type. This is due to original Editor issues. static void fixTileObjectType( Tiles & tile ); static int32_t getIndexOfMainTile( const Maps::Tiles & tile ); void swap( TilesAddon & addon ) noexcept { std::swap( addon, _mainAddon ); } // Update tile or bottom layer object image index. static void updateTileObjectIcnIndex( Maps::Tiles & tile, const uint32_t uid, const uint8_t newIndex ); private: TilesAddon * getAddonWithFlag( const uint32_t uid ); // Set or remove a flag which belongs to UID of the object. void updateFlag( const int color, const uint8_t objectSpriteIndex, const uint32_t uid, const bool setOnUpperLayer ); void _updateRoadFlag(); bool isTallObject() const; bool isDetachedObject() const; int getOriginalPassability() const; bool doesObjectExist( const uint32_t uid ) const; friend StreamBase & operator<<( StreamBase &, const Tiles & ); friend StreamBase & operator>>( StreamBase &, Tiles & ); // The following members are used in the Editor and in the game. TilesAddon _mainAddon; std::list _addonBottomLayer; std::list _addonTopLayer; int32_t _index{ 0 }; uint16_t _terrainImageIndex{ 0 }; uint8_t _terrainFlags{ 0 }; MP2::MapObjectType _mainObjectType{ MP2::OBJ_NONE }; std::array _metadata{ 0 }; uint16_t _tilePassabilityDirections{ DIRECTION_ALL }; bool _isTileMarkedAsRoad{ false }; uint8_t _occupantHeroId{ Heroes::UNKNOWN }; // The following members are only used in the game. uint8_t _fogColors{ Color::ALL }; // Heroes can only summon neutral empty boats or empty boats belonging to their kingdom. uint8_t _boatOwnerColor{ Color::NONE }; // Fog direction to render fog in Game Area. uint16_t _fogDirection{ DIRECTION_ALL }; // This field does not persist in savegame. uint32_t _region{ REGION_NODE_BLOCKED }; }; StreamBase & operator<<( StreamBase & msg, const TilesAddon & ta ); StreamBase & operator<<( StreamBase & msg, const Tiles & tile ); StreamBase & operator>>( StreamBase & msg, TilesAddon & ta ); StreamBase & operator>>( StreamBase & msg, Tiles & tile ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/maps/maps_tiles_helper.cpp000066400000000000000000004151141456075706000237160ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "maps_tiles_helper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "army.h" #include "army_troop.h" #include "artifact.h" #include "castle.h" #include "color.h" #include "direction.h" #include "ground.h" #include "logging.h" #include "map_object_info.h" #include "maps.h" #include "maps_tiles.h" #include "math_base.h" #include "monster.h" #include "mp2.h" #include "profit.h" #include "race.h" #include "rand.h" #include "resource.h" #include "skill.h" #include "spell.h" #include "tools.h" #include "week.h" #include "world.h" #include "world_object_uid.h" namespace { void updateMonsterPopulationOnTile( Maps::Tiles & tile ) { const Troop & troop = getTroopFromTile( tile ); const uint32_t troopCount = troop.GetCount(); if ( troopCount == 0 ) { Maps::setMonsterCountOnTile( tile, troop.GetRNDSize() ); } else { const uint32_t bonusUnit = ( Rand::Get( 1, 7 ) <= ( troopCount % 7 ) ) ? 1 : 0; Maps::setMonsterCountOnTile( tile, troopCount * 8 / 7 + bonusUnit ); } } void updateRandomResource( Maps::Tiles & tile ) { assert( tile.GetObject() == MP2::OBJ_RANDOM_RESOURCE ); tile.SetObject( MP2::OBJ_RESOURCE ); const uint8_t resourceSprite = Resource::GetIndexSprite( Resource::Rand( true ) ); uint32_t uidResource = tile.getObjectIdByObjectIcnType( MP2::OBJ_ICN_TYPE_OBJNRSRC ); if ( uidResource == 0 ) { uidResource = tile.GetObjectUID(); } Maps::Tiles::updateTileObjectIcnIndex( tile, uidResource, resourceSprite ); // Replace shadow of the resource. if ( Maps::isValidDirection( tile.GetIndex(), Direction::LEFT ) ) { assert( resourceSprite > 0 ); Maps::Tiles::updateTileObjectIcnIndex( world.GetTiles( Maps::GetDirectionIndex( tile.GetIndex(), Direction::LEFT ) ), uidResource, resourceSprite - 1 ); } } void updateRandomArtifact( Maps::Tiles & tile ) { Artifact art; switch ( tile.GetObject() ) { case MP2::OBJ_RANDOM_ARTIFACT: art = Artifact::Rand( Artifact::ART_LEVEL_ALL_NORMAL ); break; case MP2::OBJ_RANDOM_ARTIFACT_TREASURE: art = Artifact::Rand( Artifact::ART_LEVEL_TREASURE ); break; case MP2::OBJ_RANDOM_ARTIFACT_MINOR: art = Artifact::Rand( Artifact::ART_LEVEL_MINOR ); break; case MP2::OBJ_RANDOM_ARTIFACT_MAJOR: art = Artifact::Rand( Artifact::ART_LEVEL_MAJOR ); break; default: // Did you add another random artifact type? Add the logic above! assert( 0 ); return; } if ( !art.isValid() ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Failed to set an artifact over a random artifact on tile " << tile.GetIndex() ) resetObjectMetadata( tile ); tile.setAsEmpty(); return; } tile.SetObject( MP2::OBJ_ARTIFACT ); uint32_t uidArtifact = tile.getObjectIdByObjectIcnType( MP2::OBJ_ICN_TYPE_OBJNARTI ); if ( uidArtifact == 0 ) { uidArtifact = tile.GetObjectUID(); } static_assert( std::is_same_v, "Type of updateTileObjectIcnIndex() has been changed, check the logic below" ); // Please refer to ICN::OBJNARTI for artifact images. Since in the original game artifact UID start from 0 we have to deduct 1 from the current artifact ID. const uint32_t artSpriteIndex = ( art.GetID() - 1 ) * 2 + 1; assert( artSpriteIndex > std::numeric_limits::min() && artSpriteIndex <= std::numeric_limits::max() ); Maps::Tiles::updateTileObjectIcnIndex( tile, uidArtifact, static_cast( artSpriteIndex ) ); // replace artifact shadow if ( Maps::isValidDirection( tile.GetIndex(), Direction::LEFT ) ) { Maps::Tiles::updateTileObjectIcnIndex( world.GetTiles( Maps::GetDirectionIndex( tile.GetIndex(), Direction::LEFT ) ), uidArtifact, static_cast( artSpriteIndex - 1 ) ); } } void updateRandomMonster( Maps::Tiles & tile ) { Monster mons; switch ( tile.GetObject() ) { case MP2::OBJ_RANDOM_MONSTER: mons = Monster::Rand( Monster::LevelType::LEVEL_ANY ); break; case MP2::OBJ_RANDOM_MONSTER_WEAK: mons = Monster::Rand( Monster::LevelType::LEVEL_1 ); break; case MP2::OBJ_RANDOM_MONSTER_MEDIUM: mons = Monster::Rand( Monster::LevelType::LEVEL_2 ); break; case MP2::OBJ_RANDOM_MONSTER_STRONG: mons = Monster::Rand( Monster::LevelType::LEVEL_3 ); break; case MP2::OBJ_RANDOM_MONSTER_VERY_STRONG: mons = Monster::Rand( Monster::LevelType::LEVEL_4 ); break; default: // Did you add another random monster type? Add the logic above! assert( 0 ); break; } if ( !mons.isValid() ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Failed to set a monster over a random monster on tile " << tile.GetIndex() ) resetObjectMetadata( tile ); tile.setAsEmpty(); return; } tile.SetObject( MP2::OBJ_MONSTER ); using TileImageIndexType = decltype( tile.GetObjectSpriteIndex() ); static_assert( std::is_same_v, "Type of GetObjectSpriteIndex() has been changed, check the logic below" ); assert( mons.GetID() > std::numeric_limits::min() && mons.GetID() <= std::numeric_limits::max() ); tile.setObjectSpriteIndex( static_cast( mons.GetID() - 1 ) ); // ICN::MONS32 starts from PEASANT } void removeJailSprite( Maps::Tiles & tile ) { assert( tile.GetObject() == MP2::OBJ_JAIL ); // remove left sprite if ( Maps::isValidDirection( tile.GetIndex(), Direction::LEFT ) ) { const int32_t left = Maps::GetDirectionIndex( tile.GetIndex(), Direction::LEFT ); world.GetTiles( left ).Remove( tile.GetObjectUID() ); // remove left left sprite if ( Maps::isValidDirection( left, Direction::LEFT ) ) world.GetTiles( Maps::GetDirectionIndex( left, Direction::LEFT ) ).Remove( tile.GetObjectUID() ); } // remove top sprite if ( Maps::isValidDirection( tile.GetIndex(), Direction::TOP ) ) { const int32_t top = Maps::GetDirectionIndex( tile.GetIndex(), Direction::TOP ); Maps::Tiles & topTile = world.GetTiles( top ); topTile.Remove( tile.GetObjectUID() ); if ( topTile.GetObject() == MP2::OBJ_JAIL && topTile.GetObjectUID() == 0 ) { // Since the main object was removed from the tile is it safe to mark it as empty, even if other non-main objects exist. // This is not ideal but it doesn't break things. topTile.setAsEmpty(); topTile.FixObject(); } // remove top left sprite if ( Maps::isValidDirection( top, Direction::LEFT ) ) { Maps::Tiles & leftTile = world.GetTiles( Maps::GetDirectionIndex( top, Direction::LEFT ) ); leftTile.Remove( tile.GetObjectUID() ); if ( leftTile.GetObject() == MP2::OBJ_JAIL && leftTile.GetObjectUID() == 0 ) { // Since the main object was removed from the tile is it safe to mark it as empty, even if other non-main objects exist. // This is not ideal but it doesn't break things. leftTile.setAsEmpty(); leftTile.FixObject(); } } } tile.Remove( tile.GetObjectUID() ); } // Returns the direction vector bits from 'centerTileIndex' where the ground is 'groundId'. int getGroundDirecton( const int32_t centerTileIndex, const int groundId ) { if ( centerTileIndex < 0 || centerTileIndex >= world.w() * world.h() ) { // Center tile is out of the map borders. return Direction::UNKNOWN; } const fheroes2::Point centerTile = Maps::GetPoint( centerTileIndex ); const fheroes2::Point lastWorldTile = { world.w() - 1, world.h() - 1 }; int groundDirection = ( world.GetTiles( centerTileIndex ).GetGround() == groundId ) ? Direction::CENTER : 0; for ( const int & direction : Direction::All() ) { // We do not let 'tilePosition' to get out of the world borders, meaning that beyond the borders is the same tile type as the nearby one on the map. fheroes2::Point tilePosition = Maps::getDirectionPoint( centerTile, direction ); tilePosition.x = std::min( lastWorldTile.x, std::max( 0, tilePosition.x ) ); tilePosition.y = std::min( lastWorldTile.y, std::max( 0, tilePosition.y ) ); if ( world.GetTiles( tilePosition.x, tilePosition.y ).GetGround() == groundId ) { groundDirection |= direction; } } return groundDirection; } // Returns the direction vector bits from 'centerTileIndex' where '_tileIsRoad' bit is set for the tiles around. int getRoadDirecton( const Maps::Tiles & tile ) { const int32_t centerTileIndex = tile.GetIndex(); int roadDirection = ( tile.isRoad() ) ? Direction::CENTER : 0; const Maps::Indexes around = Maps::getAroundIndexes( centerTileIndex ); for ( const int32_t tileIndex : around ) { if ( world.GetTiles( tileIndex ).isRoad() ) { roadDirection |= Maps::GetDirection( centerTileIndex, tileIndex ); } } return roadDirection; } // Returns the direction vector bits from 'centerTileIndex' to the around tiles with streams. int getStreamDirecton( const Maps::Tiles & tile ) { const int32_t centerTileIndex = tile.GetIndex(); int streamDirection = ( tile.isStream() ) ? Direction::CENTER : 0; // For streams we can check only the next four directions. for ( const int direction : { Direction::LEFT, Direction::TOP, Direction::RIGHT, Direction::BOTTOM } ) { if ( Maps::isValidDirection( centerTileIndex, direction ) && world.GetTiles( Maps::GetDirectionIndex( centerTileIndex, direction ) ).isStream() ) { streamDirection |= direction; } } return streamDirection; } bool hasBits( const int value, const int bits ) { return ( value & bits ) == bits; } bool hasNoBits( const int value, const int bits ) { return ( value & bits ) == 0; } // Returns true if terrain transition on tile was properly set or it is not needed. bool setTerrainBoundaries( const int groundDirection, const int beachDirection, const int32_t tileId, const uint16_t imageOffset ) { if ( groundDirection == DIRECTION_ALL ) { // No transition is needed. return true; } Maps::Tiles & tile = world.GetTiles( tileId ); if ( groundDirection == ( Direction::TOP_RIGHT | Direction::TOP | DIRECTION_BOTTOM_ROW | DIRECTION_CENTER_ROW ) ) { // All directions without the top-left corner. uint16_t imageIndex = imageOffset + 12U; imageIndex += hasNoBits( beachDirection, Direction::TOP_LEFT ) ? 0U : 16U; imageIndex += static_cast( Rand::Get( 3 ) ); tile.setTerrain( imageIndex, true, false ); return true; } if ( groundDirection == ( Direction::TOP_LEFT | Direction::TOP | DIRECTION_BOTTOM_ROW | DIRECTION_CENTER_ROW ) ) { // All directions without the top-right corner. uint16_t imageIndex = imageOffset + 12U; imageIndex += hasNoBits( beachDirection, Direction::TOP_RIGHT ) ? 0U : 16U; imageIndex += static_cast( Rand::Get( 3 ) ); tile.setTerrain( imageIndex, false, false ); return true; } if ( groundDirection == ( Direction::BOTTOM_LEFT | Direction::BOTTOM | DIRECTION_TOP_ROW | DIRECTION_CENTER_ROW ) ) { // All directions without the bottom-right corner. uint16_t imageIndex = imageOffset + 12U; imageIndex += hasNoBits( beachDirection, Direction::BOTTOM_RIGHT ) ? 0U : 16U; imageIndex += static_cast( Rand::Get( 3 ) ); tile.setTerrain( imageIndex, false, true ); return true; } if ( groundDirection == ( Direction::BOTTOM_RIGHT | Direction::BOTTOM | DIRECTION_TOP_ROW | DIRECTION_CENTER_ROW ) ) { // All directions without the bottom-left corner. uint16_t imageIndex = imageOffset + 12U; imageIndex += hasNoBits( beachDirection, Direction::BOTTOM_LEFT ) ? 0U : 16U; imageIndex += static_cast( Rand::Get( 3 ) ); tile.setTerrain( imageIndex, true, true ); return true; } if ( hasBits( groundDirection, Direction::LEFT | Direction::TOP | Direction::BOTTOM ) && ( ( tile.GetGround() != Maps::Ground::WATER ) || hasBits( groundDirection, Direction::TOP_LEFT | Direction::BOTTOM_LEFT ) ) ) { // There is no ground direction to the right. // NOTICE: Initially the whole 'DIRECTION_LEFT_COL' should have direction bits. // If ground is not Water we do not check TOP_LEFT and BOTTOM_LEFT as there are no tile images for that cases. // The absence of this "corner" transition is barely noticeable. // TODO: Design tile images for these cases. if ( hasBits( beachDirection, Direction::RIGHT ) ) { // To the right there is a beach (or beach transition to the water). tile.setTerrain( imageOffset + 8U + 16U + static_cast( Rand::Get( 3 ) ), false, false ); return true; } if ( hasNoBits( groundDirection, Direction::RIGHT ) ) { // There is no beach and no current ground to the right. if ( hasBits( beachDirection, Direction::TOP_RIGHT ) ) { // Top-right is beach transition and right is dirt transition. tile.setTerrain( imageOffset + 35U, false, false ); } else if ( hasBits( beachDirection, Direction::BOTTOM_RIGHT ) ) { // Bottom-right is beach transition and right is dirt transition. tile.setTerrain( imageOffset + 35U, false, true ); } else { // Transition to the dirt to the right. tile.setTerrain( imageOffset + 8U + static_cast( Rand::Get( 3 ) ), false, false ); } return true; } } if ( hasBits( groundDirection, Direction::RIGHT | Direction::TOP | Direction::BOTTOM ) && ( ( tile.GetGround() != Maps::Ground::WATER ) || hasBits( groundDirection, Direction::TOP_RIGHT | Direction::BOTTOM_RIGHT ) ) ) { // There is no ground direction to the left. // NOTICE: Initially the whole 'DIRECTION_RIGHT_COL' should have direction bits. // If ground is not Water we do not check TOP_RIGHT and BOTTOM_RIGHT as there are no tile images for that cases. // The absence of this "corner" transition is barely noticeable. // TODO: Design tile images for these cases. if ( hasBits( beachDirection, Direction::LEFT ) ) { // To the left there is a beach (or beach transition to the water). tile.setTerrain( imageOffset + 8U + 16U + static_cast( Rand::Get( 3 ) ), true, false ); return true; } if ( hasNoBits( groundDirection, Direction::LEFT ) ) { // There is no beach and no current ground to the left. if ( hasBits( beachDirection, Direction::TOP_LEFT ) ) { // Top-left is beach transition and left is dirt transition. tile.setTerrain( imageOffset + 35U, true, false ); } else if ( hasBits( beachDirection, Direction::BOTTOM_LEFT ) ) { // Bottom-left is beach transition and left is dirt transition. tile.setTerrain( imageOffset + 35U, true, true ); } else { // Transition to the dirt to the left. tile.setTerrain( imageOffset + 8U + static_cast( Rand::Get( 3 ) ), true, false ); } return true; } } if ( hasBits( groundDirection, Direction::BOTTOM | Direction::LEFT | Direction::RIGHT ) && ( ( tile.GetGround() != Maps::Ground::WATER ) || hasBits( groundDirection, Direction::BOTTOM_LEFT | Direction::BOTTOM_RIGHT ) ) ) { // There is no ground direction to the top. // NOTICE: Initially the whole 'DIRECTION_BOTTOM_ROW' should have direction bits. // If ground is not Water we do not check BOTTOM_LEFT and BOTTOM_RIGHT as there are no tile images for that cases. // The absence of this "corner" transition is barely noticeable. // TODO: Design tile images for these cases. if ( hasBits( beachDirection, Direction::TOP ) ) { // To the top there is a beach (or beach transition to the water). tile.setTerrain( imageOffset + 16U + static_cast( Rand::Get( 3 ) ), false, false ); return true; } if ( hasNoBits( groundDirection, Direction::TOP ) ) { // There is no beach and no current ground to the top. if ( hasBits( beachDirection, Direction::TOP_RIGHT ) ) { // Top-right is beach transition and top is dirt transition. tile.setTerrain( imageOffset + 34U, false, false ); } else if ( hasBits( beachDirection, Direction::TOP_LEFT ) ) { // Top-left is beach transition and top is dirt transition. tile.setTerrain( imageOffset + 34U, true, false ); } else { // Transition to the dirt to the top. tile.setTerrain( imageOffset + static_cast( Rand::Get( 3 ) ), false, false ); } return true; } } if ( hasBits( groundDirection, Direction::TOP | Direction::LEFT | Direction::RIGHT ) && ( ( tile.GetGround() != Maps::Ground::WATER ) || hasBits( groundDirection, Direction::TOP_LEFT | Direction::TOP_RIGHT ) ) ) { // There is no ground direction to the bottom. // NOTICE: Initially the whole 'DIRECTION_TOP_ROW' should have direction bits. // If ground is not Water we do not check TOP_LEFT and TOP_RIGHT as there are no tile images for that cases. // The absence of this "corner" transition is barely noticeable. // TODO: Design tile images for these cases. if ( hasBits( beachDirection, Direction::BOTTOM ) ) { // To the bottom there is a beach (or beach transition to the water). tile.setTerrain( imageOffset + 16U + static_cast( Rand::Get( 3 ) ), false, true ); return true; } if ( hasNoBits( groundDirection, Direction::BOTTOM ) ) { // There is no beach and no current ground to the bottom. if ( hasBits( beachDirection, Direction::BOTTOM_RIGHT ) ) { // Bottom-right is beach transition and bottom is dirt transition. tile.setTerrain( imageOffset + 34U, false, true ); } else if ( hasBits( beachDirection, Direction::BOTTOM_LEFT ) ) { // Bottom-left is beach transition and bottom is dirt transition. tile.setTerrain( imageOffset + 34U, true, true ); } else { // Transition to the dirt to the bottom. tile.setTerrain( imageOffset + static_cast( Rand::Get( 3 ) ), false, true ); } return true; } } if ( hasBits( groundDirection, Direction::RIGHT | Direction::BOTTOM_RIGHT | Direction::BOTTOM ) ) { // There is no ground direction to the top, top-left and left tiles. if ( hasBits( beachDirection, Direction::TOP | Direction::LEFT ) || hasBits( beachDirection, Direction::TOP | Direction::BOTTOM_LEFT ) || hasBits( beachDirection, Direction::TOP_RIGHT | Direction::LEFT ) ) { // To the top and left there is a beach/water. tile.setTerrain( imageOffset + 4U + 16U + static_cast( Rand::Get( 3 ) ), true, false ); return true; } if ( hasNoBits( groundDirection, Direction::TOP | Direction::LEFT ) ) { // There is no beach and no current ground to the top and left. if ( hasBits( beachDirection, Direction::TOP ) ) { // Top is beach transition and left is dirt transition. tile.setTerrain( imageOffset + 36U, true, false ); } else if ( hasBits( beachDirection, Direction::LEFT ) ) { // Left is beach transition and top is dirt transition. tile.setTerrain( imageOffset + 37U, true, false ); } else if ( hasBits( beachDirection, Direction::TOP_RIGHT ) ) { // Top-right is beach transition and left is dirt transition. tile.setTerrain( imageOffset + 33U, true, false ); } else if ( hasBits( beachDirection, Direction::BOTTOM_LEFT ) ) { // Bottom-left is beach transition and left is dirt transition. tile.setTerrain( imageOffset + 32U, true, false ); } else { // Transition to the dirt to the top and left. tile.setTerrain( imageOffset + 4U + static_cast( Rand::Get( 3 ) ), true, false ); } return true; } } if ( hasBits( groundDirection, Direction::LEFT | Direction::BOTTOM_LEFT | Direction::BOTTOM ) ) { // There is no ground direction to the top, top-right and right tiles. if ( hasBits( beachDirection, Direction::TOP | Direction::RIGHT ) || hasBits( beachDirection, Direction::TOP | Direction::BOTTOM_RIGHT ) || hasBits( beachDirection, Direction::TOP_LEFT | Direction::RIGHT ) ) { // To the top and right there is a beach/water. tile.setTerrain( imageOffset + 4U + 16U + static_cast( Rand::Get( 3 ) ), false, false ); return true; } if ( hasNoBits( groundDirection, Direction::TOP | Direction::RIGHT ) ) { // There is no beach and no current ground to the top and right. if ( hasBits( beachDirection, Direction::TOP ) ) { // Top is beach transition and right is dirt transition. tile.setTerrain( imageOffset + 36U, false, false ); } else if ( hasBits( beachDirection, Direction::RIGHT ) ) { // Right is beach transition and top is dirt transition. tile.setTerrain( imageOffset + 37U, false, false ); } else if ( hasBits( beachDirection, Direction::TOP_LEFT ) ) { // Top-left is beach transition and right is dirt transition. tile.setTerrain( imageOffset + 33U, false, false ); } else if ( hasBits( beachDirection, Direction::BOTTOM_RIGHT ) ) { // Bottom-right is beach transition and top is dirt transition. tile.setTerrain( imageOffset + 32U, false, false ); } else { // Transition to the dirt to the top and right. tile.setTerrain( imageOffset + 4U + static_cast( Rand::Get( 3 ) ), false, false ); } return true; } } if ( hasBits( groundDirection, Direction::TOP | Direction::TOP_LEFT | Direction::LEFT ) ) { // There is no ground direction to the right, bottom-right and bottom tiles. if ( hasBits( beachDirection, Direction::RIGHT | Direction::BOTTOM ) || hasBits( beachDirection, Direction::RIGHT | Direction::BOTTOM_LEFT ) || hasBits( beachDirection, Direction::TOP_RIGHT | Direction::BOTTOM ) ) { // To the bottom and right there is a beach/water. tile.setTerrain( imageOffset + 4U + 16U + static_cast( Rand::Get( 3 ) ), false, true ); return true; } if ( hasNoBits( groundDirection, Direction::RIGHT | Direction::BOTTOM ) ) { // There is no beach and no current ground to the bottom and right. if ( hasBits( beachDirection, Direction::BOTTOM ) ) { // Bottom is beach transition and right is dirt transition. tile.setTerrain( imageOffset + 36U, false, true ); } else if ( hasBits( beachDirection, Direction::RIGHT ) ) { // Right is beach transition and bottom is dirt transition. tile.setTerrain( imageOffset + 37U, false, true ); } else if ( hasBits( beachDirection, Direction::BOTTOM_LEFT ) ) { // Bottom-left is beach transition and right is dirt transition. tile.setTerrain( imageOffset + 33U, false, true ); } else if ( hasBits( beachDirection, Direction::TOP_RIGHT ) ) { // Top-right is beach transition and bottom is dirt transition. tile.setTerrain( imageOffset + 32U, false, true ); } else { // Transition to the dirt to the bottom and right. tile.setTerrain( imageOffset + 4U + static_cast( Rand::Get( 3 ) ), false, true ); } return true; } } if ( hasBits( groundDirection, Direction::TOP | Direction::TOP_RIGHT | Direction::RIGHT ) ) { // There is no ground direction to the left, bottom-left and bottom tiles. if ( hasBits( beachDirection, Direction::LEFT | Direction::BOTTOM ) || hasBits( beachDirection, Direction::LEFT | Direction::BOTTOM_RIGHT ) || hasBits( beachDirection, Direction::TOP_LEFT | Direction::BOTTOM ) ) { // To the bottom and left there is a beach/water. Or a narrow path to the other land. tile.setTerrain( imageOffset + 4U + 16U + static_cast( Rand::Get( 3 ) ), true, true ); return true; } if ( hasNoBits( groundDirection, Direction::LEFT | Direction::BOTTOM ) ) { // There is no beach and no current ground to the bottom and left. if ( hasBits( beachDirection, Direction::BOTTOM ) ) { // Bottom is beach transition and left is dirt transition. tile.setTerrain( imageOffset + 36U, true, true ); } else if ( hasBits( beachDirection, Direction::LEFT ) ) { // Left is beach transition and bottom is dirt transition. tile.setTerrain( imageOffset + 37U, true, true ); } else if ( hasBits( beachDirection, Direction::BOTTOM_RIGHT ) ) { // Bottom-right is beach transition and left is dirt transition. tile.setTerrain( imageOffset + 33U, true, true ); } else if ( hasBits( beachDirection, Direction::TOP_LEFT ) ) { // Top-left is beach transition and bottom is dirt transition. tile.setTerrain( imageOffset + 32U, true, true ); } else { // Transition to the dirt to the bottom and left. tile.setTerrain( imageOffset + 4U + static_cast( Rand::Get( 3 ) ), true, true ); } return true; } } if ( hasBits( groundDirection, Direction::TOP | Direction::RIGHT | Direction::BOTTOM | Direction::LEFT ) ) { const int ground = tile.GetGround(); if ( ground != Maps::Ground::WATER ) { // More than one corner needs ground transition. // For these cases there is no extra tile image, but for now we can leave a tile with ground without transition as it is barely noticeable. // TODO: Design tile images for these cases. tile.setTerrain( Maps::Ground::getRandomTerrainImageIndex( ground, true ), false, false ); return true; } } // This terrain cannot be properly connected with the nearby terrains. There are no such ground images. DEBUG_LOG( DBG_DEVEL, DBG_WARN, "No proper ground transition found for " << Maps::Ground::String( Maps::Ground::getGroundByImageIndex( imageOffset ) ) << " at " << tileId % world.w() << ',' << tileId / world.w() << " (" << tileId << ").\nDirections: " << Direction::String( groundDirection ) ) return false; } // Returns true if terrain transition was set or it is not needed. bool updateTerrainTransitionOnTile( const int32_t tileId ) { const int ground = world.GetTiles( tileId ).GetGround(); if ( ground == Maps::Ground::BEACH ) { // Beach tile images do not have transition with the other terrains. return true; } // Check the tiles around for the need of ground transition. // Dirt has transitions only with Water and Beach, and these "Beach transitions" have image index offsets like "Dirt transitions" for all other terrains. const int tileGroundDirection = ( ground == Maps::Ground::DIRT ) ? ( DIRECTION_ALL - ( getGroundDirecton( tileId, Maps::Ground::WATER ) | getGroundDirecton( tileId, Maps::Ground::BEACH ) ) ) : ( getGroundDirecton( tileId, ground ) | Direction::CENTER ); if ( tileGroundDirection == DIRECTION_ALL ) { // Current tile does not need a transition because there is no other terrain nearby. Maps::Tiles & tile = world.GetTiles( tileId ); if ( Maps::Ground::isTerrainTransitionImage( tile.getTerrainImageIndex() ) ) { // We change image with the transition to original terrain image without transition. tile.setTerrain( Maps::Ground::getRandomTerrainImageIndex( ground, true ), false, false ); } return true; } switch ( ground ) { case Maps::Ground::WATER: case Maps::Ground::DIRT: // Water has only "Beach transition" to all other terrains. // Dirt has only "Beach transition" only with Water and/or Beach. // TODO: Set waves on the water for 3 tiles from the ground with the wave direction to the center of the ground. return setTerrainBoundaries( tileGroundDirection, 0, tileId, Maps::Ground::getTerrainStartImageIndex( ground ) ); case Maps::Ground::GRASS: case Maps::Ground::SNOW: case Maps::Ground::SWAMP: case Maps::Ground::LAVA: case Maps::Ground::DESERT: case Maps::Ground::WASTELAND: { // The transition to the Beach terrain is rendered when the near tile ground is Water or Beach. const int beachDirection = getGroundDirecton( tileId, Maps::Ground::WATER ) | getGroundDirecton( tileId, Maps::Ground::BEACH ); return setTerrainBoundaries( tileGroundDirection, beachDirection, tileId, Maps::Ground::getTerrainStartImageIndex( ground ) ); } default: // Have you added a new ground? Add the logic above! assert( 0 ); return false; } } void updateTerrainTransitionOnArea( const int newGroundId, const int32_t tileStart, const int32_t tileEnd, const int32_t tileStep ) { for ( int32_t tileId = tileStart; tileId <= tileEnd; tileId += tileStep ) { if ( updateTerrainTransitionOnTile( tileId ) ) { // The terrain transition was correctly set or transition was not needed. continue; } // Try to change the ground type to one of the others. // TODO: Change this algorithm to a more proper one. E.g. remember the previous ground and try to UNDO it here. const int groundOnTile = world.GetTiles( tileId ).GetGround(); DEBUG_LOG( DBG_DEVEL, DBG_WARN, "Ground " << Maps::Ground::String( groundOnTile ) << " at " << tileId % world.w() << ',' << tileId / world.w() << " (" << tileId << ") should be replaced by some other one." ) std::vector newGrounds; if ( groundOnTile != newGroundId ) { // Outside of filled area we can try to place the ground of this area. newGrounds.push_back( newGroundId ); } bool isWater = ( groundOnTile == Maps::Ground::WATER ); const Maps::Indexes around = Maps::getAroundIndexes( tileId ); // Get ground types from all tiles around to try them. for ( const int32_t index : around ) { const int32_t ground = world.GetTiles( index ).GetGround(); if ( ground != groundOnTile && std::find( newGrounds.begin(), newGrounds.end(), ground ) == newGrounds.end() ) { newGrounds.push_back( ground ); } isWater = isWater || ( ground == Maps::Ground::WATER ); } if ( isWater ) { // As a last chance, we can try to place Beach to make a path on the water because Beach doesn't need to transition to the water. if ( std::find( newGrounds.begin(), newGrounds.end(), Maps::Ground::BEACH ) == newGrounds.end() ) { newGrounds.push_back( Maps::Ground::BEACH ); } } else { // As a last chance, we can try to connect terrains by placing Dirt, because Dirt doesn't need to transition to other non-water terrains. if ( std::find( newGrounds.begin(), newGrounds.end(), Maps::Ground::DIRT ) == newGrounds.end() ) { newGrounds.push_back( Maps::Ground::DIRT ); } } bool needRevert = true; for ( const int newGround : newGrounds ) { DEBUG_LOG( DBG_DEVEL, DBG_WARN, "Trying ground " << Maps::Ground::String( newGround ) << " at " << tileId % world.w() << ',' << tileId / world.w() << " (" << tileId << ")." ) world.GetTiles( tileId ).setTerrain( Maps::Ground::getRandomTerrainImageIndex( newGround, true ), false, false ); if ( !updateTerrainTransitionOnTile( tileId ) ) { // The ground image has not been set properly. We move on to the next type of the ground. continue; } DEBUG_LOG( DBG_DEVEL, DBG_WARN, "Ground " << Maps::Ground::String( newGround ) << " was properly set to " << tileId % world.w() << ',' << tileId / world.w() << " (" << tileId << ")." ) // The ground on the tile has been changed, so we need to update the transitions on all the tiles around. for ( const int32_t index : around ) { if ( !updateTerrainTransitionOnTile( index ) ) { // TODO: Find a better solution without using recursions. In example, undo the tiles in 1 tile radius. DEBUG_LOG( DBG_DEVEL, DBG_WARN, "Recursive call for tile at " << tileId % world.w() << ',' << tileId / world.w() << " (" << tileId << ")." ) updateTerrainTransitionOnArea( newGroundId, index, index, 1 ); } } needRevert = false; break; } // If all ground replacements fail we revert the ground change to the initial ground type. if ( needRevert && !newGrounds.empty() ) { world.GetTiles( tileId ).setTerrain( Maps::Ground::getRandomTerrainImageIndex( groundOnTile, true ), false, false ); DEBUG_LOG( DBG_DEVEL, DBG_WARN, "Reverting ground to " << Maps::Ground::String( groundOnTile ) << " at " << tileId % world.w() << ',' << tileId / world.w() << " (" << tileId << ")." ) } } } void updateTerrainTransitionOnAreaBoundaries( const int groundId, const int32_t startX, const int32_t endX, const int32_t startY, const int32_t endY ) { const int32_t mapWidth = world.w(); const int32_t mapHeight = world.h(); // First we update the boundaries inside the filled area. updateTerrainTransitionOnArea( groundId, startX + mapWidth * startY, endX + mapWidth * startY, 1 ); if ( startY != endY ) { updateTerrainTransitionOnArea( groundId, startX + mapWidth * endY, endX + mapWidth * endY, 1 ); if ( endY - startY > 1 ) { updateTerrainTransitionOnArea( groundId, startX + mapWidth * ( startY + 1 ), startX + mapWidth * ( endY - 1 ), mapWidth ); if ( startX != endX ) { updateTerrainTransitionOnArea( groundId, endX + mapWidth * ( startY + 1 ), endX + mapWidth * ( endY - 1 ), mapWidth ); } } } // Then we update the boundaries outside the filled area, excluding the corners. if ( startY > 0 ) { const int32_t tileOffset = mapWidth * ( startY - 1 ); updateTerrainTransitionOnArea( groundId, startX + tileOffset, endX + tileOffset, 1 ); } if ( endY < mapHeight - 1 ) { const int32_t tileOffset = mapWidth * ( endY + 1 ); updateTerrainTransitionOnArea( groundId, startX + tileOffset, endX + tileOffset, 1 ); } if ( startX > 0 ) { const int32_t tileOffset = startX - 1; updateTerrainTransitionOnArea( groundId, tileOffset + mapWidth * startY, tileOffset + mapWidth * endY, mapWidth ); } if ( endX < mapWidth - 1 ) { const int32_t tileOffset = endX + 1; updateTerrainTransitionOnArea( groundId, tileOffset + mapWidth * startY, tileOffset + mapWidth * endY, mapWidth ); } // Update the corners outside of filled area. if ( startX > 0 && startY > 0 ) { const int32_t tileId = startX - 1 + mapWidth * ( startY - 1 ); updateTerrainTransitionOnArea( groundId, tileId, tileId, 1 ); } if ( startY > 0 && endX < mapWidth - 1 ) { const int32_t tileId = endX + 1 + mapWidth * ( startY - 1 ); updateTerrainTransitionOnArea( groundId, tileId, tileId, 1 ); } if ( startX > 0 && endY < mapHeight - 1 ) { const int32_t tileId = startX - 1 + mapWidth * ( endY + 1 ); updateTerrainTransitionOnArea( groundId, tileId, tileId, 1 ); } if ( endX < mapWidth - 1 && endY < mapHeight - 1 ) { const int32_t tileId = endX + 1 + mapWidth * ( endY + 1 ); updateTerrainTransitionOnArea( groundId, tileId, tileId, 1 ); } } uint8_t getRoadImageForTile( const Maps::Tiles & tile, const int roadDirection ) { // To place some roads we need to check not only the road directions around this tile, but also the road ICN index at the nearby tile. auto checkRoadIcnIndex = []( const int32_t tileIndex, const std::vector & roadIcnIndexes ) { for ( const Maps::TilesAddon & addon : world.GetTiles( tileIndex ).getBottomLayerAddons() ) { if ( addon._objectIcnType == MP2::OBJ_ICN_TYPE_ROAD ) { return std::any_of( roadIcnIndexes.begin(), roadIcnIndexes.end(), [&addon]( const uint8_t index ) { return addon._imageIndex == index; } ); } } return false; }; const int32_t tileIndex = tile.GetIndex(); if ( hasNoBits( roadDirection, Direction::CENTER ) ) { if ( hasBits( roadDirection, Direction::TOP ) && hasNoBits( roadDirection, Direction::TOP_LEFT ) ) { // We can do this without 'isValidDirection()' check because we have Direction::TOP. const int32_t upperTileIndex = tileIndex - world.w(); if ( checkRoadIcnIndex( upperTileIndex, { 7, 17, 20, 22, 24, 29 } ) ) { return 8U; } } if ( hasBits( roadDirection, Direction::TOP ) && hasNoBits( roadDirection, Direction::TOP_RIGHT ) ) { // We can do this without 'isValidDirection()' check because we have Direction::TOP. const int32_t upperTileIndex = tileIndex - world.w(); if ( checkRoadIcnIndex( upperTileIndex, { 16, 18, 19, 23, 25, 30 } ) ) { return 15U; } } if ( hasBits( roadDirection, Direction::TOP ) && ( hasBits( roadDirection, Direction::TOP_LEFT ) || hasBits( roadDirection, Direction::TOP_RIGHT ) || hasBits( roadDirection, Direction::LEFT | Direction::RIGHT ) ) ) { // We can do this without 'isValidDirection()' check because we have Direction::TOP. const int32_t upperTileIndex = tileIndex - world.w(); if ( checkRoadIcnIndex( upperTileIndex, { 2, 3, 21, 28 } ) ) { return Rand::Get( 1 ) ? 1U : 27U; } } if ( hasBits( roadDirection, Direction::BOTTOM | Direction::RIGHT ) && hasNoBits( roadDirection, Direction::TOP | Direction::LEFT ) ) { // We can do this without 'isValidDirection()' check because we have Direction::BOTTOM. const int32_t lowerTileIndex = tileIndex + world.w(); if ( checkRoadIcnIndex( lowerTileIndex, { 8, 9, 18, 20, 30 } ) ) { return Rand::Get( 1 ) ? 22U : 24U; } } if ( hasBits( roadDirection, Direction::BOTTOM | Direction::LEFT ) && hasNoBits( roadDirection, Direction::TOP | Direction::RIGHT ) ) { // We can do this without 'isValidDirection()' check because we have Direction::BOTTOM. const int32_t lowerTileIndex = tileIndex + world.w(); if ( checkRoadIcnIndex( lowerTileIndex, { 12, 15, 17, 19, 29 } ) ) { return Rand::Get( 1 ) ? 23U : 25U; } } // The next 4 conditions are to end the horizontal roads. if ( hasBits( roadDirection, Direction::LEFT ) && hasNoBits( roadDirection, Direction::TOP ) && checkRoadIcnIndex( tileIndex - 1, { 2, 21, 28 } ) ) { return Rand::Get( 1 ) ? 23U : 25U; } if ( hasBits( roadDirection, Direction::RIGHT ) && hasNoBits( roadDirection, Direction::TOP ) && checkRoadIcnIndex( tileIndex + 1, { 2, 21, 28 } ) ) { return Rand::Get( 1 ) ? 22U : 24U; } if ( hasBits( roadDirection, Direction::TOP_LEFT ) && hasNoBits( roadDirection, Direction::TOP ) && checkRoadIcnIndex( tileIndex - 1, { 1, 4, 21, 27 } ) ) { return 15U; } if ( hasBits( roadDirection, Direction::TOP_RIGHT ) && hasNoBits( roadDirection, Direction::TOP ) && checkRoadIcnIndex( tileIndex + 1, { 1, 4, 21, 27 } ) ) { return 8U; } // This tile should not have a road image. return 255U; } // The rest checks are made for the tile with the road on it: it has Direction::CENTER. if ( hasBits( roadDirection, Direction::TOP | DIRECTION_CENTER_ROW ) && ( hasBits( roadDirection, Direction::TOP_LEFT ) || hasBits( roadDirection, Direction::TOP_RIGHT ) ) ) { // = - horizontal road in this and in the upper tile. return 21U; } if ( ( ( ( hasBits( roadDirection, Direction::BOTTOM_RIGHT ) || hasBits( roadDirection, Direction::TOP_LEFT ) ) && hasNoBits( roadDirection, Direction::RIGHT ) ) || hasBits( roadDirection, Direction::RIGHT | Direction::TOP_LEFT ) ) && hasNoBits( roadDirection, Direction::TOP | Direction::BOTTOM | Direction::LEFT | Direction::TOP_RIGHT | Direction::BOTTOM_LEFT ) ) { // \ - diagonal road from top-left to bottom-right. return Rand::Get( 1 ) ? 17U : 29U; } if ( ( ( ( hasBits( roadDirection, Direction::BOTTOM_LEFT ) || hasBits( roadDirection, Direction::TOP_RIGHT ) ) && hasNoBits( roadDirection, Direction::LEFT ) ) || hasBits( roadDirection, Direction::LEFT | Direction::TOP_RIGHT ) ) && hasNoBits( roadDirection, Direction::TOP | Direction::RIGHT | Direction::BOTTOM | Direction::TOP_LEFT | Direction::BOTTOM_RIGHT ) ) { // / - diagonal road from top-right to bottom-left. return Rand::Get( 1 ) ? 18U : 30U; } if ( hasBits( roadDirection, Direction::TOP ) && ( hasBits( roadDirection, Direction::LEFT | Direction::RIGHT ) || hasBits( roadDirection, Direction::BOTTOM_LEFT | Direction::RIGHT ) || hasBits( roadDirection, Direction::LEFT | Direction::BOTTOM_RIGHT ) || ( hasBits( roadDirection, Direction::BOTTOM_LEFT | Direction::BOTTOM_RIGHT ) && hasNoBits( roadDirection, Direction::BOTTOM ) ) ) && hasNoBits( roadDirection, Direction::TOP_LEFT | Direction::TOP_RIGHT ) ) { // _|_ - cross. return 3U; } if ( hasBits( roadDirection, Direction::TOP ) && ( hasBits( roadDirection, Direction::TOP_LEFT | Direction::TOP_RIGHT ) || ( checkRoadIcnIndex( tileIndex - world.w(), { 2, 28 } ) ) ) && hasNoBits( roadDirection, Direction::LEFT | Direction::RIGHT ) ) { // T - cross. Also used for 90 degrees turn from the bottom to the left/right. return 4U; } if ( hasBits( roadDirection, Direction::TOP | Direction::TOP_RIGHT ) && hasNoBits( roadDirection, Direction::TOP_LEFT | Direction::RIGHT | Direction::LEFT ) ) { // Vertical road and branch to the right in the upper tile. return 5U; } if ( hasBits( roadDirection, Direction::TOP | Direction::RIGHT | Direction::BOTTOM ) && hasNoBits( roadDirection, Direction::TOP_RIGHT | Direction::LEFT ) ) { // L - cross. return 6U; } if ( hasBits( roadDirection, Direction::TOP ) && ( hasBits( roadDirection, Direction::RIGHT ) || hasBits( roadDirection, Direction::BOTTOM_RIGHT ) ) && hasNoBits( roadDirection, Direction::BOTTOM | Direction::LEFT ) ) { // Road turn from the top tile to the right tile. return 7U; } if ( hasBits( roadDirection, Direction::TOP_RIGHT | Direction::BOTTOM ) && hasNoBits( roadDirection, Direction::TOP | Direction::TOP_LEFT | Direction::LEFT | Direction::RIGHT ) ) { // Road turn from the bottom tile to the right tile. return 9U; } if ( hasBits( roadDirection, Direction::TOP_LEFT | Direction::BOTTOM ) && hasNoBits( roadDirection, Direction::TOP | Direction::TOP_RIGHT | Direction::RIGHT | Direction::LEFT ) ) { // Road turn from the bottom tile to the left tile. return 12U; } if ( hasBits( roadDirection, Direction::TOP | Direction::TOP_LEFT ) && hasNoBits( roadDirection, Direction::TOP_RIGHT | Direction::RIGHT | Direction::LEFT ) ) { // Vertical road and branch to the left in the upper tile. return 13U; } if ( hasBits( roadDirection, Direction::TOP | Direction::LEFT | Direction::BOTTOM ) && hasNoBits( roadDirection, Direction::TOP_LEFT | Direction::RIGHT ) ) { // _| - cross. return 14U; } if ( hasBits( roadDirection, Direction::TOP ) && ( hasBits( roadDirection, Direction::LEFT ) || hasBits( roadDirection, Direction::BOTTOM_LEFT ) ) && hasNoBits( roadDirection, Direction::BOTTOM | Direction::RIGHT ) ) { // Road turn from the top tile to the left tile. return 16U; } if ( hasBits( roadDirection, Direction::TOP_LEFT ) && ( hasBits( roadDirection, Direction::LEFT ) || hasBits( roadDirection, Direction::BOTTOM_LEFT ) ) && hasNoBits( roadDirection, DIRECTION_RIGHT_COL ) && !checkRoadIcnIndex( tileIndex - 1, { 0, 3, 6, 7, 14, 16, 26 } ) ) { // ) - road. return 19U; } if ( hasBits( roadDirection, Direction::TOP_RIGHT ) && ( hasBits( roadDirection, Direction::RIGHT ) || hasBits( roadDirection, Direction::BOTTOM_RIGHT ) ) && hasNoBits( roadDirection, DIRECTION_LEFT_COL ) && !checkRoadIcnIndex( tileIndex + 1, { 0, 3, 6, 7, 14, 16, 26 } ) ) { // ( - road. return 20U; } if ( ( hasBits( roadDirection, Direction::LEFT ) || hasBits( roadDirection, Direction::RIGHT ) || ( hasBits( roadDirection, Direction::BOTTOM_RIGHT | Direction::BOTTOM_LEFT ) && hasNoBits( roadDirection, Direction::BOTTOM ) ) ) && hasNoBits( roadDirection, Direction::TOP ) ) { // _ - horizontal road. return Rand::Get( 1 ) ? 2U : 28U; } if ( hasNoBits( roadDirection, Direction::LEFT | Direction::TOP_LEFT | Direction::TOP_RIGHT | Direction::RIGHT ) ) { // | - vertical road. return Rand::Get( 1 ) ? 0U : 26U; } // We have not found the appropriate road image and return the value for the incorrect image index. DEBUG_LOG( DBG_DEVEL, DBG_WARN, "No proper road image found for tile " << tileIndex << " with road directions: " << Direction::String( roadDirection ) ) return 255U; } void updateRoadSpriteOnTile( Maps::Tiles & tile, const bool forceRoadOnTile ) { const uint8_t imageIndex = getRoadImageForTile( tile, getRoadDirecton( tile ) | ( forceRoadOnTile ? Direction::CENTER : Direction::UNKNOWN ) ); if ( imageIndex == 255U ) { // After the check this tile should not contain a road sprite. if ( !forceRoadOnTile && !tile.isRoad() ) { // We remove any existing road sprite if this tile does not contain (or was not forced to contain) the main road sprite. tile.removeObjects( MP2::OBJ_ICN_TYPE_ROAD ); } return; } const uint32_t roadUid = tile.getObjectIdByObjectIcnType( MP2::OBJ_ICN_TYPE_ROAD ); if ( roadUid == 0 ) { tile.pushBottomLayerAddon( Maps::TilesAddon( Maps::TERRAIN_LAYER, Maps::getNewObjectUID(), MP2::OBJ_ICN_TYPE_ROAD, imageIndex ) ); } else { Maps::Tiles::updateTileObjectIcnIndex( tile, roadUid, imageIndex ); } } // Update tiles in a square starting from the tile near the center tile to edges or in reverse order. // This function can be called to update only tiles not marked as road. void updateRoadSpritesInArea( const Maps::Tiles & centerTile, const int32_t centerToRectBorderDistance, const bool updateNonRoadTilesFromEdgesToCenter ) { // We should update road sprites step by step starting from the tiles close connected to the center tile. 'getAroundIndexes()' cannot be used here. const int32_t worldWidth = world.w(); const int32_t worldHeight = world.h(); assert( worldWidth > 0 && worldHeight > 0 ); const int32_t centerTileIndex = centerTile.GetIndex(); const int32_t centerX = centerTileIndex % worldWidth; const int32_t centerY = centerTileIndex / worldWidth; // We avoid getting out of map boundaries. const int32_t minTileX = std::max( centerX - centerToRectBorderDistance, 0 ); const int32_t minTileY = std::max( centerY - centerToRectBorderDistance, 0 ); const int32_t maxTileX = std::min( centerX + centerToRectBorderDistance + 1, worldWidth ); const int32_t maxTileY = std::min( centerY + centerToRectBorderDistance + 1, worldHeight ); const int32_t distanceMax = centerToRectBorderDistance * 2 + 1; for ( int32_t distance = 1; distance < distanceMax; ++distance ) { const int32_t correctedDistance = updateNonRoadTilesFromEdgesToCenter ? distanceMax - distance : distance; for ( int32_t tileY = minTileY; tileY < maxTileY; ++tileY ) { const int32_t indexOffsetY = tileY * worldWidth; const int32_t distanceY = std::abs( tileY - centerY ); for ( int32_t tileX = minTileX; tileX < maxTileX; ++tileX ) { if ( std::abs( tileX - centerX ) + distanceY != correctedDistance ) { continue; } Maps::Tiles & tile = world.GetTiles( indexOffsetY + tileX ); if ( updateNonRoadTilesFromEdgesToCenter && tile.isRoad() ) { continue; } updateRoadSpriteOnTile( tile, false ); } } } } void updateRoadSpritesAround( const Maps::Tiles & tile ) { updateRoadSpritesInArea( tile, 2, false ); // To properly update the around sprites we call the update function the second time // for tiles not marked as road in reverse order and for 1 tile more distance from the center. updateRoadSpritesInArea( tile, 3, true ); } uint8_t getStreamImageForTile( const int streamDirection ) { if ( hasNoBits( streamDirection, Direction::CENTER ) ) { // This tile should not have a stream image. return 255U; } if ( hasBits( streamDirection, Direction::LEFT | Direction::BOTTOM ) && hasNoBits( streamDirection, Direction::TOP | Direction::RIGHT ) ) { // \ - stream from the left to the bottom. return 0U; } if ( hasBits( streamDirection, Direction::RIGHT | Direction::BOTTOM ) && hasNoBits( streamDirection, Direction::TOP | Direction::LEFT ) ) { // / - stream from the right to the bottom. return 1U; } if ( hasBits( streamDirection, Direction::RIGHT | Direction::TOP ) && hasNoBits( streamDirection, Direction::BOTTOM | Direction::LEFT ) ) { // \ - stream from the top to the right. return 4U; } if ( hasBits( streamDirection, Direction::LEFT | Direction::TOP ) && hasNoBits( streamDirection, Direction::BOTTOM | Direction::RIGHT ) ) { // / - stream from the top to the left. return 7U; } if ( hasBits( streamDirection, Direction::LEFT | Direction::TOP | Direction::RIGHT ) && hasNoBits( streamDirection, Direction::BOTTOM ) ) { // _|_ - stream from the top to the left and right. return 8U; } if ( hasBits( streamDirection, Direction::BOTTOM | Direction::TOP | Direction::RIGHT ) && hasNoBits( streamDirection, Direction::LEFT ) ) { // |- - stream from the top to the right and bottom. return 9U; } if ( hasBits( streamDirection, Direction::BOTTOM | Direction::TOP | Direction::LEFT ) && hasNoBits( streamDirection, Direction::RIGHT ) ) { // -| - stream from the top to the left and bottom. return 10U; } if ( hasBits( streamDirection, Direction::BOTTOM | Direction::LEFT | Direction::RIGHT ) && hasNoBits( streamDirection, Direction::TOP ) ) { // \/ - stream from the left and right to the bottom. return 11U; } if ( hasBits( streamDirection, Direction::BOTTOM | Direction::LEFT | Direction::TOP | Direction::RIGHT ) ) { // -|- - streams are all around. return 6U; } if ( ( hasBits( streamDirection, Direction::LEFT ) || hasBits( streamDirection, Direction::RIGHT ) ) && hasNoBits( streamDirection, Direction::TOP | Direction::BOTTOM ) ) { // - - horizontal stream. return Rand::Get( 1 ) ? 2U : 5U; } // | - in all other cases are the vertical stream sprite, including the case when there are no other streams around. return Rand::Get( 1 ) ? 3U : 12U; } void updateStreamSpriteOnTile( Maps::Tiles & tile, const bool forceStreamOnTile ) { const uint8_t imageIndex = getStreamImageForTile( getStreamDirecton( tile ) | ( forceStreamOnTile ? Direction::CENTER : Direction::UNKNOWN ) ); if ( imageIndex == 255U ) { // After the check this tile should not contain a stream sprite. return; } const uint32_t streamUid = tile.getObjectIdByObjectIcnType( MP2::OBJ_ICN_TYPE_STREAM ); if ( streamUid == 0 ) { tile.pushBottomLayerAddon( Maps::TilesAddon( Maps::TERRAIN_LAYER, Maps::getNewObjectUID(), MP2::OBJ_ICN_TYPE_STREAM, imageIndex ) ); } else { Maps::Tiles::updateTileObjectIcnIndex( tile, streamUid, imageIndex ); } } // Update streams on the left, top, right and bottom tiles around. void updateStreamSpritesAround( const Maps::Tiles & centerTile ) { const int32_t centerTileIndex = centerTile.GetIndex(); // For streams we should update only the next four directions. for ( const int direction : { Direction::LEFT, Direction::TOP, Direction::RIGHT, Direction::BOTTOM } ) { if ( Maps::isValidDirection( centerTileIndex, direction ) ) { updateStreamSpriteOnTile( world.GetTiles( Maps::GetDirectionIndex( centerTileIndex, direction ) ), false ); } } } // Checks all layers in tiles around for given UID and if it is found removes this object part // and initiates a new search around this tile using recursive call of this function. void removeUidFromTilesAround( const int32_t centerTileIndex, const uint32_t uid ) { for ( const int32_t tileIndex : Maps::getAroundIndexes( centerTileIndex ) ) { Maps::Tiles & currentTile = world.GetTiles( tileIndex ); if ( currentTile.GetObjectUID() == uid ) { currentTile.Remove( uid ); removeUidFromTilesAround( tileIndex, uid ); return; } for ( const Maps::TilesAddon & addon : currentTile.getBottomLayerAddons() ) { if ( addon._uid == uid ) { currentTile.Remove( uid ); removeUidFromTilesAround( tileIndex, uid ); return; } } for ( const Maps::TilesAddon & addon : currentTile.getTopLayerAddons() ) { if ( addon._uid == uid ) { currentTile.Remove( uid ); removeUidFromTilesAround( tileIndex, uid ); return; } } } } bool placeObjectOnTile( const Maps::Tiles & tile, const Maps::ObjectInfo & info ) { assert( !info.empty() ); // Verify that the object is allowed to be placed. const fheroes2::Point mainTilePos = tile.GetCenter(); for ( const auto & partInfo : info.groundLevelParts ) { const fheroes2::Point pos = mainTilePos + partInfo.tileOffset; const bool isMainObject = ( partInfo.layerType != Maps::SHADOW_LAYER && partInfo.layerType != Maps::TERRAIN_LAYER ); if ( !Maps::isValidAbsPoint( pos.x, pos.y ) && isMainObject ) { // Only shadow and terrain layer object parts are allowed not to be placed. assert( 0 ); return false; } } for ( const auto & partInfo : info.topLevelParts ) { const fheroes2::Point pos = mainTilePos + partInfo.tileOffset; if ( !Maps::isValidAbsPoint( pos.x, pos.y ) ) { // This shouldn't happen as the object must be verified before placement. assert( 0 ); return false; } } const uint32_t uid = Maps::getNewObjectUID(); for ( const auto & partInfo : info.groundLevelParts ) { const fheroes2::Point pos = mainTilePos + partInfo.tileOffset; const bool isMainObject = ( partInfo.layerType != Maps::SHADOW_LAYER && partInfo.layerType != Maps::TERRAIN_LAYER ); if ( !Maps::isValidAbsPoint( pos.x, pos.y ) ) { assert( !isMainObject ); continue; } Maps::Tiles & currentTile = world.GetTiles( pos.x, pos.y ); if ( !isMainObject ) { // Shadows and terrain object do not change main tile information. currentTile.pushBottomLayerAddon( Maps::TilesAddon( partInfo.layerType, uid, partInfo.icnType, static_cast( partInfo.icnIndex ) ) ); continue; } // It is important that the type of the object is set properly for this layer. assert( partInfo.objectType != MP2::OBJ_NONE ); bool setObjectType = true; if ( !MP2::isActionObject( partInfo.objectType ) ) { for ( const auto & addon : currentTile.getTopLayerAddons() ) { const MP2::MapObjectType type = Maps::getObjectTypeByIcn( addon._objectIcnType, addon._imageIndex ); if ( type != MP2::OBJ_NONE ) { setObjectType = false; break; } } } if ( setObjectType ) { currentTile.SetObject( partInfo.objectType ); } currentTile.moveMainAddonToBottomLayer(); currentTile.setObjectUID( uid ); currentTile.setObjectIcnType( partInfo.icnType ); using TileImageIndexType = decltype( currentTile.GetObjectSpriteIndex() ); static_assert( std::is_same_v, "Type of GetObjectSpriteIndex() has been changed, check the logic below" ); currentTile.setObjectSpriteIndex( static_cast( partInfo.icnIndex ) ); } for ( const auto & partInfo : info.topLevelParts ) { const fheroes2::Point pos = mainTilePos + partInfo.tileOffset; if ( !Maps::isValidAbsPoint( pos.x, pos.y ) ) { // This shouldn't happen as the object must be verified before placement. assert( 0 ); continue; } Maps::Tiles & currentTile = world.GetTiles( pos.x, pos.y ); currentTile.pushTopLayerAddon( Maps::TilesAddon( Maps::OBJECT_LAYER, uid, partInfo.icnType, static_cast( partInfo.icnIndex ) ) ); if ( partInfo.objectType != MP2::OBJ_NONE && !MP2::isActionObject( currentTile.GetObject() ) ) { currentTile.SetObject( partInfo.objectType ); } } return true; } } namespace Maps { void setTerrainOnTiles( const int32_t startTileId, const int32_t endTileId, const int groundId ) { const int32_t mapWidth = world.w(); const int32_t maxTileId = mapWidth * world.h() - 1; if ( startTileId < 0 || startTileId > maxTileId || endTileId < 0 || endTileId > maxTileId ) { return; } const fheroes2::Point startTileOffset = GetPoint( startTileId ); const fheroes2::Point endTileOffset = GetPoint( endTileId ); const int32_t startX = std::min( startTileOffset.x, endTileOffset.x ); const int32_t startY = std::min( startTileOffset.y, endTileOffset.y ); const int32_t endX = std::max( startTileOffset.x, endTileOffset.x ); const int32_t endY = std::max( startTileOffset.y, endTileOffset.y ); for ( int32_t y = startY; y <= endY; ++y ) { const int32_t tileOffset = y * mapWidth; for ( int32_t x = startX; x <= endX; ++x ) { // In original editor these tiles are never flipped. world.GetTiles( x + tileOffset ).setTerrain( Ground::getRandomTerrainImageIndex( groundId, true ), false, false ); } } // Set ground transitions on the boundaries of filled terrain area. updateTerrainTransitionOnAreaBoundaries( groundId, startX, endX, startY, endY ); } bool updateRoadOnTile( Tiles & tile, const bool setRoad ) { if ( setRoad == tile.isRoad() || ( tile.GetGround() == Ground::WATER ) ) { // We cannot place roads on the water or above already placed roads. return false; } if ( setRoad ) { // Force set road on this tile and update its sprite. updateRoadSpriteOnTile( tile, true ); if ( !tile.isRoad() ) { // The road was not set because there is no corresponding sprite for this place. return false; } updateRoadSpritesAround( tile ); if ( Maps::Ground::doesTerrainImageIndexContainEmbeddedObjects( tile.getTerrainImageIndex() ) ) { // We need to set terrain image without extra objects under the road. tile.setTerrain( Maps::Ground::getRandomTerrainImageIndex( tile.GetGround(), false ), false, false ); } } else { // Remove all road object sprites from this tile. tile.removeObjects( MP2::OBJ_ICN_TYPE_ROAD ); updateRoadSpritesAround( tile ); // After removing the road from the tile it may have road sprites for the nearby tiles with road. updateRoadSpriteOnTile( tile, false ); } return true; } bool updateStreamOnTile( Tiles & tile, const bool setStream ) { if ( setStream == tile.isStream() || ( tile.GetGround() == Ground::WATER ) ) { // We cannot place streams on the water or on already placed streams. return false; } if ( setStream ) { // Force set stream on this tile and update its sprite. updateStreamSpriteOnTile( tile, true ); if ( !tile.isStream() ) { // The stream was not set. How can it happen? assert( 0 ); return false; } updateStreamSpritesAround( tile ); if ( Maps::Ground::doesTerrainImageIndexContainEmbeddedObjects( tile.getTerrainImageIndex() ) ) { // We need to set terrain image without extra objects under the stream. tile.setTerrain( Maps::Ground::getRandomTerrainImageIndex( tile.GetGround(), false ), false, false ); } } else { // Remove all road object sprites from this tile. tile.removeObjects( MP2::OBJ_ICN_TYPE_STREAM ); updateStreamSpritesAround( tile ); } return true; } int32_t getMineSpellIdFromTile( const Tiles & tile ) { if ( tile.GetObject( false ) != MP2::OBJ_MINE ) { // Why are you calling this function for an unsupported object type? assert( 0 ); return Spell::NONE; } return static_cast( tile.metadata()[2] ); } void setMineSpellOnTile( Tiles & tile, const int32_t spellId ) { if ( tile.GetObject( false ) != MP2::OBJ_MINE ) { // Why are you calling this function for an unsupported object type? assert( 0 ); return; } tile.metadata()[2] = spellId; } void removeMineSpellFromTile( Tiles & tile ) { if ( tile.GetObject( false ) != MP2::OBJ_MINE ) { // Why are you calling this function for an unsupported object type? assert( 0 ); return; } tile.metadata()[2] = 0; } Funds getDailyIncomeObjectResources( const Tiles & tile ) { switch ( tile.GetObject( false ) ) { case MP2::OBJ_ALCHEMIST_LAB: case MP2::OBJ_MINE: case MP2::OBJ_SAWMILL: return { static_cast( tile.metadata()[0] ), tile.metadata()[1] }; default: break; } // Why are you calling this function for an unsupported object type? assert( 0 ); return {}; } Spell getSpellFromTile( const Tiles & tile ) { switch ( tile.GetObject( false ) ) { case MP2::OBJ_SHRINE_FIRST_CIRCLE: case MP2::OBJ_SHRINE_SECOND_CIRCLE: case MP2::OBJ_SHRINE_THIRD_CIRCLE: case MP2::OBJ_PYRAMID: return { static_cast( tile.metadata()[0] ) }; default: // Why are you calling this function for an unsupported object type? assert( 0 ); break; } return { Spell::NONE }; } void setSpellOnTile( Tiles & tile, const int spellId ) { switch ( tile.GetObject( false ) ) { case MP2::OBJ_SHRINE_FIRST_CIRCLE: case MP2::OBJ_SHRINE_SECOND_CIRCLE: case MP2::OBJ_SHRINE_THIRD_CIRCLE: case MP2::OBJ_PYRAMID: tile.metadata()[0] = spellId; break; case MP2::OBJ_ARTIFACT: // Only the Spell Scroll artifact can have a spell set. assert( tile.metadata()[0] == Artifact::SPELL_SCROLL ); tile.metadata()[1] = spellId; break; default: // Why are you calling this function for an unsupported object type? assert( 0 ); break; } } void setMonsterOnTileJoinCondition( Tiles & tile, const int32_t condition ) { if ( tile.GetObject() == MP2::OBJ_MONSTER ) { tile.metadata()[2] = condition; } else { // Why are you calling this function for an unsupported object type? assert( 0 ); } } bool isMonsterOnTileJoinConditionSkip( const Tiles & tile ) { if ( tile.GetObject() == MP2::OBJ_MONSTER ) { return ( tile.metadata()[2] == Monster::JOIN_CONDITION_SKIP ); } // Why are you calling this function for an unsupported object type? assert( 0 ); return false; } bool isMonsterOnTileJoinConditionFree( const Tiles & tile ) { if ( tile.GetObject() == MP2::OBJ_MONSTER ) { return ( tile.metadata()[2] == Monster::JOIN_CONDITION_FREE ); } // Why are you calling this function for an unsupported object type? assert( 0 ); return false; } int getColorFromBarrierSprite( const MP2::ObjectIcnType objectIcnType, const uint8_t icnIndex ) { // The color of the barrier is actually being stored in tile metadata but as of now we use sprite information. if ( MP2::OBJ_ICN_TYPE_X_LOC3 == objectIcnType && 60 <= icnIndex && 102 >= icnIndex ) { // 60, 66, 72, 78, 84, 90, 96, 102 return ( ( icnIndex - 60 ) / 6 ) + 1; } // Why are you calling this function for an unsupported object type? assert( 0 ); return 0; } int getColorFromTravellerTentSprite( const MP2::ObjectIcnType objectIcnType, const uint8_t icnIndex ) { // The color of the barrier is actually being stored in tile metadata but as of now we use sprite information. if ( MP2::OBJ_ICN_TYPE_X_LOC3 == objectIcnType && 110 <= icnIndex && 138 >= icnIndex ) { // 110, 114, 118, 122, 126, 130, 134, 138 return ( ( icnIndex - 110 ) / 4 ) + 1; } // Why are you calling this function for an unsupported object type? assert( 0 ); return 0; } Monster getMonsterFromTile( const Tiles & tile ) { switch ( tile.GetObject( false ) ) { case MP2::OBJ_WATCH_TOWER: return { Monster::ORC }; case MP2::OBJ_EXCAVATION: return { Monster::SKELETON }; case MP2::OBJ_CAVE: return { Monster::CENTAUR }; case MP2::OBJ_TREE_HOUSE: return { Monster::SPRITE }; case MP2::OBJ_ARCHER_HOUSE: return { Monster::ARCHER }; case MP2::OBJ_GOBLIN_HUT: return { Monster::GOBLIN }; case MP2::OBJ_DWARF_COTTAGE: return { Monster::DWARF }; case MP2::OBJ_HALFLING_HOLE: return { Monster::HALFLING }; case MP2::OBJ_PEASANT_HUT: return { Monster::PEASANT }; case MP2::OBJ_RUINS: return { Monster::MEDUSA }; case MP2::OBJ_TREE_CITY: return { Monster::SPRITE }; case MP2::OBJ_WAGON_CAMP: return { Monster::ROGUE }; case MP2::OBJ_DESERT_TENT: return { Monster::NOMAD }; case MP2::OBJ_TROLL_BRIDGE: return { Monster::TROLL }; case MP2::OBJ_DRAGON_CITY: return { Monster::RED_DRAGON }; case MP2::OBJ_CITY_OF_DEAD: return { Monster::POWER_LICH }; case MP2::OBJ_GENIE_LAMP: return { Monster::GENIE }; case MP2::OBJ_ABANDONED_MINE: return { Monster::GHOST }; case MP2::OBJ_WATER_ALTAR: return { Monster::WATER_ELEMENT }; case MP2::OBJ_AIR_ALTAR: return { Monster::AIR_ELEMENT }; case MP2::OBJ_FIRE_ALTAR: return { Monster::FIRE_ELEMENT }; case MP2::OBJ_EARTH_ALTAR: return { Monster::EARTH_ELEMENT }; case MP2::OBJ_BARROW_MOUNDS: return { Monster::GHOST }; case MP2::OBJ_MONSTER: return { tile.GetObjectSpriteIndex() + 1 }; default: break; } if ( MP2::isCaptureObject( tile.GetObject( false ) ) ) { return { world.GetCapturedObject( tile.GetIndex() ).GetTroop().GetID() }; } return { Monster::UNKNOWN }; } Artifact getArtifactFromTile( const Tiles & tile ) { switch ( tile.GetObject( false ) ) { case MP2::OBJ_DAEMON_CAVE: case MP2::OBJ_GRAVEYARD: case MP2::OBJ_SEA_CHEST: case MP2::OBJ_SHIPWRECK: case MP2::OBJ_SHIPWRECK_SURVIVOR: case MP2::OBJ_SKELETON: case MP2::OBJ_TREASURE_CHEST: case MP2::OBJ_WAGON: return { static_cast( tile.metadata()[0] ) }; case MP2::OBJ_ARTIFACT: if ( tile.metadata()[2] == static_cast( ArtifactCaptureCondition::CONTAINS_SPELL ) ) { Artifact art( Artifact::SPELL_SCROLL ); art.SetSpell( static_cast( tile.metadata()[1] ) ); return art; } return { static_cast( tile.metadata()[0] ) }; default: break; } // Why are you calling this function for an unsupported object type? assert( 0 ); return { Artifact::UNKNOWN }; } Skill::Secondary getArtifactSecondarySkillRequirement( const Tiles & tile ) { if ( tile.GetObject( false ) != MP2::OBJ_ARTIFACT ) { // Why are you calling this for an unsupported object type? assert( 0 ); return {}; } switch ( static_cast( tile.metadata()[2] ) ) { case ArtifactCaptureCondition::HAVE_WISDOM_SKILL: return { Skill::Secondary::WISDOM, Skill::Level::BASIC }; case ArtifactCaptureCondition::HAVE_LEADERSHIP_SKILL: return { Skill::Secondary::LEADERSHIP, Skill::Level::BASIC }; default: break; } // Why are you calling this for invalid conditions? assert( 0 ); return {}; } ArtifactCaptureCondition getArtifactCaptureCondition( const Tiles & tile ) { if ( tile.GetObject( false ) != MP2::OBJ_ARTIFACT ) { // Why are you calling this for an unsupported object type? assert( 0 ); return ArtifactCaptureCondition::NO_CONDITIONS; } return static_cast( tile.metadata()[2] ); } Funds getArtifactResourceRequirement( const Tiles & tile ) { if ( tile.GetObject( false ) != MP2::OBJ_ARTIFACT ) { // Why are you calling this for an unsupported object type? assert( 0 ); return {}; } switch ( static_cast( tile.metadata()[2] ) ) { case ArtifactCaptureCondition::PAY_2000_GOLD: return { Resource::GOLD, 2000 }; case ArtifactCaptureCondition::PAY_2500_GOLD_AND_3_RESOURCES: return Funds{ Resource::GOLD, 2500 } + Funds{ Resource::getResourceTypeFromIconIndex( tile.metadata()[1] - 1 ), 3 }; case ArtifactCaptureCondition::PAY_3000_GOLD_AND_5_RESOURCES: return Funds{ Resource::GOLD, 3000 } + Funds{ Resource::getResourceTypeFromIconIndex( tile.metadata()[1] - 1 ), 5 }; default: break; } // Why are you calling this for invalid conditions? assert( 0 ); return {}; } DaemonCaveCaptureBonus getDaemonCaveBonusType( const Tiles & tile ) { if ( tile.GetObject( false ) != MP2::OBJ_DAEMON_CAVE ) { // Why are you calling this for an unsupported object type? assert( 0 ); return DaemonCaveCaptureBonus::EMPTY; } return static_cast( tile.metadata()[2] ); } Funds getDaemonPaymentCondition( const Tiles & tile ) { if ( tile.GetObject( false ) != MP2::OBJ_DAEMON_CAVE ) { // Why are you calling this for an unsupported object type? assert( 0 ); return {}; } if ( static_cast( tile.metadata()[2] ) != DaemonCaveCaptureBonus::PAY_2500_GOLD ) { // Why are you calling this for invalid conditions? assert( 0 ); return {}; } return { Resource::GOLD, tile.metadata()[1] }; } ShipwreckCaptureCondition getShipwreckCaptureCondition( const Tiles & tile ) { if ( tile.GetObject( false ) != MP2::OBJ_SHIPWRECK ) { // Why are you calling this for an unsupported object type? assert( 0 ); return ShipwreckCaptureCondition::EMPTY; } return static_cast( tile.metadata()[2] ); } Funds getTreeOfKnowledgeRequirement( const Tiles & tile ) { if ( tile.GetObject( false ) != MP2::OBJ_TREE_OF_KNOWLEDGE ) { // Why are you calling this for an unsupported object type? assert( 0 ); return {}; } return { static_cast( tile.metadata()[0] ), tile.metadata()[1] }; } Skill::Secondary getSecondarySkillFromWitchsHut( const Tiles & tile ) { if ( tile.GetObject( false ) != MP2::OBJ_WITCHS_HUT ) { // Why are you calling this for an unsupported object type? assert( 0 ); return {}; } return { static_cast( tile.metadata()[0] ), Skill::Level::BASIC }; } void setResourceOnTile( Tiles & tile, const int resourceType, uint32_t value ) { tile.metadata()[0] = resourceType; tile.metadata()[1] = value; } Funds getFundsFromTile( const Tiles & tile ) { switch ( tile.GetObject( false ) ) { case MP2::OBJ_CAMPFIRE: // Campfire contains N of non-Gold resources and (N * 100) Gold. return Funds{ static_cast( tile.metadata()[0] ), tile.metadata()[1] } + Funds{ Resource::GOLD, tile.metadata()[1] * 100 }; case MP2::OBJ_FLOTSAM: return Funds{ Resource::WOOD, tile.metadata()[0] } + Funds{ Resource::GOLD, tile.metadata()[1] }; case MP2::OBJ_DAEMON_CAVE: case MP2::OBJ_GRAVEYARD: case MP2::OBJ_SEA_CHEST: case MP2::OBJ_SHIPWRECK: case MP2::OBJ_TREASURE_CHEST: return { Resource::GOLD, tile.metadata()[1] }; case MP2::OBJ_DERELICT_SHIP: case MP2::OBJ_LEAN_TO: case MP2::OBJ_MAGIC_GARDEN: case MP2::OBJ_RESOURCE: case MP2::OBJ_WINDMILL: case MP2::OBJ_WATER_WHEEL: return { static_cast( tile.metadata()[0] ), tile.metadata()[1] }; case MP2::OBJ_WAGON: return { static_cast( tile.metadata()[1] ), tile.metadata()[2] }; default: break; } // Why are you calling this for an unsupported object type? assert( 0 ); return {}; } Troop getTroopFromTile( const Tiles & tile ) { return MP2::isCaptureObject( tile.GetObject( false ) ) ? world.GetCapturedObject( tile.GetIndex() ).GetTroop() : Troop( getMonsterFromTile( tile ), getMonsterCountFromTile( tile ) ); } int getColorFromTile( const Tiles & tile ) { switch ( tile.GetObject( false ) ) { case MP2::OBJ_BARRIER: case MP2::OBJ_TRAVELLER_TENT: return static_cast( tile.metadata()[0] ); default: return world.ColorCapturedObject( tile.GetIndex() ); } } void setColorOnTile( Tiles & tile, const int color ) { switch ( tile.GetObject( false ) ) { case MP2::OBJ_BARRIER: case MP2::OBJ_TRAVELLER_TENT: tile.metadata()[0] = color; break; default: world.CaptureObject( tile.GetIndex(), color ); break; } } bool doesTileContainValuableItems( const Tiles & tile ) { switch ( tile.GetObject( false ) ) { case MP2::OBJ_ARTIFACT: case MP2::OBJ_CAMPFIRE: case MP2::OBJ_FLOTSAM: case MP2::OBJ_RESOURCE: case MP2::OBJ_SEA_CHEST: case MP2::OBJ_SHIPWRECK_SURVIVOR: case MP2::OBJ_TREASURE_CHEST: return true; case MP2::OBJ_PYRAMID: return getSpellFromTile( tile ).isValid(); case MP2::OBJ_DERELICT_SHIP: case MP2::OBJ_GRAVEYARD: case MP2::OBJ_LEAN_TO: case MP2::OBJ_MAGIC_GARDEN: case MP2::OBJ_SHIPWRECK: case MP2::OBJ_WATER_WHEEL: case MP2::OBJ_WINDMILL: return tile.metadata()[1] > 0; case MP2::OBJ_SKELETON: return getArtifactFromTile( tile ) != Artifact::UNKNOWN; case MP2::OBJ_WAGON: return getArtifactFromTile( tile ) != Artifact::UNKNOWN || tile.metadata()[2] != 0; case MP2::OBJ_DAEMON_CAVE: return tile.metadata()[2] != 0; default: break; } return false; } void resetObjectMetadata( Tiles & tile ) { for ( uint32_t & value : tile.metadata() ) { value = 0; } } void resetObjectInfoOnTile( Tiles & tile ) { resetObjectMetadata( tile ); const MP2::MapObjectType objectType = tile.GetObject( false ); if ( MP2::isPickupObject( objectType ) ) { tile.setAsEmpty(); } } uint32_t getMonsterCountFromTile( const Tiles & tile ) { switch ( tile.GetObject( false ) ) { case MP2::OBJ_ABANDONED_MINE: case MP2::OBJ_AIR_ALTAR: case MP2::OBJ_ARCHER_HOUSE: case MP2::OBJ_BARROW_MOUNDS: case MP2::OBJ_CAVE: case MP2::OBJ_CITY_OF_DEAD: case MP2::OBJ_DESERT_TENT: case MP2::OBJ_DRAGON_CITY: case MP2::OBJ_DWARF_COTTAGE: case MP2::OBJ_EARTH_ALTAR: case MP2::OBJ_EXCAVATION: case MP2::OBJ_FIRE_ALTAR: case MP2::OBJ_GENIE_LAMP: case MP2::OBJ_GOBLIN_HUT: case MP2::OBJ_HALFLING_HOLE: case MP2::OBJ_MONSTER: case MP2::OBJ_PEASANT_HUT: case MP2::OBJ_RUINS: case MP2::OBJ_TREE_CITY: case MP2::OBJ_TREE_HOUSE: case MP2::OBJ_TROLL_BRIDGE: case MP2::OBJ_WAGON_CAMP: case MP2::OBJ_WATCH_TOWER: case MP2::OBJ_WATER_ALTAR: return tile.metadata()[0]; default: // Why are you calling this function for an unsupported object type? assert( 0 ); break; } return 0; } void setMonsterCountOnTile( Tiles & tile, uint32_t count ) { switch ( tile.GetObject( false ) ) { case MP2::OBJ_ABANDONED_MINE: case MP2::OBJ_AIR_ALTAR: case MP2::OBJ_ARCHER_HOUSE: case MP2::OBJ_BARROW_MOUNDS: case MP2::OBJ_CAVE: case MP2::OBJ_CITY_OF_DEAD: case MP2::OBJ_DESERT_TENT: case MP2::OBJ_DRAGON_CITY: case MP2::OBJ_DWARF_COTTAGE: case MP2::OBJ_EARTH_ALTAR: case MP2::OBJ_EXCAVATION: case MP2::OBJ_FIRE_ALTAR: case MP2::OBJ_GENIE_LAMP: case MP2::OBJ_GOBLIN_HUT: case MP2::OBJ_HALFLING_HOLE: case MP2::OBJ_MONSTER: case MP2::OBJ_PEASANT_HUT: case MP2::OBJ_RUINS: case MP2::OBJ_TREE_CITY: case MP2::OBJ_TREE_HOUSE: case MP2::OBJ_TROLL_BRIDGE: case MP2::OBJ_WAGON_CAMP: case MP2::OBJ_WATCH_TOWER: case MP2::OBJ_WATER_ALTAR: tile.metadata()[0] = count; return; default: // Why are you calling this function for an unsupported object type? assert( 0 ); break; } } void updateDwellingPopulationOnTile( Tiles & tile, const bool isFirstLoad ) { uint32_t count = isFirstLoad ? 0 : getMonsterCountFromTile( tile ); const MP2::MapObjectType objectType = tile.GetObject( false ); switch ( objectType ) { // join monsters case MP2::OBJ_HALFLING_HOLE: count += isFirstLoad ? Rand::Get( 20, 40 ) : Rand::Get( 5, 10 ); break; case MP2::OBJ_PEASANT_HUT: count += isFirstLoad ? Rand::Get( 20, 50 ) : Rand::Get( 5, 10 ); break; case MP2::OBJ_EXCAVATION: case MP2::OBJ_TREE_HOUSE: count += isFirstLoad ? Rand::Get( 10, 25 ) : Rand::Get( 4, 8 ); break; case MP2::OBJ_CAVE: count += isFirstLoad ? Rand::Get( 10, 20 ) : Rand::Get( 3, 6 ); break; case MP2::OBJ_GOBLIN_HUT: count += isFirstLoad ? Rand::Get( 15, 40 ) : Rand::Get( 3, 6 ); break; case MP2::OBJ_TREE_CITY: count += isFirstLoad ? Rand::Get( 20, 40 ) : Rand::Get( 10, 20 ); break; case MP2::OBJ_WATCH_TOWER: count += isFirstLoad ? Rand::Get( 7, 10 ) : Rand::Get( 2, 4 ); break; case MP2::OBJ_ARCHER_HOUSE: count += isFirstLoad ? Rand::Get( 10, 25 ) : Rand::Get( 2, 4 ); break; case MP2::OBJ_DWARF_COTTAGE: count += isFirstLoad ? Rand::Get( 10, 20 ) : Rand::Get( 3, 6 ); break; case MP2::OBJ_WAGON_CAMP: count += isFirstLoad ? Rand::Get( 30, 50 ) : Rand::Get( 3, 6 ); break; case MP2::OBJ_DESERT_TENT: count += isFirstLoad ? Rand::Get( 10, 20 ) : Rand::Get( 1, 3 ); break; case MP2::OBJ_RUINS: count += isFirstLoad ? Rand::Get( 3, 5 ) : Rand::Get( 1, 3 ); break; case MP2::OBJ_WATER_ALTAR: case MP2::OBJ_AIR_ALTAR: case MP2::OBJ_FIRE_ALTAR: case MP2::OBJ_EARTH_ALTAR: case MP2::OBJ_BARROW_MOUNDS: count += Rand::Get( 2, 5 ); break; case MP2::OBJ_TROLL_BRIDGE: case MP2::OBJ_CITY_OF_DEAD: if ( isFirstLoad ) { count = Rand::Get( 4, 6 ); } else if ( getColorFromTile( tile ) != Color::NONE ) { // If the Troll Bridge or City of Dead has been captured, its population is increased by 1-3 creature per week. count += Rand::Get( 1, 3 ); } break; case MP2::OBJ_DRAGON_CITY: if ( isFirstLoad ) { count = 2; } else if ( getColorFromTile( tile ) != Color::NONE ) { // If the Dragon City has been captured or has 0 creatures, its population is increased by 1 dragon per week. ++count; } break; default: // Did you add a new dwelling on Adventure Map? Add the logic above! assert( 0 ); break; } assert( count > 0 ); setMonsterCountOnTile( tile, count ); } void updateObjectInfoTile( Tiles & tile, const bool isFirstLoad ) { switch ( tile.GetObject( false ) ) { case MP2::OBJ_WITCHS_HUT: assert( isFirstLoad ); tile.metadata()[0] = Skill::Secondary::RandForWitchsHut(); break; case MP2::OBJ_SHRINE_FIRST_CIRCLE: assert( isFirstLoad ); tile.metadata()[0] = Rand::Get( 1 ) ? Spell::RandCombat( 1 ).GetID() : Spell::RandAdventure( 1 ).GetID(); break; case MP2::OBJ_SHRINE_SECOND_CIRCLE: assert( isFirstLoad ); tile.metadata()[0] = Rand::Get( 1 ) ? Spell::RandCombat( 2 ).GetID() : Spell::RandAdventure( 2 ).GetID(); break; case MP2::OBJ_SHRINE_THIRD_CIRCLE: assert( isFirstLoad ); tile.metadata()[0] = Rand::Get( 1 ) ? Spell::RandCombat( 3 ).GetID() : Spell::RandAdventure( 3 ).GetID(); break; case MP2::OBJ_SKELETON: { assert( isFirstLoad ); Rand::Queue percents( 2 ); // 80%: empty percents.Push( 0, 80 ); // 20%: artifact 1 or 2 or 3 percents.Push( 1, 20 ); if ( percents.Get() ) { tile.metadata()[0] = Artifact::Rand( Artifact::ART_LEVEL_ALL_NORMAL ); } else { tile.metadata()[0] = Artifact::UNKNOWN; } break; } case MP2::OBJ_WAGON: { assert( isFirstLoad ); resetObjectMetadata( tile ); Rand::Queue percents( 3 ); // 20%: empty percents.Push( 0, 20 ); // 10%: artifact 1 or 2 percents.Push( 1, 10 ); // 50%: resource percents.Push( 2, 50 ); switch ( percents.Get() ) { case 1: tile.metadata()[0] = Artifact::Rand( Rand::Get( 1 ) ? Artifact::ART_LEVEL_TREASURE : Artifact::ART_LEVEL_MINOR ); break; case 2: tile.metadata()[1] = Resource::Rand( false ); tile.metadata()[2] = Rand::Get( 2, 5 ); break; default: break; } break; } case MP2::OBJ_ARTIFACT: { assert( isFirstLoad ); const int art = Artifact::FromMP2IndexSprite( tile.GetObjectSpriteIndex() ).GetID(); if ( Artifact::UNKNOWN == art ) { // This is an unknown artifact. Did you add a new one? assert( 0 ); return; } if ( art == Artifact::SPELL_SCROLL ) { static_assert( Spell::FIREBALL < Spell::SETWGUARDIAN, "The order of spell IDs has been changed, check the logic below" ); // Spell ID has a value of 1 bigger than in the original game. const uint32_t spell = std::clamp( tile.metadata()[0] + 1, static_cast( Spell::FIREBALL ), static_cast( Spell::SETWGUARDIAN ) ); tile.metadata()[1] = spell; tile.metadata()[2] = static_cast( ArtifactCaptureCondition::CONTAINS_SPELL ); } else { // 70% chance of no conditions. // Refer to ArtifactCaptureCondition enumeration. const uint32_t cond = Rand::Get( 1, 10 ) < 4 ? Rand::Get( 1, 13 ) : 0; tile.metadata()[2] = cond; if ( cond == static_cast( ArtifactCaptureCondition::PAY_2500_GOLD_AND_3_RESOURCES ) || cond == static_cast( ArtifactCaptureCondition::PAY_3000_GOLD_AND_5_RESOURCES ) ) { // TODO: why do we use icon ICN index instead of map ICN index? tile.metadata()[1] = Resource::getIconIcnIndex( Resource::Rand( false ) ) + 1; } } tile.metadata()[0] = art; break; } case MP2::OBJ_RESOURCE: { assert( isFirstLoad ); int resourceType = Resource::UNKNOWN; if ( tile.getObjectIcnType() == MP2::OBJ_ICN_TYPE_OBJNRSRC ) { // The resource is located at the top. resourceType = Resource::FromIndexSprite( tile.GetObjectSpriteIndex() ); } else { for ( TilesAddon & addon : tile.getBottomLayerAddons() ) { if ( addon._objectIcnType == MP2::OBJ_ICN_TYPE_OBJNRSRC ) { resourceType = Resource::FromIndexSprite( addon._imageIndex ); // If this happens we are in trouble. It looks like that map maker put the resource under an object which is impossible to do. // Let's swap the addon and main tile objects tile.swap( addon ); break; } } } uint32_t count = 0; switch ( resourceType ) { case Resource::GOLD: count = 100 * Rand::Get( 5, 10 ); break; case Resource::WOOD: case Resource::ORE: count = Rand::Get( 5, 10 ); break; case Resource::MERCURY: case Resource::SULFUR: case Resource::CRYSTAL: case Resource::GEMS: count = Rand::Get( 3, 6 ); break; default: // Some maps have broken resources being put which ideally we need to correct. Let's make them 0 Wood. DEBUG_LOG( DBG_GAME, DBG_WARN, "Tile " << tile.GetIndex() << " contains unknown resource type. Object ICN type " << tile.getObjectIcnType() << ", image index " << tile.GetObjectSpriteIndex() ) resourceType = Resource::WOOD; count = 0; break; } setResourceOnTile( tile, resourceType, count ); break; } case MP2::OBJ_CAMPFIRE: assert( isFirstLoad ); // 4-6 random resource and + 400-600 gold setResourceOnTile( tile, Resource::Rand( false ), Rand::Get( 4, 6 ) ); break; case MP2::OBJ_MAGIC_GARDEN: // 5 gems or 500 gold if ( Rand::Get( 1 ) ) setResourceOnTile( tile, Resource::GEMS, 5 ); else setResourceOnTile( tile, Resource::GOLD, 500 ); break; case MP2::OBJ_WATER_WHEEL: // first week 500 gold, next week 1000 gold setResourceOnTile( tile, Resource::GOLD, ( 0 == world.CountDay() ? 500 : 1000 ) ); break; case MP2::OBJ_WINDMILL: { int res = Resource::WOOD; while ( res == Resource::WOOD ) { res = Resource::Rand( false ); } // 2 pieces of random resources. setResourceOnTile( tile, res, 2 ); break; } case MP2::OBJ_LEAN_TO: assert( isFirstLoad ); // 1-4 pieces of random resources. setResourceOnTile( tile, Resource::Rand( false ), Rand::Get( 1, 4 ) ); break; case MP2::OBJ_FLOTSAM: { assert( isFirstLoad ); switch ( Rand::Get( 1, 4 ) ) { // 25%: 500 gold + 10 wood case 1: tile.metadata()[0] = 10; tile.metadata()[1] = 500; break; // 25%: 200 gold + 5 wood case 2: tile.metadata()[0] = 5; tile.metadata()[1] = 200; break; // 25%: 5 wood case 3: tile.metadata()[0] = 5; break; // 25%: empty default: break; } break; } case MP2::OBJ_SHIPWRECK_SURVIVOR: { assert( isFirstLoad ); Rand::Queue percents( 3 ); // 55%: artifact 1 percents.Push( 1, 55 ); // 30%: artifact 2 percents.Push( 2, 30 ); // 15%: artifact 3 percents.Push( 3, 15 ); switch ( percents.Get() ) { case 1: tile.metadata()[0] = Artifact::Rand( Artifact::ART_LEVEL_TREASURE ); break; case 2: tile.metadata()[0] = Artifact::Rand( Artifact::ART_LEVEL_MINOR ); break; case 3: tile.metadata()[0] = Artifact::Rand( Artifact::ART_LEVEL_MAJOR ); break; default: // Check your logic above! assert( 0 ); tile.metadata()[0] = Artifact::UNKNOWN; break; } break; } case MP2::OBJ_SEA_CHEST: { assert( isFirstLoad ); Rand::Queue percents( 3 ); // 20% - empty percents.Push( 0, 20 ); // 70% - 1500 gold percents.Push( 1, 70 ); // 10% - 1000 gold + art percents.Push( 2, 10 ); switch ( percents.Get() ) { case 0: tile.metadata()[0] = Artifact::UNKNOWN; tile.metadata()[1] = 0; break; case 1: tile.metadata()[0] = Artifact::UNKNOWN; tile.metadata()[1] = 1500; break; case 2: tile.metadata()[0] = Artifact::Rand( Artifact::ART_LEVEL_TREASURE ); tile.metadata()[1] = 1000; break; default: // Check your logic above! assert( 0 ); tile.metadata()[0] = Artifact::UNKNOWN; tile.metadata()[1] = 0; break; } break; } case MP2::OBJ_TREASURE_CHEST: assert( isFirstLoad ); if ( tile.isWater() ) { tile.SetObject( MP2::OBJ_SEA_CHEST ); updateObjectInfoTile( tile, isFirstLoad ); return; } { Rand::Queue percents( 4 ); // 31% - 2000 gold or 1500 exp percents.Push( 1, 31 ); // 32% - 1500 gold or 1000 exp percents.Push( 2, 32 ); // 32% - 1000 gold or 500 exp percents.Push( 3, 32 ); // 5% - art percents.Push( 4, 5 ); switch ( percents.Get() ) { case 1: tile.metadata()[0] = Artifact::UNKNOWN; tile.metadata()[1] = 2000; break; case 2: tile.metadata()[0] = Artifact::UNKNOWN; tile.metadata()[1] = 1500; break; case 3: tile.metadata()[0] = Artifact::UNKNOWN; tile.metadata()[1] = 1000; break; case 4: tile.metadata()[0] = Artifact::Rand( Artifact::ART_LEVEL_TREASURE ); tile.metadata()[1] = 0; break; default: // Check your logic above! tile.metadata()[0] = Artifact::UNKNOWN; tile.metadata()[1] = 0; assert( 0 ); break; } } break; case MP2::OBJ_DERELICT_SHIP: assert( isFirstLoad ); setResourceOnTile( tile, Resource::GOLD, 5000 ); break; case MP2::OBJ_SHIPWRECK: { assert( isFirstLoad ); Rand::Queue percents( 4 ); // 40% - 10ghost(1000g) percents.Push( 1, 40 ); // 30% - 15 ghost(2000g) percents.Push( 2, 30 ); // 20% - 25ghost(5000g) percents.Push( 3, 20 ); // 10% - 50ghost(2000g+art) percents.Push( 4, 10 ); tile.metadata()[2] = percents.Get(); switch ( static_cast( tile.metadata()[2] ) ) { case ShipwreckCaptureCondition::FIGHT_10_GHOSTS_AND_GET_1000_GOLD: tile.metadata()[0] = Artifact::UNKNOWN; tile.metadata()[1] = 1000; break; case ShipwreckCaptureCondition::FIGHT_15_GHOSTS_AND_GET_2000_GOLD: tile.metadata()[0] = Artifact::UNKNOWN; tile.metadata()[1] = 2000; break; case ShipwreckCaptureCondition::FIGHT_25_GHOSTS_AND_GET_5000_GOLD: tile.metadata()[0] = Artifact::UNKNOWN; tile.metadata()[1] = 5000; break; case ShipwreckCaptureCondition::FIGHT_50_GHOSTS_AND_GET_2000_GOLD_WITH_ARTIFACT: tile.metadata()[0] = Artifact::Rand( Artifact::ART_LEVEL_ALL_NORMAL ); tile.metadata()[1] = 2000; break; default: // Check your logic above! assert( 0 ); tile.metadata()[0] = Artifact::UNKNOWN; tile.metadata()[1] = 0; break; } break; } case MP2::OBJ_GRAVEYARD: assert( isFirstLoad ); tile.metadata()[0] = Artifact::Rand( Artifact::ART_LEVEL_ALL_NORMAL ); tile.metadata()[1] = 1000; break; case MP2::OBJ_PYRAMID: { assert( isFirstLoad ); // Random spell of level 5. const Spell & spell = Rand::Get( 1 ) ? Spell::RandCombat( 5 ) : Spell::RandAdventure( 5 ); setSpellOnTile( tile, spell.GetID() ); break; } case MP2::OBJ_DAEMON_CAVE: { assert( isFirstLoad ); // 1000 exp or 1000 exp + 2500 gold or 1000 exp + art or (-2500 or remove hero) tile.metadata()[2] = Rand::Get( 1, 4 ); switch ( static_cast( tile.metadata()[2] ) ) { case DaemonCaveCaptureBonus::GET_1000_EXPERIENCE: tile.metadata()[0] = Artifact::UNKNOWN; tile.metadata()[1] = 0; break; case DaemonCaveCaptureBonus::GET_1000_EXPERIENCE_AND_2500_GOLD: tile.metadata()[0] = Artifact::UNKNOWN; tile.metadata()[1] = 2500; break; case DaemonCaveCaptureBonus::GET_1000_EXPERIENCE_AND_ARTIFACT: tile.metadata()[0] = Artifact::Rand( Artifact::ART_LEVEL_ALL_NORMAL ); tile.metadata()[1] = 0; break; case DaemonCaveCaptureBonus::PAY_2500_GOLD: tile.metadata()[0] = Artifact::UNKNOWN; tile.metadata()[1] = 2500; break; default: // Check your logic above! assert( 0 ); tile.metadata()[0] = Artifact::UNKNOWN; tile.metadata()[1] = 0; break; } break; } case MP2::OBJ_TREE_OF_KNOWLEDGE: assert( isFirstLoad ); // variant: 10 gems, 2000 gold or free switch ( Rand::Get( 1, 3 ) ) { case 1: setResourceOnTile( tile, Resource::GEMS, 10 ); break; case 2: setResourceOnTile( tile, Resource::GOLD, 2000 ); break; default: break; } break; case MP2::OBJ_BARRIER: assert( isFirstLoad ); setColorOnTile( tile, getColorFromBarrierSprite( tile.getObjectIcnType(), tile.GetObjectSpriteIndex() ) ); break; case MP2::OBJ_TRAVELLER_TENT: assert( isFirstLoad ); setColorOnTile( tile, getColorFromTravellerTentSprite( tile.getObjectIcnType(), tile.GetObjectSpriteIndex() ) ); break; case MP2::OBJ_ALCHEMIST_LAB: { assert( isFirstLoad ); const auto resourceCount = fheroes2::checkedCast( ProfitConditions::FromMine( Resource::MERCURY ).mercury ); assert( resourceCount.has_value() && resourceCount > 0U ); setResourceOnTile( tile, Resource::MERCURY, resourceCount.value() ); break; } case MP2::OBJ_SAWMILL: { assert( isFirstLoad ); const auto resourceCount = fheroes2::checkedCast( ProfitConditions::FromMine( Resource::WOOD ).wood ); assert( resourceCount.has_value() && resourceCount > 0U ); setResourceOnTile( tile, Resource::WOOD, resourceCount.value() ); break; } case MP2::OBJ_MINE: { assert( isFirstLoad ); switch ( tile.GetObjectSpriteIndex() ) { case 0: { const auto resourceCount = fheroes2::checkedCast( ProfitConditions::FromMine( Resource::ORE ).ore ); assert( resourceCount.has_value() && resourceCount > 0U ); setResourceOnTile( tile, Resource::ORE, resourceCount.value() ); break; } case 1: { const auto resourceCount = fheroes2::checkedCast( ProfitConditions::FromMine( Resource::SULFUR ).sulfur ); assert( resourceCount.has_value() && resourceCount > 0U ); setResourceOnTile( tile, Resource::SULFUR, resourceCount.value() ); break; } case 2: { const auto resourceCount = fheroes2::checkedCast( ProfitConditions::FromMine( Resource::CRYSTAL ).crystal ); assert( resourceCount.has_value() && resourceCount > 0U ); setResourceOnTile( tile, Resource::CRYSTAL, resourceCount.value() ); break; } case 3: { const auto resourceCount = fheroes2::checkedCast( ProfitConditions::FromMine( Resource::GEMS ).gems ); assert( resourceCount.has_value() && resourceCount > 0U ); setResourceOnTile( tile, Resource::GEMS, resourceCount.value() ); break; } case 4: { const auto resourceCount = fheroes2::checkedCast( ProfitConditions::FromMine( Resource::GOLD ).gold ); assert( resourceCount.has_value() && resourceCount > 0U ); setResourceOnTile( tile, Resource::GOLD, resourceCount.value() ); break; } default: // This is an unknown mine type. Most likely it was added by some hex editing. tile.SetObject( MP2::OBJ_NONE ); break; } break; } case MP2::OBJ_ABANDONED_MINE: // The number of Ghosts is set only when loading the map and does not change anymore. if ( isFirstLoad ) { setMonsterCountOnTile( tile, Rand::Get( 30, 60 ) ); } break; case MP2::OBJ_BOAT: assert( isFirstLoad ); // This is a special case. Boats are different in the original editor. tile.setObjectIcnType( MP2::OBJ_ICN_TYPE_BOAT32 ); tile.setObjectSpriteIndex( 18 ); break; case MP2::OBJ_RANDOM_ARTIFACT: case MP2::OBJ_RANDOM_ARTIFACT_TREASURE: case MP2::OBJ_RANDOM_ARTIFACT_MINOR: case MP2::OBJ_RANDOM_ARTIFACT_MAJOR: assert( isFirstLoad ); updateRandomArtifact( tile ); updateObjectInfoTile( tile, isFirstLoad ); return; case MP2::OBJ_RANDOM_RESOURCE: assert( isFirstLoad ); updateRandomResource( tile ); updateObjectInfoTile( tile, isFirstLoad ); return; case MP2::OBJ_MONSTER: if ( world.CountWeek() > 1 ) updateMonsterPopulationOnTile( tile ); else updateMonsterInfoOnTile( tile ); break; case MP2::OBJ_RANDOM_MONSTER: case MP2::OBJ_RANDOM_MONSTER_WEAK: case MP2::OBJ_RANDOM_MONSTER_MEDIUM: case MP2::OBJ_RANDOM_MONSTER_STRONG: case MP2::OBJ_RANDOM_MONSTER_VERY_STRONG: assert( isFirstLoad ); updateRandomMonster( tile ); updateObjectInfoTile( tile, isFirstLoad ); return; case MP2::OBJ_GENIE_LAMP: // The number of Genies is set when loading the map and does not change anymore if ( isFirstLoad ) { setMonsterCountOnTile( tile, Rand::Get( 2, 4 ) ); } break; case MP2::OBJ_AIR_ALTAR: case MP2::OBJ_ARCHER_HOUSE: case MP2::OBJ_BARROW_MOUNDS: case MP2::OBJ_CAVE: case MP2::OBJ_CITY_OF_DEAD: case MP2::OBJ_DESERT_TENT: case MP2::OBJ_DRAGON_CITY: case MP2::OBJ_DWARF_COTTAGE: case MP2::OBJ_EARTH_ALTAR: case MP2::OBJ_EXCAVATION: case MP2::OBJ_FIRE_ALTAR: case MP2::OBJ_GOBLIN_HUT: case MP2::OBJ_HALFLING_HOLE: case MP2::OBJ_PEASANT_HUT: case MP2::OBJ_RUINS: case MP2::OBJ_TREE_CITY: case MP2::OBJ_TREE_HOUSE: case MP2::OBJ_TROLL_BRIDGE: case MP2::OBJ_WAGON_CAMP: case MP2::OBJ_WATCH_TOWER: case MP2::OBJ_WATER_ALTAR: updateDwellingPopulationOnTile( tile, isFirstLoad ); break; case MP2::OBJ_EVENT: assert( isFirstLoad ); // Event should be invisible on Adventure Map. tile.resetObjectSprite(); resetObjectMetadata( tile ); break; default: if ( isFirstLoad ) { resetObjectMetadata( tile ); } break; } } void updateMonsterInfoOnTile( Tiles & tile ) { const Monster mons = Monster( tile.GetObjectSpriteIndex() + 1 ); // ICN::MONS32 start from PEASANT setMonsterOnTile( tile, mons, tile.metadata()[0] ); } void setMonsterOnTile( Tiles & tile, const Monster & mons, const uint32_t count ) { tile.SetObject( MP2::OBJ_MONSTER ); if ( tile.getObjectIcnType() == MP2::OBJ_ICN_TYPE_UNKNOWN ) { // No object exists on this tile. Add one. tile.setObjectUID( getNewObjectUID() ); tile.setObjectIcnType( MP2::OBJ_ICN_TYPE_MONS32 ); } else if ( tile.getObjectIcnType() != MP2::OBJ_ICN_TYPE_MONS32 ) { // If there is another object sprite here (shadow for example) push it down to add-ons. tile.pushBottomLayerAddon( TilesAddon( tile.getLayerType(), tile.GetObjectUID(), tile.getObjectIcnType(), tile.GetObjectSpriteIndex() ) ); // Set unique UID for placed monster. tile.setObjectUID( getNewObjectUID() ); tile.setObjectIcnType( MP2::OBJ_ICN_TYPE_MONS32 ); } using TileImageIndexType = decltype( tile.GetObjectSpriteIndex() ); static_assert( std::is_same_v, "Type of GetObjectSpriteIndex() has been changed, check the logic below" ); const uint32_t monsSpriteIndex = mons.GetSpriteIndex(); assert( monsSpriteIndex >= std::numeric_limits::min() && monsSpriteIndex <= std::numeric_limits::max() ); tile.setObjectSpriteIndex( static_cast( monsSpriteIndex ) ); const bool setDefinedCount = ( count > 0 ); if ( setDefinedCount ) { setMonsterCountOnTile( tile, count ); } else { setMonsterCountOnTile( tile, mons.GetRNDSize() ); } if ( mons.GetID() == Monster::GHOST || mons.isElemental() ) { // Ghosts and elementals never join hero's army. setMonsterOnTileJoinCondition( tile, Monster::JOIN_CONDITION_SKIP ); } else if ( setDefinedCount || ( world.GetWeekType().GetType() == WeekName::MONSTERS && world.GetWeekType().GetMonster() == mons.GetID() ) ) { // Wandering monsters with the number of units specified by the map designer are always considered as "hostile" and always join only for money. // Monsters will be willing to join for some amount of money. setMonsterOnTileJoinCondition( tile, Monster::JOIN_CONDITION_MONEY ); } else { // 20% chance for join if ( 3 > Rand::Get( 1, 10 ) ) { setMonsterOnTileJoinCondition( tile, Monster::JOIN_CONDITION_FREE ); } else { setMonsterOnTileJoinCondition( tile, Monster::JOIN_CONDITION_MONEY ); } } } std::pair getColorRaceFromHeroSprite( const uint32_t heroSpriteIndex ) { std::pair res; if ( 7 > heroSpriteIndex ) res.first = Color::BLUE; else if ( 14 > heroSpriteIndex ) res.first = Color::GREEN; else if ( 21 > heroSpriteIndex ) res.first = Color::RED; else if ( 28 > heroSpriteIndex ) res.first = Color::YELLOW; else if ( 35 > heroSpriteIndex ) res.first = Color::ORANGE; else res.first = Color::PURPLE; switch ( heroSpriteIndex % 7 ) { case 0: res.second = Race::KNGT; break; case 1: res.second = Race::BARB; break; case 2: res.second = Race::SORC; break; case 3: res.second = Race::WRLK; break; case 4: res.second = Race::WZRD; break; case 5: res.second = Race::NECR; break; case 6: res.second = Race::RAND; break; default: assert( 0 ); break; } return res; } bool isCaptureObjectProtected( const Tiles & tile ) { const MP2::MapObjectType objectType = tile.GetObject( false ); if ( !MP2::isCaptureObject( objectType ) ) { return false; } if ( MP2::OBJ_CASTLE == objectType ) { Castle * castle = world.getCastleEntrance( tile.GetCenter() ); assert( castle != nullptr ); if ( castle ) { return castle->GetArmy().isValid(); } return false; } return getTroopFromTile( tile ).isValid(); } void restoreAbandonedMine( Tiles & tile, const int resource ) { assert( tile.GetObject( false ) == MP2::OBJ_ABANDONED_MINE ); assert( tile.GetObjectUID() != 0 ); const Funds info = ProfitConditions::FromMine( resource ); std::optional resourceCount; switch ( resource ) { case Resource::ORE: resourceCount = fheroes2::checkedCast( info.ore ); break; case Resource::SULFUR: resourceCount = fheroes2::checkedCast( info.sulfur ); break; case Resource::CRYSTAL: resourceCount = fheroes2::checkedCast( info.crystal ); break; case Resource::GEMS: resourceCount = fheroes2::checkedCast( info.gems ); break; case Resource::GOLD: resourceCount = fheroes2::checkedCast( info.gold ); break; default: assert( 0 ); break; } assert( resourceCount.has_value() && resourceCount > 0U ); setResourceOnTile( tile, resource, resourceCount.value() ); const auto restoreLeftSprite = [resource]( MP2::ObjectIcnType & objectIcnType, uint8_t & imageIndex ) { if ( MP2::OBJ_ICN_TYPE_OBJNGRAS == objectIcnType && imageIndex == 6 ) { objectIcnType = MP2::OBJ_ICN_TYPE_MTNGRAS; imageIndex = 82; } else if ( MP2::OBJ_ICN_TYPE_OBJNDIRT == objectIcnType && imageIndex == 8 ) { objectIcnType = MP2::OBJ_ICN_TYPE_MTNDIRT; imageIndex = 112; } else if ( MP2::OBJ_ICN_TYPE_EXTRAOVR == objectIcnType && imageIndex == 5 ) { switch ( resource ) { case Resource::ORE: imageIndex = 0; break; case Resource::SULFUR: imageIndex = 1; break; case Resource::CRYSTAL: imageIndex = 2; break; case Resource::GEMS: imageIndex = 3; break; case Resource::GOLD: imageIndex = 4; break; default: break; } } }; const auto restoreRightSprite = []( MP2::ObjectIcnType & objectIcnType, uint8_t & imageIndex ) { if ( MP2::OBJ_ICN_TYPE_OBJNDIRT == objectIcnType && imageIndex == 9 ) { objectIcnType = MP2::OBJ_ICN_TYPE_MTNDIRT; imageIndex = 113; } else if ( MP2::OBJ_ICN_TYPE_OBJNGRAS == objectIcnType && imageIndex == 7 ) { objectIcnType = MP2::OBJ_ICN_TYPE_MTNGRAS; imageIndex = 83; } }; const auto restoreMineObjectType = [&tile]( int directionVector ) { if ( Maps::isValidDirection( tile.GetIndex(), directionVector ) ) { Tiles & mineTile = world.GetTiles( Maps::GetDirectionIndex( tile.GetIndex(), directionVector ) ); if ( ( mineTile.GetObject() == MP2::OBJ_NON_ACTION_ABANDONED_MINE ) && ( mineTile.GetObjectUID() == tile.GetObjectUID() || mineTile.getBottomLayerAddon( tile.GetObjectUID() ) || mineTile.getTopLayerAddon( tile.GetObjectUID() ) ) ) { mineTile.SetObject( MP2::OBJ_NON_ACTION_MINE ); } } }; MP2::ObjectIcnType objectIcnTypeTemp{ tile.getObjectIcnType() }; uint8_t imageIndexTemp{ tile.GetObjectSpriteIndex() }; restoreLeftSprite( objectIcnTypeTemp, imageIndexTemp ); tile.setObjectIcnType( objectIcnTypeTemp ); tile.setObjectSpriteIndex( imageIndexTemp ); for ( TilesAddon & addon : tile.getBottomLayerAddons() ) { if ( addon._uid == tile.GetObjectUID() ) { restoreLeftSprite( addon._objectIcnType, addon._imageIndex ); } } if ( Maps::isValidDirection( tile.GetIndex(), Direction::RIGHT ) ) { Tiles & rightTile = world.GetTiles( Maps::GetDirectionIndex( tile.GetIndex(), Direction::RIGHT ) ); if ( rightTile.GetObjectUID() == tile.GetObjectUID() ) { objectIcnTypeTemp = rightTile.getObjectIcnType(); imageIndexTemp = rightTile.GetObjectSpriteIndex(); restoreRightSprite( objectIcnTypeTemp, imageIndexTemp ); rightTile.setObjectIcnType( objectIcnTypeTemp ); rightTile.setObjectSpriteIndex( imageIndexTemp ); } TilesAddon * addon = rightTile.getBottomLayerAddon( tile.GetObjectUID() ); if ( addon ) { restoreRightSprite( addon->_objectIcnType, addon->_imageIndex ); } } restoreMineObjectType( Direction::LEFT ); restoreMineObjectType( Direction::RIGHT ); restoreMineObjectType( Direction::TOP ); restoreMineObjectType( Direction::TOP_LEFT ); restoreMineObjectType( Direction::TOP_RIGHT ); } void removeObjectSprite( Tiles & tile ) { switch ( tile.GetObject() ) { case MP2::OBJ_MONSTER: tile.Remove( tile.GetObjectUID() ); break; case MP2::OBJ_JAIL: removeJailSprite( tile ); tile.resetPassability(); break; case MP2::OBJ_ARTIFACT: { const uint32_t uidArtifact = tile.getObjectIdByObjectIcnType( MP2::OBJ_ICN_TYPE_OBJNARTI ); tile.Remove( uidArtifact ); if ( Maps::isValidDirection( tile.GetIndex(), Direction::LEFT ) ) world.GetTiles( Maps::GetDirectionIndex( tile.GetIndex(), Direction::LEFT ) ).Remove( uidArtifact ); break; } case MP2::OBJ_TREASURE_CHEST: case MP2::OBJ_RESOURCE: { const uint32_t uidResource = tile.getObjectIdByObjectIcnType( MP2::OBJ_ICN_TYPE_OBJNRSRC ); tile.Remove( uidResource ); if ( Maps::isValidDirection( tile.GetIndex(), Direction::LEFT ) ) world.GetTiles( Maps::GetDirectionIndex( tile.GetIndex(), Direction::LEFT ) ).Remove( uidResource ); break; } case MP2::OBJ_BARRIER: tile.resetPassability(); [[fallthrough]]; default: // remove shadow sprite from left cell if ( Maps::isValidDirection( tile.GetIndex(), Direction::LEFT ) ) world.GetTiles( Maps::GetDirectionIndex( tile.GetIndex(), Direction::LEFT ) ).Remove( tile.GetObjectUID() ); tile.Remove( tile.GetObjectUID() ); break; } } bool isClearGround( const Tiles & tile ) { const MP2::MapObjectType objectType = tile.GetObject( true ); switch ( objectType ) { case MP2::OBJ_NONE: case MP2::OBJ_COAST: return true; case MP2::OBJ_BOAT: return false; default: break; } if ( ( tile.getObjectIcnType() == MP2::OBJ_ICN_TYPE_UNKNOWN ) || ( tile.getLayerType() == Maps::SHADOW_LAYER ) || ( tile.getLayerType() == Maps::TERRAIN_LAYER ) ) { return !MP2::isActionObject( objectType, tile.isWater() ); } return false; } void updateFogDirectionsInArea( const fheroes2::Point & minPos, const fheroes2::Point & maxPos, const int32_t color ) { assert( ( minPos.x <= maxPos.x ) && ( minPos.y <= maxPos.y ) ); const int32_t worldWidth = world.w(); const int32_t worldHeight = world.w(); // Do not get over the world borders. const int32_t minX = std::max( minPos.x, 0 ); const int32_t minY = std::max( minPos.y, 0 ); // Add extra 1 to reach the given maxPos point. const int32_t maxX = std::min( maxPos.x + 1, worldWidth ); const int32_t maxY = std::min( maxPos.y + 1, worldHeight ); // Fog data range is 1 tile bigger from each side as for the fog directions we have to check all tiles around each tile in the area. const int32_t fogMinX = std::max( minX - 1, 0 ); const int32_t fogMinY = std::max( minY - 1, 0 ); const int32_t fogMaxX = std::min( maxX + 1, worldWidth ); const int32_t fogMaxY = std::min( maxY + 1, worldHeight ); const int32_t fogDataWidth = maxX - minX + 2; const int32_t fogDataSize = fogDataWidth * ( maxY - minY + 2 ); // A vector to cache 'isFog()' data. This vector type is not as using std::vector gives the higher performance. // 1 is for the 'true' state, 0 is for the 'false' state. std::vector fogData( fogDataSize, 1 ); // Set the 'fogData' index offset from the tile index. const int32_t fogDataOffset = 1 - minX + ( 1 - minY ) * fogDataWidth; // Cache the 'fogData' data for the given area to use it in fog direction calculation. // The loops run only within the world area, if 'fogData' area includes tiles outside the world borders we do not update them as the are already set to 1. for ( int32_t y = fogMinY; y < fogMaxY; ++y ) { const int32_t fogTileOffsetY = y * worldWidth; const int32_t fogDataOffsetY = y * fogDataWidth + fogDataOffset; for ( int32_t x = fogMinX; x < fogMaxX; ++x ) { fogData[x + fogDataOffsetY] = world.GetTiles( x + fogTileOffsetY ).isFog( color ) ? 1 : 0; } } // Set the 'fogData' index offset from the tile index for the TOP LEFT direction from the tile. const int32_t topLeftDirectionOffset = -1 - fogDataWidth; #ifndef NDEBUG // Cache the maximum border for fogDataIndex corresponding the "CENTER" tile to use in assertion. Should be removed if assertion is removed. const int32_t centerfogDataIndexLimit = fogDataSize + topLeftDirectionOffset; #endif // Calculate fog directions using the cached 'isFog' data. for ( int32_t y = minY; y < maxY; ++y ) { const int32_t fogCenterDataOffsetY = y * fogDataWidth + fogDataOffset; for ( int32_t x = minX; x < maxX; ++x ) { Maps::Tiles & tile = world.GetTiles( x, y ); int32_t fogDataIndex = x + fogCenterDataOffsetY; if ( fogData[fogDataIndex] == 0 ) { // For the tile is without fog we set the UNKNOWN direction. tile.setFogDirection( Direction::UNKNOWN ); } else { // The tile is under the fog so its CENTER direction for fog is true. uint16_t fogDirection = Direction::CENTER; // 'fogDataIndex' should not get out of maximum 'fogData' vector index after all increments. assert( fogDataIndex < centerfogDataIndexLimit ); // Check all tiles around for 'fogData' starting from the top left direction and if it is true then logically add the direction to 'fogDirection'. fogDataIndex += topLeftDirectionOffset; assert( fogDataIndex >= 0 ); if ( fogData[fogDataIndex] == 1 ) { fogDirection |= Direction::TOP_LEFT; } ++fogDataIndex; if ( fogData[fogDataIndex] == 1 ) { fogDirection |= Direction::TOP; } ++fogDataIndex; if ( fogData[fogDataIndex] == 1 ) { fogDirection |= Direction::TOP_RIGHT; } // Set index to the left direction tile of the next fog data raw. fogDataIndex += fogDataWidth - 2; if ( fogData[fogDataIndex] == 1 ) { fogDirection |= Direction::LEFT; } // Skip the center tile as it was already checked. fogDataIndex += 2; if ( fogData[fogDataIndex] == 1 ) { fogDirection |= Direction::RIGHT; } fogDataIndex += fogDataWidth - 2; if ( fogData[fogDataIndex] == 1 ) { fogDirection |= Direction::BOTTOM_LEFT; } ++fogDataIndex; if ( fogData[fogDataIndex] == 1 ) { fogDirection |= Direction::BOTTOM; } ++fogDataIndex; if ( fogData[fogDataIndex] == 1 ) { fogDirection |= Direction::BOTTOM_RIGHT; } tile.setFogDirection( fogDirection ); } } } } bool removeObjectTypeFromTile( Tiles & tile, const MP2::ObjectIcnType objectIcnType ) { if ( tile.getObjectIdByObjectIcnType( objectIcnType ) == 0 ) { // There is no such object on this tile. return false; } // TODO: Improve this code to remove the multi-tile objects. tile.removeObjects( objectIcnType ); // For some objects we should set tile object type as empty and reset the metadata. switch ( objectIcnType ) { case MP2::OBJ_ICN_TYPE_MONS32: case MP2::OBJ_ICN_TYPE_MINIHERO: resetObjectMetadata( tile ); tile.setAsEmpty(); break; default: break; } return true; } bool removeObject( Tiles & tile, const uint32_t uid ) { if ( uid == 0 ) { // There is no such object on this tile. return false; } tile.Remove( uid ); // An object may occupy several tiles, we recursively check all around tiles for its parts by UID. removeUidFromTilesAround( tile.GetIndex(), uid ); resetObjectMetadata( tile ); // TODO: Improve this code to properly update '_mainObjectType' in the case when // there is a non-action objects placed under the current (action) object. tile.setAsEmpty(); return true; } bool eraseObjectsOnTiles( const int32_t startTileId, const int32_t endTileId, const uint32_t objectTypesToErase ) { if ( objectTypesToErase == ObjectErasureType::NONE ) { // Nothing to erase. return false; } const int32_t mapWidth = world.w(); const int32_t maxTileId = mapWidth * world.h() - 1; if ( startTileId < 0 || startTileId > maxTileId || endTileId < 0 || endTileId > maxTileId ) { return false; } const fheroes2::Point startTileOffset = GetPoint( startTileId ); const fheroes2::Point endTileOffset = GetPoint( endTileId ); const int32_t startX = std::min( startTileOffset.x, endTileOffset.x ); const int32_t startY = std::min( startTileOffset.y, endTileOffset.y ); const int32_t endX = std::max( startTileOffset.x, endTileOffset.x ); const int32_t endY = std::max( startTileOffset.y, endTileOffset.y ); bool needRedraw = false; for ( int32_t y = startY; y <= endY; ++y ) { const int32_t tileOffset = y * mapWidth; for ( int32_t x = startX; x <= endX; ++x ) { needRedraw |= eraseOjects( world.GetTiles( x + tileOffset ), objectTypesToErase ); } } return needRedraw; } bool eraseOjects( Tiles & tile, const uint32_t objectTypesToErase ) { if ( objectTypesToErase == ObjectErasureType::NONE ) { // Nothing to erase. return false; } bool needRedraw = false; if ( objectTypesToErase & ObjectErasureType::ROADS ) { needRedraw |= updateRoadOnTile( tile, false ); } if ( objectTypesToErase & ObjectErasureType::STREAMS ) { needRedraw |= updateStreamOnTile( tile, false ); } if ( objectTypesToErase & ObjectErasureType::MONSTERS ) { needRedraw |= removeObjectTypeFromTile( tile, MP2::OBJ_ICN_TYPE_MONS32 ); } if ( objectTypesToErase & ObjectErasureType::HEROES ) { // TODO: Implement hero removal from other objects (castles, windmills, mines, etc.) // without corrupting their object data. Do this through 'OBJ_HERO' (possibly like 'hero.Dismiss()'). needRedraw |= removeObjectTypeFromTile( tile, MP2::OBJ_ICN_TYPE_MINIHERO ); } if ( objectTypesToErase & ObjectErasureType::TREASURES && tile.getObjectIcnType() == MP2::OBJ_ICN_TYPE_OBJNRSRC ) { needRedraw |= removeObject( tile, tile.GetObjectUID() ); } if ( objectTypesToErase & ObjectErasureType::ARTIFACTS && tile.getObjectIcnType() == MP2::OBJ_ICN_TYPE_OBJNARTI ) { needRedraw |= removeObject( tile, tile.GetObjectUID() ); } return needRedraw; } bool setObjectOnTile( Tiles & tile, const ObjectInfo & info, const bool updateMapPassabilities ) { assert( !info.empty() ); switch ( info.objectType ) { case MP2::OBJ_MONSTER: setMonsterOnTile( tile, static_cast( info.metadata[0] ), 0 ); // Since setMonsterOnTile() function interprets 0 as a random number of monsters it is important to set the correct value. setMonsterCountOnTile( tile, 0 ); return true; case MP2::OBJ_RESOURCE: // Setting just 1 resource is enough. It doesn't matter as we are not saving this value into the map format. if ( !placeObjectOnTile( tile, info ) ) { return false; } setResourceOnTile( tile, static_cast( info.metadata[0] ), 1 ); return true; case MP2::OBJ_ARTIFACT: if ( !placeObjectOnTile( tile, info ) ) { return false; } // The artifact ID is stored in metadata[0]. It is used by the other engine functions. tile.metadata()[0] = info.metadata[0]; return true; case MP2::OBJ_ALCHEMIST_LAB: case MP2::OBJ_MINE: case MP2::OBJ_SAWMILL: if ( !placeObjectOnTile( tile, info ) ) { return false; } // Set resource type and income per day. tile.metadata()[0] = info.metadata[0]; tile.metadata()[1] = info.metadata[1]; if ( updateMapPassabilities ) { world.updatePassabilities(); } return true; default: break; } if ( !placeObjectOnTile( tile, info ) ) { return false; } if ( updateMapPassabilities ) { world.updatePassabilities(); } return true; } } fheroes2-1.0.12+dfsg/src/fheroes2/maps/maps_tiles_helper.h000066400000000000000000000173561456075706000233710ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include "army_troop.h" #include "artifact.h" #include "math_base.h" #include "mp2.h" #include "resource.h" #include "skill.h" class Monster; class Spell; namespace Maps { class Tiles; struct ObjectInfo; // ATTENTION: If you add any new enumeration make sure that value 0 corresponds to empty / visited object // so we don't need to write special logic in resetObjectInfoOnTile(). enum class ArtifactCaptureCondition : uint32_t { NO_CONDITIONS = 0, PAY_2000_GOLD = 1, PAY_2500_GOLD_AND_3_RESOURCES = 2, PAY_3000_GOLD_AND_5_RESOURCES = 3, HAVE_WISDOM_SKILL = 4, HAVE_LEADERSHIP_SKILL = 5, FIGHT_50_ROGUES = 6, FIGHT_1_GENIE = 7, FIGHT_1_PALADIN = 8, FIGHT_1_CYCLOPS = 9, FIGHT_1_PHOENIX = 10, FIGHT_1_GREEN_DRAGON = 11, FIGHT_1_TITAN = 12, FIGHT_1_BONE_DRAGON = 13, CONTAINS_SPELL = 15 }; enum class DaemonCaveCaptureBonus : uint32_t { EMPTY = 0, GET_1000_EXPERIENCE = 1, GET_1000_EXPERIENCE_AND_2500_GOLD = 2, GET_1000_EXPERIENCE_AND_ARTIFACT = 3, PAY_2500_GOLD = 4 }; enum class ShipwreckCaptureCondition : uint32_t { EMPTY = 0, FIGHT_10_GHOSTS_AND_GET_1000_GOLD = 1, FIGHT_15_GHOSTS_AND_GET_2000_GOLD = 2, FIGHT_25_GHOSTS_AND_GET_5000_GOLD = 3, FIGHT_50_GHOSTS_AND_GET_2000_GOLD_WITH_ARTIFACT = 4 }; enum ObjectErasureType : uint32_t { NONE = 0x00, // Terrain objects are objects that are placed in editor using a terrain palette in objects placing mode. TERRAIN_OBJECTS = 0x01, CASTLES = 0x02, MONSTERS = 0x04, HEROES = 0x08, ARTIFACTS = 0x10, STREAMS = 0x20, ROADS = 0x40, TREASURES = 0x80, ALL_OBJECTS = TERRAIN_OBJECTS | CASTLES | MONSTERS | HEROES | ARTIFACTS | STREAMS | ROADS | TREASURES, }; // Only for MP2::OBJ_MINE. int32_t getMineSpellIdFromTile( const Tiles & tile ); void setMineSpellOnTile( Tiles & tile, const int32_t spellId ); void removeMineSpellFromTile( Tiles & tile ); Funds getDailyIncomeObjectResources( const Tiles & tile ); // Only for objects which always have spell(s): Shrine and Pyramid. Spell getSpellFromTile( const Tiles & tile ); void setSpellOnTile( Tiles & tile, const int spellId ); // Only for MP2::OBJ_MONSTER. void setMonsterOnTileJoinCondition( Tiles & tile, const int32_t condition ); bool isMonsterOnTileJoinConditionSkip( const Tiles & tile ); bool isMonsterOnTileJoinConditionFree( const Tiles & tile ); int getColorFromBarrierSprite( const MP2::ObjectIcnType objectIcnType, const uint8_t icnIndex ); int getColorFromTravellerTentSprite( const MP2::ObjectIcnType objectIcnType, const uint8_t icnIndex ); Monster getMonsterFromTile( const Tiles & tile ); Artifact getArtifactFromTile( const Tiles & tile ); Skill::Secondary getArtifactSecondarySkillRequirement( const Tiles & tile ); ArtifactCaptureCondition getArtifactCaptureCondition( const Tiles & tile ); Funds getArtifactResourceRequirement( const Tiles & tile ); DaemonCaveCaptureBonus getDaemonCaveBonusType( const Tiles & tile ); Funds getDaemonPaymentCondition( const Tiles & tile ); ShipwreckCaptureCondition getShipwreckCaptureCondition( const Tiles & tile ); Funds getTreeOfKnowledgeRequirement( const Tiles & tile ); // Only for MP2::OBJ_WITCHS_HUT. Skill::Secondary getSecondarySkillFromWitchsHut( const Tiles & tile ); void setResourceOnTile( Tiles & tile, const int resourceType, uint32_t value ); Funds getFundsFromTile( const Tiles & tile ); Troop getTroopFromTile( const Tiles & tile ); int getColorFromTile( const Tiles & tile ); void setColorOnTile( Tiles & tile, const int color ); bool doesTileContainValuableItems( const Tiles & tile ); void resetObjectMetadata( Tiles & tile ); void resetObjectInfoOnTile( Tiles & tile ); uint32_t getMonsterCountFromTile( const Tiles & tile ); void setMonsterCountOnTile( Tiles & tile, uint32_t count ); void updateDwellingPopulationOnTile( Tiles & tile, const bool isFirstLoad ); void updateObjectInfoTile( Tiles & tile, const bool isFirstLoad ); void updateMonsterInfoOnTile( Tiles & tile ); void setMonsterOnTile( Tiles & tile, const Monster & mons, const uint32_t count ); std::pair getColorRaceFromHeroSprite( const uint32_t heroSpriteIndex ); // Checks whether the object to be captured is guarded by its own forces // (castle has a hero or garrison, dwelling has creatures, etc) bool isCaptureObjectProtected( const Tiles & tile ); // Restores an abandoned mine whose main tile is 'tile', turning it into an ordinary mine that brings // resources of type 'resource'. This method updates all sprites and sets object types for non-action // tiles. The object type for the action tile (i.e. the main tile) remains unchanged and should be // updated separately. void restoreAbandonedMine( Tiles & tile, const int resource ); void removeObjectSprite( Tiles & tile ); bool isClearGround( const Tiles & tile ); // Determine the fog direction in the area between min and max positions for given player(s) color code and store it in corresponding tile data. void updateFogDirectionsInArea( const fheroes2::Point & minPos, const fheroes2::Point & maxPos, const int32_t color ); // The functions below are used only in the map Editor. void setTerrainOnTiles( const int32_t startTileId, const int32_t endTileId, const int groundId ); bool updateRoadOnTile( Tiles & tile, const bool setRoad ); bool updateStreamOnTile( Tiles & tile, const bool setStream ); // Removes object and all its parts in around tiles by UID. Returns true is object is found and removed. bool removeObject( Tiles & tile, const uint32_t uid ); bool removeObjectTypeFromTile( Tiles & tile, const MP2::ObjectIcnType objectIcnType ); bool eraseObjectsOnTiles( const int32_t startTileId, const int32_t endTileId, const uint32_t objectTypesToErase ); bool eraseOjects( Tiles & tile, const uint32_t objectTypesToErase ); bool setObjectOnTile( Tiles & tile, const ObjectInfo & info, const bool updateMapPassabilities ); } fheroes2-1.0.12+dfsg/src/fheroes2/maps/maps_tiles_render.cpp000066400000000000000000001645301456075706000237210ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "maps_tiles_render.h" #include #include #include #include #include #include #include #include #include #include #include #include "agg_image.h" #include "color.h" #include "direction.h" #include "game.h" #include "heroes.h" #include "icn.h" #include "image.h" #include "interface_gamearea.h" #include "logging.h" #include "maps.h" #include "maps_tiles.h" #include "maps_tiles_helper.h" #include "monster.h" #include "monster_anim.h" #include "race.h" #include "spell.h" #include "til.h" #include "ui_object_rendering.h" #include "world.h" namespace { const fheroes2::Point monsterImageOffset{ 16, 30 }; bool contains( const int base, const int value ) { return ( base & value ) == value; } bool isTileDirectRenderingRestricted( const int icnId, const MP2::MapObjectType objectType ) { switch ( icnId ) { case ICN::UNKNOWN: case ICN::BOAT32: case ICN::MINIHERO: // Either it is an invalid sprite or a sprite which needs to be divided into tiles in order to properly render it. return true; case ICN::MONS32: // Random monsters must be displayed for the Editor. return objectType != MP2::OBJ_RANDOM_MONSTER && objectType != MP2::OBJ_RANDOM_MONSTER_WEAK && objectType != MP2::OBJ_RANDOM_MONSTER_MEDIUM && objectType != MP2::OBJ_RANDOM_MONSTER_STRONG && objectType != MP2::OBJ_RANDOM_MONSTER_VERY_STRONG; default: break; } return false; } bool isAddonDirectRenderingRestricted( const int icnId ) { switch ( icnId ) { case ICN::UNKNOWN: case ICN::BOAT32: case ICN::MINIHERO: case ICN::MONS32: // Either it is an invalid sprite or a sprite which needs to be divided into tiles in order to properly render it. return true; default: break; } return false; } void renderAddonObject( fheroes2::Image & output, const Interface::GameArea & area, const fheroes2::Point & offset, const Maps::TilesAddon & addon ) { assert( addon._objectIcnType != MP2::OBJ_ICN_TYPE_UNKNOWN && addon._imageIndex != 255 ); const int icn = MP2::getIcnIdFromObjectIcnType( addon._objectIcnType ); if ( isAddonDirectRenderingRestricted( icn ) ) { return; } const uint8_t alphaValue = area.getObjectAlphaValue( addon._uid ); const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( icn, addon._imageIndex ); // Ideally we need to check that the image is within a tile area. However, flags are among those for which this rule doesn't apply. if ( icn == ICN::FLAG32 ) { assert( sprite.width() <= TILEWIDTH && sprite.height() <= TILEWIDTH ); } else { assert( sprite.x() >= 0 && sprite.width() + sprite.x() <= TILEWIDTH && sprite.y() >= 0 && sprite.height() + sprite.y() <= TILEWIDTH ); } area.BlitOnTile( output, sprite, sprite.x(), sprite.y(), offset, false, alphaValue ); const uint32_t animationIndex = ICN::AnimationFrame( icn, addon._imageIndex, Game::getAdventureMapAnimationIndex() ); if ( animationIndex > 0 ) { const fheroes2::Sprite & animationSprite = fheroes2::AGG::GetICN( icn, animationIndex ); // If this assertion blows up we are trying to render an image bigger than a tile. Render this object properly as heroes or monsters! assert( animationSprite.x() >= 0 && animationSprite.width() + animationSprite.x() <= TILEWIDTH && animationSprite.y() >= 0 && animationSprite.height() + animationSprite.y() <= TILEWIDTH ); area.BlitOnTile( output, animationSprite, animationSprite.x(), animationSprite.y(), offset, false, alphaValue ); } } void renderMainObject( fheroes2::Image & output, const Interface::GameArea & area, const fheroes2::Point & offset, const Maps::Tiles & tile ) { assert( tile.getObjectIcnType() != MP2::OBJ_ICN_TYPE_UNKNOWN && tile.GetObjectSpriteIndex() != 255 ); const int mainObjectIcn = MP2::getIcnIdFromObjectIcnType( tile.getObjectIcnType() ); if ( isTileDirectRenderingRestricted( mainObjectIcn, tile.GetObject() ) ) { return; } const uint8_t mainObjectAlphaValue = area.getObjectAlphaValue( tile.GetObjectUID() ); const fheroes2::Sprite & mainObjectSprite = fheroes2::AGG::GetICN( mainObjectIcn, tile.GetObjectSpriteIndex() ); // If this assertion blows up we are trying to render an image bigger than a tile. Render this object properly as heroes or monsters! assert( mainObjectSprite.x() >= 0 && mainObjectSprite.width() + mainObjectSprite.x() <= TILEWIDTH && mainObjectSprite.y() >= 0 && mainObjectSprite.height() + mainObjectSprite.y() <= TILEWIDTH ); area.BlitOnTile( output, mainObjectSprite, mainObjectSprite.x(), mainObjectSprite.y(), offset, false, mainObjectAlphaValue ); // Render possible animation image. // TODO: quantity2 is used in absolutely incorrect way! Fix all the logic for it. As of now (quantity2 != 0) expression is used only for Magic Garden. const uint32_t mainObjectAnimationIndex = ICN::AnimationFrame( mainObjectIcn, tile.GetObjectSpriteIndex(), Game::getAdventureMapAnimationIndex(), tile.metadata()[1] != 0 ); if ( mainObjectAnimationIndex > 0 ) { const fheroes2::Sprite & animationSprite = fheroes2::AGG::GetICN( mainObjectIcn, mainObjectAnimationIndex ); // If this assertion blows up we are trying to render an image bigger than a tile. Render this object properly as heroes or monsters! assert( animationSprite.x() >= 0 && animationSprite.width() + animationSprite.x() <= TILEWIDTH && animationSprite.y() >= 0 && animationSprite.height() + animationSprite.y() <= TILEWIDTH ); area.BlitOnTile( output, animationSprite, animationSprite.x(), animationSprite.y(), offset, false, mainObjectAlphaValue ); } } #ifdef WITH_DEBUG const fheroes2::Image & PassableViewSurface( const int passable, const bool isActionObject ) { static std::map, fheroes2::Image> imageMap; auto key = std::make_pair( passable, isActionObject ); auto iter = imageMap.find( key ); if ( iter != imageMap.end() ) { return iter->second; } const int32_t size = 31; const uint8_t red = 0xBA; const uint8_t green = isActionObject ? 115 : 90; fheroes2::Image sf( size, size ); sf.reset(); if ( 0 == passable || Direction::CENTER == passable ) { fheroes2::DrawBorder( sf, red ); } else if ( DIRECTION_ALL == passable ) { fheroes2::DrawBorder( sf, green ); } else { const uint8_t topLeftColor = ( ( passable & Direction::TOP_LEFT ) != 0 ) ? green : red; const uint8_t bottomRightColor = ( ( passable & Direction::BOTTOM_RIGHT ) != 0 ) ? green : red; const uint8_t topRightColor = ( ( passable & Direction::TOP_RIGHT ) != 0 ) ? green : red; const uint8_t bottomLeftColor = ( ( passable & Direction::BOTTOM_LEFT ) != 0 ) ? green : red; const uint8_t topColor = ( ( passable & Direction::TOP ) != 0 ) ? green : red; const uint8_t bottomColor = ( ( passable & Direction::BOTTOM ) != 0 ) ? green : red; const uint8_t leftColor = ( ( passable & Direction::LEFT ) != 0 ) ? green : red; const uint8_t rightColor = ( ( passable & Direction::RIGHT ) != 0 ) ? green : red; uint8_t * image = sf.image(); uint8_t * transform = sf.transform(); // Horizontal for ( int32_t i = 0; i < 10; ++i ) { *( image + i ) = topLeftColor; *( transform + i ) = 0; *( image + i + ( size - 1 ) * size ) = bottomLeftColor; *( transform + i + ( size - 1 ) * size ) = 0; } for ( int32_t i = 10; i < 21; ++i ) { *( image + i ) = topColor; *( transform + i ) = 0; *( image + i + ( size - 1 ) * size ) = bottomColor; *( transform + i + ( size - 1 ) * size ) = 0; } for ( int32_t i = 21; i < size; ++i ) { *( image + i ) = topRightColor; *( transform + i ) = 0; *( image + i + ( size - 1 ) * size ) = bottomRightColor; *( transform + i + ( size - 1 ) * size ) = 0; } // Vertical for ( int32_t i = 0; i < 10; ++i ) { *( image + i * size ) = topLeftColor; *( transform + i * size ) = 0; *( image + size - 1 + i * size ) = topRightColor; *( transform + size - 1 + i * size ) = 0; } for ( int32_t i = 10; i < 21; ++i ) { *( image + i * size ) = leftColor; *( transform + i * size ) = 0; *( image + size - 1 + i * size ) = rightColor; *( transform + size - 1 + i * size ) = 0; } for ( int32_t i = 21; i < size; ++i ) { *( image + i * size ) = bottomLeftColor; *( transform + i * size ) = 0; *( image + size - 1 + i * size ) = bottomRightColor; *( transform + size - 1 + i * size ) = 0; } } return imageMap.try_emplace( std::move( key ), std::move( sf ) ).first->second; } const fheroes2::Image & getDebugFogImage() { static const fheroes2::Image fog = []() { fheroes2::Image temp( 32, 32 ); fheroes2::FillTransform( temp, 0, 0, temp.width(), temp.height(), 2 ); return temp; }(); return fog; } #endif std::pair GetMonsterSpriteIndices( const Maps::Tiles & tile, uint32_t monsterIndex, const bool isEditorMode ) { const int tileIndex = tile.GetIndex(); int attackerIndex = -1; // scan for a hero around if ( !isEditorMode ) { for ( const int32_t idx : Maps::ScanAroundObject( tileIndex, MP2::OBJ_HERO, false ) ) { const Heroes * hero = world.GetTiles( idx ).getHero(); assert( hero != nullptr ); // hero is going to attack monsters on this tile if ( hero->GetAttackedMonsterTileIndex() == tileIndex ) { attackerIndex = idx; break; } } } std::pair spriteIndices( monsterIndex * 9, 0 ); // draw an attacking sprite if there is an attacking hero nearby if ( attackerIndex != -1 ) { spriteIndices.first += 7; switch ( Maps::GetDirection( tileIndex, attackerIndex ) ) { case Direction::TOP_LEFT: case Direction::LEFT: case Direction::BOTTOM_LEFT: spriteIndices.first += 1; break; default: break; } } else { const fheroes2::Point & mp = Maps::GetPoint( tileIndex ); const std::array & monsterAnimationSequence = fheroes2::getMonsterAnimationSequence(); spriteIndices.second = monsterIndex * 9 + 1 + monsterAnimationSequence[( Game::getAdventureMapAnimationIndex() + mp.x * mp.y ) % monsterAnimationSequence.size()]; } return spriteIndices; } bool doesHeroImageNeedToBeReflected( const int directionFrom ) { switch ( directionFrom ) { case Direction::TOP_LEFT: case Direction::LEFT: case Direction::BOTTOM_LEFT: return true; default: break; } return false; } void getHeroSpriteInfo( const Heroes & hero, const int heroMovementIndex, const bool isHeroChangingDirection, int & icnId, uint32_t & icnIndex ) { icnId = ICN::UNKNOWN; if ( hero.isShipMaster() ) { icnId = ICN::BOAT32; } else { const int raceId = hero.GetRace(); switch ( raceId ) { case Race::KNGT: icnId = ICN::KNGT32; break; case Race::BARB: icnId = ICN::BARB32; break; case Race::SORC: icnId = ICN::SORC32; break; case Race::WRLK: icnId = ICN::WRLK32; break; case Race::WZRD: icnId = ICN::WZRD32; break; case Race::NECR: icnId = ICN::NECR32; break; default: // Did you add a new race? Add logic above! assert( 0 ); DEBUG_LOG( DBG_GAME, DBG_WARN, "Unknown hero race " << raceId ) break; } } icnIndex = 0; if ( isHeroChangingDirection ) { icnIndex = 45; } else { const int heroDirection = hero.GetDirection(); switch ( heroDirection ) { case Direction::TOP: icnIndex = 0; break; case Direction::TOP_RIGHT: icnIndex = 9; break; case Direction::RIGHT: icnIndex = 18; break; case Direction::BOTTOM_RIGHT: icnIndex = 27; break; case Direction::BOTTOM: icnIndex = 36; break; case Direction::BOTTOM_LEFT: icnIndex = 27; break; case Direction::LEFT: icnIndex = 18; break; case Direction::TOP_LEFT: icnIndex = 9; break; default: DEBUG_LOG( DBG_GAME, DBG_WARN, "Unknown hero direction" << heroDirection ) break; } } icnIndex = icnIndex + ( heroMovementIndex % Heroes::heroFrameCountPerTile ); } void getFlagSpriteInfo( const Heroes & hero, const int heroMovementIndex, const bool isHeroChangingDirection, fheroes2::Point & flagOffset, int & icnId, uint32_t & icnIndex ) { icnId = ICN::UNKNOWN; const int heroColor = hero.GetColor(); switch ( heroColor ) { case Color::BLUE: icnId = hero.isShipMaster() ? ICN::B_BFLG32 : ICN::B_FLAG32; break; case Color::GREEN: icnId = hero.isShipMaster() ? ICN::G_BFLG32 : ICN::G_FLAG32; break; case Color::RED: icnId = hero.isShipMaster() ? ICN::R_BFLG32 : ICN::R_FLAG32; break; case Color::YELLOW: icnId = hero.isShipMaster() ? ICN::Y_BFLG32 : ICN::Y_FLAG32; break; case Color::ORANGE: icnId = hero.isShipMaster() ? ICN::O_BFLG32 : ICN::O_FLAG32; break; case Color::PURPLE: icnId = hero.isShipMaster() ? ICN::P_BFLG32 : ICN::P_FLAG32; break; default: DEBUG_LOG( DBG_GAME, DBG_WARN, "Unknown hero color " << heroColor ) break; } icnIndex = 0; if ( isHeroChangingDirection ) { icnIndex = 45; } else { const int heroDirection = hero.GetDirection(); switch ( heroDirection ) { case Direction::TOP: icnIndex = 0; break; case Direction::TOP_RIGHT: icnIndex = 9; break; case Direction::RIGHT: icnIndex = 18; break; case Direction::BOTTOM_RIGHT: icnIndex = 27; break; case Direction::BOTTOM: icnIndex = 36; break; case Direction::BOTTOM_LEFT: icnIndex = 27; break; case Direction::LEFT: icnIndex = 18; break; case Direction::TOP_LEFT: icnIndex = 9; break; default: DEBUG_LOG( DBG_GAME, DBG_WARN, "Unknown hero direction " << heroDirection ) break; } } const int frameId = heroMovementIndex % Heroes::heroFrameCountPerTile; icnIndex = icnIndex + frameId; if ( !hero.isMoveEnabled() ) { static const fheroes2::Point offsetTop[Heroes::heroFrameCountPerTile] = { { 0, 0 }, { 0, 2 }, { 0, 3 }, { 0, 2 }, { 0, 0 }, { 0, 1 }, { 0, 3 }, { 0, 2 }, { 0, 1 } }; static const fheroes2::Point offsetBottom[Heroes::heroFrameCountPerTile] = { { 0, 0 }, { 0, -1 }, { 0, -2 }, { 0, 0 }, { 0, -1 }, { 0, -2 }, { 0, -3 }, { 0, 0 }, { 0, -1 } }; static const fheroes2::Point offsetSideways[Heroes::heroFrameCountPerTile] = { { 0, 0 }, { -1, 0 }, { 0, 0 }, { 1, 0 }, { 1, -1 }, { 2, -1 }, { 1, 0 }, { 0, 0 }, { 1, 0 } }; static const fheroes2::Point offsetTopSideways[Heroes::heroFrameCountPerTile] = { { 0, 0 }, { -1, 0 }, { 0, 0 }, { -1, -1 }, { -2, -1 }, { -2, 0 }, { -1, 0 }, { 0, 0 }, { 1, 0 } }; static const fheroes2::Point offsetBottomSideways[Heroes::heroFrameCountPerTile] = { { 0, 0 }, { -1, 0 }, { 0, -1 }, { 2, -2 }, { 0, -2 }, { -1, -3 }, { -1, -2 }, { -1, -1 }, { 1, 0 } }; static const fheroes2::Point offsetShipTopBottom[Heroes::heroFrameCountPerTile] = { { 0, -1 }, { 0, 0 }, { 0, 1 }, { 0, 1 }, { 0, 1 }, { 0, 0 }, { 0, 1 }, { 0, 1 }, { 0, 1 } }; static const fheroes2::Point offsetShipSideways[Heroes::heroFrameCountPerTile] = { { 0, -2 }, { 0, -1 }, { 0, 0 }, { 0, 1 }, { 0, 0 }, { 0, -1 }, { 0, 0 }, { 0, -1 }, { 0, 1 } }; static const fheroes2::Point offsetShipTopSideways[Heroes::heroFrameCountPerTile] = { { 0, 0 }, { 0, -1 }, { 0, 0 }, { 0, 1 }, { 0, 0 }, { 0, -1 }, { 0, 0 }, { 0, -1 }, { 0, 1 } }; static const fheroes2::Point offsetShipBottomSideways[Heroes::heroFrameCountPerTile] = { { 0, -2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } }; const int heroDirection = hero.GetDirection(); switch ( heroDirection ) { case Direction::TOP: flagOffset = hero.isShipMaster() ? offsetShipTopBottom[frameId] : offsetTop[frameId]; break; case Direction::BOTTOM: flagOffset = hero.isShipMaster() ? offsetShipTopBottom[frameId] : offsetBottom[frameId]; break; case Direction::RIGHT: case Direction::LEFT: flagOffset = hero.isShipMaster() ? offsetShipSideways[frameId] : offsetSideways[frameId]; break; case Direction::TOP_RIGHT: case Direction::TOP_LEFT: flagOffset = hero.isShipMaster() ? offsetShipTopSideways[frameId] : offsetTopSideways[frameId]; break; case Direction::BOTTOM_RIGHT: case Direction::BOTTOM_LEFT: flagOffset = hero.isShipMaster() ? offsetShipBottomSideways[frameId] : offsetBottomSideways[frameId]; break; default: DEBUG_LOG( DBG_GAME, DBG_WARN, "Unknown hero direction " << heroDirection ) break; } } } void getShadowSpriteInfo( const Heroes & hero, const int heroMovementIndex, int & icnId, uint32_t & icnIndex ) { const int32_t heroDirection = hero.GetDirection(); const bool isOnBoat = hero.isShipMaster(); switch ( heroDirection ) { case Direction::TOP: icnIndex = 0; break; case Direction::TOP_RIGHT: icnIndex = 9; break; case Direction::RIGHT: icnIndex = 18; break; case Direction::BOTTOM_RIGHT: icnIndex = 27; break; case Direction::BOTTOM: icnIndex = 36; break; case Direction::BOTTOM_LEFT: icnIndex = isOnBoat ? 45 : 77; break; case Direction::LEFT: icnIndex = isOnBoat ? 54 : 68; break; case Direction::TOP_LEFT: icnIndex = isOnBoat ? 63 : 59; break; default: DEBUG_LOG( DBG_GAME, DBG_WARN, "Unknown hero direction " << heroDirection ) break; } icnId = isOnBoat ? ICN::BOATSHAD : ICN::SHADOW32; icnIndex += ( heroMovementIndex % Heroes::heroFrameCountPerTile ); } void getFrothSpriteInfo( const Heroes & hero, const int heroMovementIndex, int & icnId, uint32_t & icnIndex ) { icnIndex = 0; const int heroDirection = hero.GetDirection(); switch ( heroDirection ) { case Direction::TOP: icnIndex = 0; break; case Direction::TOP_RIGHT: icnIndex = 9; break; case Direction::RIGHT: icnIndex = 18; break; case Direction::BOTTOM_RIGHT: icnIndex = 27; break; case Direction::BOTTOM: icnIndex = 36; break; case Direction::BOTTOM_LEFT: icnIndex = 27; break; case Direction::LEFT: icnIndex = 18; break; case Direction::TOP_LEFT: icnIndex = 9; break; default: DEBUG_LOG( DBG_GAME, DBG_WARN, "Unknown hero direction " << heroDirection ) break; } icnId = ICN::FROTH; icnIndex = icnIndex + ( heroMovementIndex % Heroes::heroFrameCountPerTile ); } } namespace Maps { void redrawEmptyTile( fheroes2::Image & dst, const fheroes2::Point & mp, const Interface::GameArea & area ) { if ( mp.y == -1 && mp.x >= 0 && mp.x < world.w() ) { // top first row area.DrawTile( dst, fheroes2::AGG::GetTIL( TIL::STON, 20 + ( mp.x % 4 ), 0 ), mp ); } else if ( mp.x == world.w() && mp.y >= 0 && mp.y < world.h() ) { // right first row area.DrawTile( dst, fheroes2::AGG::GetTIL( TIL::STON, 24 + ( mp.y % 4 ), 0 ), mp ); } else if ( mp.y == world.h() && mp.x >= 0 && mp.x < world.w() ) { // bottom first row area.DrawTile( dst, fheroes2::AGG::GetTIL( TIL::STON, 28 + ( mp.x % 4 ), 0 ), mp ); } else if ( mp.x == -1 && mp.y >= 0 && mp.y < world.h() ) { // left first row area.DrawTile( dst, fheroes2::AGG::GetTIL( TIL::STON, 32 + ( mp.y % 4 ), 0 ), mp ); } else { area.DrawTile( dst, fheroes2::AGG::GetTIL( TIL::STON, ( std::abs( mp.y ) % 4 ) * 4 + std::abs( mp.x ) % 4, 0 ), mp ); } } void redrawTopLayerExtraObjects( const Tiles & tile, fheroes2::Image & dst, const bool isPuzzleDraw, const Interface::GameArea & area ) { if ( isPuzzleDraw ) { // Extra objects should not be shown on Puzzle Map as they are temporary objects appearing under specific conditions like flags. return; } // Ghost animation is unique and can be rendered in multiple cases. bool renderFlyingGhosts = false; const MP2::MapObjectType objectType = tile.GetObject( false ); if ( objectType == MP2::OBJ_ABANDONED_MINE ) { renderFlyingGhosts = true; } else if ( objectType == MP2::OBJ_MINE ) { const int32_t spellID = Maps::getMineSpellIdFromTile( tile ); switch ( spellID ) { case Spell::NONE: // No spell exists. Nothing we need to render. case Spell::SETEGUARDIAN: case Spell::SETAGUARDIAN: case Spell::SETFGUARDIAN: case Spell::SETWGUARDIAN: // The logic for these spells is done while rendering the bottom layer. Nothing should be done here. break; case Spell::HAUNT: renderFlyingGhosts = true; break; default: // Did you add a new spell for mines? Add the rendering for it above! assert( 0 ); break; } } if ( renderFlyingGhosts ) { // This sprite is bigger than TILEWIDTH but rendering is correct for heroes and boats. // TODO: consider adding this sprite as a part of an addon. const fheroes2::Sprite & image = fheroes2::AGG::GetICN( ICN::OBJNHAUN, Game::getAdventureMapAnimationIndex() % 15 ); const uint8_t alphaValue = area.getObjectAlphaValue( tile.GetObjectUID() ); area.BlitOnTile( dst, image, image.x(), image.y(), Maps::GetPoint( tile.GetIndex() ), false, alphaValue ); } } void redrawTopLayerObject( const Tiles & tile, fheroes2::Image & dst, const bool isPuzzleDraw, const Interface::GameArea & area, const TilesAddon & addon ) { if ( isPuzzleDraw && MP2::isHiddenForPuzzle( tile.GetGround(), addon._objectIcnType, addon._imageIndex ) ) { return; } renderAddonObject( dst, area, Maps::GetPoint( tile.GetIndex() ), addon ); } void drawFog( const Tiles & tile, fheroes2::Image & dst, const Interface::GameArea & area ) { const uint16_t fogDirection = tile.getFogDirection(); // This method should not be called for a tile without fog. assert( fogDirection & Direction::CENTER ); const fheroes2::Point & mp = Maps::GetPoint( tile.GetIndex() ); // TODO: Cache all directions into a map: have an array which represents all conditions within this method. The index can be 'fogDirection', the value is index. // And another array to store revert flag. if ( DIRECTION_ALL == fogDirection ) { const fheroes2::Image & sf = fheroes2::AGG::GetTIL( TIL::CLOF32, ( mp.x + mp.y ) % 4, 0 ); area.DrawTile( dst, sf, mp ); } else { uint32_t index = 0; bool revert = false; if ( !( fogDirection & ( Direction::TOP | Direction::BOTTOM | Direction::LEFT | Direction::RIGHT ) ) ) { index = 10; } else if ( ( contains( fogDirection, Direction::TOP ) ) && !( fogDirection & ( Direction::BOTTOM | Direction::LEFT | Direction::RIGHT ) ) ) { index = 6; } else if ( ( contains( fogDirection, Direction::RIGHT ) ) && !( fogDirection & ( Direction::TOP | Direction::BOTTOM | Direction::LEFT ) ) ) { index = 7; } else if ( ( contains( fogDirection, Direction::LEFT ) ) && !( fogDirection & ( Direction::TOP | Direction::BOTTOM | Direction::RIGHT ) ) ) { index = 7; revert = true; } else if ( ( contains( fogDirection, Direction::BOTTOM ) ) && !( fogDirection & ( Direction::TOP | Direction::LEFT | Direction::RIGHT ) ) ) { index = 8; } else if ( ( contains( fogDirection, DIRECTION_CENTER_COL ) ) && !( fogDirection & ( Direction::LEFT | Direction::RIGHT ) ) ) { index = 9; } else if ( ( contains( fogDirection, DIRECTION_CENTER_ROW ) ) && !( fogDirection & ( Direction::TOP | Direction::BOTTOM ) ) ) { index = 29; } else if ( fogDirection == ( DIRECTION_ALL & ( ~Direction::TOP_RIGHT ) ) ) { index = 15; } else if ( fogDirection == ( DIRECTION_ALL & ( ~Direction::TOP_LEFT ) ) ) { index = 15; revert = true; } else if ( fogDirection == ( DIRECTION_ALL & ( ~Direction::BOTTOM_RIGHT ) ) ) { index = 22; } else if ( fogDirection == ( DIRECTION_ALL & ( ~Direction::BOTTOM_LEFT ) ) ) { index = 22; revert = true; } else if ( fogDirection == ( DIRECTION_ALL & ( ~( Direction::TOP_RIGHT | Direction::BOTTOM_RIGHT ) ) ) ) { index = 16; } else if ( fogDirection == ( DIRECTION_ALL & ( ~( Direction::TOP_LEFT | Direction::BOTTOM_LEFT ) ) ) ) { index = 16; revert = true; } else if ( fogDirection == ( DIRECTION_ALL & ( ~( Direction::TOP_RIGHT | Direction::BOTTOM_LEFT ) ) ) ) { index = 17; } else if ( fogDirection == ( DIRECTION_ALL & ( ~( Direction::TOP_LEFT | Direction::BOTTOM_RIGHT ) ) ) ) { index = 17; revert = true; } else if ( fogDirection == ( DIRECTION_ALL & ( ~( Direction::TOP_LEFT | Direction::TOP_RIGHT ) ) ) ) { index = 18; } else if ( fogDirection == ( DIRECTION_ALL & ( ~( Direction::BOTTOM_LEFT | Direction::BOTTOM_RIGHT ) ) ) ) { index = 23; } else if ( fogDirection == ( DIRECTION_ALL & ( ~DIRECTION_TOP_RIGHT_CORNER ) ) ) { index = 13; } else if ( fogDirection == ( DIRECTION_ALL & ( ~DIRECTION_TOP_LEFT_CORNER ) ) ) { index = 13; revert = true; } else if ( fogDirection == ( DIRECTION_ALL & ( ~DIRECTION_BOTTOM_RIGHT_CORNER ) ) ) { index = 14; } else if ( fogDirection == ( DIRECTION_ALL & ( ~DIRECTION_BOTTOM_LEFT_CORNER ) ) ) { index = 14; revert = true; } else if ( contains( fogDirection, Direction::LEFT | Direction::BOTTOM_LEFT | Direction::BOTTOM ) && !( fogDirection & ( Direction::TOP | Direction::RIGHT ) ) ) { index = 11; } else if ( contains( fogDirection, Direction::RIGHT | Direction::BOTTOM_RIGHT | Direction::BOTTOM ) && !( fogDirection & ( Direction::TOP | Direction::LEFT ) ) ) { index = 11; revert = true; } else if ( contains( fogDirection, Direction::LEFT | Direction::TOP_LEFT | Direction::TOP ) && !( fogDirection & ( Direction::BOTTOM | Direction::RIGHT ) ) ) { index = 12; } else if ( contains( fogDirection, Direction::RIGHT | Direction::TOP_RIGHT | Direction::TOP ) && !( fogDirection & ( Direction::BOTTOM | Direction::LEFT ) ) ) { index = 12; revert = true; } else if ( contains( fogDirection, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::TOP | Direction::TOP_LEFT ) && !( fogDirection & ( Direction::BOTTOM_LEFT | Direction::BOTTOM_RIGHT | Direction::TOP_RIGHT ) ) ) { index = 19; } else if ( contains( fogDirection, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::TOP | Direction::TOP_RIGHT ) && !( fogDirection & ( Direction::BOTTOM_LEFT | Direction::BOTTOM_RIGHT | Direction::TOP_LEFT ) ) ) { index = 19; revert = true; } else if ( contains( fogDirection, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::TOP | Direction::BOTTOM_LEFT ) && !( fogDirection & ( Direction::TOP_RIGHT | Direction::BOTTOM_RIGHT | Direction::TOP_LEFT ) ) ) { index = 20; } else if ( contains( fogDirection, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::TOP | Direction::BOTTOM_RIGHT ) && !( fogDirection & ( Direction::TOP_RIGHT | Direction::BOTTOM_LEFT | Direction::TOP_LEFT ) ) ) { index = 20; revert = true; } else if ( contains( fogDirection, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::TOP ) && !( fogDirection & ( Direction::TOP_RIGHT | Direction::BOTTOM_RIGHT | Direction::BOTTOM_LEFT | Direction::TOP_LEFT ) ) ) { index = 21; } else if ( contains( fogDirection, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::BOTTOM_LEFT ) && !( fogDirection & ( Direction::TOP | Direction::BOTTOM_RIGHT ) ) ) { index = 24; } else if ( contains( fogDirection, DIRECTION_CENTER_ROW | Direction::BOTTOM | Direction::BOTTOM_RIGHT ) && !( fogDirection & ( Direction::TOP | Direction::BOTTOM_LEFT ) ) ) { index = 24; revert = true; } else if ( contains( fogDirection, DIRECTION_CENTER_COL | Direction::LEFT | Direction::TOP_LEFT ) && !( fogDirection & ( Direction::RIGHT | Direction::BOTTOM_LEFT ) ) ) { index = 25; } else if ( contains( fogDirection, DIRECTION_CENTER_COL | Direction::RIGHT | Direction::TOP_RIGHT ) && !( fogDirection & ( Direction::LEFT | Direction::BOTTOM_RIGHT ) ) ) { index = 25; revert = true; } else if ( contains( fogDirection, DIRECTION_CENTER_COL | Direction::BOTTOM_LEFT | Direction::LEFT ) && !( fogDirection & ( Direction::RIGHT | Direction::TOP_LEFT ) ) ) { index = 26; } else if ( contains( fogDirection, DIRECTION_CENTER_COL | Direction::BOTTOM_RIGHT | Direction::RIGHT ) && !( fogDirection & ( Direction::LEFT | Direction::TOP_RIGHT ) ) ) { index = 26; revert = true; } else if ( contains( fogDirection, DIRECTION_CENTER_ROW | Direction::TOP_LEFT | Direction::TOP ) && !( fogDirection & ( Direction::BOTTOM | Direction::TOP_RIGHT ) ) ) { index = 30; } else if ( contains( fogDirection, DIRECTION_CENTER_ROW | Direction::TOP_RIGHT | Direction::TOP ) && !( fogDirection & ( Direction::BOTTOM | Direction::TOP_LEFT ) ) ) { index = 30; revert = true; } else if ( contains( fogDirection, Direction::BOTTOM | Direction::LEFT ) && !( fogDirection & ( Direction::TOP | Direction::RIGHT | Direction::BOTTOM_LEFT ) ) ) { index = 27; } else if ( contains( fogDirection, Direction::BOTTOM | Direction::RIGHT ) && !( fogDirection & ( Direction::TOP | Direction::LEFT | Direction::BOTTOM_RIGHT ) ) ) { index = 27; revert = true; } else if ( contains( fogDirection, Direction::LEFT | Direction::TOP ) && !( fogDirection & ( Direction::TOP_LEFT | Direction::RIGHT | Direction::BOTTOM ) ) ) { index = 28; } else if ( contains( fogDirection, Direction::RIGHT | Direction::TOP ) && !( fogDirection & ( Direction::TOP_RIGHT | Direction::LEFT | Direction::BOTTOM ) ) ) { index = 28; revert = true; } else if ( contains( fogDirection, DIRECTION_CENTER_ROW | Direction::TOP ) && !( fogDirection & ( Direction::BOTTOM | Direction::TOP_LEFT | Direction::TOP_RIGHT ) ) ) { index = 31; } else if ( contains( fogDirection, DIRECTION_CENTER_COL | Direction::RIGHT ) && !( fogDirection & ( Direction::LEFT | Direction::TOP_RIGHT | Direction::BOTTOM_RIGHT ) ) ) { index = 32; } else if ( contains( fogDirection, DIRECTION_CENTER_COL | Direction::LEFT ) && !( fogDirection & ( Direction::RIGHT | Direction::TOP_LEFT | Direction::BOTTOM_LEFT ) ) ) { index = 32; revert = true; } else if ( contains( fogDirection, DIRECTION_CENTER_ROW | Direction::BOTTOM ) && !( fogDirection & ( Direction::TOP | Direction::BOTTOM_LEFT | Direction::BOTTOM_RIGHT ) ) ) { index = 33; } else if ( contains( fogDirection, DIRECTION_CENTER_ROW | DIRECTION_BOTTOM_ROW ) && !( fogDirection & Direction::TOP ) ) { index = ( tile.GetIndex() % 2 ) ? 0 : 1; } else if ( contains( fogDirection, DIRECTION_CENTER_ROW | DIRECTION_TOP_ROW ) && !( fogDirection & Direction::BOTTOM ) ) { index = ( tile.GetIndex() % 2 ) ? 4 : 5; } else if ( contains( fogDirection, DIRECTION_CENTER_COL | DIRECTION_LEFT_COL ) && !( fogDirection & Direction::RIGHT ) ) { index = ( tile.GetIndex() % 2 ) ? 2 : 3; } else if ( contains( fogDirection, DIRECTION_CENTER_COL | DIRECTION_RIGHT_COL ) && !( fogDirection & Direction::LEFT ) ) { index = ( tile.GetIndex() % 2 ) ? 2 : 3; revert = true; } else { // unknown DEBUG_LOG( DBG_GAME, DBG_WARN, "Invalid direction for fog: " << Direction::String( fogDirection ) << ". Tile index: " << tile.GetIndex() ) const fheroes2::Image & sf = fheroes2::AGG::GetTIL( TIL::CLOF32, ( mp.x + mp.y ) % 4, 0 ); area.DrawTile( dst, sf, mp ); return; } const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::CLOP32, index ); area.BlitOnTile( dst, sprite, ( revert ? TILEWIDTH - sprite.x() - sprite.width() : sprite.x() ), sprite.y(), mp, revert, 255 ); } } void redrawPassable( const Tiles & tile, fheroes2::Image & dst, const int friendColors, const Interface::GameArea & area ) { #ifdef WITH_DEBUG if ( friendColors != 0 && tile.isFog( friendColors ) ) { area.BlitOnTile( dst, getDebugFogImage(), 0, 0, Maps::GetPoint( tile.GetIndex() ), false, 255 ); } const bool isActionObject = MP2::isActionObject( tile.GetObject() ); if ( isActionObject || tile.GetPassable() != DIRECTION_ALL ) { area.BlitOnTile( dst, PassableViewSurface( tile.GetPassable(), isActionObject ), 0, 0, Maps::GetPoint( tile.GetIndex() ), false, 255 ); } #else (void)tile; (void)dst; (void)area; (void)friendColors; #endif } void redrawBottomLayerObjects( const Tiles & tile, fheroes2::Image & dst, bool isPuzzleDraw, const Interface::GameArea & area, const uint8_t level ) { assert( level <= 0x03 ); const fheroes2::Point & mp = Maps::GetPoint( tile.GetIndex() ); // Since the original game stores information about objects in a very weird way and this is how it is implemented for us we need to do the following procedure: // - run through all bottom objects first which are stored in the addon stack // - check the main object which is on the tile // Some addons must be rendered after the main object on the tile. This applies for flags. // Since this method is called intensively during rendering we have to avoid memory allocation on heap. const size_t maxPostRenderAddons = 16; std::array postRenderingAddon{}; size_t postRenderAddonCount = 0; for ( const TilesAddon & addon : tile.getBottomLayerAddons() ) { if ( addon._layerType != level ) { continue; } if ( isPuzzleDraw && MP2::isHiddenForPuzzle( tile.GetGround(), addon._objectIcnType, addon._imageIndex ) ) { continue; } if ( addon._objectIcnType == MP2::OBJ_ICN_TYPE_FLAG32 ) { // Based on logically thinking it is impossible to have more than 16 flags on a single tile. assert( postRenderAddonCount < maxPostRenderAddons ); postRenderingAddon[postRenderAddonCount] = &addon; ++postRenderAddonCount; continue; } renderAddonObject( dst, area, mp, addon ); } if ( tile.getObjectIcnType() != MP2::OBJ_ICN_TYPE_UNKNOWN && tile.getLayerType() == level && ( !isPuzzleDraw || !MP2::isHiddenForPuzzle( tile.GetGround(), tile.getObjectIcnType(), tile.GetObjectSpriteIndex() ) ) ) { renderMainObject( dst, area, mp, tile ); } for ( size_t i = 0; i < postRenderAddonCount; ++i ) { assert( postRenderingAddon[i] != nullptr ); renderAddonObject( dst, area, mp, *postRenderingAddon[i] ); } } void drawByObjectIcnType( const Tiles & tile, fheroes2::Image & output, const Interface::GameArea & area, const MP2::ObjectIcnType objectIcnType ) { const fheroes2::Point & tileOffset = Maps::GetPoint( tile.GetIndex() ); for ( const TilesAddon & addon : tile.getBottomLayerAddons() ) { if ( addon._objectIcnType == objectIcnType ) { renderAddonObject( output, area, tileOffset, addon ); } } if ( tile.getObjectIcnType() == objectIcnType ) { renderMainObject( output, area, tileOffset, tile ); } for ( const TilesAddon & addon : tile.getTopLayerAddons() ) { if ( addon._objectIcnType == objectIcnType ) { renderAddonObject( output, area, tileOffset, addon ); } } } std::vector getMonsterSpritesPerTile( const Tiles & tile, const bool isEditorMode ) { assert( tile.GetObject() == MP2::OBJ_MONSTER ); const Monster monster = getMonsterFromTile( tile ); const std::pair spriteIndices = GetMonsterSpriteIndices( tile, monster.GetSpriteIndex(), isEditorMode ); const int icnId{ ICN::MINI_MONSTER_IMAGE }; const fheroes2::Sprite & monsterSprite = fheroes2::AGG::GetICN( icnId, spriteIndices.first ); const fheroes2::Point monsterSpriteOffset( monsterSprite.x() + monsterImageOffset.x, monsterSprite.y() + monsterImageOffset.y ); std::vector outputSquareInfo; std::vector> outputImageInfo; fheroes2::DivideImageBySquares( monsterSpriteOffset, monsterSprite, TILEWIDTH, outputSquareInfo, outputImageInfo ); assert( outputSquareInfo.size() == outputImageInfo.size() ); std::vector objectInfo; for ( size_t i = 0; i < outputSquareInfo.size(); ++i ) { objectInfo.emplace_back( outputSquareInfo[i], outputImageInfo[i].first, outputImageInfo[i].second, icnId, spriteIndices.first, false, static_cast( 255 ) ); } outputSquareInfo.clear(); outputImageInfo.clear(); if ( spriteIndices.second > 0 ) { const fheroes2::Sprite & secondaryMonsterSprite = fheroes2::AGG::GetICN( icnId, spriteIndices.second ); const fheroes2::Point secondaryMonsterSpriteOffset( secondaryMonsterSprite.x() + monsterImageOffset.x, secondaryMonsterSprite.y() + monsterImageOffset.y ); fheroes2::DivideImageBySquares( secondaryMonsterSpriteOffset, secondaryMonsterSprite, TILEWIDTH, outputSquareInfo, outputImageInfo ); assert( outputSquareInfo.size() == outputImageInfo.size() ); for ( size_t i = 0; i < outputSquareInfo.size(); ++i ) { objectInfo.emplace_back( outputSquareInfo[i], outputImageInfo[i].first, outputImageInfo[i].second, icnId, spriteIndices.second, false, static_cast( 255 ) ); } } return objectInfo; } std::vector getMonsterShadowSpritesPerTile( const Tiles & tile, const bool isEditorMode ) { assert( tile.GetObject() == MP2::OBJ_MONSTER ); const Monster monster = getMonsterFromTile( tile ); const std::pair spriteIndices = GetMonsterSpriteIndices( tile, monster.GetSpriteIndex(), isEditorMode ); const int icnId{ ICN::MINI_MONSTER_SHADOW }; const fheroes2::Sprite & monsterSprite = fheroes2::AGG::GetICN( icnId, spriteIndices.first ); const fheroes2::Point monsterSpriteOffset( monsterSprite.x() + monsterImageOffset.x, monsterSprite.y() + monsterImageOffset.y ); std::vector outputSquareInfo; std::vector> outputImageInfo; fheroes2::DivideImageBySquares( monsterSpriteOffset, monsterSprite, TILEWIDTH, outputSquareInfo, outputImageInfo ); assert( outputSquareInfo.size() == outputImageInfo.size() ); std::vector objectInfo; for ( size_t i = 0; i < outputSquareInfo.size(); ++i ) { objectInfo.emplace_back( outputSquareInfo[i], outputImageInfo[i].first, outputImageInfo[i].second, icnId, spriteIndices.first, false, static_cast( 255 ) ); } outputSquareInfo.clear(); outputImageInfo.clear(); if ( spriteIndices.second > 0 ) { const fheroes2::Sprite & secondaryMonsterSprite = fheroes2::AGG::GetICN( icnId, spriteIndices.second ); const fheroes2::Point secondaryMonsterSpriteOffset( secondaryMonsterSprite.x() + monsterImageOffset.x, secondaryMonsterSprite.y() + monsterImageOffset.y ); fheroes2::DivideImageBySquares( secondaryMonsterSpriteOffset, secondaryMonsterSprite, TILEWIDTH, outputSquareInfo, outputImageInfo ); assert( outputSquareInfo.size() == outputImageInfo.size() ); for ( size_t i = 0; i < outputSquareInfo.size(); ++i ) { objectInfo.emplace_back( outputSquareInfo[i], outputImageInfo[i].first, outputImageInfo[i].second, icnId, spriteIndices.second, false, static_cast( 255 ) ); } } return objectInfo; } std::vector getBoatSpritesPerTile( const Tiles & tile ) { // TODO: combine both boat image generation for heroes and empty boats. assert( tile.GetObject() == MP2::OBJ_BOAT ); const uint32_t spriteIndex = ( tile.GetObjectSpriteIndex() == 255 ) ? 18 : tile.GetObjectSpriteIndex(); const bool isReflected = ( spriteIndex > 128 ); const int icnId{ ICN::BOAT32 }; const uint32_t icnIndex = spriteIndex % 128; const fheroes2::Sprite & boatSprite = fheroes2::AGG::GetICN( icnId, icnIndex ); const fheroes2::Point boatSpriteOffset( ( isReflected ? ( TILEWIDTH + 1 - boatSprite.x() - boatSprite.width() ) : boatSprite.x() ), boatSprite.y() + TILEWIDTH - 11 ); std::vector outputSquareInfo; std::vector> outputImageInfo; fheroes2::DivideImageBySquares( boatSpriteOffset, boatSprite, TILEWIDTH, outputSquareInfo, outputImageInfo ); assert( outputSquareInfo.size() == outputImageInfo.size() ); std::vector objectInfo; for ( size_t i = 0; i < outputSquareInfo.size(); ++i ) { objectInfo.emplace_back( outputSquareInfo[i], outputImageInfo[i].first, outputImageInfo[i].second, icnId, icnIndex, isReflected, static_cast( 255 ) ); } return objectInfo; } std::vector getBoatShadowSpritesPerTile( const Tiles & tile ) { assert( tile.GetObject() == MP2::OBJ_BOAT ); // TODO: boat shadow logic is more complex than this and it is not directly depend on spriteIndex. Find the proper logic and fix it! const uint32_t spriteIndex = ( tile.GetObjectSpriteIndex() == 255 ) ? 18 : tile.GetObjectSpriteIndex(); const int icnId{ ICN::BOATSHAD }; const uint32_t icnIndex = spriteIndex % 128; const fheroes2::Sprite & boatShadowSprite = fheroes2::AGG::GetICN( icnId, icnIndex ); const fheroes2::Point boatShadowSpriteOffset( boatShadowSprite.x(), TILEWIDTH + boatShadowSprite.y() - 11 ); // Shadows cannot be flipped so flip flag is always false. std::vector outputSquareInfo; std::vector> outputImageInfo; fheroes2::DivideImageBySquares( boatShadowSpriteOffset, boatShadowSprite, TILEWIDTH, outputSquareInfo, outputImageInfo ); assert( outputSquareInfo.size() == outputImageInfo.size() ); std::vector objectInfo; for ( size_t i = 0; i < outputSquareInfo.size(); ++i ) { objectInfo.emplace_back( outputSquareInfo[i], outputImageInfo[i].first, outputImageInfo[i].second, icnId, icnIndex, false, static_cast( 255 ) ); } return objectInfo; } std::vector getMineGuardianSpritesPerTile( const Tiles & tile ) { assert( tile.GetObject( false ) == MP2::OBJ_MINE ); std::vector objectInfo; const int32_t spellID = Maps::getMineSpellIdFromTile( tile ); switch ( spellID ) { case Spell::SETEGUARDIAN: case Spell::SETAGUARDIAN: case Spell::SETFGUARDIAN: case Spell::SETWGUARDIAN: { static_assert( Spell::SETAGUARDIAN - Spell::SETEGUARDIAN == 1 && Spell::SETFGUARDIAN - Spell::SETEGUARDIAN == 2 && Spell::SETWGUARDIAN - Spell::SETEGUARDIAN == 3, "Why are you changing the order of spells?! Be extremely careful of what you are doing" ); const int icnId{ ICN::OBJNXTRA }; const uint32_t icnIndex = spellID - Spell::SETEGUARDIAN; const fheroes2::Sprite & image = fheroes2::AGG::GetICN( icnId, icnIndex ); std::vector outputSquareInfo; std::vector> outputImageInfo; fheroes2::DivideImageBySquares( { image.x(), image.y() }, image, TILEWIDTH, outputSquareInfo, outputImageInfo ); assert( outputSquareInfo.size() == outputImageInfo.size() ); for ( size_t i = 0; i < outputSquareInfo.size(); ++i ) { objectInfo.emplace_back( outputSquareInfo[i], outputImageInfo[i].first, outputImageInfo[i].second, icnId, icnIndex, false, static_cast( 255 ) ); } break; } default: break; } return objectInfo; } std::vector getHeroSpritesPerTile( const Heroes & hero ) { // Reflected hero sprite should be shifted by 1 pixel to right. const bool reflect = doesHeroImageNeedToBeReflected( hero.GetDirection() ); int flagFrameID = hero.GetSpriteIndex(); if ( !hero.isMoveEnabled() ) { flagFrameID = hero.isShipMaster() ? 0 : static_cast( Game::getAdventureMapAnimationIndex() ); } fheroes2::Point offset; // Boat sprite has to be shifted so it matches other boats. if ( hero.isShipMaster() ) { offset.y -= 11; } else { offset.y -= 1; } // Apply hero offset when he moves from one tile to another. offset += hero.getCurrentPixelOffset(); int icnId{ 0 }; uint32_t icnIndex{ 0 }; getHeroSpriteInfo( hero, hero.GetSpriteIndex(), false, icnId, icnIndex ); const fheroes2::Sprite & spriteHero = fheroes2::AGG::GetICN( icnId, icnIndex ); const fheroes2::Point heroSpriteOffset( offset.x + ( reflect ? ( TILEWIDTH + 1 - spriteHero.x() - spriteHero.width() ) : spriteHero.x() ), offset.y + spriteHero.y() + TILEWIDTH ); std::vector outputSquareInfo; std::vector> outputImageInfo; fheroes2::DivideImageBySquares( heroSpriteOffset, spriteHero, TILEWIDTH, outputSquareInfo, outputImageInfo ); assert( outputSquareInfo.size() == outputImageInfo.size() ); std::vector objectInfo; for ( size_t i = 0; i < outputSquareInfo.size(); ++i ) { objectInfo.emplace_back( outputSquareInfo[i], outputImageInfo[i].first, outputImageInfo[i].second, icnId, icnIndex, reflect, static_cast( 255 ) ); } outputSquareInfo.clear(); outputImageInfo.clear(); fheroes2::Point flagOffset; getFlagSpriteInfo( hero, flagFrameID, false, flagOffset, icnId, icnIndex ); const fheroes2::Sprite & spriteFlag = fheroes2::AGG::GetICN( icnId, icnIndex ); const fheroes2::Point flagSpriteOffset( offset.x + ( reflect ? ( TILEWIDTH - spriteFlag.x() - flagOffset.x - spriteFlag.width() ) : spriteFlag.x() + flagOffset.x ), offset.y + spriteFlag.y() + flagOffset.y + TILEWIDTH ); fheroes2::DivideImageBySquares( flagSpriteOffset, spriteFlag, TILEWIDTH, outputSquareInfo, outputImageInfo ); assert( outputSquareInfo.size() == outputImageInfo.size() ); for ( size_t i = 0; i < outputSquareInfo.size(); ++i ) { objectInfo.emplace_back( outputSquareInfo[i], outputImageInfo[i].first, outputImageInfo[i].second, icnId, icnIndex, reflect, static_cast( 255 ) ); } outputSquareInfo.clear(); outputImageInfo.clear(); if ( hero.isShipMaster() && hero.isMoveEnabled() && hero.isInDeepOcean() ) { // TODO: draw froth for all boats in deep water, not only for a moving boat. getFrothSpriteInfo( hero, hero.GetSpriteIndex(), icnId, icnIndex ); const fheroes2::Sprite & spriteFroth = fheroes2::AGG::GetICN( icnId, icnIndex ); const fheroes2::Point frothSpriteOffset( offset.x + ( reflect ? TILEWIDTH - spriteFroth.x() - spriteFroth.width() : spriteFroth.x() ), offset.y + spriteFroth.y() + TILEWIDTH ); fheroes2::DivideImageBySquares( frothSpriteOffset, spriteFroth, TILEWIDTH, outputSquareInfo, outputImageInfo ); for ( size_t i = 0; i < outputSquareInfo.size(); ++i ) { objectInfo.emplace_back( outputSquareInfo[i], outputImageInfo[i].first, outputImageInfo[i].second, icnId, icnIndex, reflect, static_cast( 255 ) ); } } return objectInfo; } std::vector getHeroShadowSpritesPerTile( const Heroes & hero ) { fheroes2::Point offset; // Boat sprite has to be shifted so it matches other boats. if ( hero.isShipMaster() ) { offset.y -= 11; } // Apply hero offset when he moves from one tile to another. offset += hero.getCurrentPixelOffset(); int icnId{ 0 }; uint32_t icnIndex{ 0 }; getShadowSpriteInfo( hero, hero.GetSpriteIndex(), icnId, icnIndex ); const fheroes2::Sprite & spriteShadow = fheroes2::AGG::GetICN( icnId, icnIndex ); const fheroes2::Point shadowSpriteOffset( offset.x + spriteShadow.x(), offset.y + spriteShadow.y() + TILEWIDTH ); std::vector outputSquareInfo; std::vector> outputImageInfo; fheroes2::DivideImageBySquares( shadowSpriteOffset, spriteShadow, TILEWIDTH, outputSquareInfo, outputImageInfo ); assert( outputSquareInfo.size() == outputImageInfo.size() ); std::vector objectInfo; for ( size_t i = 0; i < outputSquareInfo.size(); ++i ) { objectInfo.emplace_back( outputSquareInfo[i], outputImageInfo[i].first, outputImageInfo[i].second, icnId, icnIndex, false, static_cast( 255 ) ); } return objectInfo; } std::vector getEditorHeroSpritesPerTile( const Tiles & tile ) { assert( tile.GetObject() == MP2::OBJ_HERO ); const uint32_t icnIndex = tile.GetObjectSpriteIndex(); const int icnId{ ICN::MINIHERO }; const fheroes2::Sprite & boatSprite = fheroes2::AGG::GetICN( icnId, icnIndex ); const fheroes2::Point boatSpriteOffset{ 0, 32 - 50 }; std::vector outputSquareInfo; std::vector> outputImageInfo; fheroes2::DivideImageBySquares( boatSpriteOffset, boatSprite, TILEWIDTH, outputSquareInfo, outputImageInfo ); assert( outputSquareInfo.size() == outputImageInfo.size() ); std::vector objectInfo; for ( size_t i = 0; i < outputSquareInfo.size(); ++i ) { objectInfo.emplace_back( outputSquareInfo[i], outputImageInfo[i].first, outputImageInfo[i].second, icnId, icnIndex, false, static_cast( 255 ) ); } return objectInfo; } const fheroes2::Image & getTileSurface( const Tiles & tile ) { return fheroes2::AGG::GetTIL( TIL::GROUND32, tile.getTerrainImageIndex(), ( tile.getTerrainFlags() & 0x3 ) ); } } fheroes2-1.0.12+dfsg/src/fheroes2/maps/maps_tiles_render.h000066400000000000000000000070131456075706000233560ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include "math_base.h" #include "mp2.h" class Heroes; namespace fheroes2 { class Image; struct ObjectRenderingInfo; } namespace Interface { class GameArea; } namespace Maps { class Tiles; struct TilesAddon; void redrawEmptyTile( fheroes2::Image & dst, const fheroes2::Point & mp, const Interface::GameArea & area ); void redrawTopLayerExtraObjects( const Tiles & tile, fheroes2::Image & dst, const bool isPuzzleDraw, const Interface::GameArea & area ); void redrawTopLayerObject( const Tiles & tile, fheroes2::Image & dst, const bool isPuzzleDraw, const Interface::GameArea & area, const TilesAddon & addon ); void drawFog( const Tiles & tile, fheroes2::Image & dst, const Interface::GameArea & area ); void redrawPassable( const Tiles & tile, fheroes2::Image & dst, const int friendColors, const Interface::GameArea & area ); void redrawBottomLayerObjects( const Tiles & tile, fheroes2::Image & dst, bool isPuzzleDraw, const Interface::GameArea & area, const uint8_t level ); void drawByObjectIcnType( const Tiles & tile, fheroes2::Image & output, const Interface::GameArea & area, const MP2::ObjectIcnType objectIcnType ); std::vector getMonsterSpritesPerTile( const Tiles & tile, const bool isEditorMode ); std::vector getMonsterShadowSpritesPerTile( const Tiles & tile, const bool isEditorMode ); std::vector getBoatSpritesPerTile( const Tiles & tile ); std::vector getBoatShadowSpritesPerTile( const Tiles & tile ); std::vector getMineGuardianSpritesPerTile( const Tiles & tile ); std::vector getHeroSpritesPerTile( const Heroes & hero ); std::vector getHeroShadowSpritesPerTile( const Heroes & hero ); std::vector getEditorHeroSpritesPerTile( const Tiles & tile ); const fheroes2::Image & getTileSurface( const Tiles & tile ); } fheroes2-1.0.12+dfsg/src/fheroes2/maps/mp2.cpp000066400000000000000000000735111456075706000207160ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "mp2.h" #include #include #include "direction.h" #include "ground.h" #include "icn.h" #include "logging.h" #include "settings.h" #include "translations.h" namespace { bool isObjectCanBeAction( const MP2::MapObjectType objectType ) { // This is a list of all objects which cannot be an action object. switch ( objectType ) { case MP2::OBJ_NONE: case MP2::OBJ_COAST: case MP2::OBJ_NOTHING_SPECIAL: case MP2::OBJ_MOSSY_ROCK: case MP2::OBJ_TAR_PIT: case MP2::OBJ_REEFS: case MP2::OBJ_TREES: case MP2::OBJ_MOUNTAINS: case MP2::OBJ_VOLCANO: case MP2::OBJ_FLOWERS: case MP2::OBJ_ROCK: case MP2::OBJ_WATER_LAKE: case MP2::OBJ_MANDRAKE: case MP2::OBJ_DEAD_TREE: case MP2::OBJ_STUMP: case MP2::OBJ_CRATER: case MP2::OBJ_CACTUS: case MP2::OBJ_MOUND: case MP2::OBJ_DUNE: case MP2::OBJ_LAVAPOOL: case MP2::OBJ_SHRUB: return false; default: break; } return true; } } int MP2::getIcnIdFromObjectIcnType( const ObjectIcnType objectIcnType ) { switch ( objectIcnType ) { case OBJ_ICN_TYPE_UNKNOWN: return ICN::UNKNOWN; case OBJ_ICN_TYPE_BOAT32: return ICN::BOAT32; case OBJ_ICN_TYPE_OBJNARTI: return ICN::OBJNARTI; case OBJ_ICN_TYPE_MONS32: return ICN::MONS32; case OBJ_ICN_TYPE_FLAG32: return ICN::FLAG32; case OBJ_ICN_TYPE_MINIHERO: return ICN::MINIHERO; case OBJ_ICN_TYPE_MTNSNOW: return ICN::MTNSNOW; case OBJ_ICN_TYPE_MTNSWMP: return ICN::MTNSWMP; case OBJ_ICN_TYPE_MTNLAVA: return ICN::MTNLAVA; case OBJ_ICN_TYPE_MTNDSRT: return ICN::MTNDSRT; case OBJ_ICN_TYPE_MTNDIRT: return ICN::MTNDIRT; case OBJ_ICN_TYPE_MTNMULT: return ICN::MTNMULT; case OBJ_ICN_TYPE_EXTRAOVR: return ICN::EXTRAOVR; case OBJ_ICN_TYPE_ROAD: return ICN::ROAD; case OBJ_ICN_TYPE_MTNCRCK: return ICN::MTNCRCK; case OBJ_ICN_TYPE_MTNGRAS: return ICN::MTNGRAS; case OBJ_ICN_TYPE_TREJNGL: return ICN::TREJNGL; case OBJ_ICN_TYPE_TREEVIL: return ICN::TREEVIL; case OBJ_ICN_TYPE_OBJNTOWN: return ICN::OBJNTOWN; case OBJ_ICN_TYPE_OBJNTWBA: return ICN::OBJNTWBA; case OBJ_ICN_TYPE_OBJNTWSH: return ICN::OBJNTWSH; case OBJ_ICN_TYPE_OBJNTWRD: return ICN::OBJNTWRD; case OBJ_ICN_TYPE_OBJNXTRA: return ICN::OBJNXTRA; case OBJ_ICN_TYPE_OBJNWAT2: return ICN::OBJNWAT2; case OBJ_ICN_TYPE_OBJNMUL2: return ICN::OBJNMUL2; case OBJ_ICN_TYPE_TRESNOW: return ICN::TRESNOW; case OBJ_ICN_TYPE_TREFIR: return ICN::TREFIR; case OBJ_ICN_TYPE_TREFALL: return ICN::TREFALL; case OBJ_ICN_TYPE_STREAM: return ICN::STREAM; case OBJ_ICN_TYPE_OBJNRSRC: return ICN::OBJNRSRC; case OBJ_ICN_TYPE_OBJNGRA2: return ICN::OBJNGRA2; case OBJ_ICN_TYPE_TREDECI: return ICN::TREDECI; case OBJ_ICN_TYPE_OBJNWATR: return ICN::OBJNWATR; case OBJ_ICN_TYPE_OBJNGRAS: return ICN::OBJNGRAS; case OBJ_ICN_TYPE_OBJNSNOW: return ICN::OBJNSNOW; case OBJ_ICN_TYPE_OBJNSWMP: return ICN::OBJNSWMP; case OBJ_ICN_TYPE_OBJNLAVA: return ICN::OBJNLAVA; case OBJ_ICN_TYPE_OBJNDSRT: return ICN::OBJNDSRT; case OBJ_ICN_TYPE_OBJNDIRT: return ICN::OBJNDIRT; case OBJ_ICN_TYPE_OBJNCRCK: return ICN::OBJNCRCK; case OBJ_ICN_TYPE_OBJNLAV3: return ICN::OBJNLAV3; case OBJ_ICN_TYPE_OBJNMULT: return ICN::OBJNMULT; case OBJ_ICN_TYPE_OBJNLAV2: return ICN::OBJNLAV2; case OBJ_ICN_TYPE_X_LOC1: if ( Settings::Get().isPriceOfLoyaltySupported() ) { return ICN::X_LOC1; } break; case OBJ_ICN_TYPE_X_LOC2: if ( Settings::Get().isPriceOfLoyaltySupported() ) { return ICN::X_LOC2; } break; case OBJ_ICN_TYPE_X_LOC3: if ( Settings::Get().isPriceOfLoyaltySupported() ) { return ICN::X_LOC3; } break; default: break; } DEBUG_LOG( DBG_GAME, DBG_WARN, "Unknown object ICN type: " << static_cast( objectIcnType ) ) return ICN::UNKNOWN; } bool MP2::isHiddenForPuzzle( const int terrainType, const ObjectIcnType objectIcnType, uint8_t index ) { switch ( objectIcnType ) { case OBJ_ICN_TYPE_UNKNOWN: case OBJ_ICN_TYPE_UNUSED_1: case OBJ_ICN_TYPE_UNUSED_2: case OBJ_ICN_TYPE_UNUSED_3: case OBJ_ICN_TYPE_UNUSED_4: case OBJ_ICN_TYPE_UNUSED_5: case OBJ_ICN_TYPE_BOAT32: case OBJ_ICN_TYPE_UNUSED_7: case OBJ_ICN_TYPE_UNUSED_8: case OBJ_ICN_TYPE_UNUSED_9: case OBJ_ICN_TYPE_OBJNHAUN: case OBJ_ICN_TYPE_OBJNARTI: case OBJ_ICN_TYPE_MONS32: case OBJ_ICN_TYPE_UNUSED_13: case OBJ_ICN_TYPE_FLAG32: case OBJ_ICN_TYPE_UNUSED_15: case OBJ_ICN_TYPE_UNUSED_16: case OBJ_ICN_TYPE_UNUSED_17: case OBJ_ICN_TYPE_UNUSED_18: case OBJ_ICN_TYPE_UNUSED_19: case OBJ_ICN_TYPE_MINIMON: case OBJ_ICN_TYPE_MINIHERO: case OBJ_ICN_TYPE_OBJNRSRC: return true; case OBJ_ICN_TYPE_OBJNMULT: // Campfire. return ( index >= 124 && index <= 137 ); default: // TODO: verify whether we need to show pickup objects in water. break; } return isDiggingHoleSprite( terrainType, objectIcnType, index ); } const char * MP2::StringObject( MapObjectType objectType, const int count ) { if ( ( objectType & OBJ_ACTION_OBJECT_TYPE ) == OBJ_ACTION_OBJECT_TYPE ) { objectType = static_cast( objectType & ~OBJ_ACTION_OBJECT_TYPE ); } switch ( objectType ) { case OBJ_NONE: return _( "No object" ); case OBJ_NON_ACTION_ALCHEMIST_LAB: return _( "Alchemist Lab" ); case OBJ_NON_ACTION_SIGN: return _( "Sign" ); case OBJ_NON_ACTION_BUOY: return _( "Buoy" ); case OBJ_NON_ACTION_SKELETON: return _( "Skeleton" ); case OBJ_NON_ACTION_DAEMON_CAVE: return _( "Daemon Cave" ); case OBJ_NON_ACTION_TREASURE_CHEST: return _( "Treasure Chest" ); case OBJ_NON_ACTION_FAERIE_RING: return _( "Faerie Ring" ); case OBJ_NON_ACTION_CAMPFIRE: return _( "Campfire" ); case OBJ_NON_ACTION_FOUNTAIN: return _( "Fountain" ); case OBJ_NON_ACTION_GAZEBO: return _( "Gazebo" ); case OBJ_NON_ACTION_GENIE_LAMP: return _( "Genie Lamp" ); case OBJ_NON_ACTION_GRAVEYARD: return _( "Graveyard" ); case OBJ_NON_ACTION_ARCHER_HOUSE: return _( "Archer's House" ); case OBJ_NON_ACTION_GOBLIN_HUT: return _( "Goblin Hut" ); case OBJ_NON_ACTION_DWARF_COTTAGE: return _( "Dwarf Cottage" ); case OBJ_NON_ACTION_PEASANT_HUT: return _( "Peasant Hut" ); case OBJ_NON_ACTION_STABLES: return _( "Stables" ); case OBJ_NON_ACTION_ALCHEMIST_TOWER: return _( "Alchemist's Tower" ); case OBJ_NON_ACTION_EVENT: return _( "Event" ); case OBJ_NON_ACTION_DRAGON_CITY: return _( "Dragon City" ); case OBJ_NON_ACTION_LIGHTHOUSE: return _n( "Lighthouse", "Lighthouses", count ); case OBJ_NON_ACTION_WATER_WHEEL: return _n( "Water Wheel", "Water Wheels", count ); case OBJ_NON_ACTION_MINE: return _( "Mine" ); case OBJ_NON_ACTION_MONSTER: return _( "Monster" ); case OBJ_NON_ACTION_OBELISK: return _( "Obelisk" ); case OBJ_NON_ACTION_OASIS: return _( "Oasis" ); case OBJ_NON_ACTION_RESOURCE: return _( "Resource" ); case OBJ_COAST: return _( "Beach" ); case OBJ_NON_ACTION_SAWMILL: return _( "Sawmill" ); case OBJ_NON_ACTION_ORACLE: return _( "Oracle" ); case OBJ_NON_ACTION_SHRINE_FIRST_CIRCLE: return _( "Shrine of the First Circle" ); case OBJ_NON_ACTION_SHIPWRECK: return _( "Shipwreck" ); case OBJ_NON_ACTION_SEA_CHEST: return _( "Sea Chest" ); case OBJ_NON_ACTION_DESERT_TENT: return _( "Desert Tent" ); case OBJ_NON_ACTION_CASTLE: return _( "Castle" ); case OBJ_NON_ACTION_STONE_LITHS: return _( "Stone Liths" ); case OBJ_NON_ACTION_WAGON_CAMP: return _( "Wagon Camp" ); case OBJ_NON_ACTION_HUT_OF_MAGI: return _( "Hut of the Magi" ); case OBJ_NON_ACTION_WHIRLPOOL: return _( "Whirlpool" ); case OBJ_NON_ACTION_WINDMILL: return _n( "Windmill", "Windmills", count ); case OBJ_NON_ACTION_ARTIFACT: return _( "Artifact" ); case OBJ_NON_ACTION_MERMAID: return _( "Mermaid" ); case OBJ_NON_ACTION_BOAT: return _( "Boat" ); case OBJ_NON_ACTION_RANDOM_ULTIMATE_ARTIFACT: return _( "Random Ultimate Artifact" ); case OBJ_NON_ACTION_RANDOM_ARTIFACT: return _( "Random Artifact" ); case OBJ_NON_ACTION_RANDOM_RESOURCE: return _( "Random Resource" ); case OBJ_NON_ACTION_RANDOM_MONSTER: return _( "Random Monster" ); case OBJ_NON_ACTION_RANDOM_TOWN: return _( "Random Town" ); case OBJ_NON_ACTION_RANDOM_CASTLE: return _( "Random Castle" ); case OBJ_NON_ACTION_EYE_OF_MAGI: return _( "Eye of the Magi" ); case OBJ_NON_ACTION_RANDOM_MONSTER_WEAK: return _( "Random Monster - weak" ); case OBJ_NON_ACTION_RANDOM_MONSTER_MEDIUM: return _( "Random Monster - medium" ); case OBJ_NON_ACTION_RANDOM_MONSTER_STRONG: return _( "Random Monster - strong" ); case OBJ_NON_ACTION_RANDOM_MONSTER_VERY_STRONG: return _( "Random Monster - very strong" ); case OBJ_NON_ACTION_HERO: return _( "Hero" ); case OBJ_NOTHING_SPECIAL: return _( "Nothing Special" ); case OBJ_MOSSY_ROCK: return _( "Mossy Rock" ); case OBJ_NON_ACTION_WATCH_TOWER: return _( "Watch Tower" ); case OBJ_NON_ACTION_TREE_HOUSE: return _( "Tree House" ); case OBJ_NON_ACTION_TREE_CITY: return _( "Tree City" ); case OBJ_NON_ACTION_RUINS: return _( "Ruins" ); case OBJ_NON_ACTION_FORT: return _( "Fort" ); case OBJ_NON_ACTION_TRADING_POST: return _( "Trading Post" ); case OBJ_NON_ACTION_ABANDONED_MINE: return _( "Abandoned Mine" ); case OBJ_NON_ACTION_SIRENS: return _( "Sirens" ); case OBJ_NON_ACTION_STANDING_STONES: return _( "Standing Stones" ); case OBJ_NON_ACTION_IDOL: return _( "Idol" ); case OBJ_NON_ACTION_TREE_OF_KNOWLEDGE: return _( "Tree of Knowledge" ); case OBJ_NON_ACTION_WITCH_DOCTORS_HUT: return _( "Witch Doctor's Hut" ); case OBJ_NON_ACTION_TEMPLE: return _( "Temple" ); case OBJ_NON_ACTION_HILL_FORT: return _( "Hill Fort" ); case OBJ_NON_ACTION_HALFLING_HOLE: return _( "Halfling Hole" ); case OBJ_NON_ACTION_MERCENARY_CAMP: return _( "Mercenary Camp" ); case OBJ_NON_ACTION_SHRINE_SECOND_CIRCLE: return _( "Shrine of the Second Circle" ); case OBJ_NON_ACTION_SHRINE_THIRD_CIRCLE: return _( "Shrine of the Third Circle" ); case OBJ_NON_ACTION_PYRAMID: return _( "Pyramid" ); case OBJ_NON_ACTION_CITY_OF_DEAD: return _( "City of the Dead" ); case OBJ_NON_ACTION_EXCAVATION: return _( "Excavation" ); case OBJ_NON_ACTION_SPHINX: return _( "Sphinx" ); case OBJ_NON_ACTION_WAGON: return _( "Wagon" ); case OBJ_TAR_PIT: return _( "Tar Pit" ); case OBJ_NON_ACTION_ARTESIAN_SPRING: return _( "Artesian Spring" ); case OBJ_NON_ACTION_TROLL_BRIDGE: return _( "Troll Bridge" ); case OBJ_NON_ACTION_WATERING_HOLE: return _( "Watering Hole" ); case OBJ_NON_ACTION_WITCHS_HUT: return _( "Witch's Hut" ); case OBJ_NON_ACTION_XANADU: return _( "Xanadu" ); case OBJ_NON_ACTION_CAVE: return _( "Cave" ); case OBJ_NON_ACTION_LEAN_TO: return _( "Lean-To" ); case OBJ_NON_ACTION_MAGELLANS_MAPS: return _( "Magellan's Maps" ); case OBJ_NON_ACTION_FLOTSAM: return _( "Flotsam" ); case OBJ_NON_ACTION_DERELICT_SHIP: return _( "Derelict Ship" ); case OBJ_NON_ACTION_SHIPWRECK_SURVIVOR: return _( "Shipwreck Survivor" ); case OBJ_NON_ACTION_BOTTLE: return _( "Bottle" ); case OBJ_NON_ACTION_MAGIC_WELL: return _( "Magic Well" ); case OBJ_NON_ACTION_MAGIC_GARDEN: return _n( "Magic Garden", "Magic Gardens", count ); case OBJ_NON_ACTION_OBSERVATION_TOWER: return _( "Observation Tower" ); case OBJ_NON_ACTION_FREEMANS_FOUNDRY: return _( "Freeman's Foundry" ); case OBJ_REEFS: return _( "Reefs" ); case OBJ_TREES: return _( "Trees" ); case OBJ_MOUNTAINS: return _( "Mountains" ); case OBJ_VOLCANO: return _( "Volcano" ); case OBJ_FLOWERS: return _( "Flowers" ); case OBJ_ROCK: return _( "Rock" ); case OBJ_WATER_LAKE: return _( "Water Lake" ); case OBJ_MANDRAKE: return _( "Mandrake" ); case OBJ_DEAD_TREE: return _( "Dead Tree" ); case OBJ_STUMP: return _( "Stump" ); case OBJ_CRATER: return _( "Crater" ); case OBJ_CACTUS: return _( "Cactus" ); case OBJ_MOUND: return _( "Mound" ); case OBJ_DUNE: return _( "Dune" ); case OBJ_LAVAPOOL: return _( "Lava Pool" ); case OBJ_SHRUB: return _( "Shrub" ); case OBJ_NON_ACTION_ARENA: return _( "Arena" ); case OBJ_NON_ACTION_BARROW_MOUNDS: return _( "Barrow Mounds" ); case OBJ_NON_ACTION_RANDOM_ARTIFACT_TREASURE: return _( "Random Artifact - Treasure" ); case OBJ_NON_ACTION_RANDOM_ARTIFACT_MINOR: return _( "Random Artifact - Minor" ); case OBJ_NON_ACTION_RANDOM_ARTIFACT_MAJOR: return _( "Random Artifact - Major" ); case OBJ_NON_ACTION_BARRIER: return _( "Barrier" ); case OBJ_NON_ACTION_TRAVELLER_TENT: return _( "Traveller's Tent" ); case OBJ_NON_ACTION_EXPANSION_DWELLING: return "Expansion Dwelling"; case OBJ_NON_ACTION_EXPANSION_OBJECT: return "Expansion Object"; case OBJ_NON_ACTION_JAIL: return _( "Jail" ); case OBJ_NON_ACTION_FIRE_ALTAR: return _( "Fire Summoning Altar" ); case OBJ_NON_ACTION_AIR_ALTAR: return _( "Air Summoning Altar" ); case OBJ_NON_ACTION_EARTH_ALTAR: return _( "Earth Summoning Altar" ); case OBJ_NON_ACTION_WATER_ALTAR: return _( "Water Summoning Altar" ); default: // Did you add a new object type? Add the logic above! assert( 0 ); DEBUG_LOG( DBG_GAME, DBG_WARN, "Unknown object type: " << static_cast( objectType ) ) break; } return nullptr; } bool MP2::isDayLife( const MapObjectType objectType ) { // Only one object on Adventure Map restores every day and this is Magic Well. return ( objectType == OBJ_MAGIC_WELL ); } bool MP2::isWeekLife( const MapObjectType objectType ) { // TODO: list week object life switch ( objectType ) { case OBJ_STABLES: case OBJ_MAGIC_GARDEN: case OBJ_WATER_WHEEL: case OBJ_WINDMILL: case OBJ_ARTESIAN_SPRING: // join army case OBJ_WATCH_TOWER: case OBJ_EXCAVATION: case OBJ_CAVE: case OBJ_TREE_HOUSE: case OBJ_ARCHER_HOUSE: case OBJ_GOBLIN_HUT: case OBJ_DWARF_COTTAGE: case OBJ_HALFLING_HOLE: case OBJ_PEASANT_HUT: // recruit army case OBJ_RUINS: case OBJ_TREE_CITY: case OBJ_WAGON_CAMP: case OBJ_DESERT_TENT: case OBJ_WATER_ALTAR: case OBJ_AIR_ALTAR: case OBJ_FIRE_ALTAR: case OBJ_EARTH_ALTAR: case OBJ_BARROW_MOUNDS: // battle and recruit army case OBJ_DRAGON_CITY: case OBJ_CITY_OF_DEAD: case OBJ_TROLL_BRIDGE: // for AI case OBJ_HERO: return true; default: break; } return false; } bool MP2::isMonthLife( const MapObjectType objectType ) { return objectType == MP2::OBJ_CASTLE; } bool MP2::isBattleLife( const MapObjectType objectType ) { switch ( objectType ) { // luck modificators case OBJ_IDOL: case OBJ_FOUNTAIN: case OBJ_FAERIE_RING: case OBJ_PYRAMID: // morale modificators case OBJ_BUOY: case OBJ_OASIS: case OBJ_TEMPLE: case OBJ_WATERING_HOLE: case OBJ_GRAVEYARD: case OBJ_DERELICT_SHIP: case OBJ_SHIPWRECK: case OBJ_MERMAID: return true; default: break; } return false; } bool MP2::isActionObject( const MapObjectType objectType, const bool accessedFromWater ) { if ( accessedFromWater ) { return isWaterActionObject( objectType ); } return isActionObject( objectType ); } bool MP2::isWaterActionObject( const MapObjectType objectType ) { switch ( objectType ) { // These are the types of objects that can be placed on water tiles by the original editor and, // therefore, should be accessible to the hero who is on board the boat (yes, artifacts too). case OBJ_ARTIFACT: case OBJ_BOTTLE: case OBJ_BUOY: case OBJ_COAST: case OBJ_DERELICT_SHIP: case OBJ_FLOTSAM: // Heroes cannot be placed on water by the original editor, but they can board a boat case OBJ_HERO: case OBJ_MAGELLANS_MAPS: case OBJ_MERMAID: case OBJ_SEA_CHEST: case OBJ_SHIPWRECK: case OBJ_SHIPWRECK_SURVIVOR: case OBJ_SIRENS: case OBJ_WHIRLPOOL: return true; case OBJ_BOAT: case OBJ_CASTLE: return false; default: break; } // Here we would have to return false, but some map editors allow to place arbitrary objects // on water tiles, so we have to work with this. return isActionObject( objectType ); } bool MP2::isActionObject( const MapObjectType objectType ) { if ( ( objectType & OBJ_ACTION_OBJECT_TYPE ) != OBJ_ACTION_OBJECT_TYPE ) { // It is not an action object. return false; } if ( objectType == OBJ_EVENT ) { // This is the only edge case object type inherited from the original game. return false; } return isObjectCanBeAction( static_cast( objectType & ~OBJ_ACTION_OBJECT_TYPE ) ); } MP2::MapObjectType MP2::getBaseActionObjectType( const MapObjectType objectType ) { if ( ( objectType & OBJ_ACTION_OBJECT_TYPE ) == OBJ_ACTION_OBJECT_TYPE ) { // This is an action object which is considered as base. return objectType; } if ( !isObjectCanBeAction( objectType ) ) { return objectType; } return static_cast( objectType | OBJ_ACTION_OBJECT_TYPE ); } bool MP2::isValuableResourceObject( const MapObjectType objectType ) { // Sort things in alphabetical order for better readability. switch ( objectType ) { case OBJ_CAMPFIRE: case OBJ_DAEMON_CAVE: case OBJ_DERELICT_SHIP: case OBJ_FLOTSAM: case OBJ_GRAVEYARD: case OBJ_LEAN_TO: case OBJ_MAGIC_GARDEN: case OBJ_PYRAMID: case OBJ_SEA_CHEST: case OBJ_SHIPWRECK: case OBJ_SHIPWRECK_SURVIVOR: case OBJ_SKELETON: case OBJ_WAGON: case OBJ_WATER_WHEEL: case OBJ_WINDMILL: return true; default: break; } if ( isPickupObject( objectType ) ) return true; return false; } bool MP2::isCaptureObject( const MapObjectType objectType ) { // Sort things in alphabetical order for better readability. switch ( objectType ) { case OBJ_ALCHEMIST_LAB: case OBJ_CASTLE: case OBJ_LIGHTHOUSE: case OBJ_MINE: case OBJ_SAWMILL: return true; default: break; } return false; } bool MP2::isPickupObject( const MapObjectType objectType ) { // Sort things in alphabetical order for better readability. switch ( objectType ) { case OBJ_ARTIFACT: case OBJ_BOTTLE: case OBJ_CAMPFIRE: case OBJ_FLOTSAM: case OBJ_GENIE_LAMP: case OBJ_RESOURCE: case OBJ_SEA_CHEST: case OBJ_SHIPWRECK_SURVIVOR: case OBJ_TREASURE_CHEST: return true; default: break; } return false; } bool MP2::isArtifactObject( const MapObjectType objectType ) { // Sort things in alphabetical order for better readability. switch ( objectType ) { case OBJ_ARTIFACT: case OBJ_DAEMON_CAVE: case OBJ_GRAVEYARD: case OBJ_SEA_CHEST: case OBJ_SHIPWRECK: case OBJ_SHIPWRECK_SURVIVOR: case OBJ_SKELETON: case OBJ_TREASURE_CHEST: case OBJ_WAGON: return true; default: break; } return false; } bool MP2::isBattleMandatoryifObjectIsProtected( const MapObjectType objectType ) { // Sort things in alphabetical order for better readability. switch ( objectType ) { // If the artifact is guarded by rogues, it is impossible to refuse a fight. case OBJ_ARTIFACT: case OBJ_MONSTER: return true; default: break; } return isCaptureObject( objectType ); } bool MP2::isSafeForFogDiscoveryObject( const MapObjectType objectType ) { switch ( objectType ) { // Stone liths and whirlpools are mandatory because they open access to new tiles case OBJ_STONE_LITHS: case OBJ_WHIRLPOOL: // Sign messages are useless for AI, but they are harmless for fog discovery purposes case OBJ_SIGN: return true; default: break; } // Action objects in general should be avoided for fog discovery purposes, because // they may be guarded or may require wasting resources return !isActionObject( objectType ); } bool MP2::isNeedStayFront( const MapObjectType objectType ) { // Sort things in alphabetical order for better readability. switch ( objectType ) { case OBJ_BARRIER: case OBJ_BOAT: case OBJ_BUOY: case OBJ_HERO: case OBJ_JAIL: case OBJ_MERMAID: case OBJ_MONSTER: case OBJ_SHIPWRECK: case OBJ_SIRENS: case OBJ_SKELETON: return true; default: break; } return isPickupObject( objectType ); } int MP2::getActionObjectDirection( const MapObjectType objectType ) { switch ( objectType ) { case OBJ_ARTIFACT: case OBJ_BARRIER: case OBJ_BOAT: case OBJ_BOTTLE: case OBJ_BUOY: case OBJ_CAMPFIRE: case OBJ_COAST: case OBJ_FLOTSAM: case OBJ_GENIE_LAMP: case OBJ_HERO: case OBJ_JAIL: case OBJ_MONSTER: case OBJ_RANDOM_ARTIFACT: case OBJ_RANDOM_ARTIFACT_MAJOR: case OBJ_RANDOM_ARTIFACT_MINOR: case OBJ_RANDOM_ARTIFACT_TREASURE: case OBJ_RANDOM_CASTLE: case OBJ_RANDOM_MONSTER: case OBJ_RANDOM_MONSTER_MEDIUM: case OBJ_RANDOM_MONSTER_STRONG: case OBJ_RANDOM_MONSTER_VERY_STRONG: case OBJ_RANDOM_MONSTER_WEAK: case OBJ_RANDOM_TOWN: case OBJ_RANDOM_ULTIMATE_ARTIFACT: case OBJ_RANDOM_RESOURCE: case OBJ_RESOURCE: case OBJ_SEA_CHEST: case OBJ_SHIPWRECK_SURVIVOR: case OBJ_SKELETON: case OBJ_TREASURE_CHEST: case OBJ_WHIRLPOOL: return DIRECTION_ALL; case OBJ_ABANDONED_MINE: case OBJ_AIR_ALTAR: case OBJ_ALCHEMIST_LAB: case OBJ_ALCHEMIST_TOWER: case OBJ_ARCHER_HOUSE: case OBJ_ARENA: case OBJ_ARTESIAN_SPRING: case OBJ_BARROW_MOUNDS: case OBJ_CASTLE: case OBJ_CAVE: case OBJ_CITY_OF_DEAD: case OBJ_DAEMON_CAVE: case OBJ_DERELICT_SHIP: case OBJ_DESERT_TENT: case OBJ_DRAGON_CITY: case OBJ_DWARF_COTTAGE: case OBJ_EARTH_ALTAR: case OBJ_EXCAVATION: case OBJ_EYE_OF_MAGI: case OBJ_FAERIE_RING: case OBJ_FIRE_ALTAR: case OBJ_FORT: case OBJ_FOUNTAIN: case OBJ_FREEMANS_FOUNDRY: case OBJ_GAZEBO: case OBJ_GOBLIN_HUT: case OBJ_GRAVEYARD: case OBJ_HALFLING_HOLE: case OBJ_HILL_FORT: case OBJ_HUT_OF_MAGI: case OBJ_IDOL: case OBJ_LEAN_TO: case OBJ_LIGHTHOUSE: case OBJ_MAGELLANS_MAPS: case OBJ_MAGIC_GARDEN: case OBJ_MAGIC_WELL: case OBJ_MERCENARY_CAMP: case OBJ_MERMAID: case OBJ_MINE: case OBJ_OASIS: case OBJ_OBELISK: case OBJ_OBSERVATION_TOWER: case OBJ_ORACLE: case OBJ_PEASANT_HUT: case OBJ_PYRAMID: case OBJ_RUINS: case OBJ_SAWMILL: case OBJ_SHIPWRECK: case OBJ_SHRINE_FIRST_CIRCLE: case OBJ_SHRINE_SECOND_CIRCLE: case OBJ_SHRINE_THIRD_CIRCLE: case OBJ_SIGN: case OBJ_SIRENS: case OBJ_SPHINX: case OBJ_STABLES: case OBJ_STANDING_STONES: case OBJ_STONE_LITHS: case OBJ_TEMPLE: case OBJ_TRADING_POST: case OBJ_TRAVELLER_TENT: case OBJ_TREE_CITY: case OBJ_TREE_HOUSE: case OBJ_TREE_OF_KNOWLEDGE: case OBJ_TROLL_BRIDGE: case OBJ_WAGON: case OBJ_WAGON_CAMP: case OBJ_WATCH_TOWER: case OBJ_WATER_ALTAR: case OBJ_WATER_WHEEL: case OBJ_WATERING_HOLE: case OBJ_WINDMILL: case OBJ_WITCH_DOCTORS_HUT: case OBJ_WITCHS_HUT: case OBJ_XANADU: return DIRECTION_CENTER_ROW | DIRECTION_BOTTOM_ROW; default: // Did you add a new action object? Please add its passability! assert( 0 ); break; } return Direction::UNKNOWN; } bool MP2::getDiggingHoleSprite( const int terrainType, ObjectIcnType & objectIcnType, uint8_t & index ) { switch ( terrainType ) { case Maps::Ground::DESERT: objectIcnType = OBJ_ICN_TYPE_OBJNDSRT; index = 68; return true; case Maps::Ground::SNOW: objectIcnType = OBJ_ICN_TYPE_OBJNSNOW; index = 11; return true; case Maps::Ground::SWAMP: objectIcnType = OBJ_ICN_TYPE_OBJNSWMP; index = 86; return true; case Maps::Ground::WASTELAND: objectIcnType = OBJ_ICN_TYPE_OBJNCRCK; index = 70; return true; case Maps::Ground::LAVA: objectIcnType = OBJ_ICN_TYPE_OBJNLAVA; index = 26; return true; case Maps::Ground::DIRT: objectIcnType = OBJ_ICN_TYPE_OBJNDIRT; index = 140; return true; case Maps::Ground::BEACH: case Maps::Ground::GRASS: // Beach doesn't have its digging hole so we use it from Grass terrain. objectIcnType = OBJ_ICN_TYPE_OBJNGRA2; index = 9; return true; case Maps::Ground::WATER: // It is not possible to dig on water. Remember this! objectIcnType = OBJ_ICN_TYPE_UNKNOWN; index = 255; return false; default: // Did you add a new terrain type? Add the logic above! assert( 0 ); objectIcnType = OBJ_ICN_TYPE_UNKNOWN; index = 255; break; } return false; } bool MP2::isDiggingHoleSprite( const int terrainType, const ObjectIcnType objectIcnType, const uint8_t index ) { ObjectIcnType terrainObjectIcnType = OBJ_ICN_TYPE_UNKNOWN; uint8_t correctIndex = 0; if ( !getDiggingHoleSprite( terrainType, terrainObjectIcnType, correctIndex ) ) { return false; } return ( objectIcnType == terrainObjectIcnType ) && ( index == correctIndex ); } bool MP2::doesObjectNeedExtendedMetadata( const MP2::MapObjectType type ) { switch ( type ) { case OBJ_BOTTLE: case OBJ_CASTLE: case OBJ_EVENT: case OBJ_HERO: case OBJ_JAIL: case OBJ_RANDOM_CASTLE: case OBJ_RANDOM_TOWN: case OBJ_SIGN: case OBJ_SPHINX: return true; default: break; } return false; } bool MP2::doesObjectContainMetadata( const MP2::MapObjectType type ) { if ( doesObjectNeedExtendedMetadata( type ) ) { // UID for an extended metadata data chunk which should be read outside Tiles object. return true; } switch ( type ) { // Only spell scrolls require additional metadata to store spell ID. case OBJ_ARTIFACT: // Traveller's tent and barriers contain a color of the object. As of now we don't use it at all. case OBJ_BARRIER: case OBJ_TRAVELLER_TENT: // Expansion object and dwellings contain ID to identify object type. As of now we don't use it at all. case OBJ_EXPANSION_DWELLING: case OBJ_EXPANSION_OBJECT: // The number of monsters. case OBJ_MONSTER: case OBJ_RANDOM_MONSTER: case OBJ_RANDOM_MONSTER_MEDIUM: case OBJ_RANDOM_MONSTER_STRONG: case OBJ_RANDOM_MONSTER_VERY_STRONG: case OBJ_RANDOM_MONSTER_WEAK: // Ultimate artifact contains radius value to be placed by the engine. case OBJ_RANDOM_ULTIMATE_ARTIFACT: return true; default: break; } return false; } fheroes2-1.0.12+dfsg/src/fheroes2/maps/mp2.h000066400000000000000000000771331456075706000203670ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2MP2_H #define H2MP2_H #include namespace MP2 { enum MP2Info { MP2_MAP_INFO_SIZE = 428, MP2_ADDON_COUNT_SIZE = 4, MP2_ADDON_STRUCTURE_SIZE = 15, MP2_CASTLE_STRUCTURE_SIZE = 70, MP2_EVENT_STRUCTURE_MIN_SIZE = 50, MP2_HEROES_STRUCTURE_SIZE = 76, MP2_RIDDLE_STRUCTURE_MIN_SIZE = 137, MP2_RUMOR_STRUCTURE_MIN_SIZE = 9, MP2_SIGN_STRUCTURE_MIN_SIZE = 10, MP2_TILE_STRUCTURE_SIZE = 20, MP2_CASTLE_COUNT = 72, MP2_CASTLE_POSITION_SIZE = 3, MP2_CAPTURE_OBJECT_COUNT = 144, MP2_CAPTURE_OBJECT_POSITION_SIZE = 3 }; // Tile structure from the original map format. struct mp2tile_t { // Terrain image index used for terrain tile display on Adventure Map. uint16_t terrainImageIndex; // Ground (bottom) level object type (first 2 bits) and object tile set (6 bits). Tile set refers to ICN ID. uint8_t objectName1; // ICN image index (image index for corresponding ICN Id) for ground (bottom) object. 255 means it's an empty object. uint8_t bottomIcnImageIndex; // First 2 bits correspond to object layer type used to identify the order of rendering on Adventure Map. // The third bit is unknown. TODO: find out what the third bit is used for. // The last 5 bits are used together with quantity 2 as the value for the object. uint8_t quantity1; // Used as a part of quantity, field size is actually 13 bits. Has most significant bits. uint8_t quantity2; // Top level object type (first 2 bits) and object tile set (6 bits). Tile set refers to ICN ID. uint8_t objectName2; // ICN image index (image index for corresponding ICN Id) for top level object. 255 means it's an empty object. uint8_t topIcnImageIndex; // First 2 bits responsible for terrain shape (0 - 3). // Third, forth and fifth bits belong to tiles of water touching land (beach). There are only two combinations of them (from lowest to highest): // 1 0 0 // 1 0 1 // These two bit combinations are used to determine where water or land terrain can be drawn on the current tile. // Most likely these values are used only in the original Editor and have no use within the game. uint8_t terrainFlags; // The main object type for the tile. The tile can have multiple objects but the game can display information only about one. // Refer to MapObjectType enumeration below. uint8_t mapObjectType; // Next add-on index. Zero value means it's the last addon chunk. uint16_t nextAddonIndex; // Ground (bottom) level object UID. An object can allocate more than 1 tile. Each tile could have multiple objects pieces. // UID is used to find all pieces/addons which belong to the same object. // In Editor first object will have UID as 0. Then second object placed on the map will have UID 0 + number of pieces / tiles per previous object and etc. uint32_t level1ObjectUID; // Top level object UID. An object can allocate more than 1 tile. Each tile could have multiple objects pieces. // UID is used to find all pieces/addons which belong to the same object. // In Editor first object will have UID as 0. Then second object placed on the map will have UID 0 + number of pieces / tiles per previous object and etc. uint32_t level2ObjectUID; }; // Addon structure from the original map format. struct mp2addon_t { // Next add-on index. Zero value means it's the last addon chunk. uint16_t nextAddonIndex; uint8_t objectNameN1; // level 1.N. Last bit indicates if object is animated. Second-last controls overlay // ICN image index (image index for corresponding ICN Id) for ground (bottom) object. 255 means it's an empty object. uint8_t bottomIcnImageIndex; uint8_t quantityN; // Bitfield containing metadata uint8_t objectNameN2; // level 2.N // ICN image index (image index for corresponding ICN Id) for top level object. 255 means it's an empty object. uint8_t topIcnImageIndex; // Ground (bottom) level object UID. An object can allocate more than 1 tile. Each tile could have multiple objects pieces. // UID is used to find all pieces/addons which belong to the same object. // In Editor first object will have UID as 0. Then second object placed on the map will have UID 0 + number of pieces / tiles per previous object and etc. uint32_t level1ObjectUID; // Top level object UID. An object can allocate more than 1 tile. Each tile could have multiple objects pieces. // UID is used to find all pieces/addons which belong to the same object. // In Editor first object will have UID as 0. Then second object placed on the map will have UID 0 + number of pieces / tiles per previous object and etc. uint32_t level2ObjectUID; }; // An object type could be action and non-action. If both parts are present the difference between them must be 128. enum MapObjectType : uint8_t { // This section defines all types of NON-action objects which are present in the original game. // If the object by nature is an action object name it with prefix OBJ_NON_ACTION_. // Otherwise, name it with prefix OBJ_. OBJ_NONE = 0, // No object exist. OBJ_NON_ACTION_ALCHEMIST_LAB = 1, OBJ_NON_ACTION_SIGN = 2, // Never set in maps. OBJ_NON_ACTION_BUOY = 3, // Never set in maps. OBJ_NON_ACTION_SKELETON = 4, // Never set in maps. OBJ_NON_ACTION_DAEMON_CAVE = 5, OBJ_NON_ACTION_TREASURE_CHEST = 6, // Never set in maps. OBJ_NON_ACTION_FAERIE_RING = 7, OBJ_NON_ACTION_CAMPFIRE = 8, // Never set in maps. OBJ_NON_ACTION_FOUNTAIN = 9, // Never set in maps. OBJ_NON_ACTION_GAZEBO = 10, OBJ_NON_ACTION_GENIE_LAMP = 11, // Never set in maps. OBJ_NON_ACTION_GRAVEYARD = 12, OBJ_NON_ACTION_ARCHER_HOUSE = 13, OBJ_NON_ACTION_GOBLIN_HUT = 14, // Never set in maps. OBJ_NON_ACTION_DWARF_COTTAGE = 15, OBJ_NON_ACTION_PEASANT_HUT = 16, OBJ_NON_ACTION_STABLES = 17, // Never set in maps. Based on given information this was a monster dwelling called Log Cabin but set by the engine as Stables. OBJ_NON_ACTION_ALCHEMIST_TOWER = 18, // Never set in maps. Based on given information this was Road but set by the engine as Alchemist Tower. OBJ_NON_ACTION_EVENT = 19, // Never set in maps. OBJ_NON_ACTION_DRAGON_CITY = 20, OBJ_NON_ACTION_LIGHTHOUSE = 21, OBJ_NON_ACTION_WATER_WHEEL = 22, OBJ_NON_ACTION_MINE = 23, OBJ_NON_ACTION_MONSTER = 24, // Never set in maps. OBJ_NON_ACTION_OBELISK = 25, OBJ_NON_ACTION_OASIS = 26, OBJ_NON_ACTION_RESOURCE = 27, // Never set in maps. OBJ_COAST = 28, // This is the only object in non-action section which is considered as an action object (for some cases). OBJ_NON_ACTION_SAWMILL = 29, OBJ_NON_ACTION_ORACLE = 30, OBJ_NON_ACTION_SHRINE_FIRST_CIRCLE = 31, // Never set in maps. OBJ_NON_ACTION_SHIPWRECK = 32, OBJ_NON_ACTION_SEA_CHEST = 33, // Never set in maps. OBJ_NON_ACTION_DESERT_TENT = 34, OBJ_NON_ACTION_CASTLE = 35, OBJ_NON_ACTION_STONE_LITHS = 36, OBJ_NON_ACTION_WAGON_CAMP = 37, OBJ_NON_ACTION_HUT_OF_MAGI = 38, // Never set in maps. Based on given information this was a Well object. OBJ_NON_ACTION_WHIRLPOOL = 39, // Never set in maps. OBJ_NON_ACTION_WINDMILL = 40, OBJ_NON_ACTION_ARTIFACT = 41, // Never set in maps. OBJ_NON_ACTION_MERMAID = 42, // Never set in maps. Based on given information this was a Hero. OBJ_NON_ACTION_BOAT = 43, // Never set in maps. OBJ_NON_ACTION_RANDOM_ULTIMATE_ARTIFACT = 44, // Never set in maps. OBJ_NON_ACTION_RANDOM_ARTIFACT = 45, // Never set in maps. OBJ_NON_ACTION_RANDOM_RESOURCE = 46, // Never set in maps. OBJ_NON_ACTION_RANDOM_MONSTER = 47, // Never set in maps. OBJ_NON_ACTION_RANDOM_TOWN = 48, OBJ_NON_ACTION_RANDOM_CASTLE = 49, OBJ_NON_ACTION_EYE_OF_MAGI = 50, // Never set in maps. OBJ_NON_ACTION_RANDOM_MONSTER_WEAK = 51, // Never set in maps. OBJ_NON_ACTION_RANDOM_MONSTER_MEDIUM = 52, // Never set in maps. OBJ_NON_ACTION_RANDOM_MONSTER_STRONG = 53, // Never set in maps. OBJ_NON_ACTION_RANDOM_MONSTER_VERY_STRONG = 54, // Never set in maps. OBJ_NON_ACTION_HERO = 55, // Never set in maps. This type is used for any types of heroes, including random. OBJ_NOTHING_SPECIAL = 56, OBJ_MOSSY_ROCK = 57, // It is a Rock with moss for Swamp terrain. ICN::OBJNSWMP, images 138-139. In the original game it has no name. OBJ_NON_ACTION_WATCH_TOWER = 58, OBJ_NON_ACTION_TREE_HOUSE = 59, OBJ_NON_ACTION_TREE_CITY = 60, OBJ_NON_ACTION_RUINS = 61, OBJ_NON_ACTION_FORT = 62, OBJ_NON_ACTION_TRADING_POST = 63, OBJ_NON_ACTION_ABANDONED_MINE = 64, OBJ_NON_ACTION_SIRENS = 65, // Originally it was Thatched Hut which is replaced by Peasant Hut for all original maps. OBJ_NON_ACTION_STANDING_STONES = 66, // Never set in maps. OBJ_NON_ACTION_IDOL = 67, // Never set in maps. OBJ_NON_ACTION_TREE_OF_KNOWLEDGE = 68, OBJ_NON_ACTION_WITCH_DOCTORS_HUT = 69, OBJ_NON_ACTION_TEMPLE = 70, OBJ_NON_ACTION_HILL_FORT = 71, OBJ_NON_ACTION_HALFLING_HOLE = 72, OBJ_NON_ACTION_MERCENARY_CAMP = 73, OBJ_NON_ACTION_SHRINE_SECOND_CIRCLE = 74, // Never set in maps. OBJ_NON_ACTION_SHRINE_THIRD_CIRCLE = 75, // Never set in maps. OBJ_NON_ACTION_PYRAMID = 76, OBJ_NON_ACTION_CITY_OF_DEAD = 77, OBJ_NON_ACTION_EXCAVATION = 78, OBJ_NON_ACTION_SPHINX = 79, OBJ_NON_ACTION_WAGON = 80, // Never set in maps. OBJ_TAR_PIT = 81, OBJ_NON_ACTION_ARTESIAN_SPRING = 82, OBJ_NON_ACTION_TROLL_BRIDGE = 83, OBJ_NON_ACTION_WATERING_HOLE = 84, OBJ_NON_ACTION_WITCHS_HUT = 85, OBJ_NON_ACTION_XANADU = 86, OBJ_NON_ACTION_CAVE = 87, OBJ_NON_ACTION_LEAN_TO = 88, // Never set in maps. OBJ_NON_ACTION_MAGELLANS_MAPS = 89, OBJ_NON_ACTION_FLOTSAM = 90, // Never set in maps. OBJ_NON_ACTION_DERELICT_SHIP = 91, OBJ_NON_ACTION_SHIPWRECK_SURVIVOR = 92, // Never set in maps. OBJ_NON_ACTION_BOTTLE = 93, // Never set in maps. OBJ_NON_ACTION_MAGIC_WELL = 94, OBJ_NON_ACTION_MAGIC_GARDEN = 95, // Never set in maps. OBJ_NON_ACTION_OBSERVATION_TOWER = 96, OBJ_NON_ACTION_FREEMANS_FOUNDRY = 97, OBJ_REEFS = 98, // Never used within the original Editor. OBJ_TREES = 99, OBJ_MOUNTAINS = 100, OBJ_VOLCANO = 101, OBJ_FLOWERS = 102, OBJ_ROCK = 103, OBJ_WATER_LAKE = 104, OBJ_MANDRAKE = 105, OBJ_DEAD_TREE = 106, OBJ_STUMP = 107, OBJ_CRATER = 108, OBJ_CACTUS = 109, OBJ_MOUND = 110, OBJ_DUNE = 111, OBJ_LAVAPOOL = 112, OBJ_SHRUB = 113, OBJ_NON_ACTION_ARENA = 114, // Never set in maps. OBJ_NON_ACTION_BARROW_MOUNDS = 115, // Never set in maps. OBJ_NON_ACTION_RANDOM_ARTIFACT_TREASURE = 116, // Never set in maps. OBJ_NON_ACTION_RANDOM_ARTIFACT_MINOR = 117, // Never set in maps. OBJ_NON_ACTION_RANDOM_ARTIFACT_MAJOR = 118, // Never set in maps. OBJ_NON_ACTION_BARRIER = 119, // Never set in maps. OBJ_NON_ACTION_TRAVELLER_TENT = 120, OBJ_NON_ACTION_EXPANSION_DWELLING = 121, OBJ_NON_ACTION_EXPANSION_OBJECT = 122, OBJ_NON_ACTION_JAIL = 123, OBJ_NON_ACTION_FIRE_ALTAR = 124, // Never set in maps. OBJ_NON_ACTION_AIR_ALTAR = 125, // Never set in maps. OBJ_NON_ACTION_EARTH_ALTAR = 126, // Never set in maps. OBJ_NON_ACTION_WATER_ALTAR = 127, // Never set in maps. OBJ_ACTION_OBJECT_TYPE = 128, // NEVER use this object type to set in maps. This entry is used to determine if an object is action type. // This section defines all types of action objects which are present in the original game. // If the object by nature is an action object name it with prefix OBJ_. // Otherwise, name it with prefix OBJ_ACTON_. // The value of the object must be: non-action object value + OBJ_ACTION_OBJECT_TYPE. OBJ_ALCHEMIST_LAB = OBJ_NON_ACTION_ALCHEMIST_LAB + OBJ_ACTION_OBJECT_TYPE, OBJ_SIGN = OBJ_NON_ACTION_SIGN + OBJ_ACTION_OBJECT_TYPE, OBJ_BUOY = OBJ_NON_ACTION_BUOY + OBJ_ACTION_OBJECT_TYPE, OBJ_SKELETON = OBJ_NON_ACTION_SKELETON + OBJ_ACTION_OBJECT_TYPE, OBJ_DAEMON_CAVE = OBJ_NON_ACTION_DAEMON_CAVE + OBJ_ACTION_OBJECT_TYPE, OBJ_TREASURE_CHEST = OBJ_NON_ACTION_TREASURE_CHEST + OBJ_ACTION_OBJECT_TYPE, OBJ_FAERIE_RING = OBJ_NON_ACTION_FAERIE_RING + OBJ_ACTION_OBJECT_TYPE, OBJ_CAMPFIRE = OBJ_NON_ACTION_CAMPFIRE + OBJ_ACTION_OBJECT_TYPE, OBJ_FOUNTAIN = OBJ_NON_ACTION_FOUNTAIN + OBJ_ACTION_OBJECT_TYPE, OBJ_GAZEBO = OBJ_NON_ACTION_GAZEBO + OBJ_ACTION_OBJECT_TYPE, OBJ_GENIE_LAMP = OBJ_NON_ACTION_GENIE_LAMP + OBJ_ACTION_OBJECT_TYPE, OBJ_GRAVEYARD = OBJ_NON_ACTION_GRAVEYARD + OBJ_ACTION_OBJECT_TYPE, OBJ_ARCHER_HOUSE = OBJ_NON_ACTION_ARCHER_HOUSE + OBJ_ACTION_OBJECT_TYPE, OBJ_GOBLIN_HUT = OBJ_NON_ACTION_GOBLIN_HUT + OBJ_ACTION_OBJECT_TYPE, OBJ_DWARF_COTTAGE = OBJ_NON_ACTION_DWARF_COTTAGE + OBJ_ACTION_OBJECT_TYPE, OBJ_PEASANT_HUT = OBJ_NON_ACTION_PEASANT_HUT + OBJ_ACTION_OBJECT_TYPE, OBJ_STABLES = OBJ_NON_ACTION_STABLES + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_ALCHEMIST_TOWER = OBJ_NON_ACTION_ALCHEMIST_TOWER + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_EVENT = OBJ_NON_ACTION_EVENT + OBJ_ACTION_OBJECT_TYPE, OBJ_DRAGON_CITY = OBJ_NON_ACTION_DRAGON_CITY + OBJ_ACTION_OBJECT_TYPE, OBJ_LIGHTHOUSE = OBJ_NON_ACTION_LIGHTHOUSE + OBJ_ACTION_OBJECT_TYPE, OBJ_WATER_WHEEL = OBJ_NON_ACTION_WATER_WHEEL + OBJ_ACTION_OBJECT_TYPE, OBJ_MINE = OBJ_NON_ACTION_MINE + OBJ_ACTION_OBJECT_TYPE, OBJ_MONSTER = OBJ_NON_ACTION_MONSTER + OBJ_ACTION_OBJECT_TYPE, OBJ_OBELISK = OBJ_NON_ACTION_OBELISK + OBJ_ACTION_OBJECT_TYPE, OBJ_OASIS = OBJ_NON_ACTION_OASIS + OBJ_ACTION_OBJECT_TYPE, OBJ_RESOURCE = OBJ_NON_ACTION_RESOURCE + OBJ_ACTION_OBJECT_TYPE, OBJ_ACTION_COAST = OBJ_COAST + OBJ_ACTION_OBJECT_TYPE, // Never set in maps as Coast is not an action object. OBJ_SAWMILL = OBJ_NON_ACTION_SAWMILL + OBJ_ACTION_OBJECT_TYPE, OBJ_ORACLE = OBJ_NON_ACTION_ORACLE + OBJ_ACTION_OBJECT_TYPE, OBJ_SHRINE_FIRST_CIRCLE = OBJ_NON_ACTION_SHRINE_FIRST_CIRCLE + OBJ_ACTION_OBJECT_TYPE, OBJ_SHIPWRECK = OBJ_NON_ACTION_SHIPWRECK + OBJ_ACTION_OBJECT_TYPE, OBJ_SEA_CHEST = OBJ_NON_ACTION_SEA_CHEST + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_DESERT_TENT = OBJ_NON_ACTION_DESERT_TENT + OBJ_ACTION_OBJECT_TYPE, OBJ_CASTLE = OBJ_NON_ACTION_CASTLE + OBJ_ACTION_OBJECT_TYPE, OBJ_STONE_LITHS = OBJ_NON_ACTION_STONE_LITHS + OBJ_ACTION_OBJECT_TYPE, OBJ_WAGON_CAMP = OBJ_NON_ACTION_WAGON_CAMP + OBJ_ACTION_OBJECT_TYPE, OBJ_HUT_OF_MAGI = OBJ_NON_ACTION_HUT_OF_MAGI + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_WHIRLPOOL = OBJ_NON_ACTION_WHIRLPOOL + OBJ_ACTION_OBJECT_TYPE, OBJ_WINDMILL = OBJ_NON_ACTION_WINDMILL + OBJ_ACTION_OBJECT_TYPE, OBJ_ARTIFACT = OBJ_NON_ACTION_ARTIFACT + OBJ_ACTION_OBJECT_TYPE, OBJ_MERMAID = OBJ_NON_ACTION_MERMAID + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_BOAT = OBJ_NON_ACTION_BOAT + OBJ_ACTION_OBJECT_TYPE, OBJ_RANDOM_ULTIMATE_ARTIFACT = OBJ_NON_ACTION_RANDOM_ULTIMATE_ARTIFACT + OBJ_ACTION_OBJECT_TYPE, OBJ_RANDOM_ARTIFACT = OBJ_NON_ACTION_RANDOM_ARTIFACT + OBJ_ACTION_OBJECT_TYPE, OBJ_RANDOM_RESOURCE = OBJ_NON_ACTION_RANDOM_RESOURCE + OBJ_ACTION_OBJECT_TYPE, OBJ_RANDOM_MONSTER = OBJ_NON_ACTION_RANDOM_MONSTER + OBJ_ACTION_OBJECT_TYPE, OBJ_RANDOM_TOWN = OBJ_NON_ACTION_RANDOM_TOWN + OBJ_ACTION_OBJECT_TYPE, OBJ_RANDOM_CASTLE = OBJ_NON_ACTION_RANDOM_CASTLE + OBJ_ACTION_OBJECT_TYPE, OBJ_EYE_OF_MAGI = OBJ_NON_ACTION_EYE_OF_MAGI + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_RANDOM_MONSTER_WEAK = OBJ_NON_ACTION_RANDOM_MONSTER_WEAK + OBJ_ACTION_OBJECT_TYPE, OBJ_RANDOM_MONSTER_MEDIUM = OBJ_NON_ACTION_RANDOM_MONSTER_MEDIUM + OBJ_ACTION_OBJECT_TYPE, OBJ_RANDOM_MONSTER_STRONG = OBJ_NON_ACTION_RANDOM_MONSTER_STRONG + OBJ_ACTION_OBJECT_TYPE, OBJ_RANDOM_MONSTER_VERY_STRONG = OBJ_NON_ACTION_RANDOM_MONSTER_VERY_STRONG + OBJ_ACTION_OBJECT_TYPE, OBJ_HERO = OBJ_NON_ACTION_HERO + OBJ_ACTION_OBJECT_TYPE, // This type is used for any types of heroes, including random. OBJ_ACTION_NOTHING_SPECIAL = OBJ_NOTHING_SPECIAL + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_ACTION_MOSSY_ROCK = OBJ_MOSSY_ROCK + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_WATCH_TOWER = OBJ_NON_ACTION_WATCH_TOWER + OBJ_ACTION_OBJECT_TYPE, OBJ_TREE_HOUSE = OBJ_NON_ACTION_TREE_HOUSE + OBJ_ACTION_OBJECT_TYPE, OBJ_TREE_CITY = OBJ_NON_ACTION_TREE_CITY + OBJ_ACTION_OBJECT_TYPE, OBJ_RUINS = OBJ_NON_ACTION_RUINS + OBJ_ACTION_OBJECT_TYPE, OBJ_FORT = OBJ_NON_ACTION_FORT + OBJ_ACTION_OBJECT_TYPE, OBJ_TRADING_POST = OBJ_NON_ACTION_TRADING_POST + OBJ_ACTION_OBJECT_TYPE, OBJ_ABANDONED_MINE = OBJ_NON_ACTION_ABANDONED_MINE + OBJ_ACTION_OBJECT_TYPE, OBJ_SIRENS = OBJ_NON_ACTION_SIRENS + OBJ_ACTION_OBJECT_TYPE, // Originally it was Thatched Hut which is replaced by Peasant Hut for all original maps. OBJ_STANDING_STONES = OBJ_NON_ACTION_STANDING_STONES + OBJ_ACTION_OBJECT_TYPE, OBJ_IDOL = OBJ_NON_ACTION_IDOL + OBJ_ACTION_OBJECT_TYPE, OBJ_TREE_OF_KNOWLEDGE = OBJ_NON_ACTION_TREE_OF_KNOWLEDGE + OBJ_ACTION_OBJECT_TYPE, OBJ_WITCH_DOCTORS_HUT = OBJ_NON_ACTION_WITCH_DOCTORS_HUT + OBJ_ACTION_OBJECT_TYPE, OBJ_TEMPLE = OBJ_NON_ACTION_TEMPLE + OBJ_ACTION_OBJECT_TYPE, OBJ_HILL_FORT = OBJ_NON_ACTION_HILL_FORT + OBJ_ACTION_OBJECT_TYPE, OBJ_HALFLING_HOLE = OBJ_NON_ACTION_HALFLING_HOLE + OBJ_ACTION_OBJECT_TYPE, OBJ_MERCENARY_CAMP = OBJ_NON_ACTION_MERCENARY_CAMP + OBJ_ACTION_OBJECT_TYPE, OBJ_SHRINE_SECOND_CIRCLE = OBJ_NON_ACTION_SHRINE_SECOND_CIRCLE + OBJ_ACTION_OBJECT_TYPE, OBJ_SHRINE_THIRD_CIRCLE = OBJ_NON_ACTION_SHRINE_THIRD_CIRCLE + OBJ_ACTION_OBJECT_TYPE, OBJ_PYRAMID = OBJ_NON_ACTION_PYRAMID + OBJ_ACTION_OBJECT_TYPE, OBJ_CITY_OF_DEAD = OBJ_NON_ACTION_CITY_OF_DEAD + OBJ_ACTION_OBJECT_TYPE, OBJ_EXCAVATION = OBJ_NON_ACTION_EXCAVATION + OBJ_ACTION_OBJECT_TYPE, OBJ_SPHINX = OBJ_NON_ACTION_SPHINX + OBJ_ACTION_OBJECT_TYPE, OBJ_WAGON = OBJ_NON_ACTION_WAGON + OBJ_ACTION_OBJECT_TYPE, OBJ_ACTION_TAR_PIT = OBJ_TAR_PIT + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. This is not an action object. OBJ_ARTESIAN_SPRING = OBJ_NON_ACTION_ARTESIAN_SPRING + OBJ_ACTION_OBJECT_TYPE, OBJ_TROLL_BRIDGE = OBJ_NON_ACTION_TROLL_BRIDGE + OBJ_ACTION_OBJECT_TYPE, OBJ_WATERING_HOLE = OBJ_NON_ACTION_WATERING_HOLE + OBJ_ACTION_OBJECT_TYPE, OBJ_WITCHS_HUT = OBJ_NON_ACTION_WITCHS_HUT + OBJ_ACTION_OBJECT_TYPE, OBJ_XANADU = OBJ_NON_ACTION_XANADU + OBJ_ACTION_OBJECT_TYPE, OBJ_CAVE = OBJ_NON_ACTION_CAVE + OBJ_ACTION_OBJECT_TYPE, OBJ_LEAN_TO = OBJ_NON_ACTION_LEAN_TO + OBJ_ACTION_OBJECT_TYPE, OBJ_MAGELLANS_MAPS = OBJ_NON_ACTION_MAGELLANS_MAPS + OBJ_ACTION_OBJECT_TYPE, OBJ_FLOTSAM = OBJ_NON_ACTION_FLOTSAM + OBJ_ACTION_OBJECT_TYPE, OBJ_DERELICT_SHIP = OBJ_NON_ACTION_DERELICT_SHIP + OBJ_ACTION_OBJECT_TYPE, OBJ_SHIPWRECK_SURVIVOR = OBJ_NON_ACTION_SHIPWRECK_SURVIVOR + OBJ_ACTION_OBJECT_TYPE, OBJ_BOTTLE = OBJ_NON_ACTION_BOTTLE + OBJ_ACTION_OBJECT_TYPE, OBJ_MAGIC_WELL = OBJ_NON_ACTION_MAGIC_WELL + OBJ_ACTION_OBJECT_TYPE, OBJ_MAGIC_GARDEN = OBJ_NON_ACTION_MAGIC_GARDEN + OBJ_ACTION_OBJECT_TYPE, OBJ_OBSERVATION_TOWER = OBJ_NON_ACTION_OBSERVATION_TOWER + OBJ_ACTION_OBJECT_TYPE, OBJ_FREEMANS_FOUNDRY = OBJ_NON_ACTION_FREEMANS_FOUNDRY + OBJ_ACTION_OBJECT_TYPE, OBJ_ACTION_REEFS = OBJ_REEFS + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_ACTION_TREES = OBJ_TREES + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_ACTION_MOUNTAINS = OBJ_MOUNTAINS + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_ACTION_VOLCANO = OBJ_VOLCANO + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_ACTION_FLOWERS = OBJ_FLOWERS + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_ACTION_ROCK = OBJ_ROCK + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_ACTION_WATER_LAKE = OBJ_WATER_LAKE + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_ACTION_MANDRAKE = OBJ_MANDRAKE + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_ACTION_DEAD_TREE = OBJ_DEAD_TREE + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_ACTION_STUMP = OBJ_STUMP + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_ACTION_CRATER = OBJ_CRATER + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. TODO: verify the correct ID for this object. OBJ_ACTION_CACTUS = OBJ_CACTUS + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_ACTION_MOUND = OBJ_MOUND + OBJ_ACTION_OBJECT_TYPE, // Never set in maps OBJ_ACTION_DUNE = OBJ_DUNE + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_ACTION_LAVAPOOL = OBJ_LAVAPOOL + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_ACTION_SHRUB = OBJ_SHRUB + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_ARENA = OBJ_NON_ACTION_ARENA + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_BARROW_MOUNDS = OBJ_NON_ACTION_BARROW_MOUNDS + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_RANDOM_ARTIFACT_TREASURE = OBJ_NON_ACTION_RANDOM_ARTIFACT_TREASURE + OBJ_ACTION_OBJECT_TYPE, OBJ_RANDOM_ARTIFACT_MINOR = OBJ_NON_ACTION_RANDOM_ARTIFACT_MINOR + OBJ_ACTION_OBJECT_TYPE, OBJ_RANDOM_ARTIFACT_MAJOR = OBJ_NON_ACTION_RANDOM_ARTIFACT_MAJOR + OBJ_ACTION_OBJECT_TYPE, OBJ_BARRIER = OBJ_NON_ACTION_BARRIER + OBJ_ACTION_OBJECT_TYPE, OBJ_TRAVELLER_TENT = OBJ_NON_ACTION_TRAVELLER_TENT + OBJ_ACTION_OBJECT_TYPE, OBJ_EXPANSION_DWELLING = OBJ_NON_ACTION_EXPANSION_DWELLING + OBJ_ACTION_OBJECT_TYPE, OBJ_EXPANSION_OBJECT = OBJ_NON_ACTION_EXPANSION_OBJECT + OBJ_ACTION_OBJECT_TYPE, OBJ_JAIL = OBJ_NON_ACTION_JAIL + OBJ_ACTION_OBJECT_TYPE, OBJ_FIRE_ALTAR = OBJ_NON_ACTION_FIRE_ALTAR + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_AIR_ALTAR = OBJ_NON_ACTION_AIR_ALTAR + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_EARTH_ALTAR = OBJ_NON_ACTION_EARTH_ALTAR + OBJ_ACTION_OBJECT_TYPE, // Never set in maps. OBJ_WATER_ALTAR = OBJ_NON_ACTION_WATER_ALTAR + OBJ_ACTION_OBJECT_TYPE // Never set in maps. // IMPORTANT!!! Do not use any of unused entries for new objects. Add new entries below following the instruction. // This section defines all types of NON-action objects which are not present in the original game. // If the object by nature is an action object name it with prefix OBJ_NON_ACTION_. // Otherwise, name it with prefix OBJ_. // This section defines all types of action objects which are not present in the original game. // If the object by nature is an action object name it with prefix OBJ_. // Otherwise, name it with prefix OBJ_ACTON_. // The value of the object must be: non-action object value + OBJ_ACTION_OBJECT_TYPE. }; enum ObjectIcnType : uint8_t { OBJ_ICN_TYPE_UNKNOWN, // Object does not exist. OBJ_ICN_TYPE_UNUSED_1, // Unused OBJ_ICN_TYPE_UNUSED_2, // Unused OBJ_ICN_TYPE_UNUSED_3, // Unused OBJ_ICN_TYPE_UNUSED_4, // Unused OBJ_ICN_TYPE_UNUSED_5, // Unused OBJ_ICN_TYPE_BOAT32, // TODO: this is incorrect type for boats set by mistake. Fix it. OBJ_ICN_TYPE_UNUSED_7, // Unused OBJ_ICN_TYPE_UNUSED_8, // Unused OBJ_ICN_TYPE_UNUSED_9, // Unused OBJ_ICN_TYPE_OBJNHAUN, // Flying ghosts over an object (mine). OBJ_ICN_TYPE_OBJNARTI, // Artifacts OBJ_ICN_TYPE_MONS32, // MON32.icn corresponds to static monsters while we use dynamic monster animation from MINIMON.icn. OBJ_ICN_TYPE_UNUSED_13, // Unused OBJ_ICN_TYPE_FLAG32, // Flags usually used for castles. OBJ_ICN_TYPE_UNUSED_15, // Unused OBJ_ICN_TYPE_UNUSED_16, // Unused OBJ_ICN_TYPE_UNUSED_17, // Unused OBJ_ICN_TYPE_UNUSED_18, // Unused OBJ_ICN_TYPE_UNUSED_19, // Unused OBJ_ICN_TYPE_MINIMON, // Somehow it is unused but we need to use it properly. OBJ_ICN_TYPE_MINIHERO, // Heroes which are set in the original Editor. OBJ_ICN_TYPE_MTNSNOW, // Snow mountains. OBJ_ICN_TYPE_MTNSWMP, // Swamp mountains. OBJ_ICN_TYPE_MTNLAVA, // Lava mountains. OBJ_ICN_TYPE_MTNDSRT, // Desert mountains. OBJ_ICN_TYPE_MTNDIRT, // Dirt mountains. OBJ_ICN_TYPE_MTNMULT, // All terrain mountains. OBJ_ICN_TYPE_UNUSED_28, // Unused OBJ_ICN_TYPE_EXTRAOVR, // Extra overlay for mines. OBJ_ICN_TYPE_ROAD, // Roads. OBJ_ICN_TYPE_MTNCRCK, // Cracked desert mountains. OBJ_ICN_TYPE_MTNGRAS, // Grass mountains. OBJ_ICN_TYPE_TREJNGL, // Jungle trees. OBJ_ICN_TYPE_TREEVIL, // Evil trees. OBJ_ICN_TYPE_OBJNTOWN, // Towns and castles. OBJ_ICN_TYPE_OBJNTWBA, // Town basement. OBJ_ICN_TYPE_OBJNTWSH, // Town shadows. OBJ_ICN_TYPE_OBJNTWRD, // Random town. OBJ_ICN_TYPE_OBJNXTRA, // Elementals as guardians. OBJ_ICN_TYPE_OBJNWAT2, // Coastal water objects. OBJ_ICN_TYPE_OBJNMUL2, // Miscellaneous ground objects. OBJ_ICN_TYPE_TRESNOW, // Snow trees. OBJ_ICN_TYPE_TREFIR, // Fir-trees during Summer. OBJ_ICN_TYPE_TREFALL, // Fir-trees during Autumn. OBJ_ICN_TYPE_STREAM, // River streams. OBJ_ICN_TYPE_OBJNRSRC, // Resources. OBJ_ICN_TYPE_UNUSED_47, // Unused OBJ_ICN_TYPE_OBJNGRA2, // Grass objects. OBJ_ICN_TYPE_TREDECI, // Deciduous trees. OBJ_ICN_TYPE_OBJNWATR, // Water objects. OBJ_ICN_TYPE_OBJNGRAS, // Non-action grass objects. OBJ_ICN_TYPE_OBJNSNOW, // Snow objects. OBJ_ICN_TYPE_OBJNSWMP, // Swamp objects. OBJ_ICN_TYPE_OBJNLAVA, // Lava objects. OBJ_ICN_TYPE_OBJNDSRT, // Desert objects. OBJ_ICN_TYPE_OBJNDIRT, // Dirt objects. OBJ_ICN_TYPE_OBJNCRCK, // Crack desert objects. OBJ_ICN_TYPE_OBJNLAV3, // Animated lava objects. OBJ_ICN_TYPE_OBJNMULT, // Miscellaneous ground objects. OBJ_ICN_TYPE_OBJNLAV2, // Animated lava objects. OBJ_ICN_TYPE_X_LOC1, // Objects from The Price of Loyalty expansion. OBJ_ICN_TYPE_X_LOC2, // Objects from The Price of Loyalty expansion. OBJ_ICN_TYPE_X_LOC3 // Objects from The Price of Loyalty expansion. // IMPORTANT!!! If you want to add new types use UNUSED entries only. }; int getIcnIdFromObjectIcnType( const ObjectIcnType objectIcnType ); const char * StringObject( MapObjectType objectType, const int count = 1 ); bool isHiddenForPuzzle( const int terrainType, const ObjectIcnType objectIcnType, uint8_t index ); // The method checks whether the object is an action object depending on whether it is accessed from water or from land. // For example, castle can't be accessed from water. // // TODO: make a separate function to determine whether the object is an action object depending on its location and not // TODO: on where it is accessed from. bool isActionObject( const MapObjectType objectType, const bool accessedFromWater ); // The method checks if the object is an action object regardless of where it is accessed from. bool isActionObject( const MapObjectType objectType ); // The method checks if the object is an action object if it is accessed from water. bool isWaterActionObject( const MapObjectType objectType ); // Returns proper object type if the object is an action object. Otherwise it returns the object type itself. MapObjectType getBaseActionObjectType( const MapObjectType objectType ); bool isValuableResourceObject( const MapObjectType objectType ); bool isCaptureObject( const MapObjectType objectType ); bool isPickupObject( const MapObjectType objectType ); bool isArtifactObject( const MapObjectType objectType ); // Returns true if it is impossible to refuse a fight when visiting a protected object of this type. bool isBattleMandatoryifObjectIsProtected( const MapObjectType objectType ); // Returns true if this object can be safely visited by AI for fog discovery purposes. bool isSafeForFogDiscoveryObject( const MapObjectType objectType ); bool isNeedStayFront( const MapObjectType objectType ); bool isDayLife( const MapObjectType objectType ); bool isWeekLife( const MapObjectType objectType ); bool isMonthLife( const MapObjectType objectType ); bool isBattleLife( const MapObjectType objectType ); // Make sure that you pass a valid action object. int getActionObjectDirection( const MapObjectType objectType ); bool getDiggingHoleSprite( const int terrainType, ObjectIcnType & objectIcnType, uint8_t & index ); bool isDiggingHoleSprite( const int terrainType, const ObjectIcnType objectIcnType, const uint8_t index ); // Only specific objects from the original MP2 format require extended metadata. // These objects store a UID in their initial metadata (quantity1 and quantity2 values). bool doesObjectNeedExtendedMetadata( const MP2::MapObjectType type ); // Only specific objects from the original MP2 format contain metadata (quantity1 and quantity2 values). bool doesObjectContainMetadata( const MP2::MapObjectType type ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/maps/mp2_helper.cpp000066400000000000000000000051231456075706000222470ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "mp2_helper.h" #include "mp2.h" #include "serialize.h" namespace MP2 { void loadTile( StreamBase & stream, mp2tile_t & tile ) { tile.terrainImageIndex = stream.getLE16(); tile.objectName1 = stream.get(); tile.bottomIcnImageIndex = stream.get(); tile.quantity1 = stream.get(); tile.quantity2 = stream.get(); tile.objectName2 = stream.get(); tile.topIcnImageIndex = stream.get(); tile.terrainFlags = stream.get(); tile.mapObjectType = stream.get(); tile.nextAddonIndex = stream.getLE16(); tile.level1ObjectUID = stream.getLE32(); tile.level2ObjectUID = stream.getLE32(); } void loadAddon( StreamBase & stream, mp2addon_t & addon ) { addon.nextAddonIndex = stream.getLE16(); addon.objectNameN1 = stream.get() * 2; // TODO: why we multiply by 2 here? addon.bottomIcnImageIndex = stream.get(); addon.quantityN = stream.get(); addon.objectNameN2 = stream.get(); addon.topIcnImageIndex = stream.get(); addon.level1ObjectUID = stream.getLE32(); addon.level2ObjectUID = stream.getLE32(); } } fheroes2-1.0.12+dfsg/src/fheroes2/maps/mp2_helper.h000066400000000000000000000032251456075706000217150ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once class StreamBase; namespace MP2 { struct mp2tile_t; struct mp2addon_t; void loadTile( StreamBase & stream, mp2tile_t & tile ); void loadAddon( StreamBase & stream, mp2addon_t & addon ); } fheroes2-1.0.12+dfsg/src/fheroes2/maps/pairs.h000066400000000000000000000047651456075706000210100ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2PAIRS_H_ #define H2PAIRS_H_ #include #include "color.h" #include "mp2.h" class IndexObject : public std::pair { public: IndexObject() : std::pair( -1, MP2::OBJ_NONE ) {} IndexObject( int32_t index, int object ) : std::pair( index, object ) {} bool isIndex( int32_t index ) const { return index == first; } bool isObject( int object ) const { return object == second; } }; class ObjectColor : public std::pair { public: ObjectColor() : std::pair( MP2::OBJ_NONE, Color::NONE ) {} ObjectColor( int object, int color ) : std::pair( object, color ) {} bool isColor( int colors ) const { return ( colors & second ) != 0; } }; #endif fheroes2-1.0.12+dfsg/src/fheroes2/maps/position.cpp000066400000000000000000000051431456075706000220600ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "position.h" #include "maps.h" #include "serialize.h" MapPosition::MapPosition( const fheroes2::Point & pt ) : center( pt ) {} int32_t MapPosition::GetIndex() const { return Maps::GetIndexFromAbsPoint( center ); } void MapPosition::SetCenter( const fheroes2::Point & pt ) { center = pt; } void MapPosition::SetIndex( const int32_t index ) { center = Maps::isValidAbsIndex( index ) ? Maps::GetPoint( index ) : fheroes2::Point( -1, -1 ); } StreamBase & operator<<( StreamBase & sb, const MapPosition & st ) { // TODO: before 0.9.4 Point was int16_t type const int16_t x = static_cast( st.center.x ); const int16_t y = static_cast( st.center.y ); return sb << x << y; } StreamBase & operator>>( StreamBase & sb, MapPosition & st ) { // TODO: before 0.9.4 Point was int16_t type int16_t x = 0; int16_t y = 0; sb >> x >> y; st.center.x = x; st.center.y = y; return sb; } fheroes2-1.0.12+dfsg/src/fheroes2/maps/position.h000066400000000000000000000051711456075706000215260ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2010 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2POSITION_H #define H2POSITION_H #include #include "math_base.h" class StreamBase; class MapPosition { public: explicit MapPosition( const fheroes2::Point & = fheroes2::Point( -1, -1 ) ); MapPosition( const MapPosition & ) = delete; virtual ~MapPosition() = default; MapPosition & operator=( const MapPosition & ) = delete; const fheroes2::Point & GetCenter() const { return center; } int32_t GetIndex() const; void SetCenter( const fheroes2::Point & ); void SetIndex( const int32_t index ); virtual bool isPosition( const fheroes2::Point & pt ) const { return pt == center; } protected: friend StreamBase & operator<<( StreamBase &, const MapPosition & ); friend StreamBase & operator>>( StreamBase &, MapPosition & ); fheroes2::Point center; }; StreamBase & operator<<( StreamBase &, const MapPosition & ); StreamBase & operator>>( StreamBase &, MapPosition & ); #endif fheroes2-1.0.12+dfsg/src/fheroes2/maps/visit.cpp000066400000000000000000000043741456075706000213570ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "visit.h" #include "mp2.h" #include "pairs.h" bool Visit::isDayLife( const IndexObject & visit ) { return MP2::isDayLife( static_cast( visit.second ) ); } bool Visit::isWeekLife( const IndexObject & visit ) { return MP2::isWeekLife( static_cast( visit.second ) ); } bool Visit::isMonthLife( const IndexObject & visit ) { return MP2::isMonthLife( static_cast( visit.second ) ); } bool Visit::isBattleLife( const IndexObject & visit ) { return MP2::isBattleLife( static_cast( visit.second ) ); } fheroes2-1.0.12+dfsg/src/fheroes2/maps/visit.h000066400000000000000000000040031456075706000210110ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2MAPSVISIT_H #define H2MAPSVISIT_H class IndexObject; namespace Visit { enum type_t { LOCAL, GLOBAL }; bool isDayLife( const IndexObject & visit ); bool isWeekLife( const IndexObject & visit ); bool isMonthLife( const IndexObject & visit ); bool isBattleLife( const IndexObject & visit ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/monster/000077500000000000000000000000001456075706000202345ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/fheroes2/monster/monster.cpp000066400000000000000000000501001456075706000224230ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "monster.h" #include #include #include "castle.h" #include "color.h" #include "icn.h" #include "luck.h" #include "morale.h" #include "race.h" #include "rand.h" #include "resource.h" #include "spell.h" #include "translations.h" uint32_t Monster::GetMissileICN( uint32_t monsterID ) { switch ( monsterID ) { case Monster::ARCHER: case Monster::RANGER: return ICN::ARCH_MSL; case Monster::ORC: case Monster::ORC_CHIEF: return ICN::ORC__MSL; case Monster::TROLL: return ICN::TROLLMSL; case Monster::WAR_TROLL: return ICN::TROLL2MSL; case Monster::ELF: case Monster::GRAND_ELF: return ICN::ELF__MSL; case Monster::DRUID: case Monster::GREATER_DRUID: return ICN::DRUIDMSL; case Monster::CENTAUR: // Doesn't have own missile file, game falls back to ELF__MSL return ICN::ELF__MSL; case Monster::HALFLING: return ICN::HALFLMSL; case Monster::TITAN: return ICN::TITANMSL; case Monster::LICH: case Monster::POWER_LICH: return ICN::LICH_MSL; default: break; } return ICN::UNKNOWN; } Monster::Monster( const Spell & sp ) : id( UNKNOWN ) { switch ( sp.GetID() ) { case Spell::SETEGUARDIAN: case Spell::SUMMONEELEMENT: id = EARTH_ELEMENT; break; case Spell::SETAGUARDIAN: case Spell::SUMMONAELEMENT: id = AIR_ELEMENT; break; case Spell::SETFGUARDIAN: case Spell::SUMMONFELEMENT: id = FIRE_ELEMENT; break; case Spell::SETWGUARDIAN: case Spell::SUMMONWELEMENT: id = WATER_ELEMENT; break; case Spell::HAUNT: id = GHOST; break; default: break; } } Monster::Monster( int race, uint32_t dw ) : id( UNKNOWN ) { id = FromDwelling( race, dw ).id; } uint32_t Monster::GetAttack() const { return fheroes2::getMonsterData( id ).battleStats.attack; } uint32_t Monster::GetDefense() const { return fheroes2::getMonsterData( id ).battleStats.defense; } int Monster::GetColor() const { return Color::NONE; } int Monster::GetMorale() const { return Morale::NORMAL; } int Monster::GetLuck() const { return Luck::NORMAL; } int Monster::GetRace() const { return fheroes2::getMonsterData( id ).generalStats.race; } uint32_t Monster::GetShots() const { return fheroes2::getMonsterData( id ).battleStats.shots; } // Get universal heuristic of Monster type regardless of context; both combat and strategic value // Doesn't account for situational special bonuses such as spell immunity double Monster::GetMonsterStrength( int attack, int defense ) const { // TODO: do not use virtual functions when calculating strength for troops without hero's skills. // If no modified values were provided then re-calculate // GetAttack and GetDefense will call overloaded versions accounting for Hero bonuses if ( attack == -1 ) attack = GetAttack(); if ( defense == -1 ) defense = GetDefense(); const double attackDefense = 1.0 + attack * 0.1 + defense * 0.05; return attackDefense * fheroes2::getMonsterData( id ).battleStats.monsterBaseStrength; } uint32_t Monster::GetRNDSize() const { if ( !isValid() ) return 0; const uint32_t defaultArmySizePerLevel[7] = { 0, 50, 30, 25, 25, 12, 8 }; uint32_t result = 0; // Check for outliers switch ( id ) { case PEASANT: result = 80; break; case ROGUE: result = 40; break; case PIKEMAN: case VETERAN_PIKEMAN: case WOLF: case ELF: case GRAND_ELF: result = 30; break; case GARGOYLE: result = 25; break; case GHOST: case MEDUSA: result = 20; break; case MINOTAUR: case MINOTAUR_KING: case ROC: case VAMPIRE: case VAMPIRE_LORD: case UNICORN: result = 16; break; case CAVALRY: case CHAMPION: result = 18; break; case PALADIN: case CRUSADER: case CYCLOPS: case PHOENIX: result = 12; break; default: // for most units default range is okay result = defaultArmySizePerLevel[GetMonsterLevel()]; break; } return ( result > 1 ) ? Rand::Get( result / 2, result ) : 1; } bool Monster::isAbilityPresent( const fheroes2::MonsterAbilityType abilityType ) const { const std::vector & abilities = fheroes2::getMonsterData( id ).battleStats.abilities; return std::find( abilities.begin(), abilities.end(), fheroes2::MonsterAbility( abilityType ) ) != abilities.end(); } Monster Monster::GetDowngrade() const { switch ( id ) { case RANGER: return Monster( ARCHER ); case VETERAN_PIKEMAN: return Monster( PIKEMAN ); case MASTER_SWORDSMAN: return Monster( SWORDSMAN ); case CHAMPION: return Monster( CAVALRY ); case CRUSADER: return Monster( PALADIN ); case ORC_CHIEF: return Monster( ORC ); case OGRE_LORD: return Monster( OGRE ); case WAR_TROLL: return Monster( TROLL ); case BATTLE_DWARF: return Monster( DWARF ); case GRAND_ELF: return Monster( ELF ); case GREATER_DRUID: return Monster( DRUID ); case MUTANT_ZOMBIE: return Monster( ZOMBIE ); case ROYAL_MUMMY: return Monster( MUMMY ); case VAMPIRE_LORD: return Monster( VAMPIRE ); case POWER_LICH: return Monster( LICH ); case MINOTAUR_KING: return Monster( MINOTAUR ); case RED_DRAGON: return Monster( GREEN_DRAGON ); case BLACK_DRAGON: return Monster( RED_DRAGON ); case STEEL_GOLEM: return Monster( IRON_GOLEM ); case ARCHMAGE: return Monster( MAGE ); case TITAN: return Monster( GIANT ); default: break; } return Monster( id ); } Monster Monster::GetUpgrade() const { switch ( id ) { case ARCHER: return Monster( RANGER ); case PIKEMAN: return Monster( VETERAN_PIKEMAN ); case SWORDSMAN: return Monster( MASTER_SWORDSMAN ); case CAVALRY: return Monster( CHAMPION ); case PALADIN: return Monster( CRUSADER ); case ORC: return Monster( ORC_CHIEF ); case OGRE: return Monster( OGRE_LORD ); case TROLL: return Monster( WAR_TROLL ); case DWARF: return Monster( BATTLE_DWARF ); case ELF: return Monster( GRAND_ELF ); case DRUID: return Monster( GREATER_DRUID ); case ZOMBIE: return Monster( MUTANT_ZOMBIE ); case MUMMY: return Monster( ROYAL_MUMMY ); case VAMPIRE: return Monster( VAMPIRE_LORD ); case LICH: return Monster( POWER_LICH ); case MINOTAUR: return Monster( MINOTAUR_KING ); case GREEN_DRAGON: return Monster( RED_DRAGON ); case RED_DRAGON: return Monster( BLACK_DRAGON ); case IRON_GOLEM: return Monster( STEEL_GOLEM ); case MAGE: return Monster( ARCHMAGE ); case GIANT: return Monster( TITAN ); default: break; } return Monster( id ); } Monster Monster::FromDwelling( int race, uint32_t dwelling ) { switch ( dwelling ) { case DWELLING_MONSTER1: switch ( race ) { case Race::KNGT: return Monster( PEASANT ); case Race::BARB: return Monster( GOBLIN ); case Race::SORC: return Monster( SPRITE ); case Race::WRLK: return Monster( CENTAUR ); case Race::WZRD: return Monster( HALFLING ); case Race::NECR: return Monster( SKELETON ); default: break; } break; case DWELLING_MONSTER2: switch ( race ) { case Race::KNGT: return Monster( ARCHER ); case Race::BARB: return Monster( ORC ); case Race::SORC: return Monster( DWARF ); case Race::WRLK: return Monster( GARGOYLE ); case Race::WZRD: return Monster( BOAR ); case Race::NECR: return Monster( ZOMBIE ); default: break; } break; case DWELLING_UPGRADE2: switch ( race ) { case Race::KNGT: return Monster( RANGER ); case Race::BARB: return Monster( ORC_CHIEF ); case Race::SORC: return Monster( BATTLE_DWARF ); case Race::WRLK: return Monster( GARGOYLE ); case Race::WZRD: return Monster( BOAR ); case Race::NECR: return Monster( MUTANT_ZOMBIE ); default: break; } break; case DWELLING_MONSTER3: switch ( race ) { case Race::KNGT: return Monster( PIKEMAN ); case Race::BARB: return Monster( WOLF ); case Race::SORC: return Monster( ELF ); case Race::WRLK: return Monster( GRIFFIN ); case Race::WZRD: return Monster( IRON_GOLEM ); case Race::NECR: return Monster( MUMMY ); default: break; } break; case DWELLING_UPGRADE3: switch ( race ) { case Race::KNGT: return Monster( VETERAN_PIKEMAN ); case Race::BARB: return Monster( WOLF ); case Race::SORC: return Monster( GRAND_ELF ); case Race::WRLK: return Monster( GRIFFIN ); case Race::WZRD: return Monster( STEEL_GOLEM ); case Race::NECR: return Monster( ROYAL_MUMMY ); default: break; } break; case DWELLING_MONSTER4: switch ( race ) { case Race::KNGT: return Monster( SWORDSMAN ); case Race::BARB: return Monster( OGRE ); case Race::SORC: return Monster( DRUID ); case Race::WRLK: return Monster( MINOTAUR ); case Race::WZRD: return Monster( ROC ); case Race::NECR: return Monster( VAMPIRE ); default: break; } break; case DWELLING_UPGRADE4: switch ( race ) { case Race::KNGT: return Monster( MASTER_SWORDSMAN ); case Race::BARB: return Monster( OGRE_LORD ); case Race::SORC: return Monster( GREATER_DRUID ); case Race::WRLK: return Monster( MINOTAUR_KING ); case Race::WZRD: return Monster( ROC ); case Race::NECR: return Monster( VAMPIRE_LORD ); default: break; } break; case DWELLING_MONSTER5: switch ( race ) { case Race::KNGT: return Monster( CAVALRY ); case Race::BARB: return Monster( TROLL ); case Race::SORC: return Monster( UNICORN ); case Race::WRLK: return Monster( HYDRA ); case Race::WZRD: return Monster( MAGE ); case Race::NECR: return Monster( LICH ); default: break; } break; case DWELLING_UPGRADE5: switch ( race ) { case Race::KNGT: return Monster( CHAMPION ); case Race::BARB: return Monster( WAR_TROLL ); case Race::SORC: return Monster( UNICORN ); case Race::WRLK: return Monster( HYDRA ); case Race::WZRD: return Monster( ARCHMAGE ); case Race::NECR: return Monster( POWER_LICH ); default: break; } break; case DWELLING_MONSTER6: switch ( race ) { case Race::KNGT: return Monster( PALADIN ); case Race::BARB: return Monster( CYCLOPS ); case Race::SORC: return Monster( PHOENIX ); case Race::WRLK: return Monster( GREEN_DRAGON ); case Race::WZRD: return Monster( GIANT ); case Race::NECR: return Monster( BONE_DRAGON ); default: break; } break; case DWELLING_UPGRADE6: switch ( race ) { case Race::KNGT: return Monster( CRUSADER ); case Race::BARB: return Monster( CYCLOPS ); case Race::SORC: return Monster( PHOENIX ); case Race::WRLK: return Monster( RED_DRAGON ); case Race::WZRD: return Monster( TITAN ); case Race::NECR: return Monster( BONE_DRAGON ); default: break; } break; case DWELLING_UPGRADE7: switch ( race ) { case Race::KNGT: return Monster( CRUSADER ); case Race::BARB: return Monster( CYCLOPS ); case Race::SORC: return Monster( PHOENIX ); case Race::WRLK: return Monster( BLACK_DRAGON ); case Race::WZRD: return Monster( TITAN ); case Race::NECR: return Monster( BONE_DRAGON ); default: break; } break; default: break; } return Monster( UNKNOWN ); } Monster Monster::Rand( const LevelType type ) { if ( type == LevelType::LEVEL_ANY ) return Monster( Rand::Get( PEASANT, WATER_ELEMENT ) ); static std::vector monstersVec[static_cast( LevelType::LEVEL_4 )]; if ( monstersVec[0].empty() ) { for ( uint32_t i = PEASANT; i <= WATER_ELEMENT; ++i ) { const Monster monster( i ); if ( monster.GetRandomUnitLevel() > LevelType::LEVEL_ANY ) monstersVec[static_cast( monster.GetRandomUnitLevel() ) - 1].push_back( monster ); } } return Rand::Get( monstersVec[static_cast( type ) - 1] ); } Monster::LevelType Monster::GetRandomUnitLevel() const { switch ( id ) { case PEASANT: case ARCHER: case GOBLIN: case ORC: case SPRITE: case CENTAUR: case HALFLING: case SKELETON: case ZOMBIE: case ROGUE: case RANDOM_MONSTER_LEVEL_1: return LevelType::LEVEL_1; case RANGER: case PIKEMAN: case VETERAN_PIKEMAN: case ORC_CHIEF: case WOLF: case DWARF: case BATTLE_DWARF: case ELF: case GRAND_ELF: case GARGOYLE: case BOAR: case IRON_GOLEM: case MUTANT_ZOMBIE: case MUMMY: case NOMAD: case RANDOM_MONSTER_LEVEL_2: return LevelType::LEVEL_2; case SWORDSMAN: case MASTER_SWORDSMAN: case CAVALRY: case CHAMPION: case OGRE: case OGRE_LORD: case TROLL: case WAR_TROLL: case DRUID: case GREATER_DRUID: case GRIFFIN: case MINOTAUR: case MINOTAUR_KING: case STEEL_GOLEM: case ROC: case MAGE: case ARCHMAGE: case ROYAL_MUMMY: case VAMPIRE: case VAMPIRE_LORD: case LICH: case GHOST: case MEDUSA: case EARTH_ELEMENT: case AIR_ELEMENT: case FIRE_ELEMENT: case WATER_ELEMENT: case RANDOM_MONSTER_LEVEL_3: return LevelType::LEVEL_3; case PALADIN: case CRUSADER: case CYCLOPS: case UNICORN: case PHOENIX: case HYDRA: case GREEN_DRAGON: case RED_DRAGON: case BLACK_DRAGON: case GIANT: case TITAN: case POWER_LICH: case BONE_DRAGON: case GENIE: case RANDOM_MONSTER_LEVEL_4: return LevelType::LEVEL_4; case RANDOM_MONSTER: switch ( Rand::Get( 0, 3 ) ) { default: return LevelType::LEVEL_1; case 1: return LevelType::LEVEL_2; case 2: return LevelType::LEVEL_3; case 3: return LevelType::LEVEL_4; } default: break; } return LevelType::LEVEL_ANY; } uint32_t Monster::GetDwelling() const { switch ( id ) { case PEASANT: case GOBLIN: case SPRITE: case CENTAUR: case HALFLING: case SKELETON: return DWELLING_MONSTER1; case ARCHER: case ORC: case ZOMBIE: case DWARF: case GARGOYLE: case BOAR: return DWELLING_MONSTER2; case RANGER: case ORC_CHIEF: case BATTLE_DWARF: case MUTANT_ZOMBIE: return DWELLING_UPGRADE2; case PIKEMAN: case WOLF: case ELF: case IRON_GOLEM: case MUMMY: case GRIFFIN: return DWELLING_MONSTER3; case VETERAN_PIKEMAN: case GRAND_ELF: case STEEL_GOLEM: case ROYAL_MUMMY: return DWELLING_UPGRADE3; case SWORDSMAN: case OGRE: case DRUID: case MINOTAUR: case ROC: case VAMPIRE: return DWELLING_MONSTER4; case MASTER_SWORDSMAN: case OGRE_LORD: case GREATER_DRUID: case MINOTAUR_KING: case VAMPIRE_LORD: return DWELLING_UPGRADE4; case CAVALRY: case TROLL: case MAGE: case LICH: case UNICORN: case HYDRA: return DWELLING_MONSTER5; case CHAMPION: case WAR_TROLL: case ARCHMAGE: case POWER_LICH: return DWELLING_UPGRADE5; case PALADIN: case CYCLOPS: case PHOENIX: case GREEN_DRAGON: case GIANT: case BONE_DRAGON: return DWELLING_MONSTER6; case CRUSADER: case RED_DRAGON: case TITAN: return DWELLING_UPGRADE6; case BLACK_DRAGON: return DWELLING_UPGRADE7; default: break; } return 0; } const char * Monster::GetName() const { return _( fheroes2::getMonsterData( id ).generalStats.name ); } const char * Monster::GetMultiName() const { return _( fheroes2::getMonsterData( id ).generalStats.pluralName ); } const char * Monster::GetPluralName( uint32_t count ) const { const fheroes2::MonsterGeneralStats & generalStats = fheroes2::getMonsterData( id ).generalStats; return count == 1 ? _( generalStats.name ) : _( generalStats.pluralName ); } int Monster::ICNMonh() const { return id >= PEASANT && id <= WATER_ELEMENT ? ICN::MONH0000 + id - PEASANT : ICN::UNKNOWN; } Funds Monster::GetUpgradeCost() const { const Monster upgr = GetUpgrade(); if ( id == upgr.id ) { return {}; } return ( upgr.GetCost() - GetCost() ) * 2; } uint32_t Monster::GetCountFromHitPoints( const Monster & mons, const uint32_t hp ) { if ( hp == 0 ) { return 0; } const uint32_t singleMonsterHP = mons.GetHitPoints(); const uint32_t quotient = hp / singleMonsterHP; const uint32_t remainder = hp % singleMonsterHP; return ( remainder > 0 ? quotient + 1 : quotient ); } fheroes2-1.0.12+dfsg/src/fheroes2/monster/monster.h000066400000000000000000000174041456075706000221020ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2MONSTER_H #define H2MONSTER_H #include #include "monster_info.h" #include "resource.h" class Spell; class Monster { public: enum { JOIN_CONDITION_SKIP = 0, JOIN_CONDITION_MONEY = 1, JOIN_CONDITION_FREE = 2 }; enum class LevelType : int { LEVEL_ANY = 0, LEVEL_1, LEVEL_2, LEVEL_3, LEVEL_4 }; enum monster_t : int32_t { UNKNOWN, PEASANT, ARCHER, RANGER, PIKEMAN, VETERAN_PIKEMAN, SWORDSMAN, MASTER_SWORDSMAN, CAVALRY, CHAMPION, PALADIN, CRUSADER, GOBLIN, ORC, ORC_CHIEF, WOLF, OGRE, OGRE_LORD, TROLL, WAR_TROLL, CYCLOPS, SPRITE, DWARF, BATTLE_DWARF, ELF, GRAND_ELF, DRUID, GREATER_DRUID, UNICORN, PHOENIX, CENTAUR, GARGOYLE, GRIFFIN, MINOTAUR, MINOTAUR_KING, HYDRA, GREEN_DRAGON, RED_DRAGON, BLACK_DRAGON, HALFLING, BOAR, IRON_GOLEM, STEEL_GOLEM, ROC, MAGE, ARCHMAGE, GIANT, TITAN, SKELETON, ZOMBIE, MUTANT_ZOMBIE, MUMMY, ROYAL_MUMMY, VAMPIRE, VAMPIRE_LORD, LICH, POWER_LICH, BONE_DRAGON, ROGUE, NOMAD, GHOST, GENIE, MEDUSA, EARTH_ELEMENT, AIR_ELEMENT, FIRE_ELEMENT, WATER_ELEMENT, // Editor-related monsters. RANDOM_MONSTER, RANDOM_MONSTER_LEVEL_1, RANDOM_MONSTER_LEVEL_2, RANDOM_MONSTER_LEVEL_3, RANDOM_MONSTER_LEVEL_4, // IMPORTANT! Put all new monsters just above this line. MONSTER_COUNT }; Monster( const int m = UNKNOWN ) : id( m ) { // Do nothing. } explicit Monster( const Spell & ); Monster( int race, uint32_t dw ); virtual ~Monster() = default; bool operator==( const Monster & m ) const { return id == m.id; } bool operator!=( const Monster & m ) const { return id != m.id; } int GetID() const { return id; } void Upgrade() { id = GetUpgrade().id; } Monster GetUpgrade() const; Monster GetDowngrade() const; virtual uint32_t GetAttack() const; virtual uint32_t GetDefense() const; virtual int GetColor() const; virtual int GetMorale() const; virtual int GetLuck() const; virtual int GetRace() const; uint32_t GetDamageMin() const { return fheroes2::getMonsterData( id ).battleStats.damageMin; } uint32_t GetDamageMax() const { return fheroes2::getMonsterData( id ).battleStats.damageMax; } virtual uint32_t GetShots() const; uint32_t GetHitPoints() const { return fheroes2::getMonsterData( id ).battleStats.hp; } uint32_t GetSpeed() const { return fheroes2::getMonsterData( id ).battleStats.speed; } uint32_t GetGrown() const { return fheroes2::getMonsterData( id ).generalStats.baseGrowth; } int GetMonsterLevel() const { return fheroes2::getMonsterData( id ).generalStats.level; } LevelType GetRandomUnitLevel() const; uint32_t GetRNDSize() const; const char * GetName() const; const char * GetMultiName() const; const char * GetPluralName( uint32_t ) const; bool isValid() const { return id != UNKNOWN && id < MONSTER_COUNT && !isRandomMonster(); } bool isRandomMonster() const { return ( id >= RANDOM_MONSTER && id <= RANDOM_MONSTER_LEVEL_4 ); } bool isElemental() const { return isAbilityPresent( fheroes2::MonsterAbilityType::ELEMENTAL ); } bool isUndead() const { return isAbilityPresent( fheroes2::MonsterAbilityType::UNDEAD ); } bool isFlying() const { return isAbilityPresent( fheroes2::MonsterAbilityType::FLYING ); } bool isWide() const { return isAbilityPresent( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE ); } bool isArchers() const { return GetShots() > 0; } bool isAllowUpgrade() const { return id != GetUpgrade().id; } bool isRegenerating() const { return isAbilityPresent( fheroes2::MonsterAbilityType::HP_REGENERATION ); } bool isDoubleCellAttack() const { return isAbilityPresent( fheroes2::MonsterAbilityType::TWO_CELL_MELEE_ATTACK ); } bool isAllAdjacentCellsAttack() const { return isAbilityPresent( fheroes2::MonsterAbilityType::ALL_ADJACENT_CELL_MELEE_ATTACK ); } bool isIgnoringRetaliation() const { return isAbilityPresent( fheroes2::MonsterAbilityType::NO_ENEMY_RETALIATION ); } bool isDragons() const { return isAbilityPresent( fheroes2::MonsterAbilityType::DRAGON ); } bool isAffectedByMorale() const { // TODO: possible optimization: run through all abilities once. return !( isUndead() || isElemental() ); } bool isAbilityPresent( const fheroes2::MonsterAbilityType abilityType ) const; double GetMonsterStrength( int attack = -1, int defense = -1 ) const; int ICNMonh() const; uint32_t GetSpriteIndex() const { return UNKNOWN < id ? id - 1 : 0; } Funds GetCost() const { return Funds( fheroes2::getMonsterData( id ).generalStats.cost ); } uint32_t GetDwelling() const; int GetMonsterSprite() const { return fheroes2::getMonsterData( id ).icnId; } static Monster Rand( const LevelType type ); static uint32_t GetCountFromHitPoints( const Monster & mons, const uint32_t hp ); static uint32_t GetMissileICN( uint32_t monsterID ); protected: // Returns the cost of an upgrade if a monster has an upgrade. Otherwise returns no resources. Funds GetUpgradeCost() const; static Monster FromDwelling( int race, uint32_t dw ); int id; }; #endif fheroes2-1.0.12+dfsg/src/fheroes2/monster/monster_anim.cpp000066400000000000000000000155121456075706000234370ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "monster_anim.h" #include #include "monster.h" #include "monster_info.h" #include "rand.h" namespace { const std::array monsterAnimationSequence = { 0, 0, 1, 2, 1, 0, 0, 0, 3, 4, 5, 4, 3, 0, 0 }; } namespace fheroes2 { const std::array & getMonsterAnimationSequence() { return monsterAnimationSequence; } RandomMonsterAnimation::RandomMonsterAnimation( const Monster & monster ) : _reference( monster.GetID() ) , _icnID( fheroes2::getMonsterData( monster.GetID() ).icnId ) , _frameId( 0 ) , _frameOffset( 0 ) , _isFlyer( monster.isFlying() ) { _addValidMove( Monster_Info::STATIC ); _addValidMove( Monster_Info::STATIC ); _addValidMove( Monster_Info::IDLE ); _addValidMove( Monster_Info::MELEE_TOP ); _addValidMove( Monster_Info::MELEE_FRONT ); _addValidMove( Monster_Info::MELEE_BOT ); _addValidMove( Monster_Info::RANG_TOP ); _addValidMove( Monster_Info::RANG_FRONT ); _addValidMove( Monster_Info::RANG_BOT ); _addValidMove( Monster_Info::MOVING ); _addValidMove( Monster_Info::MOVING ); _addValidMove( Monster_Info::WNCE ); _addValidMove( Monster_Info::KILL ); increment(); } void RandomMonsterAnimation::increment() { if ( _frameSet.empty() ) { // make sure both are empty to avoid leftovers in case of mismatch _offsetSet.clear(); const int moveId = Rand::Get( _validMoves ); if ( moveId == Monster_Info::STATIC ) { const uint32_t counter = Rand::Get( 10, 20 ); for ( uint32_t i = 0; i < counter; ++i ) _pushFrames( Monster_Info::STATIC ); } else if ( moveId == Monster_Info::IDLE ) { _pushFrames( Monster_Info::IDLE ); } else if ( moveId == Monster_Info::MOVING ) { _pushFrames( _isFlyer ? Monster_Info::FLY_UP : Monster_Info::MOVE_START ); const uint32_t counter = Rand::Get( 3, 5 ); for ( uint32_t j = 0; j < counter; ++j ) _pushFrames( Monster_Info::MOVING ); _pushFrames( _isFlyer ? Monster_Info::FLY_LAND : Monster_Info::MOVE_END ); } else if ( moveId == Monster_Info::MELEE_TOP ) { _pushFrames( Monster_Info::MELEE_TOP ); _pushFrames( Monster_Info::MELEE_TOP_END ); } else if ( moveId == Monster_Info::MELEE_FRONT ) { _pushFrames( Monster_Info::MELEE_FRONT ); _pushFrames( Monster_Info::MELEE_FRONT_END ); } else if ( moveId == Monster_Info::MELEE_BOT ) { _pushFrames( Monster_Info::MELEE_BOT ); _pushFrames( Monster_Info::MELEE_BOT_END ); } else if ( moveId == Monster_Info::RANG_TOP ) { _pushFrames( Monster_Info::RANG_TOP ); _pushFrames( Monster_Info::RANG_TOP_END ); } else if ( moveId == Monster_Info::RANG_FRONT ) { _pushFrames( Monster_Info::RANG_FRONT ); _pushFrames( Monster_Info::RANG_FRONT_END ); } else if ( moveId == Monster_Info::RANG_BOT ) { _pushFrames( Monster_Info::RANG_BOT ); _pushFrames( Monster_Info::RANG_BOT_END ); } else if ( moveId == Monster_Info::WNCE ) { _pushFrames( Monster_Info::WNCE ); } else if ( moveId == Monster_Info::KILL ) { _pushFrames( Monster_Info::KILL ); } _pushFrames( Monster_Info::STATIC ); } _updateFrameInfo(); } int RandomMonsterAnimation::icnFile() const { return _icnID; } int RandomMonsterAnimation::frameId() const { return _frameId; } int RandomMonsterAnimation::offset() const { return _frameOffset; } void RandomMonsterAnimation::reset() { _frameSet.clear(); _offsetSet.clear(); _pushFrames( Monster_Info::STATIC ); _updateFrameInfo(); } void RandomMonsterAnimation::_pushFrames( const Monster_Info::AnimationType type ) { const std::vector & sequence = _reference.getAnimationVector( type ); _frameSet.insert( _frameSet.end(), sequence.begin(), sequence.end() ); if ( type == Monster_Info::IDLE ) { // a special case _offsetSet.insert( _offsetSet.end(), sequence.size(), 0 ); } else { const std::vector & offset = _reference.getAnimationOffset( type ); _offsetSet.insert( _offsetSet.end(), offset.begin(), offset.end() ); } if ( _offsetSet.size() != _frameSet.size() ) _offsetSet.resize( _frameSet.size(), 0 ); } void RandomMonsterAnimation::_addValidMove( const Monster_Info::AnimationType type ) { if ( !_reference.getAnimationVector( type ).empty() ) _validMoves.push_back( type ); } void RandomMonsterAnimation::_updateFrameInfo() { if ( _frameSet.empty() ) return; _frameId = _frameSet.front(); _frameSet.pop_front(); if ( !_offsetSet.empty() ) { _frameOffset = _offsetSet.front(); _offsetSet.pop_front(); } } } fheroes2-1.0.12+dfsg/src/fheroes2/monster/monster_anim.h000066400000000000000000000066561456075706000231150ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include #include #include "battle_animation.h" class Monster; namespace Monster_Info { enum AttackDirection : int { TOP, FRONT, BOTTOM }; enum AnimationType : int { NONE, STAND_STILL, // This animation contains one frame of a standing unit. STATIC, // This is a copy of STAND STILL, with the difference that the game engine may randomly change it to IDLE. IDLE, // After the idle animation is done, the game engine changes it to STATIC. MOVE_START, MOVING, MOVE_END, MOVE_QUICK, FLY_UP, FLY_LAND, MELEE_TOP, MELEE_TOP_END, MELEE_FRONT, MELEE_FRONT_END, MELEE_BOT, MELEE_BOT_END, RANG_TOP, RANG_TOP_END, RANG_FRONT, RANG_FRONT_END, RANG_BOT, RANG_BOT_END, WNCE_UP, WNCE_DOWN, WNCE, // combined UP and RETURN anim KILL, INVALID }; } namespace fheroes2 { const std::array & getMonsterAnimationSequence(); class RandomMonsterAnimation { public: explicit RandomMonsterAnimation( const Monster & monster ); RandomMonsterAnimation( const RandomMonsterAnimation & ) = delete; RandomMonsterAnimation & operator=( const RandomMonsterAnimation & ) = delete; void increment(); int icnFile() const; int frameId() const; int offset() const; void reset(); // reset to static animation private: AnimationReference _reference; int _icnID; std::vector _validMoves; std::list _frameSet; std::list _offsetSet; int _frameId; int _frameOffset; bool _isFlyer; void _pushFrames( const Monster_Info::AnimationType type ); void _addValidMove( const Monster_Info::AnimationType type ); void _updateFrameInfo(); }; } fheroes2-1.0.12+dfsg/src/fheroes2/monster/monster_info.cpp000066400000000000000000001774011456075706000234540ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "monster_info.h" #include #include #include #include #include #include #include #include #include #include #include "icn.h" #include "m82.h" #include "monster.h" #include "race.h" #include "speed.h" #include "spell.h" #include "tools.h" #include "translations.h" namespace { std::vector monsterData; bool isAbilityPresent( const std::vector & abilities, const fheroes2::MonsterAbilityType abilityType ) { return std::find( abilities.begin(), abilities.end(), fheroes2::MonsterAbility( abilityType ) ) != abilities.end(); } double getMonsterBaseStrength( const fheroes2::MonsterData & data ) { const fheroes2::MonsterBattleStats & battleStats = data.battleStats; const std::vector & abilities = battleStats.abilities; const double effectiveHP = battleStats.hp * ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::NO_ENEMY_RETALIATION ) ? 1.4 : 1 ); const bool isArchers = ( battleStats.shots > 0 ); double damagePotential = ( battleStats.damageMin + battleStats.damageMax ) / 2.0; if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::TWO_CELL_MELEE_ATTACK ) ) { // Melee attacker will lose potential on second attack after retaliation damagePotential *= ( isArchers || isAbilityPresent( abilities, fheroes2::MonsterAbilityType::NO_ENEMY_RETALIATION ) ) ? 2 : 1.75; } if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::DOUBLE_DAMAGE_TO_UNDEAD ) ) { damagePotential *= 1.15; // 15% of all Monsters are Undead, deals double damage. } if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::TWO_CELL_MELEE_ATTACK ) ) { damagePotential *= 1.2; } if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::ALWAYS_RETALIATE ) ) { damagePotential *= 1.25; } if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::ALL_ADJACENT_CELL_MELEE_ATTACK ) || isAbilityPresent( abilities, fheroes2::MonsterAbilityType::AREA_SHOT ) ) { damagePotential *= 1.3; } double monsterSpecial = 1.0; if ( isArchers ) { monsterSpecial += isAbilityPresent( abilities, fheroes2::MonsterAbilityType::NO_MELEE_PENALTY ) ? 0.5 : 0.4; } if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::FLYING ) ) { monsterSpecial += 0.3; } if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::ENEMY_HALFING ) ) { monsterSpecial += 1; } if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::SOUL_EATER ) ) { monsterSpecial += 2; } if ( isAbilityPresent( abilities, fheroes2::MonsterAbilityType::HP_DRAIN ) ) { monsterSpecial += 0.3; } auto foundAbility = std::find( abilities.begin(), abilities.end(), fheroes2::MonsterAbility( fheroes2::MonsterAbilityType::SPELL_CASTER ) ); if ( foundAbility != abilities.end() ) { // This is a tricky evaluation. Spell casting ability depends on a type of spell and chance to inflict the spell. switch ( foundAbility->value ) { case Spell::PARALYZE: case Spell::BLIND: case Spell::PETRIFY: monsterSpecial += foundAbility->percentage / 100.0; break; case Spell::DISPEL: case Spell::CURSE: // These spell are very weak and do not impact much during battle. monsterSpecial += foundAbility->percentage / 100.0 / 10.0; break; default: // Did you add a new spell casting ability? Add the logic above! assert( 0 ); break; } } // Higher speed gives initiative advantage/first attack. Remap speed value to -0.2...+0.15, AVERAGE is 0 // Punish slow speeds more as unit won't participate in first rounds and slows down strategic army const int speedDiff = battleStats.speed - Speed::AVERAGE; monsterSpecial += ( speedDiff < 0 ) ? speedDiff * 0.1 : speedDiff * 0.05; // Additional HP and Damage effectiveness diminishes with every combat round; strictly x4 HP == x2 unit count return sqrt( damagePotential * effectiveHP ) * monsterSpecial; } void populateMonsterData() { const int monsterIcnIds[Monster::MONSTER_COUNT] = { ICN::UNKNOWN, ICN::PEASANT, ICN::ARCHER, ICN::ARCHER2, ICN::PIKEMAN, ICN::PIKEMAN2, ICN::SWORDSMN, ICN::SWORDSM2, ICN::CAVALRYR, ICN::CAVALRYB, ICN::PALADIN, ICN::PALADIN2, ICN::GOBLIN, ICN::ORC, ICN::ORC2, ICN::WOLF, ICN::OGRE, ICN::OGRE2, ICN::TROLL, ICN::TROLL2, ICN::CYCLOPS, ICN::SPRITE, ICN::DWARF, ICN::DWARF2, ICN::ELF, ICN::ELF2, ICN::DRUID, ICN::DRUID2, ICN::UNICORN, ICN::PHOENIX, ICN::CENTAUR, ICN::GARGOYLE, ICN::GRIFFIN, ICN::MINOTAUR, ICN::MINOTAU2, ICN::HYDRA, ICN::DRAGGREE, ICN::DRAGRED, ICN::DRAGBLAK, ICN::HALFLING, ICN::BOAR, ICN::GOLEM, ICN::GOLEM2, ICN::ROC, ICN::MAGE1, ICN::MAGE2, ICN::TITANBLU, ICN::TITANBLA, ICN::SKELETON, ICN::ZOMBIE, ICN::ZOMBIE2, ICN::MUMMYW, ICN::MUMMY2, ICN::VAMPIRE, ICN::VAMPIRE2, ICN::LICH, ICN::LICH2, ICN::DRAGBONE, ICN::ROGUE, ICN::NOMAD, ICN::GHOST, ICN::GENIE, ICN::MEDUSA, ICN::EELEM, ICN::AELEM, ICN::FELEM, ICN::WELEM, ICN::UNKNOWN, ICN::UNKNOWN, ICN::UNKNOWN, ICN::UNKNOWN, ICN::UNKNOWN }; const char * binFileName[Monster::MONSTER_COUNT] = { "UNKNOWN", "PEAS_FRM.BIN", "ARCHRFRM.BIN", "ARCHRFRM.BIN", "PIKMNFRM.BIN", "PIKMNFRM.BIN", "SWRDSFRM.BIN", "SWRDSFRM.BIN", "CVLRYFRM.BIN", "CVLR2FRM.BIN", "PALADFRM.BIN", "PALADFRM.BIN", "GOBLNFRM.BIN", "ORC__FRM.BIN", "ORC__FRM.BIN", "WOLF_FRM.BIN", "OGRE_FRM.BIN", "OGRE_FRM.BIN", "TROLLFRM.BIN", "TROLLFRM.BIN", "CYCLOFRM.BIN", "SPRITFRM.BIN", "DWARFFRM.BIN", "DWARFFRM.BIN", "ELF__FRM.BIN", "ELF__FRM.BIN", "DRUIDFRM.BIN", "DRUIDFRM.BIN", "UNICOFRM.BIN", "PHOENFRM.BIN", "CENTRFRM.BIN", "GARGLFRM.BIN", "GRIFFFRM.BIN", "MINOTFRM.BIN", "MINOTFRM.BIN", "HYDRAFRM.BIN", "DRAGGFRM.BIN", "DRAGRFRM.BIN", "DRAGBFRM.BIN", "HALFLFRM.BIN", "BOAR_FRM.BIN", "GOLEMFRM.BIN", "GOLEMFRM.BIN", "ROC__FRM.BIN", "MAGE1FRM.BIN", "MAGE1FRM.BIN", "TITANFRM.BIN", "TITA2FRM.BIN", "SKEL_FRM.BIN", "ZOMB_FRM.BIN", "ZOMB_FRM.BIN", "MUMMYFRM.BIN", "MUMMYFRM.BIN", "VAMPIFRM.BIN", "VAMPIFRM.BIN", "LICH_FRM.BIN", "LICH_FRM.BIN", "DRABNFRM.BIN", "ROGUEFRM.BIN", "NOMADFRM.BIN", "GHOSTFRM.BIN", "GENIEFRM.BIN", "MEDUSFRM.BIN", "FELEMFRM.BIN", "FELEMFRM.BIN", "FELEMFRM.BIN", "FELEMFRM.BIN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN", "UNKNOWN" }; const fheroes2::MonsterSound monsterSounds[Monster::MONSTER_COUNT] = { // melee attack | death | movement | wince | ranged attack | takeoff | landing | explosion { M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Unknown Monster { M82::PSNTATTK, M82::PSNTKILL, M82::PSNTMOVE, M82::PSNTWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Peasant { M82::ARCHATTK, M82::ARCHKILL, M82::ARCHMOVE, M82::ARCHWNCE, M82::ARCHSHOT, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Archer { M82::ARCHATTK, M82::ARCHKILL, M82::ARCHMOVE, M82::ARCHWNCE, M82::ARCHSHOT, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Ranger { M82::PIKEATTK, M82::PIKEKILL, M82::PIKEMOVE, M82::PIKEWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Pikeman { M82::PIKEATTK, M82::PIKEKILL, M82::PIKEMOVE, M82::PIKEWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Veteran Pikeman { M82::SWDMATTK, M82::SWDMKILL, M82::SWDMMOVE, M82::SWDMWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Swordsman { M82::SWDMATTK, M82::SWDMKILL, M82::SWDMMOVE, M82::SWDMWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Master Swordsman { M82::CAVLATTK, M82::CAVLKILL, M82::CAVLMOVE, M82::CAVLWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Cavalry { M82::CAVLATTK, M82::CAVLKILL, M82::CAVLMOVE, M82::CAVLWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Champion { M82::PLDNATTK, M82::PLDNKILL, M82::PLDNMOVE, M82::PLDNWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Paladin { M82::PLDNATTK, M82::PLDNKILL, M82::PLDNMOVE, M82::PLDNWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Crusader { M82::GBLNATTK, M82::GBLNKILL, M82::GBLNMOVE, M82::GBLNWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Goblin { M82::ORC_ATTK, M82::ORC_KILL, M82::ORC_MOVE, M82::ORC_WNCE, M82::ORC_SHOT, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Orc { M82::ORC_ATTK, M82::ORC_KILL, M82::ORC_MOVE, M82::ORC_WNCE, M82::ORC_SHOT, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Orc Chief { M82::WOLFATTK, M82::WOLFKILL, M82::WOLFMOVE, M82::WOLFWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Wolf { M82::OGREATTK, M82::OGREKILL, M82::OGREMOVE, M82::OGREWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Ogre { M82::OGREATTK, M82::OGREKILL, M82::OGREMOVE, M82::OGREWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Ogre Lord { M82::TRLLATTK, M82::TRLLKILL, M82::TRLLMOVE, M82::TRLLWNCE, M82::TRLLSHOT, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Troll { M82::TRLLATTK, M82::TRLLKILL, M82::TRLLMOVE, M82::TRLLWNCE, M82::TRLLSHOT, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // War Troll { M82::CYCLATTK, M82::CYCLKILL, M82::CYCLMOVE, M82::CYCLWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Cyclops { M82::SPRTATTK, M82::SPRTKILL, M82::SPRTMOVE, M82::SPRTWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Sprite { M82::DWRFATTK, M82::DWRFKILL, M82::DWRFMOVE, M82::DWRFWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Dwarf { M82::DWRFATTK, M82::DWRFKILL, M82::DWRFMOVE, M82::DWRFWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Battle Dwarf { M82::ELF_ATTK, M82::ELF_KILL, M82::ELF_MOVE, M82::ELF_WNCE, M82::ELF_SHOT, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Elf { M82::ELF_ATTK, M82::ELF_KILL, M82::ELF_MOVE, M82::ELF_WNCE, M82::ELF_SHOT, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Grand Elf { M82::DRUIATTK, M82::DRUIKILL, M82::DRUIMOVE, M82::DRUIWNCE, M82::DRUISHOT, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Druid { M82::DRUIATTK, M82::DRUIKILL, M82::DRUIMOVE, M82::DRUIWNCE, M82::DRUISHOT, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Greater Druid { M82::UNICATTK, M82::UNICKILL, M82::UNICMOVE, M82::UNICWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Unicorn { M82::PHOEATTK, M82::PHOEKILL, M82::PHOEMOVE, M82::PHOEWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Phoenix { M82::CNTRATTK, M82::CNTRKILL, M82::CNTRMOVE, M82::CNTRWNCE, M82::CNTRSHOT, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Centaur { M82::GARGATTK, M82::GARGKILL, M82::GARGMOVE, M82::GARGWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Gargoyle { M82::GRIFATTK, M82::GRIFKILL, M82::GRIFMOVE, M82::GRIFWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Griffin { M82::MINOATTK, M82::MINOKILL, M82::MINOMOVE, M82::MINOWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Minotaur { M82::MINOATTK, M82::MINOKILL, M82::MINOMOVE, M82::MINOWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Minotaur King { M82::HYDRATTK, M82::HYDRKILL, M82::HYDRMOVE, M82::HYDRWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Hydra { M82::DRGNATTK, M82::DRGNKILL, M82::DRGNMOVE, M82::DRGNWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Green Dragon { M82::DRGNATTK, M82::DRGNKILL, M82::DRGNMOVE, M82::DRGNWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Red Dragon { M82::DRGNATTK, M82::DRGNKILL, M82::DRGNMOVE, M82::DRGNWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Black Dragon { M82::HALFATTK, M82::HALFKILL, M82::HALFMOVE, M82::HALFWNCE, M82::HALFSHOT, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Halfling { M82::BOARATTK, M82::BOARKILL, M82::BOARMOVE, M82::BOARWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Boar { M82::GOLMATTK, M82::GOLMKILL, M82::GOLMMOVE, M82::GOLMWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Iron Golem { M82::GOLMATTK, M82::GOLMKILL, M82::GOLMMOVE, M82::GOLMWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Steel Golem { M82::ROC_ATTK, M82::ROC_KILL, M82::ROC_MOVE, M82::ROC_WNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Roc { M82::MAGEATTK, M82::MAGEKILL, M82::MAGEMOVE, M82::MAGEWNCE, M82::MAGESHOT, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Mage { M82::MAGEATTK, M82::MAGEKILL, M82::MAGEMOVE, M82::MAGEWNCE, M82::MAGESHOT, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Archmage { M82::TITNATTK, M82::TITNKILL, M82::TITNMOVE, M82::TITNWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Giant { M82::TITNATTK, M82::TITNKILL, M82::TITNMOVE, M82::TITNWNCE, M82::TITNSHOT, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Titan { M82::SKELATTK, M82::SKELKILL, M82::SKELMOVE, M82::SKELWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Skeleton { M82::ZOMBATTK, M82::ZOMBKILL, M82::ZOMBMOVE, M82::ZOMBWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Zombie { M82::ZOMBATTK, M82::ZOMBKILL, M82::ZOMBMOVE, M82::ZOMBWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Mutant Zombie { M82::MUMYATTK, M82::MUMYKILL, M82::MUMYMOVE, M82::MUMYWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Mummy { M82::MUMYATTK, M82::MUMYKILL, M82::MUMYMOVE, M82::MUMYWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Royal Mummy { M82::VAMPATTK, M82::VAMPKILL, M82::VAMPMOVE, M82::VAMPWNCE, M82::UNKNOWN, M82::VAMPEXT1, M82::VAMPEXT2, M82::UNKNOWN }, // Vampire { M82::VAMPATTK, M82::VAMPKILL, M82::VAMPMOVE, M82::VAMPWNCE, M82::UNKNOWN, M82::VAMPEXT1, M82::VAMPEXT2, M82::UNKNOWN }, // Vampire Lord { M82::LICHATTK, M82::LICHKILL, M82::LICHMOVE, M82::LICHWNCE, M82::LICHSHOT, M82::UNKNOWN, M82::UNKNOWN, M82::LICHEXPL }, // Lich { M82::LICHATTK, M82::LICHKILL, M82::LICHMOVE, M82::LICHWNCE, M82::LICHSHOT, M82::UNKNOWN, M82::UNKNOWN, M82::LICHEXPL }, // Power Lich { M82::BONEATTK, M82::BONEKILL, M82::BONEMOVE, M82::BONEWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Bone Dragon { M82::ROGUATTK, M82::ROGUKILL, M82::ROGUMOVE, M82::ROGUWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Rogue { M82::NMADATTK, M82::NMADKILL, M82::NMADMOVE, M82::NMADWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Nomad { M82::GHSTATTK, M82::GHSTKILL, M82::GHSTMOVE, M82::GHSTWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Ghost { M82::GENIATTK, M82::GENIKILL, M82::GENIMOVE, M82::GENIWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Genie { M82::MEDSATTK, M82::MEDSKILL, M82::MEDSMOVE, M82::MEDSWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Medusa { M82::EELMATTK, M82::EELMKILL, M82::EELMMOVE, M82::EELMWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Earth Elemental { M82::AELMATTK, M82::AELMKILL, M82::AELMMOVE, M82::AELMWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Air Elemental { M82::FELMATTK, M82::FELMKILL, M82::FELMMOVE, M82::FELMWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Fire Elemental { M82::WELMATTK, M82::WELMKILL, M82::WELMMOVE, M82::WELMWNCE, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Water Elemental { M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Random Monster { M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Random Monster 1 { M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Random Monster 2 { M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Random Monster 3 { M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN, M82::UNKNOWN }, // Random Monster 4 }; // Monster abilities and weaknesses will be added later. Base strength is calculated based on abilities. const fheroes2::MonsterBattleStats monsterBattleStats[Monster::MONSTER_COUNT] = { // attack | defence | damageMin | damageMax | hp | speed | shots | baseStrength | abilities | weaknesses { 0, 0, 0, 0, 0, Speed::VERYSLOW, 0, 0, {}, {} }, // Unknown Monster { 1, 1, 1, 1, 1, Speed::VERYSLOW, 0, 0, {}, {} }, // Peasant { 5, 3, 2, 3, 10, Speed::VERYSLOW, 12, 0, {}, {} }, // Archer { 5, 3, 2, 3, 10, Speed::AVERAGE, 24, 0, {}, {} }, // Ranger { 5, 9, 3, 4, 15, Speed::AVERAGE, 0, 0, {}, {} }, // Pikeman { 5, 9, 3, 4, 20, Speed::FAST, 0, 0, {}, {} }, // Veteran Pikeman { 7, 9, 4, 6, 25, Speed::AVERAGE, 0, 0, {}, {} }, // Swordsman { 7, 9, 4, 6, 30, Speed::FAST, 0, 0, {}, {} }, // Master Swordsman { 10, 9, 5, 10, 30, Speed::VERYFAST, 0, 0, {}, {} }, // Cavalry { 10, 9, 5, 10, 40, Speed::ULTRAFAST, 0, 0, {}, {} }, // Champion { 11, 12, 10, 20, 50, Speed::FAST, 0, 0, {}, {} }, // Paladin { 11, 12, 10, 20, 65, Speed::VERYFAST, 0, 0, {}, {} }, // Crusader { 3, 1, 1, 2, 3, Speed::AVERAGE, 0, 0, {}, {} }, // Goblin { 3, 4, 2, 3, 10, Speed::VERYSLOW, 8, 0, {}, {} }, // Orc { 3, 4, 3, 4, 15, Speed::SLOW, 16, 0, {}, {} }, // Orc Chief { 6, 2, 3, 5, 20, Speed::VERYFAST, 0, 0, {}, {} }, // Wolf { 9, 5, 4, 6, 40, Speed::VERYSLOW, 0, 0, {}, {} }, // Ogre { 9, 5, 5, 7, 60, Speed::AVERAGE, 0, 0, {}, {} }, // Ogre Lord { 10, 5, 5, 7, 40, Speed::AVERAGE, 8, 0, {}, {} }, // Troll { 10, 5, 7, 9, 40, Speed::FAST, 16, 0, {}, {} }, // War Troll { 12, 9, 12, 24, 80, Speed::FAST, 0, 0, {}, {} }, // Cyclops { 4, 2, 1, 2, 2, Speed::AVERAGE, 0, 0, {}, {} }, // Sprite { 6, 5, 2, 4, 20, Speed::VERYSLOW, 0, 0, {}, {} }, // Dwarf { 6, 6, 2, 4, 20, Speed::AVERAGE, 0, 0, {}, {} }, // Battle Dwarf { 4, 3, 2, 3, 15, Speed::AVERAGE, 24, 0, {}, {} }, // Elf { 5, 5, 2, 3, 15, Speed::VERYFAST, 24, 0, {}, {} }, // Grand Elf { 7, 5, 5, 8, 25, Speed::FAST, 8, 0, {}, {} }, // Druid { 7, 7, 5, 8, 25, Speed::VERYFAST, 16, 0, {}, {} }, // Greater Druid { 10, 9, 7, 14, 40, Speed::FAST, 0, 0, {}, {} }, // Unicorn { 12, 10, 20, 40, 100, Speed::ULTRAFAST, 0, 0, {}, {} }, // Phoenix { 3, 1, 1, 2, 5, Speed::AVERAGE, 8, 0, {}, {} }, // Centaur { 4, 7, 2, 3, 15, Speed::VERYFAST, 0, 0, {}, {} }, // Gargoyle { 6, 6, 3, 5, 25, Speed::AVERAGE, 0, 0, {}, {} }, // Griffin { 9, 8, 5, 10, 35, Speed::AVERAGE, 0, 0, {}, {} }, // Minotaur { 9, 8, 5, 10, 45, Speed::VERYFAST, 0, 0, {}, {} }, // Minotaur King { 8, 9, 6, 12, 75, Speed::VERYSLOW, 0, 0, {}, {} }, // Hydra { 12, 12, 25, 50, 200, Speed::AVERAGE, 0, 0, {}, {} }, // Green Dragon { 13, 13, 25, 50, 250, Speed::FAST, 0, 0, {}, {} }, // Red Dragon { 14, 14, 25, 50, 300, Speed::VERYFAST, 0, 0, {}, {} }, // Black Dragon { 2, 1, 1, 3, 3, Speed::SLOW, 12, 0, {}, {} }, // Halfling { 5, 4, 2, 3, 15, Speed::VERYFAST, 0, 0, {}, {} }, // Boar { 5, 10, 4, 5, 30, Speed::VERYSLOW, 0, 0, {}, {} }, // Iron Golem { 7, 10, 4, 5, 35, Speed::SLOW, 0, 0, {}, {} }, // Steel Golem { 7, 7, 4, 8, 40, Speed::AVERAGE, 0, 0, {}, {} }, // Roc { 11, 7, 7, 9, 30, Speed::FAST, 12, 0, {}, {} }, // Mage { 12, 8, 7, 9, 35, Speed::VERYFAST, 24, 0, {}, {} }, // Archmage { 13, 10, 20, 30, 150, Speed::AVERAGE, 0, 0, {}, {} }, // Giant { 15, 15, 20, 30, 300, Speed::VERYFAST, 24, 0, {}, {} }, // Titan { 4, 3, 2, 3, 4, Speed::AVERAGE, 0, 0, {}, {} }, // Skeleton { 5, 2, 2, 3, 15, Speed::VERYSLOW, 0, 0, {}, {} }, // Zombie { 5, 2, 2, 3, 20, Speed::AVERAGE, 0, 0, {}, {} }, // Mutant Zombie { 6, 6, 3, 4, 25, Speed::AVERAGE, 0, 0, {}, {} }, // Mummy { 6, 6, 3, 4, 30, Speed::FAST, 0, 0, {}, {} }, // Royal Mummy { 8, 6, 5, 7, 30, Speed::AVERAGE, 0, 0, {}, {} }, // Vampire { 8, 6, 5, 7, 40, Speed::FAST, 0, 0, {}, {} }, // Vampire Lord { 7, 12, 8, 10, 25, Speed::FAST, 12, 0, {}, {} }, // Lich { 7, 13, 8, 10, 35, Speed::VERYFAST, 24, 0, {}, {} }, // Power Lich { 11, 9, 25, 45, 150, Speed::AVERAGE, 0, 0, {}, {} }, // Bone Dragon { 6, 1, 1, 2, 4, Speed::FAST, 0, 0, {}, {} }, // Rogue { 7, 6, 2, 5, 20, Speed::VERYFAST, 0, 0, {}, {} }, // Nomad { 8, 7, 4, 6, 20, Speed::FAST, 0, 0, {}, {} }, // Ghost { 10, 9, 20, 30, 50, Speed::VERYFAST, 0, 0, {}, {} }, // Genie { 8, 9, 6, 10, 35, Speed::AVERAGE, 0, 0, {}, {} }, // Medusa { 8, 8, 4, 5, 50, Speed::SLOW, 0, 0, {}, {} }, // Earth Elemental { 7, 7, 2, 8, 35, Speed::VERYFAST, 0, 0, {}, {} }, // Air Elemental { 8, 6, 4, 6, 40, Speed::FAST, 0, 0, {}, {} }, // Fire Elemental { 6, 8, 3, 7, 45, Speed::AVERAGE, 0, 0, {}, {} }, // Water Elemental { 0, 0, 0, 0, 0, Speed::VERYSLOW, 0, 0, {}, {} }, // Random Monster { 0, 0, 0, 0, 0, Speed::VERYSLOW, 0, 0, {}, {} }, // Random Monster 1 { 0, 0, 0, 0, 0, Speed::VERYSLOW, 0, 0, {}, {} }, // Random Monster 2 { 0, 0, 0, 0, 0, Speed::VERYSLOW, 0, 0, {}, {} }, // Random Monster 3 { 0, 0, 0, 0, 0, Speed::VERYSLOW, 0, 0, {}, {} }, // Random Monster 4 }; const fheroes2::MonsterGeneralStats monsterGeneralStats[Monster::MONSTER_COUNT] = { // name | plural name | growth | race | level | cost { gettext_noop( "Unknown Monster" ), gettext_noop( "Unknown Monsters" ), 0, Race::NONE, 0, { 0, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Peasant" ), gettext_noop( "Peasants" ), 12, Race::KNGT, 1, { 20, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Archer" ), gettext_noop( "Archers" ), 8, Race::KNGT, 2, { 150, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Ranger" ), gettext_noop( "Rangers" ), 8, Race::KNGT, 2, { 200, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Pikeman" ), gettext_noop( "Pikemen" ), 5, Race::KNGT, 3, { 200, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Veteran Pikeman" ), gettext_noop( "Veteran Pikemen" ), 5, Race::KNGT, 3, { 250, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Swordsman" ), gettext_noop( "Swordsmen" ), 4, Race::KNGT, 4, { 250, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Master Swordsman" ), gettext_noop( "Master Swordsmen" ), 4, Race::KNGT, 4, { 300, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Cavalry" ), gettext_noop( "Cavalries" ), 3, Race::KNGT, 5, { 300, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Champion" ), gettext_noop( "Champions" ), 3, Race::KNGT, 5, { 375, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Paladin" ), gettext_noop( "Paladins" ), 2, Race::KNGT, 6, { 600, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Crusader" ), gettext_noop( "Crusaders" ), 2, Race::KNGT, 6, { 1000, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Goblin" ), gettext_noop( "Goblins" ), 10, Race::BARB, 1, { 40, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Orc" ), gettext_noop( "Orcs" ), 8, Race::BARB, 2, { 140, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Orc Chief" ), gettext_noop( "Orc Chiefs" ), 8, Race::BARB, 2, { 175, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Wolf" ), gettext_noop( "Wolves" ), 5, Race::BARB, 3, { 200, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Ogre" ), gettext_noop( "Ogres" ), 4, Race::BARB, 4, { 300, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Ogre Lord" ), gettext_noop( "Ogre Lords" ), 4, Race::BARB, 4, { 500, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Troll" ), gettext_noop( "Trolls" ), 3, Race::BARB, 5, { 600, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "War Troll" ), gettext_noop( "War Trolls" ), 3, Race::BARB, 5, { 700, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Cyclops" ), gettext_noop( "Cyclopes" ), 2, Race::BARB, 6, { 750, 0, 0, 0, 0, 1, 0 } }, { gettext_noop( "Sprite" ), gettext_noop( "Sprites" ), 8, Race::SORC, 1, { 50, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Dwarf" ), gettext_noop( "Dwarves" ), 6, Race::SORC, 2, { 200, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Battle Dwarf" ), gettext_noop( "Battle Dwarves" ), 6, Race::SORC, 2, { 250, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Elf" ), gettext_noop( "Elves" ), 4, Race::SORC, 3, { 250, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Grand Elf" ), gettext_noop( "Grand Elves" ), 4, Race::SORC, 3, { 300, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Druid" ), gettext_noop( "Druids" ), 3, Race::SORC, 4, { 350, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Greater Druid" ), gettext_noop( "Greater Druids" ), 3, Race::SORC, 4, { 400, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Unicorn" ), gettext_noop( "Unicorns" ), 2, Race::SORC, 5, { 500, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Phoenix" ), gettext_noop( "Phoenixes" ), 1, Race::SORC, 6, { 1500, 0, 1, 0, 0, 0, 0 } }, { gettext_noop( "Centaur" ), gettext_noop( "Centaurs" ), 8, Race::WRLK, 1, { 60, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Gargoyle" ), gettext_noop( "Gargoyles" ), 6, Race::WRLK, 2, { 200, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Griffin" ), gettext_noop( "Griffins" ), 4, Race::WRLK, 3, { 300, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Minotaur" ), gettext_noop( "Minotaurs" ), 3, Race::WRLK, 4, { 400, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Minotaur King" ), gettext_noop( "Minotaur Kings" ), 3, Race::WRLK, 4, { 500, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Hydra" ), gettext_noop( "Hydras" ), 2, Race::WRLK, 5, { 800, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Green Dragon" ), gettext_noop( "Green Dragons" ), 1, Race::WRLK, 6, { 3000, 0, 0, 0, 1, 0, 0 } }, { gettext_noop( "Red Dragon" ), gettext_noop( "Red Dragons" ), 1, Race::WRLK, 6, { 3500, 0, 0, 0, 1, 0, 0 } }, { gettext_noop( "Black Dragon" ), gettext_noop( "Black Dragons" ), 1, Race::WRLK, 6, { 4000, 0, 0, 0, 2, 0, 0 } }, { gettext_noop( "Halfling" ), gettext_noop( "Halflings" ), 8, Race::WZRD, 1, { 50, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Boar" ), gettext_noop( "Boars" ), 6, Race::WZRD, 2, { 150, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Iron Golem" ), gettext_noop( "Iron Golems" ), 4, Race::WZRD, 3, { 300, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Steel Golem" ), gettext_noop( "Steel Golems" ), 4, Race::WZRD, 3, { 350, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Roc" ), gettext_noop( "Rocs" ), 3, Race::WZRD, 4, { 400, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Mage" ), gettext_noop( "Magi" ), 2, Race::WZRD, 5, { 600, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Archmage" ), gettext_noop( "Archmagi" ), 2, Race::WZRD, 5, { 700, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Giant" ), gettext_noop( "Giants" ), 1, Race::WZRD, 6, { 2000, 0, 0, 0, 0, 0, 1 } }, { gettext_noop( "Titan" ), gettext_noop( "Titans" ), 1, Race::WZRD, 6, { 5000, 0, 0, 0, 0, 0, 2 } }, { gettext_noop( "Skeleton" ), gettext_noop( "Skeletons" ), 8, Race::NECR, 1, { 75, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Zombie" ), gettext_noop( "Zombies" ), 6, Race::NECR, 2, { 150, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Mutant Zombie" ), gettext_noop( "Mutant Zombies" ), 6, Race::NECR, 2, { 200, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Mummy" ), gettext_noop( "Mummies" ), 4, Race::NECR, 3, { 250, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Royal Mummy" ), gettext_noop( "Royal Mummies" ), 4, Race::NECR, 3, { 300, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Vampire" ), gettext_noop( "Vampires" ), 3, Race::NECR, 4, { 500, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Vampire Lord" ), gettext_noop( "Vampire Lords" ), 3, Race::NECR, 4, { 650, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Lich" ), gettext_noop( "Liches" ), 2, Race::NECR, 5, { 750, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Power Lich" ), gettext_noop( "Power Liches" ), 2, Race::NECR, 5, { 900, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Bone Dragon" ), gettext_noop( "Bone Dragons" ), 1, Race::NECR, 6, { 1500, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Rogue" ), gettext_noop( "Rogues" ), 8, Race::NONE, 1, { 50, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Nomad" ), gettext_noop( "Nomads" ), 4, Race::NONE, 2, { 200, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Ghost" ), gettext_noop( "Ghosts" ), 3, Race::NONE, 3, { 1000, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Genie" ), gettext_noop( "Genies" ), 2, Race::NONE, 6, { 650, 0, 0, 0, 0, 0, 1 } }, { gettext_noop( "Medusa" ), gettext_noop( "Medusas" ), 5, Race::NONE, 4, { 500, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Earth Elemental" ), gettext_noop( "Earth Elementals" ), 4, Race::NONE, 4, { 500, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Air Elemental" ), gettext_noop( "Air Elementals" ), 4, Race::NONE, 4, { 500, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Fire Elemental" ), gettext_noop( "Fire Elementals" ), 4, Race::NONE, 4, { 500, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Water Elemental" ), gettext_noop( "Water Elementals" ), 4, Race::NONE, 4, { 500, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Random Monster" ), gettext_noop( "Random Monsters" ), 0, Race::NONE, 0, { 0, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Random Monster 1" ), gettext_noop( "Random Monsters 1" ), 0, Race::NONE, 1, { 0, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Random Monster 2" ), gettext_noop( "Random Monsters 2" ), 0, Race::NONE, 2, { 0, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Random Monster 3" ), gettext_noop( "Random Monsters 3" ), 0, Race::NONE, 3, { 0, 0, 0, 0, 0, 0, 0 } }, { gettext_noop( "Random Monster 4" ), gettext_noop( "Random Monsters 4" ), 0, Race::NONE, 4, { 0, 0, 0, 0, 0, 0, 0 } } }; monsterData.reserve( Monster::MONSTER_COUNT ); for ( int i = 0; i < Monster::MONSTER_COUNT; ++i ) { monsterData.emplace_back( monsterIcnIds[i], binFileName[i], monsterSounds[i], monsterBattleStats[i], monsterGeneralStats[i] ); } // Add monster abilities and weaknesses. monsterData[Monster::RANGER].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_SHOOTING ); monsterData[Monster::CAVALRY].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE ); monsterData[Monster::CHAMPION].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE ); monsterData[Monster::PALADIN].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_MELEE_ATTACK ); monsterData[Monster::CRUSADER].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_MELEE_ATTACK ); monsterData[Monster::CRUSADER].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_DAMAGE_TO_UNDEAD ); monsterData[Monster::CRUSADER].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL, 100, Spell::CURSE ); monsterData[Monster::CRUSADER].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL, 100, Spell::MASSCURSE ); monsterData[Monster::WOLF].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE ); monsterData[Monster::WOLF].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_MELEE_ATTACK ); monsterData[Monster::TROLL].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::HP_REGENERATION ); monsterData[Monster::WAR_TROLL].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::HP_REGENERATION ); monsterData[Monster::CYCLOPS].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::TWO_CELL_MELEE_ATTACK ); monsterData[Monster::CYCLOPS].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::SPELL_CASTER, 20, Spell::PARALYZE ); monsterData[Monster::SPRITE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::NO_ENEMY_RETALIATION ); monsterData[Monster::SPRITE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING ); monsterData[Monster::DWARF].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::MAGIC_RESISTANCE, 25, 0 ); monsterData[Monster::BATTLE_DWARF].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::MAGIC_RESISTANCE, 25, 0 ); monsterData[Monster::ELF].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_SHOOTING ); monsterData[Monster::GRAND_ELF].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_SHOOTING ); monsterData[Monster::UNICORN].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE ); monsterData[Monster::UNICORN].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::SPELL_CASTER, 20, Spell::BLIND ); monsterData[Monster::PHOENIX].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE ); monsterData[Monster::PHOENIX].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::TWO_CELL_MELEE_ATTACK ); monsterData[Monster::PHOENIX].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING ); monsterData[Monster::PHOENIX].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ELEMENTAL_SPELL_IMMUNITY ); monsterData[Monster::CENTAUR].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE ); monsterData[Monster::GARGOYLE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING ); monsterData[Monster::GRIFFIN].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE ); monsterData[Monster::GRIFFIN].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING ); monsterData[Monster::GRIFFIN].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ALWAYS_RETALIATE ); monsterData[Monster::HYDRA].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE ); monsterData[Monster::HYDRA].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ALL_ADJACENT_CELL_MELEE_ATTACK ); monsterData[Monster::HYDRA].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::NO_ENEMY_RETALIATION ); monsterData[Monster::GREEN_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DRAGON ); monsterData[Monster::GREEN_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE ); monsterData[Monster::GREEN_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING ); monsterData[Monster::GREEN_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::MAGIC_RESISTANCE, 100, 0 ); monsterData[Monster::GREEN_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::TWO_CELL_MELEE_ATTACK ); monsterData[Monster::RED_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DRAGON ); monsterData[Monster::RED_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE ); monsterData[Monster::RED_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING ); monsterData[Monster::RED_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::MAGIC_RESISTANCE, 100, 0 ); monsterData[Monster::RED_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::TWO_CELL_MELEE_ATTACK ); monsterData[Monster::BLACK_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DRAGON ); monsterData[Monster::BLACK_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE ); monsterData[Monster::BLACK_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING ); monsterData[Monster::BLACK_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::MAGIC_RESISTANCE, 100, 0 ); monsterData[Monster::BLACK_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::TWO_CELL_MELEE_ATTACK ); monsterData[Monster::BOAR].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE ); monsterData[Monster::IRON_GOLEM].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ELEMENTAL_SPELL_DAMAGE_REDUCTION, 50, 0 ); monsterData[Monster::STEEL_GOLEM].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ELEMENTAL_SPELL_DAMAGE_REDUCTION, 50, 0 ); monsterData[Monster::ROC].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE ); monsterData[Monster::ROC].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING ); monsterData[Monster::MAGE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::NO_MELEE_PENALTY ); monsterData[Monster::ARCHMAGE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::NO_MELEE_PENALTY ); monsterData[Monster::ARCHMAGE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::SPELL_CASTER, 20, Spell::DISPEL ); monsterData[Monster::GIANT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::MIND_SPELL_IMMUNITY ); monsterData[Monster::TITAN].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::NO_MELEE_PENALTY ); monsterData[Monster::TITAN].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::MIND_SPELL_IMMUNITY ); monsterData[Monster::SKELETON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::UNDEAD ); monsterData[Monster::ZOMBIE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::UNDEAD ); monsterData[Monster::MUTANT_ZOMBIE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::UNDEAD ); monsterData[Monster::MUMMY].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::UNDEAD ); monsterData[Monster::MUMMY].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::SPELL_CASTER, 20, Spell::CURSE ); monsterData[Monster::ROYAL_MUMMY].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::UNDEAD ); monsterData[Monster::ROYAL_MUMMY].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::SPELL_CASTER, 30, Spell::CURSE ); monsterData[Monster::VAMPIRE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::UNDEAD ); monsterData[Monster::VAMPIRE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING ); monsterData[Monster::VAMPIRE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::NO_ENEMY_RETALIATION ); monsterData[Monster::VAMPIRE_LORD].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::UNDEAD ); monsterData[Monster::VAMPIRE_LORD].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING ); monsterData[Monster::VAMPIRE_LORD].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::NO_ENEMY_RETALIATION ); monsterData[Monster::VAMPIRE_LORD].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::HP_DRAIN ); monsterData[Monster::LICH].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::UNDEAD ); monsterData[Monster::LICH].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::AREA_SHOT ); monsterData[Monster::POWER_LICH].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::UNDEAD ); monsterData[Monster::POWER_LICH].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::AREA_SHOT ); monsterData[Monster::BONE_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::UNDEAD ); monsterData[Monster::BONE_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DRAGON ); monsterData[Monster::BONE_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE ); monsterData[Monster::BONE_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING ); monsterData[Monster::BONE_DRAGON].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::MORAL_DECREMENT, 100, 1 ); monsterData[Monster::ROGUE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::NO_ENEMY_RETALIATION ); monsterData[Monster::GHOST].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::SOUL_EATER ); monsterData[Monster::GHOST].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING ); monsterData[Monster::GHOST].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::UNDEAD ); monsterData[Monster::GENIE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ENEMY_HALFING, 20, 0 ); monsterData[Monster::GENIE].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FLYING ); monsterData[Monster::MEDUSA].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE ); monsterData[Monster::MEDUSA].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::SPELL_CASTER, 20, Spell::PETRIFY ); monsterData[Monster::NOMAD].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::DOUBLE_HEX_SIZE ); monsterData[Monster::AIR_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ELEMENTAL ); monsterData[Monster::AIR_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL, 100, Spell::METEORSHOWER ); monsterData[Monster::AIR_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL, 100, Spell::LIGHTNINGBOLT ); monsterData[Monster::AIR_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL, 100, Spell::CHAINLIGHTNING ); monsterData[Monster::AIR_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL, 100, Spell::ELEMENTALSTORM ); monsterData[Monster::EARTH_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ELEMENTAL ); monsterData[Monster::EARTH_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL, 100, Spell::LIGHTNINGBOLT ); monsterData[Monster::EARTH_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL, 100, Spell::CHAINLIGHTNING ); monsterData[Monster::EARTH_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL, 100, Spell::ELEMENTALSTORM ); monsterData[Monster::EARTH_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL, 100, Spell::METEORSHOWER ); monsterData[Monster::FIRE_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ELEMENTAL ); monsterData[Monster::FIRE_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::FIRE_SPELL_IMMUNITY ); monsterData[Monster::FIRE_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_COLD_SPELL ); monsterData[Monster::WATER_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::ELEMENTAL ); monsterData[Monster::WATER_ELEMENT].battleStats.abilities.emplace_back( fheroes2::MonsterAbilityType::COLD_SPELL_IMMUNITY ); monsterData[Monster::WATER_ELEMENT].battleStats.weaknesses.emplace_back( fheroes2::MonsterWeaknessType::EXTRA_DAMAGE_FROM_FIRE_SPELL ); // Calculate base value of monster strength. for ( fheroes2::MonsterData & data : monsterData ) { data.battleStats.monsterBaseStrength = getMonsterBaseStrength( data ); } // TODO: verify that no duplicates of abilities and weaknesses exist. } void removeDuplicateSpell( std::set & sortedSpellIds, const int massSpellId, const int spellId ) { if ( sortedSpellIds.count( massSpellId ) > 0 && sortedSpellIds.count( spellId ) > 0 ) { sortedSpellIds.erase( massSpellId ); } } std::vector replaceMassSpells( const std::vector & spellIds ) { std::set sortedSpellIds( spellIds.begin(), spellIds.end() ); removeDuplicateSpell( sortedSpellIds, Spell::CHAINLIGHTNING, Spell::LIGHTNINGBOLT ); removeDuplicateSpell( sortedSpellIds, Spell::MASSCURE, Spell::CURE ); removeDuplicateSpell( sortedSpellIds, Spell::MASSCURSE, Spell::CURSE ); removeDuplicateSpell( sortedSpellIds, Spell::MASSBLESS, Spell::BLESS ); removeDuplicateSpell( sortedSpellIds, Spell::MASSHASTE, Spell::HASTE ); removeDuplicateSpell( sortedSpellIds, Spell::MASSSHIELD, Spell::SHIELD ); removeDuplicateSpell( sortedSpellIds, Spell::MASSDISPEL, Spell::DISPEL ); removeDuplicateSpell( sortedSpellIds, Spell::MASSSLOW, Spell::SLOW ); return std::vector( sortedSpellIds.begin(), sortedSpellIds.end() ); } } namespace fheroes2 { const MonsterData & getMonsterData( const int monsterId ) { if ( monsterData.empty() ) { populateMonsterData(); } assert( monsterId >= 0 && static_cast( monsterId ) < monsterData.size() ); if ( monsterId < 0 || static_cast( monsterId ) >= monsterData.size() ) { return monsterData.front(); } return monsterData[monsterId]; } std::string getMonsterAbilityDescription( const MonsterAbility & ability, const bool ignoreBasicAbility ) { switch ( ability.type ) { case MonsterAbilityType::NONE: return ignoreBasicAbility ? "" : _( "None" ); case MonsterAbilityType::DOUBLE_SHOOTING: return _( "Double shot" ); case MonsterAbilityType::DOUBLE_HEX_SIZE: return ignoreBasicAbility ? "" : _( "2-hex monster" ); case MonsterAbilityType::DOUBLE_MELEE_ATTACK: return _( "Double strike" ); case MonsterAbilityType::DOUBLE_DAMAGE_TO_UNDEAD: return _( "Double damage to Undead" ); case MonsterAbilityType::MAGIC_RESISTANCE: return std::to_string( ability.percentage ) + _( "% magic resistance" ); case MonsterAbilityType::MIND_SPELL_IMMUNITY: return _( "Immune to Mind spells" ); case MonsterAbilityType::ELEMENTAL_SPELL_IMMUNITY: return _( "Immune to Elemental spells" ); case MonsterAbilityType::FIRE_SPELL_IMMUNITY: return _( "Immune to Fire spells" ); case MonsterAbilityType::COLD_SPELL_IMMUNITY: return _( "Immune to Cold spells" ); case MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL: if ( ability.percentage == 100 ) { return std::string( _( "Immune to " ) ) + Spell( ability.value ).GetName(); } else { std::string str = _( "% immunity to %{spell} spell" ); StringReplace( str, "%{spell}", Spell( ability.value ).GetName() ); return std::to_string( ability.percentage ) + str; } case MonsterAbilityType::ELEMENTAL_SPELL_DAMAGE_REDUCTION: return std::to_string( ability.percentage ) + _( "% damage from Elemental spells" ); case MonsterAbilityType::SPELL_CASTER: if ( ability.value == Spell::DISPEL ) { return std::to_string( ability.percentage ) + _( "% chance to Dispel beneficial spells" ); } else if ( ability.value == Spell::PARALYZE ) { return std::to_string( ability.percentage ) + _( "% chance to Paralyze" ); } else if ( ability.value == Spell::PETRIFY ) { return std::to_string( ability.percentage ) + _( "% chance to Petrify" ); } else if ( ability.value == Spell::BLIND ) { return std::to_string( ability.percentage ) + _( "% chance to Blind" ); } else if ( ability.value == Spell::CURSE ) { return std::to_string( ability.percentage ) + _( "% chance to Curse" ); } else { std::string str = _( "% chance to cast %{spell} spell" ); StringReplace( str, "%{spell}", Spell( ability.value ).GetName() ); return std::to_string( ability.percentage ) + str; } case MonsterAbilityType::HP_REGENERATION: return _( "HP regeneration" ); case MonsterAbilityType::TWO_CELL_MELEE_ATTACK: return _( "Two hexes attack" ); case MonsterAbilityType::FLYING: return ignoreBasicAbility ? "" : _( "Flyer" ); case MonsterAbilityType::ALWAYS_RETALIATE: return _( "Always retaliates" ); case MonsterAbilityType::ALL_ADJACENT_CELL_MELEE_ATTACK: return _( "Attacks all adjacent enemies" ); case MonsterAbilityType::NO_MELEE_PENALTY: return _( "No melee penalty" ); case MonsterAbilityType::DRAGON: return ignoreBasicAbility ? "" : _( "Dragon" ); case MonsterAbilityType::UNDEAD: return _( "Undead" ); case MonsterAbilityType::NO_ENEMY_RETALIATION: return _( "No enemy retaliation" ); case MonsterAbilityType::HP_DRAIN: return _( "HP drain" ); case MonsterAbilityType::AREA_SHOT: return _( "Cloud attack" ); case MonsterAbilityType::MORAL_DECREMENT: return _( "Decreases enemy's morale by " ) + std::to_string( ability.value ); case MonsterAbilityType::ENEMY_HALFING: return std::to_string( ability.percentage ) + _( "% chance to halve enemy" ); case MonsterAbilityType::SOUL_EATER: return _( "Soul Eater" ); case MonsterAbilityType::ELEMENTAL: return ignoreBasicAbility ? _( "No Morale" ) : _( "Elemental" ); default: break; } assert( 0 ); // Did you add a new ability? Please add the implementation! return ""; } std::string getMonsterWeaknessDescription( const MonsterWeakness & weakness, const bool ignoreBasicAbility ) { switch ( weakness.type ) { case MonsterWeaknessType::NONE: return ignoreBasicAbility ? "" : _( "None" ); case MonsterWeaknessType::EXTRA_DAMAGE_FROM_FIRE_SPELL: return _( "200% damage from Fire spells" ); case MonsterWeaknessType::EXTRA_DAMAGE_FROM_COLD_SPELL: return _( "200% damage from Cold spells" ); case MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL: { std::string str = _( "% damage from %{spell} spell" ); StringReplace( str, "%{spell}", Spell( weakness.value ).GetName() ); return std::to_string( weakness.percentage + 100 ) + str; } default: break; } assert( 0 ); // Did you add a new weakness? Please add the implementation! return ""; } std::string getMonsterDescription( const int monsterId ) { if ( monsterData.empty() ) { populateMonsterData(); } assert( monsterId >= 0 && static_cast( monsterId ) < monsterData.size() ); if ( monsterId < 0 || static_cast( monsterId ) >= monsterData.size() ) { return ""; } const MonsterData & monster = monsterData[monsterId]; std::ostringstream os; os << "----------" << std::endl; os << "Name: " << monster.generalStats.name << std::endl; os << "Plural name: " << monster.generalStats.pluralName << std::endl; os << "Base growth: " << monster.generalStats.baseGrowth << std::endl; os << "Race: " << Race::String( monster.generalStats.race ) << std::endl; os << "Level: " << monster.generalStats.level << std::endl; os << "Cost: " << Funds( monster.generalStats.cost ).String() << std::endl; os << std::endl; os << "Attack: " << monster.battleStats.attack << std::endl; os << "Defense: " << monster.battleStats.defense << std::endl; os << "Min damage: " << monster.battleStats.damageMin << std::endl; os << "Max damage: " << monster.battleStats.damageMax << std::endl; os << "Hit Points: " << monster.battleStats.hp << std::endl; os << "Speed: " << Speed::String( monster.battleStats.speed ) << std::endl; os << "Number of shots: " << monster.battleStats.shots << std::endl; if ( !monster.battleStats.abilities.empty() ) { os << std::endl; os << "Abilities:" << std::endl; for ( const MonsterAbility & ability : monster.battleStats.abilities ) { os << " " << getMonsterAbilityDescription( ability, false ) << std::endl; } } if ( !monster.battleStats.weaknesses.empty() ) { os << std::endl; os << "Weaknesses:" << std::endl; for ( const MonsterWeakness & weakness : monster.battleStats.weaknesses ) { os << " " << getMonsterWeaknessDescription( weakness, false ) << std::endl; } } return os.str(); } std::vector getMonsterPropertiesDescription( const int monsterId ) { std::vector output; const MonsterBattleStats & battleStats = getMonsterData( monsterId ).battleStats; const std::vector & abilities = battleStats.abilities; std::map> immuneToSpells; for ( const MonsterAbility & ability : abilities ) { if ( ability.type == MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL ) { immuneToSpells[ability.percentage].emplace_back( ability.value ); continue; } const std::string abilityDescription = getMonsterAbilityDescription( ability, true ); if ( !abilityDescription.empty() ) { output.emplace_back( abilityDescription + '.' ); } } for ( auto spellInfoIter = immuneToSpells.begin(); spellInfoIter != immuneToSpells.end(); ++spellInfoIter ) { assert( !spellInfoIter->second.empty() ); std::string temp; if ( spellInfoIter->first == 100 ) { temp += _( "Immune to " ); } else { temp += std::to_string( spellInfoIter->first ) + _( "% immunity to " ); } const std::vector sortedSpells = replaceMassSpells( spellInfoIter->second ); for ( size_t i = 0; i < sortedSpells.size(); ++i ) { if ( i > 0 ) { temp += ", "; } if ( sortedSpells[i] == Spell::LIGHTNINGBOLT ) { temp += _( "Lightning" ); } else { temp += Spell( sortedSpells[i] ).GetName(); } } temp += '.'; output.emplace_back( std::move( temp ) ); } std::map> extraDamageSpells; const std::vector & weaknesses = battleStats.weaknesses; for ( const MonsterWeakness & weakness : weaknesses ) { if ( weakness.type == MonsterWeaknessType::EXTRA_DAMAGE_FROM_CERTAIN_SPELL ) { extraDamageSpells[weakness.percentage].emplace_back( weakness.value ); continue; } const std::string weaknessDescription = getMonsterWeaknessDescription( weakness, true ); if ( !weaknessDescription.empty() ) { output.emplace_back( weaknessDescription + '.' ); } } for ( auto spellInfoIter = extraDamageSpells.begin(); spellInfoIter != extraDamageSpells.end(); ++spellInfoIter ) { assert( !spellInfoIter->second.empty() ); std::string temp; temp += std::to_string( spellInfoIter->first + 100 ) + _( "% damage from " ); const std::vector sortedSpells = replaceMassSpells( spellInfoIter->second ); for ( size_t i = 0; i < sortedSpells.size(); ++i ) { if ( i > 0 ) { temp += ", "; } if ( sortedSpells[i] == Spell::LIGHTNINGBOLT ) { temp += _( "Lightning" ); } else { temp += Spell( sortedSpells[i] ).GetName(); } } temp += '.'; output.emplace_back( std::move( temp ) ); } return output; } uint32_t getSpellResistance( const int monsterId, const int spellId ) { const std::vector & abilities = getMonsterData( monsterId ).battleStats.abilities; Spell spell( spellId ); std::vector::const_iterator foundAbility; if ( spell.isMindInfluence() ) { foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::MIND_SPELL_IMMUNITY ) ); if ( foundAbility != abilities.end() ) { return 100; } foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::UNDEAD ) ); if ( foundAbility != abilities.end() ) { return 100; } foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::ELEMENTAL ) ); if ( foundAbility != abilities.end() ) { return 100; } } if ( spell.isAliveOnly() ) { foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::UNDEAD ) ); if ( foundAbility != abilities.end() ) { return 100; } } if ( spell.isUndeadOnly() ) { foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::UNDEAD ) ); if ( foundAbility == abilities.end() ) { return 100; } } if ( spell.isCold() ) { foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::COLD_SPELL_IMMUNITY ) ); if ( foundAbility != abilities.end() ) { return 100; } } if ( spell.isFire() ) { foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::FIRE_SPELL_IMMUNITY ) ); if ( foundAbility != abilities.end() ) { return 100; } } if ( spell == Spell::COLDRAY || spell == Spell::COLDRING || spell == Spell::FIREBALL || spell == Spell::FIREBLAST || spell == Spell::LIGHTNINGBOLT || spell == Spell::CHAINLIGHTNING || spell == Spell::ELEMENTALSTORM ) { foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::ELEMENTAL_SPELL_IMMUNITY ) ); if ( foundAbility != abilities.end() ) { return 100; } } uint32_t spellResistance = 0; // Find magic immunity for every spell. foundAbility = std::find( abilities.begin(), abilities.end(), MonsterAbility( MonsterAbilityType::MAGIC_RESISTANCE ) ); if ( foundAbility != abilities.end() ) { if ( foundAbility->percentage == 100 ) { // Immune to everything. return 100; } if ( spell.isDamage() || spell.isApplyToEnemies() ) { spellResistance = foundAbility->percentage; } } for ( const MonsterAbility & ability : abilities ) { if ( ability.type == MonsterAbilityType::IMMUNE_TO_CERTAIN_SPELL && static_cast( ability.value ) == spellId ) { spellResistance = std::max( spellResistance, ability.percentage ); } } return spellResistance; } } fheroes2-1.0.12+dfsg/src/fheroes2/monster/monster_info.h000066400000000000000000000135051456075706000231130ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2MONSTER_INFO_H #define H2MONSTER_INFO_H #include #include #include #include "resource.h" namespace fheroes2 { enum class MonsterAbilityType : int { // Basic abilities. NONE, DOUBLE_HEX_SIZE, FLYING, DRAGON, UNDEAD, ELEMENTAL, // Advanced abilities. DOUBLE_SHOOTING, DOUBLE_MELEE_ATTACK, DOUBLE_DAMAGE_TO_UNDEAD, MAGIC_RESISTANCE, MIND_SPELL_IMMUNITY, ELEMENTAL_SPELL_IMMUNITY, FIRE_SPELL_IMMUNITY, COLD_SPELL_IMMUNITY, IMMUNE_TO_CERTAIN_SPELL, ELEMENTAL_SPELL_DAMAGE_REDUCTION, SPELL_CASTER, HP_REGENERATION, TWO_CELL_MELEE_ATTACK, ALWAYS_RETALIATE, ALL_ADJACENT_CELL_MELEE_ATTACK, NO_MELEE_PENALTY, NO_ENEMY_RETALIATION, HP_DRAIN, AREA_SHOT, MORAL_DECREMENT, ENEMY_HALFING, SOUL_EATER }; enum class MonsterWeaknessType : int { // Basic abilities. NONE, // Advanced abilities. EXTRA_DAMAGE_FROM_FIRE_SPELL, EXTRA_DAMAGE_FROM_COLD_SPELL, EXTRA_DAMAGE_FROM_CERTAIN_SPELL }; struct MonsterAbility { explicit MonsterAbility( const MonsterAbilityType type_ ) : type( type_ ) , percentage( 0 ) , value( 0 ) {} MonsterAbility( const MonsterAbilityType type_, const uint32_t percentage_, const uint32_t value_ ) : type( type_ ) , percentage( percentage_ ) , value( value_ ) {} bool operator==( const MonsterAbility & another ) const { return type == another.type; } MonsterAbilityType type; uint32_t percentage; uint32_t value; }; struct MonsterWeakness { explicit MonsterWeakness( const MonsterWeaknessType type_ ) : type( type_ ) , percentage( 0 ) , value( 0 ) {} explicit MonsterWeakness( const MonsterWeaknessType type_, const uint32_t percentage_, const uint32_t value_ ) : type( type_ ) , percentage( percentage_ ) , value( value_ ) {} bool operator<( const MonsterWeakness & another ) const { return type < another.type || ( type == another.type && value < another.value ); } MonsterWeaknessType type; uint32_t percentage; uint32_t value; }; struct MonsterBattleStats { uint32_t attack; uint32_t defense; uint32_t damageMin; uint32_t damageMax; uint32_t hp; uint32_t speed; uint32_t shots; double monsterBaseStrength; std::vector abilities; std::vector weaknesses; }; struct MonsterGeneralStats { const char * name; const char * pluralName; uint32_t baseGrowth; uint32_t race; uint32_t level; cost_t cost; }; struct MonsterSound { int meleeAttack; int death; int movement; int wince; int rangeAttack; int takeoff; int landing; int explosion; }; struct MonsterData { MonsterData( const int icnId_, const char * binFileName_, const MonsterSound & sounds_, const MonsterBattleStats & battleStats_, const MonsterGeneralStats & generalStats_ ) : icnId( icnId_ ) , binFileName( binFileName_ ) , sounds( sounds_ ) , battleStats( battleStats_ ) , generalStats( generalStats_ ) {} int icnId; const char * binFileName; MonsterSound sounds; MonsterBattleStats battleStats; MonsterGeneralStats generalStats; }; const MonsterData & getMonsterData( const int monsterId ); std::string getMonsterAbilityDescription( const MonsterAbility & ability, const bool ignoreBasicAbility ); std::string getMonsterWeaknessDescription( const MonsterWeakness & weakness, const bool ignoreBasicAbility ); std::string getMonsterDescription( const int monsterId ); // To be utilized in future. std::vector getMonsterPropertiesDescription( const int monsterId ); uint32_t getSpellResistance( const int monsterId, const int spellId ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/objects/000077500000000000000000000000001456075706000201765ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/fheroes2/objects/mounts.cpp000066400000000000000000000047061456075706000222360ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "mounts.h" #include #include #include #include "tools.h" namespace { const std::bitset<256> objMnts1ShadowBitset = fheroes2::makeBitsetFromVector<256>( { 0, 5, 11, 17, 21, 26, 32, 38, 42, 45, 49, 52, 55, 59, 62, 65, 68, 71, 74, 75, 79, 80 } ); const std::bitset<256> objMnts2ShadowBitset = fheroes2::makeBitsetFromVector<256>( { 0, 5, 11, 17, 21, 26, 32, 38, 42, 46, 47, 53, 57, 58, 62, 68, 72, 75, 79, 82, 85, 89, 92, 95, 98, 101, 104, 105, 109, 110 } ); } /* Mnts1: MTNDSRT, MTNGRAS, MTNLAVA, MTNMULT, MTNSNOW, MTNSWMP Mnts2: MTNCRCK, MTNDIRT */ bool ObjMnts1::isShadow( const uint8_t index ) { return objMnts1ShadowBitset[index]; } bool ObjMnts2::isShadow( const uint8_t index ) { return objMnts2ShadowBitset[index]; } fheroes2-1.0.12+dfsg/src/fheroes2/objects/mounts.h000066400000000000000000000035461456075706000217040ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2MOUNTS_H #define H2MOUNTS_H #include namespace ObjMnts1 { bool isShadow( const uint8_t index ); } namespace ObjMnts2 { bool isShadow( const uint8_t index ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/objects/objcrck.cpp000066400000000000000000000040521456075706000223200ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "objcrck.h" #include #include #include #include "tools.h" namespace { const std::bitset<256> objCrckShadowBitset = fheroes2::makeBitsetFromVector<256>( { 2, 9, 13, 15, 20, 23, 28, 33, 36, 39, 45, 48, 51, 54, 56, 73, 75, 79, 200, 201, 207, 237 } ); } bool ObjCrck::isShadow( const uint8_t index ) { return objCrckShadowBitset[index]; } fheroes2-1.0.12+dfsg/src/fheroes2/objects/objcrck.h000066400000000000000000000034451456075706000217720ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2OBJCRCK_H #define H2OBJCRCK_H #include namespace ObjCrck { bool isShadow( const uint8_t index ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/objects/objdirt.cpp000066400000000000000000000043311456075706000223400ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "objdirt.h" #include #include #include #include "tools.h" namespace { const std::bitset<256> objDirtShadowBitset = fheroes2::makeBitsetFromVector<256>( { 0, 1, 5, 6, 14, 47, 52, 59, 62, 65, 68, 70, 72, 75, 78, 81, 84, 87, 91, 94, 97, 100, 103, 111, 114, 117, 126, 128, 136, 149, 150, 158, 161, 162, 163, 164, 165, 166, 167, 168, 177, 178, 179, 180, 181, 182, 183, 184, 193, 196, 200 } ); } bool ObjDirt::isShadow( const uint8_t index ) { return objDirtShadowBitset[index]; } fheroes2-1.0.12+dfsg/src/fheroes2/objects/objdirt.h000066400000000000000000000034451456075706000220120ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2OBJDIRT_H #define H2OBJDIRT_H #include namespace ObjDirt { bool isShadow( const uint8_t index ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/objects/objdsrt.cpp000066400000000000000000000041231456075706000223510ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "objdsrt.h" #include #include #include #include "tools.h" namespace { const std::bitset<256> objDsrtShadowBitset = fheroes2::makeBitsetFromVector<256>( { 11, 13, 16, 19, 23, 25, 27, 29, 33, 35, 38, 41, 44, 46, 47, 50, 52, 54, 55, 56, 57, 58, 59, 60, 71, 75, 77, 80, 86, 103, 115, 118 } ); } bool ObjDsrt::isShadow( const uint8_t index ) { return objDsrtShadowBitset[index]; } fheroes2-1.0.12+dfsg/src/fheroes2/objects/objdsrt.h000066400000000000000000000034451456075706000220240ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2OBJDSRT_H #define H2OBJDSRT_H #include namespace ObjDsrt { bool isShadow( const uint8_t index ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/objects/objgras.cpp000066400000000000000000000050321456075706000223310ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "objgras.h" #include #include #include #include "tools.h" namespace { const std::bitset<256> objGrasShadowBitset = fheroes2::makeBitsetFromVector<256>( { 0, 4, 29, 32, 36, 39, 42, 44, 46, 48, 50, 76, 79, 82, 88, 92, 94, 98, 102, 105, 108, 111, 113, 120, 124, 128, 134, 138, 141, 143, 145, 147 } ); const std::bitset<256> objGra2ShadowBitset = fheroes2::makeBitsetFromVector<256>( { 5, 14, 19, 20, 28, 31, 32, 33, 34, 35, 36, 37, 38, 47, 48, 49, 50, 51, 52, 53, 54, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 91, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 121, 124, 128 } ); } bool ObjGras::isShadow( const uint8_t index ) { return objGrasShadowBitset[index]; } bool ObjGra2::isShadow( const uint8_t index ) { return objGra2ShadowBitset[index]; } fheroes2-1.0.12+dfsg/src/fheroes2/objects/objgras.h000066400000000000000000000035461456075706000220060ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2OBJGRAS_H #define H2OBJGRAS_H #include namespace ObjGras { bool isShadow( const uint8_t index ); } namespace ObjGra2 { bool isShadow( const uint8_t index ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/objects/objlava.cpp000066400000000000000000000064401456075706000223240ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "objlava.h" #include #include #include #include "tools.h" namespace { const std::bitset<256> objLavaShadowBitset = fheroes2::makeBitsetFromVector<256>( { 10, 11, 45, 49, 79, 80, 81, 82, 109, 113, 116 } ); const std::bitset<256> objLav2ShadowBitset = fheroes2::makeBitsetFromVector<256>( { 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 29, 34, 38, 39, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 72, 77, 78 } ); const std::bitset<256> objLav3ShadowBitset = fheroes2::makeBitsetFromVector<256>( { 1, 2, 3, 4, 16, 17, 18, 19, 31, 32, 33, 34, 38, 46, 47, 48, 49, 50, 57, 58, 59, 61, 62, 63, 64, 76, 77, 91, 92, 93, 106, 107, 108, 109, 110, 111, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 136, 137, 138, 139, 142, 143, 144, 145, 146, 147, 148, 149, 166, 167, 168, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 243 } ); } bool ObjLav2::isShadow( const uint8_t index ) { return objLav2ShadowBitset[index]; } bool ObjLav3::isShadow( const uint8_t index ) { return objLav3ShadowBitset[index]; } bool ObjLava::isShadow( const uint8_t index ) { return objLavaShadowBitset[index]; } fheroes2-1.0.12+dfsg/src/fheroes2/objects/objlava.h000066400000000000000000000036471456075706000217770ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2OBJLAVA_H #define H2OBJLAVA_H #include namespace ObjLav2 { bool isShadow( const uint8_t index ); } namespace ObjLav3 { bool isShadow( const uint8_t index ); } namespace ObjLava { bool isShadow( const uint8_t index ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/objects/objmult.cpp000066400000000000000000000053551456075706000223660ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "objmult.h" #include #include #include #include "tools.h" namespace { const std::bitset<256> objMultShadowBitset = fheroes2::makeBitsetFromVector<256>( { 1, 3, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 57, 61, 67, 68, 75, 77, 79, 81, 83, 97, 98, 99, 100, 101, 102, 103, 105, 106, 107, 108, 109, 110, 113, 115, 121, 122, 124, 125, 126, 127, 128, 129, 130 } ); const std::bitset<256> objMul2ShadowBitset = fheroes2::makeBitsetFromVector<256>( { 14, 17, 20, 24, 34, 36, 42, 43, 49, 50, 60, 71, 72, 113, 115, 118, 121, 123, 127, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 164, 180, 181, 182, 183, 184, 185, 186, 189, 199, 200, 202, 206 } ); } bool ObjMult::isShadow( const uint8_t index ) { return objMultShadowBitset[index]; } bool ObjMul2::isShadow( const uint8_t index ) { return objMul2ShadowBitset[index]; } fheroes2-1.0.12+dfsg/src/fheroes2/objects/objmult.h000066400000000000000000000035461456075706000220330ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2OBJMULT_H #define H2OBJMULT_H #include namespace ObjMult { bool isShadow( const uint8_t index ); } namespace ObjMul2 { bool isShadow( const uint8_t index ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/objects/objsnow.cpp000066400000000000000000000043161456075706000223670ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "objsnow.h" #include #include #include #include "tools.h" namespace { const std::bitset<256> objSnowShadowBitset = fheroes2::makeBitsetFromVector<256>( { 21, 25, 29, 31, 33, 36, 40, 48, 54, 59, 63, 67, 70, 73, 76, 79, 101, 104, 105, 106, 107, 108, 109, 110, 111, 120, 121, 122, 123, 124, 125, 126, 127, 137, 140, 142, 144, 148, 193, 203, 207 } ); } bool ObjSnow::isShadow( const uint8_t index ) { return objSnowShadowBitset[index]; } fheroes2-1.0.12+dfsg/src/fheroes2/objects/objsnow.h000066400000000000000000000034451456075706000220360ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2OBJSNOW_H #define H2OBJSNOW_H #include namespace ObjSnow { bool isShadow( const uint8_t index ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/objects/objswmp.cpp000066400000000000000000000042661456075706000223730ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "objswmp.h" #include #include #include #include "tools.h" namespace { const std::bitset<256> objSwmpShadowBitset = fheroes2::makeBitsetFromVector<256>( { 2, 3, 14, 15, 16, 17, 18, 19, 20, 21, 31, 43, 44, 45, 46, 47, 48, 49, 66, 83, 125, 127, 130, 132, 136, 141, 163, 170, 175, 178, 195, 197, 202, 204, 207, 211, 215 } ); } bool ObjSwmp::isShadow( const uint8_t index ) { return objSwmpShadowBitset[index]; } fheroes2-1.0.12+dfsg/src/fheroes2/objects/objswmp.h000066400000000000000000000034451456075706000220360ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2OBJSWMP_H #define H2OBJSWMP_H #include namespace ObjSwmp { bool isShadow( const uint8_t index ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/objects/objtown.cpp000066400000000000000000000037461456075706000223760ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "objtown.h" #include #include #include "tools.h" namespace { const std::bitset<256> obTownShadowBitset = fheroes2::makeBitsetFromVector<256>( { 0, 16, 17, 48, 80, 81, 112, 144, 145, 161, 165, 176 } ); } bool ObjTown::isShadow( const uint8_t index ) { return obTownShadowBitset[index]; } fheroes2-1.0.12+dfsg/src/fheroes2/objects/objtown.h000066400000000000000000000034451456075706000220370ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2OBJTOWN_H #define H2OBJTOWN_H #include namespace ObjTown { bool isShadow( const uint8_t index ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/objects/objwatr.cpp000066400000000000000000000047241456075706000223610ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "objwatr.h" #include #include #include #include "tools.h" namespace { const std::bitset<256> objWatrShadowBitset = fheroes2::makeBitsetFromVector<256>( { 12, 13, 14, 15, 16, 17, 18, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 52, 55, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 184, 188, 189, 190, 191, 192, 193, 194, 240 } ); } bool ObjWatr::isShadow( const uint8_t index ) { return objWatrShadowBitset[index]; } bool ObjWat2::isShadow( const uint8_t index ) { return index == 1; } fheroes2-1.0.12+dfsg/src/fheroes2/objects/objwatr.h000066400000000000000000000035461456075706000220270ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2OBJWATR_H #define H2OBJWATR_H #include namespace ObjWatr { bool isShadow( const uint8_t index ); } namespace ObjWat2 { bool isShadow( const uint8_t index ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/objects/objxloc.cpp000066400000000000000000000056371456075706000223550ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "objxloc.h" #include #include #include #include "tools.h" namespace { const std::bitset<256> objXlc1ShadowBitset = fheroes2::makeBitsetFromVector<256>( { 1, 2, 32, 33, 34, 35, 36, 37, 38, 39, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 72, 78, 79, 83, 84, 112, 116, 120, 124, 125, 129, 133 } ); const std::bitset<256> objXlc2ShadowBitset = fheroes2::makeBitsetFromVector<256>( { 2, 7, 10, 11, 12, 13, 14, 15, 16, 17, 18, 47, 48, 49, 50, 51, 52, 53, 54, 55, 83, 84, 85, 86, 87, 88, 89, 90, 91 } ); const std::bitset<256> objXlc3ShadowBitset = fheroes2::makeBitsetFromVector<256>( { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 41, 42, 43, 44, 45, 46, 47, 48, 49, 59, 65, 71, 77, 83, 89, 95, 101, 108, 109, 112, 113, 116, 117, 120, 121, 124, 125, 128, 129, 132, 133, 136, 137 } ); } bool ObjXlc1::isShadow( const uint8_t index ) { return objXlc1ShadowBitset[index]; } bool ObjXlc2::isShadow( const uint8_t index ) { return objXlc2ShadowBitset[index]; } bool ObjXlc3::isShadow( const uint8_t index ) { return objXlc3ShadowBitset[index]; } bool ObjXlc2::isReefs( const uint8_t index ) { return index >= 111 && index <= 135; } fheroes2-1.0.12+dfsg/src/fheroes2/objects/objxloc.h000066400000000000000000000040271456075706000220120ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2OBJXLOC_H #define H2OBJXLOC_H #include namespace ObjXlc1 { bool isShadow( const uint8_t index ); } namespace ObjXlc2 { bool isShadow( const uint8_t index ); // Returns true if the index belongs to Reefs type of the object. bool isReefs( const uint8_t index ); } namespace ObjXlc3 { bool isShadow( const uint8_t index ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/objects/trees.cpp000066400000000000000000000037361456075706000220350ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "trees.h" #include #include #include "tools.h" namespace { const std::bitset<256> objTreeShadowBitset = fheroes2::makeBitsetFromVector<256>( { 0, 3, 7, 10, 13, 17, 20, 23, 26, 29, 32, 34 } ); } bool ObjTree::isShadow( const uint8_t index ) { return objTreeShadowBitset[index]; } fheroes2-1.0.12+dfsg/src/fheroes2/objects/trees.h000066400000000000000000000034411456075706000214730ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2TREES_H #define H2TREES_H #include namespace ObjTree { bool isShadow( const uint8_t index ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/resource/000077500000000000000000000000001456075706000203745ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/fheroes2/resource/artifact.cpp000066400000000000000000001317331456075706000227050ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "artifact.h" #include #include #include #include #include #include #include #include #include #include #include "agg_image.h" #include "dialog.h" #include "dialog_selectitems.h" #include "gamedefs.h" #include "heroes.h" #include "icn.h" #include "logging.h" #include "maps_fileinfo.h" #include "rand.h" #include "serialize.h" #include "settings.h" #include "skill.h" #include "spell.h" #include "spell_book.h" #include "spell_storage.h" #include "statusbar.h" #include "tools.h" #include "translations.h" #include "ui_dialog.h" namespace { const std::map> artifactSets = { { ArtifactSetData( Artifact::BATTLE_GARB, gettext_noop( "The three Anduran artifacts magically combine into one." ) ), { Artifact::HELMET_ANDURAN, Artifact::SWORD_ANDURAN, Artifact::BREASTPLATE_ANDURAN } } }; std::array artifactGlobalStatus = { 0 }; enum { ART_RNDDISABLED = 0x01, ART_RNDUSED = 0x02 }; void transferArtifactsByCondition( std::vector & artifacts, BagArtifacts & artifactBag, const std::function & condition ) { for ( auto iter = artifacts.begin(); iter != artifacts.end(); ) { if ( condition( *iter ) ) { if ( !artifactBag.PushArtifact( *iter ) ) { // The bag is full so no need to proceed further. return; } iter = artifacts.erase( iter ); continue; } ++iter; } } } const char * Artifact::GetName() const { return _( fheroes2::getArtifactData( id ).name ); } bool Artifact::isUltimate() const { switch ( id ) { case ULTIMATE_BOOK: case ULTIMATE_SWORD: case ULTIMATE_CLOAK: case ULTIMATE_WAND: case ULTIMATE_SHIELD: case ULTIMATE_STAFF: case ULTIMATE_CROWN: case GOLDEN_GOOSE: return true; default: break; } return false; } int Artifact::LoyaltyLevel() const { switch ( id ) { case MASTHEAD: case SPADE_NECROMANCY: case HEART_FIRE: case HEART_ICE: return ART_LEVEL_MINOR; case ARM_MARTYR: case HOLY_HAMMER: case LEGENDARY_SCEPTER: case STAFF_WIZARDRY: case SWORD_BREAKER: case CRYSTAL_BALL: return ART_LEVEL_MAJOR; case SPELL_SCROLL: case BROACH_SHIELDING: case SWORD_ANDURAN: case BREASTPLATE_ANDURAN: case BATTLE_GARB: case HELMET_ANDURAN: case SPHERE_NEGATION: return ART_NORANDOM; default: break; } return ART_NONE; } int Artifact::Level() const { if ( isUltimate() ) { return ART_ULTIMATE; } switch ( id ) { case MEDAL_VALOR: case MEDAL_COURAGE: case MEDAL_HONOR: case MEDAL_DISTINCTION: case THUNDER_MACE: case ARMORED_GAUNTLETS: case DEFENDER_HELM: case GIANT_FLAIL: case RABBIT_FOOT: case GOLDEN_HORSESHOE: case GAMBLER_LUCKY_COIN: case FOUR_LEAF_CLOVER: case ENCHANTED_HOURGLASS: case ICE_CLOAK: case FIRE_CLOAK: case LIGHTNING_HELM: case SNAKE_RING: case HOLY_PENDANT: case PENDANT_FREE_WILL: case PENDANT_LIFE: case SERENITY_PENDANT: case SEEING_EYE_PENDANT: case KINETIC_PENDANT: case PENDANT_DEATH: case GOLDEN_BOW: case TELESCOPE: case STATESMAN_QUILL: return ART_LEVEL_TREASURE; case CASTER_BRACELET: case MAGE_RING: case STEALTH_SHIELD: case POWER_AXE: case MINOR_SCROLL: case ENDLESS_PURSE_GOLD: case SAILORS_ASTROLABE_MOBILITY: case EVIL_EYE: case GOLD_WATCH: case SKULLCAP: case EVERCOLD_ICICLE: case EVERHOT_LAVA_ROCK: case LIGHTNING_ROD: case ANKH: case BOOK_ELEMENTS: case ELEMENTAL_RING: case POWER_RING: case AMMO_CART: case ENDLESS_CORD_WOOD: case ENDLESS_CART_ORE: case SPIKED_HELM: case WHITE_PEARL: return ART_LEVEL_MINOR; case ARCANE_NECKLACE: case WITCHES_BROACH: case BALLISTA: case DRAGON_SWORD: case DIVINE_BREASTPLATE: case MAJOR_SCROLL: case SUPERIOR_SCROLL: case FOREMOST_SCROLL: case ENDLESS_SACK_GOLD: case ENDLESS_BAG_GOLD: case NOMAD_BOOTS_MOBILITY: case TRAVELER_BOOTS_MOBILITY: case TRUE_COMPASS_MOBILITY: case WAND_NEGATION: case WIZARD_HAT: case ENDLESS_POUCH_SULFUR: case ENDLESS_VIAL_MERCURY: case ENDLESS_POUCH_GEMS: case ENDLESS_POUCH_CRYSTAL: case SPIKED_SHIELD: case BLACK_PEARL: return ART_LEVEL_MAJOR; // no random case MAGIC_BOOK: case FIZBIN_MISFORTUNE: case TAX_LIEN: case HIDEOUS_MASK: return ART_NORANDOM; // price loyalty case SPELL_SCROLL: case ARM_MARTYR: case BREASTPLATE_ANDURAN: case BROACH_SHIELDING: case BATTLE_GARB: case CRYSTAL_BALL: case HELMET_ANDURAN: case HOLY_HAMMER: case LEGENDARY_SCEPTER: case MASTHEAD: case SPHERE_NEGATION: case STAFF_WIZARDRY: case SWORD_BREAKER: case SWORD_ANDURAN: case SPADE_NECROMANCY: case HEART_FIRE: case HEART_ICE: { const GameVersion version = Settings::Get().getCurrentMapInfo().version; return ( version == GameVersion::PRICE_OF_LOYALTY || version == GameVersion::RESURRECTION ) ? ART_LOYALTY | LoyaltyLevel() : ART_LOYALTY; } default: break; } return ART_NONE; } double Artifact::getArtifactValue() const { double artifactValue = 0; const fheroes2::ArtifactData & data = fheroes2::getArtifactData( id ); const std::vector & bonuses = data.bonuses; const std::vector & curses = data.curses; for ( const fheroes2::ArtifactBonus & bonus : bonuses ) { switch ( bonus.type ) { case fheroes2::ArtifactBonusType::GOLD_INCOME: artifactValue += static_cast( bonus.value ) / 800.0; break; case fheroes2::ArtifactBonusType::SEA_MOBILITY: artifactValue += static_cast( bonus.value ) / 500.0; break; case fheroes2::ArtifactBonusType::LAND_MOBILITY: artifactValue += static_cast( bonus.value ) / 200.0; break; case fheroes2::ArtifactBonusType::CURSE_SPELL_COST_REDUCTION_PERCENT: case fheroes2::ArtifactBonusType::BLESS_SPELL_COST_REDUCTION_PERCENT: case fheroes2::ArtifactBonusType::SUMMONING_SPELL_COST_REDUCTION_PERCENT: case fheroes2::ArtifactBonusType::MIND_INFLUENCE_SPELL_COST_REDUCTION_PERCENT: case fheroes2::ArtifactBonusType::COLD_SPELL_DAMAGE_REDUCTION_PERCENT: case fheroes2::ArtifactBonusType::FIRE_SPELL_DAMAGE_REDUCTION_PERCENT: case fheroes2::ArtifactBonusType::LIGHTNING_SPELL_DAMAGE_REDUCTION_PERCENT: case fheroes2::ArtifactBonusType::ELEMENTAL_SPELL_DAMAGE_REDUCTION_PERCENT: case fheroes2::ArtifactBonusType::HYPNOTIZE_SPELL_EXTRA_EFFECTIVENESS_PERCENT: case fheroes2::ArtifactBonusType::COLD_SPELL_EXTRA_EFFECTIVENESS_PERCENT: case fheroes2::ArtifactBonusType::FIRE_SPELL_EXTRA_EFFECTIVENESS_PERCENT: case fheroes2::ArtifactBonusType::LIGHTNING_SPELL_EXTRA_EFFECTIVENESS_PERCENT: case fheroes2::ArtifactBonusType::RESURRECT_SPELL_EXTRA_EFFECTIVENESS_PERCENT: case fheroes2::ArtifactBonusType::SUMMONING_SPELL_EXTRA_EFFECTIVENESS_PERCENT: artifactValue += static_cast( bonus.value ) / 50.0; break; case fheroes2::ArtifactBonusType::NECROMANCY_SKILL: case fheroes2::ArtifactBonusType::SURRENDER_COST_REDUCTION_PERCENT: artifactValue += static_cast( bonus.value ) / 10.0; break; case fheroes2::ArtifactBonusType::EVERY_COMBAT_SPELL_DURATION: case fheroes2::ArtifactBonusType::SPELL_POINTS_DAILY_GENERATION: artifactValue += static_cast( bonus.value ) / 2.0; break; case fheroes2::ArtifactBonusType::CURSE_SPELL_IMMUNITY: case fheroes2::ArtifactBonusType::HYPNOTIZE_SPELL_IMMUNITY: case fheroes2::ArtifactBonusType::DEATH_SPELL_IMMUNITY: case fheroes2::ArtifactBonusType::BERSERK_SPELL_IMMUNITY: case fheroes2::ArtifactBonusType::BLIND_SPELL_IMMUNITY: case fheroes2::ArtifactBonusType::PARALYZE_SPELL_IMMUNITY: case fheroes2::ArtifactBonusType::HOLY_SPELL_IMMUNITY: case fheroes2::ArtifactBonusType::DISPEL_SPELL_IMMUNITY: case fheroes2::ArtifactBonusType::ENDLESS_AMMUNITION: case fheroes2::ArtifactBonusType::NO_SHOOTING_PENALTY: case fheroes2::ArtifactBonusType::VIEW_MONSTER_INFORMATION: case fheroes2::ArtifactBonusType::ADD_SPELL: case fheroes2::ArtifactBonusType::EXTRA_CATAPULT_SHOTS: artifactValue += 1; break; case fheroes2::ArtifactBonusType::MAXIMUM_MORALE: case fheroes2::ArtifactBonusType::DISABLE_ALL_SPELL_COMBAT_CASTING: case fheroes2::ArtifactBonusType::MAXIMUM_LUCK: artifactValue += 3; break; case fheroes2::ArtifactBonusType::KNOWLEDGE_SKILL: case fheroes2::ArtifactBonusType::ATTACK_SKILL: case fheroes2::ArtifactBonusType::DEFENCE_SKILL: case fheroes2::ArtifactBonusType::SPELL_POWER_SKILL: case fheroes2::ArtifactBonusType::MORALE: case fheroes2::ArtifactBonusType::LUCK: case fheroes2::ArtifactBonusType::AREA_REVEAL_DISTANCE: case fheroes2::ArtifactBonusType::CRYSTAL_INCOME: case fheroes2::ArtifactBonusType::MERCURY_INCOME: case fheroes2::ArtifactBonusType::ORE_INCOME: case fheroes2::ArtifactBonusType::GEMS_INCOME: case fheroes2::ArtifactBonusType::WOOD_INCOME: case fheroes2::ArtifactBonusType::SULFUR_INCOME: case fheroes2::ArtifactBonusType::SEA_BATTLE_LUCK_BOOST: case fheroes2::ArtifactBonusType::SEA_BATTLE_MORALE_BOOST: artifactValue += bonus.value; break; case fheroes2::ArtifactBonusType::NONE: break; default: // Did you add a new artifact bonus? Add your logic here. assert( 0 ); break; } } for ( const fheroes2::ArtifactCurse & curse : curses ) { switch ( curse.type ) { case fheroes2::ArtifactCurseType::GOLD_PENALTY: artifactValue -= static_cast( curse.value ) / 200.0; break; case fheroes2::ArtifactCurseType::COLD_SPELL_EXTRA_DAMAGE_PERCENT: case fheroes2::ArtifactCurseType::FIRE_SPELL_EXTRA_DAMAGE_PERCENT: artifactValue -= static_cast( curse.value ) / 100.0; break; case fheroes2::ArtifactCurseType::NO_JOINING_ARMIES: case fheroes2::ArtifactCurseType::UNDEAD_MORALE_PENALTY: artifactValue -= 1; break; case fheroes2::ArtifactCurseType::MORALE: case fheroes2::ArtifactCurseType::SPELL_POWER_SKILL: artifactValue -= curse.value; break; default: // Did you add a new artifact curse? Add your logic here. assert( 0 ); break; } } return artifactValue; } void Artifact::SetSpell( const int v ) { if ( id != SPELL_SCROLL ) { // This method must be called only for Spell Scroll artifact. assert( 0 ); return; } const bool adv = Rand::Get( 1 ) != 0; switch ( v ) { case Spell::RANDOM: ext = Spell::Rand( Rand::Get( 1, 5 ), adv ).GetID(); break; case Spell::RANDOM1: ext = Spell::Rand( 1, adv ).GetID(); break; case Spell::RANDOM2: ext = Spell::Rand( 2, adv ).GetID(); break; case Spell::RANDOM3: ext = Spell::Rand( 3, adv ).GetID(); break; case Spell::RANDOM4: ext = Spell::Rand( 4, adv ).GetID(); break; case Spell::RANDOM5: ext = Spell::Rand( 5, adv ).GetID(); break; default: ext = v; break; } } int32_t Artifact::getSpellId() const { const std::vector & bonuses = fheroes2::getArtifactData( id ).bonuses; for ( const fheroes2::ArtifactBonus & bonus : bonuses ) { if ( bonus.type == fheroes2::ArtifactBonusType::ADD_SPELL ) { int32_t spellId = bonus.value; if ( spellId == Spell::NONE ) { spellId = ext; } assert( spellId > Spell::NONE && spellId < Spell::SPELL_COUNT ); return spellId; } } return Spell::NONE; } int Artifact::Rand( level_t lvl ) { std::vector v; v.reserve( 25 ); // if possibly: make unique on map for ( int art = UNKNOWN + 1; art < ARTIFACT_COUNT; ++art ) { const Artifact artifact{ art }; if ( artifact.isValid() && ( lvl & artifact.Level() ) && !( artifactGlobalStatus[art] & ART_RNDDISABLED ) && !( artifactGlobalStatus[art] & ART_RNDUSED ) ) { v.push_back( art ); } } if ( v.empty() ) { for ( int art = UNKNOWN + 1; art < ARTIFACT_COUNT; ++art ) { const Artifact artifact{ art }; if ( artifact.isValid() && ( lvl & artifact.Level() ) && !( artifactGlobalStatus[art] & ART_RNDDISABLED ) ) { v.push_back( art ); } } } int res = !v.empty() ? Rand::Get( v ) : Artifact::UNKNOWN; artifactGlobalStatus[res] |= ART_RNDUSED; return res; } Artifact Artifact::FromMP2IndexSprite( uint32_t index ) { // Add 1 to all values to properly convert from the old map format. if ( 0xA2 > index ) return { static_cast( index - 1 ) / 2 + 1 }; if ( Settings::Get().isPriceOfLoyaltySupported() && 0xAB < index && 0xCE > index ) return { static_cast( index - 1 ) / 2 + 1 }; if ( 0xA3 == index ) return { Rand( ART_LEVEL_ALL_NORMAL ) }; if ( 0xA4 == index ) return { Rand( ART_ULTIMATE ) }; if ( 0xA7 == index ) return { Rand( ART_LEVEL_TREASURE ) }; if ( 0xA9 == index ) return { Rand( ART_LEVEL_MINOR ) }; if ( 0xAB == index ) return { ART_LEVEL_MAJOR }; DEBUG_LOG( DBG_GAME, DBG_WARN, "Unknown Artifact object index: " << index ) return { UNKNOWN }; } const char * Artifact::getDiscoveryDescription( const Artifact & art ) { return _( fheroes2::getArtifactData( art.GetID() ).discoveryEventDescription ); } StreamBase & operator<<( StreamBase & msg, const Artifact & art ) { return msg << art.id << art.ext; } StreamBase & operator>>( StreamBase & msg, Artifact & art ) { return msg >> art.id >> art.ext; } BagArtifacts::BagArtifacts() : std::vector( HEROESMAXARTIFACT, Artifact::UNKNOWN ) {} bool BagArtifacts::ContainSpell( const int spellId ) const { assert( spellId > Spell::NONE && spellId < Spell::SPELL_COUNT ); for ( const Artifact & artifact : *this ) { if ( artifact.getSpellId() == spellId ) { return true; } } return false; } bool BagArtifacts::isPresentArtifact( const Artifact & art ) const { return end() != std::find( begin(), end(), art ); } bool BagArtifacts::isArtifactBonusPresent( const fheroes2::ArtifactBonusType type ) const { for ( const Artifact & artifact : *this ) { const std::vector & bonuses = fheroes2::getArtifactData( artifact.GetID() ).bonuses; if ( std::find( bonuses.begin(), bonuses.end(), fheroes2::ArtifactBonus( type ) ) != bonuses.end() ) { return true; } } return false; } bool BagArtifacts::isArtifactCursePresent( const fheroes2::ArtifactCurseType type ) const { for ( const Artifact & artifact : *this ) { const std::vector & curses = fheroes2::getArtifactData( artifact.GetID() ).curses; if ( std::find( curses.begin(), curses.end(), fheroes2::ArtifactCurse( type ) ) != curses.end() ) { return true; } } return false; } int32_t BagArtifacts::getTotalArtifactEffectValue( const fheroes2::ArtifactBonusType bonus ) const { // If this assertion blows up you're calling the method for a wrong type. assert( !fheroes2::isBonusMultiplied( bonus ) && !fheroes2::isBonusUnique( bonus ) ); int32_t totalValue = 0; if ( fheroes2::isBonusCumulative( bonus ) ) { for ( const Artifact & artifact : *this ) { const std::vector & bonuses = fheroes2::getArtifactData( artifact.GetID() ).bonuses; auto bonusIter = std::find( bonuses.begin(), bonuses.end(), fheroes2::ArtifactBonus( bonus ) ); if ( bonusIter != bonuses.end() ) { totalValue += bonusIter->value; } } } else { std::set usedArtifactIds; for ( const Artifact & artifact : *this ) { const int artifactId = artifact.GetID(); if ( !usedArtifactIds.insert( artifactId ).second ) { // The artifact is present in multiple copies. continue; } const std::vector & bonuses = fheroes2::getArtifactData( artifactId ).bonuses; auto bonusIter = std::find( bonuses.begin(), bonuses.end(), fheroes2::ArtifactBonus( bonus ) ); if ( bonusIter != bonuses.end() ) { totalValue += bonusIter->value; } } } return totalValue; } int32_t BagArtifacts::getTotalArtifactEffectValue( const fheroes2::ArtifactBonusType bonus, std::string & description ) const { // If this assertion blows up you're calling the method for a wrong type. assert( !fheroes2::isBonusMultiplied( bonus ) && !fheroes2::isBonusUnique( bonus ) ); int32_t totalValue = 0; if ( fheroes2::isBonusCumulative( bonus ) ) { std::map artifactValuePerId; for ( const Artifact & artifact : *this ) { const int artifactId = artifact.GetID(); const std::vector & bonuses = fheroes2::getArtifactData( artifactId ).bonuses; auto bonusIter = std::find( bonuses.begin(), bonuses.end(), fheroes2::ArtifactBonus( bonus ) ); if ( bonusIter != bonuses.end() ) { totalValue += bonusIter->value; artifactValuePerId[artifactId] += bonusIter->value; } } for ( const auto & artifactInfo : artifactValuePerId ) { description += Artifact( artifactInfo.first ).GetName(); description += " +"; description += std::to_string( artifactInfo.second ); description += '\n'; } } else { std::set usedArtifactIds; for ( const Artifact & artifact : *this ) { const int artifactId = artifact.GetID(); if ( !usedArtifactIds.insert( artifactId ).second ) { // The artifact is present in multiple copies. continue; } const std::vector & bonuses = fheroes2::getArtifactData( artifactId ).bonuses; auto bonusIter = std::find( bonuses.begin(), bonuses.end(), fheroes2::ArtifactBonus( bonus ) ); if ( bonusIter != bonuses.end() ) { totalValue += bonusIter->value; description += artifact.GetName(); description += " +"; // to show a positive value. description += std::to_string( bonusIter->value ); description += '\n'; } } } return totalValue; } int32_t BagArtifacts::getTotalArtifactEffectValue( const fheroes2::ArtifactCurseType curse ) const { // If this assertion blows up you're calling the method for a wrong type. assert( !fheroes2::isCurseMultiplied( curse ) && !fheroes2::isCurseUnique( curse ) ); int32_t totalValue = 0; if ( fheroes2::isCurseCumulative( curse ) ) { for ( const Artifact & artifact : *this ) { const std::vector & curses = fheroes2::getArtifactData( artifact.GetID() ).curses; auto curseIter = std::find( curses.begin(), curses.end(), fheroes2::ArtifactCurse( curse ) ); if ( curseIter != curses.end() ) { totalValue += curseIter->value; } } } else { std::set usedArtifactIds; for ( const Artifact & artifact : *this ) { const int artifactId = artifact.GetID(); if ( !usedArtifactIds.insert( artifactId ).second ) { // The artifact is present in multiple copies. continue; } const std::vector & curses = fheroes2::getArtifactData( artifactId ).curses; auto curseIter = std::find( curses.begin(), curses.end(), fheroes2::ArtifactCurse( curse ) ); if ( curseIter != curses.end() ) { totalValue += curseIter->value; } } } return totalValue; } int32_t BagArtifacts::getTotalArtifactEffectValue( const fheroes2::ArtifactCurseType curse, std::string & description ) const { // If this assertion blows up you're calling the method for a wrong type. assert( !fheroes2::isCurseMultiplied( curse ) && !fheroes2::isCurseUnique( curse ) ); int32_t totalValue = 0; if ( fheroes2::isCurseCumulative( curse ) ) { std::map artifactValuePerId; for ( const Artifact & artifact : *this ) { const int artifactId = artifact.GetID(); const std::vector & curses = fheroes2::getArtifactData( artifactId ).curses; auto curseIter = std::find( curses.begin(), curses.end(), fheroes2::ArtifactCurse( curse ) ); if ( curseIter != curses.end() ) { totalValue += curseIter->value; artifactValuePerId[artifactId] += curseIter->value; } } for ( const auto & artifactInfo : artifactValuePerId ) { description += Artifact( artifactInfo.first ).GetName(); description += " -"; description += std::to_string( artifactInfo.second ); description += '\n'; } } else { std::set usedArtifactIds; for ( const Artifact & artifact : *this ) { const int artifactId = artifact.GetID(); if ( !usedArtifactIds.insert( artifactId ).second ) { // The artifact is present in multiple copies. continue; } const std::vector & curses = fheroes2::getArtifactData( artifactId ).curses; auto curseIter = std::find( curses.begin(), curses.end(), fheroes2::ArtifactCurse( curse ) ); if ( curseIter != curses.end() ) { totalValue += curseIter->value; description += artifact.GetName(); description += " -"; description += std::to_string( curseIter->value ); description += '\n'; } } } return totalValue; } std::vector BagArtifacts::getTotalArtifactMultipliedPercent( const fheroes2::ArtifactBonusType bonus ) const { if ( !fheroes2::isBonusMultiplied( bonus ) ) { // You are calling this method for a wrong bonus type! assert( 0 ); return {}; } std::vector values; std::set usedArtifactIds; for ( const Artifact & artifact : *this ) { const int artifactId = artifact.GetID(); if ( !usedArtifactIds.insert( artifactId ).second ) { // The artifact is present in multiple copies. continue; } const std::vector & bonuses = fheroes2::getArtifactData( artifactId ).bonuses; auto bonusIter = std::find( bonuses.begin(), bonuses.end(), fheroes2::ArtifactBonus( bonus ) ); if ( bonusIter != bonuses.end() ) { values.emplace_back( bonusIter->value ); } } return values; } std::vector BagArtifacts::getTotalArtifactMultipliedPercent( const fheroes2::ArtifactCurseType curse ) const { if ( !fheroes2::isCurseMultiplied( curse ) ) { // You are calling this method for a wrong curse type! assert( 0 ); return {}; } std::vector values; std::set usedArtifactIds; for ( const Artifact & artifact : *this ) { const int artifactId = artifact.GetID(); if ( !usedArtifactIds.insert( artifactId ).second ) { // The artifact is present in multiple copies. continue; } const std::vector & curses = fheroes2::getArtifactData( artifactId ).curses; auto curseIter = std::find( curses.begin(), curses.end(), fheroes2::ArtifactCurse( curse ) ); if ( curseIter != curses.end() ) { values.emplace_back( curseIter->value ); } } return values; } Artifact BagArtifacts::getFirstArtifactWithBonus( const fheroes2::ArtifactBonusType bonus ) const { for ( const Artifact & artifact : *this ) { const std::vector & bonuses = fheroes2::getArtifactData( artifact.GetID() ).bonuses; auto bonusIter = std::find( bonuses.begin(), bonuses.end(), fheroes2::ArtifactBonus( bonus ) ); if ( bonusIter != bonuses.end() ) { return artifact; } } return { Artifact::UNKNOWN }; } Artifact BagArtifacts::getFirstArtifactWithCurse( const fheroes2::ArtifactCurseType curse ) const { for ( const Artifact & artifact : *this ) { const std::vector & curses = fheroes2::getArtifactData( artifact.GetID() ).curses; auto curseIter = std::find( curses.begin(), curses.end(), fheroes2::ArtifactCurse( curse ) ); if ( curseIter != curses.end() ) { return artifact; } } return { Artifact::UNKNOWN }; } bool BagArtifacts::PushArtifact( const Artifact & art ) { if ( !art.isValid() ) { // Why an invalid artifact is being pushed? assert( 0 ); return false; } if ( art.GetID() == Artifact::MAGIC_BOOK && isPresentArtifact( art ) ) { // We add a magic book while adding a hero on the map. // In case if a map creator set Magic Book to be an artifact of the hero we face two Magic Books situation. return false; } iterator it = std::find( begin(), end(), Artifact( Artifact::UNKNOWN ) ); if ( it == end() ) return false; *it = art; // Always put Magic Book at first place. if ( art.GetID() == Artifact::MAGIC_BOOK ) { std::swap( *it, front() ); } return true; } void BagArtifacts::RemoveArtifact( const Artifact & art ) { iterator it = std::find( begin(), end(), art ); if ( it == end() ) { return; } it->Reset(); } bool BagArtifacts::isFull() const { return end() == std::find( begin(), end(), Artifact( Artifact::UNKNOWN ) ); } uint32_t BagArtifacts::CountArtifacts() const { // no way that we have more than 4 billion artifacts so static_cast is totally valid return static_cast( std::count_if( begin(), end(), []( const Artifact & art ) { return art.isValid(); } ) ); } double BagArtifacts::getArtifactValue() const { double result = 0; for ( const Artifact & art : *this ) { if ( art.isValid() ) result += art.getArtifactValue(); } return result; } void BagArtifacts::exchangeArtifacts( BagArtifacts & giftBag, const Heroes & taker, const Heroes & giver ) { std::vector combined; for ( auto it = begin(); it != end(); ++it ) { if ( it->isValid() && it->GetID() != Artifact::MAGIC_BOOK ) { combined.push_back( *it ); it->Reset(); } } for ( auto it = giftBag.begin(); it != giftBag.end(); ++it ) { if ( it->isValid() && it->GetID() != Artifact::MAGIC_BOOK ) { combined.push_back( *it ); it->Reset(); } } const auto isPureCursedArtifact = []( const Artifact & artifact ) { const fheroes2::ArtifactData & data = fheroes2::getArtifactData( artifact.GetID() ); return !data.curses.empty() && data.bonuses.empty(); }; // Pure cursed artifacts (artifacts with no bonuses but only curses) should definitely go to another bag. transferArtifactsByCondition( combined, giftBag, isPureCursedArtifact ); if ( !taker.HasSecondarySkill( Skill::Secondary::NECROMANCY ) && giver.HasSecondarySkill( Skill::Secondary::NECROMANCY ) ) { const auto isNecromancyArtifact = []( const Artifact & artifact ) { const fheroes2::ArtifactData & data = fheroes2::getArtifactData( artifact.GetID() ); if ( data.bonuses.empty() ) { return false; } for ( const fheroes2::ArtifactBonus & bonus : data.bonuses ) { if ( bonus.type != fheroes2::ArtifactBonusType::NECROMANCY_SKILL ) { return false; } } return true; }; // Giver hero has Necromancy skill so it would be more useful for him to use Necromancy related artifacts. transferArtifactsByCondition( combined, giftBag, isNecromancyArtifact ); } // Scrolls are effective if they contain spells which are not present in the book. if ( taker.HaveSpellBook() ) { const auto isScrollSpellDuplicated = [&taker]( const Artifact & artifact ) { const fheroes2::ArtifactData & data = fheroes2::getArtifactData( artifact.GetID() ); if ( data.bonuses.empty() ) { return false; } for ( const fheroes2::ArtifactBonus & bonus : data.bonuses ) { if ( bonus.type != fheroes2::ArtifactBonusType::ADD_SPELL ) { return false; } } const SpellStorage & magicBookSpells = taker.getMagicBookSpells(); const int32_t spellId = artifact.getSpellId(); assert( spellId != Spell::NONE ); return std::find( magicBookSpells.begin(), magicBookSpells.end(), Spell( spellId ) ) != magicBookSpells.end(); }; transferArtifactsByCondition( combined, giftBag, isScrollSpellDuplicated ); } // A unique artifact is an artifact with no curses and all its bonuses are unique. const auto isUniqueArtifact = []( const Artifact & artifact ) { const fheroes2::ArtifactData & data = fheroes2::getArtifactData( artifact.GetID() ); if ( !data.curses.empty() ) { return false; } for ( const fheroes2::ArtifactBonus & bonus : data.bonuses ) { if ( fheroes2::isBonusCumulative( bonus.type ) ) { return false; } } return true; }; // Search for copies of unique artifacts. All copies of unique artifacts are useless. for ( auto mainIter = combined.begin(); mainIter != combined.end(); ++mainIter ) { if ( isUniqueArtifact( *mainIter ) ) { for ( auto iter = mainIter + 1; iter != combined.end(); ) { if ( *iter == *mainIter ) { // Scrolls are considered as unique artifacts but their internal value might be different. // If they contain different spells then we should not interpret them as the same. if ( ( iter->GetID() == Artifact::SPELL_SCROLL ) && ( iter->getSpellId() != mainIter->getSpellId() ) ) { ++iter; continue; } if ( !giftBag.PushArtifact( *iter ) ) { // The bag is full. No need to proceed further. break; } iter = combined.erase( iter ); } else { ++iter; } } } } // Sort artifacts by value from lowest to highest since we pick them from the end of container. std::sort( combined.begin(), combined.end(), []( const Artifact & left, const Artifact & right ) { return left.getArtifactValue() < right.getArtifactValue(); } ); // TODO: add logic for excessive amount of artifacts and also leave one slot for a magic book if the more powerful hero doesn't have one. // reset and clear all current artifacts, put back the best while ( !combined.empty() && PushArtifact( combined.back() ) ) { combined.pop_back(); } while ( !combined.empty() && giftBag.PushArtifact( combined.back() ) ) { combined.pop_back(); } assert( combined.empty() ); // Assemble artifact sets after the exchange, if possible assembleArtifactSetIfPossible(); giftBag.assembleArtifactSetIfPossible(); } bool BagArtifacts::ContainUltimateArtifact() const { return std::any_of( begin(), end(), []( const Artifact & art ) { return art.isUltimate(); } ); } std::string BagArtifacts::String() const { std::string output; for ( const Artifact & art : *this ) { if ( !art.isValid() ) { continue; } output += art.GetName(); output += ", "; } return output; } uint32_t BagArtifacts::Count( const Artifact & art ) const { return static_cast( std::count( begin(), end(), art ) ); // no way that we have more than 4 billion artifacts } uint32_t GoldInsteadArtifact( const MP2::MapObjectType objectType ) { switch ( objectType ) { case MP2::OBJ_SKELETON: case MP2::OBJ_TREASURE_CHEST: case MP2::OBJ_SHIPWRECK_SURVIVOR: return 1000; case MP2::OBJ_SEA_CHEST: return 1500; case MP2::OBJ_GRAVEYARD: return 2000; case MP2::OBJ_SHIPWRECK: return 5000; default: break; } return 0; } void fheroes2::ResetArtifactStats() { std::fill( artifactGlobalStatus.begin(), artifactGlobalStatus.end(), static_cast( 0 ) ); } void fheroes2::ExcludeArtifactFromRandom( const int artifactID ) { const size_t id = static_cast( artifactID ); assert( id < artifactGlobalStatus.size() ); artifactGlobalStatus[id] |= ART_RNDDISABLED; } bool fheroes2::isPriceOfLoyaltyArtifact( const int artifactID ) { return artifactID >= Artifact::SPELL_SCROLL && artifactID <= Artifact::SPADE_NECROMANCY; } ArtifactsBar::ArtifactsBar( const Heroes * hero, const bool mini, const bool ro, const bool change, const bool allowOpeningMagicBook, StatusBar * bar ) : _hero( hero ) , use_mini_sprite( mini ) , read_only( ro ) , can_change( change ) , _allowOpeningMagicBook( allowOpeningMagicBook ) , _statusBar( bar ) { if ( use_mini_sprite ) { const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::HSICONS, 0 ); const fheroes2::Rect rt( 26, 21, 32, 32 ); backsf.resize( rt.width + 2, rt.height + 2 ); backsf.reset(); fheroes2::DrawBorder( backsf, fheroes2::GetColorId( 0xD0, 0xC0, 0x48 ) ); fheroes2::Blit( sprite, rt.x, rt.y, backsf, 1, 1, rt.width, rt.height ); setSingleItemSize( { backsf.width(), backsf.height() } ); spcursor.resize( backsf.width(), backsf.height() ); spcursor.reset(); fheroes2::DrawBorder( spcursor, 214 ); } else { const fheroes2::Sprite & sprite = fheroes2::AGG::GetICN( ICN::ARTIFACT, 0 ); setSingleItemSize( { sprite.width(), sprite.height() } ); spcursor.resize( 70, 70 ); spcursor.reset(); fheroes2::DrawRect( spcursor, { 0, 0, 70, 70 }, 190 ); fheroes2::DrawRect( spcursor, { 1, 1, 68, 68 }, 180 ); fheroes2::DrawRect( spcursor, { 2, 2, 66, 66 }, 190 ); } } void ArtifactsBar::ResetSelected() { spcursor.hide(); Interface::ItemsActionBar::ResetSelected(); } void ArtifactsBar::Redraw( fheroes2::Image & dstsf ) { spcursor.hide(); Interface::ItemsActionBar::Redraw( dstsf ); } void ArtifactsBar::RedrawBackground( const fheroes2::Rect & pos, fheroes2::Image & dstsf ) { if ( use_mini_sprite ) fheroes2::Blit( backsf, dstsf, pos.x, pos.y ); else fheroes2::Blit( fheroes2::AGG::GetICN( ICN::ARTIFACT, 0 ), dstsf, pos.x, pos.y ); } void ArtifactsBar::RedrawItem( Artifact & art, const fheroes2::Rect & pos, bool selected, fheroes2::Image & dstsf ) { if ( art.isValid() ) { if ( use_mini_sprite ) { const fheroes2::Sprite & artifactSprite = fheroes2::AGG::GetICN( ICN::ARTFX, art.IndexSprite32() ); fheroes2::Fill( dstsf, pos.x + 1, pos.y + 1, artifactSprite.width(), artifactSprite.height(), 0 ); fheroes2::Blit( artifactSprite, dstsf, pos.x + 1, pos.y + 1 ); } else { const fheroes2::Sprite & artifactSprite = fheroes2::AGG::GetICN( ICN::ARTIFACT, art.IndexSprite64() ); fheroes2::Fill( dstsf, pos.x, pos.y, artifactSprite.width(), artifactSprite.height(), 0 ); fheroes2::Blit( artifactSprite, dstsf, pos.x, pos.y ); } if ( selected ) { if ( use_mini_sprite ) spcursor.setPosition( pos.x, pos.y ); else spcursor.setPosition( pos.x - 3, pos.y - 3 ); spcursor.show(); } } } bool ArtifactsBar::ActionBarLeftMouseSingleClick( Artifact & art ) { if ( isMagicBook( art ) ) { const bool isMbSelected = ( !isSelected() || isMagicBook( *GetSelectedItem() ) ); if ( isMbSelected ) { if ( can_change ) { const_cast( _hero )->EditSpellBook(); } else if ( _allowOpeningMagicBook ) { if ( _statusBar != nullptr ) { const std::function statusCallback = [this]( const std::string & status ) { _statusBar->ShowMessage( status ); }; _hero->OpenSpellBook( SpellBook::Filter::ALL, false, false, &statusCallback ); } else { _hero->OpenSpellBook( SpellBook::Filter::ALL, false, false, nullptr ); } } else { messageMagicBookAbortTrading(); } } return false; } if ( isSelected() ) { if ( !read_only ) { std::swap( art, *GetSelectedItem() ); } return false; } if ( art.isValid() ) { if ( !read_only ) { spcursor.hide(); } } else { if ( can_change ) { art = Dialog::selectArtifact( Artifact::UNKNOWN ); if ( isMagicBook( art ) ) { art.Reset(); const_cast( _hero )->SpellBookActivate(); } else if ( art.GetID() == Artifact::SPELL_SCROLL ) { const int spellId = Dialog::selectSpell( Spell::RANDOM, true ).GetID(); if ( spellId == Spell::NONE ) { // No spell for the Spell Scroll artifact was selected - cancel the artifact selection. art.Reset(); } else { art.SetSpell( spellId ); } } } return false; } return true; } bool ArtifactsBar::ActionBarLeftMouseDoubleClick( Artifact & art ) { if ( art.isValid() ) { fheroes2::ArtifactDialogElement( art ).showPopup( Dialog::OK ); } ResetSelected(); return true; } bool ArtifactsBar::ActionBarRightMouseHold( Artifact & art ) { ResetSelected(); if ( art.isValid() ) { if ( can_change ) { if ( isMagicBook( art ) ) { const_cast( _hero )->SpellBookDeactivate(); } else { art.Reset(); } } else { fheroes2::ArtifactDialogElement( art ).showPopup( Dialog::ZERO ); } } return true; } bool ArtifactsBar::ActionBarLeftMouseSingleClick( Artifact & art1, Artifact & art2 ) { if ( !isMagicBook( art1 ) && !isMagicBook( art2 ) ) { std::swap( art1, art2 ); } else { messageMagicBookAbortTrading(); } return false; } bool ArtifactsBar::ActionBarCursor( Artifact & art ) { if ( isSelected() ) { const Artifact * art2 = GetSelectedItem(); if ( &art == art2 ) { if ( isMagicBook( art ) ) msg = _( "View Spells" ); else { msg = _( "View %{name} Info" ); StringReplace( msg, "%{name}", art.GetName() ); } } else if ( !art.isValid() ) { if ( !read_only ) { msg = _( "Move %{name}" ); StringReplace( msg, "%{name}", art2->GetName() ); } } else if ( !read_only ) { if ( isMagicBook( art ) ) { msg = _( "Cannot move the Spellbook" ); } else { msg = _( "Exchange %{name2} with %{name}" ); StringReplace( msg, "%{name}", art.GetName() ); StringReplace( msg, "%{name2}", art2->GetName() ); } } } else if ( art.isValid() ) { if ( isMagicBook( art ) ) { msg = _( "View Spells" ); } else { msg = _( "Select %{name}" ); StringReplace( msg, "%{name}", art.GetName() ); } } return false; } bool ArtifactsBar::ActionBarCursor( Artifact & art1, Artifact & art2 /* selected */ ) { if ( isMagicBook( art2 ) || isMagicBook( art1 ) ) msg = _( "Cannot move the Spellbook" ); else if ( art1.isValid() ) { msg = _( "Exchange %{name2} with %{name}" ); StringReplace( msg, "%{name}", art1.GetName() ); StringReplace( msg, "%{name2}", art2.GetName() ); } else { msg = _( "Move %{name}" ); StringReplace( msg, "%{name}", art2.GetName() ); } return false; } bool ArtifactsBar::QueueEventProcessing( std::string * str ) { msg.clear(); bool res = Interface::ItemsActionBar::QueueEventProcessing(); if ( str ) *str = msg; return res; } bool ArtifactsBar::QueueEventProcessing( ArtifactsBar & bar, std::string * str ) { msg.clear(); bool res = Interface::ItemsActionBar::QueueEventProcessing( bar ); if ( str ) *str = msg; return res; } bool ArtifactsBar::isMagicBook( const Artifact & artifact ) { return artifact.GetID() == Artifact::MAGIC_BOOK; } void ArtifactsBar::messageMagicBookAbortTrading() const { fheroes2::showStandardTextMessage( "", _( "This item can't be traded." ), Dialog::OK ); } std::set BagArtifacts::assembleArtifactSetIfPossible() { std::set assembledArtifactSets; for ( const auto & setData : artifactSets ) { bool foundAllArtifacts = true; while ( foundAllArtifacts ) { for ( const int artifactId : setData.second ) { if ( std::find( begin(), end(), Artifact( artifactId ) ) == end() ) { foundAllArtifacts = false; break; } } if ( !foundAllArtifacts ) break; // At this point, we have confirmed that all the artifact parts are present // so remove the parts and then add the assembled artifact to BagArtifacts for ( const int artifactId : setData.second ) RemoveArtifact( artifactId ); assembledArtifactSets.insert( setData.first ); PushArtifact( setData.first._assembledArtifactID ); } } return assembledArtifactSets; } bool ArtifactSetData::operator<( const ArtifactSetData & other ) const { return _assembledArtifactID < other._assembledArtifactID; } fheroes2-1.0.12+dfsg/src/fheroes2/resource/artifact.h000066400000000000000000000265111456075706000223470ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2ARTIFACT_H #define H2ARTIFACT_H #include #include #include #include #include #include "artifact_info.h" #include "image.h" #include "interface_itemsbar.h" #include "math_base.h" #include "mp2.h" #include "ui_tool.h" class Heroes; class StatusBar; class StreamBase; class Artifact { public: enum level_t { ART_NONE = 0, ART_LEVEL_TREASURE = 0x01, ART_LEVEL_MINOR = 0x02, ART_LEVEL_MAJOR = 0x04, ART_LEVEL_ALL_NORMAL = ART_LEVEL_TREASURE | ART_LEVEL_MINOR | ART_LEVEL_MAJOR, ART_ULTIMATE = 0x08, ART_LOYALTY = 0x10, ART_NORANDOM = 0x20 }; // All artifact IDs are by value 1 bigger than in the original game. // This is done to support new artifact addition and also align with the rest of object types. enum type_t : int { UNKNOWN = 0, // The Succession Wars artifacts. ULTIMATE_BOOK, ULTIMATE_SWORD, ULTIMATE_CLOAK, ULTIMATE_WAND, ULTIMATE_SHIELD, ULTIMATE_STAFF, ULTIMATE_CROWN, GOLDEN_GOOSE, ARCANE_NECKLACE, CASTER_BRACELET, MAGE_RING, WITCHES_BROACH, MEDAL_VALOR, MEDAL_COURAGE, MEDAL_HONOR, MEDAL_DISTINCTION, FIZBIN_MISFORTUNE, THUNDER_MACE, ARMORED_GAUNTLETS, DEFENDER_HELM, GIANT_FLAIL, BALLISTA, STEALTH_SHIELD, DRAGON_SWORD, POWER_AXE, DIVINE_BREASTPLATE, MINOR_SCROLL, MAJOR_SCROLL, SUPERIOR_SCROLL, FOREMOST_SCROLL, ENDLESS_SACK_GOLD, ENDLESS_BAG_GOLD, ENDLESS_PURSE_GOLD, NOMAD_BOOTS_MOBILITY, TRAVELER_BOOTS_MOBILITY, RABBIT_FOOT, GOLDEN_HORSESHOE, GAMBLER_LUCKY_COIN, FOUR_LEAF_CLOVER, TRUE_COMPASS_MOBILITY, SAILORS_ASTROLABE_MOBILITY, EVIL_EYE, ENCHANTED_HOURGLASS, GOLD_WATCH, SKULLCAP, ICE_CLOAK, FIRE_CLOAK, LIGHTNING_HELM, EVERCOLD_ICICLE, EVERHOT_LAVA_ROCK, LIGHTNING_ROD, SNAKE_RING, ANKH, BOOK_ELEMENTS, ELEMENTAL_RING, HOLY_PENDANT, PENDANT_FREE_WILL, PENDANT_LIFE, SERENITY_PENDANT, SEEING_EYE_PENDANT, KINETIC_PENDANT, PENDANT_DEATH, WAND_NEGATION, GOLDEN_BOW, TELESCOPE, STATESMAN_QUILL, WIZARD_HAT, POWER_RING, AMMO_CART, TAX_LIEN, HIDEOUS_MASK, ENDLESS_POUCH_SULFUR, ENDLESS_VIAL_MERCURY, ENDLESS_POUCH_GEMS, ENDLESS_CORD_WOOD, ENDLESS_CART_ORE, ENDLESS_POUCH_CRYSTAL, SPIKED_HELM, SPIKED_SHIELD, WHITE_PEARL, BLACK_PEARL, MAGIC_BOOK, // These four artifacts are used in original editor for the Ultimate and Random artifacts. // They should not exist and used on the adventure map after the game is properly started. // We do not use these or create extra entries for the Ultimate and Random artifacts in editor. UNUSED_83, UNUSED_84, UNUSED_85, UNUSED_86, // The Price of Loyalty artifacts. SPELL_SCROLL, ARM_MARTYR, BREASTPLATE_ANDURAN, BROACH_SHIELDING, BATTLE_GARB, CRYSTAL_BALL, HEART_FIRE, HEART_ICE, HELMET_ANDURAN, HOLY_HAMMER, LEGENDARY_SCEPTER, MASTHEAD, SPHERE_NEGATION, STAFF_WIZARDRY, SWORD_BREAKER, SWORD_ANDURAN, SPADE_NECROMANCY, // Resurrection artifacts. // IMPORTANT! Put all new artifacts just above this line. ARTIFACT_COUNT }; Artifact( int art = UNKNOWN ) : id( art > UNKNOWN && art < ARTIFACT_COUNT ? art : UNKNOWN ) , ext( 0 ) { // Do nothing. } bool operator==( const Artifact & art ) const { return id == art.id; } bool operator!=( const Artifact & art ) const { return id != art.id; } int GetID() const { return id; } bool isUltimate() const; bool containsCurses() const { return !fheroes2::getArtifactData( id ).curses.empty(); } bool isValid() const { return id != UNKNOWN && id < ARTIFACT_COUNT && ( id < UNUSED_83 || id > UNUSED_86 ); } void Reset() { id = UNKNOWN; ext = 0; } int Level() const; int LoyaltyLevel() const; double getArtifactValue() const; // artfx.icn uint32_t IndexSprite32() const { if ( id == UNKNOWN ) { return 255; } return id - 1; } // return index from artifact.icn uint32_t IndexSprite64() const { return id; } void SetSpell( const int v ); int32_t getSpellId() const; const char * GetName() const; std::string GetDescription() const { return fheroes2::getArtifactData( id ).getDescription( ext ); } static int Rand( level_t ); static Artifact FromMP2IndexSprite( uint32_t ); static const char * getDiscoveryDescription( const Artifact & ); private: friend StreamBase & operator<<( StreamBase &, const Artifact & ); friend StreamBase & operator>>( StreamBase &, Artifact & ); int id; int ext; }; StreamBase & operator<<( StreamBase &, const Artifact & ); StreamBase & operator>>( StreamBase &, Artifact & ); uint32_t GoldInsteadArtifact( const MP2::MapObjectType objectType ); namespace fheroes2 { void ResetArtifactStats(); void ExcludeArtifactFromRandom( const int artifactID ); bool isPriceOfLoyaltyArtifact( const int artifactID ); } struct ArtifactSetData { ArtifactSetData( const int32_t artifactID, std::string assembleMessage ) : _assembledArtifactID( artifactID ) , _assembleMessage( std::move( assembleMessage ) ) { // Do nothing. } int32_t _assembledArtifactID = Artifact::UNKNOWN; std::string _assembleMessage; bool operator<( const ArtifactSetData & other ) const; }; class BagArtifacts : public std::vector { public: BagArtifacts(); bool ContainSpell( const int spellId ) const; bool isPresentArtifact( const Artifact & ) const; bool isArtifactBonusPresent( const fheroes2::ArtifactBonusType type ) const; bool isArtifactCursePresent( const fheroes2::ArtifactCurseType type ) const; // These methods must be called only for bonuses with cumulative effect. int32_t getTotalArtifactEffectValue( const fheroes2::ArtifactBonusType bonus ) const; int32_t getTotalArtifactEffectValue( const fheroes2::ArtifactBonusType bonus, std::string & description ) const; int32_t getTotalArtifactEffectValue( const fheroes2::ArtifactCurseType curse ) const; int32_t getTotalArtifactEffectValue( const fheroes2::ArtifactCurseType curse, std::string & description ) const; // These methods must be called only for bonuses with multiplication effect. std::vector getTotalArtifactMultipliedPercent( const fheroes2::ArtifactBonusType bonus ) const; std::vector getTotalArtifactMultipliedPercent( const fheroes2::ArtifactCurseType curse ) const; // Ideally, these methods should be called only for unique bonuses. However, it can be called for other bonus types. Artifact getFirstArtifactWithBonus( const fheroes2::ArtifactBonusType bonus ) const; Artifact getFirstArtifactWithCurse( const fheroes2::ArtifactCurseType curse ) const; bool PushArtifact( const Artifact & ); // Removes the first found instance of the specified artifact from the artifact bag void RemoveArtifact( const Artifact & art ); bool isFull() const; bool ContainUltimateArtifact() const; // Automatically exchange artifacts between two heroes. The taker should get the best possible artifacts. void exchangeArtifacts( BagArtifacts & giftBag, const Heroes & taker, const Heroes & giver ); double getArtifactValue() const; uint32_t CountArtifacts() const; uint32_t Count( const Artifact & ) const; std::set assembleArtifactSetIfPossible(); std::string String() const; }; class ArtifactsBar : public Interface::ItemsActionBar { public: using Interface::ItemsActionBar::RedrawItem; using Interface::ItemsActionBar::ActionBarRightMouseHold; ArtifactsBar( const Heroes * hero, const bool mini, const bool ro, const bool change, const bool allowOpeningMagicBook, StatusBar * bar ); void RedrawBackground( const fheroes2::Rect &, fheroes2::Image & ) override; void RedrawItem( Artifact &, const fheroes2::Rect &, bool, fheroes2::Image & ) override; void ResetSelected(); void Redraw( fheroes2::Image & dstsf ); bool ActionBarLeftMouseSingleClick( Artifact & artifact ) override; bool ActionBarLeftMouseSingleClick( Artifact & artifact1, Artifact & artifact2 ) override; bool ActionBarLeftMouseDoubleClick( Artifact & artifact ) override; bool ActionBarRightMouseHold( Artifact & artifact ) override; bool QueueEventProcessing( std::string * = nullptr ); bool QueueEventProcessing( ArtifactsBar &, std::string * = nullptr ); bool ActionBarCursor( Artifact & ) override; bool ActionBarCursor( Artifact &, Artifact & ) override; protected: fheroes2::MovableSprite spcursor; private: const Heroes * _hero; fheroes2::Image backsf; const bool use_mini_sprite; const bool read_only; const bool can_change; const bool _allowOpeningMagicBook; StatusBar * _statusBar; std::string msg; static bool isMagicBook( const Artifact & artifact ); void messageMagicBookAbortTrading() const; }; #endif fheroes2-1.0.12+dfsg/src/fheroes2/resource/artifact_info.cpp000066400000000000000000002322251456075706000237160ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "artifact_info.h" #include #include #include #include #include #include "artifact.h" #include "spell.h" #include "tools.h" #include "translations.h" namespace { // This container was chosen intentionally to support future modding. std::vector artifactData; void populateArtifactData() { assert( artifactData.empty() ); artifactData = { { gettext_noop( "Invalid Artifact" ), gettext_noop( "Invalid Artifact" ), nullptr, {}, {} }, // Ultimate artifacts do not have discovery event description as they are not supposed to be found on map. { gettext_noop( "Ultimate Book of Knowledge" ), gettext_noop( "The %{name} increases the hero's knowledge by %{count}." ), nullptr, {}, {} }, { gettext_noop( "Ultimate Sword of Dominion" ), gettext_noop( "The %{name} increases the hero's attack skill by %{count}." ), nullptr, {}, {} }, { gettext_noop( "Ultimate Cloak of Protection" ), gettext_noop( "The %{name} increases the hero's defense skill by %{count}." ), nullptr, {}, {} }, { gettext_noop( "Ultimate Wand of Magic" ), gettext_noop( "The %{name} increases the hero's spell power by %{count}." ), nullptr, {}, {} }, { gettext_noop( "Ultimate Shield" ), gettext_noop( "The %{name} increases the hero's attack and defense skills by %{count} each." ), nullptr, {}, {} }, { gettext_noop( "Ultimate Staff" ), gettext_noop( "The %{name} increases the hero's spell power and knowledge by %{count} each." ), nullptr, {}, {} }, { gettext_noop( "Ultimate Crown" ), gettext_noop( "The %{name} increases each of the hero's basic skills by %{count} points." ), nullptr, {}, {} }, { gettext_noop( "Golden Goose" ), gettext_noop( "The %{name} brings in an income of %{count} gold per day." ), nullptr, {}, {} }, { gettext_noop( "Arcane Necklace of Magic" ), gettext_noop( "The %{name} increases the hero's spell power by %{count}." ), gettext_noop( "After rescuing a Sorceress from a cursed tomb, she rewards your heroism with an exquisite jeweled necklace." ), {}, {} }, { gettext_noop( "Caster's Bracelet of Magic" ), gettext_noop( "The %{name} increases the hero's spell power by %{count}." ), gettext_noop( "While searching through the rubble of a caved-in mine, you free a group of trapped Dwarves. Grateful, the leader gives you a golden bracelet." ), {}, {} }, { gettext_noop( "Mage's Ring of Power" ), gettext_noop( "The %{name} increases the hero's spell power by %{count}." ), gettext_noop( "A cry of pain leads you to a Centaur, caught in a trap. Upon setting the creature free, he hands you a small pouch. Emptying the contents, you find a dazzling jeweled ring." ), {}, {} }, { gettext_noop( "Witch's Broach of Magic" ), gettext_noop( "The %{name} increases the hero's spell power by %{count}." ), gettext_noop( "Alongside the remains of a burnt witch lies a beautiful broach, intricately designed. Approaching the corpse with caution, you add the broach to your inventory." ), {}, {} }, { gettext_noop( "Medal of Valor" ), gettext_noop( "The %{name} increases the morale of the hero's army by %{count}." ), gettext_noop( "Freeing a virtuous maiden from the clutches of an evil overlord, you are granted a Medal of Valor by the King's herald." ), {}, {} }, { gettext_noop( "Medal of Courage" ), gettext_noop( "The %{name} increases the morale of the hero's army by %{count}." ), gettext_noop( "After saving a young boy from a vicious pack of Wolves, you return him to his father's manor. The grateful nobleman awards you with a Medal of Courage." ), {}, {} }, { gettext_noop( "Medal of Honor" ), gettext_noop( "The %{name} increases the morale of the hero's army by %{count}." ), gettext_noop( "After freeing a princess of a neighboring kingdom from the evil clutches of despicable slavers, she awards you with a Medal of Honor." ), {}, {} }, { gettext_noop( "Medal of Distinction" ), gettext_noop( "The %{name} increases the morale of the hero's army by %{count}." ), gettext_noop( "Ridding the countryside of the hideous Minotaur who made a sport of eating noblemen's Knights, you are honored with the Medal of Distinction." ), {}, {} }, { gettext_noop( "Fizbin of Misfortune" ), gettext_noop( "The %{name} greatly decreases the morale of the hero's army by %{count}." ), gettext_noop( "You stumble upon a medal lying alongside the empty road. Adding the medal to your inventory, you become aware that you have acquired the undesirable Fizbin of Misfortune, greatly decreasing your army's morale." ), {}, {} }, { gettext_noop( "Thunder Mace of Dominion" ), gettext_noop( "The %{name} increases the hero's attack skill by %{count}." ), gettext_noop( "During a sudden storm, a bolt of lightning strikes a tree, splitting it. Inside the tree you find a mysterious mace." ), {}, {} }, { gettext_noop( "Armored Gauntlets of Protection" ), gettext_noop( "The %{name} increase the hero's defense skill by %{count}." ), gettext_noop( "You encounter the infamous Black Knight! After a grueling duel ending in a draw, the Knight, out of respect, offers you a pair of armored gauntlets." ), {}, {} }, { gettext_noop( "Defender Helm of Protection" ), gettext_noop( "The %{name} increases the hero's defense skill by %{count}." ), gettext_noop( "A glint of golden light catches your eye. Upon further investigation, you find a golden helm hidden under a bush." ), {}, {} }, { gettext_noop( "Giant Flail of Dominion" ), gettext_noop( "The %{name} increases the hero's attack skill by %{count}." ), gettext_noop( "A clumsy Giant has killed himself with his own flail. Knowing your superior skill with this weapon, you confidently remove the spectacular flail from the fallen Giant." ), {}, {} }, { gettext_noop( "Ballista of Quickness" ), gettext_noop( "The %{name} gives the hero's catapult one extra shot per combat round." ), gettext_noop( "Walking through the ruins of an ancient walled city, you find the instrument of the city's destruction, an elaborately crafted ballista." ), {}, {} }, { gettext_noop( "Stealth Shield of Protection" ), gettext_noop( "The %{name} increases the hero's defense skill by %{count}." ), gettext_noop( "A stone statue of a warrior holds a silver shield. As you remove the shield, the statue crumbles into dust." ), {}, {} }, { gettext_noop( "Dragon Sword of Dominion" ), gettext_noop( "The %{name} increases the hero's attack skill by %{count}." ), gettext_noop( "As you are walking along a narrow path, a nearby bush suddenly bursts into flames. Before your eyes the flames become the image of a beautiful woman. She holds out a magnificent sword to you." ), {}, {} }, { gettext_noop( "Power Axe of Dominion" ), gettext_noop( "The %{name} increases the hero's attack skill by %{count}." ), gettext_noop( "You see a silver axe embedded deeply in the ground. After several unsuccessful attempts by your army to remove the axe, you tightly grip the handle of the axe and effortlessly pull it free." ), {}, {} }, { gettext_noop( "Divine Breastplate of Protection" ), gettext_noop( "The %{name} increases the hero's defense skill by %{count}." ), gettext_noop( "A gang of Rogues is sifting through the possessions of dead warriors. Scaring off the scavengers, you note the Rogues had overlooked a beautiful breastplate." ), {}, {} }, { gettext_noop( "Minor Scroll of Knowledge" ), gettext_noop( "The %{name} increases the hero's knowledge by %{count}." ), gettext_noop( "Before you appears a levitating glass case with a scroll, perched upon a bed of crimson velvet. At your touch, the lid opens and the scroll floats into your awaiting hands." ), {}, {} }, { gettext_noop( "Major Scroll of Knowledge" ), gettext_noop( "The %{name} increases the hero's knowledge by %{count}." ), gettext_noop( "Visiting a local wiseman, you explain the intent of your journey. He reaches into a sack and withdraws a yellowed scroll and hands it to you." ), {}, {} }, { gettext_noop( "Superior Scroll of Knowledge" ), gettext_noop( "The %{name} increases the hero's knowledge by %{count}." ), gettext_noop( "You come across the remains of an ancient Druid. Bones, yellowed with age, peer from the ragged folds of her robe. Searching the robe, you discover a scroll hidden in the folds." ), {}, {} }, { gettext_noop( "Foremost Scroll of Knowledge" ), gettext_noop( "The %{name} increases the hero's knowledge by %{count}." ), gettext_noop( "Mangled bones, yellowed with age, peer from the ragged folds of a dead Druid's robe. Searching the robe, you discover a scroll hidden within." ), {}, {} }, { gettext_noop( "Endless Sack of Gold" ), gettext_noop( "The %{name} provides the hero with %{count} gold per day." ), gettext_noop( "A little leprechaun dances gleefully around a magic sack. Seeing you approach, he stops in mid-stride. The little man screams and stamps his foot ferociously, vanishing into thin air. Remembering the old leprechaun saying 'Finders Keepers', you grab the sack and leave." ), {}, {} }, { gettext_noop( "Endless Bag of Gold" ), gettext_noop( "The %{name} provides the hero with %{count} gold per day." ), gettext_noop( "A noblewoman, separated from her traveling companions, asks for your help. After escorting her home, she rewards you with a bag filled with gold." ), {}, {} }, { gettext_noop( "Endless Purse of Gold" ), gettext_noop( "The %{name} provides the hero with %{count} gold per day." ), gettext_noop( "In your travels, you find a leather purse filled with gold that once belonged to a great warrior king who had the ability to transform any inanimate object into gold." ), {}, {} }, { gettext_noop( "Nomad Boots of Mobility" ), gettext_noop( "The %{name} increase the hero's movement on land." ), gettext_noop( "A Nomad trader seeks protection from a tribe of Goblins. For your assistance, he gives you a finely crafted pair of boots made from the softest leather. Looking closely, you see fascinating ancient carvings engraved on the leather." ), {}, {} }, { gettext_noop( "Traveler's Boots of Mobility" ), gettext_noop( "The %{name} increase the hero's movement on land." ), gettext_noop( "Discovering a pair of beautifully beaded boots made from the finest and softest leather, you thank the anonymous donor and add the boots to your inventory." ), {}, {} }, { gettext_noop( "Lucky Rabbit's Foot" ), gettext_noop( "The %{name} increases the luck of the hero's army by %{count}." ), gettext_noop( "A traveling merchant offers you a rabbit's foot, made of gleaming silver fur, for safe passage. The merchant explains the charm will increase your luck in combat." ), {}, {} }, { gettext_noop( "Golden Horseshoe" ), gettext_noop( "The %{name} increases the luck of the hero's army by %{count}." ), gettext_noop( "An ensnared Unicorn whinnies in fright. Murmuring soothing words, you set her free. Snorting and stamping her front hoof once, she gallops off. Looking down you see a golden horseshoe." ), {}, {} }, { gettext_noop( "Gambler's Lucky Coin" ), gettext_noop( "The %{name} increases the luck of the hero's army by %{count}." ), gettext_noop( "You have captured a mischievous imp who has been terrorizing the region. In exchange for his release, he rewards you with a magical coin." ), {}, {} }, { gettext_noop( "Four-Leaf Clover" ), gettext_noop( "The %{name} increases the luck of the hero's army by %{count}." ), gettext_noop( "In the middle of a patch of dead and dry vegetation, to your surprise you find a healthy green four-leaf clover." ), {}, {} }, { gettext_noop( "True Compass of Mobility" ), gettext_noop( "The %{name} increases the hero's movement on land and sea." ), gettext_noop( "An old man claiming to be an inventor asks you to try his latest invention. He then hands you a compass." ), {}, {} }, { gettext_noop( "Sailor's Astrolabe of Mobility" ), gettext_noop( "The %{name} increases the hero's movement on sea." ), gettext_noop( "An old sea captain is being tortured by Ogres. You save him, and in return he rewards you with a wondrous instrument to measure the distance of a star." ), {}, {} }, { gettext_noop( "Evil Eye" ), gettext_noop( "The %{name} reduces the casting cost of curse spells by half." ), gettext_noop( "While venturing into a decrepit hut you find the Skeleton of a long dead witch. Investigation of the remains reveals a glass eye rolling around inside an empty skull." ), {}, {} }, { gettext_noop( "Enchanted Hourglass" ), gettext_noop( "The %{name} extends the duration of all the hero's spells by %{count} turns." ), gettext_noop( "A surprise turn in the landscape finds you in the midst of a grisly scene: Vultures picking at the aftermath of a terrible battle. Your cursory search of the remains turns up an enchanted hourglass." ), {}, {} }, { gettext_noop( "Gold Watch" ), gettext_noop( "The %{name} doubles the effectiveness of the hero's hypnotize spells." ), gettext_noop( "In reward for helping his cart out of a ditch, a traveling potion salesman gives you a \"magic\" gold watch. Unbeknownst to him, the watch really is magical." ), {}, {} }, { gettext_noop( "Skullcap" ), gettext_noop( "The %{name} halves the casting cost of all mind influencing spells." ), gettext_noop( "A brief stop at an improbable rural inn yields an exchange of money, tales, and accidentally, luggage. You find a magical skullcap in your new backpack." ), {}, {} }, { gettext_noop( "Ice Cloak" ), gettext_noop( "The %{name} halves all damage the hero's troops receive from cold spells." ), gettext_noop( "Responding to the panicked cries of a damsel in distress, you discover a young woman fleeing from a hungry bear. You slay the beast in the nick of time, and the grateful Sorceress weaves a magic cloak from the bear's hide." ), {}, {} }, { gettext_noop( "Fire Cloak" ), gettext_noop( "The %{name} halves all damage the hero's troops receive from fire spells." ), gettext_noop( "You've come upon a fight between a Necromancer and a Paladin. The Necromancer blasts the Paladin with a fire bolt, bringing him to his knees. Acting quickly, you slay the evil one before the final blow. The grateful Paladin gives you the fire cloak that saved him." ), {}, {} }, { gettext_noop( "Lightning Helm" ), gettext_noop( "The %{name} halves all damage the hero's troops receive from lightning spells." ), gettext_noop( "A traveling tinker in need of supplies offers you a helm with a thunderbolt design on its top in exchange for food and water. Curious, you accept, and later find out that the helm is magical." ), {}, {} }, { gettext_noop( "Evercold Icicle" ), gettext_noop( "The %{name} causes the hero's cold spells to do %{count} percent more damage to enemy troops." ), gettext_noop( "An icicle withstanding the full heat of the noonday sun attracts your attention. Intrigued, you break it off, and find that it does not melt in your hand." ), {}, {} }, { gettext_noop( "Everhot Lava Rock" ), gettext_noop( "The %{name} causes the hero's fire spells to do %{count} percent more damage to enemy troops." ), gettext_noop( "Your wanderings bring you into contact with a tribe of ape-like beings using a magical lava rock that never cools to light their fires. You take pity on them and teach them to make fire with sticks. Believing you to be a god, the apes give you their rock." ), {}, {} }, { gettext_noop( "Lightning Rod" ), gettext_noop( "The %{name} causes the hero's lightning spells to do %{count} percent more damage to enemy troops." ), gettext_noop( "While waiting out a storm, a lighting bolt strikes a nearby cottage's lightning rod, which melts and falls to the ground. The tip of the rod, however, survives intact and makes your hair stand on end when you touch it. Hmm..." ), {}, {} }, { gettext_noop( "Snake-Ring" ), gettext_noop( "The %{name} halves the casting cost of all of the hero's bless spells." ), gettext_noop( "You've found an oddly shaped ring on the finger of a long dead traveler. The ring looks like a snake biting its own tail." ), {}, {} }, { gettext_noop( "Ankh" ), gettext_noop( "The %{name} doubles the effectiveness of all of the hero's resurrect and animate spells." ), gettext_noop( "A fierce windstorm reveals the entrance to a buried tomb. Your investigation reveals that the tomb has already been looted, but the thieves overlooked an ankh on a silver chain in the dark." ), {}, {} }, { gettext_noop( "Book of Elements" ), gettext_noop( "The %{name} doubles the effectiveness of all of the hero's summoning spells." ), gettext_noop( "You come across a conjurer who begs to accompany you and your army awhile for safety. You agree, and he offers as payment a copy of the book of the elements." ), {}, {} }, { gettext_noop( "Elemental Ring" ), gettext_noop( "The %{name} halves the casting cost of all summoning spells." ), gettext_noop( "While pausing to rest, you notice a bobcat climbing a short tree to get at a crow's nest. On impulse, you climb the tree yourself and scare off the cat. When you look in the nest, you find a collection of shiny stones and a ring." ), {}, {} }, { gettext_noop( "Holy Pendant" ), gettext_noop( "The %{name} makes all of the hero's troops immune to curse spells." ), gettext_noop( "In your wanderings you come across a hermit living in a small, tidy hut. Impressed with your mission, he takes time out from his meditations to bless and give you a charm against curses." ), {}, {} }, { gettext_noop( "Pendant of Free Will" ), gettext_noop( "The %{name} makes all of the hero's troops immune to hypnotize spells." ), gettext_noop( "Responding to cries for help, you find river Sprites making a sport of dunking an old man. Feeling vengeful, you rescue the man and drag a Sprite onto dry land for awhile. The Sprite, uncomfortable in the air, gives you a magic pendant to let him go." ), {}, {} }, { gettext_noop( "Pendant of Life" ), gettext_noop( "The %{name} makes all of the hero's troops immune to death spells." ), gettext_noop( "A brief roadside encounter with a small caravan and a game of knucklebones wins a magic pendant. Its former owner says that it protects from Necromancers' death spells." ), {}, {} }, { gettext_noop( "Serenity Pendant" ), gettext_noop( "The %{name} makes all of the hero's troops immune to berserk spells." ), gettext_noop( "The sounds of combat draw you to the scene of a fight between an old Barbarian and an eight-headed Hydra. Your timely intervention swings the battle in favor of the man, and he rewards you with a pendant he used to use to calm his mind for battle." ), {}, {} }, { gettext_noop( "Seeing-eye Pendant" ), gettext_noop( "The %{name} makes all of the hero's troops immune to blindness spells." ), gettext_noop( "You come upon a very old woman, long blind from cataracts and dying alone. You tend to her final needs and promise a proper burial. Grateful, she gives you a magic pendant emblazoned with a stylized eye. It lets you see with your eyes closed." ), {}, {} }, { gettext_noop( "Kinetic Pendant" ), gettext_noop( "The %{name} makes all of the hero's troops immune to paralyze spells." ), gettext_noop( "You come across a golem wearing a glowing pendant and blocking your way. Acting on a hunch, you cut the pendant from its neck. Deprived of its power source, the golem breaks down, leaving you with the magical pendant." ), {}, {} }, { gettext_noop( "Pendant of Death" ), gettext_noop( "The %{name} makes all of the hero's troops immune to holy spells." ), gettext_noop( "A quick and deadly battle with a Necromancer wins you his magical pendant. Later, a Wizard tells you that the pendant protects undead under your control from holy word spells." ), {}, {} }, { gettext_noop( "Wand of Negation" ), gettext_noop( "The %{name} makes all of the hero's troops immune to dispel magic spells." ), gettext_noop( "You meet an old Wizard friend of yours traveling in the opposite direction. He presents you with a gift: A wand that prevents the use of the dispel magic spell on your allies." ), {}, {} }, { gettext_noop( "Golden Bow" ), gettext_noop( "The %{name} eliminates the %{count} percent penalty for the hero's troops shooting past obstacles (e.g. castle walls)." ), gettext_noop( "A chance meeting with a famous Archer finds you in a game of knucklebones pitting his bow against your horse. You win." ), {}, {} }, { gettext_noop( "Telescope" ), gettext_noop( "The %{name} increases the amount of terrain the hero reveals when adventuring by %{count} extra square." ), gettext_noop( "A merchant from far away lands trades you a new invention of his people for traveling supplies. It makes distant objects appear closer, and he calls it...\n\na telescope." ), {}, {} }, { gettext_noop( "Statesman's Quill" ), gettext_noop( "The %{name} reduces the cost of surrender to %{count} percent of the total cost of troops the hero has in their army." ), gettext_noop( "You pause to help a diplomat with a broken axle fix his problem. In gratitude, he gives you a writing quill with magical properties which he says will \"help people see things your way\"." ), {}, {} }, { gettext_noop( "Wizard's Hat" ), gettext_noop( "The %{name} increases the duration of the hero's spells by %{count} turns." ), gettext_noop( "You see a Wizard fleeing from a Griffin and riding like the wind. The Wizard opens a portal and rides through, getting his hat knocked off by the edge of the gate. The Griffin follows; the gate closes. You pick the hat up, dust it off, and put it on." ), {}, {} }, { gettext_noop( "Power Ring" ), gettext_noop( "The %{name} returns %{count} extra spell points per day to the hero." ), gettext_noop( "You find a small tree that closely resembles the great Warlock Carnauth with a ring around one of its twigs. Scraps of clothing and rotting leather lead you to suspect that it IS Carnauth, transformed. Since you can't help him, you take the magic ring." ), {}, {} }, { gettext_noop( "Ammo Cart" ), gettext_noop( "The %{name} provides endless ammunition for all of the hero's troops that shoot." ), gettext_noop( "An ammunition cart in the middle of an old battlefield catches your eye. Inspection shows it to be in good working order, so you take it along." ), {}, {} }, { gettext_noop( "Tax Lien" ), gettext_noop( "The %{name} costs the hero %{count} gold pieces per day." ), gettext_noop( "Your big spending habits have earned you a massive tax bill that you can't hope to pay. The tax man takes pity and agrees to only take 250 gold a day from your account for life. Check here if you want one dollar to go to the presidential campaign election fund." ), {}, {} }, { gettext_noop( "Hideous Mask" ), gettext_noop( "The %{name} prevents all 'wandering' armies from joining the hero." ), gettext_noop( "Your looting of the grave of Sinfilas Gardolad, the famous shapeshifting Warlock, unearths his fabled mask. Trembling, you put it on and it twists your visage into an awful grimace! Oh no! It's actually the hideous mask of Gromluck Greene, and you are stuck with it." ), {}, {} }, { gettext_noop( "Endless Pouch of Sulfur" ), gettext_noop( "The %{name} provides %{count} unit of sulfur per day." ), gettext_noop( "You visit an alchemist who, upon seeing your army, is swayed by the righteousness of your cause. The newly loyal subject gives you his endless pouch of sulfur to help with the war effort." ), {}, {} }, { gettext_noop( "Endless Vial of Mercury" ), gettext_noop( "The %{name} provides %{count} unit of mercury per day." ), gettext_noop( "A brief stop at a hastily abandoned Wizard's tower turns up a magical vial of mercury that always has a little left on the bottom. Recognizing a treasure when you see one, you cap it and slip it in your pocket." ), {}, {} }, { gettext_noop( "Endless Pouch of Gems" ), gettext_noop( "The %{name} provides %{count} unit of gems per day." ), gettext_noop( "A short rainstorm brings forth a rainbow...and you can see the end of it. Riding quickly, you seize the pot of gold you find there. The leprechaun who owns it, unable to stop you from taking it, offers an endless pouch of gems for the return of his gold. You accept." ), {}, {} }, { gettext_noop( "Endless Cord of Wood" ), gettext_noop( "The %{name} provides %{count} unit of wood per day." ), gettext_noop( "Pausing to rest and light a cook fire, you pull wood out of a nearby pile of dead wood. As you keep pulling wood from the pile, you notice that it doesn't shrink. You realize to your delight that the wood is enchanted, so you take it along." ), {}, {} }, { gettext_noop( "Endless Cart of Ore" ), gettext_noop( "The %{name} provides %{count} unit of ore per day." ), gettext_noop( "You've found a Goblin weapon smithy making weapons for use against humans. With a tremendous yell you and your army descend upon their camp and drive them away. A search finds a magic ore cart that never runs out of iron." ), {}, {} }, { gettext_noop( "Endless Pouch of Crystal" ), gettext_noop( "The %{name} provides %{count} unit of crystal per day." ), gettext_noop( "Taking shelter from a storm in a small cave, you notice a small patch of crystal in one corner. Curious, you break a piece off and notice that the original crystal grows the lost piece back. You decide to stuff the entire patch into a pouch and take it with you." ), {}, {} }, { gettext_noop( "Spiked Helm" ), gettext_noop( "The %{name} increases the hero's attack and defense skills by %{count} each." ), gettext_noop( "Your army is ambushed by a small tribe of wild (and none too bright) Orcs. You fend them off easily and the survivors flee in all directions. One of the Orcs was wearing a polished spiked helm. Figuring it will make a good souvenir, you take it." ), {}, {} }, { gettext_noop( "Spiked Shield" ), gettext_noop( "The %{name} increases the hero's attack and defense skills by %{count} each." ), gettext_noop( "You come upon a bridge spanning a dry gully. Before you can cross, a Troll steps out from under the bridge and demands payment before it will permit you to pass. You refuse, and the Troll charges, forcing you to slay it. You take its spiked shield as a trophy." ), {}, {} }, { gettext_noop( "White Pearl" ), gettext_noop( "The %{name} increases the hero's spell power and knowledge by %{count} each." ), gettext_noop( "A walk across a dry saltwater lake bed yields an unlikely prize: A white pearl amidst shattered shells and debris." ), {}, {} }, { gettext_noop( "Black Pearl" ), gettext_noop( "The %{name} increases the hero's spell power and knowledge by %{count} each." ), gettext_noop( "Rumors of a Griffin of unusual size preying upon the countryside lead you to its cave lair. A quick, brutal fight dispatches the beast, and a search of its foul nest turns up a huge black pearl." ), {}, {} }, // Magic Book cannot be located on a map. { gettext_noop( "Magic Book" ), gettext_noop( "The %{name} enables the hero to cast spells." ), nullptr, {}, {} }, // These are artifacts used only for map editor (?). { gettext_noop( "Dummy 1" ), gettext_noop( "The reserved artifact." ), nullptr, {}, {} }, { gettext_noop( "Dummy 2" ), gettext_noop( "The reserved artifact." ), nullptr, {}, {} }, { gettext_noop( "Dummy 3" ), gettext_noop( "The reserved artifact." ), nullptr, {}, {} }, { gettext_noop( "Dummy 4" ), gettext_noop( "The reserved artifact." ), nullptr, {}, {} }, { gettext_noop( "Spell Scroll" ), gettext_noop( "This %{name} gives the hero the ability to cast the %{spell} spell if the hero has a Magic Book." ), gettext_noop( "You find an elaborate container which houses an old vellum scroll. The runes on the container are very old, and the artistry with which it was put together is stunning. As you pull the scroll out, you feel imbued with magical power." ), {}, {} }, { gettext_noop( "Arm of the Martyr" ), gettext_noop( "The %{name} increases the hero's spell power by %{count} but adds the undead morale penalty." ), gettext_noop( "One of the less intelligent members of your party picks up an arm off of the ground. Despite its missing a body, it is still moving. Your troops find the dismembered arm repulsive, but you cannot bring yourself to drop it: it seems to hold some sort of magical power that influences your decision making." ), {}, {} }, { gettext_noop( "Breastplate of Anduran" ), gettext_noop( "The %{name} increases the hero's defense by %{count}." ), gettext_noop( "You come upon a sign. It reads: \"Here lies the body of Anduran. Bow and swear fealty, and you shall be rewarded.\" You decide to do as it says. As you stand up, you feel a coldness against your skin. Looking down, you find that you are suddenly wearing a gleaming, ornate breastplate." ), {}, {} }, { gettext_noop( "Broach of Shielding" ), gettext_noop( "The %{name} provides %{count} percent protection from Armageddon and Elemental Storm, but decreases spell power by 2." ), gettext_noop( "A kindly Sorceress thinks that your army's defenses could use a magical boost. She offers to enchant the Broach that you wear on your cloak, and you accept." ), {}, {} }, { gettext_noop( "Battle Garb of Anduran" ), gettext_noop( "The %{name} combines the powers of the three Anduran artifacts. It provides maximum luck and morale for the hero's troops and gives the hero the Town Portal spell." ), gettext_noop( "Out of pity for a poor peasant, you purchase a chest of old junk they are hawking for too much gold. Later, as you search through it, you find it contains the 3 pieces of the legendary battle garb of Anduran!" ), {}, {} }, { gettext_noop( "Crystal Ball" ), gettext_noop( "The %{name} lets the hero get more specific information about monsters, enemy heroes, and castles nearby the hero." ), gettext_noop( "You come upon a caravan of gypsies who are feasting and fortifying their bodies with mead. They call you forward and say \"If you prove that you can dance the Rama-Buta, we will reward you.\" You don't know it, but try anyway. They laugh hysterically, but admire your bravery, giving you a Crystal Ball." ), {}, {} }, { gettext_noop( "Heart of Fire" ), gettext_noop( "The %{name} provides %{count} percent protection from fire, but doubles the damage taken from cold." ), gettext_noop( "You enter a recently burned glade and come upon a Fire Elemental sitting atop a rock. It looks up, its flaming face contorted in a look of severe pain. It then tosses a glowing object at you. You put up your hands to block it, but it passes right through them and sears itself into your chest." ), {}, {} }, { gettext_noop( "Heart of Ice" ), gettext_noop( "The %{name} provides %{count} percent protection from cold, but doubles the damage taken from fire." ), gettext_noop( "Suddenly, a biting coldness engulfs your body. You seize up, falling from your horse. The pain subsides, but you still feel as if your chest is frozen. As you pick yourself up off of the ground, you hear hearty laughter. You turn around just in time to see a Frost Giant run off into the woods and disappear." ), {}, {} }, { gettext_noop( "Helmet of Anduran" ), gettext_noop( "The %{name} increases the hero's spell power by %{count}." ), gettext_noop( "You spy a gleaming object poking up out of the ground. You send a member of your party over to investigate. He comes back with a golden helmet in his hands. You realize that it must be the helmet of the legendary Anduran, the only man who was known to wear solid gold armor." ), {}, {} }, { gettext_noop( "Holy Hammer" ), gettext_noop( "The %{name} increases the hero's attack skill by %{count}." ), gettext_noop( "You come upon a battle where a Paladin has been mortally wounded by a group of Zombies. He asks you to take his hammer and finish what he started. As you pick it up, it begins to hum, and then everything becomes a blur. The Zombies lie dead, the hammer dripping with blood. You strap it to your belt." ), {}, {} }, { gettext_noop( "Legendary Scepter" ), gettext_noop( "The %{name} adds %{count} points to all attributes." ), gettext_noop( "Upon cresting a small hill, you come upon a ridiculous looking sight. A Sprite is attempting to carry a Scepter that is almost as big as it is. Trying not to laugh, you ask, \"Need help?\" The Sprite glares at you and answers: \"You think this is funny? Fine. You can carry it. I much prefer flying anyway.\"" ), {}, {} }, { gettext_noop( "Masthead" ), gettext_noop( "The %{name} boosts the hero's troops' luck and morale by %{count} each in sea combat." ), gettext_noop( "An old seaman tells you a tale of an enchanted masthead that he used in his youth to rally his crew during times of trouble. He then hands you a faded map that shows where he hid it. After much exploring, you find it stashed underneath a nearby dock." ), {}, {} }, { gettext_noop( "Sphere of Negation" ), gettext_noop( "The %{name} disables all spell casting, for both sides, in combat." ), gettext_noop( "You stop to help a Peasant catch a runaway mare. To show his gratitude, he hands you a tiny sphere. As soon as you grasp it, you feel the magical energy drain from your limbs..." ), {}, {} }, { gettext_noop( "Staff of Wizardry" ), gettext_noop( "The %{name} boosts the hero's spell power by %{count}." ), gettext_noop( "While out scaring up game, your troops find a mysterious staff levitating about three feet off of the ground. They hand it to you, and you notice an inscription. It reads: \"Brains best brawn and magic beats might. Heed my words, and you'll win every fight.\"" ), {}, {} }, { gettext_noop( "Sword Breaker" ), gettext_noop( "The %{name} increases the hero's defense by %{count} and attack by 1." ), gettext_noop( "A former Captain of the Guard admires your quest and gives you the enchanted Sword Breaker that he relied on during his tour of duty." ), {}, {} }, { gettext_noop( "Sword of Anduran" ), gettext_noop( "The %{name} increases the hero's attack skill by %{count}." ), gettext_noop( "A Troll stops you and says: \"Pay me 5,000 gold, or the Sword of Anduran will slay you where you stand.\" You refuse. The troll grabs the sword hanging from its belt, screams in pain, and runs away. Picking up the fabled sword, you give thanks that half-witted Trolls tend to grab the wrong end of sharp objects." ), {}, {} }, { gettext_noop( "Spade of Necromancy" ), gettext_noop( "The %{name} gives the hero increased necromancy skill." ), gettext_noop( "A dirty shovel has been thrust into a dirt mound nearby. Upon investigation, you discover it to be the enchanted shovel of the Gravediggers, long thought lost by mortals." ), {}, {} }, }; assert( artifactData.size() == ( Artifact::ARTIFACT_COUNT ) ); // Artifact bonus and curse 'value' is signed integer. However, it should not be negative. artifactData[Artifact::UNKNOWN].bonuses.emplace_back( fheroes2::ArtifactBonusType::NONE ); artifactData[Artifact::ULTIMATE_BOOK].bonuses.emplace_back( fheroes2::ArtifactBonusType::KNOWLEDGE_SKILL, 12 ); artifactData[Artifact::ULTIMATE_SWORD].bonuses.emplace_back( fheroes2::ArtifactBonusType::ATTACK_SKILL, 12 ); artifactData[Artifact::ULTIMATE_CLOAK].bonuses.emplace_back( fheroes2::ArtifactBonusType::DEFENCE_SKILL, 12 ); artifactData[Artifact::ULTIMATE_WAND].bonuses.emplace_back( fheroes2::ArtifactBonusType::SPELL_POWER_SKILL, 12 ); artifactData[Artifact::ULTIMATE_SHIELD].bonuses.emplace_back( fheroes2::ArtifactBonusType::ATTACK_SKILL, 6 ); artifactData[Artifact::ULTIMATE_SHIELD].bonuses.emplace_back( fheroes2::ArtifactBonusType::DEFENCE_SKILL, 6 ); artifactData[Artifact::ULTIMATE_STAFF].bonuses.emplace_back( fheroes2::ArtifactBonusType::SPELL_POWER_SKILL, 6 ); artifactData[Artifact::ULTIMATE_STAFF].bonuses.emplace_back( fheroes2::ArtifactBonusType::KNOWLEDGE_SKILL, 6 ); artifactData[Artifact::ULTIMATE_CROWN].bonuses.emplace_back( fheroes2::ArtifactBonusType::ATTACK_SKILL, 4 ); artifactData[Artifact::ULTIMATE_CROWN].bonuses.emplace_back( fheroes2::ArtifactBonusType::DEFENCE_SKILL, 4 ); artifactData[Artifact::ULTIMATE_CROWN].bonuses.emplace_back( fheroes2::ArtifactBonusType::SPELL_POWER_SKILL, 4 ); artifactData[Artifact::ULTIMATE_CROWN].bonuses.emplace_back( fheroes2::ArtifactBonusType::KNOWLEDGE_SKILL, 4 ); artifactData[Artifact::GOLDEN_GOOSE].bonuses.emplace_back( fheroes2::ArtifactBonusType::GOLD_INCOME, 10000 ); artifactData[Artifact::ARCANE_NECKLACE].bonuses.emplace_back( fheroes2::ArtifactBonusType::SPELL_POWER_SKILL, 4 ); artifactData[Artifact::CASTER_BRACELET].bonuses.emplace_back( fheroes2::ArtifactBonusType::SPELL_POWER_SKILL, 2 ); artifactData[Artifact::MAGE_RING].bonuses.emplace_back( fheroes2::ArtifactBonusType::SPELL_POWER_SKILL, 2 ); artifactData[Artifact::WITCHES_BROACH].bonuses.emplace_back( fheroes2::ArtifactBonusType::SPELL_POWER_SKILL, 3 ); artifactData[Artifact::MEDAL_VALOR].bonuses.emplace_back( fheroes2::ArtifactBonusType::MORALE, 1 ); artifactData[Artifact::MEDAL_COURAGE].bonuses.emplace_back( fheroes2::ArtifactBonusType::MORALE, 1 ); artifactData[Artifact::MEDAL_HONOR].bonuses.emplace_back( fheroes2::ArtifactBonusType::MORALE, 1 ); artifactData[Artifact::MEDAL_DISTINCTION].bonuses.emplace_back( fheroes2::ArtifactBonusType::MORALE, 1 ); artifactData[Artifact::FIZBIN_MISFORTUNE].curses.emplace_back( fheroes2::ArtifactCurseType::MORALE, 2 ); artifactData[Artifact::THUNDER_MACE].bonuses.emplace_back( fheroes2::ArtifactBonusType::ATTACK_SKILL, 1 ); artifactData[Artifact::ARMORED_GAUNTLETS].bonuses.emplace_back( fheroes2::ArtifactBonusType::DEFENCE_SKILL, 1 ); artifactData[Artifact::DEFENDER_HELM].bonuses.emplace_back( fheroes2::ArtifactBonusType::DEFENCE_SKILL, 1 ); artifactData[Artifact::GIANT_FLAIL].bonuses.emplace_back( fheroes2::ArtifactBonusType::ATTACK_SKILL, 1 ); artifactData[Artifact::BALLISTA].bonuses.emplace_back( fheroes2::ArtifactBonusType::EXTRA_CATAPULT_SHOTS, 1 ); artifactData[Artifact::STEALTH_SHIELD].bonuses.emplace_back( fheroes2::ArtifactBonusType::DEFENCE_SKILL, 2 ); artifactData[Artifact::DRAGON_SWORD].bonuses.emplace_back( fheroes2::ArtifactBonusType::ATTACK_SKILL, 3 ); artifactData[Artifact::POWER_AXE].bonuses.emplace_back( fheroes2::ArtifactBonusType::ATTACK_SKILL, 2 ); artifactData[Artifact::DIVINE_BREASTPLATE].bonuses.emplace_back( fheroes2::ArtifactBonusType::DEFENCE_SKILL, 3 ); artifactData[Artifact::MINOR_SCROLL].bonuses.emplace_back( fheroes2::ArtifactBonusType::KNOWLEDGE_SKILL, 2 ); artifactData[Artifact::MAJOR_SCROLL].bonuses.emplace_back( fheroes2::ArtifactBonusType::KNOWLEDGE_SKILL, 3 ); artifactData[Artifact::SUPERIOR_SCROLL].bonuses.emplace_back( fheroes2::ArtifactBonusType::KNOWLEDGE_SKILL, 4 ); artifactData[Artifact::FOREMOST_SCROLL].bonuses.emplace_back( fheroes2::ArtifactBonusType::KNOWLEDGE_SKILL, 5 ); artifactData[Artifact::ENDLESS_SACK_GOLD].bonuses.emplace_back( fheroes2::ArtifactBonusType::GOLD_INCOME, 1000 ); artifactData[Artifact::ENDLESS_BAG_GOLD].bonuses.emplace_back( fheroes2::ArtifactBonusType::GOLD_INCOME, 750 ); artifactData[Artifact::ENDLESS_PURSE_GOLD].bonuses.emplace_back( fheroes2::ArtifactBonusType::GOLD_INCOME, 500 ); artifactData[Artifact::NOMAD_BOOTS_MOBILITY].bonuses.emplace_back( fheroes2::ArtifactBonusType::LAND_MOBILITY, 600 ); artifactData[Artifact::TRAVELER_BOOTS_MOBILITY].bonuses.emplace_back( fheroes2::ArtifactBonusType::LAND_MOBILITY, 300 ); artifactData[Artifact::RABBIT_FOOT].bonuses.emplace_back( fheroes2::ArtifactBonusType::LUCK, 1 ); artifactData[Artifact::GOLDEN_HORSESHOE].bonuses.emplace_back( fheroes2::ArtifactBonusType::LUCK, 1 ); artifactData[Artifact::GAMBLER_LUCKY_COIN].bonuses.emplace_back( fheroes2::ArtifactBonusType::LUCK, 1 ); artifactData[Artifact::FOUR_LEAF_CLOVER].bonuses.emplace_back( fheroes2::ArtifactBonusType::LUCK, 1 ); artifactData[Artifact::TRUE_COMPASS_MOBILITY].bonuses.emplace_back( fheroes2::ArtifactBonusType::LAND_MOBILITY, 500 ); artifactData[Artifact::TRUE_COMPASS_MOBILITY].bonuses.emplace_back( fheroes2::ArtifactBonusType::SEA_MOBILITY, 500 ); artifactData[Artifact::SAILORS_ASTROLABE_MOBILITY].bonuses.emplace_back( fheroes2::ArtifactBonusType::SEA_MOBILITY, 1000 ); artifactData[Artifact::EVIL_EYE].bonuses.emplace_back( fheroes2::ArtifactBonusType::CURSE_SPELL_COST_REDUCTION_PERCENT, 50 ); artifactData[Artifact::ENCHANTED_HOURGLASS].bonuses.emplace_back( fheroes2::ArtifactBonusType::EVERY_COMBAT_SPELL_DURATION, 2 ); artifactData[Artifact::GOLD_WATCH].bonuses.emplace_back( fheroes2::ArtifactBonusType::HYPNOTIZE_SPELL_EXTRA_EFFECTIVENESS_PERCENT, 100 ); artifactData[Artifact::SKULLCAP].bonuses.emplace_back( fheroes2::ArtifactBonusType::MIND_INFLUENCE_SPELL_COST_REDUCTION_PERCENT, 50 ); artifactData[Artifact::ICE_CLOAK].bonuses.emplace_back( fheroes2::ArtifactBonusType::COLD_SPELL_DAMAGE_REDUCTION_PERCENT, 50 ); artifactData[Artifact::FIRE_CLOAK].bonuses.emplace_back( fheroes2::ArtifactBonusType::FIRE_SPELL_DAMAGE_REDUCTION_PERCENT, 50 ); artifactData[Artifact::LIGHTNING_HELM].bonuses.emplace_back( fheroes2::ArtifactBonusType::LIGHTNING_SPELL_DAMAGE_REDUCTION_PERCENT, 50 ); artifactData[Artifact::EVERCOLD_ICICLE].bonuses.emplace_back( fheroes2::ArtifactBonusType::COLD_SPELL_EXTRA_EFFECTIVENESS_PERCENT, 50 ); artifactData[Artifact::EVERHOT_LAVA_ROCK].bonuses.emplace_back( fheroes2::ArtifactBonusType::FIRE_SPELL_EXTRA_EFFECTIVENESS_PERCENT, 50 ); artifactData[Artifact::LIGHTNING_ROD].bonuses.emplace_back( fheroes2::ArtifactBonusType::LIGHTNING_SPELL_EXTRA_EFFECTIVENESS_PERCENT, 50 ); artifactData[Artifact::SNAKE_RING].bonuses.emplace_back( fheroes2::ArtifactBonusType::BLESS_SPELL_COST_REDUCTION_PERCENT, 50 ); artifactData[Artifact::ANKH].bonuses.emplace_back( fheroes2::ArtifactBonusType::RESURRECT_SPELL_EXTRA_EFFECTIVENESS_PERCENT, 100 ); artifactData[Artifact::BOOK_ELEMENTS].bonuses.emplace_back( fheroes2::ArtifactBonusType::SUMMONING_SPELL_EXTRA_EFFECTIVENESS_PERCENT, 100 ); artifactData[Artifact::ELEMENTAL_RING].bonuses.emplace_back( fheroes2::ArtifactBonusType::SUMMONING_SPELL_COST_REDUCTION_PERCENT, 50 ); artifactData[Artifact::HOLY_PENDANT].bonuses.emplace_back( fheroes2::ArtifactBonusType::CURSE_SPELL_IMMUNITY ); artifactData[Artifact::PENDANT_FREE_WILL].bonuses.emplace_back( fheroes2::ArtifactBonusType::HYPNOTIZE_SPELL_IMMUNITY ); artifactData[Artifact::PENDANT_LIFE].bonuses.emplace_back( fheroes2::ArtifactBonusType::DEATH_SPELL_IMMUNITY ); artifactData[Artifact::SERENITY_PENDANT].bonuses.emplace_back( fheroes2::ArtifactBonusType::BERSERK_SPELL_IMMUNITY ); artifactData[Artifact::SEEING_EYE_PENDANT].bonuses.emplace_back( fheroes2::ArtifactBonusType::BLIND_SPELL_IMMUNITY ); artifactData[Artifact::KINETIC_PENDANT].bonuses.emplace_back( fheroes2::ArtifactBonusType::PARALYZE_SPELL_IMMUNITY ); artifactData[Artifact::PENDANT_DEATH].bonuses.emplace_back( fheroes2::ArtifactBonusType::HOLY_SPELL_IMMUNITY ); artifactData[Artifact::WAND_NEGATION].bonuses.emplace_back( fheroes2::ArtifactBonusType::DISPEL_SPELL_IMMUNITY ); artifactData[Artifact::GOLDEN_BOW].bonuses.emplace_back( fheroes2::ArtifactBonusType::NO_SHOOTING_PENALTY, 50 ); artifactData[Artifact::TELESCOPE].bonuses.emplace_back( fheroes2::ArtifactBonusType::AREA_REVEAL_DISTANCE, 1 ); artifactData[Artifact::STATESMAN_QUILL].bonuses.emplace_back( fheroes2::ArtifactBonusType::SURRENDER_COST_REDUCTION_PERCENT, 10 ); artifactData[Artifact::WIZARD_HAT].bonuses.emplace_back( fheroes2::ArtifactBonusType::EVERY_COMBAT_SPELL_DURATION, 10 ); artifactData[Artifact::POWER_RING].bonuses.emplace_back( fheroes2::ArtifactBonusType::SPELL_POINTS_DAILY_GENERATION, 2 ); artifactData[Artifact::AMMO_CART].bonuses.emplace_back( fheroes2::ArtifactBonusType::ENDLESS_AMMUNITION ); artifactData[Artifact::TAX_LIEN].curses.emplace_back( fheroes2::ArtifactCurseType::GOLD_PENALTY, 250 ); artifactData[Artifact::HIDEOUS_MASK].curses.emplace_back( fheroes2::ArtifactCurseType::NO_JOINING_ARMIES ); artifactData[Artifact::ENDLESS_POUCH_SULFUR].bonuses.emplace_back( fheroes2::ArtifactBonusType::SULFUR_INCOME, 1 ); artifactData[Artifact::ENDLESS_VIAL_MERCURY].bonuses.emplace_back( fheroes2::ArtifactBonusType::MERCURY_INCOME, 1 ); artifactData[Artifact::ENDLESS_POUCH_GEMS].bonuses.emplace_back( fheroes2::ArtifactBonusType::GEMS_INCOME, 1 ); artifactData[Artifact::ENDLESS_CORD_WOOD].bonuses.emplace_back( fheroes2::ArtifactBonusType::WOOD_INCOME, 1 ); artifactData[Artifact::ENDLESS_CART_ORE].bonuses.emplace_back( fheroes2::ArtifactBonusType::ORE_INCOME, 1 ); artifactData[Artifact::ENDLESS_POUCH_CRYSTAL].bonuses.emplace_back( fheroes2::ArtifactBonusType::CRYSTAL_INCOME, 1 ); artifactData[Artifact::SPIKED_HELM].bonuses.emplace_back( fheroes2::ArtifactBonusType::ATTACK_SKILL, 1 ); artifactData[Artifact::SPIKED_HELM].bonuses.emplace_back( fheroes2::ArtifactBonusType::DEFENCE_SKILL, 1 ); artifactData[Artifact::SPIKED_SHIELD].bonuses.emplace_back( fheroes2::ArtifactBonusType::ATTACK_SKILL, 2 ); artifactData[Artifact::SPIKED_SHIELD].bonuses.emplace_back( fheroes2::ArtifactBonusType::DEFENCE_SKILL, 2 ); artifactData[Artifact::WHITE_PEARL].bonuses.emplace_back( fheroes2::ArtifactBonusType::KNOWLEDGE_SKILL, 1 ); artifactData[Artifact::WHITE_PEARL].bonuses.emplace_back( fheroes2::ArtifactBonusType::SPELL_POWER_SKILL, 1 ); artifactData[Artifact::BLACK_PEARL].bonuses.emplace_back( fheroes2::ArtifactBonusType::KNOWLEDGE_SKILL, 2 ); artifactData[Artifact::BLACK_PEARL].bonuses.emplace_back( fheroes2::ArtifactBonusType::SPELL_POWER_SKILL, 2 ); artifactData[Artifact::MAGIC_BOOK].bonuses.emplace_back( fheroes2::ArtifactBonusType::NONE ); artifactData[Artifact::UNUSED_83].bonuses.emplace_back( fheroes2::ArtifactBonusType::NONE ); artifactData[Artifact::UNUSED_84].bonuses.emplace_back( fheroes2::ArtifactBonusType::NONE ); artifactData[Artifact::UNUSED_85].bonuses.emplace_back( fheroes2::ArtifactBonusType::NONE ); artifactData[Artifact::UNUSED_86].bonuses.emplace_back( fheroes2::ArtifactBonusType::NONE ); artifactData[Artifact::SPELL_SCROLL].bonuses.emplace_back( fheroes2::ArtifactBonusType::ADD_SPELL, Spell::NONE ); artifactData[Artifact::ARM_MARTYR].bonuses.emplace_back( fheroes2::ArtifactBonusType::SPELL_POWER_SKILL, 3 ); artifactData[Artifact::ARM_MARTYR].curses.emplace_back( fheroes2::ArtifactCurseType::UNDEAD_MORALE_PENALTY ); artifactData[Artifact::BREASTPLATE_ANDURAN].bonuses.emplace_back( fheroes2::ArtifactBonusType::DEFENCE_SKILL, 5 ); artifactData[Artifact::BROACH_SHIELDING].bonuses.emplace_back( fheroes2::ArtifactBonusType::ELEMENTAL_SPELL_DAMAGE_REDUCTION_PERCENT, 50 ); artifactData[Artifact::BROACH_SHIELDING].curses.emplace_back( fheroes2::ArtifactCurseType::SPELL_POWER_SKILL, 2 ); artifactData[Artifact::BATTLE_GARB].bonuses.emplace_back( fheroes2::ArtifactBonusType::ATTACK_SKILL, 5 ); artifactData[Artifact::BATTLE_GARB].bonuses.emplace_back( fheroes2::ArtifactBonusType::DEFENCE_SKILL, 5 ); artifactData[Artifact::BATTLE_GARB].bonuses.emplace_back( fheroes2::ArtifactBonusType::SPELL_POWER_SKILL, 5 ); artifactData[Artifact::BATTLE_GARB].bonuses.emplace_back( fheroes2::ArtifactBonusType::MAXIMUM_MORALE ); artifactData[Artifact::BATTLE_GARB].bonuses.emplace_back( fheroes2::ArtifactBonusType::MAXIMUM_LUCK ); artifactData[Artifact::BATTLE_GARB].bonuses.emplace_back( fheroes2::ArtifactBonusType::ADD_SPELL, Spell::TOWNPORTAL ); artifactData[Artifact::CRYSTAL_BALL].bonuses.emplace_back( fheroes2::ArtifactBonusType::VIEW_MONSTER_INFORMATION ); artifactData[Artifact::HEART_FIRE].bonuses.emplace_back( fheroes2::ArtifactBonusType::FIRE_SPELL_DAMAGE_REDUCTION_PERCENT, 50 ); artifactData[Artifact::HEART_FIRE].curses.emplace_back( fheroes2::ArtifactCurseType::COLD_SPELL_EXTRA_DAMAGE_PERCENT, 100 ); artifactData[Artifact::HEART_ICE].bonuses.emplace_back( fheroes2::ArtifactBonusType::COLD_SPELL_DAMAGE_REDUCTION_PERCENT, 50 ); artifactData[Artifact::HEART_ICE].curses.emplace_back( fheroes2::ArtifactCurseType::FIRE_SPELL_EXTRA_DAMAGE_PERCENT, 100 ); artifactData[Artifact::HELMET_ANDURAN].bonuses.emplace_back( fheroes2::ArtifactBonusType::SPELL_POWER_SKILL, 5 ); artifactData[Artifact::HOLY_HAMMER].bonuses.emplace_back( fheroes2::ArtifactBonusType::ATTACK_SKILL, 5 ); artifactData[Artifact::LEGENDARY_SCEPTER].bonuses.emplace_back( fheroes2::ArtifactBonusType::ATTACK_SKILL, 2 ); artifactData[Artifact::LEGENDARY_SCEPTER].bonuses.emplace_back( fheroes2::ArtifactBonusType::DEFENCE_SKILL, 2 ); artifactData[Artifact::LEGENDARY_SCEPTER].bonuses.emplace_back( fheroes2::ArtifactBonusType::KNOWLEDGE_SKILL, 2 ); artifactData[Artifact::LEGENDARY_SCEPTER].bonuses.emplace_back( fheroes2::ArtifactBonusType::SPELL_POWER_SKILL, 2 ); artifactData[Artifact::MASTHEAD].bonuses.emplace_back( fheroes2::ArtifactBonusType::SEA_BATTLE_MORALE_BOOST, 1 ); artifactData[Artifact::MASTHEAD].bonuses.emplace_back( fheroes2::ArtifactBonusType::SEA_BATTLE_LUCK_BOOST, 1 ); artifactData[Artifact::SPHERE_NEGATION].bonuses.emplace_back( fheroes2::ArtifactBonusType::DISABLE_ALL_SPELL_COMBAT_CASTING ); artifactData[Artifact::STAFF_WIZARDRY].bonuses.emplace_back( fheroes2::ArtifactBonusType::SPELL_POWER_SKILL, 5 ); artifactData[Artifact::SWORD_BREAKER].bonuses.emplace_back( fheroes2::ArtifactBonusType::DEFENCE_SKILL, 4 ); artifactData[Artifact::SWORD_BREAKER].bonuses.emplace_back( fheroes2::ArtifactBonusType::ATTACK_SKILL, 1 ); artifactData[Artifact::SWORD_ANDURAN].bonuses.emplace_back( fheroes2::ArtifactBonusType::ATTACK_SKILL, 5 ); artifactData[Artifact::SPADE_NECROMANCY].bonuses.emplace_back( fheroes2::ArtifactBonusType::NECROMANCY_SKILL, 10 ); for ( const fheroes2::ArtifactData & artifact : artifactData ) { if ( artifact.bonuses.empty() && artifact.curses.empty() ) { // Artifact info is not populated properly. An artifact with no effects cannot exist. assert( 0 ); } for ( const fheroes2::ArtifactBonus & bonus : artifact.bonuses ) { if ( bonus.value < 0 ) { assert( 0 ); } } for ( const fheroes2::ArtifactCurse & curse : artifact.curses ) { if ( curse.value < 0 ) { assert( 0 ); } } } } } namespace fheroes2 { bool isBonusCumulative( const ArtifactBonusType bonus ) { switch ( bonus ) { case ArtifactBonusType::KNOWLEDGE_SKILL: case ArtifactBonusType::SPELL_POWER_SKILL: case ArtifactBonusType::ATTACK_SKILL: case ArtifactBonusType::DEFENCE_SKILL: case ArtifactBonusType::GOLD_INCOME: case ArtifactBonusType::WOOD_INCOME: case ArtifactBonusType::MERCURY_INCOME: case ArtifactBonusType::ORE_INCOME: case ArtifactBonusType::SULFUR_INCOME: case ArtifactBonusType::CRYSTAL_INCOME: case ArtifactBonusType::GEMS_INCOME: return true; default: break; } return false; } bool isBonusMultiplied( const ArtifactBonusType bonus ) { switch ( bonus ) { case ArtifactBonusType::SURRENDER_COST_REDUCTION_PERCENT: case ArtifactBonusType::CURSE_SPELL_COST_REDUCTION_PERCENT: case ArtifactBonusType::BLESS_SPELL_COST_REDUCTION_PERCENT: case ArtifactBonusType::SUMMONING_SPELL_COST_REDUCTION_PERCENT: case ArtifactBonusType::MIND_INFLUENCE_SPELL_COST_REDUCTION_PERCENT: case ArtifactBonusType::COLD_SPELL_DAMAGE_REDUCTION_PERCENT: case ArtifactBonusType::FIRE_SPELL_DAMAGE_REDUCTION_PERCENT: case ArtifactBonusType::LIGHTNING_SPELL_DAMAGE_REDUCTION_PERCENT: case ArtifactBonusType::ELEMENTAL_SPELL_DAMAGE_REDUCTION_PERCENT: case ArtifactBonusType::HYPNOTIZE_SPELL_EXTRA_EFFECTIVENESS_PERCENT: case ArtifactBonusType::COLD_SPELL_EXTRA_EFFECTIVENESS_PERCENT: case ArtifactBonusType::FIRE_SPELL_EXTRA_EFFECTIVENESS_PERCENT: case ArtifactBonusType::LIGHTNING_SPELL_EXTRA_EFFECTIVENESS_PERCENT: case ArtifactBonusType::RESURRECT_SPELL_EXTRA_EFFECTIVENESS_PERCENT: case ArtifactBonusType::SUMMONING_SPELL_EXTRA_EFFECTIVENESS_PERCENT: return true; default: break; } return false; } bool isBonusUnique( const ArtifactBonusType bonus ) { switch ( bonus ) { case ArtifactBonusType::CURSE_SPELL_IMMUNITY: case ArtifactBonusType::HYPNOTIZE_SPELL_IMMUNITY: case ArtifactBonusType::DEATH_SPELL_IMMUNITY: case ArtifactBonusType::BERSERK_SPELL_IMMUNITY: case ArtifactBonusType::BLIND_SPELL_IMMUNITY: case ArtifactBonusType::PARALYZE_SPELL_IMMUNITY: case ArtifactBonusType::HOLY_SPELL_IMMUNITY: case ArtifactBonusType::DISPEL_SPELL_IMMUNITY: case ArtifactBonusType::ENDLESS_AMMUNITION: case ArtifactBonusType::NO_SHOOTING_PENALTY: case ArtifactBonusType::VIEW_MONSTER_INFORMATION: case ArtifactBonusType::DISABLE_ALL_SPELL_COMBAT_CASTING: return true; default: break; } return false; } bool isCurseCumulative( const ArtifactCurseType curse ) { switch ( curse ) { case ArtifactCurseType::SPELL_POWER_SKILL: case ArtifactCurseType::GOLD_PENALTY: return true; default: break; } return false; } bool isCurseMultiplied( const ArtifactCurseType curse ) { switch ( curse ) { case ArtifactCurseType::FIRE_SPELL_EXTRA_DAMAGE_PERCENT: case ArtifactCurseType::COLD_SPELL_EXTRA_DAMAGE_PERCENT: return true; default: break; } return false; } bool isCurseUnique( const ArtifactCurseType curse ) { switch ( curse ) { case ArtifactCurseType::NO_JOINING_ARMIES: case ArtifactCurseType::UNDEAD_MORALE_PENALTY: return true; default: break; } return false; } std::string ArtifactData::getDescription( const int extraParameter ) const { std::string description( _( baseDescription ) ); StringReplace( description, "%{name}", _( name ) ); std::vector::const_iterator foundBonus = std::find( bonuses.begin(), bonuses.end(), ArtifactBonus( ArtifactBonusType::ADD_SPELL ) ); if ( foundBonus != bonuses.end() ) { if ( foundBonus->value == Spell::NONE ) { if ( extraParameter == Spell::NONE ) { // This is a case when artifact description is viewed from list in Battle Only mode or in Editor. StringReplace( description, "%{spell}", _( "spellBonus|selected by user" ) ); } else { StringReplace( description, "%{spell}", Spell( extraParameter ).GetName() ); } } else { StringReplace( description, "%{spell}", Spell( foundBonus->value ).GetName() ); } } else if ( !bonuses.empty() ) { StringReplace( description, "%{count}", bonuses.front().value ); } else if ( !curses.empty() ) { StringReplace( description, "%{count}", curses.front().value ); } else { // An artifact that does nothing? Check your logic! assert( 0 ); } return description; } const ArtifactData & getArtifactData( const int artifactId ) { if ( artifactData.empty() ) { populateArtifactData(); } if ( artifactId < 0 || static_cast( artifactId ) >= artifactData.size() ) { // Invalid artifact ID! assert( 0 ); return artifactData.front(); } return artifactData[artifactId]; } std::string getArtifactDescription( const int artifactId ) { const ArtifactData & data = getArtifactData( artifactId ); std::ostringstream os; os << "----------" << std::endl; os << "Name: " << data.name << std::endl; os << "Description: " << data.getDescription( Spell::RANDOM ) << std::endl; if ( data.discoveryEventDescription != nullptr ) { os << "Discovery event description: " << data.discoveryEventDescription << std::endl; } else { os << "No discovery event description" << std::endl; } if ( !data.bonuses.empty() ) { os << "Bonuses:" << std::endl; for ( const ArtifactBonus & bonus : data.bonuses ) { os << " "; if ( isBonusCumulative( bonus.type ) ) { os << "[ cumulative ] "; } else if ( isBonusMultiplied( bonus.type ) ) { os << "[ multiplied ] "; } else if ( isBonusUnique( bonus.type ) ) { os << "[ unique ] "; } else { os << "[ cumulative per artifact type ] "; } switch ( bonus.type ) { case ArtifactBonusType::NONE: os << "None" << std::endl; break; case ArtifactBonusType::KNOWLEDGE_SKILL: os << "Add " << bonus.value << " to hero's Knowledge Skill" << std::endl; break; case ArtifactBonusType::ATTACK_SKILL: os << "Add " << bonus.value << " to hero's Attack Skill" << std::endl; break; case ArtifactBonusType::DEFENCE_SKILL: os << "Add " << bonus.value << " to hero's Defence Skill" << std::endl; break; case ArtifactBonusType::SPELL_POWER_SKILL: os << "Add " << bonus.value << " to hero's Spell Power Skill" << std::endl; break; case ArtifactBonusType::GOLD_INCOME: os << "Increase daily kingdom's income by " << bonus.value << " gold" << std::endl; break; case ArtifactBonusType::WOOD_INCOME: os << "Increase daily kingdom's income by " << bonus.value << " wood" << std::endl; break; case ArtifactBonusType::MERCURY_INCOME: os << "Increase daily kingdom's income by " << bonus.value << " mercury" << std::endl; break; case ArtifactBonusType::ORE_INCOME: os << "Increase daily kingdom's income by " << bonus.value << " ore" << std::endl; break; case ArtifactBonusType::SULFUR_INCOME: os << "Increase daily kingdom's income by " << bonus.value << " sulfur" << std::endl; break; case ArtifactBonusType::CRYSTAL_INCOME: os << "Increase daily kingdom's income by " << bonus.value << " crystal" << std::endl; break; case ArtifactBonusType::GEMS_INCOME: os << "Increase daily kingdom's income by " << bonus.value << " gem" << std::endl; break; case ArtifactBonusType::SPELL_POINTS_DAILY_GENERATION: os << "Restore " << bonus.value << " spell points for a hero" << std::endl; break; case ArtifactBonusType::LAND_MOBILITY: os << "Add " << bonus.value << " move points to a hero on Land" << std::endl; break; case ArtifactBonusType::SEA_MOBILITY: os << "Add " << bonus.value << " move points to a hero on Sea" << std::endl; break; case ArtifactBonusType::MORALE: os << "Increase army Morale by " << bonus.value << std::endl; break; case ArtifactBonusType::LUCK: os << "Increase army Luck by " << bonus.value << std::endl; break; case ArtifactBonusType::EVERY_COMBAT_SPELL_DURATION: os << "Increase every combat spell duration by " << bonus.value << " rounds" << std::endl; break; case ArtifactBonusType::SURRENDER_COST_REDUCTION_PERCENT: os << "Reduce Surrender cost to " << bonus.value << " percent" << std::endl; break; case ArtifactBonusType::CURSE_SPELL_COST_REDUCTION_PERCENT: os << "Reduce Curse spell cost by " << bonus.value << " percent" << std::endl; break; case ArtifactBonusType::BLESS_SPELL_COST_REDUCTION_PERCENT: os << "Reduce Bless spell cost by " << bonus.value << " percent" << std::endl; break; case ArtifactBonusType::SUMMONING_SPELL_COST_REDUCTION_PERCENT: os << "Reduce Summoning spell cost by " << bonus.value << " percent" << std::endl; break; case ArtifactBonusType::MIND_INFLUENCE_SPELL_COST_REDUCTION_PERCENT: os << "Reduce Mind Influence related spell cost by " << bonus.value << " percent" << std::endl; break; case ArtifactBonusType::COLD_SPELL_DAMAGE_REDUCTION_PERCENT: os << "Reduce Cold spell cost by " << bonus.value << " percent" << std::endl; break; case ArtifactBonusType::FIRE_SPELL_DAMAGE_REDUCTION_PERCENT: os << "Reduce Fire spell cost by " << bonus.value << " percent" << std::endl; break; case ArtifactBonusType::LIGHTNING_SPELL_DAMAGE_REDUCTION_PERCENT: os << "Reduce Lightning spell cost by " << bonus.value << " percent" << std::endl; break; case ArtifactBonusType::ELEMENTAL_SPELL_DAMAGE_REDUCTION_PERCENT: os << "Reduce Elemental Storm and Armageddon spell cost by " << bonus.value << " percent" << std::endl; break; case ArtifactBonusType::HYPNOTIZE_SPELL_EXTRA_EFFECTIVENESS_PERCENT: os << "Improve Hypnotize spell effectiveness by " << bonus.value << " percent" << std::endl; break; case ArtifactBonusType::COLD_SPELL_EXTRA_EFFECTIVENESS_PERCENT: os << "Improve Cold spell effectiveness by " << bonus.value << " percent" << std::endl; break; case ArtifactBonusType::FIRE_SPELL_EXTRA_EFFECTIVENESS_PERCENT: os << "Improve Fire spell effectiveness by " << bonus.value << " percent" << std::endl; break; case ArtifactBonusType::LIGHTNING_SPELL_EXTRA_EFFECTIVENESS_PERCENT: os << "Improve Lightning spell effectiveness by " << bonus.value << " percent" << std::endl; break; case ArtifactBonusType::RESURRECT_SPELL_EXTRA_EFFECTIVENESS_PERCENT: os << "Improve Resurrection spell effectiveness by " << bonus.value << " percent" << std::endl; break; case ArtifactBonusType::SUMMONING_SPELL_EXTRA_EFFECTIVENESS_PERCENT: os << "Improve Summoning spell effectiveness by " << bonus.value << " percent" << std::endl; break; case ArtifactBonusType::CURSE_SPELL_IMMUNITY: os << "Add immunity to Curse spells" << std::endl; break; case ArtifactBonusType::HYPNOTIZE_SPELL_IMMUNITY: os << "Add immunity to Hypnotize spells" << std::endl; break; case ArtifactBonusType::DEATH_SPELL_IMMUNITY: os << "Add immunity to Death spells" << std::endl; break; case ArtifactBonusType::BERSERK_SPELL_IMMUNITY: os << "Add immunity to Berserk spells" << std::endl; break; case ArtifactBonusType::BLIND_SPELL_IMMUNITY: os << "Add immunity to Blind spells" << std::endl; break; case ArtifactBonusType::PARALYZE_SPELL_IMMUNITY: os << "Add immunity to Paralyze spells" << std::endl; break; case ArtifactBonusType::HOLY_SPELL_IMMUNITY: os << "Add immunity to Holy spells" << std::endl; break; case ArtifactBonusType::DISPEL_SPELL_IMMUNITY: os << "Add immunity to Dispel spells" << std::endl; break; case ArtifactBonusType::ENDLESS_AMMUNITION: os << "Shooters have unlimited number of shots" << std::endl; break; case ArtifactBonusType::NO_SHOOTING_PENALTY: os << "Shooters have no penalty over obstacles" << std::endl; break; case ArtifactBonusType::EXTRA_CATAPULT_SHOTS: os << "Add " << bonus.value << " shots for catapult" << std::endl; break; case ArtifactBonusType::AREA_REVEAL_DISTANCE: os << "Increase fog reveal area by " << bonus.value << std::endl; break; case ArtifactBonusType::ADD_SPELL: os << "Add '" << Spell( bonus.value ).GetName() << "' spell to hero's list of spells" << std::endl; break; case ArtifactBonusType::VIEW_MONSTER_INFORMATION: os << "Add ability to view monster information" << std::endl; break; case ArtifactBonusType::SEA_BATTLE_MORALE_BOOST: os << "Increase army's Morale by " << bonus.value << " when in Sea" << std::endl; break; case ArtifactBonusType::SEA_BATTLE_LUCK_BOOST: os << "Increase army's Luck by " << bonus.value << " when in Sea" << std::endl; break; case ArtifactBonusType::DISABLE_ALL_SPELL_COMBAT_CASTING: os << "Disable all spell casting during battle" << std::endl; break; case ArtifactBonusType::NECROMANCY_SKILL: os << "Increase Necromancy Skill by " << bonus.value << " percent" << std::endl; break; case ArtifactBonusType::MAXIMUM_MORALE: os << "Gives the army maximum Morale." << std::endl; break; case ArtifactBonusType::MAXIMUM_LUCK: os << "Gives the army maximum Luck." << std::endl; break; default: // Did you add a new bonus type? Add the logic above! assert( 0 ); break; } } } if ( !data.curses.empty() ) { os << "Curses:" << std::endl; for ( const ArtifactCurse & curse : data.curses ) { os << " "; if ( isCurseCumulative( curse.type ) ) { os << "[ cumulative ] "; } else if ( isCurseMultiplied( curse.type ) ) { os << "[ multiplied ] "; } else if ( isCurseUnique( curse.type ) ) { os << "[ unique ] "; } else { os << "[ cumulative per artifact type ] "; } switch ( curse.type ) { case ArtifactCurseType::NO_JOINING_ARMIES: os << "No army can join hero" << std::endl; break; case ArtifactCurseType::MORALE: os << "Decreases army's Morale by " << curse.value << std::endl; break; case ArtifactCurseType::UNDEAD_MORALE_PENALTY: os << "Add Undead Penalty to army's Morale" << std::endl; break; case ArtifactCurseType::GOLD_PENALTY: os << "Deducts " << curse.value << " gold from kingdom daily" << std::endl; break; case ArtifactCurseType::SPELL_POWER_SKILL: os << "Reduces Spell Power by " << curse.value << std::endl; break; case ArtifactCurseType::FIRE_SPELL_EXTRA_DAMAGE_PERCENT: os << "Increases Damage from Fire spells by " << curse.value << " percent" << std::endl; break; case ArtifactCurseType::COLD_SPELL_EXTRA_DAMAGE_PERCENT: os << "Increases Damage from Cold spells by " << curse.value << " percent" << std::endl; break; default: // Did you add a new curse type? Add the logic above! assert( 0 ); break; } } } return os.str(); } } fheroes2-1.0.12+dfsg/src/fheroes2/resource/artifact_info.h000066400000000000000000000151601456075706000233600ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include namespace fheroes2 { enum class ArtifactBonusType : int32_t { NONE, // These bonuses are cumulative meaning each copy of an artifact having it will add to the total value. KNOWLEDGE_SKILL, ATTACK_SKILL, DEFENCE_SKILL, SPELL_POWER_SKILL, GOLD_INCOME, WOOD_INCOME, MERCURY_INCOME, ORE_INCOME, SULFUR_INCOME, CRYSTAL_INCOME, GEMS_INCOME, // These bonuses are unique per artifact type but cumulative across multiple artifact types. MORALE, LUCK, SEA_BATTLE_MORALE_BOOST, SEA_BATTLE_LUCK_BOOST, LAND_MOBILITY, SEA_MOBILITY, SPELL_POINTS_DAILY_GENERATION, EVERY_COMBAT_SPELL_DURATION, EXTRA_CATAPULT_SHOTS, AREA_REVEAL_DISTANCE, ADD_SPELL, NECROMANCY_SKILL, // These bonuses are unique per artifact type but the effect is multiplied with different artifact types. SURRENDER_COST_REDUCTION_PERCENT, CURSE_SPELL_COST_REDUCTION_PERCENT, BLESS_SPELL_COST_REDUCTION_PERCENT, SUMMONING_SPELL_COST_REDUCTION_PERCENT, MIND_INFLUENCE_SPELL_COST_REDUCTION_PERCENT, COLD_SPELL_DAMAGE_REDUCTION_PERCENT, FIRE_SPELL_DAMAGE_REDUCTION_PERCENT, LIGHTNING_SPELL_DAMAGE_REDUCTION_PERCENT, ELEMENTAL_SPELL_DAMAGE_REDUCTION_PERCENT, HYPNOTIZE_SPELL_EXTRA_EFFECTIVENESS_PERCENT, COLD_SPELL_EXTRA_EFFECTIVENESS_PERCENT, FIRE_SPELL_EXTRA_EFFECTIVENESS_PERCENT, LIGHTNING_SPELL_EXTRA_EFFECTIVENESS_PERCENT, RESURRECT_SPELL_EXTRA_EFFECTIVENESS_PERCENT, SUMMONING_SPELL_EXTRA_EFFECTIVENESS_PERCENT, // All bonuses below are unique per hero. They do not require any extra values. Only one artifact with such bonus will be used. CURSE_SPELL_IMMUNITY, HYPNOTIZE_SPELL_IMMUNITY, DEATH_SPELL_IMMUNITY, BERSERK_SPELL_IMMUNITY, BLIND_SPELL_IMMUNITY, PARALYZE_SPELL_IMMUNITY, HOLY_SPELL_IMMUNITY, DISPEL_SPELL_IMMUNITY, ENDLESS_AMMUNITION, NO_SHOOTING_PENALTY, VIEW_MONSTER_INFORMATION, DISABLE_ALL_SPELL_COMBAT_CASTING, MAXIMUM_MORALE, MAXIMUM_LUCK }; enum class ArtifactCurseType : int32_t { // These curses are cumulative meaning each copy of an artifact having it will add to the total value. GOLD_PENALTY, SPELL_POWER_SKILL, // These curses are unique per artifact type but cumulative across multiple artifact types. MORALE, // These curses are unique per artifact type but the effect is multiplied with different artifact types. FIRE_SPELL_EXTRA_DAMAGE_PERCENT, COLD_SPELL_EXTRA_DAMAGE_PERCENT, // All curses below are unique per hero. They do not require any extra values. Only one artifact with such curse will be used. NO_JOINING_ARMIES, UNDEAD_MORALE_PENALTY }; struct ArtifactBonus { explicit ArtifactBonus( const ArtifactBonusType type_ ) : type( type_ ) , value( 0 ) { // Do nothing. } ArtifactBonus( const ArtifactBonusType type_, const int32_t value_ ) : type( type_ ) , value( value_ ) { // Do nothing. } bool operator==( const ArtifactBonus & another ) const { return type == another.type; } ArtifactBonusType type; int32_t value; }; struct ArtifactCurse { explicit ArtifactCurse( const ArtifactCurseType type_ ) : type( type_ ) , value( 0 ) { // Do nothing. } ArtifactCurse( const ArtifactCurseType type_, const int32_t value_ ) : type( type_ ) , value( value_ ) { // Do nothing. } bool operator==( const ArtifactCurse & another ) const { return type == another.type; } ArtifactCurseType type; int32_t value; }; // This is the only bonus type which is cumulative even for several copies of the same artifact. bool isBonusCumulative( const ArtifactBonusType bonus ); bool isBonusMultiplied( const ArtifactBonusType bonus ); bool isBonusUnique( const ArtifactBonusType bonus ); // This is the only curse type which is cumulative even for several copies of the same artifact. bool isCurseCumulative( const ArtifactCurseType curse ); bool isCurseMultiplied( const ArtifactCurseType curse ); bool isCurseUnique( const ArtifactCurseType curse ); struct ArtifactData { const char * name; // Do not use this member directly. Use getDescription() method. const char * baseDescription; const char * discoveryEventDescription; std::vector bonuses; std::vector curses; std::string getDescription( const int extraParameter ) const; }; const ArtifactData & getArtifactData( const int artifactId ); std::string getArtifactDescription( const int artifactId ); } fheroes2-1.0.12+dfsg/src/fheroes2/resource/artifact_ultimate.cpp000066400000000000000000000063161456075706000246070ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2011 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "artifact_ultimate.h" #include "interface_gamearea.h" #include "rand.h" #include "serialize.h" UltimateArtifact::UltimateArtifact() : _index( -1 ) , _isFound( false ) {} void UltimateArtifact::Set( const int32_t position, const Artifact & a ) { Artifact & art = *this; art = a.isValid() ? a : Artifact::Rand( Artifact::ART_ULTIMATE ); _index = position; _isFound = false; // The Ultimate Artifact should not always be shown right in the center of the puzzle to make it // harder to find, but it should be shown behind one of the last four central pieces of the puzzle. _offset.x = Rand::Get( 0, 4 ) - 2; _offset.y = Rand::Get( 0, 2 ) - 1; } fheroes2::Image UltimateArtifact::GetPuzzleMapSurface() const { return Interface::GameArea::GenerateUltimateArtifactAreaSurface( _index, _offset ); } const Artifact & UltimateArtifact::GetArtifact() const { return *this; } bool UltimateArtifact::isPosition( const int32_t position ) const { return 0 <= _index && position == _index; } void UltimateArtifact::Reset() { Artifact::Reset(); _offset = fheroes2::Point(); _index = -1; _isFound = false; } StreamBase & operator<<( StreamBase & msg, const UltimateArtifact & ultimate ) { return msg << static_cast( ultimate ) << ultimate._index << ultimate._isFound << ultimate._offset; } StreamBase & operator>>( StreamBase & msg, UltimateArtifact & ultimate ) { Artifact & artifact = ultimate; return msg >> artifact >> ultimate._index >> ultimate._isFound >> ultimate._offset; } fheroes2-1.0.12+dfsg/src/fheroes2/resource/artifact_ultimate.h000066400000000000000000000052401456075706000242470ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2011 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2ARTIFACT_ULTIMATE_H #define H2ARTIFACT_ULTIMATE_H #include #include "artifact.h" #include "image.h" #include "math_base.h" class StreamBase; class UltimateArtifact : public Artifact { public: UltimateArtifact(); bool isPosition( const int32_t position ) const; int32_t getPosition() const { return _index; } bool isFound() const { return _isFound; } void markAsFound() { _isFound = true; } void Set( const int32_t position, const Artifact & ); void Reset(); fheroes2::Image GetPuzzleMapSurface() const; const Artifact & GetArtifact() const; private: friend StreamBase & operator<<( StreamBase &, const UltimateArtifact & ); friend StreamBase & operator>>( StreamBase &, UltimateArtifact & ); fheroes2::Point _offset; int32_t _index; bool _isFound; }; StreamBase & operator<<( StreamBase &, const UltimateArtifact & ); StreamBase & operator>>( StreamBase &, UltimateArtifact & ); #endif fheroes2-1.0.12+dfsg/src/fheroes2/resource/resource.cpp000066400000000000000000000427361456075706000227430ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "resource.h" #include #include #include #include #include #include #include #include #include "agg_image.h" #include "icn.h" #include "image.h" #include "logging.h" #include "rand.h" #include "screen.h" #include "serialize.h" #include "translations.h" #include "ui_text.h" Funds::Funds() : wood( 0 ) , mercury( 0 ) , ore( 0 ) , sulfur( 0 ) , crystal( 0 ) , gems( 0 ) , gold( 0 ) {} Funds::Funds( int32_t _ore, int32_t _wood, int32_t _mercury, int32_t _sulfur, int32_t _crystal, int32_t _gems, int32_t _gold ) : wood( _wood ) , mercury( _mercury ) , ore( _ore ) , sulfur( _sulfur ) , crystal( _crystal ) , gems( _gems ) , gold( _gold ) {} Funds::Funds( int rs, uint32_t count ) : wood( 0 ) , mercury( 0 ) , ore( 0 ) , sulfur( 0 ) , crystal( 0 ) , gems( 0 ) , gold( 0 ) { switch ( rs ) { case Resource::ORE: ore = count; break; case Resource::WOOD: wood = count; break; case Resource::MERCURY: mercury = count; break; case Resource::SULFUR: sulfur = count; break; case Resource::GEMS: gems = count; break; case Resource::CRYSTAL: crystal = count; break; case Resource::GOLD: gold = count; break; default: if ( count > 0 ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Unknown resource is being added to Funds class. Ignore it." ) } break; } } Funds::Funds( const cost_t & cost ) : wood( cost.wood ) , mercury( cost.mercury ) , ore( cost.ore ) , sulfur( cost.sulfur ) , crystal( cost.crystal ) , gems( cost.gems ) , gold( cost.gold ) {} int Resource::Rand( const bool includeGold ) { switch ( Rand::Get( 1, ( includeGold ? 7 : 6 ) ) ) { case 1: return Resource::WOOD; case 2: return Resource::MERCURY; case 3: return Resource::ORE; case 4: return Resource::SULFUR; case 5: return Resource::CRYSTAL; case 6: return Resource::GEMS; case 7: return Resource::GOLD; default: break; } return Resource::UNKNOWN; } int32_t * Funds::GetPtr( int rs ) { switch ( rs ) { case Resource::ORE: return &ore; case Resource::WOOD: return &wood; case Resource::MERCURY: return &mercury; case Resource::SULFUR: return &sulfur; case Resource::GEMS: return &gems; case Resource::CRYSTAL: return &crystal; case Resource::GOLD: return &gold; default: break; } return nullptr; } int32_t Funds::Get( int rs ) const { switch ( rs ) { case Resource::ORE: return ore; case Resource::WOOD: return wood; case Resource::MERCURY: return mercury; case Resource::SULFUR: return sulfur; case Resource::GEMS: return gems; case Resource::CRYSTAL: return crystal; case Resource::GOLD: return gold; default: break; } return 0; } Funds & Funds::operator=( const cost_t & cost ) { wood = cost.wood; mercury = cost.mercury; ore = cost.ore; sulfur = cost.sulfur; crystal = cost.crystal; gems = cost.gems; gold = cost.gold; return *this; } Funds Funds::operator+( const Funds & pm ) const { Funds res; res.wood = wood + pm.wood; res.mercury = mercury + pm.mercury; res.ore = ore + pm.ore; res.sulfur = sulfur + pm.sulfur; res.crystal = crystal + pm.crystal; res.gems = gems + pm.gems; res.gold = gold + pm.gold; return res; } Funds & Funds::operator+=( const Funds & pm ) { wood += pm.wood; mercury += pm.mercury; ore += pm.ore; sulfur += pm.sulfur; crystal += pm.crystal; gems += pm.gems; gold += pm.gold; return *this; } Funds Funds::operator-( const Funds & pm ) const { Funds res; res.wood = wood - pm.wood; res.mercury = mercury - pm.mercury; res.ore = ore - pm.ore; res.sulfur = sulfur - pm.sulfur; res.crystal = crystal - pm.crystal; res.gems = gems - pm.gems; res.gold = gold - pm.gold; return res; } Funds Funds::operator/( const int32_t div ) const { if ( div == 0 ) { assert( 0 ); return {}; } Funds res; res.wood = wood / div; res.mercury = mercury / div; res.ore = ore / div; res.sulfur = sulfur / div; res.crystal = crystal / div; res.gems = gems / div; res.gold = gold / div; return res; } Funds & Funds::operator-=( const Funds & pm ) { wood -= pm.wood; mercury -= pm.mercury; ore -= pm.ore; sulfur -= pm.sulfur; crystal -= pm.crystal; gems -= pm.gems; gold -= pm.gold; return *this; } Funds Funds::max( const Funds & other ) const { Funds max; max.wood = std::max( wood, other.wood ); max.mercury = std::max( mercury, other.mercury ); max.ore = std::max( ore, other.ore ); max.sulfur = std::max( sulfur, other.sulfur ); max.crystal = std::max( crystal, other.crystal ); max.gems = std::max( gems, other.gems ); max.gold = std::max( gold, other.gold ); return max; } int Funds::getLowestQuotient( const Funds & divisor ) const { int result = ( divisor.gold ) ? gold / divisor.gold : gold; const auto divisionLambda = [&result]( int left, int right ) { if ( right > 0 ) { const int value = left / right; if ( value < result ) result = value; } }; divisionLambda( wood, divisor.wood ); divisionLambda( ore, divisor.ore ); divisionLambda( crystal, divisor.crystal ); divisionLambda( gems, divisor.gems ); divisionLambda( mercury, divisor.mercury ); divisionLambda( sulfur, divisor.sulfur ); return result; } Funds Funds::operator*( uint32_t mul ) const { Funds res; res.wood = wood * mul; res.mercury = mercury * mul; res.ore = ore * mul; res.sulfur = sulfur * mul; res.crystal = crystal * mul; res.gems = gems * mul; res.gold = gold * mul; return res; } Funds & Funds::operator*=( uint32_t mul ) { wood *= mul; mercury *= mul; ore *= mul; sulfur *= mul; crystal *= mul; gems *= mul; gold *= mul; return *this; } Funds & Funds::operator/=( const int32_t div ) { if ( div == 0 ) { assert( 0 ); return *this; } wood /= div; mercury /= div; ore /= div; sulfur /= div; crystal /= div; gems /= div; gold /= div; return *this; } bool Funds::operator==( const Funds & other ) const { return std::tie( wood, mercury, ore, sulfur, crystal, gems, gold ) == std::tie( other.wood, other.mercury, other.ore, other.sulfur, other.crystal, other.gems, other.gold ); } bool Funds::operator>=( const Funds & other ) const { return wood >= other.wood && mercury >= other.mercury && ore >= other.ore && sulfur >= other.sulfur && crystal >= other.crystal && gems >= other.gems && gold >= other.gold; } std::string Funds::String() const { std::ostringstream os; os << "ore: " << ore << ", wood: " << wood << ", mercury: " << mercury << ", sulfur: " << sulfur << ", crystal: " << crystal << ", gems: " << gems << ", gold: " << gold; return os.str(); } const char * Resource::String( int resource ) { switch ( resource ) { case Resource::WOOD: return _( "Wood" ); case Resource::MERCURY: return _( "Mercury" ); case Resource::ORE: return _( "Ore" ); case Resource::SULFUR: return _( "Sulfur" ); case Resource::CRYSTAL: return _( "Crystal" ); case Resource::GEMS: return _( "Gems" ); case Resource::GOLD: return _( "Gold" ); default: break; } return "Unknown"; } const char * Resource::getDescription() { return _( "There are seven resources in Heroes 2, used to build and improves castles, purchase troops and recruit heroes. Gold is the most common, required for " "virtually everything. Wood and ore are used for most buildings. Gems, Mercury, Sulfur and Crystal are rare magical resources used for the most " "powerful creatures and buildings." ); } uint8_t Resource::GetIndexSprite( int resource ) { switch ( resource ) { case Resource::WOOD: return 1; case Resource::MERCURY: return 3; case Resource::ORE: return 5; case Resource::SULFUR: return 7; case Resource::CRYSTAL: return 9; case Resource::GEMS: return 11; case Resource::GOLD: return 13; default: DEBUG_LOG( DBG_GAME, DBG_WARN, "Unknown resource type." ) break; } return 0; } int Resource::FromIndexSprite( uint32_t index ) { switch ( index ) { case 1: return WOOD; case 3: return MERCURY; case 5: return ORE; case 7: return SULFUR; case 9: return CRYSTAL; case 11: return GEMS; case 13: return GOLD; default: break; } return UNKNOWN; } uint32_t Resource::getIconIcnIndex( const int resourceType ) { switch ( resourceType ) { case Resource::WOOD: return 0; case Resource::MERCURY: return 1; case Resource::ORE: return 2; case Resource::SULFUR: return 3; case Resource::CRYSTAL: return 4; case Resource::GEMS: return 5; case Resource::GOLD: return 6; default: // You are passing not a single resource type or an invalid one. Fix it! assert( 0 ); DEBUG_LOG( DBG_GAME, DBG_WARN, "unknown resource" ) break; } return 0; } int Resource::getResourceTypeFromIconIndex( const uint32_t index ) { switch ( index ) { case 0: return WOOD; case 1: return MERCURY; case 2: return ORE; case 3: return SULFUR; case 4: return CRYSTAL; case 5: return GEMS; case 6: return GOLD; default: // ICN index is wrong! assert( 0 ); break; } return UNKNOWN; } Funds Resource::CalculateEventResourceUpdate( const Funds & currentFunds, const Funds & eventFunds ) { Funds funds = currentFunds + eventFunds; funds.Trim(); return funds - currentFunds; } int Funds::GetValidItems() const { int rs = 0; if ( wood ) rs |= Resource::WOOD; if ( ore ) rs |= Resource::ORE; if ( mercury ) rs |= Resource::MERCURY; if ( sulfur ) rs |= Resource::SULFUR; if ( crystal ) rs |= Resource::CRYSTAL; if ( gems ) rs |= Resource::GEMS; if ( gold ) rs |= Resource::GOLD; return rs; } uint32_t Funds::GetValidItemsCount() const { uint32_t result = 0; if ( wood ) ++result; if ( ore ) ++result; if ( mercury ) ++result; if ( sulfur ) ++result; if ( crystal ) ++result; if ( gems ) ++result; if ( gold ) ++result; return result; } void Funds::Trim() { if ( wood < 0 ) wood = 0; if ( ore < 0 ) ore = 0; if ( mercury < 0 ) mercury = 0; if ( sulfur < 0 ) sulfur = 0; if ( crystal < 0 ) crystal = 0; if ( gems < 0 ) gems = 0; if ( gold < 0 ) gold = 0; } std::pair Funds::getFirstValidResource() const { if ( wood > 0 ) { return { Resource::WOOD, wood }; } if ( ore > 0 ) { return { Resource::ORE, ore }; } if ( mercury > 0 ) { return { Resource::MERCURY, mercury }; } if ( sulfur > 0 ) { return { Resource::SULFUR, sulfur }; } if ( crystal > 0 ) { return { Resource::CRYSTAL, crystal }; } if ( gems > 0 ) { return { Resource::GEMS, gems }; } if ( gold > 0 ) { return { Resource::GOLD, gold }; } // We shouldn't reach this point. Make sure that you are calling this method for valid Funds. assert( 0 ); return { Resource::UNKNOWN, 0 }; } void Funds::Reset() { wood = 0; ore = 0; mercury = 0; sulfur = 0; crystal = 0; gems = 0; gold = 0; } Resource::BoxSprite::BoxSprite( const Funds & f, int32_t width_ ) : fheroes2::Rect( 0, 0, width_, 0 ) , rs( f ) { const uint32_t count = rs.GetValidItemsCount(); height = 4 > count ? 45 : ( 7 > count ? 90 : 135 ); } const fheroes2::Rect & Resource::BoxSprite::GetArea() const { return *this; } void Resource::BoxSprite::SetPos( int32_t px, int32_t py ) { x = px; y = py; } void RedrawResourceSprite( const fheroes2::Image & sf, const fheroes2::Point & pos, int32_t count, int32_t width, int32_t offset, int32_t value ) { fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::Point dst_pt( pos.x + width / 2 + count * width, pos.y + offset ); fheroes2::Blit( sf, display, dst_pt.x - sf.width() / 2, dst_pt.y - sf.height() ); const fheroes2::Text text{ std::to_string( value ), fheroes2::FontType::smallWhite() }; text.draw( dst_pt.x - text.width() / 2, dst_pt.y + 4, display ); } void Resource::BoxSprite::Redraw() const { std::vector> valueVsSprite; if ( rs.wood ) valueVsSprite.emplace_back( rs.wood, 0 ); if ( rs.ore ) valueVsSprite.emplace_back( rs.ore, 2 ); if ( rs.mercury ) valueVsSprite.emplace_back( rs.mercury, 1 ); if ( rs.sulfur ) valueVsSprite.emplace_back( rs.sulfur, 3 ); if ( rs.crystal ) valueVsSprite.emplace_back( rs.crystal, 4 ); if ( rs.gems ) valueVsSprite.emplace_back( rs.gems, 5 ); if ( rs.gold ) valueVsSprite.emplace_back( rs.gold, 6 ); uint32_t offsetY = 35; size_t id = 0; while ( valueVsSprite.size() - id > 2 ) { const uint32_t width_ = width / 3; const fheroes2::Sprite & res1 = fheroes2::AGG::GetICN( ICN::RESOURCE, valueVsSprite[id].second ); const fheroes2::Sprite & res2 = fheroes2::AGG::GetICN( ICN::RESOURCE, valueVsSprite[id + 1].second ); const fheroes2::Sprite & res3 = fheroes2::AGG::GetICN( ICN::RESOURCE, valueVsSprite[id + 2].second ); RedrawResourceSprite( res1, { x, y }, 0, width_, offsetY, valueVsSprite[id].first ); RedrawResourceSprite( res2, { x, y }, 1, width_, offsetY, valueVsSprite[id + 1].first ); RedrawResourceSprite( res3, { x, y }, 2, width_, offsetY, valueVsSprite[id + 2].first ); id += 3; offsetY += 45; } const bool isManyResources = valueVsSprite.size() > 2; if ( valueVsSprite.size() - id == 2 ) { const fheroes2::Sprite & res1 = fheroes2::AGG::GetICN( ICN::RESOURCE, valueVsSprite[id].second ); const fheroes2::Sprite & res2 = fheroes2::AGG::GetICN( ICN::RESOURCE, valueVsSprite[id + 1].second ); const uint32_t width_ = isManyResources ? width / 3 : width / 2; const int32_t offsetX = isManyResources ? width_ / 2 : 0; RedrawResourceSprite( res1, { x + offsetX, y }, 0, width_, offsetY, valueVsSprite[id].first ); RedrawResourceSprite( res2, { x + offsetX, y }, 1, width_, offsetY, valueVsSprite[id + 1].first ); } else if ( valueVsSprite.size() - id == 1 ) { const fheroes2::Sprite & res1 = fheroes2::AGG::GetICN( ICN::RESOURCE, valueVsSprite[id].second ); const int32_t width_ = isManyResources ? width / 3 : width; RedrawResourceSprite( res1, { x, y }, isManyResources ? 1 : 0, width_, offsetY, valueVsSprite[id].first ); } } StreamBase & operator<<( StreamBase & msg, const Funds & res ) { return msg << res.wood << res.mercury << res.ore << res.sulfur << res.crystal << res.gems << res.gold; } StreamBase & operator>>( StreamBase & msg, Funds & res ) { return msg >> res.wood >> res.mercury >> res.ore >> res.sulfur >> res.crystal >> res.gems >> res.gold; } fheroes2-1.0.12+dfsg/src/fheroes2/resource/resource.h000066400000000000000000000143061456075706000224000ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2RESOURCE_H #define H2RESOURCE_H #include #include #include #include #include #include "math_base.h" class StreamBase; struct cost_t { uint16_t gold; uint8_t wood; uint8_t mercury; uint8_t ore; uint8_t sulfur; uint8_t crystal; uint8_t gems; }; namespace Resource { enum { UNKNOWN = 0x00, WOOD = 0x01, MERCURY = 0x02, ORE = 0x04, SULFUR = 0x08, CRYSTAL = 0x10, GEMS = 0x20, GOLD = 0x40, ALL = WOOD | MERCURY | ORE | SULFUR | CRYSTAL | GEMS | GOLD }; } class Funds { public: Funds(); Funds( int32_t _ore, int32_t _wood, int32_t _mercury, int32_t _sulfur, int32_t _crystal, int32_t _gems, int32_t _gold ); Funds( int rs, uint32_t count ); explicit Funds( const cost_t & ); Funds operator+( const Funds & ) const; Funds operator*( uint32_t mul ) const; Funds operator-( const Funds & ) const; Funds operator/( const int32_t div ) const; Funds & operator+=( const Funds & ); Funds & operator*=( uint32_t mul ); Funds & operator/=( const int32_t div ); Funds & operator-=( const Funds & ); Funds & operator=( const cost_t & cost ); bool operator==( const Funds & other ) const; bool operator>=( const Funds & other ) const; bool operator<( const Funds & other ) const { return !operator>=( other ); } Funds max( const Funds & ) const; int32_t Get( int rs ) const; int32_t * GetPtr( int rs ); int getLowestQuotient( const Funds & ) const; int GetValidItems() const; uint32_t GetValidItemsCount() const; std::pair getFirstValidResource() const; // Sets all values to be >= 0 void Trim(); void Reset(); std::string String() const; int32_t wood; int32_t mercury; int32_t ore; int32_t sulfur; int32_t crystal; int32_t gems; int32_t gold; }; StreamBase & operator<<( StreamBase &, const Funds & ); StreamBase & operator>>( StreamBase &, Funds & ); namespace Resource { const char * String( int resource ); const char * getDescription(); int Rand( const bool includeGold ); Funds CalculateEventResourceUpdate( const Funds & currentFunds, const Funds & eventFunds ); // Returns index sprite objnrsrc.icn uint8_t GetIndexSprite( int resource ); int FromIndexSprite( uint32_t index ); // Return index sprite from resource.icn. uint32_t getIconIcnIndex( const int resourceType ); int getResourceTypeFromIconIndex( const uint32_t index ); class BoxSprite : protected fheroes2::Rect { public: BoxSprite( const Funds &, int32_t ); const fheroes2::Rect & GetArea() const; void SetPos( int32_t, int32_t ); void Redraw() const; const Funds rs; }; // Applies the given function object 'fn' to every valid resource in the 'resources' set template || std::is_enum_v>> void forEach( const T resources, const F & fn ) { const auto forEachImp = [&fn]( const auto res ) { constexpr int maxResourceIdBitNum = []() constexpr { static_assert( std::is_enum_v ); using ResourceUnderlyingType = std::underlying_type_t; static_assert( std::numeric_limits::radix == 2 ); for ( int i = std::numeric_limits::digits - 1; i >= 0; --i ) { if ( ( Resource::ALL & ( static_cast( 1 ) << i ) ) != 0 ) { return i; } } return -1; } (); using ResType = decltype( res ); static_assert( std::numeric_limits::radix == 2 && maxResourceIdBitNum >= 0 && maxResourceIdBitNum < std::numeric_limits::digits ); for ( int i = 0; i <= maxResourceIdBitNum; ++i ) { const ResType resItem = res & ( static_cast( 1 ) << i ); if ( resItem == 0 ) { continue; } fn( resItem ); } }; if constexpr ( std::is_enum_v ) { forEachImp( static_cast>( resources ) ); } else { forEachImp( resources ); } } } #endif fheroes2-1.0.12+dfsg/src/fheroes2/spell/000077500000000000000000000000001456075706000176645ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/fheroes2/spell/spell.cpp000066400000000000000000000625071456075706000215210ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "spell.h" #include #include #include #include "artifact.h" #include "artifact_info.h" #include "heroes_base.h" #include "monster.h" #include "race.h" #include "rand.h" #include "serialize.h" #include "translations.h" struct spellstats_t { const char * name; uint8_t spellPoints; // The number of spell points consumed/required by this spell uint16_t movePoints; // The number of movement points consumed by this spell uint16_t minMovePoints; // The minimum number of movement points required to cast this spell uint32_t imageId; uint8_t extraValue; const char * description; }; // The original resources don't have most of sprites for Mass Spells // so we made some tricks in AGG source file. All modified sprite IDs start from 60 spellstats_t spells[] = { // name | spell points | movement points | min movement points | image id | extra value | description { "Unknown", 0, 0, 0, 0, 0, "Unknown spell." }, { gettext_noop( "Fireball" ), 9, 0, 0, 8, 10, gettext_noop( "Causes a giant fireball to strike the selected area, damaging all nearby creatures." ) }, { gettext_noop( "Fireblast" ), 15, 0, 0, 9, 10, gettext_noop( "An improved version of fireball, fireblast affects two hexes around the center point of the spell, rather than one." ) }, { gettext_noop( "Lightning Bolt" ), 7, 0, 0, 4, 25, gettext_noop( "Causes a bolt of electrical energy to strike the selected creature." ) }, { gettext_noop( "Chain Lightning" ), 15, 0, 0, 5, 40, gettext_noop( "Causes a bolt of electrical energy to strike a selected creature, then strike the nearest creature with half damage, then strike the NEXT nearest creature with half again damage, and so on, until it becomes too weak to be harmful. Warning: This spell can hit your own creatures!" ) }, { gettext_noop( "Teleport" ), 9, 0, 0, 10, 0, gettext_noop( "Teleports the creature you select to any open position on the battlefield." ) }, { gettext_noop( "Cure" ), 6, 0, 0, 6, 5, gettext_noop( "Removes all negative spells cast upon one of your units, and restores up to %{count} HP per level of spell power." ) }, { gettext_noop( "Mass Cure" ), 15, 0, 0, 60, 5, gettext_noop( "Removes all negative spells cast upon your forces, and restores up to %{count} HP per level of spell power, per creature." ) }, { gettext_noop( "Resurrect" ), 12, 0, 0, 13, 50, gettext_noop( "Resurrects creatures from a damaged or dead unit until end of combat." ) }, { gettext_noop( "Resurrect True" ), 15, 0, 0, 12, 50, gettext_noop( "Resurrects creatures from a damaged or dead unit permanently." ) }, { gettext_noop( "Haste" ), 3, 0, 0, 14, 2, gettext_noop( "Increases the speed of any creature by %{count}." ) }, { gettext_noop( "Mass Haste" ), 10, 0, 0, 61, 2, gettext_noop( "Increases the speed of all of your creatures by %{count}." ) }, { gettext_noop( "spell|Slow" ), 3, 0, 0, 1, 0, gettext_noop( "Slows target to half movement rate." ) }, { gettext_noop( "Mass Slow" ), 15, 0, 0, 62, 0, gettext_noop( "Slows all enemies to half movement rate." ) }, { gettext_noop( "spell|Blind" ), 6, 0, 0, 21, 0, gettext_noop( "Clouds the affected creatures' eyes, preventing them from moving." ) }, { gettext_noop( "Bless" ), 3, 0, 0, 7, 0, gettext_noop( "Causes the selected creatures to inflict maximum damage." ) }, { gettext_noop( "Mass Bless" ), 12, 0, 0, 63, 0, gettext_noop( "Causes all of your units to inflict maximum damage." ) }, { gettext_noop( "Stoneskin" ), 3, 0, 0, 31, 3, gettext_noop( "Magically increases the defense skill of the selected creatures." ) }, { gettext_noop( "Steelskin" ), 6, 0, 0, 30, 5, gettext_noop( "Increases the defense skill of the targeted creatures. This is an improved version of Stoneskin." ) }, { gettext_noop( "Curse" ), 3, 0, 0, 3, 0, gettext_noop( "Causes the selected creatures to inflict minimum damage." ) }, { gettext_noop( "Mass Curse" ), 12, 0, 0, 64, 0, gettext_noop( "Causes all enemy troops to inflict minimum damage." ) }, { gettext_noop( "Holy Word" ), 9, 0, 0, 22, 10, gettext_noop( "Damages all undead in the battle." ) }, { gettext_noop( "Holy Shout" ), 12, 0, 0, 23, 20, gettext_noop( "Damages all undead in the battle. This is an improved version of Holy Word." ) }, { gettext_noop( "Anti-Magic" ), 7, 0, 0, 17, 0, gettext_noop( "Prevents harmful magic against the selected creatures." ) }, { gettext_noop( "Dispel Magic" ), 5, 0, 0, 18, 0, gettext_noop( "Removes all magic spells from a single target." ) }, { gettext_noop( "Mass Dispel" ), 12, 0, 0, 18, 0, gettext_noop( "Removes all magic spells from all creatures." ) }, { gettext_noop( "Magic Arrow" ), 3, 0, 0, 38, 10, gettext_noop( "Causes a magic arrow to strike the selected target." ) }, { gettext_noop( "Berserker" ), 12, 0, 0, 19, 0, gettext_noop( "Causes a creature to attack its nearest neighbor." ) }, { gettext_noop( "Armageddon" ), 20, 0, 0, 16, 50, gettext_noop( "Holy terror strikes the battlefield, causing severe damage to all creatures." ) }, { gettext_noop( "Elemental Storm" ), 15, 0, 0, 11, 25, gettext_noop( "Magical elements pour down on the battlefield, damaging all creatures." ) }, { gettext_noop( "Meteor Shower" ), 15, 0, 0, 24, 25, gettext_noop( "A rain of rocks strikes an area of the battlefield, damaging all nearby creatures." ) }, { gettext_noop( "Paralyze" ), 9, 0, 0, 20, 0, gettext_noop( "The targeted creatures are paralyzed, unable to move or retaliate." ) }, { gettext_noop( "Hypnotize" ), 15, 0, 0, 37, 25, gettext_noop( "Brings a single enemy unit under your control if its hits are less than %{count} times the caster's spell power." ) }, { gettext_noop( "Cold Ray" ), 6, 0, 0, 36, 20, gettext_noop( "Drains body heat from a single enemy unit." ) }, { gettext_noop( "Cold Ring" ), 9, 0, 0, 35, 10, gettext_noop( "Drains body heat from all units surrounding the center point, but not including the center point." ) }, { gettext_noop( "Disrupting Ray" ), 7, 0, 0, 34, 3, gettext_noop( "Reduces the defense rating of an enemy unit by three." ) }, { gettext_noop( "Death Ripple" ), 6, 0, 0, 29, 5, gettext_noop( "Damages all living (non-undead) units in the battle." ) }, { gettext_noop( "Death Wave" ), 10, 0, 0, 28, 10, gettext_noop( "Damages all living (non-undead) units in the battle. This spell is an improved version of Death Ripple." ) }, { gettext_noop( "Dragon Slayer" ), 6, 0, 0, 32, 5, gettext_noop( "Greatly increases a unit's attack skill vs. Dragons." ) }, { gettext_noop( "Blood Lust" ), 3, 0, 0, 27, 3, gettext_noop( "Increases a unit's attack skill." ) }, { gettext_noop( "Animate Dead" ), 10, 0, 0, 25, 50, gettext_noop( "Resurrects creatures from a damaged or dead undead unit permanently." ) }, { gettext_noop( "Mirror Image" ), 25, 0, 0, 26, 0, gettext_noop( "Creates an illusionary unit that duplicates one of your existing units. This illusionary unit does the same damages as the original, but will vanish if it takes any damage." ) }, { gettext_noop( "Shield" ), 3, 0, 0, 15, 2, gettext_noop( "Halves damage received from ranged attacks for a single unit. Does not affect damage received from Turrets or Ballistae." ) }, { gettext_noop( "Mass Shield" ), 7, 0, 0, 65, 0, gettext_noop( "Halves damage received from ranged attacks for all of your units. Does not affect damage received from Turrets or Ballistae." ) }, { gettext_noop( "Summon Earth Elemental" ), 30, 0, 0, 56, 3, gettext_noop( "Summons Earth Elementals to fight for your army." ) }, { gettext_noop( "Summon Air Elemental" ), 30, 0, 0, 57, 3, gettext_noop( "Summons Air Elementals to fight for your army." ) }, { gettext_noop( "Summon Fire Elemental" ), 30, 0, 0, 58, 3, gettext_noop( "Summons Fire Elementals to fight for your army." ) }, { gettext_noop( "Summon Water Elemental" ), 30, 0, 0, 59, 3, gettext_noop( "Summons Water Elementals to fight for your army." ) }, { gettext_noop( "Earthquake" ), 15, 0, 0, 33, 0, gettext_noop( "Damages castle walls." ) }, { gettext_noop( "View Mines" ), 1, 0, 0, 39, 0, gettext_noop( "Causes all mines across the land to become visible." ) }, { gettext_noop( "View Resources" ), 1, 0, 0, 40, 0, gettext_noop( "Causes all resources across the land to become visible." ) }, { gettext_noop( "View Artifacts" ), 2, 0, 0, 41, 0, gettext_noop( "Causes all artifacts across the land to become visible." ) }, { gettext_noop( "View Towns" ), 2, 0, 0, 42, 0, gettext_noop( "Causes all towns and castles across the land to become visible." ) }, { gettext_noop( "View Heroes" ), 2, 0, 0, 43, 0, gettext_noop( "Causes all Heroes across the land to become visible." ) }, { gettext_noop( "View All" ), 3, 0, 0, 44, 0, gettext_noop( "Causes the entire land to become visible." ) }, { gettext_noop( "Identify Hero" ), 3, 0, 0, 45, 0, gettext_noop( "Allows the caster to view detailed information on enemy Heroes." ) }, { gettext_noop( "Summon Boat" ), 5, 0, 0, 46, 0, gettext_noop( "Summons the nearest unoccupied, friendly boat to an adjacent shore location. A friendly boat is one which you just built or were the most recent player to occupy." ) }, { gettext_noop( "Dimension Door" ), 10, 225, 69, 47, 0, gettext_noop( "Allows the caster to magically transport to a nearby location." ) }, { gettext_noop( "Town Gate" ), 10, 225, 69, 48, 0, gettext_noop( "Returns the caster to any town or castle currently owned." ) }, { gettext_noop( "Town Portal" ), 20, 225, 69, 49, 0, gettext_noop( "Returns the hero to the town or castle of choice, provided it is controlled by you." ) }, { gettext_noop( "Visions" ), 6, 0, 0, 50, 3, gettext_noop( "Visions predicts the likely outcome of an encounter with a neutral army camp." ) }, { gettext_noop( "Haunt" ), 8, 0, 0, 51, 4, gettext_noop( "Haunts a mine you control with Ghosts. This mine stops producing resources. (If I can't keep it, nobody will!)" ) }, { gettext_noop( "Set Earth Guardian" ), 15, 0, 0, 52, 4, gettext_noop( "Sets Earth Elementals to guard a mine against enemy armies." ) }, { gettext_noop( "Set Air Guardian" ), 15, 0, 0, 53, 4, gettext_noop( "Sets Air Elementals to guard a mine against enemy armies." ) }, { gettext_noop( "Set Fire Guardian" ), 15, 0, 0, 54, 4, gettext_noop( "Sets Fire Elementals to guard a mine against enemy armies." ) }, { gettext_noop( "Set Water Guardian" ), 15, 0, 0, 55, 4, gettext_noop( "Sets Water Elementals to guard a mine against enemy armies." ) }, { gettext_noop( "Random Spell" ), 0, 0, 0, 67, 0, gettext_noop( "Randomly selected spell of any level." ) }, { gettext_noop( "Random 1st Level Spell" ), 0, 0, 0, 68, 0, gettext_noop( "Randomly selected 1st level spell." ) }, { gettext_noop( "Random 2nd Level Spell" ), 0, 0, 0, 69, 0, gettext_noop( "Randomly selected 2nd level spell." ) }, { gettext_noop( "Random 3rd Level Spell" ), 0, 0, 0, 70, 0, gettext_noop( "Randomly selected 3rd level spell." ) }, { gettext_noop( "Random 4th Level Spell" ), 0, 0, 0, 71, 0, gettext_noop( "Randomly selected 4th level spell." ) }, { gettext_noop( "Random 5th Level Spell" ), 0, 0, 0, 72, 0, gettext_noop( "Randomly selected 5th level spell." ) }, { gettext_noop( "Petrification" ), 0, 0, 0, 66, 0, gettext_noop( "Turns the affected creature into stone. A petrified creature receives half damage from a direct attack." ) }, }; const char * Spell::GetName() const { return _( spells[id].name ); } const char * Spell::GetDescription() const { return _( spells[id].description ); } uint32_t Spell::movePoints() const { return spells[id].movePoints; } uint32_t Spell::minMovePoints() const { return spells[id].minMovePoints; } uint32_t Spell::spellPoints( const HeroBase * hero ) const { if ( hero == nullptr ) { return spells[id].spellPoints; } fheroes2::ArtifactBonusType type = fheroes2::ArtifactBonusType::NONE; switch ( id ) { case BLESS: case MASSBLESS: type = fheroes2::ArtifactBonusType::BLESS_SPELL_COST_REDUCTION_PERCENT; break; case SUMMONEELEMENT: case SUMMONAELEMENT: case SUMMONFELEMENT: case SUMMONWELEMENT: type = fheroes2::ArtifactBonusType::SUMMONING_SPELL_COST_REDUCTION_PERCENT; break; case CURSE: case MASSCURSE: type = fheroes2::ArtifactBonusType::CURSE_SPELL_COST_REDUCTION_PERCENT; break; default: if ( isMindInfluence() ) { type = fheroes2::ArtifactBonusType::MIND_INFLUENCE_SPELL_COST_REDUCTION_PERCENT; } break; } if ( type == fheroes2::ArtifactBonusType::NONE ) { return spells[id].spellPoints; } int32_t spellCost = spells[id].spellPoints; const std::vector spellReductionPercentage = hero->GetBagArtifacts().getTotalArtifactMultipliedPercent( type ); for ( const int32_t value : spellReductionPercentage ) { assert( value >= 0 && value <= 100 ); spellCost = spellCost * ( 100 - value ) / 100; } if ( spellCost < 1 ) { return 1; } return static_cast( spellCost ); } double Spell::getStrategicValue( double armyStrength, uint32_t currentSpellPoints, int spellPower ) const { const uint32_t spellCost = spellPoints(); const uint32_t casts = spellCost ? std::min( 10U, currentSpellPoints / spellCost ) : 0; // use quadratic formula to diminish returns from subsequent spell casts, (up to x5 when spell has 10 uses) const double amountModifier = ( casts == 1 ) ? 1 : casts - ( 0.05 * casts * casts ); if ( isAdventure() ) { // TODO: update this logic if you add support for more Adventure Map spells. switch ( id ) { case Spell::DIMENSIONDOOR: return 500 * amountModifier; case Spell::TOWNGATE: case Spell::TOWNPORTAL: return 250 * amountModifier; case Spell::VIEWALL: return 500; default: break; } return 0; } if ( isDamage() ) { // Benchmark for Lightning for 20 power * 20 knowledge (maximum uses) is 2500.0 return amountModifier * Damage() * spellPower; } // These high impact spells can turn tide of battle if ( isResurrect() || isMassActions() || id == Spell::BLIND || id == Spell::PARALYZE ) { return armyStrength * 0.1 * amountModifier; } if ( isSummon() ) { // Summoning spells can be effective only per single turn as a summoned stack of monster could be killed within the same turn. // Also if the opponent targets the army's monsters and kill all of them the battle would be lost for this hero. return Monster( id ).GetMonsterStrength() * ExtraValue() * spellPower; } return armyStrength * 0.04 * amountModifier; } int Spell::Level() const { switch ( id ) { case BLESS: case BLOODLUST: case CURE: case CURSE: case DISPEL: case HASTE: case ARROW: case SHIELD: case SLOW: case STONESKIN: case VIEWMINES: case VIEWRESOURCES: return 1; case BLIND: case COLDRAY: case DEATHRIPPLE: case DISRUPTINGRAY: case DRAGONSLAYER: case LIGHTNINGBOLT: case STEELSKIN: case HAUNT: case SUMMONBOAT: case VIEWARTIFACTS: case VISIONS: return 2; case ANIMATEDEAD: case ANTIMAGIC: case COLDRING: case DEATHWAVE: case EARTHQUAKE: case FIREBALL: case HOLYWORD: case MASSBLESS: case MASSCURSE: case MASSDISPEL: case MASSHASTE: case PARALYZE: case TELEPORT: case IDENTIFYHERO: case VIEWHEROES: case VIEWTOWNS: return 3; case BERSERKER: case CHAINLIGHTNING: case ELEMENTALSTORM: case FIREBLAST: case HOLYSHOUT: case MASSCURE: case MASSSHIELD: case MASSSLOW: case METEORSHOWER: case RESURRECT: case SETEGUARDIAN: case SETAGUARDIAN: case SETFGUARDIAN: case SETWGUARDIAN: case TOWNGATE: case VIEWALL: return 4; case ARMAGEDDON: case HYPNOTIZE: case MIRRORIMAGE: case RESURRECTTRUE: case SUMMONEELEMENT: case SUMMONAELEMENT: case SUMMONFELEMENT: case SUMMONWELEMENT: case DIMENSIONDOOR: case TOWNPORTAL: return 5; default: break; } return 0; } bool Spell::isCombat() const { switch ( id ) { case NONE: case VIEWMINES: case VIEWRESOURCES: case VIEWARTIFACTS: case VIEWTOWNS: case VIEWHEROES: case VIEWALL: case IDENTIFYHERO: case SUMMONBOAT: case DIMENSIONDOOR: case TOWNGATE: case TOWNPORTAL: case VISIONS: case HAUNT: case SETEGUARDIAN: case SETAGUARDIAN: case SETFGUARDIAN: case SETWGUARDIAN: return false; default: break; } return true; } bool Spell::isGuardianType() const { switch ( id ) { case HAUNT: case SETEGUARDIAN: case SETAGUARDIAN: case SETFGUARDIAN: case SETWGUARDIAN: return true; default: break; } return false; } uint32_t Spell::Damage() const { switch ( id ) { case ARROW: case FIREBALL: case FIREBLAST: case LIGHTNINGBOLT: case COLDRING: case DEATHWAVE: case HOLYWORD: case CHAINLIGHTNING: case ARMAGEDDON: case ELEMENTALSTORM: case METEORSHOWER: case COLDRAY: case HOLYSHOUT: case DEATHRIPPLE: return spells[id].extraValue; default: break; } return 0; } bool Spell::isMindInfluence() const { switch ( id ) { case BLIND: case PARALYZE: case BERSERKER: case HYPNOTIZE: return true; default: break; } return false; } uint32_t Spell::IndexSprite() const { return spells[id].imageId; } uint32_t Spell::Restore() const { switch ( id ) { case Spell::CURE: case Spell::MASSCURE: return spells[id].extraValue; default: break; } return 0; } uint32_t Spell::Resurrect() const { switch ( id ) { case Spell::ANIMATEDEAD: case Spell::RESURRECT: case Spell::RESURRECTTRUE: return spells[id].extraValue; default: break; } return 0; } uint32_t Spell::ExtraValue() const { return spells[id].extraValue; } uint32_t Spell::weightForRace( const int race ) const { switch ( id ) { case Spell::HOLYWORD: case Spell::HOLYSHOUT: if ( race == Race::NECR ) { return 0; } break; case Spell::DEATHRIPPLE: case Spell::DEATHWAVE: if ( race != Race::NECR ) { return 0; } break; case Spell::SUMMONEELEMENT: case Spell::SUMMONAELEMENT: case Spell::SUMMONFELEMENT: case Spell::SUMMONWELEMENT: case Spell::TOWNPORTAL: case Spell::VISIONS: case Spell::HAUNT: case Spell::SETEGUARDIAN: case Spell::SETAGUARDIAN: case Spell::SETFGUARDIAN: case Spell::SETWGUARDIAN: return 0; default: break; } return 10; } Spell Spell::Rand( const int level, const bool isAdventure ) { std::vector vec; vec.reserve( 15 ); for ( int32_t spellId = NONE; spellId < PETRIFY; ++spellId ) { const Spell spell( spellId ); if ( level != spell.Level() ) { continue; } if ( isAdventure ) { if ( !spell.isAdventure() ) { continue; } } else { if ( !spell.isCombat() ) { continue; } } vec.push_back( spell ); } return !vec.empty() ? Rand::Get( vec ) : Spell::NONE; } Spell Spell::RandCombat( const int level ) { return Rand( level, false ); } Spell Spell::RandAdventure( const int level ) { const Spell spell = Rand( level, true ); return spell.isValid() ? spell : RandCombat( level ); } std::vector Spell::getAllSpellIdsSuitableForSpellBook( const int spellLevel /* = -1 */ ) { std::vector result; result.reserve( SPELL_COUNT ); for ( int spellId = 0; spellId < SPELL_COUNT; ++spellId ) { if ( spellId == NONE || ( spellId >= RANDOM && spellId <= PETRIFY ) ) { continue; } if ( spellLevel > 0 ) { const Spell spell( spellId ); if ( spell.Level() != spellLevel ) { continue; } } result.push_back( spellId ); } return result; } bool Spell::isUndeadOnly() const { switch ( id ) { case ANIMATEDEAD: case HOLYWORD: case HOLYSHOUT: return true; default: break; } return false; } bool Spell::isAliveOnly() const { switch ( id ) { case BLESS: case MASSBLESS: case CURSE: case MASSCURSE: case DEATHRIPPLE: case DEATHWAVE: case RESURRECT: case RESURRECTTRUE: return true; default: break; } return false; } bool Spell::isSingleTarget() const { switch ( id ) { case LIGHTNINGBOLT: case TELEPORT: case CURE: case RESURRECT: case RESURRECTTRUE: case HASTE: case SLOW: case BLIND: case BLESS: case STONESKIN: case STEELSKIN: case CURSE: case ANTIMAGIC: case DISPEL: case ARROW: case BERSERKER: case PARALYZE: case HYPNOTIZE: case COLDRAY: case DISRUPTINGRAY: case DRAGONSLAYER: case BLOODLUST: case ANIMATEDEAD: case MIRRORIMAGE: case SHIELD: return true; default: break; } return false; } bool Spell::isApplyWithoutFocusObject() const { if ( isMassActions() || isSummon() ) return true; else switch ( id ) { case DEATHRIPPLE: case DEATHWAVE: case EARTHQUAKE: case HOLYWORD: case HOLYSHOUT: case ARMAGEDDON: case ELEMENTALSTORM: return true; default: break; } return false; } bool Spell::isSummon() const { switch ( id ) { case SUMMONEELEMENT: case SUMMONAELEMENT: case SUMMONFELEMENT: case SUMMONWELEMENT: return true; default: break; } return false; } bool Spell::isEffectDispel() const { switch ( id ) { case CURE: case MASSCURE: case DISPEL: case MASSDISPEL: return true; default: break; } return false; } bool Spell::isApplyToAnyTroops() const { switch ( id ) { case DISPEL: case MASSDISPEL: return true; default: break; } return false; } bool Spell::isApplyToFriends() const { switch ( id ) { case BLESS: case BLOODLUST: case CURE: case HASTE: case SHIELD: case STONESKIN: case DRAGONSLAYER: case STEELSKIN: case ANIMATEDEAD: case ANTIMAGIC: case TELEPORT: case RESURRECT: case MIRRORIMAGE: case RESURRECTTRUE: case MASSBLESS: case MASSCURE: case MASSHASTE: case MASSSHIELD: return true; default: break; } return false; } bool Spell::isMassActions() const { switch ( id ) { case MASSCURE: case MASSHASTE: case MASSSLOW: case MASSBLESS: case MASSCURSE: case MASSDISPEL: case MASSSHIELD: return true; default: break; } return false; } bool Spell::isApplyToEnemies() const { switch ( id ) { case MASSSLOW: case MASSCURSE: case CURSE: case ARROW: case SLOW: case BLIND: case COLDRAY: case DISRUPTINGRAY: case LIGHTNINGBOLT: case CHAINLIGHTNING: case PARALYZE: case BERSERKER: case HYPNOTIZE: return true; default: break; } return false; } int32_t Spell::CalculateDimensionDoorDistance() { // original h2 variant return 14; } StreamBase & operator<<( StreamBase & msg, const Spell & spell ) { return msg << spell.id; } StreamBase & operator>>( StreamBase & msg, Spell & spell ) { return msg >> spell.id; } fheroes2-1.0.12+dfsg/src/fheroes2/spell/spell.h000066400000000000000000000157101456075706000211600ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2SPELL_H #define H2SPELL_H #include #include #define DEFAULT_SPELL_DURATION 3 class HeroBase; class StreamBase; class Spell { public: enum type_t : int32_t { NONE = 0, FIREBALL, FIREBLAST, LIGHTNINGBOLT, CHAINLIGHTNING, TELEPORT, CURE, MASSCURE, RESURRECT, RESURRECTTRUE, HASTE, MASSHASTE, SLOW, MASSSLOW, BLIND, BLESS, MASSBLESS, STONESKIN, STEELSKIN, CURSE, MASSCURSE, HOLYWORD, HOLYSHOUT, ANTIMAGIC, DISPEL, MASSDISPEL, ARROW, BERSERKER, ARMAGEDDON, ELEMENTALSTORM, METEORSHOWER, PARALYZE, HYPNOTIZE, COLDRAY, COLDRING, DISRUPTINGRAY, DEATHRIPPLE, DEATHWAVE, DRAGONSLAYER, BLOODLUST, ANIMATEDEAD, MIRRORIMAGE, SHIELD, MASSSHIELD, SUMMONEELEMENT, SUMMONAELEMENT, SUMMONFELEMENT, SUMMONWELEMENT, EARTHQUAKE, VIEWMINES, VIEWRESOURCES, VIEWARTIFACTS, VIEWTOWNS, VIEWHEROES, VIEWALL, IDENTIFYHERO, SUMMONBOAT, DIMENSIONDOOR, TOWNGATE, TOWNPORTAL, VISIONS, HAUNT, SETEGUARDIAN, SETAGUARDIAN, SETFGUARDIAN, SETWGUARDIAN, // These constants are placeholders for a random spell of the corresponding level and should not be added to the spell book. RANDOM, RANDOM1, RANDOM2, RANDOM3, RANDOM4, RANDOM5, // This spell is exclusively a built-in monster spell and should not be added to the spell book. PETRIFY, // IMPORTANT! Put all new spells above this line. SPELL_COUNT }; Spell( const int32_t spellId = NONE ) : id( ( spellId < 0 || spellId >= SPELL_COUNT ) ? NONE : spellId ) { // Do nothing. } bool operator<( const Spell & s ) const { return id < s.id; } bool operator==( const Spell & s ) const { return s.id == id; } bool operator!=( const Spell & s ) const { return s.id != id; } int GetID() const { return id; } const char * GetName() const; const char * GetDescription() const; // Returns the number of spell points consumed/required by this spell uint32_t spellPoints( const HeroBase * hero = nullptr ) const; // Returns the number of movement points consumed by this spell uint32_t movePoints() const; // Returns the minimum number of movement points required to cast this spell uint32_t minMovePoints() const; // Returns the value of the spell using the provided context double getStrategicValue( double armyStrength, uint32_t currentSpellPoints, int spellPower ) const; int Level() const; uint32_t Damage() const; uint32_t Restore() const; uint32_t Resurrect() const; uint32_t ExtraValue() const; // Returns the weight of this spell for a specific race. // See https://handbookhmm.ru/kakim-obrazom-zaklinaniya-popadayut-v-magicheskuyu-gildiyu.html for details. uint32_t weightForRace( const int race ) const; bool isValid() const { return id != Spell::NONE; } bool isLevel( int lvl ) const { return Level() == lvl; } bool isCombat() const; bool isAdventure() const { return !isCombat(); } bool isDamage() const { return Damage() != 0; } bool isSingleTarget() const; bool isRestore() const { return Restore() != 0; } bool isResurrect() const { return Resurrect() != 0; } bool isMindInfluence() const; bool isUndeadOnly() const; bool isAliveOnly() const; bool isSummon() const; bool isEffectDispel() const; bool isApplyWithoutFocusObject() const; bool isApplyToAnyTroops() const; bool isApplyToFriends() const; bool isApplyToEnemies() const; bool isMassActions() const; bool isFire() const { return id == FIREBALL || id == FIREBLAST; } bool isCold() const { return id == COLDRAY || id == COLDRING; } bool isBuiltinOnly() const { return id == PETRIFY; } bool isGuardianType() const; // Returns the index of the spell sprite in SPELLS.ICN uint32_t IndexSprite() const; static Spell Rand( const int level, const bool isAdventure ); static Spell RandCombat( const int level ); static Spell RandAdventure( const int level ); // Returns the IDs of all spells of a given level that are suitable for the spell book (i.e. no placeholders or exclusive // built-in spells for monsters are returned). If 'spellLevel' is less than 1, suitable spells of all levels are returned. static std::vector getAllSpellIdsSuitableForSpellBook( const int spellLevel = -1 ); static int32_t CalculateDimensionDoorDistance(); private: friend StreamBase & operator<<( StreamBase &, const Spell & ); friend StreamBase & operator>>( StreamBase &, Spell & ); int id; }; StreamBase & operator<<( StreamBase &, const Spell & ); StreamBase & operator>>( StreamBase &, Spell & ); #endif fheroes2-1.0.12+dfsg/src/fheroes2/spell/spell_book.cpp000066400000000000000000000554271456075706000225360ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "spell_book.h" #include #include #include #include #include #include "agg_image.h" #include "cursor.h" #include "dialog.h" #include "dialog_selectitems.h" #include "game_hotkeys.h" #include "heroes_base.h" #include "icn.h" #include "image.h" #include "localevent.h" #include "math_base.h" #include "screen.h" #include "tools.h" #include "translations.h" #include "ui_dialog.h" #include "ui_text.h" namespace { const int32_t spellsPerPage = 6; const fheroes2::Point bookmarkInfoOffset( 123, 273 ); const fheroes2::Point bookmarkAdvOffset( 266, 269 ); const fheroes2::Point bookmarkCombatoOffset( 299, 276 ); const fheroes2::Point bookmarkCloseOffset( 416, 280 ); const fheroes2::Point spellBookShadow( -16, 16 ); fheroes2::Size getSpellBookSize( const SpellBook::Filter displayableSpells ) { const fheroes2::Sprite & bookPage = fheroes2::AGG::GetICN( ICN::BOOK, 0 ); const fheroes2::Sprite & bookmark_info = fheroes2::AGG::GetICN( ICN::BOOK, 6 ); const fheroes2::Sprite & bookmark_advn = fheroes2::AGG::GetICN( ICN::BOOK, 3 ); const fheroes2::Sprite & bookmark_cmbt = fheroes2::AGG::GetICN( ICN::BOOK, 4 ); const fheroes2::Sprite & bookmark_clos = fheroes2::AGG::GetICN( ICN::BOOK, 5 ); const bool isAdventureTabPresent = displayableSpells != SpellBook::Filter::CMBT; const bool isCombatTabPresent = displayableSpells != SpellBook::Filter::ADVN; int32_t maximumHeight = bookmarkInfoOffset.y + bookmark_info.height(); if ( isAdventureTabPresent && maximumHeight < bookmarkAdvOffset.y + bookmark_advn.height() ) { maximumHeight = bookmarkAdvOffset.y + bookmark_advn.height(); } if ( isCombatTabPresent && maximumHeight < bookmarkCombatoOffset.y + bookmark_cmbt.height() ) { maximumHeight = bookmarkCombatoOffset.y + bookmark_cmbt.height(); } if ( maximumHeight < bookmarkCloseOffset.y + bookmark_clos.height() ) { maximumHeight = bookmarkCloseOffset.y + bookmark_clos.height(); } return { bookPage.width() * 2, maximumHeight }; } void SpellBookRedrawSpells( const SpellStorage & spells, std::vector & coords, const size_t index, const int32_t px, const int32_t py, const HeroBase & hero, const bool canCastSpell, const bool isRight, fheroes2::Image & output, const fheroes2::Point & outputOffset ) { for ( int32_t i = 0; i < spellsPerPage; ++i ) { if ( spells.size() <= index + i ) return; const int32_t ox = 84 + 81 * ( i & 1 ); const int32_t oy = 71 + 78 * ( i >> 1 ) - ( ( i + ( isRight ? 1 : 0 ) ) % 2 ) * 5; const Spell & spell = spells[i + index]; const std::string & spellName = spell.GetName(); const uint32_t spellCost = spell.spellPoints( &hero ); // If casting spells is prohibited in principle, it makes no sense to check whether this hero can cast them and highlight them in gray if not const bool isAvailable = !canCastSpell || hero.CanCastSpell( spell ); const fheroes2::Sprite & icon = fheroes2::AGG::GetICN( ICN::SPELLS, spell.IndexSprite() ); int vertOffset = 49 - icon.height(); if ( vertOffset > 6 ) vertOffset = 6; fheroes2::Rect rect( px + ox - ( icon.width() + icon.width() % 2 ) / 2, py + oy - icon.height() - vertOffset + 2, icon.width(), icon.height() + 10 ); fheroes2::Blit( icon, output, rect.x, rect.y ); const int32_t maxTextWidth = 80; const int32_t rowCount = fheroes2::Text{ spellName, fheroes2::FontType::smallWhite() }.rows( maxTextWidth ); const fheroes2::Text text( spellName + ( rowCount == 1 ? '\n' : ' ' ) + '[' + std::to_string( spellCost ) + ']', isAvailable ? fheroes2::FontType::smallWhite() : fheroes2::FontType{ fheroes2::FontSize::SMALL, fheroes2::FontColor::GRAY } ); text.draw( px + ox - 40, py + oy + 2, maxTextWidth, output ); rect.x += outputOffset.x; rect.y += outputOffset.y; coords.push_back( rect ); } } void SpellBookRedrawManaPoints( const fheroes2::Point & dst, uint32_t manaPoints, fheroes2::Image & output ) { fheroes2::Point tp( dst.x + 11, dst.y + 11 ); if ( manaPoints > 999 ) { manaPoints = 999; // just in case of a broken code } fheroes2::Text text( manaPoints >= 100 ? std::to_string( manaPoints / 100 ) : " ", fheroes2::FontType::smallWhite() ); text.draw( tp.x - text.width() / 2, tp.y, output ); tp.y += text.height(); text.set( manaPoints >= 10 ? std::to_string( ( manaPoints % 100 ) / 10 ) : " ", fheroes2::FontType::smallWhite() ); text.draw( tp.x - text.width() / 2, tp.y, output ); tp.y += text.height(); text.set( manaPoints > 0 ? std::to_string( manaPoints % 10 ) : "0", fheroes2::FontType::smallWhite() ); text.draw( tp.x - text.width() / 2, tp.y, output ); } void SpellBookRedrawLists( const SpellStorage & spells, std::vector & coords, const size_t index, const fheroes2::Point & pt, uint32_t manaPoints, const SpellBook::Filter displayableSpells, const HeroBase & hero, const bool canCastSpell ) { const fheroes2::Sprite & bookPage = fheroes2::AGG::GetICN( ICN::BOOK, 0 ); const fheroes2::Sprite & bookmark_info = fheroes2::AGG::GetICN( ICN::BOOK, 6 ); const fheroes2::Sprite & bookmark_advn = fheroes2::AGG::GetICN( ICN::BOOK, 3 ); const fheroes2::Sprite & bookmark_cmbt = fheroes2::AGG::GetICN( ICN::BOOK, 4 ); const fheroes2::Sprite & bookmark_clos = fheroes2::AGG::GetICN( ICN::BOOK, 5 ); const fheroes2::Size spellBookSize = getSpellBookSize( displayableSpells ); fheroes2::Sprite output( spellBookSize.width, spellBookSize.height ); output.reset(); fheroes2::Blit( bookPage, output, 0, 0, true ); fheroes2::Blit( bookPage, output, bookPage.width(), 0 ); fheroes2::Blit( bookmark_info, output, bookmarkInfoOffset.x, bookmarkInfoOffset.y ); if ( displayableSpells != SpellBook::Filter::CMBT ) fheroes2::Blit( bookmark_advn, output, bookmarkAdvOffset.x, bookmarkAdvOffset.y ); if ( displayableSpells != SpellBook::Filter::ADVN ) fheroes2::Blit( bookmark_cmbt, output, bookmarkCombatoOffset.x, bookmarkCombatoOffset.y ); fheroes2::Blit( bookmark_clos, output, bookmarkCloseOffset.x, bookmarkCloseOffset.y ); SpellBookRedrawManaPoints( bookmarkInfoOffset, manaPoints, output ); coords.clear(); SpellBookRedrawSpells( spells, coords, index, 0, 0, hero, canCastSpell, false, output, pt ); SpellBookRedrawSpells( spells, coords, index + spellsPerPage, 220, 0, hero, canCastSpell, true, output, pt ); output = fheroes2::addShadow( output, spellBookShadow, 3 ); fheroes2::Blit( output, 0, 0, fheroes2::Display::instance(), pt.x + spellBookShadow.x, pt.y, output.width(), output.height() ); } } Spell SpellBook::Open( const HeroBase & hero, const Filter displayableSpells, const bool canCastSpell, const bool restorePreviousState, const std::function * statusCallback ) const { if ( !hero.HaveSpellBook() ) { fheroes2::showStandardTextMessage( "", _( "You have no Magic Book, so you cannot cast a spell." ), Dialog::OK ); return Spell::NONE; } if ( displayableSpells == Filter::ALL ) { _spellFilter = Filter::ADVN; _startSpellIndex = 0; } else if ( !restorePreviousState || _spellFilter != displayableSpells ) { _spellFilter = displayableSpells; _startSpellIndex = 0; } SpellStorage displayedSpells = SetFilter( _spellFilter, &hero ); if ( canCastSpell && displayedSpells.empty() ) { fheroes2::showStandardTextMessage( "", _( "No spell to cast." ), Dialog::OK ); return Spell::NONE; } if ( _startSpellIndex >= displayedSpells.size() ) { _startSpellIndex = 0; } // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); const fheroes2::Sprite & bookPage = fheroes2::AGG::GetICN( ICN::BOOK, 0 ); const fheroes2::Sprite & bookmark_info = fheroes2::AGG::GetICN( ICN::BOOK, 6 ); const fheroes2::Sprite & bookmark_advn = fheroes2::AGG::GetICN( ICN::BOOK, 3 ); const fheroes2::Sprite & bookmark_cmbt = fheroes2::AGG::GetICN( ICN::BOOK, 4 ); const fheroes2::Sprite & bookmark_clos = fheroes2::AGG::GetICN( ICN::BOOK, 5 ); const fheroes2::Size spellBookSize = getSpellBookSize( displayableSpells ); fheroes2::Display & display = fheroes2::Display::instance(); const fheroes2::Rect pos( ( display.width() - ( bookPage.width() * 2 ) ) / 2, ( display.height() - bookPage.height() ) / 2, spellBookSize.width, spellBookSize.height ); const fheroes2::Rect restorerRoi( pos.x + spellBookShadow.x, pos.y, pos.width - spellBookShadow.x, pos.height + spellBookShadow.y ); fheroes2::ImageRestorer restorer( display, restorerRoi.x, restorerRoi.y, restorerRoi.width, restorerRoi.height ); const fheroes2::Rect prev_list( pos.x + 30, pos.y + 8, 30, 25 ); const fheroes2::Rect next_list( pos.x + 410, pos.y + 8, 30, 25 ); const fheroes2::Rect info_rt( pos.x + bookmarkInfoOffset.x, pos.y + bookmarkInfoOffset.y, bookmark_info.width(), bookmark_info.height() ); const fheroes2::Rect advn_rt( pos.x + bookmarkAdvOffset.x, pos.y + bookmarkAdvOffset.y, bookmark_advn.width(), bookmark_advn.height() ); const fheroes2::Rect cmbt_rt( pos.x + bookmarkCombatoOffset.x, pos.y + bookmarkCombatoOffset.y, bookmark_cmbt.width(), bookmark_cmbt.height() ); const fheroes2::Rect clos_rt( pos.x + bookmarkCloseOffset.x, pos.y + bookmarkCloseOffset.y, bookmark_clos.width(), bookmark_clos.height() ); Spell curspell = Spell::NONE; std::vector coords; coords.reserve( spellsPerPage * 2 ); SpellBookRedrawLists( displayedSpells, coords, _startSpellIndex, pos.getPosition(), hero.GetSpellPoints(), displayableSpells, hero, canCastSpell ); bool redraw = false; display.render(); LocalEvent & le = LocalEvent::Get(); // message loop while ( le.HandleEvents() ) { if ( ( le.MouseClickLeft( prev_list ) || HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_LEFT ) ) && _startSpellIndex > 0 ) { _startSpellIndex -= spellsPerPage * 2; redraw = true; } else if ( ( le.MouseClickLeft( next_list ) || HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_RIGHT ) ) && displayedSpells.size() > ( _startSpellIndex + ( spellsPerPage * 2 ) ) ) { _startSpellIndex += spellsPerPage * 2; redraw = true; } else if ( le.MouseClickLeft( info_rt ) ) { std::string str = _( "Your hero has %{point} spell points remaining." ); StringReplace( str, "%{point}", hero.GetSpellPoints() ); fheroes2::showStandardTextMessage( "", str, Dialog::OK ); } else if ( le.MouseClickLeft( advn_rt ) && _spellFilter != Filter::ADVN && displayableSpells != Filter::CMBT ) { _spellFilter = Filter::ADVN; _startSpellIndex = 0; displayedSpells = SetFilter( _spellFilter, &hero ); redraw = true; } else if ( le.MouseClickLeft( cmbt_rt ) && _spellFilter != Filter::CMBT && displayableSpells != Filter::ADVN ) { _spellFilter = Filter::CMBT; _startSpellIndex = 0; displayedSpells = SetFilter( _spellFilter, &hero ); redraw = true; } else if ( le.MousePressRight( info_rt ) ) { std::string str = _( "Your hero has %{point} spell points remaining." ); StringReplace( str, "%{point}", hero.GetSpellPoints() ); fheroes2::showStandardTextMessage( "", str, Dialog::ZERO ); } else if ( le.MousePressRight( advn_rt ) && displayableSpells != Filter::CMBT ) { fheroes2::showStandardTextMessage( "", _( "View Adventure Spells" ), Dialog::ZERO ); } else if ( le.MousePressRight( cmbt_rt ) && displayableSpells != Filter::ADVN ) { fheroes2::showStandardTextMessage( "", _( "View Combat Spells" ), Dialog::ZERO ); } else if ( le.MousePressRight( prev_list ) ) { fheroes2::showStandardTextMessage( "", _( "View previous page" ), Dialog::ZERO ); } else if ( le.MousePressRight( next_list ) ) { fheroes2::showStandardTextMessage( "", _( "View next page" ), Dialog::ZERO ); } else if ( le.MouseClickLeft( clos_rt ) || Game::HotKeyCloseWindow() ) break; else if ( le.MouseClickLeft( pos ) ) { const int32_t index = GetRectIndex( coords, le.GetMouseCursor() ); if ( 0 <= index ) { const SpellStorage::const_iterator spell = displayedSpells.begin() + ( index + _startSpellIndex ); if ( spell < displayedSpells.end() ) { if ( canCastSpell ) { std::string str; if ( hero.CanCastSpell( *spell, &str ) ) { curspell = *spell; break; } fheroes2::showStandardTextMessage( spell->GetName(), str, Dialog::OK ); display.render(); } else { fheroes2::SpellDialogElement( *spell, &hero ).showPopup( Dialog::OK ); display.render(); } } } } if ( statusCallback != nullptr ) { if ( le.MouseCursor( info_rt ) ) { std::string str = _( "Your hero has %{point} spell points remaining." ); StringReplace( str, "%{point}", hero.GetSpellPoints() ); ( *statusCallback )( str ); } else if ( le.MouseCursor( advn_rt ) && displayableSpells != Filter::CMBT ) { ( *statusCallback )( _( "View Adventure Spells" ) ); } else if ( le.MouseCursor( cmbt_rt ) && displayableSpells != Filter::ADVN ) { ( *statusCallback )( _( "View Combat Spells" ) ); } else if ( le.MouseCursor( prev_list ) ) { ( *statusCallback )( _( "View previous page" ) ); } else if ( le.MouseCursor( next_list ) ) { ( *statusCallback )( _( "View next page" ) ); } else if ( le.MouseCursor( clos_rt ) ) { ( *statusCallback )( _( "Close Spellbook" ) ); } else if ( le.MouseCursor( pos ) ) { const int32_t index = GetRectIndex( coords, le.GetMouseCursor() ); if ( 0 <= index && index + _startSpellIndex < displayedSpells.size() ) { const Spell & spell = displayedSpells[index + _startSpellIndex]; std::string str = _( "View %{spell}" ); StringReplace( str, "%{spell}", spell.GetName() ); ( *statusCallback )( str ); } else if ( displayableSpells == Filter::CMBT ) { ( *statusCallback )( _( "View Combat Spells" ) ); } else if ( displayableSpells == Filter::ADVN ) { ( *statusCallback )( _( "View Adventure Spells" ) ); } else { ( *statusCallback )( _( "View Spells" ) ); } } } if ( le.MousePressRight( pos ) ) { const int32_t index = GetRectIndex( coords, le.GetMouseCursor() ); if ( 0 <= index ) { const SpellStorage::const_iterator spell = displayedSpells.begin() + ( index + _startSpellIndex ); if ( spell < displayedSpells.end() ) { fheroes2::SpellDialogElement( *spell, &hero ).showPopup( Dialog::ZERO ); display.render(); } } } if ( redraw ) { restorer.restore(); restorer.update( restorerRoi.x, restorerRoi.y, restorerRoi.width, restorerRoi.height ); SpellBookRedrawLists( displayedSpells, coords, _startSpellIndex, pos.getPosition(), hero.GetSpellPoints(), displayableSpells, hero, canCastSpell ); display.render(); redraw = false; } } restorer.restore(); display.render(); return curspell; } void SpellBook::Edit( const HeroBase & hero ) { // Editing spells in a spell book does not require memorizing last open page. fheroes2::Display & display = fheroes2::Display::instance(); // setup cursor const CursorRestorer cursorRestorer( true, Cursor::POINTER ); size_t current_index = 0; SpellStorage displayedSpells = SetFilter( Filter::ALL, &hero ); const fheroes2::Sprite & bookmark_clos = fheroes2::AGG::GetICN( ICN::BOOK, 5 ); const fheroes2::Sprite & bookPage = fheroes2::AGG::GetICN( ICN::BOOK, 0 ); const fheroes2::Size spellBookSize = getSpellBookSize( Filter::ALL ); const fheroes2::Rect pos( ( display.width() - ( bookPage.width() * 2 ) ) / 2, ( display.height() - bookPage.height() ) / 2, spellBookSize.width, spellBookSize.height ); const fheroes2::Rect restorerRoi( pos.x + spellBookShadow.x, pos.y, pos.width - spellBookShadow.x, pos.height + spellBookShadow.y ); fheroes2::ImageRestorer restorer( display, restorerRoi.x, restorerRoi.y, restorerRoi.width, restorerRoi.height ); const fheroes2::Rect prev_list( pos.x + 30, pos.y + 8, 30, 25 ); const fheroes2::Rect next_list( pos.x + 410, pos.y + 8, 30, 25 ); const fheroes2::Rect clos_rt( pos.x + 420, pos.y + 284, bookmark_clos.width(), bookmark_clos.height() ); std::vector coords; coords.reserve( spellsPerPage * 2 ); SpellBookRedrawLists( displayedSpells, coords, current_index, pos.getPosition(), hero.GetSpellPoints(), Filter::ALL, hero, false ); bool redraw = false; display.render(); LocalEvent & le = LocalEvent::Get(); // message loop while ( le.HandleEvents() ) { if ( le.MouseClickLeft( prev_list ) && current_index ) { current_index -= spellsPerPage * 2; redraw = true; } else if ( le.MouseClickLeft( next_list ) && size() > ( current_index + spellsPerPage * 2 ) ) { current_index += spellsPerPage * 2; redraw = true; } else if ( le.MouseClickLeft( clos_rt ) || Game::HotKeyPressEvent( Game::HotKeyEvent::DEFAULT_CANCEL ) ) break; else if ( le.MouseClickLeft( pos ) ) { const int32_t index = GetRectIndex( coords, le.GetMouseCursor() ); if ( 0 <= index ) { const SpellStorage::const_iterator spell = displayedSpells.begin() + ( index + current_index ); if ( spell < displayedSpells.end() ) { fheroes2::SpellDialogElement( *spell, &hero ).showPopup( Dialog::OK ); redraw = true; } } else { const Spell spell = Dialog::selectSpell( Spell::NONE, false ); Append( spell ); displayedSpells = SetFilter( Filter::ALL, &hero ); redraw = true; } } if ( le.MousePressRight( pos ) ) { const int32_t index = GetRectIndex( coords, le.GetMouseCursor() ); if ( 0 <= index ) { const SpellStorage::const_iterator spell = displayedSpells.begin() + ( index + current_index ); if ( spell < displayedSpells.end() ) { fheroes2::SpellDialogElement( *spell, &hero ).showPopup( Dialog::ZERO ); redraw = true; } } } if ( redraw ) { restorer.restore(); restorer.update( restorerRoi.x, restorerRoi.y, restorerRoi.width, restorerRoi.height ); SpellBookRedrawLists( displayedSpells, coords, current_index, pos.getPosition(), hero.GetSpellPoints(), Filter::ALL, hero, false ); display.render(); redraw = false; } } restorer.restore(); display.render(); } void SpellBook::resetState() { _startSpellIndex = 0; _spellFilter = Filter::ADVN; } SpellStorage SpellBook::SetFilter( const Filter filter, const HeroBase * hero ) const { const SpellStorage & storage = *this; SpellStorage res = storage; // add heroes spell scrolls if ( hero != nullptr ) res.Append( hero->GetBagArtifacts() ); if ( filter != SpellBook::Filter::ALL ) { res.resize( std::distance( res.begin(), std::remove_if( res.begin(), res.end(), [filter]( const Spell & s ) { return ( ( SpellBook::Filter::ADVN == filter ) && s.isCombat() ) || ( ( SpellBook::Filter::CMBT == filter ) && !s.isCombat() ); } ) ) ); } // sorting results std::sort( res.begin(), res.end() ); return res; } fheroes2-1.0.12+dfsg/src/fheroes2/spell/spell_book.h000066400000000000000000000047161456075706000221760ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2SPELLBOOK_H #define H2SPELLBOOK_H #include #include #include #include "spell.h" #include "spell_storage.h" class HeroBase; class SpellBook : public SpellStorage { public: enum class Filter : int { ADVN = 0x01, CMBT = 0x02, ALL = ADVN | CMBT }; Spell Open( const HeroBase & hero, const Filter displayableSpells, const bool canCastSpell, const bool restorePreviousState, const std::function * statusCallback ) const; void Edit( const HeroBase & hero ); void resetState(); SpellStorage SetFilter( const Filter filter, const HeroBase * hero = nullptr ) const; private: mutable size_t _startSpellIndex = 0; mutable Filter _spellFilter = Filter::ADVN; }; #endif fheroes2-1.0.12+dfsg/src/fheroes2/spell/spell_info.cpp000066400000000000000000000331461456075706000225310ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "spell_info.h" #include #include #include #include "artifact.h" #include "artifact_info.h" #include "castle.h" #include "color.h" #include "direction.h" #include "heroes.h" #include "heroes_base.h" #include "kingdom.h" #include "maps.h" #include "maps_tiles.h" #include "math_base.h" #include "monster.h" #include "mp2.h" #include "spell.h" #include "tools.h" #include "translations.h" #include "world.h" namespace { void updateSpellDescription( const Spell & spell, std::string & description ) { const uint32_t spellExtraValue = spell.ExtraValue(); switch ( spellExtraValue ) { case 1: StringReplace( description, "%{count}", _( "one" ) ); break; case 2: StringReplace( description, "%{count}", _( "two" ) ); break; default: StringReplace( description, "%{count}", spellExtraValue ); break; } } } namespace fheroes2 { uint32_t getSpellDamage( const Spell & spell, const uint32_t spellPower, const HeroBase * hero ) { assert( spellPower > 0 ); uint32_t damage = spell.Damage() * spellPower; if ( hero == nullptr ) { return damage; } ArtifactBonusType type = ArtifactBonusType::NONE; switch ( spell.GetID() ) { case Spell::COLDRAY: case Spell::COLDRING: type = ArtifactBonusType::COLD_SPELL_EXTRA_EFFECTIVENESS_PERCENT; break; case Spell::FIREBALL: case Spell::FIREBLAST: type = ArtifactBonusType::FIRE_SPELL_EXTRA_EFFECTIVENESS_PERCENT; break; case Spell::LIGHTNINGBOLT: case Spell::CHAINLIGHTNING: type = ArtifactBonusType::LIGHTNING_SPELL_EXTRA_EFFECTIVENESS_PERCENT; break; default: break; } if ( type == ArtifactBonusType::NONE ) { return damage; } const std::vector extraDamagePercentage = hero->GetBagArtifacts().getTotalArtifactMultipliedPercent( type ); for ( const int32_t value : extraDamagePercentage ) { damage = damage * ( 100 + value ) / 100; } return damage; } uint32_t getSummonMonsterCount( const Spell & spell, const uint32_t spellPower, const HeroBase * hero ) { assert( spellPower > 0 ); uint32_t monsterCount = spell.ExtraValue() * spellPower; if ( hero == nullptr ) { return monsterCount; } const std::vector summonSpellExtraEffectPercent = hero->GetBagArtifacts().getTotalArtifactMultipliedPercent( ArtifactBonusType::SUMMONING_SPELL_EXTRA_EFFECTIVENESS_PERCENT ); for ( const int32_t value : summonSpellExtraEffectPercent ) { monsterCount = monsterCount * ( 100 + value ) / 100; } return monsterCount; } uint32_t getHPRestorePoints( const Spell & spell, const uint32_t spellPower, const HeroBase * hero ) { (void)hero; assert( spellPower > 0 ); return spell.Restore() * spellPower; } uint32_t getResurrectPoints( const Spell & spell, const uint32_t spellPower, const HeroBase * hero ) { assert( spellPower > 0 ); uint32_t resurrectionPoints = spell.Resurrect() * spellPower; if ( hero == nullptr ) { return resurrectionPoints; } const std::vector extraSpellEffectivenessPercent = hero->GetBagArtifacts().getTotalArtifactMultipliedPercent( ArtifactBonusType::RESURRECT_SPELL_EXTRA_EFFECTIVENESS_PERCENT ); for ( const int32_t value : extraSpellEffectivenessPercent ) { resurrectionPoints = resurrectionPoints * ( 100 + value ) / 100; } return resurrectionPoints; } uint32_t getGuardianMonsterCount( const Spell & spell, const uint32_t spellPower, const HeroBase * hero ) { (void)hero; assert( spellPower > 0 ); return spell.ExtraValue() * spellPower; } uint32_t getHypnotizeMonsterHPPoints( const Spell & spell, const uint32_t spellPower, const HeroBase * hero ) { assert( spell == Spell::HYPNOTIZE ); assert( spellPower > 0 ); uint32_t hpPoints = spell.ExtraValue() * spellPower; if ( hero != nullptr ) { const std::vector extraEffectiveness = hero->GetBagArtifacts().getTotalArtifactMultipliedPercent( fheroes2::ArtifactBonusType::HYPNOTIZE_SPELL_EXTRA_EFFECTIVENESS_PERCENT ); for ( const int32_t value : extraEffectiveness ) { hpPoints = hpPoints * ( 100 + value ) / 100; } } return hpPoints; } const Castle * getNearestCastleTownGate( const Heroes & hero ) { const Kingdom & kingdom = hero.GetKingdom(); const VecCastles & castles = kingdom.GetCastles(); const Point & heroPosition = hero.GetCenter(); int32_t minDistance = -1; const Castle * nearestCastle = nullptr; for ( const Castle * castle : castles ) { if ( castle == nullptr ) { continue; } const Point & castlePosition = castle->GetCenter(); const int32_t offsetX = heroPosition.x - castlePosition.x; const int32_t offsetY = heroPosition.y - castlePosition.y; const int32_t distance = offsetX * offsetX + offsetY * offsetY; if ( minDistance < 0 || distance < minDistance ) { minDistance = distance; nearestCastle = castle; } } return nearestCastle; } std::string getSpellDescription( const Spell & spell, const HeroBase * hero ) { std::string description = spell.GetDescription(); updateSpellDescription( spell, description ); if ( hero == nullptr ) { return description; } if ( spell.isDamage() ) { description += "\n\n"; description += _( "This spell does %{damage} points of damage." ); StringReplace( description, "%{damage}", getSpellDamage( spell, hero->GetPower(), hero ) ); return description; } if ( spell.isSummon() ) { const Monster monster( spell ); if ( !monster.isValid() ) { // Did you add a new summoning spell but forgot to add corresponding monster? assert( 0 ); return spell.GetDescription(); } const uint32_t summonCount = getSummonMonsterCount( spell, hero->GetPower(), hero ); description += "\n\n"; description += _( "This spell summons\n%{count} %{monster}." ); StringReplace( description, "%{count}", summonCount ); StringReplace( description, "%{monster}", monster.GetPluralName( summonCount ) ); return description; } if ( spell.isRestore() ) { description += "\n\n"; description += _( "This spell restores %{hp} HP." ); StringReplace( description, "%{hp}", getHPRestorePoints( spell, hero->GetPower(), hero ) ); return description; } if ( spell.isResurrect() ) { description += "\n\n"; description += _( "This spell restores %{hp} HP." ); StringReplace( description, "%{hp}", getResurrectPoints( spell, hero->GetPower(), hero ) ); return description; } if ( spell.isGuardianType() ) { const Monster monster( spell ); if ( !monster.isValid() ) { // Did you add a new guardian spell but forgot to add corresponding monster? assert( 0 ); return spell.GetDescription(); } const uint32_t guardianCount = getGuardianMonsterCount( spell, hero->GetPower(), hero ); description += "\n\n"; description += _( "This spell summons %{count} %{monster} to guard the mine." ); StringReplace( description, "%{count}", guardianCount ); StringReplace( description, "%{monster}", monster.GetPluralName( guardianCount ) ); return description; } if ( spell == Spell::TOWNGATE ) { const Heroes * realHero = dynamic_cast( hero ); if ( realHero == nullptr ) { return description; } const Castle * castle = getNearestCastleTownGate( *realHero ); if ( castle == nullptr ) { return description; } description += "\n\n"; description += _( "The nearest town is %{town}." ); StringReplace( description, "%{town}", castle->GetName() ); const Heroes * townHero = castle->GetHero(); if ( townHero != nullptr ) { description += "\n\n"; std::string extraLine = _( "This town is occupied by your hero %{hero}." ); StringReplace( extraLine, "%{town}", castle->GetName() ); StringReplace( extraLine, "%{hero}", townHero->GetName() ); description += extraLine; } return description; } if ( spell == Spell::HYPNOTIZE ) { description += "\n\n"; description += _( "This spell controls up to\n%{hp} HP." ); StringReplace( description, "%{hp}", getHypnotizeMonsterHPPoints( spell, hero->GetPower(), hero ) ); return description; } return description; } int32_t getPossibleBoatPosition( const Heroes & hero ) { if ( !hero.MayCastAdventureSpells() ) { return -1; } const int32_t center = hero.GetIndex(); const int tilePassability = world.GetTiles( center ).GetPassable(); const MapsIndexes tilesAround = Maps::GetFreeIndexesAroundTile( center ); std::vector possibleBoatPositions; for ( const int32_t tileId : tilesAround ) { const int direction = Maps::GetDirection( center, tileId ); assert( direction != Direction::UNKNOWN ); if ( ( tilePassability & direction ) != 0 ) { possibleBoatPositions.emplace_back( tileId ); } } const fheroes2::Point & centerPoint = Maps::GetPoint( center ); std::sort( possibleBoatPositions.begin(), possibleBoatPositions.end(), [¢erPoint]( const int32_t left, const int32_t right ) { const fheroes2::Point & leftPoint = Maps::GetPoint( left ); const fheroes2::Point & rightPoint = Maps::GetPoint( right ); const int32_t leftDiffX = leftPoint.x - centerPoint.x; const int32_t leftDiffY = leftPoint.y - centerPoint.y; const int32_t rightDiffX = rightPoint.x - centerPoint.x; const int32_t rightDiffY = rightPoint.y - centerPoint.y; return ( leftDiffX * leftDiffX + leftDiffY * leftDiffY ) < ( rightDiffX * rightDiffX + rightDiffY * rightDiffY ); } ); for ( const int32_t tileId : possibleBoatPositions ) { const Maps::Tiles & tile = world.GetTiles( tileId ); if ( tile.isWater() ) { return tileId; } } return -1; } int32_t getSummonableBoat( const Heroes & hero ) { const int32_t center = hero.GetIndex(); const int heroColor = hero.GetColor(); for ( const int32_t boatSource : Maps::GetObjectPositions( center, MP2::OBJ_BOAT, false ) ) { assert( Maps::isValidAbsIndex( boatSource ) ); const int boatColor = world.GetTiles( boatSource ).getBoatOwnerColor(); if ( boatColor != Color::NONE && boatColor != heroColor ) { continue; } const uint32_t distance = Maps::GetStraightLineDistance( boatSource, center ); if ( distance > 1 ) { return boatSource; } } return -1; } bool isHeroNearWater( const Heroes & hero ) { const MapsIndexes tilesAround = Maps::getAroundIndexes( hero.GetIndex() ); return std::any_of( tilesAround.begin(), tilesAround.end(), []( const int32_t tileId ) { return world.GetTiles( tileId ).isWater(); } ); } } fheroes2-1.0.12+dfsg/src/fheroes2/spell/spell_info.h000066400000000000000000000050661456075706000221760ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2_SPELL_INFO_H #define H2_SPELL_INFO_H #include #include class Castle; class HeroBase; class Heroes; class Spell; namespace fheroes2 { uint32_t getSpellDamage( const Spell & spell, const uint32_t spellPower, const HeroBase * hero ); uint32_t getSummonMonsterCount( const Spell & spell, const uint32_t spellPower, const HeroBase * hero ); uint32_t getHPRestorePoints( const Spell & spell, const uint32_t spellPower, const HeroBase * hero ); uint32_t getResurrectPoints( const Spell & spell, const uint32_t spellPower, const HeroBase * hero ); uint32_t getGuardianMonsterCount( const Spell & spell, const uint32_t spellPower, const HeroBase * hero ); uint32_t getHypnotizeMonsterHPPoints( const Spell & spell, const uint32_t spellPower, const HeroBase * hero ); const Castle * getNearestCastleTownGate( const Heroes & hero ); std::string getSpellDescription( const Spell & spell, const HeroBase * hero ); int32_t getPossibleBoatPosition( const Heroes & hero ); int32_t getSummonableBoat( const Heroes & hero ); bool isHeroNearWater( const Heroes & hero ); } #endif fheroes2-1.0.12+dfsg/src/fheroes2/spell/spell_storage.cpp000066400000000000000000000057401456075706000232410ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include "artifact.h" #include "spell_storage.h" SpellStorage::SpellStorage() { reserve( 67 ); } SpellStorage SpellStorage::GetSpells( int lvl ) const { SpellStorage result; result.reserve( 20 ); for ( const_iterator it = begin(); it != end(); ++it ) if ( lvl == -1 || ( *it ).isLevel( lvl ) ) result.push_back( *it ); return result; } void SpellStorage::Append( const Spell & sp ) { if ( sp != Spell::NONE && end() == std::find( begin(), end(), sp ) ) push_back( sp ); } void SpellStorage::Append( const SpellStorage & st ) { for ( const Spell & sp : st ) { if ( std::find( begin(), end(), sp ) == end() ) { push_back( sp ); } } } bool SpellStorage::isPresentSpell( const Spell & spell ) const { return end() != std::find( begin(), end(), spell ); } std::string SpellStorage::String() const { std::string output; for ( const_iterator it = begin(); it != end(); ++it ) { output += it->GetName(); output += ", "; } return output; } void SpellStorage::Append( const BagArtifacts & bag ) { for ( BagArtifacts::const_iterator it = bag.begin(); it != bag.end(); ++it ) Append( *it ); } void SpellStorage::Append( const Artifact & art ) { Append( Spell( art.getSpellId() ) ); } fheroes2-1.0.12+dfsg/src/fheroes2/spell/spell_storage.h000066400000000000000000000042451456075706000227050ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2SPELLSTORAGE_H #define H2SPELLSTORAGE_H #include #include #include "spell.h" class Artifact; class BagArtifacts; class SpellStorage : public std::vector { public: SpellStorage(); SpellStorage GetSpells( int lvl = -1 ) const; void Append( const SpellStorage & ); void Append( const Spell & ); void Append( const BagArtifacts & ); void Append( const Artifact & ); bool isPresentSpell( const Spell & ) const; std::string String() const; }; #endif fheroes2-1.0.12+dfsg/src/fheroes2/system/000077500000000000000000000000001456075706000200715ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/fheroes2/system/bitmodes.cpp000066400000000000000000000036071456075706000224110ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "bitmodes.h" #include "serialize.h" StreamBase & operator<<( StreamBase & msg, const BitModes & b ) { return msg << b.modes; } StreamBase & operator>>( StreamBase & msg, BitModes & b ) { return msg >> b.modes; } fheroes2-1.0.12+dfsg/src/fheroes2/system/bitmodes.h000066400000000000000000000051131456075706000220500ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2022 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2BITMODES_H #define H2BITMODES_H #include class StreamBase; class BitModes { public: BitModes() : modes( 0 ) {} void SetModes( const uint32_t f ) { modes |= f; } void ResetModes( const uint32_t f ) { modes &= ~f; } // Returns true if any of the requested modes is set, otherwise returns false bool Modes( const uint32_t f ) const { return ( modes & f ) != 0; } // Returns true if all the requested modes are set, otherwise returns false bool AllModes( const uint32_t f ) const { return ( modes & f ) == f && f != 0; } protected: friend StreamBase & operator<<( StreamBase &, const BitModes & ); friend StreamBase & operator>>( StreamBase &, BitModes & ); uint32_t modes; }; StreamBase & operator<<( StreamBase &, const BitModes & ); StreamBase & operator>>( StreamBase &, BitModes & ); #endif fheroes2-1.0.12+dfsg/src/fheroes2/system/gamedefs.h000066400000000000000000000042471456075706000220240ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2GAMEDEFS_H #define H2GAMEDEFS_H // hardcore defines: kingdom #define KINGDOMMAX 6 // hardcore defines: world #define DAYOFWEEK 7 #define WEEKOFMONTH 4 // hardcore defines: castle #define CASTLEMAXMONSTER 6 // hardcore defines: heroes #define HEROESMAXARTIFACT 14 #define HEROESMAXSKILL 8 // hardcore defines: skill #define MAXPRIMARYSKILL 4 #define MAXSECONDARYSKILL 14 // hardcore defines: interface #define RADARWIDTH 144 #define BORDERWIDTH 16 // ai/hero speed #define DEFAULT_BATTLE_SPEED 4 #endif fheroes2-1.0.12+dfsg/src/fheroes2/system/players.cpp000066400000000000000000000355321456075706000222640ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2011 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "players.h" #include #include #include #include #include "ai.h" #include "castle.h" #include "game.h" #include "game_io.h" #include "gamedefs.h" #include "heroes.h" #include "logging.h" #include "maps.h" #include "maps_fileinfo.h" #include "normal/ai_normal.h" #include "race.h" #include "rand.h" #include "save_format_version.h" #include "serialize.h" #include "settings.h" #include "world.h" namespace { const int playersSize = KINGDOMMAX + 1; Player * _players[playersSize] = { nullptr }; int humanColors{ Color::NONE }; enum { ST_INGAME = 0x2000 }; } void PlayerFocusReset( Player * player ) { if ( player ) player->GetFocus().Reset(); } void PlayerFixMultiControl( Player * player ) { if ( player && player->GetControl() == ( CONTROL_HUMAN | CONTROL_AI ) ) player->SetControl( CONTROL_AI ); } void PlayerRemoveAlreadySelectedRaces( const Player * player, std::vector & availableRaces ) { const int raceToRemove = player->GetRace(); availableRaces.erase( remove_if( availableRaces.begin(), availableRaces.end(), [raceToRemove]( const int race ) { return raceToRemove == race; } ), availableRaces.end() ); } void PlayerFixRandomRace( Player * player, std::vector & availableRaces ) { if ( player && player->GetRace() == Race::RAND ) { if ( availableRaces.empty() ) { player->SetRace( Race::Rand() ); } else { const int raceIndex = Rand::Get( 0, static_cast( availableRaces.size() - 1 ) ); player->SetRace( availableRaces[raceIndex] ); availableRaces.erase( availableRaces.begin() + raceIndex ); } } } bool Control::isControlAI() const { return ( CONTROL_AI & GetControl() ) != 0; } bool Control::isControlHuman() const { return ( CONTROL_HUMAN & GetControl() ) != 0; } Player::Player( int col ) : control( CONTROL_NONE ) , color( col ) , race( Race::NONE ) , friends( col ) , _ai( std::make_shared() ) , _handicapStatus( HandicapStatus::NONE ) #if defined( WITH_DEBUG ) , _isAIAutoControlMode( false ) , _isAIAutoControlModePlanned( false ) #endif { // Do nothing. } std::string Player::GetDefaultName() const { return Color::String( color ); } std::string Player::GetName() const { if ( name.empty() ) { return GetDefaultName(); } return name; } int Player::GetControl() const { #if defined( WITH_DEBUG ) if ( _isAIAutoControlMode ) { assert( ( control & CONTROL_HUMAN ) == CONTROL_HUMAN ); return CONTROL_AI; } #endif return control; } std::string Player::GetPersonalityString() const { return _ai->GetPersonalityString(); } bool Player::isPlay() const { return Modes( ST_INGAME ); } void Player::SetName( const std::string & newName ) { if ( newName == GetDefaultName() ) { name.clear(); } else { name = newName; } } void Player::SetPlay( bool f ) { if ( f ) SetModes( ST_INGAME ); else ResetModes( ST_INGAME ); } void Player::setHandicapStatus( const HandicapStatus status ) { if ( status == HandicapStatus::NONE ) { _handicapStatus = status; return; } assert( !( control & CONTROL_AI ) ); _handicapStatus = status; } #if defined( WITH_DEBUG ) void Player::setAIAutoControlMode( const bool enable ) { assert( ( control & CONTROL_HUMAN ) == CONTROL_HUMAN ); // If this mode should be enabled, then it happens immediately if ( enable ) { _isAIAutoControlMode = enable; } // Otherwise, the change is first planned and then committed, in which case this mode should be actually enabled else { assert( _isAIAutoControlMode ); } _isAIAutoControlModePlanned = enable; } void Player::commitAIAutoControlMode() { assert( ( control & CONTROL_HUMAN ) == CONTROL_HUMAN ); // If this method has been called, then this mode should be actually enabled assert( _isAIAutoControlMode ); _isAIAutoControlMode = _isAIAutoControlModePlanned; } #endif StreamBase & operator<<( StreamBase & msg, const Focus & focus ) { msg << focus.first; switch ( focus.first ) { case FOCUS_HEROES: msg << static_cast( focus.second )->GetIndex(); break; case FOCUS_CASTLE: msg << static_cast( focus.second )->GetIndex(); break; default: msg << static_cast( -1 ); break; } return msg; } StreamBase & operator>>( StreamBase & msg, Focus & focus ) { int32_t index; msg >> focus.first >> index; switch ( focus.first ) { case FOCUS_HEROES: focus.second = world.GetHeroes( Maps::GetPoint( index ) ); break; case FOCUS_CASTLE: focus.second = world.getCastle( Maps::GetPoint( index ) ); break; default: focus.second = nullptr; break; } return msg; } StreamBase & operator<<( StreamBase & msg, const Player & player ) { const BitModes & modes = player; assert( player._ai != nullptr ); msg << modes << player.control << player.color << player.race << player.friends << player.name << player.focus << *player._ai << static_cast( player._handicapStatus ); return msg; } StreamBase & operator>>( StreamBase & msg, Player & player ) { BitModes & modes = player; msg >> modes; static_assert( LAST_SUPPORTED_FORMAT_VERSION < FORMAT_VERSION_1009_RELEASE, "Remove the logic below." ); if ( Game::GetVersionOfCurrentSaveFile() < FORMAT_VERSION_1009_RELEASE ) { uint32_t temp; msg >> temp; } msg >> player.control >> player.color >> player.race >> player.friends >> player.name >> player.focus; assert( player._ai ); msg >> *player._ai; uint8_t handicapStatusInt; msg >> handicapStatusInt; player._handicapStatus = static_cast( handicapStatusInt ); return msg; } Players::Players() { reserve( KINGDOMMAX ); } Players::~Players() { clear(); } void Players::clear() { for ( iterator it = begin(); it != end(); ++it ) delete *it; std::vector::clear(); for ( uint32_t ii = 0; ii < KINGDOMMAX + 1; ++ii ) _players[ii] = nullptr; _currentColor = Color::NONE; humanColors = Color::NONE; } void Players::Init( int colors ) { clear(); const Colors vcolors( colors ); for ( Colors::const_iterator it = vcolors.begin(); it != vcolors.end(); ++it ) { push_back( new Player( *it ) ); _players[Color::GetIndex( *it )] = back(); } DEBUG_LOG( DBG_GAME, DBG_INFO, "Players: " << String() ) } void Players::Init( const Maps::FileInfo & fi ) { if ( fi.kingdomColors == 0 ) { DEBUG_LOG( DBG_GAME, DBG_INFO, "No players are set." ) return; } clear(); const Colors vcolors( fi.kingdomColors ); Player * first = nullptr; for ( const int color : vcolors ) { Player * player = new Player( color ); player->SetRace( fi.KingdomRace( color ) ); player->SetControl( CONTROL_AI ); player->SetFriends( color | fi.unions[Color::GetIndex( color )] ); if ( ( color & fi.HumanOnlyColors() ) && Settings::Get().IsGameType( Game::TYPE_MULTI ) ) player->SetControl( CONTROL_HUMAN ); else if ( color & fi.colorsAvailableForHumans ) player->SetControl( player->GetControl() | CONTROL_HUMAN ); if ( !first && ( player->GetControl() & CONTROL_HUMAN ) ) first = player; push_back( player ); _players[Color::GetIndex( color )] = back(); } if ( first ) first->SetControl( CONTROL_HUMAN ); DEBUG_LOG( DBG_GAME, DBG_INFO, "Players: " << String() ) } void Players::Set( const int color, Player * player ) { assert( color >= 0 && color < playersSize ); _players[color] = player; } Player * Players::Get( int color ) { return _players[Color::GetIndex( color )]; } bool Players::isFriends( int player, int colors ) { const Player * ptr = Get( player ); return ptr ? ( ptr->GetFriends() & colors ) != 0 : false; } void Players::SetPlayerRace( int color, int race ) { Player * player = Get( color ); if ( player ) player->SetRace( race ); } void Players::SetPlayerControl( int color, int ctrl ) { Player * player = Get( color ); if ( player ) player->SetControl( ctrl ); } int Players::GetColors( int control, bool strong ) const { int res = 0; for ( const_iterator it = begin(); it != end(); ++it ) if ( control == 0xFF || ( strong && ( *it )->GetControl() == control ) || ( !strong && ( ( *it )->GetControl() & control ) ) ) res |= ( *it )->GetColor(); return res; } int Players::GetActualColors() const { int res = 0; for ( const_iterator it = begin(); it != end(); ++it ) if ( ( *it )->isPlay() ) res |= ( *it )->GetColor(); return res; } const std::vector & Players::getVector() const { return *this; } Player * Players::GetCurrent() { return Get( _currentColor ); } const Player * Players::GetCurrent() const { return Get( _currentColor ); } int Players::GetPlayerFriends( int color ) { const Player * player = Get( color ); return player ? player->GetFriends() : 0; } int Players::GetPlayerControl( int color ) { const Player * player = Get( color ); return player ? player->GetControl() : CONTROL_NONE; } int Players::GetPlayerRace( int color ) { const Player * player = Get( color ); return player ? player->GetRace() : Race::NONE; } bool Players::GetPlayerInGame( int color ) { const Player * player = Get( color ); return player && player->isPlay(); } std::vector Players::getInPlayOpponents( const int color ) { std::vector opponentColors; const Player * playerOfColor = Players::Get( color ); assert( playerOfColor != nullptr ); const int friends = playerOfColor->GetFriends(); for ( const Player * player : Settings::Get().GetPlayers() ) { assert( player != nullptr ); const int currentColor = player->GetColor(); if ( player->isPlay() && ( ( currentColor & friends ) == 0 ) ) { opponentColors.emplace_back( currentColor ); } } return opponentColors; } void Players::SetPlayerInGame( int color, bool f ) { Player * player = Get( color ); if ( player ) player->SetPlay( f ); } void Players::SetStartGame() { vector races = { Race::KNGT, Race::BARB, Race::SORC, Race::WRLK, Race::WZRD, Race::NECR }; for_each( begin(), end(), []( Player * player ) { player->SetPlay( true ); } ); for_each( begin(), end(), []( Player * player ) { PlayerFocusReset( player ); } ); for_each( begin(), end(), [&races]( const Player * player ) { PlayerRemoveAlreadySelectedRaces( player, races ); } ); for_each( begin(), end(), [&races]( Player * player ) { PlayerFixRandomRace( player, races ); } ); for_each( begin(), end(), []( Player * player ) { PlayerFixMultiControl( player ); } ); _currentColor = Color::NONE; humanColors = Color::NONE; DEBUG_LOG( DBG_GAME, DBG_INFO, String() ) } int Players::HumanColors() { if ( humanColors == Color::NONE ) { humanColors = Settings::Get().GetPlayers().GetColors( CONTROL_HUMAN, true ); } return humanColors; } int Players::FriendColors() { const Player * player = Settings::Get().GetPlayers().GetCurrent(); if ( player ) { return player->GetFriends(); } return 0; } void Players::setCurrentColor( const int color ) { // We can set only one of 6 player colors ( BLUE | GREEN | RED | YELLOW | ORANGE | PURPLE ) or NONE (neutral player). assert( Color::Count( color ) == 1 || color == Color::NONE ); _currentColor = color; } std::string Players::String() const { std::ostringstream os; os << "Players: "; for ( const_iterator it = begin(); it != end(); ++it ) { os << Color::String( ( *it )->GetColor() ) << "(" << Race::String( ( *it )->GetRace() ) << ", "; switch ( ( *it )->GetControl() ) { case CONTROL_AI | CONTROL_HUMAN: os << "ai|human, " << ( *it )->GetPersonalityString(); break; case CONTROL_AI: os << "ai, " << ( *it )->GetPersonalityString(); break; case CONTROL_HUMAN: os << "human"; break; default: os << "unknown"; break; } os << ")" << ", "; } return os.str(); } StreamBase & operator<<( StreamBase & msg, const Players & players ) { msg << players.GetColors() << players.getCurrentColor(); for ( Players::const_iterator it = players.begin(); it != players.end(); ++it ) msg << ( **it ); return msg; } StreamBase & operator>>( StreamBase & msg, Players & players ) { int colors; int current; msg >> colors >> current; players.clear(); players.setCurrentColor( current ); const Colors vcolors( colors ); for ( uint32_t ii = 0; ii < vcolors.size(); ++ii ) { Player * player = new Player(); msg >> *player; Players::Set( Color::GetIndex( player->GetColor() ), player ); players.push_back( player ); } return msg; } fheroes2-1.0.12+dfsg/src/fheroes2/system/players.h000066400000000000000000000160101456075706000217170ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2011 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2PLAYERS_H #define H2PLAYERS_H #include #include #include #include #include #include "bitmodes.h" #include "color.h" class StreamBase; namespace Maps { struct FileInfo; } namespace AI { class Base; } class Castle; class Heroes; // control_t enum { CONTROL_NONE = 0, CONTROL_HUMAN = 1, // CONTROL_REMOTE = 2, CONTROL_AI = 4 }; enum { FOCUS_UNSEL = 0, FOCUS_HEROES = 1, FOCUS_CASTLE = 2 }; struct Focus : std::pair { Focus() : std::pair( FOCUS_UNSEL, nullptr ) {} void Reset() { first = FOCUS_UNSEL; second = nullptr; } void Set( Castle * ptr ) { first = FOCUS_CASTLE; second = ptr; } void Set( Heroes * ptr ) { first = FOCUS_HEROES; second = ptr; } Castle * GetCastle() { return first == FOCUS_CASTLE && second ? static_cast( second ) : nullptr; } Heroes * GetHeroes() { return first == FOCUS_HEROES && second ? static_cast( second ) : nullptr; } }; struct Control { virtual int GetControl() const = 0; virtual ~Control() = default; bool isControlAI() const; bool isControlHuman() const; }; class Player : public BitModes, public Control { public: enum class HandicapStatus : uint8_t { NONE, // No strings attached. MILD, // 15% fewer resources per turn SEVERE, // 30% fewer resources per turn }; explicit Player( int col = Color::NONE ); ~Player() override = default; bool isColor( int col ) const { return col == color; } bool isPlay() const; void SetColor( int cl ) { color = cl; } void SetRace( int r ) { race = r; } void SetControl( int ctl ) { control = ctl; } void SetPlay( bool ); void SetFriends( int f ) { friends = f; } void SetName( const std::string & newName ); int GetControl() const override; int GetColor() const { return color; } int GetRace() const { return race; } int GetFriends() const { return friends; } std::string GetDefaultName() const; std::string GetName() const; std::string GetPersonalityString() const; Focus & GetFocus() { return focus; } HandicapStatus getHandicapStatus() const { return _handicapStatus; } void setHandicapStatus( const HandicapStatus status ); #if defined( WITH_DEBUG ) bool isAIAutoControlMode() const { return _isAIAutoControlMode; } // Sets whether a given human player is controlled by AI. See the implementation for details. void setAIAutoControlMode( const bool enable ); // Turns the planned value of whether a given human player is controlled by AI into the actual value. // Should be called only if this mode is actually enabled. void commitAIAutoControlMode(); #endif protected: friend StreamBase & operator<<( StreamBase &, const Player & ); friend StreamBase & operator>>( StreamBase &, Player & ); int control; int color; int race; int friends; std::string name; Focus focus; std::shared_ptr _ai; HandicapStatus _handicapStatus; #if defined( WITH_DEBUG ) // These members should not be saved anywhere // Actual value of whether a given human player is controlled by AI bool _isAIAutoControlMode; // Planned value of whether a given human player is controlled by AI (will become actual upon committing it) bool _isAIAutoControlModePlanned; #endif }; StreamBase & operator<<( StreamBase &, const Player & ); StreamBase & operator>>( StreamBase &, Player & ); class Players : public std::vector { public: Players(); Players( const Players & ) = delete; ~Players(); Players & operator=( const Players & ) = delete; void Init( int colors ); void Init( const Maps::FileInfo & ); void clear(); void SetStartGame(); int GetColors( int control = 0xFF, bool strong = false ) const; int GetActualColors() const; std::string String() const; const std::vector & getVector() const; Player * GetCurrent(); const Player * GetCurrent() const; static void Set( const int color, Player * player ); static Player * Get( int color ); static int GetPlayerControl( int color ); static int GetPlayerRace( int color ); static int GetPlayerFriends( int color ); static bool GetPlayerInGame( int color ); static std::vector getInPlayOpponents( const int color ); static bool isFriends( int player, int colors ); static void SetPlayerRace( int color, int race ); static void SetPlayerControl( int color, int ctrl ); static void SetPlayerInGame( int color, bool ); static int HumanColors(); // Return current player friends colors, if player does not exist he has no friends (returns 0). static int FriendColors(); int getCurrentColor() const { return _currentColor; } // The color should belong to one player or be NONE (neutral player). void setCurrentColor( const int color ); private: int _currentColor{ Color::NONE }; }; StreamBase & operator<<( StreamBase &, const Players & ); StreamBase & operator>>( StreamBase &, Players & ); #endif fheroes2-1.0.12+dfsg/src/fheroes2/system/save_format_version.h000066400000000000000000000042561456075706000243240ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include enum SaveFileFormat : uint16_t { // !!! IMPORTANT !!! // If you're adding a new version you must assign it to CURRENT_FORMAT_VERSION located at the bottom. // If you're removing an old version you must assign the oldest available to LAST_SUPPORTED_FORMAT_VERSION located at the bottom. FORMAT_VERSION_1100_RELEASE = 10016, FORMAT_VERSION_1010_RELEASE = 10015, FORMAT_VERSION_1009_RELEASE = 10014, FORMAT_VERSION_PRE2_1009_RELEASE = 10013, FORMAT_VERSION_PRE1_1009_RELEASE = 10012, FORMAT_VERSION_1007_RELEASE = 10011, FORMAT_VERSION_1005_RELEASE = 10010, LAST_SUPPORTED_FORMAT_VERSION = FORMAT_VERSION_1005_RELEASE, CURRENT_FORMAT_VERSION = FORMAT_VERSION_1100_RELEASE }; fheroes2-1.0.12+dfsg/src/fheroes2/system/settings.cpp000066400000000000000000001025341456075706000224420ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #if defined( MACOS_APP_BUNDLE ) #include #endif #include "cursor.h" #include "difficulty.h" #include "game.h" #include "gamedefs.h" #include "logging.h" #include "render_processor.h" #include "screen.h" #include "serialize.h" #include "settings.h" #include "system.h" #include "tinyconfig.h" #include "translations.h" #include "ui_language.h" #include "version.h" #define STRINGIFY( DEF ) #DEF #define EXPANDDEF( DEF ) STRINGIFY( DEF ) namespace { enum : uint32_t { GLOBAL_FIRST_RUN = 0x00000001, GLOBAL_SHOW_INTRO = 0x00000002, GLOBAL_PRICELOYALTY = 0x00000004, GLOBAL_RENDER_VSYNC = 0x00000008, GLOBAL_TEXT_SUPPORT_MODE = 0x00000010, GLOBAL_MONOCHROME_CURSOR = 0x00000020, GLOBAL_SHOW_CONTROL_PANEL = 0x00000040, GLOBAL_SHOW_RADAR = 0x00000080, GLOBAL_SHOW_ICONS = 0x00000100, GLOBAL_SHOW_BUTTONS = 0x00000200, GLOBAL_SHOW_STATUS = 0x00000400, GLOBAL_FULLSCREEN = 0x00008000, GLOBAL_3D_AUDIO = 0x00010000, GLOBAL_SYSTEM_INFO = 0x00020000, GLOBAL_CURSOR_SOFT_EMULATION = 0x00040000, GLOBAL_EVIL_INTERFACE = 0x00080000, GLOBAL_HIDE_INTERFACE = 0x00100000, GLOBAL_BATTLE_SHOW_DAMAGE = 0x00200000, GLOBAL_BATTLE_SHOW_TURN_ORDER = 0x00400000, GLOBAL_BATTLE_SHOW_GRID = 0x00800000, GLOBAL_BATTLE_SHOW_MOUSE_SHADOW = 0x01000000, GLOBAL_BATTLE_SHOW_MOVE_SHADOW = 0x02000000, GLOBAL_BATTLE_AUTO_RESOLVE = 0x04000000, GLOBAL_BATTLE_AUTO_SPELLCAST = 0x08000000, GLOBAL_AUTO_SAVE_AT_BEGINNING_OF_TURN = 0x10000000, GLOBAL_SCREEN_SCALING_TYPE_NEAREST = 0x20000000, // TODO: remove this setting once the Editor goes public. GLOBAL_ENABLE_EDITOR = 0x40000000 }; const int defaultSpeedDelay{ 5 }; } std::string Settings::GetVersion() { return std::to_string( MAJOR_VERSION ) + '.' + std::to_string( MINOR_VERSION ) + '.' + std::to_string( INTERMEDIATE_VERSION ); } Settings::Settings() : _resolutionInfo( fheroes2::Display::DEFAULT_WIDTH, fheroes2::Display::DEFAULT_HEIGHT ) , _gameDifficulty( Difficulty::NORMAL ) , sound_volume( 6 ) , music_volume( 6 ) , _musicType( MUSIC_EXTERNAL ) , _controllerPointerSpeed( 10 ) , heroes_speed( defaultSpeedDelay ) , ai_speed( defaultSpeedDelay ) , scroll_speed( SCROLL_SPEED_NORMAL ) , battle_speed( DEFAULT_BATTLE_SPEED ) , game_type( 0 ) , preferably_count_players( 0 ) { _optGlobal.SetModes( GLOBAL_FIRST_RUN ); _optGlobal.SetModes( GLOBAL_SHOW_INTRO ); _optGlobal.SetModes( GLOBAL_SHOW_RADAR ); _optGlobal.SetModes( GLOBAL_SHOW_ICONS ); _optGlobal.SetModes( GLOBAL_SHOW_BUTTONS ); _optGlobal.SetModes( GLOBAL_SHOW_STATUS ); _optGlobal.SetModes( GLOBAL_BATTLE_SHOW_GRID ); _optGlobal.SetModes( GLOBAL_BATTLE_SHOW_MOUSE_SHADOW ); _optGlobal.SetModes( GLOBAL_BATTLE_SHOW_MOVE_SHADOW ); _optGlobal.SetModes( GLOBAL_BATTLE_AUTO_RESOLVE ); if ( System::isHandheldDevice() ) { // Due to the nature of handheld devices having small screens in general it is good to make fullscreen option by default. _optGlobal.SetModes( GLOBAL_FULLSCREEN ); // Adventure Map scrolling is disabled by default for handheld devices as it is very hard to navigate on small screens. Use drag and move logic. scroll_speed = SCROLL_SPEED_NONE; } // The Price of Loyalty is not supported by default. EnablePriceOfLoyaltySupport( false ); } Settings & Settings::Get() { static Settings conf; return conf; } bool Settings::Read( const std::string & filePath ) { TinyConfig config( '=', '#' ); std::string sval; if ( !config.Load( filePath ) ) { return false; } // debug if ( config.Exists( "debug" ) ) { setDebug( config.IntParams( "debug" ) ); } // game language sval = config.StrParams( "lang" ); if ( !sval.empty() ) { _gameLanguage = sval; } // music source _musicType = MUSIC_EXTERNAL; sval = config.StrParams( "music" ); if ( !sval.empty() ) { if ( sval == "original" ) { _musicType = MUSIC_MIDI_ORIGINAL; } else if ( sval == "expansion" ) { _musicType = MUSIC_MIDI_EXPANSION; } else if ( sval == "external" ) { _musicType = MUSIC_EXTERNAL; } } // sound volume if ( config.Exists( "sound volume" ) ) { SetSoundVolume( config.IntParams( "sound volume" ) ); } // music volume if ( config.Exists( "music volume" ) ) { SetMusicVolume( config.IntParams( "music volume" ) ); } // move speed if ( config.Exists( "ai speed" ) ) { SetAIMoveSpeed( config.IntParams( "ai speed" ) ); } if ( config.Exists( "heroes speed" ) ) { SetHeroesMoveSpeed( config.IntParams( "heroes speed" ) ); } // scroll speed SetScrollSpeed( config.IntParams( "scroll speed" ) ); if ( config.Exists( "battle speed" ) ) { SetBattleSpeed( config.IntParams( "battle speed" ) ); } if ( config.Exists( "battle grid" ) ) { SetBattleGrid( config.StrParams( "battle grid" ) == "on" ); } if ( config.Exists( "battle shadow movement" ) ) { SetBattleMovementShaded( config.StrParams( "battle shadow movement" ) == "on" ); } if ( config.Exists( "battle shadow cursor" ) ) { SetBattleMouseShaded( config.StrParams( "battle shadow cursor" ) == "on" ); } if ( config.Exists( "battle show damage" ) ) { setBattleDamageInfo( config.StrParams( "battle show damage" ) == "on" ); } if ( config.Exists( "auto resolve battles" ) ) { setBattleAutoResolve( config.StrParams( "auto resolve battles" ) == "on" ); } if ( config.Exists( "auto spell casting" ) ) { setBattleAutoSpellcast( config.StrParams( "auto spell casting" ) == "on" ); } if ( config.Exists( "battle turn order" ) ) { setBattleShowTurnOrder( config.StrParams( "battle turn order" ) == "on" ); } if ( config.Exists( "use evil interface" ) ) { setEvilInterface( config.StrParams( "use evil interface" ) == "on" ); } if ( config.Exists( "hide interface" ) ) { setHideInterface( config.StrParams( "hide interface" ) == "on" ); } if ( config.Exists( "radar window position" ) ) { pos_radr = config.PointParams( "radar window position", { -1, -1 } ); } if ( config.Exists( "buttons window position" ) ) { pos_bttn = config.PointParams( "buttons window position", { -1, -1 } ); } if ( config.Exists( "icons window position" ) ) { pos_icon = config.PointParams( "icons window position", { -1, -1 } ); } if ( config.Exists( "status window position" ) ) { pos_stat = config.PointParams( "status window position", { -1, -1 } ); } if ( config.Exists( "videomode" ) ) { _resolutionInfo = config.ResolutionParams( "videomode", { fheroes2::Display::DEFAULT_WIDTH, fheroes2::Display::DEFAULT_HEIGHT } ); } if ( config.Exists( "fullscreen" ) ) { setFullScreen( config.StrParams( "fullscreen" ) == "on" ); } if ( config.Exists( "controller pointer speed" ) ) { _controllerPointerSpeed = std::clamp( config.IntParams( "controller pointer speed" ), 0, 100 ); } if ( config.Exists( "first time game run" ) && config.StrParams( "first time game run" ) == "off" ) { resetFirstGameRun(); } if ( config.Exists( "show game intro" ) ) { if ( config.StrParams( "show game intro" ) == "on" ) { _optGlobal.SetModes( GLOBAL_SHOW_INTRO ); } else { _optGlobal.ResetModes( GLOBAL_SHOW_INTRO ); } } if ( config.Exists( "v-sync" ) ) { setVSync( config.StrParams( "v-sync" ) == "on" ); } if ( config.Exists( "text support mode" ) ) { setTextSupportMode( config.StrParams( "text support mode" ) == "on" ); } if ( config.Exists( "monochrome cursor" ) ) { // We cannot set cursor before initializing the system since we read a configuration file before initialization. if ( config.StrParams( "monochrome cursor" ) == "on" ) { _optGlobal.SetModes( GLOBAL_MONOCHROME_CURSOR ); Cursor::Get().setMonochromeCursor( true ); } else { _optGlobal.ResetModes( GLOBAL_MONOCHROME_CURSOR ); Cursor::Get().setMonochromeCursor( false ); } } if ( config.Exists( "3d audio" ) ) { set3DAudio( config.StrParams( "3d audio" ) == "on" ); } if ( config.Exists( "system info" ) ) { setSystemInfo( config.StrParams( "system info" ) == "on" ); } if ( config.Exists( "auto save at the beginning of the turn" ) ) { setAutoSaveAtBeginningOfTurn( config.StrParams( "auto save at the beginning of the turn" ) == "on" ); } if ( config.Exists( "cursor soft rendering" ) ) { if ( config.StrParams( "cursor soft rendering" ) == "on" ) { _optGlobal.SetModes( GLOBAL_CURSOR_SOFT_EMULATION ); fheroes2::cursor().enableSoftwareEmulation( true ); } else { _optGlobal.ResetModes( GLOBAL_CURSOR_SOFT_EMULATION ); fheroes2::cursor().enableSoftwareEmulation( false ); } } if ( config.Exists( "screen scaling type" ) ) { setScreenScalingTypeNearest( config.StrParams( "screen scaling type" ) == "nearest" ); } if ( config.Exists( "editor" ) && config.StrParams( "editor" ) == "beta" ) { _optGlobal.SetModes( GLOBAL_ENABLE_EDITOR ); } return true; } bool Settings::Save( const std::string_view fileName ) const { if ( fileName.empty() ) { return false; } const std::string cfgFilename = System::concatPath( System::GetConfigDirectory( "fheroes2" ), fileName ); std::fstream file; file.open( cfgFilename.data(), std::fstream::out | std::fstream::trunc ); if ( !file ) { return false; } const std::string & data = String(); file.write( data.data(), data.size() ); return true; } std::string Settings::String() const { std::ostringstream os; std::string musicType; if ( MusicType() == MUSIC_EXTERNAL ) { musicType = "external"; } else if ( MusicType() == MUSIC_MIDI_EXPANSION ) { musicType = "expansion"; } else { musicType = "original"; } os << "# fheroes2 configuration file (saved by version " << GetVersion() << ")" << std::endl; const fheroes2::Display & display = fheroes2::Display::instance(); os << std::endl << "# video mode: in-game width x in-game height : on-screen width x on-screen height" << std::endl; os << "videomode = " << display.width() << "x" << display.height() << ":" << display.screenSize().width << "x" << display.screenSize().height << std::endl; os << std::endl << "# music: original, expansion, external" << std::endl; os << "music = " << musicType << std::endl; os << std::endl << "# sound volume: 0 - 10" << std::endl; os << "sound volume = " << sound_volume << std::endl; os << std::endl << "# music volume: 0 - 10" << std::endl; os << "music volume = " << music_volume << std::endl; os << std::endl << "# run in fullscreen mode: on/off" << std::endl; os << "fullscreen = " << ( _optGlobal.Modes( GLOBAL_FULLSCREEN ) ? "on" : "off" ) << std::endl; os << std::endl << "# print debug messages (only for development, see src/engine/logging.h for possible values)" << std::endl; os << "debug = " << Logging::getDebugLevel() << std::endl; os << std::endl << "# heroes movement speed: 1 - 10" << std::endl; os << "heroes speed = " << heroes_speed << std::endl; os << std::endl << "# AI movement speed: 0 - 10" << std::endl; os << "ai speed = " << ai_speed << std::endl; os << std::endl << "# battle speed: 1 - 10" << std::endl; os << "battle speed = " << battle_speed << std::endl; os << std::endl << "# Adventure Map scrolling speed: 0 - 4. 0 means no scrolling" << std::endl; os << "scroll speed = " << scroll_speed << std::endl; os << std::endl << "# show battle grid: on/off" << std::endl; os << "battle grid = " << ( _optGlobal.Modes( GLOBAL_BATTLE_SHOW_GRID ) ? "on" : "off" ) << std::endl; os << std::endl << "# show battle shadow movement: on/off" << std::endl; os << "battle shadow movement = " << ( _optGlobal.Modes( GLOBAL_BATTLE_SHOW_MOVE_SHADOW ) ? "on" : "off" ) << std::endl; os << std::endl << "# show battle shadow cursor: on/off" << std::endl; os << "battle shadow cursor = " << ( _optGlobal.Modes( GLOBAL_BATTLE_SHOW_MOUSE_SHADOW ) ? "on" : "off" ) << std::endl; os << std::endl << "# show battle damage information: on/off" << std::endl; os << "battle show damage = " << ( _optGlobal.Modes( GLOBAL_BATTLE_SHOW_DAMAGE ) ? "on" : "off" ) << std::endl; os << std::endl << "# auto resolve battles: on/off" << std::endl; os << "auto resolve battles = " << ( _optGlobal.Modes( GLOBAL_BATTLE_AUTO_RESOLVE ) ? "on" : "off" ) << std::endl; os << std::endl << "# auto combat spell casting: on/off" << std::endl; os << "auto spell casting = " << ( _optGlobal.Modes( GLOBAL_BATTLE_AUTO_SPELLCAST ) ? "on" : "off" ) << std::endl; os << std::endl << "# show turn order during battle: on/off" << std::endl; os << "battle turn order = " << ( _optGlobal.Modes( GLOBAL_BATTLE_SHOW_TURN_ORDER ) ? "on" : "off" ) << std::endl; os << std::endl << "# use evil interface style: on/off" << std::endl; os << "use evil interface = " << ( _optGlobal.Modes( GLOBAL_EVIL_INTERFACE ) ? "on" : "off" ) << std::endl; os << std::endl << "# hide interface elements on the adventure map: on/off" << std::endl; os << "hide interface = " << ( _optGlobal.Modes( GLOBAL_HIDE_INTERFACE ) ? "on" : "off" ) << std::endl; os << std::endl << "# position of the radar window on the adventure map when interface elements are hidden" << std::endl; os << "radar window position = [ " << pos_radr.x << ", " << pos_radr.y << " ]" << std::endl; os << std::endl << "# position of the buttons window on the adventure map when interface elements are hidden" << std::endl; os << "buttons window position = [ " << pos_bttn.x << ", " << pos_bttn.y << " ]" << std::endl; os << std::endl << "# position of the icons window on the adventure map when interface elements are hidden" << std::endl; os << "icons window position = [ " << pos_icon.x << ", " << pos_icon.y << " ]" << std::endl; os << std::endl << "# position of the status window on the adventure map when interface elements are hidden" << std::endl; os << "status window position = [ " << pos_stat.x << ", " << pos_stat.y << " ]" << std::endl; os << std::endl << "# game language (an empty value means English)" << std::endl; os << "lang = " << _gameLanguage << std::endl; os << std::endl << "# controller pointer speed: 0 - 100" << std::endl; os << "controller pointer speed = " << _controllerPointerSpeed << std::endl; os << std::endl << "# first time game run (show additional hints): on/off" << std::endl; os << "first time game run = " << ( _optGlobal.Modes( GLOBAL_FIRST_RUN ) ? "on" : "off" ) << std::endl; os << std::endl << "# show game intro (splash screen and video): on/off" << std::endl; os << "show game intro = " << ( _optGlobal.Modes( GLOBAL_SHOW_INTRO ) ? "on" : "off" ) << std::endl; os << std::endl << "# enable V-Sync (Vertical Synchronization) for rendering" << std::endl; os << "v-sync = " << ( _optGlobal.Modes( GLOBAL_RENDER_VSYNC ) ? "on" : "off" ) << std::endl; os << std::endl << "# enable text support mode to output extra information in console window: on/off" << std::endl; os << "text support mode = " << ( _optGlobal.Modes( GLOBAL_TEXT_SUPPORT_MODE ) ? "on" : "off" ) << std::endl; os << std::endl << "# enable monochrome (black and white) cursors in the game: on/off" << std::endl; os << "monochrome cursor = " << ( _optGlobal.Modes( GLOBAL_MONOCHROME_CURSOR ) ? "on" : "off" ) << std::endl; os << std::endl << "# enable 3D audio for objects on Adventure Map: on/off" << std::endl; os << "3d audio = " << ( _optGlobal.Modes( GLOBAL_3D_AUDIO ) ? "on" : "off" ) << std::endl; os << std::endl << "# display system information: on/off" << std::endl; os << "system info = " << ( _optGlobal.Modes( GLOBAL_SYSTEM_INFO ) ? "on" : "off" ) << std::endl; os << std::endl << "# should auto save be performed at the beginning of the turn instead of the end of the turn: on/off" << std::endl; os << "auto save at the beginning of the turn = " << ( _optGlobal.Modes( GLOBAL_AUTO_SAVE_AT_BEGINNING_OF_TURN ) ? "on" : "off" ) << std::endl; os << std::endl << "# enable cursor software rendering" << std::endl; os << "cursor soft rendering = " << ( _optGlobal.Modes( GLOBAL_CURSOR_SOFT_EMULATION ) ? "on" : "off" ) << std::endl; os << std::endl << "# scaling type: nearest or linear (set by default)" << std::endl; os << "screen scaling type = " << ( _optGlobal.Modes( GLOBAL_SCREEN_SCALING_TYPE_NEAREST ) ? "nearest" : "linear" ) << std::endl; if ( _optGlobal.Modes( GLOBAL_ENABLE_EDITOR ) ) { os << "editor = beta" << std::endl; } return os.str(); } void Settings::SetCurrentFileInfo( const Maps::FileInfo & fi ) { current_maps_file = fi; players.Init( current_maps_file ); preferably_count_players = 0; } bool Settings::setGameLanguage( const std::string & language ) { fheroes2::updateAlphabet( language ); _gameLanguage = language; if ( _gameLanguage.empty() ) { Translation::reset(); return true; } const std::string fileName = std::string( _gameLanguage ).append( ".mo" ); #if defined( MACOS_APP_BUNDLE ) const ListFiles translations = Settings::FindFiles( "translations", fileName, false ); #else const ListFiles translations = Settings::FindFiles( System::concatPath( "files", "lang" ), fileName, false ); #endif if ( !translations.empty() ) { return Translation::bindDomain( language.c_str(), translations.back().c_str() ); } ERROR_LOG( "Translation file " << fileName << " was not found." ) return false; } void Settings::SetProgramPath( const char * argv0 ) { if ( argv0 ) path_program = argv0; } const std::vector & Settings::GetRootDirs() { static std::vector dirs; if ( !dirs.empty() ) { return dirs; } #ifdef FHEROES2_DATA // Macro-defined path. dirs.emplace_back( EXPANDDEF( FHEROES2_DATA ) ); #endif // Environment variable. const char * dataEnvPath = getenv( "FHEROES2_DATA" ); if ( dataEnvPath != nullptr && std::find( dirs.begin(), dirs.end(), dataEnvPath ) == dirs.end() ) { dirs.emplace_back( dataEnvPath ); } // The location of the application. std::string appPath = System::GetDirname( Settings::Get().path_program ); if ( !appPath.empty() && std::find( dirs.begin(), dirs.end(), appPath ) == dirs.end() ) { dirs.emplace_back( std::move( appPath ) ); } // OS specific directories. System::appendOSSpecificDirectories( dirs ); #if defined( MACOS_APP_BUNDLE ) // macOS app bundle Resources directory char resourcePath[PATH_MAX]; CFURLRef resourcesURL = CFBundleCopyResourcesDirectoryURL( CFBundleGetMainBundle() ); if ( CFURLGetFileSystemRepresentation( resourcesURL, TRUE, reinterpret_cast( resourcePath ), PATH_MAX ) && std::find( dirs.begin(), dirs.end(), resourcePath ) == dirs.end() ) { dirs.emplace_back( resourcePath ); } else { ERROR_LOG( "Unable to get app bundle path" ) } CFRelease( resourcesURL ); #endif // User config directory. std::string configPath = System::GetConfigDirectory( "fheroes2" ); if ( !configPath.empty() && std::find( dirs.begin(), dirs.end(), configPath ) == dirs.end() ) { dirs.emplace_back( std::move( configPath ) ); } // User data directory. std::string dataPath = System::GetDataDirectory( "fheroes2" ); if ( !dataPath.empty() && std::find( dirs.begin(), dirs.end(), dataPath ) == dirs.end() ) { dirs.emplace_back( std::move( dataPath ) ); } // Remove all paths that are not directories. dirs.erase( std::remove_if( dirs.begin(), dirs.end(), []( const std::string & path ) { return !System::IsDirectory( path ); } ), dirs.end() ); return dirs; } ListFiles Settings::FindFiles( const std::string & prefixDir, const std::string & fileNameFilter, const bool exactMatch ) { ListFiles res; for ( const std::string & dir : GetRootDirs() ) { const std::string path = !prefixDir.empty() ? System::concatPath( dir, prefixDir ) : dir; if ( System::IsDirectory( path ) ) { if ( exactMatch ) { res.FindFileInDir( path, fileNameFilter, false ); } else { res.ReadDir( path, fileNameFilter, false ); } } } return res; } bool Settings::findFile( const std::string & internalDirectory, const std::string & fileName, std::string & fullPath ) { std::string tempPath; for ( const std::string & rootDir : GetRootDirs() ) { tempPath = System::concatPath( rootDir, internalDirectory ); tempPath = System::concatPath( tempPath, fileName ); if ( System::IsFile( tempPath ) ) { fullPath.swap( tempPath ); return true; } } return false; } std::string Settings::GetLastFile( const std::string & prefix, const std::string & name ) { const ListFiles & files = FindFiles( prefix, name, true ); return files.empty() ? name : files.back(); } void Settings::SetAIMoveSpeed( int speed ) { ai_speed = std::clamp( speed, 0, 10 ); } void Settings::SetHeroesMoveSpeed( int speed ) { heroes_speed = std::clamp( speed, 1, 10 ); } void Settings::SetBattleSpeed( int speed ) { battle_speed = std::clamp( speed, 1, 10 ); } void Settings::setBattleAutoResolve( bool enable ) { if ( enable ) { _optGlobal.SetModes( GLOBAL_BATTLE_AUTO_RESOLVE ); } else { _optGlobal.ResetModes( GLOBAL_BATTLE_AUTO_RESOLVE ); } } void Settings::setBattleAutoSpellcast( bool enable ) { if ( enable ) { _optGlobal.SetModes( GLOBAL_BATTLE_AUTO_SPELLCAST ); } else { _optGlobal.ResetModes( GLOBAL_BATTLE_AUTO_SPELLCAST ); } } void Settings::setBattleShowTurnOrder( const bool enable ) { if ( enable ) { _optGlobal.SetModes( GLOBAL_BATTLE_SHOW_TURN_ORDER ); } else { _optGlobal.ResetModes( GLOBAL_BATTLE_SHOW_TURN_ORDER ); } } void Settings::setFullScreen( const bool enable ) { if ( enable ) { _optGlobal.SetModes( GLOBAL_FULLSCREEN ); } else { _optGlobal.ResetModes( GLOBAL_FULLSCREEN ); } if ( enable != fheroes2::engine().isFullScreen() ) { fheroes2::engine().toggleFullScreen(); fheroes2::Display::instance().render(); } } void Settings::setMonochromeCursor( const bool enable ) { if ( enable ) { _optGlobal.SetModes( GLOBAL_MONOCHROME_CURSOR ); Cursor::Get().setMonochromeCursor( true ); } else { _optGlobal.ResetModes( GLOBAL_MONOCHROME_CURSOR ); Cursor::Get().setMonochromeCursor( false ); } Cursor::Refresh(); } void Settings::setTextSupportMode( const bool enable ) { if ( enable ) { _optGlobal.SetModes( GLOBAL_TEXT_SUPPORT_MODE ); Logging::setTextSupportMode( true ); } else { _optGlobal.ResetModes( GLOBAL_TEXT_SUPPORT_MODE ); Logging::setTextSupportMode( false ); } } void Settings::set3DAudio( const bool enable ) { if ( enable ) { _optGlobal.SetModes( GLOBAL_3D_AUDIO ); } else { _optGlobal.ResetModes( GLOBAL_3D_AUDIO ); } } void Settings::setVSync( const bool enable ) { if ( enable ) { _optGlobal.SetModes( GLOBAL_RENDER_VSYNC ); fheroes2::engine().setVSync( true ); } else { _optGlobal.ResetModes( GLOBAL_RENDER_VSYNC ); fheroes2::engine().setVSync( false ); } } void Settings::setSystemInfo( const bool enable ) { if ( enable ) { _optGlobal.SetModes( GLOBAL_SYSTEM_INFO ); fheroes2::RenderProcessor::instance().enableRenderers(); } else { _optGlobal.ResetModes( GLOBAL_SYSTEM_INFO ); fheroes2::RenderProcessor::instance().disableRenderers(); } } void Settings::setAutoSaveAtBeginningOfTurn( const bool enable ) { if ( enable ) { _optGlobal.SetModes( GLOBAL_AUTO_SAVE_AT_BEGINNING_OF_TURN ); } else { _optGlobal.ResetModes( GLOBAL_AUTO_SAVE_AT_BEGINNING_OF_TURN ); } } void Settings::setBattleDamageInfo( const bool enable ) { if ( enable ) { _optGlobal.SetModes( GLOBAL_BATTLE_SHOW_DAMAGE ); } else { _optGlobal.ResetModes( GLOBAL_BATTLE_SHOW_DAMAGE ); } } void Settings::setHideInterface( const bool enable ) { if ( enable ) { _optGlobal.SetModes( GLOBAL_HIDE_INTERFACE ); } else { _optGlobal.ResetModes( GLOBAL_HIDE_INTERFACE ); } } void Settings::setEvilInterface( const bool enable ) { if ( enable ) { _optGlobal.SetModes( GLOBAL_EVIL_INTERFACE ); } else { _optGlobal.ResetModes( GLOBAL_EVIL_INTERFACE ); } } void Settings::setScreenScalingTypeNearest( const bool enable ) { if ( enable ) { _optGlobal.SetModes( GLOBAL_SCREEN_SCALING_TYPE_NEAREST ); fheroes2::engine().setNearestScaling( true ); } else { _optGlobal.ResetModes( GLOBAL_SCREEN_SCALING_TYPE_NEAREST ); fheroes2::engine().setNearestScaling( false ); } } void Settings::SetScrollSpeed( int speed ) { scroll_speed = std::clamp( speed, static_cast( SCROLL_SPEED_NONE ), static_cast( SCROLL_SPEED_VERY_FAST ) ); } bool Settings::isPriceOfLoyaltySupported() const { return _optGlobal.Modes( GLOBAL_PRICELOYALTY ); } bool Settings::isMonochromeCursorEnabled() const { return _optGlobal.Modes( GLOBAL_MONOCHROME_CURSOR ); } bool Settings::isTextSupportModeEnabled() const { return _optGlobal.Modes( GLOBAL_TEXT_SUPPORT_MODE ); } bool Settings::is3DAudioEnabled() const { return _optGlobal.Modes( GLOBAL_3D_AUDIO ); } bool Settings::isSystemInfoEnabled() const { return _optGlobal.Modes( GLOBAL_SYSTEM_INFO ); } bool Settings::isAutoSaveAtBeginningOfTurnEnabled() const { return _optGlobal.Modes( GLOBAL_AUTO_SAVE_AT_BEGINNING_OF_TURN ); } bool Settings::isBattleShowDamageInfoEnabled() const { return _optGlobal.Modes( GLOBAL_BATTLE_SHOW_DAMAGE ); } bool Settings::isHideInterfaceEnabled() const { return _optGlobal.Modes( GLOBAL_HIDE_INTERFACE ); } bool Settings::isEvilInterfaceEnabled() const { return _optGlobal.Modes( GLOBAL_EVIL_INTERFACE ); } bool Settings::isEditorEnabled() const { return _optGlobal.Modes( GLOBAL_ENABLE_EDITOR ); } bool Settings::ShowControlPanel() const { return _optGlobal.Modes( GLOBAL_SHOW_CONTROL_PANEL ); } bool Settings::ShowRadar() const { return _optGlobal.Modes( GLOBAL_SHOW_RADAR ); } bool Settings::ShowIcons() const { return _optGlobal.Modes( GLOBAL_SHOW_ICONS ); } bool Settings::ShowButtons() const { return _optGlobal.Modes( GLOBAL_SHOW_BUTTONS ); } bool Settings::ShowStatus() const { return _optGlobal.Modes( GLOBAL_SHOW_STATUS ); } bool Settings::BattleShowGrid() const { return _optGlobal.Modes( GLOBAL_BATTLE_SHOW_GRID ); } bool Settings::BattleShowMouseShadow() const { return _optGlobal.Modes( GLOBAL_BATTLE_SHOW_MOUSE_SHADOW ); } bool Settings::BattleShowMoveShadow() const { return _optGlobal.Modes( GLOBAL_BATTLE_SHOW_MOVE_SHADOW ); } bool Settings::BattleAutoResolve() const { return _optGlobal.Modes( GLOBAL_BATTLE_AUTO_RESOLVE ); } bool Settings::BattleAutoSpellcast() const { return _optGlobal.Modes( GLOBAL_BATTLE_AUTO_SPELLCAST ); } bool Settings::BattleShowTurnOrder() const { return _optGlobal.Modes( GLOBAL_BATTLE_SHOW_TURN_ORDER ); } void Settings::setDebug( int debug ) { switch ( debug ) { case 0: debug = DBG_ALL_WARN; break; case 1: debug = DBG_ALL_INFO; break; case 2: debug = DBG_ALL_TRACE; break; case 3: debug = DBG_ENGINE_TRACE; break; case 4: debug = DBG_GAME_INFO | DBG_BATTLE_INFO | DBG_AI_INFO; break; case 5: debug = DBG_GAME_TRACE | DBG_AI_INFO | DBG_BATTLE_INFO; break; case 6: debug = DBG_AI_TRACE | DBG_BATTLE_INFO | DBG_GAME_INFO; break; case 7: debug = DBG_BATTLE_TRACE | DBG_AI_INFO | DBG_GAME_INFO; break; case 8: debug = DBG_DEVEL | DBG_GAME_TRACE; break; case 9: debug = DBG_DEVEL | DBG_AI_INFO | DBG_BATTLE_INFO | DBG_GAME_INFO; break; case 10: debug = DBG_DEVEL | DBG_AI_TRACE | DBG_BATTLE_INFO | DBG_GAME_INFO; break; case 11: debug = DBG_DEVEL | DBG_AI_TRACE | DBG_BATTLE_TRACE | DBG_GAME_INFO; break; default: break; } #ifndef WITH_DEBUG debug &= ~DBG_DEVEL; #endif Logging::setDebugLevel( debug ); } void Settings::SetSoundVolume( int v ) { sound_volume = std::clamp( v, 0, 10 ); } void Settings::SetMusicVolume( int v ) { music_volume = std::clamp( v, 0, 10 ); } void Settings::SetPreferablyCountPlayers( int c ) { preferably_count_players = std::min( c, 6 ); } bool Settings::isCampaignGameType() const { return ( game_type & Game::TYPE_CAMPAIGN ) != 0; } void Settings::EnablePriceOfLoyaltySupport( const bool set ) { if ( set ) { _optGlobal.SetModes( GLOBAL_PRICELOYALTY ); } else { _optGlobal.ResetModes( GLOBAL_PRICELOYALTY ); if ( _musicType == MUSIC_MIDI_EXPANSION ) _musicType = MUSIC_MIDI_ORIGINAL; } } void Settings::SetBattleGrid( bool f ) { f ? _optGlobal.SetModes( GLOBAL_BATTLE_SHOW_GRID ) : _optGlobal.ResetModes( GLOBAL_BATTLE_SHOW_GRID ); } void Settings::SetBattleMovementShaded( bool f ) { f ? _optGlobal.SetModes( GLOBAL_BATTLE_SHOW_MOVE_SHADOW ) : _optGlobal.ResetModes( GLOBAL_BATTLE_SHOW_MOVE_SHADOW ); } void Settings::SetBattleMouseShaded( bool f ) { f ? _optGlobal.SetModes( GLOBAL_BATTLE_SHOW_MOUSE_SHADOW ) : _optGlobal.ResetModes( GLOBAL_BATTLE_SHOW_MOUSE_SHADOW ); } void Settings::SetShowControlPanel( bool f ) { f ? _optGlobal.SetModes( GLOBAL_SHOW_CONTROL_PANEL ) : _optGlobal.ResetModes( GLOBAL_SHOW_CONTROL_PANEL ); } void Settings::SetShowRadar( bool f ) { f ? _optGlobal.SetModes( GLOBAL_SHOW_RADAR ) : _optGlobal.ResetModes( GLOBAL_SHOW_RADAR ); } void Settings::SetShowIcons( bool f ) { f ? _optGlobal.SetModes( GLOBAL_SHOW_ICONS ) : _optGlobal.ResetModes( GLOBAL_SHOW_ICONS ); } void Settings::SetShowButtons( bool f ) { f ? _optGlobal.SetModes( GLOBAL_SHOW_BUTTONS ) : _optGlobal.ResetModes( GLOBAL_SHOW_BUTTONS ); } void Settings::SetShowStatus( bool f ) { f ? _optGlobal.SetModes( GLOBAL_SHOW_STATUS ) : _optGlobal.ResetModes( GLOBAL_SHOW_STATUS ); } bool Settings::FullScreen() const { return _optGlobal.Modes( GLOBAL_FULLSCREEN ); } bool Settings::isVSyncEnabled() const { return _optGlobal.Modes( GLOBAL_RENDER_VSYNC ); } bool Settings::isFirstGameRun() const { return _optGlobal.Modes( GLOBAL_FIRST_RUN ); } bool Settings::isShowIntro() const { return _optGlobal.Modes( GLOBAL_SHOW_INTRO ); } void Settings::resetFirstGameRun() { _optGlobal.ResetModes( GLOBAL_FIRST_RUN ); } StreamBase & operator<<( StreamBase & msg, const Settings & conf ) { return msg << conf._gameLanguage << conf.current_maps_file << conf._gameDifficulty << conf.game_type << conf.preferably_count_players << conf.players; } StreamBase & operator>>( StreamBase & msg, Settings & conf ) { return msg >> conf._loadedFileLanguage >> conf.current_maps_file >> conf._gameDifficulty >> conf.game_type >> conf.preferably_count_players >> conf.players; } fheroes2-1.0.12+dfsg/src/fheroes2/system/settings.h000066400000000000000000000242561456075706000221130ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2SETTINGS_H #define H2SETTINGS_H #include #include #include #include #include "bitmodes.h" #include "dir.h" #include "maps_fileinfo.h" #include "math_base.h" #include "players.h" #include "screen.h" class StreamBase; enum AdventureMapScrollSpeed : int { SCROLL_SPEED_NONE = 0, SCROLL_SPEED_SLOW = 1, SCROLL_SPEED_NORMAL = 2, SCROLL_SPEED_FAST = 3, SCROLL_SPEED_VERY_FAST = 4 }; enum MusicSource { MUSIC_MIDI_ORIGINAL, MUSIC_MIDI_EXPANSION, MUSIC_EXTERNAL }; enum class ZoomLevel : uint8_t { ZoomLevel0 = 0, ZoomLevel1 = 1, ZoomLevel2 = 2, ZoomLevel3 = 3, // Max zoom, but should only exists for debug builds }; class Settings { public: static constexpr const char * configFileName = "fheroes2.cfg"; Settings( const Settings & ) = delete; Settings & operator=( const Settings & ) = delete; ~Settings() = default; static Settings & Get(); bool Read( const std::string & filePath ); bool Save( const std::string_view fileName ) const; std::string String() const; void SetCurrentFileInfo( const Maps::FileInfo & ); const Maps::FileInfo & getCurrentMapInfo() const { return current_maps_file; } Maps::FileInfo & getCurrentMapInfo() { return current_maps_file; } int HeroesMoveSpeed() const { return heroes_speed; } int AIMoveSpeed() const { return ai_speed; } int BattleSpeed() const { return battle_speed; } int ScrollSpeed() const { return scroll_speed; } int GameDifficulty() const { return _gameDifficulty; } const std::string & getGameLanguage() const { return _gameLanguage; } const std::string & loadedFileLanguage() const { return _loadedFileLanguage; } const fheroes2::Point & PosRadar() const { return pos_radr; } const fheroes2::Point & PosButtons() const { return pos_bttn; } const fheroes2::Point & PosIcons() const { return pos_icon; } const fheroes2::Point & PosStatus() const { return pos_stat; } void SetPosRadar( const fheroes2::Point & pt ) { pos_radr = pt; } void SetPosButtons( const fheroes2::Point & pt ) { pos_bttn = pt; } void SetPosIcons( const fheroes2::Point & pt ) { pos_icon = pt; } void SetPosStatus( const fheroes2::Point & pt ) { pos_stat = pt; } bool FullScreen() const; bool ShowControlPanel() const; bool ShowRadar() const; bool ShowIcons() const; bool ShowButtons() const; bool ShowStatus() const; bool BattleShowGrid() const; bool BattleShowMouseShadow() const; bool BattleShowMoveShadow() const; bool BattleAutoResolve() const; bool BattleAutoSpellcast() const; bool BattleShowTurnOrder() const; bool isPriceOfLoyaltySupported() const; bool isMonochromeCursorEnabled() const; bool isTextSupportModeEnabled() const; bool is3DAudioEnabled() const; bool isSystemInfoEnabled() const; bool isAutoSaveAtBeginningOfTurnEnabled() const; bool isBattleShowDamageInfoEnabled() const; bool isHideInterfaceEnabled() const; bool isEvilInterfaceEnabled() const; bool isEditorEnabled() const; bool LoadedGameVersion() const { // 0x80 value should be same as in Game::TYPE_LOADFILE enumeration value // This constant not used here, to not drag dependency on the game.h and game.cpp in compilation target. return ( game_type & 0x80 ) != 0; } bool MusicMIDI() const { return _musicType == MUSIC_MIDI_ORIGINAL || _musicType == MUSIC_MIDI_EXPANSION; } bool isShowIntro() const; bool isVSyncEnabled() const; bool isFirstGameRun() const; void resetFirstGameRun(); const fheroes2::ResolutionInfo & currentResolutionInfo() const { return _resolutionInfo; } void EnablePriceOfLoyaltySupport( const bool set ); void SetGameDifficulty( const int difficulty ) { _gameDifficulty = difficulty; } void SetBattleGrid( bool ); void SetBattleMovementShaded( bool ); void SetBattleMouseShaded( bool ); void SetShowControlPanel( bool ); void SetShowRadar( bool ); void SetShowIcons( bool ); void SetShowButtons( bool ); void SetShowStatus( bool ); // Sets the speed of AI-controlled heroes in the range 0 - 10, 0 means "don't show" void SetAIMoveSpeed( int ); void SetScrollSpeed( int ); // Sets the speed of human-controlled heroes in the range 1 - 10 void SetHeroesMoveSpeed( int ); // Sets the animation speed during combat in the range 1 - 10 void SetBattleSpeed( int ); void setBattleAutoResolve( bool enable ); void setBattleAutoSpellcast( bool enable ); void setBattleShowTurnOrder( const bool enable ); void setFullScreen( const bool enable ); void setMonochromeCursor( const bool enable ); void setTextSupportMode( const bool enable ); void set3DAudio( const bool enable ); void setVSync( const bool enable ); void setSystemInfo( const bool enable ); void setAutoSaveAtBeginningOfTurn( const bool enable ); void setBattleDamageInfo( const bool enable ); void setHideInterface( const bool enable ); void setEvilInterface( const bool enable ); void setScreenScalingTypeNearest( const bool enable ); void SetSoundVolume( int v ); void SetMusicVolume( int v ); void SetMusicType( int v ) { _musicType = MUSIC_EXTERNAL <= v ? MUSIC_EXTERNAL : static_cast( v ); } bool setGameLanguage( const std::string & language ); int SoundVolume() const { return sound_volume; } int MusicVolume() const { return music_volume; } MusicSource MusicType() const { return _musicType; } bool IsGameType( int type ) const { return ( game_type & type ) != 0; } int GameType() const { return game_type; } void SetGameType( int type ) { game_type = type; } bool isCampaignGameType() const; Players & GetPlayers() { return players; } const Players & GetPlayers() const { return players; } int CurrentColor() const { return players.getCurrentColor(); } // The color should belong to one player or be NONE (neutral player). void SetCurrentColor( const int color ) { players.setCurrentColor( color ); } int PreferablyCountPlayers() const { return preferably_count_players; } void SetPreferablyCountPlayers( int ); int controllerPointerSpeed() const { return _controllerPointerSpeed; } ZoomLevel ViewWorldZoomLevel() const { return _viewWorldZoomLevel; } void SetViewWorldZoomLevel( ZoomLevel zoomLevel ) { _viewWorldZoomLevel = zoomLevel; } void SetProgramPath( const char * ); static std::string GetVersion(); static const std::vector & GetRootDirs(); static ListFiles FindFiles( const std::string & prefixDir, const std::string & fileNameFilter, const bool exactMatch ); static bool findFile( const std::string & internalDirectory, const std::string & fileName, std::string & fullPath ); static std::string GetLastFile( const std::string & prefix, const std::string & name ); private: friend StreamBase & operator<<( StreamBase &, const Settings & ); friend StreamBase & operator>>( StreamBase &, Settings & ); Settings(); static void setDebug( int debug ); // Global game options (GLOBAL_) BitModes _optGlobal; fheroes2::ResolutionInfo _resolutionInfo; int _gameDifficulty; std::string path_program; std::string _gameLanguage; // Not saved in the config file or savefile std::string _loadedFileLanguage; Maps::FileInfo current_maps_file; int sound_volume; int music_volume; MusicSource _musicType; int _controllerPointerSpeed; int heroes_speed; int ai_speed; int scroll_speed; int battle_speed; int game_type; int preferably_count_players; ZoomLevel _viewWorldZoomLevel{ ZoomLevel::ZoomLevel1 }; fheroes2::Point pos_radr{ -1, -1 }; fheroes2::Point pos_bttn{ -1, -1 }; fheroes2::Point pos_icon{ -1, -1 }; fheroes2::Point pos_stat{ -1, -1 }; Players players; }; StreamBase & operator<<( StreamBase &, const Settings & ); StreamBase & operator>>( StreamBase &, Settings & ); #endif fheroes2-1.0.12+dfsg/src/fheroes2/system/version.h000066400000000000000000000031151456075706000217270ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2021 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #define MAJOR_VERSION 1 #define MINOR_VERSION 0 #define INTERMEDIATE_VERSION 12 #ifndef BUILD_VERSION #define BUILD_VERSION 0 #endif fheroes2-1.0.12+dfsg/src/fheroes2/world/000077500000000000000000000000001456075706000176745ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/fheroes2/world/world.cpp000066400000000000000000001400251456075706000215310ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "world.h" #include #include #include #include #include #include #include #include #include #include "ai.h" #include "artifact.h" #include "campaign_savedata.h" #include "campaign_scenariodata.h" #include "castle.h" #include "color.h" #include "direction.h" #include "game.h" #include "game_io.h" #include "game_over.h" #include "gamedefs.h" #include "ground.h" #include "heroes.h" #include "logging.h" #include "maps_fileinfo.h" #include "maps_objects.h" #include "maps_tiles_helper.h" #include "mp2.h" #include "pairs.h" #include "players.h" #include "race.h" #include "rand.h" #include "resource.h" #include "route.h" #include "save_format_version.h" #include "serialize.h" #include "settings.h" #include "tools.h" #include "translations.h" #include "week.h" #include "world_object_uid.h" namespace { bool isTileBlockedForSettingMonster( const MapsTiles & mapTiles, const int32_t tileId, const int32_t radius, const std::set & excludeTiles ) { const MapsIndexes & indexes = Maps::getAroundIndexes( tileId, radius ); for ( const int32_t indexId : indexes ) { if ( excludeTiles.count( indexId ) > 0 ) { return true; } const Maps::Tiles & indexedTile = mapTiles[indexId]; if ( indexedTile.isWater() ) { continue; } const MP2::MapObjectType objectType = indexedTile.GetObject( true ); if ( objectType == MP2::OBJ_CASTLE || objectType == MP2::OBJ_HERO || objectType == MP2::OBJ_MONSTER ) { return true; } } return false; } int32_t findSuitableNeighbouringTile( const MapsTiles & mapTiles, const int32_t tileId, const bool allDirections ) { std::vector suitableIds; const MapsIndexes & indexes = Maps::getAroundIndexes( tileId ); for ( const int32_t indexId : indexes ) { // If allDirections is false, we should only consider tiles below the current object if ( !allDirections && indexId < tileId + world.w() - 2 ) { continue; } const Maps::Tiles & indexedTile = mapTiles[indexId]; if ( indexedTile.isWater() || !isClearGround( indexedTile ) ) { continue; } // If the candidate tile is a coast tile, it is suitable only if there are other coast tiles nearby if ( indexedTile.GetObject( false ) == MP2::OBJ_COAST ) { const MapsIndexes coastTiles = Maps::ScanAroundObject( indexId, MP2::OBJ_COAST ); if ( coastTiles.empty() ) { continue; } } suitableIds.emplace_back( indexId ); } if ( suitableIds.empty() ) { return -1; } return Rand::Get( suitableIds ); } int32_t getNeighbouringEmptyTileCount( const MapsTiles & mapTiles, const int32_t tileId ) { int32_t count = 0; const MapsIndexes & indexes = Maps::getAroundIndexes( tileId ); for ( const int32_t indexId : indexes ) { const Maps::Tiles & indexedTile = mapTiles[indexId]; if ( indexedTile.isWater() || !isClearGround( indexedTile ) ) { continue; } ++count; } return count; } } MapObjects::~MapObjects() { clear(); } void MapObjects::clear() { for ( iterator it = begin(); it != end(); ++it ) delete ( *it ).second; std::map::clear(); } void MapObjects::add( MapObjectSimple * obj ) { if ( obj ) { std::map & currentMap = *this; if ( currentMap[obj->GetUID()] ) delete currentMap[obj->GetUID()]; currentMap[obj->GetUID()] = obj; } } MapObjectSimple * MapObjects::get( uint32_t uid ) { iterator it = find( uid ); return it != end() ? ( *it ).second : nullptr; } std::list MapObjects::get( const fheroes2::Point & pos ) { std::list res; for ( iterator it = begin(); it != end(); ++it ) if ( ( *it ).second && ( *it ).second->isPosition( pos ) ) res.push_back( ( *it ).second ); return res; } void MapObjects::remove( uint32_t uid ) { iterator it = find( uid ); if ( it != end() ) delete ( *it ).second; erase( it ); } CapturedObject & CapturedObjects::Get( int32_t index ) { std::map & my = *this; return my[index]; } void CapturedObjects::SetColor( int32_t index, int col ) { Get( index ).SetColor( col ); } void CapturedObjects::Set( int32_t index, int obj, int col ) { CapturedObject & co = Get( index ); if ( co.GetColor() != col && co.guardians.isValid() ) co.guardians.Reset(); co.Set( obj, col ); } uint32_t CapturedObjects::GetCount( int obj, int col ) const { uint32_t result = 0; const ObjectColor objcol( obj, col ); for ( const_iterator it = begin(); it != end(); ++it ) { if ( objcol == ( *it ).second.objcol ) ++result; } return result; } uint32_t CapturedObjects::GetCountMines( const int resourceType, const int ownerColor ) const { uint32_t count = 0; const ObjectColor correctObject( MP2::OBJ_MINE, ownerColor ); for ( const auto & [tileIndex, objectInfo] : *this ) { if ( correctObject == objectInfo.objcol ) { const int32_t mineResource = Maps::getDailyIncomeObjectResources( world.GetTiles( tileIndex ) ).getFirstValidResource().first; if ( resourceType == mineResource ) { ++count; } } } return count; } int CapturedObjects::GetColor( int32_t index ) const { const_iterator it = find( index ); return it != end() ? ( *it ).second.GetColor() : Color::NONE; } void CapturedObjects::ClearFog( int colors ) { // clear abroad objects for ( const_iterator it = begin(); it != end(); ++it ) { const ObjectColor & objcol = ( *it ).second.objcol; if ( objcol.isColor( colors ) ) { int scoutingDistance = 0; switch ( objcol.first ) { case MP2::OBJ_MINE: case MP2::OBJ_ALCHEMIST_LAB: case MP2::OBJ_SAWMILL: scoutingDistance = 2; break; default: break; } if ( scoutingDistance ) Maps::ClearFog( ( *it ).first, scoutingDistance, colors ); } } } void CapturedObjects::ResetColor( int color ) { for ( iterator it = begin(); it != end(); ++it ) { ObjectColor & objcol = ( *it ).second.objcol; if ( objcol.isColor( color ) ) { const MP2::MapObjectType objectType = static_cast( objcol.first ); objcol.second = objectType == MP2::OBJ_CASTLE ? Color::UNUSED : Color::NONE; world.GetTiles( it->first ).setOwnershipFlag( objectType, objcol.second ); } } } World & world = World::Get(); World & World::Get() { static World insideWorld; return insideWorld; } void World::Defaults() { // playing kingdom vec_kingdoms.Init(); // Map seed is random and persisted on saves // this has to be generated before initializing heroes, as campaign-specific heroes start at a higher level and thus have to simulate level ups _seed = Rand::Get( std::numeric_limits::max() ); // initialize all heroes vec_heroes.Init(); vec_castles.Init(); } void World::Reset() { width = 0; height = 0; // maps tiles vec_tiles.clear(); // kingdoms vec_kingdoms.clear(); // event day vec_eventsday.clear(); // rumors _rumors.clear(); // castles vec_castles.Clear(); // heroes vec_heroes.clear(); // extra map_captureobj.clear(); map_objects.clear(); ultimate_artifact.Reset(); day = 0; week = 0; month = 0; heroes_cond_wins = Heroes::UNKNOWN; heroes_cond_loss = Heroes::UNKNOWN; _seed = 0; } void World::generateBattleOnlyMap() { const std::vector terrainTypes{ Maps::Ground::DESERT, Maps::Ground::SNOW, Maps::Ground::SWAMP, Maps::Ground::WASTELAND, Maps::Ground::BEACH, Maps::Ground::LAVA, Maps::Ground::DIRT, Maps::Ground::GRASS, Maps::Ground::WATER }; Reset(); width = 2; height = 2; Maps::FileInfo fi; fi.width = static_cast( width ); fi.height = static_cast( height ); Settings & conf = Settings::Get(); if ( conf.isPriceOfLoyaltySupported() ) { fi.version = GameVersion::PRICE_OF_LOYALTY; } conf.SetCurrentFileInfo( fi ); Defaults(); vec_tiles.resize( static_cast( width ) * height ); const int groundType = Rand::Get( terrainTypes ); for ( size_t i = 0; i < vec_tiles.size(); ++i ) { vec_tiles[i] = {}; vec_tiles[i].setIndex( static_cast( i ) ); vec_tiles[i].setTerrain( Maps::Ground::getTerrainStartImageIndex( groundType ), false, false ); } } void World::generateForEditor( const int32_t size ) { assert( size > 0 ); Reset(); width = size; height = size; Maps::FileInfo fi; fi.width = static_cast( width ); fi.height = static_cast( height ); Settings & conf = Settings::Get(); assert( conf.isPriceOfLoyaltySupported() ); fi.version = GameVersion::PRICE_OF_LOYALTY; conf.SetCurrentFileInfo( fi ); Defaults(); vec_tiles.resize( static_cast( width ) * height ); // init all tiles for ( size_t i = 0; i < vec_tiles.size(); ++i ) { vec_tiles[i] = {}; vec_tiles[i].setIndex( static_cast( i ) ); const uint8_t terrainFlag = static_cast( Rand::Get( 0, 3 ) ); vec_tiles[i].setTerrain( static_cast( Rand::Get( 16, 19 ) ), terrainFlag & 1, terrainFlag & 2 ); } } const Castle * World::getCastleEntrance( const fheroes2::Point & tilePosition ) const { if ( !isValidCastleEntrance( tilePosition ) ) { return nullptr; } return vec_castles.Get( tilePosition ); } Castle * World::getCastleEntrance( const fheroes2::Point & tilePosition ) { if ( !isValidCastleEntrance( tilePosition ) ) { return nullptr; } return vec_castles.Get( tilePosition ); } bool World::isValidCastleEntrance( const fheroes2::Point & tilePosition ) const { return Maps::isValidAbsPoint( tilePosition.x, tilePosition.y ) && ( GetTiles( tilePosition.x, tilePosition.y ).GetObject( false ) == MP2::OBJ_CASTLE ); } Heroes * World::GetHeroForHire( const int race, const int heroIDToIgnore /* = Heroes::UNKNOWN */ ) const { return vec_heroes.GetHeroForHire( race, heroIDToIgnore ); } Heroes * World::FromJailHeroes( int32_t index ) { return vec_heroes.FromJail( index ); } Heroes * World::GetHero( const Castle & castle ) const { return vec_heroes.Get( castle.GetCenter() ); } int World::GetDay() const { return LastDay() ? DAYOFWEEK : day % DAYOFWEEK; } int World::GetWeek() const { return LastWeek() ? WEEKOFMONTH : week % WEEKOFMONTH; } bool World::BeginWeek() const { return 1 == ( day % DAYOFWEEK ); } bool World::BeginMonth() const { return 1 == ( week % WEEKOFMONTH ) && BeginWeek(); } bool World::LastDay() const { return ( 0 == ( day % DAYOFWEEK ) ); } bool World::FirstWeek() const { return ( 1 == ( week % WEEKOFMONTH ) ); } bool World::LastWeek() const { return ( 0 == ( week % WEEKOFMONTH ) ); } const Week & World::GetWeekType() const { static auto cachedWeekDependencies = std::make_tuple( week, GetWeekSeed() ); static Week cachedWeek = Week::RandomWeek( FirstWeek(), GetWeekSeed() ); const auto currentWeekDependencies = std::make_tuple( week, GetWeekSeed() ); if ( cachedWeekDependencies != currentWeekDependencies ) { cachedWeekDependencies = currentWeekDependencies; cachedWeek = Week::RandomWeek( FirstWeek(), GetWeekSeed() ); } return cachedWeek; } void World::NewDay() { ++day; if ( BeginWeek() ) { ++week; if ( BeginMonth() ) { ++month; } } // first the routine of the new month if ( BeginMonth() ) { NewMonth(); vec_kingdoms.NewMonth(); vec_castles.NewMonth(); vec_heroes.NewMonth(); } // then the routine of the new week if ( BeginWeek() ) { NewWeek(); vec_kingdoms.NewWeek(); vec_castles.NewWeek(); vec_heroes.NewWeek(); } // and finally the routine of the new day vec_kingdoms.NewDay(); vec_castles.NewDay(); vec_heroes.NewDay(); // remove deprecated events assert( day > 0 ); vec_eventsday.remove_if( [this]( const EventDate & v ) { return v.isDeprecated( day - 1 ); } ); } void World::NewDayAI() { if ( BeginWeek() ) { vec_castles.NewWeekAI(); } } void World::NewWeek() { // update objects if ( week > 1 ) { for ( Maps::Tiles & tile : vec_tiles ) { if ( MP2::isWeekLife( tile.GetObject( false ) ) || tile.GetObject() == MP2::OBJ_MONSTER ) { updateObjectInfoTile( tile, false ); } } } // Reset RECRUIT mode for all heroes at once vec_heroes.ResetModes( Heroes::RECRUIT ); // Reset recruits in all kingdoms at once std::set remainingRecruits = vec_kingdoms.resetRecruits(); // Restore the RECRUIT mode for the remaining recruits for ( Heroes * hero : remainingRecruits ) { assert( hero != nullptr ); hero->SetModes( Heroes::RECRUIT ); } } void World::NewMonth() { if ( month > 1 && GetWeekType().GetType() == WeekName::MONSTERS ) { MonthOfMonstersAction( Monster( GetWeekType().GetMonster() ) ); } } void World::MonthOfMonstersAction( const Monster & mons ) { if ( !mons.isValid() ) { return; } // Find all tiles which are useful for monsters such as resources, artifacts, mines, other capture objects. Exclude heroes, monsters and castles. std::vector primaryTargetTiles; // Sometimes monsters appear on roads so find all road tiles. std::vector secondaryTargetTiles; // Lastly monster occasionally appear on empty tiles. std::vector tetriaryTargetTiles; std::set excludeTiles; for ( const Maps::Tiles & tile : vec_tiles ) { if ( tile.isWater() ) { // Monsters are not placed on water. continue; } const int32_t tileId = tile.GetIndex(); const MP2::MapObjectType objectType = tile.GetObject( true ); if ( objectType == MP2::OBJ_CASTLE || objectType == MP2::OBJ_HERO || objectType == MP2::OBJ_MONSTER ) { excludeTiles.emplace( tileId ); continue; } if ( MP2::isActionObject( objectType ) ) { if ( isTileBlockedForSettingMonster( vec_tiles, tileId, 3, excludeTiles ) ) { continue; } const int32_t tileToSet = findSuitableNeighbouringTile( vec_tiles, tileId, ( tile.GetPassable() == DIRECTION_ALL ) ); if ( tileToSet >= 0 ) { primaryTargetTiles.emplace_back( tileToSet ); excludeTiles.emplace( tileId ); } } else if ( tile.isRoad() ) { if ( isTileBlockedForSettingMonster( vec_tiles, tileId, 4, excludeTiles ) ) { continue; } if ( getNeighbouringEmptyTileCount( vec_tiles, tileId ) < 2 ) { continue; } const int32_t tileToSet = findSuitableNeighbouringTile( vec_tiles, tileId, true ); if ( tileToSet >= 0 ) { secondaryTargetTiles.emplace_back( tileToSet ); excludeTiles.emplace( tileId ); } } else if ( isClearGround( tile ) ) { if ( isTileBlockedForSettingMonster( vec_tiles, tileId, 4, excludeTiles ) ) { continue; } if ( getNeighbouringEmptyTileCount( vec_tiles, tileId ) < 4 ) { continue; } const int32_t tileToSet = findSuitableNeighbouringTile( vec_tiles, tileId, true ); if ( tileToSet >= 0 ) { tetriaryTargetTiles.emplace_back( tileToSet ); excludeTiles.emplace( tileId ); } } } // Shuffle all found tile IDs. Rand::Shuffle( primaryTargetTiles ); Rand::Shuffle( secondaryTargetTiles ); Rand::Shuffle( tetriaryTargetTiles ); // Calculate the number of monsters to be placed. uint32_t monstersToBePlaced = static_cast( primaryTargetTiles.size() / 3 ); const uint32_t mapMinimum = static_cast( vec_tiles.size() / 360 ); if ( monstersToBePlaced < mapMinimum ) { monstersToBePlaced = mapMinimum; } else { monstersToBePlaced = Rand::GetWithSeed( monstersToBePlaced * 75 / 100, monstersToBePlaced * 125 / 100, _seed ); } // 85% of positions are for primary targets // 10% of positions are for roads // 5% of positions are for empty tiles uint32_t primaryTileCount = monstersToBePlaced * 85 / 100; if ( primaryTileCount > primaryTargetTiles.size() ) { primaryTileCount = static_cast( primaryTargetTiles.size() ); } for ( uint32_t i = 0; i < primaryTileCount; ++i ) { setMonsterOnTile( vec_tiles[primaryTargetTiles[i]], mons, 0 /* random */ ); } uint32_t secondaryTileCount = monstersToBePlaced * 10 / 100; if ( secondaryTileCount > secondaryTargetTiles.size() ) { secondaryTileCount = static_cast( secondaryTargetTiles.size() ); } for ( uint32_t i = 0; i < secondaryTileCount; ++i ) { setMonsterOnTile( vec_tiles[secondaryTargetTiles[i]], mons, 0 /* random */ ); } uint32_t tetriaryTileCount = monstersToBePlaced * 5 / 100; if ( tetriaryTileCount > tetriaryTargetTiles.size() ) { tetriaryTileCount = static_cast( tetriaryTargetTiles.size() ); } for ( uint32_t i = 0; i < tetriaryTileCount; ++i ) { setMonsterOnTile( vec_tiles[tetriaryTargetTiles[i]], mons, 0 /* random */ ); } } std::string World::getCurrentRumor() const { const uint32_t standardRumorCount = 10; const uint32_t totalRumorCount = static_cast( _rumors.size() ) + standardRumorCount; const uint32_t chosenRumorId = Rand::GetWithSeed( 0, totalRumorCount - 1, GetWeekSeed() ); switch ( chosenRumorId ) { case 0: { std::string rumor( _( "The ultimate artifact is really the %{name}." ) ); StringReplace( rumor, "%{name}", ultimate_artifact.GetName() ); return rumor; } case 1: { std::string rumor( _( "The ultimate artifact may be found in the %{name} regions of the world." ) ); const int32_t artifactIndex = ultimate_artifact.getPosition(); const fheroes2::Point artifactPos = Maps::GetPoint( artifactIndex ); if ( height / 3 > artifactPos.y ) { if ( width / 3 > artifactPos.x ) { StringReplace( rumor, "%{name}", _( "north-west" ) ); } else if ( 2 * width / 3 > artifactPos.x ) { StringReplace( rumor, "%{name}", _( "north" ) ); } else { StringReplace( rumor, "%{name}", _( "north-east" ) ); } } else if ( 2 * height / 3 > artifactPos.y ) { if ( width / 3 > artifactPos.x ) { StringReplace( rumor, "%{name}", _( "west" ) ); } else if ( 2 * width / 3 > artifactPos.x ) { StringReplace( rumor, "%{name}", _( "center" ) ); } else { StringReplace( rumor, "%{name}", _( "east" ) ); } } else if ( width / 3 > artifactPos.x ) { StringReplace( rumor, "%{name}", _( "south-west" ) ); } else if ( 2 * width / 3 > artifactPos.x ) { StringReplace( rumor, "%{name}", _( "south" ) ); } else { StringReplace( rumor, "%{name}", _( "south-east" ) ); } return rumor; } case 2: return _( "The truth is out there." ); case 3: return _( "The dark side is stronger." ); case 4: return _( "The end of the world is near." ); case 5: return _( "The bones of Lord Slayer are buried in the foundation of the arena." ); case 6: return _( "A Black Dragon will take out a Titan any day of the week." ); case 7: return _( "He told her: Yada yada yada... and then she said: Blah, blah, blah..." ); case 8: return _( "An unknown force is being resurrected..." ); case 9: return _( "Check the newest version of the game at\nhttps://github.com/ihhub/\nfheroes2/releases" ); default: break; } assert( chosenRumorId >= standardRumorCount && chosenRumorId < totalRumorCount ); return _rumors[chosenRumorId - standardRumorCount]; } MapsIndexes World::GetTeleportEndPoints( const int32_t index ) const { MapsIndexes result; const Maps::Tiles & entranceTile = GetTiles( index ); if ( entranceTile.GetObject( false ) != MP2::OBJ_STONE_LITHS ) { return result; } // The type of destination stone liths must match the type of the source stone liths. for ( const int32_t teleportIndex : _allTeleports.at( entranceTile.GetObjectSpriteIndex() ) ) { const Maps::Tiles & teleportTile = GetTiles( teleportIndex ); if ( teleportIndex == index || teleportTile.getHero() != nullptr || teleportTile.isWater() != entranceTile.isWater() ) { continue; } result.push_back( teleportIndex ); } return result; } int32_t World::NextTeleport( const int32_t index ) const { const MapsIndexes teleports = GetTeleportEndPoints( index ); if ( teleports.empty() ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "not found" ) return index; } return Rand::Get( teleports ); } MapsIndexes World::GetWhirlpoolEndPoints( const int32_t index ) const { MapsIndexes result; const Maps::Tiles & entranceTile = GetTiles( index ); if ( entranceTile.GetObject( false ) != MP2::OBJ_WHIRLPOOL ) { return result; } // The exit point from the destination whirlpool must match the entry point in the source whirlpool. for ( const int32_t whirlpoolIndex : _allWhirlpools.at( entranceTile.GetObjectSpriteIndex() ) ) { const Maps::Tiles & whirlpoolTile = GetTiles( whirlpoolIndex ); if ( whirlpoolTile.GetObjectUID() == entranceTile.GetObjectUID() || whirlpoolTile.getHero() != nullptr ) { continue; } result.push_back( whirlpoolIndex ); } return result; } int32_t World::NextWhirlpool( const int32_t index ) const { const MapsIndexes whilrpools = GetWhirlpoolEndPoints( index ); if ( whilrpools.empty() ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "not found" ) return index; } return Rand::Get( whilrpools ); } uint32_t World::CountCapturedObject( int obj, int col ) const { return map_captureobj.GetCount( obj, col ); } uint32_t World::CountCapturedMines( int type, int color ) const { switch ( type ) { case Resource::WOOD: return CountCapturedObject( MP2::OBJ_SAWMILL, color ); case Resource::MERCURY: return CountCapturedObject( MP2::OBJ_ALCHEMIST_LAB, color ); default: break; } return map_captureobj.GetCountMines( type, color ); } void World::CaptureObject( int32_t index, int color ) { const MP2::MapObjectType objectType = GetTiles( index ).GetObject( false ); map_captureobj.Set( index, objectType, color ); Castle * castle = getCastleEntrance( Maps::GetPoint( index ) ); if ( castle && castle->GetColor() != color ) { castle->ChangeColor( color ); } if ( color & ( Color::ALL | Color::UNUSED ) ) { GetTiles( index ).setOwnershipFlag( objectType, color ); } } int World::ColorCapturedObject( int32_t index ) const { return map_captureobj.GetColor( index ); } CapturedObject & World::GetCapturedObject( int32_t index ) { return map_captureobj.Get( index ); } void World::ResetCapturedObjects( int color ) { map_captureobj.ResetColor( color ); } void World::ClearFog( int colors ) { colors = Players::GetPlayerFriends( colors ); // clear abroad castles vec_castles.Scout( colors ); // clear abroad heroes vec_heroes.Scout( colors ); map_captureobj.ClearFog( colors ); } const UltimateArtifact & World::GetUltimateArtifact() const { return ultimate_artifact; } bool World::DiggingForUltimateArtifact( const fheroes2::Point & center ) { Maps::Tiles & tile = GetTiles( center.x, center.y ); // Get digging hole sprite. MP2::ObjectIcnType objectIcnType = MP2::OBJ_ICN_TYPE_UNKNOWN; uint8_t imageIndex = 0; if ( !MP2::getDiggingHoleSprite( tile.GetGround(), objectIcnType, imageIndex ) ) { // Are you sure that you can dig here? assert( 0 ); return false; } tile.pushBottomLayerAddon( Maps::TilesAddon( Maps::BACKGROUND_LAYER, Maps::getNewObjectUID(), objectIcnType, imageIndex ) ); if ( ultimate_artifact.isPosition( tile.GetIndex() ) && !ultimate_artifact.isFound() ) { ultimate_artifact.markAsFound(); return true; } return false; } void World::AddEventDate( const EventDate & event ) { vec_eventsday.push_back( event ); } EventsDate World::GetEventsDate( int color ) const { EventsDate res; for ( EventsDate::const_iterator it = vec_eventsday.begin(); it != vec_eventsday.end(); ++it ) if ( ( *it ).isAllow( color, day ) ) res.push_back( *it ); return res; } std::string World::DateString() const { std::string output( "month: " ); output += std::to_string( GetMonth() ); output += ", week: "; output += std::to_string( GetWeek() ); output += ", day: "; output += std::to_string( GetDay() ); return output; } uint32_t World::CountObeliskOnMaps() { const size_t res = std::count_if( vec_tiles.begin(), vec_tiles.end(), []( const Maps::Tiles & tile ) { return MP2::OBJ_OBELISK == tile.GetObject( false ); } ); return res > 0 ? static_cast( res ) : 6; } void World::ActionForMagellanMaps( int color ) { const Kingdom & kingdom = world.GetKingdom( color ); const bool isAIPlayer = kingdom.isControlAI(); const int alliedColors = Players::GetPlayerFriends( color ); for ( Maps::Tiles & tile : vec_tiles ) { if ( tile.isWater() ) { if ( isAIPlayer && tile.isFog( color ) ) { AI::Get().revealFog( tile, kingdom ); } tile.ClearFog( alliedColors ); } } } MapEvent * World::GetMapEvent( const fheroes2::Point & pos ) { std::list res = map_objects.get( pos ); return !res.empty() ? static_cast( res.front() ) : nullptr; } MapObjectSimple * World::GetMapObject( uint32_t uid ) { return uid ? map_objects.get( uid ) : nullptr; } void World::RemoveMapObject( const MapObjectSimple * obj ) { if ( obj ) map_objects.remove( obj->GetUID() ); } const Heroes * World::GetHeroesCondWins() const { return ( ( Settings::Get().getCurrentMapInfo().ConditionWins() & GameOver::WINS_HERO ) != 0 ) ? GetHeroes( heroes_cond_wins ) : nullptr; } const Heroes * World::GetHeroesCondLoss() const { return ( ( Settings::Get().getCurrentMapInfo().ConditionLoss() & GameOver::LOSS_HERO ) != 0 ) ? GetHeroes( heroes_cond_loss ) : nullptr; } bool World::KingdomIsWins( const Kingdom & kingdom, const uint32_t wins ) const { #ifndef NDEBUG #if defined( WITH_DEBUG ) const Player * kingdomPlayer = Players::Get( kingdom.GetColor() ); assert( kingdomPlayer != nullptr ); const bool isKingdomInAIAutoControlMode = kingdomPlayer->isAIAutoControlMode(); #else const bool isKingdomInAIAutoControlMode = false; #endif // WITH_DEBUG #endif // !NDEBUG const Maps::FileInfo & mapInfo = Settings::Get().getCurrentMapInfo(); switch ( wins ) { case GameOver::WINS_ALL: // This method should be called with this condition only for a human-controlled kingdom assert( kingdom.isControlHuman() || isKingdomInAIAutoControlMode ); return kingdom.GetColor() == vec_kingdoms.GetNotLossColors(); case GameOver::WINS_TOWN: { const Castle * town = getCastleEntrance( mapInfo.WinsMapsPositionObject() ); return ( kingdom.isControlHuman() || mapInfo.WinsCompAlsoWins() ) && ( town && town->GetColor() == kingdom.GetColor() ); } case GameOver::WINS_HERO: { // This method should be called with this condition only for a human-controlled kingdom assert( kingdom.isControlHuman() || isKingdomInAIAutoControlMode ); if ( heroes_cond_wins == Heroes::UNKNOWN ) { return false; } const Heroes * hero = GetHeroesCondWins(); assert( hero != nullptr ); // The hero in question should either be available for hire or be hired by a human-controlled kingdom return ( hero->isAvailableForHire() || GetKingdom( hero->GetColor() ).isControlHuman() ); } case GameOver::WINS_ARTIFACT: { // This method should be called with this condition only for a human-controlled kingdom assert( kingdom.isControlHuman() || isKingdomInAIAutoControlMode ); const VecHeroes & heroes = kingdom.GetHeroes(); if ( mapInfo.WinsFindUltimateArtifact() ) { return std::any_of( heroes.begin(), heroes.end(), []( const Heroes * hero ) { return hero->HasUltimateArtifact(); } ); } else { const Artifact art = mapInfo.WinsFindArtifactID(); return std::any_of( heroes.begin(), heroes.end(), [&art]( const Heroes * hero ) { return hero->hasArtifact( art ); } ); } } case GameOver::WINS_SIDE: // This method should be called with this condition only for a human-controlled kingdom assert( kingdom.isControlHuman() || isKingdomInAIAutoControlMode ); return !( Game::GetActualKingdomColors() & ~Players::GetPlayerFriends( kingdom.GetColor() ) ); case GameOver::WINS_GOLD: return ( ( kingdom.isControlHuman() || mapInfo.WinsCompAlsoWins() ) && 0 < kingdom.GetFunds().Get( Resource::GOLD ) && static_cast( kingdom.GetFunds().Get( Resource::GOLD ) ) >= mapInfo.getWinningGoldAccumulationValue() ); default: break; } return false; } bool World::KingdomIsLoss( const Kingdom & kingdom, const uint32_t loss ) const { #ifndef NDEBUG #if defined( WITH_DEBUG ) const Player * kingdomPlayer = Players::Get( kingdom.GetColor() ); assert( kingdomPlayer != nullptr ); const bool isKingdomInAIAutoControlMode = kingdomPlayer->isAIAutoControlMode(); #else const bool isKingdomInAIAutoControlMode = false; #endif // WITH_DEBUG #endif // !NDEBUG // This method should only be called for a human-controlled kingdom assert( kingdom.isControlHuman() || isKingdomInAIAutoControlMode ); const Settings & conf = Settings::Get(); switch ( loss ) { case GameOver::LOSS_ALL: return kingdom.isLoss(); case GameOver::LOSS_TOWN: { const Castle * town = getCastleEntrance( conf.getCurrentMapInfo().LossMapsPositionObject() ); return ( town && town->GetColor() != kingdom.GetColor() ); } case GameOver::LOSS_HERO: { if ( heroes_cond_loss == Heroes::UNKNOWN ) { return false; } const Heroes * hero = GetHeroesCondLoss(); assert( hero != nullptr ); // The hero in question should either be available for hire... if ( hero->isAvailableForHire() ) { return true; } #if defined( WITH_DEBUG ) const Player * heroPlayer = Players::Get( hero->GetColor() ); assert( heroPlayer != nullptr ); const bool isHeroInAIAutoControlMode = heroPlayer->isAIAutoControlMode(); #else const bool isHeroInAIAutoControlMode = false; #endif // .. or be hired by an AI-controlled kingdom if ( GetKingdom( hero->GetColor() ).isControlAI() && !isHeroInAIAutoControlMode ) { // Exception for campaign: hero is not considered lost if he is hired by a friendly AI-controlled kingdom if ( conf.isCampaignGameType() && Players::isFriends( kingdom.GetColor(), hero->GetColor() ) ) { return false; } return true; } return false; } case GameOver::LOSS_TIME: return ( CountDay() > conf.getCurrentMapInfo().LossCountDays() ); default: break; } return false; } uint32_t World::CheckKingdomWins( const Kingdom & kingdom ) const { #ifndef NDEBUG #if defined( WITH_DEBUG ) const Player * kingdomPlayer = Players::Get( kingdom.GetColor() ); assert( kingdomPlayer != nullptr ); const bool isKingdomInAIAutoControlMode = kingdomPlayer->isAIAutoControlMode(); #else const bool isKingdomInAIAutoControlMode = false; #endif // WITH_DEBUG #endif // !NDEBUG // This method should only be called for a human-controlled kingdom assert( kingdom.isControlHuman() || isKingdomInAIAutoControlMode ); const Settings & conf = Settings::Get(); if ( conf.isCampaignGameType() ) { const Campaign::ScenarioVictoryCondition victoryCondition = Campaign::getCurrentScenarioVictoryCondition(); if ( victoryCondition == Campaign::ScenarioVictoryCondition::CAPTURE_DRAGON_CITY ) { const bool visited = kingdom.isVisited( MP2::OBJ_DRAGON_CITY ); if ( visited ) { return GameOver::WINS_SIDE; } return GameOver::COND_NONE; } } const std::array wins = { GameOver::WINS_ALL, GameOver::WINS_TOWN, GameOver::WINS_HERO, GameOver::WINS_ARTIFACT, GameOver::WINS_SIDE, GameOver::WINS_GOLD }; for ( const uint32_t cond : wins ) { if ( ( ( conf.getCurrentMapInfo().ConditionWins() & cond ) == cond ) && KingdomIsWins( kingdom, cond ) ) { return cond; } } return GameOver::COND_NONE; } uint32_t World::CheckKingdomLoss( const Kingdom & kingdom ) const { #ifndef NDEBUG #if defined( WITH_DEBUG ) const Player * kingdomPlayer = Players::Get( kingdom.GetColor() ); assert( kingdomPlayer != nullptr ); const bool isKingdomInAIAutoControlMode = kingdomPlayer->isAIAutoControlMode(); #else const bool isKingdomInAIAutoControlMode = false; #endif // WITH_DEBUG #endif // !NDEBUG // This method should only be called for a human-controlled kingdom assert( kingdom.isControlHuman() || isKingdomInAIAutoControlMode ); const Settings & conf = Settings::Get(); // First of all, check if the other players have not completed WINS_TOWN or WINS_GOLD yet const std::array, 4> enemy_wins = { std::make_pair( GameOver::WINS_TOWN, GameOver::LOSS_ENEMY_WINS_TOWN ), std::make_pair( GameOver::WINS_GOLD, GameOver::LOSS_ENEMY_WINS_GOLD ) }; for ( const auto & item : enemy_wins ) { if ( conf.getCurrentMapInfo().ConditionWins() & item.first ) { const int color = vec_kingdoms.FindWins( item.first ); if ( color && color != kingdom.GetColor() ) { return item.second; } } } if ( conf.isCampaignGameType() ) { const Campaign::ScenarioLossCondition lossCondition = Campaign::getCurrentScenarioLossCondition(); if ( lossCondition == Campaign::ScenarioLossCondition::LOSE_ALL_SORCERESS_VILLAGES ) { const VecCastles & castles = kingdom.GetCastles(); bool hasSorceressVillage = false; for ( size_t i = 0; i < castles.size(); ++i ) { if ( castles[i]->isCastle() || castles[i]->GetRace() != Race::SORC ) continue; hasSorceressVillage = true; break; } if ( !hasSorceressVillage ) return GameOver::LOSS_ALL; } } const std::array loss = { GameOver::LOSS_ALL, GameOver::LOSS_TOWN, GameOver::LOSS_HERO, GameOver::LOSS_TIME }; for ( const uint32_t cond : loss ) { if ( ( ( conf.getCurrentMapInfo().ConditionLoss() & cond ) == cond ) && KingdomIsLoss( kingdom, cond ) ) { return cond; } } return GameOver::COND_NONE; } uint32_t World::getDistance( const Heroes & hero, int targetIndex ) { _pathfinder.reEvaluateIfNeeded( hero ); return _pathfinder.getDistance( targetIndex ); } std::list World::getPath( const Heroes & hero, int targetIndex ) { _pathfinder.reEvaluateIfNeeded( hero ); return _pathfinder.buildPath( targetIndex ); } void World::resetPathfinder() { _pathfinder.reset(); AI::Get().resetPathfinder(); } void World::updatePassabilities() { for ( Maps::Tiles & tile : vec_tiles ) { tile.updateEmpty(); tile.setInitialPassability(); } // Once the original passabilities are set we know all neighbours. Now we have to update passabilities based on neighbours. for ( Maps::Tiles & tile : vec_tiles ) { tile.updatePassability(); } } void World::PostLoad( const bool setTilePassabilities ) { if ( setTilePassabilities ) { updatePassabilities(); } // Cache all tiles that that contain stone liths of a certain type (depending on object sprite index). _allTeleports.clear(); for ( const int32_t index : Maps::GetObjectPositions( MP2::OBJ_STONE_LITHS ) ) { _allTeleports[GetTiles( index ).GetObjectSpriteIndex()].push_back( index ); } // Cache all tiles that contain a certain part of the whirlpool (depending on object sprite index). _allWhirlpools.clear(); for ( const int32_t index : Maps::GetObjectPositions( MP2::OBJ_WHIRLPOOL ) ) { _allWhirlpools[GetTiles( index ).GetObjectSpriteIndex()].push_back( index ); } resetPathfinder(); ComputeStaticAnalysis(); } uint32_t World::GetMapSeed() const { return _seed; } uint32_t World::GetWeekSeed() const { uint32_t weekSeed = _seed; fheroes2::hashCombine( weekSeed, week ); return weekSeed; } bool World::isAnyKingdomVisited( const MP2::MapObjectType objectType, const int32_t dstIndex ) const { const Colors colors( Game::GetKingdomColors() ); for ( const int color : colors ) { const Kingdom & kingdom = GetKingdom( color ); if ( kingdom.isVisited( dstIndex, objectType ) ) { return true; } } return false; } StreamBase & operator<<( StreamBase & msg, const CapturedObject & obj ) { return msg << obj.objcol << obj.guardians; } StreamBase & operator>>( StreamBase & msg, CapturedObject & obj ) { return msg >> obj.objcol >> obj.guardians; } StreamBase & operator<<( StreamBase & msg, const MapObjects & objs ) { msg << static_cast( objs.size() ); for ( MapObjects::const_iterator it = objs.begin(); it != objs.end(); ++it ) if ( ( *it ).second ) { const MapObjectSimple & obj = *( *it ).second; msg << ( *it ).first << obj.GetType(); switch ( obj.GetType() ) { case MP2::OBJ_EVENT: msg << static_cast( obj ); break; case MP2::OBJ_SPHINX: msg << static_cast( obj ); break; case MP2::OBJ_SIGN: msg << static_cast( obj ); break; default: msg << obj; break; } } return msg; } StreamBase & operator>>( StreamBase & msg, MapObjects & objs ) { uint32_t size = 0; msg >> size; objs.clear(); for ( uint32_t ii = 0; ii < size; ++ii ) { int32_t index; int type; msg >> index >> type; switch ( type ) { case MP2::OBJ_EVENT: { MapEvent * ptr = new MapEvent(); msg >> *ptr; objs[index] = ptr; break; } case MP2::OBJ_SPHINX: { MapSphinx * ptr = new MapSphinx(); msg >> *ptr; objs[index] = ptr; break; } case MP2::OBJ_SIGN: { MapSign * ptr = new MapSign(); msg >> *ptr; objs[index] = ptr; break; } default: { MapObjectSimple * ptr = new MapObjectSimple(); msg >> *ptr; objs[index] = ptr; break; } } } return msg; } StreamBase & operator<<( StreamBase & msg, const World & w ) { return msg << w.width << w.height << w.vec_tiles << w.vec_heroes << w.vec_castles << w.vec_kingdoms << w._rumors << w.vec_eventsday << w.map_captureobj << w.ultimate_artifact << w.day << w.week << w.month << w.heroes_cond_wins << w.heroes_cond_loss << w.map_objects << w._seed; } StreamBase & operator>>( StreamBase & msg, World & w ) { static_assert( LAST_SUPPORTED_FORMAT_VERSION < FORMAT_VERSION_1010_RELEASE, "Remove the logic below." ); if ( Game::GetVersionOfCurrentSaveFile() < FORMAT_VERSION_1010_RELEASE ) { uint16_t width = 0; uint16_t height = 0; msg >> width >> height; w.width = width; w.height = height; } else { msg >> w.width >> w.height; } msg >> w.vec_tiles >> w.vec_heroes >> w.vec_castles >> w.vec_kingdoms >> w._rumors >> w.vec_eventsday >> w.map_captureobj >> w.ultimate_artifact >> w.day >> w.week >> w.month >> w.heroes_cond_wins >> w.heroes_cond_loss; static_assert( LAST_SUPPORTED_FORMAT_VERSION < FORMAT_VERSION_1010_RELEASE, "Remove the logic below." ); if ( Game::GetVersionOfCurrentSaveFile() < FORMAT_VERSION_1010_RELEASE ) { ++w.heroes_cond_wins; ++w.heroes_cond_loss; } msg >> w.map_objects >> w._seed; w.PostLoad( false ); return msg; } void EventDate::LoadFromMP2( const std::vector & data ) { assert( data.size() >= MP2::MP2_EVENT_STRUCTURE_MIN_SIZE ); assert( data[0] == 0 ); assert( data[42] == 1 ); // Structure containing information about a timed global event. // // - uint8_t (1 byte) // Always 0 as an indicator that this indeed a timed global object. // // - int32_t (4 bytes) // The amount of Wood to be given. Can be negative. // // - int32_t (4 bytes) // The amount of Mercury to be given. Can be negative. // // - int32_t (4 bytes) // The amount of Ore to be given. Can be negative. // // - int32_t (4 bytes) // The amount of Sulfur to be given. Can be negative. // // - int32_t (4 bytes) // The amount of Crystals to be given. Can be negative. // // - int32_t (4 bytes) // The amount of Gems to be given. Can be negative. // // - int32_t (4 bytes) // The amount of Gold to be given. Can be negative. // // - uint16_t (2 bytes) // Possible artifacts to be given. Not in use. // // - uint16_t (2 bytes) // A flag whether the event is applicable for AI players as well. // // - uint16_t (2 bytes) // The first day of occurrence of the event. // // - uint16_t (2 bytes) // Period in days when the event repeats. 0 means that the event never repeats. // // - unused 5 bytes // Always 0. // // - unused 1 byte // Always 1. // // - uint8_t (1 byte) // A flag to determine whether Blue player receives the event. // // - uint8_t (1 byte) // A flag to determine whether Green player receives the event. // // - uint8_t (1 byte) // A flag to determine whether Red player receives the event. // // - uint8_t (1 byte) // A flag to determine whether Yellow player receives the event. // // - uint8_t (1 byte) // A flag to determine whether Orange player receives the event. // // - uint8_t (1 byte) // A flag to determine whether Purple player receives the event. // // - string // Null terminated string containing the event text. StreamBuf dataStream( data ); dataStream.skip( 1 ); // Get the amount of resources. resource.wood = static_cast( dataStream.getLE32() ); resource.mercury = static_cast( dataStream.getLE32() ); resource.ore = static_cast( dataStream.getLE32() ); resource.sulfur = static_cast( dataStream.getLE32() ); resource.crystal = static_cast( dataStream.getLE32() ); resource.gems = static_cast( dataStream.getLE32() ); resource.gold = static_cast( dataStream.getLE32() ); dataStream.skip( 2 ); // The event applies to AI players as well. isApplicableForAIPlayers = ( dataStream.getLE16() != 0 ); // Get the first day of occurrence. firstOccurrenceDay = dataStream.getLE16(); // Get the repeat period. repeatPeriodInDays = dataStream.getLE16(); dataStream.skip( 6 ); colors = 0; if ( dataStream.get() ) { colors |= Color::BLUE; } if ( dataStream.get() ) { colors |= Color::GREEN; } if ( dataStream.get() ) { colors |= Color::RED; } if ( dataStream.get() ) { colors |= Color::YELLOW; } if ( dataStream.get() ) { colors |= Color::ORANGE; } if ( dataStream.get() ) { colors |= Color::PURPLE; } message = dataStream.toString(); DEBUG_LOG( DBG_GAME, DBG_INFO, "A timed event which occurs at day " << firstOccurrenceDay << " contains a message: " << message ) } bool EventDate::isAllow( const int col, const uint32_t date ) const { if ( ( col & colors ) == 0 ) { // This player color is not allowed for the event. return false; } if ( firstOccurrenceDay > date ) { // The date has not come. return false; } if ( firstOccurrenceDay == date ) { return true; } if ( repeatPeriodInDays == 0 ) { // This is not the same date and the event does not repeat. return false; } return ( ( date - firstOccurrenceDay ) % repeatPeriodInDays ) == 0; } StreamBase & operator<<( StreamBase & msg, const EventDate & obj ) { return msg << obj.resource << obj.isApplicableForAIPlayers << obj.firstOccurrenceDay << obj.repeatPeriodInDays << obj.colors << obj.message << obj.title; } StreamBase & operator>>( StreamBase & msg, EventDate & obj ) { return msg >> obj.resource >> obj.isApplicableForAIPlayers >> obj.firstOccurrenceDay >> obj.repeatPeriodInDays >> obj.colors >> obj.message >> obj.title; } fheroes2-1.0.12+dfsg/src/fheroes2/world/world.h000066400000000000000000000267111456075706000212030ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #ifndef H2WORLD_H #define H2WORLD_H #include #include #include #include #include #include #include #include "army_troop.h" #include "artifact_ultimate.h" #include "castle.h" #include "heroes.h" #include "kingdom.h" #include "maps.h" #include "maps_tiles.h" #include "math_base.h" #include "monster.h" #include "mp2.h" #include "pairs.h" #include "resource.h" #include "world_pathfinding.h" #include "world_regions.h" class MapObjectSimple; class StreamBase; struct MapEvent; struct Week; namespace Route { class Step; } struct MapObjects : public std::map { MapObjects() = default; MapObjects( const MapObjects & other ) = delete; MapObjects( MapObjects && other ) = delete; ~MapObjects(); MapObjects & operator=( const MapObjects & other ) = delete; MapObjects & operator=( MapObjects && other ) = delete; void clear(); void add( MapObjectSimple * ); std::list get( const fheroes2::Point & ); MapObjectSimple * get( uint32_t uid ); void remove( uint32_t uid ); }; struct CapturedObject { ObjectColor objcol; Troop guardians; CapturedObject() = default; int GetColor() const { return objcol.second; } Troop & GetTroop() { return guardians; } void Set( int obj, int col ) { objcol = ObjectColor( obj, col ); } void SetColor( int col ) { objcol.second = col; } }; struct CapturedObjects : std::map { void Set( int32_t, int, int ); void SetColor( int32_t, int ); void ClearFog( int ); void ResetColor( int ); CapturedObject & Get( int32_t ); uint32_t GetCount( int, int ) const; uint32_t GetCountMines( int, int ) const; int GetColor( int32_t ) const; }; struct EventDate { void LoadFromMP2( const std::vector & data ); bool isAllow( const int color, const uint32_t date ) const; bool isDeprecated( const uint32_t date ) const { return date > firstOccurrenceDay && repeatPeriodInDays == 0; } Funds resource; uint32_t firstOccurrenceDay{ 0 }; uint32_t repeatPeriodInDays{ 0 }; int colors{ 0 }; bool isApplicableForAIPlayers{ false }; std::string message; std::string title; }; StreamBase & operator<<( StreamBase &, const EventDate & ); StreamBase & operator>>( StreamBase &, EventDate & ); using EventsDate = std::list; using MapsTiles = std::vector; class World : protected fheroes2::Size { public: World( const World & other ) = delete; World( World && other ) = delete; ~World() { Reset(); } World & operator=( const World & other ) = delete; World & operator=( World && other ) = delete; bool LoadMapMP2( const std::string & filename, const bool isOriginalMp2File ); bool loadResurrectionMap( const std::string & filename ); // Generate 2x2 map for Battle Only mode. void generateBattleOnlyMap(); void generateForEditor( const int32_t size ); static World & Get(); int32_t w() const { return width; } int32_t h() const { return height; } const Maps::Tiles & GetTiles( const int32_t x, const int32_t y ) const { #ifdef WITH_DEBUG return vec_tiles.at( y * width + x ); #else return vec_tiles[y * width + x]; #endif } Maps::Tiles & GetTiles( const int32_t x, const int32_t y ) { #ifdef WITH_DEBUG return vec_tiles.at( y * width + x ); #else return vec_tiles[y * width + x]; #endif } const Maps::Tiles & GetTiles( const int32_t tileId ) const { #ifdef WITH_DEBUG return vec_tiles.at( tileId ); #else return vec_tiles[tileId]; #endif } Maps::Tiles & GetTiles( const int32_t tileId ) { #ifdef WITH_DEBUG return vec_tiles.at( tileId ); #else return vec_tiles[tileId]; #endif } void InitKingdoms() { vec_kingdoms.Init(); } Kingdom & GetKingdom( int color ) { return vec_kingdoms.GetKingdom( color ); } const Kingdom & GetKingdom( int color ) const { return vec_kingdoms.GetKingdom( color ); } // Get castle based on its tile. If the tile is not a part of a castle return nullptr. const Castle * getCastle( const fheroes2::Point & tilePosition ) const { return vec_castles.Get( tilePosition ); } Castle * getCastle( const fheroes2::Point & tilePosition ) { return vec_castles.Get( tilePosition ); } // Get castle based on its entrance tile. If the tile is not castle's entrance return nullptr. const Castle * getCastleEntrance( const fheroes2::Point & tilePosition ) const; Castle * getCastleEntrance( const fheroes2::Point & tilePosition ); const Heroes * GetHeroes( int id ) const { return vec_heroes.Get( id ); } Heroes * GetHeroes( int id ) { return vec_heroes.Get( id ); } const Heroes * GetHeroes( const fheroes2::Point & center ) const { return vec_heroes.Get( center ); } Heroes * GetHeroes( const fheroes2::Point & center ) { return vec_heroes.Get( center ); } Heroes * FromJailHeroes( int32_t ); Heroes * GetHeroForHire( const int race, const int heroIDToIgnore = Heroes::UNKNOWN ) const; const Heroes * GetHeroesCondWins() const; const Heroes * GetHeroesCondLoss() const; Heroes * GetHero( const Castle & ) const; const UltimateArtifact & GetUltimateArtifact() const; bool DiggingForUltimateArtifact( const fheroes2::Point & ); // overall number of cells of the world map: width * height size_t getSize() const { return vec_tiles.size(); } int GetDay() const; int GetWeek() const; uint32_t GetMonth() const { return month; } uint32_t CountDay() const { return day; } uint32_t CountWeek() const { return week; } bool BeginWeek() const; bool BeginMonth() const; bool LastDay() const; bool FirstWeek() const; bool LastWeek() const; const Week & GetWeekType() const; std::string DateString() const; void NewDay(); void NewDayAI(); void NewWeek(); void NewMonth(); std::string getCurrentRumor() const; int32_t NextTeleport( const int32_t index ) const; MapsIndexes GetTeleportEndPoints( const int32_t index ) const; int32_t NextWhirlpool( const int32_t index ) const; MapsIndexes GetWhirlpoolEndPoints( const int32_t index ) const; void CaptureObject( int32_t, int col ); uint32_t CountCapturedObject( int obj, int col ) const; uint32_t CountCapturedMines( int type, int col ) const; uint32_t CountObeliskOnMaps(); int ColorCapturedObject( int32_t ) const; void ResetCapturedObjects( int ); CapturedObject & GetCapturedObject( int32_t ); void ActionForMagellanMaps( int color ); void ClearFog( int color ); bool KingdomIsWins( const Kingdom & kingdom, const uint32_t wins ) const; bool KingdomIsLoss( const Kingdom & kingdom, const uint32_t loss ) const; uint32_t CheckKingdomWins( const Kingdom & kingdom ) const; uint32_t CheckKingdomLoss( const Kingdom & kingdom ) const; void AddEventDate( const EventDate & ); EventsDate GetEventsDate( int color ) const; MapEvent * GetMapEvent( const fheroes2::Point & ); MapObjectSimple * GetMapObject( uint32_t uid ); void RemoveMapObject( const MapObjectSimple * ); const MapRegion & getRegion( size_t id ) const; size_t getRegionCount() const; uint32_t getDistance( const Heroes & hero, int targetIndex ); std::list getPath( const Heroes & hero, int targetIndex ); void resetPathfinder(); void ComputeStaticAnalysis(); uint32_t GetMapSeed() const; uint32_t GetWeekSeed() const; bool isAnyKingdomVisited( const MP2::MapObjectType objectType, const int32_t dstIndex ) const; void updatePassabilities(); private: World() = default; void Defaults(); void Reset(); void MonthOfMonstersAction( const Monster & ); bool ProcessNewMap( const std::string & filename, const bool checkPoLObjects ); void PostLoad( const bool setTilePassabilities ); bool updateTileMetadata( Maps::Tiles & tile, const MP2::MapObjectType objectType, const bool checkPoLObjects ); bool isValidCastleEntrance( const fheroes2::Point & tilePosition ) const; friend class Radar; friend StreamBase & operator<<( StreamBase &, const World & ); friend StreamBase & operator>>( StreamBase &, World & ); MapsTiles vec_tiles; AllHeroes vec_heroes; AllCastles vec_castles; Kingdoms vec_kingdoms; std::vector _rumors; EventsDate vec_eventsday; // index, object, color CapturedObjects map_captureobj; UltimateArtifact ultimate_artifact; uint32_t day = 0; uint32_t week = 0; uint32_t month = 0; int heroes_cond_wins = Heroes::UNKNOWN; int heroes_cond_loss = Heroes::UNKNOWN; MapObjects map_objects; uint32_t _seed{ 0 }; // Map seed // The following fields are not serialized std::map _allTeleports; // All indexes of tiles that contain stone liths of a certain type (sprite index) std::map _allWhirlpools; // All indexes of tiles that contain a certain part (sprite index) of the whirlpool std::vector _regions; PlayerWorldPathfinder _pathfinder; }; StreamBase & operator<<( StreamBase &, const CapturedObject & ); StreamBase & operator>>( StreamBase &, CapturedObject & ); StreamBase & operator<<( StreamBase &, const World & ); StreamBase & operator>>( StreamBase &, World & ); StreamBase & operator<<( StreamBase &, const MapObjects & ); StreamBase & operator>>( StreamBase &, MapObjects & ); extern World & world; #endif fheroes2-1.0.12+dfsg/src/fheroes2/world/world_loadmap.cpp000066400000000000000000001116621456075706000232330ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2019 - 2024 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include "artifact.h" #include "artifact_ultimate.h" #include "campaign_savedata.h" #include "campaign_scenariodata.h" #include "castle.h" #include "color.h" #include "game_over.h" #include "heroes.h" #include "kingdom.h" #include "logging.h" #include "map_format_helper.h" #include "map_format_info.h" #include "maps.h" #include "maps_fileinfo.h" #include "maps_objects.h" #include "maps_tiles.h" #include "maps_tiles_helper.h" #include "math_base.h" #include "mp2.h" #include "mp2_helper.h" #include "players.h" #include "race.h" #include "rand.h" #include "serialize.h" #include "settings.h" #include "world.h" #include "world_object_uid.h" namespace { const int32_t ultimateArtifactOffset = 9; Artifact getUltimateArtifact() { if ( Settings::Get().isCampaignGameType() ) { const Campaign::ScenarioVictoryCondition victoryCondition = Campaign::getCurrentScenarioVictoryCondition(); if ( victoryCondition == Campaign::ScenarioVictoryCondition::OBTAIN_ULTIMATE_CROWN ) { return Artifact::ULTIMATE_CROWN; } else if ( victoryCondition == Campaign::ScenarioVictoryCondition::OBTAIN_SPHERE_NEGATION ) { return Artifact::SPHERE_NEGATION; } } return Artifact::Rand( Artifact::ART_ULTIMATE ); } void fixCastleNames( const AllCastles & castles ) { // Find castles with no names. std::vector castleWithNoName; std::set castleNames; for ( Castle * castle : castles ) { if ( castle == nullptr ) { // How do we have an empty pointer in this container? assert( 0 ); continue; } const std::string & name = castle->GetName(); if ( name.empty() ) { castleWithNoName.emplace_back( castle ); } else { castleNames.emplace( name ); } } if ( castleWithNoName.empty() ) { return; } for ( Castle * castle : castleWithNoName ) { castle->setName( castleNames ); castleNames.emplace( castle->GetName() ); } } } bool World::LoadMapMP2( const std::string & filename, const bool isOriginalMp2File ) { Reset(); Defaults(); StreamFile fs; if ( !fs.open( filename, "rb" ) ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Map file not found " << filename ) return false; } // Read magic number. if ( fs.getBE32() != 0x5C000000 ) { // It is not a MP2 or MX2 file. return false; } const size_t totalFileSize = fs.size(); if ( totalFileSize < MP2::MP2_MAP_INFO_SIZE ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Map file " << filename << " is corrupted" ) return false; } // Go to the end of the file and read last 4 bytes which are used as a UID counter for all objects on the map. fs.seek( totalFileSize - 4 ); // In theory, this counter can be smaller than the some object UIDs if the map is corrupted or modified manually. Maps::setLastObjectUID( fs.getLE32() ); // Go to the end of the map info section to read two 32-bit values representing width and height of the map. fs.seek( MP2::MP2_MAP_INFO_SIZE - 2 * 4 ); const uint32_t mapWidth = fs.getLE32(); switch ( mapWidth ) { case Maps::SMALL: case Maps::MEDIUM: case Maps::LARGE: case Maps::XLARGE: width = static_cast( mapWidth ); break; default: width = 0; break; } const uint32_t mapHeight = fs.getLE32(); switch ( mapHeight ) { case Maps::SMALL: case Maps::MEDIUM: case Maps::LARGE: case Maps::XLARGE: height = static_cast( mapHeight ); break; default: height = 0; break; } if ( width == 0 || height == 0 || width != height ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Invalid MP2 file format: dimensions of the map [" << width << " x " << height << "] are incorrect." ) return false; } // This is to make sure that we are at the end of the map info section. assert( fs.tell() == MP2::MP2_MAP_INFO_SIZE ); const int32_t worldSize = width * height; if ( totalFileSize < MP2::MP2_MAP_INFO_SIZE + static_cast( worldSize ) * MP2::MP2_TILE_STRUCTURE_SIZE + MP2::MP2_ADDON_COUNT_SIZE ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Map file " << filename << " is corrupted" ) return false; } // Skip MP2 tile structures for now and read addons. fs.skip( static_cast( worldSize ) * MP2::MP2_TILE_STRUCTURE_SIZE ); // It is a valid case that a map has no add-ons. const size_t addonCount = fs.getLE32(); std::vector vec_mp2addons( addonCount ); if ( totalFileSize < MP2::MP2_MAP_INFO_SIZE + static_cast( worldSize ) * MP2::MP2_TILE_STRUCTURE_SIZE + addonCount * MP2::MP2_ADDON_STRUCTURE_SIZE + MP2::MP2_ADDON_COUNT_SIZE ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Map file " << filename << " is corrupted" ) return false; } for ( MP2::mp2addon_t & mp2addon : vec_mp2addons ) { MP2::loadAddon( fs, mp2addon ); } // If this assertion blows up it means that we are not reading the data properly from the file. assert( fs.tell() == MP2::MP2_MAP_INFO_SIZE + static_cast( worldSize ) * MP2::MP2_TILE_STRUCTURE_SIZE + addonCount * MP2::MP2_ADDON_STRUCTURE_SIZE + MP2::MP2_ADDON_COUNT_SIZE ); const size_t afterAddonInfoPos = fs.tell(); // Come back to the end of map info section and read information about MP2 tiles. fs.seek( MP2::MP2_MAP_INFO_SIZE ); vec_tiles.resize( worldSize ); const bool checkPoLObjects = !Settings::Get().isPriceOfLoyaltySupported() && isOriginalMp2File; MapsIndexes vec_object; // index maps for OBJ_CASTLE, OBJ_HERO, OBJ_SIGN, OBJ_BOTTLE, OBJ_EVENT vec_object.reserve( 128 ); for ( int32_t i = 0; i < worldSize; ++i ) { Maps::Tiles & tile = vec_tiles[i]; MP2::mp2tile_t mp2tile; MP2::loadTile( fs, mp2tile ); // There are some tiles which have object type as 65 and 193 which are Thatched Hut. This is exactly the same object as Peasant Hut. // Since the original number of object types is limited and in order not to confuse players we will convert this type into Peasant Hut. if ( mp2tile.mapObjectType == 65 ) { mp2tile.mapObjectType = MP2::OBJ_NON_ACTION_PEASANT_HUT; } else if ( mp2tile.mapObjectType == 193 ) { mp2tile.mapObjectType = MP2::OBJ_PEASANT_HUT; } if ( checkPoLObjects ) { switch ( mp2tile.mapObjectType ) { case MP2::OBJ_BARRIER: case MP2::OBJ_EXPANSION_DWELLING: case MP2::OBJ_EXPANSION_OBJECT: case MP2::OBJ_JAIL: case MP2::OBJ_TRAVELLER_TENT: ERROR_LOG( "Failed to load The Price of Loyalty map '" << filename << "' which is not supported by this version of the game." ) // You are trying to load a PoL map named as a MP2 file. return false; default: break; } } tile.Init( i, mp2tile ); // Read extra information if it's present. size_t addonIndex = mp2tile.nextAddonIndex; while ( addonIndex > 0 ) { if ( vec_mp2addons.size() <= addonIndex ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Invalid MP2 format: incorrect addon index " << addonIndex ) break; } tile.pushBottomLayerAddon( vec_mp2addons[addonIndex] ); tile.pushTopLayerAddon( vec_mp2addons[addonIndex] ); addonIndex = vec_mp2addons[addonIndex].nextAddonIndex; } tile.AddonsSort(); if ( MP2::doesObjectNeedExtendedMetadata( tile.GetObject() ) ) { vec_object.push_back( i ); } } // If this assertion blows up it means that we are not reading the data properly from the file. assert( fs.tell() == MP2::MP2_MAP_INFO_SIZE + static_cast( worldSize ) * MP2::MP2_TILE_STRUCTURE_SIZE ); // Go back to the section after the add-on structure information and read the rest of data. fs.seek( afterAddonInfoPos ); if ( totalFileSize < afterAddonInfoPos + static_cast( MP2::MP2_CASTLE_COUNT * MP2::MP2_CASTLE_POSITION_SIZE ) ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Map file " << filename << " is corrupted" ) return false; } // Read castle / town coordinates. The number of castles is fixed per map. for ( int32_t i = 0; i < MP2::MP2_CASTLE_COUNT; ++i ) { const int32_t posX = fs.get(); const int32_t posY = fs.get(); const int32_t castleType = fs.get(); if ( 0xFF == posX && 0xFF == posY ) { // This is an empty block so skip it. continue; } // Types from 0x0 to 0x7F are to towns, from 0x80 to 0xFF are for castles. switch ( castleType ) { case 0x00: case 0x80: vec_castles.AddCastle( new Castle( posX, posY, Race::KNGT ) ); break; case 0x01: case 0x81: vec_castles.AddCastle( new Castle( posX, posY, Race::BARB ) ); break; case 0x02: case 0x82: vec_castles.AddCastle( new Castle( posX, posY, Race::SORC ) ); break; case 0x03: case 0x83: vec_castles.AddCastle( new Castle( posX, posY, Race::WRLK ) ); break; case 0x04: case 0x84: vec_castles.AddCastle( new Castle( posX, posY, Race::WZRD ) ); break; case 0x05: case 0x85: vec_castles.AddCastle( new Castle( posX, posY, Race::NECR ) ); break; case 0x06: case 0x86: vec_castles.AddCastle( new Castle( posX, posY, Race::NONE ) ); break; default: DEBUG_LOG( DBG_GAME, DBG_WARN, "Invalid MP2 format: castle at position [" << posX << "; " << posY << "], index " << posX + posY * width << " has invalid castle type " << castleType ) break; } // Add the castle to the list of objects which can be captured. map_captureobj.Set( Maps::GetIndexFromAbsPoint( posX, posY ), MP2::OBJ_CASTLE, Color::NONE ); } // If this assertion blows up it means that we are not reading the data properly from the file. assert( fs.tell() == afterAddonInfoPos + static_cast( MP2::MP2_CASTLE_COUNT * MP2::MP2_CASTLE_POSITION_SIZE ) ); if ( totalFileSize < afterAddonInfoPos + static_cast( MP2::MP2_CASTLE_COUNT * MP2::MP2_CASTLE_POSITION_SIZE + MP2::MP2_CAPTURE_OBJECT_COUNT * MP2::MP2_CAPTURE_OBJECT_POSITION_SIZE ) ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Map file " << filename << " is corrupted" ) return false; } // Read information about objects which can be captured on the map. for ( int32_t i = 0; i < MP2::MP2_CAPTURE_OBJECT_COUNT; ++i ) { const int32_t posX = fs.get(); const int32_t posY = fs.get(); const int32_t objectType = fs.get(); if ( 0xFF == posX && 0xFF == posY ) { // This is an empty block so skip it. continue; } switch ( objectType ) { case 0x00: map_captureobj.Set( Maps::GetIndexFromAbsPoint( posX, posY ), MP2::OBJ_SAWMILL, Color::NONE ); break; case 0x01: map_captureobj.Set( Maps::GetIndexFromAbsPoint( posX, posY ), MP2::OBJ_ALCHEMIST_LAB, Color::NONE ); break; case 0x02: // Ore mine. case 0x03: // Sulfur mine. case 0x04: // Crystal mine. case 0x05: // Gems mine. case 0x06: // Gold mine. // TODO: should we verify the mine type by something? map_captureobj.Set( Maps::GetIndexFromAbsPoint( posX, posY ), MP2::OBJ_MINE, Color::NONE ); break; case 0x64: map_captureobj.Set( Maps::GetIndexFromAbsPoint( posX, posY ), MP2::OBJ_LIGHTHOUSE, Color::NONE ); break; case 0x65: map_captureobj.Set( Maps::GetIndexFromAbsPoint( posX, posY ), MP2::OBJ_DRAGON_CITY, Color::NONE ); break; case 0x67: map_captureobj.Set( Maps::GetIndexFromAbsPoint( posX, posY ), MP2::OBJ_ABANDONED_MINE, Color::NONE ); break; default: DEBUG_LOG( DBG_GAME, DBG_WARN, "Invalid MP2 format: unknown capture object type " << objectType << " at position [" << posX << "; " << posY << "], index " << posX + posY * width ) break; } } // If this assertion blows up it means that we are not reading the data properly from the file. assert( fs.tell() == afterAddonInfoPos + static_cast( MP2::MP2_CASTLE_COUNT * MP2::MP2_CASTLE_POSITION_SIZE + MP2::MP2_CAPTURE_OBJECT_COUNT * MP2::MP2_CAPTURE_OBJECT_POSITION_SIZE ) ); // TODO: find a way to use this value properly. const uint8_t obeliskCount = fs.get(); (void)obeliskCount; // Get the amount of last information blocks to be read. // It looks like this is a versioning system since only the last 2 entries matter. uint32_t infoBlockCount = 0; while ( true ) { const uint32_t l = fs.get(); const uint32_t h = fs.get(); if ( fs.tell() == fs.size() ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Map file " << filename << " is corrupted" ) return false; } if ( 0 == h && 0 == l ) { break; } infoBlockCount = 256 * h + l - 1; } // castle or heroes or (events, rumors, etc) for ( uint32_t i = 0; i < infoBlockCount; ++i ) { int32_t objectTileId = -1; const size_t blockSize = fs.getLE16(); if ( blockSize == 0 ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Invalid MP2 file format: received an empty block size " ) continue; } const std::vector pblock = fs.getRaw( blockSize ); for ( const int32_t tileId : vec_object ) { const Maps::Tiles & tile = vec_tiles[tileId]; if ( ( tile.getLayerType() & 0x3 ) != Maps::OBJECT_LAYER ) { continue; } if ( tile.metadata()[0] == i + 1 ) { objectTileId = tileId; break; } } if ( 0 <= objectTileId ) { const Maps::Tiles & tile = vec_tiles[objectTileId]; const MP2::MapObjectType objectType = tile.GetObject(); switch ( objectType ) { case MP2::OBJ_CASTLE: if ( MP2::MP2_CASTLE_STRUCTURE_SIZE != pblock.size() ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Invalid MP2 file format: received invalid castle structure size equal to " << pblock.size() ) } else { Castle * castle = getCastleEntrance( Maps::GetPoint( objectTileId ) ); if ( castle ) { castle->LoadFromMP2( pblock ); map_captureobj.SetColor( tile.GetIndex(), castle->GetColor() ); } else { DEBUG_LOG( DBG_GAME, DBG_WARN, "load castle: " << "not found, index: " << objectTileId ) } } break; case MP2::OBJ_RANDOM_TOWN: case MP2::OBJ_RANDOM_CASTLE: if ( MP2::MP2_CASTLE_STRUCTURE_SIZE != pblock.size() ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Invalid MP2 file format: received invalid castle structure size equal to " << pblock.size() ) } else { // Random castle's entrance tile is marked as OBJ_RNDCASTLE or OBJ_RNDTOWN instead of OBJ_CASTLE. Castle * castle = getCastle( Maps::GetPoint( objectTileId ) ); if ( castle ) { castle->LoadFromMP2( pblock ); Maps::UpdateCastleSprite( castle->GetCenter(), castle->GetRace(), castle->isCastle(), true ); Maps::ReplaceRandomCastleObjectId( castle->GetCenter() ); map_captureobj.SetColor( tile.GetIndex(), castle->GetColor() ); } else { DEBUG_LOG( DBG_GAME, DBG_WARN, "load castle: " << "not found, index: " << objectTileId ) } } break; case MP2::OBJ_JAIL: if ( MP2::MP2_HEROES_STRUCTURE_SIZE != pblock.size() ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Invalid MP2 file format: expected minimum size of Jail structure is " << MP2::MP2_HEROES_STRUCTURE_SIZE << " while loaded size is " << pblock.size() ) } else { // Byte 60 contains race type information. const uint8_t raceId = pblock[60]; int raceType = Race::KNGT; switch ( raceId ) { case 0: raceType = Race::KNGT; break; case 1: raceType = Race::BARB; break; case 2: raceType = Race::SORC; break; case 3: raceType = Race::WRLK; break; case 4: raceType = Race::WZRD; break; case 5: raceType = Race::NECR; break; default: DEBUG_LOG( DBG_GAME, DBG_WARN, "Invalid MP2 file format: unknown race ID for Jail hero " << static_cast( raceId ) ) break; } Heroes * hero = nullptr; // Byte 17 determines whether the hero has a custom portrait, and byte 18 contains the custom portrait ID. If the hero has a custom portrait, then we // should directly use the hero corresponding to this portrait, if possible. // MP2 format stores hero IDs start from 0, while fheroes2 engine starts from 1. if ( pblock[17] && pblock[18] + 1 <= Heroes::JARKONAS ) { hero = vec_heroes.Get( pblock[18] + 1 ); } if ( !hero || !hero->isAvailableForHire() ) { hero = GetHeroForHire( raceType ); } if ( hero ) { hero->LoadFromMP2( objectTileId, Color::NONE, raceType, true, pblock ); } else { DEBUG_LOG( DBG_GAME, DBG_WARN, "MP2 file format: no free heroes are available from race " << Race::String( raceType ) ) } } break; case MP2::OBJ_HERO: if ( MP2::MP2_HEROES_STRUCTURE_SIZE != pblock.size() ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "read heroes: " << "incorrect size block: " << pblock.size() ) } else { std::pair colorRace = Maps::getColorRaceFromHeroSprite( tile.GetObjectSpriteIndex() ); const Kingdom & kingdom = GetKingdom( colorRace.first ); if ( colorRace.second == Race::RAND && colorRace.first != Color::NONE ) { colorRace.second = kingdom.GetRace(); } // Check if the kingdom has exceeded the limit on hired heroes if ( kingdom.AllowRecruitHero( false ) ) { Heroes * hero = nullptr; // Byte 17 determines whether the hero has a custom portrait, and byte 18 contains the custom portrait ID. If the hero has a custom portrait, then // we should directly use the hero corresponding to this portrait, if possible. // MP2 format stores hero IDs start from 0, while fheroes2 engine starts from 1. if ( pblock[17] && pblock[18] + 1 <= Heroes::JARKONAS ) { hero = vec_heroes.Get( pblock[18] + 1 ); } if ( !hero || !hero->isAvailableForHire() ) { hero = GetHeroForHire( colorRace.second ); } if ( hero ) { hero->LoadFromMP2( objectTileId, colorRace.first, colorRace.second, false, pblock ); } else { DEBUG_LOG( DBG_GAME, DBG_WARN, "MP2 file format: no free heroes are available from race " << Race::String( colorRace.second ) ) } } else { DEBUG_LOG( DBG_GAME, DBG_WARN, "load heroes maximum" ) } } break; case MP2::OBJ_SIGN: case MP2::OBJ_BOTTLE: if ( MP2::MP2_SIGN_STRUCTURE_MIN_SIZE <= pblock.size() && 0x01 == pblock[0] ) { MapSign * obj = new MapSign(); obj->LoadFromMP2( objectTileId, pblock ); map_objects.add( obj ); } break; case MP2::OBJ_EVENT: if ( MP2::MP2_EVENT_STRUCTURE_MIN_SIZE <= pblock.size() && 0x01 == pblock[0] ) { MapEvent * obj = new MapEvent(); obj->LoadFromMP2( objectTileId, pblock ); map_objects.add( obj ); } break; case MP2::OBJ_SPHINX: if ( MP2::MP2_RIDDLE_STRUCTURE_MIN_SIZE <= pblock.size() && 0x00 == pblock[0] ) { MapSphinx * obj = new MapSphinx(); obj->LoadFromMP2( objectTileId, pblock ); map_objects.add( obj ); } break; default: DEBUG_LOG( DBG_GAME, DBG_WARN, "Invalid MP2 file format: unhandled object type " << MP2::StringObject( objectType ) ) break; } } // other events else if ( 0x00 == pblock[0] ) { // Daily event. if ( MP2::MP2_EVENT_STRUCTURE_MIN_SIZE <= pblock.size() && pblock[42] == 1 ) { vec_eventsday.emplace_back(); vec_eventsday.back().LoadFromMP2( pblock ); } else if ( MP2::MP2_RUMOR_STRUCTURE_MIN_SIZE <= pblock.size() ) { // Structure containing information about a rumor. // // - uint8_t (1 byte) // Always equal to 0. // // - unused 7 bytes // Unknown / unused. TODO: find out what these bytes used for. // // - string // Null terminated string of the rumor. std::string rumor( reinterpret_cast( pblock.data() ) + 8 ); if ( !rumor.empty() ) { _rumors.emplace_back( std::move( rumor ) ); DEBUG_LOG( DBG_GAME, DBG_INFO, "MP2 format: add rumor " << _rumors.back() ) } } else { DEBUG_LOG( DBG_GAME, DBG_WARN, "Invalid MP2 format: unknown event type object of size of " << pblock.size() ) } } else { DEBUG_LOG( DBG_GAME, DBG_WARN, "Invalid MP2 format: unknown information object of size of " << pblock.size() ) } } // If this assertion blows up it means that we are not reading the data properly from the file. assert( fs.tell() + 4 == fs.size() ); fixCastleNames( vec_castles ); // clear artifact flags to correctly generate random artifacts fheroes2::ResetArtifactStats(); const Maps::FileInfo & mapInfo = Settings::Get().getCurrentMapInfo(); // do not let the player get a random artifact that allows him to win the game if ( ( mapInfo.ConditionWins() & GameOver::WINS_ARTIFACT ) == GameOver::WINS_ARTIFACT && !mapInfo.WinsFindUltimateArtifact() ) { fheroes2::ExcludeArtifactFromRandom( mapInfo.WinsFindArtifactID() ); } if ( !ProcessNewMap( filename, checkPoLObjects ) ) { return false; } DEBUG_LOG( DBG_GAME, DBG_INFO, "Loading of MP2 map is completed." ) return true; } bool World::loadResurrectionMap( const std::string & filename ) { Reset(); Defaults(); Maps::Map_Format::MapFormat map; if ( !Maps::Map_Format::loadMap( filename, map ) ) { DEBUG_LOG( DBG_GAME, DBG_WARN, "Map file '" << filename << "' is corrupted or missing." ) return false; } width = map.size; height = map.size; vec_tiles.resize( static_cast( width ) * height ); if ( !Maps::readAllTiles( map ) ) { return false; } // Clear artifact flags to correctly generate random artifacts. fheroes2::ResetArtifactStats(); if ( !ProcessNewMap( filename, false ) ) { return false; } DEBUG_LOG( DBG_GAME, DBG_INFO, "Loading of FH2 map is completed." ) return true; } bool World::ProcessNewMap( const std::string & filename, const bool checkPoLObjects ) { for ( Maps::Tiles & tile : vec_tiles ) { Maps::Tiles::fixTileObjectType( tile ); if ( !updateTileMetadata( tile, tile.GetObject(), checkPoLObjects ) ) { ERROR_LOG( "Failed to load The Price of Loyalty map '" << filename << "' which is not supported by this version of the game." ) // You are trying to load a PoL map named as a MP2 file. return false; } } // add heroes to kingdoms vec_kingdoms.AddHeroes( vec_heroes ); // add castles to kingdoms vec_kingdoms.AddCastles( vec_castles ); const Maps::FileInfo & mapInfo = Settings::Get().getCurrentMapInfo(); // update wins, loss conditions if ( GameOver::WINS_HERO & mapInfo.ConditionWins() ) { const fheroes2::Point & pos = mapInfo.WinsMapsPositionObject(); const Heroes * hero = GetHeroes( pos ); if ( hero == nullptr ) { heroes_cond_wins = Heroes::UNKNOWN; ERROR_LOG( "A winning condition hero at location ['" << pos.x << ", " << pos.y << "'] is not found." ) } else { heroes_cond_wins = hero->GetID(); } } if ( GameOver::LOSS_HERO & mapInfo.ConditionLoss() ) { const fheroes2::Point & pos = mapInfo.LossMapsPositionObject(); Heroes * hero = GetHeroes( pos ); if ( hero == nullptr ) { heroes_cond_loss = Heroes::UNKNOWN; ERROR_LOG( "A loosing condition hero at location ['" << pos.x << ", " << pos.y << "'] is not found." ) } else { heroes_cond_loss = hero->GetID(); hero->SetModes( Heroes::NOTDISMISS | Heroes::CUSTOM ); } } // Search for a tile with a predefined Ultimate Artifact const MapsTiles::iterator ultArtTileIter = std::find_if( vec_tiles.begin(), vec_tiles.end(), []( const Maps::Tiles & tile ) { return tile.isSameMainObject( MP2::OBJ_RANDOM_ULTIMATE_ARTIFACT ); } ); const auto checkTileForSuitabilityForUltArt = [this]( const int32_t idx ) { const int32_t x = idx % width; if ( x < ultimateArtifactOffset || x >= width - ultimateArtifactOffset ) { return false; } const int32_t y = idx / width; if ( y < ultimateArtifactOffset || y >= height - ultimateArtifactOffset ) { return false; } return GetTiles( idx ).GoodForUltimateArtifact(); }; // There is no tile with a predefined Ultimate Artifact, pick a suitable tile randomly if ( ultArtTileIter == vec_tiles.end() ) { MapsIndexes pool; pool.reserve( vec_tiles.size() / 2 ); for ( const Maps::Tiles & tile : vec_tiles ) { const int32_t idx = tile.GetIndex(); if ( checkTileForSuitabilityForUltArt( idx ) ) { pool.push_back( idx ); } } if ( !pool.empty() ) { const int32_t pos = Rand::Get( pool ); ultimate_artifact.Set( pos, getUltimateArtifact() ); DEBUG_LOG( DBG_GAME, DBG_INFO, "Ultimate Artifact index: " << pos ) } else { DEBUG_LOG( DBG_GAME, DBG_WARN, "no suitable tile to place the Ultimate Artifact was found" ) } } // There is a tile with a predefined Ultimate Artifact, pick a tile nearby in the radius specified in the artifact's properties else { const int32_t radius = static_cast( ultArtTileIter->metadata()[0] ); resetObjectMetadata( *ultArtTileIter ); // Remove the predefined Ultimate Artifact object ultArtTileIter->Remove( ultArtTileIter->GetObjectUID() ); ultArtTileIter->setAsEmpty(); // Use the predefined Ultimate Artifact tile index as a fallback int32_t pos = ultArtTileIter->GetIndex(); if ( radius > 0 ) { MapsIndexes pool = Maps::getAroundIndexes( pos, radius ); // Maps::getAroundIndexes() results does not include the central index, so we have to append it manually assert( std::find( pool.begin(), pool.end(), pos ) == pool.end() ); pool.push_back( pos ); pool.erase( std::remove_if( pool.begin(), pool.end(), [&checkTileForSuitabilityForUltArt]( const int32_t idx ) { return !checkTileForSuitabilityForUltArt( idx ); } ), pool.end() ); if ( !pool.empty() ) { pos = Rand::Get( pool ); } } ultimate_artifact.Set( pos, getUltimateArtifact() ); DEBUG_LOG( DBG_GAME, DBG_INFO, "predefined Ultimate Artifact index: " << ultArtTileIter->GetIndex() << ", radius: " << radius << ", Ultimate Artifact index: " << pos ) } PostLoad( true ); vec_kingdoms.ApplyPlayWithStartingHero(); // If we are in developer mode, then add the DEBUG_HERO if ( IS_DEVEL() ) { Kingdom & kingdom = GetKingdom( Color::GetFirst( Players::HumanColors() ) ); if ( !kingdom.GetCastles().empty() ) { const Castle * castle = kingdom.GetCastles().front(); const fheroes2::Point & cp = castle->GetCenter(); Heroes * hero = vec_heroes.Get( Heroes::DEBUG_HERO ); if ( hero && !GetTiles( cp.x, cp.y + 1 ).getHero() ) { hero->Recruit( castle->GetColor(), { cp.x, cp.y + 1 } ); } } } return true; } bool World::updateTileMetadata( Maps::Tiles & tile, const MP2::MapObjectType objectType, const bool checkPoLObjects ) { switch ( objectType ) { case MP2::OBJ_ARTIFACT: updateObjectInfoTile( tile, true ); if ( checkPoLObjects ) { const Artifact art = Maps::getArtifactFromTile( tile ); if ( fheroes2::isPriceOfLoyaltyArtifact( art.GetID() ) ) { return false; } } break; case MP2::OBJ_ABANDONED_MINE: case MP2::OBJ_ALCHEMIST_LAB: case MP2::OBJ_ARCHER_HOUSE: case MP2::OBJ_BARRIER: case MP2::OBJ_BOAT: case MP2::OBJ_CAMPFIRE: case MP2::OBJ_CAVE: case MP2::OBJ_CITY_OF_DEAD: case MP2::OBJ_DAEMON_CAVE: case MP2::OBJ_DERELICT_SHIP: case MP2::OBJ_DESERT_TENT: case MP2::OBJ_DRAGON_CITY: case MP2::OBJ_DWARF_COTTAGE: case MP2::OBJ_EVENT: case MP2::OBJ_EXCAVATION: case MP2::OBJ_FLOTSAM: case MP2::OBJ_FOUNTAIN: case MP2::OBJ_GENIE_LAMP: case MP2::OBJ_GOBLIN_HUT: case MP2::OBJ_GRAVEYARD: case MP2::OBJ_HALFLING_HOLE: case MP2::OBJ_LEAN_TO: case MP2::OBJ_MAGIC_GARDEN: case MP2::OBJ_MINE: case MP2::OBJ_MONSTER: case MP2::OBJ_PEASANT_HUT: case MP2::OBJ_PYRAMID: case MP2::OBJ_RANDOM_ARTIFACT: case MP2::OBJ_RANDOM_ARTIFACT_MAJOR: case MP2::OBJ_RANDOM_ARTIFACT_MINOR: case MP2::OBJ_RANDOM_ARTIFACT_TREASURE: case MP2::OBJ_RANDOM_MONSTER: case MP2::OBJ_RANDOM_MONSTER_MEDIUM: case MP2::OBJ_RANDOM_MONSTER_STRONG: case MP2::OBJ_RANDOM_MONSTER_VERY_STRONG: case MP2::OBJ_RANDOM_MONSTER_WEAK: case MP2::OBJ_RANDOM_RESOURCE: case MP2::OBJ_RESOURCE: case MP2::OBJ_RUINS: case MP2::OBJ_SAWMILL: case MP2::OBJ_SEA_CHEST: case MP2::OBJ_SHRINE_FIRST_CIRCLE: case MP2::OBJ_SHRINE_SECOND_CIRCLE: case MP2::OBJ_SHRINE_THIRD_CIRCLE: case MP2::OBJ_SHIPWRECK: case MP2::OBJ_SHIPWRECK_SURVIVOR: case MP2::OBJ_SKELETON: case MP2::OBJ_STONE_LITHS: case MP2::OBJ_TRAVELLER_TENT: case MP2::OBJ_TREASURE_CHEST: case MP2::OBJ_TREE_CITY: case MP2::OBJ_TREE_HOUSE: case MP2::OBJ_TREE_OF_KNOWLEDGE: case MP2::OBJ_TROLL_BRIDGE: case MP2::OBJ_WAGON: case MP2::OBJ_WAGON_CAMP: case MP2::OBJ_WATCH_TOWER: case MP2::OBJ_WATER_WHEEL: case MP2::OBJ_WINDMILL: case MP2::OBJ_WITCHS_HUT: updateObjectInfoTile( tile, true ); break; case MP2::OBJ_AIR_ALTAR: case MP2::OBJ_BARROW_MOUNDS: case MP2::OBJ_EARTH_ALTAR: case MP2::OBJ_FIRE_ALTAR: case MP2::OBJ_WATER_ALTAR: // We need to clear metadata because it is being stored as a part of an MP2 object. resetObjectInfoOnTile( tile ); updateObjectInfoTile( tile, true ); break; case MP2::OBJ_RANDOM_ULTIMATE_ARTIFACT: // We need information from an Ultimate artifact for later use. We will reset metadata later. break; case MP2::OBJ_HERO: { // remove map editor sprite if ( tile.getObjectIcnType() == MP2::OBJ_ICN_TYPE_MINIHERO ) tile.Remove( tile.GetObjectUID() ); Heroes * chosenHero = GetHeroes( Maps::GetPoint( tile.GetIndex() ) ); assert( chosenHero != nullptr ); tile.setHero( chosenHero ); if ( checkPoLObjects ) { Heroes * hero = tile.getHero(); assert( hero ); if ( hero->isPoLPortrait() ) { return false; } const BagArtifacts & artifacts = hero->GetBagArtifacts(); for ( const Artifact & artifact : artifacts ) { if ( fheroes2::isPriceOfLoyaltyArtifact( artifact.GetID() ) ) { return false; } } } const MP2::MapObjectType updatedObjectType = tile.GetObject( false ); if ( updatedObjectType != objectType ) { return updateTileMetadata( tile, updatedObjectType, checkPoLObjects ); } break; } default: // Remove any metadata from other objects. resetObjectMetadata( tile ); break; } return true; } fheroes2-1.0.12+dfsg/src/fheroes2/world/world_object_uid.cpp000066400000000000000000000035451456075706000237250ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "world_object_uid.h" namespace { uint32_t objectCounter{ 0 }; } namespace Maps { void resetObjectUID() { objectCounter = 0; } uint32_t getNewObjectUID() { ++objectCounter; return objectCounter; } uint32_t getLastObjectUID() { return objectCounter; } void setLastObjectUID( const uint32_t uid ) { objectCounter = uid; } } fheroes2-1.0.12+dfsg/src/fheroes2/world/world_object_uid.h000066400000000000000000000031761456075706000233720ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include namespace Maps { void resetObjectUID(); uint32_t getNewObjectUID(); uint32_t getLastObjectUID(); void setLastObjectUID( const uint32_t uid ); } fheroes2-1.0.12+dfsg/src/fheroes2/world/world_pathfinding.cpp000066400000000000000000001460551456075706000241150ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "world_pathfinding.h" #include #include #include #include #include #include #include #include #include "army.h" #include "artifact.h" #include "castle.h" #include "difficulty.h" #include "direction.h" #include "game.h" #include "game_over.h" #include "ground.h" #include "heroes.h" #include "kingdom.h" #include "maps.h" #include "maps_fileinfo.h" #include "maps_tiles.h" #include "maps_tiles_helper.h" #include "math_base.h" #include "pairs.h" #include "players.h" #include "rand.h" #include "route.h" #include "settings.h" #include "spell.h" #include "spell_info.h" #include "tools.h" #include "world.h" namespace { bool isFindArtifactVictoryConditionForHuman( const Artifact & art ) { assert( art.isValid() ); const Maps::FileInfo & mapInfo = Settings::Get().getCurrentMapInfo(); if ( ( mapInfo.ConditionWins() & GameOver::WINS_ARTIFACT ) == 0 ) { return false; } if ( mapInfo.WinsFindUltimateArtifact() ) { return art.isUltimate(); } return ( art.GetID() == mapInfo.WinsFindArtifactID() ); } bool isTileAvailableForWalkThrough( const int tileIndex, const bool fromWater ) { const Maps::Tiles & tile = world.GetTiles( tileIndex ); const bool toWater = tile.isWater(); const MP2::MapObjectType objectType = tile.GetObject(); if ( objectType == MP2::OBJ_HERO || objectType == MP2::OBJ_MONSTER || objectType == MP2::OBJ_BOAT ) { return false; } if ( MP2::isPickupObject( objectType ) || MP2::isActionObject( objectType, fromWater ) ) { return false; } if ( fromWater && !toWater && objectType == MP2::OBJ_COAST ) { return false; } // In general, direct movement from a shore tile to a water tile is not possible, but AI can use this movement for transparent Summon Boat // spellcasting. In this case, it may be necessary to cut the resulting path on a water tile. if ( !fromWater && toWater && objectType == MP2::OBJ_NONE ) { return false; } return true; } bool isTileAvailableForWalkThroughForAIWithArmy( const int tileIndex, const bool fromWater, const int color, const bool isArtifactsBagFull, const double armyStrength, const double minimalAdvantage ) { assert( color & Color::ALL ); const Maps::Tiles & tile = world.GetTiles( tileIndex ); const bool toWater = tile.isWater(); const MP2::MapObjectType objectType = tile.GetObject(); const auto isTileAccessible = [color, armyStrength, minimalAdvantage, &tile]() { // Creating an Army instance is a relatively heavy operation, so cache it to speed up calculations static Army tileArmy; tileArmy.setFromTile( tile ); const int tileArmyColor = tileArmy.GetColor(); // Tile can be guarded by our own or a friendly army (for example, our ally used a Set Elemental Guardian spell on his mine) if ( color == tileArmyColor || Players::isFriends( color, tileArmyColor ) ) { return true; } return tileArmy.GetStrength() * minimalAdvantage <= armyStrength; }; // Enemy heroes can be defeated and passed through if ( objectType == MP2::OBJ_HERO ) { // Heroes on the water can be attacked from the nearby shore, but they cannot be passed through if ( fromWater != toWater ) { assert( !fromWater && toWater ); return false; } const Heroes * otherHero = tile.getHero(); assert( otherHero != nullptr ); // Friendly heroes cannot be passed through if ( otherHero->isFriends( color ) ) { return false; } // Heroes in castles cannot be passed through if ( otherHero->inCastle() ) { return false; } // WINS_HERO victory condition does not apply to AI-controlled players, we have to keep this hero alive for the human player if ( otherHero == world.GetHeroesCondWins() ) { return false; } return otherHero->GetArmy().GetStrength() * minimalAdvantage <= armyStrength; } // Pickupable objects (including artifacts) can be picked up and passed through if ( MP2::isPickupObject( objectType ) ) { // Genie Lamp is special: there may not be enough money to hire all the genies and remove this object from the map, high-level // AI logic will decide what to do with it if ( objectType == MP2::OBJ_GENIE_LAMP ) { return false; } // If this object doesn't contain an artifact, then just pick it up and go through if ( !MP2::isArtifactObject( objectType ) ) { return true; } const Artifact art = Maps::getArtifactFromTile( tile ); if ( !art.isValid() ) { return true; } // WINS_ARTIFACT victory condition does not apply to AI-controlled players, we should leave this artifact untouched for the human player if ( isFindArtifactVictoryConditionForHuman( art ) ) { return false; } // This object contains an artifact, but it is not an artifact itself, pick it up and go through if ( objectType != MP2::OBJ_ARTIFACT ) { return true; } // Hero should have a place for this artifact in his artifact bag if ( isArtifactsBagFull ) { return false; } // Check the conditions for picking up the artifact (except for the artifact guard): if there are any, then we can't pick it up // "automatically", high-level AI logic will decide what to do with it const Maps::ArtifactCaptureCondition condition = getArtifactCaptureCondition( tile ); switch ( condition ) { case Maps::ArtifactCaptureCondition::PAY_2000_GOLD: case Maps::ArtifactCaptureCondition::PAY_2500_GOLD_AND_3_RESOURCES: case Maps::ArtifactCaptureCondition::PAY_3000_GOLD_AND_5_RESOURCES: case Maps::ArtifactCaptureCondition::HAVE_WISDOM_SKILL: case Maps::ArtifactCaptureCondition::HAVE_LEADERSHIP_SKILL: return false; default: break; } // Artifact may be guarded, check the power of guardians. return isTileAccessible(); } // Monsters can be defeated and passed through if ( objectType == MP2::OBJ_MONSTER ) { return isTileAccessible(); } // AI may have the key for the barrier if ( objectType == MP2::OBJ_BARRIER ) { return world.GetKingdom( color ).IsVisitTravelersTent( getColorFromTile( tile ) ); } // AI can use boats to overcome water obstacles if ( objectType == MP2::OBJ_BOAT ) { return true; } // If we can't step on this tile, then it cannot be passed through if ( MP2::isNeedStayFront( objectType ) ) { return false; } // The castle tile can be passed through if we got there using Town Gate or Town Portal spells, which means that // this should be our castle if ( objectType == MP2::OBJ_CASTLE ) { return color == Maps::getColorFromTile( tile ); } // If we can step on this tile, but it is protected by monsters and it is impossible to refuse a fight, then it // can be passed through if we manage to defeat the monsters if ( MP2::isBattleMandatoryifObjectIsProtected( objectType ) ) { return isTileAccessible(); } // We can step on this tile and it is either not protected by monsters, or we can refuse a fight, just go ahead return true; } bool isMovementAllowedForColor( const int from, const int direction, const int color, const bool isSummonBoatSpellAvailable ) { const Maps::Tiles & fromTile = world.GetTiles( from ); const bool fromWater = fromTile.isWater(); // check corner water/coast if ( fromWater ) { const int mapWidth = world.w(); switch ( direction ) { case Direction::TOP_LEFT: { assert( from >= mapWidth + 1 ); if ( world.GetTiles( from - mapWidth - 1 ).isWater() && ( !world.GetTiles( from - 1 ).isWater() || !world.GetTiles( from - mapWidth ).isWater() ) ) { // Cannot sail through the corner of land. return false; } break; } case Direction::TOP_RIGHT: { assert( from >= mapWidth && from + 1 < mapWidth * world.h() ); if ( world.GetTiles( from - mapWidth + 1 ).isWater() && ( !world.GetTiles( from + 1 ).isWater() || !world.GetTiles( from - mapWidth ).isWater() ) ) { // Cannot sail through the corner of land. return false; } break; } case Direction::BOTTOM_RIGHT: { assert( from + mapWidth + 1 < mapWidth * world.h() ); if ( world.GetTiles( from + mapWidth + 1 ).isWater() && ( !world.GetTiles( from + 1 ).isWater() || !world.GetTiles( from + mapWidth ).isWater() ) ) { // Cannot sail through the corner of land. return false; } break; } case Direction::BOTTOM_LEFT: { assert( from >= 1 && from + mapWidth - 1 < mapWidth * world.h() ); if ( world.GetTiles( from + mapWidth - 1 ).isWater() && ( !world.GetTiles( from - 1 ).isWater() || !world.GetTiles( from + mapWidth ).isWater() ) ) { // Cannot sail through the corner of land. return false; } break; } default: break; } } if ( !fromTile.isPassableTo( direction ) ) { return false; } const Maps::Tiles & toTile = world.GetTiles( Maps::GetDirectionIndex( from, direction ) ); if ( toTile.isPassableFrom( Direction::Reflect( direction ), fromWater, false, color ) ) { return true; } // Check whether it is possible to get to this tile using the Summon Boat spell... if ( !isSummonBoatSpellAvailable ) { return false; } // ... this only works when moving from the shore to an empty water tile... if ( fromWater || !toTile.isWater() || toTile.GetObject() != MP2::OBJ_NONE ) { return false; } // ... and this tile should be reachable from the shore (as if this shore tile were a water tile) return toTile.isPassableFrom( Direction::Reflect( direction ), true, false, color ); } bool isTileAccessibleForAIWithArmy( const int tileIndex, const double armyStrength, const double minimalAdvantage ) { // Tiles with monsters are considered accessible regardless of the monsters' power, high-level AI logic // will decide what to do with them if ( world.GetTiles( tileIndex ).GetObject() == MP2::OBJ_MONSTER ) { return true; } for ( const int32_t monsterIndex : Maps::getMonstersProtectingTile( tileIndex ) ) { // Creating an Army instance is a relatively heavy operation, so cache it to speed up calculations static Army tileArmy; tileArmy.setFromTile( world.GetTiles( monsterIndex ) ); // Tiles guarded by too powerful wandering monsters are considered inaccessible if ( tileArmy.GetStrength() * minimalAdvantage > armyStrength ) { return false; } } return true; } uint32_t subtractMovePoints( const uint32_t movePoints, const uint32_t subtractedMovePoints, const uint32_t maxMovePoints ) { // We do not perform pathfinding for a real hero on the map, this is no-op if ( maxMovePoints == 0 ) { return 0; } // This movement takes place at the beginning of a new turn: start with max movement points, // don't carry leftovers from the previous turn if ( movePoints < subtractedMovePoints ) { assert( maxMovePoints >= subtractedMovePoints ); return maxMovePoints - subtractedMovePoints; } // This movement takes place on the same turn return movePoints - subtractedMovePoints; } } uint32_t WorldPathfinder::getDistance( int targetIndex ) const { assert( targetIndex >= 0 && static_cast( targetIndex ) < _cache.size() ); return _cache[targetIndex]._cost; } uint32_t WorldPathfinder::getMovementPenalty( const int from, const int to, const int direction ) const { const Maps::Tiles & fromTile = world.GetTiles( from ); const Maps::Tiles & toTile = world.GetTiles( to ); uint32_t penalty = fromTile.isRoad() && toTile.isRoad() ? Maps::Ground::roadPenalty : Maps::Ground::GetPenalty( fromTile, _pathfindingSkill ); // Diagonal movement costs 50% more if ( Direction::isDiagonal( direction ) ) { penalty = penalty * 3 / 2; } // If we perform pathfinding for a real hero on the map, we have to work out the "last move" // logic: if this move is the last one on the current turn, then we can move to any adjacent // tile (both in straight and diagonal direction) as long as we have enough movement points // to move over our current tile in the straight direction if ( getMaxMovePoints( fromTile.isWater() ) > 0 ) { const WorldNode & node = _cache[from]; // No dead ends allowed assert( from == _pathStart || node._from != -1 ); const uint32_t remainingMovePoints = node._remainingMovePoints; const uint32_t fromTilePenalty = fromTile.isRoad() ? Maps::Ground::roadPenalty : Maps::Ground::GetPenalty( fromTile, _pathfindingSkill ); // If we still have enough movement points to move over the source tile in the straight // direction, but not enough to move to the destination tile, then the "last move" logic // is applied and we can move to the destination tile anyway at the expense of all the // remaining movement points if ( remainingMovePoints >= fromTilePenalty && remainingMovePoints < penalty ) { return remainingMovePoints; } } return penalty; } void WorldPathfinder::reset() { // The following optimization will only work correctly for square maps assert( world.w() == world.h() ); const size_t worldSize = world.getSize(); if ( _cache.size() != worldSize ) { _cache.clear(); _cache.resize( worldSize ); const Directions & directions = Direction::All(); _mapOffset.resize( directions.size() ); for ( size_t i = 0; i < directions.size(); ++i ) { _mapOffset[i] = Maps::GetDirectionIndex( 0, directions[i] ); } } _pathStart = -1; _color = Color::NONE; _remainingMovePoints = 0; _pathfindingSkill = Skill::Level::EXPERT; } void WorldPathfinder::processWorldMap() { assert( _cache.size() == world.getSize() && Maps::isValidAbsIndex( _pathStart ) ); for ( WorldNode & node : _cache ) { node.reset(); } _cache[_pathStart] = WorldNode( -1, 0, MP2::OBJ_NONE, _remainingMovePoints ); std::vector nodesToExplore; nodesToExplore.push_back( _pathStart ); for ( size_t lastProcessedNode = 0; lastProcessedNode < nodesToExplore.size(); ++lastProcessedNode ) { processCurrentNode( nodesToExplore, nodesToExplore[lastProcessedNode] ); } } void WorldPathfinder::checkAdjacentNodes( std::vector & nodesToExplore, int currentNodeIdx ) { const Directions & directions = Direction::All(); const WorldNode & currentNode = _cache[currentNodeIdx]; const uint32_t maxMovePoints = getMaxMovePoints( world.GetTiles( currentNodeIdx ).isWater() ); for ( size_t i = 0; i < directions.size(); ++i ) { if ( !Maps::isValidDirection( currentNodeIdx, directions[i] ) || !isMovementAllowed( currentNodeIdx, directions[i] ) ) { continue; } const int newIndex = currentNodeIdx + _mapOffset[i]; if ( newIndex == _pathStart ) { continue; } const uint32_t movementPenalty = getMovementPenalty( currentNodeIdx, newIndex, directions[i] ); const uint32_t movementCost = currentNode._cost + movementPenalty; WorldNode & newNode = _cache[newIndex]; if ( newNode._from == -1 || newNode._cost > movementCost ) { const Maps::Tiles & newTile = world.GetTiles( newIndex ); newNode._from = currentNodeIdx; newNode._cost = movementCost; newNode._objectID = newTile.GetObject(); newNode._remainingMovePoints = subtractMovePoints( currentNode._remainingMovePoints, movementPenalty, maxMovePoints ); nodesToExplore.push_back( newIndex ); } } } bool WorldPathfinder::isMovementAllowed( const int from, const int direction ) const { return isMovementAllowedForColor( from, direction, _color, false ); } void PlayerWorldPathfinder::reset() { WorldPathfinder::reset(); _maxMovePoints = 0; } void PlayerWorldPathfinder::reEvaluateIfNeeded( const Heroes & hero ) { auto currentSettings = std::tie( _pathStart, _color, _remainingMovePoints, _pathfindingSkill, _maxMovePoints ); const auto newSettings = std::make_tuple( hero.GetIndex(), hero.GetColor(), hero.GetMovePoints(), static_cast( hero.GetLevelSkill( Skill::Secondary::PATHFINDING ) ), hero.GetMaxMovePoints() ); if ( currentSettings != newSettings ) { currentSettings = newSettings; processWorldMap(); } } std::list PlayerWorldPathfinder::buildPath( const int targetIndex ) const { assert( _cache.size() == world.getSize() && Maps::isValidAbsIndex( _pathStart ) && Maps::isValidAbsIndex( targetIndex ) ); std::list path; // Destination is not reachable if ( _cache[targetIndex]._cost == 0 ) { return path; } #ifndef NDEBUG std::set uniqPathIndexes; #endif int currentNode = targetIndex; while ( currentNode != _pathStart ) { assert( currentNode != -1 ); const WorldNode & node = _cache[currentNode]; assert( node._from != -1 ); const uint32_t cost = node._cost - _cache[node._from]._cost; path.emplace_front( currentNode, node._from, Maps::GetDirection( node._from, currentNode ), cost ); // The path should not pass through the same tile more than once assert( uniqPathIndexes.insert( node._from ).second ); currentNode = node._from; } return path; } void PlayerWorldPathfinder::processCurrentNode( std::vector & nodesToExplore, const int currentNodeIdx ) { const bool isFirstNode = ( currentNodeIdx == _pathStart ); const WorldNode & currentNode = _cache[currentNodeIdx]; const bool fromWater = world.GetTiles( _pathStart ).isWater(); if ( !isFirstNode && !isTileAvailableForWalkThrough( currentNodeIdx, fromWater ) ) { return; } const MapsIndexes & monsters = Maps::getMonstersProtectingTile( currentNodeIdx ); // If the current tile is protected by monsters, and this tile is not the starting tile, then the hero can only move towards one of the neighboring monsters if ( !isFirstNode && !monsters.empty() ) { const uint32_t maxMovePoints = getMaxMovePoints( fromWater ); for ( int monsterIndex : monsters ) { const int direction = Maps::GetDirection( currentNodeIdx, monsterIndex ); if ( direction == Direction::UNKNOWN || direction == Direction::CENTER || !isMovementAllowed( currentNodeIdx, direction ) ) { continue; } const uint32_t movementPenalty = getMovementPenalty( currentNodeIdx, monsterIndex, direction ); const uint32_t movementCost = currentNode._cost + movementPenalty; WorldNode & monsterNode = _cache[monsterIndex]; if ( monsterNode._from == -1 || monsterNode._cost > movementCost ) { const Maps::Tiles & monsterTile = world.GetTiles( monsterIndex ); monsterNode._from = currentNodeIdx; monsterNode._cost = movementCost; monsterNode._objectID = monsterTile.GetObject(); monsterNode._remainingMovePoints = subtractMovePoints( currentNode._remainingMovePoints, movementPenalty, maxMovePoints ); } } } else { checkAdjacentNodes( nodesToExplore, currentNodeIdx ); } } uint32_t PlayerWorldPathfinder::getMaxMovePoints( const bool /* onWater */ ) const { return _maxMovePoints; } void AIWorldPathfinder::reset() { WorldPathfinder::reset(); _maxMovePointsOnLand = 0; _maxMovePointsOnWater = 0; _armyStrength = -1; _isArtifactsBagFull = false; _isSummonBoatSpellAvailable = false; _townGateCastleIndex = -1; _townPortalCastleIndexes.clear(); } void AIWorldPathfinder::reEvaluateIfNeeded( const Heroes & hero ) { const bool isSummonBoatSpellAvailable = [this, &hero]() { static const Spell summonBoat( Spell::SUMMONBOAT ); if ( !hero.HaveSpell( summonBoat ) ) { return false; } if ( hero.GetSpellPoints() < summonBoat.spellPoints( &hero ) + hero.GetMaxSpellPoints() * _spellPointsReserveRatio ) { return false; } return ( fheroes2::getSummonableBoat( hero ) != -1 ); }(); const int32_t townGateCastleIndex = [this, &hero]() { static const Spell townGate( Spell::TOWNGATE ); if ( hero.Modes( Heroes::PATROL ) || !hero.CanCastSpell( townGate ) ) { return -1; } if ( hero.GetSpellPoints() < townGate.spellPoints( &hero ) + hero.GetMaxSpellPoints() * _spellPointsReserveRatio ) { return -1; } const Castle * castle = fheroes2::getNearestCastleTownGate( hero ); assert( castle != nullptr && castle->GetHero() == nullptr ); return castle->GetIndex(); }(); const std::vector townPortalCastleIndexes = [this, &hero]() { static const Spell townPortal( Spell::TOWNPORTAL ); std::vector result; if ( hero.Modes( Heroes::PATROL ) || !hero.CanCastSpell( townPortal ) ) { return result; } if ( hero.GetSpellPoints() < townPortal.spellPoints( &hero ) + hero.GetMaxSpellPoints() * _spellPointsReserveRatio ) { return result; } for ( const Castle * castle : hero.GetKingdom().GetCastles() ) { assert( castle != nullptr ); if ( castle->GetHero() == nullptr ) { result.push_back( castle->GetIndex() ); } } return result; }(); auto currentSettings = std::tie( _pathStart, _color, _remainingMovePoints, _pathfindingSkill, _maxMovePointsOnLand, _maxMovePointsOnWater, _armyStrength, _isArtifactsBagFull, _isSummonBoatSpellAvailable, _townGateCastleIndex, _townPortalCastleIndexes ); const auto newSettings = std::make_tuple( hero.GetIndex(), hero.GetColor(), hero.GetMovePoints(), static_cast( hero.GetLevelSkill( Skill::Secondary::PATHFINDING ) ), hero.GetMaxMovePoints( false ), hero.GetMaxMovePoints( true ), hero.GetArmy().GetStrength(), hero.IsFullBagArtifacts(), isSummonBoatSpellAvailable, townGateCastleIndex, townPortalCastleIndexes ); if ( currentSettings != newSettings ) { currentSettings = newSettings; processWorldMap(); } } void AIWorldPathfinder::reEvaluateIfNeeded( const int start, const int color, const double armyStrength, const uint8_t skill ) { auto currentSettings = std::tie( _pathStart, _color, _remainingMovePoints, _pathfindingSkill, _maxMovePointsOnLand, _maxMovePointsOnWater, _armyStrength, _isArtifactsBagFull, _isSummonBoatSpellAvailable, _townGateCastleIndex, _townPortalCastleIndexes ); const auto newSettings = std::make_tuple( start, color, 0U, skill, 0U, 0U, armyStrength, false, false, -1, std::vector{} ); if ( currentSettings != newSettings ) { currentSettings = newSettings; processWorldMap(); } } void AIWorldPathfinder::processWorldMap() { assert( _cache.size() == world.getSize() && Maps::isValidAbsIndex( _pathStart ) ); for ( WorldNode & node : _cache ) { node.reset(); } _cache[_pathStart] = WorldNode( -1, 0, MP2::OBJ_NONE, _remainingMovePoints ); std::vector nodesToExplore; nodesToExplore.push_back( _pathStart ); const auto processTownPortal = [this, &nodesToExplore]( const Spell & spell, const int32_t castleIndex ) { assert( castleIndex >= 0 && static_cast( castleIndex ) < _cache.size() ); assert( castleIndex != _pathStart && _cache[castleIndex]._from == -1 ); const uint32_t cost = spell.movePoints(); const uint32_t remaining = ( _remainingMovePoints < cost ) ? 0 : _remainingMovePoints - cost; _cache[castleIndex] = WorldNode( _pathStart, cost, MP2::OBJ_CASTLE, remaining ); nodesToExplore.push_back( castleIndex ); }; if ( _townGateCastleIndex != -1 ) { processTownPortal( Spell::TOWNGATE, _townGateCastleIndex ); } for ( const int32_t idx : _townPortalCastleIndexes ) { if ( idx == _townGateCastleIndex ) { continue; } processTownPortal( Spell::TOWNPORTAL, idx ); } for ( size_t lastProcessedNode = 0; lastProcessedNode < nodesToExplore.size(); ++lastProcessedNode ) { processCurrentNode( nodesToExplore, nodesToExplore[lastProcessedNode] ); } } bool AIWorldPathfinder::isMovementAllowed( const int from, const int direction ) const { return isMovementAllowedForColor( from, direction, _color, _isSummonBoatSpellAvailable ); } void AIWorldPathfinder::processCurrentNode( std::vector & nodesToExplore, const int currentNodeIdx ) { const bool isFirstNode = ( currentNodeIdx == _pathStart ); WorldNode & currentNode = _cache[currentNodeIdx]; // Always allow movement from the starting point to cover the edge case where we got here before this tile became blocked if ( !isFirstNode ) { if ( !isTileAccessibleForAIWithArmy( currentNodeIdx, _armyStrength, _minimalArmyStrengthAdvantage ) ) { // If we can't move here, then reset the node currentNode.reset(); return; } // No dead ends allowed assert( currentNode._from != -1 ); const bool fromWater = world.GetTiles( currentNode._from ).isWater(); if ( !isTileAvailableForWalkThroughForAIWithArmy( currentNodeIdx, fromWater, _color, _isArtifactsBagFull, _armyStrength, _minimalArmyStrengthAdvantage ) ) { return; } } MapsIndexes teleports; // We shouldn't use teleport at the starting tile if ( !isFirstNode ) { teleports = world.GetTeleportEndPoints( currentNodeIdx ); if ( teleports.empty() ) { teleports = world.GetWhirlpoolEndPoints( currentNodeIdx ); } } // Check adjacent nodes only if we are either not on the teleport tile, or we got here from another endpoint of this teleport. // Do not check them if we came to the tile with a teleport from a neighboring tile (and are going to use it for teleportation). if ( teleports.empty() || std::find( teleports.begin(), teleports.end(), currentNode._from ) != teleports.end() ) { checkAdjacentNodes( nodesToExplore, currentNodeIdx ); } // Special case: movement via teleport for ( const int teleportIdx : teleports ) { if ( teleportIdx == _pathStart ) { continue; } WorldNode & teleportNode = _cache[teleportIdx]; // Check if the movement is really faster via teleport if ( teleportNode._from == -1 || teleportNode._cost > currentNode._cost ) { const Maps::Tiles & teleportTile = world.GetTiles( teleportIdx ); teleportNode._from = currentNodeIdx; teleportNode._cost = currentNode._cost; teleportNode._objectID = teleportTile.GetObject(); teleportNode._remainingMovePoints = currentNode._remainingMovePoints; nodesToExplore.push_back( teleportIdx ); } } } uint32_t AIWorldPathfinder::getMaxMovePoints( const bool onWater ) const { return onWater ? _maxMovePointsOnWater : _maxMovePointsOnLand; } uint32_t AIWorldPathfinder::getMovementPenalty( const int from, const int to, const int direction ) const { const Maps::Tiles & fromTile = world.GetTiles( from ); const uint32_t defaultPenalty = [this, from, to, direction, &fromTile]() { const uint32_t regularPenalty = WorldPathfinder::getMovementPenalty( from, to, direction ); if ( from == _pathStart ) { return regularPenalty; } const MP2::MapObjectType objectType = fromTile.GetObject(); if ( !MP2::isNeedStayFront( objectType ) || objectType == MP2::OBJ_BOAT ) { return regularPenalty; } const WorldNode & node = _cache[from]; // No dead ends allowed assert( node._from != -1 ); const int prevStepDirection = Maps::GetDirection( node._from, from ); assert( prevStepDirection != Direction::UNKNOWN && prevStepDirection != Direction::CENTER ); // If we are moving from a tile that we technically cannot stand on, then it means that there was // an object on this tile that we previously removed. Thus, we have spent additional movement points // when moving to this tile - once when accessing the object to remove it, and again when moving to // this tile. // // According to a rough estimate, the movement points spent can be considered the same in both cases, // therefore, we apply an additional penalty when moving from the tile containing this object to the // next tile. In general, it is impossible to perform an accurate estimation, since the stats and // skills of a moving hero may change after interacting with the object (e.g. hero can upgrade his // Pathfinding skill). // // The real path will not reach this step, so this logic will be used to estimate distances more // accurately when choosing whether to move through objects or past them. return regularPenalty + WorldPathfinder::getMovementPenalty( node._from, from, prevStepDirection ); }(); const uint32_t maxMovePoints = getMaxMovePoints( fromTile.isWater() ); assert( maxMovePoints == 0 || defaultPenalty <= maxMovePoints ); // If we perform pathfinding for a real AI-controlled hero on the map, we should correctly calculate // movement penalties when this hero overcomes water obstacles using boats. if ( maxMovePoints > 0 ) { const WorldNode & node = _cache[from]; // No dead ends allowed assert( from == _pathStart || node._from != -1 ); const Maps::Tiles & toTile = world.GetTiles( to ); // AI-controlled hero may get from the shore to an empty water tile using the Summon Boat spell const bool isEmptyWaterTile = ( toTile.isWater() && toTile.GetObject() == MP2::OBJ_NONE ); const bool isComesOnBoard = ( !fromTile.isWater() && ( toTile.GetObject() == MP2::OBJ_BOAT || isEmptyWaterTile ) ); const bool isDisembarks = ( fromTile.isWater() && toTile.GetObject() == MP2::OBJ_COAST ); // When the hero gets into a boat or disembarks, he spends all remaining movement points. if ( isComesOnBoard || isDisembarks ) { // If the hero is not able to make this movement this turn, then he will have to spend // all the movement points next turn. if ( defaultPenalty > node._remainingMovePoints ) { return maxMovePoints; } return node._remainingMovePoints; } } return defaultPenalty; } std::pair AIWorldPathfinder::getFogDiscoveryTile( const Heroes & hero ) { reEvaluateIfNeeded( hero ); const auto findBestTile = [this, scoutingDistance = hero.GetScoutingDistance()]( const auto nearbyTilePredicate ) { const Directions & directions = Direction::All(); struct TileCharacteristics { int32_t index{ -1 }; uint32_t cost{ UINT32_MAX }; int32_t tilesToReveal{ 0 }; }; TileCharacteristics bestTile; for ( size_t idx = 0; idx < _cache.size(); ++idx ) { const uint32_t nodeCost = _cache[idx]._cost; if ( nodeCost == 0 ) { continue; } const int32_t tileIdx = static_cast( idx ); if ( !MP2::isSafeForFogDiscoveryObject( world.GetTiles( tileIdx ).GetObject( true ) ) ) { continue; } bool isTileSuitable = false; for ( size_t i = 0; i < directions.size(); ++i ) { if ( !Maps::isValidDirection( tileIdx, directions[i] ) ) { continue; } if ( !nearbyTilePredicate( tileIdx + _mapOffset[i] ) ) { continue; } isTileSuitable = true; break; } if ( !isTileSuitable ) { continue; } const int32_t tilesToReveal = Maps::getFogTileCountToBeRevealed( tileIdx, scoutingDistance, _color ); assert( tilesToReveal >= 0 ); if ( tilesToReveal == 0 ) { continue; } if ( nodeCost < bestTile.cost || ( nodeCost == bestTile.cost && tilesToReveal > bestTile.tilesToReveal ) ) { bestTile = { tileIdx, nodeCost, tilesToReveal }; } } return bestTile.index; }; // First, consider the accessible tiles, one of the neighboring tiles of which is covered with fog. Most likely, some of these neighboring tiles are also accessible. { const int32_t bestTileIdx = findBestTile( [this]( const int32_t tileIdx ) { return world.GetTiles( tileIdx ).isFog( _color ); } ); if ( bestTileIdx != -1 ) { return { bestTileIdx, true }; } } // If we are unlucky, then we need to do the heavy lifting and consider the accessible tiles that have at least one neighboring tile that is inaccessible to the hero // (since there may be unexplored tiles covered with fog on the other side of such an obstacle). { const int32_t bestTileIdx = findBestTile( [this]( const int32_t tileIdx ) { return _cache[tileIdx]._cost == 0; } ); if ( bestTileIdx != -1 ) { return { bestTileIdx, false }; } } return { -1, false }; } int AIWorldPathfinder::getNearestTileToMove( const Heroes & hero ) { // paths have to be pre-calculated to find a spot where we're able to move reEvaluateIfNeeded( hero ); const int start = hero.GetIndex(); Directions directions = Direction::All(); // We have to shuffle directions to avoid cases when heroes repeat the same steps again and again. Rand::Shuffle( directions ); for ( size_t i = 0; i < directions.size(); ++i ) { if ( !Maps::isValidDirection( start, directions[i] ) ) { continue; } const int newIndex = Maps::GetDirectionIndex( start, directions[i] ); if ( newIndex == start ) { continue; } // Don't go onto action objects as they might be castles or dwellings with guards. if ( MP2::isActionObject( world.GetTiles( newIndex ).GetObject( true ) ) ) { continue; } const WorldNode & node = _cache[newIndex]; // Tile is directly reachable (in one move) and the hero has enough army to defeat potential guards if ( node._cost > 0 && node._from == start ) { return newIndex; } } return -1; } bool AIWorldPathfinder::isHeroPossiblyBlockingWay( const Heroes & hero ) { const int32_t heroIndex = hero.GetIndex(); const int heroColor = hero.GetColor(); const auto isReachableDirection = [heroIndex, heroColor]( const int direction ) { if ( !Maps::isValidDirection( heroIndex, direction ) ) { return false; } if ( !isMovementAllowedForColor( heroIndex, direction, heroColor, false ) ) { return false; } return true; }; const bool leftReachable = isReachableDirection( Direction::LEFT ); const bool rightReachable = isReachableDirection( Direction::RIGHT ); const bool topReachable = isReachableDirection( Direction::TOP ); const bool bottomReachable = isReachableDirection( Direction::BOTTOM ); const bool topLeftReachable = isReachableDirection( Direction::TOP_LEFT ); const bool topRightReachable = isReachableDirection( Direction::TOP_RIGHT ); const bool bottomLeftReachable = isReachableDirection( Direction::BOTTOM_LEFT ); const bool bottomRightReachable = isReachableDirection( Direction::BOTTOM_RIGHT ); // There are multiple cases when a hero might block way. // H - hero // x - unreachable tile // r - always reachable tile // o - optionally reachable tile // | | r | | // | x | H | x | // | | r | | if ( topReachable && bottomReachable && !leftReachable && !rightReachable ) { return true; } // | | x | | // | r | H | r | // | | x | | if ( leftReachable && rightReachable && !topReachable && !bottomReachable ) { return true; } // | x | o | | // | r | H | o | // | | x | | if ( leftReachable && ( topReachable || rightReachable ) && !topLeftReachable && !bottomReachable ) { return true; } // | | o | x | // | o | H | r | // | | x | | if ( rightReachable && ( topReachable || leftReachable ) && !topRightReachable && !bottomReachable ) { return true; } // | | x | | // | r | H | o | // | x | o | | if ( leftReachable && ( bottomReachable || rightReachable ) && !topReachable && !bottomLeftReachable ) { return true; } // | | x | | // | o | H | r | // | | o | x | if ( rightReachable && ( bottomReachable || leftReachable ) && !topReachable && !bottomRightReachable ) { return true; } // | x | o | | // | r | H | o | // | x | o | | if ( leftReachable && ( topReachable || bottomReachable || rightReachable ) && !topLeftReachable && !bottomLeftReachable ) { return true; } // | x | r | x | // | o | H | o | // | | o | | if ( topReachable && ( bottomReachable || rightReachable || leftReachable ) && !topLeftReachable && !topRightReachable ) { return true; } // | | o | x | // | o | H | r | // | | o | x | if ( rightReachable && ( topReachable || bottomReachable || leftReachable ) && !topRightReachable && !bottomRightReachable ) { return true; } // | | o | | // | o | H | o | // | x | r | x | if ( bottomReachable && ( topReachable || leftReachable || rightReachable ) && !bottomLeftReachable && !bottomRightReachable ) { return true; } // | | r | x | // | x | H | o | // | | o | | if ( topReachable && ( rightReachable || bottomReachable ) && !topRightReachable && !leftReachable ) { return true; } // | | o | | // | x | H | o | // | | r | x | if ( bottomReachable && ( rightReachable || topReachable ) && !bottomRightReachable && !leftReachable ) { return true; } // | x | r | | // | o | H | x | // | | o | | if ( topReachable && ( leftReachable || bottomReachable ) && !topLeftReachable && !rightReachable ) { return true; } // | | r | | // | o | H | x | // | x | r | | if ( bottomReachable && ( leftReachable || topReachable ) && !bottomLeftReachable && !rightReachable ) { return true; } const Maps::Tiles & heroTile = world.GetTiles( heroIndex ); // Hero in the boat can neither occupy nor block the Stone Liths if ( heroTile.isWater() ) { assert( heroTile.GetObject( false ) != MP2::OBJ_STONE_LITHS ); return false; } // Does the hero potentially block the exit from Stone Liths for another hero? for ( const int32_t idx : Maps::ScanAroundObject( heroIndex, MP2::OBJ_STONE_LITHS ) ) { const Maps::Tiles & tile = world.GetTiles( idx ); if ( tile.GetObject() == MP2::OBJ_HERO ) { const int direction = Maps::GetDirection( idx, heroIndex ); assert( CountBits( direction ) == 1 && direction != Direction::CENTER ); if ( tile.isPassableTo( direction ) && heroTile.isPassableFrom( Direction::Reflect( direction ) ) ) { return true; } } } // Is the hero standing on Stone Liths? return heroTile.GetObject( false ) == MP2::OBJ_STONE_LITHS; } std::vector AIWorldPathfinder::getObjectsOnTheWay( const int targetIndex ) const { assert( _cache.size() == world.getSize() && Maps::isValidAbsIndex( _pathStart ) && _color != Color::NONE && Maps::isValidAbsIndex( targetIndex ) ); std::vector result; // Destination is not reachable if ( _cache[targetIndex]._cost == 0 ) { return result; } const Kingdom & kingdom = world.GetKingdom( _color ); std::set uniqueIndices; const auto validateAndAdd = [&kingdom, &result, &uniqueIndices]( int index, const MP2::MapObjectType objectType ) { // std::set insert returns a pair, second value is true if it was unique if ( uniqueIndices.insert( index ).second && kingdom.isValidKingdomObject( world.GetTiles( index ), objectType ) ) { result.emplace_back( index, objectType ); } }; // skip the target itself to make sure we don't double count uniqueIndices.insert( targetIndex ); #ifndef NDEBUG std::set uniqPathIndexes; #endif int currentNode = targetIndex; while ( currentNode != _pathStart ) { assert( currentNode != -1 ); const WorldNode & node = _cache[currentNode]; assert( node._from != -1 ); validateAndAdd( currentNode, node._objectID ); // The path should not pass through the same tile more than once assert( uniqPathIndexes.insert( node._from ).second ); currentNode = node._from; } return result; } std::list AIWorldPathfinder::getDimensionDoorPath( const Heroes & hero, int targetIndex ) const { uint32_t difficultyLimit = Difficulty::GetDimensionDoorLimitForAI( Game::getDifficulty() ); const uint32_t spellsUsedThisTurn = hero.getDimensionDoorUses(); if ( hero.GetIndex() == targetIndex || spellsUsedThisTurn >= difficultyLimit ) { return {}; } const Spell dimensionDoor( Spell::DIMENSIONDOOR ); if ( !hero.HaveSpell( dimensionDoor ) || !Maps::isValidAbsIndex( targetIndex ) ) { return {}; } uint32_t currentSpellPoints = hero.GetSpellPoints(); const Maps::Tiles & tile = world.GetTiles( targetIndex ); const MP2::MapObjectType objectType = tile.GetObject( true ); // Reserve spell points only if target isn't a well that will replenish lost SP if ( objectType != MP2::OBJ_MAGIC_WELL && objectType != MP2::OBJ_ARTESIAN_SPRING ) { if ( currentSpellPoints < hero.GetMaxSpellPoints() * _spellPointsReserveRatio ) { return {}; } currentSpellPoints -= static_cast( hero.GetMaxSpellPoints() * _spellPointsReserveRatio ); } difficultyLimit -= spellsUsedThisTurn; const uint32_t movementCost = std::max( 1U, dimensionDoor.movePoints() ); const uint32_t spellcastsPossible = std::min( currentSpellPoints / std::max( 1U, dimensionDoor.spellPoints( &hero ) ), hero.GetMovePoints() / movementCost ); const uint32_t maxCasts = std::min( spellcastsPossible, difficultyLimit ); // Have to explicitly call GetObject( false ) since hero might be standing on it if ( tile.GetObject( false ) == MP2::OBJ_CASTLE ) { targetIndex = Maps::GetDirectionIndex( targetIndex, Direction::BOTTOM ); if ( !Maps::isValidAbsIndex( targetIndex ) ) { return {}; } } if ( !isTileAccessibleForAIWithArmy( targetIndex, _armyStrength, _minimalArmyStrengthAdvantage ) ) { return {}; } const fheroes2::Point targetPoint = Maps::GetPoint( targetIndex ); fheroes2::Point current = Maps::GetPoint( hero.GetIndex() ); fheroes2::Point difference = targetPoint - current; const bool water = hero.isShipMaster(); const Directions & directions = Direction::All(); const int32_t distanceLimit = Spell::CalculateDimensionDoorDistance() / 2; std::list path; uint32_t spellsUsed = 0; while ( maxCasts > spellsUsed ) { const int32_t currentNodeIdx = Maps::GetIndexFromAbsPoint( current ); fheroes2::Point another = current; another.x += ( difference.x > 0 ) ? std::min( difference.x, distanceLimit ) : std::max( difference.x, -distanceLimit ); another.y += ( difference.y > 0 ) ? std::min( difference.y, distanceLimit ) : std::max( difference.y, -distanceLimit ); const int32_t anotherNodeIdx = Maps::GetIndexFromAbsPoint( another ); bool found = Maps::isValidForDimensionDoor( anotherNodeIdx, water ); if ( !found ) { fheroes2::Point bestDirectionDiff; int bestNextIdx = -1; for ( size_t i = 0; i < directions.size(); ++i ) { if ( !Maps::isValidDirection( anotherNodeIdx, directions[i] ) ) continue; const int newIndex = anotherNodeIdx + _mapOffset[i]; if ( !Maps::isValidForDimensionDoor( newIndex, water ) ) continue; // If we are near the destination and we cannot reach the cell, skip it. if ( anotherNodeIdx == targetIndex && !isMovementAllowed( newIndex, Direction::Reflect( directions[i] ) ) ) { continue; } const fheroes2::Point newPoint = Maps::GetPoint( newIndex ); const fheroes2::Point directionDiff{ std::abs( current.x - newPoint.x ), std::abs( current.y - newPoint.y ) }; if ( directionDiff.x > distanceLimit || directionDiff.y > distanceLimit ) { continue; } if ( ( bestNextIdx < 0 ) || ( bestDirectionDiff.x + bestDirectionDiff.y > directionDiff.x + directionDiff.y ) ) { bestNextIdx = newIndex; bestDirectionDiff = directionDiff; } } if ( bestNextIdx == -1 ) { return {}; } path.emplace_back( bestNextIdx, currentNodeIdx, Direction::CENTER, movementCost ); current = Maps::GetPoint( bestNextIdx ); } else { path.emplace_back( anotherNodeIdx, currentNodeIdx, Direction::CENTER, movementCost ); current = another; } ++spellsUsed; difference = targetPoint - current; if ( std::abs( difference.x ) <= 1 && std::abs( difference.y ) <= 1 ) { // If this assertion blows up the logic above is wrong! assert( !path.empty() ); return path; } } return {}; } std::list AIWorldPathfinder::buildPath( const int targetIndex ) const { assert( _cache.size() == world.getSize() && Maps::isValidAbsIndex( _pathStart ) && Maps::isValidAbsIndex( targetIndex ) ); std::list path; // Destination is not reachable if ( _cache[targetIndex]._cost == 0 ) { return path; } const bool fromWater = world.GetTiles( _pathStart ).isWater(); #ifndef NDEBUG std::set uniqPathIndexes; #endif int lastValidNode = targetIndex; int currentNode = targetIndex; while ( currentNode != _pathStart ) { assert( currentNode != -1 ); if ( !isTileAvailableForWalkThrough( currentNode, fromWater ) ) { lastValidNode = currentNode; } const WorldNode & node = _cache[currentNode]; assert( node._from != -1 ); const uint32_t cost = node._cost - _cache[node._from]._cost; path.emplace_front( currentNode, node._from, Maps::GetDirection( node._from, currentNode ), cost ); // The path should not pass through the same tile more than once assert( uniqPathIndexes.insert( node._from ).second ); currentNode = node._from; } // Cut the path to the last valid tile/obstacle if ( lastValidNode != targetIndex ) { path.erase( std::find_if( path.begin(), path.end(), [lastValidNode]( const Route::Step & step ) { return step.GetFrom() == lastValidNode; } ), path.end() ); } return path; } uint32_t AIWorldPathfinder::getDistance( int start, int targetIndex, int color, double armyStrength, uint8_t skill ) { reEvaluateIfNeeded( start, color, armyStrength, skill ); return _cache[targetIndex]._cost; } void AIWorldPathfinder::setMinimalArmyStrengthAdvantage( const double advantage ) { if ( advantage <= 0 || std::fabs( _minimalArmyStrengthAdvantage - advantage ) <= 0.001 ) { return; } _minimalArmyStrengthAdvantage = advantage; reset(); } void AIWorldPathfinder::setSpellPointsReserveRatio( const double ratio ) { if ( ratio <= 0 || std::fabs( _spellPointsReserveRatio - ratio ) <= 0.001 ) { return; } _spellPointsReserveRatio = ratio; reset(); } fheroes2-1.0.12+dfsg/src/fheroes2/world/world_pathfinding.h000066400000000000000000000266561456075706000235660ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2024 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include #include #include "color.h" #include "mp2.h" #include "skill.h" class Heroes; class IndexObject; namespace Route { class Step; } struct WorldNode final { int _from{ -1 }; uint32_t _cost{ 0 }; MP2::MapObjectType _objectID{ MP2::OBJ_NONE }; // The number of movement points remaining for the hero after moving to this node uint32_t _remainingMovePoints{ 0 }; WorldNode() = default; WorldNode( const int node, const uint32_t cost, const MP2::MapObjectType object, const uint32_t remainingMovePoints ) : _from( node ) , _cost( cost ) , _objectID( object ) , _remainingMovePoints( remainingMovePoints ) {} void reset() { _from = -1; _cost = 0; _objectID = MP2::OBJ_NONE; _remainingMovePoints = 0; } }; // Abstract class that provides basic functionality for navigating the World Map class WorldPathfinder { public: WorldPathfinder() = default; WorldPathfinder( const WorldPathfinder & ) = delete; virtual ~WorldPathfinder() = default; WorldPathfinder & operator=( const WorldPathfinder & ) = delete; virtual void reset(); uint32_t getDistance( int targetIndex ) const; protected: virtual void processWorldMap(); void checkAdjacentNodes( std::vector & nodesToExplore, int currentNodeIdx ); // Checks whether moving from the source tile in the specified direction is allowed. The default implementation // can be overridden by a derived class. virtual bool isMovementAllowed( const int from, const int direction ) const; // Defines the pathfinding rules and should be implemented by a derived class. virtual void processCurrentNode( std::vector & nodesToExplore, const int currentNodeIdx ) = 0; // Returns the maximum number of movement points, depending on whether the movement is performed by land or by // water. Should be implemented by a derived class. virtual uint32_t getMaxMovePoints( const bool onWater ) const = 0; // Calculates the movement penalty when moving from the source tile to the adjacent destination tile in the // specified direction. If the "last move" logic should be taken into account (when performing pathfinding // for a real hero on the map), then the source tile should be already accessible for this hero and it should // also have a valid information about the hero's remaining movement points. The default implementation can be // overridden by a derived class. virtual uint32_t getMovementPenalty( const int from, const int to, const int direction ) const; std::vector _cache; std::vector _mapOffset; // Hero properties should be cached here because they can change even if the hero's position does not change, // so it should be possible to compare the old values with the new ones to detect the need to recalculate the // pathfinder's cache int _pathStart{ -1 }; int _color{ Color::NONE }; uint32_t _remainingMovePoints{ 0 }; uint8_t _pathfindingSkill{ Skill::Level::EXPERT }; }; class PlayerWorldPathfinder final : public WorldPathfinder { public: PlayerWorldPathfinder() = default; PlayerWorldPathfinder( const PlayerWorldPathfinder & ) = delete; ~PlayerWorldPathfinder() override = default; PlayerWorldPathfinder & operator=( const PlayerWorldPathfinder & ) = delete; void reset() override; void reEvaluateIfNeeded( const Heroes & hero ); // Builds and returns a path to the tile with the index 'targetIndex'. If the destination tile is not reachable, // then an empty path is returned. std::list buildPath( const int targetIndex ) const; private: // Follows regular passability rules (for the human player) void processCurrentNode( std::vector & nodesToExplore, const int currentNodeIdx ) override; // Returns the maximum number of movement points. This class is not intended for planning paths passing both on // land and on water at the same time, so the maximum number of movement points corresponding to the type of // surface on which the hero is currently located is used. uint32_t getMaxMovePoints( const bool onWater ) const override; // Hero properties should be cached here because they can change even if the hero's position does not change, // so it should be possible to compare the old values with the new ones to detect the need to recalculate the // pathfinder's cache uint32_t _maxMovePoints{ 0 }; }; class AIWorldPathfinder final : public WorldPathfinder { public: explicit AIWorldPathfinder( double advantage ) : _minimalArmyStrengthAdvantage( advantage ) {} AIWorldPathfinder( const AIWorldPathfinder & ) = delete; ~AIWorldPathfinder() override = default; AIWorldPathfinder & operator=( const AIWorldPathfinder & ) = delete; void reset() override; void reEvaluateIfNeeded( const Heroes & hero ); void reEvaluateIfNeeded( const int start, const int color, const double armyStrength, const uint8_t skill ); // Finds the most profitable tile for fog discovery. Returns a pair consisting of the tile index (-1 if no suitable tile // was found) and a boolean value, which takes the value true if there is fog next to this tile (that is, most likely, // through this tile hero can get into some new areas), and false otherwise. std::pair getFogDiscoveryTile( const Heroes & hero ); // Used for cases when heroes are stuck because one hero might be blocking the way and we have to move him. int getNearestTileToMove( const Heroes & hero ); static bool isHeroPossiblyBlockingWay( const Heroes & hero ); std::vector getObjectsOnTheWay( const int targetIndex ) const; std::list getDimensionDoorPath( const Heroes & hero, int targetIndex ) const; // Builds and returns a path to the tile with the index 'targetIndex'. If there is a need to pass through any objects // on the way to this tile, then a path to the nearest such object is returned. If the destination tile is not reachable // in principle, then an empty path is returned. std::list buildPath( const int targetIndex ) const; // Used for non-hero armies, like castles or monsters uint32_t getDistance( int start, int targetIndex, int color, double armyStrength, uint8_t skill = Skill::Level::EXPERT ); // Faster, but does not re-evaluate the map (exposed method of the base class) using WorldPathfinder::getDistance; // Returns the coefficient of the minimum required advantage in army strength in order to be able to "pass through" // protected tiles from the AI pathfinder's point of view double getMinimalArmyStrengthAdvantage() const { return _minimalArmyStrengthAdvantage; } // Sets the coefficient of the minimum required advantage in army strength in order to be able to "pass through" // protected tiles from the AI pathfinder's point of view void setMinimalArmyStrengthAdvantage( const double advantage ); // Returns the spell points reservation factor for spells associated with the movement of the hero on the adventure // map (such as Dimension Door, Town Gate or Town Portal) double getSpellPointsReserveRatio() const { return _spellPointsReserveRatio; } // Sets the spell points reservation factor for spells associated with the movement of the hero on the adventure map // (such as Dimension Door, Town Gate or Town Portal) void setSpellPointsReserveRatio( const double ratio ); private: void processWorldMap() override; // Adds special logic for AI-controlled heroes to use Summon Boat spell to overcome water obstacles (if available) bool isMovementAllowed( const int from, const int direction ) const override; // Follows custom passability rules (for the AI) void processCurrentNode( std::vector & nodesToExplore, const int currentNodeIdx ) override; // Returns the maximum number of movement points, depending on whether the movement is performed by land or by // water uint32_t getMaxMovePoints( const bool onWater ) const override; // Adds special logic for AI-controlled heroes to correctly calculate movement penalties when such a hero passes // through objects on the map or overcomes water obstacles using boats. If this logic should be taken into account // (when performing pathfinding for a real hero on the map), then the source tile should be already accessible for // this hero and it should also have a valid information about the hero's remaining movement points. uint32_t getMovementPenalty( const int from, const int to, const int direction ) const override; // Hero properties should be cached here because they can change even if the hero's position does not change, // so it should be possible to compare the old values with the new ones to detect the need to recalculate the // pathfinder's cache uint32_t _maxMovePointsOnLand{ 0 }; uint32_t _maxMovePointsOnWater{ 0 }; double _armyStrength{ -1 }; bool _isArtifactsBagFull{ false }; bool _isSummonBoatSpellAvailable{ false }; // The potential destinations of the Town Gate and Town Portal spells should be cached here because they can // change even if the hero's position does not change (e.g. when a new hero was hired in the nearby castle), // so it should be possible to compare the old values with the new ones to detect the need to recalculate the // pathfinder's cache int32_t _townGateCastleIndex{ -1 }; std::vector _townPortalCastleIndexes; // Coefficient of the minimum required advantage in army strength in order to be able to "pass through" protected // tiles from the AI pathfinder's point of view double _minimalArmyStrengthAdvantage{ 1.0 }; // Spell points reservation factor for spells associated with the movement of the hero on the adventure map // (such as Dimension Door, Town Gate or Town Portal) double _spellPointsReserveRatio{ 0.5 }; }; fheroes2-1.0.12+dfsg/src/fheroes2/world/world_regions.cpp000066400000000000000000000361061456075706000232630ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include "castle.h" #include "maps.h" #include "maps_tiles.h" #include "math_base.h" #include "mp2.h" #include "world.h" #include "world_regions.h" namespace { // Aliases to make data structures easier to work with // TileData.first is index, TileData.second is payload using TileData = std::pair; using TileDataVector = std::vector>; // Values in Direction namespace can't be used as index, use a custom value here // Converted into bitfield later enum { TOP_LEFT = 0, TOP = 1, TOP_RIGHT = 2, RIGHT = 3, BOTTOM_RIGHT = 4, BOTTOM = 5, BOTTOM_LEFT = 6, LEFT = 7 }; uint16_t GetDirectionBitmask( uint8_t direction, bool reflect = false ) { return 1 << ( reflect ? ( direction + 4 ) % 8 : direction ); } std::vector GetDirectionOffsets( const int width ) { std::vector offsets( 8 ); offsets[TOP_LEFT] = -width - 1; offsets[TOP] = -width; offsets[TOP_RIGHT] = -width + 1; offsets[RIGHT] = 1; offsets[BOTTOM_RIGHT] = width + 1; offsets[BOTTOM] = width; offsets[BOTTOM_LEFT] = width - 1; offsets[LEFT] = -1; return offsets; } int ConvertExtendedIndex( int index, uint32_t width ) { const uint32_t originalWidth = width - 2; return ( index / originalWidth + 1 ) * width + ( index % originalWidth ) + 1; } bool AppendIfFarEnough( std::vector & dataSet, int value, uint32_t distance ) { for ( const int current : dataSet ) { if ( Maps::GetStraightLineDistance( current, value ) < distance ) return false; } dataSet.push_back( value ); return true; } void CheckAdjacentTiles( std::vector & rawData, MapRegion & region, uint32_t rawDataWidth, const std::vector & offsets ) { const int nodeIndex = region._nodes[region._lastProcessedNode].index; for ( uint8_t direction = 0; direction < 8; ++direction ) { const int newIndex = ConvertExtendedIndex( nodeIndex, rawDataWidth ) + offsets[direction]; MapRegionNode & newTile = rawData[newIndex]; if ( newTile.passable & GetDirectionBitmask( direction, true ) && newTile.isWater == region._isWater ) { if ( newTile.type == REGION_NODE_OPEN ) { newTile.type = region._id; region._nodes.push_back( newTile ); } else if ( newTile.type > REGION_NODE_FOUND && newTile.type != region._id ) { region._neighbours.insert( newTile.type ); } } } } void RegionExpansion( std::vector & rawData, uint32_t rawDataWidth, MapRegion & region, const std::vector & offsets ) { // Process only "open" nodes that exist at the start of the loop and ignore what's added const size_t nodesEnd = region._nodes.size(); while ( region._lastProcessedNode < nodesEnd ) { CheckAdjacentTiles( rawData, region, rawDataWidth, offsets ); ++region._lastProcessedNode; } } void FindMissingRegions( std::vector & rawData, const fheroes2::Size & mapSize, std::vector & regions ) { const uint32_t extendedWidth = mapSize.width + 2; MapRegionNode * currentTile = rawData.data() + extendedWidth + 1; const MapRegionNode * mapEnd = rawData.data() + extendedWidth * ( mapSize.height + 1 ); const std::vector & offsets = GetDirectionOffsets( static_cast( extendedWidth ) ); for ( ; currentTile != mapEnd; ++currentTile ) { if ( currentTile->type == REGION_NODE_OPEN ) { regions.emplace_back( static_cast( regions.size() ), currentTile->index, currentTile->isWater, extendedWidth ); MapRegion & region = regions.back(); do { CheckAdjacentTiles( rawData, region, extendedWidth, offsets ); ++region._lastProcessedNode; } while ( region._lastProcessedNode != region._nodes.size() ); } } } } MapRegion::MapRegion( int regionIndex, int mapIndex, bool water, size_t expectedSize ) : _id( regionIndex ) , _isWater( water ) { _nodes.reserve( expectedSize ); _nodes.emplace_back( mapIndex ); _nodes[0].type = regionIndex; } size_t MapRegion::getNeighboursCount() const { return _neighbours.size(); } size_t World::getRegionCount() const { return _regions.size(); } const MapRegion & World::getRegion( size_t id ) const { if ( id < _regions.size() ) return _regions[id]; // empty MapRegion object is still valid to use static const MapRegion region; return region; } void World::ComputeStaticAnalysis() { // Parameters that control region generation: size and spacing between initial points const uint32_t castleRegionSize = 17; const uint32_t extraRegionSize = 18; const uint32_t emptyLineFrequency = 7; // Reset the region information for all tiles std::for_each( vec_tiles.begin(), vec_tiles.end(), []( Maps::Tiles & tile ) { tile.UpdateRegion( REGION_NODE_BLOCKED ); } ); // Step 1. Split map into terrain, water and ground points // Initialize the obstacles vector TileDataVector obstacles[4]; obstacles[0].reserve( width ); obstacles[2].reserve( width ); for ( int x = 0; x < width; ++x ) { obstacles[0].emplace_back( x, 0 ); // water, columns obstacles[2].emplace_back( x, 0 ); // ground, columns } obstacles[1].reserve( height ); obstacles[3].reserve( height ); for ( int y = 0; y < height; ++y ) { obstacles[1].emplace_back( y, 0 ); // water, rows obstacles[3].emplace_back( y, 0 ); // ground, rows } // Find the terrain for ( int y = 0; y < height; ++y ) { const int rowIndex = y * width; for ( int x = 0; x < width; ++x ) { const int index = rowIndex + x; const Maps::Tiles & tile = vec_tiles[index]; // If tile is blocked (mountain, trees, etc) then it's applied to both if ( tile.GetPassable() == 0 ) { ++obstacles[0][x].second; ++obstacles[1][y].second; ++obstacles[2][x].second; ++obstacles[3][y].second; } else if ( tile.isWater() ) { // if it's water then ground tiles consider it an obstacle ++obstacles[2][x].second; ++obstacles[3][y].second; } else { // else then ground is an obstacle for water navigation ++obstacles[0][x].second; ++obstacles[1][y].second; } } } // sort the map rows and columns based on amount of obstacles for ( int i = 0; i < 4; ++i ) std::sort( obstacles[i].begin(), obstacles[i].end(), []( const TileData & left, const TileData & right ) { return left.second < right.second; } ); // Step 2. Find lines (rows and columns) with most amount of usable space, use it for region centers later const int maxDimension = std::max( width, height ); std::vector emptyLines[4]; // 0,1 is water; 2,3 is ground for ( int i = 0; i < 4; ++i ) { emptyLines[i].reserve( maxDimension / emptyLineFrequency ); for ( const TileData & line : obstacles[i] ) { AppendIfFarEnough( emptyLines[i], line.first, emptyLineFrequency ); } } // Step 3. Check all castles on the map and create region centers based on them std::vector regionCenters; TileDataVector castleCenters; for ( const Castle * castle : vec_castles ) { castleCenters.emplace_back( castle->GetIndex(), castle->GetColor() ); } std::sort( castleCenters.begin(), castleCenters.end(), []( const TileData & left, const TileData & right ) { // Sort castles by color primarily (NONE is last) // If same color then compare map index if ( left.second == right.second ) return left.first < right.first; return left.second > right.second; } ); const size_t totalMapTiles = vec_tiles.size(); for ( const TileData & castleTile : castleCenters ) { // Check if a lot of players next to each other? (Slugfest map) // getCastle( fheroes2::Point( val % width, val / width ) )->GetColor(); const int castleIndex = castleTile.first + width; AppendIfFarEnough( regionCenters, ( castleIndex >= 0 && static_cast( castleIndex ) > totalMapTiles ) ? castleTile.first : castleIndex, castleRegionSize ); } // Step 4. Add missing region centers based on distance (for water or if there's big chunks of space without castles) const std::vector & directionOffsets = GetDirectionOffsets( width ); for ( int waterOrGround = 0; waterOrGround < 4; waterOrGround += 2 ) { for ( const int rowID : emptyLines[waterOrGround] ) { const int rowIndex = rowID * width; for ( const int colID : emptyLines[waterOrGround + 1] ) { int centerIndex = -1; const int tileIndex = rowIndex + colID; const Maps::Tiles & tile = vec_tiles[tileIndex]; if ( tile.GetPassable() && tile.isWater() ) { centerIndex = tileIndex; } else { for ( uint8_t direction = 0; direction < 8; ++direction ) { const int newIndex = tileIndex + directionOffsets[direction]; if ( newIndex >= 0 && static_cast( newIndex ) < totalMapTiles ) { const Maps::Tiles & newTile = vec_tiles[newIndex]; if ( newTile.GetPassable() != 0 && tile.isWater() == ( waterOrGround != 0 ) ) { centerIndex = newIndex; break; } } } } if ( centerIndex >= 0 ) { AppendIfFarEnough( regionCenters, centerIndex, extraRegionSize ); } } } } // Step 5. Initialize extended (by 2 tiles) map data used for region growing based on actual Maps::Tiles const uint32_t extendedWidth = width + 2; std::vector data( extendedWidth * ( height + 2 ) ); for ( int y = 0; y < height; ++y ) { const int rowIndex = y * width; for ( int x = 0; x < width; ++x ) { const int index = rowIndex + x; const Maps::Tiles & tile = vec_tiles[index]; MapRegionNode & node = data[ConvertExtendedIndex( index, extendedWidth )]; node.index = index; node.passable = tile.GetPassable(); node.isWater = tile.isWater(); const MP2::MapObjectType objectType = tile.GetObject(); node.mapObject = MP2::isActionObject( objectType, node.isWater ) ? objectType : 0; if ( node.passable != 0 ) { node.type = REGION_NODE_OPEN; } } } // Step 6. Initialize regions size_t averageRegionSize = ( static_cast( width ) * height * 2 ) / regionCenters.size(); _regions.clear(); for ( int baseIDX = 0; baseIDX < REGION_NODE_FOUND; ++baseIDX ) { _regions.emplace_back( baseIDX, 0, false, 0 ); } for ( const int tileIndex : regionCenters ) { const int regionID = static_cast( _regions.size() ); // Safe to do as we can't have so many regions _regions.emplace_back( regionID, tileIndex, vec_tiles[tileIndex].isWater(), averageRegionSize ); data[ConvertExtendedIndex( tileIndex, extendedWidth )].type = regionID; } // Step 7. Grow all regions one step at the time so they would compete for space const std::vector & offsets = GetDirectionOffsets( static_cast( extendedWidth ) ); bool stillRoomToExpand = true; while ( stillRoomToExpand ) { stillRoomToExpand = false; for ( size_t regionID = REGION_NODE_FOUND; regionID < regionCenters.size(); ++regionID ) { MapRegion & region = _regions[regionID]; RegionExpansion( data, extendedWidth, region, offsets ); if ( region._lastProcessedNode != region._nodes.size() ) stillRoomToExpand = true; } } // Step 8. Fill missing data (if there's a small island/lake or unreachable terrain) FindMissingRegions( data, { width, height }, _regions ); // Step 9. Assign regions to the map tiles and finalize the data for ( MapRegion & reg : _regions ) { if ( reg._id < REGION_NODE_FOUND ) continue; for ( const MapRegionNode & node : reg._nodes ) { vec_tiles[node.index].UpdateRegion( node.type ); // connect regions through teleports MapsIndexes exits; if ( node.mapObject == MP2::OBJ_STONE_LITHS ) { exits = GetTeleportEndPoints( node.index ); } else if ( node.mapObject == MP2::OBJ_WHIRLPOOL ) { exits = GetWhirlpoolEndPoints( node.index ); } for ( const int exitIndex : exits ) { // neighbours is a set that will force the uniqueness reg._neighbours.insert( vec_tiles[exitIndex].GetRegion() ); } } // Fix missing references for ( uint32_t adjacent : reg._neighbours ) { _regions[adjacent]._neighbours.insert( reg._id ); } } } fheroes2-1.0.12+dfsg/src/fheroes2/world/world_regions.h000066400000000000000000000044401456075706000227240ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2020 - 2022 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #pragma once #include #include #include #include enum { REGION_NODE_BLOCKED = 0, REGION_NODE_OPEN = 1, REGION_NODE_BORDER = 2, REGION_NODE_FOUND = 3 }; struct MapRegionNode { int index = -1; uint32_t type = REGION_NODE_BLOCKED; uint16_t mapObject = 0; uint16_t passable = 0; bool isWater = false; MapRegionNode() = default; explicit MapRegionNode( int index_ ) : index( index_ ) , type( REGION_NODE_OPEN ) {} }; struct MapRegion { public: uint32_t _id = REGION_NODE_FOUND; bool _isWater = false; std::set _neighbours; std::vector _nodes; size_t _lastProcessedNode = 0; MapRegion() = default; MapRegion( int regionIndex, int mapIndex, bool water, size_t expectedSize ); size_t getNeighboursCount() const; }; fheroes2-1.0.12+dfsg/src/resources/000077500000000000000000000000001456075706000170425ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/resources/Info.plist.in000066400000000000000000000020341456075706000214160ustar00rootroot00000000000000 CFBundleDevelopmentRegion en CFBundleExecutable ${MACOSX_BUNDLE_EXECUTABLE_NAME} CFBundleIconFile ${MACOSX_BUNDLE_ICON_FILE} CFBundleIdentifier ${MACOSX_BUNDLE_GUI_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName ${MACOSX_BUNDLE_BUNDLE_NAME} CFBundlePackageType APPL CFBundleSignature ???? CFBundleVersion ${MACOSX_BUNDLE_BUNDLE_VERSION} CFBundleShortVersionString ${MACOSX_BUNDLE_SHORT_VERSION_STRING} LSApplicationCategoryType public.app-category.strategy-games NSPrincipalClass NSApplication fheroes2-1.0.12+dfsg/src/resources/fheroes2.icns000066400000000000000000006221631456075706000214470ustar00rootroot00000000000000icns$sic09$k jP ‡ ftypjp2 jp2 Ojp2hihdrcolr"cdefjp2cÿOÿQ2ÿd#Creator: JasPer Version 1.900.1ÿR ÿ\@@HHPHHPHHPHHPHHPÿ]@@HHPHHPHHPHHPHHPÿ]@@HHPHHPHHPHHPHHPÿ]@@HHPHHPHHPHHPHHPÿ #*ÿ“߆‰Pnçˆ/&ÅÈX€G`ð¢vÿA2]]š‘G-~§wφ™Õc<äÂ82ºhãê‰gZ¯WÕžH¥g!·¶ØÝ%{Rí·€Pý³Cη æeÿ$>v^wü¹»ëºÔy ÝÐ/‘QqmþXæ ècþEôK,`Ù³ãs‚® Èå“üý;…N’ÒÊàš‚FÖŸ<¶8GL0Ø›Ø)J´ ±ƒaåˆuÚ®_Ê¢•×µ$Á áH¾o5“Î^·_wÇÆ@ÖˆÚ ˆiEhÞ£LþÍ&—ƒ_ÇÍÖð®»’Úó{.Án¯hþ½8ªÖjg?ÆM´F°w­â(©£ViýGBåósgŸ_’d?ñ%qÅÕ {*ÒÞê?߆…€PnùÚ¿“VÞ¯·ê¬RPæœi”F¹‘>L¤P¿]SHµ8ŽuêÆYI(jà K3¥áWZ)5_Ýl$ß…²Pn¶Z™)Ï]`Ìz’Ì2‚N­_âö ïkŠOµþ´ÓÛ“¾ I¬ÒI£-é+'¯ ½s*¹;Gþ3TxÌ¢–1ØHŸ×‚ß±¤ÎÜö-%š–W'z¸¨êã*çœ ó&í)mÌ3ÄÍŬÙÂØÑþ^¨ñ^ûý¬Rb·ÿ71¡ `¦¸~d  # 7¾Ý‚}HÍñë3ø‡¯ŽäN\øðþsvXôè ‡ŽKÄ^÷Ü5ê8²Vɶõ zð†R%³Ÿï’¡½ °¨(Ó4ãdÀÆÏÂæ¿;n~vè8ÿ;U¹,Äö…ª]#½ŒÒ`ã›1ù tïJ~7=^7+~PnwG‚õ+Wõùfï‚ÒÊîÔ é]=¨8¸XXÑÆ¡¬åGüIª¼S§1ÄiS>৯°"EƒãÔ1¯¹\¢¦7†ˆ{Ÿ±x»Ä¿Ÿ/4èZkޱœÉå,!+NÍx€ßÏfДpÅ!ãª`¾ªÑXÁÀ­eF ©¦—šÓéçÇø|R›]ܹîg,Âa©N÷ݽJ\°‹†`¨nkÐZVõÏo å()¬® (½ø‘ãkL§£f¼DšÛ˜GRª/ Š[1¶³<±âû&ßwVy»ÆBF°3û6ó1`å$Lè'žÚ‚BÇÒN|d–“s­ôm Q#éªN 2ŠJ¨;]N®¢½pdcÊ—Ú)ýXb=îæh­Ä}ë¼'üЬ—Êv ¾çô´y/{üo®m,ëzóè¨æ¿è Ɉ€“ÎÄ3Éc`½:¯ýô_wo²{Ñу”$|£ØTPˆù\fM·BÔÏÑÚ¹ÒP j&Æ·FèX ®®H´bR*Â%ÖÔn’Ùµx[Ï¡¥³Ò`*67šÚ×4MB¶®«n-’½¨¸J¤¨‹‘sSkc•¶/à•K´TŽÔ'©òr}^6…9(z úÝ¡6UÚ·Áƒøê òW°ÂéÉ(Ù({“¦ÎN¡Vâ³yÙ>¶mV’«»¢[†®ý %О¾{Ó%ªÏ; Bºˆÿ:ŸŸÀ¨åeê+z8¬÷ÛðdM›Àg¼ïãô™IĤ”wÇÉ•ºµjÀyæ4Äš¸E® gŨžÈw/i W fÃÏÈÌ-ÿ}îUý¸‡ùvQ÷¸†qh/R^µA_7ßÁ? F~v§.Î+ŒAF6t\ \˜ªJbÏ8 H¸ó7²ó“ Íù¬Ä„ˆÍ_OJnQo”gÝ7G¿Æ§Âéýß‚ãað®›éé³U|’í©ë=ÿb 3ÊÍéƒ)ŽÒ‡ß ‹Oúÿ'BÞ`¨³ûПmÕ50Ëæ‰J`T x*j¥c–)Ç>“„ÚØIã"f½Ê1ëu‘ò‡@ì l”û Ù#V*G‰’$§Åò®¾Á ÜQ|p6ÃÖ&°:ª®ZUTzÝ]{b± Ÿ"èy¹2CÔh§.å_cš„å×?«½lÍ÷Öt$îÇÜ2øìÍ|ˆÔg‡Ìñ‚󪘈^‘ÞiÚrVDëh,ÐÛ´>Ý<)z>}úˆ¢ bàÞ:Ô žâÅwø#yÿH·œâ¼W¼²cÏ“û¨¿§v2ùV/q{ôð3æåpFÖÔð>;fJ;¦’°+|“ÝZMZ[qSD1J»`1’+u;‹üù¹+ƒÙÇ\½GZfìîz¦à<™Âäß|3&åá¦Yô÷Ž©‹“U»•$ÉJtÒ7ÌÌøê D>…(*ÿ¡¥³Òa´i «Û7qoŽ—puZ£g“yR‘¾ŽAG™Nhs]é^á“HÅn®šr[RÏXœ¶4ø•†^9c¹B¼è2œŠ`(ûïÔJvy®Iÿ¥}`*=^D¹5ÏúÒI-UI¨@Ð0c)}­Ë•‡ž —”ßGߎ)ÿ~'0$Dz« R)½°±wjBTd¾Ë™Þ*A?R.å‡dsÅ!‡À(ÏEј+\YÔu›“_šAšœØƒ6­gä†v¿±c#¢ÙýûÚWLpËß§~uäüìp‘úº¤¯ÄN¡ ]MBf›•“ZÀ?ê­ÆÈò°#eÏŶæ€@ßuÌ2¬õX°NB(¥1«•Ø 1NPW'\\š5ÞmãJÖUÎ×+U}™ÒäO­¦p&´÷–C&¦l•Õ^R׆ƒ#¥üY˜ºÉïõ…:ÌÖ—ªâµ¡ô$ªÀàh¼ÐÙìHalnXÛ>{žS,÷ÇFà)2ÇNäï÷Uà‹ŽÓ_Šš–Cö>Á¢Ô¼UkÂaüîï(—ãüúwƒ”ƨÿM0JÝEÉx/÷ya{O™osú†£Pk3×mÌìÖ’†—êÄff!zI|ˆöOµïô ÃS¼,¹#Ú8.]~Ü9­áÆÃmÆß¬­sÙrydK]'Ÿ¸óUy¸þqµ†Í=­õÐæÎ1¬³ÄÚ.à¡°4WGŽò ×¾À´ÄjµKâPúÓ$P§XosyÖý›Pš&¸¦Eᶆ;î䥼J¯²§a•¢‡ƒX[ƒi&Å¡Ÿy>Ó¡.J=Þ¹+Å8gË•Ïr©º46„!+溈 tþ޾è„q”Ob]!\»¤ÜVKÂf3«6‘r¡¨jÆ!çiŠz`©ŸKHN’.ôÅ‘œ_+;?–ŪÀܲͩ"Ea«0iÇœÔ :ð0Ä×d/S)éi/hšÖh”µ-þcf  ÁÛç B‰% 6FuQ°6­€ž†C´† =CfÅq[ëCÍeó*ýöŠšð—!锽w—EÓMBüŽKÖ¿ß%? "? 8]žïӜբUu¹šòE¦–`nôðHœ+‰¹ÏÖ snmø¾ Y³La°5…÷¡!„ åÛ\‡ ¸ø<;ooE³ZÒ‰_J.Úø÷ ÔÐ Sµ'W3$ÊRؾ*æœàX à€ü‹XÕzËD‚ LQkÆÚú5zŽîK±ðÜMjèúò»f¨^úÿpcXÉ©·Ãò³š-úå u ÿRÈv -!Ú&^3ñ6Å®W Lfÿ>áì|½×»Ìr6Nìóo·>;`n£EèSÛÄÃ4-½2F6TÈÑCƒ)<Œ‡«,ï£â옶ªúEÊbŽ„‹ %#Tž•‰à½[QkÖàŽçlPBFÓÕ¯x…|Wjt žˆŽ3R|rGØÁ°„R©KF¿¾Ù¶#” Ô£²H5Á@ÙÔ±Òo*øyûùFœvoúÀøÏùžºf=@²èç~Åvš,ÖäÛ¼Å@ÿ|‹w§%'A<]³öZ5  Ɉl‰ô…”¾Œbï ²Èÿf7SW¼|1~ç*ËZЧ{œÉ¨Óµž8³Û›†š“ô°’¯±Šˆ§gÆîèQœv¥üô#º_R :S«¼×=ìÖIÛ®£ Z¿¼‘f}£gàÇ!Rå"8>P…/úïâÑiM#ddv>ŒuøpdK¦îm.Üì/I¹ýÜñœ +z €`Tž|ž(ô øXoÏv±QÍ>É6S¿Ý²¯AZU‹YC^g(=P-ä;ÆCQþçúNæÇÊ èÀо`ÖÓŒ±é`€…µ¬ì5Ã5‰ÆŸ"Òºn‰VèÎe~&àQ‡­’c Bù2Šœ£¦âY'”©1ÍÆ%nhƒÈ¶šNÌJ #àdD UÓ3 ò9hy!Bø„Ø©‚LÆþ+Â.J=Šq:U9ó†µ/â…w¬!Fj•;¾¢ëéîrö¾Jês,ÌxZÐÿ6qè05aã#¡øÍ ä·ç²T)ßRCx#\öaï66NùVÄ™ä¢KÕR ¦eý&z‘ÕÖéïo<Ÿ’íÇ`vˆ6`ã[äÏÏŠ¨k;:ù£Éœ=—ÿ^Q_z©ÿýûŠ=Aw[„¶¢TF],h6^ÿoŒ0¨Ï¥9y‹@r©Žg$,¾r7t\²í}mËx"[¸!—&ß•§Õ9u8wBîÖëºåñ€ #z˜Q÷†±×]¼ß½»Züp0³7ŒR>‹Õ ¶É]]ÿ@äã9a$£Ç†`Þ®Xµ®"Ã4€ÍaeRÅÅ’=¤1öBÔqP”iæÀî{³f. ¨À×Ö`O½Œ*õ˜ßˆ’¬ä×uGhvÅn¯F‚¬o/7\¥á\ª¼Q{ðë…Oì×ñH2H]*̇£ñíña9û±a»tD £©Fiñæ7 Xc“”è£ã­,z`¤Sâü7{Õ“y×uÐ¥ŠØy8’“á-ð3øôß—’K)­âÍJ$v~$¾÷¬iã¬℉W“–‰ÿŽÃ7‡ ÂiÌl=‹ô÷ ? ÅM(—Àn昷+s—yÀkj£’ÄÈ+¼'] ú'1P/µBiü.|MnÊNDiÅ´»hÕCaMì¢}è,PFpE‰ƒ7Ã|?¬‘¿¼8Ã3Ͻ-Ó«¸¦'püíòzw°„'öü¨ô—™!þö\ª›R©ŠîKœëXXž°`³CòšÌÛ˜ðǵ®ë)&QOêyJÄ-&Œoè º9•0‹)(W ÈuüA°àºÜp ½Nü ±vßè2SÑ÷6àr?VarÎRm,¨½Æî–8²ùIIš’j¾G¶†ß7íoʰz+1ωb€wþ=ÓLcP°5€á»÷+LçL¾Ž=įu,¼ý#Š+f™ro´†HO?¨OÙˆ€•UÁ nÂ^„è<™L)‡Ém°)N*”yÖ©é8³;N±hÆþH2I»œŽl²Ìd‚MÓ.!˜v¤­P8ãŽÄ·õÍ?=“õ«)Ü}É|u¦Ðþ[S5x;ö‹½yõ2„T´.›¸ÑÆåíê,=Ÿ©~y uº³;à]@å6&ús‡_ˆÑÞÊÊÔ<€¸ù4E°|C–k ?Rá.Cç‰- kH 2:' 7«ºQjÙuÚö$yÒÈÁjòL¥ó"Fö³)e©²²gáÝÔ‡=¾ˆÉú–…0¯ÏÏ™T»‡kˆ}KÍ($è|ëÙÁ"Ì4ÀI‾)íä5ÏÔžTž/½ÓxsÐC[DωtFQd{’Y.…Ï÷\Úáýz·-ÆH<“%`‘–9s_Ñ­°ßí_4ÎCû€>’óøcd@ ¸À!hÿf=`T'œ×@ø3±·<]òå壯˜ðfmMÎâ.m'˵§3Ç…ñ»mÓ•{x¨!ËöûôÇ:­€*~9 z݃‰„=ÐtÍq)ì¿[0šVq|N—ݵп9U²IE`¤R>ó÷Š´jšcâîñ§&›ÆéÅä×§¡*hÖZq¸jls>5¯oõŸx¬ŸubÏÇpå2™øTÒ¯Je5³?|ŒVÅ1´pëÖHÓS˜´wj߃Šü¼—Uê§Ìmñâ‰Ç$û>§Ú,<¿ÿxÎ`?Ï驸tׇNˉCt‰¿ä¹óyn"iþ8W4~oõ7 ÖÍMlâè Ͳ‚³XxâÞ¾ïVž†cµ-Ó’9¬5šÕ%µZú3K8vjÚ…Ë Îyn¸È÷ö Šè×\ä­…茮›}à"w !äл攥ãêd=fdÕ²÷Í‹t¢{ì) Ü~è±FvÄm¾)¼Ï¤Û„‹8m2’h¦±i?Ÿ9*I:®®£÷Yð¸Jg•Ž öÕp"%h)9È&Ð æ dØÀ*IoˉŒôõ¤¯ù—BSû:M­Ž_îׄ”y+ïÜæˆî»¾‰WÓÍeÊœ¼cʳªîm™Ô~ÒÍtW ¾U3ްlLîÐêp ÖF(ÕC’ñ‰%îþ "—gŽ Oúi¢HÒOHò²ªÚ­œ·1ç®Å›e[i Û$qM“ bd¨ÓÉè[h8éÌá;Ñä5òç,Œt?¦€‰ÊÖ0ïWM•¤ÂÍ»¯`X2_p„5ükLC¥Å&¶t¤ö]%0'~ûº+Z’M&g ¨ûáÛÏ4òZë–›Ü~ëz¦¨ ?'¶ôoÇ?;7WgŸè:í£÷öÞ!ÓÚê·6h,§Ù—™j¬czTk²vB˸^ãÕck룉î}ÚtUgA‘âªâ×$'ÈȦYŠ‘íž”ÞÏ›;OKLƒêžÈ6H 4g ²}[-Œì¸t›—j»nìd§¾á%‡ ^¿HâÂ"¸YûØapÀÛÏ‚LϬïÿ€Qw:̉m<ö¸UµÛ£rê6ÚKh:ç\ß>[먔€/ä*\êȹ¨km/>–ž[…èFñý¥‰K'ÿZJÊl‘ÚˆHpJŠf€v"l0 èj¼¶êiAéY5ŒðêQ+jy ƒ~:p½Èª+PmV B¸~kÍL;8Fy‚ñ¥v¤­M°Ò!È5ùÛW/"ë¨©Æ ’ó»{â €gmî5vA)pÂÄø`;V+h9U¯òr"å™ö”gø×ˆˆëf…]cáúæ}R•`‘Ø[€f$]iÜ`ÿyŠløMÌ'päkŒ¿ ¨¨Gˆ"ûì«T‹lû·1ÐáWŸÚ 0âVò›'g6·êáJá§‹!q¼Ì»Õ+½7¹ƒ +M¸5Ý›Å436œ´Ø$6ž%Qúö§Bý U?ë…@^gf,Ï8ó‹\íÇ<ý¶s(Nl8Haähd˜ð©`Äÿ7êñ´úZöß(œ5Þ;9ñ/±c‹„b¢˜<¨xx-F5™ˆÈRõ)FâSÉqI‘mù1]Vª½‡¤Ê=©Z¾PTqüGćÅ͆~ÐËEe¢š :Þa·-ÚéÖ1ýŒ¹ÿ?"õºï’).+š,<éo×ÌÛ3‰DÆó/^%Ÿ…žr·ñþOÕ ¯ô²áXµÞSÀ ]±ç|Žö-×µ2k§ßÕK¼ï¨áÌjहh’¹ßð­#ÛRöÃ|Áò¬›ß_Áý:}äü•mò²A2.$õ,i¢xàéÚ~’ Ó~fv©ÕIOL`R×ïe—bšÉ§³! t¼B3aUk/±~ùåŹ,L–É-13tj‰ð( =²L®ƒ^nš˜™s‰£ˆ °-i)}<®*!v±UÌ÷$ŽÄ­[[!&i=lÄ©qý+j®4ŸV`W.Å]M:VàFFo…”ÀÑØÝòÚóIdöÿsF+{ï òïç™ AùHeE;×{ŠU0it¦~=A#4™S>Ï_¬0Qà–6µ#€xáz‰ýC„ê¨Xµ¨%ÉÜ0ª‹Ís;–€-E2ÒO’üôhŽ zX¬ÖiÑ[år%¹#¤a°Žm¨«¢väÍ$#l O_lCz÷øðÛ ˜ïÞ'´–bÚØ1ôºÛ¬çZå}°Íò˜%VMsa¥Êh…S;x«Ç·ó†¡ Úsá ša½i™xÀE³šj4 %z¹$'NTvO-œ`øÃ¾e&=ÐD>ø$’cR€3´•v¦Wó2¸¿×"»ðóç‘=1Y9È­Ó ŠÍöꢱJâL@ïz>ˆ'&•@ª À"NHb#­þIÕmªÈû f‘›å.F±“hǬñ ËK#)ËóI¹ú Ä:øÞgý”9t 6¸þ/ Jrþ²ÕnPÃj³eŠ1æû4àrÖ#ô»ÚåËda%$Ï%ÒØuÞ Í³|ÊÕwývÍ9³Ç»¯TiÜtœ$}ÑG,Îò…Q¼ŸšÀ»ÕE÷V2­BáWKÝ”eGµÖz+$Õ£8„;S ÿuÃÝÀf’ØÏu}}°][š’âitš5Ÿ±Ì)½fO¯ê᪒³þAµ£íûÎg…_ê~Éžì`Áo‹~~ó\UêL Ì¿¦- ~Ò/+š@(D˜®9Ï5NEÿ'®)ÿf”ÊïábžHc™@ÊtöÉ[AÍ­u†0+Gª¬1e{îùÇ;p˯€= ]ú±g?ߟL7çÑl~Çò÷Í8]¦‡‘ß;‰›|{âIØ{ŠFrЊ”fÝAÍ=kCf•CÁ~‹ˆ¶8ìÖ¦zRÌ_Bb{hßApk¯O»ýN†µn{Ÿð‰÷ÓÀ]Éèk9Ž ŒnÂ2Œâ_ÓW²ÖtÐák¿–{<ñý5¢ú­ÿyvk°òŠx ЬºšåæðÖ«“/Úxض°¹Ä§*~=Jü|劾 ]Nªq;âÉ’Ï££‘ý’V¾XñCþÊ~ÝžŠ.Ð¥Cãë¾ R¤Ö‰wÒºÜz jWçf êyÓÕpâ,ŸŒøeœÇ‰Ro»Q ¯qbü&Sý]à´BÙP.娄m-4’8S‰2NlLÌìð± Ån£„Rm _çô‚@K^ 9ä‹ï^®ÏIGˆZ¯dHX©&°õß¼\Ë…â'ª? øu¤}eGå€Hü FÁ…’¥úžòͺð¤ßñ`´øHÎoÌœ™å ½Ý=žèw‹§<ËÐî®-!B^ƒ2ŸE(ű÷Ì‘v^Ôétr'ôîÿeIS¬ËñW”.¾QIÿ:ŠŒƒ(î• Ó‡%äÖŸ¬_k¯-q‚â³î¶¸Œáy¤ˆ’Oó‹ÅårH}áZmòÔ*]ñ`¥;NW¶Š¡PUìç`OÞ” ®’ËR”ŽÉ fd‚M‡øj¿! Ñ~3;31›°–]¬GmEtŒÜ‹[Š,n/ôt L:ƒ1—öíµ¢Ö+8ô\×Èý¾b½%iÜx4åPÏ–çŽ]8þð£vz‘€tFbzúHÎ_Õ_¦0SD§mõˆ£GE'ï§~>ÆüÛýG¯ìdv4eŠÀLÞKAŽ.\Ù%ZpáQöPŒ!!í%x§(™!çñÖ ï6öuYXÇXÿ]X¾p?Ãá½ µãg†4Ò#³Ã`E0£MiŒ{ÛÔ ÷·Û-Öô•]§§ÀçfZ±ö›µh…ýRÜÅûëªà`wÔ<ž¸õsáÒXD5÷$þîü8ƒœì\•ä*š«L±&ž†w;¼$5¢W•ßúbÈ^5@<ÂdêÈŸF®zƒ»ñ¬ö2ë;ª¾aBÞ÷–õ G•sbPž€!‡/R=¾Â¤wXç:WÉ+†y"x×DÄñ8¸õ\# ·T²7«I[~ÇшÝU㤚¾ûäî¶Q÷![\è7‚ VA÷ß#åm&inD°ÍPd­6H³ørd/qD—(®”Ù`aºÏ°|ÍBÁH©Ïè¸öÑþgÔZc¸É³ZqN¨ÞÞyRß>¸¼]p|EͶ[1ÔR¢Õ¿ äû+“]}¹ªÔò^|^¯¼¶â1ScT"³'ÎK^ LÆQïoýFp¶¥˜í8pîòH¸ q¡êg°=Ó„z7ò4jý`tiÅ©Xó{õ¹çæ–=” ò†A© ìeчC´S“Ã’ŒWå[ÄÀ }î–a”Öª“ÕYfQøb/¯)è圭ê«¿'ðô‹cÓðèªÊ5þìÓØ„ú´ â*s׌ෞ^òG]Meû´G þSbˆ7´ëÂ8C¹8}IG }j!v–ÍLý#P‘IúÙCËg‚±Ô˹—æ‚¿ «‘’O¥As>’޲Ê}iµÃr Ê‚ü“Mc~$…çÁ=xüéª"2þ›Òp°ƒc2ð>h1ÆY“ö©Ÿ½ &¥“L²!¡M1*2ìYEþçŸÞ'„e³¿ø•úq÷ÑûI,޽utxR‘¾’ævœ”§ã·‡˜Æ¿‰šÔü ž¾R lJxúL;ó0„ÙXsCøYÀy­ßóq¦*àE·:±¿s"\Ù Ë‚Õß:¬Ðõ‘“Z§Ð+—¢¡3+ÿc©ËX†eH)|¶XˆN œ;À®ß›ðDráwúŽ6iì[æ yÀr‰t‰odëÇCae¤[ïÅs³ÆÇ©<¸$¬Ó²óáÇ?‹yæ±Ûá|‡€s7g͇ÑÉ •“+œô² Ë%LùGŠò‰ yª¦ ¹ n†À^]€á{Éñ2ô÷1A“ß‚´ý¿hX³m¤‚Oea‘D™¾ìÁz±Ïªñu [ËܦçÁ$c¤ÓEƒóµ \­(J½ÚÔ.ÖˆÿÀmÁÐKyÆç¨•˜Å -ĘZ[•×9;P;wéÝ*:Ý?¦G«Û£*úé'‰ØÄ¨Îý¤•ê§4&g£èÙÌ{œKExP÷qM Nî‹—ŽJc@/’N`pafô¯ÏÃQ'ᥱøiØ;ÌNÿ/„µb˜Ý¢}Án4ѱyyè­ü…Áî=¼~N;8ˆ  ßóÉà#Êî÷o%©¨BLb}C" *«”v1Ig,þ·U‹{#Xô£Ñ %mÕi»¼weÀÄq™%Æœ+Y,ÄÌV@e²áçP0îÕ'¶,0fדm8XffÎóJÞO_Žˆ(P¹‹to«2‡äé2ä¼'ÁʺÃß`¿¤G§Æp~gèÕo& ã™ü·/Ôü {+-\Æ[½s³a›X3‰ÙY9z.°Užø¤}  p~ÁYG¾^âµ?!Ï*¹ªõoœ,Ë Î—{êÉÈú>©*lí$UoŒ,Ó›ç×9ç8‚pÕ~–>ïWב¨uiZÅCé1@‘ÿW‡~‹ÐÇùÏ?’UgC› oËù 9ì?(žÅ&ÄË?øTÑü,9¿ˆ°3ÈcÅdí6›bW¯lÎp•è‰ÏJä”a,º'q=o,MÛ¼50¿8­ kú³‡7[’câË lƒéä¦èÊH¿¦xÈ{βôҊЂ•mJ:ˆ-î¡ã²—Ym‰0œcÝu¥”Ð1J"­ÆúNÎ\îÛÃÂÍÍ6TI9yÒÎQ¾î6:’à&±ñÏi!¡'rÀxz¯ÖÐ )Z±àÖÊ*{à‚ࢨ„Üu¡Í|:=ªµîŠùàÚÒ÷´rÕ®DP©LÂbÒ({S«P,þ :šd1Ìî¥IÑ/Lïð~¡ ?ÝþÄ«æ;u;~vñÓXá^jÖ÷Æy©sw„µ€Â|$R€º»)fÎW G[–VC+p𩡈淨l^\ZÔš_þ›•÷°m¿³ÄM N'©?¿„œÕ~Nœ‘øè³¿cý>gn€Ìu”/Æ~7‚²)›Ÿ ¸‡RÙG%!*ÍggæjZ,O’¦(=Ø7K¾Ò宄(éF*A M@¬×íléø ï)8éì6ˆùç“ls¡,tÅè»,@zzúÊþƒ¾Ç5äö·Õ×_ˆÁà0iLÕ™£ÃÀÖ!DZÍûì•-Ê.Dqƒ¿ä±8y¢onPR+ÆNÐþ–{¥4ÔSÃgqlÞc2Ñ—º«ÈŒ›tÆâÙacÍnðTT—s*ic€‚/T§ýufrqÁ¬,Å{ YSÁ‰V)éÇ»Ê[ÕK‡Vÿ-Ä…ž…¦õ ƺ¹ÿ]ÏÃߊ~îóó÷oçOÂoB3Ó̳r9–8X$šºe]? »ÕZѰ–ÅNê”ôz€ì£ö&¾9ê”ØÄ¡Åô1…”½nñºË§íå'æwtÐÃî+hÇËØ¿F™ÉÊRjýëÅŠÃ;¸oUsfëÐmx|ÿ&ƒh¹¼N¶iœ? è9i4:GhÒÛ”r]ÔïÍନÌö Št‘Ô‡rúÞG¾†2ÍtŒL¸ïÊor¡óIJÒ¾AJqN ŠòSx~”iŸ°mÚ¦œg‚NØ—H¦Æh¢k?ÖҦίð™‰1ãÔ¸÷6J«mg”TÕÈY ‡8õ‚¹Fd²3žõ&¦Ç3‘u8àïÍ‘\°ÛŸîѳåg÷{–"#!J Dû¶CO¶«%šãĂޑïî¤Öè|”0«÷ápL;õëÃ!-jÂÑÛ¢[¦‚®/‘ˆÅ¯çwgŸš¼ªÏü¨üåÈÐ}‚®`¯Ñëá[áCR+‹Ù(e͇i “„8ŽÎŽ„yщÁA­ÆþŒTxw-íSæáiZa÷À´5…¥‚òXÑ mÓ@l«Ø«å+¤+à¼1QL!9î +Ûâ“øpà]lƒ k®ÐߨÑ0M@´eÖB]OËvÚÚ¦íºŸà¤è¡ªJ…·ß¬[Is¼÷bs6®cw…kóì®e|½ú„MAÕõø’œ9ê—ª`–Ô;Aç…+éÅöhl¾ßÿ!»‚!—O¨7µ– 6“–µ›/IŽ`t",ØS@ZÃâräX*J¿vhtçYÞ‘bG×èUrãqè”ãÃïùPNÂ<¸'"B – V¼Pª;È*{Ø#O»ŽÏ“÷_’¨ÃËŸb:n½*Ÿ÷+oºH×aJÙþ=¥ZΊôsÿSÈ’q¥Ëé^ÍoT®ÄÊï¹èš@&Ò.çüaSf$Ti™R­D­A;G¥éƒ³>’Ÿƒg¦£¦»è\t&XÄ9\Ñ~Ü ».›²=/±'|…ªý#ve¥Ã‡ûG<‚ç|³ÊCIƒMé´L¡²ŸÅ(Ã5L!2á¦T”™(=}â³l;÷Y^CÚKäU­üªÐ Ê@ÆïØLc›åõ ÿkÛI‰æQQû^¤slËKeô‹Žzq.Š˜þÁd~ÔîÏCh>@بں´-~úø Ù&aÁx¼û°Rd—ׯwMáÔ:bÒ³$tªÇ h·ßm‹N˜0XRîO¶ÄX.)Î2!9Ž6b›òª l‹!Ð FpQQÎèîðüúCžÖ°èÇ‹$$øO gqW¹¶Æâùzßȵ±UÊp»ÕøÉê·!ä9NwZqVùÐ Õ1s0¬0éܽÃwB nBH3a×üëŠÎèµtº~Óy»>u%Œ›aÃ3—Å<…)“õ!œÏ¬¯ø%>št‡+n ª·®%ò[<~i8 =Ù°}P ¹oêy­óÈ¿ «ë¶†¹Æºv5¦­›DeˆÐ9A9ëP””ÔJR9Eúª¿«½¢SG‡Ó}’ —Z™f A-IjìO¬B3iÍæ6àšùaUL’%;ô+µUµ|òÝ Õ¶8h<ÒêöSU6©c7>¬¹[@_À?Ê!Èg“ÆâŠ=(FkêEÉ!»¾§ÛU|ô Ê ïBÏkô¡ó Ë\fŒ`mî{ ŸÖ¬èNÁ$á”@ú@ÌæEBý ¬ÌÚÊéOöËDÁr4¡wÑŽ`gÇëC'Ýh»ó×6—žìÉœ&| !ÃŽ–Uú`›L©ª¿“Ue4·°T5±%x ‹þqd—„(ÍÂ`ÞÌIÙJw:£XUÊk  Ã’mLŽÜ;½Í‚9XÂI_7ØþÖÕ:‹EBPý‡6€DK¼7£Ð8@¬ ¾&Ù&ôdx:˜_Sž¦öOØHuk „õ¦ûLF i“yC(*ÊH^Ü?Ð æÀ*jQ»Œ( ×Ïç>:š|s|‹ñœ WO¾žÞº“Eúx«ƒ—#b¯ß<‹ Ö-_V"ñœ€€Uì¿M‹‚í›è þªcHh{ú/cÌÖiÒý–/­‚NÏÛj½ËÖèÊ)v„#°L}9àá ¤‰„úý°@Fbç^æði?EH$ †óìl¦V¢è:(›¢‘@V[B%øxiöšÂ ªîHgÛˆ¢øØ.hJß[£K$'R u&s¿öøÝ#zO§÷ºB¾m h?8¡/!~Q:ç)O÷Ƽ6&Àl¸iyª²èÄEh”öÜ3òPüígLÂQóX¤Võk ,R„›ÈÉ>UÅÚmÇQ„7‹øìE6>ñ˜­EªÚM·ë’JpŒBFL˜zìÅÞD™Ô°ÞÀ§ˆ—[›Â,¼_˜Asï}°ÏoH3¸ÚFØùAŽ\ã­š.(qYèšÙŸ`=;™¯stú´ß |¤Þ/ZÎU¶&W7æþQ'¬PÿsšÜ’wñQ¦‚‘û)›˜€òø«ü^ý´yåcŒ!²gªRXó¶Fø p"ˆEtE,–#ŸT¨ã~åb%Î%›ÉWµnôCr½Û.éÕAWÉ×?‘b‡qõŸ²»CüæÊ  i*u,¨ßÜ;ÉOM|š=L¸¼žÿlÿjÙ—ÞÝ“ù|ÞÆ#¾‘°<3Ž4I¦ËÊìŒ@ãjôPº<~F×¼ Pg-ñ>»È8aþ#|fv5jLB’%ý Æâ}Ûg±,|ÒH?Á£Í«ˆE.º"²°÷¦ºyÃÅëE 8‘åOÞc—sêsáu+5p·Äž1ŸÆù|4A£Èq´ÃÀWŠiÎ$}¶}#èYín÷‚øDv_Ý¾Ì ‚4NN¡o2¬¦öó,î8AœîŸ×MqÁòY¾´Â?/êóx¡–bw-!Guy(Uí‘áCvUÔ*ï3›ÏÆýrðÙE4é>¡É Ô´Ê,k5ÖQýI0>  °Ï¯.üQø* ¯µvzUMM¢S|ä™tªXëOÍ€Þ&9”ý§Iuø~Á‡ð ÕMSW¾wÖ<è%Àr3²Bå]G~Y̤GÀ˜ýŠF„%f†%gf~ûú˜8^Óë l¼‰žÐ¥îÁ’Ú×aj)’•²!ÄH;‘«˜š u [ªKÔOfß›©¶Rž¹âéäP¡ÑÁÖ9Œþiõ"F7¢´^•ÖÄh=©¾{ž2£ŠN:’±ŽøúŠšÁëFTÂÑ^L,ÄÂ8õfÔµQ1¤áŒÉùqsði`' ô³[?(0úQJ.@û¸A4a©®ŽŽf è>Ÿ‘ñÐR‡Ø AæoÃ) /špìKdÞëneURÚÑ3L/ùý«e¼ÿ$>¼–¢;|ñ’ì]sÉugH(w¨Úâ¢ØbdÐ@æ¹¢sCÖ§ðùƒIÑÜ;˜!µ!ô_!\w$ï]?>\Öh!ærµ1§ç³SïÚ}° ‰Lž®¢.±é6»zûíq_Æ÷‹ö'9~Ž2£ç§¹D£3Ô<¸x“_ ';Öþë|»lfÊÖ“‚±{·hé"Oõ·XÛ…47ž¬ä¼0º]ôV³H$j"n%–jÇ%Ò䌕ޘŒQª ¥y‘wÕV§Œ½[Ͼ'E;ç ¸êq€Ë|¨ý%r·§=Z±Û€t^œÝÇžٗˆÑÏ•¾šU_ïiXɹ’.þèêp=hÇ M„Y©Mó¹D½p§aÊcLN¥S~°>Ÿ"Á2«Jÿ!—Ó( '<·GkÅtÓ³e•Oò;kQìãÑE… ´ =”U–û}Ò”=!“QÄB¶¤N”®áJ¤/,aÓ’öXƆJëªRé¼ç”c¾9”Ê •ÕðˆdÝ'é·_c›7™Os;q<õ”b$"ü’ÈH@j2[ÛøsKº°»×7À¡“I ¹†Þÿ?“: Ý­Þõ¯¶9•‚¹fÄÏŠA•ߎþ>ØÍç©æÃKxõ‚ïd§]ݼ¿ÊÊrI~üF]yŽIa§HÛ¾(»¥ø¤AÞLh¾¶HÀV“”šŠž˜`ûlº‡MÀËCq¶›ãªWˆÿeg«Àá^H/æ•r iÒ¼Â*œD…%|†ì2_ øÆžÎ½ƒLõQ»ž—&‚’íöMضF ÿ3~!{Š „Ñ8™›|›NÓżŸ_¨´Q´\–¹÷GyÈ_[Ì6CAúKŠ>Î.Rðu˜´“Ƭ̰ÝNüáù©¬‹ Cð®VÒäÆuzÙŽ¸£ÿ‚ÑGº>É ¡#‰ù¹l(©Rˆd–\„^èŒàXA<ˆG5²&蟆ϼ•gS$Tz޵¾I’„8öüÔnªÖ ,ÙtP‚9zPùŸ‰]s˽_^»Lò•OÈždMô#vë}D‹$./zˆIˆÅL6s@JëêâwCd)}êˆò¤0è%Lü û  ØgU%"û4;,¾¦n´4ø&%hzûEÅ$ƒŸ¨´Í-4þKPó€E í¿¸SO}þ—'âýGaJBx<éÈ….)Õ…]wdšQN[_7´¸8Z›÷:•Î(]àB;ãü1‹²ôªúäNœqóssŠˆˆ<Ÿ¥ï¸_9‚rûcCÝ?ãí¨Ÿ®NN”U9VBD )¦fÆb2kÓ-þ;ýoÍeo‘’¤Kæh2q}7’ÊGo,:s/YÊ‘?iòJ¦ Râ¶Eް‹œà¹èM@;EÒnBÅK˜ÑÙèxI•"ütÞXTÆ‹Ú`ßög†ó •}Ÿ’À¤‚lª²çÝÒš‹+vZJ¦kþáQb£q¢k©¯G|2Ä“1-g Ÿ#ÃAšìáO÷CǦšã|>6=¤d ÚëÕõ1&Õº¼¡¶GpS…ttG$ÏÔJpR4øÔŶHáo:蘋A_ù¹42ôÐ ™Dozö ãÌc\{Ák§c–#õn£2êáÒ£B£ â$"¡iò„DC ¤áép]\†áX×SPs×ÿ8œÉÎyz+ZºJD;³°á¶²o‰&c(ðoð ¾é-Ùâ™øq?ÜÂè´Ä%©Z\lÄé0Qm#ßÓÂE„Æñ Ç.ø@žS(œTÇê|e#ÖX¦ƒ Ó2 ÃÚ‹ýô Õ–ãTõ›%µ4¦€ ÊA•™ØßJ¾ÿFª$¶ŸÏôž ÿ7«é½Ý-Úy‘H¦«»HÜ02¼ŒjëB 52{MR¼þ`§˜i¯ýÈÄUaªÁQ|š´üH0”QYñ¼FóU\Å7å뤅:5Ó>ÏÄ¢ àPKN¶¡ÇÄŽrRÞ±qL8°´ØY ʶ…ü*VMQîBÅ‹mxÎ@ï|Í”Ìñ!‰'ÚWï¼Í‚ùާ“ŽÔîÓiv­þ3\#üñÀݲ±±q3m'ŒZÕ±)F–qëÇæt’ „äÖ)õ>«SÚ€läP“¶G-M~0Áª×“Ûžv–wÐÎ)²)"{ñàp¹<ÄÂqŒûÁÑFþÄæ5 [ZU•è˜ä¡YÍ|ÌQ°S zä«ãýÈL±m2NjfUýNdË[gUÌñÌ( ÂÇô¯D‹‘Ÿ» °êõ¢—“Ë .cX•7î.£Tƒ‹Ó¼_6’؇ÝO@ Ü{B묹ñ»‚Hgmî"§:Ì78ÒÌ뿸¾/¸–0^tIº4]j‘"væ¿{ðÀÁOFëóǵ*þLÃèQƒœV¬Ð’^“â³ERÆb]°½M囪ç›0x;òƒ p3üÁOÏupÕ:¼âÇ 9Ù  E¡¡ùNÔ ¸Ã)ýªãÁ³¢Åë²×‘c l'æ+ð‹#šÑò …Å:ëS,>®¼›Ë[ì•¡”çÎú‡™Ñ1ùÑ™£âEà,ãhÜ |ì$\ê~!¶‚8Óœ"SÝìJ³‚­—õ ßi]yЦÜ}<¶ZŠ3f0ÓØÍP>»äA°“tF1Uµ£!Qc|e³¨žOdùë³L6oÿ(ÞåàÇŽDŸ)æHº(£ÏŸÔM¤·™q!W9_s×~_ÖäøÖ®4 ±²fbŒb·8« êf–^qðº'µ™ZDkÛ A~T‰òº“t)À#¨°ï›Ö­M°4¤)ë^—Í_«ný÷*|¶²¶á߬Á1¦Iû¬Q§t÷÷¸ÛyL—îÆ/°`ž)ݹŸ‡ŸþÛ4,…i•õ—à FÙfbÔÎMT½hÀ>¼i•aÌ÷t«a•Àg\b%ÀéOh¢zúªzIZUç©Ø‰ ˜X3¨é»gÚ +9¤³ µ¥é9NÅô÷JCðpÑ)¥"Â|qÜð%«n7kšÖÔwwÞ]ê^—æu°8eng Š¥¡^ÆÅÞžz”€­Ê“1]8)²¯Nh/%^%TÇR=Åø'(کޤV£EŽ»S[ÃÔ–9pþ:äŒ1~(Ò-“âè±d·ù a˜‡éÝ›F]M¦Qz¿Éõ6«àNŸù©i€€—Ô ósÝ[0¹Ð<²¢Æ–·›–Õaiª‹äô·¸ß ö¼4ÞhЖöXôâmnŸü`nè>‡_ŒæÈ?ÝÕ±é{`7Å él.:¶™§:á˜åÝGãr;I䯚}™¯iäCåOg¡}—Àƒ×y^3C€a9ó}y¯Ú#}«Ï»Ø‡ ûï{µÚÞ0W•q-1p ÌM$:ÿ 4GÖóÃÊÇþq5d:¥np8®€c˜ä#“N:WO³¨¨x·{-&€ <Гøezá7¾Å«Už€ÅBJš‘pOÙ•|0 ¦®rI€÷\EW\3TÕ$exøð „'¿“´w¡OrªýŽ+u"œ~ÃâóJ`ÚwçάqT|”*D1wÏcT€²MÜøë«ÉϪ¹HÊ´SâÆá4N©NT}÷–'…Ö#æý\BI:ÿW·X¹î1C¡zãAZt¦ñ|KsÒý..H¤ÕÑ’´!ÁýŸL.ONJÏÏR¿Ðñ§JùºÌFTQ¹ÚTWæ©`Ö×2 Úþæ\£°%ø’ÚN1ѺŒÕÞã¬õ%º[1ßöSø.M]Ÿu–E»Ù“nŠ–úßÑ—Ô©ž4žÕ¨ƒ·6FzÀ—ìr=µ ǃ–ˆh[ (Í29&‹Éaºë+Bþ–þ ó‹—ÜŸ’» !þ.#¢òü5Ë$ð|~ð 43yFzm „a|Ö2…lKù&Ü$¼J2ø~4ˆŸ× t9K«üŠŠÔuãh¡ÞG Â¹Š¡J=UXZ8]¨.a*¹I®ZY4>Ü}Ô_@ILÁƒ:Öz6ÝÇ77¸©€ixûF–çd|s²^ÆweÖžØÞ²L‰ðIœ«ÙØr² ÁLAa|¡‰Ö‹c3tŽ;Ruã”þN¾3¿nÏÃÞ2~âø{Š€çOÛ€Àð-ÎñØ?E©j¬¬°v“SæÏì—4…âëÅ› +.™ôêü±RE—s™¸¤™ßB-¢Êe­¥Op¬˜mˆû€Ù8­ñð¸pÙâÈñ꛳¹0R=)ð RNTÁRX¯.È"ÓŸ/´jïÝÒ¿™KÕUÁÑú´wÂs.êu’«5ó­rú¢ {ÇÅìòL½GÚXõc}nÂH4óBUaUR+×Ö5Ó›NŸ+‚Ì-Ù…pÖGÊjµª”óírí½ (ðc=½Tˆý™™"€£j(¨=¯ò‡@ï%g%e›ßCr£’~µ4Hc/;WëÎ~È÷…`QuAôÆtˆÓ^8;¯‘à¢iÞ °€Âpt«T¯p7ÖàÔ˜yЄ³¬av²ùüȽê4\ÀZd¾øöþ½× >Ja…ã³u$*ê²Þ³«¹Òû}䮕ÌÇÈÈçöiâ) [b“$>·zœSDiÜv‘8ÒGM©)9Ö0XÊžþ“þ©êí¾³+$ŒÁv—¤UBôGM$Ã…)IúwË%£õ§ö×s¾PZ¶µ€Æ÷€%]¹K®ÊBhnF¯š—UÖ™ÅK‘JŸCóâr÷1? »c/‘Ù‹ïä÷Xø’ƒO;ç~7ö4ríLYut•Fýóäò% uaªŠšP 4¼pjúÆîù£ø/5¹k¥ŽÍ3/\0v‡Á\'Ò$xüÊ~Ôå’oC“ß‘õâ¼×ˆ4C Åíÿ-&”°3Ý«g#³“²ãÁ^k@öÃW( ‹ûnˆ#ó]»Î…þr9â½LH>ʺ‡ÍJ:ý¹ØÀ±`E"¥X°Öhæ‹…–P=å ˜ýX%|Ó¦“üîÈ.ÀtD®0Œv¥ Äãg°ðÜÒÉÜÆNÝ|Z­o³^æÓÀ!`Ë À6ÄèØûC. X­AÒì剶ÍC„ÇF»s¨¢Ö²ª~t°go´FÇMðÖ"¥MÖ”Êᔕ8ëLJ.+Z¾-;pGI·äM¢ñÜÎB¡g¾sùc v¡Ùû5Hìåäq|.¯6âh!™gƒ®ÇRÙ&ÿ}=ʵãÒ8¨ËàŸxѰCíÂ_ÌûH4uKœD"x"GZ•Hô®b7”ÿ?2ÀËoGÕWã ”jlTœ±éÛ Î•\:,–=ù­ƒ7i{D=6y3L ¤Ù–¸qfb™Q2h×*7ËÔà†ª´±ßì*$óA©v¥+JJ,>U×'nÙ6-Ä'ûf†D]]G“•ê0.=<Ô£u¾ƒ…s/ÒˆäØíEÚ\å:’ZŒµ"}ìmÞý!°”ÂA¥‰œ.Ïß œ›\èïGþÈygÇÓ2º4è0¹ø JzÊ5ïû-w΂eËÄÅJSËÖøswô‰ZÂò¥ý‹[1ãù?›|%VŸÖHÄ”¤<Î| #ååcÁÊý¬Q•rI§˜áfñä¨'“ŸwWÃO]DCž8ÁcÃ9î~ù"Eh³ýÈ1S˳pêùb.—M8‘nºàK’hñöƒ-Lð4Á šd‰¡QÒVï ‘¯“1îûU5V)X¢<ÛÌW+¿ÅgÙŠu1pXëzÈ[¦ÅfðØõ&ƾG ïç|Aˆ¹®$²g@¿ë;CåµðàÄ5ä'™8Ú™ÖÈýZƒ{—á‹TLuÚ ]Péføãê‰ämÂŘ5ër®¶ø8!× õŽô‡ K­‘íüqÁI î‹ð­òE¶Mé…í,§fÆ_…T ŽÒÖ—î¸nUWM—å…%C—ˆïM#(Ÿ§—’6ãÏhë3¥wKéwá¾bôÛpßÙ·9V¯ÿްl¾S¥ðé^5¬$‘y ì´CÈYøgÉ|‹Í@nJœP¤ìФÊ¢v¯Â;^g§+ííÑT¥jpp@™óË«Y¾Ê9¼¬­\ò»fõ)ÅôÎ!‹Ø>ÒBüNuâRÂôJìG¿¬×Š®8±ËÈ¢·ÕI\ö#(áþÛzÏTÉïÈ»7Z– +†o ÚÍÐð[ülZYª”yÆæv%W—äø<„žëõ„hëVJÈ”¿Ø´ò›–gÚA€îÊù¤EþS""• Þð0³oý#iä#6{?«¸¿¤Ô\\o„ÔB‹¢9T¿ëI°UDjv[å¢Õy1ö§!#´@?ŒSt”äQ"B½å 3 £ÓÖVc¹‘K-%&Pøyºú%ŸºÍå7Æ7|‹{ÚÌm¦š¶øu+Un¼ÆôÓ~wxìS±ìøH ؽã鹈0}Ôë=2Ä—xȲã*f~ò@2º¯›½êé xEkP*þ6_Øpƒv”ÂHì }z´˜ó@’ltse5»Ù†ËfÁ…ìKÅaðq­ íÌn+fV‚*F§a8MÙçÒ{e¼K…˜,W^Áýp5Oî:Ƭ®Îº]8—37ƒ–8ПƒÚˆWKË = H£•éëÃ#bË·«l.Å3ÄlcÐä*¤¤îåí*T@t,^üÃü[­Ùãj\} "½QXÁ³èîêm\Ð0moŠYðZó̽ Þ1 –!ô kŒv.¬•+¾‘¤:q3‘A»ÙÜ¿h‚ÿw’ÿ,º˜Âp,Y˜{6ï]õ(ât^™SEŽ*“k[Íž«‹Øß]ˆ¢M7€~"ÖàØzßéN5°Œ^cz¹Ðñ©q–’ |Ê× tô°`txmæa™êq™Ûøˆƒ€õ{ÈdÙA&ÍÀЬ+dðØ&ï%¦Ó®öùôκm ‚lÃÀB!©aiíAtÜrŽ­Y8+špò xÑwü"Uøÿ$bß[¦ÞÊ‹ ØàGg÷MîýÑåIMrøGrŸ˜YÀD‘TR§D¢Iþÿk¡×$%Zqehë4¥¨ek`ÁÁ/¸Ôéj·¬™ñѤ\§ºxK“ @@²_‘c6K†!–©´æÉ¨Bl’Í $Õ¤‰Èt“¤YO= xäMP²ÆgfŒë`•«>à+aÆjÝý¿²H˜ Zß§àÓ¾¸šü¤.•@\]j>¨ßeê^H*=–{Œ¼;ºènÈ'ðÑž©cîì½Y”ï#t âI¿óÇæîC*)ÔÃX¸ð×ÙÚÿ|Îë!`k ½ gØ‘Š±ïg[¹’>A%B÷€³_—`K‰bŠ ‹ ÜxëÉ–ü€Ë!jႾIsã&ilûû”†¿ÙI.“¤#ö…YÔX7õÌ‘õw”ÜFä¦k,eR¬~Š{ìLývš‰éýi(±&í&0[à)ÖE¹¶ïnZ£ÉåûQrèlƳÔôàgy†ü¬ºÞ;°ÔÀÍÑ"¹m–0ºQw COÜÊö£7ÉßçlT?Ê>oªÛ±»×^{R-&zÀ¯ýiLp)&I¸ þ Û­rËK«á4etÖ¤c\ƒyÒ[©¾4¶Y{‚+¡ili €sȆmGPÖ딜dIC×XØäžM™wÚzXsu ÎàU|åJrK÷&’‰#ñ "QéÅQI(üáuüÿ=6r0Ŭ—>ø\ˆÐHmh°‰‰“xs’~^4#{¶uÚ êU•h©¬„61 µË†âæôúmwýsËó€/Jœ/öb„a æü Oº<ŒxÿUs`›¹÷¦a ŠgTÃå|LÔòØ2dU76#:L`7ïu§>„‚Ue=Ü»œh†VdâWYì‹ï õ›NFÅâǦ\ÉúuÏ‚E-ovÌ:ß.÷^ÚÿX ×DjØ`‹@øÅ»ÿ {ÜÃb]àblw%Åõ­<x§xm‡uš•}”GO}óù ®ôŸÝVðøáCž'ß/ P‹Î=uÐ ÿcXsqž+|Ë/×,(°'ùlá*7m“W 9µ #Å`h“ÛŒž#W*ƒ>ßレšÛIùà «r9°NcÏb—DÐûÿs6 â9rtóŽsŒÄ*Џ«¬Âj¡p"s}­ ¹±ñ‚ͯ‘^ê^?Û¹l|B¢ÅrvçlÚrº¬*äX"ÓNó˜CÞlðjª© )osä·Ð±(1z±íȧÕÉ•ãËO*‘ò‰Ëp­ÄnŸ?ñ^Ô|ÜM [ôæØ™ƒ%ÉÏ”å:Ðä8-:*ÆLÆ=¾fÇuKׯV\œWŸœ±~ù¿?=¹ˆ  ”®±tT«Ü£p›d¾NÌÞíë͹–²]M@÷ ` ³Vz¾öF¹_yl5ú§h…Y,×QÚiJ>?çʈü£JðTíP‘¹äŽbÀf-*J‡ÌÌópgÙ#&ûàUÙ û>»T¾yÇÂcIAcÙù&4 ½5Å’zÝc€!µzÞ&V09yÝÝ À" v’Â'õ$ŠŠ_ت´VH.ÀK‹Ÿ§û›2ä³/£qdsÍS‘¦cvJÆt÷YYTQ¶“eóÐÈý¦Ô¦sˆ—‡éu…ï5ІãéìÉs99k9îo›Æ >M§Uâ*F2¿¶®êWËRô¦Wx'ztÛì˜Í_Fùõ„mbzÚÀËrÛ;5›ŒÖ3Þ#ËÏ6¯Ù*¦4 hqùûÆTi猫Áà¾J!¿Z¢}B^íÄeî‡;_æ}RŠàNl"/*,¢žžôüÆë¨nµÓÄb#¥»G¶"ëqHNñ&]ý™¹*~´[žl?é¢çåáE/°E‹”þ7Û5·¥Â& Þ˜?Áb×#æÕ}åHQäÉ’]*Í÷?F0UÕ6˜ØÈÿI"Ô›d3£@í½àö–Àƒcuÿi~^G"Ô²£fÅtR0žrí˜lU¦ˆ#àâeZÿ“ŒqO¾¯fŠfñRYk0É,}~Έ¾™E7²Ãø¼‹kj†Á%WAØÏîñ+ƒ`¯)AˆÚ £<ˆ%!†ùœ½8ð5tÇËî]G—r¿?®&då·\Å&€›– KÊ¡¥5Ó >—%Îû«PäxQkyBC@ôCÛ“mÌëã:%ÓŸ‘‘M—üvŒfù&<8Þ;€ÈôÓa9׊×,i+P¢îí÷kâQuš¬—K g­$戴‘Fï}kÛ³‘3rr›ý)$ K¬Ä$åMMÚóœÿøµ0tq9v×2Ôæ4˜Í£ï’W6A  ¨Àˇ=n'|•e˜%òo¼‰e)xÝü­9EfÖùÆõç¶Ol‚ëÉgÜkÝOܬe+§U^è+§ÆC@J9?P%N4çÄkX™ÛÚ©\? ^Ï „#ç<ˆà¿‘ÇNdë rS8¶`sÝvð­-ÿl#ÂÙ^¬vŸê.‡."£sãV`¨×̓)ëf· 5¶(.?–ÀƒžƒŒÁå×QÞ>ôß>æª*&í;+³W>ý/÷<2lëÓYÈøë©zçû7èþ*¥©È @Bš^Sâ§hÙ‹àŒßG÷ÒÞèÌ|¿;Ýö³Ö²ŠËÓŸðŽ-·¿ö¨ì£ f›>-ÍÙÛàU¢7Ðñ‡“tPkÐÀ×7Æe\¾ \LN;>ÀÊR'ídíe¸*®õØZ<ñj ¯HÆ«Wœió¼<½¿I|dºUOĤP8JºæšFè(ë¡uH0^òbÇïÅzƒÙum{_3ìøk ¼©N{ù†ùÀë VR}vÂÍí¹jÆ úD*±‹, ìëÑUš¿JœõˆÉ¸—A`éQÖÏd <€Ná{f{¢Íx4©_Œyèwþz¨÷¥fÔ[ÃçÂFMûv„ÃYÃW0æPé›o‡QJV”™iÄœ87:3C-Њñ}oƒy^¦óŒ/ŠA§Ï.›8ÓíÆÄ;†^Ër„Ä” ¸bGŠÚ‘4È»¾5wG¾iíä–b‡&ü>ó˜ì><‹KýrÉc®v¶ž–.DÛa`“éÁó»Óí6ýÕVP¾]Q^E¡Lç¼]N¾rfYâsm¶.ùS.T«"cå}Ol40å0xÓu}Ì{Þ+º’þè(·Ë#´§©b6MÐÿ#ÿ†âYr®álŠç˜

    57#DVŠ)ÑT«ý;W$ñƪ´hCJÙPŒdâkl/€…ˣߴ*_›œólZ~TÞ_ôU´þÑõ˜O”¦,u©ùsƒ^H²“­áߎ”HD qŒ¢úHSpƒ"qnñYDV™‡\P†{q.þ_ÏÃÚ¶~Çø{9çJpv¬ð[H\ÊÄÎé=ËœŠÙO‰š¤dGÑ^ήÞÓD ;\©ÜÍÇS±âÒÐ *6˜ßSFØ¡P€¼zÓ×”ð£ôLTì¢ûp÷ÊV«VD†yyQÛ[‚U'iÎz si,‹TmÑœ‹p¿»ð'„£ì*’x‚¿ñx° øÓ„D÷§±ÎWìÁ e„/‹{veØäðÊîI 5^d½ÇД3±£@… J2æ¶õ ËÄ¡ÆÀw탌¼ûcx˜y?tÑ9Q1ÔÌÏ&í`Ráa““ϱ pú,Nsæ šTÐÜ>.«bnv¿Šoæö“þ`î®c5;©ÎYÙÞQ‰§PK„ÀtŽS+NÏÍÕœ#9Â_òr-üÎðĬ¬ÔÖAq·yPHUn)Qc&™1צ²0³»¬µAüa|®%B0(t¼ úL;?$£b³Ñãîñôªiî³qJI¢z‡TÎMäD¥Õ¿½ñð¬_qCãä“âïQ¢(>™?JƒëyÛb9<-Ò¶¡ðº¢;?nçðèÅþóùà¶&Ÿ§b…­Æú22c9'òþFåÝ×ò§.þŠ‚È·•ŠqËX+Óz‹«Xz)b—N;/ Ö—Ýišklë°ÅBé ݵï ãn $g7Hí1.¥Ú¨$úLk=•~²]f:á•% i.5‹¸âŒªYÎÐó<ñÚ [2iÓ‘½T 2fÉñJ㈺÷ámÂô´1óÒZHªY›ÐxëãÚ”É'tQjïòùgò ÆïŽTÄ´(ÍN…‚«œ{¥CñÂ[9ESÑ ùS\’x¬†BPYÐ)Äé¯ùEÕ'x/2jŸ¸Ô ü³kØpâœx|ÁËä Œö ±¯fS|s¿¾Ÿò¾ÓûB;{¾c¸¹ÏÐÁiÙkkź²œ¿6 ÒÑë]çØ ,îá\èÏTW(yèº;¯¥ÊÏ n-2¼ƒj3ÆœÄSbÓ,°:KÖ …¼cWULÖ4ç*¾_kœÚžä“uÙDx|•蕹ùå«ÿ/‹UŒs,y‹Úâäz9o!‡0I·”:§MŽ9óÅÛ{†_¿¨MÕ¥F&[¿†‡5º¸‚šÔ8ù©C®Â e$KºQò!`/¾pžêL|ÿa޶®MOÅàlÃ!ò¦j\µñrG€È…þ›ìôÇŠI¨e³ _u°,6ì[ “ç{#ÞUš™A‹¹ÛÉÞ¿ÛÜ{=6¯ºÝ×W–õðYO.N\q2«Dì!ÁV0Ȱ·¶Î_GízÞï— “Úˆâ—3ÕØÞ8Ä 4‘i‡dÖИofgáG8vé?Rsxb$ô'…@?Pq¡ÒOüļ Þ> Nûã¶®eUЂ:õ8mX‚iCt‡¼éCiŒþÙº‹ûA °``ùÄx@¡)ìË3l…oY+¼}ã["Ûð> ݆.<ÑR¹ˆP-Øžéq£ ð^Üï†%Ù,þŸð¶ôj°¯>‚}àzlL–£ìs™b4¬r’]ÒÅ„-Ъ0¿eØÎáÉs,• êGX­-\Ï~BkMrÚ% b•‹—ˆÇí˜# ’§­ôÝÙìý‚^„)…ÔW9ã‰amõA†I³ó³ø`-ĺ( zwCó̪PDL§"Aw·@—æX÷®V<º{=™9÷C粚[¿«#¼ÛÛŸWš±ð>X l:â·á'œ·a]†yK'^¶o.@£*¸-”k \–Ââhêr¾BÁC˜³¼þÀæ8Ý^ZõB@í‘F§ÝÎï ‚¾îƒLT¼oè+T+õ‘Á¿Ç³ l5Ÿ"¨ÄêÅ?p^ˆ)e·ÝN†² ±ði ãŒÉ&ˆSÙ<òúB{½¥Å7€;6xÝò¬?ngFû¯·× ŠùÇòº¬óV·<äÐ쑉-]Ý.•Y§­ × VÏ­fb!Aì¨gf ú OŠ=œPÙñßÕ“É׎êúŸ(O›[©°—Ï]¨—PÑI±ï¶L,Ñ*? ?Ö]Ywñ¡þþ¼DLzäf è>•Çñ8Z{^ÊëÖŸ²ÛWö>á}аŒ§eqUø-ššõ¨•óJƒ»{Íj6‰FL¶[†Ék×îò}¼V€Y hÄÓýŒOªý±1íz–-ã‚mgd%?¶—oþͯÎûäÖõ˜¢H¡=SîÈ¢rŽq°Œº²s‘ÑŒ•ìdÁÒ͇ô±ßÔ c»°I£"°ôeÜ$U%,f3?Ûvó€…å,°ë\mYG’¦HLõ¡1ŠÇV¥ì/¶ü½>q–r~h,3ç-oÙo—È\DÌ…«%¾”B›]j¿0¸ ™0pcN¢|Içm+;qÙ>Ž=€–Í–17iÁ±b[4ÃÚ±…ܾÉ飊ÔrÆó¨˜Q8¥›¨Ù iï6˜#à¶öð|(‘r¨l%p&ðéèVÜÀ¬ÈcJ)NèwE~ 1›Ü¯‘ªÿ3ËÃaÊÐ/=ý[²ofFa„™ úê¶Îâ mžÂ¯°qѲ™?bŠ’pÿR:üÔm¢¤ajꜟ‰mÌ+…2Òùᮎ!˜ÁÑꈇSÎç ³î¡¨HΑíD‘ÊÜnÙp:Zi‰ÏÒÒ뿜øžmÕLlä{âÌ~9~-+^6CxD½tŠqjQˆv3&º X¼5âEb'¸;š'DÅ¡‘@—¡”!öäª8 Æ£žá_:«a·¨8ø‘¥m ”'Œ1›#€ƒüƒ ünô »%è<âbrQŽlCk⾈"ñú´¬I¤ä½Î†ÚysZèá.g´0+²–\<[5Hd—‡qñ[À{ü‘Ǒǒûº'G ·@€¡âú=˜Ó‰K8å̈ö1Q)ÅÙK±Kä±L@Æ{%ô²¶ÖÈü´ëàO Öç"ê+Í”UIþ áw ùre—p!JY‘V;$Ú:tó '·Qb-’Ϻ+‹gû—Z5¸Ñ’¥…•Œ<<¹ý}'vçŽhìî\v¬^-nš wÆ7gƒ‚ñ¡j÷f¢ƒ@¦Xc $PƒÛËAeL.'õ:3SJön¾?Î=œÕ{Žt öPŠ[Cb$ˆt:Sž"‹pÀI=â6âèã;Ô)³Ù®4•8k>yw ×é$45*`È»üš5eaÀÆüæ¼ê—¦nèrÀé<Æ!™^Ž/o K˜üòÊ˺«T4VWŸ™²ñe/?D)˜|BR8l$I²{Î[L»`’o³Î*¤|¦Î„?¬àzâ[æ¶~å;æJÏ_ST.Wkhš®Ùbß Èá ®! d25¹¾xO¢édµcΔö  ˆW3ûØb½Æª'H¾HÕe'¼ÈL—þU¶þº—8” µwÔ›"ÑïáÔͳÂLÁ‡—é|y”ëÅO¼r©\²X„ Ù&Xc™zâáæ©„°ÞVα=½*ºØvørŠ “{´%%Ó /œ‡ƒ›9¡ 4Ü$•zRåJQ O·½Ã’(´sCU}HÖü%¯ðnv>ãˆÈe#9?ï-×—3²4vá@‚+ºçR­ßU±=CJÕ4$ްöiwõ˜™¼@¯Sµ5¡R§¾³ýÀ»ZÄÓÊô´ŠÆã&•þn;7ZÅ÷ezRY“9ͦƒêkžv=Ó+"èe Ùžöë*7«5iÊp‡’|<&=PšÎædÑ!¶2cZ X‹^$¶’ñãåJy¨2ÑÚkm9{߸ß)ÐÛ?Pt#™¸0ÕaKdwY½â ¡¬Ï³bøìÿg¼µÁàÁœ‘¦ÚêRNè¦md›I-¦3|²k ˜KÉD70Îv}¬Ã·á^šY2Þn 0BE³è$Ùo¥ÖÁp”ÏaðÏY'aå›ôo£vDÊ!¨0Â#Åð‘ÕQYÇR"›íCñKˆ@£ +ç³ûòF»€NÜ•c“eGƒ“§Ä$§%ànå; L¤ŸRÊ(ü/iÞ‚·©6 XúÎ':Q^ªÓ“`¡xwéTpbªs®U&Þ-ØäÅìõ0í.Ëä㊜J‡Üæ@•Ö¨£¶õ¢w‡ydãìWyþ7x<—ýDäòŠÓï“MuH¶Îê ˈ£æ*_.º;ôšE ÓÕ˜³¼m"É¿{÷Èîœd—7œqn½¯¨8ë4Fw]›Kïª 6ÞØñ8 Ùwl{Q~éJeCçó†*eFR8Ü—ü6ŽßªÛs»NÄt ;u8M -G<ÈœiîFNØýtE˜{gê=ÿ n¢’WznˆA$/”o-¨‰RøÃ?dqL©ø«Oe­FBÐCUP{¹ ïÏ‚:^–1x,Äw¡v%vp¿¨Ø™;ÔŒ­z{o€Ý®l{oƒÚÛ‹Ë;ÞYxcçÛJ¤÷â>×ÉãÕ8)[C'ÓoÄ#$·-6³ðˆ}K„§>û|HZ!ª×®<¶»cØWo ûz…6Xé»ã*'zK"eµ¤ƒ@åz—=4"×L=$ºƒÖ ±ì£#»âº¨Už_ó;<²†ò«gCûLÒ´™Q9¦>äú©ß¼¼ x+ö»ËyÕüZμ۶†G çÃVëäõ4Î<ôÿC—{z K- )¾;ç^¡9“éƒçëèZúùBvgŽ‹ ŽÆ%]Ðú)³[çɨBˆBœýu9Øø?)—ÑÐð.d«e4î0+pÚ˜WÇ32Mr­.ùµ<™@tN&fáòËõ*Kêë+xÜÝÝi¸„–ý„*³ŒØ ô3åÙô ÈŠ7s7ÀrOuïgz/‹×ý…•ãÆ/<˜Ì9h‚] 'Jª¨>“ϲ¥ >‰"L8g–P…Ûä¶QÈ2­‰Å×Ýþctç€Ì•‘„ÅMc3ߤÌoÂó ×>ŸYLV0àÙÐ)¿y¼—2ØäñäIÛž¨‹äd]+®ŸMÌ¥±þþû_CÉ^ „|Ògy#…~Ç %ƒWŒÐ¯•±Æ¿zè~mDV'i%D¯+£=V‘hÑÉyþà|Á±|Z˜€…µ¶ÑR¦{ï ‰Œm¦7 ­nûC¢u?âtÎßÔî¡oX>ÈsÝ¥…n™|rKMÂP)÷ÄïÔ—åÅ~¨ÔkðæÚZoBD2Rˆ)hIØÊüx²¡¸Qâ·Ò_Ç”;Mã}]ö©¸ ‚èo˜þ ÁpÐ}.Ì7ÿa«Ñ!ÀWÈJÁ• ç’W"Ê#®ç–õ¬¶s·‡‡£¿•)Š$vsÞTi¢ÎÖ?)·®’Oœ,¯ñ„®ˆ¥G7¸­àî•s;’±’›»L‘ë ýNDÑ(†n!Ò ˆ¼ü5SvýjÇu¾•–õ§S&¼(„»A…ã9ÿÕ3ån£PÂlÏ[Tµ"ˆÌU:ÌÎÒ;Ç e%‚@?•WßúMùüš ý¬6?sr¾ÌÓ쇠€›ZolS®î¡qf:_&å†YLÅR»çØÔB2“mŒw¡ìï­Ä5[-–Š æ0{@_0(tåóÁWô=[ÆT(ïªU¶Öy5º\m#ò>bª‰–ÎèøÓRzq…ʸ]4–w.§¦:3eN…@ÆIʨã„á/} )Ä%WË­ƒèŸH©GL«Nrz¹bJ;M»SÞ ¯LrªÅûzNú$l\>Fó1mqªÏÁ"p&ŽWûŽ «~99vxñsCtiE¼,×âÛuôœ°Ù%ÒG0iY®Ái ÿvÍ(€ œ1ÝÛä¸i~O"P¾4—➟…Ôøtñ &l·_Ç2awË1Á†„lwND_Q#œ³%ÑtV¢Þ÷–¼píÞm‡ÖË/ð­±u±ukTVÈꈤ«‡V;g±Í|@sÏ<UÕÁàϾТ¿ h¹(Ëú=¸¼¯Uߘ0áòÏF-å ¯.½æç•ëÈß2"5|.EÐÍÿ‚f¸AÍÐùzү̒K ϽƒÖ Ò+ì“vïX™V§÷½œ" •lOþEb#Áz}±3érÿò²=ëR;ÖQî&45å¨ßYøIw#(ˆ&êðÍ P6,«´×øDÒ_éʱ Ñ•c¦v¾ù –™S;;¹+²ï+7—aw«—¥dá ¯k÷EºŸÿJ25DÀIQg­$¶O ã"~1ÉTItµ1S"áÖnîQnxÆ9C-Œ ´Ì××bȬ!aç1¹6ÏÃæVwΦ²Ô‹DäXjv¥Ì/–S/5c¬b Z†BDnò³Úº¬ qWÒâð‘º: 0qyJßÀ¹Ó@"송xu wéQYdy‡“€çÄ?˜/÷‘ŸÔl^žÜï¤ëña0 T{›55‚óñ™ñbX‚ô¯$ÔœE¢vDLSz%^q¸† ¿#müÉCqK‰,N{IðÍb¾á;.(rUN,fƒù˜ñ<õÙø-Åõ\=¦õ?dzQŪ³ç¹ŽG±gâh0èüâÞÜ{Àbß2YŸ€ýd›C¥x7x4R Ö¢Í >Ä„ŠÀ ·ÎÒQ·oó/áí|êJ)U8Ð]˜šù;GF° R~dn³¾ŠNÔ•‚Šý¬¬“EŸ ¼Qk*—Q7gó`†~«-V(¼ÄfîÒÑÆ{Mãßpô k ˜0ºß–5þ`æ5Õ¾†ëno!Ù ;•»VÁ¾Çÿ ­:ë©Îáûq ¦­šR^³Î/^äòŠÄ Õve8Il*U‰x$÷jì}+pÚSYŽG‹+Tš%|k«ä,…áuˆTÂꘃ—‚òI“„Ó¦2æN%ðLFÿL•MÀÌd9Æ„snoI(†‹  ¾ã°ï8ãˆÜ•”32I¸Â•mÛVwm ƒŒ¦?%l®/lçþÏßß3Œ;ªoÛ sPÙÈ“9V˜>ÈQ L(ÓºDžÔŒÞŽ©ÿ7o·¥ÈÖ)Uœ²|^¢r¬¬äm$Ñíþ¾N,LYÙˆê0:‹Þ°å¹ûòbJñðÅ~æÛmG÷ž,ƒ,Îíf–ôí¥a}a“Ù=)hì>¼ÎŒMjÓÃfƒ‹,‰àRÙ$¶õ{lÑ'ÌNÌeWMgL,nÜ ¼Z•ÛTHûY) «­$DšP'&„Ÿæº±$懗Í!¯°ÿL™xÇ4}|oÎ4B1T)È%àÿ=sµ¨ÏVjK”ò«9DìËpr ôàKV³/¢›¬ÃÊ &@²@Tÿw£aN\ìI{{¼Ê&ª‡=Yy˜™r ,Cî8Z& x8,*d2xìÕM¬ƒ‡.á!ð¶1,ƒÌ%¾ÇÍêËj7§¸ôN*óê5Þ,fQ—Hãn V»ªObž}Y…ï៶¾NÖ¯«ÐÏœm lŽ ^*®Î(ÑkÌÐ)±…þŸM ùi3GßÊZó'7⵿žª‘ñé+XQÄîÌÄʤUÌ´½‡ÕÊFö? âù¬‰)`v\6p ×ñ±ì%›M¾}¥ÛZ™Rj,pgçr’—6Ù×P1úJ¶ùÊ¿e<³ÀMÓbë¡U » Cݼ¦DÁÀû€ ¶ÂUo®ZÆ©¹ "ž  •üƒúo­Ó* Fawæÿkº"j6·aÍÇÍ!ò±F«“T¾j[X;“Iqñ½¨†÷ÁÞº>¥á¨» ÜJ[™pôöÆÿt <-ÆÖzéœ ´ksJ³¦¬•²wñzÐÐd ”ê7«{)%ŸÓ,t¡)çFê­,—Ÿ¯$­ÅžÿZ!rzƒéN~@… «¤Q8µ®;Ÿ­=›Ϧåöú¨_¿áê‹ü=crû}9¿‡°¯ðõþ«¸¾ßQ/áë}ü=L‡«xàBÀª€NV¬1ˆù™D×ê°20z›sƒ4›mr+Àf·XJd_á»ÊzÃóNJPýô±€XdíVíC¾í£ŽÁ_ñ$9þ)ÿ14‚­®$y¾ÒÝ_Ày“Kƒª.˜1…¼×Xæ§4‘°GÍ“l/ÌÈ;©„ÑÜ-šý† ÆÚ±É •yYäÉ.ÑÓŸÂ G$¢šÿzp3÷šÑå Û/‡V¤@–,|ÆØQR§aó¦ó¢rKb„Ó:j%ÎXAÁ†/ô9[RÊlƒ@ÍK"i¥¡Ç&`­“樾—i(‚ub>¹ø‚Öº‚kýNšÝ Ø/yæÄuƒí×é;úmä’+¼©:]8P*XàŠÙÿW^Ç~ü!¿Ö‹–Tæñè'³¿o®:€Ã‡Â»*´‡÷H¦h†_ û`Ü}DhÁžQÀxɱëN÷ç ÍWdÎožCȼ{0 NÐð9եƿÔB`ô‚·;CÙîå ‹¢)UÕuàÆÆPÎym¬¡ŒF¬§‹Ó$Q ãÓ•“'Žic›‡X $GB<ô;8Œ íKÆâÏ™TmjŠëB ó›¢©üccñ0D ãçƒÓõô¾³¤‹&ÌÙ‚#A@‹Ɉ3ˆyïýµéévÚE)ur›}rëç"Œºó ‰ÒÉ£Z“_U¼º†B_åjÛÈ¡=1å0F¸oô‹þæ6f]êsTÚTUÒ™Ÿ~0a.êù{‹÷±‘`[,B.1ϰ–Ýç‹3_9Uˆ Îé.gÍC)yà²UEÇÿDS Ôi‹‘5Û.ñqôü䦜£ÑÂò=`elÜÐë3Hm{²üJ ´P;¯•Ää{AF«cB–‰âcw¤ÜÂÎ8T—ƒaj+fÒÖOæP/Çè(ræžìAW‡GuòÈT>bö~£ä )Zw>&Ž,‡‚FßÄ“@öà À ?xÜr½C(4 YÒ´XŸƒð€èøMÚ‡—¨ØúåHöPRôáht¢±‰(ͬR;µAoME|âºGk\DÇsÇÖÕ1 sñF`ãM‰},ËÝ×»ê÷rƒÆ+oo¿©ïCt ÷È‹hR㘎ÞY°” Ç÷ˆ`„°™U\H‹kr…d˜VÚâ¶RÃêmW„R ªß& ¿¯ äßžŸ&ë³^°w2ímBn[Dç5ø µi¥ÿ\+âÐerwPŸýxçW’|s\z%ß%B“ýýÓîÂÿ2‡˜îÑ[GÞÓ~1µƒiÜùSòyö†uÈ=Š „ñýq9 ¿õ‹þ¯Î4‘?ιӳ~ª­ö%‰uzxŽñôÀFš´=­„þk‘½¿ bý¶` C_†×]È]þÈNSØe‚É;3€„ž‰!H·>SÐãÔð„ÖF|qׂ’ŒÄ壿ºWîÆáÍuWO7+¢v¬¸ðR¨P—aUWÞÈ ü‹<²ƒJË>;†©(¹ ‚'Ë¿Žd6…³ÆÅ3>Wª^‹¾ý%›à®Š õ«Œë®2m0^Ë`;©ãn¡a˜è⻾-kŠ-Ê‘¶@Éwã~ÅÚ+Òz.e˜)í7iŠ/“U½hbðuÈß¾cç)Rø$¦ê–Ø,Ó™Šlú© yÀ ã>$)åÙf«f“(†©Ü {Ân¢ŠÈ•’1 µL6@ [ÿc¤D̈½g~vì™[a‹íËá²w*R Ãj° ¸ [6”ÉÞôÙ½Ò–j MÊà¼ÒõY2Œ oELÌoÞÛ kZÕÛ ŒÚž|¬/ä‡#úcc ‚µäeð„_¯ý«ìŒ#äÃW‰ÛRÜã&² Ú­´/u~z^/ëÂÁBNÀ2Žƒêqˆè-a®Ö)‚º°í±¯+Eu¦,*wÖ8]ñ÷ J¥+<ѥ½¢ç§µY»°Ü¾ c$|(f¬èNùêJ”Û¡«t¼ º-9 "S˜rÁs€ú¶¡^ÑÆP†¥äO0P-hl§™õÇ"aOœÐÍ™š“þõ~αÝò¡(?RC4|¨0}.ÿ‹¶s8„x÷͈»®1ðh¼¥ ™ª\|#"ö–†Èˆæ5‹_˜l‰{„×(¤³µþ\Þ—ãu=ûßÙ‡y8ço5ÏT`ŸïâC9TÛ[ u]ª-:rœxRÏXžŒ¢;šZuçÖTi_ΚÔ(‚A>£ö4@Yˆ3%öôÜIGb"䢄––9þâHÖï–P¾¼WÛ¸ÂGÚU÷¥w»çe«&£7~ÿYy­ïMwÔýs°ÃD¬Ý–¼©Š;þ&ÉÕ.ñ2‰›¬§ºõNß°!‚ÓåÏÙ»¾ð3"W3úÓi[édlIB¢Âû¶w·á GÛ#UéOp¢G† ç[*È=#šV­UÒ8 7»2nž…¶K>$¼OŒr{þªBRý݈À{3@ŸG†hð¸LI¯&^<¢âû>*âÒ6¹ ¶5“ŒGÜë!D™X-®~üUiÔã.Ow÷:D˜%*X©…„³Xßf8?Ý8’˜üÓ)FÛùfmY:úP<éì„ÿ.»Õ¶ßƒpúÅÌ„t/³ž¦Plç==ׯ_²êªQWö¡âïÖïðZYsúyèÿ\~be2QŸIŠ |M㮎T«h”ÒJ,Âfbž+d÷?ï[j¿G›Ž)èªÒ.†ôÀ÷ú=@.«‡4½ æ>Ãï¿ð’Ì68ÿ PÁ_Ø@Þ™W¼yä^‚°Üñ’¶ªbûk»:1þÍ‘B# $8X¬<÷ðÇŠ®—vVý %Šò.Rª|DÆÄÀ¥ 1‹õïuÖ¯røãêm/m¦•Å›bD6 ÒçÿU¹æÁõfæC­$g½ ÆP¸b€’÷j%?Ñ«*{¸¥|Æ“ñ~'j ¦–;6G‘GËTzÇOîïÁº¼÷2 /pO¡ÂÁ( [x2Ìr| ¬NGRòé>Wxiœ¯íý-cq=œâªªæ'l$<ž}ÑëᛇÂ"•ÿBVÉV rÊ¥†©vƒKÝä0}ÂrÙì°·‚£* àæôž1§Ý8ߨTç ¾á”¯¦AêTã;å»™;ù:„Êvå‚t;Á ÁìJ½{!Öª;làù’¯iR‰8àS®ïLv)'#éÌÅ•)¢@ôkåÀ%ÂàØÍËIŠ@æOVÜŽ€ìRÜžП}Ÿµ]iM–¹[œÇ^è—ÔØ `]89eAp@4c…›ð¹‚7ÐÄÐì ×òiÜh(ôÊISâás2¾Ð`æö^‡·äß.qEȘ7w&H“‚ï1¯ƒGÿT a»ã¤m¦g=2ɉú„¾d-•ÒLÚ‚ÕRj¸Ž¯-*bú_C ì"üõ®FÖ6'%« üI´žÿ¨—f CÈ t„ã÷7ñÿ/teÐ2<ì븚Û]ÿaÇ/öPbOóDÄØ»u˜½„ýdÔ,Mq¯ý Óäìž‘/´Ú( ÙHÌš~ËžÝKÏúNHæ×UæñNleà÷#²*é¶+üb 9åõ~5^Ñeþœá¨î¡þR0O¸]¶:ËB´A#ÅŒfZn5µ¨ÎÄuù ºÙ¼·r ›Œõ¨È][âÄ´ß¿$€N梮]´æé{àU#o#¹¸‹Ús÷ï£0I‰÷³Õï<5.÷’º“c®cPµ„p²uÌ4’H†Ü’n51K &éÏÖ»,pqÍÓë´äÜ<²`–IÍ!„žïÈ…Û€f!j.•,ÞµL½¾ 1¨=$¿÷×n&›L¶J Ž$þ'“ Œókå/–I]s2°j¸eÒé°â1J1y:p1z3µØEÛ…÷e»Hø¡3d"n§¢¥$(Ç…£‹?ª%’ìÂAñB›Ð±¨ 'ô7Åù_®ægvH!Ãû¸E |Ý;:äOÚùHçh„cçÉ<ÂTˆ‹ö­›p§„Kn9¸ËXMö;—ñ÷ÔÊW<‚%ö5þw½ ÜÙßäRͼ¹ácÄToÕþnb¿Á_”}iªÞgÌøõ R°äTÈDÜpÝ­'©ìÍÁ¼Å.Ò–ÆÅ‘ØŠj.U<ËŸMXÀ¾±ãù0ò¥ïRg†6e¬Õ1ê6æÊJ Èó—ꄚGq7(é¨$F”L—jöFPû¡í¿z©´›“)ÿT§ •^¸hœÓ„•Ìæ\.­ð‚ˆ{Š·,žè$Õc,Œ\³ð”VÛ‰òŸë gNƒ‚¿)÷‹Ìøýå—r8îEù¦¨aašo]VÚ1⨢ŽIðîÁ?>a.i8ú™tYm&ôøb¬g†(\³ôÙ³âmK¨ãJhE¬!kÕ;-&±|ÖB ¦M¸IŸ÷u6œ¢aXh$âµèÌ®ºØrñÃvJ÷»"þøZ¢äÜÊ›¸³Ú”Vø=@Ž…6 `ÖÅqX:0¸èiWÝoicÑF—«Ôo? _£¾d~^æÞežöÉ ?gí.nG" 2ºw©¶Š¸gd£Æ< W yŽ@â’]ZZ w…ï69#Û`„8ReЫ0ØËWmŸQWÄ"¯Þ«îa¸«x©\—”´ävÃí8kûœ~‹X‡¤Óñ£”{oÁžSŠ2âyÑ)SÔÜœwT‰-/ߟ×ÓÙ9Wã´ÿ.7ü¹I¿Œ‹·õ8Û7 ö}îv=kưS浿·,ÍVЇ˜ã/•µÙšÀ7õ—jgcÚ„©n}Äñ« ¹O„‹Ö×KUó’CCçìQwO2RËóSý/ô |ƒ`zúÇ”ëÜ£¦.¯P4bŽ“Sëê^ɼŸÞ}:ê ÒC.ÄóðÛê:¤:]ý¼pݘV í™%"\—=uK7Ö¦/37ÝÜ#ÐóÑ ›s©Âßy%­Ñw6Ú¯BÆŸ¾þ‘zbxi¬ÒGu£Ìù³Ü @°¡«šÐòiÏú».Ó¬pQùÄ;ry³#o˜»:ôXá÷&nÂ[ù«Š" ‰'$òp‡Úã¶X˜ÏfÇZxŸi×D6$Øyá]¸šqEd£î~Z¹ŸžÒÔÊ¿0m ÚÇ\zØ£Ëð­jr,4÷¾`ÝWh".K…§4—ïøÒ£ëê `^Û¯Ësfò@„ ¶¶†UY ÆlßÒÖ¸ñˆ½´ÞP©‘ÿC -Z2² ¢>¾ Ø›ƒ ±[±;fç'† z²ylËÌËc÷M”|ÆpW|kVØøÛbuåEDþš3¬¸Ù¦-2O&"?×ÇñÈ(ù§Y*äºâ YŽÕõ¨†{˜S'²¦B+ @zãG˜ÔZ<Ò" ?þàhÛNi¢5î’ð!wƬªÃ.ó'fge2޽¾Uœ`ˆêàCNZF´”¬úN{ïBÃ7C€R 0Ô²Ç~×j7—{oΖ ¾¤õ¾’Õ½ó`G­¢)ù7*ua© ŸhO¡›ªXxÇ®9ËÛå£èÕÎó›á9gH 8|d”¡³\ÍÈ&Ÿ±úÕïfî}q<ˆemÛ‘­c$ñIij󿵙Ěö¨häiíïI –ø1î6èB„LøÊÅÌ-8²Q8ñç„æ¢—zÃ"1æ¿…¦Ü('Vü4a•™ ÷Ä1åX#Ïé¸hé!Éñ$aL6†LÒ"a²3íC¿bê<²,¥àXZ¶{ß@ã®Sûô®7‰Ù¨•ɘ“Ô&ÖžÒ®AM}Ú-èyo´•(*™ÇÛuñ¸.¹Õc^K{7:8m_þö”z8ï“nq¤0™+ÁŒ©Úî@»/ZÙUðÛ+]þËEwÓ@)ŸdþÆgªÝMOºaķέfЄ‹¿QGõ‰¢Àuó9ÆÒN-š¦š_€ú|¿‡]ˆ\”žhFa¿­ã‚ÂÇü|È…±fnµÛìv¤œm-•¢¡Óݾ‰^6”„¸é¢dÓTlÑÝð8 ÀÌÀ­75Z·s­7$˜Œn­ôcß&Ö~Bðƒ+ÇLð] U]'¸7ãõã%Ît84ï}^‹•˜ÂÀ1Ѧl“þ4õdi}‡q @ÒYÞ%Ñ]&Û‹WÜ…õJ™!›ßŽ~“'¯âtðñûíý¢l\Õt u DäX½Þìœ4i69›|Elv¦¶ú‹¹’ÎàY9Žd( ·ÅJï¡ï¡ A5ŒÐÅLŽí'£Àðqá§8À>~!Ïbt^¥¾ŽkÔìÍ:¦$çþWÇ‘Ô5]:Þ Ò7‘Û΋”}’ïJqé] fæ }’_)Låe.ò'€²f ¨}¿\±ªÁÑ3XõíT.ý2pjðp7²‹.Q®-‹_#+mŠƒùšÀÓ3£Æ§ý’ê´ùÙÅ\ 0ÆVðµ©RC„r øaç7ë±÷1CõºÁ¥’$["`²ŒØ9-¸…;¼žHB@r´ÍêbAözr2€6²ó® ³$Wx‡£L"G‚ˆ,* þLḈ¢?ˆÉ­ë$ ¾|´9σ$”ªÐA¦jã÷Ó÷øb5IPÛV—ÚM©m}¨¦íÈšvØg­ûU)Ÿbö´8Ósiµ™Ua(â´¿Ç%šüs8  ÌÛ#™À¡d©1-É$æ5‡,mü‘8AoŸäFexnî1ÒÝæw-DJo=€.÷ÉfÞ0-ºûl)>ä`dœµT +N‚³o޹™G€ju6WÌ·’žÅ7ê»*Â"ŒŽ‰½ã )a—òÚùQ1çòHqT‘ºL(Uv#˜æ2™9êH’Ò‰JV¯_׃¡koLà±²ÑÓæí5΃óâÛ°W)¶Ïú]»°<ÎVsÔoéOæiÀÑ;ø'b«TWl ñÌ´wœÄÀ3½'ð[T÷3&ÇÑCÉQ2ðl¬((”nï5X ö©gJvr² Ë~ ýÇDÚ ÷íN¨NeÓÇ Êj0=ó°ÆoÛ¦ô6õœ¤&ÕõÕ|´úò$‚}ëU¬ÿyɘÊʸë-™cþ /´@Ðâ†^î· Z”ŸcØ ´9 Z!YÞu¢œL¿Òjéøñú«#gc«Œï/)|9Õ´oÐ (fã7™±Í¨^,’û>­æ©ö–{h.¸ÖáM­omhC}Yfw@ìü¨:d¢`ÞÏf¼Éü†_SúJŽ“ÏÇG„]zˆZ(!òž_3­‘Žâ Ò‘Ò y,‚ØôÏ´B*Å~Žý.6½`Fuþe‚„¬ŒêrÒ€Ë?žìjxÌö¼>nt½²ÇŽåƒ¢= ¯”„ºóg]k‰žù•Ìœ%~Ok1dYá÷ ¾ð;¼Hx<²€f—¡ß¢õ~U•%’A$-p‹dO «BÖÀ–`8Z²ý«º†—K|W6üÈ ežI|ܶLumÅÔÛ¦Ý] ×ÇémO9« ‘Ƴ€SvÆ4n /ج4Ùíûïí Ù¨n!MäPå\.-žî¾™ ¡F_æY„S‹3[Ø\Eý °pÈ ^:ˆ¾Q ×7îGÝ×ãg§¾è¼¿¥]?‰Î~âKr%Q®ñ¯[8U —W!H[½þëé]±mi¤*žE¿?h @¾J÷•JVÚ¢A/Õy@JŽ<_³t»¨Ó‡Fôiê%,ˆ®Üì ÍNQX÷Ì™9nWtÈ­BlÂr(ey¯$D ,¯¸U3°ÖW;ƒÞvåÐuV‚KwA¹ô@uYÎÜ (ÿ@Ÿ¶qI¨á1¼ãUž?„4µ['{{P.܈¶¯Üœ•ÈÝi™ô¬M\ñþo•jêW3R¾#MBÙÇe­l‚YÏmÓ’]ŽCС—æqT dÄWkêÆ5ä× 7꯸šÒBBFÌy±ä š þhõ\äªù„xfM|®ïÌwÃv+œ¸tlÊlâ\vXÔÄþàPõR†ºvGf”óÓdsʱþ©8 ˱Cd)!`t£Ó Õ·Õ¶i †žìyd—¾œÖi²‰½Ú)Ì3Êd00êľ¸N‹4¶—ŸNYN'*dQõð=+Öó.Ó'›øÍƒKblªÌÎç§ mà³ý?ý§ïOh̘oip}HgÙlêL™[êöI”:t¾½,œ¯[Âúo°†h¨myÌc¯^¾.ÐYÁ¨Dæ±ÝŸ.n´¶WkÒF ÂÇú‘Oç¿çÚUDk?hÌź¸¶À°?4SoàòøR'¸gòâí¯JÔMN¬UT†¶›“¡ôµñÊäêFh­74âª?ž¿ ‚p«Ü¿Ñr=°»2Y¿q0bš)”¸wÅÔD wÙ·™’@&vÖð`µ3\½~2VŽ([îfäÙ\}à˜¹hŒ@¾@¥Xµl¡°ø3N1*š]2ê>š)X¦[³x=á¯0·8»MÜÏ].”9ÇäÁŸ‚S߀³-kNπʇ'ØB‹ÉeÎùÈßÎüáZm‹ÐC&û0*Á±QÞðDÅæJ^š=lg‘¬R^˜tˆíÅÎ]{‡sy³wœ¬ 5´5º"³§åƒ'ùx)z/tçËS¬ÈáËIÁ}Å#  …v‘”ã|HÓ ‹P›°ÒöB„#b½ÎO U³Ó1NÀXr§ÀpÆÉ„V–53~*pÑ1-ÉÊ’IqÛ14ÅäRfÐnš.od®Ý€tÉžP®ÌÿÛ‹=ñЈÂKÆÊܘ ­Æë³ ´IëßEéªm*Ýeì}$Ÿ÷‡E×ê¨R#µ× £ˆ.}„ÙŠtÝç{H¦ÙM5œðã9DÔ¾î;ȳ1nY¹ßíÎñšŒ)Ì(iî¯8àÂã€a#¬4<ÆôHøè…)NW›ùÐ{§˜/:ªÊÜeXEÇyž}¢ÏŽmrQR¥¿$’Ø8~‘¯\²¹±vw¾]~Ãâ½ó’«çñTáBœÙ‰…žŸ©\ÂC"÷Ó¥*h/Ò'ïßÛå»iñάCÛIHne¹r¬`üÁØ|”(Þ^’EmNŽžïSy¬Èu7mØÐåÝAäØ¿'Ë–¼Wu‹‰Ï$½¸Y‰[Zù^0ÖÔEs…W7ƒøF’ì®v¶ÊÛxwé]ðtñš?O,pR£DÍs‘šƒw.â\ÇrReáAPÎù±úfÉR.È?Ý“¯3_yP¥¿=ÈÈàÆp€Ú#Ïë¶øL¡ÚÜûý\¿ãÒ-PþÇW&?t¤ Êm¡Z@ðs4ÌÕ™vݨu¬„…È%媳+ŸôOq³ðßG`÷¢CÄßg_m+úù#2 ùç Ÿ,LsJ.y+i—j* ˜»“KàðS„ÄÝÅÆBÉŽ¼R›{*¸‚A»óžf/95Ôm:= ˜'|ņÊìÞ8úÛ¡¨e¿Î€©|ÑÆÌÈV}æÅ*ª¤¬¸tÄ—/›]&ƒfqÝ_¯ùf¤”½ÆŠb6Ç·)W0Ãø¡$˜”Åž;¯\sÓÀ¡Am‚XXkÖ´ ¢8qDÆt—G"c:’®1®ÒI€æÕ³ä ‘Á]HfŸiO‰Üv)Nã²ìWäo­NŒÏ?ÊØà­ È5%ByST"êÌVDÉÒ⪱ [Ý ;М™RøsØt„àW£)(ržr)Vá…s#œÿXíšOßP?åI§çåÈs±'ƺsÐQúÝ;·|¶uœYët—ÉÖZ:A·v?ö–êû ºë¾ÿmy9%Ü5‹>£û#å!|SÚf4ÕuåJEÒ*˜¤+Y>?Áé¼nÜi- àhêsNÂéF-ÊZªë¤§¼<ÉÎx{­Šô¶Q“Ÿº#Oòޝœzø’–¢9zº’TWóaeºØpážJYmë÷‰³DÆ”EÞ<Ù‚ýUØd‰pˆ'ÓÎ{¢‘k¸UðÖh|ÃO†!|þZ¶Ð„Øu{ù꼎W’çƒIDÖy€Ïá•¥º6 47Kz-6þºi+-ºDãŽä†Õê†o#ã~ožŽ …ôõG«È¾»®ï2¾gòBvµÊ ¥jnGE{VËè"ýŽ ¬øõˆÈ${Æ Î–­ '£‡Àp1÷ÄÉ]ƆÙ9 ?V¬w©¢gY{籯ž»þ †u¯q×⸵dIÔåØÏQK´Óy&ñåL¦¹3Lú/Ù+i?U(°µ4™§î‚…6ÜŒ•ÄMV@5Ô7é‰Z¡ÃþöiáŒ,6¼½A¯ßPðý…§à‰s)Âä"'`ëõ}®Í¸ Yí^)§8úQ Å;A‡“ãø.+jÈfuÔÛ¥»œ1~÷/£ûä—–>í4 7»¶xÃÆà Ìíä%:ΠÀM+ˆ¹‡~ó»e¬z”ÐÌ* (9ónUçm5Õ+ÚÂ}0i¾ jj[ÄŒt Ó~;F¦²}þ2»ní[¢lø„(zä9Ý‹ž-²O€¢t_ÌqF;‰ u#?ÍØ‡"f=Faáä¸X¦±Dœ®+CÒŒzй-@ȃHh$–j~ìø~5îͳ-Æz%f•&¡ÑV_õÇiée}ä‹w ÊI_ÞZúºù ”\{ýÕ@¦üPˆiFG´†Y ~Á4صs–M {a=xÂÒaþ= ÕŠXGÞcúìoÙðò)+cØ5Û²UEX4LÞ]¦6é‹QÓ´½‘ú|Ø$Ïÿ{÷¯ç–ÏçZš4§ká ÷€õŸVˆµÿU¡°@ñ3é°àà²N}RH( {MK”ÓhFÊÜž,"òÀ›ã ½SÏ8¹¬j %K¨¿²ðÙÃF‰e„U;nYÿ›¸y<[oø27xé^¯‡á^óô,^S¨”¥½™(r§Û rÙÇIÃð@þ}‘ëãØ¨ØÙÇÅ9vJeúTH ^Uì|ïhR=¸Ó|¡ ½ùƒ>1“ÒZ&Ò¢Ñ'v§w(jSÐÃÀÇ1IÒ2aJ·hOck˜3l.)÷­”Æba »¬øê·ÐܳŒ±k£¼Îš‡Ï-6ä2ƒ§÷nãPÊq[²djY¹FÔñZ)EÅâúÁë0 O{ÒPÏ’ žy ZÄøë³j§~o¥ä•¶|˜Üµ:è­mDwºZÚð¤‚0‹ ÍÌñt¼®¬ÜXvéC9ð-f…Ôv™BE#kk?$‰%/ š5Ž=r¨á¥ôƒ[5iÅý3ÏŒA¿ÜzòöhÖVY‘…ðõƒ¹eN¸¼Ø&'ûœ'{õËäÙí´‚”iÐÉõffjnî$ggYA-V!Cðùÿ1Ö¼™åδél ,} Åík£ª™{á¤_fõh7ßç ƒÇ#Äo¦iõ7õ«ûþ{™LmŽsŽY첫õ ~/óùE½ÿóöí?‰ŠÄaš€P—ˆ2bØ£œBvŠF‹dí›nös(H½«Žð‚%!2ZbÇ4eœMß þõ T=ü¾•¼ÎAww1=ªƒ¿dŒs, ËÑœóN¿fƒáXå^l©gGõ*¹$OÖ׸ø²;`…„…UhÚ–Šîâ© ­ô–Œ+ê­WZôÊl‡,ßIí²¶©IÏz˜8 Š&j÷Õ£9rØÞ„> Jë‚:ƒÀ8±.C×c{fØÑF ÂàÐ7u=|m·‰HÿSkÙb%îsë'yUiX:FìcYYyHŸÐò¦“K­Ñ2»mÔãuWê‚Î× aU¹IžÝ…ñÌßü÷Ði’bŽƒò$öo” ‹,)z†VìBVon@\¤þsk„CÆ¥5¨¨¯¤°=ÛmR/l%^¾ÛVÛ²£ ïdÒs ÀØ—½Ø>…0Ê*¿š.[Š—!‡g¶iáÀºío÷Iž¸ÓOW7ÉnTÄkáçêê1ˆJòˆ .ÜÒÖQ'¿irÑ×ç )£Ñx‡‡ä\&ñüÌÉ-ègà™Æóå[“Mvß6:?‡ö„[Gîëj „Bß[sb]ˆß -„Å]£ëððVmˆØã`¹¨q£IWvέ•U–G¼äB µ£¼l¼¦·W2Hu~µ}ÞÙ~Š#Ç‹®f ú MR¼3¹}¿ÔC¾u(À*ïõÀ:J’c Wn,y÷æ»§ÝÜQN.;¸ÌùÏù8>Š9}¹HÊÔv«$dsc¿P“~ + Ó!.ãÒÄÙû»0 9^—ñzkê*_;þì‰Bƒ › =лO?Úöí:–6˜ßJFU†»å á-ØãWƒwoÚI#ç_÷ñu& ÎŠj;ÜÌ›fžŽý]Z©~“’}G|Ü”bœ1eÚ•­G\#Ykp3 Ž5Œznz‡SúaÛüpXJ¨´ç¹‡'yTË+·†(÷6°ckøÁ{]-„Ÿ±æïöøMõ‹©û|Š jrüÝÖìp+[^ Ñzé—{-zµ‡ÌF¢«BñwǺèøÌ«Š“Ñ„»øjÚô,Üh=Ò¬ù9ÃÝÜÕØÇ³©› ÷ÉõÀú‰äØ/!ãB¼•Æ-cgÌyŒ«^ €·vNaf¼©D°°~´µÏ»§dñij·„½Ôî 8ÆÿfØŽÄÆ¤î(GMóÊS¨dŒBÐ8ìܦÄ^[Ö‚£/=&xÊίÂÞjþŸäºsÌÖ_£½bú mSRod¶^•C8 åÆét&á ùÎCk¤¶R‡™35Í”ß6[*ë<»»ƒÛa†|Í Ê’éu£’¥ÏøÛàDÍ#„»Iå\Ù·&KËäÚ“#àcµÑ€‰Æ‹]~Ð^ãû8¿µp/îäýo ®Í7åUF'¾i½Ö’¡ª}yü|!öZ-'[|î˼‡J^™`¨ƒ*³~ÝñØöƒqÉ1²$¬ÜŸ- âÔGõ)3k ŽÔhÞY‚ l0òÉ1o (C‰ÎÖÎ…Lç¶ô|A˜$ýoú8ý®î11XLï~`dö›  k܈ٙââ¥$ks ¤3M pÂz[N_ØD£Ž,úTª0Ç<† |Œ•ÃÓ8u³VR‚…»BÚ3®q ôxx¾‡Ã SñÝÕ ˆÖ”ª}b÷¿ÚÖg{SPåGr®XÕÿeõCƲ¾¥gNIÙ°¶ú³VOg4AQ®{Ì y÷dž%köYêK­<¾F‘­qÙ‹ñvQëH67J*hð&­a’nTs¸œ;Alø!°úé7? þ(¸š0ý/’ ñ6A¡M§µ‹œK[œC+»”‘ö_[äè-¾ô˜ô–E^X0¡ŒU= ¨zP§kÙ’ó…ΈúßTÁü`½Þ—¬È—Ô+N…|ãÅ"mšpƒJŒLle˜=vrCkù¿KŸNù_ê5ðk‰Í<Jt#±/¨`’s¯€±ü7Ì£ÁĦ§¦N}uƒ(V¹s³}ïIÞ)½|LV=²ÊxòâÖv”c>ºÌßÔ¢ô§m õrDS‹êÖ×½½š3©oýa³†Øœ„àƒÿ]Ÿ !±¸åŒK{ÝÂ*ÐÎ$Oâ A¶‚­Ív ¢Ön’ª‘+ÈÍÜ2+å<Í£€ä玃ç 8ÙØ¸ÙÃQùçn4v8žMB˜kÚññÏ•æÃåÒ©¨ùêZ—Bº‚験n2ð™€ps JwÒç¾Èm›ÿ.(±÷Á»¬ÿ\âArb6'ŒA¨îâ%§éK‰) Ô8N’{E¯ÛDõ£y õÍò­Œ,ô`¿YÀb¤ª·±CµSpº¤nqÔó¦|Ñ)ó¾ð)Ä×Ç2õ‰ß–POÿ^.Å9YO,å ù¹ÿÙâmINß3ÞËgô/ú;)w±šÛMšYÝ9­ú°Œ¬uëHù›=/ÑKe•ï¿T> %’Í9þ!¡Æí—‰¼!J«±¤C®FêªâCXªmâGØÓá{ò«™P†ÜÂkðø_ Ïrêo«ÇŸvôYßLKòâðg¼K7ƒ¡‹Ô;X+å©«&‚ôIŽ ßŸ‡}Y!AE&Úh^!c«Š“è|ÔX|ì´ßͳÉÃPL+¾jƒ—â ¡;Û䡵y‰ÛËåÄÖñ"ë³ÈÓñÕ‰L\­Ü7HMlkãÎ0ü4õPá’*:ÐòÃv¤Ì¾q8²H9|¯¨…»$鬯â߇v*›è;ˆOïE,_Þ¢÷Cª5E=ʵYñ}<äÉ)ÿ^ÿT¦8HóÐlÞã`K…¿ýœ–/°Kæ±›Ÿ2ô«AÓÃ1a‘›Q .¦‹¹ºÉé“ñÆ·Í{¥D•.¡Òà?O½ä]î4ïµÙßô¡¡`i¿ýãzXóhCšl$Õ]ZŽð1œ¸›;PUQZAMo’ã÷ìù(û€ñžq³aj¹i\Àͤ=ZJuö¢‹ª‹4Ä(ŽÅR¯ 1ÑT™ê†ñ´»Öv¦.Ø(” -‚š8MJÔ³4ÃY®ÆCþÉÆ&›t—!•¢¶]ýR…úlΠŸÝ°ùµ|8™n‹SšDl€a°3J“Ž^ÿz‹WåOvŽœW&·¬eAÍgE”‘ D¨é)R®~䦣RXX}÷_‡ÄMê=d}Š’.[Œ=ÞZÏ4üh5êçDqæãýcôÅ-n%WÝ­-ôÅakŸÐ’Ë0À÷vófŸžž=š•ä¾£‰—«Œdó`BSlrP\Hm–W»‹( öx¦TEˆãV Wf†ø¶h™Æ ›f“:K;ïóáù[iÙ®·}jëÓ O_þ’ˆ)o–¥ãâžüJ9+Ôî u^Èéû°˜“Ül|AjÜ5ôÞ.A}ª¼0~øñjW …"}«îÆdÝía³ÖO\Å›AS>ç-`¦oë¸éåÚ}ko€Ï&S‹OÙiù.” ï³7ªÉ¼)œ‘¥:ÙP;kqݧWQíù¹‚H}4—_ÄÃÕö©£¬Æe/ü4Ï‘¤õÂkmƒir{#‚J!Ò,‰HŸ]=;Ù_Û…RAÎ@¥ =èʈÀ9å´®õöåÊHŠ_±ŠC3\\Sc=ÂR†æ+x¢—QÇÃàa|­’k|\Œh©y²±Z5-©ò97—ÿV§VÐ?»fb ’K fý‡%ðé¢P Æ·¥7áâÏäÞ]°Ð® ±ý·´×¯Ë8«Æ©›qDÿe>âlDþz–RÌ]sJ¡–ô~ÕE€¦/“‹yHÿ iR&¥ù6 “{ çw’™¼»w[9QŒ½si5Ê ƒ§íš°åNi‰Sv'Ñgéê–èG{L¿•Þ:X2ú8òîäŽEƒÞ­ó ˜êÔ6»Û£ÜßeÀᙢSøê£Øà–’A<»ù~»Á«ÖÓ¹N-ÍGÛÔ–r`į‚Þöy²ÍRõÁ"sóY« [“3^B¡£Ÿ½Ø2M¡U÷8´×•¬Þ<ÈÔoX˜ã2%ÄØÝ6œV]_Õ¯Z¦[Ôi]¢ë­úþJq50í*¬Ö)` ¾4²c`’A)Å ÷ý:–‚X6|õµº[DÙ‰A)Úa1K ß×_&ÝÓ¾¥ÀÂÑ °Uù…¯þsë ‡UÜ ÏanÏÉsèÀoÂ|ðˆˆngxBã÷ôñ¸ èÜ¡.œÜâ~€ÀÎ~o&eøtÛxîù´©Wª×ï--œ÷= º¬×Úá# /»B~ä ªä5EÞ…»À09Ÿ’SZ±ãøÛqEÚÒoOtŸó“?¢õã›ò:{- XÙ…ªÞ¨Y H-ÓÊ4˜…†š§ÇŸ«‡ÔÆ’„ 7ÏëÄk­šë4)à voÞÎq1X×(ô¯XAo½øíSCã*öq]©Ì4‡p—éɵ- “íoÉkúЉwáuFÞf]v’¦°œ'>Â}áðr{9ƒˆ®rp6/¤õ't˜ç-Ë–]ÂÔ/(´ó^æè5š"²˜L7xxh>þÖ nèã"UߘxÀ™¢\Åþ >=¦5¨¨~”`‰{jå)œ†™&0e&2ƒ–ȼN¸Ìè[æµÓ˜Â‘R¬tÅ·”-mø¥@†Z».xw4€‘h†Pò|"XŠiîšD스#÷„ò4r5È»„Í¿¶=ÈVRhCr}Ø4®êqpOjä÷þÏ(]ÓzbýÝæâVÉë§$‚è?øú–é€h8‹Ô/ת¾îi7¬wÅl\ø BiÊ ûÌòë*í2LIÇ9{ ·"a1yûÃ1c,O†s%\×}VeÁ‘PÏÐ)C¬©_1 ÒSï0‚ëiÊÔ©ÉØ’b}sE¯çË!ñ³eØA–oFž2Ä[Ô»g›‹…¶©ÐfyP}ã+>­XŒÁ'¤ãêÞdÐO^WATZSk(Œ¿Å-Gï ëÒGfÖ+ó\.ÐpõpmbKGÖ›„~‘ÆžR¹y6Òƒoã:–*3g_!}Â9°>[Ýëõ s©eyW<·‹ÿsž`‹žOÅÂp?3ÐÀNM¯˜QsDæÈØÞH-/qmMZžBàÉÉ’ÊëGÅÔŠ_;ÿä³ É}Ú¬2ç#=ÔÓ…má=á ½Ób°öˆ2P¤€ePd÷®Øï€°À…ºBçšûq­cjbdcGóÑÙA²ì㻄%_ÒØ²&â;0/G¸ŒHþ/‡Á_Róö¼f^€É)ì`–Â)?gÉÐf1î%ÿ6f ïž‚šk>ЅާõKR:LÕnßU#ú·^~5à¬Bæœ>ÌÆÖev[³ÕùS¼³×RÚÃ÷ô²¼ç|ùøNm¥ýA°ò,žâkà ÇJh‘Qâ=²n¯­ ”’#ª`×s:åÉxR0€—ÉÓÛŠ–ksÍ\†©,šÝ«u-1u )ÒxŠ?Ãq/¤Ãʇø¦¸ 'á¸L –ÄÉW!MrÛ8c×-o«Lß rÉ’‰ãµ|°¥ îÀñ"—"> I[™¡/»m&µ8ëä’…üiYS";ä¿l/"Ð<è˲\ǽÙhkÝï9a;Ë·*ýÖdÝbÍÒßÂ'¾+—±mÈ¢Ds—'؃rx¸H(¹®à4sñÃ|Cǃ‚…ÞÿºO\Jû^ãԛџþã13rfT£±!àµÕsãy¬·†¬ºÑz ëõ{x³,H®é‡Åg šüÙ£®gôU¿{0¼=&žã“wBê>ë@N…ŽGÁní5§ ÇC¹ë‹FÅ*öœø0Å_üÞ‹Þír_1¶[¡ù0nÏ¿{@ÔÌœìfâ¾¢p|s&£JÖm›]æGÜ2´Ù€…•›$®Üʪ/¥  4ИÝB©Ä”'›¹pÏO‰ZņD‰7Ôõ‚ðÓ}ÙÜhÒîyßÎ63‘çâuædrÚ¡:2¾G¨i|æämvÃëÎÜqóž@2Ħ±jr ºZ6Ì2áÔÄ÷l †@‚[̾oqøù£AŠiwïSz ~]uTZŒˆ«GÎw¶AȵÏ%ŠìÖl%å/Eû㪛¢çˆÑBÙ' ðŽ³ "#ÎÙNü—"(=3œ(Hj’碉ßHï¦Qšôy\7j³¬R€¿µ°²çQp×ÂÆ8ª©?ùÕEq±¾‘þ—r9 ¸Òâ8¹ ÷J‰<Ä•ey P®G ¶ ûÔa.:¯unÆ_¼¿ªÞwøþ-ÅáÇ ¨¾•Øj7ì²²ô}I¯ÕÑ¡¥0>7âd´gšåÿ"8¿oOuËœ,”LÆ……¯(GÛg¼ïõ‡³ [ú‡÷±K2Åì:.iùƒîºîæ•ø–)çB”DÉš;:dS-–Hù3sL}Wù3ÍéΩ²b¬ñ8f0­‰I<ÛÒ TÉ6¤4Ý6²¤Uûáé®4€¨¬6‰ã7EcMåöò *öû§-¡|”YX‚TÙÊ)„‚K1ÆíºÍí4C£# غ$®. «ý!úSÄ…ê´@g¹žWã«2«æ‘/Ôܺù9ò+€Dè÷n=.P–Í Fížß²fꎛ˜ÛFu…^l"KÓNÂÖÌs 6VŸ±.¶@[’± öÑØ™Š%ï5f×&ŸË†Áj€õD“sjœ+n„@¢vÉlA”D¾d2AG<ãu¥ìîhûÌN ø”Ps7RO½^tø]¤X‹3'Fí5úƒüR¤Vµ.ñ§o­,âq&a"Ûã ˜·ï7‰U§Ú Ö9ÆòXøQr’÷[žû:[–âFi9ìz7Sfûg/ÅKÇ÷²f ¦ ƒÖãzÆ>õíÚâ:â¶X£æUA´ÅjqÑèêƒeimø^Ë™Þh0x5÷dë‚A:z•êï¯z6-͈35µÊT•Ü \ ‹v…fb®ŸÝÇg¡Íì0+<ƒ“å1ˆ„ ­˜Ü+OæôÔ {‹‘³ÕÉmœÓiþ„ ¿GsºV…š–ç@ÁùØëؘsy_&âø·Óܬ0@í$Ñ€o§ÝI‹èÆ;œÆ YãÆ¬­ÆMµºº²Ë•Æ‘P“i¥KÅnââú%•/øF7ØEiÖ‰}5 Çf(k#XŸ ЫjknG h%Ûò[ÞF„£¿ÛÈe–%r:u¥8/=Ü&Ä4Fè r¶×ׄ?ƒ}ÉOq} rVVðÌÂS‡=«É«!hêÓ×ÔŸ“Bá=¬£1ˆðŸ:3~C=Kèr/î¸Åp†YÎyHyáživ+mâ]²g^ÙÊÀ°ÔŠ:™ZØg€@¯ÎdIÁ(²‹JDÖÓå3À*8Ç&Ƽ,0Ï!@^Žë*?Øòn„ú‘Dª(¤ûŠžààÀþ’æ@3d´ãrR‘…^NXAEÎR³”A™â"ùˆeûú’ä2¢„gô£«BiÀY°Œ¦uÜrL˜ÉGž—:µÿ<ìC,5LÉŒ×ç}YRÉÊÚU/»œ"ëÂ7Ë·EìÈå5‰œF)üŽâw°Í 3ŸÇM7<üŒâ”U´¦fî.õ÷)mR ˜zò†lÙ ;¨^-Ú¿µâ¾ß–~—âa @ÿ6Œ³æ•™¶:ií7—ù­ên‡)Ò ‡üs¼Sk [u‘9%铊úî ½B׌Èü‘ü™=;9á¿*ÖÛ…ZÓqáïHÙ=)>P⦲ñâ‚×Ù»-sc⩜ϒUÜnf1?åöú`^{íõþ¬™}¾‹·ÛëÉü=3?‡©Î/·ÒFû}jÿCÔSøz«ç•"$©ïúÃøyV ÁjñÂÊø)k$ÙÏV»3ÌÔ@£Á_±;Èí6PÿJãÿy_Õ][ÙÄ®U(ŽËgzº×–˜h¶„!\ý –ðÆlòÚ—ÈrFoàùS^Œ4$ öŒ•¦{‡LãI‚­`4âß.l#êÛ—þA±Ú¹Ì¨TžR¶9Aè–r0þ&ÅNéáº]SOªA¡¦š|åµñO%¬?ùÂîÀëLfáïXæÑùf•‹·½zÈ­_DÅឤ\€åÆ¿h%”!x ž2qò¿óºy ®ö¶Pî͆…åáÏqz¥OmÕ‚­áXìAjù2 ìºm@3.I|à§ÐàÎ9€Ñ–áe 6ÃU7 ãa`5å™Íß~ÑꇪS[>V¯.»†Ùñq–3bUÐËtϪ7Ê¿!À"äZÑ÷ЦêR*›x)Ieãt-`tö(¡îø6X…À¦Êž¼Ûk.`é:KŸxÁSŸ¢8Y‰á8þšhò¯6HNžЈ”Ç|%Á!~@6sùirGó ¬¦©–BÞêf{¾8ý†H?i¨QÏ•j¼c9Ê «ÿBJæuúŸaY4Ä-¾4™/3ØoqP‹ufÎÚuí>ÙByú¹ìœ÷úîâL‘>Oö*+¹y;Ø_B‹œøøÌÀ¸Äz¡-X¶Gý•øì„<:h›ªˆï¥@ÐØWE›·‹… lV[ÿ9³J«»„E&£y´4Üa~—à˜|M!eè' R‰p ¨JsÊí†Jˆ‰Rõì“ðCñ+Úç¦0ªd ÞUÁ(#¹LÐ@âÎAi§ö¾cÛ —jÏ÷ËÀ^ßQÀ¶ê5wÔÕ>SþÔÂù^1õ,X0Eh lŒÀ’˜!¡JKm¼ëRÆf$±¼¯ fÿA»ÿ2O܃€NAïFSr«J¦hY.fèæUV™>¯&_ceè+­`~Úö1švÙjXÈ©#2Š?uÞR\ÿ†{ÿn&H’áîÁ_—U¢ *>ý4=(^*vxÌ£”‹N‘ø?•÷F´ÿE™‘“)HÕ1ìÁ€ºØü/µ–ìÁ‡pæ÷Ái”¸:Øì”¿ ÙÍE5³aư;‡­dl"{Ʋ(­NO¢úAh;(ð/ˆ3[º&2€\~’8|Ù'ÍÞï|Ê18ðùKY"­3Â̰S±õµÎß7vý|@»Ê_ba„Q4Xéwì±OKXGC;?Œ ¢¡?&©Òrf‘ª¬ Ì@ÿU»>\¾ê+ö3¹ÔÁ»1ÿrê±€xÃެ¯v9!2ߥèi­ì[XMf's½Ù³YZÿ>oÎ÷N§5Bñ§®Hmì=£ZÈ©hüáé.ð}¢°«%UýL×ÜŒ\J™—˜ÚÚ„§‚5ÍùÜ:¢½¯ÕÝ»NAKÕ£¶vDG]-ö¹v˜w¹Ùíl·¾H¢‡Ó~ ½£sçà’³-Ÿ ŒÅ¿ïgu2¿R•­Ï²œÅìußð÷Ι½èô«õg{÷r'uåµ´'8Π‡HÀ— /â’»deP; y‰num¹>æ¶ê;lâŒLÓÞÈÅ&Û¬Ž´’–ŠÒ•/(ÔO ÈïÞGñ\@i`…•TžÁÖ<“z[¿„ÏÑ%«jÓ…¿­<,Â"ö¢µýøÛ¶(–ÓlëL½²IyµH÷o„ä5+6ܧëñLj „ãp˜T#טnš}æ«^ã"•LõúÊ÷u€žj&jgWotK¶(ØU±MYÂ̼¹ÙÿKõñÝe:oEÞ£kÝ¥(S.O#bÃbxSä™G£oQž€3:VPV[äW¸1ýídDþmã×C»5gx–_ö9¥®aÀrymü¹ƒï_ÌæwEư®ÿ~f'«4e k¶‚€þtÝR"†ÕÝ–§¢a7°ŽÛpÌ_ñ(cÒSuW€zO«×ôË„<œ¼q%Š6<®Ø¢*¶NP}¡ëDýàx$J4­Cq¸ˆ¨ºúŠ«ªºC[Ÿ…V§ÛÝã4ûuÓ…²E} á³ÂÓÙW\wTsV†LQöÓsÙ ¾T ëÈgNxÔŠß ï+‚ýœ|çTTþ·#´äÈìÈÌ?8m65 ÅÈ—¯RÃr“æ”ÀŸ{€ÆrA,6Y>’®Ù§ž¯`÷_:C ¼õЙ÷ ¹'°¤Ë¬½æ–¦×êü«–åQ‹S wµž©&D€© çZãQ÷F!¾ü§&Ú­d’Ó®™º«»ÆÝ·-Þý\´´Áâ ÆÇA>VŽ ïJ’aÚ,{uŽkM7‘oÔ=p8išüòÔLj"-*‘¹†úµ“Vô!Ï³Í q%l>Ñ>Ýîðø.·@(ÏØL„+ƒTÙÓQ­6¦¯›còÉÆ”cøè†TMˆïše A°¦uŒ€q l3Ášâ ^ÜV˜]øK{W#^Š×Ê¢A\6Õò?b›{ȹÝ/±çªÒIâ‘=šTͪ`n|v‘qGý|¿WgÞ|­|2ˆØ=ðáM,y{?M|ñ#e´ä©k€7I<^™ ð]õW?›ƒåJ_ýÍLùeåN­Š=hUŒ!9á˜5«XÀÉ<¦ …Ï£%Xª θÿh¶øÉÌ T&ʯY•÷G®FjŽ™‘?Äò9λ*.X…)}ôUñAI¨¶¹[\ŠÐmÙõšâI^ á^q•_AnòÛ •z1Ë ‡Fî„×E#ÏÞ5Äæ˜AÎVÆ›!~g_ôov¶ ðü_ÛõxÄ×N±2 õÁ3n-a’·Íêž‘Ç?ЏWÑ0邱éÄÏE¤£õÆÂ¡ ¾T(Yã+0õª’^ªR Źm$í¦Ð´É»Ùþ‡ç ¼9dõÀ ý4`¤5±«7&ù¦ÙÛD­ÎÙÝs=þI’H* Ć&ëU> $Ì$E 9˜‡Mù÷Ôœ|Óæ)_{ã˜,ÐTêq3–Ã!ùŸîüMŽp×ñ®v} (¾ï™ÿrüìöäxŽM¢àÆ;&Ã×ÜXÌ/³û† ˆ[ ,€ÕÑB§(³è28Iªoó%]ÔÌë…zMå `½)ä Ñ+ß 9í“áMª}R¬»åltxYy_ #ÿ?ºø¼òÐ(k6Pèƒwæýaæ^NeÄclã1cQfJ%$EÐ<é²6L7X—FÕ€UüÒoI2OÊC“+í»ïþÐ3Éx}“‹SFXÙ“c« Åš/Bx êê+&PÉï­ÿtÓ¢©ª› ŠÓÒJ¡]Ê’ñ%Ù#jDô$,ðI.­eÞÉ‘s±^; „àý¨àw²š¦vP}¥Ž {B›ŸÄàüâ쟖:&Ш¯Þ$ƒŸìÄ?#’rÀoe¬Œ=ª™ê‡Ì(äª`jVÎW(“sé0ßu%#4qþbVulð鲺—oäß.q1€Å‚ªÒ¼G­ Ó”2pJxAˆûдѶ.%+›ê…´xhAó') úrIΘ{ñ(J8þ· G@‘ö¤³¾šC8ÆŠ5º½æ}ÞþNÒœÓ9M ¢RpšÝu2\Ø©@î=¿©¢jB4ÑÙ™– Íé yk¤^–Ó„‡8HcÙï ÕG{%ü 3–6‚ø¤§µ­ÞÙa¡ÔjçÔ<³nUé›JÝ‘‚‡Ÿ&S$„-˜xFR·˜ãðW|莻nâÍ¿æýÿLàõ$þ…¸kjã,¥Ça*6·ë~ÿÓÕ¡xPÏ8¤¦(®¢¸Ä þuƒ_ž¦#ý£ûªu ¾­Ôl¥RžjŒŸ—4å y.ôz96Ë:æ™›0"úßÝÏ<®‘ÎoÕf„IR‰:t0³DÂ{ô‰–`½¶êÅ©½Pšs£,¹aþ°K“)X{‰€Ý.uÁuc,/÷ñEi<íÍDá@íÚüT& …³ÓDcTVˆ—(wÉq~9%*Ì\´ÆÕ'Qµç¡—öZ؆1¹t*!˜R ¯k¸L³kÇiù³¸tßÞ¿ŸNUªÐåd%3r(¬r3ŠpÝõþõYX×À,@o©/¯H æÿgGá"·«)¨¤s¸;JˆäX8f¿æä€þif®ÃÁä©Ö}U¾›´=ñ t›$ÃðÛ—¾>~Òlÿ"‹ÚŠÆçùþrFÓ'ÏèlxVž˜+Nm9$û« ‰œœ‘¢j<ƒ#wR•áØù®uk•à97™eñeÚ·ÎÇwŠH°ˆ¯†°Z¼!“~¢TW¿Q1 äk‘ÛCM¨yYE>–QtA£•UÏô‘·k•žôµ ÍP‰ý´,Ü>ëOO·àÓ¬-ñvÞÐ÷Bæ þÞCÁUG·¥“äŸrÎ*a¦]Kpsö›uÉì…r`pv Ë,;ž*àí÷¥‰Vû+$þà—«ò.ºPÎIºé¡ìéº1Ââ>ÙB@ë¤ø k(Œ31¼›úÏ%I\åJÛhÿ<«re®ªí%*Iõô·l¹‚»Ï3¹ÜÒXÌ7“G/U {m¬ ÖhúàiÚS&dmOÞ»€#BP^ãNlY¯¯*zß¡\ésR•Dø^ï=}аb1NIÏΠ6å)QõÜôùÀÇíÉá……×%ÃUöÙ‘rë§ÂØÌœ7ýƒ=sZ8çH_§1žŽ2U bÍ•S°Ðu| N!À/ÇðÎ'kcY¡vôeGöª¢3Vб’˜jNyCŒ®¿vOT¯QìÐVÓh°šmº pudÌcÿK2ȇ›ƒ[3ø‚Ÿþ.<Üa4fˆÆÑ‡ªì’º‡…ú¯hWüÃÚI=aÁ½%!ß`¥“ÿ©8Ö׳¢…@ìFoZ:_Cë÷[ò l@ä<³'¯^½¯ôŒY Í/_¥gN4tG¶/Év?—}(ŸœeAÍØ™Ö¹Ý$üJ|Q’Α&÷ÈšØÓ„úñGåû#ÂR¸àciãF¦Ö4Ë÷£8‘矟•k>âBÁ¨žÿ'Ð7/—Ãg4Ð׸gªßIÜ<ÌU¦pO{g…³ˆ†º—m "¤/ûì¤7ê}.:ïX³‘„Ô’ÀÌœ“š F0aû†Ù°ñw€Î’½}öSÚG“9ƒù8æqX!^ç¡I“Bºa=š™Zì~7ž«”ogW¹ñŽê€ÚwT…®T|‡)³?x©âš>9=7§oéÔ± òŒJÇ;º-/NòümÿAZ9†‚Ñ+Ê \zx†3zÊ»rK·¶ºY¼’Ù䊒Ý×ïbºÉè&ækå ¯"ÄSð³!–ªõO~Fήð¡ äQ«.×Q„F6ÉÂï˜C\™ë5/UË"*rÍÖ9+Í.n¼§Ò8„‰º–HÕ¹|áp€ å;ØŠÂÈCüóô8ý½+|¨}Ä_j ÜÑ83¥¸² ¯eVþ£‡iY§žÔ¬Î4ACø/+5I) w.‘ÚÄËæ S&¨#î|£((ˆFÖVéÎ;‘õ͙Ɵ©zŠÞÀœqèMê•¶ûS¤Þ0k¤à:Áê㜯òy(õ>µEd~Pw8=*׬®‹7›·ZY'*–»×ÜWWꩨj¨l@HŸžKcÆõX#·xí =­ƒÃÓÿ/ãÃ21 Õ2g $r*nåŵImuèëcrÞÎ7:1IX=ߘËi-?Mh+`cvûZ+úoEÖæø›B¼1ƒGf–eqh²/½–eÀ;k”í(wcÓVšE’:XCâÿ-)Ù‹ŸŒU@ïÅÓî!â×…GKH¢½o&¶1³ íNN<²iîcƒö‚uð°Ïï>Ø«A (j¸˜b󵨕A¸¼þPl:xÒz|üŶ*:òÉæžnâ¥ì˜œ²â¼õ&Ð(yذ†4–þ“%†°£S˯7'm(Ë2Î(Éž¹YSJ_è:áTèj°hU„…mTÐ>zÍÌ'Èà„˜Ä¾ä“_lÂ+ù>Ãbû÷VþÉÊ…óÂIöE¥¢8-ÏpÃR’Y<=e@”u±bÂPn¢“ß7ê–ºŸ¥´Ìi©jMi¥³eHÛåözNDzØ ^=PYdq?É&}øŽ1$…øÛ£›E>¶y³ÉHúÔ^ÌIâ¥-E8áèÃÃh[ºÞö?ùToRÉ„Uü0Œ~“Øý~Á>¼to.„‰Ð^Õ©êýˆ ¦ƒ^*æÅïü&@F·lÞ?‹|ñ¨ØîÏ|Ëcè'²•œ¼âØaR8…<æR÷ËÂBÊV"¢$–¦–ÓPjöv â8ý‹õœMx_j¿:°ðq‚ -Æ ›þJ8ïdóRŒÐî´êUÓáÏ6¬¿ž¨Üoj¡Y~–0 €bµ¶ ÕŽ³¨DMšJWþ»Gœ †èŸ|FºiŒž ‰ Þ§šzéw>øØ ž`Ãþ:اm¨Ç½P’‡®„ú¦aYûë³ eÎK×#Bü:ºF÷ÕÍìNù2 >ºlâ8ËB^¤ÝCFâ²½ËBôê€Á x ×ñ¤¡qöeÙ9ýBH[ ̈sì+-Ô"‹-oë>ë†F±m’­?.bpësD²GäÛ¬ˆI|‹~âïFÊ£áÿr%A¦¿÷´Gkn‘uVL΂Çù?Aªݨ@òhù“Õjõßög)ÏiòaN#6°ar‰-G•{é¼’¥±Z?vdÞ@笇*—åÑ[R«ìÿeÖ·3œ@‡êás?½I$Ì+ZЛ¢¾ÉÅõ;¢ãSFgþ跳ǵø„°¹°;~J߈ô »þÓ>E¹APwæ¾ #&7ÿ ©ÑuìKi§æCC ÛA†ÎeÔáåñ¢Ò…³$ä ÙèÑ}NòΊˆkœéK’7­Š¶˜Æ.*SxÜM·ÍüªQ:8Š#°nK0‰Þ €ðÞùcK0ŒKÔH™ÿd †ÁMHI/RX-TnøÒ'èŒVÿUÑ‚ÍAñã.ª&!®å‰I¦Å!…ˆŒ*¶pÿH.>uX®— |ð €t ŠÊ.Oþ— xL@Ñè™wF‹[óÜ6ö\ƒ¾WÖpbRÀ0 Þ<Ÿgˆv€*Ú†=~–;ÞCÝE5¤Û¦ÛDx'+,Í¥$G¡­ÜóÚƒu GÁI*™ö* 2:±O™ƒˆå<™7þgôsúk¿`žN¼\Á~“ k¾<Ç`a7ó:›C¡h^„ÎÎï>£Þ_']=\pd²òø* ‘cÛsÊÆ.î çcÿIq “oñ]g/{Àü·B)rž?ü;¤X×i¢f„-Õ¨'Ö5*éôKD»Â1TÆsj.»|&£¥7·Éà§Ã¤jCö@„O NËFmfòÝk³\ªOH4KZMÁ'#€ÝÑy¥=¡¨q ôÝS5¥DT›eµ(+N{óö›å¾_œ:xù,ymÊä“Xž2„“@8გ$Ej¤Âª\óÜHÃIoYsù Ÿä6Ö8iø%zš!"^cyÿcïÎUMrt/ ±Aã[ ¾µ³|„é0šjк‚X|Ìç5Mt‘“Öl¹ T"Xã­ ÎÊûÍ„ŒÑ2öåm¹œ ¯š©F¤^¨ú'Û(:OÙX$ž~Ji?ò"˜4u Á-dÏü›Ž.ý"Eku°¹­õ-—"„> …«û_\„¼àÔÎû£Õš^_‚ôÏ]ÇÙKÇ#ÏŽžª¸òÔýÙxˆë ÚhÜi¸8:½ÍÚýÜðÜʽ·þËF-OqÆè2Ì_[ H‰x"qH±¨{_1«åg£*Ä~9ÂÓ \ê=Õ\×”vLŸ\¶¦¼ÖTÛ fáè^lÏ Ô z" ¨ó¬Œâ,µÁÅÿ>ª½:cÏhV–ˆÆæ¨ÏMÒߨ5ii>$ˆJÜm¯öÔ «êÏüÞEÏH­æF¹ÕÎr'–=@_aí´«Ú¥•Ę"»‰Õ©^²cP¢tóèÂÿq1YWî·#QR/ö¢4Y5ôb}ñC’ÝøÔyæõÿ#ò›v‰‡}à õs‡Äh~lÙmfc /v‹’L/¿AÚ’nrY®á µœ¬ÌÓšoø“ƒ/­ñò ì±Qàê®Ä¢S‡èù÷žžb ¡ÒŠ,?û%Î2 {'lײoWBÓº*!îFG£‘ÍÕ¿!šÝ¿]2âÍ’EýžöÐPSòCx™\hÝJ2å•)uZò- ¼f;g§™µÝ;¬»ëö-z­¸T!lûÒåÓ´6Ó#çžþ×¶Ãk+d § á6jǘL.±Îm– îxFGø ̉›¹³Õd[.(¹ÐÖ‘‹Xœ”=±ÙïäþìiÅNwðr ®ÿL06ÇÙDmÃM‹Ì¨ö¢ ^§€dŸ)œ“³Àæ  ð¼h¯2Zèåz³˜Á!L„At“ÝÀi›‡ÿóœØãM=Š\¦ƒë„X±xÞ5kÞÕëS£ô™»aÓ•ò¬µ6Tâ.ë9#ç4 "+w3QãPhP”Ï?óuG¡7El Kæ T¶0òkV½u¤ì›¦Wíð™^Ý@Æ<&û¾©.ù6ªœ;PyèK}…e¤«¤“h•ž×È­\ æ¶é ¸;VÐüšö`nlÙ÷N«òð­ÆAÂïÓÅ„rMqm@Œè£)œz^paÝ`ÑQ«hžÆ»¬é}”ðÿ?Ë¿RrXjFëH—˜?Z—]«‚üŒ—¿ð|‚µ›Unj8Jñ¼á"Ë›ä@°t ,­8¬Ãºõ¡Ùê‚×É#Zv­ò1!F]ÁS9lÅA£F÷•¢vßHp4ó*®mPlj‘åË„/æx>«8ë›×;-ÆÁ¼5÷ñÝäÎäa™JStg6íÙ©Xü4Џúµ}½â­VÄ Jín#+Éô¥j¾ZïÕ‘×|of%Ÿó†Ôð/i"<»Q+TŸ›\Ô˜{Ư7ÂôÙ¥OW~¦ë‰Ûeì<Ï Wñ|ú æ÷;òmT§Í£¡[¯Íœ[xðð7Sð(‡J€³s‚P _£”8Ý„Xœ¶I`Ðk‡æÂs©¨ãç8Õü”q[ºŠ… -¶OK(#Y=MïX×è ƒ»weB1uƒ_œ4¡÷·mevÜ/‘‘A:c\›!™Y+çh%¹³ÜŒ¾ñ>ßu/}«…Ü•B>6yå…%šûßcó7ØÐWÁë>Eß¿®Ð³"Lht8¨QFß_Žp®oÐL²‚ÛyÕêTñçÖŒcëÂ[%¬…<‰è€c&+¿úiýyH9¶…síø¢ 1U¹ 2¢µc‹]¥Éƒ’Š\Ëv—÷_ʺ‘fÿr)“³ÀÉúž)—R.–àÅr•ð„JÕÕ¦øIiÊz°È…¿³+4Ý©FNc=œÑ€Ìg “ÔMˆúƒ¦:("0_‰Wê§=Ï€—vþñrÜQ(vì¹hÍrR¶ »õ´síåÐâ‚^¢Qs³)Áu‡®„bŸxî‹DlÍÑᵞ&• êë©»YHƒ2;1™˜õ»ßƒm4_ái¿á¥F¼“ˆÙCc<ÆØ4%U¢¿Nü²Ô¨¢yG6AÂïÈÍ.“91¹ö]ÇɺmÙGwùÖÍ;0µÍTÎ\Èï<÷,â^ú¥‡ÀÕ„¿X.1àë==z#­Õ+Î#ݼ¬Šv+¥Àú£«N8hÃJÐc6ÇŒ‰éé$dNVUúnïy==8@ÁúƒJŠþ‹+ÙƒüQëŽØwd9&NåU0|ÏM*„–WÍiêèNB¿­qqž üØÄ~Ì:‚{; ¹~îówAØ_‹ÂÛÆi\ˆuKvB¦óq·b†wðn pY½hé·À¿¸QÚ•=˜´I£Çk}GL éÖj‘ÿt¹®œ>l‘TÔei`¿/þ»9ôT¹ö žó]™i-}9CR`B”Øèø1ÜÒ øëjC ñ3?¤I½" ‡QF ghý Ò>Ò³) kÚ?}[Ä›´®>¬¼5û”Ž™ù…kyJ‹¦õå3 s¢dР+ -uIzM†©.|°²$|…øâà1 Û£^0Å©0ÇEݺ=Ðsñk ‡*«3”ëù ›LÃw%bÄ ÿ0/­ˆßî÷@†„Ã÷JÊMáLrs0üZ§rÌ&‘Zë8†…N5¤l\¸‹FI!¾Œ0¡éŽòŽp æEzD›ØA»ÐƒK8Ò ˜×­"*­r–9ç/Äùõãu´fˆKkFké‘\"$ÔÈ0Ê¡x˜% 'Uɾ}Ú@sê+þã‘ô]pQHÊ»Uðó5K !Æû”P®@ݔϸŸÔPÉ_*›5B§ ;9Ây­Ê¯x×D©š`1”cÇ7n?$E‹¦’ºsΑå,ݯº£5Û\x¹³Ôkž„¸û³÷d¾ˆ ŽZ8A¸x›äe²Ê›¨\âµÖËd“äñæ–Óàðå Éë»ÔE‰&¡ÖœK÷LåìÅéáW¥Æìy™Žm¥·Àoû´rvXØÜÏ ß«Ñhà•Oƒ{Ë;Krxôueºà˜ë#FW0SÛÔ;Û´KgZÛ•)n°g°K’㯩Á¯¡IšÄ.©ÝA²@²DJè5´ñ8í‘Lazzq£ö˜íÙD"…†Éá0V““yzaW>¼<:x¨zRc¾rtÛàK ]ožÉÉx~?<×4˜¶ &Sbò‰Ó¶¯¯IJ+@몪¿¯ÃsÒ1²ìÇ“SÇ}œ,½‘gøŸÐ£B¦&­×dÌJ_ÊÈìó %Ûa"sF×Ân&©– ¸ÖÙyÞÒ%ѯ٣Ø(ã\Z_‹g ¨âz{úêüv¨‚Ë[“ÊÓäCôÚÜ}E]¢àeÛM æ‡\ÏÒ,$ÊR ËÊ.ÉJ‹^2ðÞAlš4mùǧ¬ sÜ+ª¥&µ Ùâ)¾Æ RîÁ*Pœþ {L~õx¼ÁWZ«öpÏ+è‘@Æsˆ dq0Ð^Fžýél<ðºLƒeÉn3·"`d@XÂ^kÊ`gG>ÜœÃÄ;ÉqìØiìö$´š€E1ý7úL.Ÿà8 pGSÊuIá†ö(˜Kœ=áëîç±j|hly•Ü´KSüÏ,G=/9;K ‚RM!nf ‘y*@àÊ¡ÁeQ)'‰$r™<Æ´zú_á2Éà´ì,š£#søSÖUÖ_…9âYÔÇé´!†ÂæŠ×0lZì VA¥oÂ9ÒŽm?ú@ü§_ìsü¨GæX©ƒß…ãߣO¿…‹—ë"ž™ô3…H%Uúл–'äjîw4žõQ¥6ÑÃpi=“ˆhƾ͎ Âma‡Ç}{64PønG#yGç¸Ñ¿åOßf™H„‡L¾‹ïkYß;µ¸ù7 üõ’³¢7­?ì0¸ûY-%ép…ÁºâÚyOø´%í2U ½SL³ÿsÚ.”TN±뢟» ÓÃFk¨“s.²eeUi -ñί“¶ðd fU}>™@w•8À0!°“=ßyÙ0äë3ÛMÖþÚ`ö)`?`ªµGaoœ*G)¤ZíÔ{ïÖW˜EîùŒsq]+BšwÞ•«CÞÖr·ÑùTÄ!FÉ*ItTIc`Ž( XÔ2MÓeìj˜cÆ;ùø_IϤ¶ÏÁ+ÑÂ2¸Æ³¤ !{GGoêåÂÅÿsøýH1ï Ê8ºwR–«èÇ¿BÈÅ$uˆQ2¤®Ýa]ŽSõÝ6ýÕAOVɺ9…ð£•N-Qω€´ñQŸ90pœ0¨OŠßºyAe6x)Òw·ž;Þž x@xVgJ"ÇàJ Ñ›#JйqëToEì}¯ó«í§jä- ÉÉ»™`>˜ä~:¾QÞ¾A' c÷ ¥šâJ¼Žl›0 Æ`¡«¦ž×E¤Øôç#%ÂqZUg>ÒÃ=y2)[Ï;áO b$Æ®XÕÕùLˆväïÔ+gÖ;P®³…Þæõ$nÿd«•Ê|ÇÜðç,qŠû”p@Uò©!F‚ÓQÁ0ø}KàûóóÀörzVtP¡Žø±H¾)IÂ-XÞGö)Gö£‘µçþ¥x%1K×G\7UÖrô‘¡ð†ósƒê»ð »n-?¼»:QcXÁá¿ÄÈ “9Iz©KYó|Í’ªk}­Ï-M_u˜.Æóy|÷å$¥ÞÐÎùÓ5F 6ñ™æ×l< 9–'†óvi—`þÀÑ/¹­$lF;u&uæ¡é²tŸö’é«“÷,öêÊ3¸ wŸXyK%¶nÛ†J¶hOå«Éd„8AñaØÆÓ`U_Mg+ô¨ynìB‘ª@l¢Øõ[»‘ä»±p *莪çfýC2–Gêœ[Ò!¼°dWÂ&‹7éß_˜ÔÌíJ³m;üäd®l¢)/Þ“ˆm ‰ÑꤙoxâîD­;ËåþmjÅFãúÂíèÒ0ÛX”GѪdYH†?Tô4Í®ˆ‰0]ú<‡¥B6šô󗥯¾NÜbÄŸ—#P<¾ÿ ¬,êGiæ^¯SÈZ)‚"òòpa›¿æI߈ºãlÏbáÕ*'Î:·µiXÍê Ê–?`+„Cÿ%6ºËDÃ× G²õç(fBá‹)ŸÎŒ9ð©Â½V–êN²Þ°äšólÀˆ‘èi³Y¡¡ˆ’vŠŸ‡Öî#SgW`,õê`–¸‡§þeKw·&äü&ÍoÞ(YÖ½˜k(ñÄàÆk´ µLÜõÛÍ‘P‚KàcaAïyü/ŠKF¸Ë4òø@Ëjû¬Å'‡Ò\2FFA‘ÅZ]¤h™™j(Ý,þHJ±ƒ¤û|€Ä0…­Œ>§QìQÂ^†ìÑFÈ¿¦Ÿ˜<Ô?ôÛŒ©’‚/M †i0-½ ê’uD.ïV@ËÊÍn£C0dæ 4õ+3ˆ<Ædˆøbo2P¬SÂïõ„Aš£Y+G¿û·&ÅZ£¨4„ÔiÕŒˆ¨'Šæ6É´ŒÏI[¯Ÿ*žåUƒë «D~ºº“–L.Ö—¹eØISÙŽäp>ÌÖ˜þ$paÓuПw]yduu‚I¤è©(ˆ%[ïÐöbU À3º1šdM°eÀØ6 ݾ8)¾®ûñ?*Dfæéܰ´µ ,ì¨ûâX”dc¿Pú…+ø¶´†8ù+Ô(H^º ·—hÁ½éËm fí{¬õ?Âc×Iª–‘„:ÆÔ8¹ßÍ}¿»¹þÕ½JŸ™Á$l³¦úƒÜÿ1Ù‘£Þ&gõ¶öyBºS–ƒ]¢&ÀšëÄ%Z¨)ookͧ¬ Åúç-Œ;Ð3UºÙ»Œdã'žŽLFùw¼³ùkÚÒþ´€’ä¾¹ Þ†¥ÉÞ÷is?G5X…5ê‚b˜v.Òˆra sÉ9 ûßø ´nzl–cS”±æÄ°žqúbU¹øÙß§ h6Ý’+³D•Äéä¦U'@*wNjœ Ù‰[O~*12î ¬ø.˜O¾ôd\RZSäéæR¤Ô9ˆš¾}õ¿€m‘OùŽÝ—n/û£ŠAiºJœÙè&,´Cò¬Aç—ìKÎùëË´¢•¤=Þ×!Ž—Š—’¾!0fèÖ"2Lfþ‡ke\IEõ†Õ ½s×l ¿±$¥©î”iRCúb^oW•|˜üS?Åík™•Ëo}`Ô•tf£²YÑiÞºƒ^{Ê/W;RV¿èÔ[ Äp¬¶ uNˆp*~.üB¸öüÙt™â^‹3ð:£"ý嫘Yˆw€E¨qI‡ˆ j¸1jdb=C8Ý‹û¤”[f½¥ýAsàõFï óEKürïŸxø‘a/ÉS$ è.›óð™¤Ï Wp’îÃcÂòëðÁ{^{ViÄùtI…P/<›b˜†q—›E‚,mÞ¢C¦þwãþ:•nÜ9/¿ƳÒUÌò]š=Vbi…¯‹ 1±/,´Üÿ8%ý¯Ì+ $€0Þ—xu Ú6.”va©úí*¢Ô8 UågT?»y47ù ÅqÏ ˜¾ÌþO7a2ñ Y·×˜Ó¤³Ó!´AŸ£Qcqb~Z;¢Ýʰµ4TG `ÿú­\¼ÁÎ6hÉÔ gh5.ò°ŒâiSé7GO‰Êão‚tÆll¢ìò}mb‡ÔH½BéoŠ7Vök(—ž2TaäËD÷" …a„l7ó“s{½·1ܰǰLÉ9S2ªfá?‡R¡Nµ ¶`¼½Ñݬ¨¤ÇQd&î6¶™Ú!ŸF.ëÇO>ŒòöáÙöJjo;ÃÍ’wʆ“í¬ˆúÞS±¡hRMN¼!'´×+Ú3Nq«beMô¿öÌ‹IêSÉÌ£©ýô‘ˆÖÌ|÷õì3$ ¾š/°…1ˆ;?ðs?òß´æ°ÿeŽ;hä„,lкË8öŽW ñ­… üøü—™áG@h˯ž‰”•‡.ät1 €ySx9M´}¡³ö½sÍhzÚÕYpLkÔö½/ÆÀ9—’nËÔ“"XŒÀW}HÝ™3 ‰‰à!Æ‘CŠ2ººéˆípúÕ.úÔ£¡Ì¹òè[ÿG% ÿhC˜òb%F?"w0†ú‹¹&h]¬G® µgÖ¢B„ØR‹6åiÅæ°Ì7\úd‰ôõäg *#}?E/¼zþDðÂQÂmÕš£;7nýWL°Å º›¬¾¿zUä{GKáÎotÚ:¦¬_íÍ,ÿ9ùŒ ¬ùb°{]wï’;,9/iÌÊú¤V„´ÿvÔ?UµPÒ¿uˆ#Ûк¢eD u;Œ>–Fê£èüS~L:#ÑÔOel.(y¿–Ña¥ÿëÞíú]jGÓ“¦7 4Š»û¤ùŸJÉü2¶yŸ|q•öÐ7­Eë3`ÒhqÉãpcßá'ì|àÑŽÐKÛhj",Ó™°îßâåïà*·Y3¸E1¹MC!çÕnÅLJsœSÄËRiܯ°Ùƒ†^’XäÈF.L›w×X6›çƒû¥UÒ'NŠÌ¾Dz"}ƒhl£õ‡Ò’yõb‰÷`eñKNUI7™ÖƒÀ{V=ûȸèÍ6Ÿl¿’¤Íz3A†…éhÊÆµ\FèUÈ;Û¬³¾sÊ Äª›ÓÊ×±ÿqà,ûƒöÐζ¾~ ….@›˜Î`Â?™3…™Mnd µàÐå)* i4FÊôÚêµZÝ%zÎ\Åb]Ò®Ín 6;÷: í+¦·©sy9h<ó-w:­½Q/É>¾(_·DªåÄí°$ÑB‘A~¸kòæôéϪþ$VéÐ}Ú×J¹»Å­q+!ùB™÷¶uXù7A  QxÖR8ЮœiÑ.,ÂÅòwÈJT9vµI½W…ˆ»«A/@§ÐO®¨Cå‚ º‹ãä¶I8|‰ë9Éä<¿ò~8"úÂfÔ4–ýžÕûæ®x†ÎÍbý×ÈØî“î<†xh¾<ßõû¦hG‹Ž?q˜²r%Ç-Í]~šd:qÔǽ$zu|ɹ™K˜ªó"? ¡ÀÕnèÆÓÌqPÚKæ4S'r“ân˜Qˆí2]3] énšßG]üsI„õK[u„)§š…82÷xº G²9ëhYÊK Eƒ”¤«?;âÙ¦ÀY³§Ì”<¾Ü")†r-Ž"QžIKíè5Œ‚J1ø€cì<µ®éJz{ñF„)B寚ߗNWÿ9€F—Xˆ,fVQß·Y¥°aA'–³†¥6¶àmûŽ=-ùò¦ÚâQèW"ýA›œQÏE’!F^r$ÃR¸ø…~kq>Boÿnpy½t±ÏBÿMQ5øê͇Ëiÿ$§¼FãWâ£êâ)/7c Eè *mYý¦†ß!̘äµ8Á¹": *÷aŽ¢\±Jz#–Cv•~irH" B+Þ¸5Mº‡½ÓÛP sŠuðêkŠn‰bBŲ¬kWÌS] ¡Ù—ëYÔj,ô¯©7Ðôc(Èùd¦ôÞ?? Ñï0„§ÜUd@43õT•à‘î4מ.9ÇãõæìM×3P ·'¼ ý[ze‘à«i³I׌^rÂo‰èöR›#à¬é„æPàÍ]I¬Ò,ë‰{ý³£,²îs¾“ç±.pG`E2þ#ñ !îdæ“RmûÔuI€àãowh?o!“¹§9¯ÍûÑ™ç…âÂ{€{â\‘OOérÃÛ÷ÜäÄ骞_r±•? ¼üµá“Âù°ÉE (µð‚ׂ£{¾ù– 9îŸHláŽÌq”ˆ ®7'|=¢Ð¥ŸÕp¼Œv à&ìÅ‹¬ºYŠìL­%ª¯„u² Z íþ*{aSckP*ƒ~ìÍ,ÒY_·}Ù7ÙÕ\,Åǘ‰Ú#ótÕÞ´óé°üõ‰˜î ÔùÜICC>i2±&[%îYäµóCž ÔÆj:QoŒ|øÑïéøÐ3J”ç6ÔWiI‘oï’1ÔåÐÞtÔ;h[Ë ªOÎó×,CjL³'íѰâ–~•}òîÇdrò\vïIN¸u;ï2V^Š&jÀ/[ÜþäúŽ¥O%Þﻇl–áäq]’A[¾æÕÛ_jØ+ßëÁú–…Ê<•[Œs+•ÖÚ¾ÝÄy©·ìM´þç‘z|å•Ò,úUblŠ ÀgÎ “¾š]õC—¦0™½FÈP« ³4˜Í ¼³°¶Z: •¸ ‹P~9µ!ˆEsΘן9Oó’¥F1–"ŽÝo#³™Í<€Šƒ"P(Æ A˜!‰“]Òø‚>fÎè—뉼®âÄ…¦Üÿø»ÓJ_Å€c‹ PŒÙÿlaf@j8ø.ü°Xt,<ä£=sûްVc.ZÃ; V\ÂÕ«oO þÍ ²ëzƒhðm?_’‹Ø–¡ÓЬ[?ÚËÔ?èŸBÀÈT¾ßÁ2{fj ž/ûQa©›Ð©s*OšÞt³’C"ˆSyÅ¿\0,Êó¶6U±b.7öºûÎ16HsJG“S:^bR¦q ‘°¢p—½òþ5ÍwöÑgtS;ñm_D|O·üü6Q%ñ½RE¸D1\°„dôØ’ã÷á±v_~T\˜«û]UÎóæ Â/YÇgáƒè}" Ÿñ‰×¼J7fkh6DZ0ôm"F²MgÖ³ ×@÷¼„näÉQ]Ë †ç½fu°¹¸ûXuÛ[¡,÷Û¨:Gr¹®x’Àà¸7Ø ÈUÖ¼WŽïV?©þyÎŒ$—Õ«€öX»5S¶QGÆcœ?Ó¢f®°Pz1Àa j JukðÚÁíHÕ)"/)“™^a£Oäpêc8:b9®WÞ°zÆ3U-b˜†Omãö÷ÿ}¾³ß·Ò“öúxçðîªû}iï·Ñ®û},1}½ñ_o¬öúkPPã-"|%í»4ùá2/’ öÝ¢€Lƒfüž‰Ýs R2n¯n¿eTú) voúB°kìD0¸±!;²ãÈçfto‡Öx7Þe0Ú,-(ñ¹Gp†6M{¯’K•eöV»ä=ÿ¥Ëj_éßúl¼;.þñgÏAHÆS`Í zDíàËï·ß#·UîÕ5´]œÌº='=ÙT(²|¢¼•‘31u;É¥Òó¬ h]=*·—¯ìž0õ|ó¼—pK‘qKTÖQ:îÍs£ó¦_VA¶èu%¾à²¥é^ZÏoÝ•_#N×â¡6^ M®²ÚLkÇÖàËÓ“RNׇÆâøUO¨?-ºƒÐôG*lÈÍ^ MLɾ‡6†£Ï8+ìoÁÿ?gûcÞ¯bìƒÒ@LU]R'éÞñ$&öâ+ÌSõR¯/çwuÈ¿eª7Q'`„£‰•ñÉô®Ôœ% ä‘TüOé‚—=­ûA¦:r¤Ã´COBZ“æÂŸ9ue‰n‘eŸoPbdb8xšŸ·Œð–ãjƒÁxácz¤šïL(*âœó­Œ‚I^rŒ‚;#3¢!Ë m‰,ëZþî@ãc4«ºcuÄA*RU,“h§œÆµÄç5€â†Át_e¦|̯NÉÙôæ‹æWüÛ]V²Ì {fáÈ¡$¤˜¸ secÚ%GÜ…_†íú'™‰…Œâ¼ Ê6HÖ,-y¹™³ÈÖÁ›­Vm1³çøçšÈm9[áLà€«Ñ ù³k4‹VÂSet!@’§A¬ÉKö^n‘Š€“¡{†MÝŽ®Ð/~Àç¸% ì­È²âA %‰á­K)[€¼ša×eíTnUéñÔÄ€V[e‡ Ž”`Aõ3Á­J’ˆûÇ|Þ[º“T^)(dÉs,8û˜y¿*I"0vrÓ¸òÆÕÞ- ‚DC3Ϲ¹UàÅz#=¡«Òçâr%&cÙãg ª™›åý@;÷¥ÂI³vufoÖJú¿€îŠ\A\7àÅK…AÃQaõNNA93Éx2új’¼`Ò§ÜüuW™+›9Œ¬²Í‹ß„n„[Äþ’j¤v#¦ZJQèûÜoSyÇäaðJÉÎc7ÑÆøú $‡+‹g¬†ôü,ÝÙ76ô»:6”dä1'f“ª¨Ô˜’²3ÔGÀƒ5f|ă±ÐØN¡óWW„h&ÊÐ$‘7’EŒ{È_»Xv‡~ã4ÈãÑ«x]ûÒ¼¾ýýÇÏ„VRcH¶Bø¯ì¼=ÅD¹w%†\a =nã%Œ2x)I.ÈÛIok´YaHBóFe±Á/–YÎÒDòd1O[\îŸÌïΕ¨ /ÝD[PÐ# \¶³gÚ0YØÃݾ^Ñìþ°F°B½¡a~©×0uCÔÖéÁ¶.ouŒÆÏ¸ç¤p;—ÑV›yº/µ8lú„ ÏóMººEhQ FàŒ½¹ü—\´ýF‚çi½YË×jÁ49Êw!¿ˆ4Úµ>Ld0’“…Ú¹ðtž0@¾ìÓˆS‰×ÔS#´ŒÉš¯‹&8®ô> YÁ];s!wªò#‚JòǪ0ƒuÎÒ‘ÄpÐ2d™Ã¬¤Bÿb!‰2·ÉqèD•%@ú¬oùFGæØVüfÔÛa;à( ¸û&’êÔ!bç¾û8Cw$–}ft¶!çÀ}z ÇzJ¬Ñ¡E§?†„¦ 7?¨Nê÷îÅ%³$ï¡Cã÷]âßµŸ‘N™È×ËÌ1|×¶A[’õÞƒ×>ÝÊÇ©cò1ö33à0×fK)ü”lRë+p¦%ZPŒ0¤úNèÈ×Wýã›´ßV´<-$Ÿ¯qìOå Òœ:Añþîu~i KoWMò$n8Æ(R¹âÚ—À”q_+xÏ<·ÛRm0‚Ú‹Pn½‚¤à2 ‹™»i°B8‚³· ˆFÙ^óEìÊpd.׆iÔ’€ŽºÎ²ÊKÌà·=‹¾—[ ï\^ºÁ hö6@7¡–4 )á­³Žü¸†÷a˶xí›ØLØ,œ)c„£éž0²ï—Z†*Ìy-EÍ8Šmd|Ó1'ºgE¶©„ÑÆ?Òò–¸Êè²éæö;« …¡É„¤Wæ`e×õÅqhSÐ' ¨ "9ÇìuW“„jzvoiQëLk:Ôý‡ÞØ¾È ïÁ ¿;Òß9YÝÕ%ÃÄ.õ…ÙgûÇž-ªySoM1:ߟµòi«úq‹¾_nˆÇÞÛÒe9hƒ3;s0ÔÛñŽºÝ÷å+ 1òmÉîsFú˜8šÖG]˜c_uÑ–ôz$ fÉNÌDäfàötÉ膠BÏ m޶߬ù§Dú"²jvú`ñàAêä3ÈRwæ‚Ášb è…ñ¡Àº#îÓ¦âFÚH¢–ßå¥q˜+ ܺæcÙ·„öDî›ÖŽÙó—çåI@oÙÙñ`ᆟqôôÞVò1ZîïÃ?÷½#õÓžô¹Lt2óÜǶO'7F„7Å’IYó âvá °|(—wftrÌôæ5› £ö=É[){ïb ¡ÔâiììöÕÚZä¾÷á“iîaµ{ú·]Bh¨z'(áKçæcÏM è:§¨4á¤:ÑR¹ÌÞÏœÜ +OGä(Bÿi ¦I÷„›ôQE8‘'«sìÙåÙ3‡Qí JC0ã D[:°¿¤¢|’A.Aߟt¿̸S]o%Q¤SE…ê9ÄÅsx 4Eׯ÷‰ïxöÜ\¦ª¥¥WõŸ²p/¼Ü}Õà19JÛØ)r½<ÿ4Uj¥.õÌYâpŒf€qàX;[xÝûŽ41vW:î)NRã§ñ}Ú;ã÷#Ÿe¶}s™Ëµíèç‰y")O2ÇÅ1ÖŽoäëè>¯E—qªPH¶B‰ß»îÃÛÜ ü/ØÒ5F¦YO9Hû?JP›+:SAËöÛFLãJ?%SÚæa3-¹³ÏÓàÖÑàÄó u01s½A|ꑬL`x+Y2ßZ»Ìý¢2nÞÌåØâ3¨ú_Uô+vê8²ÁÀ÷ÑÙöfŽ[WO÷ÙâÆ—M2çÊö/êäLQî Ïg»•2àöàÍ¡›柔HÅÁZæê&y$d×Ùv†Ùh¬û½iìÄ-vEï­ná3jWŒc„´åÎHݨzÔþs%·Æ‘!N¨ˆ$Ç‹·ð¦ ¢¢®NÂWçj àZ‰ÿ ²©¿ÔÁÃo`…b^õoI/4æNûHMîè(©jT»å{[ñpÜ:†·†ˆ? B3•» Ó\õƇ!Õ\õì6‰ :ñY>•`JýÛê˜$ (³6ý³&8NËȨ:R ¯áÁE«¼Ç»™ÀQÓ›²Ùš›{ÿCšé?Ê*A¦”"Îä“^gâ%ÆR(1Í’þÇ#ˆ¡ƒšÇ«@ue,˜×KºØ|‹€ ­Äb•©ï4Hzß‘é“Dzº“š”BEâåswÌ$!2îr<¯N‚Å2̪ͩ`âó¢òSn§ ”¬á{ƒ±è(0U9úã`™i8å-òFD¥•ÆWã.«Ñ_ˆ&¼Uy¦p2“jhPËáሎI‘yË6¿ÖŽ’²¥½‘ʫ柱1å¸ðlÆ—¯i¿zP)œæGLè/ÛÕù)–àn/¿káÉ"ØN¶ :óéÙjSBŽ*¥˾Ș\$Hèæ1w&ÏÊ<ò¼?“•.4®ÓÛrSY¸*¥€ˆòñF8° ¨éɶ¬LV§n„ó‹Ô)(?lÊNtbÛZRr5ð«õ”í¸Xš•ÞP!`(?¿sh‘µÇ“z«ƽӷzŒ, Þù'š‹óS|º£€ÝÈÉÐÏ"cpÆ/Ä$àÕ”t¡ŠÕ6ýO[ëtZhæÔ0¡qá^v4³êfS:½Ð=Æ^í0~£ÓÏ^¬°äR^ £\¡v$Ìê…T’üì5+⢜5ÜVQÛÔ(>>ê¸^Ë@Ö%µ¤žX6H€!`¾inb]…Ðý“N˜LèJœcü@Z|}v†"^GÜñÁ1ýζþ£5®yöQ± Ââ°ÔÙŸ6È©f•‚ŽdÞÖ·Ü\¶‘Ï<0-YLw`Ñ“ª8øJ!KE6¦.•)KäY/ЮŒO jm,â*­1”¿³JØ´RëeZh¨kÚh©\#¾ÝÇ”;Ê ëvK}fZ—±Sm„×`Dón”@º×ïÚóS%ñtõўP KÉÞ–În;9ÖvÁ ò"nGŠÃêµ{€®ö:…½DÉ»˜LÓ@ùQSV­*–˜®‰õ?’Õîe·)"Ëg¨×°ZµTÇ'¡U»X˜ØŒš”Öf¢HÒH·îj†my{nüp"iÀ{8ι“Ûˆ´ïvP'—¢OÙÿ œ4ÖÐUD-2_¿«"ÆžÚe]m%ÄK㢻?^Eý®T>Ã/Ì.E=|™f¬î¸±ÞiNÑ3( YÓS½ý·köfÅ„ôô0£3¾ÿ\J[6×¶1¡N´ÎòõPð ØX§›"ž]j2—Ó¨©ö˜ÁãÝ­ûÕ/q€Â“©Ö}¨×âUù½us1 é‹bŽÏï//"8ÿjIC`BHgáõȦVWéf¹TÁC[#Ü>Ý¿ý£°Wúý4¨RþÊÏPŒòñðýÍfïBí=äð3üé4Еo–­‘çà ùýÖFj~´XŽzÑV£ÃÑ©ûî¦;4Uô÷7çÖ©£‹±…Ú!-ÃV€Ð Š~7­ Iˆ,½:Ýf³$¼÷<ÔÑUðŒÛY±ÇìõѪœÊ­–" êù¢ÆgDácšk¶7ß‹WD>Ê0ÂCä#‘¡™‹aiÄšDN‡8p4‰LP@‡†mŠõQšrS¦l ”]4ͤÿCËÀÒ“…ê“)Ì6ú$¯Á£AƒãGX¨8§4ì¦ËF77*‘ê¨ñUÿ.q8°!* Lú–yZb1ù ’BŸ;ŸRø€_Öë«§v³q@Ýð §yj©'>: ™Mi<.Æò¸~b€Íj™Ù`M8xHIÅþŒ*]6gê/ièMŠ"cçÐ÷@»–j‡ç³ö`oþ_ÃMʘ‰ý «ÈJ¯¨vrÄXfº½»MºÉK^ålßÉ4¸çÑÀ<êëÿJcí^uÞ¨WæÝ{XrvZ¬QOJErí1zþ[úBâãöÿ?LŽÒdЋo¨8¶6Öm¥sÝQõ~lMZ‚¸Ïfú‹ÏàVzú(/á°óF‚ëøâáPd¥ˆÒüTnyr®g©ØôJg½± K³„ðN!Û²èÜ2Õ#˜e "a#¨áù̆Õ£(¨Wº Žca6RÌ×n­.Ô£Ž^ìk\íÄN=÷_1d°K´ª<ž+˜:n¿"fÈãM³Ó÷iݳéÍÕÚÅ5 qJ¦M‰= j`W…\jÔïõy½ØºøCóü*¶¹ŸM‡šdàÆaÙ(nju±_¸¹rŒèßÌ¿T32JŸÕ–™À õæo¹ämšç£B-A5#Ζm4­*–x ÐÑl´I•€½.ØO‚ѳxÀ¥²4•D«{u›àåø¬YÉdù=9-ÿüfÚ•;‹„¨yÜC=ôÃÇ û³š­ÚQjÿzy=’9sëH&J‚|âÙD¨³…Á±¤ÐX*†û”6)nÍÅ…r‚݃.{žˆ4¹3\ÔþBÑLÆ=dJÛÌ8`s}Hbf‘>à‰8 ·ÝæMa”ý¸•œ 2>ò™SKA¦4£³Ó@_¯mkbQï‹Ú|á¤Ä–Y¦&eMþ ÇZºma¤*û¯ª»åÐw˜ Srä3Àz.ÑoMŸŽ¯ä“$ÙâB$—Yê c@Ä·n¬2ÿÇ;Ký;9Ç*ô‡(íÓêƒäù{.¾–§I{D¯úíÙÅ.¨S0 5ñ&‘̧ÔñÝmýÈ<žÌ£ý@,¡ºDð`re”Œw2cÌe sŠá†Žm8¸îQ>¥d#gøÍÙ™j¯W1 ÈâîïOô2/ûE/bƒè©cé̪çußþ¯ØÒöIÿ.ëÁ™èü­AkÊ%¾Ht¸Âvú¬œ³ºÍ޹m˜«°âñš˜A±\DÆóiPIïîe&¢Grõ+º ï•ËËŠÀÀMÙ 6COtã,õe_淅Ŧ@¶Y°Hû…_Ò|nïd‘1÷Ûì»{‹÷ûùGŒthÁ˜HèP‘Œõòã]?VhèE^f¦}ör<¬v¾`^«€’[Èdïõ2xZ©æ¬Ý¸V‹›äÏÙÌ H±Bý²×4áÕè|™ÖX?1M^ÊÀ3Õõ™A¤³ª´crmyD­@Le²Ž˜üN˜Ö¯(ã•—ÕæƒÈj0/5ÁÔ“QXD%smr¹4˜Ûã¤äKO8°†ãì£Yé1ËLcëS{íC×crä߇÷šÝ¨bNŸÕ¹Àe:u±^`Ù6ÎtóùÀ¡$*€‡ƒF¹ºe“K-yІ²,Pö=ŽGYjÁ3\:ë6™œs«áÇ\D¬\¢÷ë3*nEè’À:„•¼j.&¸¨½TBòÁGï:(~óÔž¸­ _É£/øÓ ™‘Æ<ðmžÛø¢`ÑpkÆÝNzõ@òûñ¶Š"_7Ó bôÐë¼SÚúÁ KÅŽ(.Þé4¾ Zk/ F± A^‘N®w<­dǽ&Tì‹}FYÚ'ô)ØW›#ž#¤J0uìbn‘¼.(1ÊM  š³ø¢IÆ«­û¦_‚œ°5…¸å¡·ýr¼6÷#á^õw2äÏç­ñ¡Á›3¡® åá`¤ͼ6éKnyc¨˜¶eJgèÑ«ýxéÄ|mðX ó0H€¦BÉuy+Ðf»àTÐúî"ºžFOñŠÚó³ÿ ‡M‹z ð0‚t ¬$Æòní(to¤¨Ñ²7VF‡‘YÎ+Ÿ,ÎOY|äD¬†_oxj\ϳt±ÛF²')^ ÛÖï5âdC¶ .L×§oéåR€…¾ö­DÍãΡEåzÍŒÕöW¦ƒ”‰þæû·ï©£\­„ß•ÿK3X@Mbs_{:rhF¿{kzÀR9¯SlËDR…^Î0*€àNƒ9]y”­PrX[6Mâ»Øm‚O¡¬t#ž Gž”zfK¨E}§ªˆÌZe0…ÄKžÝq¥´5ú‰4ÔŠ+…×?(0¢ê“K Ë“£ötRIÜ@°®á/±ýÏ,ƒŸàÏïœl*Oèµ`¡ÙZ {ÿQj@ î_LCDtcÅYq®>·29z¢æêÅ“w¬ŒmVö(tò‰}.0Q¸e”©W;Fµ¬½‘áΌ­¬Œã÷Önãh¾â½ÁÑS/±kͼ, Ù ÈI•¦— Ò^6øé¨õ¤¸*gÖf6¹ª~¶Ýâm5cMLþ6ï(ö OÒȬoàì9¬ë’ú º—S²Bóȸ`É«$¨¾3 ²Ró# ôAÐʰ@ÍZñëò{Š‘‘C1ôd`›S +a¹Â?²—í}á8—Èq³³˪ÊÔÆ9C…OCŒj–fÅðGZ”€Ý“¯dá°9Z3âÄ"Œ¿X#‡6ñ˜Ÿ¬¦–*SW|/yð¹[˜ÛUm‹1¢ùÍÄG9"=ƒqœ'нJ‡»\6p¿lªV9˜‰ŒúéÐY„É>Ô'îÝ”“Fį¼óY†qW£ãPüðJ‰Nè=¥äÔ(-Â&¹ˆô q`l¡î¼òòŒ±²/_~r—@ºP€W´÷;ËÞŸè#öê-&¯Ib5†Vò“1¥ŠÑ…³’wYÙG/04|4ïâëtH2{boì7ÊìÀÆŸ{kv;Ck©<î[ËÁÿij OÉÕŽüG¤—‚É|þ"¸&®Ë§®«oKÊ£Jds›ÍÂ$…”«‡)0þQ,¢¿B|Ȫ»E:N*.oäÉŒzÿwÅÿ0®P_Ž~ÄB׎øìÌN꺳ãݵéµQ<&RÈ+SéQàG9zAGGõiÄOÕºFŠ?5s‚;ù ð¦Ç­Ú¹ù’4Ð —cžþ `”ù–Âx¡Ëâ;mì^# Çš¥ÐArÆ6=FûËP Y°>Üy-Òßá IÅ^Í’©µu¯}©¨¢ex*éroDë§ìAé5p'ž¶èDcÙh{\ýàAŒ¹õÞ°ôÙJËd8'XÞØF¾ïAºI ¹ꬄ¹¯í¼|‡++†@œ4³µ¡0XŒzQ0½: ëë*>P:]2-/»ÎÀJô6Uª5{¼—Gƒ©Òa¶aûðq[Uýç9Eí‹2¨ãü®æýœ³)p¢Ôû±)Õ¡M+/³(¿åºÜ }r13?óªPÆ›´r„úƒ×"‡ž?¶”˜sD¡$UoZ¼•í¸M4‡ù¨Êm%È5†‹[LNÖæÖ£2¢m~^ÐÃҶò{H %çG¦”uwŠéýŠ\ágþt ˆeÎ VÒ7¹õ$4TâùÇÝ+_€%Ƨ§‚[òß †4æ Šáø)øåˆ^RÉß³Ü)Ægö#™ åÂ]ùމÆ4ML$!X…'°%—…ë½Û“âœþ¦¨ïÿ!®¶²{؆£•Î!ÉÆ”Œ›ND6Z£g³Æ¶jÏd’tîAAÇ!HH{ÝowŽå¿‹ ñZÏdt¬\íã¾T}¢¥;”:l1Äi(U–’ÿÄØ›³?tÖ6ýDsûª.oøÛœ&³.‘ÑüEM–«»9Œ—~ëGIm(¦hd-Ñj4ÈøÓÑky‡.êÔý4Ï{d³p#>Î×ß^„ó¦™áò27 Ï_ßLãC…>@Ô†gÞ¥÷ççPwtA•6ôÿ{‡ ¥ùž¨ÝÛ·±NíE|¦±6¦i÷³ŽÖÒ6®mF"Ñ\œ¥­æ£"œàXó{¬ž Ú´9Ñÿ!®ò?Æ8ƒÁÛ_­¨8â≿!Ÿylq<Ì@á©/)Ô;Û{‡Í^OUé¸G~`‘–É6‰Žz€OY€£ì©3>+O7’$FÌÛ Ù5—óQæÎÂ<_´ó^»X¸)ÞníÞzZŒ‘81ä˜'_å ‰#¯Y“äR›kCÄ Ì;¬pë ôAX¾Lޝ®bmŠydü«±û»²g<Ü8Ó qžØ¥ ~57„o µ§Ôý2a¬DúìÆIòp(HÄ·/Än\ñ”¤62¸ó6¦ƒ–¸”Œpƒ7ùëDr€ì(Ü—Xj€Ÿ¦,JÌö¸˜”Òxë8“ijæêE9¡ãirwή|ñÍ­ý@–²ÔŒšÌJY¸ó­ß‘—M½Ì>‰0ýy‹¼ì®mˆ‚óf£‘—Ld\oÑ…"ÍÌÙ¹³û^T…OÐ/½ÁX7ÚœCùsžÖ±ýàXšrª.Î(¢t6†„gU2få†É ?Ò}^¾¯u†SAy½^@݃V4Lš‡S-} ²¡»Š€vd”% dURg~ /¢5nr•Iܾ2>ýi;>+Ž­Y½ ‡"¯_$ŠE›÷>s4Eø6MÎ|Ü…D¼àÖv20Ñ/*2Z3. ÃC3Z.¶Ù¤VÓ˜ù,Z¬h{&uû\zîúäxdþÿ^ß ý¦{ Aþ°N:/@“Áh—í”Kn]˜ë祶¾m fä:£$#ÿpHSÏÍŽaJrÕ¨ ´&=ÿ *æÎ8Üa0dWãìùOÓñÖ£`ëªUo™Fe#6GiaAàŠúMt—qy±kŒ I…wöL°àˆßÉʰŸÊw=ÚýÈ?ìžÄ~hÊp»…¾d e ~¹ Ò/ç£ë©B>®ãÈ ï³#±ì9 èe/Çoh_›±¸VƒJ?ÀLPŒÚH¹ÔÖð®;ó2ÅÃô•‰Î5Ÿé1F,:é¤À$ûx£“{µXW~ØÇ¡ ` 6­l´°5›³¯á]ç2ç¨f²Üó@“y~Ô˜` Ol&ÒGqeWŽ‘J5ïä¡Tà…E¬ÙGõ·Ýµñ”â:ÌÈ/Ìá U/yï†V2I&ÝŽ'½ÉBn`&àõ“ñVOŒJfX[&ñÁo!®pµŒFF¾?T²ºÕªzrÀ7q¦dÄøU¹®¥Ý´ØýDÍäÛ„‰\¬¡müd «ËGµÁÙ{ƒdí¾Í"Ü)nvÑ.«ËI›SsJljì³u†™½d…€U ê)ëFJE«üJ§š¿úÁl>#Í·=þPOt ò\’ÙϔΰÛFNºü{4­Ì]gûn|"Ïb„1¦MÃk,ŽÚgµEÚ’”z ¾ŽÀ#8o©¡…ó:v‡ïŒ!!­ø ¿Ã–‰|¥èÞ6´D¿‰<ž0q‹X¼"µ¿‰SÆ­æŽ,¡T‡Z ”ýYÛ¿'Ðd,>öLú5j$§[ó = Cvn‹—QÂÚî^àÆ,¦^±ëNK (åÁ"× DÇpÂû˜3 ÅwÌ.BñÍÛ)§n‘—¶š5ù)– ÈÖ0¢¼w?ä~º•a÷Zëí~èã"H»¬vë¶Ú¶îÕ•¯¤½Òú¡—8=­** ÓF.ÚÒ=©šÙš;sØã¨Á„’xÊ2‘voBâêæYÀ'óñèÁß$ÁïzgHà "Ô)Û_@²Ü‘†v´Õx°C`‰£Ù¡å ®v‰ ü'›ò-·>V+jJøB¯Qš·•C7…% vݭzïÊwvüÈQ:ø—gc˜üÃÚ€èWG#¾ËqrçÅf­¢üº$á¼€à§@ëhiI›–¥ËÚ¹Ûq`õK1’i§Ÿ{myû¬TÛ”Ko4@n²¹AyˆªN™úÍ€ud+™Mta¥Û‘ñ'¾¬¦6 ‹ó*=«€º’íÁËÃÚ†£Ãa©[þÂë•|"­ øø7NÄ#EÙn‹‰³ä‚–^'þ N1S6.2k:¼V*?‰Pèe*5eöîe\‚±è¥ÏBÙ狘aú‹µ›­êŒpš™vÍBB¡á £5½¥é¹+˜êoƸ aÀHÇâr@jÚÌÍ"(®C¦ÜƒL9ÞVYu˜U†yS€ežBG·©›;a¿èŸÏX!C„‰¸îÎ鄉ÞI‚½©PûT®•I'»Ç‹-NçÆh<ˆ‹šQ<¼™ †&¯:<ðÇËɤ¡- Ï‚(GÒæú¹LÕ€:ZM’M±CTö¸Òóâ÷€î*lñ…Ê#¸ÅªŽY~È3†j„S¥h"ß]8B"âÿ„+i@Y‡(ØóVs8ÜŽ ~{ó1cÝL©‡?2ÀÁû ÿ1å•?U?3n2а!«¯qìTë™$²¤ò>·Å´‡Ó'î!$XÌ1ò–vˆõÊ …äPS`ljAhLØH¤q¼ÔâÓÆ в8™*YJdË”þ.3‚Ko¦’ÃîH½˜u/UÏKÕÑÀE¶’ûºÝË—P0 ¶zÒv‚Ð0Ô®[ Á”b#I²é㊲S4]\àjNÎO”»$ó¯‹Ú,ÌÃëš°NÅ·ˆuNb ×nFéX:íߺÓÔóúÖ¢Ì/º –Í}„jÖÀæ´\~°šû "é˜ SêÔKw±.ÅÞ.>óôÈÛuñâä‘Ð0“’Ü g¼ßP*:b/‚èCFvâ~)‹¨ð™5y‹­µâÏêÝ®ú`D³°  …+¯—ybÆt×7£xû)¸Š“­ãœ”NN͸AíÃd^ÌJ/„SÃÈ?‚˜lŒ8Sr·CÙeäÄþlŸØcqüRš©³,Y.oÖæñäj wR—pƯeÒm,€ã®¼X㱺A±€\¿ ôͰ;“ˆDžç•ñÎD>£¦¡3ÜÉ( ¿% u7H£Z¬É‚§¤†H˜#$ð–¬ØZ‹ˆwÌçË4¼Qk{/ýƒ˜ØXB*rx¾¬Æ_ CæšÌrÁê*µ}ÿ úöDÇ›¿4ƒ>.Õ°Á“ÂÌ7·èE´ ?›ÕØ”slct¡ìôÖßÍMôå»8=×LLdbªÜÆö¤j8^lß”"8’ñ…¦V·•6Œ4²ý§€w(`^Ó¨ŽOaŠ‘¶YÖªløš ÁC\C™^6/z»}yër2Èy¶wºÉ\²Ü †›û ,Ó#¬ÏëúšÐáu–«D©—W±¸\–`f}Ídöÿ ;-ŒÍõfW²r| R'ŽÆ&–Š*½‚€ˆ‹pÀƒsSá`š9­xùX»¶ƒ©á‡À;‘¡Øˆ±£æÊòÙkHnÅëU5§g] Ì%¶dEÚ$xo–ü´-x$µ[¥šžqkF ˜½]9‘O;Ödp{‘Ï$Ð훕Æ? nöólH)l¸ƒËoª”ŽôÙ„‰Ö¿±rŽI$y©‘Œ=; —æ~~ºH˜ëB(O”dªœ/ñÎÜYkÐr+NÂ7ú­‰ã©½¹Ê澦:ÑslAqÿYÒ–ì›ì).ýÕÜI“?íß}àPVSÐ5ðp‹+¨‹ìÌ9ÿ6j“ê!–òFí$£Š!y1wtv8³©pÕKÚF4þ5ì“F™à×ɨ`9Ý‘~—Äàž^ÆXcÇÏ3;¡üZ'ÒQU8Ïc×XfN¢@@Ö™A›õàô whÍÜŽ!¸cgsq+ãe¼°‡€P¥"ûm$Ž/}~lñAÁá¦6çù†ÈÞU Æ1LOW`‰×Hûä«3ö!øågAÛ'òÃD4ë?= ì«AtY7Ùø‘ M8¹^ sЪh`’³*= OM§‚/î‹s¢ƒfÿË4 —àŽœ¿ÉѧÿQ¡én¹]ìbǃ‚Æå¹´A™É-þWÚžBw™ò/¶ž’E4_Ä}Eë'Œõ†Øíe¤Aèìƒe_ÌJ‚Û¤ {p0Qõo§ÅFw<ÐÀÍï³â–øVn¦$sá¹Ñk<6xþ­6}ÚÚTëŒHJø!=?»é.Æ\t?M¼*)é4e„ŠkµS`ÕUÃ(¤D¬Ð•\ƒÞ‘ºs|mc Zô“*ìÅΖ­nÑ@‰\ök%WAq]œçÔ©9n8lé§E¤rò… >Iálf{T s‰'í0®àÂØn©ï‡Í­…yR¨@y0i>ŽÄ˜` GÂÀØ®_‰g̪ šºLWÙæQï~Јž•Òyð)ÞÜ—X‹æ,GÏ:O„{¥æTî YKæ‚GÜ®lôµ²»Çõ]¤ë·Ï:§:™Bã¿G¦“ØvøÔ>¹ð<îÞIð¯›NL)¥=Ø!©©¼™ëžž®s·ûzl<# 5å£û¯ãÛë_ £Ü$µYç2WsÏŒÙ#A³§H5¹À%y­$É`;S$Y²š3n]J~i–wVƒöÆPÄÇäD„Z¼Ï϶òû¼¢gg)òN=zœÞaƯ5Tr„蜌 ñ6lµúLéݽ2wzDD–"ôB°•¯©9Ï,jÒ$Å0§½€$HŠñï=³Ýì@"é9¿C˜^„§èm Y±l™Ørå{ñ_Ï^ßD:o«ÖöR8-¬;ËutG.?U¦áã.^L ùÃ6×/@¤Ýã–+c·[8"3ƒbA£÷Àf>¬Ûµ+Yݦ~?²†‘{#¸›t3 Ü>oØ¡°ˆc–›‘Û)÷p*ÈbF§ò‘sÓõ%#£f"5bjÿ›jSoÈß.ëÚ³iÄОšÃË$Å6Aùб”$ä¶Mœ1!‡^V–ƒÀ» 0ÕË[Ðõ3[²W²“Ö|úf¯o êæ4ä5[fÔ»¿*”-+Œ‘á ’¤å3- · k‘œ6ê@õ&ju3í“ihö—¶ž´rTI,ûšÙ÷7üÍҒ犜Åùi¤°“äZ†{©WOÃ3n”_¼öþ«Åª¡‚Œ¼ AƇæt_ß|½Â¥n/ò©Z©û,ÓÙZ@LÝMíÁýä7ºsyäjOø¥9ïT ÏQ[oë³XÇJ¾ªº&f Oã%{“Ò ÅŸE´Ç*ËmŽ©ÊêÁAÚmÒ¯¦Eg;8ÈAÈl»ýTOJŸØ’N±x ¼PˆøSç|_(MWƒ<‰é¶\D û ¤Ià Eà4ÓUõ¨jkûͳ„IÀ4ZV$;l² ”ïj îÿs^(aÁ)Àþºo»M¿ç}‰óÊB-Š(ÕÂG*TÝôFêýU¯~=㨩[Ÿs.¨¦_;ß‹/´0ü¨¼~X´’ÍÅ@rή¥'µU|™ˆß×K)4Qkœmÿq“NŠ—'¢ä†·B9wþðï&…•ª›•pê–ª{RëG)íEµkiTÔɌ¶³OâüWʼn]Ñ]ËeÝ\‡F߃ˆ/ î—=“ç`gžæ&;¶˜“a½‘(ÆGï .G×Á²É Ì‚ðÇ2E%SÏâÅ®Ûâïtš\Å¥ 8e(ˆ½D>ð í\ëjãi†3ó V¶À›2¾W0ëq/gI¯Éd¸Ì²Ë‹Ø tg+c+*óM’dÚ5ÙÞŒ]~—SC€w7¹"Ô¹XðýXbæ°”¯ 67?ˆµ ¢¿-*lZâ«_&ÄÚ·#¦­æOµ=È/p·e$q, ~ÿ^—¯Ð l,Óõ\ÞL"ô‚TTñE?òš,Y:mt¶þ›uù\ éæ–®#/í·}yá ›ãs@LW±sªòÀý¹Ö6ôß'ô‚é[yCæN8ŠÁ ¨šRúM·q’Û²i¥ÿeuÔòѪiÈ©òcÈCAxÀõ@Zs¼Žö~ ô–NMÞl³x!rÐÁÁ÷ÇB"(ŸöÈh $!âÁÓO¿û¢žÇ3¥\n8)”›B™ƒo¤AJ$‹Kf”_a§Š½Ó©‡ä`¡C|µ¤x”ƒçéÉùToð™ÀSéƒ&ŒE4mÑPƒŒÍÒ…ð» 4vŠ"‰§É7?9d¶Å½/¥ƒv—Ù!8à5Ñæ¼vyÖÎ ³h$;×t×È/f¬@rÇ«Œô°ß-_.6ÝÔdhvì¡_åAŽçúdXòüMî— ˜fôh°Ù½® oÎ"@A›AÈ~«*ÑwáÇÏE#ë^EH ȃ­bªÒËVÅÑ£/ª`EUšFꞪÂås95Tey‚¢WÑw“å¢ñå)Ó!««ê tØR6Ùh†xe~ÊW,&?þ!t¢ G™tõx²gV«¾Õ_³g[?­pÒȧ´Ž‹g·dÙžy½vY¿oTAì§dËF|Ò>1bq¿H׊„.^ü û_u9™Æ(E2Ö¯Hò¾xÄI¡ý&ħºCb@< ˜vû­Óüœà´xpì½Z¡<¼oîö¿âäü —ôé§žÅÒªk°D«*› ¼…›I/ehЂ=ìæeoÛA#+ ÛK4$Ÿ½˜>ãóÍ?ë:°»ˆ©çq­<³f!Ôv%qeÓÒ¨ÜaÐ’x+£s±­¼øx”˜Õë(D'ìsžY´GTž°ª0b5ète¨oEÉ›”‘æÒtä&çÁcÝÝyp–OHK\É›§–úôã˜-+©š¿Ë­OYXzUÆûT>ºR­Yí^B·9+C¸èk‘o§¡†â(^a×¾(>ÂËéfc­] ÑñG·2iÿzƒjÿ0ÞWk¼ÿn _µèéËihÜNIàÜ«¸H*HãÂnê/Æìñ‹Ä´Rß¿^e4ÏÓI(°…‚D‚}“ 7^@)'Í Ôl¾N˜gLa®ûú¹Ö–Ðuẖ@A-ôˆúˆøëO I3Þ{Ìx Ùº‹©%eU¶©8ÉhP¯7Æ‘D¢È›k!W G<©Ñá3€œÓ©6§>äÿ Ž¢¢eÐ…1{ˆ##åviÆnZ¿-}ÏÆdã姪տDÚX"ù˜1ÍJÿGÑM…åþ~!~@=?™ØÎÑæ²ÙìMØüÊ<‡˜ eü¬=ìƒê¸Øf Ï{c„O‘q,“orãÚ~TkžOaÜw'ßôŸs4Y¸fÝ&»§JC+´Cè&û¶ïŸ06Z缺$}$ÖS~Ü"cÆBeGÃøiUüV¥î½Å34Üñ9ÝR¸Û œgÑÝ/Z6í¶Ø1çKòt11ùöîÑÍC°²Om`×-™w³"N?_Ê*ÊÃ×fбšJø,äÉXô×&2Vr6:8Û\~d‘¢”D4xä™Wœ‡K{ÉíL>¯þ§üyò~ wàÅÅšàÈ=êÇ™pŽuxúþ wÝXÓ ~€¬\ËÌãÀ œ+„›äà„«]Рߟm}ŒÑìÅ~Çÿ,¶Ú7ZxVŸ‹É•îc½}«x±db•I,»Å$… ´E8lóajG¤¨FH­b¦çöŠ5uZH6ƺúT‘ä%|s#“©õä`DŸ^þ?"baS`uÓœgREˆä»[R‚m ûaXÓ¼îà±[º79Û‰”[Œ¹ýAUT8;}P㦠VSâªsu± ’F[Ò0øøFº4©ü,^I˜D‡{úë²B@{î­€Ú5TŸùùýêd°*vÏ'ÌÂfýƒ€OËþíuz–Œþ09:‡°­èéoަ*l¶5ØmsMí]'ç÷“±?%$Ölx7.ívbœ%޵ºê`T)šÈ#i3/@]ꎺ“Y6^¿‹¸5äxî†(X{@ ìµJ‰iµ‹¦?žšA¡IiÑÍ׉‰‰„M“t 5ƒXå";¡K-¬Q²ƒ.hüG¦q+Û„•'-ÂȹÈ}l-u*`Ž•+øq͘#(ì­î5^XËm>:\H½ã±/Sd¯LšY¨§`»÷Úúa ²I`U—¥ŽíÈûPÊäI¡øt¦åŒœL¹ÙN債øŸ¸ÒêMÉ#JNU®’>:YôìIæå‰è‰S™%Èƒ× >êÓ¦‚îq%ô îp~ä‚mºX¼=/" ~C+ðÊëíy{^ =hÏ9ÈÞQ˸!Ê`=~ä×Öññóþ OèãÊf$VÀhØTÕÅÓÝñ–‰O£°žÜIòe)üD˜ { ’ gd顜•£siW²Ä#ø\ td›“¹ÿs™ LíÝß²ÃÓ×2AL£Â)…YV×â_`Ît÷*rŠòæ-C² ×Åú0% ùè4éˆÃɃíc/ûaäsE×8F ^Dõ\‹æwE9jôDpÑ8æY:ôPÆÙÈàoY/¯/g©›êÊì71­•©#%#½èîñO謧µ›ðOëzþqt}Vç–~õ­é§Ûj_~\έŠxŠÉŸi@ñðí¥™ÕLÖÓíˆGÍ{‰¬bÖrATyæ~rÈ èa–ÈòVb 1u¬B Ä¥v^å"ÓúS@èlZlÖubð‘ ?'ùS4‡ª§_Ë‚®)¯Oº ö"JºQþ”èfÞ¾Fè»ð©¹îB?¿’Räÿ?§sš°|§Ždxn:‡— s¸Î}³ÔäE¬Hïf4™?0ù´¦räßJ³¯«Öœ†CaSÂÖrfJÏNßÂÃwÞEá[ Süâ+T/=G©X1,¯!ã5ñ‹Igð’C¾;•Có¬Ìqše)›Â#3Dj›|ÓWw?y¹Q°Ë³Šž]zÞ‘Îø­®¤Úâ`3{Õ¹jNÊøÝ¨2v—˹…κôçˆ[ÈeÑåhÚb¢–WLúrŽÂx®´SÝb3Ý_ó/Ž`ÿqÙ6òŽwM\ܹ×TŽå?JYç4ôÎ üNh+°}͸)ø ýk ÕçȦ[/Š×Øõ`ÓD†ñÏ"9 ÊöA:ã§]ч_”ÙÙýbí®KJ£rA;N_Tòˆâ ¦ýÉðBôNg°Õ‰¡eh…7|š˜E=$õ30–§Jr}>WƒŒ,yCÁ×Ü^@ñnª?_ {hn‰y„ó$ÛJ¢Q¯9ÍïÇ” xÀ ƒvcYo/×tQ4ÇŸï0)˜”Ú‡>d~M¦ºÚ˜!­ðZ…'¢e Ò¬ºeÉÆâ*t ¼¡ŠBéEþ6’Ïm˪î}S¾þ–BÓL¹)_ï÷M`㨜4Ò«2ýyR<‘h#›§0ËͪP«}},^$4¬êËË­ûpø çˆ±z†Øª?…Â3g²ýÀ’ˆ'ßC^~¢óNs{²WʇvèG!°ZøP˜W…ûüv ¾Pã ±T2Æí]‰jÃ>¦ÔÓYûìz$Ç@ŽE“zÆ•‚U3é¹8Aé%~4"Y¬þP,&x7ØfQÝ9ðÏLcü¿mX[hÏvm&7ŠCæåò­ÁfW³5ÔÖ`MªxBiJ¥cžÅ- ÌND>ïU“|XÝu„|›êœõâåøa×G\§%Ò6ðn¤Îd´iqý#Éfd¶^“m\UâëóàÜEwð‚Šžqy.µ£Ž!º%zùpÛM+€¾øÀ†8ΰ—-ÔžÛ…˜6<µïy8OÙ¦húLJ³èÑ][X|>U_Ž&Ѭ‘jû ‰æþÝ)ÀÕ H-C=H¯ŽŶT¥hõÉú¡Ô6“vG!ÃöÞÇ¿­ÞG .ªiv}¦Œô‘fx“Ž®ÁP¿ïWµSI.Tÿ6]P$”[Ùø½,í«Š( \ÛCƒõ.é¿hÑ—V×Á\º ¦©íëÙš’r'>Ï¥Œ³UŽh`,Ƕ˜¬Pz°½Î’”<,™º{¡;s>ú{ î§YªÓÊ„ØS:!+®wL¦·y%ÊléÓÎÙIÙk-çy"mæ˜ ;´Ûª>¼Œ5ÚZ…´ßW„Ån±-Ì.ŠV5Kç~EL½k/5"A€Ã5O,]‡"éaÓ4m}4.'†ü+àÔŠ>DZ)×wUƒg‘mzzÎl©ÇÈÛ C¨ÙkOÄÒb¼^ODç| †çe„+xH6¶7Só¾1œñ°Ž™=ðÉí²aFK³è× ƒSèåyRFÿarÏhÌe¡™®l¶ÙêÅ:Ëax®eÝæ¿DÄa¹“æªlÏ™þ ó²\+‹¼@]OËsœlÙ}úÆÛ+p"‘¿ ÌI‡¥®¤f©¤RàÈlüv ¯>%â?ö¶Â|yç$ü7bÊr½7Ò.%åƒÑ•'ßß›×jéo O+pGV »õRÐ]€8)GŒŒ³¨[û¢Ux4ó†®>Å6Öl¾‡.ôyXDõ"ÝÄx{ Õ¾e5ù¥P‘´§;I®Ðì!ô‡Y›Fâš™mð_ì꟤¢tÄÀ\ÜÇÊc“‡g‚Xâ[ô¸MðöÛâ[  \¾!ërÑ=¹ò)NíÐ ûO}üÒþÄ ó ¾Üñܳ¢O §¡LË^ÍlÊò´ÿnÂ:&=%—O ­¶AUË¬í§¬#õkƒ&L…äµfÝ.—ìèlyiJ¼¸’dräY­"{·®nû%t¡òý½BþßDÞû} ÿCÑ£öö›}]kO«ºoáן}½VO«ÒŸW¢ðî¾ûiáêÑü=b?…;ËöôAû}÷íôˆý¾÷ÕØ-õu}]Ó~Þ¨ùõuM>¯CŽû} /·ºiõQÃÕoøzßm9Åûzý¾€]öúèï««{êéÆ}]¨_oM½öôë>¯BŽ}]ÿ?‡j·ÛJÿ©÷ðôøþ0ð|.€•L5}}ñ?Îþ ‚ê')-uÉÒ_®z™(Ç"ŠÏ0«MãKíUHGŒVŸAÓÀg®ÆF©¥¶8Ýeu¡ò.P•Œ3i-h¬:‰[hû2T“µ©Ü´=vz6žãê­Œþ¶â¹ 8Ä“MƾÓ'Ì4òkÄÝûú^ìBœUgâ&µooŠÖR!SÏ>ÜPÔë«Bµ®O*åºOÆÑz°´sxÖ¡Ù6¾?&HoIÙ°¢&> ê²^ S‘,ÿ`I=è?]•3é;ìíÄÌ pùÒYûeU¦âØ ý»i·Ÿä·*ètô[WSB¶u}Ù1óüy`²Õ7d}k}À0+â¾ÛLŠhÂ!ï Á™ïpzYÍdÏy=C|§Î«o£/ Ž·Øª´úWHÿr[IRÚ¢ów…ž]I Uœ†È9ꇬ¼Ï|a‹9Eì ªUËáí#Û o9Úê!õv ê{Zä¯}RãÅÛ=ø›(»#@{¾D=J%{Ý”,¸´Ëmi“ý9?˜ƒ^8C$ƒ‹T¢º¬ùîuǹa¢‰uMÔÁ-E\EÆKhû‚¶i»{¢ÅLä]`  §®–gõÇÔ¼LÔÆÚ5kûEJ€Í¬æƒXoB2mrd&¬m‡j䨡;„:ˆ$–{e3g[èG µÀ{–ÛÎÃU‘ÉÁ½óh2Γ7“#É)„1ÚòïòLÊ•(ÌkÔjYØ3DµÅž©äCFÃÒ|¦ÀDЏ1c÷ó%zõ,Hã ˆÄW¨,K E–‰” I¬.é-(ëÍåoXƒ r â°²êjB´3×CUUÈ´ŽÑ«]ӧćܾNæ©âÃ'D¤1÷2ÈÛ¶PõBÀ ?›]'#G; •ùŽâ³Üƒ•ÌÒbý¤(ãmi_±Ô^3†TÌ yhi&“µ¶í†yò5Æ4 ¥*Õ¼ŠÇ,gvˆOuç½þ2ëßÉêó=pC‰þT Ñ`D¼$¥¹W4ï×\kLåðƒ"i¸U(jk"<•ýä\’8i³wú\ÑB~²Á@  Åì.Ò¬õÜ(ó½(ìñÄ:.÷4ñ­®U5‡éƒþL-*J¹ÊÇó„_aƒ(nF¥%C ,ö €ªôiæÔ…X%Â\Ìø• ¯M(õ!0´™ê|ܪå8M‰ÑÛ£lQLN!*¯Â ãÆW$l& .ý:@Ê8*ùD¶ QŽôn=²+dlf”ó¿,îë5“s\z§”0I©„qq_ÕP’ ùŒˆ¾!Ù/®l“в3ã×N–—ËjùX]ˆÜ” ä»/T—œÆ¯¦Óìmçþ#q‘Ùf ï¦Ñæ‹Ö¼jYLÅ€MJ•ϹÔ^UdZRYÐ’3ñ`Þ1Oʼ$E?#â:ˆÓJ«Øñâx|²ª)#ýwø`Ößõž%냴ëpº2J¸QãÉPˆË¤¸'¬«v,Ž˜ 23’K#ÂGÿm.¼½~ó={LÞi´©Bç¿Â8 æ°n Ô][ˆfz0 f1¼e#˜ /CoŹÔôÆi®#&GeM­ÒMµUréëò±NëœÄyƇ!аø½›¤`»ä\Й®Â_(Ne°Rp’èk½~öªòî;rô;”"×ùåÞCx6¨­+1©’6 ÷D—ÆHõ¦†²qg°üN‘‚ŠŸc#l˜ö}Ÿªô üÀ…a„‡âžyã†Þq?¸HtyÐɶSÆl.M)<|û,hÞ hGî3ÔäÏa{ÁA0+8i® ( ¬çÍßo#}`™ÞßÑ‘ Ë#7ODø{ç2?éÎ^§,€%¨g òüÖµ”wÄä¨rPÉñ§d ZŒÞø[°úr‚JKAöò¡@í8:ÈÒ“îŠfkËæûá¥JFù;²;öf°a­Ãm¡=: £ý/ ää5­ŽVCºù x¦—,~ üÈ›4¼}+@j¸jäQ’Ì¥^`–(K |}0Ô’u¥yžÏà#Åwziç×R›þµy ÷†ÑñÆ;PB®²!ÙM+ˆýa? sYÓŸ9­.ØÓ¶Î£yT×Òx´‡7jYf^$1¿Eˆu˨¡I( Á̘uê$üRa áPÝêÇ`Á2ËJ°Ù=€,]àt†TâÒ2¹ïBðˈ Æ*:þÖReÆy¡{3¸ÒÎ’Ü)òu‡*اž>18&éVèÿX3†ÓAþªHè1iµ û6Ÿ±]‹… öøW/Å¢3~ Ú&Ó’\LM~×ÂþK ûút\q†Öª>K¨°dQ¸ûµÆeÓmòØd–nŠ—¾É)G"б¶UÇIíb]£¹V®±z&}¾Óð»QÕ¾/د%kP¶ú‚ñªy¥üc!ŸÄÂéùÚ+öJÿ.?³ÄÌÏ ´‡²x49¢ñlI‹5rÉT-Å‚:#Šôq]ô°Ó1IÒdc§$é¶J Ö¬á1+½\.w—ËsG·%‹/áw Þ)¹Å3ø¿ÍÂcmƒî*8@Ìî¢gÁÓcétç C¥Õ(…ƒÙ@#ÒºÐâsrÞA¾Äèê  úMÀ$&†,X¿ýÆë+Ê|æiÕp}"ÿ-´#7à3gyEb¹ÐQûÅMb.Ò±lGµ>8‘h¼+þ{ì¼NÝ9ðÆ@ßöÊr{0s[d;±SgŒ^p6yÊ~;àµuŒƒÁÍÎþo^Î^ë…2¥ï: ÿj¿ä²žµÎ½™ìjWÙÉa”£—JÇ4'þÛþ§YYãvX7)Ć$þÞPúB°¥Sqóomøï/€y‹°.=ãFÂŽiN˜uË·³ÍûEòպаµ›\¼Ce ܾ©&¬`î¯{m i>æ3Ȱ¥+$BèÞÂ3Óà$ÆÒsöÓró“—É óýøLªÀ™õ6Äg3ËíÉN-©ç(ÇœèÝ>Þ¢¨:(®ÑŠz¤+\frPÄô=ù†RÌú¡úFÿux++Å?t~ŽWC’¦„)˜‡FVo\奦žÈ÷+ŽQh×Î :Äwdý^‹3wñüåƒ=yd³Àd`÷ŠÉÚØ]&ܾþ_º/в”/nËÑ7oËà ;ú«Õoœ35%D€(ð@ÃîÚyNâåÙ϶z…÷Hߊ\× Š•€UâåKòv(ê…ò`ÄËÅîß#WþTÉäûL9l÷í¯ÐÑ!ó<Ó`pÌX,Œ­ƒ6ªH&a{Îu°Í_×M*7óëõVSçï—Õùet‡‘¨å1ÀjÏIY9d¶­íoÙÒ}aLm–qæ´Ž¨+%, ˜¸’ß¾m>ÓÁözÝ:¨ô‰!“Ñÿ2ÂtÅñC„Maj[ÁmøÅ* ¤¿"Í4…]MœÅSÃÒ}Ñêæ’É·;›¾L,ð*æ\Ož ð:Â<‡É"=ÖŽühêK‹iÏ/Ñ2p^‡ˆuÉ0bÐ07xŽš³8µÔÁS¹„:‡‡(1m¡vvÿƒ«œ^™ñ× Üÿ3–åo¨ ši€!&‡™êuï×b$Á VŠO*«†ußׯ&ص}BÔ”ÖÌp©ª?ŽÉçÔ<êìè+1½êoÍŒ>UV~vt>ê±›l•kåkÚ@‘Ð…çG{†]ë¬7ŸTi8w|]E­¯Ô´w©¹4SÑ—U†>ôúú]àØÛúŠÔSNë+f™'Rç+ÙÈ®‡ÏŹ)ÈüÈ`h–aÜxÿcò‹÷ïÙMàB¶ëç¥aÄ‹¸µ$zY‡Üö²n·ó!uw¢,]FÕµ¤ *x€*ón“zúÜÆV¢bPŽVCHgè$ÿa ’kÎÄW}ƒIÇ éÞJ£ Q–×9È+&¤â¥dÊWèùvóÚ _”\°N ûÒÍñWgîD?‚“Êd+l»¬M“]"&6GÌôHë–_}ÔHXÉ,tòÿT¤¦zùÅ$I0ÌÞõÂë/ Æ7ÕЖT†¯ÏOxu`Ahf¹ƒì—ˆüÊC¾‰,Îi'ç*éQËiþàeKÒ„IkËÊÝßwsæÊr ¨›ò1ñy5¡Âð¼Xà dÍÕPšöó^e±8UÓŸní"Ø;¾ÝV=ãÑ$ã°LÔ8;´Ž4»‡ÛÚ §'e YÓëR´§$•Í.Å|nF¢kz*ýÞ@*õ£BF És‘qœî°”邈rá¹h…Qs_©ÐZÉüÔË¢øŽ*T’š¥MÞS€8…@6Zò¸X~:ì´¨°IRV* -µƒ²n%iÓÇö—ÍçQ²~Òû¹fU}¿¾çD¹Ÿ«¤1º‹®´Pvò+Å.þ¥’(!~Ô¦­E©½Æ­à€ ü¯¼Ð†ÖUÆŽ–ùqÈž=É{öwÙŸ²« ŒÂôá;- Iþ*\´AlÌugÀ)̈êÊ=Çc"ÖB]t¶ BgúÆ,’íäàÝ4n¯}ûE¿HÍ+>Û¼µà©¤’õk ÝgŸ/¬8Å<`•A3“?y”6_0’{µ¸ºU ŠEsZ™®Üé@ú|쪑Ê5Ùc´EÅ´ua²Ÿ8åØÛ½‘Õ$‹Ÿ’[ëPáDôj·ˆv 0ê%.mÃü÷NzJð šX_•P‡ø^B”¿OyÛ—BK2¾`wfþÓr*òÿ;1¾U.ÓCpTaÑg 3)½g4ôï¤Æ+¾¯`ÖtxÔíe¥Îyœ´ìgyAyÝëSå ô—e(¬T™ãCõ„Ég.uKð‰©ÇïR+R–ô¥™æ7ó÷I)Õ©û¡#H"ŰꟌª©¼:L²I·ò§ÙDÛ.X6Úu½"Ûùút‘†J-‘Ñd0¨1Ê(ðŽw^žèغ Ðì /…6ÿ7‰îJìæ­öŽx;æ·ÕGÞÙ•xe&¦d#¥~ʶÿbyÉ1«¦BBøtîƒL“œ§M¥¯³ëúp“©kw¤°%½6+ ¯¯Òºy¡ÑxˆzžKØ$ÎÁÉ»ug­(³×¦ëüüé4ˆ3©gºžJàW­{ò’4ÌÀÔ•RËO‘¸CXFƒáìMÁï%Tèê¬"º`jÝ ^âãVü±FO¦»–0LØíÉqщ–ÂQ¼­ä±X¼°“Öæ=ù]7ňye»à݃'oºö(¿¬pÁ ÄÞåÏb¼b #¼ÿ&dûìù'®]H›!±â)9Rø ΂%Ñèà¢ãÛïgàU%5{âyˆ@±°åHæ¬ ÷’+#=–×\É8ìI~6ãã¢5åd¢‚.CËë,æŸÝ!µ,ç7TÐR"¼YöWd_¾ª?¼˜°ò²nšíÍœ-ÄSA€±óæ¬D£Ô“µL_åé¹U¶Õ8ךã™ç¹·h±½ ‚5|)10Ø^{µ¦É<òòƒI¶ø¶„|E¦%¾Öõέ§­ô¾R¦H˜ v¼q¢oÔãöÖb?θosjì‘* ~Kò0Ýã–ßkúJ·œÀ­A‡Ê~EÎn†Œ’Ëwnª ÃÏÞ÷=½m“¶Ò"•q†½¼ô½÷6JC:î A«ä÷æÆîÇ =$ÿ!gç«oi¶Ct-¸ë>iãÑ1Gi#ðÊ*s‰<Œb‡Wr3ëÄg’š þ$ ¶$ÉšÞ%dÃrÒíwc´`´˜}Þþ1 ñý3B,@*}áý‘≒Šbßqá[Æñ±P±¯}ê´|T‘ë6d®ä}È• u©ßŸÇÖš.c_Ç„™o5÷ú ¤{ÒÛ[qÈñRz÷+&ûó5õÍ)l å¸÷=¾–·¸2œ¶÷¡°±Í+?ÌsÝÐX̳ü—½‹µâ×¹ øôk‚ÌaG¬ž˜ëËS}Ç3Q²7¯ëÌ®.Çý|} \ggNÞôžˆs‹"3šÅ·@c]0YS5ødú=}Ç€[}zU<®- šâ¬8Ž"»4·$úŠš öÒ<¾ 4Ýs<†ê­º‡‘`¥`:©g½W×ú1$]ÙÁþ©¿…íŽô=úHò¿Võf?ÊQÃ]yÛÞ‰ãÒŸ0ç°½p[B–•„Ç>Õb!B cãÔ°k+u8[C~|¢¬*œ®u­SH-Ñ!ñ]ýÆÜ¹¾~ŸÓçxž”â|À¼²Èe:®‡Ù¾¤8Ç„ 4-ÑYö«˜ø >‹ Õݲ¦La̸m3^'Öeú…µ9óšL#ç{‹f<Ì}ÃZÖÄqýÅ„ ‹ìê$éª)!Ô,¬y´÷ñlç´J­Û¸Ùä“2áŠhØ»®+MEÆ’(MÛÒ¥šÉ7Ö¬§X{eÃÛh'· µpR«MìS.iûB~âz7©ê QðË?øÃhz ˜šgÐDSÄ D?©[LZoèlÐJ^2çC\x1Ÿa+¡<15KÏ´Ó)Ðûæî©…—O¸fAöb H»5Åþ—‘¡ánJÁ±¬æ¸0cTœUÁ||Ø„Ãn9€Ü™¡hhïr/XÎß×r—w¢·¬ä7©­Í¯ô]L  Þúù¼—üø°+²Ûä„ÒF…Sq¬/LÉJàÆ·Éæ bHˆoXûP)g¾/šg*B„¤9«½Fž[Œ%Ƥ.›+ƒÀ-ȯ²)“©kÕªòL9µ>ºëó¥¡ß½ö„ëìÙgêÜ‘Y·Í9±!iE’´|ÚÍ£å­Y§P©”õ5¯¦¸è,„ÜÈ þ T|ðciUºÊÑ…ަ¦Î3Ê,s ~Àxj¨B,€« ®Ù‘Ñb ˜ Zü~õÇüJ—N¤(ÓnyåSQGMãý¥0»V®ôŸ+¸. ¦×àLLj#GÐ/;SñÐŒs¤"9%­µÝ9LÎ —ép6Møß¨…~âL› —ÿ%0¾Ðߪ‡sM]½C„ ™™JE/>ëîê;d,žÍ3Znóª·9ñ»$2í|u ‡ž}lX£þ½F ñk®Ps m×$Io%íü2Ýëèë ß5e¤ sˆ¦ë³À/¼5ð‰5»t>÷+Ú<öl¥fïöÍFÉÇeÅ#7n3n^èqŽ˜ ˜-Üàp÷OÅ{سéËšµµ§¿`>…MÚâTýß+Úaj½àÓkÑkhMµê#žSîÇ} ’CCq¬O99CÅ}“»­ê%à³]"ιOòá[-zV€ õå³=® •¹›;[+vÇÿAr‘Ö!Œýž ×Ç”’Èô—M‡zA@Š5Ù¯ùÿv—›ÿ+H¬œcÒ`EÐúTŠYv_²¶ª`‡‘œyHÑyP t×ì°ù " ÀÛÀ ÌáåÚk²»ôij.wx‘ù¶xNã$)dÂTÇ&kR'nûÖÀ|•ø›<¤SÝ@‘I°¥@{KukÉ©Zî6–‹³5H…L*Û|EÇ*ÕHExáö]·÷3òg/Ö4(ꇀý±7X˜Þð–þõ~1±1¶cÁÕ‚X‘:çЩ¡-Wª‘Íá޾=îÛ© ´d K©½åß{n*yþ—é×$ACóÍgs`i÷Í›ä_ê ÍKš Y /«±vÉ0ÍlÜ2":«|ºJ?í-X‹Ä—*@C€+Ôòú Ï%ÒIbØ–àÇ“±.¯€½6wÄD'oáÂñÖÓ#Z&]õ™Sv›hsG‹wïšÈÆZžî;‰\€VŸ×)X:"³¬#\Þ“•¹ hóQÆîe»+½utû\C<$çÔ³*9Çó)aØIUéÌïTdìh[1¥Q²dl’B&}ÚÏÙö “š´-rÊl‡ø O÷eÄçÖš<q ²I]‰eÅ ©3Ë…O³y@ 9ݳˆ[“X˜Õœ‹2üM£øc¹Bj¡’Èßб¿¤‹ñë’»*àf$ÅK{ç’Åt êÔWÃ_rÚÚ“0^•àÀqØ¢õ)7æ5¬cÿW ¤yT|ÆŸp˜ËM ™Ð°qaãÜÔq¾Î„!IÓ*ÀF­…P7R‚€ÉÊZ¡ðN;2w<‹ôBRÖ»SéšAÎÍ, ©´Ê¡¡U£°‰·3H«ý8µ¼ RÏhîkÝÛë÷› ‘Ðb˜~¾!ÍŠ,2“})bš±õù¯®ÊÃÝ„0ÆÜ©ÈPx/”WƒP.NVf¸,먑­¸ÀtôäôVãáˆY›ÀçÄGCÓ®Eêá[Ÿî—˾h©xo| ‹UÙ‰Ò8“£÷泟µplHG‚S_š`Ô–Ù8hÆx»»¶û¯ào]{°¾ôùFŒ}waiعº$.g$5jbŠ(C¡Å%¥ÊEcÚÊÖú8Ž–$˜¥š0¼\D%3pRåÿ$>s"XÉ®pŽ4{V ¯çy¨Õ;/å5³/Lrs¸…+%啚ûðsÅÒ~ýVzbòwî'sÔÔBKB[ßdâ7‹ƒW–Y8hWý¶Ô.¸ïøI“’Tûs³ò6ïÏxÐÆ2ö£Îf›sÿlÖQj Dy·HóGÄ#-LƸ¢‰¦é ù¡ÂùÔRøOy­°®ö¢—°<ªX¯KÒå œ¨åç{Bv0/+ÐmàÿTXGV0Âà™ ·‰.Ç9ÏG8™XacÒD »Õ³hŠ’så&ùÚæH­<.$(.—ßÕÍAÇfûâ†C’§x†}êÿ>Úå>8ÔÎìÎvˆ+*Ø„'Ù¿þ\†ChW"Cú݃~–¦˜@À¶U÷ŸÁ”èÝÍÐÒ™•&Pýë‰~¥ ;V2›dr_ô>0oˆ 5¶£/Jä%Æ@§p | :rÄ/ÚnÍ[:°@®3B›©Mq4h_Ä⻈Ázò‡WÃ$àÛ,.AYˆIj¶z´Ž_ï PåçPjÈè"aùd$A½Ãô²ÍgßkT^‘#,Þ R‘Öá ”ói^–ÇÅ¢›ä¹¯w>æZ‹ÐÌç,ªòÛ(÷ÚtSçH¡âoÂꛇ[M^NWåÕûæ‡Z!-’â Þ¼–î” …‰‰Ô„?% »P×Ñ©ñHþ˯ü†GH„”ª[üùÌj®h8+½¥m ›¤"CÑzCìªão§¦–Rás«Xm.¤]nIø{D ÀkFiL”Ó¥dWb¦¡ÛVš6SŽXØ©*Ô,°9H¥·Ùin¿Uîzêú-Òáó² †e¤Nä>Tj ½Ù Ä5-ܺêà÷_Pyhý´I uº]ž’â<ÐéN>ƒ2ìÍí\ãÝè¾@ùÍV“è9š’‘—×ˆïÏßþ Üpâëm bB BRB6‹åg'XC&p÷@ÇJZî~ùHê`'ŒŒÜ.)‘*ldŠñ®½Uåö\­¥¡Vy™¢Ë¹¾>‚5 Aöæž³9K–tÞ5wòncüñ ïeÏbS†è0,U¬h'xÅéëùGv-à фç?¶œå`rœz¡ø7Œ•#†.µk VÀ=fßÓ7±3>&Vßí_¶°9§FÂP‹'¾óg9šÖ§î€«#™[)ƒ?£–#SxÛöð¤ªÝ›ÁYE„Qü†Y¾©]Àáñ›ð sþEÄl}ºYl°”?ÿ$ £»ÒÎFU-X¤’Dí*=¶L‹vxqßDÑd“@q}‡IÓñLÛÿ&xýWÛͽSýkUµrŸ78òX׉ÿvPm»‚ä;¡p5|­ ü«Úòßÿ ±×ëj]dª:1ë1¥f¬w9w³ÒJñü ÓÛ˜l‚l÷&¨œAÁZ/Œ°8H[“Þí®U)lˬåWØ«WÅ·“¾?ÅѲm»§dMÙ§»7ÝšõQhÚí­œÖ_§žWN2#©S õ²œ0“KûÁPµé¯¡­Ç VlYªMËbâƒîûû*8°b+ »5çð0hkžý‚©sÜq»EdwUút”çV1Kí@œ³*ˆð'½B~·R‡å[9;ÍsXÄОtOÓÆ‚‚bÐÚ´˜´ü¸ zf:ÉÔ˜M\.T*¼Â«rP¬¶¸µvªÞžÝ€×ØF{4M6¾øX<Ð¥/Oº~€Ó®µÆ¤œ– #å%.rç…yû$ëbn¤À„¦1ðNÿVý×Ç`|„ ôÔøò}п~FáFq30?pÉ1æ3FþJ‘=3p¢ÈéRßph’Ë‘çj¤|¬´G&zUk¥¥#”ˆéè̘' Y¸­ˆ™¹3rªà§P«ì ]˜²§ðˆÜdµjœ©¨DùÙKÚÍ:-“ÝØ³™f*¶ù‹þ&5ùÒÀ«t®´‘ÔÂ~lr¼­:µgSä\ÿePkdÆuþC7…ô‘—‰?¯.…î:òî2Ö©;”´A…‡°ýX½ïLGD°vAœÖ¸¨£u@(zõàƒ¤‡ž$l±¸g™V GWб—é°ô‹%8ϳþ!‰\%"ÊÚ«®—Æ yV“².\?á[!mö- \bµ¤é-dÝ]OF3C†„3`-iõá@“Àñ†i’ÖöÁN-Ꮉ;RåSnzÕ܃ð$¯G[äøžqEÓö”º°mÂàS\Yá™=bG[ñòqêj¶–„¨-·Ón†E›a+ÏÀN*þ‹¬_eÆÙ¯SWfª@…ïìd’³=ÍfµeS,2( #òÊ´šLÓõÁ^ÓÒ:\&°~Zø‹€E@Ž$·FÊcŽüNdSU„êèÚlðíxµtR²×ᇂ}¤!õ<ø+ý`‚ßU‚4˜i[i{¨}¬ [q0ªSÓf 8{¯26ä`§õ‰{JÓcbºù\1ß°4Q~î;Îw5ý¥¼ ÕgÑG™¦OXGÊé[G2ʼn ´s—Ù”$ôßÝֵܒ¥’ëÙã…¯1ÎjãÓ¬'ý; Å& 4x9Ay’†èBìl÷°R‚¯kùáà#”fwy ÊA@šœg«ð©É?‘1Á§8Æi4…P¦Šä¦áø{y)S‚«ÓÁC 'Ñ5…q ¯­Ððáꀤ•Ø¿6-/ÚÓÆ@õX(|èl‘CØ2²>IyH°Êpƒ™¦XUîÔÛ!iìäÏ“2ãYÞ¸òÇͬNWÔz3u¶ÐQ €¹JÉ3„LJ]ÿ}²tÑc ø1Ó$·ôËtlÛ¨œCîÄ×/DµœÁúN…Mö¾¶ÔTÓCòí¸Åú=Òy'ß t®÷Ôá–ñ´"t#íÌÎÂFKhÚÞq—(l“=bNêÜU~àM÷‘`ˆ¦Ož2D­m+g_=ξ ^œ‘)~Š—›ù\ù‰ýééõv7bNcßý“S 1 /¼”]zÈAÝ®f“üS²ŸîIK¬(éeë"l\×JÃ&¬ù"EýOTßëÈ£>¯Ðè$Jhd{Ëä*^70¼âÛæéZÈEÆ*[`r~%²ì~rÄF¹¸§úá~ÿu|¬ÄHÊ7êÐóäÒ È¨ÝÀ,£FpôµƒøOÕÈŠ{‰Ó˜jÛHõý)ŽÔΩ%Ž eÑzûBSÛ³Û§âÍÉÜ[Žî &؇ì5žÐ7ýÿ+#P÷Nì¹­gŸ©ÛÍt â¿Ï®yÉÆÕ·Ð°¶¥J“º=åÏyçLþ–*2äÖQ˜VÁA€gt§ô›$fú¿±JEøB ß4 ¯ÏuóÅÔéÛòämÁƒaRÙÁAb„á:ôÍ32ªíCÏ®u¼P¬:Ó=Pu¬åŒ e|Ñ£_¡{Ûçɼž3ÄÖ„RÓÊ ¢éüˆð¿Y,\“<ϧÞÇØ²ú·ño°À'cé%Ø›$ÇE5Óíwá<º¤…WÈëÙqâë† yý}Úmxrî:Ø&P(¹g»u¶o9ã!":Œþ‘“—O´ðke[^vá9Û®»ýTüÀƒed¤/I$|%’Œˆ6<£Ì÷׆þé¿=”§}Íÿ|ŠŠ_4r-Ѹç/:±,„kÁ;͈èã!°Ê½n$ø×ÁàÌŽãøÛx'»L,xZmƒÈh‡ù§ÿH!B;òmO³GVË~i•GLP<4S*ÁÆC[6,<¾àýÞã×T¹jÀíãfÈ÷®Ãk¯›Ìƒz©¼ÈH¼»%ÀëköÑ.5Œ×'¨þyù>µ/j )^r¼ÎÔM>Õ¨¿Õ† ÷»Èf•VËÕ½( ™¿ršƒí3öB„þ0ö¢=t “CÞm_޾az§)¨@µ”>i^­«¦P Èð²Ìb¾ãÍfñî<-¸Õ嫿Þ"ò.6@RÅÿH…¹`e)`Y ·T^Øxf–²H—éÈxŒ›šBÚvkN×*%$<÷+Ç9Éaç×D¨§|Í2ï™…øú*¥? ¹LŸ™ÜïvÆkv’ÎL D<ÿG—‹w(è†ÈšÝÂF)±@j.ÇÛÒGwЪ8 `‚õ K<úAÓ½ÐH±?R,”¿;"wï?i:§0qÖ¸Ó´8Å/ÂNÀ[ˆüCW³¯üÞÎZ˜ŠH„)ó:˜qlϹ©¤]-ʳ‰:ˆ¡î^ÇBÿ=lmYþǾ6²/¡ñB+Ò¢0–â¯$çP%ðã2^ã8ÿ(‹é¯s@sêMO£„„W»—cÊ"œ€ŽÚŸ x* `ò÷àe[KŽþ ¶ïáó\]qˆ©‘ظ‚8ìäžáÀ§9 ÑtN­%`Ó.³e™4Ðý0—+Q–]î¼:M7}~i§¹ÓÁkéÅÿŸ§\äÿ³ÜÖÜ•f;8(³)´Ñ@ã\£n·J$_Šúçh=J ¡ÜÕY”º¯e(éc;>–ß8áGÚ4 ;7¹©²ƒõqˆ³.*óÁƈ&uº@²bi¸_+9´ŒvÑâ=ícÅëìlK'ÎüKx3“ƒ›Q¡ìç.ìZWƒ-Aš`-ÿ'ñx[’÷cbkÑP&ƒý'ùiûâ„`I+#´Š¥”sÆBa]S w‘wt=·«Ûwscõt^¾hmçá-ö&+pbÏw2¬:qëìö/ L»µÃo"~àÅ^™û²”`÷¤Ê:;Z®ø«áZ}­lé­§<Ô3·—Á“j@õ]j‡N„6ù¼Uª4Áo‘¿ëüší‡rUö±œF"žÉ€) ËÂÍ®Â83HBè âqñ´†•}üRñÙ¨Z3¸áˆþ¾Œ¨& f ð/Uú23N+7\H~›~ ª6ñg73*Ìôƨ:ؽØŠ#ëƒ(Û0sàÃ<€dî~˜Ã \¼µ‰+ëiü‚Ë êŠ.äY˳ÏÓJ´¸Ñ‹%IøâÍ „jÑx;rs?öz“ln|û¼ï¼ã34úQZ´ZÀÆm¨8M©E“ 0ìþ‚a£HXå§m±ÞaýŸt¶5¯¢`ÂùB1tG³E2®þÑꇊN¦ôŠø&º>ºÏ™#€1èTÞ¬g?“£^-ÌÇæâ$Fqãæñ€ÖÞØ¢r/9oSçƒÂ ðÓ°veà,×çá±ÂOs˜èXé²%Ò&¤-í’#ïšQl=úu¿€ªÀê£?i—7¥-aW³ û·KÖr꩎ã*ï‰[N5gŠÕâ| œãN·iÂèD7µJ‘%áêŠK» ¯ªöGºõ„6Ö„(Dºk*O—À¤I4‚°Ý1„^.*Ë4» )óš*>Ú¿qÑ¡.Ÿc­~9´Š·n%=t>Uæ~ºlÇ/÷ÞÓkwF¾½»z€!äeë·=†¤°Œn@DåÊèëÐ_þ©Êy¸«65ߟïÿ/3à¼Ðà©%Ôµ<î&˜rßí_ð²Ú„&± ´•01:q÷"åp9W}|î_~è›5é©€•E ×J©ö­@Û7 Ku1K` “öªé¬íÃ]¯&ºFkïØ{ºGÑwAÝbbã¬õÎnu” ô-æ}$y*/]Œçf&MÔÙJ£‰‚o?QTví§‹×­Ø*÷} M2‡ÁÊ:¯¸ÓOKnuª2ë³l©>ðÕzá“QªwØÜŒ :›¡W†Ã’=ÒÙ ò­žžÅ¨Å¶Ì £˜GÆ|î鬸w´KuEܧš,¯¡~c"ø¸oy•÷ÀbøíW½ Ä­ ÷¼_Ôº8Cæý¤¾Ü* Uþ²Ë<2yŒikFVò F±¿•ÆXØ":“p`Ÿõ\^mÎæR]Ê=ãžš‘ú¦6gX`â Côô9çÊ7 ¤ÛVH sâ­pdKÍ%½Kgr.‡©´ŠãßôX… žén OWñ;ßú=Qug÷8¡3LUoÎèë¢4l‹˜õ…¯,’L›Ê_RÚeÊÞäÖ²[‹ +f^ÁVmK%œ`„Ç}YÝÆw«`ãö!]ºÝÊǶñnùYp÷êv•ȹüTÊ%ueŒ8­]Ýú¼ùlŽä¿âÖ “Ò´íøõoqY\¨S?´H/•ÄÔ)8h eÄ…¢>F–ßSCžÉý‹ážiËÕj?±ÿjØgLâîC&p…Å ïé–"cm­óbMbNçéc„e;g{ʈî²LQ_(öcЯëz5ó„e¾¡'Q$¢]\¨ ¸–•ëû=ª}”¹CL~г.f +0¦ÈkÀ{4ɲn$åŸPU6Û[cÈ{éIÔ‡&œ¦¡“DQ‚#`€›KJ“þ,)º&M·è²â²ï âèú‘qÊÖÉË=“¬ \8â( K{µ9+óa”4 uÞSþdv·bÏVlƒ °£ m+&Î (f[ú?‚_ðŒ?H]ÄFx£w´¿YLòJŸÉÑ5)cô«Äœæ`âhx©€ÃA”¿ñÚúºgKkyâ(X–ÍÚâF+œÆøGˆ]SĺÜéM¡‘in?(º. îsBÒ*€-‚–Ù÷ª É$Æ¿^ÜwÇ›G‰8VCß8×äê–Þ7­4¸QàôßÝ,óàkßH#ÜÍÎJI )sM\÷ý°×|ˆ•‰ô€ßÚÍèƒx<öÙăø÷5É’Ù ç=MacP+E¦wß$Aý,éeÚ­|Æ"3_ÿNOº…@N¸xP•ë¦Ý£AøåeO<Þþ†êE׈r¤£ ‘¨‘•‹·JI$hqÚÁÆ ôØ£¬æ´ Ù·×ÀULã‰8úï@ÜÄðßì¼#ðØHØœ|>ÈúE“daD]È«lÙÝ›s Wâ¦M -ÇÂÓ9Ï40¥½ƒ—rõÆ0 ÓrBíË€!êÈh,JŠá N탋õéuè}f,Ø )¦¼ªS+¨œ¼Ó¹’&*ä¿ “a ‹{OÔbßôwÇv!ŽË‰#¦ëÔßÄáoçžòj÷ ©-Þ,§G¨Ÿ¤jDDPŒ™LXtWCDsÝÐV‹©Ñ-¥š–ˆ¿ÿvggŽ.DÃí!̆9Ò.ð¹LsfO 7M3 Ž!jß,ê•Y‚¡ÊÜ,Ù‹DÛLk3õÙÇ{h•xÅnºnVŒxë#«P2"f&ïw)Ef›0„utöEfή¾î jW¢žv‰ÔFÍŸÑ@ªûì°¯Âÿ*[O³ã²h·Lv Œ ·Öš&ËÌ“y=W££,hJî°!qÀ¡i>Z‡ùùø’ Ã…\gŽþÈw®KÊ¡}ëy™'\ç|$¦ë¯Ý|¢ïeó§ ˆ)ëá\•°%i­„$(*…·b"‹»îP@š”ÃâsçÆØV!me´Pº±82Üêæ d›Qæ9cÀЏ¦’J‹Ï.8¶JmSt$NŒU^ •1ïy׃Êü¯#·o'.Þªƒý¼ýŽ\¶N0§P°Añg¦˜%Ú¨þ\Ç8í þ$?}jÜoBsxÅ’Å ¶£ghž%¸‰ÉÈ}/ב‚BrQËvœÍ6ଛªçŒî@{o‘Ä÷F(0²ÝlÔ]̰¸CrE/£¾@ó®/ËLLÐçXp^Ç›õ‡ÛÓ]çGS˜Ÿzv^Ü`:ŸdïBÿö ŽL!¦:˜ãÕ q}ˆîÊ<6&TT>ò˜-óA¶m·òîêÖFß±-X-aÅ™Q@Þ˜‹™è}d?ò}­âäƒËêé§%¢=Æ ´öøŒd«DrA9[â åSàÔCòÉ:Y;2o¹XU-…4©¤ãÓxzœtZ¬ûƒ ±ü‰ôMà+¬j[¬"—ï U85p˜3óép¶-4£†ÉͬåDÆá¿| Ϯآï™2zj)8=ƒ¼ú»BÑÃQútÿy¾J–ÜÍž¾©‘n@ö ZÍ%Œù.RWÅ[Àõ¤\‡H×£úÕÔŒoÑþÍãÜ¢ýÐîÑ"' CÄt8Ñý»,ˆÜ\PõùØeYƒ¬E«>üÒû¾ÓÐñXQÞÎ` •~nŠÊ„ˆ‡®7ßÙY’ö“ß°S2Õ¤ï¢Ñ¶Âñɵ~&èØeI8¦œPï]ÛÅÊbëQüöœëÓ¯#â´ËVPZÍUë¿NùœÀò´.ÝlC3åC]—ÚÙ$±¦ò/Vö/vM¹{Xöoº}£2ö©Šo¹ÒÒW¬FA£ÚÈ© n#ËJÇÇN­Ò_Ó×{m™˜½{µÆ7•j”EÞVõ·:ªüèBQïkKƒMIV]¹:JƧxÿK9XÞŒ¶°í6. ¦ÑxS®åÇÂoøzá]O< þ ^ÞÞÊÄÎ4ow„_Š,Oi€|]|òÍÇ'C§ñj†qýìÿ=–¾;Ì‹Ìáü>KùÓ4{×™F†0]±üØ'Š,'lêIüoø[¥©¢uZË=|ص™¢!N²³Ê÷²s¼^cµ ‚…w”¤¯±å¼-Íè}Èg°¸´œƒºâ¹²7¬(> ¿xÁŽ êC§‘IîIÃü%”-߃ÿ'Ïy[à}ÐÁX›²@ÅÃ\î\àÌ!\Qx­àP0ÅiUá9e’æA­Î)w¹œ¥fv{YæѸ$ €ÎÞ“EEð{>ô휯l¬uϬ÷zi¼ÀŸüaçâÉ'.m‹ý’mV¹æu_Áu˜Ž Ë—Þ€‡,ï×îsx8[#Bäœe8iÄX€Ë/ÌÕ#cvm£G‡EíÃÆ[Ï#™Ç=Èy!Lò2>Â*sZ‡d*g.âAà/ÑýÊì`ƒâÊê—%,Í‚#Ãû\Zå_j™;Ö)X´äìsá­X5ª yÃE¼èOÆ¡0rRÇ–<jÄfóÿ!£ÜÅnAB)4d%àÉRPÍZ\¦Òêá»Yûšc4qQÏb»kÍÕH¤HpŠú}é„‹Y'%ü®_1QM5³•ÀýñQ£[»| JÕ…´F-ñ}Ü™i™-äj˜“‹ÕàMí8a©DžTNöð+¦ÞŠ’žrõ‚ö…è^£?ÿS ¸Zç€qeï‚…©ÄPnëTûHéñn£ºB/ÓÇGÜxþiÏL„J4§¿…uóW:ÎѶ «ÕÕ’°K§Nô¡"šöCöh‚HSãÕˆ³HûO”*{ Lj†‡N‹—êú@jÝ5ËߟÒM×–ZF—Y,©HŽ÷UÆè›! !Ð"¨ÚuŬîýš@µ}b›½‚1©ÅÀ¬“¢Âx©³t‰i±·Ñ®E V÷μ”ÐÀ(y&½z}ÞaÕ©¥…üEu+Zeؽvé,p“£…²Y‹i˜ð½§w:²^®ø®®wl'HJ¶*÷Dáè]œ @~O[{O'â+Ä=ñ±ˆJ`¯OIͯ-»Vi":ÐÊqxáÐæäw&:œT®;^%%0¾»ï+ »;­z«¯YÚ¯°B®„ixdI¦Ðf5~=ÂXu»ïÿD—,ŠW¦1ê~_,xµY*–kT³Ï+qlÙU©/D_,FkJ{ŒÖÇ× ÈýÀß_ ÿ!±[Žì¼„T*ƒ³}?¯žZǵòÉp "`¨úÁô‘P™S®ðG¤Ì=Öâˆöé»(BýIðSÍ]„EÉP4!ÚvñêÎzz߯z4‘5¹<ºÜÖå ŠbdúÚ©½mÅf ‡'È”yPó žf^‰s~·²WW–6XÄtFϼ2?ù&—|Hü꯽ævGr‡aÃÕNÁk-ñà¢9Œâ÷®ÍÜóppªG(s:wµšèÔŠƒL­uûÓT§åž¥ã[)‰*ÈäPgLJ§ Q®ÁFJ*ëYÁ(ÐX'=K=Qùûõàt÷OˆÀ‹oï±÷ïê…]ìa2Yñ9a9÷FÕôC¨o-nLÊÎðÚ+~­‰ÿ _¬” Ô§Üâm)mŸú‹(ÄRC~ŒvÓp¡'+ï.áÕrûîø¸E÷0r@hÐ¢Ž•Ä9Ç·–aýž=(¤AQªú±¶Y¾˜4q™­õ!Þð2¢å‘õÓÁ1ãå:\K þÏoCúJ @^òÄÂ-MIeÎÞã­‡mm*dÉ£þ²7Gáp°[…Œ"·F›› ÿQSâŸbZ®º`ì WhÒA§ÛlÒ–Ñ ÞJ.ÔÞuKÝ úÚõ<,ѧý žg!ßwgîšHy1"¤ö£T‚}G <Ëp°™F8•§Â»‰“˜õCAþyÒË\=$ÙdÊ"cn”W ðj€©#kTã£æ˜ûc³zë¸À—­¥Ðo‡o”Ï3TOXe#P~‰¦•зùXáTZù Ï™§€p^.^%x¤–{£×­* $ÔhÄ.Â,£…BÁb.ot²Õkjóÿ(ÐJÒ1C–¡÷–ÝŒ {‡ûX.¹íSäMD„äRÎg¦Þ]¹/j|ã{j`*y§5m5Ú32‚õù¯™i ×B‚¬‹êeíàÕ§<ø~nˤ«Ê2ü|¼-wbq•T­Ü¢8ía?’ÙI …º`Þ‰Û)Yñ%kÙ"pØd¾ß ú¤. žÞíˆo/yYÚÒwºÑGê]$Q¬Íîÿ û­²Бܤæ#…~¢¾Œ²l3:Ô G-Øh¬óWœ!™{ãòQErw¯žé¶¦³jCeý©F~ìyñ¿Æ?2Q+°õ¸9ð×RtýùIïO£Êú 3"ÝFü5%8蹇µèZ‡@ØVKa ÷Ö]FÒ¹¹”Z†Ux£ØX+Ò»jèŠ)˨©îc{¯X±‘³,”Ša¨b*n5…÷ŠÕï•1†Ÿ·N|’„ªV¦•UÙxÉûP?ðð‚ý;–íþ¶Lf”á’àºQi¦Ï‚‹j-”©”¥ š>ò†wò&ŽåXÊÃ'ÀWáâÞÕ&ü™¦rL÷ôêØgábÉ*;/ǵì±…Ÿeј ¸<Œ¤µ¯î¢5«^·r°F§“æ=ýdö€DƒmeN'šEv»/‹{±K=è1Öz8 S4¼*Ù?ÀÁz*Õâ‡Ä,$4“¯|0óµæÄ•†‚]F¡æ&͵?Êr ‹"é¸ûpÃm’œ¡´¨Y®ÉMX»Õ˜ §¤HÂzÃSf5ƒõ·âuZ0?c­Å<ƒ/@äýýúÓÖÒ©O¥—’xl)³úÑß~„+XæERõwÑEèu›UZq A¡²ÚˆD½R6·@Yªâ,.èmâYŠb¯BUÚ},³i“pÊa’Ïk»U@¿Zוüuöáäpqu&‚_jl_ åi­~]iYÌ_\h\$ó(í4Ü“o®žž,€é÷~@Š÷fDõpÌâÌÂyÛ}ÕLµóJ„\ví[å"zËã\&Ýp,áÇpäìµ¢ã¶0"ܤ¿?e¹œßÑ"©ö •¬l€,Ŧ×ì¡#Ì«E`k€ßÁ†‘,<^¼ezÄD $}Ø–ñÙùLEh¾MDÁ&p•ü šóÛ*¦æ/#f×+¦KÇÃ*ÔAÚ[åܱò™Å×f·›”ÄPÊK"CýM1ðº•M*ó@SUØ„ª°ÀDAo!Ð?<`ï<Ï'0n+ìͼje“'È,÷úiÀ°Ø,ù\zqKE6Æ%É#„ OÐ$—k™¼zäøá0Ú‹É“ºï :‰¡1µÁó¥¡GìÀÓÖÈ·ÚÎ’ÉAÁ¤í‹"Òÿá<§:ÓVŒ*lÌ1˜üômè,†$ÙC«\r‹š-K€3)~:?ÇW…XéÛ¹9¦}#sS…ûaíÕ‘°öÞȶ„I@™øF?“ rw»a+µÄa6uènÒo¶þn@¬.„Ú–b×<êŽètj(¸«ƒ”¦ÖiÈpy~à¦i3é"² ¹Öa'4Ú€æÜkŽpí+Ò‡h@Ý í€MÈz¶Y*vG3è µêð¦£Ã°´çb¶ñ‡ÌlïÈg8-mDÛ…aÞ#6§™ 6r»F*" P}AªýªÝõ‘¯hQ·Xz€ÎíZZžsᶯ'2VX&Ø‚rr›9s᧺úp¼"ù—ü•Ëbžã3 y©26˜‰€º;Ò¸=5lÅÝ—.âW‹Äíœc|VD+kU@´^»]^·1ÞîíÔ×ÞCUìõ1˜þ|57«“dpÒ>¥<ÌOoÈ÷ˆùë˜òs±ëÔTÔJÇAEV(þù%ƒ·Àïþ8·et¡Ö YJBpÍÔT ZHqbáÛÿ&¹ z·ªRõݪÈPÈ™}}X?RÊìp«6$G’œÄ“r‹-7K€ûµNÇ"Š€gagB‹n~/õŒÀ«›aH¨.K€†>µÑOþ HÿI×K4H#H4ä^­ÿ}qPòäižÉ f¹Æ<.ÅWbæ ÌUEs|&ˆÛ•‡æÄ?SQ:ÖÓWÎÅh¯ß}¡ ÂÄ<²ú^£ó߈tásö!0 >;p»õ Kkpˆë³~Ålì{GÑâ V_ôfYcQ,j6ݲÍb2H8o!±›[%x>™ë«Ï ›.ôd×&—°-b—<™êšR'M]Ä&¼Âw‹µq‡!Õð ÎÞ¯yì °¤ß|ßÏÈfçëȬ¬t-Ox¦`7¯v'è”ôhç'¨žî4üê}ËãÃWaþ©¬îcç| –+vSÀ”PJ1às8WðÒÌGêc‡úéŒÌ(´Z%oÌKGª“ݦž„t¥¿,6ùnÅ ™ÚÇ åÇ÷ÅÛÚ¥þH³¬s²aœE´)¬2˜£§º®QÔ¥zÙÕfÑÚŸ/+°„B5H"ùÐ@[€]mmk~S/Åîl¬e{…=ù…õ‹IE6ýŠÁgÿ1 6 qŠz ã jœž›3¡ zZ™“H9·¯Óßì…“=uÔ…<ÆŒXÎhàÉ<éÊXÛoéçýZGÂá{Ò—Œ\¼î,dfI„¦г‹²Û ü0fƒúÍ3;:ø¾\fæ$‰¨+±¸“ú+:€5Ïéö–âÒs”}UvÒ¥czµÃC nȦ-»1(ôÆóççëÁ&à:-Ì›m®Üó!G=®*1m+êµV{sï'ØQú ¹úFcƒ-š]á ëýbgþªk¬AU¯BƒßÚ•WK êÆš¶° ¨ñ84· Чe pú•Á÷Oøb‚'™1mg|®}?*帚Œ,Iç6D m¼ipé‡Õª-o›ÌOJùµš:’žÚÄg{IøÚ'€ŠÆ‚Þg’ĺò±Ó»È¸XÛs² ˆ¼w£@F³¿© YKJ}k^Ø£˜'%fÎ9ˆ5nÒîõÃ$C'}Ññÿp²xmNì|¿jðð\Éâ&ÊrÅö–÷aH'¾NÙVù“‹Úõ}üUÝ‘¿žk¨^òt­^ÆA¨hU¸tE\ úÎEøy?!¯ž¿ Xo;!I‹BÂÄÇÂn‚²Ò’jzÌÓ€gürèÐQæ)8¼‹s{±Ü¾×袴ô~ËM†$ÎwA*2¦-XoŠ?+eF ±üy?B‘¨l,¸"Å¢60³*’„p[)-‚yC÷o1" ÿ–"—Dà+`Òn7ò‡½èÔ `æZù«c~|X‡ }ž1Pe‹Â>eÄV` ÇáÚöO½ñÜiĘUí$g¨…D|©…M¬Ù]ûy÷Å6‰œH™³Z n*Í|`ÙÅ#€úgaÜÆÎ­¾$™0³ß·åh³7‰ªÑ/Êy×a¨QéPßÇr¶X ½Z“°S‚D%½ÒYk¥.ðqúÇr¿t%ÿáX{mRDŒôEeðt ÏǦ7Œ‰oEF{î[FÄ\ ï1Á©‘úbU)ùÙÙãS`*â„Öë¦ßsL6':c/!’ù62•#)OùP7[2¥ä?ùbˆîJ{&\“jïµø¨ÀŸmb¾õyá ÝNU¼¨ç)—«¦kþ¢¥‡ åªþ}‡g²nu¹„É(/Ç")¤€“º|2ÈZ}ÀûÍoÕySgÄÉÚD‹p]­¨6ìM ×4žOwK¤<šVf[FQË!ír 6•Ê/k¤-G¼ëÐ­á  Û§p D?4º?øZ4r¤É~5QÕï…€¹Òx…H–åÓt šª° £(¨´‘Ê$#hݸ¤rI}+úõ•ñí;¾ê$5‘W×L‘‚úø@#1¹ï5[ã=ø8ŠK¸+æ8mÝ,Øý½”ëÈ¥’ë«ÑÑXâRŒCÞICÔvw‡3ÇÂ}ÓDè733A|˜[õÄ/õGà—þ„„f©ˇŽyê†!bÌL㊱ØÏFà 6»×ik}þÉ´ØÿuB¥Ž„\$Ašk€‹ôhÃ…Q‹¹Úæ€üd½¢!‘â™lœ¿J‘Iþݾ°F1³6õSP>ÚâÀ®4ûF‹J¼mù¥mþ¡‚–&Ùàî¯ÆE7þ'Ý÷Ž~ CE^Hf+!̇~wÔîvß%Bä-l]uKžCþ›hGÜ$5šˆvÇLOí£U†»àw7L®Bo‰,ÓbSQzï=<¬~c›šÁï²éb+µ‰åÅ|é6&‰®¬™šmÎ`òê5Xšâ¦iH“›uŸõ:Îx1vâû'äX1!PiÂ-÷Kü퓽´.«éôްM,p?ÅBž÷æ$ºçS8Éû¬“<]¤f^ƒ ©fØ=F² ŸDÑlqV¤‡ÊÃTt÷SF)ÅCË…ž®ó™Ökóc^C´»`p’EKújzéN>·êj'ˆº—[ÿR€àªA?ÌöÉûÕÖ=L^÷½¡ûgÍfÜùw® "‘©¬5WT× *BtlÑ©Tö†Kø;¤! úç´û+Q¾õr[tÝ»½²ñËIzW,E6M¬o)1A¾;Ù³-qFi<ŸbAÖ2çiµt@At(–ežCWÑÎT‚«ì\·»;:tÎdúŠEªoCDz­Yšd˜î#1·oTÝmi”Ë«¤ââRÔ @ˆ†u¦Ä?Q‡M”üg;“A…œÓæeldA*©§m5_Sá‚YþÅR`ã§ÉvQR Sëµ±×R ¨U¦/xvró^›ééu’¤ K°þ÷ãÚ{ÍXá¡Ã©¡ÓÅ/…!ÔÛ˜«<¸¬]ç¨:ƒ¨V„ ô|H ”:î…p•Ò—»Å8žxÈŽœ r(èS+\v] ,Ø(vMM۬ƒ¢¢¤ÝH±Jƒ²F"ÏZšdEZ6˜}*™3j©õÿe‡B¹äÑ{ßk"ØzÄ×>2dÖœ’›íªZcZdHÉs@xDÖ˜Ñ*†å¡V ŒZz@¤*‚Q Ѩܟ‘5ºg8ÿDµ'UòÞÁŠx“&ö5¥¢ £X(娈¹îü•8¸ç¸2•ÛÇÇ%@Iv¦á¿eTrîšû¿ 4?€Ÿf¯bâYW©µ‹Õ‡@²šsFpsub`:5ÂË€oÓ¸û§B¦<€ n$~2´ˆŸØçãÙèx_úÐåÃt›su¢_+?áºM™ièšà–ÅH¶CX?…ù3 ÎÖ)žõö¯ãÂB¹ói…“»Ü`RauÑÓu—Ó¯%WîZ ^§: ŸnïÙå2:àg:Ñ-ø¦B)7^g”nBÔü°‚Jàlüz;åë=£n,‘LßB1ø{òçîLö[jn@ ùËJ”»‰ÑY:M¸BİvqœXm‚û¸äÏ‚9’dT‘wJ¡s;j³ø®‡ û›Ú/bÿ^¤WÂå3ÑÀ¥£ „6õQ§§ W”8êi(æ'i gôvvb€Å5¿(-:Á'÷„¤ØbÞ|ä ʽeü¸¤«Ó°Y>*ke˜)ÕA7@2D«Ú$ÍÛwlT&° IyªŽÂʉ¬¯<ùŽìcKIºñÎJ>‡QôÙ¡±‘Ý(í%æVèínlê(Ô ÓsÃ>ᤊê„o¦>^šrSÇ;Gy‰ìïO¦ë±é³è"Û¡¢7§)–C†ÁƒOiNÑï Ú¡[ÅÇr(y[£¤ÕbT‰øÆÌ±ÖkOjÆ¿ Þ#¥Íø 9¾uªÔG)Ìàø—7™Ší jGÇÑã͹Þl@¿9eÖ'ËïOÄŠØ9ÓÈžê§h‰Wôó‡ø4þ÷ D× Ý„èÝÁEf­ÌŠ v¯¯9–pßy–óüɹmù›êߊ‡HA¤{¡‚^w(Þ3([ž•áÖÊDóbÚÐŽ²ÂÃ¥ci!Û"‹è?µ»QÊ7V“ItK"4rE÷Z'›NÄGäŽrøè\5Lù¤«ôh`¢«T€G¤ ûÈh «ìvj70í]ñ1ëÿ^T¼碌 ä85¼ õâŠ;œO.‰{3Ž„á ¶J¬¬rA‡ýç®VÐecŽèˆõóüyªÎd$vRíûH}tñZg]ÐäÀb7?cƒÔ %«mžôÎpÑ"}³ÅDk¯FCq‡oófé_ Ü"dÜ Àããó¼…”72ϵÆ0íáì 'µ—LáàM6´,60ÿ51(Û÷ÿXBépB¼vÖ¶-•ÛÅPR‘øùÓ©ô}±òYÑ‹” p9ŠôÒìãb^B´×:Ü%¤-ÇA;D à›žcáŸá»øà»ÉñSÞBÑt/pMÓaõR¦e”`ü[ÉÛzyJ9bZõ?,iÞ; ­áèˆ/õr¦ÚÉgúåGÖKDÚ-ÒÑ+º²´C¤×ŸÐ ƒçI¸û› üàêÅ‘N¨\ç¸(V“d=ªTФ_n:„á1´saáüRªvp‡;iP4'™(‚È%ãå§\¦²[¬w|ìáx¥ÚÉ%:ÊA•TÝ€SE²Ÿ"ÉdW1'|94ƒûBã?Eb¥×Áö´›\8ÅÀ‚Ñä5r0Ï—ZMÿÉú¯i/]I«ŸS¢™nB¥{è3— T ÓÿKÙ|™Óè"QÍ(-“5¨qËôFCħ®n4I‘WF}ž`k´öúoºL£ˆ¸ 1=¤éÞ¿¹RFùlœ  ¨èVðý‹Q½.Ri*O4¯†“nÍ1Í 3åIÂhoíÀ÷–Wœª@[‹KóN±lJÜNŽÇTNEðÄ[‘|l¯,TÁ¢möÛƒ5pTr|ð™¼Y³×¯D{ÁÒŒë–áÆCÝäúpIá‹}ùk0$Ê5Í‹ƒþXXW-iN¯¦ •òNÝ,F1ŸžÜCu0g\–˜`ÞЛF%›W›vQóm0s$ëG}ˆãuFø ÁÅ·†jzCUïÈ(¿-ÀºpÓ›#®Z”$]0Ìxo^BTF|»ï1ÛG¸—×O®ö­ŸúSÆÈÞ÷˜@¿– ’Ȩ‘M+³!ŠÚ>*f ñ×3 – »¥`ÏÇ÷%›TAMþ§\ä±SÆ£$­*eŒ‡K$\ òV» ¸|wà>â#˜\e³ñ\¥­ùü³¡"¬šϳlû! )Ó`FŒÎÜ‹q\ñ¼Îf½ã=¾WÐ+†ÑjØÜpõ ø›JHlR—ÛMÏpÊóX£J/T37Vy>4£î”PVu.vÑÀTa!гæ}Wiú;ú T“Ã9A•èu”ˆnRTgç ©ÐeÈ<Š-Þ í¨˜ŽEâÖëµdJÅŽâ †Õ!"`2Gâ!Ÿ‰%”«Ng)¢¾é…C*Ú|„GÆ8«ë×ãäD™9/¨`2l[¼U†Œ ìéb8‘Ÿ²Ê·ÄVѬÊPW:¾éÝâ.‚ uÂó¹õ.3¶WòÖ5û¦—ב§è(,òQí,·äX&2å´X]ë=)cJTü8{ëWß"R ÆëÙ±b‰µ P'z7Õ 0Ç8xu¯ÀX±Vñ †”vªáGßrÈw'WýŒUe¤×¸ÌZ$©Ù‡Æl:}}LF èãA1²¹ÙNlûO¹7òoé=µD0þ@…NùÑ)rQ„P‹¿JAë9›§ÛÎRóY{}•½G3P¬UÁƒ÷JÒëRÀ‘žç Ÿ3»mû1ÝÐ` +?/Nñ!èö£óZ×k¥ÌìÓ_Ä™:7ù€@™¸©;aö5™ð9ô8Ù4ÂeÌ=&ЖJ±8UKµVjJÄ'8+·šTî±^ D° n«œÈ/•p¹éxÙ€éGŸÅ„“ÉŸÀ‹í œ[ÿz½ÓÞóû9•ÒÍÄñ{‡JÙøÊàýUò)]£–\°¨)ßvÎØÁR@š­:Ë̯ÏЭ‚J-å ßïï.ò2ÓØoõ¶B„\KÙÓ·2yqë[Õ¦Wd]ÓX¼TÏÊÑ¡””AY0%Ž°Ð ÌÅKš—ßnlŸI¯%r8»ùu5oØGW£.N†.,’†»ªj!ø›Ñˆ{&ú šÅݳa¶ñÿ@A»¡³¤ Î.¹˜uûƒH™«¢ô±Ð’_»îWÁ¹ÎÌSÙ÷îSÔÒ ­kë£ÄßK•{¶LeŒ ;gKÂxÐ{Æö3y >]ç ˜Àµªô7¬í•'o†C¼L] ƒó®Ù `ôÕsøƒâPŒp®fSü'Òõ÷ÆžRÇ“óÂÇ;b¤·6›mT‡â˜ƒê¥-× w&ñvï»’.rÜ“rެ_øHT*®qzþƒE-?émºCz<ó{péÙÙb\M[¼Þm†ªàf>\öY©A¦‰\ì2€¸KÉô#àÐ;w³—z|Á.ñÄß,ÑjYÚÜyL 2 ‚”ÏY?¶¡PhD‡O«]‡rÀŽíxý’ïñÈüô€…43ú™dû ”ïÞÍ—O‡Ûþ'-Fp®ÎÅ…ÊUúsÔN]†×<Ì›VÞ{Áq™óÉÔÙ¡“˜):Cê[™È¬L×±µ¾‹µ•„™Y±U ëéFíà³Xw›žJeìYëÊEK™úâ )´–b8ÏJË1eP¦–z±ê¤ƒö$ÈÈ û3U§9Ô òæ„®q‹œO#Ü1ó”Ö0u{€‘^ M;{\‘}xbãK;Qm9³D2‚áùAg&Ür‚;¹_¯³ÌÚþ;SKŒÿ|1UmÊkãyh¥ÒöÆÃBÇsÈ(N­ C&§"j +ûz€mN$Çò1‚Am*ÔC —zØ|A/MõE:1@Ìã;¤_õ™AGØÕ:󛇎ýžžÞ;ò·˜›¬Ì¦­pÔ•g }È-ëVÉU¼‡åÌi}›W¿û®P%ÓúÜqæû.Z/±Ú¹ä³¼ç* w`É@Œ”|_Ë# Q–vy´!ToiœÒ}CJÓ[zx°eºµ³ß¹1PHİ>áÑÉH+ž /i×h6ç t4ûÀ×J¯ { •Òá(V ›F NÖ.ÐØwÁ*›hC”‘ÊÔ~¸hECû¯n^rœ9±b`XÃúJ—hù>#×û¡•reù†ë3®ˆîˆøó 1 o@ÎwDå–˜届MHÞ¨ú3=`î~+Ó,>^Ѱ/è4ºÚèÿ(›aMv@ž}ðïáꥑ¨lv\¤G¼}}„ÑüŽ| Ñ^båã†Iýéÿ;Ê`XFO:,Ï¡²V“ƒ¯ªB^fÚÁ=ÕIy© #'³t};s\²;l^s‰<ÂÆÆoÄ3 Yîösp3ݤ  [ ñª»š€±®¦5*rKmм§ÓŠ^f¡žs?Ç6³2; Ò2åÈxÉ«othÓù¤éÓkgÕs-вø}æœÄù ™Ãùó£¿ì@Æä,FÆé‘'ÿ;„LT4 _~&óX°íËRjvIñí@&Õ<·.ÝÕâã,q­Ø.±Xí&Ë6½ÀR[–e¡Æq÷`!Á—ì…~°nûYdMg9‡Ðéb?–8Xw“+|+.¾+I”æÔ’4óþk“½dBië…ªCrUüµ'EuÅããÿ0òó|ª1óøåð‹C‚'(®)"pÞq/”úŸV7'ã35RŠ[:Ô þåÒYí1øŽ¡•þÐÝ[¿üÀöµvÚ^Ùd²ˆlÛzä£ú$¢õò•ùJÙ/ÊU›Ä<è]I¾Uzµ>¯íÄyR‘ßüÈšÇéðhÅÏÔÙ| ´>·ºÆ’hÍüÑäM{ðh$<›ç'¯èqíªŸç º’Äe84m3¬œnMDOiÆã›Rdc}B½*Þ¢i0'øcÄä+¨´»kÔ!O2Åÿ8y…î{È*ÂIÞm‹Å˜9¤òÃrØy·ë%ë€!ZÔ;kO ËtŒáˆ$ ^‘½CÎ*¡³<òŸåÕY¢›¿,9âÐAï!wl‘ ðXш›fSn³Ö‹Žâ/ ‡Q?XVFlNoùd¹ËJú3±(®vÚƒaV4æå)j'@ðüPwp‚{-yûδû'`°ù¤@zqFþñu4')nÑi× {{§MÄ«÷6™ýROµj 4sM¤ie6EÌæ¨ÍZýMÐEÝÁ¾‡Â!¯pMÕ3»¶À B‚Šš€š}%‹ÂØÅîc'‰ñl¶¬¢ј`p—·FÙ0 »!fv} m;šZÕ’).|’vfH;ØS”.£ L©òÛ™Î]AÆEg ˜÷¿vmbè_ŠfÚ4äIP/f€ŠüXÎo¨ts2O}Tá^|m9ë¥ÐcD婤a KV©úŠ~Lya /Ú¶ð'lá¯ÞÇ^°ƒ+½©h}TÎò‰ùô‹û÷§Ã‹7` •›áhæ'YkùÛËQ+Z£¸0X§vu4_(?]Ÿ†ß×ʆÙl£ŸmééÍ8Ï”(á…±ñ+&@ê–Ÿ/b¨a· „ŒÏ†ˆd7ž¿>r¼ôkR#zÊOáÁdXTƒ‰jˆfÝlçz‚r‰Gâ&x¨£ß~)&zmXö¿U$Û ÆÓбòärCŒc¾0LJí\2ªêƒÛAƒ1,PîwÐTW:ËÌU§ª_$-HƒA3ãkÊ€ä H2}ÀÚZ{P˜‚ÙkT0áx5|¾Ûš)&4UPÄN7Ùm3àA\„F¨’Ÿ€÷š½FtÅÃX|·ÊúE+ó/´Ù=iÄžƒª5½ìS“oCNRmÇ›ŸŠK›ÀÎîß«›!,¥RJÈS/-Ì<‚e4[Ájþꀛ–‚¯âœi&ü‚‡'§Ås V4ØÄëJ–@Ä„óy'»ììªÙôZ4dî[ƒqRtÒe’«žL:iÊ,Sýü,›"!BJ„ vJĸÉ6âÃYª" “ÎOHⱩyÊ—?šñ¡×§1>«[·Ey¥'$²2jÔPåøTu»Ý]èÚ(â¥5â÷9p·Rþ¹:¥Šëͼ$b´yؘG¾”SÅu«ÌξEÝúªß2ŸûÙL) ÿ'EºZ7&µÁö“á}zÉù« Îëoƒ?ò¥T8“â0¬_Sn±LS(K“ðØa^úäõäò9/̼XZšm22røu›T¦…*÷>O $ ùnú]¸ Ùvì’Ám1ºKêMrÚ¨P4’1ó«@Z§pCï¥Jô½î#å_¡§ÿFÆíu¸ß~„Èx°2ÏIWø\n ᦚۓ¼-w“ÙyFõ=•Ås¥+½*¨à—gþ03ˆòüSš7ÿ-åGþMéß ]©ÿ@—zDMSÞÉqÿLnÍÀÊ–>wkÔ”Iv@5 4ÞÁ°).Ñ.Ö|óMLgYdïæ'§éŽd9ËîœC'.©ò ëžKý=ëåøúuz`ùDsf«PÕòŸFäë¤ð„{„;}ÒÎØ-þByëIYÚö˜FänçwJDÅçwH;<¿1cÁ¨KæÔÊå°¼·)@¿ÄH…A!`‡2×Ã>OØ›v-ïº ƒT®5CKñÜ/ŠôxüÖD-ܺwìÛ9ÌCf¤c÷du¾>-y‡o?·h¸Õùt÷hcd?<ëîÔ€üçÂ!Ã,Áî ¡|7¾­Ï<. &"¦c¸`ʉÀ ê˜_àõ“ ‰é˜ß:‚R' ªÀ¼ØC¸½;is¸¿¯q÷vžNL„k®»7oç…Õ;Uø.›ÒõEê,`³0¶ý%Æ5.GKã9Éå ï¾I¯¯êKÝÑÁ%?$ ]Õ=å8WIì®æ4nôÈñ’tðqýŒûf냆”B\j›×TÚÇG ¡{ây¬ˆøžâKæ• NÛ!|I>ìªî¹!yE« ‹èÏ­ƒ5†¿kõ(ËÀò r•%W„³[£ÎEF#ÛŸþ9ýÄcÿ#x‹c'¥ÏU–L7àð#­¸w3DGpfôfcÿ(Ý[?TN³FÖ%\Lq3ÏÔí¿Ñ‘¿ÿP(mPÿ(ÐOŒÂ÷¥(Ë ü1†åé|0‰Ô$žÛT°p CÇɾ*‘4M«Á{É’7ÿgQ7Yäþ%CÂO°M\P—RÏ7“;½°*ž+Gïæ¦£µWŒ+>æ¯SÐþÛíoù¤,Ð84 õsJ1aÕ§9K^%H¸ $;£¢^S [{o§É“¤a?Ö¡·üú}x¿?ŒzJœ'3œXs$&{Q‘{é'áäU£¥7Þ=JÏu ´ä¯gêÀ !p®ª<Æy¯»£‘ÈrXÝyIbé­8~ DÐH?uSLjÀÝ’Õyµ!;¡;”$Ïe²ý;O ÄVcd‚¦ÿ"Т›\ƒ€KJ\¥b/¦@Ð#Ã+$íBá½g0mÓ%÷¤¼0[ ±Mé+Φ‡ ½hæŠôò™Oë.ëúÅ×`ÿ8*µ¸ péù ÉE ^6íüCN¾{Ý"ÈrM_1aY#`Œ”x+¬À anw@.”­ÃÆÞ—ÏZk‘¨îöH ‚g”1Û[ˆ¾UCŠy.SKxUwZ×Éàæ=˜ m^:án¼ÍÍ܉@›ýñI.ÃŒ˜!´èòCakE¢u<û!€-~Ù+®ºtdå–Ð¥š,ä.*]ê%Zlhòq+ÝÝ.ŒÜMx™9¶J¸Sð˜!zMp?WÛR š1AЂ3i¾wÅá®õÝ¢scíóïw´ÖwÞðÈÛA£F±Büù[ èés~ùÇõÖA-߯Šè¯…2c² ŽàîÒÉ”rÕLkã—Sîï§"Ñ6”|vÝKà+Œ| Â6–ì·Ü¬`º‘¾ÔœÆ—‹'ªÕsÒ—‹Ò’h1ÏC‰\í_q–ZÓ÷¢úŒézÅ'ùGÙ²h^CK£äš¿¬ý;Ô×Ô*<'eÆð7Kz.Ò7ƒKyŽVcˆ¤@Ž£&P`3+ó¢²ËîM¸'åqÉaŠ™¢¶Ò](i·¡|.µo¤5…-U¯£ðp]ÖLÓD,-•¿!-—²«È,(övI‡>!æP°«S?@°À×\­æ¾Ý×C%-Mæ#»eÈ)®SGÌJ_k@Ñ/k (Ï”d¼ó[£@÷‘ñaæ'¨Ñµg š3Oû¡zçNèÆîc®Ý‚ª¿x¿]€¥ÿâƒTÏ€+æw0.ë0¸Â%ó½†nâct+ÝhXî+Lÿ$ôÕé4¼|4Þ45T‚<à·; `âO=0Ó%?æ.æÅçvÕÃ&eHõÊwX¿`q¦žÎIªƒ¥ÀÝ*r÷û´-[E%=ÐÉÑ$‡1’ÔA1„?&ÄŠv¾®–H’S$L1‘€1´Íî<×c•_íU-±’½?5%‚ºuˆ‡n@0æ-Å’2Q¶8 !]ºß˜÷¿Zû'ÒÈk×A†õ3È›Š‰—áLE§SWº™—çŸÜÜ0öŠ9ߤ½ý*×û‰ Yªªפ׎ƒ¯Ó^ æ™M„Ô ê©ªÖ0‚ gú0Ü–p$ß©@jÏÿp9ô:Ëæ «¹«"VwfM_$”øYxÍ“¨¶ÿtžSý1gÄ18³š†Oc Žì`Ø*+M îJh¼ál (a`½Á_™7¯½ üšdîË.4«>,ðöŒl c["%¬ÄwþøÞpê·0fðFÿ\ýÇB¯ ¤Dë=¬üú¤1ì¢Âßï’‚úò×[V^s™· éç)ñ²CWÛ½Þ#±8 º¬‡þj5Åb¡Õâp+ fª¸Ü›ÃÉe8Ö>ƒÛ¿È#8oï-DD©«ª&¨Ÿæ ¦ þ[]k#JE+¯ÿR›T!&]¹0ŒÈ'%eDI£Ö”è ‰&¨…½ÉÑdèL8$uC2SXsúň8Œ‡%™>0òÎ<…ÊÖog`.!&ƛڤQYnQöûF\7Ǭé8³£]µf½|»+¥Ï‚Û9‹²êûÝ7N7a½?š‘µä…lÚä8 ¡6òZÖ:Y¸ƒ^æÎ–‚S³¡#CŸR¯eؘi.P‚èÐ?¬Výø3÷ÜÃ¥xØ*ù9“¡™è…‚ò3¼c# ¨†¤Ð0Ë×}ÇE…‚ބáeY z?¦wSfòNŽE 4“0Håf¼W§ƒÕ®ÚgÔüã|’¹ŽÃ5›úŸ×ðNg=˜úE¾ä9HÏA½:†ÔxöWƒÿÓ2D¼Õëðó—‡Ë s£Í«079úèSu*T5øXV5…3':›ÔÆVLc*âЕºp}ƒþ@džEÈõ—I"Ñ:„Y݇¯¢%·«e!´ˆgà ȧÇ£>I€`ÙNg:×þ„‚%Cñ<KÆ0Òø,mâ×»¢¯O´*€cæ ÝÓ0)ošDýDÄÔ€ã£RÔ+ã¶Èˆií´SÝÐ Vއi²ÚãüLo_ ^=š)¡k‹ÃŒö4{]ÞæwˆÆNenÐz*D\ÛÖ7«ÇøPÃXžk¬cŽÍ501xO-‹P*ƒrŸ2k/Luæ³²÷bŽìXo#Ž9G¹>e€ù-ªLN\£œàK/±‹–ø‘À ]‰kU¬}MDPl<3 Ÿ¼®f/uϬÐ󩊼1)’¯G€ø ¹ûÞoHÓ¹®gNÀ*¶þP³pËôâC€³T5éŽ.bм½OÊÞD`drÞr”&ƒ,ÍÂ-‚[+5Å[‹zoÊ¿uÄosñm„hÈ×Ä·¶Êò~'¢>³-ÝI R¥Þ‚„£­a—$„*($‘{ñÂÄVIM÷-¨ ßzï{¨rèB*Zö:ûq|H†à®|ÔZ E‚¼£µAâÅ;WiL5¼n¨©û•v`Îv›FäpÊ®MŸöð±K÷Åæ W|IíZy Å·_e35ƒèI^£º#r£*t˜þãÔXÞ¬›éà÷LNUÁ1ùõfºƒ"U@2©Ãiv"¤ç=€¥ûš™—¸$(0m{•I#˜Có^w¥ûÄW*"3kÄ"Öùnßy_ÑË éǺÁ?pë´ÜŸê¤6¢ .=wÃÕåcFøî‘õ-ÛMéX,]ƒ”h-‹ZŠÌÕ!KÏ5ÔþõEØY8~!,…\<™ÔkÝ2¼H° ä5qYø‚ßötcºåeO¦¯³E¿ÚÜ8ì·ÝPmå @È?°of:˜wûÃ6{жéæÊœŠ`n¶<|r½1 "hŽêpQ¾TÉv>w€røup”’÷&± v´[Œq·¹z© eÙ<º‹Ååß%ÿ`…'Z8n+$ùáíòˆ¶ú‡±Ïì“ðÂ9ŸE%cØñ‰­-óÞ‡‹ZÊÒ æÆŒã©×‹ÆtGßðË¡"½¤0›³yeT6HÃS”,è]Š©"vaÞÅ@‚½cߨO)‚Íî„Ekád4.1™v"]¬ª#f\º/1-()¦Š ë@ œR@㚺 lU§­þñîd0;%õ]EôWg/Òæˆu~!¼m¦ãH´d7ô­¶@_ð,"INd®å8vÀíÇø¹ïÑß.Òj߃:òsuZMýØo“å4X;ÂÌS8T ¹™õqñÉO²Ð—tA%Ã?ÉW¡ÂÏÀ}ø1yÊ_øhRÁOpÛuñÐe`L¶l¼mæpÃé]Ë^€N2›ÿÿ@jv;ô?üï.`wÑú2SžGLõ³Gÿ!nð›ÍÏ‹à‹¬W˜¼óFÖ$§Þ»UÑP*ÅmV|ŽÎÆJN<Ïêgïˆ+vj„‰OÄ çRîõÎB3¯/Ý7nŽKÛ¹ËÚòÃtÆcj€&CçÆGGøj?' ¯ÆY¡G–„[›3šXËÛé ²1™)9¦_ËKØ"5Û ÀÈ5ø"@Þ“bÝ–adAþÑû·ÄÌF¬ßz£ÒwòÖ 6‡. ý«¶˜A=º[ƒ¢Þá–²Ü^F,•«Hþ oe~>¾¤X.1ï­wŠ LÑ^,ßãéxS™A'°8 “dªL£ð°Ærwd,c¾ß¤Ë*$½v>ëÎ¥UÅ SÕœt]rÍ9L"ØúGšï}ß Ð>{TåÇ; ÃTGØnöYO릖lKËf´m¯–gÕlÁ¯#)÷àq°iP#”#Ø{½až¯Ÿ£Cõ®Þë·TÛíó×=·ÁqñªË¾k¼YkB½›1¼BÇ'[lñlfV¿gTBîŒ#;f¯€0’jRß@iÞoMºÂeûÝsÅŠßkVìANç†íwCŸòø‡Þqž©¿ûË5¨èž""ƒu’ÒV4øN½È£Û®hà«tÜ9zÇSìC"û9{D¦5qMêPöeà"BÂIbçÜÒ{Šb·C÷©_-ѵ¯æÎåH 'vO]QSŽpƒ°ö§¶û€Ð‰pa_c’×^Ù{,ýZT#~ zhˆÍJjû÷]qY%ú°0"´“,‰™Wxhey>ªÇùèE×xN$%ñzeaÕâNn²øÿv¯ŒÕïùU®ÈÞ{‹ &û„‡à¨ÎP¶oÀÌu ­æ5#&§"Oÿ-C¤ÏìÉRºµs÷ ÜJB08Q—N Ør>&ÐâfàBw²©8¤DëˆìˆpvèÒ$;´‹÷õùÙß3½PðI†G€ù¥ÉBü˜¿\‹‹Á&™Î`¨wÜè¨d˜&f¡vS¡HÔÄZ=dÑhmw+¹l[+K΋GM •™Ô³Äá"é@$hOgVOÒÒƒC£ë¢%r³¦6›âzå%ˆá! ­ãšDaÒÎ4Åbu aÊÀ\ÏÅr ÍÙ„V[W>…!†vâ¨HZ «ÐõÛSzs/·%åp\]e%Þ¸ÿtóÕÍBðHdÆ}ÈqLÇŽª˜øÄÄpY¿'cKP…‘=9==/#/1ûðŠÜ’ÈÔ½_bÈÿ-~ÅN8 ö´Ðú¯-¦TõÉf¿GÕzÖÃWq·fL.8ƒ|VÊæ'Îé å =‡K“¥žƒÃ·öQ7£$aÔn<}wd8f$Àh§>½Ò¥¢ŒZEaV8¡¦§FihõDÐJ\HÍüñþ•Äת·LkœBT½9¥ÐrOýðVÃ( Æ^†éØ#+¥Ã@&µ-³¥(tíg­}ea‹1¨¨"ÿ_}œ”~0Ë÷À¤üç[ƒdUÅ9±ÙÌaPbž4Œ”`àŒGsŽ8×d€ìÐ\ôˆ2ô~O8ž•¡°¯åH¿q­ïÀⱤdµQÓd¯–yOÁ¾kð¦b»y€ÇIyõ5c¶Ki1¯„½bëN’°ˆ!}×Þ¿ÀñüÊV±kÍ[²jœhõ®A—}ÊÑ ‚›Dë!7„A‹àa$_ÅWw=¸lj凙)zž5£±Xák°JuЉa¨Ý‘€CëB'ÞëPiŸZ‰‡1‚B-K¹·ò)ûN üÅÙ툞»!dÍå%°‹Žˆ¾ÙÔ¶¥îfÁ÷ŒeùÛñ羄.Ñ0¾Ý)ZçI‰#Óß)¼…î{²T¿¬Â¸%ÊÎ07çûó\)yðVo(…Xýa¡"Ei=^Œ8+è~xº½$M~ÒüôÖ°r/æY5êFìÔºp~ “Å•@²¥OAªó(ð`fíBq÷¼EÂ0•ÂsÏüðÎ:ßÙxʱ½[’ªÐ1ß­7™œ ûrZG|Ë_5Ö‡úäͼJr¡ÝßVþ³J(iêåã§%)—G?}c}qQw=;+DŽkˆ¹gÙ»ØÀú=ó¢ž \}‹.wO¨‚[ï/ppŸÍ\œèÇJt ¶wü'‹|Æ|'>sa3²Tй¨Hñâ'×sWn£Vôãl¿DÛgDܯâ.ò÷‚µíVo¬'¡Ê ˆÜé9~–ܧ2ÞP?ÿ5ˆ/å_RõCVúîºøÀ¾”Yn{¡í x䦈¦ir ÚÈ6‰ÌË߆~†OËΡDüjÊ$·¿™{¸×ß3®Í{ªhè$Ï) œL~<•¤½ÃÌ£Æß×ïÙœ¸²9Ú‘ìeîEÑð'Eü‘ÁS›E½D å1Ú³,xü¡Z´y±]ÝׯÉ&g%ÁQIžaÙV*ýPI$gˆ¹Í “OÀbæúË©p6¿ç•?Ÿ¹ünJ J8Ë5ôRGŽ—XŸ—Ìgòã܆<6Áª®Ôd+”jy'“‘£~j¥ ’Bvœ³úÓgâix¶qûqú+ mÛ–;+D÷V¬6ÜëQMC¤Ã·U ôÛzº[@Ó[ rõÝ$ãÊÏŸ€{е<³VÃ-˜£ù:.kƒØ88,¹("4ª±¶Ú#õMÒös냬_;õz©¹“^?îUoÞ4 {h)¢T$&8±0À”Ý|²êG¨d1ìž/ÉöúU9‡ºKàü¤õˆ%7{cŽí¼&Â>{’>¾¥k…bH |±J9$òóÒ ðR@‡€¿g–1ÅÞ¼ÌæŸã'ÜcúøÕų_ÀªK·Ÿú³ÄäuÐT>%ãm¹6!ûO¨ÌP]YlªñË5»·3»î/ÿAHÀ"ä”Ð| »in1"ª·ª!)Æ ¡žp6³¨Ò±F |…€; txeÆ‹UL©ÀÙÒhü~Keú›[ïÝsðN¥K‚ä››Œê§ P˜Ûe¨‘VäÍúBó“øeø5x–"`ï´V BQÐ0ºv>Ï-|V‰P§ôcHGRŒ’‰´á÷;—­47},N<³8ªåeN‘µ«¾Ø'î$’C®Nd²£ÆÏW¨m»f-ø·´×Á]‡¢{ Ý[t³Û@rKÓG¹!>Þ2ýÎ{hÖW,v‚‘hH åÅ´!ý±Ž[\¬“[1ŽÐ”iÌiÏ·¢ ð6 oiÚØ€œ]§/9RŠ‚¯Õº,­-è], Àâï M4¥»Ý<4IX0èÿQ GµÄì:.nAŽSjþB]â1ÒÜ,cØ÷¦Ò?WaAX4”™ÈŠYñ*5Š¿ð;¦DÖ»^rŸÐ–Ç¥Ù𜫌ô¨±„ó؆b8ìÊV’¹Ä2ƒ‘YmVmÛ)BÚU(º@Ÿ­<ž”Ë_f¿[KÉ )oüúyµ »Ã n™³[g¨‡qÅ[™×Écð—kKg³¬÷IÚ+9gýpVËæ8ùüíþÙ¶½z9Px€MðÞx…7Ǹà]¸@IåŒ:.í…ݯîÒ)Õ˜X‰ÿ"ÄÚõ‰ÛŠ>ù¡o¼„®ÝƧzrl·ÎO|.aæ`‹Ü×BuBôMøc?=D³ zeNe]_y]ÿ.øI~ƒa&jnÿ}‰ž¥ß>ù¸&Gà˜ÿB'»²8ÆGâçèÇ[XÌà`Øîå’Þäi]…ù˜DÂ’ÿxcõ7ÝÉi-•ßó:ãjû¾ID|JGCšÏbË4øN²ÎÐ&òw?E|#;“aÂF\÷¸/s=1üKh¨LOªÁ| h¢»!°;o¦"ðõ²-¶,ú·«êÇãâÑzbLPÊœ«Î’EshTJ<'€j~À½ÔÒG lõQîWœúÞßSˆ€Þ¦Op^'K/4hÒéú^›òA³Ù¦wœé+o¾×ð×=ØvM}yã¥Û›ƒg8óQ‰ú´ÎdRy²ºUÕÖ¬ÿdby³c}’«#‘0`Dkí¬-7T’Ϫ|Ÿ(A@¡d5`G¥TpÞcE·ânëÜ÷CÅ|]úކ¨€¨Y·ÙpJ™ÿ|"Åß=äz§·ÝíëA»ÿP‰½|Ò«ö õ7Sï)ØT±`ö8¾VXtŽ4ci•`Ö£GŠ×XcÚ\ +cÂ<ö7Á§êÚã¹`ÁtŽa³KE÷úǪ"¿DŠ(d°È·PÀEÐØu°ÿ xYÃêmž;.T¸ÌÛÕÍ<8·¼‹J<ß„]º’=aJ‘8«R2 ó9­Î‹„ߟÊþ……ÛßUC%yŠû¹6¿ˆ³Ø™Ça•€³Ý6vVh¾œ‰o5ú Ði~ñ{¹[—&QA&å5~kF3«²9|(0.ép¸élsrá¬ÑŸ§.("‡½)Ú⡳½{…ßD×ÅsX2¯òe^4_H½•Ö«b˜$~¶¦7ð!âˆEv„|ÕeŠ¥¹‡òvÈr™ úøÇ¤æÅUtû`óVÉPîB3k‰šŠ¬X¿`]ÿy7³äZ@)p§*i€܆™ –ëÔ—N_<%GÚ«D/CZ³HôÃGà"]doÿ¶'õ?µP$#`ÇRŽ"A+ ÌaõÔG°éÄ­(Õ@5Ua-;×Ì2̾Äa&U˜ëè-ÆX›¾Ã~/£3wã°÷ .ß­§ÏŠP”V6îƒ4òÌÕÏ÷nIî mf®™U.@R_$DUfïQ É>üÒÐy 1£ù ©¢á€ÉÎÜ•Ç ÉÇåýJË”ù—¢Ôc£tŒ—]0žrΙqPþ üÎ…MXÍÞ<ðꆊìE¢{ªq¥O¶*‚ªŒ'ž-í©oD^õG± ô’–ݤ@ÐÑ/d^W¸Ez°¿Ó,¤ƒšìßþÌE;^±üÏ¥/âQñXÍ(ÈÀðZ¸hØkÆ=·ïM\Œ<ˆŽrà(-¦j•̆”òYʼn´0ÓF+s]ÎÞ¯‹‡V¬œÒÄø@«Áz=¢ò Áq*—ˆ•õ÷}¹hQîáÈñÛ?÷V.éiÃ*PÑt‚1g—¸“§G|’KŒ„¬>¬%.~„f–BÑþ™¨f¤F&¶Côö#]¼(ŠW¨bÚ #sxfd±ƒÕdµ*@Ú …d·Õyõ8Ë’/g¦[èk©¨­{Ôs^Îû¾ 8¡W$ämMhóG8&áF.ÍÌ·’]þ-ãé§øŠCA5;„^eì=ª'hC׬q‘®îâk6ºÛ¶JJ,8þŸ‘ŽÄˆú…BÝoå+žËIï0zØ?!#r"Ж Ý ]æßÀî-/6’·„“”¦šTÏ l a¨t‚”¥å#Q&¹ì&]c Rðê³s ð®‹ß9Å^{æ†{Rù§,€)ZÏŽœ'¸mJо%™oÉ}óº†Çàš;1ôDÞg“ÀŒÞ¡uÔ‚Ç$nAZÄãh‚ÂV©«ÇÒþM]PK3Shšœ°ñ칋éÎIéeµ\…L~]’ÝT ‘2 á!˜ÁwÂÊYk“Tx{T37¡ô¸§–Š­a¹™…gÌD ’,GÍCWHU¢Ç¾è"Ïç_”ŸKMG³ò Í­6W|QEN`rÍ–[àÏ<´Kã·Ó$€?;ˆ~™ô `~® ‘å>zŸî¼l…MÆS¦ö€2K¢íešO’d§°fu¶‡RŠA3úåóüD£º?"Rá"£4÷+8wdÿ°Ÿ°zÞ!Œë¹u¶âîࣾ9Žñm/kýu!ÚÛØ9øˆòëÃNÑR±|γ\(;'+\ȺâLC…õe }­‹1ú'åç“Tn#—Ò‹üߞљðsÈÉl×ýÊÿxÞŠv ßgüÀ‚÷ó£(ùïS¬…çœ=”DdɆÍ,d°)Dس+¿§¼;õw=ó8*¯Wk½ %<ïïoƦI ö1PÛˆ`¼†u Ì ê®` A8²Ø£gÿ©0áy‰²ºh>p5Áìt®ù^ h—c¹fÐüÏ—ÿ?,l†N€uˆŽÙöJç¼­FV®{­'k*¯U¡-Dæå þe2†!˜€þW_Šz­/ï»øÅµ^¸Ö¯À~àùè¦ÄgÊÖà`î÷AÖ¹´C«ôÇhoBù¹R¾ÌLÖ9ùê’ÄöÉ)…"öDß„Cs´â·±ðcºUúr• Á"ŽJ=Y®YYZìxhd9¿Š]q…ƒîFºK «ìñq ÔY"qñÎJv¦ŠVž Ϫ*»—ã¾Ó=³¾Ùª†›T2‚Y±0¢OQC†ÿÉ8dTÂäoö4ÝJ‚‚µŒ³lžaQ 6»Ù"3Õ½‰Ÿñ#xWv3$”e ]ç:è2¤MCC½®èP}(ÇÛ›7ù×#z`¯šx_#¹K¢Àž¾`8âû(%%ŽëtÖÒ×­óâ, Vk9ì¿Ä@fEº€j¼<{‰§ó¦¶ª¦ëa"F P3 ±úñý¨mÞ[ú7©ALkånÂÈñBÎ6ZK‰FZá[ýò±?z­—^)_ ÐF¨{§ù :šGHQ÷Wù 5ùHM•îèm'çÎ.”¶VJÑ“¯ä¢UËJEk®V¿MDªˆp=üþÜ<%„+ŸŸ­¶Á¢=öOl›¾ñÑÞ\²rØY°p‚îKÛñö0!7/62‘oÆÇˆ-+Å:B2 1%À²wZ£xJ _ swÐx;…h7jèü×0ßWY8mw vU¿‡‘èg5ˆ¾Ó(~à»Ã¡Ù@ÖÚ$Å9uUF×H ä›ÇŠåív¯ª2OýÏ+`wpÃ"QñYóˆw2p0òï¥ëjûÁ?öà`x§ÉgxŠÌ #¾¾ J1}A(ÝÆP«µ%=½‡Ç*5‡ ˆ´î›“º˜=5)¼ØÕvU{‘˜~$JëEWãÕŸX‡Ö+r 2˜RÑÿR¤àeÑŽ…H¢ï8žÎ&“ù"â^ܦÁŽ™òú&tA£ˆ«$‰CVЊ2 gô‰]Çv¿õ#ª5®ñ-e:V©#Fàdûmq²æÁÉ@·Þ„l–±¶®%_ì)Wª3î|²2îhX`>3¯?©Rª•ºŽQ©2áíù5¡F¥€0k‚ïrs~ÒˆäuÀ2OyH„«ĪR¸õÒBíþ}ž;M‘ÌžõÖÿ¿Þ6†Ìý»Ç³ÛGKª`ž³°£a›·”_íøÆy!«Wy ð(%[øˆd´†Íi"ò4*MÔJ7y($Ù]JTœÃÔ:úpè©FÚ†"3òEápó»À³~ÃqÏjU|¦d,&|ßÁˆ©2ÙØ§ù9w’¯Û ‡á6„Y …’Òb~кÃ+¢¨ýÅ·L®:œKRZÚ§ÖßAý,©‘­ƒ7H`­+­Ò§B%.#½:êµ—(–™Œý1Bó/¨·V[¡ ×iC×±QòGìÕÈLË´;X£°§åI¨>‰(ýÚëÛ¶%¤aÕpw]“[í·©ÇœKÚö"* EÀd²• \f‚ƒÛi\ð»Æ æ ¬clÄ#™mc v;ôuñ‡¶Zù]FqNj£Ã,ÙiFå’¹yß8pÿ LlWà L ü=]ZÍn}ÙRäSÇq.pV âB1jÜÌ|èOg6ÒösºÂл•í[a»ã~–üžjo5„«UJeƺ‚nV9[»K¢ ‰±’i•9>ê*¹NŠ9C®_ÿ/pœŸ=“«É«í”ó1N¥&rh[2„ãÉ~s?Ì6Ê#2”/·€°—gÌa Jíð æ7b€Xš ÞêpU|ä·µˆ®f\óvó…©ìs‡mÝ`Ý÷)—ÆföárôìÊ)p¸,,!»˜RÙ hNÇ(|Ë5ð¥¹:7p t+WSÁp\&“T¿õAh—á/Mä’deKÍ M †%Ö¼ƒ>æËŒrxqâú¢âC“®r\• t|NÏtÓû{[§¨@"áŠRN3t¥ÙqØyÿlGß|æÀÄ>‹÷QWÃf3÷’ke#§©)lÀÍa\íÚpdt# 1JˆÈ–'}dFGBc‚Öi¹ì^å2…4J9¥h¶-A(åX¸¶Q®jôu€ÎÐÈ*>‹â¿«}$©£8­l ,D§g*Dó,}8’p‘ó¸|`¶”‹ ;”Ûÿ1–k0H±$³ÉÙä®p·3ÎÆÜaD"/i*)çõ¦{“;¨àZiäM5C­]ó)-]Ï"7zñw—ðA‰ð÷Mâǯ%€’kï!ºuøÇ©ˆÄH0´`Æ¡Ï)zŽÑJ!â·Æï Ž7 Š­{‡ô-‡áçúÔhHBÛÁžÝ»E¶£9Š¥ª|Î5+“v4Ý»”#Ò¿½Ÿ±EífµîåÃ*lXŠi•½ÈcefNíhŠÚº/7h~zü˜‘Ü­#Ù§³cìó§·µi§·‘p±3.äÛª7¥ ËLÓc‚H#”¸Ê»jñs›¼˜xñØ¢–ꇽ‡QÎ[|‡ÐÜ÷Õ©‹ë¾ßÈá!iǯ¹„»Î¤w°?ࣨޚqN˜Œ> /Ø–— •a˜¬Á(j®Ô° k|á9(‘hÊX¬šò™î™âJ ¶ýÿe_C÷žâ/}‡â¡Ê¯½n@£F£2„úâh²ÅPÏö¡Ä”'¤î–rB{yk@1¿ ›‚‰'‹/§KjûzYen(¨^ý\¥ ¤¬:ß°©Rþ,² ßû'Îó(Ò!ÚÆíNßÕiP‚›)Í<Øhi-yþWã!°FE"8)eï펀µG»Þ´&ñlüŒÙ,O²Å¦ej'´á¸Fy€¼XSžêé¶9þ  ¸Ü,Çöì½dTí•&ÚñÍÀ?mÍ`Ì|yXnêŸ0åÎTËaù½¸ç½P<¸ úlŠ•'Ÿ 6˜9†²þáHp¹ã1¸¯Õ‚‚>×¼®ÜÏ¡ŠmÆ]Ø(ÆŒK8Š×ÜýYGÓx”wÝLJ‘“4'Š>À9'‹ÆHï^GFmÀùnŽgLgž;íjZ$ɇc”^‚ 9Žù‰ ׎uÈ@,¶91æŸqZ.!>ñhh$3tð‰õ¾É—”Š£½³HÞß›ÆMiEý{ëF\1—àõÁp$ sɇ’ŽcÀ-%%Ú™×ß1(‚Ê¥ºj_À/JÂfä˜ ,‡ûMŒäÙëf`©°ò–9¸ÄeGö’Œ­¤Ý²åÎ_8ø™Z Šv’ »gpîcR¤°î…²²§T )áÂS:cù gúü*' õÙ¨Šé@w°£Ñ%=ža|êÎÏÍÚ¯9?Ò³Ö;\«`áB¸(¼•ï±³þ-`Aó0˜œU·ëd%(G\µ‹7‰p³ÃZwëîjÙ±ÝûœÏ‡Æÿi Å£"},|÷ÖXX1BÌ´ªÈÂ¢Þæ],j†øû¤P/ÃÍDýÀ¼œKrÒüºÇ׉"… 3‚‚@pçêƒ`8–u?c¯ÜWõ.Á™vMç“æ%2¬ü)V¯M©W(³Ð™èS‘³¸ ®[£¶âù—„ÍÒÿQ‚Ž\‘ÍÐÊöRB{íІâ¼o ÷z£ÓßÖxE(X¡v{‚TòÉ*–ægó5æÖ‰__ç.5`ÃoóóÀ*e:˹øæè‹àŠ–Ì5[H8·Ë•âS]t´¯xò Ø+ÂÅ2 ÷)¬á›éBO€›Âõâ.Äóõ¸:CÙìòNz6ëWàŸYpϼcsѰ‚OÛn–š­ÌÍ;ü?·ŽM¶Cïî{np¥!ïØp±|×2¨o_Gõ; ÒlÑG˜ãç47‚#Á«xc+ñìo´Ê}“r)i3=ÍtG"y¿?mæÛL¬è™‹x[Ò׃`È!$îy‰‰\C ¥›¾ Òÿn³X9†÷¶BæDÚA _ð#?áÜ „rig PŒ<ÍRƒª­²qRTj'¼ÂáÙƒÅØŠ$fÙ¯;Ü_ŠxØU Ô'¯vè&¿S+†ñìÓ™mä3¾n‹ÞZëÌð$³àÊíai¸ a¯hìÅ̪²Üwçý¯uú›Û÷ÄJ¬Âò¾®”ï«¿Mû} o ×íëŽ|þ²o«½ÿmì¾Þ¡§ÕéYϫіû{hŸU-ü=T¿‡«¥öÓÜo«~?o Wû}!?oxsçõ>Q³ç÷ûz²ï«¦™óý»êôFý½³ßU û}Rÿ7ÔoöÔ_·|o«¼íû}op3çõ}]<ßWo_·§}}]%_W¢G>¯A/áÙWí¤¿o©Çðõ þé’ô2Úº8ò0B—¤½l©·ä.X“ïO¡ƒ\/·¶ v3—w:ü£TÈaF @ñp¦Q:%‚ĺ-ÜN­ØlÚEI-üuií ˜IÙªÍ,f¸8^µK†ï@wnNƒ¤nã¶•à1 fzãËl}u'Ž.^0¹¨GÌÞœœ×Z” âý×/búám<:|ûú\?4C ÁùïÚñT¸ifoI~~g¸#J±‚úÃØ–Ü&U7<¦JP‹2Ӽ͌¯ž àÝÆÄÍV?Y§m€"ÄOöˆ°‰'ò7;F×V¶‹;Íh¡=ìñ –¡úÀí\SÁa4ŠöˆÎ­P¹LhTA˃µð¨AÙ­Ïõ£“E"?>Z†|­·­”É:‚9ÕõÑjé­Z_À}î(°VYG/ðü-–}»“ä3(ˆ<@á"¤Éîº î–ØÛyæ(QÛŽ4È÷§ëð©cåÉýKtîB‰dOe-½Äx÷Cºà¤Æ:›v‰¯•Ë3|e3ÔY¶þ <›%Ä'„i»þMhÞ7ŸE+*÷*ã­ö‹‹©¥ŠÛyˆXU™bC7cx£.ÁùŒèµVãúð}qûiWN.㸗€[Ù\ 'ïxxÖXâIÿKRrè„‚ Yûø¶›Wü6ëÇ]•ŒvбUÄ]~óBD”íXÿ~“*Åfo~sÕ'ñ´k˜ø^òïŠJêG£I ú¹ËÐQ®lgà¦:;yÈYÎ*týk7œ§?CdÜ3±ïŸÆ~8åšé-ªïW@oܾNáé….ƒEù‹þuÑw¸f»ÀY^ÇŒTý±û®‘÷A{ïJଓًµ…À®VfúÀUñÏM¬ª ÿbе[ªI€”,a=@|0YÌLÅêrƒhÎ(!ú’i49î âc\iØ8`ƒQÖxÿ Õ¥Ãú¿ï‡ KØâƒ2UE#ð  ûÄÅSîÐ ò—6Ztä!g¤? sÊX$—9*«`î£r¬`•иÙòÇôĬûœC8Ü…¼5oa¨ó±Œª&?Šý‹6úp1@íúcÈ@^VÉ¿ˆSqÈjPZP˧pôZe£³=þk‡©ÓÄ1´éðY^žô$|Zy°ö¨¸¡ËÐ,KË,×?´2„ ®2/MµÚq¸c/´,'.еåý#yÞÃç~QÖÑŽ°f»_‰OØ]gØ­Fü_|Ó°SËg/¬áÊ'žÑÁÕ@|*»bÕ#¶ßÆ?]¢rqëRÛ(ò.O!Ëû+‘WšjˆÐx¡I²’¦P‡M]AD.á´m®Èþ1/…)áñ öqv¶¥4ÇT¡á ¼‘—Ñ”y–ëÃW‘»ÌQÞz 7ìÏ…P w?’ÿ5S /EW2Izб1ìÇÓ`ÈAàý<ý2FååõÓ“æƒ&3uËò­Ö/ÙGDsÉáo¶þYüðN3øßÒ›Ë\‚ŒþMMчÏýSõ•&àô•‘¹A»Ýhh§eÆ}ÝdÐv’¶×ë>”Åä*–ƒp3õpã<È&6 ƒÐÕ›™!£ŸŒ,¢ÕÒéº.š°YPXù¹ÃoŸdtDÞ`H ”ª^’cg–|×îêsEÇ„}h*‚l.4áí V•¼e_¯ê ‡r‹íÆè0ÊÞ)âý§qSÝóJÈÙ{ìÅÖ¨wòCr°ÞmA×¥§þíÒö!^%ö2y•¿ë$„8‚çÛ'Q£‰÷àЬ‚XsáYGa-N©É9JÊ{K²H÷zì'„±5;'¢ÝZuÃÞÁyøŠ‡°ŸNÆœÚv«»Ú 7F^^—ÛeF¤àhaVö]vÕ'ÛgÚ¾[M>ìŠ~Ýÿ*8DÄP7ž³A`4bžåè^×U"LI¾}L¿Ó(€úÕq• ´è[a®“ëÔR] †ÄZ d™;¼µh!"NbL€ÿ Œ«6Q+ ñï÷ÇZz6Ì `z¸ûÕÚ½UÆ·“KÐ1?OžÏHbn¹%ûÖŠÈ,BÅ‚]&gZ#b¸Ìruü ®õÝ£úyõqÅ„ÌþÚã«Ü=³÷Ml—”¸D“¦m§údâÄ:˜ÿñy$_Ÿ:xàÆýöo À¯ ´w”p4Õ}‹#ÂÃí|$Ý·j¡ˆãCOË8óöÁ­ÀDÌ«ƘºÅ,ω}Tì¾YÛÓ§ê–V_äzåpS¢„ÕQD©¦Ú¶™bn@ßpëE SÑ1[˜^nÆŸË9áUÏ“žþd¤ØiŠñß×pÂÊÕ9…pi­m˜€1ùùÊñ‰y@Cl@H³k81…9ù‰JCïØbÅÁ¨ …ÅÈh«r»ÕúEø¨³Ù³!Xo-Œy>ûÈuúX.•~Zh (ɬñPÁrš…¬ªU|IŠ&DîQ÷°9µâD[ú™¢¥4ßà'4§í>iÿ@™Žå‰‘Øòl‡<—úË£”è„V±þÑ!` ¨g0³/9µý.%‡t³=U ³#Ž ä1ߺn@Nñÿ$©6þE æ­1Ǩ›I’.„Fl8=(…Y ãÜV®Ÿ<Ý7—Qñ3óŠ™BQ€y‚êþüoWy!mÆÅìÏÚ\ó*ÐÒ^ ‚KÚeS(Nù2 iã³ ¼W™Êò”ç7Ädå#² TÚAì kè¡Õ§DÄG¹9¢@Ùò(2¶$[cY Å?#‡(Aq‹@· JðõbÍîQ4|yJ6íýc„·,T•JQ½ƒ®`×^ÄB30fŽî³2ü‡ÙN_4áÓ¯ZˆäsÎÚô%9_Ë9œøÈà½Mäj´³m}̃öÆVNßveDÒ¢—1l6 ¹g¸ TH›±ðRô"XÜÀX(!üœ? (0¸S0 åª1TIûºÿUâñb½–|q…j½Ë¢×â 6>¢ßíËBõJ¢D&Ñå¬/y÷••i¼0Œqm¸)~]ŽA6Ùó]·íX$²#wiAUá?ÿ~Çù&a>¹fX‰KKC”¨,,QÿMµ2Ù¼„Wð€.¯zÒ„)1J¥µ²¼Ñaiƒ³æ2¡»p™¥È†­$éÅΔI+©ny;RZ "}ÈU¡ü½l§ j§Ù%|~@ÇcÏí9$4‹¨n§tõ¥Öî“ë¿åNÅ &VÆÚC4 .UœRyØâöR¢ÃæŒÿ_—`LgÂXÉ—ÚˆZ´Üç1—)j¨-†„“@)TB;„X–“"cK¦>M¾KM}Ò"ßýÍ\!˜ƒ!gÙ+ˆ GßV˜Ónèí|'–ÇljË¢ ?Þ. skaH Ú|³ãÔµÆÍÒÖñÄö•‡SãòݰòÀñ"…”Uø~8ÐÒl“~!XK»{ö¾¤¤¤~VYÚzÇšÄÝŠñ€KÄ‘‡Å€3˜"¶ÕÅØ“' W³ÅC7Vž¢Ý»ÿW¿Žqý%¤P3ÝaÕ6ö}ˆb6uMÚÚ|…À»ß²Êþ8\,AjIhÌŒ$bà"£L¾z´éy»Ù`ûPÕgÛ2ô+Œ¦xî}óûæu6õ”ÀLRP ûR–´f¼qJ’–iª€¾·ÌÒÄû×ò¼‡J<Ë ?î˜,•Ó¬“Âê5^”þ´ìÐ#ÚIñè8Ey9(ò3ú)Ð`ç18—ºÞ­™qKƒÎKI£CiëY?%sþ¾$›w—‚Ñ)"#vºà¹§A)¤#µñNUù?öqbe¯Õ#‰®/&Ýw0M,ö8dpÐÚ‰ÐÛÐQKºzM´l­l;R/ã+R¥ñˆ]T´›3½7÷˜6HؼÍ|ô­i‚–õ'ÔqžM\V—7,þ×f˜<à¿Âp"7ÉÒ÷òÃjü ù^äÏ«}¾)Ãe÷Ÿ0äâ×9o²Á¬ŠiÕ›¥+2â¶… kë(°Þ’ÔëUà@¦@>J±»—^)Ý]B†•ZÁ…Ï*muñ0²Ì7Ÿ^Z`űÿVxÝ̒܉'Ù»ô³Š:H>ˆ>rÓ\².cñë5̸DFí>Y¨'r ¥¯4çx{X#,ú|Ö͈ˆ¶íilz I4èðÍ=g¤Rwm÷ÐOÎ(pqŽÆ Í=R >Ð.cS€¬Þ¸ü¯ÓNC*ĺž¼ÉÙ©y÷ ’Ž9Ø,§ä×Ñ]B³¿Kv«uBp´Tz§3iªâõÜCƒP Î~}\ï¼x™Ü.n£âÚ¢xó«ªÝ»ºåñYÎl+œ $t’[S„ëOh7eV(ÚS†p¬©xL°MíIjA¾TsRe­çŠÎÏqŸÇLºÜÔYR©øökVIŸe;1¸FÞ£Èd8¦‚‹áíòÜŽ][í÷ºŒ >ãÜý—ë˜fróg±¨©:'`˜NXYÏóäU•7ƒW¥ç)%D:{w:ÏÚñƒ l*"‹y¶Í‡—?$æÝÁ-JòÙ`u‘Ï[Òb iU +d~:j“¨Ôŵw&ïšjºâXºç ÓU+ͦ‹¹©[ß&î2$·ÏÄâ»nQVg‡yE¶%ˆq †§Ží úy3º…\LháYÓ‹ÅMÚSCe¬œÔb¥8ƒÖ»7zh&õõÁV‘óµaeb¾e[QrŸïg%öLà•¬9 "‘j–Çävå¬;üñ1­õˢѼ>æ â8‰4Œ†Áà Ü”Îzgúè/„Ó=V$1;ØÑZam^Û _š)ÂÆ$ÜÊ©˜Œü™É·Šö˜mYt£_ZŠó°µáÒµ“ñ\=€´ó‹‚Ðþ·¸Üèeú¬‚~誶·Lê=Î¢Ž™Ô‰É]‰:Êt"¹H•ÿ@X½+˜ϧ6>™@ù~£àÔ'âÄ¿÷¹¨oñçþ²‹Y¾t‚Y™Œ¿0æw‰tBq÷6ÒØjnÇþ¨]¯×Wuq‰Ûý¸š1µµ~u‹ Ó5ßÃ]ºÑ÷›7-qú˜Äxÿ Ҍ碳éžìÈY€)WfeÔ"UB”%¬OÜÛÏß;¹)`Vª¼Kú~~}Æ‘U´†J¥ZI5Ï9öøæêD•âVAi3aAËË(à€X´ÃS*Œœ³–={;ÊMN²î>ßµ}ØŠ“½Gh»Ö ž~4aöaj3ó ÷—wS˜‰ëJTM°é j«œ“ÉÃveE§ÏêÅ…y+æiNk±é•EV[ë«tšeùðøžXŸN™³FtÖ ¸Ü‘)êØ ID+ϱ ú1ÒhÖŒ\ˆs7¼/Ôשˆ¥L™GŸOÖ¿%Óã••gÑFσŪÕSÕ½“çÝ‚:÷Ù¯›äÚKEºJMHBâåzF¾ pŠÑØ¥½4y¹Äèfû0‚ 2¸aL‚ç“jB2å-9G€¯ö©¢ªGK™qv“a~wdÕ²•Lï½Æì ž÷±­«”|.²5²m =‘q=÷bHdú8¨›µ" Ôo¨Œ *ÖÐøÛyn–Ú÷l¾:‚–Àe™ÜÓÔM¸Áp¾Å(9/M%ØVy<$)¿Äi·”FÃË;{(ÏÄ/…Ÿ²ßM7dŸœ°… óý^X…¿«®ñYT´´Ê*{ZóÔ.|W Ô{¸xùB× ™mhÇO¢o‚ªk§¬Ù¼Â4.Üà6"N­!€ÿ¯µWÈì\À£±•ºµoHJÚ³yÙߪ©TDø€šVÚž%’öCUW#4§ `²û AŠkо*ÎùÕeu,ÜèE-R>¢ç7ðò˜Êc‡¾~ßt ]ÙW¤q‚ó7lÖº`¨.¡ë¥ª•¹Œ€ëå ­¢£¦ OıӱM`à ª2/oHWËFY æ}C~Å"j…\¬HOùû1æ?ê÷Xôù¹¬)$S[NçaD+0zä*ÄYT áÙO”\DúˆÓ<ææ °à å’TÄÆ]Q¼°3@·¯)ìCöI¬=Íjv‡Éè¬w?§ÀÞ—GÚ÷_Eº سq6ݲN…ÜümL²öâ¯ÞTîXQ@½Ç™1üÛ´¢m'Ã-¸¿­£$üóÃ~~r1ª 9ŸÿlÜ Içá0v+Öè+¹³ š7‚Üzÿ§qø‘ÌÁŠä·2à9økHaÓ›TËЭ+V+ÒµÂè†bdé-´Z®æWÂŒ©rŸPЏL“‡þ9…–7!!Q¦m!…3>¶–>°¹«lFF‰Ò‘Bî žn–\-ðFí;}=ùõ¸€â‰ÀÛøö¡pñÆtõà%nª»A°¥òSã¬>cá΀ŽÈä~ìÃ}Ïo_$gia8ë-b…Ê-§õÆq›DÁ¥=^ú‰ýÒØ¹ý™ŒíÔet ó{¼b,yä^˵KßÞôÒm7÷''Ý(ñôˆP›9›dx‰‹\Ÿ?æ–+$˜ bl°Ä(KVƒ®;úÚ‡ŸÖäUð*ƒÁœüÜ?Ò®—’ÓêMPäÀiu“Í=`ݸE19Ò‘XC ×Í6ß?~ÕtPа‚¬=V™´If¼þŠ=~þ- —þáÅê¿2yfL--äÇGàÿ8ºVtÅ4L ¢9ou˜•ûàeëNô>k‹_bË6M7qÒz¿Í))$ÆìszÑt' WÇ4Üó0­F­ñê»éwª þï–› Ï(!ЧÉB+± Cãi€ÔçNPçg9nÝÛµ qN½¤ü —¤J|E½”‹FÝçSa³½Jÿ…Ü$5ìyu‹íÀ øÔ‡Dýcw¿_sÓKmòãɧÀáÕøÉÉNôXþH`Eÿ1÷t…7Èÿ]!<¿bm§ÈÿR;ôŰg$¹ýÙYÎbnÈÛ)Iu&þî…Œ V+ÄW£´aÖ®}¬w]½^ÃÍwqþ—!s}øÓ†Ý[À!$Ÿ@C Ú‰ |dvŒ{íƒe_¦&-œâ•ñR ´¹ ‚$ õ4›Âî®áÜ‹(…kŽˆÎÓI÷‹54÷5ž†£.Ëïû㿜lyÍ ÀÌÐo=M¾‘˜&jTŽu÷¹ŒÓ¨6·%¢•/uZÛm²jzEߘÏÍÿc XÏ—?ÁüNedôpËþTÒ)K¨É"mÚMç°ŽUÝ•`ƪ£¦™¨$·¿1çéãã•Ävd¨ÜÙÝÒPE”‚½H\šžÊO©Å–÷3ŠU^´”c¯`|Úq€mó}[š§5JöXDqÃrZ~Õß·JêT CLþ8ä5¬“æ‚}9h‰0²³õ®Ø Ö¤àÊEéé˜æAWC›è®ùŽüFФÍ)’ÍíKQ2£Ñ‹qËT¥Bœî‡èz›1Y7Ý|•ÏjŽÑò ž9Cõ'¾¶ÛÀæ^•‹_Rg`ž%áˆY™ePŸA†jC?žšü{;‰”[§ñŠ˜Ú¡¬õ–v)Œyˆü”›ú%àa¢’-Ы‘ƒ¥ª%ü–Bª3ÖÆ¡õ†Êy,¬ ò-–‹ŸmŽ}Mº¢Ö¤«$c½$ɳI-×öè—&Æ,ïWÔÎ ß²ÂÅQ*¸F‰Fë,Ÿ¯{œ¢Ö}Ns*šê:{Àr^µG©Iºjïæë¢hgÑK_ÉNo¢¿áÍS »rüHi¤Ë;3'yªŠöŸ´Âc‚„7BÁl…•3ªÚ{g·‘ÆË'%›LÙð÷^wXŸîO“‰¨ÜÚ>–Z ÷xEã`à•ÎZiˆ¬€ÒM´´zä3ÏM…'sý(Âͺܔ¬ï™ø¤À°(¼—ý‹ý‚…0íÆV|AÇ5&K†*ifQœCY®íUCuÌý]†žM^Q»…$ó×b$¼½È÷%;ºÄ=1¤]-óAD„ŸÑíа«ÑWVö€=¿pn¨¦»¬1þ%ÈÜÁ—µá-wŽûBrÓ,(_ÀÓ‚ÙyÙ@rsAÖÖ=GC˜U‚ˆ·.iÃ;ˆ«¢ö$­ìÃŽÈ6ÕâuРÙëOiãÊ£ $và㋞¯!5Zk©9ÛYIR†'Pwgç,냱³KÃæ6*N™ˆ…bJØmÅ¡XÏì%3„(¯ö‹tœdúsÌqN7ã¯PÉï9¹óžxép½ MõüAª?ø©_1φo+m“‘[òÎåy°Uøk@íõ#’è3äµ4ÖdC7–Måѽ›M×퉅5lÀî’Ä}|47ègÔZ_¶üHÌud‚|1Lp˜•2Å"ÇÈÉæ"]9{¬?)—¢¯}š&<†*wŸŽå½9 |vqˆš-+Åw¼°kž~?Ò&<Š·$èû0(Ä~™©#› °¾Ä¼]2ßedŠêrÎuâí€ÁÙþ5¤8ØÓ˺–ð^<|Ý;Z²g›VS›[°œNªõ›c*™íT ­·o`Ì“ð¿mÒÏÌŒÓåmPAüÿ$Ý*EËš cŒ—Ÿj –îz‹\{™mYlS­bœás†ì…òÔÂÒ¾lÚÀ§¡Ch€C7ƒ}ù Ÿ‰JÜæ²É¶Õ/¼½äô§Ö ˆ[–0Ÿg’2tŠœÅ–F½ü±»ÚCÎ;–G³#ÎgGwÐù–¥ï•M˜Zìx)Š›rS¸zßôäÍÑÓå¬o&ñˆ¨B‘C½¹W‚ù-¬§û°¥'› '¡„Äøt¯ôæcn¢†Ñ+B´Òl«NGE°#D¼Ä£Ë°û¬:Ä}öP§L&Mntå6¥pðøÔŒÊ«õ q×íâ„LôP拾²Åaà÷Ö‡fIꟸØ­' b¸vµå’x©JOŠúôˆšŽÝ8wÍȳ|—ô Û? òÀŒ¡ÉiÀ} m¿ \v«Õ=¬¶PuÕŽ¼¼¨Û °|w>ô¦ÀeJQsdÃ’ú‹¹9z‡½îÎAka)‡Ú|.Ebu`ÑÕ!3}eþbs çÎûi•Tñ§e¶«PmŒPr› ¹m8¢Ž B™3ô9Ê÷ð(ÓÌb Fì:¥B=︱|- >þ*K½L¡y³†W‡‚–T@ø‰WSB’-íáw¶,„^ 9Ñ.퉜àP¶.L^¥Øeæ5ìWÙóæA9 (Mü5¢ Z¼-‘@ð7DþNÏ[K‹1ŒJ©! M&%EFž_pð‹_j,|¦Ò;«î®.î¿À€f íŸs6º—|™üÍ’U®ª†ú I…H½Ÿë”X›ò®…T|™æ0ö¥¨ jµÍÃ…ëWÛwþ>Ø5¸¢±e¶ÙˆúƦ‹l¼¸ nW~["ÃlmGú^̆¯ò Hò9¡6ãÿ$˜+RÂe¥Lcü>„ÀDeiUR,ô1TîÔÃ…ÚVbbÑ+x}GiØ·±Ã ÜU¯Ãÿ@wœDGÿPe\ å‚#Ð=pIj ^€Ì·%f*êæp}ñ¶cé/.õéFã ™=N…NûÍ™/UH’ €ëzo¢™ÉSÜË×@ÇyÀÎsHù-·Ì}©Ä~þb œ™Â!®[›]LtžÃPØSp«1&øûîy+å;$ïñ0Î+•¢ÍÿYÊu òsú®Û§¼ ï8¶ê )Ðl÷“¸p·ƒCYQãR’6‡á’ãko×Wn0‚¯Ô›ÚÏ& BšþÜjñõAû[HX‹#s˜jëUOýÓAjÇ• •!¥®(Z2G/B†YíòV?/nØÙ.€ AóTâ3ø8.:Ý8䎕æt:dwþP n;D¸¦Ó×åÒûJ$à¨Y^"Ñ„”ÍÀîÍ0Y%c”œxLò“ó7ÄhÇÞÈòЂkš꡸ŸÀr%­µÓnŽ¥5=4ÎñoÖM¸‘ýÎq ~zàŠÖ›Wû"„PW…®Êòä¶D¶•Ú8jõG)q†<.ýqî>Ò‘’õ#é°ÂN`>q¨Löu„JÈퟚ¤ä´H˜ÓvO£1ݹ!ÍË…Ž„Ûþ ½ Ǹg² f]A ¡b~œ¤5möj@=£=÷Îbd¹t­¯é¡œ+åpÛAö¥+ÇÍ¿=8–†„ð x+/ªþùõ·“é°qþñë#³ŸxÌ3ÍüÇ•Owê¥îºÆ³µ¦bÕúÆùÑ ‚˜²ÍñŽ%®EÚ¦;ò»Bûƒ;gâ=Ye:W¶±V•Õx uÃ$R(¤ /hjµ2~Ç !ã½o 'K¦Lœ¿E½NrŸä8Âì¾r–0Qi’¤ŸÙ Ë;QS“Y†·v”hO å]†[ì4·­1^ßC^?mÍŽEOhp‚!u˜î}EÑf§ãïX jç†êøâŠ=¸oŒb¡MÓº&§Ž — ªɹȽ‚½–Í’Éç}¯‹ùqÉ)ÎYág_­˜ÝKJ,þý ·1Þ«¨UðïEªj#ìðÖx^sÊ1((õjŸî‡™~¥úP£XZâà9 †ŒíäÊráÏIVF¾ßfÙ3'A’û:a'?wx“„ÃðV~W ˆîzy8Üz&Í9§ë™óãêz_")¥êþ–QœÂ”Pÿ*àG…ŒpNˆj!„|w‚ì ~Ž5@ÒE5.'6€ž­Œ$ã¹B='‹ƒÂë2"â1“»$¬m;Ñ)zƒëçz"³¿S˜ñ“éëdòD¤ì_A¤üåÖžFÁiN§5¬;ÑÅÈÐÍ7×è÷ÿ1™9Mo.Ò²’ŒkOõØ´é¡kKÁHô‰œíŽ~dýú‹åšçƒ¯d$Öö¤êµót‡­ÇÌ&t¼ AŒ:ü DáÃgý$H8`jXôóÚóªÞþÀ¾D.lIÙ’4SÛëÿTk|æƒ:Ñ‹19V‹¤‚ÑpïÕ^o—éq³ŽÒÂxˆOžmMí¢±ŸE·"EMY ©ãç2~Ã7C*cÈy-"©¾XÀãåÏŽ$é˜(,\â‡6ß8ha¨2m–‹õ,« ó::6Eû `ïì.0BáÕ¼«oÔS>áî©ýÞ¶5‰ÒP#íÀ,¦dt?ŠÊ”aŒ…œ×¢ Wr èÉ]7䧬ØÚS£J"nÝ¢]¤†\/ýº=4ôqêG¿àB5YÓNþK7YÔ‡y9àüŽR&P%!†¥}Ó(Ô§9Ä^2ª“5»‘¾A»cÊ×{gëúî…ó^±Ú!¾ñÇ$®wFÛš^‘TI€ú~6,fABù™%൯ðò#?éCeÅ& CY nŸFReC•óEE³ÎèZ-)“mky¡vNÎÇ¿Š÷>ü¸_0¸dŠ[´a_šl‚µû²±{½‘8 R-‹…¿ž6Ú勞âœÕ´ï}À«ÿ9:¥Q£-Vò¦‡¡9 úWoð?Å0u…”ƒ´G(éSɵÆRþÌ­Á9ãƒe¤ä÷êôÝpê‹¡Þ}þ€™ÆºyU% ‹–t ÿPwëh;ÀÂý\¢JìäÈ\$f_|’¦Í¥Ç†Gi/âßF¾~38JÅË>¯”2ÙãCåç?†‚f–J¡Ê ‘µ·Ë«÷9Ê‹ïÆËnܤÖ÷°€°×t(ší²)d¤ð ¸#¬(¼(Ðy=×µPp2 ¤ÑvÜ8Tí*)R&â”tÓÏï×Þæf.`²?²¦ùêÜ!‚i¹7À¦y]ç©{âe-纱C^~}!ˆæxBdæÚÿO÷ÈS}]Ä¡P–¦XÞ"k-!9ŒÀ÷ ßg;øKÖSÆhðjk!÷@Ú&·žëñçó'må#CEl϶If–¹UIó€Ý,0‹¶ÖCX kQ²“°ùŸM1HÎhþ¯&–MjˆfYC‘ü˜f*ÆÖĈ®®Ž)i¤Ê+JQ¤òFØ3ËëJ/Ê uF6Yä~@.d gÃdT6«o%ü:’°'–éÔªÊ˘æ}IëWš+äx~}˜{D­*}d”ŠVa“‘\˜[X\­¤:×Ê_÷…+f€µÀ"|LA»ÿϽ_;>{´ó°zöº!nØÏÚ­W U¿#‘sô>¥ýÖzÈ…¡î$¯ kr!b«füƒÖÇBù -¬…÷ËåR‹P^…¯Éþ:V—§d·bpžxë¦^¾<¾DZTË>ýA;B×ÓäCýýâÞW«sŒ%» Lj³>Š6Ö°ïG§ç:WEجl—Â%ëþ5^~ÞMï´¢¿<ÑsŸj2÷tnÝô OªG]÷æ°å«ˆ™Ât€s(¡$¿A‘¼Y² fÇ5¨~ƒ¾ü©>ÐSï"€ŒšÝ-Œ®5;ÕÿH ·S§5 /²à×sgm'ûÏácþ*³¸hÌ¡-­'š¿ëMAE©¿n\G!ܼFªU/œIÇ`jœa‰ÜýÕ‰¯*É„g|¸Œ=Þ Å4)Ò)þµÄ㜌uäÅa­Ç—ÐIxè1ð0¾ÒÔ]KéeiáBÉ®ø6ï%ÍhùUƒ†Àò…ù×å£e[õ5y½1½Š›ŸMç¼b+äýZ¬áfüZ7$‘òRgŒ T[Ûÿ}\/ëÔ5l‡+Ü+á«=™s2ÓAÂÒ_$)tÃÿ7]ÇçØqÇÛØ‰.—±n” ÔLÒ9¦FjÈUð…&í"„[sˆHذ¸ BÑÅÙà†ÂFœ•8%ö±ðñš'æ…j\¬äDøoáóì}Ä{É‚»ˆ±&Á@ bnPz˜±oÝ\SAÖ{‘1¸µX*3I’wVÿwVšæqœwÛÓC Ïg|?ãhë–Êo$SYS<Ìì «i>aq•ÀõB§ÆÈ»ö !êTs¤¡uœF"™áçÉÙ.ðÑõˆSÖõ¬¾Ÿ”Ú²ÛÆÄ~a$·ô‰Çâ 2±tD ±ætôõ%xõê¾LÙƹ¥ƒTÌ’ ÊÇŒ¤s ®œÁetelw$'sZËxø˜8œë¦ëãVã·y棣¶ˆ- RNÌn¬ºï·3g¥ØŠá¬Š |2 ³¼W20iƒúÕ&Í÷å`+×ýól¼ÒêÐ[΀‰¼);KàL¦åßEæ n½3x®Åßôû „îä) +X§î»Úа³o®€tì®»z}t~`ñE(±dl;.!Ü9Íx½3Ùç¤Y»Ó²è°õgÝ™Dbû¼Á$Hv(LÒ‘!ç+Â×åeZŽ~ŸwÂPLêÖ…©ó…rå»?óŒ Ø=œ ýŸãݱ„Uhk¢MTKx†qTMšËÝJÄ­”'¼e9xŸdk6â'•ØÕð¿‚k…âÌnŒ‘aÖ0ñ®»¼™œ:$çˆÉ)2°»ª«Ó6Ôcé{ÓÙbõ¿ãvǪØC~é—M;úzN`ý”{O¬ïÌfùÔ#Só(ï,7ŒÉ'£Œ_§Ò5!x:­¼fÿ(Žà#6jÊ¢]‰ÁrY ŽÙGý¹ÁKaüåßžW˜liëÚÈ“zï?Ó®´äBó Óûg‡§¨Â¨®à¾ ä3TiAê™ÊUN赂g¢£°¹ôþÂú“ yM;Ynfj¥Pód(La2ò³Ï¤.lðw’ÀøzÝí| \Ò6 U,¡iE–¶úúȉ>¦«91Æ9\BöuÅD‘1}¦Bä¥þ/«xÇýu!š};À=…Ù!ánS^;Tú 3ÓÍ…ò-‰îž|Ÿ{ÿižGÍ>œÖ‚Æ QÄ–ö6¥ŽKW\/¼9|I´Á¢´[*Õ¿ Ü2þT*úé:ò$}×|N7eâïC¸Féùœéßü.ñ<<ÐìbÎ#ÊNÙ#¶Ì7ÞUî‘Îo& Ÿ[«¯š¥‡°e0×ÄlíÌÚÙïœO˹¸G!!ª-}Lë*·/FÉÝ>Š”þr!öž¡˜Ìˆ¡É.pd­›Gd¢JWšb‰]:rÜ#nv‡".d‰tü¯r¸!éÓÒØa¿ Þí>Ž×6lš~^µLP4Míý¿­ø…„.‰)¹R1í†M~+åu2­FCëà–âÑ?âŠLѪ´§Jþ©_¯¡Õí_MÖò£ y¶€sfÝxEDDsã8¡õ`¡±ÜË¿v±cÜS£ä±£‘zá5× „ Ä\ o…é,ÕˆGŽìvþº»J@€o zWý*Ë^¦§Y €ˆ ·l¢>Ó¾KëXhk<4ã|9‡òJƒº®e~Òû–äs¬çüçKk¤'Æ/Ç›õÓª„¢¹]kÄŠiÆ´×[n—!,èIGoÅÂØnž~âybÅ›Bˬ]2hüúÞèrmÖ!ü²æƒÓ-}¤âÅìW>haÈ1^L¤÷:-ˆ@ÊBub,TYpwMß Xš0ɕձц»ØÐ–[²†Í^÷f ±JwñÜÓ0(ÿ& Å®íM¾4£'Šz.t†ˆ«у¾%š!D41ÒJ”Ï6GSUÐÜç1Ó|ëØÉ äOŒ[‡+¿=lÇîCbÚ±BwTQñ,ºBeºÓŽX&;)“P4=#ç¤ ;ÁÖ<æ•*|ÂÈÑUkÖ/f%'`¡nÿ/— æ¹t›Ô«ÇŸÒwÕÉŒr2 3Jí@X®Ê“)•° $îö)L3@IôOK(Qt¿ÿBSŒO]µ_ðoP¾„ñÁÚSÃHÉÙ{Þ–Úë  õ%ÎpÚhBŒÔ:·¥}Ø~UØÜ}D¼Ù60­ÅSoœ=%< õ g/È5U¡²)g¬¸œ^³0“© ™ ‡ªjž"ïªå¶µpyr0½îîIJRd¯Ãx s¤Þ W•™FÓòŸn•4¢1« ªj‰ÈsPNS/ÄŸÒÜáÛrrñylÕ‘s}îE—X‚a¢•ÆË‰À9%Íé¢SªÃ&ÎËù#¤ÙCãKRsH¯ÂèÄh¾ù¿ªþÝŒÏÈ9hjöâédOއh­)Š[ÂW WÅáãvFêesf1|¿èÓN%&Z8Ô¯oº/¸÷µ>lMôwü*¤ê<§òào;Y¦ÆiUùMf böôbB C½„ bÝ ;"ññ}[‚‹ †‰ÓÕ²<¬:Ë‘¦¡x.Ú‡¶0ßS¼ô“Œ:š±4x }K ¾Ó¨u0HB´VÑ^îkh§€ÊwÌé® ‡¤nE£°.o´„®i`\<`1âÐ}Ž>…8Óqó¯¤`ϺM®…a§ö5‡ü`®£[‹°ÀŘ—<Î%ç°é©OÖ„wjRq ‘Øe>Nóȹä‘x.)R¹`+ŸÈ žj@:DeQ$øM›±'ì2 Ç úÔAÏj /Wí¸DÂ¹ÉØ?!k™e½xôÚVn¦çHÚ­˜]ÍQ ”5…I»ŽÄ¯ÿs—Ѡܪoó×.ñµeõôZ3åRY+ bMÔJÐ*aa¹âšä"p?9 ¨r‹Lº*ò ²·uœ|5@ŽÓ. ùÒÎÃýÐl¬b•˜ÐEÒM½ôÌ2÷›ù‡[î48öTè0Á}ÃgÏÑ•3½NH=X™®ú-{?FAp»üm¸¤6$×îB”s¬zC¼nE7o;cQä«qC­h—àæ2°…üÖ©?$]·y¬nª¤ïRM¿ý«Fpªðœ&ò¾ IÆüp¤ÃA%½-OHÔåê·ó¨‡àûŽtüZxð]®W¸_Ìïpç±¼æÌl?¹ê¿÷t T˜>³õ]?0.¦²ê²S$ƒÀKÈÀŽ„n»œ¯NËÃî«yTgc#é¿3‰É3^nSó,zÓ;™Œƒ *ô’qÍ“É)¤uåEXq ߌ,üaÍ@à@ÀLÓ¦ä =•®È!g•b[ÿ „~4%1…ÈuázÈëÄ€òWºëBTtÅñÀƒi¡4”8ù}g‘á’r‡!ÓEWŸŽ2¸ÅÁšû2 ö"wö¸Rá:3)ž‡ÿ\4gÔ$P3d‘Aͼ¬1÷Ç*¸ ÐÌCõoøhb=+ mÎyÄ‹vú¨ 8è{—H{ߘ0SûàΗø×nyÖ•¶wª¢€Àè¶ÎÇI¬ŠÖoŸŸtì®ËŽŒTž‹^¡v¢‡ ä©êÈš-aÏ„Š¡ì^ÉžEœ×LFð/ïN g‘úv%Ý‚û•C›¡eH rVnß?ÍÞR¦U‘ÿX Û%HvŠô™À§Aß(j6Ì‘!´‡ÓChùâ甲{ût]MtìâäX+Mk'‡tm9Ó;«ã^æ4T:ðÑÒ˜·ÌÀªé0ÝH*_Þ ê ÿhñ<´m’éé6ª|V Eôóßwbû/沄;¥ïÊr®ÞÍ4}öçŸ6Â.Ÿ—uíÿûÜFT”+@C6{ËpŸ ' ݧ:Qü³â£ñ…ÞFðOÀ)…ÎŒ—@eµIL¨Ài›uí[qaõ’Ò.Ñ]Äšm&Ë·´#PÞÔÅ2“™<*ú&á´yÆpô2!•á+‘ñKxÒ*þ†RtÄreÐ÷â¸znšÛ‹8h;§+¢2Äž@=Kâd…ÑTUÈX×óµxd‚ @@tÖï~ôàGdYRÇóøÙ»BlðñH5о U8wä(_xWõúgý=ä×y¶]WE­[OóbCoÅþ /ˆ9 Gö6uн$*ÑÚdrho„“¶®#_,fW¤;*A,ˤ£úÁÔ7Ë<¥«ü.Ûú5ýº€?šGößn’ßµþN]?ïq|涇¶Zˆ×qAíl2DÉGV@7Uǃ|Ä–# ”K†Õ5õ¿àð|Ú’ âæÚ@ac3ó9¶Z?…-b(qf‚}JúinÀÐÓ—èÅ¢üååÄŸ¢Êÿbä ãˈWG…*Í‘ ú¾‰X:Çð–‘±¨ )1C%Å‘/( 7üHîÿnüTB¸xsÔ´‹?©6: áÇ®WzOlÙG›‰5ʨ¬ù[ÁŸ Øo ê=3í‰Ü …Ï oPaV 5ÔVç¸Üêp%è_rC[}n Ä‹d3vméÁˆ<*â=.ŽÎ¥´ÝÁ\²ÍÕ9_§m‰€ïi±¥ mÈIjÿ Ý—ª/IÚP¼Ã˜€žU§_N+·Ü -k.á¹H0rafÑu+-Í›Æoä»À¬–Ôþ `üæË2ðÑÑ\DÝï¿ùgoš^¦4ˆ¥j9qÌýS{ª—¥Œb8Ho\™¯É,}âƒz8/4hk6ò\’(±iî¡-8£ÉÞH%,¼(Ø…%Wã&º¨%n¬çàî=1“öÙì[1醣ùÎÇÜÖOf#hr‹Q2\eýcaDh§qÉ’¢è¤ˆâÝžhì¤kšqF3µøM3Ö5}?µ7_¨RuVñ½»Ð6Hòrú9ÞŠ”…Nƒ˜ŽÆ)'ö6³˜ ^–`$–úqÞžèP7L§(«àD‹ý‰ñ|g`ób z×6£ž/k^_“P+õûYØp.ìÚ‰ô?e¾·È“èÉj0´0lôÑ‘HTáØÔ  ëþˆbjötLöלs@Ô‹ÃE&é–y‰Hì?Eä6³<-Ý"Ù&Ð0?îN ¼K±øl,”Q"1¹—I;ÆâäÿŸGÞDFd­’\†—$lOÀ“B¡yTulæAT{ÈÕñPä×&ѵωïj 3qQŒÏ¯Ì®Wz(2~»+=ßá¼WXÌw“¶;9'•Ùôå'M ꀦ™F 0u_vü¶ÍIëØý™Ÿh%£„â^ӽ耥·:_Ò|;·Xÿ3Ž_V€‘—, ‘Ã<ª¢n èÔ)hµ>ûw6 ÐØã‡: é)ÙiÚ•˜ Po½€¥©鵊§¨ŠRtѱڇÿ>;b㉧=Ã˨Ĥ_¤ïîžzI­L_:¸¸Û`Š˜£wK_ÁáÖðä8Ù›fmÅãÔOÈs–?·ërítv×úH±àî Í~pŽæ”Ò8ýü¢¾—½àvöä ÷Б Ó×ø –¯jz›i Ë¼g–ÒõÄ"8°õÔät<\ðäg2n[pÔ´.=ÉÅEDÞb©ÐÅšZ‰„ ¿öÛÕÿ¯„ù:uNgÜ 3öMÆ…ü‡í­ÝYªù¾yý¹Ká—´ÞD–ÉÎîÇæâå÷)N¯iྻ¢%ØF ìÐELÄm ˆîAòrÀ¾Ùª$ •B×g||˜äÑ>ÝÒ`†Þʾ¤Lq$×ôãÄüº½6`@«”Ä´ù¼ãì‰ÂEó“6xêðáòù4b[ID,‘¼Ù%pî}~6_ºèxïqJëG#Í.%Cw`ü2 L+|Ëóü?eÍi´±x…‚*­¼“¸TAï¼ÆBÑmÍÎFä-CôkÅ“e³:´g¦¹dÃÕ ÅòÏôI<.¸™ñ°c éFŸÓøôcÄ)s…µ Ð{d§ƒ­’¿ñ fOIzòq‰Ï ;¦Û6Ô©.qkÞ1• ¤]ŠAÅ¿Uœ*ÔPŪ.—¬›ƒ=ª•ÛãÕt;wÍÇ®Å/Ý£¤•Î|M ‚¿J!Ñèþú=R‡Îž®ÖÍ¡k0WàÊÒaè§7pˆ)yF*жwöÌCÿL¹h•@W=G̳ú¬¡LÔJ 80˜]óšx¡ úÙ×¶TV ÿZÉì>"oÝTãˆü$Ë•æéÒ%o ßšŽzê^/(÷\{­J3î’h2EμÇ»Ò*âx°ÃœM,"¡ `AÂpZý¶ÎS¡õ÷6³·qmé 5Ö„{ÅýyØXáCuKFUPTsEÿ$fl+?é¤ &uŠÍ|-š,›ßþA†Rã{ë§Ê#ÎK‘Jí~[ ¬‚ùݸâ²)¥½³µ3o®°VWtGèÓõßþKìQ³¹±Â9øsgþ¡¨cëA>”)”@(Ü熳7Wêè%.Ì©%TJÀÊ[’„» LÄ,mVjˆþ±aº´­ó Ü::mY†¢aL®Z¹éþʸFEÅ@E!E¦Ø"?ä·*˜Å……ÞB\,°'žåÑC€ÔAåÏHW&÷é«ßÖÓû½{(#¸5vß«NÕJx‰ñ¸¤?˜cC©À…‹Ä‚&V?mä“$…س>„®ø§ j,FÛCnš±°ày6s"ÒÀ¢Ðž­ëÿ{ËðLî¨#ƒï¿`ÔxŽo°åDú¿Ç¼ôQ¹²,ŒxØ, öSËÅ•VSD(Œ{äeg;q¢è…výËÏrÿA0ÓÜ™6’èVü¶…õÚ§]~Ô”ÙêDÚóyܼžúg{X:ºM–;‡YÞm[©ö¬ý`he-¬SñbÓÃD³z>‡ù%YQc—3Õ*å•ÍHô’Ÿ½Us¬ªµ,šã\MŽR°² d‹’ȳ”cصœ<¸ÛO®诌]*÷ÖL÷Ã5?Ï‹"¶¦MwòmPDê:ÝLÐ}ˆCd~y}ÚŽm¯É% Æ•†]V{áÕ\Ó»ÎÚ₸Ñá„ú=v.€¼úÅHí'¼ƒg$š°­®‘„—4¦ŽßwˆvñV„ñq¬ŠîÜ›dìÚte™ÐÄS±†[&¬;s¼…,lûÁ;‹[4!våÑØ^cŒ•¾`E-P=>®†`âì$†¡÷žÓžþ§ä~ÓFgêíÜë¤^hDŽi|!D³,9•L…íJ·É9iÃ!¾ÙGãûÚ±® P1(že“gá¾\ÖJ†b%]hãÙ5ÏJ’ïÔ’h“‡fæT˜PNþU´6Û_Æ;Ž÷bí`Ì’¤)Ê;ÚwLh$³þ#eñµ*e‹˜f©7yðç‰vÂÍÁ“Ó˜˜‘ËÅRš rMý‘­T…¹€ƒ.¦ëYÐÈGR$ ßEØä¼ ûÿE$mPåÞnlVk¯ÜRJÄ‘ø@ U°‹ïDc=Nz¸ŸÜÊ[«×¼¢¨)ƒP¤@4øÐ}ìÆ7¶5øh^‚ä6Gm¤£ª–{½§Á%’>ø,®1€õ÷þBQîüÄÝ®åD+Ï¥Åg~;‘®¾ãêpU¯›zÊœçœÀY \“Ÿ‚ðmU¦0`Ï[FÇƸmš(‰Ê^&ƒÆëkèï®ààâ2Úx? ©R9=ýØÆĸ‚sÖm$^Üñ΢6í¤%e)ü‡÷$¬Ïé\àùUI’y±KZÌ…rUåL蘕&P^™iŸclrêB.K¢o¶LÇžVÿb¥­Ô‡þñ»`öÄñcõom›8‰EMCŽ©ÁF¥øZ7`Äm®üÈV‰µ•ë¢hÉc×óaMU ˜Õιûuæ;uYh%ÿo¤Iíãˆ7ûRÈdèvß&'Ì«a))¹MÑ[Pv]¨,_ÀÈ¥ñ‘$§»1ÏÕÌiµmå|TFÂï(¤Â®J#V?Jcí!òú$Yk-Ж=ãfûsÚyϰ¯v£f]µ¼µ££ätµéâÂ(‡8ývz›ÓiÒTCù|240ÌݸV¤].ë]V¹Ìv®+šøßÏ6Óźo".ÌÂD»7PÕ ¥æ6Ó¾éÀ óUÊh(ßø¬ífž7HVôSŸD}_C>ƒ^ϳ„®Q_„¦¢•q‚s\,L`ЦYF«ô\ÐþLãÑ â©¯Ãì= «‰@O—Ón¤¾’MÝŸ¾dŽÈc’|c©ãt‚»ä‚?uÓ‡mY8}Ew1Wû‰l S;L,Ÿß  ¿æEë†ä0©tM¥ÚS%c¯+} ®‹Çœ M…|TrZ$þÓ·‡úP ’],R0+¨–ã¿aäcuA·§¥ÑÜeÚÛÑ”á‚6.c+÷¦Š~R:ç™ïEÍxuo]b £„i¨YÛVÙ’"´-© ek‰Ôá&Úd“8r÷µ›(F …MÁÀÄõLêóŽB~±нìŸä’ÑwŽa_ö»Ÿ t›cÀÐYšcK ÓÂ`;Ú.îz´|מTäa¶Kî+LÏÙ÷º`1÷ÖFÑ|h¬€µAš¿<‚âgPˆ2‘}:âpE&{Mîú‘øÀùäuÂÑç02®5Û…• \$¨Þäþm[ N †v&H.i<ïe*´´Í÷ÓBpО»ÞLR]þˆ©ª”Ú(S@ûbäµ­@JËÇ'6ÝàUø»¿r/Ùo6ýÁÀ)ŒÑÝçŠí tJ„¸õeç®ÔÕÞ©p Íö³µðv›¿ì†Õº–Åâô‹9O®öœ€ X…Ö`©š-UÉZsóëØ¡•S”Csâ⨲c°Ïë`Vðsš´¨GJÊuZ$1\ñqY«åÁõ¬Ì¨È€L)¿ƒ\§ ðTw“Ó6c°‹—äTŽÁ<2âBqYÒ÷ø@oRg…豀厣=KéÔ¼ÃóøñBW÷ÅÊöB®?~­\¶Am«ê\Ÿ1{Ã"Ë2†Íš¯ÊHئp3,Ý“`Ò ´þÜ(W±IêœØ^ÒêC<<âQ F{Ô0ÌÊ€ˆ’Þvb( Û—·ŽÄ[È(` Œ]j&õ›´üh±}Z"²•¿ï¦X/hâáï«çZ~÷˜ÓVÿ¶wª@Þ)bïÎ>,É‚íHÖ[ã%”Xn6u¢­£”Á‘Ï›QfÁ îëW€Y|5)G˜¾0š¹QBXºÊì¾Î$K ͤ´k„Ýx­Øk&g#¤r1éÕÅÎKa«z¬Àd]Ö^¹T½±Ñ¦¿é:¹ ß(ªa…š¿ÇA]UèåâÄÎ?!òlžñeÐ|î´,!ÔÉXÐù¤˜g‰uš|Aûö¨œ²ÀU?fž“È*}(ÂuLþ6bžªs‚äå䊙Àm3ë%F'²±P0Œ®tÛ O.ïÁŸ³ö—J`·ùDKFQ>9>NŸI=oˆŽÚÏÆA÷œ$ðÕCÜL,ìÓ•kÀ'sÁ’bzº,3Y4uP>BˆŠN}ø¾ø2÷ÉTµ¨ãOBjÔGUÿJT8”Šžjg°‡TŒê‹+°þhé=6q«FMÐAPÙäó нfMT:†þÑûø¹J-b¯³2\U1ÊëÛ‰»çe"wpÿMN °µ» ªqI´iÅ&®ÑÕÀ]SäÛÀP.³GMRGù¤Ñ«„ÚÜð¡Å1Í—·ó±·‰-ma•êÜ™Dé讕ˆâUì9å<+F‹´S:ƒúU‡~%(2â{ûuÏ»g„﨔̆ïï!º~×zGžÚü|”aW0JmZ© €¬¦ÏNtʪT¿köŒ,›]µÐl¦(°ºA ©è†‹øH²– ×÷eã餼ïf&ÕÈô›#F 6—8Xñ2\¯Üa¦°#O ô/ 篩Mô}5Ý…‹1¶‡0çv§Ëæ~«IÂû¯ä äewh6JÛõÀÞ¸`¹ø$Y,P¦€˜wâ r]^{D;cãßÝî¬Ο¦YwGŸpzJÓÑO×·4ÂÜ'Ï÷ÞTƒXX—71WUJˆh/¦&Å<•µÅŽ.«VdrÖ.Sˆ3ý'^ß7["'(üCåœ VA»CC},jùÏ9“°‰3ÆÏkë˜(­Œ#œ¯ÿ;Ï»´æèPöy‰e¯þðV˜rÁã™ÈÁì\7] Ó"? Ø&[8­`{ë€. ë™ ±CQÏKjN,ŽBLkQ×™Ñà’¼%xÏNg*"Õ:òï“8{öƒ’KXHnˆxÇV‚jŸ¢Jø€ªd~¸yhºÿJ¦2¨;¦¹¤x¸õVN tðØâ–u¶ .™/#¾œ‘äªÅÚ=/üœ£¯ûOÒßÈTÉÎ@—ò?k©få‰>}ã áòæ¬ñv"sy·Á´eûŦHû¿¤ˆ·ècúýóMºát–î›R‡ØþK &w_ ð±·tPeÞT¬0¯õëÜ%¦]߬‡Bóï[^0”S™øÄCCLÜЪ²\¾«R“–ûh•fŽ×v’,·î7d°^Žÿ€¬ìšâA->jñ…qÀßILI ƒ$AW7!<ŒùÀÕû"4¦{-‡¼xqûuÊÎyØE¤¬ 88AluÒRZÒ™à‡ }8]r”S–híºÜ 114°)ÒPë7ÒhŸ-(ü4cáòwó¹{ô´TÎÊ‹áâ£o`ûV¯õH+Oª à‹QÃÈö†ŠÊZOÎg­híq¶Œ'“çÛè~öY[\»qûüÕ&®Šnö®¯ÂÃo"É `úmöã¯Hm‡Šzk7þÙÜÚÎ]eˆésNßë³ö•JšïRx¡kÒZ.ý^2>˜<”ØæõS1‰*š\‡‹ ØH[ F6¨z] –:cˆr‹þ)7×$ØëI'Ìå —ˆÑïQÇmFgK3Ç(®½Œì˜`;g;Üu- ÷ñ¤Î}É3aÄTä1ÞƒKžP³>Õ¾åc¤û°2nf"/ñ¬Ð~_ãÁØÿ-p ç¡”ôî•*S‚€\–ÄœøRš± F¡v=pý˜¯àQgoúaŽ:XFaÊÁÙ°Œª±Á:qH_6MŒ-»(â ª è³,¬JrYÀÓ-¢•©Üvæ[—#¥×kæ’D ßH Âa”õ§¸åႨÁŒaÞæ/ö£³ú˸·1ñ10ΨA‰Rru1Q_(¹ÅïmbŒ<Æ71_A~èèIU‹+Ð…ð÷ç>ʼnޠ}™£àêÉë\ –Ú<¦3‹øË|y@@¢ª ö/–}oe¼?Çb'!Cà3Wz3*YÚ‡Öf#?Ù×Àc bøë Wx8ÝÓ“RyØ$šmYe$QJnV˯sÖÌ)þæ0#æÁwÕv2ó~@yÍRêž[60.F-‘›öùœ«¾.1: áHþº ,ˆ£R“ƒöœoñ×)ÇÍçà³PCÃÍd¡g^·¬ßºœóÒ]pÁÁÃ^_‡Ý´¯ÕøjÉŒ6 Ì6%šs§¥¥“äêìX}ÚF™T&Cg'F/v\½:1²æ†²|%Êë4x¦…ìaIGkQ €[7Ðb€Ã6êuõ ®2è~ž¶Ò’©·äœö‡¾¯ïÁ3B¦9Ïü…óÿuíäÃd F Õëטˆ´¤iÒws!Úûåþè„`œ'Y™ 3á>\}¼ -’ž q&'ë¤íGÉûšôeƒP¿ÑÅtþ4~k O9õ£-Y…[s§W”{öâçÞt¬m†ÕÎâ5A¨’O ù°~ ~Öø#âÚ##ÍæjhTìA­]îºÏM±r.ϺÀ§‚¹?ŠÛ‚Tl÷ŠÑv¦Ò°}ÜÓFï=õ¥%![w?;¤jãR-!²Í4Æ¿!vZ6ë´" n ®{^S%ÍÞò)åø×2¯rîJ?8O«æœìû‹==oJ¤ ¾…«þÖNìŸ3<är‡ñ0p’‰²Õå¦#VÛoļu»ÒæƒDÛjº•·H™ûN:nºê™ˆ0ðŠú˜‡Ú¤„dà#u§ÍÒ­ÅÉ£±¹|·›š5ÇZO³±pv öñĂ뻦µžbV³l°­Ëï8‡jBŽZ§·ù¿0T(&ò¢'H™m‚w°ñ „—¨[ Ï,w·ôÓíiù s"lª|si%Ê+Ÿ;—‚G_ÿ| ÂæîhA­C+1^\Å–È–lž%´ssAôk#ËQïÚÏÕç~2R¾²®8Ï ÛÀ¦É|’-Â0/oÿ7/ yÄsnÚ)ú¤Ïl&[“-/™Ù~gvˆ‚…Ñhp£o¦ãÄãl}cúo÷MtYô¿ ­ûèsxøÂ´×îÈ'ŠÐÆQÂÿIF“)åÉI5ýÄÉͤ7È&Kµ.ZR‰‰Žyêå;[M2%ü®°.”ZŒ | .+[„™&»ìf]`þ±­gsnw¸Aº§]²ˆI5£}£ëµg𸆮gDœà5¹j¾Xƒ÷èÜÆDãÖ/¼9$aLšF¹-ÕàDë¾±S݉X®L³Šx'qªEFÙ÷¼hqž>âÂ’î˜+uÓßi°qžÐ‚¯Ãý(2K³j]UÀv >«yia9~°ïqCNø†bP/1Ž:^·ÑÓž’ïáµùÍ´kÁÓ_(=}ØîŸœI»3¬lq²ÜË[ŸUè¥ æÉا{‚µ¢EíÔbœßÑzªâ‡D5X3x)íªGÆúšâTôeiÈ1h&‰OÌÕ™j2©ÁÀ"Í2tôÝú¥ÍüÍcKC˜³+?rùí49Jóm‹Í÷3É@ÃáõBÊÀ«éã*çÙÈ—sMt÷”- Vþ@e¨, üG7mºåæÛªÃPj>–v-2fE'>§êÇ£ º**NË uOÉVì.ù‹î,ïcÄ^îYE6¡ùþHdÀôº¨£G&Z•L­‰ß!ŸS€’“P1‹C®|‹ìmퟅå^XpýÊÒ׊˜è¬½MâO– ª q“dh³‚Fç͹‡ý^H#„ËØ¡]îûé¤Z}¯]˽»f”†±ò±#í&úÇsµRº—êc§ô­ø½3žüÓ£;ß9 Ú?_…«È©¸c¬^*w Ó Ž<·û¦È>Ò9”ÕTm÷YN_ÐÒì”RHü˜Ãÿ:‚ˆ²›+/?œ®’~qþ ʤÐR*°\rÅ ú‚òn-½@)ib ñìŸõC:W–˜`ÞЛF%›W›vQóm0s$ëG}ˆãuFøV-YGÆZK"æå‰1½/­é‹8{¬ ÿx[-¤ÿc ª×835»}àëã NϾ`9/*ÌÃ#1P]ç~ÄÃ~½”9Épmª§8Ï•xá=gÂ(ú B:{'Q¿tì+ä¸gñѧi£ÈÏÑ‘ˆpUy="—©Ÿ0»p—홀zǵ•tQ§‘†Ëj ŸtvãsO«ß-9}Ë;ßWÆÍQ‘ ÆtQìaP´Kåø®§à˜ï-]qj¹z†rhNúÛµ4ØYù„Ý?.ñææ…íW9º¢“ñ‰J;íU ZmÆÎ¶¶óXp(4±±ù·¥#³?1&ÛJjêý%" Zu¬%¼—qá™ÂùÓÄŠþ>P£h¸!…àIYSڞܲ\ºó5ÅÛ¸ï”M@ˆÆ ÆDŸçJnLоzÛðÚó·š6k¤TÄbD#dxÍ:<ÚPç²:N“Ø)ÎÖ»³ð†âÌ~Þν³g¢ÍdÞbÐAÞ¶5OG¿*uYÞHoõØóãL€à¦ù£™ÖÂ7ÈpÓ¢#ÊVöÄåëºxÓ°o Д„É0†’Ê%h´Œ¸ÅV”»Jù,ËÐ"ìL+¸ÒˆÎ*î)%â=+ºuŠIYfš¼;çÜÕÃŽPšR"¼²|\Z‘šdY¡ và¿ÃamŒåg™ÑD&Yî—ÄzóB†ú2(Ä1ÔNºÂ,fMÒm»‡ø·|[ è@ãAéÓ.^ƒöÂþ¤ZÚr_жévŒ*`ˆ|Ÿ¤»—+1š‰cC!ÞÁ­X«1üòviq+MÑ…‡m˜œÀ…‰3=ÁsgµdsÒŸûqˆÑñ¡²Ïû>uû¶='˜:r‰h¡½Æmà±® R¡i¬9‚:©¯7ð~1æä þð‚G#Pvó2ˆÏá«(ž2ìÔD6ápéo‚vyQï'}ü) ^˜f )Ä/ÉVݸBƒŽ áw9T(!~k¤‹UÚÜMNÒù&KÓP‡“NóØÎï÷e¿(-ùÁr‚ç 1¤øg…³<9 Ð XäXgz¬žnõi€ q,º…‡BÛ¶¢×F¤T· å`97.“ö¦â¬ ßyre‘¿¦OtÞ~WåLð½-ÓÞïÃKv9±¸îkm]ÍÙ@JŠÿSæ ümE¼ŸöLŸ¼ÀÕXŲ¾xÔcòJ:¿:&`õÙ¸–{èW,Z*&oÃÌ>Õí=IÇ9Ë1¶ùQõx’0¥¸»4u®ø+ÅþǦ4™Y)ÂÞùÚ†¸ miö_ñ—4¬²ÌtVÐÒÖÒsûa,ÚgÞŽNí±¹¥ÀÝðB¶ùìĹÎKò1X'†Q|àf­)Ùÿ<3ÅiõépçšÐ¢+:&¹tÈE¾w@@òŽkªm܈±b åÃL½v[êÆÌ"Å×ìlÕûª_c;58ª]?¯]ºÚ'ˆ,ãéúI}÷¯Xqùþµ¡Â¦Ë<éä´FNË…!>ͪæ9«·ª‘Hq-ƒ ø¢–‡¿àäãŠeX’ÙC-£œ}RÄ…\èéÚû•Ò×ëp(6|Ë ÷«'Žfíé•Dé«!ÄUó¦›2 Ý&‰™þßë}!ÿ0еœ=–›ÉuÛ(?þfŸ‘ôµKRúÎþqc?® ºr+ë5>{doÇbŸëàÑÌåöß¶a¹åÓÆsdÍ5Å;ö¨Ø D èÐàsâ#jNV²í<w5þýóÅ^ÎM•¡Çä ÆewñÅÁª€È¡¢òÔD\¹"ê„Ñ¿ç&Ö„kæk¼†™‡db²gšßúIþ =ÊñNBÉ*{”²‚Y”­s«þo,„)g¹ÖTBu€=×*hFý†ÂÓ9góÚk $À}OÚÊ.D²ÙA™ûœ¦[™'WÙUop?ßU•Ür4óœÒ€ÃØè×¾4ª½Bd(&ºSªméXê|Á^B¨ò·ØÕŒROÁŸ=½pàk¾Ü¢…»mªt­ÛGÊ?ÝðcëÖºCiHÄØY+ÊVÿUF€·¾¸ëÛi¬ÕV~Íkt-¦æ™1€»šú}0E #l˜A‚'‹úï£è'>%ògÁ3‡z¾Å£<ÌÑâwïÑTˆ$*ŸŽ.L%Q—SDe¸þãÅQB9ùìC­“ÆZBd¸)*â·Ù~œâ›@ –ʤ ’Ö9F»æj)¾p »ŒnÞä ¤Èyâ àÎD¥žB00ÊXü}øØ¼¾Ž=ö‹04›R5‘»ÇݤØu)U/uÖ6‘crZ Ívô þM.…JéZþÔîGJ‹Ü¥Ó½a¯ø™!°ä¦¦O¾äœ%îÈ©ˆŠåॵ1nún ÙÜ&û yÖ,âDN¥*— ]ùò‰®¥!û¼ÿK0°ùdŒ#~«CÌ«¥»E«K½€¹û<É iúÕîPÑÁ<›)ŸUr¾èDî!|¦Æ)xM°ˆKZœ!“䈄vaõu#ÖA.c™±@Æ -÷¾%Yíûx•vÃ)„0kk€Éëê·ëÛr>D]å… Ï±Ô*”¯ÇÝÉRûfgj½À}u僒1š‘õ¤V‰Éÿ=¢8QѲPÀ ™\Ø#Š™-Ípç±A¯†-¾Qx|Ní;ìàS¸†I0’vwb ÆÃuŒpc‚ YøTœ-íÍ[ñ-+%)²“uZTý6kÆÆ©þ>öXIIéÜL“‰d“¿°rja,£I:MÌëC¹¨í()>ÓA¥_iÏÚVú:R ÁHTÌ— oEíÕàýéžðÌGƒñйø‚çÁ¯CKeÂO]Sì®Æ£[Åg8 \µ’ÄekÓ*ÄüØÝ½×Ê©R>š ÆÝïÁJ¹¾T¨«hÕ.C]{Uajc0ÿ@èŸÜ{Ý N€ë‡»°Üdö`ÚƒnAþ;Þ¹ùýÛþýxUâ°¨õhSÅðGaQ¦Î}+üH_¡êdn]Š Æ|dïºÀ¹À¬ÌŽÇ6ìö ÃãÃÕB¿œÈEBoÇßèÅòXngöƒ#[§„øÅuyÿ^Yµ:ØM\4Z1w¹Œ1»û“¤ŸsÂj}ö\y´·Ý±ÌQëÛçc_´rü…ÒuÊEÑñˆêŒVrá=xŒtw„sôޤ0ñ¿Èkq$­¯ŠBÛ¯À‘·²Øëã‚¢0ξõ*.7xdP&\ ì£q1îßµD#$'4AïŽ ]lB‰¦tÿqÖ'f‚›xsŽÖ*dvR[B4†˜Mš—YéÜ÷Ì=MÁ¬ 9&7YÅ9$Æ ¦ƒàl㦫€y¦2†áª*þÀ–íR¬U¬uZ^k¥8dŠê×Üs!Ÿ5!”³ Ê4O(«#k¢­ºÆÀ\‡xq2?Ëλ‘÷Kotà‘ì|ÈÙš9Â`,áT‘ V‹É’_ ʏb‘‚ˆ_†™¯šô©ø;ùž·0€ë¶*3‚@±y“^ÕqÖø% ÿÔVóÚoðÎd>…Y˜ÝF¤ MˆðÌYžÍ*0¸U1·Í9oVÏ.SÀïÝŒß:?\nxð{ð à9ó2c§Y­lp€ÖÒMáÞÁÁ-«ÕX‘„BÓú½¶ ÃtâÈ ûfV¤qDJyý2¬=þ”Aì_Ç9vìÕF”üš-V¡rq/.\ìt‚iª’+o…È0éÅ⾚ÊjWª‚~á´Ä³Ë}vmSæShŸ“¼=`F\¸U¯W‰ª²ßò“´®Á ;7ÿ(8ѳ»å ]Ô•q|'Œ KÚî‘¢(«PUÔ¦Ø{IJüÞÎ}ô2÷ÕCC%æ¤Õ-®Ó‘D2½úts¥Ã$%uÑ;vN4 ð$u)„Ñ =e¸“²_2çõ“rµ×1—“½Mˆ‚Gü QûáuÆïþ3 . ¢JJö¬zó¿ÐåVuÃo)VËê ¤(öúO‰è¸…#ÁL¨éºG2( î¡æë”^ןdô3jÍò¥ µH.p4~Kù_ÓŒÞmçÿ èÙzÞOZ–ÙYZÈø2Ê@Â3ƒZÜ$m¾'{ガÁ5°ª½Œ&mæ,ƒÝØ¿ dâ­_ž¿©Í‡ì2FÏRl8ÚT´b7Ô¢ÞÁfga©zÙðúH_Q%{¹ßEºLšþ›9ÿ-”X¼R¯ƒ}}‹‚*Óg¹b¼jŠÞøúI2IÂBC‚G`éC©gö6/y\m—·ª}K‰1h7²¨#FfmÙ'襽˶7å† ,3p|*@µÊûªHÊ<Ï}Á:'¾ (R™`1Øa[V Ý!éª ÌLYé.êÍÀo×1ñÍ3Dð· ß/…‘Jsù/Úd¡,öø¶áe¾×q9²ë7¼[G(ws¼"Æï^Ú–ûž‘¾{BÔþõ€˜[ñe }/sÏã€<<T°Ãj [;4èë½È¶—@ðÂtÐZlªÛ'áG’–z4Ìåu >œ&Ú¥·Dú?{ßUmØÿƒšëP<>:C•?€õRHÜ{Drê®XÛYí—CýX;Ò:Gô´¬ù¬jM!/Ï‘q3 Ñ @óŽÌ]KCg. " ¡ Ú­[çæéTg¼>D6bZp!§ÁL‰e6äó ˜4@¥Ôú+°ÅKW¸ÊIn­ãÃ> …2‰3–ŽäcpZÊ´)üùïÿ?æH ‹¤MCß^.H“€3 °&ª(#qã#)›fàx4ŠÕ`IºúØ4Ô˜|>ýðŸÊB:µlOùRîfiEäÿfÑô­2Ë?Ê(À»£qTk“÷pó˰ʨò{&bùÇRÉÇÝë‹>§ŠøØ÷ŽyWÁpíÉÏ¡…‰‚†‘Ú›§{[©ÄñE‡1 ¾ô ÙGïjƒkKNç ˜ÑðÁ²ô÷ß¿‹ª• E¹Ët¸R¢¬·]RüE‹ ¥5e¨ ¤þî}7Glµø(1¿Þõ`ïÅ‘6÷&NÝ ½«gt –ø—”“fz^ ïÈßüלÊÎÀ Tª¤hžÇ$3-F< Bþ|å¢EVôH9î2{ý/…‘T®¼Óüä±ýõÛ©97=cmfd¬n¨@+¯0Ш÷Ún¨£å¸°‚œ-k%È’ò†¼Ãì–w‚)—âk»™·ý¤]kþXÚŒ¼MƒõõaÌ>N\.Üà™8n×ó…ä.‡áÀ›@U1î9‘>vgE?µ½½ËnÁã¯ëiŠO×q„Ëv éö }Ã_¦&(Õ‚p^6Ñ {w}8ü¸IÅ!ÏYRS| åPÙ+Ùl±ò™áž#ýoýIóSÏg€Ç £¬m“Ô.™îŽO¤ÃýA‰@=Ž„ÏkƤ_[W?T'{("РŒ6zXOþ²ÈýP`)»^]ÉuàŒÉwTIù†ÐqÉDÖÉA$WǸa®‹k“ÝúðŒ$?ã"ëÍAqÉ ‡cL‹5^… [luWA&¶×`{0PJõ”Ï+»0ÇS7g B‰ÿx¨ÊÂbuhGš¹ª#,J³óû‰ô*÷Ë ˜‰Ûb‹°™*L5' ßÃtqT¶ÔmC/øNÓœqÅðd]ð ÖÎ"#osãà™{™·YœmqÔ2 híA>ºux3F; Ù­ÿ?Ò„'0¢ÂêµÐQtÂM v­SòíúFóª¿Ãl­@±9Ìû%·þÈׇꔗˆÉcò,!d«J‰¥»ƒ^ÚN !_ÖAàJA‹-ûu‰v߸h•Ñû£QíK¥ƒí$,ƒŒÉk‹#,¶=±›ŠK©<¼¸Î„:^dÕýJ kVÿ~ë?…ÜÓžÀœ×B¸ŽÔºÅÉoþýé‘ñ”`K=Œö¬¾¸àº'§1w×FG›>¸v‰|D ‘þyT|™Sw(ÌyJ€Ê(âó¶5/q"¯º9yQ˃\ù?O„ôêÎÃZ…¿ ûõ…ÿ1˜»'ÀCb…îEš*hìH´06õ‚~ç’ÓEû/õËeËÌB f<ðÅ“lÉ õÈ$ Ñ–òN*âÑù…u.%V ÕÆº¹Í?Ë Ÿ@hOï9ú»±²sÿ<ÆœÙÉ>6… ø²âÎÇ•rÞ.¤é‹y™|ÀóA§Úìq#í¶XÝh Ézr呉}®†1ªö7¯ŒçGª®:Ýø6¸^äá]¥'€/ŸH”_È5õE{Q}‰iºÄaòÄaÒk~Ûbò…“îæªæŸ11» ?Ç陿¿~¾]Ÿ¯”ÀSêdê_Ïàø±N2²xÍóly‘ÐÏ·baâƒLêKEߨüŸÆŽ•Vª4PµtÐ(ù™÷б ¶µõ'ÙóÚTÝXQã±> cêÍÞ·@ç çlè²™¹¼HÈ8q¦ÕÚ¬þ¡þ¸bþvQƒÏ*Ö¥ƒxþc=^\sÎìkûhjŸ.û ŸGX÷º,Þœ³Sk¼ìÏï”né÷oE¥W‘Oéç8µé¢Ç¥j@‹ðFÊVÿ-ÄŠ‚¢îГôø3Ó¨è„o4ö÷ÙJžÔ6¸0‚í;Ó®†Ÿc} ;ãÈj ©âïí\[Ç´¢o‡A8_S#áS‡ç½ô†•x6e)qøŒgÇêò©ˆßWC¦àÐ(H|Uì°¯±‹{g°K¾­ÒMñj´Úù9V"BvX–~:ü%{sð5]oå¬á/à‰ó÷|e´3¿Z½[✠±ÚE[' Näd¾¬8¡šö-š¦S3[¶…úâdÕÕÎ Z ñ5÷S‹ä ÔûÊ¥èõ×¢oÜ}ÝÈJ%P„<à>gÈ.¤ ËLá¾ï¿¢h˜ÿiÓàw¼ ¸r”,Ðr.h¡Y|Ž» Lßr³6±¦i8ûô—òïsʇES³I3ÝØ+=º~ ÅXŠz£<=[Ï´[®øæÃã „Úåé®ðêëéSp>·ðÌr‡<Ö ¬¬€`puÐe©öä™ ˜C7ß(`6l^K=srCËÂJW_Žpøþ&ѹ5ô`ØþJ–g”´»¥SÄ6ƵºŒ\EhºK|myP` òï¨ôîÓOAdÆÜ- }'×-°´Ù«+F+¦‚Ó±¡ÛöW(s /¹ÁÅN üá&Á2ŒƒN›„"_ÿ~1Gøo‘͉9ÄŒm¼ÄD‹‰fí¿Î>ÓÕ ‚¸\‹>~–ÁªW›â­`i±TÉÛ׸^pÜd\òv0®‰#Tù±|:›Íj‹0óA¹*ñ6Îteª^Õ©ÂbÖ£:²ùíí@Ë¢ 8œóç ÓÛÍg¢”PvGÊåTÄ'«GÊFyKqæ‘Ý+âCxíp¸b•\´3zä`}>Ìd“rDF5ùHU é’T°, ÄÔ$oQ¡œ-ôHj9yWÓ® 7Ý «MήòYô$è«EÖÖZlP›zê—N® ´UŒ£DšXr1‡p›Ä_xó0 B¹ÙººÅ~]É_DK)ßsƒÞ~ Û}w»3JÃJ’ìgÏ2ßëÞqŸ»·ÊwÄïnDÝy#Q/èн,p¨Þ•Ú»dÔ. ´´‰±œõTs…Þ ­€²â“íðòq5ÇCt^ ©OðTzÆþ5hð×/-O*ÇQe­‰Ÿf¸6#/kmÅSú¹ˆ Œæ• ¤c«§üþzLÄ}ÂØýI".Ô‡M €N›sÝÕ&‹]äOte@¹î"G·­B^sØ=³ Òº'%Tí£áø`¾±qÊ÷yê€Ù­-1/L²ÙôJ÷Â}O½HLà tÞF3¸§, <ù­Ø¤ÓLïCF«%&ª³ãm— ›rÜEg³ç#¢7ÆýnüTbSô ’ÿ$õót#ɲyÑÚ[ÜsáçÆ‰ps„a, (øñ 5ÛÂRí oY§x™lŒ_ÍM1°ÌiLœaÅ™ÔZjß '#âµR7E¡æöб@$œ¤Â›`í¬tX´­^8wY¸(°ÉfŒ»†ÏøøR-£Ïo׃ì¨YÉ+\.bü ¤ì ¬m=zç,c°^ÕÄ44–=ý‰ðõH”c%‰">ó†‹Ý&CE ó‹” ÷‹„Ç µM+ÎõÀY¹êýBj…¸†¨‘lTvXàæ7¨Ë¹Ÿ°´ÝˆÜf§'œi¦ ¶õô4ªáoym–çîNmÜý¼w­•°ÆY+\ƒC òm#=©‰üŒ»º×(m+48Ó í•ïL³dúë#¹Ï.¬Yd÷»pßO|‰ý.36 'k$,°ÁÂÖ1fn¸ÀÞ­Ö¿IWS[à;ÿPKƒ( ìõY¨'î!VÔ!йB:ÿ/žºÝ'íæ4¸³Ú:οusóK `öhH¹ÓòI¨òuÜE;í½Ø=:ø¿ÃJu$k!ñNƒ×|<a³ÞfÌ-©»-@¥¶Ô{½4k/’¸ªÃ*(N‘²“#Žx^݇޷¾;I¼åmE*¸¸ø(˜A¬§É½ì¤ Å‰`+‘Óz0ÎkÍŽÿgkC‹ªSV¥ˆÃ.+ vÿD¾­Óëî4íY”çÙÚ…IY›kgú[ ¦ ¤‹¬\´éÖñV´P#zâI1èbùnúÍÃÁøPпR%n= aâ qOùÎIÛ‚‹C…#Rª2*µg œT\5[.Nvgò«m(kÏZšw5´Lå ñ‚svq+'Ùxʱ½[’ªÐ1ß­<øjê»Á³Ãôit¤@‡^ÑdÛE§æýjº„^7E ~UÜ€h¬ ù¶0¬¯Ýï”f;S ´ÖÌ ø)X€L°âT‹LÄÍ ’mSˆ:Jç!ù¡«P«/•®ëñ²Šªð¶Š„DÀß ŠnI)/ƒüæš;kOBž¶™G¨µiw˜:Œü¤E8¸Fˆ‘9«rÌðtM„¢AEúäPË‚aûûàÅ¢lꇉ£€özøôx¹ô @HYª÷Ð2 øq ¯¬jä—ÜûµAäêœ1PÄ™¦ÓóáÒÿub Ògµ iÛŸÓR%ý2x"P­^¿œÐjýˆ¾‚ŸŠ=—?ÌôàRP”â6“˸@V rð}j|}Ë oÀnp÷K‹ôAk†.°òõ¿P1pZIÚÐõJ(¶\gÐuRÛ™¶&ð¬‹rj8j¹9Éd6p[þÑóZÄÁwä&WÎIœK+NºZ°$’@ÀÂ?âa‰Õ ÏZü㣗½Å” =ÖÃ÷xˆÜ ysêF"ç]“Ж&=³c1°ùÕ´šzfpq®„%/›ÝX²‘;§¸ÎÖTš÷R…·àF¥À“EŠAãI-‰ ?ë‘_²>ñ¶þ•žSÐ$êðk( <«B`Üü‡ŒÐ¾ÝG×—‡Ë½_Ï_4‡'| ÖÍå³–/ªb¡ý“Ys»Æë`æF”ß/Æ,;#Û~¾#»âóÀkÚÒYG“øŽô'¹óÊt®µìD±­ó3whåè²¥‰½öH+RIr´O#T‰8I8^Kóá+¯H›j¶&ût û³ {ºlf²QA†rRà"Å}Ý‹¯óyõA`áG…¸/’W oQ¶®Níæ8‰KÆ91ú£Í¨MOöÆ6 _“ó>U‹®sf´äÖ]T‚-KS,}a™ŒVÑoŸX]H³~6¾N`÷ó;Z]‚g÷|ð÷s$ɦ¿µ´ œÖÈ0Œ#oL:dŽÑ±«rú%OòÚãA šÆô‘²¸ÊƇ(ô´õé6Iû’V <ÖNfIT“/8HžVÍó¶Ò3é[©ígËn³*Xºâs»!>îz†Õ‚p˜/'1g•í¦åÉÄL Ûu}Ë EƉǒl}!¢;€«n?Úà–ø”H”X2»8Iyà†m"Ëî(Á¦àæ‰OÅ=éBZ—Eû õÞÍXé!çOG1| m;U/Q)y_ rmjÃo¯ß1~ž­Éu5eúº±©K·fÊ$2\…àËXV…øW/Ž“r{¢BÆZa .ì.TÞ}íÜbe6Oõ+aþdO• ;Ô6}™°'óf™÷¾4_FćTÄ:r„2ü÷‹ó0/¾ãëÎÂZ0_ƒ´_㜶ÈDºwd>œ·W†i3‰ËEÉlýž.x΄zž·yŦcÍ6%'Ó&ï\êX&¼ÂÀü²xn‹÷F;”ü…_»¸À”7Å.žÉva’-²º @€Œ@ê5FI‚XÇ—œãö|pz(%¤‡Ë«=ù¦? ŠMzç™Úµ³æ(<5I…¢‡„G\ãÓ5¥çzX0Ý—¶˜FÉý)类O­¹6œJ©‘é¥2€´uïm})N&]Qž÷ŠàìJ  ÌåÔjÝB=Ø´Lv‹¦¡|¼®Áô>¦ÒBÊ.ï² ¢‡ÄíËŸ\w¤uÃr¢°«h‚‹qÎäÇ~’ŒùÁeA• ˆ?+1ysí—qgÀbí.âÿ|fqsyŽf#´mX<³ý6MsžÈãGIXÂØø™è4|Š\y†nøq쇟eð«ÜO”rÝçTV\4x½*Çí”±3TìMßhïõ¹]?þT›Y“ά[{\8enK‰­Ê{§R ©€ªð…°kàØp¶XŒ¯«þð95O& k…˜¶~.N¹ÂîÙEGCT2Ôp°÷Ì„ÏáR³û€—IÓÏ79¶ÇÍóG(‡êø³ì ,]ír§-’ô¦çøòÖà)Ë ÅP,œ¢±1®íØW»é=Ûè÷7+ÛðVQš{g˜<€É%&0dü/ºrøTVƒ6rX˲IÖu}u«Ú뤩ØÂë_%ûl<(…B(T7U“еSJŽû-vÈ9Œ6&̯ߘ ùc6Ç‹13òš­,º=$MÛUþØd DÞ ìa™|ÏÙy7¸ÍýQ‹…tÔ“¬ä1÷´Ó%GT^v“NP|xvsøAJÏ|™GC€µ¯S½h÷z¨|3=š6Ó #ŽW¸V_ÐÚu´ƒDý ÅÌŽÓ¹~˜u÷àÈ)Pžêè_'héÙ69}ŠsÚ9îYìMƒÙ©ïšKD¿B·{iíò6ÔTºÀ4EБ—sêÇ‹‰S2¢A7Q)%û½ÀE*#5ß6e|gRÚâWç`WsˆðÝÏœpŽXlí–n Ii_Ú(•VõqÆ‘”ãÂï.jsa#ÿE?ì~m’4¡Ó¯Ý®¨,Šfßä ~’ l*1>,¢Öy•{Ç3#µÚÀQÖFýù¾„§©«¹“º‡ÍŸ¤1ê¯M,ao8i„3DNàºu¹W¦WRHb…×§ÓY7‰+rø:ßaa¸¹¼$«ë–ôÜjßhq¿^‡Û@™ Ü,Û\b–WGœ;yC¿«•Y‡¦‰8 º”¦#( ›ëFˆiSW!W”Èã x£raJŸйCt@Yš²Å»—±g¤Ì{¶Ì}naþËrÚÕ•s‰¬þ[,2þªz̾¥¬l|5;­yâb^Ç)ßÓ8:&•ãs¬ôæúƒ·Õc”4Ô‰zÛà¢îŒá‹î>ëÿÀd!³>"V…«é€¶×Å?‘J$ª%«]ˉŠ(ˆ1¼ŽØŽš4v2 =ã^ë:KžÊÛܳ–w¿–®´Õ¾Ál :[‹p¸¼yÍì’joHSº¼–K\ï2Ú‡¢²–Ü#šÒ ˆòs‚IT.º|ƒA͹yÄÖ¯$–$>¶Exƒ3:˜š§ˆ¬#‹°¢%ÂkBøe³‘pó3;zž…l7÷Âû3*…ÿjw"`Ãø$Ín%*¦8M ¸¥A새£ÖrAz…²C‘l‘Y|‘ˆ†ÑÐR´fiH³0ùeZ$¶³ŸÓôX-TvWÃí «7ê°ÙxÖ{ò&ì‡e¸r‹o.8Èß)”ÏGG¯:,ñ`0]ü?ñˆM†ÀÌ(†v¨HŽ]¨f±1©y“*§ ‚•¤C9?¨ÆÜX|-@VùBðÁŒ~ã‰ù7²°S,Èš•PòY÷(Ë )ì  *¨t%ðèüàÐHÚD78·Í@ŽùD^§Ÿ:J੼ d§‰§a.û ûd¥ñÏ﬙ê™Ìõëÿ„U»0^õç=êxkéõ„* RhËýŠ“ õ_æbv1€È·ò6âTpºe;+qÁI ´ƒÃì-à[<ÌòzȦJˆ`Á³ý¥¬ˆ$gØWR|6Ú¢@¡½†Õry0‹OüZÚùËYåç62Õ Vo„ð¶ ­âZÌ IkÓã×®Ÿð#|8Ë+ëuW³•$ÚŽî?ö4ÝJ‚‚µŒ³lžaQ 6»Ù"3Õ½‰ ääyÈ[Ë8ß#°£a„~lµÜö¨}Vh"رöЏBJû²qTàÓwý=e/Ôd $¿ãÀºPCèNÃàeWþÉñn äÞÅäìòÏZ#FÒM DâÙ8Ä8FJ®µg_ìjrìÀg…têã'¢‰Îòð.›5MË£ì[zñ²o[EH¹1ж1‚c¨“î+±.­ öÿ þtÐBѱe¨Ž² ß E¦óº-´­H˜¡ÖívŠ˜ÄfÎÑýˆñÎgÿPñ—뇘È:$imc,Äe¿ÚˆF)¯µÊÙj!™ ›p'Þ ð @zXÀ•ÛÍŒmZ ..C•Jøw¿e¤ Tu5UÆ@áÄãýSÍEëQ'7:ÈF%Î-¯sê$‹ŸlËßqs×Q~CƒÂ×®Z\HΘ?N—qYŸ^ôܯŹ>äOøSàÌAðAœçÌ’'‘ñŠ–^ƒðݧ/©ÑºÖ£Ä𗍹ÛG癯q:–ú?u„ä©“œ‹ÒVçÆMßë !|ÏwÍz‚±JgÊVùÝú+’ˇOtLS¦­1bËüq#Õ|Vâ÷|ÿSÔ Ú4£yl=ÃHg3|!4+}çNûy/ ®çË>— ú‚äLæ1†ž$/(ËE}R#ÔÖÐüFVžº/•·9®n¢'JVÿ*ÑÍ/DPöqì—0Ó7õ>‡À¿‡m|}œq¬ÎrçÑ¿BAûŠŸX2¼O0„DÉÄÞë÷2)Gž9'ú‚²Úè³ 1¬ç¼½ŒU'ˆ ;q A”(Qãd÷=’nøˆ-!‹` ¡·…˧ÄQ?Öó"”ÑBžuÎ_ÅGËŽ‰©ŒuÈ-»<¤lM7-·´/èÕŽÐHC߆`d2°öV÷q÷C »÷k,Ř %ЃgT¼÷"Ï܃XO4gy™q¬Òò6Yïàè#%‚Y~³~#±Î`ò’æ ­œâØ?§Š;!U ®%‹S™ÐH˜*À1_[ÅŒô ù_¶aß”šÿ¾&èPÞ¿¨¶1­äÝÐ…ÑÌ×ù¹á«Ï†DîÒPº®†ïlEÚ°IYãÊ»yÝÆÔò–Ë׊]6v•:.;ö¹ëÁÑn¯‹ ê­ªᛠ¬¡ì!мY±ÄÕÿ2]²De7ºõA«©Ì20Y³ÉáJPÒ»ªžic¿!â}” .ÿ¨Žˆà,?H˜}s]®ëa“‡™òŽ6ÝP}þæ1]‹¢—K†ïÓdën"^Œ@äí‘Ò,5WŽH2`Ï<<”æÀ¨>PB« ŸA¡°\‹ŽèO­ž‹´q1ðÜ@îù'ª¶gȸèö³>jŠPï ¾ºn~»5ÅÇdÐøÀU,{Œt7<¯È²ñ¡3tâ%ŽÓ¯Î)Î!ê3‹'¸b¦‘èxvðª7ñ ÉÿbÄÝú‰{<ÚtA*ïøœ®Óê*Èàšz֡Ù6gx^!ËöÑE_Òéöÿcj¡Yµ‹,Ê‘} +Þ˜|ÜIKoR NnVæ–¶çÞ3±9jÏSìQd­_sØUÍ nµ¿3UzæX•X¶Oå7 A…ðZˆI*˜Ó…¤ê®·pµn@võuð@´4 |Ÿûaß”¾ljß§¸°9wã gsÚ.aóQ–0L pй·æÝ™ªq9ã¡—½}ð)2Ëïôj¾ÝÛ3˜·‰T¬û¤?!Ú5ý_ZÛ ¾:µMsœº1̾,^O÷´<ºR $ôö±Á4U,3ˆ¼>á¥~ °ÖÔ{ŒÒK  •?Œ5<Ìô¶û$ŸÛ¡FGß&ÄGkœS¥Z¨Œë:P •dÕÒÙkþŸö=‡ûHÆ%R¢ÛL§}åǺÉÉiyyV4ÆUñúT>DzCu@6çKcƒ#ã†(¤Ùd¼‚(¨™ªXFkûÛ²;‚ÖÉGÕD•ÊÛŠ78¸þ y(ˆ8ˆB‰ØÂÌ´•ǯ±cñÓÊ?y{Ï(¸†uÊŸó5‘÷Sö£yH\µññ³[î¡ÈÕ°‚K5YŒBÝsÊå}-þ+X»c«Ž3ø]µD%§ÑTâï|®NnïKWÈwÝž«·ê[MÙêÚ]u¯mh µÏ•bu×c«Þ»–ÃÇ¥Ãêuè 3@¬2űŽž/Áýˆÿk%,2‹(±/Òo"'æ~¶Øìüæí¡>&Ká;éñ¦ÑþåÜWïÞ(hI{¸­©kVÌWhG™%©Açâã=i8Á†¥M#Ïý7"f9Óí¡m6Ц~DO€ì {Ì™¢î f‰Î&r`Á7¶×slÏ èŸjÝ*½Då~ 5T³LÌmÁ{ZõOÏOöÂ>1y‚ÕÅ>TãÑ€göÆè+8†¿p"‘Ü»#oÑóÃæAޜ簅Ö%–aÃÍÞ[7eTCm´I–0m’æÐ1a'ú'€Ý‚ç[i[X©PR—¨*ùssñAÛ©Óyø€ìè ù_¶] Õ4胑)®¦m0´Ó Ýë<嘰ìcÜî„(ËœF 0Km;m×e„ó5T{˫삩m[ÙhÑ$ì)³ Åœ^âk]E‡é6f5à¸A¬SNm¨ˆÔtƒïŒ£[Å[ö¬ÒȨéÈT~¹ Wq¹ZÃ.'XŒGáeFÏA#~¼ÿ/¹ê¾Oð¦¿ë¾ßÈá!iǯ¹„»Î¤w°?åý1yÍ൹§ ì <¦Q2AâñÞw¤a·‘5HÉàð+1m¶¸U[pܨÈZY³|=€ýõ°ÏÖâ¨1zà2î¨puBί‘ÅÞþîø"üÈ:oúáH^&V²Çû'üȵfäò]y7ssiŒLríœÍÛO½f¢óF®Ö^Pðh'ÿHË÷÷Znþ÷ì΄„,éV»PÕà—9ªÛiUwuŸÄ™ÊDím(YÒ°dÚŽ.^ìíÿðIYQ̵‡•ø}Wàß ­Ù³Rã²èú‹í¬¿à8ŠW›­2©¸Ûò*/ÐóJ y¹¯Ä¯/s6¦å6;†ô.ý ½s: ½-»¹n•×§.£Íùy9·×Æà¥’Š«ÿNÅùOž™ÂÛ 23ezß‚û»Àܘá¥î4à·ÊæðqÀ–Æz¡äáèã%3Úxà§;f%9I¼þ O,e#žh â³µ¡=‚pF`£g&cýOôœ–$+9KN2¤ZH`w…‘»wPnh•ý˜Xöbmôþ5‹ ó(’!+ÀVÇ;­½þÜÓÕèÕZTÀXä"¦Ó¶˜Â<üDâµ³.×- u(÷뀂¢ºY³#…Þ4‰«›ÿbˆg¬å—÷œ"¶iûN쯗д06„<ü+Šÿ)bB/ jŒ^ªKÚEŸJèAøXIþ¥+UŒöîÂ2ôÁ[ÕèÈëBѹùôÒmœ;ž`ÈÔƒ¯z¿~l%Ëþr•Ê5¼+†{\´c" dN 0€X2dûVš•âyäW3 Ýq_«kÒaÚ2acü9 å{Q!±¿MÈ#EC“ôe€—s'wÙ„UN+óVƒ£¨‚=h€ …æ`¹æÑºˆ³ íÒª ÕR¢Í¤¯h©#+7\–"DÞîƒ:ƒ¸vAÏøW¦ì=šçQ;´“’Òåܦ÷HrYþû´ÿ< V- ôôëÇiM“Ýa§GžN²uÏ”Ÿ€)87ß®ˆ¥þO´æÀ:+Pæ^‘Ñ”Ÿèoæì±;ß»dú ßxêº[À>¶ àîÝAl5 ݉Åtñ¢Á<"lõ'± 3}ÛbmmßvWŠ›ôD6žM˜ÙnƒÅC®W°|q3GŽŠÖÀø97=¬xá“Ý ‰å^PÞ¤Åæ R»ˆ´“8s÷£ªõ…±¢&Wž0|°ïÀ˲ŸS*—ªÏ·X†÷Ü£õtÆyÙÆìé˜y}©ÍšµÈøâÕéÐ*ë?µ îSZ칚b‹^äÜÂ09vÃ$IM¿×À=ÚHåô?|ÚèÙ¼%ö¾ò$Á¤ƒ0¾„ÃÈ(ö†€Qw®³³2õ%˜"Ìžžã/l9X³ûÕ8öb šUä}½/ÏÄ0#£wÆNH\Ï!@ÔZ{úÀ¿nò\9s Ì%iZ†šö–p¢Ö?nЖ:UN!±«œ|êM—¹¡Ê—Ñ,ŸŒ¸bmŠóìþ`%;w2Âs 9ÙKÛøîMº Î^i9±ãË1ú¨¿m’Ò“ 07e‰¬LÇ8ü/ŸOÝ𽎸¡ ÉhÔœ¹½üX8yg‡ÐÙ3iyë ü¾oF²ÛpkU¿Y£9G@ýžN£ÁåŠú›ƒ¹ã.ðëÈÍ ‡+Ë0ïLŽ4Ô$ˆ^*Oвs ‚ÛIÿ»§ )q1&|“7Z ™o43++×O`¯9tð‘V$0Yw ¬º°ž˜´5ç¯^LßMžRZ¬…°’{MÒmÄ=”=Àq–Å™J½é p!àŒëp*£k &µD™¢”>CôMrÒ ‘Zs»•¾š1êŸÛ}“Ä@‰rãÍÅ_dx1P¾ë¡AÕ¢# Wàô´¡Û—I"sªçD¤`éÜ Æ”fkb<“~ƒHm‰PÂ4kÂ_ôÈ“2;Ó–¬1]¹µN¹ShžJ“bpƒ°€Ïž¶ÖØ`ß#è%³b’ù÷5ùOUFùjÛ÷gÕ²sž-áF«€*u$1 Úã^T¦ì>ï¢ ¬mC¹`e2èר8{²D&»Ãz’ˆÏ¤}ØŽHG%×2üA­ ©YX5ðC¦ €pX/3CPlû¥ƒSCñ<¡6c ‡ì³·EÔª>õó6*…*6Y e „2J–t´¢J…!áyñ4¿lj0ŸÞR_.g†5¨\æÇúd­Ñ©I…X™5ÚÐfZ3ûbâÁîÚ›G’Ÿc0d3— À‚IYBäYɦŠO‡Hé1[Ì+\å5–›&Go‡§U?¥j™îQ'"‹*×N~A~‰ÄJ‡nèO×Ó´€Á]9“ð{PÜžFÝÇ—]d]°uq6Éâ4{ñÈXžê`#"s®${Ãcäz7âõR `à¬3ú£¤ÚZ8W7^ÄÜnKr¹Ç^Ü«z ÝlQ›ò³T¢¢EkÙÞÆÔ ØWüÛ{`xÃo­€GùrBQ!el’R¼pÚ· <¶ì•˜ÏóêHÿ_ÚI‰IÝÍ—£?C'u‹&ïœ|½þζގÀÒSo£zÇ‘"‚~TÞ×¢ËdP)”PB*˜Àê·  ØÖ}_ƒ ©Äö<àe<¼ÅTÄî¢ïÏK8.xbá—B²`BTj‡ép<gl¼ã±-;Œ\ÀªöëÊL%(f ÊŠù67È‘€„Ú`/ŒrÀydÔˆ~ôˆ çMçÙ…‘„ÓýªÙe-f HÎ9¾Vi~ Éç‡N²°M–éEõýiLU)óoÅܹþqo#1£ ÐÌ…4¶Û_Êøfqµ?riœ…l\¸½ö»¬ ȦyÁÝ×㦞Ý[±ùXwNw;ãboEBt㛈7ÃHðX‘N’ÞòøI(2ŸaÁ#† £X,²äu/Ò€²^AÞƒ(&…ïׯªÔå7­Ùã¸ÜÇFçÿ‚r´ èJnlVúÖ¼DØK1F«ô1†å†o%¾DuZ³ò­Kƒ©æD@¶æM¯„DÿV•åWv°î¥5ÈÜGˆðÁ_\Žt¬8Œ˜w÷`»kÖ 2šsNeØýsH(Šf?;oâNz8Ê%¥¬€°± æÒÜí;ı¬4§ßÌ/¸¯¼“Œ2(?3§°aJ ŠþŸÉÍJweY(©-”ƒ€J7ßGèŒYlÓ*ù#MbŸ‹ÜQØ‚bwH‰Œ­GPêOE ç,¬‹Û`#–%\ØbÄi¯k-nfÕFy,l ¿›Â~ñÁ|E1)Í"¹IŸ]2‰ÄÄ7\Ý£©žS’.– @ÞÎØ'÷¯b0ð‰<ÁÒU5b”YT×€xBÅÊE¸ßxÀg>NyÅÊÞ)È%¤ò^í ¶hU§tmºdÐSÕBæš¾¦¨§€¾C†! úwªê£ÅKÂQ‹Ä-\²SªzŽäCÀ̰‰ßÆài|~ùý_J¶Ö¦£ÌÛQX€¥T-f 9‚Ãmœ` Æûˆahíþƒœ9 Þ)æYçIe·$PÀì| ]Øõ³„Z£]äK?ÐiwâTË^w»Ùyý5«¦s7‡yNs†sLÿqƒÝ•—έÔã°ÎÀX˜¢¬ÚLZHglû”0lãQtæÀÝÏ¥èC©%…a-®qV¶ç/Qo}JèaÉäùL'$Âåå)í®`ù(Ü0nÌãô˜fíæ,c;°¸ ’W›Dyhƒ{oN«½ñäÝÊÉúÊ4:Ò†ÒK£ÓÔ–·÷Ò>jŽ&̃žð * ÷þ]v-ê•Sè$æG‘•mÒ¿ë[Ó/{ÀþÈ~§ÿ¥¦àCQ-yó£C‚  Ã÷b6ï6ªbüv Ǻ‚Wʬ¼R€ñÞ¹ÌNãa‡}EàïƒfdwŠzyy²¤ƒBŒMúÁj†0€IPR‘1KJÝ¿ ±Úe3 Ô‘6.Èe›Ì4ÑúïŸã6 ,ÿ,WµÇxü’9À•Yþ22ÒV*1SíPÈ…7hœˆ”‘¹¼\JIZJ+)Ý­üö™g‚ –i[èdÛóÎvs®-»Ôž1„é|™ˆ“`¶×Äeæi²§®€=ŠükgÑZÆ}x/çs7àXë_A´K-̱ ÚA2P̳ƒ]Ué/1UN½K{fW9!›‚}~6"@˜KUÂc¦÷ Æ/Ñî@‘þÑ®5Š‚LD*œ¢dúÇ¥ù’¼R(ÍÜL´RÛ‹S0 ³:^"-¤”/ZY ÷!þ­…„Ãõöól²½w^‘:û±?˜sê®kö6O¹¾Òë,Úl$U CLèÑ´ºKÇÙh'òŒ`µN8 ¾sVU5Ží~÷2óøAFdÓÌoE'ÖÂCµН9ý© zvÂÓ”Ân¦s{ƒ‹E/×È¡•Êßf%µ1qOìe¢—{3öJlì.¼‘‚j×»€Æd`±ù<‘?-VðàZï7b)f‡ø3Sðâ_!öÐ ¸lD½ëœÌ^}ïŸú>c\8po*W·{ëº{ä!ÁK®ÙÞ 23†S"±ÂäÚGÍ(ºŸL}á¶Lü6šVm\9YVdÁ<§¬C:,»9GCHÿ8œïv Ê¼çØhìÿqضY*´©{{µç3iôvw%ÊÆ3ÆÄ²/®Z_ ‘ïyÿ ãţЭø(|Š÷ÒÞ>VÜFê@’”R'aÄôBß‚æx£žó¾nµì€ÆØ­˜g»—¾§ˆ-õ¯ae2lu1«sƒ¡ÿvÀ+ ¸‰_æ6…FÍ+£ñì‰íøªêE˹a•X®¶%—q+øéAñb‰·/n ,öè‚T–[=?ÈXœ‹¯!ýmHóœ:ÞÒ»;C·ÂʃZîM%è.;ξþÁŒ÷EXZq?q™wçÔ„E]׺¢çB×™úZ߈!À ~g^£EØ”ƒ¹ʬsÖQ`cw';iè$çæã\ Xí]Á)ö(«,_¥N‡¼¡ü%È¡üñ`ÂZáóîk |ð‰¾b"×’½pâ1:à%ðà;ÅŽáy­*8pÇ8¯ÑQ])qQzþh€6a‘àØiî~âõ~@ฤ!÷°àØ¢íʕĨ½ªÂ5.©ÞJÉý/>Û~$ÁehW„3\÷‰UãòÍ+ï& ¼ l¾DÕ ‡çDkœåIŸüš ®V4À1WC `1œPÀ³¸PŠ…ø†&úk¾šºDùG¼nz‰<#Ž…Ê#g)‚°6·YÄïÔOpR³®ô|^S~›WpqNÝ ùíIÔèµ,æQžìT×¶ACú‹f•õ9KÕäý\ÄÍùt¯H §ó)݆ÎaöXÚÌklI'\ÊAÚ›np(  L5$Ò¡±¼Ø?Þ1oÀ9¡ü!¤ ^¿ÉÆ´«â©ã¢Dè¸6_ ;Úðý2ŸOœ1;=ˆÉ3O‚0=:Û”²qÞ/Ýi,¦™<ĽrnÁ˜v±þy(#7›Õ®ewÔ¬WBÁ9§Üm( ¤ÈLïípR \¡]¿?v—Ÿ,ÚXnºÕl˻ņa}E¹¶8à2üOÍ[ãTÅFà÷ûËé84?ÎÆsQbÃBOÉúf9ö¶r "郎þxˆ#1¨øü§&ßÄ×"`‚½ÝÆ13œd¦¹y¥šú5WÒ#H4°…ˆCœ<~Mc«!’ªª×öÉFgL)}%eÅMS\ò}ëH»˜ÞßõLáÁo¤ 4ôÉÒ+C— ð99)t.–F-E9fÁ©^·Š]IâE¯Ð[ø6ÿ)’ÕpU §¹äðô þ¨_¦šy?¢í"UÐÑ'Jmæ Ý<Á•¤¬<[6fÄCÛ®©áx|,0eYbßᬘ. ó5lüˆQ;Q<”E¦«ìõVë`1Dyv¾)Ë\¹‰1ð öT²ôÓ/™ØF¡ØŒögßNŽ?ŒCa “›œò«“ØŽøCÚ¸+TiªG^$ )wŽ ñØðù‰öAŒ!,ÔÇ|¬œ9Á¡J†âÊž|vº„ÆêH¸ƒHm$ð—«>åhw“æàHªÌÚêüSÞ×®0ôË–Ên•óõåžÄtà#‰L2К1~Ã6xá«îYîor'íÑú5¸Ýòü$²sX¸˜ÓQG,w hê5Í#®sESúƒÐS-géOÒ)ò)ƒVÛ÷T„ã3"l¬›gNª\ÐN[ÃùKyºÊ!ÆiÊ®PV¶›S»tRÂ=æWÒ™®pþµ:*#àñǬjW&üÑ.–ðE[åz­9xÜŒð8úƒù+‡`è\|ûíjÝÄþhîŸÎ³;¯f‰*Öç·ÉòÑ­'0ë»g˜0@¾w\fe¹5ÃŒQ—47+œ+Ü•Y÷EœíÚBC/¨ãETBŽ%ñ.u%ŸQ–¼$Üĵ›}o¥§ºöù'cþ±ô?Ÿ:ã3F²•½d_¢§¯÷ýZ„Áî£_‘Ô+öÙ~³`ŽôH·Î0Îzë?È,6ÐW¿.cümc99¸—WéàaØ{ÓGíÊ!/ƒ¶×îé!Þ<äb•›Šÿ41l*/5ØÆêBM¨…šrÏͽð),3n1‹Ãá¾*Û³4R°œãQœ1/ "PŒÊ ¦n2:@ÏÀîÔN…å¦ lfyµŒM"ÀšQ¾IÜ÷´š]LÙл #=}hKžG!©ýÒ¤‰“óhuÁØáFypÿAñGÇIÁ¾Ö!ù‚‘´Á™•rIÓë¯=Ö¨B¿ë„M`ø$XÑù4³l]¿w|sPÊéŸNa”ó±$#_…Cêzó°r®PŽ,B7]eŸ!ª÷͆C(}÷Ú·Aë½üÙL‚MöáÑ Ž$5¬+¤Z@™ù(’'ßP%#O™@ÿ<„ÜõX§‘”QQ ÁÚüååÄ‘Œ§a9ök(³­Ùof°|--tuïHáû•ÓšÞ¦£‘V]¬‚ú®;ç¸!ÔrÎKxôžåHÉ”2©@ØL-sfÍodù"xáÙa½¯—&ÄI}@G£§-î® Œ©Š·[ü,X¾#ù1ºƒ;­oA5Äú?-a($`Ø—ztÅÇ!9A ‚÷-T"·í¸€z\ñ³»rã7þ„Å¤à‰©¤~Üàš™³R¼Ÿå €œ¨»›(8f—B1JzÄ«NE%.=²ÅÐÂäÝ•³4â€^G¿Ú—vÈÛûoÌž˜q¯¢–謢G `YTež©dæ` åíÈ—hÐZ°:Žô¥Èß”0è÷‰t³ñ–Ù¤¼ÒܳQ-†‹ûK³æt¹˜‹Ö»ë÷œš¹5þHé*b õ®6­½QO «·¼&3p—ó®~a3²:ùµ‰ôH8VK’$V-©oža´3ŒTÓD³=e"èmbÐùD ÛµÔZA®Ÿ·=ÓµQ œ šûó”"œ Ê@rP:S:Œåë•~WÞRWwí`Lô1§ž¸xÞV­O¬*åÀ@†LbÐÆÝ~¥ÏÁ”ƒ[ÚgôBÜÇaŽÃð€á3Ñ!•Aˆ‚ ¤ `ñ~_}Nqß›—}Îì/|¬‘´glän (±„’ÂQX `¬c©'òE´_MŽ¡ Þ´ò‰…êkŽgüÃÝ¡…ÿ!îVˆº˜ÀÌIz7¾KTIlY5_&‹G àþ&m?ˆgqÀ²$ ½Ùþ„Ù£=·~Hã:ýó9¦hÞìWœœ§te‡fIIÎq¤2z  Ö“ß’kÃSl’>j=þCÊ]T'Ê˵QÉgºàØhØ-³"ä¾äMÆ”™.a'ÿ šO­¦·(™®™>À¥'ÕÿŸ°Mú¾èEz)«íHüæí®;CÝwiëÇÛ|J¤Vw¥{ãðŽkÖ±ùög}>ñ#Û#ª®~>´R6#ÖbkO‡5[Æ÷ˆ¢A# *e¾C‰™Uã¿;b_¡éú¥†U:æ}³¨³ZÑ è°Äž(Æ<ÿháŠjòþô#$xÆZô mpúªÚ¢Å2FV«{ÒñúÓ˜MÒL!oÂ&†›“²vúJD9¤óªL W.ð8Ù‚èŠÃ\Á3@­~Hu{ºvÕJ ‡^4é û‹l«EÂá&„­E½mA Mj™¼è Eç>î²GWJ„¥ðxvgÆ s»ßÄ¥æT•f¼DT+Už0ê"‘ð•%Má¶Óh uæ2Eٻ؈¼MÑ 89ÔS|V¤:x ©{/OTgˆãµùJØ$žhþß'œIš ºôÀËŒT]˜0n.Iÿ‚šŸÝT9šu– m‰~%!KVœâ$¼xá´Ò—ºág0 ‹É磗|qØh 3ùßÏûÂof·ð0ð)¹åPÉ:'”hÿW¯m«±»ädÕÖÉ¡&Y ÊNÏ2]rU†\Gëö!?»™›’;$”jÝÝäÒŸšIé-ñ˜mIQY]«¼C³Ís³[tÍž=y?¶?Ûö³|N8€püͱéÈÇGªòS}L3¥Ó*žb¶áN±­À©¬B¢GxÖË$âù‹Ö²£=…Ó·ÇE?(Ûàå½lŠ·u2 æ$ Öj‡5­¿ºø+ óÝAzoLAšôZ¦!μچj!’€¦¸n¸XÝIýÿLBáŒ_~ÅJ^ÍFݾ¶÷Û6a’Ä¥8:ælN¼è5Ã?œÕ ·bSáF¡)jÑ<±Òû°ÓôáLDm…KÕOÍýÆ>±»‚FQ,CM*ðnOS¦“±f`TgS=["°¢ñqæû)M†ØMÂQ®i¨;B˜ÿ3n«è0]"Þ 冤 ³é7•™©]H®µØ‘¸ ”ÅBéöש^5•¦öË\„Éöæ+êÍË•kyk§ f«l—‹Òšœ‘“9 ¤‡«îSwÝf²6<"lG-ù‡ýÐ5fîiéSfC€›3R!vh¡Ä§'¬'5žE;ò/cÀ‚eæÇ4¦îS5ƒ&z¦†âǪ‚€K|¬Þ™Y•§6•¦y4ø"8oï"øB­Í·/ø7Y•2!Äzë‰{,<ÐNƒgO4ƒõ’ÝNÚbL¹¥æ‡ÒÝaäÀ¬§Ýè§Û[bZxf¾¶öåÜéŸ:÷ãã®öõ :.~æÓ ðùÞ)þå‡Ü"½ˆ±qîZø(ˆÁÞ;‹R`/ñ÷l.4ˆþ+KKrNŽó VämQŽ€œØÑ¢8Á¶çÚ³²Я¹X^ZËm¡sÄÓüóåìô+ä*âe³lC|ÜCæ^ItN¯n0 yì•éLÎ]³[ã.ŒAµÊµr8ò}yzã½ôå×6ˆB'ÍÕºu¦ béì•ÞM.`•¹;Mn„e¹˜3¶K™ã\v–N‚ìóeF"ý´¡Z?DC»ËÂÚD‚\…DÖ9TÙžo”§R¨c§!ïB0bgV?üŽaÊVO—E(l™”(R&T7µ0Gƒ-Œ×”§Ô’Œ^tÐ:2¬»WÊÀäVE~…­Nº½Ù­º C¨ÌÛ@Ss‰`&”¹æìEö­Bsó+™ åÛÑÁX †Ü™«×—EÉÞ^ö£Ö­™ÞfNœÁæþÜaˆÛÖ¯ýlké-wÂLOWÃî<e-ö‹p·½ý‰=”ãMè±+-Ó´2Äöm W9EO¤Ã6)J•ØpAó"Ýq×°")øõø7ûÔÅÜÏô7Ÿ'õ Þgǥߪ™¿ç’ê¦=ºäÍ’%èM>ð=æÂÚáëäBä)ùV)¨O±Ï 2´i.ŠÁÒÉ`olQŠGV bÿ,ÆÎçE}‚4ÜÇ!OÑSÔ¿[ulâ@a’qëB2ÿáÜúŸw»™ÙÀX:ýµOï,ÍS”è MDdD 3¯|#šzé î¶§4RXÒ‡(*s‰ ÷½fmŒU 0Ê5~_ñ†O¡S­Þ䟘IÑ܇ٓ=":’A›:´I2¼·yJœ€cÖ\ñIîiÓ_xþ’iwvm–‹.PW¦IðƒÂùË{ÂÂ:óó^®&)k+wý1²}™‡ª;Ú•øG7ÛÛtL”èA¼@ù\çŒUJŽx<÷:­8‹¨¹ ùÁ?jÄsÁ¤GªÞÕ<Ö¨i þŠCýÏE2•ụ+“]éÿlnRû(ôF¦·w ‹¿®lÛÔÔ3×ràá7²c½à…ý‰2Õ¯®M¢_ü'}z«ð Þ‘rºÆ…ÁÄX8sè¿åì$$N Ý®‚G&…§¿Ö'¿†®"jpӈ͛tÍ’@ì¤\'ýŽ|¹Í¼Òô,AKÎÏ=q‡EMÅw˯«Žôkš2ØSãÚþÎü\Qöi‡Î¹†<ÖžFi{ká[ /åc—K­/¯—Ç¿%Œä¢E/ï=XC·C¡K@n'™¯†µºIK ‡Ç?-ª F1¾z©ÄM‚‚©yûFý êOG¯Ød7q‚û’{QŸÿs›Þñxå©3`óÛ™Kêî~̳~J¤œ0Xi7£Ø…Þ’¥Í©Ä¿—Ÿ¨æ’q~‹é•µ,¯ýÐãªH5¼ÊOI óx1˜A|ëƒãÌðt{jïb·mùð¶ð¢Mv‰I¹Ûòú!Õòˇ:Áà,ìGÑ/^•V˜Bý QiŽÕZ½Æôabæ§œ¡_RúøKm¸aðáIv ÷öwÛâ‹wKÃïÀ§Oxæ3¼pk¼L“ÖŠ,®…B;Ίœ™£‡Ù8½„ÀYû±M¹zæÍ,aj»‚SÃ.&óaS¾‘cž¯H­¶¾5o¹Ôâ¨±ßØT’üƒàe‹tü s^_¿mgHBÎèšï+))<8šÛ]Ë H±ìH&0~C•ív‘­õ´ûwż@g×âoë˜nÁV`ëV€!I <ü £},\jísœ¼ ÕZ♬YßÄÇÐq>[9¶©”ªìx}­7:F•5D¦š*‡‹¸*¶‘5£`‘ Â`“`°‡–²ïîÌ úa^hi:‡•M„ÿôÖy HVVUB „Y–fø»êWwùÑÅCAY·Ù«LÓ·ŠÿJ,) v÷38ÊHƒb©‡v™¦©¼Þ­tçű¤&ít„D`ñL¸ÆørFU®+Õ*CDœ|¬Â…C¹‘¡â×s Ê!òßî±¾^[ÀƒÄʸºaA¢ƒ¨±ùõ]¿â `ŽŒñÐpÙ©Žáf @›Jj8ƒ…ÑÃ`CØð´k^:JÕ £äÌy1ÌxgC½GïGq6;¡Ûgƒ4þ² ÿ^ô‘ç n«¾hS¨Zl½rÜ®ÿ¦r ¦Å‡å^ߪHy+ œ²¥Ø[EÏ÷¼Í³^—d|ýºP€~–†v/fîUáÎÇ:¤Þ‰lP`ä….>UiT¬Ì|£‡×6ÿ!¬7&‹nÕï RÜ p´ñ7h6á RpŒþZߘ“5wÔÃÎ h?DËUïcg ’)¡36&Û²Z¸EcõZCf´è[m¾aÓœ€<̘ö;më×ír…ÆÖ8=(2¾©y“tä¸dÐ Ó¥ ÄÁ7Ó¥þfÿDHŠ0Ó§Í  iÌ‚mžžXinÑ@Ê+Тyˆ«HdW}MuZSYõVcîÎm[sc7H^0ÃÔ2Œ¨BcsjÒÍZcýèœ\Eêôàk˜,ÉT¹ëVŸºÎÙßK[Y °0Œú-pˆiñ(L¦â }(ÏÒMË5•7™°a»"/Ÿ³%B(#FÌÇRz}-¿¸;BSÆåA͹ý”¢Ywva—ÙëMct¼·aÌšÒ}틤³±k´ÿPKw]íУJón]¸ PÓuf´Á©ã/eæëÄN7v‹o ޲ŽqR“’ìXë½üÔ2G’Ãz=”%c„â ‘å$BŸ®>7Ú¦×`6hJ_€ZX1Œ®%æ¾æ¿Úš@:îNït<£ôy‚fÔª2þE*t½gˆGf™FY¹úGáàyfÁWèŸÕ-õÆðŸúnuÖ] ø<ëY6IX[ƒ³o“^~ì}X¯‘ÄÖ̤ÉöòL£:lóÃvÍXVoYèêû<û‘©ÇyׂpFtØ ]l’§«NM´¸9´aóÜ”_kïŠ6 Xv<›ßð?1`åZÒ2ÑÜ`•BËU°‰½oå¦ï•/ Þ7г€™9¥‘òÖ/£?`[6AúE5•£Òyo´îì0ÜúGNY|Š# 1ißôç$µ)„  ¿0êöqrÃ?¸ø¼QxTåPôÖ2{/pÝÛ N|6]—E²ÆŸ¢ÔÝÂAj”ý‰å°»‹ÉÞ2ò¥ ˆ™øœ¡JÏô“ä@®(©<ÈQÏHÚ fIñpprk&$xÚAu³9¢„…‰ÚcÇ´ÙÑkPÞØGºØ*½Þr…Dê"-è(ä稻©Ý•©ì»…6ºRnäc‚ótGz#{Ç—5‡Õ~¢´ãx©ô ³·Šò}D£ÑÀÃÛcxRsÅÇ…¯z=‘K¿Xö7!‚B:…çþqQu.5‹ìš‹ 32Û³´0ä®…«ºþåšž:º*ªêÁ5¥¢%Ï‚l6tÁ? 3‘°Ø‚™œ’Áix®sµæ—vš×}RJIÉp¨,ùnÓí9%\ƒìKÐ"”¹ lw0‹yI')LœNÖÏrúr—è?¶†óÒýÚo¿B¼±4e"Ž¥µOØÕªÎSåŠÉ×Ò4¤Ž¨éÃ:>qS»ãì;Üs¦Îæ!è[㾿êdðŒx?aãWfêT«z!Eá%c>%Ì™)Zñ‡l´ÏËÜRÓõXGö>OIßm'Ã,‹{$!Â;xâ éÔòn¼³ñ<R‘ªÀ™W(›ãœ¾”¥­I`µ=ýh!œOgÿ¡ôž ^˜V“þ‹{l4XŸKÖ9í&‚vQÌÛv"b"jƒã;?‚—S 3‰Oε-§avæ×R,G+ PvÞz¶â~’@Œ!4­µÐÌ?jëÀ'®©äŸ?–h$Mu^ß„ g-C%‡¼üÿdkk*× ê™Êo—º5áBé ·ëûi»™ã 8¾ft€ßݦv™µ¼„.@{‘1gÅ, ´’µ7x ÃqßåÑE…)¶d!Ȇqƒ‘èS#P4 >Y‰ùÜñ¯sƒÒæ­È§Ô£Cd¸JôÜ—¯P5v¼y:üj е2W åõÞLJº´øœ’}8}okE%%ŸMbcg×ꋜ'þ\Ñæh@[‚ c‚#Dv´í/˜xj³÷Æùר'¤¼þ³n{80B›Á\tK½×R²¥ŽÕŽ3ù†{ª²Áx®Ígð{ÕK×=h¿œ°¶Ÿš¹ÈiµI¡D8Uª=¤Ôžï-Ø%…ð!° ¿9¿"41*þ X8®ù‚Þ´àÕešÐW6›$Ð †Fã›ÚjI^5#aئÎs/¹Ããîî,ˆîyèé‘ʇ±ò~9ò*bkÚhl38Dgïe3”Çàú •º;É áËZ›Žç?W|ÅBKvÏ„‰J‡da¥Ã| ;]°µ“Óe@½®ùÞ:ý̬±“W‰°¥u?¾ÕV'Q¹ýnÃÊðžl¶"!?»Ê—¨}‹3âmÿo |wµ¢‡àc)Tç ãè´¢z~ŠÈÎèÞ.“$J{Ú’æc›öZ¹ï%\p˜…•õ̱ò—ú‚ûØj^[ŠÓØ,›óŠnYgù>îì:1–éÄü8Ò¨Åéùyuú²í+ ?ûàþ·¶óç°ÏXLÝTgÒ¤€:9eýTÔBI¥ËÀ`f@lN’yaQ}9öÏå:±oÏ›:êuÝC WÝ|%ë %ߨ†•^Q*5ë'Ÿtæ"^ФÈḠûŸù2ñ›­mž ®\ïCð9JqžÏDR½Ô ÷]<˜Æõ‰C{'°ñˆ+¸­Ò‘®ñNDñgØ‹>ŠÞ0Nºò®3g#l¢Ý;¼Ë›åÁ²o§Áô ‰ò›P9,@÷~|N¹÷j­5XÄöl¹«ïõÊ1·ª¦ÈRRÏÎ@þÏî¥ÒhA¢¨àEíò¼!GFXÑÜ«°ÆKÍÀ,ØÔô8\â`8å”þÔ´­P㌿`&OŠdõª­32¥´#“.Â:<‚Ò3Ìîó r {¨5– -Ë+Ê'wgÁ‡p‹.:ý  _À#p_d_¨¥»ÎXœ6•:é•Çãá§.W}ÿ}˜$1§ˆÑ ´'mP ™Þ[>ZÓë"$jå·U½1KX›‘be)¨‰Àô²ðšmô·[_äh”–ßïùXÑ/¤´Éî?¤ ès!T¾<¾óô½>G væˆö/¾áûoão’ͼy'‡²\.zÂz œ aWô`¸÷ö:dÁT \ã›/ñÂÙµì®=¼ÒS‰aòp!œƒƒó¸‰F¤/€3gÜ¢R”ók¾ÌÃ:éh¹ÆÐT×ÜTñÖ;þ q`ÏÖqtšDëøÇ×&=³a’\BHÅi„ŸâÝ©• ·8œS'¼¦Z\[|ro…ìÀ#a¡¬àŽfCåe|Æ-$}ýBMI‡³Hœ;(7öO‘†ƒ¥A>W¼yŒÑ–Í=Z~¿G[Úx­þ¥S”†šq;ãÔØ»‘¬fj™Þž c ùAJ…ëTg ¿59ie¶à¶>ÄÜùçˆÇá˜ÍÇ^ÓðÙKþ²šGÔ?”}dc¦ÿ|·k:=üF!j"¹¡Ä-qº³gq/¹àÍ sbƒï”_É€¨x-x¥¼„ì°R€‡Û^“ã«ç­”_|'ÿÓäs½èŠF_8æ*OË®·ª¶}š'{Õh ^7 ¤=¡ò{/áÿ9qÖCüè‘ON™üI’áÙªa+pÝ)û:NJ†í1Wxl¼b2Ì9ç…·UÝVëýÞ§¦ÞâËEÒQír9^ž††e™‹Lê·Ú(1¨°i™Ï {Ú» âAD#ò²Clúœ;˜]ëŰ{UòXuØ:\v‰HʃúïÖòAN)ÃK6;žø^¸# ¦ßhž¹ch®ÎÂ)ã‘à(m¨%ú`÷J¾Žõ<Ž2®:) Õm1Œ Þ~sâñ!/*øâœ3¦xhìåUÂü(ÖÊ|¬P:&‘¦Ÿß@øúe«üäê¤`oØ/ÚL|”¹ÿƒµ%/V™B¤þ±„$€s U§qŽŠ’8zF2àò@‹jO8Y«Gx >@‡U4%|ÈäT]Çý)|%eQÁm¥Ôᵡ³™•ú+¨bïå=PÅ‹AÁ=/@šGbþc8Ð} Q@ ÅqQÏ™&5Ë÷JTŒP ë&Ç NÿAm«Zò,ÿ ©:¾Î‘6OÙ6Uൕ©§ïÅô ^¡v!oÈOõù™ç§c# Ú2;[†wiS4zÆmÓÔ¶>ô,Œví´s¬ª¬WoÌå[Ý|43ÄÁæVOÕj»dâ;Ù³¿Z;ÍÞ—©9I§û¾[L¿Ð_Že<Þ!;baÃüpòàvÿG*óô6š¨)ÔO(›Q$©X–+<ë1šÖšû½—¢¢¬¢#@»K£ÊíƒJS|õn(¨åÜù©N#‡Ü‰¥Z£TûÔåÜøEÕCJS†Æƒ‡~ÊËRnR7$¥Â‚„µ¡© šÏ¨‡}&ó@µùúK¨+We\¶ç@û¶¶J‡¹WbëïßJµ˜óô¥ýÆ`Ä '%Ù?30¹ú+»°ÄW ÓÿiáŽîßòÕ»ã×x 'XM¿Ð5®#©´—Ö‰žÊÝNÓ¨-e kÞ %‹¡êÈM Qùk; Ñî‰ã“¿_8ú²É#,4®ÙÍ ch2^†1©/Û„Éà€t;Õ 1ô´è®–kpT`xĨQSp’ ©4o©­ë¤$› 5üKzMŸ|ô/Â¥EŠŒm%aö]A÷ùµnHZ~!¹xÉà1ÚúpUÔÔÎуӤXwbñ—eF*ý”ÜÿpõtiHCÇ”ªmzco‡—‹ÑAF”cÅì¾.3,ÔáÉA|}eö'9õU43“‹3d‡b€H›õsxö™a‹ÞQĤF8(…ÌèKW¦˜¢®šIé²#‘>g¾3 adR¤óFãFΣ.¼ï0e'*J‘õsÄsÛ®Œ ÍQNŸ6gÏX:…—n•O‡údËDkûý:Ôˆ¬g‚ÿz¡Dyò}zþÛ®³‘ ×ü„¤ÆƒœB™Iw`§4‰£žYçƒÇøeƒ{Õ#£±z„ÕÎ3ô]3!¥&Šò¦ñ¤æ€\£–½ë¡¸\Õ³Xt£¦:º`¿Ø¿QlY}·Ü& ªšL]û&2êD/µÍsuÀ‚ŒìPt÷èNj÷õfŸï¶b :"nF ŸX¼×»aäÁÕïáЈÒ½('Uph¨Å-¡ŸòϥгïªXró¬Óîè¬Ú0í›ñë«ËŸ<ƒ‚ï͹´°Ñ©†< 7ã8ULMg,e#’"?*‡¸GU’4§«°Ô—¶òÇLUŸb”¾×·‹Ú`—ýøµ‹Õý«GwÔ£Úª˜ùÁõóÚ-ºqóX+1(Œ>|Q/öÎX÷H~+Yc-;ßÿ&µùTnÍË÷yO­Y‚™²‘Ûõ bn6–Ôså‰"Œ÷ªLîðªÝÛ(<ýä¥TÉò£¤€øjh+îN²?Uö4¦{Yýæiþä£Ä&Iã©“®­”ðØîà¹V3|:{ÙâÛ¶”‚( Ê3< IdTЗ€í^šDÀÿ µñe+M5\žýoðqr¤Ÿ7û:Tpe4\:â…õ—çcùòïê;õKê5**¸ýŽð]úô3þ,bYCïc³;+Õ¥Ç%6J6]°`ÌœÚÉ„Aýª«×»Ñœk´¿‡OÿOèýZ€e°˜Ôö*s¨áy˜)˜AÔ_@Qw½â¡˜"ŠçhRŽ bľvV ºÅ79×Èíñ˜óÎiÒï†84ÈU@ñÛN¿ï*|»OÁ;¿&PŸÝÛÓÜÄýˆ5°ó¨VÃÅdçûè¡’íZÕZI€)Ü[ìø|·†qGí‘QC~ìþ-Ú¯¾E­ô›|Óºgº§þg£R£úáß…ø#ÂÓГ„'åL…á%P"$•…$äºuÛãeZNúعL!Õ`¦s”Ûæ gßí g\$v·‹d¨/´e›µÕÄù+Sf†ì›¦B‡ÝQeºO¦7+ì€ÛD$åSvÚ=ûh–3O~YN‰åùgLHZ{J˜9nB?£ÎŒNÊVFëë]Ìꥭôíkëõàv’ )hh[#lˆÐÓ(§øj“˳4ŧýÏ Î%Qf¨;¼9„uýOI2Üï€Á1vLÃT„‚wÝéöKÕŠŠHøž)ä7>uú‡é,5'­˜ß¤*/~ÇRvƒÈb®è#ëMègozÏØ)©;f¹±Lî5Wñ\ `i²Œ‚9ÉÛå¤ °ëGשD#éàˆzyš.ÛzÌ/ayÚÂGåñ­Åƒ¡`{î¨rLYG(:NÏÚæ v„‹.!b^`7`ÙŒe¢|çy8+ßÿ*Zrû±p°ÉâùøD/¼É0à ®'柘W"»ñxAMNbhÎÓ4†BIFÔî0†n¡H¹vû‚e§ÁÉ–™°ª_Љ’QÇ¡¬%°6aûØ/P­„£Oœse_¡[!Ï^r©hî …1(´Á«_‹ þ!éc­¢,&×Ó‡·4“Aª£¢«â»woµY]æcÎÏ5pYŸå|Ï þÓ˾€=ÍpFƒë¸”G—²Áw¿/-P£\~¬ÌêHcOs¡ú ¨ ÄqûJ÷7žhEuË© „óõ"g³Õ¢é{5ŽX$òWl÷á–ì¹Î·>'$o†µT„¦Ë|Á*{iêß%6H>–7esX”•vFİê!Êp¬I©ÅE]#ûÀ=Õɦ“"?Oy‰–G*¹¦{ôhG:зçßü&ºôZY£¯ŒÙ ìIáQ D5­ ?3JŽë̯e°ó+äv³ÖV, 'ë8…•?(‘À!µYXKä>øŸî+P“!›í§4$™Åµ¹ôn»ˆ²®]ÃÅAð¿n¿“ú1º+–˜ÓA,z¯Å7ÙV"‘S#µ­\ =·C¾óäTeÏHüù`[‡'SšÒD´‚h ç§¿‘FâÕ²?‚²ŒÈò+æåéÅî±0Ž÷›PÈtð)ó,‡ ý SO1·*MžÙ úš~gÿ7¢Óx‰ &o íüã¦é–ãЇc.;ïõN™3GÊn¾òg©…g°ïæçŸ#ƒ}ÚöЏU×ð›Ìêòm‡Qw4>}ežP{Î âmç"]¥™¼ ·ÿ1ÐEV¿7Á¨Æt’Ër,>£Íå-p~a¡¦Û/Óªz™Gãn¤Ë>Ê[¡ÂA°· (?«p›€„_»ÉK*Æ_Ý8 5ò÷ ›å°›€ìK_æ"ݵ¯®H€†:@ÝÜrµ"ç,àss¤XéÑ „SÁ«ôôTú‡ãT˜l§5ÌÝiŽ¥oOŸéLÎ(›2·¦ãš£½qZb†ä1[IÓ¨ÒÒ¹ša ?(þ±;bÌÇ÷`ªRîÚÂz^¦ÆaÝÍVIòƒ¸öæ‡R}0IxTo»Ÿ-¢oüÔGu¨gçK¢“ª7Çå-!U©ˆákàõÁbùàÃÎøãu›×S«ÚÂùMm[LŠØe‚b ÈŽÐb¿ Åúô÷î^goeHÜÏ^¦oËnÐayáÒsj{-2^õ¡Xœ÷¸G4Ý•LÓ³êh³ö­|Yë(f,-dzEms»ó Qñ¦9Ž4e' áEîÁ…8ê/ /³ôâž, XžÍy‡ÆÙA.N&‰ˆ+ Mv~×ÛDð†£úu¡°¿6±–Íiz¶/Ž9ŒPÞ˨#tfðKs¢±íÛИyÛ"ßw@¥:áàي®Ù’ê³G'¨'}Økœ!f© 7è]o×=Á¦Æû¾ȸm'ë©5B®ŠD¡!¢{±ä<¶óeµBÀXJ¾åç½ÐˆM³!Ý2š¸a²‘7ï‚ùŽÿ9º˜F®”W‹£‡nD`ºY4CM7­á¶ÇL¿í]Áöz©Þitrܲœ‹d%å!ÔŸz‘µ¶rN-؆õfÒÈ®£²ö8ó®Z~ªžî âX ©À@@âtM}Ht¤¶eÞäæˆ%C+µÁ¤Ê …ú–¦¥ï°Iijâ,y†â|9™úRÍþt»¥Ÿÿ*ípÜ õi07C‡Kž€±8«‡í2sáÄ”ut(ÄQæd“;…PŒÂAzƺrë}ú„ƒ²žºóưÆ>KæÛ—½=4ÏPψÏy…Ú ñ¦´²>+dcÁK™5êPš{v¤¿IªPÐáe¹uÑ€‡”À†5úÂÝì<ì.ÙæÀÖåKN9‹|Ô¹šd-¨—9>ýÎK? Î$÷i~| 謡ÁrÐÔd#« ª’a3ذè˺Zž¥c0êñlZvâ«ìô€¸E´©mQ’W]ôÒ΃.þ_p2Ǭ d¤íÅwO4Ç-SOQΰ™ÑQÒÔ˜(sT„þ®^ÙÑþ¨ͦ™ne$P1Î] dùb.áhÎM,É‹Ïd޹?q†a|ì/##ü¶'݇¦×9”(k”:žéÅ~õAúS÷ð&ÝÕnxõº,EJ÷¶{÷7Ë\¾»a¸2K0C›C¬uÐ%Ý8™k›‹Õc%(ò#TºË’úuÀy¢hvòÌ6À·ø#u|ߦS.s!R0Û¸91®‰”ŸY»e((«× ¤>Vù$(­ýû|þ{re{JØ/Œ†}ýÔv¼Ñ=ø,ªíM–ÀÆ ‚’àÅösÈŒèoôÐÙ9Jë‡u†nY®£ Ÿq~‚PzBRºžº:ÍË+-¦åúi¸0œq‘•75èeeS)~V<ü\:£ÙŸîµÏDüb´CÎxÛSHx‡L*E¨·}âa¯Ó·X$P/#¶HdDÕ¯Ñ~±3û)ÃW£‰÷.ja.=`ÔwЬ€|=RÕZäµe§a·ÑÑ:¼«™·:îú'©Y¾Z“¼1„Jÿ\²][ô&ô*ßegÏicÑ·Ä1®:zG|ê™(;æ„•«g0œÒYü¡Ð²ÝåîôMå¸DÃI±I1n î„W²[¯9Lp¼ï*³,¥cN Û|‘í–ß|ï¬5λº7™îÌ´dy{̽LÎ7W]öùHk7í…àÙ)73=ÐÌF‚#6gHç2ò"òRWûP,orýºv,±Î¸ùÎ.±ÚÈfªuŸ£§[uq³¡þ‚kΠÓmõ‚–ŽŸZ-1td&°²hN‚Èj/ÑBw¿fVÔúf Ÿkí Ä™Î&R[`o4pÕ¢>kw<»Ú" :^uÝÇR€’윩æÆ>Ph· qBù‰áXSQA¦n…ø¡ æ6f³V¿\ô$J×ÊÊý©bKÝkÃ@­@G5Zqs"¦4ÿº0/pý†TêÚ™û™^0Ü=Áì(‹Õ#ð&¹»Ÿ#/!¬ÁèZ@·Y§Ý¿z O#³ädSmê>?ˆÎ(†}JµÜFRžI1œ‚˜>¶g!í6U‹Qý»‚†r^˜ˆhER¸¤Æµ•Ïu&‚of¥ñSdéGÛ¾ªˆCñÔ#ÏuðP@C ÚÖœl´9ÑÅÔ¡uûMQy@ÉrêîÂOE±P~Ë‚°528Žì<ã^ŸAb³JÏК~Fâîu ïFÊ*›°ßRán øùýGãæY¯°'* $Œ×;>3¥"¿~Ö]®ñ˜@nââ ~En6‡æŸÅ–fÒTf6}æÙ2NUV…ƒ‚/Š ¥Èž&n¸íù“Ôcòú'Šƒ¹Çp¢li—@1ã®ìšžíã¦_þ‘®fÉ÷©¦(uÚ- 'ÙfÆW½b¢<“p§— ad˜Lÿ2å(}ËÛX–µ /‹äÒÅ­íÆñHA"Q5®.ÛÈÍ9Aiä”ÑäJÚÆ“÷ÓÚ Ü:” 68#µãüêÌëçzD÷Ìâì((EŸ.ÊË©iãÈñx|Ó­Fj|1DûQ"»`]ÙD>2†R¨d³vjç™Ã“DÉôÊÌ#ëݺ/LøWïŽfù{i&dGó§K½t ÈfÃ)RrM°æ¤Îçxõ®z1¼»´a‹àI@'qw>8‚º©á¤ö'JiH.›ÏúL;Üùi&z¯ã¨RüwÑë†ó‰wy²¯‘þðÛ uÚÛ)z¨\5E)kP•J{5¸k—NS$Wß¡ˆÇ¶¤À‹Ž¨%tòôââ=%…[?ûßDŠ»»®Õr g1ès~ÇØ\ [ž+&Ÿ\Ø5d©F·êúÇë]ç7¶=zfš¸ˆø…pÉÝËä ßyÔ¶jõg›{ãžaÎÅèkª!™ó+^~!²²¦wÝìI(ˆõ¨„¿¨/èñ‹iØqåþ|}Äí£ÈÕÍmà E79DƒÖH±¨a‹&Sýð÷Ù#š]RV Â3ðÂÕÏH!Í4¯OPë.Sµo«ˬ¸âÕ/ôÄèÒL9w#Gî쀨ÜÜ\]Ýá‰ßäÿNJ!}Itq~?šK?ð÷&NkÂOÛ.<2wúç™Ýx“˜@-øwÅ< Œ¯1êÉÙuØîoÏáƒ} DÁ¨£’“AýÉ–trèÚÇ)‰¯0íŽíó-¥´¾¬¼¦?ŠÚEkKîi"Ð-ü‰„$«Ÿ{•ÁåEý€¨ëu»¸bȉÄïX nê¶n~ÁÌS˜Û÷·i>þ9ªmh¨Âúø8(&TñÛ]ïÿA×Ð$ÆÁ'‰p¤ÆŠ(#Å×J³·³ié]$Rè »€€º7ßñ°Y¨ÇQPåÅùP)zUy`h\2GÜÂÊòa`à5'WøÉ]ÐXkI«^a'ý3:.E›V@Ù<¹iÒã“b8„£ø„2ð©CXà†ãó»¤$of(0 ;¸Ÿýž–/y'²'ÖBíÍ/|ô±C¶ˆU Ûg§ÒàJ•U.j£“Æåò4%uò¦Í%¸eˆã‡ù®‘¿S£8bIãºÛÅìq€v»_ɹøn=BíAõcjî­‘£fà _Ú){LVpéÌ™_ yYoæP{‡ý§–N%qúDéë"!Å{zÚ‚£¤4î=S Ò-KØð¶,€Ã¾z:ÉÝ©ò‹ÍFxÑÈÉã=ùyqáHø³c`ÙÝò³Ú´v·)) œš²W'.Ïé|‹Ê«.±°TIñzgý³Ê]ºÙ™z+| –iÐø2W¸q7Äî«´Œ&_îeöhQg–zQj­|ˆR·û†ãüÕшî“èŸã´pü²Nt蜪éfÝPº¤Ú§5TšXçG¬#Hßyl熹5–2R<§åˆ&š[Ä%Þº…NÌ{d”Q?¸“›ŒbØeËá3¸„àÓøüáqîsæ÷…мSœcÅÁ¦6s^7ž>T'7oL‰¬Y@p$ bÕ’g2ãâ‹qÍòE±í<¸¼‹8ëÀJX:|Æ«MgŸè‚u: U‡Ù\'„8Êv/€ Õ¡BX`°ØÛÌuÙ¶Ý9bʘ;&š ùºÒ+™+] žÎ²~5¸j¹µz~nªrâ'÷ÇE°38\’Ê“(¢Ù£#¸h½E+Ãìa›rNßj‡<ŠóŸ#c‘«_4è³’.„ß¹ä =æYGZs~“¥ßøçÒ6LÚ6/’fæ=vP¬cW"ŸÄ¶¹ÍòIµzuSð¶¿RmW>9H¤Y :ôs†ï,Žð“©õÍൗ4qëÊ®’„c®O¡Þ’»«/ 4šµ,#óCk Wd±¤=‹œ[ÿ)±ËøÉ_Q£“sÐ{ÍUûÎß„€´p‰cÉü1Ý®þ';ƒì†cªô[ÍÏâÖ=RÑ^×&I ~z9^óOò/Hsîg—R\Z e43$nç)u;36¯üŠ?§]Ì æ#v…žº°¿S({ O*‚êhlÍê=ùð;õ¤#¼°î̦kc‰ée}“ÎM¨E˜âšÙÙˆ5¬&ȶ¾œl¦@¢}QasPªöo+y6†­ìs54L³þöÃÒʬÄPò®>(JÔ€É Âcðü*‚¥%ô —1Ô†zð¦,òP^ð÷¼–ÄuÖža?¾öº¦±‡á/4Ê2&[|´ö´À¡kÂ]/©A=- ¾µíil‘™Ïþ5¡„ä–r^`$ÁmQ ÌðÒäžÒýœÛ Y²÷ÕuÈ/o ý† AˆuçŠ5ŒÃÌ”›y˜/"…q<Ò<™.Š“«è”ÉßË?²ËÄ;ÍUÁ'— pÊ2Z‘S:ÐKÏâhw±ïûIËÓ7’Àq½ Ñ0ÀÁŠöÏ•—7É“ÌÕ 'cÐ܃W£<éʦÙ5„ë×4@n²ÙZÃ-@ÜÌN.ÕÙ‡´öíY½{ SßÍùE»õ«pŸûº KÜÙª€M–æO¦Èã;n)ý¾•ÓEÕ”ÆiþºÌ_xÄ)½f8ÔhúG«Ú!äû­ÉéÛéßR¸¿ð—ÁÒm ¿¢rN±é¤-uœ%iÅ¿¸Y+ò™Ó£¹¬ ¨MËjw%Ê­ÃcãÒÖ¼˜õùó€5Kc[\!4UCæõÑ€&ûÿpKú™¢õ5­8É .7à 6o!m¢6Ýäbw­Âñ´_Á¤¹³ÎKè1ã©qZ\kï^ÄÎ;½éûÜŸÕÉ0ó­,á·ªFî)¨Eàf.4ax8.‡2GæŸiéÎðß;4uüͯ¢cµZ»O-îˆ3Å«SÅlt;`œˆåÒ-‚‰ ÍÞ3Ô9V'¢õZ?M¡Þ "øïnùH ù×{ÐÇ´ºŸËe!vkS¹ Ì*gØ[™5õç7[5¶‰‰Œ>”¸h$+Éúì•0Øü£÷z<+Œð”̳®õ1•ÀÏ=ƒ…<µ “9#wàx‰“" EDÕÐLsrûß!“xr¯ˆ<›:…Ö†ª}…ý93¿ ð?r ëÔ½Žž3„ ñÅä×~ÑÉÅÀmØÊãyø ôwV®é»Øø[ÒSbã¬m.8Ïû©m÷;õŒ”¼Æœ›#ÝJø<“3IÃH/*üªxò ªŽG'dýúÖ‘Ò²#{Íå5DµûhE]©ë3 ¶Ö_¨]¤ãÓ»–zÒ‚hµ˜Ë¢á€Ï],_Þâ¾ÊÝWÑeBÆj낯ÔkTö=øvà?Ù«ÊØâЫhÊ<ƒ·Ùivãek½®-ô…å>µíñT5±Æ¿ñ*—±ûuq j3(Ö,šs¨›¢ÃE©rnþz_· ;ίÛO±r~¶·F¿ž™)ˆñßèiÁå[™à Ìæ_«Y7èrYÏKøÐÅm€§"v!$ÜÐYfù÷(¤Ä¬É¹‰°•ë@F¡]âoM ý€=ÊEEÅüh~ÎðK_¶-ªÇÁ¶°ƒAEGîwmN|.—%bQªSPÆUÂ~…ƒ¤çL tÏa«Z¯²,Ô¦&Ü9£_v}Ê-£¼x–Ÿ™Lpyzòn/év‡ãfÆ"¤WvnBºŒÚŸav¸ ²>2~º£Äe¼Œr'Æú‚ƒô଺)>«fq16ëá·ÖÏÏ-ÑN]-}‹F—ÒÕud6ê>@Åç. Z‹ÄšiðŒ1ž  <康2[ÑØ¥Ž]Ïì6êEÁÁZöŠ!pʹîMá —„:¡ZiЗ\ÿ( 0["¿¢@mò§mÍôÍÊןŒ_‚29¤ÎMhk'ÔS¦s!…'i¿å à78¸x0‡^¤Ô“×wdcÎbÞ¶Ñò@…Ô‘¼êWNÂóDÞÂÓÒŽdgÛÀÊ•bŸ4§x{”$ÌÎ'±Q˜'9o¾ó ¤¢ú?ާ¡w?PS·ÉUÿ^iÙí‘)\dU¾ä+Îõù¢8Ú—FŠ´^‹©=iR €ÇA…+#~²Vc}[?¢o†FN¹>rj9¡«ûI^òÈéŒæ‰75Š!õ]|€*I‘uË!F¹% Ó²(F}¬ò±eWÔ!ctœ²ëOp)µÒu©¿òÀ4ç,îÛ Â'TæÖ>>¿À¥o¿ËÆ­- Ëà< ”@fq9U¦ˆ°ÆUS$Á¨.œ£&.ÊYxÔ!0 ^·éŸÞOBEì0Ésù+‡4G‡q2ûÏ…þû„ÃBÞÔë´`hpÎb^Dø»Úr'Gš­ SWG=§Ÿ^à‘Å1‘’«v½ÍUT¿Ãë‡{¦š^û4²ÈW)ØdbŸ*ë*øà ¿…õàÈŸ¹s{6Xs:UEú”¨6‘tË»QQÖç`–ºí¥—(—ÒPÇ[‰¤·Ã&Y›álof¨•ÿ>òq}ŽA¯ÿ-˜´»W1?Ë¥_•hVuôv€Åw…'Y%ÏæxÁÉBÄc‘W~ý9T½½\mx€=;¡L¨ß_:8×ÖÉ"¿g×R¶;±‚™=N±“ãü‚×ÈxÎÿ-™Vެk‡õv§!)—Ò‡JWw3Ð!U ÇhkaÂSòŽ"ÌqšÁÁô˜dq–ð 'n¸JL¡ø±‹µÖr®ýßìÒÁ~ÿ›ˆ¡§ÌHiírþhAÞ}»Gf9úã¨@c¤—ŠÎžÛñƒ\j‰•öpHØ”¸°ä¤2Oj±,Ú 5Öi¼÷”…3). ¬Ã1ø Éáû6Úæjo[áõ²!>‘9T.žžÎ×Ò]„ÕjiIjîìenr½ü+Dò®qå­%é´‚î̈g.L¨ô<Ðv2®ç@Jí%Â!£sä"›pëöE·QúÛ·QÞö+ £ÇK¿ýª\ãÐBÉ‹™@²õYv©ÔîxX0i%—–ZîÜ›OªL“ sŸhF&‚c xõÜQ:/`I÷m3|‘¥œ”r®9SÂÒü޸ЧQ îñýlt³u~ú•Z\cV–èêÚ”’Ê„Íû<ÛÏ…è†j˜ò.iþh1C¨ø2¬¦A Ê)ÕGÖ|ê‚ç¶õ¼/ò>€jy—, ”ø#ÇÝ€›†ÖR+’ø28šù–IÈMl_¾4Üs¨nb—^3œÈR¼vK“÷‹[™Ðí ½Äû¸¡tRœçÓjÞX¸Ò²”QØ–ï2ˆeJ„Ë×hXÒd¤3_š­`eÔ¥Luƒ¸·ÞƒWã …;[i¤¶¢|®NUPKÈø˜Wšö$ÚŒSFÂ#öDbFœŸ¦\GVÈûƒD»dª'i¹éCá3ÚK™É³{` ?Uœ=¤á°™ {Yr>P2#(M,‚ªH^ŠØ²˜œÔÈ4å„>$b£;¤¦KS×/zÅ4ÇB츕ÀðÅ>¶EøNö&&[ó†…¼ÓM.ŒNËÛá"ÚÑxK u½¬úÉJÛ«à”ú½Ï šCõ+|§…ÕÝ> ØÂøØÇ—¹Ãj§èKÐ Éœ0×hÎv›%Ó$ìøêŽ’,>”½5–#¼SŠ”Ò3³Îý|Ù"r·§ïÄÙ!ûÌmU¬Œáå㧪WÁI¨j}í¥tpПäZE(G#{á’æµó6¹ÆžS+*i4r]ìÆàôçÏøçƒ½>»j % Ôð¬mŸNH¤ØÇÈš“~}„-´ó' (i<¶–­ƒ ~Q²ç*CŸs_l ÈaËTš¢à‰3!eAL‹#PâtS¤¢Kâªý´SH C²ZÇS.…h.Òß²oŸQ&âCÙˆøh”»"ßl_`Û/fréæ=û¬#ÍÛK»¿ªŠ ¾`™üÑh‚ß$QR¦ÊÞ§³øÛÌ|] kW7s¿ß Hƒñƒ X‹råA_Š4­_o)Üe×ëò3Oî]ÄÐä´eÖpÀS#P(gëÃÆNŒ*7wÃn˜Ëy >_7J#ÅŸ[F ü—åUöÖœo]ñw2Ø]-âLS± (­©Á“»²é9dÂ ìŽØ_ê‰ÃÐ<*9½=íŽÉzñ<° ØÇZÄ<—>-Ä¥pl’h/6Ê€Û„Lõnöâï)ÝL«†¯<‘—Àðæ(Pèvé!V½©’O_$®’-³Í«¡25ùô%g`ü‚p;P6½Dyë)qÖ„Õ½.äL»¢:,ãÜ·/µðù]ð™¹1r9š¶!±Q<‡wiÚŠF·¼úd9d à˨µ7æ~OÒ{jžíìB*shXç>æÈÞÕÝÔOÀh ½€¢ÑOD¨¯WJÿoý—T©bìV¨Œ/oæE–µXGOi‘‰AëPe=#3–ŒÆÑœÝGÞ—Ñcü^h‰÷콤woýùÊá³¾.± зˆ•¼™4Mß§¬ŠÖººtß)vîî"£Â—-¬ÓW͹{ý¢%6)Ÿ3öM5NhïËF ´¸r`‚–ÌÁOwuÿT}ß÷kgRJ®â±jò›ïPU{$ƒ›·uwûn¤<_yâ]+ˆnœ3d¨œÞküÌsæXIù2scY/‹F‡L³åˆðM)‹«‡ÞõÕåòBaš;2¾Þ±gXgY̲¶»¬Ô ]êƒqÿGòó.áW=DUyÊŠŸA\Þœ4*UAø÷­ñôG[Hï5ì=ÿ/¸É·y>9eň"µ(«l9ã[ËúF ÝIaKëÁD¢þGк%å ˆgsöbšö52¤¶{LgŠÚKPm°dОҩÞ'£!¯—ŠÎ'ÛìØS"O—sMHöÁ»ãÁ Ä’˜Üy‰Æ-ìô·_{cÞ~ÜÄߎ|µh!R’ÛÅ*¼0 #˜Ÿî‘ÎŽâ# ¦²ûŸ¤¹²¤hRfH¥äØGŠš÷_×fæƒWþ[§ T6š!W– Kkô·.deeÊú V»‰y…ØìÈ¢žn9gqæµ·PÞÛtRÇ\h{Ò¿t8¿ÖЍŠlPéûÒoîHôÓ~€ijhÏÍ„-žœÔÂQÉÀg$MuŠz¬BwrˆFY&{ºyÉb’iˆà€€ªgÄ9½y‡¹è‘‘+@ºéa~Ki¯{Ú^7/sðTfh¸!g|%ùž_š"—¼óWi4'–œ9ø"žHðNy²l“ÆöþZÝy+Xƒ–(í»c(ÏØ•vh!>b Ù§æÙ×èŠc¬4z˜ @×{¡Áe¢‡½&”•Z5Ñ$ça¾/°ûªajôËÓ®BáËj0ç(3¥’ÅòVó¶ÿEƒ¿Ä Øë9vFb¿“,Q4¬tô6B’r]ð„Ä÷Á¬}œÍYåšmœñ(X>›@MaÔ6ß;)4ut}W³CdÕ$µ=Ã/žRÝí¨VQÂù!¾7BçÚ”=Âìiˆ3ˆià ü§¨züáõ?„„ÿWòþý íNV˜Ñ€Eæ'ü´’s:=Vâ›}ÄYr()FÊ3ø:qXjëg¼æÚ;ä6z,î™qû÷ù,ÃBà§B4êåÈ Úµþؤ—Ll Ã;ïïÄ8Ž9…¡Oö»‰ 瀰IÉ“¯’ì@Âëü\ÙL8т״DÚ™Ûì¥=<%NÈC=_L­wSæryË ò•)‚;ËÔ‰Ü'!îSQQ%—âPI5“Å2‚#u~Ó •ÜÈa¸ÖMÙ­W”-(èïP¤³0¿¯*­ëz0sÜ 7›¤+oê´¾!™É-ùMbøreǽ œ—>“¾56}9Âü2EÓÿ3N+Š:îû}Ñ&ê¯Ë1C„÷{l…!'?œ{pb ¹‘ÒY¸œÁ†Ù¹]ÉL°@:ׯe¸ÀC­ØhuïœmÒ_ÓÈ|ºi$°ó%âáDWôÿvÍß®¥¢èW¦ŸàôÍa+Ãa˜‰cè÷Z”K2 G„„Ë?}·ájŸOÎTì¸é–´JY¶Ñº B>švÆ­%Бý÷œAX¤] B§”8ãÚ¿”³¾â¹{¾-Iž4V°1i±VæÆ#4€µ¿¸zy÷ü¾Ahò&$c&j±5Z|6M„d>×ä ôÌ¿b,00þ6Ú+Q&óõͯdϽ¯ó•)à[šBïâ6ÄXnrøWs÷#é×…ÀLÐY<èåzX`MZõ{aM䓸½×Š)ÙW©]A<´@XµžbUÕë¥.ꛉ¾ñ'YGc îm†¼s2 ‡å³Eîh†ÚÛÆ6À­Ú‘ R, <­r«qkq§ÝäãLü¢It…n<óžï[îe¼Ø¢`øîïÒI=›¿˜°Ëå·–Û2¨u+Øž' ±ÿ{‡'îu¿»m" mG[] 9^Í`f=rêO|ŒSýŒ×"S PÏÍ\¥ñŽËèád±>¡né¡E¿Ê•˜¿:Þ“î-<ˆþ§Oèx8l]ÑHç²¹[–—\ ØÐ×°è#Ž8jÛm3Ñ­s àB"¶‹ö䋱>¬ÓŽœO ?!KÁ#EzÓ4}‡òCY5Uˆò_ìO*r%' CK>ú ÚÑûgé,æ9­×Ôÿ?ÒXSzÎÚÕÏ cPëv$U¶M©#” .ŽZW·Ówj“}±ë’\RäGvÅT »?´&vàÚ)㛜ÀXáXâ×Ù¬ “Ãã,È^kTGC‹¸ã«„TIeùîayåô÷$\³èg4s² aä¶…\0™2NøJCñ=]ÔF%/Z6€K§iz‘ÝõLlÏ."éÍ?=vGÛÌ;=>ò²-$~SôO»Y™&m«è%SF ‘/ÔÜíÂÃüÜ›>±’P Cñ©¾+‹Ñ=‹F•«.ù’àu2—é‚6ßOÑXp*j,gM+ísÆOà‹3;—n¼dÞûÙípÜj,…lýž½2ZÁSœiŽÐa4±àW4ÈßÓ®i3= õÀ^6ƒ‘c`œjJm¨õ3¿U#f&Lõ‹ˆ÷1u&NÃÔ'ô¼Åšã}©½³#7½–ëÇvj'IŒ_ P§(#à‹T®ÈDiÅé˔ͱtc‡€M(ŠþOžd†ݾÇF%Vę„õ_åì¢Ï*á7\ªî—’@;ÈøU€Úœºò^Ç /¿õ ;ôj8Ôê#À=ä›3—ýÊ…˜å,dÑLj1a©^¢%¤^ùv¨u*ÁëNZ &qE8,gã…[Pä¦ûX$t!aøÉø=ò|ï4en»t~´7÷æ—xDfв‰å•£y튡¬D‚L Ì jÌo%_ÞœAîÅß„gö6‹™¦ô þïù¢^¾ò^îÞ3[ädB‰[FNS»P¡"gÎ*ðõŒeÓï §)/ùHÉÈz|ÚÂv•­7†™PÎñ44Xuý ~×ëÔÞ³zli%µÝ{Ÿ%l:·<;†»áâ­Ý¹ÌåÌÉ`¬‹b™w•ü“×7¶Òý¥r3ÓjÇΕÈâ.ZøÊqÐߟ.ßÅG¾ÛvõÅTá€=?ŸTùùm)fªvRćŒŸ˜+¼ÓÕ‡Šš ¯£Ø@@›>Á–ð“//ù¶8HG«ê,‹>Iåâ 1cP-ÏÏgÉï”q‹,¨TgÿzÄ©¦ƒgÄ7„B4–\ -ø;ΉðfFP­†{ú::»á©†6€Ô¶7«;µ~šb0ø? »9Ú30OßóŽa¿+Í/CÁQQR-RÖÅ´ ]­SE…ÍÊa¢´ËÁx71÷[î1È~?u ¥è†ò0ÍV†“tÚ Aá ÜÝ1Ò–„or‘žúŒᇈjF t ºßÈÑaK ö@¹Ò— td?ðíût7†Š†.Ð4?¶üJ³ÝíD*5ùOkQ ÌR¬ 0c¦r¶‘¢9A܇Ìä½PA&ñrHŠÃÑAAƒæ"„ø}&´ïüûYM…ïuŠ©Ñ©uݹÅñ¾jŽ•fDÏj¾ÑÿH³`½X¡]‡4Åàf'vU#j•㇄>$À¯Ÿ3²€¦ o@g3ù[çCÄíTìJs5ÏrØïÄ{}þÒ¸…;S"¶<ºl ‰}#CÄFÈÎy+)'ªx5ðŠå™‹zˆÉã"ù‘EïêœãiÔ~CÏ%«’ljABf8’á ÜzZL¾àkà~rÅÛZ¬äA¯ˆA£ƒ¬ƒè+»D(ÒLß<뀠TêÈ!Ç3H‚•t‡„T5Xx±¥c¨'ÖJ±Ç ¬® ZW©Àðºd$®ˆE‚›±‰O|¤Š¨éÿ‰ç7¹•ˆA¶fÃ÷ß» §§”Ù¼Ç/¿ø½¶ŽdÙÞéÐ,§©éulÏ WÖ‰¬Sưí÷¾Öô£½ƒ±6“þV£Ç¾=œ÷¯©S?²}¯ý}GðÄoW̹Iñ]ƒÉE}7xh%º–*C&šYP³@;¬?Û-¥µ›}Æ—ÆHº+MCñÀô~ìÁ虜…pš‹‹ÃÍŸG=¨M“½G¢›]œ¿^Ü.»¨Ž@üŒ‡À·3o¯-‡A=u’›oqÚâcv3§ì C>4ØZ¿ØW¾ÎW1RäØ™Î¿Ò¥Ñ½æ¥Vïå:µsEè—IØôú¸ÁÇô·ÝXTóW²²3gˆ£&æhêjH±µG'YýY$ÎUªƒF#:n`•òvI%­–W¡¥Ü,ð¨àÞ\=؆Äãç ߬¸Ö•ÎW¬ŽÔ²œs|y {AȦPÁ·¯G¢Ñ½GÎÔ»³Y”¢´4¬ÄðS§ïl+ê‰=jn-L~Ñ©¨O§'§ñ*TƒÓi¥}Ñf`j-Ö=¨·ÈÕ  È•ôPo¯rõ'Úu™­Ý÷Íi \!Ã;Z'ĦCÚµ’Ž]Àb 9CñWtM,hqxíè”[„v†~NŒ8"5­(žóGUkX,Íÿf,ØL^›>*;èžÿR+Œsþñ5Š9w2[þø®„Í8H°«°RTiùN;;e MÐ:àÉrÿ‡uðß™´'¬µ1”mÜq _æËxh}Ñø©Æ[_î ”ÁæQë—lš $ún($»®{íCéÍÄõsu5gx*Àù‡2˜ë5qˆ×< dy“+êÕÞ¬7È!„ëEæ¡vv%øÉ;o¡±bK+ÐÓ®¸ù|Ç;à$¾#Ïz‹0"²ÿ(”>ÙÐ=c–kØžõÆû±Þ£ê6CZ8 YAŸý€tn¸ð}†< Âlh…¨Nºo—z¾êØX½ò.Þ¿p¢6ø+£PI÷àâÌ÷òÓ³°b€C·bå» •¬Bt«®—JUë´{™í~ž^øfˆ|–Í-L}Rtó?„øìPXŒ q`‚¯’dum í,ú sÕ½ÒNlvX‡ø³¾ f ¸ãV›x)ñ/=A@>c=K_¯ÎÑÔÌøË‘Ãmü¹pNÄGXsqN¤pés&¯Jè €ÌiŠÄìX¬†aað–:1t]Yå’-‰aMG§p˜÷òr} ƒ5{µ5Î?pk#U$R+døœÿ$:ïîô;…ÜQü'f¿ˆÑh·B†ÏÅÖ¸c-£—jr;`ð rj§Ã)ƒé}ëË[bp]ßµï LÀÖONñðѲå‘&ì¹*ÕFÍ8qËꈗ);ö“áFñR”ë ë]¾Pb—󴤵z‡‚EcUãÎÊæèGr¯1'´÷FC°>":Agêï±Ë™ ¥ZµßϺÄW*öàg\j’Ì5ÈøC¹aÜòPt«»=÷i,ÉEbÿ.ü1Ä"ƒ€ òÛ\I=éЀŒIÙY¬¸ _ûºÑÆà ¼1˜y[¬ IÐGùÛçNôOÓÜZ@)ŸS½‰ªèÉ'óèí”–¶Ìä·Í 4VQ8’‡€û³ê Nrb(2nS h»nÊ@÷ŽúÿHE—'xM+ÿ/öUDŽ‚húraô,/Â3|Iqô8Yz‰AÀ•h^‚8Íøª¥´Fæ’Òk„'Éͺ†êÆjÙÃÇ­zÍÝŽ%‰ãÒ¥¸™ãñ«þq–n35c§?ªGxÉ4 ¦´1…k÷é¢5ñ½¡¢xëw——}™–ÜÀU'%DƒÏŽ]ZðM£­Û"( þFAQøi¥Û¾?fDµåjõtðS}©Š"ð&¬KÚ $˜T ý½—˜½*×.Fû‘2àdŒ ðxÂ*ø-', ’î´ÐMª´1ËàÉb£®óLóoò’S¯€êX+.£ùI‡ÍëÁ±YÜMeV³#تÛx¹wù+vPtN›v±ýOb'’ GÑðì±$ï¨_Ü—}ki#Ie›n!ü©>v«'\þmi¿u¤¾.`×ü´)£D50Ô]x’‚ ßév)婯·Saà†7ò(q†Ü!vèêà>P¨SMBÏÜk†ç1¢„³¡å-ŽÇ)áh?aú-í•~ í–¾Îkke¥Ù}¢¥m4awx8]˜Ôêí}˜2«*Ä.ÕA ÝE­©Y{½\Í\lW|†ú3JL\¾éêÐ;N£„>_LÐ7Õ”V=´Œôf ï¨ñž ž-¹w€Ò ]þ+ýVI7 ø 6åÞ qýÞ@«î¼– îÝ“ÐxüÌ€• Ò‚õ†¤Ih A®•h©¾ªÒûY5DHþ™£gãlÌäAó‡¹öeDŠ;é8Sú™£ãí6çEd=¯NžÝû‚ÄŽs&62‡V8 I¨ \;ºØ@„)j³­x6vu ­ë‹K¦µxV àû}N7pIÁÊõñ¶hå,ÐÂjÃĶb0Ìu–ÈZÐ×Dn™£‚R-2ZðéŠÇè­ì‚}Û,ü›ÔLè ü ô-3ƾ}Â){=K¶_×D(¹ïUC¤0ìlrÏvíÝ8ÆÖ;c×vÍ_eŒ¤óƤïÓm©3¨£‹AYeÙ0Ïj¥< \—2 Ä´ç_˜Ö~¹|%GXĤ&% A£?OÈ”Í"bIŽ«k÷SÀDП°R6ÔHS"ª?%ÖA®N ÷»ÜÀ=z#àæGÉÖ?oÁocs5c”*ÂÆi â)¼7–±|õƒÙ2kò€i{çj‘~mJ 5öCu1¢‡!¾ž|°Æ ®iæ2–çÞ‘Ž3Íwër*/ÈÂL¼Òüa&Só s]òÌi\ÒÛ*ÿ$ÿ^°Ùx \¶ðz¾„dE5ËÖ—l†¹͘ø¦[Ž#— /™nªgæéÙÔµ¸x–DÓ#lÈSU˜IµazÑ$Ó¢{@ñø&"Ã)1ÀEd™u4á&@· À¸Ff°Âö­9”É´cA¹½Ù¢å.U»Í N«¾íüéØ8™¸sS×C7¸±&õÔÍ~J©gªsVê8=Ô’û*2›K³P\_ž|ÛYFEVÈÑëNyR.&E¨ž3¹$±?5X´Ãz\Ô…>SO6t²êµ€`ØÐîZ°h#²Ê©± C\¬NgR¢=n7R/@¯¾Z¼|…ÈóšaR.’,tâs¦¼kÑ[tÅ»{Y%.þg;ƒ³ÀRô%M2ô¿$rpÖVk¡7¡&¤aØ[>ü T µ/Ð#Z¹,ŽÎ ùæo¼ûÕÔ‹YÅAz=\»(ü[J¡þúÅ£9Pq „û]XwÛu²q~ü’)ãI#˜´e'Ö£!#†%ë Þ*æÈrÛ,¿tq,•ýjà !Ø1E;F§ÆC|z'¬š€WÞŒ¼›œÛ-Q°Ž§ÙÀ@7X™cP>F™=ÇÆ%¦[!%ÎÏŠɽHÏl8 <“àR%IÝm‘m)«TÛ™—ÃpY‡Nѷ䓳´3ŽcEv‰aiÉ^7õ);w2ĤPðÆ·w3½|.™2¢¿KE})>8D%нS=TáÑèb¢þ«}§\Ó/ƒÒ9÷©8 Сñ.‹äas‰$ )&Òž¶y"Ô¨ä6¬eZ;ñKùg\ªiÓî°œÑÜ×ÐyÖüFW“í-nsFÂÌì“o÷Ô•¨€¯qS6úaì[OZ÷30-\¾:Æ‹zò¡òñÓ6¦ LL¢¯Ä³MD<ÿ#™áFž?a[GȽ/ïÙ¹²Vñ…Ê %lí‹OŒ¼´LÈï&X äã|*ar> ð¯ É šh+ݺ'²©ºÝCë-¬¤ÈhEù–‘äf›~+ÊG,/0Wöe‰0Z£¨÷3c y5£nÞ?îXH‘E‚i0—ý”ðK|P{©‡“˰IXö@mš¢žUo£ÇG…zs±guê)HË_T¢»º J ³(Ob×c¥‡•£&+>'[7Pâ=@¤‚&:T B%f’,&+˜B©ŠJ@<åøËôèØìoŠ-Lï!Ûù%ö§£`1 O#·'ñ-‡Å¹ÌŠ"û—¨“œ7©º‘*ªþB×{’-N]|.kÍ`µvx•ff_°ô"Ø góÓƒ²Ãý…f{þQ=ЙÂr42 -.7j0ý÷Ð;kÂNæGü¿†/ܰœÔ3BBËýÌqKˆ”f”)”|ÄDØ-Þ’p>?}p™·ÿy‹ú£G…è1z³§ØydåÓÅ&ϼ«ìsžÚMú¸’¾*lÛly¾ÁîáCÁR«\¨AÏo8”Áp5"äÍrw‚.9@ áJÆÞv€»+¿Óœ"Çh 1Áhýð‹^Ù»ö¡Žö«Ty´{ÂðÑÓšØ÷3àÿcädèÉ]Æ øÌ„¸Â“@M­sÁºÆnV½cÄ¢mµ‹äýwUÛÞÀ]ÊZ¹Å}]1 %d¥«Lù`ñjÒ aÁ)īˌa,À«¢H9—ÚØÊ“=ç1÷’¼ÜøÃv­[‹8Ó"f/Yõ„kU«õºŽ‚b{\!cêÐê¶…Œo¦‰ï‡ÊcËBí¡VÖÐ,Y«­g–ô”(V)¿pN™Ê±·½”<éŽó¯1ùF*˜¥N'¹)^§€¼˜ „NÓzEWÉ‚‡±è¦lyN 9?™ÜÓ:/šrëDÛ;gnšð6L=eKÇxoÖ'ŠqÃr†½ùÅö>›ÕãORã–jhÔùQÇ‚›Màþ´.¼Ën…X¬AD „yýˆÇ?|‚¯âs»nFÙÅ$õû7ˆºôóÈM¤P…¾?Ú„Ûÿ|xâ²ûý{˜S2í)‘ÀÈ4ç2üÔ‚ye†¤ÏÍ" êa[KðÈüHÊü>ÆÀî»RþØ×aY^µfÊaè'T“ù¹`‚3bKR&Œ ãòÝú?‰G8ã“ìV‰ôècMǿ畩eØ|dÃYMîÁjË`ìË KÅÛÜãqOMØ¢þÊ»Nk_È(î3ŒÍrVNÿ9JCvV[ò‰ã²3œ̤ÐÔ|‰¯Ín¬ÈfuM£nÊEÐàƒ¤ ŸªÄ‡+%r?#ýPuP%¼‹Ÿ’^Œoo'Vgj·ME7纛d׉۪©213#@c2GΌ֖@MrŸx‡}å‘#Ö ÅWøÌ ¹ Ó§©ìÈ5ê#9”²cËHã±w÷,<Â{‘´NȆqhË-ïÏb²Ú®q‚?mø^¼ ‰ßB£ ®  qš³‡kËÓ{;ØrB¿F»ä§Ï1k‰eÂ'ÿ}óè•RÑÔy%“±-ŸtK´—Êú»î€Q—ÎT*‚ÑH°êß™…}q‘íWÁjí/³Nz×x¨A@jú Vâÿ[þ_‚`rs'Þuxjƻ쵾2S ë6ÿg<,îNá¨üÏöy0Ïoy’^WFÌÅÖ HhÍu¦Ü-Q¸ôÉ ßÔÀÖžX†Ì x:äÙÆ¿óGºrfˆ0»o/E±ØŒ ²òÞHKYÀŽÐ+…'H°3A+QzÒèiÝ–ë4ž£1¾éÆqdPœMĤðœ‡å™Rê]ïÅÉ9œ½yTÈD[¾fÈ‹Ì ¤ß#Š0ã.ëÊÏ“Ö]¬#ŸË·ÕSI¨ÔÛ×Ԉ㠋©ávŽq6’ô/Ï“±a –?¦5†ç”wJˆáôÙ"aV} ªÚe<Ûdù»Eœ3g¹¤ÅrÌd²1ŒŸoë©î`_¯NçŸ-„ÃPѬ7â=œÑٜʮ?ÈB~ÖtËØë*™ËöÓE˜N?í`h¨'ÐXhHÿeÚ¦•ïöN¥,Z³?òèTx&Uxð T Eù¾ˆ_H.<¡nk|N²òlÞÇ^Ïû¿ Ô¿3NVûŠ.Ãs !O–jIÛ ÙÐI×Rë"&õŽI“!îüw®Rúª¨ÿ°µ27AÉgÝh +!RB%xþ´fݵ2'†õ?©û×F ªbMÄoEI£×þ|Uöm㥄kЦæ&À–Â1<£ÌϫʤÆÛ„?ê  ÛÊ……žóSd>ÑúåxÛàЩCéöü<Ȧ:P§úŠýÓ ÊJÅlJZl_)9›1JÂr‰?ÏC22ƒq#§å¤p\®úèÞèÔ7¢ÌéñÓ\‡ºäŸßX´Áò)»o²ðГoväô®m ÍQ3·Y%"§›ÜܱÆÿ!K]‚ñg_'ª«È¨j 6Ý6«¼#ÎÚegôžQsé „u&Ëåë†Ð›m¸ìº¡ã²é@M­#þJÞ ¸B2|COQDL}¦Ú*“,r­ÐÑÈ{h×\oÁ¸˜Íö€…{8ª¦wÞ‘æ6Ê»_k¿íª£SΤˆûQ'2u#F¡;6}…tïÁºB“œd±àŠ ¿´ìÕ-†7âf® ²3¶g§ûDÑ»fiLb÷²:8µ¿î([DÀˆ’Š ðàMôÐ)Z¡.*‰&Ãëoçljšžœ•›„eq„¸Þ@ðR‰#ŒöbO=އ’Ð?éo»Ûašú'GÁÞËZ ±U~ÌØŽÒ8xó¤Ä¶nVFDË”L0m|óТl¨ïs1i¹ì­;³9Õ°¥bÍOÌè‚èŠïú“ÀV"š)Y. ye:?Ë}.Á‚‚VCÞcp˜çLp`Øš²8ëÖî «JózÓFìÙö F Ü™ 2 ,ím­ÓïD¯E5ÝÎ ~µf«·"–.àÚJ0ñÝozõ:]|aœÕ½µ_j Ð<-a¬CÎõá?kÑÔþvN? šÝ^°cøAuX[Lò·Âm>K9: ´¨ç–ØHÅÛbµgÇúC}ê¸Mzl£žTæxZ ÈCé^:S(kÒ¼Öá&ÐQ sãÜ,e€êšëønL#W‚l RS\Û/U…mG’`´2q¸ìá<žÀ=ÎÿT‚Ù© Ç(}‰.œê¯¬©æk¥–x-“‰11¥^ï„1ªÜ•ážÛÉÔ/sœ}‚;@ùß+öñLwJrÙãHp o–½/d¨H0C"ð(‘ÁÃI.sì-±ÒeÈž¦ÛLÇ\Qô2g‚2tÞ‹È'üÛÀÑeIˆ­óü4ËøU_𤿅}´±}¯¿ðºøX·ÛHÿïü4GøPüÿ½ü+/øTš¾ÖûS»í´Öûk»í£ÿB÷þ}´oá^ ?þßÂ…¾Ô‚ûO¿áN¯ð¥¿ƒßð´?…Ëü@êaFvra¿›"_Õr)(áÐgfÞ£ÿ.û†y}Ä ùQbÌáâ”ÑͱMðO™†…€*¼ÍWÉÌ/…ÎTI6¼Ô,t½7œ~¹nÁ‡ö¿i#˜/Ö’S‚Èž7“ø°½aQí÷È$Òý¡õ%hÁÖÛb+=¼%*rN3T´ç[г¬8Éû,뢵6êß5Àð\j㪠Âp1®Õ$ðXÙ힌?áG¤iŒ‰¥¬`©VŠ•¼uÒ 5gkî@…Í„¬’ƒÄ ­¬é©ŸN²á™X ”…}}!-ºÝSù´à›*ùœWáàõø™â¬û‹þ{zöD} :¥Mý0lVü+áM •‚2ò<¶Äè= BC­¡ÖæºC ,yHܾNæ©âÃ'D¦ßß¹%wšk¤+¦µ"’µYÑúË^!­å͸¢7‘ãöo.Íy$â*RE°cw@úÙ¶a¾Ä{¤Ì©È±%ù€¢°l¼×Ç÷¾òÊ©OÉ}ŸIÀRݨ’Q)KÞÃoø•ýÈ\­ýJ õ‚+ð!K!mõÕËë?íj‹ÑgìD {£+O,¾y€ŠºþWÝlz?ΞdmPãx°yÌaŽ{ò1HüœH'7åÜÏZ³ÎùÚ§$Õ6”VlËL1†•ALjLÜ Áû¾1z(.õj<ÎnÕ ßQ®óWÖEèi;0hÆ~‹°°åŸ:ˆzu /¶ïØ ô ÚøsÅ´Ø÷CòT³#˦?PÿáÖJoTÍ\m®5”i\Ó­ïÿíµ§HQbéþÖ!ö¼Pkƒ$Òë>×ØÊ³í7uD¡ƒXF€|ÆŸQj¹5ߢ†g·$$E];ŒmÌì 2ÀEëœÍé+>™±nÕ òx m7)MÆ*ØæfpWÊnZÄ~ÌI"d<E¨ä9R¸öáæºpR$nv·ÿI|›$T;ïp済ÚÎ^µó‚þsø¨[¢$üáèwë«sá ‰Ä ©–käˆÖßÒÄ\Z[9!½ÌÎ;ó'žä–Dÿí <{½ìbü$H¦¾+nëY@êqH1· ÔÚÕ—© üÛOZ6ü; 3n åZd::&švêªÔ?ÅPG&=º‚!D¬doc"!þL«·747]†»™!' à;fv±$®N:Šë‹Š%áÀ§”ôžï¾£G}‹ û«‚}Ì)¾H‰¹<¤¤íU¢KþÄ®ÞJl&tûœQ%Žò|]tÝ3^ÐT½šT8Sˆ"7-J/Û% h…œíøûà¿Éúh—Â[ž­—º_η pÚ^d:7iÒÛz’øF~¥Nøk†ìNüí+÷¿|Š ‹KŠ3GƒŽ:{én-rCFb>5ŒËTSnK¶·Êp%<KvåûaÏ­[ ç°ÍžŠuN6‡ñƒ0 I6U˜áÐiÿJ˜Ô÷°å¥aXà .‚-s˜_Ãh}ÎTŸƒ8®¢æþƒ¿˜âPÄrÞ~9×ÛÚìcG‹]QdX„âmë²'ëã‘1’ŸõZl¡dæð##•ÒϨÉlËêÑ_E÷&ˆ9‰§OFqǤŒ‚Ó:³5²àÃ_¨Hx³ÓeëÛÊD Ie¢ÌT dz\#ÈHxúmÞ¤¥õ?6‹œFsæÀõŒ }_Ï9Öáè®ÿ\Ð:tèJÄBô] ™â²¸xT™ÆäŠ^Tv“ÂôÜ•mg×®ùkgݯÌIÖ¹0ô—G•/ß<)C]˜•( =£ˆˆ!"Þ;TÐ. ØC*—­÷³Öp³»îh=œT€ýÖzÓuЀIT}£ÂTÍm(- /ÿ@»F­¨¿öz“ln|û¼ï¼ã2äî[Ì>s‚ ¢TÌt”‹Ü@ñŠ"¾ú§á¼x×*¡ˆl5:j³ç˜ãÅ6#ðø/÷_ÃÛû×®¯ø'&‘7Èz®}j{ÚzÆWºS*5@=A˾ä.²˜ùwÉ|³ì5f‡};PÏì{ð>]ÂÁrhv'Å »u >y¼ e–6Îìo*š\Ø7™#M Ê2r—ìE€²ÿ³ô€öL|¸iŽèdŒm”Õq‚~ïm( ¾R’þÍa5G ±j¼euµÊ—ÿ~’âœ4LÕËT웪Fî¿3“ÈAd¨i!2%ùÿgñÜÓ0(ÿ& Å®íòÚ¡¥ñ]œÓ0À ‡¯Œ„ ›fʦ/®‹K/ œLZà‹êàü({†¸5ø°QûâÐi—ð?dcÎ ‹[].gs#Ö,ÏXKû¦nhç–âZúãÖÕ"žŠ?7•“*Äj5¶‘+©3´Ó@˜WöXWžó`æG7Æ82Àçšmû ºt‘°læÿ@Çh½9æ?‡ÉJ<²¢D^ŒÈéÐECl´_u¿úÌá¿êEÔB7 4n¨`Ð*Þ´B~ë¯*ôé+šEYRÙo›æ¶)tÚ/-SæqµÞ2jµk.¦ñï1«3…z,Ulù@ÅZó¥]¤¬‚@$òÀt‚iNzÍRZófaå¿÷gß¼itªª6$×<·ýJãq{ciÇ ¨å s×+oéÿUY긦y(g׸³¨Ë0÷²wÙ,öÓ‰b+ÂŽuA4=šÿ7¼ôÓ‰‚Çà¤"¼‘ýp'ò~„·{ ¯Gð4ýhÐ ]‚ÞÙ\WcÝï´~èãÁôµÊœtžœfÌa–‹ÑšÊ1¤n}#Ïýsþ4Y¹:;î—‰t|TË‹ÙÔ`&v·ÈSêZ.Û#­ûÁ H3QÃ0Gà¢ìš9ÛÁ?÷ãgßKѬ8‰¥Y¿&ÓŒ¡|ºa?ÑÏMÜPj¹×oÓYžd5†©å b•‰À©üGTðÆ[%÷¢“6î>Oø’¬fî¢Õ@? –/ =EÁñ‹x@Ü૟þÆ€v–Ëãh H´‹dK{{¦ð`u/c·zâÚߢLvM”Û‚JÛg\*h¯A÷ Œð´œºáílP^¾G Ïäã|6¼'Ø2Hbš3}ñ©lƒOåu¿hÒ·ƒÅlg‘T¹`V7ˆªæõöÍ%àåZk²‚¼ÔmßÀÕEåÇYè¹_uM‹È÷ Ö|£lÁ%@á§ãe±÷ Hî »J'¢ãL&•à77çúxATj&†_ô¤|v=uA÷â–GDúÈÔÌGRÄùí–r7Ëmϵ{@¹í/‹­séìJ¤ÝÌ!'YÜÓ£À˜FÍHMަ>Qõf'±q$ã|ƈ¢oß,š7„%_7'2š~Û%4«°NÎŽä7ÈÓ¿õ+,Ã4MfgJ¬mVmp/ƒ Tì3°ÎïŽY[/6ƒ>õÅAi'¶Nbšù‘|Ã@®¤ÔäVxKa7YGÖ[L!NW€ŠyÅÆ](¤ñùÑÈa÷Ñ•ÉË 1ã’9‹ßf˜læ\»¾:W"ßçàWàB¯Ð1¦ð²lœ¥Ñ¿ýR¿=d(°¯æ\“\|ÚǾ!Ê—rD=h‚-qCÿÿsn úPᆈûç•ppù$è4uøìž+–˜`ÞG7ú(2û¯°›¨tkî¶vi•ùʈý S&'#¡”F_,=ÇÕÖᬾF´À^þBœµÐ,ÿ^˜õ$'o ›¡ƒÅù!‰4H¶;‚¯ˆ(þB‹Þ¶edÍ·‰@Áíñž«¨:f¬ëîEa1Nä7>$œ uæ»Rœýÿ!aï…™Ó*}H'«ªiA•ÓœhíÃoÿüADѳì¸A$«3Ÿþ@«?ed;잊‹7÷OîÛ38*ý-Ñú°ËÊ>¸ŒHús“‰ ’'0Xýtµ5Ò'}ym¶ø®å/G8•ø‰ð…HdŽøL&¥{Bb„Ëežä+.̈—^÷ŒŸ'pn ï3é‡^¬"ó5ô•p=q½%¯4K" *ÓBº]µÈvxÓøåI‰Û¤Û¢Êí€Ý«“@?÷²©àr{Ç# vú‹¶ó¸­E‹EUcCÝÝ0îº\Y³SáS]XEV¯{@ jÚ—Üó÷QØ´ÀmÀ„9ÏØ»Š‰cï$UõŽAýz‰»Þ‡|m UÙ)£¼ ~à]jnðæßl¾È¼ºñˆjQX mý/A¼<$Á‚W2H:QoÖ"§¡(çüÍȲÄ‹,;Ò\uŠh-$FÙ- <øá†MÂ( Uç¸Hþ¹Ä¥=2”…·–ß+‡–Ö ¾þž4Óc ;ÂÅ3„7Äë* ë˜9øÿ\ŽûaGÐ?;éð7Ë÷6™ýROµj 4sMúSçûjS¶Þ¥SÔ:Ñ:z`©mßb£¥rh¦•Ñ-©înå½/GàõPáåõÆnîŠÉëL:ÈŠ“tñ; B=Üãæ…þ—~ûBs(¡²sp—‡Í¾†Tù‡26ž„çÊM—{£ú}ÀKË ~Ê 4XøÂÎ;V“8fR°S»QnPBf<ª*µÆ?¤*•=D ¥™œÈ{EH‘5ÇüË0¥Y©´Ý¾?Ù Û"IƒZSŒ¸töìüG‚8½Ja±"ÁÏŒ>Paóf9!°»@\L—œi…Œ£_ÓÛ&Ψ¼é2µŽ†s´¼õÖ4w\nxð{ð à9ó9¼.j‚'=Ýf?È2—ÈhµŸÑÖ0:„¡>;‰1¡’²jë (^˜Gœ¼U¶[í"óŠOò-mCeÌQ¾¬0Î×”¤w…:ªVñ82dHôÄED·.¨Cg;ÑlCoè|-¯ÿs’è-u\ÇS,ÿJŒíÐÿa¶¯¿˜eÕyXðx2»¹€ÙЯ.y30Ð3¨fý`© ‰Væé¹¬í_/ð™—dÑ=ú+Õ`û­ˆ+¨^w,T]Äa¥ùh±È1¼ÏÓ³j^’²;t¼k”êm€ôͼýdÁÅLPòö‹qÚšx0KéâÒ÷¦|‡&æÈÝ{m¿žÙç¯nɵ¼šc‘¥woâ“ÿze»^WùSð=Ì¥B=~ˆX5šÆÀ{™J„zõ‡`¿×O£`=Ì¥B/榣µWŒ,öÏ,˜ÌÔÛˆ÷XYÓÑasÆ(2äyFq ´Ð(›©²Òų€Í6þHé¯qZÇi©¶–r†.ÖµáÙãñö>Ì—yÈRJFÒ‘'ë6ùÍÌ ˆç{˪…ÿ~ÎaU\§>­£lÝÎU¤[Dtk,xž*qÝmѬ±â/Î;­¢:5–7äÉï1?ƒØDü}ˆÙ_”×\ˆ|zéн A†0I%À¯žƒ‰õ'µ’=rÒ?÷3ûãC`͘¾ìâÓæKÇ´¢oˆñ,Ÿ(ÖǵLª“À/³0zÕ`oÿo ´x¾/•â䌳Ì›WÐ’JŽ}'fð··lp¥™øÂ–?‹>ñ–Dè™ èМ{; |­ÆrÛ#O ÿyd~hæaÃç}}gËÐo[·¸åŠb§t½õ»{ŽX ë¦ Þ·oqÇÑ ÜvŠ)áMÉ«™cƒ×º¹ÂY%úk†Ÿçtìqq¢ÆS.{`ØÓç¥ÈIÍÕRTmÛþ‘ LÄdŒ|Ð;¶`iܵ¤ÐžŸ¹\žÆ+)±!w/äŽëâpÈÄnh-ì~\DW£Ø°Srp˜ýTa½?˜dßýž¿wa|¨åÀgà÷¨Oë™—&ºrùì1&ï˜`ùÀ–æJLMrk©Pt¥Óïm˜N:!½ eÒëO Rà,p Nv·úò߸­7˜”=¶·kàTø|´“ÓÏD_ͬ§†5F\eùëšþ¬Žë1™NtÛLp-ý ˆ9Sôþ4c{èÿÿ~ýWé‘ {úÛ’Šäéf©°g ŒMõM„P“‰¾©¯ö4ÝJ‚‚µŒ³lžaQšøªÍÿFo÷w[—ÚÙ8“ªÿ0ÔIRñ7W"B‡RÚåaz^W#Šp÷ÂyT!’tßP„)~jDz½â܈x;Bo_ÔùZº,òÚñË×JnE`tÌMÈÌÅGÿ}±½õë`} y÷]Ù~ãôMq »£KÕàÙ˜yz4½M\ÄÌ ¼½]ˆw2…”J¥†Ç6Þ-ud–­ÖruY§ fßU»rzÛÅÆç =£ÂÑ>\7‘ÝŠwä„—ÜUGF±ÈðJ|x„ã¥ßô­|v+âš—F4POù,·ÿ}d•7(göü$¿ÿOür SÜ¥û 0€bDN?-`1!çÑÙ5ÎW¡;®MýãžÔ+dkïâ)#\fiœªDK™›Ñ×Ñ hvʰñ,7HØõ¿Ð}D ³·¡M‰ôÂNgS“=|<@Ð^y´ZŽ>ªéé?u¤ž)˜GÃrÿ„]„‚"*€Â±1½Óæ¹åm©ÇÕíô.¥ó1¾ßjáf)ç`ºúk‡S¦Û• „+.ÿþ¶æŸòˆ§eMÃÊÈèVvC•R€Ô „ Eņ®ÂHjt¤œ5aqa«°’Äœ5aqa«°ë¾ßÈá!iǯ¹„»Î¤w°?ࣨޚqN˜Œ> /Ø–Íóu³£‹—ü4’.O|`O*«yfpdxaËÔ`û…lêuÀã½8Y[-UI¶´Ø‚5U¹Ù ¾­ 6ñ`x{ÒîÜAi¹ß$x”P¹›†ß•ìPÐO¶Í]ÑyÛýf}o5§¯}@Í—ÜxÖ)…µ¦Æ4ŸÿÿG–)ll \߯X¸Ì_)Ÿku"v‹ûµœG½›"E”ëäI¶¦z5ígïfÈ‘ezÝ]JÞ/nÖqöl‰Pv]ÒI‚<3hxøËãü€ë`¯¬=MèÇÓåPO-§ˆ»Ï9‘ -&Ó1FùìßBQÄ)“؃TtÌÖ\ñãó€eJb½„ÿy“´@OÏÿC JÐô¯’0GæH¿ÿÙfheroes2-1.0.12+dfsg/src/resources/fheroes2.ico000066400000000000000000002175411456075706000212650ustar00rootroot00000000000000 hF  ¨®00 ¨%V cäþ:(     5#WmtE€šÅNލÉ*h}q &+C2b}¥7Við;YhÿW‹¨ÿBp’òG¥¦Q_0 ,G9c‚½@^qý9WcÿQˆ™ÿMuÿ/^„ÿY®ÿCœÃ_p0@ºÛ 2/Os²1Pjÿl¦¶ÿ:eoÿ"*ÿ *SÿGƒ¥ÿCoÿ:`ÿX™¯± ^pj}:W%:Qø%;ÿ^ž´ÿ9Xÿ'Eÿ)>ÿc•«ÿN©ÿg´ÿc“¯û=©l¢Åè]n 4&?^Ìÿ%<ÿb¦Äÿ4mœÿArÿ+8Aÿm—£ÿAY`ÿ2Mÿ:gÿI…£È$©Â˜´&@b0Fô ÿ"=ÿ:qžÿ~´Ðÿ4W„ÿD\iÿPv€ÿÿ.Hÿ7lÿ=jõE§ÂCF¢¾/N"3ÿÿ'@ÿ?mÿlšµÿVƒ¡ÿWsÿYu€ÿ'+1ÿ"4Nÿ6cÿ.MoÿNšµ{‘©1S± #Hÿ (Rÿ&?ÿ"$'ÿ:KRÿm ²ÿ‘±Ãÿ^…ªÿ-S„ÿ&:Vÿÿ"(/ÿL‡©ñÿ ,/RÉ Gÿ +\ÿ&@ÿ#"!ÿ+,-ÿl…ÿ§ÅÙÿi‰ë;—¶6&Y 5ï !ÿ#8ô,Jê-Qø#Bcÿ\‹¡ÿÿ!ÿ+BNÿ-Jaÿ9]ÿ "Dÿ.Qpí>˜¹9#U.MÝ-N–-QA0X)8aP*Js£Ge…ê#9Rô.LmÉ9\ƒŽ;k’l;t™€>mÁIx˜å7¯9!*2Q,+"O;8c@#IvNLn:z]£¿ ~§-ƒ¨`"†ª(ðàÀ€€( @   8> AGZfz•« }™´ Qem B* "<?>Pa$t“®N’¯êj™µþu¤ÀÿVš·î)…£´M`e 6*>!J]|•Dˆ©ècЦÿKYhÿ 7Fÿ}µÌÿcŠ©ÿl”²ÿQ¹ìƒšž/8L->(9Y%jÀY‰©üReyÿ!ÿÿ1^qÿÎàÿFoÿ3\ÿXu–ÿl¥Àý5–°Ê Q`a!*? /Fc2m‘ÓVy˜ÿ.7Dÿÿÿ7fyÿ™ÓãÿG•µÿ-[ÿ!Oÿ$Z€ÿ’ÅÚÿr¢¿ÿD¸ßdvp">%>b6fÚQt”ÿ/Nbÿ!8BÿDhvÿ¸ËÿÐáÿ:|ÿ 5^ÿ -^ÿ 6eÿl°Éÿ]¦ÁÿCmÿg‰¨ÿK ºåctj;,V2ZƒÑKhŠÿ%K_ÿÏÝÿ˜ØçÿÄÔÿ@‚”ÿ7Aÿ ÿ %Lÿ ._ÿ0iÿÂ×ÿMyÿ#Rÿ1Yÿc€˜ÿD›µÛ VfP0F#IpµKm”ÿ+Kÿ *9ÿÇ×ÿU­¿ÿ .4ÿ ÿÿÿ 'Oÿ .`ÿA| ÿe©Äÿ 1bÿ ,\ÿ+Vÿ2Pbÿ}ªÃÿ5”­ÁBN(‰¢= /Oƒ?dŠø'mÿ>uÿ3gÿ7eÿg­ÿ4£¿“ÿ¬ÀƒšD4ZÅ+B^ÿÿ ÿÿÿ5eÿD}ÿD~ÿJˆ±ÿËïøÿeÃÿ/T‚ÿ*17ÿ‚¨²ÿež­ÿ*,-ÿ**)ÿ%%$ÿ#'+ÿBqÿD}ÿ9oÿ 0`ÿVt•ÿJ¤¿ÈŸ¾œ¸$V!?fä/Fÿ ÿÿÿ'ÿ0Uÿ;fÿ ?jÿ(Oxÿ€¶ÉÿŠÎãÿ0[}ÿ+3<ÿz›¦ÿ˜ÁÎÿ/9Bÿ-28ÿ).4ÿ%,6ÿ Gÿ¤ÏÛÿf Âÿ'O…ÿ(Nƒÿ'Nƒÿ"H{ÿCvÿ=lÿ +ÿÿÿ ÿ#LSÿ}·Éÿ^ºê ˆ©*² T5Wä -ÿÿÿÿ ÿ ÿ #Eÿ,Vÿ/[ÿ8fÿŒ¥ÿ•Õãÿ«ÍÝÿ\˜µñ‰ª5² ^5Wî #ÿÿÿÿÿÿ9ÿ$Qÿ -\ÿ2dÿBvÿ…½Õÿ·âïÿ¹ÞéÿÎíõÿ´ãïÿ£áîÿœÌÙÿMq~ÿRu‚ÿ}¹Ìÿ‚ÃØÿ‹ÊÝÿÈÛÿ^ªÿ+PrÿUŒ«ö®?²#e3Vó ÿÿÿÿÿÿ(ÿ4ÿ<ÿ "HÿGoÿ¨ÜêÿQ®ÿ+?Lÿdù!>eï#Blñ'Isý)Irÿ7]ÿ9ZÿŸÒáÿ>7DWn‰Žv’œ HYb@>8 +>@>@;M]o"‡¦ÝC­ûG¬þ'Š©ç{™¦ Nbd A<) 6A@>1@Vn &€£èR‘¯þ€¥Àÿ‡¤Àÿ²ÏÿŠ«Åÿ\›·ÿ+’³í€ §DT\?5;A>E UsƒzžÝI‹ªþyž»ÿx¨ÿCO]ÿ,=ÿm°Éÿ½Ôÿw•´ÿ‰ªÅÿX›¸ÿ'•µå€˜!'J;=@>+;Vh¶2z÷n–´ÿx‘¬ÿ>L[ÿ ÿÿ*@ÿŽÌÞÿh¯Èÿ .Wÿ>X|ÿ‚›¹ÿ‚¨ÂÿDž¸û—³Ã Q`a=$>@>:RifÕJ£ÿ|›¸ÿTg{ÿÿÿÿ%ÿL–±ÿÃî÷ÿ8‘²ÿ&SÿJÿ-VÿUo’ÿŒ¬Èÿa ºÿ&œ¹áq†{@$>@?B`xiŽäZ…§ÿm‡¦ÿ-7Dÿÿÿÿ"4ÿCЦÿÈíöÿtÌáÿ Tÿ$Qÿ%Qÿ!MÿT|ÿ˜ÉÝÿœ¾Øÿx§Àÿ1¹ðšB!;@? Bc*fëe‰¬ÿZpŒÿ ÿÿÿ 1ÿ#a}ÿz¸ÍÿÃî÷ÿrÅÚÿhÿ .^ÿ *Zÿ'Wÿ/^ÿW¥Áÿ´êôÿ1ƒ§ÿh‚¤ÿ„ªÄÿ:ž¹ö‡ š@ 6A>4Xz.\‡í^†©ÿJe‚ÿ>Uÿ J[ÿ!FWÿCuˆÿ•ÉÚÿ¼êõÿ¤ãñÿF¢ºÿ ?Pÿ'Lÿ 0aÿ 0`ÿ .^ÿj“ÿ©âïÿm¿ÖÿŸ¹ö™‘ 8 (A> ,Kn'U€ç`‚§ÿAWqÿ=Rÿ“ËÝÿÇìöÿÃêôÿ¾îùÿˆÔæÿ<˜±ÿZlÿ!ÿÿ $Hÿ 1aÿ /_ÿ ?1W#Iw×VzŸÿFeŠÿ!ÿWlÿÈî÷ÿ‘àðÿ>²Ëÿ'€“ÿ CQÿÿÿÿÿ $Hÿ 1aÿ .^ÿQ}ÿ£ÝëÿA¯ÿ /`ÿ -]ÿ(Xÿ%Rÿ1ÿS_mÿ§Àÿ,œ·ä Zkb3@E?j¶FhÿSsšÿ #Gÿ ÿ$2ÿ]¨Áÿ½îøÿ+Šÿ ÿ ÿ ÿ ÿÿÿ %Iÿ 1bÿ ._ÿN{ÿžÚéÿC’³ÿ 1aÿ 0`ÿ ,]ÿ,\ÿ0Nÿ.TaÿŒ³Íÿn¥¾ÿ–±É6@=@>.Pƒ+U|øY{¡ÿ1Sÿ;ÿ&ÿ ÿ]zÿ²æóÿK ´ÿ ÿÿÿÿÿÿ,Qÿ4fÿ1aÿ >lÿŒËÝÿZ©Äÿ/\ÿ 1^ÿ Rÿ$НÿtÇÞÿ©äòÿÓéòÿ¨À×ÿNŸºþ‡¡—5?%SBjÙPs˜ÿ2F]ÿ ÿÿ*ÿ %Iÿ(„¤ÿ½îøÿN­Çÿ4Zÿ-Uÿ1Yÿ2[ÿ1Yÿ-Nÿ*ÿ*ÿ'ÿDZÿ¦ÚéÿS©Áÿ?TÿuÿfÄÜÿ³ëöÿÑóúÿÎïöÿ§ÙæÿhŸ¾ÿ„¨Ãÿ/œ·é j}Oÿÿÿ@>1V˜1Y€ÿJgŠÿ ÿÿÿ 2ÿ 8jÿV³ÏÿÝøüÿG°ÎÿBwÿ=vÿC|ÿD}ÿD~ÿ $I/MwþC`‚ÿ ÿÿ ÿÿ 8ÿ 1dÿd‘ÿƒ×êÿ¼î÷ÿ,‰²ÿB{ÿD|ÿE}ÿJƒÿ!Dsÿ!$)ÿ"! ÿ,2ÿŽÀÏÿ{ÇÙÿAPÿ;Fÿ.9ÿÿÿ'=ÿ7lÿ -^ÿ&Sÿ#Nÿj‚¢ÿb¤½ÿš¶– FS @H/XËAa‰ÿ&7Kÿÿÿÿÿ!9ÿ3gÿ=qÿF­ËÿÉóúÿ?­ÍÿMƒÿE}ÿ Kƒÿ#O‡ÿ(Jxÿ%(,ÿ$#"ÿ"7Aÿ›ÏÞÿf´Èÿ,3ÿÿÿÿÿ)@ÿ=uÿ4iÿ /`ÿ$Qÿ4Mrÿ„¨Áÿ,Ÿ»Û™µ –±0>2i >fòDd‰ÿÿÿ ÿÿÿ!:ÿ4iÿ7mÿ.вÿÃï÷ÿ™ãòÿGŸÅÿ(XŽÿ%O‡ÿ+Tÿ+Lzÿ%(-ÿ$""ÿ(L\ÿ®àîÿW¤¹ÿ!*.ÿ$$#ÿ"""ÿÿÿ)@ÿBzÿ8mÿ4gÿ /`ÿ0\ÿ|•³ÿM »û¤ÃZ²Ó :>#G›2Qxÿ5Mkÿÿÿÿÿÿ#=ÿ5jÿÿ,++ÿ+++ÿ&&&ÿ$$$ÿ$#"ÿ!3JÿF~ÿD|ÿ?vÿ5iÿ 1bÿ7aÿ™µÿE »ö£ÂEªÈ.>2}">eü3Osÿ ÿÿÿÿÿÿ&@ÿ>vÿE~ÿE~ÿI‚ÿ$Uÿ[ªËÿÞöûÿ–Ùíÿ9u¦ÿ3Wƒÿ-03ÿ*58ÿœÂÎÿ Ûëÿ7MVÿ+*)ÿ,,+ÿ+**ÿ%%$ÿ$#!ÿ"4LÿH‚ÿE~ÿC|ÿ8nÿ2eÿ/[ÿhÿ`£½ÿ£Áx"Àä5?7>> -Ktÿ%dÿ)Dkÿ*X{ÿ„ÄÖÿ¸îøÿ8³ÿ1Jhÿ.4;ÿ,=Fÿ”¿ÌÿÈíøÿGetÿ,06ÿ-29ÿ-28ÿ(-4ÿ%)0ÿ#3Gÿ I(LÓ2Uÿ $Iÿ<ÿ Kÿ&Uÿ .^ÿ 0aÿ4hÿ'Aÿÿ!!!ÿ$$$ÿ%%%ÿ+++ÿ,**ÿ)IYÿ¦Øçÿ„Òãÿ1R^ÿ=eŠÿ½áðÿìúýÿ†¹Öÿ7d˜ÿ4`•ÿ4`•ÿ1\’ÿ,Uÿ,UŽÿ'@`ÿ#""ÿÿÿÿÿÿ!ÿ}•¬ÿ=Ÿ»ê£Á-¢À?W-Rç.Rÿ=ÿ=ÿ Kÿ&Tÿ .^ÿ 0`ÿ3fÿ'Aÿÿ"""ÿ$$$ÿ%%%ÿ+++ÿ,++ÿ(5<ÿ…»Ìÿ¸îùÿH”¬ÿ[‘°ÿèöûÿÒêõÿXƒ¯ÿ2^“ÿ4`”ÿ4`”ÿ0Yÿ,TŒÿ,UŽÿ'?`ÿ##"ÿÿÿÿÿÿÿqƒ—ÿNŸ»ú ¿NªÊ!> h5Yô(Kxÿ7ÿ>ÿ Lÿ&Tÿ .^ÿ 0`ÿ3eÿ'Aÿÿ"""ÿ$$$ÿ%%%ÿ+++ÿ,,,ÿ++,ÿFqÿÄèñÿºêöÿ¹æòÿþþýÿ½ËÚÿ$~"=aý!Bmÿ4ÿ=ÿ Lÿ&Tÿ -]ÿ 0`ÿ2eÿ&@ÿÿ!!!ÿ$$$ÿ%%%ÿ+++ÿ,,,ÿ,,+ÿ*/2ÿHl{ÿÁÜçÿûýþÿåðõÿr°ÿ2^“ÿ4`”ÿ4`”ÿ0Zÿ,TŒÿ,TŒÿ+Tÿ%=^ÿ#""ÿÿÿÿÿÿ ÿJUbÿl ¼ÿ”¹6Èý EU*>+“'Ekÿ7`ÿ2ÿ<ÿJÿ%Sÿ +[ÿ 0`ÿ 2dÿ&@ÿÿÿ$$$ÿ$$$ÿ)))ÿ,,,ÿ,,,ÿ,,+ÿ)*,ÿ\ŒœÿåøýÿšÌãÿÿÿÿ$$$ÿ$$$ÿ'''ÿ,,,ÿ,,,ÿ,,,ÿ+))ÿ>cpÿÕñøÿ”Éáÿ8f™ÿ3_“ÿ2\’ÿ.VŽÿ,TŒÿ,TŒÿ+S‹ÿ&P‰ÿ";[ÿÿÿÿÿÿÿÿ*06ÿ{ž·ÿ&“µÂ‹¯ }œ1A4·)Jtÿ #Fÿ/ÿ:ÿFÿ#Oÿ'Uÿ /_ÿ 0aÿ$<ÿÿÿ"""ÿ$$$ÿ$$$ÿ)))ÿ,,,ÿ,,,ÿ*))ÿGpÿØóúÿ‚ÀÜÿ2^“ÿ.XŽÿ-Uÿ,TŒÿ,TŒÿ+S‹ÿ$O‡ÿJƒÿ6Wÿÿÿÿÿÿÿÿ $ÿz˜±ÿ.‘²Ô޳‡©5D 7Æ)Kvÿ>ÿ.ÿ5ÿAÿ"Nÿ$Qÿ)Xÿ /`ÿ$<ÿÿÿÿ$$$ÿ$$$ÿ%%%ÿ***ÿ,++ÿ*:@ÿ’½ÊÿÐóüÿZ˜Àÿ,SŒÿ,TŒÿ,TŒÿ,TŒÿ+TŒÿ%PˆÿIÿFÿ3Tÿÿÿÿÿÿÿ ÿ2:ÿxŸ»ÿ6®â Œ¯!Š« 8I <Ó(Jvÿ7ÿ/ÿ4ÿ:ÿEÿ#Oÿ&Sÿ)[ÿ":ÿÿÿÿ! ÿ$$#ÿ$#"ÿ%%$ÿ'&&ÿFp€ÿÏðøÿ„Éâÿ6d™ÿ,S‹ÿ,Tÿ,TŒÿ,TŒÿ&P‰ÿJƒÿE~ÿEÿ3Tÿÿÿÿÿÿ ÿ9>ÿH¥¹ÿ‰³Îÿ=®ì ‹®.¯ : N$AÜ&Gqÿÿÿ ÿ ÿ ÿ"ÿ+ÿ 3ÿ1ÿ 4ÿ#7ÿ#9ÿ&<ÿ/Gÿ!2Iÿ!1Hÿ3Kÿu§ºÿÍôýÿL–¹ÿ$Mwÿ$P}ÿ$Isÿ"Myÿ!Kuÿ :]ÿ 7Wÿ5Vÿ2Sÿ+Dÿ#9ÿ$:ÿ$:ÿ 5ÿ #7ÿGWÿ_¶ÇÿÕóúÿ©ÀÖÿB®ó ‹­;²;T(Eä$Cjÿÿÿÿÿÿÿÿÿ *ÿ)Zÿ /`ÿ 2cÿ3gÿ9oÿAyÿC|ÿ [Žÿ­Úèÿìøüÿ¡Ôäÿ|»Îÿ|½Òÿg«Âÿj³ÊÿnµËÿ3l‚ÿ$'ÿÿÿ,ÿS‚ÿ+‚«ÿ2µÿ&Œ²ÿ7¤ÄÿsÑæÿÏõüÿ°×ãÿwŸ½ÿI’¯ø‹­E“µ;_+Jî"@fÿÿÿÿÿÿÿÿÿ ÿIÿ&Tÿ ,\ÿ 0aÿ 2cÿ6jÿ@wÿO›½ÿØôúÿÃíøÿÞöýÿìüÿÿñýÿÿåûÿÿÎõýÿÔ÷ýÿ·âíÿ8mÿ3?ÿ:Fÿ6i}ÿ‡Ìßÿ±èóÿ³èóÿ¹îøÿÆñùÿ¹áíÿr«ÃÿLqÿXr’ÿN”²üŒ®S–º;k*Iö=aÿÿÿÿÿÿÿÿÿÿ6ÿ;ÿEÿ 'Rÿ .[ÿ /_ÿHwÿ˜ÑãÿÁîøÿT›¾ÿ[Šÿ}²Âÿ‰¸Éÿp¬¿ÿA“§ÿDªÀÿ¯éöÿÉìõÿ§Öäÿ³Ýéÿ¼éôÿ‚ÀÕÿ3tšÿ'dŒÿEƒ¥ÿ;yœÿJsÿ%Qÿ;ÿPhˆÿS”±ý¯W–º<n-M÷7Zÿÿÿÿÿÿÿÿÿÿ2ÿ2ÿ4ÿ7ÿ>ÿ%IÿLuÿ¬Ýëÿ¡áðÿX‚ÿ%ÿ!#ÿ $&ÿ "ÿÿ+0ÿ){ŒÿY¸ËÿuÊÝÿnÆÙÿ;Žªÿ>iÿ&Tÿ#Rÿ"Pÿ MÿIÿBÿ9ÿLd„ÿT•³ÿ”·c˜¼=n+K÷3Vÿÿÿÿÿÿÿÿÿ ÿ1ÿ-ÿ,ÿ.ÿ1ÿ2ÿ >bÿ¯ÝêÿÕèÿHrÿ#ÿÿÿÿÿÿÿ-1ÿ5=ÿ.4ÿ)ÿ "Eÿ JÿGÿDÿAÿ=ÿ:ÿ4ÿE^ÿX—´ÿ‘µc”¹<v*Jú3Vÿÿÿÿÿ&=ÿ9Zÿ&Ejÿ,Ltÿ-Oyÿ,Q~ÿ'Iuÿ9aÿ #Dÿ/ÿ )ÿ 5Uÿ°ÝéÿlÇÝÿ2Uÿ ÿÿÿÿÿÿÿÿÿÿ!ÿ%:ÿ =ÿ:ÿ8ÿ6ÿ5ÿ3ÿ+ÿE[{ÿU’°ÿ‰¯c´;|/Oü1Pÿÿ!ÿ7Zÿ+P{ÿ+Luÿ"?dÿ6Yü1Tö0Wñ9_ù%Dkÿ1R|ÿ:^‹ÿ1Sÿ3Zÿ 0Rÿ¢ÏÝÿ©àïÿ>[ÿÿÿÿÿ ÿ"ÿ*7FÿReÿÿÿ ÿ"2Eÿ=VvÿTs˜ÿY{¢ÿLs›ÿHl”ÿ?j“û2m”ô>q—ø^‹E eW gyi‘¯#q˜æ?~¢ýd°ÿq¯ÿz”´ÿT®ÿ‡®c‹³ 9d'Cñ7Wÿ 1Ú &b % #2CB K$ )Us2[Ð'Djü@aŠÿHj”ÿ:]…ÿ$NvúDnÔAo BrPAt :k2X8O– ƒ´KkZ j’*sœpt›Ê9{žûq–µÿSªÿˆ¯c‹³!R.ð,¸ $.P %P!'S +Wn3^Ó8dè4c¤;hV:g+R4`dˆÀ l“r›l xÙ*ƒ¥ÿŒ³cŒ´E %3)œ & ' :)V *Y- (U .`' a‚6ÿÿp˜)ƒª¢…«S‰±&% '*€¤w™z€¥ÿþÿÿÿüÿÿÿø?ÿÿàÿÿÀÿÿ€ÿÿ€ÿÿÿþÿüÿüÿøøðð?à?ààÀÀÀÀ€€€€€€€ÿÀüÿøÿƒÇÿþ?ÿãÏÿÿÿÿó‰PNG  IHDR\r¨f€IDATxÚìýi°eW–߇ýÖÞû w~÷9O@b( ªÔÐ…êªîf³z ¤¦8³ÕR°)[9lQvH¡/¶²–b8´¤h’Ã4eQ"d˜dw³Ù]]U]]¬© …P@™È9óÍïÝùœ³÷ö‡sÎ}÷M™oJÕ‘™/ï;÷ ûì½öþë¿àcùX>–åcùX>–åcùX>–åñr±§+„W4>ì{ùX>–?¦¢þ£s*ø?ýµ™& G:ÁQ¯,ùå>aórƒg^žù°ÇâcùXþøÈûÏñoÿ«^¼üŸÃ補çÈ àý.ªá§ðkšÿëÝç~öÔ‡=,ËÇòÇBäüÝÞ¹?ù¹Ê_Ÿiéÿe`äàr¢#+`Jàóbàg«†û÷0ó©ö‡=6ËÇòGZ‚_øLüêW>[ùß7«êgäH†ÿ–˜c|×O¨œ¸fø.Ö°¯¯ñ_ªÍò×>ìqúX>–?R¢*¡LÿÏÿõƯ}újøoδÍçD£íû[r°M¦êÿÑ…úF—¿\ÒwÌ>–Ljf+xd9¢ù±–½E?Õ6—§bõÌÙ¦ùå^ân,õý×ïlf×kZ½½ù±Þþˆˆúì3áÕŸùTü§ž=oþ©†ú\¥ªDv8îS õìñïN7jYÿ_ÿ•C]àD@!q øsÿÇ•„çSmî~ì|täç.ÕšÓù“¡öÿa å-4g*zt¶Éݹšþ/Z‘¾ùé3•ï ¾ßˆtú÷^[ÿ°oùüõo†Å k:ö©O] þF»®~NkbmÙÃé¯WÔÕ—ž ëÀúa¯u0´É­.°Fn.>R”ð³Mþ2â¯~–·¿½üáêÇBx¶¡/MÅü»Õ€_ ”\-í}­¤bàÊSíðÿ2²þpóæzöwûÝîWÉ'ÕÇ®Á'òÆ{£3/^ÿÜÕyý¯7"yU)b*ÂX±_ÐÏñ =NLÙRX®àÜ•Hó?xcÿ¸ÁÇ1CÂó-sùéió¯ÌWõ_Œ¼$BÅ"‚”RJ Y1òà姦ƒ/,Ôõשÿê‹£ñÞZz ÈàèþæÇòHQóSzêËŸŒ>ûÂùà×N·Ô¯Í ”òÝߣ$_ˆÞ3ü_È£Àð ²õÒ°PüV')Á/~6þÜÏ¿ÿ;§§õ/G,P¤èEAXŒQ¬oT¹ÕáTË0W»‰Èc ñÇÊ~ `†­Å¿s‘{`Hv~Iâ:„Uè®à]~s’Ç~n.âUÍßj†gž ¨©XµÏ4ôå…ºþÒ|Uÿ¥½>ä³K™€aæ ”&R€KÙ‰(!®ê‹íX}áÌŒùõAêßúÌý훫?VǘÏ>^.ƒ|­šú“/@@kðY·nÍò†Jëw6_yî¡ô8"x,{) œaïÅ¿ó¸ñï•6Ì\¼ŠTjlÞ»I¶¾H6•÷§”pªÈüÜšæ¯wRþe¨YßHøXŽ)gšNâÛWÚæó[æ/5"ù%#Ò¡ºûhÖ!Y4Å=Wg¤5ˆ!ð g‚.‘í¡l‚÷Žòå‰x´vJ+¦ê‘¼Zä§æê*WçÍ·o®ÙßZéùwÞ¼oop€øÐgùÔ•€‡k®ýåOF?õÊÕðOµê³­šzYnÙ–x‚ `£ßà{×jÜÚ¬ñ'¾ð"Ï_\àÿóÏ¿uQÇΘ}>kðøso3ö½Ò82žŠnrmjš¬Ò ÿ:6N~'·b^¨þãë]þ>ž0Ç’¿üb³~w3i7BùóíŠúk¡–Ëì¹-xD†A‹Uia%âêé*/]hÑI…k:\ zš ]¦tB”uÑ.!Œ2t0±Á{Q5U7òj½á~j®!¿ÞOý·êBýÿ¹Ø‘oœmTWÿëyïãð„ü§¥Å%k¦êR5–íâ‚ùkÕH. ˆgûbS‰¯òƒ÷ÏðÆí€³§Nó?ûåOóÌéV6qÎáý_ûˆ²Ÿ phÅâg4q¾>âL¼Ä÷› O]Eo> Y[š¼W%°iþ½gš<»™ò{0àöQ®ùÇ\‚åéêϤYöNÕÕÙJ ¾¨…6{Ì ¯C² ÁŠ´a¸:ó™ËÓÌ6b<Øsyz†ÍËMn¯ôx{¥É­Õ.:˜cVo²®àé£|Z¼$•_F9§4L5"ùŠ ’Ï¿ŸeÙµÏ_Òÿ}oÄú÷íû|ì"–/µêì•Óæ¯Ö«òR ¥M±øK s1·ÖÚ¼v·Zü…Ÿ–O?sj½Š÷ Jc­=rÔ§ì¥êàñ¹f"£U±üÔé.?^©q38 ALºöŸ&ãé©…OkájÝ0;óß­'|7q¬óÇ{¢øÒB[ý•K§ÌSFSgG ŽÎ¬l¶øþ½º¶Ég_¸Ì—^? òˆ¯à}B%òò<´‚Œ×ÔÕHWïáz›“{T5Öü•PñKÍ€ßXMø;«#¾ÃÇÂI@U ÍK-ýù Mó—š‘ú¥8Rm%RÝ}°Ç©N¼À* ´R<¿óÉs f" ïýž;ˆ÷¹"×3uÃl½ÅËçë¬uGüèn—Û&wm¦‚ŒSîU3DüßiÂV•P"ój;ªþÔéföë}×ó§Ÿ ßXëû·n®¸¯__r×—:~£øÊ7Pæ¥+Áå?ù™ÊŸ:;«??ÝP_LkEuç ïcÖºmÞ¼_çn¯Î3—ÏókŸ>Ï\«Ž ¼6(½½Òw<Ú±Õèq‘€;ãx!>Ä£@à©¶'ІüáJL?| »|‹lsurÊ(%œŽ5u> üwk ÿ¬“rƒ=2 ŒDætM._né?ÕŠÔç«üldÔ)­E©]¹TÊ0š¬©úªÆå)Ã.ÄÌ·kˆRùß±òËÿïD˜ùÂ2æT»Ê©v•î0ãÞúˆ×ïõ¹Þ» ½sqF‹5*fnˆ›˜‚F@i“N5ŒýbõÅ™šê_j«Õ—Ïê¯Y«—Þ_Mkdíƒ7ï÷o.wÇàOŠu äÁpõêóáų³úê§®„¿8ÓTŸ›jè—¦R÷NSß»õþ4oÞoðþZÄ™…þÒ«OóÌÙY4¹¯ÃÑ»Ëü½ó'6@GV‘¦¦ 6¾)ïóÈ$ ¾Š—!Nuq¶Þg*¬òÚr…‡êyY‰TDrm9¹X*`¶Xr †ªÊ¹v…¯\lr¾h)þöëxï±iB: € c”1(½{J”ß­G†gO\™¯³ÑOxw¥Í÷:Ü4©$}NU=u¿H(#´4aîåê„2f¬|P”©.4̯¹³SòW2—ö^>ÛúÆZß}s}}õÖêhôƒ»Ý÷øˆ¾÷.Ö»~úù æÊsçÌ—ç¦ÔóŠzaº¥?…ª©j¯1ñ€aµSçÇ÷æywY17ÝâÏ|ù2/^>M8gq"(•[j{…ù¶‚€Ç—£*{¾ÊŸŽ4zûÇ… #¼ o¯ñ2¤%|îô:·7š¼¡f¨F¶þ´³º} b_h>ÝøüTÈßÕšo½µÆÆ!îï'JþÕgªôx¨V´esh/œªªÿm¨åivõ””ä¦:¦gZô|…DU8;ßà¥suηB#¸ý~2bÐﳸºÎk×éXÅsçæ¸4Ñn5ÑAˆ6;q§ž|÷É]„€ÓÓu~ê©6÷7S®ÝßäÝÅIR§¢†Ì‡Ð¦K¬#’å;–Wà "#”Q´ Û•:¿:[ãW<~p~*úÇçÛÑÿëìT¨ÿö7îÿÞô‹ÏVî¬ FÓµ0ù›_}÷?ÿÙ_bq͆WN›ðßM¦ž9k^yëf½ú|øˆC9«­…8RÅo}Wðˆ‰yÐisý¡áÖj•Óó ü¹Ÿ=Å'Ÿ>Kœó¸ÜÑ/¬5O6¢ƒ†ÛÞ…sîèØßrTà}¾ûGåJ%Þ+nÇ8Z 8´‚ËíUf#Çëk-î›sèJt} › Ø:h¡bQ ŸMÿt¾ÂßÛLxhYãN Põ “-ÌVä/hÜÏj¸x®®¯ŠPgbñ+%h•ÿkM…^8Ë*u”R<;òÂé³SuB Îç¶½0çÈÒ„Ñ`ÀÒÚ&oÞZæG7–¹¾Øa%þÅry:â¹sS¼ti–ùé&S:²U ”F)E=Ô<®Îç.ƒNÆÛw׸¹–p«аMZ¦Ã\ÔAlFàC‘r~÷ˆP¤¶Ð u®ü¹Qæ¯ÿû_™þv¬-$~ôw£j3ûsŸ;ýýøû÷Š/—ðô“~'%ÆE¿ú|xéï ›í†¾(ÞýÚsgu2Ó”¯Ì2R%TWDµë$Þ6G nm4¸¹TasñÌù9þ­W¯põô¡bká—ßS ï,Þ¦ŒÒŒ0ŒˆE»ïòäĪUTA!^º@€øœ+ÔË/ÙÖÞЬvy5Œ¸iÞZ,×Ú°¾ˆí¬@– $W@(ž3Â…sUþò0â7×þþʈo,›üdù‹¥ ¡´æ«re¶"_j„îÕXñ© åïü‚Ũ2M—˜¡iÅU^šy~.¢Ò­v/|g-éhÈh8`säøñý¯½s—•¥eœ-wfO8â{#ÞzÐáwßZæÒ\OœŸá“ÚÌMÕ©Wc” óì µFk=Ž€§i®Æš«s§éŒ,ë›]~|¿ÃÃÞ4¯oôÐ& Å§#P®O@~·?›¥j"^iƼ"jà^ªÈÏáú–)ýÛÿ«Ÿ;·áT:zw)ý |¸”Zç_¿»ñþÃÍÑúÎacKAhö›éWŸ/™V-ŒRäò‚þâB[}Ây\-–OÖ"yÍT«"j›?/ tŽØñx§pÖ°ÚiñÎR›;݈v½Á OÍñò3gY˜ª!"8ç±{lc6’Žú¬löùú÷øÙOçln³¼÷'6ëOFxða•0¬£d€“M ÈÇÛk”¯ç÷«ˆWÅq¥ 󵌷×7ÌiÍYÜÊÔ`{ý°UjÕðWcÍ/OGüÞÐòíå!ÿl=áý<ó®E_ø|ÅÈ—µbZÈ{F)´R_Ê{ƬÆgè©*³øÌBÈÅ™ÕPé ¨íÁ"g-ÙhÈ`ÐÇ™7}™°yžwßþ-–>|„éYíôYí xíæ*¿=U癳mž;7Í'ζ˜nT¨DYšÛƒ*î9/Rɪ‡Šú\‹ ó-‰eµ;âÝ¥>·V"Þx|6â\4bÚ¯Áød|?åÜ :¼2>j{¯™kÚ_£MÓâßò^¼ó~ðÊ…Ö×¼÷oˆ»Œ†•á[÷’ß•,)-耹çNÍý²²ÍŸß¯ »^‡«Ô*䋼˜CZIu¯¨ý何ÿ ŒÂè€$i°¾‹›÷6cÝæôìþ³—xúT‹j.™ß#ëp.e8±¶ÙåµëËüînðpq‘Ï?Õ&KG_Ï“Œh8Í·Ñ¥ B€òU<g3zC¡ç2fƒh0”oåâ¡h^žõœf¼³ix¨Îã{5¤»‚KvÕ)%œ‰5ÿF¤ù35Ã_<íøN'å·×VFÜ$×ø…à‘ºJóQ‹~|°Èxñ 1!S§Í’HÈB3äÕ…˜ó-C¼÷8ëòB¥¥pÖæ;~2" ؤÊôùyé§¿À¹§žâÆÍ._ýöÜú‘ÁgË´z¬µíF…Ñ sÙ¶€µ–“ʘ=þð.QJÓ2#Œêx…M4oÜ^ãG†|ùbó³5¢¸Š˜Š ò ‰p¦ê˜7cÏëÁÃÚ z°†Û\ÂïVTÅŪš_ÏG„aâX«k£§Ë|UH,Ó"Ì_i©ŸnG|%6ò¥½ýij` ·ÇUQ›.‰Š9;Óà…9ö´ÂùÝ>#@:’&C’Ño"§®òéŸùs\yþ"­¦Ã&ŽVeÈT» q 鮸™¼s$£×oßçý{‹|ýG1OŸ[à çBž??ÃôT“jD1A¢”Ú#­˜OY-žË3!Ïœ®óÙ‹ z‰çí{›¬vú¼us‰4©õSêÊp¶=KÌ4U†ˆJÐT0:Úã.1ÄŒr+Dœ2šiQ„!ø›Uñ6Â;2TÐ…=—R¹Ð=ÅÏ!C+ F–¹¿Üa“iƒ§f§xå…S¼pa†™v›J¨ÆÏìöØ©Kq6#K¬­­óæ{wùÚ÷®ñÆõ{¸~DhЦbo³ò–¶O˜’ À“›Ó‡T¨úZFxÄá¼ãawÄ;‹)÷76øÒå„Ož©Óª˜ Rñq1X <5¥8Ýô¼ß7ÜØ˜£7aØÁw–pÉœÝ9J„©@xÕ(>}¶Æi%4úÿ bxmÄ×ñ’©¬ ý¨HòÕÛÙaë "€zyÞœž©È+K}ÏUå×C%gBÍK5a+StT•¡©cÂgfž™ ˜®ÇŠ|áï1™l–‘ $ÉV9ýÒ/òâÏþiæÎ^¤Vo@ÿ=²Ñ}ð1•P`I`2Ör`qÖÒïõøáÛ×yû=E£óü…yÎ6 /]žca¦E«^Å„ºp&•Ò¹»â¼'ÐB»*¼zµ÷m¾üü<½~ÂwÖè 2Þº¿N: ¨Tj„é}µ óq•Ší¡D*ò¹÷§Â¢‡()+óT‰ |€àQ’áñ^¡£ ´€—‰ÍUC+¤™EQe=ipw-!Mt¤A¿7ba¦ÁùKg™nóüùZ•€jœ£õØÃÄÏçwnæ»,a8ìsiƒ¼qkïÝäÍôy…¦ÊçEè3ª~T–c=‰9 ìV1‘ß?œ(𠯆àCœ °6׆‘ã·¯õyg9å3çë\‹©×qaŽósuꕈz5&KLæ‚1c ¡ ÚâÑ›Õ<<Ó¬ðòSy€r£Ÿ`­euïÝ[#MxçèZÃR†ýý~'O3ÊÖîî½òÔe¥R¡RYÀIÀT- 'ÄÁyO½Ñä -¦" JQ‰«4*­4ÆÄ¹úÞgÁ+ïòEomÊ`8be­Ãë7WùöëïqÿÞ6WW,ÍïíïAiƒGS?Bž)hŒŸdœÞ.Ã$åõ{›¬mvùÌÙ*ϵ4ëÆä)k-F™"Q yzNs¾ =Ëõ ÅÍêúéit: í¬bë¸,-L½íW–ÜM˜0Â+Æð @ÅÐo‡ô'õ0˜‹ùZ¨TÇ)¯PÉ'Åsœ"¨0`JëG5V)oâÁÄ ¢YÒ¨ÅÐiª•ˆ‹µŒçæã<ªèqÝù½cÏÎYÒ4¡×rg¥Çµ÷¨~é*Ì©EªÈ=Oìn„Êñ¶÷\wÙ¼‹u[1¥|ñìc­ei½ËÒz—VùÝ¥Y‹yæì,—Z\^hq~¶F³„Á6wa2Ø5VvëþZÕÖ9†|òòø¼m”9FÒшd4Úsæ‰Îq)a”_ODˆè<†•_:_•®T¢…†˜ÂÛ Jíq.-°£”N§ÇÝ¥MÞ¸³ÉÛ×n°ôð!IgœË)¶Š¤=@îJå9Æ}G¥< Vàbd Ð>÷mçæ¦åa¯Ã{«#^¼\ç©¶¥WÇ»‚Ò[þ£óh˜k…Ì6=Ïwû†›kÚ%:ëkDFðÝ”Ka¸¹‹žl§È»¶•PýZ Ô¾_ ‚<ï»Ïƒ‚ *XѸÆ<Ë#…Ôf˜>?g8;Ó¬(¥s+iŸõ—ïöi2¢ßpóî"oÜòpµK§7âT1Æ ¶•ˆ{$°É-\ºˆw=zK.ÝÉ-¦òyÇëɘ™Þ9V×7Y]ßäÖý”Öœ™ŸåÂéY^¾2Ï™–áÂLz% C‚0Bi½ï¤Q(­·+J¥ˆCE,@5 /h}ì•Êw.¢0&ž°¬r…Y.xk²4Á¦ ë›®=ìóöû÷yoiÀòÒCë« \Žo)· MþÞ•ÎïÁî¨ÌÜ9†¹ÿvòJàÉ)@”ÇÎÈ -üðaÂ{ë¼07ä•s N·câ,B!am3ò&LU4SÏÕ)ÃÆ`ÄíF…=ج]"Í<®»Lj’µhñøt„Ÿ\iûŒ¥Q*çÉÛï™Äšbç,Þ‹˜ë.n2Ôu†¦QžV¬xa>àt+àL= Ô‚6aQ•·×ò-M† G#Ö7:ܼ¿Æ­ÅwV$©C)5T#C Qm ž™$irøô!>ÛÀ{‹K,﮲¶ºˆø­’'‰¢’Ó."¹’*ø×6;¬w{üøæª‘áâé9.5=/]lS«Ä´š5Õ†˜ Ï*” ¡¬Œ1åšX»'ûD"B–ðÞ‚wEªÕa³„~ÄÆÆ÷Wº\¿q‹‡ëÞ½µÈp0 Q(<¦0wÞÛxZË–Bpœ›o/¸:óÔ\•«sUYJ†˜(ÜM.¯ha®0ßȃR½nnx†ÓsÜÞôx]AE1ý•‡ˆxêõ:ig•´»žï4ŤôÞáÓÁcöì¹$Hb Ê“e5Ú4ý^]›Â›g-¦Cbíxz&¤äf&’“9ˆ(&;;”&¤µÞ{ÒdD¯?`qyß^áÖRŸÞ(#syÐ.0[º+ „¸ª«mTå4[@7ÁÙ—.ã²U}²¾ãþ½uÖ †&£  Â n*åb×ZcL>µ&þNÉÒ„dÄk›=^WÂW߸‡VŠ‹suζ+^3{`}hùÎí¯ÝryºÇçÎU8?Scª^!Œ _Qow½Kë-Њ¶†éJQýä8³Øu,Õg1F³™îKŒ¯ÌV*„QD¦B†ƒ½ÅÛ(—¡w¶^)yñÜY*µƧô:¬hÎLÕ95Ó@‰ã©™€ZT¥¢JåpOXï1Z¡uPÜoŽärÎbmŠÍ,ýaÂÚú&÷7xq“ÅýÄaŠ[ õöH±˜n+š-Mµ©m›jb;¸tk—pv…QGXºû€´“å>¯°oôù ¢”B)Eã…Á#ƒ˜;E&u›ƒ<³³Ú[áû7 þÖC*¡„+ MN·"Lñô™iêm´ÉÝ…F5¦^Ƹˆ='×¶ÿzzÔQ–gKr ¼u¸,ãÎÒ&÷×û€Ç:áÝ»+l¬,â†Ý.Ù°—·9J@ƒ×[×r¼äÿNxe[[¤· uDˆìñ¾(*( ò„?ìV3°7»Ìã$KSÖ’€Ô4 å¦q Q>=Òí'™åíÅ>·Öæj=^9òÌ©:S*q¡M€ ônÀ [~]-È:ÅÓ3Åyg¸9r-³–Å̳ÙudÍSÅÍÄ®µu~­µØ±Y¢ Q‘ ÒQ>y óz2Jœ/üÒ”ôyNØZɈn§Ïíõ n=±¸¼Éf?ÅúíZØì˜Ó¹²ƒ™MXU˜ §¶}œÝ6½K–>×£³¹ÎÒê ©óG^ô“æ¼1† Æ»½snüç¨0Õq`²ˆõGýax–7Å1B«ç Îç5 Êž=;ÃB3(¬,Ù‚'{Oh4qµ†x‡ø­¨Ûí{‹Yzo‡à3V6îñ`y™Ìr(¯,ú ‚`¼à•RèQÐrÁå….[Jà8˜u™økÒùÛèv øƒå ?{ëx´Oeje*ƒeÄÛ|wµ.³»ç(¼(¢ò¸lâ†yÿåøÿ Š0Mê p9ßMªBœW¤*çÑ>÷—õ>1t3‘:=®œX ÀŽúdY®¥•Ÿ*ÞÏÉ¡îFÖs«ã¹óNàæ€3óŠO4ª,L…œªT™ªÇ„Qn*£³€ó)âœGk…GÆÁÅñ1ÇT®¾Ð:ÎåA£á`ÄÚf›Ö¹»ÒåÁæˆaæÁå/ZKŽS Øë!2P­(ÂÈ £YdȺØÑ{¸l ïd£Ö–zltÏ´VšõaŽ~¹è5¦¥ °5~[ À=‚¿ÎZ»íøƒÈVºnÇ ŒÏ±ïõÞ“é ™ó 6œÂø íD{BÕC»ãâ(p7‘Y)CåGJö.½~何[΃q)KðS'tC´}´å|RNÁ‘ÀN—ÞÑK¼F¼àŸßâ¬gÔ·Üxßr[ob4œmD<Õ¸ºP£‡4*!µj”kJ¥Š˜ÁvuZšø[ d­Z~7/бy0/M F¬oxÿ᛽wV R‹-} @™Ã¿L¨U Ù¢8B›i¶ÕÖû.ÎvòŸÝÁ ËêRBšnùB> ¥QƘ-øÇߣŒÿUj¸D©4¬µcâ$EDðE4<èDb\1^¡ke]ªé&ÊgxQHÉ–ÊöLrùóÎÏliÖ á ºLI7°2›+,…'p#†ºBP¬¡éÙ3Ép 9ªµDÞ©h?Ð]gDÊWŽv'GÌz2 ï­ ¹±:ä÷oö0J8Û2Ì7+LŊ˳¢0"Š­jŒ=¾;mNë"*ïóI¯vúW;íC¿µ»›Í,I’1JSúýwWºŒ’”¥!7G ’ ßQµã<­;žtÅ÷Ъ+*5ET‰Q4vܧŸ€·8;ÈKƒ3³¹Z6Œ¢@ÓÒSALp‚fåa¤¼f™5pÎ嬷‡$>J< ½eÆn \F소TÄšnaȨÙ™ÒôÃ6¡‚(¼Ëí ìx“ÿ–?OÆhÊœ¾}D`R´äÙ[[¤þ— ¼#QVí…Ø ˜2Lt4«õ¨ @áýÛJèB®¤HÅ|XüÎC/ÍG}cÑòæâ£„j¸ @kJsµÝÀLDø OÍÄÔ+aε‡ ÌVV_ ™ €p F£”Å•Ã$ÍÁ!–6‡,w¬õtGë ³^ ¿q¦éP"…¥0ù}ê±! ÷ßY½w¸lïòÅdŒP‹5Ó©GˆŠL²ÞüÉ™”Ç•ÒýpÎü÷þXVN†QƒÖhå¡âB»‚xOàRÀ“IþÊФJ“RA»d÷à1ד­ÎºóÑn‚*”@VÐÛhŸah/è}€+*ÿîQu÷‘]Ï£`° Éœgs˜Ç!6dÜ~¸dXÍjˆÑû?Ê\èXˆ·€3i”×4tV³m0N™Hùè‰B›£.,=‘§ßëåŠä.W2âè°Ý0 UAtQ¢ 5e˜oWuš›ÄâA¡XŠÚ¦””±ˆÒ-(•ÁaÅ“§ÜjÉ:Êe ‚&à†E”õi—/h<â-Ê[B„L4·‹Ÿ^‰`$¯Èq?eÐÏïÐú-÷`¯oßu©ì]†"AíbL*,)]Èchï'ŠüÈÉŽæ€õÞ£ lvâ±(Å"ÍkCP UŠKö½”å¹)ÓÕ²ÒaŠg’]Ù#¦M?M6ºÞWF4§FÔkÂF'Ë?0’DBðö£cì“B䈼ìHJ Ï(jG4’U”·(ŸØâ=^V Æ'ãX¨Ì4ÑilŠVë,J„@)œ(p`múHrŽò=šÂ"ÈìîøÙä±jb=®XHèpxä õ‰*€Ç|Ңȃ5~ð½Æâñç.#Âú€‹¾£ }Qœa“5ðnkûPºú"ÁðxKLJ£=¢V룤7ž°±º¼Ðå£.e`r2FpPqJ3k— ô”1}U'ÊzL æ%Ò~Ë:ô¢HtŒSšÈ¥„ÞÒšà:íàDÓ‹ÚXeGȨs Çw/p.lÇ ”¢d ­¹ó,ùXLhŠcÈÉ‘‚Š`´þ"?9Rjö‚ùøQõR{Š%4b… iêq£eȺ´) L N¡£‹ØôN­R©T*J;Ñ­B}¤©‹Û,nW$Ljhœ³dÙN*¿g˜Ægù³ ÌeKôtŽò §ÈD¢Ô?A-ÝÌ-£"ħ]2¡•ï¨&8eÐ>Ã)E6AØò¨5 ELhÛ]®A¶#ƒ°×† Á¥%ßý?* €¢zïà ~”¥\ðe|à8ïL“×?ˆ 7ÄaÓE¼í"A»8ʃ® ÃyD×]!¬µ†E+OøüÞ>zV[žeDyt•Pa=-Tƒ2ùAif±©'Í óB¿1d¸ÌcS¶±çJá«94V »™ÿJ ;‚ê”%ÀY&”ñGåasñR”g} çr4ä…vâÏ^²3˜ß[þµ»WÏ^HW­4&”yL¹ëäÄ@1ü‘kë÷˜ñ-c¥hµw#[æ``4Ãh@¯çÉ’’¹n*c[úÒ ØòõâjJ}£Hyni]¦Aý‡o ø¼ä‚JM˜šI8½`9ßÈX¨LËÑЊ&eÊçÖØÔÓzzÞ±>±Þµ,u·ï…<|Ðíe„~„R S]–Üö ‰ÜˆKO$/û.ßm–1V‚C\²Õ`•2ˆ'@Hp…–É#ù¥r(ñ"»ží™ƒñ»×%¨­(òJS´Q˜@&ßö‘åäÍÝä¸RÂ1'zRðFŽw’×í¸CQú‹‘"Ò°…6B +Ô¤Ÿ× [A•å|»úPyP[³S²( Ãh‚ø#ñÎ|žÊjLg\º”ðâ¸<«8Ó04¢ˆ0’Üw.lb¥A›|w¶i´+Ó·Éȱ¹i¹}/åÚí!o]ó W=…(Ç”ëÒ—˜†ïà@4Z4Îe9:ÔïV°]@® D M']”¢V@‰.JÅó…:i¨B ìœSe| +Uó(ß„Ød4.> 9a `˹%Å.ø˜`Ç~¶­…íýV ­ŸøýI=e‰ ! k¤a£å©«„&ËëQ€ óÞZJ¼Ï¶ÝƒsÞwÁr_?ô„±ÂlÃðøîšˆ¢R×\yf“Ï<íøôéùV@%¢ª B)ú!RÔo¨¢¸x‚rWÖ‚ÖB:²Ô›•X3[Í8yp7`m£ÏòÈ ¼ÏIQ%$ô ‘ω:Ò"€*~ÿ˜þäT.‡1¶”ÁäïrWkÌå„J1rŽÔ¹¢‹Öîki)H*=4gOVjÛ®¿>t˜¬O îDÞÞNp´¾íL­„|*­­°™œ˜¦ú°DÈsúãÇØ«úë®QbÂÀࣃ°‰¡®Sfe“°è®ä‹²ÃJ†…‰¨[ˆL"0I{dÇ8×ÃûD±ÉÍSÑxÉ ¨Ô‡ØY›€¹Sð¹‡üÜUÃùÓ!Õª CA”UâJ25@ ¨²!¯—ÕàKà\Ò¤G7XÁº>¯.œñÜy?ƒk#R&Òñ1™ hºAÑÏi=6Ñ3çö5ÙKQªdòÝã½~}–mm„F'²õÛ–"ñ…Xf«=¸‹”À'9¾ÿ»À pž#–Bçø{­ a(yÐæ'84ð$tXé6h!g c²¨ÁÈDxÑÌJ–(Ș‘ЍÙ.Fy¢0ÉB4QŒžKn»|‹t "š(¨`ŒÆ› "ŠØQR И€g~á‹_xÙ0ÕÒ„±B‡Q£tL\™&ˆO£Í4*hƒn!¨1é© xo?Dpx<6[&ªÞ$®Ý&®.Æ#ú#¡×îÜMQÆ`¬/R|Ù„%'9$ª`š\¤Rö¾,>ÏÝ?ÿ؈¿R9Tw\íYà2çÐ"Œö@9nEÿó˜ÁV‹pacc4MO¨ø¤,€ñýæ$9?]®¡•s3oÝù·š€Œ­Ö2P,|ÕÉÂ:^iB±4Ù$K Á¦}"!#‰¨¸¼SŽ÷ù Ñ%º¤8ï¶ë™mšd‚M½>C«Õ%-a]^£'rMÛ—ŠÉ¯ÙÂå{Ÿw!l1qUÑçnëÅ£D„(Œyþ”æÏ¾Úçå/(Úóœõ)¬FÓÑYt8‰žB‘è4*hà¤YçSÊj=ñE*Ããp£EÂäqómêSoPm¾ÅúÃE¢ <ûI‡÷÷X)n\–ûyÃ?6Ù#“Wj9·»´­TÖ{¬ÛߟŒY[ðãæÉÂÎŒ~„á\""OÊy;õ_@߆ֳéBf”äV€ÑÍ Ôôƒ’2€«d+¦A„)¿A mrÔ›C1” Uß#vbú”«L´ÉÆ~±g’”/"ÿcbT8ƒè¢+xR¦OU˜žiÑY_#Ͳâž­ óo,FAЪUhÖbªµÚmšÏ›‘dYVt¶µ9ÉZÒÌÒ9FE„÷9¥VÉ`‚€Z ^zÊð¯|jÄó/ Ó§4šçÐAef1ÑLýó˜ÊD·A×ò =&ËûÌZtxí?E¾J4õ€Jë{´ç¿ÁêýïUÖ¨6,7Þ2Üz?c£›1¹ß•DÞouíÕZá•ßÝÅ·xº¨(ͬÛ(²3ðY®hçö¤ö(ë Šol;—R…Ç;;Æ}9Ç0µÞÅPÝ«3PIq8%ã-ÝþõF€Ö‚—2b»Õ"ü‹Lf&1ýq ÐaŒ * ¿†ÁaÌözz…£î:;Κ‹÷&gÁ¥—­á’uô„ãæ½ÂI-5œnâ|JsaÄì©&îÆøá­Â(@+¡…œ?=Í'η9sîç§5³UGXm`‹^1®Xü£~ë\–aÓœ¹¸³ÞånWØìwèlnÐ$<6&Ž@[Î5{\½´Ég_L8wÉPk6©7Ï¡£Y‚ð,&zSU»šïôù“´(vb¨Páp˸¢Rk…."ÁYæ°Î!Z‘YÇ~¶Àd¦`&¾—P·ó½?—íX‚+ö›å?YZù™WâCÏÕ Ö8dg Ä&¤K¤ÓsL†dü×m™Ä”Y£r3? AlDb‰%"! ò†‡‰38ïéöSzà—’ à‡÷ ö©ñÌÑèð>˜GìD×â¯Ö¸ûþ4ëkDaÈ|XãLrj¦ÁŸÿù |öS ¦fZƒQyK-´Áí°â|:× 8‡xµ]Ò͇ØÑ&#;ducH²®XßÐ Ü iÖ{ԚꔦR©50Ñ,& µ§C¦¦s š(Êiػ݄ÕeG¯+ŒF)ýAF·oIw°àª |H9O‚Âz°{•JBé`‰¬ˆÑø™ȬökßzÞîTÐ>Ì ¤x8ñ°“ú·ì\ûGQv"¹Æ4lù÷•@A”w= °>Cã©é ]”%/½žeñNÀ© Žao…,»…ñ)HÙÖÑ#Á *~ •ÞÃû68})åʳO±¾ºI«]ç•Ù:=bž¾pîÙ Z)¼ Ý(vÞ 7uQ(æeÆ>A‚¡’ÿŒ ½A×8§ˆ³€ú)ƒKzdI‡ÔŽŠJ¸*Y–˜€jmÍcÂóDWQõ/"ÁÌEX(‚l™´ ²E`„÷# ÆËÊ4P:@™:ž*lÖƒb›!´0­Ÿ¥]$j}ƒÆÌ×hN›©¹ æÏ¤Ü¹±¾ «Kž^?O©†¡¢>å¨U5¦pú¢b攡Þ4EÏ  5ƒž¥¿áè÷5›ë–{·ï_OYZMr÷‡-7`;ð¸ùvr3Š @MªmãäÁ¾säÀÈѵŽkZ3—£ŸØ“þ9G™õ*Ù‰+—íà É€MŽób£¨£ Þ¢Ç<Ϲy¯D:»P‚­Ê¾ËÝuÇ ß#í_#JWèÌÖT€Ž¯ ‡ïâ²%ÐMª ðôËÓtÖ2N_²4¥êˆ ð¤ø¬‹£‡r QÓhU]Ïc ¢ñ>EÜ‘ðˆÍùùœÝDÉ4(…/Zh‰„ˆ 1nP(¦<Ž ¢ó?ºDTû¦ù%0ÓìÜõ}¶†ë¿ÆpýktW¿Í°·‘ã-ˆÆ&¨"*Ä„ ‚ð4¦rΣƒ³¨ø Ì*º ‹B*O„gÐÕÏTÿÔÛ@kö=æÏwéwR†Øì8ˆbE­ •º¢R ¨·¦¨¶ÎWÏ¡Ì Ë6H†·õVIGô67yêù”+7 ï¿ððAÆêš¥?ÈH2Èóo¹é«I³`bž™¸ñ|}iLà@N¹yT@F–Næy;‚/Ž ,Kú­ ÿÿI´žú EËöjÀ‹¶ô÷#­¨†šÐäØð±ÇPé;v×/ᤓç`ÐstÖbFƒ„tp;xžø¦‡`]Æ&7±vž>Åt¢©µÖÉ÷˜15p#œD(Z(ÝBL̃ŽUËw#ßÅ»QqŸõðÞaL g§pY çzˆDXµŽØÞ r…!¼Eé&:<©¾ŒT?fŠmsÄ;lÿ:£õDwéwY¾Õ‡› º–4A¡´#ˆ¦ 87±Á„ ­a¥Eµy…Zû³„S?‹ŠŸ‰Ò¤þÕê⩯ИýÓ›ßcÐ½Ž³)騗«ÄFM‚Ê qý¢úKèÊU”™Õ C£Üñ+dÉ">¹AÒ}‹Áæ›Ìž}Ÿ+/l°¾”²±f¹ÛróFÂÝû)]çrca0Òí#7‰âó°}.”À“H÷Ð; (Uv˜õ&oì)Ïý¤HiÝìù»bǯŠÈ¨qW¡Ýƒ$£¶J8(Jd[§"¥ŠÔ‘’œÒy:k½ŒÞÆmâæ×ѵç`žñôPR¹ŠÝÀ {xŸÕ"æÉýKUù,OQé¨*:ƒ“&^‡y*ÌF(Ÿ‚MÉ$D"Ÿ3ÈB§tº‚—.]Â¥‹8»†w –*ÆçЕKHåSH0³ý¡½Çuߢ÷ðï°xû«<¼µÌÃ[–›7<‹Ë ½ôÒ Aè˜oZ´™K Ba¶m™joÒh? 5ýSóßböìoÓ\ø‚ú—øRN’ ªèÆgеOMýÕä>>í ü^úàk¨ð*:ze*Eét>¶[mHæ +Ï?M4½A-¹G«ó=F1è¾KÒÈÆêÏßíñþµï¾ñà¾ccÓ2Ù|Q+E:‘NÔAĹ ç‰ÃíD«ÞYÂJRÖ~OöÂôáQ]nwLÒÉ“™%[ã¦?áhÀ"Hîã‡ÅÂ×û7oÐZ0æàA¾r·W¥©_¤êD„P—ä[Šiõ¡°¾$4Û+T›€Ž/Lÿy”.‘ f1ñÓØô6{ˆwC”i¡Â³è°‚WuÄ ±bò¶eTÀÔAÅûõE.ç¿ñ®h ’û@øÀ!þRÙµ¼ËMv—ö ]A$©¢¢ ÎäÑúIñ×}ƒÞâËíw~ƒ[o¯óÖë–ï¿¥ygE³:ªb­-ÒTqÑ"ð ©ÏÇf60UU\šÍ¸r!ãÂ…Ÿû.§†÷hL}‹¸ýKèÆÑáüDG”»•§À;—µ¼ñ„‰ô긫|1.ÛþU §Q•i¢øY¢v—F¶„Þf¦óN]øg.ýˆ§ŸßäþíŒ;7×ÞpÿA†O)[D‚¢43õ€JïÓYËh8 \Z}/Ðf6YÙ¦„Ì$h¤@¼(ªáGŸpâ (¡j¢@;ñþ+;ô8]ô8ÉáÀjœW.¥¬7*W$0Qwô:–‡· íù”ÚÚ ‚ø«èèTãs[ïF¨Ê%ô𽨠ª:ª<] Ìr&ÜüÚ¥JŸŒ¨o¿Ï\ém\ýå}Ów¦ÐÈ/‚ø2ç.ÛvÑÉu½7è<ø¯¸óÎïrëíuþð{ÿä#no–õ‹™Ørºit.?s¬"¤?\R̾ïùôÇÏõS¬»K{n©þMªíµ¾‚©~‚ù2ð+=ñ°“® €Åûï<ªhøâÅc11fb h#Á4¦rÕøñÜMj³ÿ‚ö©ßfþü;œ»ÒáÊsн6⇯% 6 ^iÞ‹BtÞIÄÓf¼w{‘Ÿ¾Ò?J¢~O9ÎÊ”‘å`À1hÞÉt¢Xár;à[Fd?¹~QB5PTÂ<¸÷¸e­‹â”ƒÈ¤©¿×uƒ ÷Aå¡“tæn)Z³!q­K\ý1Aü5Lå¦OÊ`]¹‚–R}*O£ à@¹¨·&ñIÈNÐÌ~EH¹¿˜õÞa¸üßqÿú׸ýî×ÞtüÆk77ó™¾×HîùYñaæ…û}aõºÆZÎ]í2êÿ˜fï!Íþ5âú§Q•+¨ð**¼€‹·~“ÅÛë¬Ü³|÷M͉ͭ.™G¼£¡õ¼¹Yåôú$#Oöl ·Ä »IµñQeЍz– ò<:Z@é6¸0/–‡'Á&w±É]\¶„÷Á{”™Bé Øð :ºˆ2óHx ¯§ò˜Áx JË "h|_!j~žjûŸRký•Ú}ª øíÞåöÃ!ÁüJcÈÒtÛCgi×6W“ˆ¤á ÇÃn‹‘UEöÏ„á#C|TE)…6 4b_”¤î=Ô%Rì A¾ýLýí烰ˆ-PpÀílŵS6×Sn¿3½Ðœ~¸û¢ø©Üþ.îqcÐ#ïp¯Ÿ÷ùŽ·ä>—×d½â[ ‚jÎðQ¨MOYÕfñÞâÓ5lç 6þ+÷þ%÷®/²xÇòúà­Õ qÃá±iÁC;`scÀõ·‚ОIéw‡ÄÕ‡TëyñP¥ö::¨FµqOH¥«yF"Û ®ã¼Ýòb¸‰1UÀ#º6S(3ƒ Ï££óHõ¹ÎlÚø2ð8¦lkc¦~FíÓĵߤRÿ„ñë8«ø­ßìò ·}ìE4ˆGiPº„}CfÞY´oqD=p‚”`ຫØz|õ¨÷ó‘c Q”7 ƒ­ì¾‹U)!ÖEçQ¦þøÚZh!0ú‘­µw‹çámË­·é‡Ô§¾GØú“HtŽÝÁªI)}µrQöQôñ’â³¼§‚˜“Ôáyž'-BúïV°éÎ ]GT^͇©!º†63ˆšÁ©|GTnˆMo‘®Ñ_ÿ.«Þaåa—ÅÛ–7^¾ñ~•M"´‚8ޱ֒eÙ¸Øaı ¼¥Ó…ëo6× Ó õ)G¥æ‰+QeˆÖ˱A›­ŽÅ¢ï¶zxçsî£ {aÙ¶>ïÓÆMÂø&z8ƒî¿ŽÏ¢‚óHtÆ›úE°@8÷«LWŸBéÿ Ïï3:¾úMvmœ"*'u‰Ê@»`­ïÞ¸›~óµÿþlöé¿|÷Ðãsl°mj–-•~B×¾RŠ(Š‚`û£ÖßAòúYø·7£¼f?sÿQ’$–ÛïÄœº”2sê:Õá ttvñØ,ƒä.~t›ÞÆŽ–°é Þ%Øt1¿ÿ RåN®M —õrL‘,M 6H“κ|+1ÚĘ(B©€¨z ]˜ÄÞ%$Ãeºë÷Y[\Ïså+žþþÅÛK6ÚŠ_´àJ)†Ãᱺ¥™ei¹G¿±±ÒœöLÍ Í™”¨êòÚ~ÿktÎ<¤Œà²­ Áœ¶Lr–¢ #¬e³Uê9õ¨¤=¢x,£Ò»(ýf°€ŸEÕ>ƒD “ʃ Ñ/0u!ŸsŸK¾W íz~NÈùð­ð[P{(%þŸ|£¤19–dt3ϵ@˜ƒ‰èôOX¬O)…1†0 Ç-ª"ƨGúú]ø"‚VP‰ÌøúGq›D ³ž±xÛpîéû4‡o¢Ÿ/p/Åvðƒx¿ÉpcÄhñ°ê_2¬%=úÝ>£~nät]š0,;' A’S½”4Iö<£‘Ç&~ÌNU{D±ÆE6HÝ@ÑyM–fô7›+žû·3Þ»©øƒ›1‹Y´OŒEÇ1YQŒtXE ”`]ÎÜÛ %)½nÌæJ@mJQoâ mrž¿\l¿g=ÉÈa3O QESkXj-¡ZWDU…Ò›®‘ŒTk]t¶†2M¬^Fg+D8”ùiÐííJn|žö‡w}ºk×ÀV£QZc ¾› 'ˆùßOŽ•Z:©ãí@ñÅ|ó¶N»iŽJ¼ìGO3„axè…Nñ•Á=}Ó]âP祹GØõwŠsŽÍUèm®3êü€pj©p ñ?¼¼…KW ²ùà5–7H·“a3H ëÉR ¢ˆbW0ÕXL¤PºGÒפ©Çf0ìVô2†ÃÞy‚ `zASmUÀyùj–x¼U8'¬/+Þy×ðƒ;š»ƒˆú‘Æc QJ‘¦écJ„jdè2¬Ëwó$³Ø^ŸþpÈÊš" UÎu0¼VKPšY²ÌáœÇÚ¼þ¿Äö›ÀQ­Y¦gÓ 0=§iÍjM‡M-•Ú  ƒ|‡¶ƒw#•O"ñ©©T®ž©s¿Îôü? ·ÖD›m”ÖxïÐ:B˜zÇš'¥œD `—y–Ížeíá ¾û&ÜÚ0¬d½ 2KefôM„(ÁhC’&Œ2Ï5ë$N”(n ,?ì@ó^L¬ “sëu‡)«6Æ+M’¦ôSÆô^z{‰6Êæ]‚7"`Œ&M3¢@1Lì.nþIzïòƒm¤¯eÙ±'[ç.ʬ§Ûƒ^–-·®;fçS®<›q幈©O®aƒ%DÕPI 7QµçÇ…Uå¹DŤ•K8y§ZæóíÆý5lPg£p›ì¥ºEH!íS–ûŽVµ¤EÎýk-˜Ùù¹KyÜ/ýÁÆe¶ÑŠ(Î[aï„Rí.òL Ù´pc¸e–+%¨d¯¼ýãÁAÛÎ/BÔϳeÐñ¤D T¯[žþÔé³w ìešÅó*$˜1x?À±JëTÈìì<u™ª§´Çï'?>¥²>LŠÿåÕ}§¼Çû¢6¿h¨1fΑ6+bÈlJ£¢hI–#éðøâ8玶Y+X»ÿ¸ ð` ,=?\U¤™.êçM12Ž;äÌHj߯¥øP·¡yÍñìÍ?óÙ”g¯>dötQtRGIÓ~ÇÏì@Gúñø+¥‹–õåûÈr°Ý$X‘£ï'ŠÑµéˆÕV§Úu³ÃüÅQ™¦05EåÝ‚Äàý/)õ9áü…s|òÆ{ zéc«ßõ³÷E&ÄD¤h¬M‹« 8+iŽsìJ“¦ Öf8GAv”g}T~°¢ùƒ»ž‡7qìÁÇ»Ü&² ¬ÍŠ{Ü”¬Á÷zÞXð«ÃŒŸÅ\¢TÆà1Ô >ÃdF‘|ŒÊñ‚œ,Šˆß[§ïßyóz²aíÑ”èÉ‚ôƒ¤~7§ˆ Sʨ¢ ƒ]ÖÛ›}hÊ.9A Eªñõvò¶å¼xyaË®Sº¼~[ùƒ]ì0Z¸Ä<‰Æ¸×ÜžÞ|ÈôðÄW·~k¦ÐáÙè}òOû|“z«A¿ß?ÂB”‰ïäÖ—é+É-Q£ ® ¥Q:§âRœSEÍûóÔXˆÏ`rºöÄ»#€’<ÉÐóÚ÷êâ˜6k•ÛLŸn#ªŠK#\ÿ‡>DªO#fç„ÇeÔ¤8¾ ììÌÙ—Ìx'µWcN6à§T±˜ÔøgÈ«á7B6×zô×ߥ>¼Q(€bM к†së ,Ó#®<{Šîf‡apä{:ø½oYeE®¶F»dãý(H©`“~Üþ}t-¿óM÷›üZÿM>ñ‰ª­iLÐDÔ=œ}Èh´HšÕD™ÜRì Þ¹q—/?½œ“Ä-=€ì¥K ¾ëN†Ã kÙf¾:´%Þã|Þ#}çàèǘõ9âz÷M  ?§ˆ:ˆe­d‹Èb¯çð¼}­u“8Y)wýíâéôRÖ— ›«w˜Úü&ºöR^à‚Ï9¢ èÑœaÝ*¦.\úô ÷ïv¸{ã&Î>yÜFiäŠX¡Ô^J@a?€{9ˆ”µG·J<ýNÆ?ÿº°Ùéðç‡#^~j“ÖLmbâZ— ‰m œ¢\^i–±¶¾‰’“U†{mE y‹°#I¿Ûáæ¦c`÷Y\LtÉÙãFµàöÚý¨šZ¤ÌÁ¿ÑzOÞ²<Ïp"ƒŠˆ<ttx1ÆìK{Ø;÷ª¬ÜïÒ[ù=|ïu¶N‚YTx­gÐjQÓ4b®<šÆÔÔ‰>ÿ£$·ˆ„­ó“¿Ë]ÃÒ©=)™$c9ú9`Ô³|óûšÿÇ?òÛ°Á½«¬-=¤»~?|Ÿºêo CCÞ ±<ÑñɶösŽT°e’Zï·óŠ–¹ÌRìç9©=ùݲÏZ „ƒZÕªÐà% ƶÁ(°ý'›‘¼ºð~P”S",@ß*^»©¹ô¾0sú]jíN¥ò<*(0*@Å—1ٌ΅Hh¸ø‰„åûg  {OÞ(Ó‚»Z›±UPŠ1 ký1Ò…''Zëq‹®#?±’~Æë?Ðln«ƒ.?ÿb ¥úŒz´©¡‚‰åé!Š A´|²–a¯ï¾Ï1‚€ÇvFÇãêa7¤UiE`‚m}èvb?êk”Öƒ!Ö#r¨« ¥±í¼’O0cN~ñC¾S¶Ö`ß1(€CS&ž¯*þå1nöØ\ú&®÷Û¬€p©<‹ 1ˆÄÄíKŸ˜¡ÙžÞÐ}’¢”.òÞyÁ˜pÇï¥èž¤ÆãðaÉÉXy¦c0qã½”ú» ß}§Og-cù^†6 ‚¸²ù) cŒÞêþ“Yß}ýÝô»ÿå¿ß>êm[ÈFÂÊÈñÍñ ŒývŠcM5˜­X¿•ï?ü åÿ%„‡DÞ•â¼ßqo¹ÉoÌ“™èeàè$¤¬Z<ÌäOPüþÍ:ð:÷Þ»Æpýw [elk‰F*Wеg1á¶÷Ûl.1H|U½Œ³(5CX9ueŠæÔÔfäÏY@ǵ£w+2mè?4%Pf]Ž#AŒ‰tûý„ÿ¾u}ÄpGÖCDxçæ}¤wZeóÄêà\€òÇ?yÏp”°Ôw[› [ôÖ°¿¯¿$¼D l+³ 8x‚Éï&דï(rR~ÿqJ…•ÀMáµ7c¾ƒþêoàÓE¶‘Wª^F‡s¹+ ªLŸ X8;GGGºîÑEp.#Ë‚ ÞeA£í–7p\°=0ìÙÜô|ÿ5¸ÿ0ÙÅ3a­ÃèAAÁ–K‰˜ Ž1Åö{‚#©Kt–î±2œÈ˜ÀŒ={kzr$ñNÑ¡ÚÿLv¼Ïû¬—Qÿ  ö=ùYóQXü¥¤Öó‡·n¼íX½÷»$«¿•SxM*ÝÊŠp~DØ´œ:_§Z¯à>·Rcò*¹½‚m¹E¤KÊ Nè=ì”’¡ù82L´Örï}Ï+ºRÛJ£{0Z‡ ¼ßRzJ1ŸçNÂØ&eoõIÀœÑ´ àì­µw~¦…Gæõ‹A8Р•"Ðꉤøö’’帤ķ{Å~TãöÛ‹l.þclÿ­7 Ñi”n ¤¦N¦ÚSèÇÁ*OXrS?(ÆÒìJ–ÏTr3<а5 £'†¼<‰÷»uO·Јž¦V­!j‚‡Q •js[áØòºûêÿývßù‡¿s4:08@–w|Þ£ Z‰vù"ÂB3Glp·Ìþñgl墨Ÿ"Ù5È"Zwþ'½›•¦Ýq']ô;)I½çwàÆ+÷~LÚù¸I+D·Š.½9æ¼Z*õ*:8 AéÉIY§Ô–¹SÊàà~ò$0[÷w|7 ´ó¦:_©¬µ º=¢²q¹C%z9îûž´ôΞšãê¹yt¸÷ž0 ±ãZ$©rL»ëDTâd‰n~ÃŽ`WÕ›š©ªÞåL„xs°?NJ`§hµ‘wÜ¥ø€Rî1ÙD»æƒÊI˜„Oº“Rj=i’’ŒœKwý>oø­Q!¢Ã½£æ@ H¦Òj¬¼ó(£ðø-¡L㟀>(Íec¢ñÎ[vKέ€‰Vì²ûûOB× ß—íf…f„¨-ÞFïÕjH¥ê0Å~í[¯'¿Á1`ÀpB  _ô(ÿ¯µ¦R©Œí°ÒLŒã€Fl[TZvÓ=ªp§ì4õ´R˜ è°`‘’Êì°!Ç`%Ì÷IÚ¼÷Xk Sšvdld®ŸÓ¹éUÕãR㽞QDÂ)Ú§)Q˜0”»îÉsmRZZçhÁ8$E5¡ß÷z'‘ºÛuG' ”*)°¶›µ"p÷áÖh4SÊFÛ¬Kã?l«2âd²ÄòöäY4+ƒ’Äç J©1oñ¨¹/3y2ö§ûržw/ÜÿÅá]€“21?ˆ^ŠZ q­†¦ò8Û:"Dˆß¡”ö)óSB¶“[>÷:˜$¸<Þø¢u©\Je`ÆiÁå¹;ð{.Ú^r\¥’GýósLZ["Âû«˜Á2• ãÈÛÆËq £Nr¦ÏåÒ„õ•EûnìÏ e»+/ð=;Õí3ŽJdÌÄ*€1[в%‚9í®é2ä÷}|óòIþvJha¢t¥¦÷ —ðE`P忳úh•o’» %†À{6'³—™ÉÝv/ÄÊ 8‰w! ó³Ä•*j÷àœ#Ò‚‘íAõmÿQN^Þf0ê¡Õt®ÙŠM*B=Üê|(80l-î Ðä{, }v >Jºà TJHá]„ùÿ¤“â=œ©&ÌÎdDõiTtŽœv"˜âø,ÿƒ`3G–$ã(õaŸ«´1yï@­°Ù±\×âÜ;]=ƒµv<'vd¢ÇR*€“¤;.Qˆ÷ùø|òâ,J´*ø/«ÕêÖµ€NÏ¿ó£w“ {DJõR¥Ó d9hÇ(]úE{£™kÅ„F1̇äå„¢ÝZÜnà^¾X ®8輕B¬·Ï–wç~»Ò^ˆçÒ¼bî D•6bZÛp¤}<ïFXgèõÃm <+;Ó‚Þ烓f*‚%õQzÿ¨ŸOJò8™&0ùš)7²$³<\íОv(½¥4»}÷ú×0zÿë?8:í” BtÿKC¾é<«åq“Ù‰Ž+’·ÙÊyðó7ãØ½00¶å_X;×ÜA[ríû”æÿáÏsœ@жû•ÐpålJT¥gðÔÙîÿƒ÷=Ä{´µåwQ”α.sã]Rí„yžÀÓ•´bP4pyD ·j P´cD„î`Ä»×or¶¹†¨­åX$ÊŽmFX5`êLÞаßåÆZÊ “ØTWÊÄDßÃzñ{ŒóžÌçÝ]÷ÿxL¶Ôƒb‹÷ÈÀw?¶ø¼jjDè*^ï¬÷wxßÃgëX?Äy{hkhÏñÑ æïÞ„fé‰<š÷HÁÜÏx_Ç9§÷žv³Æ¥…6:ˆ·aræ+·­¸îÄîùŸæÆn.‘&ɶ꽜,DSÝB|?·&U0ùîó0{ìÜ{ÁB÷}D â$üÿÂü¥¨KŠrAÄgÐ*d¢%&$]|ÚÁÚ.Þ]pC½àõx÷ ãh· O.Uîüù¿ËÊå:¨xÓ­:æšÛR³Þ9¢(Âh³ÅãäOVèõ"F+ª;ØdD„JЮ…ãþ 1“B,cóïAÜ‹äóƒH©o2=‰´ÔNñZÕ€/>ïX8“Òš½DPy1o²í¸Üû ѪJ·hNGÄqõØ ¦ —çÑFo) 9çÆq€ÇÞËp»ŽSèp«[”Ü[ÚÀ{Kµ¶E‘–œ­p`&ˆÞ0ÁŠf©¿ëËó·AÎÈ[È~í¿vJPìÎû•ÿ–nÆ.ÊðC¼£v:Ž9ûíþ"¼8ïùÒº¼ðÅ«Ì^ú5tíSl_uÔÓB…s¨` ¦iÎ_fæÔ™ã/˜"-˜7‹É§ž s¬€6úJŽ]ñ¬?ϸ<ÔÐLµD•xœÞ»³H”>`ª±<Þ,O ¯œlYúÈûß ²éˆÕŇ<èd;ÒrBlÔ¸v@ 8¹0÷‹èµæŽA=èâ~ìÎë~”&Ò^â¦*†O]Ș¿Ðbîì—‰§~ƃ·D©žAǧ ˜AE—©Î>ËÔ•—‰kÍ'rJ)ljÌ‹…NÕuÒqˆcÝ»6|úòÍJe[ PD¨D‘”ñì>!yw¥DÁ¿¤¸4¦NÚ8Ž[!’7 UjÒŠõxk©7šl%¯Hü Û<.pðŠÀ=°µZ /zÔ+‹9â¢+î~Å<;űEö¨@ýîà!\€a'x’é?OŒ¹ÜRükÏZ>÷âsgŸ¡½ð+˜©ŸíûMoªx¦¡tHV™?sžö'iŸ?yµª&vãØÕv‰þdˆ`‚™Hö#Þy÷:§ÛDmµ_Zµ_ýÛ¿óÎÿ÷wÎTʉfvbÄYn®;úéö<×)¸æµÃ—.+Ä*ûf>"&âaÄ#ÔÍçÏyþÜOùÂ{\¸ô,õ+ÿ6æÜŸ=Í£Vš (€Ž@ò Z½Vãʳ/Ð~æUts"+µ<ÿ]¤‰µ:v½€ÈGþrâ½§U¯pa~j[ p˜Z–Ö{ÌÔVUTm X—3}ý»Ã#_³”“´ç¶c ^€¡Šp~šR(­¨W#fë!·W»`†9g“£ò¨]};Tõðxþ2n!ößô£âCNÜ«Ïd|æÅ—>Ñ`~öKD  =ótXãñ‹×CPSÙÆL37;ËÅO~ÁíÒ¿û&ö‘tJ)¬³(YwÜ÷nâ=:aHðQÄ“§/η¶¥…ÜŠÖjû2MœŒ8!示8\GÇÛÚýŠ¢(¤‡8?Øg×ÝÿÎíÌ«·Bø‰ëý‰ýýãøð'îÿ‹ðTKñ‹/øäg‡œæ43þ,•™?ƒÄWòª¿m“]¶¬«]þ“f' <Ž#.^yŠ¥ç^%]½…ínœœ…4°µ™-HfŽ×ý㎓qËï“’#„ x«Ž¢mÁìÅõ^Þû²º•ªMkßzmx")@x¼ `9„­·3¨nK–‘Í¥£cÙ¯ø1ZZí2ÿ? ÀGÇžš‚_ù|Ïÿ‰Œ§_ù4§žþkTþ¤úLÜó[€ï=d=üànðnQøóx™jµ8ûÜgˆç¯œhüB‰Úª9‘Íí€ÙŸ$X U¯ÅÛS€o߸Ýeª±2ÞôT ¬ÕÒßüOfOÄlyÜ Y!ï| Ç˜L* °)•eN¤Kòˆv=ÌìÕ!hljRÍÑOrlÕœÄ}ôÅ3õŸÿDÊ'^qî™hžù«˜™? A;¯î›<Þ®ãzoázßÃõ~Ÿtã·pÃdÑc8}î³—_$®T{üe"f£L^.zôùƒ©­8)ÑÚðÊÕÓ4«)@É™€*¡B«<¨Œ” ƒOFGòxàP@! Š œËˆÝhWPG)Íl«F 5©µ˜Ç<ÌA‚€;!…vÙJu UÆ.D§Ÿð£k·øò³k(ŒwÿåU÷Õ¿ùÿÞxçKŸ‰qïxr+à C©Öy;Pª;fÕé¯scyHo"(’óÖ£½‹‚·y? |Tù°#ÁGºg ]ѼxF1{.czáQë+H0Ƕş­âºß"é|›dtku’¬Üañæ{ôïŽH;7H;ߨÞ9x‡(¥1¾K6\§^8{öQ|Â݃D˱vÿò^ ú0ų­O/Ô·5ýtÎc”¢Ž•g·t pž>|ã{ÇOÂã@B8Ì U0xÁõ7ÊÕ¹uŒRÔ+3xOìþ£Öâ1*vÿŠâÒ”çÊS]fOW©O]yŽm 8[!Ûü²þÉÒÛØì.[f´ÞçÖ›¾ö{5¾õÕ*ËoY’Í’u¿Ë~*Ø9 2"’eêªÇùÓ LÏÌœx°µ,:nŸ«®°v¢8DM…õº]D aÅ ÇF‰?±  t(;Ìz¬‡aùtÚDQ…ÁpË(ÉArV–ÝÓm¯Z€I$àA6ëÃìèǵ>, ”ç|Û3}*¡1=‹®~z;¾ß§¸Þëdý×I³»8»‚Ë6Éú#ÞÏð›ó?¨pëî ×ß÷º$ïáÓ5öJÏH6@|2@Eš§N1þi¢èdÌÑRJV‡Ç‘þ¥Ï]jœizâjBX‰0ÑYÄLMañv ïR\Ö§ß[emu7Þ‚ß»fYíìŒÜ{––Sz+}²á;øl· ¢ñ*ÂÛ>ëàF÷¨N9.]¡Ùž9ÑÅ6&AŽUp2Ó'-Þ{f¦yáÊ9TAÌ"ÝAÊß~ŸË ]L˜;[=\±_û;ÿcçýÿæ¦FïÑrprPéf[íÂðý5î®t¦[¼fJ)µ˜ùf¼Kì÷ºÇqí©ÇÇ @Áü“$yîæjq#!¬´ÐÑ ¨-úàFàØlƒd8`}QñÝWYÚ²“:E€Á0em9ÀîÀð»œ1QøpÑx— M‡Ù UfNŸ!ÃÇßü¤ì$U*çú.>)å=ìXˆCE»:Yà"XkÑ"4ª]Tè&DXã݃bó/OÂP‹÷¶÷tŒ¬G-Žð²vÇ•J´ Àö©'"dåNí¶ ¾ãß³›Íç /AxÒðóÎ9wäâq0èSô¨  ]ÃDöÅ']²á]¬ÝÀ;‹Í÷îTyw9Ü÷ºYfé®ddýUlzoG×`Ò1ªrŸCt <ÔŠóϧ=3"±_*nÎ:²$ã˜t¯{_Çû§?Ìùr &ÕF „ãÏnÝ_Á{K½-ÝÌ2¼y/ý1'Ô8ÈÛ:,›Sñs<~Þ;|˜9n¬eÛvof¦;¨ vöu,‘€ª0…v®Õ²‘çNÒƒlîæ¿÷9"ë‘Ïz HÖav‹ÏkDˆ+–0ôa rüdÛµ6»‡Ë6 7t=·kt·ï¸8gYZ†dCa“Û8»ï¤n¹„ ç€ X¸\åÌ• '‚ (Y–œsãöb‡Å¾æÍJ›¶ÇyÇ¥4/]Y YÆáÖÃu²ÂÌÌê–È|÷Úûé7ïýâaÝñGßÃŽ9,@–†~eèü7ƒ @ü¥Cè,2ʶxÛ¥XɳUM´#¸+ÂÏvœÿ^JÖïñÉaèÀö’'±Cœ„ÄFqi>fælJ½­¨4Îb*g¶[¶‹·=²dÑ`ƒa¬Ç$ ¦ ¬¯÷ÙXé‘ ß…ôî~#ƒ7µœTD x!§/µiNMßd/rÞ"‚è£3åLÆáØŽ•J„ ¶ªýœsŒ’„F½‚HÆÄƒ‹Râÿá?ïè-Ô^;¬š´Z3Ïï©UcRW.¨ü ­ BƒÈã~Çïvþ~÷ƒÈb¦”]z÷˜ 2®žÉhÎ$4Ú‚è4¢Z£äðYkWI“i’0 ƒaöH‹C€a’±xË‘mÞ&ë}öÁˆŽðR—‚hTÐfæl“…ó§‰ã“)*«aÂ-8ÄØN6 ù¨‰÷žV-æâB{œ_û}.ŸÍPfknôþÍËÙý‡+'k±<©Ñ‘̱…U’.7{9$X|n†ŠP¯ÆÌ·¶›“e“¶=.ÀŽïìj4ç <ž¢8Ži­=œ pª&´Û–¨âˆ« L|~[Àû oWÇ%¾ÞÁÒR»ÝÇ3ùxç¸s+eóN‡´ûÜð}öTÊL£¢…< (!q£É¹§çh¶OÀ ðà²Üú:J 0ïñhÒÏ“6ÿ£Ð=0ÕªsåÌÌ6ËÍZ‡KS¦:(¿?Øìº7þ‡ßèÝÿÏÿæÚ‰Þóa,€Ã¼¹Óõô`Ã<@$ ¥ ÆDh†qnÛÑ÷y¶{Ö²gpR ä=âOt¬ö”ÚMˆÅqa*£ÞQm*³¨øù‚ã//™òÙ&vx#Gý¥l›B˜=6*!ï½0\¼IÖù:d+ìVt N££Y¯ Óg«,œ?Ex\\@Yp+)/eñØþÏ{œîIÈÜt›Zkj+<\ë¢Ä³Eîa0ò OÕtPp^IœŸhæÑÚ€‰x°™Ѹ“ ­fý±õÛî1é µG$ÿ ïv¯ Âaå8é°i£Z2;#Ä5K£U#®]@Ç—&€’{Øä6éh‘dÔ§¿)¼¿Ò¦ŸìZÎyîÞKYºÕe°úÛ¤ëÿöÀ ô4¢gQAAÖBÎ<µ@£Õ>¼°3ë8îuèQÅÚç÷ß·>ìøŽRŠO^™§Y­l ^»ù€(»ÏìÌê¸'c’úµož0x|<î(¼Û€Ü>Kl®²2ThµÔZ³ÐˆôŽDÏ^U‚Åäõ>·¶Ýà‹ð “Ýóx¶¡ÇÎ1MɃND¸P·Ì´3⺣65KXÿ,žÙ07ÂÞ&Kî2¬bmÆæF•;kš4KI’„,{L,@ ×ëóþ›ÂàÎÃõß"]ÿ=°;Yh=˜¨YD7Á4]£9Óšž>)‡ßû}yë”TJ¡u€ÖÁ¾Ç< à Ê<0†Ùv“ oÅ9ð$YF½¡&z¬p²àñ8ô ÷›2CÈŠQ(mÐúkXØyŠbpË4áÎß»²ˆ„’ të¸Gý)_Úägp8{냈„â¸ØÑšÒ˜Š¨O]B×^Aé²ö_ðé*Ùð=†½Ûô{†]ǽû÷7oÿ@Ñ¿QçwH׿†Ëvv‹ó63ƒèâ3â8ez&Ę`׸îûGÏíïÁìûÛþZ™¼$ø ¼¯ýä ŠÜyÏ|»Á§.ŸÊÙ—ÙB¾þãÉh‡Ž¹iGµi™š!ª}žÛv”Kî’ߣ·ùQ?¥³,¼s»ÎF4žôåN•¤ © è¥å·w,ì4åµwPºÆUÿþ,Ô‚4¾0¸"Oš›x4&Têš„^rtîzï=YêpÙ^¿+GeÑ>ÄC_ ‡q)ªµÍ©ÖX€¦)épÈBk¥3œË3Kkö«ëïmž ȤtÆ–X€Ú—Nâ;#ëߎ|±|@®£* ¤NU‰Ö†(YÏ4ïtõØßKs—Ÿi£ÆÍEv³ÓMÝ‹Vò6Ùòˆk‘ß^®ü㵞už#g½'Í­@D„‹í€SgzÔ§ÕÖE‚æ«”å¿Y;øqáûIGžÅ‡1ï/Ã`8ÚjÍ,=Ë?»&ÆäÊИÂ"Pe¶åÛ·WùSK¼ò3÷9/15s ©>Å:tX(GfK뎯ß3\¿“r'~ë÷T²,[’ûŽ0r{·?ÖûÚõnŠMÀù}ãN²íxá¥g/2ÕlŽ9Eàæ½%ð ÓSz[°?ð«@zR$ “r˜-+ᙀ̈́w› x”R¤^¸±–0S‹î5Š ˜›ÃÝ‘Ù쑦·Tê Øá?è͉ÎÊžÍD'%xºžQt1Ë[e«­Å²ýœž x¼fÌn“D)Ö£\æJ¬øâ§ú\ü„0î4µ©Ï¢«Ÿ$÷âò§ÈF·È†oálŸ¸RÁeÔ™¯(>s &_·÷{pgÃÒM<ŒÒ|“MXõ¸w­îû5ê­ujÍo6^!¬\ÈwþBœµx—á\EJ`y•Ÿû„Á8Î:ÒÔ>66à=œª8δw~îIpNÚ È2Kjíž =ËŠ÷°‡%ÐU VhØ©©råôqw,áÞJ—©°C«½¹-øû8üMž@§V€óÀA+>Ôƒž{ûl]wE¨ä5§Cto™AR+væ|ç ØS­PÁð1™ONõH á±2Qdò¨Ý?Pž« KÅäš=4B`4q¤ö)7~¼ˆ¢í½Da<°¢´fþ¼â©—,\˜¢5ûaý§=ÅVðoˆ¼…M—Ðjõ*ÉÐ!iÌs;¹ýU+ï?îlZd²}B±[Z›­xwÙðúµ—žYbæì;„Y‚òú‚µ#|²Œs}</z n9i0^^á÷x¢XO®>3ëñ;̦©àýáš‚8çŽ,ýQ¶'ý¤÷`½#sŽÉ!Íмofˆ’ÓÃÌŸã™KçÑÁV{¶$µÜ~°Êüt”<§Xþ[ÿé¬ÿ_ügË';.˜Wà'AxZÕˆÅn ÝzHáT]3ݬ(wçœ;v ';`Dű…KH-dÖc“—öOf·ÿÑ8/øŽPiÌ]ì25Sk^&¨¾‚®z;ù‡]Å'·p¶‹·#œ³¬ÜY[4X¿÷ýØb"oSuóô73\¶ŽwÛ!©†¸>ëá½'-»ýžðâ?ì»÷”½%·þ”æùïAÿäsÀÞë÷ãŸ'ß1Ò>²"M\mš¹VŒŒ{À`”qçî}ž>»‚6[÷ÞÀ¿u)»ÿ`ùÉÔ,Ö8TàAϯô2ÿÍf(9AõY¦Bê¦(9A•6L5kœiî@É•A·šû]Œ>^Kik]AXY>‹Ã{WX»Ï×BN?½ÁÂÅõ©³Dµç [?‡˜&¡¿nt›­DÃAÂʽdtRÇ3 ±ûuút ŸmâÕÛº½ÜŠ8xØø`rÊ?÷Ãïs”Þ#û¥”Å{b7$ö6uƒ…ùYêõÚ ÐétPxê•’MJà›ÿÃoôîÃÉÖ”rØ4à¡k¶w ö(q¦I<¹Hú¤µï8ä[ìCÛî¤üí®ãµ1´2fÏ hNOSm~аùTõÙmõãØ Üà]¼ë  t…Þ¦ac¹Šs'—;2)šwƈšÀû{‡KûxŸ‡‰|/¥·Ù£×œhƒ†“ˆÚ—éáÃj¥ìõhظÝQ‘ZJâ„‚g¤+\œoQ™@¿*Þº¹ˆÊÖ85?ßY–1\Y·Èòÿáh àPÀNµ19‚ˆ›«<è¤Û‚xÆ‚ZQSY–k'8Š°Î“ekYvøwRæº'wçY–ìzm4Ó§-g®®0{ºF­y™¨ùELãó ÛÁ->YÁexÑpU6nÏ2èyxvß;P¯T&>Ÿ€&,Q Þp¶õ¸³éèŽN6Õ–eÙ]·ýÏá±öð$#"‚un_ÿ×y¿gƒÁ3Ô5b?"t ³Åìì,A¥6¾ç>sM… {9ß`ïþèä{û7{bc¸S« Vz[ä bSÚ»(ÂDi.43Óê@›F¾pŽ;÷ý²€i«ñèá%/RÙù,–ÜS_‰zËpþ¹MÎ\®05w…êô/´ Lƒ]Ý~’8Û/ÈA„árÀÃ[!i–k|Æ—ªâêé.Ss3˜ÊsJHÀðé2ÎÀéö¬¬v%ɉ±ù8ç>ÔÚ}ȱvàÑHÒÜ Å‘¡i·š\ZhnóÿûÃŒ×ß~ŸKg,JÛñç÷Î÷ßmþð“cÚ)OÚ`3¡ŸØ­†¡A:Ü_í’Ø­H®RšV%b&  ý<.ûÌa­ï!Í,Îù“ŽT²,—JW›†3W7˜?öi¢ÆçÑÍ_FÌ<; £ñƒøäcÖ',ÞÓ¬­÷OÎüöP¯(.\ÔÔÛWЕ lM‡ÝÂsħoí•a8î¥ ÌþÉøýù¥LÜ9Gº¨HÈ•À^¿õ@œt¨¥k(­9uþ­f e¶h§×'KSNOwǀݾ{ý÷¾3|ÿoý½Íc?û~rØtXv é$¾“8ÞÞ‚ùzBÛÃhÃîä; <3a¸¸‡iìñҬ$9ʵmA—´s:gñÞ¢fî܈S—´fæ©5_!jÿt|VY?|›, $&éU¹ýþ±:ëì”ËS#Î]0Ôf>‡„çØJ?&Øá»Øl g»$>÷nð`ct" H$çÈ;®µWŽÙØì>„(DZÉ>à3yäw»TH½f *œžmR­Ä”_‰ð懨l™Ùl(³ôy‚þ?^ºS }+ïNÆ|–‘ö6xÐIò8€Jk‚0äÌìqÈø0LBëÜØ <¬pn°‡ɲZÚ§º4¦kT— š_A×®îšdÞ;|r›,âüЈÔX½§Y]Ú$%dI†M ?üˆkÑ‘ò<¶Ëì™9LõÓ [¥Ç¤k¸äÖ®ã³é¦ãÎt‡£1@¬µ'VµwœÍ¢4ÿ÷,Xbÿà sž† Ð>C5çX˜Ù"Üÿ_Yßd¡%ÔªÛ@¯¿“üã߸ø‘RëŽ%XÝÞÈÞv“q—Ò Ë}ÇÈn‘ƒhe8;Sg~¦}à—u\%pXL¸÷0JŽ€t+Ò^„%Jj-GsÚSož¦2õó­/ä¼{ÛéPðé nt;gþEñ$›·¯­ÿÿÛûï Ù²û¾üœsî½yÓW–wÏûî÷úµCw£hÂH%q†’F#ÅÌìî;13¡ÑŒ¤ iV¢89’8rH‚@Ã5Úû~¦»Ÿ÷åmú¼öœýãfVe=[U¯^ ÙU]/óæ½çÞßïüÌ÷÷ýQ«TÐqÒQò 7Î)Çf`0…)b¥évu¼H- £ qPe~ñ*—æk4¼ˆ» ÿ1wíåu+ù¹±Ý¿ûó·:[ý›FR“y­ˆ°ìɱ}¤oMPËxçìvi„Z}–£˜Æô\\ýÿi 3¹7‘ÍÔÜê$nɺ‹ð<€k1èºÀ ëL/eñ£ éT2-4†|Æe¼7ÍåiyGr«Œ[QOw“t‹nÇ¥›Å!ıAJnP©$ňlQ‘ÊŒcek3ý\wÑ"4Ob‚YŒI}:2L÷™¹:‡ßòV×Ç$Ì:ÒÚd¾Ä@ÎöÉåšØvdwCJ„ g‰£2~k†zyšÅ¥•jÂÞ|÷¥“ôÛÊ*‚1›{f’Ä­qþ·J 4*ö©‡”¥ß¹‹B¾°2 •J…0ð¨®iš]ˆŸý—\½rßÞ­¡Z¿•l橈HøÉ7”ðcs¦CÁ-¸ÚÃqfk!B&³•e‘IYìÊa¯³³n+vˆÆ—Züpóž‡Öæ&€A*-éh‘Éå°3û‘î¶ë–Y@Ü@7O££2˜mB„€Ú|‹Ë§®±4?¿ûwA Ll6̬ۑP ¬Út7×ÑÑ2¡·Œ×l±¸œa²î$“€ˆ¨æ¼A´d'þt«Î|ˬà¤ea9.»J6ýÅìºnÖVÔ†7/ #„w ¬^Ÿ@-j²=†Tº+}T× OÚñp-#LŒ”J:ÄA̵SÓL]¾BÜXú3í°#ð’¼€ÙØyÑ$“H•Ó5ù9ö1QD2Î;Ž qœ"2öú~é$ü¶bçãˆ( ±RµÙèa=?¤é…7-ÿA’ê`ÿ¯ÁŠ<,Ñ[̱s¨e­îèA¬¹0±Àö>…ëÄ+ñ‡|ê¹{ÿÃæ A6Z”3 Î!ê+®vÒëDL—[x‘Y™Ð+•E.ë’ÍdÖý[‘$Ò7qÀíZ޽`C­×}ßZ/Àr}#-н)ÜìdêКn;abDÜLBAÜQ™\æÒ©«Ô+·/ä‚V@àë®(4¶•äjº[p„®ƒö“î:? ô4®]¡”òØL óoUÒ/ºËÂ"^pÛ:|›¹pG ÛM“Ëg× ñü‰Éil+#Õê,F­iðÿä»÷þÛ-›5Qhä<€j,Pñ4.=R–EÖM1Þ›FÊõÅØ[Ax³c$δ͉ք” [°èÉzHe`¥Æo|¿ 1¦h´ u¿6ÇÕS3,ÌÎol¢Þ˜;G‰»¯º0éFæ@:€i‡p‚\.b{_€Ü wÇ„a¸e1%‰ÏÕkNr/ë]Ÿ–çÓhù·=Ÿ[åˆ:R7iBJIvíÞA1—¡ó  !¸2µ@à5)õX]#Ña¡Mò¿{o€°y°ÑÎ@Qñt­èãÝ´t€ñꜟk­Ä§BJÒ)›Ý{}®äV 7Biô*OçßÍC›(&ŸblŸGi@âf†°ÜûnDü! ŽÀ„íªÓ¢²PçÚÅ9üfkCß,-µn.ŠmjU‹fí*:î0'c”=вzÉäûIemò½‚£½k]‡ï$û¶rçOúýƒ5÷e#ñ„mïîŽ_t[OÇ2I(£r%FFÇHerkÞy¦LoªFÿòªûã]¸¾ ÷‚äzÙ¬Ø @^©Æ/­R…ƒ 2"dº¯™XcY{úR –òë¾i[1“îúØóV^)’Ç’lzl}[ÜŒÅöƒÛ„ôàæBd»[}WÏFXÂʃ°’I½: <çQ^ªlXyâ°s­··áX6jõ ^mž¨u‘@’°™û±Rûp³ã䋽d ŠÛ›<Ÿ"ÚÌΙ8Ù'q2{É÷l§Ø—eÛΈGƤ®üÚ¹Oan©ÛI8q£ëŸÐxÝéöt*Aq»íw=ërÛä`› GÙ¿on6Ï*üN_™ÃÒ•¤ýwía¶|øíän À†€0BøJ%´Z"P^™‰òÚXÚN¹Î0Ô[XoÿV zØH×™RÙæ \Bàº6¥þ4;I¶j2²#Mßða²}+ÿ7ÃW@è*"žÅ„Kh]#–X¸ºÄòbyK{îo&1‚·§\.ŸÌ^=Kcá/ÐÞVØ>d «ð©ôìÔ(n¶@i@òÀÞ:£y½R9èÔ÷·Zñ»}³ã®{y„ ÖmÖ¾ï9­“,Q±ÔËØÀªûà‡1g.ϰ}@`¥]å?¼¥Ê½íÿ¿^îÆl˜" —+ñ‹±´ÒMg4[pa¾I#Xí·–Ê¢”K³s0·îø>Š¢-è[Ôu¯—X'¡@2†ìÖË ¤Rƒc{ˆ9ðh½6Ý•£wè>²¥_Ä.~dšë*c4q°„n#ö&‰ÃEŒ ñ—b&.–i5¼]ç¿Îʇ0çYœ8›eúRÈüµ7ªÏ‚n±b¬^¬üDZܽ¤2d‹)†Çbvô%éÝ ‚`Ë?Y£$é†7Åä-šx:Ò1úÉL“„ wø¾[uÿ%ÇKž ­Fwì¦Pȯ ÿ„T›>S³ÜQGÚ«çDfþøéà¥ÿýïÞ»þÿÖæ.>sl0 ˜mêùPsºþ1@&,ÇšéjØEt#Ȧ]ö®¸Ua@§ªp'å0ÂXÆ·Y–Eÿˆb|“‘]ã{CÛ‡éztÿ¯aõ~œâKiB® ½w0Þ•ÿ¯[DA•kçæf–0w¡P)ŸÆÞS\»d³0¹@mö{Äõ×XMJDfÊ݇ÀÍäÈ—àÀh‚³9üýºÎ+Žñ<ïö÷}`c̺ÆÄÝ)ßEŒAd{Ù1>D&›íBÿÁÙ«óÈp‘áÁ&t—ÿ–âgþÙ¿¯œûÿ¼÷忎Ümê|Ž Î ¬xºÞˆÌñî6`¿ÎÕ¥Ö–ÅÞôºQ°uÖ=åÕ@Å„·`RJ‘+:”FêôôŠýŠž¡aJCŸ&7òßb•¾Š°{¹¡äÖ0õwе·ˆïyW‰ã*hÍò•&OMS+—ïú:×+˜÷'/XÌ\Q,N¿‹_yfMB‘B¦a9£¸é>Òy‹‘ác™Íƒî¼öIò÷N÷êN!¤”’X'|Q¬×•ýnax;ŸH—Ø5Ò»Bþ ©èéKÓlï7ä õ÷­ü·ríwùùÍøÛêB9~>ÒL­®ZHɹ´èÓ Ö’„ôä³ ô×ýmï}“­ß]5·ÞM39‡á}Æb¿Eq`Bÿ“¤~ «ø)”µÖí7t°@\•°qŒ u–08GOG3,]YæíWf™º|•0¸ûÊGç¼×³n1‚Óó‰0?Y§2÷Qãøšó·Rã(g'ÊΣ,‹tFPêu°¬­'pÝ*c¯µF`;¢ux*·2IÂ1I"&îÿ.zK…ò! Úô¹|e‚C;j(+\ùûr%~óO¿×øÎ©¿Üöžö¸ß­Øhc€8·NÔýìÊÂé°Œ„LUü5a@>“âÁí=¤œõuEuâÁ»5 ë«O ¢Ð(Ö×õ¤\›áƒÛ=ŠŠb_ŽžþGÈôý22ûI¯»c'B·.UŸ%¨¿LèŸ&ަ0Ƈ(`é‚DZç.qéô9õ*aè†~›Rl×h a®ÄçW§{•ñ¾x­û¿šýoj]²Ó7³ýÈ‹Ëыݨ@â!Yåò|ãÆj@>Íî¡Â†zkpåëwlT{ Nw8%Å>CO?d 6¹âéâØ=Oµ{êWw~,—ŸÁ«þÐ{Í£ÃJ²´˜=½ÌÏMsùìµj[áÅÊÎbŒ&Ž#´Ž‰ã­ÃÓßL pµ.™ÔˆV} ã_Ó¥v©²X–BÊ6㳺û@ÇëŠù×}-í°GkC°¬@'¶¿åúŠd€J-N±ïÀ^ú{rk²ÿ•z‹kÓÜ¿«ìþñ~dÿ;²`¸~fôDL×ãy?ZEÀ‰›ø¡f¦®É´fÓ)ÛÕC1—Y÷ýU,²QOw’0Ö„Q'žÖ8)AÏ`@&¯H¹iœô*u¬î„Ÿ€¸Œ®þ¿þ‘‰8˜!ŽÊ@LXÑ\|½Ê«Ï4¸zi’V³ÖõYƒÖqœÄ‘ªý ÝMsÒzE#|›ÐWÞ¡w® j£e©rØ)e r«qW{'Û¿• C Í—n÷úßá½ÆÞây2ÆàµfD­¡z†¸ÿÀž5ØÑÿ¨¨ÌèˆÏûýïÈV€‰؈ˆŠ§ëk›ƒq ¢çêè®YZ{s-®K}Ù R H8ß×%mCHÇUäKe ,'ƒå ¡Ò{ÖbüML\?ŽW{•лHäM‡K˜¨Lk¾É»Ï^y¶ÎÄÕyš ï¦ÝlÉ·wÿ)­{‘pïZ$®®Ö}ü¦¦U÷ˆ‚Œ^}xµNÌ ,iKÜLˆëlœ‹¿s}<ÅVz6@RÅDwpýã6çÿõ<ŽÉŒÁ˜F+¤ÑŠÂäÞ õ3:XBvÍþ Øãï\`ßp@±P}ß³ÿÙ ›‹]ÔÙ¥hm5  Â¥¹µ`Õ¥BPÈgÙ;ZBm ›Çñְʬc%€%EbôôÇdò©‰´zV®ëÝüÂÚËÄÁQ°@,K,LOðæóÓ{ý*‹³Kxž¿2Œ$ õ - þ! ŽÃöÏ{ ðäjÕ¢^Iá·bâpâ*«=Ý.RåA¦1±AYèÂÆ @†[êöwÖ«3éGsÛÓ2°¢üâºcøADÓñMÆ ÓYŽÜ¿Ÿžbaû?·¸ÄÄô,÷ï   ûÿ~eÿ;²U`SaÀÕÕ‰ œŸ÷×ìdÊvØ9X¤”Ïm¬ý.KE°åúÆHGí°R)D8)Ñ¡Üd义‰[g ýKÄá"^s‰z½ÂÒB™w_ y÷ÍËKËk¦ÍtMœùÍDë˜( ˆ"­·¶Ñ¦[fšP©FøÐ+câUž¥’ñdBH„He°ÝpCžIE+.ÿVʰ˜hsÜö´Ù ;¿ÇšF+Ä £dhûÅÆÐ?¾“£‡vc»é5‡yçò"yÇct¨þdÿ;²U`3a$£ÞËkXïbŸ>ê\ž¯³†vOHvõg¸{/bÉÀÑÄf¥“ô‰Í:Ç•eƒ›m·”jƒ6B¸‘]sd­ëíxe|¿EÅL_йzÊ¡^ ¸Õ“™¸Å‰!¸Â.yÈC‚À#ZíÑc[‘ïH5TT›>Qlü*Fwf=¶+v ¥,, ÜŒ 7×Xw)0‚•LÿÖ’‚v1µ“ÚpË1ñ¦ øéföƒˆ¦®ä…R—ö=±\?p˜¡Þ5µÿFËçµç¸[ ;S]ýÅxËïSö¿#[eh_ÄFï–:»>× -3Sn1U×kvlÆåñÝ=ôä× n‹¿“tê¾¶Rërol%Éæ ›©’Ž“•Õ½+ht¼€Ñ­•y÷­ºfòœEyÉ[×RÞŠÍøºw%ߦ·&‚äÅFS­)O$Ó»™â Ò(åb; i z°°·¼ªŽrÞ‹]ÿúõB I<6)o=Õ'ê*DZ¦é%D!Ýù Î’j£)ŒlãÈýHeó+!—®ÍP/Ïsÿ®&ˆU÷©¿õ¯¿Q{æÿòk…{r½ë‘­4›ê<»N–=ýtçÆ€¥CíqavmVT(‹}ÃEõl(wG:¡7Ðî+„ _2äz–#Q–BÙ%”=Ð>焄Ø70Úo{4‚fE°8«Ö¥¨Ó¹“þ'ñn÷Ä™­©„X,ø=D@G1:¬®2ÆÂJõb»v»xëû¥µ¦ÕjÝ“]ÿf߬€~n—ýï$<=?ÙõÃ8^c(Vv@ Åáƒ{Ù1\D9«8–(6¼òî5v„ô¬vþÓóñ±çÞðª¿ó‡÷nô×d+ Àfº̹¥ð‡Ú°$…Hjý:¤× 87פ¬uÏòÙ {FJæäßt( ÚÏÛ°¿vK*%è4d ;¥Èd{±S£g°JqfB/1ÚCI‰RåyE«®×åG%cÍÌJp;IJ¢!aè^;GlA¸r\:³SÓ™'0È5©Öväu²û¾ïãyÞ=éì–î„p·ûþõÍC(m Aã‡q²ë‡ÑM«@+X$c°]—½ÛúÉd×"ÿ&ꜿt‡÷Wöj–ßóÍÔ·žiþX¾§~Ùê`£4a`üØœNf$’ª¾Ï¥¥p¥SJ‰´,v”è-n,«¥ŸõK’ ê?Dmˆ[^ˆK’þ‰“–ØN 'Ý‡å £R#k¼AâjRa PÔËŠ0XÿDD‰V+w^£°ÀC«ÕÄoÅD^cZ]9ª°f’˶Vúö=ÏÃ÷ý[öðo¥$×ÜåYÜ¢ëÏz/Œðü?ˆnYþMæ ¬~n`ÇnöíÙ‰å¤×ïä™ËäœãÛët×þ'f¢çþãwêï÷&ÞY§l¥¨° ª°Š§ëA,Ž›.€Œ › ©çg*q¢\¶mƒììKsxGÿ†’ps²ÈÛ¾ŸÉ$n‡˦ÃÛ ¥~‰R¤ÒE”Õƒ°²¸æQ°ú¡0šuŸFus¸…ÄÐAb îätÖøn§Pi„4‰BÝEJ*ÊαÒ`@¨câÅÏæ:šµcëµq¿6?L\}?ˆ ¢ø¶°•e3m§¹ïȺJPiø¼õÎEÚéã:ÞŠûÇ´N_ O’Àèß×™ç[m–Û¯†êôBð|¬™êXfm4½¦Âb¥ÉD%ĶJ)lÛ&›NñÄžÞ !;²ù‰À‰èv9èúoµ”d`D°m¿ S´Rb[¤Ê£R»Àʱ:QG£ÃtÜÀ˜ÍW¯jË×Ïܘ$Kan:r‹wß]¾€¥8O¥šÆ÷<¢` Úå¶Î£µ‡Ñ1¶#°Ržßºå,ÓõÚjé<#ZëP×Ê€W ˆcš~Øžù¸jÅê¢Þ íBÚr#Û¸ÿÐ>œôjß¿@ðîÅiÂú÷í]Ù•ü«ÄoýëoÔ^î=ñÿd« @L2I±iã­QôŽ­ŒÆ•†œ-É; G,£Wßsëa0ÈvÓ”i×ì;S~#­ ¢ÄÕï´ñ+aÞ­ªZ¯fÿ…eqôð¤ñ§ ùD¯?Íþ=¥êšÝÿ•þ·.\ —Ùø„­-—»í¼™,Û€N54ïÎû?|rÜýš­d/$®[Áñ¹ºXc±šc¬¿lÇ!ŸÏñ‰ƒC¼}u‘rµ±!I§ä´Ùáž<OK)³Ù¾_)ؤÓyÜìvz7vöqDjݱ®¶Î ãaРU÷Yœ²ðZ[ ‹•°À‰ºE#N'×]œB ”º1©YVÀ6’0Œ‰B‰‰ývCP2:ܰì~zöá¤&ØÛšçá-æßVŽcS°CÆ‹0Rð(%Ž¥Ð±¡Ö©7“5›¥šf>°iD›ç‰ÛÙ{݆ôúaLÔìZvm šµÚ7®Cç¥É ŒñБdó…ÕäŸ\ž,35;ϧŸ ruìWš…ï=×<ø¼Ïî?Üà³Ú"¼!º°éZ´àÇæ´­xd`&ª’’9Þi1Ú›Oe2I°Ý¿}€ÇnãoœÝ05V')”J¥6ŒQï)!(õØìy@3¸-O6߇åôb¹ãØ™ÃÈ܃ :®½@G ÂÊ D­sDþž× º³8c%×°8þnàJbì ª+ ßÙU;9‘ë«ó·[•c^,¨Ö|Z5ðWÉÅ„JÀNÊÎã¤ö#„²²ŒbñÅÏ^¡”öÐFÐS„~CoŸ&WtÉ“üNäGžM£¦Yš×,Í ¦æ#^¿h¸T·ÌÆFœ œB¨ ^¶Ã€Žâ_B !PBÜ–ôcù'$»dÇøÐ ¥¿Žg[±ÆèPmVÏøâµðGϾîÍóü”€N°ÑÞFQötcªk_¯zHÒ: éI‡¼=YçÑÝ}ôf“š¹ã8d3.ß7À›ç§YX*oH‘;^@'¬Øˆè3 $C£’ÑÝ6Ù\Û@9#8é£8Å/"ìµvPûW[' ¼k4sø-ŸÅiCm3™“;gW. Ž“ve)E‚N´–´YÍoX›;Þ,À 3åDY½æÄe`ŒdˆÉ*÷"@zTìÂ'©±®sè¸IÜxÈ»Œ×\"ð}嘹kß»7¥°î6cV«wbÊ]Ä&–õZHä•Ñá*#@•‡Ìƒ(g*µ[?„“{ˆ¾Ñ3+‡åŽ ¬qTfRå@'FØ-Sj£¿ö&•¹—Xž»F±Ï£·?bgoÌÉ«1gf‹‘C,ä v³Ú´<ªˆðrkÌt”X^Gç®Û¨OÇ7-ÿ%”_‰ -‹Ã?ÄÝÛÖìþ±çg0[açŽêÀV¥¦O¿rÂ[ Iþ½ï»?Ü;Ðfüœ¸´-U¶E?êuœÿ²³xÊ4éOû¿Záè¶¥Œ•$”"—Móñ½}¿8K¥¶ñ 9‡Å²¬M)D&#è2¤Ó)¤r2•Ú‹ÌtZW3ÿ¦qаqŒÐ›K9aa:baFÇ¥äÊ9mÕ¤Ÿnå‡$I(×9Ç`g±¡©T Y¯‡3(³ú\K倧% *÷N¡†±2H+Fu¡#Û‘Ú‰Ì<€U| 'ÿ ùÒ÷È_Gª9¤2ä‹ðÖŒfA»DF è6ØË÷}‚ HØ~ÒŠnV·t'I; ?ÓÎÜäRW”ßCß@ÞK¦;ö‚+ó5Nž:ÏW ±SÞ Ò0Ži½zÂÿÖåɨ¼÷ÿ·{e a eÉÀ“3áÓOŽÙ_°¤°œˆ^µÈD³Àɉ?·•TÃvRïã¡]ý<ûvkSv'vg ëP’­Nb÷›‰‚\Á"W²°B¥Qö Ò‘¢{÷'¬Ö_!& üZBpÑHbÿêrL„X–Ú´!Úˆ$ Jësõo+š±Mµ,ðZ-¢ÖíÝ8ß S‘6Âu;øÁö²^¯n«[¦°ûqz¿†½+ý ”õ-„˜@P Íæ\œ‡³K°ÐŒÂðŽù `g-: Ѿ§wâ~ÂpøûØ6>¼f÷7Fóê[§(¥+ع´2{±]ú{ó_þqõ-´ìû‡ý½N¶º Ø-dàFŸfùîl8u­ý®Pºå¸ÒÒØ¦ÎXºÁÉÉ:Uo•2L)E©§ÀWÙÁpϦêN#J)Ø9þí(¢…äŠë‘(ËFY=H{ Rã7\¶Ñ t¼H–‰"Ÿ84Ô–cf'b?„a´BÊy/ñð°ɶY@ØYò©—}šåw1áíÀ 7«ö‹®—ìú=y¿62}ˆÌðÍà®ÿ‚w²}¿Ë¾ûÓ<ð Ã£)~îˆæÛ<öåòêöÜ ¼Èõ×®„@Š[g:)c4Ûöìá‰Ç![(­iú™[,óÆÛgyâP7Õ\a‘hïþß¾45Ø8•þ=•{éÄÀ4ÐÃÍæ]Ý^Ì«“Þ #…ÌgÓý !À é—óÌ4ó¼ye9É´ï¢e;Œ ”8²{„é…ò¦ŸìÎ@kƒHÇÛN¦õ‚BªÜuÄí 2M@'°Ô0$ 4•ECu)ÙQ,)‰µAƒiO8²¬Pt¯=‚»‘@ .Í[”ç#êåK½ËØ©ñ›®ÖÚÑbÂ&#|ÐH#KH«»sÒ ìARýÿ9ª'óŸÈä_ÃÍ4q\‰›–‹Š]#—gBÞº¢¹\…@¯v󭘔6§B÷#Òéø¼ý´ŸÄx(ÇáÁGbx|;ÊvÖ\Ï o¥®shOeç8·¿ø¯þ¤ö‰N̽ß÷¬[ÄÕñ,, .{qs¢»Xàa©HƒÀÒ5v9 ¼}ÕæÁí=ô¤p‡hOúäAN^œfjniÓJÓa‰ípìÝî¬ G¥,”ÊÜ”dŒ!ò«è¨¼˜Ñ‘¡U³hµ"ÂXc+‰¥ÄšÏ„aÂøA7W—ssšòü4õ·°ò!äuqwT'ްÜd¤˜?ö/¡£*‚tƒ8.#„…LíAe¢Ò»ºJ¨& ú•Þ̜̑ïyâüËó’|É¢Ô36n3:pi*âò‚¦'£HÙ²MÖ’¨¤R £M{h›æ»Í `n‘üëtý ŒoãþûळkvÿÙù%^{ç"_~Ì#í¶VJad–þÍŸÕþÍÅka‹Ä#ößïûÕ-÷ÚtƇíÜÄgåù¥àô¡1gX„ÁÐ#–˜‹<±ÆWî/!Ú1¤eÛ/ñÔá1þèÙÚ¦i±Œ1xž‡eY¤2Îmᩇ'ôö½ cLè!R«;˜À ã2&.ø5´Ñ¡QKšyA’%Å ßÇ1Zë¤êƒf,6%“SGš>AýmÜp1i~êОÇ-ðΠ›ç’.A Ãib½Œ1Í„L„•1&BWp¢iLüITzÂʳ:H•ý¹Ñ!Òù)}ß(g&xýÜÌ]•Õ£(&nç:õâë%ÖÏ <‡8ö°ˆÐºŒ ”Ö¼W›‚®“‡ÐOêΣ±8–¼Áh­W”¾ƒ]x/’…ëºI@ÃÕYIe1¢Q>KοÔ6É;Lb¢*‘wUÚ%®ÕÑé9Œˆ@ÇDa ¿•$Μt7±ƒ9ìÌdîãHw[»’—ìÖ*½•ÁÊ=Iºç5z†ß¡2÷"õÊ$µå&õ²E«®±lãJ¢È…¿eˆ‚$ …É«²qí’fz. ÞZK!×ý!Øß9¼—T&ß5ëOpùÚÇÞ9ǯ|2‰ýµYûëÒDÔ$Ùý?Pî?l<6ߌD$ž@/›H:Vš\Ï;»\‡q cáQ ³Tc‡ýCY¤Ù†¯ºŽ…"æÄ•%‚0Ú´’@š˜í9H)òF(ªtZ°m¯E®(±,¥òH{ 錬֞…ÀøW ›Çñ[3Da@ÐÔÌ\†ùÙxµ1Űò»lÏX¹Q*á%ìî®\h˜¬k¦êë#<½h îÛ¡)  ½{±2÷ƒhS•Ç!„ø7ˆ‚ ´nbª‰%¼Ö5üÖ"õÊõjƒz¹‰ß*ó=O\hÔÊî]f …°ûP™C8¹‡É’+m#_pÈc²Å˜b¯E¾×¦Ðë/J²yAO¿¢w@Q(I %I·_QÈ L ¾a”…ÄèØÐÓWâ«_ÿvìÚ‰ÕåÝ…QÌ~÷z3WxòÑE«^Äìbüì?øíòŸ.Wµ& ‡7:L÷žË{áÀj›ðF ޏ²/LV£o2ÖBš4€‰=F%ÎM帲-Ï‘ÒvˆÛÙóÃ;9¼£ŸWNOq×ÝuíøOh½R§ï–F#¦¶Ñl4je÷cù—ÑÙÃHÕÆA ‰° 7änÚnlÚä¢hì•)Cb¥y©l¤JÐÁî' <1˜ÄŽÛæwëH`¾%˜_ŒYž­0X~§ðidf/`všX¤‘2K³5&ÆI S›YbÙ›§æiÏ·À‘7Ъ…´ê lw†L~†B¸„ìûE¬ÌQ°ó${Iû …°{±ŠŸÆ*,+ƒu`91Rš¢BÙ~y¡ÆV«¥)%E2£/ŽWð kp ] ŸÎ¤Û„Ï.É3(%0:! q,‰1Š-Á Xh&'cvî (Ï ×ÿ2½+1zÂB8•#Š`yvŠV}ž×O–9y¥Ål9B È{löŽ; ”,?"åFxõ«Äa‹8˜%•?Š•}+{au<‚®Lpî^lwvñ³è°‰Àaâ&F·‡rMÌ!t•пFÏò+TOSè+Sê—ìØk37²¼“vÇøøÇî'׳¶ì×l4øÎsoqtoÈÈ`} æÿÒDøô7¾ß˜jßÊ& Ú·¼!$w'ElTD+$4F\ÚÑ«>ÓéÀDdÄÅz–T&ËöR*Éä¶Ýã¬#XnD\šYÞtMÝ–°#/H)±RmèÎF r9èV8®|,[aÙ£í6àvm;òˆ[§ñ›—ñ½ ±g˜› ™žÐ\Ÿ{Òíf%“ï“âÆ€NÙªCðÃ?ñÈ–ŸÐXuÆ–wÚ_µ6+g–Ì´$³-±%€ÖÐçjv C:Rìë'•d²cá·NR[|‹«g—9ýf•ïü Ê·^Ž8uFsá‚æìE83rµMÞ’(dBêµð½I"ÿ<&8 á˜)\„tÚáF§±¡íïaeV¡Š{é #S#HgåîFe¡²‘Ê?H&7H:ã‘ÉEdò}‚žþcŸa|Ç5±¿ðì+'¸pá¿ø©2éLƒŽ1õ|3õ/þcíwN_ ;Ã&Ù[Ö=—÷Êx€KRܰ„±  º{lKïJþ"P"•æü+q“‡@Z¶l+¥˜)·¸6_½3yæMþfKØY×’meïðþKd¯¢!—‘ *ÒY B“rSØî(VæI­Xˆoù—ü A+fq:baJE)’÷t^´/%kÚyµi &G„1A†ñJ2qïÝ1…袱҆yO2ëÉ-q4‚†Û !Å"䋚LÏ>djGûú-Œòô3,Î4™ 8}1àì¬!2 †> 5•ªazRpu6ÄwbòBàÚ¡ qMšµYZÕÓèà4—0á$ @æÚåÇ[¥šÌÚW{Q„tÎvî0náa²…qr 7y%J}‘/ "Ú. ‚™¹%~ÿ;¯ðùkìÙ5·ùÀ¹+áŸþÃß.?×>‘¸ÀûLýu+y¯BH—€"‡‹…FØ|áRë_=µ_´£ÚDô«yæÃ~rf™¯?2ŠIbÌJ¹ õùüã\œ^f¹RosïÝäà+ÿéúÝ€%áB¹v‹–RcÛV›O@PÓŒ\ R+,{‘taUª3 2EyøˆF5f¢s¢¬h­n׉Aƒ’"I® ‹ÛžÈMaÖ½ À¼Ÿx®J Pç$ «ßµ‘ð©(N_RìÚ¥Yž½HiäE¬Üc ³ ,”»“T¦tfBQQÌIò.׳é˜ù ÉO¾¯Y8Zç‰RÊÐ[’„‘€ð©-Ÿ Ûs†l¾\Ïó¤òÇÎA¦€*´½‚õx«áƒL"åîÆ)|‚(|G#_\áù‡¤Cõéç^§”®rðÀ"M§ì·°¿ò›¿Wýf×Á—ø€Õþ»å½4°y®y|2˜z`Ìyz¨ ÿVò'Škìr9uÕæíEö eÐ&É8™,íá—ª>Ï¿rù8êÆ¤—‚”T+;®Õn ´àõEÝ»S „Æql,Ë¡"CœˆÉ¤Qhp¬%z‡f@t#>…bM£Ѩ®V#ÞZ”Í;]úõ=뫸€äu=~àÎ *¤•áɼ“`:\F¥nÕ/Ú…Y©^DÚ“’Øâ8 lÇD‹'qú¤3H¶8ÂÀØeŒ†Ç— ®Ñø¡¹áž´ÙÍp •Ó†ù8$í:”rŠFKJA„4êsè¸I6\ÆñÎ`¹°Ýƒˆô>„ÓaºÙ£nnúÿB¦(ûƒœ¹âRêK¨¾:ÏŠ¼ñîUŽ>Çßþj×mu“},¿ø–ÿßòI¼kͤïÿVò^€<¸Ä&ûž»}óË÷Û‡rŽx<‰ôE]&o²üèØF>³Ÿ¬£’Ú²Èäò<²o„É™%f.ŸoúZ{LÁ’»í"8Š•¸»3öûf²a …„º¦UƒÐׄ"ŠMÂ÷§¯WÑ•DeµãûäŸ6 ídŸŽ‰uŒn³ø+?å:vîNe ­À•¦=U²%Z·g°®7í¤6I,ˆu®£ õ†anÚbÇ…ï-%^Óq¯dân[©Ù´"ë,qëË7±`iN”#f'bvì±éRd 2iÁÉÐ׈ãsä´‡-×°üK(g¤‹Lïãbd‰Q°ó oTX.x‘Tý*Ù‘±5‰¿r­ÁÓÏ¿ÅÏŽéj÷Î_ ¿ùwcéeåï>É¿Ž¼×ÒÐ`pŸ—õâåEýƒCj¿’”J öº³¼Y±yåÜŸ=<´bÜm×et°Ä§ÚË_VÊT»{H/àÇ1B)‚ H<³Ž IÇèŒ "&ð5¡¯ÑZ€*¢E– ]l= èî´{Þ]§\¸bÚÂ-?ÛõSP2Q)J&ì6JŠ•ÎÈ(N¸ôÂX·k¬½i@B¢¤‚®A$&ö0ºEj|ÏÐlFD±Ys7m V7ÔϦ'}úCcŠá1›R¿"WÉט&–5Ýž¬0&L’†Ä¨ÆÛHÕØHÂ."¬Þ¤T©J+n¡Ο:Isê#}kðþFÇ<ýü ˆøÄƒe„Œ0æ׿cË"`ŠPãÏÍäý01p™¤Ih£¹õw—‡ ©odÍß²H‡s˼q!Íî‘võ¥Væ¾¹®Ë®‘öîÝÅñj5Cn Ûk6@ 5Ž”(:^ŸßfIA® ±lE&£ÌGbÙE”Õ5´‡Ö­d4UlÐñ´ä]'ŠG÷üƒëÀ8±1„±F«Uº0?ÒX]Jß ãvIѬ @½åͱ’Y)‘\¿j¯7ÑQƒÈ7žÆkvšrÖçþ4›†©©ˆžs1cÛC£Ãã=žÝ@Y³íÊ€DH0DÁÅö|F) Í¡dtáîÅØ#(+E¥\æÚ‰gËÛØéÕ¦.)oŸ›æµcoó7?_ÆÎ®}LÎÆo¾ø–·@¢SI9è¾ûÃûcàîr»ÿ³ûퟷ%£XFµ(Ši2&Ç3'&þÔ\ÕFôY=…' ]ZëúWêüéw_à}cÛÊk˜~–ãW~û«ßgmUíÕö{+¹—|·“N.`³ÉùÖ„7ueYÿ.Êk¡ÚIVã³73Íä|™çÞ™Z³SYŽÃ`_à®Ìu»ž9[·ÀzÄV’ù˜í™Œ$Uäò’l±Ûݳ† Têy¢à^£E˜dŒRÃ$DºïƒcV…‚ÓĵOÊŠA'ÝrëTþޏiC&/q26*5Ønä¡ó” Ã&Ahð=ƒç‰»v€|O°¸(8{ÆðÒ3>Ï|§ÎKO/qâ™2×NúÔ¯,Ì„Äu™ tuÒ¯Ç hÝHšÄutp³ÇŸ§|õ…žžµ­¾:æéO ô2Ÿ}dÔª^w/½µÒð#HjþS»’÷GÞ/’\ÀU’N¿MàŒùɹæ Ã=©'{,>×Y{´8˜mòÆ…ö÷±«ßm`©L†#{Lj[÷ó—/Å«UzkÕMAÑ¡‡ºyŪ(e1œ1ì0”z, =Šž~‹žA—ÂÀãÈüÇ»(Á4ÚŸF‡en@C½Sm$L›Ø7-«Ô`1:ˆ‰i »Í›7"ôõK²9㤱Rë^‰‰‚ B¯LÂÐàyz%†Þìµtªahð}C¥ “×BNŸb ?@ä{Iå›lÊ2>âÒ7P 0ÒÀ)«‘Àˆ#‹Êò"‹ggîíÃYãúK^û2Ï¿qœ_ÿr™L®²‚øÓšÖs¯{ÿûÿüK¯°ú (bÿ޼Ÿ &Y¨A6Î æëÚûËÑo~ýh&—uõã˜$ûß'k,X~øÖ~퓻ɺv›ÁWàfs8°ißååWßB{õµŒ$¡€'ñ[Í,Ú†û"vo·(õ) %›þa‹ÁíÈô} åtÚah\"”•€O&šÕr̽vÂ:JÒa Ãpe°‰1ׂ(N%®ò]ŠeAÿ "‘Ø©,Ò]iBûÄþ5ZÍ~Ëà7 :´¸› ™Ö´Ç}wI5Ôš1õV…Ùù*ç\m9ôslëO³§Å‘±^î¿/Ë®M²’F½Ê…“1=™!R¹BÚO0¿Tå?|•Ïn±o|yÇ8·¿ø[ÿ¡úB7ù‰âàcÿ•{ö>ÿÝðˆ‹‹áâ¥%ÿ›÷É„N¥%6Š˜©EŽ•c~pºÌW¢„A* Ëq(Í#{9we˜ùËç‰ X×íB CL2Xãú ÊQ‚C}!°wéí·³Ú1JÏÐ/£òO°†o;˜GûW‰"? 1BÃÔ¬¡^s¶4ü_³»·üz…¿Å2nÉ÷[J⺕X©djh~h¢e"’8ŒÚ»µ&ŽÍ¦vÿNS‚}H¼ Ý®HDZ'¼~í¡FƒÙÅ*³ ‹œ¹,ùÉ A¯Í‘ý>{ß;€]"“ÙE¡wpu¶IKø7Ÿy‹þL']º!ëÿþùòo^¸z¬Íü_âC²ûÃûo á bãâQ/^ˆßÜÕ—~±àšÏ™Ø`ŒÀÒ>ûòï\žaß@š#;z–…l'ø†úžzhO7jTf“ÅèÚ Þ‚"j Gv[ìÚ•¦4hÓ7l1º{þ_Ãéÿe„Õqh:òÐõ7 ýK„~…8Ô,×b¦ãm6Ó ÖÝôÓñPº•ýf£ËîµÊÒHe°-I*=Œ´Jí,ªÀè ~k’ †Ê²Á7~»»gtÜÿŽò Àêä5H<9ºªqG0ÑŒ˜žõyýí¿ø)‹¿þ•‡é[÷ xæå㜹ð.ûËK¤R­²þ/¼é-²ªC‚Ä©¼§ —òA0 ñ]›ü¼˜«…­gÏù¿ûÅûíC¶eF) ‚ªŒ§¾õÖ$…|š½#© dÛd Øk¢˜ï>¯ñÊó 8gÍŽtãj[’Ý£š]»RôôÛ ŒZŒìb`û/ø{¥61¦u ¿þ7…×j`4É_c°”!¤Ó×ó‹ëÞ½;?£(ZQðÛïìï­(ËàfŽka§F0ª´J®.UÂÈШiÊËz…û`½ÒÈÑ¡5ï´3wÖ)ný¾«/@jZ-ÉÞÂv×¢ýN]šá;ϟ૟h2<´¸&?2³?ó;7fý ðç}JënNÞ¯*Àõ2E’9ݬ/*ß¼Úš>?'ÿÚ^îFkÍ0K¸Þ"ß~é åZ3I íºäŠEØ;Êû@ZDqÒ”r+1@Æ…];Cƒ=ý’þ‘ýãŸÁø›ÈÔvVwtñ®,¿ñ6õòUüV@rŽÅcý4Øàæ–´!Œb|ß§åy4›Mêõ:ÍfsåÕjµÃdŒxgNßE”'%QŽ@¨ ²ÝeŒ&šIEÁ0óøzSIGÓn†ê óì¸ý‰ÍÑJbðöÏd²ü¿òE¾ôÉ’zWÜ¿°Tá¾ý>PçÁ}ókJ~‹åø•¿ÿ[Ëÿ´ ÷í<¯‚¤Ùí`ØïÍäƒb#°éÅ3þøå¿3[ýP˿ۄ<ÑdÏ.R¶… $òƒ•á˜þ/í"X2Z¥÷N~[]Ô00„ÁÆ ?Z›¶Ò¯íx¼¾Yɘ;—-'ůÿâgø_zŒžRßjÜ/ ~ïÛ/aË«|æc“Ë_¹†°]ò{áMo‰µºI¼Ù•|B€ŽÜ DH(#ÿäÖú>åîÜV´þV‡g^F-öeÓ;?ÇHoŽGŒ¬Ði¥2YÆF‡øô§£)æ/Ÿ%ÖtÝ^³û)4Ù¬ —µHg%ùRžléqdæá5nŒÑ˜æyÂæq¼úUšõ:q´ wS ú%ùBš}žÁ5­@S¯k®”CÎ_ 9uM0sU°TN[~ĘD¹udÀDЙ$$ÒIX|ŒÀOVÖ“ìÄùÝã»;r=•·^‡K¡”Å×>ûÿÕ/>IïàÒê£ ~üâ›,L¾ÃßøZ‹¬ë¯6úDfù™W½ßøŸÿÙš’_ò¡äÙý@öûßI>H ηÏiuàÆE~ÿtù›¿ú`êP1å>Þ&”&—Ùãh~pì=Å<{‡shc–…›Ír`Ìà=v€¿lTYž›A‘NivðõBJJ‘IðËÂà¤J8ÙýH»;)0ÑQý¢`’(¬£c²Ê’˜8®DkMÐ2Ø-MÎ( 3ûãíÑ\\ 8>áqölȵ«°´ iúæƒÇ)Õ%'ðšÐ‰ƒiL´´Ò-í^l§ˆm 2Y‰ëF´ü;÷¯Îø®nª³›Åý×ZÁ'?þ1þë¿ú†F‡×(¿’7Þ½Ì^y‡¿ñYŸÁëýÿã?Zü±17xÍwí½¾ŸòA2,æ2‰جˆ‹ófñÂ|ôÆô~%d ’‡eÐ*S 4ñœæ¯?u€ÁþÒªÈå¸oG?þ“GùÞsšÚâ\{wYCÃDw  À¬mg¦…ŽÛMG©…’ÀNõ`Ùn·Æ1qGMüF-áéÐ1aãfb2)—m‡¥‘‹“>ïL„œ»3=/hø¦í|p<! ô!¨‚ Æo^%L!RÛƒ´†qóIçNÐ310$¨Ö ·Šl:Ã;ôMz:ÊŸÜsˤ_íÙ9Á=|”ÿÇñvoDÚ«#½¤LNÏòçß{O?Ðb|Gu•àC@š…oþ°ñ¦¹9ê–íg=ò^2­W<`Pæ.Ž!OÍ4'ÆúÄB&õ˜@Ø`Nƒžt‹¹fšS“unëÃM%6PJ‰JÑ›±H¹.Wg–ñ<Å@¬²ö"È*ŒKú†’$`¾ï~¬ÜC]„Ÿc,dä!ØÎéü^ÜÜ!RÙq ‘.<Š›;D:;Nº0J¶8L¶0B:›A* ø` ®#(d-†{lvôZìèU”ú 9;é8ŒŒBǃê^/–„½%‹´½é CŸ``Ü&“Igwae$k£ÐubïqTÇĆfMÓlÜhƯËt‘–®vêiÝ¡ßHhÙoåi a‹žaÑ3=|ˆÿçõUŽ܉ífWŽ)…`¹Zç_ýÉÙ5<ÃgŸОò@™åŸ¼æýÎïþIí47:h¶ŸõÈÑÄ$¥”ÒÝžßl%ž>4”ÙîÚr·P1R…XªÉ@Ê0Qµ™iŽ—Vxø¥RØ©}9‡œW¦æ™¬Çk*Ž4ììƒÞ‹\ ¥©Âãµ ÒF:#X™ع‡° O`eEfÂÊ?€ÊÞ‡•9Œ~;wåÞ“ÇrH¹9l'ÆvB,GcÙÛ¤Ó=yŶ>›ýŠñ1‰;h°cC¬Q$ØLª`+ €6G †Ù¢ [ÅÉ} ” ”Ý‹ˆ®ù—AǤ݄¤4h â8iHŠcC™(Ï;ñ¾ì6·Øù“°!éD^ð C;vóÿúÛ¿ÈÃ÷ïÁɬEúÕ-~ç¾¥Îñõ§qŸnåæUï7þ§¼øã[ìþ‹$%ìŽ+¶Aù H²©>I(°iÖ«F £…Fxrgoz8vJÛ#±f «y{N¡tÌöážÕAJá¤Rô³D2Åñ+ËøÁ*°KkÌÀ`É"ä Ùžý(wßÚo—)P„Õ—RZy„•N„„J¸­,ÂîE¦Q•Ú…r¶‘J‘rS8)ƒ›±"DílGRÈYlrØ×o±cTQ1âPëK†udK=a½ý’R¿ “³q‹"œ!À TË*¾ˆŽç°ƒ›ØvBUÞòLÿõ²Rãgíï›ÉJ“—NÞ[ÝÎók_ቇ‘Ê×zú¡áßëE¦ßá׿´H1—$ýº•ÿq§ìwšêïfòA5,p†M’ˆ¶E,6¢æ|=:¾{ÀN§â:rÁ(”ŽÈ§r¼9MÙŒöw5€(…í¦èï)P*ä˜X¨R­'¼]ËÀxIË)Ü´O¾ ±ÓäÛͦ‡Üé$ISŠU@¦ÆPî>TúNzˆT*“Xv iE(ep\A*#Ég- 6{úlvKzÆ ãL <bsgÛ¹•@‘†”½7Nç±Üû*‰¹ejÛéA‰ `Ë1d² 7e|M£!’¤_ÖÜVþÎ ¸µë(~%ã¾sÃÛøk¿úžxä0n®gͽ¾óì1Μ~ƒ¿þå:ýîy~·S~H”þ<2ÔßÍäƒl P'ñ6CÒ±Ô [sŽïrv¥”7±òRs^›hQÌeêYø"¥"“vØ;”g°˜áÝ«ó4ZIM¸@Ã@A’JIl{ÇZĶó ¶t…^¡vþӆŶGa‹ à®^D!@¦ÎVz?–»Çé'“é#•7Ž'S‰„¿0‘ æmöö;ì´é˜¬&nÜ>4ØÚ@r :6ô (ÒÙÇYÂvP™½í Êélò2(¦0z )5ÊJ˜–«eCË3+ñ}7¨çvÓ{ã8áoЊìø^~áKŸáñGŽÊ÷ äê£.üøå“¼üêóüµ/,12RY³Fg/…ôßþ½…opsý$‰¿‰­Y°÷W>È’\€KÂ$|7"–šas´'_Ì; c¥-™CJ›œp-xùr\:ÅHi5÷(¤ÄJ¥ëËQJ[¼syž–h¨5 ƒi(d%:ö ½‹èà$2ºŒŽ–!®!¢YÂæy‚ê1týmŒž°yž°q¨u¡0á,"n"T¶Ý>ÜÕD$l„ÝJïÇÊÆI"SØN:ã&îuÖÂvb”¥I¹‚lV’Ï)†{m¶Z  ´­ÁÍ}‘-7¢rYC¾ ‘²†ë6pÒÛv2.M eáXi¤œÇèE”HHNC?¤R1Éùvïü·¨ÓAvÈ[ Ûâ—å«“(ü*·À²¶,¦™­…,–k# 1*ÖX@èÅ4*óÔ—OÑZ|‰Öò3Tg@ùÚw˜¿òm&~Dmö9'~ÀÜ•ïRžüÍ…ÐX|¯ò"žFI éô·‰D`Õ$ár·#3÷“ÊÜ“ÞC:;†›qq\I:g£ì) JA)c1T´Í[Ô4#C«~#ÔyË  c@CO¯$“HµŒcEX©];!ÊE8»°¥@èK„~'¡ÊRLµ–x¹™ñZï뤤8ºï _ýúW8|`7n¾´fç—Bð“×Oñç?ú ¿ô™yﮬ!õ "³üì«Þ?ÿÍß«¼Í­ä'øùܬ| @‹$гç+NN6.”œÙ¼uT +™5h /#\ ž;_¦˜Ï0ÒÛ•zËqØ>ØÃÁÑ"æšÌ,”11Ôk1ÕJ„_©.Ä,/,Ï7™ŸZdîÚ“—–˜›h2u©ÁÔ• ×ÎÖ¸z¶ÎÔ…:3×ÊÌMÎS[œ U=†ö¡DËÎ!¬b—!èœGRaH¼‚½XÙpÜݸ™qœT'%¢–Œ˧-²’žƒ£ Ë ßU´ºq$(ù¢BŠ©q,ƒ•>Ю˜„rv?2š$ ¯>¡Ÿ”+åd@ç­ ½?Ž“Ã8.÷=ô _þÊç9¸n®¸6æ‚åOŸþ1_ûÔ<í¯Þ üϼêýÆÿxëŒ?$ßWù‚~n&Élµ»­ tDœœ¬_éqgrîQ)DÛzÒ5ÕâùsMúK†{Ò«_ÛôäÓŒõ—8=Ufj¹ŽÐP­kN_Œx÷bÈñK!ç.øœ=rîlÀÛoû¼qÊãØ»¯¾ðÂ!¯žyõxÈkgNˆëÓö üÚ1DtKÖ‘B&;˜Hu…Ð+©Tú ÒÞåH¥œ´DZ>‚¤,Ék1Ú+ÑiM¥.ð¼äšï…0& ŒÖŠ7#ÂC0OÊéA¥¬íPY„Ѽï-áµÂÐj9¦RoóÇíGŸü¿òµÏ±kלlaM QÏ¿ù.ßxú¾üX•‡ͯ¼Ö‘ñï¼­“õÿ©pý;òa1°5UŽˆ““µKÃEwv —:*…LK»…•ž§/ÓÀFñÊ¥R1ÇPÑ]y¥´–b¨ÇeÏp/^¤9ym‰zhÌx‚ˈ š3³šw¦4§g5gç5—–àê²aºi˜ó sa¦n¸´¨9{-bq& lhdú³D­øµW‰ÇñR¥@Ǭöø«î\A:X©=¨ô~w”£Pª†”>–epÒ’b΢¿_’Ï C£)öl¡è0óı¡Ñ¡!——HiÐqË®‘ÊìF¦Æ’SCÜz¿q ¿ „ÀkÄ,-šÚðŽ$èÀöί ²8Ä‘Çã—¾øI†ÆÆ±ÝÜ Êÿ“×ÞæÏ¾ÿ*_y,â‘Óˆö¤§u*?üeý¯—“0$7ànQ‚'&ª—ÆKîÂ@ÁzÌq˶TÉäØ·Žƒâ¥‹™”Íh_©l„XV eY õf98R \÷¸2_£êÇH‘ ã6V½óºh·ÂÅÃÅ©+Œp4«>ÕÅ9jKçi,#¬¿JX¢ ¤öA¦2³¦ i¥z9€Ú…ãX¸°œRøyÛf,«(Á " Ø–Wë6×t:ÒéÐKu$·+ 5:äs‚\116ʪáºiœÌQPí9¯Âhްuš U'Ž“Z|'°úí?JÎ#Sìá‰/~…_üÜc ŽŒb¥Ò+çÖ)°<ûÚ;|û'/óËO°®rí7OñS’õ¿^>L’Ø+&¡ß«™ZcúèN±=“Žw Ùé^3ô¸ \eó£w*d]—]#CH¡ÊF)©l2‡w2³yûòu?Ú§[ Põ Wç4­åˆ ѨhjË1•êÒµÅKÔß$n½Šˆ/a)döFC ,„=ˆ•9„mõbY ”UÝBk›Q ÷Øz ézcB®é{_9/³Jé½ÒÞÛÎÌwvãµÐÝ#Qòù0Ûô”–#P*$m„ìÀ ¤…ˆ=txŽÀ›!ð"”„¾a~NFfMß`Ú½—/|ñ3|úñ#ô £·Kù^ ùî 'ùîs/ò•ÏòСYŒˆV®AkZϾæýÓÿáÿ»ø#sû¶øDù?t­¾ë‘›€$˹»= ꞉æjþÉ}£r8í¬r*ÛЛkâ*›gÞ­‘v$;†zW²ÊR*”²I§]võPp§'—hwGÓŒàÒ¢áÊlL}.¤ºQ+Gx M³ª©WCÕ Aó±w žNFˆ[ý•fEY•E¹;°œ~” QªŒT>J%Ó…{R£)È¥ ͆Áó’ž{X¥ÞêP‡';{¢ˆ&DéõšþüëퟲiC6¯pRàfÙâaTzß BXYŒ™È?Oèy+iŽÊrHµª ¶—¡,zwîáë¿ü <úð²¥þ5]}R ªõ&¿ûg8uúM~éÓËÞ»Ìõ˜¨ÙÅøÙÿå7—ÿp©ªcn½™hà,I›úO¥| @'pÙ"#0_Õ͹J|bï°5èÚbDYÂR¶@ë,½Ù:Y»À‹çëTš>Û‡J8v‡@B"¥²-v Ù;>Ìå…&Ë•ú]1õÄ*>ÌTáÊ‚fi!&hÄxõtÒuç5#Zµe¼úbï8Ò, Uåô¯2ñB‚%p¶¡Ü}¤Ý"©”‡”-⨅RËX”zmâ(¢VÓı¼®õ6ÙQ;!Ù‰×*ýínT¬5^ Ñ¡"—7£H¹¹Ò8NöhšÂ D ÂE‚æI¿ŒÛ^Dl(/êͬGžü9¾þõ/qhßΤÆ/×ÖøËÕ:ÿç7žaaê óËvï(s½~Ï.ÄÏüƒ¾ü›ÇÏun­ü‚$ë1ÖÿNòa4°J¾è²EIÁùªišˆ_)fĵá>ëKeÓF'WÞ2”ÒÞ¼0ËtÝp`¼ÇJ(È…Hic§\Fú2ì@ Ååé¥ÍJ` Ü/¨• ÞbL¨“.ê×Ðø­*^ýJ¿E ¥Š«g%,B í2µ©zqœÛª‚iGšl^’ÉYø^D½®‰£Ž›Ü!à+L<·“n£›¤Ž·›xt,ɤ$Å^I:—xnñ‘v@{Ô¹áeâhŠÈ÷<‚ÐhÉA=ú$_ûù'Ù±c '_™ÜÉÎ?77Ç¿ü“¶æøõ/•.¯Aø ‘(ÿßÿçËÿô…7½e¸mÜ?G’øûPqümT>¬#P'ÉØwy,h‡o]Š.Ž÷;³ý¹üQË Òq˜GG.…ô<Ûzë¼{ÅãâBȮᙔÓe,,;ÅPo†Cã=³i®Ì.¯À‡ïF´ªT"A¹¦iÖbêUÍü\@e)"ô ¡zsÍãHsÛÎ#­¾öÛ¾@å"œql»ÛòPV™8h`Y7-Éæ,‚VH£aÃÕ±ïßN:‰NÝÆîÇ]õ{!a¤F28¤È÷H2yHgÆPéƒ+%A¡ (bDyŠO~â ‡GP©ÌšD…‚s üÁ7¿Ï`z¯?U¡Ô·°¦ÎFféô¥ð?þ“[ù·Ï¿q[å‡dç?LJ¼Ñg=òa6Xç&‰Hßå± M_÷ÆùàâáÑA7›JïÂØŽP±PN•”Sg´q~&æøÅvô’ϦWv>!$R9d\‡ƒÛzí-0S×Ì/-ßÝY‘¸âÍ*¡`¾.¸¸`8?«¹6«QALÊèZ 8š@™s(–‘öÒêYqt¥´‘î8Jõ MÌÒŽ°,CÚ•8ޤQÕÔëIIïVÒÁå¯ôêC¨õ-ñúC>«èíS¸é7mádŽ ìÞöê+¤ÓDc[¼fF¹—þ¡Ï³gÿa ½}×Qx%ÊÿÊÛù÷öcNñóŸœ&W¨­1Xílÿ?û¿þ/ ߸2ùÜ9éwФìüS/vIv¶FÂ%¸U×#.Î5ÏÕZæ•ÝéGÜìbAÈØ–ÏH±ÎdEðÒ™Ù´Ãh_¡‹V ”ƒí¤Ø6T`×H?׿k,Vª[BìhņzÕZP«irJc C âPxóÄÁi”™Á¶‘©á®]S‚ÕÆÁ±}RŽFëaÛÉôÍj9¢Ù\;»¯óëJ£mZrVÙxWð&¥Â(i$CŠtÆ`§Z¤sXéC«se+½È‡¹ó×}„þñ7ÄûB$MN/¼u†?úöyêpÏ>QFeVùûM‰„ç.‡ÒÕÜs»ê‘ Iø}¨{ü7"? ’lm–­I ˆº…f‹ãýÖÒpxTI³âaX2d[O Æüðä®-íïA©ÕE…e§,¥ydßÒNsyjÿ.«׋* ÞF*4:4„ø Ÿ(¼„mMb§w#Ñ@ЇQØ@ L™(l`´A ý„»? “ÖÚή~üÎn¯W< ;Wf}ßÏ(29‰“ò°ä,Nz•Ú‰IåäÄ<çÞžÄ5iJCÛ°ÝÌ ñ~µÑâ¾ó Ͻö*_üØ O<8ŽG'Ý/x¾™zöuï·~ÿ[g/OFÝ|nzÏù‰û»å§Åt*(°5ˆ·.Õ/÷ËÙþ¢< ¤È&"úr-r©4Ͼ[eb¹ÉÞÑ^\G­‰}¥²)dÓÜ7^dçp/fªTêÍ;Íßh˾`¹fÐ~ŒÔšØ×„AâÆ 3C&ÝÄNíF:«t‹R9`"Œ…˘¸L·,[â5b–Ë?ŒW•ÿ=ù¢=ìNů™d2c–±ÕNªH3(qöô»,¼ý#²ºJ±««¾‰ò_š.óoþìf®žà?ÿÜ÷í¯!d7N!¡ðþÉkÞoÿÿhñG—'£Ö:ž‰Ÿ™¸¿[~Z $IÁZû÷„ƒzkDœ¸]Y¬êw©þŒ#v¶ÿ hzÒuÆ †sSÇ./1<ØOoÎ]Í ”ë²m°ÈÁCé0Snà{[fjµP0Wƒf-¦Q‹Ð¾Á¶’á™JL‘É4q2“F£¶Hå ì!¤Ñ §‰‚et¢cXZŒh4×føïÆt%óú õjbR)ÐÑ,µ…eNX"\¼Ê@Á!×Ó‡êfímó®;sÿç?¤ÏžãW¾ÁèØ2×é¶Y,ǯýàÅÖ¿ü{¿¹ô‚Ömnø[‹$¡ö:ËϘòÃO—€äÙ¬±u=Ä®ÍÇ•¹e}b¨¤ü\Zn—²“tÔ¤ì:ã=†ÅeÃKçÉ¥Fûò+;—•J!-e¥éËñÀ®²Å>&æÊÔ[ç À‹a®%˜+CX)åÀ±bÛ³¤3il÷À ’ê€t†ñ2&š$Žk âQ¯³¼lÂÍç/:—×!íˆb¨7 õjŒ#:Dûè-ô08Ø“É#dw8%ˆg_9Á÷%>¶s–/j†B©¾f^ŸÖx3 ñsÿŸ/ÿã÷Íú9mÖµ ,WH’É?sòÓf`•Ih«Êƒ‘³eÝzöíà‡öÚÙRN>¸úOK¶É{8Bòã·—ð´`|( ¬teÛ(ÇÁrÒ¸®Ã¾¡÷ïF(›«3K·C¾‰Ûør ÈKCÖ()PV@Ú]À±SXî8ÈU)Tiñ q4G4‰#CÁòBD­±Iˆs.E‰Hþfˆ´!J °sì »÷ÿƒÛî#ÛÓ»fׇÄå_®µøýo¿È‰¯ñ…Uùø#ËXnÂÜ +ý­g_oý“¿ûK¿"øÜIù°DÒá÷3©üðÓi Iâø$[9û@¢\7W‡KÊ˹ݞPÊÔéI Ž]¨rfªÊÎÑ> …ºD:»6Þ`Úɾÿõ·–_®6̪[pûûV&äYÞÂçáC+?í Ð2[×<Ô-I… ¦_Ü>¨JiGŒÚ¶°ìvo½Tƒ…&c9‡sM^<5KO.Íp_W’K$ä£ÊI‘ÎdÙ9ÒËûw+ôpqro *IøúSZ£0è¨ X’5Òé%ÜÌdj´ýn‰PY´–få^=¤Ù0T–c*Ýåʯþ\%èèR| Ö1Z)†¶ïàãO}ޝ>õ(ûvï Ó‰õ¯ƒó†qÌ‹oœâ?üÅ3 ¤/ñW?ßdßîE„Zëò·a½ü¿ýëÊ¿ú·^?ÛNö­×í¿Ð~>~6 l}óÐê“ Aõ+á«¥¼¼6Úo=`Ûb5$šlºÁö¸åñã Ô½ñ¡^Ò){͘kiÙØn†¾Bšý£F‡úiø1‹åÚ]' ë‘ Ö0Ä~LÚK ,KJUÈæ28ÙÚ•ƒ„3„·iÖë4ªšÊrÄâ¢!ŒÌ ýö '_ÛÚÐ; Ivp”G>þq¾öÅOñÈCGÅNgo`ìB0¿Tå¿û Ï¿~œ§hòù'¦)–Z×Íf\-ñýwÿŸ¥ÿti"òX_¹·£ü?Õ­½›‘Ÿ«F Û~m%ÔSÔ[&zã\xi°¤.ô¨¡”#†VÿÙ`Y>Ã%¢“âͳ˜¸0O>—f¨7¿V!Ú† N³w¤ÄC»è-Pn”kM' µj(húЛŠÉgiWáfbr…'wånkŸ„…Ð-Z•ç©•©—5ó3óóÏ_å0fUé¡£ü»4Äá=Æ/|ñ)>õă “¾Žž:»¾æõw.ñoþüd8ͯ~zžûöÏa§ôÚŸÁ÷3ýÿ®òûªïhsGXïÊ’²ªüó[xÏ*ägÉ@b<’A#[™Äç‚ËÛ­J1+ Ž%z„è 5I¼žlí%Ÿj#â‡oL±\k2Ô×C6Zó4 ©PŽK1Ÿã¾ƒ|ìà¥|†…JƒjÃÛ߀¼HÆw4iWb;‚žM¶gVöh»…X€nT_¢ÍþûSèÄNçjí£•ô hN]œæßýù3¼ùÎI>{d‘Ÿlþz{×_¥ên£úžýWýÿýT=qájØdýðîNcÏ9~Êh¼·Z~V Üc#ˆjÓ„¯ .O-ÆoŽô)?ëŠA¥VË… "b°Ø`ïPÄäDƒçNNádžѾš$!t‚t–‘þ"ìc×¶QÄøõfk]¡!ã=^4ŒŽ8 [䊒LÏQTúPûM!aåy*s'™¾ðî; •0¡S½£ÛxâSŸäë_ú$=LßàN&‡²ìëŒH)Yª6øËg^ãÛ?z™ñb“_úÄ2wÏ“ÊDµfS×^`fž}Ýû­¿÷›Køòq)Žé xÇugUùæ°ý•Ÿe«F &éØêõ€œ^Š[Ͻí[¨è—v[%7%FÅJX âbÀîáyËæÓ‹¼u~žl6M_OKÉ›‚l6ÞÑ^>~ÿvÜ?޲læ–k´îÈB$p•`0 cƒƒ£¹‹ÂÀ#XÙ &&¬½Jyö-¦®ø¼ýn@=vèß¶‹#?ÂW~þÓ<ù±Ã ŽŒ‘.Q–sC;°”ßyíä~ï[/²<{‘/=¸Àã‡gé)U±R!Bˆ•òÞ™‹áüTûßùÃꉅe±±¦®€„Ãï )ÿºd+rV €Ëíßwqoˆ Dž9îMžÿÑÿí—ògÇûÕçÓŽØg¥˜°å†ìß1ÁØ@·®†üÁ·—ؾm;_xü {GKH)×¶áJ‰•Î0è¦éïïãÐÎa>ÿÈ^¾ùìq޹ÂÂrè6¼„~^ˆ¦Ýo¿V…L(Ϥ0 ŒŽrdÏQ=¼‡á‘a ÅT*}Cf?9-AƼ{v‚ï½p‚ÚüÜåñÀÞùÂr»‚ @€T­ ÊUýÎ3¯¶þðþvùåö0õÔöל. ÿå |æg^~Ö=€ni)’êÀ–̸‰ˆZËD'/…çF¬ùBVæS¶ìI¥¥#Õjz •òØÑ_cç@ÀÌl™¾6Éâr•¾ž,ùlúFâ )–…›Î°}¤ŸÇïÛΧØIO.ÍB¥‰Æ7”¤G·K¶msÝfÓ7ÜGaèK¨ôÁdG&¦9ÿ&/Ìéý<üÀc<òà†·m'Sè¹e‚Ïhͩ˳üÁw^âÙ—_c{iŽ/l‰;H¹M !*%Q¶ ÞÐçÎ_ ¿óíŸ4ÿäÿŸ•c(ï­¬) ×UðS3·ï½ ÀªÄ$Øð&[ß?Ð-¢é™øÍ³ÁÄäbüúhŸ ò99lÛks`ȸ;[”²’w.׸ɱk,Uë ôdÉeݶë,PÊÆrRHe¡ì™B‘¾^:°O>°›]ÛGYnFTq¡è…ûm†F·ÙôŽQþeDj¯^eúê9®¼}+cdì£Ûpó= AÇuŠß!¹65ËŸ|÷¾õãH]æ«/ñðþ2é&vi lW!Ú ˜yæUïÿø_kù»?yÕ›gãÏcwÿ#å߄ܫîÃ.½Àxû罓²ŸzÐýõŸÏý¾õ %q¯Ïã%M/.WæK¼v.EKøøÑý<ùÀ.ûK ”ØNÑ};u¢£ˆ8ô [ .ÍT8ue–§_<Á›'Nñ±¡&Ÿ>ªßérðá4cû£ný–\¢©ã¤…›ÎαRîJ®ÕóJ2ûÓsK<ûú»¼rò }…2Ÿ:Ü`ÏP¥âµø}B  1sy&úþ·ñô_þ¤9åư¹gq‰djÏÏ|SÏfå#pkq€}$d£÷š Ro¶ÒÿÙg²?¼?õ‹Å¼|PIÒ73A˜âÂÄ Ç¯fˆ¬ŸxøOÙÍ`i…«¯[Œ1‘ßbna‰7ÏLК=M6¾ÊÐ6ÉðØ©Ü!šÑ.2Ž¢PÈãdrm:®[(>†Ùù%ž{ãÇÞ½„k×yüÐ<ûÆÊ¸vt}=@š™+Óñ÷ÿô‡§¿ûBkÒóaãÌM‚d§_ ø|”ì» ùÈÜ^`ŒÄ¸×á’!1™Ck‚ëÞ&Px­zb´ßúD6-öÝ$4ÞAèra¦Àñ+Y*žÃý{vñÉÇdß¶~\ÇYaõ]¯ty-w/NñÚ›ïpæòeúKo|‰\ÚCʵ”†ˆ5ùrüêékѳÿæ//ŸºlÅwé°êò_$AöýLpö¿ò‘ؘ¼—yŽèÁ’JýgOeïò°ûWo•"m3YÍñöy›« FGÆøÔƒ»9zß> ù qÛæR †ÅJ“7Þ½ÄËo¾Ãüâ<ûG›<´·J)ÀV>RR–¸>RÐ^`f.MFßùÝoÖ¿õú© Üv÷ïæ9óH‘\þ-— ÀÆÅö%¶–tôN¢·[é_}*ûÈž1ûèpŸúDÊ#\·³ZØŽ!ŽóKYŽ_q¸8ÓK*ÝÇãG÷ó±û¶38Їm].„0Œ¹4SæØ©‹¼qò,2X`ϨáÈî½=‹$T‹)@JHµSƬÆùßøQóéï%î>lþùúh×ä#°9Q$†`7Inà½è”-ħrÇ¿òdæç· Y_HÙbXˆdlžP‰rB#¨”‹\žîåü„¡nzß¾ƒÙÁp_e9DaÀÔÜ2/¿Èäô,=j™ýã°{lž\¾ Ä«ÃNÛ[1´–ªú­Éùèø_>ßzþ»/mIœíúï™|dîNà‰7°UƒHÖ+:e ñ™‡Ý±Ï=šþäpŸu´·(r³7ò$ C‹(°˜ž+pf¢‡‰EÍTÍÂÛf¼hØ9ì³o¤ÉPe ãÊy–Dƒ©4Zæô+oûÿé÷þ²ñæÅɨÁçî­Gb¥ÿh×ä#p÷’†H„ïE•àz1@<6`eþû_/üò@¯Ú>Ð+·-qS£$˜È¢RÏqòš¢RoP*äy`‡&ŸY©oVÃOò ±Yº:ýÅw^hýèĹ`þܵ¨ÁÆ1û7“ŽË•¤™ç£]ÿ=’ ÀÖˆúHB—–÷½36¨R½EeÿÊg3í³ÊgŶbN>x³œŸ¦=n+Iú% :â&ïƒ04‹sñÏ^ ßüß~¯òR»­`#-º·’Î:}äò¿Oò‘Ø:$Ê?Ô~m5åØzÄd\!óY©~õsÙƒŸ~ÄýõBV°,Q¹ÞJ kZåª>öê;þŸÿ“ÿPy9NR[ ˆj‘Œâž%1¹üï±|d¶^IWán’ñdïGXн{ÜÊÙë <õ¨ûñÑëh!+ÝÊqL3ÍâÌbüÒ¹«áñ?zºñÆå©¨ÉÖ)~§g’ÿ}— À½“xh´ýóýê¼4í—Þ=nånf ´ÆëVú½æzû|°Ôòfkb|ÚLjH”ÿ"‘t~ ä#pïE‘x{yoq7“c°kÔÊ~ò!wÇ_ùBöšœ‹žÿ—ߨ}ãÔ¥°¼ÅJß‘˜dÏ AgÈG»þB>2ïX@&A~ĤSB>xÀé_Ö­ó×ÂõLÔݨ|”Ýÿ€ËGà½8DÒOðAÙ»1>[)‚¤sïI¿þGÊÿ”ÿ?¼[ßLvÄšIEND®B`‚fheroes2-1.0.12+dfsg/src/resources/fheroes2.manifest000066400000000000000000000007571456075706000223200ustar00rootroot00000000000000 true PerMonitorV2 fheroes2-1.0.12+dfsg/src/resources/fheroes2.png000066400000000000000000000540671456075706000213010ustar00rootroot00000000000000‰PNG  IHDR€€Ã>aË pHYs  ÒÝ~ü IDATxÚì½wœ]Wyïý]e—S§i4£.YÍ’\dË cŽm°±CB \B I..yórS€ä’b%@h†6ÄcŒqøÈMØê]£é3§î¶ÖýcŸs¦hš\Þî‡ýùiæœ=ûìµÖo=Ïïù=ÏZ~}üúøõñëcÖck'ë€oú×½ñ«uä|É­Û{ pÓlçȹ.àHXärQNñ›À ‚_ÃÓBßû™¥¯wµø*°üi ±PKàŒXìóQàÿÿ¦ŽR‚EÀKüÿ-múo—e×íûÖò/_J¬è«m³Íèæ”› XGðö£5<à­@ü«ÜI+Ú´ÿƳ³ïÈ9ê£;âü޾àÇúÃï6@>ú+iò3Rß÷·‹¯°_Ú{,Ì×Ë¥ÛrëN½?Ð@Ÿ5‡P°Ôo:^C¿ÿ«ØQ®Â¿nSîÚUEñÑj¼uÔ„,˹¬ßœ?ïŠõœ÷ã½Õw=ÔWÿðàq þ«Ð®÷¿ºxÁ›.Ë|h¨T½ÚñŽ­£;³—8ÉÍùwbÚïíÀÖ–p~ÅžOª¶nÊ;§‡HÀc£ìi€à¶_k°¢¨Û/\é½em»|]©n­VF½n’œÕaXbð}ÏɰbUȡфï=íyüXò-àÆÿ¢`Ð×^˜Ùú‘×ß›èuÕZ9¿÷¨Ë§ïxùe—ðÿ]ñÿrÞ™«È\²¦±N]à ³¿½…˧på –é¯86|íÈX­÷¸iãþd /xþV>òšçsÔ¶óó`)ºÝ~u|'æy˳ןç^´¶K¿í¡C楉µcÀ±ÿ @xåE™Õ·}|ñGÎY'ÿµT?ïÄ`Íý››—3â¿’ïá}<ôð^Îêý Kwðç_ø³…¸€üL&b´rzÛ(°Íò™ÜvFw?BT®²¹'B>q°Â‹€?îû¯2ðKr¢ý…«²oY×)_W®…[û‡ö«¥ð×rÝÖ"ÿãÜJµáá^¶!Ç«ÎjçDe7=¶‘¯íg…8Èyú £5è)À__ï\`…ûÍûvË?ÚÓÇ`™ïwþ´€þ[¯Ê]ò®—_›èÚ݇GòõJÈWî^Ê õRþáÃ/¢»CBeˆþÑq̦¤žëwcÓH 2†JGŠ ïÝzœ/.bÏS¿$½ãÈHxäGOŒìÍøá]庌ÁödtvµÉòŽö€çoð2âŒó7º]ç¬uÎȸf]¹åkõ!#’Èá®G,?;x&ëÏØÎû^y6Ë;5•z©ù¬ ¨UØú–¿gÇ?½®õgrÝßÌ.¿s¶¬™“Nó¢ÉVaSÈŸ,o¸xëÇ_p€±ŠÏöóê­Ýˆ«Ýø:äHèñﻵã{ñúÉå ëp¬Fùp™;j ßm€¡ü |¦îÉÉÕ›»Äµ[:å5µ0¾(,&ºrx,ì¢kãV^wºæ¬nÍx-¢j•1î|¹ì<ÞøŸ`Ùªít-ZJÛÐqv¦˜º­¿|Õ6®È×oÝË_þów92)R jˆ< ° ï±=džù_ߺ—ÿõ­{)törÍóVóæ,áôu«ÁÏ‘XI%Œ¾Çº%m¬ï- %(Ùƒ”AúuƘ´­Æ'†ÄV,rÙ6|_á»W… Žróà†ÿøí/UV›sà8éµB Q#ä³Bp*˜R@(ºÌÊ.Å¡¡dÊ9·î¬rëÎl_•å.£¸¸‡Z,0qÄ { W¬ G]ìXÄñ0Ïý‡8xŒôbÉ¢^jÃÇÑ'FÒ` 8mä:–qàø ë×®¢§«ÈéÅ„ÿÖ«èò"Œôˆ­G9¸Úb£2÷Þ·››ï;Àƒ#°X"Á5°b’ W ½£ Ü5È8PÌ‚ªA2_HI>ŸGJ‰ã8c£˜Ji”/ßòs¾|KÚáë—vröª.>{-›V÷²mMÂñP™,(cM:ð ÏU˜$ÁÍú<¸oŒr0Äí÷=Á‘C‡ytïqö –ik0óÍ>'è,PQ>ž‰ðl‚nX|¥ý í) }Æìª£E#ÚéÇ94K—Ý{°Ê½w³mÑ®¹t%Ûzzˆœ,å8³­ËੜUE<×A°‰q㳸 í8(í`­!‰–‹³½Ä±!ŒC‚ØÙ„Râ‘ч2O=Ì÷à'OÖ‰€N‹i{x VwC¶³»eÃÂÒÐ…½üìî'(4ÒÝf†~RJ‘ÍfÉår¸®K’$Ôj5¢("I&ڞϸqcÃîþ»qã½{§\¯³³“œ«°Ü$`ñŠÕäF÷1Vy¸?@4î/r 5.d2°µ´N?o†ubH’ºôÜ,]õ¡ÔX Ö¢µ^aÓó™­ >zm1ï|-~ó èƒl_îñ;ç.gÍŠEàfˆ¬¤ÅTk ï Öv¹k±„`¼ÃB´Öx®GÖiïç–ÇsÛƒCµ°H@o&:šâ•±P³°¤D~ÓD§ý€ln;ï>¢bcA6›!ŸÏãûi¶Û˜t¶FQ”Jp¾ïûXk1“$¶$IˆãGYܬƒˆâ„R¥Îðx’Þ˜rÎE˜ dz¬ëégQPAÈ49o7k© 17^BLÁQ ­!Œkd£ƒ™.`¬ÅbB.ÈÌ €þ:¬rÙƒ°[mX’Sò/Äpï€{ìöâç¯Ì²e}'+ E¢$á§ÑZáø.±…Ð4üYûŽ'už:<Ê“†Øßo8jRSÕ‘…ÓÔEÙI¯$h‘ΠM€ìÚÖíØx[ft,e«iöûKH¬D‘¤¦= glŠ4Dc* !R¶¬DÓBXkB „HE'§c0Êã"ïùÚûU'í^Äh¦ #$6ÃOB¬˜àðZ‚«Ò6q:1;ß…(†öÚG!%ÖZ´VXNö@¦7R¢Ù§0ø3uàg‡ªüìPuN«ÓÔT“´úIJä²pºšðÖ¦3C’¼¯Óÿ­… -öÀ–! Æz+0bÛÙyÊ£1ËyP† på©·§ Šæ€{^j†jµÚ+ÑLÚ}‚ºðÈ/ÃH_¦]—ñL@Ém'ª†H + i-†i›=•þl-Ô‰>ЪùJi¬±(©Á‚rŸ! ´´£±Ïpðz¬ÍCNO ™v\³±0¦ážt:ÈŽœø<1éÿè*ÀØÐ~Ú—¦¦5¿äÏöC¬Ûüß½éq`!ö°V>£{¶ÖbA[‚@Dyl‘Â0í7!-ŽÆõÙÀ!ê ‡ÜÓH* ‰ Ô…GÑõ¨øèÚ †Ú7RÙK=NÝP3º 5Ü‚±ÃXÖ ]¡æáÀqœ ´?Çb¢QÒ¤`вa‘&¾9Ó'û}Ûp:‡%!®íkÝ·Ên€±<+×JâÐ`lÊ1llžöÀÇ‘`¼,‰B•8w)ôlªpš›Ãj( Eì¯hv¬Ó ALÐN\çôè ‚œƒC‚‚¬ N,Tú‰¥}t?.1¾ëÄ ¥Ä¤îÀ¦mtÖШ†­k-/K¦ð 9Àƒ*žÃC6 UcòÛt†«´mVÀ6~¶iH¾¢«7߯¦‘ýCChgJkˆÂ:…®®“¤æùi ÚXK¹b©–5½›‡ø¯¶\±IÓVDV`ÌÊ—Ä5C[w'ÈpçO«|æ+c<^=6=J‡S'#$qX%´ …@z‚¬…µ1A]"„%Ÿ‘d…I ÁPM’´í ÷§DøŒ„å±Ôœf,r°€£5J*¥IÄs[÷á©Ô¼…ÉÉ÷Ñô¦0ió9MX\AÆ×¬Õ'ˆìãÉ(tnáÈ‚ú1²mÂDª64âó æßôßM’×$tÍ—1–‘²%®ù¼ÿ]u>ør‹ÉÔÐ]§á¨U8N¡ˆjH-©Uw±dÓÞñV¸ëGÇxðá„RÎGiIN;hk Œ%1…À H¬ÁñR¤‘Šv,a&xJ$IjýmS¾¤|¤âDß1¤:µ0°>›IÆjH¥yþêžÎÐèI¿7°é­ D겟zay°Z¥ªŠÄ‰¥f¡‘x9‰”þÝ-®¦6âÓÑÞκ%í,cuÅx)d<¬4"ñÅoµß™Tì$'yâ"ÿöžQÞð‡‚h"÷rººÞ îú™k¶Ó~Èè¾?&Ûñc#ðØS‚L»DeB‚L Jº„aD²’Ä$„ µ@Jë6ÔÄHà+E­a ”` é¬–Êøž8ePgzÙ·„hôë=lÈJœgH˜î_' l¿Éô‹9Uè¢3Ç“‡ÑŽ&²×ÀZ2”©;‚JÍ”Om ™q:PbjY?ë6.§o÷ }£‚5Ë6ðO¼€+.ÛNuHÅ‚ƒ¬òîYxW‚[hAI°½ö8¾ûœsÉk9ûþðŸaÙx„-ÊñØ÷Ô“¸®‹Ö§æNŽËåêà0ûÆ%gu'¸ÙÌs7ýUŽÕ‹ íT¢4$ÒZÉ8c1 zZã(…±–¨QÙž÷ð!%Ƥ²äÛáöo.fó¶£lYûK›ºÅÓ[œ~ò@Ï’ø0¨NËfh9 ÇÞÀî{~À×¾¢yJ¬¤»·‚0 Vˆ4¶oD-²Í"lȽ?ÎQ•9ŽÞE[ÇGi_ôQòy°B!D†Jµ ßÇ÷ $þ€\Û6Üökp‹¯Äd»·ü[dÏø-ÞöñW0Ô_þq•j=&ב!Wñ\Åm?¯Cº¨åé tHYI™6ÆØg_ ”RÒÙÙÉ`]€'Jhøwë¦.ÀX‹°Gk<¥R®QTá;šÎ¶,R)Œ±)Há—±Ô<ËÍŸ×l¹ôcˆâ&Çx„ht~€x|ã#G$xÙ.¼lÕJ#}Fú ¡½¼ôsÁÿüEÒMßÓû[`‚Â(­išD–}Þòù'FøågnA¯Z,¢½8妮ƒï9 ¥$?z¦ C^Ñç¹!¡µ¥T8‘‰+zP &ÛU%W¦‘°Y”S-8^cR¤D›V°ôº XC‡Ïz5¦Q¡ãñ”béç6_•eYÙß »o€õJ5ÜeÔ‡þ™s¯]Ç~ÿ1ê^„ë9>·3 ºQÚmB8$I„1y’$$1 c “FFSL‹§‰+ ×’s稯ð4µZ”Ú.1µ=õž¼¿È•4̾s2'8tÞʳwǼìEgP«±¸·ˆMæ,½ŸW)(… %'ŠùR€t<Ö.r'å„}rae³y±WI–|O%)“7€5)ÁâyzÞìUs°3™ÌI•:3 ¼Öšb±H[V±¤ƒc;¶®:/&©i_7ÎÅWžGe¬†wE¥mˆO)5Z;¡QJ¢”ÄuU#KùôɲbJ ~òû¾®'øÏÛr\~Õ˜þoàÚ¯@ü6½Ylç?z˜s—`‘¨yFxúÇ£3¹GÁO?‚’‚81iUJ2Ѻ¡×OxјõEWÑÓ&Ê6˜}ƒ˜ £u#ªd³n3Ù=ÏìÏçós~ ˜Žƒïû$I‚–†/ÏmßƯO-•·x-qiW¿íL2¹.jÕà/µ\R*”r°6ÁqdZŽ5 „O§Î  €™@ñÁó~øÓ1ÆÌjZíÉ{€Ã¡Ý†¯Ý]mé <õÀðru„7.@Î4+tÂ#OòÔpZ,‘Íd‰'ýq«¢ÊBÞiÖXŒ…(š6@¦ã¤þÒ» nìlÒ|ßó¼ŸFåÌ¢6Ëî^ÎÝŸ»¸#ÕÚ%ÀrprÑK7QoàTL¸µ¥RKÐ4ƒ¾ïË醊™¾\/µJ ¹®Rj™<¹Í^cÿí&‡ö¥m 4]«ñ<É}@sìh2/b›æ–ôtâh Ö°iE`²ÅG“ IDATo˜}?òl‰ÕªÍPNIE.ëáLü´ƒf'ÆJ)òùüœƒ¯µnd¶Ã×оÈãO?Ý}êú‰œGþ|âŠÏÚs:0IZ „“˜ôµ€A´V¸n!dK¾Ö:ÕP¬µé*£B~A׳­³G9Íu yÝ‘6A½úž«yrÿ© )M92>e[æñþk žŸ%™T¢åè4ÞWÁ×iØœpêÆ <)ñ¤Difœù®”³šC­õœþTïûór! #o¹ýP»ï+s¼.„8În‹–åJ²…,ÚÑdÛ²x“˜45‡ehÖPB‚1Iƒ¤Õ<¢Á”]×}ÆdpÂõ¤xå²Å:x‡¾ãûi/x²â”]ÀŒV ¶B9Sì‘i™xOÃô{ºQ´a§^´Y·—$MaH¡•BHq’¿oZ‡„“M@³ñÍU9³Íþùfþ´0‹K©`$ÍyÖÅiïÄÍd 0úŸ$Np\7ã¦5‚ ¿>Û÷Ùl; Z¡¢µ4dYÑŠPꤔs’E€KÏYÒ!¬D¬Z£hË8üË”î>Uœœ «‚£Ø³(BJ‘Šœ“VKyÈMOªØM3¡¡ñ¤I!0Ö¢µœ•ÙºZÏÊüU#4Û±Ðm UXA¡ ·•Ú5ÂC¨6l£ZØZ›.²Ð ƒÁ͸¸í¦E²³ƒ e6[@)­½FVÓEˆôš¹\nÁ\`>àŠî|x?ks'À•d}1ó„>U  e:kªÃ'Èe<´ã±´-•v›‹ÅÌtSnÁó@)›.ò²1 f–V)Ñ3”hO6ƒ³!ï”X{=‚å™2+ΜÔÉDR"¨h¢ zòþ:lc†i±¥`î"µ…Z§‘N[’$-)Ÿà-”4]ÞË·oÇep<à´õËÉ-÷Ì-/˜*ƒ«yd8òp+%]í>A˜†€bÚ…‚Ä€² âCËœ$ÉÌ&>MñÎNîž-óo­¥ÀEgÇxí‰-a=Ã\[ŠF¾°åª'˜K,J’´–RÈÔÒ¸ïù¬Úd?? myH¾wÛ\¸n´L¤Î³Ë‚,€)Ñ;qøZ¥¦|EwžÐL•|[³YȔة_b[.`zx&g çDš>òTf”Àà¨`Äß>IñÚO\Ù_`C’8žU VŽB»×wS~4O¸(„mðû´,×\ jÜ犫@H¤×†vr 5ã éNe§€“q”bOF[v¤ !;‹¹RDÃMЀ3R¤;|L&…vj-O2ÿZÊYgø|æ¡°Ö%–’a[g•¿ø\Îê[¥e`³+…óq–^ÊÊ  Jó_ׂh¬œÍd3hgîœZ’X”r¦(b“u…p€Ù@„1®Ÿ'Ærtç£tõ ¤ä,=ö§X¤Mزf1!麽Éb „$™”+Hÿ„K˜ÈØV¢eâ¤ùâà…t¢µ–ZG‡]q\½zÿ*ˆ$µøÂëBð<`Ûþà³TKc$amÎk[cZL gžÁ)O¾ÏS±^ZÏ 2G¬Z\HɘppœÏÓôïçÕì¢;2¦Î/ŽÅ`\Ï#ˤEœr¦,KR ‘ÇugLÝR‹H…ƒ²z³ùÿùˆ”µ–ñš ¿ßòÛr÷§÷sé»oï Oßpb„ßÀ¹gl ÷¢7ŒλÇsp¼´b.´ æH`úayáÙkÁIeáÚøƒä2.¿\€´à0°™ð±Ê~±-Aû:ÜtY@NjƒiάI>l¶Á:iÕ±'%uNuVL'|CÆ¿ÿ­|öK],¾Ö’ßxiËu‘˜”ØY Ûݨ^ñþ$ câù¬@c©®56½Î\QÔStަ{¦‘À’eK 1<µw„Õ=Žvùâ-Õy5€Ù0cêp í>{Ù¹?­ pVu+‚†½hÎxÝP[åÔ °9¹ç»ÅNÅ3,@ œ•üÉe'xÓÿØ +wO4#êû:•ý¯°¿;ÛHàÀ²‚`Õů"¬ŒÍ[ ã:s&{¬µhu2Ÿyº)ãÉýóªKϩ٨ŸÍ+TÆ¡PÐój§dêÐeO&h­˜ÎB†z˜ŽuœLtzSœþ%³©´ÏÕZ£R]p†Ÿðöß‚åOLüáûˆû>Œ|žÚàwãïi”‡«I|ÆpÖ«>H½\"IæîGk,Â(G͉­gu£æ†Ugo9Ã7~ôŸS‡‚Ë¿~kÒÝÌŸN²ŽhMedhG¦åaVtQ‹¦C6“+rá~.60†J‚·^v„îs.m}O2ø}‚ñÏáväÁïYÞüæ½õgÀ‘†i±KíAÎØØÍ¢žåÕÊA+³ä ZzȳXV„ `.*0x¢D÷âò.•Š™7<¥É·§d’Ùú œˆ±IÂú•‹©5­ž™F'Ljs±[)çŒsŸî‹ ¶Ÿ7‹ßÛº•úÐQövúoáÊ¿©gwÜT„êÇV@‚„¤zã…×ÿ>Aµ:o’‰$ÕœŒƒœ­Ëò¬¶5 Ö-_ ¡1å=Ëc(: ç@y¦¥”(¯(*žJ6¦³-‹¥^b" pôÉ&¦=ì'ËÈMiU< kê±`k§"¿àâV¼¯t…±£{yíŸ[ª¶ÊŠ^—ã#†xï[m µ”dà{œùò­ä³Ô«óAiÑJ£\EÍ^¥<HÍM'žnðòKÎOkÛpú¹bŽ;ol- /?]Ìèô¤{rA™AÛ™ªƒÙ,Ë;ÓmÊ&WÕLSêbVCÐÄ䎵Sw׊ãxÁ9ÙÜŽ×%kD?±‚æR‡hüa”>Æá':¸çX†î6‰«%Ú×ì~¨ §5 Û/@d¶ƒ;Èåoz%A¥Ž™«üF1#¸ó±nŠ9Óú;ß—<ñ”Çè_g’p‰Ð.¨  \öÛQ/×ç,76ÝYt¶…*ÍåÚv‹_¨ ˜n)‚0u+ÎØ~ñÔœƒëp[šÜñL0£®´âD§³o<ƒ£=Ö®YNÝL·Tï­¼DCâiÛ±µ>›¥„DQ4ççÓ-„µ0V“œ¿Vеôâ´¾«VþOª#1÷ìÅ•J¦)Y­%ùv‡›¾naðŠ´Md^a›¯ØH.¿¸•|9ɲRIldgÔ ¬aiÊâ3ƒw¶vN¿fPX»¢t„¢÷.º–õƒï0^6>²f~‰ùT,@hRÙò ]âñV½m> Ú4´7 B•0´iã$éÌ Yíà¸Ï“-ÓmŒEsÓŠ™×€´Ô“‰-à’ÄâIž1iñAc°“0Â:~KyŒµüÆéÑ»®äÙé ßÌðñÇ8ðèjëdiû8¢ÐqZžÕí’v×atȰïß8íw_ƒÌ}#Àºk¶ ü‡8ÿÌ^úŽì¥³mf/¤ ¢ê˜D´VM¸¿‚ÐWŽ©I¹'a¦×þ7ìaã&Ž ´ifQ4PwíÅ[¥Lc¥ÇÈø9Êû’¦\~¦eÚBÑýc½RíÐöT‹hañÚ ìJ`W?L¬›oèëùžT!²“·ú´ªP[ÛÄÖž?[UŒ‰ŒE+‰@’$ßiÒ§©‹R¢KÀ1Ò•X,•²àâå ë_ùµŸjÝMeà3$~pO/K %–¶9Ÿº{„OÿÂÃ*‡$ÉPþNO=ü5~ÿV!s•Ö>ªvÃ|ûIŸ¿ûÆÎb3+‡2-(•RR-•f°)‹ VµYg$JYÐ1¨“­…”‚$6­-bã8ÆñÓLš+bFmãW¾"Ëã{9w]?®ïó/_*<ÀŸmxJð´Üãy GÄÙ¶‹äÂõÝܽ{­'$\c n„ÑfËuƒ)?Q—ü÷Meúk’ζ ßÇ…µ ÖVSØ´¢¦­˜®øñ¼ZëtgïƆ.ýl¹dx¯NÃÌÊ.l²—Å+–³ÚúüÍ•1íYÁPàðÞï°8cm„;%ûæ2Þñ¾OáœõWé6î•=¨öAzŠ>¹ÉšEj¬="P!&\Ü`]ð®3ê,/ bÊe7ÍžN‹‘­8|b˜ÄØÆ‚jœ®ôEÄíÉrÆwžàâó65|ãÖG¸î%x†Ÿü|àÁ…Zõùp² 4j(ºì8vdk ×AóÊóWp÷î)þØZK’$­DÐlþ{òç6ŒE‚¾šÄf"i’ˉäBsÓê“nã„!Ž£‰#((Öž{”¥gkXôåÙ †¿ã~ñ‡áj…$c©Ä–ª5‰mÔò§G.ƒGbœÆjGP âãÇ«¸ŽZÏnr—)~;ÑP k’Z,(—M£Zxêß+s¸dpecBªEHoÁŒœ¾%ÈèðX…G’é*gÓ ]gm»h®ƒ43É Œ°v"N¬mÔ]Zâ(D Y.½üÅX#8ÞÐSxÏÏr÷}ASþ}63–"pµÚá Ø|-!./9kÅÉZu͹c¶8_ A-‘³t¢™T}#„`|4fՖݬÞp²ûã­ôsŒ`¢_¢ÆOÿcl@ÆØÄÎZÑ[+Ã;®Øžò‡ñÛq¼#ÜwO0ŽpæØr£9ûç¯P2­(f:¥„q’ª¨bªV"„õˆ£^xÖiXkøÆåºKAù¾rSà§§ÙÍ€‘t¼bÊ=ýG’è 8>WwÚe«9ÀsY™â|Óhhl ‰išÂéÀ(ŠÓÐÐâºÃæç•q–|¢ÅúETÁü;^1Oÿ/ªÅ¥Û ãrÃWÇaâyŒÏ fŒ%÷C.«w¬v+Üw¬Ž•Šo(ž$m !¨T*sfÀ¦»Ùìz+©Ì’,X+¨×CŠ]–®ÔKZF5¹7“‡|_ûÄ×qÜôÑ.¨•kÔ*5’8™>AÆŽà´ç[`´ñ Æ¡¶#„žuð«ÕꜦ¿9â(jÔMN=O66Ë864Nb&ho­I&; Å#—œ»>u*ÃØÀc´/²PlUÝ÷œ[€Cãà:ꑞÚ÷ ÑÂ"üv²…¶=Ÿ˜IíÓJ %Ä3XæC%ê5CyX²õâc,ê½®•K2”,[âÉ›ïåÐþd‹Y„L¿O*I\1±™rm(! G'ã>ObàŠsú¨‡>a|òàVéâ¹ü¾”’`l ©Ilg´jGF1Æ6"ƒ†‹4¦å$Lž°]~&6<¹¯ÄºÞ_âû9¾õÙѦú7úl`FŒÔ ê.Ç¡üÄî#x®"¯^ï”DÓ4ï¹üfk5n#e,‘h¥¨'ƒÃZKXJÃ’3/à…×½7´l…’àd{¡àò³›ökËLaÜB¤D0‰¦FK9×ÃÁ'JHÀ[ú§t¯<“ó_4À§1R= 'r ü(ŠÀZ””„Q<-ô³ŒWjDº ‡h>¨Ó4vOâŒö#z=ž·i)JXþá+wñŽë *›áÿ:ðÝSM*-3FÇ*1mE¹§+8Î/OÔ ã„×¼hSkÀ'Ïð¹ÜÀä!Z&<¶†(²˜d‚¤h±”Ç,•3Ÿ_å²×–Y´éÇÀâ´BY‚cl}'ôífÿSGÈäü“­HcßãÉdPbÉçb¾ú= ÿòô<7GvñÇèZ½†¿ñõªGl$aÎkö›ƒo­e||”¤Ä †?)áÅ„q2eNÑÈ¢6Ã?„`t^öò«ÒGiŸû~±›Í›Ç!ëòóGëMçÜ<|"&ŸËÜ}š?η¯ 1,îj#›+̘Ë^ÄN}8¢Á6±h©ÇõŠ`í¦ ¯zï~^õÎVœý5p_Ü"&ª"Ç¿‡hæîoÃÚ=soH¬L{ÐBgþñ§ëxô[÷¶ø”hÒ¿œ3¶×9·ÇÒ7Xn=Op¾Á㘱±±†…ͤ¦§uÓÑäk !Hæ_ˆ4ç10/»æ:â0bxT²<ûcœŒGé°mêÿ÷=蛑Ž2޾+gÝ߇ïjj±äú篞âæJÛž”ßžÄDcÝj˜†© T¯@{GŽ3/Þ˦sÛé>ïä~»ö1ú0æØÿD¸÷SÞiøáw Ð™Ÿ²6qúw6uûæ«j¡Àg¿P¤´ã•ÕþNr]y®=ÿ¥šž1C7Ôccc“~OYþdHAœ$„É´-áŒIÐøŽÒX€ér¸pSË ßz€·\âgsüñÇ›æ¿þ\`Öhà©¡¸Ï÷õŽÂØ~v[êQÂï¾tã7М µZmÖr¯VG6”ÃÉ´ÌÑ’Ñr84`<.yÍÖmÅgܬhm©û"åÁ?Ãm{’þþú½ÿFµzDÜÊ®,8IÆÇƧZ­R­V jU²ºÌ÷ÙÁã?¬ƒIkÈœI%p8kÝq Ù9÷Lnšý±±±Öúþæ¦õ0i‘¾4$L¢xʆR‚dâxíÖ‹ÜÑ“ól*yÜн'‡{qbMظU"…@·¿¦uóÕƒï!nÃóŽsë§ëüÝGö˜Qr…,RA…„a )5Rª9-Pó¥%/bßá µ¡;[ç¹þR2mNBlN“RŠz½N¥R™òøØiÔ³ÁêÕZ•Z=žR“X›n-CªV WÙ{ bé"‡ W!¬á/ÿéÞýª„L6ÿŒÌÿ©`t¶/Ø;÷e|gGgÐÇ÷Öùz¢lzHQ¯Ï) yÚ™R&'†(,[_¥6j€Bšè‰"ªÃ71V>ÁMŸ;Î_x”L›!“s¨Õ’$õñƪÕqêõñ†æÀœ !!9nßíQšx–°t–c(vŒ%bʬB066F­V›Ÿa IË!„˜0ý )óÄÁÑ2µzLôÇïÂòòÜýÀãlÛ6 íÏÌüŸjMàŒÑÀOd}÷ÖÍç¶]ã¸Z"¤âõ—ž>#¬×ësÂ0Žñ¤lO!XÚÛ4m]ÚÜ žÑÊ‚p¸óÆ7pÏï<>5ĆG¨ù0ɧŸä°þ}…µÏµñú/9#?L¤Ù 5%¢eˆ¢("Ûdtݤ1œ$’J‘V5„LWU‡,gøÖöé40çæ0tCð±ií»?¼NîZÔš5ÿÁoZ:>¨‹âu:Þì'4ðܪ=8í2)]äΩ#¿`::…Y°DÈþçíéˆÅt†ñŒÆQzu{ýA:x'ñdŸ¾áÄî0‘mÝ5S< `¢ªº¦[ݼ%©ý­·Ùlí‡$I ¼» HZ{Ý7 Á;:Áµ/¬æøL¼å&Óˆx¼AÙ´(“nÃ&ùÓ”FÜ‘šÃ6†ƒ´f …öQŠ F;ëÙÐtÓ ¥ií™?]7ij„³ÏÄI£†ch: þö7MßËåç£wÚ ?¶så_GRèÊäT54$X^P,W–I»xicÝ0{|ôtj²þ@[[[û›Ú±€TI m" 9Ø|µ›~£u ÑðjÔ´—Uï€bF1tíE&xÖÄÃê&tí¬É.“­uÔýËú›iõD21²Œ¡xz®fÀ¤?1þbXt]й'Ö’h¬!ÔtLAD-º{¶Q¤‘¡Žg¢D\'bÀ·ÏAÑLdÉɲW_bâ„0Ž7“fsˆpGKº49‹> “p7ñÌ{»ð8l(ºÀ½×LêÒ d‘³t: ’Øîýè”8Àt!º†´³c£Dƒ¦Z‰Ð>K"N·gçºR„¬‘2û¬avï±{°¾ÅGõÚ–LœžAó2ÿ†‘N¡‡·a´¼…Y{€ºâšÃ 3W2î8çBxê˜i!äì­3Ù·O§±A#Ô¬nÓH¦õL²Ì¢¡46À”ó†ÓïØá˜šÎ}O®â¦KwàvXò|œ¶˜±2“ù ~]¿Âw´Ì…K¾ø‘A\“*\‚£S[K}TÆ•#íœ8 GöåÍŠšvÂÃÁJL&­¡Kí°°•%‹G’ˆv7ºBÄo½°Ñ T5AsÐG\£6ÜS7P¥] ¬¦Í]¿áÂÁœ¬ƒCAÀɪÐï²'¸S}ÿBÒéFôX=‚/„žl@W#äö¸¹øæÌ·ÇÒ{¼Iï¿eßæû|JŠó7ƒ'W§%šbo„€ƒp‹À'Ÿ¦Q•\¿Hxk»Ê‹ ùåýŸ”<³¦ŒKFåPRZÊÔKƳxÉ{ ÌØ ¦´Š'a£_®™M²íf 4)“\pöÏì2.¸2uƒ¹n¾@úÈR­LSÁ4Ò X‹jMó²HmªH€ڨ@D³À-f ›±”JJ·à¼x òBöb¡8[vبn‘I:¬¨Ã - ¼ürŠW_N0|„ÌÄäP6ÂñS 5‡xÍSf"löRl=f‚=­;vãi~@¿q`zxðñ¿ðê¢4®üB†©ÏšþXfñµ#¥_·eU¬»½èÁéX~¡¹hDqˆÞ|Ÿ–„fè\óÃSèßËÃþ¦Ll ¤ U9àXe†åEiRi…üB@,³ÂÃèÇè ÔDl]>‹,Î%|dY:Š¢J¥H$$SI4ÝDÕLâI…¦H‚†h‚–xÕа‰6ÉkŠJÁ—Ûó€S!Ü,#Û>Ÿ€r:ò %|~™­[áîyþ0'ÁÛÏ7`7 rô&g`Þ^6D{1ñ&&ûÞ{’ÃNÂëËçò_<Á7ïÂç÷±fµÂî:µ‹ò­Ió$ [_ ƒ×w´®ÌñÈË/,¨á—ÏnÆ! è¢Ìã·]Š–ã$ÒfyÛªndêqÍö¶s2"ç`RRf2üŒÓ<ëŒØ8eX·ã¶ß]¸—Åùm6‡ã;¡±¹…¤¡£šV=¾M²¢©– &MN8ܧ·'FÛÂÐÜ(ÒY¤)ŠÖtÏÜ;¨©3¹ï.i',áø’×¸yÊFv¿º›¯üÕ¬øÛ/8hÞÜ<ž{c;zü›áC $›“1ÓöÅ€;:`0©ïš¤ºÛ“žÿ4…Óæ_TàõÆF¤7ðçÕ!v‰‚¾ýùõÌs¨OAÊb„Ót+ŽÈlãwNP8þ8u² Ï€%o©èZÓËk!ÞÏS¼² nš&ªªÇÛñþp8œá(ê™V÷b{âk¬R®¢ž€ïlëñe$¢Ð2±Ù….²~h-IŸ@QoÙ¯²©a,{“²iÿÁ÷<‹où€þ}N!¯¨ѨȢÅOsûua\…¹ 8»`a‡ˆ«š#,GªkauwûÒí¯„bE¶…Ã*ï¿÷!ŸìiC°Ùøá„Ñ\6õ,j[-tÍèP,™Ð9ç<‘Ñ“½ô*?N¥öþ’ýui ÙñúÒ€I$"C[T%‹‰Dhnnn_ìh4Ú^·×9±¶2€ ’W8¬’q¥íÚÂP³WïÂX³Åâ#¨šÅBò9eì5”ž0ù2ŠûöÀçö3ùgðÌü rý<þâTÕªË9Ðë§ùHîýGZRÝíMÁˆÉ‚7÷­Ìóû]ZÚÀýKÖNv;¿úŸS<¤”úæl‹@ëM&SÔß èä¥B¨È*ön{ˆPPE^¹1§þ¨pËÕúö£k1T%}X½÷W„ŒI·ž”=£4"É(¤ââç:ŸeÑTJo mZíA1²M³;x’ ï=IÏAýqØœüô–g˜wÅÛôïãb{¥Èuó›*3Ž_wÙÎQ#Ù£µšnmªWÙÚ˜|ÑçË©8ß³›+| Y”±çøylÞø Ô4X(蔪Î@8·=éÙ}‰ˆIÀ¯â-QRÖ¨Ò7\¨Qñ”Ÿeû™~Ž|Ä’¢JDöú ‹,’SŽÛv‡F2i¶·Òuë­×4]Ó-‚§iføýVè™) fåóP4d0jÂà¯m§_à&œ&#–æpìä:€9™~íh(€t„/ÑyŽ “2®jcú¨ÒU.Oô.£ÚþÌÇ6¦Œé‡ÓídÒ˜¡¼ýÎGÔf(X½œP^ùC¯B ×jåBAŸIœO`ð£äõ½Š‚þåääÓ–ÞŽCiáìãÌ8ÕKq@gW‹Aì½² ] $Õ®i<É”I¿ÞC«A̽lCq™ó1“•Û%ݪcÒTA02csM4Ã@3M«½¾áÎÎ8ükÑ<Î=ï ´˜Âꊋ_˜Ç}·4á--àì)ûÙS§-¶vxû7s”äH+@ÖCõwuÂÒ!ez¹¼Ö'ªg54¶Øk9Œ(ËÇëËaBùq<ö²EoßÖ“úÁÀá;Á{™U:åê‹èöBËû—rC¼çãíy þ< ì$´} fM pú;ûu½ÕÆWR€ì$»¦ƒ“O«Ç×çwVâ·t2¹Î'()Öˆ·ŠÔÕØìBû=Ý4Ñ3‹/ÐÒ©j†G—\vJL¡¾Iછoeé»ÉéQÂɃ¬ZŸZÈçkü·„)§€ .PÒ5Èdòú¥å'§o+ì“Öî q2´§‡œ·L;ûþi™Úd×gE}ö€ë<äöºùÎÜ6É{žÒ_Sܳ ¯+È{ɱ¥™1ÎÃôr-Im)ÂáXQ„ú:“AuŠ Þžw=É/C|–%v¶T˜Db¢|`¯·ߤa¿Acî¼ÿ×\~éQ’m1™‹~6‡e鸞¨€ÇžNñÄ mË3h_ÇÔ{-GQ¤£ô»PÐå‡l¬‚SG÷n( UþÞº*\Žë@°Û¸aÊhžygŸãè»ÀÕ¶cßï‘›ç³ÿ“ù„Öϧqí|Âæ“ ÎÇçßžiÞZ×\%×ã/»@¾Î]zFº9­ÌÆG»T¢qëÜ|§À ÂC)€€® (Qcʪ 䪞³Á6@A/’­/¡%\lÜ csíC³Mà ¸Ï¤5÷=6©OAIkÄNμbKîÝL¯Ò<ªj%&]½¿¸;ãeŸáÖ£µ÷wtr–ôË]Ê b\8bRS|×ÜWv¤9còu\tR.² ’©ÓúC$"M@üàœ=pÿ…ðƒ™ÐwÂlðÌŽý¢Ò5ÝCÓÎùìß™F ç°`y‚eÿÑ(’àò“rhMwÓÕK€dÒ ÖùÙ‘AÇ%tæ»à¶RÜ¡uNÖ} òøC mŠÏ'¢k:ûj¡Õ„¿ü(ÃFœ€’RˆFΚq//þq5ƒÊ¼lß#qìäºÊƒœ>2û~3GY¤£øÛÙD‘³K¤0®J$+'zû£#?\[GM¢S‡öE’=Ü:}4UµûÙRÝh!r.ÀÛaïZð4®¥ië"ˆÏÇ!|Š8pYo±g¾>·áÏý Mßĸ~.Š"WêôsÙ1¥ŽŒ¡lƒOkâ·š±ɤAm¥Èàc ì,ÁÛkž°¶æð:sX»6ˆA]ä—Àóo<ÏàÁPSM­2çͺ…—îÙFß>6¶TÉ ›RßÙâ7 Ðç›V€l® ¨»p³ª)A¡_ª8±L*Ô»mÀÛkjØÞšÇ¸a}QM™KÎ…C‰±bcM·ÚÖ Ï®†­+ µš«w¡†âw-Gλº=êµ.ÅëÑÿÐÃkpú ›6¦ µZ¹$I@×LŠZ'•Ò­Ê]ÓÄn©jä8),IP4ô6@FòžL²á~¼>'›×¥X[£ÆãÅ¥ÏðÐUƒO·¶rùu³yñÁÝô*uâp»)9moæ ´}VÐÝüÚï‘(@ PÚÝIíÐXS¥¯šyVŸ†R‡·¼ªj#K>³ó£1H(pƘaL=˜§—}tÈ ÖéðAØš@i„`U=bl>yQÏÄvg1·×tl<‡ †9f — ëÒi3ƒâ™¦—’("ˆ"ºi¢š:ª®Q·GbÔh>4pNÁƒ]{›Hh’GNåñGï@l˜¦À[«jyø²ä¡JKü„£¼#k‚À\>Ï®Ê.~ŠoH„oè:~`ä¡NR’Ï?ï;©E]?wÃË÷NàŸ·^ˆ]Vq9DÔDîx”`pÿa_Øüe&ô#ÎÈ'oÔ 8´n¦qË$R‰Ÿ¼eçÉ¿$qºEœN3Ó IRºŽnÈ‚€j˜46Ìš!sÑ,ƒ¼ð֞ƞ £9vÌÅ8ì*vÉÅý‹×PõÙ"æÏ­%ß_D¥‰²²^äæzI%%N¿t¿½ì].¾@À_”Ï£OÅ9ÿÚàòL¨wðâ7w@þþk «íY•ndɇ©–KN÷®òùäòãKƒÞ·×lãn¦wªÓCŸÞ=¹}Æxª÷ÙXµï°.¾Ï€t”æB¯/`/œÁ †ã«ˆ…×b¦Ülجár ŸK·wïP "- hÜüô²;8îÔ‹((ÎÇï/à•ê¹á¶y,^°aÃdÀÜ»[cw?ÖúÏLrGé$ZÚÈ·$·pMÖ\ÜQþy«ß[â7l‹GìnÌgÑ»ýxö9¶¯*7¬çä+ïGÑõú{΃Ñ`ÈØ|JOɆÚ*{Þ±³þ}<š$' `“…öDª˜$â:-!¨7áö[ÊÏgýÐT QÎeÆÍOR -eÁ/Säæz‘mvÎúIClÅÇÉEtÞµ™#PÞõ}²dlv0ƒxuòÒ•)¥1Ì«ÓÏÎòrS#Çdþ_>¡.žÏÙ'•Ñ–V(îÙ‡ù×]D\ÑYýÙ¶CÞÀÆJðÇAÒ“ô/Û‚”{ aS^'ÒXËö"iÍÖ *NhLjb0zlÏþýA&ž{&étIØ^âü+þÀ-—,cÆÿ˜äçûYñ±FÙ„ºŠêzm.7pˆq˜£ÝþÛ £ö{÷¡N¬Ü§óÚ'©ŠüU6—0ú”A öÝÛ¶3ïïuL3ŸÇFJ3˜rÆ ülò‰|ºm75ÁÖnS–Û÷ÂÈbÈ/ÚJþ`+Ác·GÙ·ãMV½'‰®é[ j#PxLOþõnn¼ñ\^?º¢árx¹aá»üûå…®<\*þt=\~Ç4(zØÇswõdoÝ ~}Ë44ÙG*­`%ÙÏ÷¾Êöu‹YpE3½{)ôTÀæ*¸ä¦†Ê{ÔÅt?«¯ú›Â÷¿¯ @f+À—(X5ÄÁ+ K&¥ev4ó¶„MîY’ƒîËwN£´È ²Ý¢jÛ!Q_ÍÌ»ŸåÅ7Þã¼üêv8û³i×Õ„>þ+½ò„$)È’„Ûáå]Áâ¥á+ë8ñx;—œ2/O>åªyÍË9P´Ñ™h‹ä;(Âwðž¼™0Ñùe¾4}‚Ç»àš¼©¦©\Ǽ¡‰»þ^@ÁðÈo¦Rœ'“LÆ‘»G¦±ºŽ•Ë^"7Pì=“¢âb|=Šp»}x=nì.?-þ¥ÏÿƒO©äü1Qr½|.y*ÊM´fÉÝ-l,³ßÇøŽŠð½/oÆø¿ìïš(¹x¼g¦ (å±x›·© þôRø&óǹ2jx)J¼´n¢¨&ºnµeqÚM¼n7iÍÅ]¾ÌËoÀìsÖ2åìvG€¢<+Ö©œý³Æ ,®þ¡ÞèfŽ"—ï¿]¾R„¿Oä׿œ~Žëâ¦Öty4%‰ÆcÏél cÖeÓ¸þò“ÀT,.ºädçî6n^ø"µ;_gÞŒ†R) äR”ç¦!drñ¯›ƒ«6¤sè™ #include "version.h" #define STRINGIFY(DEF) #DEF #define EXPANDDEF(DEF) STRINGIFY(DEF) #define FH2_VERSION_STR EXPANDDEF(MAJOR_VERSION) "," EXPANDDEF(MINOR_VERSION) "," EXPANDDEF(INTERMEDIATE_VERSION) "," EXPANDDEF(BUILD_VERSION) "\0" #ifdef WITH_DEBUG #define FH2_DEBUG VS_FF_DEBUG #else #define FH2_DEBUG 0 #endif SHELLICON ICON "fheroes2.ico" LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US VS_VERSION_INFO VERSIONINFO FILEVERSION MAJOR_VERSION, MINOR_VERSION, INTERMEDIATE_VERSION, BUILD_VERSION PRODUCTVERSION MAJOR_VERSION, MINOR_VERSION, INTERMEDIATE_VERSION, BUILD_VERSION FILEFLAGSMASK VS_FF_DEBUG FILEFLAGS FH2_DEBUG FILEOS VOS_NT FILETYPE VFT_APP BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "040904B0" BEGIN VALUE "Comments", "Visit us at https://ihhub.github.io/fheroes2\0" VALUE "CompanyName", "fheroes2 Resurrection team\0" VALUE "FileDescription", "Free implementation of the Heroes of Might and Magic II game engine\0" VALUE "FileVersion", FH2_VERSION_STR VALUE "InternalName", "fheroes2\0" VALUE "LegalCopyright", "\251 2024 fheroes2 Resurrection team . All rights for the original HoMM II game belong to Ubisoft\0" VALUE "OriginalFilename", "fheroes2.exe\0" VALUE "ProductName", "fheroes2 engine\0" VALUE "ProductVersion", FH2_VERSION_STR END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x0, 65001 END END fheroes2-1.0.12+dfsg/src/tools/000077500000000000000000000000001456075706000161705ustar00rootroot00000000000000fheroes2-1.0.12+dfsg/src/tools/82m2wav.cpp000066400000000000000000000164561456075706000201160ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include // IWYU pragma: keep #include #include #include #include #include #include #include #include #include #include "serialize.h" #include "system.h" #include "tools.h" namespace { constexpr size_t wavHeaderLen = 44; } int main( int argc, char ** argv ) { if ( argc < 3 ) { std::string baseName = System::GetBasename( argv[0] ); std::cerr << baseName << " converts the specified 82M file(s) to WAV format." << std::endl << "Syntax: " << baseName << " dst_dir input_file.82m ..." << std::endl; return EXIT_FAILURE; } const char * dstDir = argv[1]; std::vector inputFileNames; for ( int i = 2; i < argc; ++i ) { if ( System::isShellLevelGlobbingSupported() ) { inputFileNames.emplace_back( argv[i] ); } else { System::globFiles( argv[i], inputFileNames ); } } std::error_code ec; // Using the non-throwing overloads if ( !std::filesystem::exists( dstDir, ec ) && !std::filesystem::create_directories( dstDir, ec ) ) { std::cerr << "Cannot create directory " << dstDir << std::endl; return EXIT_FAILURE; } uint32_t tracksConverted = 0; for ( const std::string & inputFileName : inputFileNames ) { std::cout << "Processing " << inputFileName << "..." << std::endl; std::ifstream inputStream( inputFileName, std::ios_base::binary | std::ios_base::ate ); if ( !inputStream ) { std::cerr << "Cannot open file " << inputFileName << std::endl; // A non-existent or inaccessible file is not considered a fatal error continue; } const auto size = fheroes2::checkedCast( static_cast( inputStream.tellg() ) ); if ( !size ) { std::cerr << "File " << inputFileName << " is too large" << std::endl; return EXIT_FAILURE; } if ( size == 0U ) { std::cerr << "File " << inputFileName << " is empty" << std::endl; return EXIT_FAILURE; } if ( size.value() > std::numeric_limits::max() - wavHeaderLen ) { std::cerr << inputFileName << ": resulting WAV is too large" << std::endl; return EXIT_FAILURE; } const auto buf = std::make_unique( size.value() ); inputStream.seekg( 0, std::ios_base::beg ); { const auto streamSize = fheroes2::checkedCast( size.value() ); if ( !streamSize ) { std::cerr << "File " << inputFileName << " is too large" << std::endl; return EXIT_FAILURE; } inputStream.read( buf.get(), streamSize.value() ); } if ( !inputStream ) { std::cerr << "Error reading from file " << inputFileName << std::endl; return EXIT_FAILURE; } const std::filesystem::path outputFilePath = std::filesystem::path( dstDir ) / std::filesystem::path( inputFileName ).filename().replace_extension( "wav" ); std::ofstream outputStream( outputFilePath, std::ios_base::binary | std::ios_base::trunc ); if ( !outputStream ) { std::cerr << "Cannot open file " << outputFilePath << std::endl; return EXIT_FAILURE; } static_assert( std::is_same_v, "uint8_t is not the same as char, check the logic below" ); StreamBuf wavHeader( wavHeaderLen ); wavHeader.putLE32( 0x46464952 ); // RIFF marker ("RIFF") wavHeader.putLE32( static_cast( size.value() ) + ( wavHeaderLen - 8 ) ); // Total size minus the size of this and previous fields wavHeader.putLE32( 0x45564157 ); // File type header ("WAVE") wavHeader.putLE32( 0x20746D66 ); // Format sub-chunk marker ("fmt ") wavHeader.putLE32( 0x10 ); // Size of the format sub-chunk wavHeader.putLE16( 0x01 ); // Audio format (1 for PCM) wavHeader.putLE16( 0x01 ); // Number of channels wavHeader.putLE32( 22050 ); // Sample rate wavHeader.putLE32( 22050 ); // Byte rate (SampleRate * BitsPerSample * NumberOfChannels) / 8 wavHeader.putLE16( 0x01 ); // Block align (BitsPerSample * NumberOfChannels) / 8 wavHeader.putLE16( 0x08 ); // Bits per sample wavHeader.putLE32( 0x61746164 ); // Data sub-chunk marker ("data") wavHeader.putLE32( static_cast( size.value() ) ); // Size of the data sub-chunk { const auto streamSize = fheroes2::checkedCast( wavHeader.size() ); if ( !streamSize ) { std::cerr << inputFileName << ": resulting WAV is too large" << std::endl; return EXIT_FAILURE; } outputStream.write( reinterpret_cast( wavHeader.data() ), streamSize.value() ); } { const auto streamSize = fheroes2::checkedCast( size.value() ); if ( !streamSize ) { std::cerr << inputFileName << ": resulting WAV is too large" << std::endl; return EXIT_FAILURE; } outputStream.write( buf.get(), streamSize.value() ); } if ( !outputStream ) { std::cerr << "Error writing to file " << outputFilePath << std::endl; return EXIT_FAILURE; } ++tracksConverted; } std::cout << "Total converted tracks: " << tracksConverted << std::endl; return EXIT_SUCCESS; } fheroes2-1.0.12+dfsg/src/tools/CMakeLists.txt000066400000000000000000000054471456075706000207420ustar00rootroot00000000000000########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2022 - 2023 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### add_compile_options("$<$:${GNU_CC_WARN_OPTS}>") add_compile_options("$<$:${GNU_CXX_WARN_OPTS}>") add_compile_options("$<$,$>:${MSVC_CC_WARN_OPTS}>") if(ENABLE_STRICT_COMPILATION) add_compile_options($<$,$>:-Werror>) add_compile_options($<$,$>:/WX>) endif(ENABLE_STRICT_COMPILATION) # MSVC: suppress deprecation warnings add_compile_definitions($<$,$>:_CRT_SECURE_NO_WARNINGS>) add_compile_definitions($<$:WITH_DEBUG>) add_executable(82m2wav 82m2wav.cpp) add_executable(bin2txt bin2txt.cpp) add_executable(extractor extractor.cpp) add_executable(h2dmgr h2dmgr.cpp) add_executable(icn2img icn2img.cpp) add_executable(pal2img pal2img.cpp) add_executable(til2img til2img.cpp) add_executable(xmi2midi xmi2midi.cpp) target_link_libraries(82m2wav engine) target_link_libraries(bin2txt engine) target_link_libraries(extractor engine) target_link_libraries(h2dmgr engine) target_link_libraries(icn2img engine) target_link_libraries(pal2img engine) target_link_libraries(til2img engine) target_link_libraries(xmi2midi engine) fheroes2-1.0.12+dfsg/src/tools/Makefile000066400000000000000000000034401456075706000176310ustar00rootroot00000000000000########################################################################### # fheroes2: https://github.com/ihhub/fheroes2 # # Copyright (C) 2021 - 2023 # # # # 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., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ########################################################################### TARGETS := 82m2wav bin2txt extractor h2dmgr icn2img pal2img til2img xmi2midi LIBENGINE := ../engine/libengine.a CCFLAGS := $(CCFLAGS) -I../engine .PHONY: all clean all: $(TARGETS) $(TARGETS): $(addsuffix .cpp, $(TARGETS)) $(LIBENGINE) $(CXX) $(CCFLAGS) $(CXXFLAGS) $(CPPFLAGS) -c $@.cpp $(CXX) -o $@ $@.o $(LIBENGINE) $(LIBS) $(LDFLAGS) clean: rm -f *.o *.exe $(TARGETS) fheroes2-1.0.12+dfsg/src/tools/README000066400000000000000000000011351456075706000170500ustar00rootroot0000000000000082m2wav - converts the specified 82M file(s) to WAV format. bin2txt - extracts various data from monster animation files. extractor - extracts the contents of the specified AGG file(s). h2dmgr - manages the contents of the specified H2D file(s). icn2img - extracts sprites in BMP or PNG format (if supported) and their offsets from the specified ICN file(s). pal2img - generates an image with colors based on a provided palette file. til2img - extracts sprites in BMP or PNG format (if supported) from the specified TIL file(s). xmi2midi - converts the specified XMI file(s) to MIDI format. fheroes2-1.0.12+dfsg/src/tools/bin2txt.cpp000066400000000000000000000231531456075706000202720ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 - 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include // IWYU pragma: keep #include #include #include #include #include #include #include "serialize.h" #include "system.h" #include "tools.h" namespace { constexpr size_t correctBINSize = 821; } int main( int argc, char ** argv ) { if ( argc < 3 ) { std::string baseName = System::GetBasename( argv[0] ); std::cerr << baseName << " extracts various data from monster animation files." << std::endl << "Syntax: " << baseName << " dst_dir input_file.bin ..." << std::endl; return EXIT_FAILURE; } const char * dstDir = argv[1]; std::vector inputFileNames; for ( int i = 2; i < argc; ++i ) { if ( System::isShellLevelGlobbingSupported() ) { inputFileNames.emplace_back( argv[i] ); } else { System::globFiles( argv[i], inputFileNames ); } } std::error_code ec; // Using the non-throwing overloads if ( !std::filesystem::exists( dstDir, ec ) && !std::filesystem::create_directories( dstDir, ec ) ) { std::cerr << "Cannot create directory " << dstDir << std::endl; return EXIT_FAILURE; } uint32_t filesProcessed = 0; for ( const std::string & inputFileName : inputFileNames ) { std::ifstream inputStream( inputFileName, std::ios_base::binary | std::ios_base::ate ); if ( !inputStream ) { std::cerr << "Cannot open file " << inputFileName << std::endl; // A non-existent or inaccessible file is not considered a fatal error continue; } const auto size = fheroes2::checkedCast( static_cast( inputStream.tellg() ) ); if ( !size ) { std::cerr << "File " << inputFileName << " is too large" << std::endl; // Ignore files of invalid size continue; } if ( size == 0U ) { std::cerr << "File " << inputFileName << " is empty" << std::endl; // Ignore files of invalid size continue; } if ( size != correctBINSize ) { // Ignore files of invalid size continue; } std::cout << "Processing " << inputFileName << "..." << std::endl; std::vector buf( size.value() ); inputStream.seekg( 0, std::ios_base::beg ); { const auto streamSize = fheroes2::checkedCast( buf.size() ); if ( !streamSize ) { std::cerr << "File " << inputFileName << " is too large" << std::endl; return EXIT_FAILURE; } inputStream.read( buf.data(), streamSize.value() ); } if ( !inputStream ) { std::cerr << "Error reading from file " << inputFileName << std::endl; return EXIT_FAILURE; } const std::filesystem::path outputFilePath = std::filesystem::path( dstDir ) / std::filesystem::path( inputFileName ).filename().replace_extension( "txt" ); std::ofstream outputStream( outputFilePath, std::ios_base::trunc ); if ( !outputStream ) { std::cerr << "Cannot open file " << outputFilePath << std::endl; return EXIT_FAILURE; } const char * data = buf.data(); outputStream << "Monster eye position: [" << fheroes2::getLEValue( data, 1 ) << ", " << fheroes2::getLEValue( data, 3 ) << "]\n\n"; outputStream << "Animation frame offsets:\n"; for ( size_t setId = 0; setId < 7; ++setId ) { outputStream << setId + 1 << " : "; for ( size_t frameId = 0; frameId < 16; ++frameId ) { const int frameValue = static_cast( data[5 + setId * 16 + frameId] ); if ( frameValue < 10 ) { outputStream << " " << frameValue << " "; } else { outputStream << frameValue << " "; } } outputStream << "\n"; } outputStream << "\n"; constexpr size_t maxIdleAnimationCount = 5; size_t idleAnimationCount = static_cast( *( data + 117 ) ); outputStream << "Number of idle animations is " << idleAnimationCount; if ( idleAnimationCount > maxIdleAnimationCount ) { outputStream << " (INVALID, maximum number should be " << maxIdleAnimationCount << ")"; idleAnimationCount = maxIdleAnimationCount; } outputStream << "\n\n"; outputStream << "Probabilities of each idle animation:\n"; for ( size_t animId = 0; animId < idleAnimationCount; ++animId ) { outputStream << animId + 1 << ": " << fheroes2::getLEValue( data, 118, animId ) << "\n"; } outputStream << "\n"; outputStream << "Idle animation delay (?) (ms): " << fheroes2::getLEValue( data, 138, 0 ) << " " << fheroes2::getLEValue( data, 138, 1 ) << " " << fheroes2::getLEValue( data, 138, 2 ) << " " << fheroes2::getLEValue( data, 138, 3 ) << " " << fheroes2::getLEValue( data, 138, 4 ) << "\n\n"; outputStream << "Idle animation delay (?) (ms): " << fheroes2::getLEValue( data, 158 ) << "\n\n"; outputStream << "Walking animation speed (ms): " << fheroes2::getLEValue( data, 162 ) << "\n\n"; outputStream << "Shooting animation speed (ms): " << fheroes2::getLEValue( data, 166 ) << "\n\n"; outputStream << "Flying animation speed (ms): " << fheroes2::getLEValue( data, 170 ) << "\n\n"; outputStream << "Projectile start positions:\n"; outputStream << "[" << fheroes2::getLEValue( data, 174, 0 ) << ", " << fheroes2::getLEValue( data, 174, 1 ) << "]\n"; outputStream << "[" << fheroes2::getLEValue( data, 174, 2 ) << ", " << fheroes2::getLEValue( data, 174, 3 ) << "]\n"; outputStream << "[" << fheroes2::getLEValue( data, 174, 4 ) << ", " << fheroes2::getLEValue( data, 174, 5 ) << "]\n"; outputStream << "\n"; const int projectileFramesCount = static_cast( *( data + 186 ) ); outputStream << "Number of projectile frames is " << projectileFramesCount << "\n\n"; outputStream << "Projectile angles:\n"; for ( size_t angleId = 0; angleId < 12; ++angleId ) { outputStream << fheroes2::getLEValue( data, 187, angleId ) << "\n"; } outputStream << "\n"; outputStream << "Troop count offset: [" << fheroes2::getLEValue( data, 235 ) << ", " << fheroes2::getLEValue( data, 239 ) << "]\n\n"; constexpr char invalidFrameId = '\xFF'; outputStream << "Animation sequence (frame IDs):\n"; for ( size_t setId = 0; setId < 34; ++setId ) { outputStream << setId + 1 << " : "; int frameCount = 0; for ( size_t frameId = 0; frameId < 16; ++frameId ) { const size_t offset = 277 + setId * 16 + frameId; if ( data[offset] == invalidFrameId ) { break; } const int frameValue = static_cast( data[offset] ); outputStream << frameValue << " "; ++frameCount; } const int expectedFrameCount = static_cast( data[243 + setId] ); if ( frameCount != expectedFrameCount ) { std::cerr << "WARNING: In " << inputFileName << " file number of animation frames for animation " << setId + 1 << " should be " << expectedFrameCount << " while found number is " << frameCount << std::endl; } outputStream << "\n"; } if ( !outputStream ) { std::cerr << "Error writing to file " << outputFilePath << std::endl; return EXIT_FAILURE; } ++filesProcessed; } std::cout << "Total processed files: " << filesProcessed << std::endl; return EXIT_SUCCESS; } fheroes2-1.0.12+dfsg/src/tools/extractor.cpp000066400000000000000000000166171456075706000207220ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include // IWYU pragma: keep #include #include #include #include #include #include #include #include #include #include #include #include #include "serialize.h" #include "system.h" #include "tools.h" namespace { constexpr size_t AGGItemNameLen = 15; struct AGGItemInfo { // Hash of this item's name, see calculateHash() for details uint32_t hash; uint32_t offset; uint32_t size; }; uint32_t calculateHash( const std::string_view str ) { uint32_t hash = 0; int32_t sum = 0; for ( auto iter = str.rbegin(); iter != str.rend(); ++iter ) { const char c = static_cast( std::toupper( static_cast( *iter ) ) ); hash = ( hash << 5 ) + ( hash >> 25 ); sum += c; hash += sum + c; } return hash; } } int main( int argc, char ** argv ) { if ( argc < 3 ) { std::string baseName = System::GetBasename( argv[0] ); std::cerr << baseName << " extracts the contents of the specified AGG file(s)." << std::endl << "Syntax: " << baseName << " dst_dir input_file.agg ..." << std::endl; return EXIT_FAILURE; } const char * dstDir = argv[1]; std::vector inputFileNames; for ( int i = 2; i < argc; ++i ) { if ( System::isShellLevelGlobbingSupported() ) { inputFileNames.emplace_back( argv[i] ); } else { System::globFiles( argv[i], inputFileNames ); } } uint32_t itemsExtracted = 0; uint32_t itemsFailed = 0; for ( const std::string & inputFileName : inputFileNames ) { std::cout << "Processing " << inputFileName << "..." << std::endl; StreamFile inputStream; if ( !inputStream.open( inputFileName, "rb" ) ) { std::cerr << "Cannot open file " << inputFileName << std::endl; // A non-existent or inaccessible file is not considered a fatal error continue; } const std::filesystem::path prefixPath = std::filesystem::path( dstDir ) / std::filesystem::path( inputFileName ).stem(); std::error_code ec; // Using the non-throwing overloads if ( !std::filesystem::exists( prefixPath, ec ) && !std::filesystem::create_directories( prefixPath, ec ) ) { std::cerr << "Cannot create directory " << prefixPath << std::endl; return EXIT_FAILURE; } const size_t inputStreamSize = inputStream.size(); const uint16_t itemsCount = inputStream.getLE16(); StreamBuf itemsStream = inputStream.toStreamBuf( static_cast( itemsCount ) * 4 * 3 /* hash, offset, size */ ); inputStream.seek( inputStreamSize - AGGItemNameLen * itemsCount ); StreamBuf namesStream = inputStream.toStreamBuf( AGGItemNameLen * itemsCount ); std::map> aggItemsMap; for ( uint16_t i = 0; i < itemsCount; ++i ) { AGGItemInfo & info = aggItemsMap[StringLower( namesStream.toString( AGGItemNameLen ) )]; info.hash = itemsStream.getLE32(); info.offset = itemsStream.getLE32(); info.size = itemsStream.getLE32(); } for ( const auto & item : aggItemsMap ) { const auto & [name, info] = item; if ( info.size == 0 ) { ++itemsFailed; std::cerr << inputFileName << ": item " << name << " is empty" << std::endl; continue; } const uint32_t hash = calculateHash( name ); if ( hash != info.hash ) { ++itemsFailed; std::cerr << inputFileName << ": invalid hash for item " << name << ": expected " << GetHexString( info.hash ) << ", got " << GetHexString( hash ) << std::endl; continue; } inputStream.seek( info.offset ); static_assert( std::is_same_v, "uint8_t is not the same as char, check the logic below" ); const std::vector buf = inputStream.getRaw( info.size ); if ( buf.size() != info.size ) { ++itemsFailed; std::cerr << inputFileName << ": item " << name << " has an invalid size of " << info.size << std::endl; continue; } const std::filesystem::path outputFilePath = prefixPath / std::filesystem::path( name ); std::ofstream outputStream( outputFilePath, std::ios_base::binary | std::ios_base::trunc ); if ( !outputStream ) { std::cerr << "Cannot open file " << outputFilePath << std::endl; return EXIT_FAILURE; } { const auto streamSize = fheroes2::checkedCast( buf.size() ); if ( !streamSize ) { std::cerr << inputFileName << ": item " << name << " is too large" << std::endl; return EXIT_FAILURE; } outputStream.write( reinterpret_cast( buf.data() ), streamSize.value() ); } if ( !outputStream ) { std::cerr << "Error writing to file " << outputFilePath << std::endl; return EXIT_FAILURE; } ++itemsExtracted; } } std::cout << "Total extracted items: " << itemsExtracted << ", failed items: " << itemsFailed << std::endl; return ( itemsFailed == 0 ) ? EXIT_SUCCESS : EXIT_FAILURE; } fheroes2-1.0.12+dfsg/src/tools/h2dmgr.cpp000066400000000000000000000311221456075706000200560ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include // IWYU pragma: keep #include #include #include #include #include #include #include #include #include #include "h2d_file.h" #include "image.h" #include "image_palette.h" #include "image_tool.h" #include "serialize.h" #include "system.h" #include "tools.h" namespace { constexpr size_t validPaletteSize = 768; constexpr uint8_t imageBackground = 142; bool isH2DImageItem( const std::string_view name ) { return std::filesystem::path( name ).extension() == ".image"; } bool isImageFile( const std::string_view fileName ) { const std::string extension = StringLower( std::filesystem::path( fileName ).extension().string() ); return ( extension == ".bmp" || extension == ".png" ); } void printUsage( char ** argv ) { std::string baseName = System::GetBasename( argv[0] ); std::cerr << baseName << " manages the contents of the specified H2D file(s)." << std::endl << "Syntax: " << baseName << " extract dst_dir palette_file.pal input_file.h2d ..." << std::endl << " " << baseName << " combine target_file.h2d palette_file.pal input_file ..." << std::endl; } bool loadPalette( const char * paletteFileName ) { StreamFile paletteStream; if ( !paletteStream.open( paletteFileName, "rb" ) ) { std::cerr << "Cannot open file " << paletteFileName << std::endl; return false; } const std::vector palette = paletteStream.getRaw(); if ( palette.size() != validPaletteSize ) { std::cerr << "Invalid palette size of " << palette.size() << " instead of " << validPaletteSize << std::endl; return false; } fheroes2::setGamePalette( palette ); return true; } int extractH2D( const int argc, char ** argv ) { assert( argc >= 5 ); const char * dstDir = argv[2]; if ( !loadPalette( argv[3] ) ) { return EXIT_FAILURE; } std::vector inputFileNames; for ( int i = 4; i < argc; ++i ) { if ( System::isShellLevelGlobbingSupported() ) { inputFileNames.emplace_back( argv[i] ); } else { System::globFiles( argv[i], inputFileNames ); } } uint32_t itemsExtracted = 0; for ( const std::string & inputFileName : inputFileNames ) { std::cout << "Processing " << inputFileName << "..." << std::endl; fheroes2::H2DReader reader; if ( !reader.open( inputFileName ) ) { std::cerr << "Cannot open file " << inputFileName << std::endl; // A non-existent or inaccessible file is not considered a fatal error continue; } const std::filesystem::path prefixPath = std::filesystem::path( dstDir ) / std::filesystem::path( inputFileName ).stem(); std::error_code ec; // Using the non-throwing overloads if ( !std::filesystem::exists( prefixPath, ec ) && !std::filesystem::create_directories( prefixPath, ec ) ) { std::cerr << "Cannot create directory " << prefixPath << std::endl; return EXIT_FAILURE; } for ( const std::string & name : reader.getAllFileNames() ) { // Image items need special processing if ( isH2DImageItem( name ) ) { fheroes2::Sprite image; if ( !fheroes2::readImageFromH2D( reader, name, image ) ) { std::cerr << inputFileName << ": item " << name << " contains an invalid image" << std::endl; return EXIT_FAILURE; } std::string outputFileName = ( prefixPath / std::filesystem::path( name ).stem() ).string(); if ( fheroes2::isPNGFormatSupported() ) { outputFileName += ".png"; } else { outputFileName += ".bmp"; } if ( !fheroes2::Save( image, outputFileName, imageBackground ) ) { std::cerr << inputFileName << ": error saving image " << name << std::endl; return EXIT_FAILURE; } } else { static_assert( std::is_same_v, "uint8_t is not the same as char, check the logic below" ); const std::vector buf = reader.getFile( name ); const std::filesystem::path outputFilePath = prefixPath / std::filesystem::path( name ); std::ofstream outputStream( outputFilePath, std::ios_base::binary | std::ios_base::trunc ); if ( !outputStream ) { std::cerr << "Cannot open file " << outputFilePath << std::endl; return EXIT_FAILURE; } const auto streamSize = fheroes2::checkedCast( buf.size() ); if ( !streamSize ) { std::cerr << inputFileName << ": item " << name << " is too large" << std::endl; return EXIT_FAILURE; } outputStream.write( reinterpret_cast( buf.data() ), streamSize.value() ); if ( !outputStream ) { std::cerr << "Error writing to file " << outputFilePath << std::endl; return EXIT_FAILURE; } } ++itemsExtracted; } } std::cout << "Total extracted items: " << itemsExtracted << std::endl; return EXIT_SUCCESS; } int combineH2D( const int argc, char ** argv ) { assert( argc >= 5 ); const char * h2dFileName = argv[2]; if ( !loadPalette( argv[3] ) ) { return EXIT_FAILURE; } std::vector inputFileNames; for ( int i = 4; i < argc; ++i ) { if ( System::isShellLevelGlobbingSupported() ) { inputFileNames.emplace_back( argv[i] ); } else { System::globFiles( argv[i], inputFileNames ); } } fheroes2::H2DWriter writer; std::error_code ec; // Using the non-throwing overload if ( std::filesystem::exists( h2dFileName, ec ) ) { const std::filesystem::path h2dFileBackupPath = std::filesystem::path( h2dFileName ).replace_extension( "bak" ); // Using the non-throwing overload if ( !std::filesystem::copy_file( h2dFileName, h2dFileBackupPath, std::filesystem::copy_options::overwrite_existing, ec ) ) { std::cerr << "Cannot create backup file " << h2dFileBackupPath << std::endl; return EXIT_FAILURE; } fheroes2::H2DReader reader; if ( !reader.open( h2dFileName ) ) { std::cerr << "Cannot open file " << h2dFileName << std::endl; return EXIT_FAILURE; } if ( !writer.add( reader ) ) { std::cerr << "Error reading from file " << h2dFileName << std::endl; return EXIT_FAILURE; } } uint32_t itemsAdded = 0; for ( const std::string & inputFileName : inputFileNames ) { std::cout << "Processing " << inputFileName << "..." << std::endl; // Image files need special processing if ( isImageFile( inputFileName ) ) { fheroes2::Image image; if ( !fheroes2::Load( inputFileName, image ) ) { std::cerr << "Cannot open file " << inputFileName << std::endl; // A non-existent or inaccessible file is not considered a fatal error continue; } if ( image.empty() ) { std::cerr << "File " << inputFileName << " contains an empty image" << std::endl; return EXIT_FAILURE; } if ( !fheroes2::writeImageToH2D( writer, std::filesystem::path( inputFileName ).filename().replace_extension( "image" ).string(), image ) ) { std::cerr << "Error adding file " << inputFileName << std::endl; return EXIT_FAILURE; } } else { std::ifstream inputStream( inputFileName, std::ios_base::binary | std::ios_base::ate ); if ( !inputStream ) { std::cerr << "Cannot open file " << inputFileName << std::endl; // A non-existent or inaccessible file is not considered a fatal error continue; } const auto size = fheroes2::checkedCast( static_cast( inputStream.tellg() ) ); if ( !size ) { std::cerr << "File " << inputFileName << " is too large" << std::endl; return EXIT_FAILURE; } if ( size == 0U ) { std::cerr << "File " << inputFileName << " is empty" << std::endl; return EXIT_FAILURE; } static_assert( std::is_same_v, "uint8_t is not the same as char, check the logic below" ); std::vector buf( size.value() ); inputStream.seekg( 0, std::ios_base::beg ); const auto streamSize = fheroes2::checkedCast( buf.size() ); if ( !streamSize ) { std::cerr << "File " << inputFileName << " is too large" << std::endl; return EXIT_FAILURE; } inputStream.read( reinterpret_cast( buf.data() ), streamSize.value() ); if ( !inputStream ) { std::cerr << "Error reading from file " << inputFileName << std::endl; return EXIT_FAILURE; } if ( !writer.add( std::filesystem::path( inputFileName ).filename().string(), buf ) ) { std::cerr << "Error adding file " << inputFileName << std::endl; return EXIT_FAILURE; } } ++itemsAdded; } if ( !writer.write( h2dFileName ) ) { std::cerr << "Error writing to file " << h2dFileName << std::endl; return EXIT_FAILURE; } std::cout << "Total added items: " << itemsAdded << std::endl; return EXIT_SUCCESS; } } int main( int argc, char ** argv ) { if ( argc >= 5 && strcmp( argv[1], "extract" ) == 0 ) { return extractH2D( argc, argv ); } if ( argc >= 5 && strcmp( argv[1], "combine" ) == 0 ) { return combineH2D( argc, argv ); } printUsage( argv ); return EXIT_FAILURE; } fheroes2-1.0.12+dfsg/src/tools/icn2img.cpp000066400000000000000000000165451456075706000202370ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include // IWYU pragma: keep #include #include #include #include #include #include #include #include "agg_file.h" #include "image.h" #include "image_palette.h" #include "image_tool.h" #include "serialize.h" #include "system.h" namespace { constexpr size_t validPaletteSize = 768; constexpr uint8_t spriteBackground = 23; } int main( int argc, char ** argv ) { if ( argc < 4 ) { std::string baseName = System::GetBasename( argv[0] ); std::cerr << baseName << " extracts sprites in BMP or PNG format (if supported) and their offsets from the specified ICN file(s) using the specified palette." << std::endl << "Syntax: " << baseName << " dst_dir palette_file.pal input_file.icn ..." << std::endl; return EXIT_FAILURE; } const char * dstDir = argv[1]; const char * paletteFileName = argv[2]; { StreamFile paletteStream; if ( !paletteStream.open( paletteFileName, "rb" ) ) { std::cerr << "Cannot open file " << paletteFileName << std::endl; return EXIT_FAILURE; } const std::vector palette = paletteStream.getRaw(); if ( palette.size() != validPaletteSize ) { std::cerr << "Invalid palette size of " << palette.size() << " instead of " << validPaletteSize << std::endl; return EXIT_FAILURE; } fheroes2::setGamePalette( palette ); } std::vector inputFileNames; for ( int i = 3; i < argc; ++i ) { if ( System::isShellLevelGlobbingSupported() ) { inputFileNames.emplace_back( argv[i] ); } else { System::globFiles( argv[i], inputFileNames ); } } uint32_t spritesExtracted = 0; uint32_t spritesFailed = 0; for ( const std::string & inputFileName : inputFileNames ) { std::cout << "Processing " << inputFileName << "..." << std::endl; StreamFile inputStream; if ( !inputStream.open( inputFileName, "rb" ) ) { std::cerr << "Cannot open file " << inputFileName << std::endl; // A non-existent or inaccessible file is not considered a fatal error continue; } const std::filesystem::path prefixPath = std::filesystem::path( dstDir ) / std::filesystem::path( inputFileName ).stem(); std::error_code ec; // Using the non-throwing overloads if ( !std::filesystem::exists( prefixPath, ec ) && !std::filesystem::create_directories( prefixPath, ec ) ) { std::cerr << "Cannot create directory " << prefixPath << std::endl; return EXIT_FAILURE; } const std::filesystem::path offsetFilePath = prefixPath / "offsets.txt"; std::ofstream offsetStream( offsetFilePath, std::ios_base::trunc ); if ( !offsetStream ) { std::cerr << "Cannot create file " << offsetFilePath << std::endl; return EXIT_FAILURE; } const uint16_t spritesCount = inputStream.getLE16(); const uint32_t totalSize = inputStream.getLE32(); const size_t beginPos = inputStream.tell(); std::vector headers( spritesCount ); for ( fheroes2::ICNHeader & header : headers ) { inputStream >> header; } for ( uint16_t spriteIdx = 0; spriteIdx < spritesCount; ++spriteIdx ) { const fheroes2::ICNHeader & header = headers[spriteIdx]; inputStream.seek( beginPos + header.offsetData ); const uint32_t dataSize = ( spriteIdx + 1 < spritesCount ? headers[spriteIdx + 1].offsetData - header.offsetData : totalSize - header.offsetData ); if ( dataSize == 0 ) { ++spritesFailed; std::cerr << inputFileName << ": sprite " << spriteIdx << " is empty" << std::endl; continue; } const std::vector buf = inputStream.getRaw( dataSize ); if ( buf.size() != dataSize ) { ++spritesFailed; std::cerr << inputFileName << ": invalid size of sprite " << spriteIdx << ": expected " << dataSize << ", got " << buf.size() << std::endl; continue; } const fheroes2::Sprite sprite = fheroes2::decodeICNSprite( buf.data(), dataSize, header.width, header.height, header.offsetX, header.offsetY ); std::ostringstream spriteIdxStream; spriteIdxStream << std::setw( 3 ) << std::setfill( '0' ) << spriteIdx; const std::string spriteIdxStr = spriteIdxStream.str(); std::string outputFileName = ( prefixPath / spriteIdxStr ).string(); if ( fheroes2::isPNGFormatSupported() ) { outputFileName += ".png"; } else { outputFileName += ".bmp"; } offsetStream << spriteIdxStr << " [" << header.offsetX << ", " << header.offsetY << "]" << std::endl; if ( !offsetStream ) { std::cerr << "Error writing to file " << offsetFilePath << std::endl; return EXIT_FAILURE; } if ( !fheroes2::Save( sprite, outputFileName, spriteBackground ) ) { ++spritesFailed; std::cerr << inputFileName << ": error saving sprite " << spriteIdx << std::endl; continue; } ++spritesExtracted; } } std::cout << "Total extracted sprites: " << spritesExtracted << ", failed sprites: " << spritesFailed << std::endl; return ( spritesFailed == 0 ) ? EXIT_SUCCESS : EXIT_FAILURE; } fheroes2-1.0.12+dfsg/src/tools/pal2img.cpp000066400000000000000000000070701456075706000202330ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2023 * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include "image.h" #include "image_palette.h" #include "image_tool.h" #include "serialize.h" #include "system.h" namespace { constexpr size_t validPaletteSize = 768; } int main( int argc, char ** argv ) { if ( argc != 3 ) { std::string baseName = System::GetBasename( argv[0] ); std::cerr << baseName << " generates an image with colors based on a provided palette file." << std::endl << "Syntax: " << baseName << " palette_file.pal output.bmp|output.png" << std::endl; return EXIT_FAILURE; } const char * paletteFileName = argv[1]; const char * outputFileName = argv[2]; { StreamFile paletteStream; if ( !paletteStream.open( paletteFileName, "rb" ) ) { std::cerr << "Cannot open file " << paletteFileName << std::endl; return EXIT_FAILURE; } const std::vector palette = paletteStream.getRaw(); if ( palette.size() != validPaletteSize ) { std::cerr << "Invalid palette size of " << palette.size() << " instead of " << validPaletteSize << std::endl; return EXIT_FAILURE; } fheroes2::setGamePalette( palette ); } fheroes2::Image image( 256, 256 ); image.reset(); // We do not need to care about the transform layer. image._disableTransformLayer(); // These color indexes are from PAL::GetCyclingPalette() method. const std::set cyclingColors{ 214, 215, 216, 217, 218, 219, 220, 221, 231, 232, 233, 234, 235, 238, 239, 240, 241 }; for ( uint8_t y = 0; y < 16; ++y ) { for ( uint8_t x = 0; x < 16; ++x ) { fheroes2::Fill( image, x * 16, y * 16, 16, 16, x + y * 16 ); if ( cyclingColors.count( x + y * 16 ) > 0 ) { fheroes2::Fill( image, x * 16, y * 16, 4, 4, 0 ); } } } if ( !fheroes2::Save( image, outputFileName ) ) { std::cerr << "Error writing to file " << outputFileName << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; } fheroes2-1.0.12+dfsg/src/tools/til2img.cpp000066400000000000000000000147011456075706000202460ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include "image.h" #include "image_palette.h" #include "image_tool.h" #include "serialize.h" #include "system.h" namespace { constexpr size_t validPaletteSize = 768; constexpr uint8_t spriteBackground = 0; } int main( int argc, char ** argv ) { if ( argc < 4 ) { std::string baseName = System::GetBasename( argv[0] ); std::cerr << baseName << " extracts sprites in BMP or PNG format (if supported) from the specified TIL file(s) using the specified palette." << std::endl << "Syntax: " << baseName << " dst_dir palette_file.pal input_file.til ..." << std::endl; return EXIT_FAILURE; } const char * dstDir = argv[1]; const char * paletteFileName = argv[2]; { StreamFile paletteStream; if ( !paletteStream.open( paletteFileName, "rb" ) ) { std::cerr << "Cannot open file " << paletteFileName << std::endl; return EXIT_FAILURE; } const std::vector palette = paletteStream.getRaw(); if ( palette.size() != validPaletteSize ) { std::cerr << "Invalid palette size of " << palette.size() << " instead of " << validPaletteSize << std::endl; return EXIT_FAILURE; } fheroes2::setGamePalette( palette ); } std::vector inputFileNames; for ( int i = 3; i < argc; ++i ) { if ( System::isShellLevelGlobbingSupported() ) { inputFileNames.emplace_back( argv[i] ); } else { System::globFiles( argv[i], inputFileNames ); } } uint32_t spritesExtracted = 0; for ( const std::string & inputFileName : inputFileNames ) { std::cout << "Processing " << inputFileName << "..." << std::endl; StreamFile inputStream; if ( !inputStream.open( inputFileName, "rb" ) ) { std::cerr << "Cannot open file " << inputFileName << std::endl; // A non-existent or inaccessible file is not considered a fatal error continue; } const std::filesystem::path prefixPath = std::filesystem::path( dstDir ) / std::filesystem::path( inputFileName ).stem(); std::error_code ec; // Using the non-throwing overloads if ( !std::filesystem::exists( prefixPath, ec ) && !std::filesystem::create_directories( prefixPath, ec ) ) { std::cerr << "Cannot create directory " << prefixPath << std::endl; return EXIT_FAILURE; } const uint16_t spritesCount = inputStream.getLE16(); if ( spritesCount == 0 ) { std::cerr << inputFileName << ": no sprites found" << std::endl; return EXIT_FAILURE; } const uint16_t spriteWidth = inputStream.getLE16(); const uint16_t spriteHeight = inputStream.getLE16(); const size_t spriteSize = static_cast( spriteWidth ) * spriteHeight; if ( spriteSize == 0 ) { std::cerr << inputFileName << ": invalid sprite size " << spriteWidth << "x" << spriteHeight << std::endl; return EXIT_FAILURE; } const std::vector buf = inputStream.getRaw( spriteSize * spritesCount ); if ( buf.size() != spriteSize * spritesCount ) { std::cerr << inputFileName << ": failed to extract sprites" << std::endl; return EXIT_FAILURE; } std::vector sprites; sprites.reserve( spritesCount ); fheroes2::decodeTILImages( buf.data(), spritesCount, spriteWidth, spriteHeight, sprites ); if ( sprites.size() != spritesCount ) { std::cerr << inputFileName << ": failed to extract sprites" << std::endl; return EXIT_FAILURE; } for ( size_t spriteIdx = 0; spriteIdx < sprites.size(); ++spriteIdx ) { std::ostringstream spriteIdxStream; spriteIdxStream << std::setw( 3 ) << std::setfill( '0' ) << spriteIdx; const std::string spriteIdxStr = spriteIdxStream.str(); std::string outputFileName = ( prefixPath / spriteIdxStr ).string(); if ( fheroes2::isPNGFormatSupported() ) { outputFileName += ".png"; } else { outputFileName += ".bmp"; } if ( !fheroes2::Save( sprites[spriteIdx], outputFileName, spriteBackground ) ) { std::cerr << inputFileName << ": error saving sprite " << spriteIdx << std::endl; return EXIT_FAILURE; } ++spritesExtracted; } } std::cout << "Total extracted sprites: " << spritesExtracted << std::endl; return EXIT_SUCCESS; } fheroes2-1.0.12+dfsg/src/tools/xmi2midi.cpp000066400000000000000000000134531456075706000204240ustar00rootroot00000000000000/*************************************************************************** * fheroes2: https://github.com/ihhub/fheroes2 * * Copyright (C) 2022 - 2023 * * * * Free Heroes2 Engine: http://sourceforge.net/projects/fheroes2 * * Copyright (C) 2009 by Andrey Afletdinov * * * * 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., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include #include #include #include #include // IWYU pragma: keep #include #include #include #include #include #include #include #include "audio.h" #include "system.h" #include "tools.h" int main( int argc, char ** argv ) { if ( argc < 3 ) { std::string baseName = System::GetBasename( argv[0] ); std::cerr << baseName << " converts the specified XMI file(s) to MIDI format." << std::endl << "Syntax: " << baseName << " dst_dir input_file.xmi ..." << std::endl; return EXIT_FAILURE; } const char * dstDir = argv[1]; std::vector inputFileNames; for ( int i = 2; i < argc; ++i ) { if ( System::isShellLevelGlobbingSupported() ) { inputFileNames.emplace_back( argv[i] ); } else { System::globFiles( argv[i], inputFileNames ); } } std::error_code ec; // Using the non-throwing overloads if ( !std::filesystem::exists( dstDir, ec ) && !std::filesystem::create_directories( dstDir, ec ) ) { std::cerr << "Cannot create directory " << dstDir << std::endl; return EXIT_FAILURE; } uint32_t tracksConverted = 0; for ( const std::string & inputFileName : inputFileNames ) { std::cout << "Processing " << inputFileName << "..." << std::endl; std::ifstream inputStream( inputFileName, std::ios_base::binary | std::ios_base::ate ); if ( !inputStream ) { std::cerr << "Cannot open file " << inputFileName << std::endl; // A non-existent or inaccessible file is not considered a fatal error continue; } const auto size = fheroes2::checkedCast( static_cast( inputStream.tellg() ) ); if ( !size ) { std::cerr << "File " << inputFileName << " is too large" << std::endl; return EXIT_FAILURE; } if ( size == 0U ) { std::cerr << "File " << inputFileName << " is empty" << std::endl; return EXIT_FAILURE; } static_assert( std::is_same_v, "uint8_t is not the same as char, check the logic below" ); std::vector buf( size.value() ); inputStream.seekg( 0, std::ios_base::beg ); { const auto streamSize = fheroes2::checkedCast( buf.size() ); if ( !streamSize ) { std::cerr << "File " << inputFileName << " is too large" << std::endl; return EXIT_FAILURE; } inputStream.read( reinterpret_cast( buf.data() ), streamSize.value() ); } if ( !inputStream ) { std::cerr << "Error reading from file " << inputFileName << std::endl; return EXIT_FAILURE; } buf = Music::Xmi2Mid( buf ); if ( buf.empty() ) { std::cerr << "Failed to convert file " << inputFileName << std::endl; return EXIT_FAILURE; } const std::filesystem::path outputFilePath = std::filesystem::path( dstDir ) / std::filesystem::path( inputFileName ).filename().replace_extension( "mid" ); std::ofstream outputStream( outputFilePath, std::ios_base::binary | std::ios_base::trunc ); if ( !outputStream ) { std::cerr << "Cannot open file " << outputFilePath << std::endl; return EXIT_FAILURE; } { const auto streamSize = fheroes2::checkedCast( buf.size() ); if ( !streamSize ) { std::cerr << inputFileName << ": resulting MIDI is too large" << std::endl; return EXIT_FAILURE; } outputStream.write( reinterpret_cast( buf.data() ), streamSize.value() ); } if ( !outputStream ) { std::cerr << "Error writing to file " << outputFilePath << std::endl; return EXIT_FAILURE; } ++tracksConverted; } std::cout << "Total converted tracks: " << tracksConverted << std::endl; return EXIT_SUCCESS; } fheroes2-1.0.12+dfsg/version.txt000066400000000000000000000000071456075706000164640ustar00rootroot000000000000001.0.12 fheroes2-1.0.12+dfsg/version_code.txt000066400000000000000000000000031456075706000174520ustar00rootroot0000000000000015